[
  {
    "path": ".gitattributes",
    "content": "* text=auto\n*.bat text eol=crlf\n*.cmd text eol=crlf"
  },
  {
    "path": ".github/codeql/codeql-config.yml",
    "content": "name: \"Aeron CodeQL Scanning\"\n\nqueries:\n  - uses: security-and-quality\n\nquery-filters:\n  - exclude:\n      id: java/missing-override-annotation\n  - exclude:\n      id: cpp/poorly-documented-function\n  - exclude:\n      id: cpp/unused-static-variable\n  - exclude:\n      id: java/unused-reference-type\n  - exclude:\n      id: java/unused-parameter\n  - exclude:\n      id: cpp/trivial-switch\n  - exclude:\n      id: cpp/long-switch\n  - exclude:\n      id: cpp/integer-used-for-enum\n  - exclude:\n      id: cpp/stack-address-escape\n"
  },
  {
    "path": ".github/workflows/ci-low-cadence.yml",
    "content": "name: Continuous Integration (Low Cadence)\n\non:\n  workflow_call:\n  workflow_dispatch:\n  repository_dispatch:\n    types: run-slow-tests\n  schedule:\n    - cron: '0 0,12 * * *'\n  push:\n    branches:\n      - master\n      - 'release/**'\n  pull_request:\n    types: [opened, synchronize]\n    branches:\n      - master\n      - 'release/**'\n\nconcurrency:\n  group: ci-low-cadence-${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ github.event_name != 'schedule' }}\n\nenv:\n  GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.java.installations.auto-detect=false -Dorg.gradle.warning.mode=fail'\n  INSTALL_COMPILER_RETRIES: '5'\n  JAVA_VERSION: '17'\n\npermissions:\n  contents: read\n\njobs:\n  java-slow-tests:\n    name: Java Slow Tests (JDK ${{ matrix.java }}, ${{ matrix.os }})\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 120\n    strategy:\n      fail-fast: false\n      matrix:\n        java: [ '17', '21', '25' ]\n        os: ['ubuntu-24.04', 'windows-latest', 'macos-15']\n    steps:\n      - name: Use faster temporary storage (Windows)\n        if: runner.os == 'Windows'\n        run: |\n          echo \"TMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n          echo \"TEMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n      - name: Disable CPU hogs (Windows)\n        if: runner.os == 'Windows'\n        run: |\n          Set-MpPreference -DisableRealtimeMonitoring $true -Force\n          Add-MpPreference -ExclusionPath \"C:\\\" -Force\n          Add-MpPreference -ExclusionPath \"D:\\\" -Force\n          sc config wuauserv start= disabled\n          Stop-Service wuauserv -ErrorAction SilentlyContinue\n          reg add \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\wsl.exe\" /v Debugger /t REG_SZ /d \"block.exe\" /f\n          reg add \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\CompatTelRunner.exe\" /v Debugger /t REG_SZ /d \"block.exe\" /f\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ matrix.java }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION (Linux/MacOS)\n        if: runner.os == 'Linux' || runner.os == 'macOS'\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ matrix.java }}\" >> $GITHUB_ENV\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION (Windows)\n        if: runner.os == 'Windows'\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=$env:JAVA_HOME\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n          echo \"BUILD_JAVA_VERSION=${{ matrix.java }}\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n      - name: Setup localhost name (MacOS)\n        if: runner.os == 'macOS'\n        run: |\n          sudo sed -i bak \"s/localhost/localhost $(hostname)/\" /etc/hosts\n          dscacheutil -flushcache\n      - name: Setup java to run Gradle\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Enable core dumps (Linux)\n        if: runner.os == 'Linux'\n        run: |\n          ulimit -c unlimited\n          sudo systemctl stop apport.service || true\n          sudo systemctl disable apport.service || true\n          sudo mkdir -p /var/coredump\n          sudo chmod a+rw /var/coredump\n          sudo sysctl -w kernel.core_pattern=\"/var/coredump/core_%e.%p\"\n      - name: Enable core dumps (MacOS)\n        if: runner.os == 'macOS'\n        run: |\n          ulimit -c unlimited\n          sudo mkdir -p /var/coredump\n          sudo chmod a+rw /var/coredump\n          sudo sysctl -w kern.coredump=1\n          sudo sysctl -w kern.corefile=\"/var/coredump/core.%P\"\n      - name: Build and Run Slow Tests with Gradle\n        run: ./gradlew slowTest\n        env:\n          BUILD_JAVA_VERSION: ${{ matrix.java }}\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          ${{ runner.os == 'Windows' && 'echo \"file=build/distributions/test_logs.tbz2\" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append' || 'echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT' }}\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-${{ matrix.os }}-java-${{ matrix.java }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  java-javadoc:\n    name: JavaDoc (JDK ${{ matrix.java }}, ${{ matrix.os }})\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 120\n    strategy:\n      fail-fast: false\n      matrix:\n        java: [ '17', '21', '25' ]\n        os: [ 'ubuntu-24.04' ]\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ matrix.java }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ matrix.java }}\" >> $GITHUB_ENV\n      - name: Setup java to run Gradle\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Build and Run Javadoc\n        run: ./gradlew javadoc\n        env:\n          BUILD_JAVA_VERSION: ${{ matrix.java }}\n\n  cpp-slow-tests-gcc:\n    name: C++ Slow System Tests GCC ${{ matrix.version }}\n    runs-on: ubuntu-24.04\n    timeout-minutes: 120\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [ '14' ]\n    env:\n      CC: gcc-${{ matrix.version }}\n      CXX: g++-${{ matrix.version }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Install compiler\n        run: |\n          echo 'Acquire::Retries \"${INSTALL_COMPILER_RETRIES}\";' | sudo tee -a /etc/apt/apt.conf.d/99retries\n          sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test\n          sudo apt-get update\n          sudo apt-get install -y g++-${{ matrix.version }} libbsd-dev uuid-dev\n      - name: Build\n        run: cppbuild/cppbuild --c-warnings-as-errors --cxx-warnings-as-errors --slow-system-tests --no-system-tests --no-unit-tests --cxx-hide-deprecation-message\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-cpp-slow-tests-gcc-${{ matrix.version }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-slow-tests-clang:\n    name: C++ Slow System Tests Clang ${{ matrix.version }}\n    runs-on: ubuntu-24.04\n    timeout-minutes: 120\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [ '22' ]\n    env:\n      CC: clang-${{ matrix.version }}\n      CXX: clang++-${{ matrix.version }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Install compiler\n        run: |\n          echo 'Acquire::Retries \"${INSTALL_COMPILER_RETRIES}\";' | sudo tee -a /etc/apt/apt.conf.d/99retries\n          sudo mkdir -p /etc/apt/keyrings/\n          curl --retry ${INSTALL_COMPILER_RETRIES} https://apt.llvm.org/llvm-snapshot.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/llvm-snapshot.gpg\n          echo \"deb [signed-by=/etc/apt/keyrings/llvm-snapshot.gpg] https://apt.llvm.org/noble/ llvm-toolchain-noble-${{ matrix.version }} main\" | sudo tee /etc/apt/sources.list.d/llvm.list\n          sudo apt-get update\n          sudo apt-get install -y clang-${{ matrix.version }} libbsd-dev uuid-dev\n      - name: Build\n        run: cppbuild/cppbuild --c-warnings-as-errors --cxx-warnings-as-errors --slow-system-tests --no-system-tests --no-unit-tests --cxx-hide-deprecation-message\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name:  crash-logs-cpp-slow-tests-clang-${{ matrix.version }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-slow-tests-msvc:\n    name: C++ Slow System Tests MSVC\n    runs-on: windows-latest\n    timeout-minutes: 120\n    env:\n      CC: cl\n      CXX: cl\n    steps:\n      - name: Use faster temporary storage (Windows)\n        run: |\n          echo \"TMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n          echo \"TEMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n      - name: Disable CPU hogs (Windows)\n        run: |\n          Set-MpPreference -DisableRealtimeMonitoring $true -Force\n          Add-MpPreference -ExclusionPath \"C:\\\" -Force\n          Add-MpPreference -ExclusionPath \"D:\\\" -Force\n          sc config wuauserv start= disabled\n          Stop-Service wuauserv -ErrorAction SilentlyContinue\n          reg add \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\wsl.exe\" /v Debugger /t REG_SZ /d \"block.exe\" /f\n          reg add \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\CompatTelRunner.exe\" /v Debugger /t REG_SZ /d \"block.exe\" /f\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=$env:JAVA_HOME\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Build\n        run: cppbuild/cppbuild.ps1 --slow-system-tests --no-system-tests --no-unit-tests --c-warnings-as-errors --cxx-warnings-as-errors --cxx-hide-deprecation-message\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" | Out-File $env:GITHUB_OUTPUT -Encoding utf8 -Append\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-cpp-slow-tests-msvc-latest\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-sanitize-gcc:\n    name: C++ Sanitise Build GCC ${{ matrix.version }}\n    runs-on: ubuntu-24.04\n    timeout-minutes: 120\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [ '14' ]\n    env:\n      CC: gcc-${{ matrix.version }}\n      CXX: g++-${{ matrix.version }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Install compiler\n        run: |\n          echo 'Acquire::Retries \"${INSTALL_COMPILER_RETRIES}\";' | sudo tee -a /etc/apt/apt.conf.d/99retries\n          sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test\n          sudo apt-get update\n          sudo apt-get install -y g++-${{ matrix.version }} libbsd-dev uuid-dev\n      - name: Build\n        run: cppbuild/cppbuild --relwithdebinfo-build --sanitise-build --c-warnings-as-errors --cxx-warnings-as-errors --slow-system-tests --cxx-hide-deprecation-message\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-cpp-sanitize-gcc-${{ matrix.version }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-sanitize-clang:\n    name: C++ Sanitise Build Clang ${{ matrix.version }}\n    runs-on: ubuntu-24.04\n    timeout-minutes: 120\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [ '22' ]\n    env:\n      CC: clang-${{ matrix.version }}\n      CXX: clang++-${{ matrix.version }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Install compiler\n        run: |\n          echo 'Acquire::Retries \"${INSTALL_COMPILER_RETRIES}\";' | sudo tee -a /etc/apt/apt.conf.d/99retries\n          sudo mkdir -p /etc/apt/keyrings/\n          curl --retry ${INSTALL_COMPILER_RETRIES} https://apt.llvm.org/llvm-snapshot.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/llvm-snapshot.gpg\n          echo \"deb [signed-by=/etc/apt/keyrings/llvm-snapshot.gpg] https://apt.llvm.org/noble/ llvm-toolchain-noble-${{ matrix.version }} main\" | sudo tee /etc/apt/sources.list.d/llvm.list\n          sudo apt-get update\n          sudo apt-get install -y clang-${{ matrix.version }} libbsd-dev uuid-dev\n      - name: Build\n        run: cppbuild/cppbuild --relwithdebinfo-build --sanitise-build --c-warnings-as-errors --cxx-warnings-as-errors --slow-system-tests --cxx-hide-deprecation-message\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-cpp-sanitize-clang-${{ matrix.version }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Continuous Integration\n\non:\n  workflow_call:\n  workflow_dispatch:\n  repository_dispatch:\n    types: run-commit-tests\n  push:\n    branches:\n      - master\n      - 'release/**'\n  pull_request:\n    types: [opened, synchronize]\n    branches:\n      - master\n      - 'release/**'\n\nconcurrency:\n  group: ci-${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\nenv:\n  GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.java.installations.auto-detect=false -Dorg.gradle.warning.mode=fail'\n  INSTALL_COMPILER_RETRIES: '5'\n  JAVA_VERSION: '17'\n\npermissions:\n  contents: read\n\njobs:\n  java-build:\n    name: Java ${{ matrix.java }} (${{ matrix.os }})\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 60\n    strategy:\n      fail-fast: false\n      matrix:\n        java: [ '17', '21', '25' ]\n        os: ['ubuntu-24.04', 'windows-latest', 'macos-15']\n    steps:\n      - name: Use faster temporary storage (Windows)\n        if: runner.os == 'Windows'\n        run: |\n          echo \"TMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n          echo \"TEMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n      - name: Disable CPU hogs (Windows)\n        if: runner.os == 'Windows'\n        run: |\n          Set-MpPreference -DisableRealtimeMonitoring $true -Force\n          Add-MpPreference -ExclusionPath \"C:\\\" -Force\n          Add-MpPreference -ExclusionPath \"D:\\\" -Force\n          sc config wuauserv start= disabled\n          Stop-Service wuauserv -ErrorAction SilentlyContinue\n          reg add \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\wsl.exe\" /v Debugger /t REG_SZ /d \"block.exe\" /f\n          reg add \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\CompatTelRunner.exe\" /v Debugger /t REG_SZ /d \"block.exe\" /f\n      - name: Setup small temp file system (Linux)\n        if: runner.os == 'Linux'\n        run: |\n          sudo mkdir -p /mnt/tmp_aeron_dir\n          sudo mount -t tmpfs -o size=50M,mode=777 tmpfs /mnt/tmp_aeron_dir\n      - name: Setup small temp file system and localhost name (MacOS)\n        if: runner.os == 'macOS'\n        run: |\n          sudo sed -i bak \"s/localhost/localhost $(hostname)/\" /etc/hosts\n          dscacheutil -flushcache\n          sudo diskutil eraseDisk APFS tmp_aeron_dir $(hdiutil attach -nomount ram://$((2 * 1024 * 60)))\n      - name: Setup small temp file system (Windows)\n        if: runner.os == 'Windows'\n        run: |\n          $current_size = (Get-PartitionSupportedSize -DiskNumber 1 -PartitionNumber 1)\n          $new_size = ($current_size.SizeMax - 100 * 1024 * 1024)\n          Resize-Partition -DiskNumber 1 -PartitionNumber 1 -Size ($new_size)\n          New-Partition -DiskNumber 1 -Size 60MB -DriveLetter T | Format-Volume -FileSystem NTFS -Confirm:$false\n          New-Item -ItemType Directory -Path T:\\tmp_aeron_dir\n      - name: Enable core dumps (Linux)\n        if: runner.os == 'Linux'\n        run: |\n          ulimit -c unlimited\n          sudo systemctl stop apport.service || true\n          sudo systemctl disable apport.service || true\n          sudo mkdir -p /var/coredump\n          sudo chmod a+rw /var/coredump\n          sudo sysctl -w kernel.core_pattern=\"/var/coredump/core_%e.%p\"\n      - name: Enable core dumps (MacOS)\n        if: runner.os == 'macOS'\n        run: |\n          ulimit -c unlimited\n          sudo mkdir -p /var/coredump\n          sudo chmod a+rw /var/coredump\n          sudo sysctl -w kern.coredump=1\n          sudo sysctl -w kern.corefile=\"/var/coredump/core.%P\"\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ matrix.java }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION (Unix)\n        if: runner.os != 'Windows'\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ matrix.java }}\" >> $GITHUB_ENV\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION (Windows)\n        if: runner.os == 'Windows'\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=$env:JAVA_HOME\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n          echo \"BUILD_JAVA_VERSION=${{ matrix.java }}\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n      - name: Setup java to run Gradle\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Build with Gradle\n        run: ./gradlew -x javadoc --console=plain\n      - name: Remove small temp file system (Linux)\n        if: always() && runner.os == 'Linux'\n        run: |\n          sudo umount /mnt/tmp_aeron_dir\n          sudo rm -rf /mnt/tmp_aeron_dir\n      - name: Remove small temp file system (MacOS)\n        if: always() && runner.os == 'macOS'\n        run: sudo diskutil eject tmp_aeron_dir\n      - name: Remove small temp file system (Windows)\n        if: always() && runner.os == 'Windows'\n        run: |\n          if (Test-Path \"T:\\tmp_aeron_dir\")\n          {\n            Remove-Partition -DriveLetter T -Confirm:$false\n          }\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          ${{ runner.os == 'Windows' && 'echo \"file=build/distributions/test_logs.tbz2\" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append' || 'echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT' }}\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-${{ matrix.os }}-java-${{ matrix.java }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  java-topology-build:\n    name: Java Topology Tests (JDK ${{ matrix.java }}, ${{ matrix.os }})\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 60\n    strategy:\n      fail-fast: false\n      matrix:\n        java: [ '17' ]\n        os: [ 'ubuntu-24.04' ]\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ matrix.java }}\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ matrix.java }}\" >> $GITHUB_ENV\n      - name: Setup virtual network interfaces\n        run: ./aeron-samples/scripts/cluster/setup-namespaces\n      - name: Build aeron with Gradle\n        run: ./gradlew clean assemble -x javadoc\n      - name: Run execution agents\n        working-directory: ./aeron-samples/scripts/cluster\n        run: ./agent-ns\n      - name: Get network information\n        run:  |\n          sudo sysctl -w net.ipv4.ip_forward=1\n          sudo iptables -F FORWARD\n          sudo iptables -P FORWARD ACCEPT\n      - name: Run topology tests with Gradle\n        run: ./gradlew topologyTest\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          ${{ runner.os == 'Windows' && 'echo \"file=build/distributions/test_logs.tbz2\" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append' || 'echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT' }}\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-topology-${{ matrix.os }}-java-${{ matrix.java }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-gcc-2404-build:\n    name: C++ GCC ${{ matrix.version }} (Ubuntu 24.04)\n    runs-on: ubuntu-24.04\n    timeout-minutes: 60\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [ '11', '12', '13', '14' ]\n    env:\n      CC: gcc-${{ matrix.version }}\n      CXX: g++-${{ matrix.version }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Install compiler\n        run: |\n          echo 'Acquire::Retries \"${INSTALL_COMPILER_RETRIES}\";' | sudo tee -a /etc/apt/apt.conf.d/99retries\n          sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test\n          sudo apt-get update\n          sudo apt-get install -y g++-${{ matrix.version }} libbsd-dev uuid-dev\n      - name: Setup small temp file system\n        run: |\n          sudo mkdir -p /mnt/tmp_aeron_dir\n          sudo mount -t tmpfs -o size=50M,mode=777 tmpfs /mnt/tmp_aeron_dir\n      - name: Build\n        run: cppbuild/cppbuild --c-warnings-as-errors --cxx-warnings-as-errors --cxx-hide-deprecation-message\n      - name: Remove small temp file system\n        if: always()\n        run: |\n          sudo umount /mnt/tmp_aeron_dir\n          sudo rm -rf /mnt/tmp_aeron_dir\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-gcc-2404-${{ matrix.version }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-gcc-rocky-build:\n    name: C++ Rocky 9\n    runs-on: ubuntu-24.04\n    timeout-minutes: 60\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [ '13' ]\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Build\n        run: GCC_VERSION=${{ matrix.version }} cppbuild/rocky-docker-build\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-gcc-rhel-${{ matrix.version }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-clang-ubuntu-2404-build:\n    name: C++ Clang ${{ matrix.version }} (Ubuntu 24.04)\n    runs-on: ubuntu-24.04\n    timeout-minutes: 60\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [ '17', '18', '19', '20', '21', '22' ]\n    env:\n      CC: clang-${{ matrix.version }}\n      CXX: clang++-${{ matrix.version }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Install compiler\n        run: |\n          echo 'Acquire::Retries \"${INSTALL_COMPILER_RETRIES}\";' | sudo tee -a /etc/apt/apt.conf.d/99retries\n          sudo mkdir -p /etc/apt/keyrings/\n          curl --retry ${INSTALL_COMPILER_RETRIES} https://apt.llvm.org/llvm-snapshot.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/llvm-snapshot.gpg\n          echo \"deb [signed-by=/etc/apt/keyrings/llvm-snapshot.gpg] https://apt.llvm.org/noble/ llvm-toolchain-noble-${{ matrix.version }} main\" | sudo tee /etc/apt/sources.list.d/llvm.list\n          sudo apt-get update\n          sudo apt-get install -y clang-${{ matrix.version }} libbsd-dev uuid-dev\n      - name: Setup small temp file system\n        run: |\n          sudo mkdir -p /mnt/tmp_aeron_dir\n          sudo mount -t tmpfs -o size=50M,mode=777 tmpfs /mnt/tmp_aeron_dir\n      - name: Build\n        run: cppbuild/cppbuild --c-warnings-as-errors --cxx-warnings-as-errors --cxx-hide-deprecation-message\n      - name: Remove small temp file system\n        if: always()\n        run: |\n          sudo umount /mnt/tmp_aeron_dir\n          sudo rm -rf /mnt/tmp_aeron_dir\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-clang-2404-${{ matrix.version }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-clang-debug-build:\n    name: C++ Clang ${{ matrix.version }} debug build\n    runs-on: ubuntu-24.04\n    timeout-minutes: 60\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [ '22' ]\n    env:\n      CC: clang-${{ matrix.version }}\n      CXX: clang++-${{ matrix.version }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Install compiler\n        run: |\n          echo 'Acquire::Retries \"${INSTALL_COMPILER_RETRIES}\";' | sudo tee -a /etc/apt/apt.conf.d/99retries\n          sudo mkdir -p /etc/apt/keyrings/\n          curl --retry ${INSTALL_COMPILER_RETRIES} https://apt.llvm.org/llvm-snapshot.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/llvm-snapshot.gpg\n          echo \"deb [signed-by=/etc/apt/keyrings/llvm-snapshot.gpg] https://apt.llvm.org/noble/ llvm-toolchain-noble-${{ matrix.version }} main\" | sudo tee /etc/apt/sources.list.d/llvm.list\n          sudo apt-get update\n          sudo apt-get install -y clang-${{ matrix.version }} libbsd-dev uuid-dev\n      - name: Build\n        run: cppbuild/cppbuild --debug-build --cxx-hide-deprecation-message\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-clang-debug-${{ matrix.version }}\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-xcode-build:\n    name: C++ Xcode (macOS)\n    runs-on: macos-15\n    timeout-minutes: 60\n    env:\n      CC: clang\n      CXX: clang++\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Setup small temp file system and localhost name\n        run: |\n          sudo sed -i bak \"s/localhost/localhost $(hostname)/\" /etc/hosts\n          dscacheutil -flushcache\n          sudo diskutil eraseDisk APFS tmp_aeron_dir $(hdiutil attach -nomount ram://$((2 * 1024 * 60)))\n      - name: Build\n        run: cppbuild/cppbuild --c-warnings-as-errors --cxx-warnings-as-errors --cxx-hide-deprecation-message\n      - name: Remove small temp file system\n        run: sudo diskutil eject tmp_aeron_dir\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" >> $GITHUB_OUTPUT\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-xcode-latest\n          path: ${{ steps.copy_test_logs.outputs.file }}\n\n  cpp-msvc-build:\n    name: C++ MSVC (Windows)\n    runs-on: windows-latest\n    timeout-minutes: 60\n    env:\n      CC: cl\n      CXX: cl\n    steps:\n      - name: Use faster temporary storage\n        run: |\n          echo \"TMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n          echo \"TEMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n      - name: Use faster temporary storage (Windows)\n        run: |\n          echo \"TMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n          echo \"TEMP=$env:RUNNER_TEMP\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n      - name: Disable CPU hogs\n        run: |\n          Set-MpPreference -DisableRealtimeMonitoring $true -Force\n          Add-MpPreference -ExclusionPath \"C:\\\" -Force\n          Add-MpPreference -ExclusionPath \"D:\\\" -Force\n          sc config wuauserv start= disabled\n          Stop-Service wuauserv -ErrorAction SilentlyContinue\n          reg add \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\wsl.exe\" /v Debugger /t REG_SZ /d \"block.exe\" /f\n          reg add \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\CompatTelRunner.exe\" /v Debugger /t REG_SZ /d \"block.exe\" /f\n      - name: Setup small temp file system\n        run: |\n          $current_size = (Get-PartitionSupportedSize -DiskNumber 1 -PartitionNumber 1)\n          $new_size = ($current_size.SizeMax - 100 * 1024 * 1024)\n          Resize-Partition -DiskNumber 1 -PartitionNumber 1 -Size ($new_size)\n          New-Partition -DiskNumber 1 -Size 60MB -DriveLetter T | Format-Volume -FileSystem NTFS -Confirm:$false\n          New-Item -ItemType Directory -Path T:\\tmp_aeron_dir\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=$env:JAVA_HOME\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Build\n        run: cppbuild/cppbuild.ps1 --c-warnings-as-errors --cxx-warnings-as-errors --cxx-hide-deprecation-message\n      - name: Remove small temp file system (Windows)\n        if: always()\n        run: |\n          if (Test-Path \"T:\\tmp_aeron_dir\")\n          {\n            Remove-Partition -DriveLetter T -Confirm:$false\n          }\n      - name: Copy test logs\n        id: copy_test_logs\n        if: failure()\n        run: |\n          echo \"file=build/distributions/test_logs.tbz2\" | Out-File $env:GITHUB_OUTPUT -Encoding utf8 -Append\n          ./gradlew tarTestLogs\n      - name: Upload crash logs\n        if: always() && steps.copy_test_logs.outputs.file == 'build/distributions/test_logs.tbz2'\n        uses: actions/upload-artifact@v5\n        with:\n          name: crash-logs-msvc-latest\n          path: ${{ steps.copy_test_logs.outputs.file }}\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: \"CodeQL\"\n\non:\n  workflow_call:\n  workflow_dispatch:\n  repository_dispatch:\n    types: run-commit-tests\n  push:\n    branches:\n      - master\n      - release/**\n  pull_request:\n    types: [opened, synchronize]\n    branches:\n      - master\n      - release/**\n\nconcurrency:\n  group: codeql-${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\nenv:\n  JAVA_VERSION: '17'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ java, cpp ]\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.sha }}\n\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n\n      - name: Install compiler\n        run: sudo apt-get install -y g++-14\n\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v4\n        with:\n          languages: ${{ matrix.language }}\n          config-file: ./.github/codeql/codeql-config.yml\n          packs: \"codeql/${{ matrix.language }}-queries:AlertSuppression.ql\"\n\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v4\n\n      - name: Perform CodeQL Analysis\n        id: analyze\n        uses: github/codeql-action/analyze@v4\n        with:\n          category: \"/language:${{ matrix.language }}\"\n          upload: false\n          output: sarif-results\n\n      - name: filter-sarif\n        uses: advanced-security/filter-sarif@v1\n        with:\n          # Filter out generated and third party code.\n          patterns: |\n            -**/thirdparty/**\n            -**/generated/**\n            -**/generated-src/**\n            -**/generated-test/**\n            -**/_deps/**\n            -**/aeron-samples/**:java/uncaught-number-format-exception\n            -**/*Test.java:java/uncaught-number-format-exception\n            -**/*Tool.java:java/uncaught-number-format-exception\n            -**/*Debug*.java:java/uncaught-number-format-exception\n          input: sarif-results/${{ matrix.language }}.sarif\n          output: sarif-results/${{ matrix.language }}.sarif\n\n      - name: Upload SARIF\n        id: upload\n        uses: github/codeql-action/upload-sarif@v4\n        with:\n          sarif_file: sarif-results/${{ matrix.language }}.sarif\n          wait-for-processing: true\n\n      # optional: for debugging the uploaded sarif\n      - name: Upload loc as a Build Artifact\n        uses: actions/upload-artifact@v5\n        with:\n          name: sarif-results-${{ matrix.language }}\n          path: sarif-results\n          retention-days: 1\n\n      - name: Dismiss alerts\n        if: github.ref == 'refs/heads/master'\n        uses: advanced-security/dismiss-alerts@v2\n        with:\n          sarif-id: ${{ steps.upload.outputs.sarif-id }}\n          sarif-file: sarif-results/${{ matrix.language }}.sarif\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  workflow_dispatch:\n  push:\n    tags:\n      - \"*.*.*\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: false\n\nenv:\n  GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.java.installations.auto-detect=false -Dorg.gradle.warning.mode=fail'\n  JAVA_VERSION: '17'\n\njobs:\n  release-java:\n    name: Release java artifacts\n    permissions:\n      contents: read\n      packages: write\n    runs-on: ubuntu-24.04\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v5\n        with:\n          ref: ${{ github.ref }}\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ env.JAVA_VERSION }}\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=${{ env.JAVA_VERSION }}\" >> $GITHUB_ENV\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v5\n      - name: Setup BUILD_JAVA_HOME & BUILD_JAVA_VERSION\n        run: |\n          java -Xinternalversion\n          echo \"BUILD_JAVA_HOME=${JAVA_HOME}\" >> $GITHUB_ENV\n          echo \"BUILD_JAVA_VERSION=17\" >> $GITHUB_ENV\n      - name: Publish with Gradle\n        run: ./gradlew publish uploadArtifactsToCentralPortal\n        env:\n          SIGNING_GPG_SECRET_KEY: ${{ secrets.GPG_RSA_SIGN_KEY }}\n          SIGNING_GPG_PASSWORD: ${{ secrets.GPG_RSA_SIGN_KEYPASS }}\n          SONATYPE_CENTRAL_PORTAL_USERNAME: ${{ secrets.SONATYPE_CENTRAL_PORTAL_USERNAME }}\n          SONATYPE_CENTRAL_PORTAL_PASSWORD: ${{ secrets.SONATYPE_CENTRAL_PORTAL_PASSWORD }}\n"
  },
  {
    "path": ".gitignore",
    "content": "target\nGTAGS\nGRTAGS\nGPATH\nprop\nout/\nclasses/\nbin/\n\n# OS X\n.DS_Store\n\n# eclipse\n.project\n.classpath\n.settings\n\n# intellij\n*.iml\n*.ipr\n*.iws\n.idea/\n.run/\n\n# editors\n*.sublime-project\n*.sublime-workspace\n*~\n\n# the build\nbuild-local.properties\n.gradle/\nbuild/\n!buildSrc/\n!buildSrc/src/**/build\ncmake-build-debug/\ncmake-build-release/\ncmake-build-debug-*/\ncmake-build-release-*/\n\n# cpp build linux\ncppbuild/CMakeCache.txt\ncppbuild/CMakeFiles/\ncppbuild/CTestTestfile.cmake\ncppbuild/Makefile\ncppbuild/Testing/\ncppbuild/aeron-common/\ncppbuild/aeron-samples/\ncppbuild/binaries/\ncppbuild/cmake_install.cmake\ncppbuild/cmake/\ncppbuild/cmake*/\n\n# cpp build windows\ncppbuild/*.opensdf\ncppbuild/*.sdf\ncppbuild/*.sln\ncppbuild/*.suo\ncppbuild/*.vcxproj\ncppbuild/*.filters\ncppbuild/*.lastbuildstate\ncppbuild/*.tlog\ncppbuild/*.log\ncppbuild/*.cache\ncppbuild/Debug\ncppbuild/Release\ncppbuild/RelWithDebInfo\ncppbuild/Win32\n\n# JVM crash reports\nhs_err_pid*\n*.hprof\n*-gc.log\n*-crash.log\n\n# Archive and Cluster data from system tests\n/aeron-cluster/aeron-archive/\n/aeron-cluster/aeron-cluster/\n\naeron-samples/scripts/cluster/logs/\naeron-samples/scripts/cluster/node*/"
  },
  {
    "path": "CHANGELOG.adoc",
    "content": "= Aeron Releases\n\n== 1.51.0 (Unreleased)\n\n=== New & Noteworthy\n\n* **Breaking:** **[Cluster]** The classes RecordingLog.Snapshot, RecordingLog.Log and Recording.RecoveryPlan have been\nchanged from flat classes to records, so the data accessors are now methods and not fields.\n* **Breaking:** **[Cluster]** Removed the `ClusteredServiceContainer.Context.standbySnapshotsEnabled` method. This\nconfiguration is no longer required. It is controlled `ConsensusModule.Context.standbySnapshotsEnabled` configuration\noption and is propagated across as this needs to be consistent across both agents.\n\n=== Changelog\n\n* **[Breaking]**: **[Client/C{plus}{plus} Wrapper]** Remove `close()` method from `Publication/ExclusivePublication`,\ni.e. the underlying `aeron_publication_t/aeron_exclusive_publication_t` will be closed when the corresponding C++\nobject is destroyed.\n* **[Client/C{plus}{plus} Wrapper]** Do not use locks for adding/removing destinations to `ExclusivePublication` and\n`Subscription` since these classes are not threadsafe.\n* **[Client/C]** Allow setting idle strategy on the `aeron_context`.\n* **[Client/C{plus}{plus} Wrapper]** Set idle strategy on the underlying `aeron_context` using configured\n`idleSleepDuration`.\n* **[Client/C]** Close image before invoking unavailable handler when closing subscription.\n* **[Client/C]** Client conductor should process one command at a time.\n* **[Client/C]** Fix blocking logic of the `aeron_add_xxx_handler/aeron_remove_xxx_handler` methods, i.e. do not exit\nearly if client command queue is empty and instead retry until command is added and confirmed or timeout occurs.\n* **[Client/C{plus}{plus} Wrapper]** Ensure that `Aeron` instance is deleted after all of its children are deleted.\n* **[Client/C{plus}{plus} Wrapper]** Handle a case when a media driver dies/is stopped leading to the underlying\nC-managed resources being closed, i.e. fix `AsyncAddSubscription` memory leak.\n* **[Client/C{plus}{plus} Wrapper]** Fix pending `AsyncDestination` memory leak when not polled to completion.\n* **[Driver/C]** Avoid double freeing memory when setting new idle strategy programmatically.\n* **[C]** Fix `gradlew` invocation from `cmake`, i.e. fix `aeron-all.jar` generation when running inside CLion.\n* **[Samples/Java]** Remove deprecated `UseBiasedLocking` JVM option.\n* **[Cluster]** Backup query responses are now resolved using the most recent entry in the RecordingLog instead of being derived from the RecoveryPlan.\n* **[Cluster]** When a ClusteredServiceAgent fails to start, e.g. from an exception during snapshot load, the ConsensusModuleAgent will also terminate with an error in the log. Previously this would hang indefinitely.\n* **[Java Samples]** Added a sample for ReplayMerge.\n* **[Java]** Avoid bind conflicts when removing and adding a subscription to the same channel.\n(https://github.com/aeron-io/aeron/pull/1955[#1955])\n* **[Driver/Java]** Close sockets on the conductor thread just like the C driver.\n* **[Driver/C]** Fix memory leak whereby async commands submitted to the async executor were not freed upon driver\ntermination. (https://github.com/aeron-io/aeron/pull/1960[#1960])\n* **[C{plus}{plus} Wrapper]** Cleanup pending async resources when `Aeron` client is closed, i.e. prevent a memory leak.\n* **[C]** Refactor async resource registration in `aeron_client_conductor` to ensure proper cleanup if resource cannot\nbe added to the client conductor.\n* **[Client]** Fixed a bug in the controlled fragment assemblers where their handler after aborting on a fragmented\nmessage would receive on subsequent calls for that message a header with invalid position and fragmented frame length.\n* **[Driver/C]** Fix data corruption resulting from `aeron_append_block` ignoring `term_offset`.  (https://github.com/aeron-io/aeron/issues/1944[#1944])\n* **[Driver]** Process pending loss without delays in case of an unreliable subscriber (`reliable=false`), i.e. do not\napply NAK delay if no recovery will be attempted. (https://github.com/aeron-io/aeron/issues/1946[#1946])\n* **[Driver/Java]** Now replies with a `RESOURCE_TEMPORARILY_UNAVAILABLE` error when the endpoint is mid-closing (as in the C driver) when reuse would be unsafe and new endpoint/socket creation would lead to a bind error.\n* **[Cluster Client/Java]** Now retries when a `RESOURCE_TEMPORARILY_UNAVAILABLE` error is encountered when creating an egress subscription.\n* **[Cluster]** Enforce a maximum service count of 10. This limit has always existed in Cluster\nbut is now explicitly enforced via the ClusteredServiceContainer.Configuration.MAX_SERVICE_COUNT setting.\n* **[Cluster]** Restores old behaviour around single-node cluster liveness timeouts. (https://github.com/aeron-io/aeron/pull/1947[#1947])\n* **[Cluster]** Fix single-node cluster leader re-election which could happen after a stall or a time jump.\n* Increase default cycle thresholds to 100ms as the previous value of 1ms was too short and their counts kept ticking up under normal conditions.\n* **[Archive]** Fix `CATALOG_RESIZE` debug logging.\n* **[C]** Renamed `aeron_archive_proxy_set_control_esssion_id` to `aeron_archive_proxy_set_control_session_id`.\n* **[Java]** Upgrade to `Gradle` 9.4.1.\n* **[Java]** Upgrade to `ByteBuddy` 1.18.7.\n* **[Java]** Upgrade to `Mockito` 5.23.0.\n* **[Java]** Upgrade to `JUnit` 6.0.3.\n* **[Java]** Upgrade to `Checkstyle` 12.3.1.\n* **[Java]** Upgrade to `Shadow` 9.4.0.\n\n== 1.50.4 (2026-03-20)\n* **[Breaking]**: **[Client/C{plus}{plus} Wrapper]** Remove `close()` method from `Publication/ExclusivePublication`,\ni.e. the underlying `aeron_publication_t/aeron_exclusive_publication_t` will be closed when the corresponding C++\nobject is destroyed.\n* **[Client/C{plus}{plus} Wrapper]** Do not use locks for adding/removing destinations to `ExclusivePublication` and\n`Subscription` since these classes are not threadsafe.\n* **[Client/C]** Allow setting idle strategy on the `aeron_context`.\n* **[Client/C{plus}{plus} Wrapper]** Set idle strategy on the underlying `aeron_context` using configured\n`idleSleepDuration`.\n* **[Client/C]** Close image before invoking unavailable handler when closing subscription.\n* **[Client/C]** Client conductor should process one command at a time.\n* **[Client/C]** Fix blocking logic of the `aeron_add_xxx_handler/aeron_remove_xxx_handler` methods, i.e. do not exit\nearly if client command queue is empty and instead retry until command is added and confirmed or timeout occurs.\n* **[Client/C{plus}{plus} Wrapper]** Ensure that `Aeron` instance is deleted after all of its children are deleted.\n* **[Client/C{plus}{plus} Wrapper]** Handle a case when a media driver dies/is stopped leading to the underlying\nC-managed resources being closed, i.e. fix `AsyncAddSubscription` memory leak.\n* **[Client/C{plus}{plus} Wrapper]** Fix pending `AsyncDestination` memory leak when not polled to completion.\n* **[Driver/C]** Avoid double freeing memory when setting new idle strategy programmatically.\n* **[C]** Fix `gradlew` invocation from `cmake`, i.e. fix `aeron-all.jar` generation when running inside CLion.\n* **[Samples/Java]** Remove deprecated `UseBiasedLocking` JVM option.\n* **[Java]** Upgrade to `Gradle` 9.4.1.\n* **[Java]** Upgrade to `Shadow` 9.4.0.\n\n== 1.50.3 (2026-03-13)\n* **[Java]** Avoid bind conflicts when removing and adding a subscription to the same channel.\n(https://github.com/aeron-io/aeron/pull/1955[#1955])\n* **[Driver/Java]** Close sockets on the conductor thread just like the C driver.\n* **[Driver/C]** Fix memory leak whereby async commands submitted to the async executor were not freed upon driver\ntermination. (https://github.com/aeron-io/aeron/pull/1960[#1960])\n* **[C{plus}{plus} Wrapper]** Cleanup pending async resources when `Aeron` client is closed, i.e. prevent a memory leak.\n* **[C]** Refactor async resource registration in `aeron_client_conductor` to ensure proper cleanup if resource cannot\nbe added to the client conductor.\n* **[Java]** Upgrade to `Gradle` 9.4.0.\n* **[Java]** Upgrade to `ByteBuddy` 1.18.7.\n* **[Java]** Upgrade to `Mockito` 5.23.0.\n* **[Java]** Upgrade to `Shadow` 9.3.2.\n\n== 1.50.2 (2026-02-27)\n\n* **[Client]** Fixed a bug in the controlled fragment assemblers where their handler after aborting on a fragmented\nmessage would receive on subsequent calls for that message a header with invalid position and fragmented frame length.\n* **[Archive/C]** Fix typo in `aeron_archive_proxy_set_control_esssion_id` function name.\n* **[Driver/C]** Fix data corruption resulting from `aeron_append_block` ignoring `term_offset`.  (https://github.com/aeron-io/aeron/issues/1944[#1944])\n* **[Driver]** Process pending loss without delays in case of an unreliable subscriber (`reliable=false`), i.e. do not\napply NAK delay if no recovery will be attempted. (https://github.com/aeron-io/aeron/issues/1946[#1946])\n* **[Cluster]** Restores old behaviour around single-node cluster liveness timeouts. (https://github.com/aeron-io/aeron/pull/1947[#1947])\n* **[Java]** Upgrade to `ByteBuddy` 1.18.5.\n* **[Java]** Upgrade to `JUnit` 6.0.3.\n* **[Java]** Upgrade to `Checkstyle` 12.3.1.\n\n== 1.50.1 (2026-02-05)\n\n* **[Cluster]** Fix single-node cluster leader re-election which could happen after a stall or a time jump.\n* Increase default cycle thresholds to 100ms as the previous value of 1ms was too short and their counts kept ticking up under normal conditions.\n* **[Archive]** Fix `CATALOG_RESIZE` debug logging.\n\n== 1.50.0 (2026-01-16)\n=== New & Noteworthy\n\n* **[Breaking]**: **[C{plus}{plus}]** Remove deprecated standalone C{plus}{plus} API. The C{plus}{plus} Wrapper API should be used instead.\n\n* The `interface` channel parameter now accepts network interface names in the `\\{interface-name}` format, e.g. `interface=\\{eth0}`.\n+\nWhen used with multicast channels, it specifies the interface to join with and send on.\n+\nThe `interface` parameter might sometimes be used with non-multicast channels to control the local address of a socket.\nIn those cases, the socket will be bound to an address of an appropriate family assigned to the named interface.\nIf there are multiple matching addresses available, it's unspecified which one will be used.\n+\nIt's also possible to specify the port used with the resolved address by using the `\\{interface-name}:port` format.\n\n=== Changelog\n\n* **[Archive]** Do not set `session-id` on the replay channel if response channels are used to avoid `session-id` clash.\n* **[Archive]** Default `AeronArchive.Context.Archive` instance is configured with `RethrowingErrorHandler` for Subscriptions.\n* **[Archive]** Allow asynchronously connecting to Archive using AgentInvokers.\n* **[Archive]** Format size and duration parameters when using `ChannelUriStringBuilder` to create full channel URI.\n* **[Archive]** Use the configured control term buffer length for the replication client instead of hardcoding `64k`.\n* **[Archive/Java/C/C{plus}{plus} Wrapper]** Make message retry attempts for `aeron_archive` configurable. (Java PR: https://github.com/aeron-io/aeron/pull/1885[#1885])\n* **[Archive]** Ensure that errors from `ReplaySession` are both captured in the error log and sent to the control session. Improve error messages.\n* **[Cluster]** Prevent fast follower node from consuming log ahead of the commit position when majority lags behind. (https://github.com/aeron-io/aeron/pull/1898[#1898])\n* **[Cluster]** Prevent infinite loop while awaiting service ACKs if services terminated/crashed.\n* **[Cluster]** Add reason to the `STATE_CHANGE` event. Always quote `reason` value across all events.\n* **[Cluster]** Default `AeronCluster.Context.Archive` instance is configured with `RethrowingErrorHandler` for Subscriptions.\n* **[Cluster]** Add support for `AgentInvoker`/`Aeron.conductorAgentInvoker` to `AeronCluster#asyncConnect`.\n* **[Cluster]** Add log event for `Vote` message, i.e. a response to `RequestVote`.\n* **[Cluster]** Handle quorum commit position going backwards.\n* **[Cluster]** Make default Cluster consensus channel use UDP term length of the media driver instead of assigning `term-length=64k`. With the Aeron defaults this increases the term length from `64KB` to `16MB`.\n* **[Cluster]** Unconditionally set `reliable=true` on the ingress subscription to ensure that the ingress stream is reliable.\n* **[Cluster]** Ignore unknown schema messages (e.g. from Sequencer) in `EgressPoller`.\n* **[Cluster]** Make `AeronCluster.asyncConnect` fully asynchronous by using `Aeron#asyncAddExclusivePublication/asyncAddPublication` API when dealing with redirect requests in case no valid existing leader publication was found.\n* **[Cluster]** Reuse existing leader ingress publication if it is still valid when receiving redirect response.\n* **[Cluster]** Only consider active members when computing `hasQuorumAtPosition`. We now always await for the active quorum of nodes o reach target position. Remove `hasVotersAtPosition` since it is no longer used.\n* **[Cluster]** Refactor `isCandidate` checks and document why we must consider all existing members when doing so. We cannot check only the \"active\" nodes since that breaks the nomination phase whereby the nodes will stop proposing themselves as candidates once `timeOfLastAppendPositionNs` stops advancing.\n* **[Cluster]** Only consider active Cluster members when computing the quorum position.\n* **[Cluster]** Consistently update follower position, i.e. always set `logPosition`, `leadershipTermId` and `timeOfLastAppendPositionNs` together. Add missing update of the follower info upon receiving `CanvassPosition`.\n* **[Cluster]** Increment snapshot counter after updating snapshot duration tracker to avoid a race in a test.\n* **[Java/C]** Allow to specify network interface name in the interface channel parameter. (https://github.com/aeron-io/aeron/pull/1901[#1901])\n* **[Driver]** Do not append threading mode to the name resolver cycle counters.\n* **[C]** Fix threshold formatting + format threshold info for the name resolver cycle tracker.\n* **[C]** Remove duty cycle threshold limit of 1 hour.\n* **[C]** Format duty cycle threshold before adding to the driver counters.\n* **[Java]** Use `SystemUtil#formatDuration` to format `threshold` for the duty cycle tracker counters.\n* **[C]** Set `ownerId` for each stream counter created via client command (e.g. `ADD_PUBLICATION`).\n* **[Java]** Set `ownerId` for each stream counter created via client command (e.g. `ADD_PUBLICATION`).\n* **[Java]** Add Publication type to the counters and log buffer metadata, i.e. `pub-pos` counter now has a suffix (`(concurrent)` or `(exclusive)` depending on the type) and log buffer metadata contains a new field `type` (`unit8`) at offset 497 which can have values `0` (concurrent publication), `1` (exclusive publication) or `2` (publication image).\n* **[C]** Add Publication type to the counters and log buffer metadata, i.e. `pub-pos` counter now has a suffix (`(concurrent)` or `(exclusive)` depending on the type)  and log buffer metadata contains a new field `is_exclusive_publication` (`unit8`) at offset 497.\n* **[Driver/C]** Increase sender/receiver IO vector capacity and publication max messages per send to `4` from `2`.\n* **[C]** Do not throttle sends to one per call if short sends are encountered.\n* **[Driver/C/Java]** Add `EF_VI`, `VMA`, `ATS`, `DPDK` and `Selector` counter types. Synchronize Java and C counter definitions.\n* **[C]** Add `aeron_format_duration_ns`.\n* **[C]** Add `aeron_format_size64`.\n* **[C]** Refactor duration and size parsing.\n* **[C]** Fixes data races on AERON_GET_ACQUIRE and AERON_SET_RELEASE usages. (https://github.com/aeron-io/aeron/pull/1902[#1902])\n* **[C]** Fixes broken atomic instructions. (https://github.com/aeron-io/aeron/pull/1905[#1905])\n* **[C]** Replace `__asm__ volatile` with `__asm__ __volatile__` just like in Linux kernel.\n* **[C]** aeron_atomic64_gcc_x86_64.h acquire/release fixes (https://github.com/aeron-io/aeron/pull/1904[#1904])\n* **[C]** Optimized register allocation aeron_atomic64_gcc_x86_64h (https://github.com/aeron-io/aeron/pull/1910[#1910])\n* **[C]** Fix aeron_log_buffer.c mem leak (https://github.com/aeron-io/aeron/pull/1907[#1907])\n* **[C]** Fixed aeron_agent_init role_name bugs. (https://github.com/aeron-io/aeron/pull/1908[#1908])\n* **[C]** Do not resolve `endpoint` with ephemeral port for MDS subscription. Copy original channel as-is if no resolution is performed.\n* **[Java]** Do not resolve `endpoint` with ephemeral port if MDS is used.\n* **[Java]** Parse channel lazily when `tryResolveChannelEndpointPort` is called.\n* **[Java]** Fix `Subscription#tryResolveChannelEndpointPort` when no addresses exist, i.e. return `null` if active address is not yet available. Also cache results and avoid doing any work if `endpoint` is not defined or does not contain a port zero.\n* **[Java]** Fixed StatusIndicator deprecated method usage. (https://github.com/aeron-io/aeron/pull/1895[#1895])\n* **[Java]** Replaced the AtomicCounter.getWeak usages by getPlain. (https://github.com/aeron-io/aeron/pull/1893[#1893])\n* **[Java]** Replaced Counter.getAndAddOrdered by getAndAddRelease. (https://github.com/aeron-io/aeron/pull/1894[#1894])\n* **[Java]** Add per-stream counters to `StreamStat` and extract counter name from label instead of relying on static mapping by type.\n* **[C]** fix: double free in `aeron_archive_context_t`. (https://github.com/aeron-io/aeron/pull/1897[#1897])\n* **[C]** Make `aeron_subscription_image_release` and `aeron_subscription_image_retain` no op if image is not found in the\ncurrent image list, i.e. handle a case of `on_unavailable_image` being called and attempting to retain an image that\nwas already removed (i.e. C{plus}{plus} Wrapper)).\n* **[C]** invoke the client when in `aeron_archive_replay_merge_close`. (https://github.com/aeron-io/aeron/pull/1873[#1873])\n* **[C]** Handle `aeron_archive_encoded_credentials_t` being `NULL` as well as `data` field being `NULL`, i.e. treat both as empty credentials.\n* **[C]** Fix race condition in `aeron_archive_client` reentrancy check, i.e. first acquire lock and then do the check. Use\nmutex to protect `aeron_archive_close` from concurrent execution.\n* **[C]** Create `aeron_mutex_t` as recursive/reentrant on POSIX systems, i.e. match behavior on Windows and the Java implementation.\n* **[C]** Fix missing mutex unlock in case of an error.\n* **[Cluster]** Optimize redirect handling in `AeronCluster`: reuse existing publication if available and use async API\nto connect to the leader otherwise.\n* **[Cluster]** Protect against NPE during follower catchup if recording is stopped, because the leader transitions to a\nfollower and stops sending log.\n* **[Driver/C]** Perform sender address re-resolution checks on a fixed schedule rather than awaiting next control\npolling cycle, i.e. align behavior with the `Sender#doWork` implementation.\n* **[Driver/Java]** Perform full name and address matching in driver name resolver. Add tests to ensure partial matches\nare not allowed. Remove allocations when matching neighbors by address.\n* **[Driver/C]** Fix segfault when invalid name resolver configuration is provided. Cleanup allocated resources to avoid\nleaking memory.\n* **[Driver/C]** Use `aeron_interface_parse_and_resolve` to resolve `aeron.driver.resolver.interface` parameter, i.e.\nskip the `aeron_find_interface` checks. This aligns the implementation with the `DriverNameResolver` in Java. Fix\nfiltering out of self entries, i.e. use full name matching instead of a prefix match.\n* **[C]** Add `aeron_is_frame_valid` method.\n* **[C/Java]** Add all missing counter type constants for the premium components.\n* **[C]** Do not truncate frames longer than 1408 bytes when logging, i.e. allow up to `AERON_MAX_UDP_PAYLOAD_LENGTH`\n(`65504`) bytes instead.\n* **[C]** Ensure that fallback `receiverId` cannot be zero.\n* **[Java]** Improve mark file APIs: use high-level operations to signal ready/terminated, reset activity timestamp\nupon failure, update tests to reflect activity timestamp changes during startup sequence.\n* **[Java/C]** Fix formatting of large values and counter ids in `AeronStat`.\n* **[Java/C]** Add dissector for the `ERR` (`0x04`) frame type.\n* **[Java]** Add version information to `ComponentLogger` and print enabled loggers upon agent startup.\n* **[C]** Print enabled loggers upon agent startup and their versions.\n* **[C]** Add function to counter number of leading zeroes in 64 bit value (`aeron_number_of_leading_zeroes_u64`).\n* **[C]** Add ability to counter number of digits in a 32-bit number (`aeron_digit_count`).\n* **[C]** Link `aeron-client` against `libbsd`.\n* **[Java]** Delete `DeduplicateTask`, i.e. use built-in capabilities of the Shadow plugin instead.\n* **[Java]** Upgrade to https://github.com/aeron-io/agrona/releases/tag/2.4.0[Agrona 2.4.0].\n* **[Java]** Upgrade to https://github.com/aeron-io/simple-binary-encoding/releases/tag/1.37.1[SBE 1.37.1].\n* **[Java]** Upgrade to `ByteBuddy` 1.18.3.\n* **[Java]** Upgrade to `ASM` 9.9.1.\n* **[Java]** Upgrade to `JUnit` 6.0.2.\n* **[Java]** Upgrade to `Mockito` 5.21.0.\n* **[Java]** Upgrade to `Checkstyle` 12.3.0.\n* **[Java]** Upgrade to `Shadow` 9.3.1.\n* **[C]** Upgrade to `CMake` 4.2.1.\n\n== 1.49.3 (2025-12-15)\n\n* **[Cluster]** Ignore messages from unknown schemas (e.g. from Sequencer) in `EgressPoller`.\n* **[Archive]** Ensure that errors from `ReplaySession` are both captured in the error log and sent to the control session.\n* **[C]** fix double free in `aeron_archive_context_t`. (https://github.com/aeron-io/aeron/pull/1897[#1897])\n* **[C]** Make `aeron_subscription_image_release` and `aeron_subscription_image_retain` no op if image is not found in the\ncurrent image list, i.e. handle a case of `on_unavailable_image` being called and attempting to retain an image that\nwas already removed (i.e. C{plus}{plus} Wrapper)).\n* **[C]** invoke the client when in `aeron_archive_replay_merge_close`. (https://github.com/aeron-io/aeron/pull/1873[#1873])\n* **[C]** Handle `aeron_archive_encoded_credentials_t` being `NULL` as well as `data` field being `NULL`, i.e. treat both as empty credentials.\n* **[C]** Fix race condition in `aeron_archive_client` reentrancy check, i.e. first acquire lock and then do the check. Use\nmutex to protect `aeron_archive_close` from concurrent execution.\n* **[C]** Create `aeron_mutex_t` as recursive/reentrant on POSIX systems, i.e. match behavior on Windows and the Java implementation.\n* **[C]** Fix missing mutex unlock in case of an error.\n\n== 1.49.2 (2025-11-24)\n\n* **[C]** Do not resolve `endpoint` with ephemeral port for MDS subscription. Copy original channel as-is if no\nresolution is performed.\n* **[Java]** Fix `Subscription#tryResolveChannelEndpointPort` when no addresses exist, i.e. return `null` if active\naddress is not yet available. Avoid doing any work if `endpoint` is not defined (IPC, MDS, response channel) or does not\ncontain a port zero.\n* **[C]** Add `aeron_is_frame_valid` method.\n* **[C/Java]** Add all missing counter type constants for the premium components.\n* **[C]** Do not truncate frames longer than 1408 bytes when logging, i.e. allow up to `AERON_MAX_UDP_PAYLOAD_LENGTH`\n(`65504`) bytes instead.\n* **[C]** Ensure that fallback `receiverId` cannot be zero.\n* **[Java]** Improve mark file APIs: use high-level operations to signal ready/terminated, reset activity timestamp\nupon failure, update tests to reflect activity timestamp changes during startup sequence.\n* **[Java/C]** Fix formatting of large values and counter ids in `AeronStat`.\n* **[Java/C]** Add dissector for the `ERR` (`0x04`) frame type.\n* **[Java]** Add version information to `ComponentLogger` and print enabled loggers upon agent startup.\n* **[C]** Print enabled loggers upon agent startup and their versions.\n* **[C]** Add function to counter number of leading zeroes in 64 bit value (`aeron_number_of_leading_zeroes_u64`).\n* **[C]** Add ability to counter number of digits in a 32-bit number (`aeron_digit_count`).\n* **[C]** Link `aeron-client` against `libbsd`.\n* **[Java]** Delete `DeduplicateTask`, i.e. use built-in capabilities of the Shadow plugin instead.\n* **[Java]** Upgrade to https://github.com/aeron-io/agrona/releases/tag/2.3.2[Agrona 2.3.2].\n* **[Java]** Upgrade to https://github.com/aeron-io/simple-binary-encoding/releases/tag/1.36.2[SBE 1.36.2].\n* **[Java]** Upgrade to `ByteBuddy` 1.17.8.\n* **[Java]** Upgrade to `ASM` 9.9.\n* **[Java]** Upgrade to `JUnit` 6.0.1.\n* **[Java]** Upgrade to `Checkstyle` 12.1.1.\n* **[C]** Upgrade to `CMake` 4.1.3.\n\n== 1.49.1 (2025-11-01)\n\n* **[Cluster]** Optimize redirect handling in `AeronCluster`: reuse existing publication if available and use async API\nto connect to the leader otherwise.\n* **[Cluster]** Protect against NPE during follower catchup if recording is stopped, because the leader transitions to a\nfollower and stops sending log.\n* **[Driver/C]** Perform sender address re-resolution checks on a fixed schedule rather than awaiting next control\npolling cycle, i.e. align behavior with the `Sender#doWork` implementation.\n* **[Driver/Java]** Perform full name and address matching in driver name resolver. Add tests to ensure partial matches\nare not allowed. Remove allocations when matching neighbors by address.\n* **[Driver/C]** Fix segfault when invalid name resolver configuration is provided. Cleanup allocated resources to avoid\nleaking memory.\n* **[Driver/C]** Use `aeron_interface_parse_and_resolve` to resolve `aeron.driver.resolver.interface` parameter, i.e.\nskip the `aeron_find_interface` checks. This aligns the implementation with the `DriverNameResolver` in Java. Fix\nfiltering out of self entries, i.e. use full name matching instead of a prefix match.\n\n== 1.49.0 (2025-10-03)\n\n=== New & Noteworthy\n\n* **[Driver]** Support for IPC media was added to response channels. (https://github.com/aeron-io/aeron/issues/1761[#1761])\n+\nThe feature works exactly the same as for UDP media but does not require `control` endpoint to be specified.\n+\nFor an example see\nhttps://github.com/aeron-io/aeron/blob/da9fffbd44676ed0a7d4bc0d874dbec2211dbc0f/aeron-system-tests/src/test/java/io/aeron/ResponseChannelsTest.java#L204-L272[ResponseChannelsTest#shouldConnectResponsePublicationUsingImageAndIpc].\n\n* **[Driver]** Add support for `response-correlation-id=prototype` to allow pre-creating response publications to\nreserve and hold onto a local port. (https://github.com/aeron-io/aeron/pull/1863[#1863])\n+\nFor usage example see https://github.com/aeron-io/aeron/blob/807e947a511147d17561ce305b6d82f46624e4fd/aeron-system-tests/src/test/java/io/aeron/ResponseChannelsTest.java**[ResponseChannelsTest]**.\n\n* **[Driver]** Add API to create counters asynchronously.\n+\nCounters can now be created asynchronously using `Aeron.asyncAddCounter` and `Aeron.asyncAddStaticCounter` methods.\nCreated counter is returned by the `Aeron.getCounter` method. There is also `Aeron.asyncRemoveCounter` to asynchronously\nremove counter by `registrationId`.\n+\nFor usage example see\nhttps://github.com/aeron-io/aeron/blob/807e947a511147d17561ce305b6d82f46624e4fd/aeron-system-tests/src/test/java/io/aeron/CounterTest.java#L523[CounterTest#shouldAddCounterAsynchronously],\nhttps://github.com/aeron-io/aeron/blob/807e947a511147d17561ce305b6d82f46624e4fd/aeron-system-tests/src/test/java/io/aeron/CounterTest.java#L561[CounterTest#shouldAddCounterAsynchronouslyUsingLabelAndTypeId]\nand\nhttps://github.com/aeron-io/aeron/blob/807e947a511147d17561ce305b6d82f46624e4fd/aeron-system-tests/src/test/java/io/aeron/CounterTest.java#L592[CounterTest#shouldAddStaticCounterAsynchronously].\n\n* **[Archive]** Named Archive clients.\n+\nClient name can be specified using `AeronArchive.Context.clientName(java.lang.String)` API or\n`aeron.archive.client.name` system property. Once specified the name will be sent to the Archive with connect request\nalong with additional information such as client version. Archive will add a new `control-session` counter\n(`typeId=113`) for each connected client:\n+\n----\ncontrol-session: name=test client 5 version=1.49.0-SNAPSHOT commit=e79ebfa4ff sourceIdentity=127.0.0.1:51360 sessionId=1083930759 - archiveId=519\n----\n\n* **[Cluster]** Named Cluster clients.\n+\nClient name can be specified using `AeronCluster.Context.clientName(java.lang.String)` API or\n`aeron.cluster.client.name` system property. Once specified the name will be sent to the Cluster with connect request\nalong with additional information such as client version. Cluster will add a new `cluster-session` counter\n(`typeId=241`) for each connected client:\n+\n----\ncluster-session: name=test client version=1.49.0-SNAPSHOT commit=4ac30af55a sourceIdentity=127.0.0.1:54444 sessionId=325223904 - clusterId=0\n----\n\n* **[Cluster]** Default Authorisation Service\n+\nUpdated the default `AuthorisationService` to allow heartbeat and standby-snapshot requests, which are required for https://aeron.io/aeron-premium/aeron-cluster-standby/**[Aeron Cluster Standby]**.\n\n* **[Breaking]** `org.agrona.concurrent.ShutdownSignalBarrier` changes in https://github.com/aeron-io/agrona/releases/tag/2.3.0[Agrona 2.3.0].\nPlease read the corresponding release notes on how to upgrade.\n\n* **[Breaking]** `ClusteredServiceContainer.Context.shutdownSignalBarrier`, `ConsensusModule.Context.shutdownSignalBarrier`\nand `ClusterBackup.Context.shutdownSignalBarrier` methods were removed and replaced with an explicit `ShutdownSignalBarrier`.\n+\nFor example this is how `ConsensusModule#main` changed:\n+\n- Before:\n+\n[source,java]\n----\npublic static void main(final String[] args)\n{\n    loadPropertiesFiles(args);\n\n    try (ConsensusModule consensusModule = launch())\n    {\n        consensusModule.context().shutdownSignalBarrier().await();\n        System.out.println(\"Shutdown ConsensusModule...\");\n    }\n}\n----\n+\n- After:\n+\n[source,java]\n----\npublic static void main(final String[] args)\n{\n    loadPropertiesFiles(args);\n\n    try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n        ConsensusModule ignored = launch(new Context().terminationHook(barrier::signalAll)))\n    {\n        barrier.await();\n        System.out.println(\"Shutdown ConsensusModule...\");\n    }\n}\n----\n\n* **[Breaking]** Aeron jars no longer provide OSGI metadata as `bnd` plugin was removed, because it breaks\ncompatibility with Gradle 9.1 and causes self-dependency via `baseline` task.\n\n=== Changelog\n* **[Agent]** Make agent classes accessible outside of `io.aeron.agent` package.\n* **[Agent]** Fix `CMD_IN_MAX_RECORDED_POSITION` logging.\n* **[Agent]** Add `CLUSTER_SESSION_STATE_CHANGE` log event.\n* **[Agent]** Add `APPEND_SESSION_OPEN` log event.\n* **[Agent]** Remove `ADD_PASSIVE_MEMBER` log event.\n* **[Agent/C]** Remove extraneous `]` suffix from log messages `CMD_IN_REMOVE_PUBLICATION`, `CMD_IN_REMOVE_SUBSCRIPTION` and `CMD_IN_REMOVE_COUNTER`.\n* **[Agent/C]** Fix `UNTETHERED_SUBSCRIPTION_STATE_CHANGE` logging, i.e. log previous state and add missing logging to `aeron_publication_image`.\n* **[Archive]** Allow for the archive to update the channel for a specific recording.\n* **[Archive]** Return an error code when replaying and stopping an empty recording. (https://github.com/aeron-io/aeron/pull/1854[#1854])\n* **[Archive]** Capture abort reason in `RecordingSession`.\n* **[Archive]** Add additional \"length\" that will replay the data available, but not follow a live recording. Add more\nappropriately named constants and update javadoc.\n* **[Archive]** Ensure `AeronArchive.State.CLOSED` is used when `AeronArchive` instance is closed.\n* **[Archive/C]** Close uri string builders upon error when concluding `aeron_archive_context`.\n* **[Archive]** Adjust aliases on Archive streams for easier debugging.\n* **[Archive/Driver/Cluster]** Lower `cycle threshold` for core components to `1ms` by default.\n* **[Archive]** Add `ALLOW_ALL` and `DENY_ALL` authorisation supplier options for the Archive.\n* **[Archive/C]** Add aeron_archive_wrapper's own sources to the `target_include_directories`.\n* **[Archive]** Do not log a warning when control response publication is disconnected or a control request image is going\naway, i.e. treat those events as normal client termination.\n* **[Archive]** Copy `response-endpoint`, `ttl` and `stream-id` URI parameters from the original channel definition when creating recordings and replays.\n* **[Client]** Add Sequencer counter types.\n* **[Client]** Read `mtu` from the log buffer metadata only once.\n* **[Client]** Provide additional context when log buffer cannot be mapped.\n* **[Client]** Add `InternalApi` annotation.\n* **[Client/C{plus}{plus} Wrapper]** Add missing assignment operator to match the copy constructor.\n* **[Client/C]** Send `CLIENT_CLOSE` command to media driver when aeron is closing. (https://github.com/aeron-io/aeron/pull/1837[#1837])\n* **[Client/C]** Fix client build to properly resolve `-DHAVE_BSDSTDLIB_H -DHAVE_ARC4RANDOM`.\n* **[Cluster]** Update the default AuthorisationService to allow requests necessary for Cluster Standby. (https://github.com/aeron-io/aeron/pull/1870[#1870])\n* **[Cluster]** Handle exception during snapshot, i.e. continue running if exception is non-terminal.\n* **[Cluster]** Remove more references to membership changes/passive members/dynamic join.\n* **[Cluster]** Re-create ingress publication upon redirect from a follower while connecting to the Cluster.\n* **[Cluster]** Ensures proper handling when ingress publication is closed but not null. (https://github.com/aeron-io/aeron/pull/1861[#1861])\n* **[Cluster]** Add more context for transitioning from `FOLLOWER_LOG_REPLAY` state.\n* **[Cluster]** Add more context to `unexpected commit position` events.\n* **[Cluster]** Add context to `unexpected commit position from new leader` event.\n* **[Cluster]** Service snapshot time now includes time to serialize sessions and connect to the Archive.\n* **[Cluster]** Only log `APPEND_SESSION_CLOSE` upon successful append to the log + reduce slow cycle frequency caused by\nfailed append attempts.\n* **[Cluster]** Within the ClusterToolOperator bind the adapter before the publication and await for the subscription to be\nbound to reduce the chance of the returning publication not being connected and rejecting the return message.\n* **[Cluster]** Prevent standby replicated snapshots getting stuck if the address on the standby entry is invalid.\n* **[Cluster]** Add `ALLOW_ALL` and `DENY_ALL` authorisation supplier options for the Cluster.\n* **[Driver]** Fix incorrect update frequency in PublisherPos javadoc. (https://github.com/aeron-io/aeron/pull/1867[#1867])\n* **[Driver]** Untethered subscriptions are closed after `LINGER` if `rejoin=false` is specified.\n* **[Driver]** Correctly handle connection status updates for untethered IPC subscribers.\n* **[Driver/C]** Ensure `rcv-naks-sent` counter is cleaned up if publication image creation fails.\n* **[Driver/C]** Prevent sending empty NAK message upon initial connection to the publisher.\n* **[Driver]** Set absolute minimum value for NAK unicast delay at `1us` and use it as a default value.\n* **[Driver]** Ignore NAKs with zero length and treat NAKs with negative length as invalid packets.\n* **[Driver/C]** Fix `untethered_linger_timeout_ns` parsing and validation. Remove `1us` min value that was applied when parsing untethered timeouts.\n* **[Driver]** Validate `untetheredLingerTimeoutNs` not below `timerIntervalNs`.\n* **[Driver/C]** Update documented NAK defaults.\n* **[Driver/Java]** Do not overwrite `imageConnections` on every received packet.\n* **[Driver/Java]** Stop processing errors when a receiver is found.\n* **[Driver/Java]** Include `SIZE_OF_INT` in `keyLength` when allocating `ChannelEndpointStatus` counters. (https://github.com/aeron-io/aeron/pull/1844[#1844])\n* **[Driver]** Add an ability to fetch next available session id from the media driver.\n* **[Driver/C]** Fix system counter creation, i.e. assign `registrationId` and unset `ownerId` fields.\n* **[Driver]** Add `Control protocol version` system counter.\n* **[Driver/Java]** Detect `Address in use` errors synchronously when creating endpoints, issue https://github.com/aeron-io/aeron/issues/1830[#1830]. (https://github.com/aeron-io/aeron/pull/1842[#1842])\n* **[Driver/C]** Error handling fixes for destination and endpoint creation.\n* **[Driver/Java]** Handle more cases of `NumberFormatException` while parsing publication parameters.\n* **[CI]** Add JDK 25.\n* **[CI]** Add `Clang` 21 to the build matrix.\n* **[CI]** Build on Rocky 9.\n* **[Java]** Network Partition Testing. (https://github.com/aeron-io/aeron/pull/1858/[#1858])\n* **[Java]** Upgrade to https://github.com/aeron-io/agrona/releases/tag/2.3.0[Agrona 2.3.0].\n* **[Java]** Upgrade to https://github.com/aeron-io/simple-binary-encoding/releases/tag/1.36.0[SBE 1.36.0].\n* **[Java]** Upgrade to `Gradle` 9.1.0.\n* **[Java]** Upgrade to `ByteBuddy` 1.17.7.\n* **[Java]** Upgrade to `Checkstyle` 11.1.0.\n* **[Java]** Upgrade to `JUnit` 6.0.0.\n* **[Java]** Upgrade to `Mockito` 5.20.0.\n* **[Java]** Upgrade to `Shadow` 9.2.2.\n* **[Java]** Upgrade to `Versions` 0.53.0.\n* **[C]** Upgrade to `CMake` 4.1.2.\n* **[C]** Upgrade to `HDR Histogram` 0.11.9.\n\n== 1.48.10 (2025-11-21)\n\n* **[C]** Add `aeron_is_frame_valid` method.\n* **[C/Java]** Add all missing counter type constants for the premium components.\n* **[C]** Do not truncate frames longer than 1408 bytes when logging, i.e. allow up to `AERON_MAX_UDP_PAYLOAD_LENGTH`\n(`65504`) bytes instead.\n* **[C]** Ensure that fallback `receiverId` cannot be zero.\n* **[C]** Link `aeron-client` against `libbsd`.\n\n== 1.48.9 (2025-11-11)\n\n* **[Java]** Upgrade to `Shadow` 9.2.2 to fix duplicate classes issue in the uber-jar (i.e. `aeron-agent.jar` and\n`aeron-all.jar`).\n\n== 1.48.8 (2025-11-11)\n\n=== Known issues\n\n* Uber-jars `aeron-agent.jar` and `aeron-all.jar` contain duplicate classes. Fixed in\nhttps://github.com/aeron-io/aeron/releases/tag/1.48.9[1.48.9].\n\n=== Changelog\n\n* **[Driver/Java/C]** Add log dissectors for `ERR` (`0x04`) frame type.\n* **[Java]** Delete `DeduplicateTask`, i.e. use built-in capabilities of the `Shadow` plugin instead.\n\n== 1.48.7 (2025-11-01)\n\n* **[Driver/C]** Fix `UNTETHERED_SUBSCRIPTION_STATE_CHANGE` logging, i.e. log previous state and add missing logging to\n`aeron_publication_image`.\n* **[Driver/C]** Ensure `rcv-naks-sent` counter is cleaned up if publication image creation fails.\n* **[Driver/C]** Prevent sending empty NAK message upon initial connection to the publisher.\n* **[Driver/C]** Ignore NAKs with zero length and treat NAKs with negative length as invalid packets.\n* **[Driver/Java]** Ignore NAKs with zero length and treat NAKs with negative length as invalid packets.\n* **[Driver/Java]** Fix connectivity status handling in `NetworkPublication`, i.e. do not transition to `NOT_CONNECTED`\nwhen the last remote subscriber disconnects if there is at least one spy and spies simulate connection (`ssc=true`).\n* **[Driver/C]** Fix connectivity status handling in `aeron_network_publication`, i.e. do not transition to\n`NOT_CONNECTED` when the last remote subscriber disconnects if there is at least one spy and spies simulate connection\n(`ssc=true`).\n* **[Driver/Java]** Handle connection status updates for untethered IPC subscribers.\n* **[Driver/C]** Handle connection status updates for untethered IPC subscribers.\n* **[Agent/C]** Remove extraneous `]` suffix from `CMD_IN_REMOVE_PUBLICATION`, `CMD_IN_REMOVE_SUBSCRIPTION` and\n`CMD_IN_REMOVE_COUNTER` log events.\n* **[Driver/C]** Perform sender address re-resolution checks on a fixed schedule rather than awaiting next control\npolling cycle, i.e. align behavior with the `Sender#doWork` implementation.\n* **[Driver/Java]** Perform full name and address matching in driver name resolver. Add tests to ensure partial matches\nare not allowed. Remove allocations when matching neighbors by address.\n* **[Driver/C]** Fix segfault when invalid name resolver configuration is provided. Cleanup allocated resources to avoid\nleaking memory.\n* **[Driver/C]** Use `aeron_interface_parse_and_resolve` to resolve `aeron.driver.resolver.interface` parameter, i.e.\nskip the `aeron_find_interface` checks. This aligns the implementation with the `DriverNameResolver` in Java. Fix\nfiltering out of self entries, i.e. use full name matching instead of a prefix match.\n\n== 1.48.6 (2025-08-08)\n\n* **[Cluster]** Prevent standby replicated snapshots getting stuck if the address on the standby entry is invalid.\n\n== 1.48.5 (2025-07-21)\n\n* **[Java]** Add `ALLOW_ALL` and `DENY_ALL` authorisation supplier options for the Archive.\n* **[Java]** Add `ALLOW_ALL` and `DENY_ALL` authorisation supplier options for the ConsensusModule.\n* **[Archive]** Copy `response-endpoint`, `ttl` and `stream-id` URI parameters from the original channel definition when creating recordings and replays.\n* **[Archive]** Do not abort `ControlSession` after it was closed and prevent multiple `abort` calls from overwriting each other.\n* **[Archive]** Do not log warning when control response publication is disconnected or a control request image is going away, i.e. treat those events as normal client termination. To avoid unnecessary warnings when `AeronArchive` is being closed and there is a race between `close` message arriving at the Archive and images going unavailable or publication disconnecting.\n* **[C]** Fix client build to properly resolve `-DHAVE_BSDSTDLIB_H -DHAVE_ARC4RANDOM`. (https://github.com/aeron-io/aeron/issues/1836[#1836])\n* **[C]** Fallback to `/dev/urandom` if `arc4random` is not available.\n* **[C]** Add aeron_archive_wrapper's own sources to the `target_include_directories`. (https://github.com/aeron-io/aeron/pull/1835[#1835])\n* **[C]** Send client_close command to media driver when aeron is closing. (https://github.com/aeron-io/aeron/pull/1837[#1837])\n* **[C]** Add missing assignment operator to match the copy constructor.\n* **[C]** Error handling fixes for destination and endpoint creation.\n* **[C]** Use static structs for holding loss state and cleanup dynamically allocated memory during initial load. (https://github.com/aeron-io/aeron/issues/1833[#1833])\n* **[C]** Bump `CMake` to 4.0.3.\n\n== 1.48.4 (2025-06-27)\n\n* **[C]** Fix `aeron_cnc_is_file_length_sufficient`, i.e. take into account error buffer length. (https://github.com/aeron-io/aeron/issues/1828[#1828])\n* **[C]** Preserve `agent_on_start_func` override during driver startup procedure. (https://github.com/aeron-io/aeron/issues/1826[#1826])\n* **[C{plus}{plus} Wrapper]** Add methods to get the `registrationId` of an asynchronously created resource.\n* *Breaking:* **[Cluster]** Allow `ConsensusModuleExtension` to do work while election is in progress. (https://github.com/aeron-io/aeron/pull/1827[#1827])\n* **[Cluster]** Add `-ext` suffix to `ConsensusModuleExtension` archive client request and response channel aliases.\n* **[Driver]** Add `fragmentedFrameLength` getter to `Header`. (https://github.com/aeron-io/aeron/issues/1829[#1829])\n* **[AeronArchive]** Allow `NOT_CONNECTED` to be returned without throwing an exception before client is closed.\n* **[Cluster]** Fix `ClusterToolOperator#queryClusterMembers` return value when query times out.\n* **[Java]** Bump `Agrona` to https://github.com/aeron-io/agrona/releases/tag/2.2.4[2.2.4].\n* **[Java]** Bump `SBE` to https://github.com/aeron-io/simple-binary-encoding/releases/tag/1.35.6[1.35.6].\n* **[Java]** Bump `ByteBuddy` to 1.17.6\n* **[Java]** Bump `Shadow` to 8.3.7.\n* **[Java]** Bump `JUnit` to 5.13.2.\n* **[Java]** Bump `Checkstyle` to 10.26.0.\n* **[Java]** Bump `jgit` to 7.3.0.202506031305-r.\n\n== 1.48.3 (2025-06-20)\n\n* **[Java/C]** Use `untethered-linger-timeout` in IPC publications.\n* **[Java]** Bump `Agrona` to 2.2.3.\n* **[Java]** Bump `SBE` to 1.35.5.\n\n== 1.48.2 (2025-06-12)\n\n* **[Java/C/C{plus}{plus}]** Poll Archive client on slow duty cycle. (https://github.com/aeron-io/aeron/pull/1817[#1817])\n* **[C]** Close send channel endpoints immediately once they have been released by the sender thread, don't wait for a managed resource cycle. (https://github.com/aeron-io/aeron/pull/1816[#1816])\n* **[Java]** Cluster tool exit code bugfix. (https://github.com/aeron-io/aeron/pull/1818[#1818)]\n* **[Java]** Fix IndexedReplicatedRecording LIVE_CHANNEL config (https://github.com/aeron-io/aeron/pull/1815[#1815])\n* **[Java]** Bump `JUnit` to 5.13.1.\n\n== 1.48.1 (2025-06-06)\n\n* **[C{plus}{plus}]** Add warning that C{plus}{plus} API will be removed in 1.50.0.\n* **[Java]** Publish artifacts to Central Portal using OSSRH Staging API.\n* **[Java]** Bump `Agrona` to 2.2.2.\n* **[Java]** Bump `SBE` to 1.35.3.\n* **[Java]** Bump `JGit` to 7.2.1.202505142326-r.\n* **[Java]** Bump `Checkstyle` to 1.25.0.\n* **[Java]** Bump `Gradle` 8.14.2.\n\n== 1.48.0 (2025-06-03)\n\n=== Noteworthy Changes\n\n* `ExclusivePublication#revoke`.\n+\n--\nRelease publisher and subscriber resources immediately with exclusive publication revoke. Publication will not linger and not allow any trailing loss to be resolved. Subscription will not wait for any data to be received.\n\nNOTE: Media driver and client code (publisher and subscriber) must run Aeron 1.48.0 or higher.\n--\n+\nFor more information see https://github.com/aeron-io/aeron/wiki/Publication%23revoke[Publication#revoke] wiki page.\n\n* `Image#reject`.\n+\nReject incoming sessions from a publisher. This allows you to quickly stop data flow in scenarios where the data is no longer needed or is invalid.\n+\nFor more information see https://github.com/aeron-io/aeron/wiki/Image%23reject[Image#reject] wiki page.\n\n* Track connection status in `AeronCluster`.\n+\n--\n`AeronCluster` now contains a state machine to track connection status. The state machine is updated during poll operations (`AeronCluster#pollEgress` and `AeronCluster#controlledPollEgress`) and while sending data to the Cluster (i.e. `AeronCluster#offer`, `AeronCluster#tryClaim`, `AeronCluster#sendKeepAlive`). If a break in communication is detected and it lasts for more than `AeronCluster.Context#newLeaderTimeoutNs()` then `AeronCluster` will close itself.\n\nNOTE: When `AeronCluster.Context#newLeaderTimeoutNs()` is not set the `AeronCluster` will wait for double the *leadership timeout* from an actual Cluster. If that is not available (i.e. Cluster is running on an older Aeron version) then it will fallback to a 10 seconds default value, i.e. will wait for 20 seconds.\n--\nIf `AeronCluster#ingressPublication` or `AeronCluster#egressSubscription` are used directly then it is a user responsibility to call new APIs in order to update the connection tracking state machine, i.e.:\n** After each invocation of the `offer`/`tryClaim` on the `AeronCluster#ingressPublication` a call to `AeronCluster#trackIngressPublicationResult` must be made.\n** Every time `AeronCluster#egressSubscription` is polled a call to `AeronCluster#pollStateChanges` must be made.\n\n* Response channels GA.\n+\nResponse channels have been promoted from experimental to General Availability. Users no longer need to enable experimental features to use this feature.\n\n* C &amp; C{plus}{plus} wrapper Archive client APIs GA.\n+\n--\nThe APIs have been promoted from experimental to General Availability, achieving feature-completeness and parity with Java.\n\nNOTE: Old C{plus}{plus} APIs will be decommissioned in *1.50.0*.\n--\n\n* Per-stream NAK counters.\n+\nTwo new stream-specific NAK counters where added:\n+\n** `snd-naks-received` (`typeId=19`) - tracks the number of NAKs received by the sender.\n** `rcv-naks-sent` (`typeId=20`) - tracks the number of NAKs sent by the receiver.\n\n* Affinity setting `AERON_DRIVER_ASYNC_EXECUTOR_CPU_AFFINITY` for async thread (`aeron_executor`) was removed.\n\n* Retransmit Receiver Window Multiple\n+\nTo avoid overwhelming receivers in the event of retransmissions, Aeron limits the amount of data sent in a single retransmission to a multiple of the receiver window. Previously, this multiple was 16 for unicast, 16 for min and tagged multicast, and 4 for max multicast. It now defaults to 16 for unicast, 4 for all multicast strategies, and can be configured with the properties `aeron.unicast.flow.control.rrwm` and `aeron.multicast.flow.control.rrwm`.\n\n* Linger timeout\n+\nThere is a new option to control how long untethered subscriptions will linger before being removed from flow control. If the new untethered linger timeout is not set, the default timeout is equal to the untethered window limit timeout. Previously, the untethered linger timeout was always equal to the window limit timeout. Now they can be changed independently. The new property name is `aeron.untethered.linger.timeout`. It can also be set via `untethered-linger-timeout` URI parameter.\n\n=== Changelog\n\n* **[Java]** Initialize `archiveId` early using CnC file if Aeron instance is not specified.\n* **[Java]** Close extension's Archive client.\n* **[Java]** Close snapshot replication before replay and recording are closed.\n* **[Java]** Adjust archive client name based on the configuration.\n* **[C]** Add client name for the implicit Aeron client created by the Archive client.\n* **[Java]** Name implicit Aeron clients based on their usage.\n* **[C]** Use `untethered-linger-timeout` on the receiver side.\n* **[Java]** Store `untethered-linger-timeout` in the log buffer metadata.\n* **[Java]** Fix a bug where `untethered-linger-timeout` was not added to the resulting URI.\n* **[Java]** Use `untethered-linger-timeout` on the receiver side.\n* **[Java]** Use `Publication#revoke` and `Image#reject` to close `ControlSession` resources.\n* **[Java]** Use `Publication#revoke` to abort replay session.\n* **[Java]** Don't through an exception when failing to copy a file within the data collector. This breaks other parts of the data collection on test failure (e.g. event log capture).\n* **[C]** Flow control retransmit receiver window multiple for C driver. (#https://github.com/aeron-io/aeron/pull/1807[1807])\n* **[C]** C version of untethered linger timeout. (#https://github.com/aeron-io/aeron/pull/1808[1808])\n* **[Java]** Require `ArchiveThreadingMode.INVOKER` if MediaDriver is running in the invoker mode.\n* **[Java/C]** Per stream NAKs. (#https://github.com/aeron-io/aeron/pull/1806[1806])\n* **[Java]** Add separate linger timeout for untethered subscriptions. (#https://github.com/aeron-io/aeron/pull/1801[1801])\n* **[Java/C]** `Publication#revoke`. (#https://github.com/aeron-io/aeron/pull/1781[1781])\n* **[Java]** Make cluster publish leader heartbeat timeout to clients. (#https://github.com/aeron-io/aeron/pull/1805[1805])\n* **[Java]** Require Aeron client to run in the invoker mode if MediaDriver is running with `ThreadingMode.INVOKER`, i.e. `Aeron.Context.useConductorAgentInvoker(true)` must be set when `Aeron.Context.driverAgentInvoker()` is set.\n* **[Java]** Add event code type for sequencer.\n* **[Java/C]** `Image#reject`. (#https://github.com/aeron-io/aeron/pull/1785[1785])\n* **[Java]** Fsync `archive.catalog` file to disc when shutting down Archive.\n* **[C]** Align flow control receiver timeout with Java, i.e. use `AERON_FLOW_CONTROL_RECEIVER_TIMEOUT` env variable instead of `AERON_MIN_MULTICAST_FLOW_CONTROL_RECEIVER_TIMEOUT`.\n* **[Java]** Remove legacy `aeron.MinMulticastFlowControl.receiverTimeout` config option, i.e. use `aeron.flow.control.receiver.timeout` directly.\n* **[C]** Remove experimental feature flag for response channels.\n* **[Java]** Remove experimental option for response channels for the response channels.\n* **[C]** MDC short send fix. (#https://github.com/aeron-io/aeron/pull/1770[1770])\n* **[Java]** Flow control retransmit receiver window multiple (#https://github.com/aeron-io/aeron/pull/1800[1800])\n* **[Java]** Prevent potential silent message loss on cluster ingress/egress.\n* **[Java]** Make AeronCluster track connection status.\n* **[Java]** Create new Ping message for archive client keepalive. (#https://github.com/aeron-io/aeron/pull/1799[1799])\n* **[Java]** File page aligned mark files. (#https://github.com/aeron-io/aeron/pull/1789[1789])\n* **[Java]** Increment snapshot counter after standby snapshots were successfully replicated.\n* **[Config]** Update code style to reduce use of '.*' imports.\n* **[Java]** Improve storage space exception detection.\n* **[Java]** Properly check for EOS flag. (#https://github.com/aeron-io/aeron/pull/1795[1795])\n* **[C]** Fix issue for untethered slow consumers impacting whole server. (#https://github.com/aeron-io/aeron/pull/1792[1792])\n* **[C{plus}{plus} Wrapper]** Remove 'experimental' indicator for C/C{plus}{plus} wrapper archive APIs. (#https://github.com/aeron-io/aeron/pull/1793[1793])\n* **[Java]** Refactor session liveness check.\n* **[C]** Use `async-executor` name for the async thread, i.e. align with the Java impl.\n* **[Java]** Use `async-executor` prefix for async threads.\n* **[Bash]** Simplify thread affinity listing.\n* **[Java]** Surface method to describe extension snapshot content in ClusterTool. Support printing snapshot entries as hex dumps.\n* **[C{plus}{plus}]** Add `#include <cstdint>`.\n* **[C{plus}{plus} Wrapper]** Add missing <cstdint> header. (#https://github.com/aeron-io/aeron/pull/1786[1786])\n* **[C]** Remove affinity settings for the async thread (`aeron_executor`).\n* **[Java]** Add TestIdleStrategy.\n* **[Java]** Synchronize session ids across cluster nodes. (#https://github.com/aeron-io/aeron/pull/1774[1774])\n* **[CI]** Add Clang 20 to the build matrix.\n* **[C]** Call close_session in `archive_close()`. (#https://github.com/aeron-io/aeron/pull/1778[1778])\n* **[Java]** Added close reason to consensus module extension call back on session close.\n* **[C]** Create log buffers sparse by default.\n* **[Java]** Create log buffers sparse by default.\n* **[Java]** Add context to the disconnected control session warning message, i.e. show the response streamId/channel pair to help identify client that was disconnected.\n* **[Java]** Use separate fragment assemblers for IPC and UDP inputs.\n* **[C{plus}{plus} Wrapper]** Sync `addAliasIfAbsent` method to ChannelUri. (#https://github.com/aeron-io/aeron/pull/1755[1755])\n* **[C{plus}{plus} Wrapper]** Allow setting the recording events channel. (#https://github.com/aeron-io/aeron/pull/1768[1768])\n* **[Java]** Use `MarkFile#timestampRelease`.\n* **[C{plus}{plus} Wrapper]** fix uri_buffer length in `Subscription.tryResolveChannelEndpointPort()`. (#https://github.com/aeron-io/aeron/pull/1767[1767])\n* **[Java]** Don't report error if the publication is closed or not connected during replay.\n* **[Doc]** Document the reserved range for Aeron counter typeIds. (#https://github.com/aeron-io/aeron/pull/1771[1771])\n* **[Java]** Update `sub-pos` iff the image was not closed. Otherwise, the JVM might crash with `SIGSEGV` while accessing closed `Position`.\n* **[CMake]** Only link to client for signal test.\n* **[C]** Add TERM signal handling to C media driver and supporting test.\n* **[C]** Add missing <mutex> header. (#https://github.com/aeron-io/aeron/pull/1765[1765])\n* **[CI]** Enable JDK 24 GA.\n* **[Java]** Do not send termination position to services if there aren't any.\n* **[Java]** Assert that a rejected image will reconnect after a liveness timeout.\n* **[C{plus}{plus}]** Use method references instead of capturing lambda to avoid warnings on Windows.\n* **[C]** Align counter API with the Java implementation: - Rename `*volatile` methods to `*release`. - Rename all `*_ordered` methods to `*_release`. - Add missing `_plain` methods.\n* **[Client]** Append block without modifying the underlying buffer, i.e. copy the entire block without the first header and the copy the header to complete write similar to `io.aeron.logbuffer.TermRebuilder.insert`.\n* **[Java]** Simple test fixes. (#https://github.com/aeron-io/aeron/pull/1760[1760])\n* **[Java]** Fix mock usage on JDK 21+.\n* **[C{plus}{plus}]** More `#include <chrono>` fixes for Windows.\n* **[C{plus}{plus} Wrapper]** Include `<chrono>` to fix compilation error on Windows.\n* **[C]** Return an actual ref count value after increment/decrement operation completes + rename `aeron_image_refcnt_volatile` to `aeron_image_refcnt_acquire`.\n* **[Java]** Switched to the new Agrona release functions. (#https://github.com/aeron-io/aeron/pull/1748[1748])\n* **[C]** Tidy up image list handling in subscription.\n* **[C]** Close an image when it is being removed from a subscription.\n* **[C]** Call `aeron_subscription_is_connected` to force an image list change number update so that image/log buffers can be freed by the conductor thread.\n* **[C]** Use `DeviceIoControl` to create sparse files.\n* **[C]** Create files with `FILE_FLAG_POSIX_SEMANTICS` on Windows.\n* **[C]** Specify `DELETE` access right when creating files on Windows.\n* **[C{plus}{plus} Wrapper]** Handle potential exception from `std::make_shared`.\n* **[C]** Use `FILE_ATTRIBUTE_SPARSE_FILE` upon file creation instead of using `DeviceIoControl`.\n* **[C]** Use `SHFileOperation` to delete file on Windows.\n* **[C]** Use `CreateFile` to open for r/w.\n* **[C]** Use `CreateFile` on Windows and enable `FILE_SHARE_DELETE` sharing mode to allow file to be deleted even if open mappings exist.\n* **[C{plus}{plus} Wrapper]** Remove definitions that shadow `aeron_logbuffer_descriptor.h` definitions.\n* **[C{plus}{plus}]** Configure ARM support in `Platform.h`.\n* **[C{plus}{plus} Wrapper]** Use randomized `aeron.dir` in `SystemTestParameterized`.\n* **[C]** Use `aeron_format_date` function from `aeron_strutil.h`.\n* **[C{plus}{plus} Wrapper]** Decrement ref count of an `Image` after it was created, because it was counted twice: once in the C code when looking the `aeron_image_t` and the second time by invoking `aeron_subscription_image_retain` inside the `Image` constructor.\n* **[C{plus}{plus}]** Await until `image_available` callback is called.\n* **[PowerShell]** CMake installation progress report.\n* **[PowerShell]** Tidy up CMake installation.\n* **[C]** Enable `--sanitise-build` on MacOS.\n* **[Java]** Use null values when resetting descriptor.\n* **[Java]** Use correlationId as sessionId.\n* **[Java]** Remove Mockito MockMaker setting from aeron-cluster.\n* **[Java]** Await recently added subscription in StreamStatTest. (#https://github.com/aeron-io/aeron/pull/1733[1733])\n* **[C]** Make sure aeron_array_fast_unordered_remove is used correctly (fixes #https://github.com/aeron-io/aeron/issues/1728[1728]).\n* **[Java]** Change log event for NAK received to use NAK length rather than packet length.\n* **[C]** Fix var-args bug in error reporting.\n* **[Java]** Bump `Agrona` to 2.2.1.\n* **[Java]** Bump `SBE` to 1.35.1.\n* **[Java]** Bump `Checkstyle` to 10.24.0.\n* **[Java]** Bump `Gradle` to 8.14.1.\n* **[Java]** Bump `ByteBuddy` to 1.17.5.\n* **[Java]** Bump `JUnit` to 5.13.0.\n* **[Java]** Bump `Mockito` to 5.18.0.\n* **[Java]** Bump `ASM` to 9.8.\n* **[C]** Bump `CMake` to 4.0.2.\n\n== 1.47.7 (2025-08-08)\n\n* **[Cluster]** Prevent standby replicated snapshots getting stuck if the address on the standby entry is invalid.\n* **[Java]** Publish artifacts to Central Portal using OSSRH Staging API.\n\n== 1.47.5 (2025-05-09)\n\n* **[Driver]** Check if `EOS` flag bit is set instead of the entire mask. (https://github.com/aeron-io/aeron/pull/1795[#1795])\n* **[Driver]** Record bytes lost in the loss report only once when a loss is detected, i.e. do not count the same loss when resending NAKs. (https://github.com/aeron-io/aeron/pull/1796[#1796])\n* **[Driver]** Prevent NetworkPublication's `pub-lmt` from wrapping around into the dirty term. (https://github.com/aeron-io/aeron/pull/1794[#1794])\n* **[Cluster]** Prevent ConsensusModule's state (`nextSessionId`) diverging between leader and follower nodes when a session is rejected during the authentication phase. (https://github.com/aeron-io/aeron/pull/1774[#1774])\n* **[Cluster]** Only send TerminationAck to the leader that requested it. (https://github.com/aeron-io/aeron/pull/1797[#1797])\n* **[Cluster]** Use separate fragment assemblers for IPC and UDP inputs.\n* **[Client/C]** Do not update image list change number when retaining/releasing images as those can be called from a client conductor thread.\n* **[Client/C{plus}{plus} Wrapper]** Use const on `Context.h` copy constructor.\n* **[Archive Client/C]** Call `close_session()` in `archive_close()`. (https://github.com/aeron-io/aeron/pull/1778[#1778])\n\n== 1.47.4 (2025-03-14)\n\n* **[Driver]** Increment retransmit count only if data was actually sent.\n* **[Cluster]** Fix buffer reference for ClusterMarkFile. (https://github.com/aeron-io/aeron/pull/1753[#1753])\n* **[Cluster/Archive]** Protect against access to the closed mark file.\n* **[Cluster/Archive]** Prevent JVM crash when opening an old version of the mark file (i.e. without a message header).\n* **[C{plus}{plus} Wrapper]** Add an AsyncDestination type definition to Subscription.h. (https://github.com/aeron-io/aeron/pull/1749[#1749])\n* **[C{plus}{plus} Wrapper]** Change wrapper version of the Context so that it does not hold a pointer to the underlying C context and track the values directly on the object and pass them through during init. Keep the pointer to the C context on the Aeron object to be properly cleaned up. (https://github.com/aeron-io/aeron/issues/1730[#1730])\n* **[C]** Check that an image exists in the Subscription when retaining/releasing. (https://github.com/aeron-io/aeron/issues/1752[#1752])\n\n== 1.47.3 (2025-02-14)\n\n* **[Java]** Reset `ClusterBackup` state if the Cluster node from which `ClusterBackup` is replaying the log is \"not available\", i.e. either no longer eligible (i.e. after an election) or the backup query cannot be sent to it (e.g. `ConsensusModule` is down).\n* **[Java]** Fix typo in ReplicationSession state change reason.\n* **[C]** Adding setter/getter methods for CPU affinity to media driver. (https://github.com/aeron-io/aeron/pull/1737[#1737])\n* **[C]** Support use of `sendmmsg()` without an address (i.e. when connect address is used). (https://github.com/aeron-io/aeron/pull/1742[#1742])\n* **[C]** Close image when it is being removed from a subscription.\n* **[C{plus}{plus} Wrapper]** Decrement ref count of an `Image` after it was created, because it was counted twice: once in the C code when looking the `aeron_image_t` and the second time by invoking `aeron_subscription_image_retain` inside the `Image` constructor.\n* **[C{plus}{plus} Wrapper]** Remove definitions that shadow `aeron_logbuffer_descriptor.h` definitions. (https://github.com/aeron-io/aeron/issues/1740[#1740])\n* **[Java]** Upgrade to Gradle 8.12.1.\n* **[Java]** Upgrade to Shadow 8.3.6.\n* **[Java]** Upgrade to Checkstyle 10.21.2.\n* **[Java]** Upgrade to ByteBuddy 1.17.1.\n\n== 1.47.2 (2025-01-30)\n\n=== Known issues\n\n* **[Java]** `ClusterBackup` might connect to two different Cluster nodes simultaneously whereby one is used to provide the live Raft log replay and to download the snapshots, whereas the other one is used to fetch the latest list of snapshot entries and the recording log metadata. As long as all of the Cluster nodes are \"in sync\" (i.e. have the same set of snapshots) then everything is ok. However, if the second node from which `ClusterBackup` fetches the snapshots was down for some time (i.e. does not have all of the snapshots) then the `ClusterBackup` might end up with a broken recording log whereby recording log entries will have a different log position to the underlying snapshot recordings.\n+\nFixed in https://github.com/aeron-io/aeron/releases/tag/1.47.3[1.47.3].\n\n=== Changelog\n\n* **[Java]** Fix a regression in `AeronArchive#listRecording` which could return arbitrary recording information when the specified `recordingId` is not found (does not exist or state is not `VALID`) instead of sending back `ControlResponseCode.RECORDING_UNKNOWN`.\n* **[C]** Apply `aeron.conductor.cpu.affinity` to the thead in `SHARED` threading mode and `aeron.sender.cpu.affinity` to sender/receiver thread in `SHARED_NETWORK` threading mode.\n* **[C]** Add support for setting CPU affinity for the async executor thread (`aeron.driver.async.executor.cpu.affinity` property and `AERON_DRIVER_ASYNC_EXECUTOR_CPU_AFFINITY` env variable).\n\n== 1.47.1 (2025-01-29)\n\n=== Known issues\n\n* **[Java]** `ClusterBackup` might connect to two different Cluster nodes simultaneously whereby one is used to provide the live Raft log replay and to download the snapshots, whereas the other one is used to fetch the latest list of snapshot entries and the recording log metadata. As long as all of the Cluster nodes are \"in sync\" (i.e. have the same set of snapshots) then everything is ok. However, if the second node from which `ClusterBackup` fetches the snapshots was down for some time (i.e. does not have all of the snapshots) then the `ClusterBackup` might end up with a broken recording log whereby recording log entries will have a different log position to the underlying snapshot recordings.\n+\nFixed in https://github.com/aeron-io/aeron/releases/tag/1.47.3[1.47.3].\n\n=== Changelog\n\n* **[Java]** Fix Archive regression where sending descriptors (i.e. https://github.com/aeron-io/aeron/blob/429ca685762a582032ab383987a0e1e20fc410ad/aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java#L1452[AeronArchive#listRecording], https://github.com/aeron-io/aeron/blob/429ca685762a582032ab383987a0e1e20fc410ad/aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java#L1368[AeronArchive#listRecordings], https://github.com/aeron-io/aeron/blob/429ca685762a582032ab383987a0e1e20fc410ad/aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java#L1407[AeronArchive#listRecordingsForUri], https://github.com/aeron-io/aeron/blob/429ca685762a582032ab383987a0e1e20fc410ad/aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java#L1719[AeronArchive#listRecordingSubscriptions]) could result in corrupted or duplicate data to be returned.\n* **[Java/C]** Send a new NAK if the gap length changes. (https://github.com/aeron-io/aeron/pull/1729[#1729])\n* **[Java]** Update the archiveId in the mark file if it is set when concluding the context. (https://github.com/aeron-io/aeron/pull/1726[#1726])\n* **[Java]** Fix java system test on Alpine (musl libc). (https://github.com/aeron-io/aeron/pull/1734[#1734])\n* **[C]** Fix var-args bug in error reporting.\n* **[C{plus}{plus}]** add aeronDir() method for context. (https://github.com/aeron-io/aeron/pull/1725[#1725])\n* **[C{plus}{plus}]** Fix the issue with .h file imports in apron-client cpp_wrapper. (https://github.com/aeron-io/aeron/pull/1727[#1727])\n* **[Java]** Upgrade to ByteBuddy 1.16.1.\n\n== 1.47.0 (2025-01-17)\n\n=== Important Update: Aeron is moving to a new GitHub organisation\n\nAeron is moving to a new GitHub organisation following its adoption by Adaptive in 2022. This transition marks a significant milestone in Aeron's journey, ensuring continued innovation and support for the world's leading low-latency message transport system.\n\nYou can find the new Aeron, SBE and Agrona repositories and all related resources at https://github.com/aeron-io[aeron-io].\n\nAll links to the previous repository location are automatically redirected to the new location.\nHowever, to avoid confusion, we recommend updating any existing local clones to point to the new repository URL. You can do this by using `git remote` on the command line:\n\n----\ngit remote set-url origin NEW_URL\n----\n\nThank you for your continued support and contributions to the Aeron Open Source project.\n\n=== Breaking changes\n\n* **[Java]** Agrona upgrade contains breaking changes. See\nhttps://github.com/aeron-io/agrona/releases/tag/2.0.0[Agrona 2.0.0 release notes].\n\nNOTE: `--add-opens java.base/jdk.internal.misc=ALL-UNNAMED` JVM option must be specified in order to run Aeron. In\naddition to `--add-opens java.base/java.util.zip=ALL-UNNAMED` that is required to running the Aeron Archive.\n\n=== Noteworthy Changes\n\n* Detect and terminate dormant Archive clients.\n+\nArchive will now send periodic heartbeat messages to each connected Archive client. By default, it is done once per\nsecond and can be configured via `aeron.archive.session.liveness.check.interval` property or programmatically via\n`io.aeron.archive.Archive.Context#sessionLivenessCheckIntervalNs(long)` method. If it detects that it cannot send such\na message for more than a connection timeout (i.e. `aeron.archive.connect.timeout`, defaults to 5 seconds) then it will\nclose the corresponding control session which will cause such Archive client to disconnect.\n\n* Eliminate interference between Archive clients.\n\n* C/C{plus}{plus} Wrapper implementation of the Archive client APIs.\n+\nIn terms of feature completeness and stability, they are still marked experimental, as there's a small chance some of the functions might change as the feature is hardened. Furthermore, a number of the async APIs have yet to be implemented.\n\n* Make the algorithm used for SecureRandom configurable and specify NativePRNGNonBlocking as the default. This stops the Archive from blocking when there is not sufficient entropy available.\n\n* Fix duplicate service messages during failover/restart when using multiple services in Cluster.\n+\nWhen service messages are being sent from multiple services, these can be enqueued in different orders. This means during failover/restart pending messages can be skipped or duplicated when a new leader is elected.\n+\nNOTE: *Upgrade procedure:* Those affected will need to do a clean shutdown (with a snapshot) and restart the whole cluster with the fix.\n\n* Invalidate Standby snapshots.\n+\nWhen invalidating latest snapshot both normal and Standby snapshots are taken into account. In order to prevent invalidated snapshots from being re-downloaded from the Standby node upon recovery.\n\n* New log events for NAK messages sent and received.\n+\n`NAK_RECEIVED` logging event was added when a NAK request is received by the sender. An existing `SEND_NAK_MESSAGE` event was renamed to `NAK_SENT` and logs a NAK message being sent by the receiver.\n\n* Prevent client process crashing by a pathologically slow consumer.\n+\nIf a call to `Controlled/FragmentHandler#onFragment` blocks for disproportionate amount of time, i.e. long enough for an `Image` to become unavailable. Then the corresponding log buffer will freed by the client conductor thread. Any further access to the log buffer will cause the client process to segfault. The `Image` was updated to prevent any further access once it was closed.\n\n=== Known issues\n\n* **[Java]** `AeronArchive#listRecording/listRecordings/listRecordingsForUri/listRecordingSubscriptions` might return a wrong recording information upon `BACK_PRESSURED`/`ADMIN_ACTION` result when sending a recording descriptor.\n+\nFixed in https://github.com/aeron-io/aeron/releases/tag/1.47.2[1.47.2].\n\n* **[Java]** `ClusterBackup` might connect to two different Cluster nodes simultaneously whereby one is used to provide the live Raft log replay and to download the snapshots, whereas the other one is used to fetch the latest list of snapshot entries and the recording log metadata. As long as all of the Cluster nodes are \"in sync\" (i.e. have the same set of snapshots) then everything is ok. However, if the second node from which `ClusterBackup` fetches the snapshots was down for some time (i.e. does not have all of the snapshots) then the `ClusterBackup` might end up with a broken recording log whereby recording log entries will have a different log position to the underlying snapshot recordings.\n+\nFixed in https://github.com/aeron-io/aeron/releases/tag/1.47.3[1.47.3].\n\n=== Changelog\n\n* **[Java]** Speedup `purgeSegments/deleteDetachedSegments` operations by only deleting files in a range between the current startPosition and the previous startPosition (purge) or the oldest existing segment file position (detached files).\n* **[C]** Fix dangling pointer in replay merge. (#1723)\n* **[Java]** Prevent segfaults through mark file API after close.\n* **[Java]** Trigger slow build on push to master.\n* **[Java]** Do not close Cluster archive when doing next rounds of backup queries since the replay might still be active. Also do not switch to `RESET_BACKUP` state unless the current Cluster node has switched its role and therefore is no longer eligible for replay.\n* **[Java]** Use ClusterEvent instead of ClusterException with Category.WARN.\n* **[Java]** Use ClusterEvent to report issues when stopping recording/replay + prevent an NPE when stopping a replay as `clusterArchive` could have been closed while in the BACKUP_QUERY stage.\n* **[C/C{plus}{plus}]** Change interval of driver keepalive error reporting.\n* **[C]** Update C driver to use the same matching logic as the Java driver for checking the validity of tagged publications and subscriptions.\n* **[CI]** Core dump dir creation.\n* **[CI]** Enable core dumps on Linux and MacOS.\n* **[CI]** Collect Windows core dump files.\n* **[CI]** Trigger slow build on PR.\n* **[C]** compare publication stream id with link stream id when checking for matching spy subscriptions (#1722)\n* **[CI]** Add `ubuntu-24.04-arm` to the build matrix for Java.\n* **[CI]** Use env to store base Java version.\n* **[Java]** Handle multiple PendingServiceMessageTrackers while producing consensus module patch.\n* **[CI]** Simplify log upload.\n* **[CI]** Fix crash log upload on Windows.\n* Bug/fix error with tagged channels reresolution (#1720)\n* **[C]** add a call to init the new fields in the logbuffer metadata (#1717)\n* **[Java]** Add the few missing fields for logbuffer descriptor (#1721)\n* **[Java]** Close temporary MarkFile when migrating from old version.\n* **[Java]** Write message header before mark file header in the `cluster-mark.dat` file to be able to use SBE features based on the `actingBlockLength` and `actingVersion`.\n* tidy up the namespace for exception_handler_t (#1715)\n* **[C]** Handle connecting to Archive without credentials. (#1716)\n* **[Java]** Write message header before mark file header in the `archive-mark.dat` file to be able to use SBE features based on the `actingBlockLength` and `actingVersion`.\n* **[Java]** Fix config not found issue.\n* **[Java]** Extract capturing lambda allocation to outside loop and yield when not making progress.\n* Replacement of ThreadHints.onSpinWait by Thread. (#1713)\n* **[Java]** Add `archiveId` to the ArchiveMarkFile.\n* **[Java]** Fix an off-by-one error while searching for counters.\n* **[Java]** Tidy up after #1711.\n* **[Java]** Add a test for address re-resolution back to the initial IP address.\n* fix resolution bug, when the new ip is back to udpChannel.remoteData， this can not be triggered `resolution changes` (#1711)\n* **[Java]** Use different URI for early access JDK build.\n* **[Java]** Fix Javadoc URI.\n* **[Java]** Run Mockito as Java agent for JDK 23+ compatibility.\n* **[CI]** Add JDK 23 to the build matrix.\n* **[Java]** Change comment prevent JDK23 javadoc warning, FIX #1710.\n* **[C]** Prevent double free of the aeron_exclusive_publication_t which is closed by the proxy.\n* **[Java]** Poll for remote Archive errors while awaiting log recording session to be created.\n* **[Java]** Add OS max/default values for SO_SNDBUF and SO_RCVBUF parameters to the log buffer metadata section.\n* **[C]** Add OS default/max fields for the `OS_SNDBUF/OS_RCVBUF` to the log buffer metadata section.\n* Fixes problem with socket snd/rcv buffer in logbuffer metadata. (#1707)\n* **[Java]** Add method to convert error code to String.\n* **[Java]** Remove remaining dynamic join APIs.\n* **[Java]** Move config printing option to the CommonContext.\n* **[Java]** Cleanup after #1705.\n* rename the null value to compatible with C{plus}{plus} (#1705)\n* **[Java]** Touch ups.\n* Logbuffer metadata extra fields (#1700)\n* **[C]** Rename `SEND_NAK_MESSAGE` to `NAK_SENT` so that it is symmetric with `NAK_SENT`\n* **[C]** Add `AERON_DRIVER_EVENT_NAK_RECEIVED` event logging.\n* **[Java]** Rename `SEND_NAK_MESSAGE` to `NAK_SENT` so that it is symmetric with `NAK_SENT`.\n* **[Java]** Add log event for when a NAK message is received.\n* Fix duplicate service messages during failover/restart when using multiple services (#1703)\n* **[Java]** Change Tests.sleep so that it uses LockSupport.parkNanos to prevent catching of InterruptedException and clearing the interrupt flag.\n* **[C]** Remove duplicate definition of aeron_semantic_version_compose. (#1701)\n* **[Java]** Close AeronArchive client if control response Subscription is disconnected.\n* **[Build]** correct release gradle cache path\n* **[Java]** Rename IngressAdapter onFragment to onMessage and remove interface to provide more appropriate naming.\n* **[Build]** Remove OSS c/c++ binary step in release workflow\n* **[Java]** Simplify synchronous connect.\n* **[Java]** Use Agrona Checksum classes.\n* **[Java]** Emit WARN event when ControlSession is closed abruptly + add reason to the ControlSession state transition log + increase default stale session check interval to 1s.\n* **[Java]** Fix `shouldRejoinAfterResting` test.\n* **[Java]** Add isConnected to Subscription.toString.\n* **[Java]** Add more detail to AeronArchive exception when subscription is not connected.\n* **[Java]** Add test utility for stubbing addition of counters.\n* **[C]** Close uri after parsing.\n* **[C]** Fix printing of the error message.\n* **[C]** Add `stream-id` and `pub-wnd` URI parameters.\n* **[C]** Add `AERON_ERROR_CODE_IMAGE_REJECTED` and `AERON_ERROR_CODE_PUBLICATION_REVOKED` error codes.\n* **[Java]** Add `IMAGE_REJECT` and `PUBLICATION_REVOKE` error codes.\n* **[Java]** Fix a race condition in `shouldRecordThenBoundReplayWithCounter`.\n* **[C]** Align logging for `aeron_driver_conductor_on_publication_error` with the Java implementation.\n* **[Java]** Use AeronEvent when logging onPublicationError and add additional parameters.\n* **[C{plus}{plus}]** Close broadcast_receiver to avoid leaking scratch buffer memory.\n* **[C]** Make broadcast_receiver scratch buffer expandable to accommodate for large responses from the media driver.\n* **[C]** Reset `aeron_uri_t` struct before parsing so that cleanup would not fail with a segfault.\n* **[Java]** Validate that channel URI does not exceed 4095 characters.\n* **[Java]** Allow client buffer to accept responses larger than 4KB.\n* **[C]** Validate channel URI length, i.e. it cannot exceed 4095 characters.\n* **[C]** Fix buffer potential buffer overflow on error.\n* **[C]** Add `AERON_URI_MAX_LENGTH` and extend `AERON_MAX_PATH` to 4096 bytes + cleanup.\n* **[CI]** Add Clang 19.\n* **[Java]** Rename extension property in AeronCluster.Context.\n* **[C{plus}{plus}]** Fix race conditions in Archive tests.\n* **[Java]** Clarify Javadoc for `scheduleTime/cancelTimer` operations.\n* **[Java]** Remove `ControlSessionDemuxer`.\n* **[C]** Fix sender MTU validation.\n* **[Java]** Fix error message for `SO_RCVBUF` validation.\n* **[Java]** Fix `logReplicationSessionStateChange`.\n* **[Java]** Validate that control publication is exclusive.\n* **[C]** Align driver conductor duty cycle with the Java implementation.\n* **[C]** Set not connected status immediately when network publication has no receivers.\n* **[Java]** Set not connected status immediately when network publication has no receivers.\n* **[Java]** invalidate standby snapshots inside `invalidateLatestSnapshot` (#1692)\n* **[Java]** Add ClusterMember to the ConsensusModuleControl.\n* **[Build]** Remove draft release notes\n* **[Java]** Improve checkstyle config for javadoc and apply changes.\n* Update checkstyle config for types.\n* Upgrade Checkstyle and BND.\n* **[Java]** Do not fail on heartbeat response.\n* **[Java]** Timeout `ControlSession` if AeronArchive is not being polled.\n* **[Java]** Image reject reason is stored as ASCII and shot be validated accordingly.\n* **[C]** Update C{plus}{plus} test code to await Archive startup.\n* **[C]** Use the same C code for the C{plus}{plus} wrapper tests.\n* **[C]** Simplify Archive test setup.\n* **[C]** Pass process handle variable into spawn/kill/await routines.\n* **[C]** Await Archive startup + fix PID resolution on Windows.\n* **[C]** More memory leak fixes.\n* **[C]** Free idle strategy state if it is not explicitly assigned.\n* **[C]** Close `aeron_uri_string_builder_t` to prevent a memory leak.\n* **[C]** Add unique `session-id` to both request and response channels to ensure that different Archive clients are not blocking each other if not being polled. This only applies if response channel does not specify `control-mode=response`.\n* **[C]** Fix `aeron_uri_string_builder`.\n* **[C]** Move helper parsing functions to the `aeron_parse_util.h`.\n* **[C]** Move `aeron_randomised_int32` implementation to `aeron_bitutil.c`.\n* **[C]** Use signals to terminate Archive process.\n* **[Java]** Make `RecordingLog.invalidateEntry` an O(1) operation and non-public.\n* **[Java]** Ensure that ENTRY_TYPE_SNAPSHOT always sorted after ENTRY_TYPE_STANDBY_SNAPSHOT and ENTRY_TYPE_TERM.\n* **[Java]** Move CRC classes to Agrona.\n* **[Java]** Surface the clusterId in the ConsensusModuleControl interface.\n* **[Java]** Add convenience method for determining if an endpoint uses a multicast address.\n* **[Java]** Remove MDS channel transports when endpoint is closing.\n* **[Java]** Make default secure random algorithm dependent on OS.\n* **[Java]** Make the algorithm used for SecureRandom configurable and specify NativePRNGNonBlocking as the default.\n* **[C]** Use the correct function point for on_request_setup when in non-shared modes.\n* **[C]** add a README for the C Archive client\n* **[C]** Use separate variable for disabling status messages.\n* Remove function macro UINT8_C() from AERON_DATA_HEADER_UNFRAGMENTED definition. (#1685)\n* **[Java]** Update RecoverPlan after standby snapshot replication completes with the replicated snapshot entries.\n* **[Java]** Use subtract and compare to zero for all deadline calculations for consistency.\n* **[Java]** Ensure that deadline checking is wrapping safe and use separate variable for disabling status messages.\n* **[C]** Use next_sm_deadline_ns instead of last_sm_time_ns as the code is simpler and safer.\n* **[Java]** Use nextSmDeadlineNs instead of lastSmTimeNs as the code is simpler and safer.\n* **[C]** Fix image matching check on `stream_id` plus refactor.\n* **[Java]** Do not log warning if the PublicationImage is active.\n* **[Java]** Fix SM timeout check.\n* **[C]** Stop sending status messages on draining/lingering images when a new publication image with the same channel/stream/session is created.\n* **[Java]** Use more accurate check for sm timeout.\n* **[Java]** Stop sending status messages on draining/lingering images when a new publication image with the same channel/stream/session is created.\n* **[C]** Align send channel validation with Java.\n* **[C]** Extract subscription matching logic and reuse across different methods.\n* Undef if defined major() and minor() defined in sys/sysmacros.h (#1681)\n* **[Java]** Send `appVersion` in the `NewLeadershipTerm`.\n* **[C]** Align network subscription validation with the Java side + fix clashing subscription validation.\n* **[C]** Extend subscription clash validation with a `isResponse` check, i.e. do not allow `control-mode=response` match a non-response subscription.\n* **[Java]** Extend subscription clash validation with a `isResponse` check, i.e. do not allow `control-mode=response` match a non-response subscription.\n* **[C]** Ensure that we use the incoming channel uri's control_mode to verify if it as response channel request when checking for clashing subscriptions.\n* **[Java]** Enable response channels test.\n* **[C]** Take into account `is_response` field when linking network Subscriptions.\n* **[C]** Create wildcard session interest when `session-id` not specified.\n* **[C]** Free allocated memory in `aeron_data_packet_dispatcher_add_subscription` in case of initialization error.\n* **[Java]** Remove session id from response channel when replaying via response channels.\n* **[Java]** Fix a race whereby subscription might try to add an image that was already removed when Aeron client gets shutdown.\n* **[Java]** Modify receive channel matching by delegating to `isWildcardOrSessionIdMatch`, i.e. take into account `isResponse` flag.\n* **[Java]** Capture complete AeronStat by delaying close after a test was executed.\n* **[Java]** Check for errors before test-level cleanup callbacks are executed, i.e. ensure that we get access to the complete set of counters before things are being closed.\n* **[Java]** Add unique `session-id` to both request and response channels to ensure that different Archive clients are not blocking each other if not being polled. This only applies if response channel does not specify `control-mode=response`.\n* enterElection reason message tidy up. (#1677)\n* **[C]** Prevent a segfault caused by a pathologically slow consumer, i.e. when a call to `Controlled/FragmentHandler#onFragment` blocks until an Image becomes unavailable and the corresponding log buffers are being freed by the client conductor.\n* **[Java]** Increase resting timeout to trigger the segfault condition.\n* **[Java]** Add PathologicallySlowConsumerTest.\n* **[Java]** Prevent a process segfault caused by a pathologically slow consumer, i.e. when a call to `Controlled/FragmentHandler#onFragment` blocks until an Image becomes unavailable and the corresponding log buffers are being freed by the client conductor.\n* **[Java]** Deprecate `aeron.cluster.members.ignore.snapshot` property.\n* **[Java]** Add commit position counter id to ConsensusModuleControl interface.\n* **[Java]** Generate unique control response stream id for AeronArchive in the Cluster. (#1670)\n* Use provided credentials on standby snapshot (#1674)\n* **[Java]** Support time travel Cluster tests.\n* **[Java]** Extend test to assert actual position recovery.\n* Tethering join position (#1672)\n* cppbuild exitcode=1 when unknown arg. (#1673)\n* **[Java]** Send recording descriptors the same way other responses are sent, i.e. retry on failure and preserve relative order of the responses.\n* **[Build]** Replace use of 7-zip in cppbuild.ps1 with the PowerShell built-in Expand-Archive.\n* **[Java]** Use `term-length=64k` for the local control request/response channels.\n* **[Java]** Add additional information that is useful for debugging, include the name of the aeron directory on the MediaDriver toString and the thread name for the background executor thread.\n* **[Java]** Use `sendErrorResponse` when sending errors back from the Archive.\n* **[Java]** Remove ControlResponseProxy parameter.\n* **[Java]** Schedule RecordingSignal sending the same way the responses are scheduled.\n* **[Java]** Track total snapshot duration on follower nodes.\n* **[C]** Do not fail fast on I/O exceptions in the send/receive path, i.e. process remaining transports/publications/destinations in the same duty cycle.\n* **[Java]** Do not fail fast on IOExceptions in the send/receive path, i.e. process remaining transports/publications/destinations in the same duty cycle.\n* **[Java]** Add end-to-end test for session interest fix.\n* **[Java]** Count bytes received when the number of transports exceeds `TransportPoller#ITERATION_THRESHOLD`.\n* **[Java]** Add testing callback that will put the test method name onto the thread name.\n* **[Java]** Do not refine response channel for a ClusterSession during a replay or loading of a snapshot.\n* **[C]** Check subscribed sessions before removing a stream interest before removing by stream_id.\n* **[build]** remove .CMakeLists.txt.swp\n* **[Java]** Assert that synchronous ControlSession calls are only allowed from a conductor thread.\n* **[Java]** Fix race with new Image becoming available while reject errors are asserted.\n* **[Java]** Add debug wrapper for `messageTimeout`.\n* **[Java]** Remove expensive file existence checks when scheduling segment file deletion.\n* **[Java]** Delete segment files without renaming so that conductor thread will not be blocked for a long period of time.\n* **[C]** Use plain read and write of the \"begin\" field in Dekker's algorithm as it is properly fenced.\n* **[Java]** Use plain read and write of the \"begin\" field in Dekker's algorithm as it is properly fenced.\n* **[Java]** Fix endianness when accessing string fields.\n* **[C]** Rename `AERON_GET_VOLATILE -> AERON_GET_ACQUIRE` and `AERON_PUT_ORDERED -> AERON_SET_RELEASE` to reflect the actual semantics of those operations and to match the naming of the Java APIs.\n* **[C]** Remove `AERON_PUT_VOLATILE`.\n* **[C]** Optimize SM and loss handling by using acquire/release operations with fences.\n* **[Java]** Optimize SM and loss handling by using acquire/release operations with fences.\n* **[Java]** Keep publishing position updates from canvass and into nominate state so other cluster node members can take action in extended election timeout durations.\n* **[Java]** Make it clear that appointed leader config is a testing feature only.\n* **[Java]** Don't return error when stopReplay is called on a replay that does not exist (likely already stopped).\n* **[Gradle]** Disable auto detection of JVMs to force a specific JVM for test execution in CI.\n* **[Java]** Update Cluster documentation around operations that require looping until success.\n* **[Java]** Tidy up incrementing of ClusterBackup snapshot counter.\n* **[Java]** Expose a counter on the Cluster Backup to track the number of snapshots downloaded.\n* **[Java]** Have ClusterTool.describeLatestConsensusModuleSnapshot return an indication of failure or success.\n* **[Java]** Migrate atomic field updaters to var handles.\n* **[Java]** Use storeStoreFence in HeaderWriter now VarHandle is available.\n* **[Java]** Move CapturingPrintStream to test-support.\n* **[Java]** Await client error before asserting static counter state.\n* **[Java]** Suppress Checkstyle LineLength for package-info.java files.\n* **[Java]** Suppress Checkstyle LineLength for package-info.java files.\n* **[Java]** Update schema reference (#1664)\n* **[Java]** Cluster tool refactoring to allow extensions (#1665)\n* **[Java]** Introduce VarHandles to begin replacing atomic field updaters.\n* **[Archive]**Enhance the ArchiveTool.delete-orphaned-segments method (#1661)\n* **[Gradle]** Simplify version management.\n* **[Java]** More robust checking of symbolic links.\n* **[Java]** Ensure that DataCollector does not traverse symbolic links when finding files.\n* **[Java]** Use typesafe version catalogs.\n* **[Git]** Exclude `buildSrc`.\n* **[Build]** Correctly disable deprecation message in the Windows build.\n* **[Build]** Add deprecation message to C{plus}{plus} API.\n* **[Java]** Include an explicit name to the NameResolver interface to aid logging/debugging.\n* **[Java]** Use unique `session-id` when creating live log recording and replaying data from the Cluster to ensure that the old replay data is not being picked up upon restart/reset.\n* **[Java]** Update ClusterTest.shouldCatchupFollowerWithSlowService to account for fragment limits and being unresponsive due to some OSs sleeping for 16ms rather than the requested 1ms.\n* **[Java]** Poll for archive response when loading snapshot any time a break occurs.\n* **[Java]** Support consensus module snapshot extension.\n* Error Frames and User Image Invalidation (#1604)\n* **[C]** Add REMOVE_BY_DESTINATION_ID to debug logging.\n* **[Java]** Add REMOVE_BY_DESTINATION_ID to debug logging.\n* **[Java]** Upgrade to Agrona 2.0.1.\n* **[Java]** Upgrade to SBE 1.34.1.\n* **[Java]** Upgrade to ByteBuddy 1.15.11.\n* **[Java]** Upgrade to JUnit 5.11.4.\n* **[Java]** Upgrade to Mockito 5.15.2.\n* **[Java]** Upgrade to Checkstyle 10.21.1.\n* **[Java]** Upgrade to Gradle 8.11.1.\n* **[Java]** Upgrade to Shadow 8.3.5.\n\n== 1.46.8 (2025-01-13)\n\n* **[Java]** Re-resolve endpoint address when address changes back to the original one. (https://github.com/aeron-io/aeron/pull/1711[#1711])\n* **[Java]** Update RecoverPlan after standby snapshot replication completes with the replicated snapshot entries.\n\n== 1.46.7 (2024-10-25)\n\n* **[Java]** Prevent an untethered subscription re-joining the stream at an old position.\n\n== 1.46.6 (2024-10-15)\n\n* **[Java]** Fix a performance regression in `ControlTransportPoller/DataTransportPoller` when the number of transports is larger than five, i.e. the `bytesReceived` count was zero and as a result `IdleStrategy.idle()` was invoked on each duty cycle.\n* **[Java]** Assert that synchronous ControlSession calls are only allowed from a conductor thread.\n* **[C]** Check subscribed sessions before removing a stream interest before removing by stream_id.\n* **[Java/C]** Do not fail fast on I/O exceptions in the send/receive path, i.e. process remaining transports/publications/destinations in the same duty cycle.\n* **[Java]** Make it clear that appointed leader config is a testing feature only.\n* **[Java]** Keep publishing position updates from canvass and into nominate state so other cluster node members can take action in extended election timeout duration.\n* **[Java]** Track total snapshot duration on the follower nodes.\n* **[Java]** Upgrade to Agrona 1.23.1.\n\n== 1.46.5 (2024-09-28)\n\n* **[Java]** Delete segment files without renaming so that the Archive conductor thread will not be blocked for a long period of time.\n* **[Java]** Remove expensive file existence checks when scheduling segment file deletion.\n\n== 1.46.4 (2024-09-24)\n\n* **[C]** Align loss and SMs handling between Java and C media drivers.\n\n== 1.46.2 (2024-09-11)\n\n* **[Java]** Fix ClusterBackup issue whereby upon restart it could end up reading data from the previously started replay thus failing to recover the log.\n\n== 1.46.1 (2024-08-30)\n\n* **[Java]** Fail build on JavaDoc errors.\n* **[Java]** Remove Nashorn dependency.\n* **[Java]** Upgrade to Checkstyle 10.18.0.\n* **[Java]** Upgrade to Mockito 5.13.0.\n* **[Java]** Upgrade to ByteBuddy 1.15.1.\n\n== 1.46.0 (2024-08-24)\n\n=== Breaking changes\n\nNOTE: *JDK 17 is required for compiling and running!*\n\n=== Changelog\n\n* **[Java]** Use new Selector API and fix samples.\n* **[Java]** Fix config validation on JDK 17  by using an external Nashorn ScriptEngine.\n* **[CI]** Execute tests using `ubuntu-24.04` and `macos-latest`.\n* **[Java]** Upgrade to Agrona 1.23.0.\n* **[Java]** Upgrade to SBE 1.33.0.\n* **[Java]** Upgrade to Gradle 8.10.\n* **[Java]** Upgrade to ByteBuddy 1.15.0.\n* **[Java]** Upgrade to Shadow 8.3.0.\n* **[Java]** Upgrade to bnd 7.0.0.\n* **[Java]** Upgrade to Mockito 5.12.0.\n* **[Java]** Upgrade to AsciiDoctor 2.5.13.\n* **[Java]** Upgrade to JGit 6.10.0.202406032230-r.\n* **[Java]** Force ASM 9.7.\n\n== 1.45.2 (2025-09-26)\n\n* **[Java]** Backport Image changes to protect against stalled subscribers.\n* **[Java]** Backport releasing to Maven Central Portal.\n* **[C/C{plus}{plus} Wrapper]** Include `<chrono>` to fix compilation error on Windows.\n* **[CI]** Build changes to support new release process.\n\n== 1.45.1 (2024-10-24)\n\n* **[Java]** Prevent an untethered subscription re-joining the stream at an old position.\n\n== 1.45.0 (2024-08-21)\n\n=== Noteworthy Changes\n\n* Per-Stream Session Limits\n+\nAllows users to limit the number of different sessions (i.e. distinct publication images) that can be created connecting to the same subscription (channel &amp; stream). This can be used to avoid resource exhaustion the driver hosting the subscription, when clients on other drivers are over zealously creating publications.\n* Infer Group Subscriptions for Recovery Behaviour\n+\nWhen using Multi-Destination Cast, if the user wants to make use of group semantics for nakking and retransmission, then previously group=true needed to be added to the channel configuration on the subscription. Failing to do so would potentially cause excessive nakking. This feature will set a flag on the SETUP message that the subscription can use to automate the setting of this configuration option. Setting group=true/false will override the derived value.\n\n* Static Counters\n+\nAdd new API to create static counters whose lifetime is decoupled from an Aeron client instance that created them. Unlike the normal counters which are deleted when an Aeron client is closed or times out, the static counters remain valid until the MediaDriver is closed.\n+\nStatic counters are similar to the system counters which are created by the MediaDriver upon startup. Such counters cannot be delete and only one instance of each counter can exist per MediaDriver instance. A static counter is identified by a tuple &lt;typeId, registrationId&gt; which are specified at the creation time. Calling addStaticCounter with the same &lt;typeId, registrationId&gt; multiple times will only create a counter once with subsequent calls returning the id of an existing counter. An exception will be thrown upon an attempt to specify &lt;typeId, registrationId&gt; of an existing non-static counter.\n\n* Archive Integration of Response Channels\n+\nUsers of the AeronArchive client can make use of response channels for control, replay and replication communication paths.\n\n* Archive Logging Improvements\n+\nTo increase visiblity of replay and recording session state changes.\n+\nEnable either REPLAY_SESSION_STATE_CHANGE or RECORDING_SESSION_STATE_CHANGE when configuring the aeron agent.\n\n* ReplayMerge Backoff\n**To slow down internal state transitions that involve calls to ‘get max recorded position’.\n** Reduces network traffic.\n** Greatly reduces log output when debugging is enabled.\n\n* RetransmitHandler Improvements\n+\nOn unicast channels, a received NAK may immediately replace a previous received NAK so long as the offset has increased.\n\n* Retransmitted Bytes Counter\n+\nTo understand network conditions, it can be useful to know how many bytes have been retransmitted due to reception of NAKs.\n+\nWe have exposed a new counter “Retransmitted bytes”, which serves as an approximate indicator of this information.\n+\nThe counter only approximates the true value, as MDC retransmits are only recorded once, rather than N times (once for each destination). It is also worth noting that the retransmitted bytes are not included in the total bytes sent counter value. We may improve these aspects in the future.\n\n* C{plus}{plus} API Deprecation\n+\nAeron currently has two C{plus}{plus} APIs, a pure C{plus}{plus} version and a wrapper over the C API. This release we are no longer adding new features to the C{plus}{plus} API. This include being able to offer raw `uint8_t *` values and the new static counters feature.\n\n=== Changelog\n\n* **[Java Cluster]** IngressAdapter to delegate to ConsensusModuleAgent when schema id is unknown\n* **[C{plus}{plus} Wrapper]** Correctly implement tracking of context on the header.\n* **[C/C{plus}{plus}]** Do not compile with `-Ofast` compilation level.\n* **[C/C{plus}{plus}]** Pass custom optimization level to the MSVC compiler.\n* **[Build]** Download and install CMake as part of the build process to ensure latest version of CMake.\n* **[C]** Tidy up aeron_reallocf to work correctly on allocation failure.\n* **[C Driver]** Expose try_connect_stream method for use from within ATS.\n* **[C Driver]** Remove unconnected stream message.\n* **[C Driver]** Fix string format for AERON_SET_ERROR arguments.\n* **[C/C{plus}{plus}/Java]** Add per channel untethered window limit and resting timeouts (#1588)\n* **[C{plus}{plus} Wrapper]** Add findByTypeIdAndRegistrationId to C{plus}{plus} Wrapper.\n* **[Java Driver]** Pre-start async task executor threads to avoid inheriting affinity from the conductor thread.\n* **[Java Cluster]** Print `serviceId` when throwing \"ack out of sequence\" exception.\n* **[C{plus}{plus}]** Fixed set thread name. (#1594)\n* **[Java/C{plus}{plus} Archive]** Use response channels in the Archive Client (#1560)\n* **[PS]** Add Powershell script for Windows build.\n* **[C/Java Driver]** Do not resolve self hostname when starting MediaDriver to avoid a DNS-induced stall. Make `resolverName` required when driver name resolution is used.\n* **[Java/C/C{plus}{plus} Client]** Propagate context though assembled header when using the buffer builder.\n* **[Java]** Fix FixedLossGenerator to not drop frames before the specified \"drop region\".\n* **[C/Java Driver]** Expose counter for number of bytes retransmitted.\n* **[Java Cluster]** Remove redundant param.\n* **[C]** Move validation earlier in the flow to prevents leaks in ATS on validation failure.\n* **[Java Samples]** Use context instead of image by session id to resolve Image on request message.\n* **[C/Java Driver]** Add session limits for a stream to prevent resource exhaustion of resources on drivers hosting subscriptions (#1598)\n* **[Java Cluster]** Add support for installing schema extensions into the ConsensusModule.\n* **[Java Samples]** Change response channels sample to use controlled poll to prevent response channel test failures.\n* **[Java]** Add some additional error test to ReplayMerge and test for concurrent ReplayMerges along with additional error reporting.\n* **[C{plus}{plus} Archive]** Rethrow original exception in ReplayMerge so information is not lost. #1587\n* **[C]** add and use aeron_mkdir_recursive (#1602)\n* **[Java]** Version interface to allow testing usage of it (#1605)\n* **[Java]** Include file name when mark file version validation fails.\n* **[Java]** Correct offsets for response setup flyweight in ReceiveChannelEndpointThreadLocals.\n* **[Java]** Default cluster log flow control to driver default which will commonly be max now that overruns can go up to half a term.\n* **[Java Archive]** Assign client name to Archive's Aeron client.\n* **[Java Cluster]** Assign client name to Cluster Aeron clients.\n* **[C{plus}{plus}/Java Client]** Pre-touch mapped log buffers on the client if `Aeron.Context.preTouchMappedMemory()` returns `true`, i.e. client configuration takes precedence over channel parameters and media driver configuration.\n* **[Java Cluster]** Delegate decision on unknown schema to consensus agent\n* **[Java Cluster]** Ensure service count is zero when consensus module extension is installed.\n* **[C/C{plus}{plus}/Java]** Annotations to support config/counters documentation/validation (#1593)\n* **[C/Java Driver]** Add unicast semantics to RetransmitHandler (#1603)\n* **[Java Cluster]** Add archive and egress response endpoints for cluster and make them optional.\n* **[Java Cluster]** Remove default for cluster services directory configuration and leave resolution of that value until conclude.\n* **[Java Archive]** Use publication.availableWindow instead of looking up the pubLmt counter.\n* **[Java Cluster]** First cut at consensus module extension integration.\n* **[C Driver]** Fixes for fixed loss interceptor used for testing.\n* **[Java Cluster]** Remove unused params.\n* **[Java Archive]** Separate lines for debugging.\n* **[Java Cluster]** Provide access to archive and aeron for consensus module extension.\n* **[Java Cluster]** Delegate cluster session creation to extension if available.\n* **[Java Cluster]** delegate callbacks to extension on state changes.\n* **[C/Java Driver]** Track EOS for each receiver connected to the NetworkPublication (#1606)\n* **[C Client]** Declare a volatile pointer to `aeron_image_list_stct` instead of a pointer to a volatile struct. (#1607)\n* **[Java Cluster]** A more decoupled approach to ConsensusModuleExtension.\n* **[Java Cluster]** Provide access to log publication after an election for consensus module extension.\n* **[Java]** Improve error messages when publication errors occur.\n* **[Java Cluster]** Consensus module extension API refinements.\n* **[Java Cluster]** Add replay of session open and close delegation to consensus module extension.\n* **[Java Cluster]** Add replay of extension message delegation to consensus module extension.\n* **[Java Cluster]** ConsensusControlState to be passed from Agent to Extension and allows control.\n* **[C/C{plus}{plus} Client]** Use `aeron-client-conductor` as a client conductor agent name across all implementations.\n* **[Java Cluster]** pass actingVersion to onMessage of ConsensusModuleExtension\n* **[Java]** Version generated class to have toString\n* **[Bash]** Add option to disable C{plus}{plus} Archive API.\n* **[Java Cluster]** Add ability to access response publication for a session and only update keepalive status when open.\n* **[Java Cluster]** Handle cluster extension messages on egress listener side.\n* **[Java Cluster]** Add extension message support to AeronCluster client.\n* **[C{plus}{plus} Wrapper]** Allow pass through of `const uint8_t *` buffers.\n* **[C{plus}{plus}]** Replace array with pointer to address code-ql issue on Publication in C{plus}{plus} API and Wrapper.\n* **[Build]** Fix aeronmd rpath in the generated package.\n* **[Java Cluster]** Use separate archive control session for consensus module extension.\n* **[Java Cluster]** Add work cycle callback for ConsensusModuleExtension.\n* **[C/Java Driver]** Replace exception with increment of counter for overflowing the retransmit pool.\n* **[C]** add -w to C AeronStat app (#1614)\n* **[Java Cluster]** Provide access to encoded principle for authorising consensus module extension session.\n* **[Java Archive]** Add support for response channels startReplay and startBoundedReplay.\n* **[Java Samples]** Fix StreamStat mapping of `sub-pos` which had a join position included in the channel uri and prevented it to be merged with the rest of stream positions. Also ensure that longest channel uri is printed.\n* **[Java Cluster]** Extract ReplicationParams for RecordingReplication to allow for more flexible configuration.\n* **[Java Cluster]** Use response channels for log replication during the election.\n* **[C Driver]** Handle a case when `getifaddrs` call fails.\n* **[Java Cluster]** Provide access to cluster client session from extension via an interface to restrict usage.\n* **[Java Cluster]** Add source response channel to MultipleRecordingReplication.\n* **[Java Cluster]** Simplify hash code for RecordingLog.\n* **[Java Cluster]** Use response channel in ClusterBackup when connecting to the cluster archive.\n* **[Java Archive]** add helper method to determine if Archive Context is concluded (#1617)\n* **[Java Cluster]** Validate standby snapshot (#1616)\n* **[Java]** Add tests for HeaderWrite class + always combine values in the same order (from MSB to LSB).\n* Intercept replay and recording session state changes (#1615)\n* **[Java Archive]** Use `getMaxRecordedPosition` instead of `getRecordingPosition` to avoid two different calls while resolving position. Also call `getMaxRecordedPosition` only once in the `attemptLiveJoin`.\n* **[Java Cluster]** Add ability to close a cluster session from the consensus module extension.\n* **[C/Java Archive/Driver]** add backoff to calls to getMaxRecordedPosition in ReplayMerge (#1618)\n* **[Java Archive]** Prevent replay token clashes.\n* **[C Samples]** Fix CnC version printed in AeronStat header.\n* **[Build]** Modify CMake build to have exports for Aeron targets.\n* **[Java Cluster]** separate ingress and log message callback\n* **[Java Cluster]** Method renaming for log extension dispatch.\n* **[Java Cluster]** Add authorization service to ConsensusModuleControl\n* **[Java Cluster]** cluster session - do not delete principal when transitioning to open\n* **[Java Cluster]** Clean up session logging.\n* **[Java Cluster]** Add event log for getMaxRecordedPosition call.\n* **[Java Cluster]** callback on prepare for new leadership in ConsensusModuleExtension\n* **[Java Cluster]** Consistent checking of leadership id in admin request to cluster.\n* **[Java Cluster]** Add reason for ELECTION_STATE_CHANGE + log state transition after the logic was executed.\n* **[Java Cluster]** Process standby snapshot control signals like normal snapshot signals. (#1634)\n* **[C Client]** Fix #1633, only get channel status if pointer is non-null.\n* **[C{plus}{plus} Wrapper]** Fix Image wrapping in a couple of cases improvements. Delete implicit *FragmentAssembler copy/move ctors/operators (#1639)\n\n* **[Java Archive]** Reduce archive listRecordingsForUri idling (#1638)\n* **[C Client]** Return an error if the client name is too long (100 chars) instead of truncating.\n* **[C{plus}{plus} Wrapper]** Use a copy constructor for wrapper Context to better handle resource management.\n* **[C/Java]** Send a flag on SETUP to indicate that the publication is expecting a group of subscription and use that to select the multicast feedback delay generator when inferring behaviour.\n* **[Java Driver]** Set default on stream session limit property.\n* **[Java Cluster]** Consolidate logic for closing a cluster session in the consensus module.\n* **[Java/C/C{plus}{plus} wrapper]** Add new API to create static counters. (#1625)\n* **[C{plus}{plus} Wrapper Client]** Implement offer with raw pointer and length for exclusive publications.\n* **[Java Cluster]** Do not reinstate cluster sessions that have timed out but not got committed before a change of leadership.\n* **[C/Java Samples]** Align implementation of AeronStat.\n* **[Java Cluster]** Do slow tick work in consensus module agent until no work is done in a cycle.\n* **[Java Client]** Improve error message when failing to open CnC file.\n* **[C/Java]** Retry on `ERROR_SHARING_VIOLATION (32)` on Windows when awaiting CnC file mapping.\n* **[C{plus}{plus}/Java Archive]** Use Publication.publicationLimit instead of looking up counter.\n* **[Java Driver]** Fix re-resolution to trigger when no SMs received by Publications and new Publications are constantly created.\n* **[Java Cluster]** Fix for #1619. Handle the case where the last portion of data a cluster log is a padding frame.\n* **[Java Cluster]** Make `describe recordingId` work with recordings in any state.\n* **[Java Archive]** Only index valida recordings and implement ArchiveTool methods via `forEach`.\n* **[Java]** Print offending value in exception with ChannelUriStringBuilder.\n* **[Java Archive]** Do not mark recording as `VALID` if there are no segment files.\n* **[Java Archive]** Always report state OK even when not changing the state.\n* **[Java Archive]** Mark purged recording as `DELETED`.\n* **[C]** Fix heap-buffer-overflow issue when removing entries from the `aeron_int64_counter_map`.\n* **[C]** Align `aeron_int64_counter_map` with Java, i.e. do not allow initial_value to be put into the map.\n* **[Java Cluster]** Add API for taking a snapshot in consensus module extension.\n* **[C/Java Driver]** add config option and uri param for setting max retransmits (#1640)\n* Upgrade to CMake 3.30.0.\n* Upgrade to HdrHistogram 2.2.2.\n* Upgrade to ByteBuddy 1.14.19.\n* Upgrade to Agrona 1.22.0.\n* Upgrade to Hamcrest 3.0.\n* Upgrade to SBE 1.32.1.\n* Upgrade to JUnit 5.11.0.\n\n== 1.44.7 (2025-07-10)\n\n* **[Archive]** Copy `ttl` URI parameter from the original channel definition when creating recordings and replays. To ensure that the control stream will use the same TTL setting as the incoming data stream.\n* **[C/C{plus}{plus} Wrapper]** Include `<chrono>` to fix compilation error on Windows.\n* **[Java]** Backport releasing to Maven Central Portal.\n\n== 1.44.6 (2024-11-11)\n\n* **[Java]** Update RecoverPlan after standby snapshot replication completes with the replicated snapshot entries.\n\n== 1.44.5 (2024-10-25)\n\n* **[Java]** Prevent an untethered subscription re-joining the stream at an old position.\n\n== 1.44.4 (2024-10-10)\n\n* **[C]** Check subscribed sessions before removing a stream interest before removing by stream_id.\n* **[Java]** Add end-to-end test for session interest fix.\n\n== 1.44.3 (2024-09-24)\n\n* **[C]** Align loss and SMs handling between Java and C media drivers.\n\n== 1.44.1 (2024-04-12)\n\n* **[Java]** Fix ClusterBackup getting stuck in the `LIVE_LOG_REPLAY` state.\n* **[Java]** Validate that the egressChannel is set.\n* **[CI]** Add Clang 18 to the build matrix.\n* Upgrade to SBE 1.31.1.\n\n== 1.44.0 (2024-04-08)\n\n=== Noteworthy Changes\n\n* https://github.com/aeron-io/aeron/wiki/Response-Channels**[Response Channels]**\n+\nA new experimental feature that provides server/client behaviour to Aeron clients.\n\n* Asynchronous DNS Name Resolution\n+\nImprove Publication/Subscription creation performance and avoid stalls by performing DNS resolution asynchronously with Aeron.\n\n* Tethering Behaviour\n+\nPrevent premature disconnections by utilizing the maximum window size for tethering, even in congested networks. OR Improve tethering behaviour when using congestion control.\n\n* Adjust Overrun Behaviour\n** Make better use of buffers and reduce dropped consumers.\n** Enhance the performance of receivers in a max flow control use-case.\n\n* Congestion Control Recovery\n+\nSignificantly reduce recovery time in congested networks, without the need for any configuration changes.\n* Loss Recovery Improvements\n+\nReduce negative acknowledgements and message redelivery to improve the efficiency of message recovery.\n\n=== Known issues\n\n* **[Cluster]** ClusterBackup is stuck in the `LIVE_LOG_REPLAY` state as it trying to find recording using wrong\n`archiveId`.\n+\nFixed in https://github.com/aeron-io/aeron/releases/tag/1.44.1[1.44.1].\n\n=== Changelog\n\n* **[Java/C Driver]** Support configuration flag for experimental features (#1577)\n* **[C Driver]** Fix buffer overflow when updating counter labels.\n* **[Java ConsensusModule]** Log cluster session failures.\n* **[Java/C/C{plus}{plus} Client/Driver]** Add ability to set name on the Aeron client. (#1581)\n* **[Java Driver]** Async DNS resolution. (#1564)\n* **[C Driver]** async DNS resolution for the C media driver (#1566)\n* **[Java CluserBackup]** Have cluster backup start from a specific position (#1579)\n* **[Java ConsensusModule]** Introduce clustered service specific SnapshotDurationTracker (#1575)\n* **[Java Cluster]** Allow setting ClusterClock via `aeron.cluster.clock` system property.\n* **[Java Driver]** Call force on CnC file when doing clean shutdown and signalling ready.\n* **[C Driver]** Add `aeron_msync` and use to flush CnC file contents upon start and shutdown.\n* **[Java Cluster/Archive]** Add *MarkFile.force() method to flush any pending updates to disc and use it for signalling readiness and when closing the mark file.\n\n* **[C/Java Driver]** Dissect frames and commands using key/value format.\n* **[C/Java Driver]** Use nanosecond resolution for logging messages.\n* **[C Driver]** Install pre-/post-interceptors logging interceptors if the incoming/outgoing chains are not NULL. Change logging order for outgoing events to log before executing command to match Java and log plain messages before encryption.\n* **[Java Driver]** Fix `FRAME_IN/FRAME_OUT` event logging, i.e. allow redefinition of previously loaded classes.\n* **[C Driver]** Scope logging functions with a \"log\" struct. (#1556)\n* **[C Driver]** Add C driver logging for send_nak_message and resend (#1553)\n* **[C Driver]** Track pending setups for cases when notified of unconnected messages. Only allow pending setups for unknown interest or existing images when in ACTIVE state. Handle unconnected streams by sending setup eliciting SMs.\n* **[C Driver]** Fix potential leak in when adding stream interest to subscription by session.\n* **[Java Samples]** Improve response channels samples.\n* **[C Driver]** Align min flow control implementation with Java + allocate memory only if a receiver is added.\n* **[Java ConsensusModule]** Use fixed 64 byte alignment that does not depend on the CACHE_LINE_LENGTH and is backwards compatible.\n* **[Java ConsensusModule]** Fix IndexOutOfBoundsException when recording entry straddles page size.\n* **[Java ConsensusModule]** Add leadership term id counter to ease debugging.\n* **[Java ConsensusModule]** Add election counter.\n* **[Java ClusterTool]** Ensure publication is connected before making cluster members query to avoid exception on checking result from ClusterTool.\n* **[Java]** Add missing properties to the `toString` + print Archive/ConsensusModule/ClusteredServiceContainer configuration if `aeron.print.configuration=true`.\n* **[C/C{plus}{plus}/Java Client]** Re-assemble header for the fragmented message before calling user code.\n* **[Java Archive]** Add Archive threading mode to the conductor duty cycle counter labels.\n* **[Java ClusteredServiceContainer]** Trap and log snapshot exceptions while allowing cluster to resume (#1568)\n* **[Java ClusteredServiceContainer]** Track ClusteredService lifecycle via a single field.\n* **[Java ConsensusModule]** Remove retry logic from the ConsensusModuleProxy.\n* **[Java ConsensusModule]** Retry sending cluster member query.\n* **[Java Cluster]** Clarify Javadoc around `clusterSessionId` for `offer/tryClaim` methods.\n* **[C Samples]** add locks around channel info map in response server sample app\n* **[Java Cluster Client]** Close any partially created ingress publications and egress subscription if connect was not completed.\n* **[Java Client]** Add `Aeron#asyncRemoveSubscription`.\n* **[Java ArchiveTool]** Update Help section of Archive Tool to describe the different 'describe' commands. (#1573)\n* **[C{plus}{plus} Archive Client]** Add `archiveId` API to the AeronArchive.\n* **[Java Archive Client]** Use `archiveId` for looking up the RecordingPos counters. (#1571)\n* **[C{plus}{plus}/Java Archive Client]** Add getMaxRecordedPosition to AeronArchive to get the length of a recording if it is active or stopped.\n* **[Java/C{plus}{plus} Archive Client]** Update Aeron Archive control protocol minor version.\n* **[C Driver]** Align implementation of retransmit handler with Java driver.\n* **[Java Cluster]** Set isLeader appropriately on cluster member when listing. Issue #1569\n* **[Java Driver]** Remove unused sourceAddress from PublicationImage.\n* **[Java Driver]** Allow the RetransmitHandler to prevent retransmissions where the term offsets don't match but NAK'd region is wholly subsumed by an existing retransmission.\n* **[C Driver]** Per stream nak delay (#1570)\n* **[Java Driver]** Add support for per channel configuration of nak-delay.\n* **[Java Driver]** Add fixed loss interceptor for testing (#1551)\n* **[Java Driver]** Suppress retransmits of the offset of the NAK is in the range of any active retransmits.\n* **[Java Driver]** Clamp all retransmissions at the point where we create the RetransmitAction to ensure clamped length is accurately applied.\n* **[C/Java Driver]** Change multicast NAK backoff and unicast delay linger to 10ms from 60ms.\n* **[C{plus}{plus} Wrapper Client]** Prevent sanitiser errors when shutting down subscription and client in quick succession (#1549)\n* **[Java Archive]** Validate replay completely before creating a replay Publication.\n* **[C Samples]** add response_client/server sample apps (#1559)\n* **[C Driver]** check for response parameters when adding destinations (#1565)\n* **[Java Driver]** add validation when control-mode=response (#1563)\n* **[C Utility]** Add support for PUT requests using aeron_http_util. (#1561)\n* **[Build]** Set aeronmd rpath in the generated package. (#1557)\n* Upgrade to Agrona 1.21.1.\n* Upgrade to SBE 1.31.0.\n* Upgrade to ByteBuddy 1.14.13.\n* Upgrade to ASM 9.7.\n\n== 1.43.0 (2023-12-19)\n\n* **[Driver]** Support specifying endpoints and tags in the same URI\n* **[C Driver]** Add reference id to counters.\n* **[Driver]** Set reference id of sub-pos to registration id of the image/publication.\n* **[C Client]** Fix aeron_print_counters.\n* **[Java Driver]** Touch ups for RetransmitHandler.\n* **[C Driver]** Put enum events in order.\n* **[C Driver]** Remove reliance of max event num in enum for driver agent.\n* **[C Driver]** Fix signed comparisons.\n* **[C Driver]** Declare void param.\n* **[C Driver]** Keep loop types consistent.\n* **[Java Driver]** Add a clamp on retransmit length.\n* **[Java Client]** Fix Aeron.getSubscription(long) JavaDoc.\n* **[Java Driver]** Encapsulate retransmit multiples constants.\n* **[Samples]** Add sample code to support stress testing of MDC and Unicast connections.\n* **[Cluster]** Mark services as active only after they have successfully started.\n* **[Driver]** Improve error messages reported by clients if the Media Driver has been shutdown.\n* **[C Client]** Populate the client image subscriber_position_id.\n* **[Cluster]** Improve error message when cluster fails to connect.\n* Add detailed version information to Aeron error counters for all components. Include release version and Git SHA.\n* **[C Driver]** Fix capture of spy subscription channels so that image availability callbacks include the correct subscription URI.\n* **[C]** Use -fno-omit-frame-pointer on Linux native builds so provide easy and accurate profiling with tools like perf.\n* Add `+guilty` to version information if using a build that has changes that have not been committed to git.\n* **[C Client]** Correctly handle the case where an image is used across multiple subscriptions. Including ensuring the unavailability callbacks a delivered correctly.\n* **[Archive]** Exclude invalid recordings from ArchiveTool describe and add a `describe-all` command which shows valid and invalid recordings.\n* **[C Driver]** Introduce a parameters structure that can be used to carry transport configuration information when initialising a udp_channel_transport. Also pass MTU information through the bindings to allow for improved validation of within the DPDK bindings.\n* **[Driver]** Only include timestamps on packets that carry the DATA_BEGIN_FLAG to prevent accidental corruption of packets when the timestamp is being carried within the payload.\n* **[Cluster]** Remove the need for MDS subscription on cluster ingress by having a separate IPC ingress subscription when required. Plus await sockets closing on the log subscription in parallel with the other activities when preparing for an election.\n* **[Cluster]** A leader should assert its leadership when a follower requests a vote and the leader has a more up-to-date log rather than re-initialising an election which could result in log truncation.\n* **[C Driver]** Ensure loop iteration index is used when setting timestamps for publication.\n* **[Cluster]** Log event for unknown session close in service container rather than throw and exception.\n* **[Cluster]** Throw exception if a member id is not found in the cluster backup's configuration.\n* **[Cluster]** Avoid list creation when throwing an exception.\n* **[Archive]** Add ArchiveTool mark-valid, mark-invalid commands. These provide a way to patch the catalog in an emergency.\n* **[Cluster]** Ensure that the ClusterMarkFile link is created using the parent directory from the MarkFile object.\n* **[Driver]** Add stricter validation on control addresses, including: Require control address with `control-mode=dynamic`. Allow control addresses on non-MDC unicast publications. Add clearer validation on control-mode when using tags.\n* **[Driver]** Introduce an enum to track the control mode specified on Channel URIs.\n* **[Driver]** Update pub-pos when updating pub-lmt.\n* **[Java Driver]** Check if CnC file has error log buffer allocated.\n* **[Driver]** Print usable space when throwing StorageSpaceException or returning an error.\n* **[C Samples]** Make AeronStat respect update interval option. AeronStat was effectively ignoring `-u` and incorrectly using `-t` instead.\n* **[Java]** Fix NullPointerException in LogBuffers when construction fails with an exception.\n* **[Driver]** Add counter to represent the number of bytes mapped by the Driver.\n* **[Driver]** Pass in setup triggering SMs into flow control so that tags can be matched for controlling setup sender limiting.\n* **[C]** Fix client image reference counting by use the appropriate literals to ensure that 64-bit parameters are used.\n* **[Cluster]** Call unexpectedTermination on incompatible app versions in the ConsensusModuleAgent.\n* **[C Driver]** Implement low storage warnings for the C driver.\n* **[Java Driver]** Backoff from sending when a short send it detected by polling for status messages as a means to provide a little breathing room for underlying stack.\n* **[Cluster]** Detect when log subscription in an election has an error, such as bind exception, throw an exception giving context and restart election.\n* **[Cluster]** Close log subscription after prepareForNewLeadership.\n* **[Cluster]** Introduce SnapshotDurationTracker to track the time taken for snapshots in the Cluster.\n* **[Java]** Rename aeron-version module to aeron-annotations.\n* **[C Driver]** add max_retransmission_length.\n* **[Java]** Remove suffix from version.\n* **[Cluster]** Stop log recording even if an image has not been picked up and no need to try stop the recording by identity if stopped by log subscription id.\n* **[Cluster]** Increase visibility from private to package-protected for a number of ClusterTool utility methods.\n* **[Cluster]** Add STANDBY as a component type to cluster mark file codec.\n* **[Cluster]** Add cluster services directory to the cluster mark file and allow it to be set via the ConsensusModule.Context. This means that ClusterTool can resolve the location of the service container mark files even if they are not stored in the same directory.\n* **[C{plus}{plus}]** Use static constexpr for declaring constants.\n* **[C/C{plus}{plus}]** Use functions to return version information to enable runtime version checks against the media driver.\n* **[C{plus}{plus} Wrapper Client]** Allow to access the underlying client of the C{plus}{plus} wrapper.\n* **[C Driver]** Fix error reporting for aeron_port_manager.\n* **[C Driver]** Implement timestamp capturing for the recvmsg path.\n* **[C Driver]** Switch sender poller to poll for a single SM, i.e. do the `recvmsg` syscall.\n* **[C Driver]** Add additional validation for frame_length on status messages.\n* **[C Driver]** Fix error format strings when configuring sockets for the udp transport.\n* **[C/C{plus}{plus}]** Remove date/time from the version files for a reproducible build.\n* **[C]** Fix line ordering bug and add missing return statements and error appending.\n* **[Driver]** Allow publication image flow control under runs if they are heartbeats so a stream can be kept connected and to get a more accurate EOS when using MDS.\n* **[C Driver]** Use correct type for array length calculation, fix #1539.\n* **[Driver]** Track EOS position per image connection for MDS.\n* **[C{plus}{plus} Wrapper]** Fix maxMessageLength.\n* **[C{plus}{plus} Wrapper]** Fix FragmentAssembler.\n* Upgrade to AsciiDoctorJ 2.5.10\n* Upgrade to JGit 5.13.2.202306221912-r\n* Upgrade to Versions Plugin 0.50.0\n* Upgrade to ByteBuddy 1.14.10\n* Upgrade to ASM 9.6.\n* Upgrade to Agrona 1.20.0\n* Upgrade to JUnit 5.10.1\n* Upgrade to SBE 1.30.0\n\n== 1.42.1 (2023-09-01)\n\n* **[Java]** Ensure that the `LoggingErrorHandler` instance is closed when wrapping a user-defined `ErrorHandler`.\n* **[Java]** Invoke background work when idle and service is active so external connections from a cluster can be maintained.\n* **[Doc]** JavaDoc clarifications on MediaDriver dirDeleteOnStart behavior.\n* Upgrade to Agrona 1.19.2.\n* Upgrade to ByteBuddy 1.14.7.\n\n== 1.42.0 (2023-08-21)\n\n* Add port manager to C and Java drivers to allow for a limited range of ports that can be used when subscription endpoint addresses and publication control addresses specify a port of `0`. This can make network access configuration simpler (Driver).\n* Wait for end of stream event on the log instead of the counter becoming unavailable before entering into a new Election (Cluster).\n* Close log publications before trying to stop recording to speed up graceful leader step down (Cluster).\n* Ignore resting subscription positions when calculating join position or draining driver entities when tethering is being used (C Driver).\n* Extensions points to support Standby Snapshots (Cluster).\n* Ensure that the `clusterDirectoryName` field is synced with the `clusterDir` configuration parameter during conclude in `ConsensusModule` and `ClusterBacker` (Cluster).\n* Cluster tutorial updates (Documentation).\n* Extract invocation of background work in the service container.\n* Do not fail if Aeron directory exists, but the CnC file does not (C Driver).\n* Use `aeron_errcode` to get the latest error code when reporting problems creating the Aeron directory (C Driver).\n* Don't let a bounded replay go beyond counter value to stop position so commit position can be used for replay (Archive).\n* Add addition example Authentication and Authorisation services (Archive/Cluster).\n* Add additional documentation to specify details when Authorisation is used (Archive/Cluster).\n* Clarify logic about extending a replay while being bounded (Archive).\n* Changes to host name resolution on start up (Driver).\n** Perform host name resolution once and track its execution time via the duty cycle tracker and the event log.\n** Use `<unresolved>` as a host name if it cannot be resolved instead of null.\n** Fix GCC 9 warning\n** Await initial counters being updated by the conductor thread before reading the values.\n* Trigger graceful leader close election based on recording stop signal (Archive).\n* Add end of stream position to Subscriptions (Client).\n* Allow for relocatable mark files for the `ConsensusModule`, `ClusteredServiceContainer`, and `ClusterBackup` (Cluster).\n* Move close operations on MDS transports on the conductor not the receiver thread (C Driver).\n* Allow preparing for new leadership to be less serial to reduce election time (Cluster).\n* Async removal of destinations as log adaptor closes when preparing for election (Cluster).\n* Fix CMD_OUT_ERROR log format (C Driver).\n* Fix `aeron_err_clear()` (C).\n* C driver handle untethered subscriptions correctly, ensure that tethering behaviour matches Java driver (C Driver).\n* Fix name in toString (Java Client).\n* Improve error message (C{plus}{plus} Archive Client).\n* Clean up Apple M1 compilation. Move loss reporter function into compilation unit. Add pragmas to account for unused but set variables (C).\n* Add logging to track information when a `ReplicationSession` ends (Archive).\n* Add deduplication step for aeron-agent jar.\n* Add deduplication step for aeron-all jar.\n* Add an annotation processor to generate a version class for a number of the Aeron packages.\n* Include the git sha in the version C binaries.\n* Extract `untetheredWindowLimit` method.\n* Upgrade to SBE 1.29.0.\n* Upgrade to JUnit 5.10.0.\n* Upgrade to Agrona 1.19.1.\n\n== 1.41.6 (2024-01-06)\n\n* **[Java/C]** Allow publication image flow control under runs if they are heartbeats so a stream can be kept connected and to get a more accurate EOS when using MDS.\n* **[Java/C]** Restrict the allowing of heartbeat under runs to only one term behind.\n* **[Java/C]** Count under runs for heartbeats.\n* **[Java/C]** Track EOS position per image connection for MDS.\n\n== 1.41.5 (2023-11-09)\n\n* **[Java]** A leader should assert its leadership when a follower requests a vote and the leader has a more up-to-date log rather than re-initialising an election which could result in log truncation.\n* **[Java]** Change role to leader after winning election so leadership can be asserted during replay or replication.\n* **[Java]** Log event for unknown session close in service container rather than throw and exception.\n\n== 1.41.4 (2023-06-14)\n\n* Store `Aeron.NULL_VALUE` for lastActivityTimestamp in the snapshot for sessions as the value should be transient (Cluster).\n* On snapshot of PendingServiceMessageTracker, check to see if the buffer is empty and the nextServiceSessionId is lower than what it should be. Correct this and log an error that follower may not have executed the service IPC logic deterministically (Cluster).\n* Include the recording position in the debug log when replicate session state changes (Archive).\n* Don't cool down an Image that is EOS (Driver)\n* Reset eos_flagged when adding a receiver (C Driver)\n* Filter images by session id when adding new network subscriptions (C Driver)\n* Allow the session-id of the publication used during replication to be specified to avoid session-id clashes on replication retries (Archive).\n* Allow the authorisation credentials to be specified on a replication request to support simple authentication between the destination and source archives (Archive).\n* Use custom session-ids when replicating snapshots and logs (Cluster).\n* SBE 1.28.3\n* Agrona 1.18.2\n* ByteBuddy 1.14.5\n* Version 0.47.0.\n\n== 1.41.3 (2023-05-16)\n\n* **[Java]** Add debug method for a replication completing withing Cluster.\n* **[Java]** Let Archive take care of handling Aeron exceptions when client is embedded.\n* **[C]** Prevent double free of the `aeron_udp_channel_t` when creating a `send_channel_endpoint_t`.\n* **[C]** Upgrade to HDR Histogram 0.11.8\n* **[Java]** Upgrade to JUnit 5.9.3 (platform console 1.9.3)\n* **[Java/C]** Name resolution execution time tracking\n* **[Java]** Simplify Client Close\n* **[Java]** Perform Runtime.exit(int) on another thread in default error handler to avoid deadlocks if Aeron instances are used on JVM shutdown hooks.\n* **[C]** Fix potential memory leak.  Use correct action with publication notification.\n* **[Java]** Ensure segment file write of final byte for extension is successful\n\n== 1.41.2 (2023-04-20)\n\n* **[Java]** Fixed ClusterBackup backwards compatibility issue caused by the missing `memberId` field.\n* **[Java]** Set isLeader flag on the list of cluster members passed to `ClusterBackupEventsListener`.\n* **[C]** Prevent negative shifts when receiving some messages from ATS.\n* Upgrade to ByteBuddy 1.14.4.\n* Upgrade to Gradle 8.1.\n* Upgrade to Shadow 8.1.1.\n\n== 1.41.1 (2023-04-17)\n\n* Upgrade to Agrona 1.18.1.\n* Upgrade to SBE 1.28.2.\n\n== 1.41.0 (2023-04-14)\n\n* Allow `NameResolver` to be configured for the `ConsensusModule` in order to support custom name resolution when configuring the ingress channel.\n* Delay election state transitions if there is an active leader to avoid unnecessary reset and new election.\n* Make `AeronCluster.asyncConnect` work completely asynchronously. Don't report exceptions to the error handler that are used for async resources.\n* Add a system property and API to allow changing a directory where an Archive mark file (`archive-mark.dat`) is stored.\n* Check the state of the interface when trying to resolve the multicast interface. Only use interfaces that are up. [Issue #1387](https://github.com/aeron-io/aeron/pull/1438[https://github.com/aeron-io/aeron/issue/1387])\n* CnC file length validation. [Issue #1410](https://github.com/aeron-io/aeron/pull/1438[https://github.com/aeron-io/aeron/issue/1410])\n* Fix issue of not capturing return code when recording signal arrives after an error to the archive client.\n* Support migrating segments to the beginning or end of an existing archive recording.\n* **[C]** Fix issue of using transport after it had been removed.\n* **[Java]** Fix concurrent close of receive destination counters on multi-destination subscriptions.\n* **[C]** Fix `remove_if` methods on pointer value maps which previously could miss an item.\n* Add debug logging for clustered service acking.\n* Add a specific error for archive replication failing to create a remote connection.\n* Fix leak with Archive replay session if the async publication has a session clash.\n* Shorten duration of cluster election after a leader has closed gracefully.\n* **[C]** Fix image rejoin by swapping correcting cooldown map insertion and removal. https://github.com/aeron-io/aeron/pull/1438[PR #1338]\n* Candidate ballot for 5+ node cluster cannot be cut short on quorum otherwise most up to date member may not be elected.\n* **[C]** Allow for attempted recreation of an Image if initial attempt fails. https://github.com/aeron-io/aeron/pull/1435[PR #1435]\n* Perform most replay validations before sending OK to the client so errors are synchronous when starting a replay.\n* Delete all recording segment files when a recording is truncated to its start position.\n* Close `ArchiveMarkFile` last when shutting down Archive to capture all errors.\n* **[C{plus}{plus}]** Apply `std::forward` to fragment handler to avoid unnecessary copy. https://github.com/aeron-io/aeron/pull/1405[PR #1405]\n* Fix handling of padding greater than max message length in Archive replay.\n* Add debug logging for Archive recording signals.\n* Close log subscription first when clustered service is cleanly closed to drop follower out of flow control as soon as possible.\n* Drop cluster follower as soon as possible out of flow control to allow cluster to progress when follower is cleanly closed.\n* **[C]** Report timeout accurately when driver keepalive beyond timeout. https://github.com/aeron-io/aeron/pull/1429[PR #1429]\n* Add ability to run Archive with only IPC control channels for clients.\n* Add `ClusterTool.isLeader` method.\n* Add `Image` to `Subscription` before calling available handler rather than after.\n* Set URI in receiver counters to match subscription channel.\n* Add cluster member node state file and migrate out state that needs to be persistent, such as `candidateTermId` and member list, so the mark file can be in /dev/shm.\n* **[C]** Fix issue with removing naming resolver neighbor that deleted adjacent memory.\n* **[C]** Improve socket error handling on Windows.\n* **[Java]** Add `toString()` to many Aeron classes to help debugging.\n* **[C]** Improve parsing of unsigned 32-bit integers.\n* **[C]** Set max of resource free queue length and resource free limit to `INT32_MAX`. This stops them being incorrectly set to 0 by aeron_config_parse_uint32 when comparing against int32 0. https://github.com/aeron-io/aeron/pull/1421[PR #1421]\n* Deprecate cluster dynamic join feature. This is to be replaced with a more robust and user friendly premium offering.\n* **[C]** Fix counter leak when subscription fails.\n* **[C]** Fix spy channel memory leak when destination is removed for multi-destination subscription.\n* **[C]** Fix channel memory leak on error when creating publications or subscriptions.\n* Fix NPE on timeout exception for cluster client in some connect states.\n* **[Java]** Improve efficiency of URI parsing.\n* **[C]** Fix error messages with incorrect varargs.\n* Warnings clean up in codebase to have less noisy CodeQL analysis.\n* Support having mark files for `Archive`, `ConsensusModule`, and `ClusteredServiceContainer` to be in alternative directory such a /dev/shm so timeouts can be avoided when recording writes queue up on a network filesystem.\n* Add timestamp params to stripped channel for pass through to Archive operations.\n* Queue resource freeing operations in driver to avoid timeouts when unmapping operations are slow.\n* **[C{plus}{plus}]** Work around compiler concurrency bug for `AtomicArrayUpdater` that can impact client Subscriptions causing image list to become corrupted.\n* Improve javadoc for recording signal usage.\n* Be strict on handling cluster leader liveness to the current leadership term.\n* Only try unblocking a client command after liveness timeout to avoid \"lost\" commands. https://github.com/aeron-io/aeron/pull/1369[PR #1369]\n* Make archive counters unique so multiple archives can run on the same media driver.\n* Truncate files after `ArchiveTool.compact` is invoked to free disk space.\n* Fix basic auction cluster tutorial configuration.\n* Improve `ClusterConfig` sample to allow for ingress configuration.\n* Add counters for the number of active recordings or replays in an Archive.\n* Add counters for reporting on read and write operations in an Archive.\n* Support allowing a `ClusteredService` being started before the `ConsensusModule`.\n* Improve false sharing protections for more consistent latency.\n* Simplify `ReplayMerge` samples to not require entity tags.\n* Add batch script for launching low-latency media driver on Windows.\n* Support message lengths greater than MTU in ping pong samples.\n* Fix options handling in `cping` sample.\n* Improve handling of timeouts in cluster elections for more robust state transitions when network is unstable. Effects are more pronounced in 5+ member clusters.\n* **[Java]** Add `Aeron.addAsyncSubscripiton` for non-block setup.\n* Compute source identity of images more precisely based on channel configuration.\n* Improved handling of out of disk space errors.\n* Support taking a cluster consensus module snapshot when member names are greater than MTU in length.\n* Allow a follower to veto a member being elected cluster leader if they believe the leader is not valid. This is important in 5+ node clusters.\n* Extend debugging for voting in cluster elections.\n* Increment error counter when invalid version exceptions occur.\n* Handle backpressure from commands between dedicated threads in driver with controlled polls to avoid live locks.\n* **[C]** Add support for controlled poll operations on SPSC and MPSC ring buffers.\n* Increase command queues to allow for more concurrent active changes in publications and images.\n* Serve cluster backup queries from followers to take load from the leader.\n* **[C]** Fix build when dot is used as thousands separator. https://github.com/aeron-io/aeron/pull/1372[PR #1372]\n* Upgrade to JUnit 5.9.2.\n* Upgrade to BND 6.4.0.\n* Upgrade to ByteBuddy 1.14.3.\n* Upgrade to Mockito 4.11.0.\n* Upgrade to Version 0.46.0.\n* Upgrade to Gradle 7.6.\n* Upgrade to SBE 1.28.1.\n* Upgrade to Agrona 1.18.0.\n\n== 1.40.0 (2022-10-21)\n\n* Memory align allocated buffers in `PublicationTest` so it works on Apple M1 processors.\n* Check that`NoOpLock` is only allowed to be used when using Aeron client in invoker mode.\n* Handle case of a delayed concurrent offer to a publication in which other threads have raced terms ahead without throwing an exception.\n* Collapse term appenders into publications to reduce memory footprint and avoid data dependent loads.\n* Short circuit Image polling operation when bound limit is less than current position to prevent term overrun.\n* Add different aliases for consensus module/service container subscriptions. https://github.com/aeron-io/aeron/pull/1366[PR #1366].\n* Stop an active cluster log replay when `ClusterBackup` is closed rather than waiting for timeout.\n* Send unavailable counter events to Aeron clients when a client closes or times out.\n* Allow Consensus Module Agent to be run via an Invoker in addition to having its own thread.\n* Apply liveness checks to Archive and Cluster mark files so that multiple instances cannot be run in the same directory and corrupt files.\n* **[Java]** Use fixed format for timestamps in agent debug logs.\n* Allow Archive replicate to overwrite all metadata for an empty recording.\n* **[C]** Handle log buffer files with `term_length == AERON_LOGBUFFER_TERM_MAX_LENGTH` on Windows. https://github.com/aeron-io/aeron/pull/1360[PR #1360].\n* **[C]** Fix inclusion of symbols for debug builds on Windows.\n* Remove `localhost` defaults for Archive and Cluster to help avoid mis-configuration in production. https://github.com/aeron-io/aeron/pull/1356[PR #1356].\n* Await 'REPLICATE_END' when catching up as a follower across multiple leadership terms to avoid clashing session-id.\n* Allow setting of receive socket buffer and window on cluster log channel subscribers. https://github.com/aeron-io/aeron/pull/1345[PR #1345].\n* Fix application of send socket buffer lengths as configured when using MDC.\n* Fix `ArchiveTool.dump` when fragment length is set &lt;= 0.\n* Capture closing sessions into snapshot so session close event is lost on cluster shutdown.\n* Remove brackets from counters labels to make it easier for extract to Prometheus.\n* Send cluster client session open acknowledgement before appending to the log to avoid race with service sending egress on open event. https://github.com/aeron-io/aeron/issues/1351[Issue #1351].\n* **[C]** Fix off by one error local socket address into channel indicator counter.\n* Add protocol version support to cluster consensus protocol.\n* Add more context to error messages on Archive `ReplaySession`. https://github.com/aeron-io/aeron/pull/1349[PR #1349].\n* Apply strict validation of consensus module snapshot state when messages are offered from clustered services. A number of customers have not been strict with all cluster nodes being deterministic and doing exactly the same thing which can result in corrupted and diverged snapshots.\n* Consensus module state snapshot can be inspected with the `describe-latest-cm-snapshot` option to `ClusterTool`.\n* If a consensus module snapshot is shown to be corrupt it may be fixed by running `ConsensusModuleSnapshotPendingServiceMessagesPatch` and if non-support customers wish to have help then they can contact link:mailto:sales@aeron.io?subject=Aeron[sales@aeron.io]. The patch can fix the leader and the fixed snapshot then needs to be replicated to the followers which can be done with `AeronArchive.replicate` using the correct recording ids.\n* Add a tool to replicate a specific recording between archives. https://github.com/aeron-io/aeron/pull/1363[PR #1363].\n* **[C{plus}{plus}]** use `getAsString` calls for pollers for record descriptors for channel fields. Add test from https://github.com/aeron-io/aeron/pull/1348[PR #1348].\n* Add `ClusteredService.doBackgroundWork` which can be used for maintaining external connections beyond ingress and egress.\n* Increase default message timeout from 5 to 10 seconds for Archive clients.\n* Add EOS flag to status messages (SMs) once a stream is totally received so the sender can take clean up action.\n* When EOS status message is received by a sender then allow the publication linger on unicast to be cut short so resources are received sooner.\n* When EOS status message is received by a sender then remove the receiver from flow control for multicast and MDC with tagged and min FC.\n* Fix the closing of session specific subscriptions to prevent resource leak.\n* Add scripts for testing raw network performance on Windows.\n* Close egress from cluster on change of leader so clients can detect it before a new leader is elected.\n* Don't timeout and close cluster client session if quorum cannot be temporarily reached.\n* Add logging support for `ClusterBackup` state changes.\n* Close cluster clients when complete cluster is restarted.\n* Support automatic reconnect from cluster client when the same leader is re-elected after a net split or temporarily loosing quorum.\n* Add authentication for `ClusterBackup` to a cluster.\n* Validate Archive mark file length before reading when mapped read-only to avoid access violations.\n* Preserve iteration order for cluster client session based on session id so snapshots can have binary compatibility.\n* Capture leadership term id for cluster backup queries.\n* Account for padding when sweeping pending services messages to avoid out of bounds exception.\n* Prevent `-1` leadership term ids appearing in the `RecordingLog`.\n* Allow Archive replication and replay request to specify session level file IO max buffer length for throttling a stream.\n* Add support for custom app version validation to clustered services with `AppVersionValidator`.\n* Add false sharing protection to `DutyCycleTracker`.\n* Update doc on `ReplayMerge` to indicate the `AeronArchive` client should not be shared. https://github.com/aeron-io/aeron/issues/1340[Issue #1340].\n* Upgrade to Versions 0.43.0.\n* Upgrade to Mockito 4.8.1.\n* Upgrade to Google Test 1.12.1.\n* Upgrade to JUnit 5.9.1.\n* Upgrade to ByteBuddy 1.12.18.\n* Upgrade to Gradle 7.5.1.\n* Upgrade to SBE 1.27.0.\n* Upgrade to Agrona 1.17.1.\n\n== 1.39.0 (2022-07-14)\n\n* **[Java]** Fix `IllegalStateException` that could exist for an MDS subscription on the rapid recycling of `ReplayMerge` operations.\n* **[C]** Align ring buffer implementations and feature set with Java.\n* **[Java]** Make sure that C and Java are aligned on resend window. Re-instate the max message length being accounted in the bottom of the resend window for Java.\n* Add duty cycle duration tracking to all agents across all modules.\n* **[C{plus}{plus}]** Improve efficiency by reducing the number of copy operations for fragment assembly when a stream has many fragmented messages.\n* **[C]** Default to `CLOCK_REALTIME` for send/receive timestamps.\n* **[Java]** Add setters for send/receive timestamp clocks to the `MediaDriver.Context`.\n* Fix handling of fragment assembly when `reliable=false` is set for a channel and loss occurs.\n* Improve handling of short sends on MDC publication to backoff from overloading a socket.\n* Add round-robin facility to MDC publication for increased fairness.\n* **[Java]** Publish `aeron-test-support` package as a JAR.\n* **[Java]** Downgrade \"unknown replay\" errors to warnings for cluster catchup.\n* **[Java]** Add `appVersion` to event logging for consensus module and check for correct app version when replaying log.\n* **[Java]** Prevent timeout warnings with cluster dynamic nodes and log replication.\n* **[Java]** Add cluster dynamic join state change logging events.\n* Add counters for the number of receivers in min and tagged flow control strategies.\n* **[Java]** Avoid race unmapping buffers on concurrent close of media drivers.\n* Modify flow control strategies to have new method for when elicited setups are sent and add counters manager to `init` methods. Modify Min and Tagged flow control to use setup `snd-lmt` as min position until timeout or receiver added on SM.\n* **[Java]** Account for possible padding in log buffer when checking for bottom resend window for retransmits.\n* **[C]** Flush output when printing configuration.\n* **[C]** Raise warning on failure to setup media timestamping.\n* **[Java]** Update `recordingId` on any signal with a valid recording id when handling signals for snapshot replication.\n* **[Java]** When attempting `ClientSession.tryClaim`, ensure that there is enough buffer space when returning a mocked offer for a follower.\n* **[C]** Ensure publication image is released before it it freed.\n* **[C]** Fix `scanf` that could result in buffer overflow when parsing HTTP for configuration.\n* **[Java]** Change default cluster session timeout from 5 to 10 seconds.\n* Prevent receiver joining min/tagged flow control if they are more than a window behind.\n* **[C]** Add sample for working with large messages.\n* **[Java]** Add logging event for appending a cluster session close.\n* Upgrade to BND 6.3.1.\n* Upgrade to Mockito 4.6.1.\n* Upgrade to ByteBuddy 1.12.10.\n* Upgrade to SBE 1.26.0.\n* Upgrade to Agrona 1.16.0.\n\n== 1.38.2 (2022-04-29) - C Driver/Client Release Only\n\n* **[C]** Driver - Ensure the correct control address is used when adding multicast destinations with MDS.\n* **[C]** Driver - Allow thread affinity on CPU 0.\n* **[C]** API - Check handler parameter before polls. Check images for NULL before polling images.\n\n== 1.38.1 (2022-04-14)\n\n* Upgrade to SBE 1.25.3.\n* Upgrade to Agrona 1.15.1.\n\n== 1.38.0 (2022-04-14)\n\n* **[Java/C/C{plus}{plus}]** Ensure driver is in ready state when requesting termination from client.\n* **[Java]** Reduce allocation when listing archive directories to find segment files.\n* **[Java]** Add flag to `ClusterTerminationException` to indicate if the termination was expected.\n* **[Java]** Expand agent logging for consensus module operations, be careful if using `all` for cluster events as volume may now be greatly expanded.\n* **[C]** Use connect and send to improve latency in C driver when sending data at lower volumes.\n* **[Java]** Improve reliability of transferring snapshots to `ClusterBackup` via archive replication with improved re-try semantics.\n* **[Java]** Support adding an IPC ingress destination to cluster leader for ingress optimisation.\n* **[Java]** Create replay publication asynchronously to reduce latency pauses in Archive.\n* **[Java/C{plus}{plus}]** Add new `RecordingSignal.REPLICATE_END` recording signal to indicate end of a replication operation.\n* **[Java/C{plus}{plus}]** Make delivery of `RecordingSignal`s to archive client sessions reliable and ordered.\n* **[Java]** Support specifying interface with endpoints in cluster config for multi-home members. https://github.com/aeron-io/aeron/pull/1290[PR #1290].\n* **[C]** Add thread affinity support to C media driver. https://github.com/aeron-io/aeron/pull/1298[PR #1298].\n* **[C/C{plus}{plus}]** Update CMake build to use `FetchContent` instead of `ExternalProject`.\n* **[C/C{plus}{plus}]** Fix build on ARM with clang. https://github.com/aeron-io/aeron/pull/1291[PR #1291].\n* **[Java]** Improve progress tracking and retry semantics for cluster members catching up in elections.\n* **[C/C{plus}{plus}]** Enable support for parallel build on Windows.\n* **[Java]** Add ability to async remove/close a publication by registration id.\n* **[Java]** Fix publication leak in `ClusterBackup` when backup response timesout.\n* **[C]** Improve agent logging in C media driver to be more consistent with Java driver.\n* **[C]** Allow for configurable IO vector for `sendmmsg` and `recmmsg` in the C media driver. https://github.com/aeron-io/aeron/pull/1285[PR #1285].\n* **[C]** Support static linking of the C media driver. https://github.com/aeron-io/aeron/pull/1261[PR #1261].\n* **[Java/C]** Support ability to extend concurrent publications by setting initial values to be equivalent to exclusive publications.\n* **[Java]** Fixed bug in `PriorityHeapTimerService.cancelTimerByCorrelationId`. https://github.com/aeron-io/aeron/pull/1281[PR #1281].\n* **[C{plus}{plus}]** Improve error reporting in Archive client when a response is not received.\n* **[Java/C{plus}{plus}]** Additional user specified delegating Invoker for Archive client to be used for progressing actions when awaiting responses.\n* **[Java]** Rename Archive segment files before delete to avoid races with streams being extended.\n* **[C{plus}{plus}]** Fixes for `ChannelUriStringBuilder`. https://github.com/aeron-io/aeron/pull/1268[PR #1268].\n* **[Java]** Add admin command so that cluster snapshot can be triggered remotely via an authorised session.\n* **[Java]** Support authorisation of service actions with a new API `AuthorisationService`. The hooks for this have been added to Archive requests and Cluster Snapshot requests.\n* **[Java/C]** Support adding spy and IPC destinations to MDS subscriptions so destinations can be all channel types.\n* **[Java]** Ensure Cluster will start on a consistent initial term id when racing to create first term.\n* **[Java]** Prevent unnecessary creation of `RecordingLog` files when using `ClusterTool`.\n* **[Java]** Add cluster session timeout to set adjusted when debugging.\n* **[C]** Fixes to prevent message duplication and unnecessary sending of messages in MDS.\n* Minimum CMake version was raised to 3.14.\n* Upgrade to HdrHistogram_c 1.11.4.\n* Upgrade to BND 6.2.0.\n* Upgrade to Versions 0.42.0.\n* Upgrade to Mockito 4.4.0.\n* Upgrade to ByteBuddy 1.12.9.\n* Upgrade to Shadow 7.1.2.\n* Upgrade to Gradle 7.4.2.\n* Upgrade to JUnit 5.8.2.\n* Upgrade to Checkstyle 9.3.\n* Upgrade to SBE 1.25.2.\n* Upgrade to Agrona 1.15.0.\n\n== 1.37.0 (2021-11-26)\n\n* **[Java]** Improve error messages on channel conflicts.\n* **[C]** Remove replicated command prefix in debug agent logging.\n* **[Java]** Use async publication add for async connect to an Archive to minimise the impact of name resolution pauses.\n* **[Java]** Make `ClusterConfig.calculatePort` public.\n* **[C]** Correct channel length on metadata for stream counters.\n* **[Java]** Extract channel value from counter label when longer than what will fit in metadata for `StreamStat`.\n* **[Java]** Relocate HdrHistogram and ByteBuddy in `aeron-all` JAR.\n* Upgrade to BND 6.1.0.\n* Upgrade to ByteBuddy 1.12.2.\n* Upgrade to Mockito 4.1.0.\n* Upgrade to SBE 1.25.1.\n* Upgrade to Agrona 1.14.0.\n\n== 1.36.0 (2021-11-19)\n\n* **[C/C{plus}{plus}]** Handle SIGINT in code samples.\n* **[Java]** Retry adding cluster member publication in election canvass to address late name registration in containers such as Kubernetes.\n* **[Java]** Log resolution failures in Cluster as warning event rather than exception.\n* **[Java]** Fix timestamp when publishing new leadership terms. https://github.com/aeron-io/aeron/pull/1254[PR #1254].\n* **[C]** Use separate transport bindings for the conductor doing name resolution. https://github.com/aeron-io/aeron/pull/1253[PR #1253].\n* **[Java/C{plus}{plus}]** Allow the setting of a `RecordingSignalConsumer` in the archive client context which is delegated to when processing control channel responses.\n* **[C]** Improve error handling and logging on Windows when dealing with network system calls.\n* **[Java]** Verify cluster log is always contiguous when joining a new image in a service.\n* **[Java]** Fix race condition when sending `RecordingSignal.SYNC` during archive replication. https://github.com/aeron-io/aeron/pull/1252[PR #1252].\n* **[Java/C]** Improve choice of subscription for choosing channel URI when labelling receiver counters.\n* **[Java]** Sort counters displayed with `StreamStat` so they are logically grouped.\n* **[Java]** Improve error messages so they are more contextual.\n* **[Java]** Extend debugging logging for archive and cluster operations.\n* **[Java]** Check for errors when cluster snapshots are replayed.\n* **[Java]** Improve tracking of cluster commit position when replicating during an election.\n* **[Java]** Allow replication to skip over empty leadership terms due to failed elections when initially starting cluster.\n* **[C]** Better handling of finding user for default `aeron.dir` when `USER` is not set in environment.\n* **[Java/C{plus}{plus}]** Reduce cache invalidations when using pollers for archive and cluster response streams.\n* **[Java]** Add support for changing cluster log params by truncated to the latest snapshot and resetting configuration. https://github.com/aeron-io/aeron/pull/1233[PR #1233].\n* **[Java]** Don't catch subclasses of `Throwable` and instead catch `Exception` so that the JVM can handle subclasses of `Error`.\n* **[Java/C]** Improve validation of ports used in channel URIs.\n* **[C]** Support building on Apple ARM.\n* **[Java]** Add priority heap backing implementation for cluster timers as an alternative to the default timer wheel implementation\n* Upgrade to Mockito 4.0.0.\n* Upgrade to Shadow 7.1.0.\n* Upgrade to BND 6.0.0.\n* Upgrade to Gradle 7.2.\n* Upgrade to ByteBuddy 1.12.1.\n* Upgrade to Checkstyle 9.1.\n* Upgrade to SBE 1.25.0.\n* Upgrade to Agrona 1.13.0.\n\n== 1.35.1 (2021-09-06)\n\n* **[Java]** Fix selection of channel based on add publication registration id rather than original registration id. https://github.com/aeron-io/aeron/issues/1218[Issue #1218].\n\n== 1.35.0 (2021-08-09)\n\n* **[Java]** Fix truncation of linger timeout in `ChannelUriStringBuilder` which lead to a short linger of Archive replays.\n* **[Java]** Remove incorrect publication linger validation.\n* **[C]** Add sanitize build for MSVC and fix issues found.\n* **[C]** Add missing free of counters associated with Cubic congestion control.\n* **[C{plus}{plus}]** Fix missing use of `FragmentAssembler` in Archive response and clean up type warnings.\n* **[Java]** Fix packaging declaration in POM file.\n* **[Java]** Javadoc improvements.\n* **[Java]** Separate thread factories for replay and recording agents in Archive for when setting thread affinity is required.\n* **[C]** Agent logging fixes. https://github.com/aeron-io/aeron/pull/1198[PR #1198].\n* **[Java/C]** Support a list of bootstrap neighbours for fault tolerance in gossip protocol for driver naming.\n* **[C]** Handle connection reset without error when polling a socket on Windows.\n* **[C{plus}{plus}]** Don't progress with archive connect until response subscription is available. https://github.com/aeron-io/aeron/pull/1196[PR #1196].\n* **[Java]** Use async publication adding for response channels from the Archive and response channels for egress and backup queries from the Cluster to reduce latency pauses for existing operations.\n* **[Java]** Ability to add publications asynchronously to Aeron client.\n* **[C/Java]** Support timestamping of packets for channel send and receive plus media/hardware receive timestamping if supported. https://github.com/aeron-io/aeron/pull/1195[PR #1195].\n* **[Java]** Ensure termination hook is run on unexpected interrupt during cluster election.\n* **[Java]** Reset cluster election state if in election and an exception happens outside the election work cycle.\n* **[Java]** Finish deleting pending archive recording for deletion on shutdown.\n* **[Java]** Ensure cluster log recording has stopped before restarting the election process to avoid spurious election failure from past recording stopping.\n* Upgrade to Google Test 1.11.0.\n* Upgrade to Mockito 3.11.2.\n* Upgrade to ByteBuddy 1.11.9.\n* Upgrade to Gradle 7.1.1.\n* Upgrade to SBE 1.24.0.\n* Upgrade to Agrona 1.12.0.\n\n== 1.34.0 (2021-06-16)\n\n* **[Java]**: added nanoClock to `AeronArchive.Context` to control time more directly. https://github.com/aeron-io/aeron/pull/1188[PR #1188].\n* **[Java]**: added `ClusterBackup.Context.toString`.\n* Various changes for Cubic congestion control, Status Message generation, and overrun determination to handle high loss scenarios with congestion control better.\n* **[Java]**: use separate archive contexts for local and remote archive clients in cluster backup. Local archive must be configured to use IPC.\n* **[Java]**: relocated ByteBuddy in agent jar.\n* **[Java]**: support constructing a `ChannelUriStringBuilder` from an existing URI. https://github.com/aeron-io/aeron/pull/1186[PR #1186].\n* Several improvements to handling initial name resolution failures for cluster and cluster clients when using name resolution from containers.\n* **[Java]**: improve tag usage for `IndexedReplicatedRecording` example.\n* **[Java]**: more information included in `extendRecoding` failures.\n* Added name resolution logging to agents.\n* Append cycle time threshold to counter label.\n* **[Java]**: support connecting to a cluster when a minority of the members are not active in a name service.\n* **[C]**: retain entropy in large collections for hashing and include full range of possible masks for UINT32.\n* **[Java]**: timeout Archive replication if recording subscription endpoint fails to resolve.\n* **[Java]**: added `AeronEvent` exception type that does not generate a stack trace.\n* MDC manual destinations can now be initially unresolved.\n* **[Java]**: Fix NPE on cluster client after multiple redirects. https://github.com/aeron-io/aeron/pull/1179[PR #1179].\n* **[C]**: improve common hash functions. https://github.com/aeron-io/aeron/pull/1178[PR #1178].\n* Various fixes for re-resolution of endpoints and adding more tests to re-resolution scenarios.\n* **[C]**: add interface URI param to MDC publication channels.\n* **[Java]**: MDS will now use the base subscription URI for congestion control, receive window, and socket buffer URI params.\n* Upgrade to SBE 1.23.0\n* Upgrade to Agrona 1.11.0\n* Upgrade to Versions 0.39.0\n* Upgrade to Unit 5.7.2\n* Upgrade to Gradle 7.0.2\n* Upgrade to Shadow 7.0.0\n* Upgrade to Mockito 3.11.1\n* Upgrade to ByteBuddy 1.11.2\n\n== 1.33.1 (2021-05-14)\n\n* **[C]** Fix clean up in CSV name resolver on error.\n* Improve error messages for channel URI configuration and clash errors.\n* **[Java/C{plus}{plus}]** Add missing arguments for full replicate and tagged replication API to Archive.\n* **[Java]** Avoid channel leak on error configuring send and receive channel.\n* **[Java]** Avoid double suffix of exception category to message for `RegistationException`.\n* **[Java]** Allow setting of socket and receiver buffer lengths in `ChannelUriStringBuilder` from `ChannelUri` with short form human friendly names.\n\n== 1.33.0 (2021-05-10)\n\n* The focus for this release has been a significant rework of cluster to make consensus more robust especially in recovery scenarios. We consider this the penultimate release to cluster being GA. As of this release we plan to stabilise the API and only make breaking changes if a significant issue is raised by a customer on commercial support.\n* Expand the range of channel URI params supported by the archive on a per stream basis.\n* Add support for dynamically switching debug logging on and off. https://github.com/aeron-io/aeron/pull/1167[PR #1155].\n* Add debug logging support for flow control.\n* **[C]** Fix memory leak and reassembly of fragmented message greater than 8K in client.\n* Fix short send of recording start event when tracking recording progress. https://github.com/aeron-io/aeron/pull/1155[PR #1155].\n* Improve clean up of subscriptions and control sessions in the Archive when failures occur.\n* **[Java]** Fix bug with flow control `gtag` being carried over erroneously which can cause issues with `ReplayMerge` and other features dependent on group flow control semantics.\n* Reduce the number of memory fences used with `min` and `tagged` flow control.\n* Set initial window segments to 10 for Cubic congestion control and fix issue measuring RTT in the presence of loss.\n* Add the ability to configure archive replication channel on a per operation basis. This enables the setting of congestion control and socket buffer lengths which are important for cluster backup.\n* Use Archive replication for cluster replication, dynamic join, and cluster backup. This requires the cluster and archive config to be correct as configuration errors will not be evident until used - be careful of using localhost for endpoints.\n* Check tag for match when reusing send channel endpoint. https://github.com/aeron-io/aeron/pull/1147[PR #1147].\n* Add the ability to configure socket buffer and receive window on a per channel basis. https://github.com/aeron-io/aeron/pull/1143[PR #1143].\n* Rework Cluster backup and dynamic join to use archive replication.\n* Add support for using a 0 port for cluster catchup endpoints.\n* **[Java]** Better clean up of allocated resources in the driver when failures occur so it can continue without leaks.\n* **[Java]** Reduce linger on explicitly closed resources in the client.\n* **[C/C{plus}{plus}]** Improve the performance of pre-faulting memory mapped file on Linux and Windows. https://github.com/aeron-io/aeron/pull/1127[PR #1127].\n* **[C/C{plus}{plus}]** Clean up warnings in Windows build.\n* Improve Javadoc.\n* Provide sender and receiver with their own cached clocks to be more responsive and isolated from conductor stalls.\n* Add new counters to detect work cycle stalls which track max work cycle latency and count of threshold exceeded observations.\n* Continue to send status messages and heartbeats when running in `DEDICATED` or `SHARED_NETWORK` thread modes to keep connections alive if the driver stalls due to DNS lookups or file IO.\n* Reduce the number of commands from client from 10 to 2 per work cycle to help prevent timeouts and reduce latency pauses.\n* Improve validation of adding destinations to publications.\n* Better handling of race conditions when clients and driver are started/restarted at the same time.\n* Extend debug logging events.\n* Improve diagnostics collection on failed cluster tests.\n* Add `disable` event codes for debug logging so all can be enabled and merged with a disabled set.\n* Add error stacks to C driver to aid debugging of issues.\n* Add storage space warnings and specific exception codes on errors returned to archive client. Archive has new config for low storage thresholds.\n* Detect archive failures in cluster so appropriate action can be taken.\n* Propagate recording errors from the archive back to the archive client that initiated the failed operation.\n* Add specialised `ClusterTerminationException` for expected cluster termination.\n* Reduce network syscalls with Java 11+ for higher numbers of active streams.\n* Respond to cluster client with session open event only after the open session is successfully appended to the cluster log.\n* Upgrade to Version 0.38.0.\n* Upgrade to BND 5.3.0.\n* Upgrade to Mockito 3.9.0.\n* Upgrade to ByteBuddy 1.10.22.\n* Upgrade to JUnit 5.7.1.\n* Upgrade to Gradle 6.8.3.\n* Upgrade to SBE 1.22.0.\n* Upgrade to Agrona 1.10.0.\n\n== 1.31.2 (2021-02-14)\n\n* Respond to cluster client with session open event only after the open session is successfully appended to the cluster log.\n\n== 1.32.0 (2021-01-26)\n\n* **[C/Java]** Fix unexpected image unavailable when a rush of connections comes in for MDC or multicast publication. https://github.com/aeron-io/aeron/issues/1115[Issue #1115].\n* Increase default flow control receiver timeout from 2 to 5 seconds.\n* **[Java]** Cluster performance improvements.\n* **[Java]** Improve liveness tracking for followers catching up with a cluster leader when service logic is running slow.\n* **[Java]** Configuration option for cluster log consumption fragment limit.\n* **[Java]** Improve coordination of Cluster services during an election for log catchup and state changes.\n* **[Java]** Rework Cluster elections to better handle edge conditions in resource limited environments.\n* **[Java]** Add multicast support for cluster log channel.\n* **[C{plus}{plus}]** Add missing methods to `ExclusivePublication` so it is compatible with `Publication`.\n* **[C]** Support compatible command line options for the C driver when running on Windows.\n* **[C]** Fix the deletion of directories on driver shutdown when running on Windows.\n* **[C]** Fix the transposed observation times in the loss report.\n* **[C/C{plus}{plus}]** Migrate the C{plus}{plus} client tools to wrap the C tools for `AeronStat`, `DriverTool`, `LossStat`, and `ErrorStat`.\n* **[C]** Reduce memory footprint and copying in client when sending driver commands.\n* **[Java]** Delete Archive segments asynchronously when purge, truncate, or delete operations are carried out so that deleting a large number of segments does not block the Archive conductor so that the Archive stays responsive. A new `RecordingSignal` has been added for tracking the completetion of the delete.\n* **[C/Java]** Run Cluster system tests against both the Java and C Media Drivers.\n* **[C/Java]** Complete logging and align of feature set with the same configuration that can applied to the Java or C media drivers. https://github.com/aeron-io/aeron/pull/1091[PR #1091].\n* **[C]** Support URIs larger than the label length on publications and subscriptions in the C media driver to be compatible with the Java media driver.\n* **[Java]** Add Java 16-ea to the test matrix.\n* **[Java]** Improve tracking of connection activity to more accurately detect the need for address re-resolution.\n* **[C{plus}{plus}]** Improve samples for better usage illustration and error reporting.\n* **[C]** Complete the feature set for the C client so the C{plus}{plus} wrapper client is a pure wrapper, e.g. provide access to a late bound port for a Subscription.\n* **[Java]** Allow the setting of different error handler when polling a `Subscription`, e.g. use a `RethrowingErrorHandler` to propagate the error out the caller and stop progress.\n* **[C]** Fix throughput issue with C Media Driver debugging logging.\n* **[Java]** Support variable length entries in Archive Catalog and allow for complete purging of old entries. Requires migration. https://github.com/aeron-io/aeron/pull/1069[PR #1069].\n* **[Java]** Reduce memory footprint and copying in client for sending driver commands.\n* **[Java]** Improved Javadoc.\n* Upgrade to Checkstyle 8.38.\n* Upgrade to ByteBuddy 1.10.19.\n* Upgrade to Mockito 3.7.7.\n* Upgrade to Versions 0.36.0.\n* Upgrade to Gradle 6.7.1.\n* Upgrade to SBE 1.21.0.\n* Upgrade to Agrona 1.9.0.\n\n== 1.31.1 (2020-11-02)\n\n* Fix bug in C{plus}{plus} client managing images under a subscriptions due to bug with GCC 7.3.1 failing to emit an acquire fence.\n* Fix bug with cleaning up log buffers which could result in segfault in native driver.\n* Fix bug in C{plus}{plus} client with `putValueVolatile`.\n* Add `AeronException.Category` name to the beginning of error message to indicate the severity in the `DistinctErrorLog`.\n* Improved Javadoc.\n* Schedule Status Messages with more relaxed memory ordering for a ~3% throughput improvement in the Java driver.\n* Memory order fix for scheduling NAKs and Status Messages in native C driver.\n* Enable higher-resolution timers on Windows for native driver so sleep periods less than 16ms.\n* Upgrade to Mockito 3.5.15.\n\n== 1.31.0 (2020-10-14)\n\n* Handle failed log buffer delete in C media driver on Windows. This can happen when a client holds a mapped file open and the driver tries to delete it. https://github.com/aeron-io/aeron/pull/1073[PR #1073].\n* Increase default client liveness timeout from 5-&gt;10s and publication unblock timeout from 10-15s to be softer on clients that experience bad GC pauses or run in resource starved environments.\n* Add C{plus}{plus} `ChannelUriStringBuilder#initialPosition` method to set the initial position of a publication.\n* Add `ownerId` to publication limit counters for being able to track which client created a publication.\n* Improve javadoc and reduce the scope of some methods that should not have been public.\n* Fix C{plus}{plus} `AtomicCounter::getAndSet`.\n* Fix timer cancellation when scheduling in cluster. https://github.com/aeron-io/aeron/issues/1071[Issue #1071].\n* `ReplayMerge` now substitutes the endpoint from the `replayDestination` into the `replayChannel` to simplify configuration.\n* Support using a port of `0` on the replay destination for `ReplayMerge` so that it is assigned by the OS from the ephemeral range.\n* Support using a port of `0` on the replication channel between archives so that it is assigned by the OS from the ephemeral range.\n* Fix the ability to add and remove a destination with port `0` to an MDS Subscription.\n* New subscriptions now late join a stream at the min of existing subscriptions rather than max.\n* Fix implementation of `ExclusivePublication::tryClaim` in C{plus}{plus} wrapper client.\n* Add Cubic congestion control support to the C media driver. https://github.com/aeron-io/aeron/pull/1065[PR #1065].\n* Default to building the C{plus}{plus} archive client as part of the native build.\n* Improve the native Windows build for CLion.\n* Remove the need for having 7-Zip installed for native build on Windows.\n* Improve error handling for archive errors in the consensus module so warnings can be issued and retried.\n* Set media driver heartbeat to `-1` on clean shutdown so it can be immediately restarted without waiting for driver timeout.\n* Add Clang 11 to build mix.\n* Add Java 15 to build mix.\n* Change stop replay failures in the cluster from errors to warnings.\n* Improve `ExtendRecordingTest` to be a better example.\n* Fix cluster tutorial scripts.\n* Improve samples code.\n* Upgrade to Checkstyle 8.36.2.\n* Upgrade to Shadow 6.1.0.\n* Upgrade to ByteBuddy 1.10.17.\n* Upgrade to HdrHistogram_c 0.11.2.\n* Upgrade to SBE 1.20.3.\n* Upgrade to Agrona 1.8.0.\n\n== 1.30.0 (2020-09-20)\n\n* Add hooks so ATS (Aeron Transport Security) can be loaded as a premium feature on the C media driver. https://github.com/aeron-io/aeron/issues/203[Issue #203].\n* Numerous improvements for the native driver on Windows.\n* Further refinement and additions to the C client which is currently at preview status.\n* Remove a number of data dependent loads caused by indirection to reduce latency outliers.\n* Improve logic for expansion of `BufferBuilder` for fragmented messages to be correct at extremes and to be more efficient.\n* Set ANY ADDR to correct protocol family for endpoint based on control when IPv6.\n* Scope Cluster Backup counters by cluster id.\n* Improve Archive client connect error messages.\n* Add deadline checking to C{plus}{plus} Archive client connect.\n* Improve the efficiency of counter searching.\n* Add extra validation for the relationships between timeouts.\n* Change tracking of untethered subscriptions so the bottom 1/4 rather then 1/8 of the window is used to make for easier eviction.\n* Add registration and owner id to counters to help avoid ABA issues and to aid monitoring.\n* Avoid updating the commit position counter when the consensus module is closed.\n* Improve active transport tracking to be more timely and accurate.\n* Make use of cached clocks when referencing counters to reduce system call overhead.\n* Improve `ReplayMerge` tests to show a better example of usage.\n* Add driver and hostname to re-resolution counter for Java and C media drivers.\n* Fix memory corruption with driver naming resolution events in C media driver.\n* Fix dynamic agent dissector logging for C media driver.\n* Improve liveness tracking for channels to reduce overhead and false sharing in the Java Driver.\n* Add a `ChannelUriStringBuilder.toString()` method.\n* Provide a registration id on the add and remove handler methods in clients so they can be removed by the registration id and not rely on the pointer or reference to the callback.\n* Allow the setting of port 0 on Archive and Cluster control response channels for clients so they are automatically allocated from the ephemeral range.\n* Improve native code use of atomics across all platforms and especially on Windows.\n* Improve error messages in the native driver to help indicate which is the offending command and URI.\n* Auto resize the Archive Catalog when full so the Archive does not need shutdown and manually extended.\n* Improve startup code for all clients finding a running media driver which is racing to start at the same time.\n* Support the C{plus}{plus} Archive client on Windows.\n* Set CMake 3.6.1 as the min required version.\n* Upgrade to JUnit 5.7.0.\n* Upgrade to HdrHistogram_c 0.11.1.\n* Upgrade to Version 0.33.0.\n* Upgrade to Checkstyle 8.36.\n* Upgrade to Gradle 6.6.1.\n* Upgrade to Mockito 3.5.10.\n* Upgrade to ByteBuddy 1.10.14.\n* Upgrade to BND 5.1.2.\n* Upgrade to SBE 1.20.2.\n* Upgrade to Agrona 1.7.2.\n\n== 1.29.0 (2020-07-21)\n\n* Further refinement and additions to the C client which is currently at experimental status.\n* Improve error messages when parsing URI params.\n* Fix application of sparse terms in Java Media driver when not used on a per channel basis.\n* Add support for session based subscriptions on IPC and spies to the C media driver.\n* Use `ssc` (Spies Simulate Connection) only in cluster when membership size is 1. This avoids the leader racing ahead of followers which are catching up and a number of cases where the start of a recording can be missed.\n* Add the ability to have spies simulation connection (ssc) configured on a per stream basis for both Java and C media drivers.\n* Fix some false sharing issued introduced for channel re-resolution checking to give a tighter latency distribution.\n* Add state checks to Cluster operations so services do no use features at inappropriate times.\n* Rework build script to help IDEA recognise generated classes and not give false compilation errors.\n* Significantly improve throughput of C media driver when used with the Solarflare ef_vi premium extension to provide the best latency and throughput possible.\n* Fix short send counting in C media driver.\n* Change Archive session workers to behave more like normal Agents so that stack traces are more informative when debugging.\n* Improve error handling and cluster elections when dynamic membership is being used and increase test coverage.\n* Improve session checks when re-adding a publication with the same session id.\n* Refinements to Cluster Backup.\n* Change defaults for throughput tests to use 8k rather than 16k MTUs to better fit with jumbograms.\n* Close recording Archive recording subscriptions with `autoStop = true` that have an error on first image.\n* Detect Archive errors in Cluster so waiting operations can abort and be retried.\n* Fix `aeron_ftruncate` on Windows for native driver so it behaves more like Linux. This addresses races with client and driver starting at the same time which can result in a corrupt CnC file.\n* Avoid int overflow with Cluster snapshots greater than 2GB in length. https://github.com/aeron-io/aeron/pull/959[PR #959].\n* Fix C{plus}{plus} client compile for CentOS 7 with GCC 4.8.5.\n* Add flow control (fc) and group tag (gtag) URI params to Archive stripped channels.\n* Configurable buffer length for Archive record and replay file operations to control batch size via `aeron.archive.file.io.max.length`. New default shows a marked increase in throughput and reduced latency in all our tests.\n* Capture logs from failed Cluster tests to aid debugging.\n* Agent logging for untethered subscription state changes in Java and C media driver.\n* Expanded agent logging for archive activities to aid debugging.\n* Fix segfault in C media driver if transport cannot bind.\n* Add Java 14 to CI.\n* Add native sanitize builds to CI.\n* Upgrade to Versions 0.29.0.\n* Upgrade to Checkstyle 8.34.\n* Upgrade to Mockito 3.4.4.\n* Upgrade to BND 5.1.1.\n* Upgrade to ByteBuddy 1.10.13.\n* Upgrade to HdrHistogram 0.11.0 for C.\n* Upgrade to Gradle 6.5.1.\n* Upgrade to SBE 1.19.0.\n* Upgrade to Agrona 1.6.0.\n\n== 1.28.2 (2020-05-28)\n\n* Fix issue with replaying cluster log when a snapshot is invalidated after a clean termination.\n* Correct arguments to `onReplayNewLeadershipTerm` which got transposed in 1.27.0 release.\n* Validate lower bound of MTU in config so payload must have some contents.\n\n== 1.28.1 (2020-05-27)\n\n* Fix race condition when calling size on C queues.\n* Remove clashing non `const` `ExclusivePublication::channelStatus()` method. https://github.com/aeron-io/aeron/issues/946[Issue #946].\n* Upgrade to SBE 1.18.2.\n* Upgrade to Agrona 1.5.1.\n\n== 1.28.0 (2020-05-25)\n\n* An experimental C API client is now available. We are happy to take feedback but be aware the API is subject to change\nas it gets refined.\n* **[Cluster]** has changed status from experimental to being a preview feature. Many refinements and bug fixes have been\nmade to cluster in the last few months as a result of significant destructive testing. The API is now stable as of this\nrelease and will only change before going GA if a significant issue is found. Support is commercially available.\n* Correct implementation of Cubic congestion control implementation to align with spec.\n* Add support to the C media driver for session-specific and multi-destination subscriptions (MDS), plus complete the functionality so the C media driver can support `Archive`.\n* Support using `0` for port on `endpoint` or `control` so OS assigns the port without conflict and then make it available on `Publication` or `Subscription` via each getting a new `localSocketAddresses()` method. Local socket addresses also get their own counters.\n* Reduced CPU time spent scanning for loss in Java and C drivers so they can scale to a larger number of connections.\n* Apply consistent approach to merge window for `ReplayMerge`, Archive replication, and Cluster catchup.\n* Add the ability to stop a recording by recording identity when the recording id is known.\n* Use CRC if configured and any possible data to help recover last fragments in a recording that may straddle a OS page after an unclean Archive shutdown.\n* Support common short name alias for idle strategies in config for both Java and C media driver such as `noop`, `spin`, `yield`, and `backoff`.\n* Update false sharing protection to support Java 15 class layout and add it to `ExclusivePublication`.\n* Improve Java and C{plus}{plus} samples so they are up to date and give more consistent performance numbers.\n* Java client close operations for publications, subscriptions, and counters now happen asynchronously so the client does not wait for acknowledgement. This allows for more rapid close of resources.\n* Add notifications for client heartbeat counters becoming available and unavailable so Aeron clients can be tracked.\n* Allow for race in creating a new recording in catalog and first segment being written which can happen when a replay is set up right after a recording starts.\n* Upgrade to javadoc-links 5.1.0.\n* Upgrade to ByteBuddy 10.10.1.\n* Upgrade to JUnit 5.6.2.\n* Upgrade to Gradle 6.4.1.\n* Upgrade to SBE 1.18.1.\n* Upgrade to Agrona 1.5.0.\n\n== 1.27.0 (2020-04-01)\n\n* Drivers can be named and names are gossiped between drivers so that they can be used to simplify configuration for endpoints. https://github.com/aeron-io/aeron/wiki/Driver-Name-Resolution**[Driver Name Resolution]**.\n* Fix header file dependencies for C{plus}{plus} archive client.\n* Spy subscriptions can now match on channel tag for publications.\n* Multicast flow control is selected when using manual or dynamic MDC (Multi-Destination-Cast).\n* Add `tryStopRecording` methods to the archive clients so they can be called without raising an exception if no recording is active.\n* Add a counter for the number of active control session on the archive.\n* Add `autoStop` overload when starting a recording in the archive so it is automatically cleaned up when the first matching recordings stops.\n* Resend recording progress events after back pressure to detect tail progress.\n* Improve URI channel parsing validation. https://github.com/aeron-io/aeron/issues/887[Issue #887].\n* Reduce allocation when churning publications.\n* Add CentOS 7 build to CI.\n* Upgrade to BND 5.0.1.\n* Upgrade to Junit 5.6.1.\n* Upgrade to Gradle 6.3.\n* Upgrade to SBE 1.17.0.\n* Upgrade to Agrona 1.4.1.\n\n== 1.26.0 (2020-03-04)\n\n* Add correlation-id to `ArchiveException` and provide the ability to get the last used correlation-id in `AeronArchive` client.\n* Add re-resolution of endpoints when they timeout and become unconnected which can happen when machines migrate in a cloud environment to the Java driver.\n* Add `TaggedMulticastFlowControl` and ability to configure flow control via URI params for Java and C media drivers.\n* Deprecate `PreferredMulticastFlowControl`.\n* Fix mutexes for the C media driver on Windows. https://github.com/aeron-io/aeron/pull/867[PR #867].\n* Fix handling of sockets the C media driver on Windows. https://github.com/aeron-io/aeron/pull/866[PR #866].\n* Fix thread handling for the C media driver on Windows. https://github.com/aeron-io/aeron/pull/864[PR #864].\n* Fix mmap on Windows for the C media driver. https://github.com/aeron-io/aeron/pull/865[PR #865].\n* `SetWaitableTimer` expects a duration in 100-nanosecond intervals on Windows in C media driver. https://github.com/aeron-io/aeron/pull/868[PR #868].\n* Fix NPE when `-checksum` flag is not used, and validation Checksum classname if it is used with `ArchiveTool`.\n* Deal with asynchronous errors from the archive when replicating or Replay Merge.\n* Fixes for Windows C driver. https://github.com/aeron-io/aeron/pull/861[PR #861].\n* Warnings clean up in native code.\n* Fix socket close on Windows for C driver. https://github.com/aeron-io/aeron/pull/857[PR #857].\n* Fix getting a random value in C driver on Windows. https://github.com/aeron-io/aeron/pull/854[PR #854].\n* Reduce allocation of direct buffers in the archive to minimum of what is required depending on configuration.\n* Improve archive behaviour from unexpected outcomes of file read operations.\n* Migrate to Gradle maven-publish plugin.\n* Improve closing of resources in aborted or interrupted operation for Java client and modules.\n* Fix unexpected unavailable image which could occur with mixed use of wildcard and session specific subscriptions on the same channel.\n* Fix deadlock which could occur in C{plus}{plus} client if destroyed too quickly after creation. https://github.com/aeron-io/aeron/issues/844[Issue #844].\n* Improve performance of Archive replay. Gains are 25%-50% depending on message length and platform.\n* Add client shared library support to C{plus}{plus} client. https://github.com/aeron-io/aeron/pull/836[PR #836].\n* Only use MDS for archive replicate when joining a live stream or using a tagged subscription. This allows for multiple concurrent replication streams of recordings which are not joining live or being tagged.\n* Make receiver id channel endpoint specific so multi-destination subscriptions get flow controlled independently as they use different sockets. This results in less loss when using Replay Merge.\n* Improve performance of logging agent to file by batching event writes.\n* Upgrade to Gradle 6.2.1.\n* Upgrade to Versions 0.28.0.\n* Upgrade to Mockito 3.3.0.\n* Upgrade to HdrHistogram_c 0.9.13.\n* Upgrade to BND 5.0.0.\n* Upgrade to SBE 1.16.3.\n* Upgrade to Agrona 1.4.0.\n\n== 1.25.1 (2020-01-21)\n\n* Log to ring buffer with zero copy semantics for improved logging performance. https://github.com/aeron-io/aeron/pull/831[PR #831].\n* Retain file handle after establishing mapping in Windows C{plus}{plus} client. https://github.com/aeron-io/aeron/issues/826[Issue #826].\n* Improve encoding performance of logging to file.\n* Log all events in a consistent manner with standard header.\n* Be consistent with the use of positional reads and writes in the archive for supported OS synchronisation and slightly improved performance.\n* Configure Java `DistinctErrorLog` to be US-ASCII rather than UTF-8 for compatibility with native driver.\n* Run slow tests daily in CI.\n* add `GNU_SOURCE` to clock for native builds on CentOS.\n* Upgrade to Agrona 1.3.0.\n* Upgrade to SBE 1.16.1.\n* Upgrade to JUnit 5.6.0.\n\n== 1.25.0 (2020-01-12)\n\n* Where possible only weave in logging hooks when enabled in the Java driver. This can help performance for those who are only logging a few events.\n* Add ability to log the control channel responses from the Archive.\n* Fix issue with truncating recordings when truncate position equals stop position and start of segment to ensure file is deleted.\n* Fix issue with unaligned access to fields in `LossReport`.\n* Introduce interceptor bind framework to C driver for supporting loss testing, logging, and media layers other than BSD sockets.\n* Apply system tests to C driver when running in CI. When apply this a number of bugs got fixed in the C media driver.\n* Move CI from Travis to GitHub Actions and test on Windows, Linux, and OSX.\n* Support for agent logging in the C driver to file to match Java with the `aeron.event.log.filename`.\n* Support for adding checksums to archive recordings as CRCs which can be verified to detect file corruption.\n* Add support for applying and verifying checksums to recordings via `ArchiveTool`.\n* Add support for fixing recordings after after a system crash running an Archive.\n* Improve crash recovery for the archive when restarting.\n* Add cached clocks to C media driver to reduce the overhead of clock calls and improve performance, especially in cloud environments. https://github.com/aeron-io/aeron/issues/606[Issue #606].\n* Fix thread local storage for Windows C media driver. https://github.com/aeron-io/aeron/pull/795[PR #795].\n* Fixes for Windows C media driver. https://github.com/aeron-io/aeron/pull/794[PR #794].\n* Improve EOS reporting in `Image.toString()` method. https://github.com/aeron-io/aeron/pull/792[PR #792].\n* Fix recovery of stop position in crashed archive when start position was non-zero.\n* Provide API for for features that existed in `CatalogTool` in new `ArchiveTool`.\n* Don't linger replay publications in `ReplayMerge` so resources can be reclaimed sooner.\n* Default warning of Aeron direction existing on media driver start to false.\n* Add poll support to C media driver on Windows. https://github.com/aeron-io/aeron/pull/784[PR #784].\n* Name log buffers based on correlation id.\n* Provide timestamp with stacktraces in default client error logger. https://github.com/aeron-io/aeron/pull/774[PR #774].\n* Reject concurrent publications that specify `init-term-id`, `term-id`, and `term-offset`. https://github.com/aeron-io/aeron/pull/773[PR #773].\n* Add sample illustrating how to build an index and basic time series on a recording that is also replicated in `IndexedReplicatedRecording`.\n* Improve performance for getting `Header.position()` in Java fragment handler.\n* Add `BasicAuthenticator` to C{plus}{plus} archive client samples.\n* Fix issue with configuring threading mode in C media driver. https://github.com/aeron-io/aeron/issues/765[Issue #785].\n* Improve validation when extending recordings in the archive.\n* Add `taggedReplicate` operation to the archive for replicating a stream with provided tags so an external subscription can follow along.\n* Don't update the recording position in the archive if an exception occurs during a write. Previous behaviour could have erroneously reported progress when disk was full or underlying storage failure.\n* Fix issue in C media driver when a subscription could have go away yet the publication considered it was still connected.\n* Fix issue with incremental build dependencies. https://github.com/aeron-io/aeron/pull/762[PR #762].\n* Fix recording events enabled property name.\n* Add authentication support to C{plus}{plus} archive client.\n* Upgrade to Agrona 1.2.0.\n* Upgrade to SBE 1.16.0.\n* Upgrade to JUnit 5.6.0-RC1.\n* Upgrade to Checkstyle 8.28.\n* Upgrade to HdrHistogram 2.1.12.\n* Upgrade to ByteBuddy 1.10.5.\n* Upgrade to Gradle 6.0.1.\n* Upgrade to javadoc-links 4.1.6.\n* Upgrade to Mockito 3.2.0.\n* Upgrade to gtest 1.10.0.\n* Upgrade to HdrHistogram_c 0.9.12.\n\n== 1.24.0 (2019-11-24)\n\n* Add bi-directional version identification to the archive network protocol\n* Add support for authenticated sessions to the archive.\n* Support setting of session-id on publications in the C media driver. https://github.com/aeron-io/aeron/pull/623[PR #623].\n* Fix setting of initial position on an exclusive publication in the C driver when the initial position is beyond the first term. https://github.com/aeron-io/aeron/pull/750[PR #750].\n* Allow for archive error log to be stored in archive mark file when running out of process from a media driver.\n* Trim down unneeded dependencies in agent and all shadow JARs.\n* Clean up allocated resources in C{plus}{plus} and Java clients when URI errors occur.\n* Add `boundedPoll` to `Image` for C{plus}{plus} and Java. https://github.com/aeron-io/aeron/pull/744[PR #744].\n* Only include what is used in C{plus}{plus} publication headers. https://github.com/aeron-io/aeron/pull/743[PR #743].\n* Provide unique type ids to error counters. https://github.com/aeron-io/aeron/pull/741[PR #741].\n* Add new archive control messages to agent logging and improve overall agent performance.\n* Fix pointcut for Archive control message logging. https://github.com/aeron-io/aeron/pull/740[PR #740].\n* Close files in Windows C{plus}{plus} client to prevent memory leak. https://github.com/aeron-io/aeron/issues/737[Issue #737].\n* Improve the performance for MDC dynamic mode in the Java driver.\n* Set javadoc encoding to UTF-8.\n* Improve validation of channel URIs for endpoint, control, tags, and distinguishing characteristics in both C and Java drivers.\n* Fix calculation for archive truncate when offset is beyond first term in a segment.\n* Check for reentrant calls when in Archive callbacks and throw an exception if detected.\n* Change sample scripts to use the aeron-all JAR as a better example.\n* Upgrade to javadoc-links 4.1.4.\n* Upgrade to Build Scan 3.0.0.\n* Upgrade to Shadow 5.2.0.\n* Upgrade to ByteBuddy 1.10.2.\n* Upgrade to SBE 1.15.0.\n* Upgrade to Agrona 1.1.0.\n\n== 1.23.1 (2019-11-06)\n\n* Correct bug when setting `MediaDriver.Context.rejoinStream` which set `reliableStream` property by mistake and update configuration output dump.\n* Add bind address and port to channel endpoint counter label to help with debugging connections.\n* Fix narrowing type conversion in C{plus}{plus} client for subscription images. https://github.com/aeron-io/aeron/pull/726[PR #726].\n* Add progress checks to `ReplayMerge` and a new terminal state of `FAILED` which is entered on exception or lost connection to the archive.\n* Track close following connections with MDS without timing them out which can help with `ReplayMerge`.\n* Support manual control on MDC not requiring the control address:port to be specified so it can be automatically assigned.\n* Add ability to disable the recording events publication in the archive to save resources when it is not required.\n* Add protocol version of the server to the connect response for archive clients.\n* Upgrade to SBE 1.14.1.\n* Upgrade to Agrona 1.0.11.\n\n== 1.23.0 (2019-10-27)\n\n* Support the separate configuration of idle strategies for the replay and recording agent in the archive when running dedicated threading mode.\n* Improve ownership tracking for subscriptions and images in C{plus}{plus} client.\n* Improve matching of tagged channels.\n* Increase archive storage version to 2.0.0 which requires the use of migration tool for existing archives.\n* Add operations to purge and restore the history of a recording in the archive.\n* Add the ability to query start position for a recording.\n* Add Image specific fragment assemblers for C{plus}{plus} client.\n* Reduce cacheline padding to save on memory footprint.\n* Fix double delete in Aeron destructor. https://github.com/aeron-io/aeron/issues/717[Issue #717].\n* C{plus}{plus} client refinements. https://github.com/aeron-io/aeron/pull/716[PR #716].\n* Upgrade to javadoc-links 4.1.3.\n* Upgrade to Gradle 5.6.3.\n* Upgrade to Checkstyle 8.25.\n* Upgrade to SBE 1.14.0.\n* Upgrade to Agrona 1.0.9.\n\n== 1.22.1 (2019-10-11)\n\n* Fix command message validation which failed to take account of message offset.\n* Address some false sharing issues in the Java and C{plus}{plus} clients which can add 50ns of latency to RTT.\n* Provide original channel URI in error message when parsing fails to port for an endpoint address. https://github.com/aeron-io/aeron/pull/714[PR #714].\n* Rewrite messages from older clients to the archive to allow for gradual upgrade of clients to the new archive. This support will last for only one minor version.\n* Separate versioning schema for network protocol from file formats for the archive to allow them to evolve independently.\n* Only check concurrent recording limits upfront in the archive to avoid later asynchronous errors.\n* Reclaim mapped memory for IPC publications as soon as ref count is 0 and drained by subscriptions without going into 10 second linger.\n\n== 1.22.0 (2019-10-08)\n\n* This release increases the major version on the archive wire protocol and file format. To upgrade it is necessary to update all archive clients and the archive at the same time. Also an archive migration is required by running the `CatalogTool` with the https://github.com/aeron-io/aeron/tree/master/aeron-archive#migration**[migrate]** option. Be sure to backup the archive first before doing a migrate.\n* Add recording signal reporting on the control stream for an archive. The `RecordingSignalAdapter` can be used to track signals of operations happening to recordings such as START, STOP, EXTEND, REPLICATE, MERGE, etc.\n* Improved Javadoc for archive configuration.\n* Improved checking for clashing session-ids for manually configured publications.\n* Reduce heartbeat updates to mark files to once per second to reduce IO traffic.\n* Reclaim mapped memory for images by not lingering when the last subscription is closed. This can reclaim the mapped memory 10 seconds sooner by default.\n* Fix ref counting to send channel endpoints which could cause a stream to get stopped early when multiple publications use the same channel.\n* Add Archive replication feature which replicate a recording from one archive to another with the option of merging with a live multicast stream and continuing to support multiple redundant recordings.\n* Reduce Java memory footprint of Archive client.\n* Reduce default max concurrent recordings and replay in the archive from 50 to 20.\n* Improve consistency of error codes and command validation to both Java and C Media Drivers.\n* Add `Image.activeTransportCount()` to track active transports when using MDS which can be used to make `ReplayMerge` more reliable.\n* Add correlation id to `RegistrationException` to help with debugging.\n* Allocate non-sparse files in Java media driver at safepoint to help avoid Time-To-SafePoint (TTSP) issues.\n* Add the ability to configure congestion control as a channel URI param with the `cc=static` or `cc=cubic` options.\n* Handle channel endpoint errors in the C{plus}{plus} client.\n* Add support to the Java client for adding an removing destinations to publications and subscriptions asynchronously.\n* Catch errors when opening receive destinations and report them to the client.\n* Clean up bound ports on Windows when destinations are removed from MDS Subscripitons.\n* Improve error messages on channel conflicts.\n* Add `rejoin` URI param to channels so that when an image gets timed out to configure if it should stream or not.\n* Don't try to send archive client close messages when publication is not connected to avoid exceptions.\n* Improve reliability of counter active and reuse checks.\n* Clean up pending setup messages when a channel when endpoints are closed.\n* Use heartbeat timestamp counters to indicate client liveness rather than command messages. This gives more stable behaviour on configurations with multiple clients sending many commands.\n* Reworking of C Media Driver internals to more easily accommodate other media APIs such as ef_vi and DPDK.\n* Add option to delete the `aeron.dir` on shutdown of the media drivers.\n* Make `MediaDriver.close()` idempotent.\n* Abort further reading of archive control stream once listed descriptors have been read so further messages are not missed.\n* Improve reliability and precision of `ReplayMerge`.\n* Update session-id in catalog entries when an archive recording is extended.\n* Add 'group' URI param to indicate if receiver group semantics, e.g. multicast NAK semantics, can be applied to Multi-Destination-Cast.\n* More efficient and less allocating IP address dissection in logging agent.\n* Change Java `RecordingReader` and `CatalogTool` so they can read active recordings.\n* Improve handling of thread interrupt in Java client and archive client.\n* Add INVOKER option and config check to C media driver.\n* Add Java client `Aeron.Context.awaitingIdleStrategy()` configuration option for what to use when making a synchronous call to the driver.\n* Add log started event with timestamp when logging is enabled.\n* Add cncVersion to configuration print on driver start.\n* Fix potential out of bounds access for bytes received update in C media driver.\n* Upgrade to Checkstyle 8.24.\n* Upgrade to Mockito 3.1.0.\n* Upgrade to javadoc-links 4.1.2.\n* Upgrade to Gradle 5.6.2.\n* Upgrade to build-scan 2.4.2.\n* Upgrade to SBE 1.13.3.\n* Upgrade to Agrona 1.0.8.\n\n== 1.21.2 (2019-08-19)\n\n* Add client close handler to C{plus}{plus} client which can be used to detect close after client/driver timeouts for cleanup. It will be called on any client close including the shared pointer going out of scope and will be called only once.\n* Try to call all the unavailable handlers in Java and C{plus}{plus} clients regardless of how the client gets closed, especially in the case of timeouts.\n* Add addition relevant fields to `Image.toString()` method for debugging.\n* Correct log position in some cluster event messages which had the potential to be wrong.\n* Upgrade to SBE 1.13.2.\n\n== 1.21.1 (2019-08-14)\n\n* Enable timestamping of sequenced events in cluster at greater precision than milliseconds.\n* Avoid seg faults by not incrementing error counters when starvation timeouts occur.\n* Check for thread interrupt in spin loops so agents can be interrupted and tests can abort more cleanly.\n* Coordinate agent shutdown in the Archive on timeout for a more clean shutdown.\n* Detect timeouts due to resource starvation in Archive and Cluster and terminate to avoid seg faults.\n* Allow the setting of an Aeron client in the Archive without an invoker.\n* Upgrade to javadoc-links 3.8.4.\n* Upgrade to SBE 1.13.1.\n* Upgrade to Agrona 1.0.7.\n\n== 1.21.0 (2019-08-02)\n\n* Complete work on IPC messages from services and reliable timers for Cluster.\n* Simplify logic in `Image.controlledPeek` methods and correct return value on closed on Java client.\n* Support OSGi manifest headers. https://github.com/aeron-io/aeron/pull/690[PR #690].\n* Add missing Javadoc to public classes.\n* Add `AeronException.Category` enum to Aeron exceptions for Java and C{plus}{plus} by separating warn, error, and fatal categories of exception.\n* Ability to add and remove close handlers on Java Aeron client to avoid use after close and segfaults due to unmapped files.\n* Check for Aeron client close in Archive due to starvation to avoid segfaults.\n* Remove layer of indirection from `Subscription` to `Image` in C{plus}{plus} client to help avoid cache misses.\n* Update licence references to https.\n* Upgrade to Mockito 3.0.0.\n* Upgrade to Checkstyle 8.23.\n* Upgrade to javadoc-links 3.8.1.\n* Upgrade to Gradle 5.5.1.\n* Upgrade to SBE 1.13.0.\n* Upgrade to Agrona 1.0.6.\n\n== 1.20.0 (2019-07-12)\n\n* Add pre-touch option for memory mapped files in clients to fault in pages to reduce latency spikes on first cycle around log buffers.\n* Rework C{plus}{plus} client to share log buffer mappings between publications and subscriptions when possible to reduce mapping costs.\n* Rework C{plus}{plus} client to manage images via `shared_ptr` to help avoid memory reclamation issues. https://github.com/aeron-io/aeron/issues/476[Issue #467].\n* Rework management of C{plus}{plus} client resources to be more O(1) when reacting to events from the driver.\n* Set error handler on Aeron client in Archive client when set. https://github.com/aeron-io/aeron/pull/687[PR #687].\n* Check for reentrant calls to Aeron client from C{plus}{plus} callbacks and call error handler if detected.\n* Add `watch` option to Java `AeronStat` and separate out counter reader. https://github.com/aeron-io/aeron/pull/684[PR #684].\n* Tighten up logic to avoid use of C{plus}{plus} Aeron client after close.\n* Replace division operations with shifts where possible to improve performance on startup in Java implementation.\n* Support adding and removing counters availability handlers after client connect and to support multiple handlers in Java and C{plus}{plus} clients.\n* Add the ability to do archive replays bounded by a counter to Java and C{plus}{plus} implementations.\n* Improve and expand Java and C{plus}{plus} samples.\n* Some fixes for the Windows native build which is still experimental.\n* Remove unused linger feature from flow control to reduce footprint and improve performance.\n* Fix concurrency issue with buffer cleaning which could result in a stream locking up and being in a permanent back-pressure state.\n* Add `stopAllReplays` operation to the Archive which can be on a specific recording or wildcarded to all recordings.\n* Don't treat explicit client closes as timeouts. https://github.com/aeron-io/aeron/pull/681[PR #681].\n* Limit resend window to half a term length to avoid unnecessary under runs.\n* Add observed values in timeout exception messages. https://github.com/aeron-io/aeron/pull/680[PR #680].\n* Reduce allocation when using logging agent and improve its performance.\n* Use more efficient clock implementations in C media driver.\n* Use correct message encoder when sending a `stopReplay` to the archive. https://github.com/aeron-io/aeron/pull/676[PR #676].\n* Correctly initialise send and clean positions on publications when a non-zero start is required in the C driver.\n* Add semantic versioning to CnC files. https://github.com/aeron-io/aeron/issues/624[Issue #624].\n* Fix `Subscription::isConnected()` in C{plus}{plus} client.\n* Many improvements to Cluster which is not yet GA.\n* Upgrade to Mockito 2.28.2.\n* Upgrade to javadoc-links 3.7.5.\n* Upgrade to Checkstyle 8.22.\n* Upgrade to Shadow 5.1.0.\n* Upgrade to SBE 1.12.8.\n* Upgrade to Agrona 1.0.3.\n\n== 1.19.1 (2019-05-26)\n\n* Fix logging of cluster election events due to class loading clash with aeron agent.\n* Fix issue with snapshot on leader not recording which service message it had appended up to.\n* Upgrade to SBE 1.12.7.\n\n== 1.19.0 (2019-05-25)\n\n* Add support for clustered services to send reliable ingress back into the cluster over IPC to be sequenced into the log.\n* Added `aeron.client.close.linger.duration` which can be set greater than the default of 0 to help clients in resource constrained environments, or long GC pause applications, from experiencing seg faults from unmapped files.\n* Close Java `Aeron` client when timeouts occur and delay before unmapping files to help prevent seg faults.\n* Add `dump` option to `CatalogTool` to dump out full details including recorded stream data. https://github.com/aeron-io/aeron/pull/669[PR #669].\n* Provide read only view of Archive Catalog. https://github.com/aeron-io/aeron/pull/668[PR #668].\n* Protect against contexts being used in multiple drivers or clients. https://github.com/aeron-io/aeron/pull/666[PR #666].\n* Add support for channel tags to C media driver. https://github.com/aeron-io/aeron/issues/622[Issue #622].\n* Make socket buffer config settings public for driver context.\n* Upgrade to javadoc-links 3.6.4 to allow for offline build.\n* Upgrade to hamcrest 2.1.\n* Upgrade to SBE 1.12.6.\n* Upgrade to Agrona 1.0.1.\n\n== 1.18.0 (2019-05-03)\n\n* Various improvements and fixes for Aeron Cluster. Elections are becoming robust and the API is firming up.\n* Fix counter resource leak when cycling UDP publications with back pressure counters. https://github.com/aeron-io/aeron/pull/663[PR #663].\n* Add `tether` support for local flow control to Java and C media drivers. This allows a subscription to declare itself as a tether or not on local flow control. If not a tether then it can be left behind if it stops or cannot keep up.\n* C and Java media drivers can optionally print their configuration on startup.\n* Greatly improved configuration for C media driver allowing configuration via properties files fetched from local file system or over HTTP, or via API for context. The list of files can be passed on the command line to `aeronmd`.\n* Include `session-id` in archive recording key regardless of using tags or not.\n* Abort Archive sessions if the response publication fails after successfully connected or when any other exception occurs during processing. This avoid infinite failure loop in the archive on some conditions.\n* Improve Javadoc.\n* Add channel param for indicating if `EOS` should be sent on close of publication or not.\n* Improve build of native driver on Windows.\n* Output from ctest on failure for native build.\n* Upgrade to io.freefair.javadoc-links 3.2.1.\n* Upgrade to Checkstyle 8.20.\n* Upgrade to ByteBuddy 1.9.10.\n* Upgrade to Mockito 2.27.0.\n* Upgrade to Gradle 5.4.1.\n* Upgrade to SBE 1.12.5.\n* Upgrade to Agrona 1.0.0.\n\n== 1.17.0 (2019-03-28)\n\n* Add a `BacklogStat` tool for inspecting the backlog in bytes for consumers of streams. https://github.com/aeron-io/aeron/pull/650[PR #650].\n* Faster startup and build time on Windows.\n* Reduce module warnings on Java 11.\n* Change default archive recording progress event channel to dynamic MDC to avoid `PortUnreachableException`s that cause garbage.\n* Add the ability to terminate a Java or C media driver by sending it a command. Useful for testing in a mixed language environment.\n* Fix issue with multi-destination subscriptions that are set up in advance. https://github.com/aeron-io/aeron/pull/649[PR #649].\n* Add sender back-pressure event counters per stream for monitoring.\n* Improve `AsyncConnect` for cluster and archive clients and use it wrapped in the sync implementation.\n* Add `min` flow control strategy to the C Media Driver.\n* Improve `toString()` methods in Aeron wire protocol flyweights for reduced allocation and better performance.\n* Complete work on C{plus}{plus} Archive client.\n* Change Stats tools to use read only file mapping for safety and better NUMA performance.\n* Add back-off idle strategy to the C Media Driver.\n* Add C{plus}{plus} `LossStat` application.\n* Fix issues with Archive Catalog failing to load due to old version.\n* Clean up native build for Windows.\n* Complete work on `ReplayMerge` for both Java and C{plus}{plus} Archive clients.\n* Add `writev` support to SPSC C ring buffer. https://github.com/aeron-io/aeron/pull/644[PR #644].\n* Add basic validation to configuration for client and unblock timeouts.\n* Add event codes and support logging in archive and cluster for major events. https://github.com/aeron-io/aeron/pull/646[PR #646].\n* Upgrade Checkstyle DTD and apply checkstyle to method Javadoc.\n* Use HTTPS in generated Javadoc links and fix warnings under Java 11 and OpenJDK.\n* Report version numbers in conflict when connecting to archive or cluster.\n* Upgrade to Mockito 2.25.1.\n* Upgrade to Gradle 5.3.1.\n* Upgrade to SBE 1.12.4.\n* Upgrade to Agrona 0.9.35.\n\n== 1.16.0 (2019-03-10)\n\n* Remove deprecated Subscription `EOS` handlers and `ExclusiveBufferClaim` from Java and C{plus}{plus}.\n* Provide the ability to ignore warning for Aeron direction existence in C Media Driver. https://github.com/aeron-io/aeron/issues/639[Issue #639].\n* Fix memory leak when adding and removing destinations for the C Media Driver.\n* Include links when generating Javadoc.\n* Add `ChannelUri` and `ChannelUriStringBuilder` to C{plus}{plus} for ease of use when with channels.\n* Add C{plus}{plus} `BackoffIdleStrategy`.\n* Reduce object allocation in Java media driver startup.\n* Provide the ability for the driver to print out all it configuration properties on start via `aeron.print.configuration=true`.\n* Clean up Java driver configurations so that all properties can be configured from system properties or context.\n* Provide the ability to list active recording subscriptions on the archive so they can be tracked and closed when clients do not clean up gracefully.\n* Handle race conditions when establishing control session connections to the archive when the same stream id and channel are used by multiple clients or over multicast.\n* Check that messages are for the correct protocol communicating with the archive and cluster.\n* Add experimental C{plus}{plus} client for Aeron Archive.\n* Add experimental `ReplayMerge` for catching up to a live stream from an archive.\n* Add semantic version support to archive and cluster protocols.\n* Upgrade to Mockito 2.25.0.\n* Upgrade to Shadow 5.0.0.\n* Upgrade to Gradle 5.2.1 and remove used of the now deprecated OSGI plugin.\n* Upgrade to Checkstyle 8.18.\n* Upgrade to SBE 1.12.3.\n* Upgrade to Agrona 0.9.34.\n\n== 1.15.3 (2019-02-16)\n\n* Cluster refinements.\n* Reset padding value on failed claim to C ring buffer. https://github.com/aeron-io/aeron/issues/636[Issue #636].\n* Add clang 6 debug build to Travis CI.\n* Correct issue with archive and cluster mark files version so it is not tied to SBE schema and instead uses semantic versioning. https://github.com/aeron-io/aeron/issues/638[Issue #638].\n* Deal with multiple archive clients connecting on the same stream id and channel. https://github.com/aeron-io/aeron/pull/634[PR #634].\n* Cleanup of C{plus}{plus} client. https://github.com/aeron-io/aeron/pull/631[PR #631].\n* Upgrade to SBE 1.12.2.\n* Upgrade to Agrona 0.9.33.\n\n== 1.15.2 (2019-02-05)\n\n* Fix issue with length of channel not being taken into account when parsing commands in the C Media Driver which could lead to corrupt URI channel params.\n* Add `Automatic-Module-Name` to help support modular Java applications using Aeron. https://github.com/aeron-io/aeron/issues/627[Issue #627].\n* Reduce cache missing when handling loss in Java Media Driver.\n* Relocate Byte Buddy in fat JAR to avoid clash with other needs for Byte Buddy such as Mockito.\n* Upgrade to Byte Buddy 1.9.7.\n* Upgrade to Mockito 2.24.0.\n* Upgrade to SBE 1.12.1.\n* Upgrade to Agrona 0.9.32.\n\n== 1.15.1 (2019-01-30)\n\n* Use unique schema ids to help detect stream miss-configuration between archive and cluster protocols, and allow for future extension.\n* Additional validation of publication and subscription URI channel params.\n* Experimental build of C Media Driver for Windows. https://github.com/aeron-io/aeron/pull/610[PR #610].\n* Fix segment length recorded in the Archive Catalog when term length greater than segment length. https://github.com/aeron-io/aeron/pull/619[PR #619].\n* Add support to C Media Driver for initialising a publication for replay at a given position.\n* Shadow JAR in the correct versions of HdrHistogram and ByteBuddy into `aeron-all`.\n* Upgrade to Checkstyle 8.17.\n* Upgrade to HdrHistogram_c 0.9.8.\n* Upgrade to SBE 1.12.0.\n* Upgrade to Agrona 0.9.31.\n\n== 1.15.0 (2019-01-21)\n\n* Correct order of `correlationId` and `relevantId` when reporting asynchronous messages back from the Archive.\n* Add `INVALID_EXTENSION` error code for requests to extend an existing recording when a new image that does not match.\n* Add checks to Archive client for use after close.\n* Update Archive Catalog with `controlSessionId` and `correlationId` when an existing recording is extended.\n* Have ownership for contexts pass to agent after successful start so the agent is responsible for close to help about seg faults with unmapped files. Contexts are now not `AutoClosable` so people are not tempted to use them in try-with-resources idioms. https://github.com/aeron-io/aeron/issues/612[Issue #612].\n* Add checks to C{plus}{plus} client for use after close.\n* Detect a broadcast buffer wrap when the client has been unresponsive for a long time and close the client.\n* Notify client if the driver times them out so they can gracefully shutdown. https://github.com/aeron-io/aeron/issues/611[Issue #611].\n* Add support for human readable short form properties for configuring the C media driver. https://github.com/aeron-io/aeron/issues/603[Issue #603].\n* Parse interface and address URI parmas without using regex for portability and better performance in C media driver.\n* Parse interface URI param without using regex for less allocation and better performance in Java media driver.\n* Validate initial position when constructing a Channel URI.\n* Upgrade to HdrHistogram 2.1.11.\n* Upgrade to Shadow 4.0.4.\n* Upgrade to SBE 1.11.0.\n* Upgrade to Agrona 0.9.30.\n\n== 1.14.0 (2019-01-06)\n\n* Lots of testing on Cluster in preparation for release.\n* Avoid using unmapped files during an interrupt close which can cause seg faults. https://github.com/aeron-io/aeron/issues/607[Issue #607].\n* Store full channel URI in counters labels for the C media driver.\n* Default build to include the C media driver with C{plus}{plus} client.\n* Enable the setting of `mtu`, `term-length`, `sparse`, `linger`, and `reliable` as channel URI params for the C media driver.\n* Macro guard against missing _PROJECT_SOURCE_DIR_. https://github.com/aeron-io/aeron/pull/601[PR #601].\n* Clean up of warnings in native code.\n* Fix unblocking of full ring buffers when clients die.\n* Fix clearing of `linger` and `sparse` params in `ChannelUriStringBuilder`.\n* Fix issue with stripped channel keeping `linger` and `sparse` params that would make stop recording fail.\n* Fix 'extended alignment' error in Visual Studio 2017 (&gt;15.8). https://github.com/aeron-io/aeron/pull/599[Issue #599].\n* Add definition for Add definition for *PROJECT_SOURCE_DIR* to aeron_client's C{plus}{plus} public interface. https://github.com/aeron-io/aeron/pull/596[PR #596].\n* Upgrade to Google Benchmark 1.4.1.\n* Upgrade to Checkstyle 8.16.\n* Upgrade to SBE 1.10.2.\n* Upgrade to Agrona 0.9.29.\n\n== 1.13.0 (2018-12-16)\n\n* Improve throughput and latency of archive replay, significantly so on Windows and OS X.\n* Add OS X to Travis for native build.\n* Improve handling of compilation flags with CMake. https://github.com/aeron-io/aeron/pull/572[PR #572].\n* Fix Page Size constants on Mac OS. https://github.com/aeron-io/aeron/issues/592[Issue #592].\n* Fix contains match when listing recordings by URI and improve its performance.\n* Add assertions and cleanup C{plus}{plus} client. https://github.com/aeron-io/aeron/issues/582[PR #582].\n* Support `alias` in URI parameters.\n* Fix disparity between Java and C{plus}{plus} for handling of raw tail on publications. https://github.com/aeron-io/aeron/issues/589[Issue #589].\n* Deprecate Subscription `pollEndOfStreams` as Images should be used directly.\n* Use relative source location in C{plus}{plus} exceptions. https://github.com/aeron-io/aeron/pull/588[PR #588].\n* Account for EOS when using multi-destination subscriptions.\n* Keep an Image around while draining without a timeout to facilitate slow consumers.\n* Add the ability to linger an archive replay publication to better cope will lossy networks.\n* Refine parsing of tags to allocate less.\n* Eliminate unnecessary direct buffer allocations when allocating publication and images.\n* Warnings clean up in code base.\n* Fix some false sharing issues between agent threads.\n* Upgrade to Checkstyle 8.15.\n* Upgrade to Gradle 4.10.3.\n* Upgrade to SBE 1.10.1.\n* Upgrade to Agrona 0.9.28.\n\n== 1.12.0 (2018-11-25)\n\n* Add `ErrorHandler` to `AeronArchive` client to better handle async errors from the archive.\n* Deprecate use of Java `ExclusiveBufferClaim` now that base `BufferClaim` has all the necessary functionality.\n* Numerous performance improvements.\n* Add a configurable connection timeout for response and replay streams from the archive.\n* Fix overflow in partition index calculation based on position resulting in SIGSEGV in C{plus}{plus} client. https://github.com/aeron-io/aeron/issues/579[Issue #579].\n* More consistent use of `const` and formatting in C{plus}{plus} client.\n* Fix case of a recording failing with messages &gt; 2MB at the end of term or on unblocking.\n* Force close Java publications and subscriptions on channel errors. https://github.com/aeron-io/aeron/issues/456[Issue #456].\n* Add monitoring counter for Aeron clients which have timed out.\n* Improve `Image` block polling to better handle padding frames.\n* Add more checks to input parameters on publications, images, and the archive to catch usage errors.\n* Add 2 buffer offer to Java `Publication` to avoid need for vectored offer.\n* Add context to C{plus}{plus} `Header` in fragment callback for access to the `Image`.\n* Upgrade to Shadow 4.0.3.\n* Upgrade to Byte Buddy 1.9.3.\n* Upgrade to Mockito 2.23.4.\n* Upgrade to SBE 1.10.0.\n* Upgrade to Agrona 0.9.27.\n\n== 1.11.3 (2018-10-29)\n\n* This is the first release that supports building and running on Java 11.\n* Account for negative initialisation or wrap on `System.nanoTime()` when checking deadlines.\n* Add a build matrix for C/C{plus}{plus} and Java on Travis.\n* Revert failing C{plus}{plus} build due to https://github.com/aeron-io/aeron/issues/555[PR #555]. https://github.com/aeron-io/aeron/issues/560[Issue #560].\n* Make the C/C{plus}{plus} build more modular. https://github.com/aeron-io/aeron/issues/561[PR #561].\n* Preserve original segment file length when extending a recording.\n* Update Java samples showing how IPv4 can give better performance.\n* Add support for multi-destination subscriptions to the C{plus}{plus} client. https://github.com/aeron-io/aeron/issues/568[PR #568].\n* Correct typos in documentation.\n* Add the ability to query the `stopPosition` of a recording in a single call.\n* Add the ability to query for the last recording matching `channel/stream-id/session-id` in the archive.\n* Don't attempt replay of archive entries that are marked invalid.\n* Set better example with list recordings samples from the Archive. https://github.com/aeron-io/aeron/issues/565[Issue #565].\n* Detect and handle thread interrupt in Aeron, Archive, and Cluster Java clients.\n* Initialise variable in `StringUtil.h`. https://github.com/aeron-io/aeron/issues/562[Issue #562].\n* Allow replay from the current recorded position in the Archive.\n* Add missing copy assignment operators to C{plus}{plus} client.\n* Upgrade to Byte Buddy 1.9.0.\n* Upgrade to Mockito 2.23.0.\n* Upgrade to Checkstyle 8.14.\n* Upgrade to SBE 1.9.0.\n* Upgrade to Agrona 0.9.26.\n\n== 1.11.2 (2018-10-05)\n\n* Fix formatting of counter label for stream positions that would corrupt channel and join position. https://github.com/aeron-io/aeron/issues/558[Issue #558].\n* Fix potential race condition with updating receiver window for Java and C Media drivers. Impact likely to be more significant on 32-bit platforms.\n* Allow accepting of subscriptions when image is in the INIT state. It could be observed that 2 or more subscriptions could see only the first succeed when done in quick succession on a dedicated thread mode.\n* Fix potential overflow in position calculations when updating the Archive Catalog after an unclean shutdown.\n* Use relative source for exceptions in C{plus}{plus} client. https://github.com/aeron-io/aeron/pull/555[PR #555].\n* Fix SHARED_NETWORK threading mode for C Media Driver.\n* Upgrade to Shadow 4.0.0.\n* Upgrade to SBE 1.8.9.\n* Upgrade to Agrona 0.9.25.\n\n== 1.11.1 (2018-09-18)\n\n* Fix issue with polling images in a controlled or peek fashion when padding was inserted at the end of a term.\n* Add ability to set file sync level on the archive catalog separate from segment files.\n* Upgrade to Gradle 4.10.1.\n\n== 1.11.0 (2018-09-12)\n\n* Account for message header when listing descriptors from the archive. https://github.com/aeron-io/aeron/issues/550[Issue #550].\n* Reduce allocation of exceptions when network issues occur.\n* Delete log buffers that are no longer referenced in C media driver.\n* Add system counter for failed attempted deletes of log buffers on Windows.\n* Fix build of C{plus}{plus} client on Clang 3.9, 4, and 5. https://github.com/aeron-io/aeron/issues/544[Issue #544].\n* Prepare for building on Java 11.\n* Finally get Aeron working on Sparc. https://github.com/aeron-io/aeron/issues/359[Issue #359].\n* Allow for debug logging in the Java driver via the `aeron.event.log.filename` system property and reduce allocation when using this.\n* Upgrade to Byte Buddy 1.8.21.\n* Upgrade to Mockito 2.22.0.\n* Upgrade to Gradle 4.10.\n* Upgrade to SBE 1.8.8.\n* Upgrade to Agrona 0.9.24.\n\n== 1.10.5 (2018-08-18)\n\n* Fix issue with running Java version of Aeron on Sparc CPUs due to endianess.\n* Improve efficiency and significantly reduce allocation when performing agent logging in Java driver.\n* Fix regression to agent logging for Java driver due to broken pointcut.\n* Make resource linger configurable in the Java client.\n* Fix buffer overrun in C driver with system counters.\n* Reduce footprint for Java subscriptions in the client.\n* Fix issue with adding and removing destination to manual MDC publications when publication is not original.\n* Improve resource lingering code in Java and C{plus}{plus} clients to narrow the possibility of a seg fault when buffers are unmapped in a low resource utilisation scenario.\n* Fix bug when 1GB term lengths are used in Java driver.\n* Apply `const` to methods and arguments in C{plus}{plus} client where appropriate.\n* Added a busy spin idle strategy to C driver that uses X86 PAUSE and make the noop idle strategy truly no op.\n* Added `NoOpIdleStrategy` to C{plus}{plus} client.\n* Fix overflow in large reassembly buffers. https://github.com/aeron-io/aeron/pull/538[PR #538].\n* Set `ByteBuffer`s in Java client to be little endian for underlying log buffers.\n* Upgrade to Checkstyle 8.12.\n* Upgrade to Byte Buddy 1.8.15.\n* Upgrade to Mockito 2.21.0.\n* Upgrade to SBE 1.8.7.\n* Upgrade to Agrona 0.9.23.\n\n== 1.10.4 (2018-07-30)\n\n* Small improvement to startup time for Java Media Driver.\n* Fix C{plus}{plus} compile for Visual Studio on Windows. https://github.com/aeron-io/aeron/pull/528[PR #528].\n* Fix issue causing a seg fault on Windows when connections are closed but clients are holding subscriptions.\n* Set control channel to be sparse for faster connections with the archive.\n* Allow setting of log buffers to be sparse on a per connection basis by using `sparse=true` as a channel URI param.\n* Cluster refinements in preparation for beta.\n* Upgrade to SBE 1.8.6.\n* Upgrade to Agrona 0.9.22.\n* Upgrade to Mockito 2.20.1.\n* Upgrade to Gradle 4.9.\n\n== 1.10.3 (2018-07-19)\n\n* Fix bug with printing contents of `DistinctErrorLog` on start-up with existing errors.\n* Upgrade to SBE 1.8.5.\n\n== 1.10.2 (2018-07-16)\n\n* Reduce allocation when setting up new connections.\n* Allow multiple replays for the same session in the archive if using tag references.\n* Change response publication from concurrent to exclusive so that a connect response is not missed due to another connection on the same channel and stream id. https://github.com/aeron-io/aeron/issues/525[Issue #525].\n* Guard against the Aeron client being used in a re-entrant fashion from a image or counter callback. https://github.com/aeron-io/aeron/issues/524[Issue #524].\n* Enqueue new image events in the archive to avoid race when sending a control action. https://github.com/aeron-io/aeron/issues/524[Issue #524].\n* Return subscription id when starting an archive recording so it can be stopped by id as an alternative to channel and stream id.\n* Upgrade to Byte Buddy 1.8.13.\n* Upgrade to Mockito 2.19.1.\n* Upgrade to SBE 1.8.4.\n* Upgrade to Agrona 0.9.21.\n\n== 1.10.1 (2018-07-07)\n\n* Change default send to status message poll ratio from 4 to 6 for more efficient message sending.\n* Check send and receive channel endpoint UDP datagram channels are open and connected to avoid exception allocation.\n* Move authentication interfaces from the cluster module to aeron client module under the security package.\n* Fix issue with missing passing of relevant id for error codes in response from the archive. https://github.com/aeron-io/aeron/issues/523[Issue #523].\n* Upgrade to Checkstyle 8.11.\n* Upgrade to Gradle 4.8.1.\n* Upgrade to SBE 1.8.3.\n* Upgrade to Agrona 0.9.20.\n\n== 1.10.0 (2018-07-03)\n\n* Preparing for beta release of Aeron Cluster.\n* Add the ability to query the `Image.sourceIdentity()` from a `RecordingPos` counter.\n* Support the extending of an existing recording with a different `session-id`.\n* Add error codes to Archive exceptions so that they can be programatically handled.\n* Provide Aeron specific exceptions where relevant.\n* Add the ability to add and remove endpoint destinations to subscriptions so they can receive from multiple sources to the same `Subscription`. Details of usage can be found on the https://github.com/aeron-io/aeron/wiki/Multiple-Destinations**[Wiki]**.\n* Add support for tagging subscriptions and publications so they can be referrenced.\n* Expand `ChannelUri` and `ChannelUriStringBuilder` to reflect new channel configuration params.\n* Simplify local function call with compile time polymorphic semantics for C{plus}{plus}. https://github.com/aeron-io/aeron/pull/514[PR #514].\n* CMake improvements for native build. https://github.com/aeron-io/aeron/pull/507[PR #507].\n* Reset Thread interrupted flag in Aeron client after catching an `InterruptedException`.\n* Allow the setting of the thread name on C{plus}{plus} `AgentRunner`. https://github.com/aeron-io/aeron/pull/509[PR #509].\n* Upgrade Google Benchmark to 1.4.0.\n* Upgrade to Checkstyle 8.10.1.\n* Upgrade to Byte Buddy 1.8.10.\n* Upgrade to Mockito 2.19.0.\n* Upgrade to SBE 1.8.2.\n* Upgrade to Agrona 0.9.19.\n\n== 1.9.3 (2018-05-09)\n\n* Fix for overflow in calculation on `Image.boundedControlledPoll()` that can result in not progressing past the end of a term.\n\n== 1.9.2 (2018-05-04)\n\n* Add `SegmentInspector` tool for debugging archive segment files.\n* Fix calculation for replaying a recording when the start is not the beginning of the stream and the position is greater than the start.\n* Address race condition between `File.length()` and `FileChannel.size()` when starting Aeron client before Media Driver.\n* Fix extend recording so offset calculation is correct after the first term.\n* Initialise archive recording before first data so that a replay can start before data is transmitted.\n* Fix `CatalogTool` to correctly deal with filenames.\n* Upgrade to Checkstyle 8.10.\n* Upgrade to Shadow 2.0.4.\n* Upgrade to SBE 1.8.1.\n* Upgrade to Agrona 0.9.18\n\n== 1.9.1 (2018-04-29)\n\n* Set HTML 5 version in javadoc if building under Java 10 to avoid warning.\n* Use `Constructor` to avoid Java 10 deprecation warning when dynamically creating new object instances from a class.\n* Fix catalog refresh for Archive on restart for recordings that did not finish gracefully. https://github.com/aeron-io/aeron/pull/502[PR #502].\n* Upgrade to Gradle 4.7.\n* Upgrade to SBE 1.8.0.\n* Upgrade to Agrona 0.9.17.\n\n== 1.9.0 (2018-04-23)\n\n* Add a new exclusive publication handler for the C{plus}{plus} client. https://github.com/aeron-io/aeron/issues/500[Issue #500].\n* Add a `StreamStat` tool for give a summary of all counters grouped by stream for monitoring.\n* Provide a publication position counter that is updated once per second for tracking the publishers for monitoring.\n* Provide monitoring counters for each Aeron client with their heartbeat timestamp.\n* Support the compressing or deletion of archive recordings by external scripts by giving a warning that the files are not available rather than giving an error.\n* Refinements to the Catalog tool.\n* Provide a limit on the range which can be reserved for session ids. https://github.com/aeron-io/aeron/issues/498[Issue #498].\n* Fix race with an Aeron client mapping a CnC file which has not been set the the correct length yet by the driver. https://github.com/aeron-io/aeron/issues/499[Issue #499].\n* Add the ability to truncate a recording in the archive whereby truncating to the stop position effectively deleting the recording.\n* Add the ability to query the current recorded position of an stream from a remote archive.\n* Add support for async connect to an archive.\n* Make archive catalog file length configurable and use a smaller default.\n* Fix write of data frame header for 32 bit machines, e.g. Raspberry Pi.\n* Faster and less garbage version for parsing URI endpoints. https://github.com/aeron-io/aeron/pull/491[Issue #491].\n* Sample code for doing large file transfer with Aeron. https://github.com/aeron-io/aeron/issues/237[Issue #237].\n* Add startup timestamp and PID to CnC file for monitoring. https://github.com/aeron-io/aeron/issues/480[Issue #480].\n* Fix Javadoc generation so the index page references all classes.\n* Upgrade to Shadow 2.0.3.\n* Upgrade to Mockito 2.18.3.\n* Upgrade to Gradle 4.6.\n* Upgrade to Checkstyle 8.9.\n* Upgrade to SBE 1.7.10.\n* Upgrade to Agrona 0.9.16.\n\n== 1.8.2 (2018-03-09)\n\n* Fix vectored offer of large messages with offsets that do not start at zero. PR https://github.com/aeron-io/aeron/pull/475[#475].\n* Replace hot switch statements in the Java driver so the JIT can make better optimisation decisions.\n* Fix race issue with archive replaying an active recording and the existing recording stopping mid replay.\n* Add support for large pages to C driver.\n* Improve error reporting in C driver startup.\n* Upgrade to SBE 1.7.9.\n* Upgrade to Agrona 0.9.15.\n\n== 1.8.1 (2018-02-28)\n\n* Guard against a counter being added with no key in C{plus}{plus} client.\n* Change move to copy semantics for C{plus}{plus} client to avoid concurrency issues while iterating images under a subscription. Issue https://github.com/aeron-io/aeron/issues/472[#472].\n* Simplify archive recording so it works more consistently on all platforms. This has resulted in a better throughput and latency profile.\n* Rework archive to better handle concurrent usage of recording catalog and aeron client.\n* Handle case of removing a subscription that has not seen any traffic that could result in a `NullPointerException`.\n* Update archive samples to be a better illustration of usage.\n* Perform a contains operation for matching channel URIs rather than an exact match when listing recording descriptors.\n* Upgrade to SBE 1.7.8.\n* Upgrade to Agrona 0.9.14.\n\n== 1.8.0 (2018-02-23)\n\n* Fix idle config for C driver from env vars.\n* Add low-latency config to Java sample scripts.\n* Allow all context types to be `Cloneable` in Java for easier config in testing.\n* Add `YeildingIdleStrategy` to C{plus}{plus} client and add a `idle()` method without args to the all strategies.\n* Bring C{plus}{plus} client inline with Java by providing simpler methods for checking channel endpoint status.\n* Add a `MarkFile` so a second archive cannot be started accidentally in the same directory plus provide metadata about the running instance.\n* Improve the `RecordingPos` counter support from tracking the progress of a local recording in the archive.\n* Add the ability to change the linger timeout for a publication to wait around after close in case the receiver needs to recover loss. Issue https://github.com/aeron-io/aeron/issues/452[#452].\n* Support human friendly values such as `term-length=64m` and `mtu=4k` in channel URI params.\n* Improve system tests to be more suitable example code.\n* Improve C{plus}{plus} client performance when built for Windows.\n* Improve the archive client for more robust connection to the archive.\n* Improve the error reporting from the driver when network errors are experienced.\n* Add the ability to extend an existing archive recording as a continuous immutable log. This requires the new publication to have the existing session id which can be provided via the channel URI.\n* Performance improvements to manual multi-destination-cast from Java driver.\n* Improve error reporting from the archive to the control sessions on invalid requests.\n* Numerous small archive performance improvements.\n* Add the ability to the archive to record specific publication sessions and update client API to support this.\n* Reduce the cost of querying the archive for recordings. The listing of a recording descriptor by recording id is now allocation free.\n* Reduce allocation when setting up new connections so connections can be cycled more efficiently.\n* Add a second local subscription to the archive in listening for control sessions. This can be over IPC for greater efficiency.\n* Add the ability to subscribed to a specific `session-id` by adding it as a channel URI param. Useful to isolating a single session rather than wild-carding on channel and stream id.\n* Add the ability to choose a publication `session-id` by setting it as a channel URI param. A default range from low to high value can be configured for the driver to not use in automatic assignment.\n* Add cooldown option on counters so they are not reused for a timeout which defaults to 0.\n* Change congestion control to more accurately measure RTT before feeding into calculations.\n* Reduce default MTU to 1408 to avoid loss on Google Cloud and inter-region AWS. This will reduce throughput on local un-contended networks and a larger value can be configured.\n* Cache clock values during duty cycle to save on system calls in Java driver.\n* Adjust idle constants so backoff will sleep and thus reduce CPU usage. Newer kernels spin on `LockSupport.parkNanos()` with a low value.\n* Handle unaligned strings used in commands to the C driver.\n* Reduce Hamcrest dependency from all to library.\n* Upgrade to Mockito 2.15.0.\n* Upgrade to Gradle 4.5.1.\n* Upgrade to JShadow 2.0.2.\n* Upgrade to Checkstyle 8.8.\n* Upgrade to SBE 1.7.7.\n* Upgrade to Agrona 0.9.13.\n\n== 1.7.0 (2017-12-15)\n\n* Bring the C{plus}{plus} client up to par with the Java client on feature set.\n* Use `fallocate` on Linux with the C driver if supported.\n* Allow `spiesSimulateConnection` to be configured for an `ArchivingMediaDriver`, it was previously hard coded to `true`.\n* Add `RecordingPos` utility to find the counter for recordings so it can be tracked and consumption of Images can be bounded to ensure they are recorded.\n* Move Images out of the active state as soon as they have reached end-of-stream and rebuilt so that they don't get spuriously captured by new Subscriptions.\n* Don't elicit SETUP messages once a Publication has reached end-of-stream to avoid spurious connections.\n* Reduce allocation in the Archive when setting up recordings or replays.\n* Rework client close so it is constant time regardless of the number of publications and subscriptions.\n* Additional simplified API for adding counters that only require a label.\n* Improve efficiency of MDC (Multi-Destination-Cast) connections.\n* Clean up C/C{plus}{plus} build warnings. PR https://github.com/aeron-io/aeron/pull/440[#440].\n* Provide an API to start and stop an Archive recording without creating a Subscription.\n* Fixes for building C{plus}{plus} with Visual Studio. PR https://github.com/aeron-io/aeron/pull/438[#384].\n* Add counter available and unavailable callbacks to the Aeron client.\n* Add the ability to set the Aeron directory to the Archive context.\n* Simplify API for getting channel endpoint status on a `Publication` or `Subscription` this is a breaking change to the feature introduced in the 1.6.0 release.\n* Add bounded control poll to `Image` so they can be conditionally consumed.\n* Fix samples for simple publication and subscription for timing issues. Issue https://github.com/aeron-io/aeron/issues/435[#435].\n* Upgrade to Checkstyle 8.5.\n* Upgrade HdrHistogram for C/C{plus}{plus}.\n* Upgrade to Mockito 2.13.0.\n* Upgrade to JShadow 2.0.1.\n* Upgrade to Gradle 4.4.\n* Upgrade to SBE 1.7.6.\n* Upgrade to Agrona 0.9.12.\n\n== 1.6.0 (2017-11-15)\n\n* Change Aeron client ownership in `AeronArchive` so that the archive client will default to not owning the Aeron client unless it creates it.\n* Detection of network channel setup errors, e.g. port already in use, and propagation back to the client public the addition of channel status indications of publications and subscriptions.\n* Reduced use of capturing lambdas to allow for faster warmup and reduced allocation on connection setup.\n* Simplify client command API to allow for easier development of clients in other languages.\n* Provide an API for allocating `Counter`s which are managed by the media driver and can be read with `AeronStat`\n* Small performance improvements and faster warm up of Multi-Destination-Cast publications.\n* Decouple Archive from media driver so that the Java Archive can be used with the C media driver.\n* Tidy up logic in archive client to better handle unexpected messages from the archive and not read messages in pollers after message may have been released.\n* Modify unblock logic so that it does not apply to exclusive publications except in when past the active state.\n* Correct Javadoc for `Publication`s and concurrent semantics. Issue https://github.com/aeron-io/aeron/issues/429[#429].\n* Update to SBE 1.7.5.\n* Update to Agrona 0.9.11.\n* Update to Byte Buddy 1.7.9.\n* Update to Mockito 2.12.0.\n* Update to Checkstyle 8.4.\n\n== 1.5.2 (2017-11-01)\n\n* Improve failure detection in `AeronArchive` client for when connections drop. Issue https://github.com/aeron-io/aeron/issues/427[#427].\n* Add a lock to `AeronArchive` client so it is threadsafe.\n* Fix issue introduced with unblock logic that would not unblock when term had rotated ahead of blocked position. Issue https://github.com/aeron-io/aeron/issues/424[#424].\n* Remove use of PAGE_SIZE in C{plus}{plus} client to avoid compilation errors on Android. Issue https://github.com/aeron-io/aeron/issues/405[#405].\n* Have `Publication` and `ExclusivePublication` have a common super class of Publication in Java so they can be interchanged.\n* Add `isConnected()` method to Subscription.\n* Check window length against socket buffer on start up fail fast. Issue https://github.com/aeron-io/aeron/issues/420[#420].\n* Support units (k, m, and g) for size such as `16k` for a 16 KB buffer length, and (s, ms, us, ns) for durations in system properties.\n* Add generated codecs to aeron-archive.jar. Issue https://github.com/aeron-io/aeron/issues/416[#416].\n* Upgrade to Mockito 2.11.0.\n\n== 1.5.1 (2017-10-16)\n\n* Fix bug with segment filename generation for the Archive. https://github.com/aeron-io/aeron/issues/414[#414].\n* Further validation of publication parameters to the C media driver.\n* Duplicate ByteBuffers for terms in client so independent subscriptions can operate in a threadsafe manner if they require access to the underlying ByteBuffers for onwards IO.\n* Remove `aeron.term.buffer.max.length` property as it is not needed given current usage.\n* Fix socket options and bind address for multicast in C driver.\n* Upgrade to SBE 1.7.3.\n* Upgrade to Agrona 0.9.9.\n\n== 1.5.0 (2017-10-11)\n\n* Separate `sendmmsg` and `recvmmsg` detection in C driver to support older kernels. Issue https://github.com/aeron-io/aeron/issues/412[#412].\n* Better support the cycling of connections so they consume less resources when lingering.\n* Provide more immediate detection of an unconnected IPC stream.\n* Allow spy subscriptions to simulate a network connection so a network publication can progress when no receivers are present. Issue https://github.com/aeron-io/aeron/issues/393[#393].\n* Add configuration for page size so the files can be on storage using huge pages to reduce TLB misses. Issue https://github.com/aeron-io/aeron/issues/387[#387]\n* Limit maximum message length to be 16MB to encourage better design and streaming in chunks.\n* Change concurrent publication algorithm to better handle the case of thread starvation with publishers and allow other publishers to help rotate a log if another publication is interrupted, or dies, while it is rotating the log. Issue https://github.com/aeron-io/aeron/issues/377[#377].\n* Add `Subscription.isConnected()`.\n* Add event logging for events which result in an exception.\n* Add the ability to set JVM_OPTS from an environment variable in command line scripts.\n* Pass through command line arguments and system properties in sample scripts.\n* Allow for mapped buffers to be ref counted and shared across publications and subscriptions within a client. Issue https://github.com/aeron-io/aeron/issues/365[#365].\n* Add vectored IO for publishing a number of buffers which make up a message as a single operation. Issues https://github.com/aeron-io/aeron/pull/401[#401], https://github.com/aeron-io/aeron/issues/394[#394].\n* Reduce copying, interface scanning, and object allocation for channel setup in Java.\n* Upgrade to SBE 1.7.2.\n* Upgrade to Agrona 0.9.8.\n* Upgrade to Checkstyle 8.3.\n* Upgrade to Mockito 2.10.0.\n* Upgrade to Byte Buddy 1.7.4.\n\n== 1.4.1 (2017-08-25)\n\n* Update the aeron-archive module to Maven Central as part of the build.\n* Improve C driver build when OS features are not available so it degrades more gracefully.\n* Tidy up of media driver configuration and added Javadoc. Note that if you used `Context.dirsDeleteOnStart(boolean)` for testing that it has been renamed to `Context.dirDeleteOnStart(boolean)`.\n* Add configuration option so that high resolution timers can be enabled on Windows.\n* Fix issue with term buffer length of 1GB. https://github.com/aeron-io/aeron/issues/388[Issue #388].\n\n== 1.4.0 (2017-08-21)\n\n* Native media driver written in C for greater throughput on Linux. Get started https://github.com/aeron-io/aeron/wiki/C-Media-Driver-Operation[here…].\n* Archive service in Java to support the recording and replay of message streams. Get started https://github.com/aeron-io/aeron/tree/master/aeron-samples/scripts/archive[here…].\n* Improved `AeronStat` for running in a Windows console and added `delay` param for controlling the update cycle in seconds.\n* Handle race condition on driver start when checking if it is active yet. https://github.com/aeron-io/aeron/issues/385[Issue #385].\n* Update `FlowControl` API to support a `shouldLinger` method and EOS parameters to allow for faster clean up of resources.\n* Encapsulate buffer and length into a struct for atomic update of images under a subscription in C{plus}{plus}. https://github.com/aeron-io/aeron/issues/383[Issue #383].\n* Add `Subscription::controlPoll` to C{plus}{plus} client.\n* C{plus}{plus} client was not going round robin on the images under a subscription. https://github.com/aeron-io/aeron/issues/381[Issue #381].\n* Added `AgentInvoker` to C{plus}{plus} client for removing the need for the client conductor thread.\n* Fix issues with publications being unblocked when clients are closing and creating new subscriptions. https://github.com/aeron-io/aeron/issues/377[Issue #377].\n* Add `Publication.MAX_POSITION_EXCEEDED` return code from offer for when `termId` can wrap due to using small term buffer lengths.\n* Add `shared_ptr` for LogBuffer saved in `Publication` and `ExclusivePublication` to keep mapping around while in scope. https://github.com/aeron-io/aeron/issues/371[Issue #371].\n* Rework Java client to better support timeouts and closing from other threads to help avoid segfaults. https://github.com/aeron-io/aeron/issues/371[Issue #371].\n* Publications store `registrationId` and `correlationId` with the ability to call `Publication.isOriginal()` to determine if this is the first publication added for a given channel and streamId on a driver.\n* Move access to the `AgentInvoker` from `Aeron.Context` to `Aeron` on the client and `MediaDriver.Context` to `MediaDriver`.\n* Fix alignment types in native buffers for supporting processors which require alignment. https://github.com/aeron-io/aeron/issues/359[Issue #359].\n* Counters now have labels in US_ASCII rather than UTF-8 to be more efficient as they are restricted to US_ASCII anyway.\n* `Image.joiningPosition()` has been renamed to `Image.joinPosition()`.\n* `ByteBuffer`s in `Subscription` and `Image` polling callbacks now have offsets the same as termBuffer offsets so the `ByteBuffer` can be used directly.\n* Stricter validation of channel URI parameters and new classes for client usage in `ChannelUri` and `ChannelUriStringBuilder`.\n* Reduce object allocations when cycling streams in Java media driver.\n* Add `Image.isEndOfStream()` for fast detection of stream end.\n* Add direct `ByteBuffer` support to `FragmentAssembler`s to avoid the need for copying of received messages before sending on the an NIO channel.\n* Upgrade to Agrona 0.9.7.\n* Upgrade to SBE 1.7.1.\n* Upgrade to Mockito 2.8.47.\n* Upgrade to Byte Buddy 1.6.14.\n* Upgrade to Checkstyle 8.1.\n* Upgrade to sevntu-checks:1.24.1.\n* Upgrade to Gradle 4.1.\n\n== 1.3.0 (2017-05-14)\n\n* Fixed issue with a Subscription being closed and rapidly reopened on the same streamId and channel.\n* Add `ThreadingMode.INVOKER` to the driver so no threads are started and the media driver can be invoked by the client for its duty cycle. Example in the Archiver.\n* Added system property that allows TCP_MODE to be enabled on CUBIC congestion control.\n* Throughput and latency improvements on the receiver side when interacting with NIO `DatagramChannel`s.\n* Add `Header.context()` member which contains the `Image` for use in subscriber callbacks.\n* Allow for client thread and lock to be elided when used in a low resource environment where the client will invoke the client duty cycle. Example can be found in the Archiver.\n* Notify that a new Image is available before it is added to the `Subscription` so setup actions can be taken.\n* Fix `Ping` samples for messages larger than `MTU`.\n* Numerous improvements to the performance and GC overhead of creating and releasing connections.\n* Add `Image.joiningPosition()`.\n* Avoid use of `std::function` for performance in C{plus}{plus}.\n* Add `ExclusivePublication` support to C{plus}{plus}.\n* Close file descriptor after mapping an existing file in C{plus}{plus}. Issue https://github.com/aeron-io/aeron/issues/339[#339]\n* Upgrade to Checkstyle 7.7.\n* Upgrade to Agrona 0.9.6.\n\n== 1.2.5 (2017-04-12)\n\n* Make `ExclusiveBufferClaim.flags()` consistent with `Header.flags()`.\n* Move responsibility for closing channel endpoints on shutdown from the driver-conductor to sender and receiver as appropriate.\n* Don't wait for Receiver to close a channel endpoint within the driver conductor. Issue https://github.com/aeron-io/aeron/issues/338[#338].\n* Remove use of `RandomAccessFile` so that only `FileChannel`s are used to reference files.\n* Improve efficiency of startup.\n* Upgrade to Agrona 0.9.5.\n* Upgrade to Mockito 2.7.22.\n* Upgrade to Gradle 3.5.\n\n== 1.2.4 (2017-04-06)\n\n* Update CnC file to accommodate counters with labels up to 380 bytes. This bumps the CNC_VERSION so all clients and the driver on the same machine need to be upgraded at the same time.\n* Truncate log buffers when a stream is closed so space can be reclaimed as soon as possible.\n* Add `Image.controlledPeek` to peek ahead into a stream without advancing the subscriber position.\n* Improve the efficiency of Aeron startup and shutdown.\n* Back pressure a Publication as soon as its timeout is triggered due to not receiving status messages so that low throughput connections signal NOT_CONNECTED sooner. Also applies to IPC when all Subscriptions are closed.\n* Add per `Subscription` callbacks for notification of `Image` availability and unavailability.\n* Make media driver conductor more efficient so it can deal with a higher number of connected streams.\n* Add the ability to create an `ExclusivePublication` with initial state set so it can replay existing archived data.\n* Add `ExclusivePublication` for higher throughput single threaded publisher.\n* Allow the Aeron client to start before the media driver when an existing CnC file is present.\n* Clean up `Aeron.Context` if an error occurs when trying to establish a connection with the media driver.\n* Add `Aeron.nextCorrelationId()`.\n* Avoid a recursive close in the Aeron Java client when a timeout occurs.\n* Upgrade to sevntu-checks 1.23.1.\n* Upgrade to Checkstyle 7.6.1.\n* Upgrade to Byte Buddy 1.6.12.\n* Upgrade to Mockito 2.7.21.\n* Upgrade to Agrona 0.9.4.\n\n== 1.2.3 (2017-03-02)\n\n* Fix bug with managing the lifetime of `Subscription`s to IPC `Publication`s when a publication is added and removed multiple times in quick succession without break on the same stream id.\n* Improvements to C{plus}{plus} client for building on Windows.\n\n== 1.2.2 (2017-03-01)\n\n* Fix bug with IPC `Subscription`s being linked multiple times when `Publication` added from different clients that match.\n* Transition IPC `Publication`s to inactive as soon as the Publication reference count reaches zero.\n\n== 1.2.1 (2017-02-28)\n\n* Fix bug with IPC `Publication`s going `NOT_CONNECTED` with very slow subscribers that are still connected.\n* Grow fragment assembly buffer using the golden ratio rather than doubling on a `Subscription`.\n* Improve Javadoc for Multi-Destination-Cast params.\n* Upgrade to SBE 1.6.0.\n\n== 1.2.0 (2017-02-22)\n\n* https://github.com/aeron-io/Aeron/wiki/Channel-Configuration[Multi-Destination-Cast] for multicast like semantics over multiple UDP unicast streams.\n* Linger networked `Publication`s that have spies until the spies have caught up with the sender position.\n* Manage `Image` availability and `Subscription`s for IPC in the same way as for network streams.\n* Performance improvements from fixing a few false sharing issues.\n* Fix network frame logging with debugging agent.\n* Fix intermittent system tests on Windows.\n* Make the Java client more robust to misuse.\n* Windows C{plus}{plus} compilation fixes.\n* Be more strict with validating client parameters before mutating state.\n* Ability to set the MTU length on individual UDP streams.\n* Upgrade to Checkstyle 7.5.1.\n* Upgrade to Byte Buddy 1.6.9.\n* Upgrade to Mockito 2.7.9.\n* Upgrade to Agrona 0.9.3.\n\n== 1.1.0 (2017-01-23)\n\n* Numerous small performance improvements.\n* Fix bug with publications going NOT_CONNECTED because nano time was used rather than the epoch clock.\n* Ability to subscribe to a stream as `reliable=false` which enables gaps being filled without invoking the https://github.com/aeron-io/Aeron/wiki/Message-Delivery-Assurances**[reliable recovery]** process.\n* https://github.com/aeron-io/Aeron/wiki/Monitoring-and-Debugging#loss-reporting**[Loss Reporting]** by stream for monitoring.\n* OSI layer 4 https://github.com/aeron-io/aeron/wiki/Flow-and-Congestion-Control**[congestion control/avoidance]** service which can optionally be enabled on congested networks.\n* Support for running 32-bit C{plus}{plus} clients on ARM.\n* Rework of loss detection to make it more efficient.\n* Ability to measure RTT added to the network protocol.\n* Upgrade to Mockito 2.6.8.\n* Upgrade to Checkstyle 7.4.\n* Upgrade to sevntu-checks 1.23.0.\n* Upgrade to ByteBuddy 1.6.5.\n* Upgrade to Agrona 0.9.2.\n* Upgrade to Gradle 3.3.\n\n== 1.0.5 (2016-12-20)\n\n* Use `BusySpinIdleStrategy` for the conductor in the `LowLatencyMediaDriver` to reduce some latency outliers at the expense of more CPU usage.\n* Fix some cases of false sharing on key data structures.\n* Remove OSGi plugin from aeron-agent to fix issue that prevented debug logging due to premain not in manifest.\n* Allow driver and examples to be shutdown with SIGTERM as well as SIGINT.\n* New flow control strategies for minimum and preferred receivers on multicast as additions to existing max strategy.\n* Add receiver ID to status messages to enable additional flow control strategies such as preferred receiver.\n* Remove deprecated channel URI format support.\n* Added warm up cycle to C{plus}{plus} ping pong example.\n* Apply `const` where possible in C{plus}{plus} client code.\n* Fix checks against max message and max payload length in C{plus}{plus} publication claim.\n* Upgrade to Mockito 2.2.29.\n* Upgrade to Checkstyle 7.3.\n* Upgrade to sevntu-checks 1.22.0.\n* Upgrade to ByteBuddy 1.5.9.\n* Upgrade to Agrona 0.9.1.\n* Upgrade to Gradle 3.2.1.\n\n== 1.0.4 (2016-11-17)\n\n* Detect Aeron Client use after close operations and improve locking strategy.\n* Park the client conductor when waiting on the driver response.\n* Fix concurrency issues in system tests.\n* Added OSGi Gradle plugin.\n* Upgrade to Mockito 2.2.16.\n* Upgrade to sevntu-checks 1.21.1.\n* Upgrade to ByteBuddy 1.5.5.\n* Upgrade to Agrona 0.9.0.\n* Upgrade to Gradle 3.2.\n\n== 1.0.3 (2016-11-03)\n\n* Linger client resources before informing driver to remove reference to avoid race when client is suffering resource starvation.\n* Sleep for less than a millisecond between polls for completed admin actions to make setup more responsive.\n* Remove subscription spies before cleaning up `NetworkPublication`s to avoid potential SIGEV.\n* Use project based file reference to allow for composable Gradle builds.\n* Increase default low file space warning to 10 times term length.\n* Upgrade to Mockito 2.2.9.\n* Upgrade to Checkstyle 7.2.\n* Upgrade to ByteBuddy 1.5.0.\n* Upgrade to Agrona 0.5.6.\n\n== 1.0.2 (2016-10-15)\n\n* Bugfix for double free of counters that resulted in issues https://github.com/aeron-io/aeron/issues/269[#269] and https://github.com/aeron-io/aeron/issues/281[#281].\n* Enable high resolution timer on Windows for more regular polling.\n* Add sender limit as a counter for extra telemetry.\n* Avoid false sharing on `Sender` and `Subscription` round robin counters.\n* Make rebuilding of `PublicationImage`s a pure monotonic function to avoid unlikely but possible regression in state.\n* Validate flow control window before accepting a heartbeat message.\n* Poll for control messages as a ratio to data packets sent, default to 4, to allow for scaling to larger numbers of connections.\n* Allow multi-digit version numbering with C{plus}{plus} build.\n* Inline logging code when activated rather than delegating for better performance.\n* Low file space warnings and error checks for sufficient space in volume holding log files.\n* Update to ByteBuddy 1.4.32.\n* Update to Gradle 3.1.\n* Update to Agrona 0.5.5.\n\n== 1.0.1 (2016-08-24)\n\n* Adjust incremental cleaning to avoid overwrite of cleaned log in loss scenarios to address Issue #271.\n* Correctly initialise cleaning position for late joining a stream. Issue #268.\n* Make `clientLivenessTimeout` configurable from `MediaDriver.Context`.\n* Shadow samples into aeron-all JAR.\n* Support creating Agent threads via `ThreadFactory` to enable pinning.\n* Update to Agrona 0.5.4.\n* Update to ByteBuddy 1.4.20.\n\n== 1.0.0 (2016-07-22)\n\n* Expose maxPayloadLength Publications.\n* Added setting clientId to remove messages from clients.\n* Reduce the number of data dependent loads and indirections for reduced cache missing and memory footprint.\n* Simplified Java version of DriverProxy.\n* Driver is more conservative about sending available images for new Subscriptions with images in LINGER.\n* NAKs must be within a single term length of the sender to cause a retransmission.\n* Incremental cleaning of log buffers to smooth out pauses by the driver conductor.\n* Java client API will yield the current thread when spinning waiting for response from the driver.\n* Java and C{plus}{plus} APIs will attempt to make sure driver is completed startup before using CnC file.\n* Per term metadata moved into the log metadata section of the logbuffer.\n* C{plus}{plus} API term appending now safer for unblock operation.\n* Simplification and reorg of Javadoc and Configuration.\n* Update to Agrona 0.5.3\n* Update to Checkstyle 7.0.\n* Update to ByteBuddy 1.4.9.\n\n== 0.9.9 (2016-06-28)\n\n* Numerous small improvements for performance and consistent latency.\n* Default the rebuilt images to be read-only when memory-mapped in the Java client.\n* Move signalling of term buffers for cleaning from the client to the driver. This reduces latency on rotation.\n* Enable the `LogInspector` to scan over gaps in the term buffers.\n* Constrain in-flight windows that have been miss-configured to fit within term buffers.\n* Counters for receiver rebuild position and joining position on streams for subscribers.\n* Reduce memory footprint and processing for NAKs.\n* Fix for stream sticking when loss occurred on the last frame in a term. https://github.com/aeron-io/aeron/issues/245[Issue #245]\n* Removed the need for `aeron.rcv.buffer.length` as a configuration option.\n* The ability to set the reserved value field in the header when calling a overloaded `Publication.offer()` taking a function that returns the value. Useful for applying a checksum or timestamp.\n* The ability to spy on an outgoing network stream as an IPC Subscription. Useful for archiving. https://github.com/aeron-io/aeron/issues/230[Issue #230]\n* Warn when old style channel URIs are used and update all samples to use the new format.\n* Ability to set term length as a URI param to the channel when adding a stream. This overrides the defaults for `aeron.term.buffer.length` and `aeron.ipc.term.buffer.length`. https://github.com/aeron-io/aeron/issues/193[Issue #193]\n* Target minimum of CMAKE 3.0.2 for c++.\n* Validate MTU lengths are correct multiples and within range.\n* Change `Image.filePoll()` to `Image.rawPoll()` and allow access to the underlying `UnsafeBuffer`. This is useful for adding things like a checksum to header reserved value field.\n* Update to Agrona 0.5.2.\n* Update to HdrHistogram 2.1.9.\n* Update to ByteBuddy 1.4.5.\n* Update to Checkstyle 6.19.\n* Update to Gradle 2.14.\n\n== 0.9.8 (2016-05-23)\n\n* Change samples to use aligned buffers for better copy performance.\n* Ability to check the status of a channel to see socket connect status. This can be seen in the counters with `AeronStat`.\n* Change low-latency samples to not use sparse files to avoid page faults.\n* Move channel endpoint dispatch logic to the pollers for simplicity. This is breaking for existing channel endpoint extensions that overrode dispatch.\n* Use only one copy of send buffers and flyweights for control messages from `NetworkPublication`s to allow for better scalability of streams.\n* Pull out receive buffers and flyweights from channels into `TransportPoller`s so only one instance of each is used regardless of how many channels are used.\n* Use aligned `ByteBuffer`s for receiving data and control messages.\n* Add Bash and Windows batch file scripts for running samples.\n* Update to ByteBuddy 1.3.19.\n* Update to Agrona 0.5.1.\n* Update to Checkstyle 6.18.\n* Update to Gradle 2.13.\n\n== 0.9.7 (2016-04-25)\n\n* Change `Header.position()` in Subscription callback to include alignment in calculated resulting position.\n* Added `Subscription.forEachImage()` to allow allocation free iteration over active Images on a Subscription.\n\n== 0.9.6 (2016-04-22)\n\n* Frame alignment changed to 32 bytes. Improving Java IPC byte throughput. (Not backwards compatible).\n* Frame header increased to 32 bytes. Allowing inclusion of a 64-bit user supplied value when sending. (Not backwards compatible).\n* Simplified network publication cleanup.\n* Update to Agrona 0.5.\n* Update to Checkstyle 6.17\n\n== 0.9.5 (2016-03-24)\n\n* Migrate package layout from `uk.co.real_logic.aeron` -&gt; `io.aeron` (Breaking for existing code).\n* Allow flow control flexibility to vary by distinct streams.\n* Change event logging to be Java agent based using byte code weaving.\n* Ability to globally set TTL for multicast by system property or channel specific by uri param.\n* Expand `AeronStat` to allow filtering based on counters metadata.\n* Update to Agrona 0.4.13.\n* Update to Checkstyle 6.16.1.\n* Update to Gradle 2.12.\n\n== 0.9.4 (2016-03-03)\n\n* Bug fix for NPE due to errorLog not being setup correctly.\n* Save errors from existing CnC file on `MediaDriver` restart.\n* Extract loss generation to be cleanly separated for use in debug channel endpoints.\n\n== 0.9.3 (2016-03-01)\n\n* Fix bug with negative sessionIds that can be generated with higher numbers of streams.\n* A number of changes the enable faster creation of Publications and Subscriptions to support 100s of streams.\n* Add new DistinctErrorLog to CnC buffer so errors don't go to the event log. This error log can be read with ErrorStat.\n* Remove the need for the template file allowing for faster driver startup and save space in /dev/shm.\n* Add `Publication.initialTermId()` accessor.\n* Call `onUnavailableImage()` for all Images contained in a Subscription when it is closed.\n* Support new channel syntax for endpoints.\n* Added the ability to load multiple configuration files and merge them.\n* Allow the creation of term buffers in a log to be sparse for faster connection setup.\n* Add `Publication.isConnected()` method to check if a publication is currently connected to a subscriber.\n* Return `NOT_CONNECTED` from Publication when offer fails and no recent status messages have been seen.\n* Linger client Java Publications to help avoid segfaults when used after being closed.\n* Update to Agrona 0.4.12.\n* Update to Checkstyle 6.15.\n* Update to Gradle 2.11.\n\n== 0.9.2 (2016-02-06)\n\n* Add tracking of last status message (SM) to publications for knowing if they are no longer connected to a receiver.\n* Removal of unused configuration parameters.\n* Fix event reader output for unavailable image reporting.\n* Fix control protocol flyweights to use native rather than little endian byte ordering.\n* Configurable supplier support for channel endpoints for introducing channel specific behaviour such as debugging, monitoring, loss introduction, etc.\n* Update to Agrona 0.4.11.\n\n== 0.9.1 (2016-01-27)\n\n* Iterate SelectedKeys taking account of size when transport count greater than `ITERATION_THRESHOLD`.\n* Don't keep attempting to log an exception when event buffer is full.\n* Return `ADMIN_ACTION` rather than `BACK_PRESSURED` from publication offer when another thread is rotating the log so that the publisher knows to retry.\n\n== 0.9 (2016-01-18)\n\n* Added Checkstyle for indentation and fixed violations.\n* Align C{plus}{plus} and Java on feature set for client API.\n* Rework flyweights to use less memory and avoid one layer of indirection.\n* Simplify the event logging code to be more efficient.\n* Modified network publication to always send an initial SETUP upon first send() duty cycle. Adding tracking of last sent SETUP to effectively handle SM with SETUP implosion. Fixed sending of SETUP when triggered by SM with SETUP flag when position has not been advanced at all yet.\n* Correct bug with incorrect minimum on producer position for unblocking a dead publication.\n* Reduce memory footprint on quite a few data structures and reduced code path for setting up connections to speed startup time.\n* Update to Agrona 0.4.10.\n\n== 0.2.3 (2016-01-06)\n\n* Improved unblocking code to deal with more cases of clients dying.\n* Reworked publication algorithm to be more robust on machines with limited CPU resource.\n* Reduced the potential for cache missing when publication logs rotate.\n* Updated IdleStrategies.\n* Added ControlledFragmentHandler to control the polling progress and position reporting from within the handler.\n* Simplified API for Image availability notification.\n* Added position tracking accessors to the Publication.\n* Improved error messages for insufficient buffer sizes related to sockets.\n* Minor performance improvements for consistency.\n* Update to Gradle 2.10.\n* Update to Checkstyle 2.14.1.\n* Update to HdrHistogam 2.1.8.\n* Update to Agrona 0.4.9.\n\n== 0.2.2 (2015-11-26)\n\n* Add ADMIN_ACTION as return from publication.\n* Migrate client liveness timeout to CNC file.\n* Bugfix for fragmentLimit on Subscription.poll().\n* Ignore CloseChannelException on Publication.\n* Small performance improvement to publication.\n* Update to Agrona 0.4.8.\n* Update to Gradle 2.9.\n\n== 0.2.1 (2015-11-13)\n\n* Bugfix to RingBuffer for sending client commands.\n* Update to Agrona 0.4.7.\n\n== 0.2 (2015-11-03)\n\n* Ability to detect and fix a blocked Publication or command ring buffer.\n* Configuration via a properties file that is in classpath, local directory, or on a webserver.\n* Meta data available on Publications and Images for term length, source identity, stream details.\n* Detection of closed Images on poll.\n* Ability to abort a BufferClaim.\n* Update to Gradle 2.8, Agrona 0.4.6 and Checkstyle 6.11.2.\n\n== 0.1.5 (2015-10-14)\n\n* Latency and throughput improvements in the Publication path for both networked and IPC comms.\n* Reduced workload on the conductor thread to better support larger number of streams or when running with shared mode for agents when threads need to be conserved.\n* Apply Eclipse plugin to build files.\n* Update to Agrona 0.4.5\n\n== 0.1.4 (2015-09-25)\n\n* IPC transport for high throughput and low-latency messaging inter thread and process.\n* Added configuration support via a properties file.\n* Added IPC samples.\n* Improved latency and throughput for `Publication`s.\n* Improved performance for receiving status and NAK messages.\n* Update Gradle to 2.7, Agrona to 0.4.4, HdrHistogram to 2.1.7\n\n== 0.1.3 (2015-08-26)\n\n* Experimentation IPC transport via shared memory.\n* Major bugfix for handling late joining publications in other Aeron clients on the same driver.\n* Added the ability to configure client keep-alives.\n* Rename client callbacks and provide access to underlying images.\n* Update to Agrona 0.4.3.\n\n== 0.1.2 (2015-07-27)\n\n* Rename connections to images.\n* Add NOT_CONNECTED semantics.\n* Close all publications and subscriptions with an Aeron client when it is closed.\n* Update to Agrona 0.4.2.\n\n== 0.1.1 (2015-07-20)\n\n* Fix bug with FileChannel being closed on Image which preventing pollFile()\n* Migrated assignment of publication session ids from client to the driver to easy the sharing of publications across processes.\n* Update Gradle to 2.5, Checkstyle to 6.8.1, and Agrona to 0.4.1.\n\n== 0.1 (2015-07-09)\n\n* Initial Beta Release\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\ncmake_minimum_required(VERSION 3.30 FATAL_ERROR)\ncmake_policy(VERSION 3.30)\ncmake_policy(SET CMP0003 NEW)\ncmake_policy(SET CMP0135 NEW)\n\nif (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})\n    message(FATAL_ERROR \"In-source builds not allowed. Please make a new directory (called a build directory)\"\n        \"and run CMake from there. You may need to remove CMakeCache.txt.\")\nendif ()\n\nstring(TIMESTAMP DSTAMP \"%Y%m%d\")\n\ninclude(FetchContent)\n\nfile(STRINGS version.txt AERON_VERSION_TXT LIMIT_COUNT 1 REGEX \"^[0-9]+(\\\\.[0-9]+)+\")\nstring(REGEX REPLACE \"^([0-9]+(\\\\.[0-9]+)+).*$\" \"\\\\1\" AERON_VERSION_FROM_FILE \"${AERON_VERSION_TXT}\")\n\nif (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_BINARY_DIR})\n    message(FATAL_ERROR \"In-source builds not allowed. Please make a new directory (called a build directory)\"\n        \"and run CMake from there. You may need to remove CMakeCache.txt.\")\nendif ()\n\nif (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})\n    set(STANDALONE_BUILD TRUE)\nendif ()\n\noption(BUILD_AERON_DRIVER \"Build Aeron driver\" ON)\noption(BUILD_AERON_ARCHIVE_API \"Build Aeron Archive API\" ON)\n\noption(C_WARNINGS_AS_ERRORS \"Enable warnings as errors for C\" OFF)\noption(CXX_WARNINGS_AS_ERRORS \"Enable warnings as errors for C++\" OFF)\noption(SANITISE_BUILD \"Enable sanitise options\" OFF)\noption(COVERAGE_BUILD \"Enable code coverage\" OFF)\noption(AERON_HIDE_DEPRECATION_MESSAGE \"Hide C++ deprecation message\" OFF)\noption(AERON_TESTS \"Enable tests\" ${STANDALONE_BUILD})\noption(AERON_UNIT_TESTS \"Enable unit tests\" ${STANDALONE_BUILD})\noption(AERON_SYSTEM_TESTS \"Enable system tests\" ${STANDALONE_BUILD})\noption(AERON_SLOW_SYSTEM_TESTS \"Enable slow system tests\" OFF)\noption(AERON_BUILD_SAMPLES \"Enable building the sample projects\" ${STANDALONE_BUILD})\noption(LINK_SAMPLES_CLIENT_SHARED \"Enable shared linking for sample projects\" OFF)\noption(AERON_BUILD_DOCUMENTATION \"Build Aeron documentation\" ${STANDALONE_BUILD})\noption(AERON_INSTALL_TARGETS \"Enable installation step\" ${STANDALONE_BUILD})\n\nunset(STANDALONE_BUILD)\n\n# CMAKE_OSX_ARCHITECTURES can be set to arm64 or x86_64 (or both) to make a specific architecture version. Requires\n# CMake 3.21. Must be set before project(), etc.\n#set(CMAKE_OSX_ARCHITECTURES x86_64)\n\nproject(\"aeron\" VERSION \"${AERON_VERSION_FROM_FILE}\")\n\nif (AERON_TESTS)\n    enable_testing()\n    include(CTest)\nendif ()\n\n# default built type is Release\nif (NOT CMAKE_BUILD_TYPE)\n    set(CMAKE_BUILD_TYPE \"Release\" CACHE STRING \"Choose the type of build\" FORCE)\nendif (NOT CMAKE_BUILD_TYPE)\n\n##########################################################\n# Platform flags, etc.\n\nfind_package(Threads)\n\n##########################################################\n# Doxygen for generating doc\n\nif (AERON_BUILD_DOCUMENTATION)\n    find_package(Doxygen)\nendif ()\n\nif (NOT DEFINED CMAKE_CXX_STANDARD)\n    set(CMAKE_CXX_STANDARD 11)\nendif ()\n\nif (NOT DEFINED CMAKE_CXX_EXTENSIONS)\n    set(CMAKE_CXX_EXTENSIONS OFF)\nendif ()\n\nif (NOT DEFINED CMAKE_C_STANDARD)\n    set(CMAKE_C_STANDARD 11)\nendif ()\n\nif (NOT DEFINED CMAKE_C_EXTENSIONS)\n    set(CMAKE_C_EXTENSIONS OFF)\nendif ()\n\nexecute_process(\n    COMMAND git log -1 --format=%h --abbrev=10\n    WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}\n    OUTPUT_VARIABLE AERON_VERSION_GITSHA\n    OUTPUT_STRIP_TRAILING_WHITESPACE\n)\n\nexecute_process(\n    COMMAND git diff-index --quiet HEAD --\n    WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}\n    RESULT_VARIABLE AERON_GIT_DIFF_INDEX_RESULT\n)\n\nif (NOT AERON_GIT_DIFF_INDEX_RESULT EQUAL 0)\n    set(AERON_VERSION_GITSHA \"${AERON_VERSION_GITSHA}+guilty\")\nendif ()\n\nadd_definitions(-DAERON_VERSION_TXT=\"${AERON_VERSION_TXT}\")\nadd_definitions(-DAERON_VERSION_MAJOR=${aeron_VERSION_MAJOR})\nadd_definitions(-DAERON_VERSION_MINOR=${aeron_VERSION_MINOR})\nadd_definitions(-DAERON_VERSION_PATCH=${aeron_VERSION_PATCH})\nadd_definitions(-DAERON_VERSION_GITSHA=\"${AERON_VERSION_GITSHA}\")\n\nif (AERON_HIDE_DEPRECATION_MESSAGE)\n    add_compile_definitions(\"AERON_HIDE_DEPRECATION_MESSAGE\")\nendif (AERON_HIDE_DEPRECATION_MESSAGE)\n\n# all UNIX-based platform compiler flags\nif (UNIX)\n    add_compile_options(-fstrict-aliasing -Wall -Wpedantic -Wextra -Wno-unused-parameter -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer)\n\n    if (CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER \"11.0\")\n        add_compile_options(-Wno-error=maybe-uninitialized)\n    elseif (CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER \"21.0\")\n        # See gtest: https://github.com/google/googletest/issues/4762\n        add_compile_options(-Wno-error=character-conversion)\n    endif ()\n\n    if (C_WARNINGS_AS_ERRORS)\n        add_compile_options($<$<COMPILE_LANGUAGE:C>:-Werror>)\n    endif (C_WARNINGS_AS_ERRORS)\n\n    if (CXX_WARNINGS_AS_ERRORS)\n        add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Werror>)\n    endif (CXX_WARNINGS_AS_ERRORS)\n\n    if (SANITISE_BUILD)\n        if (NOT DEFINED APPLE)\n            set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=leak -fsanitize=undefined -DAERON_SANITIZE_ENABLED\")\n            set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=leak -fsanitize=undefined -DAERON_SANITIZE_ENABLED\")\n        endif ()\n    endif (SANITISE_BUILD)\n\n    if (COVERAGE_BUILD)\n        add_compile_options(-fno-inline --coverage)\n        SET(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} --coverage\")\n    endif (COVERAGE_BUILD)\nendif ()\n\n# platform specific flags\nif (APPLE)\n    add_compile_options(-Wsign-compare)\n    add_compile_options(-Wno-deprecated-register)\n\n    if (SANITISE_BUILD)\n        set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined -DAERON_SANITIZE_ENABLED\")\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined -DAERON_SANITIZE_ENABLED\")\n    endif (SANITISE_BUILD)\nelseif (CYGWIN)\n    add_definitions(-DWIN32)\n    if (AERON_TESTS)\n        add_definitions(-DGTEST_HAS_PTHREAD)\n    endif (AERON_TESTS)\n    set(CMAKE_CXX_EXTENSIONS ON)\nelseif (MSVC)\n    add_definitions(-DWIN32)\n    add_definitions(-D_CRT_SECURE_NO_WARNINGS)\n    add_definitions(-D_CRT_NONSTDC_NO_WARNINGS)\n    add_definitions(-DNOMINMAX)\n\n    if (${MSVC_VERSION} GREATER_EQUAL 1915)\n        # Acknowledge that we understand MSVC resolved a byte alignment issue in this compiler\n        add_definitions(-D_ENABLE_EXTENDED_ALIGNED_STORAGE)\n    endif ()\n\n    add_compile_options(/Oy-)\n    add_compile_options($<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>:/MD>)\n    add_compile_options($<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>:/MDd>)\n    add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/MP>)\n    add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/wd4251>)\n    add_compile_options($<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>:/MD>)\n    add_compile_options($<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>:/MDd>)\n    add_compile_options($<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>:/Od>)\n    add_compile_options($<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>:/Zi>)\n\n    if (C_WARNINGS_AS_ERRORS)\n        add_compile_options($<$<COMPILE_LANGUAGE:C>:/WX>)\n    endif (C_WARNINGS_AS_ERRORS)\n\n    if (CXX_WARNINGS_AS_ERRORS)\n        add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/WX>)\n    endif (CXX_WARNINGS_AS_ERRORS)\n\n    if (SANITISE_BUILD)\n        set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} /fsanitize=address /Zi /D AERON_SANITIZE_ENABLED\")\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /fsanitize=address /Zi /D AERON_SANITIZE_ENABLED\")\n    endif (SANITISE_BUILD)\n\nendif ()\n\nif (DEFINED AERON_COMPILER_OPTIMIZATION_LEVEL)\n    if (MSVC)\n        add_compile_options(/O${AERON_COMPILER_OPTIMIZATION_LEVEL})\n    else ()\n        add_compile_options(-O${AERON_COMPILER_OPTIMIZATION_LEVEL})\n    endif ()\nendif()\n\n##########################################################\n# Project variables, etc.\n\nif (NOT DEFINED GRADLE_WRAPPER)\n    if (MSVC)\n        set(GRADLE_WRAPPER \"gradlew.bat\" CACHE INTERNAL \"Location of the Gradle wrapper script\")\n    else ()\n        set(GRADLE_WRAPPER \"./gradlew\" CACHE INTERNAL \"Location of the Gradle wrapper script\")\n    endif ()\nendif ()\n\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}/binaries\")\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}/lib\")\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}/lib\")\n\nif (AERON_BUILD_SAMPLES)\n    set(AERON_SAMPLES_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-samples/src/main/cpp\")\n    set(AERON_C_SAMPLES_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-samples/src/main/c\")\nendif ()\n\n# client source\nset(AERON_CLIENT_WRAPPER_SOURCE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-client/src/main/cpp_wrapper\")\nset(AERON_C_CLIENT_SOURCE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-client/src/main/c\")\n\n# driver source\nset(AERON_DRIVER_SOURCE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-driver/src/main/c\")\n\n# archive source\nset(AERON_ARCHIVE_WRAPPER_SOURCE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-archive/src/main/cpp_wrapper\")\nset(AERON_C_ARCHIVE_SOURCE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-archive/src/main/c\")\n\nif (AERON_TESTS)\n    # client tests\n    set(AERON_CLIENT_WRAPPER_TEST_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-client/src/test/cpp_wrapper\")\n    set(AERON_C_CLIENT_TEST_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-client/src/test/c\")\n\n    # driver tests\n    set(AERON_DRIVER_TEST_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-driver/src/test/c\")\n\n    # archive tests\n    set(AERON_ARCHIVE_WRAPPER_TEST_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-archive/src/test/cpp_wrapper\")\n    set(AERON_C_ARCHIVE_TEST_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-archive/src/test/c\")\n\n    set(AERON_SYSTEM_TEST_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-system-tests\")\nendif ()\n\nif (AERON_BUILD_SAMPLES)\n    # hdr_histogram\n    include_directories(${HDRHISTOGRAM_SOURCE_DIR}/include)\nendif ()\n\n##########################################################\n\nadd_definitions(-D_FILE_OFFSET_BITS=64)\n\n##########################################################\n# gmock usage\n\nif (AERON_TESTS)\n    set(INSTALL_GMOCK OFF CACHE INTERNAL \"Install Googletest's GMock?\")\n    set(INSTALL_GTEST OFF CACHE INTERNAL \"Install Googletest's GTest?\" )\n\n    FetchContent_Declare(\n        gmock\n        URL ${CMAKE_CURRENT_SOURCE_DIR}/cppbuild/googletest-1.14.0.zip\n        URL_MD5 b4911e882c51cba34bebfb5df500a650)\n\n    FetchContent_MakeAvailable(gmock)\nendif ()\n\n##########################################################\n# HdrHistogram usage - use MD5 as means to identify snapshot\n\nif (AERON_BUILD_SAMPLES)\n    set(HDR_LOG_REQUIRED \"OFF\" CACHE INTERNAL \"Set log required option\")\n    set(HDR_HISTOGRAM_BUILD_PROGRAMS \"OFF\" CACHE INTERNAL \"Set build programs option\")\n    FetchContent_Declare(\n        hdr_histogram\n        URL ${CMAKE_CURRENT_SOURCE_DIR}/cppbuild/HdrHistogram_c-0.11.9.zip\n        URL_MD5 dce4d27b89ea0f73cc74be505dc957e3)\n\n    FetchContent_MakeAvailable(hdr_histogram)\nendif ()\n\nadd_subdirectory(${AERON_CLIENT_WRAPPER_SOURCE_PATH})\nadd_subdirectory(${AERON_C_CLIENT_SOURCE_PATH})\n\nif (AERON_TESTS)\n    add_subdirectory(${AERON_CLIENT_WRAPPER_TEST_PATH})\n    add_subdirectory(${AERON_C_CLIENT_TEST_PATH})\nendif ()\nif (AERON_BUILD_SAMPLES)\n    add_subdirectory(${AERON_SAMPLES_PATH})\n    add_subdirectory(${AERON_C_SAMPLES_PATH})\nendif ()\n\nif (BUILD_AERON_DRIVER)\n    add_subdirectory(${AERON_DRIVER_SOURCE_PATH})\n    if (AERON_TESTS)\n        add_subdirectory(${AERON_DRIVER_TEST_PATH})\n        add_subdirectory(${AERON_SYSTEM_TEST_PATH})\n    endif ()\nendif (BUILD_AERON_DRIVER)\n\nif (BUILD_AERON_ARCHIVE_API)\n    set(ARCHIVE_CODEC_TARGET_DIR \"${CMAKE_CURRENT_BINARY_DIR}/generated\")\n    set(ARCHIVE_C_CODEC_TARGET_DIR \"${CMAKE_CURRENT_BINARY_DIR}/generated/c\")\n    set(ARCHIVE_CODEC_SCHEMA_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-archive/src/main/resources/archive\")\n    set(ARCHIVE_CODEC_WORKING_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\")\n\n    add_subdirectory(${AERON_ARCHIVE_WRAPPER_SOURCE_PATH})\n    add_subdirectory(${AERON_C_ARCHIVE_SOURCE_PATH})\n    set(AERON_ALL_JAR \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-all/build/libs/aeron-all-${AERON_VERSION_TXT}.jar\")\n    set(AERON_AGENT_JAR \"${CMAKE_CURRENT_SOURCE_DIR}/aeron-agent/build/libs/aeron-agent-${AERON_VERSION_TXT}.jar\")\n\n    file(GLOB_RECURSE AERON_ALL_SOURCES\n        \"${CMAKE_CURRENT_SOURCE_DIR}}/aeron-archive/src/main/java/*.java\")\n\n    add_custom_command(OUTPUT ${AERON_ALL_JAR}\n        COMMAND ${CMAKE_COMMAND}\n                -E env\n                JAVA_HOME=${JAVA_HOME}\n                BUILD_JAVA_HOME=${BUILD_JAVA_HOME}\n                BUILD_JAVA_VERSION=${BUILD_JAVA_VERSION}\n                ${GRADLE_WRAPPER}\n                :aeron-all:clean\n                :aeron-all:assemble\n                --exclude-task  javadoc\n                --exclude-task  check\n                --no-daemon\n                --console=plain\n                --quiet\n        DEPENDS ${AERON_ALL_SOURCES}\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n        COMMENT \"Generating aeron-all jar\"\n        VERBATIM)\n\n    add_custom_target(aeron-all-jar\n        DEPENDS ${AERON_ALL_JAR})\n\n    if (AERON_TESTS)\n        add_subdirectory(${AERON_ARCHIVE_WRAPPER_TEST_PATH})\n        add_subdirectory(${AERON_C_ARCHIVE_TEST_PATH})\n    endif ()\nendif (BUILD_AERON_ARCHIVE_API)\n##########################################################\n# doc target\n\nif (AERON_BUILD_DOCUMENTATION AND DOXYGEN_FOUND)\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cppbuild/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)\n\n    add_custom_target(\n        doc\n        ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile\n        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}\n        COMMENT \"Generating API documentation with Doxygen\" VERBATIM)\n\n    if (AERON_INSTALL_TARGETS)\n        # install the doc if it has been built\n        install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc DESTINATION share OPTIONAL)\n    endif ()\nendif ()\n\n##########################################################\n# export targets\n\nif (AERON_INSTALL_TARGETS)\n    include(CMakePackageConfigHelpers)\n\n    install(\n        EXPORT aeron-targets\n        FILE aeron-targets.cmake\n        NAMESPACE aeron::\n        DESTINATION lib/cmake/aeron)\n\n    write_basic_package_version_file(\n        \"${CMAKE_CURRENT_BINARY_DIR}/aeron-config-version.cmake\"\n        VERSION \"${AERON_VERSION_FROM_FILE}\"\n        COMPATIBILITY AnyNewerVersion)\n\n    configure_package_config_file(\n        ${CMAKE_CURRENT_SOURCE_DIR}/aeron-config.cmake.in\n        \"${CMAKE_CURRENT_BINARY_DIR}/aeron-config.cmake\"\n        INSTALL_DESTINATION lib/cmake/aeron)\n\n    install(FILES\n        \"${CMAKE_CURRENT_BINARY_DIR}/aeron-config.cmake\"\n        \"${CMAKE_CURRENT_BINARY_DIR}/aeron-config-version.cmake\"\n        DESTINATION lib/cmake/aeron)\n\n    export(EXPORT aeron-targets\n        FILE \"${CMAKE_CURRENT_BINARY_DIR}/aeron-targets.cmake\"\n        NAMESPACE aeron::)\nendif ()\n\n##########################################################\n# package target\n\nset(CPACK_RESOURCE_FILE_LICENSE \"${CMAKE_CURRENT_SOURCE_DIR}/LICENSE\")\nset(CPACK_PACKAGE_VERSION_MAJOR \"${aeron_VERSION_MAJOR}\")\nset(CPACK_PACKAGE_VERSION_MINOR \"${aeron_VERSION_MINOR}\")\nset(CPACK_PACKAGE_VERSION_PATCH \"${aeron_VERSION_PATCH}\")\n\nset(CPACK_GENERATOR \"TGZ;STGZ\")\ninclude(CPack)\n\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Aeron\n\nIf you would like to contribute code you can do so through GitHub by sending a pull request or raising an issue with an attached patch.\n\nWhen submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible.\n\n[![Gitter](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/aeron-io/Aeron?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) To chat with other Aeron contributors.\n\n## License\n\nBy contributing your code, you agree to license your contribution under the terms of the APLv2:\n \nhttps://github.com/aeron-io/aeron/blob/master/LICENSE\n\nAll files are made available under the Apache 2.0 license.\n\nIf you are adding a new file it should have the following header:\n\n```\n/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n ```"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n     https://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "Aeron\n=====\n\n[![GitHub](https://img.shields.io/github/license/aeron-io/Aeron.svg)](https://github.com/aeron-io/aeron/blob/master/LICENSE)\n[![Javadocs](https://www.javadoc.io/badge/io.aeron/aeron-all.svg)](https://www.javadoc.io/doc/io.aeron/aeron-all)\n\n[![Actions Status](https://github.com/aeron-io/aeron/workflows/Continuous%20Integration/badge.svg)](https://github.com/aeron-io/aeron/actions)\n[![CodeQL Status](https://github.com/aeron-io/aeron/workflows/CodeQL/badge.svg)](https://github.com/aeron-io/aeron/actions)\n\nEfficient reliable UDP unicast, UDP multicast, and IPC message transport. Java, C, and C++ clients are available in this\nrepository, and a [.NET client](https://github.com/AdaptiveConsulting/Aeron.NET) is available. All\nclients can exchange messages across machines, or on the same machine via IPC, very efficiently. Message streams can be\nrecorded by the [Archive](https://github.com/aeron-io/aeron/tree/master/aeron-archive) module to persistent storage\nfor later, or real-time, replay. Aeron [Cluster](https://github.com/aeron-io/aeron/tree/master/aeron-cluster)\nprovides support for fault-tolerant services as replicated state machines based on the\n[Raft](https://raft.github.io/) consensus algorithm.\n\nPerformance is the key focus. A design goal for Aeron is to be the highest throughput with the lowest and most\npredictable latency of any messaging system. Aeron integrates with\n[Simple Binary Encoding (SBE)](https://github.com/aeron-io/simple-binary-encoding) for the best possible message\nencoding and decoding performance. Many of the data structures used in the creation of Aeron have been factored out to\nthe [Agrona](https://github.com/aeron-io/agrona) project.\n\nFor details of usage, protocol specification, FAQ, etc., please check out the\n[Wiki](https://github.com/aeron-io/aeron/wiki).\n\nFor the latest version information and changes, see the [Change Log](https://github.com/aeron-io/aeron/blob/master/CHANGELOG.adoc)\nwith Java **downloads** at [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Caeron).\n\nAeron is owned and operated by Adaptive Financial Consulting. Originally created by Martin Thompson and Todd Montgomery, the Aeron team joined Adaptive in 2022.\n\nFor Business users, to get started with Aeron Premium, please visit [Aeron.io](https://aeron.io)\n\nWe provide a range of services, including:\n* Training for development and operations with Aeron and Aeron Cluster.\n* Consulting, for example, if you’re not sure how to design your system or need help tuning your system.\n* We also offer a number of proprietary enhancements on top of Aeron and Aeron Cluster, such as kernel bypass with DPDK for increased performance, and blazing fast encryption with ATS.\n* If you’re building a new trading system, we have experienced Aeron developers who can help.\n\nPlease get in touch at [sales@aeron.io](mailto:sales@aeron.io?subject=Aeron) if you would like to learn more about any of these.\n\n### How do I use Aeron?\n\n1. [Java Programming Guide](https://github.com/aeron-io/aeron/wiki/Java-Programming-Guide)\n1. [C++11 Programming Guide](https://github.com/aeron-io/aeron/wiki/Cpp-Programming-Guide)\n1. [Best Practices Guide](https://github.com/aeron-io/aeron/wiki/Best-Practices-Guide)\n1. [Monitoring and Debugging](https://github.com/aeron-io/aeron/wiki/Monitoring-and-Debugging)\n1. [Configuration Options](https://github.com/aeron-io/aeron/wiki/Configuration-Options)\n1. [Channel Specific Configuration](https://github.com/aeron-io/aeron/wiki/Channel-Configuration)\n1. [Aeron Archive (Durable/Persistent Stream Storage)](https://github.com/aeron-io/aeron/wiki/Aeron-Archive)\n1. [Aeron Cluster (Fault Tolerant Services)](https://github.com/aeron-io/aeron/tree/master/aeron-cluster)\n1. [Aeron Docs](https://aeron.io/docs/)\n\n### How does Aeron work?\n\n1. [Transport Protocol Specification](https://github.com/aeron-io/aeron/wiki/Transport-Protocol-Specification)\n1. [Design Overview](https://github.com/aeron-io/aeron/wiki/Design-Overview)\n1. [Design Principles](https://github.com/aeron-io/aeron/wiki/Design-Principles)\n1. [Flow Control Semantics](https://github.com/aeron-io/aeron/wiki/Flow-and-Congestion-Control)\n1. [Media Driver Operation](https://github.com/aeron-io/aeron/wiki/Media-Driver-Operation)\n\n### How do I hack on Aeron?\n\n1. [Hacking on Aeron](https://github.com/aeron-io/aeron/wiki/Hacking-on-Aeron)\n1. [Performance Testing](https://github.com/aeron-io/aeron/wiki/Performance-Testing)\n1. [Building Aeron](https://github.com/aeron-io/aeron/wiki/Building-Aeron)\n\nLicense (See LICENSE file for full license)\n-------------------------------------------\nCopyright 2014-2025 Real Logic Limited.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    https://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.  \n"
  },
  {
    "path": "aeron-agent/README.md",
    "content": "Aeron Agent\n===\n\n[![Javadocs](http://www.javadoc.io/badge/io.aeron/aeron-all.svg)](http://www.javadoc.io/doc/io.aeron/aeron-all)\n\nA Java agent which when attached to a JVM will weave byte code to intercept and log events that\nimplement [EventCode](https://github.com/aeron-io/aeron/blob/master/aeron-agent/src/main/java/io/aeron/agent/EventCode.java),\nthose include [DriverEventCode](https://github.com/aeron-io/aeron/blob/master/aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java), [ArchiveEventCode](https://github.com/aeron-io/aeron/blob/master/aeron-agent/src/main/java/io/aeron/agent/ArchiveEventCode.java),\nand [ClusterEventCode](https://github.com/aeron-io/aeron/blob/master/aeron-agent/src/main/java/io/aeron/agent/ClusterEventCode.java)\n\nEvents are recorded to an in-memory\n[RingBuffer](https://github.com/aeron-io/agrona/blob/master/agrona/src/main/java/org/agrona/concurrent/ringbuffer/RingBuffer.java)\nwhich is consumed and appended asynchronously to a log as defined by the system property `aeron.event.log.reader.classname`\nfor the reader [Agent](https://github.com/aeron-io/agrona/blob/master/agrona/src/main/java/org/agrona/concurrent/Agent.java)\nwhich defaults to [EventLogReaderAgent](https://github.com/aeron-io/aeron/blob/master/aeron-agent/src/main/java/io/aeron/agent/EventLogReaderAgent.java)."
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ArchiveComponentLogger.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.AeronCounters;\nimport io.aeron.version.Versioned;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport java.util.EnumSet;\nimport java.util.Map;\n\nimport static io.aeron.agent.ArchiveEventCode.*;\nimport static io.aeron.agent.ConfigOption.DISABLED_ARCHIVE_EVENT_CODES;\nimport static io.aeron.agent.ConfigOption.ENABLED_ARCHIVE_EVENT_CODES;\nimport static io.aeron.agent.EventConfiguration.parseEventCodes;\nimport static net.bytebuddy.asm.Advice.to;\nimport static net.bytebuddy.matcher.ElementMatchers.nameEndsWith;\nimport static net.bytebuddy.matcher.ElementMatchers.named;\n\n/**\n * Implementation of a component logger for archive log events.\n */\n@Versioned\npublic final class ArchiveComponentLogger implements ComponentLogger\n{\n    static final EnumSet<ArchiveEventCode> ENABLED_EVENTS = EnumSet.noneOf(ArchiveEventCode.class);\n    private static final Object2ObjectHashMap<String, EnumSet<ArchiveEventCode>> SPECIAL_EVENTS =\n        new Object2ObjectHashMap<>();\n\n    static\n    {\n        SPECIAL_EVENTS.put(\"all\", EnumSet.allOf(ArchiveEventCode.class));\n    }\n\n    /**\n     * Create an ArchiveComponentLogger, used by java service API.\n     */\n    public ArchiveComponentLogger()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int typeCode()\n    {\n        return EventCodeType.ARCHIVE.getTypeCode();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void decode(\n        final MutableDirectBuffer buffer, final int offset, final int eventCodeId, final StringBuilder builder)\n    {\n        ArchiveEventCode.get(eventCodeId).decode(buffer, offset, builder);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public AgentBuilder addInstrumentation(final AgentBuilder agentBuilder, final Map<String, String> configOptions)\n    {\n        ENABLED_EVENTS.clear();\n        ENABLED_EVENTS.addAll(getArchiveEventCodes(configOptions.get(ENABLED_ARCHIVE_EVENT_CODES)));\n        ENABLED_EVENTS.removeAll(getArchiveEventCodes(configOptions.get(DISABLED_ARCHIVE_EVENT_CODES)));\n\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addArchiveControlSessionAdapterInstrumentation(tempBuilder);\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            CMD_OUT_RESPONSE,\n            \"ControlResponseProxy\",\n            ControlInterceptor.ControlResponse.class,\n            \"logSendResponse\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            RECORDING_SIGNAL,\n            \"ControlResponseProxy\",\n            ControlInterceptor.RecordingSignal.class,\n            \"logSendSignal\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            REPLAY_SESSION_STATE_CHANGE,\n            \"ReplaySession\",\n            ArchiveInterceptor.ReplaySessionStateChange.class,\n            \"logStateChange\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            RECORDING_SESSION_STATE_CHANGE,\n            \"RecordingSession\",\n            ArchiveInterceptor.RecordingSessionStateChange.class,\n            \"logStateChange\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            REPLICATION_SESSION_STATE_CHANGE,\n            \"ReplicationSession\",\n            ArchiveInterceptor.ReplicationSessionStateChange.class,\n            \"logStateChange\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            REPLICATION_SESSION_DONE,\n            \"ReplicationSession\",\n            ArchiveInterceptor.ReplicationSessionDone.class,\n            \"logReplicationSessionDone\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            CONTROL_SESSION_STATE_CHANGE,\n            \"ControlSession\",\n            ArchiveInterceptor.ControlSessionStateChange.class,\n            \"logStateChange\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            REPLAY_SESSION_ERROR,\n            \"ReplaySession\",\n            ArchiveInterceptor.ReplaySession.class,\n            \"onPendingError\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            CATALOG_RESIZE,\n            \"Catalog\",\n            ArchiveInterceptor.Catalog.class,\n            \"catalogResized\");\n\n        return tempBuilder;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void reset()\n    {\n        ENABLED_EVENTS.clear();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String version()\n    {\n        return AeronCounters.formatVersionInfo(\n            ArchiveComponentLoggerVersion.VERSION, ArchiveComponentLoggerVersion.GIT_SHA);\n    }\n\n    private static EnumSet<ArchiveEventCode> getArchiveEventCodes(final String enabledEventCodes)\n    {\n        return parseEventCodes(\n            ArchiveEventCode.class,\n            enabledEventCodes,\n            SPECIAL_EVENTS,\n            ArchiveEventCode::get,\n            ArchiveEventCode::valueOf);\n    }\n\n    private static AgentBuilder addArchiveControlSessionAdapterInstrumentation(final AgentBuilder agentBuilder)\n    {\n        if (ArchiveEventLogger.CONTROL_REQUEST_EVENTS.stream().noneMatch(ENABLED_EVENTS::contains))\n        {\n            return agentBuilder;\n        }\n\n        return agentBuilder\n            .type(nameEndsWith(\"ControlSessionAdapter\"))\n            .transform(((builder, typeDescription, classLoader, module, protectionDomain) -> builder\n                .visit(to(ControlInterceptor.ControlRequest.class)\n                    .on(named(\"onFragment\")))));\n    }\n\n    private static AgentBuilder addEventInstrumentation(\n        final AgentBuilder agentBuilder,\n        final ArchiveEventCode code,\n        final String typeName,\n        final Class<?> interceptorClass,\n        final String interceptorMethod)\n    {\n        if (!ENABLED_EVENTS.contains(code))\n        {\n            return agentBuilder;\n        }\n\n        return agentBuilder\n            .type(nameEndsWith(typeName))\n            .transform((builder, typeDescription, classLoader, javaModule, protectionDomain) ->\n                builder.visit(to(interceptorClass).on(named(interceptorMethod))));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ArchiveEventCode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.archive.codecs.*;\nimport org.agrona.MutableDirectBuffer;\n\nimport java.util.Arrays;\nimport java.util.function.ToIntFunction;\n\nimport static io.aeron.agent.ArchiveEventDissector.*;\n\n/**\n * Events that can be enabled for logging in the archive module.\n */\npublic enum ArchiveEventCode implements EventCode\n{\n    /**\n     * Archive logging event for {@code connect} command.\n     */\n    CMD_IN_CONNECT(1, ConnectRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code close-session} command.\n     */\n    CMD_IN_CLOSE_SESSION(2, CloseSessionRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code start-recording} command.\n     */\n    CMD_IN_START_RECORDING(3, StartRecordingRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code stop-recording} command.\n     */\n    CMD_IN_STOP_RECORDING(4, StopRecordingRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code replay} command.\n     */\n    CMD_IN_REPLAY(5, ReplayRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code stop-replay} command.\n     */\n    CMD_IN_STOP_REPLAY(6, StopReplayRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code list-recordings} command.\n     */\n    CMD_IN_LIST_RECORDINGS(7, ListRecordingsRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code list-recordings-by-uri} command.\n     */\n    CMD_IN_LIST_RECORDINGS_FOR_URI(8, ListRecordingsForUriRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code list-recording} command.\n     */\n    CMD_IN_LIST_RECORDING(9, ListRecordingRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code extend-recording} command.\n     */\n    CMD_IN_EXTEND_RECORDING(10, ExtendRecordingRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code recording-position} command.\n     */\n    CMD_IN_RECORDING_POSITION(11, RecordingPositionRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code truncate-recording} command.\n     */\n    CMD_IN_TRUNCATE_RECORDING(12, TruncateRecordingRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code stop-recording-subscription} command.\n     */\n    CMD_IN_STOP_RECORDING_SUBSCRIPTION(13, StopRecordingSubscriptionRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code stop-position} command.\n     */\n    CMD_IN_STOP_POSITION(14, StopPositionRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code find-last-matching-recording} command.\n     */\n    CMD_IN_FIND_LAST_MATCHING_RECORD(15, FindLastMatchingRecordingRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code list-recording-subscriptions} command.\n     */\n    CMD_IN_LIST_RECORDING_SUBSCRIPTIONS(16, ListRecordingSubscriptionsRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code start-bounded-replay} command.\n     */\n    CMD_IN_START_BOUNDED_REPLAY(17, BoundedReplayRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code stop-all-replays} command.\n     */\n    CMD_IN_STOP_ALL_REPLAYS(18, StopAllReplaysRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code replicate} command.\n     */\n    CMD_IN_REPLICATE(19, ReplicateRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code stop-replication} command.\n     */\n    CMD_IN_STOP_REPLICATION(20, StopReplicationRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code start-position} command.\n     */\n    CMD_IN_START_POSITION(21, StartPositionRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code detach-segments} command.\n     */\n    CMD_IN_DETACH_SEGMENTS(22, DetachSegmentsRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code delete-detached-segments} command.\n     */\n    CMD_IN_DELETE_DETACHED_SEGMENTS(23, DeleteDetachedSegmentsRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code purge-segments} command.\n     */\n    CMD_IN_PURGE_SEGMENTS(24, PurgeSegmentsRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code attach-segments} command.\n     */\n    CMD_IN_ATTACH_SEGMENTS(25, AttachSegmentsRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code migrate-segments} command.\n     */\n    CMD_IN_MIGRATE_SEGMENTS(26, MigrateSegmentsRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code auth-connect} command.\n     */\n    CMD_IN_AUTH_CONNECT(27, AuthConnectRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code keep-alive} command.\n     */\n    CMD_IN_KEEP_ALIVE(28, KeepAliveRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code tagged-replicate} command.\n     */\n    CMD_IN_TAGGED_REPLICATE(29, TaggedReplicateRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for Archive response.\n     */\n    CMD_OUT_RESPONSE(30, ControlResponseDecoder.TEMPLATE_ID,\n        (event, buffer, offset, builder) -> dissectControlResponse(buffer, offset, builder)),\n    /**\n     * Archive logging event for {@code start-recording2} command.\n     */\n    CMD_IN_START_RECORDING2(31, StartRecordingRequest2Decoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code extend-recording2} command.\n     */\n    CMD_IN_EXTEND_RECORDING2(32, ExtendRecordingRequest2Decoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code stop-recording-by-identity} command.\n     */\n    CMD_IN_STOP_RECORDING_BY_IDENTITY(33, StopRecordingByIdentityRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for replication state change.\n     */\n    REPLICATION_SESSION_STATE_CHANGE(34, -1,\n        (event, buffer, offset, builder) -> dissectReplicationSessionStateChange(buffer, offset, builder)),\n    /**\n     * Archive logging event for control session state change.\n     */\n    CONTROL_SESSION_STATE_CHANGE(35, -1,\n        (event, buffer, offset, builder) -> dissectControlSessionStateChange(buffer, offset, builder)),\n    /**\n     * Archive logging event for replay session error.\n     */\n    REPLAY_SESSION_ERROR(36, -1,\n        (event, buffer, offset, builder) -> dissectReplaySessionError(buffer, offset, builder)),\n    /**\n     * Archive logging event for Catalog resize.\n     */\n    CATALOG_RESIZE(37, -1,\n        (event, buffer, offset, builder) -> dissectCatalogResize(buffer, offset, builder)),\n    /**\n     * Archive logging event for {@code purge-recording} command.\n     */\n    CMD_IN_PURGE_RECORDING(38, PurgeRecordingRequestDecoder.TEMPLATE_ID,\n        ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for {@code replicate2} command.\n     */\n    CMD_IN_REPLICATE2(39, ReplicateRequest2Decoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for recording signal.\n     */\n    RECORDING_SIGNAL(40, RecordingSignalEventDecoder.TEMPLATE_ID,\n        (event, buffer, offset, builder) -> dissectRecordingSignal(buffer, offset, builder)),\n    /**\n     * Archive logging event for replication session done.\n     */\n    REPLICATION_SESSION_DONE(\n        41, -1, (event, buffer, offset, builder) -> dissectReplicationSessionDone(buffer, offset, builder)),\n    /**\n     * Archive logging event for {@code request-replay-token}.\n     */\n    CMD_IN_REQUEST_REPLAY_TOKEN(\n        42, ReplayTokenRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest),\n    /**\n     * Archive logging event for replay state change.\n     */\n    REPLAY_SESSION_STATE_CHANGE(43, -1,\n        (event, buffer, offset, builder) -> dissectReplaySessionStateChange(buffer, offset, builder)),\n    /**\n     * Archive logging event for recording state change.\n     */\n    RECORDING_SESSION_STATE_CHANGE(44, -1,\n        (event, buffer, offset, builder) -> dissectRecordingSessionStateChange(buffer, offset, builder)),\n    /**\n     * Archive logging event for {@code max-recorded-position} command.\n     */\n    CMD_IN_MAX_RECORDED_POSITION(\n        45, MaxRecordedPositionRequestDecoder.TEMPLATE_ID, ArchiveEventDissector::dissectControlRequest);\n\n    static final int EVENT_CODE_TYPE = EventCodeType.ARCHIVE.getTypeCode();\n    private static final ArchiveEventCode[] EVENT_CODE_BY_ID;\n    private static final ArchiveEventCode[] EVENT_CODE_BY_TEMPLATE_ID;\n\n    private final int id;\n    private final int templateId;\n    private final DissectFunction<ArchiveEventCode> dissector;\n\n    static\n    {\n        final ArchiveEventCode[] codes = ArchiveEventCode.values();\n        EVENT_CODE_BY_ID = createLookupArray(codes, ArchiveEventCode::id);\n        EVENT_CODE_BY_TEMPLATE_ID = createLookupArray(codes, ArchiveEventCode::templateId);\n    }\n\n    private static ArchiveEventCode[] createLookupArray(\n        final ArchiveEventCode[] codes, final ToIntFunction<ArchiveEventCode> idSupplier)\n    {\n        final int maxId = Arrays.stream(codes).mapToInt(idSupplier).max().orElse(0);\n        if (maxId > 100_000)\n        {\n            throw new IllegalStateException(\"length of the lookup array exceeds 100000: \" + maxId);\n        }\n        final ArchiveEventCode[] array = new ArchiveEventCode[maxId + 1];\n\n        for (final ArchiveEventCode code : codes)\n        {\n            final int id = idSupplier.applyAsInt(code);\n            if (id >= 0)\n            {\n                if (null != array[id])\n                {\n                    throw new IllegalArgumentException(\"id already in use: \" + id);\n                }\n\n                array[id] = code;\n            }\n        }\n\n        return array;\n    }\n\n    ArchiveEventCode(final int id, final int templateId, final DissectFunction<ArchiveEventCode> dissector)\n    {\n        this.id = id;\n        this.templateId = templateId;\n        this.dissector = dissector;\n    }\n\n    static ArchiveEventCode get(final int id)\n    {\n        if (id < 0 || id >= EVENT_CODE_BY_ID.length)\n        {\n            throw new IllegalArgumentException(\"no ArchiveEventCode for id: \" + id);\n        }\n\n        final ArchiveEventCode code = EVENT_CODE_BY_ID[id];\n        if (null == code)\n        {\n            throw new IllegalArgumentException(\"no ArchiveEventCode for id: \" + id);\n        }\n\n        return code;\n    }\n\n    static ArchiveEventCode getByTemplateId(final int templateId)\n    {\n        return templateId >= 0 && templateId < EVENT_CODE_BY_TEMPLATE_ID.length ?\n            EVENT_CODE_BY_TEMPLATE_ID[templateId] : null;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int id()\n    {\n        return id;\n    }\n\n    /**\n     * Template ID of the SBE message.\n     *\n     * @return template ID of the SBE message.\n     */\n    public int templateId()\n    {\n        return templateId;\n    }\n\n    /**\n     * Get {@link ArchiveEventCode#id()} from {@link #id()}.\n     *\n     * @return get {@link ArchiveEventCode#id()} from {@link #id()}.\n     */\n    public int toEventCodeId()\n    {\n        return EVENT_CODE_TYPE << 16 | (id & 0xFFFF);\n    }\n\n    /**\n     * Get {@link ArchiveEventCode} from its event code id.\n     *\n     * @param eventCodeId to convert.\n     * @return {@link ArchiveEventCode} from its event code id.\n     */\n    public static ArchiveEventCode fromEventCodeId(final int eventCodeId)\n    {\n        return get(eventCodeId - (EVENT_CODE_TYPE << 16));\n    }\n\n    /**\n     * Decode an event serialised in a buffer to a provided {@link StringBuilder}.\n     *\n     * @param buffer  containing the encoded event.\n     * @param offset  offset at which the event begins.\n     * @param builder to write the decoded event to.\n     */\n    public void decode(final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        dissector.dissect(this, buffer, offset, builder);\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ArchiveEventDissector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.archive.codecs.*;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.agent.ArchiveEventCode.*;\nimport static io.aeron.agent.CommonEventDissector.dissectLogHeader;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_BYTE;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\nfinal class ArchiveEventDissector\n{\n    static final String CONTEXT = \"ARCHIVE\";\n\n    private static final MessageHeaderDecoder HEADER_DECODER = new MessageHeaderDecoder();\n    private static final ConnectRequestDecoder CONNECT_REQUEST_DECODER = new ConnectRequestDecoder();\n    private static final CloseSessionRequestDecoder CLOSE_SESSION_REQUEST_DECODER = new CloseSessionRequestDecoder();\n    private static final StartRecordingRequestDecoder START_RECORDING_REQUEST_DECODER =\n        new StartRecordingRequestDecoder();\n    private static final StartRecordingRequest2Decoder START_RECORDING_REQUEST2_DECODER =\n        new StartRecordingRequest2Decoder();\n    private static final StopRecordingRequestDecoder STOP_RECORDING_REQUEST_DECODER = new StopRecordingRequestDecoder();\n    private static final ReplayRequestDecoder REPLAY_REQUEST_DECODER = new ReplayRequestDecoder();\n    private static final StopReplayRequestDecoder STOP_REPLAY_REQUEST_DECODER = new StopReplayRequestDecoder();\n    private static final ListRecordingsRequestDecoder LIST_RECORDINGS_REQUEST_DECODER =\n        new ListRecordingsRequestDecoder();\n    private static final ListRecordingsForUriRequestDecoder LIST_RECORDINGS_FOR_URI_REQUEST_DECODER =\n        new ListRecordingsForUriRequestDecoder();\n    private static final ListRecordingRequestDecoder LIST_RECORDING_REQUEST_DECODER = new ListRecordingRequestDecoder();\n    private static final ExtendRecordingRequestDecoder EXTEND_RECORDING_REQUEST_DECODER =\n        new ExtendRecordingRequestDecoder();\n    private static final ExtendRecordingRequest2Decoder EXTEND_RECORDING_REQUEST2_DECODER =\n        new ExtendRecordingRequest2Decoder();\n    private static final RecordingPositionRequestDecoder RECORDING_POSITION_REQUEST_DECODER =\n        new RecordingPositionRequestDecoder();\n    private static final TruncateRecordingRequestDecoder TRUNCATE_RECORDING_REQUEST_DECODER =\n        new TruncateRecordingRequestDecoder();\n    private static final StopRecordingSubscriptionRequestDecoder STOP_RECORDING_SUBSCRIPTION_REQUEST_DECODER =\n        new StopRecordingSubscriptionRequestDecoder();\n    private static final StopPositionRequestDecoder STOP_POSITION_REQUEST_DECODER = new StopPositionRequestDecoder();\n    private static final FindLastMatchingRecordingRequestDecoder FIND_LAST_MATCHING_RECORDING_REQUEST_DECODER =\n        new FindLastMatchingRecordingRequestDecoder();\n    private static final ListRecordingSubscriptionsRequestDecoder LIST_RECORDING_SUBSCRIPTIONS_REQUEST_DECODER =\n        new ListRecordingSubscriptionsRequestDecoder();\n    private static final BoundedReplayRequestDecoder BOUNDED_REPLAY_REQUEST_DECODER = new BoundedReplayRequestDecoder();\n    private static final StopAllReplaysRequestDecoder STOP_ALL_REPLAYS_REQUEST_DECODER =\n        new StopAllReplaysRequestDecoder();\n    private static final ReplicateRequestDecoder REPLICATE_REQUEST_DECODER = new ReplicateRequestDecoder();\n    private static final ReplicateRequest2Decoder REPLICATE_REQUEST2_DECODER = new ReplicateRequest2Decoder();\n    private static final StopReplicationRequestDecoder STOP_REPLICATION_REQUEST_DECODER =\n        new StopReplicationRequestDecoder();\n    private static final StartPositionRequestDecoder START_POSITION_REQUEST_DECODER = new StartPositionRequestDecoder();\n    private static final DetachSegmentsRequestDecoder DETACH_SEGMENTS_REQUEST_DECODER =\n        new DetachSegmentsRequestDecoder();\n    private static final DeleteDetachedSegmentsRequestDecoder DELETE_DETACHED_SEGMENTS_REQUEST_DECODER =\n        new DeleteDetachedSegmentsRequestDecoder();\n    private static final PurgeSegmentsRequestDecoder PURGE_SEGMENTS_REQUEST_DECODER = new PurgeSegmentsRequestDecoder();\n    private static final AttachSegmentsRequestDecoder ATTACH_SEGMENTS_REQUEST_DECODER =\n        new AttachSegmentsRequestDecoder();\n    private static final MigrateSegmentsRequestDecoder MIGRATE_SEGMENTS_REQUEST_DECODER =\n        new MigrateSegmentsRequestDecoder();\n    private static final AuthConnectRequestDecoder AUTH_CONNECT_REQUEST_DECODER = new AuthConnectRequestDecoder();\n    private static final KeepAliveRequestDecoder KEEP_ALIVE_REQUEST_DECODER = new KeepAliveRequestDecoder();\n    private static final TaggedReplicateRequestDecoder TAGGED_REPLICATE_REQUEST_DECODER =\n        new TaggedReplicateRequestDecoder();\n    private static final StopRecordingByIdentityRequestDecoder STOP_RECORDING_BY_IDENTITY_REQUEST_DECODER =\n        new StopRecordingByIdentityRequestDecoder();\n    private static final PurgeRecordingRequestDecoder PURGE_RECORDING_REQUEST_DECODER =\n        new PurgeRecordingRequestDecoder();\n    private static final ControlResponseDecoder CONTROL_RESPONSE_DECODER = new ControlResponseDecoder();\n    private static final RecordingSignalEventDecoder RECORDING_SIGNAL_EVENT_DECODER = new RecordingSignalEventDecoder();\n    private static final ReplayTokenRequestDecoder REPLAY_TOKEN_REQUEST_DECODER = new ReplayTokenRequestDecoder();\n    private static final MaxRecordedPositionRequestDecoder MAX_RECORDED_POSITION_REQUEST_DECODER =\n        new MaxRecordedPositionRequestDecoder();\n\n    private ArchiveEventDissector()\n    {\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    static void dissectControlRequest(\n        final ArchiveEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int encodedLength = dissectLogHeader(CONTEXT, eventCode, buffer, offset, builder);\n\n        HEADER_DECODER.wrap(buffer, offset + encodedLength);\n        encodedLength += MessageHeaderDecoder.ENCODED_LENGTH;\n\n        switch (eventCode)\n        {\n            case CMD_IN_CONNECT:\n                CONNECT_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendConnect(builder);\n                break;\n\n            case CMD_IN_CLOSE_SESSION:\n                CLOSE_SESSION_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendCloseSession(builder);\n                break;\n\n            case CMD_IN_START_RECORDING:\n                START_RECORDING_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStartRecording(builder);\n                break;\n\n            case CMD_IN_STOP_RECORDING:\n                STOP_RECORDING_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStopRecording(builder);\n                break;\n\n            case CMD_IN_REPLAY:\n                REPLAY_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendReplay(builder);\n                break;\n\n            case CMD_IN_STOP_REPLAY:\n                STOP_REPLAY_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStopReplay(builder);\n                break;\n\n            case CMD_IN_LIST_RECORDINGS:\n                LIST_RECORDINGS_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendListRecordings(builder);\n                break;\n\n            case CMD_IN_LIST_RECORDINGS_FOR_URI:\n                LIST_RECORDINGS_FOR_URI_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendListRecordingsForUri(builder);\n                break;\n\n            case CMD_IN_LIST_RECORDING:\n                LIST_RECORDING_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendListRecording(builder);\n                break;\n\n            case CMD_IN_EXTEND_RECORDING:\n                EXTEND_RECORDING_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendExtendRecording(builder);\n                break;\n\n            case CMD_IN_RECORDING_POSITION:\n                RECORDING_POSITION_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendRecordingPosition(builder);\n                break;\n\n            case CMD_IN_TRUNCATE_RECORDING:\n                TRUNCATE_RECORDING_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendTruncateRecording(builder);\n                break;\n\n            case CMD_IN_STOP_RECORDING_SUBSCRIPTION:\n                STOP_RECORDING_SUBSCRIPTION_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStopRecordingSubscription(builder);\n                break;\n\n            case CMD_IN_STOP_POSITION:\n                STOP_POSITION_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStopPosition(builder);\n                break;\n\n            case CMD_IN_FIND_LAST_MATCHING_RECORD:\n                FIND_LAST_MATCHING_RECORDING_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendFindLastMatchingRecord(builder);\n                break;\n\n            case CMD_IN_LIST_RECORDING_SUBSCRIPTIONS:\n                LIST_RECORDING_SUBSCRIPTIONS_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendListRecordingSubscriptions(builder);\n                break;\n\n            case CMD_IN_START_BOUNDED_REPLAY:\n                BOUNDED_REPLAY_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStartBoundedReplay(builder);\n                break;\n\n            case CMD_IN_STOP_ALL_REPLAYS:\n                STOP_ALL_REPLAYS_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStopAllReplays(builder);\n                break;\n\n            case CMD_IN_REPLICATE:\n                REPLICATE_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendReplicate(builder);\n                break;\n\n            case CMD_IN_STOP_REPLICATION:\n                STOP_REPLICATION_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStopReplication(builder);\n                break;\n\n            case CMD_IN_START_POSITION:\n                START_POSITION_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStartPosition(builder);\n                break;\n\n            case CMD_IN_DETACH_SEGMENTS:\n                DETACH_SEGMENTS_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendDetachSegments(builder);\n                break;\n\n            case CMD_IN_DELETE_DETACHED_SEGMENTS:\n                DELETE_DETACHED_SEGMENTS_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendDeleteDetachedSegments(builder);\n                break;\n\n            case CMD_IN_PURGE_SEGMENTS:\n                PURGE_SEGMENTS_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendPurgeSegments(builder);\n                break;\n\n            case CMD_IN_ATTACH_SEGMENTS:\n                ATTACH_SEGMENTS_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendAttachSegments(builder);\n                break;\n\n            case CMD_IN_MIGRATE_SEGMENTS:\n                MIGRATE_SEGMENTS_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendMigrateSegments(builder);\n                break;\n\n            case CMD_IN_AUTH_CONNECT:\n                AUTH_CONNECT_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendAuthConnect(builder);\n                break;\n\n            case CMD_IN_KEEP_ALIVE:\n                KEEP_ALIVE_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendKeepAlive(builder);\n                break;\n\n            case CMD_IN_TAGGED_REPLICATE:\n                TAGGED_REPLICATE_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendTaggedReplicate(builder);\n                break;\n\n            case CMD_IN_START_RECORDING2:\n                START_RECORDING_REQUEST2_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStartRecording2(builder);\n                break;\n\n            case CMD_IN_EXTEND_RECORDING2:\n                EXTEND_RECORDING_REQUEST2_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendExtendRecording2(builder);\n                break;\n\n            case CMD_IN_STOP_RECORDING_BY_IDENTITY:\n                STOP_RECORDING_BY_IDENTITY_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendStopRecordingByIdentity(builder);\n                break;\n\n            case CMD_IN_PURGE_RECORDING:\n                PURGE_RECORDING_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendPurgeRecording(builder);\n                break;\n\n            case CMD_IN_REPLICATE2:\n                REPLICATE_REQUEST2_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendReplicate2(builder);\n                break;\n\n            case CMD_IN_REQUEST_REPLAY_TOKEN:\n                REPLAY_TOKEN_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendReplayToken(builder);\n                break;\n\n            case CMD_IN_MAX_RECORDED_POSITION:\n                MAX_RECORDED_POSITION_REQUEST_DECODER.wrap(\n                    buffer,\n                    offset + encodedLength,\n                    HEADER_DECODER.blockLength(),\n                    HEADER_DECODER.version());\n                appendMaxRecordedPosition(builder);\n                break;\n\n            default:\n                builder.append(\": unknown command\");\n        }\n    }\n\n    static void dissectControlResponse(final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int encodedLength = dissectLogHeader(CONTEXT, CMD_OUT_RESPONSE, buffer, offset, builder);\n\n        HEADER_DECODER.wrap(buffer, offset + encodedLength);\n        encodedLength += MessageHeaderDecoder.ENCODED_LENGTH;\n\n        CONTROL_RESPONSE_DECODER.wrap(\n            buffer,\n            offset + encodedLength,\n            HEADER_DECODER.blockLength(),\n            HEADER_DECODER.version());\n\n        builder.append(\": controlSessionId=\").append(CONTROL_RESPONSE_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(CONTROL_RESPONSE_DECODER.correlationId())\n            .append(\" relevantId=\").append(CONTROL_RESPONSE_DECODER.relevantId())\n            .append(\" code=\").append(CONTROL_RESPONSE_DECODER.code())\n            .append(\" version=\").append(CONTROL_RESPONSE_DECODER.version())\n            .append(\" errorMessage=\");\n\n        CONTROL_RESPONSE_DECODER.getErrorMessage(builder);\n    }\n\n    static void dissectRecordingSignal(final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int encodedLength = dissectLogHeader(CONTEXT, RECORDING_SIGNAL, buffer, offset, builder);\n\n        HEADER_DECODER.wrap(buffer, offset + encodedLength);\n        encodedLength += MessageHeaderDecoder.ENCODED_LENGTH;\n\n        RECORDING_SIGNAL_EVENT_DECODER.wrap(\n            buffer,\n            offset + encodedLength,\n            HEADER_DECODER.blockLength(),\n            HEADER_DECODER.version());\n\n        builder.append(\": controlSessionId=\").append(RECORDING_SIGNAL_EVENT_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(RECORDING_SIGNAL_EVENT_DECODER.correlationId())\n            .append(\" recordingId=\").append(RECORDING_SIGNAL_EVENT_DECODER.recordingId())\n            .append(\" subscriptionId=\").append(RECORDING_SIGNAL_EVENT_DECODER.subscriptionId())\n            .append(\" position=\").append(RECORDING_SIGNAL_EVENT_DECODER.position())\n            .append(\" signal=\").append(RECORDING_SIGNAL_EVENT_DECODER.signal());\n    }\n\n    static void dissectReplicationSessionDone(\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, REPLICATION_SESSION_DONE, buffer, offset, builder);\n\n        final long controlSessionId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long replicationId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long srcRecordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long replayPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long srcStopPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long dstRecordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long dstStopPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long position = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final boolean isClosed = 1 == buffer.getByte(absoluteOffset);\n        absoluteOffset += SIZE_OF_BYTE;\n        final boolean isEndOfStream = 1 == buffer.getByte(absoluteOffset);\n        absoluteOffset += SIZE_OF_BYTE;\n        final boolean isSynced = 1 == buffer.getByte(absoluteOffset);\n        absoluteOffset += SIZE_OF_BYTE;\n\n        builder\n            .append(\": controlSessionId=\").append(controlSessionId)\n            .append(\" replicationId=\").append(replicationId)\n            .append(\" srcRecordingId=\").append(srcRecordingId)\n            .append(\" replayPosition=\").append(replayPosition)\n            .append(\" srcStopPosition=\").append(srcStopPosition)\n            .append(\" dstRecordingId=\").append(dstRecordingId)\n            .append(\" dstStopPosition=\").append(dstStopPosition)\n            .append(\" position=\").append(position)\n            .append(\" isClosed=\").append(isClosed)\n            .append(\" isEndOfStream=\").append(isEndOfStream)\n            .append(\" isSynced=\").append(isSynced);\n    }\n\n    static void dissectReplaySessionStateChange(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, REPLAY_SESSION_STATE_CHANGE, buffer, absoluteOffset, builder);\n\n        final long replaySessionId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long recordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long position = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\": replaySessionId=\").append(replaySessionId);\n        builder.append(\" replayId=\").append(replaySessionId >> 32);\n        builder.append(\" sessionId=\").append((int)replaySessionId);\n        builder.append(\" recordingId=\").append(recordingId);\n        builder.append(\" position=\").append(position);\n\n        builder.append(\" \");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" reason=\\\"\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append(\"\\\"\");\n    }\n\n    static void dissectRecordingSessionStateChange(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, RECORDING_SESSION_STATE_CHANGE, buffer, absoluteOffset, builder);\n\n        final long recordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long position = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\": recordingId=\").append(recordingId);\n        builder.append(\" position=\").append(position);\n\n        builder.append(\" \");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" reason=\\\"\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append(\"\\\"\");\n    }\n\n    static void dissectReplicationSessionStateChange(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, REPLICATION_SESSION_STATE_CHANGE, buffer, absoluteOffset, builder);\n\n        final long replicationId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long srcRecordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long dstRecordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long position = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\": replicationId=\").append(replicationId);\n        builder.append(\" srcRecordingId=\").append(srcRecordingId);\n        builder.append(\" dstRecordingId=\").append(dstRecordingId);\n        builder.append(\" position=\").append(position);\n\n        builder.append(\" \");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" reason=\\\"\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append(\"\\\"\");\n    }\n\n    static void dissectControlSessionStateChange(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, CONTROL_SESSION_STATE_CHANGE, buffer, absoluteOffset, builder);\n\n        final long controlSessionId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\": controlSessionId=\").append(controlSessionId);\n        builder.append(\" \");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" reason=\\\"\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append(\"\\\"\");\n    }\n\n    static void dissectReplaySessionError(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, REPLAY_SESSION_ERROR, buffer, absoluteOffset, builder);\n\n        final long sessionId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long recordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\": sessionId=\").append(sessionId);\n        builder.append(\" recordingId=\").append(recordingId);\n        builder.append(\" errorMessage=\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n    }\n\n    static void dissectCatalogResize(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, CATALOG_RESIZE, buffer, absoluteOffset, builder);\n\n        final long catalogLength = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long newCatalogLength = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n\n        builder.append(\": \").append(catalogLength).append(\" bytes => \").append(newCatalogLength).append(\" bytes\");\n    }\n\n    private static void appendConnect(final StringBuilder builder)\n    {\n        builder.append(\": correlationId=\").append(CONNECT_REQUEST_DECODER.correlationId())\n            .append(\" responseStreamId=\").append(CONNECT_REQUEST_DECODER.responseStreamId())\n            .append(\" version=\").append(CONNECT_REQUEST_DECODER.version())\n            .append(\" responseChannel=\");\n\n        CONNECT_REQUEST_DECODER.getResponseChannel(builder);\n    }\n\n    private static void appendAuthConnect(final StringBuilder builder)\n    {\n        builder.append(\": correlationId=\").append(AUTH_CONNECT_REQUEST_DECODER.correlationId())\n            .append(\" responseStreamId=\").append(AUTH_CONNECT_REQUEST_DECODER.responseStreamId())\n            .append(\" version=\").append(AUTH_CONNECT_REQUEST_DECODER.version())\n            .append(\" responseChannel=\");\n\n        AUTH_CONNECT_REQUEST_DECODER.getResponseChannel(builder);\n\n        builder.append(\" encodedCredentialsLength=\").append(AUTH_CONNECT_REQUEST_DECODER.encodedCredentialsLength());\n    }\n\n    private static void appendCloseSession(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(CLOSE_SESSION_REQUEST_DECODER.controlSessionId());\n    }\n\n    private static void appendStartRecording(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(START_RECORDING_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(START_RECORDING_REQUEST_DECODER.correlationId())\n            .append(\" streamId=\").append(START_RECORDING_REQUEST_DECODER.streamId())\n            .append(\" sourceLocation=\").append(START_RECORDING_REQUEST_DECODER.sourceLocation())\n            .append(\" channel=\");\n\n        START_RECORDING_REQUEST_DECODER.getChannel(builder);\n    }\n\n    private static void appendStartRecording2(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(START_RECORDING_REQUEST2_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(START_RECORDING_REQUEST2_DECODER.correlationId())\n            .append(\" streamId=\").append(START_RECORDING_REQUEST2_DECODER.streamId())\n            .append(\" sourceLocation=\").append(START_RECORDING_REQUEST2_DECODER.sourceLocation())\n            .append(\" autoStop=\").append(START_RECORDING_REQUEST2_DECODER.autoStop())\n            .append(\" channel=\");\n\n        START_RECORDING_REQUEST2_DECODER.getChannel(builder);\n    }\n\n    private static void appendStopRecording(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(STOP_RECORDING_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(STOP_RECORDING_REQUEST_DECODER.correlationId())\n            .append(\" streamId=\").append(STOP_RECORDING_REQUEST_DECODER.streamId())\n            .append(\" channel=\");\n\n        STOP_RECORDING_REQUEST_DECODER.getChannel(builder);\n    }\n\n    private static void appendReplay(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(REPLAY_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(REPLAY_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(REPLAY_REQUEST_DECODER.recordingId())\n            .append(\" position=\").append(REPLAY_REQUEST_DECODER.position())\n            .append(\" length=\").append(REPLAY_REQUEST_DECODER.length())\n            .append(\" replayStreamId=\").append(REPLAY_REQUEST_DECODER.replayStreamId())\n            .append(\" replayChannel=\");\n\n        REPLAY_REQUEST_DECODER.getReplayChannel(builder);\n    }\n\n    private static void appendStopReplay(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(STOP_REPLAY_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(STOP_REPLAY_REQUEST_DECODER.correlationId())\n            .append(\" replaySessionId=\").append(STOP_REPLAY_REQUEST_DECODER.replaySessionId());\n    }\n\n    private static void appendListRecordings(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(LIST_RECORDINGS_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(LIST_RECORDINGS_REQUEST_DECODER.correlationId())\n            .append(\" fromRecordingId=\").append(LIST_RECORDINGS_REQUEST_DECODER.fromRecordingId())\n            .append(\" recordCount=\").append(LIST_RECORDINGS_REQUEST_DECODER.recordCount());\n    }\n\n    private static void appendListRecording(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(LIST_RECORDING_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(LIST_RECORDING_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(LIST_RECORDING_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendListRecordingsForUri(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(LIST_RECORDINGS_FOR_URI_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(LIST_RECORDINGS_FOR_URI_REQUEST_DECODER.correlationId())\n            .append(\" fromRecordingId=\").append(LIST_RECORDINGS_FOR_URI_REQUEST_DECODER.fromRecordingId())\n            .append(\" recordCount=\").append(LIST_RECORDINGS_FOR_URI_REQUEST_DECODER.recordCount())\n            .append(\" streamId=\").append(LIST_RECORDINGS_FOR_URI_REQUEST_DECODER.streamId())\n            .append(\" channel=\");\n\n        LIST_RECORDINGS_FOR_URI_REQUEST_DECODER.getChannel(builder);\n    }\n\n    private static void appendExtendRecording(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(EXTEND_RECORDING_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(EXTEND_RECORDING_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(EXTEND_RECORDING_REQUEST_DECODER.recordingId())\n            .append(\" streamId=\").append(EXTEND_RECORDING_REQUEST_DECODER.streamId())\n            .append(\" sourceLocation=\").append(EXTEND_RECORDING_REQUEST_DECODER.sourceLocation())\n            .append(\" channel=\");\n\n        EXTEND_RECORDING_REQUEST_DECODER.getChannel(builder);\n    }\n\n    private static void appendExtendRecording2(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(EXTEND_RECORDING_REQUEST2_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(EXTEND_RECORDING_REQUEST2_DECODER.correlationId())\n            .append(\" recordingId=\").append(EXTEND_RECORDING_REQUEST2_DECODER.recordingId())\n            .append(\" streamId=\").append(EXTEND_RECORDING_REQUEST2_DECODER.streamId())\n            .append(\" sourceLocation=\").append(EXTEND_RECORDING_REQUEST2_DECODER.sourceLocation())\n            .append(\" autoStop=\").append(EXTEND_RECORDING_REQUEST2_DECODER.autoStop())\n            .append(\" channel=\");\n\n        EXTEND_RECORDING_REQUEST2_DECODER.getChannel(builder);\n    }\n\n    private static void appendRecordingPosition(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(RECORDING_POSITION_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(RECORDING_POSITION_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(RECORDING_POSITION_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendMaxRecordedPosition(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(MAX_RECORDED_POSITION_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(MAX_RECORDED_POSITION_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(MAX_RECORDED_POSITION_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendTruncateRecording(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(TRUNCATE_RECORDING_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(TRUNCATE_RECORDING_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(TRUNCATE_RECORDING_REQUEST_DECODER.recordingId())\n            .append(\" position=\").append(TRUNCATE_RECORDING_REQUEST_DECODER.position());\n    }\n\n    private static void appendStopRecordingSubscription(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(STOP_RECORDING_SUBSCRIPTION_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(STOP_RECORDING_SUBSCRIPTION_REQUEST_DECODER.correlationId())\n            .append(\" subscriptionId=\").append(STOP_RECORDING_SUBSCRIPTION_REQUEST_DECODER.subscriptionId());\n    }\n\n    private static void appendStopRecordingByIdentity(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(STOP_RECORDING_BY_IDENTITY_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(STOP_RECORDING_BY_IDENTITY_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(STOP_RECORDING_BY_IDENTITY_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendStopPosition(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(STOP_POSITION_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(STOP_POSITION_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(STOP_POSITION_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendFindLastMatchingRecord(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(FIND_LAST_MATCHING_RECORDING_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(FIND_LAST_MATCHING_RECORDING_REQUEST_DECODER.correlationId())\n            .append(\" minRecordingId=\").append(FIND_LAST_MATCHING_RECORDING_REQUEST_DECODER.minRecordingId())\n            .append(\" sessionId=\").append(FIND_LAST_MATCHING_RECORDING_REQUEST_DECODER.sessionId())\n            .append(\" streamId=\").append(FIND_LAST_MATCHING_RECORDING_REQUEST_DECODER.streamId())\n            .append(\" channel=\");\n\n        FIND_LAST_MATCHING_RECORDING_REQUEST_DECODER.getChannel(builder);\n    }\n\n    private static void appendListRecordingSubscriptions(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(LIST_RECORDING_SUBSCRIPTIONS_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(LIST_RECORDING_SUBSCRIPTIONS_REQUEST_DECODER.correlationId())\n            .append(\" pseudoIndex=\").append(LIST_RECORDING_SUBSCRIPTIONS_REQUEST_DECODER.pseudoIndex())\n            .append(\" applyStreamId=\").append(LIST_RECORDING_SUBSCRIPTIONS_REQUEST_DECODER.applyStreamId())\n            .append(\" subscriptionCount=\").append(LIST_RECORDING_SUBSCRIPTIONS_REQUEST_DECODER.subscriptionCount())\n            .append(\" streamId=\").append(LIST_RECORDING_SUBSCRIPTIONS_REQUEST_DECODER.streamId())\n            .append(\" channel=\");\n\n        LIST_RECORDING_SUBSCRIPTIONS_REQUEST_DECODER.getChannel(builder);\n    }\n\n    private static void appendStartBoundedReplay(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(BOUNDED_REPLAY_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(BOUNDED_REPLAY_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(BOUNDED_REPLAY_REQUEST_DECODER.recordingId())\n            .append(\" position=\").append(BOUNDED_REPLAY_REQUEST_DECODER.position())\n            .append(\" length=\").append(BOUNDED_REPLAY_REQUEST_DECODER.length())\n            .append(\" limitCounterId=\").append(BOUNDED_REPLAY_REQUEST_DECODER.limitCounterId())\n            .append(\" replayStreamId=\").append(BOUNDED_REPLAY_REQUEST_DECODER.replayStreamId())\n            .append(\" replayChannel=\");\n\n        BOUNDED_REPLAY_REQUEST_DECODER.getReplayChannel(builder);\n    }\n\n    private static void appendStopAllReplays(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(STOP_ALL_REPLAYS_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(STOP_ALL_REPLAYS_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(STOP_ALL_REPLAYS_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendReplicate(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(REPLICATE_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(REPLICATE_REQUEST_DECODER.correlationId())\n            .append(\" srcRecordingId=\").append(REPLICATE_REQUEST_DECODER.srcRecordingId())\n            .append(\" dstRecordingId=\").append(REPLICATE_REQUEST_DECODER.dstRecordingId())\n            .append(\" srcControlStreamId=\").append(REPLICATE_REQUEST_DECODER.srcControlStreamId())\n            .append(\" srcControlChannel=\");\n\n        REPLICATE_REQUEST_DECODER.getSrcControlChannel(builder);\n\n        builder.append(\" liveDestination=\");\n        REPLICATE_REQUEST_DECODER.getLiveDestination(builder);\n    }\n\n    private static void appendReplicate2(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(REPLICATE_REQUEST2_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(REPLICATE_REQUEST2_DECODER.correlationId())\n            .append(\" srcRecordingId=\").append(REPLICATE_REQUEST2_DECODER.srcRecordingId())\n            .append(\" dstRecordingId=\").append(REPLICATE_REQUEST2_DECODER.dstRecordingId())\n            .append(\" stopPosition=\").append(REPLICATE_REQUEST2_DECODER.stopPosition())\n            .append(\" channelTagId=\").append(REPLICATE_REQUEST2_DECODER.channelTagId())\n            .append(\" subscriptionTagId=\").append(REPLICATE_REQUEST2_DECODER.subscriptionTagId())\n            .append(\" srcControlStreamId=\").append(REPLICATE_REQUEST2_DECODER.srcControlStreamId())\n            .append(\" srcControlChannel=\");\n\n        REPLICATE_REQUEST2_DECODER.getSrcControlChannel(builder);\n\n        builder.append(\" liveDestination=\");\n        REPLICATE_REQUEST2_DECODER.getLiveDestination(builder);\n\n        builder.append(\" replicationChannel=\");\n        REPLICATE_REQUEST2_DECODER.getReplicationChannel(builder);\n    }\n\n    private static void appendStopReplication(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(STOP_REPLICATION_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(STOP_REPLICATION_REQUEST_DECODER.correlationId())\n            .append(\" replicationId=\").append(STOP_REPLICATION_REQUEST_DECODER.replicationId());\n    }\n\n    private static void appendStartPosition(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(START_POSITION_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(START_POSITION_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(START_POSITION_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendDetachSegments(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(DETACH_SEGMENTS_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(DETACH_SEGMENTS_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(DETACH_SEGMENTS_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendDeleteDetachedSegments(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(DELETE_DETACHED_SEGMENTS_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(DELETE_DETACHED_SEGMENTS_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(DELETE_DETACHED_SEGMENTS_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendPurgeSegments(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(PURGE_SEGMENTS_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(PURGE_SEGMENTS_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(PURGE_SEGMENTS_REQUEST_DECODER.recordingId())\n            .append(\" newStartPosition=\").append(PURGE_SEGMENTS_REQUEST_DECODER.newStartPosition());\n    }\n\n    private static void appendAttachSegments(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(ATTACH_SEGMENTS_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(ATTACH_SEGMENTS_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(ATTACH_SEGMENTS_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendMigrateSegments(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(MIGRATE_SEGMENTS_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(MIGRATE_SEGMENTS_REQUEST_DECODER.correlationId())\n            .append(\" srcRecordingId=\").append(MIGRATE_SEGMENTS_REQUEST_DECODER.srcRecordingId())\n            .append(\" dstRecordingId=\").append(MIGRATE_SEGMENTS_REQUEST_DECODER.dstRecordingId());\n    }\n\n    private static void appendKeepAlive(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(KEEP_ALIVE_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(KEEP_ALIVE_REQUEST_DECODER.correlationId());\n    }\n\n    private static void appendTaggedReplicate(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(TAGGED_REPLICATE_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(TAGGED_REPLICATE_REQUEST_DECODER.correlationId())\n            .append(\" srcRecordingId=\").append(TAGGED_REPLICATE_REQUEST_DECODER.srcRecordingId())\n            .append(\" dstRecordingId=\").append(TAGGED_REPLICATE_REQUEST_DECODER.dstRecordingId())\n            .append(\" channelTagId=\").append(TAGGED_REPLICATE_REQUEST_DECODER.channelTagId())\n            .append(\" subscriptionTagId=\").append(TAGGED_REPLICATE_REQUEST_DECODER.subscriptionTagId())\n            .append(\" srcControlStreamId=\").append(TAGGED_REPLICATE_REQUEST_DECODER.srcControlStreamId())\n            .append(\" srcControlChannel=\");\n\n        TAGGED_REPLICATE_REQUEST_DECODER.getSrcControlChannel(builder);\n\n        builder.append(\" liveDestination=\");\n        TAGGED_REPLICATE_REQUEST_DECODER.getLiveDestination(builder);\n    }\n\n    private static void appendPurgeRecording(final StringBuilder builder)\n    {\n        builder.append(\": controlSessionId=\").append(PURGE_RECORDING_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(PURGE_RECORDING_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(PURGE_RECORDING_REQUEST_DECODER.recordingId());\n    }\n\n    private static void appendReplayToken(final StringBuilder builder)\n    {\n        builder\n            .append(\": controlSessionId=\").append(REPLAY_TOKEN_REQUEST_DECODER.controlSessionId())\n            .append(\" correlationId=\").append(REPLAY_TOKEN_REQUEST_DECODER.correlationId())\n            .append(\" recordingId=\").append(REPLAY_TOKEN_REQUEST_DECODER.recordingId());\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ArchiveEventEncoder.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.agent.CommonEventEncoder.*;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_BYTE;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\nfinal class ArchiveEventEncoder\n{\n    private ArchiveEventEncoder()\n    {\n    }\n\n    static <E extends Enum<E>> int encodeReplaySessionStateChange(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final E from,\n        final E to,\n        final long id,\n        final long recordingId,\n        final long position,\n        final String reason)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, id, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(offset + encodedLength, recordingId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(offset + encodedLength, position, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodedLength += encodeStateChange(encodingBuffer, offset + encodedLength, from, to);\n\n        encodedLength += encodeTrailingString(encodingBuffer, offset + encodedLength,\n            captureLength + LOG_HEADER_LENGTH - encodedLength, reason);\n\n        return encodedLength;\n    }\n\n    static <E extends Enum<E>> int replaySessionStateChangeLength(final E from, final E to, final String reason)\n    {\n        return stateTransitionStringLength(from, to) + (3 * SIZE_OF_LONG) + (SIZE_OF_INT + reason.length());\n    }\n\n    static <E extends Enum<E>> int encodeRecordingSessionStateChange(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final E from,\n        final E to,\n        final long recordingId,\n        final long position,\n        final String reason)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, recordingId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(offset + encodedLength, position, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodedLength += encodeStateChange(encodingBuffer, offset + encodedLength, from, to);\n\n        encodedLength += encodeTrailingString(encodingBuffer, offset + encodedLength,\n            captureLength + LOG_HEADER_LENGTH - encodedLength, reason);\n\n        return encodedLength;\n    }\n\n    static <E extends Enum<E>> int recordingSessionStateChangeLength(final E from, final E to, final String reason)\n    {\n        return stateTransitionStringLength(from, to) + (2 * SIZE_OF_LONG) + (SIZE_OF_INT + reason.length());\n    }\n\n    static <E extends Enum<E>> int encodeReplicationSessionStateChange(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final E from,\n        final E to,\n        final long replicationId,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long position,\n        final String reason)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, replicationId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(offset + encodedLength, srcRecordingId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(offset + encodedLength, dstRecordingId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(offset + encodedLength, position, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodedLength += encodeStateChange(encodingBuffer, offset + encodedLength, from, to);\n\n        encodedLength += encodeTrailingString(encodingBuffer, offset + encodedLength,\n            captureLength + LOG_HEADER_LENGTH - encodedLength, reason);\n\n        return encodedLength;\n    }\n\n    static <E extends Enum<E>> int replicationSessionStateChangeLength(final E from, final E to, final String reason)\n    {\n        return stateTransitionStringLength(from, to) + (4 * SIZE_OF_LONG) + (SIZE_OF_INT + reason.length());\n    }\n\n    static <E extends Enum<E>> int encodeControlSessionStateChange(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final E from,\n        final E to,\n        final long id,\n        final String reason)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, id, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodedLength += encodeStateChange(encodingBuffer, offset + encodedLength, from, to);\n\n        encodedLength += encodeTrailingString(encodingBuffer, offset + encodedLength,\n            captureLength + LOG_HEADER_LENGTH - encodedLength, reason);\n\n        return encodedLength;\n    }\n\n    static <E extends Enum<E>> int sessionStateChangeLength(final E from, final E to, final String reason)\n    {\n        return stateTransitionStringLength(from, to) + SIZE_OF_LONG + (SIZE_OF_INT + reason.length());\n    }\n\n    static void encodeReplaySessionError(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final long sessionId,\n        final long recordingId,\n        final String errorMessage)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, sessionId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, recordingId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodeTrailingString(encodingBuffer, offset + encodedLength, captureLength - (SIZE_OF_INT * 2), errorMessage);\n    }\n\n    static void encodeCatalogResize(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final long catalogLength,\n        final long newCatalogLength)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, catalogLength, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, newCatalogLength, LITTLE_ENDIAN);\n    }\n\n    static int replicationSessionDoneLength()\n    {\n        return 8 * SIZE_OF_LONG + 3 * SIZE_OF_BYTE;\n    }\n\n    static void encodeReplicationSessionDone(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final long controlSessionId,\n        final long replicationId,\n        final long srcRecordingId,\n        final long replayPosition,\n        final long srcStopPosition,\n        final long dstRecordingId,\n        final long dstStopPosition,\n        final long position,\n        final boolean isClosed,\n        final boolean isEndOfStream,\n        final boolean isSynced)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, controlSessionId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(bodyOffset + bodyLength, replicationId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(bodyOffset + bodyLength, srcRecordingId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(bodyOffset + bodyLength, replayPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(bodyOffset + bodyLength, srcStopPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(bodyOffset + bodyLength, dstRecordingId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(bodyOffset + bodyLength, dstStopPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putLong(bodyOffset + bodyLength, position, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putByte(bodyOffset + bodyLength, (byte)(isClosed ? 1 : 0));\n        bodyLength += SIZE_OF_BYTE;\n        encodingBuffer.putByte(bodyOffset + bodyLength, (byte)(isEndOfStream ? 1 : 0));\n        bodyLength += SIZE_OF_BYTE;\n        encodingBuffer.putByte(bodyOffset + bodyLength, (byte)(isSynced ? 1 : 0));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ArchiveEventLogger.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.archive.codecs.MessageHeaderDecoder;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\n\nimport java.util.EnumSet;\n\nimport static io.aeron.agent.ArchiveEventCode.*;\nimport static io.aeron.agent.ArchiveEventEncoder.*;\nimport static io.aeron.agent.CommonEventEncoder.*;\nimport static io.aeron.agent.EventConfiguration.EVENT_RING_BUFFER;\nimport static java.util.EnumSet.complementOf;\nimport static java.util.EnumSet.of;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Event logger interface used by interceptors for recording events into a {@link RingBuffer} for an\n * {@link io.aeron.archive.Archive} for via a Java Agent.\n */\npublic final class ArchiveEventLogger\n{\n    /**\n     * Logger for writing into the {@link EventConfiguration#EVENT_RING_BUFFER}.\n     */\n    public static final ArchiveEventLogger LOGGER = new ArchiveEventLogger(EVENT_RING_BUFFER);\n\n    static final EnumSet<ArchiveEventCode> CONTROL_REQUEST_EVENTS = complementOf(of(\n        CMD_OUT_RESPONSE,\n        REPLICATION_SESSION_STATE_CHANGE,\n        CONTROL_SESSION_STATE_CHANGE,\n        REPLAY_SESSION_ERROR,\n        CATALOG_RESIZE,\n        RECORDING_SIGNAL,\n        REPLICATION_SESSION_DONE,\n        REPLAY_SESSION_STATE_CHANGE,\n        RECORDING_SESSION_STATE_CHANGE));\n\n    private final MessageHeaderDecoder headerDecoder = new MessageHeaderDecoder();\n    private final ManyToOneRingBuffer ringBuffer;\n\n    ArchiveEventLogger(final ManyToOneRingBuffer eventRingBuffer)\n    {\n        ringBuffer = eventRingBuffer;\n    }\n\n    /**\n     * Log in incoming control request to the archive.\n     *\n     * @param buffer containing the encoded request.\n     * @param offset in the buffer at which the request begins.\n     * @param length of the request in the buffer.\n     */\n    public void logControlRequest(final DirectBuffer buffer, final int offset, final int length)\n    {\n        headerDecoder.wrap(buffer, offset);\n\n        final int templateId = headerDecoder.templateId();\n        final ArchiveEventCode eventCode = getByTemplateId(templateId);\n        if (eventCode != null && ArchiveComponentLogger.ENABLED_EVENTS.contains(eventCode))\n        {\n            log(eventCode, buffer, offset, length);\n        }\n    }\n\n    /**\n     * Log an outgoing control response from the archive.\n     *\n     * @param buffer containing the encoded response.\n     * @param offset at which response message begins.\n     * @param length of the response in the buffer.\n     */\n    public void logControlResponse(final DirectBuffer buffer, final int offset, final int length)\n    {\n        log(CMD_OUT_RESPONSE, buffer, offset, length);\n    }\n\n    /**\n     * Log the {@link io.aeron.archive.codecs.RecordingSignal} being send.\n     *\n     * @param buffer containing the encoded response.\n     * @param offset at which response message begins.\n     * @param length of the response in the buffer.\n     */\n    public void logRecordingSignal(final DirectBuffer buffer, final int offset, final int length)\n    {\n        log(RECORDING_SIGNAL, buffer, offset, length);\n    }\n\n    /**\n     * Log a state change event for an archive replay session.\n     *\n     * @param <E>         type representing the state change.\n     * @param oldState    before the change.\n     * @param newState    after the change.\n     * @param sessionId   identity for the replay session on the Archive.\n     * @param recordingId recording id on the Archive.\n     * @param position    position of state change ({@link io.aeron.archive.client.AeronArchive#NULL_POSITION}\n     *                    if not relevant).\n     * @param reason      a string indicating the reason for the state change.\n     */\n    public <E extends Enum<E>> void logReplaySessionStateChange(\n        final E oldState,\n        final E newState,\n        final long sessionId,\n        final long recordingId,\n        final long position,\n        final String reason)\n    {\n        final int length = replaySessionStateChangeLength(oldState, newState, reason);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(REPLAY_SESSION_STATE_CHANGE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeReplaySessionStateChange(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    oldState,\n                    newState,\n                    sessionId,\n                    recordingId,\n                    position,\n                    reason);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a state change event for an archive recording session.\n     *\n     * @param <E>         type representing the state change.\n     * @param oldState    before the change.\n     * @param newState    after the change.\n     * @param recordingId recording id on the Archive.\n     * @param position    position of state change ({@link io.aeron.archive.client.AeronArchive#NULL_POSITION}\n     *                    if not relevant).\n     * @param reason      a string indicating the reason for the state change.\n     */\n    public <E extends Enum<E>> void logRecordingSessionStateChange(\n        final E oldState,\n        final E newState,\n        final long recordingId,\n        final long position,\n        final String reason)\n    {\n        final int length = recordingSessionStateChangeLength(oldState, newState, reason);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(RECORDING_SESSION_STATE_CHANGE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeRecordingSessionStateChange(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    oldState,\n                    newState,\n                    recordingId,\n                    position,\n                    reason);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a state change event for an archive replication session.\n     *\n     * @param <E>            type representing the state change.\n     * @param oldState       before the change.\n     * @param newState       after the change.\n     * @param replicationId  replication id on the Archive.\n     * @param srcRecordingId source recording id on the Archive.\n     * @param dstRecordingId destination recording id on the Archive.\n     * @param position       position of state change ({@link io.aeron.archive.client.AeronArchive#NULL_POSITION}\n     *                       if not relevant).\n     * @param reason         a string indicating the reason for the state change.\n     */\n    public <E extends Enum<E>> void logReplicationSessionStateChange(\n        final E oldState,\n        final E newState,\n        final long replicationId,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long position,\n        final String reason)\n    {\n        final int length = replicationSessionStateChangeLength(oldState, newState, reason);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(REPLICATION_SESSION_STATE_CHANGE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeReplicationSessionStateChange(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    oldState,\n                    newState,\n                    replicationId,\n                    srcRecordingId,\n                    dstRecordingId,\n                    position,\n                    reason);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a state change event for an archive control session.\n     *\n     * @param <E>              type representing the state change.\n     * @param oldState         before the change.\n     * @param newState         after the change.\n     * @param controlSessionId identity for the control session on the Archive.\n     * @param reason           a string indicating the reason for the state change.\n     */\n    public <E extends Enum<E>> void logControlSessionStateChange(\n        final E oldState,\n        final E newState,\n        final long controlSessionId,\n        final String reason)\n    {\n        final int length = sessionStateChangeLength(oldState, newState, reason);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(CONTROL_SESSION_STATE_CHANGE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeControlSessionStateChange(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    oldState,\n                    newState,\n                    controlSessionId,\n                    reason);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the replication session done event.\n     *\n     * @param controlSessionId identity for the control session on the Archive.\n     * @param replicationId    identity for the replication session.\n     * @param srcRecordingId   identity for the recording in the source Archive.\n     * @param replayPosition   position to start the replay from.\n     * @param srcStopPosition  stop position of the source recording.\n     * @param dstRecordingId   identity for the recording in the destination Archive.\n     * @param dstStopPosition  stop position of the destination recording.\n     * @param position         position of the replication when the session stopped.\n     * @param isClosed         is the source image closed.\n     * @param isEndOfStream    is the source image at the end of the stream.\n     * @param isSynced         has the destination recording position reached the stop position of the source\n     *                         recording.\n     */\n    public void logReplicationSessionDone(\n        final long controlSessionId,\n        final long replicationId,\n        final long srcRecordingId,\n        final long replayPosition,\n        final long srcStopPosition,\n        final long dstRecordingId,\n        final long dstStopPosition,\n        final long position,\n        final boolean isClosed,\n        final boolean isEndOfStream,\n        final boolean isSynced)\n    {\n        final int length = replicationSessionDoneLength();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(REPLICATION_SESSION_DONE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeReplicationSessionDone(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    controlSessionId,\n                    replicationId,\n                    srcRecordingId,\n                    replayPosition,\n                    srcStopPosition,\n                    dstRecordingId,\n                    dstStopPosition,\n                    position,\n                    isClosed,\n                    isEndOfStream,\n                    isSynced);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a control response error.\n     *\n     * @param sessionId    associated with the response.\n     * @param recordingId  to which the error applies.\n     * @param errorMessage which resulted.\n     */\n    public void logReplaySessionError(final long sessionId, final long recordingId, final String errorMessage)\n    {\n        final int length = SIZE_OF_LONG * 2 + SIZE_OF_INT + errorMessage.length();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(REPLAY_SESSION_ERROR.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeReplaySessionError(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    sessionId,\n                    recordingId,\n                    errorMessage);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a Catalog resize event.\n     *\n     * @param oldCatalogLength before the resize.\n     * @param newCatalogLength after the resize.\n     */\n    public void logCatalogResize(final long oldCatalogLength, final long newCatalogLength)\n    {\n        final int length = SIZE_OF_LONG * 2;\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(CATALOG_RESIZE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeCatalogResize(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    oldCatalogLength,\n                    newCatalogLength);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log an Archive control event.\n     *\n     * @param eventCode for the type of control event.\n     * @param buffer    containing the encoded event.\n     * @param offset    in the buffer at which the event begins.\n     * @param length    of the encoded event.\n     */\n    private void log(final ArchiveEventCode eventCode, final DirectBuffer buffer, final int offset, final int length)\n    {\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(eventCode.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encode((UnsafeBuffer)ringBuffer.buffer(), index, captureLength, length, buffer, offset);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ArchiveInterceptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport net.bytebuddy.asm.Advice;\n\nimport static io.aeron.agent.ArchiveEventLogger.LOGGER;\n\n/**\n * Intercepts calls in the archive which relate to state changes.\n */\nclass ArchiveInterceptor\n{\n    static class ReplaySessionStateChange\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void logStateChange(\n            final E oldState,\n            final E newState,\n            final long sessionId,\n            final long recordingId,\n            final long position,\n            final String reason)\n        {\n            LOGGER.logReplaySessionStateChange(\n                oldState, newState, sessionId, recordingId, position, reason);\n        }\n    }\n\n    static class RecordingSessionStateChange\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void logStateChange(\n            final E oldState,\n            final E newState,\n            final long recordingId,\n            final long position,\n            final String reason)\n        {\n            LOGGER.logRecordingSessionStateChange(oldState, newState, recordingId, position, reason);\n        }\n    }\n\n    static class ReplicationSessionStateChange\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void logStateChange(\n            final E oldState,\n            final E newState,\n            final long replicationId,\n            final long srcRecordingId,\n            final long dstRecordingId,\n            final long position,\n            final String reason)\n        {\n            LOGGER.logReplicationSessionStateChange(\n                oldState, newState, replicationId, srcRecordingId, dstRecordingId, position, reason);\n        }\n    }\n\n    static class ReplicationSessionDone\n    {\n        @Advice.OnMethodEnter\n        static void logReplicationSessionDone(\n            final long controlSessionId,\n            final long replicationId,\n            final long srcRecordingId,\n            final long replayPosition,\n            final long srcStopPosition,\n            final long dstRecordingId,\n            final long dstStopPosition,\n            final long position,\n            final boolean isClosed,\n            final boolean isEndOfStream,\n            final boolean isSynced)\n        {\n            LOGGER.logReplicationSessionDone(\n                controlSessionId,\n                replicationId,\n                srcRecordingId,\n                replayPosition,\n                srcStopPosition,\n                dstRecordingId,\n                dstStopPosition,\n                position,\n                isClosed,\n                isEndOfStream,\n                isSynced);\n        }\n    }\n\n    static class ControlSessionStateChange\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void logStateChange(\n            final E oldState, final E newState, final long controlSessionId, final String reason)\n        {\n            LOGGER.logControlSessionStateChange(oldState, newState, controlSessionId, reason);\n        }\n    }\n\n    static class ReplaySession\n    {\n        @Advice.OnMethodEnter\n        static void onPendingError(final long sessionId, final long recordingId, final String errorMessage)\n        {\n            LOGGER.logReplaySessionError(sessionId, recordingId, errorMessage);\n        }\n    }\n\n    static class Catalog\n    {\n        @Advice.OnMethodEnter\n        static void catalogResized(final long catalogLength, final long newCatalogLength)\n        {\n            LOGGER.logCatalogResize(catalogLength, newCatalogLength);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ChannelEndpointInterceptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.driver.media.ImageConnection;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.protocol.NakFlyweight;\nimport net.bytebuddy.asm.Advice;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.agent.DriverEventCode.*;\nimport static io.aeron.agent.DriverEventLogger.LOGGER;\n\nclass ChannelEndpointInterceptor\n{\n    static class SenderProxy\n    {\n        static class RegisterSendChannelEndpoint\n        {\n            @Advice.OnMethodEnter\n            static void registerSendChannelEndpoint(final SendChannelEndpoint channelEndpoint)\n            {\n                LOGGER.logString(SEND_CHANNEL_CREATION, channelEndpoint.udpChannel().description());\n            }\n        }\n\n        static class CloseSendChannelEndpoint\n        {\n            @Advice.OnMethodEnter\n            static void closeSendChannelEndpoint(final SendChannelEndpoint channelEndpoint)\n            {\n                LOGGER.logString(SEND_CHANNEL_CLOSE, channelEndpoint.udpChannel().description());\n            }\n        }\n    }\n\n    static class ReceiverProxy\n    {\n        static class RegisterReceiveChannelEndpoint\n        {\n            @Advice.OnMethodEnter\n            static void registerReceiveChannelEndpoint(final ReceiveChannelEndpoint channelEndpoint)\n            {\n                LOGGER.logString(RECEIVE_CHANNEL_CREATION, channelEndpoint.udpChannel().description());\n            }\n        }\n\n        static class CloseReceiveChannelEndpoint\n        {\n            @Advice.OnMethodEnter\n            static void closeReceiveChannelEndpoint(final ReceiveChannelEndpoint channelEndpoint)\n            {\n                LOGGER.logString(RECEIVE_CHANNEL_CLOSE, channelEndpoint.udpChannel().description());\n            }\n        }\n    }\n\n    static class UdpChannelTransport\n    {\n        static class SendHook\n        {\n            @Advice.OnMethodEnter\n            static void sendHook(final ByteBuffer buffer, final InetSocketAddress address)\n            {\n                LOGGER.logFrameOut(buffer, address);\n            }\n        }\n\n        static class ReceiveHook\n        {\n            @Advice.OnMethodEnter\n            static void receiveHook(final UnsafeBuffer buffer, final int length, final InetSocketAddress address)\n            {\n                LOGGER.logFrameIn(buffer, 0, length, address);\n            }\n        }\n\n        static class ResendHook\n        {\n            @Advice.OnMethodEnter\n            static void resendHook(\n                final int sessionId,\n                final int streamId,\n                final int termId,\n                final int termOffset,\n                final int length,\n                @Advice.This final Object thisObject)\n            {\n                final io.aeron.driver.media.UdpChannelTransport transport =\n                    (io.aeron.driver.media.UdpChannelTransport)thisObject;\n                LOGGER.logResend(\n                    sessionId, streamId, termId, termOffset, length, transport.udpChannel().originalUriString());\n            }\n        }\n    }\n\n    static class ReceiveChannelEndpointInterceptor\n    {\n        static class NakSent\n        {\n            @Advice.OnMethodEnter\n            static void sendNakMessage(\n                final ImageConnection[] connections,\n                final int sessionId,\n                final int streamId,\n                final int termId,\n                final int termOffset,\n                final int length,\n                @Advice.This final Object thisObject)\n            {\n                if (null == connections || null == thisObject)\n                {\n                    return;\n                }\n\n                final ReceiveChannelEndpoint endpoint = (ReceiveChannelEndpoint)thisObject;\n                final String channel = endpoint.originalUriString();\n                for (final ImageConnection connection : connections)\n                {\n                    if (null != connection)\n                    {\n                        LOGGER.logNakMessage(\n                            NAK_SENT,\n                            connection.controlAddress,\n                            sessionId,\n                            streamId,\n                            termId,\n                            termOffset,\n                            length,\n                            channel);\n                    }\n                }\n            }\n        }\n    }\n\n    static class SendChannelEndpointInterceptor\n    {\n        static class NakReceived\n        {\n            @Advice.OnMethodEnter\n            static void onNakMessage(\n                final NakFlyweight msg,\n                final UnsafeBuffer buffer,\n                final int length,\n                final InetSocketAddress srcAddress,\n                @Advice.This final Object thisObject)\n            {\n                if (null == thisObject)\n                {\n                    return;\n                }\n\n                final String channel = ((SendChannelEndpoint)thisObject).originalUriString();\n                LOGGER.logNakMessage(\n                    NAK_RECEIVED,\n                    srcAddress,\n                    msg.sessionId(),\n                    msg.streamId(),\n                    msg.termId(),\n                    msg.termOffset(),\n                    msg.length(),\n                    channel);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/CleanupInterceptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.driver.IpcPublication;\nimport io.aeron.driver.SubscriptionLink;\nimport io.aeron.driver.NetworkPublication;\nimport io.aeron.driver.PublicationImage;\nimport net.bytebuddy.asm.Advice;\n\nimport static io.aeron.agent.DriverEventLogger.LOGGER;\n\nclass CleanupInterceptor\n{\n    static class CleanupImage\n    {\n        @Advice.OnMethodEnter\n        static void cleanupImage(final PublicationImage image)\n        {\n            LOGGER.logImageRemoval(image.channel(), image.sessionId(), image.streamId(), image.correlationId());\n        }\n    }\n\n    static class CleanupPublication\n    {\n        @Advice.OnMethodEnter\n        static void cleanupPublication(final NetworkPublication publication)\n        {\n            LOGGER.logPublicationRemoval(publication.channel(), publication.sessionId(), publication.streamId());\n        }\n    }\n\n    static class CleanupIpcPublication\n    {\n        @Advice.OnMethodEnter\n        static void cleanupIpcPublication(final IpcPublication publication)\n        {\n            LOGGER.logPublicationRemoval(publication.channel(), publication.sessionId(), publication.streamId());\n        }\n    }\n\n    static class CleanupSubscriptionLink\n    {\n        @Advice.OnMethodEnter\n        static void cleanupSubscriptionLink(final SubscriptionLink link)\n        {\n            LOGGER.logSubscriptionRemoval(link.channel(), link.streamId(), link.registrationId());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ClusterComponentLogger.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.AeronCounters;\nimport io.aeron.version.Versioned;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport java.util.EnumSet;\nimport java.util.Map;\n\nimport static io.aeron.agent.ClusterEventCode.*;\nimport static io.aeron.agent.ConfigOption.DISABLED_CLUSTER_EVENT_CODES;\nimport static io.aeron.agent.ConfigOption.ENABLED_CLUSTER_EVENT_CODES;\nimport static io.aeron.agent.EventConfiguration.parseEventCodes;\nimport static net.bytebuddy.asm.Advice.to;\nimport static net.bytebuddy.matcher.ElementMatchers.nameEndsWith;\nimport static net.bytebuddy.matcher.ElementMatchers.named;\n\n/**\n * Implementation of a component logger for cluster log events.\n */\n@Versioned\npublic class ClusterComponentLogger implements ComponentLogger\n{\n    static final EnumSet<ClusterEventCode> ENABLED_EVENTS = EnumSet.noneOf(ClusterEventCode.class);\n\n    private static final Object2ObjectHashMap<String, EnumSet<ClusterEventCode>> SPECIAL_EVENTS =\n        new Object2ObjectHashMap<>();\n\n    static\n    {\n        SPECIAL_EVENTS.put(\"all\", EnumSet.allOf(ClusterEventCode.class));\n    }\n\n    /**\n     * Create a ClusterComponentLogger, used by Java Service API.\n     */\n    public ClusterComponentLogger()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int typeCode()\n    {\n        return EventCodeType.CLUSTER.getTypeCode();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void decode(\n        final MutableDirectBuffer buffer, final int offset, final int eventCodeId, final StringBuilder builder)\n    {\n        get(eventCodeId).decode(buffer, offset, builder);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public AgentBuilder addInstrumentation(final AgentBuilder agentBuilder, final Map<String, String> configOptions)\n    {\n        ENABLED_EVENTS.clear();\n        ENABLED_EVENTS.addAll(getClusterEventCodes(configOptions.get(ENABLED_CLUSTER_EVENT_CODES)));\n        ENABLED_EVENTS.removeAll(getClusterEventCodes(configOptions.get(DISABLED_CLUSTER_EVENT_CODES)));\n\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            ELECTION_STATE_CHANGE,\n            \"Election\",\n            ClusterInterceptor.ElectionStateChange.class,\n            \"logStateChange\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            TRUNCATE_LOG_ENTRY,\n            \"Election\",\n            ClusterInterceptor.TruncateLogEntry.class,\n            \"onTruncateLogEntry\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            CLUSTER_BACKUP_STATE_CHANGE,\n            \"ClusterBackupAgent\",\n            ClusterInterceptor.ClusterBackupStateChange.class,\n            \"logStateChange\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            CLUSTER_SESSION_STATE_CHANGE,\n            \"ClusterSession\",\n            ClusterInterceptor.ClusterSession.class,\n            \"logStateChange\");\n\n        tempBuilder = addClusterConsensusModuleAgentInstrumentation(tempBuilder);\n\n        return tempBuilder;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void reset()\n    {\n        ENABLED_EVENTS.clear();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String version()\n    {\n        return AeronCounters.formatVersionInfo(\n            ClusterComponentLoggerVersion.VERSION, ArchiveComponentLoggerVersion.GIT_SHA);\n    }\n\n    private static EnumSet<ClusterEventCode> getClusterEventCodes(final String enabledEventCodes)\n    {\n        return parseEventCodes(\n            ClusterEventCode.class,\n            enabledEventCodes,\n            SPECIAL_EVENTS,\n            ClusterEventCode::get,\n            ClusterEventCode::valueOf);\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private static AgentBuilder addClusterConsensusModuleAgentInstrumentation(final AgentBuilder agentBuilder)\n    {\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            REPLAY_NEW_LEADERSHIP_TERM,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.ReplayNewLeadershipTerm.class,\n            \"logOnReplayNewLeadershipTermEvent\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            APPEND_POSITION,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.AppendPosition.class,\n            \"logOnAppendPosition\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            COMMIT_POSITION,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.CommitPosition.class,\n            \"logOnCommitPosition\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            APPEND_SESSION_CLOSE,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.AppendSessionClose.class,\n            \"logAppendSessionClose\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            APPEND_SESSION_CLOSE,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.AppendSessionOpen.class,\n            \"logAppendSessionOpen\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            STATE_CHANGE,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.ConsensusModuleStateChange.class,\n            \"logStateChange\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            ROLE_CHANGE,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.ConsensusModuleRoleChange.class,\n            \"logRoleChange\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            NEW_LEADERSHIP_TERM,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.NewLeadershipTerm.class,\n            \"logOnNewLeadershipTerm\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            CANVASS_POSITION,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.CanvassPosition.class,\n            \"logOnCanvassPosition\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            REQUEST_VOTE,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.RequestVote.class,\n            \"logOnRequestVote\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            REQUEST_VOTE,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.Vote.class,\n            \"logOnVote\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            CATCHUP_POSITION,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.CatchupPosition.class,\n            \"logOnCatchupPosition\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            STOP_CATCHUP,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.StopCatchup.class,\n            \"logOnStopCatchup\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            TERMINATION_POSITION,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.TerminationPosition.class,\n            \"logOnTerminationPosition\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            TERMINATION_ACK,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.TerminationAck.class,\n            \"logOnTerminationAck\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            SERVICE_ACK,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.ServiceAck.class,\n            \"logOnServiceAck\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            REPLICATION_ENDED,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.ReplicationEnded.class,\n            \"logReplicationEnded\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            STANDBY_SNAPSHOT_NOTIFICATION,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.StandbySnapshotNotification.class,\n            \"logStandbySnapshotNotification\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            NEW_ELECTION,\n            \"ConsensusModuleAgent\",\n            ClusterInterceptor.NewElection.class,\n            \"logNewElection\");\n\n        return tempBuilder;\n    }\n\n    private static AgentBuilder addEventInstrumentation(\n        final AgentBuilder agentBuilder,\n        final ClusterEventCode code,\n        final String typeName,\n        final Class<?> interceptorClass,\n        final String interceptorMethod)\n    {\n        if (!ENABLED_EVENTS.contains(code))\n        {\n            return agentBuilder;\n        }\n\n        return agentBuilder\n            .type(nameEndsWith(typeName))\n            .transform((builder, typeDescription, classLoader, javaModule, protectionDomain) ->\n                builder.visit(to(interceptorClass).on(named(interceptorMethod))));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ClusterEventCode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport java.util.Arrays;\n\nimport static io.aeron.agent.ClusterEventDissector.dissectElectionStateChange;\nimport static io.aeron.agent.ClusterEventDissector.dissectNewLeadershipTerm;\n\n/**\n * Events that can be enabled for logging in the cluster module.\n */\npublic enum ClusterEventCode implements EventCode\n{\n    /**\n     * State change events within a cluster election.\n     */\n    ELECTION_STATE_CHANGE(1,\n        (eventCode, buffer, offset, builder) -> dissectElectionStateChange(buffer, offset, builder)),\n\n    /**\n     * A new term of leadership is to begin for an elected cluster member.\n     */\n    NEW_LEADERSHIP_TERM(2, (eventCode, buffer, offset, builder) -> dissectNewLeadershipTerm(buffer, offset, builder)),\n\n    /**\n     * State change in the cluster node consensus module.\n     */\n    STATE_CHANGE(3, ClusterEventDissector::dissectStateChange),\n\n    /**\n     * Role change for the cluster member.\n     */\n    ROLE_CHANGE(4, ClusterEventDissector::dissectStateChange),\n\n    /**\n     * A Canvass position event to notify the state of a member's log before nomination.\n     */\n    CANVASS_POSITION(5, ClusterEventDissector::dissectCanvassPosition),\n\n    /**\n     * A vote request for new leadership.\n     */\n    REQUEST_VOTE(6, ClusterEventDissector::dissectRequestVote),\n\n    /**\n     * Notification of a follower's catchup position.\n     */\n    CATCHUP_POSITION(7, ClusterEventDissector::dissectCatchupPosition),\n\n    /**\n     * A request to stop follower catchup.\n     */\n    STOP_CATCHUP(8, ClusterEventDissector::dissectStopCatchup),\n\n    /**\n     * Event when a RecordingLog entry is being truncated.\n     */\n    TRUNCATE_LOG_ENTRY(9, ClusterEventDissector::dissectTruncateLogEntry),\n\n    /**\n     * Event when a new leadership term is replayed.\n     */\n    REPLAY_NEW_LEADERSHIP_TERM(10, ClusterEventDissector::dissectReplayNewLeadershipTerm),\n\n    /**\n     * Event when an append position is received.\n     */\n    APPEND_POSITION(11, ClusterEventDissector::dissectAppendPosition),\n\n    /**\n     * Event when a commit position is received.\n     */\n    COMMIT_POSITION(12, ClusterEventDissector::dissectCommitPosition),\n\n    /**\n     * Event when a session is closed.\n     */\n    APPEND_SESSION_CLOSE(14, ClusterEventDissector::dissectAppendSessionClose),\n\n    /**\n     * Event when the DynamicJoin changes state (Unused).\n     */\n    DYNAMIC_JOIN_STATE_CHANGE_UNUSED(15, ClusterEventDissector::dissectNoOp),\n\n    /**\n     * Event when the ClusterBackup changes state.\n     */\n    CLUSTER_BACKUP_STATE_CHANGE(16, ClusterEventDissector::dissectStateChange),\n\n    /**\n     * Event when a node is instructed to terminate.\n     */\n    TERMINATION_POSITION(17, ClusterEventDissector::dissectTerminationPosition),\n\n    /**\n     * Event when a node acks the termination request.\n     */\n    TERMINATION_ACK(18, ClusterEventDissector::dissectTerminationAck),\n\n    /**\n     * Event when a nodes consensus module receives an ack from a service.\n     */\n    SERVICE_ACK(19, ClusterEventDissector::dissectServiceAck),\n\n    /**\n     * Event when a replication has ended.\n     */\n    REPLICATION_ENDED(20, ClusterEventDissector::dissectReplicationEnded),\n\n    /**\n     * Event when a standby snapshot notification has been received by a consensus module.\n     */\n    STANDBY_SNAPSHOT_NOTIFICATION(21, ClusterEventDissector::dissectStandbySnapshotNotification),\n\n    /**\n     * Event when a new Election is started.\n     *\n     * @since 1.44.0\n     */\n    NEW_ELECTION(22, ClusterEventDissector::dissectNewElection),\n\n    /**\n     * Event when a session is opened.\n     *\n     * @since 1.49.0\n     */\n    APPEND_SESSION_OPEN(23, ClusterEventDissector::dissectAppendSessionOpen),\n\n    /**\n     * Event for {@code ClusterSession} state changes.\n     *\n     * @since 1.49.0\n     */\n    CLUSTER_SESSION_STATE_CHANGE(24, ClusterEventDissector::dissectClusterSessionStateChange),\n\n    /**\n     * An actual vote for new leadership, i.e. response to the {@link #REQUEST_VOTE}.\n     *\n     * @since 1.50.0\n     */\n    VOTE(25, ClusterEventDissector::dissectVote);\n\n    static final int EVENT_CODE_TYPE = EventCodeType.CLUSTER.getTypeCode();\n    private static final ClusterEventCode[] EVENT_CODE_BY_ID;\n\n    private final int id;\n    private final DissectFunction<ClusterEventCode> dissector;\n\n    static\n    {\n        final ClusterEventCode[] codes = ClusterEventCode.values();\n        final int maxId = Arrays.stream(codes).mapToInt(ClusterEventCode::id).max().orElse(0);\n        EVENT_CODE_BY_ID = new ClusterEventCode[maxId + 1];\n\n        for (final ClusterEventCode code : codes)\n        {\n            final int id = code.id();\n            if (null != EVENT_CODE_BY_ID[id])\n            {\n                throw new IllegalArgumentException(\"id already in use: \" + id);\n            }\n\n            EVENT_CODE_BY_ID[id] = code;\n        }\n    }\n\n    ClusterEventCode(final int id, final DissectFunction<ClusterEventCode> dissector)\n    {\n        this.id = id;\n        this.dissector = dissector;\n    }\n\n    static ClusterEventCode get(final int id)\n    {\n        if (id < 0 || id >= EVENT_CODE_BY_ID.length)\n        {\n            throw new IllegalArgumentException(\"no ClusterEventCode for id: \" + id);\n        }\n\n        final ClusterEventCode code = EVENT_CODE_BY_ID[id];\n        if (null == code)\n        {\n            throw new IllegalArgumentException(\"no ClusterEventCode for id: \" + id);\n        }\n\n        return code;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int id()\n    {\n        return id;\n    }\n\n    /**\n     * Get {@link ClusterEventCode#id()} from {@link #id()}.\n     *\n     * @return get {@link ClusterEventCode#id()} from {@link #id()}.\n     */\n    public int toEventCodeId()\n    {\n        return EVENT_CODE_TYPE << 16 | (id & 0xFFFF);\n    }\n\n    /**\n     * Get {@link ClusterEventCode} from its event code id.\n     *\n     * @param eventCodeId to convert.\n     * @return {@link ClusterEventCode} from its event code id.\n     */\n    public static ClusterEventCode fromEventCodeId(final int eventCodeId)\n    {\n        return get(eventCodeId - (EVENT_CODE_TYPE << 16));\n    }\n\n    /**\n     * Decode an event serialised in a buffer to a provided {@link StringBuilder}.\n     *\n     * @param buffer  containing the encoded event.\n     * @param offset  offset at which the event begins.\n     * @param builder to write the decoded event to.\n     */\n    public void decode(final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        dissector.dissect(this, buffer, offset, builder);\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ClusterEventDissector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.protocol.HeaderFlyweight;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.SemanticVersion;\n\nimport static io.aeron.agent.ClusterEventCode.ELECTION_STATE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.NEW_LEADERSHIP_TERM;\nimport static io.aeron.agent.CommonEventDissector.dissectLogHeader;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.*;\n\nfinal class ClusterEventDissector\n{\n    static final String CONTEXT = \"CLUSTER\";\n\n    private ClusterEventDissector()\n    {\n    }\n\n    static void dissectNewLeadershipTerm(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, NEW_LEADERSHIP_TERM, buffer, absoluteOffset, builder);\n\n        final long logLeadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long nextLeadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long nextTermBaseLogPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long nextLogPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long termBaseLogPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long commitPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long leaderRecordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long timestamp = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        final int leaderId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        final int logSessionId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        final int appVersion = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        final boolean isStartup = 1 == buffer.getByte(absoluteOffset);\n\n        builder.append(\": memberId=\").append(memberId)\n            .append(\" logLeadershipTermId=\").append(logLeadershipTermId)\n            .append(\" nextLeadershipTermId=\").append(nextLeadershipTermId)\n            .append(\" nextTermBaseLogPosition=\").append(nextTermBaseLogPosition)\n            .append(\" nextLogPosition=\").append(nextLogPosition)\n            .append(\" leadershipTermId=\").append(leadershipTermId)\n            .append(\" termBaseLogPosition=\").append(termBaseLogPosition)\n            .append(\" logPosition=\").append(logPosition)\n            .append(\" commitPosition=\").append(commitPosition)\n            .append(\" leaderRecordingId=\").append(leaderRecordingId)\n            .append(\" timestamp=\").append(timestamp)\n            .append(\" leaderId=\").append(leaderId)\n            .append(\" logSessionId=\").append(logSessionId)\n            .append(\" appVersion=\");\n        appendSemanticVersion(appVersion, builder);\n        builder.append(\" isStartup=\").append(isStartup);\n    }\n\n    private static void appendSemanticVersion(final int version, final StringBuilder builder)\n    {\n        builder.append(SemanticVersion.major(version))\n            .append('.')\n            .append(SemanticVersion.minor(version))\n            .append('.')\n            .append(SemanticVersion.patch(version));\n    }\n\n    static void dissectStateChange(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(' ');\n        final int stateTransitionLength = buffer.getInt(absoluteOffset);\n        absoluteOffset += SIZE_OF_INT;\n        absoluteOffset += buffer.getStringWithoutLengthAscii(absoluteOffset, stateTransitionLength, builder);\n        builder.append(\" reason=\\\"\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append('\"');\n    }\n\n    static void dissectNoOp(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n    }\n\n    static void dissectElectionStateChange(\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, ELECTION_STATE_CHANGE, buffer, absoluteOffset, builder);\n\n        final long candidateTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logLeadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long appendPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long catchupPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int leaderId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId).append(' ');\n        absoluteOffset += SIZE_OF_INT + buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n\n        builder.append(\" leaderId=\").append(leaderId);\n        builder.append(\" candidateTermId=\").append(candidateTermId);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" logLeadershipTermId=\").append(logLeadershipTermId);\n        builder.append(\" appendPosition=\").append(appendPosition);\n        builder.append(\" catchupPosition=\").append(catchupPosition);\n        builder.append(\" reason=\\\"\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append('\"');\n    }\n\n    static void dissectCanvassPosition(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long logLeadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int followerMemberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int protocolVersion = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" logLeadershipTermId=\").append(logLeadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" followerMemberId=\").append(followerMemberId);\n        builder.append(\" protocolVersion=\");\n        appendSemanticVersion(protocolVersion, builder);\n    }\n\n    static void dissectRequestVote(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long logLeadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long candidateTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int candidateId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int protocolVersion = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" logLeadershipTermId=\").append(logLeadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" candidateTermId=\").append(candidateTermId);\n        builder.append(\" candidateId=\").append(candidateId);\n        builder.append(\" protocolVersion=\");\n        appendSemanticVersion(protocolVersion, builder);\n    }\n\n    static void dissectVote(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long logLeadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long candidateTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int candidateId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int voterId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final boolean vote = 0 != buffer.getByte(absoluteOffset);\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" logLeadershipTermId=\").append(logLeadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" candidateTermId=\").append(candidateTermId);\n        builder.append(\" candidateId=\").append(candidateId);\n        builder.append(\" voterId=\").append(voterId);\n        builder.append(\" vote=\").append(vote);\n    }\n\n    static void dissectCatchupPosition(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int followerMemberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int catchupEndpointLength = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" followerMemberId=\").append(followerMemberId);\n        builder.append(\" catchupEndpoint=\");\n        buffer.getStringWithoutLengthAscii(absoluteOffset, catchupEndpointLength, builder);\n    }\n\n    static void dissectStopCatchup(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int followerMemberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" followerMemberId=\").append(followerMemberId);\n    }\n\n    static void dissectTruncateLogEntry(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long logLeadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long candidateTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long commitPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long appendPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long oldPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long newPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" state=\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append(\" logLeadershipTermId=\").append(logLeadershipTermId);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" candidateTermId=\").append(candidateTermId);\n        builder.append(\" commitPosition=\").append(commitPosition);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" appendPosition=\").append(appendPosition);\n        builder.append(\" oldPosition=\").append(oldPosition);\n        builder.append(\" newPosition=\").append(newPosition);\n    }\n\n    static void dissectReplayNewLeadershipTerm(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long timestamp = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final long termBaseLogPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        final int appVersion = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        final boolean isInElection = 0 != buffer.getByte(absoluteOffset);\n        absoluteOffset += SIZE_OF_BYTE;\n\n        final int timeUnitLength = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" isInElection=\").append(isInElection);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" termBaseLogPosition=\").append(termBaseLogPosition);\n        builder.append(\" appVersion=\").append(appVersion);\n        builder.append(\" timestamp=\").append(timestamp);\n        builder.append(\" timeUnit=\");\n\n        buffer.getStringWithoutLengthAscii(absoluteOffset, timeUnitLength, builder);\n    }\n\n    static void dissectAppendPosition(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int followerMemberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final short flags = (short)(buffer.getByte(absoluteOffset) & 0xFF);\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" followerMemberId=\").append(followerMemberId);\n        builder.append(\" flags=0b\");\n        HeaderFlyweight.appendFlagsAsChars(flags, builder);\n    }\n\n    static void dissectCommitPosition(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int leaderId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" leaderId=\").append(leaderId);\n    }\n\n    static void dissectAppendSessionClose(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long sessionId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long timestamp = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" sessionId=\").append(sessionId);\n        builder.append(\" closeReason=\");\n        absoluteOffset += SIZE_OF_INT + buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" timestamp=\").append(timestamp);\n        builder.append(\" timeUnit=\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n    }\n\n    static void dissectAppendSessionOpen(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long sessionId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long timestamp = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" sessionId=\").append(sessionId);\n\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" timestamp=\").append(timestamp);\n        builder.append(\" timeUnit=\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n    }\n\n    static void dissectTerminationPosition(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long logLeadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long position = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" logLeadershipTermId=\").append(logLeadershipTermId);\n        builder.append(\" logPosition=\").append(position);\n    }\n\n    static void dissectTerminationAck(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long logLeadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long position = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int senderMemberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" logLeadershipTermId=\").append(logLeadershipTermId);\n        builder.append(\" logPosition=\").append(position);\n        builder.append(\" senderMemberId=\").append(senderMemberId);\n\n    }\n\n    static void dissectServiceAck(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long timestamp = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long ackId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long relevantId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final long serviceId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" timestamp=\").append(timestamp);\n        builder.append(\" timeUnit=\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append(\" ackId=\").append(ackId);\n        builder.append(\" relevantId=\").append(relevantId);\n        builder.append(\" serviceId=\").append(serviceId);\n    }\n\n    static void dissectReplicationEnded(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long srcRecordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long dstRecordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long position = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final boolean hasSynced = 1 == buffer.getByte(absoluteOffset);\n        absoluteOffset += SIZE_OF_BYTE;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" purpose=\");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" channel=\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n\n        builder.append(\" srcRecordingId=\").append(srcRecordingId);\n        builder.append(\" dstRecordingId=\").append(dstRecordingId);\n        builder.append(\" position=\").append(position);\n        builder.append(\" hasSynced=\").append(hasSynced);\n    }\n\n    static void dissectStandbySnapshotNotification(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long recordingId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long termBaseLeadershipPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long timestamp = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        final int serviceId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" recordingId=\").append(recordingId);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" termBaseLeadershipPosition=\").append(termBaseLeadershipPosition);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" timestamp=\").append(timestamp);\n        builder.append(\" timeUnit=\");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" serviceId=\").append(serviceId);\n        builder.append(\" archiveEndpoint=\");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n    }\n\n    static void dissectNewElection(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long leadershipTermId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long logPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final long appendPosition = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" leadershipTermId=\").append(leadershipTermId);\n        builder.append(\" logPosition=\").append(logPosition);\n        builder.append(\" appendPosition=\").append(appendPosition);\n        builder.append(\" reason=\\\"\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append('\"');\n    }\n\n    static void dissectClusterSessionStateChange(\n        final ClusterEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n\n        final long sessionId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n        final int memberId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\": memberId=\").append(memberId);\n        builder.append(\" sessionId=\").append(sessionId);\n\n        builder.append(\" action=\");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" \");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" reason=\\\"\");\n        buffer.getStringAscii(absoluteOffset, builder, LITTLE_ENDIAN);\n        builder.append('\"');\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ClusterEventEncoder.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.cluster.codecs.CloseReason;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.agent.CommonEventEncoder.LOG_HEADER_LENGTH;\nimport static io.aeron.agent.CommonEventEncoder.encodeLogHeader;\nimport static io.aeron.agent.CommonEventEncoder.encodeTrailingString;\nimport static io.aeron.agent.CommonEventEncoder.enumName;\nimport static io.aeron.agent.CommonEventEncoder.stateTransitionStringLength;\nimport static io.aeron.agent.CommonEventEncoder.trailingStringLength;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_BYTE;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Cluster event encoder.\n */\npublic final class ClusterEventEncoder\n{\n    static final int MAX_REASON_LENGTH = 300;\n\n    private ClusterEventEncoder()\n    {\n    }\n\n    static int encodeOnNewLeadershipTerm(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long logLeadershipTermId,\n        final long nextLeadershipTermId,\n        final long nextTermBaseLogPosition,\n        final long nextLogPosition,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long commitPosition,\n        final long leaderRecordingId,\n        final long timestamp,\n        final int leaderId,\n        final int logSessionId,\n        final int appVersion,\n        final boolean isStartup)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, logLeadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, nextLeadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, nextTermBaseLogPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, nextLogPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, leadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, termBaseLogPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, logPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, commitPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, leaderRecordingId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, timestamp, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(offset + encodedLength, memberId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, leaderId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, logSessionId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, appVersion, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putByte(offset + encodedLength, (byte)(isStartup ? 1 : 0));\n        encodedLength += SIZE_OF_BYTE;\n\n        return encodedLength;\n    }\n\n    static int newLeaderShipTermLength()\n    {\n        return (SIZE_OF_LONG * 10) + (SIZE_OF_INT * 4) + SIZE_OF_BYTE;\n    }\n\n    static <E extends Enum<E>> int encodeStateChange(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final E from,\n        final E to,\n        final String reason)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putInt(offset + encodedLength, memberId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodedLength += CommonEventEncoder.encodeStateChange(encodingBuffer, offset + encodedLength, from, to);\n\n        encodedLength += encodeTrailingString(encodingBuffer, offset + encodedLength,\n            captureLength + LOG_HEADER_LENGTH - encodedLength, reason);\n\n        return encodedLength;\n    }\n\n    static <E extends Enum<E>> int stateChangeLength(final E from, final E to, final String reason)\n    {\n        return stateTransitionStringLength(from, to) + SIZE_OF_INT + reason.length() + SIZE_OF_INT;\n    }\n\n    static <A extends Enum<A>, S extends Enum<S>> int clusterSessionStateChangeLength(\n        final A action, final S from, final S to, final String reason)\n    {\n        return SIZE_OF_LONG + SIZE_OF_INT + stateTransitionStringLength(from, to) + SIZE_OF_INT +\n            enumName(action).length() + SIZE_OF_INT + reason.length() + SIZE_OF_INT;\n    }\n\n    static <E extends Enum<E>> int encodeElectionStateChange(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final E from,\n        final E to,\n        final int leaderId,\n        final long candidateTermId,\n        final long leadershipTermId,\n        final long logPosition,\n        final long logLeadershipTermId,\n        final long appendPosition,\n        final long catchupPosition,\n        final String reason)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, candidateTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, leadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, logPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, logLeadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, appendPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, catchupPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(offset + encodedLength, memberId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, leaderId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodedLength += CommonEventEncoder.encodeStateChange(encodingBuffer, offset + encodedLength, from, to);\n\n        encodedLength += encodeTrailingString(\n            encodingBuffer, offset + encodedLength, captureLength + LOG_HEADER_LENGTH - encodedLength, reason);\n\n        return encodedLength;\n    }\n\n    static <E extends Enum<E>> int electionStateChangeLength(final E from, final E to, final String reason)\n    {\n        return (2 * SIZE_OF_INT) + (6 * SIZE_OF_LONG) + stateTransitionStringLength(from, to) +\n            trailingStringLength(reason, MAX_REASON_LENGTH);\n    }\n\n    static int encodeOnCanvassPosition(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long leadershipTermId,\n        final int followerMemberId,\n        final int protocolVersion)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, logLeadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, logPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, leadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(offset + encodedLength, followerMemberId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, protocolVersion, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, memberId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        return encodedLength;\n    }\n\n    static int canvassPositionLength()\n    {\n        return 3 * SIZE_OF_LONG + 3 * SIZE_OF_INT;\n    }\n\n    static int encodeOnRequestVote(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateId,\n        final int protocolVersion)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, logLeadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, logPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, candidateTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(offset + encodedLength, candidateId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, protocolVersion, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, memberId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        return encodedLength;\n    }\n\n    static int encodeOnVote(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long candidateTermId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final int candidateId,\n        final int voterId,\n        final boolean vote)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, logLeadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, logPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, candidateTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(offset + encodedLength, candidateId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, voterId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, memberId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putByte(offset + encodedLength, (byte)(vote ? 1 : 0));\n        encodedLength += SIZE_OF_BYTE;\n\n        return encodedLength;\n    }\n\n    static int encodeOnCatchupPosition(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final String catchupEndpoint)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, leadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, followerMemberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        bodyLength += encodeTrailingString(\n            encodingBuffer, bodyOffset + bodyLength, captureLength - bodyLength, catchupEndpoint);\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int catchupPositionLength(final String endpoint)\n    {\n        return 2 * SIZE_OF_LONG + 2 * SIZE_OF_INT + SIZE_OF_INT + endpoint.length();\n    }\n\n    static int encodeOnStopCatchup(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int length,\n        final int captureLength,\n        final int memberId,\n        final long leadershipTermId,\n        final int followerMemberId)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, leadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, followerMemberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static <E extends Enum<E>> int encodeTruncateLogEntry(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int length,\n        final int captureLength,\n        final int memberId,\n        final E state,\n        final long logLeadershipTermId,\n        final long leadershipTermId,\n        final long candidateTermId,\n        final long commitPosition,\n        final long logPosition,\n        final long appendPosition,\n        final long oldPosition,\n        final long newPosition)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, logLeadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, leadershipTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, candidateTermId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, commitPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, logPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, appendPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, oldPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(offset + encodedLength, newPosition, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(offset + encodedLength, memberId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodedLength += encodingBuffer.putStringAscii(offset + encodedLength, enumName(state), LITTLE_ENDIAN);\n\n        return encodedLength;\n    }\n\n    static int encodeOnReplayNewLeadershipTermEvent(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final boolean isInElection,\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final long termBaseLogPosition,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, leadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, timestamp, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, termBaseLogPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, appVersion, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putByte(bodyOffset + bodyLength, (byte)(isInElection ? 1 : 0));\n        bodyLength += SIZE_OF_BYTE;\n\n        final String unit = enumName(timeUnit);\n        encodingBuffer.putStringAscii(bodyOffset + bodyLength, unit, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT + unit.length();\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int replayNewLeadershipTermEventLength(final TimeUnit timeUnit)\n    {\n        return (2 * SIZE_OF_INT) + (4 * SIZE_OF_LONG) + SIZE_OF_BYTE + SIZE_OF_INT + timeUnit.name().length();\n    }\n\n    static int encodeOnAppendPosition(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final short flags)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, leadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, followerMemberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putByte(bodyOffset + bodyLength, (byte)flags);\n        bodyLength += SIZE_OF_BYTE;\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int encodeOnCommitPosition(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final int leaderId)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, leadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, leaderId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int appendSessionCloseLength(final CloseReason closeReason, final TimeUnit timeUnit)\n    {\n        return 3 * SIZE_OF_LONG + SIZE_OF_INT + (SIZE_OF_INT + enumName(closeReason).length()) +\n            (SIZE_OF_INT + enumName(timeUnit).length());\n    }\n\n    static int appendSessionOpenLength(final TimeUnit timeUnit)\n    {\n        return 4 * SIZE_OF_LONG + SIZE_OF_INT + (SIZE_OF_INT + enumName(timeUnit).length());\n    }\n\n    static int encodeAppendSessionClose(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long sessionId,\n        final CloseReason closeReason,\n        final long leadershipTermId,\n        final long timestamp,\n        final TimeUnit timeUnit)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, sessionId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, leadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, timestamp, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        bodyLength += encodingBuffer.putStringAscii(bodyOffset + bodyLength, enumName(closeReason), LITTLE_ENDIAN);\n\n        bodyLength += encodingBuffer.putStringAscii(bodyOffset + bodyLength, enumName(timeUnit), LITTLE_ENDIAN);\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int encodeAppendSessionOpen(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long sessionId,\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final TimeUnit timeUnit)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, sessionId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, leadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, timestamp, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        bodyLength += encodingBuffer.putStringAscii(bodyOffset + bodyLength, enumName(timeUnit), LITTLE_ENDIAN);\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int terminationPositionLength()\n    {\n        return SIZE_OF_INT + 2 * SIZE_OF_LONG;\n    }\n\n    static int encodeTerminationPosition(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logLeadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int terminationAckLength()\n    {\n        return 2 * SIZE_OF_LONG + 2 * SIZE_OF_INT;\n    }\n\n    static int encodeTerminationAck(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final int senderMemberId)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logLeadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, senderMemberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int serviceAckLength(final TimeUnit timeUnit)\n    {\n        return (2 * SIZE_OF_INT) + (4 * SIZE_OF_LONG) + (SIZE_OF_INT + enumName(timeUnit).length());\n    }\n\n    static int encodeServiceAck(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long logPosition,\n        final long timestamp,\n        final TimeUnit timeUnit,\n        final long ackId,\n        final long relevantId,\n        final int serviceId)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, timestamp, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, ackId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, relevantId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, serviceId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        bodyLength += encodingBuffer.putStringAscii(bodyOffset + bodyLength, enumName(timeUnit), LITTLE_ENDIAN);\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int replicationEndedLength(final String purpose, final String controlUri)\n    {\n        return (3 * SIZE_OF_LONG) + (SIZE_OF_INT) + (SIZE_OF_BYTE) + (SIZE_OF_INT + purpose.length()) +\n            (SIZE_OF_INT + controlUri.length());\n    }\n\n    static int encodeReplicationEnded(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final String purpose,\n        final String channel,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long position,\n        final boolean hasSynced)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, srcRecordingId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, dstRecordingId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, position, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putByte(bodyOffset + bodyLength, (byte)(hasSynced ? 1 : 0));\n        bodyLength += SIZE_OF_BYTE;\n\n        bodyLength += encodingBuffer.putStringAscii(bodyOffset + bodyLength, purpose, LITTLE_ENDIAN);\n        bodyLength += encodeTrailingString(\n            encodingBuffer, bodyOffset + bodyLength, captureLength - bodyLength, channel);\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int standbySnapshotNotificationLength(final TimeUnit timeUnit, final String archiveEndpoint)\n    {\n        return (5 * SIZE_OF_LONG) + (2 * SIZE_OF_LONG) +\n            (2 * SIZE_OF_INT) + timeUnit.name().length() + archiveEndpoint.length();\n    }\n\n    static int encodeStandbySnapshotNotification(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long recordingId,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long timestamp,\n        final TimeUnit timeUnit,\n        final int serviceId,\n        final String archiveEndpoint)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, recordingId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, leadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, termBaseLogPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, timestamp, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, serviceId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        bodyLength += encodingBuffer.putStringAscii(bodyOffset + bodyLength, timeUnit.name(), LITTLE_ENDIAN);\n        bodyLength += encodeTrailingString(\n            encodingBuffer, bodyOffset + bodyLength, captureLength - bodyLength, archiveEndpoint);\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static int newElectionLength(final String reason)\n    {\n        return (3 * SIZE_OF_LONG) + SIZE_OF_INT + trailingStringLength(reason, 300);\n    }\n\n    static int encodeNewElection(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final long appendPosition,\n        final String reason)\n    {\n        final int logHeaderLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + logHeaderLength;\n        int bodyLength = 0;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, leadershipTermId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, logPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putLong(bodyOffset + bodyLength, appendPosition, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, memberId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n\n        encodeTrailingString(encodingBuffer, bodyOffset + bodyLength, captureLength - bodyLength, reason);\n\n        return logHeaderLength + bodyLength;\n    }\n\n    static <A extends Enum<A>, S extends Enum<S>> void encodeClusterSessionStateChange(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final int memberId,\n        final long sessionId,\n        final A action,\n        final S from,\n        final S to,\n        final String reason)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, sessionId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(offset + encodedLength, memberId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodedLength += encodingBuffer.putStringAscii(offset + encodedLength, enumName(action), LITTLE_ENDIAN);\n\n        encodedLength += CommonEventEncoder.encodeStateChange(encodingBuffer, offset + encodedLength, from, to);\n\n        encodeTrailingString(\n            encodingBuffer, offset + encodedLength, captureLength + LOG_HEADER_LENGTH - encodedLength, reason);\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ClusterEventLogger.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.codecs.CloseReason;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.agent.ClusterEventCode.APPEND_POSITION;\nimport static io.aeron.agent.ClusterEventCode.APPEND_SESSION_CLOSE;\nimport static io.aeron.agent.ClusterEventCode.APPEND_SESSION_OPEN;\nimport static io.aeron.agent.ClusterEventCode.CANVASS_POSITION;\nimport static io.aeron.agent.ClusterEventCode.CATCHUP_POSITION;\nimport static io.aeron.agent.ClusterEventCode.CLUSTER_SESSION_STATE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.COMMIT_POSITION;\nimport static io.aeron.agent.ClusterEventCode.ELECTION_STATE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.NEW_ELECTION;\nimport static io.aeron.agent.ClusterEventCode.NEW_LEADERSHIP_TERM;\nimport static io.aeron.agent.ClusterEventCode.REPLAY_NEW_LEADERSHIP_TERM;\nimport static io.aeron.agent.ClusterEventCode.REPLICATION_ENDED;\nimport static io.aeron.agent.ClusterEventCode.REQUEST_VOTE;\nimport static io.aeron.agent.ClusterEventCode.SERVICE_ACK;\nimport static io.aeron.agent.ClusterEventCode.STANDBY_SNAPSHOT_NOTIFICATION;\nimport static io.aeron.agent.ClusterEventCode.STOP_CATCHUP;\nimport static io.aeron.agent.ClusterEventCode.TERMINATION_ACK;\nimport static io.aeron.agent.ClusterEventCode.TERMINATION_POSITION;\nimport static io.aeron.agent.ClusterEventCode.TRUNCATE_LOG_ENTRY;\nimport static io.aeron.agent.ClusterEventCode.VOTE;\nimport static io.aeron.agent.ClusterEventEncoder.appendSessionCloseLength;\nimport static io.aeron.agent.ClusterEventEncoder.appendSessionOpenLength;\nimport static io.aeron.agent.ClusterEventEncoder.canvassPositionLength;\nimport static io.aeron.agent.ClusterEventEncoder.catchupPositionLength;\nimport static io.aeron.agent.ClusterEventEncoder.clusterSessionStateChangeLength;\nimport static io.aeron.agent.ClusterEventEncoder.electionStateChangeLength;\nimport static io.aeron.agent.ClusterEventEncoder.encodeClusterSessionStateChange;\nimport static io.aeron.agent.ClusterEventEncoder.encodeElectionStateChange;\nimport static io.aeron.agent.ClusterEventEncoder.encodeOnCanvassPosition;\nimport static io.aeron.agent.ClusterEventEncoder.encodeOnCatchupPosition;\nimport static io.aeron.agent.ClusterEventEncoder.encodeOnNewLeadershipTerm;\nimport static io.aeron.agent.ClusterEventEncoder.encodeOnReplayNewLeadershipTermEvent;\nimport static io.aeron.agent.ClusterEventEncoder.encodeOnRequestVote;\nimport static io.aeron.agent.ClusterEventEncoder.encodeOnStopCatchup;\nimport static io.aeron.agent.ClusterEventEncoder.encodeOnVote;\nimport static io.aeron.agent.ClusterEventEncoder.encodeStateChange;\nimport static io.aeron.agent.ClusterEventEncoder.encodeTruncateLogEntry;\nimport static io.aeron.agent.ClusterEventEncoder.newLeaderShipTermLength;\nimport static io.aeron.agent.ClusterEventEncoder.replayNewLeadershipTermEventLength;\nimport static io.aeron.agent.ClusterEventEncoder.stateChangeLength;\nimport static io.aeron.agent.ClusterEventEncoder.terminationPositionLength;\nimport static io.aeron.agent.CommonEventEncoder.captureLength;\nimport static io.aeron.agent.CommonEventEncoder.encodedLength;\nimport static io.aeron.agent.CommonEventEncoder.enumName;\nimport static io.aeron.agent.EventConfiguration.EVENT_RING_BUFFER;\nimport static org.agrona.BitUtil.SIZE_OF_BYTE;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Event logger interface used by interceptors for recording cluster events into a {@link RingBuffer} for a\n * {@link ConsensusModule} events via a Java Agent.\n */\npublic final class ClusterEventLogger\n{\n    /**\n     * Logger for writing into the {@link EventConfiguration#EVENT_RING_BUFFER}.\n     */\n    public static final ClusterEventLogger LOGGER = new ClusterEventLogger(EVENT_RING_BUFFER);\n\n    private final ManyToOneRingBuffer ringBuffer;\n\n    ClusterEventLogger(final ManyToOneRingBuffer eventRingBuffer)\n    {\n        ringBuffer = eventRingBuffer;\n    }\n\n    /**\n     * Log a new leadership term event.\n     *\n     * @param memberId                of the current cluster node.\n     * @param logLeadershipTermId     term for which log entries are present.\n     * @param nextLeadershipTermId    next term relative to the logLeadershipTermId\n     * @param nextTermBaseLogPosition base log position for the next term.\n     * @param nextLogPosition         committed log position for next term.\n     * @param leadershipTermId        new leadership term id.\n     * @param termBaseLogPosition     position the log reached at base of new term.\n     * @param logPosition             position the log reached for the new term (i.e. appendPosition of the leader node).\n     * @param commitPosition          of the Cluster, i.e. quorum log position.\n     * @param leaderRecordingId       of the log in the leader archive.\n     * @param timestamp               of the new term.\n     * @param leaderId                member id for the new leader.\n     * @param logSessionId            session id of the log extension.\n     * @param appVersion              associated with the recorded state.\n     * @param isStartup               is the leader starting up fresh.\n     */\n    public void logOnNewLeadershipTerm(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long nextLeadershipTermId,\n        final long nextTermBaseLogPosition,\n        final long nextLogPosition,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long commitPosition,\n        final long leaderRecordingId,\n        final long timestamp,\n        final int leaderId,\n        final int logSessionId,\n        final int appVersion,\n        final boolean isStartup)\n    {\n        final int length = newLeaderShipTermLength();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(NEW_LEADERSHIP_TERM.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeOnNewLeadershipTerm(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    logLeadershipTermId,\n                    nextLeadershipTermId,\n                    nextTermBaseLogPosition,\n                    nextLogPosition,\n                    leadershipTermId,\n                    termBaseLogPosition,\n                    logPosition,\n                    commitPosition,\n                    leaderRecordingId,\n                    timestamp,\n                    leaderId,\n                    logSessionId,\n                    appVersion,\n                    isStartup);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a state change event for a cluster node.\n     *\n     * @param <E>       type representing the state change.\n     * @param eventCode for the type of state change.\n     * @param memberId  of the current cluster node.\n     * @param oldState  before the change.\n     * @param newState  after the change.\n     * @param reason for state to change.\n     */\n    public <E extends Enum<E>> void logStateChange(\n        final ClusterEventCode eventCode, final int memberId, final E oldState, final E newState, final String reason)\n    {\n        final int length = stateChangeLength(oldState, newState, reason);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(eventCode.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeStateChange(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    oldState,\n                    newState,\n                    reason);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log an election state change event for a cluster node.\n     *\n     * @param <E>                 type representing the state change.\n     * @param memberId            on which the change has taken place.\n     * @param oldState            before the change.\n     * @param newState            after the change.\n     * @param leaderId            of the cluster.\n     * @param candidateTermId     of the node.\n     * @param leadershipTermId    of the node.\n     * @param logPosition         of the node.\n     * @param logLeadershipTermId of the node.\n     * @param appendPosition      of the node.\n     * @param catchupPosition     of the node.\n     * @param reason              for the state transition to occur.\n     */\n    public <E extends Enum<E>> void logElectionStateChange(\n        final int memberId,\n        final E oldState,\n        final E newState,\n        final int leaderId,\n        final long candidateTermId,\n        final long leadershipTermId,\n        final long logPosition,\n        final long logLeadershipTermId,\n        final long appendPosition,\n        final long catchupPosition,\n        final String reason)\n    {\n        final int length = electionStateChangeLength(oldState, newState, reason);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(ELECTION_STATE_CHANGE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeElectionStateChange(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    oldState,\n                    newState,\n                    leaderId,\n                    candidateTermId,\n                    leadershipTermId,\n                    logPosition,\n                    logLeadershipTermId,\n                    appendPosition,\n                    catchupPosition,\n                    reason);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a canvass position event received by the cluster node.\n     *\n     * @param memberId            member who sent the event.\n     * @param logLeadershipTermId leadershipTermId reached by the member for it recorded log.\n     * @param logPosition         position the member has durably recorded.\n     * @param leadershipTermId    the most current leadershipTermId a member has seen.\n     * @param followerMemberId    follower node id.\n     * @param protocolVersion     of the consensus module.\n     */\n    public void logOnCanvassPosition(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long leadershipTermId,\n        final int followerMemberId,\n        final int protocolVersion)\n    {\n        final int length = canvassPositionLength();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(CANVASS_POSITION.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeOnCanvassPosition(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    logLeadershipTermId,\n                    logPosition,\n                    leadershipTermId,\n                    followerMemberId,\n                    protocolVersion);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a request to vote from a cluster candidate for new leadership.\n     *\n     * @param memberId            of the current cluster node.\n     * @param logLeadershipTermId leadershipTermId processes from the log by the candidate.\n     * @param logPosition         position reached in the log for the latest leadership term.\n     * @param candidateTermId     the term id as the candidate sees it for the election.\n     * @param candidateId         id of the candidate node.\n     * @param protocolVersion     from the request.\n     */\n    public void logOnRequestVote(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateId,\n        final int protocolVersion)\n    {\n        final int length = 3 * SIZE_OF_LONG + 3 * SIZE_OF_INT;\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(REQUEST_VOTE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeOnRequestVote(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    logLeadershipTermId,\n                    logPosition,\n                    candidateTermId,\n                    candidateId,\n                    protocolVersion);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a vote response from a cluster candidate for new leadership.\n     *\n     * @param memberId            of the current cluster node.\n     * @param logLeadershipTermId leadershipTermId processes from the log by the candidate.\n     * @param logPosition         position reached in the log for the latest leadership term.\n     * @param candidateTermId     the term id as the candidate sees it for the election.\n     * @param candidateId         id of the candidate node.\n     * @param voterId             id of the follower node that voted.\n     * @param vote                expressed by the follower node.\n     */\n    public void logOnVote(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateId,\n        final int voterId,\n        final boolean vote)\n    {\n        final int length = 3 * SIZE_OF_LONG + 3 * SIZE_OF_INT + SIZE_OF_BYTE;\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(VOTE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeOnVote(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    candidateTermId,\n                    logLeadershipTermId,\n                    logPosition,\n                    candidateId,\n                    voterId,\n                    vote);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the catchup position message.\n     *\n     * @param memberId         of the current cluster node.\n     * @param leadershipTermId leadership term to catch up on\n     * @param logPosition      position to catchup from\n     * @param followerMemberId the id of the follower that is catching up\n     * @param catchupEndpoint  the endpoint to send catchup messages\n     */\n    public void logOnCatchupPosition(\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final String catchupEndpoint)\n    {\n        final int length = catchupPositionLength(catchupEndpoint);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(CATCHUP_POSITION.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeOnCatchupPosition(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    leadershipTermId,\n                    logPosition,\n                    followerMemberId,\n                    catchupEndpoint);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the stop catchup message.\n     *\n     * @param memberId         of the current cluster node.\n     * @param leadershipTermId current leadershipTermId.\n     * @param followerMemberId id of follower currently catching up.\n     */\n    public void logOnStopCatchup(\n        final int memberId, final long leadershipTermId, final int followerMemberId)\n    {\n        final int length = SIZE_OF_LONG + 2 * SIZE_OF_INT;\n        final int encodedLength = encodedLength(length);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(STOP_CATCHUP.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeOnStopCatchup(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    length,\n                    length,\n                    memberId,\n                    leadershipTermId,\n                    followerMemberId);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log an event when a log entry is being truncated.\n     *\n     * @param <E>                 type of the enum.\n     * @param memberId            the node which truncates its log entry.\n     * @param state               of the election.\n     * @param logLeadershipTermId the election is in.\n     * @param leadershipTermId    the election is in.\n     * @param candidateTermId     the election is in.\n     * @param commitPosition      when the truncation happens.\n     * @param logPosition         of the election.\n     * @param appendPosition      of the election.\n     * @param oldPosition         truncated from.\n     * @param newPosition         truncated to.\n     */\n    public <E extends Enum<E>> void logOnTruncateLogEntry(\n        final int memberId,\n        final E state,\n        final long logLeadershipTermId,\n        final long leadershipTermId,\n        final long candidateTermId,\n        final long commitPosition,\n        final long logPosition,\n        final long appendPosition,\n        final long oldPosition,\n        final long newPosition)\n    {\n        final int length = SIZE_OF_INT + enumName(state).length() + SIZE_OF_INT + 8 * SIZE_OF_LONG;\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(TRUNCATE_LOG_ENTRY.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeTruncateLogEntry(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    state,\n                    logLeadershipTermId,\n                    leadershipTermId,\n                    candidateTermId,\n                    commitPosition,\n                    logPosition,\n                    appendPosition,\n                    oldPosition,\n                    newPosition);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the replay of the leadership term id.\n     *\n     * @param memberId            current memberId.\n     * @param isInElection        an election is currently in process.\n     * @param leadershipTermId    the logged leadership term id.\n     * @param logPosition         current position in the log.\n     * @param timestamp           logged timestamp.\n     * @param termBaseLogPosition initial position for this term.\n     * @param timeUnit            cluster time unit.\n     * @param appVersion          version of the application.\n     */\n    public void logOnReplayNewLeadershipTermEvent(\n        final int memberId,\n        final boolean isInElection,\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final long termBaseLogPosition,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n        final int length = replayNewLeadershipTermEventLength(timeUnit);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(REPLAY_NEW_LEADERSHIP_TERM.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeOnReplayNewLeadershipTermEvent(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    isInElection,\n                    leadershipTermId,\n                    logPosition,\n                    timestamp,\n                    termBaseLogPosition,\n                    timeUnit,\n                    appVersion);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * The Append position received by the leader from a follower.\n     *\n     * @param memberId         of the current cluster node.\n     * @param leadershipTermId the current leadership term id.\n     * @param logPosition      the current position in the log.\n     * @param followerMemberId follower member sending the Append position.\n     * @param flags            applied to append position by follower.\n     */\n    public void logOnAppendPosition(\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final short flags)\n    {\n        final int length = 2 * SIZE_OF_LONG + 2 * SIZE_OF_INT + SIZE_OF_BYTE;\n        final int encodedLength = encodedLength(length);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(APPEND_POSITION.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeOnAppendPosition(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    length,\n                    length,\n                    memberId,\n                    leadershipTermId,\n                    logPosition,\n                    followerMemberId,\n                    flags);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * The commit position received by the follower form the leader.\n     *\n     * @param memberId         of the node receiving commit position message.\n     * @param leadershipTermId the current leadership term id.\n     * @param logPosition      the current position in the log.\n     * @param leaderId         leader member sending the commit position.\n     */\n    public void logOnCommitPosition(\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final int leaderId)\n    {\n        final int length = 2 * SIZE_OF_LONG + 2 * SIZE_OF_INT;\n        final int encodedLength = encodedLength(length);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(COMMIT_POSITION.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeOnCommitPosition(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    length,\n                    length,\n                    memberId,\n                    leadershipTermId,\n                    logPosition,\n                    leaderId);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the appending of a session close event to the log.\n     *\n     * @param memberId         member (leader) publishing the event.\n     * @param sessionId        session id of the session be closed.\n     * @param closeReason      reason to close the session.\n     * @param leadershipTermId current leadership term id.\n     * @param timestamp        the current timestamp.\n     * @param timeUnit         units for the timestamp.\n     */\n    public void logAppendSessionClose(\n        final int memberId,\n        final long sessionId,\n        final CloseReason closeReason,\n        final long leadershipTermId,\n        final long timestamp,\n        final TimeUnit timeUnit)\n    {\n        final int length = appendSessionCloseLength(closeReason, timeUnit);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(APPEND_SESSION_CLOSE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeAppendSessionClose(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    sessionId,\n                    closeReason,\n                    leadershipTermId,\n                    timestamp,\n                    timeUnit);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the appending of a session open event to the log.\n     *\n     * @param memberId         member (leader) publishing the event.\n     * @param sessionId        session id of the session be closed.\n     * @param leadershipTermId current leadership term id.\n     * @param logPosition      when session was opened.\n     * @param timestamp        the current timestamp.\n     * @param timeUnit         units for the timestamp.\n     */\n    public void logAppendSessionOpen(\n        final int memberId,\n        final long sessionId,\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final TimeUnit timeUnit)\n    {\n        final int length = appendSessionOpenLength(timeUnit);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(APPEND_SESSION_OPEN.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeAppendSessionOpen(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    sessionId,\n                    leadershipTermId,\n                    logPosition,\n                    timestamp,\n                    timeUnit);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the receiving of a termination position event.\n     *\n     * @param memberId            that received the termination position.\n     * @param logLeadershipTermId leadership term for the supplied position.\n     * @param logPosition         position to terminate at.\n     */\n    public void logTerminationPosition(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition)\n    {\n        final int length = terminationPositionLength();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(TERMINATION_POSITION.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeTerminationPosition(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    logLeadershipTermId,\n                    logPosition);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the receiving of an acknowledgement to a termination position event.\n     *\n     * @param memberId            that received the termination ack.\n     * @param logLeadershipTermId leadership term for the supplied position.\n     * @param logPosition         position to terminate at.\n     * @param senderMemberId      member sending the ack.\n     */\n    public void logTerminationAck(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final int senderMemberId)\n    {\n        final int length = ClusterEventEncoder.terminationAckLength();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(TERMINATION_ACK.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeTerminationAck(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    logLeadershipTermId,\n                    logPosition,\n                    senderMemberId);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log an ack received from a cluster service.\n     *\n     * @param memberId    memberId receiving the ack.\n     * @param logPosition position in the log when the ack was sent.\n     * @param timestamp   timestamp when the ack was sent.\n     * @param timeUnit    time unit used for the timestamp.\n     * @param ackId       id of the ack.\n     * @param relevantId  associated id used in the ack, e.g. recordingId for snapshot acks.\n     * @param serviceId   the id of the service that sent the ack.\n     */\n    public void logServiceAck(\n        final int memberId,\n        final long logPosition,\n        final long timestamp,\n        final TimeUnit timeUnit,\n        final long ackId,\n        final long relevantId,\n        final int serviceId)\n    {\n        final int length = ClusterEventEncoder.serviceAckLength(timeUnit);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(SERVICE_ACK.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeServiceAck(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    logPosition,\n                    timestamp,\n                    timeUnit,\n                    ackId,\n                    relevantId,\n                    serviceId);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a replication end event.\n     *\n     * @param memberId       memberId running the replication.\n     * @param purpose        the reason for the replication.\n     * @param channel        the channel used to connect to the source archive.\n     * @param srcRecordingId source recording id.\n     * @param dstRecordingId destination recording id.\n     * @param position       the position where the recording ended.\n     * @param hasSynced      was the sync event been received for the replication.\n     */\n    public void logReplicationEnded(\n        final int memberId,\n        final String purpose,\n        final String channel,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long position,\n        final boolean hasSynced)\n    {\n        final int length = ClusterEventEncoder.replicationEndedLength(purpose, channel);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(REPLICATION_ENDED.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeReplicationEnded(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    purpose,\n                    channel,\n                    srcRecordingId,\n                    dstRecordingId,\n                    position,\n                    hasSynced);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a standby snapshot notification.\n     *\n     * @param memberId            memberId receiving the notification.\n     * @param recordingId         the recording id of the standby snapshot in the remote archive.\n     * @param leadershipTermId    the leadershipTermId of the standby snapshot.\n     * @param termBaseLogPosition the termBaseLogPosition of the standby snapshot.\n     * @param logPosition         the position of the standby snapshot when it is taken.\n     * @param timestamp           the cluster timestamp when the snapshot is taken.\n     * @param timeUnit            the cluster time unit.\n     * @param serviceId           the serviceId for the snapshot.\n     * @param archiveEndpoint     the endpoint holding the standby snapshot.\n     */\n    public void logStandbySnapshotNotification(\n        final int memberId,\n        final long recordingId,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long timestamp,\n        final TimeUnit timeUnit,\n        final int serviceId,\n        final String archiveEndpoint)\n    {\n        final int length = ClusterEventEncoder.standbySnapshotNotificationLength(timeUnit, archiveEndpoint);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(STANDBY_SNAPSHOT_NOTIFICATION.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeStandbySnapshotNotification(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    recordingId,\n                    leadershipTermId,\n                    termBaseLogPosition,\n                    logPosition,\n                    timestamp,\n                    timeUnit,\n                    serviceId,\n                    archiveEndpoint);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the start of the new election.\n     *\n     * @param memberId         memberId which start the election.\n     * @param leadershipTermId of the member.\n     * @param logPosition      the log position.\n     * @param appendPosition   the append position.\n     * @param reason           for election to be started.\n     */\n    public void logNewElection(\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final long appendPosition,\n        final String reason)\n    {\n        final int length = ClusterEventEncoder.newElectionLength(reason);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(NEW_ELECTION.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                ClusterEventEncoder.encodeNewElection(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    leadershipTermId,\n                    logPosition,\n                    appendPosition,\n                    reason);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a state change event for a cluster session.\n     *\n     * @param <A>       type representing the action.\n     * @param <S>       type representing the state change.\n     * @param memberId  of the current cluster node.\n     * @param sessionId of the session.\n     * @param action    action.\n     * @param oldState  before the change.\n     * @param newState  after the change.\n     * @param reason    for the change.\n     */\n    public <A extends Enum<A>, S extends Enum<S>> void logClusterSessionStateChange(\n        final int memberId,\n        final long sessionId,\n        final A action,\n        final S oldState,\n        final S newState,\n        final String reason)\n    {\n        final int length = clusterSessionStateChangeLength(action, oldState, newState, reason);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(CLUSTER_SESSION_STATE_CHANGE.toEventCodeId(), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeClusterSessionStateChange(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    memberId,\n                    sessionId,\n                    action,\n                    oldState,\n                    newState,\n                    reason);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ClusterInterceptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.Aeron;\nimport io.aeron.cluster.codecs.CloseReason;\nimport net.bytebuddy.asm.Advice;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.agent.ClusterEventCode.*;\nimport static io.aeron.agent.ClusterEventLogger.LOGGER;\n\nclass ClusterInterceptor\n{\n    static class ElectionStateChange\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void logStateChange(\n            final int memberId,\n            final E oldState,\n            final E newState,\n            final int leaderId,\n            final long candidateTermId,\n            final long leadershipTermId,\n            final long logPosition,\n            final long logLeadershipTermId,\n            final long appendPosition,\n            final long catchupPosition,\n            final String reason)\n        {\n            LOGGER.logElectionStateChange(\n                memberId,\n                oldState,\n                newState,\n                leaderId,\n                candidateTermId,\n                leadershipTermId,\n                logPosition,\n                logLeadershipTermId,\n                appendPosition,\n                catchupPosition,\n                reason);\n        }\n    }\n\n    static class NewLeadershipTerm\n    {\n        @Advice.OnMethodEnter\n        static void logOnNewLeadershipTerm(\n            final int memberId,\n            final long logLeadershipTermId,\n            final long nextLeadershipTermId,\n            final long nextTermBaseLogPosition,\n            final long nextLogPosition,\n            final long leadershipTermId,\n            final long termBaseLogPosition,\n            final long logPosition,\n            final long commitPosition,\n            final long leaderRecordingId,\n            final long timestamp,\n            final int leaderId,\n            final int logSessionId,\n            final int appVersion,\n            final boolean isStartup)\n        {\n            LOGGER.logOnNewLeadershipTerm(\n                memberId,\n                logLeadershipTermId,\n                nextLeadershipTermId,\n                nextTermBaseLogPosition,\n                nextLogPosition,\n                leadershipTermId,\n                termBaseLogPosition,\n                logPosition,\n                commitPosition,\n                leaderRecordingId,\n                timestamp,\n                leaderId,\n                logSessionId,\n                appVersion,\n                isStartup);\n        }\n    }\n\n    static class ConsensusModuleStateChange\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void logStateChange(\n            final int memberId, final E oldState, final E newState, final String reason)\n        {\n            LOGGER.logStateChange(STATE_CHANGE, memberId, oldState, newState, reason);\n        }\n    }\n\n    static class ConsensusModuleRoleChange\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void logRoleChange(final int memberId, final E oldRole, final E newRole)\n        {\n            LOGGER.logStateChange(ROLE_CHANGE, memberId, oldRole, newRole, \"\");\n        }\n    }\n\n    static class CanvassPosition\n    {\n        @Advice.OnMethodEnter\n        static void logOnCanvassPosition(\n            final int memberId,\n            final long logLeadershipTermId,\n            final long logPosition,\n            final long leadershipTermId,\n            final int followerMemberId,\n            final int protocolVersion)\n        {\n            LOGGER.logOnCanvassPosition(\n                memberId, logLeadershipTermId, logPosition, leadershipTermId, followerMemberId, protocolVersion);\n        }\n    }\n\n    static class RequestVote\n    {\n        @Advice.OnMethodEnter\n        static void logOnRequestVote(\n            final int memberId,\n            final long logLeadershipTermId,\n            final long logPosition,\n            final long candidateTermId,\n            final int candidateId,\n            final int protocolVersion)\n        {\n            LOGGER.logOnRequestVote(\n                memberId, logLeadershipTermId, logPosition, candidateTermId, candidateId, protocolVersion);\n        }\n    }\n\n    static class Vote\n    {\n        @Advice.OnMethodEnter\n        static void logOnVote(\n            final int memberId,\n            final long logLeadershipTermId,\n            final long logPosition,\n            final long candidateTermId,\n            final int candidateId,\n            final int voterId,\n            final boolean vote)\n        {\n            LOGGER.logOnVote(\n                memberId, logLeadershipTermId, logPosition, candidateTermId, candidateId, voterId, vote);\n        }\n    }\n\n    static class CatchupPosition\n    {\n        @Advice.OnMethodEnter\n        static void logOnCatchupPosition(\n            final int memberId,\n            final long leadershipTermId,\n            final long logPosition,\n            final int followerMemberId,\n            final String catchupEndpoint)\n        {\n            LOGGER.logOnCatchupPosition(memberId, leadershipTermId, logPosition, followerMemberId, catchupEndpoint);\n        }\n    }\n\n    static class StopCatchup\n    {\n        @Advice.OnMethodEnter\n        static void logOnStopCatchup(final int memberId, final long leadershipTermId, final int followerMemberId)\n        {\n            LOGGER.logOnStopCatchup(memberId, leadershipTermId, followerMemberId);\n        }\n    }\n\n    static class TruncateLogEntry\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void onTruncateLogEntry(\n            final int memberId,\n            final E state,\n            final long logLeadershipTermId,\n            final long leadershipTermId,\n            final long candidateTermId,\n            final long commitPosition,\n            final long logPosition,\n            final long appendPosition,\n            final long oldPosition,\n            final long newPosition)\n        {\n            LOGGER.logOnTruncateLogEntry(\n                memberId,\n                state,\n                logLeadershipTermId,\n                leadershipTermId,\n                candidateTermId,\n                commitPosition,\n                logPosition,\n                appendPosition,\n                oldPosition,\n                newPosition);\n        }\n    }\n\n    static class ReplayNewLeadershipTerm\n    {\n        @Advice.OnMethodEnter\n        static void logOnReplayNewLeadershipTermEvent(\n            final int memberId,\n            final boolean isInElection,\n            final long leadershipTermId,\n            final long logPosition,\n            final long timestamp,\n            final long termBaseLogPosition,\n            final TimeUnit timeUnit,\n            final int appVersion)\n        {\n            LOGGER.logOnReplayNewLeadershipTermEvent(\n                memberId,\n                isInElection,\n                leadershipTermId,\n                logPosition,\n                timestamp,\n                termBaseLogPosition,\n                timeUnit,\n                appVersion);\n        }\n    }\n\n    static class AppendPosition\n    {\n        @Advice.OnMethodEnter\n        static void logOnAppendPosition(\n            final int memberId,\n            final long leadershipTermId,\n            final long logPosition,\n            final int followerMemberId,\n            final short flags)\n        {\n            LOGGER.logOnAppendPosition(\n                memberId,\n                leadershipTermId,\n                logPosition,\n                followerMemberId,\n                flags);\n        }\n    }\n\n    static class CommitPosition\n    {\n        @Advice.OnMethodEnter\n        static void logOnCommitPosition(\n            final int memberId,\n            final long leadershipTermId,\n            final long logPosition,\n            final int leaderMemberId)\n        {\n            LOGGER.logOnCommitPosition(memberId, leadershipTermId, logPosition, leaderMemberId);\n        }\n    }\n\n    static class AppendSessionClose\n    {\n        @Advice.OnMethodEnter\n        static void logAppendSessionClose(\n            final int memberId,\n            final long sessionId,\n            final CloseReason closeReason,\n            final long leadershipTermId,\n            final long timestamp,\n            final TimeUnit timeUnit)\n        {\n            LOGGER.logAppendSessionClose(memberId, sessionId, closeReason, leadershipTermId, timestamp, timeUnit);\n        }\n    }\n\n    static class AppendSessionOpen\n    {\n        @Advice.OnMethodEnter\n        static void logAppendSessionOpen(\n            final int memberId,\n            final long sessionId,\n            final long leadershipTermId,\n            final long logPosition,\n            final long timestamp,\n            final TimeUnit timeUnit)\n        {\n            LOGGER.logAppendSessionOpen(memberId, sessionId, leadershipTermId, logPosition, timestamp, timeUnit);\n        }\n    }\n\n    static class ClusterBackupStateChange\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void logStateChange(final E oldState, final E newState, final long nowMs)\n        {\n            LOGGER.logStateChange(CLUSTER_BACKUP_STATE_CHANGE, Aeron.NULL_VALUE, oldState, newState, \"\");\n        }\n    }\n\n    static class TerminationPosition\n    {\n        @Advice.OnMethodEnter\n        static void logOnTerminationPosition(final int memberId, final long leadershipTermId, final long position)\n        {\n            LOGGER.logTerminationPosition(memberId, leadershipTermId, position);\n        }\n    }\n\n    static class TerminationAck\n    {\n        @Advice.OnMethodEnter\n        static void logOnTerminationAck(\n            final int memberId, final long leadershipTermId, final long position, final int senderMemberId)\n        {\n            LOGGER.logTerminationAck(memberId, leadershipTermId, position, senderMemberId);\n        }\n    }\n\n    static class ServiceAck\n    {\n        @Advice.OnMethodEnter\n        static void logOnServiceAck(\n            final int memberId,\n            final long logPosition,\n            final long timestamp,\n            final TimeUnit timeUnit,\n            final long ackId,\n            final long relevantId,\n            final int serviceId)\n        {\n            LOGGER.logServiceAck(memberId, logPosition, timestamp, timeUnit, ackId, relevantId, serviceId);\n        }\n    }\n\n    static class ReplicationEnded\n    {\n        @Advice.OnMethodEnter\n        static void logReplicationEnded(\n            final int memberId,\n            final String purpose,\n            final String channel,\n            final long srcRecordingId,\n            final long dstRecordingId,\n            final long position,\n            final boolean hasSynced)\n        {\n            LOGGER.logReplicationEnded(memberId, purpose, channel, srcRecordingId, dstRecordingId, position, hasSynced);\n        }\n    }\n\n    static class StandbySnapshotNotification\n    {\n        @Advice.OnMethodEnter\n        static void logStandbySnapshotNotification(\n            final int memberId,\n            final long recordingId,\n            final long leadershipTermId,\n            final long termBaseLogPosition,\n            final long logPosition,\n            final long timestamp,\n            final TimeUnit timeUnit,\n            final int serviceId,\n            final String archiveEndpoint)\n        {\n            LOGGER.logStandbySnapshotNotification(\n                memberId,\n                recordingId,\n                leadershipTermId,\n                termBaseLogPosition,\n                logPosition,\n                timestamp,\n                timeUnit,\n                serviceId,\n                archiveEndpoint);\n        }\n    }\n\n    static class NewElection\n    {\n        @Advice.OnMethodEnter\n        static void logNewElection(\n            final int memberId,\n            final long leadershipTermId,\n            final long logPosition,\n            final long appendPosition,\n            final String reason)\n        {\n            LOGGER.logNewElection(memberId, leadershipTermId, logPosition, appendPosition, reason);\n        }\n    }\n\n    static class ClusterSession\n    {\n        @Advice.OnMethodEnter\n        static <A extends Enum<A>, S extends Enum<S>> void logStateChange(\n            final int memberId,\n            final long sessionId,\n            final A action,\n            final S oldState,\n            final S newState,\n            final String reason)\n        {\n            LOGGER.logClusterSessionStateChange(memberId, sessionId, action, oldState, newState, reason);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/CmdInterceptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport net.bytebuddy.asm.Advice;\nimport org.agrona.DirectBuffer;\n\nimport java.util.EnumSet;\n\nimport static io.aeron.agent.DriverEventCode.*;\nimport static io.aeron.agent.DriverEventLogger.LOGGER;\nimport static io.aeron.command.ControlProtocolEvents.*;\n\nclass CmdInterceptor\n{\n    static final EnumSet<DriverEventCode> EVENTS = EnumSet.of(\n        CMD_IN_ADD_PUBLICATION,\n        CMD_IN_REMOVE_PUBLICATION,\n        CMD_IN_ADD_EXCLUSIVE_PUBLICATION,\n        CMD_IN_ADD_SUBSCRIPTION,\n        CMD_IN_REMOVE_SUBSCRIPTION,\n        CMD_IN_KEEPALIVE_CLIENT,\n        CMD_IN_ADD_DESTINATION,\n        CMD_IN_REMOVE_DESTINATION,\n        CMD_OUT_AVAILABLE_IMAGE,\n        CMD_OUT_ERROR,\n        CMD_OUT_ON_OPERATION_SUCCESS,\n        CMD_OUT_PUBLICATION_READY,\n        CMD_OUT_ON_UNAVAILABLE_IMAGE,\n        CMD_OUT_EXCLUSIVE_PUBLICATION_READY,\n        CMD_OUT_SUBSCRIPTION_READY,\n        CMD_OUT_COUNTER_READY,\n        CMD_OUT_ON_UNAVAILABLE_COUNTER,\n        CMD_IN_ADD_COUNTER,\n        CMD_IN_REMOVE_COUNTER,\n        CMD_IN_CLIENT_CLOSE,\n        CMD_IN_ADD_RCV_DESTINATION,\n        CMD_IN_REMOVE_RCV_DESTINATION,\n        CMD_OUT_ON_CLIENT_TIMEOUT,\n        CMD_IN_TERMINATE_DRIVER,\n        CMD_IN_REMOVE_DESTINATION_BY_ID,\n        CMD_IN_REJECT_IMAGE);\n\n    @SuppressWarnings(\"methodlength\")\n    @Advice.OnMethodEnter\n    static void logCmd(final int msgTypeId, final DirectBuffer buffer, final int index, final int length)\n    {\n        switch (msgTypeId)\n        {\n            case ADD_PUBLICATION:\n                LOGGER.log(CMD_IN_ADD_PUBLICATION, buffer, index, length);\n                break;\n\n            case REMOVE_PUBLICATION:\n                LOGGER.log(CMD_IN_REMOVE_PUBLICATION, buffer, index, length);\n                break;\n\n            case ADD_EXCLUSIVE_PUBLICATION:\n                LOGGER.log(CMD_IN_ADD_EXCLUSIVE_PUBLICATION, buffer, index, length);\n                break;\n\n            case ADD_SUBSCRIPTION:\n                LOGGER.log(CMD_IN_ADD_SUBSCRIPTION, buffer, index, length);\n                break;\n\n            case REMOVE_SUBSCRIPTION:\n                LOGGER.log(CMD_IN_REMOVE_SUBSCRIPTION, buffer, index, length);\n                break;\n\n            case CLIENT_KEEPALIVE:\n                LOGGER.log(CMD_IN_KEEPALIVE_CLIENT, buffer, index, length);\n                break;\n\n            case ADD_DESTINATION:\n                LOGGER.log(CMD_IN_ADD_DESTINATION, buffer, index, length);\n                break;\n\n            case REMOVE_DESTINATION:\n                LOGGER.log(CMD_IN_REMOVE_DESTINATION, buffer, index, length);\n                break;\n\n            case ON_AVAILABLE_IMAGE:\n                LOGGER.log(CMD_OUT_AVAILABLE_IMAGE, buffer, index, length);\n                break;\n\n            case ON_ERROR:\n                LOGGER.log(CMD_OUT_ERROR, buffer, index, length);\n                break;\n\n            case ON_OPERATION_SUCCESS:\n                LOGGER.log(CMD_OUT_ON_OPERATION_SUCCESS, buffer, index, length);\n                break;\n\n            case ON_PUBLICATION_READY:\n                LOGGER.log(CMD_OUT_PUBLICATION_READY, buffer, index, length);\n                break;\n\n            case ON_UNAVAILABLE_IMAGE:\n                LOGGER.log(CMD_OUT_ON_UNAVAILABLE_IMAGE, buffer, index, length);\n                break;\n\n            case ON_EXCLUSIVE_PUBLICATION_READY:\n                LOGGER.log(CMD_OUT_EXCLUSIVE_PUBLICATION_READY, buffer, index, length);\n                break;\n\n            case ON_SUBSCRIPTION_READY:\n                LOGGER.log(CMD_OUT_SUBSCRIPTION_READY, buffer, index, length);\n                break;\n\n            case ON_COUNTER_READY:\n                LOGGER.log(CMD_OUT_COUNTER_READY, buffer, index, length);\n                break;\n\n            case ON_UNAVAILABLE_COUNTER:\n                LOGGER.log(CMD_OUT_ON_UNAVAILABLE_COUNTER, buffer, index, length);\n                break;\n\n            case ADD_COUNTER:\n                LOGGER.log(CMD_IN_ADD_COUNTER, buffer, index, length);\n                break;\n\n            case REMOVE_COUNTER:\n                LOGGER.log(CMD_IN_REMOVE_COUNTER, buffer, index, length);\n                break;\n\n            case CLIENT_CLOSE:\n                LOGGER.log(CMD_IN_CLIENT_CLOSE, buffer, index, length);\n                break;\n\n            case ADD_RCV_DESTINATION:\n                LOGGER.log(CMD_IN_ADD_RCV_DESTINATION, buffer, index, length);\n                break;\n\n            case REMOVE_RCV_DESTINATION:\n                LOGGER.log(CMD_IN_REMOVE_RCV_DESTINATION, buffer, index, length);\n                break;\n\n            case ON_CLIENT_TIMEOUT:\n                LOGGER.log(CMD_OUT_ON_CLIENT_TIMEOUT, buffer, index, length);\n                break;\n\n            case TERMINATE_DRIVER:\n                LOGGER.log(CMD_IN_TERMINATE_DRIVER, buffer, index, length);\n                break;\n\n            case REMOVE_DESTINATION_BY_ID:\n                LOGGER.log(CMD_IN_REMOVE_DESTINATION_BY_ID, buffer, index, length);\n                break;\n\n            case REJECT_IMAGE:\n                LOGGER.log(CMD_IN_REJECT_IMAGE, buffer, index, length);\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/CollectingEventLogReaderAgent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.MessageHandler;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\n\nimport javax.management.InstanceAlreadyExistsException;\nimport javax.management.MBeanRegistrationException;\nimport javax.management.MBeanServer;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.NotCompliantMBeanException;\nimport javax.management.ObjectName;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.lang.management.ManagementFactory;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\nimport static io.aeron.agent.EventConfiguration.EVENT_READER_FRAME_LIMIT;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static io.aeron.agent.EventLogReaderAgent.decodeLogEvent;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\n\n/**\n * Simple reader of {@link EventConfiguration#EVENT_RING_BUFFER} that is useful for testing. It will register\n * itself into JMX and allow users to switch on and off capture of log events in memory and allows the user\n * to periodically write them to a file.\n */\npublic final class CollectingEventLogReaderAgent implements Agent, CollectingEventLogReaderAgentMBean\n{\n    /**\n     * MBean name for this logging agent.\n     */\n    public static final String LOGGING_MBEAN_NAME = \"io.aeron:type=logging\";\n    private final Int2ObjectHashMap<ComponentLogger> loggers = new Int2ObjectHashMap<>();\n    private String startMessage;\n\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        COLLECTING, IGNORING\n    }\n\n    private final ManyToOneRingBuffer ringBuffer = EventConfiguration.EVENT_RING_BUFFER;\n    private final ExpandableArrayBuffer collectingBuffer = new ExpandableArrayBuffer();\n    private final MessageHandler messageHandler = this::onMessage;\n    private final Object mutex = new Object();\n\n    private final StringBuilder decodeBuffer = new StringBuilder(MAX_EVENT_LENGTH);\n    private volatile State state = State.IGNORING;\n    private int bufferPosition = 0;\n\n    CollectingEventLogReaderAgent(final String fileName, final List<ComponentLogger> loggers)\n    {\n        for (final ComponentLogger componentLogger : loggers)\n        {\n            this.loggers.put(componentLogger.typeCode(), componentLogger);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        try\n        {\n            final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();\n            final ObjectName oName = new ObjectName(LOGGING_MBEAN_NAME);\n            mBeanServer.registerMBean(this, oName);\n        }\n        catch (final MalformedObjectNameException |\n            InstanceAlreadyExistsException |\n            MBeanRegistrationException |\n            NotCompliantMBeanException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"inmemory-event-log-reader\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        synchronized (mutex)\n        {\n            return ringBuffer.read(messageHandler, EVENT_READER_FRAME_LIMIT);\n        }\n    }\n\n    private void onMessage(final int msgTypeId, final MutableDirectBuffer buffer, final int index, final int length)\n    {\n        if (state == State.IGNORING)\n        {\n            return;\n        }\n\n        int position = bufferPosition;\n\n        collectingBuffer.putInt(position, msgTypeId);\n        position += SIZE_OF_INT;\n        collectingBuffer.putInt(position, length);\n        position += SIZE_OF_INT;\n        collectingBuffer.putBytes(position, buffer, index, length);\n        position += length;\n\n        bufferPosition = position;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void setCollecting(final boolean isCollecting)\n    {\n        state = isCollecting ? State.COLLECTING : State.IGNORING;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void startCollecting(final String name)\n    {\n        synchronized (mutex)\n        {\n            resetWritePosition();\n            writeLogStartMessage(name);\n            state = State.COLLECTING;\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isCollecting()\n    {\n        return state == State.COLLECTING;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void reset()\n    {\n        synchronized (mutex)\n        {\n            state = State.IGNORING;\n            resetWritePosition();\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void writeToFile(final String filename)\n    {\n        synchronized (mutex)\n        {\n            doOutputToFile(filename);\n        }\n    }\n\n    private void resetWritePosition()\n    {\n        bufferPosition = 0;\n    }\n\n    private void writeLogStartMessage(final String name)\n    {\n        final long timestampNs = System.nanoTime();\n        decodeBuffer.setLength(0);\n        LogUtil.appendTimestamp(decodeBuffer, timestampNs);\n        startMessage = decodeBuffer\n            .append(\" [\")\n            .append(LocalDateTime.now())\n            .append(\"] \")\n            .append(name)\n            .toString();\n    }\n\n    private void doOutputToFile(final String filename)\n    {\n        System.out.println(\"Dumping to file: \" + filename);\n\n        try (PrintStream out = new PrintStream(filename))\n        {\n            final int terminalPosition = bufferPosition;\n\n            out.println(startMessage);\n\n            int readingPosition = 0;\n            while (readingPosition < terminalPosition)\n            {\n                final int msgTypeId = collectingBuffer.getInt(readingPosition);\n                readingPosition += SIZE_OF_INT;\n\n                final int length = collectingBuffer.getInt(readingPosition);\n                readingPosition += SIZE_OF_INT;\n\n                final int eventCodeTypeId = msgTypeId >> 16;\n                final int eventCodeId = msgTypeId & 0xFFFF;\n\n                decodeBuffer.setLength(0);\n                decodeLogEvent(\n                    collectingBuffer, readingPosition, eventCodeTypeId, eventCodeId, loggers, decodeBuffer);\n                readingPosition += length;\n\n                out.print(decodeBuffer);\n            }\n\n            bufferPosition = 0;\n        }\n        catch (final IOException ex)\n        {\n            System.err.println(\"Failed to write to output log: \" + ex.getMessage());\n            ex.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/CollectingEventLogReaderAgentMBean.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\n/**\n * MBean interface for a logging agent that stores events in memory and allows them to be periodically written\n * out. Useful within tests.\n */\npublic interface CollectingEventLogReaderAgentMBean\n{\n    /**\n     * Enable or disable the collection of logs.\n     *\n     * @param isCollecting whether logs should be collected or not.\n     */\n    void setCollecting(boolean isCollecting);\n\n    /**\n     * Shows whether logs are being collected or not.\n     *\n     * @return true to indicate logs are being collected in memory.\n     */\n    boolean isCollecting();\n\n    /**\n     * Reset the internal positions within the collector discarding all previous logs. Should be called periodically,\n     * so to avoid consuming all available memory.\n     */\n    void reset();\n\n    /**\n     * Start collecting messages and use the specified name as a title to start logging.\n     *\n     * @param name a value to be included as a message in the log to delineate events.\n     */\n    void startCollecting(String name);\n\n    /**\n     * Output the collected logs to file.\n     *\n     * @param filename of file to write to.\n     */\n    void writeToFile(String filename);\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/CommonEventDissector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport java.time.ZoneId;\nimport java.time.format.DateTimeFormatter;\n\nimport static java.lang.Integer.toHexString;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.time.Instant.ofEpochMilli;\nimport static java.time.OffsetDateTime.ofInstant;\nimport static java.time.format.DateTimeFormatter.ofPattern;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Helper class to dissect log events.\n */\npublic final class CommonEventDissector\n{\n    private static final DateTimeFormatter DATE_TIME_FORMATTER = ofPattern(\"uuuu-MM-dd HH:mm:ss.SSSZ\");\n\n    private CommonEventDissector()\n    {\n    }\n\n    /**\n     * Parse log start message.\n     *\n     * @param timestampNs timestamp in nanos.\n     * @param timestampMs timestamp in millis.\n     * @param zone        timezone.\n     * @param builder     for the dissected message.\n     */\n    public static void dissectLogStartMessage(\n        final long timestampNs, final long timestampMs, final ZoneId zone, final StringBuilder builder)\n    {\n        LogUtil.appendTimestamp(builder, timestampNs);\n        builder.append(\"log started \")\n            .append(DATE_TIME_FORMATTER.format(ofInstant(ofEpochMilli(timestampMs), zone)));\n    }\n\n    /**\n     * Dissect event header.\n     *\n     * @param context for the event.\n     * @param code    event type.\n     * @param buffer  log buffer.\n     * @param offset  where log event starts.\n     * @param builder for the dissected message.\n     * @return length in bytes.\n     */\n    public static int dissectLogHeader(\n        final String context,\n        final Enum<?> code,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int encodedLength = 0;\n\n        final int captureLength = buffer.getInt(offset + encodedLength, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        final int bufferLength = buffer.getInt(offset + encodedLength, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        final long timestampNs = buffer.getLong(offset + encodedLength, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        LogUtil.appendTimestamp(builder, timestampNs);\n        builder.append(context)\n            .append(\": \")\n            .append(code.name())\n            .append(\" [\")\n            .append(captureLength)\n            .append('/')\n            .append(bufferLength)\n            .append(']');\n\n        return encodedLength;\n    }\n\n    /**\n     * Dissect {@code java.net.InetSocketAddress} address.\n     *\n     * @param buffer  log buffer.\n     * @param offset  where log event starts.\n     * @param builder for the dissected message.\n     * @return length in bytes.\n     */\n    public static int dissectSocketAddress(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int encodedLength = 0;\n        final int port = buffer.getInt(offset + encodedLength, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodedLength += dissectInetAddress(buffer, offset + encodedLength, builder);\n\n        builder.append(':').append(port);\n\n        return encodedLength;\n    }\n\n    /**\n     * Dissect {@link java.net.InetAddress} address.\n     *\n     * @param buffer  log buffer.\n     * @param offset  where log event starts.\n     * @param builder for the dissected message.\n     * @return length in bytes.\n     */\n    public static int dissectInetAddress(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int encodedLength = 0;\n\n        final int addressLength = buffer.getInt(offset + encodedLength, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        if (4 == addressLength)\n        {\n            final int i = offset + encodedLength;\n            builder\n                .append(buffer.getByte(i) & 0xFF)\n                .append('.')\n                .append(buffer.getByte(i + 1) & 0xFF)\n                .append('.')\n                .append(buffer.getByte(i + 2) & 0xFF)\n                .append('.')\n                .append(buffer.getByte(i + 3) & 0xFF);\n        }\n        else if (16 == addressLength)\n        {\n            final int i = offset + encodedLength;\n            builder\n                .append('[')\n                .append(toHexString(((buffer.getByte(i) << 8) & 0xFF00) | buffer.getByte(i + 1) & 0xFF))\n                .append(':')\n                .append(toHexString(((buffer.getByte(i + 2) << 8) & 0xFF00) | buffer.getByte(i + 3) & 0xFF))\n                .append(':')\n                .append(toHexString(((buffer.getByte(i + 4) << 8) & 0xFF00) | buffer.getByte(i + 5) & 0xFF))\n                .append(':')\n                .append(toHexString(((buffer.getByte(i + 6) << 8) & 0xFF00) | buffer.getByte(i + 7) & 0xFF))\n                .append(':')\n                .append(toHexString(((buffer.getByte(i + 8) << 8) & 0xFF00) | buffer.getByte(i + 9) & 0xFF))\n                .append(':')\n                .append(toHexString(((buffer.getByte(i + 10) << 8) & 0xFF00) | buffer.getByte(i + 11) & 0xFF))\n                .append(':')\n                .append(toHexString(((buffer.getByte(i + 12) << 8) & 0xFF00) | buffer.getByte(i + 13) & 0xFF))\n                .append(':')\n                .append(toHexString(((buffer.getByte(i + 14) << 8) & 0xFF00) | buffer.getByte(i + 15) & 0xFF))\n                .append(']');\n        }\n        else\n        {\n            builder.append(\"unknown-address\");\n        }\n\n        encodedLength += addressLength;\n\n        return encodedLength;\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/CommonEventEncoder.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\n\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static java.lang.Math.min;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Helper class for encoding log events.\n */\npublic final class CommonEventEncoder\n{\n    /**\n     * Length of log header in bytes.\n     */\n    public static final int LOG_HEADER_LENGTH = 16;\n\n    /**\n     * Max capture length.\n     */\n    public static final int MAX_CAPTURE_LENGTH = MAX_EVENT_LENGTH - LOG_HEADER_LENGTH;\n\n    /**\n     * State transition separator.\n     */\n    public static final String STATE_SEPARATOR = \" -> \";\n\n    private CommonEventEncoder()\n    {\n    }\n\n    /**\n     * Encode event log header.\n     *\n     * @param encodingBuffer log buffer.\n     * @param offset         in the log buffer.\n     * @param captureLength  capture length.\n     * @param length         original length.\n     * @return header length in bytes.\n     */\n    public static int encodeLogHeader(\n        final MutableDirectBuffer encodingBuffer, final int offset, final int captureLength, final int length)\n    {\n        return internalEncodeLogHeader(encodingBuffer, offset, captureLength, length, SystemNanoClock.INSTANCE);\n    }\n\n    static int internalEncodeLogHeader(\n        final MutableDirectBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final NanoClock nanoClock)\n    {\n        if (captureLength < 0 || captureLength > length || captureLength > MAX_CAPTURE_LENGTH)\n        {\n            throw new IllegalArgumentException(\"invalid input: captureLength=\" + captureLength + \", length=\" + length);\n        }\n\n        int encodedLength = 0;\n        /*\n         * Stream of values:\n         * - capture buffer length (int)\n         * - total buffer length (int)\n         * - timestamp (long)\n         * - buffer (until end)\n         */\n\n        encodingBuffer.putInt(offset + encodedLength, captureLength, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, length, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putLong(offset + encodedLength, nanoClock.nanoTime(), LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        return encodedLength;\n    }\n\n    /**\n     * Encode {@link InetSocketAddress} address.\n     *\n     * @param encodingBuffer log buffer.\n     * @param offset         in the log buffer.\n     * @param address        to encode.\n     * @return encoded length in bytes.\n     */\n    public static int encodeSocketAddress(\n        final UnsafeBuffer encodingBuffer, final int offset, final InetSocketAddress address)\n    {\n        int encodedLength = 0;\n        /*\n         * Stream of values:\n         * - port (int) (unsigned short int)\n         * - IP address length (int) (4 or 16)\n         * - IP address (4 or 16 bytes)\n         */\n\n        encodingBuffer.putInt(offset + encodedLength, address.getPort(), LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        final byte[] addressBytes = address.getAddress().getAddress();\n        encodingBuffer.putInt(offset + encodedLength, addressBytes.length, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putBytes(offset + encodedLength, addressBytes);\n        encodedLength += addressBytes.length;\n\n        return encodedLength;\n    }\n\n    /**\n     * Encode {@link InetAddress} address.\n     *\n     * @param encodingBuffer log buffer.\n     * @param offset         in the log buffer.\n     * @param address        to encode.\n     * @return encoded length in bytes.\n     */\n    public static int encodeInetAddress(\n        final UnsafeBuffer encodingBuffer, final int offset, final InetAddress address)\n    {\n        int encodedLength = 0;\n\n        if (null != address)\n        {\n            /*\n             * Stream of values:\n             * - IP address length (int) (4 or 16)\n             * - IP address (4 or 16 bytes)\n             */\n            final byte[] addressBytes = address.getAddress();\n            encodingBuffer.putInt(offset + encodedLength, addressBytes.length, LITTLE_ENDIAN);\n            encodedLength += SIZE_OF_INT;\n\n            encodingBuffer.putBytes(offset + encodedLength, addressBytes);\n            encodedLength += addressBytes.length;\n        }\n        else\n        {\n            encodingBuffer.putInt(offset, 0);\n            encodedLength += SIZE_OF_INT;\n        }\n\n        return encodedLength;\n    }\n\n    /**\n     * Encode string at the end of the log event.\n     *\n     * @param encodingBuffer    log buffer.\n     * @param offset            in the log buffer.\n     * @param remainingCapacity till end of the event.\n     * @param value             to encode.\n     * @return encoded length in bytes.\n     */\n    public static int encodeTrailingString(\n        final UnsafeBuffer encodingBuffer, final int offset, final int remainingCapacity, final String value)\n    {\n        final int maxLength = remainingCapacity - SIZE_OF_INT;\n        if (value.length() <= maxLength)\n        {\n            return encodingBuffer.putStringAscii(offset, value, LITTLE_ENDIAN);\n        }\n        else\n        {\n            encodingBuffer.putInt(offset, maxLength, LITTLE_ENDIAN);\n            encodingBuffer.putStringWithoutLengthAscii(offset + SIZE_OF_INT, value, 0, maxLength - 3);\n            encodingBuffer.putStringWithoutLengthAscii(offset + SIZE_OF_INT + maxLength - 3, \"...\");\n            return remainingCapacity;\n        }\n    }\n\n    /**\n     * Encode binary event.\n     *\n     * @param encodingBuffer log buffer.\n     * @param offset         in the log buffer.\n     * @param captureLength  capture length.\n     * @param length         original length.\n     * @param srcBuffer      containing binary message.\n     * @param srcOffset      where source event begins.\n     * @return encoded length in bytes.\n     */\n    public static int encode(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final DirectBuffer srcBuffer,\n        final int srcOffset)\n    {\n        final int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        encodingBuffer.putBytes(offset + encodedLength, srcBuffer, srcOffset, captureLength);\n        return encodedLength + captureLength;\n    }\n\n    /**\n     * Encode state transition.\n     *\n     * @param <E>            enum type.\n     * @param encodingBuffer log buffer.\n     * @param offset         in the log buffer.\n     * @param from           previous state.\n     * @param to             new state.\n     * @return encoded length in bytes.\n     */\n    public static <E extends Enum<E>> int encodeStateChange(\n        final UnsafeBuffer encodingBuffer, final int offset, final E from, final E to)\n    {\n        int encodedLength = 0;\n\n        final String fromName = enumName(from);\n        final String toName = enumName(to);\n\n        encodingBuffer.putInt(\n            offset,\n            fromName.length() + STATE_SEPARATOR.length() + toName.length(),\n            LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n        encodedLength += encodingBuffer.putStringWithoutLengthAscii(offset + encodedLength, fromName);\n        encodedLength += encodingBuffer.putStringWithoutLengthAscii(offset + encodedLength, STATE_SEPARATOR);\n        encodedLength += encodingBuffer.putStringWithoutLengthAscii(offset + encodedLength, toName);\n\n        return encodedLength;\n    }\n\n    /**\n     * Encode state transition at the end.\n     *\n     * @param <E>                  enum type.\n     * @param encodingBuffer       log buffer.\n     * @param offset               in the log buffer.\n     * @param runningEncodedLength running     encoding length.\n     * @param captureLength        capture length.\n     * @param from                 previous state.\n     * @param to                   new state.\n     * @return encoded length in bytes.\n     */\n    public static <E extends Enum<E>> int encodeTrailingStateChange(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int runningEncodedLength,\n        final int captureLength,\n        final E from,\n        final E to)\n    {\n        int encodedLength = runningEncodedLength;\n        encodingBuffer.putInt(\n            offset + encodedLength,\n            captureLength - (runningEncodedLength - LOG_HEADER_LENGTH + SIZE_OF_INT),\n            LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        final String fromName = enumName(from);\n        final String toName = enumName(to);\n        encodedLength += encodingBuffer.putStringWithoutLengthAscii(offset + encodedLength, fromName);\n        encodedLength += encodingBuffer.putStringWithoutLengthAscii(offset + encodedLength, STATE_SEPARATOR);\n        encodedLength += encodingBuffer.putStringWithoutLengthAscii(offset + encodedLength, toName);\n\n        return encodedLength;\n    }\n\n    /**\n     * Returns capture length.\n     *\n     * @param length to compute.\n     * @return capture length in bytes capped at {@link #MAX_CAPTURE_LENGTH}.\n     */\n    public static int captureLength(final int length)\n    {\n        return min(length, MAX_CAPTURE_LENGTH);\n    }\n\n    /**\n     * Returns full encoded length for given the capture length.\n     *\n     * @param captureLength capture length.\n     * @return encoded length in bytes.\n     */\n    public static int encodedLength(final int captureLength)\n    {\n        return LOG_HEADER_LENGTH + captureLength;\n    }\n\n    /**\n     * Compute encoded length for {@link InetSocketAddress}.\n     *\n     * @param address to encode.\n     * @return length in bytes.\n     */\n    public static int socketAddressLength(final InetSocketAddress address)\n    {\n        return SIZE_OF_INT + inetAddressLength(address.getAddress());\n    }\n\n    /**\n     * Compute encoded length for {@link InetAddress}.\n     *\n     * @param address to encode.\n     * @return length in bytes.\n     */\n    public static int inetAddressLength(final InetAddress address)\n    {\n        return SIZE_OF_INT + (null != address ? address.getAddress().length : 0);\n    }\n\n    /**\n     * Compute encoded length for trailing string.\n     *\n     * @param value     new state.\n     * @param maxLength max length.\n     * @return length in bytes.\n     */\n    public static int trailingStringLength(final String value, final int maxLength)\n    {\n        return SIZE_OF_INT + min(value.length(), maxLength);\n    }\n\n    /**\n     * Compute state transition encoded length in bytes.\n     *\n     * @param <E>  type of the enum.\n     * @param from old state.\n     * @param to   new state.\n     * @return length in bytes.\n     */\n    public static <E extends Enum<E>> int stateTransitionStringLength(final E from, final E to)\n    {\n        return SIZE_OF_INT + enumName(from).length() + STATE_SEPARATOR.length() + enumName(to).length();\n    }\n\n    /**\n     * Null-safe name for the enum.\n     *\n     * @param <E>   type of the enum.\n     * @param value value to return name for.\n     * @return name or {@code \"null\"} if {@code null == value}\n     */\n    public static <E extends Enum<E>> String enumName(final E value)\n    {\n        return null == value ? \"null\" : value.name();\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ComponentLogger.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport org.agrona.MutableDirectBuffer;\n\nimport java.util.Map;\n\n/**\n * Interface that describes a logger for a given Aeron component.\n */\npublic interface ComponentLogger\n{\n    /**\n     * The type code to distinguish this logger when encoding/decoding messages.\n     *\n     * @return the type code for this logger.\n     */\n    int typeCode();\n\n    /**\n     * Decode a message on the reader side.\n     *\n     * @param buffer      containing the message.\n     * @param offset      in the buffer to the message.\n     * @param eventCodeId of the event to be decoded.\n     * @param builder     to render the message to.\n     */\n    void decode(MutableDirectBuffer buffer, int offset, int eventCodeId, StringBuilder builder);\n\n    /**\n     * Add code instrumentation to inject log messages.\n     *\n     * @param agentBuilder  to perform instrumentation with.\n     * @param configOptions list of configuration options which are coming via system properties or the\n     *                      {@code agentArgs} String of the\n     *                      {@code premain(String agentArgs, Instrumentation instrumentation)} method.\n     * @return the updated agent builder after instrumentation has been applied. Return the original\n     * {@code agentBuilder} instance to indicate that no instrumentation has been applied.\n     */\n    AgentBuilder addInstrumentation(AgentBuilder agentBuilder, Map<String, String> configOptions);\n\n    /**\n     * Reset the logger and its configuration. Typically called when stopping/disabling logging.\n     */\n    void reset();\n\n    /**\n     * Get version information for this logger. Typically, will use version and commit info, e.g.\n     * {@code version=1.49.0 commit=4b09b14043}.\n     *\n     * @return version information, e.g. {@code version=1.49.0 commit=4b09b14043}.\n     */\n    String version();\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ConfigOption.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.Strings;\nimport org.agrona.concurrent.Agent;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\n/**\n * A set of configuration options.\n */\nfinal class ConfigOption\n{\n    /**\n     * Event Buffer log file name system property. If not set then output will default to {@link System#out}.\n     */\n    static final String LOG_FILENAME = \"aeron.event.log.filename\";\n\n    /**\n     * Event reader {@link Agent} which consumes the {@link EventConfiguration#EVENT_RING_BUFFER} to output log events.\n     */\n    static final String READER_CLASSNAME = \"aeron.event.log.reader.classname\";\n\n    /**\n     * Driver Event tags system property. This is either:\n     * <ul>\n     * <li>A comma separated list of {@link DriverEventCode}s to enable.</li>\n     * <li>{@code all} which enables all driver events.</li>\n     * <li>{@code admin} which enables all driver events except for {@link DriverEventCode#FRAME_IN},\n     * {@link DriverEventCode#FRAME_OUT}, {@link DriverEventCode#NAME_RESOLUTION_NEIGHBOR_ADDED},\n     * {@link DriverEventCode#NAME_RESOLUTION_NEIGHBOR_REMOVED}.</li>\n     * </ul>\n     */\n    static final String ENABLED_DRIVER_EVENT_CODES = \"aeron.event.log\";\n\n    /**\n     * Disabled Driver Event tags system property. Follows the format specified for\n     * {@link #ENABLED_DRIVER_EVENT_CODES}. This property will disable any codes in the set\n     * specified there. Defined on its own has no effect.\n     */\n    static final String DISABLED_DRIVER_EVENT_CODES = \"aeron.event.log.disable\";\n\n    /**\n     * Archive Event tags system property. This is either:\n     * <ul>\n     * <li>A comma separated list of {@link ArchiveEventCode}s to enable.</li>\n     * <li>{@code all} which enables all the codes.</li>\n     * </ul>\n     */\n    static final String ENABLED_ARCHIVE_EVENT_CODES = \"aeron.event.archive.log\";\n\n    /**\n     * Disabled Archive Event tags system property. Follows the format specified for\n     * {@link #ENABLED_ARCHIVE_EVENT_CODES}. This property will disable any codes in the\n     * set specified there. Defined on its own has no effect.\n     */\n    static final String DISABLED_ARCHIVE_EVENT_CODES = \"aeron.event.archive.log.disable\";\n\n    /**\n     * Cluster Event tags system property. This is either:\n     * <ul>\n     * <li>A comma separated list of {@link ClusterEventCode}s to enable.</li>\n     * <li>{@code all} which enables all the codes.</li>\n     * </ul>\n     */\n    static final String ENABLED_CLUSTER_EVENT_CODES = \"aeron.event.cluster.log\";\n\n    /**\n     * Disabled Cluster Event tags system property name. Follows the format specified for\n     * {@link #ENABLED_CLUSTER_EVENT_CODES}. This property will disable any codes in the\n     * set specified there. Defined on its own has no effect.\n     */\n    static final String DISABLED_CLUSTER_EVENT_CODES = \"aeron.event.cluster.log.disable\";\n\n    static final String START_COMMAND = \"start\";\n    static final String STOP_COMMAND = \"stop\";\n\n    private static final char VALUE_SEPARATOR = '=';\n    private static final char OPTION_SEPARATOR = '|';\n\n    static Map<String, String> fromSystemProperties()\n    {\n        final HashMap<String, String> result = new HashMap<>();\n        final Properties properties = System.getProperties();\n        for (final Map.Entry<Object, Object> entry : properties.entrySet())\n        {\n            result.put((String)entry.getKey(), (String)entry.getValue());\n        }\n        return result;\n    }\n\n    static String buildAgentArgs(final Map<String, String> configOptions)\n    {\n        if (configOptions.isEmpty())\n        {\n            return null;\n        }\n\n        final StringBuilder builder = new StringBuilder();\n        for (final Map.Entry<String, String> entry : configOptions.entrySet())\n        {\n            builder.append(entry.getKey())\n                .append(VALUE_SEPARATOR)\n                .append(entry.getValue())\n                .append(OPTION_SEPARATOR);\n        }\n\n        if (builder.length() > 0)\n        {\n            builder.setLength(builder.length() - 1);\n        }\n\n        return builder.toString();\n    }\n\n    static Map<String, String> parseAgentArgs(final String agentArgs)\n    {\n        if (Strings.isEmpty(agentArgs))\n        {\n            throw new IllegalArgumentException(\"cannot parse empty value\");\n        }\n\n        final Map<String, String> values = new HashMap<>();\n\n        int optionIndex = -1;\n        do\n        {\n            final int valueIndex = agentArgs.indexOf(VALUE_SEPARATOR, optionIndex);\n            if (valueIndex <= 0)\n            {\n                break;\n            }\n\n            int nameIndex = -1;\n            while (optionIndex < valueIndex)\n            {\n                nameIndex = optionIndex;\n                optionIndex = agentArgs.indexOf(OPTION_SEPARATOR, optionIndex + 1);\n                if (optionIndex < 0)\n                {\n                    break;\n                }\n            }\n\n            final String optionName = agentArgs.substring(nameIndex + 1, valueIndex);\n            final String value = agentArgs.substring(\n                valueIndex + 1,\n                optionIndex > 0 ? optionIndex : agentArgs.length());\n            values.put(optionName, value);\n        }\n        while (optionIndex > 0);\n\n        return values;\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/ControlInterceptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport net.bytebuddy.asm.Advice;\nimport org.agrona.DirectBuffer;\n\nimport static io.aeron.agent.ArchiveEventLogger.LOGGER;\n\nclass ControlInterceptor\n{\n    static class ControlRequest\n    {\n        @Advice.OnMethodEnter\n        static void onFragment(\n            @Advice.Argument(0) final DirectBuffer buffer,\n            @Advice.Argument(1) final int offset,\n            @Advice.Argument(2) final int length)\n        {\n            LOGGER.logControlRequest(buffer, offset, length);\n        }\n    }\n\n    static class ControlResponse\n    {\n        @Advice.OnMethodEnter\n        static void logSendResponse(final DirectBuffer buffer, final int offset, final int length)\n        {\n            LOGGER.logControlResponse(buffer, offset, length);\n        }\n    }\n\n    static class RecordingSignal\n    {\n        @Advice.OnMethodEnter\n        static void logSendSignal(final DirectBuffer buffer, final int offset, final int length)\n        {\n            LOGGER.logRecordingSignal(buffer, offset, length);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/DissectFunction.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.MutableDirectBuffer;\n\n/**\n * Takes an event and serialises it to the supplied {@link StringBuilder}.\n *\n * @param <T> the type of the event\n */\n@FunctionalInterface\npublic interface DissectFunction<T>\n{\n    /**\n     * Dissect an event and serialise it to a {@link StringBuilder}.\n     *\n     * @param event   to be dissected.\n     * @param buffer  with the encoded event.\n     * @param offset  at which the encoded event begins.\n     * @param builder into which the event will be serialised.\n     */\n    void dissect(T event, MutableDirectBuffer buffer, int offset, StringBuilder builder);\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/DriverComponentLogger.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.AeronCounters;\nimport io.aeron.version.Versioned;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport java.util.EnumSet;\nimport java.util.Map;\n\nimport static io.aeron.agent.ConfigOption.DISABLED_DRIVER_EVENT_CODES;\nimport static io.aeron.agent.ConfigOption.ENABLED_DRIVER_EVENT_CODES;\nimport static io.aeron.agent.DriverEventCode.*;\nimport static net.bytebuddy.asm.Advice.to;\nimport static net.bytebuddy.matcher.ElementMatchers.nameEndsWith;\nimport static net.bytebuddy.matcher.ElementMatchers.named;\n\n/**\n * Implementation of a component logger for media driver log events.\n */\n@Versioned\npublic class DriverComponentLogger implements ComponentLogger\n{\n    static final EnumSet<DriverEventCode> ENABLED_EVENTS = EnumSet.noneOf(DriverEventCode.class);\n\n    private static final Object2ObjectHashMap<String, EnumSet<DriverEventCode>> SPECIAL_EVENTS =\n        new Object2ObjectHashMap<>();\n\n    static\n    {\n        SPECIAL_EVENTS.put(\"all\", EnumSet.allOf(DriverEventCode.class));\n        SPECIAL_EVENTS.put(\"admin\", EnumSet.complementOf(EnumSet.of(FRAME_IN, FRAME_OUT)));\n    }\n\n    /**\n     * Create a DriverComponentLogger, used by Java Service API.\n     */\n    public DriverComponentLogger()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int typeCode()\n    {\n        return EventCodeType.DRIVER.getTypeCode();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void decode(\n        final MutableDirectBuffer buffer, final int offset, final int eventCodeId, final StringBuilder builder)\n    {\n        DriverEventCode.get(eventCodeId).decode(buffer, offset, builder);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public AgentBuilder addInstrumentation(final AgentBuilder agentBuilder, final Map<String, String> configOptions)\n    {\n        ENABLED_EVENTS.clear();\n        ENABLED_EVENTS.addAll(getDriverEventCodes(configOptions.get(ENABLED_DRIVER_EVENT_CODES)));\n        ENABLED_EVENTS.removeAll(getDriverEventCodes(configOptions.get(DISABLED_DRIVER_EVENT_CODES)));\n\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addDriverConductorInstrumentation(tempBuilder);\n        tempBuilder = addDriverCommandInstrumentation(tempBuilder);\n        tempBuilder = addDriverSenderProxyInstrumentation(tempBuilder);\n        tempBuilder = addDriverReceiverProxyInstrumentation(tempBuilder);\n        tempBuilder = addDriverUdpChannelTransportInstrumentation(tempBuilder);\n        tempBuilder = addChannelEndpointInstrumentation(tempBuilder);\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            UNTETHERED_SUBSCRIPTION_STATE_CHANGE,\n            \"UntetheredSubscription\",\n            DriverInterceptor.UntetheredSubscriptionStateChange.class,\n            \"logStateChange\");\n\n        tempBuilder = addDriverNameResolutionInstrumentation(tempBuilder);\n\n        tempBuilder = addDriverFlowControlInstrumentation(tempBuilder);\n\n        tempBuilder = addPublicationRevokeInstrumentation(tempBuilder);\n\n        return tempBuilder;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void reset()\n    {\n        ENABLED_EVENTS.clear();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String version()\n    {\n        return AeronCounters.formatVersionInfo(\n            DriverComponentLoggerVersion.VERSION, DriverComponentLoggerVersion.GIT_SHA);\n    }\n\n    private static EnumSet<DriverEventCode> getDriverEventCodes(final String enabledEventCodes)\n    {\n        return EventConfiguration.parseEventCodes(\n            DriverEventCode.class,\n            enabledEventCodes,\n            SPECIAL_EVENTS,\n            DriverEventCode::get,\n            DriverEventCode::get);\n    }\n\n    private static AgentBuilder addDriverConductorInstrumentation(final AgentBuilder agentBuilder)\n    {\n        final boolean hasImageHook = ENABLED_EVENTS.contains(REMOVE_IMAGE_CLEANUP);\n        final boolean hasPublicationHook = ENABLED_EVENTS.contains(REMOVE_PUBLICATION_CLEANUP);\n        final boolean hasSubscriptionHook = ENABLED_EVENTS.contains(REMOVE_SUBSCRIPTION_CLEANUP);\n\n        if (!hasImageHook && !hasPublicationHook && !hasSubscriptionHook)\n        {\n            return agentBuilder;\n        }\n\n        return agentBuilder.type(nameEndsWith(\"DriverConductor\"))\n            .transform((builder, typeDescription, classLoader, javaModule, protectionDomain) ->\n            {\n                if (hasImageHook)\n                {\n                    builder = builder.visit(to(CleanupInterceptor.CleanupImage.class)\n                        .on(named(\"cleanupImage\")));\n                }\n                if (hasPublicationHook)\n                {\n                    builder = builder.visit(to(CleanupInterceptor.CleanupPublication.class)\n                            .on(named(\"cleanupPublication\")))\n                        .visit(to(CleanupInterceptor.CleanupIpcPublication.class)\n                            .on(named(\"cleanupIpcPublication\")));\n                }\n                if (hasSubscriptionHook)\n                {\n                    builder = builder.visit(to(CleanupInterceptor.CleanupSubscriptionLink.class)\n                        .on(named(\"cleanupSubscriptionLink\")));\n                }\n\n                return builder;\n            });\n    }\n\n    private static AgentBuilder addDriverCommandInstrumentation(final AgentBuilder agentBuilder)\n    {\n        if (CmdInterceptor.EVENTS.stream().noneMatch(ENABLED_EVENTS::contains))\n        {\n            return agentBuilder;\n        }\n\n        return agentBuilder\n            .type(nameEndsWith(\"ClientCommandAdapter\"))\n            .transform((builder, typeDescription, classLoader, javaModule, protectionDomain) -> builder\n                .visit(to(CmdInterceptor.class)\n                    .on(named(\"onMessage\"))))\n            .type(nameEndsWith(\"ClientProxy\"))\n            .transform((builder, typeDescription, classLoader, javaModule, protectionDomain) -> builder\n                .visit(to(CmdInterceptor.class)\n                    .on(named(\"transmit\"))));\n    }\n\n    private static AgentBuilder addDriverSenderProxyInstrumentation(final AgentBuilder agentBuilder)\n    {\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            SEND_CHANNEL_CREATION,\n            \"SenderProxy\",\n            ChannelEndpointInterceptor.SenderProxy.RegisterSendChannelEndpoint.class,\n            \"registerSendChannelEndpoint\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            SEND_CHANNEL_CLOSE,\n            \"SenderProxy\",\n            ChannelEndpointInterceptor.SenderProxy.CloseSendChannelEndpoint.class,\n            \"closeSendChannelEndpoint\");\n\n        return tempBuilder;\n    }\n\n    private static AgentBuilder addDriverReceiverProxyInstrumentation(final AgentBuilder agentBuilder)\n    {\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            RECEIVE_CHANNEL_CREATION,\n            \"ReceiverProxy\",\n            ChannelEndpointInterceptor.ReceiverProxy.RegisterReceiveChannelEndpoint.class,\n            \"registerReceiveChannelEndpoint\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            RECEIVE_CHANNEL_CLOSE,\n            \"ReceiverProxy\",\n            ChannelEndpointInterceptor.ReceiverProxy.CloseReceiveChannelEndpoint.class,\n            \"closeReceiveChannelEndpoint\");\n\n        return tempBuilder;\n    }\n\n    private static AgentBuilder addDriverUdpChannelTransportInstrumentation(final AgentBuilder agentBuilder)\n    {\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            FRAME_OUT,\n            \"UdpChannelTransport\",\n            ChannelEndpointInterceptor.UdpChannelTransport.SendHook.class,\n            \"sendHook\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            FRAME_IN,\n            \"UdpChannelTransport\",\n            ChannelEndpointInterceptor.UdpChannelTransport.ReceiveHook.class,\n            \"receiveHook\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            RESEND,\n            \"UdpChannelTransport\",\n            ChannelEndpointInterceptor.UdpChannelTransport.ResendHook.class,\n            \"resendHook\");\n\n        return tempBuilder;\n    }\n\n    private AgentBuilder addChannelEndpointInstrumentation(final AgentBuilder agentBuilder)\n    {\n        AgentBuilder tempBuilder = agentBuilder;\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            NAK_SENT,\n            \"ReceiveChannelEndpoint\",\n            ChannelEndpointInterceptor.ReceiveChannelEndpointInterceptor.NakSent.class,\n            \"sendNakMessage\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            NAK_RECEIVED,\n            \"SendChannelEndpoint\",\n            ChannelEndpointInterceptor.SendChannelEndpointInterceptor.NakReceived.class,\n            \"onNakMessage\");\n\n        return tempBuilder;\n    }\n\n\n    private static AgentBuilder addDriverNameResolutionInstrumentation(final AgentBuilder agentBuilder)\n    {\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            NAME_RESOLUTION_NEIGHBOR_ADDED,\n            \"Neighbor\",\n            DriverInterceptor.NameResolution.NeighborAdded.class,\n            \"neighborAdded\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            NAME_RESOLUTION_NEIGHBOR_REMOVED,\n            \"Neighbor\",\n            DriverInterceptor.NameResolution.NeighborRemoved.class,\n            \"neighborRemoved\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            NAME_RESOLUTION_RESOLVE,\n            \"TimeTrackingNameResolver\",\n            DriverInterceptor.NameResolution.Resolve.class,\n            \"logResolve\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            NAME_RESOLUTION_LOOKUP,\n            \"TimeTrackingNameResolver\",\n            DriverInterceptor.NameResolution.Lookup.class,\n            \"logLookup\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            NAME_RESOLUTION_HOST_NAME,\n            \"TimeTrackingNameResolver\",\n            DriverInterceptor.NameResolution.HostName.class,\n            \"logHostName\");\n\n        return tempBuilder;\n    }\n\n    private static AgentBuilder addDriverFlowControlInstrumentation(final AgentBuilder agentBuilder)\n    {\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            FLOW_CONTROL_RECEIVER_ADDED,\n            \"AbstractMinMulticastFlowControl\",\n            DriverInterceptor.FlowControl.ReceiverAdded.class,\n            \"receiverAdded\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            FLOW_CONTROL_RECEIVER_REMOVED,\n            \"AbstractMinMulticastFlowControl\",\n            DriverInterceptor.FlowControl.ReceiverRemoved.class,\n            \"receiverRemoved\");\n\n        return tempBuilder;\n    }\n\n    private static AgentBuilder addPublicationRevokeInstrumentation(final AgentBuilder agentBuilder)\n    {\n        AgentBuilder tempBuilder = agentBuilder;\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            PUBLICATION_REVOKE,\n            \"NetworkPublication\",\n            DriverInterceptor.Revoke.PublicationRevoke.class,\n            \"logRevoke\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            PUBLICATION_REVOKE,\n            \"IpcPublication\",\n            DriverInterceptor.Revoke.PublicationRevoke.class,\n            \"logRevoke\");\n\n        tempBuilder = addEventInstrumentation(\n            tempBuilder,\n            PUBLICATION_IMAGE_REVOKE,\n            \"PublicationImage\",\n            DriverInterceptor.Revoke.PublicationImageRevoke.class,\n            \"logRevoke\");\n\n        return tempBuilder;\n    }\n\n    private static AgentBuilder addEventInstrumentation(\n        final AgentBuilder agentBuilder,\n        final DriverEventCode code,\n        final String typeName,\n        final Class<?> interceptorClass,\n        final String interceptorMethod)\n    {\n        if (!ENABLED_EVENTS.contains(code))\n        {\n            return agentBuilder;\n        }\n\n        return agentBuilder\n            .type(nameEndsWith(typeName))\n            .transform((builder, typeDescription, classLoader, javaModule, protectionDomain) ->\n                builder.visit(to(interceptorClass).on(named(interceptorMethod))));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/DriverEventCode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport java.util.Arrays;\n\nimport static io.aeron.agent.DriverEventDissector.*;\n\n/**\n * Events and codecs for encoding/decoding events recorded to the {@link EventConfiguration#EVENT_RING_BUFFER}.\n */\npublic enum DriverEventCode implements EventCode\n{\n    /**\n     * Incoming frame.\n     */\n    FRAME_IN(1, DriverEventDissector::dissectFrame),\n    /**\n     * Outgoing frame.\n     */\n    FRAME_OUT(2, DriverEventDissector::dissectFrame),\n    /**\n     * Add publication command.\n     */\n    CMD_IN_ADD_PUBLICATION(3, DriverEventDissector::dissectCommand),\n    /**\n     * Remove publication command.\n     */\n    CMD_IN_REMOVE_PUBLICATION(4, DriverEventDissector::dissectCommand),\n    /**\n     * Add subscription command.\n     */\n    CMD_IN_ADD_SUBSCRIPTION(5, DriverEventDissector::dissectCommand),\n    /**\n     * Remove subscription command.\n     */\n    CMD_IN_REMOVE_SUBSCRIPTION(6, DriverEventDissector::dissectCommand),\n    /**\n     * Publication ready response.\n     */\n    CMD_OUT_PUBLICATION_READY(7, DriverEventDissector::dissectCommand),\n    /**\n     * On available image response.\n     */\n    CMD_OUT_AVAILABLE_IMAGE(8, DriverEventDissector::dissectCommand),\n    /**\n     * Operation success response.\n     */\n    CMD_OUT_ON_OPERATION_SUCCESS(12, DriverEventDissector::dissectCommand),\n    /**\n     * Keepalive command.\n     */\n    CMD_IN_KEEPALIVE_CLIENT(13, DriverEventDissector::dissectCommand),\n    /**\n     * Cleanup publication event.\n     */\n    REMOVE_PUBLICATION_CLEANUP(14,\n        (code, buffer, offset, builder) -> dissectRemovePublicationCleanup(buffer, offset, builder)),\n    /**\n     * Cleanup subscription event.\n     */\n    REMOVE_SUBSCRIPTION_CLEANUP(15,\n        (code, buffer, offset, builder) -> dissectRemoveSubscriptionCleanup(buffer, offset, builder)),\n    /**\n     * Cleanup image event.\n     */\n    REMOVE_IMAGE_CLEANUP(16,\n        (code, buffer, offset, builder) -> dissectRemoveImageCleanup(buffer, offset, builder)),\n    /**\n     * On unavailable image response.\n     */\n    CMD_OUT_ON_UNAVAILABLE_IMAGE(17, DriverEventDissector::dissectCommand),\n    /**\n     * Send channel creation event.\n     */\n    SEND_CHANNEL_CREATION(23, DriverEventDissector::dissectString),\n    /**\n     * Receive channel creation event.\n     */\n    RECEIVE_CHANNEL_CREATION(24, DriverEventDissector::dissectString),\n    /**\n     * Send channel closed event.\n     */\n    SEND_CHANNEL_CLOSE(25, DriverEventDissector::dissectString),\n    /**\n     * Receive channel creation event.\n     */\n    RECEIVE_CHANNEL_CLOSE(26, DriverEventDissector::dissectString),\n    /**\n     * Add destination command.\n     */\n    CMD_IN_ADD_DESTINATION(30, DriverEventDissector::dissectCommand),\n    /**\n     * Remove destination command.\n     */\n    CMD_IN_REMOVE_DESTINATION(31, DriverEventDissector::dissectCommand),\n    /**\n     * Add exclusive publication command.\n     */\n    CMD_IN_ADD_EXCLUSIVE_PUBLICATION(32, DriverEventDissector::dissectCommand),\n    /**\n     * Exclusive publication ready.\n     */\n    CMD_OUT_EXCLUSIVE_PUBLICATION_READY(33, DriverEventDissector::dissectCommand),\n    /**\n     * Error response.\n     */\n    CMD_OUT_ERROR(34, DriverEventDissector::dissectCommand),\n    /**\n     * Add counter command.\n     */\n    CMD_IN_ADD_COUNTER(35, DriverEventDissector::dissectCommand),\n    /**\n     * Remove counter command.\n     */\n    CMD_IN_REMOVE_COUNTER(36, DriverEventDissector::dissectCommand),\n    /**\n     * Subscription ready.\n     */\n    CMD_OUT_SUBSCRIPTION_READY(37, DriverEventDissector::dissectCommand),\n    /**\n     * Counter ready.\n     */\n    CMD_OUT_COUNTER_READY(38, DriverEventDissector::dissectCommand),\n    /**\n     * On unavailable counter event.\n     */\n    CMD_OUT_ON_UNAVAILABLE_COUNTER(39, DriverEventDissector::dissectCommand),\n    /**\n     * Close client command.\n     */\n    CMD_IN_CLIENT_CLOSE(40, DriverEventDissector::dissectCommand),\n    /**\n     * Add receive destination command.\n     */\n    CMD_IN_ADD_RCV_DESTINATION(41, DriverEventDissector::dissectCommand),\n    /**\n     * Remove receive destination command.\n     */\n    CMD_IN_REMOVE_RCV_DESTINATION(42, DriverEventDissector::dissectCommand),\n    /**\n     * On client timeout.\n     */\n    CMD_OUT_ON_CLIENT_TIMEOUT(43, DriverEventDissector::dissectCommand),\n    /**\n     * Terminate driver command.\n     */\n    CMD_IN_TERMINATE_DRIVER(44, DriverEventDissector::dissectCommand),\n    /**\n     * Untethered subscription state change.\n     */\n    UNTETHERED_SUBSCRIPTION_STATE_CHANGE(45,\n        (code, buffer, offset, builder) -> dissectUntetheredSubscriptionStateChange(buffer, offset, builder)),\n    /**\n     * Name resolution neighbor added.\n     */\n    NAME_RESOLUTION_NEIGHBOR_ADDED(46, DriverEventDissector::dissectAddress),\n    /**\n     * Name resolution neighbor removed.\n     */\n    NAME_RESOLUTION_NEIGHBOR_REMOVED(47, DriverEventDissector::dissectAddress),\n    /**\n     * Flow control receiver added.\n     */\n    FLOW_CONTROL_RECEIVER_ADDED(48, DriverEventDissector::dissectFlowControlReceiver),\n    /**\n     * Flow control receiver removed.\n     */\n    FLOW_CONTROL_RECEIVER_REMOVED(49, DriverEventDissector::dissectFlowControlReceiver),\n    /**\n     * Name resolution resolve.\n     */\n    NAME_RESOLUTION_RESOLVE(50,\n        (code, buffer, offset, builder) -> DriverEventDissector.dissectResolve(buffer, offset, builder)),\n    /**\n     * Free text event.\n     */\n    TEXT_DATA(51, DriverEventDissector::dissectString),\n    /**\n     * Name resolution lookup.\n     */\n    NAME_RESOLUTION_LOOKUP(52,\n        (code, buffer, offset, builder) -> DriverEventDissector.dissectLookup(buffer, offset, builder)),\n    /**\n     * Name resolution host name.\n     */\n    NAME_RESOLUTION_HOST_NAME(53,\n        (code, buffer, offset, builder) -> DriverEventDissector.dissectHostName(buffer, offset, builder)),\n\n    /**\n     * Nak sent.\n     */\n    NAK_SENT(54, DriverEventDissector::dissectNak),\n\n    /**\n     * Resend data upon Nak.\n     */\n    RESEND(55,\n        (code, buffer, offset, builder) -> DriverEventDissector.dissectResend(buffer, offset, builder)),\n\n    /**\n     * Remove destination by id.\n     */\n    CMD_IN_REMOVE_DESTINATION_BY_ID(56, DriverEventDissector::dissectCommand),\n\n    /**\n     * Reject image command received by the driver.\n     */\n    CMD_IN_REJECT_IMAGE(57, DriverEventDissector::dissectCommand),\n\n    /**\n     * Nak received.\n     */\n    NAK_RECEIVED(58, DriverEventDissector::dissectNak),\n\n    /**\n     * Publication revoked.\n     */\n    PUBLICATION_REVOKE(59,\n        (code, buffer, offset, builder) -> DriverEventDissector.dissectPublicationRevoke(buffer, offset, builder)),\n\n    /**\n     * Publication Image revoked.\n     */\n    PUBLICATION_IMAGE_REVOKE(60,\n        (code, buffer, offset, builder) -> DriverEventDissector.dissectPublicationImageRevoke(buffer, offset, builder));\n\n    static final int EVENT_CODE_TYPE = EventCodeType.DRIVER.getTypeCode();\n\n    private static final DriverEventCode[] EVENT_CODE_BY_ID;\n\n    private final int id;\n    private final DissectFunction<DriverEventCode> dissector;\n\n    static\n    {\n        final DriverEventCode[] codes = DriverEventCode.values();\n        final int maxId = Arrays.stream(codes).mapToInt(DriverEventCode::id).max().orElse(0);\n        EVENT_CODE_BY_ID = new DriverEventCode[maxId + 1];\n\n        for (final DriverEventCode code : codes)\n        {\n            final int id = code.id();\n            if (null != EVENT_CODE_BY_ID[id])\n            {\n                throw new IllegalArgumentException(\"id already in use: \" + id);\n            }\n\n            EVENT_CODE_BY_ID[id] = code;\n        }\n    }\n\n    DriverEventCode(final int id, final DissectFunction<DriverEventCode> dissector)\n    {\n        this.id = id;\n        this.dissector = dissector;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int id()\n    {\n        return id;\n    }\n\n    static DriverEventCode get(final int id)\n    {\n        if (id < 0 || id >= EVENT_CODE_BY_ID.length)\n        {\n            throw new IllegalArgumentException(\"no DriverEventCode for id: \" + id);\n        }\n\n        final DriverEventCode code = EVENT_CODE_BY_ID[id];\n\n        if (null == code)\n        {\n            throw new IllegalArgumentException(\"no DriverEventCode for id: \" + id);\n        }\n\n        return code;\n    }\n\n    static DriverEventCode get(final String name)\n    {\n        if (\"SEND_NAK_MESSAGE\".equals(name))\n        {\n            return NAK_SENT;\n        }\n        else\n        {\n            return DriverEventCode.valueOf(name);\n        }\n    }\n\n    /**\n     * Decode an event serialised in a buffer to a provided {@link StringBuilder}.\n     *\n     * @param buffer  containing the encoded event.\n     * @param offset  offset at which the event begins.\n     * @param builder to write the decoded event to.\n     */\n    public void decode(final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        dissector.dissect(this, buffer, offset, builder);\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/DriverEventDissector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.command.*;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.protocol.*;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.agent.CommonEventDissector.*;\nimport static io.aeron.agent.DriverEventCode.*;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.*;\n\n/**\n * Dissect encoded log events and append them to a provided {@link StringBuilder}.\n * <p>\n * <b>Note:</b>The event consumer of the log should be single threaded.\n */\nfinal class DriverEventDissector\n{\n    private static final DataHeaderFlyweight DATA_HEADER = new DataHeaderFlyweight();\n    private static final NakFlyweight NAK_HEADER = new NakFlyweight();\n    private static final StatusMessageFlyweight SM_HEADER = new StatusMessageFlyweight();\n    private static final ErrorFlyweight ERROR_HEADER = new ErrorFlyweight();\n    private static final SetupFlyweight SETUP_HEADER = new SetupFlyweight();\n    private static final RttMeasurementFlyweight RTT_MEASUREMENT = new RttMeasurementFlyweight();\n    private static final HeaderFlyweight HEADER = new HeaderFlyweight();\n    private static final ResolutionEntryFlyweight RESOLUTION = new ResolutionEntryFlyweight();\n    private static final ResponseSetupFlyweight RSP_SETUP = new ResponseSetupFlyweight();\n    private static final PublicationMessageFlyweight PUB_MSG = new PublicationMessageFlyweight();\n    private static final SubscriptionMessageFlyweight SUB_MSG = new SubscriptionMessageFlyweight();\n    private static final PublicationBuffersReadyFlyweight PUB_READY = new PublicationBuffersReadyFlyweight();\n    private static final ImageBuffersReadyFlyweight IMAGE_READY = new ImageBuffersReadyFlyweight();\n    private static final CorrelatedMessageFlyweight CORRELATED_MSG = new CorrelatedMessageFlyweight();\n    private static final ImageMessageFlyweight IMAGE_MSG = new ImageMessageFlyweight();\n    private static final RemoveCounterFlyweight REMOVE_COUNTER = new RemoveCounterFlyweight();\n    private static final RemovePublicationFlyweight REMOVE_PUBLICATION = new RemovePublicationFlyweight();\n    private static final RemoveSubscriptionFlyweight REMOVE_SUBSCRIPTION = new RemoveSubscriptionFlyweight();\n    private static final DestinationMessageFlyweight DESTINATION_MSG = new DestinationMessageFlyweight();\n    private static final ErrorResponseFlyweight ERROR_MSG = new ErrorResponseFlyweight();\n    private static final CounterMessageFlyweight COUNTER_MSG = new CounterMessageFlyweight();\n    private static final CounterUpdateFlyweight COUNTER_UPDATE = new CounterUpdateFlyweight();\n    private static final OperationSucceededFlyweight OPERATION_SUCCEEDED = new OperationSucceededFlyweight();\n    private static final SubscriptionReadyFlyweight SUBSCRIPTION_READY = new SubscriptionReadyFlyweight();\n    private static final ClientTimeoutFlyweight CLIENT_TIMEOUT = new ClientTimeoutFlyweight();\n    private static final TerminateDriverFlyweight TERMINATE_DRIVER = new TerminateDriverFlyweight();\n    private static final DestinationByIdMessageFlyweight DESTINATION_BY_ID = new DestinationByIdMessageFlyweight();\n    private static final RejectImageFlyweight REJECT_IMAGE = new RejectImageFlyweight();\n\n    static final String CONTEXT = \"DRIVER\";\n\n    private DriverEventDissector()\n    {\n    }\n\n    static void dissectFrame(\n        final DriverEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int encodedLength = dissectLogHeader(CONTEXT, eventCode, buffer, offset, builder);\n\n        builder.append(\": address=\");\n\n        encodedLength += dissectSocketAddress(buffer, offset + encodedLength, builder);\n\n        builder.append(\" \");\n\n        final int frameOffset = offset + encodedLength;\n        final int frameType = frameType(buffer, frameOffset);\n        switch (frameType)\n        {\n            case HeaderFlyweight.HDR_TYPE_PAD:\n            case HeaderFlyweight.HDR_TYPE_DATA:\n                DATA_HEADER.wrap(buffer, frameOffset, buffer.capacity() - frameOffset);\n                dissectDataFrame(builder);\n                break;\n\n            case HeaderFlyweight.HDR_TYPE_NAK:\n                NAK_HEADER.wrap(buffer, frameOffset, buffer.capacity() - frameOffset);\n                dissectNakFrame(builder);\n                break;\n\n            case HeaderFlyweight.HDR_TYPE_SM:\n                SM_HEADER.wrap(buffer, frameOffset, buffer.capacity() - frameOffset);\n                dissectStatusFrame(builder);\n                break;\n\n            case HeaderFlyweight.HDR_TYPE_ERR:\n                ERROR_HEADER.wrap(buffer, frameOffset, buffer.capacity() - frameOffset);\n                dissectErrorFrame(builder);\n                break;\n\n            case HeaderFlyweight.HDR_TYPE_SETUP:\n                SETUP_HEADER.wrap(buffer, frameOffset, buffer.capacity() - frameOffset);\n                dissectSetupFrame(builder);\n                break;\n\n            case HeaderFlyweight.HDR_TYPE_RTTM:\n                RTT_MEASUREMENT.wrap(buffer, frameOffset, buffer.capacity() - frameOffset);\n                dissectRttFrame(builder);\n                break;\n\n            case HeaderFlyweight.HDR_TYPE_RES:\n                dissectResFrame(buffer, frameOffset, builder);\n                break;\n\n            case HeaderFlyweight.HDR_TYPE_RSP_SETUP:\n                RSP_SETUP.wrap(buffer, frameOffset, buffer.capacity() - frameOffset);\n                dissectRspSetupFrame(builder);\n                break;\n\n            default:\n                builder.append(\"type=UNKNOWN(\").append(frameType).append(\")\");\n                break;\n        }\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    static void dissectCommand(\n        final DriverEventCode code, final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        final int encodedLength = dissectLogHeader(CONTEXT, code, buffer, offset, builder);\n        builder.append(\": \");\n\n        switch (code)\n        {\n            case CMD_IN_ADD_PUBLICATION:\n            case CMD_IN_ADD_EXCLUSIVE_PUBLICATION:\n                PUB_MSG.wrap(buffer, offset + encodedLength);\n                dissectPublication(builder);\n                break;\n\n            case CMD_IN_ADD_SUBSCRIPTION:\n                SUB_MSG.wrap(buffer, offset + encodedLength);\n                dissectSubscription(builder);\n                break;\n\n            case CMD_IN_REMOVE_PUBLICATION:\n                REMOVE_PUBLICATION.wrap(buffer, offset + encodedLength);\n                final int captureLength = buffer.getInt(offset, LITTLE_ENDIAN);\n                dissectRemovePublicationEvent(builder, captureLength);\n                break;\n\n            case CMD_IN_REMOVE_SUBSCRIPTION:\n                REMOVE_SUBSCRIPTION.wrap(buffer, offset + encodedLength);\n                dissectRemoveSubscriptionEvent(builder);\n                break;\n\n            case CMD_IN_REMOVE_COUNTER:\n                REMOVE_COUNTER.wrap(buffer, offset + encodedLength);\n                dissectRemoveCounterEvent(builder);\n                break;\n\n            case CMD_OUT_PUBLICATION_READY:\n            case CMD_OUT_EXCLUSIVE_PUBLICATION_READY:\n                PUB_READY.wrap(buffer, offset + encodedLength);\n                dissectPublicationReady(builder);\n                break;\n\n            case CMD_OUT_AVAILABLE_IMAGE:\n                IMAGE_READY.wrap(buffer, offset + encodedLength);\n                dissectImageReady(builder);\n                break;\n\n            case CMD_OUT_ON_OPERATION_SUCCESS:\n                OPERATION_SUCCEEDED.wrap(buffer, offset + encodedLength);\n                dissectOperationSuccess(builder);\n                break;\n\n            case CMD_IN_KEEPALIVE_CLIENT:\n            case CMD_IN_CLIENT_CLOSE:\n                CORRELATED_MSG.wrap(buffer, offset + encodedLength);\n                dissectCorrelationEvent(builder);\n                break;\n\n            case CMD_OUT_ON_UNAVAILABLE_IMAGE:\n                IMAGE_MSG.wrap(buffer, offset + encodedLength);\n                dissectImage(builder);\n                break;\n\n            case CMD_IN_ADD_DESTINATION:\n            case CMD_IN_REMOVE_DESTINATION:\n            case CMD_IN_ADD_RCV_DESTINATION:\n            case CMD_IN_REMOVE_RCV_DESTINATION:\n                DESTINATION_MSG.wrap(buffer, offset + encodedLength);\n                dissectDestination(builder);\n                break;\n\n            case CMD_OUT_ERROR:\n                ERROR_MSG.wrap(buffer, offset + encodedLength);\n                dissectError(builder);\n                break;\n\n            case CMD_IN_ADD_COUNTER:\n                COUNTER_MSG.wrap(buffer, offset + encodedLength);\n                dissectCounter(builder);\n                break;\n\n            case CMD_OUT_SUBSCRIPTION_READY:\n                SUBSCRIPTION_READY.wrap(buffer, offset + encodedLength);\n                dissectSubscriptionReady(builder);\n                break;\n\n            case CMD_OUT_COUNTER_READY:\n            case CMD_OUT_ON_UNAVAILABLE_COUNTER:\n                COUNTER_UPDATE.wrap(buffer, offset + encodedLength);\n                dissectCounterUpdate(builder);\n                break;\n\n            case CMD_OUT_ON_CLIENT_TIMEOUT:\n                CLIENT_TIMEOUT.wrap(buffer, offset + encodedLength);\n                dissectClientTimeout(builder);\n                break;\n\n            case CMD_IN_TERMINATE_DRIVER:\n                TERMINATE_DRIVER.wrap(buffer, offset + encodedLength);\n                dissectTerminateDriver(builder);\n                break;\n\n            case CMD_IN_REMOVE_DESTINATION_BY_ID:\n                DESTINATION_BY_ID.wrap(buffer, offset + encodedLength);\n                dissectDestinationById(builder);\n                break;\n\n            case CMD_IN_REJECT_IMAGE:\n                REJECT_IMAGE.wrap(buffer, offset + encodedLength);\n                dissectRejectImage(builder);\n                break;\n\n            default:\n                builder.append(\"COMMAND_UNKNOWN: \").append(code);\n                break;\n        }\n    }\n\n    static void dissectString(\n        final DriverEventCode code, final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        final int encodedLength = dissectLogHeader(CONTEXT, code, buffer, offset, builder);\n        builder.append(\": \").append(buffer.getStringAscii(offset + encodedLength, LITTLE_ENDIAN));\n    }\n\n    static void dissectRemovePublicationCleanup(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, REMOVE_PUBLICATION_CLEANUP, buffer, absoluteOffset, builder);\n\n        builder.append(\": sessionId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" streamId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" channel=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectRemoveSubscriptionCleanup(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, REMOVE_SUBSCRIPTION_CLEANUP, buffer, absoluteOffset, builder);\n\n        builder.append(\": streamId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" id=\").append(buffer.getLong(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\" channel=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectRemoveImageCleanup(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, REMOVE_IMAGE_CLEANUP, buffer, absoluteOffset, builder);\n\n        builder.append(\": sessionId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" streamId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" id=\").append(buffer.getLong(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\" channel=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectUntetheredSubscriptionStateChange(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(\n            CONTEXT, UNTETHERED_SUBSCRIPTION_STATE_CHANGE, buffer, absoluteOffset, builder);\n\n        builder.append(\": subscriptionId=\").append(buffer.getLong(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\" streamId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" sessionId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" \");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectAddress(\n        final DriverEventCode code, final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, code, buffer, absoluteOffset, builder);\n\n        builder.append(\": \");\n        dissectSocketAddress(buffer, absoluteOffset, builder);\n    }\n\n    static void dissectFlowControlReceiver(\n        final DriverEventCode code, final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, code, buffer, absoluteOffset, builder);\n\n        builder.append(\": receiverCount=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" receiverId=\").append(buffer.getLong(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\" sessionId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" streamId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" channel=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectResolve(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, NAME_RESOLUTION_RESOLVE, buffer, absoluteOffset, builder);\n\n        final boolean isReResolution = 1 == buffer.getByte(absoluteOffset);\n        absoluteOffset += SIZE_OF_BYTE;\n\n        final long durationNs = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\": resolver=\");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" durationNs=\").append(durationNs);\n\n        builder.append(\" name=\");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" isReResolution=\").append(isReResolution);\n\n        builder.append(\" address=\");\n        dissectInetAddress(buffer, absoluteOffset, builder);\n    }\n\n    static void dissectLookup(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, NAME_RESOLUTION_LOOKUP, buffer, absoluteOffset, builder);\n\n        final boolean isReLookup = 1 == buffer.getByte(absoluteOffset);\n        absoluteOffset += SIZE_OF_BYTE;\n\n        final long durationNs = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\": resolver=\");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" durationNs=\").append(durationNs);\n\n        builder.append(\" name=\");\n        absoluteOffset += buffer.getStringAscii(absoluteOffset, builder);\n        absoluteOffset += SIZE_OF_INT;\n\n        builder.append(\" isReLookup=\").append(isReLookup);\n\n        builder.append(\" resolvedName=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectHostName(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, NAME_RESOLUTION_HOST_NAME, buffer, absoluteOffset, builder);\n\n        builder.append(\": durationNs=\").append(buffer.getLong(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_LONG;\n\n        builder.append(\" hostName=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectNak(\n        final DriverEventCode eventCode,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, eventCode, buffer, absoluteOffset, builder);\n        builder.append(\": address=\");\n        final int encodedSocketLength = dissectSocketAddress(buffer, absoluteOffset, builder);\n        absoluteOffset += encodedSocketLength;\n        builder.append(\" sessionId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" streamId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" termId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" termOffset=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" length=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" channel=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectResend(final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, RESEND, buffer, absoluteOffset, builder);\n        builder.append(\": sessionId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" streamId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" termId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" termOffset=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" length=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" channel=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectPublicationRevoke(\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, PUBLICATION_REVOKE, buffer, absoluteOffset, builder);\n        builder.append(\": revokedPos=\").append(buffer.getLong(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_LONG;\n        builder.append(\" sessionId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" streamId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" channel=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static void dissectPublicationImageRevoke(\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final StringBuilder builder)\n    {\n        int absoluteOffset = offset;\n        absoluteOffset += dissectLogHeader(CONTEXT, PUBLICATION_IMAGE_REVOKE, buffer, absoluteOffset, builder);\n        builder.append(\": revokedPos=\").append(buffer.getLong(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_LONG;\n        builder.append(\" sessionId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" streamId=\").append(buffer.getInt(absoluteOffset, LITTLE_ENDIAN));\n        absoluteOffset += SIZE_OF_INT;\n        builder.append(\" channel=\");\n        buffer.getStringAscii(absoluteOffset, builder);\n    }\n\n    static int frameType(final MutableDirectBuffer buffer, final int termOffset)\n    {\n        return buffer.getShort(FrameDescriptor.typeOffset(termOffset), LITTLE_ENDIAN) & 0xFFFF;\n    }\n\n    private static void dissectDataFrame(final StringBuilder builder)\n    {\n        builder\n            .append(\"type=\")\n            .append(DATA_HEADER.headerType() == HeaderFlyweight.HDR_TYPE_PAD ? \"PAD\" : \"DATA\")\n            .append(\" flags=\");\n\n        HeaderFlyweight.appendFlagsAsChars(DATA_HEADER.flags(), builder);\n\n        builder\n            .append(\" frameLength=\")\n            .append(DATA_HEADER.frameLength())\n            .append(\" sessionId=\")\n            .append(DATA_HEADER.sessionId())\n            .append(\" streamId=\")\n            .append(DATA_HEADER.streamId())\n            .append(\" termId=\")\n            .append(DATA_HEADER.termId())\n            .append(\" termOffset=\")\n            .append(DATA_HEADER.termOffset());\n    }\n\n    private static void dissectStatusFrame(final StringBuilder builder)\n    {\n        builder.append(\"type=SM flags=\");\n        HeaderFlyweight.appendFlagsAsChars(SM_HEADER.flags(), builder);\n\n        builder\n            .append(\" frameLength=\")\n            .append(SM_HEADER.frameLength())\n            .append(\" sessionId=\")\n            .append(SM_HEADER.sessionId())\n            .append(\" streamId=\")\n            .append(SM_HEADER.streamId())\n            .append(\" termId=\")\n            .append(SM_HEADER.consumptionTermId())\n            .append(\" termOffset=\")\n            .append(SM_HEADER.consumptionTermOffset())\n            .append(\" receiverWindowLength=\")\n            .append(SM_HEADER.receiverWindowLength())\n            .append(\" receiverId=\")\n            .append(SM_HEADER.receiverId());\n    }\n\n    private static void dissectNakFrame(final StringBuilder builder)\n    {\n        builder.append(\"type=NAK flags=\");\n        HeaderFlyweight.appendFlagsAsChars(NAK_HEADER.flags(), builder);\n\n        builder\n            .append(\" frameLength=\")\n            .append(NAK_HEADER.frameLength())\n            .append(\" sessionId=\")\n            .append(NAK_HEADER.sessionId())\n            .append(\" streamId=\")\n            .append(NAK_HEADER.streamId())\n            .append(\" termId=\")\n            .append(NAK_HEADER.termId())\n            .append(\" termOffset=\")\n            .append(NAK_HEADER.termOffset())\n            .append(\" length=\")\n            .append(NAK_HEADER.length());\n    }\n\n    private static void dissectErrorFrame(final StringBuilder builder)\n    {\n        builder.append(\"type=ERR flags=\");\n        HeaderFlyweight.appendFlagsAsChars(ERROR_HEADER.flags(), builder);\n\n        builder\n            .append(\" frameLength=\")\n            .append(ERROR_HEADER.frameLength())\n            .append(\" sessionId=\")\n            .append(ERROR_HEADER.sessionId())\n            .append(\" streamId=\")\n            .append(ERROR_HEADER.streamId())\n            .append(\" receiverId=\")\n            .append(ERROR_HEADER.receiverId())\n            .append(\" groupTag=\")\n            .append(ERROR_HEADER.groupTag())\n            .append(\" errorCode=\")\n            .append(ERROR_HEADER.errorCode())\n            .append(\" errorMessage=\\\"\")\n            .append(ERROR_HEADER.errorMessage())\n            .append('\"');\n    }\n\n    private static void dissectSetupFrame(final StringBuilder builder)\n    {\n        builder.append(\"type=SETUP flags=\");\n        HeaderFlyweight.appendFlagsAsChars(SETUP_HEADER.flags(), builder);\n\n        builder\n            .append(\" frameLength=\")\n            .append(SETUP_HEADER.frameLength())\n            .append(\" sessionId=\")\n            .append(SETUP_HEADER.sessionId())\n            .append(\" streamId=\")\n            .append(SETUP_HEADER.streamId())\n            .append(\" activeTermId=\")\n            .append(SETUP_HEADER.activeTermId())\n            .append(\" initialTermId=\")\n            .append(SETUP_HEADER.initialTermId())\n            .append(\" termOffset=\")\n            .append(SETUP_HEADER.termOffset())\n            .append(\" termLength=\")\n            .append(SETUP_HEADER.termLength())\n            .append(\" mtu=\")\n            .append(SETUP_HEADER.mtuLength())\n            .append(\" ttl=\")\n            .append(SETUP_HEADER.ttl());\n    }\n\n    private static void dissectRttFrame(final StringBuilder builder)\n    {\n        builder.append(\"type=RTT flags=\");\n        HeaderFlyweight.appendFlagsAsChars(RTT_MEASUREMENT.flags(), builder);\n\n        builder\n            .append(\" frameLength=\")\n            .append(RTT_MEASUREMENT.frameLength())\n            .append(\" sessionId=\")\n            .append(RTT_MEASUREMENT.sessionId())\n            .append(\" streamId=\")\n            .append(RTT_MEASUREMENT.streamId())\n            .append(\" echoTimestampNs=\")\n            .append(RTT_MEASUREMENT.echoTimestampNs())\n            .append(\" receptionDelta=\")\n            .append(RTT_MEASUREMENT.receptionDelta())\n            .append(\" receiverId=\")\n            .append(RTT_MEASUREMENT.receiverId());\n    }\n\n    private static void dissectResFrame(\n        final MutableDirectBuffer buffer, final int offset, final StringBuilder builder)\n    {\n        int currentOffset = offset;\n\n        HEADER.wrap(buffer, offset, buffer.capacity() - offset);\n        final int length = offset + Math.min(HEADER.frameLength(), CommonEventEncoder.MAX_CAPTURE_LENGTH);\n        currentOffset += HeaderFlyweight.MIN_HEADER_LENGTH;\n\n        builder.append(\"type=RES flags=\");\n        HeaderFlyweight.appendFlagsAsChars(HEADER.flags(), builder);\n\n        builder\n            .append(\" frameLength=\")\n            .append(HEADER.frameLength());\n\n        while (length > currentOffset)\n        {\n            RESOLUTION.wrap(buffer, currentOffset, buffer.capacity() - currentOffset);\n\n            if ((length - offset) < RESOLUTION.entryLength())\n            {\n                builder.append(\" ... \").append(length - offset).append(\" bytes left\");\n                break;\n            }\n\n            dissectResEntry(builder);\n\n            currentOffset += RESOLUTION.entryLength();\n        }\n    }\n\n    private static void dissectRspSetupFrame(final StringBuilder builder)\n    {\n        builder.append(\"type=RSP_SETUP flags=\");\n        HeaderFlyweight.appendFlagsAsChars(RSP_SETUP.flags(), builder);\n\n        builder\n            .append(\" frameLength=\")\n            .append(RSP_SETUP.frameLength())\n            .append(\" sessionId=\")\n            .append(RSP_SETUP.sessionId())\n            .append(\" streamId=\")\n            .append(RSP_SETUP.streamId())\n            .append(\" responseSessionId=\")\n            .append(RSP_SETUP.responseSessionId());\n    }\n\n    private static void dissectResEntry(final StringBuilder builder)\n    {\n        builder\n            .append(\" [resType=\")\n            .append(RESOLUTION.resType())\n            .append(\" flags=\");\n\n        HeaderFlyweight.appendFlagsAsChars(RESOLUTION.flags(), builder);\n\n        builder\n            .append(\" port=\")\n            .append(RESOLUTION.udpPort())\n            .append(\" ageInMs=\")\n            .append(RESOLUTION.ageInMs());\n\n        builder.append(\" address=\");\n        RESOLUTION.appendAddress(builder);\n\n        builder.append(\" name=\");\n        RESOLUTION.appendName(builder);\n        builder.append(']');\n    }\n\n    private static void dissectPublication(final StringBuilder builder)\n    {\n        builder\n            .append(\"streamId=\").append(PUB_MSG.streamId())\n            .append(\" clientId=\").append(PUB_MSG.clientId())\n            .append(\" correlationId=\").append(PUB_MSG.correlationId())\n            .append(\" channel=\");\n\n        PUB_MSG.appendChannel(builder);\n    }\n\n    private static void dissectSubscription(final StringBuilder builder)\n    {\n        builder\n            .append(\"streamId=\").append(SUB_MSG.streamId())\n            .append(\" registrationCorrelationId=\").append(SUB_MSG.registrationCorrelationId())\n            .append(\" clientId=\").append(SUB_MSG.clientId())\n            .append(\" correlationId=\").append(SUB_MSG.correlationId())\n            .append(\" channel=\");\n\n        SUB_MSG.appendChannel(builder);\n    }\n\n    private static void dissectPublicationReady(final StringBuilder builder)\n    {\n        builder\n            .append(\"sessionId=\").append(PUB_READY.sessionId())\n            .append(\" streamId=\").append(PUB_READY.streamId())\n            .append(\" publicationLimitCounterId=\").append(PUB_READY.publicationLimitCounterId())\n            .append(\" channelStatusCounterId=\").append(PUB_READY.channelStatusCounterId())\n            .append(\" correlationId=\").append(PUB_READY.correlationId())\n            .append(\" registrationId=\").append(PUB_READY.registrationId())\n            .append(\" logFileName=\");\n\n        PUB_READY.appendLogFileName(builder);\n    }\n\n    private static void dissectImageReady(final StringBuilder builder)\n    {\n        builder\n            .append(\"sessionId=\").append(IMAGE_READY.sessionId())\n            .append(\" streamId=\").append(IMAGE_READY.streamId())\n            .append(\" subscriberPositionId=\").append(IMAGE_READY.subscriberPositionId())\n            .append(\" subscriptionRegistrationId=\").append(IMAGE_READY.subscriptionRegistrationId())\n            .append(\" correlationId=\").append(IMAGE_READY.correlationId());\n\n        builder.append(\" sourceIdentity=\");\n        IMAGE_READY.appendSourceIdentity(builder);\n        builder.append(\" logFileName=\");\n        IMAGE_READY.appendLogFileName(builder);\n    }\n\n    private static void dissectCorrelationEvent(final StringBuilder builder)\n    {\n        builder\n            .append(\"clientId=\").append(CORRELATED_MSG.clientId())\n            .append(\" correlationId=\").append(CORRELATED_MSG.correlationId());\n    }\n\n    private static void dissectImage(final StringBuilder builder)\n    {\n        builder\n            .append(\"streamId=\").append(IMAGE_MSG.streamId())\n            .append(\" correlationId=\").append(IMAGE_MSG.correlationId())\n            .append(\" subscriptionRegistrationId=\")\n            .append(IMAGE_MSG.subscriptionRegistrationId())\n            .append(\" channel=\");\n\n        IMAGE_MSG.appendChannel(builder);\n    }\n\n    private static void dissectRemoveCounterEvent(final StringBuilder builder)\n    {\n        builder\n            .append(\"registrationId=\").append(REMOVE_COUNTER.registrationId())\n            .append(\" clientId=\").append(REMOVE_COUNTER.clientId())\n            .append(\" correlationId=\").append(REMOVE_COUNTER.correlationId());\n    }\n\n    private static void dissectRemovePublicationEvent(final StringBuilder builder, final int captureLength)\n    {\n        builder\n            .append(\"registrationId=\").append(REMOVE_PUBLICATION.registrationId())\n            .append(\" clientId=\").append(REMOVE_PUBLICATION.clientId())\n            .append(\" correlationId=\").append(REMOVE_PUBLICATION.correlationId());\n\n        if (REMOVE_PUBLICATION.flagsFieldIsValid(captureLength))\n        {\n            builder.append(\" revoke=\").append(REMOVE_PUBLICATION.revoke());\n        }\n    }\n\n    private static void dissectRemoveSubscriptionEvent(final StringBuilder builder)\n    {\n        builder\n            .append(\"registrationId=\").append(REMOVE_SUBSCRIPTION.registrationId())\n            .append(\" clientId=\").append(REMOVE_SUBSCRIPTION.clientId())\n            .append(\" correlationId=\").append(REMOVE_SUBSCRIPTION.correlationId());\n    }\n\n    private static void dissectDestination(final StringBuilder builder)\n    {\n        builder\n            .append(\"registrationCorrelationId=\").append(DESTINATION_MSG.registrationCorrelationId())\n            .append(\" clientId=\").append(DESTINATION_MSG.clientId())\n            .append(\" correlationId=\").append(DESTINATION_MSG.correlationId())\n            .append(\" channel=\");\n\n        DESTINATION_MSG.appendChannel(builder);\n    }\n\n    private static void dissectError(final StringBuilder builder)\n    {\n        builder\n            .append(\"offendingCommandCorrelationId=\").append(ERROR_MSG.offendingCommandCorrelationId())\n            .append(\" errorCode=\").append(ERROR_MSG.errorCode())\n            .append(\" message=\");\n\n        ERROR_MSG.appendMessage(builder);\n    }\n\n    private static void dissectCounter(final StringBuilder builder)\n    {\n        builder\n            .append(\"typeId=\").append(COUNTER_MSG.typeId())\n            .append(\" keyBufferOffset=\").append(COUNTER_MSG.keyBufferOffset())\n            .append(\" keyBufferLength=\").append(COUNTER_MSG.keyBufferLength())\n            .append(\" labelBufferOffset=\").append(COUNTER_MSG.labelBufferOffset())\n            .append(\" labelBufferLength=\").append(COUNTER_MSG.labelBufferLength())\n            .append(\" clientId=\").append(COUNTER_MSG.clientId())\n            .append(\" correlationId=\").append(COUNTER_MSG.correlationId());\n    }\n\n    private static void dissectCounterUpdate(final StringBuilder builder)\n    {\n        builder\n            .append(\"correlationId=\").append(COUNTER_UPDATE.correlationId())\n            .append(\" counterId=\").append(COUNTER_UPDATE.counterId());\n    }\n\n    private static void dissectOperationSuccess(final StringBuilder builder)\n    {\n        builder.append(\"correlationId=\").append(OPERATION_SUCCEEDED.correlationId());\n    }\n\n    private static void dissectSubscriptionReady(final StringBuilder builder)\n    {\n        builder\n            .append(\"correlationId=\").append(SUBSCRIPTION_READY.correlationId())\n            .append(\" channelStatusCounterId=\").append(SUBSCRIPTION_READY.channelStatusCounterId());\n    }\n\n    private static void dissectClientTimeout(final StringBuilder builder)\n    {\n        builder.append(\"clientId=\").append(CLIENT_TIMEOUT.clientId());\n    }\n\n    private static void dissectTerminateDriver(final StringBuilder builder)\n    {\n        builder\n            .append(\"clientId=\").append(TERMINATE_DRIVER.clientId())\n            .append(\" tokenBufferLength=\").append(TERMINATE_DRIVER.tokenBufferLength());\n    }\n\n    private static void dissectDestinationById(final StringBuilder builder)\n    {\n        builder\n            .append(\"resourceRegistrationId=\").append(DESTINATION_BY_ID.resourceRegistrationId())\n            .append(\" destinationRegistrationId=\").append(DESTINATION_BY_ID.destinationRegistrationId());\n    }\n\n    private static void dissectRejectImage(final StringBuilder builder)\n    {\n        builder\n            .append(\"clientId=\").append(REJECT_IMAGE.clientId())\n            .append(\" correlationId=\").append(REJECT_IMAGE.correlationId())\n            .append(\" imageCorrelationId=\").append(REJECT_IMAGE.imageCorrelationId())\n            .append(\" position=\").append(REJECT_IMAGE.position())\n            .append(\" reason=\").append(REJECT_IMAGE.reason());\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/DriverEventEncoder.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.agent.CommonEventEncoder.*;\nimport static io.aeron.agent.DriverEventLogger.MAX_HOST_NAME_LENGTH;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.*;\n\n/**\n * Encoding of event types to a {@link UnsafeBuffer} for logging.\n */\nfinal class DriverEventEncoder\n{\n    private DriverEventEncoder()\n    {\n    }\n\n    static void encode(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final ByteBuffer srcBuffer,\n        final int srcOffset,\n        final InetSocketAddress dstAddress)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        final int encodedSocketLength = encodeSocketAddress(encodingBuffer, offset + encodedLength, dstAddress);\n        encodedLength += encodedSocketLength;\n\n        final int bufferCaptureLength = captureLength - encodedSocketLength;\n        encodingBuffer.putBytes(offset + encodedLength, srcBuffer, srcOffset, bufferCaptureLength);\n    }\n\n    static void encode(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final DirectBuffer srcBuffer,\n        final int srcOffset,\n        final InetSocketAddress dstAddress)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        final int encodedSocketLength = encodeSocketAddress(encodingBuffer, offset + encodedLength, dstAddress);\n        encodedLength += encodedSocketLength;\n\n        final int bufferCaptureLength = captureLength - encodedSocketLength;\n        encodingBuffer.putBytes(offset + encodedLength, srcBuffer, srcOffset, bufferCaptureLength);\n    }\n\n    static void encode(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final DirectBuffer srcBuffer,\n        final int srcOffset,\n        final InetAddress dstAddress)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        final int encodedInetAddressLength = encodeInetAddress(encodingBuffer, offset + encodedLength, dstAddress);\n        encodedLength += encodedInetAddressLength;\n\n        final int bufferCaptureLength = captureLength - encodedInetAddressLength;\n        encodingBuffer.putBytes(offset + encodedLength, srcBuffer, srcOffset, bufferCaptureLength);\n    }\n\n    public static void encode(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final String value)\n    {\n        final int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        encodeTrailingString(encodingBuffer, offset + encodedLength, captureLength, value);\n    }\n\n    static void encode(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final InetSocketAddress address)\n    {\n        final int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        encodeSocketAddress(encodingBuffer, offset + encodedLength, address);\n    }\n\n    static void encodePublicationRemoval(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final String channel,\n        final int sessionId,\n        final int streamId)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putInt(offset + encodedLength, sessionId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, streamId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodeTrailingString(encodingBuffer, offset + encodedLength, captureLength - SIZE_OF_INT * 2, channel);\n    }\n\n    static void encodeSubscriptionRemoval(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final String channel,\n        final int streamId,\n        final long id)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putInt(offset + encodedLength, streamId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putLong(offset + encodedLength, id, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodeTrailingString(\n            encodingBuffer, offset + encodedLength, captureLength - SIZE_OF_INT - SIZE_OF_LONG, channel);\n    }\n\n    static void encodeImageRemoval(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final String channel,\n        final int sessionId,\n        final int streamId,\n        final long id)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putInt(offset + encodedLength, sessionId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, streamId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putLong(offset + encodedLength, id, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodeTrailingString(\n            encodingBuffer, offset + encodedLength, captureLength - SIZE_OF_INT * 2 - SIZE_OF_LONG, channel);\n    }\n\n    static <E extends Enum<E>> int untetheredSubscriptionStateChangeLength(final E from, final E to)\n    {\n        return stateTransitionStringLength(from, to) + SIZE_OF_LONG + 2 * SIZE_OF_INT;\n    }\n\n    static <E extends Enum<E>> void encodeUntetheredSubscriptionStateChange(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final E from,\n        final E to,\n        final long subscriptionId,\n        final int streamId,\n        final int sessionId)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, subscriptionId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(offset + encodedLength, streamId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, sessionId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodeTrailingStateChange(encodingBuffer, offset, encodedLength, captureLength, from, to);\n    }\n\n    static void encodeFlowControlReceiver(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int captureLength,\n        final int length,\n        final long receiverId,\n        final int sessionId,\n        final int streamId,\n        final String channel,\n        final int receiverCount)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putInt(offset + encodedLength, receiverCount, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putLong(offset + encodedLength, receiverId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodingBuffer.putInt(offset + encodedLength, sessionId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodingBuffer.putInt(offset + encodedLength, streamId, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_INT;\n\n        encodeTrailingString(\n            encodingBuffer, offset + encodedLength, captureLength - SIZE_OF_INT * 3 - SIZE_OF_LONG, channel);\n    }\n\n    static void encodeResolve(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int length,\n        final int captureLength,\n        final String resolverName,\n        final long durationNs,\n        final String hostName,\n        final boolean isReResolution,\n        final InetAddress inetAddress)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putByte(offset + encodedLength, (byte)(isReResolution ? 1 : 0));\n        encodedLength += SIZE_OF_BOOLEAN;\n\n        encodingBuffer.putLong(offset + encodedLength, durationNs, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodedLength += encodeTrailingString(\n            encodingBuffer, offset + encodedLength, SIZE_OF_INT + MAX_HOST_NAME_LENGTH, resolverName);\n\n        encodedLength += encodeTrailingString(\n            encodingBuffer, offset + encodedLength, SIZE_OF_INT + MAX_HOST_NAME_LENGTH, hostName);\n\n        encodeInetAddress(encodingBuffer, offset + encodedLength, inetAddress);\n    }\n\n    static void encodeLookup(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int length,\n        final int captureLength,\n        final String resolverName,\n        final long durationNs,\n        final String name,\n        final boolean isReLookup,\n        final String resolvedName)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putByte(offset + encodedLength, (byte)(isReLookup ? 1 : 0));\n        encodedLength += SIZE_OF_BOOLEAN;\n\n        encodingBuffer.putLong(offset + encodedLength, durationNs, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodedLength += encodeTrailingString(\n            encodingBuffer, offset + encodedLength, SIZE_OF_INT + MAX_HOST_NAME_LENGTH, resolverName);\n\n        encodedLength += encodeTrailingString(\n            encodingBuffer, offset + encodedLength, SIZE_OF_INT + MAX_HOST_NAME_LENGTH, name);\n\n        encodeTrailingString(\n            encodingBuffer, offset + encodedLength, SIZE_OF_INT + MAX_HOST_NAME_LENGTH, resolvedName);\n    }\n\n    static void encodeHostName(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int length,\n        final int captureLength,\n        final long durationNs,\n        final String hostName)\n    {\n        int encodedLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n\n        encodingBuffer.putLong(offset + encodedLength, durationNs, LITTLE_ENDIAN);\n        encodedLength += SIZE_OF_LONG;\n\n        encodedLength += encodeTrailingString(\n            encodingBuffer, offset + encodedLength, SIZE_OF_INT + MAX_HOST_NAME_LENGTH, hostName);\n    }\n\n    static void encodeNakMessage(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int length,\n        final int captureLength,\n        final InetSocketAddress address,\n        final int sessionId,\n        final int streamId,\n        final int termId,\n        final int termOffset,\n        final int nakLength, final String channel)\n    {\n        final int headerLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + headerLength;\n        int bodyLength = 0;\n        final int socketEncodedLength = encodeSocketAddress(encodingBuffer, bodyOffset + bodyLength, address);\n        bodyLength += socketEncodedLength;\n\n        encodingBuffer.putInt(bodyOffset + bodyLength, sessionId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, streamId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, termId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, termOffset, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, nakLength, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodeTrailingString(encodingBuffer, bodyOffset + bodyLength, captureLength - bodyLength, channel);\n    }\n\n    static void encodeResend(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int length,\n        final int captureLength,\n        final int sessionId,\n        final int streamId,\n        final int termId,\n        final int termOffset,\n        final int nakLength,\n        final String channel)\n    {\n        final int headerLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + headerLength;\n\n        int bodyLength = 0;\n        encodingBuffer.putInt(bodyOffset + bodyLength, sessionId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, streamId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, termId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, termOffset, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, nakLength, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodeTrailingString(encodingBuffer, bodyOffset + bodyLength, captureLength - bodyLength, channel);\n    }\n\n    static void encodePublicationRevoke(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int length,\n        final int captureLength,\n        final long revokedPos,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int headerLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + headerLength;\n\n        int bodyLength = 0;\n        encodingBuffer.putLong(bodyOffset + bodyLength, revokedPos, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putInt(bodyOffset + bodyLength, sessionId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, streamId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodeTrailingString(encodingBuffer, bodyOffset + bodyLength, captureLength - bodyLength, channel);\n    }\n\n    static void encodePublicationImageRevoke(\n        final UnsafeBuffer encodingBuffer,\n        final int offset,\n        final int length,\n        final int captureLength,\n        final long revokedPos,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int headerLength = encodeLogHeader(encodingBuffer, offset, captureLength, length);\n        final int bodyOffset = offset + headerLength;\n\n        int bodyLength = 0;\n        encodingBuffer.putLong(bodyOffset + bodyLength, revokedPos, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_LONG;\n        encodingBuffer.putInt(bodyOffset + bodyLength, sessionId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodingBuffer.putInt(bodyOffset + bodyLength, streamId, LITTLE_ENDIAN);\n        bodyLength += SIZE_OF_INT;\n        encodeTrailingString(encodingBuffer, bodyOffset + bodyLength, captureLength - bodyLength, channel);\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/DriverEventLogger.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.agent.CommonEventEncoder.encode;\nimport static io.aeron.agent.CommonEventEncoder.*;\nimport static io.aeron.agent.DriverEventCode.*;\nimport static io.aeron.agent.DriverEventEncoder.encode;\nimport static io.aeron.agent.DriverEventEncoder.*;\nimport static io.aeron.agent.EventConfiguration.EVENT_RING_BUFFER;\nimport static org.agrona.BitUtil.*;\n\n/**\n * Event logger interface used by interceptors for recording into a {@link RingBuffer} for a\n * {@link io.aeron.driver.MediaDriver} via a Java Agent.\n */\npublic final class DriverEventLogger\n{\n    /**\n     * Logger for writing into the {@link EventConfiguration#EVENT_RING_BUFFER}.\n     */\n    public static final DriverEventLogger LOGGER = new DriverEventLogger(EVENT_RING_BUFFER);\n\n    /**\n     * Maximum length of a host name.\n     */\n    public static final int MAX_HOST_NAME_LENGTH = 256;\n\n    /**\n     * Maximum length of a Channel URI.\n     */\n    public static final int MAX_CHANNEL_URI_LENGTH = 4096;\n\n    private final ManyToOneRingBuffer ringBuffer;\n\n    DriverEventLogger(final ManyToOneRingBuffer ringBuffer)\n    {\n        this.ringBuffer = ringBuffer;\n    }\n\n    /**\n     * Log an event for the driver.\n     *\n     * @param code   for the type of event.\n     * @param buffer containing the encoded event.\n     * @param offset in the buffer at which the event begins.\n     * @param length of the encoded event.\n     */\n    public void log(final DriverEventCode code, final DirectBuffer buffer, final int offset, final int length)\n    {\n        if (DriverComponentLogger.ENABLED_EVENTS.contains(code))\n        {\n            final int captureLength = captureLength(length);\n            final int encodedLength = encodedLength(captureLength);\n\n            final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n            final int index = ringBuffer.tryClaim(toEventCodeId(code), encodedLength);\n            if (index > 0)\n            {\n                try\n                {\n                    encode((UnsafeBuffer)ringBuffer.buffer(), index, captureLength, length, buffer, offset);\n                }\n                finally\n                {\n                    ringBuffer.commit(index);\n                }\n            }\n        }\n    }\n\n    /**\n     * Log a frame coming in from the media.\n     *\n     * @param buffer      containing the frame.\n     * @param offset      in the buffer at which the frame begins.\n     * @param frameLength of the frame.\n     * @param dstAddress  for the frame.\n     */\n    public void logFrameIn(\n        final DirectBuffer buffer, final int offset, final int frameLength, final InetSocketAddress dstAddress)\n    {\n        final int length = frameLength + socketAddressLength(dstAddress);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(FRAME_IN), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encode((UnsafeBuffer)ringBuffer.buffer(), index, captureLength, length, buffer, offset, dstAddress);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a frame being sent out from the driver to the media.\n     *\n     * @param buffer     containing the frame.\n     * @param dstAddress for the frame.\n     */\n    public void logFrameOut(final ByteBuffer buffer, final InetSocketAddress dstAddress)\n    {\n        final int length = buffer.remaining() + socketAddressLength(dstAddress);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(FRAME_OUT), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encode(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    buffer,\n                    buffer.position(),\n                    dstAddress);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the removal of a publication.\n     *\n     * @param channel   for the channel.\n     * @param sessionId for the publication.\n     * @param streamId  within the channel.\n     */\n    public void logPublicationRemoval(final String channel, final int sessionId, final int streamId)\n    {\n        final int length = SIZE_OF_INT * 3 + channel.length();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(REMOVE_PUBLICATION_CLEANUP), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                final UnsafeBuffer buffer = (UnsafeBuffer)ringBuffer.buffer();\n                encodePublicationRemoval(buffer, index, captureLength, length, channel, sessionId, streamId);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the removal of a subscription.\n     *\n     * @param channel        for the channel.\n     * @param streamId       within the channel.\n     * @param subscriptionId for the subscription.\n     */\n    public void logSubscriptionRemoval(final String channel, final int streamId, final long subscriptionId)\n    {\n        final int length = SIZE_OF_INT * 2 + SIZE_OF_LONG + channel.length();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(REMOVE_SUBSCRIPTION_CLEANUP), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                final UnsafeBuffer buffer = (UnsafeBuffer)ringBuffer.buffer();\n                encodeSubscriptionRemoval(buffer, index, captureLength, length, channel, streamId, subscriptionId);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the removal of an image from the driver.\n     *\n     * @param channel       for the channel.\n     * @param sessionId     for the image.\n     * @param streamId      for the image.\n     * @param correlationId for the image.\n     */\n    public void logImageRemoval(final String channel, final int sessionId, final int streamId, final long correlationId)\n    {\n        final int length = SIZE_OF_INT * 3 + SIZE_OF_LONG + channel.length();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(REMOVE_IMAGE_CLEANUP), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                final UnsafeBuffer buffer = (UnsafeBuffer)ringBuffer.buffer();\n                encodeImageRemoval(buffer, index, captureLength, length, channel, sessionId, streamId, correlationId);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a generic string associated with an event.\n     *\n     * @param code  for the event type.\n     * @param value of the string to be logged.\n     */\n    public void logString(final DriverEventCode code, final String value)\n    {\n        final int length = value.length() + SIZE_OF_INT;\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(code), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encode((UnsafeBuffer)ringBuffer.buffer(), index, captureLength, length, value);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log an untethered subscription state change.\n     *\n     * @param <E>            type of the event.\n     * @param oldState       before the change.\n     * @param newState       after the change.\n     * @param subscriptionId to which the change applies.\n     * @param streamId       of the image.\n     * @param sessionId      of the image.\n     */\n    public <E extends Enum<E>> void logUntetheredSubscriptionStateChange(\n        final E oldState, final E newState, final long subscriptionId, final int streamId, final int sessionId)\n    {\n        final int length = untetheredSubscriptionStateChangeLength(oldState, newState);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(UNTETHERED_SUBSCRIPTION_STATE_CHANGE), encodedLength);\n\n        if (index > 0)\n        {\n            try\n            {\n                encodeUntetheredSubscriptionStateChange(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    oldState,\n                    newState,\n                    subscriptionId,\n                    streamId,\n                    sessionId);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log an address with associated event.\n     *\n     * @param code    representing the event type.\n     * @param address to be logged.\n     */\n    public void logAddress(final DriverEventCode code, final InetSocketAddress address)\n    {\n        final int length = socketAddressLength(address);\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(code), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encode((UnsafeBuffer)ringBuffer.buffer(), index, captureLength, length, address);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a resolution for a resolver and the associated result.\n     *\n     * @param resolverName   simple class name of the resolver.\n     * @param durationNs     of the call in nanoseconds.\n     * @param name           host name being resolved.\n     * @param isReResolution {@code true} if this is a re-resolution or {@code false} if initial resolution.\n     * @param address        address that was resolved to, can be {@code null}.\n     */\n    public void logResolve(\n        final String resolverName,\n        final long durationNs,\n        final String name,\n        final boolean isReResolution,\n        final InetAddress address)\n    {\n        final int length = SIZE_OF_BOOLEAN + SIZE_OF_LONG +\n            trailingStringLength(resolverName, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(name, MAX_HOST_NAME_LENGTH) +\n            inetAddressLength(address);\n\n        final int encodedLength = encodedLength(length);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(NAME_RESOLUTION_RESOLVE), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encodeResolve(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    length,\n                    length,\n                    resolverName,\n                    durationNs,\n                    name,\n                    isReResolution,\n                    address);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a resolution for a resolver and the associated result.\n     *\n     * @param resolverName simple class name of the resolver\n     * @param durationNs   of the call in nanoseconds.\n     * @param name         host name being resolved.\n     * @param isReLookup   address that was resolved to, can be null.\n     * @param resolvedName address that was resolved to, can be null.\n     */\n    public void logLookup(\n        final String resolverName,\n        final long durationNs,\n        final String name,\n        final boolean isReLookup,\n        final String resolvedName)\n    {\n        final int length = SIZE_OF_LONG + trailingStringLength(resolverName, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(name, MAX_HOST_NAME_LENGTH) + SIZE_OF_BOOLEAN +\n            trailingStringLength(resolvedName, MAX_HOST_NAME_LENGTH);\n\n        final int encodedLength = encodedLength(length);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(NAME_RESOLUTION_LOOKUP), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encodeLookup(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    length,\n                    length,\n                    resolverName,\n                    durationNs,\n                    name,\n                    isReLookup,\n                    resolvedName);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log a host name resolution duration.\n     *\n     * @param durationNs of the call in nanoseconds.\n     * @param hostName   host name being resolved.\n     */\n    public void logHostName(final long durationNs, final String hostName)\n    {\n        final int length = SIZE_OF_LONG + trailingStringLength(hostName, MAX_HOST_NAME_LENGTH);\n\n        final int encodedLength = encodedLength(length);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(NAME_RESOLUTION_HOST_NAME), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encodeHostName(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    length,\n                    length,\n                    durationNs,\n                    hostName);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Log the information about receiver for the corresponding flow control event.\n     *\n     * @param code          flow control event type.\n     * @param receiverId    of the receiver.\n     * @param sessionId     of the image.\n     * @param streamId      of the image.\n     * @param channel       uri of the channel.\n     * @param receiverCount number of the receivers after the event.\n     */\n    public void logFlowControlReceiver(\n        final DriverEventCode code,\n        final long receiverId,\n        final int sessionId,\n        final int streamId,\n        final String channel,\n        final int receiverCount)\n    {\n        final int length = SIZE_OF_INT * 4 + SIZE_OF_LONG + channel.length();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(code), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encodeFlowControlReceiver(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    receiverId,\n                    sessionId,\n                    streamId,\n                    channel,\n                    receiverCount);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Logs a NAK message sent by the receiver for a single control address or received by the sender.\n     *\n     * @param eventCode  to log Nak by.\n     * @param address    Nak UDP destination/source.\n     * @param sessionId  of the Nak.\n     * @param streamId   of the Nak.\n     * @param termId     of the Nak.\n     * @param termOffset of the Nak.\n     * @param nakLength  of the Nak.\n     * @param channel    of the Nak.\n     */\n    public void logNakMessage(\n        final DriverEventCode eventCode,\n        final InetSocketAddress address,\n        final int sessionId,\n        final int streamId,\n        final int termId,\n        final int termOffset,\n        final int nakLength,\n        final String channel)\n    {\n        final int length = socketAddressLength(address) + (SIZE_OF_INT * 6) + channel.length();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(eventCode), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encodeNakMessage(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    address,\n                    sessionId,\n                    streamId,\n                    termId,\n                    termOffset,\n                    nakLength,\n                    channel);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Logs a nak message sent by the receiver for a single control address.\n     *\n     * @param sessionId    of the Resend.\n     * @param streamId     of the Resend.\n     * @param termId       of the Resend.\n     * @param termOffset   of the Resend.\n     * @param resendLength of the Resend.\n     * @param channel      of the Resend.\n     */\n    public void logResend(\n        final int sessionId,\n        final int streamId,\n        final int termId,\n        final int termOffset,\n        final int resendLength,\n        final String channel)\n    {\n        final int length = (SIZE_OF_INT * 6) + channel.length();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(RESEND), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encodeResend(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    sessionId,\n                    streamId,\n                    termId,\n                    termOffset,\n                    resendLength,\n                    channel);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Logs a publication being revoked.\n     *\n     * @param revokedPos of the PublicationRevoke\n     * @param sessionId  of the PublicationRevoke\n     * @param streamId   of the PublicationRevoke\n     * @param channel    of the PublicationRevoke\n     */\n    public void logPublicationRevoke(\n        final long revokedPos,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int length = SIZE_OF_LONG + (SIZE_OF_INT * 3) + channel.length();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(PUBLICATION_REVOKE), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encodePublicationRevoke(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    revokedPos,\n                    sessionId,\n                    streamId,\n                    channel);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    /**\n     * Logs a publication image being revoked.\n     *\n     * @param revokedPos of the PublicationImageRevoke\n     * @param sessionId  of the PublicationImageRevoke\n     * @param streamId   of the PublicationImageRevoke\n     * @param channel    of the PublicationImageRevoke\n     */\n    public void logPublicationImageRevoke(\n        final long revokedPos,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int length = SIZE_OF_LONG + (SIZE_OF_INT * 3) + channel.length();\n        final int captureLength = captureLength(length);\n        final int encodedLength = encodedLength(captureLength);\n\n        final ManyToOneRingBuffer ringBuffer = this.ringBuffer;\n        final int index = ringBuffer.tryClaim(toEventCodeId(PUBLICATION_IMAGE_REVOKE), encodedLength);\n        if (index > 0)\n        {\n            try\n            {\n                encodePublicationImageRevoke(\n                    (UnsafeBuffer)ringBuffer.buffer(),\n                    index,\n                    captureLength,\n                    length,\n                    revokedPos,\n                    sessionId,\n                    streamId,\n                    channel);\n            }\n            finally\n            {\n                ringBuffer.commit(index);\n            }\n        }\n    }\n\n    static int toEventCodeId(final DriverEventCode code)\n    {\n        return EVENT_CODE_TYPE << 16 | (code.id() & 0xFFFF);\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/DriverInterceptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport net.bytebuddy.asm.Advice;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\n\nimport static io.aeron.agent.DriverEventCode.*;\nimport static io.aeron.agent.DriverEventLogger.LOGGER;\n\nclass DriverInterceptor\n{\n    static class UntetheredSubscriptionStateChange\n    {\n        @Advice.OnMethodEnter\n        static <E extends Enum<E>> void logStateChange(\n            final E oldState, final E newState, final long subscriptionId, final int streamId, final int sessionId)\n        {\n            LOGGER.logUntetheredSubscriptionStateChange(oldState, newState, subscriptionId, streamId, sessionId);\n        }\n    }\n\n    static class NameResolution\n    {\n        static class NeighborAdded\n        {\n            @Advice.OnMethodEnter\n            static void neighborAdded(final long nowMs, final InetSocketAddress address)\n            {\n                LOGGER.logAddress(NAME_RESOLUTION_NEIGHBOR_ADDED, address);\n            }\n        }\n\n        static class NeighborRemoved\n        {\n            @Advice.OnMethodEnter\n            static void neighborRemoved(final long nowMs, final InetSocketAddress address)\n            {\n                LOGGER.logAddress(NAME_RESOLUTION_NEIGHBOR_REMOVED, address);\n            }\n        }\n\n        static class Resolve\n        {\n            @Advice.OnMethodEnter\n            static void logResolve(\n                final String resolverName,\n                final long durationNs,\n                final String name,\n                final boolean isReResolution,\n                final InetAddress address)\n            {\n                LOGGER.logResolve(resolverName, durationNs, name, isReResolution, address);\n            }\n        }\n\n        static class Lookup\n        {\n            @Advice.OnMethodEnter\n            static void logLookup(\n                final String resolverName,\n                final long durationNs,\n                final String name,\n                final boolean isReLookup,\n                final String resolvedName)\n            {\n                LOGGER.logLookup(resolverName, durationNs, name, isReLookup, resolvedName);\n            }\n        }\n\n        static class HostName\n        {\n            @Advice.OnMethodEnter\n            static void logHostName(\n                final long durationNs,\n                final String hostName)\n            {\n                LOGGER.logHostName(durationNs, hostName);\n            }\n        }\n    }\n\n    static class FlowControl\n    {\n        static class ReceiverAdded\n        {\n            @Advice.OnMethodEnter\n            static void receiverAdded(\n                final long receiverId,\n                final int sessionId,\n                final int streamId,\n                final String channel,\n                final int receiverCount)\n            {\n                LOGGER.logFlowControlReceiver(\n                    FLOW_CONTROL_RECEIVER_ADDED, receiverId, sessionId, streamId, channel, receiverCount);\n            }\n        }\n\n        static class ReceiverRemoved\n        {\n            @Advice.OnMethodEnter\n            static void receiverRemoved(\n                final long receiverId,\n                final int sessionId,\n                final int streamId,\n                final String channel,\n                final int receiverCount)\n            {\n                LOGGER.logFlowControlReceiver(\n                    FLOW_CONTROL_RECEIVER_REMOVED, receiverId, sessionId, streamId, channel, receiverCount);\n            }\n        }\n    }\n\n    static class Revoke\n    {\n        static class PublicationRevoke\n        {\n            @Advice.OnMethodEnter\n            static void logRevoke(\n                final long revokedPos,\n                final int sessionId,\n                final int streamId,\n                final String channel)\n            {\n                LOGGER.logPublicationRevoke(revokedPos, sessionId, streamId, channel);\n            }\n        }\n\n        static class PublicationImageRevoke\n        {\n            @Advice.OnMethodEnter\n            static void logRevoke(\n                final long revokedPos,\n                final int sessionId,\n                final int streamId,\n                final String channel)\n            {\n                LOGGER.logPublicationImageRevoke(revokedPos, sessionId, streamId, channel);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/DynamicLoggingAgent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport org.agrona.PropertyAction;\nimport org.agrona.Strings;\nimport org.agrona.SystemUtil;\n\nimport java.io.File;\nimport java.nio.file.Paths;\nimport java.util.Map;\n\nimport static io.aeron.agent.ConfigOption.*;\nimport static java.lang.System.out;\n\n/**\n * Attach/detach logging agent dynamically to a running process.\n */\npublic final class DynamicLoggingAgent\n{\n    private DynamicLoggingAgent()\n    {\n    }\n\n    /**\n     * Attach logging agent jar to a running process.\n     *\n     * @param args program arguments.\n     */\n    public static void main(final String[] args)\n    {\n        if (args.length < 3)\n        {\n            printHelp();\n            System.exit(-1);\n        }\n\n        final File agentJar = Paths.get(args[0]).toAbsolutePath().toFile();\n        if (!agentJar.exists())\n        {\n            throw new IllegalArgumentException(agentJar + \" does not exist!\");\n        }\n\n        final String processId = args[1];\n        if (Strings.isEmpty(processId))\n        {\n            throw new IllegalArgumentException(\"no PID provided!\");\n        }\n\n        final String command = args[2];\n        switch (command)\n        {\n            case START_COMMAND:\n            {\n                for (int i = 3; i < args.length; i++)\n                {\n                    SystemUtil.loadPropertiesFile(PropertyAction.PRESERVE, args[i]);\n                }\n\n                final Map<String, String> configOptions = fromSystemProperties();\n                final String agentArgs = buildAgentArgs(configOptions);\n                attachAgent(START_COMMAND, agentJar, processId, agentArgs);\n                out.println(\"Logging started.\");\n                break;\n            }\n\n            case STOP_COMMAND:\n            {\n                attachAgent(STOP_COMMAND, agentJar, processId, command);\n                out.println(\"Logging stopped.\");\n                break;\n            }\n\n            default:\n                throw new IllegalArgumentException(\"invalid command: \" + command);\n        }\n    }\n\n    private static void printHelp()\n    {\n        out.println(\"Usage: <agent-jar> <java-process-id> <command> [property files...]\");\n        out.println(\"  <agent-jar> - fully qualified path to the agent jar\");\n        out.println(\"  <java-process-id> - PID of the Java process to attach an agent to\");\n        out.println(\"  <command> - either '\" + START_COMMAND + \"' or '\" + STOP_COMMAND + \"'\");\n        out.println(\"  [property files...] - an optional list of property files to configure logging options\");\n        out.println(\"Note: logging options can be specified either via system properties or the property files.\");\n    }\n\n    private static void attachAgent(\n        final String command, final File agentJar, final String processId, final String agentArgs)\n    {\n        try\n        {\n            ByteBuddyAgent.attach(agentJar, processId, agentArgs);\n        }\n        catch (final Exception ex)\n        {\n            out.println(\"Command '\" + command + \"' failed, cause: \" + getCause(ex));\n            System.exit(-1);\n        }\n    }\n\n    private static Throwable getCause(final Throwable t)\n    {\n        Throwable cause = t;\n        while (null != cause.getCause())\n        {\n            cause = cause.getCause();\n        }\n\n        return cause;\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/EventCode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\n/**\n * Identifies an event that can be enabled for logging.\n */\npublic interface EventCode\n{\n    /**\n     * Returns the unique event identifier withing an {@link EventCodeType}.\n     *\n     * @return the unique event identifier withing an {@link EventCodeType}.\n     */\n    int id();\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/EventCodeType.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\n/**\n * Specifies the type of EventCode that can be handled by the logging agent.\n */\npublic enum EventCodeType\n{\n    /**\n     * Events related to media driver operation.\n     */\n    DRIVER(0),\n\n    /**\n     * Events related to archive service operation.\n     */\n    ARCHIVE(1),\n\n    /**\n     * Events related to cluster election and consensus operation.\n     */\n    CLUSTER(2),\n\n    /**\n     * Events related to cluster standby operations including standby consensus and transitioning.\n     */\n    STANDBY(3),\n\n    /**\n     * Events related to the sequencer operations.\n     */\n    SEQUENCER(4),\n\n    /**\n     * User defined events for third party usage.\n     */\n    USER(0xFFFF);\n\n    private final int typeCode;\n\n    EventCodeType(final int typeCode)\n    {\n        this.typeCode = typeCode;\n    }\n\n    /**\n     * The type code which classifies the events to identify one of the {@link EventCodeType} enum value.\n     *\n     * @return type code which classifies the events to identify one of the {@link EventCodeType} enum value.\n     */\n    public int getTypeCode()\n    {\n        return typeCode;\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/EventConfiguration.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.Strings;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\n\nimport java.util.EnumSet;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.function.IntFunction;\n\nimport static java.lang.System.err;\nimport static java.lang.System.lineSeparator;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.BufferUtil.allocateDirectAligned;\nimport static org.agrona.SystemUtil.getSizeAsInt;\nimport static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TRAILER_LENGTH;\n\n/**\n * Common configuration elements between event loggers and event reader side.\n */\npublic final class EventConfiguration\n{\n    /**\n     * Event buffer length system property name.\n     */\n    public static final String BUFFER_LENGTH_PROP_NAME = \"aeron.event.buffer.length\";\n\n    /**\n     * Event Buffer default length (in bytes).\n     */\n    public static final int BUFFER_LENGTH_DEFAULT = 8 * 1024 * 1024;\n\n    /**\n     * Maximum length of an event in bytes.\n     */\n    public static final int MAX_EVENT_LENGTH = 4096 - lineSeparator().length();\n\n    /**\n     * Iteration limit for event reader loop.\n     */\n    public static final int EVENT_READER_FRAME_LIMIT = 20;\n\n    /**\n     * Ring Buffer to use for logging that will be read by {@link ConfigOption#READER_CLASSNAME}.\n     */\n    public static final ManyToOneRingBuffer EVENT_RING_BUFFER;\n\n    static\n    {\n        EVENT_RING_BUFFER = new ManyToOneRingBuffer(new UnsafeBuffer(allocateDirectAligned(\n            getSizeAsInt(BUFFER_LENGTH_PROP_NAME, BUFFER_LENGTH_DEFAULT) + TRAILER_LENGTH, CACHE_LINE_LENGTH)));\n    }\n\n    private EventConfiguration()\n    {\n    }\n\n    /**\n     * Parse agent configuration.\n     *\n     * @param <E> type of the enum values.\n     * @param eventCodeType for the enum.\n     * @param eventCodes passed by the user.\n     * @param specialEvents such as {@code admin} and {@code all}.\n     * @param eventCodeById function to parse event id.\n     * @param eventCodeByName function to parse event name.\n     * @return set of enabled events.\n     */\n    public static <E extends Enum<E>> EnumSet<E> parseEventCodes(\n        final Class<E> eventCodeType,\n        final String eventCodes,\n        final Map<String, EnumSet<E>> specialEvents,\n        final IntFunction<E> eventCodeById,\n        final Function<String, E> eventCodeByName)\n    {\n        if (Strings.isEmpty(eventCodes))\n        {\n            return EnumSet.noneOf(eventCodeType);\n        }\n\n        final EnumSet<E> eventCodeSet = EnumSet.noneOf(eventCodeType);\n        final String[] codeIds = eventCodes.split(\",\");\n\n        for (final String codeId : codeIds)\n        {\n            final EnumSet<E> specialCodes = specialEvents.get(codeId);\n            if (null != specialCodes)\n            {\n                eventCodeSet.addAll(specialCodes);\n            }\n            else\n            {\n                E code = null;\n                try\n                {\n                    code = eventCodeByName.apply(codeId);\n                }\n                catch (final IllegalArgumentException ignore)\n                {\n                }\n\n                if (null == code)\n                {\n                    try\n                    {\n                        code = eventCodeById.apply(Integer.parseInt(codeId));\n                    }\n                    catch (final IllegalArgumentException ignore)\n                    {\n                    }\n                }\n\n                if (null != code)\n                {\n                    eventCodeSet.add(code);\n                }\n                else\n                {\n                    err.println(\"unknown event code: \" + codeId);\n                }\n            }\n        }\n\n        return eventCodeSet;\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/EventLogAgent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport net.bytebuddy.ByteBuddy;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.agent.builder.ResettableClassFileTransformer;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.dynamic.scaffold.TypeValidation;\nimport net.bytebuddy.utility.JavaModule;\nimport org.agrona.CloseHelper;\nimport org.agrona.Strings;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.SleepingMillisIdleStrategy;\n\nimport java.lang.instrument.Instrumentation;\nimport java.lang.reflect.Constructor;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.ServiceLoader;\n\nimport static io.aeron.agent.ConfigOption.*;\nimport static io.aeron.agent.EventConfiguration.EVENT_RING_BUFFER;\n\n/**\n * A Java agent which when attached to a JVM will weave byte code to intercept events as defined by\n * {@link DriverEventCode}. Events are recorded to an in-memory {@link org.agrona.concurrent.ringbuffer.RingBuffer}\n * which is consumed and appended asynchronous to a log as defined by the class {@link #READER_CLASSNAME_PROP_NAME}\n * which defaults to {@link EventLogReaderAgent}.\n */\npublic final class EventLogAgent\n{\n    /**\n     * Event reader {@link Agent} which consumes the {@link EventConfiguration#EVENT_RING_BUFFER} to output log events.\n     */\n    public static final String READER_CLASSNAME_PROP_NAME = READER_CLASSNAME;\n    /**\n     * Default value for the log reader agent.\n     */\n    public static final String READER_CLASSNAME_DEFAULT = \"io.aeron.agent.EventLogReaderAgent\";\n\n    private static final long SLEEP_PERIOD_MS = 1L;\n\n    private static List<ComponentLogger> loggers;\n    private static AgentRunner readerAgentRunner;\n    private static Instrumentation instrumentation;\n    private static ResettableClassFileTransformer logTransformer;\n    private static Thread thread;\n\n    private EventLogAgent()\n    {\n    }\n\n    /**\n     * Premain method to run before the main method of the application.\n     *\n     * @param agentArgs       which are ignored.\n     * @param instrumentation for applying to the agent.\n     */\n    public static void premain(final String agentArgs, final Instrumentation instrumentation)\n    {\n        startLogging(instrumentation, ConfigOption.fromSystemProperties());\n    }\n\n    /**\n     * Agent main method for dynamic attach.\n     *\n     * @param agentArgs       containing configuration options or command to stop.\n     * @param instrumentation for applying to the agent.\n     */\n    public static void agentmain(final String agentArgs, final Instrumentation instrumentation)\n    {\n        if (STOP_COMMAND.equals(agentArgs))\n        {\n            stopLogging();\n        }\n        else\n        {\n            final Map<String, String> configOptions = Strings.isEmpty(agentArgs) ? fromSystemProperties() :\n                parseAgentArgs(agentArgs);\n            startLogging(instrumentation, configOptions);\n        }\n    }\n\n    /**\n     * Remove the transformer and close the agent runner for the event log reader.\n     */\n    @Deprecated\n    public static void removeTransformer()\n    {\n        stopLogging();\n    }\n\n    /**\n     * Remove the transformer and close the agent runner for the event log reader.\n     */\n    public static synchronized void stopLogging()\n    {\n        if (logTransformer != null)\n        {\n            logTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);\n            instrumentation = null;\n            logTransformer = null;\n            thread = null;\n\n            for (final ComponentLogger logger : loggers)\n            {\n                logger.reset();\n            }\n            loggers = null;\n            EVENT_RING_BUFFER.unblock();\n\n            CloseHelper.close(readerAgentRunner);\n            readerAgentRunner = null;\n        }\n    }\n\n    private static synchronized void startLogging(\n        final Instrumentation instrumentation, final Map<String, String> configOptions)\n    {\n        if (null != logTransformer)\n        {\n            throw new IllegalStateException(\"agent already instrumented\");\n        }\n\n        final ArrayList<ComponentLogger> loggers = new ArrayList<>();\n        for (final ComponentLogger componentLogger : ServiceLoader.load(ComponentLogger.class))\n        {\n            loggers.add(componentLogger);\n        }\n\n        AgentBuilder agentBuilder = new AgentBuilder.Default(new ByteBuddy()\n            .with(TypeValidation.DISABLED))\n            .disableClassFormatChanges()\n            .with(new AgentBuilderListener())\n            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)\n            .with(AgentBuilder.RedefinitionStrategy.DiscoveryStrategy.Reiterating.INSTANCE);\n\n        final AgentBuilder initialAgentBuilder = agentBuilder;\n\n        for (final ComponentLogger componentLogger : loggers)\n        {\n            agentBuilder = componentLogger.addInstrumentation(agentBuilder, configOptions);\n        }\n\n        if (initialAgentBuilder == agentBuilder)\n        {\n            return; // no log events configured\n        }\n\n        EventLogAgent.instrumentation = instrumentation;\n\n        logTransformer = agentBuilder.installOn(instrumentation);\n\n        EventLogAgent.loggers = loggers;\n\n        readerAgentRunner = new AgentRunner(\n            new SleepingMillisIdleStrategy(SLEEP_PERIOD_MS),\n            Throwable::printStackTrace,\n            null,\n            newReaderAgent(configOptions, loggers));\n\n        thread = new Thread(readerAgentRunner);\n        thread.setName(\"event-log-reader\");\n        thread.setDaemon(true);\n        thread.start();\n    }\n\n    private static Agent newReaderAgent(final Map<String, String> configOptions, final List<ComponentLogger> loggers)\n    {\n        try\n        {\n            final Class<?> aClass = Class.forName(\n                configOptions.getOrDefault(READER_CLASSNAME, READER_CLASSNAME_DEFAULT));\n\n            try\n            {\n                final Constructor<?> constructor = aClass.getDeclaredConstructor(String.class, List.class);\n                return (Agent)constructor.newInstance(configOptions.get(LOG_FILENAME), loggers);\n            }\n            catch (final NoSuchMethodException ex)\n            {\n                try\n                {\n                    final Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);\n                    return (Agent)constructor.newInstance(configOptions.get(LOG_FILENAME));\n                }\n                catch (final NoSuchMethodException ex2)\n                {\n                    return (Agent)aClass.getDeclaredConstructor().newInstance();\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            throw new RuntimeException(ex);\n        }\n    }\n}\n\nfinal class AgentBuilderListener implements AgentBuilder.Listener\n{\n    public void onDiscovery(\n        final String typeName,\n        final ClassLoader classLoader,\n        final JavaModule javaModule,\n        final boolean loaded)\n    {\n    }\n\n    public void onTransformation(\n        final TypeDescription typeDescription,\n        final ClassLoader classLoader,\n        final JavaModule javaModule,\n        final boolean loaded,\n        final DynamicType dynamicType)\n    {\n    }\n\n    public void onIgnored(\n        final TypeDescription typeDescription,\n        final ClassLoader classLoader,\n        final JavaModule javaModule,\n        final boolean loaded)\n    {\n    }\n\n    public void onError(\n        final String typeName,\n        final ClassLoader classLoader,\n        final JavaModule javaModule,\n        final boolean loaded,\n        final Throwable throwable)\n    {\n        System.err.println(\"ERROR \" + typeName);\n        throwable.printStackTrace(System.err);\n    }\n\n    public void onComplete(\n        final String typeName,\n        final ClassLoader classLoader,\n        final JavaModule javaModule,\n        final boolean loaded)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/EventLogReaderAgent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.CloseHelper;\nimport org.agrona.LangUtil;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.collections.IntHashSet;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.MessageHandler;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\n\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.io.UncheckedIOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static io.aeron.agent.CommonEventDissector.dissectLogStartMessage;\nimport static io.aeron.agent.EventConfiguration.EVENT_READER_FRAME_LIMIT;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static java.lang.System.lineSeparator;\nimport static java.nio.channels.FileChannel.open;\nimport static java.nio.file.StandardOpenOption.APPEND;\nimport static java.nio.file.StandardOpenOption.CREATE;\nimport static java.nio.file.StandardOpenOption.WRITE;\nimport static java.time.ZoneId.systemDefault;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.BufferUtil.allocateDirectAligned;\n\n/**\n * Simple reader of {@link EventConfiguration#EVENT_RING_BUFFER} that appends to {@link System#out} by default\n * or to file if {@link #LOG_FILENAME_PROP_NAME} System property is set.\n */\npublic final class EventLogReaderAgent implements Agent\n{\n    /**\n     * Event Buffer length system property name. If not set then output will default to {@link System#out}.\n     */\n    public static final String LOG_FILENAME_PROP_NAME = ConfigOption.LOG_FILENAME;\n\n    private final ManyToOneRingBuffer ringBuffer = EventConfiguration.EVENT_RING_BUFFER;\n    private final StringBuilder builder = new StringBuilder(MAX_EVENT_LENGTH);\n    private final MessageHandler messageHandler = this::onMessage;\n    private final ByteBuffer byteBuffer;\n    private final FileChannel fileChannel;\n    private final Int2ObjectHashMap<ComponentLogger> loggers = new Int2ObjectHashMap<>();\n    private final PrintStream out;\n    private final NanoClock nanoClock;\n    private final EpochClock epochClock;\n\n    EventLogReaderAgent(final String filename, final List<ComponentLogger> loggers)\n    {\n        this(filename, System.out, SystemNanoClock.INSTANCE, SystemEpochClock.INSTANCE, loggers);\n    }\n\n    EventLogReaderAgent(\n        final String filename,\n        final PrintStream out,\n        final NanoClock nanoClock,\n        final EpochClock epochClock,\n        final List<ComponentLogger> loggers)\n    {\n        this.nanoClock = Objects.requireNonNull(nanoClock);\n        this.epochClock = Objects.requireNonNull(epochClock);\n        for (final ComponentLogger componentLogger : loggers)\n        {\n            this.loggers.put(componentLogger.typeCode(), componentLogger);\n        }\n\n        if (null != filename)\n        {\n            this.out = null;\n            try\n            {\n                fileChannel = open(Paths.get(filename), CREATE, APPEND, WRITE);\n            }\n            catch (final IOException ex)\n            {\n                throw new UncheckedIOException(ex);\n            }\n\n            byteBuffer = allocateDirectAligned(MAX_EVENT_LENGTH * 2, CACHE_LINE_LENGTH);\n        }\n        else\n        {\n            fileChannel = null;\n            byteBuffer = null;\n            this.out = Objects.requireNonNull(out);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        final long startTimeNs = nanoClock.nanoTime();\n        final long startTimeMs = epochClock.time();\n        dissectLogStartMessage(startTimeNs, startTimeMs, systemDefault(), builder);\n\n        builder.append(\", enabled loggers: {\");\n\n        final EventCodeType[] eventCodeTypes = EventCodeType.values();\n        final IntHashSet visited = new IntHashSet(loggers.size());\n        String separator = \"\";\n        for (final EventCodeType type : eventCodeTypes)\n        {\n            visited.add(type.getTypeCode());\n            final ComponentLogger logger = loggers.get(type.getTypeCode());\n            if (null != logger)\n            {\n                builder.append(separator).append(type).append(\": \").append(logger.version());\n                separator = \", \";\n            }\n        }\n\n        loggers.forEachInt((type, logger) ->\n        {\n            if (!visited.contains(type))\n            {\n                builder.append(\", \").append(logger.getClass().getName()).append(\": \").append(logger.version());\n            }\n        });\n\n        builder.append('}').append(lineSeparator());\n\n        if (null == fileChannel)\n        {\n            out.print(builder);\n        }\n        else\n        {\n            appendEvent(builder, byteBuffer, fileChannel);\n            write(byteBuffer, fileChannel);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        CloseHelper.close(fileChannel);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"event-log-reader\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        final int eventsRead = ringBuffer.read(messageHandler, EVENT_READER_FRAME_LIMIT);\n        if (null != byteBuffer && byteBuffer.position() > 0)\n        {\n            write(byteBuffer, fileChannel);\n        }\n\n        return eventsRead;\n    }\n\n    private void onMessage(final int msgTypeId, final MutableDirectBuffer buffer, final int index, final int length)\n    {\n        final int eventCodeTypeId = msgTypeId >> 16;\n        final int eventCodeId = msgTypeId & 0xFFFF;\n\n        builder.setLength(0);\n\n        decodeLogEvent(buffer, index, eventCodeTypeId, eventCodeId, loggers, builder);\n\n        if (null == fileChannel)\n        {\n            out.print(builder);\n        }\n        else\n        {\n            appendEvent(builder, byteBuffer, fileChannel);\n        }\n    }\n\n    static void decodeLogEvent(\n        final MutableDirectBuffer buffer,\n        final int index,\n        final int eventCodeTypeId,\n        final int eventCodeId,\n        final Int2ObjectHashMap<ComponentLogger> loggers,\n        final StringBuilder builder)\n    {\n        final ComponentLogger componentLogger = loggers.get(eventCodeTypeId);\n        if (null != componentLogger)\n        {\n            componentLogger.decode(buffer, index, eventCodeId, builder);\n        }\n        else\n        {\n            builder.append(\"Unknown EventCodeType: \").append(eventCodeTypeId);\n        }\n\n        builder.append(lineSeparator());\n    }\n\n    private static void appendEvent(final StringBuilder builder, final ByteBuffer buffer, final FileChannel fileChannel)\n    {\n        final int length = builder.length();\n\n        if (buffer.position() + length > buffer.capacity())\n        {\n            write(buffer, fileChannel);\n        }\n\n        final int position = buffer.position();\n\n        for (int i = 0, p = position; i < length; i++, p++)\n        {\n            buffer.put(p, (byte)builder.charAt(i));\n        }\n\n        buffer.position(position + length);\n    }\n\n    private static void write(final ByteBuffer buffer, final FileChannel fileChannel)\n    {\n        try\n        {\n            buffer.flip();\n\n            do\n            {\n                fileChannel.write(buffer);\n            }\n            while (buffer.remaining() > 0);\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n        finally\n        {\n            buffer.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/LogUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.AsciiEncoding;\n\n/**\n * Utility methods for loggers.\n */\nfinal class LogUtil\n{\n    private static final long NANOS_PER_SECOND = 1_000_000_000;\n    private static final long NANOS_PER_MICROSECOND = 1_000;\n\n    /**\n     * Render a nanosecond timestamp to the supplied builder in the format [seconds].[microseconds].\n     *\n     * @param builder       to render the timestamp too.\n     * @param timestampNs   the nanosecond timestamp.\n     */\n    static void appendTimestamp(final StringBuilder builder, final long timestampNs)\n    {\n        final long seconds = timestampNs / NANOS_PER_SECOND;\n        final long nanos = timestampNs - seconds * NANOS_PER_SECOND;\n        final int numDigitsAfterDot = AsciiEncoding.digitCount(nanos);\n        builder.append('[');\n        builder.append(seconds);\n        builder.append('.');\n        for (int i = 0, size = 9 - numDigitsAfterDot; i < size; i++)\n        {\n            builder.append('0');\n        }\n        builder.append(nanos);\n        builder.append(']').append(' ');\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/main/java/io/aeron/agent/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * A Java agent which when attached to a JVM will weave byte code to intercept and log events as defined by\n * implementations of {@link io.aeron.agent.EventCode} which include {@link io.aeron.agent.DriverEventCode},\n * {@link io.aeron.agent.ArchiveEventCode}, and {@link io.aeron.agent.ClusterEventCode}.\n * <p>\n * Events are recorded to an in-memory\n * {@link org.agrona.concurrent.ringbuffer.RingBuffer} which is consumed\n * and appended asynchronously to a log as defined by the class\n * {@link io.aeron.agent.EventLogAgent#READER_CLASSNAME_PROP_NAME} which defaults to\n * {@link io.aeron.agent.EventLogReaderAgent}.\n */\npackage io.aeron.agent;"
  },
  {
    "path": "aeron-agent/src/main/resources/META-INF/services/io.aeron.agent.ComponentLogger",
    "content": "io.aeron.agent.DriverComponentLogger\nio.aeron.agent.ArchiveComponentLogger\nio.aeron.agent.ClusterComponentLogger\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/AgentTests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.Tests;\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\n\nimport java.util.EnumSet;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static io.aeron.agent.ArchiveEventCode.*;\nimport static io.aeron.agent.ClusterEventCode.ROLE_CHANGE;\nimport static io.aeron.agent.CommonEventEncoder.LOG_HEADER_LENGTH;\nimport static io.aeron.agent.ConfigOption.*;\nimport static io.aeron.agent.DriverEventCode.*;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.concurrent.ringbuffer.RecordDescriptor.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nfinal class AgentTests\n{\n    static void startLogging(final Map<String, String> configOptions)\n    {\n        EventLogAgent.agentmain(buildAgentArgs(configOptions), ByteBuddyAgent.install());\n    }\n\n    static void stopLogging()\n    {\n        EventLogAgent.stopLogging();\n    }\n\n    static void verifyLogHeader(\n        final UnsafeBuffer logBuffer,\n        final int recordOffset,\n        final int eventCodeId,\n        final int captureLength,\n        final int length)\n    {\n        assertEquals(\n            HEADER_LENGTH + LOG_HEADER_LENGTH + captureLength,\n            logBuffer.getInt(lengthOffset(recordOffset), LITTLE_ENDIAN));\n        assertEquals(eventCodeId, logBuffer.getInt(typeOffset(recordOffset), LITTLE_ENDIAN));\n        assertEquals(captureLength, logBuffer.getInt(encodedMsgOffset(recordOffset), LITTLE_ENDIAN));\n        assertEquals(length, logBuffer.getInt(encodedMsgOffset(recordOffset + SIZE_OF_INT), LITTLE_ENDIAN));\n        assertNotEquals(0, logBuffer.getLong(encodedMsgOffset(recordOffset + SIZE_OF_INT * 2), LITTLE_ENDIAN));\n    }\n\n    @AfterEach\n    void afterEach()\n    {\n        stopLogging();\n        TestLoggingAgent.INSTANCE_COUNT.set(0);\n        TestLoggingAgent.isOnStartCalled = false;\n        TestLoggingAgent.isOnStopCalled = false;\n        TestLoggingAgentWithFileName.LOG_FILE_NAME.set(null);\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void agentmainShouldUseSystemPropertiesWhenAgentsArgsIsEmpty(final String agentArgs)\n    {\n        System.setProperty(ENABLED_DRIVER_EVENT_CODES, \"admin\");\n        System.setProperty(\n            ENABLED_ARCHIVE_EVENT_CODES,\n            \"CMD_IN_EXTEND_RECORDING,REPLICATION_SESSION_STATE_CHANGE,CATALOG_RESIZE\");\n        System.setProperty(DISABLED_ARCHIVE_EVENT_CODES, \"REPLICATION_SESSION_STATE_CHANGE\");\n        System.setProperty(ENABLED_CLUSTER_EVENT_CODES, \"all\");\n        System.setProperty(DISABLED_CLUSTER_EVENT_CODES, \"ROLE_CHANGE\");\n        System.setProperty(READER_CLASSNAME, TestLoggingAgent.class.getName());\n        final int instanceCount = TestLoggingAgent.INSTANCE_COUNT.get();\n        try\n        {\n            EventLogAgent.agentmain(agentArgs, ByteBuddyAgent.install());\n\n            assertEquals(instanceCount + 1, TestLoggingAgent.INSTANCE_COUNT.get());\n            assertEquals(\n                EnumSet.complementOf(EnumSet.of(\n                FRAME_IN,\n                FRAME_OUT)),\n                DriverComponentLogger.ENABLED_EVENTS);\n            assertEquals(\n                EnumSet.of(CMD_IN_EXTEND_RECORDING, CATALOG_RESIZE),\n                ArchiveComponentLogger.ENABLED_EVENTS);\n            assertEquals(\n                EnumSet.complementOf(EnumSet.of(ROLE_CHANGE)),\n                ClusterComponentLogger.ENABLED_EVENTS);\n        }\n        finally\n        {\n            System.clearProperty(ENABLED_DRIVER_EVENT_CODES);\n            System.clearProperty(ENABLED_ARCHIVE_EVENT_CODES);\n            System.clearProperty(DISABLED_ARCHIVE_EVENT_CODES);\n            System.clearProperty(ENABLED_CLUSTER_EVENT_CODES);\n            System.clearProperty(DISABLED_CLUSTER_EVENT_CODES);\n            System.clearProperty(READER_CLASSNAME);\n        }\n    }\n\n    @Test\n    void shouldFailToStartLoggingIfAgentAlreadyRunning()\n    {\n        final int instanceCount = TestLoggingAgent.INSTANCE_COUNT.get();\n        final Map<String, String> configOptions = new HashMap<>();\n        configOptions.put(READER_CLASSNAME, TestLoggingAgent.class.getName());\n\n        startLogging(configOptions); // logging is not started as no events are configured\n        assertEquals(instanceCount, TestLoggingAgent.INSTANCE_COUNT.get());\n\n        configOptions.put(ENABLED_DRIVER_EVENT_CODES, \"admin\");\n        startLogging(configOptions); // logging is running now\n        assertEquals(instanceCount + 1, TestLoggingAgent.INSTANCE_COUNT.get());\n\n        final IllegalStateException exception =\n            assertThrows(IllegalStateException.class, () -> startLogging(configOptions));\n        assertEquals(\"agent already instrumented\", exception.getMessage());\n    }\n\n    @Test\n    void shouldStartLoggingAfterItWasStopped()\n    {\n        final int instanceCount = TestLoggingAgent.INSTANCE_COUNT.get();\n        final Map<String, String> configOptions = new HashMap<>();\n        configOptions.put(READER_CLASSNAME, TestLoggingAgent.class.getName());\n        configOptions.put(ENABLED_ARCHIVE_EVENT_CODES, CMD_IN_AUTH_CONNECT.name());\n\n        startLogging(configOptions);\n        assertEquals(instanceCount + 1, TestLoggingAgent.INSTANCE_COUNT.get());\n\n        stopLogging();\n        assertEquals(EnumSet.noneOf(DriverEventCode.class), DriverComponentLogger.ENABLED_EVENTS);\n        assertEquals(EnumSet.noneOf(ArchiveEventCode.class), ArchiveComponentLogger.ENABLED_EVENTS);\n        assertEquals(EnumSet.noneOf(ClusterEventCode.class), ClusterComponentLogger.ENABLED_EVENTS);\n\n        startLogging(configOptions);\n        assertEquals(instanceCount + 2, TestLoggingAgent.INSTANCE_COUNT.get());\n    }\n\n    @Test\n    void shouldInitializeLoggingAgentWithAFileName()\n    {\n        assertNull(TestLoggingAgentWithFileName.LOG_FILE_NAME.get());\n        final String logFileName = \"/dev/shm/my-file.txt\";\n        final Map<String, String> configOptions = new HashMap<>();\n        configOptions.put(READER_CLASSNAME, TestLoggingAgentWithFileName.class.getName());\n        configOptions.put(LOG_FILENAME, logFileName);\n        configOptions.put(ENABLED_ARCHIVE_EVENT_CODES, \"all\");\n\n        startLogging(configOptions);\n        assertEquals(logFileName, TestLoggingAgentWithFileName.LOG_FILE_NAME.get());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldStartAndStopLoggingAgent()\n    {\n        final int instanceCount = TestLoggingAgent.INSTANCE_COUNT.get();\n        final Map<String, String> configOptions = new HashMap<>();\n        configOptions.put(READER_CLASSNAME, TestLoggingAgent.class.getName());\n        configOptions.put(ENABLED_ARCHIVE_EVENT_CODES, \"all\");\n\n        startLogging(configOptions);\n        assertEquals(instanceCount + 1, TestLoggingAgent.INSTANCE_COUNT.get());\n        Tests.await(() -> TestLoggingAgent.isOnStartCalled);\n\n        stopLogging();\n        Tests.await(() -> TestLoggingAgent.isOnStopCalled);\n    }\n\n    private static class TestLoggingAgent implements Agent\n    {\n        static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();\n        static volatile boolean isOnStartCalled = false;\n        static volatile boolean isOnStopCalled = false;\n\n        TestLoggingAgent()\n        {\n            INSTANCE_COUNT.getAndIncrement();\n        }\n\n        public int doWork()\n        {\n            return 0;\n        }\n\n        public String roleName()\n        {\n            return \"test-logging-agent\";\n        }\n\n        public void onStart()\n        {\n            isOnStartCalled = true;\n        }\n\n        public void onClose()\n        {\n            isOnStopCalled = true;\n        }\n    }\n\n    private static class TestLoggingAgentWithFileName implements Agent\n    {\n        static final AtomicReference<String> LOG_FILE_NAME = new AtomicReference<>();\n\n        TestLoggingAgentWithFileName(final String logFileName)\n        {\n            LOG_FILE_NAME.set(logFileName);\n        }\n\n        public int doWork()\n        {\n            return 0;\n        }\n\n        public String roleName()\n        {\n            return \"test-logging-agent-with-file-name\";\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ArchiveEventCodeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static io.aeron.agent.ArchiveEventCode.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass ArchiveEventCodeTest\n{\n    @ParameterizedTest\n    @EnumSource(ArchiveEventCode.class)\n    void getCodeById(final ArchiveEventCode code)\n    {\n        assertSame(code, get(code.id()));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, -1, 101, Integer.MIN_VALUE, Integer.MAX_VALUE })\n    void getShouldThrowIllegalArgumentExceptionIfIdIsUnknown(final int id)\n    {\n        assertThrows(IllegalArgumentException.class, () -> get(id));\n    }\n\n    @ParameterizedTest\n    @EnumSource(ArchiveEventCode.class)\n    void getCodeByTemplateId(final ArchiveEventCode code)\n    {\n        final int templateId = code.templateId();\n        if (templateId < 0)\n        {\n            assertNull(getByTemplateId(templateId));\n        }\n        else\n        {\n            assertSame(code, getByTemplateId(templateId));\n        }\n    }\n\n    @ParameterizedTest\n    @EnumSource(ArchiveEventCode.class)\n    void toEventCodeIdComputesEventId(final ArchiveEventCode eventCode)\n    {\n        assertEquals((EVENT_CODE_TYPE << 16) | (0xFFFF & eventCode.id()), eventCode.toEventCodeId());\n    }\n\n    @ParameterizedTest\n    @EnumSource(ArchiveEventCode.class)\n    void fromEventCodeIdLooksUpEventCode(final ArchiveEventCode eventCode)\n    {\n        assertSame(eventCode, fromEventCodeId(eventCode.toEventCodeId()));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, -1, 13, Integer.MIN_VALUE, Integer.MAX_VALUE })\n    void fromEventCodeIdThrowsIllegalArgumentExceptionIfCodeIsInvalid(final int eventCodeId)\n    {\n        assertThrows(IllegalArgumentException.class, () -> fromEventCodeId(eventCodeId));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ArchiveEventDissectorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.archive.codecs.*;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\n\nimport static io.aeron.agent.ArchiveEventCode.*;\nimport static io.aeron.agent.ArchiveEventDissector.*;\nimport static io.aeron.agent.CommonEventEncoder.LOG_HEADER_LENGTH;\nimport static io.aeron.agent.CommonEventEncoder.internalEncodeLogHeader;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static io.aeron.archive.codecs.ControlResponseCode.NULL_VAL;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass ArchiveEventDissectorTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH]);\n    private final StringBuilder builder = new StringBuilder();\n    private final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n\n    @Test\n    void controlResponse()\n    {\n        internalEncodeLogHeader(buffer, 0, 100, 100, () -> 1_250_000_000);\n        final ControlResponseEncoder responseEncoder = new ControlResponseEncoder();\n        responseEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(13)\n            .correlationId(42)\n            .relevantId(8)\n            .code(NULL_VAL)\n            .version(111)\n            .errorMessage(\"the %ERR% msg\");\n\n        dissectControlResponse(buffer, 0, builder);\n\n        assertEquals(\"[1.250000000] \" + CONTEXT + \": \" + CMD_OUT_RESPONSE.name() + \" [100/100]: \" +\n            \"controlSessionId=13\" +\n            \" correlationId=42\" +\n            \" relevantId=8\" +\n            \" code=\" + NULL_VAL +\n            \" version=111\" +\n            \" errorMessage=the %ERR% msg\",\n            builder.toString());\n    }\n\n    @Test\n    void recordingSignal()\n    {\n        internalEncodeLogHeader(buffer, 0, 88, 99, () -> 2_250_000_000L);\n        final RecordingSignalEventEncoder encoder = new RecordingSignalEventEncoder();\n        encoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(49)\n            .correlationId(-100)\n            .recordingId(42)\n            .subscriptionId(15)\n            .position(234723197419023749L)\n            .signal(RecordingSignal.DELETE);\n\n        dissectRecordingSignal(buffer, 0, builder);\n\n        assertEquals(\"[2.250000000] \" + CONTEXT + \": \" + RECORDING_SIGNAL.name() + \" [88/99]: \" +\n            \"controlSessionId=49\" +\n            \" correlationId=-100\" +\n            \" recordingId=42\" +\n            \" subscriptionId=15\" +\n            \" position=234723197419023749\" +\n            \" signal=DELETE\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestConnect()\n    {\n        internalEncodeLogHeader(buffer, 0, 32, 64, () -> 5_600_000_000L);\n        final ConnectRequestEncoder requestEncoder = new ConnectRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .correlationId(88)\n            .responseStreamId(42)\n            .version(-10)\n            .responseChannel(\"call me maybe\");\n\n        dissectControlRequest(CMD_IN_CONNECT, buffer, 0, builder);\n\n        assertEquals(\"[5.600000000] \" + CONTEXT + \": \" + CMD_IN_CONNECT.name() + \" [32/64]: \" +\n            \"correlationId=88\" +\n            \" responseStreamId=42\" +\n            \" version=-10\" +\n            \" responseChannel=call me maybe\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestCloseSession()\n    {\n        internalEncodeLogHeader(buffer, 0, 32, 64, () -> 5_600_000_000L);\n        final CloseSessionRequestEncoder requestEncoder = new CloseSessionRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(-1);\n\n        dissectControlRequest(CMD_IN_CLOSE_SESSION, buffer, 0, builder);\n\n        assertEquals(\"[5.600000000] \" + CONTEXT + \": \" + CMD_IN_CLOSE_SESSION.name() + \" [32/64]: controlSessionId=-1\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStartRecording()\n    {\n        internalEncodeLogHeader(buffer, 0, 32, 64, () -> 5_600_000_000L);\n        final StartRecordingRequestEncoder requestEncoder = new StartRecordingRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(5)\n            .correlationId(13)\n            .streamId(7)\n            .sourceLocation(SourceLocation.REMOTE)\n            .channel(\"foo\");\n\n        dissectControlRequest(CMD_IN_START_RECORDING, buffer, 0, builder);\n\n        assertEquals(\"[5.600000000] \" + CONTEXT + \": \" + CMD_IN_START_RECORDING.name() + \" [32/64]:\" +\n            \" controlSessionId=5\" +\n            \" correlationId=13\" +\n            \" streamId=7\" +\n            \" sourceLocation=\" + SourceLocation.REMOTE +\n            \" channel=foo\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStartRecording2()\n    {\n        internalEncodeLogHeader(buffer, 0, 32, 64, () -> 5_600_000_000L);\n        final StartRecordingRequest2Encoder requestEncoder = new StartRecordingRequest2Encoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(5)\n            .correlationId(13)\n            .streamId(7)\n            .sourceLocation(SourceLocation.REMOTE)\n            .autoStop(BooleanType.TRUE)\n            .channel(\"foo\");\n\n        dissectControlRequest(CMD_IN_START_RECORDING2, buffer, 0, builder);\n\n        assertEquals(\"[5.600000000] \" + CONTEXT + \": \" + CMD_IN_START_RECORDING2.name() + \" [32/64]:\" +\n            \" controlSessionId=5\" +\n            \" correlationId=13\" +\n            \" streamId=7\" +\n            \" sourceLocation=\" + SourceLocation.REMOTE +\n            \" autoStop=\" + BooleanType.TRUE +\n            \" channel=foo\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStopRecording()\n    {\n        internalEncodeLogHeader(buffer, 0, 32, 64, () -> 5_600_000_000L);\n        final StopRecordingRequestEncoder requestEncoder = new StopRecordingRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(5)\n            .correlationId(42)\n            .streamId(7)\n            .channel(\"bar\");\n\n        dissectControlRequest(CMD_IN_STOP_RECORDING, buffer, 0, builder);\n\n        assertEquals(\"[5.600000000] \" + CONTEXT + \": \" + CMD_IN_STOP_RECORDING.name() + \" [32/64]:\" +\n            \" controlSessionId=5\" +\n            \" correlationId=42\" +\n            \" streamId=7\" +\n            \" channel=bar\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestReplay()\n    {\n        internalEncodeLogHeader(buffer, 0, 90, 90, () -> 1_125_000_000L);\n        final ReplayRequestEncoder requestEncoder = new ReplayRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(5)\n            .correlationId(42)\n            .recordingId(178)\n            .position(Long.MAX_VALUE)\n            .length(2000)\n            .replayStreamId(99)\n            .replayChannel(\"replay channel\");\n\n        dissectControlRequest(CMD_IN_REPLAY, buffer, 0, builder);\n\n        assertEquals(\"[1.125000000] \" + CONTEXT + \": \" + CMD_IN_REPLAY.name() + \" [90/90]:\" +\n            \" controlSessionId=5\" +\n            \" correlationId=42\" +\n            \" recordingId=178\" +\n            \" position=\" + Long.MAX_VALUE +\n            \" length=2000\" +\n            \" replayStreamId=99\" +\n            \" replayChannel=replay channel\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStopReplay()\n    {\n        internalEncodeLogHeader(buffer, 0, 90, 90, () -> 1_125_000_000L);\n        final StopReplayRequestEncoder requestEncoder = new StopReplayRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(5)\n            .correlationId(42)\n            .replaySessionId(66);\n\n        dissectControlRequest(CMD_IN_STOP_REPLAY, buffer, 0, builder);\n\n        assertEquals(\"[1.125000000] \" + CONTEXT + \": \" + CMD_IN_STOP_REPLAY.name() + \" [90/90]:\" +\n            \" controlSessionId=5\" +\n            \" correlationId=42\" +\n            \" replaySessionId=66\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestListRecordings()\n    {\n        internalEncodeLogHeader(buffer, 0, 32, 32, () -> 100_000_000L);\n        final ListRecordingsRequestEncoder requestEncoder = new ListRecordingsRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(9)\n            .correlationId(78)\n            .fromRecordingId(45)\n            .recordCount(10);\n\n        dissectControlRequest(CMD_IN_LIST_RECORDINGS, buffer, 0, builder);\n\n        assertEquals(\"[0.100000000] \" + CONTEXT + \": \" + CMD_IN_LIST_RECORDINGS.name() + \" [32/32]:\" +\n            \" controlSessionId=9\" +\n            \" correlationId=78\" +\n            \" fromRecordingId=45\" +\n            \" recordCount=10\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestListRecordingsForUri()\n    {\n        internalEncodeLogHeader(buffer, 0, 32, 32, () -> 100_000_000L);\n        final ListRecordingsForUriRequestEncoder requestEncoder = new ListRecordingsForUriRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(9)\n            .correlationId(78)\n            .fromRecordingId(45)\n            .recordCount(10)\n            .streamId(200)\n            .channel(\"CH\");\n\n        dissectControlRequest(CMD_IN_LIST_RECORDINGS_FOR_URI, buffer, 0, builder);\n\n        assertEquals(\"[0.100000000] \" + CONTEXT + \": \" + CMD_IN_LIST_RECORDINGS_FOR_URI.name() + \" [32/32]:\" +\n            \" controlSessionId=9\" +\n            \" correlationId=78\" +\n            \" fromRecordingId=45\" +\n            \" recordCount=10\" +\n            \" streamId=200\" +\n            \" channel=CH\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestListRecording()\n    {\n        internalEncodeLogHeader(buffer, 0, 32, 32, () -> 100_000_000L);\n        final ListRecordingRequestEncoder requestEncoder = new ListRecordingRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(19)\n            .correlationId(178)\n            .recordingId(1010101);\n\n        dissectControlRequest(CMD_IN_LIST_RECORDING, buffer, 0, builder);\n\n        assertEquals(\"[0.100000000] \" + CONTEXT + \": \" + CMD_IN_LIST_RECORDING.name() + \" [32/32]:\" +\n            \" controlSessionId=19\" +\n            \" correlationId=178\" +\n            \" recordingId=1010101\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestExtendRecording()\n    {\n        internalEncodeLogHeader(buffer, 0, 12, 32, () -> 10_000_000_000L);\n        final ExtendRecordingRequestEncoder requestEncoder = new ExtendRecordingRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(9)\n            .correlationId(78)\n            .recordingId(1010101)\n            .streamId(43)\n            .sourceLocation(SourceLocation.LOCAL)\n            .channel(\"extend me\");\n\n        dissectControlRequest(CMD_IN_EXTEND_RECORDING, buffer, 0, builder);\n\n        assertEquals(\"[10.000000000] \" + CONTEXT + \": \" + CMD_IN_EXTEND_RECORDING.name() + \" [12/32]:\" +\n            \" controlSessionId=9\" +\n            \" correlationId=78\" +\n            \" recordingId=1010101\" +\n            \" streamId=43\" +\n            \" sourceLocation=\" + SourceLocation.LOCAL +\n            \" channel=extend me\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestExtendRecording2()\n    {\n        internalEncodeLogHeader(buffer, 0, 12, 32, () -> 10_000_000_000L);\n        final ExtendRecordingRequest2Encoder requestEncoder = new ExtendRecordingRequest2Encoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(9)\n            .correlationId(78)\n            .recordingId(1010101)\n            .streamId(43)\n            .sourceLocation(SourceLocation.LOCAL)\n            .autoStop(BooleanType.TRUE)\n            .channel(\"extend me\");\n\n        dissectControlRequest(CMD_IN_EXTEND_RECORDING2, buffer, 0, builder);\n\n        assertEquals(\"[10.000000000] \" + CONTEXT + \": \" + CMD_IN_EXTEND_RECORDING2.name() + \" [12/32]:\" +\n            \" controlSessionId=9\" +\n            \" correlationId=78\" +\n            \" recordingId=1010101\" +\n            \" streamId=43\" +\n            \" sourceLocation=\" + SourceLocation.LOCAL +\n            \" autoStop=\" + BooleanType.TRUE +\n            \" channel=extend me\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestRecordingPosition()\n    {\n        internalEncodeLogHeader(buffer, 0, 12, 32, () -> 10_000_000_000L);\n        final RecordingPositionRequestEncoder requestEncoder = new RecordingPositionRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(2)\n            .correlationId(3)\n            .recordingId(6);\n\n        dissectControlRequest(CMD_IN_RECORDING_POSITION, buffer, 0, builder);\n\n        assertEquals(\"[10.000000000] \" + CONTEXT + \": \" + CMD_IN_RECORDING_POSITION.name() + \" [12/32]:\" +\n            \" controlSessionId=2\" +\n            \" correlationId=3\" +\n            \" recordingId=6\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestTruncateRecording()\n    {\n        internalEncodeLogHeader(buffer, 0, 12, 32, () -> 10_000_000_000L);\n        final TruncateRecordingRequestEncoder requestEncoder = new TruncateRecordingRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(2)\n            .correlationId(3)\n            .recordingId(8)\n            .position(1_000_000);\n\n        dissectControlRequest(CMD_IN_TRUNCATE_RECORDING, buffer, 0, builder);\n\n        assertEquals(\"[10.000000000] \" + CONTEXT + \": \" + CMD_IN_TRUNCATE_RECORDING.name() + \" [12/32]:\" +\n            \" controlSessionId=2\" +\n            \" correlationId=3\" +\n            \" recordingId=8\" +\n            \" position=1000000\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStopRecordingSubscription()\n    {\n        internalEncodeLogHeader(buffer, 0, 12, 32, () -> 10_000_000_000L);\n        final StopRecordingSubscriptionRequestEncoder requestEncoder = new StopRecordingSubscriptionRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(22)\n            .correlationId(33)\n            .subscriptionId(888);\n\n        dissectControlRequest(CMD_IN_STOP_RECORDING_SUBSCRIPTION, buffer, 0, builder);\n\n        assertEquals(\"[10.000000000] \" + CONTEXT + \": \" + CMD_IN_STOP_RECORDING_SUBSCRIPTION.name() + \" [12/32]:\" +\n            \" controlSessionId=22\" +\n            \" correlationId=33\" +\n            \" subscriptionId=888\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStopRecordingByIdentity()\n    {\n        internalEncodeLogHeader(buffer, 0, 12, 32, () -> 10_000_000_000L);\n        final StopRecordingByIdentityRequestEncoder requestEncoder = new StopRecordingByIdentityRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(22)\n            .correlationId(33)\n            .recordingId(777);\n\n        dissectControlRequest(CMD_IN_STOP_RECORDING_BY_IDENTITY, buffer, 0, builder);\n\n        assertEquals(\"[10.000000000] \" + CONTEXT + \": \" + CMD_IN_STOP_RECORDING_BY_IDENTITY.name() + \" [12/32]:\" +\n            \" controlSessionId=22\" +\n            \" correlationId=33\" +\n            \" recordingId=777\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStopPosition()\n    {\n        internalEncodeLogHeader(buffer, 0, 12, 32, () -> 10_000_000_000L);\n        final StopPositionRequestEncoder requestEncoder = new StopPositionRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(22)\n            .correlationId(33)\n            .recordingId(44);\n\n        dissectControlRequest(CMD_IN_STOP_POSITION, buffer, 0, builder);\n\n        assertEquals(\"[10.000000000] \" + CONTEXT + \": \" + CMD_IN_STOP_POSITION.name() + \" [12/32]:\" +\n            \" controlSessionId=22\" +\n            \" correlationId=33\" +\n            \" recordingId=44\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestFindLastMatchingRecording()\n    {\n        internalEncodeLogHeader(buffer, 0, 90, 90, () -> 10_325_000_000L);\n        final FindLastMatchingRecordingRequestEncoder requestEncoder = new FindLastMatchingRecordingRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(1)\n            .correlationId(2)\n            .minRecordingId(3)\n            .sessionId(4)\n            .streamId(5)\n            .channel(\"this is a channel\");\n\n        dissectControlRequest(CMD_IN_FIND_LAST_MATCHING_RECORD, buffer, 0, builder);\n\n        assertEquals(\"[10.325000000] \" + CONTEXT + \": \" + CMD_IN_FIND_LAST_MATCHING_RECORD.name() + \" [90/90]:\" +\n            \" controlSessionId=1\" +\n            \" correlationId=2\" +\n            \" minRecordingId=3\" +\n            \" sessionId=4\" +\n            \" streamId=5\" +\n            \" channel=this is a channel\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestListRecordingSubscriptions()\n    {\n        internalEncodeLogHeader(buffer, 0, 90, 90, () -> 10_325_000_000L);\n        final ListRecordingSubscriptionsRequestEncoder requestEncoder = new ListRecordingSubscriptionsRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(1)\n            .correlationId(2)\n            .pseudoIndex(1111111)\n            .applyStreamId(BooleanType.TRUE)\n            .subscriptionCount(777)\n            .streamId(555)\n            .channel(\"ch2\");\n\n        dissectControlRequest(CMD_IN_LIST_RECORDING_SUBSCRIPTIONS, buffer, 0, builder);\n\n        assertEquals(\"[10.325000000] \" + CONTEXT + \": \" + CMD_IN_LIST_RECORDING_SUBSCRIPTIONS.name() + \" [90/90]:\" +\n            \" controlSessionId=1\" +\n            \" correlationId=2\" +\n            \" pseudoIndex=1111111\" +\n            \" applyStreamId=\" + BooleanType.TRUE +\n            \" subscriptionCount=777\" +\n            \" streamId=555\" +\n            \" channel=ch2\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStartBoundedReplay()\n    {\n        internalEncodeLogHeader(buffer, 0, 90, 90, () -> 10_325_000_000L);\n        final BoundedReplayRequestEncoder requestEncoder = new BoundedReplayRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(10)\n            .correlationId(20)\n            .recordingId(30)\n            .position(40)\n            .length(50)\n            .limitCounterId(-123)\n            .replayStreamId(14)\n            .replayChannel(\"rep ch\");\n\n        dissectControlRequest(CMD_IN_START_BOUNDED_REPLAY, buffer, 0, builder);\n\n        assertEquals(\"[10.325000000] \" + CONTEXT + \": \" + CMD_IN_START_BOUNDED_REPLAY.name() + \" [90/90]:\" +\n            \" controlSessionId=10\" +\n            \" correlationId=20\" +\n            \" recordingId=30\" +\n            \" position=40\" +\n            \" length=50\" +\n            \" limitCounterId=-123\" +\n            \" replayStreamId=14\" +\n            \" replayChannel=rep ch\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStopAllReplays()\n    {\n        internalEncodeLogHeader(buffer, 0, 90, 90, () -> 10_325_000_000L);\n        final StopAllReplaysRequestEncoder requestEncoder = new StopAllReplaysRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(10)\n            .correlationId(20)\n            .recordingId(30);\n\n        dissectControlRequest(CMD_IN_STOP_ALL_REPLAYS, buffer, 0, builder);\n\n        assertEquals(\"[10.325000000] \" + CONTEXT + \": \" + CMD_IN_STOP_ALL_REPLAYS.name() + \" [90/90]:\" +\n            \" controlSessionId=10\" +\n            \" correlationId=20\" +\n            \" recordingId=30\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestReplicate()\n    {\n        internalEncodeLogHeader(buffer, 0, 1000, 1000, () -> 500_000_000L);\n        final ReplicateRequestEncoder requestEncoder = new ReplicateRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(2)\n            .correlationId(5)\n            .srcRecordingId(17)\n            .dstRecordingId(2048)\n            .srcControlStreamId(10)\n            .srcControlChannel(\"CTRL ch\")\n            .liveDestination(\"live destination\");\n\n        dissectControlRequest(CMD_IN_REPLICATE, buffer, 0, builder);\n\n        assertEquals(\"[0.500000000] \" + CONTEXT + \": \" + CMD_IN_REPLICATE.name() + \" [1000/1000]:\" +\n            \" controlSessionId=2\" +\n            \" correlationId=5\" +\n            \" srcRecordingId=17\" +\n            \" dstRecordingId=2048\" +\n            \" srcControlStreamId=10\" +\n            \" srcControlChannel=CTRL ch\" +\n            \" liveDestination=live destination\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestReplicate2()\n    {\n        internalEncodeLogHeader(buffer, 0, 1000, 1000, () -> 500_000_000L);\n        final ReplicateRequest2Encoder requestEncoder = new ReplicateRequest2Encoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(2)\n            .correlationId(5)\n            .srcRecordingId(17)\n            .dstRecordingId(2048)\n            .stopPosition(4096)\n            .channelTagId(123)\n            .subscriptionTagId(321)\n            .srcControlStreamId(10)\n            .srcControlChannel(\"CTRL ch\")\n            .liveDestination(\"live destination\")\n            .replicationChannel(\"replication channel\");\n\n        dissectControlRequest(CMD_IN_REPLICATE2, buffer, 0, builder);\n\n        assertEquals(\"[0.500000000] \" + CONTEXT + \": \" + CMD_IN_REPLICATE2.name() + \" [1000/1000]:\" +\n            \" controlSessionId=2\" +\n            \" correlationId=5\" +\n            \" srcRecordingId=17\" +\n            \" dstRecordingId=2048\" +\n            \" stopPosition=4096\" +\n            \" channelTagId=123\" +\n            \" subscriptionTagId=321\" +\n            \" srcControlStreamId=10\" +\n            \" srcControlChannel=CTRL ch\" +\n            \" liveDestination=live destination\" +\n            \" replicationChannel=replication channel\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStopReplication()\n    {\n        internalEncodeLogHeader(buffer, 0, 1000, 1000, () -> 500_000_000L);\n        final StopReplicationRequestEncoder requestEncoder = new StopReplicationRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(-2)\n            .correlationId(-5)\n            .replicationId(-999);\n\n        dissectControlRequest(CMD_IN_STOP_REPLICATION, buffer, 0, builder);\n\n        assertEquals(\"[0.500000000] \" + CONTEXT + \": \" + CMD_IN_STOP_REPLICATION.name() + \" [1000/1000]:\" +\n            \" controlSessionId=-2\" +\n            \" correlationId=-5\" +\n            \" replicationId=-999\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestStartPosition()\n    {\n        internalEncodeLogHeader(buffer, 0, 1000, 1000, () -> 500_000_000L);\n        final StartPositionRequestEncoder requestEncoder = new StartPositionRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(3)\n            .correlationId(16)\n            .recordingId(1);\n\n        dissectControlRequest(CMD_IN_START_POSITION, buffer, 0, builder);\n\n        assertEquals(\"[0.500000000] \" + CONTEXT + \": \" + CMD_IN_START_POSITION.name() + \" [1000/1000]:\" +\n            \" controlSessionId=3\" +\n            \" correlationId=16\" +\n            \" recordingId=1\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestDetachSegments()\n    {\n        internalEncodeLogHeader(buffer, 0, 1000, 1000, () -> 500_000_000L);\n        final DetachSegmentsRequestEncoder requestEncoder = new DetachSegmentsRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(3)\n            .correlationId(16)\n            .recordingId(1);\n\n        dissectControlRequest(CMD_IN_DETACH_SEGMENTS, buffer, 0, builder);\n\n        assertEquals(\"[0.500000000] \" + CONTEXT + \": \" + CMD_IN_DETACH_SEGMENTS.name() + \" [1000/1000]:\" +\n            \" controlSessionId=3\" +\n            \" correlationId=16\" +\n            \" recordingId=1\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestDeleteDetachedSegments()\n    {\n        internalEncodeLogHeader(buffer, 0, 1000, 1000, () -> 500_000_000L);\n        final DeleteDetachedSegmentsRequestEncoder requestEncoder = new DeleteDetachedSegmentsRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(53)\n            .correlationId(516)\n            .recordingId(51);\n\n        dissectControlRequest(CMD_IN_DELETE_DETACHED_SEGMENTS, buffer, 0, builder);\n\n        assertEquals(\"[0.500000000] \" + CONTEXT + \": \" + CMD_IN_DELETE_DETACHED_SEGMENTS.name() + \" [1000/1000]:\" +\n            \" controlSessionId=53\" +\n            \" correlationId=516\" +\n            \" recordingId=51\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestPurgeSegments()\n    {\n        internalEncodeLogHeader(buffer, 0, 1000, 1000, () -> 500_000_000L);\n        final PurgeSegmentsRequestEncoder requestEncoder = new PurgeSegmentsRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(3)\n            .correlationId(56)\n            .recordingId(15)\n            .newStartPosition(100);\n\n        dissectControlRequest(CMD_IN_PURGE_SEGMENTS, buffer, 0, builder);\n\n        assertEquals(\"[0.500000000] \" + CONTEXT + \": \" + CMD_IN_PURGE_SEGMENTS.name() + \" [1000/1000]:\" +\n            \" controlSessionId=3\" +\n            \" correlationId=56\" +\n            \" recordingId=15\" +\n            \" newStartPosition=100\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestAttachSegments()\n    {\n        internalEncodeLogHeader(buffer, 0, 1000, 1000, () -> 500_000_000L);\n        final AttachSegmentsRequestEncoder requestEncoder = new AttachSegmentsRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(30)\n            .correlationId(560)\n            .recordingId(50);\n\n        dissectControlRequest(CMD_IN_ATTACH_SEGMENTS, buffer, 0, builder);\n\n        assertEquals(\"[0.500000000] \" + CONTEXT + \": \" + CMD_IN_ATTACH_SEGMENTS.name() + \" [1000/1000]:\" +\n            \" controlSessionId=30\" +\n            \" correlationId=560\" +\n            \" recordingId=50\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestMigrateSegments()\n    {\n        internalEncodeLogHeader(buffer, 0, 1000, 1000, () -> 500_000_000L);\n        final MigrateSegmentsRequestEncoder requestEncoder = new MigrateSegmentsRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(7)\n            .correlationId(6)\n            .srcRecordingId(1)\n            .dstRecordingId(21902);\n\n        dissectControlRequest(CMD_IN_MIGRATE_SEGMENTS, buffer, 0, builder);\n\n        assertEquals(\"[0.500000000] \" + CONTEXT + \": \" + CMD_IN_MIGRATE_SEGMENTS.name() + \" [1000/1000]:\" +\n            \" controlSessionId=7\" +\n            \" correlationId=6\" +\n            \" srcRecordingId=1\" +\n            \" dstRecordingId=21902\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestAuthConnect()\n    {\n        internalEncodeLogHeader(buffer, 0, 3, 6, () -> 5_500_000_000L);\n        final AuthConnectRequestEncoder requestEncoder = new AuthConnectRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .correlationId(16)\n            .responseStreamId(19)\n            .version(2)\n            .responseChannel(\"English Channel\")\n            .putEncodedCredentials(\"hello\".getBytes(US_ASCII), 0, 5);\n\n        dissectControlRequest(CMD_IN_AUTH_CONNECT, buffer, 0, builder);\n\n        assertEquals(\"[5.500000000] \" + CONTEXT + \": \" + CMD_IN_AUTH_CONNECT.name() + \" [3/6]:\" +\n            \" correlationId=16\" +\n            \" responseStreamId=19\" +\n            \" version=2\" +\n            \" responseChannel=English Channel\" +\n            \" encodedCredentialsLength=5\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestKeepAlive()\n    {\n        internalEncodeLogHeader(buffer, 0, 3, 6, () -> 5_500_000_000L);\n        final KeepAliveRequestEncoder requestEncoder = new KeepAliveRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(31)\n            .correlationId(119);\n\n        dissectControlRequest(CMD_IN_KEEP_ALIVE, buffer, 0, builder);\n\n        assertEquals(\"[5.500000000] \" + CONTEXT + \": \" + CMD_IN_KEEP_ALIVE.name() + \" [3/6]:\" +\n            \" controlSessionId=31\" +\n            \" correlationId=119\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestTaggedReplicate()\n    {\n        internalEncodeLogHeader(buffer, 0, 3, 6, () -> 5_500_000_000L);\n        final TaggedReplicateRequestEncoder requestEncoder = new TaggedReplicateRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(1)\n            .correlationId(-10)\n            .srcRecordingId(9)\n            .dstRecordingId(31)\n            .channelTagId(4)\n            .subscriptionTagId(7)\n            .srcControlStreamId(15)\n            .srcControlChannel(\"src\")\n            .liveDestination(\"alive and well\");\n\n        dissectControlRequest(CMD_IN_TAGGED_REPLICATE, buffer, 0, builder);\n\n        assertEquals(\"[5.500000000] \" + CONTEXT + \": \" + CMD_IN_TAGGED_REPLICATE.name() + \" [3/6]:\" +\n            \" controlSessionId=1\" +\n            \" correlationId=-10\" +\n            \" srcRecordingId=9\" +\n            \" dstRecordingId=31\" +\n            \" channelTagId=4\" +\n            \" subscriptionTagId=7\" +\n            \" srcControlStreamId=15\" +\n            \" srcControlChannel=src\" +\n            \" liveDestination=alive and well\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestUnknownCommand()\n    {\n        internalEncodeLogHeader(buffer, 0, 10, 20, () -> 2_543_298_765L);\n        headerEncoder.wrap(buffer, LOG_HEADER_LENGTH).templateId(Integer.MIN_VALUE);\n\n        dissectControlRequest(CMD_OUT_RESPONSE, buffer, 0, builder);\n\n        assertEquals(\"[2.543298765] \" + CONTEXT + \": \" + CMD_OUT_RESPONSE.name() + \" [10/20]: unknown command\",\n            builder.toString());\n    }\n\n    @Test\n    void replaySessionStateChange()\n    {\n        final String reason = \"some reason\";\n        internalEncodeLogHeader(buffer, 0, 10, 20, () -> 1_600_000_000L);\n        buffer.putLong(LOG_HEADER_LENGTH, 20_000_000_000L, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + SIZE_OF_LONG, 30_000_000_000L, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + 2 * SIZE_OF_LONG, 40_000_000_000L, LITTLE_ENDIAN);\n        final int stateChangeLength = buffer.putStringAscii(LOG_HEADER_LENGTH + 3 * SIZE_OF_LONG, \"x -> y\");\n        buffer.putStringAscii(LOG_HEADER_LENGTH + 3 * SIZE_OF_LONG + stateChangeLength, reason);\n\n        dissectReplaySessionStateChange(buffer, 0, builder);\n\n        assertEquals(\"[1.600000000] \" + CONTEXT + \": \" + REPLAY_SESSION_STATE_CHANGE.name() + \" [10/20]:\" +\n            \" replaySessionId=20000000000\" +\n            \" replayId=4\" +\n            \" sessionId=-1474836480\" +\n            \" recordingId=30000000000\" +\n            \" position=40000000000\" +\n            \" x -> y\" +\n            \" reason=\\\"some reason\\\"\",\n            builder.toString());\n    }\n\n    @Test\n    void recordingSessionStateChange()\n    {\n        final String reason = \"some other reason\";\n        internalEncodeLogHeader(buffer, 0, 10, 20, () -> 1_700_000_000L);\n        buffer.putLong(LOG_HEADER_LENGTH, 30_000_000_000L, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + SIZE_OF_LONG, 40_000_000_000L, LITTLE_ENDIAN);\n        final int stateChangeLength = buffer.putStringAscii(LOG_HEADER_LENGTH + 2 * SIZE_OF_LONG, \"x -> y\");\n        buffer.putStringAscii(LOG_HEADER_LENGTH + 2 * SIZE_OF_LONG + stateChangeLength, reason);\n\n        dissectRecordingSessionStateChange(buffer, 0, builder);\n\n        assertEquals(\"[1.700000000] \" + CONTEXT + \": \" + RECORDING_SESSION_STATE_CHANGE.name() + \" [10/20]:\" +\n            \" recordingId=30000000000\" +\n            \" position=40000000000\" +\n            \" x -> y\" +\n            \" reason=\\\"some other reason\\\"\",\n            builder.toString());\n    }\n\n    @Test\n    void replicationSessionStateChange()\n    {\n        final String reason = \"no reason\";\n        internalEncodeLogHeader(buffer, 0, 10, 20, () -> 1_500_000_000L);\n        buffer.putLong(LOG_HEADER_LENGTH, 10_000_000_000L, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + SIZE_OF_LONG, 20_000_000_000L, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + 2 * SIZE_OF_LONG, 30_000_000_000L, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + 3 * SIZE_OF_LONG, 40_000_000_000L, LITTLE_ENDIAN);\n        final int stateChangeLength = buffer.putStringAscii(LOG_HEADER_LENGTH + 4 * SIZE_OF_LONG, \"x -> y\");\n        buffer.putStringAscii(LOG_HEADER_LENGTH + 4 * SIZE_OF_LONG + stateChangeLength, reason);\n\n        dissectReplicationSessionStateChange(buffer, 0, builder);\n\n        assertEquals(\"[1.500000000] \" + CONTEXT + \": \" + REPLICATION_SESSION_STATE_CHANGE.name() + \" [10/20]:\" +\n            \" replicationId=10000000000\" +\n            \" srcRecordingId=20000000000\" +\n            \" dstRecordingId=30000000000\" +\n            \" position=40000000000\" +\n            \" x -> y\" +\n            \" reason=\\\"no reason\\\"\",\n            builder.toString());\n    }\n\n    @Test\n    void controlSessionStateChange()\n    {\n        internalEncodeLogHeader(buffer, 0, 10, 20, () -> 1_500_000_000L);\n        buffer.putLong(LOG_HEADER_LENGTH, -10_000_000_000L, LITTLE_ENDIAN);\n        final int length = buffer.putStringAscii(LOG_HEADER_LENGTH + SIZE_OF_LONG, \"x -> y\");\n        buffer.putStringAscii(LOG_HEADER_LENGTH + SIZE_OF_LONG + length, \"the very reason to report\");\n\n        dissectControlSessionStateChange(buffer, 0, builder);\n\n        assertEquals(\"[1.500000000] \" + CONTEXT + \": \" + CONTROL_SESSION_STATE_CHANGE.name() + \" [10/20]:\" +\n            \" controlSessionId=-10000000000\" +\n            \" x -> y reason=\\\"the very reason to report\\\"\",\n            builder.toString());\n    }\n\n    @Test\n    void replaySessionError()\n    {\n        internalEncodeLogHeader(buffer, 0, 6, 100, () -> 5_600_000_000L);\n        buffer.putLong(LOG_HEADER_LENGTH, -8, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + SIZE_OF_LONG, 42, LITTLE_ENDIAN);\n        buffer.putStringAscii(LOG_HEADER_LENGTH + SIZE_OF_LONG * 2, \"something went wrong\");\n\n        dissectReplaySessionError(buffer, 0, builder);\n\n        assertEquals(\"[5.600000000] \" + CONTEXT + \": \" + REPLAY_SESSION_ERROR.name() + \" [6/100]:\" +\n            \" sessionId=-8 recordingId=42 errorMessage=something went wrong\",\n            builder.toString());\n    }\n\n    @Test\n    void catalogResize()\n    {\n        internalEncodeLogHeader(buffer, 0, 16, 16, () -> 5_600_000_000L);\n        buffer.putLong(LOG_HEADER_LENGTH, 128, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + SIZE_OF_LONG, 1024, LITTLE_ENDIAN);\n\n        dissectCatalogResize(buffer, 0, builder);\n\n        assertEquals(\"[5.600000000] \" + CONTEXT + \": \" + CATALOG_RESIZE.name() + \" [16/16]:\" +\n            \" 128 bytes => 1024 bytes\",\n            builder.toString());\n    }\n\n    @Test\n    void controlRequestPurgeRecording()\n    {\n        internalEncodeLogHeader(buffer, 0, 56, 901, () -> 1_125_000_000L);\n        final PurgeRecordingRequestEncoder requestEncoder = new PurgeRecordingRequestEncoder();\n        requestEncoder.wrapAndApplyHeader(buffer, LOG_HEADER_LENGTH, headerEncoder)\n            .controlSessionId(15)\n            .correlationId(421)\n            .recordingId(6);\n\n        dissectControlRequest(CMD_IN_PURGE_RECORDING, buffer, 0, builder);\n\n        assertEquals(\"[1.125000000] \" + CONTEXT + \": \" + CMD_IN_PURGE_RECORDING.name() + \" [56/901]:\" +\n            \" controlSessionId=15\" +\n            \" correlationId=421\" +\n            \" recordingId=6\",\n            builder.toString());\n    }\n\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ArchiveEventEncoderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.temporal.ChronoUnit;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.agent.ArchiveEventEncoder.*;\nimport static io.aeron.agent.CommonEventEncoder.*;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.util.concurrent.TimeUnit.DAYS;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\nclass ArchiveEventEncoderTest\n{\n    enum State\n    {\n        ALPHA, BETA\n    }\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH]);\n\n    @Test\n    void testEncodeControlSessionStateChange()\n    {\n        final int offset = 24;\n        final TimeUnit from = DAYS;\n        final TimeUnit to = MILLISECONDS;\n        final long sessionId = Long.MAX_VALUE;\n        final String payload = from.name() + STATE_SEPARATOR + to.name();\n        final String reason = \"test reason for now\";\n        final int length = payload.length() + SIZE_OF_LONG + SIZE_OF_INT + SIZE_OF_INT + reason.length();\n        final int captureLength = captureLength(length);\n\n        final int encodedLength = encodeControlSessionStateChange(\n            buffer, offset, captureLength, length, from, to, sessionId, reason);\n\n        assertEquals(encodedLength(sessionStateChangeLength(from, to, reason)), encodedLength);\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getLong(offset + LOG_HEADER_LENGTH));\n        assertEquals(payload, buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG));\n    }\n\n    @Test\n    void testSessionStateChangeLength()\n    {\n        final ChronoUnit from = ChronoUnit.ERAS;\n        final ChronoUnit to = ChronoUnit.MILLENNIA;\n        final String payload = from.name() + STATE_SEPARATOR + to.name();\n        final String reason = \"hfskhflkdhfldshlfkhllkshflkhsldfhaslkfhsaklhflksahdflsahlhalks\";\n\n        assertEquals(\n            payload.length() + SIZE_OF_LONG + SIZE_OF_INT * 2 + reason.length(),\n            sessionStateChangeLength(from, to, reason));\n    }\n\n    @Test\n    void testEncodeReplaySessionError()\n    {\n        final int offset = 24;\n        final long sessionId = Long.MAX_VALUE;\n        final long recordingId = 56;\n        final String errorMessage = \"funny\";\n        final int length = errorMessage.length() + SIZE_OF_LONG * 2 + SIZE_OF_INT;\n        final int captureLength = captureLength(length);\n\n        encodeReplaySessionError(buffer, offset, captureLength, length, sessionId, recordingId, errorMessage);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getLong(offset + LOG_HEADER_LENGTH));\n        assertEquals(recordingId, buffer.getLong(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG));\n        assertEquals(errorMessage, buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG * 2));\n    }\n\n    @Test\n    void testEncodeCatalogResize()\n    {\n        final int offset = 24;\n        final int length = SIZE_OF_LONG * 2 + SIZE_OF_INT * 2;\n        final int captureLength = captureLength(length);\n        final long catalogLength = 128;\n        final long newCatalogLength = 1024;\n\n        encodeCatalogResize(buffer, offset, captureLength, length, catalogLength, newCatalogLength);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(catalogLength, buffer.getLong(offset + LOG_HEADER_LENGTH));\n        assertEquals(newCatalogLength, buffer.getLong(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG));\n    }\n\n    @Test\n    void testEncodeReplaySessionStateChange()\n    {\n        int offset = 24;\n        final int length = replaySessionStateChangeLength(State.ALPHA, State.BETA, \"reason\");\n        final int captureLength = captureLength(length);\n\n        encodeReplaySessionStateChange(buffer, offset, captureLength, length,\n            State.ALPHA, State.BETA, 1, 2, 3, \"reason\");\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + 2 * SIZE_OF_INT, LITTLE_ENDIAN));\n        offset += LOG_HEADER_LENGTH;\n\n        assertEquals(1, buffer.getLong(offset, LITTLE_ENDIAN));\n        offset += SIZE_OF_LONG;\n        assertEquals(2, buffer.getLong(offset, LITTLE_ENDIAN));\n        offset += SIZE_OF_LONG;\n        assertEquals(3, buffer.getLong(offset, LITTLE_ENDIAN));\n        offset += SIZE_OF_LONG;\n        assertEquals(\"ALPHA -> BETA\", buffer.getStringAscii(offset));\n        offset += SIZE_OF_INT + \"ALPHA -> BETA\".length();\n        assertEquals(\"reason\", buffer.getStringAscii(offset));\n    }\n\n    @Test\n    void testEncodeRecordingSessionStateChange()\n    {\n        int offset = 24;\n        final int length = recordingSessionStateChangeLength(State.ALPHA, State.BETA, \"reason\");\n        final int captureLength = captureLength(length);\n\n        encodeRecordingSessionStateChange(buffer, offset, captureLength, length,\n            State.ALPHA, State.BETA, 1, 2, \"reason\");\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + 2 * SIZE_OF_INT, LITTLE_ENDIAN));\n        offset += LOG_HEADER_LENGTH;\n\n        assertEquals(1, buffer.getLong(offset, LITTLE_ENDIAN));\n        offset += SIZE_OF_LONG;\n        assertEquals(2, buffer.getLong(offset, LITTLE_ENDIAN));\n        offset += SIZE_OF_LONG;\n        assertEquals(\"ALPHA -> BETA\", buffer.getStringAscii(offset));\n        offset += SIZE_OF_INT + \"ALPHA -> BETA\".length();\n        assertEquals(\"reason\", buffer.getStringAscii(offset));\n    }\n\n    @Test\n    void testEncodeReplicationSessionStateChange()\n    {\n        int offset = 24;\n        final int length = replicationSessionStateChangeLength(State.ALPHA, State.BETA, \"reason\");\n        final int captureLength = captureLength(length);\n\n        encodeReplicationSessionStateChange(buffer, offset, captureLength, length,\n            State.ALPHA, State.BETA, 1, 2, 3, 4, \"reason\");\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + 2 * SIZE_OF_INT, LITTLE_ENDIAN));\n        offset += LOG_HEADER_LENGTH;\n\n        assertEquals(1, buffer.getLong(offset, LITTLE_ENDIAN));\n        offset += SIZE_OF_LONG;\n        assertEquals(2, buffer.getLong(offset, LITTLE_ENDIAN));\n        offset += SIZE_OF_LONG;\n        assertEquals(3, buffer.getLong(offset, LITTLE_ENDIAN));\n        offset += SIZE_OF_LONG;\n        assertEquals(4, buffer.getLong(offset, LITTLE_ENDIAN));\n        offset += SIZE_OF_LONG;\n        assertEquals(\"ALPHA -> BETA\", buffer.getStringAscii(offset));\n        offset += SIZE_OF_INT + \"ALPHA -> BETA\".length();\n        assertEquals(\"reason\", buffer.getStringAscii(offset));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ArchiveEventLoggerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.archive.codecs.ListRecordingRequestDecoder;\nimport io.aeron.archive.codecs.MaxRecordedPositionRequestEncoder;\nimport io.aeron.archive.codecs.MessageHeaderEncoder;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.time.temporal.ChronoUnit;\n\nimport static io.aeron.agent.AgentTests.verifyLogHeader;\nimport static io.aeron.agent.ArchiveEventCode.CATALOG_RESIZE;\nimport static io.aeron.agent.ArchiveEventCode.CMD_IN_MAX_RECORDED_POSITION;\nimport static io.aeron.agent.ArchiveEventCode.CMD_OUT_RESPONSE;\nimport static io.aeron.agent.ArchiveEventCode.CONTROL_SESSION_STATE_CHANGE;\nimport static io.aeron.agent.ArchiveEventCode.RECORDING_SIGNAL;\nimport static io.aeron.agent.ArchiveEventCode.REPLAY_SESSION_ERROR;\nimport static io.aeron.agent.ArchiveEventCode.REPLICATION_SESSION_DONE;\nimport static io.aeron.agent.ArchiveEventCode.REPLICATION_SESSION_STATE_CHANGE;\nimport static io.aeron.agent.ArchiveEventEncoder.replicationSessionDoneLength;\nimport static io.aeron.agent.ArchiveEventEncoder.replicationSessionStateChangeLength;\nimport static io.aeron.agent.ArchiveEventLogger.CONTROL_REQUEST_EVENTS;\nimport static io.aeron.agent.CommonEventEncoder.LOG_HEADER_LENGTH;\nimport static io.aeron.agent.CommonEventEncoder.MAX_CAPTURE_LENGTH;\nimport static io.aeron.agent.CommonEventEncoder.STATE_SEPARATOR;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static io.aeron.archive.codecs.MessageHeaderEncoder.ENCODED_LENGTH;\nimport static java.nio.ByteBuffer.allocateDirect;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.BitUtil.align;\nimport static org.agrona.concurrent.ringbuffer.RecordDescriptor.ALIGNMENT;\nimport static org.agrona.concurrent.ringbuffer.RecordDescriptor.HEADER_LENGTH;\nimport static org.agrona.concurrent.ringbuffer.RecordDescriptor.encodedMsgOffset;\nimport static org.agrona.concurrent.ringbuffer.RecordDescriptor.lengthOffset;\nimport static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.HEAD_CACHE_POSITION_OFFSET;\nimport static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TAIL_POSITION_OFFSET;\nimport static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TRAILER_LENGTH;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;\nimport static org.junit.jupiter.params.provider.EnumSource.Mode.INCLUDE;\n\nclass ArchiveEventLoggerTest\n{\n    private static final int CAPACITY = align(MAX_EVENT_LENGTH, CACHE_LINE_LENGTH) * 8;\n    private final UnsafeBuffer logBuffer = new UnsafeBuffer(allocateDirect(CAPACITY + TRAILER_LENGTH));\n    private final ArchiveEventLogger logger = new ArchiveEventLogger(new ManyToOneRingBuffer(logBuffer));\n    private final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH * 3]);\n\n    @AfterEach\n    void after()\n    {\n        ArchiveComponentLogger.ENABLED_EVENTS.clear();\n        EventConfiguration.EVENT_RING_BUFFER.unblock();\n    }\n\n    @ParameterizedTest\n    @EnumSource(\n        value = ArchiveEventCode.class,\n        mode = EXCLUDE,\n        names = {\n            \"CMD_OUT_RESPONSE\", \"REPLICATION_SESSION_STATE_CHANGE\",\n            \"CONTROL_SESSION_STATE_CHANGE\", \"REPLAY_SESSION_ERROR\", \"CATALOG_RESIZE\",\n            \"REPLICATION_SESSION_DONE\", \"REPLAY_SESSION_STATE_CHANGE\", \"RECORDING_SESSION_STATE_CHANGE\"\n        })\n    void logControlRequest(final ArchiveEventCode eventCode)\n    {\n        ArchiveComponentLogger.ENABLED_EVENTS.add(eventCode);\n        final int srcOffset = 100;\n        final int length = MAX_EVENT_LENGTH * 2;\n        new MessageHeaderEncoder().wrap(srcBuffer, srcOffset).templateId(eventCode.templateId());\n        srcBuffer.setMemory(srcOffset + ENCODED_LENGTH, length, (byte)3);\n        final int captureLength = MAX_CAPTURE_LENGTH;\n        logBuffer.putLong(CAPACITY + HEAD_CACHE_POSITION_OFFSET, CAPACITY * 3L);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, 128 + CAPACITY * 3L);\n        final int recordOffset = 128;\n\n        logger.logControlRequest(srcBuffer, srcOffset, length);\n\n        verifyLogHeader(logBuffer, recordOffset, eventCode.toEventCodeId(), captureLength, length);\n        for (int i = 0; i < captureLength - ENCODED_LENGTH; i++)\n        {\n            assertEquals((byte)3,\n                logBuffer.getByte(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + ENCODED_LENGTH + i)));\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { Integer.MIN_VALUE, ListRecordingRequestDecoder.TEMPLATE_ID })\n    void logControlRequestNoOp(final int templateId)\n    {\n        final int srcOffset = 0;\n        new MessageHeaderEncoder().wrap(srcBuffer, srcOffset).templateId(templateId);\n        final int length = 100;\n        srcBuffer.setMemory(srcOffset + ENCODED_LENGTH, length, (byte)3);\n        final int recordOffset = 0;\n\n        logger.logControlRequest(srcBuffer, srcOffset, length);\n\n        assertEquals(0, logBuffer.getInt(lengthOffset(recordOffset), LITTLE_ENDIAN));\n    }\n\n    @Test\n    void logControlResponse()\n    {\n        final int offset = 4;\n        final int length = 64;\n        srcBuffer.setMemory(0, offset, (byte)255);\n        srcBuffer.setMemory(offset, length, (byte)1);\n        final int recordOffset = HEADER_LENGTH * 5;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n\n        logger.logControlResponse(srcBuffer, offset, length);\n\n        verifyLogHeader(logBuffer, recordOffset, CMD_OUT_RESPONSE.toEventCodeId(), length, length);\n        for (int i = 0; i < length; i++)\n        {\n            assertEquals((byte)1, logBuffer.getByte(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + i)));\n        }\n    }\n\n    @Test\n    void logRecordingSignal()\n    {\n        final int offset = 10;\n        final int length = 31;\n        srcBuffer.setMemory(0, offset, (byte)255);\n        srcBuffer.setMemory(offset, length, (byte)3);\n        final int recordOffset = HEADER_LENGTH * 7;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n\n        logger.logRecordingSignal(srcBuffer, offset, length);\n\n        verifyLogHeader(logBuffer, recordOffset, RECORDING_SIGNAL.toEventCodeId(), length, length);\n        for (int i = 0; i < length; i++)\n        {\n            assertEquals((byte)3, logBuffer.getByte(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + i)));\n        }\n    }\n\n    @ParameterizedTest\n    @EnumSource(\n        value = ArchiveEventCode.class,\n        mode = EXCLUDE,\n        names = {\n            \"CMD_OUT_RESPONSE\",\n            \"REPLICATION_SESSION_STATE_CHANGE\",\n            \"CONTROL_SESSION_STATE_CHANGE\",\n            \"REPLAY_SESSION_ERROR\",\n            \"CATALOG_RESIZE\",\n            \"RECORDING_SIGNAL\",\n            \"REPLICATION_SESSION_DONE\",\n            \"REPLAY_SESSION_STATE_CHANGE\",\n            \"RECORDING_SESSION_STATE_CHANGE\" })\n    void controlRequestEvents(final ArchiveEventCode eventCode)\n    {\n        assertTrue(CONTROL_REQUEST_EVENTS.contains(eventCode));\n    }\n\n    @ParameterizedTest\n    @EnumSource(\n        value = ArchiveEventCode.class,\n        mode = INCLUDE,\n        names = { \"CMD_OUT_RESPONSE\", \"REPLICATION_SESSION_STATE_CHANGE\",\n            \"REPLAY_SESSION_STATE_CHANGE\", \"RECORDING_SESSION_STATE_CHANGE\",\n            \"CONTROL_SESSION_STATE_CHANGE\", \"REPLAY_SESSION_ERROR\", \"CATALOG_RESIZE\" })\n    void nonControlRequestEvents(final ArchiveEventCode eventCode)\n    {\n        assertFalse(CONTROL_REQUEST_EVENTS.contains(eventCode));\n    }\n\n    @Test\n    void logControlSessionStateChange()\n    {\n        final int offset = ALIGNMENT * 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final ChronoUnit from = ChronoUnit.CENTURIES;\n        final ChronoUnit to = ChronoUnit.MICROS;\n        final long id = 555_000_000_000L;\n        final String payload = from.name() + STATE_SEPARATOR + to.name();\n        final String reason = \"test reason to check\";\n        final int captureLength = SIZE_OF_LONG + SIZE_OF_INT + payload.length() + SIZE_OF_INT + reason.length();\n\n        logger.logControlSessionStateChange(from, to, id, reason);\n\n        verifyLogHeader(\n            logBuffer, offset, CONTROL_SESSION_STATE_CHANGE.toEventCodeId(), captureLength, captureLength);\n        assertEquals(id, logBuffer.getLong(encodedMsgOffset(offset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(\n            payload, logBuffer.getStringAscii(encodedMsgOffset(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG)));\n        assertEquals(reason, logBuffer.getStringAscii(encodedMsgOffset(\n            offset + LOG_HEADER_LENGTH + SIZE_OF_LONG + SIZE_OF_INT + payload.length())));\n    }\n\n    @Test\n    void logReplaySessionError()\n    {\n        final int offset = ALIGNMENT * 5 + 128;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final long sessionId = 123;\n        final long recordingId = Long.MIN_VALUE;\n        final String errorMessage = \"the error\";\n        final int captureLength = SIZE_OF_LONG * 2 + SIZE_OF_INT + errorMessage.length();\n\n        logger.logReplaySessionError(sessionId, recordingId, errorMessage);\n\n        verifyLogHeader(logBuffer, offset, REPLAY_SESSION_ERROR.toEventCodeId(), captureLength, captureLength);\n        assertEquals(sessionId, logBuffer.getLong(encodedMsgOffset(offset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(recordingId,\n            logBuffer.getLong(encodedMsgOffset(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(errorMessage,\n            logBuffer.getStringAscii(encodedMsgOffset(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG * 2)));\n    }\n\n    @Test\n    void logCatalogResize()\n    {\n        final int offset = ALIGNMENT * 3;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final int captureLength = SIZE_OF_LONG * 2;\n        final long catalogLength = 42;\n        final long newCatalogLength = 142;\n\n        logger.logCatalogResize(catalogLength, newCatalogLength);\n\n        verifyLogHeader(logBuffer, offset, CATALOG_RESIZE.toEventCodeId(), captureLength, captureLength);\n        assertEquals(catalogLength,\n            logBuffer.getLong(encodedMsgOffset(offset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(newCatalogLength,\n            logBuffer.getLong(encodedMsgOffset(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG), LITTLE_ENDIAN));\n    }\n\n    @Test\n    void logReplicationSessionDone()\n    {\n        final long controlSessionId = 232345;\n        final long replicationId = 456456;\n        final long srcRecordingId = 345123;\n        final long replayPosition = 2345;\n        final long srcStopPosition = 3245;\n        final long dstRecordingId = 435675346;\n        final long dstStopPosition = 5685623;\n        final long position = 3425234;\n        final boolean isClosed = true;\n        final boolean isEndOfStream = true;\n        final boolean isSynced = false;\n\n        final int offset = ALIGNMENT * 3;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        logger.logReplicationSessionDone(\n            controlSessionId,\n            replicationId,\n            srcRecordingId,\n            replayPosition,\n            srcStopPosition,\n            dstRecordingId,\n            dstStopPosition,\n            position,\n            isClosed,\n            isEndOfStream,\n            isSynced);\n\n        verifyLogHeader(\n            logBuffer,\n            offset,\n            REPLICATION_SESSION_DONE.toEventCodeId(),\n            replicationSessionDoneLength(),\n            replicationSessionDoneLength());\n\n        final StringBuilder sb = new StringBuilder();\n        ArchiveEventDissector.dissectReplicationSessionDone(\n            logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern =\n            \"\\\\[[0-9]+\\\\.[0-9]+] ARCHIVE: REPLICATION_SESSION_DONE \\\\[67/67]: controlSessionId=\" + controlSessionId +\n            \" replicationId=\" + replicationId + \" srcRecordingId=\" + srcRecordingId +\n            \" replayPosition=\" + replayPosition + \" srcStopPosition=\" + srcStopPosition +\n            \" dstRecordingId=\" + dstRecordingId + \" dstStopPosition=\" + dstStopPosition + \" position=\" + position +\n            \" isClosed=\" + isClosed + \" isEndOfStream=\" + isEndOfStream + \" isSynced=\" + isSynced;\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logReplicationSessionStateChange()\n    {\n        final ChronoUnit oldState = ChronoUnit.ERAS;\n        final ChronoUnit newState = ChronoUnit.MILLENNIA;\n        final long replicationId = 456456;\n        final long srcRecordingId = 345123;\n        final long dstRecordingId = 435675346;\n        final long position = 3425234;\n        final String reason = \"some text goes here\";\n\n        final int offset = ALIGNMENT * 3;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        logger.logReplicationSessionStateChange(\n            oldState,\n            newState,\n            replicationId,\n            srcRecordingId,\n            dstRecordingId,\n            position,\n            reason);\n\n        verifyLogHeader(\n            logBuffer,\n            offset,\n            REPLICATION_SESSION_STATE_CHANGE.toEventCodeId(),\n            replicationSessionStateChangeLength(oldState, newState, reason),\n            replicationSessionStateChangeLength(oldState, newState, reason));\n\n        final StringBuilder sb = new StringBuilder();\n        ArchiveEventDissector.dissectReplicationSessionStateChange(\n            logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern =\n            \"\\\\[[0-9]+\\\\.[0-9]+] ARCHIVE: REPLICATION_SESSION_STATE_CHANGE \\\\[76/76]:\" +\n            \" replicationId=\" + replicationId + \" srcRecordingId=\" + srcRecordingId +\n            \" dstRecordingId=\" + dstRecordingId + \" position=\" + position +\n            \" \" + oldState.name() + \" -> \" + newState.name() + \" reason=\\\"\" + reason + \"\\\"\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logMaxRecordedPosition()\n    {\n        final ArchiveEventCode eventCode = CMD_IN_MAX_RECORDED_POSITION;\n        ArchiveComponentLogger.ENABLED_EVENTS.add(eventCode);\n\n        final long controlSessionId = 0x777777;\n        final long recordingId = -99999999999999123L;\n        final long controlId = 0x11111111;\n\n        final int srcOffset = 64;\n        final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n        final MaxRecordedPositionRequestEncoder maxRecordedPositionRequestEncoder =\n            new MaxRecordedPositionRequestEncoder()\n                .wrapAndApplyHeader(srcBuffer, srcOffset, messageHeaderEncoder)\n                .controlSessionId(controlSessionId)\n                .recordingId(recordingId)\n                .correlationId(controlId);\n\n        final int length = maxRecordedPositionRequestEncoder.encodedLength() + messageHeaderEncoder.encodedLength();\n        final int recordOffset = 1024;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n\n        logger.logControlRequest(srcBuffer, srcOffset, length);\n\n        verifyLogHeader(logBuffer, recordOffset, eventCode.toEventCodeId(), length, length);\n\n        final StringBuilder sb = new StringBuilder();\n        ArchiveEventDissector.dissectControlRequest(eventCode, logBuffer, encodedMsgOffset(recordOffset), sb);\n\n        final String expectedMessagePattern =\n            \"\"\"\n                \\\\[[0-9]+\\\\.[0-9]+] ARCHIVE: CMD_IN_MAX_RECORDED_POSITION \\\\[32/32]:\\\n                 controlSessionId=7829367 correlationId=286331153 recordingId=-99999999999999123\"\"\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ArchiveLoggingAgentTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.archive.ArchivingMediaDriver;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.Tests;\nimport org.agrona.IoUtil;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.MessageHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.io.File;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.agent.ArchiveEventCode.*;\nimport static io.aeron.agent.EventConfiguration.EVENT_READER_FRAME_LIMIT;\nimport static io.aeron.agent.EventConfiguration.EVENT_RING_BUFFER;\nimport static java.util.Collections.synchronizedSet;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ArchiveLoggingAgentTest\n{\n    private static final Set<ArchiveEventCode> WAIT_LIST = synchronizedSet(new HashSet<>());\n\n    private File testDir;\n\n    @AfterEach\n    void after()\n    {\n        AgentTests.stopLogging();\n\n        if (testDir != null && testDir.exists())\n        {\n            IoUtil.delete(testDir, false);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void logAll()\n    {\n        testArchiveLogging(\"all\", EnumSet.of(\n            CMD_OUT_RESPONSE, CMD_IN_AUTH_CONNECT, CMD_IN_KEEP_ALIVE, CMD_IN_START_RECORDING,\n            CMD_IN_FIND_LAST_MATCHING_RECORD, CMD_IN_MAX_RECORDED_POSITION, CMD_IN_STOP_RECORDING));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void logControlSessionAdapterOnFragment()\n    {\n        testArchiveLogging(CMD_IN_KEEP_ALIVE.name() + \",\" + CMD_IN_AUTH_CONNECT.id(),\n            EnumSet.of(CMD_IN_AUTH_CONNECT, CMD_IN_KEEP_ALIVE));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void logControlResponseProxySendResponseHook()\n    {\n        testArchiveLogging(CMD_OUT_RESPONSE.name(), EnumSet.of(CMD_OUT_RESPONSE));\n    }\n\n    @SuppressWarnings(\"try\")\n    private void testArchiveLogging(final String enabledEvents, final EnumSet<ArchiveEventCode> expectedEvents)\n    {\n        before(enabledEvents, expectedEvents);\n\n        final MediaDriver.Context mediaDriverCtx = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        final AeronArchive.Context aeronArchiveContext = new AeronArchive.Context()\n            .controlRequestChannel(\"aeron:udp?term-length=64k|endpoint=localhost:8010\")\n            .controlRequestStreamId(100)\n            .controlResponseChannel(\"aeron:udp?term-length=64k|endpoint=localhost:0\")\n            .controlResponseStreamId(101);\n\n        final Archive.Context archiveCtx = new Archive.Context()\n            .errorHandler(Tests::onError)\n            .archiveDir(new File(testDir, \"archive\"))\n            .deleteArchiveOnStart(true)\n            .recordingEventsEnabled(false)\n            .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n            .controlChannel(aeronArchiveContext.controlRequestChannel())\n            .controlStreamId(aeronArchiveContext.controlRequestStreamId())\n            .localControlStreamId(aeronArchiveContext.controlRequestStreamId())\n            .recordingEventsChannel(aeronArchiveContext.recordingEventsChannel())\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n        try (ArchivingMediaDriver ignore1 = ArchivingMediaDriver.launch(mediaDriverCtx, archiveCtx);\n            AeronArchive aeronArchive = AeronArchive.connect(aeronArchiveContext))\n        {\n            final String channel = \"aeron:ipc?ssc=true\";\n            final int streamId = 1000;\n            final ExclusivePublication publication = aeronArchive.addRecordedExclusivePublication(channel, streamId);\n\n            final UnsafeBuffer msg = new UnsafeBuffer(new byte[256]);\n            ThreadLocalRandom.current().nextBytes(msg.byteArray());\n            while (publication.offer(msg) < 0)\n            {\n                Tests.yield();\n            }\n\n            final CountersReader counters = aeronArchive.context().aeron().countersReader();\n            final int recordingCounterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, recordingCounterId);\n            assertNotEquals(RecordingPos.NULL_RECORDING_ID, recordingId);\n\n            assertEquals(\n                recordingId,\n                aeronArchive.findLastMatchingRecording(0, \"aeron:ipc\", streamId, publication.sessionId()));\n\n            Tests.await(() -> publication.position() == aeronArchive.getMaxRecordedPosition(recordingId));\n\n            aeronArchive.stopRecording(publication);\n\n            Tests.await(WAIT_LIST::isEmpty);\n        }\n    }\n\n    private void before(final String enabledEvents, final EnumSet<ArchiveEventCode> expectedEvents)\n    {\n        final Map<String, String> configOptions = new HashMap<>();\n        configOptions.put(ConfigOption.READER_CLASSNAME, StubEventLogReaderAgent.class.getName());\n        configOptions.put(ConfigOption.ENABLED_ARCHIVE_EVENT_CODES, enabledEvents);\n        AgentTests.startLogging(configOptions);\n\n        WAIT_LIST.clear();\n        WAIT_LIST.addAll(expectedEvents);\n\n        testDir = Paths.get(IoUtil.tmpDirName(), \"archive-test\").toFile();\n        if (testDir.exists())\n        {\n            IoUtil.delete(testDir, false);\n        }\n    }\n\n    static final class StubEventLogReaderAgent implements Agent, MessageHandler\n    {\n        public String roleName()\n        {\n            return \"event-log-reader\";\n        }\n\n        public int doWork()\n        {\n            return EVENT_RING_BUFFER.read(this, EVENT_READER_FRAME_LIMIT);\n        }\n\n        public void onMessage(final int msgTypeId, final MutableDirectBuffer buffer, final int index, final int length)\n        {\n            WAIT_LIST.remove(ArchiveEventCode.fromEventCodeId(msgTypeId));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ClusterEventCodeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static io.aeron.agent.ClusterEventCode.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass ClusterEventCodeTest\n{\n    @ParameterizedTest\n    @EnumSource(ClusterEventCode.class)\n    void getCodeById(final ClusterEventCode code)\n    {\n        assertSame(code, get(code.id()));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, -1, 77, Integer.MIN_VALUE, Integer.MAX_VALUE })\n    void getCodeByIdShouldThrowIllegalArgumentExceptionForUnknownId(final int id)\n    {\n        assertThrows(IllegalArgumentException.class, () -> get(id));\n    }\n\n    @ParameterizedTest\n    @EnumSource(ClusterEventCode.class)\n    void toEventCodeIdComputesEventId(final ClusterEventCode eventCode)\n    {\n        assertEquals(EVENT_CODE_TYPE << 16 | (eventCode.id() & 0xFFFF), eventCode.toEventCodeId());\n    }\n\n    @ParameterizedTest\n    @EnumSource(ClusterEventCode.class)\n    void fromEventCodeIdLooksUpEventCode(final ClusterEventCode eventCode)\n    {\n        assertSame(eventCode, fromEventCodeId(eventCode.toEventCodeId()));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, -1, 13, Integer.MIN_VALUE, Integer.MAX_VALUE })\n    void fromEventCodeIdShouldThrowIllegalArgumentExceptionForUnknownCodeId(final int eventCodeId)\n    {\n        assertThrows(IllegalArgumentException.class, () -> fromEventCodeId(eventCodeId));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ClusterEventDissectorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport static io.aeron.agent.ClusterEventCode.*;\nimport static io.aeron.agent.ClusterEventDissector.CONTEXT;\nimport static io.aeron.agent.CommonEventEncoder.LOG_HEADER_LENGTH;\nimport static io.aeron.agent.CommonEventEncoder.internalEncodeLogHeader;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass ClusterEventDissectorTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH]);\n    private final StringBuilder builder = new StringBuilder();\n\n    @Test\n    void dissectNewLeadershipTerm()\n    {\n        internalEncodeLogHeader(buffer, 0, 8, 9, () -> 33_000_000_000L);\n        buffer.putLong(LOG_HEADER_LENGTH, 1, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + SIZE_OF_LONG, 2, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 2), 3, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 3), 13, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 4), 23, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 5), 4, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 6), 5, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 7), 9999, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 8), 6, LITTLE_ENDIAN);\n        buffer.putLong(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 9), 7, LITTLE_ENDIAN);\n        buffer.putInt(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 10), 13, LITTLE_ENDIAN);\n        buffer.putInt(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 10) + SIZE_OF_INT, 100, LITTLE_ENDIAN);\n        buffer.putInt(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 10) + SIZE_OF_INT * 2, 200, LITTLE_ENDIAN);\n        buffer.putInt(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 10) + SIZE_OF_INT * 3, 300, LITTLE_ENDIAN);\n        buffer.putByte(LOG_HEADER_LENGTH + (SIZE_OF_LONG * 10) + SIZE_OF_INT * 4, (byte)1);\n\n        ClusterEventDissector.dissectNewLeadershipTerm(buffer, 0, builder);\n\n        assertEquals(\"[33.000000000] \" + CONTEXT + \": \" + NEW_LEADERSHIP_TERM.name() + \" [8/9]: memberId=13 \" +\n            \"logLeadershipTermId=1 nextLeadershipTermId=2 nextTermBaseLogPosition=3 nextLogPosition=13 \" +\n            \"leadershipTermId=23 termBaseLogPosition=4 logPosition=5 commitPosition=9999 leaderRecordingId=6 \" +\n            \"timestamp=7 leaderId=100 logSessionId=200 appVersion=0.1.44 isStartup=true\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = ClusterEventCode.class, names = { \"CLUSTER_BACKUP_STATE_CHANGE\", \"ROLE_CHANGE\" })\n    void dissectStateChange(final ClusterEventCode code)\n    {\n        internalEncodeLogHeader(buffer, 0, 100, 200, () -> -1_000_000_000);\n        buffer.putInt(LOG_HEADER_LENGTH, 42, LITTLE_ENDIAN);\n        final int stateLength = buffer.putStringAscii(LOG_HEADER_LENGTH + SIZE_OF_INT, \"a -> b\");\n        final String reason = CLUSTER_BACKUP_STATE_CHANGE == code ? \"\" : \"the reason!\";\n        buffer.putStringAscii(LOG_HEADER_LENGTH + SIZE_OF_INT + stateLength, reason);\n\n        ClusterEventDissector.dissectStateChange(code, buffer, 0, builder);\n\n        assertEquals(\"[-1.000000000] \" + CONTEXT + \": \" + code.name() +\n            \" [100/200]: memberId=42 a -> b reason=\\\"\" + reason + \"\\\"\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectElectionStateChange()\n    {\n        final int offset = 10;\n        int writeIndex = offset;\n        writeIndex += internalEncodeLogHeader(buffer, offset, 100, 200, () -> 5_000_000_000L);\n        buffer.putLong(writeIndex, 101010, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 6, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 1024, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 2, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 1218, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 800, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putInt(writeIndex, 86, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_INT;\n        buffer.putInt(writeIndex, 3, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_INT;\n        writeIndex += buffer.putStringAscii(writeIndex, \"old -> new\");\n        buffer.putStringAscii(writeIndex, \"this is a test\");\n\n        ClusterEventDissector.dissectElectionStateChange(buffer, offset, builder);\n\n        assertEquals(\"[5.000000000] \" + CONTEXT + \": \" + ELECTION_STATE_CHANGE.name() + \" [100/200]: memberId=86\" +\n            \" old -> new leaderId=3 candidateTermId=101010 leadershipTermId=6 logPosition=1024 logLeadershipTermId=2\" +\n            \" appendPosition=1218 catchupPosition=800 reason=\\\"this is a test\\\"\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectTruncateLogEntry()\n    {\n        final int offset = 10;\n        int writeIndex = offset;\n        writeIndex += internalEncodeLogHeader(buffer, offset, 100, 200, () -> 5_000_000_000L);\n        buffer.putLong(writeIndex, 555, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 166, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 42, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 1024, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 998, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 1024, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 1200, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putLong(writeIndex, 800, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_LONG;\n        buffer.putInt(writeIndex, 123, LITTLE_ENDIAN);\n        writeIndex += SIZE_OF_INT;\n        buffer.putStringAscii(writeIndex, \"election state\", LITTLE_ENDIAN);\n\n        ClusterEventDissector.dissectTruncateLogEntry(TRUNCATE_LOG_ENTRY, buffer, offset, builder);\n\n        assertEquals(\"[5.000000000] \" + CONTEXT + \": \" + TRUNCATE_LOG_ENTRY.name() + \" [100/200]: memberId=123 \" +\n            \"state=election state logLeadershipTermId=555 leadershipTermId=166 candidateTermId=42 \" +\n            \"commitPosition=1024 logPosition=998 appendPosition=1024 oldPosition=1200 newPosition=800\",\n            builder.toString());\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ClusterEventEncoderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.cluster.ConsensusModule;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.time.temporal.ChronoUnit;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.agent.ClusterEventEncoder.electionStateChangeLength;\nimport static io.aeron.agent.ClusterEventEncoder.encodeElectionStateChange;\nimport static io.aeron.agent.ClusterEventEncoder.encodeOnNewLeadershipTerm;\nimport static io.aeron.agent.ClusterEventEncoder.encodeStateChange;\nimport static io.aeron.agent.ClusterEventEncoder.encodeTruncateLogEntry;\nimport static io.aeron.agent.ClusterEventEncoder.newLeaderShipTermLength;\nimport static io.aeron.agent.ClusterEventEncoder.stateChangeLength;\nimport static io.aeron.agent.CommonEventEncoder.LOG_HEADER_LENGTH;\nimport static io.aeron.agent.CommonEventEncoder.STATE_SEPARATOR;\nimport static io.aeron.agent.CommonEventEncoder.captureLength;\nimport static io.aeron.agent.CommonEventEncoder.encodedLength;\nimport static io.aeron.agent.CommonEventEncoder.enumName;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_BYTE;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\nclass ClusterEventEncoderTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH]);\n\n    @Test\n    void testEncodeStateChange()\n    {\n        final int offset = 24;\n        final ConsensusModule.State from = ConsensusModule.State.ACTIVE;\n        final ConsensusModule.State to = ConsensusModule.State.CLOSED;\n        final int memberId = 42;\n        final String payload = from.name() + STATE_SEPARATOR + to.name();\n        final String reason = \"test it here\";\n        final int length = stateChangeLength(from, to, reason);\n        final int captureLength = captureLength(length);\n\n        final int encodedLength = encodeStateChange(buffer, offset, captureLength, length, memberId, from, to, reason);\n        assertEquals(encodedLength(captureLength), encodedLength);\n\n        int index = offset;\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, buffer.getInt(offset + LOG_HEADER_LENGTH));\n        index += SIZE_OF_INT;\n        assertEquals(payload, buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT));\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    void testEncodeNewLeadershipTerm()\n    {\n        final int offset = 200;\n        final int captureLength = 18;\n        final int length = 54;\n        final long logLeadershipTermId = 111;\n        final long nextLeadershipTermId = 2561;\n        final long nextTermBaseLogPosition = 2562;\n        final long nextLogPosition = 2563;\n        final long leadershipTermId = 222;\n        final long logPosition = 1024;\n        final long commitPosition = 512;\n        final long timestamp = 32423436;\n        final int memberId = 5;\n        final int leaderId = 42;\n        final int logSessionId = 18;\n        final int appVersion = 777;\n        final long termBaseLogPosition = 23874;\n        final long leaderRecordingId = 9;\n        final boolean isStartup = true;\n\n        final int encodedLength = encodeOnNewLeadershipTerm(\n            buffer,\n            offset,\n            captureLength,\n            length,\n            memberId,\n            logLeadershipTermId,\n            nextLeadershipTermId,\n            nextTermBaseLogPosition,\n            nextLogPosition,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            commitPosition,\n            leaderRecordingId,\n            timestamp,\n            leaderId,\n            logSessionId,\n            appVersion,\n            isStartup);\n\n        assertEquals(encodedLength(newLeaderShipTermLength()), encodedLength);\n\n        int index = offset;\n        assertEquals(captureLength, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(length, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertNotEquals(0, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logLeadershipTermId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(nextLeadershipTermId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(nextTermBaseLogPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(nextLogPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leadershipTermId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(termBaseLogPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(commitPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leaderRecordingId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(timestamp, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(leaderId, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(logSessionId, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(appVersion, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n\n        assertEquals(isStartup, 1 == buffer.getInt(index, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void testNewLeaderShipTermLength()\n    {\n        assertEquals(SIZE_OF_LONG * 10 + SIZE_OF_INT * 4 + SIZE_OF_BYTE, newLeaderShipTermLength());\n    }\n\n    @Test\n    void testStateChangeLength()\n    {\n        final ChronoUnit from = ChronoUnit.CENTURIES;\n        final ChronoUnit to = ChronoUnit.HALF_DAYS;\n        final String payload = from.name() + STATE_SEPARATOR + to.name();\n        final String data = \"data ...\";\n\n        assertEquals(\n            payload.length() + (SIZE_OF_INT * 2) + SIZE_OF_INT + data.length(),\n            stateChangeLength(from, to, data));\n    }\n\n    @Test\n    void testElectionStateChangeLength()\n    {\n        final TimeUnit from = TimeUnit.DAYS;\n        final TimeUnit to = TimeUnit.MICROSECONDS;\n        final String payload = from.name() + STATE_SEPARATOR + to.name();\n        final String reason = \"this state transition occured in a test\";\n\n        assertEquals((2 * SIZE_OF_INT) + (6 * SIZE_OF_LONG) + SIZE_OF_INT + payload.length() +\n            SIZE_OF_INT + reason.length(),\n            electionStateChangeLength(from, to, reason));\n    }\n\n    @Test\n    void testEncodeElectionStateChange()\n    {\n        final int offset = 8;\n        final ChronoUnit to = ChronoUnit.MILLENNIA;\n        final int memberId = 278;\n        final int leaderId = -100;\n        final long candidateTermId = 777L;\n        final long leadershipTermId = 42L;\n        final long logPosition = 128L;\n        final long logLeadershipTermId = 1L;\n        final long appendPosition = 998L;\n        final long catchupPosition = 200L;\n        final String reason = \"test\";\n        final int captureLength = captureLength(electionStateChangeLength(null, to, reason));\n        final int length = encodedLength(captureLength);\n\n        final int encodedLength = encodeElectionStateChange(\n            buffer,\n            offset,\n            captureLength,\n            length,\n            memberId,\n            null,\n            to,\n            leaderId,\n            candidateTermId,\n            leadershipTermId,\n            logPosition,\n            logLeadershipTermId,\n            appendPosition,\n            catchupPosition,\n            reason);\n\n        assertEquals(length, encodedLength);\n\n        int index = offset;\n        assertEquals(captureLength, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(length, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertNotEquals(0, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(candidateTermId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leadershipTermId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logLeadershipTermId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(appendPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(catchupPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(leaderId, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(\"null\" + STATE_SEPARATOR + to.name(), buffer.getStringAscii(index));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"logEntryStates\")\n    void testEncodeTruncateLogEntry(final TimeUnit state)\n    {\n        final int offset = 8;\n        final int length = 300;\n        final int captureLength = 128;\n        final int memberId = -18;\n        final long logLeadershipTermId = 42;\n        final long leadershipTermId = -408324982349823L;\n        final long candidateTermId = 233333L;\n        final long commitPosition = 192L;\n        final long logPosition = 100L;\n        final long appendPosition = 120L;\n        final long oldPosition = 200L;\n        final long newPosition = 88L;\n\n        final int encodedLength = encodeTruncateLogEntry(\n            buffer,\n            offset,\n            length,\n            captureLength,\n            memberId, state,\n            logLeadershipTermId,\n            leadershipTermId,\n            candidateTermId,\n            commitPosition,\n            logPosition,\n            appendPosition,\n            oldPosition,\n            newPosition);\n\n        assertEquals(\n            encodedLength(SIZE_OF_INT + 8 * SIZE_OF_LONG + SIZE_OF_INT + enumName(state).length()),\n            encodedLength);\n        int index = offset;\n        assertEquals(captureLength, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(length, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertNotEquals(0, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logLeadershipTermId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leadershipTermId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(candidateTermId, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(commitPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(appendPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(oldPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(newPosition, buffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, buffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(enumName(state), buffer.getStringAscii(index, LITTLE_ENDIAN));\n    }\n\n    private static List<TimeUnit> logEntryStates()\n    {\n        return Arrays.asList(\n            TimeUnit.MICROSECONDS,\n            null);\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ClusterEventLoggerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.test.Tests;\nimport org.agrona.SemanticVersion;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Arrays;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.agent.AgentTests.verifyLogHeader;\nimport static io.aeron.agent.ClusterEventCode.APPEND_POSITION;\nimport static io.aeron.agent.ClusterEventCode.APPEND_SESSION_CLOSE;\nimport static io.aeron.agent.ClusterEventCode.APPEND_SESSION_OPEN;\nimport static io.aeron.agent.ClusterEventCode.CANVASS_POSITION;\nimport static io.aeron.agent.ClusterEventCode.CATCHUP_POSITION;\nimport static io.aeron.agent.ClusterEventCode.CLUSTER_SESSION_STATE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.COMMIT_POSITION;\nimport static io.aeron.agent.ClusterEventCode.ELECTION_STATE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.NEW_ELECTION;\nimport static io.aeron.agent.ClusterEventCode.NEW_LEADERSHIP_TERM;\nimport static io.aeron.agent.ClusterEventCode.REPLAY_NEW_LEADERSHIP_TERM;\nimport static io.aeron.agent.ClusterEventCode.REPLICATION_ENDED;\nimport static io.aeron.agent.ClusterEventCode.REQUEST_VOTE;\nimport static io.aeron.agent.ClusterEventCode.SERVICE_ACK;\nimport static io.aeron.agent.ClusterEventCode.STANDBY_SNAPSHOT_NOTIFICATION;\nimport static io.aeron.agent.ClusterEventCode.STATE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.STOP_CATCHUP;\nimport static io.aeron.agent.ClusterEventCode.TERMINATION_ACK;\nimport static io.aeron.agent.ClusterEventCode.TERMINATION_POSITION;\nimport static io.aeron.agent.ClusterEventCode.TRUNCATE_LOG_ENTRY;\nimport static io.aeron.agent.ClusterEventCode.VOTE;\nimport static io.aeron.agent.ClusterEventEncoder.MAX_REASON_LENGTH;\nimport static io.aeron.agent.ClusterEventEncoder.canvassPositionLength;\nimport static io.aeron.agent.ClusterEventEncoder.clusterSessionStateChangeLength;\nimport static io.aeron.agent.ClusterEventEncoder.electionStateChangeLength;\nimport static io.aeron.agent.ClusterEventEncoder.newElectionLength;\nimport static io.aeron.agent.ClusterEventEncoder.newLeaderShipTermLength;\nimport static io.aeron.agent.ClusterEventEncoder.replayNewLeadershipTermEventLength;\nimport static io.aeron.agent.ClusterEventEncoder.replicationEndedLength;\nimport static io.aeron.agent.ClusterEventEncoder.serviceAckLength;\nimport static io.aeron.agent.ClusterEventEncoder.standbySnapshotNotificationLength;\nimport static io.aeron.agent.ClusterEventEncoder.terminationAckLength;\nimport static io.aeron.agent.ClusterEventEncoder.terminationPositionLength;\nimport static io.aeron.agent.CommonEventEncoder.LOG_HEADER_LENGTH;\nimport static io.aeron.agent.CommonEventEncoder.STATE_SEPARATOR;\nimport static io.aeron.agent.CommonEventEncoder.enumName;\nimport static io.aeron.agent.EventConfiguration.BUFFER_LENGTH_DEFAULT;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static java.util.concurrent.TimeUnit.MINUTES;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.BitUtil.SIZE_OF_BYTE;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.BitUtil.align;\nimport static org.agrona.concurrent.ringbuffer.RecordDescriptor.ALIGNMENT;\nimport static org.agrona.concurrent.ringbuffer.RecordDescriptor.encodedMsgOffset;\nimport static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TAIL_POSITION_OFFSET;\nimport static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TRAILER_LENGTH;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass ClusterEventLoggerTest\n{\n    private static final int CAPACITY = align(BUFFER_LENGTH_DEFAULT, CACHE_LINE_LENGTH);\n    private final UnsafeBuffer logBuffer = new UnsafeBuffer(\n        ByteBuffer.allocateDirect(BUFFER_LENGTH_DEFAULT + TRAILER_LENGTH));\n    private final ClusterEventLogger logger = new ClusterEventLogger(new ManyToOneRingBuffer(logBuffer));\n\n    @Test\n    void logOnNewLeadershipTerm()\n    {\n        final int offset = align(22, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final long logLeadershipTermId = 434;\n        final long nextLeadershipTermId = 2561;\n        final long nextTermBaseLogPosition = 2562;\n        final long nextLogPosition = 2563;\n        final long leadershipTermId = -500;\n        final long logPosition = 432;\n        final long commitPosition = 290;\n        final long timestamp = 2;\n        final int memberId = 19;\n        final int leaderId = -1;\n        final int logSessionId = 3;\n        final int appVersion = SemanticVersion.compose(0, 3, 9);\n        final int captureLength = newLeaderShipTermLength();\n        final boolean isStartup = true;\n        final long termBaseLogPosition = 982734;\n        final long leaderRecordingId = 76434;\n\n        logger.logOnNewLeadershipTerm(\n            memberId,\n            logLeadershipTermId,\n            nextLeadershipTermId,\n            nextTermBaseLogPosition,\n            nextLogPosition,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            commitPosition,\n            leaderRecordingId,\n            timestamp,\n            leaderId,\n            logSessionId,\n            appVersion,\n            isStartup);\n\n        verifyLogHeader(logBuffer, offset, NEW_LEADERSHIP_TERM.toEventCodeId(), captureLength, captureLength);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(logLeadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(nextLeadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(nextTermBaseLogPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(nextLogPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(termBaseLogPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(commitPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leaderRecordingId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(timestamp, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(leaderId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(logSessionId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(appVersion, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n\n        assertEquals(isStartup, 1 == logBuffer.getByte(index));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectNewLeadershipTerm(logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: NEW_LEADERSHIP_TERM \" +\n            \"\\\\[97/97]: memberId=19 logLeadershipTermId=434 nextLeadershipTermId=2561 \" +\n            \"nextTermBaseLogPosition=2562 nextLogPosition=2563 leadershipTermId=-500 termBaseLogPosition=982734 \" +\n            \"logPosition=432 commitPosition=290 leaderRecordingId=76434 timestamp=2 leaderId=-1 logSessionId=3 \" +\n            \"appVersion=0.3.9 isStartup=true\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logStateChange()\n    {\n        final int offset = ALIGNMENT * 11;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final TimeUnit from = MINUTES;\n        final TimeUnit to = SECONDS;\n        final int memberId = 42;\n        final String payload = from.name() + STATE_SEPARATOR + to.name();\n        final String reason = \"this is the way\";\n        final int captureLength = ClusterEventEncoder.stateChangeLength(from, to, reason);\n\n        logger.logStateChange(STATE_CHANGE, memberId, from, to, reason);\n\n        verifyLogHeader(logBuffer, offset, STATE_CHANGE.toEventCodeId(), captureLength, captureLength);\n        final int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        assertEquals(payload, logBuffer.getStringAscii(index + SIZE_OF_INT));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectStateChange(\n            ClusterEventCode.STATE_CHANGE, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: STATE_CHANGE \" +\n            \"\\\\[45/45]: memberId=42 MINUTES -> SECONDS reason=\\\"this is the way\\\"\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logElectionStateChange()\n    {\n        final int offset = ALIGNMENT * 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final ChronoUnit from = ChronoUnit.ERAS;\n        final ChronoUnit to = null;\n        final int memberId = 18;\n        final int leaderId = -1;\n        final long candidateTermId = 29L;\n        final long leadershipTermId = 0L;\n        final long logPosition = 100L;\n        final long logLeadershipTermId = -9L;\n        final long appendPosition = 16 * 1024L;\n        final long catchupPosition = 8192L;\n        final String reason = Tests.generateStringWithSuffix(\"reason-\", \"x\", MAX_REASON_LENGTH * 5);\n        final int length = electionStateChangeLength(from, to, reason);\n\n        logger.logElectionStateChange(\n            memberId,\n            from,\n            to,\n            leaderId,\n            candidateTermId,\n            leadershipTermId,\n            logPosition,\n            logLeadershipTermId,\n            appendPosition,\n            catchupPosition,\n            reason);\n\n        verifyLogHeader(logBuffer, offset, ELECTION_STATE_CHANGE.toEventCodeId(), length, length);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(candidateTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logLeadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(appendPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(catchupPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(leaderId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        final String encodedStateTransition = from.name() + STATE_SEPARATOR + \"null\";\n        assertEquals(encodedStateTransition, logBuffer.getStringAscii(index));\n        index += SIZE_OF_INT + encodedStateTransition.length();\n        final String trailingReason = reason.substring(0, MAX_REASON_LENGTH - 3) + \"...\";\n        assertEquals(trailingReason, logBuffer.getStringAscii(index));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectElectionStateChange(logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: ELECTION_STATE_CHANGE \" +\n            \"\\\\[376/376]: memberId=18 ERAS -> null leaderId=-1 candidateTermId=29 leadershipTermId=0 \" +\n            \"logPosition=100 logLeadershipTermId=-9 appendPosition=16384 catchupPosition=8192 reason=\\\"\" +\n            trailingReason + \"\\\"\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logCatchupPosition()\n    {\n        final int offset = ALIGNMENT * 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final long leadershipTermId = 1233L;\n        final long logPosition = 100L;\n        final int followerMemberId = 18;\n        final int memberId = -901;\n        final String catchupEndpoint = \"aeron:udp?endpoint=localhost:9090\";\n\n        logger.logOnCatchupPosition(memberId, leadershipTermId, logPosition, followerMemberId, catchupEndpoint);\n\n        final int length = 2 * SIZE_OF_LONG + 2 * SIZE_OF_INT + SIZE_OF_INT + catchupEndpoint.length();\n        verifyLogHeader(logBuffer, offset, CATCHUP_POSITION.toEventCodeId(), length, length);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(followerMemberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        final int catchupEndpointLength = logBuffer.getInt(index, LITTLE_ENDIAN);\n        index += SIZE_OF_INT;\n        assertEquals(catchupEndpoint, logBuffer.getStringWithoutLengthAscii(index, catchupEndpointLength));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectCatchupPosition(CATCHUP_POSITION, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: CATCHUP_POSITION \\\\[61/61]: \" +\n            \"memberId=-901 leadershipTermId=1233 logPosition=100 followerMemberId=18 \" +\n            \"catchupEndpoint=aeron:udp\\\\?endpoint=localhost:9090\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logCatchupPositionLongCatchupEndpoint()\n    {\n        final int offset = ALIGNMENT * 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final long leadershipTermId = 1233L;\n        final long logPosition = 100L;\n        final int followerMemberId = -7;\n        final int memberId = 44;\n\n        final byte[] alias = new byte[8192];\n        Arrays.fill(alias, (byte)'x');\n\n        final String catchupEndpoint = \"aeron:udp?endpoint=localhost:9090|alias=\" + new String(\n            alias,\n            StandardCharsets.US_ASCII);\n\n        logger.logOnCatchupPosition(memberId, leadershipTermId, logPosition, followerMemberId, catchupEndpoint);\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectCatchupPosition(CATCHUP_POSITION, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]*\\\\.[0-9]*] CLUSTER: CATCHUP_POSITION \\\\[[0-9]*/8260]: \" +\n            \"memberId=44 leadershipTermId=1233 logPosition=100 followerMemberId=-7 \" +\n            \"catchupEndpoint=aeron:udp\\\\?endpoint=localhost:9090\\\\|alias=(x)*\\\\.\\\\.\\\\.\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logStopCatchup()\n    {\n        final int offset = ALIGNMENT * 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final long leadershipTermId = 1233L;\n        final int followerMemberId = 7;\n        final int memberId = 2;\n\n        logger.logOnStopCatchup(memberId, leadershipTermId, followerMemberId);\n\n        final int length = SIZE_OF_LONG + 2 * SIZE_OF_INT;\n        verifyLogHeader(logBuffer, offset, STOP_CATCHUP.toEventCodeId(), length, length);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(followerMemberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectStopCatchup(STOP_CATCHUP, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: STOP_CATCHUP \\\\[16/16]: \" +\n            \"memberId=2 leadershipTermId=1233 followerMemberId=7\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logTruncateLogEntry()\n    {\n        final int offset = align(22, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final ChronoUnit state = ChronoUnit.FOREVER;\n        final int memberId = 8;\n        final long logLeadershipTermId = 777L;\n        final long leadershipTermId = 1233L;\n        final long candidateTermId = 42L;\n        final int commitPosition = 1000;\n        final long logPosition = 33L;\n        final long appendPosition = 555L;\n        final long oldPosition = 98L;\n        final long newPosition = 24L;\n\n        logger.logOnTruncateLogEntry(\n            memberId,\n            state,\n            logLeadershipTermId,\n            leadershipTermId,\n            candidateTermId,\n            commitPosition,\n            logPosition,\n            appendPosition,\n            oldPosition,\n            newPosition);\n\n        final int length = SIZE_OF_INT + state.name().length() + SIZE_OF_INT + 8 * SIZE_OF_LONG;\n        verifyLogHeader(logBuffer, offset, TRUNCATE_LOG_ENTRY.toEventCodeId(), length, length);\n\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(logLeadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(candidateTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(commitPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(appendPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(oldPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(newPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(enumName(state), logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectTruncateLogEntry(TRUNCATE_LOG_ENTRY, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: TRUNCATE_LOG_ENTRY \\\\[79/79]: \" +\n            \"memberId=8 state=FOREVER logLeadershipTermId=777 leadershipTermId=1233 candidateTermId=42 \" +\n            \"commitPosition=1000 logPosition=33 appendPosition=555 oldPosition=98 newPosition=24\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logReplayNewLeadershipTerm()\n    {\n        final int offset = ALIGNMENT * 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final int memberId = 982374;\n        final boolean isInElection = true;\n        final long leadershipTermId = 1233L;\n        final long logPosition = 988723465L;\n        final long timestamp = 890723452345L;\n        final long termBaseLogPosition = logPosition - 32;\n        final TimeUnit timeUnit = NANOSECONDS;\n        final int appVersion = 13;\n\n        logger.logOnReplayNewLeadershipTermEvent(\n            memberId,\n            isInElection,\n            leadershipTermId,\n            logPosition,\n            timestamp,\n            termBaseLogPosition,\n            TimeUnit.NANOSECONDS,\n            appVersion);\n\n        final int length = replayNewLeadershipTermEventLength(timeUnit);\n        verifyLogHeader(logBuffer, offset, REPLAY_NEW_LEADERSHIP_TERM.toEventCodeId(), length, length);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(timestamp, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(termBaseLogPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(appVersion, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(isInElection, 0 != logBuffer.getByte(index));\n        index += SIZE_OF_BYTE;\n        assertEquals(timeUnit.name(), logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectReplayNewLeadershipTerm(\n            REPLAY_NEW_LEADERSHIP_TERM, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: REPLAY_NEW_LEADERSHIP_TERM \" +\n            \"\\\\[56/56]: memberId=982374 isInElection=true leadershipTermId=1233 logPosition=988723465 \" +\n            \"termBaseLogPosition=988723433 appVersion=13 timestamp=890723452345 timeUnit=NANOSECONDS\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logOnAppendPosition()\n    {\n        final int offset = ALIGNMENT * 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final long leadershipTermId = 1233L;\n        final long logPosition = 988723465L;\n        final int followerMemberId = 982374;\n        final int memberId = 61;\n        final byte flags = 1;\n\n        logger.logOnAppendPosition(memberId, leadershipTermId, logPosition, followerMemberId, flags);\n\n        final int length = 2 * SIZE_OF_LONG + 2 * SIZE_OF_INT + SIZE_OF_BYTE;\n        verifyLogHeader(logBuffer, offset, APPEND_POSITION.toEventCodeId(), length, length);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(followerMemberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(flags, logBuffer.getByte(index));\n        index += SIZE_OF_BYTE;\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectAppendPosition(APPEND_POSITION, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: APPEND_POSITION \" +\n            \"\\\\[25/25]: memberId=61 leadershipTermId=1233 logPosition=988723465 followerMemberId=982374 \" +\n            \"flags=0b00000001\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logOnCommitPosition()\n    {\n        final int offset = ALIGNMENT * 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        final long leadershipTermId = 1233L;\n        final long logPosition = 988723465L;\n        final int leaderId = 982374;\n        final int memberId = 2;\n\n        logger.logOnCommitPosition(memberId, leadershipTermId, logPosition, leaderId);\n\n        final int length = 2 * SIZE_OF_LONG + 2 * SIZE_OF_INT;\n        verifyLogHeader(logBuffer, offset, COMMIT_POSITION.toEventCodeId(), length, length);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leaderId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectCommitPosition(COMMIT_POSITION, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: COMMIT_POSITION \" +\n            \"\\\\[24/24]: memberId=2 leadershipTermId=1233 logPosition=988723465 leaderId=982374\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logAppendSessionClose()\n    {\n        final int offset = ALIGNMENT + 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        final int memberId = 829374;\n        final long sessionId = 289374L;\n        final CloseReason closeReason = CloseReason.TIMEOUT;\n        final long leadershipTermId = 2039842L;\n        final long timestamp = 29384;\n        final TimeUnit timeUnit = MILLISECONDS;\n\n        logger.logAppendSessionClose(memberId, sessionId, closeReason, leadershipTermId, timestamp, timeUnit);\n\n        final int length = 3 * SIZE_OF_LONG + SIZE_OF_INT + (SIZE_OF_INT + closeReason.name().length()) +\n            (SIZE_OF_INT + timeUnit.name().length());\n\n        verifyLogHeader(logBuffer, offset, APPEND_SESSION_CLOSE.toEventCodeId(), length, length);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n\n        assertEquals(sessionId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(timestamp, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(closeReason.name(), logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT + closeReason.name().length();\n        assertEquals(timeUnit.name(), logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectAppendSessionClose(\n            APPEND_SESSION_CLOSE, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: APPEND_SESSION_CLOSE \" +\n            \"\\\\[55/55]: memberId=829374 sessionId=289374 closeReason=TIMEOUT leadershipTermId=2039842 \" +\n            \"timestamp=29384 timeUnit=MILLISECONDS\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logAppendSessionOpen()\n    {\n        final int offset = ALIGNMENT + 4;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        final int memberId = 829374;\n        final long sessionId = 289374L;\n        final long leadershipTermId = 2039842L;\n        final long logPosition = 4L * 1024 * 1024 * 1024;\n        final long timestamp = 29384;\n        final TimeUnit timeUnit = MILLISECONDS;\n\n        logger.logAppendSessionOpen(memberId, sessionId, leadershipTermId, logPosition, timestamp, timeUnit);\n\n        final int length = 4 * SIZE_OF_LONG + SIZE_OF_INT + (SIZE_OF_INT + timeUnit.name().length());\n\n        verifyLogHeader(logBuffer, offset, APPEND_SESSION_OPEN.toEventCodeId(), length, length);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n\n        assertEquals(sessionId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(timestamp, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(timeUnit.name(), logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectAppendSessionOpen(\n            APPEND_SESSION_OPEN, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\"\"\n            \\\\[[0-9]+\\\\.[0-9]+] CLUSTER: APPEND_SESSION_OPEN \\\n            \\\\[52/52]: memberId=829374 sessionId=289374 leadershipTermId=2039842 logPosition=4294967296 \\\n            timestamp=29384 timeUnit=MILLISECONDS\"\"\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logOnRequestVote()\n    {\n        final long logLeadershipTermId = 12;\n        final long logPosition = 4723489263846823L;\n        final long candidateTermId = -19;\n        final int candidateId = 89;\n        final int protocolVersion = SemanticVersion.compose(2, 5, 17);\n        final int memberId = 3;\n        final int offset = 8;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        logger.logOnRequestVote(\n            memberId, logLeadershipTermId, logPosition, candidateTermId, candidateId, protocolVersion);\n\n        verifyLogHeader(logBuffer, offset, REQUEST_VOTE.toEventCodeId(), 36, 36);\n        final int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(logLeadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        assertEquals(logPosition, logBuffer.getLong(index + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(candidateTermId, logBuffer.getLong(index + 2 * SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(candidateId, logBuffer.getInt(index + 3 * SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(protocolVersion, logBuffer.getInt(index + 3 * SIZE_OF_LONG + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(memberId, logBuffer.getInt(index + 3 * SIZE_OF_LONG + 2 * SIZE_OF_INT, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectRequestVote(\n            REQUEST_VOTE, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: REQUEST_VOTE \" +\n            \"\\\\[36/36]: memberId=3 logLeadershipTermId=12 logPosition=4723489263846823 candidateTermId=-19 \" +\n            \"candidateId=89 protocolVersion=2.5.17\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void logOnVote(final boolean vote)\n    {\n        final long logLeadershipTermId = 8;\n        final long logPosition = 1024;\n        final long candidateTermId = 42;\n        final int candidateId = 5;\n        final int voterId = 1;\n        final int memberId = 4;\n        final int offset = 16;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        logger.logOnVote(\n            memberId, logLeadershipTermId, logPosition, candidateTermId, candidateId, voterId, vote);\n\n        final ClusterEventCode eventCode = VOTE;\n        final int captureLength = 37;\n        verifyLogHeader(logBuffer, offset, eventCode.toEventCodeId(), captureLength, captureLength);\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(logLeadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(candidateTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(candidateId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(voterId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(vote ? 1 : 0, logBuffer.getByte(index));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectVote(eventCode, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: VOTE \" +\n            \"\\\\[\" + captureLength + \"/\" + captureLength + \"]:\" +\n            \" memberId=\" + memberId +\n            \" logLeadershipTermId=\" + logLeadershipTermId +\n            \" logPosition=\" + logPosition +\n            \" candidateTermId=\" + candidateTermId +\n            \" candidateId=\" + candidateId +\n            \" voterId=\" + voterId +\n            \" vote=\" + vote;\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logOnCanvassPosition()\n    {\n        final long logLeadershipTermId = 96;\n        final long logPosition = 128L;\n        final long leadershipTermId = 54;\n        final int followerMemberId = 15;\n        final int protocolVersion = SemanticVersion.compose(1, 9, 9);\n        final int memberId = 222;\n        final int offset = 64;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        logger.logOnCanvassPosition(\n            memberId, logLeadershipTermId, logPosition, leadershipTermId, followerMemberId, protocolVersion);\n\n        verifyLogHeader(\n            logBuffer, offset, CANVASS_POSITION.toEventCodeId(), canvassPositionLength(), canvassPositionLength());\n        final int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(logLeadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        assertEquals(logPosition, logBuffer.getLong(index + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(leadershipTermId, logBuffer.getLong(index + 2 * SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(followerMemberId, logBuffer.getInt(index + 3 * SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(protocolVersion, logBuffer.getInt(index + 3 * SIZE_OF_LONG + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(memberId, logBuffer.getInt(index + 3 * SIZE_OF_LONG + 2 * SIZE_OF_INT, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectCanvassPosition(\n            CANVASS_POSITION, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: CANVASS_POSITION \" +\n            \"\\\\[36/36]: memberId=222 logLeadershipTermId=96 logPosition=128 leadershipTermId=54 followerMemberId=15 \" +\n            \"protocolVersion=1.9.9\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logTerminationPosition()\n    {\n        final long logLeadershipTermId = 96;\n        final long logPosition = 128L;\n        final int memberId = 222;\n        final int offset = 64;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        logger.logTerminationPosition(memberId, logLeadershipTermId, logPosition);\n\n        verifyLogHeader(\n            logBuffer,\n            offset,\n            TERMINATION_POSITION.toEventCodeId(),\n            terminationPositionLength(),\n            terminationPositionLength());\n\n        final int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(logLeadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        assertEquals(logPosition, logBuffer.getLong(index + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(memberId, logBuffer.getInt(index + 2 * SIZE_OF_LONG, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectTerminationPosition(\n            TERMINATION_POSITION, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: TERMINATION_POSITION \" +\n            \"\\\\[20/20]: memberId=222 logLeadershipTermId=96 logPosition=128\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logTerminationAck()\n    {\n        final long logLeadershipTermId = 96;\n        final long logPosition = 128L;\n        final int memberId = 222;\n        final int senderMemberId = 982374;\n        final int offset = 64;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        logger.logTerminationAck(memberId, logLeadershipTermId, logPosition, senderMemberId);\n\n        verifyLogHeader(\n            logBuffer,\n            offset,\n            TERMINATION_ACK.toEventCodeId(),\n            terminationAckLength(),\n            terminationAckLength());\n\n        final int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(logLeadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        assertEquals(logPosition, logBuffer.getLong(index + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(memberId, logBuffer.getInt(index + 2 * SIZE_OF_LONG, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectTerminationAck(\n            TERMINATION_ACK, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: TERMINATION_ACK \" +\n            \"\\\\[24/24]: memberId=222 logLeadershipTermId=96 logPosition=128 senderMemberId=982374\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logServiceAck()\n    {\n        final int memberId = 222;\n        final long logPosition = 128L;\n        final long timestamp = 98273423L;\n        final long ackId = 98234L;\n        final long relevantId = 8998L;\n        final int serviceId = 982374;\n        final int offset = 64;\n        final TimeUnit timeUnit = MILLISECONDS;\n\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        logger.logServiceAck(memberId, logPosition, timestamp, timeUnit, ackId, relevantId, serviceId);\n\n        verifyLogHeader(\n            logBuffer,\n            offset,\n            SERVICE_ACK.toEventCodeId(),\n            serviceAckLength(timeUnit),\n            serviceAckLength(timeUnit));\n\n        final int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(logPosition, logBuffer.getLong(index, LITTLE_ENDIAN));\n        assertEquals(timestamp, logBuffer.getLong(index + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(ackId, logBuffer.getLong(index + (2 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(relevantId, logBuffer.getLong(index + (3 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(memberId, logBuffer.getInt(index + (4 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(serviceId, logBuffer.getInt(index + SIZE_OF_INT + (4 * SIZE_OF_LONG), LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectServiceAck(\n            SERVICE_ACK, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern = \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: SERVICE_ACK \" +\n            \"\\\\[56/56]: memberId=222 logPosition=128 timestamp=98273423 timeUnit=MILLISECONDS \" +\n            \"ackId=98234 relevantId=8998 serviceId=982374\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logReplicationEnded()\n    {\n        final int memberId = 222;\n        final String purpose = \"STANDBY_SNAPSHOT\";\n        final String channel = \"aeron:udp?endpoint=localhost:9090\";\n        final long srcRecordingId = 234;\n        final long dstRecordingId = 8435;\n        final long position = 982342;\n        final boolean hasSynced = true;\n        final int offset = 64;\n\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        logger.logReplicationEnded(\n            memberId, purpose, channel, srcRecordingId, dstRecordingId, position, hasSynced);\n\n        verifyLogHeader(\n            logBuffer,\n            offset,\n            REPLICATION_ENDED.toEventCodeId(),\n            replicationEndedLength(purpose, channel),\n            replicationEndedLength(purpose, channel));\n\n        final int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(srcRecordingId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        assertEquals(dstRecordingId, logBuffer.getLong(index + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(position, logBuffer.getLong(index + (2 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(memberId, logBuffer.getInt(index + (3 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(1, logBuffer.getByte(index + (3 * SIZE_OF_LONG) + (SIZE_OF_INT)));\n        final int purposeIndex = index + (3 * SIZE_OF_LONG) + (SIZE_OF_INT) + (SIZE_OF_BYTE);\n        assertEquals(purpose, logBuffer.getStringAscii(purposeIndex));\n        final int channelIndex = purposeIndex + SIZE_OF_INT + purpose.length();\n        assertEquals(channel, logBuffer.getStringAscii(channelIndex, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectReplicationEnded(\n            REPLICATION_ENDED, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern =\n            \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: REPLICATION_ENDED \\\\[86/86]: memberId=222 \" +\n            \"purpose=STANDBY_SNAPSHOT channel=aeron:udp\\\\?endpoint=localhost:9090 srcRecordingId=234 \" +\n            \"dstRecordingId=8435 position=982342 hasSynced=true\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logStandbySnapshotNotification()\n    {\n        final int memberId = 222;\n        final long recordingId = 9823674L;\n        final long leadershipTermId = 23478L;\n        final long termBaseLogPosition = 823423L;\n        final long logPosition = 9827342L;\n        final long timestamp = 98273423434L;\n        final int serviceId = 1;\n        final String archiveEndpoint = \"localhost:9090\";\n        final int offset = 64;\n        final TimeUnit timeUnit = MILLISECONDS;\n\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n        logger.logStandbySnapshotNotification(\n            memberId,\n            recordingId,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            timestamp,\n            timeUnit,\n            serviceId,\n            archiveEndpoint);\n\n        verifyLogHeader(\n            logBuffer,\n            offset,\n            STANDBY_SNAPSHOT_NOTIFICATION.toEventCodeId(),\n            standbySnapshotNotificationLength(timeUnit, archiveEndpoint),\n            standbySnapshotNotificationLength(timeUnit, archiveEndpoint));\n\n        final int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(recordingId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        assertEquals(leadershipTermId, logBuffer.getLong(index + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(termBaseLogPosition, logBuffer.getLong(index + (2 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(logPosition, logBuffer.getLong(index + (3 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(timestamp, logBuffer.getLong(index + (4 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(memberId, logBuffer.getInt(index + (5 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(serviceId, logBuffer.getInt(index + (5 * SIZE_OF_LONG) + (SIZE_OF_INT), LITTLE_ENDIAN));\n\n        final int timeUnitIndex = index + (5 * SIZE_OF_LONG) + (2 * SIZE_OF_INT);\n        assertEquals(timeUnit.name(), logBuffer.getStringAscii(timeUnitIndex));\n        final int archiveEndpointIndex = timeUnitIndex + SIZE_OF_INT + timeUnit.name().length();\n        assertEquals(archiveEndpoint, logBuffer.getStringAscii(archiveEndpointIndex, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectStandbySnapshotNotification(\n            STANDBY_SNAPSHOT_NOTIFICATION, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern =\n            \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: STANDBY_SNAPSHOT_NOTIFICATION \\\\[90/90]: memberId=222 \" +\n            \"recordingId=9823674 leadershipTermId=23478 termBaseLeadershipPosition=823423 logPosition=9827342 \" +\n            \"timestamp=98273423434 timeUnit=MILLISECONDS serviceId=1 archiveEndpoint=localhost:9090\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logNewElection()\n    {\n        final int memberId = 42;\n        final long leadershipTermId = 8L;\n        final long logPosition = 9827342L;\n        final long appendPosition = 342384382L;\n        final String reason = \"why an election was started\";\n        final int offset = 16;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        final int encodedLength = newElectionLength(reason);\n\n        logger.logNewElection(memberId, leadershipTermId, logPosition, appendPosition, reason);\n\n        verifyLogHeader(\n            logBuffer,\n            offset,\n            NEW_ELECTION.toEventCodeId(),\n            encodedLength,\n            encodedLength);\n\n        final int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(leadershipTermId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        assertEquals(logPosition, logBuffer.getLong(index + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(appendPosition, logBuffer.getLong(index + 2 * SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(memberId, logBuffer.getInt(index + (3 * SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(reason, logBuffer.getStringAscii(index + (3 * SIZE_OF_LONG) + SIZE_OF_INT, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectNewElection(NEW_ELECTION, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern =\n            \"\\\\[[0-9]+\\\\.[0-9]+] CLUSTER: NEW_ELECTION \\\\[59/59]: memberId=42 \" +\n            \"leadershipTermId=8 logPosition=9827342 appendPosition=342384382 reason=\\\"why an election was started\\\"\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logClusterSessionStateChange()\n    {\n        final int memberId = 7;\n        final long sessionId = -47238947;\n        final ChronoUnit action = ChronoUnit.WEEKS;\n        final TimeUnit from = TimeUnit.NANOSECONDS;\n        final TimeUnit to = TimeUnit.HOURS;\n        final String reason = \"state changed somehow\";\n        final int offset = 16;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, offset);\n\n        final int encodedLength = clusterSessionStateChangeLength(action, from, to, reason);\n\n        logger.logClusterSessionStateChange(\n            memberId, sessionId, action, from, to, reason);\n\n        verifyLogHeader(\n            logBuffer,\n            offset,\n            CLUSTER_SESSION_STATE_CHANGE.toEventCodeId(),\n            encodedLength,\n            encodedLength);\n\n        int index = encodedMsgOffset(offset) + LOG_HEADER_LENGTH;\n        assertEquals(sessionId, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n        assertEquals(memberId, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        assertEquals(action.name(), logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT + action.name().length();\n        final String expectedStateTransition = from + \" -> \" + to;\n        assertEquals(expectedStateTransition, logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT + expectedStateTransition.length();\n        assertEquals(reason, logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n\n        final StringBuilder sb = new StringBuilder();\n        ClusterEventDissector.dissectClusterSessionStateChange(\n            CLUSTER_SESSION_STATE_CHANGE, logBuffer, encodedMsgOffset(offset), sb);\n\n        final String expectedMessagePattern =\n            \"\"\"\n                \\\\[[0-9]+\\\\.[0-9]+] CLUSTER: CLUSTER_SESSION_STATE_CHANGE \\\\[74/74]: memberId=7 \\\n                sessionId=-47238947 action=WEEKS NANOSECONDS -> HOURS reason=\\\"state changed somehow\\\"\"\"\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ClusterLoggingAgentTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.CommonContext;\nimport io.aeron.Counter;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.ClusteredMediaDriver;\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.ElectionState;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver.Context;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.ClusterTests;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.MessageHandler;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.io.File;\nimport java.util.EnumSet;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\nimport static io.aeron.agent.ClusterEventCode.APPEND_SESSION_CLOSE;\nimport static io.aeron.agent.ClusterEventCode.APPEND_SESSION_OPEN;\nimport static io.aeron.agent.ClusterEventCode.CLUSTER_SESSION_STATE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.ELECTION_STATE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.NEW_ELECTION;\nimport static io.aeron.agent.ClusterEventCode.ROLE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.STATE_CHANGE;\nimport static io.aeron.agent.ClusterEventCode.fromEventCodeId;\nimport static io.aeron.agent.CommonEventEncoder.LOG_HEADER_LENGTH;\nimport static io.aeron.agent.EventConfiguration.EVENT_READER_FRAME_LIMIT;\nimport static io.aeron.agent.EventConfiguration.EVENT_RING_BUFFER;\nimport static java.util.Collections.synchronizedSet;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ClusterLoggingAgentTest\n{\n    private static final Set<ClusterEventCode> WAIT_LIST = synchronizedSet(EnumSet.noneOf(ClusterEventCode.class));\n\n    private File testDir;\n    private ClusteredMediaDriver clusteredMediaDriver;\n    private ClusteredServiceContainer container;\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(clusteredMediaDriver.consensusModule(), container, clusteredMediaDriver);\n        AgentTests.stopLogging();\n\n        if (testDir != null && testDir.exists())\n        {\n            IoUtil.delete(testDir, false);\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void logAll()\n    {\n        testClusterEventsLogging(\n            \"all\",\n            EnumSet.of(\n                ROLE_CHANGE,\n                STATE_CHANGE,\n                ELECTION_STATE_CHANGE,\n                NEW_ELECTION,\n                APPEND_SESSION_OPEN,\n                APPEND_SESSION_CLOSE,\n                CLUSTER_SESSION_STATE_CHANGE));\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void logRoleChange()\n    {\n        testClusterEventsLogging(ROLE_CHANGE.name(), EnumSet.of(ROLE_CHANGE));\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void logStateChange()\n    {\n        testClusterEventsLogging(STATE_CHANGE.name(), EnumSet.of(STATE_CHANGE));\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void logElectionStateChange()\n    {\n        testClusterEventsLogging(ELECTION_STATE_CHANGE.name(), EnumSet.of(ELECTION_STATE_CHANGE));\n    }\n\n    private void testClusterEventsLogging(\n        final String enabledEvents, final EnumSet<ClusterEventCode> expectedEvents)\n    {\n        before(enabledEvents, expectedEvents);\n\n        final Context mediaDriverCtx = new Context()\n            .errorHandler(Tests::onError)\n            .dirDeleteOnStart(true)\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .threadingMode(ThreadingMode.SHARED);\n\n        final AeronArchive.Context aeronArchiveContext = new AeronArchive.Context()\n            .aeronDirectoryName(mediaDriverCtx.aeronDirectoryName())\n            .controlRequestChannel(\"aeron:ipc?term-length=64k\")\n            .controlRequestStreamId(AeronArchive.Configuration.localControlStreamId())\n            .controlResponseChannel(\"aeron:ipc?term-length=64k\")\n            .controlResponseStreamId(AeronArchive.Configuration.localControlStreamId() + 1)\n            .controlResponseStreamId(101);\n\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .aeronDirectoryName(mediaDriverCtx.aeronDirectoryName())\n            .errorHandler(Tests::onError)\n            .archiveDir(new File(testDir, \"archive\"))\n            .deleteArchiveOnStart(true)\n            .recordingEventsEnabled(false)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n        final ConsensusModule.Context consensusModuleCtx = new ConsensusModule.Context()\n            .aeronDirectoryName(mediaDriverCtx.aeronDirectoryName())\n            .errorHandler(ClusterTests.errorHandler(0))\n            .clusterDir(new File(testDir, \"consensus-module\"))\n            .archiveContext(aeronArchiveContext.clone())\n            .clusterMemberId(0)\n            .clusterMembers(\"0,localhost:20110,localhost:20220,localhost:20330,localhost:20440,localhost:8010\")\n            .logChannel(\"aeron:udp?term-length=256k|control-mode=manual|control=localhost:20550\")\n            .ingressChannel(\"aeron:udp?term-length=64k\")\n            .replicationChannel(\"aeron:udp?endpoint=localhost:0\");\n\n        final ClusteredService clusteredService = mock(ClusteredService.class);\n        final ClusteredServiceContainer.Context clusteredServiceCtx = new ClusteredServiceContainer.Context()\n            .aeronDirectoryName(mediaDriverCtx.aeronDirectoryName())\n            .errorHandler(ClusterTests.errorHandler(0))\n            .archiveContext(aeronArchiveContext.clone())\n            .clusterDir(new File(testDir, \"service\"))\n            .clusteredService(clusteredService);\n\n        clusteredMediaDriver = ClusteredMediaDriver.launch(mediaDriverCtx, archiveCtx, consensusModuleCtx);\n        container = ClusteredServiceContainer.launch(clusteredServiceCtx);\n\n        final Counter state = clusteredMediaDriver.consensusModule().context().electionStateCounter();\n\n        final Supplier<String> message = () -> ElectionState.get(state).toString();\n        while (ElectionState.CLOSED != ElectionState.get(state))\n        {\n            Tests.sleep(1, message);\n        }\n\n        final AeronCluster aeronCluster = AeronCluster.connect(new AeronCluster.Context()\n            .aeronDirectoryName(mediaDriverCtx.aeronDirectoryName())\n            .ingressChannel(\"aeron:udp\")\n            .ingressEndpoints(\"0=localhost:20110\")\n            .egressChannel(\"aeron:udp?term-length=256k|endpoint=localhost:0\"));\n\n        aeronCluster.close();\n\n        Tests.await(WAIT_LIST::isEmpty);\n    }\n\n    private void before(final String enabledEvents, final EnumSet<ClusterEventCode> expectedEvents)\n    {\n        final Map<String, String> configOptions = new HashMap<>();\n        configOptions.put(ConfigOption.READER_CLASSNAME, StubEventLogReaderAgent.class.getName());\n        configOptions.put(ConfigOption.ENABLED_CLUSTER_EVENT_CODES, enabledEvents);\n        AgentTests.startLogging(configOptions);\n\n        WAIT_LIST.clear();\n        WAIT_LIST.addAll(expectedEvents);\n\n        testDir = new File(IoUtil.tmpDirName(), \"cluster-test\");\n        if (testDir.exists())\n        {\n            IoUtil.delete(testDir, false);\n        }\n    }\n\n    static final class StubEventLogReaderAgent implements Agent, MessageHandler\n    {\n        public String roleName()\n        {\n            return \"event-log-reader\";\n        }\n\n        public int doWork()\n        {\n            return EVENT_RING_BUFFER.read(this, EVENT_READER_FRAME_LIMIT);\n        }\n\n        public void onMessage(final int msgTypeId, final MutableDirectBuffer buffer, final int index, final int length)\n        {\n            final ClusterEventCode eventCode = fromEventCodeId(msgTypeId);\n\n            switch (eventCode)\n            {\n                case ROLE_CHANGE:\n                {\n                    final int offset = index + LOG_HEADER_LENGTH + SIZE_OF_INT;\n                    final String role = buffer.getStringAscii(offset);\n                    if (role.contains(\"LEADER\"))\n                    {\n                        WAIT_LIST.remove(eventCode);\n                    }\n                    break;\n                }\n\n                case STATE_CHANGE:\n                {\n                    final int offset = index + LOG_HEADER_LENGTH + SIZE_OF_INT;\n                    final String state = buffer.getStringAscii(offset);\n                    if (state.contains(\"ACTIVE\"))\n                    {\n                        WAIT_LIST.remove(eventCode);\n                    }\n                    break;\n                }\n\n                case ELECTION_STATE_CHANGE:\n                {\n                    final int offset = index + LOG_HEADER_LENGTH + 2 * SIZE_OF_INT + 6 * SIZE_OF_LONG;\n                    final String state = buffer.getStringAscii(offset);\n                    if (state.contains(\"CLOSED\"))\n                    {\n                        WAIT_LIST.remove(eventCode);\n                    }\n                    break;\n                }\n\n                case APPEND_SESSION_CLOSE:\n                {\n                    final int offset = index + LOG_HEADER_LENGTH + 3 * SIZE_OF_LONG + SIZE_OF_INT;\n                    final String closeReason = buffer.getStringAscii(offset);\n                    if (\"CLIENT_ACTION\".equals(closeReason))\n                    {\n                        WAIT_LIST.remove(eventCode);\n                    }\n                    break;\n                }\n\n                case APPEND_SESSION_OPEN:\n                {\n                    final int offset = index + LOG_HEADER_LENGTH + SIZE_OF_LONG;\n                    if (buffer.getLong(offset + SIZE_OF_LONG) > buffer.getLong(offset))\n                    {\n                        WAIT_LIST.remove(eventCode);\n                    }\n                    break;\n                }\n\n                case CLUSTER_SESSION_STATE_CHANGE:\n                {\n                    final int offset = index + LOG_HEADER_LENGTH + SIZE_OF_LONG + SIZE_OF_INT;\n                    final String action = buffer.getStringAscii(offset);\n                    final String stateTransition = buffer.getStringAscii(offset + SIZE_OF_INT + action.length());\n                    final String reason = buffer.getStringAscii(\n                        offset + SIZE_OF_INT + action.length() + SIZE_OF_INT + stateTransition.length());\n                    if (\"CLIENT_ACTION\".equals(reason))\n                    {\n                        WAIT_LIST.remove(eventCode);\n                    }\n                    break;\n                }\n\n                default:\n                    WAIT_LIST.remove(eventCode);\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/CommonEventDissectorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.ZoneId;\n\nimport static io.aeron.agent.CommonEventEncoder.LOG_HEADER_LENGTH;\nimport static io.aeron.agent.CommonEventEncoder.internalEncodeLogHeader;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.hamcrest.CoreMatchers.*;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass CommonEventDissectorTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH]);\n    private final StringBuilder builder = new StringBuilder();\n\n    @Test\n    void dissectLogStartMessage()\n    {\n        final long timestampNs = 10_000_001_955L;\n        final long timestampMs = 10_000_001L;\n\n        CommonEventDissector.dissectLogStartMessage(timestampNs, timestampMs, ZoneId.of(\"UTC\"), builder);\n        assertThat(builder.toString(), equalTo(\"[10.000001955] log started 1970-01-01 02:46:40.001+0000\"));\n\n        builder.delete(0, builder.length());\n\n        CommonEventDissector.dissectLogStartMessage(timestampNs, timestampMs, ZoneId.of(\"America/New_York\"), builder);\n        assertThat(builder.toString(), equalTo(\"[10.000001955] log started 1969-12-31 21:46:40.001-0500\"));\n    }\n\n    @Test\n    void dissectLogHeader()\n    {\n        internalEncodeLogHeader(buffer, 0, 100, 222, () -> 1234567890);\n\n        final int decodedLength = CommonEventDissector\n            .dissectLogHeader(\"test ctx\", ArchiveEventCode.CMD_OUT_RESPONSE, buffer, 0, builder);\n\n        assertEquals(LOG_HEADER_LENGTH, decodedLength);\n        assertEquals(\"[1.234567890] test ctx: CMD_OUT_RESPONSE [100/222]\", builder.toString());\n    }\n\n    @Test\n    void dissectSocketAddressIpv4()\n    {\n        final int offset = 16;\n        buffer.putInt(offset, 12121, LITTLE_ENDIAN);\n        buffer.putInt(offset + SIZE_OF_INT, 4, LITTLE_ENDIAN);\n        buffer.putBytes(offset + SIZE_OF_INT * 2, new byte[]{ 127, 0, 0, 1 });\n\n        final int decodedLength = CommonEventDissector.dissectSocketAddress(buffer, offset, builder);\n\n        assertEquals(12, decodedLength);\n        assertEquals(\"127.0.0.1:12121\", builder.toString());\n    }\n\n    @Test\n    void dissectSocketAddressIpv6()\n    {\n        final int offset = 16;\n        buffer.putInt(offset, 7777, LITTLE_ENDIAN);\n        buffer.putInt(offset + SIZE_OF_INT, 16, LITTLE_ENDIAN);\n        buffer.putBytes(offset + (SIZE_OF_INT * 2),\n            new byte[]{ -100, 124, 0, 18, 120, -128, 44, 44, 10, -80, 80, 22, 122, 5, 5, -99 });\n\n        final int decodedLength = CommonEventDissector.dissectSocketAddress(buffer, offset, builder);\n\n        assertEquals(24, decodedLength);\n        assertEquals(\"[9c7c:12:7880:2c2c:ab0:5016:7a05:59d]:7777\", builder.toString());\n    }\n\n    @Test\n    void dissectSocketAddressInvalidLength()\n    {\n        final int offset = 16;\n        buffer.putInt(offset, 555, LITTLE_ENDIAN);\n        buffer.putInt(offset + SIZE_OF_INT, 7, LITTLE_ENDIAN);\n\n        final int decodedLength = CommonEventDissector.dissectSocketAddress(buffer, offset, builder);\n\n        assertEquals(15, decodedLength);\n        assertEquals(\"unknown-address:555\", builder.toString());\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/CommonEventEncoderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.net.InetSocketAddress;\nimport java.time.temporal.ChronoUnit;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.agent.CommonEventEncoder.*;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.util.Arrays.asList;\nimport static java.util.Arrays.fill;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\nclass CommonEventEncoderTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH * 10]);\n\n    @Test\n    void encodeLogHeader()\n    {\n        final int offset = 12;\n\n        final int encodedLength = internalEncodeLogHeader(buffer, offset, 100, Integer.MAX_VALUE, () -> 555_000L);\n\n        assertEquals(LOG_HEADER_LENGTH, encodedLength);\n        assertEquals(100, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(Integer.MAX_VALUE, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(555_000L, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodeLogHeaderThrowsIllegalArgumentExceptionIfCaptureLengthIsNegative()\n    {\n        assertThrows(IllegalArgumentException.class,\n            () -> internalEncodeLogHeader(buffer, 0, -1, Integer.MAX_VALUE, () -> 0));\n    }\n\n    @Test\n    void encodeLogHeaderThrowsIllegalArgumentExceptionIfCaptureLengthIsGreaterThanMaxCaptureSize()\n    {\n        assertThrows(IllegalArgumentException.class,\n            () -> internalEncodeLogHeader(buffer, 0, MAX_CAPTURE_LENGTH + 1, Integer.MAX_VALUE, () -> 0));\n    }\n\n    @Test\n    void encodeLogHeaderThrowsIllegalArgumentExceptionIfCaptureLengthIsGreaterThanLength()\n    {\n        assertThrows(IllegalArgumentException.class,\n            () -> internalEncodeLogHeader(buffer, 0, 100, 80, () -> 0));\n    }\n\n    @Test\n    void encodeLogHeaderThrowsIllegalArgumentExceptionIfLengthIsNegative()\n    {\n        assertThrows(IllegalArgumentException.class,\n            () -> internalEncodeLogHeader(buffer, 0, 20, -2, () -> 0));\n    }\n\n    @Test\n    void encodeSocketAddress()\n    {\n        final InetSocketAddress socketAddress = new InetSocketAddress(15015);\n        final byte[] address = socketAddress.getAddress().getAddress();\n\n        final int encodedLength = CommonEventEncoder.encodeSocketAddress(buffer, 4, socketAddress);\n\n        assertEquals(SIZE_OF_INT * 2 + address.length, encodedLength);\n        assertEquals(15015, buffer.getInt(4, LITTLE_ENDIAN));\n        assertEquals(address.length, buffer.getInt(4 + SIZE_OF_INT, LITTLE_ENDIAN));\n        final byte[] encodedAddress = new byte[address.length];\n        buffer.getBytes(4 + SIZE_OF_INT * 2, encodedAddress);\n        assertArrayEquals(address, encodedAddress);\n    }\n\n    @Test\n    void encodeTrailingStringAsAsciiWhenPayloadIsSmallerThanMaxMessageSizeWithoutHeader()\n    {\n        final int offset = 17;\n        final int remainingCapacity = 22;\n\n        final int encodedLength = encodeTrailingString(buffer, offset, remainingCapacity, \"ab©d️\");\n\n        assertEquals(SIZE_OF_INT + 5, encodedLength);\n        assertEquals(\"ab?d?\", buffer.getStringAscii(offset));\n    }\n\n    @Test\n    void encodeTrailingStringAsAsciiWhenPayloadExceedsMaxMessageSizeWithoutHeader()\n    {\n        final int offset = 23;\n        final int remainingCapacity = 59;\n        final char[] chars = new char[100];\n        fill(chars, 'x');\n        final String value = new String(chars);\n\n        final int encodedLength = encodeTrailingString(buffer, offset, remainingCapacity, value);\n\n        assertEquals(remainingCapacity, encodedLength);\n        assertEquals(value.substring(0, remainingCapacity - SIZE_OF_INT - 3) + \"...\",\n            buffer.getStringAscii(offset));\n    }\n\n    @Test\n    void encodeBufferSmallerThanMaxCaptureSize()\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[256]);\n        final int offset = 24;\n        final int srcOffset = 20;\n        final int length = 128;\n        srcBuffer.setMemory(srcOffset, length, (byte)111);\n\n        final int encodedLength = encode(buffer, offset, length, length, srcBuffer, srcOffset);\n\n        assertEquals(LOG_HEADER_LENGTH + length, encodedLength);\n        assertEquals(length, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        for (int i = 0; i < length; i++)\n        {\n            assertEquals(111, buffer.getByte(offset + LOG_HEADER_LENGTH + i));\n        }\n    }\n\n    @Test\n    void encodeBufferBuggerThanMaxCaptureSize()\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH * 2]);\n        final int offset = 256;\n        final int srcOffset = 20;\n        final int length = MAX_EVENT_LENGTH + 1000;\n        srcBuffer.setMemory(srcOffset, length, (byte)-5);\n\n        final int encodedLength = encode(buffer, offset, MAX_CAPTURE_LENGTH, length, srcBuffer, srcOffset);\n\n        assertEquals(MAX_EVENT_LENGTH, encodedLength);\n        assertEquals(MAX_CAPTURE_LENGTH, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        for (int i = 0; i < MAX_CAPTURE_LENGTH; i++)\n        {\n            assertEquals(-5, buffer.getByte(offset + LOG_HEADER_LENGTH + i));\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"captureLengthArgs\")\n    void testCaptureLength(final int length, final int captureLength)\n    {\n        assertEquals(captureLength, captureLength(length));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"encodedLengthArgs\")\n    void testEncodedLength(final int length, final int encodedLength)\n    {\n        assertEquals(encodedLength, encodedLength(length));\n    }\n\n    @Test\n    void testStateTransitionStringLength()\n    {\n        final TimeUnit from = TimeUnit.NANOSECONDS;\n        final TimeUnit to = TimeUnit.HOURS;\n        assertEquals(from.name().length() + STATE_SEPARATOR.length() + to.name().length() + SIZE_OF_INT,\n            stateTransitionStringLength(from, to));\n    }\n\n    @Test\n    void stateNameReturnsNameOfTheEnumConstant()\n    {\n        final ChronoUnit state = ChronoUnit.CENTURIES;\n        assertEquals(state.name(), enumName(state));\n    }\n\n    @Test\n    void stateNameReturnsNullIfNull()\n    {\n        assertEquals(\"null\", enumName(null));\n    }\n\n    private static List<Arguments> captureLengthArgs()\n    {\n        return asList(\n            arguments(0, 0),\n            arguments(10, 10),\n            arguments(MAX_CAPTURE_LENGTH, MAX_CAPTURE_LENGTH),\n            arguments(Integer.MAX_VALUE, MAX_CAPTURE_LENGTH));\n    }\n\n    private static List<Arguments> encodedLengthArgs()\n    {\n        return asList(\n            arguments(0, LOG_HEADER_LENGTH),\n            arguments(2, LOG_HEADER_LENGTH + 2),\n            arguments(-LOG_HEADER_LENGTH, 0),\n            arguments(Integer.MAX_VALUE, Integer.MIN_VALUE + LOG_HEADER_LENGTH - 1));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/ConfigOptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport static io.aeron.agent.ConfigOption.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass ConfigOptionTest\n{\n    @Test\n    void shouldReadSystemProperties()\n    {\n        final Map<String, String> expectedValues = new HashMap<>();\n        try\n        {\n            expectedValues.put(DISABLED_ARCHIVE_EVENT_CODES, \"abc\");\n            expectedValues.put(LOG_FILENAME, \"\");\n            expectedValues.put(ENABLED_CLUSTER_EVENT_CODES, \"1,2,3\");\n            for (final Map.Entry<String, String> entry : expectedValues.entrySet())\n            {\n                System.setProperty(entry.getKey(), entry.getValue());\n            }\n            System.setProperty(\"ignore me\", \"1000\");\n\n            final Map<String, String> values = fromSystemProperties();\n\n            assertEquals(\"abc\", values.get(DISABLED_ARCHIVE_EVENT_CODES));\n            assertEquals(\"\", values.get(LOG_FILENAME));\n            assertEquals(\"1,2,3\", values.get(ENABLED_CLUSTER_EVENT_CODES));\n        }\n        finally\n        {\n            System.clearProperty(DISABLED_ARCHIVE_EVENT_CODES);\n            System.clearProperty(LOG_FILENAME);\n            System.clearProperty(ENABLED_CLUSTER_EVENT_CODES);\n            System.clearProperty(\"ignore me\");\n        }\n    }\n\n    @Test\n    void shouldReturnAllSystemProperties()\n    {\n        final Map<String, String> values = fromSystemProperties();\n\n        assertNotEquals(Collections.emptySet(), values);\n    }\n\n    @Test\n    void shouldThrowNullPointerExceptionIfConfigOptionsMapIsNull()\n    {\n        assertThrows(NullPointerException.class, () -> buildAgentArgs(null));\n    }\n\n    @Test\n    void shouldReturnNullIfConfigOptionsMapIsEmpty()\n    {\n        assertNull(buildAgentArgs(new HashMap<>()));\n    }\n\n    @Test\n    void shouldCombineConfigOptionsIntoAnAgentArgsString()\n    {\n        final Map<String, String> configOptions = new LinkedHashMap<>();\n        configOptions.put(READER_CLASSNAME, \"reader class\");\n        configOptions.put(DISABLED_CLUSTER_EVENT_CODES, \"CANVASS_POSITION\");\n        configOptions.put(ENABLED_DRIVER_EVENT_CODES, \"all\");\n        configOptions.put(LOG_FILENAME, \"file.txt\");\n        configOptions.put(DISABLED_DRIVER_EVENT_CODES, \"admin\");\n\n        final String agentArgs = buildAgentArgs(configOptions);\n\n        assertEquals(\n            \"aeron.event.log.reader.classname=reader class|\" +\n            \"aeron.event.cluster.log.disable=CANVASS_POSITION|\" +\n            \"aeron.event.log=all|\" +\n            \"aeron.event.log.filename=file.txt|\" +\n            \"aeron.event.log.disable=admin\",\n            agentArgs);\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void shouldThrowExceptionIfNullOrEmptyAgentArgs(final String agentArgs)\n    {\n        final IllegalArgumentException exception =\n            assertThrows(IllegalArgumentException.class, () -> parseAgentArgs(agentArgs));\n        assertEquals(\"cannot parse empty value\", exception.getMessage());\n    }\n\n    @Test\n    void shouldParseAgentArgsAsAConfigOptionsMap()\n    {\n        final Map<String, String> expectedOptions = new HashMap<>();\n        expectedOptions.put(LOG_FILENAME, \"log.out\");\n        expectedOptions.put(READER_CLASSNAME, \"my reader\");\n        expectedOptions.put(ENABLED_DRIVER_EVENT_CODES, \"all\");\n        expectedOptions.put(DISABLED_DRIVER_EVENT_CODES, \"FRAME_IN,FRAME_OUT\");\n        expectedOptions.put(ENABLED_CLUSTER_EVENT_CODES, \"all\");\n        expectedOptions.put(DISABLED_CLUSTER_EVENT_CODES, \"CANVASS_POSITION,STATE_CHANGE\");\n\n        final Map<String, String> configOptions = parseAgentArgs(\n            \"||1600|abc|aeron.event.log.filename=log.out|\" +\n            \"aeron.event.cluster.log=all|\" +\n            \"aeron.event.log.reader.classname=my reader|\" +\n            \"aeron.event.log.disable=FRAME_IN,FRAME_OUT|\" +\n            \"aeron.event.log=all|\" +\n            \"aeron.event.cluster.log.disable=CANVASS_POSITION,STATE_CHANGE\");\n\n        assertEquals(expectedOptions, configOptions);\n    }\n\n    @Test\n    void shouldParseAgentArgsThatEndWithExtraSeparators()\n    {\n        final Map<String, String> expectedOptions = new HashMap<>();\n        expectedOptions.put(ENABLED_ARCHIVE_EVENT_CODES, \"REPLICATION_SESSION_STATE_CHANGE,CMD_IN_START_RECORDING\");\n\n        final Map<String, String> configOptions = parseAgentArgs(\n            \"aeron.event.archive.log=REPLICATION_SESSION_STATE_CHANGE,CMD_IN_START_RECORDING||||\");\n\n        assertEquals(expectedOptions, configOptions);\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/DriverEventCodeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport static org.junit.jupiter.api.Assertions.assertSame;\n\nclass DriverEventCodeTest\n{\n    @ParameterizedTest\n    @EnumSource(DriverEventCode.class)\n    void getCodeById(final DriverEventCode code)\n    {\n        assertSame(code, DriverEventCode.get(code.id()));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/DriverEventDissectorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.ErrorCode;\nimport io.aeron.command.*;\nimport io.aeron.protocol.*;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.UnknownHostException;\n\nimport static io.aeron.agent.CommonEventEncoder.*;\nimport static io.aeron.agent.DriverEventCode.*;\nimport static io.aeron.agent.DriverEventDissector.*;\nimport static io.aeron.agent.DriverEventLogger.MAX_CHANNEL_URI_LENGTH;\nimport static io.aeron.agent.DriverEventLogger.MAX_HOST_NAME_LENGTH;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static io.aeron.protocol.HeaderFlyweight.*;\nimport static java.nio.ByteBuffer.allocate;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.*;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.endsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass DriverEventDissectorTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH]);\n    private final StringBuilder builder = new StringBuilder();\n\n    @Test\n    void dissectRemovePublicationCleanup()\n    {\n        final int offset = 12;\n        internalEncodeLogHeader(buffer, offset, 22, 88, () -> 2_500_000_000L);\n        buffer.putInt(offset + LOG_HEADER_LENGTH, 42, LITTLE_ENDIAN);\n        buffer.putInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, 11, LITTLE_ENDIAN);\n        buffer.putStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2, \"channel uri\");\n\n        DriverEventDissector.dissectRemovePublicationCleanup(buffer, offset, builder);\n\n        assertEquals(\"[2.500000000] \" + CONTEXT + \": \" + REMOVE_PUBLICATION_CLEANUP.name() +\n            \" [22/88]: sessionId=42 streamId=11 channel=channel uri\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectRemoveSubscriptionCleanup()\n    {\n        final int offset = 16;\n        internalEncodeLogHeader(buffer, offset, 100, 100, () -> 100_000_000L);\n        buffer.putInt(offset + LOG_HEADER_LENGTH, 33, LITTLE_ENDIAN);\n        buffer.putLong(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, 111_111_111_111L, LITTLE_ENDIAN);\n        buffer.putStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT + SIZE_OF_LONG, \"test\");\n\n        DriverEventDissector.dissectRemoveSubscriptionCleanup(buffer, offset, builder);\n\n        assertEquals(\"[0.100000000] \" + CONTEXT + \": \" + REMOVE_SUBSCRIPTION_CLEANUP.name() +\n            \" [100/100]: streamId=33 id=111111111111 channel=test\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectRemoveImageCleanup()\n    {\n        final int offset = 32;\n        internalEncodeLogHeader(buffer, offset, 66, 99, () -> 12345678900L);\n        buffer.putInt(offset + LOG_HEADER_LENGTH, 77, LITTLE_ENDIAN);\n        buffer.putInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, 55, LITTLE_ENDIAN);\n        buffer.putLong(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2, 1_000_000L, LITTLE_ENDIAN);\n        buffer.putStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2 + SIZE_OF_LONG, \"URI\");\n\n        DriverEventDissector.dissectRemoveImageCleanup(buffer, offset, builder);\n\n        assertEquals(\"[12.345678900] \" + CONTEXT + \": \" + REMOVE_IMAGE_CLEANUP.name() +\n            \" [66/99]: sessionId=77 streamId=55 id=1000000 channel=URI\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectFrameTypePad()\n    {\n        internalEncodeLogHeader(buffer, 0, 5, 5, () -> 1_000_000_000);\n        final int socketAddressOffset = encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH, new InetSocketAddress(\"localhost\", 8080));\n        final DataHeaderFlyweight flyweight = new DataHeaderFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH + socketAddressOffset, 300);\n        flyweight.headerType(HDR_TYPE_PAD);\n        flyweight.flags((short)13);\n        flyweight.frameLength(100);\n        flyweight.sessionId(42);\n        flyweight.streamId(5);\n        flyweight.termId(16);\n        flyweight.termOffset(1045);\n\n        dissectFrame(FRAME_IN, buffer, 0, builder);\n\n        assertEquals(\"[1.000000000] \" + CONTEXT + \": \" + FRAME_IN.name() + \" [5/5]: \" +\n            \"address=127.0.0.1:8080 type=PAD flags=00001101 frameLength=100 sessionId=42 streamId=5 termId=16 \" +\n            \"termOffset=1045\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectFrameTypeData()\n    {\n        internalEncodeLogHeader(buffer, 0, 5, 5, () -> 1_000_000_000);\n        final int socketAddressOffset = encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH, new InetSocketAddress(\"localhost\", 8888));\n        final DataHeaderFlyweight flyweight = new DataHeaderFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH + socketAddressOffset, 300);\n        flyweight.headerType(HDR_TYPE_DATA);\n        flyweight.flags((short)23);\n        flyweight.frameLength(77);\n        flyweight.sessionId(12);\n        flyweight.streamId(51);\n        flyweight.termId(6);\n        flyweight.termOffset(444);\n\n        dissectFrame(FRAME_IN, buffer, 0, builder);\n\n        assertEquals(\"[1.000000000] \" + CONTEXT + \": \" + FRAME_IN.name() + \" [5/5]: \" +\n            \"address=127.0.0.1:8888 type=DATA flags=00010111 frameLength=77 sessionId=12 streamId=51 termId=6 \" +\n            \"termOffset=444\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectFrameTypeStatusMessage()\n    {\n        internalEncodeLogHeader(buffer, 0, 5, 5, () -> 1_000_000_000);\n        final int socketAddressOffset = encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH, new InetSocketAddress(\"localhost\", 8888));\n        final StatusMessageFlyweight flyweight = new StatusMessageFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH + socketAddressOffset, 300);\n        flyweight.headerType(HDR_TYPE_SM);\n        flyweight.flags((short)7);\n        flyweight.frameLength(121);\n        flyweight.sessionId(5);\n        flyweight.streamId(8);\n        flyweight.consumptionTermId(4);\n        flyweight.consumptionTermOffset(18);\n        flyweight.receiverWindowLength(2048);\n        flyweight.receiverId(11);\n\n        dissectFrame(FRAME_OUT, buffer, 0, builder);\n\n        assertEquals(\"[1.000000000] \" + CONTEXT + \": \" + FRAME_OUT.name() + \" [5/5]: \" +\n            \"address=127.0.0.1:8888 type=SM flags=00000111 frameLength=121 sessionId=5 streamId=8 termId=4 \" +\n            \"termOffset=18 receiverWindowLength=2048 receiverId=11\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectFrameTypeNak()\n    {\n        internalEncodeLogHeader(buffer, 0, 3, 3, () -> 3_000_000_000L);\n        final int socketAddressOffset = encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH, new InetSocketAddress(\"localhost\", 8888));\n        final NakFlyweight flyweight = new NakFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH + socketAddressOffset, 300);\n        flyweight.headerType(HDR_TYPE_NAK);\n        flyweight.flags((short)2);\n        flyweight.frameLength(54);\n        flyweight.sessionId(5);\n        flyweight.streamId(8);\n        flyweight.termId(20);\n        flyweight.termOffset(0);\n        flyweight.length(999999);\n\n        dissectFrame(FRAME_OUT, buffer, 0, builder);\n\n        assertEquals(\"[3.000000000] \" + CONTEXT + \": \" + FRAME_OUT.name() +\n            \" [3/3]: address=127.0.0.1:8888 type=NAK flags=00000010 frameLength=54 sessionId=5 streamId=8 \" +\n            \"termId=20 termOffset=0 length=999999\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectFrameTypeSetup()\n    {\n        internalEncodeLogHeader(buffer, 0, 3, 3, () -> 3_000_000_000L);\n        final int socketAddressOffset = encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH, new InetSocketAddress(\"localhost\", 8888));\n        final SetupFlyweight flyweight = new SetupFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH + socketAddressOffset, 300);\n        flyweight.headerType(HDR_TYPE_SETUP);\n        flyweight.flags((short)200);\n        flyweight.frameLength(1);\n        flyweight.sessionId(15);\n        flyweight.streamId(18);\n        flyweight.activeTermId(81);\n        flyweight.initialTermId(69);\n        flyweight.termOffset(10);\n        flyweight.termLength(444);\n        flyweight.mtuLength(8096);\n        flyweight.ttl(20_000);\n\n        dissectFrame(FRAME_IN, buffer, 0, builder);\n\n        assertEquals(\"[3.000000000] \" + CONTEXT + \": \" + FRAME_IN.name() +\n            \" [3/3]: address=127.0.0.1:8888 type=SETUP flags=11001000 frameLength=1 sessionId=15 streamId=18 \" +\n            \"activeTermId=81 initialTermId=69 termOffset=10 termLength=444 mtu=8096 ttl=20000\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectFrameTypeRtt()\n    {\n        internalEncodeLogHeader(buffer, 0, 3, 3, () -> 3_000_000_000L);\n        final int socketAddressOffset = encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH, new InetSocketAddress(\"localhost\", 8888));\n        final RttMeasurementFlyweight flyweight = new RttMeasurementFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH + socketAddressOffset, 300);\n        flyweight.headerType(HDR_TYPE_RTTM);\n        flyweight.flags((short)20);\n        flyweight.frameLength(100);\n        flyweight.sessionId(0);\n        flyweight.streamId(1);\n        flyweight.echoTimestampNs(123456789);\n        flyweight.receptionDelta(354);\n        flyweight.receiverId(22);\n\n        dissectFrame(FRAME_OUT, buffer, 0, builder);\n\n        assertEquals(\"[3.000000000] \" + CONTEXT + \": \" + FRAME_OUT.name() + \" [3/3]: \" +\n            \"address=127.0.0.1:8888 type=RTT flags=00010100 frameLength=100 sessionId=0 streamId=1 \" +\n            \"echoTimestampNs=123456789 receptionDelta=354 receiverId=22\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectFrameTypeError()\n    {\n        internalEncodeLogHeader(buffer, 0, 3, 3, () -> 3_000_000_000L);\n        final int socketAddressOffset = encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH, new InetSocketAddress(\"localhost\", 5555));\n        final ErrorFlyweight flyweight = new ErrorFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH + socketAddressOffset, 300);\n        flyweight.headerType(HDR_TYPE_ERR);\n        flyweight.flags((short)ErrorFlyweight.HAS_GROUP_ID_FLAG);\n        flyweight.frameLength(876);\n        flyweight.sessionId(42);\n        flyweight.streamId(999);\n        flyweight.receiverId(-4723947284689L);\n        flyweight.groupTag(1_000_000_000_000_1L);\n        flyweight.errorCode(1959);\n        flyweight.errorMessage(\"test err msg string\");\n\n        dissectFrame(FRAME_OUT, buffer, 0, builder);\n\n        assertEquals(\"[3.000000000] \" + CONTEXT + \": \" + FRAME_OUT.name() + \" [3/3]: \" +\n            \"address=127.0.0.1:5555 type=ERR flags=00001000 frameLength=59 sessionId=42 streamId=999 \" +\n            \"receiverId=-4723947284689 groupTag=10000000000001 errorCode=1959 errorMessage=\\\"test err msg string\\\"\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectFrameTypeUnknown()\n    {\n        internalEncodeLogHeader(buffer, 0, 3, 3, () -> 3_000_000_000L);\n        final int socketAddressOffset = encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH, new InetSocketAddress(\"localhost\", 8888));\n        final DataHeaderFlyweight flyweight = new DataHeaderFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH + socketAddressOffset, 300);\n        flyweight.headerType(Integer.MAX_VALUE);\n\n        dissectFrame(CMD_OUT_ON_OPERATION_SUCCESS, buffer, 0, builder);\n\n        assertEquals(\"[3.000000000] \" + CONTEXT + \": \" + CMD_OUT_ON_OPERATION_SUCCESS.name() +\n            \" [3/3]: address=127.0.0.1:8888 type=UNKNOWN(65535)\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectString()\n    {\n        internalEncodeLogHeader(buffer, 0, 1, 1, () -> 1_100_000_000L);\n        buffer.putStringAscii(LOG_HEADER_LENGTH, \"Hello, World!\");\n\n        DriverEventDissector.dissectString(CMD_IN_CLIENT_CLOSE, buffer, 0, builder);\n\n        assertEquals(\"[1.100000000] \" + CONTEXT + \": \" + CMD_IN_CLIENT_CLOSE.name() + \" [1/1]: Hello, World!\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_IN_ADD_PUBLICATION\", \"CMD_IN_ADD_EXCLUSIVE_PUBLICATION\" })\n    void dissectCommandPublication(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, 10, 10, () -> 1_780_000_000L);\n        final PublicationMessageFlyweight flyweight = new PublicationMessageFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.channel(\"pub channel\");\n        flyweight.streamId(3);\n        flyweight.clientId(eventCode.id());\n        flyweight.correlationId(15);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[1.780000000] \" + CONTEXT + \": \" + eventCode.name() + \" [10/10]: \" +\n            \"streamId=3 clientId=\" + eventCode.id() + \" correlationId=15 channel=pub channel\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_IN_ADD_SUBSCRIPTION\" })\n    void dissectCommandSubscription(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 10, () -> 1_780_000_000L);\n        final SubscriptionMessageFlyweight flyweight = new SubscriptionMessageFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.channel(\"sub channel\");\n        flyweight.streamId(31);\n        flyweight.registrationCorrelationId(90);\n        flyweight.clientId(eventCode.id());\n        flyweight.correlationId(6);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[1.780000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/10]: \" +\n            \"streamId=31 registrationCorrelationId=90 clientId=\" + eventCode.id() + \" correlationId=6 \" +\n            \"channel=sub channel\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_IN_REMOVE_COUNTER\" })\n    void dissectCommandRemoveCounterEvent(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 87, () -> 21_032_000_000L);\n        final RemoveCounterFlyweight flyweight = new RemoveCounterFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.registrationId(11);\n        flyweight.clientId(eventCode.id());\n        flyweight.correlationId(16);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/87]: \" +\n            \"registrationId=11 clientId=\" + eventCode.id() + \" correlationId=16\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_IN_REMOVE_PUBLICATION\" })\n    void dissectCommandRemovePublicationEvent(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, RemovePublicationFlyweight.length(), 87, () -> 21_032_000_000L);\n        final RemovePublicationFlyweight flyweight = new RemovePublicationFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.registrationId(11);\n        flyweight.clientId(eventCode.id());\n        flyweight.correlationId(16);\n        flyweight.revoke(true);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" +\n            RemovePublicationFlyweight.length() + \"/87]: \" + \"registrationId=11 clientId=\" +\n            eventCode.id() + \" correlationId=16 revoke=true\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_IN_REMOVE_PUBLICATION\" })\n    void dissectOldShortCommandRemovePublicationEvent(final DriverEventCode eventCode)\n    {\n        // Remove Publication Commands from an old client will be shorter.  The won't have the flags field.\n        // The dissector should notice the shorter length and act appropriately.\n        internalEncodeLogHeader(buffer, 0, 24, 87, () -> 21_032_000_000L);\n        final RemovePublicationFlyweight flyweight = new RemovePublicationFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.registrationId(11);\n        flyweight.clientId(eventCode.id());\n        flyweight.correlationId(16);\n        flyweight.revoke(true);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [24/87]: \" +\n            \"registrationId=11 clientId=\" + eventCode.id() + \" correlationId=16\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_IN_REMOVE_SUBSCRIPTION\" })\n    void dissectCommandRemoveSubscriptionEvent(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 87, () -> 21_032_000_000L);\n        final RemoveSubscriptionFlyweight flyweight = new RemoveSubscriptionFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.registrationId(11);\n        flyweight.clientId(eventCode.id());\n        flyweight.correlationId(16);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/87]: \" +\n            \"registrationId=11 clientId=\" + eventCode.id() + \" correlationId=16\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class,\n        names = { \"CMD_OUT_PUBLICATION_READY\", \"CMD_OUT_EXCLUSIVE_PUBLICATION_READY\" })\n    void dissectCommandPublicationReady(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 100, () -> 21_032_000_000L);\n        final PublicationBuffersReadyFlyweight flyweight = new PublicationBuffersReadyFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.sessionId(eventCode.ordinal());\n        flyweight.streamId(-24);\n        flyweight.publicationLimitCounterId(1);\n        flyweight.channelStatusCounterId(5);\n        flyweight.correlationId(8);\n        flyweight.registrationId(eventCode.id());\n        flyweight.logFileName(\"log.txt\");\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/100]: \" +\n            \"sessionId=\" + eventCode.ordinal() + \" streamId=-24 publicationLimitCounterId=1 channelStatusCounterId=5 \" +\n            \"correlationId=8 registrationId=\" + eventCode.id() + \" logFileName=log.txt\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_OUT_AVAILABLE_IMAGE\" })\n    void dissectCommandImageReady(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 100, () -> 21_032_000_000L);\n        final ImageBuffersReadyFlyweight flyweight = new ImageBuffersReadyFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.sessionId(eventCode.ordinal());\n        flyweight.streamId(22);\n        flyweight.subscriberPositionId(0);\n        flyweight.subscriptionRegistrationId(245);\n        flyweight.correlationId(767);\n        flyweight.logFileName(\"log2.txt\");\n        flyweight.sourceIdentity(\"source identity\");\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/100]: \" +\n            \"sessionId=\" + eventCode.ordinal() + \" streamId=22 subscriberPositionId=0 subscriptionRegistrationId=245 \" +\n            \"correlationId=767 sourceIdentity=source identity logFileName=log2.txt\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_OUT_ON_OPERATION_SUCCESS\" })\n    void dissectCommandOperationSuccess(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 111, () -> 21_032_000_000L);\n        final OperationSucceededFlyweight flyweight = new OperationSucceededFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.correlationId(eventCode.id());\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/111]: \" +\n            \"correlationId=\" + eventCode.id(),\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_IN_KEEPALIVE_CLIENT\", \"CMD_IN_CLIENT_CLOSE\" })\n    void dissectCommandCorrelationEvent(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 100, () -> 21_032_000_000L);\n        final CorrelatedMessageFlyweight flyweight = new CorrelatedMessageFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.clientId(eventCode.id());\n        flyweight.correlationId(2);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/100]: \" +\n            \"clientId=\" + eventCode.id() + \" correlationId=2\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_OUT_ON_UNAVAILABLE_IMAGE\" })\n    void dissectCommandImage(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 99, () -> 21_032_000_000L);\n        final ImageMessageFlyweight flyweight = new ImageMessageFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.streamId(300);\n        flyweight.correlationId(eventCode.id());\n        flyweight.subscriptionRegistrationId(-19);\n        flyweight.channel(\"the channel\");\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/99]: \" +\n            \"streamId=300 correlationId=\" + eventCode.id() + \" subscriptionRegistrationId=-19 channel=the channel\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = {\n        \"CMD_IN_ADD_DESTINATION\",\n        \"CMD_IN_REMOVE_DESTINATION\",\n        \"CMD_IN_ADD_RCV_DESTINATION\",\n        \"CMD_IN_REMOVE_RCV_DESTINATION\" })\n    void dissectCommandDestination(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 77, () -> 21_032_000_000L);\n        final DestinationMessageFlyweight flyweight = new DestinationMessageFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.channel(\"dst\");\n        flyweight.registrationCorrelationId(eventCode.id());\n        flyweight.clientId(1010101);\n        flyweight.correlationId(404);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[21.032000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/77]: \" +\n            \"registrationCorrelationId=\" + eventCode.id() + \" clientId=1010101 correlationId=404 channel=dst\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_OUT_ERROR\" })\n    void dissectCommandError(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 100, () -> 1_900_000_000L);\n        final ErrorResponseFlyweight flyweight = new ErrorResponseFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.offendingCommandCorrelationId(eventCode.id());\n        flyweight.errorCode(ErrorCode.MALFORMED_COMMAND);\n        flyweight.errorMessage(\"Huge stacktrace!\");\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[1.900000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/100]: \" +\n            \"offendingCommandCorrelationId=\" + eventCode.id() + \" errorCode=\" + ErrorCode.MALFORMED_COMMAND +\n            \" message=Huge stacktrace!\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_IN_ADD_COUNTER\" })\n    void dissectCommandCounter(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 100, () -> 1_900_000_000L);\n        final CounterMessageFlyweight flyweight = new CounterMessageFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.typeId(3);\n        flyweight.keyBuffer(newBuffer(new byte[20]), 0, 10);\n        flyweight.labelBuffer(newBuffer(new byte[100]), 26, 13);\n        flyweight.clientId(eventCode.id());\n        flyweight.correlationId(42);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[1.900000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/100]: \" +\n            \"typeId=3 keyBufferOffset=\" + flyweight.keyBufferOffset() + \" keyBufferLength=10 labelBufferOffset=\" +\n            flyweight.labelBufferOffset() + \" labelBufferLength=13 clientId=\" + eventCode.id() + \" correlationId=42\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_OUT_SUBSCRIPTION_READY\" })\n    void dissectCommandSubscriptionReady(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 100, () -> 1_900_000_000L);\n        final SubscriptionReadyFlyweight flyweight = new SubscriptionReadyFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.correlationId(42);\n        flyweight.channelStatusCounterId(eventCode.id());\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[1.900000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/100]: \" +\n            \"correlationId=42 channelStatusCounterId=\" + eventCode.id(),\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_OUT_COUNTER_READY\", \"CMD_OUT_ON_UNAVAILABLE_COUNTER\" })\n    void dissectCommandCounterUpdate(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 100, () -> 1_900_000_000L);\n        final CounterUpdateFlyweight flyweight = new CounterUpdateFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.correlationId(eventCode.id());\n        flyweight.counterId(18);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[1.900000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/100]: \" +\n            \"correlationId=\" + eventCode.id() + \" counterId=18\",\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_OUT_ON_CLIENT_TIMEOUT\" })\n    void dissectCommandClientTimeout(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 100, () -> 1_900_000_000L);\n        final ClientTimeoutFlyweight flyweight = new ClientTimeoutFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.clientId(eventCode.id());\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[1.900000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/100]: \" +\n            \"clientId=\" + eventCode.id(),\n            builder.toString());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"CMD_IN_TERMINATE_DRIVER\" })\n    void dissectCommandTerminateDriver(final DriverEventCode eventCode)\n    {\n        internalEncodeLogHeader(buffer, 0, eventCode.ordinal(), 100, () -> 1_900_000_000L);\n        final TerminateDriverFlyweight flyweight = new TerminateDriverFlyweight();\n        flyweight.wrap(buffer, LOG_HEADER_LENGTH);\n        flyweight.clientId(eventCode.id());\n        flyweight.tokenBuffer(newBuffer(new byte[15]), 4, 11);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[1.900000000] \" + CONTEXT + \": \" + eventCode.name() + \" [\" + eventCode.ordinal() + \"/100]: \" +\n            \"clientId=\" + eventCode.id() + \" tokenBufferLength=11\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectCommandUnknown()\n    {\n        final DriverEventCode eventCode = SEND_CHANNEL_CREATION;\n        internalEncodeLogHeader(buffer, 0, 5, 5, () -> 1_000_000_000L);\n\n        dissectCommand(eventCode, buffer, 0, builder);\n\n        assertEquals(\"[1.000000000] \" + CONTEXT + \": \" + eventCode.name() + \" [5/5]: COMMAND_UNKNOWN: \" + eventCode,\n            builder.toString());\n    }\n\n    @Test\n    void dissectUntetheredSubscriptionStateChange()\n    {\n        final int offset = 12;\n        internalEncodeLogHeader(buffer, offset, 22, 88, () -> 1_500_000_000L);\n        buffer.putLong(offset + LOG_HEADER_LENGTH, 88, LITTLE_ENDIAN);\n        buffer.putInt(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG, 123, LITTLE_ENDIAN);\n        buffer.putInt(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG + SIZE_OF_INT, 777, LITTLE_ENDIAN);\n        buffer.putStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG + 2 * SIZE_OF_INT, \"state changed\");\n\n        DriverEventDissector.dissectUntetheredSubscriptionStateChange(buffer, offset, builder);\n\n        assertEquals(\"[1.500000000] \" + CONTEXT + \": \" + UNTETHERED_SUBSCRIPTION_STATE_CHANGE.name() +\n            \" [22/88]: subscriptionId=88 streamId=123 sessionId=777 state changed\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectAddress()\n    {\n        final int offset = 24;\n        internalEncodeLogHeader(buffer, offset, 17, 27, () -> 2_500_000_000L);\n        encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH + offset, new InetSocketAddress(\"localhost\", 4848));\n\n        DriverEventDissector.dissectAddress(NAME_RESOLUTION_NEIGHBOR_ADDED, buffer, offset, builder);\n\n        assertEquals(\"[2.500000000] \" + CONTEXT + \": \" + NAME_RESOLUTION_NEIGHBOR_ADDED.name() +\n            \" [17/27]: 127.0.0.1:4848\", builder.toString());\n    }\n\n    @Test\n    void dissectIpv6Address()\n    {\n        final int offset = 24;\n        internalEncodeLogHeader(buffer, offset, 17, 27, () -> 2_500_000_000L);\n        encodeSocketAddress(\n            buffer, LOG_HEADER_LENGTH + offset, new InetSocketAddress(\"2001:0db8:85a3:0000:0000:8a2e:0370:7334\", 4848));\n\n        DriverEventDissector.dissectAddress(NAME_RESOLUTION_NEIGHBOR_ADDED, buffer, offset, builder);\n\n        assertEquals(\"[2.500000000] \" + CONTEXT + \": \" + NAME_RESOLUTION_NEIGHBOR_ADDED.name() +\n            \" [17/27]: [2001:db8:85a3:0:0:8a2e:370:7334]:4848\", builder.toString());\n    }\n\n    @Test\n    void dissectFlowControlReceiver()\n    {\n        final int offset = 24;\n        internalEncodeLogHeader(buffer, offset, 42, 48, () -> 2_500_000_000L);\n        buffer.putInt(offset + LOG_HEADER_LENGTH, 5, LITTLE_ENDIAN);\n        buffer.putLong(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, -45754449919191L, LITTLE_ENDIAN);\n        buffer.putInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT + SIZE_OF_LONG, 11, LITTLE_ENDIAN);\n        buffer.putInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2 + SIZE_OF_LONG, 4, LITTLE_ENDIAN);\n        buffer.putStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 3 + SIZE_OF_LONG, \"ABC\");\n\n        DriverEventDissector.dissectFlowControlReceiver(FLOW_CONTROL_RECEIVER_ADDED, buffer, offset, builder);\n\n        assertEquals(\"[2.500000000] \" + CONTEXT + \": \" + FLOW_CONTROL_RECEIVER_ADDED.name() +\n            \" [42/48]: receiverCount=5 receiverId=-45754449919191 sessionId=11 streamId=4 channel=ABC\",\n            builder.toString());\n    }\n\n    @Test\n    void dissectResolve() throws UnknownHostException\n    {\n        final String resolver = \"testResolver\";\n        final long durationNs = 32167;\n        final String hostname = \"localhost\";\n        final boolean isReResolution = false;\n        final InetAddress address = InetAddress.getByName(\"127.0.0.1\");\n\n        final int length = SIZE_OF_BOOLEAN + SIZE_OF_LONG + trailingStringLength(resolver, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(hostname, MAX_HOST_NAME_LENGTH) +\n            inetAddressLength(address);\n\n        DriverEventEncoder.encodeResolve(\n            buffer, 0, length, length, resolver, durationNs, hostname, isReResolution, address);\n        final StringBuilder builder = new StringBuilder();\n        DriverEventDissector.dissectResolve(buffer, 0, builder);\n\n        assertThat(builder.toString(), endsWith(\n            \"DRIVER: NAME_RESOLUTION_RESOLVE [46/46]: \" +\n            \"resolver=testResolver durationNs=32167 name=localhost isReResolution=false address=127.0.0.1\"));\n    }\n\n    @Test\n    void dissectResolveNullAddress()\n    {\n        final String resolver = \"myResolver\";\n        final long durationNs = -1;\n        final String hostname = \"some-host\";\n        final boolean isReResolution = true;\n        final InetAddress address = null;\n\n        final int length = SIZE_OF_BOOLEAN + SIZE_OF_LONG + trailingStringLength(resolver, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(hostname, MAX_HOST_NAME_LENGTH) +\n            inetAddressLength(address);\n\n        DriverEventEncoder.encodeResolve(\n            buffer, 0, length, length, resolver, durationNs, hostname, isReResolution, address);\n        final StringBuilder builder = new StringBuilder();\n        DriverEventDissector.dissectResolve(buffer, 0, builder);\n\n        assertThat(builder.toString(), endsWith(\n            \"DRIVER: NAME_RESOLUTION_RESOLVE [40/40]: \" +\n            \"resolver=myResolver durationNs=-1 name=some-host isReResolution=true address=unknown-address\"));\n    }\n\n    @Test\n    void dissectResolveWithReallyLongNames() throws UnknownHostException\n    {\n        final String longString = \"testResolver.this.is.a.really.long.string.to.force.truncation.0000000000000000000\" +\n            \"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\" +\n            \"00000000000000000000000000000000000000000000000000000000000000000000000000000000\";\n\n        final String expected = \"DRIVER: NAME_RESOLUTION_RESOLVE [537/537]: resolver=testResolver.\" +\n            \"this.is.a.really.long.string.to.force.truncation.0000000000000000000000000000000000000000000000000000000\" +\n            \"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\" +\n            \"00000000000000000000000000000000... \" +\n            \"durationNs=555 \" +\n            \"name=testResolver.this.is.a.really.long.string.to.force.truncation.0000000000000000000000000000000000000\" +\n            \"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\" +\n            \"00000000000000000000000000000000000000000000000000... \" +\n            \"isReResolution=false \" +\n            \"address=127.0.0.1\";\n\n        final InetAddress address = InetAddress.getByName(\"127.0.0.1\");\n\n        final int length = SIZE_OF_BOOLEAN + SIZE_OF_LONG + trailingStringLength(longString, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(longString, MAX_HOST_NAME_LENGTH) +\n            inetAddressLength(address);\n\n        DriverEventEncoder.encodeResolve(buffer, 0, length, length, longString, 555, longString, false, address);\n        final StringBuilder builder = new StringBuilder();\n        DriverEventDissector.dissectResolve(buffer, 0, builder);\n\n        assertThat(builder.toString(), endsWith(expected));\n    }\n\n    @Test\n    void dissectLookup()\n    {\n        final int offset = 48;\n        final String resolver = \"xyz\";\n        final long durationNs = 32167;\n        final String name = \"localhost:7777\";\n        final boolean isReLookup = false;\n        final String resolvedName = \"test:1234\";\n\n        final int length = SIZE_OF_BOOLEAN + SIZE_OF_LONG +\n            trailingStringLength(resolver, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(name, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(resolvedName, MAX_HOST_NAME_LENGTH);\n\n        DriverEventEncoder.encodeLookup(\n            buffer, offset, length, length, resolver, durationNs, name, isReLookup, resolvedName);\n        final StringBuilder builder = new StringBuilder();\n        DriverEventDissector.dissectLookup(buffer, offset, builder);\n\n        assertThat(builder.toString(), endsWith(\n            \"DRIVER: NAME_RESOLUTION_LOOKUP [47/47]: \" +\n            \"resolver=xyz durationNs=32167 name=localhost:7777 isReLookup=false resolvedName=test:1234\"));\n    }\n\n    @Test\n    void dissectHostName()\n    {\n        final int offset = 8;\n        final long durationNs = 32167;\n        final String hostName = \"some.funky.host.name\";\n\n        final int length = SIZE_OF_LONG + trailingStringLength(hostName, MAX_HOST_NAME_LENGTH);\n\n        DriverEventEncoder.encodeHostName(buffer, offset, length, length, durationNs, hostName);\n        final StringBuilder builder = new StringBuilder();\n        DriverEventDissector.dissectHostName(buffer, offset, builder);\n\n        assertThat(builder.toString(), endsWith(\n            \"DRIVER: NAME_RESOLUTION_HOST_NAME [32/32]: durationNs=32167 hostName=some.funky.host.name\"));\n    }\n\n    @Test\n    void dissectPublicationRevoke()\n    {\n        final int offset = 0;\n        final long revokePos = 1234;\n        final int sessionId = 4;\n        final int streamId = 99;\n        final String channel = \"excellent.channel\";\n\n        final int length = SIZE_OF_LONG + (SIZE_OF_INT * 2) + trailingStringLength(channel, MAX_CHANNEL_URI_LENGTH);\n\n        DriverEventEncoder.encodePublicationRevoke(\n            buffer, offset, length, length, revokePos, sessionId, streamId, channel);\n        final StringBuilder builder = new StringBuilder();\n        DriverEventDissector.dissectPublicationRevoke(buffer, offset, builder);\n\n        assertThat(builder.toString(), endsWith(\n            \"DRIVER: PUBLICATION_REVOKE [37/37]: revokedPos=1234 sessionId=4 streamId=99 channel=excellent.channel\"));\n    }\n\n    @Test\n    void dissectPublicationImageRevoke()\n    {\n        final int offset = 0;\n        final long revokePos = 1234;\n        final int sessionId = 4;\n        final int streamId = 99;\n        final String channel = \"excellent.channel\";\n\n        final int length = SIZE_OF_LONG + (SIZE_OF_INT * 2) + trailingStringLength(channel, MAX_CHANNEL_URI_LENGTH);\n\n        DriverEventEncoder.encodePublicationImageRevoke(\n            buffer, offset, length, length, revokePos, sessionId, streamId, channel);\n        final StringBuilder builder = new StringBuilder();\n        DriverEventDissector.dissectPublicationImageRevoke(buffer, offset, builder);\n\n        assertThat(builder.toString(), endsWith(\n            \"DRIVER: PUBLICATION_IMAGE_REVOKE [37/37]: \" +\n            \"revokedPos=1234 sessionId=4 streamId=99 channel=excellent.channel\"));\n    }\n\n    private DirectBuffer newBuffer(final byte[] bytes)\n    {\n        final UnsafeBuffer buffer = new UnsafeBuffer(allocate(bytes.length));\n        buffer.putBytes(0, bytes);\n        return buffer;\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/DriverEventEncoderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.cluster.codecs.ClusterTimeUnit;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.temporal.ChronoField;\n\nimport static io.aeron.agent.CommonEventEncoder.*;\nimport static io.aeron.agent.DriverEventEncoder.*;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.util.Arrays.fill;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass DriverEventEncoderTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MAX_EVENT_LENGTH * 10]);\n\n    @Test\n    void encodePublicationRemovalShouldWriteChannelLast()\n    {\n        final int offset = 10;\n        final String channel = \"aeron:udp?endpoint=224.10.9.8\";\n        final int sessionId = 42;\n        final int streamId = 5;\n        final int captureLength = 3 * SIZE_OF_INT + channel.length();\n\n        encodePublicationRemoval(buffer, offset, captureLength, captureLength, channel, sessionId, streamId);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(captureLength, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getInt(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(streamId, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(channel, buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodePublicationRemovalShouldTruncateChannelIfItExceedsMaxMessageLength()\n    {\n        final int offset = 121;\n        final char[] data = new char[MAX_EVENT_LENGTH];\n        fill(data, 'z');\n        final String channel = new String(data);\n        final int length = data.length + 3 * SIZE_OF_INT;\n        final int captureLength = captureLength(length);\n\n        encodePublicationRemoval(buffer, offset, captureLength, length, channel, 1, -1);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(1, buffer.getInt(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(-1, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(channel.substring(0, captureLength - 3 * SIZE_OF_INT - 3) + \"...\",\n            buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodeSubscriptionRemovalShouldWriteChannelLast()\n    {\n        final int offset = 0;\n        final String channel = \"aeron:udp?endpoint=224.10.9.8\";\n        final int streamId = 13;\n        final long id = Long.MAX_VALUE;\n        final int captureLength = 2 * SIZE_OF_INT + SIZE_OF_LONG + channel.length();\n\n        encodeSubscriptionRemoval(buffer, offset, captureLength, captureLength, channel, streamId, id);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(captureLength, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(streamId, buffer.getInt(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(id, buffer.getLong(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(channel,\n            buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT + SIZE_OF_LONG, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodeSubscriptionRemovalShouldTruncateChannelIfItExceedsMaxMessageLength()\n    {\n        final char[] data = new char[MAX_EVENT_LENGTH * 3 + 5];\n        fill(data, 'a');\n        final int offset = 0;\n        final int length = SIZE_OF_INT * 2 + SIZE_OF_LONG + data.length;\n        final int captureLength = captureLength(length);\n        final String channel = new String(data);\n        final int streamId = 1;\n        final long id = -1;\n\n        encodeSubscriptionRemoval(buffer, offset, captureLength, length, channel, streamId, id);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(streamId, buffer.getInt(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(id, buffer.getLong(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(channel.substring(0, captureLength - SIZE_OF_INT * 2 - SIZE_OF_LONG - 3) + \"...\",\n            buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT + SIZE_OF_LONG, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodeImageRemovalShouldWriteChannelLast()\n    {\n        final int offset = 0;\n        final String channel = \"aeron:udp?endpoint=224.10.9.8\";\n        final int sessionId = 13;\n        final int streamId = 42;\n        final long id = Long.MAX_VALUE;\n        final int captureLength = 3 * SIZE_OF_INT + SIZE_OF_LONG + channel.length();\n\n        encodeImageRemoval(buffer, offset, captureLength, captureLength, channel, sessionId, streamId, id);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(captureLength, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getInt(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(streamId, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(id, buffer.getLong(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(channel,\n            buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2 + SIZE_OF_LONG, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodeImageRemovalShouldTruncateChannelIfItExceedsMaxMessageLength()\n    {\n        final char[] data = new char[MAX_EVENT_LENGTH + 8];\n        fill(data, 'a');\n        final int offset = 0;\n        final int length = data.length + SIZE_OF_LONG + SIZE_OF_INT * 3;\n        final int captureLength = captureLength(length);\n        final String channel = new String(data);\n        final int sessionId = -1;\n        final int streamId = 1;\n        final long id = 0;\n\n        encodeImageRemoval(buffer, offset, captureLength, length, channel, sessionId, streamId, id);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getInt(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(streamId, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(id, buffer.getLong(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(channel.substring(0, captureLength - SIZE_OF_LONG - SIZE_OF_INT * 3 - 3) + \"...\",\n            buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2 + SIZE_OF_LONG, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void untetheredSubscriptionStateChangeLengthComputesLengthBasedOnProvidedState()\n    {\n        final ClusterTimeUnit from = ClusterTimeUnit.MILLIS;\n        final ClusterTimeUnit to = ClusterTimeUnit.NANOS;\n\n        assertEquals(stateTransitionStringLength(from, to) + SIZE_OF_LONG + 2 * SIZE_OF_INT,\n            untetheredSubscriptionStateChangeLength(from, to));\n    }\n\n    @Test\n    void encodeUntetheredSubscriptionStateChangeShouldEncodeStateChangeLast()\n    {\n        final int offset = 0;\n        final ChronoField from = ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH;\n        final ChronoField to = ChronoField.AMPM_OF_DAY;\n        final int length = untetheredSubscriptionStateChangeLength(from, to);\n        final int captureLength = captureLength(length);\n        final long subscriptionId = 1_010_010_000_010L;\n        final int sessionId = 42;\n        final int streamId = Integer.MIN_VALUE;\n\n        encodeUntetheredSubscriptionStateChange(\n            buffer, offset, captureLength, length, from, to, subscriptionId, streamId, sessionId);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(subscriptionId, buffer.getLong(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(streamId, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(from.name() + STATE_SEPARATOR + to.name(),\n            buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodeFlowControlReceiverShouldWriteChannelLast()\n    {\n        final int offset = 48;\n        final long receiverId = 1947384623864823283L;\n        final int sessionId = 219;\n        final int streamId = 3;\n        final String channel = \"my channel\";\n        final int receiverCount = 17;\n        final int length = 4 * SIZE_OF_INT + SIZE_OF_LONG + channel.length();\n        final int captureLength = captureLength(length);\n\n        encodeFlowControlReceiver(\n            buffer, offset, captureLength, length, receiverId, sessionId, streamId, channel, receiverCount);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(receiverCount, buffer.getInt(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(receiverId, buffer.getLong(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(streamId,\n            buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2 + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(channel,\n            buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 3 + SIZE_OF_LONG, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodeFlowControlReceiverShouldTruncateChannelChannelIfTooLong()\n    {\n        final char[] data = new char[MAX_EVENT_LENGTH + 11];\n        fill(data, 'x');\n        final int offset = 16;\n        final long receiverId = Long.MIN_VALUE;\n        final int sessionId = 42;\n        final int streamId = 0;\n        final String channel = new String(data);\n        final int receiverCount = 0;\n        final int length = 4 * SIZE_OF_INT + SIZE_OF_LONG + data.length;\n        final int captureLength = captureLength(length);\n\n        encodeFlowControlReceiver(\n            buffer, offset, captureLength, length, receiverId, sessionId, streamId, channel, receiverCount);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(length, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertNotEquals(0, buffer.getLong(offset + SIZE_OF_INT * 2, LITTLE_ENDIAN));\n        assertEquals(receiverCount, buffer.getInt(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(receiverId, buffer.getLong(offset + LOG_HEADER_LENGTH + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(streamId,\n            buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2 + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(channel.substring(0, captureLength - SIZE_OF_LONG - SIZE_OF_INT * 4 - 3) + \"...\",\n            buffer.getStringAscii(offset + LOG_HEADER_LENGTH + SIZE_OF_INT * 3 + SIZE_OF_LONG, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodePublicationRevokeShouldWriteChannelLast()\n    {\n        final int offset = 10;\n        final String channel = \"aeron:udp?endpoint=224.10.9.8\";\n        final long revokePos = 999;\n        final int sessionId = 42;\n        final int streamId = 5;\n        final int captureLength = SIZE_OF_LONG + (3 * SIZE_OF_INT) + channel.length();\n\n        encodePublicationRevoke(buffer, offset, captureLength, captureLength, revokePos, sessionId, streamId, channel);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(captureLength, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(revokePos, buffer.getLong(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(streamId, buffer.getInt(\n            offset + LOG_HEADER_LENGTH + SIZE_OF_LONG + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(channel, buffer.getStringAscii(\n            offset + LOG_HEADER_LENGTH + SIZE_OF_LONG + (SIZE_OF_INT * 2), LITTLE_ENDIAN));\n    }\n\n    @Test\n    void encodePublicationImageRevokeShouldWriteChannelLast()\n    {\n        final int offset = 10;\n        final String channel = \"aeron:udp?endpoint=224.10.9.8\";\n        final long revokePos = 999;\n        final int sessionId = 42;\n        final int streamId = 5;\n        final int captureLength = SIZE_OF_LONG + (3 * SIZE_OF_INT) + channel.length();\n\n        encodePublicationImageRevoke(\n            buffer, offset, captureLength, captureLength, revokePos, sessionId, streamId, channel);\n\n        assertEquals(captureLength, buffer.getInt(offset, LITTLE_ENDIAN));\n        assertEquals(captureLength, buffer.getInt(offset + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(revokePos, buffer.getLong(offset + LOG_HEADER_LENGTH, LITTLE_ENDIAN));\n        assertEquals(sessionId, buffer.getInt(offset + LOG_HEADER_LENGTH + SIZE_OF_LONG, LITTLE_ENDIAN));\n        assertEquals(streamId, buffer.getInt(\n            offset + LOG_HEADER_LENGTH + SIZE_OF_LONG + SIZE_OF_INT, LITTLE_ENDIAN));\n        assertEquals(channel, buffer.getStringAscii(\n            offset + LOG_HEADER_LENGTH + SIZE_OF_LONG + (SIZE_OF_INT * 2), LITTLE_ENDIAN));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/DriverEventLoggerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.UnknownHostException;\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.agent.AgentTests.verifyLogHeader;\nimport static io.aeron.agent.CommonEventEncoder.*;\nimport static io.aeron.agent.DriverEventCode.*;\nimport static io.aeron.agent.DriverEventEncoder.untetheredSubscriptionStateChangeLength;\nimport static io.aeron.agent.DriverEventLogger.MAX_HOST_NAME_LENGTH;\nimport static io.aeron.agent.DriverEventLogger.toEventCodeId;\nimport static io.aeron.agent.EventConfiguration.MAX_EVENT_LENGTH;\nimport static io.aeron.test.Tests.generateStringWithSuffix;\nimport static java.nio.ByteBuffer.allocate;\nimport static java.nio.ByteBuffer.allocateDirect;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.util.Arrays.fill;\nimport static org.agrona.BitUtil.*;\nimport static org.agrona.concurrent.ringbuffer.RecordDescriptor.*;\nimport static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TAIL_POSITION_OFFSET;\nimport static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TRAILER_LENGTH;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass DriverEventLoggerTest\n{\n    private static final int CAPACITY = 32 * 1024;\n    private final UnsafeBuffer logBuffer = new UnsafeBuffer(allocateDirect(CAPACITY + TRAILER_LENGTH));\n    private final DriverEventLogger logger = new DriverEventLogger(new ManyToOneRingBuffer(logBuffer));\n    private final UnsafeBuffer buffer = new UnsafeBuffer(allocate(MAX_EVENT_LENGTH * 3));\n\n    @AfterEach\n    void after()\n    {\n        DriverComponentLogger.ENABLED_EVENTS.clear();\n        EventConfiguration.EVENT_RING_BUFFER.unblock();\n    }\n\n    @ParameterizedTest\n    @EnumSource(DriverEventCode.class)\n    void toEventCodeIdComputesEventId(final DriverEventCode eventCode)\n    {\n        assertEquals(eventCode.id(), toEventCodeId(eventCode));\n    }\n\n    @Test\n    void logIsNoOpIfEventIsNotEnabled()\n    {\n        buffer.setMemory(20, 100, (byte)5);\n\n        logger.log(CMD_OUT_ERROR, buffer, 20, 100);\n\n        assertEquals(0, logBuffer.getInt(lengthOffset(0), LITTLE_ENDIAN));\n    }\n\n    @Test\n    void log()\n    {\n        final DriverEventCode eventCode = CMD_IN_TERMINATE_DRIVER;\n        DriverComponentLogger.ENABLED_EVENTS.add(eventCode);\n        final int recordOffset = align(13, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        final int length = 100;\n        final int srcOffset = 20;\n        buffer.setMemory(srcOffset, length, (byte)5);\n\n        logger.log(eventCode, buffer, srcOffset, length);\n\n        verifyLogHeader(logBuffer, recordOffset, toEventCodeId(eventCode), length, length);\n        for (int i = 0; i < length; i++)\n        {\n            assertEquals(5, logBuffer.getByte(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + i)));\n        }\n    }\n\n    @Test\n    void logFrameIn()\n    {\n        final int recordOffset = align(100, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        final int length = 10_000;\n        final int captureLength = MAX_CAPTURE_LENGTH;\n        final int srcOffset = 4;\n        buffer.setMemory(srcOffset, MAX_CAPTURE_LENGTH, (byte)3);\n        final int encodedSocketLength = 12;\n\n        logger.logFrameIn(buffer, srcOffset, length, new InetSocketAddress(\"localhost\", 5555));\n\n        verifyLogHeader(logBuffer, recordOffset, toEventCodeId(FRAME_IN), captureLength, length + encodedSocketLength);\n        assertEquals(5555, logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(srcOffset,\n            logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT), LITTLE_ENDIAN));\n\n        for (int i = 0; i < captureLength - encodedSocketLength; i++)\n        {\n            assertEquals(3,\n                logBuffer.getByte(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + encodedSocketLength + i)));\n        }\n    }\n\n    @Test\n    void logFrameOut()\n    {\n        final int recordOffset = 24;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        final ByteBuffer byteBuffer = buffer.byteBuffer();\n        byteBuffer.position(8);\n        final byte[] bytes = new byte[32];\n        fill(bytes, (byte)-1);\n        byteBuffer.put(bytes);\n        byteBuffer.flip().position(10).limit(38);\n        final int encodedSocketLength = 12;\n        final int length = byteBuffer.remaining() + encodedSocketLength;\n        final int arrayCaptureLength = length - encodedSocketLength;\n\n        logger.logFrameOut(byteBuffer, new InetSocketAddress(\"localhost\", 3232));\n\n        verifyLogHeader(logBuffer, recordOffset, toEventCodeId(FRAME_OUT), length, length);\n        assertEquals(3232, logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(4,\n            logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT), LITTLE_ENDIAN));\n\n        for (int i = 0; i < arrayCaptureLength; i++)\n        {\n            assertEquals(-1,\n                logBuffer.getByte(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + encodedSocketLength + i)));\n        }\n    }\n\n    @Test\n    void logString()\n    {\n        final int recordOffset = align(100, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        final DriverEventCode eventCode = CMD_IN_ADD_PUBLICATION;\n        final String value = \"abc\";\n        final int captureLength = value.length() + SIZE_OF_INT;\n\n        logger.logString(eventCode, value);\n\n        verifyLogHeader(logBuffer, recordOffset, toEventCodeId(eventCode), captureLength, captureLength);\n        assertEquals(value,\n            logBuffer.getStringAscii(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n    }\n\n    @Test\n    void logPublicationRemoval()\n    {\n        final int recordOffset = align(1111, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        final String uri = \"uri\";\n        final int sessionId = 42;\n        final int streamId = 19;\n        final int captureLength = uri.length() + SIZE_OF_INT * 3;\n\n        logger.logPublicationRemoval(uri, sessionId, streamId);\n\n        verifyLogHeader(\n            logBuffer, recordOffset, toEventCodeId(REMOVE_PUBLICATION_CLEANUP), captureLength, captureLength);\n        assertEquals(sessionId, logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(streamId,\n            logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT), LITTLE_ENDIAN));\n        assertEquals(uri,\n            logBuffer.getStringAscii(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2),\n            LITTLE_ENDIAN));\n    }\n\n    @Test\n    void logSubscriptionRemoval()\n    {\n        final int recordOffset = align(131, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        final String uri = \"uri\";\n        final int streamId = 42;\n        final long id = 19;\n        final int captureLength = uri.length() + SIZE_OF_INT * 2 + SIZE_OF_LONG;\n\n        logger.logSubscriptionRemoval(uri, streamId, id);\n\n        verifyLogHeader(\n            logBuffer, recordOffset, toEventCodeId(REMOVE_SUBSCRIPTION_CLEANUP), captureLength, captureLength);\n        assertEquals(streamId, logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(id,\n            logBuffer.getLong(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT), LITTLE_ENDIAN));\n        assertEquals(uri,\n            logBuffer.getStringAscii(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT + SIZE_OF_LONG),\n            LITTLE_ENDIAN));\n    }\n\n    @Test\n    void logImageRemoval()\n    {\n        final int recordOffset = align(192, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        final String uri = \"uri\";\n        final int sessionId = 8;\n        final int streamId = 61;\n        final long id = 19;\n        final int captureLength = uri.length() + SIZE_OF_INT * 3 + SIZE_OF_LONG;\n\n        logger.logImageRemoval(uri, sessionId, streamId, id);\n\n        verifyLogHeader(\n            logBuffer, recordOffset, toEventCodeId(REMOVE_IMAGE_CLEANUP), captureLength, captureLength);\n        assertEquals(sessionId, logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(streamId,\n            logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT), LITTLE_ENDIAN));\n        assertEquals(id,\n            logBuffer.getLong(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2), LITTLE_ENDIAN));\n        assertEquals(uri, logBuffer.getStringAscii(\n            encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2 + SIZE_OF_LONG), LITTLE_ENDIAN));\n    }\n\n    @Test\n    void logUntetheredSubscriptionStateChange()\n    {\n        final int recordOffset = align(192, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        final TimeUnit from = TimeUnit.DAYS;\n        final TimeUnit to = TimeUnit.NANOSECONDS;\n        final long subscriptionId = Long.MIN_VALUE;\n        final int streamId = 61;\n        final int sessionId = 8;\n        final int captureLength = captureLength(untetheredSubscriptionStateChangeLength(from, to));\n\n        logger.logUntetheredSubscriptionStateChange(from, to, subscriptionId, streamId, sessionId);\n\n        verifyLogHeader(\n            logBuffer, recordOffset, toEventCodeId(UNTETHERED_SUBSCRIPTION_STATE_CHANGE), captureLength, captureLength);\n        assertEquals(subscriptionId,\n            logBuffer.getLong(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(streamId,\n            logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_LONG), LITTLE_ENDIAN));\n        assertEquals(sessionId,\n            logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_LONG + SIZE_OF_INT),\n            LITTLE_ENDIAN));\n        assertEquals(from.name() + STATE_SEPARATOR + to.name(), logBuffer.getStringAscii(\n            encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT * 2 + SIZE_OF_LONG), LITTLE_ENDIAN));\n    }\n\n    @Test\n    void logAddress()\n    {\n        final int recordOffset = 64;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        final DriverEventCode eventCode = NAME_RESOLUTION_NEIGHBOR_REMOVED;\n        final int captureLength = 12;\n\n        logger.logAddress(eventCode, new InetSocketAddress(\"localhost\", 5656));\n\n        verifyLogHeader(logBuffer, recordOffset, toEventCodeId(eventCode), captureLength, captureLength);\n        assertEquals(5656, logBuffer.getInt(encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH), LITTLE_ENDIAN));\n        assertEquals(4, logBuffer.getInt(\n            encodedMsgOffset(recordOffset + LOG_HEADER_LENGTH + SIZE_OF_INT), LITTLE_ENDIAN));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { false, true })\n    void logResolve(final boolean isReResolution) throws UnknownHostException\n    {\n        final int recordOffset = 64;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n\n        final String resolverName = \"test\";\n        final long durationNs = TimeUnit.DAYS.toNanos(1);\n        final String hostName = generateStringWithSuffix(\"very-l\", \"0\", 1000);\n        final String expectedHostName = hostName.substring(0, MAX_HOST_NAME_LENGTH - 3) + \"...\";\n        final InetAddress address = InetAddress.getLocalHost();\n        final int captureLength = SIZE_OF_BOOLEAN + SIZE_OF_LONG +\n            trailingStringLength(resolverName, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(hostName, MAX_HOST_NAME_LENGTH) +\n            inetAddressLength(address);\n\n        logger.logResolve(resolverName, durationNs, hostName, isReResolution, address);\n\n        verifyLogHeader(logBuffer, recordOffset, toEventCodeId(NAME_RESOLUTION_RESOLVE), captureLength, captureLength);\n\n        int index = encodedMsgOffset(recordOffset) + LOG_HEADER_LENGTH;\n\n        assertEquals(isReResolution, 1 == logBuffer.getByte(index));\n        index += SIZE_OF_BYTE;\n\n        assertEquals(durationNs, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n\n        assertEquals(resolverName, logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT + resolverName.length();\n\n        assertEquals(expectedHostName, logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT + expectedHostName.length();\n\n        final byte[] addressBytes = address.getAddress();\n        assertEquals(addressBytes.length, logBuffer.getInt(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT;\n        for (int i = 0; i < addressBytes.length; i++)\n        {\n            assertEquals(addressBytes[i], logBuffer.getByte(index + i));\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { false, true })\n    void logLookup(final boolean isReLookup)\n    {\n        final int recordOffset = align(30, ALIGNMENT);\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n\n        final String resolverName = generateStringWithSuffix(\"my-resolver\", \"*\", 1000);\n        final String expectedResolverName = resolverName.substring(0, MAX_HOST_NAME_LENGTH - 3) + \"...\";\n        final long durationNs = TimeUnit.HOURS.toNanos(3);\n        final String name = \"abc.example.com:5555\";\n        final String resolvedName = \"corporate.fancy.address.returned:5555\";\n        final int captureLength = SIZE_OF_BOOLEAN + SIZE_OF_LONG +\n            trailingStringLength(resolverName, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(name, MAX_HOST_NAME_LENGTH) +\n            trailingStringLength(resolvedName, MAX_HOST_NAME_LENGTH);\n\n        logger.logLookup(resolverName, durationNs, name, isReLookup, resolvedName);\n\n        verifyLogHeader(logBuffer, recordOffset, toEventCodeId(NAME_RESOLUTION_LOOKUP), captureLength, captureLength);\n\n        int index = encodedMsgOffset(recordOffset) + LOG_HEADER_LENGTH;\n\n        assertEquals(isReLookup ? 1 : 0, logBuffer.getByte(index));\n        index += SIZE_OF_BOOLEAN;\n\n        assertEquals(durationNs, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n\n        assertEquals(expectedResolverName, logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT + expectedResolverName.length();\n\n        assertEquals(name, logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n        index += SIZE_OF_INT + name.length();\n\n        assertEquals(resolvedName, logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n    }\n\n    @Test\n    void logHost()\n    {\n        final int recordOffset = 64;\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n\n        final long durationNs = TimeUnit.DAYS.toNanos(1);\n        final String hostName = generateStringWithSuffix(\"host-name\", \"e\", 1000);\n        final String expectedHostName = hostName.substring(0, MAX_HOST_NAME_LENGTH - 3) + \"...\";\n        final int captureLength = SIZE_OF_LONG +\n            trailingStringLength(hostName, MAX_HOST_NAME_LENGTH);\n\n        logger.logHostName(durationNs, hostName);\n\n        verifyLogHeader(\n            logBuffer, recordOffset, toEventCodeId(NAME_RESOLUTION_HOST_NAME), captureLength, captureLength);\n\n        int index = encodedMsgOffset(recordOffset) + LOG_HEADER_LENGTH;\n\n        assertEquals(durationNs, logBuffer.getLong(index, LITTLE_ENDIAN));\n        index += SIZE_OF_LONG;\n\n        assertEquals(expectedHostName, logBuffer.getStringAscii(index, LITTLE_ENDIAN));\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, names = { \"NAK_SENT\", \"NAK_RECEIVED\" })\n    void logNakMessage(final DriverEventCode eventCode)\n    {\n        final InetSocketAddress inetSocketAddress = new InetSocketAddress(\"192.168.1.1\", 10001);\n\n        final int sessionId = 9821374;\n        final int streamId = 988234;\n        final int termId = 89324;\n        final int termOffset = 9862314;\n        final int length = 1239;\n        final String channel =\n            \"aeron:udp?endpoint=localhost:10000|term-length=1m|init-term-id=0|term-id=0|term-offset=0\";\n        final int recordOffset = 64;\n        final int captureLength = socketAddressLength(inetSocketAddress) + (6 * SIZE_OF_INT) + channel.length();\n\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        logger.logNakMessage(eventCode, inetSocketAddress, sessionId, streamId, termId, termOffset, length, channel);\n        verifyLogHeader(\n            logBuffer, recordOffset, toEventCodeId(eventCode), captureLength, captureLength);\n\n        final StringBuilder sb = new StringBuilder();\n        DriverEventDissector.dissectNak(eventCode, logBuffer, encodedMsgOffset(recordOffset), sb);\n\n        final String expectedMessagePattern =\n            \"\\\\[[0-9]+\\\\.[0-9]+] DRIVER: \" + eventCode + \" \\\\[124/124]: address=192.168.1.1:10001 \" +\n            \"sessionId=9821374 streamId=988234 termId=89324 termOffset=9862314 length=1239 channel=\" +\n            \"aeron:udp\\\\?endpoint=localhost:10000\\\\|term-length=1m\\\\|init-term-id=0\\\\|term-id=0\\\\|term-offset=0\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n\n    @Test\n    void logResend()\n    {\n        final int sessionId = 9821374;\n        final int streamId = 988234;\n        final int termId = 89324;\n        final int termOffset = 9862314;\n        final int length = 1239;\n        final int recordOffset = 64;\n        final String channel =\n            \"aeron:udp?endpoint=localhost:10000|term-length=1m|init-term-id=0|term-id=0|term-offset=0\";\n        final int captureLength = 6 * SIZE_OF_INT + channel.length();\n\n        logBuffer.putLong(CAPACITY + TAIL_POSITION_OFFSET, recordOffset);\n        logger.logResend(sessionId, streamId, termId, termOffset, length, channel);\n        verifyLogHeader(\n            logBuffer, recordOffset, toEventCodeId(RESEND), captureLength, captureLength);\n\n        final StringBuilder sb = new StringBuilder();\n        DriverEventDissector.dissectResend(logBuffer, encodedMsgOffset(recordOffset), sb);\n\n        final String expectedMessagePattern =\n            \"\\\\[[0-9]+\\\\.[0-9]+] DRIVER: RESEND \\\\[112/112]: sessionId=9821374 streamId=988234 termId=89324 \" +\n            \"termOffset=9862314 length=1239 \" +\n            \"channel=\" +\n            \"aeron:udp\\\\?endpoint=localhost:10000\\\\|term-length=1m\\\\|init-term-id=0\\\\|term-id=0\\\\|term-offset=0\";\n\n        assertThat(sb.toString(), Matchers.matchesPattern(expectedMessagePattern));\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/DriverLoggingAgentTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.Aeron;\nimport io.aeron.AvailableImageHandler;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.UnavailableImageHandler;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.Tests;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.MessageHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport java.util.EnumSet;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Supplier;\n\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.agent.DriverEventCode.CMD_IN_ADD_EXCLUSIVE_PUBLICATION;\nimport static io.aeron.agent.DriverEventCode.CMD_IN_ADD_PUBLICATION;\nimport static io.aeron.agent.DriverEventCode.CMD_IN_ADD_SUBSCRIPTION;\nimport static io.aeron.agent.DriverEventCode.CMD_IN_CLIENT_CLOSE;\nimport static io.aeron.agent.DriverEventCode.CMD_IN_REMOVE_PUBLICATION;\nimport static io.aeron.agent.DriverEventCode.CMD_IN_REMOVE_SUBSCRIPTION;\nimport static io.aeron.agent.DriverEventCode.CMD_OUT_AVAILABLE_IMAGE;\nimport static io.aeron.agent.DriverEventCode.CMD_OUT_COUNTER_READY;\nimport static io.aeron.agent.DriverEventCode.CMD_OUT_EXCLUSIVE_PUBLICATION_READY;\nimport static io.aeron.agent.DriverEventCode.CMD_OUT_ON_OPERATION_SUCCESS;\nimport static io.aeron.agent.DriverEventCode.CMD_OUT_ON_UNAVAILABLE_COUNTER;\nimport static io.aeron.agent.DriverEventCode.CMD_OUT_PUBLICATION_READY;\nimport static io.aeron.agent.DriverEventCode.CMD_OUT_SUBSCRIPTION_READY;\nimport static io.aeron.agent.DriverEventCode.FLOW_CONTROL_RECEIVER_ADDED;\nimport static io.aeron.agent.DriverEventCode.FLOW_CONTROL_RECEIVER_REMOVED;\nimport static io.aeron.agent.DriverEventCode.FRAME_IN;\nimport static io.aeron.agent.DriverEventCode.FRAME_OUT;\nimport static io.aeron.agent.DriverEventCode.PUBLICATION_IMAGE_REVOKE;\nimport static io.aeron.agent.DriverEventCode.PUBLICATION_REVOKE;\nimport static io.aeron.agent.DriverEventCode.RECEIVE_CHANNEL_CLOSE;\nimport static io.aeron.agent.DriverEventCode.RECEIVE_CHANNEL_CREATION;\nimport static io.aeron.agent.DriverEventCode.REMOVE_IMAGE_CLEANUP;\nimport static io.aeron.agent.DriverEventCode.REMOVE_PUBLICATION_CLEANUP;\nimport static io.aeron.agent.DriverEventCode.SEND_CHANNEL_CLOSE;\nimport static io.aeron.agent.DriverEventCode.SEND_CHANNEL_CREATION;\nimport static io.aeron.agent.EventConfiguration.EVENT_READER_FRAME_LIMIT;\nimport static io.aeron.agent.EventConfiguration.EVENT_RING_BUFFER;\nimport static java.util.Collections.synchronizedSet;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.params.provider.EnumSource.Mode.INCLUDE;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass DriverLoggingAgentTest\n{\n    private static final String NETWORK_CHANNEL =\n        \"aeron:udp?control-mode=dynamic|control=localhost:20550|fc=min,t:1ns\";\n    private static final int STREAM_ID = 1777;\n\n    private static final Set<DriverEventCode> WAIT_LIST = synchronizedSet(EnumSet.noneOf(DriverEventCode.class));\n\n    private final AvailableImageHandler availableImageHandler = mock(AvailableImageHandler.class);\n    private final UnavailableImageHandler unavailableImageHandler = mock(UnavailableImageHandler.class);\n\n    @AfterEach\n    void after()\n    {\n        AgentTests.stopLogging();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void logAllNetworkChannel()\n    {\n        testLogMediaDriverEvents(NETWORK_CHANNEL, \"all\", EnumSet.of(\n            FRAME_IN,\n            FRAME_OUT,\n            CMD_IN_ADD_PUBLICATION,\n            CMD_IN_REMOVE_PUBLICATION,\n            CMD_IN_ADD_SUBSCRIPTION,\n            CMD_IN_REMOVE_SUBSCRIPTION,\n            CMD_OUT_PUBLICATION_READY,\n            CMD_OUT_AVAILABLE_IMAGE,\n            CMD_OUT_ON_OPERATION_SUCCESS,\n            REMOVE_PUBLICATION_CLEANUP,\n            REMOVE_IMAGE_CLEANUP,\n            SEND_CHANNEL_CREATION,\n            RECEIVE_CHANNEL_CREATION,\n            SEND_CHANNEL_CLOSE,\n            RECEIVE_CHANNEL_CLOSE,\n            CMD_OUT_SUBSCRIPTION_READY,\n            CMD_OUT_ON_UNAVAILABLE_COUNTER,\n            CMD_OUT_COUNTER_READY,\n            CMD_IN_CLIENT_CLOSE,\n            FLOW_CONTROL_RECEIVER_ADDED,\n            FLOW_CONTROL_RECEIVER_REMOVED));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void logAllExclusivePublicationNetworkChannel()\n    {\n        testLogExclusivePublicationMediaDriverEvents(NETWORK_CHANNEL, \"all\", EnumSet.of(\n            FRAME_IN,\n            FRAME_OUT,\n            CMD_IN_ADD_EXCLUSIVE_PUBLICATION,\n            CMD_IN_REMOVE_PUBLICATION,\n            CMD_IN_ADD_SUBSCRIPTION,\n            CMD_IN_REMOVE_SUBSCRIPTION,\n            CMD_OUT_EXCLUSIVE_PUBLICATION_READY,\n            CMD_OUT_AVAILABLE_IMAGE,\n            CMD_OUT_ON_OPERATION_SUCCESS,\n            REMOVE_PUBLICATION_CLEANUP,\n            REMOVE_IMAGE_CLEANUP,\n            SEND_CHANNEL_CREATION,\n            RECEIVE_CHANNEL_CREATION,\n            SEND_CHANNEL_CLOSE,\n            RECEIVE_CHANNEL_CLOSE,\n            CMD_OUT_SUBSCRIPTION_READY,\n            CMD_OUT_ON_UNAVAILABLE_COUNTER,\n            CMD_OUT_COUNTER_READY,\n            CMD_IN_CLIENT_CLOSE,\n            FLOW_CONTROL_RECEIVER_ADDED,\n            FLOW_CONTROL_RECEIVER_REMOVED,\n            PUBLICATION_REVOKE,\n            PUBLICATION_IMAGE_REVOKE));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void logAllIpcChannel()\n    {\n        testLogMediaDriverEvents(IPC_CHANNEL, \"all\", EnumSet.of(\n            CMD_IN_ADD_PUBLICATION,\n            CMD_IN_REMOVE_PUBLICATION,\n            CMD_IN_ADD_SUBSCRIPTION,\n            CMD_IN_REMOVE_SUBSCRIPTION,\n            CMD_OUT_PUBLICATION_READY,\n            CMD_OUT_AVAILABLE_IMAGE,\n            CMD_OUT_ON_OPERATION_SUCCESS,\n            REMOVE_PUBLICATION_CLEANUP,\n            CMD_OUT_SUBSCRIPTION_READY,\n            CMD_OUT_COUNTER_READY,\n            CMD_OUT_ON_UNAVAILABLE_COUNTER,\n            CMD_IN_CLIENT_CLOSE));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void logAllExclusivePublicationIpcChannel()\n    {\n        testLogExclusivePublicationMediaDriverEvents(IPC_CHANNEL, \"all\", EnumSet.of(\n            CMD_IN_ADD_EXCLUSIVE_PUBLICATION,\n            CMD_IN_REMOVE_PUBLICATION,\n            CMD_IN_ADD_SUBSCRIPTION,\n            CMD_IN_REMOVE_SUBSCRIPTION,\n            CMD_OUT_EXCLUSIVE_PUBLICATION_READY,\n            CMD_OUT_AVAILABLE_IMAGE,\n            CMD_OUT_ON_OPERATION_SUCCESS,\n            REMOVE_PUBLICATION_CLEANUP,\n            CMD_OUT_SUBSCRIPTION_READY,\n            CMD_OUT_COUNTER_READY,\n            CMD_OUT_ON_UNAVAILABLE_COUNTER,\n            CMD_IN_CLIENT_CLOSE,\n            PUBLICATION_REVOKE));\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, mode = INCLUDE, names = {\n        \"REMOVE_IMAGE_CLEANUP\",\n        \"REMOVE_PUBLICATION_CLEANUP\",\n        \"SEND_CHANNEL_CREATION\",\n        \"SEND_CHANNEL_CLOSE\",\n        \"RECEIVE_CHANNEL_CREATION\",\n        \"RECEIVE_CHANNEL_CLOSE\",\n        \"FRAME_IN\",\n        \"FRAME_OUT\",\n        \"CMD_IN_ADD_SUBSCRIPTION\",\n        \"CMD_OUT_AVAILABLE_IMAGE\"\n    })\n    @InterruptAfter(10)\n    void logIndividualEvents(final DriverEventCode eventCode)\n    {\n        testLogMediaDriverEvents(NETWORK_CHANNEL, eventCode.name(), EnumSet.of(eventCode));\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = DriverEventCode.class, mode = INCLUDE, names = {\n        \"PUBLICATION_REVOKE\",\n        \"PUBLICATION_IMAGE_REVOKE\"\n    })\n    @InterruptAfter(10)\n    void logIndividualExclusivePublicationEvents(final DriverEventCode eventCode)\n    {\n        testLogExclusivePublicationMediaDriverEvents(NETWORK_CHANNEL, eventCode.name(), EnumSet.of(eventCode));\n    }\n\n    private void testLogMediaDriverEvents(\n        final String channel, final String enabledEvents, final EnumSet<DriverEventCode> expectedEvents)\n    {\n        before(enabledEvents, expectedEvents);\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .threadingMode(ThreadingMode.SHARED)\n            .errorHandler(Tests::onError)\n            .publicationLingerTimeoutNs(1_000_000_000L)\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(1));\n\n        try (MediaDriver mediaDriver = MediaDriver.launch(driverCtx))\n        {\n            try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n                Subscription subscription =\n                    aeron.addSubscription(channel, STREAM_ID, availableImageHandler, unavailableImageHandler);\n                Publication publication = aeron.addPublication(channel, STREAM_ID))\n            {\n                final UnsafeBuffer offerBuffer = new UnsafeBuffer(new byte[32]);\n                while (publication.offer(offerBuffer) < 0)\n                {\n                    Tests.yield();\n                }\n\n                final MutableInteger counter = new MutableInteger();\n                final FragmentHandler handler = (buffer, offset, length, header) -> counter.value++;\n\n                while (0 == subscription.poll(handler, 1))\n                {\n                    Tests.yield();\n                }\n\n                assertEquals(counter.get(), 1);\n            }\n\n            final Supplier<String> errorMessage = () -> \"Pending events: \" + WAIT_LIST;\n            while (!WAIT_LIST.isEmpty())\n            {\n                Tests.yieldingIdle(errorMessage);\n            }\n        }\n    }\n\n    private void testLogExclusivePublicationMediaDriverEvents(\n        final String channel, final String enabledEvents, final EnumSet<DriverEventCode> expectedEvents)\n    {\n        before(enabledEvents, expectedEvents);\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .publicationLingerTimeoutNs(3_000_000_000L)\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(1));\n\n        try (MediaDriver mediaDriver = MediaDriver.launch(driverCtx))\n        {\n            final AtomicInteger unavailableImages = new AtomicInteger(0);\n            doAnswer(invocation ->\n            {\n                final Image image = invocation.getArgument(0, Image.class);\n                assertTrue(image.isPublicationRevoked());\n\n                unavailableImages.incrementAndGet();\n                return null;\n            }).when(unavailableImageHandler).onUnavailableImage(any(Image.class));\n\n            try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n                Subscription subscription =\n                    aeron.addSubscription(channel, STREAM_ID, availableImageHandler, unavailableImageHandler);\n                ExclusivePublication publication = aeron.addExclusivePublication(channel, STREAM_ID))\n            {\n                final UnsafeBuffer offerBuffer = new UnsafeBuffer(new byte[32]);\n                while (publication.offer(offerBuffer) < 0)\n                {\n                    Tests.yield();\n                }\n\n                final MutableInteger counter = new MutableInteger();\n                final FragmentHandler handler = (buffer, offset, length, header) -> counter.value++;\n\n                while (0 == subscription.poll(handler, 1))\n                {\n                    Tests.yield();\n                }\n\n                assertEquals(counter.get(), 1);\n\n                publication.revoke();\n\n                while (unavailableImages.get() == 0)\n                {\n                    Tests.yield();\n                }\n            }\n\n            final Supplier<String> errorMessage = () -> \"Pending events: \" + WAIT_LIST;\n            while (!WAIT_LIST.isEmpty())\n            {\n                Tests.yieldingIdle(errorMessage);\n            }\n        }\n    }\n\n    private void before(final String enabledEvents, final EnumSet<DriverEventCode> expectedEvents)\n    {\n        final Map<String, String> configOptions = new HashMap<>();\n        configOptions.put(ConfigOption.READER_CLASSNAME, StubEventLogReaderAgent.class.getName());\n        configOptions.put(ConfigOption.ENABLED_DRIVER_EVENT_CODES, enabledEvents);\n        AgentTests.startLogging(configOptions);\n\n        WAIT_LIST.clear();\n        WAIT_LIST.addAll(expectedEvents);\n    }\n\n    static final class StubEventLogReaderAgent implements Agent, MessageHandler\n    {\n        public String roleName()\n        {\n            return \"event-log-reader\";\n        }\n\n        public int doWork()\n        {\n            return EVENT_RING_BUFFER.read(this, EVENT_READER_FRAME_LIMIT);\n        }\n\n        public void onMessage(final int msgTypeId, final MutableDirectBuffer buffer, final int index, final int length)\n        {\n            WAIT_LIST.remove(DriverEventCode.get(msgTypeId));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/EventConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.PrintStream;\nimport java.util.Collections;\nimport java.util.EnumSet;\n\nimport static io.aeron.agent.EventConfiguration.parseEventCodes;\nimport static io.aeron.driver.Configuration.ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME;\nimport static io.aeron.driver.Configuration.asyncTaskExecutorThreads;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.startsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class EventConfigurationTest\n{\n    @Test\n    void nullValueMeansNoEventsEnabled()\n    {\n        final EnumSet<TestEvent> parsedEvents = parseEventCodes(\n            TestEvent.class, null, Collections.emptyMap(), (i) -> TestEvent.values()[i], TestEvent::valueOf);\n\n        assertEquals(EnumSet.noneOf(TestEvent.class), parsedEvents);\n    }\n\n    @Test\n    void parseEventCodesShouldIgnoreInvalidEventCodes()\n    {\n        final PrintStream err = System.err;\n        final ByteArrayOutputStream stderr = new ByteArrayOutputStream();\n        System.setErr(new PrintStream(stderr));\n        try\n        {\n            final EnumSet<TestEvent> parsedEvents = parseEventCodes(\n                TestEvent.class, \"A,FOO,2\", Collections.emptyMap(), (i) -> TestEvent.values()[i], TestEvent::valueOf);\n            assertEquals(EnumSet.of(TestEvent.FOO, TestEvent.BAZ), parsedEvents);\n            assertThat(stderr.toString(), startsWith(\"unknown event code: A\"));\n        }\n        finally\n        {\n            System.setErr(err);\n        }\n    }\n\n    @Test\n    void asyncTaskExecutorThreadsReturnsOneByDefault()\n    {\n        try\n        {\n            assertEquals(1, asyncTaskExecutorThreads());\n        }\n        finally\n        {\n            System.clearProperty(ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME);\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -123, 4, 0, Integer.MAX_VALUE })\n    void asyncTaskExecutorThreadsReturnsZeroIfNegative(final int threads)\n    {\n        System.setProperty(ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME, Integer.toString(threads));\n        try\n        {\n            assertEquals(threads, asyncTaskExecutorThreads());\n        }\n        finally\n        {\n            System.clearProperty(ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME);\n        }\n    }\n\n    @Test\n    void asyncTaskExecutorThreadsReturnsOneIfInvalid()\n    {\n        System.setProperty(ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME, \"abc\");\n        try\n        {\n            assertEquals(1, asyncTaskExecutorThreads());\n        }\n        finally\n        {\n            System.clearProperty(ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME);\n        }\n    }\n\n    enum TestEvent\n    {\n        FOO, BAR, BAZ\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/EventLogReaderAgentTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport io.aeron.test.CapturingPrintStream;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.io.UncheckedIOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.ZoneId;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass EventLogReaderAgentTest\n{\n    @Test\n    void shouldListEnabledLoggersOnStartPrintStream()\n    {\n        final CapturingPrintStream out = new CapturingPrintStream();\n        testOnStart(null, out.resetAndGetPrintStream(), out::flushAndGetContent);\n    }\n\n    @Test\n    void shouldListEnabledLoggersOnStartFile(@TempDir final Path tempDir)\n    {\n        final Path file = tempDir.resolve(\"test-out.log\");\n        assertFalse(Files.exists(file));\n\n        testOnStart(\n            file.toString(),\n            null,\n            () ->\n            {\n                assertTrue(Files.exists(file));\n                try\n                {\n                    return Files.readString(file);\n                }\n                catch (final IOException ex)\n                {\n                    throw new UncheckedIOException(ex);\n                }\n            });\n    }\n\n    @Test\n    void shouldListSingleEnabledLoggerOnStart()\n    {\n        final CapturingPrintStream out = new CapturingPrintStream();\n        final CachedNanoClock nanoClock = new CachedNanoClock();\n        nanoClock.update(System.nanoTime());\n        final CachedEpochClock epochClock = new CachedEpochClock();\n        epochClock.update(System.currentTimeMillis());\n        final EventLogReaderAgent logReaderAgent = new EventLogReaderAgent(\n            null,\n            out.resetAndGetPrintStream(),\n            nanoClock,\n            epochClock,\n            List.of(new TestLogger(EventCodeType.SEQUENCER.getTypeCode(), \"sequencer v0\")));\n\n        logReaderAgent.onStart();\n\n        final String actual = out.flushAndGetContent();\n        final StringBuilder expected = new StringBuilder();\n        CommonEventDissector.dissectLogStartMessage(\n            nanoClock.nanoTime(), epochClock.time(), ZoneId.systemDefault(), expected);\n        expected.append(\", enabled loggers: {SEQUENCER: sequencer v0}\");\n        expected.append(System.lineSeparator());\n        assertThat(actual, equalTo(expected.toString()));\n    }\n\n    private static void testOnStart(final String fileName, final PrintStream out, final Supplier<String> loggedMessage)\n    {\n        final CachedNanoClock nanoClock = new CachedNanoClock();\n        nanoClock.update(System.nanoTime());\n        final CachedEpochClock epochClock = new CachedEpochClock();\n        epochClock.update(System.currentTimeMillis());\n        final EventLogReaderAgent logReaderAgent = new EventLogReaderAgent(\n            fileName,\n            out,\n            nanoClock,\n            epochClock,\n            List.of(\n                new TestLogger(EventCodeType.SEQUENCER.getTypeCode(), \"sequencer v0\"),\n                new TestLogger(100, \"logger 100\"),\n                new TestLogger(EventCodeType.DRIVER.getTypeCode(), \"driver v1\"),\n                new TestLogger(EventCodeType.USER.getTypeCode(), \"user logger\"),\n                new TestLogger(EventCodeType.STANDBY.getTypeCode(), \"standby version=1.49.0 commit=100\")));\n\n        logReaderAgent.onStart();\n\n        final String actual = loggedMessage.get();\n        final StringBuilder expected = new StringBuilder();\n        CommonEventDissector.dissectLogStartMessage(\n            nanoClock.nanoTime(), epochClock.time(), ZoneId.systemDefault(), expected);\n        expected.append(\", enabled loggers: {DRIVER: driver v1, STANDBY: standby version=1.49.0 commit=100, \")\n            .append(\"SEQUENCER: sequencer v0, USER: user logger, \")\n            .append(\"io.aeron.agent.EventLogReaderAgentTest$TestLogger: logger 100}\");\n        expected.append(System.lineSeparator());\n        assertThat(actual, equalTo(expected.toString()));\n    }\n\n    @Test\n    void throwsNullPointerExceptionIfNanoClockIsNull()\n    {\n        assertThrowsExactly(NullPointerException.class, () -> new EventLogReaderAgent(\n            null,\n            System.out,\n            null,\n            SystemEpochClock.INSTANCE,\n            List.of(new TestLogger(1, \"x\"))));\n    }\n\n    @Test\n    void throwsNullPointerExceptionIfEpochClockIsNull()\n    {\n        assertThrowsExactly(NullPointerException.class, () -> new EventLogReaderAgent(\n            null,\n            System.out,\n            SystemNanoClock.INSTANCE,\n            null,\n            List.of(new TestLogger(1, \"x\"))));\n    }\n\n    @Test\n    void throwsNullPointerExceptionIfFileIsNullAndPrintStreamIsNull()\n    {\n        assertThrowsExactly(NullPointerException.class, () -> new EventLogReaderAgent(\n            null,\n            null,\n            SystemNanoClock.INSTANCE,\n            SystemEpochClock.INSTANCE,\n            List.of(new TestLogger(1, \"x\"))));\n    }\n\n    private record TestLogger(int eventType, String version) implements ComponentLogger\n    {\n        public int typeCode()\n        {\n            return eventType;\n        }\n\n        public void decode(\n            final MutableDirectBuffer buffer, final int offset, final int eventCodeId, final StringBuilder builder)\n        {\n\n        }\n\n        public AgentBuilder addInstrumentation(final AgentBuilder agentBuilder, final Map<String, String> configOptions)\n        {\n            return null;\n        }\n\n        public void reset()\n        {\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-agent/src/test/java/io/aeron/agent/LogUtilTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.agent;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass LogUtilTest\n{\n    private final StringBuilder buff = new StringBuilder(32);\n\n    @ParameterizedTest\n    @CsvSource({\n        \"0, [0.000000000]\",\n        \"1, [0.000000001]\",\n        \"1234, [0.000001234]\",\n        \"12345678, [0.012345678]\",\n        \"123456789, [0.123456789]\",\n        \"1000000000, [1.000000000]\",\n        \"1987654321, [1.987654321]\",\n        \"9223372036854775807, [9223372036.854775807]\",\n    })\n    void shouldAppendTimestamp(final long timestamp, final String expected)\n    {\n        LogUtil.appendTimestamp(buff, timestamp);\n\n        final int lastCharIndex = buff.length() - 1;\n        assertEquals(expected, buff.substring(0, lastCharIndex));\n        assertEquals(' ', buff.charAt(lastCharIndex));\n    }\n}\n"
  },
  {
    "path": "aeron-all/README.md",
    "content": "Aeron All\n===\n\nThis subproject contains all-in-one build of Aeron with dependencies. \n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/api/InternalApi.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.api;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Annotation for marking parts of the Aeron API for internal use only.\n * <p>\n * APIs annotated with {@code InternalApi} can be changed at any time (including patch releases).\n *\n * @since 1.49.0\n */\n@Target({ ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })\n@Retention(RetentionPolicy.SOURCE)\npublic @interface InternalApi\n{\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/Config.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config;\n\nimport java.lang.annotation.*;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Annotation to indicate this is a config option.\n */\n@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})\n@Retention(RetentionPolicy.SOURCE)\npublic @interface Config\n{\n    /**\n     * Type is used to indicate whether the annotation is marking a property name or a default value.\n     */\n    enum Type\n    {\n        /**\n         * Undefined.\n         */\n        UNDEFINED,\n        /**\n         * Property.\n         */\n        PROPERTY_NAME,\n        /**\n         * Default value.\n         */\n        DEFAULT\n    }\n\n    /**\n     * What type of field is being annotated.\n     *\n     * @return what type of field is being annotated.\n     */\n    Type configType() default Type.UNDEFINED;\n\n    /**\n     * The unique id that ties together all the usages of the annotation across fields/methods.\n     *\n     * @return the unique id that ties together all the usages of the annotation across fields/methods.\n     */\n    String id() default \"\";\n\n    /**\n     * The uri parameter (if any) associated with this option.\n     *\n     * @return the uri parameter (if any) associated with this option.\n     */\n    String uriParam() default \"\";\n\n    /**\n     * Whether this config option exists in the C code.\n     *\n     * @return whether this config option exists in the C code.\n     */\n    boolean existsInC() default true;\n\n    /**\n     * The expected C #define name that will be set with the env variable name for this option.\n     *\n     * @return the expected C #define name that will be set with the env variable name for this option.\n     */\n    String expectedCEnvVarFieldName() default \"\";\n\n    /**\n     * The expected C env variable name for this option.\n     *\n     * @return the expected C env variable name for this option.\n     */\n    String expectedCEnvVar() default \"\";\n\n    /**\n     * The expected C #define name that will be set with the default value for this option.\n     *\n     * @return the expected C #define name that will be set with the default value for this option.\n     */\n    String expectedCDefaultFieldName() default \"\";\n\n    /**\n     * The expected C default value for this option.\n     *\n     * @return the expected C default value for this option.\n     */\n    String expectedCDefault() default \"\";\n\n    /**\n     * Whether to skip validation of the default in C.\n     *\n     * @return whether to skip validation of the default in C.\n     */\n    boolean skipCDefaultValidation() default false;\n\n    /**\n     * What's the type of default (string, int, etc...).\n     *\n     * @return what's the type of default (string, int, etc...).\n     */\n    DefaultType defaultType() default DefaultType.UNDEFINED;\n\n    /**\n     * Specify the default boolean, if defaultType is BOOLEAN.\n     *\n     * @return specify the default boolean, if defaultType is BOOLEAN.\n     */\n    boolean defaultBoolean() default false;\n\n    /**\n     * Specify the default int, if defaultType is INT.\n     *\n     * @return specify the default int, if defaultType is INT.\n     */\n    int defaultInt() default 0;\n\n    /**\n     * Specify the default long, if defaultType is LONG.\n     *\n     * @return specify the default long, if defaultType is LONG.\n     */\n    long defaultLong() default 0;\n\n    /**\n     * Specify the default double, if defaultType is DOUBLE.\n     *\n     * @return specify the default double, if defaultType is DOUBLE.\n     */\n    double defaultDouble() default 0.0;\n\n    /**\n     * Specify the default string, if defaultType is STRING.\n     *\n     * @return specify the default string, if defaultType is STRING.\n     */\n    String defaultString() default \"\";\n\n    /**\n     * Specify a string that acts as a stand-in for the default value when generating documentation.\n     *\n     * @return specify a string that acts as a stand-in for the default value when generating documentation.\n     */\n    String defaultValueString() default \"\";\n\n    /**\n     * Used to indicate whether the default value is a time value.\n     */\n    enum IsTimeValue\n    {\n        /**\n         * Undefined.\n         */\n        UNDEFINED,\n        /**\n         * True.\n         */\n        TRUE,\n        /**\n         * False.\n         */\n        FALSE\n    }\n\n    /**\n     * Whether the default value is a time value.\n     *\n     * @return whether the default value is a time value.\n     */\n    IsTimeValue isTimeValue() default IsTimeValue.UNDEFINED;\n\n    /**\n     * The time unit if the default value is a time value of some sort.\n     *\n     * @return the time unit if the default value is a time value of some sort.\n     */\n    TimeUnit timeUnit() default TimeUnit.NANOSECONDS;\n\n    /**\n     * Whether this config option has a 'context'.\n     *\n     * @return whether this config option has a 'context'.\n     */\n    boolean hasContext() default true;\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/ConfigInfo.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config;\n\nimport java.io.Serializable;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A handy class for storing data that gets serialized into json.\n */\npublic class ConfigInfo implements Serializable\n{\n    private static final long serialVersionUID = 6600224566064248728L;\n\n    /**\n     * Id.\n     */\n    public final String id;\n    /**\n     * List of expectations.\n     */\n    public final ExpectedConfig expectations;\n    /**\n     * Whether property was found.\n     */\n    public boolean foundPropertyName = false;\n    /**\n     * Whether default was found.\n     */\n    public boolean foundDefault = false;\n    /**\n     * Property description.\n     */\n    public String propertyNameDescription;\n    /**\n     * Property field name.\n     */\n    public String propertyNameFieldName;\n    /**\n     * Property class name.\n     */\n    public String propertyNameClassName;\n    /**\n     * Property name.\n     */\n    public String propertyName;\n    /**\n     * Default description.\n     */\n    public String defaultDescription;\n    /**\n     * Default field name.\n     */\n    public String defaultFieldName;\n    /**\n     * Default class name.\n     */\n    public String defaultClassName;\n    /**\n     * Default value.\n     */\n    public String defaultValue;\n    /**\n     * Default value string.\n     */\n    public String defaultValueString;\n    /**\n     * Default value type.\n     */\n    public DefaultType defaultValueType = DefaultType.UNDEFINED;\n    /**\n     * Default override type.\n     */\n    public String overrideDefaultValue;\n    /**\n     * Default override type value.\n     */\n    public DefaultType overrideDefaultValueType = DefaultType.UNDEFINED;\n    /**\n     * Uri param.\n     */\n    public String uriParam;\n    /**\n     * Whether property has context.\n     */\n    public boolean hasContext = true;\n    /**\n     * Context.\n     */\n    public String context;\n    /**\n     * Context description.\n     */\n    public String contextDescription;\n    /**\n     * Is time value.\n     */\n    public Boolean isTimeValue;\n    /**\n     * Time unit.\n     */\n    public TimeUnit timeUnit;\n    /**\n     * Whether property is deprecated.\n     */\n    public boolean deprecated = false;\n\n    /**\n     * Construct the ConfigInfo with the unique id.\n     *\n     * @param id the unique identifier for this block o' config information.\n     */\n    public ConfigInfo(final String id)\n    {\n        this.id = id;\n        expectations = new ExpectedConfig();\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/ConfigProcessor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config;\n\nimport io.aeron.utility.ElementIO;\nimport io.aeron.utility.Processor;\n\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.annotation.processing.SupportedAnnotationTypes;\nimport javax.lang.model.element.*;\nimport javax.tools.Diagnostic;\nimport javax.tools.FileObject;\nimport javax.tools.StandardLocation;\nimport java.util.*;\nimport java.util.stream.Stream;\n\n/**\n * ConfigOption processor.\n */\n@SupportedAnnotationTypes(\"io.aeron.config.Config\")\npublic class ConfigProcessor extends Processor\n{\n    private static final String[] PROPERTY_NAME_SUFFIXES = new String[] {\"_PROP_NAME\"};\n\n    private static final String[] DEFAULT_SUFFIXES = new String[] {\"_DEFAULT\", \"_DEFAULT_NS\"};\n\n    private final Map<String, Config> typeConfigMap = new HashMap<>();\n\n    /**\n     * Construct the ConfigProcessor.\n     */\n    public ConfigProcessor()\n    {\n    }\n\n    @Override\n    protected String getEnabledPropertyName()\n    {\n        return \"aeron.build.configProcessor.enabled\";\n    }\n\n    @Override\n    protected String getPrintNotesPropertyName()\n    {\n        return \"aeron.build.configProcessor.printNotes\";\n    }\n\n    @Override\n    protected String getFailOnErrorPropertyName()\n    {\n        return \"aeron.build.configProcessor.failOnError\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void doProcess(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv)\n    {\n        final Map<String, ConfigInfo> configInfoMap = new HashMap<>();\n\n        for (final TypeElement annotation : annotations)\n        {\n            for (final Element element : roundEnv.getElementsAnnotatedWith(annotation))\n            {\n                try\n                {\n                    final ConfigInfo configInfo;\n\n                    if (element instanceof VariableElement)\n                    {\n                        configInfo = processElement(configInfoMap, (VariableElement)element);\n                    }\n                    else if (element instanceof ExecutableElement)\n                    {\n                        configInfo = processExecutableElement(configInfoMap, (ExecutableElement)element);\n                    }\n                    else if (element instanceof TypeElement)\n                    {\n                        processTypeElement((TypeElement)element);\n                        configInfo = null;\n                    }\n                    else\n                    {\n                        configInfo = null;\n                    }\n\n                    if (configInfo != null)\n                    {\n                        if (element.getAnnotation(Deprecated.class) != null)\n                        {\n                            configInfo.deprecated = true;\n                        }\n                    }\n                }\n                catch (final Exception e)\n                {\n                    error(\"an error occurred processing an element: \" + e.getMessage(), element);\n                    e.printStackTrace(System.err);\n                }\n            }\n        }\n\n        if (!configInfoMap.isEmpty())\n        {\n            try\n            {\n                configInfoMap.forEach(this::applyTypeDefaults);\n                configInfoMap.forEach(this::deriveCExpectations);\n                configInfoMap.forEach(this::sanityCheck);\n            }\n            catch (final Exception e)\n            {\n                e.printStackTrace(System.err);\n                return;\n            }\n\n            try\n            {\n                final FileObject resourceFile = processingEnv.getFiler()\n                    .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, \"\", \"config-info.dat\");\n\n                ElementIO.write(resourceFile, configInfoMap.values());\n            }\n            catch (final Exception e)\n            {\n                e.printStackTrace(System.err);\n                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,\n                    \"an error occurred while writing output: \" + e.getMessage());\n            }\n        }\n    }\n\n    private ConfigInfo processElement(final Map<String, ConfigInfo> configInfoMap, final VariableElement element)\n    {\n        final Config config = element.getAnnotation(Config.class);\n\n        if (Objects.isNull(config))\n        {\n            error(\"element found with no expected annotations\", element);\n            return null;\n        }\n\n        final String id;\n        final ConfigInfo configInfo;\n        final Object constantValue = element.getConstantValue();\n        switch (getConfigType(element, config))\n        {\n            case PROPERTY_NAME:\n                id = getConfigId(element, PROPERTY_NAME_SUFFIXES, config.id());\n                configInfo = configInfoMap.computeIfAbsent(id, ConfigInfo::new);\n                if (configInfo.foundPropertyName)\n                {\n                    error(\"duplicate config option info for id: \" + id + \".  Previous definition found at \" +\n                        configInfo.propertyNameClassName + \":\" + configInfo.propertyNameFieldName, element);\n                    return configInfo;\n                }\n                configInfo.foundPropertyName = true;\n\n                configInfo.propertyNameFieldName = element.toString();\n                configInfo.propertyNameClassName = element.getEnclosingElement().toString();\n                configInfo.propertyNameDescription = getDocComment(element);\n\n                if (constantValue instanceof String)\n                {\n                    configInfo.propertyName = (String)constantValue;\n                }\n                else\n                {\n                    error(\"Property names must be Strings\", element);\n                }\n                break;\n\n            case DEFAULT:\n                id = getConfigId(element, DEFAULT_SUFFIXES, config.id());\n                configInfo = configInfoMap.computeIfAbsent(id, ConfigInfo::new);\n                if (configInfo.foundDefault)\n                {\n                    error(\"duplicate config default info for id: \" + id + \".  Previous definition found at \" +\n                        configInfo.defaultClassName + \":\" + configInfo.defaultFieldName, element);\n                    return configInfo;\n                }\n                configInfo.foundDefault = true;\n\n                configInfo.defaultFieldName = element.toString();\n                configInfo.defaultClassName = element.getEnclosingElement().toString();\n                configInfo.defaultDescription = getDocComment(element);\n\n                if (constantValue != null)\n                {\n                    configInfo.defaultValue = constantValue.toString();\n                    configInfo.defaultValueType =\n                        DefaultType.fromCanonicalName(constantValue.getClass().getCanonicalName());\n                }\n                break;\n\n            default:\n                error(\"unable to determine config type\", element);\n                return null;\n        }\n\n        if (!config.uriParam().isEmpty())\n        {\n            configInfo.uriParam = config.uriParam();\n        }\n\n        if (!config.hasContext())\n        {\n            configInfo.hasContext = false;\n        }\n\n        if (!config.defaultValueString().isEmpty())\n        {\n            configInfo.defaultValueString = config.defaultValueString();\n        }\n\n        handleTimeValue(config, configInfo, id);\n\n        handleDefaultTypeOverride(element, config, configInfo);\n\n        handleCExpectations(element, configInfo, config);\n\n        return configInfo;\n    }\n\n    private static void handleTimeValue(final Config config, final ConfigInfo configInfo, final String id)\n    {\n        switch (config.isTimeValue())\n        {\n            case UNDEFINED:\n                if (configInfo.isTimeValue == null)\n                {\n                    configInfo.isTimeValue =\n                        Stream.of(\"timeout\", \"backoff\", \"delay\", \"linger\", \"interval\", \"duration\")\n                            .anyMatch((k) -> id.toLowerCase().contains(k));\n                }\n                break;\n            case TRUE:\n                configInfo.isTimeValue = true;\n                break;\n            case FALSE:\n                // fall through\n            default:\n                configInfo.isTimeValue = false;\n                break;\n        }\n\n        if (configInfo.isTimeValue)\n        {\n            // TODO make sure this is either seconds, milliseconds, microseconds, or nanoseconds\n            configInfo.timeUnit = config.timeUnit();\n        }\n    }\n\n    private void handleDefaultTypeOverride(\n        final VariableElement element,\n        final Config config,\n        final ConfigInfo configInfo)\n    {\n        if (DefaultType.isUndefined(config.defaultType()))\n        {\n            return;\n        }\n\n        if (DefaultType.isUndefined(configInfo.defaultValueType))\n        {\n            note(\"defaultType is set explicitly, rather than relying on a separately defined field\", element);\n\n            configInfo.overrideDefaultValueType = config.defaultType();\n            switch (config.defaultType())\n            {\n                case INT:\n                    configInfo.overrideDefaultValue = \"\" + config.defaultInt();\n                    break;\n                case LONG:\n                    configInfo.overrideDefaultValue = \"\" + config.defaultLong();\n                    break;\n                case DOUBLE:\n                    configInfo.overrideDefaultValue = \"\" + config.defaultDouble();\n                    break;\n                case BOOLEAN:\n                    configInfo.overrideDefaultValue = \"\" + config.defaultBoolean();\n                    break;\n                case STRING:\n                    configInfo.overrideDefaultValue = config.defaultString();\n                    break;\n                default:\n                    error(\"unhandled default type\", element);\n                    break;\n            }\n        }\n        else\n        {\n            error(\"defaultType specified twice\", element);\n        }\n    }\n\n    private void handleCExpectations(final VariableElement element, final ConfigInfo configInfo, final Config config)\n    {\n        final ExpectedCConfig c = configInfo.expectations.c;\n\n        if (!config.existsInC())\n        {\n            c.exists = false;\n            return;\n        }\n\n        if (c.envVarFieldName == null && !config.expectedCEnvVarFieldName().isEmpty())\n        {\n            note(\"expectedCEnvVarFieldName is set\", element);\n            c.envVarFieldName = config.expectedCEnvVarFieldName();\n        }\n\n        if (c.envVar == null && !config.expectedCEnvVar().isEmpty())\n        {\n            note(\"expectedCEnvVar is set\", element);\n            c.envVar = config.expectedCEnvVar();\n        }\n\n        if (c.defaultFieldName == null && !config.expectedCDefaultFieldName().isEmpty())\n        {\n            note(\"expectedCDefaultFieldName is set\", element);\n            c.defaultFieldName = config.expectedCDefaultFieldName();\n        }\n\n        if (c.defaultValue == null && !config.expectedCDefault().isEmpty())\n        {\n            note(\"expectedCDefault is set\", element);\n            c.defaultValue = config.expectedCDefault();\n        }\n\n        if (config.skipCDefaultValidation())\n        {\n            note(\"skipCDefaultValidation is set\", element);\n            c.skipDefaultValidation = true;\n        }\n    }\n\n    private ConfigInfo processExecutableElement(\n        final Map<String, ConfigInfo> configInfoMap, final ExecutableElement element)\n    {\n        final Config config = element.getAnnotation(Config.class);\n\n        if (Objects.isNull(config))\n        {\n            error(\"element found with no expected annotations\", element);\n            return null;\n        }\n\n        final String id = getConfigId(element, config.id());\n        final ConfigInfo configInfo = configInfoMap.computeIfAbsent(id, ConfigInfo::new);\n\n        final String methodName = element.toString();\n        final String enclosingElementName = element.getEnclosingElement().toString();\n\n        Element e = element.getEnclosingElement();\n        while (e.getKind() != ElementKind.PACKAGE)\n        {\n            e = e.getEnclosingElement();\n        }\n\n        final String packageName = e.toString();\n\n        configInfo.context = enclosingElementName.substring(packageName.length() + 1) + \".\" + methodName;\n        configInfo.contextDescription = getDocComment(element);\n\n        return configInfo;\n    }\n\n    private void processTypeElement(final TypeElement element)\n    {\n        final Config config = element.getAnnotation(Config.class);\n\n        if (Objects.isNull(config))\n        {\n            error(\"element found with no expected annotations\", element);\n            return;\n        }\n\n        typeConfigMap.put(element.getQualifiedName().toString(), config);\n    }\n\n    private Config.Type getConfigType(final VariableElement element, final Config config)\n    {\n        // use an explicitly configured type\n        if (config.configType() != Config.Type.UNDEFINED)\n        {\n            return config.configType();\n        }\n\n        if (element.toString().endsWith(\"_PROP_NAME\"))\n        {\n            return Config.Type.PROPERTY_NAME;\n        }\n\n        if (element.toString().contains(\"DEFAULT\"))\n        {\n            return Config.Type.DEFAULT;\n        }\n\n        return Config.Type.UNDEFINED;\n    }\n\n    private String getConfigId(final ExecutableElement element, final String id)\n    {\n        final StringBuilder builder = new StringBuilder();\n\n        for (final char next: element.toString().toCharArray())\n        {\n            if (Character.isLetter(next))\n            {\n                if (Character.isUpperCase(next))\n                {\n                    builder.append(\"_\");\n                }\n                builder.append(Character.toUpperCase(next));\n            }\n            else if (next == '(')\n            {\n                break;\n            }\n        }\n\n        final String calculatedId = builder.toString().replace(\"_NS\", \"\");\n\n        if (null != id && !id.isEmpty())\n        {\n            if (id.equals(calculatedId))\n            {\n                error(\"redundant id specified\", element);\n            }\n            note(\"Config ID is overridden\", element);\n            return id;\n        }\n\n        return calculatedId;\n    }\n\n    private String getConfigId(final VariableElement element, final String[] suffixes, final String id)\n    {\n        if (null != id && !id.isEmpty())\n        {\n            note(\"Config ID is overridden\", element);\n            return id;\n        }\n\n        final String fieldName = element.toString();\n\n        for (final String suffix: suffixes)\n        {\n            if (fieldName.endsWith(suffix))\n            {\n                return fieldName.substring(0, fieldName.length() - suffix.length());\n            }\n        }\n\n        error(\"unable to determine id for: \" + fieldName, element);\n\n        return fieldName;\n    }\n\n    private void applyTypeDefaults(final String id, final ConfigInfo configInfo)\n    {\n        Optional.ofNullable(typeConfigMap.get(configInfo.propertyNameClassName))\n            .filter(config -> !config.existsInC())\n            .ifPresent(config -> configInfo.expectations.c.exists = false);\n    }\n\n    private void deriveCExpectations(final String id, final ConfigInfo configInfo)\n    {\n        if (!configInfo.expectations.c.exists)\n        {\n            return; // skip it\n        }\n\n        try\n        {\n            final ExpectedCConfig c = configInfo.expectations.c;\n\n            if (Objects.isNull(c.envVar) && configInfo.foundPropertyName)\n            {\n                c.envVar = configInfo.propertyName.toUpperCase().replace('.', '_');\n            }\n\n            if (Objects.isNull(c.envVarFieldName))\n            {\n                c.envVarFieldName = c.envVar + \"_ENV_VAR\";\n            }\n\n            if (Objects.isNull(c.defaultFieldName))\n            {\n                c.defaultFieldName = c.envVar + \"_DEFAULT\";\n            }\n\n            if (DefaultType.isUndefined(configInfo.overrideDefaultValueType))\n            {\n                if (Objects.isNull(c.defaultValue))\n                {\n                    c.defaultValue = configInfo.defaultValue;\n                }\n\n                if (Objects.isNull(c.defaultValueType))\n                {\n                    c.defaultValueType = configInfo.defaultValueType;\n                }\n            }\n            else\n            {\n                if (Objects.isNull(c.defaultValue))\n                {\n                    c.defaultValue = configInfo.overrideDefaultValue;\n                }\n\n                if (Objects.isNull(c.defaultValueType))\n                {\n                    c.defaultValueType = configInfo.overrideDefaultValueType;\n                }\n            }\n        }\n        catch (final Exception e)\n        {\n            error(\"an error occurred while deriving C config expectations for: \" + id);\n            e.printStackTrace(System.err);\n        }\n    }\n\n    private void sanityCheck(final String id, final ConfigInfo configInfo)\n    {\n        if (!configInfo.foundPropertyName)\n        {\n            insane(id, \"no property name found\");\n        }\n\n        if (configInfo.defaultValue == null &&\n            configInfo.overrideDefaultValue == null &&\n            configInfo.defaultValueString == null)\n        {\n            insane(id, \"no default value found\");\n        }\n\n        if (configInfo.hasContext && (configInfo.context == null || configInfo.context.isEmpty()))\n        {\n            note(\"Configuration (\" + id + \") is missing context\");\n        }\n    }\n\n    private void insane(final String id, final String errMsg)\n    {\n        error(\"Configuration (\" + id + \"): \" + errMsg);\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/DefaultType.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * Indicates the 'type' of the default field/value.\n */\npublic enum DefaultType\n{\n    /**\n     * Type is undefined.\n     */\n    UNDEFINED(\"\", \"\", false),\n    /**\n     * Boolean type.\n     */\n    BOOLEAN(\"java.lang.Boolean\", \"Boolean\", false),\n    /**\n     * Int32 type.\n     */\n    INT(\"java.lang.Integer\", \"Integer\", true),\n    /**\n     * Int64 type.\n     */\n    LONG(\"java.lang.Long\", \"Long\", true),\n    /**\n     * Floating point type.\n     */\n    DOUBLE(\"java.lang.Double\", \"Double\", true),\n    /**\n     * String type.\n     */\n    STRING(\"java.lang.String\", \"String\", false);\n\n    private static final Map<String, DefaultType> BY_CANONICAL_NAME = new HashMap<>();\n\n    static\n    {\n        for (final DefaultType t : values())\n        {\n            BY_CANONICAL_NAME.put(t.canonicalName, t);\n        }\n    }\n\n    /**\n     * Resolve a <code>DefaultType</code> from a <code>canonicalName</code>.\n     *\n     * @param canonicalName the name of the java class.\n     * @return the associated DefaultType.\n     */\n    public static DefaultType fromCanonicalName(final String canonicalName)\n    {\n        return BY_CANONICAL_NAME.getOrDefault(canonicalName, UNDEFINED);\n    }\n\n    /**\n     * Determines if a <code>DefaultType</code> is undefined.\n     *\n     * @param defaultType a DefaultType or null.\n     * @return true if the type is null or if it's UNDEFINED, otherwise false.\n     */\n    public static boolean isUndefined(final DefaultType defaultType)\n    {\n        return Objects.isNull(defaultType) || UNDEFINED == defaultType;\n    }\n\n    private final String canonicalName;\n    private final String simpleName;\n    private final boolean numeric;\n\n    DefaultType(final String canonicalName, final String simpleName, final boolean numeric)\n    {\n        this.canonicalName = canonicalName;\n        this.simpleName = simpleName;\n        this.numeric = numeric;\n    }\n\n    /**\n     * Indicates whether the value is numeric (int or long).\n     *\n     * @return indicates whether the value is numeric (int or long).\n     */\n    public boolean isNumeric()\n    {\n        return this.numeric;\n    }\n\n    /**\n     * A simple name, for display purposes.\n     *\n     * @return a simple name, for display purposes.\n     */\n    public String getSimpleName()\n    {\n        return this.simpleName;\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/ExpectedCConfig.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config;\n\nimport java.io.Serializable;\n\n/**\n * A handy class for storing expected C config info that can be serialized into json.\n */\npublic class ExpectedCConfig implements Serializable\n{\n    private static final long serialVersionUID = -4549394851227986144L;\n\n    /**\n     * Whether C config exists.\n     */\n    public boolean exists = true;\n    /**\n     * ENV var field name.\n     */\n    public String envVarFieldName;\n    /**\n     * ENV var name.\n     */\n    public String envVar;\n    /**\n     * Default field name.\n     */\n    public String defaultFieldName;\n    /**\n     * Default value.\n     */\n    public String defaultValue;\n    /**\n     * Default value type.\n     */\n    public DefaultType defaultValueType;\n    /**\n     * Whether default value validation should be skipped.\n     */\n    public boolean skipDefaultValidation = false;\n\n    /**\n     * Default constructor.\n     */\n    public ExpectedCConfig()\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/ExpectedConfig.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config;\n\nimport java.io.Serializable;\n\n/**\n * Wrapper class to contain the expected C config.\n */\npublic class ExpectedConfig implements Serializable\n{\n    private static final long serialVersionUID = -2025994445988286324L;\n\n    /**\n     * C config.\n     */\n    public final ExpectedCConfig c;\n\n    ExpectedConfig()\n    {\n        c = new ExpectedCConfig();\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/docgen/ConfigDocGenerator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config.docgen;\n\nimport io.aeron.config.ConfigInfo;\nimport io.aeron.config.DefaultType;\n\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.text.DecimalFormat;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nfinal class ConfigDocGenerator implements AutoCloseable\n{\n    static void generate(final List<ConfigInfo> configInfoCollection, final String outputFilename) throws Exception\n    {\n        try (ConfigDocGenerator generator = new ConfigDocGenerator(outputFilename))\n        {\n            generator.generateDoc(configInfoCollection);\n        }\n    }\n\n    private final FileWriter writer;\n\n    private ConfigDocGenerator(final String outputFile) throws Exception\n    {\n        writer = new FileWriter(outputFile);\n    }\n\n    @Override\n    public void close()\n    {\n        try\n        {\n            writer.close();\n        }\n        catch (final Exception e)\n        {\n            e.printStackTrace(System.err);\n        }\n    }\n\n    private void generateDoc(final List<ConfigInfo> configInfoCollection) throws Exception\n    {\n        for (final ConfigInfo configInfo: sort(configInfoCollection))\n        {\n            writeHeader(\n                toHeaderString(configInfo.id) +\n                (configInfo.expectations.c.exists ? \"\" : \" (***JAVA ONLY***)\") +\n                (configInfo.deprecated ? \" (***DEPRECATED***)\" : \"\"));\n            write(\"Description\", configInfo.propertyNameDescription);\n            write(\"Type\",\n                (DefaultType.isUndefined(configInfo.overrideDefaultValueType) ?\n                configInfo.defaultValueType :\n                configInfo.overrideDefaultValueType).getSimpleName());\n            writeCode(\"System Property\", configInfo.propertyName);\n            if (configInfo.context != null && !configInfo.context.isEmpty())\n            {\n                writeCode(\"Context\", configInfo.context);\n            }\n            if (configInfo.contextDescription != null && !configInfo.contextDescription.isEmpty())\n            {\n                write(\"Context Description\", configInfo.contextDescription);\n            }\n            if (configInfo.uriParam != null && !configInfo.uriParam.isEmpty())\n            {\n                writeCode(\"URI Param\", configInfo.uriParam);\n            }\n\n            if (configInfo.defaultDescription != null)\n            {\n                write(\"Default Description\", configInfo.defaultDescription);\n            }\n            final String defaultValue = configInfo.overrideDefaultValue == null ?\n                (configInfo.defaultValue == null ? \"\" : configInfo.defaultValue) :\n                configInfo.overrideDefaultValue;\n\n            write(\"Default\", getDefaultString(\n                configInfo.defaultValueString == null ? defaultValue : configInfo.defaultValueString,\n                configInfo.isTimeValue,\n                configInfo.timeUnit));\n            if (configInfo.isTimeValue == Boolean.TRUE)\n            {\n                write(\"Time Unit\", configInfo.timeUnit.toString());\n            }\n\n            if (configInfo.expectations.c.exists)\n            {\n                writeCode(\"C Env Var\", configInfo.expectations.c.envVar);\n                write(\"C Default\", getDefaultString(\n                    configInfo.expectations.c.defaultValue,\n                    configInfo.isTimeValue,\n                    configInfo.timeUnit));\n            }\n            writeLine();\n        }\n    }\n\n    private List<ConfigInfo> sort(final List<ConfigInfo> config)\n    {\n        return config\n            .stream()\n            .sorted(Comparator.comparing(a -> a.id))\n            .collect(Collectors.toList());\n    }\n\n    private void writeHeader(final String t) throws IOException\n    {\n        writeRow(\"\", t);\n        writeLine();\n        writeRow(\"---\", \"---\");\n        writeLine();\n    }\n\n    private void writeCode(final String a, final String b) throws IOException\n    {\n        write(a, \"`\" + b + \"`\");\n    }\n\n    private void write(final String a, final String b) throws IOException\n    {\n        writeRow(\"**\" + a + \"**\", b.replaceAll(\"\\n\", \" \").trim());\n        writeLine();\n    }\n\n    private void writeLine() throws IOException\n    {\n        writer.write(\"\\n\");\n    }\n\n    private void writeRow(final String a, final String b) throws IOException\n    {\n        writer.write(\"| \" + a + \" | \" + b + \" |\");\n    }\n\n    private String toHeaderString(final String t)\n    {\n        final StringBuilder builder = new StringBuilder();\n\n        char previous = '_';\n        for (final char next: t.toCharArray())\n        {\n            if (next == '_')\n            {\n                builder.append(' ');\n            }\n            else if (previous == '_')\n            {\n                builder.append(Character.toUpperCase(next));\n            }\n            else\n            {\n                builder.append(Character.toLowerCase(next));\n            }\n            previous = next;\n        }\n        return builder.toString();\n    }\n\n    private String getDefaultString(\n        final String defaultValue,\n        final boolean isTimeValue,\n        final TimeUnit timeUnit) throws Exception\n    {\n        if (defaultValue != null && !defaultValue.isEmpty() && defaultValue.chars().allMatch(Character::isDigit))\n        {\n            final long defaultLong;\n\n            try\n            {\n                defaultLong = Long.parseLong(defaultValue);\n            }\n            catch (final NumberFormatException nfe)\n            {\n                // This shouldn't be possible since we've already validated that every character is a digit\n                throw new Exception(nfe);\n            }\n\n            final StringBuilder builder = new StringBuilder();\n\n            builder.append(defaultValue);\n\n            if (defaultValue.length() > 3)\n            {\n                builder.append(\" (\");\n                builder.append(DecimalFormat.getNumberInstance().format(defaultLong));\n                builder.append(\")\");\n\n                int kCount = 0;\n                long remainingValue = defaultLong;\n                while (remainingValue % 1024 == 0)\n                {\n                    kCount++;\n                    remainingValue = remainingValue / 1024;\n                }\n\n                if (kCount > 0 && remainingValue < 1024)\n                {\n                    builder.append(\" (\");\n                    builder.append(remainingValue);\n                    IntStream.range(0, kCount).forEach(i -> builder.append(\" * 1024\"));\n                    builder.append(\")\");\n                }\n            }\n\n            if (isTimeValue)\n            {\n                int tCount = 0;\n\n                long remaining = timeUnit.toNanos(defaultLong);\n                while (remaining % 1000 == 0 && tCount < 3)\n                {\n                    tCount++;\n                    remaining = remaining / 1000;\n                }\n                builder.append(\" (\");\n                builder.append(remaining);\n\n                switch (tCount)\n                {\n                    case 0:\n                        builder.append(\" nano\");\n                        break;\n                    case 1:\n                        builder.append(\" micro\");\n                        break;\n                    case 2:\n                        builder.append(\" milli\");\n                        break;\n                    case 3:\n                        builder.append(\" \");\n                        break;\n                }\n                builder.append(\"second\");\n                if (remaining != 1)\n                {\n                    builder.append(\"s\");\n                }\n                builder.append(\")\");\n            }\n\n            return builder.toString();\n        }\n        return defaultValue;\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/docgen/GenerateConfigDocTask.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config.docgen;\n\nimport io.aeron.utility.ElementIO;\n\n/**\n * A gradle task for generating config documentation.\n */\npublic final class GenerateConfigDocTask\n{\n    private GenerateConfigDocTask()\n    {\n    }\n\n    /**\n     * Run the config generator task.\n     *\n     * @param args Arg 0 should be the location of a config-info.xml file with a list of ConfigInfo objects. Arg 1\n     *             should be the location of an output file where a .md file is to be written.\n     * @throws Exception on IO failure.\n     */\n    public static void main(final String[] args) throws Exception\n    {\n        ConfigDocGenerator.generate(ElementIO.read(args[0]), args[1]);\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/validation/Entry.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config.validation;\n\nimport java.io.PrintStream;\nimport io.aeron.config.ConfigInfo;\n\nclass Entry\n{\n    private final ConfigInfo configInfo;\n    final Validation envVarValidation;\n    final Validation defaultValidation;\n\n    Entry(final ConfigInfo configInfo)\n    {\n        this.configInfo = configInfo;\n        this.envVarValidation = new Validation();\n        this.defaultValidation = new Validation();\n    }\n\n    void printOn(final PrintStream out)\n    {\n        if (configInfo.expectations.c.exists)\n        {\n            out.println(configInfo.id);\n            envVarValidation.printOn(out);\n            defaultValidation.printOn(out);\n        }\n        else\n        {\n            out.println(configInfo.id + \" -- SKIPPED\");\n        }\n    }\n\n    void printFailuresOn(final PrintStream out)\n    {\n        if (configInfo.expectations.c.exists && (!envVarValidation.isValid() || !defaultValidation.isValid()))\n        {\n            printOn(out);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/validation/ValidateConfigExpectationsTask.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config.validation;\n\nimport io.aeron.utility.ElementIO;\n\n/**\n * A gradle task for validating the C code looks as expected, based on the contents of the @Config java annotations.\n */\npublic final class ValidateConfigExpectationsTask\n{\n    private ValidateConfigExpectationsTask()\n    {\n    }\n\n    /**\n     * Launch task to validate config expectations.\n     *\n     * @param args Arg 0 should be the location of a config-info.xml file with a list of ConfigInfo objects. Arg 1\n     *             should be the location of the C source code\n     * @throws Exception on IO failure.\n     */\n    public static void main(final String[] args) throws Exception\n    {\n        Validator.validate(ElementIO.read(args[0]), args[1]).printFailuresOn(System.err);\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/validation/Validation.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config.validation;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.PrintStream;\n\nclass Validation\n{\n    private boolean valid = false;\n\n    private String message;\n\n    private ByteArrayOutputStream baOut;\n\n    private PrintStream psOut;\n\n    boolean isValid()\n    {\n        return valid;\n    }\n\n    void close()\n    {\n        if (this.psOut != null)\n        {\n            this.psOut.close();\n        }\n    }\n\n    void valid(final String message)\n    {\n        this.valid = true;\n        this.message = message;\n    }\n\n    void invalid(final String message)\n    {\n        this.valid = false;\n        this.message = message;\n    }\n\n    PrintStream out()\n    {\n        if (this.psOut == null)\n        {\n            this.baOut = new ByteArrayOutputStream();\n            this.psOut = new PrintStream(baOut);\n        }\n\n        return psOut;\n    }\n\n    void printOn(final PrintStream out)\n    {\n        out.println(\" \" + (this.valid ? \"+\" : \"-\") + \" \" + this.message);\n        if (this.psOut != null)\n        {\n            out.println(this.baOut);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/validation/ValidationReport.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config.validation;\n\nimport io.aeron.config.ConfigInfo;\nimport io.aeron.config.ExpectedCConfig;\n\nimport java.io.PrintStream;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.BiConsumer;\n\nfinal class ValidationReport\n{\n    private final List<Entry> entries;\n\n    ValidationReport()\n    {\n        entries = new ArrayList<>();\n    }\n\n    void addEntry(\n        final ConfigInfo configInfo,\n        final BiConsumer<Validation, ExpectedCConfig> validateCEnvVar,\n        final BiConsumer<Validation, ExpectedCConfig> validateCDefault)\n    {\n        final Entry entry = new Entry(configInfo);\n        final ExpectedCConfig c = configInfo.expectations.c;\n        if (c.exists)\n        {\n            validate(validateCEnvVar, entry.envVarValidation, c);\n\n            if (c.skipDefaultValidation)\n            {\n                entry.defaultValidation.valid(\"skipped\");\n            }\n            else\n            {\n                validate(validateCDefault, entry.defaultValidation, c);\n            }\n        }\n        entries.add(entry);\n    }\n\n    private void validate(\n        final BiConsumer<Validation, ExpectedCConfig> func,\n        final Validation validation,\n        final ExpectedCConfig c)\n    {\n        try\n        {\n            func.accept(validation, c);\n        }\n        catch (final Exception e)\n        {\n            validation.invalid(e.getMessage());\n            e.printStackTrace(validation.out());\n        }\n        finally\n        {\n            validation.close();\n        }\n    }\n\n    void printOn(final PrintStream out)\n    {\n        entries.forEach(entry -> entry.printOn(out));\n    }\n\n    void printFailuresOn(final PrintStream out)\n    {\n        entries.forEach(entry -> entry.printFailuresOn(out));\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/config/validation/Validator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.config.validation;\n\nimport io.aeron.config.ConfigInfo;\nimport io.aeron.config.DefaultType;\nimport io.aeron.config.ExpectedCConfig;\nimport io.aeron.validation.Grep;\n\nimport java.util.Collection;\nimport java.util.Objects;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nfinal class Validator\n{\n    static ValidationReport validate(\n        final Collection<ConfigInfo> configInfoCollection,\n        final String sourceDir)\n    {\n        return new Validator(sourceDir).validate(configInfoCollection).report;\n    }\n\n    private final String sourceDir;\n    private final ValidationReport report;\n\n    private Validator(final String sourceDir)\n    {\n        this.sourceDir = sourceDir;\n        this.report = new ValidationReport();\n    }\n\n    private Validator validate(final Collection<ConfigInfo> configInfoCollection)\n    {\n        configInfoCollection.forEach(this::validateCExpectations);\n\n        // TODO look through C code in 'sourceDir' and check for anything marked '_ENV_VAR' that hasn't been processed.\n        // These will be 'C only' config options that need to be accounted for...\n        // ... perhaps in the Java code we can add @Config annotations to some static final fields that Java ignores.\n\n        return this;\n    }\n\n    private void validateCExpectations(final ConfigInfo configInfo)\n    {\n        report.addEntry(configInfo, this::validateCEnvVar, this::validateCDefault);\n    }\n\n    private void validateCEnvVar(final Validation validation, final ExpectedCConfig c)\n    {\n        if (Objects.isNull(c.envVarFieldName))\n        {\n            return;\n        }\n\n        /* Expectations:\n         * #define AERON_OPTION_ENV_VAR \"AERON_OPTION\"\n         */\n        final String pattern = \"#define[ \\t]+\" + c.envVarFieldName + \"[ \\t]+\\\"\" + c.envVar + \"\\\"\";\n        final Grep grep = Grep.execute(pattern, sourceDir);\n        if (grep.success())\n        {\n            validation.valid(\"Expected Env Var found in \" + grep.getFilenameAndLine());\n        }\n        else\n        {\n            validation.invalid(\"Expected Env Var NOT found.  `grep` command:\\n\" + grep.getCommandString());\n        }\n    }\n\n    private void validateCDefault(final Validation validation, final ExpectedCConfig c)\n    {\n        if (Objects.isNull(c.defaultFieldName))\n        {\n            return;\n        }\n\n        /* Expectations:\n         * #define AERON_OPTION_DEFAULT (\"some_string\")\n         * #define AERON_OPTION_DEFAULT (1234)\n         * #define AERON_OPTION_DEFAULT (10 * 1024)\n         * #define AERON_OPTION_DEFAULT (1024 * INT64_C(1000))\n         * #define AERON_OPTION_DEFAULT false\n         * #define AERON_OPTION_DEFAULT (true)\n         */\n        final String pattern = \"#define[ \\t]+\" + c.defaultFieldName;\n\n        final Grep grep = Grep.execute(pattern, sourceDir);\n        if (!grep.success())\n        {\n            validation.invalid(\"Expected Default NOT found.  `grep` command:\\n\" + grep.getCommandString());\n            return;\n        }\n        final String location = grep.getFilenameAndLine();\n\n        final Matcher matcher = Pattern.compile(pattern + \"(.*)$\").matcher(grep.getOutput());\n        if (!matcher.find())\n        {\n            validation.invalid(\"Found Default but the pattern doesn't match at \" + location);\n            return;\n        }\n\n        final String originalFoundDefaultString = matcher.group(1).trim();\n\n        if (c.defaultValueType == DefaultType.STRING)\n        {\n            validateCDefaultString(validation, c, originalFoundDefaultString, location);\n        }\n        else if (c.defaultValueType == DefaultType.BOOLEAN)\n        {\n            validateCDefaultBoolean(validation, c, originalFoundDefaultString, location);\n        }\n        else if (c.defaultValueType.isNumeric())\n        {\n            validateCDefaultNumeric(validation, c, originalFoundDefaultString, location);\n        }\n        else\n        {\n            validation.invalid(\"bad default type\");\n        }\n    }\n\n    private void validateCDefaultString(\n        final Validation validation,\n        final ExpectedCConfig c,\n        final String originalFoundDefaultString,\n        final String location)\n    {\n        final String foundDefaultString = originalFoundDefaultString\n            .replaceFirst(\"^\\\\(\", \"\")\n            .replaceFirst(\"\\\\)$\", \"\")\n            .replaceFirst(\"^\\\"\", \"\")\n            .replaceFirst(\"\\\"$\", \"\");\n\n        if (foundDefaultString.equals(c.defaultValue))\n        {\n            validation.valid(\"Expected Default (\\\"\" + foundDefaultString + \"\\\") found in \" + location);\n        }\n        else\n        {\n            validation.invalid(\"Expected Default string doesn't match.  Expected '\" + c.defaultValue +\n                \"' but found '\" + foundDefaultString + \"' in \" + location);\n        }\n    }\n\n    private void validateCDefaultBoolean(\n        final Validation validation,\n        final ExpectedCConfig c,\n        final String originalFoundDefaultString,\n        final String location)\n    {\n        final String foundDefaultString = originalFoundDefaultString\n            .replaceFirst(\"^\\\\(\", \"\")\n            .replaceFirst(\"\\\\)$\", \"\");\n\n        if (foundDefaultString.equals(c.defaultValue))\n        {\n            validation.valid(\"Expected Default '\" + foundDefaultString + \"' found in \" + location);\n        }\n        else\n        {\n            validation.invalid(\"boolean doesn't match: \" + location);\n        }\n    }\n\n    private void validateCDefaultNumeric(\n        final Validation validation,\n        final ExpectedCConfig c,\n        final String originalFoundDefaultString,\n        final String location)\n    {\n        final String foundDefaultString = originalFoundDefaultString\n            .replaceAll(\"INT64_C\", \"\")\n            .replaceAll(\"UINT32_C\", \"\")\n            .replaceAll(\"([0-9]+)LL\", \"$1\")\n            .replaceAll(\"([0-9]+)L\", \"$1\");\n\n        if (foundDefaultString.equals(c.defaultValue))\n        {\n            validation.valid(\"Expected Default '\" + foundDefaultString + \"' found in \" + location);\n        }\n        else\n        {\n            validation.invalid(\"found \" + foundDefaultString + \" but expected \" + c.defaultValue);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/counter/AeronCounter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.counter;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Annotation to indicate this is an Aeron counter.\n */\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.SOURCE)\npublic @interface AeronCounter\n{\n    /**\n     * Whether this counter exists in the C code.\n     *\n     * @return whether this counter exists in the C code.\n     */\n    boolean existsInC() default true;\n\n    /**\n     * The name of the #define in C.\n     *\n     * @return the name of the #define in C.\n     */\n    String expectedCName() default \"\";\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/counter/CounterInfo.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.counter;\n\nimport java.io.Serializable;\n\n/**\n * A handy class for storing data that gets serialized into json.\n */\npublic class CounterInfo implements Serializable\n{\n    private static final long serialVersionUID = -5863246029522577056L;\n\n    /**\n     * Counter name.\n     */\n    public final String name;\n\n    /**\n     * Counter id.\n     */\n    public int id;\n\n    /**\n     * Counter description.\n     */\n    public String counterDescription;\n\n    /**\n     * Whether counter exists in the C media driver.\n     */\n    public boolean existsInC = true;\n\n    /**\n     * Expected name in the C media driver.\n     */\n    public String expectedCName;\n\n    /**\n     * Default constructor.\n     */\n    public CounterInfo()\n    {\n        this.name = null;\n    }\n\n    /**\n     * Create the CounterInfo given a name.\n     *\n     * @param name the name of the counter\n     */\n    public CounterInfo(final String name)\n    {\n        this.name = name;\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/counter/CounterProcessor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.counter;\n\nimport io.aeron.utility.ElementIO;\nimport io.aeron.utility.Processor;\n\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.annotation.processing.SupportedAnnotationTypes;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.element.VariableElement;\nimport javax.tools.Diagnostic;\nimport javax.tools.FileObject;\nimport javax.tools.StandardLocation;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * AeronCounter processor.\n */\n@SupportedAnnotationTypes(\"io.aeron.counter.AeronCounter\")\npublic class CounterProcessor extends Processor\n{\n    private static final String SYSTEM_COUNTER_ID_PREFIX = \"SYSTEM_COUNTER_ID_\";\n    private static final String TYPE_ID_SUFFIX = \"_TYPE_ID\";\n\n    /**\n     * Create CounterProcessor to processor counter id definitions.\n     */\n    public CounterProcessor()\n    {\n    }\n\n    protected String getEnabledPropertyName()\n    {\n        return \"aeron.build.counterProcessor.enabled\";\n    }\n\n    protected String getPrintNotesPropertyName()\n    {\n        return \"aeron.build.counterProcessor.printNotes\";\n    }\n\n    protected String getFailOnErrorPropertyName()\n    {\n        return \"aeron.build.counterProcessor.failOnError\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void doProcess(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv)\n    {\n        final Map<String, CounterInfo> counterInfoMap = new HashMap<>();\n\n        for (final TypeElement annotation : annotations)\n        {\n            for (final Element element : roundEnv.getElementsAnnotatedWith(annotation))\n            {\n                try\n                {\n                    if (element instanceof VariableElement)\n                    {\n                        processElement(counterInfoMap, (VariableElement)element);\n                    }\n                }\n                catch (final Exception e)\n                {\n                    error(\"an error occurred processing an element: \" + e.getMessage(), element);\n                    e.printStackTrace(System.err);\n                }\n            }\n        }\n\n        if (!counterInfoMap.isEmpty())\n        {\n            try\n            {\n                final FileObject resourceFile = processingEnv.getFiler()\n                    .createResource(StandardLocation.NATIVE_HEADER_OUTPUT, \"\", \"counter-info.dat\");\n\n                ElementIO.write(resourceFile, counterInfoMap.values());\n            }\n            catch (final Exception e)\n            {\n                e.printStackTrace(System.err);\n                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,\n                    \"an error occurred while writing output: \" + e.getMessage());\n            }\n        }\n    }\n\n    private void processElement(final Map<String, CounterInfo> counterInfoMap, final VariableElement element)\n    {\n        final AeronCounter counter = element.getAnnotation(AeronCounter.class);\n\n        if (Objects.isNull(counter))\n        {\n            error(\"element found with no expected annotations\", element);\n            return;\n        }\n\n        final String name = element.toString();\n        final boolean systemCounterId = name.startsWith(SYSTEM_COUNTER_ID_PREFIX);\n        final boolean typeId = name.endsWith(TYPE_ID_SUFFIX);\n        if (!systemCounterId && !typeId)\n        {\n            error(\"unable to determine type and/or id\", element);\n            return;\n        }\n\n        final CounterInfo counterInfo = new CounterInfo(systemCounterId ?\n            name.substring(SYSTEM_COUNTER_ID_PREFIX.length()) :\n            name.substring(0, name.length() - TYPE_ID_SUFFIX.length()));\n\n        if (null != counterInfoMap.put(counterInfo.name, counterInfo))\n        {\n            error(\"duplicate counters found\", element);\n            return;\n        }\n\n        counterInfo.counterDescription = getDocComment(element);\n\n        final Object constantValue = element.getConstantValue();\n        if (constantValue instanceof Integer)\n        {\n            counterInfo.id = (Integer)constantValue;\n        }\n        else\n        {\n            error(\"Counter value must be an Integer\", element);\n        }\n\n        if (!counter.existsInC())\n        {\n            note(\"Counter isn't expected to exist in C\", element);\n            counterInfo.existsInC = false;\n        }\n\n        if (counterInfo.existsInC)\n        {\n            final StringBuilder builder = new StringBuilder();\n\n            builder.append(\"AERON_\");\n            if (systemCounterId)\n            {\n                builder.append(SYSTEM_COUNTER_ID_PREFIX);\n            }\n            else\n            {\n                builder.append(\"COUNTER_\");\n            }\n\n            if (counter.expectedCName().isEmpty())\n            {\n                if (typeId && counterInfo.name.startsWith(\"DRIVER_\"))\n                {\n                    builder.append(counterInfo.name.substring(7));\n                }\n                else\n                {\n                    builder.append(counterInfo.name);\n                }\n            }\n            else\n            {\n                note(\"Counter's C name is overridden\", element);\n\n                builder.append(counter.expectedCName());\n            }\n\n            if (typeId)\n            {\n                builder.append(TYPE_ID_SUFFIX);\n            }\n\n            counterInfo.expectedCName = builder.toString();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/counter/validation/ValidateCounterExpectationsTask.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.counter.validation;\n\nimport io.aeron.utility.ElementIO;\n\n/**\n * A gradle task for validating C counters conform to expectations set by the AeronCounter annotation in java.\n */\npublic final class ValidateCounterExpectationsTask\n{\n    private ValidateCounterExpectationsTask()\n    {\n    }\n\n    /**\n     * Launch task to validate counter expectations.\n     *\n     * @param args Arg 0 should be the location of a counter-info.xml file with a list of CounterInfo objects. Arg 1\n     *             should be the location of the C source code\n     * @throws Exception on IO failure.\n     */\n    public static void main(final String[] args) throws Exception\n    {\n        Validator.validate(ElementIO.read(args[0]), args[1]).printFailuresOn(System.err);\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/counter/validation/Validation.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.counter.validation;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.PrintStream;\n\nclass Validation\n{\n    private final String name;\n    private boolean valid = false;\n\n    private String message;\n\n    private ByteArrayOutputStream baOut;\n\n    private PrintStream psOut;\n\n    Validation(final String name)\n    {\n        this.name = name;\n    }\n\n    boolean isValid()\n    {\n        return valid;\n    }\n\n    void close()\n    {\n        if (this.psOut != null)\n        {\n            this.psOut.close();\n        }\n    }\n\n    void valid(final String message)\n    {\n        this.valid = true;\n        this.message = message;\n    }\n\n    void invalid(final String message)\n    {\n        this.valid = false;\n        this.message = message;\n    }\n\n    PrintStream out()\n    {\n        if (this.psOut == null)\n        {\n            this.baOut = new ByteArrayOutputStream();\n            this.psOut = new PrintStream(baOut);\n        }\n\n        return psOut;\n    }\n\n    void printOn(final PrintStream out)\n    {\n        out.println(name);\n        out.println(\" \" + (this.valid ? \"+\" : \"-\") + \" \" + this.message);\n        if (this.psOut != null)\n        {\n            out.println(this.baOut);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/counter/validation/ValidationReport.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.counter.validation;\n\nimport io.aeron.counter.CounterInfo;\n\nimport java.io.PrintStream;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.BiConsumer;\n\nfinal class ValidationReport\n{\n    private final List<Validation> validations;\n\n    ValidationReport()\n    {\n        validations = new ArrayList<>();\n    }\n\n    void addValidation(\n        final CounterInfo counterInfo,\n        final BiConsumer<Validation, CounterInfo> validateFunc)\n    {\n        final Validation validation = new Validation(counterInfo.name);\n        validate(validateFunc, validation, counterInfo);\n        validations.add(validation);\n    }\n\n    void addValidation(\n        final boolean valid,\n        final String name,\n        final String message)\n    {\n        final Validation validation = new Validation(name);\n        if (valid)\n        {\n            validation.valid(message);\n        }\n        else\n        {\n            validation.invalid(message);\n        }\n        validations.add(validation);\n    }\n\n    private void validate(\n        final BiConsumer<Validation, CounterInfo> func,\n        final Validation validation,\n        final CounterInfo c)\n    {\n        try\n        {\n            func.accept(validation, c);\n        }\n        catch (final Exception e)\n        {\n            validation.invalid(e.getMessage());\n            e.printStackTrace(validation.out());\n        }\n        finally\n        {\n            validation.close();\n        }\n    }\n\n    void printOn(final PrintStream out)\n    {\n        validations.forEach(validation -> validation.printOn(out));\n    }\n\n    void printFailuresOn(final PrintStream out)\n    {\n        validations.stream().filter(validation -> !validation.isValid()).forEach(validation -> validation.printOn(out));\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/counter/validation/Validator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.counter.validation;\n\nimport io.aeron.counter.CounterInfo;\nimport io.aeron.validation.Grep;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\nfinal class Validator\n{\n    static ValidationReport validate(\n        final Collection<CounterInfo> counterInfoCollection,\n        final String sourceDir)\n    {\n        return new Validator(sourceDir).validate(counterInfoCollection).report;\n    }\n\n    private final String sourceDir;\n    private final ValidationReport report;\n\n    private Validator(final String sourceDir)\n    {\n        this.sourceDir = sourceDir;\n        this.report = new ValidationReport();\n    }\n\n    private Validator validate(final Collection<CounterInfo> counterInfoCollection)\n    {\n        counterInfoCollection.forEach(this::validateCExpectations);\n\n        identifyExtraCCounters(counterInfoCollection);\n\n        return this;\n    }\n\n    private void identifyExtraCCounters(final Collection<CounterInfo> counterInfoCollection)\n    {\n        final Pattern compiledPattern = Pattern.compile(\"#define[ \\t]+([A-Z_]+)[ \\t]+\\\\([0-9]+\\\\)\");\n        final List<String> expectedCNames = counterInfoCollection\n            .stream()\n            .filter(counterInfo -> counterInfo.existsInC)\n            .map(counterInfo -> counterInfo.expectedCName)\n            .collect(Collectors.toList());\n\n        final String pattern = \"#define[ \\t]+AERON_COUNTER_([A-Z_]+)_TYPE_ID[ \\t]+\\\\([0-9]+\\\\)\";\n        final Grep grep = Grep.execute(pattern, sourceDir);\n\n        grep.forEach((fileAndLineNo, line) ->\n        {\n            final Matcher matcher = compiledPattern.matcher(line);\n            if (matcher.find())\n            {\n                final String name = matcher.group(1);\n\n                if (expectedCNames.stream().noneMatch(cName -> cName.equals(name)))\n                {\n                    report.addValidation(false, name,\n                        \"Found C counter with no matching Java counter - \" + fileAndLineNo);\n                }\n            }\n            else\n            {\n                System.err.println(\"malformed line: \" + line);\n            }\n        });\n    }\n\n    private void validateCExpectations(final CounterInfo counterInfo)\n    {\n        if (counterInfo.existsInC)\n        {\n            report.addValidation(counterInfo, this::validate);\n        }\n    }\n\n    private void validate(final Validation validation, final CounterInfo counterInfo)\n    {\n        /* Expectations:\n         * #define AERON_COUNTER_SOME_NAME (50)\n         */\n        final String pattern = \"#define[ \\t]+\" + counterInfo.expectedCName + \"[ \\t]+\\\\([0-9]+\\\\)\";\n        final Grep grep = Grep.execute(pattern, sourceDir);\n        if (grep.success())\n        {\n\n            final Matcher matcher =\n                Pattern.compile(\"#define[ \\t]+[A-Z_]+[ \\t]+\\\\(([0-9]+)\\\\)\").matcher(grep.getOutput());\n            if (matcher.find())\n            {\n                final String id = matcher.group(1);\n\n                try\n                {\n                    if (counterInfo.id == Integer.parseInt(id))\n                    {\n                        validation.valid(\"Expected ID found in \" + grep.getFilenameAndLine());\n                    }\n                    else\n                    {\n                        validation.invalid(\"Incorrect ID found.  Expected: \" + counterInfo.id + \" but found: \" + id);\n                    }\n                }\n                catch (final NumberFormatException numberFormatException)\n                {\n                    validation.invalid(\"Unable to parse ID.  Expected a number but found: \" + id);\n                }\n            }\n            else\n            {\n                validation.invalid(\"WHAT??\");\n            }\n        }\n        else\n        {\n            validation.invalid(\"Expected ID NOT found.  `grep` command:\\n\" + grep.getCommandString());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/utility/ElementIO.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.utility;\n\nimport javax.tools.FileObject;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * Utility class for wring configuration elements.\n */\npublic final class ElementIO\n{\n    private ElementIO()\n    {\n    }\n\n    /**\n     * Read a list of objects from the named file.\n     *\n     * @param elementsFilename the name of the filename that contains a list of objects\n     * @return a list of elements\n     * @param <T> the type of elements - ConfigInfo or CounterInfo\n     * @throws Exception on IO failure.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> List<T> read(final String elementsFilename) throws Exception\n    {\n        try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(elementsFilename))))\n        {\n            return ((List<T>)in.readObject());\n        }\n    }\n\n    /**\n     * Write a list of objects to the named file.\n     *\n     * @param resourceFile the destination file to write to\n     * @param elements a Collection of elements\n     * @throws Exception on IO failure.\n     */\n    public static void write(final FileObject resourceFile, final Collection<?> elements) throws Exception\n    {\n        try (ObjectOutputStream out = new ObjectOutputStream(resourceFile.openOutputStream()))\n        {\n            out.writeObject(new ArrayList<>(elements));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/utility/Processor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.utility;\n\nimport javax.annotation.processing.AbstractProcessor;\nimport javax.annotation.processing.ProcessingEnvironment;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.lang.model.SourceVersion;\nimport javax.lang.model.element.*;\nimport javax.tools.Diagnostic;\nimport java.util.Set;\n\n/**\n * Abstract processor.\n */\npublic abstract class Processor extends AbstractProcessor\n{\n    private boolean enabled = false;\n\n    private boolean printNotes = false;\n\n    private Diagnostic.Kind errorKind;\n\n    /**\n     * Default constructor.\n     */\n    public Processor()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public SourceVersion getSupportedSourceVersion()\n    {\n        return SourceVersion.latest();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public synchronized void init(final ProcessingEnvironment processingEnv)\n    {\n        enabled = System.getProperty(getEnabledPropertyName(), \"true\").equalsIgnoreCase(\"true\");\n        printNotes = System.getProperty(getPrintNotesPropertyName(), \"false\").equalsIgnoreCase(\"true\");\n        errorKind =\n            System.getProperty(getFailOnErrorPropertyName(), \"false\").equalsIgnoreCase(\"true\") ?\n                Diagnostic.Kind.ERROR :\n                Diagnostic.Kind.NOTE;\n        super.init(processingEnv);\n    }\n\n    /**\n     * Get enabled property name.\n     *\n     * @return enabled property name.\n     */\n    protected abstract String getEnabledPropertyName();\n\n    /**\n     * Get print notes property name.\n     *\n     * @return print notes property name.\n     */\n    protected abstract String getPrintNotesPropertyName();\n\n    /**\n     * Get fail on error property name.\n     *\n     * @return fail on error property name.\n     */\n    protected abstract String getFailOnErrorPropertyName();\n\n    /**\n     * Process annotations.\n     *\n     * @param annotations to be processed.\n     * @param roundEnv    environment info.\n     */\n    protected abstract void doProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv)\n    {\n        if (enabled)\n        {\n            doProcess(annotations, roundEnv);\n        }\n\n        return false;\n    }\n\n    /**\n     * Get doc comment.\n     *\n     * @param element element.\n     * @return comment.\n     */\n    protected String getDocComment(final Element element)\n    {\n        final String description = processingEnv.getElementUtils().getDocComment(element);\n        if (description == null)\n        {\n            error(\"no javadoc found\", element);\n            return \"NO DESCRIPTION FOUND\";\n        }\n\n        return description.trim();\n    }\n\n    /**\n     * On error hook.\n     *\n     * @param errMsg string.\n     */\n    protected void error(final String errMsg)\n    {\n        error(errMsg, null);\n    }\n\n    /**\n     * Error hook with extra context.\n     *\n     * @param errMsg  message.\n     * @param element element.\n     */\n    protected void error(final String errMsg, final Element element)\n    {\n        printMessage(errorKind, errMsg, element);\n    }\n\n    /**\n     * Add a note.\n     *\n     * @param msg note.\n     */\n    protected void note(final String msg)\n    {\n        note(msg, null);\n    }\n\n    /**\n     * Add note to an element.\n     *\n     * @param msg     note.\n     * @param element element.\n     */\n    protected void note(final String msg, final Element element)\n    {\n        if (printNotes)\n        {\n            printMessage(Diagnostic.Kind.NOTE, msg, element);\n        }\n    }\n\n    private void printMessage(final Diagnostic.Kind kind, final String msg, final Element element)\n    {\n        processingEnv.getMessager().printMessage(kind, msg, element);\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/validation/Grep.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.validation;\n\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.BiConsumer;\nimport java.util.stream.Collectors;\n\n/**\n * A utility class for running 'grep'.\n */\npublic final class Grep\n{\n    /**\n     * Execute a grep process.\n     *\n     * @param pattern the regex pattern passed to grep.\n     * @param sourceDir the base directory where the search should begin.\n     * @return a Grep object with the results of the action.\n     */\n    public static Grep execute(final String pattern, final String sourceDir)\n    {\n        final String commandString = \"grep -r -n -E '^\" + pattern + \"' \" + sourceDir;\n\n        try\n        {\n            // TODO make grep location configurable??\n            final Process process = new ProcessBuilder()\n                .redirectErrorStream(true)\n                .command(new String[] {\"/usr/bin/grep\", \"-r\", \"-n\", \"-E\", \"^\" + pattern, sourceDir})\n                .start();\n\n            final int exitCode = process.waitFor();\n\n            try (\n                InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream());\n                BufferedReader reader = new BufferedReader(inputStreamReader))\n            {\n                return new Grep(commandString, exitCode, reader.lines().collect(Collectors.toList()));\n            }\n        }\n        catch (final Exception e)\n        {\n            return new Grep(commandString, e);\n        }\n    }\n\n    private final String commandString;\n\n    private final int exitCode;\n\n    private final List<String> lines;\n\n    private final Exception e;\n\n    private Grep(final String commandString, final Exception e)\n    {\n        this.commandString = commandString;\n        this.exitCode = -1;\n        this.lines = Collections.emptyList();\n        this.e = e;\n    }\n\n    private Grep(final String commandString, final int exitCode, final List<String> lines)\n    {\n        this.commandString = commandString;\n        this.exitCode = exitCode;\n        this.lines = lines;\n        this.e = null;\n    }\n\n    /**\n     * Whether grep succeeded.\n     *\n     * @return whether grep succeeded.\n     */\n    public boolean success()\n    {\n        return success(true);\n    }\n\n    /**\n     * Determine if the process completed successfully.\n     *\n     * @param expectOneLine many of the usages expect only a single line to be found.\n     *                      if more than one are found, that counts as a failure\n     * @return whether grep succeeded.\n     */\n    public boolean success(final boolean expectOneLine)\n    {\n        if (this.e != null)\n        {\n            return false;\n        }\n\n        if (this.exitCode != 0)\n        {\n            return false;\n        }\n\n        return !expectOneLine || this.lines.size() == 1;\n    }\n\n    /**\n     * The command string that was executed.\n     *\n     * @return the command string that was executed.\n     */\n    public String getCommandString()\n    {\n        return this.commandString;\n    }\n\n    /**\n     * The filename and line number of the first line of output.\n     *\n     * @return the filename and line number of the first line of output.\n     */\n    public String getFilenameAndLine()\n    {\n        return getFilenameAndLine(0);\n    }\n\n    /**\n     * Get the file name and line, given the line number.\n     *\n     * @param lineNumber specify the line of output\n     * @return the filename and line number of the specified line of output\n     */\n    public String getFilenameAndLine(final int lineNumber)\n    {\n        final String[] pieces = this.lines.get(lineNumber).split(\":\");\n        return pieces[0] + \":\" + pieces[1];\n    }\n\n    /**\n     * The first line of output (minus the filename and line number).\n     *\n     * @return the first line of output (minus the filename and line number).\n     */\n    public String getOutput()\n    {\n        return getOutput(0);\n    }\n\n    /**\n     * Get the output given the line number.\n     *\n     * @param lineNumber specify the line of output\n     * @return the output of the specified line number (minus the filename and the line number)\n     */\n    public String getOutput(final int lineNumber)\n    {\n        return this.lines.get(lineNumber).split(\":\")[2];\n    }\n\n    /**\n     * Apply the consumer to all the lines in the result.\n     *\n     * @param action a BiConsumer that consumes the filename/line number and output for each line of output\n     */\n    public void forEach(final BiConsumer<String, String> action)\n    {\n        for (int i = 0; i < lines.size(); i++)\n        {\n            action.accept(getFilenameAndLine(i), getOutput(i));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/version/Version.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.version;\n\n/**\n * Version information for a specific component.\n */\npublic interface Version\n{\n    /**\n     * Major version portion.\n     *\n     * @return major version portion.\n     */\n    int majorVersion();\n\n    /**\n     * Minor version portion.\n     *\n     * @return minor version portion.\n     */\n    int minorVersion();\n\n    /**\n     * Patched version portion.\n     *\n     * @return patched version portion.\n     */\n    int patchVersion();\n\n    /**\n     * Git SHA.\n     *\n     * @return git SHA.\n     */\n    String gitSha();\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/version/VersionProcessor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.version;\n\nimport javax.annotation.processing.AbstractProcessor;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.annotation.processing.SupportedAnnotationTypes;\nimport javax.annotation.processing.SupportedOptions;\nimport javax.lang.model.SourceVersion;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.PackageElement;\nimport javax.lang.model.element.TypeElement;\nimport javax.tools.JavaFileObject;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Version processor.\n */\n@SupportedAnnotationTypes(\"io.aeron.version.Versioned\")\n@SupportedOptions({\"io.aeron.version\", \"io.aeron.gitsha\"})\npublic class VersionProcessor extends AbstractProcessor\n{\n    private static final String VERSION_IMPL =\n        \"\\n\" +\n        \"    @Override\\n\" +\n        \"    public String toString()\\n\" +\n        \"    {\\n\" +\n        \"        return VERSION;\\n\" +\n        \"    }\\n\" +\n        \"\\n\" +\n        \"    public int majorVersion()\\n\" +\n        \"    {\\n\" +\n        \"        return MAJOR_VERSION;\\n\" +\n        \"    }\\n\" +\n        \"\\n\" +\n        \"    public int minorVersion()\\n\" +\n        \"    {\\n\" +\n        \"        return MINOR_VERSION;\\n\" +\n        \"    }\\n\" +\n        \"\\n\" +\n        \"    public int patchVersion()\\n\" +\n        \"    {\\n\" +\n        \"        return PATCH_VERSION;\\n\" +\n        \"    }\\n\" +\n        \"\\n\" +\n        \"    public String gitSha()\\n\" +\n        \"    {\\n\" +\n        \"        return GIT_SHA;\\n\" +\n        \"    }\";\n\n    /**\n     * Default constructor.\n     */\n    public VersionProcessor()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public SourceVersion getSupportedSourceVersion()\n    {\n        return SourceVersion.latest();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv)\n    {\n        for (final TypeElement annotation : annotations)\n        {\n            final Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(annotation);\n            for (final Element element : elementsAnnotatedWith)\n            {\n                final PackageElement pkg = processingEnv.getElementUtils().getPackageOf(element);\n                final String packageName = pkg.getQualifiedName().toString();\n                final String className = element.getSimpleName() + \"Version\";\n\n                try\n                {\n                    final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(\n                        packageName + '.' + className);\n                    try (PrintWriter out = new PrintWriter(sourceFile.openWriter()))\n                    {\n                        final String versionString = processingEnv.getOptions().get(\"io.aeron.version\");\n                        final VersionInformation info = new VersionInformation(versionString);\n                        final String gitSha = processingEnv.getOptions().get(\"io.aeron.gitsha\");\n\n                        out.printf(\"package %s;%n\", packageName);\n                        out.println();\n                        out.printf(\"public class %s%n implements io.aeron.version.Version%n\", className);\n                        out.printf(\"{%n\");\n                        out.printf(\"    public static final String VERSION = \\\"%s\\\";%n\", versionString);\n                        out.printf(\"    public static final int MAJOR_VERSION = %s;%n\", info.major);\n                        out.printf(\"    public static final int MINOR_VERSION = %s;%n\", info.minor);\n                        out.printf(\"    public static final int PATCH_VERSION = %s;%n\", info.patch);\n                        out.printf(\"    public static final String GIT_SHA = \\\"%s\\\";%n\", gitSha);\n                        out.println(VERSION_IMPL);\n                        out.printf(\"}%n\");\n                    }\n                }\n                catch (final IOException e)\n                {\n                    throw new RuntimeException(e);\n                }\n            }\n        }\n\n        return false;\n    }\n\n    private static class VersionInformation\n    {\n        private static final Pattern VERSION_PATTERN = Pattern.compile(\"([0-9]+).([0-9]+).([0-9]+)(?:-.+)?\");\n        private final int major;\n        private final int minor;\n        private final int patch;\n\n        VersionInformation(final String versionString)\n        {\n            final Matcher matcher = VERSION_PATTERN.matcher(versionString);\n            if (!matcher.matches())\n            {\n                throw new IllegalArgumentException(\"The version string: '\" + versionString + \"' is not valid\");\n            }\n\n            major = Integer.parseInt(matcher.group(1));\n            minor = Integer.parseInt(matcher.group(2));\n            patch = Integer.parseInt(matcher.group(3));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/java/io/aeron/version/Versioned.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.version;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Annotation to indicate a version class should be generated.\n */\n@Target({ElementType.TYPE, ElementType.CONSTRUCTOR})\n@Retention(RetentionPolicy.SOURCE)\npublic @interface Versioned\n{\n}\n"
  },
  {
    "path": "aeron-annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor",
    "content": "io.aeron.version.VersionProcessor\nio.aeron.config.ConfigProcessor\nio.aeron.counter.CounterProcessor\n"
  },
  {
    "path": "aeron-archive/README.md",
    "content": "Aeron Archive\n===\n\n[![Javadocs](http://www.javadoc.io/badge/io.aeron/aeron-all.svg)](http://www.javadoc.io/doc/io.aeron/aeron-all)\n\nThe aeron-archive is a module which enables Aeron data stream recording and replay from durable storage. \n\nSamples can be found [here](https://github.com/aeron-io/aeron/blob/master/aeron-samples/scripts/archive/README.md) and\nsystems tests [here](https://github.com/aeron-io/aeron/tree/master/aeron-system-tests/src/test/java/io/aeron/archive).\n\nFeatures:\n\n- **Record:** service can record a particular subscription, described by `<channel, streamId>`. Each resulting image\n for the subscription will be recorded under a new `recordingId`. Local network publications are recorded using the spy\n feature for efficiency. If no subscribers are active then the recording can advance the stream by setting the\n `aeron.spies.simulate.connection` system property to true.\n\n- **Extend:** service can extend an existing recording by appending.\n\n- **Replay:** service can replay a recorded `recordingId` from a particular `position`, and for a particular `length`\n which can be `Aeron.NULL_VALUE` for an open-ended replay. An open-ended replay will stop when it reaches the stop\n position of a recording.\n\n- **Query:** the catalog for existing recordings, and the recorded position of an active recording.\n\n- **Truncate:** allows a stopped recording to have its length truncated, and if truncated to the start position then it\n is effectively deleted.\n\n- **Replay Merge:** allows a late joining subscriber of a recorded stream to replay a recording and then merge with the\n live stream for cut over if the consumer is fast enough to keep up.\n\n- **Replicate:** recordings can be replicated from a source to destination archive with the option to follow on with\n a live stream when the source is multicast. When using replication it is necessary to configure the replication channel\n for the destination archive with `aeron.archive.replication.channel`.\n\n- **Recording Storage Maintenance:** Manage the storage of large recordings by performing purge, detach, and delete\n operations, plus the ability to attach and migrate segments at the beginning of recordings. \n\nUsage\n=====\n\nProtocol\n=====\nMessages specification use SBE [aeron-archive-codecs.xml](https://github.com/aeron-io/aeron/blob/master/aeron-archive/src/main/resources/archive/aeron-archive-codecs.xml).\nThe Archive communicates via the following interfaces:\n\n - **Recording Events stream:** other parties can subscribe to events for the start,\n stop, and progress of recordings. These are the recording events messages specified in the codec.\n \n - **Control Request stream:** this allows clients to initiate replay or queries interactions with the archive.\n Requests have a correlationId sent on the initiating request. The `correlationId` is expected to be managed by\n the clients and is offered as a means for clients to track multiple concurrent requests. A request will typically\n involve the archive sending data back on the reply channel specified by the client on the `ConnectRequest`.\n\nA control session can be established with the Archive after a `ConnectRequest`. Operations happen within\nthe context of such a ControlSession which is allocated a `controlSessionId`.\n\nRecording Progress Events\n----\nAeron clients wishing to observe the Archive recordings lifecycle can do so by subscribing to the recording events\nchannel. The messages are described in the codec. To fully capture the state of the Archive a client could subscribe\nto these events as well as query for the full list of descriptors.\n\nRecording Signal Events\n----\nOn a control session signals can be tracked for when a recording starts and stop plus other operations like extend,\nreplicate, and live merge.\n\nRecording Durability\n----\nAn archive can be instructed to record streams, i.e. `<channel, streamId>` pairs. These streams are recorded with the\nfile sync level the archive has been launched with. Progress is reported on the recording events stream.\n\n- `aeron.archive.file.sync.level=0`: for normal writes to the OS page cache for background writing to disk.\n- `aeron.archive.file.sync.level=1`: for forcing the dirty data pages to disk. \n- `aeron.archive.file.sync.level=2`: for forcing the dirty data pages and file metadata to disk.\n\nWhen setting file sync level greater than zero it is also important to sync the archive catalog with the\n `aeron.archive.catalog.file.sync.level` to the same value.\n\nRecordings will be assigned a `recordingId` and a full description of the stream is captured in the Archive Catalog.\nThe Catalog chronicles the contents of an archive as `RecordingDescriptor`s which can be queried.\n\nThe progress of active recordings can be tracked using `AeronStat` to view the `rec-pos` counter for each stream.\n\nPersisted Format\n=====\nThe Archive is backed by 3 file types, all of which are expected to reside in the `archiveDir`.\n\n -  **Catalog (one per archive):** The catalog contains records of recording descriptors. The descriptors can\n be queried as described above. See the codec schema for full descriptor details.\n \n - **Recording Segment Files (many per recorded stream):** This is where the recorded data is kept.\n Recording segments follow the naming convention of: `<recordingId>-<segment base position>.rec`\n The Archive copies data as is from the recorded Image. As such the files follow the same convention\n as Aeron data streams. Data starts at `startPosition`, which translates into the offset\n `startPosition % termBufferLength` in the first segment file. From there one can read fragments\n as described by the `DataHeaderFlyweight` up to the `stopPosition`. Segment length is a multiple of `termBufferLength`.\n \n  - **Mark File:** This file contains the archive distinct error log and heartbeat timestamp to ensure two or more\n archives do not run in the same directory.\n\nMigration\n=====\nThe Archive may need to be migrated between major versions. This migration will be evident if attempting\nto run `ArchiveTool` with the `describe` command on the archive directory. A previous version will\nonly be readable by a previous version of `ArchiveTool`. To migrate the archive, please follow\nthe steps below.\n\n- Shutdown the Archive and ensure all recordings have a stop position.\n- Take a backup of the Archive directory.\n- Run `ArchiveTool` command `migrate`. Information on versions, etc. will be displayed. Errors\nwill also be displayed.\n- Run `ArchiveTool` command `verify` to check for validity.\n\nAPIs\n=====\nThe synchronous C and C++ wrapper APIs are complete.  The async APIs have yet to be implemented.\n"
  },
  {
    "path": "aeron-archive/src/main/c/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\ninclude(CheckSymbolExists)\n\nif (MSVC AND \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\")\n    set(AERON_LIB_WINSOCK_LIBS wsock32 ws2_32 Iphlpapi)\n    set(WSAPOLL_PROTOTYPE_EXISTS True)\nendif ()\n\nif (MSVC)\n    set(AERON_STATIC_LIB_LINK_OPTS \"\")\nelse()\n    # Because dlsym() is used to load strategies.\n    set(AERON_STATIC_LIB_LINK_OPTS \"-rdynamic\")\nendif()\n\nif (WSAPOLL_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_WSAPOLL)\nendif ()\n\nif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n    set(CMAKE_REQUIRED_DEFINITIONS \"-D_GNU_SOURCE\")\n    add_definitions(-D_DEFAULT_SOURCE)\nendif ()\n\ncheck_symbol_exists(fallocate \"fcntl.h\" FALLOCATE_PROTOTYPE_EXISTS)\ncheck_symbol_exists(posix_fallocate \"fcntl.h\" POSIX_FALLOCATE_PROTOTYPE_EXISTS)\ncheck_symbol_exists(F_PREALLOCATE \"fcntl.h\" F_PREALLOCATE_PROTOTYPE_EXISTS)\n\nif (FALLOCATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_FALLOCATE)\nendif ()\n\nif (POSIX_FALLOCATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_POSIX_FALLOCATE)\nendif ()\n\nif (F_PREALLOCATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_F_PREALLOCATE)\nendif ()\n\nif (MSVC AND \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\")\n    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)\n    set(BUILD_SHARED_LIBS ON)\nendif ()\n\nfind_package(Java REQUIRED)\n\nset(CODEC_SCHEMA ${ARCHIVE_CODEC_SCHEMA_DIR}/aeron-archive-codecs.xml)\nset(GENERATED_C_CODECS\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/archiveIdRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/attachSegmentsRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/authConnectRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/booleanType.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/boundedReplayRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/catalogHeader.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/challenge.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/challengeResponse.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/closeSessionRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/connectRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/controlResponse.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/controlResponseCode.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/deleteDetachedSegmentsRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/detachSegmentsRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/extendRecordingRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/extendRecordingRequest2.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/findLastMatchingRecordingRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/keepAliveRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/listRecordingRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/listRecordingSubscriptionsRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/listRecordingsForUriRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/listRecordingsRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/maxRecordedPositionRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/messageHeader.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/migrateSegmentsRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/purgeRecordingRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/purgeSegmentsRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingDescriptor.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingDescriptorHeader.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingPositionRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingProgress.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingSignal.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingSignalEvent.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingStarted.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingState.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingStopped.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/recordingSubscriptionDescriptor.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/replayRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/replayTokenRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/replicateRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/replicateRequest2.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/sourceLocation.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/startPositionRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/startRecordingRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/startRecordingRequest2.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/stopAllReplaysRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/stopPositionRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/stopRecordingByIdentityRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/stopRecordingRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/stopRecordingSubscriptionRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/stopReplayRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/stopReplicationRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/taggedReplicateRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/truncateRecordingRequest.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/varAsciiEncoding.h\n    ${ARCHIVE_CODEC_TARGET_DIR}/aeron_c_archive_client/varDataEncoding.h)\n\nadd_custom_command(OUTPUT ${GENERATED_C_CODECS}\n    COMMAND ${CMAKE_COMMAND}\n            -E env\n            JAVA_HOME=${JAVA_HOME}\n            BUILD_JAVA_HOME=${BUILD_JAVA_HOME}\n            BUILD_JAVA_VERSION=${BUILD_JAVA_VERSION}\n            ${GRADLE_WRAPPER}\n            -Dcodec.target.dir=${ARCHIVE_C_CODEC_TARGET_DIR}\n            :aeron-archive:generateCCodecs\n            --no-daemon\n            --console=plain\n            --quiet\n    DEPENDS ${CODEC_SCHEMA} aeron-all-jar\n    WORKING_DIRECTORY ${ARCHIVE_CODEC_WORKING_DIR}\n    COMMENT \"Generating C Archive codecs\"\n    VERBATIM)\n\nadd_custom_target(c_codecs DEPENDS ${GENERATED_C_CODECS})\n\nSET(SOURCE\n    client/aeron_archive_async_connect.c\n    client/aeron_archive_client.c\n    client/aeron_archive_client_version.c\n    client/aeron_archive_configuration.c\n    client/aeron_archive_context.c\n    client/aeron_archive_control_response_poller.c\n    client/aeron_archive_credentials_supplier.c\n    client/aeron_archive_proxy.c\n    client/aeron_archive_recording_descriptor_poller.c\n    client/aeron_archive_recording_pos.c\n    client/aeron_archive_recording_signal.c\n    client/aeron_archive_recording_subscription_descriptor_poller.c\n    client/aeron_archive_replay_merge.c\n    client/aeron_archive_replay_params.c\n    client/aeron_archive_replication_params.c\n)\n\nSET(HEADERS\n    client/aeron_archive.h\n    client/aeron_archive_async_connect.h\n    client/aeron_archive_client.h\n    client/aeron_archive_client_version.h\n    client/aeron_archive_configuration.h\n    client/aeron_archive_context.h\n    client/aeron_archive_control_response_poller.h\n    client/aeron_archive_credentials_supplier.h\n    client/aeron_archive_proxy.h\n    client/aeron_archive_recording_descriptor_poller.h\n    client/aeron_archive_recording_signal.h\n    client/aeron_archive_recording_subscription_descriptor_poller.h\n    client/aeron_archive_replay_params.h\n)\n\n# shared library\nadd_library(aeron_archive_c_client SHARED ${SOURCE} ${HEADERS})\nadd_library(aeron::aeron_archive_c_client ALIAS aeron_archive_c_client)\n\nadd_dependencies(aeron_archive_c_client c_codecs)\n\ntarget_include_directories(aeron_archive_c_client\n    PUBLIC \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\" \"$<INSTALL_INTERFACE:include/aeron>\"\n    PRIVATE ${ARCHIVE_CODEC_TARGET_DIR})\n\ntarget_link_libraries(\n    aeron_archive_c_client\n    aeron\n    ${CMAKE_THREAD_LIBS_INIT})\n\n# static library\nadd_library(aeron_archive_c_client_static STATIC ${SOURCE} ${HEADERS})\nadd_library(aeron::aeron_archive_c_client_static ALIAS aeron_archive_c_client_static)\n\nadd_dependencies(aeron_archive_c_client_static c_codecs)\n\ntarget_include_directories(aeron_archive_c_client_static\n    PUBLIC \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\" \"$<INSTALL_INTERFACE:include/aeron>\"\n    PRIVATE ${ARCHIVE_CODEC_TARGET_DIR})\n\ntarget_link_libraries(\n    aeron_archive_c_client_static\n    aeron_static\n    ${CMAKE_THREAD_LIBS_INIT})\n\nif (NOT WIN32)\n    set(CMAKE_THREAD_PREFER_PTHREAD TRUE)\n    set(THREADS_PREFER_PTHREAD_FLAG TRUE)\nendif ()\n\nif (AERON_INSTALL_TARGETS)\n    install(\n        TARGETS aeron_archive_c_client aeron_archive_c_client_static\n        EXPORT aeron-targets\n        RUNTIME DESTINATION lib\n        LIBRARY DESTINATION lib\n        ARCHIVE DESTINATION lib)\n    install(DIRECTORY ./ DESTINATION include/aeron FILES_MATCHING PATTERN \"aeron_archive.h\")\nendif ()\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/README.md",
    "content": "# Usage Guide\n\nThere are examples of nearly all the public C Archive Client APIs being used in the unit test file at `aeron-archive/src/test/c/client/aeron_archive_test.cpp`.\n\n### Establishing a connection to an archive\n\n#### Synchronous connect\n\n```\naeron_archive_context_t *ctx;\naeron_archive_t *archive = NULL;\n\naeron_archive_context_init(&ctx);\naeron_archive_connect(&archive, ctx);\naeron_archive_context_close(ctx);\n```\n\nThe archive context passed into connect is immediately copied, and so it can be closed immediately after connect returns.\n\n#### Asynchronous connect\n\n```\naeron_archive_context_t *ctx;\naeron_archive_async_connect_t *async;\naeron_archive_t *archive = NULL;\n\naeron_archive_context_init(&ctx);\naeron_archive_async_connect(&async, ctx);\naeron_archive_context_close(ctx);\n\nwhile (NULL == archive)\n{\n    idle();\n\n    aeron_archive_async_connect_poll(&archive, async);\n}\n```\n\nThe `connect_poll` call will set the archive pointer once the connection is complete.\n\n### Closing a connection\n\n```\naeron_archive_close(archive);\n```\n\n### Recording\n\nStarting and stopping:\n```\nint64_t subscription_id;\n\naeron_archive_start_recording(\n    &subscription_id,\n    archive,                                // an aeron_archive_t\n    \"aeron:udp?endpoint=localhost:3333\",    // channel to record\n    1234,                                   // stream id to record\n    AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n    false);\n...\n\naeron_archive_stop_recording_subscription(\n    archive,\n    subscription_id);\n```\n\n### List Recordings\n\nFirst, define a recording descriptor consumer callback:\n```\nvoid recording_descriptor_consumer(\n    aeron_archive_recording_descriptor_t *descriptor,\n    void *clientd)\n{\n    void *my_data = clientd;\n\n    descriptor->recording_id;\n}\n```\n\nList some number of recordings:\n```\nint32_t count;\nvoid *my_data\n\naeron_archive_list_recordings(\n    &count,                         // will be set to the number of descriptors found\n    archive,\n    initial_recording_id,           // recording id at which to start the listing\n    max_record_count,               // the max number of recordings to list\n    recording_descriptor_consumer,\n    my_data);\n```\n\nList recordings that match a pattern:\n```\naeron_archive_list_recordings_for_uri(\n    &count,\n    archive,\n    initial_recording_id,\n    max_record_count,\n    \"aeron:udp\",                    // a string fragment to match against\n    stream_id,                      // a stream id to match against\n    recording_descriptor_consumer,\n    my_data);\n```\n\nList a single recording using a recording id:\n```\n\naeron_archive_list_recording(\n    &count,\n    archive,\n    recording_id,                   // the recording id to list\n    recording_descriptor_consumer,\n    my_data);\n```\n\n### Replay\n\nFirst, initialize some replay parameters:\n```\naeron_archive_replay_params_t replay_params;\naeron_archive_replay_params_init(&replay_params);\n\nreplay_params.position = 0;                  // the position in the recording at which to start the replay\nreplay_params.length = stop_position;\nreplay_params.file_io_max_length = 4096;\n```\n\nStarting and stopping:\n```\nint64_t replay_session_id;\naeron_archive_start_replay(\n    &replay_session_id,        // will be set to the replay session id\n    archive,\n    recording_id,              // the recording id indicating the recording to replay\n    my_channel,                // the channel onto which the replay should be published\n    my_stream_id,              // the stream onto which the replay should be published\n    &replay_params);\n\n...\n\naeron_archive_stop_replay(archive, replay_session_id));\n```\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_H\n#define AERON_ARCHIVE_H\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n#include \"aeronc.h\"\n#include \"aeron_common.h\"\n\n#define ARCHIVE_ERROR_CODE_GENERIC (0)\n#define ARCHIVE_ERROR_CODE_ACTIVE_LISTING (1)\n#define ARCHIVE_ERROR_CODE_ACTIVE_RECORDING (2)\n#define ARCHIVE_ERROR_CODE_ACTIVE_SUBSCRIPTION (3)\n#define ARCHIVE_ERROR_CODE_UNKNOWN_SUBSCRIPTION (4)\n#define ARCHIVE_ERROR_CODE_UNKNOWN_RECORDING (5)\n#define ARCHIVE_ERROR_CODE_UNKNOWN_REPLAY (6)\n#define ARCHIVE_ERROR_CODE_MAX_REPLAYS (7)\n#define ARCHIVE_ERROR_CODE_MAX_RECORDINGS (8)\n#define ARCHIVE_ERROR_CODE_INVALID_EXTENSION (9)\n#define ARCHIVE_ERROR_CODE_AUTHENTICATION_REJECTED (10)\n#define ARCHIVE_ERROR_CODE_STORAGE_SPACE (11)\n#define ARCHIVE_ERROR_CODE_UNKNOWN_REPLICATION (12)\n#define ARCHIVE_ERROR_CODE_UNAUTHORISED_ACTION (13)\n\n#define AERON_NULL_POSITION AERON_NULL_VALUE\n\ntypedef struct aeron_archive_stct aeron_archive_t;\ntypedef struct aeron_archive_context_stct aeron_archive_context_t;\ntypedef struct aeron_archive_async_connect_stct aeron_archive_async_connect_t;\n\ntypedef struct aeron_archive_encoded_credentials_stct\n{\n    const char *data;\n    uint32_t length;\n}\naeron_archive_encoded_credentials_t;\n\n/**\n * Callback to return encoded credentials.\n *\n * @return encoded credentials to include with the connect request\n */\ntypedef aeron_archive_encoded_credentials_t *(*aeron_archive_credentials_encoded_credentials_supplier_func_t)(void *clientd);\n\n/**\n * Callback to return encoded credentials given a specific encoded challenge.\n *\n * @param encoded_challenge to use to generate the encoded credentials\n * @return encoded credentials to include with the challenge response\n */\ntypedef aeron_archive_encoded_credentials_t *(*aeron_archive_credentials_challenge_supplier_func_t)(\n    aeron_archive_encoded_credentials_t *encoded_challenge,\n    void *clientd);\n\n/**\n * Callback to return encoded credentials so they may be reused or freed.\n *\n * @param credentials to reuse or free\n */\ntypedef void (*aeron_archive_credentials_free_func_t)(\n    aeron_archive_encoded_credentials_t *credentials,\n    void *clientd);\n\n/**\n * Callback to allow execution of a delegating invoker to be run.\n */\ntypedef void (*aeron_archive_delegating_invoker_func_t)(void *clientd);\n\n/**\n * Struct containing the available replay parameters.\n */\ntypedef struct aeron_archive_replay_params_stct\n{\n    /**\n     * Set the counter id to be used for bounding the replay.\n     * Setting this value will trigger the sending of a bounded replay request, instead of a normal replay.\n     * By default, a bound will not be applied.\n     */\n    int32_t bounding_limit_counter_id;\n\n    /**\n     * The maximum size of a file operation when reading from the archive to execute the replay.\n     */\n    int32_t file_io_max_length;\n\n    /**\n     * The position at which to start the replay.\n     * By default, the stream is replayed from the start.\n     */\n    int64_t position;\n\n    /**\n     * The length of the recorded stream to replay.\n     * By default, the whole stream will be replayed.\n     * If set to INT64_MAX, it will follow a live recording.\n     */\n    int64_t length;\n\n    /**\n     * The token used for replays when the initiating image is not the one used to create the archive connection/session.\n     */\n    int64_t replay_token;\n\n    /**\n     * The subscription registration id used when doing a start replay using response channels and the response channel is already created.\n     */\n    int64_t subscription_registration_id;\n}\naeron_archive_replay_params_t;\n\n/**\n * Initialize an aeron_archive_replay_params_t with the default values.\n */\nint aeron_archive_replay_params_init(aeron_archive_replay_params_t *params);\n\n/**\n * Struct containing the available replication parameters.\n */\ntypedef struct aeron_archive_replication_params_stct\n{\n    /**\n     * The stop position for the replication.\n     * The default of AERON_NULL_VALUE indicates a continuous replication.\n     */\n    int64_t stop_position;\n\n    /**\n     * The recording id of the destination Archive to extend.\n     * The default of AERON_NULL_VALUE triggers the creation of a new recording at the destination Archive.\n     */\n    int64_t dst_recording_id;\n\n    /**\n     * Specify the destination for the live stream if a merge is required.\n     * The default of an empty string means no merge will occur.\n     */\n    const char *live_destination;\n\n    /**\n     * Specify the channel to use to replicate the recording.\n     * The default of an empty string will trigger the use of the context's default replication channel.\n     */\n    const char *replication_channel;\n\n    /**\n     * Specify the control address of the source archive when using response channels during replication.\n     */\n    const char *src_response_channel;\n\n    /**\n     * Specify a tag to apply to the channel used by the Archive's subscription for replication.\n     */\n    int64_t channel_tag_id;\n\n    /**\n     * Specify a subscription tag to apply to the channel used by the Archive's subscription for replication.\n     */\n    int64_t subscription_tag_id;\n\n    /**\n     * Specify the max length for file IO operations used in the replay.\n     */\n    int32_t file_io_max_length;\n\n    /**\n     * Specify session id to be used for the replicated file instead of the session id from the source archive.\n     * This is useful in cases where we are replicating the same recording in multiple stages.\n     */\n    int32_t replication_session_id;\n\n    /**\n     * Specify the encoded credentials that will be passed to the source archive for authentication.\n     * Currently, only simple authentication (i.e. not challenge/response) is supported for replication.\n     */\n    aeron_archive_encoded_credentials_t *encoded_credentials;\n}\naeron_archive_replication_params_t;\n\n/**\n * Initialize an aeron_archive_replication_params_t with the default values\n */\nint aeron_archive_replication_params_init(aeron_archive_replication_params_t *params);\n\n/**\n * Struct containing the details of a recording\n */\ntypedef struct aeron_archive_recording_descriptor_stct\n{\n    /// control session id of the request\n    int64_t control_session_id;\n\n    /// correlation id of the request\n    int64_t correlation_id;\n\n    /// id of the recording\n    int64_t recording_id;\n\n    /// timestamp of recording start\n    int64_t start_timestamp;\n\n    /// timestamp of recording stop\n    int64_t stop_timestamp;\n\n    /// the start position of the recording against the recorded publication\n    int64_t start_position;\n\n    /// the highest position reached for this recording\n    int64_t stop_position;\n\n    /// the initial term id of the recorded publication\n    int32_t initial_term_id;\n\n    /// the segment file length - a multiple of the term_buffer_length\n    int32_t segment_file_length;\n\n    /// term buffer length of the publication\n    int32_t term_buffer_length;\n\n    /// mtu length of the recorded publication\n    int32_t mtu_length;\n\n    /// session id of the recorded publication\n    int32_t session_id;\n\n    /// stream id of the recorded publication\n    int32_t stream_id;\n\n    /// channel used for recording subscription at the Aeron Archive\n    char *stripped_channel;\n\n    /// length of the stripped_channel string\n    size_t stripped_channel_length;\n\n    /// channel provided to start the recording request\n    char *original_channel;\n\n    /// length of the original_channel string\n    size_t original_channel_length;\n\n    /// source identity of the recorded stream\n    char *source_identity;\n\n    /// length of the source_identity string\n    size_t source_identity_length;\n}\naeron_archive_recording_descriptor_t;\n\n/**\n * Callback to return recording descriptors.\n */\ntypedef void (*aeron_archive_recording_descriptor_consumer_func_t)(\n    aeron_archive_recording_descriptor_t *recording_descriptor,\n    void *clientd);\n\n/**\n * Struct containing the details of a recording subscription\n */\ntypedef struct aeron_archive_recording_subscription_descriptor_stct\n{\n    /// control session id of the request\n    int64_t control_session_id;\n\n    /// correlation id of the request\n    int64_t correlation_id;\n\n    /// the subscription id - can be used to stop the recording subscription\n    int64_t subscription_id;\n\n    /// the stream id the subscription was registered with\n    int32_t stream_id;\n\n    /// the channel the subscription was registered with\n    char *stripped_channel;\n\n    /// the length of the stripped_channel string\n    size_t stripped_channel_length;\n}\naeron_archive_recording_subscription_descriptor_t;\n\n/**\n * Callback to return recording subscription descriptors.\n */\ntypedef void (*aeron_archive_recording_subscription_descriptor_consumer_func_t)(\n    aeron_archive_recording_subscription_descriptor_t *recording_subscription_descriptor,\n    void *clientd);\n\ntypedef enum aeron_archive_client_recording_signal_en\n{\n    AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_START = INT32_C(0),\n    AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_STOP = INT32_C(1),\n    AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_EXTEND = INT32_C(2),\n    AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_REPLICATE = INT32_C(3),\n    AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_MERGE = INT32_C(4),\n    AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_SYNC = INT32_C(5),\n    AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_DELETE = INT32_C(6),\n    AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_REPLICATE_END = INT32_C(7),\n    AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_NULL_VALUE = INT32_MIN\n}\naeron_archive_client_recording_signal_t;\n\n/**\n * Struct containing the details of a recording signal.\n */\ntypedef struct aeron_archive_recording_signal_stct\n{\n    /// Control session id of the originating session.\n    int64_t control_session_id;\n\n    /// Recording ID of the recording which transitioned.\n    int64_t recording_id;\n\n    /// Subscription ID of the subscription which captured the recording.\n    int64_t subscription_id;\n\n    /// The position of the recording at the time of transition.\n    int64_t position;\n\n    /// Raw code representing the operation the recording has undertaken.\n    int32_t recording_signal_code;\n}\naeron_archive_recording_signal_t;\n\n/**\n * Callback to return recording signals.\n */\ntypedef void (*aeron_archive_recording_signal_consumer_func_t)(\n    aeron_archive_recording_signal_t *recording_signal,\n    void *clientd);\n\ntypedef enum aeron_archive_source_location_en\n{\n    AERON_ARCHIVE_SOURCE_LOCATION_LOCAL = 0,\n    AERON_ARCHIVE_SOURCE_LOCATION_REMOTE = 1\n}\naeron_archive_source_location_t;\n\n/* context */\n\n/**\n * Create an aeron_archive_context_t struct.\n *\n * @param ctx context to create and initialize\n */\nint aeron_archive_context_init(aeron_archive_context_t **ctx);\n\n/**\n * Close and delete the aeron_archive_context_t struct.\n *\n * @param ctx context to delete\n */\nint aeron_archive_context_close(aeron_archive_context_t *ctx);\n\n/**\n * Specify the client used for communicating with the local Media Driver.\n * <p>\n * This client will be closed with the aeron_archive_t is closed if aeron_archive_context_set_owns_aeron_client is true.\n */\nint aeron_archive_context_set_aeron(aeron_archive_context_t *ctx, aeron_t *aeron);\naeron_t *aeron_archive_context_get_aeron(aeron_archive_context_t *ctx);\n\n/**\n * Specify whether or not this context owns the client and, therefore, takes responsibility for closing it.\n */\nint aeron_archive_context_set_owns_aeron_client(aeron_archive_context_t *ctx, bool owns_aeron_client);\nbool aeron_archive_context_get_owns_aeron_client(aeron_archive_context_t *ctx);\n\n/**\n * Specify the top level Aeron directory used for communication between the Aeron client and the Media Driver.\n */\nint aeron_archive_context_set_aeron_directory_name(aeron_archive_context_t *ctx, const char *aeron_directory_name);\nconst char *aeron_archive_context_get_aeron_directory_name(aeron_archive_context_t *ctx);\n\n/**\n * Specify the channel used for sending requests to the Aeron Archive.\n */\nint aeron_archive_context_set_control_request_channel(aeron_archive_context_t *ctx, const char *control_request_channel);\nconst char *aeron_archive_context_get_control_request_channel(aeron_archive_context_t *ctx);\n\n/**\n * Specify the stream used for sending requests to the Aeron Archive.\n */\nint aeron_archive_context_set_control_request_stream_id(aeron_archive_context_t *ctx, int32_t control_request_stream_id);\nint32_t aeron_archive_context_get_control_request_stream_id(aeron_archive_context_t *ctx);\n\n/**\n * Specify the channel used for receiving responses from the Aeron Archive.\n */\nint aeron_archive_context_set_control_response_channel(aeron_archive_context_t *ctx, const char *control_response_channel);\nconst char *aeron_archive_context_get_control_response_channel(aeron_archive_context_t *ctx);\n\n/**\n * Specify the stream used for receiving responses from the Aeron Archive.\n */\nint aeron_archive_context_set_control_response_stream_id(aeron_archive_context_t *ctx, int32_t control_response_stream_id);\nint32_t aeron_archive_context_get_control_response_stream_id(aeron_archive_context_t *ctx);\n\n/**\n * Specify the channel used for receiving recording events from the Aeron Archive.\n */\nint aeron_archive_context_set_recording_events_channel(aeron_archive_context_t *ctx, const char *recording_events_channel);\nconst char *aeron_archive_context_get_recording_events_channel(aeron_archive_context_t *ctx);\n\n/**\n * Specify the stream id used for recording events channel.\n */\nint aeron_archive_context_set_recording_events_stream_id(aeron_archive_context_t *ctx, int32_t recording_events_stream_id);\nint32_t aeron_archive_context_get_recording_events_stream_id(aeron_archive_context_t *ctx);\n\n/**\n * Specify the message timeout, in nanoseconds, to wait for sending or receiving a message.\n */\nint aeron_archive_context_set_message_timeout_ns(aeron_archive_context_t *ctx, uint64_t message_timeout_ns);\nuint64_t aeron_archive_context_get_message_timeout_ns(aeron_archive_context_t *ctx);\n\n/**\n * Specify the number of retry attempts when offering messages to the archive.\n */\nint aeron_archive_context_set_message_retry_attempts(aeron_archive_context_t *ctx, uint32_t message_retry_attempts);\nuint32_t aeron_archive_context_get_message_retry_attempts(aeron_archive_context_t *ctx);\n\n/**\n * Specify the default term buffer length for the control request/response channels.\n */\nint aeron_archive_context_set_control_term_buffer_length(aeron_archive_context_t *ctx, size_t control_term_buffer_length);\nsize_t aeron_archive_context_get_control_term_buffer_length(aeron_archive_context_t *ctx);\n\n/**\n * Specify the default MTU length for the control request/response channels.\n */\nint aeron_archive_context_set_control_mtu_length(aeron_archive_context_t *ctx, size_t control_mtu_length);\nsize_t aeron_archive_context_get_control_mtu_length(aeron_archive_context_t *ctx);\n\n/**\n * Specify the default MTU length for the control request/response channels.\n */\nint aeron_archive_context_set_control_term_buffer_sparse(aeron_archive_context_t *ctx, bool control_term_buffer_sparse);\nbool aeron_archive_context_get_control_term_buffer_sparse(aeron_archive_context_t *ctx);\n\n/**\n * Specify client name to identify this client on the archive side.\n */\nint aeron_archive_context_set_client_name(aeron_archive_context_t *context, const char *value);\nconst char *aeron_archive_context_get_client_name(aeron_archive_context_t *context);\n\n/**\n * Specify the idle strategy function and associated state used by the client between polling calls.\n */\nint aeron_archive_context_set_idle_strategy(\n    aeron_archive_context_t *ctx,\n    aeron_idle_strategy_func_t idle_strategy_func,\n    void *idle_strategy_state);\n\n/**\n * Specify the various credentials callbacks to use when connecting to the Aeron Archive.\n */\nint aeron_archive_context_set_credentials_supplier(\n    aeron_archive_context_t *ctx,\n    aeron_archive_credentials_encoded_credentials_supplier_func_t encoded_credentials,\n    aeron_archive_credentials_challenge_supplier_func_t on_challenge,\n    aeron_archive_credentials_free_func_t on_free,\n    void *clientd);\n\n/**\n * Specify the callback to which recording signals are dispatched while polling for control responses.\n */\nint aeron_archive_context_set_recording_signal_consumer(\n    aeron_archive_context_t *ctx,\n    aeron_archive_recording_signal_consumer_func_t on_recording_signal,\n    void *clientd);\n\n/**\n * Specify the callback to which errors are dispatched while executing archive client commands.\n */\nint aeron_archive_context_set_error_handler(\n    aeron_archive_context_t *ctx,\n    aeron_error_handler_t error_handler,\n    void *clientd);\n\n/**\n * Specify the callback to be invoked in addition to any invoker used by the Aeron instance.\n * <p>\n * Useful when running in a low thread count environment.\n */\nint aeron_archive_context_set_delegating_invoker(\n    aeron_archive_context_t *ctx,\n    aeron_archive_delegating_invoker_func_t delegating_invoker_func,\n    void *clientd);\n\n/* client */\n\n/**\n * Begin an attempt at creating a connection which can be completed by calling aeron_archive_async_connect_poll.\n *\n * @param async aeron_archive_async_connect_t to create and initialize\n * @param ctx aeron_archive_context_t for the archive connection\n */\nint aeron_archive_async_connect(aeron_archive_async_connect_t **async, aeron_archive_context_t *ctx);\n\n/**\n * Poll for a complete connection.\n *\n * @param aeron_archive aeron_archive_t that will be created/initialized upon successful connection\n * @param async aeron_archive_async_connect_t to poll\n * @return -1 for failure, 0 for 'try again', and 1 for success\n * <p>\n * Note that after a return of either -1 or 1, the provided aeron_archive_async_connect_t will have been deleted.\n * <p>\n * Also note that after a return of 1, the aeron_archive pointer will be set to a ready to use aeron_archive_t.\n */\nint aeron_archive_async_connect_poll(aeron_archive_t **aeron_archive, aeron_archive_async_connect_t *async);\n\n/**\n * Connect to an Aeron Archive.\n *\n * @param aeron_archive aeron_archive_t that will be created/initialized upon successful connection\n * @param ctx aeron_archive_context_t for the archive connection\n */\nint aeron_archive_connect(aeron_archive_t **aeron_archive, aeron_archive_context_t *ctx);\n\n/**\n * Close the connection to the Aeron Archive and free up associated resources.\n */\nint aeron_archive_close(aeron_archive_t *aeron_archive);\n\n/**\n * Retrieve the underlying aeron_archive_context_t used to configure the provided aeron_archive_t.\n */\naeron_archive_context_t *aeron_archive_get_archive_context(aeron_archive_t *aeron_archive);\n\n/**\n * Retrieve the underlying aeron_archive_context_t used to configure the provided aeron_archive_t.\n * <p>\n * Additionally, calling this function transfers ownership of the returned aeron_archive_context_t to the caller.\n * i.e. it is now the the caller's responsibility to close the context.\n * This is useful when wrapping the C library in other, higher level languages.\n */\naeron_archive_context_t *aeron_archive_get_and_own_archive_context(aeron_archive_t *aeron_archive);\n\n/**\n * Retrieve the archive id of the connected Aeron Archive.\n */\nint64_t aeron_archive_get_archive_id(aeron_archive_t *aeron_archive);\n\n/**\n * Retrieve the underlying aeron_subscription_t used for reading responses from the connected Aeron Archive.\n */\naeron_subscription_t *aeron_archive_get_control_response_subscription(aeron_archive_t *aeron_archive);\n\n/**\n * Retrieve the underlying aeron_subscription_t used for reading responses from the connected Aeron Archive.\n * <p>\n * Additionally, calling this function transfers ownership of the returned aeron_subscription_t to the caller.\n * i.e. it is now the caller's responsibility to close the subscription.\n * This is useful when wrapping the C library in other, high level languages.\n */\naeron_subscription_t *aeron_archive_get_and_own_control_response_subscription(aeron_archive_t *aeron_archive);\n\n// helpful for testing... not necessarily useful otherwise\nint64_t aeron_archive_control_session_id(aeron_archive_t *aeron_archive);\n\n/**\n * Poll for recording signals, dispatching them to the configured aeron_archive_recording_signal_consumer_func_t in the context\n *\n * @param count_p out param that indicates the number of recording signals dispatched.\n * @return 0 for success, -1 for failure.\n */\nint aeron_archive_poll_for_recording_signals(int32_t *count_p, aeron_archive_t *aeron_archive);\n\n/**\n * Poll the response stream once for an error.\n * If another message is present then it will be skipped over, so only call when not expecting another response.\n *\n * @return 0 if an error sent from the Aeron Archive is found, in which case, the provided buffer contains the error message.\n * If there was no error, the buffer will be an empty string.\n * <p>\n * -1 if an error occurs while attempting to read from the subscription.\n */\nint aeron_archive_poll_for_error_response(aeron_archive_t *aeron_archive, char *buffer, size_t buffer_length);\n\n/**\n * Poll the response stream once for an error.\n *\n * @return 0 if no error is found OR if an error is found but an error handler is specified in the context.\n * <p>\n * -1 if an error is found and no error handler is specified.  The error message can be retrieved by calling aeron_errmsg()\n */\nint aeron_archive_check_for_error_response(aeron_archive_t *aeron_archive);\n\n/**\n * Add a publication and set it up to be recorded.\n *\n * @param publication_p out param set to the aeron_publication_t upon success\n * @param aeron_archive the archive client\n * @param channel the channel for the publication\n * @param stream_id the stream id for the publication\n */\nint aeron_archive_add_recorded_publication(\n    aeron_publication_t **publication_p,\n    aeron_archive_t *aeron_archive,\n    const char *channel,\n    int32_t stream_id);\n\n/**\n * Add an exclusive publication and set it up to be recorded.\n *\n * @param publication_p out param set to the aeron_exclusive_publication_t upon success\n * @param aeron_archive the archive client\n * @param channel the channel for the exclusive publication\n * @param stream_id the stream id for the exclusive publication\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_add_recorded_exclusive_publication(\n    aeron_exclusive_publication_t **exclusive_publication_p,\n    aeron_archive_t *aeron_archive,\n    const char *channel,\n    int32_t stream_id);\n\n/**\n * Start recording a channel/stream pairing.\n * <p>\n * Channels that include session id parameters are considered different than channels without session ids.\n * If a publication matches both a session id specific channel recording and a non session id specific recording,\n * it will be recorded twice.\n *\n * @param subscription_id_p out param set to the subscription id of the recording\n * @param aeron_archive the archive client\n * @param recording_channel the channel of the publication to be recorded\n * @param recording_stream_id the stream id of the publication to be recorded\n * @param source_location the source location of the publication to be recorded\n * @param auto_stop should the recording be automatically stopped when complete\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_start_recording(\n    int64_t *subscription_id_p,\n    aeron_archive_t *aeron_archive,\n    const char *recording_channel,\n    int32_t recording_stream_id,\n    aeron_archive_source_location_t source_location,\n    bool auto_stop);\n\n/**\n * Fetch the position recorded for the specified recording.\n *\n * @param recording_position_p out param set to the recording position of the specified recording\n * @param aeron_archive the archive client\n * @param recording_id the active recording id\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_get_recording_position(\n    int64_t *recording_position_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id);\n\n/**\n * Fetch the start position for the specified recording.\n *\n * @param start_position_p out param set to the start position of the specified recording\n * @param aeron_archive the archive client\n * @param recording_id the active recording id\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_get_start_position(\n    int64_t *start_position_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id);\n\n/**\n * Fetch the stop position for the specified recording.\n *\n * @param stop_position_p out param set to the stop position of the specified recording\n * @param aeron_archive the archive client\n * @param recording_id the active recording id\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_get_stop_position(\n    int64_t *stop_position_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id);\n\n/**\n * Fetch the stop or active position for the specified recording.\n *\n * @param max_recorded_position_p out param set to the stop or active position of the specified recording\n * @param aeron_archive the archive client\n * @param recording_id the active recording id\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_get_max_recorded_position(\n    int64_t *max_recorded_position_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id);\n\n/**\n * Stop recording for the specified subscription id.\n * This is the subscription id returned from aeron_archive_start_recording or aeron_archive_extend_recording.\n *\n * @param aeron_archive the archive client\n * @param subscription_id the subscription id for the recording in the Aeron Archive\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_stop_recording_subscription(\n    aeron_archive_t *aeron_archive,\n    int64_t subscription_id);\n\n/**\n * Try to stop a recording for the specified subscription id.\n * This is the subscription id returned from aeron_archive_start_recording or aeron_archive_extend_recording.\n *\n * @param stopped_p out param indicating true if stopped, or false if the subscription is not currently active\n * @param aeron_archive the archive client\n * @param subscription_id the subscription id for the recording in the Aeron Archive\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_try_stop_recording_subscription(\n    bool *stopped_p,\n    aeron_archive_t *aeron_archive,\n    int64_t subscription_id);\n\n/**\n * Stop recording for the specified channel and stream.\n * <p>\n * Channels that include session id parameters are considered different than channels without session ids.\n * Stopping a recording on a channel without a session id parameter will not stop the recording of any\n * session id specific recordings that use the same channel and stream id.\n *\n * @param aeron_archive the archive client\n * @param channel the channel of the recording to be stopped\n * @param stream_id the stream id of the recording to be stopped\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_stop_recording_channel_and_stream(\n    aeron_archive_t *aeron_archive,\n    const char *channel,\n    int32_t stream_id);\n\n/**\n * Try to stop recording for the specified channel and stream.\n * <p>\n * Channels that include session id parameters are considered different than channels without session ids.\n * Stopping a recording on a channel without a session id parameter will not stop the recording of any\n * session id specific recordings that use the same channel and stream id.\n *\n * @param stopped_p out param indicating true if stopped, or false if the channel/stream pair is not currently active\n * @param aeron_archive the archive client\n * @param channel the channel of the recording to be stopped\n * @param stream_id the stream id of the recording to be stopped\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_try_stop_recording_channel_and_stream(\n    bool *stopped_p,\n    aeron_archive_t *aeron_archive,\n    const char *channel,\n    int32_t stream_id);\n\n/**\n * Stop recording for the specified recording id.\n *\n * @param stopped_p out param indicating true if stopped, or false if the recording is not currently active\n * @param aeron_archive the archive client\n * @param recording_id the id of the recording to be stopped\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_try_stop_recording_by_identity(\n    bool *stopped_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id);\n\n/**\n * Stop recording a session id specific recording that pertains to the given publication.\n *\n * @param aeron_archive the archive client\n * @param publication the publication to stop recording\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_stop_recording_publication(\n    aeron_archive_t *aeron_archive,\n    aeron_publication_t *publication);\n\n/**\n * Stop recording a session id specific recording that pertains to the given exclusive publication.\n *\n * @param aeron_archive the archive client\n * @param exclusive_publication the exclusive publication to stop recording\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_stop_recording_exclusive_publication(\n    aeron_archive_t *aeron_archive,\n    aeron_exclusive_publication_t *exclusive_publication);\n\n/**\n * Find the last recording that matches the given criteria.\n *\n * @param recording_id_p out param for the recording id that matches\n * @param aeron_archive the archive client\n * @param min_recording_id the lowest recording id to search back to\n * @param channel_fragment for a 'contains' match on the original channel stored with the Aeron Archive\n * @param stream_id the stream id of the recording\n * @param session_id the session id of the recording\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_find_last_matching_recording(\n    int64_t *recording_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t min_recording_id,\n    const char *channel_fragment,\n    int32_t stream_id,\n    int32_t session_id);\n\n/**\n * List a recording descriptor for a single recording id.\n *\n * @param count_p out param indicating the number of descriptors found\n * @param aeron_archive the archive client\n * @param recording_id the id of the recording\n * @param recording_descriptor_consumer to be called for each descriptor\n * @param recording_descriptor_consumer_clientd to be passed for each descriptor\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_list_recording(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd);\n\n/**\n * List all recording descriptors starting at a particular recording id, with a limit of total descriptors delivered.\n *\n * @param count_p out param indicating the number of descriptors found\n * @param aeron_archive the archive client\n * @param from_recording_id the id at which to begin the listing\n * @param record_count the limit of total descriptors to deliver\n * @param recording_descriptor_consumer to be called for each descriptor\n * @param recording_descriptor_consumer_clientd to be passed for each descriptor\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_list_recordings(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t from_recording_id,\n    int32_t record_count,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd);\n\n/**\n * List all recording descriptors for a given channel fragment and stream id, starting at a particular recording id, with a limit of total descriptors delivered.\n *\n * @param count_p out param indicating the number of descriptors found\n * @param aeron_archive the archive client\n * @param from_recording_id the id at which to begin the listing\n * @param record_count the limit of total descriptors to deliver\n * @param channel_fragment for a 'contains' match on the original channel stored with the Aeron Archive\n * @param stream_id the stream id of the recording\n * @param recording_descriptor_consumer to be called for each descriptor\n * @param recording_descriptor_consumer_clientd to be passed for each descriptor\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_list_recordings_for_uri(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t from_recording_id,\n    int32_t record_count,\n    const char *channel_fragment,\n    int32_t stream_id,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd);\n\n/**\n * Start a replay\n * <p>\n * The lower 32-bits of the replay session id contain the session id of the image of the received replay\n * and can be obtained by casting the replay session id to an int32_t.\n * All 64-bits are required to uniquely identify the replay when calling aeron_archive_stop_replay.\n *\n * @param replay_session_id_p out param set to the replay session id\n * @param aeron_archive the archive client\n * @param recording_id the id of the recording\n * @param replay_channel the channel to which the replay should be sent\n * @param replay_stream_id the stream id to which the replay should be sent\n * @param params the aeron_archive_replay_params_t that control the behaviour of the replay\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_start_replay(\n    int64_t *replay_session_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params);\n\n/**\n * Start a replay.\n *\n * @param subscription_p out param set to the subscription created for consuming the replay\n * @param aeron_archive the archive client\n * @param recording_id the id of the recording\n * @param replay_channel the channel to which the replay should be sent\n * @param replay_stream_id the stream id to which the replay should be sent\n * @param params the aeron_archive_replay_params_t that control the behaviour of the replay\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_replay(\n    aeron_subscription_t **subscription_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params);\n\n/**\n * Truncate a stopped recording to the specified position.\n * The position must be less than the stopped position.\n * The position must be on a fragment boundary.\n * Truncating a recording to the start position effectively deletes the recording.\n *\n * @param count_p out param set to the number of segments deleted\n * @param aeron_archive the archive client\n * @param recording_id the id of the recording\n * @param position the position to which the recording will be truncated\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_truncate_recording(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    int64_t position);\n\n/**\n * Stop a replay session.\n *\n * @param aeron_archive the archive client\n * @param replay_session_id the replay session id indicating the replay to stop\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_stop_replay(\n    aeron_archive_t *aeron_archive,\n    int64_t replay_session_id);\n\n/**\n * Stop all replays matching a recording id.\n * If recording_id is AERON_NULL_VALUE then match all replays.\n *\n * @param aeron_archive the archive client\n * @param recording_id the id of the recording for which all replays will be stopped\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_stop_all_replays(\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id);\n\n/**\n * List active recording subscriptions in the Aeron Archive.\n * These are the result of calling aeron_archive_start_recording or aeron_archive_extend_recording.\n * The subscription id in the returned descriptor can be used when calling aeron_archive_stop_recording_subscription.\n *\n * @param count_p out param set to the count of matched subscriptions\n * @param aeron_archive the archive client\n * @param pseudo_index the index into the active list at which to begin listing\n * @param subscription_count the limit of total descriptors to deliver\n * @param channel_fragment for a 'contains' match on the original channel stored with the Aeron Archive\n * @param stream_id the stream id of the recording\n * @param apply_stream_id whether or not the stream id should be matched\n * @param recording_subscription_descriptor_consumer to be called for each descriptor\n * @param recording_subscription_descriptor_consumer_clientd to be passed for each descriptor\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_list_recording_subscriptions(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int32_t pseudo_index,\n    int32_t subscription_count,\n    const char *channel_fragment,\n    int32_t stream_id,\n    bool apply_stream_id,\n    aeron_archive_recording_subscription_descriptor_consumer_func_t recording_subscription_descriptor_consumer,\n    void *recording_subscription_descriptor_consumer_clientd);\n\n/**\n * Purge a stopped recording.\n * i.e. Mark the recording as INVALID at the Archive and delete the corresponding segment files.\n * The space in the Catalog will be reclaimed upon compaction.\n *\n * @param deleted_segments_count_p out param set to the number of deleted segments\n * @param aeron_archive the archive client\n * @param recording_id the id of the stopped recording to be purged\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_purge_recording(\n    int64_t *deleted_segments_count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id);\n\n/**\n * Extend an existing, non-active recording for a channel and stream pairing.\n * <p>\n * The channel must be configured with the initial position from which it will be extended.\n * This can be done with aeron_uri_string_builder_set_initial_position.\n * The details required to initialize can be found by calling aeron_archive_list_recording.\n *\n * @param subscription_id_p out param set to the subscription id of the recording\n * @param aeron_archive the archive client\n * @param recording_id the id of the existing recording\n * @param recording_channel the channel of the publication to be recorded\n * @param recording_stream_id the stream id of the publication to be recorded\n * @param source_location the source location of the publication to be recorded\n * @param auto_stop should the recording be automatically stopped when complete\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_extend_recording(\n    int64_t *subscription_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *recording_channel,\n    int32_t recording_stream_id,\n    aeron_archive_source_location_t source_location,\n    bool auto_stop);\n\n/**\n * Replicate a recording from a source Archive to a destination.\n * This can be considered a backup for a primary Archive.\n * The source recording will be replayed via the provided replay channel and use the original stream id.\n * The behavior of the replication will be governed by the values specified in the aeron_archive_replication_params_t.\n * <p>\n * For a source recording that is still active, the replay can merge with the live stream and then follow it directly and no longer require the replay from the source.\n * This would require a multicast live destination.\n * <p>\n * Errors will be reported asynchronously and can be checked for with aeron_archive_check_for_error_response and aeron_archive_poll_for_error_response.\n *\n * @param replication_id_p out param set to the replication id that can be used to stop the replication\n * @param aeron_archive the archive client\n * @param src_recording_id the recording id that must exist at the source Archive\n * @param src_control_channel remote control channel for the source archive on which to instruct the replay\n * @param src_control_stream_id remote control stream id for the source archive on which to instruct the replay\n * @param params optional parameters to configure the behavior of the replication\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_replicate(\n    int64_t *replication_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t src_recording_id,\n    const char *src_control_channel,\n    int32_t src_control_stream_id,\n    aeron_archive_replication_params_t *params);\n\n/**\n * Stop a replication by the replication id.\n *\n * @param aeron_archive the archive client\n * @param replication_id the replication id retrieved when calling aeron_archive_replicate\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_stop_replication(\n    aeron_archive_t *aeron_archive,\n    int64_t replication_id);\n\n/**\n * Try to stop a replication by the replication id.\n *\n * @param stopped_p out param indicating true if stopped, or false if the recording is not currently active\n * @param aeron_archive the archive client\n * @param replication_id the replication id retrieved when calling aeron_archive_replicate\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_try_stop_replication(\n    bool *stopped_p,\n    aeron_archive_t *aeron_archive,\n    int64_t replication_id);\n\n/**\n * Detach segments from the beginning of a recording up to the provided new start position.\n * <p>\n * The new start position must be the first byte position of a segment after the existing start position.\n * <p>\n * It is not possible to detach segments which are active for recording or being replayed.\n *\n * @param aeron_archive the archive client\n * @param recording_id the id of an existing recording\n * @param new_start_position the new starting position for the recording after the segments are detached\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_detach_segments(\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    int64_t new_start_position);\n\n/**\n * Delete segments which have been previously detached from a recording.\n *\n * @param count_p out param set to the number of segments deleted\n * @param aeron_archive the archive client\n * @param recording_id the id of an existing recording\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_delete_detached_segments(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id);\n\n/**\n * Purge (Detach and delete) segments from the beginning of a recording up to the provided new start position.\n * <p>\n * The new start position must be the first byte position of a segment after the existing start position.\n * <p>\n * It is not possible to detach segments which are active for recording or being replayed.\n *\n * @param count_p out param set to the number of segments deleted\n * @param aeron_archive the archive client\n * @param recording_id the id of an existing recording\n * @param new_start_position the new starting position for the recording after the segments are detached\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_purge_segments(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    int64_t new_start_position);\n\n/**\n * Attach segments to the beginning of a recording to restore history that was previously detached.\n * <p>\n * Segment files must match the existing recording and join exactly to the start position of the recording they are being attached to.\n *\n * @param count_p out param set to the number of segments attached\n * @param aeron_archive the archive client\n * @param recording_id the id of an existing recording\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_attach_segments(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id);\n\n/**\n * Migrate segments from a source recording and attach them to the beginning of a destination recording.\n * <p>\n * The source recording must match the destination recording for segment length, term length, mtu length,\n * stream id, plus the stop position and term id of the source must join with the start position of the destination\n * and be on a segment boundary.\n * <p>\n * The source recording will be effectively truncated back to its start position after the migration.\n *\n * @param count_p out param set to the number of segments deleted\n * @param aeron_archive the archive client\n * @param src_recording_id the id of an existing recording from which segments will be migrated\n * @param dst_recording_id the id of an existing recording to which segments will be migrated\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_migrate_segments(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t src_recording_id,\n    int64_t dst_recording_id);\n\n/**\n * Update the channel for a recording, i.e. replace original and stripped channel information in the catalog.\n *\n * @param aeron_archive the archive client\n * @param recording_id the id of the recording.\n * @param new_channel to use in the catalogue.\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_update_channel(aeron_archive_t *aeron_archive, int64_t recording_id, const char *new_channel);\n\n/**\n * Position of the recorded stream at the base of a segment file.\n * <p>\n * If a recording starts within a term then the base position can be before the recording started.\n *\n * @param start_position start position of the stream\n * @param position position in the stream to calculate the segment base position from.\n * @param term_buffer_length term buffer length of the stream\n * @param segment_file_length segment file length, which is a multiple of term buffer length\n * @return the position of the recorded stream at the beginning of a segment file\n */\nint64_t aeron_archive_segment_file_base_position(\n    int64_t start_position,\n    int64_t position,\n    int32_t term_buffer_length,\n    int32_t segment_file_length);\n\n/**\n * Find the active counter id for a stream based on the recording id.\n *\n * @param counters_reader an aeron_counters_reader_t to search within\n * @param recording_id the recording id of an active recording\n * @return the counter id if found, otherwise AERON_NULL_COUNTER_ID\n */\nint32_t aeron_archive_recording_pos_find_counter_id_by_recording_id(aeron_counters_reader_t *counters_reader, int64_t recording_id);\n\n/**\n * Find the active counter id for a stream based on the session id.\n *\n * @param counters_reader an aeron_counters_reader_t to search within\n * @param session_id the session id of an active recording\n * @return the counter id if found, otherwise AERON_NULL_COUNTER_ID\n */\nint32_t aeron_archive_recording_pos_find_counter_id_by_session_id(aeron_counters_reader_t *counters_reader, int32_t session_id);\n\n/**\n * Get the recording id for a given counter id.\n *\n * @param counters_reader an aeron_counters_reader_t to search within\n * @param counter_id the counter id of an active recording\n * @return the recording id if found, otherwise AERON_NULL_COUNTER_ID\n */\nint64_t aeron_archive_recording_pos_get_recording_id(aeron_counters_reader_t *counters_reader, int32_t counter_id);\n\n/**\n * Get the source identity for the recording.\n * <p>\n * See source_identity in aeron_image_constants_t.\n *\n * @param counters_reader an aeron_counters_reader_t to search within\n * @param counter_id the counter id of an active recording\n * @param dst a destination buffer into which the source identity will be written\n * @param len_p a pointer to a size_t that initially indicates the length of the dst buffer.  After the function return successfully, len_p will be set to the length of the source identity string in dst\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_recording_pos_get_source_identity(aeron_counters_reader_t *counters_reader, int32_t counter_id, const char *dst, size_t *len_p);\n\n/**\n * Is the recording counter still active?\n *\n * @param is_active out param set to true if the counter is still active\n * @param counters_reader an aeron_counters_reader_t to search within\n * @param counter_id the counter id to search for\n * @param recording_id the recording id to match against\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_recording_pos_is_active(bool *is_active, aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t recording_id);\n\n/* replay/merge */\n\ntypedef struct aeron_archive_replay_merge_stct aeron_archive_replay_merge_t;\n\n#define REPLAY_MERGE_PROGRESS_TIMEOUT_DEFAULT_MS (5 * 1000)\n\n/**\n * Create an aeron_archive_replay_merge_t to manage the merging of a replayed stream into a live stream.\n *\n * @param replay_merge the aeron_archive_replay_merge_t to create and initialize\n * @param subscription the subscription to use for the replay and live stream.  Must be a multi-destination subscription\n * @param aeron_archive the archive client\n * @param replay_channel the channel to use for the replay\n * @param replay_destination the replay channel to use for the destination added by the subscription\n * @param live_destination the live stream channel to use for the destination added by the subscription\n * @param recording_id the recording id of the archive to replay\n * @param start_position the start position of the replay\n * @param epoch_clock the clock to use for progress checks\n * @param merge_progress_timeout_ms the timeout to use for progress checks\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_replay_merge_init(\n    aeron_archive_replay_merge_t **replay_merge,\n    aeron_subscription_t *subscription,\n    aeron_archive_t *aeron_archive,\n    const char *replay_channel,\n    const char *replay_destination,\n    const char *live_destination,\n    int64_t recording_id,\n    int64_t start_position,\n    long long epoch_clock,\n    int64_t merge_progress_timeout_ms);\n\n/**\n * Close and delete the aeron_archive_replay_merge_t struct.\n *\n * @param replay_merge the aeron_archive_replay_merge_t to close and delete\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_replay_merge_close(aeron_archive_replay_merge_t *replay_merge);\n\n/**\n * Process the operation of the merge.  Do not call the processing of fragments on the subscription.\n *\n * @param work_count_p an indicator of work done\n * @param replay_merge the replay_merge to process\n * @return 0 for success, -1 for failure\n */\nint aeron_archive_replay_merge_do_work(int *work_count_p, aeron_archive_replay_merge_t *replay_merge);\n\n/**\n * Poll the image used for the merging replay and live stream.\n * The aeron_archive_replay_merge_do_work will be called before the poll so that processing of the merge can be done.\n *\n * @param replay_merge the replay_merge to process/poll\n * @param handler the handler to call for incoming fragments\n * @param clientd the clientd to provide to the handler\n * @param fragment_limit the max number of fragments to process before returning\n * @return >= 0 indicates the number of fragments processed, -1 for failure\n */\nint aeron_archive_replay_merge_poll(\n    aeron_archive_replay_merge_t *replay_merge,\n    aeron_fragment_handler_t handler,\n    void *clientd,\n    int fragment_limit);\n\n/**\n * The image used for the replay and live stream.\n *\n * @param replay_merge the replay_merge that owns the image.\n * @return the aeron_image_t\n */\naeron_image_t *aeron_archive_replay_merge_image(aeron_archive_replay_merge_t *replay_merge);\n\n/**\n * Is the live stream merged and the replay stopped?\n *\n * @param replay_merge the replay_merge to check\n * @return true if merged, false otherwise\n */\nbool aeron_archive_replay_merge_is_merged(aeron_archive_replay_merge_t *replay_merge);\n\n/**\n * Has the replay_merge failed due to an error?\n *\n * @param replay_merge the replay_merge to check\n * @return true if an error occurred\n */\nbool aeron_archive_replay_merge_has_failed(aeron_archive_replay_merge_t *replay_merge);\n\n/**\n * Is the live destination added to the subscription?\n *\n * @param replay_merge the replay_merge to check\n * @return true if the live destination is added to the subscription\n */\nbool aeron_archive_replay_merge_is_live_added(aeron_archive_replay_merge_t *replay_merge);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif //AERON_ARCHIVE_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_async_connect.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_archive.h\"\n#include \"aeron_archive_async_connect.h\"\n#include \"aeron_archive_client.h\"\n#include \"aeron_archive_configuration.h\"\n#include \"aeron_archive_context.h\"\n#include \"aeron_archive_control_response_poller.h\"\n#include \"aeron_archive_recording_descriptor_poller.h\"\n#include \"aeron_archive_recording_subscription_descriptor_poller.h\"\n#include \"aeron_archive_proxy.h\"\n\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n#include \"uri/aeron_uri_string_builder.h\"\n\ntypedef enum aeron_archive_async_connect_state_en\n{\n    ADD_PUBLICATION = 0,\n    AWAIT_PUBLICATION_CONNECTED = 1,\n    SEND_CONNECT_REQUEST = 2,\n    AWAIT_SUBSCRIPTION_CONNECTED = 3,\n    AWAIT_CONNECT_RESPONSE = 4,\n    SEND_ARCHIVE_ID_REQUEST = 5,\n    AWAIT_ARCHIVE_ID_RESPONSE = 6,\n    DONE = 7,\n    SEND_CHALLENGE_RESPONSE = 8,\n    AWAIT_CHALLENGE_RESPONSE = 9\n}\naeron_archive_async_connect_state_t;\n\nstruct aeron_archive_async_connect_stct\n{\n    aeron_archive_async_connect_state_t state;\n    aeron_archive_context_t *ctx;\n    aeron_t *aeron;\n    aeron_async_add_subscription_t *async_add_subscription;\n    aeron_subscription_t *subscription;\n    aeron_async_add_exclusive_publication_t *async_add_exclusive_publication;\n    aeron_exclusive_publication_t *exclusive_publication;\n    aeron_archive_encoded_credentials_t *encoded_credentials_from_challenge;\n    int64_t deadline_ns;\n    aeron_archive_proxy_t *archive_proxy;\n    aeron_archive_control_response_poller_t *control_response_poller;\n    int64_t correlation_id;\n    int64_t control_session_id;\n};\n\nint aeron_archive_check_and_setup_response_channel(aeron_archive_context_t *ctx, int64_t subscription_id);\n\nint aeron_archive_async_connect_transition_to_done(aeron_archive_t **aeron_archive, aeron_archive_async_connect_t *async, int64_t archive_id);\n\nint aeron_archive_async_connect_delete(aeron_archive_async_connect_t *async);\n\n/* *********************** */\n\nuint8_t aeron_archive_async_connect_step(aeron_archive_async_connect_t *async)\n{\n    return async->state;\n}\n\nint aeron_archive_async_connect(aeron_archive_async_connect_t **async, aeron_archive_context_t *ctx)\n{\n    *async = NULL;\n\n    if (aeron_archive_context_conclude(ctx) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    aeron_t *aeron = NULL;\n    aeron_async_add_subscription_t *async_add_subscription = NULL;\n    aeron_async_add_exclusive_publication_t *async_add_exclusive_publication = NULL;\n\n    aeron = ctx->aeron;\n\n    if (aeron_async_add_subscription(\n        &async_add_subscription,\n        aeron,\n        ctx->control_response_channel,\n        ctx->control_response_stream_id,\n        NULL,\n        NULL,\n        NULL,\n        NULL) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    int64_t subscription_id = aeron_async_add_subscription_get_registration_id(async_add_subscription);\n\n    if (aeron_archive_check_and_setup_response_channel(ctx, subscription_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_async_add_exclusive_publication(\n        &async_add_exclusive_publication,\n        aeron,\n        ctx->control_request_channel,\n        ctx->control_request_stream_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    aeron_archive_async_connect_t *_async = NULL;\n\n    if (aeron_alloc((void **)&_async, sizeof(aeron_archive_async_connect_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_archive_async_connect_t\");\n        return -1;\n    }\n\n    _async->state = ADD_PUBLICATION;\n\n    if (aeron_archive_context_duplicate(&_async->ctx, ctx) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_free(_async);\n        return -1;\n    }\n\n    // Now that we've copied the original context into the _async->ctx, the original ctx no longer owns the aeron client.\n    aeron_archive_context_set_owns_aeron_client(ctx, false);\n\n    _async->aeron = aeron;\n    _async->async_add_subscription = async_add_subscription;\n    _async->subscription = NULL;\n    _async->async_add_exclusive_publication = async_add_exclusive_publication;\n    _async->exclusive_publication = NULL;\n    _async->encoded_credentials_from_challenge = NULL;\n    _async->deadline_ns = aeron_nano_clock() + ctx->message_timeout_ns;\n    _async->archive_proxy = NULL;\n    _async->control_response_poller = NULL;\n    _async->correlation_id = AERON_NULL_VALUE;\n    _async->control_session_id = AERON_NULL_VALUE;\n\n    *async = _async;\n\n    return 0;\n}\n\nint aeron_archive_async_connect_poll(aeron_archive_t **aeron_archive, aeron_archive_async_connect_t *async)\n{\n    if (aeron_nano_clock() > async->deadline_ns)\n    {\n        AERON_SET_ERR(-1, \"%s\", \"connect timeout\");\n        goto cleanup;\n    }\n\n    if (ADD_PUBLICATION == async->state)\n    {\n        if (NULL == async->exclusive_publication)\n        {\n            int rc = aeron_async_add_exclusive_publication_poll(\n                &async->exclusive_publication,\n                async->async_add_exclusive_publication);\n\n            if (rc == 0)\n            {\n                // try again\n            }\n            else if (rc == 1)\n            {\n                // success - exclusive_publication should now be set\n                async->async_add_exclusive_publication = NULL;\n            }\n            else\n            {\n                // error\n                async->async_add_exclusive_publication = NULL;\n                AERON_APPEND_ERR(\"%s\", \"\");\n                goto cleanup;\n            }\n        }\n\n        if (NULL != async->exclusive_publication && NULL == async->archive_proxy)\n        {\n            if (aeron_archive_proxy_create(\n                &async->archive_proxy,\n                async->ctx,\n                async->exclusive_publication,\n                (int)async->ctx->message_retry_attempts) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                goto cleanup;\n            }\n        }\n\n        if (NULL == async->subscription)\n        {\n            int rc = aeron_async_add_subscription_poll(\n                &async->subscription,\n                async->async_add_subscription);\n\n            if (rc == 0)\n            {\n                // try again\n            }\n            else if (rc == 1)\n            {\n                // success - subscription should now be set\n                async->async_add_subscription = NULL;\n            }\n            else\n            {\n                // error\n                async->async_add_subscription = NULL;\n                AERON_APPEND_ERR(\"%s\", \"\");\n                goto cleanup;\n            }\n        }\n\n        if (NULL != async->subscription && NULL == async->control_response_poller)\n        {\n            if (aeron_archive_control_response_poller_create(\n                &async->control_response_poller,\n                async->subscription,\n                AERON_ARCHIVE_CONTROL_RESPONSE_POLLER_FRAGMENT_LIMIT_DEFAULT) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                goto cleanup;\n            }\n        }\n\n        if (NULL != async->archive_proxy && NULL != async->control_response_poller)\n        {\n            async->state = AWAIT_PUBLICATION_CONNECTED;\n        }\n    }\n\n    if (AWAIT_PUBLICATION_CONNECTED == async->state)\n    {\n        if (aeron_exclusive_publication_is_connected(async->exclusive_publication))\n        {\n            async->state = SEND_CONNECT_REQUEST;\n        }\n        else\n        {\n            return 0;\n        }\n    }\n\n    if (SEND_CONNECT_REQUEST == async->state)\n    {\n        aeron_subscription_constants_t constants;\n        char *control_response_channel;\n\n        aeron_subscription_constants(async->subscription, &constants);\n        size_t control_response_channel_len = strlen(constants.channel) + AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN;\n        aeron_alloc((void **)&control_response_channel, control_response_channel_len);\n\n        if (aeron_subscription_try_resolve_channel_endpoint_port(\n            async->subscription,\n            control_response_channel,\n            control_response_channel_len) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto cleanup;\n        }\n\n        if ('\\0' == control_response_channel[0])\n        {\n            aeron_free(control_response_channel);\n            return 0;\n        }\n\n        aeron_archive_encoded_credentials_t *encoded_credentials =\n            aeron_archive_credentials_supplier_encoded_credentials(&async->ctx->credentials_supplier);\n\n        async->correlation_id = aeron_next_correlation_id(async->aeron);\n\n        bool sent = aeron_archive_proxy_try_connect(\n            async->archive_proxy,\n            control_response_channel,\n            async->ctx->control_response_stream_id,\n            encoded_credentials,\n            async->correlation_id);\n\n        aeron_archive_credentials_supplier_on_free(&async->ctx->credentials_supplier, encoded_credentials);\n        aeron_free(control_response_channel);\n\n        if (sent)\n        {\n            async->state = AWAIT_SUBSCRIPTION_CONNECTED;\n        }\n        else\n        {\n            return 0;\n        }\n    }\n\n    if (AWAIT_SUBSCRIPTION_CONNECTED == async->state)\n    {\n        if (aeron_subscription_is_connected(async->subscription))\n        {\n            async->state = AWAIT_CONNECT_RESPONSE;\n        }\n        else\n        {\n            return 0;\n        }\n    }\n\n    if (SEND_ARCHIVE_ID_REQUEST == async->state)\n    {\n        if (!aeron_archive_proxy_archive_id(\n            async->archive_proxy,\n            async->correlation_id))\n        {\n            return 0;\n        }\n\n        async->state = AWAIT_ARCHIVE_ID_RESPONSE;\n    }\n\n    if (SEND_CHALLENGE_RESPONSE == async->state)\n    {\n        if (!aeron_archive_proxy_challenge_response(\n            async->archive_proxy,\n            async->encoded_credentials_from_challenge,\n            async->correlation_id))\n        {\n            return 0;\n        }\n\n        aeron_archive_credentials_supplier_on_free(\n            &async->ctx->credentials_supplier,\n            async->encoded_credentials_from_challenge);\n\n        async->encoded_credentials_from_challenge = NULL;\n\n        async->state = AWAIT_CHALLENGE_RESPONSE;\n    }\n\n    aeron_archive_control_response_poller_t *poller = async->control_response_poller;\n\n    if (NULL != poller)\n    {\n        if (aeron_archive_control_response_poller_poll(poller) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto cleanup;\n        }\n\n        if (poller->is_poll_complete &&\n            poller->correlation_id == async->correlation_id)\n        {\n            async->control_session_id = poller->control_session_id;\n            aeron_archive_proxy_set_control_session_id(async->archive_proxy, poller->control_session_id);\n\n            if (poller->was_challenged)\n            {\n                async->encoded_credentials_from_challenge =\n                    aeron_archive_credentials_supplier_on_challenge(\n                        &async->ctx->credentials_supplier,\n                        &poller->encoded_challenge);\n\n                async->correlation_id = aeron_next_correlation_id(async->aeron);\n                async->state = SEND_CHALLENGE_RESPONSE;\n            }\n            else\n            {\n                if (!poller->is_code_ok)\n                {\n                    aeron_archive_proxy_close_session(async->archive_proxy);\n\n                    if (poller->is_code_error)\n                    {\n                        AERON_SET_ERR(EINVAL, \"%s\", poller->error_message);\n                    }\n                    else\n                    {\n                        AERON_SET_ERR(EINVAL, \"unexpected response code: code=%i\", poller->code_value);\n                    }\n\n                    goto cleanup;\n                }\n\n                if (AWAIT_ARCHIVE_ID_RESPONSE == async->state)\n                {\n                    int64_t archive_id = poller->relevant_id;\n\n                    return aeron_archive_async_connect_transition_to_done(aeron_archive, async, archive_id);\n                }\n                else // AWAIT_CONNECT_RESPONSE or AWAIT_CHALLENGE_RESPONSE\n                {\n                    int32_t archive_protocol_version = poller->version;\n\n                    if (archive_protocol_version < aeron_archive_protocol_version_with_archive_id())\n                    {\n                        return aeron_archive_async_connect_transition_to_done(aeron_archive, async, AERON_NULL_VALUE);\n                    }\n                    else\n                    {\n                        async->correlation_id = aeron_next_correlation_id(async->aeron);\n                        async->state = SEND_ARCHIVE_ID_REQUEST;\n                    }\n                }\n            }\n        }\n    }\n\n    return 0;\n\ncleanup:\n\n    aeron_archive_async_connect_delete(async);\n\n    return -1;\n}\n\n/* *********************** */\n\nint aeron_archive_check_and_setup_response_channel(aeron_archive_context_t *ctx, int64_t subscription_id)\n{\n    int rc = 0;\n    aeron_uri_string_builder_t builder;\n\n    if (aeron_uri_string_builder_init_on_string(&builder, ctx->control_response_channel) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    const char *control_mode = aeron_uri_string_builder_get(&builder, AERON_UDP_CHANNEL_CONTROL_MODE_KEY);\n\n    if (NULL != control_mode &&\n        0 == strcmp(AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE, control_mode))\n    {\n        aeron_uri_string_builder_close(&builder); // close the previous builder\n\n        char uri[AERON_URI_MAX_LENGTH];\n        if (aeron_uri_string_builder_init_on_string(&builder,ctx->control_request_channel) < 0 ||\n            aeron_uri_string_builder_put_int64(&builder,AERON_URI_RESPONSE_CORRELATION_ID_KEY,subscription_id) < 0 ||\n            aeron_uri_string_builder_sprint(&builder,uri, sizeof(uri)) < 0 ||\n            aeron_archive_context_set_control_request_channel(ctx, uri) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            rc = -1;\n        }\n    }\n\ncleanup:\n    aeron_uri_string_builder_close(&builder);\n\n    return rc;\n}\n\nint aeron_archive_async_connect_transition_to_done(aeron_archive_t **aeron_archive, aeron_archive_async_connect_t *async, int64_t archive_id)\n{\n    aeron_archive_recording_descriptor_poller_t *recording_descriptor_poller;\n\n    if (aeron_archive_recording_descriptor_poller_create(\n        &recording_descriptor_poller,\n        async->ctx,\n        async->subscription,\n        async->control_session_id,\n        AERON_ARCHIVE_RECORDING_DESCRIPTOR_POLLER_FRAGMENT_LIMIT_DEFAULT) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    aeron_archive_recording_subscription_descriptor_poller_t *recording_subscription_descriptor_poller;\n\n    if (aeron_archive_recording_subscription_descriptor_poller_create(\n        &recording_subscription_descriptor_poller,\n        async->ctx,\n        async->subscription,\n        async->control_session_id,\n        AERON_ARCHIVE_RECORDING_SUBSCRIPTION_DESCRIPTOR_POLLER_FRAGMENT_LIMIT_DEFAULT) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_archive_recording_descriptor_poller_close(recording_descriptor_poller);\n        return -1;\n    }\n\n    int rc = aeron_archive_create(\n        aeron_archive,\n        async->ctx,\n        async->archive_proxy,\n        async->subscription,\n        async->control_response_poller,\n        recording_descriptor_poller,\n        recording_subscription_descriptor_poller,\n        async->control_session_id,\n        archive_id);\n\n    if (rc == 0)\n    {\n        /*\n         * What's returned here is what's returned from aeron_archive_async_connect_poll().\n         * '0' means 'try again'.\n         * '1' means 'success' and then it's expected that aeron_archive is now valid.\n         * If aeron_archive_create() returns 0, then the aeron_archive should be valid.  So return 1.\n         */\n        rc = 1;\n\n        async->subscription = NULL;\n        async->exclusive_publication = NULL;\n        async->archive_proxy = NULL;\n        async->control_response_poller = NULL;\n        async->ctx = NULL;\n    }\n\n    aeron_archive_async_connect_delete(async);\n\n    return rc;\n}\n\nint aeron_archive_async_connect_delete(aeron_archive_async_connect_t *async)\n{\n    if (NULL != async->subscription)\n    {\n        aeron_subscription_close(async->subscription, NULL, NULL);\n        async->subscription = NULL;\n    }\n\n    if (NULL != async->exclusive_publication && NULL == async->archive_proxy)\n    {\n        aeron_exclusive_publication_close(async->exclusive_publication, NULL, NULL);\n        async->exclusive_publication = NULL;\n    }\n\n    if (NULL != async->archive_proxy)\n    {\n        aeron_archive_proxy_delete(async->archive_proxy);\n        async->archive_proxy = NULL;\n    }\n\n    if (NULL != async->control_response_poller)\n    {\n        aeron_archive_control_response_poller_close(async->control_response_poller);\n        async->control_response_poller = NULL;\n    }\n\n    if (NULL != async->encoded_credentials_from_challenge)\n    {\n        aeron_archive_credentials_supplier_on_free(\n            &async->ctx->credentials_supplier,\n            async->encoded_credentials_from_challenge);\n    }\n\n    if (NULL != async->ctx)\n    {\n        aeron_archive_context_close(async->ctx);\n    }\n\n    aeron_free(async);\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_async_connect.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_ASYNC_CONNECT_H\n#define AERON_ARCHIVE_ASYNC_CONNECT_H\n\n#include \"aeron_archive.h\"\n\nuint8_t aeron_archive_async_connect_step(aeron_archive_async_connect_t *async);\n\n#endif //AERON_ARCHIVE_ASYNC_CONNECT_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_client.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#include \"aeron_archive.h\"\n#include \"aeron_archive_async_connect.h\"\n#include \"aeron_archive_context.h\"\n#include \"aeron_archive_client.h\"\n#include \"aeron_archive_recording_signal.h\"\n#include \"aeron_archive_replay_params.h\"\n\n#include \"aeron_alloc.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_counters.h\"\n#include \"util/aeron_error.h\"\n#include \"uri/aeron_uri_string_builder.h\"\n#include \"command/aeron_control_protocol.h\"\n\n#define ENSURE_NOT_REENTRANT_CHECK_RETURN(_aa, _rc) \\\ndo { \\\n    if ((_aa)->is_in_callback) \\\n    { \\\n        aeron_mutex_unlock(&aeron_archive->lock); \\\n        AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"%s\", \"reentrant calls not permitted during callbacks\"); \\\n        return (_rc); \\\n    } \\\n} while (0)\n\nstatic void aeron_archive_handle_control_response_with_error_handler(\n    aeron_archive_context_t *ctx, aeron_archive_control_response_poller_t *poller)\n{\n    aeron_archive_context_invoke_error_handler(\n        ctx, poller->correlation_id, (int32_t)poller->relevant_id, poller->error_message);\n}\n\nstatic void aeron_archive_dispatch_recording_signal(aeron_archive_t *aeron_archive)\n{\n    aeron_archive_control_response_poller_t *poller = aeron_archive->control_response_poller;\n\n    aeron_archive_recording_signal_t signal;\n\n    signal.control_session_id = poller->control_session_id;\n    signal.recording_id = poller->recording_id;\n    signal.subscription_id = poller->subscription_id;\n    signal.position = poller->position;\n    signal.recording_signal_code = poller->recording_signal_code;\n\n    aeron_archive_recording_signal_dispatch_signal(aeron_archive->ctx, &signal);\n}\n\nstatic int aeron_archive_poll_next_response(\n    aeron_archive_t *aeron_archive,\n    const char *operation_name,\n    int64_t correlation_id,\n    int64_t deadline_ns)\n{\n    aeron_archive_control_response_poller_t *poller = aeron_archive->control_response_poller;\n\n    while (true)\n    {\n        int fragments = aeron_archive_control_response_poller_poll(poller);\n\n        if (fragments < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        if (poller->is_poll_complete)\n        {\n            if (poller->is_recording_signal &&\n                poller->control_session_id == aeron_archive->control_session_id)\n            {\n                aeron_archive_dispatch_recording_signal(aeron_archive);\n\n                continue;\n            }\n\n            break;\n        }\n\n        if (fragments > 0)\n        {\n            continue;\n        }\n\n        {\n            if (!aeron_subscription_is_connected(poller->subscription))\n            {\n                AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"%s\", \"subscription to archive is not connected\");\n                return -1;\n            }\n        }\n\n        if (aeron_nano_clock() > deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s awaiting response - correlationId=%\" PRIi64, operation_name, correlation_id);\n            return -1;\n        }\n\n        aeron_archive_idle(aeron_archive);\n\n        aeron_archive_context_invoke_aeron_client(aeron_archive->ctx);\n    }\n\n    return 0;\n}\n\nstatic int aeron_archive_poll_for_response_allowing_error(\n    bool *success_p,\n    aeron_archive_t *aeron_archive,\n    const char *operation_name,\n    int64_t correlation_id,\n    int32_t expected_error_code)\n{\n    aeron_archive_control_response_poller_t *poller = aeron_archive->control_response_poller;\n\n    int64_t deadline_ns = aeron_nano_clock() + aeron_archive->ctx->message_timeout_ns;\n\n    while (true)\n    {\n        if (aeron_archive_poll_next_response(\n            aeron_archive,\n            operation_name,\n            correlation_id,\n            deadline_ns) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        // make sure the session id matches\n        if (poller->control_session_id != aeron_archive->control_session_id)\n        {\n            aeron_archive_context_invoke_aeron_client(aeron_archive->ctx);\n            continue;\n        }\n\n        if (poller->is_code_error)\n        {\n            if (poller->correlation_id == correlation_id)\n            {\n                if (poller->relevant_id == expected_error_code)\n                {\n                    *success_p = false;\n                    return 0;\n                }\n\n                // got an error, and the correlation ids match\n                AERON_SET_ERR(\n                    -AERON_ERROR_CODE_GENERIC_ERROR,\n                    \"response for correlationId=%\" PRIi64 \", errorCode=%\" PRIi64 \", error: %s\",\n                    correlation_id,\n                    poller->relevant_id,\n                    poller->error_message);\n\n                return -1;\n            }\n            else if (NULL != aeron_archive->ctx->error_handler)\n            {\n                aeron_archive_handle_control_response_with_error_handler(aeron_archive->ctx, poller);\n            }\n        }\n        else if (poller->correlation_id == correlation_id)\n        {\n            if (!poller->is_code_ok)\n            {\n                AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"unexpected response code: %i\", poller->code_value);\n                return -1;\n            }\n\n            *success_p = true;\n            return 0;\n        }\n    }\n}\n\nint aeron_archive_poll_for_descriptors(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    const char *operation_name,\n    int64_t correlation_id,\n    int32_t record_count,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd);\n\nint aeron_archive_poll_for_subscription_descriptors(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    const char *operation_name,\n    int64_t correlation_id,\n    int32_t subscription_count,\n    aeron_archive_recording_subscription_descriptor_consumer_func_t recording_subscription_descriptor_consumer,\n    void *recording_subscription_descriptor_consumer_clientd);\n\nint aeron_archive_replay_via_response_channel(\n    aeron_subscription_t **subscription_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params);\n\nint aeron_archive_start_replay_via_response_channel(\n    int64_t *replay_session_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params);\n\nint aeron_archive_initiate_replay_via_response_channel(\n    int64_t *replay_session_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params,\n    int64_t subscription_id,\n    int64_t deadline_ns);\n\nint aeron_archive_channel_with_session_id(char *out, size_t out_len, const char *in, int32_t session_id);\n\n/* **************** */\n\nint aeron_archive_connect(aeron_archive_t **aeron_archive_p, aeron_archive_context_t *ctx)\n{\n    aeron_archive_async_connect_t *async;\n\n    if (aeron_archive_async_connect(&async, ctx) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    uint8_t previous_step = aeron_archive_async_connect_step(async);\n    if (aeron_archive_async_connect_poll(aeron_archive_p, async) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    while (NULL == *aeron_archive_p)\n    {\n        if (aeron_archive_async_connect_step(async) == previous_step)\n        {\n            aeron_archive_context_idle(ctx);\n        }\n        else\n        {\n            previous_step = aeron_archive_async_connect_step(async);\n        }\n\n        aeron_archive_context_invoke_aeron_client(ctx);\n\n        if (aeron_archive_async_connect_poll(aeron_archive_p, async) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        // _poll returned either 0 or 1\n        // 0 leaves *aeron_archive as NULL, which is basically 'continue'\n        // 1 sets *aeron_archive, which is basically a 'break'\n        // if we sit in this loop for 'too long', eventually _poll will return -1\n    }\n\n    return 0;\n}\n\nint aeron_archive_create(\n    aeron_archive_t **aeron_archive,\n    aeron_archive_context_t *ctx,\n    aeron_archive_proxy_t *archive_proxy,\n    aeron_subscription_t *subscription,\n    aeron_archive_control_response_poller_t *control_response_poller,\n    aeron_archive_recording_descriptor_poller_t *recording_descriptor_poller,\n    aeron_archive_recording_subscription_descriptor_poller_t *recording_subscription_descriptor_poller,\n    int64_t control_session_id,\n    int64_t archive_id)\n{\n    aeron_archive_t *_aeron_archive = NULL;\n\n    if (aeron_alloc((void **)&_aeron_archive, sizeof(aeron_archive_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_archive_t\");\n        return -1;\n    }\n\n    _aeron_archive->owns_ctx = true;\n    _aeron_archive->ctx = ctx;\n\n    aeron_mutex_init(&_aeron_archive->lock);\n\n    _aeron_archive->archive_proxy = archive_proxy;\n    _aeron_archive->owns_control_response_subscription = true;\n    _aeron_archive->subscription = subscription;\n    _aeron_archive->control_response_poller = control_response_poller;\n    _aeron_archive->recording_descriptor_poller = recording_descriptor_poller;\n    _aeron_archive->recording_subscription_descriptor_poller = recording_subscription_descriptor_poller;\n    _aeron_archive->control_session_id = control_session_id;\n    _aeron_archive->archive_id = archive_id;\n    _aeron_archive->is_in_callback = false;\n\n    *aeron_archive = _aeron_archive;\n    return 0;\n}\n\nint aeron_archive_close(aeron_archive_t *aeron_archive)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n\n    if (aeron_exclusive_publication_is_connected(aeron_archive->archive_proxy->exclusive_publication))\n    {\n        aeron_archive_proxy_close_session(aeron_archive->archive_proxy);\n    }\n\n    aeron_archive_proxy_delete(aeron_archive->archive_proxy);\n    aeron_archive->archive_proxy = NULL;\n\n    aeron_archive_control_response_poller_close(aeron_archive->control_response_poller);\n    aeron_archive->control_response_poller = NULL;\n\n    aeron_archive_recording_descriptor_poller_close(aeron_archive->recording_descriptor_poller);\n    aeron_archive->recording_descriptor_poller = NULL;\n\n    aeron_archive_recording_subscription_descriptor_poller_close(aeron_archive->recording_subscription_descriptor_poller);\n    aeron_archive->recording_subscription_descriptor_poller = NULL;\n\n    if (aeron_archive->owns_control_response_subscription)\n    {\n        aeron_subscription_close(aeron_archive->subscription, NULL, NULL);\n    }\n    aeron_archive->subscription = NULL;\n\n    if (aeron_archive->owns_ctx)\n    {\n        aeron_archive_context_close(aeron_archive->ctx);\n    }\n    aeron_archive->ctx = NULL;\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    aeron_mutex_destroy(&aeron_archive->lock);\n\n    aeron_free(aeron_archive);\n\n    return 0;\n}\n\nvoid aeron_archive_idle(aeron_archive_t *aeron_archive)\n{\n    aeron_archive_context_idle(aeron_archive->ctx);\n}\n\naeron_archive_control_response_poller_t *aeron_archive_control_response_poller(aeron_archive_t *aeron_archive)\n{\n    return aeron_archive->control_response_poller;\n}\n\naeron_archive_proxy_t *aeron_archive_proxy(aeron_archive_t *aeron_archive)\n{\n    return aeron_archive->archive_proxy;\n}\n\nint64_t aeron_archive_control_session_id(aeron_archive_t *aeron_archive)\n{\n    return aeron_archive->control_session_id;\n}\n\nint64_t aeron_archive_next_correlation_id(aeron_archive_t *aeron_archive)\n{\n    return aeron_next_correlation_id(aeron_archive->ctx->aeron);\n}\n\nint aeron_archive_poll_for_recording_signals(int32_t *count_p, aeron_archive_t *aeron_archive)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n\n    int32_t count = 0;\n    int rc = 0;\n\n    aeron_archive_control_response_poller_t *poller = aeron_archive->control_response_poller;\n\n    if (aeron_archive_control_response_poller_poll(poller) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (poller->is_poll_complete &&\n        poller->control_session_id == aeron_archive->control_session_id)\n    {\n        if (poller->is_control_response && poller->is_code_error)\n        {\n            if (NULL == aeron_archive->ctx->error_handler)\n            {\n                AERON_SET_ERR(\n                    -AERON_ERROR_CODE_GENERIC_ERROR,\n                    \"response for correlationId=%\" PRIi64 \", errorCode=%\" PRIi64 \", error: %s\",\n                    poller->correlation_id,\n                    poller->relevant_id,\n                    poller->error_message);\n                rc = -1;\n            }\n            else\n            {\n               aeron_archive_handle_control_response_with_error_handler(aeron_archive->ctx, poller);\n            }\n        }\n        else if (poller->is_recording_signal)\n        {\n            aeron_archive_dispatch_recording_signal(aeron_archive);\n\n            count++;\n        }\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n\n    if (NULL != count_p)\n    {\n        *count_p = count;\n    }\n\n    return rc;\n}\n\nint aeron_archive_poll_for_error_response(aeron_archive_t *aeron_archive, char *buffer, size_t buffer_length)\n{\n    int rc = 0;\n\n    aeron_mutex_lock(&aeron_archive->lock);\n\n    if (!aeron_subscription_is_connected(aeron_archive->subscription))\n    {\n        AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"%s\", \"subscription to archive is not connected\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    aeron_archive_control_response_poller_t *poller = aeron_archive->control_response_poller;\n\n    if (aeron_archive_control_response_poller_poll(poller) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    if (poller->is_poll_complete &&\n        poller->control_session_id == aeron_archive->control_session_id)\n    {\n        if (poller->is_control_response &&\n            poller->is_code_error)\n        {\n            snprintf(buffer, buffer_length, \"%s\", poller->error_message);\n\n            goto cleanup;\n        }\n        else if (poller->is_recording_signal)\n        {\n            aeron_archive_dispatch_recording_signal(aeron_archive);\n        }\n    }\n\n    if (buffer_length > 0)\n    {\n        buffer[0] = '\\0';\n    }\n\ncleanup:\n    aeron_mutex_unlock(&aeron_archive->lock);\n\n    return rc;\n}\n\nint aeron_archive_check_for_error_response(aeron_archive_t *aeron_archive)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n\n    int rc = 0;\n\n    if (!aeron_subscription_is_connected(aeron_archive->subscription))\n    {\n        if (NULL == aeron_archive->ctx->error_handler)\n        {\n            AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"%s\", \"not connected\");\n            rc = -1;\n        }\n        else\n        {\n            aeron_archive->ctx->error_handler(\n                aeron_archive->ctx->error_handler_clientd,\n                AERON_ERROR_CODE_GENERIC_ERROR,\n                \"not connected\");\n        }\n\n        goto cleanup;\n    }\n\n    aeron_archive_control_response_poller_t *poller = aeron_archive->control_response_poller;\n\n    if (aeron_archive_control_response_poller_poll(poller) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    if (poller->is_poll_complete &&\n        poller->control_session_id == aeron_archive->control_session_id)\n    {\n        if (poller->is_control_response &&\n            poller->is_code_error)\n        {\n            if (NULL == aeron_archive->ctx->error_handler)\n            {\n                AERON_SET_ERR(\n                    -AERON_ERROR_CODE_GENERIC_ERROR,\n                    \"response for correlationId=%\" PRIi64 \", errorCode=%\" PRIi64 \", error: %s\",\n                    poller->correlation_id,\n                    poller->relevant_id,\n                    poller->error_message);\n\n                rc = -1;\n            }\n            else\n            {\n               aeron_archive_handle_control_response_with_error_handler(aeron_archive->ctx, poller);\n            }\n        }\n        else if (poller->is_recording_signal)\n        {\n            aeron_archive_dispatch_recording_signal(aeron_archive);\n        }\n    }\n\ncleanup:\n    aeron_mutex_unlock(&aeron_archive->lock);\n\n    return rc;\n}\n\nint aeron_archive_add_recorded_publication(\n    aeron_publication_t **publication_p,\n    aeron_archive_t *aeron_archive,\n    const char *channel,\n    int32_t stream_id)\n{\n    aeron_async_add_publication_t *async;\n    aeron_publication_t *_publication = NULL;\n\n    if (aeron_async_add_publication(\n        &async,\n        aeron_archive->ctx->aeron,\n        channel,\n        stream_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_async_add_publication_poll(&_publication, async) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n    while (NULL == _publication)\n    {\n        aeron_archive_idle(aeron_archive);\n\n        if (aeron_async_add_publication_poll(&_publication, async) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n    }\n\n    aeron_publication_constants_t constants;\n    aeron_publication_constants(_publication, &constants);\n    if (constants.original_registration_id != constants.registration_id)\n    {\n        // not original\n        AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"publication already added for channel=%s streamId=%\" PRIi32, channel, stream_id);\n        return -1;\n    }\n\n    char *recording_channel;\n    size_t recording_channel_len = strlen(channel) + 50; // add enough space for an additional session id\n    aeron_alloc((void **)&recording_channel, recording_channel_len);\n\n    int rc = 0;\n\n    if (aeron_archive_channel_with_session_id(\n        recording_channel,\n        recording_channel_len,\n        channel,\n        constants.session_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    if (aeron_archive_start_recording(\n        NULL,\n        aeron_archive,\n        recording_channel,\n        stream_id,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    *publication_p = _publication;\n\ncleanup:\n    aeron_free(recording_channel);\n    return rc;\n}\n\nint aeron_archive_add_recorded_exclusive_publication(\n    aeron_exclusive_publication_t **exclusive_publication_p,\n    aeron_archive_t *aeron_archive,\n    const char *channel,\n    int32_t stream_id)\n{\n    aeron_async_add_exclusive_publication_t *async;\n    aeron_exclusive_publication_t *_exclusive_publication = NULL;\n\n    if (aeron_async_add_exclusive_publication(\n        &async,\n        aeron_archive->ctx->aeron,\n        channel,\n        stream_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_async_add_exclusive_publication_poll(&_exclusive_publication, async) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n    while (NULL == _exclusive_publication)\n    {\n        aeron_archive_idle(aeron_archive);\n\n        if (aeron_async_add_exclusive_publication_poll(&_exclusive_publication, async) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n    }\n\n    aeron_publication_constants_t constants;\n    aeron_exclusive_publication_constants(_exclusive_publication, &constants);\n\n    char *recording_channel;\n    size_t recording_channel_len = strlen(channel) + 50; // add enough space for an additional session id\n    aeron_alloc((void **)&recording_channel, recording_channel_len);\n\n    int rc = 0;\n\n    if (aeron_archive_channel_with_session_id(\n        recording_channel,\n        recording_channel_len,\n        channel,\n        constants.session_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    if (aeron_archive_start_recording(\n        NULL,\n        aeron_archive,\n        recording_channel,\n        stream_id,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    *exclusive_publication_p = _exclusive_publication;\n\ncleanup:\n    aeron_free(recording_channel);\n    return rc;\n}\n\nint aeron_archive_start_recording(\n    int64_t *subscription_id_p,\n    aeron_archive_t *aeron_archive,\n    const char *recording_channel,\n    int32_t recording_stream_id,\n    aeron_archive_source_location_t source_location,\n    bool auto_stop)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_start_recording(\n        aeron_archive->archive_proxy,\n        recording_channel,\n        recording_stream_id,\n        source_location == AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        auto_stop,\n        correlation_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            subscription_id_p,\n            aeron_archive,\n            \"AeronArchive::startRecording\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_get_recording_position(\n    int64_t *recording_position_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_get_recording_position(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            recording_position_p,\n            aeron_archive,\n            \"AeronArchive::getRecordingPosition\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_get_start_position(\n    int64_t *start_position_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_get_start_position(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            start_position_p,\n            aeron_archive,\n            \"AeronArchive::getStartPosition\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_get_stop_position(\n    int64_t *stop_position_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_get_stop_position(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            stop_position_p,\n            aeron_archive,\n            \"AeronArchive::getStopPosition\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_get_max_recorded_position(\n    int64_t *max_recorded_position_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_get_max_recorded_position(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            max_recorded_position_p,\n            aeron_archive,\n            \"AeronArchive::getMaxRecordedPosition\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_stop_recording_subscription(\n    aeron_archive_t *aeron_archive,\n    int64_t subscription_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_stop_recording_subscription(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        subscription_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            NULL,\n            aeron_archive,\n            \"AeronArchive::stopRecordingSubscription\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_try_stop_recording_subscription(\n    bool *stopped_p,\n    aeron_archive_t *aeron_archive,\n    int64_t subscription_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_stop_recording_subscription(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        subscription_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response_allowing_error(\n            stopped_p,\n            aeron_archive,\n            \"AeronArchive::tryStopRecordingSubscription\",\n            correlation_id,\n            ARCHIVE_ERROR_CODE_UNKNOWN_SUBSCRIPTION);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_stop_recording_channel_and_stream(\n    aeron_archive_t *aeron_archive,\n    const char *channel,\n    int32_t stream_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_stop_recording(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        channel,\n        stream_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            NULL,\n            aeron_archive,\n            \"AeronArchive::stopRecording\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_try_stop_recording_channel_and_stream(\n    bool *stopped_p,\n    aeron_archive_t *aeron_archive,\n    const char *channel,\n    int32_t stream_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_stop_recording(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        channel,\n        stream_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response_allowing_error(\n            stopped_p,\n            aeron_archive,\n            \"AeronArchive::tryStopRecordingChannelAndStream\",\n            correlation_id,\n            ARCHIVE_ERROR_CODE_UNKNOWN_SUBSCRIPTION);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_try_stop_recording_by_identity(\n    bool *stopped_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_stop_recording_by_identity(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        int64_t relevant_id;\n        rc = aeron_archive_poll_for_response(\n            &relevant_id,\n            aeron_archive,\n            \"AeronArchive::tryStopRecordingByIdentity\",\n            correlation_id);\n\n        if (rc == 0)\n        {\n            *stopped_p = relevant_id != 0;\n        }\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_stop_recording_publication_constants(\n    aeron_archive_t *aeron_archive,\n    aeron_publication_constants_t *constants)\n{\n    char *recording_channel;\n    size_t recording_channel_len = strlen(constants->channel) + 50; // add enough space for an additional session id\n    aeron_alloc((void **)&recording_channel, recording_channel_len);\n\n    int rc;\n\n    if (aeron_archive_channel_with_session_id(\n        recording_channel,\n        recording_channel_len,\n        constants->channel,\n        constants->session_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_stop_recording_channel_and_stream(\n            aeron_archive,\n            recording_channel,\n            constants->stream_id);\n    }\n\n    aeron_free(recording_channel);\n    return rc;\n}\n\nint aeron_archive_stop_recording_publication(\n    aeron_archive_t *aeron_archive,\n    aeron_publication_t *publication)\n{\n    aeron_publication_constants_t constants;\n\n    aeron_publication_constants(publication, &constants);\n\n    return aeron_archive_stop_recording_publication_constants(aeron_archive, &constants);\n}\n\nint aeron_archive_stop_recording_exclusive_publication(\n    aeron_archive_t *aeron_archive,\n    aeron_exclusive_publication_t *exclusive_publication)\n{\n    aeron_publication_constants_t constants;\n\n    aeron_exclusive_publication_constants(exclusive_publication, &constants);\n\n    return aeron_archive_stop_recording_publication_constants(aeron_archive, &constants);\n}\n\nint aeron_archive_find_last_matching_recording(\n    int64_t *recording_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t min_recording_id,\n    const char *channel_fragment,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_find_last_matching_recording(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        min_recording_id,\n        channel_fragment,\n        stream_id,\n        session_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            recording_id_p,\n            aeron_archive,\n            \"AeronArchive::findLastMatchingRecording\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_list_recording(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_list_recording(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        aeron_archive->is_in_callback = true;\n\n        rc = aeron_archive_poll_for_descriptors(\n            count_p,\n            aeron_archive,\n            \"AeronArchive::listRecording\",\n            correlation_id,\n            1,\n            recording_descriptor_consumer,\n            recording_descriptor_consumer_clientd);\n\n        aeron_archive->is_in_callback = false;\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_list_recordings(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t from_recording_id,\n    int32_t record_count,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_list_recordings(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        from_recording_id,\n        record_count))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        aeron_archive->is_in_callback = true;\n\n        rc = aeron_archive_poll_for_descriptors(\n            count_p,\n            aeron_archive,\n            \"AeronArchive::listRecordings\",\n            correlation_id,\n            record_count,\n            recording_descriptor_consumer,\n            recording_descriptor_consumer_clientd);\n\n        aeron_archive->is_in_callback = false;\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_list_recordings_for_uri(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t from_recording_id,\n    int32_t record_count,\n    const char *channel_fragment,\n    int32_t stream_id,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_list_recordings_for_uri(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        from_recording_id,\n        record_count,\n        channel_fragment,\n        stream_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        aeron_archive->is_in_callback = true;\n\n        rc = aeron_archive_poll_for_descriptors(\n            count_p,\n            aeron_archive,\n            \"AeronArchive::listRecordingsForUri\",\n            correlation_id,\n            record_count,\n            recording_descriptor_consumer,\n            recording_descriptor_consumer_clientd);\n\n        aeron_archive->is_in_callback = false;\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\n// called by _start_replay with the lock already held\nstatic int aeron_archive_start_replay_locked(\n    int64_t *replay_session_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params)\n{\n    {\n        aeron_uri_string_builder_t builder;\n\n        if (aeron_uri_string_builder_init_on_string(&builder, replay_channel) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        const char *control_mode = aeron_uri_string_builder_get(&builder, AERON_UDP_CHANNEL_CONTROL_MODE_KEY);\n\n        bool has_control_response_mode =\n            NULL != control_mode &&\n                strcmp(control_mode, AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE) == 0;\n\n        aeron_uri_string_builder_close(&builder);\n\n        if (has_control_response_mode)\n        {\n            int rc = aeron_archive_start_replay_via_response_channel(\n                replay_session_id_p,\n                aeron_archive,\n                recording_id,\n                replay_channel,\n                replay_stream_id,\n                params);\n\n            if (rc < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n            }\n\n            return rc;\n        }\n    }\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n\n    if (!aeron_archive_proxy_replay(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id,\n        replay_channel,\n        replay_stream_id,\n        params))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    int rc = aeron_archive_poll_for_response(\n        replay_session_id_p,\n        aeron_archive,\n        \"AeronArchive::startReplay\",\n        correlation_id);\n\n    return rc;\n}\n\nint aeron_archive_start_replay(\n    int64_t *replay_session_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int rc = aeron_archive_start_replay_locked(replay_session_id_p, aeron_archive, recording_id, replay_channel, replay_stream_id, params);\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n\n    return rc;\n}\n\n// called by _replay with the lock already held\nstatic int aeron_archive_replay_locked(\n    aeron_subscription_t **subscription_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params)\n{\n    {\n        aeron_uri_string_builder_t builder;\n\n        if (aeron_uri_string_builder_init_on_string(&builder, replay_channel) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        const char *control_mode = aeron_uri_string_builder_get(&builder, AERON_UDP_CHANNEL_CONTROL_MODE_KEY);\n\n        bool has_control_response_mode =\n            NULL != control_mode &&\n            strcmp(control_mode, AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE) == 0;\n\n        aeron_uri_string_builder_close(&builder);\n\n        if (has_control_response_mode)\n        {\n            int rc = aeron_archive_replay_via_response_channel(\n                subscription_p,\n                aeron_archive,\n                recording_id,\n                replay_channel,\n                replay_stream_id,\n                params);\n\n            if (rc < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n            }\n\n            return rc;\n        }\n    }\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n\n    if (!aeron_archive_proxy_replay(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id,\n        replay_channel,\n        replay_stream_id,\n        params))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    int64_t replay_session_id;\n\n    if (aeron_archive_poll_for_response(\n        &replay_session_id,\n        aeron_archive,\n        \"AeronArchive::replay\",\n        correlation_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    char *replay_channel_with_sid;\n    size_t replay_channel_with_sid_len = strlen(replay_channel) + 50; // add enough space for an additional session id\n    aeron_alloc((void **)&replay_channel_with_sid, replay_channel_with_sid_len);\n\n    int rc = 0;\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_on_string(&builder, replay_channel);\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_SESSION_ID_KEY, (int32_t)replay_session_id);\n        aeron_uri_string_builder_sprint(&builder, replay_channel_with_sid, replay_channel_with_sid_len);\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    aeron_async_add_subscription_t *async_add_subscription;\n    aeron_subscription_t *subscription = NULL;\n\n    if (aeron_async_add_subscription(\n        &async_add_subscription,\n        aeron_archive->ctx->aeron,\n        replay_channel_with_sid,\n        replay_stream_id,\n        NULL,\n        NULL,\n        NULL,\n        NULL) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    if (aeron_async_add_subscription_poll(&subscription, async_add_subscription) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n        goto cleanup;\n    }\n\n    while (NULL == subscription)\n    {\n        aeron_archive_idle(aeron_archive);\n\n        if (aeron_async_add_subscription_poll(&subscription, async_add_subscription) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            rc = -1;\n            goto cleanup;\n        }\n    }\n\n    *subscription_p = subscription;\n\ncleanup:\n    aeron_free(replay_channel_with_sid);\n    return rc;\n}\n\nint aeron_archive_replay(\n    aeron_subscription_t **subscription_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int rc = aeron_archive_replay_locked(subscription_p, aeron_archive, recording_id, replay_channel, replay_stream_id, params);\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n\n    return rc;\n}\n\nint aeron_archive_truncate_recording(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    int64_t position)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_truncate_recording(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id,\n        position))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            count_p,\n            aeron_archive,\n            \"AeronArchive::truncateRecording\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_stop_replay(\n    aeron_archive_t *aeron_archive,\n    int64_t replay_session_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_stop_replay(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        replay_session_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            NULL,\n            aeron_archive,\n            \"AeronArchive::stopReplay\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_stop_all_replays(\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_stop_all_replays(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            NULL,\n            aeron_archive,\n            \"AeronArchive::stopAllReplays\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_list_recording_subscriptions(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int32_t pseudo_index,\n    int32_t subscription_count,\n    const char *channel_fragment,\n    int32_t stream_id,\n    bool apply_stream_id,\n    aeron_archive_recording_subscription_descriptor_consumer_func_t recording_subscription_descriptor_consumer,\n    void *recording_subscription_descriptor_consumer_clientd)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_list_recording_subscriptions(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        pseudo_index,\n        subscription_count,\n        channel_fragment,\n        stream_id,\n        apply_stream_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        aeron_archive->is_in_callback = true;\n\n        rc = aeron_archive_poll_for_subscription_descriptors(\n            count_p,\n            aeron_archive,\n            \"AeronArchive::listRecordingSubscriptions\",\n            correlation_id,\n            subscription_count,\n            recording_subscription_descriptor_consumer,\n            recording_subscription_descriptor_consumer_clientd);\n\n        aeron_archive->is_in_callback = false;\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_purge_recording(\n    int64_t *deleted_segments_count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_purge_recording(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            deleted_segments_count_p,\n            aeron_archive,\n            \"AeronArchive::purgeRecording\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_extend_recording(\n    int64_t *subscription_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *recording_channel,\n    int32_t recording_stream_id,\n    aeron_archive_source_location_t source_location,\n    bool auto_stop)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_extend_recording(\n        aeron_archive->archive_proxy,\n        recording_id,\n        recording_channel,\n        recording_stream_id,\n        source_location == AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        auto_stop,\n        correlation_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            subscription_id_p,\n            aeron_archive,\n            \"AeronArchive::extendRecording\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_replicate(\n    int64_t *replication_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t src_recording_id,\n    const char *src_control_channel,\n    int32_t src_control_stream_id,\n    aeron_archive_replication_params_t *params)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_replicate(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        src_recording_id,\n        src_control_stream_id,\n        src_control_channel,\n        params))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            replication_id_p,\n            aeron_archive,\n            \"AeronArchive::replicate\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_stop_replication(\n    aeron_archive_t *aeron_archive,\n    int64_t replication_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_stop_replication(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        replication_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            NULL,\n            aeron_archive,\n            \"AeronArchive::stopReplication\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_try_stop_replication(\n    bool *stopped_p,\n    aeron_archive_t *aeron_archive,\n    int64_t replication_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_stop_replication(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        replication_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response_allowing_error(\n            stopped_p,\n            aeron_archive,\n            \"AeronArchive::tryStopReplication\",\n            correlation_id,\n            ARCHIVE_ERROR_CODE_UNKNOWN_SUBSCRIPTION);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_detach_segments(\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    int64_t new_start_position)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_detach_segments(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id,\n        new_start_position))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            NULL,\n            aeron_archive,\n            \"AeronArchive::detachSegments\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_delete_detached_segments(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_delete_detached_segments(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            count_p,\n            aeron_archive,\n            \"AeronArchive::deleteDetachedSegments\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_purge_segments(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    int64_t new_start_position)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_purge_segments(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id,\n        new_start_position))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            count_p,\n            aeron_archive,\n            \"AeronArchive::purgeSegments\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_attach_segments(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_attach_segments(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            count_p,\n            aeron_archive,\n            \"AeronArchive::attachSegments\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_migrate_segments(\n    int64_t *count_p,\n    aeron_archive_t *aeron_archive,\n    int64_t src_recording_id,\n    int64_t dst_recording_id)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_migrate_segments(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        src_recording_id,\n        dst_recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n            count_p,\n            aeron_archive,\n            \"AeronArchive::migrateSegments\",\n            correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint aeron_archive_update_channel(aeron_archive_t *aeron_archive, int64_t recording_id, const char *new_channel)\n{\n    aeron_mutex_lock(&aeron_archive->lock);\n    ENSURE_NOT_REENTRANT_CHECK_RETURN(aeron_archive, -1);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n    int rc;\n\n    if (!aeron_archive_proxy_update_channel(\n            aeron_archive->archive_proxy,\n            correlation_id,\n            recording_id,\n            new_channel))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n    else\n    {\n        rc = aeron_archive_poll_for_response(\n                NULL,\n                aeron_archive,\n                \"AeronArchive::updateChannel\",\n                correlation_id);\n    }\n\n    aeron_mutex_unlock(&aeron_archive->lock);\n    return rc;\n}\n\nint64_t aeron_archive_segment_file_base_position(\n    int64_t start_position,\n    int64_t position,\n    int32_t term_buffer_length,\n    int32_t segment_file_length)\n{\n    int64_t start_term_base_position = start_position - (start_position & (term_buffer_length - 1));\n    int64_t length_from_base_position = position - start_term_base_position;\n    int64_t segments = (length_from_base_position - (length_from_base_position & (segment_file_length - 1)));\n\n    return start_term_base_position + segments;\n}\n\naeron_archive_context_t *aeron_archive_get_archive_context(aeron_archive_t *aeron_archive)\n{\n    return aeron_archive->ctx;\n}\n\naeron_archive_context_t *aeron_archive_get_and_own_archive_context(aeron_archive_t *aeron_archive)\n{\n    aeron_archive->owns_ctx = false;\n\n    return aeron_archive->ctx;\n}\n\nint64_t aeron_archive_get_archive_id(aeron_archive_t *aeron_archive)\n{\n    return aeron_archive->archive_id;\n}\n\naeron_subscription_t *aeron_archive_get_control_response_subscription(aeron_archive_t *aeron_archive)\n{\n    return aeron_archive->subscription;\n}\n\naeron_subscription_t *aeron_archive_get_and_own_control_response_subscription(aeron_archive_t *aeron_archive)\n{\n    aeron_archive->owns_control_response_subscription = false;\n\n    return aeron_archive->subscription;\n}\n\n/* **************** */\n\nint aeron_archive_poll_for_response(\n    int64_t *relevant_id_p,\n    aeron_archive_t *aeron_archive,\n    const char *operation_name,\n    int64_t correlation_id)\n{\n    aeron_archive_control_response_poller_t *poller = aeron_archive->control_response_poller;\n\n    int64_t deadline_ns = aeron_nano_clock() + aeron_archive->ctx->message_timeout_ns;\n\n    while (true)\n    {\n        if (aeron_archive_poll_next_response(\n            aeron_archive,\n            operation_name,\n            correlation_id,\n            deadline_ns) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        // make sure the session id matches\n        if (poller->control_session_id != aeron_archive->control_session_id)\n        {\n            aeron_archive_context_invoke_aeron_client(aeron_archive->ctx);\n            continue;\n        }\n\n        if (poller->is_code_error)\n        {\n            if (poller->correlation_id == correlation_id)\n            {\n                // got an error, and the correlation ids match\n                AERON_SET_ERR(\n                    -AERON_ERROR_CODE_GENERIC_ERROR,\n                    \"response for correlationId=%\" PRIi64 \", errorCode=%\" PRIi64 \", error: %s\",\n                    correlation_id,\n                    poller->relevant_id,\n                    poller->error_message);\n                return -1;\n            }\n            else if (NULL != aeron_archive->ctx->error_handler)\n            {\n               aeron_archive_handle_control_response_with_error_handler(aeron_archive->ctx, poller);\n            }\n        }\n        else if (poller->correlation_id == correlation_id)\n        {\n            if (!poller->is_code_ok)\n            {\n                AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"unexpected response code: %i\", poller->code_value);\n                return -1;\n            }\n\n            if (NULL != relevant_id_p)\n            {\n                *relevant_id_p = poller->relevant_id;\n            }\n\n            return 0;\n        }\n    }\n}\n\nint aeron_archive_poll_for_descriptors(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    const char *operation_name,\n    int64_t correlation_id,\n    int32_t record_count,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd)\n{\n    aeron_archive_recording_descriptor_poller_t *poller = aeron_archive->recording_descriptor_poller;\n\n    int32_t existing_remain_count = record_count;\n\n    int64_t deadline_ns = aeron_nano_clock() + aeron_archive->ctx->message_timeout_ns;\n\n    aeron_archive_recording_descriptor_poller_reset(\n        poller,\n        correlation_id,\n        record_count,\n        recording_descriptor_consumer,\n        recording_descriptor_consumer_clientd);\n\n    while (true)\n    {\n        const int fragments = aeron_archive_recording_descriptor_poller_poll(poller);\n\n        if (fragments < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        const int32_t remaining_record_count = poller->remaining_record_count;\n\n        if (poller->is_dispatch_complete)\n        {\n            *count_p = record_count - remaining_record_count;\n\n            return 0;\n        }\n\n        if (remaining_record_count != existing_remain_count)\n        {\n            existing_remain_count = remaining_record_count;\n\n            deadline_ns = aeron_nano_clock() + aeron_archive->ctx->message_timeout_ns;\n        }\n\n        aeron_archive_context_invoke_aeron_client(aeron_archive->ctx);\n\n        if (fragments > 0)\n        {\n            continue;\n        }\n\n        if (!aeron_subscription_is_connected(aeron_archive->subscription))\n        {\n            AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"%s\", \"not connected\");\n            return -1;\n        }\n\n        if (aeron_nano_clock() > deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s awaiting recording descriptors - correlationId=%\" PRIi64, operation_name, correlation_id);\n            return -1;\n        }\n\n        aeron_archive_idle(aeron_archive);\n    }\n}\n\nint aeron_archive_poll_for_subscription_descriptors(\n    int32_t *count_p,\n    aeron_archive_t *aeron_archive,\n    const char *operation_name,\n    int64_t correlation_id,\n    int32_t subscription_count,\n    aeron_archive_recording_subscription_descriptor_consumer_func_t recording_subscription_descriptor_consumer,\n    void *recording_subscription_descriptor_consumer_clientd)\n{\n    aeron_archive_recording_subscription_descriptor_poller_t *poller = aeron_archive->recording_subscription_descriptor_poller;\n\n    int32_t existing_remain_count = subscription_count;\n\n    int64_t deadline_ns = aeron_nano_clock() + aeron_archive->ctx->message_timeout_ns;\n\n    aeron_archive_recording_subscription_descriptor_poller_reset(\n        poller,\n        correlation_id,\n        subscription_count,\n        recording_subscription_descriptor_consumer,\n        recording_subscription_descriptor_consumer_clientd);\n\n    while (true)\n    {\n        const int fragments = aeron_archive_recording_subscription_descriptor_poller_poll(poller);\n\n        if (fragments < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        const int32_t remaining_subscription_count = poller->remaining_subscription_count;\n\n        if (poller->is_dispatch_complete)\n        {\n            *count_p = subscription_count - remaining_subscription_count;\n\n            return 0;\n        }\n\n        if (remaining_subscription_count != existing_remain_count)\n        {\n            existing_remain_count = remaining_subscription_count;\n\n            // we made progress, so update the deadline_ns\n            deadline_ns = aeron_nano_clock() + aeron_archive->ctx->message_timeout_ns;\n        }\n\n        aeron_archive_context_invoke_aeron_client(aeron_archive->ctx);\n\n        if (fragments > 0)\n        {\n            continue;\n        }\n\n        if (!aeron_subscription_is_connected(aeron_archive->subscription))\n        {\n            AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"%s\", \"subscription is not connected\");\n            return -1;\n        }\n\n        if (aeron_nano_clock() > deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s awaiting recording descriptors - correlationId=%\" PRIi64, operation_name, correlation_id);\n            return -1;\n        }\n\n        aeron_archive_idle(aeron_archive);\n    }\n}\n\nint aeron_archive_replay_via_response_channel(\n    aeron_subscription_t **subscription_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params)\n{\n    int64_t deadline_ns = aeron_nano_clock() + aeron_archive->ctx->message_timeout_ns;\n\n    aeron_async_add_subscription_t *async;\n    if (aeron_async_add_subscription(\n        &async,\n        aeron_archive->ctx->aeron,\n        replay_channel,\n        replay_stream_id,\n        NULL, NULL, NULL, NULL) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    int64_t subscription_id = aeron_async_add_subscription_get_registration_id(async);\n\n    aeron_subscription_t *replay_subscription = NULL;\n    if (aeron_async_add_subscription_poll(&replay_subscription, async) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    while (NULL == replay_subscription)\n    {\n        if (aeron_nano_clock() > deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s\", \"waiting for subscription to be created\");\n            return -1;\n        }\n\n        aeron_archive_idle(aeron_archive);\n\n        if (aeron_async_add_subscription_poll(&replay_subscription, async) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n    }\n\n    if (aeron_archive_initiate_replay_via_response_channel(\n        NULL,\n        aeron_archive,\n        recording_id,\n        replay_channel,\n        replay_stream_id,\n        params,\n        subscription_id,\n        deadline_ns) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    while (!aeron_subscription_is_connected(replay_subscription))\n    {\n        if (aeron_nano_clock() > deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s\", \"waiting for subscription to be connected\");\n            return -1;\n        }\n\n        aeron_archive_idle(aeron_archive);\n    }\n\n    *subscription_p = replay_subscription;\n\n    return 0;\n}\n\nint aeron_archive_start_replay_via_response_channel(\n    int64_t *replay_session_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params)\n{\n    if (AERON_NULL_VALUE == params->subscription_registration_id)\n    {\n        AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"%s\", \"must supply a valid subscription registration id\");\n        return -1;\n    }\n\n    int64_t deadline_ns = aeron_nano_clock() + aeron_archive->ctx->message_timeout_ns;\n\n    if (aeron_archive_initiate_replay_via_response_channel(\n        replay_session_id_p,\n        aeron_archive,\n        recording_id,\n        replay_channel,\n        replay_stream_id,\n        params,\n        params->subscription_registration_id,\n        deadline_ns) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_archive_initiate_replay_via_response_channel(\n    int64_t *replay_session_id_p,\n    aeron_archive_t *aeron_archive,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params,\n    int64_t subscription_id,\n    int64_t deadline_ns)\n{\n    char *control_request_channel = NULL;\n    int rc = -1;\n\n    // acquire a replay token, and load it up into a copy of the params\n    aeron_archive_replay_params_t response_channel_replay_params;\n    aeron_archive_replay_params_copy(&response_channel_replay_params, params);\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n\n    if (!aeron_archive_request_replay_token(\n        aeron_archive->archive_proxy,\n        correlation_id,\n        recording_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto cleanup;\n    }\n\n    if (aeron_archive_poll_for_response(\n        &response_channel_replay_params.replay_token,\n        aeron_archive,\n        \"AeronArchive::replayToken\",\n        correlation_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto cleanup;\n    }\n\n    // update the control request channel uri with the subscription id of the response channel\n    // add enough space for an additional k/v pairs:\n    size_t control_request_channel_len = strlen(aeron_archive->ctx->control_request_channel) + 100;\n    aeron_alloc((void **)&control_request_channel, control_request_channel_len);\n\n    aeron_uri_string_builder_t builder;\n\n    bool failure = aeron_uri_string_builder_init_on_string(&builder, aeron_archive->ctx->control_request_channel) < 0 ||\n        aeron_uri_string_builder_put(&builder, AERON_URI_TERM_OFFSET_KEY, NULL) < 0 ||\n        aeron_uri_string_builder_put(&builder, AERON_URI_TERM_ID_KEY, NULL) < 0 ||\n        aeron_uri_string_builder_put(&builder, AERON_URI_INITIAL_TERM_ID_KEY, NULL) < 0 ||\n        aeron_uri_string_builder_put_int64(&builder, AERON_URI_RESPONSE_CORRELATION_ID_KEY, subscription_id) < 0 ||\n        aeron_uri_string_builder_put(&builder, AERON_URI_TERM_LENGTH_KEY, \"64k\") < 0 ||\n        aeron_uri_string_builder_put(&builder, AERON_URI_SPIES_SIMULATE_CONNECTION_KEY, \"false\") < 0 ||\n        aeron_uri_string_builder_sprint(&builder, control_request_channel, control_request_channel_len) < 0;\n\n    aeron_uri_string_builder_close(&builder);\n\n    if (failure)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto cleanup;\n    }\n\n    // create an exclusive publication that is the 'request' channel that will trigger the creation of the 'response' channel\n    aeron_async_add_exclusive_publication_t *async;\n    if (aeron_async_add_exclusive_publication(\n        &async,\n        aeron_archive->ctx->aeron,\n        control_request_channel,\n        aeron_archive->ctx->control_request_stream_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto cleanup;\n    }\n\n    int64_t publication_id = aeron_async_add_exclusive_exclusive_publication_get_registration_id(async);\n\n    aeron_exclusive_publication_t *exclusive_publication;\n    if (aeron_async_add_exclusive_publication_poll(&exclusive_publication, async) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto cleanup;\n    }\n\n    while (NULL == exclusive_publication)\n    {\n        if (aeron_nano_clock() > deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s\", \"waiting for publication to be created\");\n            goto cleanup;\n        }\n\n        aeron_archive_idle(aeron_archive);\n\n        if (aeron_async_add_exclusive_publication_poll(&exclusive_publication, async) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto cleanup;\n        }\n    }\n\n    // create a (short-lived) proxy based on the exclusive publication just created\n    aeron_archive_proxy_t archive_proxy;\n\n    // the exclusive_publication is now 'owned' by the proxy\n    if (aeron_archive_proxy_init(\n        &archive_proxy,\n        aeron_archive->ctx,\n        exclusive_publication,\n        (int)aeron_archive->ctx->message_retry_attempts) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_exclusive_publication_close(exclusive_publication, NULL, NULL);\n        goto cleanup;\n    }\n\n    aeron_archive_proxy_set_control_session_id(&archive_proxy, aeron_archive->control_session_id);\n\n    aeron_counters_reader_t *counters_reader = aeron_counters_reader(aeron_archive->ctx->aeron);\n    int pub_limit_counter_id = aeron_counters_reader_find_by_type_id_and_registration_id(\n        counters_reader,\n        AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID,\n        publication_id);\n\n    while (!aeron_exclusive_publication_is_connected(exclusive_publication))\n    {\n        if (aeron_nano_clock() > deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s\", \"waiting for publication to connect\");\n            goto cleanup_proxy;\n        }\n\n        aeron_archive_idle(aeron_archive);\n    }\n\n    int64_t *pub_limit_counter = aeron_counters_reader_addr(counters_reader, pub_limit_counter_id);\n\n    while (0 == *pub_limit_counter)\n    {\n        if (aeron_nano_clock() > deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s\", \"waiting for available publication limit\");\n            goto cleanup_proxy;\n        }\n\n        aeron_archive_idle(aeron_archive);\n    }\n\n    // using the newly created proxy (based on the newly created exclusive publisher), send the replay request\n    correlation_id = aeron_archive_next_correlation_id(aeron_archive);\n\n    if (!aeron_archive_proxy_replay(\n        &archive_proxy,\n        correlation_id,\n        recording_id,\n        replay_channel,\n        replay_stream_id,\n        &response_channel_replay_params))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto cleanup_proxy;\n    }\n\n    if (aeron_archive_poll_for_response(\n        replay_session_id_p,\n        aeron_archive,\n        \"AeronArchive::replay\",\n        correlation_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto cleanup_proxy;\n    }\n\n    rc = 0;\n\ncleanup_proxy:\n    aeron_archive_proxy_close(&archive_proxy);\n\ncleanup:\n    if (NULL != control_request_channel)\n    {\n        aeron_free(control_request_channel);\n    }\n    return rc;\n}\n\nint aeron_archive_channel_with_session_id(char *out, size_t out_len, const char *in, int32_t session_id)\n{\n    int rc = 0;\n    aeron_uri_string_builder_t builder;\n\n    if (aeron_uri_string_builder_init_on_string(&builder, in) < 0 ||\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_SESSION_ID_KEY, session_id) < 0 ||\n        aeron_uri_string_builder_sprint(&builder, out, out_len) < 0)\n    {\n        rc = -1;\n    }\n\n    aeron_uri_string_builder_close(&builder);\n\n    return rc;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_client.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_CLIENT_H\n#define AERON_ARCHIVE_CLIENT_H\n\n#include \"aeron_archive_proxy.h\"\n#include \"aeron_archive_control_response_poller.h\"\n#include \"aeron_archive_recording_descriptor_poller.h\"\n#include \"aeron_archive_recording_subscription_descriptor_poller.h\"\n#include \"concurrent/aeron_thread.h\"\n\nstruct aeron_archive_stct\n{\n    bool owns_ctx;\n    aeron_archive_context_t *ctx;\n    aeron_mutex_t lock;\n    aeron_archive_proxy_t *archive_proxy;\n    bool owns_control_response_subscription;\n    aeron_subscription_t *subscription; // shared by various pollers\n    aeron_archive_control_response_poller_t *control_response_poller;\n    aeron_archive_recording_descriptor_poller_t *recording_descriptor_poller;\n    aeron_archive_recording_subscription_descriptor_poller_t *recording_subscription_descriptor_poller;\n    int64_t control_session_id;\n    int64_t archive_id;\n    bool is_in_callback;\n};\n\nint aeron_archive_create(\n    aeron_archive_t **aeron_archive,\n    aeron_archive_context_t *ctx,\n    aeron_archive_proxy_t *archive_proxy,\n    aeron_subscription_t *subscription,\n    aeron_archive_control_response_poller_t *control_response_poller,\n    aeron_archive_recording_descriptor_poller_t *recording_descriptor_poller,\n    aeron_archive_recording_subscription_descriptor_poller_t *recording_subscription_descriptor_poller,\n    int64_t control_session_id,\n    int64_t archive_id);\n\nvoid aeron_archive_idle(aeron_archive_t *aeron_archive);\n\naeron_archive_control_response_poller_t *aeron_archive_control_response_poller(aeron_archive_t *aeron_archive);\n\naeron_archive_proxy_t *aeron_archive_proxy(aeron_archive_t *aeron_archive);\n\nint64_t aeron_archive_next_correlation_id(aeron_archive_t *aeron_archive);\n\nint aeron_archive_poll_for_response(\n    int64_t *relevant_id_p, aeron_archive_t *aeron_archive, const char *operation_name, int64_t correlation_id);\n\n\n#endif //AERON_ARCHIVE_CLIENT_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_client_version.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_archive_client_version.h\"\n\nconst char *aeron_archive_client_version_text(void)\n{\n    return AERON_VERSION_TXT;\n}\n\nconst char *aeron_archive_client_version_git_sha(void)\n{\n    return AERON_VERSION_GITSHA;\n}\n\nint aeron_archive_client_version_major(void)\n{\n    return AERON_VERSION_MAJOR;\n}\n\nint aeron_archive_client_version_minor(void)\n{\n    return AERON_VERSION_MINOR;\n}\n\nint aeron_archive_client_version_patch(void)\n{\n    return AERON_VERSION_PATCH;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_client_version.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_CLIENT_VERSION_H\n#define AERON_ARCHIVE_CLIENT_VERSION_H\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\nconst char *aeron_archive_client_version_text(void);\nconst char *aeron_archive_client_version_git_sha(void);\nint aeron_archive_client_version_major(void);\nint aeron_archive_client_version_minor(void);\nint aeron_archive_client_version_patch(void);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif //AERON_ARCHIVE_CLIENT_VERSION_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_configuration.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_archive_configuration.h\"\n#include \"aeron_common.h\"\n\nint32_t aeron_archive_semantic_version(void)\n{\n    return aeron_semantic_version_compose(AERON_ARCHIVE_MAJOR_VERSION, AERON_ARCHIVE_MINOR_VERSION, AERON_ARCHIVE_PATCH_VERSION);\n}\n\nint32_t aeron_archive_protocol_version_with_archive_id(void)\n{\n    return aeron_semantic_version_compose(1, 11, 0);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_configuration.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_ARCHIVE_CONFIGURATION_H\n#define AERON_C_ARCHIVE_CONFIGURATION_H\n\n#include \"aeron_archive_proxy.h\"\n\n#define AERON_ARCHIVE_MAJOR_VERSION 1\n#define AERON_ARCHIVE_MINOR_VERSION 11\n#define AERON_ARCHIVE_PATCH_VERSION 0\n\nint32_t aeron_archive_semantic_version(void);\n\nint32_t aeron_archive_protocol_version_with_archive_id(void);\n\n#endif //AERON_C_ARCHIVE_CONFIGURATION_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_context.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#include \"aeron_archive.h\"\n#include \"aeron_archive_context.h\"\n\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"aeron_agent.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"concurrent/aeron_logbuffer_descriptor.h\"\n#include \"uri/aeron_uri_string_builder.h\"\n#include \"command/aeron_control_protocol.h\"\n\nint aeron_archive_context_init(aeron_archive_context_t **ctx)\n{\n    aeron_archive_context_t *_ctx = NULL;\n\n    if (NULL == ctx)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"aeron_archive_context_init(NULL)\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&_ctx, sizeof(aeron_archive_context_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_archive_context_init\");\n        return -1;\n    }\n\n    _ctx->aeron = NULL;\n    if (aeron_default_path(_ctx->aeron_directory_name, sizeof(_ctx->aeron_directory_name)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to resolve default aeron directory path\");\n        return -1;\n    }\n\n    _ctx->owns_aeron_client = false;\n\n    _ctx->control_request_channel = NULL;\n    _ctx->control_request_channel_length = 0;\n    _ctx->control_request_stream_id = AERON_ARCHIVE_CONTROL_STREAM_ID_DEFAULT;\n\n    _ctx->control_response_channel = NULL;\n    _ctx->control_response_channel_length = 0;\n    _ctx->control_response_stream_id = AERON_ARCHIVE_CONTROL_RESPONSE_STREAM_ID_DEFAULT;\n\n    _ctx->recording_events_channel = NULL;\n    _ctx->recording_events_channel_length = 0;\n    _ctx->recording_events_stream_id = AERON_ARCHIVE_RECORDING_EVENTS_STREAM_ID_DEFAULT;\n\n    _ctx->message_timeout_ns = AERON_ARCHIVE_MESSAGE_TIMEOUT_NS_DEFAULT;\n    _ctx->message_retry_attempts = AERON_ARCHIVE_MESSAGE_RETRY_ATTEMPTS_DEFAULT;\n\n    _ctx->control_term_buffer_sparse = AERON_ARCHIVE_CONTROL_TERM_BUFFER_SPARSE_DEFAULT;\n    _ctx->control_term_buffer_length = AERON_ARCHIVE_CONTROL_TERM_BUFFER_LENGTH_DEFAULT;\n\n    _ctx->idle_strategy_func = NULL;\n    _ctx->idle_strategy_state = NULL;\n    _ctx->owns_idle_strategy = false;\n\n    aeron_archive_context_set_credentials_supplier(_ctx, NULL, NULL, NULL, NULL);\n\n    _ctx->error_handler = NULL;\n    _ctx->error_handler_clientd = NULL;\n\n    _ctx->delegating_invoker_func = NULL;\n    _ctx->delegating_invoker_func_clientd = NULL;\n\n    _ctx->on_recording_signal = NULL;\n    _ctx->on_recording_signal_clientd = NULL;\n\n    // like in Java the default value of the control MTU is defined by the driver configuration\n    _ctx->control_mtu_length = aeron_config_parse_size64(\n        \"AERON_MTU_LENGTH\",\n        getenv(\"AERON_MTU_LENGTH\"),\n        1408,\n        AERON_DATA_HEADER_LENGTH,\n        AERON_MAX_UDP_PAYLOAD_LENGTH);\n\n    _ctx->client_name[0] = '\\0';\n\n    char *value = NULL;\n\n    if ((value = getenv(AERON_DIR_ENV_VAR)))\n    {\n        aeron_archive_context_set_aeron_directory_name(_ctx, value);\n    }\n\n    if ((value = getenv(AERON_ARCHIVE_CLIENT_NAME_ENV_VAR)))\n    {\n        aeron_archive_context_set_client_name(_ctx, value);\n    }\n\n    if ((value = getenv(AERON_ARCHIVE_CONTROL_CHANNEL_ENV_VAR)))\n    {\n        if (aeron_archive_context_set_control_request_channel(_ctx, value) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    _ctx->control_request_stream_id = aeron_config_parse_int32(\n        AERON_ARCHIVE_CONTROL_STREAM_ID_ENV_VAR,\n        getenv(AERON_ARCHIVE_CONTROL_STREAM_ID_ENV_VAR),\n        _ctx->control_request_stream_id,\n        INT32_MIN,\n        INT32_MAX);\n\n    if ((value = getenv(AERON_ARCHIVE_CONTROL_RESPONSE_CHANNEL_ENV_VAR)))\n    {\n        if (aeron_archive_context_set_control_response_channel(_ctx, value) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    _ctx->control_response_stream_id = aeron_config_parse_int32(\n        AERON_ARCHIVE_CONTROL_RESPONSE_STREAM_ID_ENV_VAR,\n        getenv(AERON_ARCHIVE_CONTROL_RESPONSE_STREAM_ID_ENV_VAR),\n        _ctx->control_response_stream_id,\n        INT32_MIN,\n        INT32_MAX);\n\n    if ((value = getenv(AERON_ARCHIVE_RECORDING_EVENTS_CHANNEL_ENV_VAR)))\n    {\n        if (aeron_archive_context_set_recording_events_channel(_ctx, value) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    _ctx->recording_events_stream_id = aeron_config_parse_int32(\n        AERON_ARCHIVE_RECORDING_EVENTS_STREAM_ID_ENV_VAR,\n        getenv(AERON_ARCHIVE_RECORDING_EVENTS_STREAM_ID_ENV_VAR),\n        _ctx->recording_events_stream_id,\n        INT32_MIN,\n        INT32_MAX);\n\n    _ctx->message_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_ARCHIVE_MESSAGE_TIMEOUT_ENV_VAR,\n        getenv(AERON_ARCHIVE_MESSAGE_TIMEOUT_ENV_VAR),\n        _ctx->message_timeout_ns,\n        1000,\n        INT64_MAX);\n\n    _ctx->message_retry_attempts = aeron_config_parse_duration_ns(\n        AERON_ARCHIVE_MESSAGE_RETRY_ATTEMPTS_ENV_VAR,\n        getenv(AERON_ARCHIVE_MESSAGE_RETRY_ATTEMPTS_ENV_VAR),\n        _ctx->message_retry_attempts,\n        0,\n        INT32_MAX);\n\n    _ctx->control_term_buffer_length = aeron_config_parse_size64(\n        AERON_ARCHIVE_CONTROL_TERM_BUFFER_LENGTH_ENV_VAR,\n        getenv(AERON_ARCHIVE_CONTROL_TERM_BUFFER_LENGTH_ENV_VAR),\n        _ctx->control_term_buffer_length,\n        AERON_LOGBUFFER_TERM_MIN_LENGTH,\n        AERON_LOGBUFFER_TERM_MAX_LENGTH);\n\n    _ctx->control_mtu_length = aeron_config_parse_size64(\n        AERON_ARCHIVE_CONTROL_MTU_LENGTH_ENV_VAR,\n        getenv(AERON_ARCHIVE_CONTROL_MTU_LENGTH_ENV_VAR),\n        _ctx->control_mtu_length,\n        AERON_DATA_HEADER_LENGTH,\n        AERON_MAX_UDP_PAYLOAD_LENGTH);\n\n    _ctx->control_term_buffer_sparse = aeron_parse_bool(\n        getenv(AERON_ARCHIVE_CONTROL_TERM_BUFFER_SPARSE_ENV_VAR), _ctx->control_term_buffer_sparse);\n\n    *ctx = _ctx;\n    return 0;\n\nerror:\n    aeron_free(_ctx);\n    return -1;\n}\n\nint aeron_archive_context_close(aeron_archive_context_t *ctx)\n{\n    if (NULL != ctx)\n    {\n        if (ctx->owns_aeron_client)\n        {\n            aeron_context_t *aeron_ctx = aeron_context(ctx->aeron);\n\n            aeron_close(ctx->aeron);\n            ctx->aeron = NULL;\n\n            aeron_context_close(aeron_ctx);\n        }\n\n        aeron_free(ctx->control_request_channel);\n        aeron_free(ctx->control_response_channel);\n        aeron_free(ctx->recording_events_channel);\n\n        if (ctx->owns_idle_strategy)\n        {\n            aeron_free(ctx->idle_strategy_state);\n        }\n\n        aeron_free(ctx);\n    }\n\n    return 0;\n}\n\nint aeron_archive_context_duplicate(aeron_archive_context_t **dest_p, aeron_archive_context_t *src)\n{\n    aeron_archive_context_t *_ctx = NULL;\n\n    if (aeron_alloc((void **)&_ctx, sizeof(aeron_archive_context_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_archive_context_init\");\n        return -1;\n    }\n\n    memcpy(_ctx, src, sizeof(aeron_archive_context_t));\n\n    _ctx->control_request_channel = NULL;\n    if (aeron_archive_context_set_control_request_channel(_ctx, src->control_request_channel) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    _ctx->control_response_channel = NULL;\n    if(aeron_archive_context_set_control_response_channel(_ctx, src->control_response_channel) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    _ctx->recording_events_channel = NULL;\n    if (aeron_archive_context_set_recording_events_channel(_ctx, src->recording_events_channel) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    _ctx->owns_idle_strategy = false;\n\n    *dest_p = _ctx;\n\n    return 0;\n}\n\nstatic int aeron_archive_apply_default_parameters(aeron_archive_context_t *ctx, const char* uri, aeron_uri_string_builder_t *builder)\n{\n\n    if (aeron_uri_string_builder_init_on_string(builder, uri) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (NULL == aeron_uri_string_builder_get(builder, AERON_URI_TERM_LENGTH_KEY))\n    {\n        if (aeron_uri_string_builder_put_int32(builder, AERON_URI_TERM_LENGTH_KEY, (int32_t)ctx->control_term_buffer_length) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    if (NULL == aeron_uri_string_builder_get(builder, AERON_URI_MTU_LENGTH_KEY))\n    {\n        if (aeron_uri_string_builder_put_int32(builder, AERON_URI_MTU_LENGTH_KEY, (int32_t)ctx->control_mtu_length) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    if (NULL == aeron_uri_string_builder_get(builder, AERON_URI_SPARSE_TERM_KEY))\n    {\n        if (aeron_uri_string_builder_put(builder, AERON_URI_SPARSE_TERM_KEY, ctx->control_term_buffer_sparse ? \"true\" : \"false\") < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    return 0;\n\nerror:\n    aeron_uri_string_builder_close(builder);\n    return -1;\n}\n\nint aeron_archive_context_conclude(aeron_archive_context_t *ctx)\n{\n    if (NULL == ctx->control_request_channel)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"control request channel is required\");\n        goto error;\n    }\n\n    if (NULL == ctx->control_response_channel)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"control response channel is required\");\n        goto error;\n    }\n\n    if (0 == ctx->message_retry_attempts)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"message_retry_attempts must be > 0\");\n        goto error;\n    }\n\n    if (NULL == ctx->aeron)\n    {\n        ctx->owns_aeron_client = true;\n\n        aeron_context_t *aeron_ctx;\n        if (aeron_context_init(&aeron_ctx) < 0 ||\n            aeron_context_set_dir(aeron_ctx, ctx->aeron_directory_name) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n\n        if (aeron_context_set_client_name(aeron_ctx, \"archive-client\") < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n\n        if (aeron_init(&ctx->aeron, aeron_ctx) < 0 ||\n            aeron_start(ctx->aeron) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    aeron_uri_string_builder_t request_channel;\n    if (aeron_archive_apply_default_parameters(ctx, ctx->control_request_channel, &request_channel) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    aeron_uri_string_builder_t response_channel;\n    if (aeron_archive_apply_default_parameters(ctx, ctx->control_response_channel, &response_channel) < 0)\n    {\n        aeron_uri_string_builder_close(&request_channel);\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    const char* control_mode =\n        aeron_uri_string_builder_get(&response_channel, AERON_UDP_CHANNEL_CONTROL_MODE_KEY);\n    if (NULL == control_mode ||\n        0 != strcmp(AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE, control_mode))\n    {\n        aeron_async_get_next_available_session_id_t *async = NULL;\n        if (aeron_async_next_session_id(&async, ctx->aeron, ctx->control_request_stream_id) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to fetch next session-id\");\n            goto error_close_uri_builders;\n        }\n\n        int result = 0;\n        int32_t session_id;\n        do\n        {\n            aeron_archive_context_invoke_aeron_client(ctx);\n\n            result = aeron_async_next_session_id_poll(&session_id, async);\n            if (result < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"Failed to fetch next session-id\");\n                goto error_close_uri_builders;\n            }\n        } while (0 == result);\n\n        if (aeron_uri_string_builder_put_int32(&request_channel, AERON_URI_SESSION_ID_KEY, session_id) < 0 ||\n            aeron_uri_string_builder_put_int32(&response_channel, AERON_URI_SESSION_ID_KEY, session_id) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_close_uri_builders;\n        }\n    }\n\n    char uri[AERON_URI_MAX_LENGTH];\n    if (aeron_uri_string_builder_sprint(&request_channel, uri, sizeof(uri)) < 0 ||\n        aeron_archive_context_set_control_request_channel(ctx, uri) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_close_uri_builders;\n    }\n\n    if (aeron_uri_string_builder_sprint(&response_channel, uri, sizeof(uri)) < 0 ||\n        aeron_archive_context_set_control_response_channel(ctx, uri) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_close_uri_builders;\n    }\n\n    aeron_uri_string_builder_close(&request_channel);\n    aeron_uri_string_builder_close(&response_channel);\n\n    if (NULL == ctx->idle_strategy_func)\n    {\n        ctx->owns_idle_strategy = true;\n        if (NULL == (ctx->idle_strategy_func = aeron_idle_strategy_load(\n            \"backoff\",\n            &ctx->idle_strategy_state,\n            \"AERON_ARCHIVE_IDLE_STRATEGY\",\n            NULL)))\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    return 0;\n\nerror_close_uri_builders:\n    aeron_uri_string_builder_close(&request_channel);\n    aeron_uri_string_builder_close(&response_channel);\nerror:\n    if (ctx->owns_aeron_client && NULL != ctx->aeron)\n    {\n        aeron_close(ctx->aeron);\n        ctx->aeron = NULL;\n    }\n\n    return -1;\n}\n\nvoid aeron_archive_context_idle(aeron_archive_context_t *ctx)\n{\n    ctx->idle_strategy_func(ctx->idle_strategy_state, 0);\n}\n\nvoid aeron_archive_context_invoke_aeron_client(aeron_archive_context_t *ctx)\n{\n    if (aeron_context_get_use_conductor_agent_invoker(aeron_context(ctx->aeron)))\n    {\n        aeron_main_do_work(ctx->aeron);\n    }\n\n    if (NULL != ctx->delegating_invoker_func)\n    {\n        ctx->delegating_invoker_func(ctx->delegating_invoker_func_clientd);\n    }\n}\n\nvoid aeron_archive_context_invoke_error_handler(\n    aeron_archive_context_t *ctx, int64_t correlation_id, int32_t error_code, const char *error_message)\n{\n    size_t formatted_message_length = strlen(error_message) + 100;\n    char *formatted_error_message;\n    aeron_alloc((void **)&formatted_error_message, formatted_message_length);\n\n    snprintf(\n        formatted_error_message,\n        formatted_message_length,\n        \"response for correlationId=%\" PRIi64 \", errorCode=%\" PRIi32 \", error: %s\",\n        correlation_id,\n        error_code,\n        error_message);\n\n    ctx->error_handler(\n        ctx->error_handler_clientd,\n        AERON_ERROR_CODE_GENERIC_ERROR,\n        formatted_error_message);\n\n    aeron_free(formatted_error_message);\n}\n\nint aeron_archive_context_set_aeron(aeron_archive_context_t *ctx, aeron_t *aeron)\n{\n    ctx->aeron = aeron;\n\n    return 0;\n}\n\naeron_t *aeron_archive_context_get_aeron(aeron_archive_context_t *ctx)\n{\n    return ctx->aeron;\n}\n\nint aeron_archive_context_set_owns_aeron_client(aeron_archive_context_t *ctx, bool owns_aeron_client)\n{\n    ctx->owns_aeron_client = owns_aeron_client;\n\n    return 0;\n}\n\nbool aeron_archive_context_get_owns_aeron_client(aeron_archive_context_t *ctx)\n{\n    return ctx->owns_aeron_client;\n}\n\nint aeron_archive_context_set_aeron_directory_name(aeron_archive_context_t *ctx, const char *aeron_directory_name)\n{\n    snprintf(ctx->aeron_directory_name, sizeof(ctx->aeron_directory_name), \"%s\", aeron_directory_name);\n\n    return 0;\n}\n\nconst char *aeron_archive_context_get_aeron_directory_name(aeron_archive_context_t *ctx)\n{\n    return ctx->aeron_directory_name;\n}\n\nstatic int aeron_archive_context_set_channel(char **target_channel, size_t *target_channel_length, const char *channel)\n{\n    if (NULL == channel)\n    {\n        aeron_free(*target_channel);\n        *target_channel = NULL;\n        *target_channel_length = 0;\n    }\n    else\n    {\n        const size_t channel_length = 1 + strlen(channel);\n        char *temp = *target_channel;\n\n        if (NULL == temp)\n        {\n            if (aeron_alloc((void **)&temp, channel_length) < 0)\n            {\n                AERON_SET_ERR(ENOMEM, \"%s\", \"unable to allocate control_response_channel\");\n                return -1;\n            }\n        }\n        else if (channel_length > *target_channel_length)\n        {\n            if (aeron_reallocf((void **)&temp, channel_length) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"unable to reallocate control_response_channel\");\n                return -1;\n            }\n        }\n\n        snprintf(temp, channel_length, \"%s\", channel);\n\n        *target_channel = temp;\n        *target_channel_length = channel_length;\n    }\n\n    return 0;\n}\n\nint aeron_archive_context_set_control_request_channel(\n    aeron_archive_context_t *ctx,\n    const char *control_request_channel)\n{\n    return aeron_archive_context_set_channel(\n        &ctx->control_request_channel, &ctx->control_request_channel_length, control_request_channel);\n}\n\nconst char *aeron_archive_context_get_control_request_channel(aeron_archive_context_t *ctx)\n{\n    return ctx->control_request_channel;\n}\n\nint aeron_archive_context_set_control_request_stream_id(aeron_archive_context_t *ctx, int32_t control_request_stream_id)\n{\n    ctx->control_request_stream_id = control_request_stream_id;\n\n    return 0;\n}\n\nint32_t aeron_archive_context_get_control_request_stream_id(aeron_archive_context_t *ctx)\n{\n    return ctx->control_request_stream_id;\n}\n\nint aeron_archive_context_set_control_response_channel(\n    aeron_archive_context_t *ctx,\n    const char *control_response_channel)\n{\n    return aeron_archive_context_set_channel(\n        &ctx->control_response_channel, &ctx->control_response_channel_length, control_response_channel);\n}\n\nconst char *aeron_archive_context_get_control_response_channel(aeron_archive_context_t *ctx)\n{\n    return ctx->control_response_channel;\n}\n\nint aeron_archive_context_set_control_response_stream_id(aeron_archive_context_t *ctx, int32_t control_response_stream_id)\n{\n    ctx->control_response_stream_id = control_response_stream_id;\n\n    return 0;\n}\n\nint32_t aeron_archive_context_get_control_response_stream_id(aeron_archive_context_t *ctx)\n{\n    return ctx->control_response_stream_id;\n}\n\nint aeron_archive_context_set_control_term_buffer_length(aeron_archive_context_t *ctx, size_t control_term_buffer_length)\n{\n    ctx->control_term_buffer_length = control_term_buffer_length;\n\n    return 0;\n}\n\nsize_t aeron_archive_context_get_control_term_buffer_length(aeron_archive_context_t *ctx)\n{\n    return ctx->control_term_buffer_length;\n}\n\nint aeron_archive_context_set_control_term_buffer_sparse(aeron_archive_context_t *ctx, bool control_term_buffer_sparse)\n{\n    ctx->control_term_buffer_sparse = control_term_buffer_sparse;\n\n    return 0;\n}\n\nbool aeron_archive_context_get_control_term_buffer_sparse(aeron_archive_context_t *ctx)\n{\n    return ctx->control_term_buffer_sparse;\n}\n\nint aeron_archive_context_set_control_mtu_length(aeron_archive_context_t *ctx, size_t control_mtu_length)\n{\n    ctx->control_mtu_length = control_mtu_length;\n\n    return 0;\n}\n\nsize_t aeron_archive_context_get_control_mtu_length(aeron_archive_context_t *ctx)\n{\n    return ctx->control_mtu_length;\n}\n\nint aeron_archive_context_set_client_name(aeron_archive_context_t *context, const char *value)\n{\n    size_t copy_length = 0;\n    if (!aeron_str_length(value, AERON_COUNTER_MAX_CLIENT_NAME_LENGTH + 1, &copy_length))\n    {\n        AERON_SET_ERR(EINVAL, \"client_name length must <= %d\", AERON_COUNTER_MAX_CLIENT_NAME_LENGTH);\n        return -1;\n    }\n\n    memcpy(context->client_name, value, copy_length);\n    context->client_name[copy_length] = '\\0';\n    return 0;\n}\n\nconst char *aeron_archive_context_get_client_name(aeron_archive_context_t *context)\n{\n    return context->client_name;\n}\n\nint aeron_archive_context_set_recording_events_channel(\n    aeron_archive_context_t *ctx,\n    const char *recording_events_channel)\n{\n    return aeron_archive_context_set_channel(\n        &ctx->recording_events_channel, &ctx->recording_events_channel_length, recording_events_channel);\n}\n\nconst char *aeron_archive_context_get_recording_events_channel(aeron_archive_context_t *ctx)\n{\n    return ctx->recording_events_channel;\n}\n\nint aeron_archive_context_set_recording_events_stream_id(aeron_archive_context_t *ctx, int32_t recording_events_stream_id)\n{\n    ctx->recording_events_stream_id = recording_events_stream_id;\n\n    return 0;\n}\n\nint32_t aeron_archive_context_get_recording_events_stream_id(aeron_archive_context_t *ctx)\n{\n    return ctx->recording_events_stream_id;\n}\n\nint aeron_archive_context_set_message_timeout_ns(aeron_archive_context_t *ctx, uint64_t message_timeout_ns)\n{\n    ctx->message_timeout_ns = message_timeout_ns;\n\n    return 0;\n}\n\nuint64_t aeron_archive_context_get_message_timeout_ns(aeron_archive_context_t *ctx)\n{\n    return ctx->message_timeout_ns;\n}\n\nint aeron_archive_context_set_message_retry_attempts(aeron_archive_context_t *ctx, uint32_t message_retry_attempts)\n{\n    ctx->message_retry_attempts = message_retry_attempts;\n    return 0;\n}\n\nuint32_t aeron_archive_context_get_message_retry_attempts(aeron_archive_context_t *ctx)\n{\n    return ctx->message_retry_attempts;\n}\n\nint aeron_archive_context_set_idle_strategy(\n    aeron_archive_context_t *ctx,\n    aeron_idle_strategy_func_t idle_strategy_func,\n    void *idle_strategy_state)\n{\n    ctx->idle_strategy_func = idle_strategy_func;\n    ctx->idle_strategy_state = idle_strategy_state;\n\n    return 0;\n}\n\nint aeron_archive_context_set_credentials_supplier(\n    aeron_archive_context_t *ctx,\n    aeron_archive_credentials_encoded_credentials_supplier_func_t encoded_credentials,\n    aeron_archive_credentials_challenge_supplier_func_t  on_challenge,\n    aeron_archive_credentials_free_func_t on_free,\n    void *clientd)\n{\n    ctx->credentials_supplier.encoded_credentials = encoded_credentials;\n    ctx->credentials_supplier.on_challenge = on_challenge;\n    ctx->credentials_supplier.on_free = on_free;\n    ctx->credentials_supplier.clientd = clientd;\n\n    return 0;\n}\n\nint aeron_archive_context_set_recording_signal_consumer(\n    aeron_archive_context_t *ctx,\n    aeron_archive_recording_signal_consumer_func_t on_recording_signal,\n    void *clientd)\n{\n    ctx->on_recording_signal = on_recording_signal;\n    ctx->on_recording_signal_clientd = clientd;\n\n    return 0;\n}\n\nint aeron_archive_context_set_error_handler(\n    aeron_archive_context_t *ctx,\n    aeron_error_handler_t error_handler,\n    void *clientd)\n{\n    ctx->error_handler = error_handler;\n    ctx->error_handler_clientd = clientd;\n\n    return 0;\n}\n\nint aeron_archive_context_set_delegating_invoker(\n    aeron_archive_context_t *ctx,\n    aeron_archive_delegating_invoker_func_t delegating_invoker_func,\n    void *clientd)\n{\n    ctx->delegating_invoker_func = delegating_invoker_func;\n    ctx->delegating_invoker_func_clientd = clientd;\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_context.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_CLIENT_CONTEXT_H\n#define AERON_ARCHIVE_CLIENT_CONTEXT_H\n\n#include \"aeron_archive.h\"\n#include \"aeron_archive_credentials_supplier.h\"\n\n#define AERON_ARCHIVE_MESSAGE_TIMEOUT_ENV_VAR \"AERON_ARCHIVE_MESSAGE_TIMEOUT\"\n#define AERON_ARCHIVE_MESSAGE_TIMEOUT_NS_DEFAULT  (UINT64_C(10) * 1000 * 1000 * 1000) // 10 seconds\n#define AERON_ARCHIVE_MESSAGE_RETRY_ATTEMPTS_ENV_VAR \"AERON_ARCHIVE_MESSAGE_RETRY_ATTEMPTS\"\n#define AERON_ARCHIVE_MESSAGE_RETRY_ATTEMPTS_DEFAULT  UINT32_C(3)\n#define AERON_ARCHIVE_CONTROL_CHANNEL_ENV_VAR \"AERON_ARCHIVE_CONTROL_CHANNEL\"\n#define AERON_ARCHIVE_CONTROL_STREAM_ID_ENV_VAR \"AERON_ARCHIVE_CONTROL_STREAM_ID\"\n#define AERON_ARCHIVE_CONTROL_STREAM_ID_DEFAULT (10)\n#define AERON_ARCHIVE_CONTROL_TERM_BUFFER_LENGTH_ENV_VAR \"AERON_ARCHIVE_CONTROL_TERM_BUFFER_LENGTH\"\n#define AERON_ARCHIVE_CONTROL_TERM_BUFFER_LENGTH_DEFAULT (64 * 1024)\n#define AERON_ARCHIVE_CONTROL_TERM_BUFFER_SPARSE_ENV_VAR \"AERON_ARCHIVE_CONTROL_TERM_BUFFER_SPARSE\"\n#define AERON_ARCHIVE_CONTROL_TERM_BUFFER_SPARSE_DEFAULT (true)\n#define AERON_ARCHIVE_CONTROL_MTU_LENGTH_ENV_VAR \"AERON_ARCHIVE_CONTROL_MTU_LENGTH\"\n#define AERON_ARCHIVE_CONTROL_RESPONSE_CHANNEL_ENV_VAR \"AERON_ARCHIVE_CONTROL_RESPONSE_CHANNEL\"\n#define AERON_ARCHIVE_CONTROL_RESPONSE_STREAM_ID_ENV_VAR \"AERON_ARCHIVE_CONTROL_RESPONSE_STREAM_ID\"\n#define AERON_ARCHIVE_CONTROL_RESPONSE_STREAM_ID_DEFAULT (20)\n#define AERON_ARCHIVE_RECORDING_EVENTS_CHANNEL_ENV_VAR \"AERON_ARCHIVE_RECORDING_EVENTS_CHANNEL\"\n#define AERON_ARCHIVE_RECORDING_EVENTS_STREAM_ID_ENV_VAR \"AERON_ARCHIVE_RECORDING_EVENTS_STREAM_ID\"\n#define AERON_ARCHIVE_RECORDING_EVENTS_STREAM_ID_DEFAULT (30)\n#define AERON_ARCHIVE_CLIENT_NAME_ENV_VAR \"AERON_ARCHIVE_CLIENT_NAME\"\n\nstruct aeron_archive_context_stct\n{\n    aeron_t *aeron;\n    char aeron_directory_name[AERON_MAX_PATH];\n    char client_name[AERON_COUNTER_MAX_CLIENT_NAME_LENGTH];\n    bool owns_aeron_client;\n\n    char *control_request_channel;\n    size_t control_request_channel_length;\n    int32_t control_request_stream_id;\n\n    char *control_response_channel;\n    size_t control_response_channel_length;\n    int32_t control_response_stream_id;\n\n    char *recording_events_channel;\n    size_t recording_events_channel_length;\n    int32_t recording_events_stream_id;\n\n    uint64_t message_timeout_ns;\n    uint32_t message_retry_attempts;\n\n    size_t control_term_buffer_length;\n    size_t control_mtu_length;\n    bool control_term_buffer_sparse;\n\n    aeron_idle_strategy_func_t idle_strategy_func;\n    void *idle_strategy_state;\n    bool owns_idle_strategy;\n\n    aeron_archive_credentials_supplier_t credentials_supplier;\n\n    aeron_archive_recording_signal_consumer_func_t on_recording_signal;\n    void *on_recording_signal_clientd;\n\n    aeron_error_handler_t error_handler;\n    void *error_handler_clientd;\n\n    aeron_archive_delegating_invoker_func_t delegating_invoker_func;\n    void *delegating_invoker_func_clientd;\n};\n\nint aeron_archive_context_duplicate(aeron_archive_context_t **dest_p, aeron_archive_context_t *src);\n\nint aeron_archive_context_conclude(aeron_archive_context_t *ctx);\n\nvoid aeron_archive_context_idle(aeron_archive_context_t *ctx);\n\nvoid aeron_archive_context_invoke_aeron_client(aeron_archive_context_t *ctx);\n\nvoid aeron_archive_context_invoke_error_handler(\n    aeron_archive_context_t *ctx, int64_t correlation_id, int32_t error_code, const char *error_message);\n\n#endif //AERON_ARCHIVE_CLIENT_CONTEXT_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_control_response_poller.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n\n#include \"aeron_archive.h\"\n#include \"aeron_archive_control_response_poller.h\"\n\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n\n#include \"c/aeron_archive_client/messageHeader.h\"\n#include \"c/aeron_archive_client/controlResponse.h\"\n#include \"c/aeron_archive_client/challenge.h\"\n#include \"c/aeron_archive_client/recordingSignalEvent.h\"\n\n#define AERON_ARCHIVE_CONTROL_RESPONSE_POLLER_ERROR_MESSAGE_INITIAL_LEN 10000\n#define AERON_ARCHIVE_CONTROL_RESPONSE_POLLER_ENCODED_CHALLENGE_BUFFER_INITIAL_LEN 10000\n\nvoid aeron_archive_control_response_poller_reset(aeron_archive_control_response_poller_t *poller);\n\naeron_controlled_fragment_handler_action_t aeron_archive_control_response_poller_on_fragment(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\n\n/* *************** */\n\nint aeron_archive_control_response_poller_create(\n    aeron_archive_control_response_poller_t **poller,\n    aeron_subscription_t *subscription,\n    int fragment_limit)\n{\n    aeron_archive_control_response_poller_t *_poller = NULL;\n\n    if (aeron_alloc((void **)&_poller, sizeof(aeron_archive_control_response_poller_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_archive_control_response_poller_t\");\n        return -1;\n    }\n\n    _poller->subscription = subscription;\n    _poller->fragment_limit = fragment_limit;\n\n    if (aeron_controlled_fragment_assembler_create(\n        &_poller->fragment_assembler,\n        aeron_archive_control_response_poller_on_fragment,\n        _poller) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"aeron_fragment_assembler_create\\n\");\n        return -1;\n    }\n\n    _poller->error_message_malloced_len = AERON_ARCHIVE_CONTROL_RESPONSE_POLLER_ERROR_MESSAGE_INITIAL_LEN;\n    if (aeron_alloc((void **)&_poller->error_message, _poller->error_message_malloced_len) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    _poller->encoded_challenge_buffer_malloced_len = AERON_ARCHIVE_CONTROL_RESPONSE_POLLER_ENCODED_CHALLENGE_BUFFER_INITIAL_LEN;\n    if (aeron_alloc((void **)&_poller->encoded_challenge_buffer, _poller->encoded_challenge_buffer_malloced_len) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    aeron_archive_control_response_poller_reset(_poller);\n\n    *poller = _poller;\n\n    return 0;\n}\n\nint aeron_archive_control_response_poller_close(aeron_archive_control_response_poller_t *poller)\n{\n    aeron_controlled_fragment_assembler_delete(poller->fragment_assembler);\n    poller->fragment_assembler = NULL;\n\n    aeron_free(poller->error_message);\n    poller->error_message = NULL;\n    poller->error_message_malloced_len = 0;\n\n    aeron_free(poller->encoded_challenge_buffer);\n    poller->encoded_challenge_buffer = NULL;\n    poller->encoded_challenge_buffer_malloced_len = 0;\n\n    aeron_free(poller);\n\n    return 0;\n}\n\nint aeron_archive_control_response_poller_poll(aeron_archive_control_response_poller_t *poller)\n{\n    if (poller->is_poll_complete)\n    {\n        aeron_archive_control_response_poller_reset(poller);\n    }\n\n    int rc = aeron_subscription_controlled_poll(\n        poller->subscription,\n        aeron_controlled_fragment_assembler_handler,\n        poller->fragment_assembler,\n        poller->fragment_limit);\n\n    if (rc < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n    }\n    else if (poller->error_on_fragment)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n\n    return rc;\n}\n\n/* *************** */\n\nvoid aeron_archive_control_response_poller_reset(aeron_archive_control_response_poller_t *poller)\n{\n    poller->error_on_fragment = false;\n\n    poller->control_session_id = AERON_NULL_VALUE;\n    poller->correlation_id = AERON_NULL_VALUE;\n    poller->relevant_id = AERON_NULL_VALUE;\n    poller->recording_id = AERON_NULL_VALUE;\n    poller->subscription_id = AERON_NULL_VALUE;\n    poller->position = AERON_NULL_VALUE;\n\n    poller->recording_signal_code = INT32_MIN;\n    poller->version = 0;\n\n    memset(poller->error_message, 0, poller->error_message_malloced_len);\n    memset(poller->encoded_challenge_buffer, 0, poller->encoded_challenge_buffer_malloced_len);\n\n    poller->encoded_challenge.data = NULL;\n    poller->encoded_challenge.length = 0;\n\n    poller->code_value = -1;\n\n    poller->is_poll_complete = false;\n    poller->is_code_ok = false;\n    poller->is_code_error = false;\n    poller->is_control_response = false;\n    poller->was_challenged = false;\n    poller->is_recording_signal = false;\n}\n\naeron_controlled_fragment_handler_action_t aeron_archive_control_response_poller_on_fragment(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_archive_control_response_poller_t *poller = (aeron_archive_control_response_poller_t *)clientd;\n\n    if (poller->is_poll_complete)\n    {\n        return AERON_ACTION_ABORT;\n    }\n\n    struct aeron_archive_client_messageHeader hdr;\n\n    if (aeron_archive_client_messageHeader_wrap(\n        &hdr,\n        (char *)buffer,\n        0,\n        aeron_archive_client_messageHeader_sbe_schema_version(),\n        length) == NULL)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"unable to wrap buffer\");\n        poller->error_on_fragment = true;\n        return AERON_ACTION_BREAK;\n    }\n\n    uint16_t schema_id = aeron_archive_client_messageHeader_schemaId(&hdr);\n\n    if (schema_id != aeron_archive_client_messageHeader_sbe_schema_id())\n    {\n        AERON_SET_ERR(-1, \"found schema id: %i that doesn't match expected id: %i\", schema_id, aeron_archive_client_messageHeader_sbe_schema_id());\n        poller->error_on_fragment = true;\n        return AERON_ACTION_BREAK;\n    }\n\n    uint16_t template_id = aeron_archive_client_messageHeader_templateId(&hdr);\n\n    switch(template_id)\n    {\n        case AERON_ARCHIVE_CLIENT_CONTROL_RESPONSE_SBE_TEMPLATE_ID:\n        {\n            struct aeron_archive_client_controlResponse control_response;\n\n            aeron_archive_client_controlResponse_wrap_for_decode(\n                &control_response,\n                (char *)buffer,\n                aeron_archive_client_messageHeader_encoded_length(),\n                aeron_archive_client_controlResponse_sbe_block_length(),\n                aeron_archive_client_controlResponse_sbe_schema_version(),\n                length);\n\n            poller->control_session_id = aeron_archive_client_controlResponse_controlSessionId(&control_response);\n            poller->correlation_id = aeron_archive_client_controlResponse_correlationId(&control_response);\n            poller->relevant_id = aeron_archive_client_controlResponse_relevantId(&control_response);\n            poller->version = aeron_archive_client_controlResponse_version(&control_response);\n\n            if (!aeron_archive_client_controlResponse_code(\n                &control_response,\n                (enum aeron_archive_client_controlResponseCode *)&poller->code_value))\n            {\n                AERON_SET_ERR(-1, \"%s\", \"unable to read control response code\");\n                poller->error_on_fragment = true;\n                return AERON_ACTION_BREAK;\n            }\n\n            poller->is_code_error = poller->code_value == aeron_archive_client_controlResponseCode_ERROR;\n            poller->is_code_ok = poller->code_value == aeron_archive_client_controlResponseCode_OK;\n\n            uint32_t error_message_len = aeron_archive_client_controlResponse_errorMessage_length(&control_response);\n            uint32_t len_with_terminator = error_message_len + 1;\n            if (len_with_terminator > poller->error_message_malloced_len)\n            {\n                if (aeron_reallocf((void **)&poller->error_message, len_with_terminator) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"unable to reallocate error_message\");\n                    poller->error_on_fragment = true;\n                    return AERON_ACTION_BREAK;\n                }\n                poller->error_message_malloced_len = len_with_terminator;\n            }\n\n            aeron_archive_client_controlResponse_get_errorMessage(\n                &control_response,\n                poller->error_message,\n                error_message_len);\n            poller->error_message[error_message_len] = '\\0';\n\n            poller->is_control_response = true;\n            poller->is_poll_complete = true;\n\n            return AERON_ACTION_BREAK;\n        }\n\n        case AERON_ARCHIVE_CLIENT_CHALLENGE_SBE_TEMPLATE_ID:\n        {\n            struct aeron_archive_client_challenge challenge;\n\n            aeron_archive_client_challenge_wrap_for_decode(\n                &challenge,\n                (char *)buffer,\n                aeron_archive_client_messageHeader_encoded_length(),\n                aeron_archive_client_challenge_sbe_block_length(),\n                aeron_archive_client_challenge_sbe_schema_version(),\n                length);\n\n            poller->control_session_id = aeron_archive_client_challenge_controlSessionId(&challenge);\n            poller->correlation_id = aeron_archive_client_challenge_correlationId(&challenge);\n            poller->relevant_id = AERON_NULL_VALUE;\n            poller->version = aeron_archive_client_challenge_version(&challenge);\n\n            poller->code_value = aeron_archive_client_controlResponseCode_NULL_VALUE;\n            poller->is_code_error = false;\n            poller->is_code_ok = false;\n\n            uint32_t encoded_challenge_length = aeron_archive_client_challenge_encodedChallenge_length(&challenge);\n            uint32_t len_with_terminator = encoded_challenge_length + 1;\n            if (len_with_terminator > poller->encoded_challenge_buffer_malloced_len)\n            {\n                if (aeron_reallocf((void **)&poller->encoded_challenge_buffer, len_with_terminator) < 0)\n                {\n                    AERON_SET_ERR(ENOMEM, \"%s\", \"unable to reallocate encoded_challenge_buffer\");\n                    poller->error_on_fragment = true;\n                    return AERON_ACTION_BREAK;\n                }\n                poller->encoded_challenge_buffer_malloced_len = len_with_terminator;\n            }\n\n            aeron_archive_client_challenge_get_encodedChallenge(\n                &challenge,\n                poller->encoded_challenge_buffer,\n                encoded_challenge_length);\n            poller->encoded_challenge_buffer[encoded_challenge_length] = '\\0';\n\n            poller->encoded_challenge.data = poller->encoded_challenge_buffer;\n            poller->encoded_challenge.length = encoded_challenge_length;\n\n            poller->is_control_response = false;\n            poller->was_challenged = true;\n            poller->is_poll_complete = true;\n\n            return AERON_ACTION_BREAK;\n        }\n\n        case AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_EVENT_SBE_TEMPLATE_ID:\n        {\n            struct aeron_archive_client_recordingSignalEvent recording_signal_event;\n\n            aeron_archive_client_recordingSignalEvent_wrap_for_decode(\n                &recording_signal_event,\n                (char *)buffer,\n                aeron_archive_client_messageHeader_encoded_length(),\n                aeron_archive_client_recordingSignalEvent_sbe_block_length(),\n                aeron_archive_client_recordingSignalEvent_sbe_schema_version(),\n                length);\n\n            poller->control_session_id = aeron_archive_client_recordingSignalEvent_controlSessionId(&recording_signal_event);\n            poller->correlation_id = aeron_archive_client_recordingSignalEvent_correlationId(&recording_signal_event);\n            poller->recording_id = aeron_archive_client_recordingSignalEvent_recordingId(&recording_signal_event);\n            poller->subscription_id = aeron_archive_client_recordingSignalEvent_subscriptionId(&recording_signal_event);\n            poller->position = aeron_archive_client_recordingSignalEvent_position(&recording_signal_event);\n\n            if (!aeron_archive_client_recordingSignalEvent_signal(\n                &recording_signal_event,\n                (enum aeron_archive_client_recordingSignal *)&poller->recording_signal_code))\n            {\n                AERON_SET_ERR(-1, \"%s\", \"unable to read recording signal code\");\n                poller->error_on_fragment = true;\n                return AERON_ACTION_BREAK;\n            }\n\n            poller->is_recording_signal = true;\n            poller->is_poll_complete = true;\n\n            return AERON_ACTION_BREAK;\n        }\n\n        default:\n            // do nothing\n            break;\n    }\n\n    return AERON_ACTION_CONTINUE;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_control_response_poller.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_ARCHIVE_CONTROL_RESPONSE_POLLER_H\n#define AERON_C_ARCHIVE_CONTROL_RESPONSE_POLLER_H\n\n#include \"aeron_archive.h\"\n\n#include \"aeronc.h\"\n\n#define AERON_ARCHIVE_CONTROL_RESPONSE_POLLER_FRAGMENT_LIMIT_DEFAULT 10\n\ntypedef struct aeron_archive_control_response_poller_stct\n{\n    aeron_subscription_t *subscription;\n    int fragment_limit;\n    aeron_controlled_fragment_assembler_t *fragment_assembler;\n    bool error_on_fragment;\n\n    int64_t control_session_id;\n    int64_t correlation_id;\n    int64_t relevant_id;\n    int64_t recording_id;\n    int64_t subscription_id;\n    int64_t position;\n\n    int32_t recording_signal_code;\n    int32_t version;\n\n    char *error_message;\n    uint32_t error_message_malloced_len;\n\n    char *encoded_challenge_buffer;\n    uint32_t encoded_challenge_buffer_malloced_len;\n\n    aeron_archive_encoded_credentials_t encoded_challenge;\n\n    int code_value;\n\n    bool is_poll_complete;\n    bool is_code_ok;\n    bool is_code_error;\n    bool is_control_response;\n    bool was_challenged;\n    bool is_recording_signal;\n}\naeron_archive_control_response_poller_t;\n\nint aeron_archive_control_response_poller_create(\n    aeron_archive_control_response_poller_t **poller,\n    aeron_subscription_t *subscription,\n    int fragment_limit);\n\nint aeron_archive_control_response_poller_close(aeron_archive_control_response_poller_t *poller);\n\nint aeron_archive_control_response_poller_poll(aeron_archive_control_response_poller_t *poller);\n\n#endif // AERON_C_ARCHIVE_CONTROL_RESPONSE_POLLER_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_credentials_supplier.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_archive.h\"\n#include \"aeron_archive_credentials_supplier.h\"\n\naeron_archive_encoded_credentials_t *aeron_archive_credentials_supplier_encoded_credentials(aeron_archive_credentials_supplier_t *supplier)\n{\n    if (NULL == supplier->encoded_credentials) {\n        return NULL;\n    }\n\n    return supplier->encoded_credentials(supplier->clientd);\n}\n\naeron_archive_encoded_credentials_t *aeron_archive_credentials_supplier_on_challenge(\n    aeron_archive_credentials_supplier_t *supplier,\n    aeron_archive_encoded_credentials_t *encoded_challenge)\n{\n    if (NULL == supplier->on_challenge) {\n        return NULL;\n    }\n\n    return supplier->on_challenge(encoded_challenge, supplier->clientd);\n}\n\nvoid aeron_archive_credentials_supplier_on_free(aeron_archive_credentials_supplier_t *supplier, aeron_archive_encoded_credentials_t *credentials)\n{\n    if (NULL != supplier->on_free)\n    {\n        supplier->on_free(credentials, supplier->clientd);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_credentials_supplier.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_CREDENTIALS_SUPPLIER_H\n#define AERON_ARCHIVE_CREDENTIALS_SUPPLIER_H\n\n#include \"aeron_archive.h\"\n#include \"aeronc.h\"\n#include \"aeron_common.h\"\n\ntypedef struct aeron_archive_credentials_supplier_stct\n{\n    aeron_archive_credentials_encoded_credentials_supplier_func_t encoded_credentials;\n    aeron_archive_credentials_challenge_supplier_func_t  on_challenge;\n    aeron_archive_credentials_free_func_t on_free;\n    void *clientd;\n}\naeron_archive_credentials_supplier_t;\n\naeron_archive_encoded_credentials_t *aeron_archive_credentials_supplier_encoded_credentials(aeron_archive_credentials_supplier_t *supplier);\naeron_archive_encoded_credentials_t *aeron_archive_credentials_supplier_on_challenge(\n    aeron_archive_credentials_supplier_t *supplier,\n    aeron_archive_encoded_credentials_t *encoded_challenge);\nvoid aeron_archive_credentials_supplier_on_free(aeron_archive_credentials_supplier_t *supplier, aeron_archive_encoded_credentials_t *credentials);\n\n#endif //AERON_ARCHIVE_CREDENTIALS_SUPPLIER_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_proxy.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n\n#include \"aeron_alloc.h\"\n#include \"aeron_archive.h\"\n#include \"aeron_archive_client_version.h\"\n#include \"aeron_archive_context.h\"\n#include \"aeron_archive_proxy.h\"\n#include \"aeron_archive_configuration.h\"\n#include \"aeron_archive_replay_params.h\"\n#include \"util/aeron_error.h\"\n\n#include \"c/aeron_archive_client/authConnectRequest.h\"\n#include \"c/aeron_archive_client/archiveIdRequest.h\"\n#include \"c/aeron_archive_client/challengeResponse.h\"\n#include \"c/aeron_archive_client/closeSessionRequest.h\"\n#include \"c/aeron_archive_client/startRecordingRequest2.h\"\n#include \"c/aeron_archive_client/extendRecordingRequest2.h\"\n#include \"c/aeron_archive_client/recordingPositionRequest.h\"\n#include \"c/aeron_archive_client/startPositionRequest.h\"\n#include \"c/aeron_archive_client/stopPositionRequest.h\"\n#include \"c/aeron_archive_client/maxRecordedPositionRequest.h\"\n#include \"c/aeron_archive_client/stopRecordingRequest.h\"\n#include \"c/aeron_archive_client/stopRecordingSubscriptionRequest.h\"\n#include \"c/aeron_archive_client/stopRecordingByIdentityRequest.h\"\n#include \"c/aeron_archive_client/findLastMatchingRecordingRequest.h\"\n#include \"c/aeron_archive_client/listRecordingRequest.h\"\n#include \"c/aeron_archive_client/listRecordingsRequest.h\"\n#include \"c/aeron_archive_client/listRecordingsForUriRequest.h\"\n#include \"c/aeron_archive_client/boundedReplayRequest.h\"\n#include \"c/aeron_archive_client/replayRequest.h\"\n#include \"c/aeron_archive_client/truncateRecordingRequest.h\"\n#include \"c/aeron_archive_client/stopReplayRequest.h\"\n#include \"c/aeron_archive_client/stopAllReplaysRequest.h\"\n#include \"c/aeron_archive_client/listRecordingSubscriptionsRequest.h\"\n#include \"c/aeron_archive_client/purgeRecordingRequest.h\"\n#include \"c/aeron_archive_client/replicateRequest2.h\"\n#include \"c/aeron_archive_client/stopReplicationRequest.h\"\n#include \"c/aeron_archive_client/replayTokenRequest.h\"\n#include \"c/aeron_archive_client/detachSegmentsRequest.h\"\n#include \"c/aeron_archive_client/deleteDetachedSegmentsRequest.h\"\n#include \"c/aeron_archive_client/purgeSegmentsRequest.h\"\n#include \"c/aeron_archive_client/attachSegmentsRequest.h\"\n#include \"c/aeron_archive_client/migrateSegmentsRequest.h\"\n#include \"c/aeron_archive_client/updateChannelRequest.h\"\n\nint64_t aeron_archive_proxy_offer_once(aeron_archive_proxy_t *archive_proxy, size_t length);\n\nbool aeron_archive_proxy_offer(\n    aeron_archive_proxy_t *archive_proxy,\n    size_t length);\n\n/* **************** */\n\nint aeron_archive_proxy_create(\n    aeron_archive_proxy_t **archive_proxy,\n    aeron_archive_context_t *ctx,\n    aeron_exclusive_publication_t *exclusive_publication,\n    int retry_attempts)\n{\n    aeron_archive_proxy_t *_archive_proxy = NULL;\n\n    if (aeron_alloc((void **)&_archive_proxy, sizeof(aeron_archive_proxy_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_archive_proxy_t\");\n        return -1;\n    }\n\n    aeron_archive_proxy_init(_archive_proxy, ctx, exclusive_publication, retry_attempts);\n\n    *archive_proxy = _archive_proxy;\n\n    return 0;\n}\n\nint aeron_archive_proxy_init(\n    aeron_archive_proxy_t *archive_proxy,\n    aeron_archive_context_t *ctx,\n    aeron_exclusive_publication_t *exclusive_publication,\n    int retry_attempts)\n{\n    int total_length = snprintf(\n        archive_proxy->client_info,\n        sizeof(archive_proxy->client_info),\n        \"name=%s version=%s commit=%s\",\n        ctx->client_name,\n        aeron_archive_client_version_text(),\n        aeron_archive_client_version_git_sha());\n    if (total_length < 0)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Failed to format client_info\");\n        return -1;\n    }\n    archive_proxy->client_info[total_length] = '\\0';\n\n    archive_proxy->ctx = ctx;\n    archive_proxy->exclusive_publication = exclusive_publication;\n    archive_proxy->control_session_id = AERON_NULL_VALUE;\n    archive_proxy->retry_attempts = retry_attempts;\n\n    return 0;\n}\n\nint aeron_archive_proxy_set_control_session_id(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t control_session_id)\n{\n    archive_proxy->control_session_id = control_session_id;\n\n    return 0;\n}\n\nint aeron_archive_proxy_close(aeron_archive_proxy_t *archive_proxy)\n{\n    aeron_exclusive_publication_close(archive_proxy->exclusive_publication, NULL, NULL);\n    archive_proxy->exclusive_publication = NULL;\n\n    return 0;\n}\n\nint aeron_archive_proxy_delete(aeron_archive_proxy_t *archive_proxy)\n{\n    aeron_archive_proxy_close(archive_proxy);\n\n    aeron_free(archive_proxy);\n\n    return 0;\n}\n\nbool aeron_archive_proxy_try_connect(\n    aeron_archive_proxy_t *archive_proxy,\n    const char *control_response_channel,\n    int32_t control_response_stream_id,\n    aeron_archive_encoded_credentials_t *encoded_credentials,\n    int64_t correlation_id)\n{\n    struct aeron_archive_client_authConnectRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_authConnectRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_authConnectRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_authConnectRequest_set_responseStreamId(&codec, control_response_stream_id);\n    aeron_archive_client_authConnectRequest_set_version(&codec, aeron_archive_semantic_version());\n    aeron_archive_client_authConnectRequest_put_responseChannel(\n        &codec,\n        control_response_channel,\n        strlen(control_response_channel));\n    aeron_archive_client_authConnectRequest_put_encodedCredentials(\n        &codec,\n        NULL != encoded_credentials && NULL != encoded_credentials->data ? encoded_credentials->data : \"\",\n        NULL != encoded_credentials && NULL != encoded_credentials->data ? encoded_credentials->length : 0);\n    aeron_archive_client_authConnectRequest_put_clientInfo(\n        &codec,\n        archive_proxy->client_info,\n        strlen(archive_proxy->client_info));\n\n    return aeron_archive_proxy_offer_once(\n        archive_proxy,\n        aeron_archive_client_authConnectRequest_encoded_length(&codec)) > 0;\n}\n\nbool aeron_archive_proxy_archive_id(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id)\n{\n    struct aeron_archive_client_archiveIdRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_archiveIdRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_archiveIdRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_archiveIdRequest_set_correlationId(&codec, correlation_id);\n\n    return aeron_archive_proxy_offer_once(\n        archive_proxy,\n        aeron_archive_client_archiveIdRequest_encoded_length(&codec)) > 0;\n}\n\nbool aeron_archive_proxy_challenge_response(\n    aeron_archive_proxy_t *archive_proxy,\n    aeron_archive_encoded_credentials_t *encoded_credentials,\n    int64_t correlation_id)\n{\n    struct aeron_archive_client_challengeResponse codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_challengeResponse_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_challengeResponse_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_challengeResponse_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_challengeResponse_put_encodedCredentials(\n        &codec,\n        NULL == encoded_credentials ? \"\" : encoded_credentials->data,\n        NULL == encoded_credentials ? 0 : encoded_credentials->length);\n\n    return aeron_archive_proxy_offer_once(\n        archive_proxy,\n        aeron_archive_client_challengeResponse_encoded_length(&codec)) > 0;\n}\n\nbool aeron_archive_proxy_close_session(aeron_archive_proxy_t *archive_proxy)\n{\n    struct aeron_archive_client_closeSessionRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_closeSessionRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_closeSessionRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n\n    return aeron_archive_proxy_offer_once(\n        archive_proxy,\n        aeron_archive_client_closeSessionRequest_encoded_length(&codec)) > 0;\n}\n\nbool aeron_archive_proxy_start_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    const char *recording_channel,\n    int32_t recording_stream_id,\n    bool local_source,\n    bool auto_stop,\n    int64_t correlation_id)\n{\n    struct aeron_archive_client_startRecordingRequest2 codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_startRecordingRequest2_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_startRecordingRequest2_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_startRecordingRequest2_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_startRecordingRequest2_set_streamId(&codec, recording_stream_id);\n    aeron_archive_client_startRecordingRequest2_set_sourceLocation(\n        &codec,\n        local_source ? aeron_archive_client_sourceLocation_LOCAL : aeron_archive_client_sourceLocation_REMOTE);\n    aeron_archive_client_startRecordingRequest2_set_autoStop(\n        &codec,\n        auto_stop ? aeron_archive_client_booleanType_TRUE : aeron_archive_client_booleanType_FALSE);\n    aeron_archive_client_startRecordingRequest2_put_channel(\n        &codec,\n        recording_channel,\n        strlen(recording_channel));\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_startRecordingRequest2_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_get_recording_position(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_recordingPositionRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_recordingPositionRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_recordingPositionRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_recordingPositionRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_recordingPositionRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_recordingPositionRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_get_start_position(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_startPositionRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_startPositionRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_startPositionRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_startPositionRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_startPositionRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_startPositionRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_get_stop_position(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_stopPositionRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_stopPositionRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_stopPositionRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_stopPositionRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_stopPositionRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_stopPositionRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_get_max_recorded_position(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_maxRecordedPositionRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_maxRecordedPositionRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_maxRecordedPositionRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_maxRecordedPositionRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_maxRecordedPositionRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_maxRecordedPositionRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_stop_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    const char *channel,\n    int32_t stream_id)\n{\n    struct aeron_archive_client_stopRecordingRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_stopRecordingRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_stopRecordingRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_stopRecordingRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_stopRecordingRequest_set_streamId(&codec, stream_id);\n    aeron_archive_client_stopRecordingRequest_put_channel(\n        &codec,\n        channel,\n        strlen(channel));\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_stopRecordingRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_stop_recording_subscription(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t subscription_id)\n{\n    struct aeron_archive_client_stopRecordingSubscriptionRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_stopRecordingSubscriptionRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_stopRecordingSubscriptionRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_stopRecordingSubscriptionRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_stopRecordingSubscriptionRequest_set_subscriptionId(&codec, subscription_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_stopRecordingSubscriptionRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_stop_recording_by_identity(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_stopRecordingByIdentityRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_stopRecordingByIdentityRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_stopRecordingByIdentityRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_stopRecordingByIdentityRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_stopRecordingByIdentityRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_stopRecordingByIdentityRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_find_last_matching_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t min_recording_id,\n    const char *channel_fragment,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    struct aeron_archive_client_findLastMatchingRecordingRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_findLastMatchingRecordingRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_findLastMatchingRecordingRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_findLastMatchingRecordingRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_findLastMatchingRecordingRequest_set_minRecordingId(&codec, min_recording_id);\n    aeron_archive_client_findLastMatchingRecordingRequest_set_sessionId(&codec, session_id);\n    aeron_archive_client_findLastMatchingRecordingRequest_set_streamId(&codec, stream_id);\n    aeron_archive_client_findLastMatchingRecordingRequest_put_channel(\n        &codec,\n        channel_fragment,\n        strlen(channel_fragment));\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_findLastMatchingRecordingRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_list_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_listRecordingRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_listRecordingRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_listRecordingRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_listRecordingRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_listRecordingRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_listRecordingRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_list_recordings(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t from_recording_id,\n    int32_t record_count)\n{\n    struct aeron_archive_client_listRecordingsRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_listRecordingsRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_listRecordingsRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_listRecordingsRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_listRecordingsRequest_set_fromRecordingId(&codec, from_recording_id);\n    aeron_archive_client_listRecordingsRequest_set_recordCount(&codec, record_count);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_listRecordingsRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_list_recordings_for_uri(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t from_recording_id,\n    int32_t record_count,\n    const char *channel_fragment,\n    int32_t stream_id)\n{\n    struct aeron_archive_client_listRecordingsForUriRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_listRecordingsForUriRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_listRecordingsForUriRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_listRecordingsForUriRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_listRecordingsForUriRequest_set_fromRecordingId(&codec, from_recording_id);\n    aeron_archive_client_listRecordingsForUriRequest_set_recordCount(&codec, record_count);\n    aeron_archive_client_listRecordingsForUriRequest_set_streamId(&codec, stream_id);\n    aeron_archive_client_listRecordingsForUriRequest_put_channel(\n        &codec,\n        channel_fragment,\n        strlen(channel_fragment));\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_listRecordingsForUriRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_replay(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params)\n{\n    size_t length;\n    struct aeron_archive_client_messageHeader hdr;\n\n    if (aeron_archive_replay_params_is_bounded(params))\n    {\n        struct aeron_archive_client_boundedReplayRequest codec;\n\n        aeron_archive_client_boundedReplayRequest_wrap_and_apply_header(\n            &codec,\n            (char *)archive_proxy->buffer,\n            0,\n            AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n            &hdr);\n        aeron_archive_client_boundedReplayRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n        aeron_archive_client_boundedReplayRequest_set_correlationId(&codec, correlation_id);\n        aeron_archive_client_boundedReplayRequest_set_recordingId(&codec, recording_id);\n        aeron_archive_client_boundedReplayRequest_set_position(&codec, params->position);\n        aeron_archive_client_boundedReplayRequest_set_length(&codec, params->length);\n        aeron_archive_client_boundedReplayRequest_set_limitCounterId(&codec, params->bounding_limit_counter_id);\n        aeron_archive_client_boundedReplayRequest_set_replayStreamId(&codec, replay_stream_id);\n        aeron_archive_client_boundedReplayRequest_set_fileIoMaxLength(&codec, params->file_io_max_length);\n        aeron_archive_client_boundedReplayRequest_set_replayToken(&codec, params->replay_token);\n        aeron_archive_client_boundedReplayRequest_put_replayChannel(\n            &codec,\n           replay_channel,\n            strlen(replay_channel));\n\n        length = aeron_archive_client_boundedReplayRequest_encoded_length(&codec);\n    }\n    else\n    {\n        struct aeron_archive_client_replayRequest codec;\n\n        aeron_archive_client_replayRequest_wrap_and_apply_header(\n            &codec,\n            (char *)archive_proxy->buffer,\n            0,\n            AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n            &hdr);\n        aeron_archive_client_replayRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n        aeron_archive_client_replayRequest_set_correlationId(&codec, correlation_id);\n        aeron_archive_client_replayRequest_set_recordingId(&codec, recording_id);\n        aeron_archive_client_replayRequest_set_position(&codec, params->position);\n        aeron_archive_client_replayRequest_set_length(&codec, params->length);\n        aeron_archive_client_replayRequest_set_replayStreamId(&codec, replay_stream_id);\n        aeron_archive_client_replayRequest_set_fileIoMaxLength(&codec, params->file_io_max_length);\n        aeron_archive_client_replayRequest_set_replayToken(&codec, params->replay_token);\n        aeron_archive_client_replayRequest_put_replayChannel(\n            &codec,\n            replay_channel,\n            strlen(replay_channel));\n\n        length = aeron_archive_client_replayRequest_encoded_length(&codec);\n    }\n\n    return aeron_archive_proxy_offer(archive_proxy, length);\n}\n\nbool aeron_archive_proxy_truncate_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id,\n    int64_t position)\n{\n    struct aeron_archive_client_truncateRecordingRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_truncateRecordingRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_truncateRecordingRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_truncateRecordingRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_truncateRecordingRequest_set_recordingId(&codec, recording_id);\n    aeron_archive_client_truncateRecordingRequest_set_position(&codec, position);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_truncateRecordingRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_stop_replay(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t replay_session_id)\n{\n    struct aeron_archive_client_stopReplayRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_stopReplayRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_stopReplayRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_stopReplayRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_stopReplayRequest_set_replaySessionId(&codec, replay_session_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_stopReplayRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_stop_all_replays(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_stopAllReplaysRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_stopAllReplaysRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_stopAllReplaysRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_stopAllReplaysRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_stopAllReplaysRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_stopAllReplaysRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_list_recording_subscriptions(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int32_t pseudo_index,\n    int32_t subscription_count,\n    const char *channel_fragment,\n    int32_t stream_id,\n    bool apply_stream_id)\n{\n    struct aeron_archive_client_listRecordingSubscriptionsRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_listRecordingSubscriptionsRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_listRecordingSubscriptionsRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_listRecordingSubscriptionsRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_listRecordingSubscriptionsRequest_set_pseudoIndex(&codec, pseudo_index);\n    aeron_archive_client_listRecordingSubscriptionsRequest_set_subscriptionCount(&codec, subscription_count);\n    aeron_archive_client_listRecordingSubscriptionsRequest_set_applyStreamId(\n        &codec,\n        apply_stream_id ? aeron_archive_client_booleanType_TRUE : aeron_archive_client_booleanType_FALSE);\n    aeron_archive_client_listRecordingSubscriptionsRequest_set_streamId(&codec, stream_id);\n    aeron_archive_client_listRecordingSubscriptionsRequest_put_channel(\n        &codec,\n        channel_fragment,\n        strlen(channel_fragment));\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_listRecordingSubscriptionsRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_purge_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_purgeRecordingRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_purgeRecordingRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_purgeRecordingRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_purgeRecordingRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_purgeRecordingRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_purgeRecordingRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_extend_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t recording_id,\n    const char *recording_channel,\n    int32_t recording_stream_id,\n    bool local_source,\n    bool auto_stop,\n    int64_t correlation_id)\n{\n    struct aeron_archive_client_extendRecordingRequest2 codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_extendRecordingRequest2_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_extendRecordingRequest2_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_extendRecordingRequest2_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_extendRecordingRequest2_set_recordingId(&codec, recording_id);\n    aeron_archive_client_extendRecordingRequest2_set_streamId(&codec, recording_stream_id);\n    aeron_archive_client_extendRecordingRequest2_set_sourceLocation(\n        &codec,\n        local_source ? aeron_archive_client_sourceLocation_LOCAL : aeron_archive_client_sourceLocation_REMOTE);\n    aeron_archive_client_extendRecordingRequest2_set_autoStop(\n        &codec,\n        auto_stop ? aeron_archive_client_booleanType_TRUE : aeron_archive_client_booleanType_FALSE);\n    aeron_archive_client_extendRecordingRequest2_put_channel(\n        &codec,\n        recording_channel,\n        strlen(recording_channel));\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_extendRecordingRequest2_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_replicate(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t src_recording_id,\n    int32_t src_control_stream_id,\n    const char *src_control_channel,\n    aeron_archive_replication_params_t *params)\n{\n    struct aeron_archive_client_replicateRequest2 codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_replicateRequest2_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_replicateRequest2_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_replicateRequest2_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_replicateRequest2_set_srcRecordingId(&codec, src_recording_id);\n    aeron_archive_client_replicateRequest2_set_dstRecordingId(&codec, params->dst_recording_id);\n    aeron_archive_client_replicateRequest2_set_stopPosition(&codec, params->stop_position);\n    aeron_archive_client_replicateRequest2_set_channelTagId(&codec, params->channel_tag_id);\n    aeron_archive_client_replicateRequest2_set_subscriptionTagId(&codec, params->subscription_tag_id);\n    aeron_archive_client_replicateRequest2_set_srcControlStreamId(&codec, src_control_stream_id);\n    aeron_archive_client_replicateRequest2_set_fileIoMaxLength(&codec, params->file_io_max_length);\n    aeron_archive_client_replicateRequest2_set_replicationSessionId(&codec, params->replication_session_id);\n\n    aeron_archive_client_replicateRequest2_put_srcControlChannel(\n        &codec,\n        src_control_channel,\n        strlen(src_control_channel));\n    aeron_archive_client_replicateRequest2_put_liveDestination(\n        &codec,\n        params->live_destination,\n        strlen(params->live_destination));\n    aeron_archive_client_replicateRequest2_put_replicationChannel(\n        &codec,\n        params->replication_channel,\n        strlen(params->replication_channel));\n    aeron_archive_client_replicateRequest2_put_encodedCredentials(\n        &codec,\n        NULL == params->encoded_credentials ? \"\" : params->encoded_credentials->data,\n        NULL == params->encoded_credentials ? 0 : params->encoded_credentials->length);\n    aeron_archive_client_replicateRequest2_put_srcResponseChannel(\n        &codec,\n        params->src_response_channel,\n        strlen(params->src_response_channel));\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_replicateRequest2_encoded_length(&codec));\n}\n\n\nbool aeron_archive_proxy_stop_replication(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t replication_id)\n{\n    struct aeron_archive_client_stopReplicationRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_stopReplicationRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_stopReplicationRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_stopReplicationRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_stopReplicationRequest_set_replicationId(&codec, replication_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_stopReplicationRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_request_replay_token(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_replayTokenRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_replayTokenRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_replayTokenRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_replayTokenRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_replayTokenRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_replayTokenRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_detach_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id,\n    int64_t new_start_position)\n{\n    struct aeron_archive_client_detachSegmentsRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_detachSegmentsRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_detachSegmentsRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_detachSegmentsRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_detachSegmentsRequest_set_recordingId(&codec, recording_id);\n    aeron_archive_client_detachSegmentsRequest_set_newStartPosition(&codec, new_start_position);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_detachSegmentsRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_delete_detached_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_deleteDetachedSegmentsRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_deleteDetachedSegmentsRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_deleteDetachedSegmentsRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_deleteDetachedSegmentsRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_deleteDetachedSegmentsRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_deleteDetachedSegmentsRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_purge_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id,\n    int64_t new_start_position)\n{\n    struct aeron_archive_client_purgeSegmentsRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_purgeSegmentsRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_purgeSegmentsRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_purgeSegmentsRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_purgeSegmentsRequest_set_recordingId(&codec, recording_id);\n    aeron_archive_client_purgeSegmentsRequest_set_newStartPosition(&codec, new_start_position);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_purgeSegmentsRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_attach_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id)\n{\n    struct aeron_archive_client_attachSegmentsRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_attachSegmentsRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_attachSegmentsRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_attachSegmentsRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_attachSegmentsRequest_set_recordingId(&codec, recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_attachSegmentsRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_migrate_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t src_recording_id,\n    int64_t dst_recording_id)\n{\n    struct aeron_archive_client_migrateSegmentsRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_migrateSegmentsRequest_wrap_and_apply_header(\n        &codec,\n        (char *)archive_proxy->buffer,\n        0,\n        AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n        &hdr);\n    aeron_archive_client_migrateSegmentsRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_migrateSegmentsRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_migrateSegmentsRequest_set_srcRecordingId(&codec, src_recording_id);\n    aeron_archive_client_migrateSegmentsRequest_set_dstRecordingId(&codec, dst_recording_id);\n\n    return aeron_archive_proxy_offer(\n        archive_proxy,\n        aeron_archive_client_migrateSegmentsRequest_encoded_length(&codec));\n}\n\nbool aeron_archive_proxy_update_channel(\n        aeron_archive_proxy_t *archive_proxy,\n        int64_t correlation_id,\n        int64_t recording_id,\n        const char *new_channel)\n{\n    struct aeron_archive_client_updateChannelRequest codec;\n    struct aeron_archive_client_messageHeader hdr;\n\n    aeron_archive_client_updateChannelRequest_wrap_and_apply_header(\n            &codec,\n            (char *)archive_proxy->buffer,\n            0,\n            AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH,\n            &hdr);\n    aeron_archive_client_updateChannelRequest_set_controlSessionId(&codec, archive_proxy->control_session_id);\n    aeron_archive_client_updateChannelRequest_set_correlationId(&codec, correlation_id);\n    aeron_archive_client_updateChannelRequest_set_recordingId(&codec, recording_id);\n    aeron_archive_client_updateChannelRequest_put_channel(&codec, new_channel, strlen(new_channel));\n\n    return aeron_archive_proxy_offer(\n            archive_proxy, aeron_archive_client_updateChannelRequest_encoded_length(&codec));\n}\n\n/* ************* */\n\n// The length here must NOT include the messageHeader encoded length\nint64_t aeron_archive_proxy_offer_once(aeron_archive_proxy_t *archive_proxy, size_t length)\n{\n    return aeron_exclusive_publication_offer(\n        archive_proxy->exclusive_publication,\n        archive_proxy->buffer,\n        aeron_archive_client_messageHeader_encoded_length() + length,\n        NULL,\n        NULL);\n}\n\n// The length here must NOT include the messageHeader encoded length\nbool aeron_archive_proxy_offer(\n    aeron_archive_proxy_t *archive_proxy,\n    size_t length)\n{\n    int attempts = archive_proxy->retry_attempts;\n\n    while (true)\n    {\n        int64_t result = aeron_archive_proxy_offer_once(archive_proxy, length);\n\n        if (result > 0)\n        {\n            return true;\n        }\n\n        if (AERON_PUBLICATION_CLOSED == result ||\n            AERON_PUBLICATION_NOT_CONNECTED == result ||\n            AERON_PUBLICATION_MAX_POSITION_EXCEEDED == result)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return false;\n        }\n\n        if (--attempts <= 0)\n        {\n            AERON_SET_ERR(-1, \"%s\", \"too many retries\");\n            return false;\n        }\n\n        aeron_archive_context_idle(archive_proxy->ctx);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_proxy.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_PROXY_H\n#define AERON_ARCHIVE_PROXY_H\n\n#include \"aeron_archive.h\"\n#include \"aeronc.h\"\n#include \"aeron_common.h\"\n\n#define AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH (8 * 1024)\n\ntypedef struct aeron_archive_proxy_stct\n{\n    aeron_archive_context_t *ctx;\n    aeron_exclusive_publication_t *exclusive_publication;\n    int64_t control_session_id;\n    int retry_attempts;\n    uint8_t buffer[AERON_ARCHIVE_PROXY_REQUEST_BUFFER_LENGTH];\n    char client_info[AERON_COUNTER_MAX_CLIENT_NAME_LENGTH * 2];\n}\naeron_archive_proxy_t;\n\nint aeron_archive_proxy_create(\n    aeron_archive_proxy_t **archive_proxy,\n    aeron_archive_context_t *ctx,\n    aeron_exclusive_publication_t *exclusive_publication,\n    int retry_attempts);\n\nint aeron_archive_proxy_init(\n    aeron_archive_proxy_t *archive_proxy,\n    aeron_archive_context_t *ctx,\n    aeron_exclusive_publication_t *exclusive_publication,\n    int retry_attempts);\n\nint aeron_archive_proxy_set_control_session_id(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t control_session_id);\n\nint aeron_archive_proxy_close(aeron_archive_proxy_t *archive_proxy);\n\nint aeron_archive_proxy_delete(aeron_archive_proxy_t *archive_proxy);\n\nbool aeron_archive_proxy_try_connect(\n    aeron_archive_proxy_t *archive_proxy,\n    const char *control_response_channel,\n    int32_t control_response_stream_id,\n    aeron_archive_encoded_credentials_t *encoded_credentials,\n    int64_t correlation_id);\n\nbool aeron_archive_proxy_archive_id(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id);\n\nbool aeron_archive_proxy_challenge_response(\n    aeron_archive_proxy_t *archive_proxy,\n    aeron_archive_encoded_credentials_t *encoded_credentials,\n    int64_t correlation_id);\n\nbool aeron_archive_proxy_close_session(aeron_archive_proxy_t *archive_proxy);\n\nbool aeron_archive_proxy_start_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    const char *recording_channel,\n    int32_t recording_stream_id,\n    bool local_source,\n    bool auto_stop,\n    int64_t correlation_id);\n\nbool aeron_archive_proxy_get_recording_position(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_get_start_position(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_get_stop_position(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_get_max_recorded_position(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_stop_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    const char *channel,\n    int32_t stream_id);\n\nbool aeron_archive_proxy_stop_recording_subscription(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t subscription_id);\n\nbool aeron_archive_proxy_stop_recording_by_identity(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_find_last_matching_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t min_recording_id,\n    const char *channel_fragment,\n    int32_t stream_id,\n    int32_t session_id);\n\nbool aeron_archive_proxy_list_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_list_recordings(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t from_recording_id,\n    int32_t record_count);\n\nbool aeron_archive_proxy_list_recordings_for_uri(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t from_recording_id,\n    int32_t record_count,\n    const char *channel_fragment,\n    int32_t stream_id);\n\nbool aeron_archive_proxy_replay(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id,\n    const char *replay_channel,\n    int32_t replay_stream_id,\n    aeron_archive_replay_params_t *params);\n\nbool aeron_archive_proxy_truncate_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id,\n    int64_t position);\n\nbool aeron_archive_proxy_stop_replay(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t replay_session_id);\n\nbool aeron_archive_proxy_stop_all_replays(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_list_recording_subscriptions(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int32_t pseudo_index,\n    int32_t subscription_count,\n    const char *channel_fragment,\n    int32_t stream_id,\n    bool apply_stream_id);\n\nbool aeron_archive_proxy_purge_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_extend_recording(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t recording_id,\n    const char *recording_channel,\n    int32_t recording_stream_id,\n    bool local_source,\n    bool auto_stop,\n    int64_t correlation_id);\n\nbool aeron_archive_proxy_replicate(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t src_recording_id,\n    int32_t src_control_stream_id,\n    const char *src_control_channel,\n    aeron_archive_replication_params_t *params);\n\nbool aeron_archive_proxy_stop_replication(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t replication_id);\n\nbool aeron_archive_request_replay_token(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_detach_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id,\n    int64_t new_start_position);\n\nbool aeron_archive_proxy_delete_detached_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_purge_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id,\n    int64_t new_start_position);\n\nbool aeron_archive_proxy_attach_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id);\n\nbool aeron_archive_proxy_migrate_segments(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t src_recording_id,\n    int64_t dst_recording_id);\n\nbool aeron_archive_proxy_update_channel(\n    aeron_archive_proxy_t *archive_proxy,\n    int64_t correlation_id,\n    int64_t recording_id,\n    const char *new_channel);\n\n#endif //AERON_ARCHIVE_PROXY_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_recording_descriptor_poller.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <inttypes.h>\n\n#include \"aeron_archive.h\"\n#include \"aeron_archive_context.h\"\n#include \"aeron_archive_recording_descriptor_poller.h\"\n#include \"aeron_archive_recording_signal.h\"\n\n#include \"c/aeron_archive_client/messageHeader.h\"\n#include \"c/aeron_archive_client/controlResponse.h\"\n#include \"c/aeron_archive_client/recordingDescriptor.h\"\n#include \"c/aeron_archive_client/recordingSignalEvent.h\"\n\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n\naeron_controlled_fragment_handler_action_t aeron_archive_recording_descriptor_poller_on_fragment(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\n\n/* *************** */\n\nint aeron_archive_recording_descriptor_poller_create(\n    aeron_archive_recording_descriptor_poller_t **poller,\n    aeron_archive_context_t *ctx,\n    aeron_subscription_t *subscription,\n    int64_t control_session_id,\n    int fragment_limit)\n{\n    aeron_archive_recording_descriptor_poller_t *_poller = NULL;\n\n    if (aeron_alloc((void **)&_poller, sizeof(aeron_archive_recording_descriptor_poller_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_archive_recording_descriptor_poller_t\");\n        return -1;\n    }\n\n    _poller->ctx = ctx;\n    _poller->subscription = subscription;\n    _poller->control_session_id = control_session_id;\n\n    _poller->fragment_limit = fragment_limit;\n\n    if (aeron_controlled_fragment_assembler_create(\n        &_poller->fragment_assembler,\n        aeron_archive_recording_descriptor_poller_on_fragment,\n        _poller) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"aeron_fragment_assembler_create\\n\");\n        return -1;\n    }\n\n    _poller->error_on_fragment = false;\n    _poller->is_dispatch_complete = false;\n\n    *poller = _poller;\n\n    return 0;\n}\n\nint aeron_archive_recording_descriptor_poller_close(aeron_archive_recording_descriptor_poller_t *poller)\n{\n    aeron_controlled_fragment_assembler_delete(poller->fragment_assembler);\n    poller->fragment_assembler = NULL;\n\n    aeron_free(poller);\n\n    return 0;\n}\n\nvoid aeron_archive_recording_descriptor_poller_reset(\n    aeron_archive_recording_descriptor_poller_t *poller,\n    int64_t correlation_id,\n    int32_t record_count,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd)\n{\n    poller->error_on_fragment = false;\n\n    poller->correlation_id = correlation_id;\n    poller->remaining_record_count = record_count;\n    poller->recording_descriptor_consumer = recording_descriptor_consumer;\n    poller->recording_descriptor_consumer_clientd = recording_descriptor_consumer_clientd;\n}\n\nint aeron_archive_recording_descriptor_poller_poll(aeron_archive_recording_descriptor_poller_t *poller)\n{\n    if (poller->is_dispatch_complete)\n    {\n        poller->is_dispatch_complete = false;\n    }\n\n    int rc = aeron_subscription_controlled_poll(\n        poller->subscription,\n        aeron_controlled_fragment_assembler_handler,\n        poller->fragment_assembler,\n        poller->fragment_limit);\n\n    if (rc < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n    }\n    else if (poller->error_on_fragment)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n\n    return rc;\n}\n\n/* ************* */\n\naeron_controlled_fragment_handler_action_t aeron_archive_recording_descriptor_poller_on_fragment(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_archive_recording_descriptor_poller_t *poller = (aeron_archive_recording_descriptor_poller_t *)clientd;\n\n    if (poller->is_dispatch_complete)\n    {\n        return AERON_ACTION_ABORT;\n    }\n\n    struct aeron_archive_client_messageHeader hdr;\n\n    if (aeron_archive_client_messageHeader_wrap(\n        &hdr,\n        (char *)buffer,\n        0,\n        aeron_archive_client_messageHeader_sbe_schema_version(),\n        length) == NULL)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"unable to wrap buffer\");\n        poller->error_on_fragment = true;\n        return AERON_ACTION_BREAK;\n    }\n\n    uint16_t schema_id = aeron_archive_client_messageHeader_schemaId(&hdr);\n\n    if (schema_id != aeron_archive_client_messageHeader_sbe_schema_id())\n    {\n        AERON_SET_ERR(-1, \"found schema id: %i that doesn't match expected id: %i\", schema_id, aeron_archive_client_messageHeader_sbe_schema_id());\n        poller->error_on_fragment = true;\n        return AERON_ACTION_BREAK;\n    }\n\n    uint16_t template_id = aeron_archive_client_messageHeader_templateId(&hdr);\n\n    switch(template_id)\n    {\n        case AERON_ARCHIVE_CLIENT_CONTROL_RESPONSE_SBE_TEMPLATE_ID:\n        {\n            struct aeron_archive_client_controlResponse control_response;\n\n            aeron_archive_client_controlResponse_wrap_for_decode(\n                &control_response,\n                (char *)buffer,\n                aeron_archive_client_messageHeader_encoded_length(),\n                aeron_archive_client_controlResponse_sbe_block_length(),\n                aeron_archive_client_controlResponse_sbe_schema_version(),\n                length);\n\n            if (aeron_archive_client_controlResponse_controlSessionId(&control_response) == poller->control_session_id)\n            {\n                int code;\n\n                if (!aeron_archive_client_controlResponse_code(\n                    &control_response,\n                    (enum aeron_archive_client_controlResponseCode *)&code))\n                {\n                    AERON_SET_ERR(-1, \"%s\", \"unable to read control response code\");\n                    poller->error_on_fragment = true;\n                    return AERON_ACTION_BREAK;\n                }\n\n                int64_t correlation_id = aeron_archive_client_controlResponse_correlationId(&control_response);\n\n                if (aeron_archive_client_controlResponseCode_RECORDING_UNKNOWN == code &&\n                    correlation_id == poller->correlation_id)\n                {\n                    poller->is_dispatch_complete = true;\n\n                    return AERON_ACTION_BREAK;\n                }\n\n                if (aeron_archive_client_controlResponseCode_ERROR == code)\n                {\n                    if (correlation_id == poller->correlation_id)\n                    {\n                        struct aeron_archive_client_controlResponse_string_view string_view =\n                            aeron_archive_client_controlResponse_get_errorMessage_as_string_view(&control_response);\n\n                        AERON_SET_ERR(\n                            (int32_t)aeron_archive_client_controlResponse_relevantId(&control_response),\n                            \"correlation_id=%\" PRIi64 \" %.*s\",\n                            correlation_id,\n                            string_view.length,\n                            string_view.data);\n                        poller->error_on_fragment = true;\n                        return AERON_ACTION_BREAK;\n                    }\n                    else if (NULL != poller->ctx->error_handler)\n                    {\n                        struct aeron_archive_client_controlResponse_string_view string_view =\n                            aeron_archive_client_controlResponse_get_errorMessage_as_string_view(&control_response);\n\n                        aeron_archive_context_invoke_error_handler(\n                            poller->ctx,\n                            poller->correlation_id,\n                            (int32_t)aeron_archive_client_controlResponse_relevantId(&control_response),\n                            string_view.data);\n                    }\n                }\n            }\n\n            break;\n        }\n\n        case AERON_ARCHIVE_CLIENT_RECORDING_DESCRIPTOR_SBE_TEMPLATE_ID:\n        {\n            struct aeron_archive_client_recordingDescriptor recording_descriptor;\n\n            aeron_archive_client_recordingDescriptor_wrap_for_decode(\n                &recording_descriptor,\n                (char *)buffer,\n                aeron_archive_client_messageHeader_encoded_length(),\n                aeron_archive_client_recordingDescriptor_sbe_block_length(),\n                aeron_archive_client_recordingDescriptor_sbe_schema_version(),\n                length);\n\n            if (aeron_archive_client_recordingDescriptor_controlSessionId(&recording_descriptor) == poller->control_session_id &&\n                aeron_archive_client_recordingDescriptor_correlationId(&recording_descriptor)== poller->correlation_id)\n            {\n                struct aeron_archive_client_recordingDescriptor_string_view view;\n\n                aeron_archive_recording_descriptor_t descriptor;\n\n                view = aeron_archive_client_recordingDescriptor_get_strippedChannel_as_string_view(&recording_descriptor);\n                descriptor.stripped_channel_length = view.length;\n                if (aeron_alloc((void **)&descriptor.stripped_channel, descriptor.stripped_channel_length + 1) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"\");\n                    poller->error_on_fragment = true;\n                    return AERON_ACTION_BREAK;\n                }\n                memcpy(descriptor.stripped_channel, view.data, descriptor.stripped_channel_length);\n                descriptor.stripped_channel[descriptor.stripped_channel_length] = '\\0';\n\n                view = aeron_archive_client_recordingDescriptor_get_originalChannel_as_string_view(&recording_descriptor);\n                descriptor.original_channel_length = view.length;\n                if (aeron_alloc((void **)&descriptor.original_channel, descriptor.original_channel_length + 1) < 0)\n                {\n                    aeron_free(descriptor.stripped_channel);\n                    AERON_APPEND_ERR(\"%s\", \"\");\n                    poller->error_on_fragment = true;\n                    return AERON_ACTION_BREAK;\n                }\n                memcpy(descriptor.original_channel, view.data, descriptor.original_channel_length);\n                descriptor.original_channel[descriptor.original_channel_length] = '\\0';\n\n                view = aeron_archive_client_recordingDescriptor_get_sourceIdentity_as_string_view(&recording_descriptor);\n                descriptor.source_identity_length = view.length;\n                if (aeron_alloc((void **)&descriptor.source_identity, descriptor.source_identity_length + 1) < 0)\n                {\n                    aeron_free(descriptor.stripped_channel);\n                    aeron_free(descriptor.original_channel);\n                    AERON_APPEND_ERR(\"%s\", \"\");\n                    poller->error_on_fragment = true;\n                    return AERON_ACTION_BREAK;\n                }\n                memcpy(descriptor.source_identity, view.data, descriptor.source_identity_length);\n                descriptor.source_identity[descriptor.source_identity_length] = '\\0';\n\n                descriptor.control_session_id = poller->control_session_id;\n                descriptor.correlation_id = poller->correlation_id;\n                descriptor.recording_id = aeron_archive_client_recordingDescriptor_recordingId(&recording_descriptor);\n                descriptor.start_timestamp = aeron_archive_client_recordingDescriptor_startTimestamp(&recording_descriptor);\n                descriptor.stop_timestamp = aeron_archive_client_recordingDescriptor_stopTimestamp(&recording_descriptor);\n                descriptor.start_position = aeron_archive_client_recordingDescriptor_startPosition(&recording_descriptor);\n                descriptor.stop_position = aeron_archive_client_recordingDescriptor_stopPosition(&recording_descriptor);\n                descriptor.initial_term_id = aeron_archive_client_recordingDescriptor_initialTermId(&recording_descriptor);\n                descriptor.segment_file_length = aeron_archive_client_recordingDescriptor_segmentFileLength(&recording_descriptor);\n                descriptor.term_buffer_length = aeron_archive_client_recordingDescriptor_termBufferLength(&recording_descriptor);\n                descriptor.mtu_length = aeron_archive_client_recordingDescriptor_mtuLength(&recording_descriptor);\n                descriptor.session_id = aeron_archive_client_recordingDescriptor_sessionId(&recording_descriptor);\n                descriptor.stream_id = aeron_archive_client_recordingDescriptor_streamId(&recording_descriptor);\n\n                poller->recording_descriptor_consumer(\n                    &descriptor,\n                    poller->recording_descriptor_consumer_clientd);\n\n                aeron_free(descriptor.stripped_channel);\n                aeron_free(descriptor.original_channel);\n                aeron_free(descriptor.source_identity);\n\n                if (0 == --poller->remaining_record_count)\n                {\n                    poller->is_dispatch_complete = true;\n\n                    return AERON_ACTION_BREAK;\n                }\n            }\n\n            break;\n        }\n\n        case AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_EVENT_SBE_TEMPLATE_ID:\n        {\n            if (aeron_archive_recording_signal_dispatch_buffer(poller->ctx, buffer, length) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                poller->error_on_fragment = true;\n                return AERON_ACTION_BREAK;\n            }\n\n            break;\n        }\n\n        default:\n            // do nothing\n            break;\n    }\n\n    return AERON_ACTION_CONTINUE;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_recording_descriptor_poller.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_RECORDING_DESCRIPTOR_POLLER_H\n#define AERON_ARCHIVE_RECORDING_DESCRIPTOR_POLLER_H\n\n#include \"aeron_archive.h\"\n\n#include \"aeronc.h\"\n\n#define AERON_ARCHIVE_RECORDING_DESCRIPTOR_POLLER_FRAGMENT_LIMIT_DEFAULT 10\n\ntypedef struct aeron_archive_recording_descriptor_poller_stct\n{\n    aeron_archive_context_t *ctx;\n    aeron_subscription_t *subscription;\n    int64_t control_session_id;\n\n    int fragment_limit;\n    aeron_controlled_fragment_assembler_t *fragment_assembler;\n    bool error_on_fragment;\n\n    int64_t correlation_id;\n    int32_t remaining_record_count;\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer;\n    void *recording_descriptor_consumer_clientd;\n\n    bool is_dispatch_complete;\n}\naeron_archive_recording_descriptor_poller_t;\n\nint aeron_archive_recording_descriptor_poller_create(\n    aeron_archive_recording_descriptor_poller_t **poller,\n    aeron_archive_context_t *ctx,\n    aeron_subscription_t *subscription,\n    int64_t control_session_id,\n    int fragment_limit);\n\nint aeron_archive_recording_descriptor_poller_close(aeron_archive_recording_descriptor_poller_t *poller);\n\nvoid aeron_archive_recording_descriptor_poller_reset(\n    aeron_archive_recording_descriptor_poller_t *poller,\n    int64_t correlation_id,\n    int32_t record_count,\n    aeron_archive_recording_descriptor_consumer_func_t recording_descriptor_consumer,\n    void *recording_descriptor_consumer_clientd);\n\nint aeron_archive_recording_descriptor_poller_poll(aeron_archive_recording_descriptor_poller_t *poller);\n\n#endif // AERON_ARCHIVE_RECORDING_DESCRIPTOR_POLLER_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_recording_pos.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"util/aeron_error.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n\n#define AERON_ARCHIVE_RECORDING_POSITION_TYPE_ID 100\n\n#pragma pack(push)\n#pragma pack(4)\nstruct aeron_archive_recording_pos_key_defn\n{\n    int64_t recording_id;\n    int32_t session_id;\n    int32_t source_identity_length;\n};\n#pragma pack(pop)\n\ntypedef bool (*key_matcher_func_t)(struct aeron_archive_recording_pos_key_defn *key, void *clientd);\n\nstatic int32_t find_counter_id(aeron_counters_reader_t *counters_reader, key_matcher_func_t matcher, void *clientd)\n{\n    for (int32_t i = 0, size = aeron_counters_reader_max_counter_id(counters_reader); i < size; i++)\n    {\n        int32_t counter_state;\n\n        if (aeron_counters_reader_counter_state(counters_reader, i, &counter_state) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return AERON_NULL_COUNTER_ID;\n        }\n\n        if (AERON_COUNTER_RECORD_ALLOCATED == counter_state)\n        {\n            int32_t type_id;\n\n            if (aeron_counters_reader_counter_type_id(counters_reader, i, &type_id) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return AERON_NULL_COUNTER_ID;\n            }\n\n            if (AERON_ARCHIVE_RECORDING_POSITION_TYPE_ID == type_id)\n            {\n                struct aeron_archive_recording_pos_key_defn *key;\n\n                if (aeron_counters_reader_metadata_key(counters_reader, i, (uint8_t **)&key) < 0)\n                {\n                    AERON_SET_ERR(-1, \"unable to locate metadata key for counter %i\", i);\n                    return AERON_NULL_COUNTER_ID;\n                }\n\n                if (matcher(key, clientd))\n                {\n                    return i;\n                }\n            }\n        }\n        else if (AERON_COUNTER_RECORD_UNUSED == counter_state)\n        {\n            break;\n        }\n    }\n\n    return AERON_NULL_COUNTER_ID;\n}\n\nstatic bool recording_id_matcher(struct aeron_archive_recording_pos_key_defn *key, void *clientd)\n{\n    return key->recording_id == *(int64_t *)clientd;\n}\n\nint32_t aeron_archive_recording_pos_find_counter_id_by_recording_id(aeron_counters_reader_t *counters_reader, int64_t recording_id)\n{\n    return find_counter_id(counters_reader, recording_id_matcher, &recording_id);\n}\n\nstatic bool session_id_matcher(struct aeron_archive_recording_pos_key_defn *key, void *clientd)\n{\n    return key->session_id == *(int32_t *)clientd;\n}\n\nint32_t aeron_archive_recording_pos_find_counter_id_by_session_id(aeron_counters_reader_t *counters_reader, int32_t session_id)\n{\n    return find_counter_id(counters_reader, session_id_matcher, &session_id);\n}\n\nint64_t aeron_archive_recording_pos_get_recording_id(aeron_counters_reader_t *counters_reader, int32_t counter_id)\n{\n    int32_t state, type_id;\n\n    if (aeron_counters_reader_counter_state(counters_reader, counter_id, &state) < 0 ||\n        aeron_counters_reader_counter_type_id(counters_reader, counter_id, &type_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return AERON_NULL_COUNTER_ID;\n    }\n\n    if (AERON_COUNTER_RECORD_ALLOCATED != state ||\n        AERON_ARCHIVE_RECORDING_POSITION_TYPE_ID != type_id)\n    {\n        return AERON_NULL_COUNTER_ID;\n    }\n\n    struct aeron_archive_recording_pos_key_defn *key;\n\n    if (aeron_counters_reader_metadata_key(counters_reader, counter_id, (uint8_t **)&key) < 0)\n    {\n        return AERON_NULL_COUNTER_ID;\n    }\n\n    return key->recording_id;\n}\n\nint aeron_archive_recording_pos_get_source_identity(aeron_counters_reader_t *counters_reader, int32_t counter_id, const char *dst, size_t *len_p)\n{\n    int32_t state, type_id;\n    struct aeron_archive_recording_pos_key_defn *key;\n\n    if (aeron_counters_reader_counter_state(counters_reader, counter_id, &state) < 0 ||\n        aeron_counters_reader_counter_type_id(counters_reader, counter_id, &type_id) < 0 ||\n        aeron_counters_reader_metadata_key(counters_reader, counter_id, (uint8_t **)&key) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    int32_t len = 0;\n\n    if (AERON_COUNTER_RECORD_ALLOCATED == state &&\n        AERON_ARCHIVE_RECORDING_POSITION_TYPE_ID == type_id)\n    {\n        len = (int32_t)*len_p;\n\n        // use the shorter of the two\n        len = key->source_identity_length < len ? key->source_identity_length : len;\n\n        // the source_identity string comes right after the key definition\n        memcpy((void *)dst,((uint8_t *)key + sizeof(struct aeron_archive_recording_pos_key_defn)),len);\n    }\n\n    *len_p = len;\n\n    return 0;\n}\n\nint aeron_archive_recording_pos_is_active(bool *is_active, aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t recording_id)\n{\n    int32_t state, type_id;\n    struct aeron_archive_recording_pos_key_defn *key;\n\n    if (aeron_counters_reader_counter_state(counters_reader, counter_id, &state) < 0 ||\n        aeron_counters_reader_counter_type_id(counters_reader, counter_id, &type_id) < 0 ||\n        aeron_counters_reader_metadata_key(counters_reader, counter_id, (uint8_t **)&key) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    *is_active = AERON_COUNTER_RECORD_ALLOCATED == state &&\n        AERON_ARCHIVE_RECORDING_POSITION_TYPE_ID == type_id &&\n        key->recording_id == recording_id;\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_recording_signal.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_archive_recording_signal.h\"\n#include \"aeron_archive_context.h\"\n\n#include \"c/aeron_archive_client/recordingSignalEvent.h\"\n#include \"util/aeron_error.h\"\n\nint aeron_archive_recording_signal_dispatch_buffer(aeron_archive_context_t *ctx, const uint8_t* buffer, size_t length)\n{\n    if (NULL != ctx->on_recording_signal)\n    {\n        struct aeron_archive_client_recordingSignalEvent recording_signal_event;\n\n        aeron_archive_client_recordingSignalEvent_wrap_for_decode(\n            &recording_signal_event,\n            (char *)buffer,\n            aeron_archive_client_messageHeader_encoded_length(),\n            aeron_archive_client_recordingSignalEvent_sbe_block_length(),\n            aeron_archive_client_recordingSignalEvent_sbe_schema_version(),\n            length);\n\n        aeron_archive_recording_signal_t signal;\n\n        signal.control_session_id = aeron_archive_client_recordingSignalEvent_controlSessionId(&recording_signal_event);\n        signal.recording_id = aeron_archive_client_recordingSignalEvent_recordingId(&recording_signal_event);\n        signal.subscription_id = aeron_archive_client_recordingSignalEvent_subscriptionId(&recording_signal_event);\n        signal.position = aeron_archive_client_recordingSignalEvent_position(&recording_signal_event);\n\n        if (!aeron_archive_client_recordingSignalEvent_signal(\n            &recording_signal_event,\n            (enum aeron_archive_client_recordingSignal *)&signal.recording_signal_code))\n        {\n            AERON_SET_ERR(-1, \"%s\", \"unable to read recording signal code\");\n            return -1;\n        }\n\n        ctx->on_recording_signal(&signal, ctx->on_recording_signal_clientd);\n    }\n\n    return 0;\n}\n\nvoid aeron_archive_recording_signal_dispatch_signal(aeron_archive_context_t *ctx, aeron_archive_recording_signal_t *signal)\n{\n    if (NULL != ctx->on_recording_signal)\n    {\n        ctx->on_recording_signal(signal, ctx->on_recording_signal_clientd);\n    }\n}\n\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_recording_signal.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_RECORDING_SIGNAL_H\n#define AERON_ARCHIVE_RECORDING_SIGNAL_H\n\n#include \"aeron_archive.h\"\n\nint aeron_archive_recording_signal_dispatch_buffer(aeron_archive_context_t *ctx, const uint8_t* buffer, size_t length);\n\nvoid aeron_archive_recording_signal_dispatch_signal(aeron_archive_context_t *ctx, aeron_archive_recording_signal_t *signal);\n\n#endif //AERON_ARCHIVE_RECORDING_SIGNAL_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_recording_subscription_descriptor_poller.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <inttypes.h>\n\n#include \"aeron_archive.h\"\n#include \"aeron_archive_context.h\"\n#include \"aeron_archive_recording_subscription_descriptor_poller.h\"\n#include \"aeron_archive_recording_signal.h\"\n\n#include \"c/aeron_archive_client/messageHeader.h\"\n#include \"c/aeron_archive_client/controlResponse.h\"\n#include \"c/aeron_archive_client/recordingSubscriptionDescriptor.h\"\n#include \"c/aeron_archive_client/recordingSignalEvent.h\"\n\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n\naeron_controlled_fragment_handler_action_t aeron_archive_recording_subscription_descriptor_poller_on_fragment(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\n\n/* *************** */\n\nint aeron_archive_recording_subscription_descriptor_poller_create(\n    aeron_archive_recording_subscription_descriptor_poller_t **poller,\n    aeron_archive_context_t *ctx,\n    aeron_subscription_t *subscription,\n    int64_t control_session_id,\n    int fragment_limit)\n{\n    aeron_archive_recording_subscription_descriptor_poller_t *_poller = NULL;\n\n    if (aeron_alloc((void **)&_poller, sizeof(aeron_archive_recording_subscription_descriptor_poller_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_archive_recording_subscription_descriptor_poller_t\");\n        return -1;\n    }\n\n    _poller->ctx = ctx;\n    _poller->subscription = subscription;\n    _poller->control_session_id = control_session_id;\n\n    _poller->fragment_limit = fragment_limit;\n\n    if (aeron_controlled_fragment_assembler_create(\n        &_poller->fragment_assembler,\n        aeron_archive_recording_subscription_descriptor_poller_on_fragment,\n        _poller) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"aeron_fragment_assembler_create\\n\");\n        return -1;\n    }\n\n    _poller->error_on_fragment = false;\n    _poller->is_dispatch_complete = false;\n\n    *poller = _poller;\n\n    return 0;\n}\n\nint aeron_archive_recording_subscription_descriptor_poller_close(aeron_archive_recording_subscription_descriptor_poller_t *poller)\n{\n    aeron_controlled_fragment_assembler_delete(poller->fragment_assembler);\n    poller->fragment_assembler = NULL;\n\n    aeron_free(poller);\n\n    return 0;\n}\n\nvoid aeron_archive_recording_subscription_descriptor_poller_reset(\n    aeron_archive_recording_subscription_descriptor_poller_t *poller,\n    int64_t correlation_id,\n    int32_t subscription_count,\n    aeron_archive_recording_subscription_descriptor_consumer_func_t recording_subscription_descriptor_consumer,\n    void *recording_subscription_descriptor_consumer_clientd)\n{\n    poller->error_on_fragment = false;\n\n    poller->correlation_id = correlation_id;\n    poller->remaining_subscription_count = subscription_count;\n    poller->recording_subscription_descriptor_consumer = recording_subscription_descriptor_consumer;\n    poller->recording_subscription_descriptor_consumer_clientd = recording_subscription_descriptor_consumer_clientd;\n}\n\nint aeron_archive_recording_subscription_descriptor_poller_poll(aeron_archive_recording_subscription_descriptor_poller_t *poller)\n{\n    if (poller->is_dispatch_complete)\n    {\n        poller->is_dispatch_complete = false;\n    }\n\n    int rc = aeron_subscription_controlled_poll(\n        poller->subscription,\n        aeron_controlled_fragment_assembler_handler,\n        poller->fragment_assembler,\n        poller->fragment_limit);\n\n    if (rc < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n    }\n    else if (poller->error_on_fragment)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        rc = -1;\n    }\n\n    return rc;\n}\n\n/* ************* */\n\naeron_controlled_fragment_handler_action_t aeron_archive_recording_subscription_descriptor_poller_on_fragment(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_archive_recording_subscription_descriptor_poller_t *poller = (aeron_archive_recording_subscription_descriptor_poller_t *)clientd;\n\n    if (poller->is_dispatch_complete)\n    {\n        return AERON_ACTION_ABORT;\n    }\n\n    struct aeron_archive_client_messageHeader hdr;\n\n    if (aeron_archive_client_messageHeader_wrap(\n        &hdr,\n        (char *)buffer,\n        0,\n        aeron_archive_client_messageHeader_sbe_schema_version(),\n        length) == NULL)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"unable to wrap buffer\");\n        poller->error_on_fragment = true;\n        return AERON_ACTION_BREAK;\n    }\n\n    uint16_t schema_id = aeron_archive_client_messageHeader_schemaId(&hdr);\n\n    if (schema_id != aeron_archive_client_messageHeader_sbe_schema_id())\n    {\n        AERON_SET_ERR(-1, \"found schema id: %i that doesn't match expected id: %i\", schema_id, aeron_archive_client_messageHeader_sbe_schema_id());\n        poller->error_on_fragment = true;\n        return AERON_ACTION_BREAK;\n    }\n\n    uint16_t template_id = aeron_archive_client_messageHeader_templateId(&hdr);\n\n    switch(template_id)\n    {\n        case AERON_ARCHIVE_CLIENT_CONTROL_RESPONSE_SBE_TEMPLATE_ID:\n        {\n            struct aeron_archive_client_controlResponse control_response;\n\n            aeron_archive_client_controlResponse_wrap_for_decode(\n                &control_response,\n                (char *)buffer,\n                aeron_archive_client_messageHeader_encoded_length(),\n                aeron_archive_client_controlResponse_sbe_block_length(),\n                aeron_archive_client_controlResponse_sbe_schema_version(),\n                length);\n\n            if (aeron_archive_client_controlResponse_controlSessionId(&control_response) == poller->control_session_id)\n            {\n                int code;\n\n                if (!aeron_archive_client_controlResponse_code(\n                    &control_response,\n                    (enum aeron_archive_client_controlResponseCode *)&code))\n                {\n                    AERON_SET_ERR(-1, \"%s\", \"unable to read control response code\");\n                    poller->error_on_fragment = true;\n                    return AERON_ACTION_BREAK;\n                }\n\n                int64_t correlation_id = aeron_archive_client_controlResponse_correlationId(&control_response);\n\n                if (aeron_archive_client_controlResponseCode_SUBSCRIPTION_UNKNOWN == code &&\n                    correlation_id == poller->correlation_id)\n                {\n                    poller->is_dispatch_complete = true;\n\n                    return AERON_ACTION_BREAK;\n                }\n\n                if (aeron_archive_client_controlResponseCode_ERROR == code)\n                {\n                    if (correlation_id == poller->correlation_id)\n                    {\n                        struct aeron_archive_client_controlResponse_string_view string_view =\n                            aeron_archive_client_controlResponse_get_errorMessage_as_string_view(&control_response);\n\n                        AERON_SET_ERR(\n                            (int32_t)aeron_archive_client_controlResponse_relevantId(&control_response),\n                            \"correlation_id=%\" PRIi64 \" %.*s\",\n                            correlation_id,\n                            string_view.length,\n                            string_view.data);\n                        poller->error_on_fragment = true;\n                        return AERON_ACTION_BREAK;\n                    }\n                    else if (NULL != poller->ctx->error_handler)\n                    {\n                        struct aeron_archive_client_controlResponse_string_view string_view =\n                            aeron_archive_client_controlResponse_get_errorMessage_as_string_view(&control_response);\n\n                        aeron_archive_context_invoke_error_handler(\n                            poller->ctx,\n                            poller->correlation_id,\n                            (int32_t)aeron_archive_client_controlResponse_relevantId(&control_response),\n                            string_view.data);\n                    }\n                }\n            }\n\n            break;\n        }\n\n        case AERON_ARCHIVE_CLIENT_RECORDING_SUBSCRIPTION_DESCRIPTOR_SBE_TEMPLATE_ID:\n        {\n            struct aeron_archive_client_recordingSubscriptionDescriptor recording_subscription_descriptor;\n\n            aeron_archive_client_recordingSubscriptionDescriptor_wrap_for_decode(\n                &recording_subscription_descriptor,\n                (char *)buffer,\n                aeron_archive_client_messageHeader_encoded_length(),\n                aeron_archive_client_recordingSubscriptionDescriptor_sbe_block_length(),\n                aeron_archive_client_recordingSubscriptionDescriptor_sbe_schema_version(),\n                length);\n\n            if (aeron_archive_client_recordingSubscriptionDescriptor_controlSessionId(&recording_subscription_descriptor) == poller->control_session_id &&\n                aeron_archive_client_recordingSubscriptionDescriptor_correlationId(&recording_subscription_descriptor)== poller->correlation_id)\n            {\n                struct aeron_archive_client_recordingSubscriptionDescriptor_string_view view;\n\n                aeron_archive_recording_subscription_descriptor_t descriptor;\n\n                view = aeron_archive_client_recordingSubscriptionDescriptor_get_strippedChannel_as_string_view(&recording_subscription_descriptor);\n                descriptor.stripped_channel_length = view.length;\n                if (aeron_alloc((void **)&descriptor.stripped_channel, descriptor.stripped_channel_length + 1) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"\");\n                    poller->error_on_fragment = true;\n                    return AERON_ACTION_BREAK;\n                }\n                memcpy(descriptor.stripped_channel, view.data, descriptor.stripped_channel_length);\n                descriptor.stripped_channel[descriptor.stripped_channel_length] = '\\0';\n\n                descriptor.control_session_id = poller->control_session_id;\n                descriptor.correlation_id = poller->correlation_id;\n                descriptor.subscription_id = aeron_archive_client_recordingSubscriptionDescriptor_subscriptionId(&recording_subscription_descriptor);\n                descriptor.stream_id = aeron_archive_client_recordingSubscriptionDescriptor_streamId(&recording_subscription_descriptor);\n\n                poller->recording_subscription_descriptor_consumer(\n                    &descriptor,\n                    poller->recording_subscription_descriptor_consumer_clientd);\n\n                aeron_free(descriptor.stripped_channel);\n\n                if (0 == --poller->remaining_subscription_count)\n                {\n                    poller->is_dispatch_complete = true;\n\n                    return AERON_ACTION_BREAK;\n                }\n            }\n\n            break;\n        }\n\n        case AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_EVENT_SBE_TEMPLATE_ID:\n        {\n            if (aeron_archive_recording_signal_dispatch_buffer(poller->ctx, buffer, length) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                poller->error_on_fragment = true;\n                return AERON_ACTION_BREAK;\n            }\n\n            break;\n        }\n\n        default:\n            // do nothing\n            break;\n    }\n\n    return AERON_ACTION_CONTINUE;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_recording_subscription_descriptor_poller.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_RECORDING_SUBSCRIPTION_DESCRIPTOR_POLLER_H\n#define AERON_ARCHIVE_RECORDING_SUBSCRIPTION_DESCRIPTOR_POLLER_H\n\n#include \"aeron_archive.h\"\n\n#include \"aeronc.h\"\n\n#define AERON_ARCHIVE_RECORDING_SUBSCRIPTION_DESCRIPTOR_POLLER_FRAGMENT_LIMIT_DEFAULT 10\n\ntypedef struct aeron_archive_recording_subscription_descriptor_poller_stct\n{\n    aeron_archive_context_t *ctx;\n    aeron_subscription_t *subscription;\n    int64_t control_session_id;\n\n    int fragment_limit;\n    aeron_controlled_fragment_assembler_t *fragment_assembler;\n    bool error_on_fragment;\n\n    int64_t correlation_id;\n    int32_t remaining_subscription_count;\n    aeron_archive_recording_subscription_descriptor_consumer_func_t recording_subscription_descriptor_consumer;\n    void *recording_subscription_descriptor_consumer_clientd;\n\n    bool is_dispatch_complete;\n}\naeron_archive_recording_subscription_descriptor_poller_t;\n\n\nint aeron_archive_recording_subscription_descriptor_poller_create(\n    aeron_archive_recording_subscription_descriptor_poller_t **poller,\n    aeron_archive_context_t *ctx,\n    aeron_subscription_t *subscription,\n    int64_t control_session_id,\n    int fragment_limit);\n\nint aeron_archive_recording_subscription_descriptor_poller_close(aeron_archive_recording_subscription_descriptor_poller_t *poller);\n\nvoid aeron_archive_recording_subscription_descriptor_poller_reset(\n    aeron_archive_recording_subscription_descriptor_poller_t *poller,\n    int64_t correlation_id,\n    int32_t subscription_count,\n    aeron_archive_recording_subscription_descriptor_consumer_func_t recording_subscription_descriptor_consumer,\n    void *recording_subscription_descriptor_consumer_clientd);\n\nint aeron_archive_recording_subscription_descriptor_poller_poll(aeron_archive_recording_subscription_descriptor_poller_t *poller);\n\n#endif // AERON_ARCHIVE_RECORDING_SUBSCRIPTION_DESCRIPTOR_POLLER_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_replay_merge.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <inttypes.h>\n\n#include \"aeron_archive.h\"\n#include \"aeron_archive_context.h\"\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n#include \"uri/aeron_uri_string_builder.h\"\n#include \"aeron_archive_client.h\"\n#include \"command/aeron_control_protocol.h\"\n\n#define REPLAY_MERGE_LIVE_ADD_MAX_WINDOW (32 * 1024 * 1024)\n#define REPLAY_MERGE_REPLAY_REMOVE_THRESHOLD (0)\n#define REPLAY_MERGE_INITIAL_GET_MAX_RECORDED_POSITION_BACKOFF_MS (8)\n#define REPLAY_MERGE_GET_MAX_RECORDED_POSITION_BACKOFF_MAX_MS (500)\n#define REPLAY_MERGE_ARCHIVE_POLL_INTERVAL_MS (100)\n\ntypedef enum aeron_archive_replay_merge_state_en\n{\n    RESOLVE_REPLAY_PORT = 0,\n    GET_RECORDING_POSITION = 1,\n    REPLAY = 2,\n    CATCHUP = 3,\n    ATTEMPT_LIVE_JOIN = 4,\n    MERGED = 5,\n    FAILED = 6,\n    CLOSED = 7\n}\naeron_archive_replay_merge_state_t;\n\nstruct aeron_archive_replay_merge_stct\n{\n    aeron_subscription_t *subscription;\n    aeron_archive_t *aeron_archive;\n    aeron_t *aeron;\n    aeron_archive_proxy_t *archive_proxy;\n    int64_t control_session_id;\n    aeron_uri_string_builder_t replay_channel_builder;\n    char *replay_destination;\n    char *live_destination;\n    char *replay_endpoint;\n    size_t replay_endpoint_malloced_len;\n    int64_t recording_id;\n    int64_t start_position;\n    long long epoch_clock;\n    int64_t merge_progress_timeout_ms;\n    long long time_of_last_progress_ms;\n    long long time_of_next_get_max_recorded_position_ms;\n    aeron_async_destination_t *async_destination;\n    aeron_archive_replay_merge_state_t state;\n    aeron_image_t *image;\n    int64_t active_correlation_id;\n    int64_t next_target_position;\n    long long get_max_recorded_position_backoff_ms;\n    long long time_of_last_scheduled_archive_poll_ms;\n    bool is_replay_active;\n    int64_t replay_session_id;\n    int64_t position_of_last_progress;\n    bool is_live_added;\n};\n\nstatic int aeron_archive_replay_merge_resolve_replay_port(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms);\nstatic int aeron_archive_replay_merge_get_recording_position(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms);\nstatic int aeron_archive_replay_merge_replay(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms);\nstatic int aeron_archive_replay_merge_catchup(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms);\nstatic int aeron_archive_replay_merge_attempt_live_join(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms);\n\nstatic void aeron_archive_replay_merge_set_state(aeron_archive_replay_merge_t *replay_merge, aeron_archive_replay_merge_state_t new_state);\nstatic int aeron_archive_replay_merge_handle_async_destination(aeron_archive_replay_merge_t *replay_merge);\n\nstatic bool aeron_archive_replay_merge_call_get_max_recorded_position(aeron_archive_replay_merge_t *replay_merge, long long now_ms);\nstatic int aeron_archive_replay_merge_poll_for_response(bool *found_response_p, aeron_archive_replay_merge_t *replay_merge);\nstatic bool aeron_archive_replay_merge_should_add_live_destination(aeron_archive_replay_merge_t *replay_merge, int64_t position);\nstatic bool aeron_archive_replay_merge_should_stop_and_remove_replay(aeron_archive_replay_merge_t *replay_merge, int64_t position);\n\nstatic void aeron_archive_replay_merge_stop_replay(aeron_archive_replay_merge_t *replay_merge);\n\n/* ************* */\n\nint aeron_archive_replay_merge_init(\n    aeron_archive_replay_merge_t **replay_merge,\n    aeron_subscription_t *subscription,\n    aeron_archive_t *aeron_archive,\n    const char *replay_channel,\n    const char *replay_destination,\n    const char *live_destination,\n    int64_t recording_id,\n    int64_t start_position,\n    long long epoch_clock,\n    int64_t merge_progress_timeout_ms)\n{\n    {\n        aeron_subscription_constants_t constants;\n        aeron_subscription_constants(subscription, &constants);\n\n        if (strncmp(constants.channel, AERON_IPC_CHANNEL, AERON_IPC_CHANNEL_LEN) == 0 ||\n            strncmp(replay_channel, AERON_IPC_CHANNEL, AERON_IPC_CHANNEL_LEN) == 0 ||\n            strncmp(replay_destination, AERON_IPC_CHANNEL, AERON_IPC_CHANNEL_LEN) == 0 ||\n            strncmp(live_destination, AERON_IPC_CHANNEL, AERON_IPC_CHANNEL_LEN) == 0)\n        {\n            AERON_SET_ERR(EINVAL, \"%s\", \"IPC merging is not supported\");\n            return -1;\n        }\n\n        if (NULL == strstr(constants.channel, \"control-mode=manual\"))\n        {\n            AERON_SET_ERR(EINVAL, \"Subscription URI must have 'control-mode=manual' uri=%s\", constants.channel);\n            return -1;\n        }\n    }\n\n    aeron_archive_replay_merge_t *_replay_merge;\n\n    if (aeron_alloc((void **)&_replay_merge, sizeof(aeron_archive_replay_merge_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_archive_replay_merge_t\");\n        return -1;\n    }\n\n    _replay_merge->subscription = subscription;\n    _replay_merge->aeron_archive = aeron_archive;\n    _replay_merge->aeron = aeron_archive_context_get_aeron(aeron_archive_get_archive_context(aeron_archive));\n    _replay_merge->archive_proxy = aeron_archive_proxy(aeron_archive);\n    _replay_merge->control_session_id = aeron_archive_control_session_id(aeron_archive);\n\n    aeron_uri_string_builder_init_on_string(&_replay_merge->replay_channel_builder, replay_channel);\n    aeron_uri_string_builder_put(&_replay_merge->replay_channel_builder, AERON_URI_LINGER_TIMEOUT_KEY, \"0\");\n    aeron_uri_string_builder_put(&_replay_merge->replay_channel_builder, AERON_URI_EOS_KEY, \"false\");\n\n    size_t replay_endpoint_len;\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_on_string(&builder, replay_destination);\n\n        const char *replay_endpoint = aeron_uri_string_builder_get(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY);\n        replay_endpoint_len = strlen(replay_endpoint);\n        _replay_merge->replay_endpoint_malloced_len = replay_endpoint_len + 25; // +25 for terminator and possible resolved ip/port\n\n        aeron_alloc((void **)&_replay_merge->replay_endpoint, _replay_merge->replay_endpoint_malloced_len);\n\n        snprintf(\n            _replay_merge->replay_endpoint,\n            _replay_merge->replay_endpoint_malloced_len,\n            \"%s\",\n            replay_endpoint);\n\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    {\n        if (strncmp(\":0\", &_replay_merge->replay_endpoint[replay_endpoint_len - 2], 2) == 0)\n        {\n            _replay_merge->state = RESOLVE_REPLAY_PORT;\n        }\n        else\n        {\n            aeron_uri_string_builder_put(\n                &_replay_merge->replay_channel_builder,\n                AERON_UDP_CHANNEL_ENDPOINT_KEY,\n                _replay_merge->replay_endpoint);\n            _replay_merge->state = GET_RECORDING_POSITION;\n        }\n    }\n\n    size_t replay_destination_len = strlen(replay_destination);\n    aeron_alloc((void **)&_replay_merge->replay_destination, replay_destination_len + 1);\n    memcpy(_replay_merge->replay_destination, replay_destination, replay_destination_len);\n    _replay_merge->replay_destination[replay_destination_len] = '\\0';\n\n    size_t live_destination_len = strlen(live_destination);\n    aeron_alloc((void **)&_replay_merge->live_destination, live_destination_len + 1);\n    memcpy(_replay_merge->live_destination, live_destination, live_destination_len);\n    _replay_merge->live_destination[live_destination_len] = '\\0';\n\n    _replay_merge->recording_id = recording_id;\n    _replay_merge->start_position = start_position;\n    _replay_merge->epoch_clock = epoch_clock;\n    _replay_merge->merge_progress_timeout_ms = merge_progress_timeout_ms;\n\n    if (aeron_subscription_async_add_destination(\n        &_replay_merge->async_destination,\n        _replay_merge->aeron,\n        _replay_merge->subscription,\n        _replay_merge->replay_destination) < 0)\n    {\n        aeron_uri_string_builder_close(&_replay_merge->replay_channel_builder);\n        aeron_free(_replay_merge->replay_destination);\n        aeron_free(_replay_merge->live_destination);\n        aeron_free(_replay_merge->replay_endpoint);\n        aeron_free(_replay_merge);\n\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    _replay_merge->time_of_last_progress_ms = epoch_clock;\n    _replay_merge->time_of_next_get_max_recorded_position_ms = epoch_clock;\n\n    _replay_merge->image = NULL;\n    _replay_merge->active_correlation_id = AERON_NULL_VALUE;\n    _replay_merge->next_target_position = AERON_NULL_VALUE;\n    _replay_merge->get_max_recorded_position_backoff_ms = REPLAY_MERGE_INITIAL_GET_MAX_RECORDED_POSITION_BACKOFF_MS;\n    _replay_merge->time_of_last_scheduled_archive_poll_ms = 0;\n    _replay_merge->is_replay_active = false;\n    _replay_merge->replay_session_id = AERON_NULL_VALUE;\n    _replay_merge->position_of_last_progress = AERON_NULL_VALUE;\n    _replay_merge->is_live_added = false;\n\n    *replay_merge = _replay_merge;\n\n    return 0;\n}\n\nint aeron_archive_replay_merge_close(aeron_archive_replay_merge_t *replay_merge)\n{\n    if (!aeron_is_closed(replay_merge->aeron))\n    {\n        if (aeron_archive_replay_merge_handle_async_destination(replay_merge) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        while (NULL != replay_merge->async_destination)\n        {\n            aeron_archive_idle(replay_merge->aeron_archive);\n\n            aeron_archive_context_invoke_aeron_client(replay_merge->aeron_archive->ctx);\n\n            if (aeron_archive_replay_merge_handle_async_destination(replay_merge) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return -1;\n            }\n        }\n\n        if (MERGED != replay_merge->state)\n        {\n            if (aeron_subscription_async_remove_destination(\n                &replay_merge->async_destination,\n                replay_merge->aeron,\n                replay_merge->subscription,\n                replay_merge->replay_destination) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return -1;\n            }\n\n            if (aeron_archive_replay_merge_handle_async_destination(replay_merge) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return -1;\n            }\n\n            while (NULL != replay_merge->async_destination)\n            {\n                aeron_archive_idle(replay_merge->aeron_archive);\n\n                aeron_archive_context_invoke_aeron_client(replay_merge->aeron_archive->ctx);\n\n                if (aeron_archive_replay_merge_handle_async_destination(replay_merge) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"\");\n                    return -1;\n                }\n            }\n        }\n\n        if (replay_merge->is_replay_active)\n        {\n            aeron_archive_replay_merge_stop_replay(replay_merge);\n        }\n    }\n\n    aeron_uri_string_builder_close(&replay_merge->replay_channel_builder);\n    aeron_free(replay_merge->replay_destination);\n    aeron_free(replay_merge->live_destination);\n    aeron_free(replay_merge->replay_endpoint);\n    aeron_free(replay_merge);\n\n    return 0;\n}\n\nint aeron_archive_replay_merge_do_work(int *work_count_p, aeron_archive_replay_merge_t *replay_merge)\n{\n    if (aeron_archive_replay_merge_handle_async_destination(replay_merge) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_archive_replay_merge_set_state(replay_merge, FAILED);\n        return -1;\n    }\n\n    int rc = 0;\n    const long long now_ms = aeron_epoch_clock();\n    bool check_progress = true;\n    int work_count = 0;\n\n    switch (replay_merge->state)\n    {\n        case RESOLVE_REPLAY_PORT:\n            rc = aeron_archive_replay_merge_resolve_replay_port(&work_count, replay_merge, now_ms);\n            break;\n\n        case GET_RECORDING_POSITION:\n            rc = aeron_archive_replay_merge_get_recording_position(&work_count, replay_merge, now_ms);\n            break;\n\n        case REPLAY:\n            rc = aeron_archive_replay_merge_replay(&work_count, replay_merge, now_ms);\n            break;\n\n        case CATCHUP:\n            rc = aeron_archive_replay_merge_catchup(&work_count, replay_merge, now_ms);\n            break;\n\n        case ATTEMPT_LIVE_JOIN:\n            rc = aeron_archive_replay_merge_attempt_live_join(&work_count, replay_merge, now_ms);\n            break;\n\n        default:\n            check_progress = false;\n            break;\n    }\n\n    if (NULL != work_count_p)\n    {\n        *work_count_p += work_count;\n    }\n\n    if (-1 == rc)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_archive_replay_merge_set_state(replay_merge, FAILED);\n        return -1;\n    }\n\n    if (check_progress)\n    {\n        if (now_ms > (replay_merge->time_of_last_progress_ms + replay_merge->merge_progress_timeout_ms))\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"replay_merge no progress: state=%i\", replay_merge->state);\n            aeron_archive_replay_merge_set_state(replay_merge, FAILED);\n            return -1;\n        }\n\n        if (AERON_NULL_VALUE == replay_merge->active_correlation_id &&\n            (now_ms > (replay_merge->time_of_last_scheduled_archive_poll_ms + REPLAY_MERGE_ARCHIVE_POLL_INTERVAL_MS)))\n        {\n            replay_merge->time_of_last_scheduled_archive_poll_ms = now_ms;\n\n            bool ignored;\n            if (aeron_archive_replay_merge_poll_for_response(&ignored, replay_merge) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                aeron_archive_replay_merge_set_state(replay_merge, FAILED);\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nint aeron_archive_replay_merge_poll(\n    aeron_archive_replay_merge_t *replay_merge,\n    aeron_fragment_handler_t handler,\n    void *clientd,\n    int fragment_limit)\n{\n    if (aeron_archive_replay_merge_do_work(NULL, replay_merge) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return NULL == replay_merge->image ? 0 : aeron_image_poll(replay_merge->image, handler, clientd, fragment_limit);\n}\n\naeron_image_t *aeron_archive_replay_merge_image(aeron_archive_replay_merge_t *replay_merge)\n{\n    return replay_merge->image;\n\n}\n\nbool aeron_archive_replay_merge_is_merged(aeron_archive_replay_merge_t *replay_merge)\n{\n    return MERGED == replay_merge->state;\n}\n\nbool aeron_archive_replay_merge_has_failed(aeron_archive_replay_merge_t *replay_merge)\n{\n    return FAILED == replay_merge->state;\n}\n\nbool aeron_archive_replay_merge_is_live_added(aeron_archive_replay_merge_t *replay_merge)\n{\n    return replay_merge->is_live_added;\n}\n\n/* ************* */\n\nstatic int aeron_archive_replay_merge_resolve_replay_port(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms)\n{\n    char resolved_endpoint[AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN] = { 0 };\n    int rc = aeron_subscription_resolved_endpoint(replay_merge->subscription, resolved_endpoint, AERON_URI_MAX_LENGTH);\n\n    if (rc < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (rc == 0) // not found\n    {\n        *work_count_p = 0;\n        return 0;\n    }\n\n    if (strlen(resolved_endpoint) != 0)\n    {\n        char *p = strrchr(resolved_endpoint, ':');\n\n        if (NULL == p)\n        {\n            AERON_SET_ERR(-1, \"malformed endpoint missing semicolon: resolved_endpoint=%s\", resolved_endpoint);\n            return -1;\n        }\n\n        size_t dest_idx = strlen(replay_merge->replay_endpoint) - 2;\n        char *dest = &replay_merge->replay_endpoint[dest_idx];\n\n        if (dest_idx + strlen(p) >= replay_merge->replay_endpoint_malloced_len)\n        {\n            AERON_SET_ERR(\n                -1,\n                \"resolved endpoint is too long: replay_endpoint=%s resolved_endpoint=%s\",\n                replay_merge->replay_endpoint,\n                resolved_endpoint);\n            return -1;\n        }\n\n        snprintf(dest, replay_merge->replay_endpoint_malloced_len - dest_idx, \"%s\", p);\n\n        aeron_uri_string_builder_put(\n            &replay_merge->replay_channel_builder,\n            AERON_UDP_CHANNEL_ENDPOINT_KEY,\n            replay_merge->replay_endpoint);\n\n        replay_merge->time_of_last_progress_ms = now_ms;\n\n        aeron_archive_replay_merge_set_state(replay_merge, GET_RECORDING_POSITION);\n\n        *work_count_p = 1;\n    }\n    else\n    {\n        *work_count_p = 0;\n    }\n\n    return 0;\n}\n\nstatic int aeron_archive_replay_merge_get_recording_position(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms)\n{\n    int work_count = 0;\n\n    if (AERON_NULL_VALUE == replay_merge->active_correlation_id)\n    {\n        if (aeron_archive_replay_merge_call_get_max_recorded_position(replay_merge, now_ms))\n        {\n            replay_merge->time_of_last_progress_ms = now_ms;\n            work_count += 1;\n        }\n    }\n    else\n    {\n        bool found_response;\n\n        if (aeron_archive_replay_merge_poll_for_response(&found_response, replay_merge) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        if (found_response)\n        {\n            replay_merge->next_target_position = aeron_archive_control_response_poller(replay_merge->aeron_archive)->relevant_id;\n            replay_merge->active_correlation_id = AERON_NULL_VALUE;\n\n            if (AERON_NULL_VALUE != replay_merge->next_target_position)\n            {\n                replay_merge->time_of_last_progress_ms = now_ms;\n                aeron_archive_replay_merge_set_state(replay_merge, REPLAY);\n            }\n\n            work_count += 1;\n        }\n    }\n\n    *work_count_p = work_count;\n\n    return 0;\n}\n\nstatic int aeron_archive_replay_merge_replay(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms)\n{\n    int work_count = 0;\n\n    if (AERON_NULL_VALUE == replay_merge->active_correlation_id)\n    {\n        char replay_channel[AERON_URI_MAX_LENGTH];\n\n        int64_t correlation_id = aeron_archive_next_correlation_id(replay_merge->aeron_archive);\n\n        aeron_uri_string_builder_sprint(&replay_merge->replay_channel_builder, replay_channel, sizeof(replay_channel));\n\n        aeron_archive_replay_params_t replay_params;\n        aeron_archive_replay_params_init(&replay_params);\n\n        replay_params.position = replay_merge->start_position;\n        replay_params.length = INT64_MAX;\n\n        aeron_subscription_constants_t subscription_constants;\n        aeron_subscription_constants(replay_merge->subscription, &subscription_constants);\n\n        if (aeron_archive_proxy_replay(\n            replay_merge->archive_proxy,\n            correlation_id,\n            replay_merge->recording_id,\n            replay_channel,\n            subscription_constants.stream_id,\n            &replay_params))\n        {\n            replay_merge->time_of_last_progress_ms = now_ms;\n            replay_merge->active_correlation_id = correlation_id;\n            work_count += 1;\n        }\n    }\n    else\n    {\n        bool found_response;\n\n        if (aeron_archive_replay_merge_poll_for_response(&found_response, replay_merge) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        if (found_response)\n        {\n            replay_merge->is_replay_active = true;\n            replay_merge->replay_session_id = aeron_archive_control_response_poller(replay_merge->aeron_archive)->relevant_id;\n            replay_merge->time_of_last_progress_ms = now_ms;\n            replay_merge->active_correlation_id = AERON_NULL_VALUE;\n\n            replay_merge->get_max_recorded_position_backoff_ms = REPLAY_MERGE_INITIAL_GET_MAX_RECORDED_POSITION_BACKOFF_MS;\n            replay_merge->time_of_next_get_max_recorded_position_ms = now_ms;\n\n            aeron_archive_replay_merge_set_state(replay_merge, CATCHUP);\n\n            work_count += 1;\n        }\n    }\n\n    *work_count_p = work_count;\n\n    return 0;\n}\n\nstatic int aeron_archive_replay_merge_catchup(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms)\n{\n    int work_count = 0;\n\n    if (NULL == replay_merge->image && aeron_subscription_is_connected(replay_merge->subscription))\n    {\n        replay_merge->time_of_last_progress_ms = now_ms;\n        replay_merge->image = aeron_subscription_image_by_session_id(replay_merge->subscription, (int32_t)replay_merge->replay_session_id);\n        replay_merge->position_of_last_progress = NULL == replay_merge->image ? AERON_NULL_VALUE : aeron_image_position(replay_merge->image);\n    }\n\n    if (NULL != replay_merge->image)\n    {\n        int64_t position = aeron_image_position(replay_merge->image);\n\n        if (position >= replay_merge->next_target_position)\n        {\n            replay_merge->time_of_last_progress_ms = now_ms;\n            replay_merge->position_of_last_progress = position;\n            replay_merge->active_correlation_id = AERON_NULL_VALUE;\n\n            aeron_archive_replay_merge_set_state(replay_merge, ATTEMPT_LIVE_JOIN);\n\n            work_count += 1;\n        }\n        else if (position > replay_merge->position_of_last_progress)\n        {\n            replay_merge->time_of_last_progress_ms = now_ms;\n            replay_merge->position_of_last_progress = position;\n        }\n        else if (aeron_image_is_closed(replay_merge->image))\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s\", \"replay merge image closed unexpectedly\");\n            return -1;\n        }\n    }\n\n    *work_count_p = work_count;\n\n    return 0;\n}\n\nstatic int aeron_archive_replay_merge_attempt_live_join(int *work_count_p, aeron_archive_replay_merge_t *replay_merge, long long now_ms)\n{\n    /* if we're still waiting around for a previous async destination to be complete, just bail out early */\n    if (NULL != replay_merge->async_destination)\n    {\n        *work_count_p = 0;\n\n        return 0;\n    }\n\n    int work_count = 0;\n\n    if (AERON_NULL_VALUE == replay_merge->active_correlation_id)\n    {\n        if (aeron_archive_replay_merge_call_get_max_recorded_position(replay_merge, now_ms))\n        {\n            replay_merge->time_of_last_progress_ms = now_ms;\n            work_count += 1;\n        }\n    }\n    else\n    {\n        bool found_response;\n\n        if (aeron_archive_replay_merge_poll_for_response(&found_response, replay_merge) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        if (found_response)\n        {\n            replay_merge->next_target_position = aeron_archive_control_response_poller(replay_merge->aeron_archive)->relevant_id;\n            replay_merge->active_correlation_id = AERON_NULL_VALUE;\n\n            if (AERON_NULL_VALUE != replay_merge->next_target_position)\n            {\n                aeron_archive_replay_merge_state_t next_state = CATCHUP;\n\n                if (NULL != replay_merge->image)\n                {\n                    int64_t position = aeron_image_position(replay_merge->image);\n\n                    if (aeron_archive_replay_merge_should_add_live_destination(replay_merge, position))\n                    {\n                        if (aeron_subscription_async_add_destination(\n                            &replay_merge->async_destination,\n                            replay_merge->aeron,\n                            replay_merge->subscription,\n                            replay_merge->live_destination) < 0)\n                        {\n                            AERON_APPEND_ERR(\"%s\", \"\");\n                            return -1;\n                        }\n\n                        replay_merge->time_of_last_progress_ms = now_ms;\n                        replay_merge->position_of_last_progress = position;\n                        replay_merge->is_live_added = true;\n                    }\n                    else if (aeron_archive_replay_merge_should_stop_and_remove_replay(replay_merge, position))\n                    {\n                        if (aeron_subscription_async_remove_destination(\n                            &replay_merge->async_destination,\n                            replay_merge->aeron,\n                            replay_merge->subscription,\n                            replay_merge->replay_destination) < 0)\n                        {\n                            AERON_APPEND_ERR(\"%s\", \"\");\n                            return -1;\n                        }\n\n                        aeron_archive_replay_merge_stop_replay(replay_merge);\n                        replay_merge->time_of_last_progress_ms = now_ms;\n                        replay_merge->position_of_last_progress = position;\n                        next_state = MERGED;\n                    }\n                }\n\n                aeron_archive_replay_merge_set_state(replay_merge, next_state);\n            }\n\n            work_count += 1;\n        }\n    }\n\n    *work_count_p = work_count;\n\n    return 0;\n}\n\nstatic void aeron_archive_replay_merge_set_state(aeron_archive_replay_merge_t *replay_merge, aeron_archive_replay_merge_state_t new_state)\n{\n    replay_merge->state = new_state;\n}\n\nstatic int aeron_archive_replay_merge_handle_async_destination(aeron_archive_replay_merge_t *replay_merge)\n{\n    if (NULL != replay_merge->async_destination)\n    {\n        int rc = aeron_subscription_async_destination_poll(replay_merge->async_destination);\n\n        if (rc == 1)\n        {\n            replay_merge->async_destination = NULL;\n        }\n        else if (rc < 0)\n        {\n            replay_merge->async_destination = NULL;\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nstatic bool aeron_archive_replay_merge_call_get_max_recorded_position(aeron_archive_replay_merge_t *replay_merge, long long now_ms)\n{\n    if (now_ms < replay_merge->time_of_next_get_max_recorded_position_ms)\n    {\n        return false;\n    }\n\n    int64_t correlation_id = aeron_archive_next_correlation_id(replay_merge->aeron_archive);\n\n    bool result = aeron_archive_proxy_get_max_recorded_position(\n        replay_merge->archive_proxy,\n        correlation_id,\n        replay_merge->recording_id);\n\n    if (result)\n    {\n        replay_merge->active_correlation_id = correlation_id;\n    }\n\n    replay_merge->get_max_recorded_position_backoff_ms *= 2;\n    if (replay_merge->get_max_recorded_position_backoff_ms > REPLAY_MERGE_GET_MAX_RECORDED_POSITION_BACKOFF_MAX_MS)\n    {\n        replay_merge->get_max_recorded_position_backoff_ms = REPLAY_MERGE_GET_MAX_RECORDED_POSITION_BACKOFF_MAX_MS;\n    }\n    replay_merge->time_of_next_get_max_recorded_position_ms = now_ms + replay_merge->get_max_recorded_position_backoff_ms;\n\n    return result;\n}\n\nstatic int aeron_archive_replay_merge_poll_for_response(bool *found_response_p, aeron_archive_replay_merge_t *replay_merge)\n{\n    aeron_archive_control_response_poller_t *poller = aeron_archive_control_response_poller(replay_merge->aeron_archive);\n\n    const int poll_count = aeron_archive_control_response_poller_poll(poller);\n\n    if (poll_count < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (poller->is_poll_complete &&\n        poller->control_session_id == replay_merge->control_session_id)\n    {\n        if (poller->is_code_error)\n        {\n            AERON_SET_ERR(\n                (int32_t)poller->relevant_id,\n                \"correlation_id=%\" PRIi64 \" %s\",\n                poller->correlation_id,\n                poller->error_message);\n            return -1;\n        }\n\n        *found_response_p = poller->correlation_id == replay_merge->active_correlation_id;\n    }\n    else\n    {\n        *found_response_p = false;\n\n        if (poll_count == 0 && !aeron_subscription_is_connected(poller->subscription))\n        {\n            AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR, \"%s\", \"archive is not connected\");\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nstatic bool aeron_archive_replay_merge_should_add_live_destination(aeron_archive_replay_merge_t *replay_merge, int64_t position)\n{\n    aeron_image_constants_t image_constants;\n\n    aeron_image_constants(replay_merge->image, &image_constants);\n    size_t window_len = image_constants.term_buffer_length / 4;\n\n    return !replay_merge->is_live_added &&\n        (replay_merge->next_target_position - position) <=\n            (int64_t)(window_len < REPLAY_MERGE_LIVE_ADD_MAX_WINDOW ? window_len : REPLAY_MERGE_LIVE_ADD_MAX_WINDOW);\n}\n\nstatic bool aeron_archive_replay_merge_should_stop_and_remove_replay(aeron_archive_replay_merge_t *replay_merge, int64_t position)\n{\n    return replay_merge->is_live_added &&\n        (replay_merge->next_target_position - position) <= REPLAY_MERGE_REPLAY_REMOVE_THRESHOLD &&\n        aeron_image_active_transport_count(replay_merge->image) >= 2;\n}\n\nstatic void aeron_archive_replay_merge_stop_replay(aeron_archive_replay_merge_t *replay_merge)\n{\n    int64_t correlation_id = aeron_archive_next_correlation_id(replay_merge->aeron_archive);\n\n    if (aeron_archive_proxy_stop_replay(\n        replay_merge->archive_proxy,\n        correlation_id,\n        replay_merge->replay_session_id))\n    {\n        replay_merge->is_replay_active = false;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_replay_params.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_archive_replay_params.h\"\n\nint aeron_archive_replay_params_init(aeron_archive_replay_params_t *params)\n{\n    params->bounding_limit_counter_id = AERON_NULL_VALUE;\n    params->file_io_max_length = AERON_NULL_VALUE;\n    params->position = AERON_NULL_VALUE;\n    params->length = AERON_NULL_VALUE;\n    params->replay_token = AERON_NULL_VALUE;\n    params->subscription_registration_id = AERON_NULL_VALUE;\n\n    return 0;\n}\n\nint aeron_archive_replay_params_copy(aeron_archive_replay_params_t *dst, aeron_archive_replay_params_t *src)\n{\n    dst->bounding_limit_counter_id = src->bounding_limit_counter_id;\n    dst->file_io_max_length = src->file_io_max_length;\n    dst->position = src->position;\n    dst->length = src->length;\n    dst->replay_token = src->replay_token;\n    dst->subscription_registration_id = src->subscription_registration_id;\n\n    return 0;\n}\n\nbool aeron_archive_replay_params_is_bounded(aeron_archive_replay_params_t *params)\n{\n    return AERON_NULL_COUNTER_ID != params->bounding_limit_counter_id;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_replay_params.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARCHIVE_REPLAY_PARAMS_H\n#define AERON_ARCHIVE_REPLAY_PARAMS_H\n\n#include \"aeron_archive.h\"\n\nint aeron_archive_replay_params_copy(aeron_archive_replay_params_t *dst, aeron_archive_replay_params_t *src);\n\nbool aeron_archive_replay_params_is_bounded(aeron_archive_replay_params_t *params);\n\n#endif //AERON_ARCHIVE_REPLAY_PARAMS_H\n"
  },
  {
    "path": "aeron-archive/src/main/c/client/aeron_archive_replication_params.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_archive.h\"\n\nint aeron_archive_replication_params_init(aeron_archive_replication_params_t *params)\n{\n    params->stop_position = AERON_NULL_VALUE;\n    params->dst_recording_id = AERON_NULL_VALUE;\n    params->live_destination = \"\";\n    params->replication_channel = \"\";\n    params->src_response_channel = \"\";\n    params->channel_tag_id = AERON_NULL_VALUE;\n    params->subscription_tag_id = AERON_NULL_VALUE;\n    params->file_io_max_length = AERON_NULL_VALUE;\n    params->replication_session_id = AERON_NULL_VALUE;\n    params->encoded_credentials = NULL;\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-archive/src/main/cpp_wrapper/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nif (MSVC AND \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\")\n    set(BUILD_SHARED_LIBS ON)\nendif ()\n\nSET(HEADERS\n    ${CMAKE_CURRENT_SOURCE_DIR}/client/archive/AeronArchive.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/client/archive/ArchiveContext.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/client/archive/CredentialsSupplier.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/client/archive/RecordingPos.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/client/archive/ReplayMerge.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/client/archive/ReplayParams.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/client/archive/ReplicationParams.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/client/util/ArchiveExceptions.h\n)\n\n# header only library\nadd_library(aeron_archive_wrapper INTERFACE)\nadd_library(aeron::aeron_archive_wrapper ALIAS aeron_archive_wrapper)\ntarget_include_directories(aeron_archive_wrapper\n    INTERFACE\n    \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\"\n    \"$<BUILD_INTERFACE:${AERON_C_ARCHIVE_SOURCE_PATH}>\"\n    \"$<BUILD_INTERFACE:${AERON_CLIENT_WRAPPER_SOURCE_PATH}>\"\n    \"$<INSTALL_INTERFACE:include/wrapper>\"\n)\n\ntarget_sources(aeron_archive_wrapper INTERFACE \"$<BUILD_INTERFACE:${HEADERS}>\")\n\nif (MSVC)\n    string(REPLACE \"/\" \"\\\\\\\\\" NATIVE_PROJECT_SOURCE_DIR \"${PROJECT_SOURCE_DIR}\")\nelse ()\n    set(NATIVE_PROJECT_SOURCE_DIR \"${PROJECT_SOURCE_DIR}\")\nendif ()\n\nif (NOT WIN32)\n    set(CMAKE_THREAD_PREFER_PTHREAD TRUE)\n    set(THREADS_PREFER_PTHREAD_FLAG TRUE)\nendif ()\n\ntarget_link_libraries(aeron_archive_wrapper INTERFACE ${CMAKE_THREAD_LIBS_INIT})\n\nif (AERON_INSTALL_TARGETS)\n    install(\n        TARGETS aeron_archive_wrapper\n        EXPORT aeron-targets)\n    install(DIRECTORY ./ DESTINATION include/wrapper FILES_MATCHING PATTERN \"*.h\")\nendif ()\n"
  },
  {
    "path": "aeron-archive/src/main/cpp_wrapper/client/archive/AeronArchive.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_ARCHIVE_WRAPPER_H\n#define AERON_ARCHIVE_WRAPPER_H\n\n#include \"client/aeron_archive.h\"\n\n#include \"Aeron.h\"\n#include \"client/util/ArchiveExceptions.h\"\n\n#include \"ArchiveContext.h\"\n#include \"ReplayParams.h\"\n#include \"ReplicationParams.h\"\n\nnamespace aeron { namespace archive { namespace client\n{\n\nconstexpr const std::int64_t NULL_POSITION = aeron::NULL_VALUE;\n\nconstexpr const std::int64_t NULL_LENGTH = aeron::NULL_VALUE;\n\nstruct RecordingDescriptor\n{\n    RecordingDescriptor(\n        std::int64_t controlSessionId,\n        std::int64_t correlationId,\n        std::int64_t recordingId,\n        std::int64_t startTimestamp,\n        std::int64_t stopTimestamp,\n        std::int64_t startPosition,\n        std::int64_t stopPosition,\n        std::int32_t initialTermId,\n        std::int32_t segmentFileLength,\n        std::int32_t termBufferLength,\n        std::int32_t mtuLength,\n        std::int32_t sessionId,\n        std::int32_t streamId,\n        std::string strippedChannel,\n        std::string originalChannel,\n        std::string sourceIdentity) :\n        m_controlSessionId(controlSessionId),\n        m_correlationId(correlationId),\n        m_recordingId(recordingId),\n        m_startTimestamp(startTimestamp),\n        m_stopTimestamp(stopTimestamp),\n        m_startPosition(startPosition),\n        m_stopPosition(stopPosition),\n        m_initialTermId(initialTermId),\n        m_segmentFileLength(segmentFileLength),\n        m_termBufferLength(termBufferLength),\n        m_mtuLength(mtuLength),\n        m_sessionId(sessionId),\n        m_streamId(streamId),\n        m_strippedChannel(std::move(strippedChannel)),\n        m_originalChannel(std::move(originalChannel)),\n        m_sourceIdentity(std::move(sourceIdentity))\n    {\n    }\n\n    std::int64_t m_controlSessionId;\n    std::int64_t m_correlationId;\n    std::int64_t m_recordingId;\n    std::int64_t m_startTimestamp;\n    std::int64_t m_stopTimestamp;\n    std::int64_t m_startPosition;\n    std::int64_t m_stopPosition;\n    std::int32_t m_initialTermId;\n    std::int32_t m_segmentFileLength;\n    std::int32_t m_termBufferLength;\n    std::int32_t m_mtuLength;\n    std::int32_t m_sessionId;\n    std::int32_t m_streamId;\n    const std::string m_strippedChannel;\n    const std::string m_originalChannel;\n    const std::string m_sourceIdentity;\n};\n\ntypedef std::function<void(RecordingDescriptor &recordingDescriptor)> recording_descriptor_consumer_t;\n\nstruct RecordingSubscriptionDescriptor\n{\n    RecordingSubscriptionDescriptor(\n        std::int64_t controlSessionId,\n        std::int64_t correlationId,\n        std::int64_t subscriptionId,\n        std::int32_t streamId,\n        std::string strippedChannel) :\n        m_controlSessionId(controlSessionId),\n        m_correlationId(correlationId),\n        m_subscriptionId(subscriptionId),\n        m_streamId(streamId),\n        m_strippedChannel(std::move(strippedChannel))\n    {\n    }\n\n    std::int64_t m_controlSessionId;\n    std::int64_t m_correlationId;\n    std::int64_t m_subscriptionId;\n    std::int32_t m_streamId;\n    const std::string m_strippedChannel;\n};\n\ntypedef std::function<void(RecordingSubscriptionDescriptor &recordingSubscriptionDescriptor)> recording_subscription_descriptor_consumer_t;\n\nusing namespace aeron::util;\n\n/**\n * Client for interacting with a local or remote Aeron Archive.\n */\nclass AeronArchive\n{\n\n    friend class ReplayMerge;\n\npublic:\n    using Context_t = aeron::archive::client::Context;\n\n    /// Location of the source with respect to the archive.\n    enum SourceLocation : int\n    {\n        /// Source is local to the archive and will be recorded using a spy Subscription.\n        LOCAL = 0,\n\n        /// Source is remote to the archive and will be recorded using a network Subscription.\n        REMOTE = 1\n    };\n\n    /**\n     * Allows for the async establishment of an Aeron Archive session.\n     */\n    class AsyncConnect\n    {\n        friend class AeronArchive;\n\n    public:\n\n        /**\n         * Poll for a complete connection.\n         *\n         * @return a new AeronArchive when successfully connected, otherwise returns null\n         *\n         * @see aeron_archive_async_connect_poll\n         */\n        std::shared_ptr<AeronArchive> poll()\n        {\n            if (nullptr == m_async)\n            {\n                throw ArchiveException(\"AsyncConnect already complete\", SOURCEINFO);\n            }\n\n            aeron_archive_t *aeron_archive = nullptr;\n\n            if (aeron_archive_async_connect_poll(&aeron_archive, m_async) < 0)\n            {\n                ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n            }\n\n            if (nullptr == aeron_archive)\n            {\n                return {};\n            }\n\n            m_async = nullptr; // _poll() just free'd this up\n\n            return std::shared_ptr<AeronArchive>(\n                new AeronArchive(\n                    aeron_archive,\n                    m_aeronW,\n                    m_recordingSignalConsumer,\n                    m_errorHandler,\n                    m_delegatingInvoker,\n                    m_maxErrorMessageLength));\n        }\n\n    private:\n        explicit AsyncConnect(\n            Context &ctx) :\n            m_async(nullptr),\n            m_aeronW(ctx.aeron()),\n            m_recordingSignalConsumer(ctx.m_recordingSignalConsumer),\n            m_errorHandler(ctx.m_errorHandler),\n            m_delegatingInvoker(ctx.m_delegatingInvoker),\n            m_maxErrorMessageLength(ctx.m_maxErrorMessageLength)\n        {\n            // async_connect makes a copy of the underlying aeron_archive_context_t\n            if (aeron_archive_async_connect(&m_async, ctx.m_aeron_archive_ctx_t) < 0)\n            {\n                ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n            }\n        }\n\n        aeron_archive_async_connect_t *m_async;\n        std::shared_ptr<Aeron> m_aeronW;\n\n        const recording_signal_consumer_t m_recordingSignalConsumer;\n        const exception_handler_t m_errorHandler;\n        const delegating_invoker_t m_delegatingInvoker;\n\n        const std::uint32_t m_maxErrorMessageLength;\n    };\n\n    /**\n     * Initiate an asynchronous connection attempt to an Aeron Archive.\n     *\n     * @param ctx a reference to an Archive Context with relevant configuration\n     * @return the AsyncConnect object that can be polled for completion\n     *\n     * @see aeron_archive_async_connect\n     */\n    static std::shared_ptr<AsyncConnect> asyncConnect(Context &ctx)\n    {\n        return std::shared_ptr<AsyncConnect>(new AsyncConnect(ctx));\n    }\n\n    /**\n     *\n     * @param ctx a reference to an Archive Context with relevant configuration\n     * @return a new AeronArchive\n     *\n     * @see aeron_archive_connect\n     */\n    static std::shared_ptr<AeronArchive> connect(Context &ctx)\n    {\n        aeron_archive_t *aeron_archive = nullptr;\n\n        if (aeron_archive_connect(&aeron_archive, ctx.m_aeron_archive_ctx_t) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return std::shared_ptr<AeronArchive>(\n            new AeronArchive(\n                aeron_archive,\n                ctx.aeron(),\n                ctx.m_recordingSignalConsumer,\n                ctx.m_errorHandler,\n                ctx.m_delegatingInvoker,\n                ctx.m_maxErrorMessageLength));\n    }\n\n    /**\n     * Close the connection to the Archive.\n     *\n     * @see aeron_archive_close\n     */\n    ~AeronArchive()\n    {\n        // make sure to clean things up in the correct order\n        m_controlResponseSubscription = nullptr;\n\n        aeron_archive_close(m_aeron_archive_t);\n    }\n\n    /**\n     * Get the Context used to connect this archive client.\n     *\n     * @return the Context used to connect this archive client\n     */\n    const Context &context()\n    {\n        return m_archiveCtxW;\n    }\n\n    /**\n     * The id of the archive.\n     *\n     * @return the id of the archive to which this client is connected\n     *\n     * @see aeron_archive_get_archive_id\n     */\n    std::int64_t archiveId()\n    {\n        return aeron_archive_get_archive_id(m_aeron_archive_t);\n    }\n\n    /**\n     * Get a reference to the Subscription used to receive responses from the Archive.\n     *\n     * @return a reference to the Subscription\n     *\n     * @see aeron_archive_get_control_response_subscription\n     */\n    Subscription &controlResponseSubscription()\n    {\n        return *m_controlResponseSubscription;\n    }\n\n    // helpful for testing... not necessarily useful otherwise\n    inline std::int64_t controlSessionId()\n    {\n        return aeron_archive_control_session_id(m_aeron_archive_t);\n    }\n\n    /**\n     * Poll for recording signals, dispatching them to the configured handler.\n     *\n     * @return the number of RecordingSignals dispatched\n     *\n     * @see Context#recordingSignalConsumer\n     * @see aeron_archive_poll_for_recording_signals\n     */\n    inline std::int32_t pollForRecordingSignals()\n    {\n        std::int32_t count;\n\n        if (aeron_archive_poll_for_recording_signals(&count, m_aeron_archive_t) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * Poll the response stream once for an error.\n     *\n     * @return an error string, which will be empty if no error was found\n     *\n     * @see aeron_archive_poll_for_error_response\n     */\n    inline std::string pollForErrorResponse()\n    {\n        char *buffer = m_errorMessageBuffer.get();\n\n        if (aeron_archive_poll_for_error_response(m_aeron_archive_t, buffer, m_archiveCtxW.maxErrorMessageLength()) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return { buffer };\n    }\n\n    /**\n     * Poll the response stream once for an error.\n     * If an error handler is specified, the error (if found) will be dispatched to the handler.\n     * If no error handler is specified, an exception will be thrown.\n     *\n     * @see aeron_archive_check_for_error_response\n     */\n    inline void checkForErrorResponse()\n    {\n        if (aeron_archive_check_for_error_response(m_aeron_archive_t) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Create a publication and set it up to be recorded.\n     *\n     * @param channel the channel for the publication\n     * @param streamId the stream id for the publication\n     * @return a Publication that's being recorded\n     *\n     * @see aeron_archive_add_recorded_publication\n     */\n    inline std::shared_ptr<Publication> addRecordedPublication(const std::string &channel, std::int32_t streamId)\n    {\n        aeron_publication_t *publication;\n\n        if (aeron_archive_add_recorded_publication(\n            &publication,\n            m_aeron_archive_t,\n            channel.c_str(),\n            streamId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return std::make_shared<Publication>(m_archiveCtxW.aeron(), m_archiveCtxW.aeron()->aeron(), publication);\n    }\n\n    /**\n     * Create an exclusive publication and set it up to be recorded.\n     *\n     * @param channel the channel for the exclusive publication\n     * @param streamId the stream id for the exclusive publication\n     * @return an ExclusivePublication that's being recorded\n     *\n     * @see aeron_archive_add_recorded_exclusive_publication\n     */\n    inline std::shared_ptr<ExclusivePublication> addRecordedExclusivePublication(const std::string &channel, std::int32_t streamId)\n    {\n        aeron_exclusive_publication_t *exclusivePublication;\n\n        if (aeron_archive_add_recorded_exclusive_publication(\n            &exclusivePublication,\n            m_aeron_archive_t,\n            channel.c_str(),\n            streamId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return std::make_shared<ExclusivePublication>(m_archiveCtxW.aeron(), m_archiveCtxW.aeron()->aeron(), exclusivePublication);\n    }\n\n    /**\n     * Start recording a channel/stream pairing.\n     * <p>\n     * Channels that include session id parameters are considered different than channels without session ids.\n     * If a publication matches both a session id specific channel recording and a non session id specific recording,\n     * it will be recorded twice.\n     *\n     * @param channel the channel of the publication to be recorded\n     * @param streamId the stream id of the publication to be recorded\n     * @param sourceLocation the SourceLocation of the publication to be recorded\n     * @param autoStop should the recording be automatically stopped when complete\n     * @return the recording's subscription id\n     *\n     * @see aeron_archive_start_recording\n     */\n    inline std::int64_t startRecording(\n        const std::string &channel,\n        std::int32_t streamId,\n        SourceLocation sourceLocation,\n        bool autoStop = false)\n    {\n        std::int64_t subscription_id;\n\n        if (aeron_archive_start_recording(\n            &subscription_id,\n            m_aeron_archive_t,\n            channel.c_str(),\n            streamId,\n            sourceLocation == SourceLocation::LOCAL ?\n                AERON_ARCHIVE_SOURCE_LOCATION_LOCAL :\n                AERON_ARCHIVE_SOURCE_LOCATION_REMOTE,\n            autoStop) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return subscription_id;\n    }\n\n    /**\n     * Fetch the position recorded for the specified recording.\n     *\n     * @param recordingId the archive recording id\n     * @return the position of the specified recording\n     *\n     * @see aeron_archive_get_recording_position\n     */\n    inline std::int64_t getRecordingPosition(std::int64_t recordingId)\n    {\n        std::int64_t recording_position;\n\n        if (aeron_archive_get_recording_position(\n            &recording_position,\n            m_aeron_archive_t,\n            recordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return recording_position;\n    }\n\n    /**\n     * Fetch the start position for the specified recording.\n     *\n     * @param recordingId the archive recording id\n     * @return the start position of the specified recording\n     *\n     * @see aeron_archive_get_start_position\n     */\n    inline std::int64_t getStartPosition(std::int64_t recordingId)\n    {\n        std::int64_t startPosition;\n\n        if (aeron_archive_get_start_position(\n            &startPosition,\n            m_aeron_archive_t,\n            recordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return startPosition;\n    }\n\n    /**\n     * Fetch the stop position for the specified recording.\n     *\n     * @param recordingId the active recording id\n     * @return the stop position of the specified recording\n     *\n     * @see aeron_archive_get_stop_position\n     */\n    inline std::int64_t getStopPosition(std::int64_t recordingId)\n    {\n        std::int64_t stop_position;\n\n        if (aeron_archive_get_stop_position(\n            &stop_position,\n            m_aeron_archive_t,\n            recordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return stop_position;\n    }\n\n    /**\n     * Fetch the stop or active position for the specified recording.\n     *\n     * @param recordingId the active recording id\n     * @return the max recorded position of the specified recording\n     *\n     * @see aeron_archive_get_max_recorded_position\n     */\n    inline std::int64_t getMaxRecordedPosition(std::int64_t recordingId)\n    {\n        std::int64_t max_recorded_position;\n\n        if (aeron_archive_get_max_recorded_position(\n            &max_recorded_position,\n            m_aeron_archive_t,\n            recordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return max_recorded_position;\n    }\n\n    /**\n     * Stop recording the specified subscription id.\n     * This is the subscription id returned from startRecording or extendRecording.\n     *\n     * @param subscriptionId the subscription id for the recording in the Aeron Archive\n     *\n     * @see aeron_archive_stop_recording_subscription\n     */\n    inline void stopRecording(std::int64_t subscriptionId)\n    {\n        if (aeron_archive_stop_recording_subscription(\n            m_aeron_archive_t,\n            subscriptionId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Try to stop a recording for the specified subscription id.\n     * This is the subscription id returned from startRecording or extendRecording.\n     *\n     * @param subscriptionId the subscription id for the recording in the Aeron Archive\n     * @return true if stopped, or false if the subscription is not currently active\n     *\n     * @see aeron_archive_try_stop_recording_subscription\n     */\n    inline bool tryStopRecording(std::int64_t subscriptionId)\n    {\n        bool stopped;\n\n        if (aeron_archive_try_stop_recording_subscription(\n            &stopped,\n            m_aeron_archive_t,\n            subscriptionId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return stopped;\n    }\n\n    /**\n     * Stop recording for the specified channel and stream.\n     * <p>\n     * Channels that include session id parameters are considered different than channels without session ids.\n     * Stopping a recording on a channel without a session id parameter will not stop the recording of any\n     * session id specific recordings that use the same channel and stream id.\n     *\n     * @param channel the channel of the recording to be stopped\n     * @param streamId  the stream id of the recording to be stopped\n     *\n     * @see aeron_archive_stop_recording_channel_and_stream\n     */\n    inline void stopRecording(const std::string &channel, std::int32_t streamId)\n    {\n        if (aeron_archive_stop_recording_channel_and_stream(\n            m_aeron_archive_t,\n            channel.c_str(),\n            streamId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Try to stop recording for the specified channel and stream.\n     * <p>\n     * Channels that include session id parameters are considered different than channels without session ids.\n     * Stopping a recording on a channel without a session id parameter will not stop the recording of any\n     * session id specific recordings that use the same channel and stream id.\n     *\n     * @param channel the channel of the recording to be stopped\n     * @param streamId  the stream id of the recording to be stopped\n     * @return true if stopped, or false if the subscription is not currently active\n     *\n     * @see aeron_archive_try_stop_recording_channel_and_stream\n     */\n    inline bool tryStopRecording(const std::string &channel, std::int32_t streamId)\n    {\n        bool stopped;\n\n        if (aeron_archive_try_stop_recording_channel_and_stream(\n            &stopped,\n            m_aeron_archive_t,\n            channel.c_str(),\n            streamId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return stopped;\n    }\n\n    /**\n     * Stop recording for the specified recording id.\n     *\n     * @param recordingId the id of the recording to be stopped\n     * @return true if stopped, or false if the subscription is not currently active\n     *\n     * @see aeron_archive_try_stop_recording_by_identity\n     */\n    inline bool tryStopRecordingByIdentity(std::int64_t recordingId)\n    {\n        bool stopped;\n\n        if (aeron_archive_try_stop_recording_by_identity(\n            &stopped,\n            m_aeron_archive_t,\n            recordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return stopped;\n    }\n\n    /**\n     * Stop recording a session id specific recording that pertains to the given publication.\n     *\n     * @param publication the Publication to stop recording\n     *\n     * @see aeron_archive_stop_recording_publication\n     */\n    inline void stopRecording(const std::shared_ptr<Publication> &publication)\n    {\n        if (aeron_archive_stop_recording_publication(\n            m_aeron_archive_t,\n            publication->publication()) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Stop recording a session id specific recording that pertains to the given ExclusivePublication.\n     *\n     * @param exclusivePublication the ExclusivePublication to stop recording\n     *\n     * @see aeron_archive_stop_recording_exclusive_publication\n     */\n    inline void stopRecording(const std::shared_ptr<ExclusivePublication> &exclusivePublication)\n    {\n        if (aeron_archive_stop_recording_exclusive_publication(\n            m_aeron_archive_t,\n            exclusivePublication->publication()) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Find the last recording that matches the given criteria.\n     *\n     * @param minRecordingId the lowest recording id to search back to\n     * @param channelFragment for a 'contains' match on the original channel stored with the Aeron Archive\n     * @param streamId the stream id of the recording\n     * @param sessionId the session id of the recording\n     * @return the recording id that matches\n     *\n     * @see aeron_archive_find_last_matching_recording\n     */\n    inline std::int64_t findLastMatchingRecording(\n        std::int64_t minRecordingId,\n        const std::string &channelFragment,\n        std::int32_t streamId,\n        std::int32_t sessionId)\n    {\n        std::int64_t recording_id;\n\n        if (aeron_archive_find_last_matching_recording(\n            &recording_id,\n            m_aeron_archive_t,\n            minRecordingId,\n            channelFragment.c_str(),\n            streamId,\n            sessionId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return recording_id;\n    }\n\n    /**\n     * List a recording descriptor for a single recording id.\n     *\n     * @param recordingId the id of the recording\n     * @param consumer to be called for each descriptor\n     * @return the number of descriptors found\n     *\n     * @see aeron_archive_list_recording\n     */\n    inline std::int32_t listRecording(\n        std::int64_t recordingId,\n        const recording_descriptor_consumer_t &consumer)\n    {\n        std::int32_t count;\n\n        if (aeron_archive_list_recording(\n            &count,\n            m_aeron_archive_t,\n            recordingId,\n            recording_descriptor_consumer_func,\n            const_cast<void *>(reinterpret_cast<const void *>(&consumer))) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * List all recording descriptors starting at a particular recording id,\n     * with a limit of total descriptors delivered.\n     *\n     * @param fromRecordingId the id at which to begin the listing\n     * @param recordCount the limit of total descriptors to deliver\n     * @param consumer to be called for each descriptor\n     * @return the number of descriptors found\n     *\n     * @see aeron_archive_list_recordings\n     */\n    inline std::int32_t listRecordings(\n        std::int64_t fromRecordingId,\n        std::int32_t recordCount,\n        const recording_descriptor_consumer_t &consumer)\n    {\n        std::int32_t count;\n\n        if (aeron_archive_list_recordings(\n            &count,\n            m_aeron_archive_t,\n            fromRecordingId,\n            recordCount,\n            recording_descriptor_consumer_func,\n            const_cast<void *>(reinterpret_cast<const void *>(&consumer))) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * List all recording descriptors for a given channel fragment and stream id, starting at a particular recording id, with a limit of total descriptors delivered.\n     *\n     * @param fromRecordingId the id at which to begin the listing\n     * @param recordCount the limit of total descriptors to deliver\n     * @param channelFragment for a 'contains' match on the original channel stored with the Aeron Archive\n     * @param streamId the stream id of the recording\n     * @param consumer to be called for each descriptor\n     * @return the number of descriptors found\n     *\n     * @see aeron_archive_list_recordings_for_uri\n     */\n    inline std::int32_t listRecordingsForUri(\n        std::int64_t fromRecordingId,\n        std::int32_t recordCount,\n        const std::string &channelFragment,\n        std::int32_t streamId,\n        const recording_descriptor_consumer_t &consumer)\n    {\n        std::int32_t count;\n\n        if (aeron_archive_list_recordings_for_uri(\n            &count,\n            m_aeron_archive_t,\n            fromRecordingId,\n            recordCount,\n            channelFragment.c_str(),\n            streamId,\n            recording_descriptor_consumer_func,\n            const_cast<void *>(reinterpret_cast<const void *>(&consumer))) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * Start a replay\n     * <p>\n     * The lower 32-bits of the replay session id contain the session id of the image of the received replay\n     * and can be obtained by casting the replay session id to an int32_t.\n     * All 64-bits are required to uniquely identify the replay when calling #stopReplay.\n     *\n     * @param recordingId the id of the recording\n     * @param replayChannel the channel to which the replay should be sent\n     * @param replayStreamId the stream id to which the replay should be sent\n     * @param replayParams the ReplayParams that control the behavior of the replay\n     * @return the replay session id\n     *\n     * @see aeron_archive_start_replay\n     */\n    inline std::int64_t startReplay(\n        std::int64_t recordingId,\n        const std::string &replayChannel,\n        std::int32_t replayStreamId,\n        ReplayParams &replayParams)\n    {\n        std::int64_t replay_session_id;\n\n        if (aeron_archive_start_replay(\n            &replay_session_id,\n            m_aeron_archive_t,\n            recordingId,\n            replayChannel.c_str(),\n            replayStreamId,\n            &replayParams.m_params) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return replay_session_id;\n    }\n\n    /**\n     * Start a replay.\n     *\n     * @param recordingId the id of the recording\n     * @param replayChannel the channel to which the replay should be sent\n     * @param replayStreamId the stream id to which the replay should be sent\n     * @param replayParams the ReplayParams that control the behavior of the replay\n     * @return the Subscription created for consuming the replay\n     *\n     * @see aeron_archive_replay\n     */\n    inline std::shared_ptr<Subscription> replay(\n        std::int64_t recordingId,\n        const std::string &replayChannel,\n        std::int32_t replayStreamId,\n        ReplayParams &replayParams)\n    {\n        aeron_subscription_t *subscription;\n\n        if (aeron_archive_replay(\n            &subscription,\n            m_aeron_archive_t,\n            recordingId,\n            replayChannel.c_str(),\n            replayStreamId,\n            &replayParams.m_params) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return std::make_shared<Subscription>(\n            m_archiveCtxW.aeron(), m_archiveCtxW.aeron()->aeron(), subscription, nullptr);\n    }\n\n    /**\n     * Truncate a stopped recording to the specified position.\n     * The position must be less than the stopped position.\n     * The position must be on a fragment boundary.\n     * Truncating a recording to the start position effectively deletes the recording.\n     *\n     * @param recordingId the id of the recording\n     * @param position the position to which the recording will be truncated\n     * @return the number of segments deleted\n     *\n     * @see aeron_archive_truncate_recording\n     */\n    inline std::int64_t truncateRecording(std::int64_t recordingId, std::int64_t position)\n    {\n        std::int64_t count;\n\n        if (aeron_archive_truncate_recording(\n            &count,\n            m_aeron_archive_t,\n            recordingId,\n            position) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * Stop a replay session.\n     *\n     * @param replaySessionId the replay session id indicating the replay to stop\n     *\n     * @see aeron_archive_stop_replay\n     */\n    inline void stopReplay(std::int64_t replaySessionId)\n    {\n        if (aeron_archive_stop_replay(m_aeron_archive_t, replaySessionId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Stop all replays matching a recording id.\n     * If recordingId is aeron::NULL_VALUE then match all replays.\n     *\n     * @param recordingId the id of the recording for which all replays will be stopped\n     *\n     * @see aeron_archive_stop_all_replays\n     */\n    inline void stopAllReplays(std::int64_t recordingId)\n    {\n        if (aeron_archive_stop_all_replays(\n            m_aeron_archive_t,\n            recordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * List active recording subscriptions in the Aeron Archive.\n     * These are the result of calling aeron_archive_start_recording or aeron_archive_extend_recording.\n     * The subscription id in the returned descriptor can be used when calling AeronArchive:stopRecording.\n     *\n     * @param pseudoIndex the index into the active list at which to begin listing\n     * @param subscriptionCount the limit of the total descriptors to deliver\n     * @param channelFragment for a 'contains' match on the original channel stored with the Aeron Archive\n     * @param streamId the stream id of the recording\n     * @param applyStreamId whether or not the stream id should be matched\n     * @param consumer to be called for each recording subscription\n     * @return the number of matched subscriptions\n     *\n     * @see aeron_archive_list_recording_subscriptions\n     */\n    inline std::int32_t listRecordingSubscriptions(\n        std::int32_t pseudoIndex,\n        std::int32_t subscriptionCount,\n        const std::string &channelFragment,\n        std::int32_t streamId,\n        bool applyStreamId,\n        const recording_subscription_descriptor_consumer_t &consumer)\n    {\n        std::int32_t count;\n\n        if (aeron_archive_list_recording_subscriptions(\n            &count,\n            m_aeron_archive_t,\n            pseudoIndex,\n            subscriptionCount,\n            channelFragment.c_str(),\n            streamId,\n            applyStreamId,\n            recording_subscription_descriptor_consumer_func,\n            const_cast<void *>(reinterpret_cast<const void *>(&consumer))) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * Purge a stopped recording.\n     * i.e. Mark the recording as INVALID at the Archive and delete the corresponding segment files.\n     * The space in the Catalog will be reclaimed upon compaction.\n     *\n     * @param recordingId the id of the stopped recording to be purged\n     * @return the number of deleted segments\n     *\n     * @see aeron_archive_purge_recording\n     */\n    inline std::int64_t purgeRecording(std::int64_t recordingId)\n    {\n        std::int64_t deletedSegmentsCount;\n\n        if (aeron_archive_purge_recording(\n            &deletedSegmentsCount,\n            m_aeron_archive_t,\n            recordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return deletedSegmentsCount;\n    }\n\n    /**\n     * Extend an existing, non-active recording for a channel and stream pairing.\n     * <p>\n     * The channel must be configured with the initial position from which it will be extended.\n     * This can be done with ChannelUriStringBuilder#initialPosition.\n     * The details required to initialize can be found by calling #listRecording.\n     *\n     * @param recordingId the id of the existing recording\n     * @param channel the channel of the publication to be recorded\n     * @param streamId the stream id of the publication to be recorded\n     * @param sourceLocation the source location of the publication to be recorded\n     * @param autoStop should the recording be automatically stopped when complete\n     * @return the subscription id of the recording\n     *\n     * @see aeron_archive_extend_recording\n     */\n    inline std::int64_t extendRecording(\n        std::int64_t recordingId,\n        const std::string &channel,\n        std::int32_t streamId,\n        SourceLocation sourceLocation,\n        bool autoStop)\n    {\n        std::int64_t subscription_id;\n\n        if (aeron_archive_extend_recording(\n            &subscription_id,\n            m_aeron_archive_t,\n            recordingId,\n            channel.c_str(),\n            streamId,\n            sourceLocation == SourceLocation::LOCAL ?\n                AERON_ARCHIVE_SOURCE_LOCATION_LOCAL :\n                AERON_ARCHIVE_SOURCE_LOCATION_REMOTE,\n            autoStop) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return subscription_id;\n    }\n\n    /**\n     * Replicate a recording from a source Archive to a destination.\n     * This can be considered a backup for a primary Archive.\n     * The source recording will be replayed via the provided replay channel and use the original stream id.\n     * The behavior of the replication will be governed by the values specified in the ReplicationParams.\n     * <p>\n     * For a source recording that is still active, the replay can merge with the live stream and then follow it directly and no longer require the replay from the source.\n     * This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with #checkForErrorResponse and #pollForErrorResponse\n     *\n     * @param srcRecordingId the recording id that must exist at the source archive\n     * @param srcControlStreamId remote control channel for the source archive on which to instruct the replay\n     * @param srcControlChannel remote control stream id for the source archive on which to instruct the replay\n     * @param replicationParams optional parameters to configure the behavior of the replication\n     * @return the replication id that can be used to stop the replication\n     *\n     * @see aeron_archive_replicate\n     */\n    inline std::int64_t replicate(\n        std::int64_t srcRecordingId,\n        std::int32_t srcControlStreamId,\n        const std::string &srcControlChannel,\n        ReplicationParams &replicationParams)\n    {\n        std::int64_t replicationId;\n\n        if (aeron_archive_replicate(\n            &replicationId,\n            m_aeron_archive_t,\n            srcRecordingId,\n            srcControlChannel.c_str(),\n            srcControlStreamId,\n            &replicationParams.m_params) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return replicationId;\n    }\n\n    /**\n     * Stop a replication by the replication id.\n     *\n     * @param replicationId the replication id retrieved when calling #replicate\n     *\n     * @see aeron_archive_stop_replication\n     */\n    inline void stopReplication(std::int64_t replicationId)\n    {\n        if (aeron_archive_stop_replication(m_aeron_archive_t, replicationId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Try to stop a replication by the replication id.\n     *\n     * @param replicationId the replication id retrieved when calling #replicate\n     * @return true if stopped, or false if the recording is not currently active\n     *\n     * @see aeron_archive_try_stop_replication\n     */\n    inline bool tryStopReplication(std::int64_t replicationId)\n    {\n        bool stopped;\n\n        if (aeron_archive_try_stop_replication(\n            &stopped,\n            m_aeron_archive_t,\n            replicationId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return stopped;\n    }\n\n    /**\n     * Detach segments from the beginning of a recording up to the provided new start position.\n     * <p>\n     * The new start position must be the first byte position of a segment after the existing start position.\n     * <p>\n     * It is not possible to detach segments which are active for recording or being replayed.\n     *\n     * @param recordingId the id of an existing recording\n     * @param newStartPosition the new starting position for the recording after the segments are detached\n     *\n     * @see aeron_archive_detach_segments\n     */\n    inline void detachSegments(std::int64_t recordingId, std::int64_t newStartPosition)\n    {\n        if (aeron_archive_detach_segments(\n            m_aeron_archive_t,\n            recordingId,\n            newStartPosition) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Delete segments which have been previously detached from a recording.\n     *\n     * @param recordingId the id of an existing recording\n     * @return the number of segments deleted\n     *\n     * @see aeron_archive_delete_detached_segments\n     */\n    inline std::int64_t deleteDetachedSegments(std::int64_t recordingId)\n    {\n        std::int64_t count;\n\n        if (aeron_archive_delete_detached_segments(\n            &count,\n            m_aeron_archive_t,\n            recordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * Purge (Detach and delete) segments from the beginning of a recording up to the provided new start position.\n     * <p>\n     * The new start position must be the first byte position of a segment after the existing start position.\n     * <p>\n     * It is not possible to detach segments which are active for recording or being replayed.\n     *\n     * @param recordingId the id of an existing recording\n     * @param newStartPosition the new starting position for the recording after the segments are detached\n     * @return the number of segments deleted\n     *\n     * @see aeron_archive_purge_segments\n     */\n    inline std::int64_t purgeSegments(std::int64_t recordingId, std::int64_t newStartPosition)\n    {\n        std::int64_t count;\n\n        if (aeron_archive_purge_segments(\n            &count,\n            m_aeron_archive_t,\n            recordingId,\n            newStartPosition) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * Attach segments to the beginning of a recording to restore history that was previously detached.\n     * <p>\n     * Segment files must match the existing recording and join exactly to the start position of the recording they are being attached to.\n     *\n     * @param recordingId the id of an existing recording\n     * @return the number of segments attached\n     *\n     * @see aeron_archive_attach_segments\n     */\n    inline std::int64_t attachSegments(std::int64_t recordingId)\n    {\n        std::int64_t count;\n\n        if (aeron_archive_attach_segments(\n            &count,\n            m_aeron_archive_t,\n            recordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * Migrate segments from a source recording and attach them to the beginning of a destination recording.\n     * <p>\n     * The source recording must match the destination recording for segment length, term length, mtu length,\n     * stream id, plus the stop position and term id of the source must join with the start position of the destination\n     * and be on a segment boundary.\n     * <p>\n     * The source recording will be effectively truncated back to its start position after the migration.\n     *\n     * @param srcRecordingId the id of an existing recording from which segments will be migrated\n     * @param dstRecordingId the id of an exisintg recording to which segments will be migrated\n     * @return the number of segments deleted\n     */\n    inline std::int64_t migrateSegments(std::int64_t srcRecordingId, std::int64_t dstRecordingId)\n    {\n        std::int64_t count;\n\n        if (aeron_archive_migrate_segments(\n            &count,\n            m_aeron_archive_t,\n            srcRecordingId,\n            dstRecordingId) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * Update the channel for a recording, i.e. replace original and stripped channel information in the catalog.\n     *\n     * @param recordingId the recording id to update.\n     * @param newChannel  to use in the catalogue.\n     */\n    inline void updateChannel(std::int64_t recordingId, std::string newChannel)\n    {\n        if (aeron_archive_update_channel(\n            m_aeron_archive_t,\n            recordingId,\n            newChannel.c_str()) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Position of the recorded stream at the base of a segment file.\n     * <p>\n     * If a recording starts within a term then the base position can be before the recording started.\n     *\n     * @param startPosition start position of the stream\n     * @param position the position in the stream to calculate the segment base position from\n     * @param termBufferLength term buffer length of the stream\n     * @param segmentFileLength segment file length, which is a multiple of term buffer length\n     * @return the position of the recorded stream at the beginning of a segment file\n     *\n     * @see aeron_archive_segment_file_base_position\n     */\n    static std::int64_t segmentFileBasePosition(\n        std::int64_t startPosition,\n        std::int64_t position,\n        std::int32_t termBufferLength,\n        std::int32_t segmentFileLength)\n    {\n        return aeron_archive_segment_file_base_position(\n            startPosition,\n            position,\n            termBufferLength,\n            segmentFileLength);\n    }\n\nprivate:\n    explicit AeronArchive(\n        aeron_archive_t *aeron_archive,\n        const std::shared_ptr<Aeron> &originalAeron,\n        const recording_signal_consumer_t &recordingSignalConsumer,\n        const exception_handler_t &errorHandler,\n        const delegating_invoker_t &delegatingInvoker,\n        const std::uint32_t maxErrorMessageLength) :\n        m_aeron_archive_t(aeron_archive),\n        m_archiveCtxW(aeron_archive_get_and_own_archive_context(m_aeron_archive_t))\n    {\n        // The following line divorces the aeron_t from the underlying aeron_archive\n        aeron_archive_context_set_owns_aeron_client(m_archiveCtxW.m_aeron_archive_ctx_t, false);\n\n        // Can't get the aeron_t via 'm_archiveCtxW.aeron()->aeron()' because m_archiveCtxW doesn't have an aeron set yet.\n        // So use the C functions to acquire the underlying aeron_t.\n        auto *aeron = aeron_archive_context_get_aeron(aeron_archive_get_archive_context(aeron_archive));\n\n        m_archiveCtxW\n            .aeron(nullptr == originalAeron ? std::make_shared<Aeron>(aeron) : originalAeron)\n            .recordingSignalConsumer(recordingSignalConsumer)\n            .delegatingInvoker(delegatingInvoker)\n            .maxErrorMessageLength(maxErrorMessageLength);\n\n        // If no previous errorHandler was set, then the underlying C code will use the default one, which just prints to stderr.\n        // If there WAS an errorHandler set, then go ahead and copy it into the new archive context wrapper.\n        if (nullptr != errorHandler)\n        {\n            m_archiveCtxW.errorHandler(errorHandler);\n        }\n\n        m_controlResponseSubscription = std::make_unique<Subscription>(\n            m_archiveCtxW.aeron(),\n            aeron,\n            aeron_archive_get_and_own_control_response_subscription(m_aeron_archive_t),\n            nullptr);\n\n        m_errorMessageBuffer = std::make_unique<char[]>(maxErrorMessageLength);\n    }\n\n    aeron_archive_t *m_aeron_archive_t = nullptr;\n    Context m_archiveCtxW;\n    std::unique_ptr<Subscription> m_controlResponseSubscription = nullptr;\n    std::unique_ptr<char[]> m_errorMessageBuffer;\n\n    static void recording_descriptor_consumer_func(\n        aeron_archive_recording_descriptor_t *recording_descriptor,\n        void *clientd)\n    {\n        auto consumer = *reinterpret_cast<recording_descriptor_consumer_t *>(clientd);\n\n        RecordingDescriptor descriptor(\n            recording_descriptor->control_session_id,\n            recording_descriptor->correlation_id,\n            recording_descriptor->recording_id,\n            recording_descriptor->start_timestamp,\n            recording_descriptor->stop_timestamp,\n            recording_descriptor->start_position,\n            recording_descriptor->stop_position,\n            recording_descriptor->initial_term_id,\n            recording_descriptor->segment_file_length,\n            recording_descriptor->term_buffer_length,\n            recording_descriptor->mtu_length,\n            recording_descriptor->session_id,\n            recording_descriptor->stream_id,\n            recording_descriptor->stripped_channel,\n            recording_descriptor->original_channel,\n            recording_descriptor->source_identity);\n\n        consumer(descriptor);\n    }\n\n    static void recording_subscription_descriptor_consumer_func(\n        aeron_archive_recording_subscription_descriptor_t *recording_subscription_descriptor,\n        void *clientd)\n    {\n        auto consumer = *reinterpret_cast<recording_subscription_descriptor_consumer_t *>(clientd);\n\n        RecordingSubscriptionDescriptor descriptor(\n            recording_subscription_descriptor->control_session_id,\n            recording_subscription_descriptor->correlation_id,\n            recording_subscription_descriptor->subscription_id,\n            recording_subscription_descriptor->stream_id,\n            recording_subscription_descriptor->stripped_channel);\n\n        consumer(descriptor);\n    }\n};\n\n}}}\n\n#endif //AERON_ARCHIVE_WRAPPER_H\n"
  },
  {
    "path": "aeron-archive/src/main/cpp_wrapper/client/archive/ArchiveContext.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_ARCHIVE_WRAPPER_CONTEXT_H\n#define AERON_ARCHIVE_WRAPPER_CONTEXT_H\n\n#include \"AeronArchive.h\"\n#include \"CredentialsSupplier.h\"\n#include \"concurrent/YieldingIdleStrategy.h\"\n\n#include \"Context.h\"\n\nnamespace aeron { namespace archive { namespace client\n{\n\nclass RecordingSignal\n{\npublic:\n\n    enum Value\n    {\n        START = INT32_C(0),\n        STOP = INT32_C(1),\n        EXTEND = INT32_C(2),\n        REPLICATE = INT32_C(3),\n        MERGE = INT32_C(4),\n        SYNC = INT32_C(5),\n        ABC_DELETE = INT32_C(6),\n        REPLICATE_END = INT32_C(7),\n        NULL_VALUE = INT32_MIN\n    };\n\n    RecordingSignal(\n        std::int64_t controlSessionId,\n        std::int64_t recordingId,\n        std::int64_t subscriptionId,\n        std::int64_t position,\n        std::int32_t recordingSignalCode) :\n        m_controlSessionId(controlSessionId),\n        m_recordingId(recordingId),\n        m_subscriptionId(subscriptionId),\n        m_position(position),\n        m_recordingSignalCode(recordingSignalCode)\n    {\n    }\n\n    std::int64_t m_controlSessionId;\n    std::int64_t m_recordingId;\n    std::int64_t m_subscriptionId;\n    std::int64_t m_position;\n    std::int32_t m_recordingSignalCode;\n};\n\ntypedef std::function<void(RecordingSignal &recordingSignal)> recording_signal_consumer_t;\n\ntypedef std::function<void()> delegating_invoker_t;\n\nclass Context\n{\n    friend class AeronArchive;\n\npublic:\n    Context()\n    {\n        if (aeron_archive_context_init(&m_aeron_archive_ctx_t) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        setupContext();\n    }\n\n    ~Context()\n    {\n        aeron_archive_context_close(m_aeron_archive_ctx_t);\n        m_aeron_archive_ctx_t = nullptr;\n    }\n\n    inline Context &aeron(std::shared_ptr<Aeron> aeron)\n    {\n        if (aeron_archive_context_set_aeron(m_aeron_archive_ctx_t,aeron->aeron()) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        m_aeronW = std::move(aeron);\n\n        return *this;\n    }\n\n    inline std::shared_ptr<Aeron> aeron() const\n    {\n        return m_aeronW;\n    }\n\n    inline Context &aeronDirectoryName(const std::string &directoryName)\n    {\n        aeron_archive_context_set_aeron_directory_name(m_aeron_archive_ctx_t, directoryName.c_str());\n        return *this;\n    }\n\n    inline std::string aeronDirectoryName() const\n    {\n        return { aeron_archive_context_get_aeron_directory_name(m_aeron_archive_ctx_t) };\n    }\n\n    inline Context &controlRequestChannel(const std::string &channel)\n    {\n        aeron_archive_context_set_control_request_channel(m_aeron_archive_ctx_t, channel.c_str());\n        return *this;\n    }\n\n    inline std::string controlRequestChannel() const\n    {\n        return aeron_archive_context_get_control_request_channel(m_aeron_archive_ctx_t);\n    }\n\n    inline Context &controlRequestStreamId(const std::int32_t streamId)\n    {\n        aeron_archive_context_set_control_request_stream_id(m_aeron_archive_ctx_t, streamId);\n        return *this;\n    }\n\n    inline std::int32_t controlRequestStreamId() const\n    {\n        return aeron_archive_context_get_control_request_stream_id(m_aeron_archive_ctx_t);\n    }\n\n    inline Context &controlResponseChannel(const std::string &channel)\n    {\n        aeron_archive_context_set_control_response_channel(m_aeron_archive_ctx_t, channel.c_str());\n        return *this;\n    }\n\n    inline std::string controlResponseChannel() const\n    {\n        return aeron_archive_context_get_control_response_channel(m_aeron_archive_ctx_t);\n    }\n\n    inline Context &controlResponseStreamId(const std::int32_t streamId)\n    {\n        aeron_archive_context_set_control_response_stream_id(m_aeron_archive_ctx_t, streamId);\n        return *this;\n    }\n\n    inline std::int32_t controlResponseStreamId() const\n    {\n        return aeron_archive_context_get_control_response_stream_id(m_aeron_archive_ctx_t);\n    }\n\n    inline Context &recordingEventsChannel(const std::string &channel)\n    {\n        aeron_archive_context_set_recording_events_channel(m_aeron_archive_ctx_t, channel.c_str());\n        return *this;\n    }\n\n    inline std::string recordingEventsChannel() const\n    {\n        return aeron_archive_context_get_recording_events_channel(m_aeron_archive_ctx_t);\n    }\n\n    inline Context &messageTimeoutNs(const std::uint64_t messageTimeoutNs)\n    {\n        aeron_archive_context_set_message_timeout_ns(m_aeron_archive_ctx_t, messageTimeoutNs);\n        return *this;\n    }\n\n    inline std::uint64_t messageTimeoutNs() const\n    {\n        return aeron_archive_context_get_message_timeout_ns(m_aeron_archive_ctx_t);\n    }\n\n    inline Context &messageRetryAttempts(const std::uint32_t messageRetryAttempts)\n    {\n        aeron_archive_context_set_message_retry_attempts(m_aeron_archive_ctx_t, messageRetryAttempts);\n        return *this;\n    }\n\n    inline std::uint32_t messageRetryAttempts() const\n    {\n        return aeron_archive_context_get_message_retry_attempts(m_aeron_archive_ctx_t);\n    }\n\n    template<typename IdleStrategy>\n    inline Context &idleStrategy(IdleStrategy &idleStrategy)\n    {\n        m_idleFunc = [&idleStrategy](int work_count){ idleStrategy.idle(work_count); };\n        return *this;\n    }\n\n    inline Context &credentialsSupplier(const CredentialsSupplier &credentialsSupplier)\n    {\n        m_credentialsSupplier.m_encodedCredentials = credentialsSupplier.m_encodedCredentials;\n        m_credentialsSupplier.m_onChallenge = credentialsSupplier.m_onChallenge;\n        m_credentialsSupplier.m_onFree = credentialsSupplier.m_onFree;\n        return *this;\n    }\n\n    inline Context &recordingSignalConsumer(const recording_signal_consumer_t &consumer)\n    {\n        m_recordingSignalConsumer = consumer;\n        return *this;\n    }\n\n    inline Context &errorHandler(const exception_handler_t &errorHandler)\n    {\n        if (aeron_archive_context_set_error_handler(\n            m_aeron_archive_ctx_t,\n            error_handler_func,\n            (void *)this) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        m_errorHandler = errorHandler;\n        return *this;\n    }\n\n    inline exception_handler_t errorHandler() const\n    {\n        return m_errorHandler;\n    }\n\n    inline Context &delegatingInvoker(const delegating_invoker_t &delegatingInvokerFunc)\n    {\n        m_delegatingInvoker = delegatingInvokerFunc;\n        return *this;\n    }\n\n    inline delegating_invoker_t delegatingInvoker() const\n    {\n        return m_delegatingInvoker;\n    }\n\n    inline Context &maxErrorMessageLength(const std::uint32_t maxErrorMessageLength)\n    {\n        m_maxErrorMessageLength = maxErrorMessageLength;\n        return *this;\n    }\n\n    inline std::uint32_t maxErrorMessageLength() const\n    {\n        return m_maxErrorMessageLength;\n    }\n\nprivate:\n    aeron_archive_context_t *m_aeron_archive_ctx_t = nullptr; // backing C struct\n\n    std::shared_ptr<Aeron> m_aeronW = nullptr;\n\n    std::function<void(int work_count)> m_idleFunc;\n    YieldingIdleStrategy m_defaultIdleStrategy;\n\n    CredentialsSupplier m_credentialsSupplier;\n    aeron_archive_encoded_credentials_t m_lastEncodedCredentials = {};\n\n    recording_signal_consumer_t m_recordingSignalConsumer = nullptr;\n    exception_handler_t m_errorHandler = nullptr;\n    delegating_invoker_t m_delegatingInvoker = nullptr;\n\n    std::uint32_t m_maxErrorMessageLength = 1000;\n\n    // This Context is created after connect succeeds and wraps around a context_t created in the C layer\n    explicit Context(\n        aeron_archive_context_t *archive_context) :\n        m_aeron_archive_ctx_t(archive_context)\n    {\n        setupContext();\n    }\n\n    void setupContext()\n    {\n        if (aeron_archive_context_set_idle_strategy(\n            m_aeron_archive_ctx_t,\n            idle,\n            (void *)this) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        if (aeron_archive_context_set_credentials_supplier(\n            m_aeron_archive_ctx_t,\n            encodedCredentialsFunc,\n            onChallengeFunc,\n            onFreeFunc,\n            (void *)this) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        if (aeron_archive_context_set_recording_signal_consumer(\n            m_aeron_archive_ctx_t,\n            recording_signal_consumer_func,\n            (void *)this) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        if (aeron_archive_context_set_delegating_invoker(\n            m_aeron_archive_ctx_t,\n            delegating_invoker_func,\n            (void *)this) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        this->idleStrategy(m_defaultIdleStrategy);\n    }\n\n    static void idle(void *clientd, int work_count)\n    {\n        auto ctx = (Context *)clientd;\n\n        ctx->m_idleFunc(work_count);\n    }\n\n    static aeron_archive_encoded_credentials_t *encodedCredentialsFunc(void *clientd)\n    {\n        auto ctx = (Context *)clientd;\n\n        auto credentials = ctx->m_credentialsSupplier.m_encodedCredentials();\n\n        ctx->m_lastEncodedCredentials = { credentials.first, credentials.second };\n\n        return &ctx->m_lastEncodedCredentials;\n    }\n\n    static aeron_archive_encoded_credentials_t *onChallengeFunc(aeron_archive_encoded_credentials_t *encodedChallenge, void *clientd)\n    {\n        auto ctx = (Context *)clientd;\n\n        auto credentials = ctx->m_credentialsSupplier.m_onChallenge({ encodedChallenge->data, encodedChallenge->length});\n\n        ctx->m_lastEncodedCredentials = { credentials.first, credentials.second };\n\n        return &ctx->m_lastEncodedCredentials;\n    }\n\n    static void onFreeFunc(aeron_archive_encoded_credentials_t *credentials, void *clientd)\n    {\n        auto ctx = (Context *)clientd;\n\n        ctx->m_credentialsSupplier.m_onFree({ credentials->data, credentials->length});\n    }\n\n    static void recording_signal_consumer_func(\n        aeron_archive_recording_signal_t *recording_signal,\n        void *clientd)\n    {\n        auto ctx = (Context *)clientd;\n\n        if (nullptr != ctx->m_recordingSignalConsumer)\n        {\n            RecordingSignal signal(\n                recording_signal->control_session_id,\n                recording_signal->recording_id,\n                recording_signal->subscription_id,\n                recording_signal->position,\n                recording_signal->recording_signal_code);\n\n            ctx->m_recordingSignalConsumer(signal);\n        }\n    }\n\n    static void error_handler_func(void *clientd, int errcode, const char *message)\n    {\n        auto ctx = (Context *)clientd;\n\n        if (nullptr != ctx->m_errorHandler)\n        {\n            try\n            {\n                ARCHIVE_MAP_TO_SOURCED_EXCEPTION_AND_THROW(errcode, message);\n            }\n            catch (SourcedException &exception)\n            {\n                ctx->m_errorHandler(exception);\n            }\n        }\n    }\n\n    static void delegating_invoker_func(void *clientd)\n    {\n        auto ctx = (Context *)clientd;\n\n        if (nullptr != ctx->m_delegatingInvoker)\n        {\n            ctx->m_delegatingInvoker();\n        }\n    }\n\n};\n\n}}}\n\n#endif //AERON_ARCHIVE_WRAPPER_CONTEXT_H\n"
  },
  {
    "path": "aeron-archive/src/main/cpp_wrapper/client/archive/CredentialsSupplier.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_ARCHIVE_WRAPPER_CREDENTIALS_SUPPLIER_H\n#define AERON_ARCHIVE_WRAPPER_CREDENTIALS_SUPPLIER_H\n\n#include \"AeronArchive.h\"\n#include \"Context.h\"\n\nnamespace aeron { namespace archive { namespace client\n{\n\ntypedef std::function<std::pair<const char *, std::uint32_t>()> credentials_encoded_credentials_supplier_t;\n\ninline std::pair<const char *, std::uint32_t> defaultCredentialsEncodedCredentials()\n{\n    return { nullptr, 0 };\n}\n\ntypedef std::function<std::pair<const char *, std::uint32_t>(\n    std::pair<const char *, std::uint32_t> encodedChallenge)> credentials_challenge_supplier_t;\n\ninline std::pair<const char *, std::uint32_t> defaultCredentialsOnChallenge(\n    std::pair<const char *, std::uint32_t> encodedChallenge)\n{\n    return { nullptr, 0 };\n}\n\ntypedef std::function<void(std::pair<const char *, std::uint32_t> encodedCredentials)> credentials_free_t;\n\ninline void defaultCredentialsOnFree(std::pair<const char *, std::uint32_t> credentials)\n{\n    delete[] credentials.first;\n}\n\nstruct CredentialsSupplier\n{\n    credentials_encoded_credentials_supplier_t m_encodedCredentials = defaultCredentialsEncodedCredentials;\n    credentials_challenge_supplier_t m_onChallenge = defaultCredentialsOnChallenge;\n    credentials_free_t m_onFree = defaultCredentialsOnFree;\n\n    explicit CredentialsSupplier(\n        credentials_encoded_credentials_supplier_t encodedCredentials = defaultCredentialsEncodedCredentials,\n        credentials_challenge_supplier_t onChallenge = defaultCredentialsOnChallenge,\n        credentials_free_t onFree = defaultCredentialsOnFree) :\n        m_encodedCredentials(std::move(encodedCredentials)),\n        m_onChallenge(std::move(onChallenge)),\n        m_onFree(std::move(onFree))\n    {\n    }\n};\n\n}}}\n\n#endif //AERON_ARCHIVE_WRAPPER_CREDENTIALS_SUPPLIER_H\n"
  },
  {
    "path": "aeron-archive/src/main/cpp_wrapper/client/archive/RecordingPos.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_ARCHIVE_WRAPPER_RECORDING_POS_H\n#define AERON_ARCHIVE_WRAPPER_RECORDING_POS_H\n\n#include \"AeronArchive.h\"\n\nnamespace aeron { namespace archive { namespace client\n{\n\nnamespace RecordingPos\n{\n\ninline static std::int32_t findCounterIdByRecordingId(CountersReader &countersReader, std::int64_t recordingId)\n{\n    return aeron_archive_recording_pos_find_counter_id_by_recording_id(countersReader.countersReader(), recordingId);\n}\n\ninline static std::int32_t findCounterIdBySessionId(CountersReader &countersReader, std::int32_t sessionId)\n{\n    return aeron_archive_recording_pos_find_counter_id_by_session_id(countersReader.countersReader(), sessionId);\n}\n\ninline static std::int64_t getRecordingId(CountersReader &countersReader, std::int32_t counterId)\n{\n    return aeron_archive_recording_pos_get_recording_id(countersReader.countersReader(), counterId);\n}\n\ninline static std::string getSourceIdentity(CountersReader &countersReader, std::int32_t counterId)\n{\n    const size_t initial_sib_len = AERON_COUNTER_MAX_LABEL_LENGTH;\n    const char source_identity_buffer[initial_sib_len] = { '\\0' };\n    size_t sib_len = initial_sib_len;\n\n    if (aeron_archive_recording_pos_get_source_identity(\n        countersReader.countersReader(),\n        counterId,\n        source_identity_buffer,\n        &sib_len) < 0)\n    {\n        ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n    }\n\n    return { source_identity_buffer, sib_len };\n}\n\ninline static bool isActive(CountersReader &countersReader, std::int32_t counterId, std::int64_t recordingId)\n{\n    bool isActive;\n\n    if (aeron_archive_recording_pos_is_active(&isActive, countersReader.countersReader(), counterId, recordingId) < 0)\n    {\n        ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n    }\n\n    return isActive;\n}\n\n}\n\n}}}\n\n#endif //AERON_ARCHIVE_WRAPPER_RECORDING_POS_H\n"
  },
  {
    "path": "aeron-archive/src/main/cpp_wrapper/client/archive/ReplayMerge.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_ARCHIVE_WRAPPER_REPLAY_MERGE_H\n#define AERON_ARCHIVE_WRAPPER_REPLAY_MERGE_H\n\n#include \"AeronArchive.h\"\n\nnamespace aeron { namespace archive { namespace client\n{\n\nclass ReplayMerge\n{\npublic:\n\n    ReplayMerge(\n        const std::shared_ptr<Subscription> &subscription,\n        const std::shared_ptr<AeronArchive> &archive,\n        const std::string &replayChannel,\n        const std::string &replayDestination,\n        const std::string &liveDestination,\n        std::int64_t recordingId,\n        std::int64_t startPosition,\n        const epoch_clock_t &epochClock = aeron::currentTimeMillis,\n        std::int64_t mergeProgressTimeoutMs = REPLAY_MERGE_PROGRESS_TIMEOUT_DEFAULT_MS) :\n        m_subscription(subscription),\n        m_archive(archive),\n        m_replay_merge_t(nullptr),\n        m_image(nullptr)\n    {\n        if (aeron_archive_replay_merge_init(\n            &m_replay_merge_t,\n            subscription->subscription(),\n            archive->m_aeron_archive_t,\n            replayChannel.c_str(),\n            replayDestination.c_str(),\n            liveDestination.c_str(),\n            recordingId,\n            startPosition,\n            epochClock(),\n            mergeProgressTimeoutMs) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    ~ReplayMerge()\n    {\n        m_image = nullptr;\n        aeron_archive_replay_merge_close(m_replay_merge_t);\n    }\n\n    inline int doWork()\n    {\n        int count;\n\n        if (aeron_archive_replay_merge_do_work(\n            &count,\n            m_replay_merge_t) < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    template<typename F>\n    inline int poll(F &&fragmentHandler, int fragmentLimit)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n\n        int rc = aeron_archive_replay_merge_poll(\n            m_replay_merge_t,\n            doPoll<handler_type>,\n            const_cast<void *>(reinterpret_cast<const void *>(&fragmentHandler)),\n            fragmentLimit);\n\n        if (rc < 0)\n        {\n            ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return rc;\n    }\n\n    inline std::shared_ptr<Image> image()\n    {\n        if (nullptr == m_image)\n        {\n            aeron_image_t *image = aeron_archive_replay_merge_image(m_replay_merge_t);\n\n            if (nullptr == image)\n            {\n                return nullptr;\n            }\n\n            m_image = std::make_shared<Image>(m_subscription->subscription(), image);\n        }\n\n        return m_image;\n    }\n\n    inline bool isMerged() const\n    {\n        return aeron_archive_replay_merge_is_merged(m_replay_merge_t);\n    }\n\n    inline bool hasFailed() const\n    {\n        return aeron_archive_replay_merge_has_failed(m_replay_merge_t);\n    }\n\n    inline bool isLiveAdded() const\n    {\n        return aeron_archive_replay_merge_is_live_added(m_replay_merge_t);\n    }\n\nprivate:\n    std::shared_ptr<Subscription> m_subscription;\n    std::shared_ptr<AeronArchive> m_archive;\n    aeron_archive_replay_merge_t *m_replay_merge_t;\n\n    std::shared_ptr<Image> m_image;\n};\n\n}}}\n\n#endif //AERON_ARCHIVE_WRAPPER_REPLAY_MERGE_H\n"
  },
  {
    "path": "aeron-archive/src/main/cpp_wrapper/client/archive/ReplayParams.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_ARCHIVE_WRAPPER_REPLAY_PARAMS_H\n#define AERON_ARCHIVE_WRAPPER_REPLAY_PARAMS_H\n\n#include \"AeronArchive.h\"\n\nnamespace aeron { namespace archive { namespace client\n{\n\nclass ReplayParams\n{\npublic:\n    friend class AeronArchive;\n\n    ReplayParams()\n    {\n        aeron_archive_replay_params_init(&m_params);\n    }\n\n    std::int32_t boundingLimitCounterId() const\n    {\n        return m_params.bounding_limit_counter_id;\n    }\n\n    ReplayParams &boundingLimitCounterId(std::int32_t boundingLimitCounterId)\n    {\n        m_params.bounding_limit_counter_id = boundingLimitCounterId;\n        return *this;\n    }\n\n    std::int32_t fileIoMaxLength() const\n    {\n        return m_params.file_io_max_length;\n    }\n\n    ReplayParams &fileIoMaxLength(std::int32_t fileIoMaxLength)\n    {\n        m_params.file_io_max_length = fileIoMaxLength;\n        return *this;\n    }\n\n    std::int64_t position() const\n    {\n        return m_params.position;\n    }\n\n    ReplayParams &position(std::int64_t position)\n    {\n        m_params.position = position;\n        return *this;\n    }\n\n    std::int64_t length() const\n    {\n        return m_params.length;\n    }\n\n    ReplayParams &length(std::int64_t length)\n    {\n        m_params.length = length;\n        return *this;\n    }\n\n    bool isBounded() const\n    {\n        return NULL_VALUE != boundingLimitCounterId();\n    }\n\n    ReplayParams &replayToken(std::int64_t replayToken)\n    {\n        m_params.replay_token = replayToken;\n        return *this;\n    }\n\n    std::int64_t replayToken() const\n    {\n        return m_params.replay_token;\n    }\n\n    ReplayParams &subscriptionRegistrationId(std::int64_t registrationId)\n    {\n        m_params.subscription_registration_id = registrationId;\n        return *this;\n    }\n\n    std::int64_t subscriptionRegistrationId() const\n    {\n        return m_params.subscription_registration_id;\n    }\n\nprivate:\n    aeron_archive_replay_params_t m_params;\n\n};\n\n}}}\n\n#endif //AERON_ARCHIVE_WRAPPER_REPLAY_PARAMS_H\n"
  },
  {
    "path": "aeron-archive/src/main/cpp_wrapper/client/archive/ReplicationParams.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_ARCHIVE_WRAPPER_REPLICATION_PARAMS_H\n#define AERON_ARCHIVE_WRAPPER_REPLICATION_PARAMS_H\n\n#include \"AeronArchive.h\"\n\nnamespace aeron { namespace archive { namespace client\n{\n\nclass ReplicationParams\n{\npublic:\n    friend class AeronArchive;\n\n    ReplicationParams()\n    {\n        aeron_archive_replication_params_init(&m_params);\n        liveDestination(\"\");\n        replicationChannel(\"\");\n        m_params.encoded_credentials = &m_encoded_credentials_t;\n    }\n\n    std::int64_t stopPosition() const\n    {\n        return m_params.stop_position;\n    }\n\n    ReplicationParams& stopPosition(std::int64_t stopPosition)\n    {\n        m_params.stop_position = stopPosition;\n        return *this;\n    }\n\n    std::int64_t dstRecordingId() const\n    {\n        return m_params.dst_recording_id;\n    }\n\n    ReplicationParams &dstRecordingId(std::int64_t dstRecordingId)\n    {\n        m_params.dst_recording_id = dstRecordingId;\n        return *this;\n    }\n\n    const std::string &liveDestination() const\n    {\n        return m_liveDestination;\n    }\n\n    ReplicationParams &liveDestination(const std::string &liveDestination)\n    {\n        m_liveDestination = liveDestination;\n        m_params.live_destination = m_liveDestination.c_str();\n        return *this;\n    }\n\n    const std::string &replicationChannel() const\n    {\n        return m_replicationChannel;\n    }\n\n    ReplicationParams &replicationChannel(const std::string &replicationChannel)\n    {\n        m_replicationChannel = replicationChannel;\n        m_params.replication_channel = m_replicationChannel.c_str();\n        return *this;\n    }\n\n    std::int64_t channelTagId() const\n    {\n        return m_params.channel_tag_id;\n    }\n\n    ReplicationParams &channelTagId(std::int64_t channelTagId)\n    {\n        m_params.channel_tag_id = channelTagId;\n        return *this;\n    }\n\n    std::int64_t subscriptionTagId() const\n    {\n        return m_params.subscription_tag_id;\n    }\n\n    ReplicationParams &subscriptionTagId(std::int64_t subscriptionTagId)\n    {\n        m_params.subscription_tag_id = subscriptionTagId;\n        return *this;\n    }\n\n    std::int32_t fileIoMaxLength() const\n    {\n        return m_params.file_io_max_length;\n    }\n\n    ReplicationParams &fileIoMaxLength(std::int32_t fileIoMaxLength)\n    {\n        m_params.file_io_max_length = fileIoMaxLength;\n        return *this;\n    }\n\n    std::int32_t replicationSessionId() const\n    {\n        return m_params.replication_session_id;\n    }\n\n    ReplicationParams &replicationSessionId(std::int32_t replicationSessionId)\n    {\n        m_params.replication_session_id = replicationSessionId;\n        return *this;\n    }\n\n    std::pair<const char *, std::uint32_t> encodedCredentials() const\n    {\n        return m_encodedCredentials;\n    }\n\n    ReplicationParams &encodedCredentials(std::pair<const char *, std::uint32_t> encodedCredentials)\n    {\n        m_encodedCredentials = encodedCredentials;\n        m_encoded_credentials_t = { m_encodedCredentials.first, m_encodedCredentials.second };\n        return *this;\n    }\n\nprivate:\n    aeron_archive_replication_params_t m_params;\n\n    std::string m_liveDestination;\n    std::string m_replicationChannel;\n\n    std::pair<const char *, std::uint32_t> m_encodedCredentials;\n    aeron_archive_encoded_credentials_t m_encoded_credentials_t;\n\n};\n\n}}}\n\n#endif //AERON_ARCHIVE_WRAPPER_REPLICATION_PARAMS_H\n"
  },
  {
    "path": "aeron-archive/src/main/cpp_wrapper/client/util/ArchiveExceptions.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_ARCHIVE_WRAPPER_UTIL_EXCEPTIONS_H\n#define AERON_ARCHIVE_WRAPPER_UTIL_EXCEPTIONS_H\n\n#include \"util/Exceptions.h\"\n\nnamespace aeron { namespace util\n{\n\nAERON_DECLARE_SOURCED_EXCEPTION(ArchiveException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\n\n#define ARCHIVE_MAP_TO_SOURCED_EXCEPTION_AND_THROW(code, message) AERON_MAP_TO_SOURCED_EXCEPTION_AND_THROW_WITH_DEFAULT(code, message, ArchiveException)\n\n#define ARCHIVE_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW ARCHIVE_MAP_TO_SOURCED_EXCEPTION_AND_THROW(aeron_errcode(), aeron_errmsg())\n\n}}\n\n#endif // AERON_ARCHIVE_WRAPPER_UTIL_EXCEPTIONS_H\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/AbstractListRecordingsSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nabstract class AbstractListRecordingsSession implements Session\n{\n    static final int MAX_SCANS_PER_WORK_CYCLE = 64;\n\n    private final UnsafeBuffer descriptorBuffer;\n    private final Catalog catalog;\n    private final int count;\n    private final ControlSession controlSession;\n    private final long correlationId;\n    private long recordingId;\n    private int sent;\n    private boolean isDone = false;\n\n    AbstractListRecordingsSession(\n        final long correlationId,\n        final long fromRecordingId,\n        final int count,\n        final Catalog catalog,\n        final ControlSession controlSession,\n        final UnsafeBuffer descriptorBuffer)\n    {\n        this.correlationId = correlationId;\n        this.recordingId = fromRecordingId;\n        this.count = count;\n        this.controlSession = controlSession;\n        this.catalog = catalog;\n        this.descriptorBuffer = descriptorBuffer;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n        isDone = true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return isDone;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        if (isDone)\n        {\n            return 0;\n        }\n\n        final CatalogIndex catalogIndex = catalog.index();\n        final int lastPosition = catalogIndex.lastPosition();\n        final long[] index = catalogIndex.index();\n        int position = CatalogIndex.find(index, recordingId, lastPosition);\n\n        if (position < 0)\n        {\n            for (int i = 0; i <= lastPosition; i += 2)\n            {\n                if (index[i] >= recordingId)\n                {\n                    position = i;\n                    break;\n                }\n            }\n        }\n\n        final int batchStartPosition = position;\n        for (int recordsScanned = 0; sent < count && recordsScanned < MAX_SCANS_PER_WORK_CYCLE; recordsScanned++)\n        {\n            final boolean noMoreRecordings = position < 0 || position > lastPosition;\n            if (noMoreRecordings || catalog.wrapDescriptorAtOffset(descriptorBuffer, (int)index[position + 1]) < 0)\n            {\n                controlSession.sendRecordingUnknown(correlationId, noMoreRecordings ? recordingId : index[position]);\n                isDone = true;\n                break;\n            }\n\n            if (acceptDescriptor(descriptorBuffer))\n            {\n                if (!controlSession.sendDescriptor(correlationId, descriptorBuffer))\n                {\n                    isDone = controlSession.isDone();\n                    break;\n                }\n                ++sent;\n            }\n\n            if (position < lastPosition)\n            {\n                recordingId = index[position + 2];\n            }\n            else\n            {\n                recordingId++;\n            }\n            position += 2;\n        }\n\n        if (sent == count)\n        {\n            isDone = true;\n        }\n\n        return (position - batchStartPosition) / 2;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        controlSession.activeListing(null);\n    }\n\n    abstract boolean acceptDescriptor(UnsafeBuffer descriptorBuffer);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/Archive.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.Counter;\nimport io.aeron.Image;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.checksum.Checksums;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\nimport io.aeron.driver.DutyCycleTracker;\nimport io.aeron.driver.status.DutyCycleStallTracker;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.AuthenticatorSupplier;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.security.AuthorisationServiceSupplier;\nimport io.aeron.version.Versioned;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.MarkFile;\nimport org.agrona.Strings;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.StatusIndicator;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.FileStore;\nimport java.nio.file.Files;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.SecureRandom;\nimport java.util.Objects;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.AeronCounters.ARCHIVE_CONTROL_SESSIONS_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_ERROR_COUNT_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_RECORDING_SESSION_COUNT_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID;\nimport static io.aeron.AeronCounters.validateCounterTypeId;\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.CommonContext.fallbackLogger;\nimport static io.aeron.archive.Archive.Configuration.ERROR_BUFFER_LENGTH_DEFAULT;\nimport static io.aeron.archive.Archive.Configuration.SESSION_LIVENESS_CHECK_INTERVAL_DEFAULT_NS;\nimport static io.aeron.archive.Archive.Configuration.SESSION_LIVENESS_CHECK_INTERVAL_PROP_NAME;\nimport static io.aeron.archive.ArchiveThreadingMode.DEDICATED;\nimport static io.aeron.exceptions.AeronException.Category.ERROR;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MAX_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.checkTermLength;\nimport static java.lang.System.getProperty;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.BitUtil.isPowerOfTwo;\nimport static org.agrona.BufferUtil.allocateDirectAligned;\nimport static org.agrona.SystemUtil.getDurationInNanos;\nimport static org.agrona.SystemUtil.getSizeAsInt;\nimport static org.agrona.SystemUtil.getSizeAsLong;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * The Aeron Archive which allows for the recording and replay of local and remote {@link io.aeron.Publication}s.\n */\n@Versioned\npublic final class Archive implements AutoCloseable\n{\n    private final Context ctx;\n    private final AgentRunner conductorRunner;\n    private final AgentInvoker conductorInvoker;\n\n    Archive(final Context ctx)\n    {\n        try\n        {\n            ctx.conclude();\n            this.ctx = ctx;\n\n            final ArchiveConductor conductor = DEDICATED == ctx.threadingMode() ?\n                (new DedicatedModeArchiveConductor(ctx)) : (new SharedModeArchiveConductor(ctx));\n\n            if (ArchiveThreadingMode.INVOKER == ctx.threadingMode())\n            {\n                conductorInvoker = new AgentInvoker(ctx.errorHandler(), ctx.errorCounter(), conductor);\n                conductorRunner = null;\n            }\n            else\n            {\n                conductorInvoker = null;\n                conductorRunner = new AgentRunner(\n                    ctx.idleStrategy(), ctx.errorHandler(), ctx.errorCounter(), conductor);\n            }\n        }\n        catch (final ConcurrentConcludeException ex)\n        {\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            final ArchiveMarkFile markFile = ctx.markFile;\n            if (null != markFile)\n            {\n                markFile.signalReady(NULL_VALUE);\n            }\n            CloseHelper.quietClose(ctx::close);\n            throw ex;\n        }\n    }\n\n    /**\n     * Launch an {@link Archive} with that communicates with an out of process {@link io.aeron.driver.MediaDriver}\n     * and await a shutdown signal.\n     *\n     * @param args command line argument which is a list for properties files as URLs or filenames.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            Archive ignore = launch(new Context().errorHandler(\n                (throwable) ->\n                {\n                    if (throwable instanceof AgentTerminationException)\n                    {\n                        barrier.signal();\n                    }\n                    else if (AeronException.isFatal(throwable))\n                    {\n                        barrier.signal();\n                    }\n                })))\n        {\n            barrier.await();\n            System.out.println(\"Shutdown Archive...\");\n        }\n    }\n\n    /**\n     * Get the {@link Archive.Context} that is used by this {@link Archive}.\n     *\n     * @return the {@link Archive.Context} that is used by this {@link Archive}.\n     */\n    public Context context()\n    {\n        return ctx;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(conductorInvoker);\n        CloseHelper.close(conductorRunner);\n    }\n\n    /**\n     * Get the {@link AgentInvoker} for the archive if it is running in {@link ArchiveThreadingMode#INVOKER}.\n     *\n     * @return the {@link AgentInvoker} for the archive if it is running in {@link ArchiveThreadingMode#INVOKER}\n     * otherwise null.\n     */\n    public AgentInvoker invoker()\n    {\n        return conductorInvoker;\n    }\n\n    /**\n     * Launch an Archive using a default configuration.\n     *\n     * @return a new instance of an Archive.\n     */\n    public static Archive launch()\n    {\n        return launch(new Context());\n    }\n\n    /**\n     * Launch an Archive by providing a configuration context.\n     *\n     * @param ctx for the configuration parameters.\n     * @return a new instance of an Archive.\n     */\n    public static Archive launch(final Context ctx)\n    {\n        final Archive archive = new Archive(ctx);\n        if (ArchiveThreadingMode.INVOKER == ctx.threadingMode())\n        {\n            archive.conductorInvoker.start();\n        }\n        else\n        {\n            AgentRunner.startOnThread(archive.conductorRunner, ctx.threadFactory());\n        }\n\n        return archive;\n    }\n\n    /**\n     * Configuration for system properties and defaults.\n     * <p>\n     * Details for the individual parameters can be found in the Javadoc for the {@link Context} setters.\n     */\n    @Config(existsInC = false)\n    public static final class Configuration\n    {\n        private Configuration()\n        {\n        }\n\n        /**\n         * Filename for the single instance of a {@link Catalog} contents for an archive.\n         */\n        static final String CATALOG_FILE_NAME = \"archive.catalog\";\n\n        /**\n         * Recording segment file suffix extension.\n         */\n        static final String RECORDING_SEGMENT_SUFFIX = \".rec\";\n\n        /**\n         * Default block length of data in a single IO operation during a recording or replay.\n         */\n        @Config\n        public static final int FILE_IO_MAX_LENGTH_DEFAULT = 1024 * 1024;\n\n        /**\n         * Maximum length of a file IO operation for recording or replay. Must be a power of 2.\n         */\n        @Config\n        public static final String FILE_IO_MAX_LENGTH_PROP_NAME = \"aeron.archive.file.io.max.length\";\n\n        /**\n         * Directory in which the archive stores it files such as the catalog and recordings.\n         */\n        @Config\n        public static final String ARCHIVE_DIR_PROP_NAME = \"aeron.archive.dir\";\n\n        /**\n         * Default directory for the archive files.\n         *\n         * @see #ARCHIVE_DIR_PROP_NAME\n         */\n        @Config\n        public static final String ARCHIVE_DIR_DEFAULT = \"aeron-archive\";\n\n        /**\n         * Alternative directory to store mark file (i.e. {@code archive-mark.dat}).\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String MARK_FILE_DIR_PROP_NAME = \"aeron.archive.mark.file.dir\";\n\n        /**\n         * Recordings will be segmented on disk in files limited to the segment length which must be a multiple of\n         * the term length for each stream. For lots of small recording this value may be reduced.\n         */\n        @Config\n        public static final String SEGMENT_FILE_LENGTH_PROP_NAME = \"aeron.archive.segment.file.length\";\n\n        /**\n         * Default segment file length which is multiple of terms.\n         *\n         * @see #SEGMENT_FILE_LENGTH_PROP_NAME\n         */\n        @Config\n        public static final int SEGMENT_FILE_LENGTH_DEFAULT = 128 * 1024 * 1024;\n\n        /**\n         * Threshold below which the archive will reject new recording requests.\n         */\n        @Config\n        public static final String LOW_STORAGE_SPACE_THRESHOLD_PROP_NAME = \"aeron.archive.low.storage.space.threshold\";\n\n        /**\n         * Default threshold below which the archive will reject new recording requests.\n         *\n         * @see #LOW_STORAGE_SPACE_THRESHOLD_PROP_NAME\n         */\n        @Config\n        public static final int LOW_STORAGE_SPACE_THRESHOLD_DEFAULT = SEGMENT_FILE_LENGTH_DEFAULT;\n\n        /**\n         * The level at which recording files should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         */\n        @Config\n        public static final String FILE_SYNC_LEVEL_PROP_NAME = \"aeron.archive.file.sync.level\";\n\n        /**\n         * Default is to use normal file writes which may mean some data loss in the event of a power failure.\n         *\n         * @see #FILE_SYNC_LEVEL_PROP_NAME\n         */\n        @Config\n        public static final int FILE_SYNC_LEVEL_DEFAULT = 0;\n\n        /**\n         * The level at which catalog updates and directory should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         */\n        @Config\n        public static final String CATALOG_FILE_SYNC_LEVEL_PROP_NAME = \"aeron.archive.catalog.file.sync.level\";\n\n        /**\n         * Default is to use normal file writes which may mean some data loss in the event of a power failure.\n         *\n         * @see #CATALOG_FILE_SYNC_LEVEL_PROP_NAME\n         */\n        @Config\n        public static final int CATALOG_FILE_SYNC_LEVEL_DEFAULT = FILE_SYNC_LEVEL_DEFAULT;\n\n        /**\n         * What {@link ArchiveThreadingMode} should be used.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"DEDICATED\")\n        public static final String THREADING_MODE_PROP_NAME = \"aeron.archive.threading.mode\";\n\n        /**\n         * Default {@link IdleStrategy} to be used for the archive {@link Agent}s when not busy.\n         */\n        @Config\n        public static final String ARCHIVE_IDLE_STRATEGY_PROP_NAME = \"aeron.archive.idle.strategy\";\n\n        /**\n         * The {@link IdleStrategy} to be used for the archive recorder {@link Agent} when not busy.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String ARCHIVE_RECORDER_IDLE_STRATEGY_PROP_NAME = \"aeron.archive.recorder.idle.strategy\";\n\n        /**\n         * The {@link IdleStrategy} to be used for the archive replayer {@link Agent} when not busy.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String ARCHIVE_REPLAYER_IDLE_STRATEGY_PROP_NAME = \"aeron.archive.replayer.idle.strategy\";\n\n        /**\n         * Default {@link IdleStrategy} to be used for the archive {@link Agent}s when not busy.\n         *\n         * @see #ARCHIVE_IDLE_STRATEGY_PROP_NAME\n         */\n        @Config(id = \"ARCHIVE_IDLE_STRATEGY\")\n        public static final String DEFAULT_IDLE_STRATEGY = \"org.agrona.concurrent.BackoffIdleStrategy\";\n\n        /**\n         * Maximum number of concurrent recordings which can be active at a time. Going beyond this number will\n         * result in an exception and further recordings will be rejected. Since wildcard subscriptions can have\n         * multiple images, and thus multiple recordings, then the limit may come later. It is best to\n         * use session based subscriptions.\n         */\n        @Config\n        public static final String MAX_CONCURRENT_RECORDINGS_PROP_NAME = \"aeron.archive.max.concurrent.recordings\";\n\n        /**\n         * Default maximum number of concurrent recordings. Unless on a very fast SSD and having sufficient memory\n         * for the page cache then this number should be kept low, especially when sync'ing writes.\n         *\n         * @see #MAX_CONCURRENT_RECORDINGS_PROP_NAME\n         */\n        @Config\n        public static final int MAX_CONCURRENT_RECORDINGS_DEFAULT = 20;\n\n        /**\n         * Maximum number of concurrent replays. Beyond this maximum an exception will be raised and further replays\n         * will be rejected.\n         */\n        @Config\n        public static final String MAX_CONCURRENT_REPLAYS_PROP_NAME = \"aeron.archive.max.concurrent.replays\";\n\n        /**\n         * Default maximum number of concurrent replays. Unless on a fast SSD and having sufficient memory\n         * for the page cache then this number should be kept low.\n         */\n        @Config\n        public static final int MAX_CONCURRENT_REPLAYS_DEFAULT = 20;\n\n        /**\n         * Maximum number of entries for the archive {@link Catalog}. Increasing this limit will require use of the\n         * {@link CatalogTool}. The number of entries can be reduced by extending existing recordings rather than\n         * creating new ones.\n         *\n         * @deprecated Use {@link #CATALOG_CAPACITY_PROP_NAME} instead.\n         */\n        @Deprecated\n        @Config\n        public static final String MAX_CATALOG_ENTRIES_PROP_NAME = \"aeron.archive.max.catalog.entries\";\n\n        /**\n         * Default limit for the entries in the {@link Catalog}.\n         *\n         * @see #MAX_CATALOG_ENTRIES_PROP_NAME\n         */\n        @Deprecated\n        @Config\n        public static final long MAX_CATALOG_ENTRIES_DEFAULT = 8 * 1024;\n\n        /**\n         * Default capacity in bytes of the archive {@link Catalog}. {@link Catalog} will resize itself when this\n         * limit is reached.\n         */\n        @Config\n        public static final String CATALOG_CAPACITY_PROP_NAME = \"aeron.archive.catalog.capacity\";\n\n        /**\n         * Default capacity in bytes for the {@link Catalog}.\n         *\n         * @see #CATALOG_CAPACITY_PROP_NAME\n         */\n        @Config\n        public static final long CATALOG_CAPACITY_DEFAULT = Catalog.DEFAULT_CAPACITY;\n\n        /**\n         * Timeout for making a connection back to a client for a control session or replay.\n         */\n        @Config\n        public static final String CONNECT_TIMEOUT_PROP_NAME = \"aeron.archive.connect.timeout\";\n\n        /**\n         * Default timeout for connecting back to a client for a control session or replay. You may want to\n         * increase this on higher latency networks.\n         *\n         * @see #CONNECT_TIMEOUT_PROP_NAME\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 5L * 1000 * 1000 * 1000)\n        public static final long CONNECT_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5);\n\n        /**\n         * Time interval in nanoseconds for checking session liveness checks.\n         *\n         * @since 1.47.0\n         */\n        @Config\n        public static final String SESSION_LIVENESS_CHECK_INTERVAL_PROP_NAME =\n            \"aeron.archive.session.liveness.check.interval\";\n\n        /**\n         * Default time interval in nanoseconds for checking session liveness.\n         *\n         * @see #SESSION_LIVENESS_CHECK_INTERVAL_PROP_NAME\n         * @since 1.47.0\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000)\n        public static final long SESSION_LIVENESS_CHECK_INTERVAL_DEFAULT_NS = TimeUnit.SECONDS.toNanos(1);\n\n        /**\n         * How long a replay publication should linger after all data is sent. Longer linger can help avoid tail loss.\n         */\n        @Config\n        public static final String REPLAY_LINGER_TIMEOUT_PROP_NAME = \"aeron.archive.replay.linger.timeout\";\n\n        /**\n         * Default for long to linger a replay connection which defaults to\n         * {@link io.aeron.driver.Configuration#publicationLingerTimeoutNs()}.\n         *\n         * @see #REPLAY_LINGER_TIMEOUT_PROP_NAME\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 5L * 1000 * 1000 * 1000)\n        public static final long REPLAY_LINGER_TIMEOUT_DEFAULT_NS =\n            io.aeron.driver.Configuration.publicationLingerTimeoutNs();\n\n        /**\n         * Property name for threshold value for the conductor work cycle threshold to track for being exceeded.\n         */\n        @Config\n        public static final String CONDUCTOR_CYCLE_THRESHOLD_PROP_NAME = \"aeron.archive.conductor.cycle.threshold\";\n\n        /**\n         * Default threshold value for the conductor work cycle threshold to track for being exceeded.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 100_000_000L)\n        public static final long CONDUCTOR_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n        /**\n         * Property name for threshold value for the recorder work cycle threshold to track for being exceeded.\n         */\n        @Config\n        public static final String RECORDER_CYCLE_THRESHOLD_PROP_NAME = \"aeron.archive.recorder.cycle.threshold\";\n\n        /**\n         * Default threshold value for the recorder work cycle threshold to track for being exceeded.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 100_000_000L)\n        public static final long RECORDER_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n        /**\n         * Property name for threshold value for the replayer work cycle threshold to track for being exceeded.\n         */\n        @Config\n        public static final String REPLAYER_CYCLE_THRESHOLD_PROP_NAME = \"aeron.archive.replayer.cycle.threshold\";\n\n        /**\n         * Default threshold value for the replayer work cycle threshold to track for being exceeded.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 100_000_000L)\n        public static final long REPLAYER_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n        /**\n         * Should the archive delete existing files on start. Default is false and should only be true for testing.\n         */\n        @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false)\n        public static final String ARCHIVE_DIR_DELETE_ON_START_PROP_NAME = \"aeron.archive.dir.delete.on.start\";\n\n        /**\n         * Channel for receiving replication streams replayed from another archive.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String REPLICATION_CHANNEL_PROP_NAME = \"aeron.archive.replication.channel\";\n\n        /**\n         * Name of the system property for specifying a supplier of {@link Authenticator} for the archive.\n         */\n        @Config\n        public static final String AUTHENTICATOR_SUPPLIER_PROP_NAME = \"aeron.archive.authenticator.supplier\";\n\n        /**\n         * Name of the class to use as a supplier of {@link Authenticator} for the archive. Default is\n         * a non-authenticating option.\n         */\n        @Config\n        public static final String AUTHENTICATOR_SUPPLIER_DEFAULT = \"io.aeron.security.DefaultAuthenticatorSupplier\";\n\n        /**\n         * Name of the system property for specifying a supplier of {@link AuthorisationService} for the archive.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME =\n            \"aeron.archive.authorisation.service.supplier\";\n\n        /**\n         * Default {@link AuthorisationServiceSupplier} that returns {@link AuthorisationService} that allows any\n         * command to be executed (i.e. {@link AuthorisationService#ALLOW_ALL}).\n         */\n        public static final AuthorisationServiceSupplier DEFAULT_AUTHORISATION_SERVICE_SUPPLIER =\n            () -> AuthorisationService.ALLOW_ALL;\n\n        /**\n         * The type id of the {@link Counter} used for keeping track of the number of errors that have occurred.\n         */\n        public static final int ARCHIVE_ERROR_COUNT_TYPE_ID = AeronCounters.ARCHIVE_ERROR_COUNT_TYPE_ID;\n\n        /**\n         * The type id of the {@link Counter} used for keeping track of the count of concurrent control sessions.\n         */\n        public static final int ARCHIVE_CONTROL_SESSIONS_TYPE_ID = AeronCounters.ARCHIVE_CONTROL_SESSIONS_TYPE_ID;\n\n        /**\n         * Size in bytes of the error buffer for the archive when not externally provided.\n         */\n        @Config\n        public static final String ERROR_BUFFER_LENGTH_PROP_NAME = \"aeron.archive.error.buffer.length\";\n\n        /**\n         * Size in bytes of the error buffer for the archive when not eternally provided.\n         */\n        @Config\n        public static final int ERROR_BUFFER_LENGTH_DEFAULT = 1024 * 1024;\n\n        /**\n         * Property that specifies fully qualified class name of the {@link io.aeron.archive.checksum.Checksum}\n         * to be used for checksum computation during recording.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String RECORD_CHECKSUM_PROP_NAME = \"aeron.archive.record.checksum\";\n\n        /**\n         * Property that specifies fully qualified class name of the {@link io.aeron.archive.checksum.Checksum}\n         * to be used for checksum validation during replay.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String REPLAY_CHECKSUM_PROP_NAME = \"aeron.archive.replay.checksum\";\n\n        /**\n         * Property name for the identity of the Archive instance. If not specified defaults to the\n         * {@link Aeron#clientId()} of the assigned {@link Aeron} instance.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = -1)\n        public static final String ARCHIVE_ID_PROP_NAME = \"aeron.archive.id\";\n\n        /**\n         * Is network (UDP) control channel enabled. Defaults to {@code true}.\n         * <p>\n         * If set to anything other than {@code true} then control channel is disabled, i.e. the Archive will run in\n         * IPC-only mode.\n         */\n        @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true)\n        public static final String CONTROL_CHANNEL_ENABLED_PROP_NAME = \"aeron.archive.control.channel.enabled\";\n\n        /**\n         * Update interval in ms for archive mark file.\n         */\n        static final long MARK_FILE_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);\n\n        /**\n         * Timeout in milliseconds for detecting if there is an active Archive instance.\n         */\n        static final long LIVENESS_TIMEOUT_MS = 10 * MARK_FILE_UPDATE_INTERVAL_MS;\n\n        /**\n         * Get the directory name to be used for storing the archive.\n         *\n         * @return the directory name to be used for storing the archive.\n         */\n        public static String archiveDirName()\n        {\n            return System.getProperty(ARCHIVE_DIR_PROP_NAME, ARCHIVE_DIR_DEFAULT);\n        }\n\n        /**\n         * Get the alternative directory to be used for storing the archive mark file.\n         *\n         * @return the directory to be used for storing the archive mark file.\n         */\n        public static String markFileDir()\n        {\n            return System.getProperty(MARK_FILE_DIR_PROP_NAME);\n        }\n\n        /**\n         * The maximum length of a file IO operation.\n         *\n         * @return the maximum length of a file IO operation.\n         */\n        public static int fileIoMaxLength()\n        {\n            return getSizeAsInt(FILE_IO_MAX_LENGTH_PROP_NAME, FILE_IO_MAX_LENGTH_DEFAULT);\n        }\n\n        /**\n         * The length of file to be used for storing recording segments that must be a power of 2.\n         * <p>\n         * If the {@link Image#termBufferLength()} is greater than this will take priority.\n         *\n         * @return length of file to be used for storing recording segments.\n         */\n        public static int segmentFileLength()\n        {\n            return getSizeAsInt(SEGMENT_FILE_LENGTH_PROP_NAME, SEGMENT_FILE_LENGTH_DEFAULT);\n        }\n\n        /**\n         * The low storage space threshold beyond which the archive will reject new requests to record streams.\n         *\n         * @return threshold beyond which the archive will reject new requests to record streams.\n         */\n        public static long lowStorageSpaceThreshold()\n        {\n            return getSizeAsLong(LOW_STORAGE_SPACE_THRESHOLD_PROP_NAME, LOW_STORAGE_SPACE_THRESHOLD_DEFAULT);\n        }\n\n        /**\n         * The level at which files should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         *\n         * @return level at which files should be sync'ed to disk.\n         * @see #FILE_SYNC_LEVEL_PROP_NAME\n         */\n        public static int fileSyncLevel()\n        {\n            return Integer.getInteger(FILE_SYNC_LEVEL_PROP_NAME, FILE_SYNC_LEVEL_DEFAULT);\n        }\n\n        /**\n         * The level at which the catalog file and directory should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         *\n         * @return level at which files should be sync'ed to disk.\n         * @see #CATALOG_FILE_SYNC_LEVEL_PROP_NAME\n         */\n        public static int catalogFileSyncLevel()\n        {\n            return Integer.getInteger(CATALOG_FILE_SYNC_LEVEL_PROP_NAME, CATALOG_FILE_SYNC_LEVEL_DEFAULT);\n        }\n\n        /**\n         * The threading mode to be employed by the archive.\n         *\n         * @return the threading mode to be employed by the archive.\n         */\n        public static ArchiveThreadingMode threadingMode()\n        {\n            return ArchiveThreadingMode.valueOf(System.getProperty(THREADING_MODE_PROP_NAME, DEDICATED.name()));\n        }\n\n        /**\n         * Create a supplier of {@link IdleStrategy}s for the {@link #ARCHIVE_IDLE_STRATEGY_PROP_NAME}\n         * system property.\n         *\n         * @param controllableStatus if a {@link org.agrona.concurrent.ControllableIdleStrategy} is required.\n         * @return the new idle strategy {@link Supplier}.\n         */\n        public static Supplier<IdleStrategy> idleStrategySupplier(final StatusIndicator controllableStatus)\n        {\n            return () ->\n            {\n                final String name = System.getProperty(ARCHIVE_IDLE_STRATEGY_PROP_NAME, DEFAULT_IDLE_STRATEGY);\n                return io.aeron.driver.Configuration.agentIdleStrategy(name, controllableStatus);\n            };\n        }\n\n        /**\n         * Create a supplier of {@link IdleStrategy}s for the {@link #ARCHIVE_RECORDER_IDLE_STRATEGY_PROP_NAME}\n         * system property.\n         *\n         * @param controllableStatus if a {@link org.agrona.concurrent.ControllableIdleStrategy} is required.\n         * @return the new idle strategy {@link Supplier}.\n         */\n        public static Supplier<IdleStrategy> recorderIdleStrategySupplier(final StatusIndicator controllableStatus)\n        {\n            final String name = System.getProperty(ARCHIVE_RECORDER_IDLE_STRATEGY_PROP_NAME);\n            if (null == name)\n            {\n                return null;\n            }\n\n            return () -> io.aeron.driver.Configuration.agentIdleStrategy(name, controllableStatus);\n        }\n\n        /**\n         * Create a supplier of {@link IdleStrategy}s for the {@link #ARCHIVE_REPLAYER_IDLE_STRATEGY_PROP_NAME}\n         * system property.\n         *\n         * @param controllableStatus if a {@link org.agrona.concurrent.ControllableIdleStrategy} is required.\n         * @return the new idle strategy {@link Supplier}.\n         */\n        public static Supplier<IdleStrategy> replayerIdleStrategySupplier(final StatusIndicator controllableStatus)\n        {\n            final String name = System.getProperty(ARCHIVE_REPLAYER_IDLE_STRATEGY_PROP_NAME);\n            if (null == name)\n            {\n                return null;\n            }\n\n            return () -> io.aeron.driver.Configuration.agentIdleStrategy(name, controllableStatus);\n        }\n\n        /**\n         * The maximum number of recordings that can operate concurrently after which new requests will be rejected.\n         *\n         * @return the maximum number of recordings that can operate concurrently.\n         */\n        public static int maxConcurrentRecordings()\n        {\n            return Integer.getInteger(MAX_CONCURRENT_RECORDINGS_PROP_NAME, MAX_CONCURRENT_RECORDINGS_DEFAULT);\n        }\n\n        /**\n         * The maximum number of replays that can operate concurrently after which new requests will be rejected.\n         *\n         * @return the maximum number of replays that can operate concurrently.\n         */\n        public static int maxConcurrentReplays()\n        {\n            return Integer.getInteger(MAX_CONCURRENT_REPLAYS_PROP_NAME, MAX_CONCURRENT_REPLAYS_DEFAULT);\n        }\n\n        /**\n         * Maximum number of catalog entries to allocate for the catalog file.\n         *\n         * @return the maximum number of catalog entries to support for the catalog file.\n         * @see #catalogCapacity()\n         * @deprecated Use {@link #catalogCapacity()} instead.\n         */\n        @Deprecated\n        public static long maxCatalogEntries()\n        {\n            return SystemUtil.getSizeAsLong(MAX_CATALOG_ENTRIES_PROP_NAME, MAX_CATALOG_ENTRIES_DEFAULT);\n        }\n\n        /**\n         * Default capacity (size) in bytes for the catalog file.\n         *\n         * @return default size of the catalog file in bytes.\n         */\n        public static long catalogCapacity()\n        {\n            return SystemUtil.getSizeAsLong(CATALOG_CAPACITY_PROP_NAME, CATALOG_CAPACITY_DEFAULT);\n        }\n\n        /**\n         * The timeout in nanoseconds to wait for a connection.\n         *\n         * @return timeout in nanoseconds to wait for a connection.\n         * @see #CONNECT_TIMEOUT_PROP_NAME\n         */\n        public static long connectTimeoutNs()\n        {\n            return getDurationInNanos(CONNECT_TIMEOUT_PROP_NAME, CONNECT_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * The timeout in nanoseconds to for a replay network publication to linger after draining.\n         *\n         * @return timeout in nanoseconds for a replay network publication to wait in linger.\n         * @see #REPLAY_LINGER_TIMEOUT_PROP_NAME\n         * @see io.aeron.driver.Configuration#PUBLICATION_LINGER_PROP_NAME\n         */\n        public static long replayLingerTimeoutNs()\n        {\n            return getDurationInNanos(REPLAY_LINGER_TIMEOUT_PROP_NAME, REPLAY_LINGER_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * Get threshold value for the conductor work cycle threshold to track for being exceeded.\n         *\n         * @return threshold value in nanoseconds.\n         */\n        public static long conductorCycleThresholdNs()\n        {\n            return getDurationInNanos(CONDUCTOR_CYCLE_THRESHOLD_PROP_NAME, CONDUCTOR_CYCLE_THRESHOLD_DEFAULT_NS);\n        }\n\n        /**\n         * Get threshold value for the recorder work cycle threshold to track for being exceeded.\n         *\n         * @return threshold value in nanoseconds.\n         */\n        public static long recorderCycleThresholdNs()\n        {\n            return getDurationInNanos(RECORDER_CYCLE_THRESHOLD_PROP_NAME, RECORDER_CYCLE_THRESHOLD_DEFAULT_NS);\n        }\n\n        /**\n         * Get threshold value for the replayer work cycle threshold to track for being exceeded.\n         *\n         * @return threshold value in nanoseconds.\n         */\n        public static long replayerCycleThresholdNs()\n        {\n            return getDurationInNanos(REPLAYER_CYCLE_THRESHOLD_PROP_NAME, REPLAYER_CYCLE_THRESHOLD_DEFAULT_NS);\n        }\n\n        /**\n         * Whether to delete directory on start or not.\n         *\n         * @return whether to delete directory on start or not.\n         * @see #ARCHIVE_DIR_DELETE_ON_START_PROP_NAME\n         */\n        public static boolean deleteArchiveOnStart()\n        {\n            return \"true\".equals(getProperty(ARCHIVE_DIR_DELETE_ON_START_PROP_NAME, \"false\"));\n        }\n\n        /**\n         * The system property {@link #REPLICATION_CHANNEL_PROP_NAME} if set, null otherwise.\n         *\n         * @return system property {@link #REPLICATION_CHANNEL_PROP_NAME} if set.\n         */\n        public static String replicationChannel()\n        {\n            return System.getProperty(REPLICATION_CHANNEL_PROP_NAME);\n        }\n\n        /**\n         * Size in bytes of the error buffer in the mark file.\n         *\n         * @return length of error buffer in bytes.\n         * @see #ERROR_BUFFER_LENGTH_PROP_NAME\n         */\n        public static int errorBufferLength()\n        {\n            return getSizeAsInt(ERROR_BUFFER_LENGTH_PROP_NAME, ERROR_BUFFER_LENGTH_DEFAULT);\n        }\n\n        /**\n         * The value {@link #AUTHENTICATOR_SUPPLIER_DEFAULT} or system property\n         * {@link #AUTHENTICATOR_SUPPLIER_PROP_NAME} if set.\n         *\n         * @return {@link #AUTHENTICATOR_SUPPLIER_DEFAULT} or system property\n         * {@link #AUTHENTICATOR_SUPPLIER_PROP_NAME} if set.\n         */\n        public static AuthenticatorSupplier authenticatorSupplier()\n        {\n            final String supplierClassName = System.getProperty(\n                AUTHENTICATOR_SUPPLIER_PROP_NAME, AUTHENTICATOR_SUPPLIER_DEFAULT);\n\n            AuthenticatorSupplier supplier = null;\n            try\n            {\n                supplier = (AuthenticatorSupplier)Class.forName(supplierClassName).getConstructor().newInstance();\n            }\n            catch (final Exception ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n\n            return supplier;\n        }\n\n        /**\n         * The {@link AuthorisationServiceSupplier} specified in the\n         * {@link #AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME} system property or the\n         * {@link #DEFAULT_AUTHORISATION_SERVICE_SUPPLIER}.\n         *\n         * @return system property {@link #AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME} if set or\n         * {@link #DEFAULT_AUTHORISATION_SERVICE_SUPPLIER} otherwise.\n         */\n        public static AuthorisationServiceSupplier authorisationServiceSupplier()\n        {\n            final String supplierClassName = System.getProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n            if (Strings.isEmpty(supplierClassName))\n            {\n                return DEFAULT_AUTHORISATION_SERVICE_SUPPLIER;\n            }\n            else if (AuthorisationService.DENY_ALL_NAME.equals(supplierClassName))\n            {\n                return () -> AuthorisationService.DENY_ALL;\n            }\n            else if (AuthorisationService.ALLOW_ALL_NAME.equals(supplierClassName))\n            {\n                fallbackLogger().println(\"Warning: Cluster authorisation service set to allow all requests\");\n                return () -> AuthorisationService.ALLOW_ALL;\n            }\n\n            try\n            {\n                return (AuthorisationServiceSupplier)Class.forName(supplierClassName).getConstructor().newInstance();\n            }\n            catch (final Exception ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n                return null;\n            }\n        }\n\n        /**\n         * Fully qualified class name of the {@link io.aeron.archive.checksum.Checksum} implementation to use during\n         * recording to compute checksums. Non-empty value means that checksum is enabled for recording.\n         *\n         * @return class that implements {@link io.aeron.archive.checksum.Checksum} interface\n         * @see Configuration#RECORD_CHECKSUM_PROP_NAME\n         */\n        public static String recordChecksum()\n        {\n            return getProperty(RECORD_CHECKSUM_PROP_NAME);\n        }\n\n        /**\n         * Fully qualified class name of the {@link io.aeron.archive.checksum.Checksum} implementation to use during\n         * replay for the checksum. Non-empty value means that checksum is enabled for replay.\n         *\n         * @return class that implements {@link io.aeron.archive.checksum.Checksum} interface\n         * @see Configuration#REPLAY_CHECKSUM_PROP_NAME\n         */\n        public static String replayChecksum()\n        {\n            return getProperty(REPLAY_CHECKSUM_PROP_NAME);\n        }\n\n        /**\n         * Should the network (UDP) control channel be enabled.\n         *\n         * @return {@code true} if the network control channel to be enabled.\n         * @see #CONTROL_CHANNEL_ENABLED_PROP_NAME\n         */\n        public static boolean controlChannelEnabled()\n        {\n            return \"true\".equals(System.getProperty(CONTROL_CHANNEL_ENABLED_PROP_NAME, \"true\"));\n        }\n\n        /**\n         * Return configured id for the Archive.\n         *\n         * @return id for the Archive or {@link Aeron#NULL_VALUE}.\n         * @see #ARCHIVE_ID_PROP_NAME\n         */\n        public static long archiveId()\n        {\n            final String prop = getProperty(Configuration.ARCHIVE_ID_PROP_NAME);\n            if (!Strings.isEmpty(prop))\n            {\n                return AsciiEncoding.parseLongAscii(prop, 0, prop.length());\n            }\n            return NULL_VALUE;\n        }\n    }\n\n    /**\n     * Overrides for the defaults and system properties.\n     * <p>\n     * The context will be owned by {@link ArchiveConductor} after a successful\n     * {@link Archive#launch(Context)} and closed via {@link Archive#close()}.\n     */\n    public static final class Context implements Cloneable\n    {\n        private static final VarHandle IS_CONCLUDED_VH;\n\n        static\n        {\n            try\n            {\n                IS_CONCLUDED_VH = MethodHandles.lookup().findVarHandle(Context.class, \"isConcluded\", boolean.class);\n            }\n            catch (final ReflectiveOperationException ex)\n            {\n                throw new ExceptionInInitializerError(ex);\n            }\n        }\n\n        private volatile boolean isConcluded;\n        private boolean deleteArchiveOnStart = Configuration.deleteArchiveOnStart();\n        private boolean ownsAeronClient = false;\n        private String aeronDirectoryName = CommonContext.getAeronDirectoryName();\n        private Aeron aeron;\n        private File archiveDir;\n        private File markFileDir;\n        private String archiveDirectoryName = Configuration.archiveDirName();\n        private FileChannel archiveDirChannel;\n        private FileStore archiveFileStore;\n        private Catalog catalog;\n        private ArchiveMarkFile markFile;\n        private AeronArchive.Context archiveClientContext;\n        private AgentInvoker mediaDriverAgentInvoker;\n\n        private boolean controlChannelEnabled = Configuration.controlChannelEnabled();\n        private String controlChannel = AeronArchive.Configuration.controlChannel();\n        private int controlStreamId = AeronArchive.Configuration.controlStreamId();\n        private String localControlChannel = AeronArchive.Configuration.localControlChannel();\n        private int localControlStreamId = AeronArchive.Configuration.localControlStreamId();\n        private boolean controlTermBufferSparse = AeronArchive.Configuration.controlTermBufferSparse();\n        private int controlTermBufferLength = AeronArchive.Configuration.controlTermBufferLength();\n        private int controlMtuLength = AeronArchive.Configuration.controlMtuLength();\n        private String recordingEventsChannel = AeronArchive.Configuration.recordingEventsChannel();\n        private int recordingEventsStreamId = AeronArchive.Configuration.recordingEventsStreamId();\n        private boolean recordingEventsEnabled = AeronArchive.Configuration.recordingEventsEnabled();\n        private String replicationChannel = Configuration.replicationChannel();\n\n        private long connectTimeoutNs = Configuration.connectTimeoutNs();\n        private long sessionLivenessCheckIntervalNs =\n            getDurationInNanos(SESSION_LIVENESS_CHECK_INTERVAL_PROP_NAME, SESSION_LIVENESS_CHECK_INTERVAL_DEFAULT_NS);\n        private long replayLingerTimeoutNs = Configuration.replayLingerTimeoutNs();\n        private long conductorCycleThresholdNs = Configuration.conductorCycleThresholdNs();\n        private long recorderCycleThresholdNs = Configuration.recorderCycleThresholdNs();\n        private long replayerCycleThresholdNs = Configuration.replayerCycleThresholdNs();\n        private long catalogCapacity = Configuration.catalogCapacity();\n        private long lowStorageSpaceThreshold = Configuration.lowStorageSpaceThreshold();\n        private int segmentFileLength = Configuration.segmentFileLength();\n        private int fileSyncLevel = Configuration.fileSyncLevel();\n        private int catalogFileSyncLevel = Configuration.catalogFileSyncLevel();\n        private int maxConcurrentRecordings = Configuration.maxConcurrentRecordings();\n        private int maxConcurrentReplays = Configuration.maxConcurrentReplays();\n        private int fileIoMaxLength = Configuration.fileIoMaxLength();\n        private long archiveId = Configuration.archiveId();\n        private ArchiveThreadingMode threadingMode = Configuration.threadingMode();\n        private ThreadFactory threadFactory;\n        private ThreadFactory recorderThreadFactory;\n        private ThreadFactory replayerThreadFactory;\n        private CountDownLatch abortLatch;\n\n        private Supplier<IdleStrategy> idleStrategySupplier;\n        private Supplier<IdleStrategy> replayerIdleStrategySupplier;\n        private Supplier<IdleStrategy> recorderIdleStrategySupplier;\n        private EpochClock epochClock;\n        private NanoClock nanoClock;\n        private AuthenticatorSupplier authenticatorSupplier;\n        private AuthorisationServiceSupplier authorisationServiceSupplier;\n        private Counter controlSessionsCounter;\n        private Counter recordingSessionCounter;\n        private Counter replaySessionCounter;\n        private int errorBufferLength = Configuration.errorBufferLength();\n        private ErrorHandler errorHandler;\n        private AtomicCounter errorCounter;\n        private CountedErrorHandler countedErrorHandler;\n\n        private Checksum recordChecksum;\n        private Checksum replayChecksum;\n\n        private UnsafeBuffer dataBuffer;\n        private UnsafeBuffer replayBuffer;\n        private UnsafeBuffer recordChecksumBuffer;\n        private DutyCycleTracker conductorDutyCycleTracker;\n        private DutyCycleTracker recorderDutyCycleTracker;\n        private DutyCycleTracker replayerDutyCycleTracker;\n\n        private Counter totalWriteBytesCounter;\n        private Counter totalWriteTimeCounter;\n        private Counter maxWriteTimeCounter;\n        private Counter totalReadBytesCounter;\n        private Counter totalReadTimeCounter;\n        private Counter maxReadTimeCounter;\n        private String secureRandomAlgorithm = CommonContext.getSecureRandomAlgorithm();\n\n        /**\n         * Construct a Context using default values and loading from system properties.\n         */\n        public Context()\n        {\n        }\n\n        /**\n         * Perform a shallow copy of the object.\n         *\n         * @return a shallow copy of the object.\n         */\n        public Context clone()\n        {\n            try\n            {\n                return (Context)super.clone();\n            }\n            catch (final CloneNotSupportedException ex)\n            {\n                throw new RuntimeException(ex);\n            }\n        }\n\n        /**\n         * Conclude the configuration parameters by resolving dependencies and null values to use defaults.\n         */\n        @SuppressWarnings(\"MethodLength\")\n        public void conclude()\n        {\n            if ((boolean)IS_CONCLUDED_VH.getAndSet(this, true))\n            {\n                throw new ConcurrentConcludeException();\n            }\n\n            if (catalogFileSyncLevel < fileSyncLevel)\n            {\n                throw new ConfigurationException(\n                    \"catalogFileSyncLevel \" + catalogFileSyncLevel + \" < fileSyncLevel \" + fileSyncLevel);\n            }\n\n            if (fileIoMaxLength < TERM_MIN_LENGTH || !BitUtil.isPowerOfTwo(fileIoMaxLength))\n            {\n                throw new ConfigurationException(\"invalid fileIoMaxLength=\" + fileIoMaxLength);\n            }\n\n            io.aeron.driver.Configuration.validateMtuLength(controlMtuLength);\n            checkTermLength(controlTermBufferLength);\n\n            if (controlChannelEnabled)\n            {\n                if (null == controlChannel)\n                {\n                    throw new ConfigurationException(\"Archive.Context.controlChannel must be set\");\n                }\n\n                if (!controlChannel.startsWith(CommonContext.UDP_CHANNEL))\n                {\n                    throw new ConfigurationException(\n                        \"Archive.Context.controlChannel must be UDP media: uri=\" + controlChannel);\n                }\n            }\n\n            if (!localControlChannel.startsWith(CommonContext.IPC_CHANNEL))\n            {\n                throw new ConfigurationException(\"local control channel must be IPC media: uri=\" + localControlChannel);\n            }\n\n            if (null == replicationChannel)\n            {\n                throw new ConfigurationException(\"Archive.Context.replicationChannel must be set\");\n            }\n\n            if (recordingEventsEnabled() && null == recordingEventsChannel())\n            {\n                throw new ConfigurationException(\n                    \"Archive.Context.recordingEventsChannel must be set if \" +\n                    \"Archive.Context.recordingEventsEnabled is true\");\n            }\n\n            if (null != mediaDriverAgentInvoker && ArchiveThreadingMode.INVOKER != threadingMode)\n            {\n                throw new ConfigurationException(\n                    \"Archive.Context.threadingMode(ArchiveThreadingMode.INVOKER) must be set if \" +\n                    \"Archive.Context.mediaDriverAgentInvoker is set\");\n            }\n\n            if (null == archiveDir)\n            {\n                archiveDir = new File(archiveDirectoryName);\n            }\n\n            if (null == markFileDir)\n            {\n                final String markFileDirPath = Configuration.markFileDir();\n                markFileDir = !Strings.isEmpty(markFileDirPath) ? new File(markFileDirPath) : archiveDir;\n            }\n\n            try\n            {\n                archiveDir = archiveDir.getCanonicalFile();\n                archiveDirectoryName = archiveDir.getAbsolutePath();\n                markFileDir = markFileDir.getCanonicalFile();\n            }\n            catch (final IOException e)\n            {\n                throw new UncheckedIOException(e);\n            }\n\n            if (deleteArchiveOnStart)\n            {\n                IoUtil.delete(archiveDir, false);\n            }\n\n            IoUtil.ensureDirectoryExists(archiveDir, \"archive\");\n            IoUtil.ensureDirectoryExists(markFileDir, \"mark file\");\n\n            archiveDirChannel = channelForDirectorySync(archiveDir, catalogFileSyncLevel);\n\n            if (null == archiveFileStore)\n            {\n                try\n                {\n                    archiveFileStore = Files.getFileStore(archiveDir.toPath());\n                }\n                catch (final IOException ex)\n                {\n                    throw new UncheckedIOException(ex);\n                }\n            }\n\n            if (null == epochClock)\n            {\n                epochClock = SystemEpochClock.INSTANCE;\n            }\n\n            if (null == nanoClock)\n            {\n                nanoClock = SystemNanoClock.INSTANCE;\n            }\n\n            if (null != aeron)\n            {\n                aeronDirectoryName = aeron.context().aeronDirectoryName();\n            }\n\n            concludeArchiveId();\n\n            if (null == markFile)\n            {\n                if (errorBufferLength < ERROR_BUFFER_LENGTH_DEFAULT ||\n                    errorBufferLength > Integer.MAX_VALUE - ArchiveMarkFile.HEADER_LENGTH)\n                {\n                    throw new ConfigurationException(\"invalid errorBufferLength=\" + errorBufferLength);\n                }\n\n                markFile = new ArchiveMarkFile(this);\n            }\n\n            MarkFile.ensureMarkFileLink(\n                archiveDir,\n                new File(markFile.parentDirectory(), ArchiveMarkFile.FILENAME),\n                ArchiveMarkFile.LINK_FILENAME);\n\n            errorHandler = CommonContext.setupErrorHandler(\n                errorHandler, new DistinctErrorLog(markFile.errorBuffer(), epochClock, US_ASCII));\n\n            final ExpandableArrayBuffer tempBuffer = new ExpandableArrayBuffer();\n\n            final String clientName = \"archive archiveId=\" + archiveId;\n            if (null == aeron)\n            {\n                ownsAeronClient = true;\n\n                aeron = Aeron.connect(\n                    new Aeron.Context()\n                        .aeronDirectoryName(aeronDirectoryName)\n                        .epochClock(epochClock)\n                        .nanoClock(nanoClock)\n                        .errorHandler(errorHandler)\n                        .driverAgentInvoker(mediaDriverAgentInvoker)\n                        .useConductorAgentInvoker(true)\n                        .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE)\n                        .awaitingIdleStrategy(YieldingIdleStrategy.INSTANCE)\n                        .clientLock(NoOpLock.INSTANCE)\n                        .clientName(clientName));\n\n                if (null == errorCounter)\n                {\n                    if (NULL_VALUE !=\n                        ArchiveCounters.find(aeron.countersReader(), ARCHIVE_ERROR_COUNT_TYPE_ID, archiveId))\n                    {\n                        throw new ArchiveException(\"found existing archive for archiveId=\" + archiveId);\n                    }\n                    errorCounter = ArchiveCounters.allocateErrorCounter(aeron, tempBuffer, archiveId);\n                }\n            }\n            else if (!aeron.context().useConductorAgentInvoker())\n            {\n                throw new ArchiveException(\n                    \"Aeron client instance must set Aeron.Context.useConductorInvoker(true)\");\n            }\n\n            if (!(aeron.context().subscriberErrorHandler() instanceof RethrowingErrorHandler))\n            {\n                throw new ArchiveException(\"Aeron client must use a RethrowingErrorHandler\");\n            }\n\n            Objects.requireNonNull(errorCounter, \"Error counter must be supplied if aeron client is\");\n\n            if (null == countedErrorHandler)\n            {\n                countedErrorHandler = new CountedErrorHandler(errorHandler, errorCounter);\n            }\n\n            if (null == threadFactory)\n            {\n                threadFactory = Thread::new;\n            }\n\n            if (null == recorderThreadFactory)\n            {\n                recorderThreadFactory = threadFactory;\n            }\n\n            if (null == replayerThreadFactory)\n            {\n                replayerThreadFactory = threadFactory;\n            }\n\n            if (null == idleStrategySupplier)\n            {\n                idleStrategySupplier = Configuration.idleStrategySupplier(null);\n            }\n\n            if (null == conductorDutyCycleTracker)\n            {\n                conductorDutyCycleTracker = new DutyCycleStallTracker(\n                    ArchiveCounters.allocate(\n                        aeron,\n                        tempBuffer,\n                        AeronCounters.ARCHIVE_MAX_CYCLE_TIME_TYPE_ID,\n                        \"archive-conductor max cycle time in ns: \" + threadingMode.name(),\n                        archiveId),\n                    ArchiveCounters.allocate(\n                        aeron,\n                        tempBuffer,\n                        AeronCounters.ARCHIVE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID,\n                        \"archive-conductor work cycle time exceeded count: threshold=\" +\n                            SystemUtil.formatDuration(conductorCycleThresholdNs) + \" \" + threadingMode.name(),\n                        archiveId),\n                    conductorCycleThresholdNs);\n            }\n\n            if (DEDICATED == threadingMode)\n            {\n                if (null == recorderIdleStrategySupplier)\n                {\n                    recorderIdleStrategySupplier = Configuration.recorderIdleStrategySupplier(null);\n                    if (null == recorderIdleStrategySupplier)\n                    {\n                        recorderIdleStrategySupplier = idleStrategySupplier;\n                    }\n                }\n\n                if (null == replayerIdleStrategySupplier)\n                {\n                    replayerIdleStrategySupplier = Configuration.replayerIdleStrategySupplier(null);\n                    if (null == replayerIdleStrategySupplier)\n                    {\n                        replayerIdleStrategySupplier = idleStrategySupplier;\n                    }\n                }\n\n                if (null == recorderDutyCycleTracker)\n                {\n                    recorderDutyCycleTracker = new DutyCycleStallTracker(\n                        ArchiveCounters.allocate(\n                            aeron,\n                            tempBuffer,\n                            AeronCounters.ARCHIVE_MAX_CYCLE_TIME_TYPE_ID,\n                            \"archive-recorder max cycle time in ns: \" + threadingMode.name(),\n                            archiveId),\n                        ArchiveCounters.allocate(\n                            aeron,\n                            tempBuffer,\n                            AeronCounters.ARCHIVE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID,\n                            \"archive-recorder work cycle time exceeded count: threshold=\" +\n                                SystemUtil.formatDuration(recorderCycleThresholdNs) + \" \" + threadingMode.name(),\n                            archiveId),\n                        recorderCycleThresholdNs);\n                }\n\n                if (null == replayerDutyCycleTracker)\n                {\n                    replayerDutyCycleTracker = new DutyCycleStallTracker(\n                        ArchiveCounters.allocate(\n                            aeron,\n                            tempBuffer,\n                            AeronCounters.ARCHIVE_MAX_CYCLE_TIME_TYPE_ID,\n                            \"archive-replayer max cycle time in ns: \" + threadingMode.name(),\n                            archiveId),\n                        ArchiveCounters.allocate(\n                            aeron,\n                            tempBuffer,\n                            AeronCounters.ARCHIVE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID,\n                            \"archive-replayer work cycle time exceeded count: threshold=\" +\n                                SystemUtil.formatDuration(replayerCycleThresholdNs) + \" \" + threadingMode.name(),\n                            archiveId),\n                        replayerCycleThresholdNs);\n                }\n            }\n\n            if (!isPowerOfTwo(segmentFileLength))\n            {\n                throw new ArchiveException(\"segment file length not a power of 2: \" + segmentFileLength);\n            }\n            else if (segmentFileLength < TERM_MIN_LENGTH || segmentFileLength > TERM_MAX_LENGTH)\n            {\n                throw new ArchiveException(\"segment file length not in valid range: \" + segmentFileLength);\n            }\n\n            if (null == authenticatorSupplier)\n            {\n                authenticatorSupplier = Configuration.authenticatorSupplier();\n            }\n\n            if (null == authorisationServiceSupplier)\n            {\n                authorisationServiceSupplier = Configuration.authorisationServiceSupplier();\n            }\n\n            concludeRecordChecksum();\n            concludeReplayChecksum();\n\n            if (null == catalog)\n            {\n                catalog = new Catalog(\n                    archiveDir,\n                    archiveDirChannel,\n                    catalogFileSyncLevel,\n                    catalogCapacity,\n                    epochClock,\n                    recordChecksum,\n                    null != recordChecksum ? recordChecksumBuffer() : dataBuffer());\n            }\n\n            if (null == archiveClientContext)\n            {\n                archiveClientContext = new AeronArchive.Context();\n            }\n\n            if (null == archiveClientContext.controlResponseChannel())\n            {\n                if (controlChannelEnabled)\n                {\n                    final ChannelUri controlChannelUri = ChannelUri.parse(controlChannel);\n                    final String endpoint = controlChannelUri.get(ENDPOINT_PARAM_NAME);\n                    final int separatorIndex;\n\n                    if (null == endpoint || -1 == (separatorIndex = endpoint.lastIndexOf(':')))\n                    {\n                        throw new ConfigurationException(\n                            \"Unable to derive Archive.Context.archiveClientContext.controlResponseChannel as \" +\n                                \"Archive.Context.controlChannel.endpoint=\" + endpoint +\n                                \" and is not in the <host>:<port> format\");\n                    }\n\n                    final String responseEndpoint = endpoint.substring(0, separatorIndex) + \":0\";\n                    final String responseChannel = new ChannelUriStringBuilder()\n                        .media(\"udp\")\n                        .endpoint(responseEndpoint)\n                        .build();\n\n                    archiveClientContext.controlResponseChannel(responseChannel);\n                }\n                else\n                {\n                    throw new ConfigurationException(\n                        \"Archive.Context.archiveClientContext.controlResponseChannel must be set if \" +\n                            \"Archive.Context.controlChannelEnabled is false\"\n                    );\n                }\n            }\n\n            archiveClientContext\n                .aeron(aeron)\n                .lock(NoOpLock.INSTANCE)\n                .errorHandler(errorHandler)\n                .clientName(clientName);\n\n            if (null == controlSessionsCounter)\n            {\n                controlSessionsCounter = ArchiveCounters.allocate(\n                    aeron, tempBuffer, ARCHIVE_CONTROL_SESSIONS_TYPE_ID, \"Archive Control Sessions\", archiveId);\n            }\n            validateCounterTypeId(aeron, controlSessionsCounter, ARCHIVE_CONTROL_SESSIONS_TYPE_ID);\n\n            if (null == recordingSessionCounter)\n            {\n                recordingSessionCounter = ArchiveCounters.allocate(\n                    aeron,\n                    tempBuffer,\n                    ARCHIVE_RECORDING_SESSION_COUNT_TYPE_ID,\n                    \"Archive Recording Sessions\",\n                    archiveId);\n            }\n            validateCounterTypeId(aeron, recordingSessionCounter, ARCHIVE_RECORDING_SESSION_COUNT_TYPE_ID);\n\n            if (null == replaySessionCounter)\n            {\n                replaySessionCounter = ArchiveCounters.allocate(\n                    aeron,\n                    tempBuffer,\n                    ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID,\n                    \"Archive Replay Sessions\",\n                    archiveId);\n            }\n            validateCounterTypeId(aeron, replaySessionCounter, ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID);\n\n            if (null == maxWriteTimeCounter)\n            {\n                final int counterId = ArchiveCounters.find(\n                    aeron.countersReader(), ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID, archiveId);\n                if (NULL_VALUE != counterId)\n                {\n                    throw new ConfigurationException(\n                        \"existing max write time counter detected for archiveId=\" + archiveId);\n                }\n\n                maxWriteTimeCounter = ArchiveCounters.allocate(\n                    aeron,\n                    tempBuffer,\n                    ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID,\n                    \"archive-recorder max write time in ns\",\n                    archiveId);\n            }\n            validateCounterTypeId(aeron, maxWriteTimeCounter, ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID);\n\n            if (null == totalWriteBytesCounter)\n            {\n                totalWriteBytesCounter = ArchiveCounters.allocate(\n                    aeron,\n                    tempBuffer,\n                    ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID,\n                    \"archive-recorder total write bytes\",\n                    archiveId);\n            }\n            validateCounterTypeId(aeron, totalWriteBytesCounter, ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID);\n\n            if (null == totalWriteTimeCounter)\n            {\n                totalWriteTimeCounter = ArchiveCounters.allocate(\n                    aeron,\n                    tempBuffer,\n                    ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID,\n                    \"archive-recorder total write time in ns\",\n                    archiveId);\n            }\n            validateCounterTypeId(aeron, totalWriteTimeCounter, ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID);\n\n            if (null == maxReadTimeCounter)\n            {\n                maxReadTimeCounter = ArchiveCounters.allocate(\n                    aeron,\n                    tempBuffer,\n                    ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID,\n                    \"archive-replayer max read time in ns\",\n                    archiveId);\n            }\n            validateCounterTypeId(aeron, maxReadTimeCounter, ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID);\n\n            if (null == totalReadBytesCounter)\n            {\n                totalReadBytesCounter = ArchiveCounters.allocate(\n                    aeron,\n                    tempBuffer,\n                    ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID,\n                    \"archive-replayer total read bytes\",\n                    archiveId);\n            }\n            validateCounterTypeId(aeron, totalReadBytesCounter, ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID);\n\n            if (null == totalReadTimeCounter)\n            {\n                totalReadTimeCounter = ArchiveCounters.allocate(\n                    aeron,\n                    tempBuffer,\n                    ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID,\n                    \"archive-replayer total read time in ns\",\n                    archiveId);\n            }\n            validateCounterTypeId(aeron, totalReadTimeCounter, ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID);\n\n            int expectedCount = DEDICATED == threadingMode ? 2 : 0;\n            expectedCount += aeron.conductorAgentInvoker() == null ? 1 : 0;\n            abortLatch = new CountDownLatch(expectedCount);\n\n            markFile.signalReady(epochClock.time());\n\n            if (CommonContext.shouldPrintConfigurationOnStart())\n            {\n                System.out.println(this);\n            }\n        }\n\n        /**\n         * Has the context had the {@link #conclude()} method called.\n         *\n         * @return true of the {@link #conclude()} method has been called.\n         */\n        public boolean isConcluded()\n        {\n            return isConcluded;\n        }\n\n        /**\n         * Should an existing archive be deleted on start. Useful only for testing.\n         *\n         * @param deleteArchiveOnStart true if an existing archive should be deleted on startup.\n         * @return this for a fluent API.\n         */\n        public Context deleteArchiveOnStart(final boolean deleteArchiveOnStart)\n        {\n            this.deleteArchiveOnStart = deleteArchiveOnStart;\n            return this;\n        }\n\n        /**\n         * Should an existing archive be deleted on start. Useful only for testing.\n         *\n         * @return {@code true} if an existing archive should be deleted on start up.\n         */\n        @Config(id = \"ARCHIVE_DIR_DELETE_ON_START\")\n        public boolean deleteArchiveOnStart()\n        {\n            return deleteArchiveOnStart;\n        }\n\n        /**\n         * Set the directory name to be used for the archive to store recordings and the {@link Catalog}.\n         * This name is used if {@link #archiveDir(File)} is not set.\n         *\n         * @param archiveDirectoryName to store recordings and the {@link Catalog}.\n         * @return this for a fluent API.\n         * @see Configuration#ARCHIVE_DIR_PROP_NAME\n         */\n        public Context archiveDirectoryName(final String archiveDirectoryName)\n        {\n            this.archiveDirectoryName = archiveDirectoryName;\n            return this;\n        }\n\n        /**\n         * Get the directory name to be used to store recordings and the {@link Catalog}.\n         *\n         * @return the directory name to be used for the archive to store recordings and the {@link Catalog}.\n         */\n        @Config(id = \"ARCHIVE_DIR\")\n        public String archiveDirectoryName()\n        {\n            return archiveDirectoryName;\n        }\n\n        /**\n         * Get the directory in which the Archive will store recordings and the {@link Catalog}.\n         *\n         * @return the directory in which the Archive will store recordings and the {@link Catalog}.\n         */\n        public File archiveDir()\n        {\n            return archiveDir;\n        }\n\n        /**\n         * Set the directory in which the Archive will store recordings and the {@link Catalog}.\n         *\n         * @param archiveDir the directory in which the Archive will store recordings and the {@link Catalog}.\n         * @return this for a fluent API.\n         */\n        public Context archiveDir(final File archiveDir)\n        {\n            this.archiveDir = archiveDir;\n            return this;\n        }\n\n        /**\n         * Get the directory in which the Archive will store mark file (i.e. {@code archive-mark.dat}). It defaults to\n         * {@link #archiveDir()} if it is not set explicitly via the\n         * {@link Configuration#MARK_FILE_DIR_PROP_NAME}.\n         *\n         * @return the directory in which the Archive will store mark file (i.e. {@code archive-mark.dat}).\n         * @see Configuration#MARK_FILE_DIR_PROP_NAME\n         * @see #archiveDir()\n         */\n        @Config\n        public File markFileDir()\n        {\n            return markFileDir;\n        }\n\n        /**\n         * Set the directory in which the Archive will store mark file (i.e. {@code archive-mark.dat}).\n         *\n         * @param markFileDir the directory in which the Archive will store mark file (i.e. {@code archive-mark.dat}).\n         * @return this for a fluent API.\n         */\n        public Context markFileDir(final File markFileDir)\n        {\n            this.markFileDir = markFileDir;\n            return this;\n        }\n\n        /**\n         * Get the {@link FileStore} where the archive will record streams.\n         *\n         * @return the {@link FileStore} where the archive will record streams.\n         */\n        public FileStore archiveFileStore()\n        {\n            return archiveFileStore;\n        }\n\n        /**\n         * Set the {@link FileStore} where the archive will record streams. This should only be used for testing.\n         *\n         * @param fileStore where the archive will record streams.\n         * @return this for a fluent API.\n         */\n        public Context archiveFileStore(final FileStore fileStore)\n        {\n            this.archiveFileStore = fileStore;\n            return this;\n        }\n\n        /**\n         * Get the {@link FileChannel} for the directory in which the Archive will store recordings and the\n         * {@link Catalog}. This can be used for sync'ing the directory.\n         *\n         * @return the directory in which the Archive will store recordings and the {@link Catalog}.\n         */\n        public FileChannel archiveDirChannel()\n        {\n            return archiveDirChannel;\n        }\n\n        Context archiveDirChannel(final FileChannel archiveDirChannel)\n        {\n            this.archiveDirChannel = archiveDirChannel;\n            return this;\n        }\n\n        /**\n         * Set the {@link io.aeron.archive.client.AeronArchive.Context} that should be used for communicating\n         * with a remote archive for replication.\n         *\n         * @param archiveContext that should be used for communicating with a remote Archive.\n         * @return this for a fluent API.\n         */\n        public Context archiveClientContext(final AeronArchive.Context archiveContext)\n        {\n            this.archiveClientContext = archiveContext;\n            return this;\n        }\n\n        /**\n         * Get the {@link io.aeron.archive.client.AeronArchive.Context} that should be used for communicating\n         * with a remote archive for replication.\n         *\n         * @return the {@link io.aeron.archive.client.AeronArchive.Context} that should be used for communicating\n         * with a remote archive for replication.\n         */\n        public AeronArchive.Context archiveClientContext()\n        {\n            return archiveClientContext;\n        }\n\n        /**\n         * Should the UDP control channel be enabled.\n         *\n         * @return {@code true} if the UDP control channel should be enabled.\n         * @see Configuration#CONTROL_CHANNEL_ENABLED_PROP_NAME\n         */\n        @Config\n        public boolean controlChannelEnabled()\n        {\n            return controlChannelEnabled;\n        }\n\n        /**\n         * Set if the UDP control channel should be enabled.\n         *\n         * @param controlChannelEnabled indication of if the network control channel should be enabled.\n         * @return this for a fluent API.\n         * @see Configuration#CONTROL_CHANNEL_ENABLED_PROP_NAME\n         */\n        public Context controlChannelEnabled(final boolean controlChannelEnabled)\n        {\n            this.controlChannelEnabled = controlChannelEnabled;\n            return this;\n        }\n\n        /**\n         * Get the channel URI on which the control request subscription will listen.\n         *\n         * @return the channel URI on which the control request subscription will listen.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_CHANNEL_PROP_NAME\n         */\n        public String controlChannel()\n        {\n            return controlChannel;\n        }\n\n        /**\n         * Set the channel URI on which the control request subscription will listen.\n         *\n         * @param controlChannel channel URI on which the control request subscription will listen.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_CHANNEL_PROP_NAME\n         */\n        public Context controlChannel(final String controlChannel)\n        {\n            this.controlChannel = controlChannel;\n            return this;\n        }\n\n        /**\n         * Get the stream id on which the control request subscription will listen.\n         *\n         * @return the stream id on which the control request subscription will listen.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_STREAM_ID_PROP_NAME\n         */\n        public int controlStreamId()\n        {\n            return controlStreamId;\n        }\n\n        /**\n         * Set the stream id on which the control request subscription will listen.\n         *\n         * @param controlStreamId stream id on which the control request subscription will listen.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_STREAM_ID_PROP_NAME\n         */\n        public Context controlStreamId(final int controlStreamId)\n        {\n            this.controlStreamId = controlStreamId;\n            return this;\n        }\n\n        /**\n         * Get the driver local channel URI on which the control request subscription will listen.\n         *\n         * @return the channel URI on which the control request subscription will listen.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#LOCAL_CONTROL_CHANNEL_PROP_NAME\n         */\n        public String localControlChannel()\n        {\n            return localControlChannel;\n        }\n\n        /**\n         * Set the driver local channel URI on which the control request subscription will listen.\n         *\n         * @param controlChannel channel URI on which the control request subscription will listen.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#LOCAL_CONTROL_CHANNEL_PROP_NAME\n         */\n        public Context localControlChannel(final String controlChannel)\n        {\n            this.localControlChannel = controlChannel;\n            return this;\n        }\n\n        /**\n         * Get the local stream id on which the control request subscription will listen.\n         *\n         * @return the stream id on which the control request subscription will listen.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#LOCAL_CONTROL_STREAM_ID_PROP_NAME\n         */\n        public int localControlStreamId()\n        {\n            return localControlStreamId;\n        }\n\n        /**\n         * Should the control streams use sparse file term buffers.\n         *\n         * @param controlTermBufferSparse for the control stream.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_TERM_BUFFER_SPARSE_PROP_NAME\n         */\n        public Context controlTermBufferSparse(final boolean controlTermBufferSparse)\n        {\n            this.controlTermBufferSparse = controlTermBufferSparse;\n            return this;\n        }\n\n        /**\n         * Should the control streams use sparse file term buffers.\n         *\n         * @return {@code true} if the control stream should use sparse file term buffers.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_TERM_BUFFER_SPARSE_PROP_NAME\n         */\n        public boolean controlTermBufferSparse()\n        {\n            return controlTermBufferSparse;\n        }\n\n        /**\n         * Set the term buffer length for the control streams.\n         *\n         * @param controlTermBufferLength for the control streams.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_TERM_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context controlTermBufferLength(final int controlTermBufferLength)\n        {\n            this.controlTermBufferLength = controlTermBufferLength;\n            return this;\n        }\n\n        /**\n         * Get the term buffer length for the control streams.\n         *\n         * @return the term buffer length for the control streams.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_TERM_BUFFER_LENGTH_PROP_NAME\n         */\n        public int controlTermBufferLength()\n        {\n            return controlTermBufferLength;\n        }\n\n        /**\n         * Set the MTU length for the control streams.\n         *\n         * @param controlMtuLength for the control streams.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_MTU_LENGTH_PROP_NAME\n         */\n        public Context controlMtuLength(final int controlMtuLength)\n        {\n            this.controlMtuLength = controlMtuLength;\n            return this;\n        }\n\n        /**\n         * Get the MTU length for the control streams.\n         *\n         * @return the MTU length for the control streams.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#CONTROL_MTU_LENGTH_PROP_NAME\n         */\n        public int controlMtuLength()\n        {\n            return controlMtuLength;\n        }\n\n        /**\n         * Set the local stream id on which the control request subscription will listen.\n         *\n         * @param controlStreamId stream id on which the control request subscription will listen.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#LOCAL_CONTROL_STREAM_ID_PROP_NAME\n         */\n        public Context localControlStreamId(final int controlStreamId)\n        {\n            this.localControlStreamId = controlStreamId;\n            return this;\n        }\n\n        /**\n         * Get the channel URI on which the recording events publication will publish. Will be null if not configured.\n         *\n         * @return the channel URI on which the recording events publication will publish.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#RECORDING_EVENTS_CHANNEL_PROP_NAME\n         */\n        public String recordingEventsChannel()\n        {\n            return recordingEventsChannel;\n        }\n\n        /**\n         * Set the channel URI on which the recording events publication will publish.\n         * <p>\n         * To support dynamic subscribers then this can be set to multicast or MDC (Multi-Destination-Cast) if\n         * multicast cannot be supported for on the available the network infrastructure.\n         *\n         * @param recordingEventsChannel channel URI on which the recording events publication will publish.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#RECORDING_EVENTS_CHANNEL_PROP_NAME\n         * @see io.aeron.CommonContext#MDC_CONTROL_PARAM_NAME\n         */\n        public Context recordingEventsChannel(final String recordingEventsChannel)\n        {\n            this.recordingEventsChannel = recordingEventsChannel;\n            return this;\n        }\n\n        /**\n         * Get the stream id on which the recording events publication will publish.\n         *\n         * @return the stream id on which the recording events publication will publish.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#RECORDING_EVENTS_STREAM_ID_PROP_NAME\n         */\n        public int recordingEventsStreamId()\n        {\n            return recordingEventsStreamId;\n        }\n\n        /**\n         * Set the stream id on which the recording events publication will publish.\n         *\n         * @param recordingEventsStreamId stream id on which the recording events publication will publish.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#RECORDING_EVENTS_STREAM_ID_PROP_NAME\n         */\n        public Context recordingEventsStreamId(final int recordingEventsStreamId)\n        {\n            this.recordingEventsStreamId = recordingEventsStreamId;\n            return this;\n        }\n\n        /**\n         * Should the recording events channel be enabled.\n         *\n         * @return {@code true} if the recording events channel should be enabled.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#RECORDING_EVENTS_ENABLED_PROP_NAME\n         */\n        @Config\n        public boolean recordingEventsEnabled()\n        {\n            return recordingEventsEnabled;\n        }\n\n        /**\n         * Set if the recording events channel should be enabled.\n         *\n         * @param recordingEventsEnabled indication of if the recording events channel should be enabled.\n         * @return this for a fluent API.\n         * @see io.aeron.archive.client.AeronArchive.Configuration#RECORDING_EVENTS_ENABLED_PROP_NAME\n         */\n        public Context recordingEventsEnabled(final boolean recordingEventsEnabled)\n        {\n            this.recordingEventsEnabled = recordingEventsEnabled;\n            return this;\n        }\n\n        /**\n         * Get the channel URI for replicating stream from another archive as replays.\n         *\n         * @return the channel URI for replicating stream from another archive as replays.\n         * @see Archive.Configuration#REPLICATION_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String replicationChannel()\n        {\n            return replicationChannel;\n        }\n\n        /**\n         * The channel URI for replicating stream from another archive as replays.\n         *\n         * @param replicationChannel channel URI for replicating stream from another archive as replays.\n         * @return this for a fluent API.\n         * @see Archive.Configuration#REPLICATION_CHANNEL_PROP_NAME\n         */\n        public Context replicationChannel(final String replicationChannel)\n        {\n            this.replicationChannel = replicationChannel;\n            return this;\n        }\n\n        /**\n         * The timeout in nanoseconds to wait for connection to be established.\n         *\n         * @param connectTimeoutNs to wait for a connection to be established.\n         * @return this for a fluent API.\n         * @see Configuration#CONNECT_TIMEOUT_PROP_NAME\n         */\n        public Context connectTimeoutNs(final long connectTimeoutNs)\n        {\n            this.connectTimeoutNs = connectTimeoutNs;\n            return this;\n        }\n\n        /**\n         * The timeout in nanoseconds to wait for connection to be established.\n         *\n         * @return the message timeout in nanoseconds to wait for a connection to be established.\n         * @see Configuration#CONNECT_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long connectTimeoutNs()\n        {\n            return connectTimeoutNs;\n        }\n\n        /**\n         * The time internal in nanoseconds at which session liveness checks are performed.\n         *\n         * @param sessionLivenessCheckIntervalNs of a liveness check.\n         * @return this for a fluent API.\n         * @see Configuration#SESSION_LIVENESS_CHECK_INTERVAL_PROP_NAME\n         * @since 1.47.0\n         */\n        public Context sessionLivenessCheckIntervalNs(final long sessionLivenessCheckIntervalNs)\n        {\n            this.sessionLivenessCheckIntervalNs = sessionLivenessCheckIntervalNs;\n            return this;\n        }\n\n        /**\n         * The time internal in nanoseconds at which session liveness checks are performed.\n         *\n         * @return the time internal in nanoseconds at which session liveness checks are performed.\n         * @see Configuration#SESSION_LIVENESS_CHECK_INTERVAL_PROP_NAME\n         * @since 1.47.0\n         */\n        @Config\n        public long sessionLivenessCheckIntervalNs()\n        {\n            return sessionLivenessCheckIntervalNs;\n        }\n\n        /**\n         * The timeout in nanoseconds for or a replay publication to linger after draining.\n         *\n         * @param replayLingerTimeoutNs in nanoseconds for a replay publication to linger after draining.\n         * @return this for a fluent API.\n         * @see Configuration#REPLAY_LINGER_TIMEOUT_PROP_NAME\n         * @see io.aeron.driver.Configuration#PUBLICATION_LINGER_PROP_NAME\n         */\n        public Context replayLingerTimeoutNs(final long replayLingerTimeoutNs)\n        {\n            this.replayLingerTimeoutNs = replayLingerTimeoutNs;\n            return this;\n        }\n\n        /**\n         * The timeout in nanoseconds for a replay publication to linger after draining.\n         *\n         * @return the timeout in nanoseconds for a replay publication to linger after draining.\n         * @see Configuration#REPLAY_LINGER_TIMEOUT_PROP_NAME\n         * @see io.aeron.driver.Configuration#PUBLICATION_LINGER_PROP_NAME\n         */\n        @Config\n        public long replayLingerTimeoutNs()\n        {\n            return replayLingerTimeoutNs;\n        }\n\n        /**\n         * Set a threshold for the conductor work cycle time which when exceed it will increment the\n         * conductor cycle time exceeded count.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see io.aeron.archive.Archive.Configuration#CONDUCTOR_CYCLE_THRESHOLD_PROP_NAME\n         * @see io.aeron.archive.Archive.Configuration#CONDUCTOR_CYCLE_THRESHOLD_DEFAULT_NS\n         */\n        public Context conductorCycleThresholdNs(final long thresholdNs)\n        {\n            this.conductorCycleThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for the conductor work cycle time which when exceed it will increment the\n         * conductor cycle time exceeded count.\n         *\n         * @return threshold to track for the conductor work cycle time.\n         */\n        @Config\n        public long conductorCycleThresholdNs()\n        {\n            return conductorCycleThresholdNs;\n        }\n\n        /**\n         * Set a threshold for the recorder work cycle time which when exceed it will increment the\n         * recorder cycle time exceeded count.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see io.aeron.archive.Archive.Configuration#RECORDER_CYCLE_THRESHOLD_PROP_NAME\n         * @see io.aeron.archive.Archive.Configuration#RECORDER_CYCLE_THRESHOLD_DEFAULT_NS\n         */\n        public Context recorderCycleThresholdNs(final long thresholdNs)\n        {\n            this.recorderCycleThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for the recorder work cycle time which when exceed it will increment the\n         * recorder cycle time exceeded count.\n         *\n         * @return threshold to track for the recorder work cycle time.\n         */\n        @Config\n        public long recorderCycleThresholdNs()\n        {\n            return recorderCycleThresholdNs;\n        }\n\n        /**\n         * Set a threshold for the replayer work cycle time which when exceed it will increment the\n         * replayer cycle time exceeded count.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see io.aeron.archive.Archive.Configuration#REPLAYER_CYCLE_THRESHOLD_PROP_NAME\n         * @see io.aeron.archive.Archive.Configuration#REPLAYER_CYCLE_THRESHOLD_DEFAULT_NS\n         */\n        public Context replayerCycleThresholdNs(final long thresholdNs)\n        {\n            this.replayerCycleThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for the replayer work cycle time which when exceed it will increment the\n         * replayer cycle time exceeded count.\n         *\n         * @return threshold to track for the replayer work cycle time.\n         */\n        @Config\n        public long replayerCycleThresholdNs()\n        {\n            return replayerCycleThresholdNs;\n        }\n\n        /**\n         * Set the duty cycle tracker for the conductor.\n         *\n         * @param dutyCycleTracker for the conductor.\n         * @return this for a fluent API.\n         */\n        public Context conductorDutyCycleTracker(final DutyCycleTracker dutyCycleTracker)\n        {\n            this.conductorDutyCycleTracker = dutyCycleTracker;\n            return this;\n        }\n\n        /**\n         * The duty cycle tracker for the conductor.\n         *\n         * @return duty cycle tracker for the conductor.\n         */\n        public DutyCycleTracker conductorDutyCycleTracker()\n        {\n            return conductorDutyCycleTracker;\n        }\n\n        /**\n         * Set the duty cycle tracker for the recorder.\n         * NOTE: Only used in DEDICATED threading mode.\n         *\n         * @param dutyCycleTracker for the recorder.\n         * @return this for a fluent API.\n         */\n        public Context recorderDutyCycleTracker(final DutyCycleTracker dutyCycleTracker)\n        {\n            this.recorderDutyCycleTracker = dutyCycleTracker;\n            return this;\n        }\n\n        /**\n         * The duty cycle tracker for the recorder.\n         * NOTE: Only used in DEDICATED threading mode.\n         *\n         * @return duty cycle tracker for the recorder.\n         */\n        public DutyCycleTracker recorderDutyCycleTracker()\n        {\n            return recorderDutyCycleTracker;\n        }\n\n        /**\n         * Set the duty cycle tracker for the replayer.\n         * NOTE: Only used in DEDICATED threading mode.\n         *\n         * @param dutyCycleTracker for the replayer.\n         * @return this for a fluent API.\n         */\n        public Context replayerDutyCycleTracker(final DutyCycleTracker dutyCycleTracker)\n        {\n            this.replayerDutyCycleTracker = dutyCycleTracker;\n            return this;\n        }\n\n        /**\n         * The duty cycle tracker for the replayer.\n         * NOTE: Only used in DEDICATED threading mode.\n         *\n         * @return duty cycle tracker for the replayer.\n         */\n        public DutyCycleTracker replayerDutyCycleTracker()\n        {\n            return replayerDutyCycleTracker;\n        }\n\n        /**\n         * Provides an explicit {@link Checksum} for checksum computation during recording.\n         *\n         * @param recordChecksum to be used for recordings.\n         * @return this for a fluent API.\n         * @see Configuration#RECORD_CHECKSUM_PROP_NAME\n         */\n        public Context recordChecksum(final Checksum recordChecksum)\n        {\n            this.recordChecksum = recordChecksum;\n            return this;\n        }\n\n        /**\n         * Get the {@link Checksum} for checksum computation during recording.\n         *\n         * @return the {@link Checksum} instance for checksum computation during recording or\n         * {@code null} if no {@link Checksum} was configured.\n         * @see Configuration#RECORD_CHECKSUM_PROP_NAME\n         */\n        @Config\n        public Checksum recordChecksum()\n        {\n            return recordChecksum;\n        }\n\n        /**\n         * The {@link Checksum} for checksum computation during replay.\n         *\n         * @param replayChecksum to be used for replays.\n         * @return this for a fluent API.\n         * @see Configuration#REPLAY_CHECKSUM_PROP_NAME\n         */\n        public Context replayChecksum(final Checksum replayChecksum)\n        {\n            this.replayChecksum = replayChecksum;\n            return this;\n        }\n\n        /**\n         * Get the {@link Checksum} for checksum computation during replay.\n         *\n         * @return the {@link Checksum} instance for checksum computation during replay or\n         * {@code null} if no replay {@link Checksum} was configured.\n         * @see Configuration#REPLAY_CHECKSUM_PROP_NAME\n         */\n        @Config\n        public Checksum replayChecksum()\n        {\n            return replayChecksum;\n        }\n\n        /**\n         * Provides an {@link IdleStrategy} supplier for idling the conductor or composite {@link Agent}. Which is also\n         * the default for recorder and replayer {@link Agent}s.\n         *\n         * @param idleStrategySupplier supplier for idling the conductor.\n         * @return this for a fluent API.\n         */\n        public Context idleStrategySupplier(final Supplier<IdleStrategy> idleStrategySupplier)\n        {\n            this.idleStrategySupplier = idleStrategySupplier;\n            return this;\n        }\n\n        /**\n         * Get a new {@link IdleStrategy} for idling the conductor or composite {@link Agent}. Which is also\n         * the default for recorder and replayer {@link Agent}s.\n         *\n         * @return a new {@link IdleStrategy} for idling the conductor or composite {@link Agent}.\n         */\n        @Config(id = \"ARCHIVE_IDLE_STRATEGY\")\n        public IdleStrategy idleStrategy()\n        {\n            return idleStrategySupplier.get();\n        }\n\n        /**\n         * Provides an {@link IdleStrategy} supplier for idling the recorder {@link Agent}.\n         *\n         * @param idleStrategySupplier supplier for idling the conductor.\n         * @return this for a fluent API.\n         */\n        public Context recorderIdleStrategySupplier(final Supplier<IdleStrategy> idleStrategySupplier)\n        {\n            this.recorderIdleStrategySupplier = idleStrategySupplier;\n            return this;\n        }\n\n        /**\n         * Get a new {@link IdleStrategy} for idling the recorder {@link Agent}.\n         *\n         * @return a new {@link IdleStrategy} for idling the recorder {@link Agent}.\n         */\n        @Config(id = \"ARCHIVE_RECORDER_IDLE_STRATEGY\")\n        public IdleStrategy recorderIdleStrategy()\n        {\n            return recorderIdleStrategySupplier.get();\n        }\n\n        /**\n         * Provides an {@link IdleStrategy} supplier for idling the replayer {@link Agent}.\n         *\n         * @param idleStrategySupplier supplier for idling the replayer.\n         * @return this for a fluent API.\n         */\n        public Context replayerIdleStrategySupplier(final Supplier<IdleStrategy> idleStrategySupplier)\n        {\n            this.replayerIdleStrategySupplier = idleStrategySupplier;\n            return this;\n        }\n\n        /**\n         * Get a new {@link IdleStrategy} for idling the replayer {@link Agent}.\n         *\n         * @return a new {@link IdleStrategy} for idling the replayer {@link Agent}.\n         */\n        @Config(id = \"ARCHIVE_REPLAYER_IDLE_STRATEGY\")\n        public IdleStrategy replayerIdleStrategy()\n        {\n            return replayerIdleStrategySupplier.get();\n        }\n\n        /**\n         * Set the {@link EpochClock} to be used for tracking wall clock time.\n         *\n         * @param clock {@link EpochClock} to be used for tracking wall clock time.\n         * @return this for a fluent API.\n         */\n        public Context epochClock(final EpochClock clock)\n        {\n            this.epochClock = clock;\n            return this;\n        }\n\n        /**\n         * Get the {@link EpochClock} to used for tracking wall clock time.\n         *\n         * @return the {@link EpochClock} to used for tracking wall clock time.\n         */\n        public EpochClock epochClock()\n        {\n            return epochClock;\n        }\n\n        /**\n         * Set the {@link NanoClock} to be used for tracking wall clock time.\n         *\n         * @param clock {@link NanoClock} to be used for tracking wall clock time.\n         * @return this for a fluent API.\n         */\n        public Context nanoClock(final NanoClock clock)\n        {\n            this.nanoClock = clock;\n            return this;\n        }\n\n        /**\n         * Get the {@link NanoClock} to used for tracking wall clock time.\n         *\n         * @return the {@link NanoClock} to used for tracking wall clock time.\n         */\n        public NanoClock nanoClock()\n        {\n            return nanoClock;\n        }\n\n        /**\n         * Get the file length used for recording data segment files.\n         *\n         * @return the file length used for recording data segment files\n         * @see Configuration#SEGMENT_FILE_LENGTH_PROP_NAME\n         */\n        @Config\n        public int segmentFileLength()\n        {\n            return segmentFileLength;\n        }\n\n        /**\n         * Set the file length to be used for recording data segment files. If the {@link Image#termBufferLength()} is\n         * larger than the segment file length then the term length will be used.\n         *\n         * @param segmentFileLength the file length to be used for recording data segment files.\n         * @return this for a fluent API.\n         * @see Configuration#SEGMENT_FILE_LENGTH_PROP_NAME\n         */\n        public Context segmentFileLength(final int segmentFileLength)\n        {\n            this.segmentFileLength = segmentFileLength;\n            return this;\n        }\n\n        /**\n         * Get level at which files should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         *\n         * @return the level to be applied for file write.\n         * @see #catalogFileSyncLevel()\n         * @see Configuration#FILE_SYNC_LEVEL_PROP_NAME\n         */\n        @Config\n        public int fileSyncLevel()\n        {\n            return fileSyncLevel;\n        }\n\n        /**\n         * Set level at which files should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         *\n         * @param syncLevel to be applied for file writes.\n         * @return this for a fluent API.\n         * @see #catalogFileSyncLevel()\n         * @see Configuration#FILE_SYNC_LEVEL_PROP_NAME\n         */\n        public Context fileSyncLevel(final int syncLevel)\n        {\n            this.fileSyncLevel = syncLevel;\n            return this;\n        }\n\n        /**\n         * Get level at which the catalog file should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         *\n         * @return the level to be applied for file write.\n         * @see #fileSyncLevel()\n         * @see Configuration#CATALOG_FILE_SYNC_LEVEL_PROP_NAME\n         */\n        @Config\n        public int catalogFileSyncLevel()\n        {\n            return catalogFileSyncLevel;\n        }\n\n        /**\n         * Set level at which the catalog file should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         *\n         * @param syncLevel to be applied for file writes.\n         * @return this for a fluent API.\n         * @see #fileSyncLevel()\n         * @see Configuration#CATALOG_FILE_SYNC_LEVEL_PROP_NAME\n         */\n        public Context catalogFileSyncLevel(final int syncLevel)\n        {\n            this.catalogFileSyncLevel = syncLevel;\n            return this;\n        }\n\n        /**\n         * Get the {@link AgentInvoker} that should be used for the Media Driver if running in a lightweight mode.\n         *\n         * @return the {@link AgentInvoker} that should be used for the Media Driver if running in a lightweight mode.\n         */\n        public AgentInvoker mediaDriverAgentInvoker()\n        {\n            return mediaDriverAgentInvoker;\n        }\n\n        /**\n         * Set the {@link AgentInvoker} that should be used for the Media Driver if running in a lightweight mode.\n         *\n         * @param mediaDriverAgentInvoker that should be used for the Media Driver if running in a lightweight mode.\n         * @return this for a fluent API.\n         */\n        public Context mediaDriverAgentInvoker(final AgentInvoker mediaDriverAgentInvoker)\n        {\n            this.mediaDriverAgentInvoker = mediaDriverAgentInvoker;\n            return this;\n        }\n\n        /**\n         * Get the {@link ErrorHandler} to be used by the Archive.\n         *\n         * @return the {@link ErrorHandler} to be used by the Archive.\n         */\n        public ErrorHandler errorHandler()\n        {\n            return errorHandler;\n        }\n\n        /**\n         * Set the {@link ErrorHandler} to be used by the Archive.\n         *\n         * @param errorHandler the error handler to be used by the Archive.\n         * @return this for a fluent API\n         */\n        public Context errorHandler(final ErrorHandler errorHandler)\n        {\n            this.errorHandler = errorHandler;\n            return this;\n        }\n\n        /**\n         * Non-default for context.\n         *\n         * @param countedErrorHandler to override the default.\n         * @return this for a fluent API.\n         */\n        public Context countedErrorHandler(final CountedErrorHandler countedErrorHandler)\n        {\n            this.countedErrorHandler = countedErrorHandler;\n            return this;\n        }\n\n        /**\n         * The {@link #errorHandler()} that will increment {@link #errorCounter()} by default.\n         *\n         * @return {@link #errorHandler()} that will increment {@link #errorCounter()} by default.\n         */\n        public CountedErrorHandler countedErrorHandler()\n        {\n            return countedErrorHandler;\n        }\n\n        /**\n         * Get the archive threading mode.\n         *\n         * @return the archive threading mode.\n         * @see Configuration#THREADING_MODE_PROP_NAME\n         */\n        @Config\n        public ArchiveThreadingMode threadingMode()\n        {\n            return threadingMode;\n        }\n\n        /**\n         * Set the archive threading mode.\n         *\n         * @param threadingMode archive threading mode.\n         * @return this for a fluent API.\n         * @see Configuration#THREADING_MODE_PROP_NAME\n         */\n        public Context threadingMode(final ArchiveThreadingMode threadingMode)\n        {\n            this.threadingMode = threadingMode;\n            return this;\n        }\n\n        /**\n         * Get the thread factory used for creating threads in {@link ArchiveThreadingMode#SHARED} and\n         * {@link ArchiveThreadingMode#DEDICATED} threading modes.\n         *\n         * @return thread factory used for creating threads in SHARED and DEDICATED threading modes.\n         */\n        public ThreadFactory threadFactory()\n        {\n            return threadFactory;\n        }\n\n        /**\n         * Set the thread factory used for creating threads in {@link ArchiveThreadingMode#SHARED} and\n         * {@link ArchiveThreadingMode#DEDICATED} threading modes. The thread factories can be overridden for the\n         * recorder and replayer by using {@link #recorderThreadFactory(ThreadFactory)} and\n         * {@link #replayerThreadFactory(ThreadFactory)} respectively.\n         *\n         * @param threadFactory used for creating threads in SHARED and DEDICATED threading modes.\n         * @return this for a fluent API.\n         */\n        public Context threadFactory(final ThreadFactory threadFactory)\n        {\n            this.threadFactory = threadFactory;\n            return this;\n        }\n\n        /**\n         * Get the thread factory used for creating the recorder thread when running in DEDICATED threading mode.\n         *\n         * @return the thread factory used for creating the recorder thread when running in DEDICATED threading mode.\n         */\n        public ThreadFactory recorderThreadFactory()\n        {\n            return recorderThreadFactory;\n        }\n\n        /**\n         * Set the thread factory used for creating the recorder thread when running in DEDICATED threading mode. This\n         * will be used to override {@link #threadFactory}.\n         *\n         * @param threadFactory used for creating the recorder thread when running in DEDICATED threading mode.\n         * @return this for a fluent API.\n         */\n        public Context recorderThreadFactory(final ThreadFactory threadFactory)\n        {\n            this.recorderThreadFactory = threadFactory;\n            return this;\n        }\n\n        /**\n         * Get the thread factory used for creating the replayer thread when running in DEDICATED threading mode.\n         *\n         * @return the thread factory used for creating the replayer thread when running in DEDICATED threading mode.\n         */\n        public ThreadFactory replayerThreadFactory()\n        {\n            return replayerThreadFactory;\n        }\n\n        /**\n         * Set the thread factory used for creating the replayer thread when running in DEDICATED threading mode. This\n         * will be used to override {@link #threadFactory}.\n         *\n         * @param threadFactory used for creating the replayer thread when running in DEDICATED threading mode.\n         * @return this for a fluent API.\n         */\n        public Context replayerThreadFactory(final ThreadFactory threadFactory)\n        {\n            this.replayerThreadFactory = threadFactory;\n            return this;\n        }\n\n        /**\n         * Set the error buffer length in bytes to use.\n         *\n         * @param errorBufferLength in bytes to use.\n         * @return this for a fluent API.\n         * @see Configuration#ERROR_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context errorBufferLength(final int errorBufferLength)\n        {\n            this.errorBufferLength = errorBufferLength;\n            return this;\n        }\n\n        /**\n         * The error buffer length in bytes.\n         *\n         * @return error buffer length in bytes.\n         * @see Configuration#ERROR_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config\n        public int errorBufferLength()\n        {\n            return errorBufferLength;\n        }\n\n        /**\n         * Set the id for this Archive instance.\n         *\n         * @param archiveId for this Archive instance.\n         * @return this for a fluent API\n         * @see io.aeron.archive.Archive.Configuration#ARCHIVE_ID_PROP_NAME\n         */\n        public Context archiveId(final long archiveId)\n        {\n            this.archiveId = archiveId;\n            return this;\n        }\n\n        /**\n         * Get the id of this Archive instance.\n         *\n         * @return the id of this Archive instance.\n         * @see io.aeron.archive.Archive.Configuration#ARCHIVE_ID_PROP_NAME\n         */\n        @Config\n        public long archiveId()\n        {\n            return archiveId;\n        }\n\n        /**\n         * Get the error counter that will record the number of errors observed.\n         *\n         * @return the error counter that will record the number of errors observed.\n         */\n        public AtomicCounter errorCounter()\n        {\n            return errorCounter;\n        }\n\n        /**\n         * Set the error counter that will record the number of errors observed.\n         *\n         * @param errorCounter the error counter that will record the number of errors observed.\n         * @return this for a fluent API.\n         */\n        public Context errorCounter(final AtomicCounter errorCounter)\n        {\n            this.errorCounter = errorCounter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the number of active control sessions.\n         *\n         * @return the counter used to track the number of active control sessions.\n         */\n        public Counter controlSessionsCounter()\n        {\n            return controlSessionsCounter;\n        }\n\n        /**\n         * Set the counter used to track the number of active control sessions.\n         *\n         * @param controlSessionsCounter the counter used to track the number of active control sessions.\n         * @return this for a fluent API.\n         */\n        public Context controlSessionsCounter(final Counter controlSessionsCounter)\n        {\n            this.controlSessionsCounter = controlSessionsCounter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the count of concurrent recording sessions.\n         *\n         * @return the counter used to track the count of concurrent recording sessions.\n         */\n        public Counter recordingSessionCounter()\n        {\n            return recordingSessionCounter;\n        }\n\n        /**\n         * Set the counter used to track the count of concurrent recording sessions.\n         *\n         * @param counter used to track the count of concurrent recording sessions.\n         * @return this for a fluent API.\n         */\n        public Context recordingSessionCounter(final Counter counter)\n        {\n            this.recordingSessionCounter = counter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the count of concurrent replay sessions.\n         *\n         * @return the counter used to track the count of concurrent replay sessions.\n         */\n        public Counter replaySessionCounter()\n        {\n            return replaySessionCounter;\n        }\n\n        /**\n         * Set the counter used to track the count of concurrent replay sessions.\n         *\n         * @param counter used to track the count of concurrent replay sessions.\n         * @return this for a fluent API.\n         */\n        public Context replaySessionCounter(final Counter counter)\n        {\n            this.replaySessionCounter = counter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the total number of bytes written by the recorder.\n         *\n         * @return the counter used to track the total number of bytes written by the recorder.\n         */\n        public Counter totalWriteBytesCounter()\n        {\n            return totalWriteBytesCounter;\n        }\n\n        /**\n         * Set the counter used to track the total number of bytes written by the recorder.\n         *\n         * @param counter used to track the total number of bytes written by the recorder.\n         * @return this for a fluent API.\n         */\n        public Context totalWriteBytesCounter(final Counter counter)\n        {\n            this.totalWriteBytesCounter = counter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the total time used by the recorder to write data.\n         *\n         * @return the counter used to track the total time used by the recorder to write data.\n         */\n        public Counter totalWriteTimeCounter()\n        {\n            return totalWriteTimeCounter;\n        }\n\n        /**\n         * Set the counter used to track the total time used by the recorder to write data.\n         *\n         * @param counter used to track the total time used by the recorder to write data.\n         * @return this for a fluent API.\n         */\n        public Context totalWriteTimeCounter(final Counter counter)\n        {\n            this.totalWriteTimeCounter = counter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the max time used by the recorder to write a block of data.\n         *\n         * @return the counter used to track the max time used by the recorder to write a block of data.\n         */\n        public Counter maxWriteTimeCounter()\n        {\n            return maxWriteTimeCounter;\n        }\n\n        /**\n         * Set the counter used to track the max time used by the recorder to write a block of data.\n         *\n         * @param counter used to track the max time used by the recorder to write a block of data.\n         * @return this for a fluent API.\n         */\n        public Context maxWriteTimeCounter(final Counter counter)\n        {\n            maxWriteTimeCounter = counter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the total number of bytes read by the replayer.\n         *\n         * @return the counter used to track the total number of bytes read by the replayer.\n         */\n        public Counter totalReadBytesCounter()\n        {\n            return totalReadBytesCounter;\n        }\n\n        /**\n         * Set the counter used to track the total number of bytes read by the replayer.\n         *\n         * @param counter used to track the total number of bytes read by the replayer.\n         * @return this for a fluent API.\n         */\n        public Context totalReadBytesCounter(final Counter counter)\n        {\n            this.totalReadBytesCounter = counter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the total time used by the replayer to read data.\n         *\n         * @return the counter used to track the total time used by the replayer to read data.\n         */\n        public Counter totalReadTimeCounter()\n        {\n            return totalReadTimeCounter;\n        }\n\n        /**\n         * Set the counter used to track the total time used by the replayer to read data.\n         *\n         * @param counter used to track the total time used by the replayer to read data.\n         * @return this for a fluent API.\n         */\n        public Context totalReadTimeCounter(final Counter counter)\n        {\n            this.totalReadTimeCounter = counter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the max time used by the replayer to read a block of data.\n         *\n         * @return the counter used to track the max time used by the replayer to read a block of data.\n         */\n        public Counter maxReadTimeCounter()\n        {\n            return maxReadTimeCounter;\n        }\n\n        /**\n         * Set the counter used to track the max time used by the replayer to read a block of data.\n         *\n         * @param counter used to track the max time used by the replayer to read a block of data.\n         * @return this for a fluent API.\n         */\n        public Context maxReadTimeCounter(final Counter counter)\n        {\n            this.maxReadTimeCounter = counter;\n            return this;\n        }\n\n        /**\n         * Get the max number of concurrent recordings.\n         *\n         * @return the max number of concurrent recordings.\n         * @see Configuration#MAX_CONCURRENT_RECORDINGS_PROP_NAME\n         */\n        @Config\n        public int maxConcurrentRecordings()\n        {\n            return maxConcurrentRecordings;\n        }\n\n        /**\n         * Set the max number of concurrent recordings.\n         *\n         * @param maxConcurrentRecordings the max number of concurrent recordings.\n         * @return this for a fluent API.\n         * @see Configuration#MAX_CONCURRENT_RECORDINGS_PROP_NAME\n         */\n        public Context maxConcurrentRecordings(final int maxConcurrentRecordings)\n        {\n            this.maxConcurrentRecordings = maxConcurrentRecordings;\n            return this;\n        }\n\n        /**\n         * Get the max number of concurrent replays.\n         *\n         * @return the max number of concurrent replays.\n         * @see Configuration#MAX_CONCURRENT_REPLAYS_PROP_NAME\n         */\n        @Config\n        public int maxConcurrentReplays()\n        {\n            return maxConcurrentReplays;\n        }\n\n        /**\n         * Set the max number of concurrent replays.\n         *\n         * @param maxConcurrentReplays the max number of concurrent replays.\n         * @return this for a fluent API.\n         * @see Configuration#MAX_CONCURRENT_REPLAYS_PROP_NAME\n         */\n        public Context maxConcurrentReplays(final int maxConcurrentReplays)\n        {\n            this.maxConcurrentReplays = maxConcurrentReplays;\n            return this;\n        }\n\n        /**\n         * Get the max length of a file IO operation.\n         *\n         * @return the max length of a file IO operation.\n         * @see Configuration#FILE_IO_MAX_LENGTH_PROP_NAME\n         */\n        @Config\n        public int fileIoMaxLength()\n        {\n            return fileIoMaxLength;\n        }\n\n        /**\n         * Set the max length of a file IO operation.\n         *\n         * @param fileIoMaxLength the max length of a file IO operation.\n         * @return this for a fluent API.\n         * @see Configuration#FILE_IO_MAX_LENGTH_PROP_NAME\n         */\n        public Context fileIoMaxLength(final int fileIoMaxLength)\n        {\n            this.fileIoMaxLength = fileIoMaxLength;\n            return this;\n        }\n\n        /**\n         * Threshold below which the archive will reject new recording requests.\n         *\n         * @param lowStorageSpaceThreshold in bytes.\n         * @return this for a fluent API.\n         * @see Configuration#LOW_STORAGE_SPACE_THRESHOLD_PROP_NAME\n         */\n        public Context lowStorageSpaceThreshold(final long lowStorageSpaceThreshold)\n        {\n            this.lowStorageSpaceThreshold = lowStorageSpaceThreshold;\n            return this;\n        }\n\n        /**\n         * Threshold below which the archive will reject new recording requests.\n         *\n         * @return threshold below which the archive will reject new recording requests in bytes.\n         * @see Configuration#LOW_STORAGE_SPACE_THRESHOLD_PROP_NAME\n         */\n        @Config\n        public long lowStorageSpaceThreshold()\n        {\n            return lowStorageSpaceThreshold;\n        }\n\n        /**\n         * Delete the archive directory if the {@link #archiveDir()} value is not null.\n         * <p>\n         * Use {@link #deleteDirectory()} instead.\n         */\n        @Deprecated\n        public void deleteArchiveDirectory()\n        {\n            deleteDirectory();\n        }\n\n        /**\n         * Delete the archive directory if the {@link #archiveDir()} value is not null.\n         */\n        public void deleteDirectory()\n        {\n            if (null != archiveDir)\n            {\n                IoUtil.delete(archiveDir, false);\n            }\n        }\n\n        /**\n         * Set the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @param aeronDirectoryName the top level Aeron directory.\n         * @return this for a fluent API.\n         */\n        public Context aeronDirectoryName(final String aeronDirectoryName)\n        {\n            this.aeronDirectoryName = aeronDirectoryName;\n            return this;\n        }\n\n        /**\n         * Get the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @return The top level Aeron directory.\n         */\n        public String aeronDirectoryName()\n        {\n            return aeronDirectoryName;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * This client will be closed when the {@link Archive#close()} or {@link #close()} methods are called if\n         * {@link #ownsAeronClient()} is true.\n         *\n         * @param aeron client for communicating with the local Media Driver.\n         * @return this for a fluent API.\n         * @see Aeron#connect()\n         */\n        public Context aeron(final Aeron aeron)\n        {\n            this.aeron = aeron;\n            return this;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * If not provided then a default will be established during {@link #conclude()} by calling\n         * {@link Aeron#connect()}.\n         *\n         * @return client for communicating with the local Media Driver.\n         */\n        public Aeron aeron()\n        {\n            return aeron;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @param ownsAeronClient does this context own the {@link #aeron()} client.\n         * @return this for a fluent API.\n         */\n        public Context ownsAeronClient(final boolean ownsAeronClient)\n        {\n            this.ownsAeronClient = ownsAeronClient;\n            return this;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @return does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         */\n        public boolean ownsAeronClient()\n        {\n            return ownsAeronClient;\n        }\n\n        /**\n         * The {@link Catalog} describing the contents of the Archive.\n         *\n         * @param catalog {@link Catalog} describing the contents of the Archive.\n         * @return this for a fluent API.\n         */\n        Context catalog(final Catalog catalog)\n        {\n            this.catalog = catalog;\n            return this;\n        }\n\n        /**\n         * The {@link Catalog} describing the contents of the Archive.\n         *\n         * @return the {@link Catalog} describing the contents of the Archive.\n         */\n        Catalog catalog()\n        {\n            return catalog;\n        }\n\n        /**\n         * The {@link ArchiveMarkFile} for the Archive.\n         *\n         * @param archiveMarkFile {@link ArchiveMarkFile} for the Archive.\n         * @return this for a fluent API.\n         */\n        public Context archiveMarkFile(final ArchiveMarkFile archiveMarkFile)\n        {\n            this.markFile = archiveMarkFile;\n            return this;\n        }\n\n        /**\n         * The {@link ArchiveMarkFile} for the Archive.\n         *\n         * @return {@link ArchiveMarkFile} for the Archive.\n         */\n        public ArchiveMarkFile archiveMarkFile()\n        {\n            return markFile;\n        }\n\n        /**\n         * Maximum number of catalog entries for the Archive.\n         * <p>\n         * <b>Note: </b> This method has no effect.\n         *\n         * @param maxCatalogEntries for the archive.\n         * @return this for a fluent API.\n         * @see #catalogCapacity(long)\n         * @deprecated This method was deprecated in favor of {@link #catalogCapacity(long)} which works with bytes\n         * rather than number of entries.\n         */\n        @Deprecated\n        public Context maxCatalogEntries(final long maxCatalogEntries)\n        {\n            return this;\n        }\n\n        /**\n         * Maximum number of catalog entries for the Archive.\n         * <p>\n         * <b>Note: </b> This method is not used.\n         *\n         * @return maximum number of catalog entries for the Archive.\n         * @see #catalogCapacity()\n         * @deprecated This method was deprecated in favor of {@link #catalogCapacity()} which returns capacity of\n         * the {@link Catalog} in bytes rather than in number of entries.\n         */\n        @Deprecated\n        @Config\n        public long maxCatalogEntries()\n        {\n            return -1;\n        }\n\n        /**\n         * Capacity in bytes of the {@link Catalog}.\n         *\n         * @param catalogCapacity in bytes.\n         * @return this for a fluent API.\n         * @see Configuration#CATALOG_CAPACITY_PROP_NAME\n         */\n        public Context catalogCapacity(final long catalogCapacity)\n        {\n            this.catalogCapacity = catalogCapacity;\n            return this;\n        }\n\n        /**\n         * Capacity in bytes of the {@link Catalog}.\n         *\n         * @return capacity in bytes of the {@link Catalog}.\n         * @see Configuration#CATALOG_CAPACITY_PROP_NAME\n         */\n        @Config\n        public long catalogCapacity()\n        {\n            return catalogCapacity;\n        }\n\n        /**\n         * Get the {@link AuthenticatorSupplier} that should be used for the Archive.\n         *\n         * @return the {@link AuthenticatorSupplier} to be used for the Archive.\n         */\n        @Config\n        public AuthenticatorSupplier authenticatorSupplier()\n        {\n            return authenticatorSupplier;\n        }\n\n        /**\n         * Set the {@link AuthenticatorSupplier} that will be used for the Archive.\n         *\n         * @param authenticatorSupplier {@link AuthenticatorSupplier} to use for the Archive.\n         * @return this for a fluent API.\n         */\n        public Context authenticatorSupplier(final AuthenticatorSupplier authenticatorSupplier)\n        {\n            this.authenticatorSupplier = authenticatorSupplier;\n            return this;\n        }\n\n        /**\n         * Get the {@link AuthorisationServiceSupplier} that should be used for the Archive.\n         *\n         * @return the {@link AuthorisationServiceSupplier} to be used for the Archive.\n         */\n        @Config\n        public AuthorisationServiceSupplier authorisationServiceSupplier()\n        {\n            return authorisationServiceSupplier;\n        }\n\n        /**\n         * <p>Set the {@link AuthorisationServiceSupplier} that will be used for the Archive.</p>\n         * <p>When using an authorisation service for the ConsensusModule, then the following values for protocolId,\n         * actionId, and type should be considered.</p>\n         *\n         * <table>\n         *     <caption>Parameters for authorisation service queries from the Archive</caption>\n         *     <thead>\n         *         <tr><td>Description</td><td>protocolId</td><td>actionId</td><td>type(s)</td></tr>\n         *     </thead>\n         *     <tbody>\n         *         <tr>\n         *             <td>Start the recording of a stream</td>\n         *             <td>{@link io.aeron.archive.codecs.MessageHeaderDecoder#SCHEMA_ID}</td>\n         *             <td>{@link io.aeron.archive.codecs.StartRecordingRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Start the recording of a stream (version 2)</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.StartRecordingRequest2Decoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Stop the recording of a stream</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.StopRecordingRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Replay a stream from the archive</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ReplayRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Stop a replay from the archive</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.StopReplayRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>List the recordings from the archive</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ListRecordingsRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>List the recordings from the archive for a specific URI</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ListRecordingsForUriRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>List a specific recording from the archive</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ListRecordingRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Extend a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ExtendRecordingRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Extend a recording (version 2)</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ExtendRecordingRequest2Decoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Gets the position of a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.RecordingPositionRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Truncate a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.TruncateRecordingRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Stop a recording by subscription</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.StopRecordingSubscriptionRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Extend a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ExtendRecordingRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Get the stop position for a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.StopPositionRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Find the last recording for a given stream, session and channel fragment</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.FindLastMatchingRecordingRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>List subscriptions being used for recordings</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ListRecordingSubscriptionsRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Stop all replays</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.StopAllReplaysRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Start replicating a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ReplicateRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Start replicating a recording (version 2)</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.ReplicateRequest2Decoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Stop replication a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.StopReplicationRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Get the start position of a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.StartRecordingRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Detach segment files from a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.DetachSegmentsRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Delete detached segments</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.DeleteDetachedSegmentsRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Attach new segments for a recording</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.AttachSegmentsRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Migrate segments from one recording to another</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.MigrateSegmentsRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Keep alive the archive connection</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.KeepAliveRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Replicate a recording with a tag</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.TaggedReplicateRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Stop recording by recording id</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.StopRecordingByIdentityRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Purge a recording by recording id</td>\n         *             <td></td>\n         *             <td>{@link io.aeron.archive.codecs.PurgeRecordingRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *     </tbody>\n         * </table>\n         *\n         * @param authorisationServiceSupplier {@link AuthorisationServiceSupplier} to use for the Archive.\n         * @return this for a fluent API.\n         */\n        public Context authorisationServiceSupplier(final AuthorisationServiceSupplier authorisationServiceSupplier)\n        {\n            this.authorisationServiceSupplier = authorisationServiceSupplier;\n            return this;\n        }\n\n        /**\n         * Get the secure random algorithm should be used by various Aeron components.\n         *\n         * @return the secure random algorithm to be used.\n         * @see #secureRandomAlgorithm(String)\n         */\n        public String secureRandomAlgorithm()\n        {\n            return secureRandomAlgorithm;\n        }\n\n        /**\n         * Define which secure random algorithm should be used by various Aeron components. This string will be passed\n         * to {@link java.security.SecureRandom#getInstance(String)}, with one exception. The special case of\n         * <code>strong</code> (case-insensitive) will use {@link SecureRandom#getInstanceStrong()}\n         *\n         * @param algorithm the algorithm to be used or <code>strong</code>\n         * @return this for the fluent API.\n         * @see CommonContext#getSecureRandomAlgorithm()\n         * @see CommonContext#SECURE_RANDOM_ALGORITHM_PROP_NAME\n         * @see CommonContext#SECURE_RANDOM_ALGORITHM_DEFAULT\n         */\n        public Context secureRandomAlgorithm(final String algorithm)\n        {\n            this.secureRandomAlgorithm = algorithm;\n            return this;\n        }\n\n        /**\n         * Get the configured instance of SecureRandom using {@link SecureRandom#getInstanceStrong()} if\n         * <code>strong</code> is specified.\n         *\n         * @return instance of SecureRandom\n         * @throws AeronException if there is a problem resolving the algorithm\n         */\n        public SecureRandom secureRandom()\n        {\n            try\n            {\n                if (\"strong\".equalsIgnoreCase(secureRandomAlgorithm))\n                {\n                    return SecureRandom.getInstanceStrong();\n                }\n                else\n                {\n                    return SecureRandom.getInstance(secureRandomAlgorithm);\n                }\n            }\n            catch (final NoSuchAlgorithmException ex)\n            {\n                throw new AeronException(\n                    \"unable to create SecureRandom for algorithm=\" + secureRandomAlgorithm, ex, ERROR);\n            }\n        }\n\n        CountDownLatch abortLatch()\n        {\n            return abortLatch;\n        }\n\n        void concludeRecordChecksum()\n        {\n            if (null == recordChecksum)\n            {\n                final String checksumClass = Configuration.recordChecksum();\n                if (!Strings.isEmpty(checksumClass))\n                {\n                    recordChecksum = Checksums.newInstance(checksumClass);\n                }\n            }\n        }\n\n        void concludeReplayChecksum()\n        {\n            if (null == replayChecksum)\n            {\n                final String checksumClass = Configuration.replayChecksum();\n                if (!Strings.isEmpty(checksumClass))\n                {\n                    replayChecksum = Checksums.newInstance(checksumClass);\n                }\n            }\n        }\n\n        Context dataBuffer(final UnsafeBuffer dataBuffer)\n        {\n            this.dataBuffer = dataBuffer;\n            return this;\n        }\n\n        UnsafeBuffer dataBuffer()\n        {\n            if (null == dataBuffer)\n            {\n                dataBuffer = new UnsafeBuffer(allocateDirectAligned(fileIoMaxLength, CACHE_LINE_LENGTH));\n            }\n\n            return dataBuffer;\n        }\n\n        Context replayBuffer(final UnsafeBuffer replayBuffer)\n        {\n            this.replayBuffer = replayBuffer;\n            return this;\n        }\n\n        UnsafeBuffer replayBuffer()\n        {\n            if (DEDICATED != threadingMode)\n            {\n                return dataBuffer();\n            }\n\n            if (null == replayBuffer)\n            {\n                replayBuffer = new UnsafeBuffer(allocateDirectAligned(fileIoMaxLength, CACHE_LINE_LENGTH));\n            }\n\n            return replayBuffer;\n        }\n\n        Context recordChecksumBuffer(final UnsafeBuffer recordChecksumBuffer)\n        {\n            this.recordChecksumBuffer = recordChecksumBuffer;\n            return this;\n        }\n\n        UnsafeBuffer recordChecksumBuffer()\n        {\n            if (null == recordChecksum)\n            {\n                return null;\n            }\n\n            if (DEDICATED != threadingMode)\n            {\n                return dataBuffer();\n            }\n\n            if (null == recordChecksumBuffer)\n            {\n                recordChecksumBuffer = new UnsafeBuffer(allocateDirectAligned(fileIoMaxLength, CACHE_LINE_LENGTH));\n            }\n\n            return recordChecksumBuffer;\n        }\n\n        /**\n         * Close the context and free applicable resources.\n         * <p>\n         * If {@link #ownsAeronClient()} is true then the {@link #aeron()} client will be closed.\n         */\n        public void close()\n        {\n            CloseHelper.close(countedErrorHandler, catalog);\n            CloseHelper.close(countedErrorHandler, archiveDirChannel);\n\n            if (ownsAeronClient)\n            {\n                CloseHelper.close(aeron);\n            }\n            else\n            {\n                CloseHelper.close(countedErrorHandler, controlSessionsCounter);\n                CloseHelper.close(countedErrorHandler, recordingSessionCounter);\n                CloseHelper.close(countedErrorHandler, replaySessionCounter);\n                CloseHelper.close(countedErrorHandler, totalWriteBytesCounter);\n                CloseHelper.close(countedErrorHandler, totalWriteTimeCounter);\n                CloseHelper.close(countedErrorHandler, maxWriteTimeCounter);\n                CloseHelper.close(countedErrorHandler, totalReadBytesCounter);\n                CloseHelper.close(countedErrorHandler, totalReadTimeCounter);\n                CloseHelper.close(countedErrorHandler, maxReadTimeCounter);\n                closeDutyCycleCounters(conductorDutyCycleTracker);\n                closeDutyCycleCounters(recorderDutyCycleTracker);\n                closeDutyCycleCounters(replayerDutyCycleTracker);\n                CloseHelper.close(errorCounter);\n            }\n\n            if (errorHandler instanceof AutoCloseable)\n            {\n                CloseHelper.close((AutoCloseable)errorHandler);\n            }\n            CloseHelper.close(markFile);\n        }\n\n        private void closeDutyCycleCounters(final DutyCycleTracker dutyCycleTracker)\n        {\n            if (dutyCycleTracker instanceof DutyCycleStallTracker)\n            {\n                final DutyCycleStallTracker dutyCycleStallTracker = (DutyCycleStallTracker)dutyCycleTracker;\n                CloseHelper.close(countedErrorHandler, dutyCycleStallTracker.maxCycleTime());\n                CloseHelper.close(countedErrorHandler, dutyCycleStallTracker.cycleTimeThresholdExceededCount());\n            }\n        }\n\n        private void concludeArchiveId()\n        {\n            if (NULL_VALUE == archiveId)\n            {\n                if (null != aeron)\n                {\n                    archiveId = aeron.clientId();\n                }\n                else\n                {\n                    archiveId = CommonContext.nextCorrelationId(\n                        new File(aeronDirectoryName), epochClock, new CommonContext().driverTimeoutMs());\n                }\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"Archive.Context\" +\n                \"\\n{\" +\n                \"\\n    isConcluded=\" + isConcluded() +\n                \"\\n    deleteArchiveOnStart=\" + deleteArchiveOnStart +\n                \"\\n    ownsAeronClient=\" + ownsAeronClient +\n                \"\\n    aeronDirectoryName='\" + aeronDirectoryName + '\\'' +\n                \"\\n    aeron=\" + aeron +\n                \"\\n    archiveDir=\" + archiveDir +\n                \"\\n    archiveDirectoryName='\" + archiveDirectoryName + '\\'' +\n                \"\\n    archiveDirChannel=\" + archiveDirChannel +\n                \"\\n    archiveFileStore=\" + archiveFileStore +\n                \"\\n    archiveId=\" + archiveId +\n                \"\\n    catalog=\" + catalog +\n                \"\\n    markFile=\" + markFile +\n                \"\\n    archiveClientContext=\" + archiveClientContext +\n                \"\\n    mediaDriverAgentInvoker=\" + mediaDriverAgentInvoker +\n                \"\\n    controlChannel='\" + controlChannel + '\\'' +\n                \"\\n    controlStreamId=\" + controlStreamId +\n                \"\\n    localControlChannel='\" + localControlChannel + '\\'' +\n                \"\\n    localControlStreamId=\" + localControlStreamId +\n                \"\\n    controlTermBufferSparse=\" + controlTermBufferSparse +\n                \"\\n    controlTermBufferLength=\" + controlTermBufferLength +\n                \"\\n    controlMtuLength=\" + controlMtuLength +\n                \"\\n    recordingEventsChannel='\" + recordingEventsChannel + '\\'' +\n                \"\\n    recordingEventsStreamId=\" + recordingEventsStreamId +\n                \"\\n    recordingEventsEnabled=\" + recordingEventsEnabled +\n                \"\\n    replicationChannel='\" + replicationChannel + '\\'' +\n                \"\\n    connectTimeoutNs=\" + connectTimeoutNs +\n                \"\\n    sessionLivenessCheckIntervalNs=\" + sessionLivenessCheckIntervalNs +\n                \"\\n    replayLingerTimeoutNs=\" + replayLingerTimeoutNs +\n                \"\\n    maxCatalogEntries=\" + -1 +\n                \"\\n    catalogCapacity=\" + catalogCapacity +\n                \"\\n    lowStorageSpaceThreshold=\" + lowStorageSpaceThreshold +\n                \"\\n    segmentFileLength=\" + segmentFileLength +\n                \"\\n    fileSyncLevel=\" + fileSyncLevel +\n                \"\\n    catalogFileSyncLevel=\" + catalogFileSyncLevel +\n                \"\\n    maxConcurrentRecordings=\" + maxConcurrentRecordings +\n                \"\\n    maxConcurrentReplays=\" + maxConcurrentReplays +\n                \"\\n    fileIoMaxLength=\" + fileIoMaxLength +\n                \"\\n    threadingMode=\" + threadingMode +\n                \"\\n    threadFactory=\" + threadFactory +\n                \"\\n    abortLatch=\" + abortLatch +\n                \"\\n    idleStrategySupplier=\" + idleStrategySupplier +\n                \"\\n    replayerIdleStrategySupplier=\" + replayerIdleStrategySupplier +\n                \"\\n    recorderIdleStrategySupplier=\" + recorderIdleStrategySupplier +\n                \"\\n    epochClock=\" + epochClock +\n                \"\\n    authenticatorSupplier=\" + authenticatorSupplier +\n                \"\\n    controlSessionsCounter=\" + controlSessionsCounter +\n                \"\\n    recordingSessionCounter=\" + recordingSessionCounter +\n                \"\\n    replaySessionCounter=\" + replaySessionCounter +\n                \"\\n    errorBufferLength=\" + errorBufferLength +\n                \"\\n    errorHandler=\" + errorHandler +\n                \"\\n    errorCounter=\" + errorCounter +\n                \"\\n    countedErrorHandler=\" + countedErrorHandler +\n                \"\\n    recordChecksum=\" + recordChecksum +\n                \"\\n    replayChecksum=\" + replayChecksum +\n                \"\\n    dataBuffer=\" + dataBuffer +\n                \"\\n    replayBuffer=\" + replayBuffer +\n                \"\\n    recordChecksumBuffer=\" + recordChecksumBuffer +\n                \"\\n    conductorCycleThresholdNs=\" + conductorCycleThresholdNs +\n                \"\\n    recorderCycleThresholdNs=\" + recorderCycleThresholdNs +\n                \"\\n    replayerCycleThresholdNs=\" + replayerCycleThresholdNs +\n                \"\\n    conductorDutyCycleTracker=\" + conductorDutyCycleTracker +\n                \"\\n    recorderDutyCycleTracker=\" + recorderDutyCycleTracker +\n                \"\\n    replayerDutyCycleTracker=\" + replayerDutyCycleTracker +\n                \"\\n    totalWriteBytesCounter=\" + totalWriteBytesCounter +\n                \"\\n    totalWriteTimeCounter=\" + totalWriteTimeCounter +\n                \"\\n    maxWriteTimeCounter=\" + maxWriteTimeCounter +\n                \"\\n    totalReadBytesCounter=\" + totalReadBytesCounter +\n                \"\\n    totalReadTimeCounter=\" + totalReadTimeCounter +\n                \"\\n    maxReadTimeCounter=\" + maxReadTimeCounter +\n                \"\\n}\";\n        }\n    }\n\n    /**\n     * The filename to be used for a segment file based on recording id and position the segment begins.\n     *\n     * @param recordingId         to identify the recorded stream.\n     * @param segmentBasePosition at which the segment file begins.\n     * @return the filename to be used for a segment file based on recording id and position the segment begins.\n     */\n    static String segmentFileName(final long recordingId, final long segmentBasePosition)\n    {\n        return recordingId + \"-\" + segmentBasePosition + Configuration.RECORDING_SEGMENT_SUFFIX;\n    }\n\n    /**\n     * Get the {@link FileChannel} for the parent directory for the recordings and catalog, so it can be sync'ed\n     * to storage when new files are created.\n     *\n     * @param directory     which will store the files created by the archive.\n     * @param fileSyncLevel to be applied for file updates, {@link Archive.Configuration#FILE_SYNC_LEVEL_PROP_NAME}.\n     * @return the {@link FileChannel} for the parent directory for the recordings and catalog if fileSyncLevel\n     * greater than zero otherwise null.\n     */\n    static FileChannel channelForDirectorySync(final File directory, final int fileSyncLevel)\n    {\n        if (fileSyncLevel > 0)\n        {\n            try\n            {\n                return FileChannel.open(directory.toPath());\n            }\n            catch (final IOException ignore)\n            {\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveConductor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.AvailableImageHandler;\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.UnavailableCounterHandler;\nimport io.aeron.UnavailableImageHandler;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveEvent;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.DutyCycleTracker;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.AuthorisationService;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.CloseHelper;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.SemanticVersion;\nimport org.agrona.Strings;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.collections.Long2LongCounterMap;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.collections.Object2ObjectHashMap;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.StandardOpenOption;\nimport java.util.ArrayDeque;\nimport java.util.EnumSet;\nimport java.util.Iterator;\nimport java.util.Random;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.CONTROL_MODE_RESPONSE;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_PARAM_NAME;\nimport static io.aeron.CommonContext.MTU_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.SPARSE_PARAM_NAME;\nimport static io.aeron.CommonContext.SPY_PREFIX;\nimport static io.aeron.CommonContext.TERM_LENGTH_PARAM_NAME;\nimport static io.aeron.archive.Archive.Configuration.MARK_FILE_UPDATE_INTERVAL_MS;\nimport static io.aeron.archive.Archive.Configuration.RECORDING_SEGMENT_SUFFIX;\nimport static io.aeron.archive.Archive.segmentFileName;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.AeronArchive.segmentFileBasePosition;\nimport static io.aeron.archive.client.ArchiveException.ACTIVE_LISTING;\nimport static io.aeron.archive.client.ArchiveException.ACTIVE_RECORDING;\nimport static io.aeron.archive.client.ArchiveException.ACTIVE_SUBSCRIPTION;\nimport static io.aeron.archive.client.ArchiveException.EMPTY_RECORDING;\nimport static io.aeron.archive.client.ArchiveException.GENERIC;\nimport static io.aeron.archive.client.ArchiveException.INVALID_EXTENSION;\nimport static io.aeron.archive.client.ArchiveException.MAX_RECORDINGS;\nimport static io.aeron.archive.client.ArchiveException.MAX_REPLAYS;\nimport static io.aeron.archive.client.ArchiveException.STORAGE_SPACE;\nimport static io.aeron.archive.client.ArchiveException.UNKNOWN_RECORDING;\nimport static io.aeron.archive.client.ArchiveException.UNKNOWN_REPLICATION;\nimport static io.aeron.archive.client.ArchiveException.UNKNOWN_SUBSCRIPTION;\nimport static io.aeron.archive.codecs.RecordingState.DELETED;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.DataHeaderFlyweight.fragmentLength;\nimport static io.aeron.protocol.DataHeaderFlyweight.streamId;\nimport static io.aeron.protocol.DataHeaderFlyweight.termId;\nimport static java.lang.Math.max;\nimport static java.lang.Math.min;\nimport static java.nio.file.StandardOpenOption.READ;\nimport static java.nio.file.StandardOpenOption.WRITE;\nimport static org.agrona.AsciiEncoding.digitCount;\nimport static org.agrona.concurrent.status.CountersReader.METADATA_LENGTH;\n\nabstract class ArchiveConductor\n    extends SessionWorker<Session>\n    implements UnavailableImageHandler, UnavailableCounterHandler\n{\n    private static final EnumSet<StandardOpenOption> FILE_OPTIONS = EnumSet.of(READ, WRITE);\n    static final String DELETE_SUFFIX = \".del\";\n\n    private final long closeHandlerRegistrationId;\n    private final long unavailableCounterHandlerRegistrationId;\n    private final long connectTimeoutMs;\n    private final long sessionLivenessCheckIntervalMs;\n    private long nextSessionId = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);\n    private long markFileUpdateDeadlineMs = 0;\n    private int replayId = 1;\n    private volatile boolean isAbort;\n\n    private final RecordingSummary recordingSummary = new RecordingSummary();\n    private final ControlRequestDecoders decoders = new ControlRequestDecoders();\n    private final ArrayDeque<Runnable> taskQueue = new ArrayDeque<>();\n    private final Long2ObjectHashMap<ReplaySession> replaySessionByIdMap = new Long2ObjectHashMap<>();\n    private final Long2ObjectHashMap<RecordingSession> recordingSessionByIdMap = new Long2ObjectHashMap<>();\n    private final Long2ObjectHashMap<ReplicationSession> replicationSessionByIdMap = new Long2ObjectHashMap<>();\n    private final Long2ObjectHashMap<DeleteSegmentsSession> deleteSegmentsSessionByIdMap = new Long2ObjectHashMap<>();\n    private final Int2ObjectHashMap<Counter> counterByIdMap = new Int2ObjectHashMap<>();\n    private final Object2ObjectHashMap<String, Subscription> recordingSubscriptionByKeyMap =\n        new Object2ObjectHashMap<>();\n    private final Long2ObjectHashMap<SessionForReplay> controlSessionByReplayToken = new Long2ObjectHashMap<>();\n    private final UnsafeBuffer descriptorBuffer = new UnsafeBuffer();\n    private final RecordingDescriptorDecoder recordingDescriptorDecoder = new RecordingDescriptorDecoder();\n    private final UnsafeBuffer counterMetadataBuffer = new UnsafeBuffer(new byte[METADATA_LENGTH]);\n    private final Long2LongCounterMap subscriptionRefCountMap = new Long2LongCounterMap(0L);\n\n    private final Aeron aeron;\n    private final AgentInvoker aeronAgentInvoker;\n    private final AgentInvoker driverAgentInvoker;\n    private final EpochClock epochClock;\n    private final NanoClock nanoClock;\n    private final CachedEpochClock cachedEpochClock = new CachedEpochClock();\n    private final File archiveDir;\n    private final Subscription controlSubscription;\n    private final Subscription localControlSubscription;\n    private final Catalog catalog;\n    private final ArchiveMarkFile markFile;\n    private final RecordingEventsProxy recordingEventsProxy;\n    private final Authenticator authenticator;\n    private final AuthorisationService authorisationService;\n    private final ControlSessionAdapter controlSessionAdapter;\n    private final ControlResponseProxy controlResponseProxy = new ControlResponseProxy();\n    private final ControlSessionProxy controlSessionProxy = new ControlSessionProxy(controlResponseProxy);\n    private final DutyCycleTracker dutyCycleTracker;\n    private final Random random;\n    private final ExpandableArrayBuffer tempBuffer = new ExpandableArrayBuffer(300);\n    final Archive.Context ctx;\n    Recorder recorder;\n    Replayer replayer;\n\n    ArchiveConductor(final Archive.Context ctx)\n    {\n        super(\"archive-conductor\", ctx.countedErrorHandler());\n\n        this.ctx = ctx;\n\n        aeron = ctx.aeron();\n        aeronAgentInvoker = aeron.conductorAgentInvoker();\n        driverAgentInvoker = ctx.mediaDriverAgentInvoker();\n        epochClock = ctx.epochClock();\n        nanoClock = ctx.nanoClock();\n        archiveDir = ctx.archiveDir();\n        connectTimeoutMs = TimeUnit.NANOSECONDS.toMillis(ctx.connectTimeoutNs());\n        sessionLivenessCheckIntervalMs = TimeUnit.NANOSECONDS.toMillis(ctx.sessionLivenessCheckIntervalNs());\n        catalog = ctx.catalog();\n        markFile = ctx.archiveMarkFile();\n        dutyCycleTracker = ctx.conductorDutyCycleTracker();\n        cachedEpochClock.update(epochClock.time());\n\n        random = ctx.secureRandom();\n\n        authenticator = ctx.authenticatorSupplier().get();\n        if (null == authenticator)\n        {\n            throw new ArchiveException(\"authenticator cannot be null\");\n        }\n\n        authorisationService = ctx.authorisationServiceSupplier().get();\n        if (null == authorisationService)\n        {\n            throw new ArchiveException(\"authorisation service cannot be null\");\n        }\n\n        unavailableCounterHandlerRegistrationId = aeron.addUnavailableCounterHandler(this);\n        closeHandlerRegistrationId = aeron.addCloseHandler(this::abort);\n\n        recordingEventsProxy = ctx.recordingEventsEnabled() ? new RecordingEventsProxy(\n            aeron.addExclusivePublication(ctx.recordingEventsChannel(), ctx.recordingEventsStreamId())) : null;\n\n        if (ctx.controlChannelEnabled())\n        {\n            final ChannelUri controlChannelUri = ChannelUri.parse(ctx.controlChannel());\n            controlChannelUri.put(CommonContext.SPARSE_PARAM_NAME, Boolean.toString(ctx.controlTermBufferSparse()));\n            controlSubscription = aeron.addSubscription(\n                controlChannelUri.toString(), ctx.controlStreamId(), null, this);\n        }\n        else\n        {\n            controlSubscription = null;\n        }\n\n        localControlSubscription = aeron.addSubscription(\n            ctx.localControlChannel(), ctx.localControlStreamId(), null, this);\n\n        controlSessionAdapter = new ControlSessionAdapter(\n            decoders, controlSubscription, localControlSubscription, this, authorisationService);\n    }\n\n    public void onStart()\n    {\n        recorder = newRecorder();\n        replayer = newReplayer();\n\n        dutyCycleTracker.update(nanoClock.nanoTime());\n    }\n\n    public void onUnavailableImage(final Image image)\n    {\n        controlSessionAdapter.abortControlSessionByImage(image);\n    }\n\n    public void onUnavailableCounter(\n        final CountersReader countersReader, final long registrationId, final int counterId)\n    {\n        final Counter counter = counterByIdMap.remove(counterId);\n        if (null != counter)\n        {\n            counter.close();\n        }\n    }\n\n    abstract Recorder newRecorder();\n\n    abstract Replayer newReplayer();\n\n    /**\n     * {@inheritDoc}\n     */\n    protected final void preSessionsClose()\n    {\n        closeSessionWorkers();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected abstract void closeSessionWorkers();\n\n    /**\n     * {@inheritDoc}\n     */\n    protected void postSessionsClose()\n    {\n        if (isAbort)\n        {\n            ctx.abortLatch().countDown();\n        }\n        else\n        {\n            aeron.removeCloseHandler(closeHandlerRegistrationId);\n            aeron.removeUnavailableCounterHandler(unavailableCounterHandlerRegistrationId);\n\n            if (!ctx.ownsAeronClient())\n            {\n                for (final Subscription subscription : recordingSubscriptionByKeyMap.values())\n                {\n                    subscription.close();\n                }\n\n                CloseHelper.close(localControlSubscription);\n                CloseHelper.close(controlSubscription);\n                CloseHelper.close(recordingEventsProxy);\n            }\n        }\n\n        markFile.signalTerminated();\n        ctx.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected void abort()\n    {\n        try\n        {\n            isAbort = true;\n\n            if (null != recorder)\n            {\n                recorder.abort();\n            }\n\n            if (null != replayer)\n            {\n                replayer.abort();\n            }\n\n            ctx.errorCounter().close();\n            if (!ctx.abortLatch().await(AgentRunner.RETRY_CLOSE_TIMEOUT_MS * 3L, TimeUnit.MILLISECONDS))\n            {\n                errorHandler.onError(new TimeoutException(\"awaiting abort latch\", AeronException.Category.WARN));\n            }\n        }\n        catch (final InterruptedException ignore)\n        {\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        final long nowNs = nanoClock.nanoTime();\n        int workCount = 0;\n\n        if (isAbort)\n        {\n            throw new AgentTerminationException(\"unexpected Aeron close\");\n        }\n\n        dutyCycleTracker.measureAndUpdate(nowNs);\n\n        final long nowMs = epochClock.time();\n        if (cachedEpochClock.time() != nowMs)\n        {\n            cachedEpochClock.update(nowMs);\n            workCount += invokeAeronInvoker();\n\n            if (nowMs >= markFileUpdateDeadlineMs)\n            {\n                markFileUpdateDeadlineMs = nowMs + MARK_FILE_UPDATE_INTERVAL_MS;\n                markFile.updateActivityTimestamp(nowMs);\n            }\n        }\n\n        workCount += controlSessionAdapter.poll();\n\n        workCount += checkReplayTokens(nowNs);\n        workCount += invokeDriverConductor();\n        workCount += runTasks(taskQueue);\n\n        return workCount + super.doWork();\n    }\n\n    Archive.Context context()\n    {\n        return ctx;\n    }\n\n    ControlResponseProxy controlResponseProxy()\n    {\n        return controlResponseProxy;\n    }\n\n    final int invokeAeronInvoker()\n    {\n        int workCount = 0;\n\n        if (null != aeronAgentInvoker)\n        {\n            workCount += aeronAgentInvoker.invoke();\n\n            if (isAbort || aeronAgentInvoker.isClosed())\n            {\n                isAbort = true;\n                throw new AgentTerminationException(\"unexpected Aeron close\");\n            }\n        }\n\n        return workCount;\n    }\n\n    final int invokeDriverConductor()\n    {\n        int workCount = 0;\n\n        if (null != driverAgentInvoker)\n        {\n            workCount += driverAgentInvoker.invoke();\n\n            if (driverAgentInvoker.isClosed())\n            {\n                throw new AgentTerminationException(\"unexpected driver close\");\n            }\n        }\n\n        return workCount;\n    }\n\n    void logWarning(final String message)\n    {\n        errorHandler.onError(new ArchiveEvent(message));\n    }\n\n    ControlSession newControlSession(\n        final Image image,\n        final long correlationId,\n        final int streamId,\n        final int version,\n        final String channel,\n        final byte[] encodedCredentials,\n        final String clientInfo,\n        final ControlSessionAdapter controlSessionAdapter)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(channel);\n\n        final String mtuStr = channelUri.get(CommonContext.MTU_LENGTH_PARAM_NAME);\n        final int mtuLength = null == mtuStr ?\n            ctx.controlMtuLength() : (int)SystemUtil.parseSize(MTU_LENGTH_PARAM_NAME, mtuStr);\n        final String termLengthStr = channelUri.get(TERM_LENGTH_PARAM_NAME);\n        final int termLength = null == termLengthStr ?\n            ctx.controlTermBufferLength() : (int)SystemUtil.parseSize(TERM_LENGTH_PARAM_NAME, termLengthStr);\n        final String isSparseStr = channelUri.get(SPARSE_PARAM_NAME);\n        final boolean isSparse = null == isSparseStr ?\n            ctx.controlTermBufferSparse() : Boolean.parseBoolean(isSparseStr);\n        final boolean usingResponseChannel = CONTROL_MODE_RESPONSE.equals(channelUri.get(MDC_CONTROL_MODE_PARAM_NAME));\n\n        final ChannelUriStringBuilder urlBuilder = strippedChannelBuilder(channelUri)\n            .termLength(termLength)\n            .sparse(isSparse)\n            .mtu(mtuLength);\n\n        if (usingResponseChannel)\n        {\n            urlBuilder.responseCorrelationId(image.correlationId());\n        }\n\n        final String responseChannel = urlBuilder.build();\n\n        String invalidVersionMessage = null;\n        if (SemanticVersion.major(version) != AeronArchive.Configuration.PROTOCOL_MAJOR_VERSION)\n        {\n            invalidVersionMessage = \"invalid client version \" + SemanticVersion.toString(version) +\n                \", archive is \" + SemanticVersion.toString(AeronArchive.Configuration.PROTOCOL_SEMANTIC_VERSION);\n        }\n\n        final long controlSessionId = nextSessionId++;\n        final long controlPublicationRegistrationId = aeron.asyncAddExclusivePublication(responseChannel, streamId);\n\n        final String imageInfo = \"sourceIdentity=\" + image.sourceIdentity() + \" sessionId=\" + image.sessionId();\n        final long sessionCounterRegistrationId = ControlSessionCounter.allocate(\n            aeron,\n            tempBuffer,\n            ctx.archiveId(),\n            controlSessionId,\n            Strings.isEmpty(clientInfo) ? imageInfo : clientInfo + \" \" + imageInfo);\n\n        final ControlSession controlSession = new ControlSession(\n            controlSessionId,\n            correlationId,\n            connectTimeoutMs,\n            sessionLivenessCheckIntervalMs,\n            controlPublicationRegistrationId,\n            sessionCounterRegistrationId,\n            responseChannel,\n            streamId,\n            invalidVersionMessage,\n            controlSessionAdapter,\n            aeron,\n            this,\n            cachedEpochClock,\n            controlResponseProxy,\n            authenticator,\n            controlSessionProxy);\n\n        authenticator.onConnectRequest(controlSession.sessionId(), encodedCredentials, cachedEpochClock.time());\n\n        addSession(controlSession);\n        ctx.controlSessionsCounter().incrementRelease();\n\n        return controlSession;\n    }\n\n    void archiveId(final long correlationId, final ControlSession controlSession)\n    {\n        controlSession.sendOkResponse(correlationId, ctx.archiveId());\n    }\n\n    void startRecording(\n        final long correlationId,\n        final int streamId,\n        final SourceLocation sourceLocation,\n        final boolean autoStop,\n        final String originalChannel,\n        final ControlSession controlSession)\n    {\n        if (recordingSessionByIdMap.size() >= ctx.maxConcurrentRecordings())\n        {\n            final String msg = \"max concurrent recordings reached \" + ctx.maxConcurrentRecordings();\n            controlSession.sendErrorResponse(correlationId, MAX_RECORDINGS, msg);\n            return;\n        }\n\n        if (null != isLowStorageSpace(correlationId, controlSession))\n        {\n            return;\n        }\n\n        try\n        {\n            final ChannelUri channelUri = ChannelUri.parse(originalChannel);\n            final String key = makeKey(streamId, channelUri);\n            final Subscription oldSubscription = recordingSubscriptionByKeyMap.get(key);\n\n            if (null == oldSubscription)\n            {\n                final String strippedChannel = strippedChannelBuilder(channelUri).build();\n                final String channel = sourceLocation == SourceLocation.LOCAL && channelUri.isUdp() ?\n                    SPY_PREFIX + strippedChannel : strippedChannel;\n\n                final AvailableImageHandler handler = (image) -> taskQueue.addLast(() -> startRecordingSession(\n                    controlSession, correlationId, strippedChannel, originalChannel, image, autoStop));\n\n                final Subscription subscription = aeron.addSubscription(channel, streamId, handler, null);\n\n                recordingSubscriptionByKeyMap.put(key, subscription);\n                subscriptionRefCountMap.incrementAndGet(subscription.registrationId());\n                controlSession.sendOkResponse(correlationId, subscription.registrationId());\n            }\n            else\n            {\n                final String msg = \"recording exists for streamId=\" + streamId + \" channel=\" + originalChannel;\n                controlSession.sendErrorResponse(correlationId, ACTIVE_SUBSCRIPTION, msg);\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n            controlSession.sendErrorResponse(correlationId, ex.getMessage());\n        }\n    }\n\n    void stopRecording(\n        final long correlationId, final int streamId, final String channel, final ControlSession controlSession)\n    {\n        try\n        {\n            final String key = makeKey(streamId, ChannelUri.parse(channel));\n            final Subscription subscription = recordingSubscriptionByKeyMap.remove(key);\n\n            if (null != subscription)\n            {\n                abortRecordingSessionAndCloseSubscription(subscription);\n\n                controlSession.sendOkResponse(correlationId);\n            }\n            else\n            {\n                final String msg = \"no recording found for streamId=\" + streamId + \" channel=\" + channel;\n                controlSession.sendErrorResponse(correlationId, UNKNOWN_SUBSCRIPTION, msg);\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n            controlSession.sendErrorResponse(correlationId, ex.getMessage());\n        }\n    }\n\n    void stopRecordingSubscription(\n        final long correlationId, final long subscriptionId, final ControlSession controlSession)\n    {\n        if (stopRecordingSubscription(subscriptionId))\n        {\n            controlSession.sendOkResponse(correlationId);\n        }\n        else\n        {\n            final String msg = \"no recording subscription found for subscriptionId=\" + subscriptionId;\n            controlSession.sendErrorResponse(correlationId, UNKNOWN_SUBSCRIPTION, msg);\n        }\n    }\n\n    boolean stopRecordingSubscription(final long subscriptionId)\n    {\n        final Subscription subscription = removeRecordingSubscription(subscriptionId);\n        if (null != subscription)\n        {\n            abortRecordingSessionAndCloseSubscription(subscription);\n            return true;\n        }\n\n        return false;\n    }\n\n    void newListRecordingsSession(\n        final long correlationId, final long fromId, final int count, final ControlSession controlSession)\n    {\n        if (controlSession.hasActiveListing())\n        {\n            final String msg = \"active listing already in progress\";\n            controlSession.sendErrorResponse(correlationId, ACTIVE_LISTING, msg);\n            return;\n        }\n\n        final ListRecordingsSession session = new ListRecordingsSession(\n            correlationId,\n            fromId,\n            count,\n            catalog,\n            controlSession,\n            descriptorBuffer);\n        addSession(session);\n        controlSession.activeListing(session);\n    }\n\n    void newListRecordingsForUriSession(\n        final long correlationId,\n        final long fromRecordingId,\n        final int count,\n        final int streamId,\n        final byte[] channelFragment,\n        final ControlSession controlSession)\n    {\n        if (controlSession.hasActiveListing())\n        {\n            final String msg = \"active listing already in progress\";\n            controlSession.sendErrorResponse(correlationId, ACTIVE_LISTING, msg);\n            return;\n        }\n\n        final ListRecordingsForUriSession session = new ListRecordingsForUriSession(\n            correlationId,\n            fromRecordingId,\n            count,\n            channelFragment,\n            streamId,\n            catalog,\n            controlSession,\n            descriptorBuffer,\n            recordingDescriptorDecoder);\n        addSession(session);\n        controlSession.activeListing(session);\n    }\n\n    void listRecording(final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        if (controlSession.hasActiveListing())\n        {\n            final String msg = \"active listing already in progress\";\n            controlSession.sendErrorResponse(correlationId, ACTIVE_LISTING, msg);\n        }\n        else if (!catalog.hasRecording(recordingId))\n        {\n            controlSession.sendRecordingUnknown(correlationId, recordingId);\n        }\n        else\n        {\n            final ListRecordingByIdSession session =\n                new ListRecordingByIdSession(correlationId, recordingId, catalog, controlSession, descriptorBuffer);\n            addSession(session);\n            controlSession.activeListing(session);\n        }\n    }\n\n    void updateChannel(\n        final long correlationId,\n        final long recordingId,\n        final String channel,\n        final ControlSession controlSession)\n    {\n        if (controlSession.hasActiveListing())\n        {\n            final String msg = \"active listing already in progress\";\n            controlSession.sendErrorResponse(correlationId, ACTIVE_LISTING, msg);\n        }\n        else if (!catalog.hasRecording(recordingId))\n        {\n            controlSession.sendRecordingUnknown(correlationId, recordingId);\n        }\n        else\n        {\n            final ChannelUri channelUri = ChannelUri.parse(channel);\n            final String strippedChannel = strippedChannelBuilder(channelUri).build();\n\n            final UpdateChannelSession updateChannelSession = new UpdateChannelSession(\n                correlationId,\n                recordingId,\n                channel,\n                strippedChannel,\n                catalog,\n                controlSession,\n                descriptorBuffer);\n\n            addSession(updateChannelSession);\n            controlSession.activeListing(updateChannelSession);\n        }\n    }\n\n    void findLastMatchingRecording(\n        final long correlationId,\n        final long minRecordingId,\n        final int sessionId,\n        final int streamId,\n        final byte[] channelFragment,\n        final ControlSession controlSession)\n    {\n        if (minRecordingId < 0)\n        {\n            final String msg = \"minRecordingId=\" + minRecordingId + \" < 0\";\n            controlSession.sendErrorResponse(correlationId, UNKNOWN_RECORDING, msg);\n        }\n        else\n        {\n            final long recordingId = catalog.findLast(minRecordingId, sessionId, streamId, channelFragment);\n            // If not found, recordingId is -1, which matches client side specification.\n            controlSession.sendOkResponse(correlationId, recordingId);\n        }\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    void startReplay(\n        final long correlationId,\n        final long recordingId,\n        final long position,\n        final long length,\n        final int fileIoMaxLength,\n        final int replayStreamId,\n        final String replayChannel,\n        final Counter limitPositionCounter,\n        final ControlSession controlSession)\n    {\n        if (replaySessionByIdMap.size() >= ctx.maxConcurrentReplays())\n        {\n            final String msg = \"max concurrent replays reached \" + ctx.maxConcurrentReplays();\n            controlSession.sendErrorResponse(correlationId, MAX_REPLAYS, msg);\n            return;\n        }\n\n        if (!catalog.hasRecording(recordingId))\n        {\n            final String msg = \"unknown recording id: \" + recordingId;\n            controlSession.sendErrorResponse(correlationId, UNKNOWN_RECORDING, msg);\n            return;\n        }\n\n        Counter replayLimitPositionCounter = limitPositionCounter;\n        if (null == replayLimitPositionCounter)\n        {\n            final RecordingSession recordingSession = recordingSessionByIdMap.get(recordingId);\n            if (null != recordingSession)\n            {\n                replayLimitPositionCounter = recordingSession.recordingPosition();\n            }\n        }\n        long limitPosition = NULL_POSITION;\n        if (null != replayLimitPositionCounter)\n        {\n            limitPosition = replayLimitPositionCounter.get();\n        }\n\n        catalog.recordingSummary(recordingId, recordingSummary);\n        final long startPosition = recordingSummary.startPosition;\n        long replayPosition = startPosition;\n        if (NULL_POSITION != position)\n        {\n            if (isInvalidReplayPosition(correlationId, controlSession, recordingId, position, recordingSummary))\n            {\n                return;\n            }\n            replayPosition = position;\n        }\n\n        if (fileIoMaxLength > 0 && fileIoMaxLength < recordingSummary.mtuLength)\n        {\n            final String msg = \"fileIoMaxLength=\" + fileIoMaxLength + \" < mtuLength=\" + recordingSummary.mtuLength;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return;\n        }\n        else if (ctx.replayBuffer().capacity() < recordingSummary.mtuLength)\n        {\n            final int replayBufferCapacity = ctx.replayBuffer().capacity();\n            final String msg = \"replayBufferCapacity=\" + replayBufferCapacity +\n                \" < mtuLength=\" + recordingSummary.mtuLength;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return;\n        }\n\n        final long stopPosition;\n        final long maxLength;\n        if (NULL_POSITION != limitPosition)\n        {\n            if (replayPosition > limitPosition)\n            {\n                final String msg = \"requested replay start position=\" + replayPosition +\n                    \" must be less than the limit position=\" + limitPosition + \" for recording \" + recordingId;\n                controlSession.sendErrorResponse(correlationId, msg);\n                return;\n            }\n            stopPosition = limitPosition;\n            maxLength = Long.MAX_VALUE - replayPosition;\n        }\n        else\n        {\n            stopPosition = recordingSummary.stopPosition;\n            maxLength = stopPosition - replayPosition;\n        }\n\n        final long replayLength;\n        if (AeronArchive.REPLAY_ALL_AND_FOLLOW == length)\n        {\n            replayLength = maxLength;\n        }\n        else if (AeronArchive.REPLAY_ALL_AND_STOP == length)\n        {\n            replayLength = (stopPosition - replayPosition);\n            if (0 == replayLength)\n            {\n                final String msg =\n                    \"When replaying and stopping the replay length must be non-zero, recordingId=\" + recordingId;\n                controlSession.sendErrorResponse(correlationId, EMPTY_RECORDING, msg);\n            }\n        }\n        else\n        {\n            replayLength = min(length, maxLength);\n        }\n\n        if (replayLength < 0)\n        {\n            final String msg = \"replay length must be positive: replayLength=\" + replayLength + \", length=\" + length +\n                \", stopPosition=\" + stopPosition + \", replayPosition=\" + replayPosition + \" for recording \" +\n                recordingId;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return;\n        }\n\n        try\n        {\n            final ChannelUri channelUri = ChannelUri.parse(replayChannel);\n            final ChannelUriStringBuilder channelBuilder = strippedChannelBuilder(channelUri)\n                .initialPosition(replayPosition, recordingSummary.initialTermId, recordingSummary.termBufferLength)\n                .eos(channelUri)\n                .sparse(channelUri)\n                .mtu(recordingSummary.mtuLength);\n\n            final String lingerValue = channelUri.get(CommonContext.LINGER_PARAM_NAME);\n            channelBuilder.linger(null != lingerValue ? Long.parseLong(lingerValue) : ctx.replayLingerTimeoutNs());\n\n            addSession(new CreateReplayPublicationSession(\n                correlationId,\n                recordingId,\n                replayPosition,\n                replayLength,\n                startPosition,\n                stopPosition,\n                recordingSummary.segmentFileLength,\n                recordingSummary.termBufferLength,\n                recordingSummary.streamId,\n                aeron.asyncAddExclusivePublication(channelBuilder.build(), replayStreamId),\n                fileIoMaxLength,\n                replayLimitPositionCounter,\n                aeron,\n                controlSession,\n                this));\n        }\n        catch (final Exception ex)\n        {\n            final String msg = \"failed to process replayChannel - \" + ex.getMessage();\n            controlSession.sendErrorResponse(correlationId, msg);\n            throw ex;\n        }\n    }\n\n    void newReplaySession(\n        final long correlationId,\n        final long recordingId,\n        final long replayPosition,\n        final long replayLength,\n        final long startPosition,\n        final long stopPosition,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int streamId,\n        final int fileIoMaxLength,\n        final ControlSession controlSession,\n        final Counter replayLimitPosition,\n        final ExclusivePublication replayPublication)\n    {\n        final long replaySessionId = ((long)(replayId++) << 32) | (replayPublication.sessionId() & 0xFFFF_FFFFL);\n\n        final UnsafeBuffer replayBuffer;\n        if (0 < fileIoMaxLength && fileIoMaxLength < ctx.replayBuffer().capacity())\n        {\n            replayBuffer = new UnsafeBuffer(ctx.replayBuffer(), 0, fileIoMaxLength);\n        }\n        else\n        {\n            replayBuffer = ctx.replayBuffer();\n        }\n\n        final ReplaySession replaySession = new ReplaySession(\n            correlationId,\n            recordingId,\n            replayPosition,\n            replayLength,\n            startPosition,\n            stopPosition,\n            segmentFileLength,\n            termBufferLength,\n            streamId,\n            replaySessionId,\n            connectTimeoutMs,\n            controlSession,\n            replayBuffer,\n            archiveDir,\n            cachedEpochClock,\n            nanoClock,\n            replayPublication,\n            aeron.countersReader(),\n            replayLimitPosition,\n            ctx.replayChecksum(),\n            replayer);\n\n        replaySessionByIdMap.put(replaySessionId, replaySession);\n        replayer.addSession(replaySession);\n        ctx.replaySessionCounter().incrementRelease();\n    }\n\n    void startBoundedReplay(\n        final long correlationId,\n        final long recordingId,\n        final long position,\n        final long length,\n        final int limitCounterId,\n        final int fileIoMaxLength,\n        final int replayStreamId,\n        final String replayChannel,\n        final ControlSession controlSession)\n    {\n        Counter replayLimitCounter = counterByIdMap.get(limitCounterId);\n        if (null == replayLimitCounter)\n        {\n            try\n            {\n                replayLimitCounter = new Counter(aeron.countersReader(), NULL_VALUE, limitCounterId);\n            }\n            catch (final Exception ex)\n            {\n                final String msg = \"unable to create replay limit counter id= \" + limitCounterId +\n                    \" because of: \" + ex.getMessage();\n                controlSession.sendErrorResponse(correlationId, GENERIC, msg);\n                return;\n            }\n\n            counterByIdMap.put(limitCounterId, replayLimitCounter);\n        }\n\n        startReplay(\n            correlationId,\n            recordingId,\n            position,\n            length,\n            fileIoMaxLength,\n            replayStreamId,\n            replayChannel,\n            replayLimitCounter,\n            controlSession);\n    }\n\n    void stopReplay(final long correlationId, final long replaySessionId, final ControlSession controlSession)\n    {\n        final ReplaySession replaySession = replaySessionByIdMap.get(replaySessionId);\n        if (null != replaySession)\n        {\n            replaySession.abort(\"stop replay\");\n        }\n\n        controlSession.sendOkResponse(correlationId);\n    }\n\n    void stopAllReplays(final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        for (final ReplaySession replaySession : replaySessionByIdMap.values())\n        {\n            if (NULL_VALUE == recordingId || replaySession.recordingId() == recordingId)\n            {\n                replaySession.abort(\"stop all replays\");\n            }\n        }\n\n        controlSession.sendOkResponse(correlationId);\n    }\n\n    /* Returns a Subscription or a String error message indicating why the subscription couldn't be acquired */\n    Object extendRecording(\n        final long correlationId,\n        final long recordingId,\n        final int streamId,\n        final SourceLocation sourceLocation,\n        final boolean autoStop,\n        final String originalChannel,\n        final ControlSession controlSession)\n    {\n        if (recordingSessionByIdMap.size() >= ctx.maxConcurrentRecordings())\n        {\n            final String msg = \"max concurrent recordings reached at \" + ctx.maxConcurrentRecordings();\n            controlSession.sendErrorResponse(correlationId, MAX_RECORDINGS, msg);\n            return msg;\n        }\n\n        if (!catalog.hasRecording(recordingId))\n        {\n            final String msg = \"unknown recording id: \" + recordingId;\n            controlSession.sendErrorResponse(correlationId, UNKNOWN_RECORDING, msg);\n            return msg;\n        }\n\n        catalog.recordingSummary(recordingId, recordingSummary);\n        if (streamId != recordingSummary.streamId)\n        {\n            final String msg = \"cannot extend recording \" + recordingSummary.recordingId +\n                \" with streamId=\" + streamId + \" for existing streamId=\" + recordingSummary.streamId;\n            controlSession.sendErrorResponse(correlationId, UNKNOWN_RECORDING, msg);\n            return msg;\n        }\n\n        if (recordingSessionByIdMap.containsKey(recordingId))\n        {\n            final String msg = \"cannot extend active recording \" + recordingId;\n            controlSession.sendErrorResponse(correlationId, ACTIVE_RECORDING, msg);\n            return msg;\n        }\n\n        final DeleteSegmentsSession deleteSegmentsSession = deleteSegmentsSessionByIdMap.get(recordingId);\n        if (null != deleteSegmentsSession && deleteSegmentsSession.maxDeletePosition() >= recordingSummary.stopPosition)\n        {\n            final String msg = \"cannot extend recording \" + recordingId + \" due to an outstanding delete operation\";\n            controlSession.sendErrorResponse(correlationId, msg);\n            return msg;\n        }\n\n        final String lowStorageSpaceMsg = isLowStorageSpace(correlationId, controlSession);\n        if (null != lowStorageSpaceMsg)\n        {\n            return lowStorageSpaceMsg;\n        }\n\n        try\n        {\n            final ChannelUri channelUri = ChannelUri.parse(originalChannel);\n            final String key = makeKey(streamId, channelUri);\n            final Subscription oldSubscription = recordingSubscriptionByKeyMap.get(key);\n\n            if (null == oldSubscription)\n            {\n                final String strippedChannel = strippedChannelBuilder(channelUri).build();\n                final String channel = originalChannel.contains(\"udp\") && sourceLocation == SourceLocation.LOCAL ?\n                    SPY_PREFIX + strippedChannel : strippedChannel;\n\n                final AvailableImageHandler handler = (image) -> taskQueue.addLast(() -> extendRecordingSession(\n                    controlSession, correlationId, recordingId, strippedChannel, originalChannel, image, autoStop));\n\n                final Subscription subscription = aeron.addSubscription(channel, streamId, handler, null);\n\n                recordingSubscriptionByKeyMap.put(key, subscription);\n                subscriptionRefCountMap.incrementAndGet(subscription.registrationId());\n                controlSession.sendOkResponse(correlationId, subscription.registrationId());\n\n                return subscription;\n            }\n            else\n            {\n                final String msg = \"recording exists for streamId=\" + streamId + \" channel=\" + originalChannel;\n                controlSession.sendErrorResponse(correlationId, ACTIVE_SUBSCRIPTION, msg);\n                return msg;\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n            controlSession.sendErrorResponse(correlationId, ex.getMessage());\n            return ex.getMessage();\n        }\n    }\n\n    void getStartPosition(final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession))\n        {\n            controlSession.sendOkResponse(correlationId, catalog.startPosition(recordingId));\n        }\n    }\n\n    void getRecordingPosition(final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession))\n        {\n            final RecordingSession recordingSession = recordingSessionByIdMap.get(recordingId);\n            final long position = null == recordingSession ? NULL_POSITION : recordingSession.recordingPosition().get();\n\n            controlSession.sendOkResponse(correlationId, position);\n        }\n    }\n\n    void getStopPosition(final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession))\n        {\n            controlSession.sendOkResponse(correlationId, catalog.stopPosition(recordingId));\n        }\n    }\n\n    void getMaxRecordedPosition(\n        final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession))\n        {\n            final RecordingSession recordingSession = recordingSessionByIdMap.get(recordingId);\n            final long maxRecordedPosition = null != recordingSession ?\n                recordingSession.recordingPosition().get() : catalog.stopPosition(recordingId);\n\n            controlSession.sendOkResponse(correlationId, maxRecordedPosition);\n        }\n    }\n\n    void truncateRecording(\n        final long correlationId, final long recordingId, final long position, final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession) &&\n            isValidTruncate(correlationId, controlSession, recordingId, position) &&\n            isDeleteAllowed(recordingId, correlationId, controlSession))\n        {\n            final long stopPosition = recordingSummary.stopPosition;\n            final int segmentLength = recordingSummary.segmentFileLength;\n            final int termLength = recordingSummary.termBufferLength;\n            final long startPosition = recordingSummary.startPosition;\n            final long segmentBasePosition = segmentFileBasePosition(\n                startPosition, position, termLength, segmentLength);\n            final int segmentOffset = (int)(position - segmentBasePosition);\n\n            catalog.stopPosition(recordingId, position);\n\n            final ArrayDeque<String> files = new ArrayDeque<>();\n            if (startPosition == position)\n            {\n                listSegmentFiles(recordingId, files::addLast);\n            }\n            else\n            {\n                if (segmentOffset > 0)\n                {\n                    if (stopPosition != position)\n                    {\n                        final File file = new File(archiveDir, segmentFileName(recordingId, segmentBasePosition));\n                        if (!eraseRemainingSegment(\n                            correlationId, controlSession, position, segmentLength, segmentOffset, termLength, file))\n                        {\n                            return;\n                        }\n                    }\n                }\n                else\n                {\n                    files.addLast(segmentFileName(recordingId, segmentBasePosition));\n                }\n\n                for (long p = segmentBasePosition + segmentLength; p <= stopPosition; p += segmentLength)\n                {\n                    files.addLast(segmentFileName(recordingId, p));\n                }\n            }\n\n            deleteSegments(correlationId, recordingId, controlSession, files);\n        }\n    }\n\n    void purgeRecording(final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession) &&\n            isValidPurge(correlationId, controlSession, recordingId) &&\n            isDeleteAllowed(recordingId, correlationId, controlSession))\n        {\n            catalog.changeState(recordingId, DELETED);\n\n            final ArrayDeque<String> files = new ArrayDeque<>();\n            listSegmentFiles(recordingId, files::addLast);\n\n            deleteSegments(correlationId, recordingId, controlSession, files);\n        }\n    }\n\n    void listRecordingSubscriptions(\n        final long correlationId,\n        final int pseudoIndex,\n        final int subscriptionCount,\n        final boolean applyStreamId,\n        final int streamId,\n        final String channelFragment,\n        final ControlSession controlSession)\n    {\n        if (controlSession.hasActiveListing())\n        {\n            final String msg = \"active listing already in progress\";\n            controlSession.sendErrorResponse(correlationId, ACTIVE_LISTING, msg);\n        }\n        else if (pseudoIndex < 0 || pseudoIndex >= recordingSubscriptionByKeyMap.size() || subscriptionCount <= 0)\n        {\n            controlSession.sendSubscriptionUnknown(correlationId);\n        }\n        else\n        {\n            final ListRecordingSubscriptionsSession session = new ListRecordingSubscriptionsSession(\n                recordingSubscriptionByKeyMap,\n                pseudoIndex,\n                subscriptionCount,\n                streamId,\n                applyStreamId,\n                channelFragment,\n                correlationId,\n                controlSession\n            );\n            addSession(session);\n            controlSession.activeListing(session);\n        }\n    }\n\n    void stopRecordingByIdentity(final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession))\n        {\n            int found = 0;\n            final RecordingSession recordingSession = recordingSessionByIdMap.get(recordingId);\n\n            if (null != recordingSession)\n            {\n                recordingSession.abort(\"stop recording by identity\");\n\n                final long subscriptionId = recordingSession.subscription().registrationId();\n                final Subscription subscription = removeRecordingSubscription(subscriptionId);\n\n                if (null != subscription)\n                {\n                    found = 1;\n                    if (0 == subscriptionRefCountMap.decrementAndGet(subscriptionId))\n                    {\n                        subscription.close();\n                    }\n                }\n            }\n\n            controlSession.sendOkResponse(correlationId, found);\n        }\n    }\n\n    void closeRecordingSession(final RecordingSession session)\n    {\n        if (isAbort)\n        {\n            session.abortClose();\n        }\n        else\n        {\n            final Subscription subscription = session.subscription();\n            final long position = session.recordedPosition();\n            final long recordingId = session.sessionId();\n            final long subscriptionId = subscription.registrationId();\n\n            try\n            {\n                catalog.recordingStopped(recordingId, position, epochClock.time());\n                recordingSessionByIdMap.remove(recordingId);\n                session.sendPendingError();\n                session.controlSession().sendSignal(\n                    session.correlationId(),\n                    recordingId,\n                    subscriptionId,\n                    position,\n                    RecordingSignal.STOP);\n            }\n            catch (final Exception ex)\n            {\n                errorHandler.onError(ex);\n            }\n\n            if (subscriptionRefCountMap.decrementAndGet(subscriptionId) <= 0 || session.isAutoStop())\n            {\n                closeAndRemoveRecordingSubscription(subscription, \"close recording session\");\n            }\n            closeSession(session);\n            ctx.recordingSessionCounter().decrementRelease();\n        }\n    }\n\n    void closeReplaySession(final ReplaySession session)\n    {\n        if (!isAbort)\n        {\n            try\n            {\n                session.sendPendingError();\n            }\n            catch (final Exception ex)\n            {\n                errorHandler.onError(ex);\n            }\n        }\n\n        replaySessionByIdMap.remove(session.sessionId());\n        closeSession(session);\n        ctx.replaySessionCounter().decrementRelease();\n    }\n\n    void replicate(\n        final long correlationId,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long stopPosition,\n        final long channelTagId,\n        final long subscriptionTagId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination,\n        final String replicationChannel,\n        final int fileIoMaxLength,\n        final int replicationSessionId,\n        final byte[] encodedCredentials,\n        final String srcResponseChannel,\n        final ControlSession controlSession)\n    {\n        final String replicationChannel0 = Strings.isEmpty(replicationChannel) ?\n            ctx.replicationChannel() : replicationChannel;\n\n        final ChannelUri replicationChannelUri = ChannelUri.parse(replicationChannel0);\n        if (replicationChannelUri.hasControlModeResponse() && !Strings.isEmpty(liveDestination))\n        {\n            final String msg = \"response channels can't be used with live destinations\";\n            controlSession.sendErrorResponse(correlationId, GENERIC, msg);\n            return;\n        }\n\n        if (replicationChannelUri.hasControlModeResponse() &&\n            (NULL_VALUE != channelTagId || NULL_VALUE != subscriptionTagId))\n        {\n            final String msg = \"response channels can't be used with tagged replication\";\n            controlSession.sendErrorResponse(correlationId, GENERIC, msg);\n            return;\n        }\n\n        final boolean hasRecording = catalog.hasRecording(dstRecordingId);\n        if (NULL_VALUE != dstRecordingId && !hasRecording)\n        {\n            final String msg = \"unknown destination recording id \" + dstRecordingId;\n            controlSession.sendErrorResponse(correlationId, UNKNOWN_RECORDING, msg);\n            return;\n        }\n\n        if (hasRecording)\n        {\n            catalog.recordingSummary(dstRecordingId, recordingSummary);\n\n            if (NULL_POSITION == recordingSummary.stopPosition || recordingSessionByIdMap.containsKey(dstRecordingId))\n            {\n                final String msg = \"cannot replicate to active recording \" + dstRecordingId;\n                controlSession.sendErrorResponse(correlationId, ACTIVE_RECORDING, msg);\n                return;\n            }\n        }\n\n        final AeronArchive.Context remoteArchiveContext = ctx.archiveClientContext().clone()\n            .controlRequestChannel(srcControlChannel)\n            .controlRequestStreamId(srcControlStreamId);\n\n        if (null != encodedCredentials && 0 < encodedCredentials.length)\n        {\n            remoteArchiveContext.credentialsSupplier(new ReplicationCredentialsSupplier(encodedCredentials));\n        }\n\n        if (!Strings.isEmpty(srcResponseChannel))\n        {\n            remoteArchiveContext.controlResponseChannel(srcResponseChannel);\n        }\n\n        final long replicationId = nextSessionId++;\n        remoteArchiveContext.clientName(remoteArchiveContext.clientName() + \" replicationSessionId=\" + replicationId);\n        final ReplicationSession replicationSession = new ReplicationSession(\n            srcRecordingId,\n            dstRecordingId,\n            channelTagId,\n            subscriptionTagId,\n            replicationId,\n            stopPosition,\n            liveDestination,\n            replicationChannel0,\n            fileIoMaxLength,\n            replicationSessionId,\n            hasRecording ? recordingSummary : null,\n            remoteArchiveContext,\n            cachedEpochClock,\n            catalog,\n            controlSession);\n\n        replicationSessionByIdMap.put(replicationId, replicationSession);\n        addSession(replicationSession);\n\n        controlSession.sendOkResponse(correlationId, replicationId);\n    }\n\n    void stopReplication(final long correlationId, final long replicationId, final ControlSession controlSession)\n    {\n        final ReplicationSession session = replicationSessionByIdMap.remove(replicationId);\n        if (null == session)\n        {\n            final String msg = \"unknown replication id \" + replicationId;\n            controlSession.sendErrorResponse(correlationId, UNKNOWN_REPLICATION, msg);\n        }\n        else\n        {\n            session.abort(\"stop replication\");\n            controlSession.sendOkResponse(correlationId);\n        }\n    }\n\n    void detachSegments(\n        final long correlationId,\n        final long recordingId,\n        final long newStartPosition,\n        final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession) &&\n            isValidDetach(correlationId, controlSession, recordingId, newStartPosition))\n        {\n            catalog.startPosition(recordingId, newStartPosition);\n            controlSession.sendOkResponse(correlationId);\n        }\n    }\n\n    void deleteDetachedSegments(final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession) &&\n            isDeleteAllowed(recordingId, correlationId, controlSession))\n        {\n            final MutableLong minPosition = new MutableLong(Long.MAX_VALUE);\n            final int prefixLength = digitCount(recordingId) + 1;\n            listSegmentFiles(\n                recordingId,\n                (segmentFile) ->\n                {\n                    final int dotIndex = segmentFile.indexOf('.');\n                    final long filePosition =\n                        AsciiEncoding.parseLongAscii(segmentFile, prefixLength, dotIndex - prefixLength);\n                    minPosition.set(Math.min(minPosition.get(), filePosition));\n                });\n\n            final ArrayDeque<String> files = new ArrayDeque<>();\n            if (Long.MAX_VALUE != minPosition.get())\n            {\n                findDetachedSegments(recordingId, files, minPosition.get());\n            }\n            deleteSegments(correlationId, recordingId, controlSession, files);\n        }\n    }\n\n    void purgeSegments(\n        final long correlationId,\n        final long recordingId,\n        final long newStartPosition,\n        final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession) &&\n            isValidDetach(correlationId, controlSession, recordingId, newStartPosition) &&\n            isDeleteAllowed(recordingId, correlationId, controlSession))\n        {\n            catalog.recordingSummary(recordingId, recordingSummary);\n            final long oldStartPosition = recordingSummary.startPosition;\n\n            catalog.startPosition(recordingId, newStartPosition);\n\n            final ArrayDeque<String> files = new ArrayDeque<>();\n            findDetachedSegments(recordingId, files, oldStartPosition);\n            deleteSegments(correlationId, recordingId, controlSession, files);\n        }\n    }\n\n    void attachSegments(final long correlationId, final long recordingId, final ControlSession controlSession)\n    {\n        if (hasRecording(recordingId, correlationId, controlSession))\n        {\n            catalog.recordingSummary(recordingId, recordingSummary);\n            final int segmentLength = recordingSummary.segmentFileLength;\n            final int termLength = recordingSummary.termBufferLength;\n            final int bitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n            final int streamId = recordingSummary.streamId;\n            long position = recordingSummary.startPosition - segmentLength;\n            long count = 0;\n\n            while (position >= 0)\n            {\n                final File file = new File(archiveDir, segmentFileName(recordingId, position));\n                if (!file.exists())\n                {\n                    break;\n                }\n\n                final long fileLength = file.length();\n                if (fileLength != segmentLength)\n                {\n                    final String msg = \"fileLength=\" + fileLength + \" not equal to segmentLength=\" + segmentLength;\n                    controlSession.sendErrorResponse(correlationId, msg);\n                    return;\n                }\n\n                try (FileChannel fileChannel = FileChannel.open(file.toPath(), FILE_OPTIONS))\n                {\n                    final int termCount = (int)(position >> bitsToShift);\n                    final int termId = recordingSummary.initialTermId + termCount;\n                    final int termOffset = findTermOffsetForStart(\n                        correlationId, controlSession, file, fileChannel, streamId, termId, termLength);\n\n                    if (termOffset < 0)\n                    {\n                        return;\n                    }\n                    else if (0 == termOffset)\n                    {\n                        catalog.startPosition(recordingId, position);\n                        count += 1;\n                        position -= segmentLength;\n                    }\n                    else\n                    {\n                        catalog.startPosition(recordingId, position + termOffset);\n                        count += 1;\n                        break;\n                    }\n                }\n                catch (final IOException ex)\n                {\n                    controlSession.sendErrorResponse(correlationId, ex.getMessage());\n                    LangUtil.rethrowUnchecked(ex);\n                }\n            }\n\n            controlSession.sendOkResponse(correlationId, count);\n        }\n    }\n\n    void migrateSegments(\n        final long correlationId,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final ControlSession controlSession)\n    {\n        if (hasRecording(srcRecordingId, correlationId, controlSession) &&\n            hasRecording(dstRecordingId, correlationId, controlSession))\n        {\n            final RecordingSummary srcSummary = catalog.recordingSummary(srcRecordingId, recordingSummary);\n            final RecordingSummary dstSummary = catalog.recordingSummary(dstRecordingId, new RecordingSummary());\n\n            if (isActiveRecording(controlSession, correlationId, srcSummary) ||\n                !hasMatchingStreamParameters(controlSession, correlationId, srcSummary, dstSummary))\n            {\n                return;\n            }\n\n            final long joinPosition;\n\n            if (srcSummary.stopPosition == dstSummary.startPosition)\n            {\n                joinPosition = srcSummary.stopPosition;\n            }\n            else if (srcSummary.startPosition == dstSummary.stopPosition)\n            {\n                joinPosition = srcSummary.startPosition;\n            }\n            else\n            {\n                final String msg = \"invalid migrate: src and dst are not contiguous\" +\n                    \" srcStartPosition=\" + srcSummary.startPosition +\n                    \" srcStopPosition=\" + srcSummary.stopPosition +\n                    \" dstStartPosition=\" + dstSummary.startPosition +\n                    \" dstStopPosition=\" + dstSummary.stopPosition;\n                controlSession.sendErrorResponse(correlationId, msg);\n                return;\n            }\n\n            if (isJoinPositionSegmentUnaligned(controlSession, correlationId, \"src\", srcSummary, joinPosition) ||\n                isJoinPositionSegmentUnaligned(controlSession, correlationId, \"dst\", dstSummary, joinPosition))\n            {\n                return;\n            }\n\n            final ArrayDeque<String> emptyFollowingSrcSegment = new ArrayDeque<>();\n\n            final long movedSegmentCount = moveAllSegments(\n                controlSession,\n                correlationId,\n                srcRecordingId,\n                dstRecordingId,\n                srcSummary,\n                emptyFollowingSrcSegment);\n\n            if (movedSegmentCount >= 0)\n            {\n                final int toBeDeletedSegmentCount = addDeleteSegmentsSession(\n                    correlationId, srcRecordingId, controlSession, emptyFollowingSrcSegment);\n\n                if (toBeDeletedSegmentCount >= 0)\n                {\n                    if (srcSummary.stopPosition == dstSummary.startPosition)\n                    {\n                        catalog.startPosition(dstRecordingId, srcSummary.startPosition);\n                    }\n                    else\n                    {\n                        catalog.stopPosition(dstRecordingId, srcSummary.stopPosition);\n                    }\n\n                    catalog.stopPosition(srcRecordingId, srcSummary.startPosition);\n\n                    controlSession.sendOkResponse(correlationId, movedSegmentCount);\n\n                    final boolean hasSegmentsToDelete = toBeDeletedSegmentCount > 0;\n                    if (movedSegmentCount > 0 && !hasSegmentsToDelete)\n                    {\n                        controlSession.sendSignal(\n                            correlationId, srcRecordingId, Aeron.NULL_VALUE, Aeron.NULL_VALUE, RecordingSignal.DELETE);\n                    }\n                }\n            }\n        }\n    }\n\n    void removeReplicationSession(final ReplicationSession replicationSession)\n    {\n        replicationSessionByIdMap.remove(replicationSession.sessionId());\n    }\n\n    void removeDeleteSegmentsSession(final DeleteSegmentsSession deleteSegmentsSession)\n    {\n        deleteSegmentsSessionByIdMap.remove(deleteSegmentsSession.sessionId());\n    }\n\n    private void findDetachedSegments(\n        final long recordingId, final ArrayDeque<String> files, final long prevStartPosition)\n    {\n        catalog.recordingSummary(recordingId, recordingSummary);\n        final int segmentFileLength = recordingSummary.segmentFileLength;\n\n        final long prevSegmentFilePosition = segmentFileBasePosition(\n            prevStartPosition, prevStartPosition, recordingSummary.termBufferLength, segmentFileLength);\n\n        final long startSegmentFilePosition = segmentFileBasePosition(\n            recordingSummary.startPosition,\n            recordingSummary.startPosition,\n            recordingSummary.termBufferLength,\n            segmentFileLength);\n\n        long filenamePosition = startSegmentFilePosition - segmentFileLength;\n\n        while (filenamePosition >= prevSegmentFilePosition)\n        {\n            final String segmentFileName = segmentFileName(recordingId, filenamePosition);\n            files.addFirst(segmentFileName);\n            filenamePosition -= segmentFileLength;\n        }\n    }\n\n    private int addDeleteSegmentsSession(\n        final long correlationId,\n        final long recordingId,\n        final ControlSession controlSession,\n        final ArrayDeque<String> files)\n    {\n        if (files.isEmpty())\n        {\n            return 0;\n        }\n\n        final ArrayDeque<File> deleteList = new ArrayDeque<>(files.size());\n        for (final String name : files)\n        {\n            deleteList.add(new File(archiveDir, name));\n        }\n\n        final DeleteSegmentsSession session = new DeleteSegmentsSession(\n            recordingId, correlationId, deleteList, controlSession, errorHandler);\n        addSession(session);\n        deleteSegmentsSessionByIdMap.put(session.sessionId(), session);\n\n        return files.size();\n    }\n\n    private void abortRecordingSessionAndCloseSubscription(final Subscription subscription)\n    {\n        for (final RecordingSession session : recordingSessionByIdMap.values())\n        {\n            if (subscription == session.subscription())\n            {\n                session.abort(\"stop recording\");\n            }\n        }\n\n        if (0 == subscriptionRefCountMap.decrementAndGet(subscription.registrationId()))\n        {\n            subscription.close();\n        }\n    }\n\n    private int findTermOffsetForStart(\n        final long correlationId,\n        final ControlSession controlSession,\n        final File file,\n        final FileChannel fileChannel,\n        final int streamId,\n        final int termId,\n        final int termLength)\n        throws IOException\n    {\n        int termOffset = 0;\n        final UnsafeBuffer buffer = ctx.dataBuffer();\n        final ByteBuffer byteBuffer = buffer.byteBuffer();\n        byteBuffer.clear().limit(HEADER_LENGTH);\n\n        if (HEADER_LENGTH != fileChannel.read(byteBuffer, 0))\n        {\n            final String msg = \"failed to read segment file\";\n            controlSession.sendErrorResponse(correlationId, msg);\n            return termOffset;\n        }\n\n        final int fragmentLength = fragmentLength(buffer, termOffset);\n        if (fragmentLength <= 0)\n        {\n            boolean found = false;\n            do\n            {\n                byteBuffer.clear().limit(min(termLength - termOffset, byteBuffer.capacity()));\n                final int bytesRead = fileChannel.read(byteBuffer, termOffset);\n                if (bytesRead <= 0)\n                {\n                    final String msg = \"read failed on \" + file;\n                    controlSession.sendErrorResponse(correlationId, msg);\n                    return NULL_VALUE;\n                }\n\n                final int limit = bytesRead - (bytesRead & (FRAME_ALIGNMENT - 1));\n                int offset = 0;\n                while (offset < limit)\n                {\n                    if (fragmentLength(buffer, offset) > 0)\n                    {\n                        found = true;\n                        break;\n                    }\n\n                    offset += FRAME_ALIGNMENT;\n                }\n\n                termOffset += offset;\n            }\n            while (termOffset < termLength && !found);\n        }\n\n        if (termOffset >= termLength)\n        {\n            final String msg = \"fragment not found in first term of segment \" + file;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return NULL_VALUE;\n        }\n\n        final int fileTermId = termId(buffer, termOffset);\n        if (fileTermId != termId)\n        {\n            final String msg = \"term id does not match: actual=\" + fileTermId + \" expected=\" + termId;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return NULL_VALUE;\n        }\n\n        final int fileStreamId = streamId(buffer, termOffset);\n        if (fileStreamId != streamId)\n        {\n            final String msg = \"stream id does not match: actual=\" + fileStreamId + \" expected=\" + streamId;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return NULL_VALUE;\n        }\n\n        return termOffset;\n    }\n\n    private int runTasks(final ArrayDeque<Runnable> taskQueue)\n    {\n        int workCount = 0;\n\n        Runnable runnable;\n        while (null != (runnable = taskQueue.pollFirst()))\n        {\n            runnable.run();\n            workCount++;\n        }\n\n        return workCount;\n    }\n\n    static ChannelUriStringBuilder strippedChannelBuilder(final ChannelUri channelUri)\n    {\n        return new ChannelUriStringBuilder()\n            .media(channelUri)\n            .endpoint(channelUri)\n            .networkInterface(channelUri)\n            .controlEndpoint(channelUri)\n            .controlMode(channelUri)\n            .tags(channelUri)\n            .rejoin(channelUri)\n            .group(channelUri)\n            .tether(channelUri)\n            .flowControl(channelUri)\n            .groupTag(channelUri)\n            .congestionControl(channelUri)\n            .socketRcvbufLength(channelUri)\n            .socketSndbufLength(channelUri)\n            .receiverWindowLength(channelUri)\n            .channelSendTimestampOffset(channelUri)\n            .channelReceiveTimestampOffset(channelUri)\n            .mediaReceiveTimestampOffset(channelUri)\n            .sessionId(channelUri)\n            .alias(channelUri)\n            .responseCorrelationId(channelUri)\n            .responseEndpoint(channelUri)\n            .ttl(channelUri)\n            .streamId(channelUri);\n    }\n\n    private static String makeKey(final int streamId, final ChannelUri channelUri)\n    {\n        final StringBuilder sb = new StringBuilder();\n\n        sb.append(streamId).append(':').append(channelUri.media()).append('?');\n\n        final String endpointStr = channelUri.get(CommonContext.ENDPOINT_PARAM_NAME);\n        if (null != endpointStr)\n        {\n            sb.append(CommonContext.ENDPOINT_PARAM_NAME).append('=').append(endpointStr).append('|');\n        }\n\n        final String interfaceStr = channelUri.get(CommonContext.INTERFACE_PARAM_NAME);\n        if (null != interfaceStr)\n        {\n            sb.append(CommonContext.INTERFACE_PARAM_NAME).append('=').append(interfaceStr).append('|');\n        }\n\n        final String controlStr = channelUri.get(CommonContext.MDC_CONTROL_PARAM_NAME);\n        if (null != controlStr)\n        {\n            sb.append(CommonContext.MDC_CONTROL_PARAM_NAME).append('=').append(controlStr).append('|');\n        }\n\n        final String sessionIdStr = channelUri.get(CommonContext.SESSION_ID_PARAM_NAME);\n        if (null != sessionIdStr)\n        {\n            sb.append(CommonContext.SESSION_ID_PARAM_NAME).append('=').append(sessionIdStr).append('|');\n        }\n\n        final String tagsStr = channelUri.get(CommonContext.TAGS_PARAM_NAME);\n        if (null != tagsStr)\n        {\n            sb.append(CommonContext.TAGS_PARAM_NAME).append('=').append(tagsStr).append('|');\n        }\n\n        sb.setLength(sb.length() - 1);\n\n        return sb.toString();\n    }\n\n    private boolean hasRecording(final long recordingId, final long correlationId, final ControlSession session)\n    {\n        if (!catalog.hasRecording(recordingId))\n        {\n            final String msg = \"unknown recording id: \" + recordingId;\n            session.sendErrorResponse(correlationId, UNKNOWN_RECORDING, msg);\n            return false;\n        }\n\n        return true;\n    }\n\n    private boolean isDeleteAllowed(\n        final long recordingId, final long correlationId, final ControlSession controlSession)\n    {\n        if (deleteSegmentsSessionByIdMap.containsKey(recordingId))\n        {\n            final String msg = \"another delete operation in progress for recording id: \" + recordingId;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n        return true;\n    }\n\n    private void listSegmentFiles(final long recordingId, final Consumer<String> segmentFileConsumer)\n    {\n        final String prefix = recordingId + \"-\";\n        final String[] recordingFiles = archiveDir.list();\n        if (null != recordingFiles)\n        {\n            for (final String name : recordingFiles)\n            {\n                if (name.startsWith(prefix) &&\n                    (name.endsWith(RECORDING_SEGMENT_SUFFIX) || name.endsWith(DELETE_SUFFIX)))\n                {\n                    segmentFileConsumer.accept(name);\n                }\n            }\n        }\n    }\n\n    private void startRecordingSession(\n        final ControlSession controlSession,\n        final long correlationId,\n        final String strippedChannel,\n        final String originalChannel,\n        final Image image,\n        final boolean autoStop)\n    {\n        final int sessionId = image.sessionId();\n        final int streamId = image.subscription().streamId();\n        final String sourceIdentity = image.sourceIdentity();\n        final int termBufferLength = image.termBufferLength();\n        final int mtuLength = image.mtuLength();\n        final int initialTermId = image.initialTermId();\n        final long startPosition = image.joinPosition();\n        final int segmentFileLength = max(ctx.segmentFileLength(), termBufferLength);\n\n        final long recordingId = catalog.addNewRecording(\n            startPosition,\n            cachedEpochClock.time(),\n            initialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId,\n            streamId,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity);\n\n        final Counter position = RecordingPos.allocate(\n            aeron,\n            counterMetadataBuffer,\n            ctx.archiveId(),\n            recordingId,\n            sessionId,\n            streamId,\n            strippedChannel,\n            sourceIdentity);\n        position.setRelease(startPosition);\n\n        final RecordingSession session = new RecordingSession(\n            correlationId,\n            recordingId,\n            startPosition,\n            segmentFileLength,\n            originalChannel,\n            recordingEventsProxy,\n            image,\n            position,\n            ctx,\n            controlSession,\n            autoStop,\n            recorder);\n\n        controlSession.sendSignal(\n            correlationId,\n            recordingId,\n            image.subscription().registrationId(),\n            image.joinPosition(),\n            RecordingSignal.START);\n\n        subscriptionRefCountMap.incrementAndGet(image.subscription().registrationId());\n        recordingSessionByIdMap.put(recordingId, session);\n        recorder.addSession(session);\n        ctx.recordingSessionCounter().incrementRelease();\n    }\n\n    private void extendRecordingSession(\n        final ControlSession controlSession,\n        final long correlationId,\n        final long recordingId,\n        final String strippedChannel,\n        final String originalChannel,\n        final Image image,\n        final boolean autoStop)\n    {\n        final long subscriptionId = image.subscription().registrationId();\n        try\n        {\n            if (recordingSessionByIdMap.containsKey(recordingId))\n            {\n                final String msg = \"cannot extend active recording \" + recordingId +\n                    \" streamId=\" + image.subscription().streamId() + \" channel=\" + originalChannel;\n                controlSession.sendErrorResponse(correlationId, ACTIVE_RECORDING, msg);\n                throw new ArchiveEvent(msg);\n            }\n\n            catalog.recordingSummary(recordingId, recordingSummary);\n\n            final DeleteSegmentsSession deleteSegmentsSession = deleteSegmentsSessionByIdMap.get(recordingId);\n            if (null != deleteSegmentsSession &&\n                deleteSegmentsSession.maxDeletePosition() >= recordingSummary.stopPosition)\n            {\n                final String msg = \"cannot extend recording \" + recordingId +\n                    \" due to an outstanding delete operation: streamId=\" +\n                    image.subscription().streamId() + \" channel=\" + originalChannel;\n                controlSession.sendErrorResponse(correlationId, GENERIC, msg);\n                throw new ArchiveEvent(msg);\n            }\n\n            validateImageForExtendRecording(correlationId, controlSession, image, recordingSummary);\n\n            final Counter position = RecordingPos.allocate(\n                aeron,\n                counterMetadataBuffer,\n                ctx.archiveId(),\n                recordingId,\n                image.sessionId(),\n                image.subscription().streamId(),\n                strippedChannel,\n                image.sourceIdentity());\n\n            position.setRelease(image.joinPosition());\n\n            final RecordingSession session = new RecordingSession(\n                correlationId,\n                recordingId,\n                recordingSummary.startPosition,\n                recordingSummary.segmentFileLength,\n                originalChannel,\n                recordingEventsProxy,\n                image,\n                position,\n                ctx,\n                controlSession,\n                autoStop,\n                recorder);\n\n            catalog.extendRecording(recordingId, controlSession.sessionId(), correlationId, image.sessionId());\n            controlSession.sendSignal(\n                correlationId, recordingId, subscriptionId, image.joinPosition(), RecordingSignal.EXTEND);\n\n            subscriptionRefCountMap.incrementAndGet(subscriptionId);\n            recordingSessionByIdMap.put(recordingId, session);\n            recorder.addSession(session);\n            ctx.recordingSessionCounter().incrementRelease();\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n            if (autoStop)\n            {\n                closeAndRemoveRecordingSubscription(image.subscription(), ex.getMessage());\n            }\n        }\n    }\n\n    private Subscription removeRecordingSubscription(final long subscriptionId)\n    {\n        final Iterator<Subscription> iter = recordingSubscriptionByKeyMap.values().iterator();\n        while (iter.hasNext())\n        {\n            final Subscription subscription = iter.next();\n            if (subscription.registrationId() == subscriptionId)\n            {\n                iter.remove();\n                return subscription;\n            }\n        }\n\n        return null;\n    }\n\n    private void validateImageForExtendRecording(\n        final long correlationId,\n        final ControlSession controlSession,\n        final Image image,\n        final RecordingSummary recordingSummary)\n    {\n        if (image.joinPosition() != recordingSummary.stopPosition)\n        {\n            final String msg = \"cannot extend recording \" + recordingSummary.recordingId +\n                \" image.joinPosition=\" + image.joinPosition() + \" != rec.stopPosition=\" + recordingSummary.stopPosition;\n            controlSession.sendErrorResponse(correlationId, INVALID_EXTENSION, msg);\n            throw new ArchiveEvent(msg);\n        }\n\n        if (image.initialTermId() != recordingSummary.initialTermId)\n        {\n            final String msg = \"cannot extend recording \" + recordingSummary.recordingId +\n                \" image.initialTermId=\" + image.initialTermId() +\n                \" != rec.initialTermId=\" + recordingSummary.initialTermId;\n            controlSession.sendErrorResponse(correlationId, INVALID_EXTENSION, msg);\n            throw new ArchiveEvent(msg);\n        }\n\n        if (image.termBufferLength() != recordingSummary.termBufferLength)\n        {\n            final String msg = \"cannot extend recording \" + recordingSummary.recordingId +\n                \" image.termBufferLength=\" + image.termBufferLength() +\n                \" != rec.termBufferLength=\" + recordingSummary.termBufferLength;\n            controlSession.sendErrorResponse(correlationId, INVALID_EXTENSION, msg);\n            throw new ArchiveEvent(msg);\n        }\n\n        if (image.mtuLength() != recordingSummary.mtuLength)\n        {\n            final String msg = \"cannot extend recording \" + recordingSummary.recordingId +\n                \" image.mtuLength=\" + image.mtuLength() + \" != rec.mtuLength=\" + recordingSummary.mtuLength;\n            controlSession.sendErrorResponse(correlationId, INVALID_EXTENSION, msg);\n            throw new ArchiveEvent(msg);\n        }\n    }\n\n    private boolean isValidTruncate(\n        final long correlationId, final ControlSession controlSession, final long recordingId, final long position)\n    {\n        for (final ReplaySession replaySession : replaySessionByIdMap.values())\n        {\n            if (replaySession.recordingId() == recordingId)\n            {\n                final String msg = \"cannot truncate recording with active replay \" + recordingId;\n                controlSession.sendErrorResponse(correlationId, ACTIVE_RECORDING, msg);\n                return false;\n            }\n        }\n\n        catalog.recordingSummary(recordingId, recordingSummary);\n        final long stopPosition = recordingSummary.stopPosition;\n        final long startPosition = recordingSummary.startPosition;\n\n        if (NULL_POSITION == stopPosition)\n        {\n            final String msg = \"cannot truncate active recording\";\n            controlSession.sendErrorResponse(correlationId, ACTIVE_RECORDING, msg);\n            return false;\n        }\n\n        if (position < startPosition || position > stopPosition || ((position & (FRAME_ALIGNMENT - 1)) != 0))\n        {\n            final String msg = \"invalid position \" + position +\n                \": start=\" + startPosition + \" stop=\" + stopPosition + \" alignment=\" + FRAME_ALIGNMENT;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        return true;\n    }\n\n    private boolean isValidPurge(final long correlationId, final ControlSession controlSession, final long recordingId)\n    {\n        for (final ReplaySession replaySession : replaySessionByIdMap.values())\n        {\n            if (replaySession.recordingId() == recordingId)\n            {\n                final String msg = \"cannot purge recording with active replay \" + recordingId;\n                controlSession.sendErrorResponse(correlationId, ACTIVE_RECORDING, msg);\n                return false;\n            }\n        }\n\n        catalog.recordingSummary(recordingId, recordingSummary);\n\n        final long stopPosition = recordingSummary.stopPosition;\n        if (NULL_POSITION == stopPosition)\n        {\n            final String msg = \"cannot purge active recording \" + recordingId;\n            controlSession.sendErrorResponse(correlationId, ACTIVE_RECORDING, msg);\n            return false;\n        }\n\n        return true;\n    }\n\n    private boolean isInvalidReplayPosition(\n        final long correlationId,\n        final ControlSession controlSession,\n        final long recordingId,\n        final long position,\n        final RecordingSummary recordingSummary)\n    {\n        if ((position & (FRAME_ALIGNMENT - 1)) != 0)\n        {\n            final String msg = \"requested replay start position=\" + position +\n                \" is not a multiple of FRAME_ALIGNMENT (\" + FRAME_ALIGNMENT + \") for recording \" + recordingId;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return true;\n        }\n\n        final long startPosition = recordingSummary.startPosition;\n        if (position - startPosition < 0)\n        {\n            final String msg = \"requested replay start position=\" + position +\n                \" is less than recording start position=\" + startPosition + \" for recording \" + recordingId;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return true;\n        }\n\n        final long stopPosition = recordingSummary.stopPosition;\n        if (stopPosition != NULL_POSITION && position >= stopPosition)\n        {\n            final String msg = \"requested replay start position=\" + position +\n                \" must be less than highest recorded position=\" + stopPosition + \" for recording \" + recordingId;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return true;\n        }\n\n        return false;\n    }\n\n    private boolean isValidDetach(\n        final long correlationId, final ControlSession controlSession, final long recordingId, final long position)\n    {\n        catalog.recordingSummary(recordingId, recordingSummary);\n\n        final int segmentLength = recordingSummary.segmentFileLength;\n        final long startPosition = recordingSummary.startPosition;\n        final int termLength = recordingSummary.termBufferLength;\n        final long lowerBound =\n            segmentFileBasePosition(startPosition, startPosition, termLength, segmentLength) + segmentLength;\n\n        if (position != segmentFileBasePosition(startPosition, position, termLength, segmentLength))\n        {\n            final String msg = \"invalid segment start: newStartPosition=\" + position;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        if (position < lowerBound)\n        {\n            final String msg = \"invalid detach: newStartPosition=\" + position + \" lowerBound=\" + lowerBound;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        final long stopPosition = recordingSummary.stopPosition;\n        long endPosition = NULL_VALUE == stopPosition ?\n            recordingSessionByIdMap.get(recordingId).recordedPosition() : stopPosition;\n        endPosition = segmentFileBasePosition(startPosition, endPosition, termLength, segmentLength);\n\n        if (position > endPosition)\n        {\n            final String msg = \"invalid detach: in use, newStartPosition=\" + position + \" upperBound=\" + endPosition;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        ReplaySession minReplaySession = null;\n        for (final ReplaySession replaySession : replaySessionByIdMap.values())\n        {\n            final long replayPos = replaySession.segmentFileBasePosition();\n            if (recordingId == replaySession.recordingId() && position > replayPos &&\n                (null == minReplaySession || replayPos < minReplaySession.segmentFileBasePosition()))\n            {\n                minReplaySession = replaySession;\n            }\n        }\n\n        if (null != minReplaySession)\n        {\n            final String msg = \"invalid detach: replay in progress - \" +\n                \" state=\" + minReplaySession.state() +\n                \" newStartPosition=\" + position +\n                \" upperBound=\" + minReplaySession.segmentFileBasePosition() +\n                \" sessionId=\" + (int)minReplaySession.sessionId() +\n                \" streamId=\" + minReplaySession.replayStreamId() +\n                \" channel=\" + minReplaySession.replayChannel();\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        return true;\n    }\n\n    private boolean isJoinPositionSegmentUnaligned(\n        final ControlSession controlSession,\n        final long correlationId,\n        final String label,\n        final RecordingSummary recordingSummary,\n        final long seamPosition\n    )\n    {\n        final long segmentBasePosition = segmentFileBasePosition(\n            recordingSummary.startPosition,\n            seamPosition,\n            recordingSummary.termBufferLength,\n            recordingSummary.segmentFileLength);\n\n        if (segmentBasePosition != seamPosition)\n        {\n            final String error = \"invalid migrate: join position is not on segment boundary of \" +\n                label + \" recording\" +\n                \" seamPosition=\" + seamPosition +\n                \" startPosition=\" + recordingSummary.startPosition +\n                \" stopPosition=\" + recordingSummary.stopPosition +\n                \" termBufferLength=\" + recordingSummary.termBufferLength +\n                \" segmentFileLength=\" + recordingSummary.segmentFileLength;\n            controlSession.sendErrorResponse(correlationId, error);\n            return true;\n        }\n\n        return false;\n    }\n\n    private boolean isActiveRecording(\n        final ControlSession controlSession,\n        final long correlationId,\n        final RecordingSummary srcRecordingSummary)\n    {\n        final long srcStopPosition = srcRecordingSummary.stopPosition;\n        if (NULL_POSITION == srcStopPosition)\n        {\n            final String message = \"recording \" + srcRecordingSummary.recordingId + \" is still active\";\n            controlSession.sendErrorResponse(correlationId, message);\n            return true;\n        }\n        return false;\n    }\n\n    private boolean hasMatchingStreamParameters(\n        final ControlSession controlSession,\n        final long correlationId,\n        final RecordingSummary srcRecordingSummary,\n        final RecordingSummary dstRecordingSummary)\n    {\n        final int srcSegmentFileLength = srcRecordingSummary.segmentFileLength;\n        final int dstSegmentFileLength = dstRecordingSummary.segmentFileLength;\n        if (dstSegmentFileLength != srcSegmentFileLength)\n        {\n            final String msg = \"invalid migrate: srcSegmentFileLength=\" + srcSegmentFileLength +\n                \" dstSegmentFileLength=\" + dstSegmentFileLength;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        final int srcTermBufferLength = srcRecordingSummary.termBufferLength;\n        final int dstTermBufferLength = dstRecordingSummary.termBufferLength;\n        if (dstTermBufferLength != srcTermBufferLength)\n        {\n            final String msg = \"invalid migrate: srcTermBufferLength=\" + srcTermBufferLength +\n                \" dstTermBufferLength=\" + dstTermBufferLength;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        final int srcInitialTermId = srcRecordingSummary.initialTermId;\n        final int dstInitialTermId = dstRecordingSummary.initialTermId;\n        if (dstInitialTermId != srcInitialTermId)\n        {\n            final String msg = \"invalid migrate: srcInitialTermId=\" + srcInitialTermId +\n                \" dstInitialTermId=\" + dstInitialTermId;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        final int srcStreamId = srcRecordingSummary.streamId;\n        final int dstStreamId = dstRecordingSummary.streamId;\n        if (dstStreamId != srcStreamId)\n        {\n            final String msg = \"invalid migrate: srcStreamId=\" + srcStreamId +\n                \" dstStreamId=\" + dstStreamId;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        final int srcMtuLength = srcRecordingSummary.mtuLength;\n        final int dstMtuLength = dstRecordingSummary.mtuLength;\n        if (dstMtuLength != srcMtuLength)\n        {\n            final String msg = \"invalid migrate: srcMtuLength=\" + srcMtuLength + \" dstMtuLength=\" + dstMtuLength;\n            controlSession.sendErrorResponse(correlationId, msg);\n            return false;\n        }\n\n        return true;\n    }\n\n    private long moveAllSegments(\n        final ControlSession controlSession,\n        final long correlationId,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final RecordingSummary srcRecordingSummary,\n        final ArrayDeque<String> emptyFollowingSrcSegment)\n    {\n        final long firstSegmentPos = segmentFileBasePosition(\n            srcRecordingSummary.startPosition,\n            srcRecordingSummary.startPosition,\n            srcRecordingSummary.termBufferLength,\n            srcRecordingSummary.segmentFileLength);\n\n        final long lastSegmentPos = segmentFileBasePosition(\n            srcRecordingSummary.startPosition,\n            srcRecordingSummary.stopPosition,\n            srcRecordingSummary.termBufferLength,\n            srcRecordingSummary.segmentFileLength);\n\n        long attachedSegmentCount = 0;\n\n        final int segmentLength = srcRecordingSummary.segmentFileLength;\n\n        for (long position = firstSegmentPos; position <= lastSegmentPos; position += segmentLength)\n        {\n            final String segmentFileName = segmentFileName(srcRecordingId, position);\n            final File srcFile = new File(archiveDir, segmentFileName);\n            final String dstFileName = segmentFileName(dstRecordingId, position);\n            final File dstFile = new File(archiveDir, dstFileName);\n\n            final boolean isEmptyFollowingSrcSegment = position == srcRecordingSummary.stopPosition;\n            if (!isEmptyFollowingSrcSegment)\n            {\n                if (!srcFile.exists())\n                {\n                    final String msg = \"missing src segment file \" + srcFile;\n                    controlSession.sendErrorResponse(correlationId, msg);\n                    return -1L;\n                }\n\n                if (dstFile.exists())\n                {\n                    final String msg = \"preexisting dst segment file \" + dstFile;\n                    controlSession.sendErrorResponse(correlationId, msg);\n                    return -1L;\n                }\n            }\n        }\n\n        for (long position = firstSegmentPos; position <= lastSegmentPos; position += segmentLength)\n        {\n            final String segmentFileName = segmentFileName(srcRecordingId, position);\n            final File srcFile = new File(archiveDir, segmentFileName);\n\n            final boolean isEmptyFollowingSrcSegment = position == srcRecordingSummary.stopPosition;\n            if (isEmptyFollowingSrcSegment)\n            {\n                emptyFollowingSrcSegment.addFirst(segmentFileName);\n            }\n            else\n            {\n                final String dstFileName = segmentFileName(dstRecordingId, position);\n                final File dstFile = new File(archiveDir, dstFileName);\n                if (!srcFile.renameTo(dstFile))\n                {\n                    final String msg = \"failed to rename \" + srcFile + \" to \" + dstFile;\n                    controlSession.sendErrorResponse(correlationId, msg);\n                    return -1L;\n                }\n\n                attachedSegmentCount++;\n            }\n        }\n\n        return attachedSegmentCount;\n    }\n\n    private boolean eraseRemainingSegment(\n        final long correlationId,\n        final ControlSession controlSession,\n        final long position,\n        final int segmentLength,\n        final int segmentOffset,\n        final int termLength,\n        final File file)\n    {\n        try (FileChannel channel = FileChannel.open(file.toPath(), FILE_OPTIONS))\n        {\n            final int termOffset = (int)(position & (termLength - 1));\n            final int termCount = (int)(position >> LogBufferDescriptor.positionBitsToShift(termLength));\n            final int termId = recordingSummary.initialTermId + termCount;\n            final UnsafeBuffer dataBuffer = ctx.dataBuffer();\n\n            if (ReplaySession.notHeaderAligned(\n                channel, dataBuffer, segmentOffset, termOffset, termId, recordingSummary.streamId))\n            {\n                final String msg = position + \" position not aligned to a data header\";\n                controlSession.sendErrorResponse(correlationId, msg);\n                return false;\n            }\n\n            channel.truncate(segmentOffset);\n            dataBuffer.byteBuffer().put(0, (byte)0).limit(1).position(0);\n\n            while (true)\n            {\n                final int written = channel.write(dataBuffer.byteBuffer(), segmentLength - 1);\n                if (1 == written)\n                {\n                    break;\n                }\n            }\n        }\n        catch (final IOException ex)\n        {\n            controlSession.sendErrorResponse(correlationId, ex.getMessage());\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return true;\n    }\n\n    private void closeAndRemoveRecordingSubscription(final Subscription subscription, final String reason)\n    {\n        final long subscriptionId = subscription.registrationId();\n        subscriptionRefCountMap.remove(subscriptionId);\n\n        for (final RecordingSession session : recordingSessionByIdMap.values())\n        {\n            if (subscription == session.subscription())\n            {\n                session.abort(reason);\n            }\n        }\n\n        removeRecordingSubscription(subscriptionId);\n        CloseHelper.close(errorHandler, subscription);\n    }\n\n    private String isLowStorageSpace(final long correlationId, final ControlSession controlSession)\n    {\n        try\n        {\n            final long threshold = ctx.lowStorageSpaceThreshold();\n            final long usableSpace = ctx.archiveFileStore().getUsableSpace();\n\n            if (usableSpace <= threshold)\n            {\n                final String msg = \"low storage threshold=\" + threshold + \" <= usableSpace=\" + usableSpace;\n                controlSession.sendErrorResponse(correlationId, STORAGE_SPACE, msg);\n                return msg;\n            }\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return null;\n    }\n\n    private void deleteSegments(\n        final long correlationId,\n        final long recordingId,\n        final ControlSession controlSession,\n        final ArrayDeque<String> files)\n    {\n        final int count = addDeleteSegmentsSession(correlationId, recordingId, controlSession, files);\n        if (count >= 0)\n        {\n            controlSession.sendOkResponse(correlationId, count);\n\n            if (0 == count)\n            {\n                controlSession.sendSignal(\n                    correlationId, recordingId, Aeron.NULL_VALUE, Aeron.NULL_VALUE, RecordingSignal.DELETE);\n            }\n        }\n    }\n\n    public long generateReplayToken(final ControlSession session, final long recordingId)\n    {\n        long replayToken = NULL_VALUE;\n        while (NULL_VALUE == replayToken || controlSessionByReplayToken.containsKey(replayToken))\n        {\n            replayToken = random.nextLong();\n        }\n\n        final SessionForReplay sessionForReplay = new SessionForReplay(\n            recordingId, session, nanoClock.nanoTime() + TimeUnit.MILLISECONDS.toNanos(connectTimeoutMs));\n        controlSessionByReplayToken.put(replayToken, sessionForReplay);\n\n        return replayToken;\n    }\n\n    public ControlSession getReplaySession(final long replayToken, final long recordingId)\n    {\n        final SessionForReplay sessionForReplay = controlSessionByReplayToken.get(replayToken);\n\n        final long nowNs = nanoClock.nanoTime();\n        if (null != sessionForReplay &&\n            recordingId == sessionForReplay.recordingId &&\n            nowNs < sessionForReplay.deadlineNs)\n        {\n            return sessionForReplay.controlSession;\n        }\n\n        return null;\n    }\n\n    void removeReplayTokensForSession(final long sessionId)\n    {\n        //noinspection Java8CollectionRemoveIf\n        for (Long2ObjectHashMap<SessionForReplay>.ValueIterator it = controlSessionByReplayToken.values().iterator();\n            it.hasNext();)\n        {\n            final SessionForReplay sessionForReplay = it.next();\n            if (sessionForReplay.controlSession.sessionId() == sessionId)\n            {\n                it.remove();\n            }\n        }\n    }\n\n    private int checkReplayTokens(final long nowNs)\n    {\n        //noinspection Java8CollectionRemoveIf\n        for (Long2ObjectHashMap<SessionForReplay>.ValueIterator it = controlSessionByReplayToken.values().iterator();\n            it.hasNext();)\n        {\n            final SessionForReplay sessionForReplay = it.next();\n            if (sessionForReplay.deadlineNs <= nowNs)\n            {\n                it.remove();\n            }\n        }\n\n        return 0;\n    }\n\n    abstract static class Recorder extends SessionWorker<RecordingSession>\n    {\n        private long totalWriteBytes;\n        private long totalWriteTimeNs;\n        private long maxWriteTimeNs;\n        private final Counter totalWriteBytesCounter;\n        private final Counter totalWriteTimeCounter;\n        private final Counter maxWriteTimeCounter;\n\n        Recorder(final CountedErrorHandler errorHandler, final Archive.Context context)\n        {\n            super(\"archive-recorder\", errorHandler);\n            totalWriteBytesCounter = context.totalWriteBytesCounter();\n            totalWriteTimeCounter = context.totalWriteTimeCounter();\n            maxWriteTimeCounter = context.maxWriteTimeCounter();\n        }\n\n        final void bytesWritten(final long bytes)\n        {\n            totalWriteBytes += bytes;\n        }\n\n        final void writeTimeNs(final long nanos)\n        {\n            totalWriteTimeNs += nanos;\n\n            if (nanos > maxWriteTimeNs)\n            {\n                maxWriteTimeNs = nanos;\n            }\n        }\n\n        public int doWork()\n        {\n            final int workCount = super.doWork();\n            if (workCount > 0)\n            {\n                totalWriteBytesCounter.setRelease(totalWriteBytes);\n                totalWriteTimeCounter.setRelease(totalWriteTimeNs);\n                maxWriteTimeCounter.setRelease(maxWriteTimeNs);\n            }\n\n            return workCount;\n        }\n    }\n\n    abstract static class Replayer extends SessionWorker<ReplaySession>\n    {\n        private long totalReadBytes;\n        private long totalReadTimeNs;\n        private long maxReadTimeNs;\n        private final Counter totalReadBytesCounter;\n        private final Counter totalReadTimeCounter;\n        private final Counter maxReadTimeCounter;\n\n        Replayer(final CountedErrorHandler errorHandler, final Archive.Context context)\n        {\n            super(\"archive-replayer\", errorHandler);\n            totalReadBytesCounter = context.totalReadBytesCounter();\n            totalReadTimeCounter = context.totalReadTimeCounter();\n            maxReadTimeCounter = context.maxReadTimeCounter();\n        }\n\n        final void bytesRead(final long bytes)\n        {\n            totalReadBytes += bytes;\n        }\n\n        final void readTimeNs(final long nanos)\n        {\n            totalReadTimeNs += nanos;\n\n            if (nanos > maxReadTimeNs)\n            {\n                maxReadTimeNs = nanos;\n            }\n        }\n\n        public int doWork()\n        {\n            final int workCount = super.doWork();\n            if (workCount > 0)\n            {\n                totalReadBytesCounter.setRelease(totalReadBytes);\n                totalReadTimeCounter.setRelease(totalReadTimeNs);\n                maxReadTimeCounter.setRelease(maxReadTimeNs);\n            }\n\n            return workCount;\n        }\n    }\n\n    private static final class SessionForReplay\n    {\n        private final long recordingId;\n        private final ControlSession controlSession;\n        private final long deadlineNs;\n\n        private SessionForReplay(final long recordingId, final ControlSession controlSession, final long deadlineNs)\n        {\n            this.recordingId = recordingId;\n            this.controlSession = controlSession;\n            this.deadlineNs = deadlineNs;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveCounters.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.Counter;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.concurrent.status.CountersReader.*;\n\n/**\n * For allocating and finding Archive associated counters identified by the {@link Aeron#clientId()}.\n */\npublic final class ArchiveCounters\n{\n    static final String ARCHIVE_ID_LABEL_SUFFIX = \" - archiveId=\";\n\n    private ArchiveCounters()\n    {\n    }\n\n    /**\n     * Allocate a counter to represent state within an Archive. The {@code archiveId} is assumed to be\n     * {@link Aeron#clientId()}.\n     *\n     * @param aeron      from {@link Archive} instance to allocate the counter.\n     * @param tempBuffer temporary storage to create label and metadata.\n     * @param typeId     for the counter.\n     * @param name       of the counter for the label.\n     * @param archiveId  to which the allocated counter belongs.\n     * @return the {@link Counter} for the commit position.\n     */\n    public static Counter allocate(\n        final Aeron aeron,\n        final MutableDirectBuffer tempBuffer,\n        final int typeId,\n        final String name,\n        final long archiveId)\n    {\n        int index = 0;\n        tempBuffer.putLong(index, archiveId);\n        index += SIZE_OF_LONG;\n        final int keyLength = index;\n\n        index += tempBuffer.putStringWithoutLengthAscii(index, name);\n        index += appendArchiveIdLabel(tempBuffer, index, archiveId);\n\n        return aeron.addCounter(typeId, tempBuffer, 0, keyLength, tempBuffer, keyLength, index - keyLength);\n    }\n\n    static Counter allocateErrorCounter(\n        final Aeron aeron,\n        final MutableDirectBuffer tempBuffer,\n        final long archiveId)\n    {\n        int index = 0;\n        tempBuffer.putLong(index, archiveId);\n        index += SIZE_OF_LONG;\n        final int keyLength = index;\n\n        index += tempBuffer.putStringWithoutLengthAscii(index, \"Archive Errors\");\n        index += appendArchiveIdLabel(tempBuffer, index, archiveId);\n        index += AeronCounters.appendVersionInfo(tempBuffer, index, ArchiveVersion.VERSION, ArchiveVersion.GIT_SHA);\n\n        return aeron.addCounter(\n            AeronCounters.ARCHIVE_ERROR_COUNT_TYPE_ID,\n            tempBuffer,\n            0,\n            keyLength,\n            tempBuffer,\n            keyLength,\n            index - keyLength);\n    }\n\n    /**\n     * Append {@code archiveId} at the end of the counter label.\n     *\n     * @param tempBuffer to append label to.\n     * @param offset     at which current label data ends.\n     * @param archiveId  to use as suffix.\n     * @return length of the suffix appended.\n     */\n    public static int appendArchiveIdLabel(\n        final MutableDirectBuffer tempBuffer, final int offset, final long archiveId)\n    {\n        int suffixLength = 0;\n        suffixLength += tempBuffer.putStringWithoutLengthAscii(offset, ARCHIVE_ID_LABEL_SUFFIX);\n        suffixLength += tempBuffer.putLongAscii(offset + suffixLength, archiveId);\n        return suffixLength;\n    }\n\n    /**\n     * Returns the length of the archive id suffix in bytes.\n     *\n     * @param archiveId that will be added to the label.\n     * @return the length of the archive id suffix in bytes.\n     */\n    public static int lengthOfArchiveIdLabel(final long archiveId)\n    {\n        if (archiveId < 0)\n        {\n            return Long.MIN_VALUE == archiveId ?\n                ARCHIVE_ID_LABEL_SUFFIX.length() + AsciiEncoding.MIN_LONG_VALUE.length :\n                ARCHIVE_ID_LABEL_SUFFIX.length() + 1 + AsciiEncoding.digitCount(-archiveId);\n        }\n        else\n        {\n            return ARCHIVE_ID_LABEL_SUFFIX.length() + AsciiEncoding.digitCount(archiveId);\n        }\n    }\n\n    /**\n     * Find the counter id for a type of counter in an Archive.\n     *\n     * @param counters  to search within.\n     * @param typeId    of the counter.\n     * @param archiveId to which the allocated counter belongs.\n     * @return the matching counter id or {@link Aeron#NULL_VALUE} if not found.\n     */\n    public static int find(final CountersReader counters, final int typeId, final long archiveId)\n    {\n        final AtomicBuffer buffer = counters.metaDataBuffer();\n\n        for (int i = 0, size = counters.maxCounterId(); i < size; i++)\n        {\n            final int counterState = counters.getCounterState(i);\n\n            if (RECORD_ALLOCATED == counterState)\n            {\n                if (counters.getCounterTypeId(i) == typeId &&\n                    buffer.getLong(CountersReader.metaDataOffset(i) + KEY_OFFSET) == archiveId)\n                {\n                    return i;\n                }\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return NULL_VALUE;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveMarkFile.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.CommonContext;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.mark.MarkFileHeaderDecoder;\nimport io.aeron.archive.codecs.mark.MarkFileHeaderEncoder;\nimport io.aeron.archive.codecs.mark.MessageHeaderDecoder;\nimport io.aeron.archive.codecs.mark.MessageHeaderEncoder;\nimport io.aeron.archive.codecs.mark.VarAsciiEncodingEncoder;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.MarkFile;\nimport org.agrona.SemanticVersion;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.nio.MappedByteBuffer;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.function.Consumer;\nimport java.util.function.IntConsumer;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.Archive.Configuration.LIVENESS_TIMEOUT_MS;\n\n/**\n * Used to mark the presence of a running {@link Archive} in a directory to guard it.\n */\npublic class ArchiveMarkFile implements AutoCloseable\n{\n    /**\n     * Major version for the archive files stored on disk. A change to this requires migration.\n     */\n    public static final int MAJOR_VERSION = 3;\n\n    /**\n     * Minor version for the archive files stored on disk. A change to this indicates new features.\n     */\n    public static final int MINOR_VERSION = 1;\n\n    /**\n     * Patch version for the archive files stored on disk. A change to this indicates feature parity bug fixes.\n     */\n    public static final int PATCH_VERSION = 0;\n\n    /**\n     * Combined semantic version for the stored files.\n     *\n     * @see SemanticVersion\n     */\n    public static final int SEMANTIC_VERSION = SemanticVersion.compose(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION);\n\n    /**\n     * Header length for the {@link MarkFile} containing the metadata.\n     */\n    public static final int HEADER_LENGTH = 8 * 1024;\n\n    /**\n     * Name for the archive {@link MarkFile} stored in the {@link Archive.Configuration#ARCHIVE_DIR_PROP_NAME}.\n     */\n    public static final String FILENAME = \"archive-mark.dat\";\n\n    /**\n     * Name for a file contain a link to the directory containing the {@link MarkFile}.\n     */\n    public static final String LINK_FILENAME = \"archive-mark.lnk\";\n    private static final UnsafeBuffer EMPTY_BUFFER = new UnsafeBuffer();\n\n    private static final int HEADER_OFFSET = MessageHeaderDecoder.ENCODED_LENGTH;\n\n    private final MarkFileHeaderDecoder headerDecoder = new MarkFileHeaderDecoder();\n    private final MarkFileHeaderEncoder headerEncoder = new MarkFileHeaderEncoder();\n    private final MarkFile markFile;\n    private final UnsafeBuffer buffer;\n    private final UnsafeBuffer errorBuffer;\n\n    ArchiveMarkFile(final Archive.Context ctx)\n    {\n        this(\n            new File(ctx.markFileDir(), FILENAME),\n            alignedTotalFileLength(ctx),\n            ctx.errorBufferLength(),\n            ctx.epochClock(),\n            LIVENESS_TIMEOUT_MS);\n\n        encode(ctx);\n    }\n\n    ArchiveMarkFile(\n        final File file,\n        final int totalFileLength,\n        final int errorBufferLength,\n        final EpochClock epochClock,\n        final long timeoutMs)\n    {\n        final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n\n        if (file.exists())\n        {\n            final int currentHeaderOffset = headerOffset(file);\n            final MarkFile existingMarkFile = new MarkFile(\n                file,\n                true,\n                currentHeaderOffset + MarkFileHeaderDecoder.versionEncodingOffset(),\n                currentHeaderOffset + MarkFileHeaderDecoder.activityTimestampEncodingOffset(),\n                totalFileLength,\n                timeoutMs,\n                epochClock,\n                (version) -> validateVersion(file, version),\n                null);\n\n            final UnsafeBuffer existingBuffer = existingMarkFile.buffer();\n\n            if (0 != currentHeaderOffset)\n            {\n                headerDecoder.wrapAndApplyHeader(existingBuffer, 0, messageHeaderDecoder);\n            }\n            else\n            {\n                headerDecoder.wrap(\n                    existingBuffer, 0, MarkFileHeaderDecoder.BLOCK_LENGTH, MarkFileHeaderDecoder.SCHEMA_VERSION);\n            }\n\n            final int existingErrorBufferLength = headerDecoder.errorBufferLength();\n            if (existingErrorBufferLength > 0)\n            {\n                final UnsafeBuffer existingErrorBuffer = new UnsafeBuffer(\n                    existingBuffer, headerDecoder.headerLength(), existingErrorBufferLength);\n\n                saveExistingErrors(file, existingErrorBuffer, CommonContext.fallbackLogger());\n                existingErrorBuffer.setMemory(0, existingErrorBufferLength, (byte)0);\n            }\n\n            if (0 != currentHeaderOffset)\n            {\n                markFile = existingMarkFile;\n                buffer = existingBuffer;\n            }\n            else\n            {\n                headerDecoder.wrap(EMPTY_BUFFER, 0, 0, 0);\n                CloseHelper.close(existingMarkFile);\n\n                markFile = new MarkFile(\n                    file,\n                    true,\n                    HEADER_OFFSET + MarkFileHeaderDecoder.versionEncodingOffset(),\n                    HEADER_OFFSET + MarkFileHeaderDecoder.activityTimestampEncodingOffset(),\n                    totalFileLength,\n                    timeoutMs,\n                    epochClock,\n                    (version) -> {},\n                    null);\n                buffer = markFile.buffer();\n                buffer.setMemory(0, buffer.capacity(), (byte)0);\n            }\n        }\n        else\n        {\n            markFile = new MarkFile(\n                file,\n                false,\n                HEADER_OFFSET + MarkFileHeaderDecoder.versionEncodingOffset(),\n                HEADER_OFFSET + MarkFileHeaderDecoder.activityTimestampEncodingOffset(),\n                totalFileLength,\n                timeoutMs,\n                epochClock,\n                (version) -> {},\n                null);\n            buffer = markFile.buffer();\n        }\n\n        headerEncoder\n            .wrapAndApplyHeader(buffer, 0, new MessageHeaderEncoder())\n            .pid(SystemUtil.getPid());\n\n        headerDecoder.wrapAndApplyHeader(buffer, 0, messageHeaderDecoder);\n\n        errorBuffer = new UnsafeBuffer(buffer, HEADER_LENGTH, errorBufferLength);\n    }\n\n    /**\n     * Construct the {@link MarkFile} based on an existing directory containing a mark file of a running archive.\n     *\n     * @param directory  containing the archive files.\n     * @param filename   for the mark file.\n     * @param epochClock to be used for checking liveness.\n     * @param timeoutMs  after which the opening will be aborted if no archive starts.\n     * @param logger     to detail any discoveries.\n     */\n    public ArchiveMarkFile(\n        final File directory,\n        final String filename,\n        final EpochClock epochClock,\n        final long timeoutMs,\n        final Consumer<String> logger)\n    {\n        this(\n            directory,\n            filename,\n            epochClock,\n            timeoutMs,\n            (version) -> validateVersion(new File(directory, filename), version),\n            logger);\n    }\n\n    /**\n     * Open an existing {@link MarkFile} or create a new one to be used in migration.\n     *\n     * @param directory    containing the archive files.\n     * @param filename     for the mark file.\n     * @param epochClock   to be used for checking liveness.\n     * @param timeoutMs    after which the opening will be aborted if no archive starts.\n     * @param versionCheck for confirming the correct version.\n     * @param logger       to detail any discoveries.\n     */\n    public ArchiveMarkFile(\n        final File directory,\n        final String filename,\n        final EpochClock epochClock,\n        final long timeoutMs,\n        final IntConsumer versionCheck,\n        final Consumer<String> logger)\n    {\n        this(openExistingMarkFile(directory, filename, epochClock, timeoutMs, versionCheck, logger));\n    }\n\n    ArchiveMarkFile(final MarkFile markFile)\n    {\n        this.markFile = markFile;\n\n        buffer = markFile.buffer();\n\n        if (0 != headerOffset(buffer))\n        {\n            headerEncoder.wrap(buffer, HEADER_OFFSET);\n            headerDecoder.wrapAndApplyHeader(buffer, 0, new MessageHeaderDecoder());\n        }\n        else\n        {\n            headerDecoder.wrap(buffer, 0, MarkFileHeaderDecoder.BLOCK_LENGTH, MarkFileHeaderDecoder.SCHEMA_VERSION);\n\n            // determine the actual sbe schema version used\n            final int actingBlockLength = 128;\n            final int actingVersion = headerDecoder.headerLength() > 0 ? 1 : 0;\n            headerDecoder.wrap(buffer, 0, actingBlockLength, actingVersion);\n            headerEncoder.wrap(buffer, 0);\n        }\n\n        errorBuffer = headerDecoder.headerLength() > 0 ?\n            new UnsafeBuffer(buffer, headerDecoder.headerLength(), headerDecoder.errorBufferLength()) :\n            new UnsafeBuffer(buffer, 0, 0);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (!markFile.isClosed())\n        {\n            headerEncoder.wrap(EMPTY_BUFFER, 0);\n            headerDecoder.wrap(EMPTY_BUFFER, 0, 0, 0);\n            errorBuffer.wrap(0, 0);\n            CloseHelper.close(markFile);\n        }\n    }\n\n    /**\n     * Check if the {@link MarkFile} is closed.\n     *\n     * @return true if the {@link MarkFile} is closed.\n     */\n    public boolean isClosed()\n    {\n        return markFile.isClosed();\n    }\n\n    /**\n     * Get archive id that is stored in the mark file.\n     *\n     * @return archive id or {@link io.aeron.Aeron#NULL_VALUE} if mark file does not contain this information.\n     * @since 1.47.0\n     */\n    public long archiveId()\n    {\n        return markFile.isClosed() ? NULL_VALUE : headerDecoder.archiveId();\n    }\n\n    /**\n     * Signal the archive has concluded successfully and ready to start.\n     *\n     * @deprecated Use {@link #signalReady(long)} instead.\n     */\n    @Deprecated(forRemoval = true)\n    public void signalReady()\n    {\n        if (!markFile.isClosed())\n        {\n            markFile.signalReady(SEMANTIC_VERSION);\n        }\n    }\n\n    /**\n     * Signal the archive has concluded successfully and ready to start.\n     *\n     * @param activityTimestamp to be used.\n     */\n    public void signalReady(final long activityTimestamp)\n    {\n        if (!markFile.isClosed())\n        {\n            markFile.timestampRelease(activityTimestamp);\n            markFile.signalReady(SEMANTIC_VERSION);\n            force();\n        }\n    }\n\n    /**\n     * Signal the archive has terminated cleanly.\n     */\n    public void signalTerminated()\n    {\n        signalReady(NULL_VALUE);\n    }\n\n    /**\n     * Update the activity timestamp as a proof of life.\n     *\n     * @param nowMs activity timestamp as a proof of life.\n     */\n    public void updateActivityTimestamp(final long nowMs)\n    {\n        if (!markFile.isClosed())\n        {\n            markFile.timestampRelease(nowMs);\n        }\n    }\n\n    /**\n     * Read the activity timestamp of the archive with volatile semantics.\n     *\n     * @return the activity timestamp of the archive with volatile semantics.\n     */\n    public long activityTimestampVolatile()\n    {\n        return markFile.isClosed() ? NULL_VALUE : markFile.timestampVolatile();\n    }\n\n    /**\n     * The encoder for writing the {@link MarkFile} header.\n     *\n     * @return the encoder for writing the {@link MarkFile} header.\n     */\n    public MarkFileHeaderEncoder encoder()\n    {\n        return headerEncoder;\n    }\n\n    /**\n     * The decoder for reading the {@link MarkFile} header.\n     *\n     * @return the decoder for reading the {@link MarkFile} header.\n     */\n    public MarkFileHeaderDecoder decoder()\n    {\n        return headerDecoder;\n    }\n\n    /**\n     * The direct buffer which wraps the region of the {@link MarkFile} which contains the error log.\n     *\n     * @return the direct buffer which wraps the region of the {@link MarkFile} which contains the error log.\n     */\n    public AtomicBuffer errorBuffer()\n    {\n        return errorBuffer;\n    }\n\n    /**\n     * Save the existing errors from a {@link MarkFile} to a {@link PrintStream} for logging.\n     *\n     * @param markFile    which contains the error buffer.\n     * @param errorBuffer which wraps the error log.\n     * @param logger      to which the existing errors will be printed.\n     */\n    public static void saveExistingErrors(final File markFile, final AtomicBuffer errorBuffer, final PrintStream logger)\n    {\n        CommonContext.saveExistingErrors(markFile, errorBuffer, logger, \"archive\");\n    }\n\n    /**\n     * Determine if the path matches the archive mark file name.\n     *\n     * @param path       to match.\n     * @param attributes ignored, only needed for BiPredicate signature matching.\n     * @return true if the filename matches.\n     */\n    public static boolean isArchiveMarkFile(final Path path, final BasicFileAttributes attributes)\n    {\n        return FILENAME.equals(path.getFileName().toString());\n    }\n\n    /**\n     * Get the parent directory containing the mark file.\n     *\n     * @return parent directory of the mark file.\n     * @see MarkFile#parentDirectory()\n     */\n    public File parentDirectory()\n    {\n        return markFile.parentDirectory();\n    }\n\n    /**\n     * Forces any changes made to the mark file's content to be written to the storage device containing the mapped\n     * file.\n     *\n     * @since 1.44.0\n     */\n    public void force()\n    {\n        if (!markFile.isClosed())\n        {\n            markFile.mappedByteBuffer().force();\n        }\n    }\n\n    private static int alignedTotalFileLength(final Archive.Context ctx)\n    {\n        final int headerLength =\n            HEADER_OFFSET +\n            MarkFileHeaderEncoder.BLOCK_LENGTH +\n            (4 * VarAsciiEncodingEncoder.lengthEncodingLength()) +\n            (null != ctx.controlChannel() ? ctx.controlChannel().length() : 0) +\n            ctx.localControlChannel().length() +\n            (null != ctx.recordingEventsChannel() ? ctx.recordingEventsChannel().length() : 0) +\n            ctx.aeronDirectoryName().length();\n\n        if (headerLength > HEADER_LENGTH)\n        {\n            throw new ArchiveException(\n                \"ArchiveMarkFile headerLength=\" + headerLength + \" > headerLengthCapacity=\" + HEADER_LENGTH);\n        }\n\n        final int filePageSize = null != ctx.aeron() ? ctx.aeron().context().filePageSize() :\n            CommonContext.driverFilePageSize(\n                new File(ctx.aeronDirectoryName()), ctx.epochClock(), new CommonContext().driverTimeoutMs());\n\n        LogBufferDescriptor.checkPageSize(filePageSize);\n\n        return BitUtil.align(HEADER_LENGTH + ctx.errorBufferLength(), filePageSize);\n    }\n\n    String aeronDirectory()\n    {\n        headerDecoder.sbeRewind();\n        headerDecoder.skipControlChannel();\n        headerDecoder.skipLocalControlChannel();\n        headerDecoder.skipEventsChannel();\n        return headerDecoder.aeronDirectory();\n    }\n\n    UnsafeBuffer buffer()\n    {\n        return buffer;\n    }\n\n    private void encode(final Archive.Context ctx)\n    {\n        headerEncoder\n            .startTimestamp(ctx.epochClock().time())\n            .controlStreamId(ctx.controlStreamId())\n            .localControlStreamId(ctx.localControlStreamId())\n            .eventsStreamId(ctx.recordingEventsStreamId())\n            .headerLength(HEADER_LENGTH)\n            .errorBufferLength(ctx.errorBufferLength())\n            .archiveId(ctx.archiveId())\n            .controlChannel(ctx.controlChannel())\n            .localControlChannel(ctx.localControlChannel())\n            .eventsChannel(ctx.recordingEventsChannel())\n            .aeronDirectory(ctx.aeronDirectoryName());\n    }\n\n    private static void validateVersion(final File markFile, final int version)\n    {\n        if (SemanticVersion.major(version) != MAJOR_VERSION)\n        {\n            throw new IllegalArgumentException(\n                \"mark file (\" + markFile.getAbsolutePath() + \") major version \" + SemanticVersion.major(version) +\n                \" does not match software: \" + MAJOR_VERSION);\n        }\n    }\n\n    private static int headerOffset(final File file)\n    {\n        final MappedByteBuffer mappedByteBuffer = IoUtil.mapExistingFile(file, FILENAME);\n        try\n        {\n            final UnsafeBuffer unsafeBuffer =\n                new UnsafeBuffer(mappedByteBuffer, 0, HEADER_OFFSET);\n            return headerOffset(unsafeBuffer);\n        }\n        finally\n        {\n            IoUtil.unmap(mappedByteBuffer);\n        }\n    }\n\n    private static int headerOffset(final UnsafeBuffer headerBuffer)\n    {\n        final MessageHeaderDecoder decoder = new MessageHeaderDecoder();\n        decoder.wrap(headerBuffer, 0);\n        return MarkFileHeaderDecoder.TEMPLATE_ID == decoder.templateId() &&\n            MarkFileHeaderDecoder.SCHEMA_ID == decoder.schemaId() ? HEADER_OFFSET : 0;\n    }\n\n    private static MarkFile openExistingMarkFile(\n        final File directory,\n        final String filename,\n        final EpochClock epochClock,\n        final long timeoutMs,\n        final IntConsumer versionCheck,\n        final Consumer<String> logger)\n    {\n        final int headerOffset = headerOffset(new File(directory, filename));\n        return new MarkFile(\n            directory,\n            filename,\n            headerOffset + MarkFileHeaderDecoder.versionEncodingOffset(),\n            headerOffset + MarkFileHeaderDecoder.activityTimestampEncodingOffset(),\n            timeoutMs,\n            epochClock,\n            versionCheck,\n            logger);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ArchiveMarkFile{\" +\n            \"markFile=\" + markFile +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveMigrationPlanner.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Creates a series of migration steps given a starting semantic version.\n * <p>\n * Migration steps are stored statically in a list sorted by order of operation. Each has\n * a minimum version. The first step that has a minimum version greater than the passed in version forms\n * the start of the migration steps. All steps afterward are included in the migration.\n * <p>\n * A step need not be a complete operation. A series of operations may be broken down in steps and\n * included with the same minimum version.\n */\nfinal class ArchiveMigrationPlanner\n{\n    private static final ArrayList<ArchiveMigrationStep> ALL_MIGRATION_STEPS = new ArrayList<>();\n\n    static\n    {\n        ALL_MIGRATION_STEPS.add(new ArchiveMigration_0_1());\n        ALL_MIGRATION_STEPS.add(new ArchiveMigration_1_2());\n        ALL_MIGRATION_STEPS.add(new ArchiveMigration_2_3());\n        // as migrations are added, they are added to the static list in order of operation\n    }\n\n    private ArchiveMigrationPlanner()\n    {\n    }\n\n    static List<ArchiveMigrationStep> createPlan(final int version)\n    {\n        final List<ArchiveMigrationStep> steps = new ArrayList<>();\n\n        for (int i = 0, size = ALL_MIGRATION_STEPS.size(); i < size; i++)\n        {\n            if (ALL_MIGRATION_STEPS.get(i).minimumVersion() > version)\n            {\n                steps.addAll(ALL_MIGRATION_STEPS.subList(i, size));\n                break;\n            }\n        }\n\n        return steps;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveMigrationStep.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport java.io.File;\nimport java.io.PrintStream;\n\ninterface ArchiveMigrationStep\n{\n    int minimumVersion();\n\n    void migrate(PrintStream stream, ArchiveMarkFile markFile, Catalog catalog, File archiveDir);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveMigration_0_1.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorEncoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderEncoder;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.LangUtil;\nimport org.agrona.SemanticVersion;\nimport org.agrona.collections.ArrayUtil;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.FileTime;\n\nimport static io.aeron.archive.MigrationUtils.fullVersionString;\nimport static io.aeron.archive.codecs.RecordingState.INVALID;\n\nclass ArchiveMigration_0_1 implements ArchiveMigrationStep\n{\n    private static final int MINIMUM_VERSION = SemanticVersion.compose(1, 0, 0);\n\n    /**\n     * {@inheritDoc}\n     */\n    public int minimumVersion()\n    {\n        return MINIMUM_VERSION;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @SuppressWarnings(\"try\")\n    public void migrate(\n        final PrintStream stream,\n        final ArchiveMarkFile markFile,\n        final Catalog catalog,\n        final File archiveDir)\n    {\n        try (FileChannel ignore = MigrationUtils.createMigrationTimestampFile(\n            archiveDir, catalog.version(), minimumVersion()))\n        {\n            catalog.forEach(\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, encoder, decoder) ->\n                {\n                    final String version0Prefix = decoder.recordingId() + \"-\";\n                    final String version0Suffix = \".rec\";\n                    String[] segmentFiles = archiveDir.list(\n                        (dir, filename) -> filename.startsWith(version0Prefix) && filename.endsWith(version0Suffix));\n\n                    if (null == segmentFiles)\n                    {\n                        segmentFiles = ArrayUtil.EMPTY_STRING_ARRAY;\n                    }\n\n                    migrateRecording(\n                        stream,\n                        archiveDir,\n                        segmentFiles,\n                        version0Prefix,\n                        version0Suffix,\n                        headerEncoder,\n                        headerDecoder,\n                        encoder,\n                        decoder);\n                });\n\n            markFile.encoder().version(minimumVersion());\n            catalog.updateVersion(minimumVersion());\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    private void migrateRecording(\n        final PrintStream stream,\n        final File archiveDir,\n        final String[] segmentFiles,\n        final String prefix,\n        final String suffix,\n        final RecordingDescriptorHeaderEncoder headerEncoder,\n        final RecordingDescriptorHeaderDecoder headerDecoder,\n        final RecordingDescriptorEncoder encoder,\n        final RecordingDescriptorDecoder decoder)\n    {\n        final long recordingId = decoder.recordingId();\n        final long startPosition = decoder.startPosition();\n        final long segmentLength = decoder.segmentFileLength();\n        final long segmentBasePosition = startPosition - (startPosition & (segmentLength - 1));\n        final int positionBitsToShift = LogBufferDescriptor.positionBitsToShift((int)segmentLength);\n\n        if (headerDecoder.state() == INVALID)\n        {\n            return;\n        }\n\n        stream.println(\n            \"(recordingId=\" + recordingId + \") segmentBasePosition=\" + segmentBasePosition + \" \" +\n            \"segmentLength=\" + segmentLength + \"(\" + positionBitsToShift + \")\");\n\n        for (final String filename : segmentFiles)\n        {\n            final int length = filename.length();\n            final int offset = prefix.length();\n            final int remaining = length - offset - suffix.length();\n            final long segmentIndex;\n\n            if (remaining > 0)\n            {\n                try\n                {\n                    segmentIndex = AsciiEncoding.parseIntAscii(filename, offset, remaining);\n                }\n                catch (final Exception ex)\n                {\n                    stream.println(\n                        \"(recordingId=\" + recordingId + \") ERR: malformed recording filename:\" + filename);\n                    throw ex;\n                }\n\n                final long segmentPosition = (segmentIndex << positionBitsToShift) + segmentBasePosition;\n                final String newFilename = prefix + segmentPosition + suffix;\n\n                final Path sourcePath = new File(archiveDir, filename).toPath();\n                final Path targetPath = sourcePath.resolveSibling(newFilename);\n\n                stream.println(\"(recordingId=\" + recordingId + \") renaming \" + sourcePath + \" -> \" + targetPath);\n\n                try\n                {\n                    Files.move(sourcePath, targetPath);\n                    Files.setLastModifiedTime(targetPath, FileTime.fromMillis(System.currentTimeMillis()));\n                }\n                catch (final Exception ex)\n                {\n                    stream.println(\n                        \"(recordingId=\" + recordingId + \") ERR: could not rename filename: \" +\n                        sourcePath + \" -> \" + targetPath);\n                    LangUtil.rethrowUnchecked(ex);\n                }\n            }\n        }\n\n        stream.println(\"(recordingId=\" + recordingId + \") OK\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"to \" + fullVersionString(minimumVersion());\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveMigration_1_2.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorEncoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderEncoder;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.LangUtil;\nimport org.agrona.SemanticVersion;\nimport org.agrona.collections.ArrayUtil;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.FileTime;\nimport java.util.Arrays;\n\nimport static io.aeron.archive.MigrationUtils.fullVersionString;\nimport static io.aeron.archive.codecs.RecordingState.INVALID;\n\nclass ArchiveMigration_1_2 implements ArchiveMigrationStep\n{\n    private static final int MINIMUM_VERSION = SemanticVersion.compose(2, 0, 0);\n\n    /**\n     * {@inheritDoc}\n     */\n    public int minimumVersion()\n    {\n        return MINIMUM_VERSION;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @SuppressWarnings(\"try\")\n    public void migrate(\n        final PrintStream stream,\n        final ArchiveMarkFile markFile,\n        final Catalog catalog,\n        final File archiveDir)\n    {\n        try (FileChannel ignore = MigrationUtils.createMigrationTimestampFile(\n            archiveDir, markFile.decoder().version(), minimumVersion()))\n        {\n            catalog.forEach(\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, encoder, decoder) ->\n                {\n                    final String version1Prefix = decoder.recordingId() + \"-\";\n                    final String version1Suffix = \".rec\";\n                    String[] segmentFiles = archiveDir.list(\n                        (dir, filename) -> filename.startsWith(version1Prefix) && filename.endsWith(version1Suffix));\n\n                    if (null == segmentFiles)\n                    {\n                        segmentFiles = ArrayUtil.EMPTY_STRING_ARRAY;\n                    }\n\n                    migrateRecording(\n                        stream,\n                        archiveDir,\n                        segmentFiles,\n                        version1Prefix,\n                        version1Suffix,\n                        headerEncoder,\n                        headerDecoder,\n                        encoder,\n                        decoder);\n                });\n\n            markFile.encoder().version(minimumVersion());\n            catalog.updateVersion(minimumVersion());\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    private void migrateRecording(\n        final PrintStream stream,\n        final File archiveDir,\n        final String[] segmentFiles,\n        final String prefix,\n        final String suffix,\n        final RecordingDescriptorHeaderEncoder headerEncoder,\n        final RecordingDescriptorHeaderDecoder headerDecoder,\n        final RecordingDescriptorEncoder encoder,\n        final RecordingDescriptorDecoder decoder)\n    {\n        final long recordingId = decoder.recordingId();\n        final long startPosition = decoder.startPosition();\n        final long termLength = decoder.termBufferLength();\n        final long segmentLength = decoder.segmentFileLength();\n        final long startTermBasePosition = startPosition - (startPosition & (termLength - 1));\n\n        if (headerDecoder.state() == INVALID)\n        {\n            return;\n        }\n\n        if (startTermBasePosition == 0 || termLength == segmentLength)\n        {\n            stream.println(\"(recordingId=\" + recordingId + \") OK - skipped.\");\n            return;\n        }\n\n        stream.println(\n            \"(recordingId=\" + recordingId + \") startTermBasePosition=\" + startTermBasePosition + \")\");\n\n        Arrays.sort(segmentFiles);\n\n        for (final String filename : segmentFiles)\n        {\n            final int length = filename.length();\n            final int offset = prefix.length();\n            final int remaining = length - offset - suffix.length();\n            final long segmentPosition;\n\n            if (remaining > 0)\n            {\n                try\n                {\n                    segmentPosition = AsciiEncoding.parseLongAscii(filename, offset, remaining);\n                }\n                catch (final Exception ex)\n                {\n                    stream.println(\n                        \"(recordingId=\" + recordingId + \") ERR: malformed recording filename:\" + filename);\n                    throw ex;\n                }\n\n                final long newSegmentPosition = startTermBasePosition + segmentPosition;\n                final String newFilename = prefix + newSegmentPosition + suffix;\n\n                final Path sourcePath = new File(archiveDir, filename).toPath();\n                final Path targetPath = sourcePath.resolveSibling(newFilename);\n\n                stream.println(\"(recordingId=\" + recordingId + \") renaming \" + sourcePath + \" -> \" + targetPath);\n\n                try\n                {\n                    Files.move(sourcePath, targetPath);\n                    Files.setLastModifiedTime(targetPath, FileTime.fromMillis(System.currentTimeMillis()));\n                }\n                catch (final Exception ex)\n                {\n                    stream.println(\n                        \"(recordingId=\" + recordingId + \") ERR: could not rename filename: \" +\n                        sourcePath + \" -> \" + targetPath);\n                    LangUtil.rethrowUnchecked(ex);\n                }\n            }\n        }\n\n        stream.println(\"(recordingId=\" + recordingId + \") OK\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"to \" + fullVersionString(minimumVersion());\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveMigration_2_3.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport org.agrona.*;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static io.aeron.archive.Archive.Configuration.CATALOG_FILE_NAME;\nimport static io.aeron.archive.Catalog.DESCRIPTOR_HEADER_LENGTH;\nimport static io.aeron.archive.Catalog.MAX_CATALOG_LENGTH;\nimport static io.aeron.archive.MigrationUtils.fullVersionString;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.nio.channels.FileChannel.MapMode.READ_WRITE;\nimport static java.nio.file.StandardOpenOption.*;\nimport static org.agrona.BitUtil.*;\n\nclass ArchiveMigration_2_3 implements ArchiveMigrationStep\n{\n    private static final int MINIMUM_VERSION = SemanticVersion.compose(3, 0, 0);\n\n    /**\n     * {@inheritDoc}\n     */\n    public int minimumVersion()\n    {\n        return MINIMUM_VERSION;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @SuppressWarnings(\"try\")\n    public void migrate(\n        final PrintStream stream,\n        final ArchiveMarkFile markFile,\n        final Catalog catalog,\n        final File archiveDir)\n    {\n        final int version = minimumVersion();\n        try (FileChannel ignore = MigrationUtils.createMigrationTimestampFile(\n            archiveDir, markFile.decoder().version(), version))\n        {\n            final File newFile = new File(archiveDir, CATALOG_FILE_NAME + \".updated\");\n            IoUtil.deleteIfExists(newFile);\n            final Path updatedCatalogFile = newFile.toPath();\n\n            try (FileChannel channel = FileChannel.open(updatedCatalogFile, READ, WRITE, CREATE_NEW))\n            {\n                final MappedByteBuffer mappedByteBuffer = channel.map(READ_WRITE, 0, MAX_CATALOG_LENGTH);\n                mappedByteBuffer.order(LITTLE_ENDIAN);\n                final int offset;\n                try\n                {\n                    offset = migrateCatalogFile(catalog, version, mappedByteBuffer);\n                }\n                finally\n                {\n                    BufferUtil.free(mappedByteBuffer);\n                }\n\n                channel.truncate(offset); // trim file to actual length used\n            }\n\n            catalog.close();\n\n            final Path catalogFilePath = updatedCatalogFile.resolveSibling(CATALOG_FILE_NAME);\n            Files.delete(catalogFilePath);\n            Files.move(updatedCatalogFile, catalogFilePath);\n\n            markFile.encoder().version(version);\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"to \" + fullVersionString(minimumVersion());\n    }\n\n    private int migrateCatalogFile(final Catalog catalog, final int version, final MappedByteBuffer mappedByteBuffer)\n    {\n        final UnsafeBuffer buffer = new UnsafeBuffer(mappedByteBuffer);\n\n        final int alignment = CACHE_LINE_LENGTH;\n\n        // Create CatalogHeader for version 3.0.0\n        final int catalogHeaderLength =\n            writeCatalogHeader(buffer, version, catalog.nextRecordingId(), alignment);\n\n        final MutableInteger offset = new MutableInteger(catalogHeaderLength);\n\n        catalog.forEach(\n            (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n            {\n                final int strippedChannelLength = descriptorDecoder.strippedChannelLength();\n                descriptorDecoder.skipStrippedChannel();\n\n                final int originalChannelLength = descriptorDecoder.originalChannelLength();\n                descriptorDecoder.skipOriginalChannel();\n\n                final int sourceIdentityLength = descriptorDecoder.sourceIdentityLength();\n\n                final int frameLength =\n                    align(DESCRIPTOR_HEADER_LENGTH + 7 * SIZE_OF_LONG + 6 * SIZE_OF_INT +\n                    SIZE_OF_INT + strippedChannelLength +\n                    SIZE_OF_INT + originalChannelLength +\n                    SIZE_OF_INT + sourceIdentityLength,\n                    alignment);\n\n                final DirectBuffer srcBuffer = headerDecoder.buffer();\n                final int headerLength = headerDecoder.encodedLength();\n\n                // Copy recording header\n                int index = offset.get();\n                buffer.putBytes(\n                    index,\n                    srcBuffer,\n                    0,\n                    headerLength);\n                index += headerLength;\n\n                // Correct length\n                buffer.putInt(offset.get(), frameLength - headerLength, LITTLE_ENDIAN);\n\n                // Copy recording descriptor\n                buffer.putBytes(\n                    index,\n                    srcBuffer,\n                    headerLength,\n                    frameLength - headerLength);\n\n                offset.addAndGet(frameLength);\n            });\n\n        return offset.get();\n    }\n\n    private int writeCatalogHeader(\n        final UnsafeBuffer buffer, final int version, final long nextRecordingId, final int alignment)\n    {\n        final int catalogHeaderLength = 32;\n\n        int index = 0;\n        buffer.putInt(index, version, LITTLE_ENDIAN); // version\n        index += SIZE_OF_INT;\n\n        buffer.putInt(index, catalogHeaderLength, LITTLE_ENDIAN); // length\n        index += SIZE_OF_INT;\n\n        buffer.putLong(index, nextRecordingId, LITTLE_ENDIAN); // nextRecordingId\n        index += SIZE_OF_LONG;\n\n        buffer.putInt(index, alignment, LITTLE_ENDIAN); // alignment\n\n        return catalogHeaderLength;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveThreadingMode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage io.aeron.archive;\n\n/**\n * Threading mode to be employed by the {@link org.agrona.concurrent.Agent}s in the {@link Archive}.\n */\npublic enum ArchiveThreadingMode\n{\n    /**\n     * No threads are started in the {@link Archive}.\n     * <p>\n     * The {@link Archive} will be made runnable via an {@link Archive#invoker()}\n     */\n    INVOKER,\n\n    /**\n     * One thread shared by all {@link Archive} {@link org.agrona.concurrent.Agent}s.\n     */\n    SHARED,\n\n    /**\n     * 3 Threads, one dedicated to each of the {@link org.agrona.concurrent.Agent}s.\n     */\n    DEDICATED\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchiveTool.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.CncFileDescriptor;\nimport io.aeron.CommonContext;\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.driver.Configuration;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport org.agrona.*;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.ByteBuffer;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\nimport java.util.function.IntConsumer;\nimport java.util.stream.Stream;\n\nimport static io.aeron.archive.Archive.Configuration.CATALOG_FILE_NAME;\nimport static io.aeron.archive.Archive.Configuration.FILE_IO_MAX_LENGTH_DEFAULT;\nimport static io.aeron.archive.ArchiveTool.VerifyOption.APPLY_CHECKSUM;\nimport static io.aeron.archive.ArchiveTool.VerifyOption.VERIFY_ALL_SEGMENT_FILES;\nimport static io.aeron.archive.Catalog.*;\nimport static io.aeron.archive.MigrationUtils.fullVersionString;\nimport static io.aeron.archive.ReplaySession.isInvalidHeader;\nimport static io.aeron.archive.checksum.Checksums.newInstance;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.AeronArchive.segmentFileBasePosition;\nimport static io.aeron.archive.codecs.RecordingState.*;\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computeTermIdFromPosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.positionBitsToShift;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.DataHeaderFlyweight.SESSION_ID_FIELD_OFFSET;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_DATA;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_PAD;\nimport static java.lang.Math.min;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.nio.channels.FileChannel.MapMode.READ_WRITE;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static java.nio.file.StandardOpenOption.*;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.emptySet;\nimport static java.util.stream.Collectors.toMap;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.BitUtil.align;\nimport static org.agrona.BufferUtil.NATIVE_BYTE_ORDER;\nimport static org.agrona.concurrent.SystemEpochClock.INSTANCE;\n\n/**\n * Tool for inspecting and performing administrative tasks on an {@link Archive} and its contents which is described\n * in a {@link Catalog}.\n */\npublic final class ArchiveTool\n{\n    /**\n     * Allows user to confirm or reject an action.\n     *\n     * @param <T> type of the context data.\n     */\n    @FunctionalInterface\n    public interface ActionConfirmation<T>\n    {\n        /**\n         * Confirm or reject the action.\n         *\n         * @param context context data.\n         * @return {@code true} confirms the action and {@code false} to reject the action.\n         */\n        boolean confirm(T context);\n    }\n\n    private ArchiveTool()\n    {\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"MethodLength\")\n    public static void main(final String[] args)\n    {\n        if (args.length == 0 || args.length > 6)\n        {\n            printHelp();\n            System.exit(-1);\n        }\n\n        final File archiveDir = new File(args[0]);\n        if (!archiveDir.exists())\n        {\n            System.err.println(\"ERR: Archive folder not found: \" + archiveDir.getAbsolutePath());\n            printHelp();\n            System.exit(-1);\n        }\n\n        final PrintStream out = System.out;\n        if (args.length > 1 && \"describe-all\".equals(args[1]))\n        {\n            describeAll(out, archiveDir);\n        }\n        else if (args.length == 2 && \"describe\".equals(args[1]))\n        {\n            describe(out, archiveDir);\n        }\n        else if (args.length == 3 && \"describe\".equals(args[1]))\n        {\n            describeRecording(out, archiveDir, Long.parseLong(args[2]));\n        }\n        else if (args.length >= 2 && \"dump\".equals(args[1]))\n        {\n            dump(\n                out,\n                archiveDir,\n                args.length >= 3 ? Long.parseLong(args[2]) : Long.MAX_VALUE,\n                ArchiveTool::continueOnFrameLimit);\n        }\n        else if (args.length == 2 && \"errors\".equals(args[1]))\n        {\n            printErrors(out, archiveDir);\n        }\n        else if (args.length == 2 && \"pid\".equals(args[1]))\n        {\n            out.println(pid(archiveDir));\n        }\n        else if (args.length >= 2 && \"verify\".equals(args[1]))\n        {\n            final boolean hasErrors;\n\n            if (args.length == 2)\n            {\n                hasErrors = !verify(\n                    out,\n                    archiveDir,\n                    emptySet(),\n                    null,\n                    ArchiveTool::truncateOnPageStraddle);\n            }\n            else if (args.length == 3)\n            {\n                if (VERIFY_ALL_SEGMENT_FILES == VerifyOption.byFlag(args[2]))\n                {\n                    hasErrors = !verify(\n                        out,\n                        archiveDir,\n                        EnumSet.of(VERIFY_ALL_SEGMENT_FILES),\n                        null,\n                        ArchiveTool::truncateOnPageStraddle);\n                }\n                else\n                {\n                    hasErrors = !verifyRecording(\n                        out,\n                        archiveDir,\n                        Long.parseLong(args[2]),\n                        emptySet(),\n                        null,\n                        ArchiveTool::truncateOnPageStraddle);\n                }\n            }\n            else if (args.length == 4)\n            {\n                if (APPLY_CHECKSUM == VerifyOption.byFlag(args[2]))\n                {\n                    hasErrors = !verify(\n                        out,\n                        archiveDir,\n                        EnumSet.of(APPLY_CHECKSUM),\n                        validateChecksumClass(args[3]),\n                        ArchiveTool::truncateOnPageStraddle);\n                }\n                else\n                {\n                    hasErrors = !verifyRecording(\n                        out,\n                        archiveDir,\n                        Long.parseLong(args[2]),\n                        EnumSet.of(VERIFY_ALL_SEGMENT_FILES),\n                        null,\n                        ArchiveTool::truncateOnPageStraddle);\n                }\n            }\n            else if (args.length == 5)\n            {\n                if (VERIFY_ALL_SEGMENT_FILES == VerifyOption.byFlag(args[2]))\n                {\n                    hasErrors = !verify(\n                        out,\n                        archiveDir,\n                        EnumSet.allOf(VerifyOption.class),\n                        validateChecksumClass(args[4]),\n                        ArchiveTool::truncateOnPageStraddle);\n                }\n                else\n                {\n                    hasErrors = !verifyRecording(\n                        out,\n                        archiveDir,\n                        Long.parseLong(args[2]),\n                        EnumSet.of(APPLY_CHECKSUM),\n                        validateChecksumClass(args[4]),\n                        ArchiveTool::truncateOnPageStraddle);\n                }\n            }\n            else\n            {\n                hasErrors = !verifyRecording(\n                    out,\n                    archiveDir,\n                    Long.parseLong(args[2]),\n                    EnumSet.allOf(VerifyOption.class),\n                    validateChecksumClass(args[5]),\n                    ArchiveTool::truncateOnPageStraddle);\n            }\n\n            if (hasErrors)\n            {\n                System.exit(-1);\n            }\n        }\n        else if (args.length >= 3 && \"checksum\".equals(args[1]))\n        {\n            if (args.length == 3)\n            {\n                checksum(out, archiveDir, false, args[2]);\n            }\n            else\n            {\n                if (\"-a\".equals(args[3]))\n                {\n                    checksum(out, archiveDir, true, args[2]);\n                }\n                else\n                {\n                    checksumRecording(\n                        out,\n                        archiveDir,\n                        Long.parseLong(args[3]),\n                        args.length > 4 && \"-a\".equals(args[4]),\n                        args[2]);\n                }\n            }\n        }\n        else if (args.length == 2 && \"count-entries\".equals(args[1]))\n        {\n            out.println(entryCount(archiveDir));\n        }\n        else if (args.length == 2 && \"max-entries\".equals(args[1]))\n        {\n            out.println(\"max-entries is deprecated please use capacity.\");\n        }\n        else if (args.length == 3 && \"max-entries\".equals(args[1]))\n        {\n            out.println(\"max-entries is deprecated please use capacity.\");\n        }\n        else if (args.length == 2 && \"migrate\".equals(args[1]))\n        {\n            out.print(\"WARNING: please ensure archive is not running and that a backup has been taken of \" +\n                \"the archive directory before attempting migration(s).\");\n\n            if (readContinueAnswer(\"Continue? (y/n)\"))\n            {\n                migrate(out, archiveDir);\n            }\n        }\n        else if (args.length == 2 && \"compact\".equals(args[1]))\n        {\n            out.print(\"WARNING: Compacting the Catalog is non-recoverable operation! All recordings in state \" +\n                \"`INVALID` will be deleted along with the corresponding segment files.\");\n\n            if (readContinueAnswer(\"Continue? (y/n)\"))\n            {\n                compact(out, archiveDir);\n            }\n        }\n        else if (args.length >= 2 && \"delete-orphaned-segments\".equals(args[1]))\n        {\n            if (args.length == 2)\n            {\n                out.print(\"WARNING: All orphaned segment files will be deleted.\");\n                if (readContinueAnswer(\"Continue? (y/n)\"))\n                {\n                    deleteOrphanedSegments(out, archiveDir);\n                }\n            }\n            else\n            {\n                final long recordingId = Long.parseLong(args[2]);\n                out.print(\"WARNING: All orphaned segment files owned by the RecordingId[\");\n                out.print(recordingId);\n                out.print(\"] will be deleted.\");\n                if (readContinueAnswer(\"Continue? (y/n)\"))\n                {\n                    deleteOrphanedSegments(out, archiveDir, recordingId);\n                }\n            }\n        }\n        else if (args.length == 3 && \"mark-valid\".equals(args[1]))\n        {\n            markRecordingValid(\n                out,\n                archiveDir,\n                Long.parseLong(args[2]));\n        }\n        else if (args.length == 3 && \"mark-invalid\".equals(args[1]))\n        {\n            markRecordingInvalid(\n                out,\n                archiveDir,\n                Long.parseLong(args[2]));\n        }\n        else\n        {\n            System.err.println(\"ERR: Invalid command\");\n            printHelp();\n            System.exit(-1);\n        }\n    }\n\n    /**\n     * Get the maximum number of entries supported by a {@link Catalog}.\n     *\n     * @param archiveDir containing the {@link Catalog}.\n     * @return the maximum number of entries supported by a {@link Catalog}.\n     * @see #capacity(File)\n     * @deprecated Use {@link #capacity(File)} instead.\n     */\n    @Deprecated\n    public static int maxEntries(final File archiveDir)\n    {\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, INSTANCE))\n        {\n            return (int)(catalog.capacity() / DEFAULT_RECORD_LENGTH);\n        }\n    }\n\n    /**\n     * Set the maximum number of entries supported by a {@link Catalog}.\n     *\n     * @param archiveDir    containing the {@link Catalog}.\n     * @param newMaxEntries value to set.\n     * @return the maximum number of entries supported by a {@link Catalog} after update.\n     * @see #capacity(File, long)\n     * @deprecated Use {@link #capacity(File, long)} instead.\n     */\n    @Deprecated\n    public static int maxEntries(final File archiveDir, final long newMaxEntries)\n    {\n        final long newCapacity = capacity(archiveDir, newMaxEntries * DEFAULT_RECORD_LENGTH);\n        return (int)(newCapacity / DEFAULT_RECORD_LENGTH);\n    }\n\n    /**\n     * Get the capacity in bytes of the {@link Catalog}.\n     *\n     * @param archiveDir containing the {@link Catalog}.\n     * @return capacity in bytes of the {@link Catalog}, i.e. size of the {@link Catalog} file.\n     */\n    public static long capacity(final File archiveDir)\n    {\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, INSTANCE))\n        {\n            return catalog.capacity();\n        }\n    }\n\n    /**\n     * Set the capacity in bytes of the {@link Catalog}. If new capacity is smaller than current {@link Catalog}\n     * capacity then this method is a no op.\n     *\n     * @param archiveDir  containing the {@link Catalog}.\n     * @param newCapacity value to set.\n     * @return the capacity of the {@link Catalog} after update.\n     */\n    public static long capacity(final File archiveDir, final long newCapacity)\n    {\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, INSTANCE, newCapacity, null, null))\n        {\n            return catalog.capacity();\n        }\n    }\n\n    /**\n     * Describe the metadata for all entries in the {@link Catalog}.\n     * This will include entries that have been invalidated.\n     *\n     * @param out        to which the entries will be printed.\n     * @param archiveDir containing the {@link Catalog}.\n     */\n    public static void describeAll(final PrintStream out, final File archiveDir)\n    {\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, INSTANCE);\n            ArchiveMarkFile markFile = openMarkFile(archiveDir, out::println))\n        {\n            printMarkInformation(markFile, out);\n            out.println(\"Catalog capacity in bytes: \" + catalog.capacity());\n            catalog.forEach((recordingDescriptorOffset, he, hd, e, d) -> out.println(d + \"|\" + hd.state()));\n        }\n    }\n\n    /**\n     * Describe the metadata for all valid entries in the {@link Catalog}.\n     * This will not include entries that have been invalidated.\n     *\n     * @param out        to which the entries will be printed.\n     * @param archiveDir containing the {@link Catalog}.\n     */\n    public static void describe(final PrintStream out, final File archiveDir)\n    {\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, INSTANCE);\n            ArchiveMarkFile markFile = openMarkFile(archiveDir, out::println))\n        {\n            printMarkInformation(markFile, out);\n            out.println(\"Catalog capacity in bytes: \" + catalog.capacity());\n            catalog.forEach((recordingDescriptorOffset, he, hd, e, d) ->\n            {\n                if (hd.state() == VALID)\n                {\n                    out.println(d);\n                }\n            });\n        }\n    }\n\n    /**\n     * Describe the metadata for an entry in the {@link Catalog} identified by recording id.\n     *\n     * @param out         to which the entry will be printed.\n     * @param archiveDir  containing the {@link Catalog}.\n     * @param recordingId to identify the entry.\n     */\n    public static void describeRecording(final PrintStream out, final File archiveDir, final long recordingId)\n    {\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, INSTANCE, MIN_CAPACITY, null, null))\n        {\n            final MutableBoolean found = new MutableBoolean(false);\n            catalog.forEach((recordingDescriptorOffset, headerEnc, headerDec, encoder, decoder) ->\n            {\n                if (decoder.recordingId() == recordingId)\n                {\n                    found.set(true);\n                    out.println(decoder);\n                }\n            });\n\n            if (!found.get())\n            {\n                throw new AeronException(\"no recording found with recordingId: \" + recordingId);\n            }\n        }\n    }\n\n    /**\n     * Count of the number of entries in the {@link Catalog}.\n     *\n     * @param archiveDir containing the {@link Catalog}.\n     * @return the number of entries in the {@link Catalog}.\n     */\n    public static int entryCount(final File archiveDir)\n    {\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, INSTANCE))\n        {\n            return catalog.entryCount();\n        }\n    }\n\n    /**\n     * Get the pid of the process for the {@link Archive}.\n     *\n     * @param archiveDir containing the {@link org.agrona.MarkFile}.\n     * @return the pid of the process for the {@link Archive}.\n     */\n    public static long pid(final File archiveDir)\n    {\n        try (ArchiveMarkFile markFile = openMarkFile(archiveDir, null))\n        {\n            return markFile.decoder().pid();\n        }\n    }\n\n    /**\n     * Print the errors in the {@link org.agrona.MarkFile}.\n     *\n     * @param out        stream to which the errors will be printed.\n     * @param archiveDir containing the {@link org.agrona.MarkFile}.\n     */\n    public static void printErrors(final PrintStream out, final File archiveDir)\n    {\n        try (ArchiveMarkFile markFile = openMarkFile(archiveDir, null))\n        {\n            printErrors(out, markFile);\n        }\n    }\n\n    /**\n     * Dump the contents of an {@link Archive} so it can be inspected or debugged. Each recording will have its\n     * contents dumped up to fragmentCountLimit before requesting to continue.\n     *\n     * @param out                               to which the contents will be printed.\n     * @param archiveDir                        containing the {@link Archive}.\n     * @param fragmentCountLimit                limit of data fragments to print from each recording before continue.\n     * @param confirmActionOnFragmentCountLimit confirm continue to dump at limit.\n     */\n    public static void dump(\n        final PrintStream out,\n        final File archiveDir,\n        final long fragmentCountLimit,\n        final ActionConfirmation<Long> confirmActionOnFragmentCountLimit)\n    {\n        if (fragmentCountLimit <= 0)\n        {\n            throw new IllegalArgumentException(\"Invalid fragmentCountLimit=\" + fragmentCountLimit);\n        }\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, INSTANCE);\n            ArchiveMarkFile markFile = openMarkFile(archiveDir, out::println))\n        {\n            printMarkInformation(markFile, out);\n            out.println(\"Catalog capacity in bytes: \" + catalog.capacity());\n\n            out.println();\n            out.println(\"Dumping up to \" + fragmentCountLimit + \" fragments per recording\");\n\n            final SimpleFragmentHandler fragmentHandler = (buffer, offset, length, frameType, flags, reservedValue) ->\n            {\n                out.println(\"data at offset [\" + offset + \"] with length = \" + length);\n                if (HDR_TYPE_PAD == frameType)\n                {\n                    out.println(\"PADDING FRAME\");\n                }\n                else if (HDR_TYPE_DATA == frameType)\n                {\n                    if ((flags & UNFRAGMENTED) != UNFRAGMENTED)\n                    {\n                        String suffix = (flags & BEGIN_FRAG_FLAG) == BEGIN_FRAG_FLAG ? \"BEGIN_FRAGMENT\" : \"\";\n                        suffix += (flags & END_FRAG_FLAG) == END_FRAG_FLAG ? \"END_FRAGMENT\" : \"\";\n                        out.println(\"Fragmented frame. \" + suffix);\n                    }\n                    out.println(PrintBufferUtil.prettyHexDump(buffer, offset, length));\n                }\n                else\n                {\n                    out.println(\"Unexpected frame type \" + frameType);\n                }\n            };\n\n            catalog.forEach(\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) -> dump(\n                out,\n                archiveDir,\n                catalog,\n                fragmentCountLimit,\n                confirmActionOnFragmentCountLimit,\n                headerDecoder,\n                descriptorDecoder,\n                fragmentHandler));\n        }\n    }\n\n    /**\n     * Set of options for {@link #verify(PrintStream, File, Set, String, ActionConfirmation)} and\n     * {@link #verifyRecording(PrintStream, File, long, Set, String, ActionConfirmation)} methods.\n     */\n    public enum VerifyOption\n    {\n        /**\n         * Enables verification for all segment files of a given recording.\n         * By default, only last segment file is verify.\n         */\n        VERIFY_ALL_SEGMENT_FILES(\"-a\"),\n\n        /**\n         * Perform checksum for each data frame within a segment file being verified.\n         */\n        APPLY_CHECKSUM(\"-checksum\");\n\n        private final String flag;\n\n        VerifyOption(final String flag)\n        {\n            this.flag = flag;\n        }\n\n        private static final Map<String, VerifyOption> BY_FLAG = Stream.of(values())\n            .collect(toMap((opt) -> opt.flag, (opt) -> opt));\n\n        /**\n         * Lookup the {@link VerifyOption} by string format of the command line flag.\n         *\n         * @param flag command line option.\n         * @return the {@link VerifyOption} which matches the flag or null if not found.\n         */\n        public static VerifyOption byFlag(final String flag)\n        {\n            return BY_FLAG.get(flag);\n        }\n    }\n\n    /**\n     * Verify descriptors in the catalog, checking recording files availability and contents.\n     * <p>\n     * Faulty entries are marked as unusable.\n     *\n     * @param out                    stream to print results and errors to.\n     * @param archiveDir             that contains {@link org.agrona.MarkFile}, {@link Catalog}, and recordings.\n     * @param options                set of options that control verification behavior.\n     * @param truncateOnPageStraddle action to perform if last fragment in the max segment file straddles the page\n     *                               boundary, i.e. if {@code true} the file will be truncated (last fragment\n     *                               will be deleted), if {@code false} the fragment is considered complete.\n     * @param checksumClassName      (optional) fully qualified class name of the {@link Checksum} implementation.\n     * @return {@code true} if no errors have been encountered, {@code false} otherwise (faulty entries are marked\n     * as unusable if {@code false} is returned)\n     */\n    public static boolean verify(\n        final PrintStream out,\n        final File archiveDir,\n        final Set<VerifyOption> options,\n        final String checksumClassName,\n        final ActionConfirmation<File> truncateOnPageStraddle)\n    {\n        final Checksum checksum = createChecksum(options, checksumClassName);\n        return verify(out, archiveDir, options, checksum, INSTANCE, truncateOnPageStraddle);\n    }\n\n    /**\n     * Verify descriptor in the catalog according to recordingId, checking recording files availability and contents.\n     * <p>\n     * Faulty entries are marked as unusable.\n     *\n     * @param out                    stream to print results and errors to.\n     * @param archiveDir             that contains {@link org.agrona.MarkFile}, {@link Catalog}, and recordings.\n     * @param recordingId            to verify.\n     * @param options                set of options that control verification behavior.\n     * @param truncateOnPageStraddle action to perform if last fragment in the max segment file straddles the page\n     *                               boundary, i.e. if {@code true} the file will be truncated (last fragment\n     *                               will be deleted), if {@code false} the fragment if considered complete.\n     * @param checksumClassName      (optional) fully qualified class name of the {@link Checksum} implementation.\n     * @return {@code true} if no errors have been encountered, {@code false} otherwise (the recording is marked\n     * as unusable if {@code false} is returned)\n     * @throws AeronException if there is no recording with {@code recordingId} in the archive\n     **/\n    public static boolean verifyRecording(\n        final PrintStream out,\n        final File archiveDir,\n        final long recordingId,\n        final Set<VerifyOption> options,\n        final String checksumClassName,\n        final ActionConfirmation<File> truncateOnPageStraddle)\n    {\n        return verifyRecording(\n            out,\n            archiveDir,\n            recordingId,\n            options,\n            createChecksum(options, checksumClassName),\n            INSTANCE,\n            truncateOnPageStraddle);\n    }\n\n    /**\n     * Compute and persist CRC-32(c) checksums for every fragment of a segment file for all recordings in the catalog.\n     *\n     * @param out               stream to print results and errors to.\n     * @param archiveDir        that contains {@link org.agrona.MarkFile}, {@link Catalog}, and recordings.\n     * @param allFiles          should compute checksums for all segment file or only for the last one.\n     * @param checksumClassName fully qualified class name of the {@link Checksum} implementation.\n     */\n    public static void checksum(\n        final PrintStream out, final File archiveDir, final boolean allFiles, final String checksumClassName)\n    {\n        checksum(out, archiveDir, allFiles, newInstance(validateChecksumClass(checksumClassName)), INSTANCE);\n    }\n\n    /**\n     * Compute and persist CRC-32(c) checksums for every fragment of a segment file(s) for a given recording.\n     *\n     * @param out               stream to print results and errors to.\n     * @param archiveDir        that contains {@link org.agrona.MarkFile}, {@link Catalog}, and recordings.\n     * @param recordingId       to compute checksums for.\n     * @param allFiles          should compute checksums for all segment file or only for the last one.\n     * @param checksumClassName fully qualified class name of the {@link Checksum} implementation.\n     * @throws AeronException if there is no recording with {@code recordingId} in the archive\n     */\n    public static void checksumRecording(\n        final PrintStream out,\n        final File archiveDir,\n        final long recordingId,\n        final boolean allFiles,\n        final String checksumClassName)\n    {\n        checksumRecording(\n            out, archiveDir, recordingId, allFiles, newInstance(validateChecksumClass(checksumClassName)), INSTANCE);\n    }\n\n    /**\n     * Mark a recording valid by marking it as {@link RecordingState#VALID} in the catalog.\n     *\n     * @param out         stream to print results and errors to.\n     * @param archiveDir  that contains {@link org.agrona.MarkFile}, {@link Catalog}, and recordings.\n     * @param recordingId to revive\n     * @throws AeronException if there is no recording with {@code recordingId} in the archive\n     */\n    public static void markRecordingValid(final PrintStream out, final File archiveDir, final long recordingId)\n    {\n        changeRecordingState(out, archiveDir, recordingId, INVALID, VALID);\n    }\n\n    /**\n     * Mark a recording invalid by marking it as {@link RecordingState#INVALID} in the catalog.\n     *\n     * @param out         stream to print results and errors to.\n     * @param archiveDir  that contains {@link org.agrona.MarkFile}, {@link Catalog}, and recordings.\n     * @param recordingId to invalidate\n     * @throws AeronException if there is no recording with {@code recordingId} in the archive\n     */\n    public static void markRecordingInvalid(final PrintStream out, final File archiveDir, final long recordingId)\n    {\n        changeRecordingState(out, archiveDir, recordingId, VALID, INVALID);\n    }\n\n    /**\n     * Migrate previous archive {@link org.agrona.MarkFile}, {@link Catalog}, and recordings from previous version\n     * to the latest version.\n     *\n     * @param out        output stream to print results and errors to.\n     * @param archiveDir that contains MarkFile, Catalog and recordings.\n     */\n    public static void migrate(final PrintStream out, final File archiveDir)\n    {\n        final EpochClock epochClock = INSTANCE;\n        try\n        {\n            final int markFileVersion;\n            final IntConsumer noVersionCheck = (version) -> {};\n            try (ArchiveMarkFile markFile = openMarkFileReadWrite(archiveDir, epochClock);\n                Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, null, noVersionCheck))\n            {\n                markFileVersion = markFile.decoder().version();\n                out.println(\"MarkFile version=\" + fullVersionString(markFileVersion));\n                out.println(\"Catalog version=\" + fullVersionString(catalog.version()));\n                out.println(\"Latest version=\" + fullVersionString(ArchiveMarkFile.SEMANTIC_VERSION));\n            }\n\n            final List<ArchiveMigrationStep> steps = ArchiveMigrationPlanner.createPlan(markFileVersion);\n            for (final ArchiveMigrationStep step : steps)\n            {\n                try (ArchiveMarkFile markFile = openMarkFileReadWrite(archiveDir, epochClock);\n                    Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, null, noVersionCheck))\n                {\n                    out.println(\"Migration step \" + step.toString());\n                    step.migrate(out, markFile, catalog, archiveDir);\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            ex.printStackTrace(out);\n        }\n    }\n\n    /**\n     * Compact Catalog file by removing all recordings in state {@link io.aeron.archive.codecs.RecordingState#INVALID}\n     * and delete the corresponding segment files.\n     *\n     * @param out        stream to print results and errors to.\n     * @param archiveDir that contains {@link MarkFile}, {@link Catalog}, and recordings.\n     */\n    public static void compact(final PrintStream out, final File archiveDir)\n    {\n        compact(out, archiveDir, INSTANCE);\n    }\n\n    /**\n     * Delete orphaned recording segments that have been detached for all recordings, i.e. outside the start and stop\n     * recording range, but are not deleted.\n     *\n     * @param out        stream to print results and errors to.\n     * @param archiveDir that contains {@link MarkFile}, {@link Catalog}, and recordings.\n     */\n    public static void deleteOrphanedSegments(final PrintStream out, final File archiveDir)\n    {\n        deleteOrphanedSegments(out, archiveDir, INSTANCE, NULL_RECORD_ID);\n    }\n\n    /**\n     * Delete orphaned recording segments that have been detached, i.e. outside the start and stop recording range,\n     * but are not deleted.\n     *\n     * @param out               stream to print results and errors to.\n     * @param archiveDir        that contains {@link MarkFile}, {@link Catalog}, and recordings.\n     * @param targetRecordingId optional recordingId to delete orphaned segments for a specific recording.\n     *                          If {@link Aeron#NULL_VALUE}, delete orphaned segments for all recordings.\n     */\n    public static void deleteOrphanedSegments(\n        final PrintStream out,\n        final File archiveDir,\n        final long targetRecordingId)\n    {\n        deleteOrphanedSegments(out, archiveDir, INSTANCE, targetRecordingId);\n    }\n\n    static void deleteOrphanedSegments(\n        final PrintStream out,\n        final File archiveDir,\n        final EpochClock epochClock,\n        final long targetRecordingId)\n    {\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            final Long2ObjectHashMap<List<String>> segmentFilesByRecordingId = indexSegmentFiles(archiveDir);\n\n            final MutableBoolean found = new MutableBoolean(false);\n            catalog.forEach((recordingDescriptorOffset,\n                headerEncoder,\n                headerDecoder,\n                descriptorEncoder,\n                descriptorDecoder) ->\n            {\n                final long recordingId = descriptorDecoder.recordingId();\n                if (NULL_RECORD_ID == targetRecordingId || targetRecordingId == recordingId)\n                {\n                    found.set(true);\n                    final List<String> files = segmentFilesByRecordingId.getOrDefault(recordingId, emptyList());\n                    deleteOrphanedSegmentFiles(out, archiveDir, descriptorDecoder, files);\n                }\n            });\n\n            if (NULL_RECORD_ID != targetRecordingId && !found.get())\n            {\n                throw new AeronException(\"no recording found with recordingId: \" + targetRecordingId);\n            }\n        }\n    }\n\n    static void compact(final PrintStream out, final File archiveDir, final EpochClock epochClock)\n    {\n        final File compactFile = new File(archiveDir, CATALOG_FILE_NAME + \".compact\");\n        try\n        {\n            final MutableInteger offset = new MutableInteger(CatalogHeaderEncoder.BLOCK_LENGTH);\n            final MutableInteger deletedRecords = new MutableInteger();\n            final MutableInteger reclaimedBytes = new MutableInteger();\n\n            final Path compactFilePath = compactFile.toPath();\n            try (FileChannel channel = FileChannel.open(compactFilePath, READ, WRITE, CREATE_NEW);\n                Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n            {\n                final MappedByteBuffer mappedByteBuffer = channel.map(READ_WRITE, 0, catalog.capacity());\n                mappedByteBuffer.order(CatalogHeaderEncoder.BYTE_ORDER);\n                try\n                {\n                    final UnsafeBuffer unsafeBuffer = new UnsafeBuffer(mappedByteBuffer);\n\n                    new CatalogHeaderEncoder()\n                        .wrap(unsafeBuffer, 0)\n                        .version(catalog.version())\n                        .length(CatalogHeaderEncoder.BLOCK_LENGTH)\n                        .nextRecordingId(catalog.nextRecordingId())\n                        .alignment(catalog.alignment());\n\n                    final Long2ObjectHashMap<List<String>> segmentFilesByRecordingId = indexSegmentFiles(archiveDir);\n\n                    catalog.forEach(\n                        (recordingDescriptorOffset,\n                        headerEncoder,\n                        headerDecoder,\n                        descriptorEncoder,\n                        descriptorDecoder) ->\n                        {\n                            final int frameLength = headerDecoder.encodedLength() + headerDecoder.length();\n                            if (VALID != headerDecoder.state())\n                            {\n                                deletedRecords.increment();\n                                reclaimedBytes.addAndGet(frameLength);\n\n                                final List<String> segmentFiles = segmentFilesByRecordingId.getOrDefault(\n                                    descriptorDecoder.recordingId(), emptyList());\n\n                                for (final String segmentFile : segmentFiles)\n                                {\n                                    IoUtil.deleteIfExists(new File(archiveDir, segmentFile));\n                                }\n                            }\n                            else\n                            {\n                                final int index = offset.getAndAdd(frameLength);\n                                unsafeBuffer.putBytes(index, headerDecoder.buffer(), 0, frameLength);\n                            }\n                        });\n                }\n                finally\n                {\n                    BufferUtil.free(mappedByteBuffer);\n                }\n\n                channel.truncate(offset.get()); // trim to size\n            }\n\n            out.println(\"Compaction result: deleted \" + deletedRecords.get() + \" records and reclaimed \" +\n                reclaimedBytes.get() + \" bytes\");\n\n            final Path catalogFilePath = compactFilePath.resolveSibling(CATALOG_FILE_NAME);\n            Files.delete(catalogFilePath);\n            Files.move(compactFilePath, catalogFilePath);\n        }\n        catch (final IOException ex)\n        {\n            ex.printStackTrace(out);\n        }\n        finally\n        {\n            IoUtil.deleteIfExists(compactFile);\n        }\n    }\n\n    static boolean verify(\n        final PrintStream out,\n        final File archiveDir,\n        final Set<VerifyOption> options,\n        final Checksum checksum,\n        final EpochClock epochClock,\n        final ActionConfirmation<File> truncateOnPageStraddle)\n    {\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, checksum, null))\n        {\n            final MutableInteger errorCount = new MutableInteger();\n            catalog.forEach(createVerifyEntryProcessor(\n                out, archiveDir, options, catalog, checksum, epochClock, errorCount, truncateOnPageStraddle));\n\n            return errorCount.get() == 0;\n        }\n    }\n\n    static boolean verifyRecording(\n        final PrintStream out,\n        final File archiveDir,\n        final long recordingId,\n        final Set<VerifyOption> options,\n        final Checksum checksum,\n        final EpochClock epochClock,\n        final ActionConfirmation<File> truncateOnPageStraddle)\n    {\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, checksum, null))\n        {\n            final MutableBoolean foundRecording = new MutableBoolean();\n            final MutableInteger errorCount = new MutableInteger();\n            final CatalogEntryProcessor delegate = createVerifyEntryProcessor(\n                out, archiveDir, options, catalog, checksum, epochClock, errorCount, truncateOnPageStraddle);\n            catalog.forEach((recordingDescriptorOffset, he, hd, encoder, decoder) ->\n            {\n                if (decoder.recordingId() == recordingId)\n                {\n                    foundRecording.set(true);\n                    delegate.accept(recordingDescriptorOffset, he, hd, encoder, decoder);\n                }\n            });\n\n            if (!foundRecording.get())\n            {\n                throw new AeronException(\"no recording found with recordingId: \" + recordingId);\n            }\n\n            return errorCount.get() == 0;\n        }\n    }\n\n    static Catalog openCatalogReadOnly(final File archiveDir, final EpochClock epochClock)\n    {\n        return new Catalog(archiveDir, epochClock);\n    }\n\n    static Catalog openCatalogReadWrite(\n        final File archiveDir,\n        final EpochClock epochClock,\n        final long capacity,\n        final Checksum checksum,\n        final IntConsumer versionCheck)\n    {\n        return new Catalog(archiveDir, epochClock, capacity, true, checksum, versionCheck);\n    }\n\n    private static void changeRecordingState(\n        final PrintStream out,\n        final File archiveDir,\n        final long recordingId,\n        final RecordingState expectedState,\n        final RecordingState targetState)\n    {\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, INSTANCE, MIN_CAPACITY, null, null))\n        {\n            final MutableBoolean found = new MutableBoolean(false);\n            catalog.forEach((recordingDescriptorOffset, he, hDecoder, descriptorEncoder, descriptorDecoder) ->\n            {\n                if (descriptorDecoder.recordingId() == recordingId)\n                {\n                    found.set(true);\n                    final RecordingState currentState = hDecoder.state();\n                    if (targetState != currentState)\n                    {\n                        if (expectedState != currentState)\n                        {\n                            throw new AeronException(\"(recordingId=\" + recordingId + \") state transition \" +\n                                currentState + \" -> \" + targetState + \" is not allowed\");\n                        }\n                        he.state(targetState);\n                        out.println(\"(recordingId=\" + recordingId + \") changed state to \" + targetState);\n                    }\n                }\n            });\n\n            if (!found.get())\n            {\n                throw new AeronException(\"no recording found with recordingId: \" + recordingId);\n            }\n        }\n    }\n\n    private static String validateChecksumClass(final String checksumClassName)\n    {\n        final String className = null == checksumClassName ? null : checksumClassName.trim();\n        if (Strings.isEmpty(className))\n        {\n            throw new IllegalArgumentException(\"Checksum class name must be specified!\");\n        }\n\n        return className;\n    }\n\n    private static Checksum createChecksum(final Set<VerifyOption> options, final String checksumClassName)\n    {\n        if (null == checksumClassName)\n        {\n            if (options.contains(APPLY_CHECKSUM))\n            {\n                throw new IllegalArgumentException(\n                    \"Checksum class name is required when \" + APPLY_CHECKSUM + \" option is specified!\");\n            }\n\n            return null;\n        }\n\n        return newInstance(checksumClassName);\n    }\n\n    private static CatalogEntryProcessor createVerifyEntryProcessor(\n        final PrintStream out,\n        final File archiveDir,\n        final Set<VerifyOption> options,\n        final Catalog catalog,\n        final Checksum checksum,\n        final EpochClock epochClock,\n        final MutableInteger errorCount,\n        final ActionConfirmation<File> truncateOnPageStraddle)\n    {\n        final ByteBuffer buffer = BufferUtil.allocateDirectAligned(FILE_IO_MAX_LENGTH_DEFAULT, CACHE_LINE_LENGTH);\n        buffer.order(LITTLE_ENDIAN);\n        final DataHeaderFlyweight headerFlyweight = new DataHeaderFlyweight(buffer);\n\n        final Long2ObjectHashMap<List<String>> segmentFilesByRecordingId = indexSegmentFiles(archiveDir);\n\n        return (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n            verifyRecording(\n                out,\n                archiveDir,\n                segmentFilesByRecordingId,\n                options,\n                catalog,\n                checksum,\n                epochClock,\n                errorCount,\n                truncateOnPageStraddle,\n                headerFlyweight,\n                recordingDescriptorOffset,\n                headerEncoder,\n                headerDecoder,\n                descriptorEncoder,\n                descriptorDecoder);\n    }\n\n    private static boolean truncateOnPageStraddle(final File maxSegmentFile)\n    {\n        return readContinueAnswer(String.format(\n            \"Last fragment in segment file: %s straddles a page boundary,%n\" +\n            \"i.e. it is not possible to verify if it was written correctly.%n%n\" +\n            \"Please choose the corrective action: (y) to truncate the file or (n) to do nothing\",\n            maxSegmentFile.getAbsolutePath()));\n    }\n\n    private static boolean continueOnFrameLimit(final Long frameLimit)\n    {\n        return readContinueAnswer(\"Specified frame limit \" + frameLimit + \" reached. Continue? (y/n)\");\n    }\n\n    private static boolean readContinueAnswer(final String msg)\n    {\n        System.out.println();\n        System.out.print(msg + \": \");\n        final String answer = new Scanner(System.in).nextLine();\n\n        return answer.isEmpty() || answer.equalsIgnoreCase(\"y\") || answer.equalsIgnoreCase(\"yes\");\n    }\n\n    private static ArchiveMarkFile openMarkFile(final File archiveDir, final Consumer<String> logger)\n    {\n        return new ArchiveMarkFile(\n            resolveMarkFileDir(archiveDir), ArchiveMarkFile.FILENAME, INSTANCE, TimeUnit.SECONDS.toMillis(5), logger);\n    }\n\n    private static ArchiveMarkFile openMarkFileReadWrite(final File archiveDir, final EpochClock epochClock)\n    {\n        return new ArchiveMarkFile(\n            resolveMarkFileDir(archiveDir),\n            ArchiveMarkFile.FILENAME,\n            epochClock,\n            TimeUnit.SECONDS.toMillis(5),\n            (version) -> {},\n            null);\n    }\n\n    private static File resolveMarkFileDir(final File archiveDir)\n    {\n        final File linkFile = new File(archiveDir, ArchiveMarkFile.LINK_FILENAME);\n        final File markFileDir;\n        if (linkFile.exists())\n        {\n            try\n            {\n                final byte[] bytes = Files.readAllBytes(linkFile.toPath());\n                final String markFileDirPath = new String(bytes, US_ASCII).trim();\n                markFileDir = new File(markFileDirPath);\n            }\n            catch (final IOException ex)\n            {\n                throw new RuntimeException(\"failed to read link file=\" + linkFile, ex);\n            }\n        }\n        else\n        {\n            markFileDir = archiveDir;\n        }\n\n        return markFileDir;\n    }\n\n    private static void dump(\n        final PrintStream out,\n        final File archiveDir,\n        final Catalog catalog,\n        final long fragmentCountLimit,\n        final ActionConfirmation<Long> continueActionOnFragmentLimit,\n        final RecordingDescriptorHeaderDecoder header,\n        final RecordingDescriptorDecoder descriptor,\n        final SimpleFragmentHandler fragmentHandler)\n    {\n        final long stopPosition = descriptor.stopPosition();\n        final long streamLength = stopPosition - descriptor.startPosition();\n\n        out.println();\n        out.println();\n        out.println(\"Recording \" + descriptor.recordingId());\n        out.println(\"  channel: \" + descriptor.strippedChannel());\n        out.println(\"  streamId: \" + descriptor.streamId());\n        out.println(\"  stream length: \" + (NULL_POSITION == stopPosition ? NULL_POSITION : streamLength));\n        out.println(header);\n        out.println(descriptor);\n\n        if (0 == streamLength)\n        {\n            out.println(\"Recording is empty\");\n            return;\n        }\n\n        try (RecordingReader reader = new RecordingReader(\n            catalog.recordingSummary(descriptor.recordingId(), new RecordingSummary()),\n            archiveDir,\n            descriptor.startPosition(),\n            NULL_POSITION))\n        {\n            boolean isActive = true;\n            long fragmentCount = fragmentCountLimit;\n            do\n            {\n                out.println();\n                out.print(\"Frame at position [\" + reader.replayPosition() + \"] \");\n\n                if (reader.poll(fragmentHandler, 1) > 0)\n                {\n                    if (--fragmentCount == 0)\n                    {\n                        fragmentCount = fragmentCountLimit;\n                        if (NULL_POSITION != stopPosition)\n                        {\n                            out.println(\n                                streamLength - reader.replayPosition() + \" bytes of (from \" + streamLength +\n                                \") remaining in recording id \" + descriptor.recordingId());\n                        }\n\n                        isActive = continueActionOnFragmentLimit.confirm(fragmentCountLimit);\n                    }\n                }\n            }\n            while (isActive && !reader.isDone());\n        }\n    }\n\n    private static void printMarkInformation(final ArchiveMarkFile markFile, final PrintStream out)\n    {\n        out.format(\"%1$tH:%1$tM:%1$tS (start: %2$tF %2$tH:%2$tM:%2$tS, activity: %3$tF %3$tH:%3$tM:%3$tS)%n\",\n            new Date(), new Date(markFile.decoder().startTimestamp()),\n            new Date(markFile.activityTimestampVolatile()));\n        out.println(markFile.decoder());\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private static void verifyRecording(\n        final PrintStream out,\n        final File archiveDir,\n        final Long2ObjectHashMap<List<String>> segmentFileByRecordingId,\n        final Set<VerifyOption> options,\n        final Catalog catalog,\n        final Checksum checksum,\n        final EpochClock epochClock,\n        final MutableInteger errorCount,\n        final ActionConfirmation<File> truncateOnPageStraddle,\n        final DataHeaderFlyweight headerFlyweight,\n        final int recordingDescriptorOffset,\n        final RecordingDescriptorHeaderEncoder headerEncoder,\n        final RecordingDescriptorHeaderDecoder headerDecoder,\n        final RecordingDescriptorEncoder encoder,\n        final RecordingDescriptorDecoder decoder)\n    {\n        final long recordingId = decoder.recordingId();\n        final RecordingState state = headerDecoder.state();\n        if (VALID != state && INVALID != state)\n        {\n            out.println(\"(recordingId=\" + recordingId + \") skipping: \" + state);\n            return;\n        }\n\n        final long startPosition = decoder.startPosition();\n        final long stopPosition = decoder.stopPosition();\n        if (isPositionInvariantViolated(out, recordingId, startPosition, stopPosition))\n        {\n            errorCount.increment();\n            headerEncoder.state(INVALID);\n            return;\n        }\n\n        final int segmentLength = decoder.segmentFileLength();\n        final int termLength = decoder.termBufferLength();\n        final List<String> segmentFiles = segmentFileByRecordingId.getOrDefault(recordingId, emptyList());\n        final String maxSegmentFile;\n        final long computedStopPosition;\n\n        try\n        {\n            maxSegmentFile = findSegmentFileWithHighestPosition(segmentFiles);\n            if (maxSegmentFile != null)\n            {\n                final long maxSegmentPosition = parseSegmentFilePosition(maxSegmentFile) + (segmentLength - 1);\n                if (startPosition > maxSegmentPosition || stopPosition > maxSegmentPosition)\n                {\n                    out.println(\"(recordingId=\" + recordingId + \") ERR: Invariant violation: startPosition=\" +\n                        startPosition + \" and/or stopPosition=\" + stopPosition + \" exceed max segment file position=\" +\n                        maxSegmentPosition);\n                    errorCount.increment();\n                    headerEncoder.state(INVALID);\n                    return;\n                }\n            }\n\n            computedStopPosition = computeStopPosition(\n                archiveDir,\n                maxSegmentFile,\n                startPosition,\n                termLength,\n                segmentLength,\n                checksum,\n                headerFlyweight,\n                truncateOnPageStraddle::confirm);\n        }\n        catch (final Exception ex)\n        {\n            final String message = ex.getMessage();\n            out.println(\"(recordingId=\" + recordingId + \") ERR: \" + (null != message ? message : ex.toString()));\n            errorCount.increment();\n            headerEncoder.state(INVALID);\n            return;\n        }\n\n        final boolean applyChecksum = options.contains(APPLY_CHECKSUM);\n        if (applyChecksum)\n        {\n            final int recordingDescriptorChecksum = catalog.computeRecordingDescriptorChecksum(\n                recordingDescriptorOffset, headerDecoder.length());\n            if (recordingDescriptorChecksum != headerDecoder.checksum())\n            {\n                out.println(\"(recordingId=\" + recordingId + \") ERR: invalid Catalog checksum: expected=\" +\n                    recordingDescriptorChecksum + \", actual=\" + headerDecoder.checksum());\n                errorCount.increment();\n                headerEncoder.state(INVALID);\n                return;\n            }\n        }\n\n        if (null != maxSegmentFile)\n        {\n            final int streamId = decoder.streamId();\n            if (options.contains(VERIFY_ALL_SEGMENT_FILES))\n            {\n                for (final String filename : segmentFiles)\n                {\n                    if (isInvalidSegmentFile(\n                        out,\n                        archiveDir,\n                        recordingId,\n                        filename,\n                        startPosition,\n                        termLength,\n                        segmentLength,\n                        streamId,\n                        decoder.initialTermId(),\n                        applyChecksum,\n                        checksum,\n                        headerFlyweight))\n                    {\n                        errorCount.increment();\n                        headerEncoder.state(INVALID);\n                        return;\n                    }\n                }\n            }\n            else if (isInvalidSegmentFile(\n                out,\n                archiveDir,\n                recordingId,\n                maxSegmentFile,\n                startPosition,\n                termLength,\n                segmentLength,\n                streamId,\n                decoder.initialTermId(),\n                applyChecksum,\n                checksum,\n                headerFlyweight))\n            {\n                errorCount.increment();\n                headerEncoder.state(INVALID);\n                return;\n            }\n        }\n\n        if (computedStopPosition != stopPosition)\n        {\n            encoder.stopPosition(computedStopPosition);\n            encoder.stopTimestamp(epochClock.time());\n        }\n\n        if (INVALID == state && !segmentFiles.isEmpty())\n        {\n            headerEncoder.state(VALID);\n        }\n        out.println(\"(recordingId=\" + recordingId + \") OK\");\n    }\n\n    private static boolean isPositionInvariantViolated(\n        final PrintStream out, final long recordingId, final long startPosition, final long stopPosition)\n    {\n        if (startPosition < 0)\n        {\n            out.println(\"(recordingId=\" + recordingId + \") ERR: Negative startPosition=\" + startPosition);\n            return true;\n        }\n        else if (isNotFrameAligned(startPosition))\n        {\n            out.println(\"(recordingId=\" + recordingId + \") ERR: Non-aligned startPosition=\" + startPosition);\n            return true;\n        }\n        else if (stopPosition != NULL_POSITION)\n        {\n            if (stopPosition < startPosition)\n            {\n                out.println(\"(recordingId=\" + recordingId + \") ERR: Invariant violation stopPosition=\" +\n                    stopPosition + \" is before startPosition=\" + startPosition);\n                return true;\n            }\n            else if (isNotFrameAligned(stopPosition))\n            {\n                out.println(\"(recordingId=\" + recordingId + \") ERR: Non-aligned stopPosition=\" + stopPosition);\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    private static boolean isNotFrameAligned(final long position)\n    {\n        return 0 != (position & (FRAME_ALIGNMENT - 1));\n    }\n\n    private static boolean isInvalidSegmentFile(\n        final PrintStream out,\n        final File archiveDir,\n        final long recordingId,\n        final String fileName,\n        final long startPosition,\n        final int termLength,\n        final int segmentLength,\n        final int streamId,\n        final int initialTermId,\n        final boolean applyChecksum,\n        final Checksum checksum,\n        final DataHeaderFlyweight headerFlyweight)\n    {\n        final File file = new File(archiveDir, fileName);\n        try (FileChannel channel = FileChannel.open(file.toPath(), READ))\n        {\n            final long offsetLimit = min(segmentLength, channel.size());\n            final int positionBitsToShift = positionBitsToShift(termLength);\n            final long startTermOffset = startPosition & (termLength - 1);\n            final long startTermBasePosition = startPosition - startTermOffset;\n            final long segmentFileBasePosition = parseSegmentFilePosition(fileName);\n\n            final ByteBuffer byteBuffer = headerFlyweight.byteBuffer();\n            final long bufferAddress = headerFlyweight.addressOffset();\n\n            long fileOffset = segmentFileBasePosition == startTermBasePosition ? startTermOffset : 0;\n            long position = segmentFileBasePosition + fileOffset;\n            do\n            {\n                byteBuffer.clear().limit(HEADER_LENGTH);\n                if (HEADER_LENGTH != channel.read(byteBuffer, fileOffset))\n                {\n                    out.println(\"(recordingId=\" + recordingId + \", file=\" + file +\n                        \") ERR: failed to read fragment header\");\n                    return true;\n                }\n\n                final int frameLength = headerFlyweight.frameLength();\n                if (0 == frameLength)\n                {\n                    break;\n                }\n\n                final int termId = computeTermIdFromPosition(position, positionBitsToShift, initialTermId);\n                final int termOffset = (int)(position & (termLength - 1));\n                if (isInvalidHeader(headerFlyweight, streamId, termId, termOffset))\n                {\n                    out.println(\"(recordingId=\" + recordingId + \", file=\" + file + \") ERR: fragment \" +\n                        \"termOffset=\" + headerFlyweight.termOffset() + \" (expected=\" + termOffset + \"), \" +\n                        \"termId=\" + headerFlyweight.termId() + \" (expected=\" + termId + \"), \" +\n                        \"streamId=\" + headerFlyweight.streamId() + \" (expected=\" + streamId + \")\");\n                    return true;\n                }\n                final int frameType = frameType(headerFlyweight, 0);\n                final int sessionId = frameSessionId(headerFlyweight, 0);\n\n                final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n                final int dataLength = alignedFrameLength - HEADER_LENGTH;\n                byteBuffer.clear().limit(dataLength);\n                if (dataLength != channel.read(byteBuffer, fileOffset + HEADER_LENGTH))\n                {\n                    out.println(\"(recordingId=\" + recordingId + \", file=\" + file + \") ERR: failed to read \" +\n                        dataLength + \" byte(s) of data at offset \" + (fileOffset + HEADER_LENGTH));\n                    return true;\n                }\n\n                if (applyChecksum && HDR_TYPE_DATA == frameType)\n                {\n                    final int computedChecksum = checksum.compute(bufferAddress, 0, dataLength);\n                    if (computedChecksum != sessionId)\n                    {\n                        out.println(\"(recordingId=\" + recordingId + \", file=\" + file + \") ERR: checksum failed \" +\n                            \"recorded=\" + sessionId + \" (expected=\" + computedChecksum + \")\");\n                        return true;\n                    }\n                }\n\n                fileOffset += alignedFrameLength;\n                position += alignedFrameLength;\n            }\n            while (fileOffset < offsetLimit);\n        }\n        catch (final IOException ex)\n        {\n            out.println(\"(recordingId=\" + recordingId + \", file=\" + file + \") ERR: failed to verify file\");\n            ex.printStackTrace(out);\n            return true;\n        }\n\n        return false;\n    }\n\n    private static void printErrors(final PrintStream out, final ArchiveMarkFile markFile)\n    {\n        out.println(\"Archive error log:\");\n        CommonContext.printErrorLog(markFile.errorBuffer(), out);\n\n        final String aeronDirectory = markFile.aeronDirectory();\n\n        out.println();\n        out.println(\"Aeron driver error log (directory: \" + aeronDirectory + \"):\");\n        final File cncFile = new File(aeronDirectory, CncFileDescriptor.CNC_FILE);\n\n        final MappedByteBuffer cncByteBuffer = IoUtil.mapExistingFile(cncFile, FileChannel.MapMode.READ_ONLY, \"cnc\");\n        final DirectBuffer cncMetaDataBuffer = CncFileDescriptor.createMetaDataBuffer(cncByteBuffer);\n        final int cncVersion = cncMetaDataBuffer.getInt(CncFileDescriptor.cncVersionOffset(0));\n\n        CncFileDescriptor.checkVersion(cncVersion);\n        CommonContext.printErrorLog(CncFileDescriptor.createErrorLogBuffer(cncByteBuffer, cncMetaDataBuffer), out);\n    }\n\n    static void checksumRecording(\n        final PrintStream out,\n        final File archiveDir,\n        final long recordingId,\n        final boolean allFiles,\n        final Checksum checksum,\n        final EpochClock epochClock)\n    {\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, checksum, null))\n        {\n            final Long2ObjectHashMap<List<String>> segmentFilesByRecordingId = indexSegmentFiles(archiveDir);\n\n            final CatalogEntryProcessor catalogEntryProcessor =\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                {\n                    final ByteBuffer buffer = ByteBuffer.allocateDirect(\n                        align(descriptorDecoder.mtuLength(), CACHE_LINE_LENGTH));\n                    buffer.order(LITTLE_ENDIAN);\n                    catalog.updateChecksum(recordingDescriptorOffset);\n                    checksum(buffer, out, archiveDir, segmentFilesByRecordingId, allFiles, checksum, descriptorDecoder);\n                };\n\n            if (!catalog.forEntry(recordingId, catalogEntryProcessor))\n            {\n                throw new AeronException(\"no recording found with recordingId: \" + recordingId);\n            }\n        }\n    }\n\n    private static void checksum(\n        final ByteBuffer buffer,\n        final PrintStream out,\n        final File archiveDir,\n        final Long2ObjectHashMap<List<String>> segmentFilesByRecordingId,\n        final boolean allFiles,\n        final Checksum checksum,\n        final RecordingDescriptorDecoder descriptorDecoder)\n    {\n        final long recordingId = descriptorDecoder.recordingId();\n        final long startPosition = descriptorDecoder.startPosition();\n        final int termLength = descriptorDecoder.termBufferLength();\n        final List<String> segmentFiles = segmentFilesByRecordingId.getOrDefault(recordingId, emptyList());\n\n        if (allFiles)\n        {\n            for (final String fileName : segmentFiles)\n            {\n                checksumSegmentFile(\n                    buffer, out, archiveDir, checksum, recordingId, fileName, startPosition, termLength);\n            }\n        }\n        else\n        {\n            final String lastFile = findSegmentFileWithHighestPosition(segmentFiles);\n            checksumSegmentFile(buffer, out, archiveDir, checksum, recordingId, lastFile, startPosition, termLength);\n        }\n    }\n\n    private static void checksumSegmentFile(\n        final ByteBuffer buffer,\n        final PrintStream out,\n        final File archiveDir,\n        final Checksum checksum,\n        final long recordingId,\n        final String fileName,\n        final long startPosition,\n        final int termLength)\n    {\n        final File file = new File(archiveDir, fileName);\n        final long startTermOffset = startPosition & (termLength - 1);\n        final long startTermBasePosition = startPosition - startTermOffset;\n        final long segmentFileBasePosition = parseSegmentFilePosition(fileName);\n\n        try (FileChannel channel = FileChannel.open(file.toPath(), READ, WRITE))\n        {\n            final HeaderFlyweight headerFlyweight = new HeaderFlyweight(buffer);\n            final long bufferAddress = headerFlyweight.addressOffset();\n            final long size = channel.size();\n            long fileOffset = segmentFileBasePosition == startTermBasePosition ? startTermOffset : 0;\n\n            while (fileOffset < size)\n            {\n                buffer.clear().limit(HEADER_LENGTH);\n                if (HEADER_LENGTH != channel.read(buffer, fileOffset))\n                {\n                    out.println(\"(recordingId=\" + recordingId + \", file=\" + file +\n                        \") ERR: failed to read fragment header\");\n                    return;\n                }\n\n                final int frameLength = headerFlyweight.frameLength();\n                if (0 == frameLength)\n                {\n                    break;\n                }\n\n                final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n                if (HDR_TYPE_DATA == frameType(headerFlyweight, 0))\n                {\n                    final int dataLength = alignedLength - HEADER_LENGTH;\n                    buffer.clear().limit(dataLength);\n                    if (dataLength != channel.read(buffer, fileOffset + HEADER_LENGTH))\n                    {\n                        out.println(\"(recordingId=\" + recordingId + \", file=\" + file + \") ERR: failed to read \" +\n                            dataLength + \" byte(s) of data at offset \" + (fileOffset + HEADER_LENGTH));\n                        return;\n                    }\n\n                    int checksumResult = checksum.compute(bufferAddress, 0, dataLength);\n                    if (NATIVE_BYTE_ORDER != LITTLE_ENDIAN)\n                    {\n                        checksumResult = Integer.reverseBytes(checksumResult);\n                    }\n\n                    buffer.clear();\n                    buffer.putInt(checksumResult).flip();\n                    channel.write(buffer, fileOffset + SESSION_ID_FIELD_OFFSET);\n                }\n\n                fileOffset += alignedLength;\n            }\n        }\n        catch (final Exception ex)\n        {\n            out.println(\"(recordingId=\" + recordingId + \", file=\" + file + \") ERR: failed to checksum\");\n            ex.printStackTrace(out);\n        }\n    }\n\n    static void checksum(\n        final PrintStream out,\n        final File archiveDir,\n        final boolean allFiles,\n        final Checksum checksum,\n        final EpochClock epochClock)\n    {\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, checksum, null))\n        {\n            final ByteBuffer buffer = ByteBuffer.allocateDirect(\n                align(Configuration.MAX_UDP_PAYLOAD_LENGTH, CACHE_LINE_LENGTH));\n            buffer.order(LITTLE_ENDIAN);\n            final Long2ObjectHashMap<List<String>> segmentFilesByRecordingId = indexSegmentFiles(archiveDir);\n\n            catalog.forEach(\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                {\n                    try\n                    {\n                        catalog.updateChecksum(recordingDescriptorOffset);\n                        checksum(\n                            buffer, out, archiveDir, segmentFilesByRecordingId, allFiles, checksum, descriptorDecoder);\n                    }\n                    catch (final Exception ex)\n                    {\n                        out.println(\n                            \"(recordingId=\" + descriptorDecoder.recordingId() + \") ERR: failed to compute checksums\");\n                        out.println(ex);\n                    }\n                });\n        }\n    }\n\n    private static void deleteOrphanedSegmentFiles(\n        final PrintStream out,\n        final File archiveDir,\n        final RecordingDescriptorDecoder descriptorDecoder,\n        final List<String> segmentFiles)\n    {\n        final long minBaseOffset = segmentFileBasePosition(\n            descriptorDecoder.startPosition(),\n            descriptorDecoder.startPosition(),\n            descriptorDecoder.termBufferLength(),\n            descriptorDecoder.segmentFileLength());\n\n        final long maxBaseOffset = NULL_POSITION == descriptorDecoder.stopPosition() ? NULL_POSITION :\n            segmentFileBasePosition(\n            descriptorDecoder.startPosition(),\n            descriptorDecoder.stopPosition(),\n            descriptorDecoder.termBufferLength(),\n            descriptorDecoder.segmentFileLength());\n\n        for (final String segmentFile : segmentFiles)\n        {\n            boolean delete;\n            try\n            {\n                final long offset = parseSegmentFilePosition(segmentFile);\n                delete = offset < minBaseOffset || NULL_POSITION != maxBaseOffset && offset > maxBaseOffset;\n            }\n            catch (final RuntimeException ex)\n            {\n                delete = true; // invalid file name\n            }\n\n            if (delete)\n            {\n                try\n                {\n                    Files.deleteIfExists(archiveDir.toPath().resolve(segmentFile));\n                }\n                catch (final IOException ex)\n                {\n                    ex.printStackTrace(out);\n                }\n            }\n        }\n    }\n\n    private static void printHelp()\n    {\n        System.out.format(\n            \"Usage: <archive-dir> <command> (items in square brackets are optional)%n%n\" +\n            \"  capacity [capacity in bytes]: gets or increases catalog capacity.%n%n\" +\n            \"  checksum className [recordingId] [-a]: computes and persists checksums.%n\" +\n            \"     checksums are computed using the specified Checksum implementation%n\" +\n            \"     (e.g. io.aeron.archive.checksum.Crc32).%n\" +\n            \"     Only the last segment file of each recording is processed by default,%n\" +\n            \"     unless flag '-a' is specified in which case all of the segment files are processed.%n%n\" +\n            \"  compact: compacts Catalog file by removing entries in non-valid state and deleting the%n\" +\n            \"     corresponding segment files.%n%n\" +\n            \"  count-entries: queries the number of `VALID` recording entries in the catalog.%n%n\" +\n            \"  delete-orphaned-segments [recordingId]: deletes orphaned recording segments that have been detached,%n\" +\n            \"     If recordingId is specified, only delete orphaned segments for that recording.%n\" +\n            \"     i.e. outside the start and stop recording range, but are not deleted.%n%n\" +\n            \"  describe: prints out descriptors for all valid recordings in the catalog.%n%n\" +\n            \"  describe recordingId: prints out descriptor for the specified recording entry in the catalog.%n%n\" +\n            \"  describe-all: prints out descriptors for all recordings in the catalog.%n%n\" +\n            \"  dump [data fragment limit per recording]: prints descriptor(s)%n\" +\n            \"     in the catalog and associated recorded data.%n%n\" +\n            \"  errors: prints errors for the archive and media driver.%n%n\" +\n            \"  max-entries [number of entries]: *** DEPRECATED: use `capacity` instead. ***%n%n\" +\n            \"  migrate: migrates archive MarkFile, Catalog, and recordings to the latest version.%n%n\" +\n            \"  pid: prints just PID of archive.%n%n\" +\n            \"  verify [recordingId] [-a] [-checksum className]: verifies descriptor(s) in the catalog%n\" +\n            \"     checking recording files availability and contents. Only the last segment file is%n\" +\n            \"     verified unless flag '-a' is specified, i.e. meaning verify all segment files.%n\" +\n            \"     To perform checksum for each data frame specify the '-checksum' flag together with%n\" +\n            \"     the Checksum implementation class name (e.g. io.aeron.archive.checksum.Crc32).%n\" +\n            \"     Faulty entries are marked as `INVALID`.%n%n\" +\n            \"  mark-invalid <recordingId>: marks a recording as invalid. %n%n\" +\n            \"  mark-valid <recordingId>: marks a previously invalidated recording as valid. %n%n\");\n        System.out.flush();\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ArchivingMediaDriver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Archiving media driver which is an aggregate of a {@link MediaDriver} and an {@link Archive}.\n */\npublic class ArchivingMediaDriver implements AutoCloseable\n{\n    private final MediaDriver driver;\n    private final Archive archive;\n\n    ArchivingMediaDriver(final MediaDriver driver, final Archive archive)\n    {\n        this.driver = driver;\n        this.archive = archive;\n    }\n\n    /**\n     * Launch an {@link Archive} with an embedded {@link MediaDriver} and await a shutdown signal.\n     *\n     * @param args command line argument which is a list for properties files as URLs or filenames.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            ArchivingMediaDriver ignore = launch(\n                new MediaDriver.Context().terminationHook(barrier::signalAll),\n                new Archive.Context()))\n        {\n            barrier.await();\n            System.out.println(\"Shutdown Archive...\");\n        }\n    }\n\n    /**\n     * Launch a new {@link ArchivingMediaDriver} with defaults for {@link io.aeron.driver.MediaDriver.Context} and\n     * {@link io.aeron.archive.Archive.Context}.\n     *\n     * @return a new {@link ArchivingMediaDriver} with default contexts.\n     */\n    public static ArchivingMediaDriver launch()\n    {\n        return launch(new MediaDriver.Context(), new Archive.Context());\n    }\n\n    /**\n     * Launch a new {@link ArchivingMediaDriver} with provided contexts.\n     *\n     * @param driverCtx  for configuring the {@link MediaDriver}.\n     * @param archiveCtx for configuring the {@link Archive}.\n     * @return a new {@link ArchivingMediaDriver} with the provided contexts.\n     */\n    public static ArchivingMediaDriver launch(final MediaDriver.Context driverCtx, final Archive.Context archiveCtx)\n    {\n        MediaDriver driver = null;\n        Archive archive = null;\n        try\n        {\n            driver = MediaDriver.launch(driverCtx);\n\n            final int errorCounterId = SystemCounterDescriptor.ERRORS.id();\n            final AtomicCounter errorCounter = null != archiveCtx.errorCounter() ?\n                archiveCtx.errorCounter() : new AtomicCounter(driverCtx.countersValuesBuffer(), errorCounterId);\n            final ErrorHandler errorHandler = null != archiveCtx.errorHandler() ?\n                archiveCtx.errorHandler() : driverCtx.errorHandler();\n\n            archive = Archive.launch(archiveCtx\n                .mediaDriverAgentInvoker(driver.sharedAgentInvoker())\n                .aeronDirectoryName(driverCtx.aeronDirectoryName())\n                .errorHandler(errorHandler)\n                .errorCounter(errorCounter));\n\n            return new ArchivingMediaDriver(driver, archive);\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietCloseAll(archive, driver);\n            throw ex;\n        }\n    }\n\n    /**\n     * Get the launched {@link Archive}.\n     *\n     * @return the launched {@link Archive}.\n     */\n    public Archive archive()\n    {\n        return archive;\n    }\n\n    /**\n     * Get the launched {@link MediaDriver}.\n     *\n     * @return the launched {@link MediaDriver}.\n     */\n    public MediaDriver mediaDriver()\n    {\n        return driver;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.closeAll(archive, driver);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/Catalog.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.*;\nimport org.agrona.*;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.StandardOpenOption;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.IntConsumer;\nimport java.util.function.Predicate;\n\nimport static io.aeron.archive.Archive.Configuration.FILE_IO_MAX_LENGTH_DEFAULT;\nimport static io.aeron.archive.Archive.Configuration.RECORDING_SEGMENT_SUFFIX;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.AeronArchive.NULL_TIMESTAMP;\nimport static io.aeron.archive.codecs.RecordingDescriptorDecoder.*;\nimport static io.aeron.archive.codecs.RecordingState.VALID;\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static java.lang.Math.max;\nimport static java.lang.Math.min;\nimport static java.nio.ByteOrder.nativeOrder;\nimport static java.nio.channels.FileChannel.MapMode.READ_ONLY;\nimport static java.nio.channels.FileChannel.MapMode.READ_WRITE;\nimport static java.nio.file.StandardOpenOption.*;\nimport static java.util.Collections.emptyList;\nimport static org.agrona.AsciiEncoding.parseLongAscii;\nimport static org.agrona.BitUtil.*;\n\n/**\n * Catalog for the archive keeps details of recorded images, past and present, and used for browsing.\n * The format is simple, allocating a variable length record for each record descriptor. An index of recording id\n * to offset if maintained for fast access. The first record contains the catalog header and is\n * {@link #DEFAULT_ALIGNMENT} in length.\n * <p>\n *\n * @see RecordingDescriptorHeaderDecoder\n * @see RecordingDescriptorDecoder\n * Catalog file format:\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                      Descriptor Length                        |\n *  +---------------------------------------------------------------+\n *  |                            State                              |\n *  +---------------------------------------------------------------+\n *  |                         Check Sum                             |\n *  +---------------------------------------------------------------+\n *  |                          Reserved                             |\n *  +---------------------------------------------------------------+\n *  |                          Reserved                             |\n *  +---------------------------------------------------------------+\n *  |                          Reserved                             |\n *  +---------------------------------------------------------------+\n *  |                          Reserved                             |\n *  +---------------------------------------------------------------+\n *  |                          Reserved                             |\n *  +---------------------------------------------------------------+\n *  |                Recording Descriptor (variable)                |\n *  |                                                              ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                          Repeats...                           |\n *  |                                                              ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\nfinal class Catalog implements AutoCloseable\n{\n    @FunctionalInterface\n    interface CatalogEntryProcessor\n    {\n        void accept(\n            int recordingDescriptorOffset,\n            RecordingDescriptorHeaderEncoder headerEncoder,\n            RecordingDescriptorHeaderDecoder headerDecoder,\n            RecordingDescriptorEncoder descriptorEncoder,\n            RecordingDescriptorDecoder descriptorDecoder);\n    }\n\n    static final int PAGE_SIZE = 4096;\n    static final int NULL_RECORD_ID = Aeron.NULL_VALUE;\n\n    static final int DESCRIPTOR_HEADER_LENGTH = RecordingDescriptorHeaderDecoder.BLOCK_LENGTH;\n    @Deprecated\n    static final int DEFAULT_RECORD_LENGTH = 1024;\n    static final int DEFAULT_ALIGNMENT = 1024;\n    static final long MAX_CATALOG_LENGTH = Integer.MAX_VALUE;\n    static final long DEFAULT_CAPACITY = 1024 * 1024;\n    static final long MIN_CAPACITY = CatalogHeaderDecoder.BLOCK_LENGTH;\n\n    private final CatalogHeaderDecoder catalogHeaderDecoder = new CatalogHeaderDecoder();\n    private final CatalogHeaderEncoder catalogHeaderEncoder = new CatalogHeaderEncoder();\n\n    private final RecordingDescriptorHeaderDecoder descriptorHeaderDecoder = new RecordingDescriptorHeaderDecoder();\n    private final RecordingDescriptorHeaderEncoder descriptorHeaderEncoder = new RecordingDescriptorHeaderEncoder();\n\n    private final RecordingDescriptorEncoder descriptorEncoder = new RecordingDescriptorEncoder();\n    private final RecordingDescriptorDecoder descriptorDecoder = new RecordingDescriptorDecoder();\n\n    private final boolean forceWrites;\n    private final boolean forceMetadata;\n    private boolean isClosed;\n    private final File catalogFile;\n    private final File archiveDir;\n    private final EpochClock epochClock;\n    private final Checksum checksum;\n    private final CatalogIndex catalogIndex = new CatalogIndex();\n    private final int alignment;\n    private final int firstRecordingDescriptorOffset;\n\n    private FileChannel catalogChannel;\n    private MappedByteBuffer catalogByteBuffer;\n    private UnsafeBuffer catalogBuffer;\n    private UnsafeBuffer fieldAccessBuffer;\n    private UnsafeBuffer headerAccessBuffer;\n    private long catalogByteBufferAddress;\n    private long capacity;\n    private long nextRecordingId;\n    private int nextRecordingDescriptorOffset;\n\n    Catalog(\n        final File archiveDir,\n        final FileChannel archiveDirChannel,\n        final int fileSyncLevel,\n        final long catalogCapacity,\n        final EpochClock epochClock,\n        final Checksum checksum,\n        final UnsafeBuffer buffer)\n    {\n        this.archiveDir = archiveDir;\n        this.forceWrites = fileSyncLevel > 0;\n        this.forceMetadata = fileSyncLevel > 1;\n        this.epochClock = epochClock;\n        this.checksum = checksum;\n\n        validateCapacity(catalogCapacity);\n\n        catalogFile = new File(archiveDir, Archive.Configuration.CATALOG_FILE_NAME);\n        try\n        {\n            final boolean catalogExists = catalogFile.exists();\n            MappedByteBuffer catalogMappedByteBuffer = null;\n            FileChannel catalogFileChannel = null;\n\n            try\n            {\n                catalogFileChannel = FileChannel.open(catalogFile.toPath(), CREATE, READ, WRITE, SPARSE);\n                if (catalogExists)\n                {\n                    capacity = max(catalogFileChannel.size(), catalogCapacity);\n                }\n                else\n                {\n                    capacity = catalogCapacity;\n                }\n\n                catalogMappedByteBuffer = catalogFileChannel.map(READ_WRITE, 0, capacity);\n            }\n            catch (final Exception ex)\n            {\n                CloseHelper.close(catalogFileChannel);\n                LangUtil.rethrowUnchecked(ex);\n            }\n\n            catalogChannel = catalogFileChannel;\n            initBuffers(catalogMappedByteBuffer);\n\n            final UnsafeBuffer catalogHeaderBuffer = new UnsafeBuffer(catalogByteBuffer);\n            catalogHeaderDecoder.wrap(\n                catalogHeaderBuffer, 0, CatalogHeaderDecoder.BLOCK_LENGTH, CatalogHeaderDecoder.SCHEMA_VERSION);\n            catalogHeaderEncoder.wrap(catalogHeaderBuffer, 0);\n\n            if (catalogExists)\n            {\n                final int version = catalogHeaderDecoder.version();\n                if (SemanticVersion.major(version) != ArchiveMarkFile.MAJOR_VERSION)\n                {\n                    throw new ArchiveException(\n                        \"incompatible catalog file version \" + SemanticVersion.toString(version) +\n                        \", archive software is \" + SemanticVersion.toString(ArchiveMarkFile.SEMANTIC_VERSION));\n                }\n\n                alignment = catalogHeaderDecoder.alignment();\n                nextRecordingId = catalogHeaderDecoder.nextRecordingId();\n            }\n            else\n            {\n                alignment = CACHE_LINE_LENGTH;\n\n                catalogHeaderEncoder\n                    .version(ArchiveMarkFile.SEMANTIC_VERSION)\n                    .length(CatalogHeaderEncoder.BLOCK_LENGTH)\n                    .nextRecordingId(nextRecordingId)\n                    .alignment(alignment);\n\n                forceWrites(archiveDirChannel);\n            }\n            firstRecordingDescriptorOffset = CatalogHeaderEncoder.BLOCK_LENGTH;\n\n            buildIndex(true);\n            refreshCatalog(true, checksum, buffer);\n        }\n        catch (final Exception ex)\n        {\n            close();\n            throw ex;\n        }\n    }\n\n    Catalog(final File archiveDir, final EpochClock epochClock)\n    {\n        this(archiveDir, epochClock, MIN_CAPACITY, false, null, null);\n    }\n\n    Catalog(\n        final File archiveDir,\n        final EpochClock epochClock,\n        final long catalogCapacity,\n        final boolean writable,\n        final Checksum checksum,\n        final IntConsumer versionCheck)\n    {\n        this.archiveDir = archiveDir;\n        this.forceWrites = false;\n        this.forceMetadata = false;\n        this.epochClock = epochClock;\n        this.catalogChannel = null;\n        this.checksum = checksum;\n        catalogFile = new File(archiveDir, Archive.Configuration.CATALOG_FILE_NAME);\n\n        validateCapacity(catalogCapacity);\n\n        try\n        {\n            MappedByteBuffer catalogMappedByteBuffer = null;\n\n            final StandardOpenOption[] openOptions =\n                writable ? new StandardOpenOption[]{ READ, WRITE, SPARSE } : new StandardOpenOption[]{ READ };\n\n            try (FileChannel channel = FileChannel.open(catalogFile.toPath(), openOptions))\n            {\n                capacity = max(channel.size(), catalogCapacity);\n                catalogMappedByteBuffer = channel.map(writable ? READ_WRITE : READ_ONLY, 0, capacity);\n            }\n            catch (final Exception ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n\n            initBuffers(catalogMappedByteBuffer);\n\n            final UnsafeBuffer catalogHeaderBuffer = new UnsafeBuffer(catalogByteBuffer);\n            catalogHeaderDecoder.wrap(\n                catalogHeaderBuffer, 0, CatalogHeaderDecoder.BLOCK_LENGTH, CatalogHeaderDecoder.SCHEMA_VERSION);\n            catalogHeaderEncoder.wrap(catalogHeaderBuffer, 0);\n\n            final int version = catalogHeaderDecoder.version();\n            if (null == versionCheck)\n            {\n                if (SemanticVersion.major(version) != ArchiveMarkFile.MAJOR_VERSION)\n                {\n                    throw new ArchiveException(\n                        \"incompatible catalog file version \" + SemanticVersion.toString(version) +\n                        \", archive software is \" + SemanticVersion.toString(ArchiveMarkFile.SEMANTIC_VERSION));\n                }\n            }\n            else\n            {\n                versionCheck.accept(version);\n            }\n\n            final int alignment = catalogHeaderDecoder.alignment();\n            if (0 != alignment)\n            {\n                this.alignment = alignment;\n                firstRecordingDescriptorOffset = CatalogHeaderEncoder.BLOCK_LENGTH;\n                nextRecordingId = catalogHeaderDecoder.nextRecordingId();\n            }\n            else\n            {\n                this.alignment = DEFAULT_ALIGNMENT;\n                firstRecordingDescriptorOffset = DEFAULT_ALIGNMENT;\n            }\n\n            buildIndex(writable);\n            refreshCatalog(false, null, null);\n        }\n        catch (final Exception ex)\n        {\n            close();\n            throw ex;\n        }\n    }\n\n    public void close()\n    {\n        if (!isClosed)\n        {\n            isClosed = true;\n            unmapAndCloseChannel();\n        }\n    }\n\n    boolean isClosed()\n    {\n        return isClosed;\n    }\n\n    long capacity()\n    {\n        return capacity;\n    }\n\n    int alignment()\n    {\n        return alignment;\n    }\n\n    int entryCount()\n    {\n        return catalogIndex.size();\n    }\n\n    long nextRecordingId()\n    {\n        return nextRecordingId;\n    }\n\n    CatalogIndex index()\n    {\n        return catalogIndex;\n    }\n\n    int version()\n    {\n        return catalogHeaderDecoder.version();\n    }\n\n    void updateVersion(final int version)\n    {\n        catalogHeaderEncoder.version(version);\n    }\n\n    long addNewRecording(\n        final long startPosition,\n        final long stopPosition,\n        final long startTimestamp,\n        final long stopTimestamp,\n        final int imageInitialTermId,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int mtuLength,\n        final int sessionId,\n        final int streamId,\n        final String strippedChannel,\n        final String originalChannel,\n        final String sourceIdentity)\n    {\n        final int frameLength = recordingDescriptorFrameLength(strippedChannel, originalChannel, sourceIdentity);\n        final int recordingDescriptorOffset = nextRecordingDescriptorOffset;\n\n        if (recordingDescriptorOffset + frameLength > capacity)\n        {\n            growCatalog(MAX_CATALOG_LENGTH, frameLength);\n        }\n\n        final long recordingId = nextRecordingId;\n\n        catalogBuffer.wrap(catalogByteBuffer, recordingDescriptorOffset, frameLength);\n        descriptorEncoder\n            .wrap(catalogBuffer, DESCRIPTOR_HEADER_LENGTH)\n            .recordingId(recordingId)\n            .startTimestamp(startTimestamp)\n            .stopTimestamp(stopTimestamp)\n            .startPosition(startPosition)\n            .stopPosition(stopPosition)\n            .initialTermId(imageInitialTermId)\n            .segmentFileLength(segmentFileLength)\n            .termBufferLength(termBufferLength)\n            .mtuLength(mtuLength)\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .strippedChannel(strippedChannel)\n            .originalChannel(originalChannel)\n            .sourceIdentity(sourceIdentity);\n\n        final int recordingLength = frameLength - DESCRIPTOR_HEADER_LENGTH;\n        descriptorHeaderEncoder\n            .wrap(catalogBuffer, 0)\n            .length(recordingLength)\n            .checksum(computeRecordingDescriptorChecksum(recordingDescriptorOffset, recordingLength))\n            .state(VALID);\n\n        catalogHeaderEncoder.nextRecordingId(recordingId + 1);\n\n        forceWrites(catalogChannel);\n\n        nextRecordingId = recordingId + 1;\n        nextRecordingDescriptorOffset = recordingDescriptorOffset + frameLength;\n        catalogIndex.add(recordingId, recordingDescriptorOffset);\n\n        return recordingId;\n    }\n\n    long addNewRecording(\n        final long startPosition,\n        final long startTimestamp,\n        final int imageInitialTermId,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int mtuLength,\n        final int sessionId,\n        final int streamId,\n        final String strippedChannel,\n        final String originalChannel,\n        final String sourceIdentity)\n    {\n        return addNewRecording(\n            startPosition,\n            NULL_POSITION,\n            startTimestamp,\n            NULL_TIMESTAMP,\n            imageInitialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId,\n            streamId,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity);\n    }\n\n    boolean wrapDescriptor(final long recordingId, final UnsafeBuffer buffer)\n    {\n        if (recordingId < 0)\n        {\n            return false;\n        }\n\n        final int recordingDescriptorOffset = recordingDescriptorOffset(recordingId);\n        if (recordingDescriptorOffset < 0)\n        {\n            return false;\n        }\n\n        return wrapDescriptorAtOffset(buffer, recordingDescriptorOffset) > 0;\n    }\n\n    int wrapDescriptorAtOffset(final UnsafeBuffer buffer, final int recordingDescriptorOffset)\n    {\n        final int recordingLength = fieldAccessBuffer.getInt(\n            recordingDescriptorOffset + RecordingDescriptorHeaderDecoder.lengthEncodingOffset(), BYTE_ORDER);\n\n        if (recordingLength > 0)\n        {\n            final int frameLength = align(recordingLength + DESCRIPTOR_HEADER_LENGTH, alignment);\n            buffer.wrap(catalogByteBuffer, recordingDescriptorOffset, frameLength);\n            return frameLength;\n        }\n\n        return -1;\n    }\n\n    boolean hasRecording(final long recordingId)\n    {\n        return recordingId >= 0 && recordingDescriptorOffset(recordingId) > 0;\n    }\n\n    int forEach(final CatalogEntryProcessor consumer)\n    {\n        int count = 0;\n        int offset = firstRecordingDescriptorOffset;\n        while (offset < nextRecordingDescriptorOffset)\n        {\n            final int frameLength = wrapDescriptorAtOffset(catalogBuffer, offset);\n            if (frameLength < 0)\n            {\n                break;\n            }\n\n            invokeEntryProcessor(offset, consumer);\n\n            offset += frameLength;\n            count++;\n        }\n\n        return count;\n    }\n\n    boolean forEntry(final long recordingId, final CatalogEntryProcessor consumer)\n    {\n        if (recordingId < 0)\n        {\n            return false;\n        }\n\n        final int recordingDescriptorOffset = recordingDescriptorOffset(recordingId);\n        if (recordingDescriptorOffset < 0)\n        {\n            return false;\n        }\n\n        if (wrapDescriptorAtOffset(catalogBuffer, recordingDescriptorOffset) > 0)\n        {\n            invokeEntryProcessor(recordingDescriptorOffset, consumer);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    long findLast(final long minRecordingId, final int sessionId, final int streamId, final byte[] channelFragment)\n    {\n        if (minRecordingId < 0)\n        {\n            return NULL_RECORD_ID;\n        }\n\n        final long[] index = catalogIndex.index();\n        final int lastPosition = catalogIndex.lastPosition();\n        for (int i = lastPosition; i >= 0; i -= 2)\n        {\n            final long recordingId = index[i];\n            if (recordingId < minRecordingId)\n            {\n                break;\n            }\n\n            wrapDescriptorAtOffset(catalogBuffer, (int)index[i + 1]);\n\n            descriptorDecoder.wrap(\n                catalogBuffer,\n                DESCRIPTOR_HEADER_LENGTH,\n                RecordingDescriptorDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.SCHEMA_VERSION);\n\n            if (sessionId == descriptorDecoder.sessionId() &&\n                streamId == descriptorDecoder.streamId() &&\n                originalChannelContains(descriptorDecoder, channelFragment))\n            {\n                return recordingId;\n            }\n        }\n\n        return NULL_RECORD_ID;\n    }\n\n    //\n    // Methods for accessing specific record fields by recordingId.\n    // Note: These methods are thread safe.\n    /////////////////////////////////////////////////////////////\n\n    static boolean originalChannelContains(\n        final RecordingDescriptorDecoder descriptorDecoder, final byte[] channelFragment)\n    {\n        final int fragmentLength = channelFragment.length;\n        if (0 == fragmentLength)\n        {\n            return true;\n        }\n\n        final int limit = descriptorDecoder.limit();\n        final int strippedChannelLength = descriptorDecoder.strippedChannelLength();\n        final int originalChannelOffset =\n            limit + RecordingDescriptorDecoder.strippedChannelHeaderLength() + strippedChannelLength;\n\n        descriptorDecoder.limit(originalChannelOffset);\n        final int channelLength = descriptorDecoder.originalChannelLength();\n        descriptorDecoder.limit(limit);\n\n        final DirectBuffer buffer = descriptorDecoder.buffer();\n        int offset = descriptorDecoder.offset() + descriptorDecoder.sbeBlockLength() +\n            RecordingDescriptorDecoder.strippedChannelHeaderLength() + strippedChannelLength +\n            RecordingDescriptorDecoder.originalChannelHeaderLength();\n\n        nextChar:\n        for (int end = offset + (channelLength - fragmentLength); offset <= end; offset++)\n        {\n            for (int i = 0; i < fragmentLength; i++)\n            {\n                if (buffer.getByte(offset + i) != channelFragment[i])\n                {\n                    continue nextChar;\n                }\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n\n    void recordingStopped(final long recordingId, final long position, final long timestampMs)\n    {\n        final int recordingDescriptorOffset = recordingDescriptorOffset(recordingId);\n        final int offset = recordingDescriptorOffset + DESCRIPTOR_HEADER_LENGTH;\n        final long stopPosition = nativeOrder() == BYTE_ORDER ? position : Long.reverseBytes(position);\n\n        fieldAccessBuffer.putLong(offset + stopTimestampEncodingOffset(), timestampMs, BYTE_ORDER);\n        fieldAccessBuffer.putLongVolatile(offset + stopPositionEncodingOffset(), stopPosition);\n        updateChecksum(recordingDescriptorOffset);\n        forceWrites(catalogChannel);\n    }\n\n    void stopPosition(final long recordingId, final long position)\n    {\n        final int recordingDescriptorOffset = recordingDescriptorOffset(recordingId);\n        final int offset = recordingDescriptorOffset + DESCRIPTOR_HEADER_LENGTH;\n        final long stopPosition = nativeOrder() == BYTE_ORDER ? position : Long.reverseBytes(position);\n\n        fieldAccessBuffer.putLongVolatile(offset + stopPositionEncodingOffset(), stopPosition);\n        updateChecksum(recordingDescriptorOffset);\n        forceWrites(catalogChannel);\n    }\n\n    void extendRecording(\n        final long recordingId, final long controlSessionId, final long correlationId, final int sessionId)\n    {\n        final int recordingDescriptorOffset = recordingDescriptorOffset(recordingId);\n        final int offset = recordingDescriptorOffset + DESCRIPTOR_HEADER_LENGTH;\n        final long stopPosition = nativeOrder() == BYTE_ORDER ? NULL_POSITION : Long.reverseBytes(NULL_POSITION);\n\n        fieldAccessBuffer.putLong(offset + controlSessionIdEncodingOffset(), controlSessionId, BYTE_ORDER);\n        fieldAccessBuffer.putLong(offset + correlationIdEncodingOffset(), correlationId, BYTE_ORDER);\n        fieldAccessBuffer.putLong(offset + stopTimestampEncodingOffset(), NULL_TIMESTAMP, BYTE_ORDER);\n        fieldAccessBuffer.putInt(offset + sessionIdEncodingOffset(), sessionId, BYTE_ORDER);\n        fieldAccessBuffer.putLongVolatile(offset + stopPositionEncodingOffset(), stopPosition);\n        updateChecksum(recordingDescriptorOffset);\n        forceWrites(catalogChannel);\n    }\n\n    void replaceRecording(\n        final long recordingId,\n        final long startPosition,\n        final long stopPosition,\n        final long startTimestamp,\n        final long stopTimestamp,\n        final int imageInitialTermId,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int mtuLength,\n        final int sessionId,\n        final int streamId,\n        final String strippedChannel,\n        final String originalChannel,\n        final String sourceIdentity)\n    {\n        final int recordingOffset = recordingDescriptorOffset(recordingId);\n        if (-1 == recordingOffset)\n        {\n            throw new ArchiveException(\"unknown recording id: \" + recordingId);\n        }\n\n        final int recordingLength = fieldAccessBuffer.getInt(\n            recordingOffset + RecordingDescriptorHeaderDecoder.lengthEncodingOffset(), BYTE_ORDER);\n        final int oldFrameLength = align(recordingLength + DESCRIPTOR_HEADER_LENGTH, alignment);\n        final int newFrameLength = recordingDescriptorFrameLength(strippedChannel, originalChannel, sourceIdentity);\n        final int length, checksumLength;\n        if (newFrameLength > oldFrameLength)\n        {\n            length = checksumLength = newFrameLength - DESCRIPTOR_HEADER_LENGTH;\n\n            final int shiftBytes = newFrameLength - oldFrameLength;\n            final int endOfLastRecording = nextRecordingDescriptorOffset;\n            if (endOfLastRecording + shiftBytes > capacity)\n            {\n                growCatalog(MAX_CATALOG_LENGTH, shiftBytes);\n            }\n\n            nextRecordingDescriptorOffset += shiftBytes;\n\n            final long[] index = catalogIndex.index();\n            final int lastPosition = catalogIndex.lastPosition();\n            if (recordingId != index[lastPosition])\n            {\n                shiftDataToTheRight(recordingOffset, oldFrameLength, newFrameLength, endOfLastRecording);\n                fixupIndexForShifterRecordings(index, lastPosition, recordingId, shiftBytes);\n            }\n\n            catalogBuffer.wrap(catalogByteBuffer, recordingOffset, newFrameLength);\n        }\n        else\n        {\n            length = oldFrameLength - DESCRIPTOR_HEADER_LENGTH;\n            checksumLength = newFrameLength - DESCRIPTOR_HEADER_LENGTH;\n            catalogBuffer.wrap(catalogByteBuffer, recordingOffset, oldFrameLength);\n            catalogBuffer.setMemory(newFrameLength, oldFrameLength - newFrameLength, (byte)0);\n        }\n\n        descriptorEncoder\n            .wrap(catalogBuffer, DESCRIPTOR_HEADER_LENGTH)\n            .recordingId(recordingId)\n            .startTimestamp(startTimestamp)\n            .stopTimestamp(stopTimestamp)\n            .startPosition(startPosition)\n            .stopPosition(stopPosition)\n            .initialTermId(imageInitialTermId)\n            .segmentFileLength(segmentFileLength)\n            .termBufferLength(termBufferLength)\n            .mtuLength(mtuLength)\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .strippedChannel(strippedChannel)\n            .originalChannel(originalChannel)\n            .sourceIdentity(sourceIdentity);\n\n        descriptorHeaderEncoder\n            .wrap(catalogBuffer, 0)\n            .length(length)\n            .checksum(computeRecordingDescriptorChecksum(recordingOffset, checksumLength))\n            .state(VALID);\n\n        forceWrites(catalogChannel);\n    }\n\n    long startPosition(final long recordingId)\n    {\n        final int offset = recordingDescriptorOffset(recordingId) +\n            DESCRIPTOR_HEADER_LENGTH + startPositionEncodingOffset();\n\n        final long startPosition = fieldAccessBuffer.getLongVolatile(offset);\n\n        return nativeOrder() == BYTE_ORDER ? startPosition : Long.reverseBytes(startPosition);\n    }\n\n    void startPosition(final long recordingId, final long position)\n    {\n        final int recordingDescriptorOffset = recordingDescriptorOffset(recordingId);\n        final int offset = recordingDescriptorOffset +\n            DESCRIPTOR_HEADER_LENGTH + startPositionEncodingOffset();\n\n        fieldAccessBuffer.putLong(offset, position, BYTE_ORDER);\n        updateChecksum(recordingDescriptorOffset);\n        forceWrites(catalogChannel);\n    }\n\n    long stopPosition(final long recordingId)\n    {\n        final int offset = recordingDescriptorOffset(recordingId) +\n            DESCRIPTOR_HEADER_LENGTH + stopPositionEncodingOffset();\n\n        final long stopPosition = fieldAccessBuffer.getLongVolatile(offset);\n\n        return nativeOrder() == BYTE_ORDER ? stopPosition : Long.reverseBytes(stopPosition);\n    }\n\n    boolean changeState(final long recordingId, final RecordingState newState)\n    {\n        if (recordingId >= 0)\n        {\n            final long offset = catalogIndex.remove(recordingId);\n            if (CatalogIndex.NULL_VALUE != offset)\n            {\n                fieldAccessBuffer.putInt(\n                    (int)offset + RecordingDescriptorHeaderEncoder.stateEncodingOffset(),\n                    newState.value(),\n                    BYTE_ORDER);\n\n                forceWrites(catalogChannel);\n\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    RecordingSummary recordingSummary(final long recordingId, final RecordingSummary summary)\n    {\n        final int offset = recordingDescriptorOffset(recordingId) + DESCRIPTOR_HEADER_LENGTH;\n\n        summary.recordingId = recordingId;\n        summary.startPosition = fieldAccessBuffer.getLong(offset + startPositionEncodingOffset(), BYTE_ORDER);\n        summary.stopPosition = fieldAccessBuffer.getLong(offset + stopPositionEncodingOffset(), BYTE_ORDER);\n        summary.initialTermId = fieldAccessBuffer.getInt(offset + initialTermIdEncodingOffset(), BYTE_ORDER);\n        summary.segmentFileLength = fieldAccessBuffer.getInt(offset + segmentFileLengthEncodingOffset(), BYTE_ORDER);\n        summary.termBufferLength = fieldAccessBuffer.getInt(offset + termBufferLengthEncodingOffset(), BYTE_ORDER);\n        summary.mtuLength = fieldAccessBuffer.getInt(offset + mtuLengthEncodingOffset(), BYTE_ORDER);\n        summary.streamId = fieldAccessBuffer.getInt(offset + streamIdEncodingOffset(), BYTE_ORDER);\n        summary.sessionId = fieldAccessBuffer.getInt(offset + sessionIdEncodingOffset(), BYTE_ORDER);\n\n        return summary;\n    }\n\n    static int descriptorLength(final UnsafeBuffer descriptorBuffer)\n    {\n        return descriptorBuffer.getInt(RecordingDescriptorHeaderDecoder.lengthEncodingOffset(), BYTE_ORDER);\n    }\n\n    static boolean isValidDescriptor(final UnsafeBuffer descriptorBuffer)\n    {\n        return VALID.value() ==\n            descriptorBuffer.getInt(RecordingDescriptorHeaderDecoder.stateEncodingOffset(), BYTE_ORDER);\n    }\n\n    static long recordingId(final UnsafeBuffer descriptorBuffer)\n    {\n        return descriptorBuffer.getLong(DESCRIPTOR_HEADER_LENGTH + recordingIdEncodingOffset(), BYTE_ORDER);\n    }\n\n    int recordingDescriptorOffset(final long recordingId)\n    {\n        final long recordingDescriptorOffset = catalogIndex.recordingOffset(recordingId);\n        if (CatalogIndex.NULL_VALUE == recordingDescriptorOffset)\n        {\n            return -1;\n        }\n        return (int)recordingDescriptorOffset;\n    }\n\n    void growCatalog(final long maxCatalogCapacity, final int frameLength)\n    {\n        final long oldCapacity = capacity;\n        final long recordingOffset = nextRecordingDescriptorOffset;\n        final long targetCapacity = recordingOffset + frameLength;\n        if (targetCapacity > maxCatalogCapacity)\n        {\n            if (maxCatalogCapacity == oldCapacity)\n            {\n                throw new ArchiveException(\"catalog is full, max capacity reached: \" + maxCatalogCapacity);\n            }\n            else\n            {\n                throw new ArchiveException(\n                    \"recording is too big: total recording length is \" + frameLength + \" bytes,\" +\n                    \" available space is \" + (maxCatalogCapacity - recordingOffset) + \" bytes\");\n            }\n        }\n\n        long newCapacity = oldCapacity;\n        while (newCapacity < targetCapacity)\n        {\n            newCapacity = min(newCapacity + (newCapacity >> 1), maxCatalogCapacity);\n        }\n\n        final MappedByteBuffer mappedByteBuffer;\n        try\n        {\n            unmapAndCloseChannel();\n            catalogChannel = FileChannel.open(catalogFile.toPath(), READ, WRITE, SPARSE);\n            mappedByteBuffer = catalogChannel.map(READ_WRITE, 0, newCapacity);\n        }\n        catch (final Exception ex)\n        {\n            close();\n            LangUtil.rethrowUnchecked(ex);\n            return;\n        }\n\n        capacity = newCapacity;\n        initBuffers(mappedByteBuffer);\n\n        final UnsafeBuffer catalogHeaderBuffer = new UnsafeBuffer(catalogByteBuffer);\n        catalogHeaderDecoder.wrap(\n            catalogHeaderBuffer, 0, CatalogHeaderDecoder.BLOCK_LENGTH, CatalogHeaderDecoder.SCHEMA_VERSION);\n        catalogHeaderEncoder.wrap(catalogHeaderBuffer, 0);\n\n        catalogResized(oldCapacity, newCapacity);\n    }\n\n    void updateChecksum(final int recordingDescriptorOffset)\n    {\n        if (null != checksum)\n        {\n            final UnsafeBuffer headerBuffer = this.headerAccessBuffer;\n            final int recordingLength = headerBuffer.getInt(\n                recordingDescriptorOffset + RecordingDescriptorHeaderEncoder.lengthEncodingOffset(), BYTE_ORDER);\n            final int checksumValue = checksum.compute(\n                catalogByteBufferAddress, DESCRIPTOR_HEADER_LENGTH + recordingDescriptorOffset, recordingLength);\n            headerBuffer.putInt(\n                recordingDescriptorOffset + RecordingDescriptorHeaderEncoder.checksumEncodingOffset(),\n                checksumValue,\n                BYTE_ORDER);\n        }\n    }\n\n    int computeRecordingDescriptorChecksum(final int recordingDescriptorOffset, final int recordingLength)\n    {\n        if (null != checksum)\n        {\n            return checksum.compute(\n                catalogByteBufferAddress,\n                DESCRIPTOR_HEADER_LENGTH + recordingDescriptorOffset,\n                recordingLength);\n        }\n\n        return 0;\n    }\n\n    void catalogResized(final long oldCapacity, final long newCapacity)\n    {\n//        System.out.println(\"Catalog capacity changed: \" + oldCapacity + \" bytes => \" + newCapacity + \" bytes\");\n    }\n\n    private static void validateCapacity(final long catalogCapacity)\n    {\n        if (catalogCapacity < MIN_CAPACITY || catalogCapacity > MAX_CATALOG_LENGTH)\n        {\n            throw new IllegalArgumentException(\"Invalid catalog capacity provided: expected value >= \" +\n                MIN_CAPACITY + \", got \" + catalogCapacity);\n        }\n    }\n\n    private void initBuffers(final MappedByteBuffer catalogMappedByteBuffer)\n    {\n        catalogByteBuffer = catalogMappedByteBuffer;\n        catalogByteBuffer.order(BYTE_ORDER);\n        catalogBuffer = new UnsafeBuffer(catalogByteBuffer);\n        catalogByteBufferAddress = catalogBuffer.addressOffset();\n        fieldAccessBuffer = new UnsafeBuffer(catalogByteBuffer);\n        headerAccessBuffer = new UnsafeBuffer(catalogByteBuffer);\n    }\n\n    private void buildIndex(final boolean writable)\n    {\n        final int endOffset = (int)capacity;\n        int offset = firstRecordingDescriptorOffset;\n        long recordingId = -1;\n        while (offset < endOffset)\n        {\n            final int frameLength = wrapDescriptorAtOffset(catalogBuffer, offset);\n            if (frameLength < 0)\n            {\n                break;\n            }\n\n            recordingId = recordingId(catalogBuffer);\n            if (isValidDescriptor(catalogBuffer))\n            {\n                catalogIndex.add(recordingId, offset);\n            }\n\n            offset += frameLength;\n        }\n\n        nextRecordingDescriptorOffset = offset;\n\n        if (0 == nextRecordingId)\n        {\n            nextRecordingId = recordingId + 1;\n        }\n        else if (writable && nextRecordingId < recordingId + 1)\n        {\n            throw new ArchiveException(\"invalid nextRecordingId: expected value greater or equal to \" +\n                (recordingId + 1) + \", was \" + nextRecordingId);\n        }\n    }\n\n    private void invokeEntryProcessor(final int recordingDescriptorOffset, final CatalogEntryProcessor consumer)\n    {\n        descriptorHeaderDecoder.wrap(\n            catalogBuffer, 0, DESCRIPTOR_HEADER_LENGTH, RecordingDescriptorHeaderDecoder.SCHEMA_VERSION);\n\n        descriptorHeaderEncoder.wrap(catalogBuffer, 0);\n\n        descriptorDecoder.wrap(\n            catalogBuffer,\n            DESCRIPTOR_HEADER_LENGTH,\n            RecordingDescriptorDecoder.BLOCK_LENGTH,\n            RecordingDescriptorDecoder.SCHEMA_VERSION);\n\n        descriptorEncoder.wrap(catalogBuffer, DESCRIPTOR_HEADER_LENGTH);\n\n        consumer.accept(\n            recordingDescriptorOffset,\n            descriptorHeaderEncoder,\n            descriptorHeaderDecoder,\n            descriptorEncoder,\n            descriptorDecoder);\n    }\n\n    private int recordingDescriptorFrameLength(\n        final String strippedChannel, final String originalChannel, final String sourceIdentity)\n    {\n        final int recordingDescriptorLength =\n            7 * SIZE_OF_LONG +\n            6 * SIZE_OF_INT +\n            SIZE_OF_INT + strippedChannel.length() +\n            SIZE_OF_INT + originalChannel.length() +\n            SIZE_OF_INT + sourceIdentity.length();\n        return align(DESCRIPTOR_HEADER_LENGTH + recordingDescriptorLength, alignment);\n    }\n\n    private void shiftDataToTheRight(\n        final int recordingOffset, final int oldFrameLength, final int newFrameLength, final int endOfLastRecording)\n    {\n        fieldAccessBuffer.putBytes(\n            recordingOffset + newFrameLength,\n            fieldAccessBuffer,\n            recordingOffset + oldFrameLength,\n            endOfLastRecording - (recordingOffset + oldFrameLength));\n    }\n\n    private static void fixupIndexForShifterRecordings(\n        final long[] index, final int lastPosition, final long recordingId, final int shiftBytes)\n    {\n        boolean updateOffset = false;\n        for (int i = 0; i <= lastPosition; i += 2)\n        {\n            if (updateOffset)\n            {\n                index[i + 1] += shiftBytes;\n            }\n            else if (recordingId == index[i])\n            {\n                updateOffset = true;\n            }\n        }\n    }\n\n    /**\n     * On catalog load we verify entries are in coherent state and attempt to recover entries data where untimely\n     * termination of recording has resulted in an unaccounted for stopPosition/stopTimestamp. This operation may be\n     * expensive for large catalogs.\n     *\n     * @param fixOnRefresh set if the catalog should have its entries fixed.\n     * @param checksum     to validate the last fragment upon the page straddle.\n     * @param buffer       to recover the stop position.\n     */\n    @SuppressWarnings(\"checkstyle:indentation\")\n    private void refreshCatalog(final boolean fixOnRefresh, final Checksum checksum, final UnsafeBuffer buffer)\n    {\n        if (fixOnRefresh)\n        {\n            final UnsafeBuffer tmpBuffer = null != buffer ?\n                buffer : new UnsafeBuffer(ByteBuffer.allocateDirect(FILE_IO_MAX_LENGTH_DEFAULT));\n            final Long2ObjectHashMap<List<String>> segmentFiles = indexSegmentFiles(archiveDir);\n            forEach((recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                refreshAndFixDescriptor(\n                    headerDecoder, descriptorEncoder, descriptorDecoder, segmentFiles, checksum, tmpBuffer));\n        }\n    }\n\n    private void refreshAndFixDescriptor(\n        final RecordingDescriptorHeaderDecoder headerDecoder,\n        final RecordingDescriptorEncoder encoder,\n        final RecordingDescriptorDecoder decoder,\n        final Long2ObjectHashMap<List<String>> segmentFilesByRecordingId,\n        final Checksum checksum,\n        final UnsafeBuffer buffer)\n    {\n        final long recordingId = decoder.recordingId();\n        if (VALID == headerDecoder.state() && NULL_POSITION == decoder.stopPosition())\n        {\n            final List<String> segmentFiles = segmentFilesByRecordingId.getOrDefault(recordingId, emptyList());\n            final String maxSegmentFile = findSegmentFileWithHighestPosition(segmentFiles);\n\n            encoder.stopPosition(computeStopPosition(\n                archiveDir,\n                maxSegmentFile,\n                decoder.startPosition(),\n                decoder.termBufferLength(),\n                decoder.segmentFileLength(),\n                checksum,\n                buffer,\n                (segmentFile) ->\n                {\n                    throw new ArchiveException(\n                        \"Found potentially incomplete last fragment straddling page boundary in file: \" +\n                        segmentFile.getAbsolutePath() +\n                        \"\\nRun `ArchiveTool verify` for corrective action!\");\n                }));\n\n            encoder.stopTimestamp(epochClock.time());\n        }\n    }\n\n    private void forceWrites(final FileChannel channel)\n    {\n        fsync(channel, forceWrites, forceMetadata);\n    }\n\n    private void fsync(final FileChannel channel, final boolean forceWrites, final boolean forceMetadata)\n    {\n        if (null != channel && forceWrites)\n        {\n            try\n            {\n                channel.force(forceMetadata);\n            }\n            catch (final Exception ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n        }\n    }\n\n    static ArrayList<String> listSegmentFiles(final File archiveDir, final long recordingId)\n    {\n        final ArrayList<String> segmentFiles = new ArrayList<>();\n        final String[] files = archiveDir.list();\n\n        if (null != files)\n        {\n            final String prefix = recordingId + \"-\";\n\n            for (final String file : files)\n            {\n                if (file.startsWith(prefix) && file.endsWith(RECORDING_SEGMENT_SUFFIX))\n                {\n                    segmentFiles.add(file);\n                }\n            }\n        }\n\n        return segmentFiles;\n    }\n\n    static Long2ObjectHashMap<List<String>> indexSegmentFiles(final File archiveDir)\n    {\n        final Long2ObjectHashMap<List<String>> index = new Long2ObjectHashMap<>();\n        final String[] files = archiveDir.list();\n\n        if (null != files)\n        {\n            for (final String file : files)\n            {\n                if (file.endsWith(RECORDING_SEGMENT_SUFFIX))\n                {\n                    try\n                    {\n                        final long recordingId = parseSegmentFileRecordingId(file);\n                        index.computeIfAbsent(recordingId, r -> new ArrayList<>()).add(file);\n                    }\n                    catch (final InvalidRecordingNameException ignore)\n                    {\n                        // Just skip over invalid files.\n                    }\n                }\n            }\n        }\n\n        return index;\n    }\n\n    static String findSegmentFileWithHighestPosition(final List<String> segmentFiles)\n    {\n        long maxSegmentPosition = NULL_POSITION;\n        String maxFileName = null;\n\n        for (final String filename : segmentFiles)\n        {\n            final long filePosition = parseSegmentFilePosition(filename);\n            if (filePosition < 0)\n            {\n                throw new ArchiveException(\"negative position encoded in the file name: \" + filename);\n            }\n\n            if (filePosition > maxSegmentPosition)\n            {\n                maxSegmentPosition = filePosition;\n                maxFileName = filename;\n            }\n        }\n\n        return maxFileName;\n    }\n\n    static long parseSegmentFilePosition(final String filename)\n    {\n        final int dashOffset = filename.indexOf('-');\n        if (-1 == dashOffset)\n        {\n            throw new ArchiveException(\"invalid filename format: \" + filename);\n        }\n\n        final int positionOffset = dashOffset + 1;\n        final int positionLength = filename.length() - positionOffset - RECORDING_SEGMENT_SUFFIX.length();\n        if (0 >= positionLength)\n        {\n            throw new ArchiveException(\"no position encoded in the segment file: \" + filename);\n        }\n\n        return parseLongAscii(filename, positionOffset, positionLength);\n    }\n\n    static long parseSegmentFileRecordingId(final String filename)\n    {\n        final int dashOffset = filename.indexOf('-');\n        if (-1 == dashOffset || 0 == dashOffset)\n        {\n            throw new InvalidRecordingNameException(\"invalid filename format: \" + filename);\n        }\n\n        return parseLongAscii(filename, 0, dashOffset);\n    }\n\n    static long computeStopPosition(\n        final File archiveDir,\n        final String maxSegmentFile,\n        final long startPosition,\n        final int termLength,\n        final int segmentLength,\n        final Checksum checksum,\n        final UnsafeBuffer buffer,\n        final Predicate<File> truncateOnPageStraddle)\n    {\n        if (null == maxSegmentFile)\n        {\n            return startPosition;\n        }\n        else\n        {\n            final int startTermOffset = (int)(startPosition & (termLength - 1));\n            final long startTermBasePosition = startPosition - startTermOffset;\n            final long segmentFileBasePosition = parseSegmentFilePosition(maxSegmentFile);\n            final int fileOffset = segmentFileBasePosition == startTermBasePosition ? startTermOffset : 0;\n            final int segmentStopOffset = recoverStopOffset(\n                archiveDir, maxSegmentFile, fileOffset, segmentLength, truncateOnPageStraddle, checksum, buffer);\n\n            return max(segmentFileBasePosition + segmentStopOffset, startPosition);\n        }\n    }\n\n    static boolean fragmentStraddlesPageBoundary(final int fragmentOffset, final int fragmentLength)\n    {\n        return (fragmentOffset / PAGE_SIZE) != ((fragmentOffset + (fragmentLength - 1)) / PAGE_SIZE);\n    }\n\n    private void unmapAndCloseChannel()\n    {\n        final MappedByteBuffer buffer = this.catalogByteBuffer;\n        BufferUtil.free(buffer);\n        this.catalogByteBuffer = null;\n        fsync(catalogChannel, true, true);\n        CloseHelper.close(catalogChannel);\n    }\n\n    private static int recoverStopOffset(\n        final File archiveDir,\n        final String segmentFile,\n        final int offset,\n        final int segmentFileLength,\n        final Predicate<File> truncateOnPageStraddle,\n        final Checksum checksum,\n        final UnsafeBuffer buffer)\n    {\n        final File file = new File(archiveDir, segmentFile);\n        try (FileChannel segmentFileChannel = FileChannel.open(file.toPath(), READ, WRITE))\n        {\n            final int offsetLimit = (int)min(segmentFileLength, segmentFileChannel.size());\n            final ByteBuffer byteBuffer = buffer.byteBuffer();\n\n            int nextFragmentOffset = offset;\n            int lastFragmentLength = 0;\n            int bufferOffset = 0;\n\n            out:\n            while (nextFragmentOffset < offsetLimit)\n            {\n                final int bytesRead = readNextChunk(segmentFileChannel, byteBuffer, nextFragmentOffset, offsetLimit);\n\n                bufferOffset = 0;\n                while (bufferOffset < bytesRead)\n                {\n                    final int frameLength = frameLength(buffer, bufferOffset);\n                    if (frameLength <= 0)\n                    {\n                        break out;\n                    }\n\n                    lastFragmentLength = align(frameLength, FRAME_ALIGNMENT);\n                    nextFragmentOffset += lastFragmentLength;\n                    bufferOffset += lastFragmentLength;\n                }\n            }\n\n            final int lastFragmentOffset = nextFragmentOffset - lastFragmentLength;\n            if (fragmentStraddlesPageBoundary(lastFragmentOffset, lastFragmentLength) &&\n                !isValidFragment(buffer, bufferOffset - lastFragmentLength, lastFragmentLength, checksum) &&\n                truncateOnPageStraddle.test(file))\n            {\n                final int singleByte = 1;\n                segmentFileChannel.truncate(lastFragmentOffset);\n                byteBuffer.put(0, (byte)0).limit(singleByte).position(0);\n                if (singleByte != segmentFileChannel.write(byteBuffer, segmentFileLength - 1))\n                {\n                    throw new IllegalStateException(\"Failed to write single byte to set segment file length\");\n                }\n\n                return lastFragmentOffset;\n            }\n            else\n            {\n                return nextFragmentOffset;\n            }\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n            return Aeron.NULL_VALUE;\n        }\n    }\n\n    private static int readNextChunk(\n        final FileChannel segment, final ByteBuffer byteBuffer, final int offset, final int limit) throws IOException\n    {\n        int position = offset;\n        byteBuffer.clear().limit(min(byteBuffer.capacity(), limit - position));\n        do\n        {\n            final int bytesRead = segment.read(byteBuffer, position);\n            if (bytesRead < 0)\n            {\n                break;\n            }\n            position += bytesRead;\n        }\n        while (byteBuffer.remaining() > 0);\n\n        return position - offset;\n    }\n\n    private static boolean isValidFragment(\n        final UnsafeBuffer buffer, final int fragmentOffset, final int alignedFragmentLength, final Checksum checksum)\n    {\n        return null != checksum && hasValidChecksum(buffer, fragmentOffset, alignedFragmentLength, checksum) ||\n            hasDataInAllPagesAfterStraddle(buffer, fragmentOffset, alignedFragmentLength);\n    }\n\n    private static boolean hasValidChecksum(\n        final UnsafeBuffer buffer, final int fragmentOffset, final int alignedFragmentLength, final Checksum checksum)\n    {\n        final int computedChecksum = checksum.compute(\n            buffer.addressOffset(),\n            fragmentOffset + HEADER_LENGTH,\n            alignedFragmentLength - HEADER_LENGTH);\n        final int recordedChecksum = frameSessionId(buffer, fragmentOffset);\n\n        return recordedChecksum == computedChecksum;\n    }\n\n    private static boolean hasDataInAllPagesAfterStraddle(\n        final UnsafeBuffer buffer, final int fragmentOffset, final int alignedFragmentLength)\n    {\n        int straddleOffset = (fragmentOffset / PAGE_SIZE + 1) * PAGE_SIZE;\n        final int endOffset = fragmentOffset + alignedFragmentLength;\n        while (straddleOffset < endOffset)\n        {\n            if (isEmptyPage(buffer, straddleOffset, endOffset))\n            {\n                return false;\n            }\n            straddleOffset += PAGE_SIZE;\n        }\n\n        return true;\n    }\n\n    private static boolean isEmptyPage(final UnsafeBuffer buffer, final int pageStart, final int endOffset)\n    {\n        for (int i = pageStart, pageEnd = min(pageStart + PAGE_SIZE, endOffset); i < pageEnd; i += SIZE_OF_LONG)\n        {\n            if (0L != buffer.getLong(i))\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    public String toString()\n    {\n        return \"Catalog{\" +\n            \"forceWrites=\" + forceWrites +\n            \", forceMetadata=\" + forceMetadata +\n            \", isClosed=\" + isClosed +\n            \", catalogFile=\" + catalogFile +\n            \", archiveDir=\" + archiveDir +\n            \", epochClock=\" + epochClock +\n            \", checksum=\" + checksum +\n            \", alignment=\" + alignment +\n            \", firstRecordingDescriptorOffset=\" + firstRecordingDescriptorOffset +\n            \", catalogChannel=\" + catalogChannel +\n            \", capacity=\" + capacity +\n            \", nextRecordingId=\" + nextRecordingId +\n            \", nextRecordingDescriptorOffset=\" + nextRecordingDescriptorOffset +\n            '}';\n    }\n\n    static class InvalidRecordingNameException extends ArchiveException\n    {\n        private static final long serialVersionUID = -2374545383916725365L;\n\n        InvalidRecordingNameException(final String message)\n        {\n            super(message);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/CatalogIndex.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport static java.util.Arrays.copyOf;\n\n/**\n * {@code CatalogIndex} maps recording id to its position in the catalog file.\n */\nfinal class CatalogIndex\n{\n    static final int DEFAULT_INDEX_SIZE = 10;\n\n    static final long NULL_VALUE = -1;\n\n    private long[] index;\n    private int count;\n\n    CatalogIndex()\n    {\n        index = new long[DEFAULT_INDEX_SIZE << 1];\n    }\n\n    /**\n     * Add mapping between recording id and its file offset to the index.\n     *\n     * @param recordingId               to add.\n     * @param recordingDescriptorOffset for the given id.\n     * @throws IllegalArgumentException if {@code recordingId < 0 || recordingDescriptorOffset < 0}.\n     * @throws IllegalArgumentException if {@code recordingId} is less than or equal to the last recording id added,\n     *                                  i.e. {@code recordingId} must always increase.\n     */\n    void add(final long recordingId, final long recordingDescriptorOffset)\n    {\n        ensurePositive(recordingId, \"recordingId\");\n        ensurePositive(recordingDescriptorOffset, \"recordingDescriptorOffset\");\n\n        final int nextPosition = count << 1;\n        long[] index = this.index;\n        if (nextPosition > 0)\n        {\n            if (recordingId <= index[nextPosition - 2])\n            {\n                throw new IllegalArgumentException(\"recordingId \" + recordingId +\n                    \" is less than or equal to the last recordingId \" + index[nextPosition - 2]);\n            }\n            if (nextPosition == index.length)\n            {\n                index = expand(index);\n                this.index = index;\n            }\n        }\n        index[nextPosition] = recordingId;\n        index[nextPosition + 1] = recordingDescriptorOffset;\n\n        count++;\n    }\n\n    /**\n     * Remove given recording id from the index.\n     *\n     * @param recordingId to remove.\n     * @return recording file offset or {@link #NULL_VALUE} if not found.\n     */\n    long remove(final long recordingId)\n    {\n        ensurePositive(recordingId, \"recordingId\");\n\n        final long[] index = this.index;\n        final int lastPosition = lastPosition();\n        final int position = find(index, recordingId, lastPosition);\n        if (position < 0)\n        {\n            return NULL_VALUE;\n        }\n\n        final long recordingDescriptorOffset = index[position + 1];\n\n        count--;\n\n        // Shift data to the left\n        for (int i = position; i < lastPosition; i += 2)\n        {\n            index[i] = index[i + 2];\n            index[i + 1] = index[i + 3];\n        }\n        // Reset last copied element\n        index[lastPosition] = 0;\n        index[lastPosition + 1] = 0;\n\n        return recordingDescriptorOffset;\n    }\n\n    /**\n     * Get recording file offset by its id.\n     *\n     * @param recordingId to lookup.\n     * @return recording file offset or {@link #NULL_VALUE} if not found.\n     */\n    long recordingOffset(final long recordingId)\n    {\n        ensurePositive(recordingId, \"recordingId\");\n\n        final long[] index = this.index;\n        final int lastPosition = lastPosition();\n        final int position = find(index, recordingId, lastPosition);\n        if (position < 0)\n        {\n            return NULL_VALUE;\n        }\n\n        return index[position + 1];\n    }\n\n    /**\n     * Returns size of the index.\n     *\n     * @return number of the entries in the index.\n     */\n    int size()\n    {\n        return count;\n    }\n\n    /**\n     * Position of the last inserted entry.\n     *\n     * @return position of the last entry or negative value if index is empty.\n     */\n    int lastPosition()\n    {\n        return (count << 1) - 2;\n    }\n\n    /**\n     * Index array.\n     *\n     * @return index array.\n     */\n    long[] index()\n    {\n        return index;\n    }\n\n    static int find(final long[] index, final long recordingId, final int lastPosition)\n    {\n        if (lastPosition > 0 && recordingId >= index[0] && recordingId <= index[lastPosition])\n        {\n            int position = (int)((recordingId - index[0]) * lastPosition / (index[lastPosition] - index[0]));\n            position = 0 == (position & 1) ? position : position + 1;\n\n            if (recordingId == index[position])\n            {\n                return position;\n            }\n            else if (recordingId > index[position])\n            {\n                for (int i = position + 2; i <= lastPosition; i += 2)\n                {\n                    final long id = index[i];\n                    if (recordingId == id)\n                    {\n                        return i;\n                    }\n                    else if (id > recordingId)\n                    {\n                        break;\n                    }\n                }\n            }\n            else\n            {\n                for (int i = position - 2; i >= 0; i -= 2)\n                {\n                    final long id = index[i];\n                    if (recordingId == id)\n                    {\n                        return i;\n                    }\n                    else if (id < recordingId)\n                    {\n                        break;\n                    }\n                }\n            }\n        }\n        else if (0 == lastPosition && recordingId == index[0])\n        {\n            return 0;\n        }\n\n        return -1;\n    }\n\n    private static long[] expand(final long[] index)\n    {\n        final int length = index.length;\n        final int entries = length >> 1;\n        final int newLength = (entries + (entries >> 1)) << 1;\n\n        return copyOf(index, newLength);\n    }\n\n    private static void ensurePositive(final long value, final String name)\n    {\n        if (value < 0L)\n        {\n            throw new IllegalArgumentException(name + \" cannot be negative: value=\" + value);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/CatalogTool.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\n/**\n * Tool for inspecting and performing administrative tasks on an {@link Archive} and its contents which is described in\n * the {@link Catalog}. The tool delegates to {@link ArchiveTool}.\n */\npublic final class CatalogTool\n{\n    private CatalogTool()\n    {\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        ArchiveTool.main(args);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/CatalogView.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport java.io.File;\n\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.RecordingDescriptorConsumer;\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorEncoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderEncoder;\n\n/**\n * Read only view of a {@link Catalog} which can be used for listing entries.\n */\npublic final class CatalogView\n{\n    private CatalogView()\n    {\n    }\n\n    /**\n     * List all recording descriptors in a {@link Catalog}.\n     *\n     * @param archiveDir the directory containing the {@link Catalog}.\n     * @param consumer   to which the descriptor are dispatched.\n     * @return the count of entries listed.\n     */\n    public static int listRecordings(final File archiveDir, final RecordingDescriptorConsumer consumer)\n    {\n        try (Catalog catalog = new Catalog(archiveDir, System::currentTimeMillis))\n        {\n            return catalog.forEach(new RecordingDescriptorConsumerAdapter(consumer));\n        }\n    }\n\n    /**\n     * List a recording descriptor for a single recording id.\n     * <p>\n     * If the recording id is not found then nothing is returned.\n     *\n     * @param archiveDir  the directory containing the {@link Catalog}.\n     * @param recordingId to view\n     * @param consumer    to which the descriptor are dispatched.\n     * @return the true of a descriptor is found.\n     */\n    public static boolean listRecording(\n        final File archiveDir, final long recordingId, final RecordingDescriptorConsumer consumer)\n    {\n        try (Catalog catalog = new Catalog(archiveDir, System::currentTimeMillis))\n        {\n            return catalog.forEntry(recordingId, new RecordingDescriptorConsumerAdapter(consumer));\n        }\n    }\n\n    static class RecordingDescriptorConsumerAdapter implements Catalog.CatalogEntryProcessor\n    {\n        private final RecordingDescriptorConsumer consumer;\n\n        RecordingDescriptorConsumerAdapter(final RecordingDescriptorConsumer consumer)\n        {\n            this.consumer = consumer;\n        }\n\n        public void accept(\n            final int recordingDescriptorOffset,\n            final RecordingDescriptorHeaderEncoder headerEncoder,\n            final RecordingDescriptorHeaderDecoder headerDecoder,\n            final RecordingDescriptorEncoder descriptorEncoder,\n            final RecordingDescriptorDecoder descriptorDecoder)\n        {\n            consumer.onRecordingDescriptor(\n                Aeron.NULL_VALUE,\n                Aeron.NULL_VALUE,\n                descriptorDecoder.recordingId(),\n                descriptorDecoder.startTimestamp(),\n                descriptorDecoder.stopTimestamp(),\n                descriptorDecoder.startPosition(),\n                descriptorDecoder.stopPosition(),\n                descriptorDecoder.initialTermId(),\n                descriptorDecoder.segmentFileLength(),\n                descriptorDecoder.termBufferLength(),\n                descriptorDecoder.mtuLength(),\n                descriptorDecoder.sessionId(),\n                descriptorDecoder.streamId(),\n                descriptorDecoder.strippedChannel(),\n                descriptorDecoder.originalChannel(),\n                descriptorDecoder.sourceIdentity());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ControlRequestDecoders.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.codecs.*;\n\nclass ControlRequestDecoders\n{\n    final MessageHeaderDecoder header = new MessageHeaderDecoder();\n    final ConnectRequestDecoder connectRequest = new ConnectRequestDecoder();\n    final CloseSessionRequestDecoder closeSessionRequest = new CloseSessionRequestDecoder();\n    final StartRecordingRequestDecoder startRecordingRequest = new StartRecordingRequestDecoder();\n    final StartRecordingRequest2Decoder startRecordingRequest2 = new StartRecordingRequest2Decoder();\n    final StopRecordingRequestDecoder stopRecordingRequest = new StopRecordingRequestDecoder();\n    final ReplayRequestDecoder replayRequest = new ReplayRequestDecoder();\n    final StopReplayRequestDecoder stopReplayRequest = new StopReplayRequestDecoder();\n    final ListRecordingsRequestDecoder listRecordingsRequest = new ListRecordingsRequestDecoder();\n    final ListRecordingsForUriRequestDecoder listRecordingsForUriRequest = new ListRecordingsForUriRequestDecoder();\n    final ListRecordingRequestDecoder listRecordingRequest = new ListRecordingRequestDecoder();\n    final ExtendRecordingRequestDecoder extendRecordingRequest = new ExtendRecordingRequestDecoder();\n    final ExtendRecordingRequest2Decoder extendRecordingRequest2 = new ExtendRecordingRequest2Decoder();\n    final RecordingPositionRequestDecoder recordingPositionRequest = new RecordingPositionRequestDecoder();\n    final TruncateRecordingRequestDecoder truncateRecordingRequest = new TruncateRecordingRequestDecoder();\n    final PurgeRecordingRequestDecoder purgeRecordingRequest = new PurgeRecordingRequestDecoder();\n    final StopRecordingSubscriptionRequestDecoder stopRecordingSubscriptionRequest =\n        new StopRecordingSubscriptionRequestDecoder();\n    final StopPositionRequestDecoder stopPositionRequest = new StopPositionRequestDecoder();\n    final FindLastMatchingRecordingRequestDecoder findLastMatchingRecordingRequest =\n        new FindLastMatchingRecordingRequestDecoder();\n    final ListRecordingSubscriptionsRequestDecoder listRecordingSubscriptionsRequest =\n        new ListRecordingSubscriptionsRequestDecoder();\n    final StopRecordingByIdentityRequestDecoder stopRecordingByIdentityRequest =\n        new StopRecordingByIdentityRequestDecoder();\n    final BoundedReplayRequestDecoder boundedReplayRequest = new BoundedReplayRequestDecoder();\n    final StopAllReplaysRequestDecoder stopAllReplaysRequest = new StopAllReplaysRequestDecoder();\n    final ReplicateRequestDecoder replicateRequest = new ReplicateRequestDecoder();\n    final ReplicateRequest2Decoder replicateRequest2 = new ReplicateRequest2Decoder();\n    final StopReplicationRequestDecoder stopReplicationRequest = new StopReplicationRequestDecoder();\n    final StartPositionRequestDecoder startPositionRequest = new StartPositionRequestDecoder();\n    final DetachSegmentsRequestDecoder detachSegmentsRequest = new DetachSegmentsRequestDecoder();\n    final DeleteDetachedSegmentsRequestDecoder deleteDetachedSegmentsRequest =\n        new DeleteDetachedSegmentsRequestDecoder();\n    final PurgeSegmentsRequestDecoder purgeSegmentsRequest = new PurgeSegmentsRequestDecoder();\n    final MaxRecordedPositionRequestDecoder maxRecordedPositionRequest =\n        new MaxRecordedPositionRequestDecoder();\n    final AttachSegmentsRequestDecoder attachSegmentsRequest = new AttachSegmentsRequestDecoder();\n    final MigrateSegmentsRequestDecoder migrateSegmentsRequest = new MigrateSegmentsRequestDecoder();\n    final AuthConnectRequestDecoder authConnectRequest = new AuthConnectRequestDecoder();\n    final ChallengeResponseDecoder challengeResponse = new ChallengeResponseDecoder();\n    final KeepAliveRequestDecoder keepAliveRequest = new KeepAliveRequestDecoder();\n    final TaggedReplicateRequestDecoder taggedReplicateRequest = new TaggedReplicateRequestDecoder();\n    final ArchiveIdRequestDecoder archiveIdRequestDecoder = new ArchiveIdRequestDecoder();\n    final ReplayTokenRequestDecoder replayTokenRequestDecoder = new ReplayTokenRequestDecoder();\n    final UpdateChannelRequestDecoder updateChannelRequestDecoder = new UpdateChannelRequestDecoder();\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ControlResponseProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveEvent;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.archive.codecs.RecordingDescriptorEncoder.recordingIdEncodingOffset;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\nclass ControlResponseProxy\n{\n    private static final int SEND_ATTEMPTS = 3;\n    private static final int MESSAGE_HEADER_LENGTH = MessageHeaderEncoder.ENCODED_LENGTH;\n    private static final int DESCRIPTOR_PREFIX_LENGTH = MESSAGE_HEADER_LENGTH + 2 * SIZE_OF_LONG;\n    private static final int DESCRIPTOR_CONTENT_OFFSET =\n        RecordingDescriptorHeaderDecoder.BLOCK_LENGTH + recordingIdEncodingOffset();\n\n    private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(1024);\n    private final BufferClaim bufferClaim = new BufferClaim();\n\n    private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n    private final ControlResponseEncoder responseEncoder = new ControlResponseEncoder();\n    private final RecordingDescriptorEncoder recordingDescriptorEncoder = new RecordingDescriptorEncoder();\n    private final RecordingSubscriptionDescriptorEncoder recordingSubscriptionDescriptorEncoder =\n        new RecordingSubscriptionDescriptorEncoder();\n    private final RecordingSignalEventEncoder recordingSignalEventEncoder = new RecordingSignalEventEncoder();\n    private final ChallengeEncoder challengeEncoder = new ChallengeEncoder();\n    private final PingEncoder pingEncoder = new PingEncoder();\n\n    boolean sendDescriptor(\n        final long controlSessionId,\n        final long correlationId,\n        final UnsafeBuffer descriptorBuffer,\n        final ControlSession session)\n    {\n        final int messageLength = Catalog.descriptorLength(descriptorBuffer) + MESSAGE_HEADER_LENGTH;\n        final int contentLength = messageLength - recordingIdEncodingOffset() - MESSAGE_HEADER_LENGTH;\n\n        recordingDescriptorEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId);\n\n        int attempts = SEND_ATTEMPTS;\n        final Publication publication = session.controlPublication();\n        do\n        {\n            final long position = publication.offer(\n                buffer,\n                0,\n                DESCRIPTOR_PREFIX_LENGTH,\n                descriptorBuffer,\n                DESCRIPTOR_CONTENT_OFFSET,\n                contentLength);\n            if (position > 0)\n            {\n                return true;\n            }\n\n            checkResult(session, position);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean sendSubscriptionDescriptor(\n        final long controlSessionId,\n        final long correlationId,\n        final Subscription subscription,\n        final ControlSession session)\n    {\n        recordingSubscriptionDescriptorEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .subscriptionId(subscription.registrationId())\n            .streamId(subscription.streamId())\n            .strippedChannel(subscription.channel());\n\n        final int length = MESSAGE_HEADER_LENGTH + recordingSubscriptionDescriptorEncoder.encodedLength();\n\n        return send(session, buffer, length);\n    }\n\n    boolean sendResponse(\n        final long controlSessionId,\n        final long correlationId,\n        final long relevantId,\n        final ControlResponseCode code,\n        final String errorMessage,\n        final ControlSession session)\n    {\n        responseEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .relevantId(relevantId)\n            .code(code)\n            .version(AeronArchive.Configuration.PROTOCOL_SEMANTIC_VERSION)\n            .errorMessage(errorMessage);\n\n        final int length = MESSAGE_HEADER_LENGTH + responseEncoder.encodedLength();\n        final int offset = 0;\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = session.controlPublication().offer(buffer, offset, length);\n            if (position > 0)\n            {\n                logSendResponse(buffer, offset, length);\n                return true;\n            }\n\n            checkResult(session, position);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean sendChallenge(\n        final long controlSessionId,\n        final long correlationId,\n        final byte[] encodedChallenge,\n        final ControlSession session)\n    {\n        challengeEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .putEncodedChallenge(encodedChallenge, 0, encodedChallenge.length);\n\n        return send(session, buffer, MESSAGE_HEADER_LENGTH + challengeEncoder.encodedLength());\n    }\n\n    boolean sendSignal(\n        final long controlSessionId,\n        final long correlationId,\n        final long recordingId,\n        final long subscriptionId,\n        final long position,\n        final RecordingSignal recordingSignal,\n        final Publication controlPublication)\n    {\n        final int length = MESSAGE_HEADER_LENGTH + RecordingSignalEventEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long result = controlPublication.tryClaim(length, bufferClaim);\n            if (result > 0)\n            {\n                final MutableDirectBuffer buffer = bufferClaim.buffer();\n                final int offset = bufferClaim.offset();\n\n                recordingSignalEventEncoder\n                    .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n                    .controlSessionId(controlSessionId)\n                    .correlationId(correlationId)\n                    .recordingId(recordingId)\n                    .subscriptionId(subscriptionId)\n                    .position(position)\n                    .signal(recordingSignal);\n\n                bufferClaim.commit();\n\n                logSendSignal(buffer, offset, length);\n                return true;\n            }\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    public boolean sendPing(final long controlSessionId, final Publication controlPublication)\n    {\n        final int length = MESSAGE_HEADER_LENGTH + PingEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long result = controlPublication.tryClaim(length, bufferClaim);\n            if (result > 0)\n            {\n                final MutableDirectBuffer buffer = bufferClaim.buffer();\n                final int offset = bufferClaim.offset();\n                pingEncoder.wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n                    .controlSessionId(controlSessionId);\n                bufferClaim.commit();\n                return true;\n            }\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    private boolean send(final ControlSession session, final DirectBuffer buffer, final int length)\n    {\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = session.controlPublication().offer(buffer, 0, length);\n            if (position > 0)\n            {\n                return true;\n            }\n\n            checkResult(session, position);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    private static void checkResult(final ControlSession session, final long result)\n    {\n        if (result == Publication.NOT_CONNECTED)\n        {\n            session.abort(ControlSession.RESPONSE_NOT_CONNECTED_MSG);\n            throw new ArchiveEvent(ControlSession.RESPONSE_NOT_CONNECTED_MSG + \": \" + session);\n        }\n\n        if (result == Publication.CLOSED)\n        {\n            session.abort(\"control response publication is closed\");\n            throw new ArchiveEvent(\"response publication is closed: \" + session, AeronException.Category.ERROR);\n        }\n\n        if (result == Publication.MAX_POSITION_EXCEEDED)\n        {\n            session.abort(\"control response publication is at max position\");\n            throw new ArchiveEvent(\n                \"response publication is at max position: \" + session, AeronException.Category.ERROR);\n        }\n    }\n\n    private void logSendResponse(final DirectBuffer buffer, final int offset, final int length)\n    {\n    }\n\n    private void logSendSignal(final DirectBuffer buffer, final int offset, final int length)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ControlSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.ArchiveEvent;\nimport io.aeron.archive.codecs.ControlResponseCode;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.security.Authenticator;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.ManyToOneConcurrentLinkedQueue;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.util.ArrayDeque;\nimport java.util.function.BooleanSupplier;\n\nimport static io.aeron.archive.client.ArchiveException.AUTHENTICATION_REJECTED;\nimport static io.aeron.archive.codecs.ControlResponseCode.ERROR;\nimport static io.aeron.archive.codecs.ControlResponseCode.OK;\nimport static io.aeron.archive.codecs.ControlResponseCode.RECORDING_UNKNOWN;\nimport static io.aeron.archive.codecs.ControlResponseCode.SUBSCRIPTION_UNKNOWN;\n\n/**\n * Control sessions are interacted with from the {@link ArchiveConductor}. The interaction may result in pending\n * send actions being queued for execution by the {@link ArchiveConductor}.\n */\nfinal class ControlSession implements Session\n{\n    static final String SESSION_CLOSED_MSG = \"session closed\";\n    static final String RESPONSE_NOT_CONNECTED_MSG = \"control response publication is not connected\";\n    static final String REQUEST_IMAGE_NOT_AVAILABLE_MSG = \"control request publication image unavailable\";\n    private static final long RESEND_INTERVAL_MS = 200L;\n    private static final String SESSION_REJECTED_MSG = \"authentication rejected\";\n    private final Thread conductorThread;\n    private long sessionLivenessCheckDeadlineMs;\n    private String abortReason;\n\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        INIT, CONNECTING, CONNECTED, CHALLENGED, AUTHENTICATED, ACTIVE, REJECTED, DONE\n    }\n\n    private final long controlSessionId;\n    private final long connectTimeoutMs;\n    private final long sessionLivenessCheckIntervalMs;\n    private final long controlPublicationRegistrationId;\n    private final long sessionCounterRegistrationId;\n    private final Aeron aeron;\n    private final ArchiveConductor conductor;\n    private final CachedEpochClock cachedEpochClock;\n    private final ControlResponseProxy controlResponseProxy;\n    private final Authenticator authenticator;\n    private final ControlSessionProxy controlSessionProxy;\n    private final ArrayDeque<BooleanSupplier> syncResponseQueue = new ArrayDeque<>(8);\n    private final ManyToOneConcurrentLinkedQueue<BooleanSupplier> asyncResponseQueue =\n        new ManyToOneConcurrentLinkedQueue<>();\n    private final ControlSessionAdapter controlSessionAdapter;\n    private final int controlPublicationStreamId;\n    private final String controlPublicationChannel;\n    private final String invalidVersionMessage;\n    private State state = State.INIT;\n    private long correlationId;\n    private long resendDeadlineMs;\n    private long activityDeadlineMs;\n    private Session activeListing = null;\n    private ExclusivePublication controlPublication;\n    private Counter sessionCounter;\n    private byte[] encodedPrincipal;\n\n    ControlSession(\n        final long controlSessionId,\n        final long correlationId,\n        final long connectTimeoutMs,\n        final long sessionLivenessCheckIntervalMs,\n        final long controlPublicationRegistrationId,\n        final long sessionCounterRegistrationId,\n        final String controlPublicationChannel,\n        final int controlPublicationStreamId,\n        final String invalidVersionMessage,\n        final ControlSessionAdapter controlSessionAdapter,\n        final Aeron aeron,\n        final ArchiveConductor conductor,\n        final CachedEpochClock cachedEpochClock,\n        final ControlResponseProxy controlResponseProxy,\n        final Authenticator authenticator,\n        final ControlSessionProxy controlSessionProxy)\n    {\n        this.controlSessionId = controlSessionId;\n        this.correlationId = correlationId;\n        this.connectTimeoutMs = connectTimeoutMs;\n        this.sessionLivenessCheckIntervalMs = sessionLivenessCheckIntervalMs;\n        this.controlPublicationChannel = controlPublicationChannel;\n        this.controlPublicationStreamId = controlPublicationStreamId;\n        this.invalidVersionMessage = invalidVersionMessage;\n        this.controlSessionAdapter = controlSessionAdapter;\n        this.aeron = aeron;\n        this.controlPublicationRegistrationId = controlPublicationRegistrationId;\n        this.sessionCounterRegistrationId = sessionCounterRegistrationId;\n        this.conductor = conductor;\n        this.cachedEpochClock = cachedEpochClock;\n        this.controlResponseProxy = controlResponseProxy;\n        this.authenticator = authenticator;\n        this.controlSessionProxy = controlSessionProxy;\n        this.activityDeadlineMs = cachedEpochClock.time() + connectTimeoutMs;\n        this.sessionLivenessCheckDeadlineMs = cachedEpochClock.time() + sessionLivenessCheckIntervalMs;\n        conductorThread = Thread.currentThread();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return controlSessionId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n        if (State.DONE != state || null != abortReason && SESSION_CLOSED_MSG.equals(reason))\n        {\n            abortReason = reason;\n            state(State.DONE, reason);\n            if (null != activeListing)\n            {\n                activeListing.abort(reason);\n            }\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (null == controlPublication)\n        {\n            aeron.asyncRemovePublication(controlPublicationRegistrationId);\n        }\n        else\n        {\n            controlPublication.revokeOnClose();\n            CloseHelper.close(conductor.context().countedErrorHandler(), controlPublication);\n        }\n\n        if (null == sessionCounter)\n        {\n            aeron.asyncRemoveCounter(sessionCounterRegistrationId);\n        }\n        else\n        {\n            CloseHelper.close(conductor.context().countedErrorHandler(), sessionCounter);\n        }\n\n        final boolean sessionAborted = null != abortReason &&\n            !SESSION_CLOSED_MSG.equals(abortReason) &&\n            !RESPONSE_NOT_CONNECTED_MSG.equals(abortReason) &&\n            !abortReason.startsWith(REQUEST_IMAGE_NOT_AVAILABLE_MSG);\n        controlSessionAdapter.removeControlSession(controlSessionId, sessionAborted, abortReason);\n\n        if (sessionAborted)\n        {\n            conductor.errorHandler.onError(new ArchiveEvent(\n                \"controlSessionId=\" + controlSessionId + \" (controlResponseStreamId=\" + controlPublicationStreamId +\n                \" controlResponseChannel=\" + controlPublicationChannel + \") terminated: \" + abortReason));\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return state == State.DONE;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n        final long nowMs = cachedEpochClock.time();\n\n        if (hasNoActivity(nowMs))\n        {\n            abort(State.ACTIVE == state ?\n                \"failed to send response for more than connectTimeoutMs=\" + connectTimeoutMs :\n                \"failed to establish initial connection: state=\" + state);\n            workCount++;\n        }\n\n        switch (state)\n        {\n            case INIT:\n                workCount += init(nowMs);\n                break;\n\n            case CONNECTING:\n                workCount += waitForConnection(nowMs);\n                break;\n\n            case CONNECTED:\n                workCount += sendConnectResponse(nowMs);\n                break;\n\n            case CHALLENGED:\n                workCount += waitForChallengeResponse(nowMs);\n                break;\n\n            case AUTHENTICATED:\n                workCount += waitForRequest(nowMs);\n                break;\n\n            case ACTIVE:\n            {\n                workCount += performLivenessCheck(nowMs);\n                workCount += sendResponses(nowMs);\n                break;\n            }\n\n            case REJECTED:\n                workCount += sendReject(nowMs);\n                break;\n\n            case DONE:\n                break;\n        }\n\n        return workCount;\n    }\n\n    byte[] encodedPrincipal()\n    {\n        return encodedPrincipal;\n    }\n\n    long correlationId()\n    {\n        return correlationId;\n    }\n\n    State state()\n    {\n        return state;\n    }\n\n    ArchiveConductor archiveConductor()\n    {\n        return conductor;\n    }\n\n    ExclusivePublication controlPublication()\n    {\n        return controlPublication;\n    }\n\n    boolean hasActiveListing()\n    {\n        return null != activeListing;\n    }\n\n    void activeListing(final Session activeListing)\n    {\n        this.activeListing = activeListing;\n    }\n\n    void onChallengeResponse(final long correlationId, final byte[] encodedCredentials)\n    {\n        if (State.CHALLENGED == state)\n        {\n            this.correlationId = correlationId;\n            authenticator.onChallengeResponse(controlSessionId, encodedCredentials, cachedEpochClock.time());\n        }\n    }\n\n    void onKeepAlive(@SuppressWarnings(\"unused\") final long correlationId)\n    {\n        attemptToActivate();\n    }\n\n    void onStopRecording(final long correlationId, final int streamId, final String channel)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.stopRecording(correlationId, streamId, channel, this);\n        }\n    }\n\n    void onStopRecordingSubscription(final long correlationId, final long subscriptionId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.stopRecordingSubscription(correlationId, subscriptionId, this);\n        }\n    }\n\n    void onStartRecording(\n        final long correlationId,\n        final int streamId,\n        final SourceLocation sourceLocation,\n        final boolean autoStop,\n        final String channel)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.startRecording(correlationId, streamId, sourceLocation, autoStop, channel, this);\n        }\n    }\n\n    void onListRecordingsForUri(\n        final long correlationId,\n        final long fromRecordingId,\n        final int recordCount,\n        final int streamId,\n        final byte[] channelFragment)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.newListRecordingsForUriSession(\n                correlationId,\n                fromRecordingId,\n                recordCount,\n                streamId,\n                channelFragment,\n                this);\n        }\n    }\n\n    void onListRecordings(final long correlationId, final long fromRecordingId, final int recordCount)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.newListRecordingsSession(correlationId, fromRecordingId, recordCount, this);\n        }\n    }\n\n    void onListRecording(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.listRecording(correlationId, recordingId, this);\n        }\n    }\n\n    void onFindLastMatchingRecording(\n        final long correlationId,\n        final long minRecordingId,\n        final int sessionId,\n        final int streamId,\n        final byte[] channelFragment)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.findLastMatchingRecording(\n                correlationId,\n                minRecordingId,\n                sessionId,\n                streamId,\n                channelFragment,\n                this);\n        }\n    }\n\n    void onStartReplay(\n        final long correlationId,\n        final long recordingId,\n        final long position,\n        final long length,\n        final int fileIoMaxLength,\n        final int replayStreamId,\n        final String replayChannel)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.startReplay(\n                correlationId,\n                recordingId,\n                position,\n                length,\n                fileIoMaxLength,\n                replayStreamId,\n                replayChannel,\n                null,\n                this);\n        }\n    }\n\n    void onStartBoundedReplay(\n        final long correlationId,\n        final long recordingId,\n        final long position,\n        final long length,\n        final int limitCounterId,\n        final int fileIoMaxLength,\n        final int replayStreamId,\n        final String replayChannel)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.startBoundedReplay(\n                correlationId,\n                recordingId,\n                position,\n                length,\n                limitCounterId,\n                fileIoMaxLength,\n                replayStreamId,\n                replayChannel,\n                this);\n        }\n    }\n\n    void onStopReplay(final long correlationId, final long replaySessionId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.stopReplay(correlationId, replaySessionId, this);\n        }\n    }\n\n    void onStopAllReplays(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.stopAllReplays(correlationId, recordingId, this);\n        }\n    }\n\n    void onExtendRecording(\n        final long correlationId,\n        final long recordingId,\n        final int streamId,\n        final SourceLocation sourceLocation,\n        final boolean autoStop,\n        final String channel)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.extendRecording(correlationId, recordingId, streamId, sourceLocation, autoStop, channel, this);\n        }\n    }\n\n    void onGetRecordingPosition(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.getRecordingPosition(correlationId, recordingId, this);\n        }\n    }\n\n    void onTruncateRecording(final long correlationId, final long recordingId, final long position)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.truncateRecording(correlationId, recordingId, position, this);\n        }\n    }\n\n    void onPurgeRecording(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.purgeRecording(correlationId, recordingId, this);\n        }\n    }\n\n    void onGetStopPosition(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.getStopPosition(correlationId, recordingId, this);\n        }\n    }\n\n    void onGetMaxRecordedPosition(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.getMaxRecordedPosition(correlationId, recordingId, this);\n        }\n    }\n\n    void onArchiveId(final long correlationId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.archiveId(correlationId, this);\n        }\n    }\n\n    void onListRecordingSubscriptions(\n        final long correlationId,\n        final int pseudoIndex,\n        final int subscriptionCount,\n        final boolean applyStreamId,\n        final int streamId,\n        final String channelFragment)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.listRecordingSubscriptions(\n                correlationId,\n                pseudoIndex,\n                subscriptionCount,\n                applyStreamId,\n                streamId,\n                channelFragment,\n                this);\n        }\n    }\n\n    void onStopRecordingByIdentity(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.stopRecordingByIdentity(correlationId, recordingId, this);\n        }\n    }\n\n    void onReplicate(\n        final long correlationId,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long stopPosition,\n        final long channelTagId,\n        final long subscriptionTagId,\n        final int srcControlStreamId,\n        final int fileIoMaxLength,\n        final int replicationSessionId,\n        final String srcControlChannel,\n        final String liveDestination,\n        final String replicationChannel,\n        final byte[] encodedCredentials,\n        final String srcResponseChannel)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.replicate(\n                correlationId,\n                srcRecordingId,\n                dstRecordingId,\n                stopPosition,\n                channelTagId,\n                subscriptionTagId,\n                srcControlStreamId,\n                srcControlChannel,\n                liveDestination,\n                replicationChannel,\n                fileIoMaxLength,\n                replicationSessionId,\n                encodedCredentials,\n                srcResponseChannel,\n                this);\n        }\n    }\n\n    void onStopReplication(final long correlationId, final long replicationId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.stopReplication(correlationId, replicationId, this);\n        }\n    }\n\n    void onGetStartPosition(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.getStartPosition(correlationId, recordingId, this);\n        }\n    }\n\n    void onDetachSegments(final long correlationId, final long recordingId, final long newStartPosition)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.detachSegments(correlationId, recordingId, newStartPosition, this);\n        }\n    }\n\n    void onDeleteDetachedSegments(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.deleteDetachedSegments(correlationId, recordingId, this);\n        }\n    }\n\n    void onPurgeSegments(final long correlationId, final long recordingId, final long newStartPosition)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.purgeSegments(correlationId, recordingId, newStartPosition, this);\n        }\n    }\n\n    void onAttachSegments(final long correlationId, final long recordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.attachSegments(correlationId, recordingId, this);\n        }\n    }\n\n    void onMigrateSegments(final long correlationId, final long srcRecordingId, final long dstRecordingId)\n    {\n        attemptToActivate();\n        if (State.ACTIVE == state)\n        {\n            conductor.migrateSegments(correlationId, srcRecordingId, dstRecordingId, this);\n        }\n    }\n\n    void sendOkResponse(final long correlationId)\n    {\n        sendResponse(correlationId, 0L, OK, null);\n    }\n\n    void sendOkResponse(final long correlationId, final long relevantId)\n    {\n        sendResponse(correlationId, relevantId, OK, null);\n    }\n\n    void sendErrorResponse(final long correlationId, final String errorMessage)\n    {\n        sendResponse(correlationId, 0L, ERROR, errorMessage);\n    }\n\n    void sendErrorResponse(\n        final long correlationId, final long relevantId, final String errorMessage)\n    {\n        sendResponse(correlationId, relevantId, ERROR, errorMessage);\n    }\n\n    void sendRecordingUnknown(final long correlationId, final long recordingId)\n    {\n        sendResponse(correlationId, recordingId, RECORDING_UNKNOWN, null);\n    }\n\n    void sendSubscriptionUnknown(final long correlationId)\n    {\n        sendResponse(correlationId, 0L, SUBSCRIPTION_UNKNOWN, null);\n    }\n\n    void sendResponse(\n        final long correlationId,\n        final long relevantId,\n        final ControlResponseCode code,\n        final String errorMessage)\n    {\n        assertCalledOnConductorThread();\n\n        if (!syncResponseQueue.isEmpty() ||\n            !controlResponseProxy.sendResponse(controlSessionId, correlationId, relevantId, code, errorMessage, this))\n        {\n            updateActivityDeadline(cachedEpochClock.time());\n            queueResponse(correlationId, relevantId, code, errorMessage);\n        }\n        else\n        {\n            activityDeadlineMs = Aeron.NULL_VALUE;\n        }\n    }\n\n    void asyncSendOkResponse(final long correlationId, final long replaySessionId)\n    {\n        if (!asyncResponseQueue.offer(() -> controlResponseProxy.sendResponse(\n            controlSessionId,\n            correlationId,\n            replaySessionId,\n            OK,\n            null,\n            this)))\n        {\n            throw new IllegalStateException(\"failed to offer async replay response\");\n        }\n    }\n\n    boolean sendDescriptor(final long correlationId, final UnsafeBuffer descriptorBuffer)\n    {\n        assertCalledOnConductorThread();\n        final boolean sent =\n            controlResponseProxy.sendDescriptor(controlSessionId, correlationId, descriptorBuffer, this);\n        if (!sent)\n        {\n            updateActivityDeadline(cachedEpochClock.time());\n        }\n        else\n        {\n            activityDeadlineMs = Aeron.NULL_VALUE;\n        }\n        return sent;\n    }\n\n    boolean sendSubscriptionDescriptor(final long correlationId, final Subscription subscription)\n    {\n        assertCalledOnConductorThread();\n        final boolean sent =\n            controlResponseProxy.sendSubscriptionDescriptor(controlSessionId, correlationId, subscription, this);\n        if (!sent)\n        {\n            updateActivityDeadline(cachedEpochClock.time());\n        }\n        else\n        {\n            activityDeadlineMs = Aeron.NULL_VALUE;\n        }\n        return sent;\n    }\n\n    void sendSignal(\n        final long correlationId,\n        final long recordingId,\n        final long subscriptionId,\n        final long position,\n        final RecordingSignal recordingSignal)\n    {\n        assertCalledOnConductorThread();\n        if (!syncResponseQueue.isEmpty() || !controlResponseProxy.sendSignal(\n            controlSessionId,\n            correlationId,\n            recordingId,\n            subscriptionId,\n            position,\n            recordingSignal,\n            controlPublication))\n        {\n            updateActivityDeadline(cachedEpochClock.time());\n            syncResponseQueue.offer(() -> controlResponseProxy.sendSignal(\n                controlSessionId,\n                correlationId,\n                recordingId,\n                subscriptionId,\n                position,\n                recordingSignal,\n                controlPublication));\n        }\n        else\n        {\n            activityDeadlineMs = Aeron.NULL_VALUE;\n        }\n    }\n\n    int maxPayloadLength()\n    {\n        return controlPublication.maxPayloadLength();\n    }\n\n    void challenged()\n    {\n        state(State.CHALLENGED, \"challenged\");\n    }\n\n    void authenticate(final byte[] encodedPrincipal)\n    {\n        this.encodedPrincipal = encodedPrincipal;\n        activityDeadlineMs = Aeron.NULL_VALUE;\n        state(State.AUTHENTICATED, \"authenticated\");\n    }\n\n    void reject()\n    {\n        state(State.REJECTED, SESSION_REJECTED_MSG);\n    }\n\n    private void assertCalledOnConductorThread()\n    {\n        if (Thread.currentThread() != conductorThread)\n        {\n            throw new IllegalStateException(\n                \"Invalid concurrent access detected: \" + Thread.currentThread() + \" != \" + conductorThread);\n        }\n    }\n\n    private void queueResponse(\n        final long correlationId, final long relevantId, final ControlResponseCode code, final String message)\n    {\n        syncResponseQueue.offer(() -> controlResponseProxy.sendResponse(\n            controlSessionId,\n            correlationId,\n            relevantId,\n            code,\n            message,\n            this));\n    }\n\n    private int init(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (null == controlPublication)\n        {\n\n            final ExclusivePublication publication = aeron.getExclusivePublication(controlPublicationRegistrationId);\n            if (null != publication)\n            {\n                controlPublication = publication;\n                workCount++;\n            }\n        }\n\n        if (null == sessionCounter)\n        {\n            final Counter counter = aeron.getCounter(sessionCounterRegistrationId);\n            if (null != counter)\n            {\n                final Aeron.Context context = aeron.context();\n                AeronCounters.setReferenceId(\n                    context.countersMetaDataBuffer(),\n                    context.countersValuesBuffer(),\n                    counter.id(),\n                    controlPublicationRegistrationId);\n                counter.setRelease(controlSessionId);\n                sessionCounter = counter;\n                workCount++;\n            }\n        }\n\n        if (null != controlPublication && null != sessionCounter)\n        {\n            activityDeadlineMs = nowMs + connectTimeoutMs;\n            state(State.CONNECTING, \"connecting\");\n        }\n\n        return workCount;\n    }\n\n    private int waitForConnection(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (controlPublication.isConnected())\n        {\n            state(State.CONNECTED, \"connected\");\n            workCount += 1;\n        }\n\n        return workCount;\n    }\n\n    private int sendConnectResponse(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (nowMs > resendDeadlineMs)\n        {\n            resendDeadlineMs = nowMs + RESEND_INTERVAL_MS;\n            if (null != invalidVersionMessage)\n            {\n                controlResponseProxy.sendResponse(\n                    controlSessionId,\n                    correlationId,\n                    controlSessionId,\n                    ERROR,\n                    invalidVersionMessage,\n                    this);\n            }\n            else\n            {\n                authenticator.onConnectedSession(controlSessionProxy.controlSession(this), nowMs);\n            }\n\n            workCount += 1;\n        }\n\n        return workCount;\n    }\n\n    private int waitForChallengeResponse(final long nowMs)\n    {\n        authenticator.onChallengedSession(controlSessionProxy.controlSession(this), nowMs);\n        return 1;\n    }\n\n    private int waitForRequest(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (nowMs > resendDeadlineMs)\n        {\n            resendDeadlineMs = nowMs + RESEND_INTERVAL_MS;\n            if (controlResponseProxy.sendResponse(\n                controlSessionId,\n                correlationId,\n                controlSessionId,\n                OK,\n                null,\n                this))\n            {\n                activityDeadlineMs = Aeron.NULL_VALUE;\n                workCount += 1;\n            }\n        }\n\n        return workCount;\n    }\n\n    private int performLivenessCheck(final long nowMs)\n    {\n        if (sessionLivenessCheckDeadlineMs - nowMs < 0)\n        {\n            sessionLivenessCheckDeadlineMs = nowMs + sessionLivenessCheckIntervalMs;\n            if (!controlResponseProxy.sendPing(controlSessionId, controlPublication))\n            {\n                updateActivityDeadline(nowMs);\n            }\n            else\n            {\n                activityDeadlineMs = Aeron.NULL_VALUE;\n            }\n            return 1;\n        }\n        return 0;\n    }\n\n    private int sendResponses(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (!controlPublication.isConnected())\n        {\n            abort(RESPONSE_NOT_CONNECTED_MSG);\n            workCount++;\n        }\n        else\n        {\n            if (!syncResponseQueue.isEmpty())\n            {\n                if (syncResponseQueue.peekFirst().getAsBoolean())\n                {\n                    syncResponseQueue.pollFirst();\n                    activityDeadlineMs = Aeron.NULL_VALUE;\n                    workCount++;\n                }\n            }\n\n            final BooleanSupplier response = asyncResponseQueue.peek();\n            if (null != response)\n            {\n                if (response.getAsBoolean())\n                {\n                    asyncResponseQueue.poll();\n                    activityDeadlineMs = Aeron.NULL_VALUE;\n                    workCount++;\n                }\n                else\n                {\n                    updateActivityDeadline(nowMs);\n                }\n            }\n        }\n\n        return workCount;\n    }\n\n    private int sendReject(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (nowMs > resendDeadlineMs)\n        {\n            resendDeadlineMs = nowMs + RESEND_INTERVAL_MS;\n            controlResponseProxy.sendResponse(\n                controlSessionId, correlationId, AUTHENTICATION_REJECTED, ERROR, SESSION_REJECTED_MSG, this);\n\n            workCount += 1;\n        }\n\n        return workCount;\n    }\n\n    private boolean hasNoActivity(final long nowMs)\n    {\n        return Aeron.NULL_VALUE != activityDeadlineMs && nowMs > activityDeadlineMs;\n    }\n\n    private void updateActivityDeadline(final long nowMs)\n    {\n        if (Aeron.NULL_VALUE == activityDeadlineMs)\n        {\n            activityDeadlineMs = nowMs + connectTimeoutMs;\n        }\n    }\n\n    private void attemptToActivate()\n    {\n        if (State.AUTHENTICATED == state && null == invalidVersionMessage)\n        {\n            state(State.ACTIVE, \"active\");\n        }\n    }\n\n    void state(final State state, final String reason)\n    {\n        logStateChange(this.state, state, controlSessionId, reason);\n        this.state = state;\n    }\n\n    private void logStateChange(\n        final State oldState, final State newState, final long controlSessionId, final String reason)\n    {\n//        System.out.println(controlSessionId + \": \" + oldState + \" -> \" + newState + \", reason=\\\"\" + reason + \"\\\"\");\n    }\n\n    String abortReason()\n    {\n        return abortReason;\n    }\n\n    public String toString()\n    {\n        return \"ControlSession{\" +\n            \"controlSessionId=\" + controlSessionId +\n            \", correlationId=\" + correlationId +\n            \", state=\" + state +\n            \", controlPublication=\" + controlPublication +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ControlSessionAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.*;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.security.NullCredentialsSupplier;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.collections.Long2ObjectHashMap;\n\nimport static io.aeron.CommonContext.RESPONSE_CORRELATION_ID_PARAM_NAME;\n\nclass ControlSessionAdapter implements FragmentHandler\n{\n    private static final int FRAGMENT_LIMIT = 10;\n    private static final int FILE_IO_MAX_LENGTH_VERSION = 7;\n    private static final int SESSION_ID_VERSION = 8;\n    private static final int ENCODED_CREDENTIALS_VERSION = 8;\n    private static final int REPLAY_TOKEN_VERSION = 10;\n\n    private final ControlRequestDecoders decoders;\n    private final AuthorisationService authorisationService;\n    private final FragmentAssembler fragmentAssembler = new FragmentAssembler(this);\n    private final Long2ObjectHashMap<SessionInfo> controlSessionByIdMap = new Long2ObjectHashMap<>();\n    private final Subscription controlSubscription;\n    private final Subscription localControlSubscription;\n    private final ArchiveConductor conductor;\n\n    ControlSessionAdapter(\n        final ControlRequestDecoders decoders,\n        final Subscription controlSubscription,\n        final Subscription localControlSubscription,\n        final ArchiveConductor conductor,\n        final AuthorisationService authorisationService)\n    {\n        this.decoders = decoders;\n        this.controlSubscription = controlSubscription;\n        this.localControlSubscription = localControlSubscription;\n        this.conductor = conductor;\n        this.authorisationService = authorisationService;\n    }\n\n    public int poll()\n    {\n        int fragmentsRead = 0;\n\n        if (null != controlSubscription)\n        {\n            fragmentsRead += controlSubscription.poll(fragmentAssembler, FRAGMENT_LIMIT);\n        }\n\n        fragmentsRead += localControlSubscription.poll(fragmentAssembler, FRAGMENT_LIMIT);\n\n        return fragmentsRead;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @SuppressWarnings(\"MethodLength\")\n    public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final MessageHeaderDecoder headerDecoder = decoders.header;\n        headerDecoder.wrap(buffer, offset);\n\n        final int schemaId = headerDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ArchiveException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        final int templateId = headerDecoder.templateId();\n        switch (templateId)\n        {\n            case ConnectRequestDecoder.TEMPLATE_ID:\n            {\n                final ConnectRequestDecoder decoder = decoders.connectRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final Image image = (Image)header.context();\n\n                final ControlSession session = conductor.newControlSession(\n                    image,\n                    decoder.correlationId(),\n                    decoder.responseStreamId(),\n                    decoder.version(),\n                    decoder.responseChannel(),\n                    ArrayUtil.EMPTY_BYTE_ARRAY,\n                    \"\",\n                    this);\n                controlSessionByIdMap.put(session.sessionId(), new SessionInfo(image, session));\n                break;\n            }\n\n            case CloseSessionRequestDecoder.TEMPLATE_ID:\n            {\n                final CloseSessionRequestDecoder decoder = decoders.closeSessionRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final SessionInfo info = controlSessionByIdMap.get(controlSessionId);\n                if (null != info)\n                {\n                    info.controlSession.abort(ControlSession.SESSION_CLOSED_MSG);\n                }\n                break;\n            }\n\n            case StartRecordingRequestDecoder.TEMPLATE_ID:\n            {\n                final StartRecordingRequestDecoder decoder = decoders.startRecordingRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onStartRecording(\n                        correlationId,\n                        decoder.streamId(),\n                        decoder.sourceLocation(),\n                        false,\n                        decoder.channel());\n                }\n                break;\n            }\n\n            case StopRecordingRequestDecoder.TEMPLATE_ID:\n            {\n                final StopRecordingRequestDecoder decoder = decoders.stopRecordingRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onStopRecording(correlationId, decoder.streamId(), decoder.channel());\n                }\n                break;\n            }\n\n            case ReplayRequestDecoder.TEMPLATE_ID:\n            {\n                final ReplayRequestDecoder decoder = decoders.replayRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final int fileIoMaxLength = FILE_IO_MAX_LENGTH_VERSION <= headerDecoder.version() ?\n                    decoder.fileIoMaxLength() : Aeron.NULL_VALUE;\n                final long recordingId = decoder.recordingId();\n                final long position = decoder.position();\n                final long replayLength = decoder.length();\n                final int replayStreamId = decoder.replayStreamId();\n                final long replayToken = REPLAY_TOKEN_VERSION <= headerDecoder.version() ?\n                    decoder.replayToken() : Aeron.NULL_VALUE;\n\n                final String replayChannel = decoder.replayChannel();\n                final ChannelUri channelUri = ChannelUri.parse(replayChannel);\n                final Image image = (Image)header.context();\n                final ControlSession controlSession = setupSessionAndChannelForReplay(\n                    channelUri,\n                    replayToken,\n                    recordingId,\n                    correlationId,\n                    controlSessionId,\n                    templateId,\n                    image.correlationId());\n\n                if (null != controlSession)\n                {\n                    controlSession.onStartReplay(\n                        correlationId,\n                        recordingId,\n                        position,\n                        replayLength,\n                        fileIoMaxLength,\n                        replayStreamId,\n                        channelUri.toString());\n                }\n                break;\n            }\n\n            case StopReplayRequestDecoder.TEMPLATE_ID:\n            {\n                final StopReplayRequestDecoder decoder = decoders.stopReplayRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onStopReplay(correlationId, decoder.replaySessionId());\n                }\n                break;\n            }\n\n            case ListRecordingsRequestDecoder.TEMPLATE_ID:\n            {\n                final ListRecordingsRequestDecoder decoder = decoders.listRecordingsRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onListRecordings(correlationId, decoder.fromRecordingId(), decoder.recordCount());\n                }\n                break;\n            }\n\n            case ListRecordingsForUriRequestDecoder.TEMPLATE_ID:\n            {\n                final ListRecordingsForUriRequestDecoder decoder = decoders.listRecordingsForUriRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    final int channelLength = decoder.channelLength();\n                    final byte[] bytes = 0 == channelLength ? ArrayUtil.EMPTY_BYTE_ARRAY : new byte[channelLength];\n                    decoder.getChannel(bytes, 0, channelLength);\n\n                    controlSession.onListRecordingsForUri(\n                        correlationId,\n                        decoder.fromRecordingId(),\n                        decoder.recordCount(),\n                        decoder.streamId(),\n                        bytes);\n                }\n                break;\n            }\n\n            case ListRecordingRequestDecoder.TEMPLATE_ID:\n            {\n                final ListRecordingRequestDecoder decoder = decoders.listRecordingRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onListRecording(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case ExtendRecordingRequestDecoder.TEMPLATE_ID:\n            {\n                final ExtendRecordingRequestDecoder decoder = decoders.extendRecordingRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onExtendRecording(\n                        correlationId,\n                        decoder.recordingId(),\n                        decoder.streamId(),\n                        decoder.sourceLocation(),\n                        false,\n                        decoder.channel());\n                }\n                break;\n            }\n\n            case RecordingPositionRequestDecoder.TEMPLATE_ID:\n            {\n                final RecordingPositionRequestDecoder decoder = decoders.recordingPositionRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onGetRecordingPosition(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case TruncateRecordingRequestDecoder.TEMPLATE_ID:\n            {\n                final TruncateRecordingRequestDecoder decoder = decoders.truncateRecordingRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onTruncateRecording(correlationId, decoder.recordingId(), decoder.position());\n                }\n                break;\n            }\n\n            case StopRecordingSubscriptionRequestDecoder.TEMPLATE_ID:\n            {\n                final StopRecordingSubscriptionRequestDecoder decoder = decoders.stopRecordingSubscriptionRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onStopRecordingSubscription(correlationId, decoder.subscriptionId());\n                }\n                break;\n            }\n\n            case StopPositionRequestDecoder.TEMPLATE_ID:\n            {\n                final StopPositionRequestDecoder decoder = decoders.stopPositionRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onGetStopPosition(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case FindLastMatchingRecordingRequestDecoder.TEMPLATE_ID:\n            {\n                final FindLastMatchingRecordingRequestDecoder decoder = decoders.findLastMatchingRecordingRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    final int channelLength = decoder.channelLength();\n                    final byte[] bytes = 0 == channelLength ? ArrayUtil.EMPTY_BYTE_ARRAY : new byte[channelLength];\n\n                    decoder.getChannel(bytes, 0, channelLength);\n                    controlSession.onFindLastMatchingRecording(\n                        correlationId,\n                        decoder.minRecordingId(),\n                        decoder.sessionId(),\n                        decoder.streamId(),\n                        bytes);\n                }\n                break;\n            }\n\n            case ListRecordingSubscriptionsRequestDecoder.TEMPLATE_ID:\n            {\n                final ListRecordingSubscriptionsRequestDecoder decoder = decoders.listRecordingSubscriptionsRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onListRecordingSubscriptions(\n                        correlationId,\n                        decoder.pseudoIndex(),\n                        decoder.subscriptionCount(),\n                        decoder.applyStreamId() == BooleanType.TRUE,\n                        decoder.streamId(),\n                        decoder.channel());\n                }\n                break;\n            }\n\n            case BoundedReplayRequestDecoder.TEMPLATE_ID:\n            {\n                final BoundedReplayRequestDecoder decoder = decoders.boundedReplayRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final long position = decoder.position();\n                final long replayLength = decoder.length();\n                final long recordingId = decoder.recordingId();\n                final int limitCounterId = decoder.limitCounterId();\n                final int replayStreamId = decoder.replayStreamId();\n                final int fileIoMaxLength = FILE_IO_MAX_LENGTH_VERSION <= headerDecoder.version() ?\n                    decoder.fileIoMaxLength() : Aeron.NULL_VALUE;\n                final long replayToken = REPLAY_TOKEN_VERSION <= headerDecoder.version() ?\n                    decoder.replayToken() : Aeron.NULL_VALUE;\n\n                final String replayChannel = decoder.replayChannel();\n\n                final Image image = (Image)header.context();\n\n                final ChannelUri channelUri = ChannelUri.parse(replayChannel);\n                final ControlSession controlSession = setupSessionAndChannelForReplay(\n                    channelUri,\n                    replayToken,\n                    recordingId,\n                    correlationId,\n                    controlSessionId,\n                    templateId,\n                    image.correlationId());\n\n                if (null != controlSession)\n                {\n                    controlSession.onStartBoundedReplay(\n                        correlationId,\n                        recordingId,\n                        position,\n                        replayLength,\n                        limitCounterId,\n                        fileIoMaxLength,\n                        replayStreamId,\n                        channelUri.toString());\n                }\n                break;\n            }\n\n            case StopAllReplaysRequestDecoder.TEMPLATE_ID:\n            {\n                final StopAllReplaysRequestDecoder decoder = decoders.stopAllReplaysRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onStopAllReplays(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case ReplicateRequestDecoder.TEMPLATE_ID:\n            {\n                final ReplicateRequestDecoder decoder = decoders.replicateRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onReplicate(\n                        correlationId,\n                        decoder.srcRecordingId(),\n                        decoder.dstRecordingId(),\n                        AeronArchive.NULL_POSITION,\n                        Aeron.NULL_VALUE,\n                        Aeron.NULL_VALUE,\n                        decoder.srcControlStreamId(),\n                        Aeron.NULL_VALUE,\n                        Aeron.NULL_VALUE,\n                        decoder.srcControlChannel(),\n                        decoder.liveDestination(),\n                        \"\",\n                        NullCredentialsSupplier.NULL_CREDENTIAL,\n                        \"\");\n                }\n                break;\n            }\n\n            case StopReplicationRequestDecoder.TEMPLATE_ID:\n            {\n                final StopReplicationRequestDecoder decoder = decoders.stopReplicationRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onStopReplication(correlationId, decoder.replicationId());\n                }\n                break;\n            }\n\n            case StartPositionRequestDecoder.TEMPLATE_ID:\n            {\n                final StartPositionRequestDecoder decoder = decoders.startPositionRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onGetStartPosition(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case DetachSegmentsRequestDecoder.TEMPLATE_ID:\n            {\n                final DetachSegmentsRequestDecoder decoder = decoders.detachSegmentsRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onDetachSegments(correlationId, decoder.recordingId(), decoder.newStartPosition());\n                }\n                break;\n            }\n\n            case DeleteDetachedSegmentsRequestDecoder.TEMPLATE_ID:\n            {\n                final DeleteDetachedSegmentsRequestDecoder decoder = decoders.deleteDetachedSegmentsRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onDeleteDetachedSegments(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case PurgeSegmentsRequestDecoder.TEMPLATE_ID:\n            {\n                final PurgeSegmentsRequestDecoder decoder = decoders.purgeSegmentsRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onPurgeSegments(correlationId, decoder.recordingId(), decoder.newStartPosition());\n                }\n                break;\n            }\n\n            case AttachSegmentsRequestDecoder.TEMPLATE_ID:\n            {\n                final AttachSegmentsRequestDecoder decoder = decoders.attachSegmentsRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onAttachSegments(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case MigrateSegmentsRequestDecoder.TEMPLATE_ID:\n            {\n                final MigrateSegmentsRequestDecoder decoder = decoders.migrateSegmentsRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onMigrateSegments(correlationId, decoder.srcRecordingId(), decoder.dstRecordingId());\n                }\n                break;\n            }\n\n            case AuthConnectRequestDecoder.TEMPLATE_ID:\n            {\n                final AuthConnectRequestDecoder decoder = decoders.authConnectRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final String responseChannel = decoder.responseChannel();\n                final int credentialsLength = decoder.encodedCredentialsLength();\n                final byte[] credentials;\n\n                if (credentialsLength > 0)\n                {\n                    credentials = new byte[credentialsLength];\n                    decoder.getEncodedCredentials(credentials, 0, credentialsLength);\n                }\n                else\n                {\n                    credentials = ArrayUtil.EMPTY_BYTE_ARRAY;\n                    decoder.skipEncodedCredentials();\n                }\n                final String clientInfo = decoder.clientInfo();\n\n                final Image image = (Image)header.context();\n\n                final ControlSession session = conductor.newControlSession(\n                    image,\n                    decoder.correlationId(),\n                    decoder.responseStreamId(),\n                    decoder.version(),\n                    responseChannel,\n                    credentials,\n                    clientInfo,\n                    this);\n                controlSessionByIdMap.put(session.sessionId(), new SessionInfo(image, session));\n                break;\n            }\n\n            case ChallengeResponseDecoder.TEMPLATE_ID:\n            {\n                final ChallengeResponseDecoder decoder = decoders.challengeResponse;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final SessionInfo info = controlSessionByIdMap.get(controlSessionId);\n                if (null != info)\n                {\n                    final int credentialsLength = decoder.encodedCredentialsLength();\n                    final byte[] credentials;\n\n                    if (credentialsLength > 0)\n                    {\n                        credentials = new byte[credentialsLength];\n                        decoder.getEncodedCredentials(credentials, 0, credentialsLength);\n                    }\n                    else\n                    {\n                        credentials = ArrayUtil.EMPTY_BYTE_ARRAY;\n                    }\n\n                    info.controlSession.onChallengeResponse(decoder.correlationId(), credentials);\n                }\n                break;\n            }\n\n            case KeepAliveRequestDecoder.TEMPLATE_ID:\n            {\n                final KeepAliveRequestDecoder decoder = decoders.keepAliveRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onKeepAlive(correlationId);\n                }\n                break;\n            }\n\n            case TaggedReplicateRequestDecoder.TEMPLATE_ID:\n            {\n                final TaggedReplicateRequestDecoder decoder = decoders.taggedReplicateRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onReplicate(\n                        correlationId,\n                        decoder.srcRecordingId(),\n                        decoder.dstRecordingId(),\n                        AeronArchive.NULL_POSITION,\n                        decoder.channelTagId(),\n                        decoder.subscriptionTagId(),\n                        decoder.srcControlStreamId(),\n                        Aeron.NULL_VALUE,\n                        Aeron.NULL_VALUE,\n                        decoder.srcControlChannel(),\n                        decoder.liveDestination(),\n                        \"\",\n                        NullCredentialsSupplier.NULL_CREDENTIAL,\n                        \"\");\n                }\n                break;\n            }\n\n            case StartRecordingRequest2Decoder.TEMPLATE_ID:\n            {\n                final StartRecordingRequest2Decoder decoder = decoders.startRecordingRequest2;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onStartRecording(\n                        correlationId,\n                        decoder.streamId(),\n                        decoder.sourceLocation(),\n                        decoder.autoStop() == BooleanType.TRUE,\n                        decoder.channel());\n                }\n                break;\n            }\n\n            case ExtendRecordingRequest2Decoder.TEMPLATE_ID:\n            {\n                final ExtendRecordingRequest2Decoder decoder = decoders.extendRecordingRequest2;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onExtendRecording(\n                        correlationId,\n                        decoder.recordingId(),\n                        decoder.streamId(),\n                        decoder.sourceLocation(),\n                        decoder.autoStop() == BooleanType.TRUE,\n                        decoder.channel());\n                }\n                break;\n            }\n\n            case StopRecordingByIdentityRequestDecoder.TEMPLATE_ID:\n            {\n                final StopRecordingByIdentityRequestDecoder decoder = decoders.stopRecordingByIdentityRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onStopRecordingByIdentity(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case ReplicateRequest2Decoder.TEMPLATE_ID:\n            {\n                final ReplicateRequest2Decoder decoder = decoders.replicateRequest2;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                final int fileIoMaxLength = FILE_IO_MAX_LENGTH_VERSION <= headerDecoder.version() ?\n                    decoder.fileIoMaxLength() : Aeron.NULL_VALUE;\n\n                final int sessionId = SESSION_ID_VERSION <= headerDecoder.version() ?\n                    decoder.replicationSessionId() : Aeron.NULL_VALUE;\n\n                final String srcControlChannel = decoder.srcControlChannel();\n                final String liveDestination = decoder.liveDestination();\n                final String replicationChannel = decoder.replicationChannel();\n                final byte[] encodedCredentials;\n                if (ENCODED_CREDENTIALS_VERSION <= headerDecoder.version())\n                {\n                    encodedCredentials = new byte[decoder.encodedCredentialsLength()];\n                    decoder.getEncodedCredentials(encodedCredentials, 0, decoder.encodedCredentialsLength());\n                }\n                else\n                {\n                    encodedCredentials = NullCredentialsSupplier.NULL_CREDENTIAL;\n                }\n                final String srcResponseChannel = decoder.srcResponseChannel();\n\n                if (null != controlSession)\n                {\n                    controlSession.onReplicate(\n                        correlationId,\n                        decoder.srcRecordingId(),\n                        decoder.dstRecordingId(),\n                        decoder.stopPosition(),\n                        decoder.channelTagId(),\n                        decoder.subscriptionTagId(),\n                        decoder.srcControlStreamId(),\n                        fileIoMaxLength,\n                        sessionId,\n                        srcControlChannel,\n                        liveDestination,\n                        replicationChannel,\n                        encodedCredentials,\n                        srcResponseChannel);\n                }\n                break;\n            }\n\n            case PurgeRecordingRequestDecoder.TEMPLATE_ID:\n            {\n                final PurgeRecordingRequestDecoder decoder = decoders.purgeRecordingRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onPurgeRecording(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case MaxRecordedPositionRequestDecoder.TEMPLATE_ID:\n            {\n                final MaxRecordedPositionRequestDecoder decoder = decoders.maxRecordedPositionRequest;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onGetMaxRecordedPosition(correlationId, decoder.recordingId());\n                }\n                break;\n            }\n\n            case ArchiveIdRequestDecoder.TEMPLATE_ID:\n            {\n                final ArchiveIdRequestDecoder decoder = decoders.archiveIdRequestDecoder;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n\n                if (null != controlSession)\n                {\n                    controlSession.onArchiveId(correlationId);\n                }\n                break;\n            }\n\n            case ReplayTokenRequestDecoder.TEMPLATE_ID:\n            {\n                final ReplayTokenRequestDecoder decoder = decoders.replayTokenRequestDecoder;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final long recordingId = decoder.recordingId();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n                if (null != controlSession)\n                {\n                    final long replayToken = conductor.generateReplayToken(controlSession, recordingId);\n                    controlSession.sendOkResponse(correlationId, replayToken);\n                }\n\n                break;\n            }\n\n            case UpdateChannelRequestDecoder.TEMPLATE_ID:\n            {\n                final UpdateChannelRequestDecoder decoder = decoders.updateChannelRequestDecoder;\n                decoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    headerDecoder.blockLength(),\n                    headerDecoder.version());\n\n                final long controlSessionId = decoder.controlSessionId();\n                final long correlationId = decoder.correlationId();\n                final long recordingId = decoder.recordingId();\n                final String channel = decoder.channel();\n                final ControlSession controlSession = getControlSession(correlationId, controlSessionId, templateId);\n                if (null != controlSession)\n                {\n                    conductor.updateChannel(correlationId, recordingId, channel, controlSession);\n                }\n\n                break;\n            }\n        }\n    }\n\n    void abortControlSessionByImage(final Image image)\n    {\n        for (final SessionInfo info : controlSessionByIdMap.values())\n        {\n            if (info.image == image && !info.controlSession.isDone())\n            {\n                final Subscription subscription = image.subscription();\n                info.controlSession.abort(ControlSession.REQUEST_IMAGE_NOT_AVAILABLE_MSG +\n                    \" : controlSessionId=\" + info.controlSession.sessionId() +\n                    \" image.correlationId=\" + image.correlationId() +\n                    \" sessionId=\" + image.sessionId() +\n                    \" streamId=\" + subscription.streamId() +\n                    \" channel=\" + subscription.channel());\n                break;\n            }\n        }\n    }\n\n    void removeControlSession(final long controlSessionId, final boolean sessionAborted, final String abortReason)\n    {\n        final SessionInfo sessionInfo = controlSessionByIdMap.remove(controlSessionId);\n        if (null != sessionInfo && sessionAborted)\n        {\n            sessionInfo.image.reject(abortReason);\n        }\n        conductor.removeReplayTokensForSession(controlSessionId);\n        final Counter controlSessionsCounter = conductor.context().controlSessionsCounter();\n        if (!controlSessionsCounter.isClosed())\n        {\n            controlSessionsCounter.decrementRelease();\n        }\n    }\n\n    private ControlSession setupSessionAndChannelForReplay(\n        final ChannelUri channelUri,\n        final long replayToken,\n        final long recordingId,\n        final long correlationId,\n        final long controlSessionId,\n        final int templateId,\n        final long imageCorrelationId)\n    {\n        final ControlSession controlSession;\n        if (channelUri.hasControlModeResponse() && Aeron.NULL_VALUE != replayToken)\n        {\n            controlSession = conductor.getReplaySession(replayToken, recordingId);\n            if (null == controlSession)\n            {\n                throw new ArchiveException(\"Unknown session or token timeout for replayToken=\" + replayToken);\n            }\n\n            channelUri.put(RESPONSE_CORRELATION_ID_PARAM_NAME, Long.toString(imageCorrelationId));\n        }\n        else\n        {\n            controlSession = getControlSession(correlationId, controlSessionId, templateId);\n        }\n        return controlSession;\n    }\n\n    private ControlSession getControlSession(\n        final long correlationId, final long controlSessionId, final int templateId)\n    {\n        final SessionInfo info = controlSessionByIdMap.get(controlSessionId);\n        if (null != info)\n        {\n            final ControlSession controlSession = info.controlSession;\n            final byte[] principal = controlSession.encodedPrincipal();\n            if (!authorisationService.isAuthorised(MessageHeaderDecoder.SCHEMA_ID, templateId, null, principal))\n            {\n                conductor.logWarning(\"unauthorised archive action=\" + templateId +\n                    \" controlSessionId=\" + controlSessionId + \" source=\" + info.image.sourceIdentity());\n\n                controlSession.sendErrorResponse(\n                    correlationId, ArchiveException.UNAUTHORISED_ACTION, \"unauthorised action\");\n\n                return null;\n            }\n            return controlSession;\n        }\n        else\n        {\n            conductor.logWarning(\"control request for unknown session:\" +\n                \" controlSessionId=\" + controlSessionId +\n                \" templateId=\" + templateId);\n            return null;\n        }\n    }\n\n    private record SessionInfo(Image image, ControlSession controlSession)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ControlSessionCounter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.concurrent.status.CountersReader.KEY_OFFSET;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_ALLOCATED;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_UNUSED;\nimport static org.agrona.concurrent.status.CountersReader.metaDataOffset;\n\n/**\n * A counter that represents client connected to the Archive. It stores control session id as a value and client\n * details in the label.\n *\n * @since 1.49.0\n */\npublic final class ControlSessionCounter\n{\n    /**\n     * Type id of the counter.\n     */\n    public static final int TYPE_ID = AeronCounters.ARCHIVE_CONTROL_SESSION_TYPE_ID;\n\n    /**\n     * Offset withing {@code key} buffer where archive id is stored.\n     */\n    public static final int ARCHIVE_ID_KEY_OFFSET = 0;\n\n    /**\n     * Offset withing {@code key} buffer where control session id is stored.\n     */\n    public static final int CONTROL_SESSION_ID_KEY_OFFSET = ARCHIVE_ID_KEY_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * Counter name, i.e. the first part of the counter label.\n     */\n    public static final String NAME = \"control-session\";\n\n    private ControlSessionCounter()\n    {\n    }\n\n    static long allocate(\n        final Aeron aeron,\n        final MutableDirectBuffer tempBuffer,\n        final long archiveId,\n        final long controlSessionId,\n        final String clientInfo)\n    {\n        tempBuffer.putLong(ARCHIVE_ID_KEY_OFFSET, archiveId);\n        tempBuffer.putLong(CONTROL_SESSION_ID_KEY_OFFSET, controlSessionId);\n        final int keyLength = 2 * SIZE_OF_LONG;\n\n        int labelLength = 0;\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, NAME);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, \": \");\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, clientInfo);\n        labelLength += ArchiveCounters.appendArchiveIdLabel(tempBuffer, keyLength + labelLength, archiveId);\n\n        return aeron.asyncAddCounter(TYPE_ID, tempBuffer, 0, keyLength, tempBuffer, keyLength, labelLength);\n    }\n\n    /**\n     * Find the active counter id based on the provided archive and control session ids.\n     *\n     * @param countersReader   to search within.\n     * @param archiveId        to target specific Archive.\n     * @param controlSessionId for which counter was created.\n     * @return the counter id if found, otherwise {@link CountersReader#NULL_COUNTER_ID}.\n     */\n    public static int findByControlSessionId(\n        final CountersReader countersReader, final long archiveId, final long controlSessionId)\n    {\n        final DirectBuffer buffer = countersReader.metaDataBuffer();\n\n        for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n        {\n            final int counterState = countersReader.getCounterState(counterId);\n            if (RECORD_ALLOCATED == counterState)\n            {\n                if (countersReader.getCounterTypeId(counterId) == TYPE_ID)\n                {\n                    final int keyOffset = metaDataOffset(counterId) + KEY_OFFSET;\n                    if (buffer.getLong(keyOffset + ARCHIVE_ID_KEY_OFFSET) == archiveId &&\n                        buffer.getLong(keyOffset + CONTROL_SESSION_ID_KEY_OFFSET) == controlSessionId)\n                    {\n                        return counterId;\n                    }\n                }\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return NULL_COUNTER_ID;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ControlSessionProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.security.SessionProxy;\n\nimport static io.aeron.archive.codecs.ControlResponseCode.OK;\n\nclass ControlSessionProxy implements SessionProxy\n{\n    private final ControlResponseProxy controlResponseProxy;\n    private ControlSession controlSession;\n\n    ControlSessionProxy(final ControlResponseProxy controlResponseProxy)\n    {\n        this.controlResponseProxy = controlResponseProxy;\n    }\n\n    ControlSessionProxy controlSession(final ControlSession controlSession)\n    {\n        this.controlSession = controlSession;\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return controlSession.sessionId();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean challenge(final byte[] encodedChallenge)\n    {\n        if (controlResponseProxy.sendChallenge(\n            controlSession.sessionId(),\n            controlSession.correlationId(),\n            encodedChallenge,\n            controlSession))\n        {\n            controlSession.challenged();\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean authenticate(final byte[] encodedPrincipal)\n    {\n        if (controlResponseProxy.sendResponse(\n            controlSession.sessionId(),\n            controlSession.correlationId(),\n            controlSession.sessionId(),\n            OK,\n            null,\n            controlSession))\n        {\n            controlSession.authenticate(encodedPrincipal);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void reject()\n    {\n        controlSession.reject();\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/CreateReplayPublicationSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\n\nclass CreateReplayPublicationSession implements Session\n{\n    private final long correlationId;\n    private final long recordingId;\n    private final long replayPosition;\n    private final long replayLength;\n    private final long startPosition;\n    private final long stopPosition;\n    private final int segmentFileLength;\n    private final int termBufferLength;\n    private final int streamId;\n    private long publicationRegistrationId;\n    private final int fileIoMaxLength;\n    private boolean isDone = false;\n    private final Aeron aeron;\n    private final Counter limitPositionCounter;\n    private final ControlSession controlSession;\n    private final ArchiveConductor conductor;\n\n    CreateReplayPublicationSession(\n        final long correlationId,\n        final long recordingId,\n        final long replayPosition,\n        final long replayLength,\n        final long startPosition,\n        final long stopPosition,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int streamId,\n        final long publicationRegistrationId,\n        final int fileIoMaxLength,\n        final Counter limitPositionCounter,\n        final Aeron aeron,\n        final ControlSession controlSession,\n        final ArchiveConductor conductor)\n    {\n        this.correlationId = correlationId;\n        this.recordingId = recordingId;\n        this.replayPosition = replayPosition;\n        this.replayLength = replayLength;\n        this.startPosition = startPosition;\n        this.stopPosition = stopPosition;\n        this.segmentFileLength = segmentFileLength;\n        this.termBufferLength = termBufferLength;\n        this.streamId = streamId;\n        this.publicationRegistrationId = publicationRegistrationId;\n        this.fileIoMaxLength = fileIoMaxLength;\n        this.limitPositionCounter = limitPositionCounter;\n        this.aeron = aeron;\n        this.controlSession = controlSession;\n        this.conductor = conductor;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (Aeron.NULL_VALUE != publicationRegistrationId)\n        {\n            aeron.asyncRemovePublication(publicationRegistrationId);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n        isDone = true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return isDone;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return publicationRegistrationId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n\n        if (!isDone)\n        {\n            final ExclusivePublication publication;\n            try\n            {\n                publication = aeron.getExclusivePublication(publicationRegistrationId);\n            }\n            catch (final Exception ex)\n            {\n                isDone = true;\n                final String msg = \"failed to create replay publication: \" + ex.getMessage();\n                controlSession.sendErrorResponse(correlationId, msg);\n                throw ex;\n            }\n\n            if (null != publication)\n            {\n                publicationRegistrationId = Aeron.NULL_VALUE;\n                isDone = true;\n                workCount += 1;\n\n                conductor.newReplaySession(\n                    correlationId,\n                    recordingId,\n                    replayPosition,\n                    replayLength,\n                    startPosition,\n                    stopPosition,\n                    segmentFileLength,\n                    termBufferLength,\n                    streamId,\n                    fileIoMaxLength,\n                    controlSession,\n                    limitPositionCounter,\n                    publication);\n            }\n        }\n\n        return workCount;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/DedicatedModeArchiveConductor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage io.aeron.archive;\n\nimport io.aeron.driver.DutyCycleTracker;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.*;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.util.concurrent.CountDownLatch;\n\nfinal class DedicatedModeArchiveConductor extends ArchiveConductor\n{\n    private static final int COMMAND_LIMIT = 10;\n\n    private final ManyToOneConcurrentLinkedQueue<Session> closeQueue;\n    private AgentRunner recorderAgentRunner;\n    private AgentRunner replayerAgentRunner;\n\n    DedicatedModeArchiveConductor(final Archive.Context ctx)\n    {\n        super(ctx);\n        closeQueue = new ManyToOneConcurrentLinkedQueue<>();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        super.onStart();\n\n        recorderAgentRunner = new AgentRunner(ctx.recorderIdleStrategy(), errorHandler, ctx.errorCounter(), recorder);\n        replayerAgentRunner = new AgentRunner(ctx.replayerIdleStrategy(), errorHandler, ctx.errorCounter(), replayer);\n\n        AgentRunner.startOnThread(recorderAgentRunner, ctx.recorderThreadFactory());\n        AgentRunner.startOnThread(replayerAgentRunner, ctx.replayerThreadFactory());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        final int workCount = processCloseQueue();\n        return workCount + super.doWork();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected void closeSessionWorkers()\n    {\n        CloseHelper.close(errorHandler, recorderAgentRunner);\n        CloseHelper.close(errorHandler, replayerAgentRunner);\n\n        while (processCloseQueue() > 0 || !closeQueue.isEmpty())\n        {\n            Thread.yield();\n            if (Thread.currentThread().isInterrupted())\n            {\n                break;\n            }\n        }\n    }\n\n    Recorder newRecorder()\n    {\n        return new DedicatedModeRecorder(\n            errorHandler,\n            ctx.errorCounter(),\n            closeQueue,\n            ctx.abortLatch(),\n            ctx.recorderDutyCycleTracker(),\n            ctx);\n    }\n\n    Replayer newReplayer()\n    {\n        return new DedicatedModeReplayer(\n            errorHandler,\n            ctx.errorCounter(),\n            closeQueue,\n            ctx.abortLatch(),\n            ctx.replayerDutyCycleTracker(),\n            ctx);\n    }\n\n    private int processCloseQueue()\n    {\n        int i;\n        Session session;\n        for (i = 0; i < COMMAND_LIMIT && (session = closeQueue.poll()) != null; i++)\n        {\n            if (session instanceof RecordingSession)\n            {\n                closeRecordingSession((RecordingSession)session);\n            }\n            else if (session instanceof ReplaySession)\n            {\n                closeReplaySession((ReplaySession)session);\n            }\n            else\n            {\n                closeSession(session);\n            }\n        }\n\n        return i;\n    }\n\n    static class DedicatedModeRecorder extends Recorder\n    {\n        private final ManyToOneConcurrentLinkedQueue<RecordingSession> sessionsQueue;\n        private final ManyToOneConcurrentLinkedQueue<Session> closeQueue;\n        private final AtomicCounter errorCounter;\n        private final CountDownLatch abortLatch;\n        private final DutyCycleTracker dutyCycleTracker;\n        private final NanoClock nanoClock;\n        private volatile boolean isAbort;\n\n        DedicatedModeRecorder(\n            final CountedErrorHandler errorHandler,\n            final AtomicCounter errorCounter,\n            final ManyToOneConcurrentLinkedQueue<Session> closeQueue,\n            final CountDownLatch abortLatch,\n            final DutyCycleTracker dutyCycleTracker,\n            final Archive.Context context)\n        {\n            super(errorHandler, context);\n\n            this.closeQueue = closeQueue;\n            this.errorCounter = errorCounter;\n            this.sessionsQueue = new ManyToOneConcurrentLinkedQueue<>();\n            this.abortLatch = abortLatch;\n            this.dutyCycleTracker = dutyCycleTracker;\n            this.nanoClock = context.nanoClock();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void abort()\n        {\n            isAbort = true;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onStart()\n        {\n            super.onStart();\n\n            dutyCycleTracker.update(nanoClock.nanoTime());\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public int doWork()\n        {\n            if (isAbort)\n            {\n                throw new AgentTerminationException();\n            }\n\n            dutyCycleTracker.measureAndUpdate(nanoClock.nanoTime());\n\n            return drainSessionsQueue() + super.doWork();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void preSessionsClose()\n        {\n            drainSessionsQueue();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void addSession(final RecordingSession session)\n        {\n            send(session);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void closeSession(final RecordingSession session)\n        {\n            while (!closeQueue.offer(session))\n            {\n                if (!errorCounter.isClosed())\n                {\n                    errorCounter.increment();\n                }\n\n                Thread.yield();\n                if (Thread.currentThread().isInterrupted())\n                {\n                    break;\n                }\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void postSessionsClose()\n        {\n            if (isAbort)\n            {\n                abortLatch.countDown();\n            }\n        }\n\n        private int drainSessionsQueue()\n        {\n            int workCount = 0;\n            RecordingSession session;\n\n            while (null != (session = sessionsQueue.poll()))\n            {\n                workCount += 1;\n                super.addSession(session);\n            }\n\n            return workCount;\n        }\n\n        private void send(final RecordingSession session)\n        {\n            while (!sessionsQueue.offer(session))\n            {\n                if (!errorCounter.isClosed())\n                {\n                    errorCounter.increment();\n                }\n\n                Thread.yield();\n                if (Thread.currentThread().isInterrupted())\n                {\n                    break;\n                }\n            }\n        }\n    }\n\n    static class DedicatedModeReplayer extends Replayer\n    {\n        private final ManyToOneConcurrentLinkedQueue<ReplaySession> sessionsQueue;\n        private final ManyToOneConcurrentLinkedQueue<Session> closeQueue;\n        private final AtomicCounter errorCounter;\n        private final CountDownLatch abortLatch;\n        private final DutyCycleTracker dutyCycleTracker;\n        private final NanoClock nanoClock;\n        private volatile boolean isAbort;\n\n        DedicatedModeReplayer(\n            final CountedErrorHandler errorHandler,\n            final AtomicCounter errorCounter,\n            final ManyToOneConcurrentLinkedQueue<Session> closeQueue,\n            final CountDownLatch abortLatch,\n            final DutyCycleTracker dutyCycleTracker,\n            final Archive.Context context)\n        {\n            super(errorHandler, context);\n\n            this.closeQueue = closeQueue;\n            this.errorCounter = errorCounter;\n            this.sessionsQueue = new ManyToOneConcurrentLinkedQueue<>();\n            this.abortLatch = abortLatch;\n            this.dutyCycleTracker = dutyCycleTracker;\n            this.nanoClock = context.nanoClock();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void abort()\n        {\n            isAbort = true;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void addSession(final ReplaySession session)\n        {\n            send(session);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onStart()\n        {\n            super.onStart();\n            dutyCycleTracker.update(nanoClock.nanoTime());\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public int doWork()\n        {\n            if (isAbort)\n            {\n                throw new AgentTerminationException();\n            }\n\n            dutyCycleTracker.measureAndUpdate(nanoClock.nanoTime());\n\n            return drainSessionQueue() + super.doWork();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void preSessionsClose()\n        {\n            drainSessionQueue();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void closeSession(final ReplaySession session)\n        {\n            while (!closeQueue.offer(session))\n            {\n                if (!errorCounter.isClosed())\n                {\n                    errorCounter.increment();\n                }\n\n                Thread.yield();\n                if (Thread.currentThread().isInterrupted())\n                {\n                    break;\n                }\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        protected void postSessionsClose()\n        {\n            if (isAbort)\n            {\n                abortLatch.countDown();\n            }\n        }\n\n        private int drainSessionQueue()\n        {\n            int workCount = 0;\n            ReplaySession session;\n\n            while (null != (session = sessionsQueue.poll()))\n            {\n                workCount += 1;\n                super.addSession(session);\n            }\n\n            return workCount;\n        }\n\n        private void send(final ReplaySession session)\n        {\n            while (!sessionsQueue.offer(session))\n            {\n                if (!errorCounter.isClosed())\n                {\n                    errorCounter.increment();\n                }\n\n                Thread.yield();\n                if (Thread.currentThread().isInterrupted())\n                {\n                    break;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/DeleteSegmentsSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.client.ArchiveEvent;\nimport io.aeron.archive.client.ArchiveException;\nimport org.agrona.ErrorHandler;\n\nimport java.io.File;\nimport java.util.ArrayDeque;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.ArchiveConductor.DELETE_SUFFIX;\nimport static io.aeron.archive.codecs.RecordingSignal.DELETE;\nimport static org.agrona.AsciiEncoding.digitCount;\nimport static org.agrona.AsciiEncoding.parseLongAscii;\n\nclass DeleteSegmentsSession implements Session\n{\n    private final long recordingId;\n    private final long correlationId;\n    private final long maxDeletePosition;\n    private final ArrayDeque<File> files;\n    private final ControlSession controlSession;\n    private final ErrorHandler errorHandler;\n\n    DeleteSegmentsSession(\n        final long recordingId,\n        final long correlationId,\n        final ArrayDeque<File> files,\n        final ControlSession controlSession,\n        final ErrorHandler errorHandler)\n    {\n        this.recordingId = recordingId;\n        this.correlationId = correlationId;\n        this.files = files;\n        this.controlSession = controlSession;\n        this.errorHandler = errorHandler;\n\n        long maxSegmentPosition = Long.MIN_VALUE;\n        final int prefixLength = digitCount(recordingId) + 1;\n        for (final File file : files)\n        {\n            final String name = file.getName();\n            final int dotIndex = name.indexOf('.');\n            maxSegmentPosition = Math.max(\n                maxSegmentPosition, parseLongAscii(name, prefixLength, dotIndex - prefixLength));\n        }\n        maxDeletePosition = maxSegmentPosition;\n    }\n\n    long maxDeletePosition()\n    {\n        return maxDeletePosition;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        controlSession.archiveConductor().removeDeleteSegmentsSession(this);\n        controlSession.sendSignal(correlationId, recordingId, NULL_VALUE, NULL_VALUE, DELETE);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return files.isEmpty();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return recordingId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n        final File file = files.pollFirst();\n        if (null != file)\n        {\n            if (!file.delete())\n            {\n                if (file.exists())\n                {\n                    onDeleteError(file);\n                }\n                else if (!file.getName().endsWith(DELETE_SUFFIX))\n                {\n                    final File renamedFile = new File(file.getParent(), file.getName() + DELETE_SUFFIX);\n                    if (!renamedFile.delete() && renamedFile.exists())\n                    {\n                        onDeleteError(renamedFile);\n                    }\n                }\n            }\n\n            workCount = 1;\n        }\n\n        return workCount;\n    }\n\n    private void onDeleteError(final File file)\n    {\n        final String errorMessage = \"unable to delete segment file: \" + file;\n        controlSession.sendErrorResponse(correlationId, ArchiveException.GENERIC, errorMessage);\n        errorHandler.onError(new ArchiveEvent(errorMessage));\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ListRecordingByIdSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nclass ListRecordingByIdSession implements Session\n{\n    private final long correlationId;\n    private final long recordingId;\n    private final Catalog catalog;\n    private final ControlSession controlSession;\n    private final UnsafeBuffer descriptorBuffer;\n    private boolean isDone;\n\n    ListRecordingByIdSession(\n        final long correlationId,\n        final long recordingId,\n        final Catalog catalog,\n        final ControlSession controlSession,\n        final UnsafeBuffer descriptorBuffer)\n    {\n        this.correlationId = correlationId;\n        this.recordingId = recordingId;\n        this.catalog = catalog;\n        this.controlSession = controlSession;\n        this.descriptorBuffer = descriptorBuffer;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n        isDone = true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return isDone;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        if (isDone)\n        {\n            return 0;\n        }\n\n        if (catalog.wrapDescriptor(recordingId, descriptorBuffer))\n        {\n            if (controlSession.sendDescriptor(correlationId, descriptorBuffer))\n            {\n                isDone = true;\n            }\n        }\n        else\n        {\n            controlSession.sendRecordingUnknown(correlationId, recordingId);\n            isDone = true;\n        }\n\n        return 1;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        controlSession.activeListing(null);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ListRecordingSubscriptionsSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.Subscription;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nclass ListRecordingSubscriptionsSession implements Session\n{\n    private final long correlationId;\n    private final int subscriptionCount;\n    private int pseudoIndex;\n    private int sent;\n    private final int streamId;\n    private final boolean applyStreamId;\n    private boolean isDone = false;\n    private final String channelFragment;\n    private final Object2ObjectHashMap<String, Subscription> subscriptionByKeyMap;\n    private final ControlSession controlSession;\n\n    ListRecordingSubscriptionsSession(\n        final Object2ObjectHashMap<String, Subscription> subscriptionByKeyMap,\n        final int pseudoIndex,\n        final int subscriptionCount,\n        final int streamId,\n        final boolean applyStreamId,\n        final String channelFragment,\n        final long correlationId,\n        final ControlSession controlSession)\n    {\n        this.subscriptionByKeyMap = subscriptionByKeyMap;\n        this.pseudoIndex = pseudoIndex;\n        this.subscriptionCount = subscriptionCount;\n        this.streamId = streamId;\n        this.applyStreamId = applyStreamId;\n        this.channelFragment = channelFragment;\n        this.correlationId = correlationId;\n        this.controlSession = controlSession;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        controlSession.activeListing(null);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n        isDone = true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return isDone;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return Aeron.NULL_VALUE;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n        int index = 0;\n        final int size = subscriptionByKeyMap.size();\n\n        for (final Subscription subscription : subscriptionByKeyMap.values())\n        {\n            if (index++ >= pseudoIndex)\n            {\n                if (!(applyStreamId && subscription.streamId() != streamId) &&\n                    subscription.channel().contains(channelFragment))\n                {\n                    if (!controlSession.sendSubscriptionDescriptor(correlationId, subscription))\n                    {\n                        isDone = controlSession.isDone();\n                        break;\n                    }\n                    workCount += 1;\n\n                    if (++sent >= subscriptionCount)\n                    {\n                        isDone = true;\n                        break;\n                    }\n                }\n\n                pseudoIndex = index - 1;\n            }\n        }\n\n        if (!isDone && index >= size)\n        {\n            controlSession.sendSubscriptionUnknown(correlationId);\n            isDone = true;\n            workCount += 1;\n        }\n\n        return workCount;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ListRecordingsForUriSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderDecoder;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nclass ListRecordingsForUriSession extends AbstractListRecordingsSession\n{\n    private final int streamId;\n    private final byte[] channelFragment;\n    private final RecordingDescriptorDecoder descriptorDecoder;\n\n    ListRecordingsForUriSession(\n        final long correlationId,\n        final long fromRecordingId,\n        final int count,\n        final byte[] channelFragment,\n        final int streamId,\n        final Catalog catalog,\n        final ControlSession controlSession,\n        final UnsafeBuffer descriptorBuffer,\n        final RecordingDescriptorDecoder recordingDescriptorDecoder)\n    {\n        super(\n            correlationId,\n            fromRecordingId,\n            count,\n            catalog,\n            controlSession,\n            descriptorBuffer);\n\n        this.streamId = streamId;\n        this.channelFragment = channelFragment;\n        descriptorDecoder = recordingDescriptorDecoder;\n    }\n\n    boolean acceptDescriptor(final UnsafeBuffer descriptorBuffer)\n    {\n        descriptorDecoder.wrap(\n            descriptorBuffer,\n            RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n            RecordingDescriptorDecoder.BLOCK_LENGTH,\n            RecordingDescriptorDecoder.SCHEMA_VERSION);\n\n        return streamId == descriptorDecoder.streamId() &&\n            Catalog.originalChannelContains(descriptorDecoder, channelFragment);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ListRecordingsSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nclass ListRecordingsSession extends AbstractListRecordingsSession\n{\n    ListRecordingsSession(\n        final long correlationId,\n        final long fromRecordingId,\n        final int count,\n        final Catalog catalog,\n        final ControlSession controlSession,\n        final UnsafeBuffer descriptorBuffer)\n    {\n        super(\n            correlationId,\n            fromRecordingId,\n            count,\n            catalog,\n            controlSession,\n            descriptorBuffer);\n    }\n\n    boolean acceptDescriptor(final UnsafeBuffer descriptorBuffer)\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/MigrationUtils.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport org.agrona.LangUtil;\nimport org.agrona.SemanticVersion;\n\nimport java.io.File;\nimport java.nio.channels.FileChannel;\n\nimport static java.nio.file.StandardOpenOption.*;\n\nfinal class MigrationUtils\n{\n    private static final String MIGRATION_TIMESTAMP_FILE_PREFIX = \"migration-\";\n    private static final String MIGRATION_TIMESTAMP_FILE_SUFFIX = \".dat\";\n\n    private MigrationUtils()\n    {\n    }\n\n    static FileChannel createMigrationTimestampFile(\n        final File directory, final int fromVersion, final int toVersion)\n    {\n        final String filename = migrationTimestampFileName(fromVersion, toVersion);\n        final File timestampFile = new File(directory, filename);\n\n        FileChannel fileChannel = null;\n\n        try\n        {\n            fileChannel = FileChannel.open(timestampFile.toPath(), CREATE_NEW, READ, WRITE, SPARSE);\n            fileChannel.force(true);\n        }\n        catch (final Exception ex)\n        {\n            System.err.println(\"Could not create migration timestamp file:\" + timestampFile);\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return fileChannel;\n    }\n\n    static String migrationTimestampFileName(final int fromVersion, final int toVersion)\n    {\n        return MIGRATION_TIMESTAMP_FILE_PREFIX + fromVersion + \"-to-\" + toVersion + MIGRATION_TIMESTAMP_FILE_SUFFIX;\n    }\n\n    static String fullVersionString(final int version)\n    {\n        return version +\n            \"(Major \" + SemanticVersion.major(version) +\n            \" Minor \" + SemanticVersion.minor(version) +\n            \" Patch \" + SemanticVersion.patch(version) + \")\";\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/RecordingEventsProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Publication;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.MessageHeaderEncoder;\nimport io.aeron.archive.codecs.RecordingProgressEncoder;\nimport io.aeron.archive.codecs.RecordingStartedEncoder;\nimport io.aeron.archive.codecs.RecordingStoppedEncoder;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.CloseHelper;\nimport org.agrona.ExpandableArrayBuffer;\n\nclass RecordingEventsProxy implements AutoCloseable\n{\n    private static final int SEND_ATTEMPTS = 3;\n\n    private final Publication publication;\n    private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(1024);\n    private final BufferClaim bufferClaim = new BufferClaim();\n    private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n    private final RecordingStartedEncoder recordingStartedEncoder = new RecordingStartedEncoder();\n    private final RecordingProgressEncoder recordingProgressEncoder = new RecordingProgressEncoder();\n    private final RecordingStoppedEncoder recordingStoppedEncoder = new RecordingStoppedEncoder();\n\n    RecordingEventsProxy(final Publication publication)\n    {\n        this.publication = publication;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(publication);\n    }\n\n    void started(\n        final long recordingId,\n        final long startPosition,\n        final int sessionId,\n        final int streamId,\n        final String channel,\n        final String sourceIdentity)\n    {\n        recordingStartedEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .recordingId(recordingId)\n            .startPosition(startPosition)\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .channel(channel)\n            .sourceIdentity(sourceIdentity);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + recordingStartedEncoder.encodedLength();\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.offer(buffer, 0, length);\n            if (position > 0 || Publication.NOT_CONNECTED == position)\n            {\n                break;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n    }\n\n    boolean progress(final long recordingId, final long startPosition, final long position)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + RecordingProgressEncoder.BLOCK_LENGTH;\n        final long result = publication.tryClaim(length, bufferClaim);\n\n        if (result > 0)\n        {\n            recordingProgressEncoder\n                .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                .recordingId(recordingId)\n                .startPosition(startPosition)\n                .position(position);\n\n            bufferClaim.commit();\n            return true;\n        }\n        else\n        {\n            checkResult(result, publication);\n        }\n\n        return false;\n    }\n\n    void stopped(final long recordingId, final long startPosition, final long stopPosition)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + RecordingStoppedEncoder.BLOCK_LENGTH;\n        int attempts = SEND_ATTEMPTS;\n\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                recordingStoppedEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .recordingId(recordingId)\n                    .startPosition(startPosition)\n                    .stopPosition(stopPosition);\n\n                bufferClaim.commit();\n                break;\n            }\n            else if (Publication.NOT_CONNECTED == position)\n            {\n                break;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n    }\n\n    private static void checkResult(final long position, final Publication publication)\n    {\n        if (position == Publication.CLOSED)\n        {\n            throw new ArchiveException(\"recording events publication is closed\");\n        }\n\n        if (position == Publication.MAX_POSITION_EXCEEDED)\n        {\n            throw new ArchiveException(\n                \"recording events publication at max position, term-length=\" + publication.termBufferLength());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/RecordingReader.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.StandardOpenOption;\nimport java.util.EnumSet;\n\nimport static io.aeron.archive.Archive.segmentFileName;\nimport static io.aeron.archive.client.AeronArchive.*;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.protocol.DataHeaderFlyweight.RESERVED_VALUE_OFFSET;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.nio.channels.FileChannel.MapMode.READ_ONLY;\nimport static java.nio.file.StandardOpenOption.READ;\n\nclass RecordingReader implements AutoCloseable\n{\n    private static final EnumSet<StandardOpenOption> FILE_OPTIONS = EnumSet.of(READ);\n\n    private final File archiveDir;\n    private final long recordingId;\n    private final int segmentLength;\n    private final int termLength;\n\n    private final UnsafeBuffer termBuffer;\n    private MappedByteBuffer mappedSegmentBuffer;\n\n    private final long replayLimit;\n    private long replayPosition;\n    private long segmentFilePosition;\n    private int termOffset;\n    private int termBaseSegmentOffset;\n    private boolean isDone = false;\n\n    RecordingReader(\n        final RecordingSummary recordingSummary, final File archiveDir, final long position, final long length)\n    {\n        if (position < NULL_POSITION)\n        {\n            throw new IllegalArgumentException(\"invalid position: \" + position);\n        }\n\n        if (length < NULL_LENGTH)\n        {\n            throw new IllegalArgumentException(\"invalid length: \" + length);\n        }\n\n        this.archiveDir = archiveDir;\n        this.termLength = recordingSummary.termBufferLength;\n        this.segmentLength = recordingSummary.segmentFileLength;\n        this.recordingId = recordingSummary.recordingId;\n\n        final long startPosition = recordingSummary.startPosition;\n        final long fromPosition = position == NULL_POSITION ? startPosition : position;\n        final long maxLength = recordingSummary.stopPosition != NULL_POSITION ?\n            recordingSummary.stopPosition - fromPosition : Long.MAX_VALUE - fromPosition;\n\n        final long replayLength = length == NULL_LENGTH ? maxLength : Math.min(length, maxLength);\n        if (replayLength < 0)\n        {\n            throw new IllegalArgumentException(\"length must be positive\");\n        }\n\n        final int positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n        final long startTermBasePosition = startPosition - (startPosition & (termLength - 1));\n        final int segmentOffset = (int)(fromPosition - startTermBasePosition) & (segmentLength - 1);\n        final int termId = ((int)(fromPosition >> positionBitsToShift) + recordingSummary.initialTermId);\n\n        segmentFilePosition = segmentFileBasePosition(startPosition, fromPosition, termLength, segmentLength);\n        openRecordingSegment();\n\n        termOffset = (int)(fromPosition & (termLength - 1));\n        termBaseSegmentOffset = segmentOffset - termOffset;\n        termBuffer = new UnsafeBuffer(mappedSegmentBuffer, termBaseSegmentOffset, termLength);\n\n        if (fromPosition > startPosition &&\n            (DataHeaderFlyweight.termOffset(termBuffer, termOffset) != termOffset ||\n            DataHeaderFlyweight.termId(termBuffer, termOffset) != termId ||\n            DataHeaderFlyweight.streamId(termBuffer, termOffset) != recordingSummary.streamId))\n        {\n            close();\n            throw new IllegalArgumentException(fromPosition + \" position not aligned to valid fragment\");\n        }\n\n        replayPosition = fromPosition;\n        replayLimit = fromPosition + replayLength;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        closeRecordingSegment();\n    }\n\n    long replayPosition()\n    {\n        return replayPosition;\n    }\n\n    boolean isDone()\n    {\n        return isDone;\n    }\n\n    int poll(final SimpleFragmentHandler fragmentHandler, final int fragmentLimit)\n    {\n        int fragments = 0;\n\n        while (replayPosition < replayLimit && fragments < fragmentLimit)\n        {\n            if (termOffset == termLength)\n            {\n                nextTerm();\n            }\n\n            final int frameOffset = termOffset;\n            final UnsafeBuffer termBuffer = this.termBuffer;\n            final int frameLength = FrameDescriptor.frameLength(termBuffer, frameOffset);\n            if (frameLength <= 0)\n            {\n                isDone = true;\n                closeRecordingSegment();\n                break;\n            }\n\n            final int frameType = FrameDescriptor.frameType(termBuffer, frameOffset);\n            final byte flags = FrameDescriptor.frameFlags(termBuffer, frameOffset);\n            final long reservedValue = termBuffer.getLong(frameOffset + RESERVED_VALUE_OFFSET, LITTLE_ENDIAN);\n\n            final int alignedLength = BitUtil.align(frameLength, FRAME_ALIGNMENT);\n            final int dataOffset = frameOffset + DataHeaderFlyweight.HEADER_LENGTH;\n            final int dataLength = frameLength - DataHeaderFlyweight.HEADER_LENGTH;\n\n            fragmentHandler.onFragment(termBuffer, dataOffset, dataLength, frameType, flags, reservedValue);\n\n            replayPosition += alignedLength;\n            termOffset += alignedLength;\n            fragments++;\n\n            if (replayPosition >= replayLimit)\n            {\n                isDone = true;\n                closeRecordingSegment();\n                break;\n            }\n        }\n\n        return fragments;\n    }\n\n    private void nextTerm()\n    {\n        termOffset = 0;\n        termBaseSegmentOffset += termLength;\n\n        if (termBaseSegmentOffset == segmentLength)\n        {\n            closeRecordingSegment();\n            segmentFilePosition += segmentLength;\n            openRecordingSegment();\n            termBaseSegmentOffset = 0;\n        }\n\n        termBuffer.wrap(mappedSegmentBuffer, termBaseSegmentOffset, termLength);\n    }\n\n    private void closeRecordingSegment()\n    {\n        final MappedByteBuffer mappedSegmentBuffer = this.mappedSegmentBuffer;\n        this.mappedSegmentBuffer = null;\n        BufferUtil.free(mappedSegmentBuffer);\n    }\n\n    private void openRecordingSegment()\n    {\n        final String segmentFileName = segmentFileName(recordingId, segmentFilePosition);\n        final File segmentFile = new File(archiveDir, segmentFileName);\n\n        if (!segmentFile.exists())\n        {\n            throw new IllegalArgumentException(\"failed to open recording segment file \" + segmentFileName);\n        }\n\n        try (FileChannel channel = FileChannel.open(segmentFile.toPath(), FILE_OPTIONS))\n        {\n            mappedSegmentBuffer = channel.map(READ_ONLY, 0, segmentLength);\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/RecordingSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.*;\nimport io.aeron.archive.client.ArchiveException;\nimport org.agrona.CloseHelper;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.CountedErrorHandler;\n\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\n\n/**\n * Consumes an {@link Image} and records data to file using a {@link RecordingWriter}.\n */\nclass RecordingSession implements Session\n{\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        INIT, RECORDING, INACTIVE, STOPPED\n    }\n\n    private final boolean isAutoStop;\n    private volatile boolean isAborted = false;\n    private final long correlationId;\n    private final long recordingId;\n    private long progressEventPosition;\n    private final int blockLengthLimit;\n    private final RecordingEventsProxy recordingEventsProxy;\n    private final Image image;\n    private final Counter position;\n    private final RecordingWriter recordingWriter;\n    private final String originalChannel;\n    private final ControlSession controlSession;\n    private final CountedErrorHandler countedErrorHandler;\n    private State state = State.INIT;\n    private String errorMessage;\n    private String abortReason;\n    private int errorCode = ArchiveException.GENERIC;\n\n    RecordingSession(\n        final long correlationId,\n        final long recordingId,\n        final long startPosition,\n        final int segmentLength,\n        final String originalChannel,\n        final RecordingEventsProxy recordingEventsProxy,\n        final Image image,\n        final Counter position,\n        final Archive.Context ctx,\n        final ControlSession controlSession,\n        final boolean isAutoStop,\n        final ArchiveConductor.Recorder recorder)\n    {\n        this.correlationId = correlationId;\n        this.recordingId = recordingId;\n        this.originalChannel = originalChannel;\n        this.recordingEventsProxy = recordingEventsProxy;\n        this.image = image;\n        this.position = position;\n        this.controlSession = controlSession;\n        this.isAutoStop = isAutoStop;\n        this.countedErrorHandler = ctx.countedErrorHandler();\n        this.progressEventPosition = image.joinPosition();\n\n        blockLengthLimit = Math.min(image.termBufferLength(), ctx.fileIoMaxLength());\n        recordingWriter = new RecordingWriter(recordingId, startPosition, segmentLength, image, ctx, recorder);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return recordingId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return state == State.STOPPED;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n        abortReason = reason;\n        isAborted = true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        recordingWriter.close();\n        CloseHelper.close(countedErrorHandler, position);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n\n        if (isAborted)\n        {\n            state(State.INACTIVE, abortReason);\n        }\n\n        if (State.INIT == state)\n        {\n            workCount += init();\n        }\n\n        if (State.RECORDING == state)\n        {\n            workCount += record();\n        }\n\n        if (State.INACTIVE == state)\n        {\n            state(State.STOPPED, \"\");\n            recordingWriter.close();\n            workCount++;\n\n            if (null != recordingEventsProxy)\n            {\n                recordingEventsProxy.stopped(recordingId, image.joinPosition(), position.getPlain());\n            }\n        }\n\n        return workCount;\n    }\n\n    void abortClose()\n    {\n        recordingWriter.close();\n    }\n\n    long correlationId()\n    {\n        return correlationId;\n    }\n\n    Counter recordingPosition()\n    {\n        return position;\n    }\n\n    long recordedPosition()\n    {\n        if (position.isClosed())\n        {\n            return NULL_POSITION;\n        }\n\n        return position.get();\n    }\n\n    Subscription subscription()\n    {\n        return image.subscription();\n    }\n\n    ControlSession controlSession()\n    {\n        return controlSession;\n    }\n\n    boolean isAutoStop()\n    {\n        return isAutoStop;\n    }\n\n    void sendPendingError()\n    {\n        if (null != errorMessage)\n        {\n            controlSession.sendErrorResponse(correlationId, errorCode, errorMessage);\n        }\n    }\n\n    private int init()\n    {\n        try\n        {\n            recordingWriter.init();\n        }\n        catch (final Exception ex)\n        {\n            errorMessage = ex.getClass().getName() + \": \" + ex.getMessage();\n            recordingWriter.close();\n            state(State.STOPPED, errorMessage);\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        if (null != recordingEventsProxy)\n        {\n            recordingEventsProxy.started(\n                recordingId,\n                image.joinPosition(),\n                image.sessionId(),\n                image.subscription().streamId(),\n                originalChannel,\n                image.sourceIdentity());\n        }\n        state(State.RECORDING, \"\");\n\n        return 1;\n    }\n\n    private int record()\n    {\n        try\n        {\n            final int workCount = image.blockPoll(recordingWriter, blockLengthLimit);\n            if (workCount > 0)\n            {\n                this.position.setRelease(recordingWriter.position());\n            }\n            else if (image.isEndOfStream() || image.isClosed())\n            {\n                state(\n                    State.INACTIVE,\n                    \"image.isEndOfStream=\" + image.isEndOfStream() + \", image.isClosed=\" + image.isClosed());\n            }\n\n            if (null != recordingEventsProxy)\n            {\n                final long recordedPosition = recordingWriter.position();\n                if (progressEventPosition < recordedPosition)\n                {\n                    if (recordingEventsProxy.progress(recordingId, image.joinPosition(), recordedPosition))\n                    {\n                        progressEventPosition = recordedPosition;\n                    }\n                }\n            }\n\n            return workCount;\n        }\n        catch (final ArchiveException ex)\n        {\n            countedErrorHandler.onError(ex);\n            errorMessage = ex.getMessage();\n            errorCode = ex.errorCode();\n            state(State.INACTIVE, errorMessage);\n        }\n        catch (final Exception ex)\n        {\n            countedErrorHandler.onError(ex);\n            errorMessage = ex.getClass().getName() + \": \" + ex.getMessage();\n            state(State.INACTIVE, errorMessage);\n        }\n\n        return 1;\n    }\n\n    private void state(final State newState, final String reason)\n    {\n        logStateChange(\n            state,\n            newState,\n            recordingId,\n            null != image ? image.position() : NULL_POSITION,\n            null == reason ? \"\" : reason);\n        state = newState;\n    }\n\n    @SuppressWarnings(\"unused\")\n    private void logStateChange(\n        final State oldState,\n        final State newState,\n        final long recordingId,\n        final long position,\n        final String reason)\n    {\n        //System.out.println(\"RecordingSession: \" + state + \" -> \" + newState);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/RecordingSummary.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\n/**\n * Summary of the fixed length fields from a {@link io.aeron.archive.client.RecordingDescriptorConsumer}.\n */\npublic class RecordingSummary\n{\n    /**\n     * Unique identity of a recording.\n     */\n    public long recordingId;\n\n    /**\n     * Start position of a recording captured by the archive.\n     */\n    public long startPosition;\n\n    /**\n     * Stop position of a recording. This can be {@link io.aeron.archive.client.AeronArchive#NULL_POSITION} if active.\n     */\n    public long stopPosition;\n\n    /**\n     * The initial-term-id for the recorded stream.\n     */\n    public int initialTermId;\n\n    /**\n     * Length of the segment files for the stream which are a multiple of term-length.\n     */\n    public int segmentFileLength;\n\n    /**\n     * The term-length for the recorded stream.\n     */\n    public int termBufferLength;\n\n    /**\n     * UDP datagram length, or MTU, of the recording. Beyond this value messages are fragmented.\n     */\n    public int mtuLength;\n\n    /**\n     * The session-id of the recorded stream which is updated if the recording is extended.\n     */\n    public int sessionId;\n\n    /**\n     * The stream-id of the recorded stream.\n     */\n    public int streamId;\n\n    /**\n     * Default constructor.\n     */\n    public RecordingSummary()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"RecordingSummary{\" +\n            \"recordingId=\" + recordingId +\n            \", startPosition=\" + startPosition +\n            \", stopPosition=\" + stopPosition +\n            \", initialTermId=\" + initialTermId +\n            \", segmentFileLength=\" + segmentFileLength +\n            \", termBufferLength=\" + termBufferLength +\n            \", mtuLength=\" + mtuLength +\n            \", sessionId=\" + sessionId +\n            \", streamId=\" + streamId +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/RecordingWriter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Image;\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.exceptions.StorageSpaceException;\nimport io.aeron.logbuffer.BlockHandler;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ClosedByInterruptException;\nimport java.nio.channels.FileChannel;\n\nimport static io.aeron.archive.client.AeronArchive.segmentFileBasePosition;\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.agrona.BitUtil.align;\n\n/**\n * Responsible for writing out a recording into the file system. A recording has descriptor file and a set of data files\n * written into the archive folder.\n * <p>\n * <b>Design note:</b> While this class is notionally closely related to the {@link RecordingSession} it is separated\n * from it for the following reasons:\n * <ul>\n * <li>Easier testing and in particular simplified re-use in testing.</li>\n * <li>Isolation of an external relationship, namely the file system.</li>\n * </ul>\n */\nfinal class RecordingWriter implements BlockHandler, AutoCloseable\n{\n    private final long recordingId;\n    private final int segmentLength;\n    private final boolean forceWrites;\n    private final boolean forceMetadata;\n    private final UnsafeBuffer checksumBuffer;\n    private final Checksum checksum;\n    private final FileChannel archiveDirChannel;\n    private final File archiveDir;\n    private final CountedErrorHandler countedErrorHandler;\n    private final NanoClock nanoClock;\n    private final Archive.Context ctx;\n\n    private final ArchiveConductor.Recorder recorder;\n\n    private long segmentBasePosition;\n    private int segmentOffset;\n    private FileChannel recordingFileChannel;\n\n    private boolean isClosed = false;\n\n    RecordingWriter(\n        final long recordingId,\n        final long startPosition,\n        final int segmentLength,\n        final Image image,\n        final Archive.Context ctx,\n        final ArchiveConductor.Recorder recorder)\n    {\n        this.recordingId = recordingId;\n        this.archiveDirChannel = ctx.archiveDirChannel();\n        this.segmentLength = segmentLength;\n\n        archiveDir = ctx.archiveDir();\n        forceWrites = ctx.fileSyncLevel() > 0;\n        forceMetadata = ctx.fileSyncLevel() > 1;\n\n        countedErrorHandler = ctx.countedErrorHandler();\n        checksumBuffer = ctx.recordChecksumBuffer();\n        checksum = ctx.recordChecksum();\n        nanoClock = ctx.nanoClock();\n        this.ctx = ctx;\n        this.recorder = recorder;\n\n        final int termLength = image.termBufferLength();\n        final long joinPosition = image.joinPosition();\n        segmentBasePosition = segmentFileBasePosition(startPosition, joinPosition, termLength, segmentLength);\n        segmentOffset = (int)(joinPosition - segmentBasePosition);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onBlock(\n        final DirectBuffer termBuffer, final int termOffset, final int length, final int sessionId, final int termId)\n    {\n        try\n        {\n            final boolean isPaddingFrame = termBuffer.getShort(typeOffset(termOffset)) == PADDING_FRAME_TYPE;\n            final int dataLength = isPaddingFrame ? HEADER_LENGTH : length;\n            final ByteBuffer byteBuffer;\n\n            final long startNs = nanoClock.nanoTime();\n            if (null == checksum || isPaddingFrame)\n            {\n                byteBuffer = termBuffer.byteBuffer();\n                byteBuffer.limit(termOffset + dataLength).position(termOffset);\n            }\n            else\n            {\n                checksumBuffer.putBytes(0, termBuffer, termOffset, dataLength);\n                computeChecksum(checksum, checksumBuffer, dataLength);\n                byteBuffer = checksumBuffer.byteBuffer();\n                byteBuffer.limit(dataLength).position(0);\n            }\n\n            int fileOffset = segmentOffset;\n            do\n            {\n                fileOffset += recordingFileChannel.write(byteBuffer, fileOffset);\n            }\n            while (byteBuffer.remaining() > 0);\n\n            if (forceWrites)\n            {\n                recordingFileChannel.force(forceMetadata);\n            }\n\n            final long writeTimeNs = nanoClock.nanoTime() - startNs;\n            recorder.bytesWritten(dataLength);\n            recorder.writeTimeNs(writeTimeNs);\n\n            segmentOffset += length;\n            if (segmentOffset >= segmentLength)\n            {\n                onFileRollOver();\n            }\n        }\n        catch (final ClosedByInterruptException ex)\n        {\n            close();\n            throw new ArchiveException(\"file closed by interrupt, recording aborted\", ex, ArchiveException.GENERIC);\n        }\n        catch (final IOException ex)\n        {\n            close();\n            checkErrorType(ex, length);\n        }\n        catch (final Exception ex)\n        {\n            close();\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (!isClosed)\n        {\n            isClosed = true;\n            CloseHelper.close(countedErrorHandler, recordingFileChannel);\n        }\n    }\n\n    long position()\n    {\n        return segmentBasePosition + segmentOffset;\n    }\n\n    void init() throws IOException\n    {\n        openRecordingSegmentFile(new File(archiveDir, Archive.segmentFileName(recordingId, segmentBasePosition)));\n\n        if (segmentOffset != 0)\n        {\n            recordingFileChannel.position(segmentOffset);\n        }\n    }\n\n    private void computeChecksum(final Checksum checksum, final UnsafeBuffer buffer, final int length)\n    {\n        final long address = buffer.addressOffset();\n        int frameOffset = 0;\n\n        while (frameOffset < length)\n        {\n            final int alignedLength = align(frameLength(buffer, frameOffset), FRAME_ALIGNMENT);\n            final int computedChecksum = checksum.compute(\n                address, frameOffset + HEADER_LENGTH, alignedLength - HEADER_LENGTH);\n            frameSessionId(buffer, frameOffset, computedChecksum);\n            frameOffset += alignedLength;\n        }\n    }\n\n    private void openRecordingSegmentFile(final File segmentFile)\n    {\n        RandomAccessFile recordingFile = null;\n        try\n        {\n            recordingFile = new RandomAccessFile(segmentFile, \"rw\");\n            recordingFile.setLength(segmentLength);\n            recordingFileChannel = recordingFile.getChannel();\n            if (forceWrites && null != archiveDirChannel)\n            {\n                archiveDirChannel.force(forceMetadata);\n            }\n        }\n        catch (final IOException ex)\n        {\n            CloseHelper.close(recordingFile);\n            close();\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    private void onFileRollOver()\n    {\n        CloseHelper.close(recordingFileChannel);\n        segmentOffset = 0;\n        segmentBasePosition += segmentLength;\n\n        final File file = new File(archiveDir, Archive.segmentFileName(recordingId, segmentBasePosition));\n        if (file.exists())\n        {\n            throw new ArchiveException(\"segment file already exists: \" + file);\n        }\n\n        openRecordingSegmentFile(file);\n    }\n\n    private void checkErrorType(final IOException ex, final int writeLength)\n    {\n        boolean isLowStorageSpace = false;\n        IOException suppressed = null;\n\n        try\n        {\n            isLowStorageSpace = StorageSpaceException.isStorageSpaceError(ex) ||\n                ctx.archiveFileStore().getUsableSpace() < writeLength;\n        }\n        catch (final IOException ex2)\n        {\n            suppressed = ex2;\n        }\n\n        final int errorCode = isLowStorageSpace ? ArchiveException.STORAGE_SPACE : ArchiveException.GENERIC;\n        final ArchiveException error = new ArchiveException(\"java.io.IOException - \" + ex.getMessage(), ex, errorCode);\n\n        if (null != suppressed)\n        {\n            error.addSuppressed(suppressed);\n        }\n\n        throw error;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ReplaySession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Publication;\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.StandardOpenOption;\nimport java.util.EnumSet;\n\nimport static io.aeron.archive.Archive.segmentFileName;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.frameLength;\nimport static io.aeron.logbuffer.FrameDescriptor.frameSessionId;\nimport static io.aeron.logbuffer.FrameDescriptor.frameType;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_DATA;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_PAD;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.DataHeaderFlyweight.SESSION_ID_FIELD_OFFSET;\nimport static io.aeron.protocol.DataHeaderFlyweight.STREAM_ID_FIELD_OFFSET;\nimport static io.aeron.protocol.DataHeaderFlyweight.streamId;\nimport static io.aeron.protocol.DataHeaderFlyweight.termId;\nimport static io.aeron.protocol.DataHeaderFlyweight.termOffset;\nimport static java.lang.Math.min;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.nio.file.StandardOpenOption.READ;\nimport static org.agrona.BitUtil.align;\n\n/**\n * A replay session with a client which works through the required request response flow and streaming of recorded data.\n * The {@link ArchiveConductor} will initiate a session on receiving a ReplayRequest\n * (see {@link io.aeron.archive.codecs.ReplayRequestDecoder}).\n * <p>\n * The session will:\n * <ul>\n * <li>Validate request parameters and respond with appropriate error if unable to replay.</li>\n * <li>Wait for replay publication to connect to the subscriber. If no subscriber appears within\n * {@link Archive.Configuration#CONNECT_TIMEOUT_PROP_NAME} the session will terminate and respond with an error.</li>\n * <li>Once the replay publication is connected an OK response to control client will be sent.</li>\n * <li>Stream recorded data into the publication {@link ExclusivePublication}.</li>\n * <li>If the replay is aborted part way through, send a ReplayAborted message and terminate.</li>\n * </ul>\n */\nclass ReplaySession implements Session, AutoCloseable\n{\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        INIT, REPLAY, INACTIVE, DONE\n    }\n\n    private static final EnumSet<StandardOpenOption> FILE_OPTIONS = EnumSet.of(READ);\n\n    private final long connectDeadlineMs;\n    private final long correlationId;\n    private final long sessionId;\n    private final long recordingId;\n    private final long startPosition;\n    private long replayPosition;\n    private long stopPosition;\n    private long replayLimit;\n    private long segmentFileBasePosition;\n    private int termBaseSegmentOffset;\n    private int termOffset;\n    private final int streamId;\n    private final int termLength;\n    private final int segmentLength;\n\n    private final long replayBufferAddress;\n    private final Checksum checksum;\n\n    private final ExclusivePublication publication;\n    private final ControlSession controlSession;\n    private final CachedEpochClock epochClock;\n\n    private final NanoClock nanoClock;\n    final ArchiveConductor.Replayer replayer;\n    private final File archiveDir;\n    private final CountersReader countersReader;\n    private final Counter limitPosition;\n    private final UnsafeBuffer replayBuffer;\n    private FileChannel fileChannel;\n    private File segmentFile;\n    private State state = State.INIT;\n    private String errorMessage = null;\n    private boolean revokePublication;\n    private volatile boolean isAborted;\n\n    ReplaySession(\n        final long correlationId,\n        final long recordingId,\n        final long replayPosition,\n        final long replayLength,\n        final long startPosition,\n        final long stopPosition,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int streamId,\n        final long replaySessionId,\n        final long connectTimeoutMs,\n        final ControlSession controlSession,\n        final UnsafeBuffer replayBuffer,\n        final File archiveDir,\n        final CachedEpochClock epochClock,\n        final NanoClock nanoClock,\n        final ExclusivePublication publication,\n        final CountersReader countersReader,\n        final Counter replayLimitPosition,\n        final Checksum checksum,\n        final ArchiveConductor.Replayer replayer)\n    {\n        this.controlSession = controlSession;\n        this.sessionId = replaySessionId;\n        this.correlationId = correlationId;\n        this.recordingId = recordingId;\n        this.segmentLength = segmentFileLength;\n        this.termLength = termBufferLength;\n        this.streamId = streamId;\n        this.epochClock = epochClock;\n        this.nanoClock = nanoClock;\n        this.archiveDir = archiveDir;\n        this.publication = publication;\n        this.countersReader = countersReader;\n        this.limitPosition = replayLimitPosition;\n        this.replayBuffer = replayBuffer;\n        this.replayBufferAddress = replayBuffer.addressOffset();\n        this.checksum = checksum;\n        this.startPosition = startPosition;\n        this.stopPosition = stopPosition;\n        this.replayer = replayer;\n\n        segmentFileBasePosition = AeronArchive.segmentFileBasePosition(\n            startPosition, replayPosition, termLength, segmentLength);\n        this.replayPosition = replayPosition;\n        replayLimit = replayPosition + replayLength;\n\n        segmentFile = new File(archiveDir, segmentFileName(recordingId, segmentFileBasePosition));\n        connectDeadlineMs = epochClock.time() + connectTimeoutMs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        final CountedErrorHandler errorHandler = controlSession.archiveConductor().context().countedErrorHandler();\n        if (revokePublication)\n        {\n            try\n            {\n                publication.revoke();\n            }\n            catch (final Exception ex)\n            {\n                errorHandler.onError(ex);\n            }\n        }\n        else\n        {\n            CloseHelper.close(errorHandler, publication);\n        }\n        CloseHelper.close(errorHandler, fileChannel);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return sessionId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n\n        if (isAborted)\n        {\n            revokePublication = true;\n            state(State.INACTIVE, \"replay aborted\");\n        }\n\n        try\n        {\n            if (State.INIT == state)\n            {\n                workCount += init();\n            }\n\n            if (State.REPLAY == state)\n            {\n                workCount += replay();\n            }\n        }\n        catch (final IOException ex)\n        {\n            raiseError(ex.toString(), ex);\n        }\n\n        if (State.INACTIVE == state)\n        {\n            closeRecordingSegment();\n            state(State.DONE, \"\");\n        }\n\n        return workCount;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n        isAborted = true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return state == State.DONE;\n    }\n\n    long recordingId()\n    {\n        return recordingId;\n    }\n\n    State state()\n    {\n        return state;\n    }\n\n    String replayChannel()\n    {\n        return publication.channel();\n    }\n\n    int replayStreamId()\n    {\n        return publication.streamId();\n    }\n\n    long segmentFileBasePosition()\n    {\n        return segmentFileBasePosition;\n    }\n\n    void sendPendingError()\n    {\n        if (null != errorMessage)\n        {\n            onPendingError(sessionId, recordingId, errorMessage);\n            controlSession.sendErrorResponse(correlationId, ArchiveException.GENERIC, errorMessage);\n        }\n    }\n\n    @SuppressWarnings(\"unused\")\n    void onPendingError(final long sessionId, final long recordingId, final String errorMessage)\n    {\n        // Hook for Agent logging\n    }\n\n    private int init() throws IOException\n    {\n        if (null == fileChannel)\n        {\n            if (!segmentFile.exists())\n            {\n                if (epochClock.time() > connectDeadlineMs)\n                {\n                    raiseError(\"recording segment file not created\", null);\n                }\n\n                return 0;\n            }\n            else\n            {\n                final long startTermBasePosition = startPosition - (startPosition & (termLength - 1));\n                final int segmentOffset = (int)((replayPosition - startTermBasePosition) & (segmentLength - 1));\n                final int termId = LogBufferDescriptor.computeTermIdFromPosition(\n                    replayPosition, publication.positionBitsToShift(), publication.initialTermId());\n\n                openRecordingSegment();\n\n                termOffset = (int)(replayPosition & (termLength - 1));\n                termBaseSegmentOffset = segmentOffset - termOffset;\n\n                if (replayPosition > startPosition && replayPosition != stopPosition)\n                {\n                    if (notHeaderAligned(fileChannel, replayBuffer, segmentOffset, termOffset, termId, streamId))\n                    {\n                        raiseError(\"replayPosition=\" + framePosition(0) + \" does not point to a valid frame\", null);\n                        return 0;\n                    }\n                }\n\n                controlSession.asyncSendOkResponse(correlationId, sessionId);\n            }\n        }\n\n        if (!publication.isConnected())\n        {\n            if (epochClock.time() > connectDeadlineMs)\n            {\n                raiseError(\"no connection established for replayChannel=\" + publication.channel() +\n                    \", replayStreamId=\" + publication.streamId(), null);\n            }\n\n            return 0;\n        }\n\n        state(State.REPLAY, \"\");\n\n        return 1;\n    }\n\n    @SuppressWarnings(\"methodlength\")\n    private int replay() throws IOException\n    {\n        if (!publication.isConnected())\n        {\n            revokePublication = true;\n            state(State.INACTIVE, \"publication is not connected\");\n            return 0;\n        }\n\n        if (startPosition == stopPosition && 0 == replayLimit)\n        {\n            state(State.INACTIVE, \"empty replay\");\n            return 0;\n        }\n\n        if (null != limitPosition && replayPosition >= stopPosition && notExtended(replayPosition, stopPosition))\n        {\n            return 0;\n        }\n\n        if (termOffset == termLength)\n        {\n            nextTerm();\n        }\n\n        int workCount = 0;\n        if (publication.availableWindow() > 0)\n        {\n            final long startNs = nanoClock.nanoTime();\n            final int bytesRead = readRecording(stopPosition - replayPosition);\n            if (bytesRead > 0)\n            {\n                final int sessionId = publication.sessionId();\n                final int streamId = publication.streamId();\n                final int remaining = (int)Math.min(replayLimit - replayPosition, LogBufferDescriptor.TERM_MAX_LENGTH);\n                int batchOffset = 0;\n                int paddingFrameLength = 0;\n\n                while (batchOffset < bytesRead && batchOffset < remaining)\n                {\n                    final int frameLength = frameLength(replayBuffer, batchOffset);\n                    if (frameLength <= 0)\n                    {\n                        raiseError(\"unexpected end of recording at position=\" + framePosition(batchOffset), null);\n                    }\n\n                    final int frameType = frameType(replayBuffer, batchOffset);\n                    final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n\n                    if (HDR_TYPE_DATA == frameType)\n                    {\n                        if (batchOffset + alignedLength > bytesRead)\n                        {\n                            break;\n                        }\n\n                        if (null != checksum)\n                        {\n                            verifyChecksum(checksum, batchOffset, alignedLength);\n                        }\n\n                        replayBuffer.putInt(batchOffset + SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n                        replayBuffer.putInt(batchOffset + STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n                        batchOffset += alignedLength;\n                    }\n                    else if (HDR_TYPE_PAD == frameType)\n                    {\n                        paddingFrameLength = frameLength;\n                        break;\n                    }\n                }\n\n                final long readTimeNs = nanoClock.nanoTime() - startNs;\n                replayer.bytesRead(bytesRead);\n                replayer.readTimeNs(readTimeNs);\n\n                if (batchOffset > 0)\n                {\n                    final long position = publication.offerBlock(replayBuffer, 0, batchOffset);\n                    if (hasPublicationAdvanced(position, batchOffset))\n                    {\n                        workCount++;\n                    }\n                    else\n                    {\n                        paddingFrameLength = 0;\n                    }\n                }\n\n                if (paddingFrameLength > 0)\n                {\n                    final long position = publication.appendPadding(paddingFrameLength - HEADER_LENGTH);\n                    if (hasPublicationAdvanced(position, align(paddingFrameLength, FRAME_ALIGNMENT)))\n                    {\n                        workCount++;\n                    }\n                }\n            }\n        }\n\n        return workCount;\n    }\n\n    private String framePosition(final int frameOffset)\n    {\n        final long pos = segmentFileBasePosition + termBaseSegmentOffset + termOffset + frameOffset;\n        return pos +\n            \" (segmentFilePosition=\" + segmentFileBasePosition +\n            \", segmentOffset=\" + termBaseSegmentOffset +\n            \", termOffset=\" + termOffset +\n            \", frameOffset=\" + frameOffset + \")\";\n    }\n\n    private boolean hasPublicationAdvanced(final long position, final int alignedLength)\n    {\n        if (position > 0)\n        {\n            termOffset += alignedLength;\n            replayPosition += alignedLength;\n\n            if (replayPosition >= replayLimit)\n            {\n                state(State.INACTIVE, \"position (\" + replayPosition + \") past limit (\" + replayLimit + \")\");\n            }\n\n            return true;\n        }\n        else if (Publication.CLOSED == position || Publication.NOT_CONNECTED == position)\n        {\n            revokePublication = true;\n            state(State.INACTIVE, \"stream closed before replay complete\");\n        }\n\n        return false;\n    }\n\n    private void verifyChecksum(final Checksum checksum, final int frameOffset, final int alignedLength)\n    {\n        final int computedChecksum = checksum.compute(\n            replayBufferAddress, frameOffset + HEADER_LENGTH, alignedLength - HEADER_LENGTH);\n        final int recordedChecksum = frameSessionId(replayBuffer, frameOffset);\n\n        if (computedChecksum != recordedChecksum)\n        {\n            final String message = \"CRC checksum mismatch at position=\" + framePosition(frameOffset) +\n                \": recorded checksum=\" + recordedChecksum + \", computed checksum=\" + computedChecksum;\n            raiseError(message, null);\n        }\n    }\n\n    private int readRecording(final long availableReplay) throws IOException\n    {\n        final int limit = min((int)min(availableReplay, replayBuffer.capacity()), termLength - termOffset);\n        final ByteBuffer byteBuffer = replayBuffer.byteBuffer();\n        byteBuffer.clear().limit(limit);\n\n        int position = termBaseSegmentOffset + termOffset;\n        do\n        {\n            final int bytesRead = fileChannel.read(byteBuffer, position);\n            if (bytesRead <= 0)\n            {\n                break;\n            }\n\n            position += bytesRead;\n        }\n        while (byteBuffer.remaining() > 0);\n\n        return limit;\n    }\n\n    private void raiseError(final String errorMessage, final Throwable cause)\n    {\n        revokePublication = true;\n        this.errorMessage = errorMessage +\n            \", recordingId=\" + recordingId +\n            \", replaySessionId=\" + sessionId +\n            \", segmentFile=\" + segmentFileName(recordingId, segmentFileBasePosition);\n        state(State.INACTIVE, errorMessage);\n        throw new ArchiveException(this.errorMessage, cause, ArchiveException.GENERIC);\n    }\n\n    private boolean notExtended(final long replayPosition, final long oldStopPosition)\n    {\n        final Counter limitPosition = this.limitPosition;\n        final long currentLimitPosition = limitPosition.get();\n        long newStopPosition = oldStopPosition;\n\n        if (limitPosition.isClosed())\n        {\n            if (countersReader.getCounterRegistrationId(limitPosition.id()) == limitPosition.registrationId())\n            {\n                replayLimit = currentLimitPosition;\n                newStopPosition = Math.max(oldStopPosition, currentLimitPosition);\n            }\n            else if (replayLimit >= oldStopPosition)\n            {\n                replayLimit = oldStopPosition;\n            }\n        }\n        else\n        {\n            newStopPosition = currentLimitPosition;\n        }\n\n        if (replayPosition >= replayLimit)\n        {\n            state(State.INACTIVE, \"position (\" + replayPosition + \") past limit (\" + replayLimit + \") (notExtended)\");\n        }\n        else if (newStopPosition > oldStopPosition)\n        {\n            stopPosition = newStopPosition;\n            return false;\n        }\n\n        return true;\n    }\n\n    private void nextTerm() throws IOException\n    {\n        termOffset = 0;\n        termBaseSegmentOffset += termLength;\n\n        if (termBaseSegmentOffset == segmentLength)\n        {\n            closeRecordingSegment();\n            segmentFileBasePosition += segmentLength;\n            openRecordingSegment();\n            termBaseSegmentOffset = 0;\n        }\n    }\n\n    private void closeRecordingSegment()\n    {\n        CloseHelper.close(fileChannel);\n        fileChannel = null;\n        segmentFile = null;\n    }\n\n    private void openRecordingSegment() throws IOException\n    {\n        if (null == segmentFile)\n        {\n            final String segmentFileName = segmentFileName(recordingId, segmentFileBasePosition);\n            segmentFile = new File(archiveDir, segmentFileName);\n\n            if (!segmentFile.exists())\n            {\n                raiseError(\"recording segment not found\", null);\n                return;\n            }\n        }\n\n        fileChannel = FileChannel.open(segmentFile.toPath(), FILE_OPTIONS);\n    }\n\n    static boolean notHeaderAligned(\n        final FileChannel channel,\n        final UnsafeBuffer buffer,\n        final int segmentOffset,\n        final int termOffset,\n        final int termId,\n        final int streamId) throws IOException\n    {\n        final ByteBuffer byteBuffer = buffer.byteBuffer();\n        byteBuffer.clear().limit(HEADER_LENGTH);\n        if (HEADER_LENGTH != channel.read(byteBuffer, segmentOffset))\n        {\n            throw new IOException(\"failed to read fragment header\");\n        }\n\n        return isInvalidHeader(buffer, streamId, termId, termOffset);\n    }\n\n    private void state(final State newState, final String reason)\n    {\n        logStateChange(state, newState, sessionId, recordingId, replayPosition, null == reason ? \"\" : reason);\n        state = newState;\n    }\n\n    @SuppressWarnings(\"unused\")\n    private void logStateChange(\n        final State oldState,\n        final State newState,\n        final long sessionId,\n        final long recordingId,\n        final long position,\n        final String reason)\n    {\n        //System.out.println(\"ReplaySession: \" + state + \" -> \" + newState);\n    }\n\n    static boolean isInvalidHeader(\n        final UnsafeBuffer buffer, final int streamId, final int termId, final int termOffset)\n    {\n        return\n            termOffset(buffer, 0) != termOffset ||\n            termId(buffer, 0) != termId ||\n            streamId(buffer, 0) != streamId;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ReplicationCredentialsSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.security.CredentialsSupplier;\n\nclass ReplicationCredentialsSupplier implements CredentialsSupplier\n{\n    private final byte[] encodedCredentials;\n\n    ReplicationCredentialsSupplier(final byte[] encodedCredentials)\n    {\n        this.encodedCredentials = encodedCredentials;\n    }\n\n    public byte[] encodedCredentials()\n    {\n        return encodedCredentials;\n    }\n\n    public byte[] onChallenge(final byte[] encodedChallenge)\n    {\n        throw new ArchiveException(\"Replication does not support challenge/response authentication\");\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/ReplicationSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.client.ArchiveProxy;\nimport io.aeron.archive.client.ControlResponsePoller;\nimport io.aeron.archive.client.RecordingDescriptorConsumer;\nimport io.aeron.archive.client.RecordingDescriptorPoller;\nimport io.aeron.archive.client.ReplayParams;\nimport io.aeron.archive.codecs.ControlResponseCode;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.TimeoutException;\nimport org.agrona.CloseHelper;\nimport org.agrona.Strings;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CountedErrorHandler;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.ReplayMerge.LIVE_ADD_MAX_WINDOW;\nimport static io.aeron.archive.codecs.RecordingSignal.MERGE;\nimport static io.aeron.archive.codecs.RecordingSignal.REPLICATE;\nimport static io.aeron.archive.codecs.RecordingSignal.REPLICATE_END;\nimport static io.aeron.archive.codecs.RecordingSignal.SYNC;\n\nclass ReplicationSession implements Session, RecordingDescriptorConsumer\n{\n    private static final int REPLAY_REMOVE_THRESHOLD = 0;\n    private static final int RETRY_ATTEMPTS = 3;\n    private static final int SOURCE_ARCHIVE_POLL_INTERVAL_MS = 100;\n    private final int replicationSessionId;\n\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        CONNECT,\n        REPLICATE_DESCRIPTOR,\n        SRC_RECORDING_POSITION,\n        EXTEND,\n        REPLAY_TOKEN,\n        GET_ARCHIVE_PROXY,\n        REPLAY,\n        AWAIT_IMAGE,\n        REPLICATE,\n        CATCHUP,\n        ATTEMPT_LIVE_JOIN,\n        DONE\n    }\n\n    private long activeCorrelationId = NULL_VALUE;\n    private long srcReplaySessionId = NULL_VALUE;\n    private long replayPosition = NULL_POSITION;\n    private long srcStopPosition = NULL_POSITION;\n    private long srcRecordingPosition = NULL_POSITION;\n    private final long dstStopPosition;\n    private final boolean isDestinationRecordingEmpty;\n    private long timeOfLastActionMs;\n    private final long actionTimeoutMs;\n    private final long replicationId;\n    private final long channelTagId;\n    private final long subscriptionTagId;\n    private final long srcRecordingId;\n    private long dstRecordingId;\n    private int replayStreamId;\n    private int replaySessionId;\n    private int retryAttempts = RETRY_ATTEMPTS;\n    private boolean isLiveAdded;\n    private final boolean isTagged;\n    private final String replicationChannel;\n    private final String liveDestination;\n    private String replayDestination;\n    private final CachedEpochClock epochClock;\n    private final ArchiveConductor conductor;\n    private final ControlSession controlSession;\n    private final Catalog catalog;\n    private final int fileIoMaxLength;\n    private final Aeron aeron;\n    private final AeronArchive.Context context;\n    private AeronArchive.AsyncConnect asyncConnect;\n    private AeronArchive srcArchive;\n    private Subscription recordingSubscription;\n    private Image image;\n    private State state = State.CONNECT;\n    private long replayToken = NULL_VALUE;\n    private long responsePublicationRegistrationId = NULL_VALUE;\n    private ExclusivePublication responsePublication = null;\n    private ArchiveProxy responseArchiveProxy = null;\n    private long timeOfLastScheduledSourceArchivePollMs;\n\n    ReplicationSession(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long channelTagId,\n        final long subscriptionTagId,\n        final long replicationId,\n        final long stopPosition,\n        final String liveDestination,\n        final String replicationChannel,\n        final int fileIoMaxLength,\n        final int replicationSessionId,\n        final RecordingSummary recordingSummary,\n        final AeronArchive.Context context,\n        final CachedEpochClock epochClock,\n        final Catalog catalog,\n        final ControlSession controlSession)\n    {\n        this.replicationId = replicationId;\n        this.srcRecordingId = srcRecordingId;\n        this.dstRecordingId = dstRecordingId;\n        this.liveDestination = Strings.isEmpty(liveDestination) ? null : liveDestination;\n        this.replicationChannel = replicationChannel;\n        this.fileIoMaxLength = fileIoMaxLength;\n        this.replicationSessionId = replicationSessionId;\n        this.aeron = context.aeron();\n        this.context = context;\n        this.catalog = catalog;\n        this.epochClock = epochClock;\n        this.conductor = controlSession.archiveConductor();\n        this.controlSession = controlSession;\n        this.actionTimeoutMs = TimeUnit.NANOSECONDS.toMillis(context.messageTimeoutNs());\n        this.dstStopPosition = stopPosition;\n\n        this.isTagged = NULL_VALUE != channelTagId || NULL_VALUE != subscriptionTagId;\n        this.channelTagId = NULL_VALUE == channelTagId ? replicationId : channelTagId;\n        this.subscriptionTagId = NULL_VALUE == subscriptionTagId ? replicationId : subscriptionTagId;\n\n        if (null != recordingSummary)\n        {\n            replayPosition = recordingSummary.stopPosition;\n            replayStreamId = recordingSummary.streamId;\n            isDestinationRecordingEmpty = recordingSummary.startPosition == recordingSummary.stopPosition;\n        }\n        else\n        {\n            isDestinationRecordingEmpty = false;\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return replicationId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return State.DONE == state;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n        this.state(State.DONE, \"abort\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        final ArchiveConductor archiveConductor = controlSession.archiveConductor();\n        final CountedErrorHandler countedErrorHandler = archiveConductor.context().countedErrorHandler();\n\n        stopRecording();\n        stopReplaySession(countedErrorHandler);\n\n        CloseHelper.close(countedErrorHandler, asyncConnect);\n        CloseHelper.close(countedErrorHandler, srcArchive);\n        CloseHelper.close(countedErrorHandler, responsePublication);\n\n        archiveConductor.removeReplicationSession(this);\n        signal(NULL_POSITION, REPLICATE_END);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n\n        try\n        {\n            if (null != recordingSubscription && recordingSubscription.isClosed())\n            {\n                state(State.DONE, \"recording subscription closed\");\n                return 1;\n            }\n\n            switch (state)\n            {\n                case CONNECT:\n                    workCount += connect();\n                    break;\n\n                case REPLICATE_DESCRIPTOR:\n                    workCount += replicateDescriptor();\n                    break;\n\n                case SRC_RECORDING_POSITION:\n                    workCount += srcRecordingPosition();\n                    break;\n\n                case EXTEND:\n                    workCount += extend();\n                    break;\n\n                case REPLAY_TOKEN:\n                    workCount += replayToken();\n                    break;\n\n                case GET_ARCHIVE_PROXY:\n                    workCount += getArchiveProxy();\n                    break;\n\n                case REPLAY:\n                    workCount += replay();\n                    break;\n\n                case AWAIT_IMAGE:\n                    workCount += awaitImage();\n                    break;\n\n                case REPLICATE:\n                    workCount += replicate();\n                    break;\n\n                case CATCHUP:\n                    workCount += catchup();\n                    break;\n\n                case ATTEMPT_LIVE_JOIN:\n                    workCount += attemptLiveJoin();\n                    break;\n\n                case DONE:\n                    break;\n            }\n\n            pollSourceArchiveEvents();\n        }\n        catch (final Exception ex)\n        {\n            state(State.DONE, ex.getMessage());\n            error(ex.getMessage(), ArchiveException.GENERIC);\n            throw ex;\n        }\n\n        return workCount;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onRecordingDescriptor(\n        final long controlSessionId,\n        final long correlationId,\n        final long recordingId,\n        final long startTimestamp,\n        final long stopTimestamp,\n        final long startPosition,\n        final long stopPosition,\n        final int initialTermId,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int mtuLength,\n        final int sessionId,\n        final int streamId,\n        final String strippedChannel,\n        final String originalChannel,\n        final String sourceIdentity)\n    {\n        srcStopPosition = stopPosition;\n        replayStreamId = streamId;\n        if (null == liveDestination && NULL_VALUE != replicationSessionId)\n        {\n            replaySessionId = replicationSessionId;\n        }\n        else\n        {\n            replaySessionId = sessionId;\n        }\n\n        if (Aeron.NULL_VALUE != fileIoMaxLength && fileIoMaxLength < mtuLength)\n        {\n            final String errorMsg = \"Replication fileIoMaxLength (\" + fileIoMaxLength +\n                \") is less than than the recording mtuLength (\" + mtuLength + \")\";\n            state(State.DONE, errorMsg);\n            error(errorMsg, ArchiveException.GENERIC);\n            return;\n        }\n\n        if (NULL_VALUE == dstRecordingId)\n        {\n            replayPosition = startPosition;\n            dstRecordingId = catalog.addNewRecording(\n                startPosition,\n                startPosition,\n                startTimestamp,\n                startTimestamp,\n                initialTermId,\n                segmentFileLength,\n                termBufferLength,\n                mtuLength,\n                sessionId,\n                streamId,\n                strippedChannel,\n                originalChannel,\n                sourceIdentity);\n\n            signal(startPosition, REPLICATE);\n        }\n        else if (isDestinationRecordingEmpty)\n        {\n            replayPosition = startPosition;\n            catalog.replaceRecording(\n                dstRecordingId,\n                startPosition,\n                startPosition,\n                startTimestamp,\n                startTimestamp,\n                initialTermId,\n                segmentFileLength,\n                termBufferLength,\n                mtuLength,\n                sessionId,\n                streamId,\n                strippedChannel,\n                originalChannel,\n                sourceIdentity);\n        }\n\n        State nextState = State.EXTEND;\n        String reason = \"\";\n\n        if (null != liveDestination)\n        {\n            if (NULL_POSITION != stopPosition)\n            {\n                final String errorMsg = \"cannot live merge without active source recording\";\n                state(State.DONE, errorMsg);\n                error(errorMsg, ArchiveException.GENERIC);\n                return;\n            }\n\n            nextState = State.SRC_RECORDING_POSITION;\n            reason = \"liveDestination=\" + liveDestination;\n        }\n\n        if (startPosition == stopPosition ||\n            (NULL_VALUE != dstRecordingId && stopPosition == catalog.stopPosition(dstRecordingId)))\n        {\n            signal(stopPosition, SYNC);\n            nextState = State.DONE;\n            reason = \"in sync\";\n        }\n\n        state(nextState, reason);\n    }\n\n    private int connect()\n    {\n        int workCount = 0;\n\n        if (null == asyncConnect)\n        {\n            asyncConnect = AeronArchive.asyncConnect(context);\n            workCount += 1;\n        }\n        else\n        {\n            final int step = asyncConnect.step();\n            try\n            {\n                final AeronArchive archive = asyncConnect.poll();\n\n                if (null == archive)\n                {\n                    if (asyncConnect.step() != step)\n                    {\n                        workCount += 1;\n                    }\n                }\n                else\n                {\n                    srcArchive = archive;\n                    asyncConnect = null;\n                    state(State.REPLICATE_DESCRIPTOR, \"connected to the Archive\");\n                    workCount += 1;\n                }\n            }\n            catch (final AeronException ex)\n            {\n                state(State.DONE, ex.getMessage());\n                error(\n                    \"Replication connection failed=\" + ex.getMessage(),\n                    ArchiveException.REPLICATION_CONNECTION_FAILURE);\n            }\n        }\n\n        return workCount;\n    }\n\n    private int replicateDescriptor()\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            final long correlationId = aeron.nextCorrelationId();\n            if (srcArchive.archiveProxy().listRecording(srcRecordingId, correlationId, srcArchive.controlSessionId()))\n            {\n                workCount += trackAction(correlationId);\n                srcArchive.recordingDescriptorPoller().reset(correlationId, 1, this);\n            }\n            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n            {\n                throw new TimeoutException(\"failed to list remote recording descriptor\");\n            }\n        }\n        else\n        {\n            final RecordingDescriptorPoller poller = srcArchive.recordingDescriptorPoller();\n            final int fragments = poller.poll();\n\n            if (poller.isDispatchComplete() && poller.remainingRecordCount() > 0)\n            {\n                final String errorMsg = \"unknown src recording id \" + srcRecordingId;\n                state(State.DONE, errorMsg);\n                error(errorMsg, ArchiveException.UNKNOWN_RECORDING);\n            }\n\n            if (0 == fragments && epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n            {\n                throw new TimeoutException(\"failed to fetch remote recording descriptor\");\n            }\n\n            workCount += fragments;\n        }\n\n        return workCount;\n    }\n\n    private int srcRecordingPosition()\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            final long correlationId = aeron.nextCorrelationId();\n            final long controlSessionId = srcArchive.controlSessionId();\n            if (srcArchive.archiveProxy().getRecordingPosition(srcRecordingId, correlationId, controlSessionId))\n            {\n                workCount += trackAction(correlationId);\n            }\n            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n            {\n                throw new TimeoutException(\"failed to send recording position request\");\n            }\n        }\n        else\n        {\n            final ControlResponsePoller poller = srcArchive.controlResponsePoller();\n            workCount += poller.poll();\n\n            if (hasResponse(poller))\n            {\n                srcRecordingPosition = poller.relevantId();\n                if (NULL_POSITION == srcRecordingPosition && null != liveDestination)\n                {\n                    throw new ArchiveException(\"cannot live merge without active source recording\");\n                }\n\n                state(State.EXTEND, \"\");\n            }\n            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n            {\n                throw new TimeoutException(\"failed to get recording position\");\n            }\n        }\n\n        return workCount;\n    }\n\n    private int extend()\n    {\n        final boolean isMds = isTagged || null != liveDestination;\n        final ChannelUri channelUri = ChannelUri.parse(replicationChannel);\n        final String endpoint = channelUri.get(CommonContext.ENDPOINT_PARAM_NAME);\n        channelUri.put(CommonContext.REJOIN_PARAM_NAME, \"false\");\n        if (!channelUri.hasControlModeResponse())\n        {\n            channelUri.put(CommonContext.SESSION_ID_PARAM_NAME, Integer.toString(replaySessionId));\n        }\n\n        if (isMds)\n        {\n            channelUri.remove(CommonContext.ENDPOINT_PARAM_NAME);\n            channelUri.put(CommonContext.TAGS_PARAM_NAME, channelTagId + \",\" + subscriptionTagId);\n            channelUri.put(CommonContext.MDC_CONTROL_MODE_PARAM_NAME, CommonContext.MDC_CONTROL_MODE_MANUAL);\n        }\n\n        final String channel = channelUri.toString();\n        final Object recordingSubscriptionOrErrorMsg = conductor.extendRecording(\n            replicationId, dstRecordingId, replayStreamId, SourceLocation.REMOTE, true, channel, controlSession);\n\n        if (recordingSubscriptionOrErrorMsg instanceof Subscription)\n        {\n            recordingSubscription = (Subscription)recordingSubscriptionOrErrorMsg;\n            if (isMds)\n            {\n                replayDestination = ChannelUri.createDestinationUri(replicationChannel, endpoint);\n                recordingSubscription.asyncAddDestination(replayDestination);\n                state(State.REPLAY_TOKEN, \"replay destination added: \" + replayDestination);\n            }\n            else\n            {\n                state(State.REPLAY_TOKEN, \"replay destination added\");\n            }\n        }\n        else\n        {\n            state(State.DONE, (String)recordingSubscriptionOrErrorMsg);\n        }\n\n        return 1;\n    }\n\n    private int replayToken()\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE != replayToken || !ChannelUri.parse(replicationChannel).hasControlModeResponse())\n        {\n            workCount++;\n            state(State.GET_ARCHIVE_PROXY, \"\");\n            return workCount;\n        }\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            final long lastCorrelationId = aeron.nextCorrelationId();\n            if (srcArchive.archiveProxy().requestReplayToken(\n                lastCorrelationId, srcArchive.controlSessionId(), srcRecordingId))\n            {\n                workCount++;\n                activeCorrelationId = lastCorrelationId;\n            }\n            else\n            {\n                return workCount;\n            }\n        }\n\n        final ControlResponsePoller poller = srcArchive.controlResponsePoller();\n        workCount += poller.poll();\n        if (hasResponse(poller))\n        {\n            replayToken = poller.relevantId();\n            state(State.GET_ARCHIVE_PROXY, \"\");\n        }\n\n        return workCount;\n    }\n\n    private int getArchiveProxy()\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == replayToken)\n        {\n            ++workCount;\n            state(State.REPLAY, \"\");\n            return workCount;\n        }\n\n        if (NULL_VALUE == responsePublicationRegistrationId)\n        {\n            final String uri = new ChannelUriStringBuilder(context.controlRequestChannel())\n                .responseCorrelationId(recordingSubscription.registrationId())\n                .termId((Integer)null).initialTermId((Integer)null).termOffset((Integer)null)\n                .termLength(64 * 1024)\n                .build();\n            final int controlRequestStreamId = srcArchive.context().controlRequestStreamId();\n\n            responsePublicationRegistrationId = aeron.asyncAddExclusivePublication(uri, controlRequestStreamId);\n        }\n\n        if (null == responsePublication)\n        {\n            responsePublication = aeron.getExclusivePublication(responsePublicationRegistrationId);\n            if (null != responsePublication)\n            {\n                ++workCount;\n            }\n            else\n            {\n                return workCount;\n            }\n        }\n\n        if (responsePublication.isConnected())\n        {\n            ++workCount;\n        }\n        else\n        {\n            return workCount;\n        }\n\n        if (0 < responsePublication.availableWindow())\n        {\n            ++workCount;\n        }\n        else\n        {\n            return workCount;\n        }\n\n        responseArchiveProxy = new ArchiveProxy(responsePublication);\n        state(State.REPLAY, \"ArchiveProxy created\");\n\n        return workCount;\n    }\n\n    private int replay()\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            final String resolvedEndpoint = recordingSubscription.resolvedEndpoint();\n            if (null == resolvedEndpoint)\n            {\n                if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n                {\n                    throw new TimeoutException(\n                        \"failed to resolve subscription endpoint: channel=\" + recordingSubscription.channel());\n                }\n\n                return workCount;\n            }\n\n            final ChannelUri channelUri = ChannelUri.parse(replicationChannel);\n            final String endpoint = channelUri.get(CommonContext.ENDPOINT_PARAM_NAME);\n            if (null != endpoint)\n            {\n                channelUri.replaceEndpointWildcardPort(resolvedEndpoint);\n            }\n\n            if (null != liveDestination)\n            {\n                channelUri.put(CommonContext.LINGER_PARAM_NAME, \"0\");\n                channelUri.put(CommonContext.EOS_PARAM_NAME, \"false\");\n            }\n\n            if (channelUri.hasControlModeResponse())\n            {\n                channelUri.put(\n                    CommonContext.RESPONSE_CORRELATION_ID_PARAM_NAME,\n                    String.valueOf(recordingSubscription.registrationId()));\n            }\n            else\n            {\n                channelUri.put(CommonContext.SESSION_ID_PARAM_NAME, Integer.toString(replaySessionId));\n            }\n\n            final long correlationId = aeron.nextCorrelationId();\n\n            final ReplayParams replayParams = new ReplayParams()\n                .position(replayPosition)\n                .length(NULL_POSITION == dstStopPosition ? AeronArchive.NULL_LENGTH : dstStopPosition - replayPosition)\n                .fileIoMaxLength(fileIoMaxLength)\n                .replayToken(replayToken);\n\n            final ArchiveProxy archiveProxy = null != responseArchiveProxy ?\n                responseArchiveProxy : srcArchive.archiveProxy();\n\n            if (archiveProxy.replay(\n                srcRecordingId,\n                channelUri.toString(),\n                replayStreamId,\n                replayParams,\n                correlationId,\n                srcArchive.controlSessionId()))\n            {\n                workCount += trackAction(correlationId);\n            }\n            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n            {\n                throw new TimeoutException(\"failed to send replay request\");\n            }\n        }\n        else\n        {\n            final ControlResponsePoller poller = srcArchive.controlResponsePoller();\n            workCount += poller.poll();\n\n            if (hasResponse(poller))\n            {\n                srcReplaySessionId = poller.relevantId();\n                state(State.AWAIT_IMAGE, \"srcReplaySessionId=\" + srcReplaySessionId);\n            }\n            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n            {\n                throw new TimeoutException(\"failed get acknowledgement of replay request to: \" + replicationChannel);\n            }\n        }\n\n        return workCount;\n    }\n\n    private int awaitImage()\n    {\n        int workCount = 0;\n\n        final Image image = recordingSubscription.imageBySessionId((int)srcReplaySessionId);\n        if (null != image)\n        {\n            this.image = image;\n            state(null == liveDestination ? State.REPLICATE : State.CATCHUP,\n                \"image.correlationId=\" + image.correlationId() +\n                \", image.sessionId=\" + image.sessionId() +\n                \", image.joinPosition=\" + image.joinPosition());\n            workCount += 1;\n        }\n        else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n        {\n            throw new TimeoutException(\n                \"failed get replay image for sessionId=\" + (int)srcReplaySessionId +\n                \" on channel=\" + recordingSubscription.channel());\n        }\n\n        return workCount;\n    }\n\n    private int replicate()\n    {\n        int workCount = 0;\n\n        final boolean isClosed = image.isClosed();\n        final boolean isEndOfStream = image.isEndOfStream();\n        final long position = image.position();\n        final boolean isSynced = NULL_POSITION != srcStopPosition && position >= srcStopPosition;\n\n        if (isSynced ||\n            (NULL_POSITION != dstStopPosition && position >= dstStopPosition) ||\n            isEndOfStream || isClosed)\n        {\n            logReplicationSessionDone(\n                controlSession.sessionId(),\n                replicationId,\n                srcRecordingId,\n                replayPosition,\n                srcStopPosition,\n                dstRecordingId,\n                dstStopPosition,\n                position,\n                isClosed,\n                isEndOfStream,\n                isSynced);\n\n            if (isSynced)\n            {\n                signal(position, SYNC);\n            }\n\n            srcReplaySessionId = NULL_VALUE;\n            state(State.DONE, isSynced ? \"sync\" : \"done\");\n            workCount += 1;\n        }\n\n        return workCount;\n    }\n\n    private int catchup()\n    {\n        int workCount = 0;\n\n        if (image.position() >= srcRecordingPosition)\n        {\n            state(State.ATTEMPT_LIVE_JOIN, \"image position (\" + image.position() +\n                \") >= srcRecordingPosition (\" + srcRecordingPosition + \")\");\n            workCount += 1;\n        }\n        else if (image.isClosed())\n        {\n            throw new ArchiveException(\"replication image closed unexpectedly\");\n        }\n\n        return workCount;\n    }\n\n    private int attemptLiveJoin()\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            final long correlationId = aeron.nextCorrelationId();\n            if (srcArchive.archiveProxy().getRecordingPosition(\n                srcRecordingId, correlationId, srcArchive.controlSessionId()))\n            {\n                workCount += trackAction(correlationId);\n            }\n            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n            {\n                throw new TimeoutException(\"failed to send recording position request\");\n            }\n        }\n        else\n        {\n            final ControlResponsePoller poller = srcArchive.controlResponsePoller();\n            workCount += poller.poll();\n\n            if (hasResponse(poller))\n            {\n                trackAction(NULL_VALUE);\n                retryAttempts = RETRY_ATTEMPTS;\n                srcRecordingPosition = poller.relevantId();\n\n                if (NULL_POSITION == srcRecordingPosition && null != liveDestination)\n                {\n                    throw new ArchiveException(\"cannot live merge without active source recording\");\n                }\n\n                final long position = image.position();\n                if (shouldAddLiveDestination(position))\n                {\n                    recordingSubscription.asyncAddDestination(liveDestination);\n                    isLiveAdded = true;\n                }\n                else if (shouldStopReplay(position))\n                {\n                    recordingSubscription.asyncRemoveDestination(replayDestination);\n                    replayDestination = null;\n                    recordingSubscription = null;\n                    signal(position, MERGE);\n                    state(State.DONE, \"merge\");\n                }\n\n                workCount += 1;\n            }\n            else if (image.isClosed())\n            {\n                throw new ArchiveException(\"replication image closed unexpectedly\");\n            }\n            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n            {\n                if (--retryAttempts == 0)\n                {\n                    throw new TimeoutException(\"failed to get recording position\");\n                }\n\n                trackAction(NULL_VALUE);\n            }\n        }\n\n        return workCount;\n    }\n\n    private boolean hasResponse(final ControlResponsePoller poller)\n    {\n        if (poller.isPollComplete() && poller.controlSessionId() == srcArchive.controlSessionId())\n        {\n            final ControlResponseCode code = poller.code();\n            if (ControlResponseCode.ERROR == code)\n            {\n                throw new ArchiveException(poller.errorMessage(), (int)poller.relevantId());\n            }\n\n            return poller.correlationId() == activeCorrelationId && ControlResponseCode.OK == code;\n        }\n\n        return false;\n    }\n\n    private void error(final String msg, final int errorCode)\n    {\n        controlSession.sendErrorResponse(replicationId, errorCode, msg);\n    }\n\n    private void signal(final long position, final RecordingSignal recordingSignal)\n    {\n        final long subscriptionId = null != recordingSubscription ? recordingSubscription.registrationId() : NULL_VALUE;\n        controlSession.sendSignal(replicationId, dstRecordingId, subscriptionId, position, recordingSignal);\n    }\n\n    private void stopReplaySession(final CountedErrorHandler countedErrorHandler)\n    {\n        if (NULL_VALUE != srcReplaySessionId)\n        {\n            try\n            {\n                srcArchive.archiveProxy().stopReplay(\n                    srcReplaySessionId, aeron.nextCorrelationId(), srcArchive.controlSessionId());\n            }\n            catch (final Exception ex)\n            {\n                countedErrorHandler.onError(ex);\n            }\n            srcReplaySessionId = NULL_VALUE;\n        }\n    }\n\n    private void stopRecording()\n    {\n        if (null != recordingSubscription)\n        {\n            conductor.stopRecordingSubscription(recordingSubscription.registrationId());\n            recordingSubscription = null;\n        }\n    }\n\n    private boolean shouldAddLiveDestination(final long position)\n    {\n        return !isLiveAdded &&\n            (srcRecordingPosition - position) <= Math.min(image.termBufferLength() >> 2, LIVE_ADD_MAX_WINDOW);\n    }\n\n    private boolean shouldStopReplay(final long position)\n    {\n        return isLiveAdded &&\n            (srcRecordingPosition - position) <= REPLAY_REMOVE_THRESHOLD &&\n            image.activeTransportCount() >= 2;\n    }\n\n    private int trackAction(final long correlationId)\n    {\n        timeOfLastActionMs = epochClock.time();\n        activeCorrelationId = correlationId;\n        return 1;\n    }\n\n    private void state(final State newState, final String reason)\n    {\n        logStateChange(state, newState, replicationId,\n            srcRecordingId, dstRecordingId,\n            null != image ? image.position() : NULL_POSITION,\n            null == reason ? \"\" : reason);\n        state = newState;\n        activeCorrelationId = NULL_VALUE;\n        timeOfLastActionMs = epochClock.time();\n    }\n\n    private void pollSourceArchiveEvents()\n    {\n        if (null != srcArchive && State.DONE != state && NULL_VALUE == activeCorrelationId)\n        {\n            final long nowMs = epochClock.time();\n            if (nowMs > (timeOfLastScheduledSourceArchivePollMs + SOURCE_ARCHIVE_POLL_INTERVAL_MS))\n            {\n                timeOfLastScheduledSourceArchivePollMs = nowMs;\n\n                final String errorMessage = srcArchive.pollForErrorResponse();\n                if (null != errorMessage)\n                {\n                    throw new ArchiveException(errorMessage);\n                }\n            }\n        }\n    }\n\n    private void logStateChange(\n        final State oldState,\n        final State newState,\n        final long replicationId,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long position,\n        final String reason)\n    {\n        //System.out.println(\"ReplicationSession: \" + oldState + \" -> \" + newState + \" replicationId=\" + replicationId);\n    }\n\n    private void logReplicationSessionDone(\n        final long controlSessionId,\n        final long replicationId,\n        final long srcRecordingId,\n        final long replayPosition,\n        final long srcStopPosition,\n        final long dstRecordingId,\n        final long dstStopPosition,\n        final long position,\n        final boolean isClosed,\n        final boolean isEndOfStream,\n        final boolean isSynced)\n    {\n//        System.out.println(\n//            \"ReplicationDone: controlSessionId = \" + controlSessionId + \", replicationId = \" + replicationId +\n//            \", srcRecordingId = \" + srcRecordingId + \", srcRecordingPosition = \" + srcRecordingPosition +\n//            \", srcStopPosition = \" + srcStopPosition + \", dstRecordingId = \" + dstRecordingId +\n//            \", dstStopPosition = \" + dstStopPosition + \", position = \" + position + \", isClosed = \" + isClosed +\n//            \", isEndOfStream = \" + isEndOfStream + \", isSynced = \" + isSynced);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/Session.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage io.aeron.archive;\n\n/**\n * Sessions are created by the conductor but perform their work on a different {@link SessionWorker} potentially. After\n * construction sessions are safely published to the {@link SessionWorker} and thereafter interacted with only from\n * that thread until they are done. Once done they are closed from the conductor.\n */\ninterface Session\n{\n    void abort(String reason);\n\n    boolean isDone();\n\n    int doWork();\n\n    long sessionId();\n\n    void close();\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/SessionWorker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage io.aeron.archive;\n\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.CountedErrorHandler;\n\nimport java.util.ArrayList;\n\n/**\n * This is a common workflow to {@link Session} handling in the archive. Hooks are provided for specialisation as\n * protected methods.\n *\n * @param <T> session type\n */\nclass SessionWorker<T extends Session> implements Agent\n{\n    private final ArrayList<T> sessions = new ArrayList<>();\n    private final String roleName;\n    protected final CountedErrorHandler errorHandler;\n    private boolean isClosed = false;\n\n    SessionWorker(final String roleName, final CountedErrorHandler errorHandler)\n    {\n        this.roleName = roleName;\n        this.errorHandler = errorHandler;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return roleName;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n\n        final ArrayList<T> sessions = this.sessions;\n        for (int lastIndex = sessions.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final T session = sessions.get(i);\n            try\n            {\n                workCount += session.doWork();\n            }\n            catch (final Exception ex)\n            {\n                errorHandler.onError(ex);\n            }\n\n            if (session.isDone())\n            {\n                ArrayListUtil.fastUnorderedRemove(sessions, i, lastIndex--);\n                closeSession(session);\n            }\n        }\n\n        return workCount;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public final void onClose()\n    {\n        if (isClosed)\n        {\n            return;\n        }\n\n        isClosed = true;\n\n        try\n        {\n            preSessionsClose();\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n\n        for (int i = 0, size = sessions.size(); i < size; i++)\n        {\n            closeSession(sessions.get(i));\n        }\n\n        postSessionsClose();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected void abort()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected void closeSession(final T session)\n    {\n        try\n        {\n            session.close();\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected void postSessionsClose()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected void preSessionsClose()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected void addSession(final T session)\n    {\n        sessions.add(session);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/SharedModeArchiveConductor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *  https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage io.aeron.archive;\n\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.CountedErrorHandler;\n\nfinal class SharedModeArchiveConductor extends ArchiveConductor\n{\n    private AgentInvoker replayerAgentInvoker;\n    private AgentInvoker recorderAgentInvoker;\n\n    SharedModeArchiveConductor(final Archive.Context ctx)\n    {\n        super(ctx);\n    }\n\n    public void onStart()\n    {\n        super.onStart();\n\n        replayerAgentInvoker = new AgentInvoker(errorHandler, ctx.errorCounter(), replayer);\n        recorderAgentInvoker = new AgentInvoker(errorHandler, ctx.errorCounter(), recorder);\n\n        replayerAgentInvoker.start();\n        recorderAgentInvoker.start();\n    }\n\n    Recorder newRecorder()\n    {\n        return new SharedModeRecorder(errorHandler, ctx);\n    }\n\n    Replayer newReplayer()\n    {\n        return new SharedModeReplayer(errorHandler, ctx);\n    }\n\n    public int doWork()\n    {\n        return super.doWork() +\n            replayerAgentInvoker.invoke() +\n            invokeAeronInvoker() +\n            invokeDriverConductor() +\n            recorderAgentInvoker.invoke() +\n            invokeAeronInvoker() +\n            invokeDriverConductor();\n    }\n\n    protected void closeSessionWorkers()\n    {\n        CloseHelper.close(ctx.countedErrorHandler(), recorderAgentInvoker);\n        CloseHelper.close(ctx.countedErrorHandler(), replayerAgentInvoker);\n    }\n\n    class SharedModeRecorder extends Recorder\n    {\n        SharedModeRecorder(final CountedErrorHandler errorHandler, final Archive.Context context)\n        {\n            super(errorHandler, context);\n        }\n\n        protected void closeSession(final RecordingSession session)\n        {\n            closeRecordingSession(session);\n        }\n    }\n\n    class SharedModeReplayer extends Replayer\n    {\n        SharedModeReplayer(final CountedErrorHandler errorHandler, final Archive.Context context)\n        {\n            super(errorHandler, context);\n        }\n\n        protected void closeSession(final ReplaySession session)\n        {\n            closeReplaySession(session);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/SimpleFragmentHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\n@FunctionalInterface\ninterface SimpleFragmentHandler\n{\n    /**\n     * Called by the {@link RecordingReader}. Implementors need to process DATA and PADDING fragments.\n     *\n     * @param buffer        containing the fragment.\n     * @param offset        the data begins at.\n     * @param length        length of the data.\n     * @param frameType     to distinguish between DATA and PADDING fragments.\n     * @param flags         flags for the frame.\n     * @param reservedValue stored for the frame.\n     */\n    void onFragment(UnsafeBuffer buffer, int offset, int length, int frameType, byte flags, long reservedValue);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/UpdateChannelSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderDecoder;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nclass UpdateChannelSession implements Session\n{\n    private final long correlationId;\n    private final long recordingId;\n    private final String originalChannel;\n    private final String strippedChannel;\n    private final Catalog catalog;\n    private final ControlSession controlSession;\n    private final UnsafeBuffer descriptorBuffer;\n    private final RecordingDescriptorDecoder recordingDescriptorDecoder = new RecordingDescriptorDecoder();\n\n    private boolean isDone;\n\n    UpdateChannelSession(\n        final long correlationId,\n        final long recordingId,\n        final String originalChannel,\n        final String strippedChannel,\n        final Catalog catalog,\n        final ControlSession controlSession,\n        final UnsafeBuffer descriptorBuffer)\n    {\n        this.correlationId = correlationId;\n        this.recordingId = recordingId;\n        this.originalChannel = originalChannel;\n        this.strippedChannel = strippedChannel;\n        this.catalog = catalog;\n        this.controlSession = controlSession;\n        this.descriptorBuffer = descriptorBuffer;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void abort(final String reason)\n    {\n        isDone = true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isDone()\n    {\n        return isDone;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        if (isDone)\n        {\n            return 0;\n        }\n\n        if (catalog.wrapDescriptor(recordingId, descriptorBuffer))\n        {\n            recordingDescriptorDecoder.wrap(\n                descriptorBuffer,\n                RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.SCHEMA_VERSION);\n\n            final long startPosition = recordingDescriptorDecoder.startPosition();\n            final long stopPosition = recordingDescriptorDecoder.stopPosition();\n            final long startTimestamp = recordingDescriptorDecoder.startTimestamp();\n            final long stopTimestamp = recordingDescriptorDecoder.stopTimestamp();\n            final int imageInitialTermId = recordingDescriptorDecoder.initialTermId();\n            final int segmentFileLength = recordingDescriptorDecoder.segmentFileLength();\n            final int termBufferLength = recordingDescriptorDecoder.termBufferLength();\n            final int mtuLength = recordingDescriptorDecoder.mtuLength();\n            final int sessionId = recordingDescriptorDecoder.sessionId();\n            final int streamId = recordingDescriptorDecoder.streamId();\n            recordingDescriptorDecoder.skipStrippedChannel();\n            recordingDescriptorDecoder.skipOriginalChannel();\n            final String sourceIdentity = recordingDescriptorDecoder.sourceIdentity();\n\n            catalog.replaceRecording(\n                recordingId,\n                startPosition,\n                stopPosition,\n                startTimestamp,\n                stopTimestamp,\n                imageInitialTermId,\n                segmentFileLength,\n                termBufferLength,\n                mtuLength,\n                sessionId,\n                streamId,\n                strippedChannel,\n                originalChannel,\n                sourceIdentity);\n\n            controlSession.sendOkResponse(correlationId);\n        }\n        else\n        {\n            controlSession.sendRecordingUnknown(correlationId, recordingId);\n        }\n\n        isDone = true;\n        return 1;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long sessionId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        controlSession.activeListing(null);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/checksum/Checksum.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.checksum;\n\n/**\n * An interface representing API to compute a data checksum.\n * <p>\n * <b>Note:</b> Instances should be threadsafe and stateless.\n */\npublic interface Checksum\n{\n    /**\n     * Computes a checksum based on the contents of a {@code DirectByteBuffer}.\n     *\n     * @param address of the buffer.\n     * @param offset  within the buffer to begin at.\n     * @param length  of the data to read.\n     * @return computed checksum value.\n     */\n    int compute(long address, int offset, int length);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/checksum/Checksums.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.checksum;\n\nimport org.agrona.Strings;\nimport org.agrona.checksum.Crc32;\nimport org.agrona.checksum.Crc32c;\n\n/**\n * Factory and common methods for working with {@link Checksum} instances.\n */\npublic final class Checksums\n{\n    private static final Checksum CRC_32 = Crc32.INSTANCE::compute;\n    private static final Checksum CRC_32C = Crc32c.INSTANCE::compute;\n\n    private Checksums()\n    {\n    }\n\n    /**\n     * Returns an instance of {@link Checksum} that computes CRC-32 checksums.\n     *\n     * @return CRC-32 implementation.\n     */\n    public static Checksum crc32()\n    {\n        return CRC_32;\n    }\n\n    /**\n     * Returns an instance of {@link Checksum} that computes CRC-32C checksums.\n     *\n     * @return CRC-32C implementation.\n     */\n    public static Checksum crc32c()\n    {\n        return CRC_32C;\n    }\n\n    /**\n     * Factory method to create an instance of the {@link Checksum} interface.\n     *\n     * @param className fully qualified class name or an alias of the {@link Checksum} implementation.\n     * @return a {@link Checksum} instance.\n     * @throws IllegalArgumentException if {@code className == null} or empty.\n     * @throws IllegalStateException    if an attempt was made to acquire CRC-32C while running on JDK 8.\n     * @throws IllegalArgumentException if an error occurs while creating a {@link Checksum} instance.\n     * @throws ClassCastException       if created instance does not implement the {@link Checksum} interface.\n     * @see #crc32c()\n     */\n    public static Checksum newInstance(final String className)\n    {\n        if (Strings.isEmpty(className))\n        {\n            throw new IllegalArgumentException(\"className is empty\");\n        }\n\n        return switch (className)\n        {\n            case \"CRC-32\":\n            case \"io.aeron.archive.checksum.Crc32\":\n            case \"org.agrona.checksum.Crc32\":\n                yield crc32();\n            case \"CRC-32C\":\n            case \"io.aeron.archive.checksum.Crc32c\":\n            case \"org.agrona.checksum.Crc32c\":\n                yield crc32c();\n            default:\n            {\n                try\n                {\n                    final Class<?> klass = Class.forName(className);\n                    final Object instance = klass.getDeclaredConstructor().newInstance();\n                    yield (Checksum)instance;\n                }\n                catch (final ReflectiveOperationException ex)\n                {\n                    throw new IllegalArgumentException(\n                        \"failed to create Checksum instance for class: \" + className, ex);\n                }\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/checksum/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Checksums can be added to recordings so that file corruption can be detected.\n * <p>\n * Recordings can be checksumed as they are recorded or later via {@link io.aeron.archive.ArchiveTool} checksum methods.\n * Checksums are generated via implementations of {@link io.aeron.archive.checksum.Checksum} interface.\n * <p>\n * Recordings can be verified on replay or via the {@link io.aeron.archive.ArchiveTool} verify methods.\n */\npackage io.aeron.archive.checksum;"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.AvailableImageHandler;\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.Subscription;\nimport io.aeron.UnavailableImageHandler;\nimport io.aeron.archive.codecs.ControlResponseCode;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.codecs.RecordingSignalEventDecoder;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.security.CredentialsSupplier;\nimport io.aeron.security.NullCredentialsSupplier;\nimport io.aeron.version.Versioned;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.LangUtil;\nimport org.agrona.SemanticVersion;\nimport org.agrona.Strings;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.BackoffIdleStrategy;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.NoOpLock;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.CONTROL_MODE_RESPONSE;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_PARAM_NAME;\nimport static io.aeron.CommonContext.MTU_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.RESPONSE_CORRELATION_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.SESSION_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.SPARSE_PARAM_NAME;\nimport static io.aeron.CommonContext.TERM_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.checkDebugTimeout;\nimport static io.aeron.CommonContext.getAeronDirectoryName;\nimport static io.aeron.driver.Configuration.IDLE_MAX_PARK_NS;\nimport static io.aeron.driver.Configuration.IDLE_MAX_SPINS;\nimport static io.aeron.driver.Configuration.IDLE_MAX_YIELDS;\nimport static io.aeron.driver.Configuration.IDLE_MIN_PARK_NS;\nimport static org.agrona.SystemUtil.getDurationInNanos;\nimport static org.agrona.SystemUtil.getProperty;\nimport static org.agrona.SystemUtil.getSizeAsInt;\n\n/**\n * Client for interacting with a local or remote Aeron Archive which records and replays message streams from storage.\n * <p>\n * This client provides a simple interaction model which is mostly synchronous and may not be optimal.\n * The underlying components such as the {@link ArchiveProxy} and the {@link ControlResponsePoller} or\n * {@link RecordingDescriptorPoller} may be used directly if a more asynchronous interaction is required.\n * <p>\n * Note: This class is threadsafe but the lock can be elided for single threaded access via {@link Context#lock(Lock)}\n * being set to {@link NoOpLock}.\n */\n@Versioned\npublic final class AeronArchive implements AutoCloseable\n{\n    /**\n     * Represents a timestamp that has not been set. Can be used when the time is not known.\n     */\n    public static final long NULL_TIMESTAMP = NULL_VALUE;\n\n    /**\n     * Represents a position that has not been set. Can be used when the position is not known.\n     */\n    public static final long NULL_POSITION = NULL_VALUE;\n\n    /**\n     * Represents a length that has not been set. If null length is provided then replay the whole recorded stream.\n     */\n    public static final long NULL_LENGTH = NULL_VALUE;\n\n    /**\n     * Indicates the client is no longer connected to an archive.\n     */\n    public static final String NOT_CONNECTED_MSG = \"not connected\";\n\n    /**\n     * When replaying a live recording, replay the whole stream and follow the live recording. This will behave the\n     * same way as providing {@link AeronArchive#NULL_LENGTH}\n     */\n    public static final long REPLAY_ALL_AND_FOLLOW = NULL_LENGTH;\n\n    /**\n     * When replaying a live recording, replay up to the current limit then stop the replay and end the stream.\n     */\n    public static final long REPLAY_ALL_AND_STOP = -2;\n\n    /**\n     * Describes state of the client instance.\n     */\n    public enum State\n    {\n        /**\n         * Client connected to the archive.\n         */\n        CONNECTED,\n\n        /**\n         * Connection to the archive was lost. It is only possible to close this client instance. A new client instance\n         * must be created in order to establish connection with archive again.\n         */\n        DISCONNECTED,\n\n        /**\n         * Client was closed and can no longer be used. A new client instance must be created in order to establish\n         * connection with archive again.\n         */\n        CLOSED;\n    }\n\n    private static final int FRAGMENT_LIMIT = 10;\n\n    private volatile State state;\n    private boolean isInCallback = false;\n    private long lastCorrelationId = NULL_VALUE;\n    private final long controlSessionId;\n    private final long archiveId;\n    private final long messageTimeoutNs;\n    private final Context context;\n    private final Aeron aeron;\n    private final ArchiveProxy archiveProxy;\n    private final IdleStrategy idleStrategy;\n    private final ControlResponsePoller controlResponsePoller;\n    private final Lock lock;\n    private final NanoClock nanoClock;\n    private RecordingDescriptorPoller recordingDescriptorPoller;\n    private RecordingSubscriptionDescriptorPoller recordingSubscriptionDescriptorPoller;\n\n    AeronArchive(\n        final Context context,\n        final ControlResponsePoller controlResponsePoller,\n        final ArchiveProxy archiveProxy,\n        final long controlSessionId,\n        final long archiveId)\n    {\n        this.context = context;\n        aeron = context.aeron();\n        idleStrategy = context.idleStrategy();\n        messageTimeoutNs = context.messageTimeoutNs();\n        lock = context.lock();\n        nanoClock = aeron.context().nanoClock();\n        this.controlResponsePoller = controlResponsePoller;\n        this.archiveProxy = archiveProxy;\n        this.controlSessionId = controlSessionId;\n        this.archiveId = archiveId;\n        state = State.CONNECTED;\n    }\n\n    /**\n     * Position of the recorded stream at the base of a segment file. If a recording starts within a term\n     * then the base position can be before the recording started.\n     *\n     * @param startPosition     of the stream.\n     * @param position          of the stream to calculate the segment base position from.\n     * @param termBufferLength  of the stream.\n     * @param segmentFileLength which is a multiple of term length.\n     * @return the position of the recorded stream at the beginning of a segment file.\n     */\n    public static long segmentFileBasePosition(\n        final long startPosition, final long position, final int termBufferLength, final int segmentFileLength)\n    {\n        final long startTermBasePosition = startPosition - (startPosition & (termBufferLength - 1));\n        final long lengthFromBasePosition = position - startTermBasePosition;\n        final long segments = (lengthFromBasePosition - (lengthFromBasePosition & (segmentFileLength - 1)));\n\n        return startTermBasePosition + segments;\n    }\n\n    /**\n     * Returns the state of this client.\n     *\n     * @return client state.\n     */\n    public State state()\n    {\n        return state;\n    }\n\n    /**\n     * Notify the archive that this control session is closed, so it can promptly release resources then close the\n     * local resources associated with the client.\n     */\n    public void close()\n    {\n        lock.lock();\n        try\n        {\n            if (State.CLOSED != state)\n            {\n                state(State.CLOSED);\n                final ErrorHandler errorHandler = context.errorHandler();\n                Exception resultEx = null;\n\n                if (archiveProxy.publication().isConnected())\n                {\n                    resultEx = quietClose(resultEx, () -> archiveProxy.closeSession(controlSessionId));\n                }\n\n                if (!context.ownsAeronClient())\n                {\n                    resultEx = quietClose(resultEx, archiveProxy.publication());\n                    resultEx = quietClose(resultEx, controlResponsePoller.subscription());\n                }\n\n                boolean rethrow = false;\n                try\n                {\n                    context.close();\n                }\n                catch (final Exception ex)\n                {\n                    rethrow = true;\n                    if (null != resultEx)\n                    {\n                        resultEx.addSuppressed(ex);\n                    }\n                    else\n                    {\n                        resultEx = ex;\n                    }\n                }\n\n                if (null != resultEx)\n                {\n                    if (null != errorHandler)\n                    {\n                        errorHandler.onError(resultEx);\n                    }\n\n                    if (rethrow)\n                    {\n                        LangUtil.rethrowUnchecked(resultEx);\n                    }\n                }\n            }\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Connect to an Aeron archive using a default {@link Context}. This will create a control session.\n     *\n     * @return the newly created Aeron Archive client.\n     */\n    public static AeronArchive connect()\n    {\n        return connect(new Context());\n    }\n\n    /**\n     * Connect to an Aeron archive by providing a {@link Context}. This will create a control session.\n     * <p>\n     * Before connecting {@link Context#conclude()} will be called.\n     * If an exception occurs then {@link Context#close()} will be called.\n     *\n     * @param ctx for connection configuration.\n     * @return the newly created Aeron Archive client.\n     */\n    public static AeronArchive connect(final Context ctx)\n    {\n        final AsyncConnect asyncConnect = asyncConnect(ctx);\n        try\n        {\n            final IdleStrategy idleStrategy = ctx.idleStrategy();\n            AsyncConnect.State previousState = asyncConnect.state();\n\n            AeronArchive aeronArchive;\n            while (null == (aeronArchive = asyncConnect.poll()))\n            {\n                if (asyncConnect.state() == previousState)\n                {\n                    idleStrategy.idle();\n                }\n                else\n                {\n                    idleStrategy.reset();\n                    previousState = asyncConnect.state();\n                }\n            }\n\n            return aeronArchive;\n        }\n        catch (final Exception ex)\n        {\n            final Exception error = quietClose(ex, asyncConnect);\n            LangUtil.rethrowUnchecked(error);\n            return null;\n        }\n    }\n\n    /**\n     * Begin an attempt at creating a connection which can be completed by calling {@link AsyncConnect#poll()} until\n     * it returns the client, before complete it will return null.\n     *\n     * @return the {@link AsyncConnect} that can be polled for completion.\n     */\n    public static AsyncConnect asyncConnect()\n    {\n        return asyncConnect(new Context());\n    }\n\n    /**\n     * Begin an attempt at creating a connection which can be completed by calling {@link AsyncConnect#poll()} until\n     * it returns the client, before complete it will return null.\n     *\n     * @param ctx for the archive connection.\n     * @return the {@link AsyncConnect} that can be polled for completion.\n     */\n    public static AsyncConnect asyncConnect(final Context ctx)\n    {\n        return new AsyncConnect(ctx);\n    }\n\n    /**\n     * Get the {@link Context} used to connect this archive client.\n     *\n     * @return the {@link Context} used to connect this archive client.\n     */\n    public Context context()\n    {\n        return context;\n    }\n\n    /**\n     * The last correlation id used for sending a request to the archive via method on this class.\n     *\n     * @return last correlation id used for sending a request to the archive.\n     */\n    public long lastCorrelationId()\n    {\n        return lastCorrelationId;\n    }\n\n    /**\n     * The control session id allocated for this connection to the archive.\n     *\n     * @return control session id allocated for this connection to the archive.\n     */\n    public long controlSessionId()\n    {\n        return controlSessionId;\n    }\n\n    /**\n     * The {@link ArchiveProxy} for send asynchronous messages to the connected archive.\n     *\n     * @return the {@link ArchiveProxy} for send asynchronous messages to the connected archive.\n     */\n    public ArchiveProxy archiveProxy()\n    {\n        return archiveProxy;\n    }\n\n    /**\n     * Get the {@link ControlResponsePoller} for polling additional events on the control channel.\n     *\n     * @return the {@link ControlResponsePoller} for polling additional events on the control channel.\n     */\n    public ControlResponsePoller controlResponsePoller()\n    {\n        return controlResponsePoller;\n    }\n\n    /**\n     * Get the {@link RecordingDescriptorPoller} for polling recording descriptors on the control channel.\n     *\n     * @return the {@link RecordingDescriptorPoller} for polling recording descriptors on the control channel.\n     */\n    public RecordingDescriptorPoller recordingDescriptorPoller()\n    {\n        if (null == recordingDescriptorPoller)\n        {\n            recordingDescriptorPoller = new RecordingDescriptorPoller(\n                controlResponsePoller.subscription(),\n                context.errorHandler(),\n                context.recordingSignalConsumer(),\n                controlSessionId,\n                FRAGMENT_LIMIT);\n        }\n\n        return recordingDescriptorPoller;\n    }\n\n    /**\n     * The {@link RecordingSubscriptionDescriptorPoller} for polling subscription descriptors on the control channel.\n     *\n     * @return the {@link RecordingSubscriptionDescriptorPoller} for polling subscription descriptors on the control\n     * channel.\n     */\n    public RecordingSubscriptionDescriptorPoller recordingSubscriptionDescriptorPoller()\n    {\n        if (null == recordingSubscriptionDescriptorPoller)\n        {\n            recordingSubscriptionDescriptorPoller = new RecordingSubscriptionDescriptorPoller(\n                controlResponsePoller.subscription(),\n                context.errorHandler(),\n                context.recordingSignalConsumer(),\n                controlSessionId,\n                FRAGMENT_LIMIT);\n        }\n\n        return recordingSubscriptionDescriptorPoller;\n    }\n\n    /**\n     * Poll the response stream once for an error. If another message is present then it will be skipped over\n     * so only call when not expecting another response. If not connected then return {@link #NOT_CONNECTED_MSG}.\n     *\n     * @return the error String otherwise null if no error is found.\n     */\n    public String pollForErrorResponse()\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n\n            final ControlResponsePoller poller = controlResponsePoller;\n            if (!poller.subscription().isConnected())\n            {\n                state(State.DISCONNECTED);\n                return NOT_CONNECTED_MSG;\n            }\n\n            if (poller.poll() != 0 && poller.isPollComplete())\n            {\n                if (poller.controlSessionId() == controlSessionId)\n                {\n                    if (poller.code() == ControlResponseCode.ERROR)\n                    {\n                        return poller.errorMessage();\n                    }\n                    else if (poller.templateId() == RecordingSignalEventDecoder.TEMPLATE_ID)\n                    {\n                        dispatchRecordingSignal(poller);\n                    }\n                }\n            }\n\n            return null;\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Check if an error has been returned for the control session, or if it is no longer connected, and then throw\n     * a {@link ArchiveException} if {@link Context#errorHandler(ErrorHandler)} is not set.\n     * <p>\n     * To check for an error response without raising an exception then try {@link #pollForErrorResponse()}.\n     *\n     * @see #pollForErrorResponse()\n     */\n    public void checkForErrorResponse()\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n\n            final ControlResponsePoller poller = controlResponsePoller;\n            if (!poller.subscription().isConnected())\n            {\n                state(State.DISCONNECTED);\n                if (null != context.errorHandler())\n                {\n                    context.errorHandler().onError(new ArchiveException(NOT_CONNECTED_MSG));\n                }\n                else\n                {\n                    throw new ArchiveException(NOT_CONNECTED_MSG);\n                }\n            }\n            else if (poller.poll() != 0 && poller.isPollComplete())\n            {\n                if (poller.controlSessionId() == controlSessionId)\n                {\n                    if (poller.code() == ControlResponseCode.ERROR)\n                    {\n                        final ArchiveException ex = new ArchiveException(\n                            poller.errorMessage(),\n                            (int)poller.relevantId(),\n                            poller.correlationId());\n\n                        if (null != context.errorHandler())\n                        {\n                            context.errorHandler().onError(ex);\n                        }\n                        else\n                        {\n                            throw ex;\n                        }\n                    }\n                    else if (poller.templateId() == RecordingSignalEventDecoder.TEMPLATE_ID)\n                    {\n                        dispatchRecordingSignal(poller);\n                    }\n                }\n            }\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Poll for {@link RecordingSignal}s for this session which will be dispatched to\n     * {@link Context#recordingSignalConsumer}.\n     *\n     * @return positive value if signals dispatched otherwise 0.\n     */\n    public int pollForRecordingSignals()\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n\n            final ControlResponsePoller poller = controlResponsePoller;\n            if (poller.poll() != 0 && poller.isPollComplete())\n            {\n                if (poller.controlSessionId() == controlSessionId)\n                {\n                    if (poller.code() == ControlResponseCode.ERROR)\n                    {\n                        final ArchiveException ex = new ArchiveException(\n                            poller.errorMessage(),\n                            (int)poller.relevantId(),\n                            poller.correlationId());\n\n                        if (null != context.errorHandler())\n                        {\n                            context.errorHandler().onError(ex);\n                        }\n                        else\n                        {\n                            throw ex;\n                        }\n                    }\n                    else if (poller.templateId() == RecordingSignalEventDecoder.TEMPLATE_ID)\n                    {\n                        dispatchRecordingSignal(poller);\n                        return 1;\n                    }\n                }\n            }\n\n            return 0;\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Add a {@link Publication} and set it up to be recorded. If this is not the first,\n     * i.e. {@link Publication#isOriginal()} is true, then an {@link ArchiveException}\n     * will be thrown and the recording not initiated.\n     * <p>\n     * This is a sessionId specific recording.\n     *\n     * @param channel  for the publication.\n     * @param streamId for the publication.\n     * @return the {@link Publication} ready for use.\n     */\n    public Publication addRecordedPublication(final String channel, final int streamId)\n    {\n        Publication publication = null;\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            publication = aeron.addPublication(channel, streamId);\n            if (!publication.isOriginal())\n            {\n                throw new ArchiveException(\n                    \"publication already added for channel=\" + channel + \" streamId=\" + streamId);\n            }\n\n            startRecording(ChannelUri.addSessionId(channel, publication.sessionId()), streamId, SourceLocation.LOCAL);\n        }\n        catch (final RuntimeException ex)\n        {\n            CloseHelper.quietClose(publication);\n            throw ex;\n        }\n        finally\n        {\n            lock.unlock();\n        }\n\n        return publication;\n    }\n\n    /**\n     * Add an {@link ExclusivePublication} and set it up to be recorded.\n     * <p>\n     * This is a sessionId specific recording.\n     *\n     * @param channel  for the publication.\n     * @param streamId for the publication.\n     * @return the {@link ExclusivePublication} ready for use.\n     */\n    public ExclusivePublication addRecordedExclusivePublication(final String channel, final int streamId)\n    {\n        ExclusivePublication publication = null;\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            publication = aeron.addExclusivePublication(channel, streamId);\n            startRecording(ChannelUri.addSessionId(channel, publication.sessionId()), streamId, SourceLocation.LOCAL);\n        }\n        catch (final RuntimeException ex)\n        {\n            CloseHelper.quietClose(publication);\n            throw ex;\n        }\n        finally\n        {\n            lock.unlock();\n        }\n\n        return publication;\n    }\n\n    /**\n     * Start recording a channel and stream pairing.\n     * <p>\n     * Channels that include sessionId parameters are considered different from channels without sessionIds. If a\n     * publication matches both a sessionId specific channel recording and a non-sessionId specific recording,\n     * it will be recorded twice.\n     *\n     * @param channel        to be recorded.\n     * @param streamId       to be recorded.\n     * @param sourceLocation of the publication to be recorded.\n     * @return the subscriptionId, i.e. {@link Subscription#registrationId()}, of the recording. This can be\n     * passed to {@link #stopRecording(long)}.\n     */\n    public long startRecording(final String channel, final int streamId, final SourceLocation sourceLocation)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.startRecording(channel, streamId, sourceLocation, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send start recording request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Start recording a channel and stream pairing.\n     * <p>\n     * Channels that include sessionId parameters are considered different from channels without sessionIds. If a\n     * publication matches both a sessionId specific channel recording and a non-sessionId specific recording,\n     * it will be recorded twice.\n     *\n     * @param channel        to be recorded.\n     * @param streamId       to be recorded.\n     * @param sourceLocation of the publication to be recorded.\n     * @param autoStop       if the recording should be automatically stopped when complete.\n     * @return the subscriptionId, i.e. {@link Subscription#registrationId()}, of the recording. This can be\n     * passed to {@link #stopRecording(long)}. However, if is autoStop is true then no need to stop the recording\n     * unless you want to abort early.\n     */\n    public long startRecording(\n        final String channel, final int streamId, final SourceLocation sourceLocation, final boolean autoStop)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.startRecording(\n                channel, streamId, sourceLocation, autoStop, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send start recording request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Extend an existing, non-active recording of a channel and stream pairing.\n     * <p>\n     * The channel must be configured for the initial position from which it will be extended. This can be done\n     * with {@link ChannelUriStringBuilder#initialPosition(long, int, int)}. The details required to initialise can\n     * be found by calling {@link #listRecording(long, RecordingDescriptorConsumer)}.\n     *\n     * @param recordingId    of the existing recording.\n     * @param channel        to be recorded.\n     * @param streamId       to be recorded.\n     * @param sourceLocation of the publication to be recorded.\n     * @return the subscriptionId, i.e. {@link Subscription#registrationId()}, of the recording. This can be\n     * passed to {@link #stopRecording(long)}.\n     */\n    public long extendRecording(\n        final long recordingId,\n        final String channel,\n        final int streamId,\n        final SourceLocation sourceLocation)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.extendRecording(\n                channel, streamId, sourceLocation, recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send extend recording request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Extend an existing, non-active recording of a channel and stream pairing.\n     * <p>\n     * The channel must be configured for the initial position from which it will be extended. This can be done\n     * with {@link ChannelUriStringBuilder#initialPosition(long, int, int)}. The details required to initialise can\n     * be found by calling {@link #listRecording(long, RecordingDescriptorConsumer)}.\n     *\n     * @param recordingId    of the existing recording.\n     * @param channel        to be recorded.\n     * @param streamId       to be recorded.\n     * @param sourceLocation of the publication to be recorded.\n     * @param autoStop       if the recording should be automatically stopped when complete.\n     * @return the subscriptionId, i.e. {@link Subscription#registrationId()}, of the recording. This can be\n     * passed to {@link #stopRecording(long)}. However, if is autoStop is true then no need to stop the recording\n     * unless you want to abort early.\n     */\n    public long extendRecording(\n        final long recordingId,\n        final String channel,\n        final int streamId,\n        final SourceLocation sourceLocation,\n        final boolean autoStop)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.extendRecording(\n                channel, streamId, sourceLocation, autoStop, recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send extend recording request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Stop recording for a channel and stream pairing.\n     * <p>\n     * Channels that include sessionId parameters are considered different from channels without sessionIds. Stopping\n     * a recording on a channel without a sessionId parameter will not stop the recording of any sessionId specific\n     * recordings that use the same channel and streamId.\n     *\n     * @param channel  to stop recording for.\n     * @param streamId to stop recording for.\n     */\n    public void stopRecording(final String channel, final int streamId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.stopRecording(channel, streamId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send stop recording request\");\n            }\n\n            pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Try to stop a recording for a channel and stream pairing.\n     * <p>\n     * Channels that include sessionId parameters are considered different from channels without sessionIds. Stopping\n     * a recording on a channel without a sessionId parameter will not stop the recording of any sessionId specific\n     * recordings that use the same channel and streamId.\n     *\n     * @param channel  to stop recording for.\n     * @param streamId to stop recording for.\n     * @return {@code true} if the recording was stopped or false if the subscription is not currently active.\n     */\n    public boolean tryStopRecording(final String channel, final int streamId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.stopRecording(channel, streamId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send stop recording request\");\n            }\n\n            return pollForResponseAllowingError(lastCorrelationId, ArchiveException.UNKNOWN_SUBSCRIPTION);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Stop recording for a subscriptionId that has been returned from\n     * {@link #startRecording(String, int, SourceLocation)} or\n     * {@link #extendRecording(long, String, int, SourceLocation)}.\n     *\n     * @param subscriptionId is the {@link Subscription#registrationId()} for the recording in the archive.\n     */\n    public void stopRecording(final long subscriptionId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.stopRecording(subscriptionId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send stop recording request\");\n            }\n\n            pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Try stop a recording for a subscriptionId that has been returned from\n     * {@link #startRecording(String, int, SourceLocation)} or\n     * {@link #extendRecording(long, String, int, SourceLocation)}.\n     *\n     * @param subscriptionId is the {@link Subscription#registrationId()} for the recording in the archive.\n     * @return {@code true} if the recording was stopped or false if the subscription is not currently active.\n     */\n    public boolean tryStopRecording(final long subscriptionId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.stopRecording(subscriptionId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send stop recording request\");\n            }\n\n            return pollForResponseAllowingError(lastCorrelationId, ArchiveException.UNKNOWN_SUBSCRIPTION);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Try stop an active recording by its recording id.\n     *\n     * @param recordingId for which active recording should be stopped.\n     * @return {@code true} if the recording was stopped or false if the recording is not currently active.\n     */\n    public boolean tryStopRecordingByIdentity(final long recordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.stopRecordingByIdentity(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send stop recording request\");\n            }\n\n            return pollForResponse(lastCorrelationId) != 0;\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Stop recording a sessionId specific recording that pertains to the given {@link Publication}.\n     *\n     * @param publication to stop recording for.\n     */\n    public void stopRecording(final Publication publication)\n    {\n        stopRecording(ChannelUri.addSessionId(publication.channel(), publication.sessionId()), publication.streamId());\n    }\n\n    /**\n     * Start a replay for a length in bytes of a recording from a position. If the position is {@link #NULL_POSITION}\n     * then the stream will be replayed from the start.\n     * <p>\n     * The lower 32-bits of the returned value contains the {@link Image#sessionId()} of the received replay. All\n     * 64-bits are required to uniquely identify the replay when calling {@link #stopReplay(long)}. The lower 32-bits\n     * can be obtained by casting the {@code long} value to an {@code int}.\n     *\n     * @param recordingId    to be replayed.\n     * @param position       from which the replay should begin or {@link #NULL_POSITION} if from the start.\n     * @param length         of the stream to be replayed or {@link AeronArchive#REPLAY_ALL_AND_FOLLOW} to follow a live\n     *                       recording. Use {@link AeronArchive#REPLAY_ALL_AND_STOP} to read up the available limit\n     *                       and stop the replay.  {@link Long#MAX_VALUE} can also be used to follow a live stream.\n     * @param replayChannel  to which the replay should be sent.\n     * @param replayStreamId to which the replay should be sent.\n     * @return the id of the replay session which will be the same as the {@link Image#sessionId()} of the received\n     * replay for correlation with the matching channel and stream id in the lower 32 bits.\n     */\n    public long startReplay(\n        final long recordingId,\n        final long position,\n        final long length,\n        final String replayChannel,\n        final int replayStreamId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.replay(\n                recordingId,\n                position,\n                length,\n                replayChannel,\n                replayStreamId,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send replay request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Start a replay for a length in bytes of a recording from a position bounded by a position counter.\n     * If the position is {@link #NULL_POSITION} then the stream will be replayed from the start.\n     * <p>\n     * The lower 32-bits of the returned value contains the {@link Image#sessionId()} of the received replay. All\n     * 64-bits are required to uniquely identify the replay when calling {@link #stopReplay(long)}. The lower 32-bits\n     * can be obtained by casting the {@code long} value to an {@code int}.\n     *\n     * @param recordingId    to be replayed.\n     * @param position       from which the replay should begin or {@link #NULL_POSITION} if from the start.\n     * @param length         of the stream to be replayed or {@link AeronArchive#REPLAY_ALL_AND_FOLLOW} to follow a live\n     *                       recording. Use {@link AeronArchive#REPLAY_ALL_AND_STOP} to read up the available limit\n     *                       and stop the replay.  {@link Long#MAX_VALUE} can also be used to follow a live stream.\n     * @param limitCounterId to use to bound replay.\n     * @param replayChannel  to which the replay should be sent.\n     * @param replayStreamId to which the replay should be sent.\n     * @return the id of the replay session which will be the same as the {@link Image#sessionId()} of the received\n     * replay for correlation with the matching channel and stream id in the lower 32 bits.\n     */\n    public long startBoundedReplay(\n        final long recordingId,\n        final long position,\n        final long length,\n        final int limitCounterId,\n        final String replayChannel,\n        final int replayStreamId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.boundedReplay(\n                recordingId,\n                position,\n                length,\n                limitCounterId,\n                replayChannel,\n                replayStreamId,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send bounded replay request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Start a replay for a recording based upon the parameters set in ReplayParams. By default, it will replay\n     * all the recording from the start. The ReplayParams is free to be reused when this call completes.\n     *\n     * @param recordingId    to be replayed.\n     * @param replayChannel  to which the replay should be sent.\n     * @param replayStreamId to which the replay should be sent.\n     * @param replayParams   optional parameters for the replay\n     * @return the id of the replay session which will be the same as the {@link Image#sessionId()} of the received\n     * replay for correlation with the matching channel and stream id in the lower 32 bits.\n     * @see ReplayParams\n     */\n    public long startReplay(\n        final long recordingId,\n        final String replayChannel,\n        final int replayStreamId,\n        final ReplayParams replayParams)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            final ChannelUri replayChannelUri = ChannelUri.parse(replayChannel);\n            if (replayChannelUri.hasControlModeResponse())\n            {\n                return startReplayViaResponseChannel(recordingId, replayChannel, replayStreamId, replayParams);\n            }\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.replay(\n                recordingId,\n                replayChannel,\n                replayStreamId,\n                replayParams,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send bounded replay request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Stop an existing replay session.\n     *\n     * @param replaySessionId to stop replay for which would have been returned from\n     *                        {@link #startReplay(long, long, long, String, int)}.\n     */\n    public void stopReplay(final long replaySessionId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.stopReplay(replaySessionId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send stop replay request\");\n            }\n\n            pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Stop all replay sessions for a given recording id or all replays in general.\n     *\n     * @param recordingId to stop replay for or {@link Aeron#NULL_VALUE} for all replays.\n     */\n    public void stopAllReplays(final long recordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.stopAllReplays(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send stop all replays request\");\n            }\n\n            pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Replay a length in bytes of a recording from a position and for convenience create a {@link Subscription}\n     * to receive the replay. If the position is {@link #NULL_POSITION} then the stream will be replayed from the start.\n     *\n     * @param recordingId    to be replayed.\n     * @param position       from which the replay should begin or {@link #NULL_POSITION} if from the start.\n     * @param length         of the stream to be replayed or {@link AeronArchive#REPLAY_ALL_AND_FOLLOW} to follow a live\n     *                       recording. Use {@link AeronArchive#REPLAY_ALL_AND_STOP} to read up the available limit\n     *                       and stop the replay. {@link Long#MAX_VALUE} can also be used to follow a live stream.\n     * @param replayChannel  to which the replay should be sent.\n     * @param replayStreamId to which the replay should be sent.\n     * @return the {@link Subscription} for consuming the replay.\n     */\n    public Subscription replay(\n        final long recordingId,\n        final long position,\n        final long length,\n        final String replayChannel,\n        final int replayStreamId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            final ChannelUri replayChannelUri = ChannelUri.parse(replayChannel);\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.replay(\n                recordingId,\n                position,\n                length,\n                replayChannel,\n                replayStreamId,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send replay request\");\n            }\n\n            final int replaySessionId = (int)pollForResponse(lastCorrelationId);\n            replayChannelUri.put(SESSION_ID_PARAM_NAME, Integer.toString(replaySessionId));\n\n            return aeron.addSubscription(replayChannelUri.toString(), replayStreamId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Replay a length in bytes of a recording from a position and for convenience create a {@link Subscription}\n     * to receive the replay. If the position is {@link #NULL_POSITION} then the stream will be replayed from the start.\n     *\n     * @param recordingId             to be replayed.\n     * @param position                from which the replay should begin or {@link #NULL_POSITION} if from the start.\n     * @param length                  of the stream to be replayed or {@link AeronArchive#REPLAY_ALL_AND_FOLLOW} to\n     *                                follow a live recording. Use {@link AeronArchive#REPLAY_ALL_AND_STOP} to read up\n     *                                the available limit and stop the replay.  {@link Long#MAX_VALUE} can also be used\n     *                                to follow a live stream.\n     * @param replayChannel           to which the replay should be sent.\n     * @param replayStreamId          to which the replay should be sent.\n     * @param availableImageHandler   to be called when the replay image becomes available.\n     * @param unavailableImageHandler to be called when the replay image goes unavailable.\n     * @return the {@link Subscription} for consuming the replay.\n     */\n    public Subscription replay(\n        final long recordingId,\n        final long position,\n        final long length,\n        final String replayChannel,\n        final int replayStreamId,\n        final AvailableImageHandler availableImageHandler,\n        final UnavailableImageHandler unavailableImageHandler)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            final ChannelUri replayChannelUri = ChannelUri.parse(replayChannel);\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.replay(\n                recordingId,\n                position,\n                length,\n                replayChannel,\n                replayStreamId,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send replay request\");\n            }\n\n            final int replaySessionId = (int)pollForResponse(lastCorrelationId);\n            replayChannelUri.put(SESSION_ID_PARAM_NAME, Integer.toString(replaySessionId));\n\n            return aeron.addSubscription(\n                replayChannelUri.toString(), replayStreamId, availableImageHandler, unavailableImageHandler);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Replay a recording based upon the parameters set in ReplayParams. By default, it will replay all the recording\n     * from the start. The ReplayParams is free to be reused when this call completes.\n     *\n     * @param recordingId    to be replayed.\n     * @param replayChannel  to which the replay should be sent.\n     * @param replayStreamId to which the replay should be sent.\n     * @param replayParams   optional parameters for the replay\n     * @return the {@link Subscription} for consuming the replay.\n     * @see ReplayParams\n     */\n    public Subscription replay(\n        final long recordingId,\n        final String replayChannel,\n        final int replayStreamId,\n        final ReplayParams replayParams)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            final ChannelUri replayChannelUri = ChannelUri.parse(replayChannel);\n            if (replayChannelUri.hasControlModeResponse())\n            {\n                return replayViaResponseChannel(recordingId, replayChannel, replayStreamId, replayParams);\n            }\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.replay(\n                recordingId,\n                replayChannel,\n                replayStreamId,\n                replayParams,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send replay request\");\n            }\n\n            final int replaySessionId = (int)pollForResponse(lastCorrelationId);\n            replayChannelUri.put(SESSION_ID_PARAM_NAME, Integer.toString(replaySessionId));\n\n            return aeron.addSubscription(replayChannelUri.toString(), replayStreamId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * List all recording descriptors from a recording id with a limit of record count.\n     * <p>\n     * If the recording id is greater than the largest known id then nothing is returned.\n     *\n     * @param fromRecordingId at which to begin the listing.\n     * @param recordCount     to limit for each query.\n     * @param consumer        to which the descriptors are dispatched.\n     * @return the number of descriptors found and consumed.\n     */\n    public int listRecordings(\n        final long fromRecordingId, final int recordCount, final RecordingDescriptorConsumer consumer)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            isInCallback = true;\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.listRecordings(fromRecordingId, recordCount, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send list recordings request\");\n            }\n\n            return pollForDescriptors(lastCorrelationId, recordCount, consumer);\n        }\n        finally\n        {\n            isInCallback = false;\n            lock.unlock();\n        }\n    }\n\n    /**\n     * List recording descriptors from a recording id with a limit of record count for a given channelFragment and\n     * stream id.\n     * <p>\n     * If the recording id is greater than the largest known id then nothing is returned.\n     *\n     * @param fromRecordingId at which to begin the listing.\n     * @param recordCount     to limit for each query.\n     * @param channelFragment for a contains match on the original channel stored with the archive descriptor.\n     * @param streamId        to match.\n     * @param consumer        to which the descriptors are dispatched.\n     * @return the number of descriptors found and consumed.\n     */\n    public int listRecordingsForUri(\n        final long fromRecordingId,\n        final int recordCount,\n        final String channelFragment,\n        final int streamId,\n        final RecordingDescriptorConsumer consumer)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            isInCallback = true;\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.listRecordingsForUri(\n                fromRecordingId,\n                recordCount,\n                channelFragment,\n                streamId,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send list recordings request\");\n            }\n\n            return pollForDescriptors(lastCorrelationId, recordCount, consumer);\n        }\n        finally\n        {\n            isInCallback = false;\n            lock.unlock();\n        }\n    }\n\n    /**\n     * List a recording descriptor for a single recording id.\n     * <p>\n     * If the recording id is greater than the largest known id then nothing is returned.\n     *\n     * @param recordingId at which to begin the listing.\n     * @param consumer    to which the descriptors are dispatched.\n     * @return the number of descriptors found and consumed.\n     */\n    public int listRecording(final long recordingId, final RecordingDescriptorConsumer consumer)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n            isInCallback = true;\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.listRecording(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send list recording request\");\n            }\n\n            return pollForDescriptors(lastCorrelationId, 1, consumer);\n        }\n        finally\n        {\n            isInCallback = false;\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Get the start position for a recording.\n     *\n     * @param recordingId of the recording for which the position is required.\n     * @return the start position of a recording.\n     * @see #getStopPosition(long)\n     */\n    public long getStartPosition(final long recordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.getStartPosition(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send get start position request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Get the position recorded for an active recording. If no active recording then return {@link #NULL_POSITION}.\n     *\n     * @param recordingId of the active recording for which the position is required.\n     * @return the recorded position for the active recording or {@link #NULL_POSITION} if recording not active.\n     * @see #getStopPosition(long)\n     */\n    public long getRecordingPosition(final long recordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.getRecordingPosition(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send get recording position request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Get the stop position for a recording.\n     *\n     * @param recordingId of the recording for which the position is required.\n     * @return the stop position, or {@link #NULL_POSITION} if still active.\n     * @see #getRecordingPosition(long)\n     */\n    public long getStopPosition(final long recordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.getStopPosition(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send get stop position request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Get the max recorded position of a recording. For active recordings it will be the recording position,\n     * and for inactive recordings it will be the stop position.\n     *\n     * @param recordingId of the recording for which the position is required.\n     * @return the max recorded position of the recording.\n     * @since 1.44.0\n     */\n    public long getMaxRecordedPosition(final long recordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.getMaxRecordedPosition(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send get max recorded position request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Get the id of the Archive.\n     *\n     * @return the id of the Archive.\n     * @since 1.44.0\n     */\n    public long archiveId()\n    {\n        return archiveId;\n    }\n\n    /**\n     * Find the last recording that matches the given criteria.\n     *\n     * @param minRecordingId  to search back to.\n     * @param channelFragment for a contains match on the original channel stored with the archive descriptor.\n     * @param streamId        of the recording to match.\n     * @param sessionId       of the recording to match.\n     * @return the recordingId if found otherwise {@link Aeron#NULL_VALUE} if not found.\n     */\n    public long findLastMatchingRecording(\n        final long minRecordingId, final String channelFragment, final int streamId, final int sessionId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.findLastMatchingRecording(\n                minRecordingId, channelFragment, streamId, sessionId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send find last matching recording request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Truncate a stopped recording to a given position that is less than the stopped position. The provided position\n     * must be on a fragment boundary. Truncating a recording to the start position effectively deletes the recording.\n     *\n     * @param recordingId of the stopped recording to be truncated.\n     * @param position    to which the recording will be truncated.\n     * @return count of deleted segment files.\n     */\n    public long truncateRecording(final long recordingId, final long position)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.truncateRecording(recordingId, position, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send truncate recording request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n\n    /**\n     * Purge a stopped recording, i.e. mark recording as {@link io.aeron.archive.codecs.RecordingState#INVALID}\n     * and delete the corresponding segment files. The space in the Catalog will be reclaimed upon compaction.\n     *\n     * @param recordingId of the stopped recording to be purged.\n     * @return count of deleted segment files.\n     */\n    public long purgeRecording(final long recordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.purgeRecording(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send invalidate recording request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * List active recording subscriptions in the archive. These are the result of requesting one of\n     * {@link #startRecording(String, int, SourceLocation)} or a\n     * {@link #extendRecording(long, String, int, SourceLocation)}. The returned subscription id can be used for\n     * passing to {@link #stopRecording(long)}.\n     *\n     * @param pseudoIndex       in the active list at which to begin for paging.\n     * @param subscriptionCount to get in a listing.\n     * @param channelFragment   to do a contains match on the stripped channel URI. Empty string is match all.\n     * @param streamId          to match on the subscription.\n     * @param applyStreamId     true if the stream id should be matched.\n     * @param consumer          for the matched subscription descriptors.\n     * @return the count of matched subscriptions.\n     */\n    public int listRecordingSubscriptions(\n        final int pseudoIndex,\n        final int subscriptionCount,\n        final String channelFragment,\n        final int streamId,\n        final boolean applyStreamId,\n        final RecordingSubscriptionDescriptorConsumer consumer)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            isInCallback = true;\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.listRecordingSubscriptions(\n                pseudoIndex,\n                subscriptionCount,\n                channelFragment,\n                streamId,\n                applyStreamId,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send list recording subscriptions request\");\n            }\n\n            return pollForSubscriptionDescriptors(lastCorrelationId, subscriptionCount, consumer);\n        }\n        finally\n        {\n            isInCallback = false;\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The source recording will be replayed via the provided replay channel and use the original stream id.\n     * If the destination recording id is {@link io.aeron.Aeron#NULL_VALUE} then a new destination recording is created,\n     * otherwise the provided destination recording id will be extended. The details of the source recording\n     * descriptor will be replicated.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param dstRecordingId     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param liveDestination    destination for the live stream if merge is required. Empty or null for no merge.\n     * @return return the replication session id which can be passed later to {@link #stopReplication(long)}.\n     */\n    public long replicate(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.replicate(\n                srcRecordingId,\n                dstRecordingId,\n                srcControlStreamId,\n                srcControlChannel,\n                liveDestination,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send replicate request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The source recording will be replayed via the provided replay channel and use the original stream id.\n     * If the destination recording id is {@link io.aeron.Aeron#NULL_VALUE} then a new destination recording is created,\n     * otherwise the provided destination recording id will be extended. The details of the source recording\n     * descriptor will be replicated.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     * <p>\n     * Stop recording this stream when the position of the destination reaches the specified stop position.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param dstRecordingId     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.\n     * @param stopPosition       position to stop the replication. {@link AeronArchive#NULL_POSITION} to stop at end\n     *                           of current recording.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param liveDestination    destination for the live stream if merge is required. Empty or null for no merge.\n     * @param replicationChannel channel over which the replication will occur. Empty or null for default channel.\n     * @return return the replication session id which can be passed later to {@link #stopReplication(long)}.\n     */\n    public long replicate(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long stopPosition,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination,\n        final String replicationChannel)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.replicate(\n                srcRecordingId,\n                dstRecordingId,\n                stopPosition,\n                srcControlStreamId,\n                srcControlChannel,\n                liveDestination,\n                replicationChannel,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send replicate request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The source recording will be replayed via the provided replay channel and use the original stream id.\n     * If the destination recording id is {@link io.aeron.Aeron#NULL_VALUE} then a new destination recording is created,\n     * otherwise the provided destination recording id will be extended. The details of the source recording\n     * descriptor will be replicated. The subscription used in the archive will be tagged with the provided tags.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param dstRecordingId     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.\n     * @param channelTagId       used to tag the replication subscription.\n     * @param subscriptionTagId  used to tag the replication subscription.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param liveDestination    destination for the live stream if merge is required. Empty or null for no merge.\n     * @return return the replication session id which can be passed later to {@link #stopReplication(long)}.\n     */\n    public long taggedReplicate(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long channelTagId,\n        final long subscriptionTagId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.taggedReplicate(\n                srcRecordingId,\n                dstRecordingId,\n                channelTagId,\n                subscriptionTagId,\n                srcControlStreamId,\n                srcControlChannel,\n                liveDestination,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send tagged replicate request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The source recording will be replayed via the provided replay channel and use the original stream id.\n     * If the destination recording id is {@link io.aeron.Aeron#NULL_VALUE} then a new destination recording is created,\n     * otherwise the provided destination recording id will be extended. The details of the source recording\n     * descriptor will be replicated. The subscription used in the archive will be tagged with the provided tags.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param dstRecordingId     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.\n     * @param stopPosition       position to stop the replication. {@link AeronArchive#NULL_POSITION} to stop at end\n     *                           of current recording.\n     * @param channelTagId       used to tag the replication subscription.\n     * @param subscriptionTagId  used to tag the replication subscription.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param liveDestination    destination for the live stream if merge is required. Empty or null for no merge.\n     * @param replicationChannel channel over which the replication will occur. Empty or null for default channel.\n     * @return return the replication session id which can be passed later to {@link #stopReplication(long)}.\n     */\n    public long taggedReplicate(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long stopPosition,\n        final long channelTagId,\n        final long subscriptionTagId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination,\n        final String replicationChannel)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.taggedReplicate(\n                srcRecordingId,\n                dstRecordingId,\n                stopPosition,\n                channelTagId,\n                subscriptionTagId,\n                srcControlStreamId,\n                srcControlChannel,\n                liveDestination,\n                replicationChannel,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send tagged replicate request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The behaviour of the replication is controlled through the {@link ReplicationParams}.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     * <p>\n     * The ReplicationParams is free to be reused when this call completes.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param replicationParams  Optional parameters to control the behaviour of the replication.\n     * @return return the replication session id which can be passed later to {@link #stopReplication(long)}.\n     */\n    public long replicate(\n        final long srcRecordingId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final ReplicationParams replicationParams)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.replicate(\n                srcRecordingId,\n                srcControlStreamId,\n                srcControlChannel,\n                replicationParams,\n                lastCorrelationId,\n                controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send replicate request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Stop a replication session by id returned from {@link #replicate(long, long, int, String, String)}.\n     *\n     * @param replicationId to stop replication for.\n     * @see #replicate(long, long, int, String, String)\n     */\n    public void stopReplication(final long replicationId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.stopReplication(replicationId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send stop replication request\");\n            }\n\n            pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n\n    /**\n     * Attempt to stop a replication session by id returned from {@link #replicate(long, long, int, String, String)}.\n     *\n     * @param replicationId to stop replication for.\n     * @return {@code true} if the replication was stopped, false if the replication is not active.\n     * @see #replicate(long, long, int, String, String)\n     */\n    public boolean tryStopReplication(final long replicationId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.stopReplication(replicationId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send stop replication request\");\n            }\n\n            return pollForResponseAllowingError(lastCorrelationId, ArchiveException.UNKNOWN_REPLICATION);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Detach segments from the beginning of a recording up to the provided new start position.\n     * <p>\n     * The new start position must be first byte position of a segment after the existing start position.\n     * <p>\n     * It is not possible to detach segments which are active for recording or being replayed.\n     *\n     * @param recordingId      to which the operation applies.\n     * @param newStartPosition for the recording after the segments are detached.\n     * @see #segmentFileBasePosition(long, long, int, int)\n     */\n    public void detachSegments(final long recordingId, final long newStartPosition)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.detachSegments(recordingId, newStartPosition, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send detach segments request\");\n            }\n\n            pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Delete segments which have been previously detached from a recording.\n     *\n     * @param recordingId to which the operation applies.\n     * @return count of deleted segment files.\n     * @see #detachSegments(long, long)\n     */\n    public long deleteDetachedSegments(final long recordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.deleteDetachedSegments(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send delete detached segments request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Purge (detach and delete) segments from the beginning of a recording up to the provided new start position.\n     * <p>\n     * The new start position must be first byte position of a segment after the existing start position.\n     * <p>\n     * It is not possible to detach segments which are active for recording or being replayed.\n     *\n     * @param recordingId      to which the operation applies.\n     * @param newStartPosition for the recording after the segments are detached.\n     * @return count of deleted segment files.\n     * @see #detachSegments(long, long)\n     * @see #deleteDetachedSegments(long)\n     * @see #segmentFileBasePosition(long, long, int, int)\n     */\n    public long purgeSegments(final long recordingId, final long newStartPosition)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.purgeSegments(recordingId, newStartPosition, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send purge segments request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Attach segments to the beginning of a recording to restore history that was previously detached.\n     * <p>\n     * Segment files must match the existing recording and join exactly to the start position of the recording\n     * they are being attached to.\n     *\n     * @param recordingId to which the operation applies.\n     * @return count of attached segment files.\n     * @see #detachSegments(long, long)\n     */\n    public long attachSegments(final long recordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.attachSegments(recordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send attach segments request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Migrate segments from a source recording and attach them to the beginning or end of a destination recording.\n     * <p>\n     * The source recording must match the destination recording for segment length, term length, mtu length,\n     * stream id. The source recording must join to the destination recording on a segment boundary and without gaps,\n     * i.e., the stop position and term id of one must match the start position and term id of the other.\n     * <p>\n     * The source recording must be stopped. The destination recording must be stopped if migrating segments\n     * to the end of the destination recording.\n     * <p>\n     * The source recording will be effectively truncated back to its start position after the migration.\n     *\n     * @param srcRecordingId source recording from which the segments will be migrated.\n     * @param dstRecordingId destination recording to which the segments will be attached.\n     * @return count of attached segment files.\n     */\n    public long migrateSegments(final long srcRecordingId, final long dstRecordingId)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.migrateSegments(srcRecordingId, dstRecordingId, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send migrate segments request\");\n            }\n\n            return pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * Update the channel for a recording, i.e. replace original and stripped channel information in the catalog.\n     *\n     * @param recordingId the recording id to update.\n     * @param newChannel  to use in the catalogue.\n     */\n    public void updateChannel(final long recordingId, final String newChannel)\n    {\n        lock.lock();\n        try\n        {\n            ensureConnected();\n            ensureNotReentrant();\n\n            lastCorrelationId = aeron.nextCorrelationId();\n\n            if (!archiveProxy.updateChannel(recordingId, newChannel, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send migrate segments request\");\n            }\n\n            pollForResponse(lastCorrelationId);\n        }\n        finally\n        {\n            lock.unlock();\n        }\n    }\n\n    private void checkDeadline(final long deadlineNs, final String errorMessage, final long correlationId)\n    {\n        if (deadlineNs - nanoClock.nanoTime() < 0)\n        {\n            throw new TimeoutException(\n                errorMessage + \" - correlationId=\" + correlationId + \" messageTimeout=\" +\n                SystemUtil.formatDuration(messageTimeoutNs));\n        }\n\n        if (Thread.currentThread().isInterrupted())\n        {\n            throw new AeronException(\"unexpected interrupt\");\n        }\n    }\n\n    private void pollNextResponse(final long correlationId, final long deadlineNs, final ControlResponsePoller poller)\n    {\n        idleStrategy.reset();\n\n        while (true)\n        {\n            final int fragments = poller.poll();\n\n            if (poller.isPollComplete())\n            {\n                if (poller.templateId() == RecordingSignalEventDecoder.TEMPLATE_ID &&\n                    poller.controlSessionId() == controlSessionId)\n                {\n                    dispatchRecordingSignal(poller);\n                    continue;\n                }\n\n                break;\n            }\n\n            if (fragments > 0)\n            {\n                continue;\n            }\n\n            final Subscription subscription = poller.subscription();\n            checkForDisconnect(subscription);\n\n            checkDeadline(deadlineNs, \"awaiting response\", correlationId);\n            idleStrategy.idle(context.runInvokers());\n        }\n    }\n\n    private void checkForDisconnect(final Subscription subscription)\n    {\n        if (!subscription.isConnected())\n        {\n            state(State.DISCONNECTED);\n            throw new ArchiveException(\n                \"response channel from archive is not connected, \" +\n                \"channel=\" + subscription.channel() +\n                \", streamId=\" + subscription.streamId() +\n                \", imageCount=\" + subscription.imageCount());\n        }\n    }\n\n    private long pollForResponse(final long correlationId)\n    {\n        final long deadlineNs = nanoClock.nanoTime() + messageTimeoutNs;\n        final ControlResponsePoller poller = controlResponsePoller;\n\n        while (true)\n        {\n            pollNextResponse(correlationId, deadlineNs, poller);\n\n            if (poller.controlSessionId() != controlSessionId)\n            {\n                context.runInvokers();\n                continue;\n            }\n\n            final ControlResponseCode code = poller.code();\n            if (ControlResponseCode.ERROR == code)\n            {\n                final ArchiveException ex = new ArchiveException(\n                    \"response for correlationId=\" + correlationId + \", error: \" + poller.errorMessage(),\n                    (int)poller.relevantId(),\n                    poller.correlationId());\n\n                if (poller.correlationId() == correlationId)\n                {\n                    throw ex;\n                }\n                else if (context.errorHandler() != null)\n                {\n                    context.errorHandler().onError(ex);\n                }\n            }\n            else if (poller.correlationId() == correlationId)\n            {\n                if (ControlResponseCode.OK != code)\n                {\n                    throw new ArchiveException(\"unexpected response code: \" + code);\n                }\n\n                return poller.relevantId();\n            }\n        }\n    }\n\n    private boolean pollForResponseAllowingError(final long correlationId, final int allowedErrorCode)\n    {\n        final long deadlineNs = nanoClock.nanoTime() + messageTimeoutNs;\n        final ControlResponsePoller poller = controlResponsePoller;\n\n        while (true)\n        {\n            pollNextResponse(correlationId, deadlineNs, poller);\n\n            if (poller.controlSessionId() != controlSessionId)\n            {\n                context.runInvokers();\n                continue;\n            }\n\n            final ControlResponseCode code = poller.code();\n            if (ControlResponseCode.ERROR == code)\n            {\n                final long relevantId = poller.relevantId();\n                if (poller.correlationId() == correlationId)\n                {\n                    if (relevantId == allowedErrorCode)\n                    {\n                        return false;\n                    }\n\n                    throw new ArchiveException(\n                        \"response for correlationId=\" + correlationId + \", error: \" + poller.errorMessage(),\n                        (int)relevantId,\n                        poller.correlationId());\n                }\n                else if (context.errorHandler() != null)\n                {\n                    context.errorHandler().onError(new ArchiveException(\n                        \"response for correlationId=\" + correlationId + \", error: \" + poller.errorMessage(),\n                        (int)relevantId,\n                        poller.correlationId()));\n                }\n            }\n            else if (poller.correlationId() == correlationId)\n            {\n                if (ControlResponseCode.OK != code)\n                {\n                    throw new ArchiveException(\"unexpected response code: \" + code);\n                }\n\n                return true;\n            }\n        }\n    }\n\n    private int pollForDescriptors(\n        final long correlationId, final int count, final RecordingDescriptorConsumer consumer)\n    {\n        int existingRemainCount = count;\n        long deadlineNs = nanoClock.nanoTime() + messageTimeoutNs;\n        final RecordingDescriptorPoller poller = recordingDescriptorPoller();\n        poller.reset(correlationId, count, consumer);\n        idleStrategy.reset();\n\n        while (true)\n        {\n            final int fragments = poller.poll();\n            final int remainingRecordCount = poller.remainingRecordCount();\n\n            if (poller.isDispatchComplete())\n            {\n                return count - remainingRecordCount;\n            }\n\n            if (remainingRecordCount != existingRemainCount)\n            {\n                existingRemainCount = remainingRecordCount;\n                deadlineNs = nanoClock.nanoTime() + messageTimeoutNs;\n            }\n\n            if (fragments > 0)\n            {\n                continue;\n            }\n\n            checkForDisconnect(poller.subscription());\n\n            checkDeadline(deadlineNs, \"awaiting recording descriptors\", correlationId);\n            idleStrategy.idle(context.runInvokers());\n        }\n    }\n\n    private int pollForSubscriptionDescriptors(\n        final long correlationId, final int count, final RecordingSubscriptionDescriptorConsumer consumer)\n    {\n        int existingRemainCount = count;\n        long deadlineNs = nanoClock.nanoTime() + messageTimeoutNs;\n        final RecordingSubscriptionDescriptorPoller poller = recordingSubscriptionDescriptorPoller();\n        poller.reset(correlationId, count, consumer);\n        idleStrategy.reset();\n\n        while (true)\n        {\n            final int fragments = poller.poll();\n            final int remainingSubscriptionCount = poller.remainingSubscriptionCount();\n\n            if (poller.isDispatchComplete())\n            {\n                return count - remainingSubscriptionCount;\n            }\n\n            if (remainingSubscriptionCount != existingRemainCount)\n            {\n                existingRemainCount = remainingSubscriptionCount;\n                deadlineNs = nanoClock.nanoTime() + messageTimeoutNs;\n            }\n\n            if (fragments > 0)\n            {\n                continue;\n            }\n\n            checkForDisconnect(poller.subscription());\n\n            checkDeadline(deadlineNs, \"awaiting subscription descriptors\", correlationId);\n            idleStrategy.idle(context.runInvokers());\n        }\n    }\n\n    private void dispatchRecordingSignal(final ControlResponsePoller poller)\n    {\n        context.recordingSignalConsumer().onSignal(\n            poller.controlSessionId(),\n            poller.correlationId(),\n            poller.recordingId(),\n            poller.subscriptionId(),\n            poller.position(),\n            poller.recordingSignal());\n    }\n\n    private void ensureConnected()\n    {\n        final State currentState = state;\n        if (State.CONNECTED != currentState)\n        {\n            if (State.CLOSED == currentState)\n            {\n                throw new ArchiveException(\"client is closed\");\n            }\n            else\n            {\n                close();\n            }\n        }\n    }\n\n    private void ensureNotReentrant()\n    {\n        if (isInCallback)\n        {\n            throw new AeronException(\"reentrant calls not permitted during callbacks\");\n        }\n    }\n\n    private void state(final State newState)\n    {\n        if (State.CLOSED != state)\n        {\n            state = newState;\n        }\n    }\n\n    /**\n     * Common configuration properties for communicating with an Aeron archive.\n     */\n    @Config(existsInC = false)\n    public static final class Configuration\n    {\n        private Configuration()\n        {\n        }\n\n        /**\n         * Major version of the network protocol from client to archive. If these don't match then client and archive\n         * are not compatible.\n         */\n        public static final int PROTOCOL_MAJOR_VERSION = 1;\n\n        /**\n         * Minor version of the network protocol from client to archive. If these don't match then some features may\n         * not be available.\n         */\n        public static final int PROTOCOL_MINOR_VERSION = 12;\n\n        /**\n         * Patch version of the network protocol from client to archive. If these don't match then bug fixes may not\n         * have been applied.\n         */\n        public static final int PROTOCOL_PATCH_VERSION = 0;\n\n        /**\n         * Combined semantic version for the archive control protocol.\n         *\n         * @see SemanticVersion\n         */\n        public static final int PROTOCOL_SEMANTIC_VERSION = SemanticVersion.compose(\n            PROTOCOL_MAJOR_VERSION, PROTOCOL_MINOR_VERSION, PROTOCOL_PATCH_VERSION);\n\n        /**\n         * Timeout in nanoseconds when waiting on a message to be sent or received.\n         */\n        @Config\n        public static final String MESSAGE_TIMEOUT_PROP_NAME = \"aeron.archive.message.timeout\";\n\n        /**\n         * Timeout when waiting on a message to be sent or received.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000)\n        public static final long MESSAGE_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10);\n\n        /**\n         * The number of retry attempts to be made when offering messages to the archive.\n         */\n        @Config\n        public static final String MESSAGE_RETRY_ATTEMPTS_PROP_NAME = \"aeron.archive.message.retry.attempts\";\n\n        /**\n         * Default number of retry attempts to be made when offering messages to the archive.\n         */\n        @Config\n        public static final int MESSAGE_RETRY_ATTEMPTS_DEFAULT = 3;\n\n        /**\n         * Channel for sending control messages to an archive.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String CONTROL_CHANNEL_PROP_NAME = \"aeron.archive.control.channel\";\n\n        /**\n         * Stream id within a channel for sending control messages to an archive.\n         */\n        @Config\n        public static final String CONTROL_STREAM_ID_PROP_NAME = \"aeron.archive.control.stream.id\";\n\n        /**\n         * Stream id within a channel for sending control messages to an archive.\n         */\n        @Config\n        public static final int CONTROL_STREAM_ID_DEFAULT = 10;\n\n        /**\n         * Channel for sending control messages to a driver local archive.\n         */\n        @Config(hasContext = false)\n        public static final String LOCAL_CONTROL_CHANNEL_PROP_NAME = \"aeron.archive.local.control.channel\";\n\n        /**\n         * Channel for sending control messages to a driver local archive. Default to IPC.\n         */\n        @Config\n        public static final String LOCAL_CONTROL_CHANNEL_DEFAULT = \"aeron:ipc?term-length=64k\";\n\n        /**\n         * Stream id within a channel for sending control messages to a driver local archive.\n         */\n        @Config(hasContext = false)\n        public static final String LOCAL_CONTROL_STREAM_ID_PROP_NAME = \"aeron.archive.local.control.stream.id\";\n\n        /**\n         * Stream id within a channel for sending control messages to a driver local archive.\n         */\n        @Config\n        public static final int LOCAL_CONTROL_STREAM_ID_DEFAULT = CONTROL_STREAM_ID_DEFAULT;\n\n        /**\n         * Channel for receiving control response messages from an archive.\n         *\n         * <p>\n         * Channel's <em>endpoint</em> can be specified explicitly (i.e. by providing address and port pair) or\n         * by using zero as a port number. Here is an example of valid response channels:\n         * <ul>\n         *     <li>{@code aeron:udp?endpoint=localhost:8020} - listen on port {@code 8020} on localhost.</li>\n         *     <li>{@code aeron:udp?endpoint=192.168.10.10:8020} - listen on port {@code 8020} on\n         *     {@code 192.168.10.10}.</li>\n         *     <li>{@code aeron:udp?endpoint=localhost:0} - in this case the port is unspecified and the OS\n         *     will assign a free port from the\n         *     <a href=\"https://en.wikipedia.org/wiki/Ephemeral_port\">ephemeral port range</a>.</li>\n         * </ul>\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String CONTROL_RESPONSE_CHANNEL_PROP_NAME = \"aeron.archive.control.response.channel\";\n\n        /**\n         * Stream id within a channel for receiving control messages from an archive.\n         */\n        @Config\n        public static final String CONTROL_RESPONSE_STREAM_ID_PROP_NAME = \"aeron.archive.control.response.stream.id\";\n\n        /**\n         * Stream id within a channel for receiving control messages from an archive.\n         */\n        @Config\n        public static final int CONTROL_RESPONSE_STREAM_ID_DEFAULT = 20;\n\n        /**\n         * Channel for receiving progress events of recordings from an archive.\n         */\n        @Config\n        public static final String RECORDING_EVENTS_CHANNEL_PROP_NAME = \"aeron.archive.recording.events.channel\";\n\n        /**\n         * Channel for receiving progress events of recordings from an archive.\n         * For production, it is recommended that multicast or dynamic multi-destination-cast (MDC) is used to allow\n         * for dynamic subscribers, an endpoint can be added to the subscription side for controlling port usage.\n         */\n        @Config\n        public static final String RECORDING_EVENTS_CHANNEL_DEFAULT =\n            \"aeron:udp?control-mode=dynamic|control=localhost:8030\";\n\n        /**\n         * Stream id within a channel for receiving progress of recordings from an archive.\n         */\n        @Config\n        public static final String RECORDING_EVENTS_STREAM_ID_PROP_NAME = \"aeron.archive.recording.events.stream.id\";\n\n        /**\n         * Stream id within a channel for receiving progress of recordings from an archive.\n         */\n        @Config\n        public static final int RECORDING_EVENTS_STREAM_ID_DEFAULT = 30;\n\n        /**\n         * Is channel enabled for recording progress events of recordings from an archive.\n         */\n        @Config\n        public static final String RECORDING_EVENTS_ENABLED_PROP_NAME = \"aeron.archive.recording.events.enabled\";\n\n        /**\n         * Channel enabled for recording progress events of recordings from an archive which defaults to false.\n         */\n        @Config\n        public static final boolean RECORDING_EVENTS_ENABLED_DEFAULT = false;\n\n        /**\n         * Sparse term buffer indicator for control streams.\n         */\n        @Config\n        public static final String CONTROL_TERM_BUFFER_SPARSE_PROP_NAME = \"aeron.archive.control.term.buffer.sparse\";\n\n        /**\n         * Overrides {@link io.aeron.driver.Configuration#TERM_BUFFER_SPARSE_FILE_PROP_NAME} for if term buffer files\n         * are sparse on the control channel.\n         */\n        @Config\n        public static final boolean CONTROL_TERM_BUFFER_SPARSE_DEFAULT = true;\n\n        /**\n         * Term length for control streams.\n         */\n        @Config\n        public static final String CONTROL_TERM_BUFFER_LENGTH_PROP_NAME = \"aeron.archive.control.term.buffer.length\";\n\n        /**\n         * Low term length for control channel reflects expected low bandwidth usage.\n         */\n        @Config\n        public static final int CONTROL_TERM_BUFFER_LENGTH_DEFAULT = 64 * 1024;\n\n        /**\n         * MTU length for control streams.\n         */\n        @Config\n        public static final String CONTROL_MTU_LENGTH_PROP_NAME = \"aeron.archive.control.mtu.length\";\n\n        /**\n         * MTU to reflect default for the control streams.\n         */\n        @Config(defaultType = DefaultType.INT, defaultValueString = \"io.aeron.driver.Configuration.mtuLength()\")\n        public static final int CONTROL_MTU_LENGTH_DEFAULT = io.aeron.driver.Configuration.mtuLength();\n\n        /**\n         * System property to name Archive client. Defaults to empty string.\n         *\n         * @since 1.49.0\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\", skipCDefaultValidation = true)\n        public static final String CLIENT_NAME_PROP_NAME = \"aeron.archive.client.name\";\n\n        /**\n         * Default no operation {@link RecordingSignalConsumer} to be used when not set explicitly.\n         */\n        public static final RecordingSignalConsumer NO_OP_RECORDING_SIGNAL_CONSUMER =\n            (controlSessionId, correlationId, recordingId, subscriptionId, position, signal) -> {};\n\n        /**\n         * The timeout in nanoseconds to wait for a message.\n         *\n         * @return timeout in nanoseconds to wait for a message.\n         * @see #MESSAGE_TIMEOUT_PROP_NAME\n         */\n        public static long messageTimeoutNs()\n        {\n            return getDurationInNanos(MESSAGE_TIMEOUT_PROP_NAME, MESSAGE_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * The number of retry attempts to be made when offering messages to the archive.\n         *\n         * @return the number of retry attempts.\n         * @see #MESSAGE_RETRY_ATTEMPTS_PROP_NAME\n         */\n        public static int messageRetryAttempts()\n        {\n            return Integer.getInteger(MESSAGE_RETRY_ATTEMPTS_PROP_NAME, MESSAGE_RETRY_ATTEMPTS_DEFAULT);\n        }\n\n        /**\n         * Should term buffer files be sparse for control request and response streams.\n         *\n         * @return {@code true} if term buffer files should be sparse for control request and response streams.\n         * @see #CONTROL_TERM_BUFFER_SPARSE_PROP_NAME\n         */\n        public static boolean controlTermBufferSparse()\n        {\n            final String propValue = System.getProperty(\n                CONTROL_TERM_BUFFER_SPARSE_PROP_NAME, Boolean.toString(CONTROL_TERM_BUFFER_SPARSE_DEFAULT));\n            return \"true\".equals(propValue);\n        }\n\n        /**\n         * Term buffer length to be used for control request and response streams.\n         *\n         * @return term buffer length to be used for control request and response streams.\n         * @see #CONTROL_TERM_BUFFER_LENGTH_PROP_NAME\n         */\n        public static int controlTermBufferLength()\n        {\n            return getSizeAsInt(CONTROL_TERM_BUFFER_LENGTH_PROP_NAME, CONTROL_TERM_BUFFER_LENGTH_DEFAULT);\n        }\n\n        /**\n         * MTU length to be used for control request and response streams.\n         *\n         * @return MTU length to be used for control request and response streams.\n         * @see #CONTROL_MTU_LENGTH_PROP_NAME\n         */\n        public static int controlMtuLength()\n        {\n            return getSizeAsInt(CONTROL_MTU_LENGTH_PROP_NAME, CONTROL_MTU_LENGTH_DEFAULT);\n        }\n\n        /**\n         * The value of system property {@link #CONTROL_CHANNEL_PROP_NAME} if set, null otherwise.\n         *\n         * @return system property {@link #CONTROL_CHANNEL_PROP_NAME} if set.\n         */\n        public static String controlChannel()\n        {\n            return System.getProperty(CONTROL_CHANNEL_PROP_NAME);\n        }\n\n        /**\n         * The value {@link #CONTROL_STREAM_ID_DEFAULT} or system property\n         * {@link #CONTROL_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #CONTROL_STREAM_ID_DEFAULT} or system property\n         * {@link #CONTROL_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int controlStreamId()\n        {\n            return Integer.getInteger(CONTROL_STREAM_ID_PROP_NAME, CONTROL_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #LOCAL_CONTROL_CHANNEL_DEFAULT} or system property\n         * {@link #LOCAL_CONTROL_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #LOCAL_CONTROL_CHANNEL_DEFAULT} or system property\n         * {@link #LOCAL_CONTROL_CHANNEL_PROP_NAME} if set.\n         */\n        public static String localControlChannel()\n        {\n            return System.getProperty(LOCAL_CONTROL_CHANNEL_PROP_NAME, LOCAL_CONTROL_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #LOCAL_CONTROL_STREAM_ID_DEFAULT} or system property\n         * {@link #LOCAL_CONTROL_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #LOCAL_CONTROL_STREAM_ID_DEFAULT} or system property\n         * {@link #LOCAL_CONTROL_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int localControlStreamId()\n        {\n            return Integer.getInteger(LOCAL_CONTROL_STREAM_ID_PROP_NAME, LOCAL_CONTROL_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The value of system property {@link #CONTROL_RESPONSE_CHANNEL_PROP_NAME} if set, null otherwise.\n         *\n         * @return of system property {@link #CONTROL_RESPONSE_CHANNEL_PROP_NAME} if set.\n         */\n        public static String controlResponseChannel()\n        {\n            return System.getProperty(CONTROL_RESPONSE_CHANNEL_PROP_NAME);\n        }\n\n        /**\n         * The value {@link #CONTROL_RESPONSE_STREAM_ID_DEFAULT} or system property\n         * {@link #CONTROL_RESPONSE_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #CONTROL_RESPONSE_STREAM_ID_DEFAULT} or system property\n         * {@link #CONTROL_RESPONSE_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int controlResponseStreamId()\n        {\n            return Integer.getInteger(CONTROL_RESPONSE_STREAM_ID_PROP_NAME, CONTROL_RESPONSE_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The value of system property {@link #RECORDING_EVENTS_CHANNEL_PROP_NAME} if set, null otherwise.\n         *\n         * @return system property {@link #RECORDING_EVENTS_CHANNEL_PROP_NAME} if set.\n         */\n        public static String recordingEventsChannel()\n        {\n            return System.getProperty(RECORDING_EVENTS_CHANNEL_PROP_NAME);\n        }\n\n        /**\n         * The value {@link #RECORDING_EVENTS_STREAM_ID_DEFAULT} or system property\n         * {@link #RECORDING_EVENTS_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #RECORDING_EVENTS_STREAM_ID_DEFAULT} or system property\n         * {@link #RECORDING_EVENTS_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int recordingEventsStreamId()\n        {\n            return Integer.getInteger(RECORDING_EVENTS_STREAM_ID_PROP_NAME, RECORDING_EVENTS_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * Should the recording events stream be enabled.\n         *\n         * @return {@code true} if the recording events stream be enabled.\n         * @see #RECORDING_EVENTS_ENABLED_PROP_NAME\n         */\n        public static boolean recordingEventsEnabled()\n        {\n            final String propValue = System.getProperty(\n                RECORDING_EVENTS_ENABLED_PROP_NAME, Boolean.toString(RECORDING_EVENTS_ENABLED_DEFAULT));\n            return \"true\".equals(propValue);\n        }\n\n        /**\n         * Get the configured client name.\n         *\n         * @return specified client name or empty string if not set.\n         * @see #CLIENT_NAME_PROP_NAME\n         * @since 1.49.0\n         */\n        public static String clientName()\n        {\n            return getProperty(CLIENT_NAME_PROP_NAME, \"\");\n        }\n    }\n\n    /**\n     * Specialised configuration options for communicating with an Aeron Archive.\n     * <p>\n     * The context will be owned by {@link AeronArchive} after a successful\n     * {@link AeronArchive#connect(Context)} and closed via {@link AeronArchive#close()}.\n     */\n    public static final class Context implements Cloneable\n    {\n        private static final VarHandle IS_CONCLUDED_VH;\n\n        static\n        {\n            try\n            {\n                IS_CONCLUDED_VH = MethodHandles.lookup().findVarHandle(Context.class, \"isConcluded\", boolean.class);\n            }\n            catch (final ReflectiveOperationException ex)\n            {\n                throw new ExceptionInInitializerError(ex);\n            }\n        }\n\n        private volatile boolean isConcluded;\n        private long messageTimeoutNs = Configuration.messageTimeoutNs();\n        private int messageRetryAttempts = Configuration.messageRetryAttempts();\n        private String clientName = AeronArchive.Configuration.clientName();\n        private String recordingEventsChannel = AeronArchive.Configuration.recordingEventsChannel();\n        private int recordingEventsStreamId = AeronArchive.Configuration.recordingEventsStreamId();\n        private String controlRequestChannel = Configuration.controlChannel();\n        private int controlRequestStreamId = Configuration.controlStreamId();\n        private String controlResponseChannel = Configuration.controlResponseChannel();\n        private int controlResponseStreamId = Configuration.controlResponseStreamId();\n        private boolean controlTermBufferSparse = Configuration.controlTermBufferSparse();\n        private int controlTermBufferLength = Configuration.controlTermBufferLength();\n        private int controlMtuLength = Configuration.controlMtuLength();\n        private IdleStrategy idleStrategy;\n        private Lock lock;\n        private String aeronDirectoryName = getAeronDirectoryName();\n        private Aeron aeron;\n        private ErrorHandler errorHandler;\n        private CredentialsSupplier credentialsSupplier;\n        private RecordingSignalConsumer recordingSignalConsumer = Configuration.NO_OP_RECORDING_SIGNAL_CONSUMER;\n        private AgentInvoker agentInvoker;\n        private boolean ownsAeronClient = false;\n\n        /**\n         * Construct a Context using default values and loading from system properties.\n         */\n        public Context()\n        {\n        }\n\n        /**\n         * Perform a shallow copy of the object.\n         *\n         * @return a shallow copy of the object.\n         */\n        public Context clone()\n        {\n            try\n            {\n                return (Context)super.clone();\n            }\n            catch (final CloneNotSupportedException ex)\n            {\n                throw new RuntimeException(ex);\n            }\n        }\n\n        /**\n         * Conclude configuration by setting up defaults when specifics are not provided.\n         */\n        public void conclude()\n        {\n            if ((boolean)IS_CONCLUDED_VH.getAndSet(this, true))\n            {\n                throw new ConcurrentConcludeException();\n            }\n\n            if (null == controlRequestChannel)\n            {\n                throw new ConfigurationException(\"AeronArchive.Context.controlRequestChannel must be set\");\n            }\n\n            if (null == controlResponseChannel)\n            {\n                throw new ConfigurationException(\"AeronArchive.Context.controlResponseChannel must be set\");\n            }\n\n            if (clientName.length() > Aeron.Configuration.MAX_CLIENT_NAME_LENGTH)\n            {\n                throw new ConfigurationException(\n                    \"AeronArchive.Context.clientName length must be <= \" + Aeron.Configuration.MAX_CLIENT_NAME_LENGTH);\n            }\n\n            if (messageRetryAttempts <= 0)\n            {\n                throw new ConfigurationException(\"AeronArchive.Context.messageRetryAttempts must be > 0, got: \" +\n                    messageRetryAttempts);\n            }\n\n            if (null == aeron)\n            {\n                aeron = Aeron.connect(\n                    new Aeron.Context()\n                        .aeronDirectoryName(aeronDirectoryName)\n                        .clientName(clientName.isEmpty() ? \"archive-client\" : clientName)\n                        .errorHandler(errorHandler)\n                        .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE));\n                ownsAeronClient = true;\n            }\n\n            final ChannelUri requestChannel = applyDefaultParams(controlRequestChannel);\n            final ChannelUri responseChannel = applyDefaultParams(controlResponseChannel);\n            if (!CONTROL_MODE_RESPONSE.equals(responseChannel.get(MDC_CONTROL_MODE_PARAM_NAME)))\n            {\n                final String sessionId = Integer.toString(aeron.nextSessionId(controlRequestStreamId));\n                requestChannel.put(SESSION_ID_PARAM_NAME, sessionId);\n                responseChannel.put(SESSION_ID_PARAM_NAME, sessionId);\n            }\n            controlRequestChannel = requestChannel.toString();\n            controlResponseChannel = responseChannel.toString();\n\n            if (null == idleStrategy)\n            {\n                idleStrategy = new BackoffIdleStrategy(\n                    IDLE_MAX_SPINS, IDLE_MAX_YIELDS, IDLE_MIN_PARK_NS, IDLE_MAX_PARK_NS);\n            }\n\n            if (null == credentialsSupplier)\n            {\n                credentialsSupplier = new NullCredentialsSupplier();\n            }\n\n            if (null == lock)\n            {\n                lock = new ReentrantLock();\n            }\n        }\n\n        /**\n         * Has the context had the {@link #conclude()} method called.\n         *\n         * @return true of the {@link #conclude()} method has been called.\n         */\n        public boolean isConcluded()\n        {\n            return isConcluded;\n        }\n\n        /**\n         * Set the message timeout in nanoseconds to wait for sending or receiving a message.\n         *\n         * @param messageTimeoutNs to wait for sending or receiving a message.\n         * @return this for a fluent API.\n         * @see Configuration#MESSAGE_TIMEOUT_PROP_NAME\n         */\n        public Context messageTimeoutNs(final long messageTimeoutNs)\n        {\n            this.messageTimeoutNs = messageTimeoutNs;\n            return this;\n        }\n\n        /**\n         * The message timeout in nanoseconds to wait for sending or receiving a message.\n         *\n         * @return the message timeout in nanoseconds to wait for sending or receiving a message.\n         * @see Configuration#MESSAGE_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long messageTimeoutNs()\n        {\n            return checkDebugTimeout(messageTimeoutNs, TimeUnit.NANOSECONDS);\n        }\n\n        /**\n         * Set the number of retry attempts to be made when offering messages to the archive.\n         *\n         * @param messageRetryAttempts the number of retry attempts.\n         * @return this for a fluent API.\n         * @see Configuration#MESSAGE_RETRY_ATTEMPTS_PROP_NAME\n         */\n        public Context messageRetryAttempts(final int messageRetryAttempts)\n        {\n            this.messageRetryAttempts = messageRetryAttempts;\n            return this;\n        }\n\n        /**\n         * The number of retry attempts to be made when offering messages to the archive.\n         *\n         * @return the number of retry attempts.\n         * @see Configuration#MESSAGE_RETRY_ATTEMPTS_PROP_NAME\n         */\n        @Config\n        public int messageRetryAttempts()\n        {\n            return messageRetryAttempts;\n        }\n\n        /**\n         * Get the channel URI on which the recording events publication will publish.\n         *\n         * @return the channel URI on which the recording events publication will publish.\n         */\n        @Config\n        public String recordingEventsChannel()\n        {\n            return recordingEventsChannel;\n        }\n\n        /**\n         * Set the channel URI on which the recording events publication will publish.\n         * <p>\n         * To support dynamic subscribers then this can be set to multicast or MDC (Multi-Destination-Cast) if\n         * multicast cannot be supported for on the available the network infrastructure.\n         *\n         * @param recordingEventsChannel channel URI on which the recording events publication will publish.\n         * @return this for a fluent API.\n         * @see io.aeron.CommonContext#MDC_CONTROL_PARAM_NAME\n         */\n        public Context recordingEventsChannel(final String recordingEventsChannel)\n        {\n            this.recordingEventsChannel = recordingEventsChannel;\n            return this;\n        }\n\n        /**\n         * Get the stream id on which the recording events publication will publish.\n         *\n         * @return the stream id on which the recording events publication will publish.\n         */\n        @Config\n        public int recordingEventsStreamId()\n        {\n            return recordingEventsStreamId;\n        }\n\n        /**\n         * Set the stream id on which the recording events publication will publish.\n         *\n         * @param recordingEventsStreamId stream id on which the recording events publication will publish.\n         * @return this for a fluent API.\n         */\n        public Context recordingEventsStreamId(final int recordingEventsStreamId)\n        {\n            this.recordingEventsStreamId = recordingEventsStreamId;\n            return this;\n        }\n\n        /**\n         * Set the channel parameter for the control request channel.\n         *\n         * @param channel parameter for the control request channel.\n         * @return this for a fluent API.\n         * @see Configuration#CONTROL_CHANNEL_PROP_NAME\n         */\n        public Context controlRequestChannel(final String channel)\n        {\n            controlRequestChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the control request channel.\n         *\n         * @return the channel parameter for the control request channel.\n         * @see Configuration#CONTROL_CHANNEL_PROP_NAME\n         */\n        @Config(id = \"CONTROL_CHANNEL\")\n        public String controlRequestChannel()\n        {\n            return controlRequestChannel;\n        }\n\n        /**\n         * Set the stream id for the control request channel.\n         *\n         * @param streamId for the control request channel.\n         * @return this for a fluent API\n         * @see Configuration#CONTROL_STREAM_ID_PROP_NAME\n         */\n        public Context controlRequestStreamId(final int streamId)\n        {\n            controlRequestStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the control request channel.\n         *\n         * @return the stream id for the control request channel.\n         * @see Configuration#CONTROL_STREAM_ID_PROP_NAME\n         */\n        @Config(id = \"CONTROL_STREAM_ID\")\n        public int controlRequestStreamId()\n        {\n            return controlRequestStreamId;\n        }\n\n        /**\n         * Set the channel parameter for the control response channel.\n         *\n         * @param channel parameter for the control response channel.\n         * @return this for a fluent API.\n         * @see Configuration#CONTROL_RESPONSE_CHANNEL_PROP_NAME\n         */\n        public Context controlResponseChannel(final String channel)\n        {\n            controlResponseChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the control response channel.\n         *\n         * @return the channel parameter for the control response channel.\n         * @see Configuration#CONTROL_RESPONSE_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String controlResponseChannel()\n        {\n            return controlResponseChannel;\n        }\n\n        /**\n         * Set the stream id for the control response channel.\n         *\n         * @param streamId for the control response channel.\n         * @return this for a fluent API\n         * @see Configuration#CONTROL_RESPONSE_STREAM_ID_PROP_NAME\n         */\n        public Context controlResponseStreamId(final int streamId)\n        {\n            controlResponseStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the control response channel.\n         *\n         * @return the stream id for the control response channel.\n         * @see Configuration#CONTROL_RESPONSE_STREAM_ID_PROP_NAME\n         */\n        @Config\n        public int controlResponseStreamId()\n        {\n            return controlResponseStreamId;\n        }\n\n        /**\n         * Should the control streams use sparse file term buffers.\n         *\n         * @param controlTermBufferSparse for the control stream.\n         * @return this for a fluent API.\n         * @see Configuration#CONTROL_TERM_BUFFER_SPARSE_PROP_NAME\n         */\n        public Context controlTermBufferSparse(final boolean controlTermBufferSparse)\n        {\n            this.controlTermBufferSparse = controlTermBufferSparse;\n            return this;\n        }\n\n        /**\n         * Should the control streams use sparse file term buffers.\n         *\n         * @return {@code true} if the control stream should use sparse file term buffers.\n         * @see Configuration#CONTROL_TERM_BUFFER_SPARSE_PROP_NAME\n         */\n        @Config\n        public boolean controlTermBufferSparse()\n        {\n            return controlTermBufferSparse;\n        }\n\n        /**\n         * Set the term buffer length for the control streams.\n         *\n         * @param controlTermBufferLength for the control streams.\n         * @return this for a fluent API.\n         * @see Configuration#CONTROL_TERM_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context controlTermBufferLength(final int controlTermBufferLength)\n        {\n            this.controlTermBufferLength = controlTermBufferLength;\n            return this;\n        }\n\n        /**\n         * Get the term buffer length for the control streams.\n         *\n         * @return the term buffer length for the control streams.\n         * @see Configuration#CONTROL_TERM_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config\n        public int controlTermBufferLength()\n        {\n            return controlTermBufferLength;\n        }\n\n        /**\n         * Set the MTU length for the control streams.\n         *\n         * @param controlMtuLength for the control streams.\n         * @return this for a fluent API.\n         * @see Configuration#CONTROL_MTU_LENGTH_PROP_NAME\n         */\n        public Context controlMtuLength(final int controlMtuLength)\n        {\n            this.controlMtuLength = controlMtuLength;\n            return this;\n        }\n\n        /**\n         * Get the MTU length for the control streams.\n         *\n         * @return the MTU length for the control streams.\n         * @see Configuration#CONTROL_MTU_LENGTH_PROP_NAME\n         */\n        @Config\n        public int controlMtuLength()\n        {\n            return controlMtuLength;\n        }\n\n        /**\n         * Set the {@link IdleStrategy} used when waiting for responses.\n         *\n         * @param idleStrategy used when waiting for responses.\n         * @return this for a fluent API.\n         */\n        public Context idleStrategy(final IdleStrategy idleStrategy)\n        {\n            this.idleStrategy = idleStrategy;\n            return this;\n        }\n\n        /**\n         * Get the {@link IdleStrategy} used when waiting for responses.\n         *\n         * @return the {@link IdleStrategy} used when waiting for responses.\n         */\n        public IdleStrategy idleStrategy()\n        {\n            return idleStrategy;\n        }\n\n        /**\n         * Set the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @param aeronDirectoryName the top level Aeron directory.\n         * @return this for a fluent API.\n         */\n        public Context aeronDirectoryName(final String aeronDirectoryName)\n        {\n            this.aeronDirectoryName = aeronDirectoryName;\n            return this;\n        }\n\n        /**\n         * Get the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @return The top level Aeron directory.\n         */\n        public String aeronDirectoryName()\n        {\n            return aeronDirectoryName;\n        }\n\n        /**\n         * Sets the name used to identify this client among other clients connected to the Archive.\n         *\n         * @param clientName to use.\n         * @return this for a fluent API.\n         * @see AeronArchive.Configuration#CLIENT_NAME_PROP_NAME\n         * @since 1.49.0\n         */\n        public Context clientName(final String clientName)\n        {\n            this.clientName = Strings.isEmpty(clientName) ? \"\" : clientName;\n            return this;\n        }\n\n        /**\n         * Returns the name of this client.\n         *\n         * @return name of this client or empty String if not configured.\n         * @see AeronArchive.Configuration#CLIENT_NAME_PROP_NAME\n         * @since 1.49.0\n         */\n        public String clientName()\n        {\n            return clientName;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * This client will be closed when the {@link AeronArchive#close()} or {@link #close()} methods are called if\n         * {@link #ownsAeronClient()} is true.\n         *\n         * @param aeron client for communicating with the local Media Driver.\n         * @return this for a fluent API.\n         * @see Aeron#connect()\n         */\n        public Context aeron(final Aeron aeron)\n        {\n            this.aeron = aeron;\n            return this;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * If not provided then a default will be established during {@link #conclude()} by calling\n         * {@link Aeron#connect()}.\n         *\n         * @return client for communicating with the local Media Driver.\n         */\n        public Aeron aeron()\n        {\n            return aeron;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and thus takes responsibility for closing it?\n         *\n         * @param ownsAeronClient does this context own the {@link #aeron()} client?\n         * @return this for a fluent API.\n         */\n        public Context ownsAeronClient(final boolean ownsAeronClient)\n        {\n            this.ownsAeronClient = ownsAeronClient;\n            return this;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and thus takes responsibility for closing it?\n         *\n         * @return does this context own the {@link #aeron()} client and thus takes responsibility for closing it?\n         */\n        public boolean ownsAeronClient()\n        {\n            return ownsAeronClient;\n        }\n\n        /**\n         * The {@link Lock} that is used to provide mutual exclusion in the {@link AeronArchive} client.\n         * <p>\n         * If the {@link AeronArchive} is used from only a single thread then the lock can be set to\n         * {@link NoOpLock} to elide the lock overhead.\n         *\n         * @param lock that is used to provide mutual exclusion in the {@link AeronArchive} client.\n         * @return this for a fluent API.\n         */\n        public Context lock(final Lock lock)\n        {\n            this.lock = lock;\n            return this;\n        }\n\n        /**\n         * Get the {@link Lock} that is used to provide mutual exclusion in the {@link AeronArchive} client.\n         *\n         * @return the {@link Lock} that is used to provide mutual exclusion in the {@link AeronArchive} client.\n         */\n        public Lock lock()\n        {\n            return lock;\n        }\n\n        /**\n         * Handle errors returned asynchronously from the archive for a control session.\n         *\n         * @param errorHandler method to handle objects of type Throwable.\n         * @return this for a fluent API.\n         */\n        public Context errorHandler(final ErrorHandler errorHandler)\n        {\n            this.errorHandler = errorHandler;\n            return this;\n        }\n\n        /**\n         * Get the error handler that will be called for asynchronous errors.\n         *\n         * @return the error handler that will be called for asynchronous errors.\n         */\n        public ErrorHandler errorHandler()\n        {\n            return errorHandler;\n        }\n\n        /**\n         * Set the {@link CredentialsSupplier} to be used for authentication with the archive.\n         *\n         * @param credentialsSupplier to be used for authentication with the archive.\n         * @return this for fluent API.\n         */\n        public Context credentialsSupplier(final CredentialsSupplier credentialsSupplier)\n        {\n            this.credentialsSupplier = credentialsSupplier;\n            return this;\n        }\n\n        /**\n         * Get the {@link CredentialsSupplier} to be used for authentication with the archive.\n         *\n         * @return the {@link CredentialsSupplier} to be used for authentication with the archive.\n         */\n        public CredentialsSupplier credentialsSupplier()\n        {\n            return credentialsSupplier;\n        }\n\n        /**\n         * Set the {@link RecordingSignalConsumer} to will be called when polling for responses from an Archive.\n         *\n         * @param recordingSignalConsumer to called with recording signal events.\n         * @return this for a fluent API.\n         */\n        public Context recordingSignalConsumer(final RecordingSignalConsumer recordingSignalConsumer)\n        {\n            this.recordingSignalConsumer = recordingSignalConsumer;\n            return this;\n        }\n\n        /**\n         * Set the {@link RecordingSignalConsumer} to will be called when polling for responses from an Archive.\n         *\n         * @return a recording signal consumer.\n         */\n        public RecordingSignalConsumer recordingSignalConsumer()\n        {\n            return recordingSignalConsumer;\n        }\n\n        /**\n         * Set the {@link AgentInvoker} to be invoked in addition to any invoker used by the {@link #aeron()} instance.\n         * <p>\n         * Useful for when running on a low thread count scenario.\n         * <p>\n         * <em><strong>Note:</strong> it is the responsibility of the user to invoke {@code agentInvoker} and the\n         * {@code aeron.conductorAgentInvoker()} periodically outside of this class. {@link AeronArchive} will invoke\n         * those when doing blocking operations (e.g. {@link #getMaxRecordedPosition(long)})</em>\n         *\n         * @param agentInvoker to be invoked while awaiting a response in the client.\n         * @return this for a fluent API.\n         */\n        public Context agentInvoker(final AgentInvoker agentInvoker)\n        {\n            this.agentInvoker = agentInvoker;\n            return this;\n        }\n\n        /**\n         * Get the {@link AgentInvoker} to be invoked in addition to any invoker used by the {@link #aeron()} instance.\n         *\n         * @return the {@link AgentInvoker} that is used.\n         */\n        public AgentInvoker agentInvoker()\n        {\n            return agentInvoker;\n        }\n\n        /**\n         * Close the context and free applicable resources.\n         * <p>\n         * If {@link #ownsAeronClient()} is true then the {@link #aeron()} client will be closed.\n         */\n        public void close()\n        {\n            if (ownsAeronClient)\n            {\n                CloseHelper.close(aeron);\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"AeronArchive.Context\" +\n                \"\\n{\" +\n                \"\\n    isConcluded=\" + isConcluded() +\n                \"\\n    ownsAeronClient=\" + ownsAeronClient +\n                \"\\n    aeronDirectoryName='\" + aeronDirectoryName + '\\'' +\n                \"\\n    aeron=\" + aeron +\n                \"\\n    messageTimeoutNs=\" + messageTimeoutNs +\n                \"\\n    recordingEventsChannel='\" + recordingEventsChannel + '\\'' +\n                \"\\n    recordingEventsStreamId=\" + recordingEventsStreamId +\n                \"\\n    controlRequestChannel='\" + controlRequestChannel + '\\'' +\n                \"\\n    controlRequestStreamId=\" + controlRequestStreamId +\n                \"\\n    controlResponseChannel='\" + controlResponseChannel + '\\'' +\n                \"\\n    controlResponseStreamId=\" + controlResponseStreamId +\n                \"\\n    controlTermBufferSparse=\" + controlTermBufferSparse +\n                \"\\n    controlTermBufferLength=\" + controlTermBufferLength +\n                \"\\n    controlMtuLength=\" + controlMtuLength +\n                \"\\n    idleStrategy=\" + idleStrategy +\n                \"\\n    lock=\" + lock +\n                \"\\n    errorHandler=\" + errorHandler +\n                \"\\n    credentialsSupplier=\" + credentialsSupplier +\n                \"\\n}\";\n        }\n\n        int runInvokers()\n        {\n            int workCount = 0;\n            final AgentInvoker conductorAgentInvoker = aeron.conductorAgentInvoker();\n            if (null != conductorAgentInvoker)\n            {\n                workCount += conductorAgentInvoker.invoke();\n            }\n\n            final AgentInvoker agentInvoker = this.agentInvoker;\n            if (null != agentInvoker)\n            {\n                workCount += agentInvoker.invoke();\n            }\n            return workCount;\n        }\n\n        private ChannelUri applyDefaultParams(final String channel)\n        {\n            final ChannelUri channelUri = ChannelUri.parse(channel);\n\n            if (!channelUri.containsKey(TERM_LENGTH_PARAM_NAME))\n            {\n                channelUri.put(TERM_LENGTH_PARAM_NAME, Integer.toString(controlTermBufferLength));\n            }\n\n            if (!channelUri.containsKey(MTU_LENGTH_PARAM_NAME))\n            {\n                channelUri.put(MTU_LENGTH_PARAM_NAME, Integer.toString(controlMtuLength));\n            }\n\n            if (!channelUri.containsKey(SPARSE_PARAM_NAME))\n            {\n                channelUri.put(SPARSE_PARAM_NAME, Boolean.toString(controlTermBufferSparse));\n            }\n\n            return channelUri;\n        }\n    }\n\n    /**\n     * Allows for the async establishment of an archive session.\n     */\n    public static final class AsyncConnect implements AutoCloseable\n    {\n        /**\n         * Represents connection state.\n         */\n        public enum State\n        {\n            /**\n             * Initial state of awaiting an asynchronous subscription for the control response channel.\n             */\n            AWAIT_SUBSCRIPTION(0),\n            /**\n             * Add publication for control request channel.\n             */\n            ADD_PUBLICATION(1),\n            /**\n             * Await publication being added.\n             */\n            AWAIT_PUBLICATION_CONNECTED(2),\n            /**\n             * Sending {@code connect} request to the Archive.\n             */\n            SEND_CONNECT_REQUEST(3),\n            /**\n             * Await response subscription connected.\n             */\n            AWAIT_SUBSCRIPTION_CONNECTED(4),\n            /**\n             * Await connect response.\n             */\n            AWAIT_CONNECT_RESPONSE(5),\n            /**\n             * Send {@code archive-id} request.\n             */\n            SEND_ARCHIVE_ID_REQUEST(6),\n            /**\n             * Await response for the {@code archive-id} request.\n             */\n            AWAIT_ARCHIVE_ID_RESPONSE(7),\n            /**\n             * Sending a challenge response.\n             */\n            SEND_CHALLENGE_RESPONSE(8),\n            /**\n             * Await challenge response.\n             */\n            AWAIT_CHALLENGE_RESPONSE(9),\n            /**\n             * Archive connection established.\n             */\n            DONE(10);\n\n            final int step;\n\n            State(final int step)\n            {\n                this.step = step;\n            }\n        }\n\n        static final int PROTOCOL_VERSION_WITH_ARCHIVE_ID = SemanticVersion.compose(1, 11, 0);\n        private final Context ctx;\n        private final long deadlineNs;\n        private ControlResponsePoller controlResponsePoller;\n        private long subscriptionRegistrationId = NULL_VALUE;\n        private long publicationRegistrationId = NULL_VALUE;\n        private long correlationId = NULL_VALUE;\n        private long controlSessionId = NULL_VALUE;\n        private byte[] encodedCredentialsFromChallenge = null;\n        private State state;\n        private ArchiveProxy archiveProxy;\n        private AeronArchive aeronArchive;\n\n        AsyncConnect(final Context ctx)\n        {\n            ctx.conclude();\n            try\n            {\n                this.ctx = ctx;\n                final Aeron aeron = ctx.aeron();\n\n                subscriptionRegistrationId = aeron.asyncAddSubscription(ctx.controlResponseChannel(),\n                    ctx.controlResponseStreamId(),\n                    null,\n                    (image) ->\n                    {\n                        final AeronArchive client = aeronArchive;\n                        if (null != client)\n                        {\n                            client.state(AeronArchive.State.DISCONNECTED);\n                        }\n                    });\n\n                state = State.AWAIT_SUBSCRIPTION;\n                deadlineNs = aeron.context().nanoClock().nanoTime() + ctx.messageTimeoutNs();\n            }\n            catch (final Exception ex)\n            {\n                close();\n                throw ex;\n            }\n        }\n\n        /**\n         * Close any allocated resources.\n         */\n        public void close()\n        {\n            if (State.DONE != state)\n            {\n                if (!ctx.ownsAeronClient())\n                {\n                    if (null != controlResponsePoller)\n                    {\n                        CloseHelper.close(ctx.errorHandler(), controlResponsePoller.subscription());\n                    }\n                    else if (NULL_VALUE != subscriptionRegistrationId)\n                    {\n                        ctx.aeron().asyncRemoveSubscription(subscriptionRegistrationId);\n                    }\n\n                    if (null != archiveProxy)\n                    {\n                        CloseHelper.close(ctx.errorHandler(), archiveProxy.publication());\n                    }\n                    else if (NULL_VALUE != publicationRegistrationId)\n                    {\n                        ctx.aeron().asyncRemovePublication(publicationRegistrationId);\n                    }\n                }\n\n                ctx.close();\n            }\n        }\n\n        /**\n         * Get the {@link AeronArchive.Context} used for this client.\n         *\n         * @return the {@link AeronArchive.Context} used for this client.\n         */\n        public Context context()\n        {\n            return ctx;\n        }\n\n        /**\n         * Get the index of the current step.\n         *\n         * @return the index of the current step.\n         */\n        public int step()\n        {\n            return state.step;\n        }\n\n        /**\n         * Get the current connection state.\n         *\n         * @return current state.\n         */\n        public State state()\n        {\n            return state;\n        }\n\n        /**\n         * Poll for a complete connection.\n         *\n         * @return a new {@link AeronArchive} if successfully connected otherwise null.\n         */\n        @SuppressWarnings(\"MethodLength\")\n        public AeronArchive poll()\n        {\n            checkDeadline();\n            ctx.runInvokers();\n\n            switch (state)\n            {\n                case AWAIT_SUBSCRIPTION:\n                    awaitSubscription();\n                    break;\n\n                case ADD_PUBLICATION:\n                    addPublication();\n                    break;\n\n                case AWAIT_PUBLICATION_CONNECTED:\n                    awaitPublicationConnected();\n                    break;\n\n                case SEND_CONNECT_REQUEST:\n                    sendConnectRequest();\n                    break;\n\n                case AWAIT_SUBSCRIPTION_CONNECTED:\n                    awaitSubscriptionConnected();\n                    break;\n\n                case SEND_ARCHIVE_ID_REQUEST:\n                    sendArchiveIdRequest();\n                    break;\n\n                case SEND_CHALLENGE_RESPONSE:\n                    sendChallengeResponse();\n                    break;\n\n                case AWAIT_CONNECT_RESPONSE:\n                case AWAIT_ARCHIVE_ID_RESPONSE:\n                case AWAIT_CHALLENGE_RESPONSE:\n                    pollForResponse();\n                    break;\n\n                case DONE:\n                    return aeronArchive;\n\n                default:\n                    break;\n            }\n\n            return null;\n        }\n\n        private void checkDeadline()\n        {\n            if (deadlineNs - ctx.aeron().context().nanoClock().nanoTime() < 0)\n            {\n                throw new TimeoutException(\"Archive connect timeout: step=\" + state +\n                    \" publication=\" + (null != archiveProxy ? archiveProxy.publication() :\n                    ctx.controlRequestChannel()) +\n                    \" subscription=\" + (null != controlResponsePoller ? controlResponsePoller.subscription() :\n                    ctx.controlResponseChannel()));\n            }\n\n            if (Thread.currentThread().isInterrupted())\n            {\n                throw new AeronException(\"unexpected interrupt\");\n            }\n        }\n\n        private void awaitSubscription()\n        {\n            final Subscription subscription = ctx.aeron().getSubscription(subscriptionRegistrationId);\n            if (null != subscription)\n            {\n                if (ChannelUri.isControlModeResponse(ctx.controlResponseChannel()))\n                {\n                    final ChannelUri requestChannel = ChannelUri.parse(ctx.controlRequestChannel());\n                    requestChannel.put(\n                        RESPONSE_CORRELATION_ID_PARAM_NAME, Long.toString(subscription.registrationId()));\n                    ctx.controlRequestChannel(requestChannel.toString());\n                }\n                controlResponsePoller = new ControlResponsePoller(subscription);\n                subscriptionRegistrationId = NULL_VALUE;\n                state(State.ADD_PUBLICATION);\n            }\n        }\n\n        private void addPublication()\n        {\n            final Aeron aeron = ctx.aeron();\n            if (NULL_VALUE == publicationRegistrationId)\n            {\n                publicationRegistrationId = aeron.asyncAddExclusivePublication(\n                    ctx.controlRequestChannel(), ctx.controlRequestStreamId());\n            }\n\n            final ExclusivePublication publication = aeron.getExclusivePublication(publicationRegistrationId);\n            if (null != publication)\n            {\n                final String clientInfo = \"name=\" + ctx.clientName() + \" \" +\n                    AeronCounters.formatVersionInfo(AeronArchiveVersion.VERSION, AeronArchiveVersion.GIT_SHA);\n                archiveProxy = new ArchiveProxy(\n                    publication,\n                    ctx.idleStrategy(),\n                    aeron.context().nanoClock(),\n                    ctx.messageTimeoutNs(),\n                    ctx.messageRetryAttempts(),\n                    ctx.credentialsSupplier(),\n                    clientInfo);\n                publicationRegistrationId = NULL_VALUE;\n\n                state(State.AWAIT_PUBLICATION_CONNECTED);\n            }\n        }\n\n        private void awaitPublicationConnected()\n        {\n            if (archiveProxy.publication().isConnected())\n            {\n                correlationId = ctx.aeron().nextCorrelationId();\n                state(State.SEND_CONNECT_REQUEST);\n            }\n        }\n\n        private void sendConnectRequest()\n        {\n            final String responseChannel = controlResponsePoller.subscription().tryResolveChannelEndpointPort();\n            if (null != responseChannel)\n            {\n                if (archiveProxy.tryConnect(responseChannel, ctx.controlResponseStreamId(), correlationId))\n                {\n                    state(State.AWAIT_SUBSCRIPTION_CONNECTED);\n                }\n            }\n        }\n\n        private void awaitSubscriptionConnected()\n        {\n            if (controlResponsePoller.subscription().isConnected())\n            {\n                state(State.AWAIT_CONNECT_RESPONSE);\n            }\n        }\n\n        private void sendArchiveIdRequest()\n        {\n            if (archiveProxy.archiveId(correlationId, controlSessionId))\n            {\n                state(State.AWAIT_ARCHIVE_ID_RESPONSE);\n            }\n        }\n\n        private void sendChallengeResponse()\n        {\n            if (archiveProxy.tryChallengeResponse(\n                encodedCredentialsFromChallenge, correlationId, controlSessionId))\n            {\n                state(State.AWAIT_CHALLENGE_RESPONSE);\n            }\n        }\n\n        private void pollForResponse()\n        {\n            controlResponsePoller.poll();\n\n            if (controlResponsePoller.isPollComplete() &&\n                controlResponsePoller.correlationId() == correlationId)\n            {\n                controlSessionId = controlResponsePoller.controlSessionId();\n                if (controlResponsePoller.wasChallenged())\n                {\n                    encodedCredentialsFromChallenge = ctx.credentialsSupplier().onChallenge(\n                        controlResponsePoller.encodedChallenge());\n\n                    correlationId = ctx.aeron().nextCorrelationId();\n                    state(State.SEND_CHALLENGE_RESPONSE);\n                }\n                else\n                {\n                    final ControlResponseCode code = controlResponsePoller.code();\n                    if (ControlResponseCode.OK != code)\n                    {\n                        archiveProxy.closeSession(controlSessionId);\n                        if (ControlResponseCode.ERROR == code)\n                        {\n                            final String errorMessage = controlResponsePoller.errorMessage();\n                            final int errorCode = (int)controlResponsePoller.relevantId();\n\n                            throw new ArchiveException(errorMessage, errorCode, correlationId);\n                        }\n\n                        throw new ArchiveException(\n                            \"unexpected response: code=\" + code, correlationId, AeronException.Category.ERROR);\n                    }\n\n                    if (State.AWAIT_ARCHIVE_ID_RESPONSE == state)\n                    {\n                        final long archiveId = controlResponsePoller.relevantId();\n                        aeronArchive = transitionToDone(archiveId);\n                    }\n                    else\n                    {\n                        final int archiveProtocolVersion = controlResponsePoller.version();\n                        if (archiveProtocolVersion < PROTOCOL_VERSION_WITH_ARCHIVE_ID)\n                        {\n                            aeronArchive = transitionToDone(NULL_VALUE);\n                        }\n                        else\n                        {\n                            correlationId = ctx.aeron().nextCorrelationId();\n                            state(State.SEND_ARCHIVE_ID_REQUEST);\n                        }\n                    }\n                }\n            }\n        }\n\n        private void state(final State newState)\n        {\n//            System.out.println(state + \" -> \" + newState);\n            state = newState;\n        }\n\n        private AeronArchive transitionToDone(final long archiveId)\n        {\n            if (!archiveProxy.keepAlive(controlSessionId, NULL_VALUE))\n            {\n                archiveProxy.closeSession(controlSessionId);\n                throw new ArchiveException(\"failed to send keep alive after archive connect\");\n            }\n\n            final AeronArchive aeronArchive = new AeronArchive(\n                ctx, controlResponsePoller, archiveProxy, controlSessionId, archiveId);\n\n            state(State.DONE);\n            return aeronArchive;\n        }\n    }\n\n    static Exception quietClose(final Exception previousException, final AutoCloseable closeable)\n    {\n        Exception resultException = previousException;\n        if (null != closeable)\n        {\n            try\n            {\n                closeable.close();\n            }\n            catch (final Exception ex)\n            {\n                if (null != resultException)\n                {\n                    resultException.addSuppressed(ex);\n                }\n                else\n                {\n                    resultException = ex;\n                }\n            }\n        }\n\n        return resultException;\n    }\n\n    private Subscription replayViaResponseChannel(\n        final long recordingId,\n        final String replayChannel,\n        final int replayStreamId,\n        final ReplayParams replayParams)\n    {\n        lastCorrelationId = aeron.nextCorrelationId();\n\n        if (!archiveProxy.requestReplayToken(lastCorrelationId, controlSessionId, recordingId))\n        {\n            throw new ArchiveException(\"failed to send replay token request\");\n        }\n\n        final long replayToken = pollForResponse(lastCorrelationId);\n\n        replayParams.replayToken(replayToken);\n        final Subscription replaySubscription = aeron.addSubscription(replayChannel, replayStreamId);\n        final ChannelUriStringBuilder uriBuilder = new ChannelUriStringBuilder(context.controlRequestChannel())\n            .sessionId((Integer)null)\n            .responseCorrelationId(replaySubscription.registrationId())\n            .termId((Integer)null).initialTermId((Integer)null).termOffset((Integer)null)\n            .termLength(64 * 1024)\n            .spiesSimulateConnection(false);\n\n        final String channel = uriBuilder.build();\n\n        try (ExclusivePublication publication =\n            aeron.addExclusivePublication(channel, context().controlRequestStreamId()))\n        {\n            final ArchiveProxy responseArchiveProxy = new ArchiveProxy(publication);\n\n            final int pubLmtCounterId = aeron.countersReader().findByTypeIdAndRegistrationId(\n                AeronCounters.DRIVER_PUBLISHER_LIMIT_TYPE_ID, publication.registrationId());\n\n            final long deadlineNs = aeron.context().nanoClock().nanoTime() + context.messageTimeoutNs();\n            while (!publication.isConnected() || 0 == aeron.countersReader().getCounterValue(pubLmtCounterId))\n            {\n                if (deadlineNs <= aeron.context().nanoClock().nanoTime())\n                {\n                    throw new ArchiveException(\"timed out wait for replay publication to connect\");\n                }\n\n                idleStrategy.idle();\n            }\n\n            if (!responseArchiveProxy.replay(\n                recordingId, replayChannel, replayStreamId, replayParams, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send replay request\");\n            }\n\n            pollForResponse(lastCorrelationId);\n            while (!replaySubscription.isConnected())\n            {\n                idleStrategy.idle();\n            }\n\n            return replaySubscription;\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.close(replaySubscription);\n            throw ex;\n        }\n    }\n\n    private long startReplayViaResponseChannel(\n        final long recordingId,\n        final String replayChannel,\n        final int replayStreamId,\n        final ReplayParams replayParams)\n    {\n        lastCorrelationId = aeron.nextCorrelationId();\n\n        if (NULL_VALUE == replayParams.subscriptionRegistrationId())\n        {\n            throw new ArchiveException(\n                \"when using startReplay with a response channel, ReplayParams::subscriptionRegistrationId must be set\");\n        }\n\n        if (!archiveProxy.requestReplayToken(lastCorrelationId, controlSessionId, recordingId))\n        {\n            throw new ArchiveException(\"failed to send replay token request\");\n        }\n\n        final long replayToken = pollForResponse(lastCorrelationId);\n\n        replayParams.replayToken(replayToken);\n        final ChannelUriStringBuilder uriBuilder = new ChannelUriStringBuilder(context.controlRequestChannel())\n            .sessionId((Integer)null)\n            .responseCorrelationId(replayParams.subscriptionRegistrationId())\n            .termId((Integer)null).initialTermId((Integer)null).termOffset((Integer)null)\n            .termLength(context.controlTermBufferLength())\n            .spiesSimulateConnection(false);\n\n        final String channel = uriBuilder.build();\n\n        try (ExclusivePublication publication =\n            aeron.addExclusivePublication(channel, context().controlRequestStreamId()))\n        {\n            final ArchiveProxy responseArchiveProxy = new ArchiveProxy(publication);\n\n            final long deadlineNs = aeron.context().nanoClock().nanoTime() + context.messageTimeoutNs();\n\n            while (!publication.isConnected())\n            {\n                checkDeadline(\n                    idleStrategy,\n                    aeron.context().nanoClock(),\n                    deadlineNs,\n                    \"timed out waiting to establish replay connection\");\n            }\n\n            while (0 == publication.positionLimit())\n            {\n                checkDeadline(\n                    idleStrategy,\n                    aeron.context().nanoClock(),\n                    deadlineNs,\n                    \"timed out waiting for replay connection to have available publication limit\");\n            }\n\n            if (!responseArchiveProxy.replay(\n                recordingId, replayChannel, replayStreamId, replayParams, lastCorrelationId, controlSessionId))\n            {\n                throw new ArchiveException(\"failed to send replay request\");\n            }\n\n            pollForResponse(lastCorrelationId);\n\n            return lastCorrelationId;\n        }\n    }\n\n    private static void checkDeadline(\n        final IdleStrategy idleStrategy,\n        final NanoClock nanoClock,\n        final long deadlineNs,\n        final String msg)\n    {\n        if (deadlineNs <= nanoClock.nanoTime())\n        {\n            throw new ArchiveException(msg);\n        }\n\n        idleStrategy.idle();\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ArchiveEvent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.exceptions.AeronEvent;\nimport io.aeron.exceptions.AeronException;\n\n/**\n * A means to capture an Archive event of significance that does not require a stack trace, so it can be lighter-weight\n * and take up less space in a {@link org.agrona.concurrent.errors.DistinctErrorLog}.\n */\npublic class ArchiveEvent extends AeronEvent\n{\n    private static final long serialVersionUID = 6814756736866124884L;\n\n    /**\n     * Archive event with provided message and {@link io.aeron.exceptions.AeronException.Category#WARN}.\n     *\n     * @param message to detail the event.\n     */\n    public ArchiveEvent(final String message)\n    {\n        super(message, AeronException.Category.WARN);\n    }\n\n    /**\n     * Archive event with provided message and {@link io.aeron.exceptions.AeronException.Category}.\n     *\n     * @param message  to detail the event.\n     * @param category of the event.\n     */\n    public ArchiveEvent(final String message, final AeronException.Category category)\n    {\n        super(message, category);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ArchiveException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.exceptions.AeronException;\n\n/**\n * Exception raised when communicating with the {@link AeronArchive}.\n */\npublic class ArchiveException extends AeronException\n{\n    /**\n     * Generic archive error with detail likely in the message.\n     */\n    public static final int GENERIC = 0;\n\n    /**\n     * An active listing of recordings is currently in operation on the session.\n     */\n    public static final int ACTIVE_LISTING = 1;\n\n    /**\n     * The recording is currently active so the requested operation is not valid.\n     */\n    public static final int ACTIVE_RECORDING = 2;\n\n    /**\n     * A subscription is currently active for the requested channel and stream id which would clash.\n     */\n    public static final int ACTIVE_SUBSCRIPTION = 3;\n\n    /**\n     * The subscription for the requested operation is not known to the archive.\n     */\n    public static final int UNKNOWN_SUBSCRIPTION = 4;\n\n    /**\n     * The recording identity for the operation is not know to the archive.\n     */\n    public static final int UNKNOWN_RECORDING = 5;\n\n    /**\n     * The replay identity for the operation is not known to the archive.\n     */\n    public static final int UNKNOWN_REPLAY = 6;\n\n    /**\n     * The archive has reached its maximum concurrent replay sessions.\n     */\n    public static final int MAX_REPLAYS = 7;\n\n    /**\n     * The archive has reached its maximum concurrent recording sessions.\n     */\n    public static final int MAX_RECORDINGS = 8;\n\n    /**\n     * The extend-recording operation is not valid for the existing recording.\n     */\n    public static final int INVALID_EXTENSION = 9;\n\n    /**\n     * The archive is rejecting the session because of failed authentication.\n     */\n    public static final int AUTHENTICATION_REJECTED = 10;\n\n    /**\n     * The archive storage is at minimum threshold or exhausted.\n     */\n    public static final int STORAGE_SPACE = 11;\n\n    /**\n     * The replication identity for this operation is not known to the archive.\n     */\n    public static final int UNKNOWN_REPLICATION = 12;\n\n    /**\n     * The principle was not authorised to take the requested action.\n     */\n    public static final int UNAUTHORISED_ACTION = 13;\n\n    /**\n     * The replication session failed to connect to the source archive.\n     */\n    public static final int REPLICATION_CONNECTION_FAILURE = 14;\n\n    /**\n     * The recording specified for replay is empty.\n     */\n    public static final short EMPTY_RECORDING = 15;\n\n    private static final long serialVersionUID = 386758252787901080L;\n\n    /**\n     * Error code.\n     */\n    private final int errorCode;\n    /**\n     * Command correlation id.\n     */\n    private final long correlationId;\n\n    /**\n     * Default ArchiveException exception as {@link Category#ERROR} and\n     * {@link #errorCode()} = {@link #GENERIC}.\n     */\n    public ArchiveException()\n    {\n        super();\n        errorCode = GENERIC;\n        correlationId = Aeron.NULL_VALUE;\n    }\n\n    /**\n     * ArchiveException exception as {@link Category#ERROR} and\n     * {@link #errorCode()} = {@link #GENERIC}, plus detail.\n     *\n     * @param message providing detail.\n     */\n    public ArchiveException(final String message)\n    {\n        super(message);\n        errorCode = GENERIC;\n        correlationId = Aeron.NULL_VALUE;\n    }\n\n    /**\n     * ArchiveException exception as {@link Category#ERROR}, plus detail and\n     * error code.\n     *\n     * @param message   providing detail.\n     * @param errorCode for type.\n     */\n    public ArchiveException(final String message, final int errorCode)\n    {\n        super(message);\n        this.errorCode = errorCode;\n        correlationId = Aeron.NULL_VALUE;\n    }\n\n    /**\n     * ArchiveException exception as {@link Category#ERROR}, plus detail, cause,\n     * and error code.\n     *\n     * @param message   providing detail.\n     * @param cause     of the error.\n     * @param errorCode for type.\n     */\n    public ArchiveException(final String message, final Throwable cause, final int errorCode)\n    {\n        super(message, cause);\n        this.errorCode = errorCode;\n        correlationId = Aeron.NULL_VALUE;\n    }\n\n    /**\n     * ArchiveException exception as {@link Category#ERROR}, plus detail, error code,\n     * and correlation if of the control request.\n     *\n     * @param message       providing detail.\n     * @param errorCode     for type.\n     * @param correlationId of the control request.\n     */\n    public ArchiveException(final String message, final int errorCode, final long correlationId)\n    {\n        super(message);\n        this.errorCode = errorCode;\n        this.correlationId = correlationId;\n    }\n\n    /**\n     * ArchiveException exception {@link #errorCode()} = {@link #GENERIC}, plus detail and\n     * {@link Category}.\n     *\n     * @param message  providing detail.\n     * @param category for type.\n     */\n    public ArchiveException(final String message, final Category category)\n    {\n        super(message, category);\n        this.errorCode = GENERIC;\n        this.correlationId = Aeron.NULL_VALUE;\n    }\n\n    /**\n     * ArchiveException exception {@link #errorCode()} = {@link #GENERIC}, plus detail, correlation id of control\n     * request, and {@link Category}.\n     *\n     * @param message       providing detail.\n     * @param correlationId of the control request.\n     * @param category      for type.\n     */\n    public ArchiveException(final String message, final long correlationId, final Category category)\n    {\n        super(message, category);\n        this.errorCode = GENERIC;\n        this.correlationId = correlationId;\n    }\n\n    /**\n     * ArchiveException exception, plus detail, error code, correlation id of control request,\n     * and {@link Category}.\n     *\n     * @param message       providing detail.\n     * @param errorCode     for type.\n     * @param correlationId of the control request.\n     * @param category      for type.\n     */\n    public ArchiveException(\n        final String message, final int errorCode, final long correlationId, final Category category)\n    {\n        super(message, category);\n        this.errorCode = errorCode;\n        this.correlationId = correlationId;\n    }\n\n    /**\n     * Error code providing more detail into what went wrong.\n     *\n     * @return code providing more detail into what went wrong.\n     */\n    public int errorCode()\n    {\n        return errorCode;\n    }\n\n    /**\n     * Optional correlation-id associated with a control protocol request. Will be {@link Aeron#NULL_VALUE} if\n     * not set.\n     *\n     * @return correlation-id associated with a control protocol request.\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * Return String representation for given error code.\n     *\n     * @param errorCode to convert.\n     * @return error code string.\n     */\n    public static String errorCodeAsString(final int errorCode)\n    {\n        return switch (errorCode)\n        {\n            case GENERIC -> \"GENERIC\";\n            case ACTIVE_LISTING -> \"ACTIVE_LISTING\";\n            case ACTIVE_RECORDING -> \"ACTIVE_RECORDING\";\n            case ACTIVE_SUBSCRIPTION -> \"ACTIVE_SUBSCRIPTION\";\n            case UNKNOWN_SUBSCRIPTION -> \"UNKNOWN_SUBSCRIPTION\";\n            case UNKNOWN_RECORDING -> \"UNKNOWN_RECORDING\";\n            case UNKNOWN_REPLAY -> \"UNKNOWN_REPLAY\";\n            case MAX_REPLAYS -> \"MAX_REPLAYS\";\n            case MAX_RECORDINGS -> \"MAX_RECORDINGS\";\n            case INVALID_EXTENSION -> \"INVALID_EXTENSION\";\n            case AUTHENTICATION_REJECTED -> \"AUTHENTICATION_REJECTED\";\n            case STORAGE_SPACE -> \"STORAGE_SPACE\";\n            case UNKNOWN_REPLICATION -> \"UNKNOWN_REPLICATION\";\n            case UNAUTHORISED_ACTION -> \"UNAUTHORISED_ACTION\";\n            case REPLICATION_CONNECTION_FAILURE -> \"REPLICATION_CONNECTION_FAILURE\";\n            default -> \"unknown error code: \" + errorCode;\n        };\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ArchiveProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.ArchiveIdRequestEncoder;\nimport io.aeron.archive.codecs.AttachSegmentsRequestEncoder;\nimport io.aeron.archive.codecs.AuthConnectRequestEncoder;\nimport io.aeron.archive.codecs.BooleanType;\nimport io.aeron.archive.codecs.BoundedReplayRequestEncoder;\nimport io.aeron.archive.codecs.ChallengeResponseEncoder;\nimport io.aeron.archive.codecs.CloseSessionRequestEncoder;\nimport io.aeron.archive.codecs.DeleteDetachedSegmentsRequestEncoder;\nimport io.aeron.archive.codecs.DetachSegmentsRequestEncoder;\nimport io.aeron.archive.codecs.ExtendRecordingRequest2Encoder;\nimport io.aeron.archive.codecs.ExtendRecordingRequestEncoder;\nimport io.aeron.archive.codecs.FindLastMatchingRecordingRequestEncoder;\nimport io.aeron.archive.codecs.KeepAliveRequestEncoder;\nimport io.aeron.archive.codecs.ListRecordingRequestEncoder;\nimport io.aeron.archive.codecs.ListRecordingSubscriptionsRequestEncoder;\nimport io.aeron.archive.codecs.ListRecordingsForUriRequestEncoder;\nimport io.aeron.archive.codecs.ListRecordingsRequestEncoder;\nimport io.aeron.archive.codecs.MaxRecordedPositionRequestEncoder;\nimport io.aeron.archive.codecs.MessageHeaderEncoder;\nimport io.aeron.archive.codecs.MigrateSegmentsRequestEncoder;\nimport io.aeron.archive.codecs.PurgeRecordingRequestEncoder;\nimport io.aeron.archive.codecs.PurgeSegmentsRequestEncoder;\nimport io.aeron.archive.codecs.RecordingPositionRequestEncoder;\nimport io.aeron.archive.codecs.ReplayRequestEncoder;\nimport io.aeron.archive.codecs.ReplayTokenRequestEncoder;\nimport io.aeron.archive.codecs.ReplicateRequest2Encoder;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.archive.codecs.StartPositionRequestEncoder;\nimport io.aeron.archive.codecs.StartRecordingRequest2Encoder;\nimport io.aeron.archive.codecs.StartRecordingRequestEncoder;\nimport io.aeron.archive.codecs.StopAllReplaysRequestEncoder;\nimport io.aeron.archive.codecs.StopPositionRequestEncoder;\nimport io.aeron.archive.codecs.StopRecordingByIdentityRequestEncoder;\nimport io.aeron.archive.codecs.StopRecordingRequestEncoder;\nimport io.aeron.archive.codecs.StopRecordingSubscriptionRequestEncoder;\nimport io.aeron.archive.codecs.StopReplayRequestEncoder;\nimport io.aeron.archive.codecs.StopReplicationRequestEncoder;\nimport io.aeron.archive.codecs.TruncateRecordingRequestEncoder;\nimport io.aeron.archive.codecs.UpdateChannelRequestEncoder;\nimport io.aeron.security.CredentialsSupplier;\nimport io.aeron.security.NullCredentialsSupplier;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.agrona.concurrent.YieldingIdleStrategy;\n\nimport static io.aeron.archive.client.AeronArchive.Configuration.MESSAGE_RETRY_ATTEMPTS_DEFAULT;\nimport static io.aeron.archive.client.AeronArchive.Configuration.MESSAGE_TIMEOUT_DEFAULT_NS;\n\n/**\n * Proxy class for encapsulating encoding and sending of control protocol messages to an archive.\n */\npublic final class ArchiveProxy\n{\n    /**\n     * Default number of retry attempts to be made when offering requests.\n     *\n     * @see AeronArchive.Configuration#MESSAGE_RETRY_ATTEMPTS_DEFAULT\n     */\n    public static final int DEFAULT_RETRY_ATTEMPTS = MESSAGE_RETRY_ATTEMPTS_DEFAULT;\n\n    private final long connectTimeoutNs;\n    private final int retryAttempts;\n    private final IdleStrategy retryIdleStrategy;\n    private final NanoClock nanoClock;\n    private final CredentialsSupplier credentialsSupplier;\n    private final String clientInfo;\n\n    private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(256);\n    private final ExclusivePublication publication;\n    private final MessageHeaderEncoder messageHeader = new MessageHeaderEncoder();\n    private final AuthConnectRequestEncoder connectRequestEncoder = new AuthConnectRequestEncoder();\n    private final KeepAliveRequestEncoder keepAliveRequestEncoder = new KeepAliveRequestEncoder();\n    private final CloseSessionRequestEncoder closeSessionRequestEncoder = new CloseSessionRequestEncoder();\n    private final ChallengeResponseEncoder challengeResponseEncoder = new ChallengeResponseEncoder();\n    private final StartRecordingRequestEncoder startRecordingRequest = new StartRecordingRequestEncoder();\n    private final StartRecordingRequest2Encoder startRecordingRequest2 = new StartRecordingRequest2Encoder();\n    private final StopRecordingRequestEncoder stopRecordingRequest = new StopRecordingRequestEncoder();\n    private final StopRecordingSubscriptionRequestEncoder stopRecordingSubscriptionRequest =\n        new StopRecordingSubscriptionRequestEncoder();\n    private final StopRecordingByIdentityRequestEncoder stopRecordingByIdentityRequest =\n        new StopRecordingByIdentityRequestEncoder();\n    private final ReplayRequestEncoder replayRequest = new ReplayRequestEncoder();\n    private final StopReplayRequestEncoder stopReplayRequest = new StopReplayRequestEncoder();\n    private final ListRecordingsRequestEncoder listRecordingsRequest = new ListRecordingsRequestEncoder();\n    private final ListRecordingsForUriRequestEncoder listRecordingsForUriRequest =\n        new ListRecordingsForUriRequestEncoder();\n    private final ListRecordingRequestEncoder listRecordingRequest = new ListRecordingRequestEncoder();\n    private final ExtendRecordingRequestEncoder extendRecordingRequest = new ExtendRecordingRequestEncoder();\n    private final ExtendRecordingRequest2Encoder extendRecordingRequest2 = new ExtendRecordingRequest2Encoder();\n    private final RecordingPositionRequestEncoder recordingPositionRequest = new RecordingPositionRequestEncoder();\n    private final TruncateRecordingRequestEncoder truncateRecordingRequest = new TruncateRecordingRequestEncoder();\n    private final PurgeRecordingRequestEncoder purgeRecordingRequest = new PurgeRecordingRequestEncoder();\n    private final StopPositionRequestEncoder stopPositionRequest = new StopPositionRequestEncoder();\n    private final MaxRecordedPositionRequestEncoder maxRecordedPositionRequestEncoder =\n        new MaxRecordedPositionRequestEncoder();\n    private final FindLastMatchingRecordingRequestEncoder findLastMatchingRecordingRequest =\n        new FindLastMatchingRecordingRequestEncoder();\n    private final ListRecordingSubscriptionsRequestEncoder listRecordingSubscriptionsRequest =\n        new ListRecordingSubscriptionsRequestEncoder();\n    private final BoundedReplayRequestEncoder boundedReplayRequest = new BoundedReplayRequestEncoder();\n    private final StopAllReplaysRequestEncoder stopAllReplaysRequest = new StopAllReplaysRequestEncoder();\n    private final ReplicateRequest2Encoder replicateRequest = new ReplicateRequest2Encoder();\n    private final StopReplicationRequestEncoder stopReplicationRequest = new StopReplicationRequestEncoder();\n    private final StartPositionRequestEncoder startPositionRequest = new StartPositionRequestEncoder();\n    private final DetachSegmentsRequestEncoder detachSegmentsRequest = new DetachSegmentsRequestEncoder();\n    private final DeleteDetachedSegmentsRequestEncoder deleteDetachedSegmentsRequest =\n        new DeleteDetachedSegmentsRequestEncoder();\n    private final PurgeSegmentsRequestEncoder purgeSegmentsRequest = new PurgeSegmentsRequestEncoder();\n    private final AttachSegmentsRequestEncoder attachSegmentsRequest = new AttachSegmentsRequestEncoder();\n    private final MigrateSegmentsRequestEncoder migrateSegmentsRequest = new MigrateSegmentsRequestEncoder();\n    private final ArchiveIdRequestEncoder archiveIdRequestEncoder = new ArchiveIdRequestEncoder();\n    private final ReplayTokenRequestEncoder replayTokenRequestEncoder = new ReplayTokenRequestEncoder();\n    private final UpdateChannelRequestEncoder updateChannelRequestEncoder = new UpdateChannelRequestEncoder();\n\n    /**\n     * Create a proxy with a {@link ExclusivePublication} for sending control message requests.\n     * <p>\n     * This provides a default {@link IdleStrategy} of a {@link YieldingIdleStrategy} when offers are back pressured\n     * with a defaults of {@link AeronArchive.Configuration#MESSAGE_TIMEOUT_DEFAULT_NS} and\n     * {@link AeronArchive.Configuration#MESSAGE_RETRY_ATTEMPTS_DEFAULT}.\n     *\n     * @param publication publication for sending control messages to an archive.\n     * @throws ClassCastException if {@code publication} is not an instance of {@link ExclusivePublication}.\n     * @deprecated Use another constructor with an {@link ExclusivePublication}.\n     */\n    @Deprecated(forRemoval = true, since = \"1.47.0\")\n    public ArchiveProxy(final Publication publication)\n    {\n        this((ExclusivePublication)publication);\n    }\n\n    /**\n     * Create a proxy with a {@link ExclusivePublication} for sending control message requests.\n     * <p>\n     * This provides a default {@link IdleStrategy} of a {@link YieldingIdleStrategy} when offers are back pressured\n     * with a defaults of {@link AeronArchive.Configuration#MESSAGE_TIMEOUT_DEFAULT_NS} and\n     * {@link #DEFAULT_RETRY_ATTEMPTS}.\n     *\n     * @param publication publication for sending control messages to an archive.\n     * @throws ClassCastException if {@code publication} is not an instance of {@link ExclusivePublication}.\n     */\n    public ArchiveProxy(final ExclusivePublication publication)\n    {\n        this(\n            publication,\n            YieldingIdleStrategy.INSTANCE,\n            SystemNanoClock.INSTANCE,\n            MESSAGE_TIMEOUT_DEFAULT_NS,\n            DEFAULT_RETRY_ATTEMPTS,\n            new NullCredentialsSupplier(),\n            null);\n    }\n\n    /**\n     * Create a proxy with a {@link ExclusivePublication} for sending control message requests.\n     *\n     * @param publication         publication for sending control messages to an archive.\n     * @param retryIdleStrategy   for what should happen between retry attempts at offering messages.\n     * @param nanoClock           to be used for calculating checking deadlines.\n     * @param connectTimeoutNs    for connection requests.\n     * @param retryAttempts       for offering control messages before giving up.\n     * @param credentialsSupplier for the AuthConnectRequest\n     * @throws ClassCastException if {@code publication} is not an instance of {@link ExclusivePublication}.\n     * @deprecated Use another constructor with an {@link ExclusivePublication}.\n     */\n    @Deprecated(forRemoval = true, since = \"1.47.0\")\n    public ArchiveProxy(\n        final Publication publication,\n        final IdleStrategy retryIdleStrategy,\n        final NanoClock nanoClock,\n        final long connectTimeoutNs,\n        final int retryAttempts,\n        final CredentialsSupplier credentialsSupplier)\n    {\n        this(\n            (ExclusivePublication)publication,\n            retryIdleStrategy,\n            nanoClock,\n            connectTimeoutNs,\n            retryAttempts,\n            credentialsSupplier);\n    }\n\n    /**\n     * Create a proxy with a {@link ExclusivePublication} for sending control message requests without specifying\n     * client details.\n     *\n     * @param publication         publication for sending control messages to an archive.\n     * @param retryIdleStrategy   for what should happen between retry attempts at offering messages.\n     * @param nanoClock           to be used for calculating checking deadlines.\n     * @param connectTimeoutNs    for connection requests.\n     * @param retryAttempts       for offering control messages before giving up.\n     * @param credentialsSupplier for the AuthConnectRequest\n     * @since 1.47.0\n     */\n    public ArchiveProxy(\n        final ExclusivePublication publication,\n        final IdleStrategy retryIdleStrategy,\n        final NanoClock nanoClock,\n        final long connectTimeoutNs,\n        final int retryAttempts,\n        final CredentialsSupplier credentialsSupplier)\n    {\n        this(publication, retryIdleStrategy, nanoClock, connectTimeoutNs, retryAttempts, credentialsSupplier, null);\n    }\n\n    /**\n     * Create a proxy with a {@link ExclusivePublication} for sending control message requests with specified\n     * client info.\n     *\n     * @param publication         publication for sending control messages to an archive.\n     * @param retryIdleStrategy   for what should happen between retry attempts at offering messages.\n     * @param nanoClock           to be used for calculating checking deadlines.\n     * @param connectTimeoutNs    for connection requests.\n     * @param retryAttempts       for offering control messages before giving up.\n     * @param credentialsSupplier for the {@code AuthConnectRequest}.\n     * @param clientInfo          for the {@code AuthConnectRequest}.\n     * @since 1.49.0\n     */\n    public ArchiveProxy(\n        final ExclusivePublication publication,\n        final IdleStrategy retryIdleStrategy,\n        final NanoClock nanoClock,\n        final long connectTimeoutNs,\n        final int retryAttempts,\n        final CredentialsSupplier credentialsSupplier,\n        final String clientInfo)\n    {\n        this.publication = publication;\n        this.retryIdleStrategy = retryIdleStrategy;\n        this.nanoClock = nanoClock;\n        this.connectTimeoutNs = connectTimeoutNs;\n        this.retryAttempts = retryAttempts;\n        this.credentialsSupplier = credentialsSupplier;\n        this.clientInfo = clientInfo;\n    }\n\n    /**\n     * Get the {@link Publication} used for sending control messages.\n     *\n     * @return the {@link Publication} used for sending control messages.\n     */\n    public Publication publication()\n    {\n        return publication;\n    }\n\n    /**\n     * Connect to an archive on its control interface providing the response stream details.\n     *\n     * @param responseChannel  for the control message responses.\n     * @param responseStreamId for the control message responses.\n     * @param correlationId    for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean connect(final String responseChannel, final int responseStreamId, final long correlationId)\n    {\n        final byte[] encodedCredentials = credentialsSupplier.encodedCredentials();\n\n        connectRequestEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .correlationId(correlationId)\n            .responseStreamId(responseStreamId)\n            .version(AeronArchive.Configuration.PROTOCOL_SEMANTIC_VERSION)\n            .responseChannel(responseChannel)\n            .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length)\n            .clientInfo(clientInfo);\n\n        return offerWithTimeout(connectRequestEncoder.encodedLength(), null);\n    }\n\n    /**\n     * Try and connect to an archive on its control interface providing the response stream details. Only one attempt\n     * will be made to offer the request.\n     *\n     * @param responseChannel  for the control message responses.\n     * @param responseStreamId for the control message responses.\n     * @param correlationId    for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean tryConnect(final String responseChannel, final int responseStreamId, final long correlationId)\n    {\n        final byte[] encodedCredentials = credentialsSupplier.encodedCredentials();\n\n        connectRequestEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .correlationId(correlationId)\n            .responseStreamId(responseStreamId)\n            .version(AeronArchive.Configuration.PROTOCOL_SEMANTIC_VERSION)\n            .responseChannel(responseChannel)\n            .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length)\n            .clientInfo(clientInfo);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + connectRequestEncoder.encodedLength();\n\n        return publication.offer(buffer, 0, length) > 0;\n    }\n\n    /**\n     * Keep this archive session alive by notifying the archive.\n     *\n     * @param controlSessionId with the archive.\n     * @param correlationId    for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean keepAlive(final long controlSessionId, final long correlationId)\n    {\n        keepAliveRequestEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId);\n\n        return offer(keepAliveRequestEncoder.encodedLength());\n    }\n\n    /**\n     * Close this control session with the archive.\n     *\n     * @param controlSessionId with the archive.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean closeSession(final long controlSessionId)\n    {\n        closeSessionRequestEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId);\n\n        return offer(closeSessionRequestEncoder.encodedLength());\n    }\n\n    /**\n     * Try and send a ChallengeResponse to an archive on its control interface providing the credentials. Only one\n     * attempt will be made to offer the request.\n     *\n     * @param encodedCredentials to send.\n     * @param correlationId      for this response.\n     * @param controlSessionId   for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean tryChallengeResponse(\n        final byte[] encodedCredentials, final long correlationId, final long controlSessionId)\n    {\n        challengeResponseEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + challengeResponseEncoder.encodedLength();\n\n        return publication.offer(buffer, 0, length) > 0;\n    }\n\n    /**\n     * Start recording streams for a given channel and stream id pairing.\n     *\n     * @param channel          to be recorded.\n     * @param streamId         to be recorded.\n     * @param sourceLocation   of the publication to be recorded.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean startRecording(\n        final String channel,\n        final int streamId,\n        final SourceLocation sourceLocation,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        startRecordingRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .streamId(streamId)\n            .sourceLocation(sourceLocation)\n            .channel(channel);\n\n        return offer(startRecordingRequest.encodedLength());\n    }\n\n    /**\n     * Start recording streams for a given channel and stream id pairing.\n     *\n     * @param channel          to be recorded.\n     * @param streamId         to be recorded.\n     * @param sourceLocation   of the publication to be recorded.\n     * @param autoStop         if the recording should be automatically stopped when complete.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean startRecording(\n        final String channel,\n        final int streamId,\n        final SourceLocation sourceLocation,\n        final boolean autoStop,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        startRecordingRequest2\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .streamId(streamId)\n            .sourceLocation(sourceLocation)\n            .autoStop(autoStop ? BooleanType.TRUE : BooleanType.FALSE)\n            .channel(channel);\n\n        return offer(startRecordingRequest2.encodedLength());\n    }\n\n    /**\n     * Stop an active recording.\n     *\n     * @param channel          to be stopped.\n     * @param streamId         to be stopped.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean stopRecording(\n        final String channel, final int streamId, final long correlationId, final long controlSessionId)\n    {\n        stopRecordingRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .streamId(streamId)\n            .channel(channel);\n\n        return offer(stopRecordingRequest.encodedLength());\n    }\n\n    /**\n     * Stop a recording by the {@link Subscription#registrationId()} it was registered with.\n     *\n     * @param subscriptionId   that identifies the subscription in the archive doing the recording.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean stopRecording(final long subscriptionId, final long correlationId, final long controlSessionId)\n    {\n        stopRecordingSubscriptionRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .subscriptionId(subscriptionId);\n\n        return offer(stopRecordingSubscriptionRequest.encodedLength());\n    }\n\n    /**\n     * Stop an active recording by the recording id. This is not the {@link Subscription#registrationId()}.\n     *\n     * @param recordingId      that identifies a recording in the archive.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean stopRecordingByIdentity(\n        final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        stopRecordingByIdentityRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(stopRecordingByIdentityRequest.encodedLength());\n    }\n\n    /**\n     * Replay a recording from a given position. Supports specifying {@link ReplayParams} to change the behaviour of the\n     * replay. For example a bounded replay can be requested by specifying the boundingLimitCounterId. The ReplayParams\n     * is free to be reused after this call completes.\n     *\n     * @param recordingId      to be replayed.\n     * @param replayChannel    to which the replay should be sent.\n     * @param replayStreamId   to which the replay should be sent.\n     * @param replayParams     optional parameters change the behaviour of the replay.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     * @see ReplayParams\n     */\n    public boolean replay(\n        final long recordingId,\n        final String replayChannel,\n        final int replayStreamId,\n        final ReplayParams replayParams,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        if (replayParams.isBounded())\n        {\n            return boundedReplay(\n                recordingId,\n                replayParams.position(),\n                replayParams.length(),\n                replayParams.boundingLimitCounterId(),\n                replayChannel,\n                replayStreamId,\n                correlationId,\n                controlSessionId,\n                replayParams.fileIoMaxLength(),\n                replayParams.replayToken());\n        }\n        else\n        {\n            return replay(\n                recordingId,\n                replayParams.position(),\n                replayParams.length(),\n                replayChannel,\n                replayStreamId,\n                correlationId,\n                controlSessionId,\n                replayParams.fileIoMaxLength(),\n                replayParams.replayToken());\n        }\n    }\n\n    /**\n     * Replay a recording from a given position.\n     *\n     * @param recordingId      to be replayed.\n     * @param position         from which the replay should be started.\n     * @param length           of the stream to be replayed. Use {@link Long#MAX_VALUE} to follow a live stream.\n     * @param replayChannel    to which the replay should be sent.\n     * @param replayStreamId   to which the replay should be sent.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean replay(\n        final long recordingId,\n        final long position,\n        final long length,\n        final String replayChannel,\n        final int replayStreamId,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        return replay(\n            recordingId,\n            position,\n            length,\n            replayChannel,\n            replayStreamId,\n            correlationId,\n            controlSessionId,\n            Aeron.NULL_VALUE,\n            Aeron.NULL_VALUE);\n    }\n\n    /**\n     * Replay a recording from a given position bounded by a position counter.\n     *\n     * @param recordingId      to be replayed.\n     * @param position         from which the replay should be started.\n     * @param length           of the stream to be replayed. Use {@link Long#MAX_VALUE} to follow a live stream.\n     * @param limitCounterId   to use as the replay bound.\n     * @param replayChannel    to which the replay should be sent.\n     * @param replayStreamId   to which the replay should be sent.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean boundedReplay(\n        final long recordingId,\n        final long position,\n        final long length,\n        final int limitCounterId,\n        final String replayChannel,\n        final int replayStreamId,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        return boundedReplay(\n            recordingId,\n            position,\n            length,\n            limitCounterId,\n            replayChannel,\n            replayStreamId,\n            correlationId,\n            controlSessionId,\n            Aeron.NULL_VALUE,\n            Aeron.NULL_VALUE);\n    }\n\n    /**\n     * Stop an existing replay session.\n     *\n     * @param replaySessionId  that should be stopped.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean stopReplay(final long replaySessionId, final long correlationId, final long controlSessionId)\n    {\n        stopReplayRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .replaySessionId(replaySessionId);\n\n        return offer(stopReplayRequest.encodedLength());\n    }\n\n    /**\n     * Stop any existing replay sessions for recording id or all replay sessions regardless of recording id.\n     *\n     * @param recordingId      that should be stopped.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean stopAllReplays(final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        stopAllReplaysRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(stopAllReplaysRequest.encodedLength());\n    }\n\n    /**\n     * List a range of recording descriptors.\n     *\n     * @param fromRecordingId  at which to begin listing.\n     * @param recordCount      for the number of descriptors to be listed.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean listRecordings(\n        final long fromRecordingId, final int recordCount, final long correlationId, final long controlSessionId)\n    {\n        listRecordingsRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .fromRecordingId(fromRecordingId)\n            .recordCount(recordCount);\n\n        return offer(listRecordingsRequest.encodedLength());\n    }\n\n    /**\n     * List a range of recording descriptors which match a channel URI fragment and stream id.\n     *\n     * @param fromRecordingId  at which to begin listing.\n     * @param recordCount      for the number of descriptors to be listed.\n     * @param channelFragment  to match recordings on from the original channel URI in the archive descriptor.\n     * @param streamId         to match recordings on.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean listRecordingsForUri(\n        final long fromRecordingId,\n        final int recordCount,\n        final String channelFragment,\n        final int streamId,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        listRecordingsForUriRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .fromRecordingId(fromRecordingId)\n            .recordCount(recordCount)\n            .streamId(streamId)\n            .channel(channelFragment);\n\n        return offer(listRecordingsForUriRequest.encodedLength());\n    }\n\n    /**\n     * List a recording descriptor for a given recording id.\n     *\n     * @param recordingId      at which to begin listing.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean listRecording(final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        listRecordingRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(listRecordingRequest.encodedLength());\n    }\n\n    /**\n     * Extend an existing, non-active, recorded stream for the same channel and stream id.\n     * <p>\n     * The channel must be configured for the initial position from which it will be extended. This can be done\n     * with {@link ChannelUriStringBuilder#initialPosition(long, int, int)}. The details required to initialise can\n     * be found by calling {@link #listRecording(long, long, long)}.\n     *\n     * @param channel          to be recorded.\n     * @param streamId         to be recorded.\n     * @param sourceLocation   of the publication to be recorded.\n     * @param recordingId      to be extended.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean extendRecording(\n        final String channel,\n        final int streamId,\n        final SourceLocation sourceLocation,\n        final long recordingId,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        extendRecordingRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId)\n            .streamId(streamId)\n            .sourceLocation(sourceLocation)\n            .channel(channel);\n\n        return offer(extendRecordingRequest.encodedLength());\n    }\n\n    /**\n     * Extend an existing, non-active, recorded stream for the same channel and stream id.\n     * <p>\n     * The channel must be configured for the initial position from which it will be extended. This can be done\n     * with {@link ChannelUriStringBuilder#initialPosition(long, int, int)}. The details required to initialise can\n     * be found by calling {@link #listRecording(long, long, long)}.\n     *\n     * @param channel          to be recorded.\n     * @param streamId         to be recorded.\n     * @param sourceLocation   of the publication to be recorded.\n     * @param autoStop         if the recording should be automatically stopped when complete.\n     * @param recordingId      to be extended.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean extendRecording(\n        final String channel,\n        final int streamId,\n        final SourceLocation sourceLocation,\n        final boolean autoStop,\n        final long recordingId,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        extendRecordingRequest2\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId)\n            .streamId(streamId)\n            .sourceLocation(sourceLocation)\n            .autoStop(autoStop ? BooleanType.TRUE : BooleanType.FALSE)\n            .channel(channel);\n\n        return offer(extendRecordingRequest2.encodedLength());\n    }\n\n    /**\n     * Get the recorded position of an active recording.\n     *\n     * @param recordingId      of the active recording that the position is being requested for.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean getRecordingPosition(final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        recordingPositionRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(recordingPositionRequest.encodedLength());\n    }\n\n    /**\n     * Truncate a stopped recording to a given position that is less than the stopped position. The provided position\n     * must be on a fragment boundary. Truncating a recording to the start position effectively deletes the recording.\n     *\n     * @param recordingId      of the stopped recording to be truncated.\n     * @param position         to which the recording will be truncated.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean truncateRecording(\n        final long recordingId, final long position, final long correlationId, final long controlSessionId)\n    {\n        truncateRecordingRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId)\n            .position(position);\n\n        return offer(truncateRecordingRequest.encodedLength());\n    }\n\n    /**\n     * Purge a stopped recording, i.e. mark recording as {@link io.aeron.archive.codecs.RecordingState#INVALID}\n     * and delete the corresponding segment files. The space in the Catalog will be reclaimed upon compaction.\n     *\n     * @param recordingId      of the stopped recording to be purged.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean purgeRecording(\n        final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        purgeRecordingRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(purgeRecordingRequest.encodedLength());\n    }\n\n    /**\n     * Get the start position of a recording.\n     *\n     * @param recordingId      of the recording that the position is being requested for.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean getStartPosition(final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        startPositionRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(startPositionRequest.encodedLength());\n    }\n\n    /**\n     * Get the stop position of a recording.\n     *\n     * @param recordingId      of the recording that the stop position is being requested for.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean getStopPosition(final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        stopPositionRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(stopPositionRequest.encodedLength());\n    }\n\n    /**\n     * Get the stop or active recorded position of a recording.\n     *\n     * @param recordingId      of the recording that the stop of active recording position is being requested for.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean getMaxRecordedPosition(\n        final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        maxRecordedPositionRequestEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(maxRecordedPositionRequestEncoder.encodedLength());\n    }\n\n    /**\n     * Get the id of the Archive.\n     *\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean archiveId(final long correlationId, final long controlSessionId)\n    {\n        archiveIdRequestEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId);\n\n        return offer(archiveIdRequestEncoder.encodedLength());\n    }\n\n    /**\n     * Find the last recording that matches the given criteria.\n     *\n     * @param minRecordingId   to search back to.\n     * @param channelFragment  for a contains match on the original channel stored with the archive descriptor.\n     * @param streamId         of the recording to match.\n     * @param sessionId        of the recording to match.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean findLastMatchingRecording(\n        final long minRecordingId,\n        final String channelFragment,\n        final int streamId,\n        final int sessionId,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        findLastMatchingRecordingRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .minRecordingId(minRecordingId)\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .channel(channelFragment);\n\n        return offer(findLastMatchingRecordingRequest.encodedLength());\n    }\n\n    /**\n     * List registered subscriptions in the archive which have been used to record streams.\n     *\n     * @param pseudoIndex       in the list of active recording subscriptions.\n     * @param subscriptionCount for the number of descriptors to be listed.\n     * @param channelFragment   for a contains match on the stripped channel used with the registered subscription.\n     * @param streamId          for the subscription.\n     * @param applyStreamId     when matching.\n     * @param correlationId     for this request.\n     * @param controlSessionId  for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean listRecordingSubscriptions(\n        final int pseudoIndex,\n        final int subscriptionCount,\n        final String channelFragment,\n        final int streamId,\n        final boolean applyStreamId,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        listRecordingSubscriptionsRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .pseudoIndex(pseudoIndex)\n            .subscriptionCount(subscriptionCount)\n            .applyStreamId(applyStreamId ? BooleanType.TRUE : BooleanType.FALSE)\n            .streamId(streamId)\n            .channel(channelFragment);\n\n        return offer(listRecordingSubscriptionsRequest.encodedLength());\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The source recording will be replayed via the provided replay channel and use the original stream id.\n     * If the destination recording id is {@link io.aeron.Aeron#NULL_VALUE} then a new destination recording is created,\n     * otherwise the provided destination recording id will be extended. The details of the source recording\n     * descriptor will be replicated.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param dstRecordingId     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param liveDestination    destination for the live stream if merge is required. Empty or null for no merge.\n     * @param correlationId      for this request.\n     * @param controlSessionId   for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean replicate(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        return replicate(\n            srcRecordingId,\n            dstRecordingId,\n            AeronArchive.NULL_POSITION,\n            Aeron.NULL_VALUE,\n            Aeron.NULL_VALUE,\n            srcControlStreamId,\n            srcControlChannel,\n            liveDestination,\n            null,\n            correlationId,\n            controlSessionId,\n            Aeron.NULL_VALUE,\n            Aeron.NULL_VALUE,\n            NullCredentialsSupplier.NULL_CREDENTIAL,\n            null);\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The source recording will be replayed via the provided replay channel and use the original stream id.\n     * If the destination recording id is {@link io.aeron.Aeron#NULL_VALUE} then a new destination recording is created,\n     * otherwise the provided destination recording id will be extended. The details of the source recording\n     * descriptor will be replicated.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param dstRecordingId     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.\n     * @param stopPosition       position to stop the replication. {@link AeronArchive#NULL_POSITION} to stop at end\n     *                           of current recording.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param liveDestination    destination for the live stream if merge is required. Empty or null for no merge.\n     * @param replicationChannel channel over which the replication will occur. Empty or null for default channel.\n     * @param correlationId      for this request.\n     * @param controlSessionId   for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean replicate(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long stopPosition,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination,\n        final String replicationChannel,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        return replicate(\n            srcRecordingId,\n            dstRecordingId,\n            stopPosition,\n            Aeron.NULL_VALUE,\n            Aeron.NULL_VALUE,\n            srcControlStreamId,\n            srcControlChannel,\n            liveDestination,\n            replicationChannel,\n            correlationId,\n            controlSessionId,\n            Aeron.NULL_VALUE,\n            Aeron.NULL_VALUE,\n            NullCredentialsSupplier.NULL_CREDENTIAL,\n            null);\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The source recording will be replayed via the provided replay channel and use the original stream id.\n     * If the destination recording id is {@link io.aeron.Aeron#NULL_VALUE} then a new destination recording is created,\n     * otherwise the provided destination recording id will be extended. The details of the source recording\n     * descriptor will be replicated. The subscription used in the archive will be tagged with the provided tags.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param dstRecordingId     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.\n     * @param channelTagId       used to tag the replication subscription.\n     * @param subscriptionTagId  used to tag the replication subscription.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param liveDestination    destination for the live stream if merge is required. Empty or null for no merge.\n     * @param correlationId      for this request.\n     * @param controlSessionId   for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean taggedReplicate(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long channelTagId,\n        final long subscriptionTagId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        return replicate(\n            srcRecordingId,\n            dstRecordingId,\n            AeronArchive.NULL_POSITION,\n            channelTagId,\n            subscriptionTagId,\n            srcControlStreamId,\n            srcControlChannel,\n            liveDestination,\n            null,\n            correlationId,\n            controlSessionId,\n            Aeron.NULL_VALUE,\n            Aeron.NULL_VALUE,\n            NullCredentialsSupplier.NULL_CREDENTIAL,\n            null);\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The source recording will be replayed via the provided replay channel and use the original stream id.\n     * If the destination recording id is {@link io.aeron.Aeron#NULL_VALUE} then a new destination recording is created,\n     * otherwise the provided destination recording id will be extended. The details of the source recording\n     * descriptor will be replicated. The subscription used in the archive will be tagged with the provided tags.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param dstRecordingId     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.\n     * @param stopPosition       position to stop the replication. {@link AeronArchive#NULL_POSITION} to stop at end\n     *                           of current recording.\n     * @param channelTagId       used to tag the replication subscription.\n     * @param subscriptionTagId  used to tag the replication subscription.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param liveDestination    destination for the live stream if merge is required. Empty or null for no merge.\n     * @param replicationChannel channel over which the replication will occur. Empty or null for default channel.\n     * @param correlationId      for this request.\n     * @param controlSessionId   for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean taggedReplicate(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long stopPosition,\n        final long channelTagId,\n        final long subscriptionTagId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination,\n        final String replicationChannel,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        return replicate(\n            srcRecordingId,\n            dstRecordingId,\n            stopPosition,\n            channelTagId,\n            subscriptionTagId,\n            srcControlStreamId,\n            srcControlChannel,\n            liveDestination,\n            replicationChannel,\n            correlationId,\n            controlSessionId,\n            Aeron.NULL_VALUE,\n            Aeron.NULL_VALUE,\n            NullCredentialsSupplier.NULL_CREDENTIAL,\n            null);\n    }\n\n    /**\n     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary\n     * archive. The behaviour of the replication is controlled through the {@link ReplicationParams}.\n     * <p>\n     * For a source recording that is still active the replay can merge with the live stream and then follow it\n     * directly and no longer require the replay from the source. This would require a multicast live destination.\n     * <p>\n     * Errors will be reported asynchronously and can be checked for with {@link AeronArchive#pollForErrorResponse()}\n     * or {@link AeronArchive#checkForErrorResponse()}.\n     * <p>\n     * The ReplicationParams is free to be reused when this call completes.\n     *\n     * @param srcRecordingId     recording id which must exist in the source archive.\n     * @param srcControlChannel  remote control channel for the source archive to instruct the replay on.\n     * @param srcControlStreamId remote control stream id for the source archive to instruct the replay on.\n     * @param replicationParams  optional parameters to control the behaviour of the replication.\n     * @param correlationId      for this request.\n     * @param controlSessionId   for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     * @see ReplicationParams\n     */\n    public boolean replicate(\n        final long srcRecordingId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final ReplicationParams replicationParams,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        if (null != replicationParams.liveDestination() && Aeron.NULL_VALUE != replicationParams.replicationSessionId())\n        {\n            throw new IllegalArgumentException(\n                \"ReplicationParams.liveDestination and ReplicationParams.sessionId can not be specified together\");\n        }\n\n        return replicate(\n            srcRecordingId,\n            replicationParams.dstRecordingId(),\n            replicationParams.stopPosition(),\n            replicationParams.channelTagId(),\n            replicationParams.subscriptionTagId(),\n            srcControlStreamId,\n            srcControlChannel,\n            replicationParams.liveDestination(),\n            replicationParams.replicationChannel(),\n            correlationId,\n            controlSessionId,\n            replicationParams.fileIoMaxLength(),\n            replicationParams.replicationSessionId(),\n            replicationParams.encodedCredentials(),\n            replicationParams.srcResponseChannel());\n    }\n\n    /**\n     * Stop an active replication by the registration id it was registered with.\n     *\n     * @param replicationId    that identifies the session in the archive doing the replication.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean stopReplication(final long replicationId, final long correlationId, final long controlSessionId)\n    {\n        stopReplicationRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .replicationId(replicationId);\n\n        return offer(stopReplicationRequest.encodedLength());\n    }\n\n    /**\n     * Detach segments from the beginning of a recording up to the provided new start position.\n     * <p>\n     * The new start position must be first byte position of a segment after the existing start position.\n     * <p>\n     * It is not possible to detach segments which are active for recording or being replayed.\n     *\n     * @param recordingId      to which the operation applies.\n     * @param newStartPosition for the recording after the segments are detached.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     * @see AeronArchive#segmentFileBasePosition(long, long, int, int)\n     */\n    public boolean detachSegments(\n        final long recordingId, final long newStartPosition, final long correlationId, final long controlSessionId)\n    {\n        detachSegmentsRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId)\n            .newStartPosition(newStartPosition);\n\n        return offer(detachSegmentsRequest.encodedLength());\n    }\n\n    /**\n     * Delete segments which have been previously detached from a recording.\n     *\n     * @param recordingId      to which the operation applies.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     * @see #detachSegments(long, long, long, long)\n     */\n    public boolean deleteDetachedSegments(final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        deleteDetachedSegmentsRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(deleteDetachedSegmentsRequest.encodedLength());\n    }\n\n    /**\n     * Purge (detach and delete) segments from the beginning of a recording up to the provided new start position.\n     * <p>\n     * The new start position must be first byte position of a segment after the existing start position.\n     * <p>\n     * It is not possible to purge segments which are active for recording or being replayed.\n     *\n     * @param recordingId      to which the operation applies.\n     * @param newStartPosition for the recording after the segments are detached.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     * @see #detachSegments(long, long, long, long)\n     * @see #deleteDetachedSegments(long, long, long)\n     * @see AeronArchive#segmentFileBasePosition(long, long, int, int)\n     */\n    public boolean purgeSegments(\n        final long recordingId, final long newStartPosition, final long correlationId, final long controlSessionId)\n    {\n        purgeSegmentsRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId)\n            .newStartPosition(newStartPosition);\n\n        return offer(purgeSegmentsRequest.encodedLength());\n    }\n\n    /**\n     * Attach segments to the beginning of a recording to restore history that was previously detached.\n     * <p>\n     * Segment files must match the existing recording and join exactly to the start position of the recording\n     * they are being attached to.\n     *\n     * @param recordingId      to which the operation applies.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     * @see #detachSegments(long, long, long, long)\n     */\n    public boolean attachSegments(final long recordingId, final long correlationId, final long controlSessionId)\n    {\n        attachSegmentsRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId);\n\n        return offer(attachSegmentsRequest.encodedLength());\n    }\n\n    /**\n     * Migrate segments from a source recording and attach them to the beginning or end of a destination recording.\n     * <p>\n     * The source recording must match the destination recording for segment length, term length, mtu length, and\n     * stream id. The source recording must join to the destination recording on a segment boundary and without gaps,\n     * i.e., the stop position and term id of one must match the start position and term id of the other.\n     * <p>\n     * The source recording must be stopped. The destination recording must be stopped if migrating segments\n     * to the end of the destination recording.\n     * <p>\n     * The source recording will be effectively truncated back to its start position after the migration.\n     *\n     * @param srcRecordingId   source recording from which the segments will be migrated.\n     * @param dstRecordingId   destination recording to which the segments will be attached.\n     * @param correlationId    for this request.\n     * @param controlSessionId for this request.\n     * @return {@code true} if successfully offered otherwise {@code false}.\n     */\n    public boolean migrateSegments(\n        final long srcRecordingId, final long dstRecordingId, final long correlationId, final long controlSessionId)\n    {\n        migrateSegmentsRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .srcRecordingId(srcRecordingId)\n            .dstRecordingId(dstRecordingId);\n\n        return offer(migrateSegmentsRequest.encodedLength());\n    }\n\n    /**\n     * Request a token for this session that will allow a replay to be initiated from another image without\n     * re-authentication.\n     *\n     * @param lastCorrelationId for the request\n     * @param controlSessionId  for the request\n     * @param recordingId       that will be replayed.\n     * @return true if successfully offered\n     */\n    public boolean requestReplayToken(final long lastCorrelationId, final long controlSessionId, final long recordingId)\n    {\n        replayTokenRequestEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(lastCorrelationId)\n            .recordingId(recordingId);\n\n        return offer(replayTokenRequestEncoder.encodedLength());\n    }\n\n    /**\n     * Update the channel for a recording.\n     *\n     * @param recordingId      the recording id to update.\n     * @param channel          the new channel to include in the catalogue.\n     * @param correlationId    for the request.\n     * @param controlSessionId for the request.\n     * @return true if successfully offered.\n     */\n    public boolean updateChannel(\n        final long recordingId,\n        final String channel,\n        final long correlationId,\n        final long controlSessionId)\n    {\n        updateChannelRequestEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId)\n            .channel(channel);\n\n        return offer(updateChannelRequestEncoder.encodedLength());\n    }\n\n    private boolean offer(final int length)\n    {\n        retryIdleStrategy.reset();\n\n        int attempts = retryAttempts;\n        while (true)\n        {\n            final long position = publication.offer(buffer, 0, MessageHeaderEncoder.ENCODED_LENGTH + length);\n            if (position > 0)\n            {\n                return true;\n            }\n\n            if (position == Publication.CLOSED)\n            {\n                throw new ArchiveException(\"connection to the archive has been closed\");\n            }\n\n            if (position == Publication.NOT_CONNECTED)\n            {\n                throw new ArchiveException(\"connection to the archive is no longer available\");\n            }\n\n            if (position == Publication.MAX_POSITION_EXCEEDED)\n            {\n                throw new ArchiveException(\n                    \"offer failed due to max position being reached: term-length=\" + publication.termBufferLength());\n            }\n\n            if (--attempts <= 0)\n            {\n                return false;\n            }\n\n            retryIdleStrategy.idle();\n        }\n    }\n\n    private boolean offerWithTimeout(final int length, final AgentInvoker aeronClientInvoker)\n    {\n        retryIdleStrategy.reset();\n\n        final long deadlineNs = nanoClock.nanoTime() + connectTimeoutNs;\n        while (true)\n        {\n            final long position = publication.offer(buffer, 0, MessageHeaderEncoder.ENCODED_LENGTH + length);\n            if (position > 0)\n            {\n                return true;\n            }\n\n            if (position == Publication.CLOSED)\n            {\n                throw new ArchiveException(\"connection to the archive has been closed\");\n            }\n\n            if (position == Publication.MAX_POSITION_EXCEEDED)\n            {\n                throw new ArchiveException(\n                    \"offer failed due to max position being reached: term-length=\" + publication.termBufferLength());\n            }\n\n            if (deadlineNs - nanoClock.nanoTime() < 0)\n            {\n                return false;\n            }\n\n            if (null != aeronClientInvoker)\n            {\n                aeronClientInvoker.invoke();\n            }\n\n            retryIdleStrategy.idle();\n        }\n    }\n\n    private boolean replay(\n        final long recordingId,\n        final long position,\n        final long length,\n        final String replayChannel,\n        final int replayStreamId,\n        final long correlationId,\n        final long controlSessionId,\n        final int fileIoMaxLength,\n        final long replayToken)\n    {\n        replayRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId)\n            .position(position)\n            .length(length)\n            .replayStreamId(replayStreamId)\n            .fileIoMaxLength(fileIoMaxLength)\n            .replayToken(replayToken)\n            .replayChannel(replayChannel);\n\n        return offer(replayRequest.encodedLength());\n    }\n\n    private boolean boundedReplay(\n        final long recordingId,\n        final long position,\n        final long length,\n        final int limitCounterId,\n        final String replayChannel,\n        final int replayStreamId,\n        final long correlationId,\n        final long controlSessionId,\n        final int fileIoMaxLength,\n        final long replayToken)\n    {\n        boundedReplayRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .recordingId(recordingId)\n            .position(position)\n            .length(length)\n            .limitCounterId(limitCounterId)\n            .replayStreamId(replayStreamId)\n            .fileIoMaxLength(fileIoMaxLength)\n            .replayToken(replayToken)\n            .replayChannel(replayChannel);\n\n        return offer(boundedReplayRequest.encodedLength());\n    }\n\n    private boolean replicate(\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long stopPosition,\n        final long channelTagId,\n        final long subscriptionTagId,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String liveDestination,\n        final String replicationChannel,\n        final long correlationId,\n        final long controlSessionId,\n        final int fileIoMaxLength,\n        final int replicationSessionId,\n        final byte[] encodedCredentials,\n        final String srcResponseChannel)\n    {\n        replicateRequest\n            .wrapAndApplyHeader(buffer, 0, messageHeader)\n            .controlSessionId(controlSessionId)\n            .correlationId(correlationId)\n            .srcRecordingId(srcRecordingId)\n            .dstRecordingId(dstRecordingId)\n            .stopPosition(stopPosition)\n            .channelTagId(channelTagId)\n            .subscriptionTagId(subscriptionTagId)\n            .srcControlStreamId(srcControlStreamId)\n            .fileIoMaxLength(fileIoMaxLength)\n            .srcControlChannel(srcControlChannel)\n            .liveDestination(liveDestination)\n            .replicationChannel(replicationChannel)\n            .replicationSessionId(replicationSessionId)\n            .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length)\n            .srcResponseChannel(srcResponseChannel);\n\n        return offer(replicateRequest.encodedLength());\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ControlEventListener.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.archive.codecs.ControlResponseCode;\n\n/**\n * Listener for responses to requests made on the archive control channel and async notification of errors which may\n * happen later.\n */\n@FunctionalInterface\npublic interface ControlEventListener\n{\n    /**\n     * An event has been received from the Archive in response to a request with a given correlation id.\n     *\n     * @param controlSessionId of the originating session.\n     * @param correlationId    of the associated request.\n     * @param relevantId       of the object to which the response applies.\n     * @param code             for the response status.\n     * @param errorMessage     when is set if the response code is not OK.\n     */\n    void onResponse(\n        long controlSessionId,\n        long correlationId,\n        long relevantId,\n        ControlResponseCode code,\n        String errorMessage);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ControlResponseAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\n/**\n * Encapsulate the polling, decoding, and dispatching of archive control protocol response messages.\n */\npublic final class ControlResponseAdapter\n{\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final ControlResponseDecoder controlResponseDecoder = new ControlResponseDecoder();\n    private final RecordingDescriptorDecoder recordingDescriptorDecoder = new RecordingDescriptorDecoder();\n    private final RecordingSignalEventDecoder recordingSignalEventDecoder = new RecordingSignalEventDecoder();\n\n    private final int fragmentLimit;\n    private final ControlResponseListener controlResponseListener;\n    private final RecordingSignalConsumer recordingSignalConsumer;\n    private final Subscription subscription;\n    private final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::onFragment);\n\n    /**\n     * Create an adapter for a given subscription to an archive for control response messages.\n     *\n     * @param controlResponseListener for dispatching responses.\n     * @param subscription            to poll for responses.\n     * @param fragmentLimit           to apply for each polling operation.\n     */\n    public ControlResponseAdapter(\n        final ControlResponseListener controlResponseListener,\n        final Subscription subscription,\n        final int fragmentLimit)\n    {\n        this(\n            controlResponseListener,\n            AeronArchive.Configuration.NO_OP_RECORDING_SIGNAL_CONSUMER,\n            subscription,\n            fragmentLimit);\n    }\n\n    /**\n     * Create an adapter for a given subscription to an archive for control response messages.\n     *\n     * @param controlResponseListener for dispatching responses.\n     * @param recordingSignalConsumer for dispatching recording signals.\n     * @param subscription            to poll for responses.\n     * @param fragmentLimit           to apply for each polling operation.\n     */\n    public ControlResponseAdapter(\n        final ControlResponseListener controlResponseListener,\n        final RecordingSignalConsumer recordingSignalConsumer,\n        final Subscription subscription,\n        final int fragmentLimit)\n    {\n        this.fragmentLimit = fragmentLimit;\n        this.controlResponseListener = controlResponseListener;\n        this.recordingSignalConsumer = recordingSignalConsumer;\n        this.subscription = subscription;\n    }\n\n    /**\n     * Poll for recording events and dispatch them to the {@link ControlResponseListener} for this instance.\n     *\n     * @return the number of fragments read during the operation. Zero if no events are available.\n     */\n    public int poll()\n    {\n        return subscription.poll(fragmentAssembler, fragmentLimit);\n    }\n\n    /**\n     * Dispatch a descriptor message to a consumer by reading the fields in the correct order.\n     *\n     * @param decoder  which wraps the encoded message ready for reading.\n     * @param consumer to which the decoded fields should be passed.\n     */\n    public static void dispatchDescriptor(\n        final RecordingDescriptorDecoder decoder, final RecordingDescriptorConsumer consumer)\n    {\n        consumer.onRecordingDescriptor(\n            decoder.controlSessionId(),\n            decoder.correlationId(),\n            decoder.recordingId(),\n            decoder.startTimestamp(),\n            decoder.stopTimestamp(),\n            decoder.startPosition(),\n            decoder.stopPosition(),\n            decoder.initialTermId(),\n            decoder.segmentFileLength(),\n            decoder.termBufferLength(),\n            decoder.mtuLength(),\n            decoder.sessionId(),\n            decoder.streamId(),\n            decoder.strippedChannel(),\n            decoder.originalChannel(),\n            decoder.sourceIdentity());\n    }\n\n    void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ArchiveException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (messageHeaderDecoder.templateId())\n        {\n            case ControlResponseDecoder.TEMPLATE_ID:\n                handleControlResponse(controlResponseListener, buffer, offset);\n                break;\n\n            case RecordingDescriptorDecoder.TEMPLATE_ID:\n                handleRecordingDescriptor(controlResponseListener, buffer, offset);\n                break;\n\n            case RecordingSignalEventDecoder.TEMPLATE_ID:\n                handleRecordingSignal(recordingSignalConsumer, buffer, offset);\n                break;\n        }\n    }\n\n    private void handleControlResponse(\n        final ControlResponseListener listener, final DirectBuffer buffer, final int offset)\n    {\n        controlResponseDecoder.wrap(\n            buffer,\n            offset + MessageHeaderEncoder.ENCODED_LENGTH,\n            messageHeaderDecoder.blockLength(),\n            messageHeaderDecoder.version());\n\n        listener.onResponse(\n            controlResponseDecoder.controlSessionId(),\n            controlResponseDecoder.correlationId(),\n            controlResponseDecoder.relevantId(),\n            controlResponseDecoder.code(),\n            controlResponseDecoder.errorMessage());\n    }\n\n    private void handleRecordingDescriptor(\n        final ControlResponseListener listener, final DirectBuffer buffer, final int offset)\n    {\n        recordingDescriptorDecoder.wrap(\n            buffer,\n            offset + MessageHeaderEncoder.ENCODED_LENGTH,\n            messageHeaderDecoder.blockLength(),\n            messageHeaderDecoder.version());\n\n        dispatchDescriptor(recordingDescriptorDecoder, listener);\n    }\n\n    private void handleRecordingSignal(\n        final RecordingSignalConsumer recordingSignalConsumer, final DirectBuffer buffer, final int offset)\n    {\n        recordingSignalEventDecoder.wrap(\n            buffer,\n            offset + MessageHeaderDecoder.ENCODED_LENGTH,\n            messageHeaderDecoder.blockLength(),\n            messageHeaderDecoder.version());\n\n        recordingSignalConsumer.onSignal(\n            recordingSignalEventDecoder.controlSessionId(),\n            recordingSignalEventDecoder.correlationId(),\n            recordingSignalEventDecoder.recordingId(),\n            recordingSignalEventDecoder.subscriptionId(),\n            recordingSignalEventDecoder.position(),\n            recordingSignalEventDecoder.signal());\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ControlResponseListener.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\n/**\n * Interface for listening to events from the archive in response to requests.\n */\npublic interface ControlResponseListener extends RecordingDescriptorConsumer, ControlEventListener\n{\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ControlResponsePoller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SemanticVersion;\n\nimport java.util.Objects;\n\n/**\n * Encapsulate the polling and decoding of archive control protocol response messages.\n */\npublic final class ControlResponsePoller\n{\n    /**\n     * Limit to apply when polling response messages.\n     */\n    public static final int FRAGMENT_LIMIT = 10;\n\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final ControlResponseDecoder controlResponseDecoder = new ControlResponseDecoder();\n    private final ChallengeDecoder challengeDecoder = new ChallengeDecoder();\n    private final RecordingSignalEventDecoder recordingSignalEventDecoder = new RecordingSignalEventDecoder();\n\n    private final Subscription subscription;\n    private final ControlledFragmentAssembler fragmentAssembler = new ControlledFragmentAssembler(this::onFragment);\n    private final int fragmentLimit;\n\n    private long controlSessionId = Aeron.NULL_VALUE;\n    private long correlationId = Aeron.NULL_VALUE;\n    private long relevantId = Aeron.NULL_VALUE;\n    private int templateId = Aeron.NULL_VALUE;\n    private int version = 0;\n    private ControlResponseCode code = null;\n    private String errorMessage = null;\n    private long recordingId = Aeron.NULL_VALUE;\n    private long subscriptionId = Aeron.NULL_VALUE;\n    private long position = Aeron.NULL_VALUE;\n    private RecordingSignal recordingSignal = null;\n    private byte[] encodedChallenge = null;\n    private boolean isPollComplete = false;\n\n    /**\n     * Create a poller for a given subscription to an archive for control response messages with a default\n     * fragment limit for polling as {@link #FRAGMENT_LIMIT}.\n     *\n     * @param subscription to poll for new events.\n     */\n    public ControlResponsePoller(final Subscription subscription)\n    {\n        this(subscription, FRAGMENT_LIMIT);\n    }\n\n    /**\n     * Create a poller for a given subscription to an archive for control response messages.\n     *\n     * @param subscription  to poll for new events.\n     * @param fragmentLimit to apply when polling.\n     */\n    public ControlResponsePoller(final Subscription subscription, final int fragmentLimit)\n    {\n        this.subscription = Objects.requireNonNull(subscription);\n        this.fragmentLimit = fragmentLimit;\n    }\n\n    /**\n     * Get the {@link Subscription} used for polling responses.\n     *\n     * @return the {@link Subscription} used for polling responses.\n     */\n    public Subscription subscription()\n    {\n        return subscription;\n    }\n\n    /**\n     * Poll for control response events.\n     *\n     * @return the number of fragments read during the operation. Zero if no events are available.\n     */\n    public int poll()\n    {\n        if (isPollComplete)\n        {\n            controlSessionId = Aeron.NULL_VALUE;\n            correlationId = Aeron.NULL_VALUE;\n            relevantId = Aeron.NULL_VALUE;\n            templateId = Aeron.NULL_VALUE;\n            version = 0;\n            code = null;\n            errorMessage = null;\n            recordingId = Aeron.NULL_VALUE;\n            subscriptionId = Aeron.NULL_VALUE;\n            position = Aeron.NULL_VALUE;\n            encodedChallenge = null;\n            recordingSignal = null;\n            isPollComplete = false;\n        }\n\n        return subscription.controlledPoll(fragmentAssembler, fragmentLimit);\n    }\n\n    /**\n     * SBE template id of polled message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return SBE template id of polled message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     */\n    public int templateId()\n    {\n        return templateId;\n    }\n\n    /**\n     * Control session id of polled message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return control session id of polled message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     */\n    public long controlSessionId()\n    {\n        return controlSessionId;\n    }\n\n    /**\n     * Correlation id of the message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return correlation id of polled message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * Get the relevant id returned with the response, e.g. replay session id.\n     *\n     * @return the relevant id returned with the response.\n     */\n    public long relevantId()\n    {\n        return relevantId;\n    }\n\n    /**\n     * Recording id of polled {@link RecordingSignal} or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return recording id of polled {@link RecordingSignal} or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     */\n    public long recordingId()\n    {\n        return recordingId;\n    }\n\n    /**\n     * Subscription id of polled {@link RecordingSignal} or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return subscription id of polled {@link RecordingSignal} or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     */\n    public long subscriptionId()\n    {\n        return subscriptionId;\n    }\n\n    /**\n     * Position of polled {@link RecordingSignal} or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return position of polled {@link RecordingSignal} or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     */\n    public long position()\n    {\n        return position;\n    }\n\n    /**\n     * Enum of polled {@link RecordingSignal} or null if poll returned nothing.\n     *\n     * @return enum of polled {@link RecordingSignal} or null if poll returned nothing.\n     */\n    public RecordingSignal recordingSignal()\n    {\n        return recordingSignal;\n    }\n\n    /**\n     * Version response from the server in semantic version form.\n     *\n     * @return response from the server in semantic version form.\n     */\n    public int version()\n    {\n        return version;\n    }\n\n    /**\n     * Has the last polling action received a complete message?\n     *\n     * @return true if the last polling action received a complete message?\n     */\n    public boolean isPollComplete()\n    {\n        return isPollComplete;\n    }\n\n    /**\n     * Get the response code of the last response.\n     *\n     * @return the response code of the last response.\n     */\n    public ControlResponseCode code()\n    {\n        return code;\n    }\n\n    /**\n     * Get the error message of the response.\n     *\n     * @return the error message of the response.\n     */\n    public String errorMessage()\n    {\n        return errorMessage;\n    }\n\n    /**\n     * Was the last polling action received a challenge message?\n     *\n     * @return true if the last polling action received was a challenge message, false if not.\n     */\n    public boolean wasChallenged()\n    {\n        return null != encodedChallenge;\n    }\n\n    /**\n     * Get the encoded challenge of the last challenge.\n     *\n     * @return the encoded challenge of the last challenge.\n     */\n    public byte[] encodedChallenge()\n    {\n        return encodedChallenge;\n    }\n\n    ControlledFragmentAssembler.Action onFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        if (isPollComplete)\n        {\n            return ControlledFragmentHandler.Action.ABORT;\n        }\n\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ArchiveException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        templateId = messageHeaderDecoder.templateId();\n        switch (templateId)\n        {\n            case ControlResponseDecoder.TEMPLATE_ID:\n            {\n                controlResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderEncoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                controlSessionId = controlResponseDecoder.controlSessionId();\n                correlationId = controlResponseDecoder.correlationId();\n                relevantId = controlResponseDecoder.relevantId();\n                code = controlResponseDecoder.code();\n                version = controlResponseDecoder.version();\n                errorMessage = controlResponseDecoder.errorMessage();\n                isPollComplete = true;\n\n                return ControlledFragmentHandler.Action.BREAK;\n            }\n\n            case ChallengeDecoder.TEMPLATE_ID:\n            {\n                challengeDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderEncoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                controlSessionId = challengeDecoder.controlSessionId();\n                correlationId = challengeDecoder.correlationId();\n                relevantId = Aeron.NULL_VALUE;\n                code = ControlResponseCode.NULL_VAL;\n                version = challengeDecoder.version();\n                errorMessage = \"\";\n\n                final int encodedChallengeLength = challengeDecoder.encodedChallengeLength();\n                encodedChallenge = new byte[encodedChallengeLength];\n                challengeDecoder.getEncodedChallenge(encodedChallenge, 0, encodedChallengeLength);\n\n                isPollComplete = true;\n\n                return ControlledFragmentHandler.Action.BREAK;\n            }\n\n            case RecordingSignalEventDecoder.TEMPLATE_ID:\n            {\n                recordingSignalEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                controlSessionId = recordingSignalEventDecoder.controlSessionId();\n                correlationId = recordingSignalEventDecoder.correlationId();\n                recordingId = recordingSignalEventDecoder.recordingId();\n                subscriptionId = recordingSignalEventDecoder.subscriptionId();\n                position = recordingSignalEventDecoder.position();\n                recordingSignal = recordingSignalEventDecoder.signal();\n\n                isPollComplete = true;\n\n                return ControlledFragmentHandler.Action.BREAK;\n            }\n        }\n\n        return ControlledFragmentHandler.Action.CONTINUE;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ControlResponsePoller{\" +\n            \"templateId=\" + templateId +\n            \", controlSessionId=\" + controlSessionId +\n            \", correlationId=\" + correlationId +\n            \", relevantId=\" + relevantId +\n            \", recordingId=\" + recordingId +\n            \", subscriptionId=\" + subscriptionId +\n            \", position=\" + position +\n            \", recordingSignal=\" + recordingSignal +\n            \", code=\" + code +\n            \", version=\" + SemanticVersion.toString(version) +\n            \", errorMessage='\" + errorMessage + '\\'' +\n            \", isPollComplete=\" + isPollComplete +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingDescriptorConsumer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Image;\nimport io.aeron.Subscription;\n\n/**\n * Consumer of events describing Aeron stream recordings.\n */\n@FunctionalInterface\npublic interface RecordingDescriptorConsumer\n{\n    /**\n     * A recording descriptor returned as a result of requesting a listing of recordings.\n     *\n     * @param controlSessionId  of the originating session requesting to list recordings.\n     * @param correlationId     of the associated request to list recordings.\n     * @param recordingId       of this recording descriptor.\n     * @param startTimestamp    of the recording.\n     * @param stopTimestamp     of the recording.\n     * @param startPosition     of the recording against the recorded publication, the {@link Image#joinPosition()}.\n     * @param stopPosition      reached for the recording, final position for {@link Image#position()}.\n     * @param initialTermId     of the recorded stream, {@link Image#initialTermId()}.\n     * @param segmentFileLength of the recording which is a multiple of termBufferLength.\n     * @param termBufferLength  of the recorded stream, {@link Image#termBufferLength()}.\n     * @param mtuLength         of the recorded stream, {@link Image#mtuLength()}.\n     * @param sessionId         of the recorded stream, this will be the most recent session id for extended recordings.\n     * @param streamId          of the recorded stream, {@link Subscription#streamId()}.\n     * @param strippedChannel   of the recorded stream which is used for the recording subscription in the archive.\n     * @param originalChannel   of the recorded stream provided to the start recording request, {@link Subscription#channel()}.\n     * @param sourceIdentity    of the recorded stream, the {@link Image#sourceIdentity()}.\n     */\n    void onRecordingDescriptor(\n        long controlSessionId,\n        long correlationId,\n        long recordingId,\n        long startTimestamp,\n        long stopTimestamp,\n        long startPosition,\n        long stopPosition,\n        int initialTermId,\n        int segmentFileLength,\n        int termBufferLength,\n        int mtuLength,\n        int sessionId,\n        int streamId,\n        String strippedChannel,\n        String originalChannel,\n        String sourceIdentity);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingDescriptorPoller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\n\n/**\n * Encapsulate the polling, decoding, dispatching of recording descriptors from an archive.\n */\npublic final class RecordingDescriptorPoller\n{\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final ControlResponseDecoder controlResponseDecoder = new ControlResponseDecoder();\n    private final RecordingDescriptorDecoder recordingDescriptorDecoder = new RecordingDescriptorDecoder();\n    private final RecordingSignalEventDecoder recordingSignalEventDecoder = new RecordingSignalEventDecoder();\n\n    private final long controlSessionId;\n    private final int fragmentLimit;\n    private final Subscription subscription;\n    private final ControlledFragmentAssembler fragmentAssembler = new ControlledFragmentAssembler(this::onFragment);\n    private final ErrorHandler errorHandler;\n    private final RecordingSignalConsumer recordingSignalConsumer;\n\n    private long correlationId;\n    private int remainingRecordCount;\n    private boolean isDispatchComplete = false;\n    private RecordingDescriptorConsumer recordingDescriptorConsumer;\n\n    /**\n     * Create a poller for a given subscription to an archive for control response messages.\n     *\n     * @param subscription     to poll for new events.\n     * @param errorHandler     to call for asynchronous errors.\n     * @param controlSessionId to filter the responses.\n     * @param fragmentLimit    to apply for each polling operation.\n     */\n    public RecordingDescriptorPoller(\n        final Subscription subscription,\n        final ErrorHandler errorHandler,\n        final long controlSessionId,\n        final int fragmentLimit)\n    {\n        this(\n            subscription,\n            errorHandler,\n            AeronArchive.Configuration.NO_OP_RECORDING_SIGNAL_CONSUMER,\n            controlSessionId,\n            fragmentLimit);\n    }\n\n    /**\n     * Create a poller for a given subscription to an archive for control response messages.\n     *\n     * @param subscription            to poll for new events.\n     * @param errorHandler            to call for asynchronous errors.\n     * @param recordingSignalConsumer for consuming interleaved recording signals on the control session.\n     * @param controlSessionId        to filter the responses.\n     * @param fragmentLimit           to apply for each polling operation.\n     */\n    public RecordingDescriptorPoller(\n        final Subscription subscription,\n        final ErrorHandler errorHandler,\n        final RecordingSignalConsumer recordingSignalConsumer,\n        final long controlSessionId,\n        final int fragmentLimit)\n    {\n        this.subscription = subscription;\n        this.errorHandler = errorHandler;\n        this.recordingSignalConsumer = recordingSignalConsumer;\n        this.fragmentLimit = fragmentLimit;\n        this.controlSessionId = controlSessionId;\n    }\n\n    /**\n     * Get the {@link Subscription} used for polling responses.\n     *\n     * @return the {@link Subscription} used for polling responses.\n     */\n    public Subscription subscription()\n    {\n        return subscription;\n    }\n\n    /**\n     * Poll for recording events.\n     *\n     * @return the number of fragments read during the operation. Zero if no events are available.\n     */\n    public int poll()\n    {\n        if (isDispatchComplete)\n        {\n            isDispatchComplete = false;\n        }\n\n        return subscription.controlledPoll(fragmentAssembler, fragmentLimit);\n    }\n\n    /**\n     * Control session id for filtering responses.\n     *\n     * @return control session id for filtering responses.\n     */\n    public long controlSessionId()\n    {\n        return controlSessionId;\n    }\n\n    /**\n     * Is the dispatch of descriptors complete?\n     *\n     * @return true if the dispatch of descriptors complete?\n     */\n    public boolean isDispatchComplete()\n    {\n        return isDispatchComplete;\n    }\n\n    /**\n     * Get the number of remaining records are expected.\n     *\n     * @return the number of remaining records are expected.\n     */\n    public int remainingRecordCount()\n    {\n        return remainingRecordCount;\n    }\n\n    /**\n     * Reset the poller to dispatch the descriptors returned from a query.\n     *\n     * @param correlationId for the response.\n     * @param recordCount   of descriptors to expect.\n     * @param consumer      to which the recording descriptors are to be dispatched.\n     */\n    public void reset(final long correlationId, final int recordCount, final RecordingDescriptorConsumer consumer)\n    {\n        this.correlationId = correlationId;\n        this.recordingDescriptorConsumer = consumer;\n        this.remainingRecordCount = recordCount;\n        isDispatchComplete = false;\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    ControlledFragmentAssembler.Action onFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        if (isDispatchComplete)\n        {\n            return ControlledFragmentAssembler.Action.ABORT;\n        }\n\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ArchiveException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        final int templateId = messageHeaderDecoder.templateId();\n        switch (templateId)\n        {\n            case ControlResponseDecoder.TEMPLATE_ID:\n                controlResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderEncoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                if (controlResponseDecoder.controlSessionId() == controlSessionId)\n                {\n                    final ControlResponseCode code = controlResponseDecoder.code();\n                    final long responseCorrelationId = controlResponseDecoder.correlationId();\n\n                    if (ControlResponseCode.RECORDING_UNKNOWN == code && responseCorrelationId == correlationId)\n                    {\n                        isDispatchComplete = true;\n                        return ControlledFragmentAssembler.Action.BREAK;\n                    }\n\n                    if (ControlResponseCode.ERROR == code)\n                    {\n                        final ArchiveException ex = new ArchiveException(\n                            \"response for correlationId=\" + correlationId +\n                            \", error: \" + controlResponseDecoder.errorMessage(),\n                            (int)controlResponseDecoder.relevantId(),\n                            responseCorrelationId);\n\n                        if (responseCorrelationId == correlationId)\n                        {\n                            throw ex;\n                        }\n                        else if (null != errorHandler)\n                        {\n                            errorHandler.onError(ex);\n                        }\n                    }\n                }\n                break;\n\n            case RecordingDescriptorDecoder.TEMPLATE_ID:\n                recordingDescriptorDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderEncoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                if (recordingDescriptorDecoder.controlSessionId() == controlSessionId &&\n                    recordingDescriptorDecoder.correlationId() == correlationId)\n                {\n                    recordingDescriptorConsumer.onRecordingDescriptor(\n                        controlSessionId,\n                        correlationId,\n                        recordingDescriptorDecoder.recordingId(),\n                        recordingDescriptorDecoder.startTimestamp(),\n                        recordingDescriptorDecoder.stopTimestamp(),\n                        recordingDescriptorDecoder.startPosition(),\n                        recordingDescriptorDecoder.stopPosition(),\n                        recordingDescriptorDecoder.initialTermId(),\n                        recordingDescriptorDecoder.segmentFileLength(),\n                        recordingDescriptorDecoder.termBufferLength(),\n                        recordingDescriptorDecoder.mtuLength(),\n                        recordingDescriptorDecoder.sessionId(),\n                        recordingDescriptorDecoder.streamId(),\n                        recordingDescriptorDecoder.strippedChannel(),\n                        recordingDescriptorDecoder.originalChannel(),\n                        recordingDescriptorDecoder.sourceIdentity());\n\n                    if (0 == --remainingRecordCount)\n                    {\n                        isDispatchComplete = true;\n                        return ControlledFragmentAssembler.Action.BREAK;\n                    }\n                }\n                break;\n\n            case RecordingSignalEventDecoder.TEMPLATE_ID:\n                recordingSignalEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                if (controlSessionId == recordingSignalEventDecoder.controlSessionId())\n                {\n                    recordingSignalConsumer.onSignal(\n                        recordingSignalEventDecoder.controlSessionId(),\n                        recordingSignalEventDecoder.correlationId(),\n                        recordingSignalEventDecoder.recordingId(),\n                        recordingSignalEventDecoder.subscriptionId(),\n                        recordingSignalEventDecoder.position(),\n                        recordingSignalEventDecoder.signal());\n                }\n                break;\n        }\n\n        return ControlledFragmentAssembler.Action.CONTINUE;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"RecordingDescriptorPoller{\" +\n            \"controlSessionId=\" + controlSessionId +\n            \", correlationId=\" + correlationId +\n            \", remainingRecordCount=\" + remainingRecordCount +\n            \", isDispatchComplete=\" + isDispatchComplete +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingEventsAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.MessageHeaderDecoder;\nimport io.aeron.archive.codecs.RecordingProgressDecoder;\nimport io.aeron.archive.codecs.RecordingStartedDecoder;\nimport io.aeron.archive.codecs.RecordingStoppedDecoder;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\n/**\n * Encapsulate the polling, decoding, and dispatching of recording events.\n */\npublic final class RecordingEventsAdapter implements FragmentHandler\n{\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final RecordingStartedDecoder recordingStartedDecoder = new RecordingStartedDecoder();\n    private final RecordingProgressDecoder recordingProgressDecoder = new RecordingProgressDecoder();\n    private final RecordingStoppedDecoder recordingStoppedDecoder = new RecordingStoppedDecoder();\n\n    private final int fragmentLimit;\n    private final RecordingEventsListener listener;\n    private final Subscription subscription;\n\n    /**\n     * Create an adapter for a given subscription to an archive for recording events.\n     *\n     * @param listener      to which events are dispatched.\n     * @param subscription  to poll for new events.\n     * @param fragmentLimit to apply for each polling operation.\n     */\n    public RecordingEventsAdapter(\n        final RecordingEventsListener listener, final Subscription subscription, final int fragmentLimit)\n    {\n        this.fragmentLimit = fragmentLimit;\n        this.listener = listener;\n        this.subscription = subscription;\n    }\n\n    /**\n     * Poll for recording events and dispatch them to the {@link RecordingEventsListener} for this instance.\n     *\n     * @return the number of fragments read during the operation. Zero if no events are available.\n     */\n    public int poll()\n    {\n        return subscription.poll(this, fragmentLimit);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ArchiveException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (messageHeaderDecoder.templateId())\n        {\n            case RecordingStartedDecoder.TEMPLATE_ID:\n                recordingStartedDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                listener.onStart(\n                    recordingStartedDecoder.recordingId(),\n                    recordingStartedDecoder.startPosition(),\n                    recordingStartedDecoder.sessionId(),\n                    recordingStartedDecoder.streamId(),\n                    recordingStartedDecoder.channel(),\n                    recordingStartedDecoder.sourceIdentity());\n                break;\n\n            case RecordingProgressDecoder.TEMPLATE_ID:\n                recordingProgressDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                listener.onProgress(\n                    recordingProgressDecoder.recordingId(),\n                    recordingProgressDecoder.startPosition(),\n                    recordingProgressDecoder.position());\n                break;\n\n            case RecordingStoppedDecoder.TEMPLATE_ID:\n                recordingStoppedDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                listener.onStop(\n                    recordingStoppedDecoder.recordingId(),\n                    recordingStoppedDecoder.startPosition(),\n                    recordingStoppedDecoder.stopPosition());\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingEventsListener.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\n/**\n * Event listener for observing the status of recordings for an Archive.\n */\npublic interface RecordingEventsListener\n{\n    /**\n     * Fired when a recording is started.\n     *\n     * @param recordingId    assigned to the new recording.\n     * @param startPosition  in the stream at which the recording started.\n     * @param sessionId      of the publication being recorded.\n     * @param streamId       of the publication being recorded.\n     * @param channel        of the publication being recorded.\n     * @param sourceIdentity of the publication being recorded.\n     */\n    void onStart(\n        long recordingId,\n        long startPosition,\n        int sessionId,\n        int streamId,\n        String channel,\n        String sourceIdentity);\n\n    /**\n     * Progress indication of an active recording.\n     *\n     * @param recordingId   for which progress is being reported.\n     * @param startPosition in the stream at which the recording started.\n     * @param position      reached in recording the publication.\n     */\n    void onProgress(long recordingId, long startPosition, long position);\n\n    /**\n     * Fired when a recording is stopped.\n     *\n     * @param recordingId   of the publication that has stopped recording.\n     * @param startPosition in the stream at which the recording started.\n     * @param stopPosition  at which the recording stopped.\n     */\n    void onStop(long recordingId, long startPosition, long stopPosition);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingEventsPoller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.MessageHeaderDecoder;\nimport io.aeron.archive.codecs.RecordingProgressDecoder;\nimport io.aeron.archive.codecs.RecordingStartedDecoder;\nimport io.aeron.archive.codecs.RecordingStoppedDecoder;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\n/**\n * Encapsulate the polling and decoding of recording events.\n */\npublic final class RecordingEventsPoller implements ControlledFragmentHandler\n{\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final RecordingStartedDecoder recordingStartedDecoder = new RecordingStartedDecoder();\n    private final RecordingProgressDecoder recordingProgressDecoder = new RecordingProgressDecoder();\n    private final RecordingStoppedDecoder recordingStoppedDecoder = new RecordingStoppedDecoder();\n\n    private final Subscription subscription;\n    private int templateId;\n    private boolean isPollComplete;\n\n    private long recordingId;\n    private long recordingStartPosition;\n    private long recordingPosition;\n    private long recordingStopPosition;\n\n    /**\n     * Create a poller for a given subscription to an archive for recording events.\n     *\n     * @param subscription to poll for new events.\n     */\n    public RecordingEventsPoller(final Subscription subscription)\n    {\n        this.subscription = subscription;\n    }\n\n    /**\n     * Poll for recording events.\n     *\n     * @return the number of fragments read during the operation. Zero if no events are available.\n     */\n    public int poll()\n    {\n        if (isPollComplete)\n        {\n            isPollComplete = false;\n            templateId = Aeron.NULL_VALUE;\n        }\n\n        return subscription.controlledPoll(this, 1);\n    }\n\n    /**\n     * Has the last polling action received a complete message?\n     *\n     * @return true of the last polling action received a complete message?\n     */\n    public boolean isPollComplete()\n    {\n        return isPollComplete;\n    }\n\n    /**\n     * Get the template id of the last received message.\n     *\n     * @return the template id of the last received message.\n     */\n    public int templateId()\n    {\n        return templateId;\n    }\n\n    /**\n     * Get the recording id of the last received event.\n     *\n     * @return the recording id of the last received event.\n     */\n    public long recordingId()\n    {\n        return recordingId;\n    }\n\n    /**\n     * Get the position the recording started at.\n     *\n     * @return the position the recording started at.\n     */\n    public long recordingStartPosition()\n    {\n        return recordingStartPosition;\n    }\n\n    /**\n     * Get the current recording position.\n     *\n     * @return the current recording position.\n     */\n    public long recordingPosition()\n    {\n        return recordingPosition;\n    }\n\n    /**\n     * Get the position the recording stopped at.\n     *\n     * @return the position the recording stopped at.\n     */\n    public long recordingStopPosition()\n    {\n        return recordingStopPosition;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public ControlledFragmentHandler.Action onFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        if (isPollComplete)\n        {\n            return ControlledFragmentHandler.Action.ABORT;\n        }\n\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ArchiveException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        templateId = messageHeaderDecoder.templateId();\n        switch (templateId)\n        {\n            case RecordingStartedDecoder.TEMPLATE_ID:\n                recordingStartedDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                recordingId = recordingStartedDecoder.recordingId();\n                recordingStartPosition = recordingStartedDecoder.startPosition();\n                recordingPosition = recordingStartPosition;\n                recordingStopPosition = Aeron.NULL_VALUE;\n                isPollComplete = true;\n                return ControlledFragmentHandler.Action.BREAK;\n\n            case RecordingProgressDecoder.TEMPLATE_ID:\n                recordingProgressDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                recordingId = recordingProgressDecoder.recordingId();\n                recordingStartPosition = recordingProgressDecoder.startPosition();\n                recordingPosition = recordingProgressDecoder.position();\n                recordingStopPosition = Aeron.NULL_VALUE;\n                isPollComplete = true;\n                return ControlledFragmentHandler.Action.BREAK;\n\n            case RecordingStoppedDecoder.TEMPLATE_ID:\n                recordingStoppedDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                recordingId = recordingStoppedDecoder.recordingId();\n                recordingStartPosition = recordingStoppedDecoder.startPosition();\n                recordingStopPosition = recordingStoppedDecoder.stopPosition();\n                recordingPosition = recordingStopPosition;\n                isPollComplete = true;\n                return ControlledFragmentHandler.Action.BREAK;\n        }\n\n        return ControlledFragmentHandler.Action.CONTINUE;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingSignalAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\nimport static io.aeron.logbuffer.ControlledFragmentHandler.Action.*;\n\n/**\n * Encapsulate the polling, decoding, and dispatching of recording transition events for a session plus the\n * asynchronous events to check for errors.\n * <p>\n * Important: set the underlying {@link RecordingSignalConsumer} instance on the {@link AeronArchive} using the\n * {@link AeronArchive.Context#recordingSignalConsumer(RecordingSignalConsumer)} method to avoid missing signals.\n *\n * @see RecordingSignal\n */\npublic final class RecordingSignalAdapter\n{\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final ControlResponseDecoder controlResponseDecoder = new ControlResponseDecoder();\n    private final RecordingSignalEventDecoder recordingSignalEventDecoder = new RecordingSignalEventDecoder();\n    private final ControlledFragmentAssembler assembler = new ControlledFragmentAssembler(this::onFragment);\n    private final ControlEventListener controlEventListener;\n    private final RecordingSignalConsumer recordingSignalConsumer;\n    private final Subscription subscription;\n    private final int fragmentLimit;\n    private final long controlSessionId;\n    private boolean isDone = false;\n\n    /**\n     * Create an adapter for a given subscription to an archive for recording events.\n     *\n     * @param controlSessionId        to listen for associated asynchronous control events, such as errors.\n     * @param controlEventListener    listener for control events which may indicate an error on the session.\n     * @param recordingSignalConsumer consumer of recording transition events.\n     * @param subscription            to poll for new events.\n     * @param fragmentLimit           to apply for each polling operation.\n     */\n    public RecordingSignalAdapter(\n        final long controlSessionId,\n        final ControlEventListener controlEventListener,\n        final RecordingSignalConsumer recordingSignalConsumer,\n        final Subscription subscription,\n        final int fragmentLimit)\n    {\n        this.controlSessionId = controlSessionId;\n        this.controlEventListener = controlEventListener;\n        this.recordingSignalConsumer = recordingSignalConsumer;\n        this.subscription = subscription;\n        this.fragmentLimit = fragmentLimit;\n    }\n\n    /**\n     * Poll for recording transitions and dispatch them to the {@link RecordingSignalConsumer} for this instance,\n     * plus check for async responses for this control session which may have an exception and dispatch to the\n     * {@link ControlResponseListener}.\n     *\n     * @return the number of fragments read during the operation. Zero if no events are available.\n     */\n    public int poll()\n    {\n        if (isDone)\n        {\n            isDone = false;\n        }\n\n        return subscription.controlledPoll(assembler, fragmentLimit);\n    }\n\n    /**\n     * Indicate that poll was successful and a signal or control response was received.\n     *\n     * @return true if a signal or control response was received.\n     */\n    public boolean isDone()\n    {\n        return isDone;\n    }\n\n    private ControlledFragmentHandler.Action onFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        if (isDone)\n        {\n            return ABORT;\n        }\n\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ArchiveException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (messageHeaderDecoder.templateId())\n        {\n            case ControlResponseDecoder.TEMPLATE_ID:\n                controlResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                if (controlResponseDecoder.controlSessionId() == controlSessionId)\n                {\n                    controlEventListener.onResponse(\n                        controlSessionId,\n                        controlResponseDecoder.correlationId(),\n                        controlResponseDecoder.relevantId(),\n                        controlResponseDecoder.code(),\n                        controlResponseDecoder.errorMessage());\n\n                    isDone = true;\n                    return BREAK;\n                }\n                break;\n\n            case RecordingSignalEventDecoder.TEMPLATE_ID:\n                recordingSignalEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                if (recordingSignalEventDecoder.controlSessionId() == controlSessionId)\n                {\n                    recordingSignalConsumer.onSignal(\n                        recordingSignalEventDecoder.controlSessionId(),\n                        recordingSignalEventDecoder.correlationId(),\n                        recordingSignalEventDecoder.recordingId(),\n                        recordingSignalEventDecoder.subscriptionId(),\n                        recordingSignalEventDecoder.position(),\n                        recordingSignalEventDecoder.signal());\n\n                    isDone = true;\n                    return BREAK;\n                }\n                break;\n        }\n\n        return CONTINUE;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingSignalConsumer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.archive.codecs.RecordingSignal;\n\n/**\n * Consumer of signals representing operations applied to a recording.\n */\n@FunctionalInterface\npublic interface RecordingSignalConsumer\n{\n    /**\n     * Signal of operation taken on a recording.\n     *\n     * @param controlSessionId that initiated the operation.\n     * @param correlationId    that initiated the operation, could be the replication id.\n     * @param recordingId      which has signalled.\n     * @param subscriptionId   of the {@link io.aeron.Subscription} associated with the recording.\n     * @param position         of the recorded stream at the point of signal.\n     * @param signal           type of the operation applied to the recording.\n     */\n    void onSignal(\n        long controlSessionId,\n        long correlationId,\n        long recordingId,\n        long subscriptionId,\n        long position,\n        RecordingSignal signal);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingSignalPoller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SemanticVersion;\n\n/**\n * Encapsulate the polling and decoding of archive control protocol response and recording signal messages.\n */\npublic final class RecordingSignalPoller\n{\n    /**\n     * Limit to apply when polling messages.\n     */\n    public static final int FRAGMENT_LIMIT = 10;\n\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final ControlResponseDecoder controlResponseDecoder = new ControlResponseDecoder();\n    private final RecordingSignalEventDecoder recordingSignalEventDecoder = new RecordingSignalEventDecoder();\n\n    private final Subscription subscription;\n    private final ControlledFragmentAssembler fragmentAssembler = new ControlledFragmentAssembler(this::onFragment);\n    private final long controlSessionId;\n    private long correlationId = Aeron.NULL_VALUE;\n    private long relevantId = Aeron.NULL_VALUE;\n    private int templateId = Aeron.NULL_VALUE;\n    private int version = 0;\n    private long recordingId = Aeron.NULL_VALUE;\n    private long recordingSubscriptionId = Aeron.NULL_VALUE;\n    private long recordingPosition = Aeron.NULL_VALUE;\n    private RecordingSignal recordingSignal = null;\n    private ControlResponseCode code;\n    private String errorMessage;\n    private final int fragmentLimit;\n    private boolean isPollComplete = false;\n\n    /**\n     * Create a poller for a given subscription to an archive for control messages.\n     *\n     * @param controlSessionId to listen for associated asynchronous control events, such as errors.\n     * @param subscription     to poll for new events.\n     * @param fragmentLimit    to apply when polling.\n     */\n    private RecordingSignalPoller(final long controlSessionId, final Subscription subscription, final int fragmentLimit)\n    {\n        this.controlSessionId = controlSessionId;\n        this.subscription = subscription;\n        this.fragmentLimit = fragmentLimit;\n    }\n\n    /**\n     * Create a poller for a given subscription to an archive for control response messages with a default\n     * fragment limit for polling as {@link #FRAGMENT_LIMIT}.\n     *\n     * @param controlSessionId to listen for associated asynchronous control events, such as errors.\n     * @param subscription to poll for new events.\n     */\n    public RecordingSignalPoller(final long controlSessionId, final Subscription subscription)\n    {\n        this(controlSessionId, subscription, FRAGMENT_LIMIT);\n    }\n\n    /**\n     * Get the {@link Subscription} used for polling messages.\n     *\n     * @return the {@link Subscription} used for polling messages.\n     */\n    public Subscription subscription()\n    {\n        return subscription;\n    }\n\n    /**\n     * Poll for control response events.\n     *\n     * @return the number of fragments read during the operation. Zero if no events are available.\n     */\n    public int poll()\n    {\n        if (isPollComplete)\n        {\n            isPollComplete = false;\n            templateId = Aeron.NULL_VALUE;\n            correlationId = Aeron.NULL_VALUE;\n            relevantId = Aeron.NULL_VALUE;\n            version = 0;\n            errorMessage = null;\n            recordingId = Aeron.NULL_VALUE;\n            recordingSubscriptionId = Aeron.NULL_VALUE;\n            recordingPosition = Aeron.NULL_VALUE;\n            recordingSignal = null;\n        }\n\n        return subscription.controlledPoll(fragmentAssembler, fragmentLimit);\n    }\n\n    /**\n     * Control session id of the last polled message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return control session id of the last polled message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     */\n    public long controlSessionId()\n    {\n        return controlSessionId;\n    }\n\n    /**\n     * Correlation id of the last polled message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return correlation id of the last polled message or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * Get the relevant id returned with the response, e.g. replay session id.\n     *\n     * @return the relevant id returned with the response.\n     */\n    public long relevantId()\n    {\n        return relevantId;\n    }\n\n    /**\n     * Get the template id of the last received message.\n     *\n     * @return the template id of the last received message.\n     */\n    public int templateId()\n    {\n        return templateId;\n    }\n\n    /**\n     * Get the recording id of the last received message.\n     *\n     * @return the recording id of the last received message.\n     */\n    public long recordingId()\n    {\n        return recordingId;\n    }\n\n    /**\n     * Get the recording subscription id of the last received message.\n     *\n     * @return the recording subscription id of the last received message.\n     */\n    public long recordingSubscriptionId()\n    {\n        return recordingSubscriptionId;\n    }\n\n    /**\n     * Get the recording position of the last received message.\n     *\n     * @return the recording position of the last received message.\n     */\n    public long recordingPosition()\n    {\n        return recordingPosition;\n    }\n\n    /**\n     * Get the recording signal of the last received message.\n     *\n     * @return the recording signal of the last received message.\n     */\n    public RecordingSignal recordingSignal()\n    {\n        return recordingSignal;\n    }\n\n    /**\n     * Version response from the server in semantic version form.\n     *\n     * @return response from the server in semantic version form.\n     */\n    public int version()\n    {\n        return version;\n    }\n\n    /**\n     * Has the last polling action received a complete message?\n     *\n     * @return true if the last polling action received a complete message?\n     */\n    public boolean isPollComplete()\n    {\n        return isPollComplete;\n    }\n\n    /**\n     * Get the response code of the last response.\n     *\n     * @return the response code of the last response.\n     */\n    public ControlResponseCode code()\n    {\n        return code;\n    }\n\n    /**\n     * Get the error message of the last response.\n     *\n     * @return the error message of the last response.\n     */\n    public String errorMessage()\n    {\n        return errorMessage;\n    }\n\n    ControlledFragmentHandler.Action onFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        if (isPollComplete)\n        {\n            return ControlledFragmentHandler.Action.ABORT;\n        }\n\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ArchiveException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        final int templateId = messageHeaderDecoder.templateId();\n\n        if (ControlResponseDecoder.TEMPLATE_ID == templateId)\n        {\n            controlResponseDecoder.wrap(\n                buffer,\n                offset + MessageHeaderEncoder.ENCODED_LENGTH,\n                messageHeaderDecoder.blockLength(),\n                messageHeaderDecoder.version());\n\n            if (controlResponseDecoder.controlSessionId() == controlSessionId)\n            {\n                this.templateId = templateId;\n                correlationId = controlResponseDecoder.correlationId();\n                relevantId = controlResponseDecoder.relevantId();\n                code = controlResponseDecoder.code();\n                version = controlResponseDecoder.version();\n                errorMessage = controlResponseDecoder.errorMessage();\n                isPollComplete = true;\n\n                return ControlledFragmentHandler.Action.BREAK;\n            }\n        }\n        else if (RecordingSignalEventDecoder.TEMPLATE_ID == templateId)\n        {\n            recordingSignalEventDecoder.wrap(\n                buffer,\n                offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                messageHeaderDecoder.blockLength(),\n                messageHeaderDecoder.version());\n\n            if (recordingSignalEventDecoder.controlSessionId() == controlSessionId)\n            {\n                this.templateId = templateId;\n                correlationId = recordingSignalEventDecoder.correlationId();\n                recordingId = recordingSignalEventDecoder.recordingId();\n                recordingSubscriptionId = recordingSignalEventDecoder.subscriptionId();\n                recordingPosition = recordingSignalEventDecoder.position();\n                recordingSignal = recordingSignalEventDecoder.signal();\n                isPollComplete = true;\n\n                return ControlledFragmentHandler.Action.BREAK;\n            }\n        }\n\n        return ControlledFragmentHandler.Action.CONTINUE;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"RecordingSignalPoller{\" +\n            \"controlSessionId=\" + controlSessionId +\n            \", correlationId=\" + correlationId +\n            \", relevantId=\" + relevantId +\n            \", code=\" + code +\n            \", templateId=\" + templateId +\n            \", version=\" + SemanticVersion.toString(version) +\n            \", errorMessage='\" + errorMessage + '\\'' +\n            \", recordingId=\" + recordingId +\n            \", recordingSubscriptionId=\" + recordingSubscriptionId +\n            \", recordingPosition=\" + recordingPosition +\n            \", recordingSignal=\" + recordingSignal +\n            \", isPollComplete=\" + isPollComplete +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingSubscriptionDescriptorConsumer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\n/**\n * Consumer for descriptors of active archive recording {@link io.aeron.Subscription}s.\n */\n@FunctionalInterface\npublic interface RecordingSubscriptionDescriptorConsumer\n{\n    /**\n     * Descriptor for an active recording subscription on the archive.\n     *\n     * @param controlSessionId for the request.\n     * @param correlationId    for the request.\n     * @param subscriptionId   that can be used to stop the recording subscription.\n     * @param streamId         the subscription was registered with.\n     * @param strippedChannel  the subscription was registered with.\n     */\n    void onSubscriptionDescriptor(\n        long controlSessionId,\n        long correlationId,\n        long subscriptionId,\n        int streamId,\n        String strippedChannel);\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/RecordingSubscriptionDescriptorPoller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\n\n/**\n * Encapsulate the polling, decoding, dispatching of recording descriptors from an archive.\n *\n * @see RecordingSubscriptionDescriptorConsumer\n * @see ArchiveProxy#listRecordingSubscriptions(int, int, String, int, boolean, long, long)\n * @see AeronArchive#listRecordingSubscriptions(int, int, String, int, boolean, RecordingSubscriptionDescriptorConsumer)\n */\npublic final class RecordingSubscriptionDescriptorPoller\n{\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final ControlResponseDecoder controlResponseDecoder = new ControlResponseDecoder();\n    private final RecordingSubscriptionDescriptorDecoder recordingSubscriptionDescriptorDecoder =\n        new RecordingSubscriptionDescriptorDecoder();\n    private final RecordingSignalEventDecoder recordingSignalEventDecoder = new RecordingSignalEventDecoder();\n\n    private final long controlSessionId;\n    private final int fragmentLimit;\n    private final Subscription subscription;\n    private final ControlledFragmentAssembler fragmentAssembler = new ControlledFragmentAssembler(this::onFragment);\n    private final ErrorHandler errorHandler;\n    private final RecordingSignalConsumer recordingSignalConsumer;\n\n    private long correlationId;\n    private int remainingSubscriptionCount;\n    private boolean isDispatchComplete = false;\n    private RecordingSubscriptionDescriptorConsumer subscriptionDescriptorConsumer;\n\n    /**\n     * Create a poller for a given subscription to an archive for control response messages.\n     *\n     * @param subscription     to poll for new events.\n     * @param errorHandler     to call for asynchronous errors.\n     * @param controlSessionId to filter the responses.\n     * @param fragmentLimit    to apply for each polling operation.\n     */\n    public RecordingSubscriptionDescriptorPoller(\n        final Subscription subscription,\n        final ErrorHandler errorHandler,\n        final long controlSessionId,\n        final int fragmentLimit)\n    {\n        this(\n            subscription,\n            errorHandler,\n            AeronArchive.Configuration.NO_OP_RECORDING_SIGNAL_CONSUMER,\n            controlSessionId,\n            fragmentLimit);\n    }\n\n    /**\n     * Create a poller for a given subscription to an archive for control response messages.\n     *\n     * @param subscription            to poll for new events.\n     * @param errorHandler            to call for asynchronous errors.\n     * @param recordingSignalConsumer for consuming interleaved recording signals on the control session.\n     * @param controlSessionId        to filter the responses.\n     * @param fragmentLimit           to apply for each polling operation.\n     */\n    public RecordingSubscriptionDescriptorPoller(\n        final Subscription subscription,\n        final ErrorHandler errorHandler,\n        final RecordingSignalConsumer recordingSignalConsumer,\n        final long controlSessionId,\n        final int fragmentLimit)\n    {\n        this.subscription = subscription;\n        this.errorHandler = errorHandler;\n        this.recordingSignalConsumer = recordingSignalConsumer;\n        this.fragmentLimit = fragmentLimit;\n        this.controlSessionId = controlSessionId;\n    }\n\n    /**\n     * Get the {@link Subscription} used for polling responses.\n     *\n     * @return the {@link Subscription} used for polling responses.\n     */\n    public Subscription subscription()\n    {\n        return subscription;\n    }\n\n    /**\n     * Poll for recording subscriptions and delegate to the {@link RecordingSubscriptionDescriptorConsumer}.\n     *\n     * @return the number of fragments read during the operation. Zero if no events are available.\n     */\n    public int poll()\n    {\n        if (isDispatchComplete)\n        {\n            isDispatchComplete = false;\n        }\n\n        return subscription.controlledPoll(fragmentAssembler, fragmentLimit);\n    }\n\n    /**\n     * Control session id for filtering responses.\n     *\n     * @return control session id for filtering responses.\n     */\n    public long controlSessionId()\n    {\n        return controlSessionId;\n    }\n\n    /**\n     * Is the dispatch of descriptors complete?\n     *\n     * @return true if the dispatch of descriptors complete?\n     */\n    public boolean isDispatchComplete()\n    {\n        return isDispatchComplete;\n    }\n\n    /**\n     * Get the number of remaining subscriptions expected.\n     *\n     * @return the number of remaining subscriptions expected.\n     */\n    public int remainingSubscriptionCount()\n    {\n        return remainingSubscriptionCount;\n    }\n\n    /**\n     * Reset the poller to dispatch the descriptors returned from a query.\n     *\n     * @param correlationId     for the response.\n     * @param subscriptionCount of descriptors to expect.\n     * @param consumer          to which the recording subscription descriptors are to be dispatched.\n     */\n    public void reset(\n        final long correlationId, final int subscriptionCount, final RecordingSubscriptionDescriptorConsumer consumer)\n    {\n        this.correlationId = correlationId;\n        this.subscriptionDescriptorConsumer = consumer;\n        this.remainingSubscriptionCount = subscriptionCount;\n        isDispatchComplete = false;\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    ControlledFragmentAssembler.Action onFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        if (isDispatchComplete)\n        {\n            return ControlledFragmentAssembler.Action.ABORT;\n        }\n\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ArchiveException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        final int templateId = messageHeaderDecoder.templateId();\n        switch (templateId)\n        {\n            case ControlResponseDecoder.TEMPLATE_ID:\n                controlResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderEncoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                if (controlResponseDecoder.controlSessionId() == controlSessionId)\n                {\n                    final ControlResponseCode code = controlResponseDecoder.code();\n                    final long responseCorrelationId = controlResponseDecoder.correlationId();\n\n                    if (ControlResponseCode.SUBSCRIPTION_UNKNOWN == code && responseCorrelationId == correlationId)\n                    {\n                        isDispatchComplete = true;\n                        return ControlledFragmentAssembler.Action.BREAK;\n                    }\n\n                    if (ControlResponseCode.ERROR == code)\n                    {\n                        final ArchiveException ex = new ArchiveException(\n                            \"response for correlationId=\" + correlationId +\n                            \", error: \" + controlResponseDecoder.errorMessage(),\n                            (int)controlResponseDecoder.relevantId(),\n                            responseCorrelationId);\n\n                        if (responseCorrelationId == correlationId)\n                        {\n                            throw ex;\n                        }\n                        else if (null != errorHandler)\n                        {\n                            errorHandler.onError(ex);\n                        }\n                    }\n                }\n                break;\n\n            case RecordingSubscriptionDescriptorDecoder.TEMPLATE_ID:\n                recordingSubscriptionDescriptorDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderEncoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                if (recordingSubscriptionDescriptorDecoder.controlSessionId() == controlSessionId &&\n                    recordingSubscriptionDescriptorDecoder.correlationId() == correlationId)\n                {\n                    subscriptionDescriptorConsumer.onSubscriptionDescriptor(\n                        controlSessionId,\n                        correlationId,\n                        recordingSubscriptionDescriptorDecoder.subscriptionId(),\n                        recordingSubscriptionDescriptorDecoder.streamId(),\n                        recordingSubscriptionDescriptorDecoder.strippedChannel());\n\n                    if (0 == --remainingSubscriptionCount)\n                    {\n                        isDispatchComplete = true;\n                        return ControlledFragmentAssembler.Action.BREAK;\n                    }\n                }\n                break;\n\n            case RecordingSignalEventDecoder.TEMPLATE_ID:\n                recordingSignalEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                if (controlSessionId == recordingSignalEventDecoder.controlSessionId())\n                {\n                    recordingSignalConsumer.onSignal(\n                        recordingSignalEventDecoder.controlSessionId(),\n                        recordingSignalEventDecoder.correlationId(),\n                        recordingSignalEventDecoder.recordingId(),\n                        recordingSignalEventDecoder.subscriptionId(),\n                        recordingSignalEventDecoder.position(),\n                        recordingSignalEventDecoder.signal());\n                }\n                break;\n        }\n\n        return ControlledFragmentAssembler.Action.CONTINUE;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ReplayMerge.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.ControlResponseCode;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport org.agrona.concurrent.EpochClock;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.CommonContext.IPC_CHANNEL;\n\n/**\n * Replay a recorded stream from a starting position and merge with live stream for a full history of a stream.\n * <p>\n * Once constructed either of {@link #poll(FragmentHandler, int)} or {@link #doWork()}, interleaved with consumption\n * of the {@link #image()}, should be called in a duty cycle loop until {@link #isMerged()} is {@code true}.\n * After which the {@link ReplayMerge} can be closed and continued usage can be made of the {@link Image} or its\n * parent {@link Subscription}. If an exception occurs or progress stops, the merge will fail and\n * {@link #hasFailed()} will be {@code true}.\n * <p>\n * If the endpoint on the replay destination uses a port of 0, then the OS will assign a port from the ephemeral\n * range and this will be added to the replay channel for instructing the archive.\n * <p>\n * NOTE: Merging is only supported with UDP streams.\n * <p>\n * NOTE: ReplayMerge is not threadsafe and should <b>not</b> be used with a shared {@link AeronArchive} client.\n */\npublic final class ReplayMerge implements AutoCloseable\n{\n    /**\n     * The maximum window at which a live destination should be added when trying to merge.\n     */\n    public static final int LIVE_ADD_MAX_WINDOW = 32 * 1024 * 1024;\n\n    private static final int REPLAY_REMOVE_THRESHOLD = 0;\n    private static final long MERGE_PROGRESS_TIMEOUT_DEFAULT_MS = TimeUnit.SECONDS.toMillis(5);\n    private static final long INITIAL_GET_MAX_RECORDED_POSITION_BACKOFF_MS = 8;\n    private static final long GET_MAX_RECORDED_POSITION_BACKOFF_MAX_MS = 500;\n    private static final long ARCHIVE_POLL_INTERVAL_MS = 100;\n\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        RESOLVE_REPLAY_PORT,\n        GET_RECORDING_POSITION,\n        REPLAY,\n        CATCHUP,\n        ATTEMPT_LIVE_JOIN,\n        MERGED,\n        FAILED,\n        CLOSED\n    }\n\n    private final long recordingId;\n    private final long startPosition;\n    private final long mergeProgressTimeoutMs;\n    private long replaySessionId = NULL_VALUE;\n    private long activeCorrelationId = NULL_VALUE;\n    private long nextTargetPosition = NULL_VALUE;\n    private long positionOfLastProgress = NULL_VALUE;\n    private long timeOfLastProgressMs;\n    private long timeOfNextGetMaxRecordedPositionMs;\n    private long getMaxRecordedPositionBackoffMs = INITIAL_GET_MAX_RECORDED_POSITION_BACKOFF_MS;\n    private long timeOfLastScheduledArchivePollMs;\n    private boolean isLiveAdded = false;\n    private boolean isReplayActive = false;\n    private State state;\n    private Image image;\n\n    private final AeronArchive archive;\n    private final Subscription subscription;\n    private final EpochClock epochClock;\n    private final String replayDestination;\n    private final String liveDestination;\n    private final ChannelUri replayChannelUri;\n\n    /**\n     * Create a {@link ReplayMerge} to manage the merging of a replayed stream and switching over to live stream as\n     * appropriate.\n     *\n     * @param subscription           to use for the replay and live stream. Must be a multi-destination subscription.\n     * @param archive                to use for the replay.\n     * @param replayChannel          to as a template for what the archive will use.\n     * @param replayDestination      to send the replay to and the destination added by the {@link Subscription}.\n     * @param liveDestination        for the live stream and the destination added by the {@link Subscription}.\n     * @param recordingId            for the replay.\n     * @param startPosition          for the replay.\n     * @param epochClock             to use for progress checks.\n     * @param mergeProgressTimeoutMs to use for progress checks.\n     */\n    public ReplayMerge(\n        final Subscription subscription,\n        final AeronArchive archive,\n        final String replayChannel,\n        final String replayDestination,\n        final String liveDestination,\n        final long recordingId,\n        final long startPosition,\n        final EpochClock epochClock,\n        final long mergeProgressTimeoutMs)\n    {\n        if (subscription.channel().startsWith(IPC_CHANNEL) ||\n            replayChannel.startsWith(IPC_CHANNEL) ||\n            replayDestination.startsWith(IPC_CHANNEL) ||\n            liveDestination.startsWith(IPC_CHANNEL))\n        {\n            throw new IllegalArgumentException(\"IPC merging is not supported\");\n        }\n\n        if (!subscription.channel().contains(\"control-mode=manual\"))\n        {\n            throw new IllegalArgumentException(\n                \"Subscription URI must have 'control-mode=manual' uri=\" + subscription.channel());\n        }\n\n        this.archive = archive;\n        this.subscription = subscription;\n        this.epochClock = epochClock;\n        this.replayDestination = replayDestination;\n        this.liveDestination = liveDestination;\n        this.recordingId = recordingId;\n        this.startPosition = startPosition;\n        this.mergeProgressTimeoutMs = mergeProgressTimeoutMs;\n\n        replayChannelUri = ChannelUri.parse(replayChannel);\n        replayChannelUri.put(CommonContext.LINGER_PARAM_NAME, \"0\");\n        replayChannelUri.put(CommonContext.EOS_PARAM_NAME, \"false\");\n\n        final String replayEndpoint = ChannelUri.parse(replayDestination).get(ENDPOINT_PARAM_NAME);\n        if (replayEndpoint.endsWith(\":0\"))\n        {\n            state = State.RESOLVE_REPLAY_PORT;\n        }\n        else\n        {\n            replayChannelUri.put(ENDPOINT_PARAM_NAME, replayEndpoint);\n            state = State.GET_RECORDING_POSITION;\n        }\n\n        subscription.asyncAddDestination(replayDestination);\n        timeOfLastProgressMs = timeOfNextGetMaxRecordedPositionMs = epochClock.time();\n    }\n\n    /**\n     * Create a {@link ReplayMerge} to manage the merging of a replayed stream and switching over to live stream as\n     * appropriate.\n     *\n     * @param subscription      to use for the replay and live stream. Must be a multi-destination subscription.\n     * @param archive           to use for the replay.\n     * @param replayChannel     to use as a template for what the archive will use.\n     * @param replayDestination to send the replay to and the destination added by the {@link Subscription}.\n     * @param liveDestination   for the live stream and the destination added by the {@link Subscription}.\n     * @param recordingId       for the replay.\n     * @param startPosition     for the replay.\n     */\n    public ReplayMerge(\n        final Subscription subscription,\n        final AeronArchive archive,\n        final String replayChannel,\n        final String replayDestination,\n        final String liveDestination,\n        final long recordingId,\n        final long startPosition)\n    {\n        this(\n            subscription,\n            archive,\n            replayChannel,\n            replayDestination,\n            liveDestination,\n            recordingId,\n            startPosition,\n            archive.context().aeron().context().epochClock(),\n            MERGE_PROGRESS_TIMEOUT_DEFAULT_MS);\n    }\n\n    /**\n     * Close and stop any active replay. Will remove the replay destination from the subscription.\n     * This operation Will NOT remove the live destination if it has been added, so it can be used for live consumption.\n     */\n    public void close()\n    {\n        final State state = this.state;\n        if (State.CLOSED != state)\n        {\n            if (!archive.context().aeron().isClosed())\n            {\n                if (State.MERGED != state)\n                {\n                    subscription.asyncRemoveDestination(replayDestination);\n                }\n\n                if (isReplayActive && archive.archiveProxy().publication().isConnected())\n                {\n                    stopReplay();\n                }\n            }\n\n            state(State.CLOSED);\n        }\n    }\n\n    /**\n     * Get the {@link Subscription} used to consume the replayed and merged stream.\n     *\n     * @return the {@link Subscription} used to consume the replayed and merged stream.\n     */\n    public Subscription subscription()\n    {\n        return subscription;\n    }\n\n    /**\n     * Perform the work of replaying and merging. Should only be used if polling the underlying {@link Image} directly,\n     * call {@link #poll(FragmentHandler, int)} on this class.\n     *\n     * @return indication of work done processing the merge.\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n        final long nowMs = epochClock.time();\n\n        try\n        {\n            switch (state)\n            {\n                case RESOLVE_REPLAY_PORT:\n                    workCount += resolveReplayPort(nowMs);\n                    checkProgress(nowMs);\n                    break;\n\n                case GET_RECORDING_POSITION:\n                    workCount += getRecordingPosition(nowMs);\n                    checkProgress(nowMs);\n                    break;\n\n                case REPLAY:\n                    workCount += replay(nowMs);\n                    checkProgress(nowMs);\n                    break;\n\n                case CATCHUP:\n                    workCount += catchup(nowMs);\n                    checkProgress(nowMs);\n                    break;\n\n                case ATTEMPT_LIVE_JOIN:\n                    workCount += attemptLiveJoin(nowMs);\n                    checkProgress(nowMs);\n                    break;\n\n                case MERGED:\n                case CLOSED:\n                case FAILED:\n                    break;\n            }\n        }\n        catch (final Exception ex)\n        {\n            state(State.FAILED);\n            throw ex;\n        }\n\n        return workCount;\n    }\n\n    /**\n     * Poll the {@link Image} used for replay and merging and live stream. The {@link ReplayMerge#doWork()} method\n     * will be called before the poll so that processing of the merge can be done.\n     *\n     * @param fragmentHandler to call for fragments.\n     * @param fragmentLimit   for poll call.\n     * @return number of fragments processed.\n     */\n    public int poll(final FragmentHandler fragmentHandler, final int fragmentLimit)\n    {\n        doWork();\n        return null == image ? 0 : image.poll(fragmentHandler, fragmentLimit);\n    }\n\n    /**\n     * Is the live stream merged and the replay stopped?\n     *\n     * @return true if live stream is merged and the replay stopped or false if not.\n     */\n    public boolean isMerged()\n    {\n        return state == State.MERGED;\n    }\n\n    /**\n     * Has the replay merge failed due to an error?\n     *\n     * @return true if replay merge has failed due to an error.\n     */\n    public boolean hasFailed()\n    {\n        return state == State.FAILED;\n    }\n\n    /**\n     * The {@link Image} which is a merge of the replay and live stream.\n     *\n     * @return the {@link Image} which is a merge of the replay and live stream.\n     */\n    public Image image()\n    {\n        return image;\n    }\n\n    /**\n     * Is the live destination added to the {@link #subscription()}?\n     *\n     * @return true if live destination added or false if not.\n     */\n    public boolean isLiveAdded()\n    {\n        return isLiveAdded;\n    }\n\n    private int resolveReplayPort(final long nowMs)\n    {\n        int workCount = 0;\n\n        final String resolvedEndpoint = subscription.resolvedEndpoint();\n        if (null != resolvedEndpoint)\n        {\n            replayChannelUri.replaceEndpointWildcardPort(resolvedEndpoint);\n\n            timeOfLastProgressMs = nowMs;\n            state(State.GET_RECORDING_POSITION);\n            workCount += 1;\n        }\n\n        return workCount;\n    }\n\n    private int getRecordingPosition(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            if (callGetMaxRecordedPosition(nowMs))\n            {\n                timeOfLastProgressMs = nowMs;\n                workCount += 1;\n            }\n        }\n        else if (pollForResponse(archive, activeCorrelationId))\n        {\n            nextTargetPosition = polledRelevantId(archive);\n            activeCorrelationId = NULL_VALUE;\n\n            if (AeronArchive.NULL_POSITION != nextTargetPosition)\n            {\n                timeOfLastProgressMs = nowMs;\n                state(State.REPLAY);\n            }\n\n            workCount += 1;\n        }\n\n        return workCount;\n    }\n\n    private int replay(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            final long correlationId = archive.context().aeron().nextCorrelationId();\n\n            if (archive.archiveProxy().replay(\n                recordingId,\n                startPosition,\n                Long.MAX_VALUE,\n                replayChannelUri.toString(),\n                subscription.streamId(),\n                correlationId,\n                archive.controlSessionId()))\n            {\n                activeCorrelationId = correlationId;\n                timeOfLastProgressMs = nowMs;\n                workCount += 1;\n            }\n        }\n        else if (pollForResponse(archive, activeCorrelationId))\n        {\n            isReplayActive = true;\n            replaySessionId = polledRelevantId(archive);\n            timeOfLastProgressMs = nowMs;\n            activeCorrelationId = NULL_VALUE;\n\n            // reset getRecordingPosition backoff when moving to CATCHUP state\n            getMaxRecordedPositionBackoffMs = INITIAL_GET_MAX_RECORDED_POSITION_BACKOFF_MS;\n            timeOfNextGetMaxRecordedPositionMs = nowMs;\n\n            state(State.CATCHUP);\n            workCount += 1;\n        }\n\n        return workCount;\n    }\n\n    private int catchup(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (null == image && subscription.isConnected())\n        {\n            timeOfLastProgressMs = nowMs;\n            final Image image = subscription.imageBySessionId((int)replaySessionId);\n            if (null == this.image && null != image)\n            {\n                this.image = image;\n                positionOfLastProgress = image.position();\n            }\n            else\n            {\n                positionOfLastProgress = NULL_VALUE;\n            }\n        }\n\n        if (null != image)\n        {\n            final long position = image.position();\n            if (position >= nextTargetPosition)\n            {\n                timeOfLastProgressMs = nowMs;\n                positionOfLastProgress = position;\n                state(State.ATTEMPT_LIVE_JOIN);\n                workCount += 1;\n            }\n            else if (position > positionOfLastProgress)\n            {\n                timeOfLastProgressMs = nowMs;\n                positionOfLastProgress = position;\n            }\n            else if (image.isClosed())\n            {\n                throw new IllegalStateException(\"ReplayMerge Image closed unexpectedly.\");\n            }\n        }\n\n        return workCount;\n    }\n\n    private int attemptLiveJoin(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            if (callGetMaxRecordedPosition(nowMs))\n            {\n                timeOfLastProgressMs = nowMs;\n                workCount += 1;\n            }\n        }\n        else if (pollForResponse(archive, activeCorrelationId))\n        {\n            nextTargetPosition = polledRelevantId(archive);\n            activeCorrelationId = NULL_VALUE;\n\n            if (AeronArchive.NULL_POSITION != nextTargetPosition)\n            {\n                State nextState = State.CATCHUP;\n\n                if (null != image)\n                {\n                    final long position = image.position();\n                    if (shouldAddLiveDestination(position))\n                    {\n                        subscription.asyncAddDestination(liveDestination);\n                        timeOfLastProgressMs = nowMs;\n                        positionOfLastProgress = position;\n                        isLiveAdded = true;\n                    }\n                    else if (shouldStopAndRemoveReplay(position))\n                    {\n                        subscription.asyncRemoveDestination(replayDestination);\n                        stopReplay();\n                        timeOfLastProgressMs = nowMs;\n                        positionOfLastProgress = position;\n                        nextState = State.MERGED;\n                    }\n                }\n\n                state(nextState);\n            }\n\n            workCount += 1;\n        }\n\n        return workCount;\n    }\n\n    private boolean callGetMaxRecordedPosition(final long nowMs)\n    {\n        if (nowMs < timeOfNextGetMaxRecordedPositionMs)\n        {\n            return false;\n        }\n\n        final long correlationId = archive.context().aeron().nextCorrelationId();\n\n        final boolean result = archive.archiveProxy().getMaxRecordedPosition(\n            recordingId, correlationId, archive.controlSessionId());\n\n        if (result)\n        {\n            activeCorrelationId = correlationId;\n        }\n\n        // increase backoff regardless of result\n        getMaxRecordedPositionBackoffMs = Long.min(\n            getMaxRecordedPositionBackoffMs * 2, GET_MAX_RECORDED_POSITION_BACKOFF_MAX_MS);\n        timeOfNextGetMaxRecordedPositionMs = nowMs + getMaxRecordedPositionBackoffMs;\n\n        return result;\n    }\n\n    private void stopReplay()\n    {\n        final long correlationId = archive.context().aeron().nextCorrelationId();\n        if (archive.archiveProxy().stopReplay(replaySessionId, correlationId, archive.controlSessionId()))\n        {\n            isReplayActive = false;\n        }\n    }\n\n    private void state(final ReplayMerge.State newState)\n    {\n        //System.out.println(state + \" -> \" + newState);\n        state = newState;\n        activeCorrelationId = NULL_VALUE;\n    }\n\n    private boolean shouldAddLiveDestination(final long position)\n    {\n        return !isLiveAdded &&\n            (nextTargetPosition - position) <= Math.min(image.termBufferLength() >> 2, LIVE_ADD_MAX_WINDOW);\n    }\n\n    private boolean shouldStopAndRemoveReplay(final long position)\n    {\n        return isLiveAdded &&\n            (nextTargetPosition - position) <= REPLAY_REMOVE_THRESHOLD &&\n            image.activeTransportCount() >= 2;\n    }\n\n    private void checkProgress(final long nowMs)\n    {\n        if (nowMs > (timeOfLastProgressMs + mergeProgressTimeoutMs))\n        {\n            final int transportCount = null != image ? image.activeTransportCount() : 0;\n            throw new TimeoutException(\n                \"ReplayMerge no progress: state=\" + state + \", activeTransportCount=\" + transportCount);\n        }\n\n        if (NULL_VALUE == activeCorrelationId &&\n            (nowMs > (timeOfLastScheduledArchivePollMs + ARCHIVE_POLL_INTERVAL_MS)))\n        {\n            timeOfLastScheduledArchivePollMs = nowMs;\n            pollForResponse(archive, NULL_VALUE);\n        }\n    }\n\n    private static boolean pollForResponse(final AeronArchive archive, final long correlationId)\n    {\n        final ControlResponsePoller poller = archive.controlResponsePoller();\n\n        final int pollCount = poller.poll();\n        if (poller.isPollComplete())\n        {\n            if (poller.controlSessionId() == archive.controlSessionId())\n            {\n                if (poller.code() == ControlResponseCode.ERROR)\n                {\n                    throw new ArchiveException(\"archive response for correlationId=\" + poller.correlationId() +\n                        \", error: \" + poller.errorMessage(),\n                        (int)poller.relevantId(),\n                        poller.correlationId());\n                }\n\n                return poller.correlationId() == correlationId;\n            }\n        }\n        else if (pollCount == 0 && !poller.subscription().isConnected())\n        {\n            throw new ArchiveException(\"archive is not connected\");\n        }\n\n        return false;\n    }\n\n    private static long polledRelevantId(final AeronArchive archive)\n    {\n        return archive.controlResponsePoller().relevantId();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ReplayMerge{\" +\n            \"state=\" + state +\n            \", nextTargetPosition=\" + nextTargetPosition +\n            \", timeOfLastProgressMs=\" + timeOfLastProgressMs +\n            \", positionOfLastProgress=\" + positionOfLastProgress +\n            \", isLiveAdded=\" + isLiveAdded +\n            \", isReplayActive=\" + isReplayActive +\n            \", replayChannelUri=\" + replayChannelUri +\n            \", image=\" + image +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ReplayParams.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Aeron;\n\n/**\n * Fluent API for setting optional replay parameters. Allows the user to configure starting position,\n * replay length, bounding counter (for a bounded replay) and the max length for file I/O operations.\n * <p>\n * Not threadsafe.\n */\npublic class ReplayParams\n{\n    private int boundingLimitCounterId;\n    private int fileIoMaxLength;\n    private long position;\n    private long length;\n    private long replayToken;\n    private long subscriptionRegistrationId;\n\n    /**\n     * Default, initialise all values to \"null\".\n     */\n    @SuppressWarnings(\"this-escape\")\n    public ReplayParams()\n    {\n        reset();\n    }\n\n    /**\n     * Reset all value to \"null\", allows for an instance to be reused.\n     *\n     * @return this for a fluent API\n     */\n    public ReplayParams reset()\n    {\n        boundingLimitCounterId = Aeron.NULL_VALUE;\n        fileIoMaxLength = Aeron.NULL_VALUE;\n        position = AeronArchive.NULL_POSITION;\n        length = AeronArchive.REPLAY_ALL_AND_FOLLOW;\n        replayToken = Aeron.NULL_VALUE;\n        subscriptionRegistrationId = Aeron.NULL_VALUE;\n        return this;\n    }\n\n    /**\n     * Set the position to start the replay. If set to {@link AeronArchive#NULL_POSITION} (which is the default) then\n     * the stream will be replayed from the start.\n     *\n     * @param position to start the replay from.\n     * @return this for a fluent API.\n     */\n    public ReplayParams position(final long position)\n    {\n        this.position = position;\n        return this;\n    }\n\n    /**\n     * Position to start the replay at.\n     *\n     * @return position for the start of the replay.\n     * @see ReplayParams#position(long)\n     */\n    public long position()\n    {\n        return position;\n    }\n\n    /**\n     * The length of the recorded stream to replay. If set to {@link AeronArchive#REPLAY_ALL_AND_FOLLOW} (the default)\n     * it will replay a whole stream of unknown length and then continue to follow the replay if it is live. If set to\n     * {@link AeronArchive#REPLAY_ALL_AND_STOP} it will replay up the limit calculated when the replay request is\n     * received then stop the replay, thereby ending the stream.\n     * <p>\n     * {@link AeronArchive#REPLAY_ALL_AND_FOLLOW} retains the same behaviour as using {@link AeronArchive#NULL_LENGTH}\n     * or {@link Long#MAX_VALUE}.\n     *\n     * @param length of the recording to be replayed.\n     * @return this for a fluent API.\n     */\n    public ReplayParams length(final long length)\n    {\n        this.length = length;\n        return this;\n    }\n\n    /**\n     * Length of the recording to replay.\n     *\n     * @return length of the recording to replay.\n     * @see ReplayParams#length(long)\n     */\n    public long length()\n    {\n        return length;\n    }\n\n    /**\n     * Sets the counter id to be used for bounding the replay. Setting this value will trigger the sending of a\n     * bounded replay request instead of a normal replay.\n     *\n     * @param boundingLimitCounterId counter to use to bound the replay\n     * @return this for a fluent API\n     */\n    public ReplayParams boundingLimitCounterId(final int boundingLimitCounterId)\n    {\n        this.boundingLimitCounterId = boundingLimitCounterId;\n        return this;\n    }\n\n    /**\n     * Gets the counterId specified for the bounding the replay. Returns {@link Aeron#NULL_VALUE} if unspecified.\n     *\n     * @return the counter id to bound the replay.\n     */\n    public int boundingLimitCounterId()\n    {\n        return this.boundingLimitCounterId;\n    }\n\n    /**\n     * The maximum size of a file operation when reading from the archive to execute the replay. Will use the value\n     * defined in the context otherwise. This can be used reduce the size of file IO operations to lower the\n     * priority of some replays. Setting it to a value larger than the context value will have no affect.\n     *\n     * @param fileIoMaxLength maximum length of a replay file operation\n     * @return this for a fluent API\n     */\n    public ReplayParams fileIoMaxLength(final int fileIoMaxLength)\n    {\n        this.fileIoMaxLength = fileIoMaxLength;\n        return this;\n    }\n\n    /**\n     * Gets the maximum length for file IO operations in the replay. Defaults to {@link Aeron#NULL_VALUE} if not\n     * set, which will trigger the use of the Archive.Context default.\n     *\n     * @return maximum file length for IO operations during replay.\n     */\n    public int fileIoMaxLength()\n    {\n        return this.fileIoMaxLength;\n    }\n\n    /**\n     * Determines if the parameter setup has requested a bounded replay.\n     *\n     * @return true if the replay should be bounded, false otherwise.\n     */\n    public boolean isBounded()\n    {\n        return Aeron.NULL_VALUE != boundingLimitCounterId;\n    }\n\n    /**\n     * Set a token used for replays when the initiating image is not the one used to create the archive\n     * connection/session.\n     *\n     * @param replayToken token to identify the replay\n     * @return this for a fluent API.\n     */\n    public ReplayParams replayToken(final long replayToken)\n    {\n        this.replayToken = replayToken;\n        return this;\n    }\n\n    /**\n     * Get a token used for replays when the initiating image is not the one used to create the archive\n     * connection/session.\n     *\n     * @return the replay token\n     */\n    public long replayToken()\n    {\n        return replayToken;\n    }\n\n    /**\n     * Set the subscription registration id to be used when doing a start replay using response channels and the\n     * response subscription is already created.\n     *\n     * @param registrationId of the subscription to receive the replay (should be set up with control-mode=response).\n     */\n    public void subscriptionRegistrationId(final long registrationId)\n    {\n        this.subscriptionRegistrationId = registrationId;\n    }\n\n    /**\n     * Get the subscription registration id to be used when doing a start replay using response channels and the\n     * response subscription is already created.\n     *\n     * @return registrationId of the subscription to receive the replay (should be set up with control-mode=response).\n     */\n    public long subscriptionRegistrationId()\n    {\n        return subscriptionRegistrationId;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/ReplicationParams.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.security.NullCredentialsSupplier;\n\nimport java.util.Objects;\n\n/**\n * Contains the optional parameters that can be passed to a Replication Request. Controls the behaviour of the\n * replication including tagging, stop position, extending destination recordings, live merging, and setting the\n * maximum length of the file I/O operations.\n */\npublic class ReplicationParams\n{\n    private long stopPosition;\n    private long dstRecordingId;\n    private String liveDestination;\n    private String replicationChannel;\n    private long channelTagId;\n    private long subscriptionTagId;\n    private int fileIoMaxLength;\n    private int replicationSessionId;\n    private byte[] encodedCredentials;\n    private String srcResponseChannel;\n\n    /**\n     * Initialise all parameters to defaults.\n     */\n    @SuppressWarnings(\"this-escape\")\n    public ReplicationParams()\n    {\n        reset();\n    }\n\n    /**\n     * Reset the state of the parameters to the default for reuse.\n     *\n     * @return this for a fluent API.\n     */\n    public ReplicationParams reset()\n    {\n        stopPosition = AeronArchive.NULL_POSITION;\n        dstRecordingId = Aeron.NULL_VALUE;\n        liveDestination = null;\n        replicationChannel = null;\n        channelTagId = Aeron.NULL_VALUE;\n        subscriptionTagId = Aeron.NULL_VALUE;\n        fileIoMaxLength = Aeron.NULL_VALUE;\n        replicationSessionId = Aeron.NULL_VALUE;\n        encodedCredentials = NullCredentialsSupplier.NULL_CREDENTIAL;\n        srcResponseChannel = null;\n\n        return this;\n    }\n\n    /**\n     * Set the stop position for replication, default is {@link AeronArchive#NULL_POSITION}, which will continuously\n     * replicate.\n     *\n     * @param stopPosition position to stop the replication at.\n     * @return this for a fluent API\n     */\n    public ReplicationParams stopPosition(final long stopPosition)\n    {\n        this.stopPosition = stopPosition;\n        return this;\n    }\n\n    /**\n     * The stop position for this replication request.\n     * @return stop position\n     */\n    public long stopPosition()\n    {\n        return stopPosition;\n    }\n\n    /**\n     * The recording in the local archive to extend. Default is {@link Aeron#NULL_VALUE} which will trigger the creation\n     * of a new recording in the destination archive.\n     *\n     * @param dstRecordingId destination recording to extend.\n     * @return this for a fluent API.\n     */\n    public ReplicationParams dstRecordingId(final long dstRecordingId)\n    {\n        this.dstRecordingId = dstRecordingId;\n        return this;\n    }\n\n    /**\n     * Destination recording id to extend.\n     *\n     * @return destination recording id.\n     */\n    public long dstRecordingId()\n    {\n        return dstRecordingId;\n    }\n\n    /**\n     * Destination for the live stream if merge is required. Default is null for no merge.\n     *\n     * @param liveChannel for the live stream merge\n     * @return this for a fluent API.\n     */\n    public ReplicationParams liveDestination(final String liveChannel)\n    {\n        this.liveDestination = liveChannel;\n        return this;\n    }\n\n    /**\n     * Gets the destination for the live stream merge.\n     *\n     * @return destination for live stream merge.\n     */\n    public String liveDestination()\n    {\n        return liveDestination;\n    }\n\n    /**\n     * Channel to use for replicating the recording, empty string will mean that the default channel is used.\n     * @return channel to replicate the recording.\n     */\n    public String replicationChannel()\n    {\n        return replicationChannel;\n    }\n\n    /**\n     * Channel use to replicate the recording. Default is null which will use the context's default replication\n     * channel\n     *\n     * @param replicationChannel to use for replicating the recording.\n     * @return this for a fluent API.\n     */\n    public ReplicationParams replicationChannel(final String replicationChannel)\n    {\n        this.replicationChannel = replicationChannel;\n        return this;\n    }\n\n    /**\n     * The channel used by the archive's subscription for replication will have the supplied channel tag applied to it.\n     * The default value for channelTagId is {@link Aeron#NULL_VALUE}\n     *\n     * @param channelTagId tag to apply to the archive's subscription.\n     * @return this for a fluent API\n     */\n    public ReplicationParams channelTagId(final long channelTagId)\n    {\n        this.channelTagId = channelTagId;\n        return this;\n    }\n\n    /**\n     * Gets channel tag id for the archive subscription.\n     *\n     * @return channel tag id.\n     */\n    public long channelTagId()\n    {\n        return channelTagId;\n    }\n\n    /**\n     * The channel used by the archive's subscription for replication will have the supplied subscription tag applied to\n     * it. The default value for subscriptionTagId is {@link Aeron#NULL_VALUE}\n     *\n     * @param subscriptionTagId tag to apply to the archive's subscription.\n     * @return this for a fluent API\n     */\n    public ReplicationParams subscriptionTagId(final long subscriptionTagId)\n    {\n        this.subscriptionTagId = subscriptionTagId;\n        return this;\n    }\n\n    /**\n     * Gets subscription tag id for the archive subscription.\n     *\n     * @return subscription tag id.\n     */\n    public long subscriptionTagId()\n    {\n        return subscriptionTagId;\n    }\n\n    /**\n     * The maximum size of a file operation when reading from the archive to execute the replication. Will use the value\n     * defined in the context otherwise. This can be used reduce the size of file IO operations to lower the\n     * priority of some replays. Setting it to a value larger than the context value will have no affect.\n     *\n     * @param fileIoMaxLength maximum length of a file I/O operation.\n     * @return this for a fluent API\n     */\n    public ReplicationParams fileIoMaxLength(final int fileIoMaxLength)\n    {\n        this.fileIoMaxLength = fileIoMaxLength;\n        return this;\n    }\n\n    /**\n     * Gets the maximum length for file IO operations in the replay. Defaults to {@link Aeron#NULL_VALUE} if not\n     * set, which will trigger the use of the Archive.Context default.\n     *\n     * @return maximum length of a file I/O operation.\n     */\n    public int fileIoMaxLength()\n    {\n        return this.fileIoMaxLength;\n    }\n\n    /**\n     * Sets the session-id to be used for the replicated file instead of the session id from the source archive. This\n     * is useful in cases where we are replicating the same recording in multiple stages.\n     *\n     * @param replicationSessionId the session-id to be set for the received recording.\n     * @return this for fluent API\n     */\n    public ReplicationParams replicationSessionId(final int replicationSessionId)\n    {\n        this.replicationSessionId = replicationSessionId;\n        return this;\n    }\n\n    /**\n     * The session-id to be used for the replicated recording.\n     *\n     * @return session-id to be useful for the replicated recording.\n     */\n    public int replicationSessionId()\n    {\n        return this.replicationSessionId;\n    }\n\n    /**\n     * Sets the encoded credentials that will be passed to the source archive for authentication. Currently only simple\n     * authentication (i.e. not challenge/response) is supported for replication.\n     *\n     * @param encodedCredentials credentials to be passed to the source archive.\n     * @return this for a fluent API.\n     */\n    public ReplicationParams encodedCredentials(final byte[] encodedCredentials)\n    {\n        this.encodedCredentials = encodedCredentials;\n        return this;\n    }\n\n    /**\n     * Gets the encoded credentials that will be used to authenticate against the source archive.\n     *\n     * @return encoded credentials used for authentication.\n     */\n    public byte[] encodedCredentials()\n    {\n        return encodedCredentials;\n    }\n\n    /**\n     * Control address of the source archive to use response channels during replication.\n     *\n     * @param responseChannel control address of the response publication for the source archive.\n     * @return this for a fluent API.\n     */\n    public ReplicationParams srcResponseChannel(final String responseChannel)\n    {\n        this.srcResponseChannel = responseChannel;\n        return this;\n    }\n\n    /**\n     * Control address of the source archive to use response channels during replication.\n     *\n     * @return control address of the response publication for the source archive.\n     */\n    public String srcResponseChannel()\n    {\n        return srcResponseChannel;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean equals(final Object o)\n    {\n        if (this == o)\n        {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass())\n        {\n            return false;\n        }\n        final ReplicationParams that = (ReplicationParams)o;\n        return stopPosition == that.stopPosition && dstRecordingId == that.dstRecordingId &&\n            channelTagId == that.channelTagId && subscriptionTagId == that.subscriptionTagId &&\n            fileIoMaxLength == that.fileIoMaxLength && replicationSessionId == that.replicationSessionId &&\n            Objects.equals(liveDestination, that.liveDestination) &&\n            Objects.equals(replicationChannel, that.replicationChannel);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int hashCode()\n    {\n        return Objects.hash(\n            stopPosition, dstRecordingId, liveDestination, replicationChannel, channelTagId, subscriptionTagId,\n            fileIoMaxLength, replicationSessionId);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ReplicationParams{\" +\n            \"stopPosition=\" + stopPosition +\n            \", dstRecordingId=\" + dstRecordingId +\n            \", liveDestination='\" + liveDestination + '\\'' +\n            \", replicationChannel='\" + replicationChannel + '\\'' +\n            \", channelTagId=\" + channelTagId +\n            \", subscriptionTagId=\" + subscriptionTagId +\n            \", fileIoMaxLength=\" + fileIoMaxLength +\n            \", replicationSessionId=\" + replicationSessionId +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/client/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * {@link io.aeron.archive.client.AeronArchive} client for communicating with a local or remote Archive.\n */\npackage io.aeron.archive.client;"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The aeron-archive is a module which enables Aeron data stream recording and replay from persistent storage.\n * <p>\n * Features:\n * <ul>\n *     <li><b>Record:</b> service can record a particular subscription, described by {@code <channel, streamId>}. Each\n *     resulting image for the subscription will be recorded under a new {@code recordingId}. Local network publications\n *     are recorded using the spy feature for efficiency.</li>\n *     <li><b>Extend:</b> an existing recording by appending.</li>\n *     <li><b>Replay:</b> service can replay a recorded {@code recordingId} from a particular position and length.</li>\n *     <li><b>Query:</b> the catalog for existing recordings and the recorded position of an active recording.</li>\n *     <li><b>Truncate:</b> allows a stopped recording to have its length truncated, and if truncated to the start\n *     position then it is effectively deleted.</li>\n *     <li><b>Replicate:</b> allows the replication of a stream from a source to destination archive with the\n *     option to continue after catching up and merging with a live multicast stream.</li>\n *     <li><b>Replay Merge:</b> allows a late joining subscriber of a recorded stream to replay a recording and then\n *     merge with the live multicast stream for cut over if the consumer is fast enough to keep up.</li>\n * </ul>\n */\npackage io.aeron.archive;"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/status/RecordingPos.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.status;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.Counter;\nimport io.aeron.Image;\nimport io.aeron.archive.ArchiveCounters;\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.concurrent.status.CountersReader.*;\n\n/**\n * The position a recording has reached when being archived.\n * <p>\n * Key has the following layout:\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                        Recording ID                           |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                         Session ID                            |\n *  +---------------------------------------------------------------+\n *  |                Source Identity for the Image                  |\n *  |                                                              ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                         Archive ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic final class RecordingPos\n{\n    /**\n     * Type id of a recording position counter.\n     */\n    public static final int RECORDING_POSITION_TYPE_ID = AeronCounters.ARCHIVE_RECORDING_POSITION_TYPE_ID;\n\n    /**\n     * Represents a null recording id when not found.\n     */\n    public static final long NULL_RECORDING_ID = Aeron.NULL_VALUE;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"rec-pos\";\n\n    private RecordingPos()\n    {\n    }\n\n    static final int RECORDING_ID_OFFSET = 0;\n    static final int SESSION_ID_OFFSET = RECORDING_ID_OFFSET + SIZE_OF_LONG;\n    static final int SOURCE_IDENTITY_LENGTH_OFFSET = SESSION_ID_OFFSET + SIZE_OF_INT;\n    static final int SOURCE_IDENTITY_OFFSET = SOURCE_IDENTITY_LENGTH_OFFSET + SIZE_OF_INT;\n\n    /**\n     * Allocated a recording position counter and populate the metadata.\n     *\n     * @param aeron           on which the counter will be registered.\n     * @param tempBuffer      for encoding the metadata.\n     * @param archiveId       to which the counter belongs.\n     * @param recordingId     for the recording.\n     * @param sessionId       for the publication being recorded.\n     * @param streamId        for the publication being recorded.\n     * @param strippedChannel for the recording subscription.\n     * @param sourceIdentity  for the publication.\n     * @return the allocated counter.\n     */\n    public static Counter allocate(\n        final Aeron aeron,\n        final UnsafeBuffer tempBuffer,\n        final long archiveId,\n        final long recordingId,\n        final int sessionId,\n        final int streamId,\n        final String strippedChannel,\n        final String sourceIdentity)\n    {\n        tempBuffer.putLong(RECORDING_ID_OFFSET, recordingId);\n        tempBuffer.putInt(SESSION_ID_OFFSET, sessionId);\n\n        final int sourceIdentityLength = Math.min(\n            sourceIdentity.length(), MAX_KEY_LENGTH - SOURCE_IDENTITY_OFFSET - SIZE_OF_LONG);\n        tempBuffer.putInt(SOURCE_IDENTITY_LENGTH_OFFSET, sourceIdentityLength);\n        tempBuffer.putStringWithoutLengthAscii(SOURCE_IDENTITY_OFFSET, sourceIdentity, 0, sourceIdentityLength);\n        final int archiveIdOffset = SOURCE_IDENTITY_OFFSET + sourceIdentityLength;\n        tempBuffer.putLong(archiveIdOffset, archiveId);\n        final int keyLength = archiveIdOffset + SIZE_OF_LONG;\n\n        final int labelOffset = BitUtil.align(keyLength, SIZE_OF_INT);\n        int labelLength = 0;\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset, NAME + \": \");\n        labelLength += tempBuffer.putLongAscii(labelOffset + labelLength, recordingId);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, \" \");\n        labelLength += tempBuffer.putIntAscii(labelOffset + labelLength, sessionId);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, \" \");\n        labelLength += tempBuffer.putIntAscii(labelOffset + labelLength, streamId);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, \" \");\n        labelLength += tempBuffer.putStringWithoutLengthAscii(\n            labelOffset + labelLength,\n            strippedChannel,\n            0,\n            MAX_LABEL_LENGTH - labelLength - ArchiveCounters.lengthOfArchiveIdLabel(archiveId));\n        labelLength += ArchiveCounters.appendArchiveIdLabel(tempBuffer, labelOffset + labelLength, archiveId);\n\n        return aeron.addCounter(\n            RECORDING_POSITION_TYPE_ID, tempBuffer, 0, keyLength, tempBuffer, labelOffset, labelLength);\n    }\n\n    /**\n     * Find the active counter id for a stream based on the recording id.\n     *\n     * @param countersReader to search within.\n     * @param recordingId    for the active recording.\n     * @return the counter id if found otherwise {@link CountersReader#NULL_COUNTER_ID}.\n     * @deprecated Use {@link #findCounterIdByRecording(CountersReader, long, long)} instead.\n     */\n    @Deprecated\n    public static int findCounterIdByRecording(final CountersReader countersReader, final long recordingId)\n    {\n        return findCounterIdByRecording(countersReader, recordingId, Aeron.NULL_VALUE);\n    }\n\n    /**\n     * Find the active counter id for a stream based on the recording id and archive id.\n     *\n     * @param countersReader to search within.\n     * @param recordingId    for the active recording.\n     * @param archiveId      to target specific Archive. Use {@link Aeron#NULL_VALUE} to emulate old behavior.\n     * @return the counter id if found otherwise {@link CountersReader#NULL_COUNTER_ID}.\n     * @since 1.44.0\n     */\n    public static int findCounterIdByRecording(\n        final CountersReader countersReader, final long recordingId, final long archiveId)\n    {\n        final DirectBuffer buffer = countersReader.metaDataBuffer();\n\n        for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n        {\n            final int counterState = countersReader.getCounterState(counterId);\n            if (RECORD_ALLOCATED == counterState)\n            {\n                if (countersReader.getCounterTypeId(counterId) == RECORDING_POSITION_TYPE_ID)\n                {\n                    final int keyOffset = metaDataOffset(counterId) + KEY_OFFSET;\n                    if (buffer.getLong(keyOffset + RECORDING_ID_OFFSET) == recordingId)\n                    {\n                        final int sourceIdentityLength = buffer.getInt(keyOffset + SOURCE_IDENTITY_LENGTH_OFFSET);\n                        final int archiveIdOffset = keyOffset + SOURCE_IDENTITY_OFFSET + sourceIdentityLength;\n                        if (Aeron.NULL_VALUE == archiveId || buffer.getLong(archiveIdOffset) == archiveId)\n                        {\n                            return counterId;\n                        }\n                    }\n                }\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return NULL_COUNTER_ID;\n    }\n\n    /**\n     * Find the active counter id for a stream based on the session id.\n     *\n     * @param countersReader to search within.\n     * @param sessionId      for the active recording.\n     * @return the counter id if found otherwise {@link CountersReader#NULL_COUNTER_ID}.\n     * @deprecated Use {@link #findCounterIdBySession(CountersReader, int, long)} instead.\n     */\n    @Deprecated\n    public static int findCounterIdBySession(final CountersReader countersReader, final int sessionId)\n    {\n        return findCounterIdBySession(countersReader, sessionId, Aeron.NULL_VALUE);\n    }\n\n    /**\n     * Find the active counter id for a stream based on the session id and archive id.\n     *\n     * @param countersReader to search within.\n     * @param sessionId      for the active recording.\n     * @param archiveId      to target specific Archive. Use {@link Aeron#NULL_VALUE} to emulate old behavior.\n     * @return the counter id if found otherwise {@link CountersReader#NULL_COUNTER_ID}.\n     * @since 1.44.0\n     */\n    public static int findCounterIdBySession(\n        final CountersReader countersReader, final int sessionId, final long archiveId)\n    {\n        final DirectBuffer buffer = countersReader.metaDataBuffer();\n\n        for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n        {\n            final int counterState = countersReader.getCounterState(counterId);\n            if (RECORD_ALLOCATED == counterState)\n            {\n                if (countersReader.getCounterTypeId(counterId) == RECORDING_POSITION_TYPE_ID)\n                {\n                    final int keyOffset = metaDataOffset(counterId) + KEY_OFFSET;\n                    if (buffer.getInt(keyOffset + SESSION_ID_OFFSET) == sessionId)\n                    {\n                        final int sourceIdentityLength = buffer.getInt(keyOffset + SOURCE_IDENTITY_LENGTH_OFFSET);\n                        final int archiveIdOffset = keyOffset + SOURCE_IDENTITY_OFFSET + sourceIdentityLength;\n                        if (Aeron.NULL_VALUE == archiveId || buffer.getLong(archiveIdOffset) == archiveId)\n                        {\n                            return counterId;\n                        }\n                    }\n                }\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return NULL_COUNTER_ID;\n    }\n\n    /**\n     * Get the recording id for a given counter id.\n     *\n     * @param countersReader to search within.\n     * @param counterId      for the active recording.\n     * @return the counter id if found otherwise {@link #NULL_RECORDING_ID}.\n     */\n    public static long getRecordingId(final CountersReader countersReader, final int counterId)\n    {\n        if (countersReader.getCounterState(counterId) == RECORD_ALLOCATED &&\n            countersReader.getCounterTypeId(counterId) == RECORDING_POSITION_TYPE_ID)\n        {\n            return countersReader.metaDataBuffer()\n                .getLong(metaDataOffset(counterId) + KEY_OFFSET + RECORDING_ID_OFFSET);\n        }\n\n        return NULL_RECORDING_ID;\n    }\n\n    /**\n     * Get the {@link Image#sourceIdentity()} for the recording.\n     *\n     * @param counters  to search within.\n     * @param counterId for the active recording.\n     * @return {@link Image#sourceIdentity()} for the recording or null if not found.\n     */\n    public static String getSourceIdentity(final CountersReader counters, final int counterId)\n    {\n        if (counters.getCounterState(counterId) == RECORD_ALLOCATED &&\n            counters.getCounterTypeId(counterId) == RECORDING_POSITION_TYPE_ID)\n        {\n            final int recordOffset = metaDataOffset(counterId);\n            return counters.metaDataBuffer().getStringAscii(recordOffset + KEY_OFFSET + SOURCE_IDENTITY_LENGTH_OFFSET);\n        }\n\n        return null;\n    }\n\n    /**\n     * Is the recording counter still active.\n     *\n     * @param counters    to search within.\n     * @param counterId   to search for.\n     * @param recordingId to confirm it is still the same value.\n     * @return true if the counter is still active otherwise false.\n     */\n    public static boolean isActive(final CountersReader counters, final int counterId, final long recordingId)\n    {\n        final int recordingIdOffset = metaDataOffset(counterId) + KEY_OFFSET + RECORDING_ID_OFFSET;\n        return counters.getCounterState(counterId) == RECORD_ALLOCATED &&\n            counters.getCounterTypeId(counterId) == RECORDING_POSITION_TYPE_ID &&\n            counters.metaDataBuffer().getLong(recordingIdOffset) == recordingId;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/main/java/io/aeron/archive/status/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Counters for tracking progress of recordings in a local Archive.\n */\npackage io.aeron.archive.status;"
  },
  {
    "path": "aeron-archive/src/main/resources/archive/aeron-archive-codecs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<sbe:messageSchema xmlns:sbe=\"http://fixprotocol.io/2016/sbe\"\n                   package=\"io.aeron.archive.codecs\"\n                   id=\"101\"\n                   version=\"13\"\n                   semanticVersion=\"5.2\"\n                   description=\"Message Codecs for communicating with an Aeron Archive.\"\n                   byteOrder=\"littleEndian\">\n    <types>\n        <composite name=\"messageHeader\" description=\"Message identifiers and length of message root.\">\n            <type name=\"blockLength\" primitiveType=\"uint16\"/>\n            <type name=\"templateId\"  primitiveType=\"uint16\"/>\n            <type name=\"schemaId\"    primitiveType=\"uint16\"/>\n            <type name=\"version\"     primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"groupSizeEncoding\" description=\"Repeating group dimensions.\">\n            <type name=\"blockLength\" primitiveType=\"uint16\"/>\n            <type name=\"numInGroup\"  primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"varAsciiEncoding\" description=\"Variable length ASCII string header.\">\n            <type name=\"length\"      primitiveType=\"uint32\" maxValue=\"1073741824\"/>\n            <type name=\"varData\"     primitiveType=\"uint8\" length=\"0\" characterEncoding=\"US-ASCII\"/>\n        </composite>\n        <composite name=\"varDataEncoding\" description=\"Variable length data blob header.\">\n            <type name=\"length\"      primitiveType=\"uint32\" maxValue=\"1073741824\"/>\n            <type name=\"varData\"     primitiveType=\"uint8\" length=\"0\"/>\n        </composite>\n        <enum name=\"ControlResponseCode\" encodingType=\"int32\" description=\"Control protocol response code.\">\n            <validValue name=\"OK\" description=\"Operation successful.\">0</validValue>\n            <validValue name=\"ERROR\" description=\"Error occurred during operation.\">1</validValue>\n            <validValue name=\"RECORDING_UNKNOWN\" description=\"Recording id was unknown.\">2</validValue>\n            <validValue name=\"SUBSCRIPTION_UNKNOWN\" description=\"Subscription id was unknown.\">3</validValue>\n        </enum>\n        <enum name=\"SourceLocation\" encodingType=\"int32\" description=\"Source location for recorded stream.\">\n            <validValue name=\"LOCAL\" description=\"Archive is local to driver.\">0</validValue>\n            <validValue name=\"REMOTE\" description=\"Archive is remote to driver.\">1</validValue>\n        </enum>\n        <enum name=\"BooleanType\" encodingType=\"int32\" description=\"Language independent boolean type.\">\n            <validValue name=\"FALSE\" description=\"Language independent boolean false.\">0</validValue>\n            <validValue name=\"TRUE\" description=\"Language independent boolean true.\">1</validValue>\n        </enum>\n        <enum name=\"RecordingSignal\" encodingType=\"int32\" description=\"Signal of operations happening to a recording.\">\n            <validValue name=\"START\" description=\"Recording has started for a stream.\">0</validValue>\n            <validValue name=\"STOP\" description=\"Recording has stopped for a stream.\">1</validValue>\n            <validValue name=\"EXTEND\" description=\"Recording has started extending.\">2</validValue>\n            <validValue name=\"REPLICATE\" description=\"Recording descriptor replicated from source archive.\">3</validValue>\n            <validValue name=\"MERGE\" description=\"Recording merged with live stream after replay.\">4</validValue>\n            <validValue name=\"SYNC\" description=\"Recording synchronised with source archive.\">5</validValue>\n            <validValue name=\"DELETE\" description=\"Recording has deleted segments.\">6</validValue>\n            <validValue name=\"REPLICATE_END\" description=\"Recording replication has ended.\">7</validValue>\n        </enum>\n        <enum name=\"RecordingState\" encodingType=\"int32\" description=\"State of a recording in the Catalog.\">\n            <validValue name=\"INVALID\" description=\"Recording is invalid.\">0</validValue>\n            <validValue name=\"VALID\" description=\"Recording is valid.\">1</validValue>\n            <validValue name=\"DELETED\" description=\"Recording was deleted.\" sinceVersion=\"10\">2</validValue>\n        </enum>\n        <type name=\"time_t\" primitiveType=\"int64\" description=\"Epoch time in milliseconds since 1 Jan 1970 UTC.\"/>\n        <type name=\"version_t\" primitiveType=\"int32\" presence=\"optional\" nullValue=\"0\" minValue=\"2\" maxValue=\"16777215\"\n              description=\"Protocol suite version using semantic version form.\"/>\n    </types>\n\n<!--\n    Archive Control Protocol\n    ========================\n\n    Control Protocol:\n        -> [connect | auth-connect],\n           *[start-recording | stop-recording | stop-recording-subscription | replay | stop-replay | stop-all-replays |\n             list-recordings | list-recordings-by-uri | list-recording | find-last-matching-recording |\n             list-recording-subscriptions | stop-recording-by-identity\n             extend-recording | truncate-recording | replicate-recording | stop-replication | stop-all-replication |\n             start-position | recording-position | stop-position | stop-or-recording-position |\n             detach-segments | delete-detached-segments | purge-segments | attach-segments | migrate-segments |\n             purge-recording | archive-id | replay-token-request],\n           close\n                \\\n        <-       +[control-response | challenge],\n                 *[control-response | recording-descriptor | recording-subscription-descriptor | recording-transition]\n\n    Recording Progress Events:\n        <- recording-started, *recording-progress, recording-stopped\n\n    1. Connect\n        - Connect to an archive providing response channel, stream id, and optional credentials if authenticating.\n        - Connection can be local or remote. Local can be optimised for IPC.\n        - If successful a control session will be created with control responses provided.\n        - If a response stream cannot be created within timeout then the session is automatically closed.\n\n    2. Recording\n        - A recording can be started for a given channel and stream id. Channel can be restricted to session specific.\n        - Each matching Image will get its own recording and assigned a unique id which can be used for replay.\n        - Each Image recording can be tracked with recording-events or locally via a RecordingPos counter.\n        - Recordings can be stopped by URI and stream-id or subscription registration id.\n        - Existing recordings can be extended or truncated.\n\n    3. Replay\n        - Recordings can be replayed to a provided channel and stream id which is different to the control stream.\n        - Replays will start when the replay stream is connected and will automatically close at the end or if the\n          replay stream is not connected.\n        - Replays will likely have a different session if from the original recording.\n        - A replay can be for a live recording which will follow a live recording in an open-ended fashion if requested\n          length is -1 or greater than current recorded position.\n        - A replay may be stopped early by closing the replay subscription or calling stop-replay.\n        - A replay can be bounded by a provided counter id which limits the replay by externally controlled counter.\n\n    4. Query\n        - Existing recordings can be listed by recording id range and filtered by uri and stream-id.\n        - Recording subscriptions can be listed for all sessions, so they can be closed on failure.\n          An active recording subscription can be found by recording id.\n        - The start, stop, and active recording position for recordings.\n\n    5. Recording Signals\n        - Signals indicating the asynchronous start, stop, extension, replicate, etc. of recordings during operation.\n\n    6. Truncate\n        - Truncate an existing recording by deleting the end of a recording from a position.\n        - Truncated to zero is an effective delete.\n\n    7. Replication\n        - Replication a recording from one archive to another including the recording descriptor.\n        - Replicated stream will likely not have the same recording id in destination archive.\n        - Can follow a live recording up to the point it is stopped at the source.\n        - Optionally Merge with a live stream after replay catch up to have multiple recordings of a live stream.\n        - The Subscription used for replication can have its tags provided, so it can be followed externally.\n\n    8. Recording Progress Events\n        - Events indicating the asynchronous start, stop, and progress of all recordings.\n        - This can be a high volume stream which reports progress in batches of messages recorded to storage.\n        - If only interested in the change of lifecycle events such as start, stop, and extend then use the below.\n\n    9. Recording Storage Maintenance\n        - Detach segments from the beginning of a recording, so they can be moved or deleted.\n        - Delete segments from the beginning of a recording which have been detached.\n        - Purge segments from the beginning of a recording which is composite action of detach and delete.\n        - Attach segments to the beginning of a recording which have been previously detached.\n        - Migrate segments from another matching recording and attached them to the beginning of a recording.\n-->\n\n    <sbe:message name=\"ControlResponse\"\n                 id=\"1\"\n                 description=\"Generic response to a control request. Error message will be empty if code is OK.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"\n               description=\"Session id for a multiplexed session over a shared connection, i.e. same Image.\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"\n               description=\"Request correlation id with which this response is associated.\"/>\n        <field name=\"relevantId\"           id=\"3\" type=\"int64\"\n               description=\"Relevant identity of an object, e.g. recordingId if RECORDING_UNKNOWN, or error code.\"/>\n        <field name=\"code\"                 id=\"4\" type=\"ControlResponseCode\"\n               description=\"Code type of the response which is one of ControlResponseCode.\"/>\n        <field name=\"version\"              id=\"5\" type=\"version_t\" presence=\"optional\" sinceVersion=\"4\"\n               description=\"Protocol version for the server using semantic version form.\"/>\n        <data  name=\"errorMessage\"         id=\"6\" type=\"varAsciiEncoding\"\n               description=\"Detailed error message which is provided when code is ERROR.\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ConnectRequest\"\n                 id=\"2\"\n                 description=\"Request a connection to an archive and provide response details for the control session.\">\n        <field name=\"correlationId\"        id=\"1\" type=\"int64\"/>\n        <field name=\"responseStreamId\"     id=\"2\" type=\"int32\"/>\n        <field name=\"version\"              id=\"3\" type=\"version_t\" presence=\"optional\" sinceVersion=\"2\"/>\n        <data  name=\"responseChannel\"      id=\"4\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"CloseSessionRequest\"\n                 id=\"3\"\n                 description=\"Close an existing control session.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StartRecordingRequest\"\n                 id=\"4\"\n                 description=\"Request a channel:stream be recorded.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"streamId\"             id=\"3\" type=\"int32\"/>\n        <field name=\"sourceLocation\"       id=\"4\" type=\"SourceLocation\"/>\n        <data  name=\"channel\"              id=\"5\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StopRecordingRequest\"\n                 id=\"5\"\n                 description=\"Request a channel:stream stop recording.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"streamId\"             id=\"3\" type=\"int32\"/>\n        <data  name=\"channel\"              id=\"4\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ReplayRequest\"\n                 id=\"6\"\n                 description=\"Replay recording range request.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n        <field name=\"position\"             id=\"4\" type=\"int64\"/>\n        <field name=\"length\"               id=\"5\" type=\"int64\"/>\n        <field name=\"replayStreamId\"       id=\"6\" type=\"int32\"/>\n        <field name=\"fileIoMaxLength\"      id=\"8\" type=\"int32\" sinceVersion=\"7\"/>\n        <field name=\"replayToken\"          id=\"9\" type=\"int64\" sinceVersion=\"10\"/>\n        <data  name=\"replayChannel\"        id=\"7\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StopReplayRequest\"\n                 id=\"7\"\n                 description=\"Stop active Replay request.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"replaySessionId\"      id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ListRecordingsRequest\"\n                 id=\"8\"\n                 description=\"Request a range of recording descriptors.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"fromRecordingId\"      id=\"3\" type=\"int64\"/>\n        <field name=\"recordCount\"          id=\"4\" type=\"int32\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ListRecordingsForUriRequest\"\n                 id=\"9\"\n                 description=\"Request a range of recording descriptors.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"fromRecordingId\"      id=\"3\" type=\"int64\"/>\n        <field name=\"recordCount\"          id=\"4\" type=\"int32\"/>\n        <field name=\"streamId\"             id=\"5\" type=\"int32\"/>\n        <data  name=\"channel\"              id=\"6\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ListRecordingRequest\"\n                 id=\"10\"\n                 description=\"Request a descriptor for a recording id.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ExtendRecordingRequest\"\n                 id=\"11\"\n                 description=\"Request an existing recording be extended\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n        <field name=\"streamId\"             id=\"4\" type=\"int32\"/>\n        <field name=\"sourceLocation\"       id=\"5\" type=\"SourceLocation\"/>\n        <data  name=\"channel\"              id=\"6\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"RecordingPositionRequest\"\n                 id=\"12\"\n                 description=\"Request the recorded position of an active recording.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"TruncateRecordingRequest\"\n                 id=\"13\"\n                 description=\"Request the truncation of stopped recording.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n        <field name=\"position\"             id=\"4\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StopRecordingSubscriptionRequest\"\n                 id=\"14\"\n                 description=\"Request a subscription stop recording.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"subscriptionId\"       id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StopPositionRequest\"\n                 id=\"15\"\n                 description=\"Request the stop position of a recording.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"FindLastMatchingRecordingRequest\"\n                 id=\"16\"\n                 description=\"Find the last recording id after minRecordingId which matches criteria.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"minRecordingId\"       id=\"3\" type=\"int64\"/>\n        <field name=\"sessionId\"            id=\"4\" type=\"int32\"/>\n        <field name=\"streamId\"             id=\"5\" type=\"int32\"/>\n        <data  name=\"channel\"              id=\"6\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ListRecordingSubscriptionsRequest\"\n                 id=\"17\"\n                 description=\"Request a range of active recording subscriptions which match a criteria.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"pseudoIndex\"          id=\"3\" type=\"int32\"/>\n        <field name=\"subscriptionCount\"    id=\"4\" type=\"int32\"/>\n        <field name=\"applyStreamId\"        id=\"5\" type=\"BooleanType\"/>\n        <field name=\"streamId\"             id=\"6\" type=\"int32\"/>\n        <data  name=\"channel\"              id=\"7\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"BoundedReplayRequest\"\n                 id=\"18\"\n                 description=\"Replay recording range request bounded by a position counter.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n        <field name=\"position\"             id=\"4\" type=\"int64\"/>\n        <field name=\"length\"               id=\"5\" type=\"int64\"/>\n        <field name=\"limitCounterId\"       id=\"6\" type=\"int32\"/>\n        <field name=\"replayStreamId\"       id=\"7\" type=\"int32\"/>\n        <field name=\"fileIoMaxLength\"      id=\"9\" type=\"int32\" sinceVersion=\"7\"/>\n        <field name=\"replayToken\"          id=\"10\" type=\"int64\" sinceVersion=\"10\"/>\n        <data  name=\"replayChannel\"        id=\"8\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StopAllReplaysRequest\"\n                 id=\"19\"\n                 description=\"Stop all active replays.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n<!-- Archive Catalog Metadata -->\n\n    <sbe:message name=\"CatalogHeader\"\n                 id=\"20\"\n                 description=\"Used as first element in Catalog to set the version and alignment of entries.\">\n        <field name=\"version\"              id=\"1\"  type=\"int32\"/>\n        <field name=\"length\"               id=\"2\"  type=\"int32\"/>\n        <field name=\"nextRecordingId\"      id=\"3\"  type=\"int64\"/>\n        <field name=\"alignment\"            id=\"4\"  type=\"int32\"/>\n        <field name=\"reserved\"             id=\"5\"  type=\"int8\" offset=\"31\"/>\n    </sbe:message>\n\n    <sbe:message name=\"RecordingDescriptorHeader\"\n                 id=\"21\"\n                 description=\"Used in the catalog to describe the recording descriptor entry which follows.\">\n        <field name=\"length\"               id=\"1\" type=\"int32\"\n               description=\"Length of the RecordingDescriptor in bytes including alignment padding.\"/>\n        <field name=\"state\"                id=\"2\" type=\"RecordingState\" description=\"State of the recording.\"/>\n        <field name=\"checksum\"             id=\"4\" type=\"int32\"\n               description=\"Checksum of the entire RecordingDescriptor.\"/>\n        <field name=\"reserved\"             id=\"3\" type=\"int8\" offset=\"31\"/>\n    </sbe:message>\n\n<!-- Records in the Catalog which describe recordings that can be listed via the Control Protocol -->\n\n    <sbe:message name=\"RecordingDescriptor\"\n                 id=\"22\"\n                 description=\"Describes a recording in the catalog.\">\n        <field name=\"controlSessionId\"     id=\"1\"  type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\"  type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\"  type=\"int64\"/>\n        <field name=\"startTimestamp\"       id=\"4\"  type=\"time_t\"/>\n        <field name=\"stopTimestamp\"        id=\"5\"  type=\"time_t\"/>\n        <field name=\"startPosition\"        id=\"6\"  type=\"int64\"/>\n        <field name=\"stopPosition\"         id=\"7\"  type=\"int64\"/>\n        <field name=\"initialTermId\"        id=\"8\"  type=\"int32\"/>\n        <field name=\"segmentFileLength\"    id=\"9\"  type=\"int32\"/>\n        <field name=\"termBufferLength\"     id=\"10\" type=\"int32\"/>\n        <field name=\"mtuLength\"            id=\"11\" type=\"int32\"/>\n        <field name=\"sessionId\"            id=\"12\" type=\"int32\"/>\n        <field name=\"streamId\"             id=\"13\" type=\"int32\"/>\n        <data  name=\"strippedChannel\"      id=\"14\" type=\"varAsciiEncoding\"/>\n        <data  name=\"originalChannel\"      id=\"15\" type=\"varAsciiEncoding\"/>\n        <data  name=\"sourceIdentity\"       id=\"16\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"RecordingSubscriptionDescriptor\"\n                 id=\"23\"\n                 description=\"Describes a recording subscription that has been started and not yet stopped.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"subscriptionId\"       id=\"3\" type=\"int64\"/>\n        <field name=\"streamId\"             id=\"4\" type=\"int32\"/>\n        <data  name=\"strippedChannel\"      id=\"5\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"RecordingSignalEvent\"\n                 id=\"24\"\n                 description=\"Signal of operations which happen to a recording.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n        <field name=\"subscriptionId\"       id=\"4\" type=\"int64\"/>\n        <field name=\"position\"             id=\"5\" type=\"int64\"/>\n        <field name=\"signal\"               id=\"6\" type=\"RecordingSignal\"/>\n    </sbe:message>\n\n<!-- Extended Control Protocol -->\n\n    <sbe:message name=\"ReplicateRequest\"\n                 id=\"50\"\n                 description=\"Replicate a recording from another archive.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"srcRecordingId\"       id=\"3\" type=\"int64\"/>\n        <field name=\"dstRecordingId\"       id=\"4\" type=\"int64\"/>\n        <field name=\"srcControlStreamId\"   id=\"5\" type=\"int32\"/>\n        <data  name=\"srcControlChannel\"    id=\"6\" type=\"varAsciiEncoding\"/>\n        <data  name=\"liveDestination\"      id=\"7\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StopReplicationRequest\"\n                 id=\"51\"\n                 description=\"Stop active replication session.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"replicationId\"        id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StartPositionRequest\"\n                 id=\"52\"\n                 description=\"Request the start position of a recording.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"DetachSegmentsRequest\"\n                 id=\"53\"\n                 description=\"Detach segments from a recording to give a new start position.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n        <field name=\"newStartPosition\"     id=\"4\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"DeleteDetachedSegmentsRequest\"\n                 id=\"54\"\n                 description=\"Delete detached segments from a recording.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"PurgeSegmentsRequest\"\n                 id=\"55\"\n                 description=\"Purge (detach and delete) segments from a recording to give a new start position.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n        <field name=\"newStartPosition\"     id=\"4\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"AttachSegmentsRequest\"\n                 id=\"56\"\n                 description=\"Attach segments to beginning of a recording to give a new start position.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"MigrateSegmentsRequest\"\n                 id=\"57\"\n                 description=\"Migrate segments to beginning of a recording to give a new start position.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"srcRecordingId\"       id=\"3\" type=\"int64\"/>\n        <field name=\"dstRecordingId\"       id=\"4\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"AuthConnectRequest\"\n                 id=\"58\"\n                 description=\"Connect request with authentication credentials.\">\n        <field name=\"correlationId\"        id=\"1\" type=\"int64\"/>\n        <field name=\"responseStreamId\"     id=\"2\" type=\"int32\"/>\n        <field name=\"version\"              id=\"3\" type=\"version_t\"/>\n        <data  name=\"responseChannel\"      id=\"4\" type=\"varAsciiEncoding\"/>\n        <data  name=\"encodedCredentials\"   id=\"5\" type=\"varDataEncoding\"/>\n        <data  name=\"clientInfo\"           id=\"6\" type=\"varAsciiEncoding\" sinceVersion=\"12\"/>\n    </sbe:message>\n\n    <sbe:message name=\"Challenge\"\n                 id=\"59\"\n                 description=\"Challenge the client to provide credentials.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"version\"              id=\"3\" type=\"version_t\"/>\n        <data  name=\"encodedChallenge\"     id=\"4\" type=\"varDataEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ChallengeResponse\"\n                 id=\"60\"\n                 description=\"Response to an archive challenge with credentials.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <data  name=\"encodedCredentials\"   id=\"3\" type=\"varDataEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"KeepAliveRequest\"\n                 id=\"61\"\n                 description=\"Keep an archive control session alive by indicating the client is alive.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"TaggedReplicateRequest\"\n                 id=\"62\"\n                 description=\"Replicate a recording from another archive and set tags on subscription.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"srcRecordingId\"       id=\"3\" type=\"int64\"/>\n        <field name=\"dstRecordingId\"       id=\"4\" type=\"int64\"/>\n        <field name=\"channelTagId\"         id=\"5\" type=\"int64\"/>\n        <field name=\"subscriptionTagId\"    id=\"6\" type=\"int64\"/>\n        <field name=\"srcControlStreamId\"   id=\"7\" type=\"int32\"/>\n        <data  name=\"srcControlChannel\"    id=\"8\" type=\"varAsciiEncoding\"/>\n        <data  name=\"liveDestination\"      id=\"9\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StartRecordingRequest2\"\n                 id=\"63\"\n                 description=\"Request a channel:stream be recorded with the option to auto stop when complete.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"streamId\"             id=\"3\" type=\"int32\"/>\n        <field name=\"sourceLocation\"       id=\"4\" type=\"SourceLocation\"/>\n        <field name=\"autoStop\"             id=\"5\" type=\"BooleanType\"/>\n        <data  name=\"channel\"              id=\"6\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ExtendRecordingRequest2\"\n                 id=\"64\"\n                 description=\"Request an existing recording be extended.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n        <field name=\"streamId\"             id=\"4\" type=\"int32\"/>\n        <field name=\"sourceLocation\"       id=\"5\" type=\"SourceLocation\"/>\n        <field name=\"autoStop\"             id=\"6\" type=\"BooleanType\"/>\n        <data  name=\"channel\"              id=\"7\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StopRecordingByIdentityRequest\"\n                 id=\"65\"\n                 description=\"Try and stop an active recording by its identity.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ReplicateRequest2\"\n                 id=\"66\"\n                 description=\"Replicate a recording from another archive.\"\n                 sinceVersion=\"6\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"srcRecordingId\"       id=\"3\" type=\"int64\"/>\n        <field name=\"dstRecordingId\"       id=\"4\" type=\"int64\"/>\n        <field name=\"stopPosition\"         id=\"5\" type=\"int64\"/>\n        <field name=\"channelTagId\"         id=\"6\" type=\"int64\"/>\n        <field name=\"subscriptionTagId\"    id=\"7\" type=\"int64\"/>\n        <field name=\"srcControlStreamId\"   id=\"8\" type=\"int32\"/>\n        <field name=\"fileIoMaxLength\"      id=\"12\" type=\"int32\" sinceVersion=\"7\"/>\n        <field name=\"replicationSessionId\" id=\"13\" type=\"int32\" sinceVersion=\"8\"/>\n        <data  name=\"srcControlChannel\"    id=\"9\" type=\"varAsciiEncoding\"/>\n        <data  name=\"liveDestination\"      id=\"10\" type=\"varAsciiEncoding\"/>\n        <data  name=\"replicationChannel\"   id=\"11\" type=\"varAsciiEncoding\"/>\n        <data  name=\"encodedCredentials\"   id=\"14\" type=\"varDataEncoding\" sinceVersion=\"8\"/>\n        <data  name=\"srcResponseChannel\"   id=\"15\" type=\"varAsciiEncoding\" sinceVersion=\"10\"/>\n    </sbe:message>\n\n    <sbe:message name=\"MaxRecordedPositionRequest\"\n                 id=\"67\"\n                 description=\"Request the stop or active recording position of a recording.\"\n                 sinceVersion=\"9\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ArchiveIdRequest\"\n                 id=\"68\"\n                 description=\"Resolve id of the Archive.\"\n                 sinceVersion=\"9\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n    </sbe:message>\n\n    <!-- Archive Recording Progress Events -->\n\n    <sbe:message name=\"RecordingStarted\"\n                 id=\"101\"\n                 description=\"Describes a new image recording as a notification.\">\n        <field name=\"recordingId\"          id=\"1\" type=\"int64\"/>\n        <field name=\"startPosition\"        id=\"2\" type=\"int64\"/>\n        <field name=\"sessionId\"            id=\"3\" type=\"int32\"/>\n        <field name=\"streamId\"             id=\"4\" type=\"int32\"/>\n        <data  name=\"channel\"              id=\"5\" type=\"varAsciiEncoding\"/>\n        <data  name=\"sourceIdentity\"       id=\"6\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"RecordingProgress\"\n                 id=\"102\"\n                 description=\"Recording progress notification for an Image.\">\n        <field name=\"recordingId\"          id=\"1\" type=\"int64\"/>\n        <field name=\"startPosition\"        id=\"2\" type=\"int64\"/>\n        <field name=\"position\"             id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"RecordingStopped\"\n                 id=\"103\"\n                 description=\"Recording termination notification.\">\n        <field name=\"recordingId\"          id=\"1\" type=\"int64\"/>\n        <field name=\"startPosition\"        id=\"2\" type=\"int64\"/>\n        <field name=\"stopPosition\"         id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"PurgeRecordingRequest\"\n                 id=\"104\"\n                 description=\"Request the invalidation of stopped recording.\"\n                 sinceVersion=\"5\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ReplayTokenRequest\"\n                 id=\"105\"\n                 sinceVersion=\"10\"\n                 description=\"Request a token to allow a replay to be triggered from a different image.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"Ping\"\n                 id=\"106\"\n                 sinceVersion=\"11\"\n                 description=\"Ping for heartbeat.\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"UpdateChannelRequest\"\n                 id=\"107\"\n                 sinceVersion=\"13\"\n                 description=\"Request to update the channel for a recording\">\n        <field name=\"controlSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"        id=\"2\" type=\"int64\"/>\n        <field name=\"recordingId\"          id=\"3\" type=\"int64\"/>\n        <data  name=\"channel\"              id=\"4\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n</sbe:messageSchema>\n"
  },
  {
    "path": "aeron-archive/src/main/resources/archive/aeron-archive-mark-codecs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<sbe:messageSchema xmlns:sbe=\"http://fixprotocol.io/2016/sbe\"\n                   package=\"io.aeron.archive.codecs.mark\"\n                   id=\"100\"\n                   version=\"2\"\n                   semanticVersion=\"5.2\"\n                   description=\"Codecs for Mark file of Aeron Archive.\"\n                   byteOrder=\"littleEndian\">\n    <types>\n        <composite name=\"messageHeader\" description=\"Message identifiers and length of message root.\">\n            <type name=\"blockLength\" primitiveType=\"uint16\"/>\n            <type name=\"templateId\"  primitiveType=\"uint16\"/>\n            <type name=\"schemaId\"    primitiveType=\"uint16\"/>\n            <type name=\"version\"     primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"groupSizeEncoding\" description=\"Repeating group dimensions.\">\n            <type name=\"blockLength\" primitiveType=\"uint16\"/>\n            <type name=\"numInGroup\"  primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"varAsciiEncoding\" description=\"Variable length ASCII string header.\">\n            <type name=\"length\"      primitiveType=\"uint32\" maxValue=\"1073741824\"/>\n            <type name=\"varData\"     primitiveType=\"uint8\" length=\"0\" characterEncoding=\"US-ASCII\"/>\n        </composite>\n        <type name=\"time_t\"   primitiveType=\"int64\" description=\"Epoch time in milliseconds since 1 Jan 1970 UTC.\"/>\n        <type name=\"length_t\" primitiveType=\"int32\" presence=\"optional\" nullValue=\"0\" description=\"Length of a buffer\"/>\n        <type name=\"archiveId_t\" primitiveType=\"int64\" presence=\"optional\" nullValue=\"-1\" description=\"unique Archive id\"/>\n    </types>\n\n    <sbe:message name=\"MarkFileHeader\"\n                 id=\"200\"\n                 blockLength=\"128\"\n                 description=\"Mark file header.\">\n        <field name=\"version\"              id=\"1\"  type=\"int32\"/>\n        <field name=\"activityTimestamp\"    id=\"2\"  type=\"time_t\" offset=\"8\"/>\n        <field name=\"startTimestamp\"       id=\"3\"  type=\"time_t\"/>\n        <field name=\"pid\"                  id=\"4\"  type=\"int64\"/>\n        <field name=\"controlStreamId\"      id=\"5\"  type=\"int32\"/>\n        <field name=\"localControlStreamId\" id=\"6\"  type=\"int32\"/>\n        <field name=\"eventsStreamId\"       id=\"7\"  type=\"int32\"/>\n        <field name=\"headerLength\"         id=\"8\"  type=\"length_t\" sinceVersion=\"1\"/>\n        <field name=\"errorBufferLength\"    id=\"9\"  type=\"length_t\" sinceVersion=\"1\"/>\n        <field name=\"archiveId\"            id=\"14\" type=\"archiveId_t\" offset=\"56\" sinceVersion=\"2\"/>\n        <data  name=\"controlChannel\"       id=\"10\" type=\"varAsciiEncoding\"/>\n        <data  name=\"localControlChannel\"  id=\"11\" type=\"varAsciiEncoding\"/>\n        <data  name=\"eventsChannel\"        id=\"12\" type=\"varAsciiEncoding\"/>\n        <data  name=\"aeronDirectory\"       id=\"13\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n</sbe:messageSchema>\n"
  },
  {
    "path": "aeron-archive/src/main/resources/archive/fpl/sbe.xsd",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xs:schema xmlns:sbe=\"http://fixprotocol.io/2016/sbe\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" targetNamespace=\"http://fixprotocol.io/2016/sbe\" elementFormDefault=\"unqualified\" version=\"1.0 Draft Standard\">\n    <xs:annotation>\n        <xs:documentation>\n            Message schema for FIX Simple Binary Encoding\n            Version: 1.0 Draft Standard\n            © Copyright 2014-2016 FIX Protocol Limited\n            License: Creative Commons Attribution-NoDerivatives 4.0 International Public License\n        </xs:documentation>\n    </xs:annotation>\n    <xs:element name=\"messageSchema\">\n        <xs:annotation>\n            <xs:documentation>\n                Root of XML document, holds all message templates\n                and their elements\n            </xs:documentation>\n        </xs:annotation>\n        <xs:complexType>\n            <xs:sequence>\n                <xs:element name=\"types\" maxOccurs=\"unbounded\">\n                    <xs:annotation>\n                        <xs:documentation>\n                            More than one set of types may be provided.\n                            Names must be unique across all encoding\n                            types.\n                            Encoding types may appear in any order.\n                        </xs:documentation>\n                    </xs:annotation>\n                    <xs:complexType>\n                        <xs:choice maxOccurs=\"unbounded\">\n                            <xs:element name=\"type\" type=\"sbe:encodedDataType\"/>\n                            <xs:element name=\"composite\" type=\"sbe:compositeDataType\"/>\n                            <xs:element name=\"enum\" type=\"sbe:enumType\"/>\n                            <xs:element name=\"set\" type=\"sbe:setType\"/>\n                        </xs:choice>\n                    </xs:complexType>\n                </xs:element>\n                <xs:element ref=\"sbe:message\" maxOccurs=\"unbounded\"/>\n            </xs:sequence>\n            <xs:attribute name=\"package\" type=\"xs:string\"/>\n            <xs:attribute name=\"id\" type=\"xs:unsignedShort\">\n                <xs:annotation>\n                    <xs:documentation>Unique ID of a message schema\n                    </xs:documentation>\n                </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"version\" type=\"xs:nonNegativeInteger\" use=\"required\">\n                <xs:annotation>\n                    <xs:documentation>The version of a message schema. Initial version\n                        is 0.\n                    </xs:documentation>\n                </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"semanticVersion\" type=\"xs:string\" use=\"optional\">\n                <xs:annotation>\n                    <xs:documentation>Application layer specification version, such as\n                        FIX version 'FIX.5.0SP2'\n                    </xs:documentation>\n                </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"description\" type=\"xs:string\" use=\"optional\"/>\n            <xs:attribute name=\"byteOrder\" default=\"littleEndian\">\n                <xs:simpleType>\n                    <xs:restriction base=\"xs:token\">\n                        <xs:enumeration value=\"bigEndian\"/>\n                        <xs:enumeration value=\"littleEndian\"/>\n                    </xs:restriction>\n                </xs:simpleType>\n            </xs:attribute>\n            <xs:attribute name=\"headerType\" type=\"sbe:symbolicName_t\" default=\"messageHeader\">\n                <xs:annotation>\n                    <xs:documentation>\n                        Name of the encoding type of the message header,\n                        which is the same for all messages in a schema. The name has a\n                        default, but an encoding of that name must be present under a\n                        'types' element.\n                    </xs:documentation>\n                </xs:annotation>\n            </xs:attribute>\n        </xs:complexType>\n    </xs:element>\n    <xs:element name=\"message\" type=\"sbe:blockType\">\n        <xs:annotation>\n            <xs:documentation>\n                A message type, also known as a message template\n            </xs:documentation>\n        </xs:annotation>\n    </xs:element>\n    <xs:complexType name=\"blockType\">\n        <xs:annotation>\n            <xs:documentation>Base type of message and repeating group entry\n            </xs:documentation>\n        </xs:annotation>\n        <xs:sequence>\n            <xs:element name=\"field\" type=\"sbe:fieldType\" minOccurs=\"0\" maxOccurs=\"unbounded\">\n                <xs:annotation>\n                    <xs:documentation>Fixed-length fields</xs:documentation>\n                </xs:annotation>\n            </xs:element>\n            <xs:element name=\"group\" type=\"sbe:groupType\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n            <xs:element name=\"data\" type=\"sbe:fieldType\" minOccurs=\"0\" maxOccurs=\"unbounded\">\n                <xs:annotation>\n                    <xs:documentation>Variable-length fields</xs:documentation>\n                </xs:annotation>\n            </xs:element>\n        </xs:sequence>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"id\" type=\"xs:unsignedShort\" use=\"required\">\n            <xs:annotation>\n                <xs:documentation>Unique ID of a message template\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attribute name=\"blockLength\" type=\"xs:nonNegativeInteger\" use=\"optional\">\n            <xs:annotation>\n                <xs:documentation>Space reserved for root level of message, not\n                    include groups or variable-length\n                    data elements.\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"groupType\">\n        <xs:annotation>\n            <xs:documentation>\n                A repeating group contains an array of entries\n            </xs:documentation>\n        </xs:annotation>\n        <xs:complexContent>\n            <xs:extension base=\"sbe:blockType\">\n                <xs:attribute name=\"dimensionType\" type=\"sbe:symbolicName_t\" default=\"groupSizeEncoding\"/>\n            </xs:extension>\n        </xs:complexContent>\n    </xs:complexType>\n    <xs:complexType name=\"encodedDataType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                Simple wire encoding consisting of a primitive type\n                or array of primitives\n            </xs:documentation>\n        </xs:annotation>\n        <xs:simpleContent>\n            <xs:extension base=\"xs:token\">\n                <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n                <xs:attribute name=\"nullValue\" type=\"xs:string\" use=\"optional\">\n                    <xs:annotation>\n                        <xs:documentation>Override of default null indicator for the data\n                            type in SBE specification,\n                            as a string.\n                        </xs:documentation>\n                    </xs:annotation>\n                </xs:attribute>\n                <xs:attribute name=\"minValue\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attribute name=\"maxValue\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attribute name=\"length\" type=\"xs:nonNegativeInteger\" default=\"1\"/>\n                <xs:attribute name=\"primitiveType\" use=\"required\">\n                    <xs:simpleType>\n                        <xs:restriction base=\"xs:token\">\n                            <xs:enumeration value=\"char\"/>\n                            <xs:enumeration value=\"int8\"/>\n                            <xs:enumeration value=\"int16\"/>\n                            <xs:enumeration value=\"int32\"/>\n                            <xs:enumeration value=\"int64\"/>\n                            <xs:enumeration value=\"uint8\"/>\n                            <xs:enumeration value=\"uint16\"/>\n                            <xs:enumeration value=\"uint32\"/>\n                            <xs:enumeration value=\"uint64\"/>\n                            <xs:enumeration value=\"float\"/>\n                            <xs:enumeration value=\"double\"/>\n                        </xs:restriction>\n                    </xs:simpleType>\n                </xs:attribute>\n                <xs:attribute name=\"characterEncoding\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n                <xs:attributeGroup ref=\"sbe:presenceAttributes\"/>\n                <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n                <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n            </xs:extension>\n        </xs:simpleContent>\n    </xs:complexType>\n    <xs:complexType name=\"compositeDataType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                A wire encoding composed of multiple parts\n            </xs:documentation>\n        </xs:annotation>\n        <xs:choice maxOccurs=\"unbounded\">\n            <xs:element name=\"type\" type=\"sbe:encodedDataType\"/>\n            <xs:element name=\"enum\" type=\"sbe:enumType\"/>\n            <xs:element name=\"set\" type=\"sbe:setType\"/>\n            <xs:element name=\"composite\" type=\"sbe:compositeDataType\"/>\n            <xs:element name=\"ref\" type=\"sbe:refType\"/>\n        </xs:choice>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"enumType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                An enumeration of valid values\n            </xs:documentation>\n        </xs:annotation>\n        <xs:sequence>\n            <xs:element name=\"validValue\" type=\"sbe:validValue\" maxOccurs=\"unbounded\"/>\n        </xs:sequence>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"encodingType\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"validValue\">\n        <xs:annotation>\n            <xs:documentation>\n                Valid value as a string\n            </xs:documentation>\n        </xs:annotation>\n        <xs:simpleContent>\n            <xs:extension base=\"xs:token\">\n                <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n                <xs:attribute name=\"description\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n            </xs:extension>\n        </xs:simpleContent>\n    </xs:complexType>\n    <xs:complexType name=\"refType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                A reference to any existing encoding type (simple type, enum or set)\n                to reuse as a member of a composite type\n            </xs:documentation>\n        </xs:annotation>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"type\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"setType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                A multi value choice (encoded as a bitset)\n            </xs:documentation>\n        </xs:annotation>\n        <xs:sequence>\n            <xs:element name=\"choice\" type=\"sbe:choice\" maxOccurs=\"64\"/>\n        </xs:sequence>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"encodingType\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"choice\">\n        <xs:annotation>\n            <xs:documentation>\n                A choice within a multi value set. Value is the\n                position within a bitset (zero-based index).\n            </xs:documentation>\n        </xs:annotation>\n        <xs:simpleContent>\n            <xs:extension base=\"xs:nonNegativeInteger\">\n                <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n                <xs:attribute name=\"description\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n            </xs:extension>\n        </xs:simpleContent>\n    </xs:complexType>\n    <xs:complexType name=\"fieldType\">\n        <xs:annotation>\n            <xs:documentation>\n                A field of a message of a specified dataType\n            </xs:documentation>\n        </xs:annotation>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"id\" type=\"xs:unsignedShort\" use=\"required\"/>\n        <xs:attribute name=\"type\" type=\"sbe:symbolicName_t\" use=\"required\">\n            <xs:annotation>\n                <xs:documentation>Must match the name of an encoding contained by\n                    'types' element\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attribute name=\"epoch\" type=\"xs:string\" default=\"unix\"/>\n        <xs:attribute name=\"timeUnit\" type=\"xs:string\" default=\"nanosecond\">\n            <xs:annotation>\n                <xs:documentation>Deprecated - only for back compatibility with RC2\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:presenceAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n        <!-- start of time period - default is UNIX epoch -->\n    </xs:complexType>\n    <xs:attributeGroup name=\"semanticAttributes\">\n        <xs:annotation>\n            <xs:documentation>\n                Application layer class. Maps a field or encoding\n                to a FIX data type.\n            </xs:documentation>\n        </xs:annotation>\n        <xs:attribute name=\"semanticType\" type=\"xs:token\" use=\"optional\"/>\n        <xs:attribute name=\"description\" type=\"xs:string\" use=\"optional\"/>\n    </xs:attributeGroup>\n    <xs:attributeGroup name=\"versionAttributes\">\n        <xs:annotation>\n            <xs:documentation>\n                Schema versioning supports message extension\n            </xs:documentation>\n        </xs:annotation>\n        <xs:attribute name=\"sinceVersion\" type=\"xs:nonNegativeInteger\" default=\"0\">\n            <xs:annotation>\n                <xs:documentation>\n                    The schema version in which an element was added\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attribute name=\"deprecated\" type=\"xs:nonNegativeInteger\" use=\"optional\">\n            <xs:annotation>\n                <xs:documentation>\n                    The version of the schema in which an element was\n                    deprecated. It is retained for back compatibility but should no\n                    longer be used by updated applications.\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n    </xs:attributeGroup>\n    <xs:attributeGroup name=\"alignmentAttributes\">\n        <xs:attribute name=\"offset\" type=\"xs:unsignedInt\" use=\"optional\">\n            <xs:annotation>\n                <xs:documentation>Offset from start of a composite type or block\n                    as a zero-based index.\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n    </xs:attributeGroup>\n    <xs:attributeGroup name=\"presenceAttributes\">\n        <xs:attribute name=\"presence\" default=\"required\">\n            <xs:simpleType>\n                <xs:restriction base=\"xs:token\">\n                    <xs:enumeration value=\"required\">\n                        <xs:annotation>\n                            <xs:documentation>The value must always be populated\n                            </xs:documentation>\n                        </xs:annotation>\n                    </xs:enumeration>\n                    <xs:enumeration value=\"optional\">\n                        <xs:annotation>\n                            <xs:documentation>Value may be set to nullValue for its data type\n                            </xs:documentation>\n                        </xs:annotation>\n                    </xs:enumeration>\n                    <xs:enumeration value=\"constant\">\n                        <xs:annotation>\n                            <xs:documentation>Value does not vary so it need not be\n                                serialized on the wire\n                            </xs:documentation>\n                        </xs:annotation>\n                    </xs:enumeration>\n                </xs:restriction>\n            </xs:simpleType>\n        </xs:attribute>\n        <xs:attribute name=\"valueRef\" type=\"sbe:qualifiedName_t\" use=\"optional\">\n            <xs:annotation>\n                <xs:documentation>A constant value as valid value of an enum\n                    in the form enum-name.valid-value-name\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n    </xs:attributeGroup>\n    <xs:simpleType name=\"symbolicName_t\">\n        <xs:restriction base=\"xs:string\">\n            <xs:minLength value=\"1\"/>\n            <xs:maxLength value=\"64\"/>\n            <xs:pattern value=\"([A-Z]|[a-z]|_)([0-9]|[A-Z]|[a-z]|_)*\"/>\n        </xs:restriction>\n    </xs:simpleType>\n    <xs:simpleType name=\"qualifiedName_t\">\n        <xs:restriction base=\"xs:string\">\n            <xs:pattern value=\"([A-Z]|[a-z]|_)([0-9]|[A-Z]|[a-z]|_)*\\.([A-Z]|[a-z]|_)([0-9]|[A-Z]|[a-z]|_)*\"/>\n        </xs:restriction>\n    </xs:simpleType>\n</xs:schema>\n"
  },
  {
    "path": "aeron-archive/src/test/c/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nfind_package(Java REQUIRED)\n\nadd_definitions(-DAERON_ALL_JAR=\"${AERON_ALL_JAR}\")\nadd_definitions(-DAERON_AGENT_JAR=\"${AERON_AGENT_JAR}\")\nadd_definitions(-DJAVA_EXECUTABLE=\"${Java_JAVA_EXECUTABLE}\")\nadd_definitions(-DARCHIVE_DIR=\"${CMAKE_CURRENT_BINARY_DIR}/archive\")\nadd_definitions(-DJAVA_MAJOR_VERSION=${Java_VERSION_MAJOR})\nadd_definitions(-DJAVA_MINOR_VERSION=${Java_VERSION_MINOR})\n\nfunction(aeron_c_archive_test name file)\n        add_executable(${name} \"TestArchive.h\" ${file} ${SOURCE} ${HEADERS})\n        add_dependencies(${name} gmock aeron-all-jar aeron_archive_c_client)\n        target_include_directories(${name} PUBLIC ${AERON_C_ARCHIVE_SOURCE_PATH})\n        target_include_directories(${name} PUBLIC ${ARCHIVE_C_CODEC_TARGET_DIR})\n        target_link_libraries(${name} aeron aeron_archive_c_client gmock_main ${CMAKE_THREAD_LIBS_INIT})\n        add_test(NAME ${name} COMMAND ${name})\n        set_tests_properties(${name} PROPERTIES TIMEOUT 300)\n        set_tests_properties(${name} PROPERTIES RUN_SERIAL TRUE)\nendfunction()\n\nif (AERON_UNIT_TESTS)\n    aeron_c_archive_test(aeron_archive_test client/aeron_archive_test.cpp)\nendif ()\n"
  },
  {
    "path": "aeron-archive/src/test/c/TestArchive.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_TESTARCHIVE_H\n#define AERON_TESTARCHIVE_H\n\n// Uncomment for logging\n// #define ENABLE_AGENT_DEBUG_LOGGING 1\n\nextern \"C\"\n{\n#include <atomic>\n#include <signal.h>\n}\n\n#include <thread>\n\n#define TERM_LENGTH AERON_LOGBUFFER_TERM_MIN_LENGTH\n#define SEGMENT_LENGTH (TERM_LENGTH * 2)\n#define ARCHIVE_MARK_FILE_HEADER_LENGTH (8192)\n\n#ifdef _WIN32\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n#include <shellapi.h>\ntypedef intptr_t pid_t;\n\nstatic void await_process_terminated(pid_t process_handle)\n{\n    WaitForSingleObject(reinterpret_cast<HANDLE>(process_handle), INFINITE);\n}\n#else\n#include \"ftw.h\"\n#include \"spawn.h\"\n\nstatic void await_process_terminated(pid_t process_handle)\n{\n    int process_status = -1;\n    while (true)\n    {\n        waitpid(process_handle, &process_status, WUNTRACED);\n        if (WIFSIGNALED(process_status) || WIFEXITED(process_status))\n        {\n            break;\n        }\n    }\n}\n#endif\n\nclass TestArchive\n{\npublic:\n    TestArchive(\n        std::string aeronDir,\n        std::string archiveDir,\n        std::ostream &stream,\n        std::string controlChannel,\n        std::string replicationChannel,\n        std::int64_t archiveId,\n        std::int32_t maxConcurrentReplays = 100)\n        : m_archiveDir(archiveDir), m_aeronDir(aeronDir), m_stream(stream)\n    {\n        m_stream << aeron_epoch_clock() << \" [SetUp] Starting ArchivingMediaDriver...\" << std::endl;\n\n        std::string aeronDirArg = \"-Daeron.dir=\" + aeronDir;\n        std::string archiveDirArg = \"-Daeron.archive.dir=\" + archiveDir;\n        std::string archiveMarkFileDirArg = \"-Daeron.archive.mark.file.dir=\" + aeronDir;\n        m_stream << aeron_epoch_clock() << \" [SetUp] \" << aeronDirArg << std::endl;\n        m_stream << aeron_epoch_clock() << \" [SetUp] \" << archiveDirArg << std::endl;\n        std::string controlChannelArg = \"-Daeron.archive.control.channel=\" + controlChannel;\n        std::string replicationChannelArg = \"-Daeron.archive.replication.channel=\" + replicationChannel;\n        std::string archiveIdArg = \"-Daeron.archive.id=\" + std::to_string(archiveId);\n        std::string segmentLength = \"-Daeron.archive.segment.file.length=\" + std::to_string(SEGMENT_LENGTH);\n        std::string maxConcurrentReplaysArg = \"-Daeron.archive.max.concurrent.replays=\" + std::to_string(maxConcurrentReplays);\n\n        const char *const argv[] =\n        {\n            \"java\",\n            \"--add-opens\",\n            \"java.base/jdk.internal.misc=ALL-UNNAMED\",\n            \"--add-opens\",\n            \"java.base/java.util.zip=ALL-UNNAMED\",\n#if ENABLE_AGENT_DEBUG_LOGGING\n            m_aeronAgentJar.c_str(),\n            \"-Daeron.event.log=admin\",\n            \"-Daeron.event.archive.log=all\",\n#endif\n            \"-Daeron.dir.delete.on.start=true\",\n            \"-Daeron.dir.delete.on.shutdown=true\",\n            \"-Daeron.archive.dir.delete.on.start=true\",\n            \"-Daeron.archive.max.catalog.entries=128\",\n            \"-Daeron.term.buffer.sparse.file=true\",\n            \"-Daeron.perform.storage.checks=false\",\n            \"-Daeron.term.buffer.length=64k\",\n            \"-Daeron.ipc.term.buffer.length=64k\",\n            \"-Daeron.threading.mode=SHARED\",\n            \"-Daeron.shared.idle.strategy=yield\",\n            \"-Daeron.archive.threading.mode=SHARED\",\n            \"-Daeron.archive.idle.strategy=yield\",\n            \"-Daeron.archive.recording.events.enabled=false\",\n            \"-Daeron.driver.termination.validator=io.aeron.driver.DefaultAllowTerminationValidator\",\n            \"-Daeron.archive.authenticator.supplier=io.aeron.samples.archive.SampleAuthenticatorSupplier\",\n            \"-Daeron.enable.experimental.features=true\",\n            \"-Daeron.spies.simulate.connection=true\",\n            maxConcurrentReplaysArg.c_str(),\n            segmentLength.c_str(),\n            archiveIdArg.c_str(),\n            controlChannelArg.c_str(),\n            replicationChannelArg.c_str(),\n            \"-Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0\",\n            archiveDirArg.c_str(),\n            archiveMarkFileDirArg.c_str(),\n            aeronDirArg.c_str(),\n            \"-cp\",\n            m_aeronAllJar.c_str(),\n            \"io.aeron.archive.ArchivingMediaDriver\",\n            nullptr\n        };\n        m_process_handle = -1;\n\n#if defined(_WIN32)\n        m_process_handle = _spawnv(P_NOWAIT, m_java.c_str(), &argv[0]);\n#else\n        if (0 != posix_spawn(&m_process_handle, m_java.c_str(), nullptr, nullptr, (char *const *)&argv[0], nullptr))\n        {\n            perror(\"spawn\");\n            ::exit(EXIT_FAILURE);\n        }\n#endif\n\n        if (m_process_handle < 0)\n        {\n            perror(\"spawn\");\n            ::exit(EXIT_FAILURE);\n        }\n\n        m_pid = m_process_handle;\n#ifdef _WIN32\n        m_pid = GetProcessId((HANDLE)m_process_handle);\n#endif\n\n        const std::string mark_file = aeronDir + std::string(1, AERON_FILE_SEP) + \"archive-mark.dat\";\n\n        // await mark file creation as an indicator that Archive process is running\n        while (true)\n        {\n            int64_t file_length = aeron_file_length(mark_file.c_str());\n            if (file_length >= ARCHIVE_MARK_FILE_HEADER_LENGTH)\n            {\n                break;\n            }\n            aeron_micro_sleep(1000);\n        }\n        m_stream << aeron_epoch_clock() << \" [SetUp] ArchivingMediaDriver PID \" << m_pid << std::endl;\n    }\n\n    ~TestArchive()\n    {\n        if (m_process_handle > 0)\n        {\n            m_stream << aeron_epoch_clock() << \" [TearDown] Shutting down ArchivingMediaDriver PID \" << m_pid << std::endl;\n\n            bool archive_terminated = false;\n#ifndef _WIN32\n            if (0 == kill(m_process_handle, SIGTERM))\n            {\n                m_stream << aeron_epoch_clock() << \" [TearDown] waiting for ArchivingMediaDriver termination...\" << std::endl;\n                await_process_terminated(m_process_handle);\n                m_stream << aeron_epoch_clock() << \" [TearDown] ArchivingMediaDriver terminated\" << std::endl;\n                archive_terminated = true;\n            }\n#endif\n\n            if (!archive_terminated)\n            {\n                const std::string aeronPath = m_aeronDir;\n                const std::string cncFilename = aeronPath + std::string(1, AERON_FILE_SEP) + \"cnc.dat\";\n\n                if (aeron_context_request_driver_termination(aeronPath.c_str(), nullptr, 0))\n                {\n                    m_stream << aeron_epoch_clock() << \" [TearDown] Waiting for driver termination\" << std::endl;\n\n                    while (aeron_file_length(cncFilename.c_str()) > 0)\n                    {\n                        aeron_micro_sleep(1000);\n                    }\n\n                    m_stream << aeron_epoch_clock() << \" [TearDown] CnC file no longer exists\" << std::endl;\n\n                    await_process_terminated(m_process_handle);\n                    m_stream << aeron_epoch_clock() << \" [TearDown] Driver terminated\" << std::endl;\n                    archive_terminated = true;\n                }\n                else\n                {\n                    m_stream << aeron_epoch_clock() << \" [TearDown] Failed to send driver terminate command\" << std::endl;\n                }\n            }\n\n            if (archive_terminated && aeron_is_directory(m_archiveDir.c_str()) >= 0)\n            {\n                m_stream << aeron_epoch_clock() << \" [TearDown] Deleting \" << m_archiveDir << std::endl;\n                if (aeron_delete_directory(m_archiveDir.c_str()) != 0)\n                {\n                    m_stream << aeron_epoch_clock() << \" [TearDown] Failed to delete \" << m_archiveDir << std::endl;\n                }\n            }\n            m_stream.flush();\n        }\n    }\n\nprivate:\n    const std::string m_java = JAVA_EXECUTABLE;          // Defined in CMakeLists.txt\n    const std::string m_aeronAllJar = AERON_ALL_JAR;     // Defined in CMakeLists.txt\n    const std::string m_aeronAgentJar = \"-javaagent:\" AERON_AGENT_JAR; // Defined in CMakeLists.txt\n    const std::string m_archiveDir;\n    const std::string m_aeronDir;\n    std::ostream &m_stream;\n    pid_t m_process_handle = -1;\n    pid_t m_pid = 0;\n};\n\n#endif //AERON_TESTARCHIVE_H\n"
  },
  {
    "path": "aeron-archive/src/test/c/client/aeron_archive_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"gtest/gtest.h\"\n#include <gmock/gmock-matchers.h>\n\n#ifdef _MSC_VER\n#define AERON_FILE_SEP '\\\\'\n#else\n#define AERON_FILE_SEP '/'\n#endif\n\nextern \"C\"\n{\n#include <inttypes.h>\n#include \"aeronc.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_client.h\"\n#include \"aeron_counter.h\"\n#include \"aeron_counters.h\"\n#include \"client/aeron_archive.h\"\n#include \"client/aeron_archive_client.h\"\n#include \"client/aeron_archive_client_version.h\"\n#include \"client/aeron_archive_context.h\"\n#include \"uri/aeron_uri_string_builder.h\"\n#include \"util/aeron_env.h\"\n}\n\n#include \"../TestArchive.h\"\n#include \"aeron_archive_client/controlResponse.h\"\n\ntesting::AssertionResult EqualOrErrmsg(const int x, const int y)\n{\n    if (x == y)\n    {\n        return testing::AssertionSuccess();\n    }\n    else\n    {\n        return testing::AssertionFailure() << aeron_errmsg();\n    }\n}\n\n#define ASSERT_EQ_ERR(__x, __y) ASSERT_TRUE(EqualOrErrmsg((__x), (__y)))\n\ntypedef struct fragment_handler_clientd_stct\n{\n    size_t received;\n    int64_t position;\n}\nfragment_handler_clientd_t;\n\nvoid fragment_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_stct *header)\n{\n    auto *cd = (fragment_handler_clientd_t *)clientd;\n    cd->received++;\n    cd->position = aeron_header_position(header);\n}\n\ntypedef struct credentials_supplier_clientd_stct\n{\n    aeron_archive_encoded_credentials_t *credentials;\n    aeron_archive_encoded_credentials_t *on_challenge_credentials;\n}\ncredentials_supplier_clientd_t;\n\naeron_archive_encoded_credentials_t default_creds = { \"admin:admin\", 11 };\naeron_archive_encoded_credentials_t bad_creds = { \"admin:NotAdmin\", 14 };\n\ncredentials_supplier_clientd_t default_creds_clientd = { &default_creds, nullptr };\n\nstatic aeron_archive_encoded_credentials_t *encoded_credentials_supplier(void *clientd)\n{\n    return ((credentials_supplier_clientd_t *)clientd)->credentials;\n}\n\nstatic aeron_archive_encoded_credentials_t *encoded_credentials_on_challenge(\n    aeron_archive_encoded_credentials_t *encoded_challenge, void *clientd)\n{\n    return ((credentials_supplier_clientd_t *)clientd)->on_challenge_credentials;\n}\n\ntypedef struct recording_signal_consumer_clientd_stct\n{\n    std::set<std::int32_t> signals;\n}\nrecording_signal_consumer_clientd_t;\n\nvoid recording_signal_consumer(\n    aeron_archive_recording_signal_t *signal,\n    void *clientd)\n{\n    auto cd = (recording_signal_consumer_clientd_t *)clientd;\n    cd->signals.insert(signal->recording_signal_code);\n}\n\nclass AeronCArchiveTestBase\n{\npublic:\n    ~AeronCArchiveTestBase()\n    {\n        if (m_debug)\n        {\n            std::cout << m_stream.str();\n        }\n    }\n\n    void DoSetUp(std::int64_t archiveId = 42, std::int32_t maxConcurrentReplays = 100)\n    {\n        char aeron_dir[AERON_MAX_PATH];\n        aeron_default_path(aeron_dir, sizeof(aeron_dir));\n\n        std::string sourceArchiveDir = m_archiveDir + AERON_FILE_SEP + \"source\";\n        m_testArchive = std::make_shared<TestArchive>(\n            aeron_dir,\n            sourceArchiveDir,\n            std::cout,\n            \"aeron:udp?endpoint=localhost:8010\",\n            \"aeron:udp?endpoint=localhost:0\",\n            archiveId,\n            maxConcurrentReplays);\n    }\n\n    void DoTearDown()\n    {\n        if (nullptr != m_archive)\n        {\n            ASSERT_EQ_ERR(0, aeron_archive_close(m_archive));\n        }\n        if (nullptr != m_ctx)\n        {\n            ASSERT_EQ_ERR(0, aeron_archive_context_close(m_ctx));\n        }\n        if (nullptr != m_dest_archive)\n        {\n            ASSERT_EQ_ERR(0, aeron_archive_close(m_dest_archive));\n        }\n        if (nullptr != m_dest_ctx)\n        {\n            ASSERT_EQ_ERR(0, aeron_archive_context_close(m_dest_ctx));\n        }\n    }\n\n    void idle()\n    {\n        aeron_idle_strategy_sleeping_idle((void *)&m_idle_duration_ns, 0);\n\n        if (m_invoke_on_idle)\n        {\n            aeron_main_do_work(m_aeron);\n        }\n    }\n\n    void connect(\n        void *recording_signal_consumer_clientd = nullptr,\n        const char *request_channel = \"aeron:udp?endpoint=localhost:8010\",\n        const char *response_channel = \"aeron:udp?endpoint=localhost:0\",\n        const char *client_name = \"\")\n    {\n        ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_client_name(m_ctx, client_name));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_ctx, request_channel));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(m_ctx, response_channel));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n            m_ctx,\n            encoded_credentials_supplier,\n            nullptr,\n            nullptr,\n            &default_creds_clientd));\n\n        if (nullptr != recording_signal_consumer_clientd)\n        {\n            ASSERT_EQ_ERR(0, aeron_archive_context_set_recording_signal_consumer(\n                m_ctx,\n                recording_signal_consumer,\n                recording_signal_consumer_clientd));\n        }\n\n        ASSERT_EQ_ERR(0, aeron_archive_connect(&m_archive, m_ctx));\n\n        m_aeron = aeron_archive_context_get_aeron(m_ctx);\n    }\n\n    aeron_subscription_t *addSubscription(std::string channel, int32_t stream_id)\n    {\n        aeron_async_add_subscription_t *async_add_subscription;\n        aeron_subscription_t *subscription = nullptr;\n\n        if (aeron_async_add_subscription(\n            &async_add_subscription,\n            m_aeron,\n            channel.c_str(),\n            stream_id,\n            nullptr,\n            nullptr,\n            nullptr,\n            nullptr) < 0)\n        {\n            fprintf(stderr, \" -- GOT AN ERROR :: %s\\n\", aeron_errmsg());\n        }\n\n        if (aeron_async_add_subscription_poll(&subscription, async_add_subscription) < 0)\n        {\n            fprintf(stderr, \" -- GOT AN ERROR :: %s\\n\", aeron_errmsg());\n        }\n        while (nullptr == subscription)\n        {\n            idle();\n            if (aeron_async_add_subscription_poll(&subscription, async_add_subscription) < 0)\n            {\n                fprintf(stderr, \" -- GOT AN ERROR :: %s\\n\", aeron_errmsg());\n            }\n        }\n\n        return subscription;\n    }\n\n    aeron_publication_t *addPublication(std::string channel, int32_t stream_id)\n    {\n        aeron_async_add_publication_t *async_add_publication;\n        aeron_publication_t *publication = nullptr;\n\n        if (aeron_async_add_publication(\n            &async_add_publication,\n            m_aeron,\n            channel.c_str(),\n            stream_id) < 0)\n        {\n            fprintf(stderr, \" -- GOT AN ERROR :: %s\\n\", aeron_errmsg());\n        }\n\n        if (aeron_async_add_publication_poll(&publication, async_add_publication) < 0)\n        {\n            fprintf(stderr, \" -- GOT AN ERROR :: %s\\n\", aeron_errmsg());\n        }\n        while (nullptr == publication)\n        {\n            idle();\n            if (aeron_async_add_publication_poll(&publication, async_add_publication) < 0)\n            {\n                fprintf(stderr, \" -- GOT AN ERROR :: %s\\n\", aeron_errmsg());\n            }\n        }\n\n        return publication;\n    }\n\n    void setupCounters(int32_t session_id)\n    {\n        m_counters_reader = aeron_counters_reader(m_aeron);\n        m_counter_id = getRecordingCounterId(session_id, m_counters_reader);\n        m_recording_id_from_counter = aeron_archive_recording_pos_get_recording_id(m_counters_reader, m_counter_id);\n    }\n\n    void waitUntilCaughtUp(int64_t position)\n    {\n        while (*aeron_counters_reader_addr(m_counters_reader, m_counter_id) < position)\n        {\n            idle();\n        }\n    }\n\n    static int32_t getRecordingCounterId(int32_t session_id, aeron_counters_reader_t *counters_reader)\n    {\n        int32_t counter_id;\n\n        while (AERON_NULL_COUNTER_ID ==\n            (counter_id = aeron_archive_recording_pos_find_counter_id_by_session_id(counters_reader, session_id)))\n        {\n            std::this_thread::yield();\n        }\n\n        return counter_id;\n    }\n\n    static void offerMessages(\n        aeron_publication_t *publication,\n        size_t message_count = 10,\n        size_t start_count = 0,\n        const char *message_prefix = \"Message \")\n    {\n        for (size_t i = 0; i < message_count; i++)\n        {\n            size_t index = i + start_count;\n            char message[1000];\n            size_t len = snprintf(message, 1000, \"%s%zu\", message_prefix, index);\n\n            while (aeron_publication_offer(publication, (uint8_t *)message, len, nullptr, nullptr) < 0)\n            {\n                aeron_idle_strategy_yielding_idle(nullptr, 0);\n            }\n        }\n    }\n\n    static void offerMessagesToPosition(\n        aeron_publication_t *publication,\n        int64_t minimum_position,\n        const char *message_prefix = \"Message \")\n    {\n        for (size_t i = 0; aeron_publication_position(publication) < minimum_position; i++)\n        {\n            char message[1000];\n            size_t len = snprintf(message, 1000, \"%s%zu\", message_prefix, i);\n\n            while (aeron_publication_offer(publication, (uint8_t *)message, len, nullptr, nullptr) < 0)\n            {\n                aeron_idle_strategy_yielding_idle(nullptr, 0);\n            }\n        }\n    }\n\n    static void offerMessages(\n        aeron_exclusive_publication_t *exclusive_publication,\n        size_t message_count = 10,\n        size_t start_count = 0,\n        const char *message_prefix = \"Message \")\n    {\n        for (size_t i = 0; i < message_count; i++)\n        {\n            size_t index = i + start_count;\n            char message[1000];\n            size_t len = snprintf(message, 1000, \"%s%zu\", message_prefix, index);\n\n            while (aeron_exclusive_publication_offer(exclusive_publication, (uint8_t *)message, len, nullptr, nullptr) < 0)\n            {\n                aeron_idle_strategy_yielding_idle(nullptr, 0);\n            }\n        }\n    }\n\n    static void consumeMessages(\n        aeron_subscription_t *subscription,\n        size_t message_count = 10)\n    {\n        fragment_handler_clientd_t clientd;\n        clientd.received = 0;\n\n        while (clientd.received < message_count)\n        {\n            if (0 == aeron_subscription_poll(subscription, fragment_handler, (void *)&clientd, 10))\n            {\n                aeron_idle_strategy_yielding_idle(nullptr, 0);\n            }\n        }\n\n        ASSERT_EQ(clientd.received, message_count);\n    }\n\n    static int64_t consumeMessagesExpectingBound(\n        aeron_subscription_t *subscription,\n        int64_t bound_position,\n        int64_t timeout_ms)\n    {\n        fragment_handler_clientd_t clientd;\n        clientd.received = 0;\n        clientd.position = 0;\n\n        int64_t deadline_ms = aeron_epoch_clock() + timeout_ms;\n\n        while (aeron_epoch_clock() < deadline_ms)\n        {\n            if (0 == aeron_subscription_poll(subscription, fragment_handler, (void *)&clientd, 10))\n            {\n                aeron_idle_strategy_yielding_idle(nullptr, 0);\n            }\n        }\n\n        return clientd.position;\n    }\n\n    bool attemptReplayMerge(\n        aeron_archive_replay_merge_t *replay_merge,\n        aeron_publication_t *publication,\n        aeron_fragment_handler_t handler,\n        void *clientd,\n        size_t total_message_count,\n        size_t *messages_published,\n        size_t *received_message_count,\n        const char *message_prefix = \"Message \")\n    {\n        for (size_t i = *messages_published; i < total_message_count; i++)\n        {\n            char message[1000];\n            size_t len = snprintf(message, 1000, \"%s%zu\", message_prefix, i);\n\n            while (aeron_publication_offer(publication, (uint8_t *)message, len, nullptr, nullptr) < 0)\n            {\n                idle();\n\n                int fragments = aeron_archive_replay_merge_poll(replay_merge, handler, clientd, m_fragment_limit);\n\n                if (0 == fragments && aeron_archive_replay_merge_has_failed(replay_merge))\n                {\n                    return false;\n                }\n            }\n\n            (*messages_published)++;\n        }\n\n        while (!aeron_archive_replay_merge_is_merged(replay_merge))\n        {\n            int fragments = aeron_archive_replay_merge_poll(replay_merge, handler, clientd, m_fragment_limit);\n\n            if (0 == fragments && aeron_archive_replay_merge_has_failed(replay_merge))\n            {\n                return false;\n            }\n\n            idle();\n        }\n\n        aeron_image_t *image = aeron_archive_replay_merge_image(replay_merge);\n        while (*received_message_count < total_message_count)\n        {\n            int fragments = aeron_image_poll(image, handler, clientd, m_fragment_limit);\n\n            if (0 == fragments && aeron_image_is_closed(image))\n            {\n                return false;\n            }\n\n            idle();\n        }\n\n        return true;\n    }\n\n    void startDestArchive()\n    {\n        char aeron_dir[AERON_MAX_PATH];\n        aeron_default_path(aeron_dir, sizeof(aeron_dir));\n        std::string dest_aeron_dir = std::string(aeron_dir) + \"_dest\";\n\n        const std::string archiveDir = m_archiveDir + AERON_FILE_SEP + \"dest\";\n        const std::string controlChannel = \"aeron:udp?endpoint=localhost:8011\";\n        const std::string replicationChannel = \"aeron:udp?endpoint=localhost:8012\";\n\n        m_destTestArchive = std::make_shared<TestArchive>(\n            dest_aeron_dir, archiveDir, m_stream, controlChannel, replicationChannel, -7777);\n\n        ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_dest_ctx));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_dest_ctx, controlChannel.c_str()));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(m_dest_ctx, \"aeron:udp?endpoint=localhost:0\"));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n            m_dest_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n            m_dest_ctx,\n            encoded_credentials_supplier,\n            nullptr,\n            nullptr,\n            &default_creds_clientd));\n        ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_dest_ctx, controlChannel.c_str()));\n    }\n\n    void recordData(\n        bool tryStop,\n        int64_t *recording_id,\n        int64_t *stop_position,\n        int64_t *halfway_position,\n        size_t message_count = 1000)\n    {\n        int64_t subscription_id;\n        ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n            &subscription_id,\n            m_archive,\n            m_recordingChannel.c_str(),\n            m_recordingStreamId,\n            AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n            false));\n\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        int32_t session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n        *recording_id = m_recording_id_from_counter;\n\n        bool is_active;\n        ASSERT_EQ_ERR(0, aeron_archive_recording_pos_is_active(\n            &is_active,\n            m_counters_reader,\n            m_counter_id,\n            m_recording_id_from_counter));\n        EXPECT_TRUE(is_active);\n\n        EXPECT_EQ(m_counter_id, aeron_archive_recording_pos_find_counter_id_by_recording_id(\n            m_counters_reader,\n            m_recording_id_from_counter));\n\n        {\n            size_t sib_len = AERON_COUNTER_MAX_LABEL_LENGTH;\n            const char source_identity_buffer[AERON_COUNTER_MAX_LABEL_LENGTH] = { '\\0' };\n\n            ASSERT_EQ_ERR(0,\n                aeron_archive_recording_pos_get_source_identity(\n                    m_counters_reader,\n                    m_counter_id,\n                    source_identity_buffer,\n                    &sib_len));\n            EXPECT_EQ(9, sib_len);\n            EXPECT_STREQ(\"aeron:ipc\", source_identity_buffer);\n        }\n\n        int64_t half_count = message_count / 2;\n\n        offerMessages(publication, half_count);\n        *halfway_position = aeron_publication_position(publication);\n        offerMessages(publication, half_count, half_count);\n        consumeMessages(subscription, message_count);\n\n        *stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(*stop_position);\n\n        if (tryStop)\n        {\n            bool stopped;\n            ASSERT_EQ_ERR(0, aeron_archive_try_stop_recording_subscription(\n                &stopped,\n                m_archive,\n                subscription_id));\n            EXPECT_TRUE(stopped);\n        }\n        else\n        {\n            ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n                m_archive,\n                subscription_id));\n        }\n    }\n\nprotected:\n    const std::string m_archiveDir = ARCHIVE_DIR;\n\n    const std::string m_recordingChannel = \"aeron:udp?endpoint=localhost:3333\";\n    const std::int32_t m_recordingStreamId = 33;\n    const std::string m_replayChannel = \"aeron:udp?endpoint=localhost:6666\";\n    const std::int32_t m_replayStreamId = 66;\n\n    const int m_fragment_limit = 10;\n\n    aeron_counters_reader_t *m_counters_reader;\n    std::int32_t m_counter_id;\n    std::int64_t m_recording_id_from_counter;\n\n    std::ostringstream m_stream;\n\n    std::shared_ptr<TestArchive> m_testArchive;\n    std::shared_ptr<TestArchive> m_destTestArchive;\n\n    bool m_debug = true;\n\n    const uint64_t m_idle_duration_ns = UINT64_C(1000) * UINT64_C(1000); /* 1ms */\n\n    aeron_archive_context_t *m_ctx = nullptr;\n    aeron_archive_t *m_archive = nullptr;\n    aeron_t *m_aeron = nullptr;\n\n    aeron_archive_context_t *m_dest_ctx = nullptr;\n    aeron_archive_t *m_dest_archive = nullptr;\n\n    bool m_invoke_on_idle = false;\n};\n\nclass AeronCArchiveTest : public AeronCArchiveTestBase, public testing::Test\n{\npublic:\n    void SetUp() final\n    {\n        DoSetUp();\n    }\n\n    void TearDown() final\n    {\n        DoTearDown();\n    }\n};\n\nclass AeronCArchiveParamTest : public AeronCArchiveTestBase, public testing::TestWithParam<bool>\n{\npublic:\n    void SetUp() final\n    {\n        DoSetUp();\n    }\n\n    void TearDown() final\n    {\n        DoTearDown();\n    }\n};\n\nINSTANTIATE_TEST_SUITE_P(AeronCArchive, AeronCArchiveParamTest, testing::Values(true, false));\n\nclass AeronCArchiveIdTest : public AeronCArchiveTestBase, public testing::Test\n{\n};\n\ntypedef struct recording_descriptor_consumer_clientd_stct\n{\n    bool verify_recording_id = false;\n    int64_t recording_id;\n    bool verify_stream_id = false;\n    int32_t stream_id;\n    bool verify_start_equals_stop_position = false;\n    bool verify_session_id = false;\n    int32_t session_id;\n    const char *original_channel = nullptr;\n    std::set<std::int32_t> session_ids;\n    aeron_archive_recording_descriptor_t last_descriptor;\n    char last_descriptor_stripped_channel[AERON_URI_MAX_LENGTH];\n    char last_descriptor_source_identity[512];\n}\nrecording_descriptor_consumer_clientd_t;\n\nstatic void recording_descriptor_consumer(\n    aeron_archive_recording_descriptor_t *descriptor,\n    void *clientd)\n{\n    auto *cd = (recording_descriptor_consumer_clientd_t *)clientd;\n\n    if (cd->verify_recording_id)\n    {\n        EXPECT_EQ(cd->recording_id, descriptor->recording_id);\n    }\n    if (cd->verify_stream_id)\n    {\n        EXPECT_EQ(cd->stream_id, descriptor->stream_id);\n    }\n    if (cd->verify_start_equals_stop_position)\n    {\n        EXPECT_EQ(descriptor->start_position, descriptor->stop_position);\n    }\n    if (cd->verify_session_id)\n    {\n        EXPECT_EQ(cd->session_id, descriptor->session_id);\n    }\n    if (nullptr != cd->original_channel)\n    {\n        EXPECT_EQ(strlen(cd->original_channel), descriptor->original_channel_length);\n        EXPECT_STREQ(cd->original_channel, descriptor->original_channel);\n    }\n\n    cd->session_ids.insert(descriptor->session_id);\n\n    memcpy(&cd->last_descriptor, descriptor, sizeof(aeron_archive_recording_descriptor_t));\n    strcpy(cd->last_descriptor_stripped_channel, descriptor->stripped_channel);\n    strcpy(cd->last_descriptor_source_identity, descriptor->source_identity);\n}\n\nstruct SubscriptionDescriptor\n{\n    const std::int64_t m_controlSessionId;\n    const std::int64_t m_correlationId;\n    const std::int64_t m_subscriptionId;\n    const std::int32_t m_streamId;\n\n    SubscriptionDescriptor(\n        std::int64_t controlSessionId,\n        std::int64_t correlationId,\n        std::int64_t subscriptionId,\n        std::int32_t streamId) :\n        m_controlSessionId(controlSessionId),\n        m_correlationId(correlationId),\n        m_subscriptionId(subscriptionId),\n        m_streamId(streamId)\n    {\n    }\n};\n\nstruct subscription_descriptor_consumer_clientd\n{\n    std::vector<SubscriptionDescriptor> descriptors;\n};\n\nstatic void recording_subscription_descriptor_consumer(\n    aeron_archive_recording_subscription_descriptor_t *descriptor,\n    void *clientd)\n{\n    auto cd = (subscription_descriptor_consumer_clientd *)clientd;\n    cd->descriptors.emplace_back(\n        descriptor->control_session_id,\n        descriptor->correlation_id,\n        descriptor->subscription_id,\n        descriptor->stream_id);\n}\n\nTEST_F(AeronCArchiveTest, shouldAsyncConnectToArchive)\n{\n    aeron_archive_context_t *ctx;\n    aeron_archive_async_connect_t *async;\n    aeron_archive_t *archive = nullptr;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_async_connect(&async, ctx));\n\n    // the ctx passed into async_connect gets duplicated, so it should be safe to delete it now\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx));\n\n    ASSERT_EQ_ERR(0, aeron_archive_async_connect_poll(&archive, async));\n\n    while (nullptr == archive)\n    {\n        idle();\n\n        ASSERT_NE(-1, aeron_archive_async_connect_poll(&archive, async));\n    }\n\n    ctx = aeron_archive_get_archive_context(archive);\n    ASSERT_TRUE(aeron_archive_context_get_owns_aeron_client(ctx));\n\n    aeron_subscription_t *subscription = aeron_archive_get_control_response_subscription(archive);\n    ASSERT_TRUE(aeron_subscription_is_connected(subscription));\n\n    ASSERT_EQ(42, aeron_archive_get_archive_id(archive));\n\n    ASSERT_EQ_ERR(0, aeron_archive_close(archive));\n}\n\nTEST_F(AeronCArchiveTest, shouldAsyncConnectToArchiveWithPrebuiltAeron)\n{\n\n    aeron_archive_context_t *ctx;\n    aeron_archive_async_connect_t *async;\n    aeron_archive_t *archive = nullptr;\n\n    aeron_context_t *aeron_ctx;\n    aeron_t *aeron;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n\n    ASSERT_EQ_ERR(0, aeron_context_init(&aeron_ctx));\n    ASSERT_EQ_ERR(0, aeron_context_set_dir(aeron_ctx, aeron_archive_context_get_aeron_directory_name(ctx)));\n    ASSERT_EQ_ERR(0, aeron_init(&aeron, aeron_ctx));\n    ASSERT_EQ_ERR(0, aeron_start(aeron));\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_aeron(ctx, aeron));\n    ASSERT_EQ_ERR(0, aeron_archive_async_connect(&async, ctx));\n\n    // the ctx passed into async_connect gets duplicated, so it should be safe to delete it now\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx));\n\n    ASSERT_EQ_ERR(0, aeron_archive_async_connect_poll(&archive, async));\n\n    while (nullptr == archive)\n    {\n        idle();\n\n        ASSERT_NE(-1, aeron_archive_async_connect_poll(&archive, async));\n    }\n\n    ctx = aeron_archive_get_archive_context(archive);\n    ASSERT_FALSE(aeron_archive_context_get_owns_aeron_client(ctx));\n\n    aeron_subscription_t *subscription = aeron_archive_get_control_response_subscription(archive);\n    ASSERT_TRUE(aeron_subscription_is_connected(subscription));\n\n    ASSERT_EQ(42, aeron_archive_get_archive_id(archive));\n\n    ASSERT_EQ_ERR(0, aeron_archive_close(archive));\n\n    ASSERT_EQ_ERR(0, aeron_close(aeron));\n    ASSERT_EQ_ERR(0, aeron_context_close(aeron_ctx));\n}\n\nTEST_F(AeronCArchiveTest, shouldHandleNullCredentialsSupplier)\n{\n    aeron_archive_context_t *ctx;\n    aeron_archive_async_connect_t *async;\n    aeron_archive_t *archive = nullptr;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        nullptr,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_async_connect(&async, ctx));\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx));\n\n    while (-1 != aeron_archive_async_connect_poll(&archive, async))\n    {\n        idle();\n    }\n\n    ASSERT_EQ(EINVAL, aeron_errcode());\n    auto errorMsg = std::string(aeron_errmsg());\n    ASSERT_NE(std::string::npos, errorMsg.find(\"authentication rejected\"));\n}\n\nTEST_F(AeronCArchiveTest, shouldHandleNullDataInTheEncodedCredentials)\n{\n    aeron_archive_context_t *ctx;\n    aeron_archive_async_connect_t *async;\n    aeron_archive_t *archive = nullptr;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n\n    aeron_archive_encoded_credentials_t null_creds = {nullptr, 7 /* fake value */ };\n    credentials_supplier_clientd_t credentials_clientd = { &null_creds, nullptr };\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &credentials_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_async_connect(&async, ctx));\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx));\n\n    while (-1 != aeron_archive_async_connect_poll(&archive, async))\n    {\n        idle();\n    }\n\n    ASSERT_EQ(EINVAL, aeron_errcode());\n    auto errorMsg = std::string(aeron_errmsg());\n    ASSERT_NE(std::string::npos, errorMsg.find(\"authentication rejected\"));\n}\n\nTEST_F(AeronCArchiveTest, shouldConnectToArchive)\n{\n    aeron_archive_context_t *ctx;\n    aeron_archive_t *archive = nullptr;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&archive, ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx));\n\n    ctx = aeron_archive_get_archive_context(archive);\n    ASSERT_TRUE(aeron_archive_context_get_owns_aeron_client(ctx));\n\n    aeron_subscription_t *subscription = aeron_archive_get_control_response_subscription(archive);\n    ASSERT_TRUE(aeron_subscription_is_connected(subscription));\n\n    ASSERT_EQ(42, aeron_archive_get_archive_id(archive));\n\n    ASSERT_EQ_ERR(0, aeron_archive_close(archive));\n}\n\nTEST_F(AeronCArchiveTest, shouldConnectToArchiveWithPrebuiltAeron)\n{\n    aeron_archive_context_t *ctx;\n    aeron_archive_t *archive = nullptr;\n\n    aeron_context_t *aeron_ctx;\n    aeron_t *aeron;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n\n    ASSERT_EQ_ERR(0, aeron_context_init(&aeron_ctx));\n    ASSERT_EQ_ERR(0, aeron_context_set_dir(aeron_ctx, aeron_archive_context_get_aeron_directory_name(ctx)));\n    ASSERT_EQ_ERR(0, aeron_init(&aeron, aeron_ctx));\n    ASSERT_EQ_ERR(0, aeron_start(aeron));\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_aeron(ctx, aeron));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&archive, ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx));\n\n    ctx = aeron_archive_get_archive_context(archive);\n    ASSERT_FALSE(aeron_archive_context_get_owns_aeron_client(ctx));\n\n    aeron_subscription_t *subscription = aeron_archive_get_control_response_subscription(archive);\n    ASSERT_TRUE(aeron_subscription_is_connected(subscription));\n\n    ASSERT_EQ(42, aeron_archive_get_archive_id(archive));\n\n    ASSERT_EQ_ERR(0, aeron_archive_close(archive));\n\n    ASSERT_EQ_ERR(0, aeron_close(aeron));\n    ASSERT_EQ_ERR(0, aeron_context_close(aeron_ctx));\n}\n\nvoid invoker_func(void *clientd)\n{\n    *(bool *)clientd = true;\n}\n\nTEST_F(AeronCArchiveTest, shouldConnectToArchiveAndCallInvoker)\n{\n    aeron_archive_context_t *ctx;\n    aeron_archive_t *archive = nullptr;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    bool invokerCalled = false;\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_delegating_invoker(\n        ctx,\n        invoker_func,\n        &invokerCalled));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&archive, ctx));\n    ASSERT_TRUE(invokerCalled);\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx));\n\n    ctx = aeron_archive_get_archive_context(archive);\n    ASSERT_TRUE(aeron_archive_context_get_owns_aeron_client(ctx));\n\n    aeron_subscription_t *subscription = aeron_archive_get_control_response_subscription(archive);\n    ASSERT_TRUE(aeron_subscription_is_connected(subscription));\n\n    ASSERT_EQ(42, aeron_archive_get_archive_id(archive));\n\n    ASSERT_EQ_ERR(0, aeron_archive_close(archive));\n}\n\nTEST_F(AeronCArchiveTest, shouldConnectFromTwoClientsUsingIpc)\n{\n    aeron_archive_context_t *ctx1, *ctx2;\n    aeron_archive_t *archive1 = nullptr, *archive2 = nullptr;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx1));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx1, \"aeron:ipc\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx1, \"aeron:ipc\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx1, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx1,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&archive1, ctx1));\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx1));\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx2));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx2, \"aeron:ipc\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx2, \"aeron:ipc\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx2, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx2,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&archive2, ctx2));\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx2));\n\n    ASSERT_EQ(42, aeron_archive_get_archive_id(archive1));\n    ASSERT_EQ(42, aeron_archive_get_archive_id(archive2));\n    ctx1 = aeron_archive_get_archive_context(archive1);\n    ctx2 = aeron_archive_get_archive_context(archive2);\n    const auto *requestChannel1 = aeron_archive_context_get_control_request_channel(ctx1);\n    aeron_uri_t reqChannel1;\n    ASSERT_EQ(0, aeron_uri_parse(strlen(requestChannel1), requestChannel1, &reqChannel1));\n    const auto *responseChannel1 = aeron_archive_context_get_control_response_channel(ctx1);\n    aeron_uri_t respChannel1;\n    ASSERT_EQ(0, aeron_uri_parse(strlen(responseChannel1), responseChannel1, &respChannel1));\n    const char *sessionId1 = aeron_uri_find_param_value(&reqChannel1.params.ipc.additional_params, AERON_URI_SESSION_ID_KEY);\n    ASSERT_STREQ(sessionId1, aeron_uri_find_param_value(&respChannel1.params.ipc.additional_params, AERON_URI_SESSION_ID_KEY));\n    aeron_uri_close(&reqChannel1);\n    aeron_uri_close(&respChannel1);\n\n    const auto *requestChannel2 = aeron_archive_context_get_control_request_channel(ctx2);\n    aeron_uri_t reqChannel2;\n    ASSERT_EQ(0, aeron_uri_parse(strlen(requestChannel2), requestChannel2, &reqChannel2));\n    const auto *responseChannel2 = aeron_archive_context_get_control_response_channel(ctx2);\n    aeron_uri_t respChannel2;\n    ASSERT_EQ(0, aeron_uri_parse(strlen(responseChannel2), responseChannel2, &respChannel2));\n    const char *sessionId2 = aeron_uri_find_param_value(&reqChannel2.params.ipc.additional_params, AERON_URI_SESSION_ID_KEY);\n    ASSERT_STREQ(sessionId2, aeron_uri_find_param_value(&respChannel2.params.ipc.additional_params, AERON_URI_SESSION_ID_KEY));\n    aeron_uri_close(&reqChannel2);\n    aeron_uri_close(&respChannel2);\n\n    ASSERT_STRNE(sessionId1, sessionId2);\n\n    ASSERT_EQ_ERR(0, aeron_archive_close(archive1));\n    ASSERT_EQ_ERR(0, aeron_archive_close(archive2));\n}\n\nTEST_F(AeronCArchiveTest, shouldObserveErrorOnBadDataOnControlResponseChannel)\n{\n    aeron_archive_context_t *ctx;\n    aeron_archive_t *archive = nullptr;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&archive, ctx));\n\n    EXPECT_FALSE(aeron_archive_context_get_owns_aeron_client(ctx));\n\n    aeron_subscription_t *subscription = aeron_archive_get_control_response_subscription(archive);\n    EXPECT_TRUE(aeron_subscription_is_connected(subscription));\n\n    int64_t found_start_position;\n    EXPECT_EQ(-1, aeron_archive_get_start_position(&found_start_position, archive, INT64_MAX));\n    EXPECT_NE(\n        std::string::npos,\n        std::string(aeron_errmsg()).find(\"errorCode=5, error: unknown recording id: 9223372036854775807\"));\n\n    EXPECT_EQ(0, aeron_archive_close(archive));\n    EXPECT_EQ(0, aeron_archive_context_close(ctx));\n}\n\ntypedef struct error_handler_clientd_stct\n{\n    bool called;\n    int err_code;\n    char message[1000];\n}\nerror_handler_clientd_t;\n\nvoid error_handler(void *clientd, int errcode, const char *message)\n{\n    auto *ehc = (error_handler_clientd_t *)clientd;\n    ehc->called = true;\n    ehc->err_code = errcode;\n    snprintf(ehc->message, sizeof(ehc->message), \"%s\", message);\n}\n\nTEST_F(AeronCArchiveTest, shouldCallErrorHandlerOnError)\n{\n    aeron_archive_context_t *ctx;\n    aeron_archive_t *archive = nullptr;\n    error_handler_clientd_t ehc;\n\n    ehc.called = false;\n    ehc.message[0] = '\\0';\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_error_handler(ctx, error_handler, &ehc));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_message_timeout_ns(ctx, 500000000));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&archive, ctx));\n\n    EXPECT_TRUE(aeron_archive_proxy_get_start_position(archive->archive_proxy, 1000, 12345));\n\n    int64_t found_start_position;\n    EXPECT_EQ(-1, aeron_archive_poll_for_response(\n        &found_start_position, archive, \"AeronArchive::getStartPosition\", 2222));\n\n    EXPECT_TRUE(ehc.called);\n    EXPECT_EQ(AERON_ERROR_CODE_GENERIC_ERROR, ehc.err_code);\n    EXPECT_STREQ(\"response for correlationId=1000, errorCode=5, error: unknown recording id: 12345\", ehc.message);\n\n    EXPECT_EQ(0, aeron_archive_close(archive));\n    EXPECT_EQ(0, aeron_archive_context_close(ctx));\n}\n\nTEST_F(AeronCArchiveTest, shouldRecordPublicationAndFindRecording)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    connect();\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n\n        int64_t found_recording_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n            &found_recording_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_recording_position);\n\n        int64_t found_stop_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n            &found_stop_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(AERON_NULL_VALUE, found_stop_position);\n\n        int64_t found_max_recorded_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_max_recorded_position(\n            &found_max_recorded_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_max_recorded_position);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    int64_t found_recording_id;\n    const char *channel_fragment = \"endpoint=localhost:3333\";\n    ASSERT_EQ_ERR(0, aeron_archive_find_last_matching_recording(\n        &found_recording_id,\n        m_archive,\n        0,\n        channel_fragment,\n        m_recordingStreamId,\n        session_id));\n\n    EXPECT_EQ(m_recording_id_from_counter, found_recording_id);\n\n    int64_t found_stop_position;\n    ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n        &found_stop_position,\n        m_archive,\n        m_recording_id_from_counter));\n    EXPECT_EQ(stop_position, found_stop_position);\n\n    int32_t count;\n\n    recording_descriptor_consumer_clientd_t clientd;\n    clientd.verify_recording_id = true;\n    clientd.recording_id = found_recording_id;\n    clientd.verify_stream_id = true;\n    clientd.stream_id = m_recordingStreamId;\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording(\n        &count,\n        m_archive,\n        found_recording_id,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(1, count);\n}\n\nTEST_F(AeronCArchiveTest, shouldRecordPublicationAndTryStopById)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    connect();\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n\n        int64_t found_recording_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n            &found_recording_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_recording_position);\n\n        int64_t found_stop_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n            &found_stop_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(AERON_NULL_VALUE, found_stop_position);\n\n        int64_t found_max_recorded_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_max_recorded_position(\n            &found_max_recorded_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_max_recorded_position);\n    }\n\n    bool stopped;\n    EXPECT_EQ(-1, aeron_archive_try_stop_recording_by_identity(\n        &stopped,\n        m_archive,\n        m_recording_id_from_counter + 5)); // invalid recording id\n\n    ASSERT_EQ_ERR(0, aeron_archive_try_stop_recording_by_identity(\n        &stopped,\n        m_archive,\n        m_recording_id_from_counter));\n    EXPECT_TRUE(stopped);\n\n    int64_t found_recording_id;\n    const char *channel_fragment = \"endpoint=localhost:3333\";\n    ASSERT_EQ_ERR(0, aeron_archive_find_last_matching_recording(\n        &found_recording_id,\n        m_archive,\n        0,\n        channel_fragment,\n        m_recordingStreamId,\n        session_id));\n\n    EXPECT_EQ(m_recording_id_from_counter, found_recording_id);\n\n    int64_t found_stop_position;\n    ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n        &found_stop_position,\n        m_archive,\n        m_recording_id_from_counter));\n    EXPECT_EQ(stop_position, found_stop_position);\n}\n\nTEST_F(AeronCArchiveTest, shouldRecordThenReplay)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    connect();\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        bool is_active;\n        ASSERT_EQ_ERR(0, aeron_archive_recording_pos_is_active(\n            &is_active,\n            m_counters_reader,\n            m_counter_id,\n            m_recording_id_from_counter));\n        EXPECT_TRUE(is_active);\n\n        EXPECT_EQ(m_counter_id, aeron_archive_recording_pos_find_counter_id_by_recording_id(\n            m_counters_reader,\n            m_recording_id_from_counter));\n\n        {\n            size_t sib_len = AERON_COUNTER_MAX_LABEL_LENGTH;\n            const char source_identity_buffer[AERON_COUNTER_MAX_LABEL_LENGTH] = { '\\0' };\n\n            ASSERT_EQ_ERR(0,\n                aeron_archive_recording_pos_get_source_identity(\n                    m_counters_reader,\n                    m_counter_id,\n                    source_identity_buffer,\n                    &sib_len));\n            EXPECT_EQ(9, sib_len);\n            EXPECT_STREQ(\"aeron:ipc\", source_identity_buffer);\n        }\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    int64_t found_stop_position;\n    ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n        &found_stop_position,\n        m_archive,\n        m_recording_id_from_counter));\n    while (found_stop_position != stop_position)\n    {\n        idle();\n\n        ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n            &found_stop_position,\n            m_archive,\n            m_recording_id_from_counter));\n    }\n\n    {\n        int64_t position = 0;\n        int64_t length = stop_position - position;\n\n        aeron_subscription_t *subscription = addSubscription(m_replayChannel, m_replayStreamId);\n\n        aeron_archive_replay_params_t replay_params;\n        aeron_archive_replay_params_init(&replay_params);\n\n        replay_params.position = position;\n        replay_params.length = length;\n        replay_params.file_io_max_length = 4096;\n\n        ASSERT_EQ_ERR(0, aeron_archive_start_replay(\n            nullptr,\n            m_archive,\n            m_recording_id_from_counter,\n            m_replayChannel.c_str(),\n            m_replayStreamId,\n            &replay_params));\n\n        consumeMessages(subscription);\n\n        aeron_image_t *image = aeron_subscription_image_at_index(subscription, 0);\n        ASSERT_EQ(stop_position, aeron_image_position(image));\n    }\n}\n\nTEST_F(AeronCArchiveTest, shouldRecordThenBoundedReplay)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    connect();\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    {\n\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    const char *counter_name = \"BoundedTestCounter\";\n\n    aeron_async_add_counter_t *async_add_counter;\n    ASSERT_EQ_ERR(0, aeron_async_add_counter(\n        &async_add_counter,\n        m_aeron,\n        10001,\n        (const uint8_t *)counter_name,\n        strlen(counter_name),\n        counter_name,\n        strlen(counter_name)));\n\n    aeron_counter_t *counter = nullptr;\n    aeron_async_add_counter_poll(&counter, async_add_counter);\n    while (nullptr == counter)\n    {\n        idle();\n        aeron_async_add_counter_poll(&counter, async_add_counter);\n    }\n\n    int64_t found_stop_position;\n    ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n        &found_stop_position,\n        m_archive,\n        m_recording_id_from_counter));\n    while (found_stop_position != stop_position)\n    {\n        idle();\n\n        ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n            &found_stop_position,\n            m_archive,\n            m_recording_id_from_counter));\n    }\n\n    {\n        int64_t position = 0;\n        int64_t length = stop_position - position;\n        int64_t bounded_length = (length / 4) * 3;\n        aeron_counter_set_release(aeron_counter_addr(counter), bounded_length);\n\n        aeron_subscription_t *subscription = addSubscription(m_replayChannel, m_replayStreamId);\n\n        aeron_archive_replay_params_t replay_params;\n        aeron_archive_replay_params_init(&replay_params);\n\n        replay_params.position = position;\n        replay_params.length = length;\n        replay_params.bounding_limit_counter_id = counter->counter_id;\n        replay_params.file_io_max_length = 4096;\n\n        ASSERT_EQ_ERR(0, aeron_archive_start_replay(\n            nullptr,\n            m_archive,\n            m_recording_id_from_counter,\n            m_replayChannel.c_str(),\n            m_replayStreamId,\n            &replay_params));\n\n        int64_t position_consumed = consumeMessagesExpectingBound(\n            subscription, position + bounded_length, 1000);\n\n        EXPECT_LT(position + (length / 2), position_consumed);\n        EXPECT_LE(position_consumed, position + bounded_length);\n    }\n}\n\nTEST_F(AeronCArchiveTest, shouldRecordThenReplayThenTruncate)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    connect();\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n\n        int64_t found_recording_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n            &found_recording_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_recording_position);\n\n        int64_t found_stop_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n            &found_stop_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(AERON_NULL_VALUE, found_stop_position);\n\n        int64_t found_max_recorded_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_max_recorded_position(\n            &found_max_recorded_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_max_recorded_position);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    int64_t found_recording_id;\n    const char *channel_fragment = \"endpoint=localhost:3333\";\n    ASSERT_EQ_ERR(0, aeron_archive_find_last_matching_recording(\n        &found_recording_id,\n        m_archive,\n        0,\n        channel_fragment,\n        m_recordingStreamId,\n        session_id));\n\n    EXPECT_EQ(m_recording_id_from_counter, found_recording_id);\n\n    int64_t found_stop_position;\n    ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n        &found_stop_position,\n        m_archive,\n        m_recording_id_from_counter));\n    EXPECT_EQ(stop_position, found_stop_position);\n\n    int64_t position = 0;\n\n    {\n        int64_t length = stop_position - position;\n\n        aeron_archive_replay_params_t replay_params;\n        aeron_archive_replay_params_init(&replay_params);\n\n        replay_params.position = position;\n        replay_params.length = length;\n        replay_params.file_io_max_length = 4096;\n\n        aeron_subscription_t *subscription;\n\n        ASSERT_EQ_ERR(0, aeron_archive_replay(\n            &subscription,\n            m_archive,\n            m_recording_id_from_counter,\n            m_replayChannel.c_str(),\n            m_replayStreamId,\n            &replay_params));\n\n        consumeMessages(subscription);\n\n        aeron_image_t *image = aeron_subscription_image_at_index(subscription, 0);\n        EXPECT_EQ(stop_position, aeron_image_position(image));\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_truncate_recording(\n        nullptr,\n        m_archive,\n        m_recording_id_from_counter,\n        position));\n\n    int32_t count;\n\n    recording_descriptor_consumer_clientd_t clientd;\n    clientd.verify_start_equals_stop_position = true;\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording(\n        &count,\n        m_archive,\n        found_recording_id,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(1, count);\n}\n\nTEST_F(AeronCArchiveTest, shouldRecordAndCancelReplayEarly)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    connect();\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n\n        aeron_publication_t *publication;\n        ASSERT_EQ_ERR(0, aeron_archive_add_recorded_publication(\n            &publication,\n            m_archive,\n            m_recordingChannel.c_str(),\n            m_recordingStreamId));\n\n        {\n            aeron_publication_t *duplicate_publication;\n            EXPECT_EQ(-1, aeron_archive_add_recorded_publication(\n                &duplicate_publication,\n                m_archive,\n                m_recordingChannel.c_str(),\n                m_recordingStreamId));\n        }\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n\n        int64_t found_recording_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n            &found_recording_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_recording_position);\n\n        ASSERT_EQ_ERR(0, aeron_archive_stop_recording_publication(m_archive, publication));\n\n        ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n            &found_recording_position,\n            m_archive,\n            m_recording_id_from_counter));\n        while (AERON_NULL_VALUE != found_recording_position)\n        {\n            idle();\n\n            ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n                &found_recording_position,\n                m_archive,\n                m_recording_id_from_counter));\n        }\n    }\n\n    const int64_t position = 0;\n    const int64_t length = stop_position - position;\n    int64_t replay_session_id;\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = position;\n    replay_params.length = length;\n    replay_params.file_io_max_length = 4096;\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_replay(\n        &replay_session_id,\n        m_archive,\n        m_recording_id_from_counter,\n        m_replayChannel.c_str(),\n        m_replayStreamId,\n        &replay_params));\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_replay(m_archive, replay_session_id));\n}\n\nTEST_F(AeronCArchiveTest, shouldRecordAndCancelReplayEarlyWithExclusivePublication)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    connect();\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n\n        aeron_exclusive_publication_t *exclusive_publication;\n        ASSERT_EQ_ERR(0, aeron_archive_add_recorded_exclusive_publication(\n            &exclusive_publication,\n            m_archive,\n            m_recordingChannel.c_str(),\n            m_recordingStreamId));\n\n        aeron_publication_constants_t constants;\n        aeron_exclusive_publication_constants(exclusive_publication, &constants);\n        session_id = constants.session_id;\n\n        setupCounters(session_id);\n\n        offerMessages(exclusive_publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_exclusive_publication_position(exclusive_publication);\n\n        waitUntilCaughtUp(stop_position);\n\n        int64_t found_recording_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n            &found_recording_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_recording_position);\n\n        ASSERT_EQ_ERR(0, aeron_archive_stop_recording_exclusive_publication(m_archive, exclusive_publication));\n\n        ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n            &found_recording_position,\n            m_archive,\n            m_recording_id_from_counter));\n        while (AERON_NULL_VALUE != found_recording_position)\n        {\n            idle();\n\n            ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n                &found_recording_position,\n                m_archive,\n                m_recording_id_from_counter));\n        }\n    }\n\n    const int64_t position = 0;\n    const int64_t length = stop_position - position;\n    int64_t replay_session_id;\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = position;\n    replay_params.length = length;\n    replay_params.file_io_max_length = 4096;\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_replay(\n        &replay_session_id,\n        m_archive,\n        m_recording_id_from_counter,\n        m_replayChannel.c_str(),\n        m_replayStreamId,\n        &replay_params));\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_replay(m_archive, replay_session_id));\n}\n\nTEST_F(AeronCArchiveTest, shouldGetStartPosition)\n{\n    int32_t session_id;\n\n    connect();\n\n    aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n    aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n    session_id = aeron_publication_session_id(publication);\n\n    offerMessages(publication);\n    consumeMessages(subscription);\n\n    int64_t halfway_position = aeron_publication_position(publication);\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    setupCounters(session_id);\n\n    offerMessages(publication);\n    consumeMessages(subscription);\n\n    int64_t end_position = aeron_publication_position(publication);\n\n    waitUntilCaughtUp(end_position);\n\n    int64_t found_start_position;\n    ASSERT_EQ_ERR(0, aeron_archive_get_start_position(\n        &found_start_position,\n        m_archive,\n        m_recording_id_from_counter));\n    ASSERT_EQ(found_start_position, halfway_position);\n}\n\nTEST_F(AeronCArchiveTest, shouldReplayRecordingFromLateJoinPosition)\n{\n    int32_t session_id;\n\n    connect();\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        int64_t current_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(current_position);\n\n        aeron_archive_replay_params_t replay_params;\n        aeron_archive_replay_params_init(&replay_params);\n\n        replay_params.position = current_position;\n        replay_params.file_io_max_length = 4096;\n\n        aeron_subscription_t *replay_subscription;\n\n        ASSERT_EQ_ERR(0, aeron_archive_replay(\n            &replay_subscription,\n            m_archive,\n            m_recording_id_from_counter,\n            m_replayChannel.c_str(),\n            m_replayStreamId,\n            &replay_params));\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n        consumeMessages(replay_subscription);\n\n        int64_t end_position = aeron_publication_position(publication);\n\n        aeron_image_t *image = aeron_subscription_image_at_index(replay_subscription, 0);\n        EXPECT_EQ(end_position, aeron_image_position(image));\n    }\n}\n\nTEST_F(AeronCArchiveTest, shouldListRegisteredRecordingSubscriptions)\n{\n    subscription_descriptor_consumer_clientd clientd;\n\n    int32_t expected_stream_id = 7;\n    const char *channelOne = \"aeron:ipc\";\n    const char *channelTwo = \"aeron:udp?endpoint=localhost:5678\";\n    const char *channelThree = \"aeron:udp?endpoint=localhost:4321\";\n\n    connect();\n\n    int64_t subscription_id_one;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id_one,\n        m_archive,\n        channelOne,\n        expected_stream_id,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    int64_t subscription_id_two;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id_two,\n        m_archive,\n        channelTwo,\n        expected_stream_id + 1,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    int64_t subscription_id_three;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id_three,\n        m_archive,\n        channelThree,\n        expected_stream_id + 2,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    const auto pub2 = addPublication(channelTwo, expected_stream_id + 1);\n    const auto pub3 = addPublication(channelThree, expected_stream_id + 2);\n\n    // await recording started\n    auto countersReader = aeron_counters_reader(m_aeron);\n    const auto sub2CounterId =\n        getRecordingCounterId(aeron_publication_session_id(pub2), countersReader);\n    const auto sub3CounterId = getRecordingCounterId(aeron_publication_session_id(pub3), countersReader);\n\n    int32_t count_one;\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording_subscriptions(\n        &count_one,\n        m_archive,\n        0,\n        5,\n        \"ipc\",\n        expected_stream_id,\n        true,\n        recording_subscription_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(1, clientd.descriptors.size());\n    EXPECT_EQ(1, count_one);\n\n    clientd.descriptors.clear();\n\n    int32_t count_two;\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording_subscriptions(\n        &count_two,\n        m_archive,\n        0,\n        5,\n        \"\",\n        expected_stream_id,\n        false,\n        recording_subscription_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(3, clientd.descriptors.size());\n    EXPECT_EQ(3, count_two);\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id_two));\n    clientd.descriptors.clear();\n\n    // await recording stopped\n    int state;\n    while (true)\n    {\n        EXPECT_EQ(0, aeron_counters_reader_counter_state(countersReader, sub2CounterId, &state));\n        if (AERON_COUNTER_RECORD_ALLOCATED != state)\n        {\n            break;\n        }\n        std::this_thread::yield();\n    }\n\n    EXPECT_EQ(0, aeron_counters_reader_counter_state(countersReader, sub3CounterId, &state));\n    EXPECT_EQ(AERON_COUNTER_RECORD_ALLOCATED, state);\n\n    int32_t count_three;\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording_subscriptions(\n        &count_three,\n        m_archive,\n        0,\n        5,\n        \"\",\n        expected_stream_id,\n        false,\n        recording_subscription_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(2, clientd.descriptors.size());\n    EXPECT_EQ(2, count_three);\n}\n\nTEST_F(AeronCArchiveTest, shouldMergeFromReplayToLive)\n{\n    const std::size_t termLength = 64 * 1024;\n    const std::string message_prefix = \"Message \";\n    const std::size_t min_messages_per_term = termLength / (message_prefix.length() + AERON_DATA_HEADER_LENGTH);\n    const char *control_endpoint = \"localhost:23265\";\n    const char *recording_endpoint = \"localhost:23266\";\n    const char *live_endpoint = \"localhost:23267\";\n    const char *replay_endpoint = \"localhost:0\";\n\n    char publication_channel[AERON_URI_MAX_LENGTH];\n    char live_destination[AERON_URI_MAX_LENGTH];\n    char replay_destination[AERON_URI_MAX_LENGTH];\n    char recording_channel[AERON_URI_MAX_LENGTH];\n    char subscription_channel[AERON_URI_MAX_LENGTH];\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_CONTROL_KEY, control_endpoint);\n        aeron_uri_string_builder_put(\n            &builder, AERON_UDP_CHANNEL_CONTROL_MODE_KEY, AERON_UDP_CHANNEL_CONTROL_MODE_DYNAMIC_VALUE);\n        aeron_uri_string_builder_put(&builder, AERON_URI_FC_KEY, \"tagged,g:99901/1,t:5s\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_TERM_LENGTH_KEY, termLength);\n\n        aeron_uri_string_builder_sprint(&builder, publication_channel, sizeof(publication_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, live_endpoint);\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_CONTROL_KEY, control_endpoint);\n\n        aeron_uri_string_builder_sprint(&builder, live_destination, sizeof(live_destination));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, replay_endpoint);\n\n        aeron_uri_string_builder_sprint(&builder, replay_destination, sizeof(replay_destination));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    const size_t initial_message_count = min_messages_per_term * 3;\n    const size_t subsequent_message_count = min_messages_per_term * 3;\n    const size_t total_message_count = min_messages_per_term + subsequent_message_count;\n\n    connect();\n\n    aeron_publication_t *publication = addPublication(publication_channel, m_recordingStreamId);\n\n    int32_t session_id = aeron_publication_session_id(publication);\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_URI_GTAG_KEY, \"99901\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_SESSION_ID_KEY, session_id);\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, recording_endpoint);\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_CONTROL_KEY, control_endpoint);\n\n        aeron_uri_string_builder_sprint(&builder, recording_channel, sizeof(recording_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(\n            &builder, AERON_UDP_CHANNEL_CONTROL_MODE_KEY, AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL_VALUE);\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_SESSION_ID_KEY, session_id);\n\n        aeron_uri_string_builder_sprint(&builder, subscription_channel, sizeof(subscription_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        nullptr,\n        m_archive,\n        recording_channel,\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_REMOTE,\n        true));\n\n    setupCounters(session_id);\n\n    bool is_active;\n    ASSERT_EQ_ERR(0, aeron_archive_recording_pos_is_active(\n        &is_active,\n        m_counters_reader,\n        m_counter_id,\n        m_recording_id_from_counter));\n    EXPECT_TRUE(is_active);\n\n    ASSERT_EQ_ERR(m_counter_id, aeron_archive_recording_pos_find_counter_id_by_recording_id(\n        m_counters_reader,\n        m_recording_id_from_counter));\n\n    {\n        size_t sib_len = AERON_COUNTER_MAX_LABEL_LENGTH;\n        const char source_identity_buffer[AERON_COUNTER_MAX_LABEL_LENGTH] = { '\\0' };\n\n        ASSERT_EQ_ERR(0,\n            aeron_archive_recording_pos_get_source_identity(\n                m_counters_reader,\n                m_counter_id,\n                source_identity_buffer,\n                &sib_len));\n        EXPECT_STREQ(\"127.0.0.1:23265\", source_identity_buffer);\n    }\n\n    offerMessages(publication, initial_message_count);\n\n    waitUntilCaughtUp(aeron_publication_position(publication));\n\n    size_t messages_published = initial_message_count;\n\n    fragment_handler_clientd_t clientd;\n    clientd.received = 0;\n    clientd.position = 0;\n\n    while (true)\n    {\n        aeron_subscription_t *subscription = addSubscription(subscription_channel, m_recordingStreamId);\n\n        char replay_channel[AERON_URI_MAX_LENGTH];\n        {\n            aeron_uri_string_builder_t builder;\n\n            aeron_uri_string_builder_init_new(&builder);\n\n            aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n            aeron_uri_string_builder_put_int32(&builder, AERON_URI_SESSION_ID_KEY, session_id);\n\n            aeron_uri_string_builder_sprint(&builder, replay_channel, sizeof(replay_channel));\n            aeron_uri_string_builder_close(&builder);\n        }\n\n        aeron_archive_replay_merge_t *replay_merge;\n\n        ASSERT_EQ_ERR(0, aeron_archive_replay_merge_init(\n            &replay_merge,\n            subscription,\n            m_archive,\n            replay_channel,\n            replay_destination,\n            live_destination,\n            m_recording_id_from_counter,\n            clientd.position,\n            aeron_epoch_clock(),\n            REPLAY_MERGE_PROGRESS_TIMEOUT_DEFAULT_MS));\n\n        if (attemptReplayMerge(\n            replay_merge,\n            publication,\n            fragment_handler,\n            &clientd,\n            total_message_count,\n            &messages_published,\n            &clientd.received))\n        {\n            ASSERT_EQ_ERR(0, aeron_archive_replay_merge_close(replay_merge));\n            break;\n        }\n\n        ASSERT_EQ_ERR(0, aeron_archive_replay_merge_close(replay_merge));\n        idle();\n    }\n\n    EXPECT_EQ(clientd.received, total_message_count);\n    EXPECT_EQ(clientd.position, aeron_publication_position(publication));\n}\n\nTEST_F(AeronCArchiveTest, shouldFailForIncorrectInitialCredentials)\n{\n    credentials_supplier_clientd_t bad_creds_clientd = { &bad_creds, nullptr };\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        m_ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &bad_creds_clientd));\n\n    ASSERT_EQ(-1, aeron_archive_connect(&m_archive, m_ctx));\n}\n\nTEST_F(AeronCArchiveTest, shouldBeAbleToHandleBeingChallenged)\n{\n    aeron_archive_encoded_credentials_t creds = { \"admin:adminC\", 12 };\n    aeron_archive_encoded_credentials_t challenge_creds = { \"admin:CSadmin\", 13 };\n    credentials_supplier_clientd_t creds_clientd = { &creds, &challenge_creds };\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(m_ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        m_ctx,\n        encoded_credentials_supplier,\n        encoded_credentials_on_challenge,\n        nullptr,\n        &creds_clientd));\n\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_archive, m_ctx));\n}\n\nTEST_F(AeronCArchiveTest, shouldExceptionForIncorrectChallengeCredentials)\n{\n    aeron_archive_encoded_credentials_t creds = { \"admin:adminC\", 12 };\n    aeron_archive_encoded_credentials_t bad_challenge_creds = { \"admin:adminNoCS\", 15 };\n    credentials_supplier_clientd_t creds_clientd = { &creds, &bad_challenge_creds };\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        m_ctx,\n        encoded_credentials_supplier,\n        encoded_credentials_on_challenge,\n        nullptr,\n        &creds_clientd));\n\n    ASSERT_EQ(-1, aeron_archive_connect(&m_archive, m_ctx));\n}\n\nTEST_F(AeronCArchiveTest, shouldPurgeStoppedRecording)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    connect();\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n\n        int64_t found_recording_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n            &found_recording_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_recording_position);\n\n        int64_t found_stop_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n            &found_stop_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(AERON_NULL_VALUE, found_stop_position);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    int64_t found_recording_id;\n    const char *channel_fragment = \"endpoint=localhost:3333\";\n    ASSERT_EQ_ERR(0, aeron_archive_find_last_matching_recording(\n        &found_recording_id,\n        m_archive,\n        0,\n        channel_fragment,\n        m_recordingStreamId,\n        session_id));\n\n    EXPECT_EQ(m_recording_id_from_counter, found_recording_id);\n\n    int64_t found_stop_position;\n    ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n        &found_stop_position,\n        m_archive,\n        m_recording_id_from_counter));\n    EXPECT_EQ(stop_position, found_stop_position);\n\n    int64_t deleted_segments_count;\n    ASSERT_EQ_ERR(0, aeron_archive_purge_recording(&deleted_segments_count, m_archive, m_recording_id_from_counter));\n    EXPECT_EQ(1, deleted_segments_count);\n\n    int32_t count = 1234; // <-- just to make sure later when it's zero it's because it was explicitly set to 0.\n\n    recording_descriptor_consumer_clientd_t clientd;\n    clientd.verify_start_equals_stop_position = true;\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording(\n        &count,\n        m_archive,\n        found_recording_id,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(0, count);\n}\n\nTEST_F(AeronCArchiveTest, shouldReadRecordingDescriptor)\n{\n    int32_t session_id;\n\n    connect();\n\n    aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n    session_id = aeron_publication_session_id(publication);\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    setupCounters(session_id);\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    int32_t count = 1234;\n\n    recording_descriptor_consumer_clientd_t clientd;\n    clientd.verify_recording_id = true;\n    clientd.recording_id = m_recording_id_from_counter;\n    clientd.verify_stream_id = true;\n    clientd.stream_id = m_recordingStreamId;\n    clientd.verify_session_id = true;\n    clientd.session_id = session_id;\n    clientd.original_channel = m_recordingChannel.c_str();\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording(\n        &count,\n        m_archive,\n        m_recording_id_from_counter,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(1, count);\n}\n\nTEST_F(AeronCArchiveTest, shouldFindMultipleRecordingDescriptors)\n{\n    aeron_publication_t *publication;\n    int32_t session_id;\n    int64_t subscription_id;\n    int64_t subscription_id2;\n\n    std::set<std::int32_t> session_ids;\n\n    connect();\n\n    publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n    session_id = aeron_publication_session_id(publication);\n    session_ids.insert(session_id);\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    setupCounters(session_id);\n\n    const std::string recordingChannel2 = \"aeron:udp?endpoint=localhost:3334\";\n    publication = addPublication(recordingChannel2, m_recordingStreamId);\n\n    session_id = aeron_publication_session_id(publication);\n    session_ids.insert(session_id);\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id2,\n        m_archive,\n        recordingChannel2.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    setupCounters(session_id);\n\n    int32_t count = 1234;\n\n    recording_descriptor_consumer_clientd_t clientd;\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recordings(\n        &count,\n        m_archive,\n        INT64_MIN,\n        10,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(2, count);\n    EXPECT_EQ(session_ids, clientd.session_ids);\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recordings(\n        &count,\n        m_archive,\n        INT64_MIN,\n        1,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(1, count);\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id2));\n}\n\nTEST_F(AeronCArchiveTest, shouldFindRecordingDescriptorForUri)\n{\n    aeron_publication_t *publication;\n    int32_t session_id;\n    int64_t subscription_id;\n    int64_t subscription_id2;\n\n    std::set<std::int32_t> session_ids;\n\n    connect();\n\n    publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n    session_id = aeron_publication_session_id(publication);\n    session_ids.insert(session_id);\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    setupCounters(session_id);\n\n    const std::string recordingChannel2 = \"aeron:udp?endpoint=localhost:3334\";\n    publication = addPublication(recordingChannel2, m_recordingStreamId);\n\n    session_id = aeron_publication_session_id(publication);\n    session_ids.insert(session_id);\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id2,\n        m_archive,\n        recordingChannel2.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    setupCounters(session_id);\n\n    int32_t count = 1234;\n\n    recording_descriptor_consumer_clientd_t clientd;\n\n    clientd.verify_session_id = true;\n    clientd.session_id = session_id;\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recordings_for_uri(\n        &count,\n        m_archive,\n        INT64_MIN,\n        2,\n        \"3334\",\n        m_recordingStreamId,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(1, count);\n\n    clientd.verify_session_id = false;\n    clientd.session_ids.clear();\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recordings_for_uri(\n        &count,\n        m_archive,\n        INT64_MIN,\n        10,\n        \"333\",\n        m_recordingStreamId,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(2, count);\n    EXPECT_EQ(session_ids, clientd.session_ids);\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recordings_for_uri(\n        &count,\n        m_archive,\n        INT64_MIN,\n        10,\n        \"no-match\",\n        m_recordingStreamId,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(0, count);\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id2));\n}\n\nTEST_F(AeronCArchiveTest, shouldReadJumboRecordingDescriptor)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    std::string recordingChannel = \"aeron:udp?endpoint=localhost:3333|term-length=64k|alias=\";\n    recordingChannel.append(2000, 'X');\n\n    connect();\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    {\n        aeron_subscription_t *subscription = addSubscription(recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n\n        int64_t found_recording_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_recording_position(\n            &found_recording_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(stop_position, found_recording_position);\n\n        int64_t found_stop_position;\n        ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n            &found_stop_position,\n            m_archive,\n            m_recording_id_from_counter));\n        EXPECT_EQ(AERON_NULL_VALUE, found_stop_position);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    int64_t found_stop_position;\n    ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n        &found_stop_position,\n        m_archive,\n        m_recording_id_from_counter));\n    EXPECT_EQ(stop_position, found_stop_position);\n\n    int32_t count = 1234;\n\n    recording_descriptor_consumer_clientd_t clientd;\n    clientd.verify_recording_id = true;\n    clientd.recording_id = m_recording_id_from_counter;\n    clientd.verify_stream_id = true;\n    clientd.stream_id = m_recordingStreamId;\n    clientd.original_channel = recordingChannel.c_str();\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording(\n        &count,\n        m_archive,\n        m_recording_id_from_counter,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(1, count);\n}\n\nTEST_F(AeronCArchiveTest, shouldRecordReplicateThenReplay)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    startDestArchive();\n\n    recording_signal_consumer_clientd_t rsc_cd;\n    rsc_cd.signals.clear();\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_recording_signal_consumer(\n        m_dest_ctx, recording_signal_consumer, &rsc_cd));\n\n    connect();\n\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_dest_archive, m_dest_ctx));\n\n    ASSERT_EQ(42, aeron_archive_get_archive_id(m_archive));\n    ASSERT_EQ(-7777, aeron_archive_get_archive_id(m_dest_archive));\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        bool is_active;\n        ASSERT_EQ_ERR(0, aeron_archive_recording_pos_is_active(\n            &is_active,\n            m_counters_reader,\n            m_counter_id,\n            m_recording_id_from_counter));\n        EXPECT_TRUE(is_active);\n\n        EXPECT_EQ(m_counter_id, aeron_archive_recording_pos_find_counter_id_by_recording_id(\n            m_counters_reader,\n            m_recording_id_from_counter));\n\n        {\n            size_t sib_len = AERON_COUNTER_MAX_LABEL_LENGTH;\n            const char source_identity_buffer[AERON_COUNTER_MAX_LABEL_LENGTH] = { '\\0' };\n\n            ASSERT_EQ_ERR(0,\n                aeron_archive_recording_pos_get_source_identity(\n                    m_counters_reader,\n                    m_counter_id,\n                    source_identity_buffer,\n                    &sib_len));\n            EXPECT_EQ(9, sib_len);\n            EXPECT_STREQ(\"aeron:ipc\", source_identity_buffer);\n        }\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    int64_t found_stop_position;\n    do {\n        ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n            &found_stop_position,\n            m_archive,\n            m_recording_id_from_counter));\n\n        idle();\n    }\n    while (found_stop_position != stop_position);\n\n    aeron_archive_replication_params_t replication_params;\n    aeron_archive_replication_params_init(&replication_params);\n\n    replication_params.encoded_credentials = &default_creds;\n\n    ASSERT_EQ_ERR(0, aeron_archive_replicate(\n        nullptr,\n        m_dest_archive,\n        m_recording_id_from_counter,\n        aeron_archive_context_get_control_request_channel(m_ctx),\n        aeron_archive_context_get_control_request_stream_id(m_ctx),\n        &replication_params));\n\n    while (0 == rsc_cd.signals.count(AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_SYNC))\n    {\n        aeron_archive_poll_for_recording_signals(nullptr, m_dest_archive);\n\n        idle();\n    }\n\n    int64_t position = 0;\n    int64_t length = stop_position - position;\n\n    aeron_subscription_t *subscription = addSubscription(m_replayChannel, m_replayStreamId);\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = position;\n    replay_params.length = length;\n    replay_params.file_io_max_length = 4096;\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_replay(\n        nullptr,\n        m_dest_archive,\n        m_recording_id_from_counter,\n        m_replayChannel.c_str(),\n        m_replayStreamId,\n        &replay_params));\n\n    consumeMessages(subscription);\n\n    aeron_image_t *image = aeron_subscription_image_at_index(subscription, 0);\n    EXPECT_EQ(stop_position, aeron_image_position(image));\n}\n\nTEST_P(AeronCArchiveParamTest, shouldRecordReplicateThenStop)\n{\n    bool tryStop = GetParam();\n\n    int32_t session_id;\n    int64_t stop_position;\n\n    startDestArchive();\n\n    recording_signal_consumer_clientd_t rsc_cd;\n    rsc_cd.signals.clear();\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_recording_signal_consumer(\n        m_dest_ctx, recording_signal_consumer, &rsc_cd));\n\n    connect();\n\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_dest_archive, m_dest_ctx));\n\n    ASSERT_EQ(42, aeron_archive_get_archive_id(m_archive));\n    ASSERT_EQ(-7777, aeron_archive_get_archive_id(m_dest_archive));\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n    aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n    session_id = aeron_publication_session_id(publication);\n\n    setupCounters(session_id);\n\n    offerMessages(publication);\n    consumeMessages(subscription);\n\n    stop_position = aeron_publication_position(publication);\n\n    waitUntilCaughtUp(stop_position);\n\n    aeron_archive_replication_params_t replication_params;\n    aeron_archive_replication_params_init(&replication_params);\n\n    replication_params.encoded_credentials = &default_creds;\n\n    int64_t replication_id;\n    ASSERT_EQ_ERR(0, aeron_archive_replicate(\n        &replication_id,\n        m_dest_archive,\n        m_recording_id_from_counter,\n        aeron_archive_context_get_control_request_channel(m_ctx),\n        aeron_archive_context_get_control_request_stream_id(m_ctx),\n        &replication_params));\n\n    while (\n        0 == rsc_cd.signals.count(AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_REPLICATE) ||\n        0 == rsc_cd.signals.count(AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_EXTEND))\n    {\n        aeron_archive_poll_for_recording_signals(nullptr, m_dest_archive);\n\n        idle();\n    }\n\n    int64_t position = 0;\n\n    aeron_subscription_t *replay_subscription = addSubscription(m_replayChannel, m_replayStreamId);\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = position;\n    replay_params.file_io_max_length = 4096;\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_replay(\n        nullptr,\n        m_dest_archive,\n        m_recording_id_from_counter,\n        m_replayChannel.c_str(),\n        m_replayStreamId,\n        &replay_params));\n\n    consumeMessages(replay_subscription);\n\n    if (tryStop)\n    {\n        bool stopped;\n        ASSERT_EQ_ERR(0, aeron_archive_try_stop_replication(&stopped, m_dest_archive, replication_id));\n        ASSERT_TRUE(stopped);\n    }\n    else\n    {\n        ASSERT_EQ_ERR(0, aeron_archive_stop_replication(m_dest_archive, replication_id));\n    }\n\n    offerMessages(publication);\n\n    ASSERT_EQ_ERR(0, consumeMessagesExpectingBound(replay_subscription, 0, 1000));\n\n    while (0 == rsc_cd.signals.count(AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_REPLICATE_END))\n    {\n        aeron_archive_poll_for_recording_signals(nullptr, m_dest_archive);\n\n        idle();\n    }\n\n    aeron_image_t *image = aeron_subscription_image_at_index(replay_subscription, 0);\n    EXPECT_EQ(stop_position, aeron_image_position(image));\n}\n\nTEST_F(AeronCArchiveTest, shouldRecordReplicateTwice)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    startDestArchive();\n\n    recording_signal_consumer_clientd_t rsc_cd;\n    rsc_cd.signals.clear();\n\n    ASSERT_EQ_ERR(\n        0, aeron_archive_context_set_recording_signal_consumer(m_dest_ctx, recording_signal_consumer, &rsc_cd));\n\n    connect();\n\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_dest_archive, m_dest_ctx));\n\n    ASSERT_EQ(42, aeron_archive_get_archive_id(m_archive));\n    ASSERT_EQ(-7777, aeron_archive_get_archive_id(m_dest_archive));\n\n    int64_t subscription_id;\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        &subscription_id,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n        false));\n\n    int64_t halfway_position;\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(m_recordingChannel, m_recordingStreamId);\n\n        session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        bool is_active;\n        ASSERT_EQ_ERR(0, aeron_archive_recording_pos_is_active(\n            &is_active,\n            m_counters_reader,\n            m_counter_id,\n            m_recording_id_from_counter));\n        EXPECT_TRUE(is_active);\n\n        EXPECT_EQ(m_counter_id, aeron_archive_recording_pos_find_counter_id_by_recording_id(\n            m_counters_reader,\n            m_recording_id_from_counter));\n\n        {\n            size_t sib_len = AERON_COUNTER_MAX_LABEL_LENGTH;\n            const char source_identity_buffer[AERON_COUNTER_MAX_LABEL_LENGTH] = { '\\0' };\n\n            ASSERT_EQ_ERR(0,\n                aeron_archive_recording_pos_get_source_identity(\n                    m_counters_reader,\n                    m_counter_id,\n                    source_identity_buffer,\n                    &sib_len));\n            EXPECT_EQ(9, sib_len);\n            EXPECT_STREQ(\"aeron:ipc\", source_identity_buffer);\n        }\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n        halfway_position = aeron_publication_position(publication);\n        waitUntilCaughtUp(halfway_position);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n        stop_position = aeron_publication_position(publication);\n        waitUntilCaughtUp(stop_position);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_recording_subscription(\n        m_archive,\n        subscription_id));\n\n    int64_t found_stop_position;\n    do\n    {\n        ASSERT_EQ_ERR(0, aeron_archive_get_stop_position(\n            &found_stop_position,\n            m_archive,\n            m_recording_id_from_counter));\n\n        idle();\n    }\n    while (found_stop_position != stop_position);\n\n    aeron_archive_replication_params_t replication_params1;\n    aeron_archive_replication_params_init(&replication_params1);\n\n    replication_params1.encoded_credentials = &default_creds;\n    replication_params1.stop_position = halfway_position;\n    replication_params1.replication_session_id = 1;\n\n    ASSERT_EQ_ERR(0, aeron_archive_replicate(\n        nullptr,\n        m_dest_archive,\n        m_recording_id_from_counter,\n        aeron_archive_context_get_control_request_channel(m_ctx),\n        aeron_archive_context_get_control_request_stream_id(m_ctx),\n        &replication_params1));\n\n    while (0 == rsc_cd.signals.count(AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_REPLICATE_END))\n    {\n        aeron_archive_poll_for_recording_signals(nullptr, m_dest_archive);\n\n        idle();\n    }\n\n    aeron_archive_replication_params_t replication_params2;\n    aeron_archive_replication_params_init(&replication_params2);\n\n    replication_params2.encoded_credentials = &default_creds;\n    replication_params2.replication_session_id = 2;\n\n    ASSERT_EQ_ERR(0, aeron_archive_replicate(\n        nullptr,\n        m_dest_archive,\n        m_recording_id_from_counter,\n        aeron_archive_context_get_control_request_channel(m_ctx),\n        aeron_archive_context_get_control_request_stream_id(m_ctx),\n        &replication_params2));\n\n    rsc_cd.signals.clear();\n\n    while (0 == rsc_cd.signals.count(AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_REPLICATE_END))\n    {\n        aeron_archive_poll_for_recording_signals(nullptr, m_dest_archive);\n\n        idle();\n    }\n}\n\nTEST_F(AeronCArchiveIdTest, shouldInitializeContextWithDefaultValues)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n\n    EXPECT_EQ(nullptr, m_ctx->aeron);\n    EXPECT_NE(nullptr, m_ctx->aeron_directory_name);\n    EXPECT_NE(0, strlen(m_ctx->aeron_directory_name));\n    EXPECT_FALSE(m_ctx->owns_aeron_client);\n\n    EXPECT_EQ(nullptr, m_ctx->control_request_channel);\n    EXPECT_EQ(AERON_ARCHIVE_CONTROL_STREAM_ID_DEFAULT, m_ctx->control_request_stream_id);\n\n    EXPECT_EQ(nullptr, m_ctx->control_response_channel);\n    EXPECT_EQ(AERON_ARCHIVE_CONTROL_RESPONSE_STREAM_ID_DEFAULT, m_ctx->control_response_stream_id);\n\n    EXPECT_EQ(nullptr, m_ctx->recording_events_channel);\n    EXPECT_EQ(AERON_ARCHIVE_RECORDING_EVENTS_STREAM_ID_DEFAULT, m_ctx->recording_events_stream_id);\n\n    EXPECT_EQ(AERON_ARCHIVE_MESSAGE_TIMEOUT_NS_DEFAULT, m_ctx->message_timeout_ns);\n    EXPECT_EQ(AERON_ARCHIVE_MESSAGE_RETRY_ATTEMPTS_DEFAULT, m_ctx->message_retry_attempts);\n\n    EXPECT_EQ(AERON_ARCHIVE_CONTROL_TERM_BUFFER_LENGTH_DEFAULT, m_ctx->control_term_buffer_length);\n    EXPECT_EQ(AERON_ARCHIVE_CONTROL_TERM_BUFFER_SPARSE_DEFAULT, m_ctx->control_term_buffer_sparse);\n    EXPECT_EQ(1408, m_ctx->control_mtu_length);\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n}\n\nTEST_F(AeronCArchiveIdTest, shouldInitializeContextWithValuesSpecifiedViaEnvironment)\n{\n    const auto aeron_dir = \"/dev/shm/aeron-test-dir\";\n    const auto control_channel = \"aeron:udp?endpoint=localhost:5555\";\n    const auto response_channel = \"aeron:udp?endpoint=localhost:0\";\n    const auto recording_events_channel = \"aeron:udp?endpoint=localhost:8888|alias=events\";\n    aeron_env_set(AERON_DIR_ENV_VAR, aeron_dir);\n    aeron_env_set(AERON_ARCHIVE_CONTROL_CHANNEL_ENV_VAR, control_channel);\n    aeron_env_set(AERON_ARCHIVE_CONTROL_STREAM_ID_ENV_VAR, \"-4321\");\n    aeron_env_set(AERON_ARCHIVE_CONTROL_RESPONSE_CHANNEL_ENV_VAR, response_channel);\n    aeron_env_set(AERON_ARCHIVE_CONTROL_RESPONSE_STREAM_ID_ENV_VAR, \"2009\");\n    aeron_env_set(AERON_ARCHIVE_RECORDING_EVENTS_CHANNEL_ENV_VAR, recording_events_channel);\n    aeron_env_set(AERON_ARCHIVE_RECORDING_EVENTS_STREAM_ID_ENV_VAR, \"2147483647\");\n    aeron_env_set(AERON_ARCHIVE_MESSAGE_TIMEOUT_ENV_VAR, \"9223372036s\");\n    aeron_env_set(AERON_ARCHIVE_MESSAGE_RETRY_ATTEMPTS_ENV_VAR, \"404\");\n    aeron_env_set(AERON_ARCHIVE_CONTROL_TERM_BUFFER_LENGTH_ENV_VAR, \"128k\");\n    aeron_env_set(AERON_ARCHIVE_CONTROL_TERM_BUFFER_SPARSE_ENV_VAR, \"false\");\n    aeron_env_set(AERON_ARCHIVE_CONTROL_MTU_LENGTH_ENV_VAR, \"8k\");\n\n    EXPECT_EQ(0, aeron_archive_context_init(&m_ctx));\n\n    aeron_env_unset(AERON_DIR_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_CONTROL_CHANNEL_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_CONTROL_STREAM_ID_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_CONTROL_RESPONSE_CHANNEL_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_CONTROL_RESPONSE_STREAM_ID_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_RECORDING_EVENTS_CHANNEL_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_RECORDING_EVENTS_STREAM_ID_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_MESSAGE_TIMEOUT_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_MESSAGE_RETRY_ATTEMPTS_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_CONTROL_TERM_BUFFER_LENGTH_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_CONTROL_TERM_BUFFER_SPARSE_ENV_VAR);\n    aeron_env_unset(AERON_ARCHIVE_CONTROL_MTU_LENGTH_ENV_VAR);\n\n    EXPECT_EQ(nullptr, m_ctx->aeron);\n    EXPECT_STREQ(aeron_dir, m_ctx->aeron_directory_name);\n    EXPECT_FALSE(m_ctx->owns_aeron_client);\n\n    EXPECT_STREQ(control_channel, m_ctx->control_request_channel);\n    EXPECT_EQ(-4321, m_ctx->control_request_stream_id);\n\n    EXPECT_STREQ(response_channel, m_ctx->control_response_channel);\n    EXPECT_EQ(2009, m_ctx->control_response_stream_id);\n\n    EXPECT_STREQ(recording_events_channel, m_ctx->recording_events_channel);\n    EXPECT_EQ(INT32_MAX, m_ctx->recording_events_stream_id);\n\n    EXPECT_EQ(9223372036000000000UL, m_ctx->message_timeout_ns);\n    EXPECT_EQ(404, m_ctx->message_retry_attempts);\n\n    EXPECT_EQ(128 * 1024, m_ctx->control_term_buffer_length);\n    EXPECT_EQ(false, m_ctx->control_term_buffer_sparse);\n    EXPECT_EQ(8192, m_ctx->control_mtu_length);\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n}\n\nTEST_F(AeronCArchiveIdTest, shouldFailWithErrorIfControlRequestChannelIsNotDefined)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(-1, aeron_archive_context_conclude(m_ctx));\n\n    EXPECT_EQ(EINVAL, aeron_errcode());\n    EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(\"control request channel is required\"));\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n}\n\nTEST_F(AeronCArchiveIdTest, shouldFailWithErrorIfControlResponseChannelIsNotDefined)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n\n    aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:ipc\");\n    ASSERT_EQ_ERR(-1, aeron_archive_context_conclude(m_ctx));\n\n    EXPECT_EQ(EINVAL, aeron_errcode());\n    EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(\"control response channel is required\"));\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n}\n\nTEST_F(AeronCArchiveIdTest, shouldFailWithErrorIfRetryAttemptsIsZero)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n\n    aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:ipc\");\n    aeron_archive_context_set_control_response_channel(m_ctx, \"aeron:ipc\");\n    aeron_archive_context_set_message_retry_attempts(m_ctx, 0);\n    ASSERT_EQ(0, aeron_archive_context_get_message_retry_attempts(m_ctx));\n    ASSERT_EQ_ERR(-1, aeron_archive_context_conclude(m_ctx));\n\n    EXPECT_EQ(EINVAL, aeron_errcode());\n    EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(\"message_retry_attempts must be > 0\"));\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n}\n\nTEST_F(AeronCArchiveIdTest, shouldFailWithErrorIfAeronClientFailsToConnect)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n\n    aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:ipc\");\n    aeron_archive_context_set_control_response_channel(m_ctx, \"aeron:ipc\");\n    aeron_env_set(AERON_CLIENT_NAME_ENV_VAR, std::string(\"super very long client name\").append(100, 'x').c_str());\n    ASSERT_EQ_ERR(-1, aeron_archive_context_conclude(m_ctx));\n    aeron_env_unset(AERON_CLIENT_NAME_ENV_VAR);\n\n    EXPECT_EQ(EINVAL, aeron_errcode());\n    EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(\"client_name length must <= 100\"));\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n}\n\nTEST_F(AeronCArchiveIdTest, shouldApplyDefaultParametersToRequestAndResponseChannels)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n\n    aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:ipc\");\n    aeron_archive_context_set_control_response_channel(m_ctx, \"aeron:udp?endpoint=127.0.0.1:0\");\n    aeron_t aeron = {};\n    aeron.conductor.control_protocol_version = 0;\n    const size_t buffer_capacity = 128 + AERON_RB_TRAILER_LENGTH;\n    auto *buffer = new uint8_t[buffer_capacity];\n    ASSERT_EQ_ERR(0, aeron_mpsc_rb_init(&aeron.conductor.to_driver_buffer, buffer, buffer_capacity));\n    aeron_archive_context_set_aeron(m_ctx, &aeron);\n    aeron_archive_context_set_error_handler(m_ctx, error_handler, nullptr);\n    aeron_archive_context_set_control_term_buffer_length(m_ctx, 256 * 1024);\n    aeron_archive_context_set_control_mtu_length(m_ctx, 2048);\n    aeron_archive_context_set_control_term_buffer_sparse(m_ctx, false);\n    ASSERT_EQ_ERR(0, aeron_archive_context_conclude(m_ctx));\n\n    aeron_uri_string_builder_t request_channel;\n    EXPECT_EQ(0, aeron_uri_string_builder_init_on_string(\n        &request_channel,\n        aeron_archive_context_get_control_request_channel(m_ctx)));\n    EXPECT_STREQ(\"262144\", aeron_uri_string_builder_get(&request_channel, AERON_URI_TERM_LENGTH_KEY));\n    EXPECT_STREQ(\"2048\", aeron_uri_string_builder_get(&request_channel, AERON_URI_MTU_LENGTH_KEY));\n    EXPECT_STREQ(\"false\", aeron_uri_string_builder_get(&request_channel, AERON_URI_SPARSE_TERM_KEY));\n    EXPECT_STRNE(\"\", aeron_uri_string_builder_get(&request_channel, AERON_URI_SESSION_ID_KEY));\n\n    aeron_uri_string_builder_t response_channel;\n    EXPECT_EQ(0, aeron_uri_string_builder_init_on_string(\n        &response_channel,\n        aeron_archive_context_get_control_response_channel(m_ctx)));\n    EXPECT_STREQ(\"262144\", aeron_uri_string_builder_get(&response_channel, AERON_URI_TERM_LENGTH_KEY));\n    EXPECT_STREQ(\"2048\", aeron_uri_string_builder_get(&response_channel, AERON_URI_MTU_LENGTH_KEY));\n    EXPECT_STREQ(\"false\", aeron_uri_string_builder_get(&response_channel, AERON_URI_SPARSE_TERM_KEY));\n    EXPECT_STREQ(\"127.0.0.1:0\", aeron_uri_string_builder_get(&response_channel, AERON_UDP_CHANNEL_ENDPOINT_KEY));\n    EXPECT_STRNE(\"\", aeron_uri_string_builder_get(&response_channel, AERON_URI_SESSION_ID_KEY));\n\n    EXPECT_STREQ(aeron_uri_string_builder_get(&request_channel, AERON_URI_SESSION_ID_KEY), aeron_uri_string_builder_get(&response_channel, AERON_URI_SESSION_ID_KEY));\n    EXPECT_EQ(0, aeron_uri_string_builder_close(&request_channel));\n    EXPECT_EQ(0, aeron_uri_string_builder_close(&response_channel));\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n    delete[] buffer;\n}\n\nTEST_F(AeronCArchiveIdTest, shouldNotApplyDefaultParametersToRequestAndResponseChannelsIfTheyAreSetExplicitly)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n\n    aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8080|term-length=64k|mtu=1408|sparse=true|session-id=0|ttl=3|interface=127.0.0.1\");\n    aeron_archive_context_set_control_response_channel(m_ctx, \"aeron:ipc?term-length=128k|mtu=4096|sparse=true|alias=response\");\n    aeron_t aeron = {};\n    aeron.conductor.control_protocol_version = 0;\n    const size_t buffer_capacity = 128 + AERON_RB_TRAILER_LENGTH;\n    auto *buffer = new uint8_t[buffer_capacity];\n    ASSERT_EQ_ERR(0, aeron_mpsc_rb_init(&aeron.conductor.to_driver_buffer, buffer, buffer_capacity));\n    aeron_archive_context_set_aeron(m_ctx, &aeron);\n    aeron_archive_context_set_error_handler(m_ctx, error_handler, nullptr);\n    aeron_archive_context_set_control_term_buffer_length(m_ctx, 256 * 1024);\n    aeron_archive_context_set_control_mtu_length(m_ctx, 2048);\n    aeron_archive_context_set_control_term_buffer_sparse(m_ctx, false);\n    ASSERT_EQ_ERR(0, aeron_archive_context_conclude(m_ctx));\n\n    aeron_uri_string_builder_t request_channel;\n    EXPECT_EQ(0, aeron_uri_string_builder_init_on_string(\n        &request_channel,\n        aeron_archive_context_get_control_request_channel(m_ctx)));\n    EXPECT_STREQ(\"64k\", aeron_uri_string_builder_get(&request_channel, AERON_URI_TERM_LENGTH_KEY));\n    EXPECT_STREQ(\"1408\", aeron_uri_string_builder_get(&request_channel, AERON_URI_MTU_LENGTH_KEY));\n    EXPECT_STREQ(\"true\", aeron_uri_string_builder_get(&request_channel, AERON_URI_SPARSE_TERM_KEY));\n    EXPECT_STREQ(\"3\", aeron_uri_string_builder_get(&request_channel, AERON_UDP_CHANNEL_TTL_KEY));\n    EXPECT_STREQ(\"127.0.0.1\", aeron_uri_string_builder_get(&request_channel, AERON_UDP_CHANNEL_INTERFACE_KEY));\n    EXPECT_STREQ(\"udp\", aeron_uri_string_builder_get(&request_channel, AERON_URI_STRING_BUILDER_MEDIA_KEY));\n    const auto session_id = aeron_uri_string_builder_get(&request_channel, AERON_URI_SESSION_ID_KEY);\n    EXPECT_NE(nullptr, session_id);\n    EXPECT_STRNE(\"\", session_id);\n\n    aeron_uri_string_builder_t response_channel;\n    EXPECT_EQ(0, aeron_uri_string_builder_init_on_string(\n        &response_channel,\n        aeron_archive_context_get_control_response_channel(m_ctx)));\n    EXPECT_STREQ(\"128k\", aeron_uri_string_builder_get(&response_channel, AERON_URI_TERM_LENGTH_KEY));\n    EXPECT_STREQ(\"4096\", aeron_uri_string_builder_get(&response_channel, AERON_URI_MTU_LENGTH_KEY));\n    EXPECT_STREQ(\"true\", aeron_uri_string_builder_get(&response_channel, AERON_URI_SPARSE_TERM_KEY));\n    EXPECT_STREQ(\"response\", aeron_uri_string_builder_get(&response_channel, AERON_URI_ALIAS_KEY));\n    EXPECT_STREQ(\"ipc\", aeron_uri_string_builder_get(&response_channel, AERON_URI_STRING_BUILDER_MEDIA_KEY));\n    EXPECT_STRNE(\"\", aeron_uri_string_builder_get(&response_channel, AERON_URI_SESSION_ID_KEY));\n\n    EXPECT_STREQ(session_id, aeron_uri_string_builder_get(&response_channel, AERON_URI_SESSION_ID_KEY));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_close(&request_channel));\n    EXPECT_EQ(0, aeron_uri_string_builder_close(&response_channel));\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n    delete[] buffer;\n}\n\nTEST_F(AeronCArchiveIdTest, shouldNotSetSessionIdOnControlRequestAndReponseChannelsIfControlModeResponseIsUsed)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n\n    aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8080\");\n    aeron_archive_context_set_control_response_channel(m_ctx, \"aeron:udp?control=localhost:9090|control-mode=response\");\n    aeron_t aeron = {};\n    aeron.conductor.control_protocol_version = 0;\n    const size_t buffer_capacity = 128 + AERON_RB_TRAILER_LENGTH;\n    auto *buffer = new uint8_t[buffer_capacity];\n    ASSERT_EQ_ERR(0, aeron_mpsc_rb_init(&aeron.conductor.to_driver_buffer, buffer, buffer_capacity));\n    aeron_archive_context_set_aeron(m_ctx, &aeron);\n    aeron_archive_context_set_error_handler(m_ctx, error_handler, nullptr);\n    aeron_archive_context_set_control_term_buffer_length(m_ctx, 256 * 1024);\n    aeron_archive_context_set_control_mtu_length(m_ctx, 2048);\n    aeron_archive_context_set_control_term_buffer_sparse(m_ctx, false);\n    ASSERT_EQ_ERR(0, aeron_archive_context_conclude(m_ctx));\n\n    aeron_uri_string_builder_t request_channel;\n    EXPECT_EQ(0, aeron_uri_string_builder_init_on_string(\n        &request_channel,\n        aeron_archive_context_get_control_request_channel(m_ctx)));\n    EXPECT_STREQ(\"localhost:8080\", aeron_uri_string_builder_get(&request_channel, AERON_UDP_CHANNEL_ENDPOINT_KEY));\n    EXPECT_EQ(nullptr, aeron_uri_string_builder_get(&request_channel, AERON_URI_SESSION_ID_KEY));\n\n    aeron_uri_string_builder_t response_channel;\n    EXPECT_EQ(0, aeron_uri_string_builder_init_on_string(\n        &response_channel,\n        aeron_archive_context_get_control_response_channel(m_ctx)));\n    EXPECT_EQ(nullptr, aeron_uri_string_builder_get(&response_channel, AERON_UDP_CHANNEL_ENDPOINT_KEY));\n    EXPECT_STREQ(\"localhost:9090\", aeron_uri_string_builder_get(&response_channel, AERON_UDP_CHANNEL_CONTROL_KEY));\n    EXPECT_STREQ(AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE, aeron_uri_string_builder_get(&response_channel, AERON_UDP_CHANNEL_CONTROL_MODE_KEY));\n    EXPECT_EQ(nullptr, aeron_uri_string_builder_get(&response_channel, AERON_URI_SESSION_ID_KEY));\n    EXPECT_EQ(0, aeron_uri_string_builder_close(&request_channel));\n    EXPECT_EQ(0, aeron_uri_string_builder_close(&response_channel));\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n    delete[] buffer;\n}\n\nTEST_F(AeronCArchiveIdTest, shouldDuplicateContext)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n\n    aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8080\");\n    aeron_archive_context_set_control_request_stream_id(m_ctx, 42);\n    aeron_archive_context_set_control_response_channel(m_ctx, \"aeron:udp?endpoint=localhost:0\");\n    aeron_archive_context_set_control_response_stream_id(m_ctx, -5);\n    aeron_archive_context_set_recording_events_channel(m_ctx, nullptr);\n    aeron_archive_context_set_recording_events_stream_id(m_ctx, 777);\n    aeron_archive_context_set_control_term_buffer_length(m_ctx, 256 * 1024);\n    aeron_archive_context_set_control_mtu_length(m_ctx, 2048);\n    aeron_archive_context_set_control_term_buffer_sparse(m_ctx, false);\n    aeron_archive_context_set_message_timeout_ns(m_ctx, 1000000000);\n    aeron_t aeron = {};\n    aeron.conductor.control_protocol_version = 0;\n    const size_t buffer_capacity = 128 + AERON_RB_TRAILER_LENGTH;\n    auto *buffer = new uint8_t[buffer_capacity];\n    ASSERT_EQ_ERR(0, aeron_mpsc_rb_init(&aeron.conductor.to_driver_buffer, buffer, buffer_capacity));\n    aeron_archive_context_set_aeron(m_ctx, &aeron);\n    aeron_archive_context_set_error_handler(m_ctx, error_handler, nullptr);\n    aeron_archive_context_set_idle_strategy(m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns);\n\n    aeron_archive_context_t *copy_ctx;\n    ASSERT_EQ_ERR(0, aeron_archive_context_duplicate(&copy_ctx, m_ctx));\n\n    EXPECT_NE(nullptr, copy_ctx);\n    EXPECT_NE(m_ctx, copy_ctx);\n    EXPECT_EQ(m_ctx->aeron, copy_ctx->aeron);\n    EXPECT_EQ(m_ctx->owns_aeron_client, copy_ctx->owns_aeron_client);\n    EXPECT_NE(m_ctx->control_request_channel, copy_ctx->control_request_channel);\n    EXPECT_STREQ(m_ctx->control_request_channel, copy_ctx->control_request_channel);\n    EXPECT_EQ(m_ctx->control_request_channel_length, copy_ctx->control_request_channel_length);\n    EXPECT_NE(m_ctx->control_response_channel, copy_ctx->control_response_channel);\n    EXPECT_STREQ(m_ctx->control_response_channel, copy_ctx->control_response_channel);\n    EXPECT_EQ(m_ctx->control_response_channel_length, copy_ctx->control_response_channel_length);\n    EXPECT_EQ(m_ctx->recording_events_channel, copy_ctx->recording_events_channel);\n    EXPECT_EQ(m_ctx->recording_events_channel_length, copy_ctx->recording_events_channel_length);\n    EXPECT_EQ(m_ctx->message_timeout_ns, copy_ctx->message_timeout_ns);\n    EXPECT_EQ(m_ctx->control_term_buffer_sparse, copy_ctx->control_term_buffer_sparse);\n    EXPECT_EQ(m_ctx->control_term_buffer_length, copy_ctx->control_term_buffer_length);\n    EXPECT_EQ(m_ctx->control_mtu_length, copy_ctx->control_mtu_length);\n    EXPECT_EQ(m_ctx->error_handler, copy_ctx->error_handler);\n    EXPECT_EQ(m_ctx->error_handler_clientd, copy_ctx->error_handler_clientd);\n    EXPECT_EQ(m_ctx->idle_strategy_func, copy_ctx->idle_strategy_func);\n    EXPECT_EQ(m_ctx->idle_strategy_state, copy_ctx->idle_strategy_state);\n    EXPECT_EQ(m_ctx->delegating_invoker_func, copy_ctx->delegating_invoker_func);\n    EXPECT_EQ(m_ctx->delegating_invoker_func_clientd, copy_ctx->delegating_invoker_func_clientd);\n    EXPECT_EQ(m_ctx->on_recording_signal, copy_ctx->on_recording_signal);\n    EXPECT_EQ(m_ctx->on_recording_signal_clientd, copy_ctx->on_recording_signal_clientd);\n\n    EXPECT_EQ(0, aeron_archive_context_close(m_ctx));\n    EXPECT_EQ(0, aeron_archive_context_close(copy_ctx));\n    delete[] buffer;\n}\n\nTEST_F(AeronCArchiveIdTest, shouldResolveArchiveId)\n{\n    std::int64_t archiveId = 0x4236483BEEF;\n    DoSetUp(archiveId);\n\n    connect();\n\n    aeron_subscription_t *subscription = aeron_archive_get_control_response_subscription(m_archive);\n    EXPECT_TRUE(aeron_subscription_is_connected(subscription));\n    EXPECT_EQ(archiveId, aeron_archive_get_archive_id(m_archive));\n\n    DoTearDown();\n}\n\nTEST_F(AeronCArchiveTest, shouldConnectToArchiveWithResponseChannels)\n{\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(\n        m_ctx,\n        \"aeron:udp?control-mode=response|control=localhost:10002\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        m_ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_archive, m_ctx));\n\n    aeron_subscription_t *subscription = aeron_archive_get_control_response_subscription(m_archive);\n    EXPECT_TRUE(aeron_subscription_is_connected(subscription));\n}\n\nTEST_P(AeronCArchiveParamTest, shouldReplayWithResponseChannel)\n{\n    bool tryStop = GetParam();\n\n    size_t message_count = 1000;\n    const char *response_channel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(\n        m_ctx,\n        response_channel));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        m_ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_archive, m_ctx));\n\n    m_aeron = aeron_archive_context_get_aeron(m_ctx);\n\n    int64_t recording_id, stop_position, halfway_position;\n\n    recordData(tryStop, &recording_id, &stop_position, &halfway_position, message_count);\n\n    int64_t position = 0L;\n    int64_t length = stop_position - position;\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = position;\n    replay_params.length = length;\n    replay_params.file_io_max_length = 4096;\n\n    aeron_subscription_t *subscription;\n\n    ASSERT_EQ_ERR(0, aeron_archive_replay(\n        &subscription,\n        m_archive,\n        recording_id,\n        response_channel,\n        m_replayStreamId,\n        &replay_params));\n\n    consumeMessages(subscription, message_count);\n\n    aeron_image_t *image = aeron_subscription_image_at_index(subscription, 0);\n    EXPECT_EQ(stop_position, aeron_image_position(image));\n}\n\nTEST_P(AeronCArchiveParamTest, shouldBoundedReplayWithResponseChannel)\n{\n    bool tryStop = GetParam();\n\n    size_t message_count = 1000;\n    const char *response_channel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n    const std::int64_t key = 1234567890;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(\n        m_ctx,\n        response_channel));\n    ASSERT_EQ_ERR(0,\n        aeron_archive_context_set_idle_strategy(m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        m_ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_archive, m_ctx));\n\n    m_aeron = aeron_archive_context_get_aeron(m_ctx);\n\n    int64_t recording_id, stop_position, halfway_position;\n\n    recordData(tryStop, &recording_id, &stop_position, &halfway_position, message_count);\n\n    const char *counter_name = \"test bounded counter\";\n    aeron_async_add_counter_t *async_add_counter;\n    ASSERT_EQ_ERR(0, aeron_async_add_counter(\n        &async_add_counter,\n        m_aeron,\n        10001,\n        (const uint8_t *)&key,\n        sizeof(key),\n        counter_name,\n        strlen(counter_name)));\n\n    aeron_counter_t *counter = nullptr;\n    aeron_async_add_counter_poll(&counter, async_add_counter);\n    while (nullptr == counter)\n    {\n        idle();\n        aeron_async_add_counter_poll(&counter, async_add_counter);\n    }\n\n    aeron_counter_set_release(aeron_counter_addr(counter), halfway_position);\n\n    int64_t position = 0L;\n    int64_t length = stop_position - position;\n\n    aeron_counter_constants_t counter_constants;\n    aeron_counter_constants(counter, &counter_constants);\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = position;\n    replay_params.length = length;\n    replay_params.file_io_max_length = 4096;\n    replay_params.bounding_limit_counter_id = counter_constants.counter_id;\n\n    aeron_subscription_t *subscription;\n\n    ASSERT_EQ_ERR(0, aeron_archive_replay(\n        &subscription,\n        m_archive,\n        recording_id,\n        response_channel,\n        m_replayStreamId,\n        &replay_params));\n\n    consumeMessages(subscription, message_count / 2);\n\n    aeron_image_t *image = aeron_subscription_image_at_index(subscription, 0);\n    EXPECT_EQ(halfway_position, aeron_image_position(image));\n}\n\nTEST_P(AeronCArchiveParamTest, shouldStartReplayWithResponseChannel)\n{\n    bool tryStop = GetParam();\n\n    size_t message_count = 1000;\n    const char *response_channel = \"aeron:udp?control-mode=response|control=localhost:10003\";\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(\n        m_ctx,\n        response_channel));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        m_ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_archive, m_ctx));\n\n    m_aeron = aeron_archive_context_get_aeron(m_ctx);\n\n    int64_t recording_id, stop_position, halfway_position;\n\n    recordData(tryStop, &recording_id, &stop_position, &halfway_position, message_count);\n\n    aeron_subscription_t *subscription = addSubscription(response_channel, m_replayStreamId);\n\n    int64_t position = 0L;\n    int64_t length = stop_position - position;\n\n    aeron_subscription_constants_t subscription_constants;\n    aeron_subscription_constants(subscription, &subscription_constants);\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = position;\n    replay_params.length = length;\n    replay_params.file_io_max_length = 4096;\n    replay_params.subscription_registration_id = subscription_constants.registration_id;\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_replay(\n        nullptr,\n        m_archive,\n        recording_id,\n        response_channel,\n        m_replayStreamId,\n        &replay_params));\n\n    consumeMessages(subscription, message_count);\n\n    aeron_image_t *image = aeron_subscription_image_at_index(subscription, 0);\n    EXPECT_EQ(stop_position, aeron_image_position(image));\n}\n\nTEST_P(AeronCArchiveParamTest, shouldStartBoundedReplayWithResponseChannel)\n{\n    bool tryStop = GetParam();\n\n    size_t message_count = 1000;\n    const char *response_channel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n    const std::int64_t key = 1234567890;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(\n        m_ctx,\n        response_channel));\n    ASSERT_EQ_ERR(0,\n        aeron_archive_context_set_idle_strategy(m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        m_ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_archive, m_ctx));\n\n    m_aeron = aeron_archive_context_get_aeron(m_ctx);\n\n    int64_t recording_id, stop_position, halfway_position;\n\n    recordData(tryStop, &recording_id, &stop_position, &halfway_position, message_count);\n\n    const char *counter_name = \"test bounded counter\";\n    aeron_async_add_counter_t *async_add_counter;\n    ASSERT_EQ_ERR(0, aeron_async_add_counter(\n        &async_add_counter,\n        m_aeron,\n        10001,\n        (const uint8_t *)&key,\n        sizeof(key),\n        counter_name,\n        strlen(counter_name)));\n\n    aeron_counter_t *counter = nullptr;\n    aeron_async_add_counter_poll(&counter, async_add_counter);\n    while (nullptr == counter)\n    {\n        idle();\n        aeron_async_add_counter_poll(&counter, async_add_counter);\n    }\n\n    aeron_counter_set_release(aeron_counter_addr(counter), halfway_position);\n\n    aeron_subscription_t *subscription = addSubscription(response_channel, m_replayStreamId);\n\n    int64_t position = 0L;\n    int64_t length = stop_position - position;\n\n    aeron_counter_constants_t counter_constants;\n    aeron_counter_constants(counter, &counter_constants);\n\n    aeron_subscription_constants_t subscription_constants;\n    aeron_subscription_constants(subscription, &subscription_constants);\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = position;\n    replay_params.length = length;\n    replay_params.file_io_max_length = 4096;\n    replay_params.bounding_limit_counter_id = counter_constants.counter_id;\n    replay_params.subscription_registration_id = subscription_constants.registration_id;\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_replay(\n        nullptr,\n        m_archive,\n        recording_id,\n        response_channel,\n        m_replayStreamId,\n        &replay_params));\n\n    consumeMessages(subscription, message_count / 2);\n\n    aeron_image_t *image = aeron_subscription_image_at_index(subscription, 0);\n    EXPECT_EQ(halfway_position, aeron_image_position(image));\n}\n\nTEST_F(AeronCArchiveTest, shouldDisconnectAfterStopAllReplays)\n{\n    const char *response_channel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&m_ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(m_ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(\n        m_ctx,\n        response_channel));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        m_ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        m_ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n    ASSERT_EQ_ERR(0, aeron_archive_connect(&m_archive, m_ctx));\n\n    m_aeron = aeron_archive_context_get_aeron(m_ctx);\n\n    addSubscription(m_recordingChannel, m_recordingStreamId);\n\n    aeron_publication_t *publication;\n    ASSERT_EQ_ERR(0, aeron_archive_add_recorded_publication(\n        &publication,\n        m_archive,\n        m_recordingChannel.c_str(),\n        m_recordingStreamId));\n\n    int32_t session_id = aeron_publication_session_id(publication);\n\n    setupCounters(session_id);\n\n    offerMessages(publication);\n\n    int64_t stop_position = aeron_publication_position(publication);\n\n    waitUntilCaughtUp(stop_position);\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = 0L;\n    replay_params.file_io_max_length = 4096;\n\n    aeron_subscription_t *subscription;\n\n    ASSERT_EQ_ERR(0, aeron_archive_replay(\n        &subscription,\n        m_archive,\n        m_recording_id_from_counter,\n        response_channel,\n        m_replayStreamId,\n        &replay_params));\n\n    consumeMessages(subscription);\n\n    aeron_image_t *image = aeron_subscription_image_at_index(subscription, 0);\n    EXPECT_EQ(stop_position, aeron_image_position(image));\n\n    ASSERT_EQ_ERR(0, aeron_archive_stop_all_replays(\n        m_archive,\n        m_recording_id_from_counter));\n\n    while (aeron_subscription_is_connected(subscription))\n    {\n        idle();\n    }\n}\n\nTEST_P(AeronCArchiveParamTest, shouldRecordAndExtend)\n{\n    bool tryStop = GetParam();\n\n    connect();\n\n    {\n        aeron_subscription_t *subscription = addSubscription(m_recordingChannel, m_recordingStreamId);\n\n        aeron_publication_t *publication;\n        ASSERT_EQ_ERR(0, aeron_archive_add_recorded_publication(\n            &publication,\n            m_archive,\n            m_recordingChannel.c_str(),\n            m_recordingStreamId));\n\n        int32_t session_id = aeron_publication_session_id(publication);\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        int64_t stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n\n        ASSERT_EQ_ERR(0, aeron_archive_stop_recording_publication(m_archive, publication));\n\n        ASSERT_EQ_ERR(0, aeron_subscription_close(subscription, nullptr, nullptr));\n        ASSERT_EQ_ERR(0, aeron_publication_close(publication, nullptr, nullptr));\n    }\n\n    recording_descriptor_consumer_clientd_t clientd;\n\n    int32_t count;\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording(\n        &count,\n        m_archive,\n        m_recording_id_from_counter,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(1, count);\n\n    char recordingChannel2[AERON_URI_MAX_LENGTH];\n\n    aeron_uri_string_builder_t builder;\n    ASSERT_EQ_ERR(0, aeron_uri_string_builder_init_on_string(&builder, \"aeron:udp?endpoint=localhost:3332\"));\n    ASSERT_EQ_ERR(0, aeron_uri_string_builder_set_initial_position(\n        &builder,\n        clientd.last_descriptor.stop_position,\n        clientd.last_descriptor.initial_term_id,\n        clientd.last_descriptor.term_buffer_length));\n    ASSERT_EQ_ERR(0, aeron_uri_string_builder_sprint(&builder, recordingChannel2, sizeof(recordingChannel2)));\n    ASSERT_EQ_ERR(0, aeron_uri_string_builder_close(&builder));\n\n    {\n        aeron_subscription_t *subscription = addSubscription(recordingChannel2, m_recordingStreamId);\n        aeron_publication_t *publication = addPublication(recordingChannel2, m_recordingStreamId);\n\n        int32_t session_id = aeron_publication_session_id(publication);\n\n        int64_t subscription_id;\n        ASSERT_EQ_ERR(0, aeron_archive_extend_recording(\n            &subscription_id,\n            m_archive,\n            m_recording_id_from_counter,\n            recordingChannel2,\n            m_recordingStreamId,\n            AERON_ARCHIVE_SOURCE_LOCATION_LOCAL,\n            false));\n\n        setupCounters(session_id);\n\n        offerMessages(publication);\n        consumeMessages(subscription);\n\n        int64_t stop_position = aeron_publication_position(publication);\n\n        waitUntilCaughtUp(stop_position);\n\n        if (tryStop)\n        {\n            bool stopped;\n            ASSERT_EQ_ERR(0, aeron_archive_try_stop_recording_channel_and_stream(\n                &stopped, m_archive, recordingChannel2, m_recordingStreamId));\n            EXPECT_TRUE(stopped);\n        }\n        else\n        {\n            ASSERT_EQ_ERR(0, aeron_archive_stop_recording_channel_and_stream(\n                m_archive, recordingChannel2, m_recordingStreamId));\n        }\n\n        ASSERT_EQ_ERR(0, aeron_subscription_close(subscription, nullptr, nullptr));\n        ASSERT_EQ_ERR(0, aeron_publication_close(publication, nullptr, nullptr));\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording(\n        &count,\n        m_archive,\n        m_recording_id_from_counter,\n        recording_descriptor_consumer,\n        &clientd));\n    EXPECT_EQ(1, count);\n\n    aeron_archive_replay_params_t replay_params;\n    aeron_archive_replay_params_init(&replay_params);\n\n    replay_params.position = clientd.last_descriptor.start_position;\n    replay_params.file_io_max_length = 4096;\n\n    aeron_subscription_t *replay_subscription;\n\n    ASSERT_EQ_ERR(0, aeron_archive_replay(\n        &replay_subscription,\n        m_archive,\n        m_recording_id_from_counter,\n        m_replayChannel.c_str(),\n        m_replayStreamId,\n        &replay_params));\n\n    consumeMessages(replay_subscription, 20);\n\n    aeron_image_t *image = aeron_subscription_image_at_index(replay_subscription, 0);\n    ASSERT_EQ(clientd.last_descriptor.stop_position, aeron_image_position(image));\n}\n\n#define TERM_LENGTH AERON_LOGBUFFER_TERM_MIN_LENGTH\n#define SEGMENT_LENGTH (TERM_LENGTH * 2)\n#define MTU_LENGTH 1024\n\nTEST_F(AeronCArchiveTest, shouldPurgeSegments)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    recording_signal_consumer_clientd_t rsc_cd;\n\n    connect(&rsc_cd);\n\n    char publication_channel[AERON_URI_MAX_LENGTH];\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, \"localhost:3333\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_TERM_LENGTH_KEY, TERM_LENGTH);\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_MTU_LENGTH_KEY, MTU_LENGTH);\n\n        aeron_uri_string_builder_sprint(&builder, publication_channel, sizeof(publication_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    aeron_publication_t *publication;\n    ASSERT_EQ_ERR(0, aeron_archive_add_recorded_publication(\n        &publication,\n        m_archive,\n        publication_channel,\n        m_recordingStreamId));\n\n    session_id = aeron_publication_session_id(publication);\n\n    setupCounters(session_id);\n\n    int64_t targetPosition = (SEGMENT_LENGTH * 3L) + 1;\n    offerMessagesToPosition(publication, targetPosition);\n\n    stop_position = aeron_publication_position(publication);\n\n    waitUntilCaughtUp(stop_position);\n\n    int64_t start_position = 0;\n    int64_t segment_file_base_position = aeron_archive_segment_file_base_position(\n        start_position,\n        SEGMENT_LENGTH * 2L,\n        TERM_LENGTH,\n        SEGMENT_LENGTH);\n\n    int64_t count;\n    ASSERT_EQ_ERR(0, aeron_archive_purge_segments(\n        &count,\n        m_archive,\n        m_recording_id_from_counter,\n        segment_file_base_position));\n\n    while (0 == rsc_cd.signals.count(AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_DELETE))\n    {\n        aeron_archive_poll_for_recording_signals(nullptr, m_archive);\n\n        idle();\n    }\n\n    ASSERT_EQ(2, count);\n\n    ASSERT_EQ_ERR(0, aeron_archive_get_start_position(&start_position, m_archive, m_recording_id_from_counter));\n    ASSERT_EQ(start_position, segment_file_base_position);\n}\n\nTEST_F(AeronCArchiveTest, shouldDetachAndDeleteSegments)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    recording_signal_consumer_clientd_t rsc_cd;\n\n    connect(&rsc_cd);\n\n    char publication_channel[AERON_URI_MAX_LENGTH];\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, \"localhost:3333\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_TERM_LENGTH_KEY, TERM_LENGTH);\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_MTU_LENGTH_KEY, MTU_LENGTH);\n\n        aeron_uri_string_builder_sprint(&builder, publication_channel, sizeof(publication_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    aeron_publication_t *publication;\n    ASSERT_EQ_ERR(0, aeron_archive_add_recorded_publication(\n        &publication,\n        m_archive,\n        publication_channel,\n        m_recordingStreamId));\n\n    session_id = aeron_publication_session_id(publication);\n\n    setupCounters(session_id);\n\n    int64_t targetPosition = (SEGMENT_LENGTH * 4L) + 1;\n    offerMessagesToPosition(publication, targetPosition);\n\n    stop_position = aeron_publication_position(publication);\n\n    waitUntilCaughtUp(stop_position);\n\n    int64_t start_position = 0;\n    int64_t segment_file_base_position = aeron_archive_segment_file_base_position(\n        start_position,\n        SEGMENT_LENGTH * 3L,\n        TERM_LENGTH,\n        SEGMENT_LENGTH);\n\n    ASSERT_EQ_ERR(0, aeron_archive_detach_segments(\n        m_archive,\n        m_recording_id_from_counter,\n        segment_file_base_position));\n\n    int64_t count;\n    ASSERT_EQ_ERR(0, aeron_archive_delete_detached_segments(\n        &count,\n        m_archive,\n        m_recording_id_from_counter));\n\n    while (0 == rsc_cd.signals.count(AERON_ARCHIVE_CLIENT_RECORDING_SIGNAL_DELETE))\n    {\n        aeron_archive_poll_for_recording_signals(nullptr, m_archive);\n\n        idle();\n    }\n\n    ASSERT_EQ(3, count);\n\n    ASSERT_EQ_ERR(0, aeron_archive_get_start_position(&start_position, m_archive, m_recording_id_from_counter));\n    ASSERT_EQ(start_position, segment_file_base_position);\n}\n\nTEST_F(AeronCArchiveTest, shouldDetachAndReattachSegments)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    recording_signal_consumer_clientd_t rsc_cd;\n\n    connect(&rsc_cd);\n\n    char publication_channel[AERON_URI_MAX_LENGTH];\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, \"localhost:3333\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_TERM_LENGTH_KEY, TERM_LENGTH);\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_MTU_LENGTH_KEY, MTU_LENGTH);\n\n        aeron_uri_string_builder_sprint(&builder, publication_channel, sizeof(publication_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    aeron_publication_t *publication;\n    ASSERT_EQ_ERR(0, aeron_archive_add_recorded_publication(\n        &publication,\n        m_archive,\n        publication_channel,\n        m_recordingStreamId));\n\n    session_id = aeron_publication_session_id(publication);\n\n    setupCounters(session_id);\n\n    int64_t targetPosition = (SEGMENT_LENGTH * 5L) + 1;\n    offerMessagesToPosition(publication, targetPosition);\n\n    stop_position = aeron_publication_position(publication);\n\n    waitUntilCaughtUp(stop_position);\n\n    int64_t start_position = 0;\n    int64_t segment_file_base_position = aeron_archive_segment_file_base_position(\n        start_position,\n        SEGMENT_LENGTH * 4L,\n        TERM_LENGTH,\n        SEGMENT_LENGTH);\n\n    ASSERT_EQ_ERR(0, aeron_archive_detach_segments(\n        m_archive,\n        m_recording_id_from_counter,\n        segment_file_base_position));\n\n    ASSERT_EQ_ERR(0, aeron_archive_get_start_position(&start_position, m_archive, m_recording_id_from_counter));\n    ASSERT_EQ(start_position, segment_file_base_position);\n\n    int64_t count;\n    ASSERT_EQ_ERR(0, aeron_archive_attach_segments(\n        &count,\n        m_archive,\n        m_recording_id_from_counter));\n\n    ASSERT_EQ(4, count);\n\n    ASSERT_EQ_ERR(0, aeron_archive_get_start_position(&start_position, m_archive, m_recording_id_from_counter));\n    ASSERT_EQ(start_position, 0);\n}\n\nTEST_F(AeronCArchiveTest, shouldSetAeronClientName)\n{\n    connect(\n        nullptr,\n        \"aeron:udp?endpoint=localhost:8010\",\n        \"aeron:udp?control=localhost:9090|control-mode=response\");\n\n    auto aeron = aeron_archive_context_get_aeron(m_ctx);\n    const auto client_name = std::string(aeron_context_get_client_name(aeron->context));\n    EXPECT_NE(std::string::npos, client_name.find(\"archive-client\"));\n}\n\nTEST_F(AeronCArchiveTest, shouldSendClientInfoToArchive)\n{\n    connect(\n        nullptr,\n        \"aeron:udp?endpoint=localhost:8010\",\n        \"aeron:udp?control=localhost:9090|control-mode=response\",\n        \"my client\");\n\n    m_aeron = aeron_archive_context_get_aeron(m_ctx);\n    m_counters_reader = aeron_counters_reader(m_aeron);\n\n    int64_t controlSessionId = aeron_archive_control_session_id(m_archive);\n\n    struct counter_data\n    {\n        int32_t id;\n        size_t key_length;\n        size_t label_length;\n        uint8_t key[AERON_COUNTER_MAX_KEY_LENGTH];\n        char label[AERON_COUNTER_MAX_LABEL_LENGTH];\n    };\n    counter_data counter = {};\n\n    static constexpr int32_t controlSessionTypeId = AERON_COUNTER_ARCHIVE_CONTROL_SESSION_TYPE_ID;\n\n    aeron_counters_reader_foreach_counter(\n        m_counters_reader,\n[](int64_t value,\n        int32_t id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_length,\n        const char *label,\n        size_t label_length,\n        void *clientd)\n    {\n        if (controlSessionTypeId == type_id)\n        {\n            auto *data = static_cast<counter_data *>(clientd);\n            data->id = id;\n            data->key_length = key_length;\n            data->label_length = label_length;\n            memcpy(data->key, key, key_length);\n            memcpy(data->label, label, label_length);\n        }\n    },\n    &counter);\n\n    ASSERT_NE(AERON_NULL_COUNTER_ID, counter.id);\n    ASSERT_GE(counter.key_length, 2 * sizeof(int64_t));\n\n    int64_t actualArchiveId, actualControlSessionId;\n    memcpy(&actualArchiveId, counter.key, sizeof(int64_t));\n    ASSERT_EQ(aeron_archive_get_archive_id(m_archive), actualArchiveId);\n    memcpy(&actualControlSessionId, counter.key + sizeof(int64_t), sizeof(int64_t));\n    ASSERT_EQ(controlSessionId, actualControlSessionId);\n\n    const auto label = std::string(counter.label);\n    const auto expected = std::string(\"name=\")\n        .append(aeron_archive_context_get_client_name(m_ctx))\n        .append(\" version=\").append(aeron_archive_client_version_text())\n        .append(\" commit=\").append(aeron_archive_client_version_git_sha());\n    EXPECT_NE(std::string::npos, label.find(expected));\n    EXPECT_NE(std::string::npos, label.find(std::string(\"archiveId=\").append(std::to_string(actualArchiveId))));\n}\n\nTEST_F(AeronCArchiveTest, shouldUpdateChannel)\n{\n    int32_t session_id;\n    int64_t stop_position;\n\n    recording_signal_consumer_clientd_t rsc_cd;\n\n    connect(&rsc_cd);\n\n    char publication_channel[AERON_URI_MAX_LENGTH];\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, \"localhost:3333\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_TERM_LENGTH_KEY, TERM_LENGTH);\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_MTU_LENGTH_KEY, MTU_LENGTH);\n\n        aeron_uri_string_builder_sprint(&builder, publication_channel, sizeof(publication_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    aeron_publication_t *publication;\n    ASSERT_EQ_ERR(0, aeron_archive_add_recorded_publication(\n            &publication,\n            m_archive,\n            publication_channel,\n            m_recordingStreamId));\n\n    session_id = aeron_publication_session_id(publication);\n\n    setupCounters(session_id);\n\n    int64_t targetPosition = TERM_LENGTH;\n    offerMessagesToPosition(publication, targetPosition);\n    stop_position = aeron_publication_position(publication);\n    waitUntilCaughtUp(stop_position);\n\n    const char *newChannel = \"aeron:ipc?alias=test42|rejoin=false|mtu=8k|term-length=1m|pub-wnd=512k|ttl=6\";\n    ASSERT_EQ_ERR(0, aeron_archive_update_channel(m_archive, m_recording_id_from_counter, newChannel));\n\n    recording_descriptor_consumer_clientd_t clientd;\n    clientd.verify_recording_id = true;\n    clientd.recording_id = m_recording_id_from_counter;\n    clientd.verify_stream_id = true;\n    clientd.stream_id = m_recordingStreamId;\n    clientd.original_channel = newChannel;\n\n    int32_t count;\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording(\n            &count,\n            m_archive,\n            m_recording_id_from_counter,\n            recording_descriptor_consumer,\n            &clientd));\n    EXPECT_EQ(1, count);\n\n    aeron_uri_t uri = {};\n    EXPECT_EQ(0, aeron_uri_parse(strlen(clientd.last_descriptor_stripped_channel), clientd.last_descriptor_stripped_channel, &uri));\n\n    EXPECT_STREQ(nullptr, aeron_uri_find_param_value(&uri.params.ipc.additional_params, AERON_URI_PUBLICATION_WINDOW_KEY));\n    EXPECT_STREQ(nullptr, aeron_uri_find_param_value(&uri.params.ipc.additional_params, AERON_URI_TERM_LENGTH_KEY));\n    EXPECT_STREQ(nullptr, aeron_uri_find_param_value(&uri.params.ipc.additional_params, AERON_URI_MTU_LENGTH_KEY));\n    EXPECT_STREQ(\"test42\", aeron_uri_find_param_value(&uri.params.ipc.additional_params, AERON_URI_ALIAS_KEY));\n    EXPECT_STREQ(\"6\", aeron_uri_find_param_value(&uri.params.ipc.additional_params, AERON_UDP_CHANNEL_TTL_KEY));\n    EXPECT_STREQ(\"false\", aeron_uri_find_param_value(&uri.params.ipc.additional_params, AERON_URI_REJOIN_KEY));\n    aeron_uri_close(&uri);\n}\n\nTEST_F(AeronCArchiveTest, shouldRejectReentractCallsDuringCallbacks)\n{\n    recording_signal_consumer_clientd_t rsc_cd;\n\n    connect(&rsc_cd);\n\n    char publication_channel[AERON_URI_MAX_LENGTH];\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, \"localhost:3333\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_TERM_LENGTH_KEY, TERM_LENGTH);\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_MTU_LENGTH_KEY, MTU_LENGTH);\n\n        aeron_uri_string_builder_sprint(&builder, publication_channel, sizeof(publication_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    aeron_publication_t *publication;\n    ASSERT_EQ_ERR(0, aeron_archive_add_recorded_publication(\n            &publication,\n            m_archive,\n            publication_channel,\n            m_recordingStreamId));\n\n    int32_t session_id = aeron_publication_session_id(publication);\n\n    setupCounters(session_id);\n\n    struct archive_wrapper\n    {\n        aeron_archive_t *archive;\n    };\n\n    archive_wrapper callback_client_d{};\n    callback_client_d.archive = m_archive;\n\n    int32_t count;\n    ASSERT_EQ_ERR(0, aeron_archive_list_recording(\n            &count,\n            m_archive,\n            m_recording_id_from_counter,\n            [](auto recording_descriptor, auto clientd)\n            {\n                auto *wrapper = (archive_wrapper *)clientd;\n\n                int dummy;\n                EXPECT_EQ(-1, aeron_archive_list_recording(&dummy, wrapper->archive, 0, nullptr, nullptr));\n                EXPECT_EQ(-AERON_ERROR_CODE_GENERIC_ERROR, aeron_errcode());\n                EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(\"reentrant calls not permitted during callbacks\"));\n\n                int64_t dummy_position;\n                EXPECT_EQ(-1, aeron_archive_get_max_recorded_position(&dummy_position, wrapper->archive, 0));\n                EXPECT_EQ(-AERON_ERROR_CODE_GENERIC_ERROR, aeron_errcode());\n                EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(\"reentrant calls not permitted during callbacks\"));\n\n                EXPECT_EQ(-1, aeron_archive_detach_segments(wrapper->archive, 0, 0));\n                EXPECT_EQ(-AERON_ERROR_CODE_GENERIC_ERROR, aeron_errcode());\n                EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(\"reentrant calls not permitted during callbacks\"));\n            },\n            &callback_client_d));\n    EXPECT_EQ(1, count);\n\n#ifndef _MSC_VER\n    // verify that lock was unlocked during the failed reentrant calls\n    EXPECT_EQ(EPERM, aeron_mutex_unlock(&m_archive->lock));\n#endif\n}\n\nclass AeronCArchiveNoSetupTest : public AeronCArchiveTestBase, public testing::Test\n{\n};\n\nTEST_F(AeronCArchiveNoSetupTest, shouldNotHangClosingReplayMergeWhenUsingInvoker)\n{\n    m_invoke_on_idle = true;\n\n    DoSetUp(42, 0);\n\n    const std::size_t termLength = 64 * 1024;\n    const std::string message_prefix = \"Message \";\n    const std::size_t min_messages_per_term = termLength / (message_prefix.length() + AERON_DATA_HEADER_LENGTH);\n    const char *control_endpoint = \"localhost:23265\";\n    const char *recording_endpoint = \"localhost:23266\";\n    const char *live_endpoint = \"localhost:23267\";\n    const char *replay_endpoint = \"localhost:0\";\n\n    char publication_channel[AERON_URI_MAX_LENGTH];\n    char live_destination[AERON_URI_MAX_LENGTH];\n    char replay_destination[AERON_URI_MAX_LENGTH];\n    char recording_channel[AERON_URI_MAX_LENGTH];\n    char subscription_channel[AERON_URI_MAX_LENGTH];\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_CONTROL_KEY, control_endpoint);\n        aeron_uri_string_builder_put(\n            &builder, AERON_UDP_CHANNEL_CONTROL_MODE_KEY, AERON_UDP_CHANNEL_CONTROL_MODE_DYNAMIC_VALUE);\n        aeron_uri_string_builder_put(&builder, AERON_URI_FC_KEY, \"tagged,g:99901/1,t:5s\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_TERM_LENGTH_KEY, termLength);\n\n        aeron_uri_string_builder_sprint(&builder, publication_channel, sizeof(publication_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, live_endpoint);\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_CONTROL_KEY, control_endpoint);\n\n        aeron_uri_string_builder_sprint(&builder, live_destination, sizeof(live_destination));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, replay_endpoint);\n\n        aeron_uri_string_builder_sprint(&builder, replay_destination, sizeof(replay_destination));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    const size_t initial_message_count = min_messages_per_term * 3;\n\n    aeron_archive_context_t *ctx;\n    aeron_archive_async_connect_t *async;\n\n    aeron_context_t *aeron_ctx;\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_init(&ctx));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_request_channel(ctx, \"aeron:udp?endpoint=localhost:8010\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_control_response_channel(ctx, \"aeron:udp?endpoint=localhost:0\"));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_idle_strategy(\n        ctx, aeron_idle_strategy_sleeping_idle, (void *)&m_idle_duration_ns));\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_credentials_supplier(\n        ctx,\n        encoded_credentials_supplier,\n        nullptr,\n        nullptr,\n        &default_creds_clientd));\n\n    ASSERT_EQ_ERR(0, aeron_context_init(&aeron_ctx));\n    ASSERT_EQ_ERR(0, aeron_context_set_use_conductor_agent_invoker(aeron_ctx, true));\n    ASSERT_EQ_ERR(0, aeron_context_set_dir(aeron_ctx, aeron_archive_context_get_aeron_directory_name(ctx)));\n    ASSERT_EQ_ERR(0, aeron_init(&m_aeron, aeron_ctx));\n    ASSERT_EQ_ERR(0, aeron_start(m_aeron));\n\n    ASSERT_EQ_ERR(0, aeron_archive_context_set_aeron(ctx, m_aeron));\n    ASSERT_EQ_ERR(0, aeron_archive_async_connect(&async, ctx));\n\n    // the ctx passed into async_connect gets duplicated, so it should be safe to delete it now\n    ASSERT_EQ_ERR(0, aeron_archive_context_close(ctx));\n\n    ASSERT_EQ_ERR(0, aeron_archive_async_connect_poll(&m_archive, async));\n\n    while (nullptr == m_archive)\n    {\n        idle();\n\n        ASSERT_NE(-1, aeron_archive_async_connect_poll(&m_archive, async));\n    }\n\n    ctx = aeron_archive_get_archive_context(m_archive);\n    ASSERT_FALSE(aeron_archive_context_get_owns_aeron_client(ctx));\n\n    aeron_publication_t *publication = addPublication(publication_channel, m_recordingStreamId);\n\n    int32_t session_id = aeron_publication_session_id(publication);\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(&builder, AERON_URI_GTAG_KEY, \"99901\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_SESSION_ID_KEY, session_id);\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_ENDPOINT_KEY, recording_endpoint);\n        aeron_uri_string_builder_put(&builder, AERON_UDP_CHANNEL_CONTROL_KEY, control_endpoint);\n\n        aeron_uri_string_builder_sprint(&builder, recording_channel, sizeof(recording_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put(\n            &builder, AERON_UDP_CHANNEL_CONTROL_MODE_KEY, AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL_VALUE);\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_SESSION_ID_KEY, session_id);\n\n        aeron_uri_string_builder_sprint(&builder, subscription_channel, sizeof(subscription_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    ASSERT_EQ_ERR(0, aeron_archive_start_recording(\n        nullptr,\n        m_archive,\n        recording_channel,\n        m_recordingStreamId,\n        AERON_ARCHIVE_SOURCE_LOCATION_REMOTE,\n        true));\n\n    setupCounters(session_id);\n\n    bool is_active;\n    ASSERT_EQ_ERR(0, aeron_archive_recording_pos_is_active(\n        &is_active,\n        m_counters_reader,\n        m_counter_id,\n        m_recording_id_from_counter));\n    EXPECT_TRUE(is_active);\n\n    ASSERT_EQ_ERR(m_counter_id, aeron_archive_recording_pos_find_counter_id_by_recording_id(\n        m_counters_reader,\n        m_recording_id_from_counter));\n\n    {\n        size_t sib_len = AERON_COUNTER_MAX_LABEL_LENGTH;\n        const char source_identity_buffer[AERON_COUNTER_MAX_LABEL_LENGTH] = { '\\0' };\n\n        ASSERT_EQ_ERR(0,\n            aeron_archive_recording_pos_get_source_identity(\n                m_counters_reader,\n                m_counter_id,\n                source_identity_buffer,\n                &sib_len));\n        EXPECT_STREQ(\"127.0.0.1:23265\", source_identity_buffer);\n    }\n\n    offerMessages(publication, initial_message_count);\n\n    waitUntilCaughtUp(aeron_publication_position(publication));\n\n    fragment_handler_clientd_t clientd;\n    clientd.received = 0;\n    clientd.position = 0;\n\n    aeron_subscription_t *subscription = addSubscription(subscription_channel, m_recordingStreamId);\n\n    char replay_channel[AERON_URI_MAX_LENGTH];\n    {\n        aeron_uri_string_builder_t builder;\n\n        aeron_uri_string_builder_init_new(&builder);\n\n        aeron_uri_string_builder_put(&builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\");\n        aeron_uri_string_builder_put_int32(&builder, AERON_URI_SESSION_ID_KEY, session_id);\n\n        aeron_uri_string_builder_sprint(&builder, replay_channel, sizeof(replay_channel));\n        aeron_uri_string_builder_close(&builder);\n    }\n\n    aeron_archive_replay_merge_t *replay_merge;\n\n    ASSERT_EQ_ERR(0, aeron_archive_replay_merge_init(\n        &replay_merge,\n        subscription,\n        m_archive,\n        replay_channel,\n        replay_destination,\n        live_destination,\n        m_recording_id_from_counter,\n        clientd.position,\n        aeron_epoch_clock(),\n        REPLAY_MERGE_PROGRESS_TIMEOUT_DEFAULT_MS));\n\n    while (aeron_archive_replay_merge_poll(replay_merge, nullptr, nullptr, 123) >= 0)\n    {\n        idle();\n    }\n    ASSERT_THAT(aeron_errmsg(), testing::HasSubstr(\"max concurrent replays reached 0\"));\n\n    ASSERT_EQ_ERR(0, aeron_archive_replay_merge_close(replay_merge));\n\n    DoTearDown();\n\n    ASSERT_EQ_ERR(0, aeron_close(m_aeron));\n    ASSERT_EQ_ERR(0, aeron_context_close(aeron_ctx));\n}\n"
  },
  {
    "path": "aeron-archive/src/test/cpp_wrapper/AeronArchiveWrapperTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <chrono>\n#include <thread>\n#include <iostream>\n#include <vector>\n#include <cstring>\n\n#include <gtest/gtest.h>\n\n#include \"client/archive/AeronArchive.h\"\n#include \"client/archive/CredentialsSupplier.h\"\n#include \"client/archive/RecordingPos.h\"\n#include \"client/archive/ReplayMerge.h\"\n#include \"client/archive/ReplayParams.h\"\n#include \"client/archive/ReplicationParams.h\"\n\n#include \"ChannelUriStringBuilder.h\"\n\n#include \"concurrent/SleepingIdleStrategy.h\"\n#include \"CncFileReader.h\"\n\n#if defined(__linux__) || defined(__APPLE__)\n#include <ftw.h>\n#include <spawn.h>\n#include <cstdint>\n#elif defined(_WIN32)\ntypedef intptr_t pid_t;\n#else\n#error \"must spawn Java Archive per test\"\n#endif\n\n#include \"TestArchive.h\"\nstatic const std::chrono::duration<long, std::milli> IDLE_SLEEP_MS_1(1);\n\nusing namespace aeron;\nusing namespace aeron::archive::client;\n\nclass AeronArchiveWrapperTestBase\n{\npublic:\n    ~AeronArchiveWrapperTestBase()\n    {\n        if (m_debug)\n        {\n            std::cout << m_stream.str();\n        }\n    }\n\n    void DoSetUp(std::int64_t archiveId = 42)\n    {\n        std::string sourceArchiveDir = m_archiveDir + AERON_FILE_SEP + \"source\";\n        const auto control_channel = std::string(\"aeron:udp?endpoint=localhost:8010\");\n        m_archive = std::make_shared<TestArchive>(m_context.aeronDirectoryName(), sourceArchiveDir, std::cout,\n            control_channel, \"aeron:udp?endpoint=localhost:0\", archiveId);\n\n        setCredentials(m_context);\n        m_context.controlRequestChannel(control_channel)\n            .controlResponseChannel(\"aeron:udp?endpoint=localhost:0\");\n\n        m_context.idleStrategy(m_idleStrategy);\n    }\n\n    void DoTearDown()\n    {\n    }\n\n    static std::shared_ptr<Publication> addPublication(Aeron &aeron, const std::string &channel, std::int32_t streamId)\n    {\n        std::int64_t publicationId = aeron.addPublication(channel, streamId);\n        std::shared_ptr<Publication> publication = aeron.findPublication(publicationId);\n        YieldingIdleStrategy idleStrategy;\n        while (!publication)\n        {\n            idleStrategy.idle();\n            publication = aeron.findPublication(publicationId);\n        }\n\n        return publication;\n    }\n\n    static std::shared_ptr<Subscription> addSubscription(\n        Aeron &aeron, const std::string &channel, std::int32_t streamId)\n    {\n        std::int64_t subscriptionId = aeron.addSubscription(channel, streamId);\n        std::shared_ptr<Subscription> subscription = aeron.findSubscription(subscriptionId);\n        YieldingIdleStrategy idleStrategy;\n        while (!subscription)\n        {\n            idleStrategy.idle();\n            subscription = aeron.findSubscription(subscriptionId);\n        }\n\n        return subscription;\n    }\n\n    static std::int32_t getRecordingCounterId(std::int32_t sessionId, CountersReader &countersReader)\n    {\n        std::int32_t counterId;\n        while (CountersReader::NULL_COUNTER_ID ==\n            (counterId = RecordingPos::findCounterIdBySessionId(countersReader, sessionId)))\n        {\n            std::this_thread::yield();\n        }\n\n        return counterId;\n    }\n\n    static void offerMessagesToPosition(\n        Publication &publication,\n        int64_t minimumPosition,\n        const std::string &messagePrefix = \"Message \",\n        std::size_t startCount = 0)\n    {\n        BufferClaim bufferClaim;\n        YieldingIdleStrategy idleStrategy;\n\n        for (std::size_t i = 0; publication.position() < minimumPosition; i++)\n        {\n            std::size_t index = i + startCount;\n            const std::string message = messagePrefix + std::to_string(index);\n            while (publication.tryClaim(static_cast<util::index_t>(message.length()), bufferClaim) < 0)\n            {\n                idleStrategy.idle();\n            }\n\n            bufferClaim.buffer().putStringWithoutLength(bufferClaim.offset(), message);\n            bufferClaim.commit();\n        }\n    }\n\n    static void offerMessages(\n        Publication &publication,\n        std::size_t messageCount,\n        const std::string &messagePrefix,\n        std::size_t startCount = 0)\n    {\n        BufferClaim bufferClaim;\n        YieldingIdleStrategy idleStrategy;\n\n        for (std::size_t i = 0; i < messageCount; i++)\n        {\n            std::size_t index = i + startCount;\n            const std::string message = messagePrefix + std::to_string(index);\n            while (publication.tryClaim(static_cast<util::index_t>(message.length()), bufferClaim) < 0)\n            {\n                idleStrategy.idle();\n            }\n\n            bufferClaim.buffer().putStringWithoutLength(bufferClaim.offset(), message);\n            bufferClaim.commit();\n        }\n    }\n\n    static void offerMessagesExclusive(\n        ExclusivePublication &publication,\n        std::size_t messageCount,\n        const std::string &messagePrefix,\n        std::size_t startCount = 0)\n    {\n        BufferClaim bufferClaim;\n        YieldingIdleStrategy idleStrategy;\n\n        for (std::size_t i = 0; i < messageCount; i++)\n        {\n            std::size_t index = i + startCount;\n            const std::string message = messagePrefix + std::to_string(index);\n            while (publication.tryClaim(static_cast<util::index_t>(message.length()), bufferClaim) < 0)\n            {\n                idleStrategy.idle();\n            }\n\n            bufferClaim.buffer().putStringWithoutLength(bufferClaim.offset(), message);\n            bufferClaim.commit();\n        }\n    }\n\n    void consumeMessages(Subscription &subscription, std::size_t messageCount, const std::string &messagePrefix, std::size_t startOffset = 0) const\n    {\n        std::size_t received = 0;\n        YieldingIdleStrategy idleStrategy;\n\n        fragment_handler_t handler =\n            [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n            {\n                const std::string expected = messagePrefix + std::to_string(received + startOffset);\n                const std::string actual = buffer.getStringWithoutLength(offset, static_cast<std::size_t>(length));\n\n                EXPECT_EQ(expected, actual);\n\n                received++;\n            };\n\n        while (received < messageCount)\n        {\n            if (0 == subscription.poll(handler, m_fragmentLimit))\n            {\n                idleStrategy.idle();\n            }\n        }\n\n        ASSERT_EQ(received, messageCount);\n    }\n\n    std::int64_t consumeMessagesExpectingBound(\n        Subscription &subscription,\n        std::int64_t boundPosition,\n        const std::string &messagePrefix,\n        std::int64_t timeoutMs) const\n    {\n        std::size_t received = 0;\n        std::int64_t position = 0;\n        YieldingIdleStrategy idleStrategy;\n\n        fragment_handler_t handler =\n            [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n            {\n                const std::string expected = messagePrefix + std::to_string(received);\n                const std::string actual = buffer.getStringWithoutLength(offset, static_cast<std::size_t>(length));\n                EXPECT_EQ(expected, actual);\n\n                received++;\n                position = header.position();\n            };\n\n        const long long deadlineMs = currentTimeMillis() + timeoutMs;\n\n        while (currentTimeMillis() < deadlineMs)\n        {\n            if (0 == subscription.poll(handler, m_fragmentLimit))\n            {\n                idleStrategy.idle();\n            }\n        }\n\n        return position;\n    }\n\n    bool attemptReplayMerge(\n        ReplayMerge &replayMerge,\n        Publication &publication,\n        fragment_handler_t &handler,\n        const std::string &messagePrefix,\n        std::size_t totalMessageCount,\n        std::size_t &messagesPublished,\n        std::size_t &receivedMessageCount) const\n    {\n        YieldingIdleStrategy idleStrategy;\n\n        for (std::size_t i = messagesPublished; i < totalMessageCount; i++)\n        {\n            BufferClaim bufferClaim;\n            const std::string message = messagePrefix + std::to_string(i);\n\n            idleStrategy.reset();\n            while (publication.tryClaim(static_cast<util::index_t>(message.length()), bufferClaim) < 0)\n            {\n                idleStrategy.idle();\n                int fragments = replayMerge.poll(handler, m_fragmentLimit);\n                if (0 == fragments && replayMerge.hasFailed())\n                {\n                    return false;\n                }\n            }\n\n            bufferClaim.buffer().putStringWithoutLength(bufferClaim.offset(), message);\n            bufferClaim.commit();\n            ++messagesPublished;\n\n            int fragments = replayMerge.poll(handler, m_fragmentLimit);\n            if (0 == fragments && replayMerge.hasFailed())\n            {\n                return false;\n            }\n        }\n\n        while (!replayMerge.isMerged())\n        {\n            int fragments = replayMerge.poll(handler, m_fragmentLimit);\n            if (0 == fragments && replayMerge.hasFailed())\n            {\n                return false;\n            }\n            idleStrategy.idle(fragments);\n        }\n\n        Image &image = *replayMerge.image();\n        while (receivedMessageCount < totalMessageCount)\n        {\n            int fragments = image.poll(handler, m_fragmentLimit);\n            if (0 == fragments && image.isClosed())\n            {\n                return false;\n            }\n            idleStrategy.idle(fragments);\n        }\n\n        return true;\n    }\n\n    void startDestArchive()\n    {\n        const std::string aeronDir = aeron::Context::defaultAeronPath() + \"_dest\";\n        const std::string archiveDir = m_archiveDir + AERON_FILE_SEP + \"dest\";\n        const std::string controlChannel = \"aeron:udp?endpoint=localhost:8011\";\n        const std::string replicationChannel = \"aeron:udp?endpoint=localhost:8012\";\n        m_destArchive = std::make_shared<TestArchive>(\n            aeronDir, archiveDir, std::cout, controlChannel, replicationChannel, -7777);\n        m_destContext.controlRequestChannel(controlChannel)\n            .controlResponseChannel(\"aeron:udp?endpoint=localhost:0\");\n        setCredentials(m_destContext);\n    }\n\n    std::tuple<std::int64_t, std::int64_t, std::int64_t> recordData(\n        bool tryStop,\n        AeronArchive &aeronArchive,\n        size_t messageCount,\n        const std::string &prefix)\n    {\n        const std::int64_t subscriptionId = aeronArchive.startRecording(\n            m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n        \n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive.context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeronArchive.context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        std::int32_t sessionId = publication->sessionId();\n\n        CountersReader &countersReader = aeronArchive.context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        std::int64_t recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n        EXPECT_TRUE(RecordingPos::isActive(countersReader, counterId, recordingId));\n        EXPECT_EQ(counterId, RecordingPos::findCounterIdByRecordingId(countersReader, recordingId));\n        EXPECT_EQ(\"aeron:ipc\", RecordingPos::getSourceIdentity(countersReader, counterId));\n\n        std::size_t halfCount = messageCount / 2;\n        offerMessages(*publication, halfCount, prefix);\n        std::int64_t halfwayPosition = publication->position();\n        offerMessages(*publication, halfCount, prefix, halfCount);\n        consumeMessages(*subscription, messageCount, prefix);\n\n        std::int64_t stopPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        if (tryStop)\n        {\n            EXPECT_TRUE(aeronArchive.tryStopRecording(subscriptionId));\n        }\n        else\n        {\n            aeronArchive.stopRecording(subscriptionId);\n        }\n\n        return std::make_tuple(recordingId, stopPosition, halfwayPosition);\n    }\n\nprotected:\n    std::shared_ptr<TestArchive> m_destArchive;\n    AeronArchive::Context_t m_destContext;\n\n    const int m_fragmentLimit = 10;\n    const std::string m_recordingChannel = \"aeron:udp?endpoint=localhost:3333\";\n    const std::int32_t m_recordingStreamId = 33;\n\n    const std::string m_replayChannel = \"aeron:udp?endpoint=localhost:6666\";\n    const std::int32_t m_replayStreamId = 66;\n\n    std::shared_ptr<TestArchive> m_archive;\n    const std::string m_archiveDir = ARCHIVE_DIR;\n    aeron::archive::client::Context m_context;\n\n    std::ostringstream m_stream;\n    bool m_debug = true;\n    SleepingIdleStrategy m_idleStrategy = SleepingIdleStrategy(IDLE_SLEEP_MS_1);\n\n\n    static void setCredentials(AeronArchive::Context_t &context)\n    {\n        auto onEncodedCredentials =\n            []() -> std::pair<const char *, std::uint32_t>\n            {\n                std::string credentials(\"admin:admin\");\n\n                char *arr = new char[credentials.length() + 1];\n                std::memcpy(arr, credentials.data(), credentials.length());\n                arr[credentials.length()] = '\\0';\n\n                return { arr, static_cast<std::uint32_t>(credentials.length()) };\n            };\n\n        context.credentialsSupplier(CredentialsSupplier(onEncodedCredentials));\n    }\n\nprivate:\n};\n\nclass AeronArchiveWrapperTest : public AeronArchiveWrapperTestBase, public testing::Test\n{\npublic:\n    void SetUp() final\n    {\n        DoSetUp();\n    }\n\n    void TearDown() final\n    {\n        DoTearDown();\n    }\n};\n\nclass AeronArchiveWrapperIdTest : public AeronArchiveWrapperTestBase, public testing::Test\n{\n};\n\nclass AeronArchiveWrapperParamTest : public AeronArchiveWrapperTestBase, public testing::TestWithParam<bool>\n{\npublic:\n    void SetUp() final\n    {\n        DoSetUp();\n    }\n\n    void TearDown() final\n    {\n        DoTearDown();\n    }\n};\n\nINSTANTIATE_TEST_SUITE_P(AeronArchive, AeronArchiveWrapperParamTest, testing::Values(true, false));\n\nTEST_F(AeronArchiveWrapperTest, shouldAsyncConnectToArchive)\n{\n    SleepingIdleStrategy idleStrategy(IDLE_SLEEP_MS_1);\n    std::shared_ptr<AeronArchive::AsyncConnect> asyncConnect = AeronArchive::asyncConnect(m_context);\n\n    std::shared_ptr<AeronArchive> aeronArchive = asyncConnect->poll();\n    while (!aeronArchive)\n    {\n        idleStrategy.idle();\n\n        aeronArchive = asyncConnect->poll();\n    }\n\n    EXPECT_TRUE(aeronArchive->controlResponseSubscription().isConnected());\n    EXPECT_EQ(42, aeronArchive->archiveId());\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldAsyncConnectToArchiveWithPrebuiltAeron)\n{\n    aeron::Context aeronCtx;\n    aeronCtx.aeronDir(m_context.aeronDirectoryName());\n    auto aeron = std::make_shared<Aeron>(aeronCtx);\n    m_context.aeron(aeron);\n\n    SleepingIdleStrategy idleStrategy(IDLE_SLEEP_MS_1);\n    std::shared_ptr<AeronArchive::AsyncConnect> asyncConnect = AeronArchive::asyncConnect(m_context);\n\n    std::shared_ptr<AeronArchive> aeronArchive = asyncConnect->poll();\n    while (!aeronArchive)\n    {\n        idleStrategy.idle();\n\n        aeronArchive = asyncConnect->poll();\n    }\n\n    EXPECT_TRUE(aeronArchive->controlResponseSubscription().isConnected());\n    EXPECT_EQ(42, aeronArchive->archiveId());\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldConnectToArchive)\n{\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    EXPECT_TRUE(aeronArchive->controlResponseSubscription().isConnected());\n    EXPECT_EQ(42, aeronArchive->archiveId());\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldConnectToArchiveWithPrebuiltAeron)\n{\n    aeron::Context aeronCtx;\n    aeronCtx.aeronDir(m_context.aeronDirectoryName());\n    auto aeron = std::make_shared<Aeron>(aeronCtx);\n    m_context.aeron(aeron);\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    EXPECT_TRUE(aeronArchive->controlResponseSubscription().isConnected());\n    EXPECT_EQ(42, aeronArchive->archiveId());\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldConnectToArchiveAndCallInvoker)\n{\n    bool invokerCalled = false;\n    m_context.delegatingInvoker([&invokerCalled](){ invokerCalled = true; });\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    ASSERT_TRUE(invokerCalled);\n\n    EXPECT_TRUE(aeronArchive->controlResponseSubscription().isConnected());\n    EXPECT_EQ(42, aeronArchive->archiveId());\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldObserveErrorOnBadDataOnControlResponseChannel)\n{\n    m_context.errorHandler(\n        [](const std::exception &exception)\n        {\n            fprintf(stderr, \"ERROR HANDLER >> %s\\n\", exception.what());\n        });\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    EXPECT_TRUE(aeronArchive->controlResponseSubscription().isConnected());\n    auto resolved_uri = aeronArchive->controlResponseSubscription().tryResolveChannelEndpointPort();\n\n    std::string errorMessage;\n    try\n    {\n        aeronArchive->getStartPosition(1234);\n        FAIL();\n    }\n    catch (std::exception &exception)\n    {\n        errorMessage = std::string(exception.what());\n        EXPECT_NE(std::string::npos, errorMessage.find(\"errorCode=5, error: unknown recording id: 1234\"));\n    }\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldRecordPublicationAndFindRecording)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingIdFromCounter;\n    std::int64_t stopPosition;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    const std::int64_t subscriptionId = aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        sessionId = publication->sessionId();\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingIdFromCounter = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        EXPECT_EQ(aeronArchive->getRecordingPosition(recordingIdFromCounter), stopPosition);\n        EXPECT_EQ(aeronArchive->getStopPosition(recordingIdFromCounter), aeron::NULL_VALUE);\n        EXPECT_EQ(aeronArchive->getMaxRecordedPosition(recordingIdFromCounter), stopPosition);\n    }\n\n    aeronArchive->stopRecording(subscriptionId);\n\n    const std::int64_t recordingId = aeronArchive->findLastMatchingRecording(\n        0, \"endpoint=localhost:3333\", m_recordingStreamId, sessionId);\n\n    EXPECT_EQ(recordingIdFromCounter, recordingId);\n    EXPECT_EQ(aeronArchive->getStopPosition(recordingIdFromCounter), stopPosition);\n\n    const std::int32_t count = aeronArchive->listRecording(\n        recordingId,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            EXPECT_EQ(recordingId, recordingDescriptor.m_recordingId);\n            EXPECT_EQ(recordingDescriptor.m_streamId, m_recordingStreamId);\n        });\n\n    EXPECT_EQ(count, 1);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldRecordPublicationAndTryStopById)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingIdFromCounter;\n    std::int64_t stopPosition;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        sessionId = publication->sessionId();\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingIdFromCounter = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        EXPECT_EQ(aeronArchive->getRecordingPosition(recordingIdFromCounter), stopPosition);\n        EXPECT_EQ(aeronArchive->getStopPosition(recordingIdFromCounter), aeron::NULL_VALUE);\n        EXPECT_EQ(aeronArchive->getMaxRecordedPosition(recordingIdFromCounter), stopPosition);\n\n        // Interesting... if I move the following tryStop out of the current scope (just past the closing curly braces)\n        // then tryStop fails - it seems that closing the scope causes the publisher and subscriber to close,\n        // which stops the recording, and then the subsequent tryStop fails.\n        // ... And by 'fails', I mean the function returns 'false'.  No exception is thrown.\n        EXPECT_TRUE(aeronArchive->tryStopRecordingByIdentity(recordingIdFromCounter));\n    }\n\n    const std::int64_t recordingId = aeronArchive->findLastMatchingRecording(\n        0, \"endpoint=localhost:3333\", m_recordingStreamId, sessionId);\n\n    EXPECT_EQ(recordingIdFromCounter, recordingId);\n    EXPECT_EQ(aeronArchive->getStopPosition(recordingIdFromCounter), stopPosition);\n\n    const std::int32_t count = aeronArchive->listRecording(\n        recordingId,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            EXPECT_EQ(recordingId, recordingDescriptor.m_recordingId);\n            EXPECT_EQ(recordingDescriptor.m_streamId, m_recordingStreamId);\n        });\n\n    EXPECT_EQ(count, 1);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldRecordThenReplay)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingId;\n    std::int64_t stopPosition;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    const std::int64_t subscriptionId = aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        sessionId = publication->sessionId();\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n        EXPECT_TRUE(RecordingPos::isActive(countersReader, counterId, recordingId));\n        EXPECT_EQ(counterId, RecordingPos::findCounterIdByRecordingId(countersReader, recordingId));\n        EXPECT_EQ(\"aeron:ipc\", RecordingPos::getSourceIdentity(countersReader, counterId));\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    aeronArchive->stopRecording(subscriptionId);\n\n    YieldingIdleStrategy idleStrategy;\n    while (aeronArchive->getStopPosition(recordingId) != stopPosition)\n    {\n        idleStrategy.idle();\n    }\n\n    {\n        const std::int64_t position = 0L;\n        const std::int64_t length = stopPosition - position;\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_replayChannel, m_replayStreamId);\n\n        aeronArchive->startReplay(\n            recordingId,\n            m_replayChannel,\n            m_replayStreamId,\n            ReplayParams().position(position).length(length).fileIoMaxLength(4096));\n\n        consumeMessages(*subscription, messageCount, messagePrefix);\n        EXPECT_EQ(stopPosition, subscription->imageByIndex(0)->position());\n    }\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldRecordThenBoundedReplay)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingIdFromCounter;\n    std::int64_t stopPosition;\n    YieldingIdleStrategy idleStrategy;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    const std::int64_t subscriptionId = aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        sessionId = publication->sessionId();\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingIdFromCounter = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = publication->position();\n\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    aeronArchive->stopRecording(subscriptionId);\n    std::string counterName = \"BoundedTestCounter\";\n    int64_t counterId = aeronArchive->context().aeron()->addCounter(\n        10001, reinterpret_cast<const uint8_t *>(counterName.c_str()), counterName.length(), counterName);\n    std::shared_ptr<Counter> counter;\n    while (nullptr == (counter = aeronArchive->context().aeron()->findCounter(counterId)))\n    {\n        idleStrategy.idle();\n    }\n\n    while (aeronArchive->getStopPosition(recordingIdFromCounter) != stopPosition)\n    {\n        idleStrategy.idle();\n    }\n\n    {\n        const std::int64_t position = 0L;\n        const std::int64_t length = stopPosition - position;\n        const std::int64_t boundedLength = (length / 4) * 3;\n        counter->set(boundedLength);\n\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_replayChannel, m_replayStreamId);\n\n        aeronArchive->startReplay(\n            recordingIdFromCounter,\n            m_replayChannel,\n            m_replayStreamId,\n            ReplayParams()\n                .position(position)\n                .length(length)\n                .boundingLimitCounterId(counter->id())\n                .fileIoMaxLength(4096));\n\n        const std::int64_t positionConsumed = consumeMessagesExpectingBound(\n            *subscription, position + boundedLength, messagePrefix, 1000);\n\n        EXPECT_LT(position + (length / 2), positionConsumed);\n        EXPECT_LE(positionConsumed, position + boundedLength);\n    }\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldRecordThenReplayThenTruncate)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingIdFromCounter;\n    std::int64_t stopPosition;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    const std::int64_t subscriptionId = aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        sessionId = publication->sessionId();\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingIdFromCounter = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        EXPECT_EQ(aeronArchive->getRecordingPosition(recordingIdFromCounter), stopPosition);\n        EXPECT_EQ(aeronArchive->getStopPosition(recordingIdFromCounter), aeron::NULL_VALUE);\n        EXPECT_EQ(aeronArchive->getMaxRecordedPosition(recordingIdFromCounter), stopPosition);\n    }\n\n    aeronArchive->stopRecording(subscriptionId);\n\n    const std::int64_t recordingId = aeronArchive->findLastMatchingRecording(\n        0, \"endpoint=localhost:3333\", m_recordingStreamId, sessionId);\n\n    EXPECT_EQ(recordingIdFromCounter, recordingId);\n    EXPECT_EQ(aeronArchive->getStopPosition(recordingIdFromCounter), stopPosition);\n\n    const std::int64_t position = 0L;\n\n    {\n        const std::int64_t length = stopPosition - position;\n        std::shared_ptr<Subscription> subscription;\n\n        subscription = aeronArchive->replay(\n            recordingId,\n            m_replayChannel,\n            m_replayStreamId,\n            ReplayParams().position(position).length(length).fileIoMaxLength(4096));\n\n        consumeMessages(*subscription, messageCount, messagePrefix);\n        EXPECT_EQ(stopPosition, subscription->imageByIndex(0)->position());\n    }\n\n    aeronArchive->truncateRecording(recordingId, position);\n\n    const std::int32_t count = aeronArchive->listRecording(\n        recordingId,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            EXPECT_EQ(recordingDescriptor.m_startPosition, recordingDescriptor.m_stopPosition);\n        });\n\n    EXPECT_EQ(count, 1);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldRecordAndCancelReplayEarly)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int64_t recordingId;\n    std::int64_t stopPosition;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = aeronArchive->addRecordedPublication(\n            m_recordingChannel, m_recordingStreamId);\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(publication->sessionId(), countersReader);\n        recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        EXPECT_EQ(aeronArchive->getRecordingPosition(recordingId), stopPosition);\n\n        aeronArchive->stopRecording(publication);\n\n        idleStrategy.reset();\n        while (NULL_POSITION != aeronArchive->getRecordingPosition(recordingId))\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    const std::int64_t position = 0L;\n    const std::int64_t length = stopPosition - position;\n\n    std::int64_t replaySessionId;\n    replaySessionId = aeronArchive->startReplay(\n        recordingId,\n        m_replayChannel,\n        m_replayStreamId,\n        ReplayParams().position(position).length(length).fileIoMaxLength(4096));\n\n    aeronArchive->stopReplay(replaySessionId);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldRecordAndCancelReplayEarlyWithExclusivePublication)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int64_t recordingId;\n    std::int64_t stopPosition;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<ExclusivePublication> exclusivePublication = aeronArchive->addRecordedExclusivePublication(\n            m_recordingChannel, m_recordingStreamId);\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(exclusivePublication->sessionId(), countersReader);\n        recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessagesExclusive(*exclusivePublication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = exclusivePublication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        EXPECT_EQ(aeronArchive->getRecordingPosition(recordingId), stopPosition);\n\n        aeronArchive->stopRecording(exclusivePublication);\n\n        idleStrategy.reset();\n        while (NULL_POSITION != aeronArchive->getRecordingPosition(recordingId))\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    const std::int64_t position = 0L;\n    const std::int64_t length = stopPosition - position;\n\n    std::int64_t replaySessionId;\n    replaySessionId = aeronArchive->startReplay(\n        recordingId,\n        m_replayChannel,\n        m_replayStreamId,\n        ReplayParams().position(position).length(length).fileIoMaxLength(4096));\n\n    aeronArchive->stopReplay(replaySessionId);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldGetStartPosition)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    std::shared_ptr<Subscription> subscription = addSubscription(\n        *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n    std::shared_ptr<Publication> publication = addPublication(\n        *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n    offerMessages(*publication, messageCount, messagePrefix);\n    consumeMessages(*subscription, messageCount, messagePrefix);\n\n    const std::int64_t halfwayPosition = publication->position();\n\n    aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL, true);\n\n    CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n    const std::int32_t counterId = getRecordingCounterId(publication->sessionId(), countersReader);\n    const std::int64_t recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n    offerMessages(*publication, messageCount, messagePrefix);\n    consumeMessages(*subscription, messageCount, messagePrefix);\n\n    const std::int64_t endPosition = publication->position();\n\n    YieldingIdleStrategy idleStrategy;\n    while (countersReader.getCounterValue(counterId) < endPosition)\n    {\n        idleStrategy.idle();\n    }\n\n    EXPECT_EQ(halfwayPosition, aeronArchive->getStartPosition(recordingId));\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldReplayRecordingFromLateJoinPosition)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL, true);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(publication->sessionId(), countersReader);\n        const std::int64_t recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        const std::int64_t currentPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < currentPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        {\n            std::shared_ptr<Subscription> replaySubscription;\n\n            replaySubscription = aeronArchive->replay(\n                recordingId,\n                m_replayChannel,\n                m_replayStreamId,\n                ReplayParams().position(currentPosition).fileIoMaxLength(4096));\n\n            offerMessages(*publication, messageCount, messagePrefix);\n            consumeMessages(*subscription, messageCount, messagePrefix);\n            consumeMessages(*replaySubscription, messageCount, messagePrefix);\n\n            const std::int64_t endPosition = publication->position();\n            EXPECT_EQ(endPosition, replaySubscription->imageByIndex(0)->position());\n        }\n    }\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldListRegisteredRecordingSubscriptions)\n{\n    std::vector<RecordingSubscriptionDescriptor> descriptors;\n\n    recording_subscription_descriptor_consumer_t consumer =\n        [&descriptors](const RecordingSubscriptionDescriptor &descriptor)\n        {\n            descriptors.emplace_back(descriptor);\n        };\n\n    const std::int32_t expectedStreamId = 7;\n    const std::string channelOne = \"aeron:ipc\";\n    const std::string channelTwo = \"aeron:udp?endpoint=localhost:5678\";\n    const std::string channelThree = \"aeron:udp?endpoint=localhost:4321\";\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    auto countersReader = aeronArchive->context().aeron()->countersReader();\n\n    const std::int64_t subIdOne = aeronArchive->startRecording(\n        channelOne, expectedStreamId, AeronArchive::SourceLocation::LOCAL);\n    const std::int64_t subIdTwo = aeronArchive->startRecording(\n        channelTwo, expectedStreamId + 1, AeronArchive::SourceLocation::LOCAL);\n    const std::int64_t subIdThree = aeronArchive->startRecording(\n        channelThree, expectedStreamId + 2, AeronArchive::SourceLocation::LOCAL);\n\n    std::shared_ptr<Publication> pub2 = addPublication(\n        *aeronArchive->context().aeron(), channelTwo, expectedStreamId + 1);\n    std::shared_ptr<Publication> pub3 = addPublication(\n        *aeronArchive->context().aeron(), channelThree, expectedStreamId + 2);\n\n    // await recording started\n    const auto sub2CounterId = getRecordingCounterId(pub2->sessionId(), countersReader);\n    const auto sub3CounterId = getRecordingCounterId(pub3->sessionId(), countersReader);\n\n    const std::int32_t countOne = aeronArchive->listRecordingSubscriptions(\n        0, 5, \"ipc\", expectedStreamId, true, consumer);\n\n    EXPECT_EQ(1uL, descriptors.size());\n    EXPECT_EQ(1L, countOne);\n\n    descriptors.clear();\n\n    const std::int32_t countTwo = aeronArchive->listRecordingSubscriptions(\n        0, 5, \"\", expectedStreamId, false, consumer);\n\n    EXPECT_EQ(3uL, descriptors.size());\n    EXPECT_EQ(3L, countTwo);\n\n    aeronArchive->stopRecording(subIdTwo);\n    descriptors.clear();\n\n    // await recording stopped\n    while (CountersReader::RECORD_RECLAIMED != countersReader.getCounterState(sub2CounterId))\n    {\n        std::this_thread::yield();\n    }\n\n    const std::int32_t stateAllocated = CountersReader::RECORD_ALLOCATED;\n    EXPECT_EQ(stateAllocated, countersReader.getCounterState(sub3CounterId));\n\n    const std::int32_t countThree = aeronArchive->listRecordingSubscriptions(\n        0, 5, \"\", expectedStreamId, false, consumer);\n\n    EXPECT_EQ(2uL, descriptors.size());\n    EXPECT_EQ(2L, countThree);\n\n    EXPECT_EQ(1L, std::count_if(\n        descriptors.begin(),\n        descriptors.end(),\n        [=](const RecordingSubscriptionDescriptor &descriptor){ return descriptor.m_subscriptionId == subIdOne; }));\n\n    EXPECT_EQ(1L, std::count_if(\n        descriptors.begin(),\n        descriptors.end(),\n        [=](const RecordingSubscriptionDescriptor &descriptor){ return descriptor.m_subscriptionId == subIdThree; }));\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldMergeFromReplayToLive)\n{\n    const std::size_t termLength = 64 * 1024;\n    const std::string messagePrefix = \"Message \";\n    const std::size_t minMessagesPerTerm = termLength / (messagePrefix.length() + DataFrameHeader::LENGTH);\n    const std::string controlEndpoint = \"localhost:23265\";\n    const std::string recordingEndpoint = \"localhost:23266\";\n    const std::string liveEndpoint = \"localhost:23267\";\n    const std::string replayEndpoint = \"localhost:0\";\n\n    const std::string publicationChannel = ChannelUriStringBuilder()\n        .media(UDP_MEDIA)\n        .controlEndpoint(controlEndpoint)\n        .controlMode(MDC_CONTROL_MODE_DYNAMIC)\n        .flowControl(\"tagged,g:99901/1,t:5s\")\n        .termLength(termLength)\n        .build();\n\n    const std::string liveDestination = ChannelUriStringBuilder()\n        .media(UDP_MEDIA)\n        .endpoint(liveEndpoint)\n        .controlEndpoint(controlEndpoint)\n        .build();\n\n    const std::string replayDestination = ChannelUriStringBuilder()\n        .media(UDP_MEDIA)\n        .endpoint(replayEndpoint)\n        .build();\n\n    const std::size_t initialMessageCount = minMessagesPerTerm * 3;\n    const std::size_t subsequentMessageCount = minMessagesPerTerm * 3;\n    const std::size_t totalMessageCount = initialMessageCount + subsequentMessageCount;\n    YieldingIdleStrategy idleStrategy;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    std::shared_ptr<Publication> publication = addPublication(\n        *aeronArchive->context().aeron(), publicationChannel, m_recordingStreamId);\n\n    const std::int32_t sessionId = publication->sessionId();\n\n    const std::string recordingChannel = ChannelUriStringBuilder()\n        .media(UDP_MEDIA)\n        .groupTag(99901)\n        .sessionId(sessionId)\n        .endpoint(recordingEndpoint)\n        .controlEndpoint(controlEndpoint)\n        .build();\n\n    const std::string subscriptionChannel = ChannelUriStringBuilder()\n        .media(UDP_MEDIA)\n        .controlMode(MDC_CONTROL_MODE_MANUAL)\n        .sessionId(sessionId)\n        .build();\n\n    aeronArchive->startRecording(\n        recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::REMOTE, true);\n\n    CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n    const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n    const std::int64_t recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n    EXPECT_TRUE(RecordingPos::isActive(countersReader, counterId, recordingId));\n    EXPECT_EQ(counterId, RecordingPos::findCounterIdByRecordingId(countersReader, recordingId));\n    EXPECT_EQ(\"127.0.0.1:23265\", RecordingPos::getSourceIdentity(countersReader, counterId));\n\n    offerMessages(*publication, initialMessageCount, messagePrefix);\n    while (countersReader.getCounterValue(counterId) < publication->position())\n    {\n        idleStrategy.idle();\n    }\n\n    std::size_t messagesPublished = initialMessageCount;\n    std::size_t receivedMessageCount = 0;\n    std::int64_t receivedPosition = 0;\n\n    fragment_handler_t fragment_handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            const std::string expected = messagePrefix + std::to_string(receivedMessageCount);\n            const std::string actual = buffer.getStringWithoutLength(offset, static_cast<std::size_t>(length));\n\n            EXPECT_EQ(expected, actual);\n\n            receivedMessageCount++;\n            receivedPosition = header.position();\n        };\n\n    while (true)\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), subscriptionChannel, m_recordingStreamId);\n\n        const std::string replayChannel = ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .sessionId(publication->sessionId())\n            .build();\n\n        ReplayMerge replayMerge(\n            subscription,\n            aeronArchive,\n            replayChannel,\n            replayDestination,\n            liveDestination,\n            recordingId,\n            receivedPosition);\n\n        if (attemptReplayMerge(\n            replayMerge,\n            *publication,\n            fragment_handler,\n            messagePrefix,\n            totalMessageCount,\n            messagesPublished,\n            receivedMessageCount))\n        {\n            break;\n        }\n\n        idleStrategy.reset();\n        idleStrategy.idle();\n    }\n\n    EXPECT_EQ(receivedMessageCount, totalMessageCount);\n    EXPECT_EQ(receivedPosition, publication->position());\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldExceptionForIncorrectInitialCredentials)\n{\n    auto onEncodedCredentials =\n        []() -> std::pair<const char *, std::uint32_t>\n        {\n            std::string credentials(\"admin:NotAdmin\");\n\n            char *arr = new char[credentials.length() + 1];\n            std::memcpy(arr, credentials.data(), credentials.length());\n            arr[credentials.length()] = '\\0';\n\n            return { arr, static_cast<std::uint32_t>(credentials.length()) };\n        };\n\n    m_context.credentialsSupplier(CredentialsSupplier(onEncodedCredentials));\n\n    try\n    {\n        std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n        FAIL();\n    }\n    catch (IllegalArgumentException &exception)\n    {\n        EXPECT_NE(std::string::npos, std::string(exception.what()).find(\"authentication rejected\"));\n    }\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldBeAbleToHandleBeingChallenged)\n{\n    auto onEncodedCredentials =\n        []() -> std::pair<const char *, std::uint32_t>\n        {\n            std::string credentials(\"admin:adminC\");\n\n            char *arr = new char[credentials.length() + 1];\n            std::memcpy(arr, credentials.data(), credentials.length());\n            arr[credentials.length()] = '\\0';\n\n            return { arr, static_cast<std::uint32_t>(credentials.length()) };\n        };\n\n    auto onChallenge =\n        [](std::pair<const char *, std::uint32_t> encodedChallenge) -> std::pair<const char *, std::uint32_t>\n        {\n            std::string credentials(\"admin:CSadmin\");\n\n            char *arr = new char[credentials.length() + 1];\n            std::memcpy(arr, credentials.data(), credentials.length());\n            arr[credentials.length()] = '\\0';\n\n            return { arr, static_cast<std::uint32_t>(credentials.length()) };\n        };\n\n    m_context.credentialsSupplier(CredentialsSupplier(onEncodedCredentials, onChallenge));\n\n    ASSERT_NO_THROW(\n        {\n            std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n        });\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldExceptionForIncorrectChallengeCredentials)\n{\n    auto onEncodedCredentials =\n        []() -> std::pair<const char *, std::uint32_t>\n        {\n            std::string credentials(\"admin:adminC\");\n\n            char *arr = new char[credentials.length() + 1];\n            std::memcpy(arr, credentials.data(), credentials.length());\n            arr[credentials.length()] = '\\0';\n\n            return { arr, static_cast<std::uint32_t>(credentials.length()) };\n        };\n\n    auto onChallenge =\n        [](std::pair<const char *, std::uint32_t> encodedChallenge) -> std::pair<const char *, std::uint32_t>\n        {\n            std::string credentials(\"admin:adminNoCS\");\n\n            char *arr = new char[credentials.length() + 1];\n            std::memcpy(arr, credentials.data(), credentials.length());\n            arr[credentials.length()] = '\\0';\n\n            return { arr, static_cast<std::uint32_t>(credentials.length()) };\n        };\n\n    m_context.credentialsSupplier(CredentialsSupplier(onEncodedCredentials, onChallenge));\n\n    try\n    {\n        std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n        FAIL();\n    }\n    catch (IllegalArgumentException &exception)\n    {\n        EXPECT_NE(std::string::npos, std::string(exception.what()).find(\"authentication rejected\"));\n    }\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldPurgeStoppedRecording)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingIdFromCounter;\n    std::int64_t stopPosition;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    const std::int64_t subscriptionId = aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        sessionId = publication->sessionId();\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingIdFromCounter = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        EXPECT_EQ(aeronArchive->getRecordingPosition(recordingIdFromCounter), stopPosition);\n        EXPECT_EQ(aeronArchive->getStopPosition(recordingIdFromCounter), aeron::NULL_VALUE);\n    }\n\n    aeronArchive->stopRecording(subscriptionId);\n\n    const std::int64_t recordingId = aeronArchive->findLastMatchingRecording(\n        0, \"endpoint=localhost:3333\", m_recordingStreamId, sessionId);\n\n    EXPECT_EQ(recordingIdFromCounter, recordingId);\n    EXPECT_EQ(aeronArchive->getStopPosition(recordingIdFromCounter), stopPosition);\n\n    EXPECT_EQ(1, aeronArchive->purgeRecording(recordingId));\n\n    const std::int32_t count = aeronArchive->listRecording(\n        recordingId,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            FAIL();\n        });\n\n    EXPECT_EQ(count, 0);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldReadRecordingDescriptor)\n{\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    std::shared_ptr<Aeron> aeron = aeronArchive->context().aeron();\n\n    std::shared_ptr<Publication> publication = addPublication(\n        *aeron, m_recordingChannel, m_recordingStreamId);\n\n    const std::int32_t sessionId = publication->sessionId();\n\n    const std::int64_t subscriptionId = aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    const std::int64_t recordingId =\n        [&]\n        {\n            CountersReader &countersReader = aeron->countersReader();\n            const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n            return RecordingPos::getRecordingId(countersReader, counterId);\n        }();\n\n    aeronArchive->stopRecording(subscriptionId);\n\n    const std::int32_t count = aeronArchive->listRecording(\n        recordingId,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            EXPECT_EQ(recordingDescriptor.m_sessionId, sessionId);\n            EXPECT_EQ(recordingDescriptor.m_recordingId, recordingId);\n            EXPECT_EQ(recordingDescriptor.m_streamId, m_recordingStreamId);\n            EXPECT_EQ(recordingDescriptor.m_originalChannel, m_recordingChannel);\n        });\n\n    EXPECT_EQ(count, 1);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldFindMultipleRecordingDescriptors)\n{\n    std::set<std::int32_t> session_ids;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    std::shared_ptr<Aeron> aeron = aeronArchive->context().aeron();\n\n    std::shared_ptr<Publication> publication = addPublication(\n        *aeron, m_recordingChannel, m_recordingStreamId);\n\n    session_ids.insert(publication->sessionId());\n\n    std::int64_t subscriptionId = aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    const std::string recordingChannel2 = \"aeron:udp?endpoint=localhost:3334\";\n    std::shared_ptr<Publication> publication2 = addPublication(\n        *aeron, recordingChannel2, m_recordingStreamId);\n\n    session_ids.insert(publication2->sessionId());\n\n    std::int64_t subscriptionId2 = aeronArchive->startRecording(\n        recordingChannel2, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    const std::int64_t nullCounterId = CountersReader::NULL_COUNTER_ID;\n    EXPECT_NE(\n        nullCounterId, getRecordingCounterId(publication2->sessionId(), aeron->countersReader()));\n\n    std::set<std::int32_t> found_session_ids;\n\n    int32_t count = aeronArchive->listRecordings(\n        INT64_MIN,\n        10,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            found_session_ids.insert(recordingDescriptor.m_sessionId);\n        });\n    EXPECT_EQ(session_ids, found_session_ids);\n    EXPECT_EQ(count, 2);\n\n    count = aeronArchive->listRecordings(\n        INT64_MIN,\n        1,\n        [&](const RecordingDescriptor &recordingDescriptor){});\n    EXPECT_EQ(count, 1);\n\n    aeronArchive->stopRecording(subscriptionId);\n    aeronArchive->stopRecording(subscriptionId2);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldFindRecordingDescriptorForUri)\n{\n    std::set<std::int32_t> session_ids;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    std::shared_ptr<Aeron> aeron = aeronArchive->context().aeron();\n\n    std::shared_ptr<Publication> publication = addPublication(\n        *aeron, m_recordingChannel, m_recordingStreamId);\n\n    session_ids.insert(publication->sessionId());\n\n    std::int64_t subscriptionId = aeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    const std::string recordingChannel2 = \"aeron:udp?endpoint=localhost:3334\";\n    std::shared_ptr<Publication> publication2 = addPublication(\n        *aeron, recordingChannel2, m_recordingStreamId);\n\n    session_ids.insert(publication2->sessionId());\n\n    std::int64_t subscriptionId2 = aeronArchive->startRecording(\n        recordingChannel2, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    const std::int64_t nullCounterId = CountersReader::NULL_COUNTER_ID;\n    EXPECT_NE(\n        nullCounterId, getRecordingCounterId(publication2->sessionId(), aeron->countersReader()));\n\n    std::set<std::int32_t> found_session_ids;\n\n    int32_t count = aeronArchive->listRecordingsForUri(\n        INT64_MIN,\n        2,\n        \"3334\",\n        m_recordingStreamId,\n        [&](const RecordingDescriptor &recordingDescriptor){});\n    EXPECT_EQ(count, 1);\n\n    count = aeronArchive->listRecordingsForUri(\n        INT64_MIN,\n        10,\n        \"333\",\n        m_recordingStreamId,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            found_session_ids.insert(recordingDescriptor.m_sessionId);\n        });\n    EXPECT_EQ(count, 2);\n    EXPECT_EQ(session_ids, found_session_ids);\n\n    count = aeronArchive->listRecordingsForUri(\n        INT64_MIN,\n        10,\n        \"no-match\",\n        m_recordingStreamId,\n        [&](const RecordingDescriptor &recordingDescriptor){});\n    EXPECT_EQ(count, 0);\n\n    aeronArchive->stopRecording(subscriptionId);\n    aeronArchive->stopRecording(subscriptionId2);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldReadJumboRecordingDescriptor)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingId;\n    std::int64_t stopPosition;\n    std::string recordingChannel = \"aeron:udp?endpoint=localhost:3333|term-length=64k|alias=\";\n    recordingChannel.append(2000, 'X');\n\n    m_context.controlRequestChannel(\"aeron:udp?endpoint=localhost:8010\")\n        .controlResponseChannel(\"aeron:udp?endpoint=localhost:0\");\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    const std::int64_t subscriptionId = aeronArchive->startRecording(\n        recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeronArchive->context().aeron(), recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeronArchive->context().aeron(), recordingChannel, m_recordingStreamId);\n\n        sessionId = publication->sessionId();\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        EXPECT_EQ(aeronArchive->getRecordingPosition(recordingId), stopPosition);\n        EXPECT_EQ(aeronArchive->getStopPosition(recordingId), aeron::NULL_VALUE);\n    }\n\n    aeronArchive->stopRecording(subscriptionId);\n\n    EXPECT_EQ(aeronArchive->getStopPosition(recordingId), stopPosition);\n\n    const std::int32_t count = aeronArchive->listRecording(\n        recordingId,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            EXPECT_EQ(recordingDescriptor.m_recordingId, recordingId);\n            EXPECT_EQ(recordingDescriptor.m_streamId, m_recordingStreamId);\n            EXPECT_EQ(recordingDescriptor.m_originalChannel, recordingChannel);\n        });\n\n    EXPECT_EQ(count, 1);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldRecordReplicateThenReplay)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingId;\n    std::int64_t stopPosition;\n\n    startDestArchive();\n\n    std::set<std::int32_t> signals;\n\n    auto signalConsumer = [&](RecordingSignal recordingSignal) -> void\n    {\n        signals.insert(recordingSignal.m_recordingSignalCode);\n    };\n\n    m_destContext.recordingSignalConsumer(signalConsumer);\n\n    std::shared_ptr<AeronArchive> srcAeronArchive = AeronArchive::connect(m_context);\n    std::shared_ptr<AeronArchive> dstAeronArchive = AeronArchive::connect(m_destContext);\n    EXPECT_EQ(42, srcAeronArchive->archiveId());\n    EXPECT_EQ(-7777, dstAeronArchive->archiveId());\n\n    const std::int64_t subscriptionId = srcAeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *srcAeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *srcAeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        sessionId = publication->sessionId();\n\n        CountersReader &countersReader = srcAeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n        EXPECT_TRUE(RecordingPos::isActive(countersReader, counterId, recordingId));\n        EXPECT_EQ(counterId, RecordingPos::findCounterIdByRecordingId(countersReader, recordingId));\n        EXPECT_EQ(\"aeron:ipc\", RecordingPos::getSourceIdentity(countersReader, counterId));\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n\n        stopPosition = publication->position();\n\n        YieldingIdleStrategy idleStrategy;\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    srcAeronArchive->stopRecording(subscriptionId);\n\n    YieldingIdleStrategy idleStrategy;\n    while (srcAeronArchive->getStopPosition(recordingId) != stopPosition)\n    {\n        idleStrategy.idle();\n    }\n\n    auto credentials = std::make_pair(\"admin:admin\", 11);\n\n    ReplicationParams params;\n    params.encodedCredentials(credentials);\n\n    dstAeronArchive->replicate(\n        recordingId, m_context.controlRequestStreamId(), m_context.controlRequestChannel(), params);\n\n    while (0 == signals.count(aeron::archive::client::RecordingSignal::Value::SYNC))\n    {\n        dstAeronArchive->pollForRecordingSignals();\n        idleStrategy.idle();\n    }\n\n    const std::int64_t position = 0L;\n    const std::int64_t length = stopPosition - position;\n    std::shared_ptr<Subscription> subscription = addSubscription(\n        *srcAeronArchive->context().aeron(), m_replayChannel, m_replayStreamId);\n\n    srcAeronArchive->startReplay(\n        recordingId,\n        m_replayChannel,\n        m_replayStreamId,\n        ReplayParams().position(position).length(length).fileIoMaxLength(4096));\n\n    consumeMessages(*subscription, messageCount, messagePrefix);\n    EXPECT_EQ(stopPosition, subscription->imageByIndex(0)->position());\n}\n\nTEST_P(AeronArchiveWrapperParamTest, shouldRecordReplicateThenStop)\n{\n    bool tryStop = GetParam();\n\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingId;\n    std::int64_t stopPosition;\n\n    startDestArchive();\n\n    std::set<std::int32_t> signals;\n\n    auto signalConsumer = [&](RecordingSignal recordingSignal) -> void\n    {\n        signals.insert(recordingSignal.m_recordingSignalCode);\n    };\n\n    m_context.controlRequestChannel(\"aeron:udp?endpoint=localhost:8010\")\n        .controlResponseChannel(\"aeron:udp?endpoint=localhost:0\");\n    m_destContext.recordingSignalConsumer(signalConsumer);\n\n    std::shared_ptr<AeronArchive> srcAeronArchive = AeronArchive::connect(m_context);\n    std::shared_ptr<AeronArchive> dstAeronArchive = AeronArchive::connect(m_destContext);\n    EXPECT_EQ(42, srcAeronArchive->archiveId());\n    EXPECT_EQ(-7777, dstAeronArchive->archiveId());\n\n    srcAeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    std::shared_ptr<Subscription> subscription = addSubscription(\n        *srcAeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n    std::shared_ptr<Publication> publication = addPublication(\n        *srcAeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n    sessionId = publication->sessionId();\n\n    CountersReader &countersReader = srcAeronArchive->context().aeron()->countersReader();\n    const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n    recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n    EXPECT_TRUE(RecordingPos::isActive(countersReader, counterId, recordingId));\n    EXPECT_EQ(counterId, RecordingPos::findCounterIdByRecordingId(countersReader, recordingId));\n    EXPECT_EQ(\"aeron:ipc\", RecordingPos::getSourceIdentity(countersReader, counterId));\n\n    offerMessages(*publication, messageCount, messagePrefix);\n    consumeMessages(*subscription, messageCount, messagePrefix);\n\n    stopPosition = publication->position();\n\n    YieldingIdleStrategy idleStrategy;\n    while (countersReader.getCounterValue(counterId) < stopPosition)\n    {\n        idleStrategy.idle();\n    }\n\n    auto credentials = std::make_pair(\"admin:admin\", 11);\n\n    ReplicationParams params;\n    params.encodedCredentials(credentials);\n\n    int64_t replicationId = dstAeronArchive->replicate(\n        recordingId, m_context.controlRequestStreamId(), m_context.controlRequestChannel(), params);\n\n    while (\n        0 == signals.count(aeron::archive::client::RecordingSignal::Value::REPLICATE) ||\n        0 == signals.count(aeron::archive::client::RecordingSignal::Value::EXTEND))\n    {\n        dstAeronArchive->pollForRecordingSignals();\n        idleStrategy.idle();\n    }\n\n    const std::int64_t position = 0L;\n    std::shared_ptr<Subscription> replaySubscription = addSubscription(\n        *srcAeronArchive->context().aeron(), m_replayChannel, m_replayStreamId);\n\n    srcAeronArchive->startReplay(\n        recordingId,\n        m_replayChannel,\n        m_replayStreamId,\n        ReplayParams().position(position).fileIoMaxLength(4096));\n\n    if (tryStop)\n    {\n        ASSERT_TRUE(dstAeronArchive->tryStopReplication(replicationId));\n    }\n    else\n    {\n        dstAeronArchive->stopReplication(replicationId);\n    }\n\n    offerMessages(*publication, messageCount, messagePrefix, 10);\n\n    consumeMessagesExpectingBound(*replaySubscription, 0, messagePrefix, 1000);\n\n    while (0 == signals.count(aeron::archive::client::RecordingSignal::Value::REPLICATE_END))\n    {\n        dstAeronArchive->pollForRecordingSignals();\n        idleStrategy.idle();\n    }\n\n    EXPECT_EQ(stopPosition, subscription->imageByIndex(0)->position());\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldRecordReplicateTwice)\n{\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 10;\n    std::int32_t sessionId;\n    std::int64_t recordingId;\n    std::int64_t stopPosition;\n    YieldingIdleStrategy idleStrategy;\n\n    startDestArchive();\n\n    std::set<std::int32_t> signals;\n\n    auto signalConsumer = [&](RecordingSignal recordingSignal) -> void\n    {\n        signals.insert(recordingSignal.m_recordingSignalCode);\n    };\n\n    m_destContext.recordingSignalConsumer(signalConsumer);\n\n    std::shared_ptr<AeronArchive> srcAeronArchive = AeronArchive::connect(m_context);\n    std::shared_ptr<AeronArchive> dstAeronArchive = AeronArchive::connect(m_destContext);\n\n    const std::int64_t subscriptionId = srcAeronArchive->startRecording(\n        m_recordingChannel, m_recordingStreamId, AeronArchive::SourceLocation::LOCAL);\n\n    std::int64_t halfwayPosition;\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *srcAeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *srcAeronArchive->context().aeron(), m_recordingChannel, m_recordingStreamId);\n\n        sessionId = publication->sessionId();\n\n        CountersReader &countersReader = srcAeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n        EXPECT_TRUE(RecordingPos::isActive(countersReader, counterId, recordingId));\n        EXPECT_EQ(counterId, RecordingPos::findCounterIdByRecordingId(countersReader, recordingId));\n        EXPECT_EQ(\"aeron:ipc\", RecordingPos::getSourceIdentity(countersReader, counterId));\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n        halfwayPosition = publication->position();\n        while (countersReader.getCounterValue(counterId) < halfwayPosition)\n        {\n            idleStrategy.idle();\n        }\n\n        offerMessages(*publication, messageCount, messagePrefix);\n        consumeMessages(*subscription, messageCount, messagePrefix);\n        stopPosition = publication->position();\n\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    srcAeronArchive->stopRecording(subscriptionId);\n    while (srcAeronArchive->getStopPosition(recordingId) != stopPosition)\n    {\n        idleStrategy.idle();\n    }\n\n    auto credentials = std::make_pair(\"admin:admin\", 11);\n\n    ReplicationParams params1;\n    params1\n        .encodedCredentials(credentials)\n        .stopPosition(halfwayPosition)\n        .replicationSessionId(1);\n\n    dstAeronArchive->replicate(\n        recordingId, m_context.controlRequestStreamId(), m_context.controlRequestChannel(), params1);\n\n    while (0 == signals.count(aeron::archive::client::RecordingSignal::Value::REPLICATE_END))\n    {\n        dstAeronArchive->pollForRecordingSignals();\n        idleStrategy.idle();\n    }\n\n    ReplicationParams params2;\n    params2\n        .encodedCredentials(credentials)\n        .replicationSessionId(2);\n\n    dstAeronArchive->replicate(\n        recordingId, m_context.controlRequestStreamId(), m_context.controlRequestChannel(), params2);\n\n    signals.clear();\n\n    while (0 == signals.count(aeron::archive::client::RecordingSignal::Value::REPLICATE_END))\n    {\n        dstAeronArchive->pollForRecordingSignals();\n        idleStrategy.idle();\n    }\n}\n\nTEST_F(AeronArchiveWrapperIdTest, shouldResolveArchiveId)\n{\n    std::int64_t archiveId = 0x4236483BEEF;\n    DoSetUp(archiveId);\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    EXPECT_TRUE(aeronArchive->controlResponseSubscription().isConnected());\n    EXPECT_EQ(archiveId, aeronArchive->archiveId());\n\n    DoTearDown();\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldConnectToArchiveWithResponseChannels)\n{\n    m_context.controlResponseChannel(\"aeron:udp?control-mode=response|control=localhost:10002\");\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    EXPECT_TRUE(aeronArchive->controlResponseSubscription().isConnected());\n}\n\nTEST_P(AeronArchiveWrapperParamTest, shouldReplayWithResponseChannel)\n{\n    bool tryStop = GetParam();\n\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 1000;\n    const std::string responseChannel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n\n    m_context.controlResponseChannel(responseChannel);\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    const std::tuple<std::int64_t, std::int64_t, std::int64_t > result = recordData(\n        tryStop, *aeronArchive, messageCount, messagePrefix);\n\n    std::int64_t recordingId = std::get<0>(result);\n    std::int64_t stopPosition = std::get<1>(result);\n\n    const std::int64_t position = 0L;\n    const std::int64_t length = stopPosition - position;\n\n    const std::shared_ptr<Subscription> subscription = aeronArchive->replay(\n        recordingId,\n        responseChannel,\n        m_replayStreamId,\n        ReplayParams().position(position).length(length).fileIoMaxLength(4096));\n\n    consumeMessages(*subscription, messageCount, messagePrefix);\n    EXPECT_EQ(stopPosition, subscription->imageByIndex(0)->position());\n}\n\nTEST_P(AeronArchiveWrapperParamTest, shouldBoundedReplayWithResponseChannel)\n{\n    bool tryStop = GetParam();\n\n    YieldingIdleStrategy idle;\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 1000;\n    const std::int64_t key = 1234567890;\n\n    m_context.controlResponseChannel(\"aeron:udp?control-mode=response|control=localhost:10002\");\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    const std::shared_ptr<Aeron> aeron = aeronArchive->context().aeron();\n\n    const std::tuple<std::int64_t, std::int64_t, std::int64_t> result = recordData(\n        tryStop, *aeronArchive, messageCount, messagePrefix);\n\n    std::int64_t recordingId = std::get<0>(result);\n    std::int64_t stopPosition = std::get<1>(result);\n    std::int64_t halfwayPosition = std::get<2>(result);\n\n    int64_t registrationId = aeron->addCounter(\n        10001, reinterpret_cast<const uint8_t *>(&key), sizeof(key), \"test bounded counter\");\n    std::shared_ptr<Counter> boundedCounter = aeron->findCounter(registrationId);\n    while (!boundedCounter)\n    {\n        idle.idle();\n        boundedCounter = aeron->findCounter(registrationId);\n    }\n\n    boundedCounter->setOrdered(halfwayPosition);\n\n    const std::int64_t position = 0L;\n    const std::int64_t length = stopPosition - position;\n    const std::string replayChannel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n\n    ReplayParams params = ReplayParams()\n        .position(position)\n        .length(length)\n        .fileIoMaxLength(4096)\n        .boundingLimitCounterId(boundedCounter->id());\n\n    const std::shared_ptr<Subscription> subscription = aeronArchive->replay(\n        recordingId, replayChannel, m_replayStreamId, params);\n\n    consumeMessages(*subscription, messageCount / 2, messagePrefix);\n    EXPECT_EQ(halfwayPosition, subscription->imageByIndex(0)->position());\n}\n\nTEST_P(AeronArchiveWrapperParamTest, shouldStartReplayWithResponseChannel)\n{\n    bool tryStop = GetParam();\n\n    YieldingIdleStrategy idle;\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 1000;\n    const std::string responseChannel = \"aeron:udp?control-mode=response|control=localhost:10003\";\n\n    m_context.controlResponseChannel(responseChannel);\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    const std::shared_ptr<Aeron> aeron = aeronArchive->context().aeron();\n\n    const std::tuple<std::int64_t, std::int64_t, std::int64_t > result = recordData(\n        tryStop, *aeronArchive, messageCount, messagePrefix);\n\n    std::int64_t recordingId = std::get<0>(result);\n    std::int64_t stopPosition = std::get<1>(result);\n\n    int64_t subscriptionId = aeron->addSubscription(responseChannel, m_replayStreamId);\n    std::shared_ptr<Subscription> subscription = aeron->findSubscription(subscriptionId);\n    while (!subscription)\n    {\n        idle.idle();\n        subscription = aeron->findSubscription(subscriptionId);\n    }\n\n    const std::int64_t position = 0L;\n    const std::int64_t length = stopPosition - position;\n\n    ReplayParams params = ReplayParams()\n        .position(position)\n        .length(length)\n        .fileIoMaxLength(4096)\n        .subscriptionRegistrationId(subscription->registrationId());\n\n    aeronArchive->startReplay(recordingId, responseChannel, m_replayStreamId, params);\n\n    consumeMessages(*subscription, messageCount, messagePrefix);\n    EXPECT_EQ(stopPosition, subscription->imageByIndex(0)->position());\n}\n\nTEST_P(AeronArchiveWrapperParamTest, shouldStartBoundedReplayWithResponseChannel)\n{\n    bool tryStop = GetParam();\n\n    YieldingIdleStrategy idle;\n    const std::string messagePrefix = \"Message \";\n    const std::size_t messageCount = 1000;\n    const std::int64_t key = 1234567890;\n    const char *responseChannel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n\n    m_context.controlResponseChannel(responseChannel);\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    const std::shared_ptr<Aeron> aeron = aeronArchive->context().aeron();\n\n    const std::tuple<std::int64_t, std::int64_t, std::int64_t> result = recordData(\n        tryStop, *aeronArchive, messageCount, messagePrefix);\n\n    std::int64_t recordingId = std::get<0>(result);\n    std::int64_t stopPosition = std::get<1>(result);\n    std::int64_t halfwayPosition = std::get<2>(result);\n\n    int64_t registrationId = aeron->addCounter(\n        10001, reinterpret_cast<const uint8_t *>(&key), sizeof(key), \"test bounded counter\");\n    std::shared_ptr<Counter> boundedCounter = aeron->findCounter(registrationId);\n    while (!boundedCounter)\n    {\n        idle.idle();\n        boundedCounter = aeron->findCounter(registrationId);\n    }\n\n    boundedCounter->setOrdered(halfwayPosition);\n\n    int64_t subscriptionId = aeron->addSubscription(responseChannel, m_replayStreamId);\n    std::shared_ptr<Subscription> subscription = aeron->findSubscription(subscriptionId);\n    while (!subscription)\n    {\n        idle.idle();\n        subscription = aeron->findSubscription(subscriptionId);\n    }\n\n    const std::int64_t position = 0L;\n    const std::int64_t length = stopPosition - position;\n    const std::string replayChannel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n\n    ReplayParams params = ReplayParams()\n        .position(position)\n        .length(length)\n        .fileIoMaxLength(4096)\n        .boundingLimitCounterId(boundedCounter->id())\n        .subscriptionRegistrationId(subscription->registrationId());\n\n    aeronArchive->startReplay(\n        recordingId, replayChannel, m_replayStreamId, params);\n\n    consumeMessages(*subscription, messageCount / 2, messagePrefix);\n    EXPECT_EQ(halfwayPosition, subscription->imageByIndex(0)->position());\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldDisconnectAfterStopAllReplays)\n{\n    YieldingIdleStrategy idle;\n    const std::string messagePrefix = \"Message \";\n    const char *responseChannel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n\n    m_context.controlResponseChannel(responseChannel);\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    const std::shared_ptr<Aeron> aeron = aeronArchive->context().aeron();\n\n    std::shared_ptr<Subscription> subscription = addSubscription(\n        *aeron, m_recordingChannel, m_recordingStreamId);\n\n    std::shared_ptr<Publication> publication = aeronArchive->addRecordedPublication(\n        m_recordingChannel, m_recordingStreamId);\n\n    std::int32_t sessionId = publication->sessionId();\n\n    CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n    const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n    std::int64_t recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n    offerMessages(*publication, 10, messagePrefix);\n\n    std::int64_t stopPosition = publication->position();\n\n    while (countersReader.getCounterValue(counterId) < stopPosition)\n    {\n        idle.idle();\n    }\n\n    auto replaySubscription = aeronArchive->replay(\n        recordingId,\n        responseChannel,\n        m_replayStreamId,\n        ReplayParams().position(0L).fileIoMaxLength(4096));\n\n    consumeMessages(*replaySubscription, 10, messagePrefix);\n\n    EXPECT_EQ(stopPosition, replaySubscription->imageByIndex(0)->position());\n\n    aeronArchive->stopAllReplays(recordingId);\n\n    while (replaySubscription->isConnected())\n    {\n        idle.idle();\n    }\n}\n\nTEST_P(AeronArchiveWrapperParamTest, shouldRecordAndExtend)\n{\n    bool tryStop = GetParam();\n\n    YieldingIdleStrategy idle;\n    const std::string messagePrefix = \"Message \";\n    std::int64_t recordingId;\n    int64_t stopPosition;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n    std::shared_ptr<Aeron> aeron = aeronArchive->context().aeron();\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeron, m_recordingChannel, m_recordingStreamId);\n\n        std::shared_ptr<Publication> publication = aeronArchive->addRecordedPublication(\n            m_recordingChannel, m_recordingStreamId);\n\n        std::int32_t sessionId = publication->sessionId();\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, 10, messagePrefix);\n        consumeMessages(*subscription, 10, messagePrefix);\n\n        stopPosition = publication->position();\n\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idle.idle();\n        }\n\n        aeronArchive->stopRecording(publication);\n    }\n\n    int32_t initialTermId;\n    int32_t termBufferLength;\n\n    std::int32_t count = aeronArchive->listRecording(\n        recordingId,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            stopPosition = recordingDescriptor.m_stopPosition;\n            initialTermId = recordingDescriptor.m_initialTermId;\n            termBufferLength = recordingDescriptor.m_termBufferLength;\n        });\n\n    EXPECT_EQ(count, 1);\n\n    auto recordingChannel2 = ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3332\")\n        .initialPosition(stopPosition, initialTermId, termBufferLength)\n        .build();\n\n    {\n        std::shared_ptr<Subscription> subscription = addSubscription(\n            *aeron, recordingChannel2, m_recordingStreamId);\n        std::shared_ptr<Publication> publication = addPublication(\n            *aeron, recordingChannel2, m_recordingStreamId);\n\n        std::int32_t sessionId = publication->sessionId();\n\n        aeronArchive->extendRecording(\n            recordingId,\n            recordingChannel2,\n            m_recordingStreamId,\n            AeronArchive::SourceLocation::LOCAL,\n            false);\n\n        CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n        const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n        recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n        offerMessages(*publication, 10, messagePrefix, 10);\n        consumeMessages(*subscription, 10, messagePrefix, 10);\n\n        stopPosition = publication->position();\n\n        while (countersReader.getCounterValue(counterId) < stopPosition)\n        {\n            idle.idle();\n        }\n\n        if (tryStop)\n        {\n            EXPECT_TRUE(aeronArchive->tryStopRecording(recordingChannel2, m_recordingStreamId));\n        }\n        else\n        {\n            aeronArchive->stopRecording(recordingChannel2, m_recordingStreamId);\n        }\n    }\n\n    int64_t startPosition;\n\n    count = aeronArchive->listRecording(\n        recordingId,\n        [&](const RecordingDescriptor &recordingDescriptor)\n        {\n            stopPosition = recordingDescriptor.m_stopPosition;\n            startPosition = recordingDescriptor.m_startPosition;\n        });\n\n    EXPECT_EQ(count, 1);\n\n    auto replaySubscription = aeronArchive->replay(\n        recordingId,\n        m_replayChannel,\n        m_replayStreamId,\n        ReplayParams().position(startPosition).fileIoMaxLength(4096));\n\n    consumeMessages(*replaySubscription, 20, messagePrefix);\n\n    ASSERT_EQ(stopPosition, replaySubscription->imageByIndex(0)->position());\n}\n\n#define TERM_LENGTH AERON_LOGBUFFER_TERM_MIN_LENGTH\n#define SEGMENT_LENGTH (TERM_LENGTH * 2)\n#define MTU_LENGTH 1024\n\nTEST_F(AeronArchiveWrapperTest, shouldPurgeSegments)\n{\n    YieldingIdleStrategy idle;\n    std::set<std::int32_t> signals;\n\n    auto signalConsumer = [&](RecordingSignal recordingSignal) -> void\n    {\n        signals.insert(recordingSignal.m_recordingSignalCode);\n    };\n\n    m_context.recordingSignalConsumer(signalConsumer);\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    auto publicationChannel = ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3333\")\n        .termLength(TERM_LENGTH)\n        .mtu(MTU_LENGTH)\n        .build();\n\n    auto publication = aeronArchive->addRecordedPublication(publicationChannel, m_recordingStreamId);\n\n    int32_t sessionId = publication->sessionId();\n\n    CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n    const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n    int64_t recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n    int64_t targetPosition = (SEGMENT_LENGTH * 3L) + 1;\n    offerMessagesToPosition(*publication, targetPosition);\n\n    int64_t stopPosition = publication->position();\n\n    while (countersReader.getCounterValue(counterId) < stopPosition)\n    {\n        idle.idle();\n    }\n\n    int64_t startPosition = 0;\n    int64_t segmentFileBasePosition = AeronArchive::segmentFileBasePosition(\n        startPosition,\n        SEGMENT_LENGTH * 2L,\n        TERM_LENGTH,\n        SEGMENT_LENGTH);\n\n    int64_t count = aeronArchive->purgeSegments(recordingId, segmentFileBasePosition);\n\n    while (0 == signals.count(aeron::archive::client::RecordingSignal::Value::ABC_DELETE))\n    {\n        aeronArchive->pollForRecordingSignals();\n        idle.idle();\n    }\n\n    ASSERT_EQ(2, count);\n\n    ASSERT_EQ(aeronArchive->getStartPosition(recordingId), segmentFileBasePosition);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldDetachAndDeleteSegments)\n{\n    YieldingIdleStrategy idle;\n    std::set<std::int32_t> signals;\n\n    auto signalConsumer = [&](RecordingSignal recordingSignal) -> void\n    {\n        signals.insert(recordingSignal.m_recordingSignalCode);\n    };\n\n    m_context.recordingSignalConsumer(signalConsumer);\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    auto publicationChannel = ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3333\")\n        .termLength(TERM_LENGTH)\n        .mtu(MTU_LENGTH)\n        .build();\n\n    auto publication = aeronArchive->addRecordedPublication(publicationChannel, m_recordingStreamId);\n\n    int32_t sessionId = publication->sessionId();\n\n    CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n    const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n    int64_t recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n    int64_t targetPosition = (SEGMENT_LENGTH * 4L) + 1;\n    offerMessagesToPosition(*publication, targetPosition);\n\n    int64_t stopPosition = publication->position();\n\n    while (countersReader.getCounterValue(counterId) < stopPosition)\n    {\n        idle.idle();\n    }\n\n    int64_t startPosition = 0;\n    int64_t segmentFileBasePosition = AeronArchive::segmentFileBasePosition(\n        startPosition,\n        SEGMENT_LENGTH * 3L,\n        TERM_LENGTH,\n        SEGMENT_LENGTH);\n\n    aeronArchive->detachSegments(recordingId, segmentFileBasePosition);\n\n    int64_t count = aeronArchive->deleteDetachedSegments(recordingId);\n\n    while (0 == signals.count(aeron::archive::client::RecordingSignal::Value::ABC_DELETE))\n    {\n        aeronArchive->pollForRecordingSignals();\n        idle.idle();\n    }\n\n    ASSERT_EQ(3, count);\n\n    ASSERT_EQ(aeronArchive->getStartPosition(recordingId), segmentFileBasePosition);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldDetachAndReattachSegments)\n{\n    YieldingIdleStrategy idle;\n    std::set<std::int32_t> signals;\n\n    auto signalConsumer = [&](RecordingSignal recordingSignal) -> void\n    {\n        signals.insert(recordingSignal.m_recordingSignalCode);\n    };\n\n    m_context.recordingSignalConsumer(signalConsumer);\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    auto publicationChannel = ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3333\")\n        .termLength(TERM_LENGTH)\n        .mtu(MTU_LENGTH)\n        .build();\n\n    auto publication = aeronArchive->addRecordedPublication(publicationChannel, m_recordingStreamId);\n\n    int32_t sessionId = publication->sessionId();\n\n    CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n    const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n    int64_t recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n    int64_t targetPosition = (SEGMENT_LENGTH * 5L) + 1;\n    offerMessagesToPosition(*publication, targetPosition);\n\n    int64_t stopPosition = publication->position();\n\n    while (countersReader.getCounterValue(counterId) < stopPosition)\n    {\n        idle.idle();\n    }\n\n    int64_t startPosition = 0;\n    int64_t segmentFileBasePosition = AeronArchive::segmentFileBasePosition(\n        startPosition,\n        SEGMENT_LENGTH * 4L,\n        TERM_LENGTH,\n        SEGMENT_LENGTH);\n\n    aeronArchive->detachSegments(recordingId, segmentFileBasePosition);\n\n    ASSERT_EQ(aeronArchive->getStartPosition(recordingId), segmentFileBasePosition);\n\n    ASSERT_EQ(4, aeronArchive->attachSegments(recordingId));\n\n    ASSERT_EQ(aeronArchive->getStartPosition(recordingId), 0);\n}\n\nTEST_F(AeronArchiveWrapperTest, shouldUpdateChannel)\n{\n    YieldingIdleStrategy idle;\n\n    std::shared_ptr<AeronArchive> aeronArchive = AeronArchive::connect(m_context);\n\n    auto publicationChannel = ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3333\")\n        .termLength(TERM_LENGTH)\n        .mtu(MTU_LENGTH)\n        .build();\n\n    auto publication = aeronArchive->addRecordedPublication(publicationChannel, m_recordingStreamId);\n\n    int32_t sessionId = publication->sessionId();\n\n    CountersReader &countersReader = aeronArchive->context().aeron()->countersReader();\n    const std::int32_t counterId = getRecordingCounterId(sessionId, countersReader);\n    int64_t recordingId = RecordingPos::getRecordingId(countersReader, counterId);\n\n    int64_t targetPosition = (SEGMENT_LENGTH * 5L) + 1;\n    offerMessagesToPosition(*publication, targetPosition);\n\n    int64_t stopPosition = publication->position();\n\n    while (countersReader.getCounterValue(counterId) < stopPosition)\n    {\n        idle.idle();\n    }\n\n    const char *newChannel = \"aeron:udp?alias=zzz|term-length=1g|mtu=2484|ttl=3|reliable=true|pub-wnd=1m|rejoin=false\";\n    aeronArchive->updateChannel(recordingId, newChannel);\n\n    std::string original_channel = std::string(\"\");\n    std::string stripped_channel = std::string(\"\");\n    std::int32_t count = aeronArchive->listRecording(\n            recordingId,\n            [&](const RecordingDescriptor &recordingDescriptor)\n            {\n                original_channel.append(recordingDescriptor.m_originalChannel);\n                stripped_channel.append(recordingDescriptor.m_strippedChannel);\n            });\n\n    EXPECT_EQ(count, 1);\n    EXPECT_EQ(std::string(newChannel), original_channel);\n    EXPECT_NE(std::string(newChannel), stripped_channel);\n    EXPECT_NE(std::string::npos, stripped_channel.find(\"alias=zzz\"));\n    EXPECT_NE(std::string::npos, stripped_channel.find(\"ttl=3\"));\n    EXPECT_NE(std::string::npos, stripped_channel.find(\"rejoin=false\"));\n    EXPECT_EQ(std::string::npos, stripped_channel.find(\"mtu=2484\"));\n    EXPECT_EQ(std::string::npos, stripped_channel.find(\"term-length=1g\"));\n    EXPECT_EQ(std::string::npos, stripped_channel.find(\"reliable=true\"));\n}\n"
  },
  {
    "path": "aeron-archive/src/test/cpp_wrapper/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nfind_package(Java REQUIRED)\n\nadd_definitions(-DAERON_ALL_JAR=\"${AERON_ALL_JAR}\")\nadd_definitions(-DAERON_AGENT_JAR=\"${AERON_AGENT_JAR}\")\nadd_definitions(-DJAVA_EXECUTABLE=\"${Java_JAVA_EXECUTABLE}\")\nadd_definitions(-DARCHIVE_DIR=\"${CMAKE_CURRENT_BINARY_DIR}/archive\")\nadd_definitions(-DJAVA_MAJOR_VERSION=${Java_VERSION_MAJOR})\nadd_definitions(-DJAVA_MINOR_VERSION=${Java_VERSION_MINOR})\n\nset(TEST_HEADERS)\n\ninclude_directories(${AERON_ARCHIVE_WRAPPER_SOURCE_PATH})\n\nfunction(aeron_archive_wrapper_test name file)\n    set(wrapper_name \"${name}W\")\n    add_executable(${wrapper_name} ${file} ${TEST_HEADERS})\n    add_dependencies(${wrapper_name} aeron_archive_c_client gmock)\n    target_include_directories(${wrapper_name} PUBLIC ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\n    target_link_libraries(${wrapper_name} aeron_archive_c_client aeron_archive_wrapper gmock_main ${CMAKE_THREAD_LIBS_INIT})\n    target_compile_definitions(${wrapper_name} PUBLIC \"_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING\")\n    add_test(NAME ${wrapper_name} COMMAND ${wrapper_name})\nendfunction()\n\nif (AERON_UNIT_TESTS)\n    aeron_archive_wrapper_test(archiveTest AeronArchiveWrapperTest.cpp)\nendif ()"
  },
  {
    "path": "aeron-archive/src/test/cpp_wrapper/TestArchive.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_TESTARCHIVE_H\n#define AERON_TESTARCHIVE_H\n\n// Uncomment for logging\n// #define ENABLE_AGENT_DEBUG_LOGGING 1\n\nextern \"C\"\n{\n#include <atomic>\n#include <signal.h>\n\n#include \"aeron_log_buffer.h\"\n}\n\n#include <thread>\n\n#define TERM_LENGTH AERON_LOGBUFFER_TERM_MIN_LENGTH\n#define SEGMENT_LENGTH (TERM_LENGTH * 2)\n#define ARCHIVE_MARK_FILE_HEADER_LENGTH (8192)\n\n#ifdef _WIN32\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n#include <shellapi.h>\ntypedef intptr_t pid_t;\n\nstatic void await_process_terminated(pid_t process_handle)\n{\n    WaitForSingleObject(reinterpret_cast<HANDLE>(process_handle), INFINITE);\n}\n#else\n#include \"ftw.h\"\n#include \"spawn.h\"\n\nstatic void await_process_terminated(pid_t process_handle)\n{\n    int process_status = -1;\n    while (true)\n    {\n        waitpid(process_handle, &process_status, WUNTRACED);\n        if (WIFSIGNALED(process_status) || WIFEXITED(process_status))\n        {\n            break;\n        }\n    }\n}\n#endif\n\nclass TestArchive\n{\npublic:\n    TestArchive(\n        std::string aeronDir,\n        std::string archiveDir,\n        std::ostream &stream,\n        std::string controlChannel,\n        std::string replicationChannel,\n        std::int64_t archiveId)\n        : m_archiveDir(archiveDir), m_aeronDir(aeronDir), m_stream(stream)\n    {\n        m_stream << aeron_epoch_clock() << \" [SetUp] Starting ArchivingMediaDriver...\" << std::endl;\n\n        std::string aeronDirArg = \"-Daeron.dir=\" + aeronDir;\n        std::string archiveDirArg = \"-Daeron.archive.dir=\" + archiveDir;\n        std::string archiveMarkFileDirArg = \"-Daeron.archive.mark.file.dir=\" + aeronDir;\n        m_stream << aeron_epoch_clock() << \" [SetUp] \" << aeronDirArg << std::endl;\n        m_stream << aeron_epoch_clock() << \" [SetUp] \" << archiveDirArg << std::endl;\n        std::string controlChannelArg = \"-Daeron.archive.control.channel=\" + controlChannel;\n        std::string replicationChannelArg = \"-Daeron.archive.replication.channel=\" + replicationChannel;\n        std::string archiveIdArg = \"-Daeron.archive.id=\" + std::to_string(archiveId);\n        std::string segmentLength = \"-Daeron.archive.segment.file.length=\" + std::to_string(SEGMENT_LENGTH);\n\n        const char *const argv[] =\n            {\n                \"java\",\n                \"--add-opens\",\n                \"java.base/jdk.internal.misc=ALL-UNNAMED\",\n                \"--add-opens\",\n                \"java.base/java.util.zip=ALL-UNNAMED\",\n#if ENABLE_AGENT_DEBUG_LOGGING\n                m_aeronAgentJar.c_str(),\n                \"-Daeron.event.log=admin\",\n                \"-Daeron.event.archive.log=all\",\n#endif\n                \"-Daeron.dir.delete.on.start=true\",\n                \"-Daeron.dir.delete.on.shutdown=true\",\n                \"-Daeron.archive.dir.delete.on.start=true\",\n                \"-Daeron.archive.max.catalog.entries=128\",\n                \"-Daeron.term.buffer.sparse.file=true\",\n                \"-Daeron.perform.storage.checks=false\",\n                \"-Daeron.term.buffer.length=64k\",\n                \"-Daeron.ipc.term.buffer.length=64k\",\n                \"-Daeron.threading.mode=SHARED\",\n                \"-Daeron.shared.idle.strategy=yield\",\n                \"-Daeron.archive.threading.mode=SHARED\",\n                \"-Daeron.archive.idle.strategy=yield\",\n                \"-Daeron.archive.recording.events.enabled=false\",\n                \"-Daeron.driver.termination.validator=io.aeron.driver.DefaultAllowTerminationValidator\",\n                \"-Daeron.archive.authenticator.supplier=io.aeron.samples.archive.SampleAuthenticatorSupplier\",\n                \"-Daeron.enable.experimental.features=true\",\n                \"-Daeron.spies.simulate.connection=true\",\n                segmentLength.c_str(),\n                archiveIdArg.c_str(),\n                controlChannelArg.c_str(),\n                replicationChannelArg.c_str(),\n                \"-Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0\",\n                archiveDirArg.c_str(),\n                archiveMarkFileDirArg.c_str(),\n                aeronDirArg.c_str(),\n                \"-cp\",\n                m_aeronAllJar.c_str(),\n                \"io.aeron.archive.ArchivingMediaDriver\",\n                nullptr\n            };\n        m_process_handle = -1;\n\n#if defined(_WIN32)\n        m_process_handle = _spawnv(P_NOWAIT, m_java.c_str(), &argv[0]);\n#else\n        if (0 != posix_spawn(&m_process_handle, m_java.c_str(), nullptr, nullptr, (char *const *)&argv[0], nullptr))\n        {\n            perror(\"spawn\");\n            ::exit(EXIT_FAILURE);\n        }\n#endif\n\n        if (m_process_handle < 0)\n        {\n            perror(\"spawn\");\n            ::exit(EXIT_FAILURE);\n        }\n\n        m_pid = m_process_handle;\n#ifdef _WIN32\n        m_pid = GetProcessId((HANDLE)m_process_handle);\n#endif\n\n        const std::string mark_file = aeronDir + std::string(1, AERON_FILE_SEP) + \"archive-mark.dat\";\n\n        // await mark file creation as an indicator that Archive process is running\n        while (true)\n        {\n            int64_t file_length = aeron_file_length(mark_file.c_str());\n            if (file_length >= ARCHIVE_MARK_FILE_HEADER_LENGTH)\n            {\n                break;\n            }\n            aeron_micro_sleep(1000);\n        }\n        m_stream << aeron_epoch_clock() << \" [SetUp] ArchivingMediaDriver PID \" << m_pid << std::endl;\n    }\n\n    ~TestArchive()\n    {\n        if (m_process_handle > 0)\n        {\n            m_stream << aeron_epoch_clock() << \" [TearDown] Shutting down ArchivingMediaDriver PID \" << m_pid << std::endl;\n\n            bool archive_terminated = false;\n#ifndef _WIN32\n            if (0 == kill(m_process_handle, SIGTERM))\n            {\n                m_stream << aeron_epoch_clock() << \" [TearDown] waiting for ArchivingMediaDriver termination...\" << std::endl;\n                await_process_terminated(m_process_handle);\n                m_stream << aeron_epoch_clock() << \" [TearDown] ArchivingMediaDriver terminated\" << std::endl;\n                archive_terminated = true;\n            }\n#endif\n\n            if (!archive_terminated)\n            {\n                const std::string aeronPath = m_aeronDir;\n                const std::string cncFilename = aeronPath + std::string(1, AERON_FILE_SEP) + \"cnc.dat\";\n\n                if (aeron_context_request_driver_termination(aeronPath.c_str(), nullptr, 0))\n                {\n                    m_stream << aeron_epoch_clock() << \" [TearDown] Waiting for driver termination\" << std::endl;\n\n                    while (aeron_file_length(cncFilename.c_str()) > 0)\n                    {\n                        aeron_micro_sleep(1000);\n                    }\n\n                    m_stream << aeron_epoch_clock() << \" [TearDown] CnC file no longer exists\" << std::endl;\n\n                    await_process_terminated(m_process_handle);\n                    m_stream << aeron_epoch_clock() << \" [TearDown] Driver terminated\" << std::endl;\n                    archive_terminated = true;\n                }\n                else\n                {\n                    m_stream << aeron_epoch_clock() << \" [TearDown] Failed to send driver terminate command\" << std::endl;\n                }\n            }\n\n            if (archive_terminated && aeron_is_directory(m_archiveDir.c_str()) >= 0)\n            {\n                m_stream << aeron_epoch_clock() << \" [TearDown] Deleting \" << m_archiveDir << std::endl;\n                if (aeron_delete_directory(m_archiveDir.c_str()) != 0)\n                {\n                    m_stream << aeron_epoch_clock() << \" [TearDown] Failed to delete \" << m_archiveDir << std::endl;\n                }\n            }\n            m_stream.flush();\n        }\n    }\n\nprivate:\n    const std::string m_java = JAVA_EXECUTABLE;          // Defined in CMakeLists.txt\n    const std::string m_aeronAllJar = AERON_ALL_JAR;     // Defined in CMakeLists.txt\n    const std::string m_aeronAgentJar = \"-javaagent:\" AERON_AGENT_JAR; // Defined in CMakeLists.txt\n    const std::string m_archiveDir;\n    const std::string m_aeronDir;\n    std::ostream &m_stream;\n    pid_t m_process_handle = -1;\n    pid_t m_pid = 0;\n};\n\n#endif //AERON_TESTARCHIVE_H\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveConductorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport org.junit.jupiter.api.Test;\n\nimport java.lang.reflect.Field;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass ArchiveConductorTest\n{\n    @Test\n    void strippedChannelBuilderShouldCopyKeyParameters() throws ReflectiveOperationException\n    {\n        final ChannelUri uri = ChannelUri.parse(\"\"\"\n            aeron:udp?term-length=1g|mtu=2048|session-id=tag:424242424242|rcv-wnd=32k|so-sndbuf=2m|so-rcvbuf=1m|\\\n            endpoint=some-host:7777|interface=192.168.0.1/24|control-mode=dynamic|control=localhost:5555|\\\n            tether=false|reliable=false|linger=1321ms|sparse=false|cc=cubic|alias=test-stripped-channel|\\\n            fc=tagged,g:1111/3,t:4s|gtag=2222|tags=17,432|tag=5|ttl=3|eos=true|rejoin=false|ssc=true|group=true|\\\n            response-correlation-id=88888888881|response-endpoint=publisher:10101|\\\n            media-rcv-ts-offset=reserved|channel-rcv-ts-offset=136|channel-snd-ts-offset=144|\\\n            init-term-id=300|term-id=341|term-offset=4096|nak-delay=100ns|\\\n            untethered-window-limit-timeout=3s|untethered-linger-timeout=2s|untethered-resting-timeout=1s|\\\n            max-resend=13|stream-id=-654|pub-wnd=32m\n            \"\"\".stripIndent());\n\n        final ChannelUriStringBuilder builder = ArchiveConductor.strippedChannelBuilder(uri);\n        assertNotNull(builder);\n        assertEquals(\"udp\", builder.media());\n        assertEquals(\"some-host:7777\", builder.endpoint());\n        assertEquals(\"192.168.0.1/24\", builder.networkInterface());\n        assertEquals(\"localhost:5555\", builder.controlEndpoint());\n        assertEquals(\"dynamic\", builder.controlMode());\n        assertEquals(\"17,432\", builder.tags());\n        assertEquals(false, builder.rejoin());\n        assertEquals(true, builder.group());\n        assertEquals(false, builder.tether());\n        assertEquals(\"tagged,g:1111/3,t:4s\", builder.flowControl());\n        assertEquals(2222, builder.groupTag());\n        assertEquals(\"cubic\", builder.congestionControl());\n        assertEquals(1024 * 1024, builder.socketRcvbufLength());\n        assertEquals(2 * 1024 * 1024, builder.socketSndbufLength());\n        assertEquals(32 * 1024, builder.receiverWindowLength());\n        assertEquals(\"144\", builder.channelSendTimestampOffset());\n        assertEquals(\"136\", builder.channelReceiveTimestampOffset());\n        assertEquals(\"reserved\", builder.mediaReceiveTimestampOffset());\n        assertTrue(builder.isSessionIdTagged());\n        final Field sessionIdField = builder.getClass().getDeclaredField(\"sessionId\");\n        sessionIdField.trySetAccessible();\n        assertEquals(424242424242L, sessionIdField.get(builder));\n        assertEquals(\"test-stripped-channel\", builder.alias());\n        assertEquals(\"88888888881\", builder.responseCorrelationId());\n        assertEquals(\"publisher:10101\", builder.responseEndpoint());\n        assertEquals(3, builder.ttl());\n        assertEquals(-654, builder.streamId());\n        assertNull(builder.reliable());\n        assertNull(builder.linger());\n        assertNull(builder.sparse());\n        assertNull(builder.eos());\n        assertNull(builder.spiesSimulateConnection());\n        assertNull(builder.initialTermId());\n        assertNull(builder.termId());\n        assertNull(builder.termOffset());\n        assertNull(builder.nakDelay());\n        assertNull(builder.untetheredWindowLimitTimeoutNs());\n        assertNull(builder.untetheredLingerTimeoutNs());\n        assertNull(builder.untetheredRestingTimeoutNs());\n        assertNull(builder.maxResend());\n        assertNull(builder.publicationWindowLength());\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveContextTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.Counter;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.driver.status.DutyCycleStallTracker;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.security.AuthorisationServiceSupplier;\nimport io.aeron.test.TestContexts;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.InOrder;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.NoSuchFileException;\nimport java.nio.file.Path;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.AeronCounters.ARCHIVE_CONTROL_SESSIONS_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_RECORDING_SESSION_COUNT_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID;\nimport static io.aeron.AeronCounters.ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID;\nimport static io.aeron.archive.Archive.Configuration.ARCHIVE_ID_PROP_NAME;\nimport static io.aeron.archive.Archive.Configuration.AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME;\nimport static io.aeron.archive.Archive.Configuration.CONTROL_CHANNEL_ENABLED_PROP_NAME;\nimport static io.aeron.archive.Archive.Configuration.DEFAULT_AUTHORISATION_SERVICE_SUPPLIER;\nimport static io.aeron.archive.Archive.Configuration.ERROR_BUFFER_LENGTH_DEFAULT;\nimport static io.aeron.archive.Archive.Configuration.ERROR_BUFFER_LENGTH_PROP_NAME;\nimport static io.aeron.archive.Archive.Configuration.MARK_FILE_DIR_PROP_NAME;\nimport static io.aeron.driver.Configuration.MAX_UDP_PAYLOAD_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PAGE_MIN_SIZE;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MAX_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.startsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.Mockito.withSettings;\n\nclass ArchiveContextTest\n{\n    private final Aeron aeron = mock(Aeron.class);\n    private final Archive.Context context = TestContexts.localhostArchive();\n    private static final int ARCHIVE_CONTROL_SESSIONS_COUNTER_ID = 928234;\n\n    @BeforeEach\n    void beforeEach(final @TempDir Path tempDir)\n    {\n        final CountersReader countersReader = mock(CountersReader.class);\n        final MutableInteger nextCounterId = new MutableInteger(1000);\n        when(aeron.addCounter(\n            anyInt(), any(DirectBuffer.class), anyInt(), anyInt(), any(DirectBuffer.class), anyInt(), anyInt()))\n            .thenAnswer(invocation ->\n            {\n                final int typeId = invocation.getArgument(0);\n                final DirectBuffer labelBuffer = invocation.getArgument(4);\n                final int labelOffset = invocation.getArgument(5);\n                final int labelLength = invocation.getArgument(6);\n                final String label = labelBuffer.getStringWithoutLengthAscii(labelOffset, labelLength);\n                return mockCounter(countersReader, typeId, nextCounterId.getAndIncrement(), label);\n            });\n        final Aeron.Context aeronContext = mock(Aeron.Context.class);\n        when(aeronContext.subscriberErrorHandler()).thenReturn(RethrowingErrorHandler.INSTANCE);\n        when(aeronContext.useConductorAgentInvoker()).thenReturn(true);\n        when(aeronContext.aeronDirectoryName()).thenReturn(\"test-archive-config\");\n        when(aeronContext.filePageSize()).thenReturn(PAGE_MIN_SIZE);\n        when(aeron.context()).thenReturn(aeronContext);\n        when(aeron.countersReader()).thenReturn(countersReader);\n\n        final File archiveDir = tempDir.resolve(\"archive-test\").toFile();\n        init(context, archiveDir);\n    }\n\n    private void init(final Archive.Context context, final File archiveDir)\n    {\n        final CountersReader countersReader = aeron.countersReader();\n        context.archiveDir(archiveDir)\n            .aeron(aeron)\n            .errorCounter(mock(AtomicCounter.class))\n            .controlSessionsCounter(mockCounter(\n                countersReader, ARCHIVE_CONTROL_SESSIONS_TYPE_ID, ARCHIVE_CONTROL_SESSIONS_COUNTER_ID, \"label\"))\n            .recordingSessionCounter(mockCounter(countersReader, ARCHIVE_RECORDING_SESSION_COUNT_TYPE_ID, 101, \"label\"))\n            .replaySessionCounter(mockCounter(countersReader, ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID, 102, \"label\"))\n            .totalWriteBytesCounter(mockCounter(\n                countersReader, ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID, 111, \"label\"))\n            .totalWriteTimeCounter(mockCounter(countersReader, ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID, 222, \"label\"))\n            .maxWriteTimeCounter(mockCounter(countersReader, ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID, 333, \"label\"))\n            .totalReadBytesCounter(mockCounter(countersReader, ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID, 77, \"label\"))\n            .totalReadTimeCounter(mockCounter(countersReader, ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID, 88, \"label\"))\n            .maxReadTimeCounter(mockCounter(countersReader, ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID, 99, \"label\"));\n    }\n\n    @AfterEach\n    void afterEach()\n    {\n        context.close();\n    }\n\n    @Test\n    void defaultAuthorisationServiceSupplierReturnsAnAllowAllAuthorisationService()\n    {\n        assertSame(AuthorisationService.ALLOW_ALL, DEFAULT_AUTHORISATION_SERVICE_SUPPLIER.get());\n    }\n\n    @Test\n    void shouldUseDefaultAuthorisationServiceSupplierIfTheSystemPropertyIsNotSet()\n    {\n        assertNull(context.authorisationServiceSupplier());\n\n        context.conclude();\n\n        System.clearProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        assertSame(DEFAULT_AUTHORISATION_SERVICE_SUPPLIER, context.authorisationServiceSupplier());\n    }\n\n    @Test\n    void shouldUseDefaultAuthorisationServiceSupplierIfTheSystemPropertyIsSetToEmptyValue()\n    {\n        System.setProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME, \"\");\n        try\n        {\n            assertNull(context.authorisationServiceSupplier());\n\n            context.conclude();\n\n            assertSame(DEFAULT_AUTHORISATION_SERVICE_SUPPLIER, context.authorisationServiceSupplier());\n        }\n        finally\n        {\n            System.clearProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldInstantiateAuthorisationServiceSupplierBasedOnTheSystemProperty()\n    {\n        System.setProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME, TestAuthorisationSupplier.class.getName());\n        try\n        {\n            context.conclude();\n            final AuthorisationServiceSupplier supplier = context.authorisationServiceSupplier();\n            assertNotSame(DEFAULT_AUTHORISATION_SERVICE_SUPPLIER, supplier);\n            assertInstanceOf(TestAuthorisationSupplier.class, supplier);\n        }\n        finally\n        {\n            System.clearProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldUseProvidedAuthorisationServiceSupplierInstance()\n    {\n        final AuthorisationServiceSupplier providedSupplier = mock(AuthorisationServiceSupplier.class);\n        context.authorisationServiceSupplier(providedSupplier);\n        assertSame(providedSupplier, context.authorisationServiceSupplier());\n\n        System.setProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME, TestAuthorisationSupplier.class.getName());\n        try\n        {\n            context.conclude();\n            assertSame(providedSupplier, context.authorisationServiceSupplier());\n        }\n        finally\n        {\n            System.clearProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldThrowIfReplicationChannelIsNotSet()\n    {\n        context.replicationChannel(null);\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldDeriveArchiveClientContextResponseChannelFromArchiveControlChannel()\n    {\n        context.controlChannel(\"aeron:udp?endpoint=127.0.0.2:23005\");\n        context.conclude();\n        assertEquals(\"aeron:udp?endpoint=127.0.0.2:0\", context.archiveClientContext().controlResponseChannel());\n    }\n\n    @Test\n    void shouldThrowConfigurationExceptionIfUnableToDeriveArchiveClientContextResponseChannelDueToEndpointFormat()\n    {\n        context.controlChannel(\"aeron:udp?endpoint=some_logical_name\");\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldThrowConfigurationExceptionIfUnableToDeriveArchiveClientContextResponseChannelDueToEndpointNull()\n    {\n        context.controlChannel(\"aeron:udp?control-mode=dynamic|control=192.168.0.1:12345\");\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldThrowIllegalStateExceptionIfThereIsAnActiveMarkFile()\n    {\n        context.conclude();\n        assertNotNull(context.archiveMarkFile());\n        assertNotEquals(0, context.archiveMarkFile().activityTimestampVolatile());\n\n        final Archive.Context anotherContext = TestContexts.localhostArchive()\n            .archiveDir(context.archiveDir())\n            .errorHandler(context.errorHandler())\n            .aeron(context.aeron());\n\n        final IllegalStateException exception =\n            assertThrowsExactly(IllegalStateException.class, anotherContext::conclude);\n        assertThat(exception.getMessage(), startsWith(\"active mark file detected: \"));\n    }\n\n    @Test\n    void shouldValidateThatSessionCounterIsOfTheCorrectType()\n    {\n        when(context.aeron().countersReader().getCounterTypeId(ARCHIVE_CONTROL_SESSIONS_COUNTER_ID))\n            .thenReturn(AeronCounters.ARCHIVE_ERROR_COUNT_TYPE_ID);\n\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void markFileDirShouldReturnExplicitlySetDirectory(final @TempDir File tempDir)\n    {\n        final File archiveDir = new File(tempDir, \"archiveDir\");\n        final File markFileDir = new File(tempDir, \"markFileDir\");\n        context.archiveDir(archiveDir);\n        context.markFileDir(markFileDir);\n\n        assertSame(markFileDir, context.markFileDir());\n        assertSame(archiveDir, context.archiveDir());\n    }\n\n    @Test\n    void configurationMarkFileDirReturnsNullIfPropertyNotSet()\n    {\n        System.clearProperty(MARK_FILE_DIR_PROP_NAME);\n        assertNull(Archive.Configuration.markFileDir());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"\", \"abc\", \"x/y/z\" })\n    void configurationMarkFileDirReturnsValueSet(final String markFileDir)\n    {\n        System.setProperty(MARK_FILE_DIR_PROP_NAME, markFileDir);\n        try\n        {\n            assertEquals(markFileDir, Archive.Configuration.markFileDir());\n        }\n        finally\n        {\n            System.clearProperty(MARK_FILE_DIR_PROP_NAME);\n        }\n    }\n\n    @Test\n    void concludeShouldCreateMarkFileDirSetViaSystemProperty(final @TempDir File tempDir) throws IOException\n    {\n        final File rootDir = new File(tempDir, \"root\");\n        final File markFileDir = new File(rootDir, \"mark/./file/../dir\");\n        assertFalse(markFileDir.exists());\n\n        System.setProperty(MARK_FILE_DIR_PROP_NAME, markFileDir.getPath());\n        try\n        {\n            assertNull(context.markFileDir());\n\n            context.conclude();\n\n            assertEquals(markFileDir.getCanonicalFile(), context.markFileDir());\n            assertTrue(markFileDir.getCanonicalFile().exists());\n        }\n        finally\n        {\n            System.clearProperty(MARK_FILE_DIR_PROP_NAME);\n        }\n    }\n\n    @Test\n    void concludeShouldCreateMarkFileDirSetDirectly(final @TempDir File tempDir) throws IOException\n    {\n        final File rootDir = new File(tempDir, \"root\");\n        final File markFileDir = new File(rootDir, \"mark/../file/./dir\");\n        assertFalse(markFileDir.exists());\n        context.markFileDir(markFileDir);\n\n        context.conclude();\n\n        assertEquals(markFileDir.getCanonicalFile(), context.markFileDir());\n        assertTrue(markFileDir.getCanonicalFile().exists());\n    }\n\n    @Test\n    void concludeCreatesTotalWriteBytesCounter()\n    {\n        context.totalWriteBytesCounter(null);\n\n        final long archiveId = 555;\n        final ArgumentCaptor<DirectBuffer> tempBuffer = ArgumentCaptor.forClass(DirectBuffer.class);\n        final Counter counter = mockArchiveCounter(\n            archiveId, ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID, 42, tempBuffer);\n\n        context.conclude();\n\n        assertSame(counter, context.totalWriteBytesCounter());\n        final DirectBuffer buffer = tempBuffer.getValue();\n        assertEquals(archiveId, buffer.getLong(0));\n        final String expectedLabel = \"archive-recorder total write bytes - archiveId=\" + archiveId;\n        assertEquals(expectedLabel, buffer.getStringWithoutLengthAscii(SIZE_OF_LONG, expectedLabel.length()));\n    }\n\n    @Test\n    void concludeValidatesTotalWriteBytesCounter()\n    {\n        final Counter counter = mock(Counter.class);\n        context.totalWriteBytesCounter(counter);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().endsWith(\"expected=\" + ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID));\n    }\n\n    @Test\n    void concludeCreatesTotalWriteTimeCounter()\n    {\n        context.totalWriteTimeCounter(null);\n\n        final long archiveId = 89;\n        final ArgumentCaptor<DirectBuffer> tempBuffer = ArgumentCaptor.forClass(DirectBuffer.class);\n        final Counter counter = mockArchiveCounter(\n            archiveId, ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID, -666, tempBuffer);\n\n\n        context.conclude();\n\n        assertSame(counter, context.totalWriteTimeCounter());\n        final DirectBuffer buffer = tempBuffer.getValue();\n        assertEquals(archiveId, buffer.getLong(0));\n        final String expectedLabel = \"archive-recorder total write time in ns - archiveId=\" + archiveId;\n        assertEquals(expectedLabel, buffer.getStringWithoutLengthAscii(SIZE_OF_LONG, expectedLabel.length()));\n    }\n\n    @Test\n    void concludeValidatesTotalWriteTimeCounter()\n    {\n        final Counter counter = mock(Counter.class);\n        context.totalWriteTimeCounter(counter);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().endsWith(\"expected=\" + ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID));\n    }\n\n    @Test\n    void concludeCreatesMaxWriteTimeCounter()\n    {\n        context.maxWriteTimeCounter(null);\n\n        final long archiveId = -76555;\n        final ArgumentCaptor<DirectBuffer> tempBuffer = ArgumentCaptor.forClass(DirectBuffer.class);\n        final Counter counter = mockArchiveCounter(\n            archiveId, ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID, 234126361, tempBuffer);\n\n        context.conclude();\n\n        assertSame(counter, context.maxWriteTimeCounter());\n        final DirectBuffer buffer = tempBuffer.getValue();\n        assertEquals(archiveId, buffer.getLong(0));\n        final String expectedLabel = \"archive-recorder max write time in ns - archiveId=\" + archiveId;\n        assertEquals(expectedLabel, buffer.getStringWithoutLengthAscii(SIZE_OF_LONG, expectedLabel.length()));\n    }\n\n    @Test\n    void concludeValidatesMaxWriteTimeCounter()\n    {\n        final Counter counter = mock(Counter.class);\n        context.maxWriteTimeCounter(counter);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().endsWith(\"expected=\" + ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID));\n    }\n\n    @Test\n    void concludeCreatesTotalReadBytesCounter()\n    {\n        context.totalReadBytesCounter(null);\n\n        final long archiveId = 4234623784689L;\n        final ArgumentCaptor<DirectBuffer> tempBuffer = ArgumentCaptor.forClass(DirectBuffer.class);\n        final Counter counter = mockArchiveCounter(\n            archiveId, ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID, 999, tempBuffer);\n\n        context.conclude();\n\n        assertSame(counter, context.totalReadBytesCounter());\n        final DirectBuffer buffer = tempBuffer.getValue();\n        assertEquals(archiveId, buffer.getLong(0));\n        final String expectedLabel = \"archive-replayer total read bytes - archiveId=\" + archiveId;\n        assertEquals(expectedLabel, buffer.getStringWithoutLengthAscii(SIZE_OF_LONG, expectedLabel.length()));\n    }\n\n    @Test\n    void concludeValidatesTotalReadBytesCounter()\n    {\n        final Counter counter = mock(Counter.class);\n        context.totalReadBytesCounter(counter);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().endsWith(\"expected=\" + ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID));\n    }\n\n    @Test\n    void concludeCreatesTotalReadTimeCounter()\n    {\n        context.totalReadTimeCounter(null);\n\n        final long archiveId = 3;\n        final ArgumentCaptor<DirectBuffer> tempBuffer = ArgumentCaptor.forClass(DirectBuffer.class);\n        final Counter counter = mockArchiveCounter(\n            archiveId, ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID, 0, tempBuffer);\n\n        context.conclude();\n\n        assertSame(counter, context.totalReadTimeCounter());\n        final DirectBuffer buffer = tempBuffer.getValue();\n        assertEquals(archiveId, buffer.getLong(0));\n        final String expectedLabel = \"archive-replayer total read time in ns - archiveId=\" + archiveId;\n        assertEquals(expectedLabel, buffer.getStringWithoutLengthAscii(SIZE_OF_LONG, expectedLabel.length()));\n    }\n\n    @Test\n    void concludeValidatesTotalReadTimeCounter()\n    {\n        final Counter counter = mock(Counter.class);\n        context.totalReadTimeCounter(counter);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().endsWith(\"expected=\" + ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID));\n    }\n\n    @Test\n    void concludeCreatesMaxReadTimeCounter()\n    {\n        context.maxReadTimeCounter(null);\n\n        final long archiveId = 4321L;\n        final ArgumentCaptor<DirectBuffer> tempBuffer = ArgumentCaptor.forClass(DirectBuffer.class);\n        final Counter counter = mockArchiveCounter(archiveId, ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID, -76, tempBuffer);\n\n        context.conclude();\n\n        assertSame(counter, context.maxReadTimeCounter());\n    }\n\n    @Test\n    void concludeValidatesMaxReadTimeCounter()\n    {\n        final Counter counter = mock(Counter.class);\n        context.maxReadTimeCounter(counter);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().endsWith(\"expected=\" + ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID));\n    }\n\n    @Test\n    void archiveIdIsNullValueByDefault()\n    {\n        assertEquals(NULL_VALUE, context.archiveId());\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { Long.MIN_VALUE, Long.MAX_VALUE, 0, 5, 28, -17 })\n    void archiveIdReturnsAssignedValue(final long archiveId)\n    {\n        context.archiveId(archiveId);\n        assertEquals(archiveId, context.archiveId());\n\n        context.conclude();\n        assertEquals(archiveId, context.archiveId());\n    }\n\n    @Test\n    void concludeUsesSystemPropertyToAssignArchiveId()\n    {\n        final long archiveId = 53110011;\n        System.setProperty(ARCHIVE_ID_PROP_NAME, Long.toString(archiveId));\n        try\n        {\n            final Archive.Context ctx = new Archive.Context();\n\n            assertEquals(archiveId, ctx.archiveId());\n        }\n        finally\n        {\n            System.clearProperty(ARCHIVE_ID_PROP_NAME);\n        }\n    }\n\n    @Test\n    void concludeUsesAeronClientIdIfSystemPropertyIsNotSet()\n    {\n        final long archiveId = -236462348238L;\n        when(context.aeron().clientId()).thenReturn(archiveId);\n\n        context.conclude();\n\n        assertEquals(archiveId, context.archiveId());\n    }\n\n    @Test\n    void concludeUsesAeronClientIdIfSystemPropertyIsEmpty(@TempDir final Path archiveDir)\n    {\n        System.setProperty(ARCHIVE_ID_PROP_NAME, \"\");\n        final long archiveId = 42;\n        final Archive.Context ctx = TestContexts.localhostArchive();\n        try\n        {\n            init(ctx, archiveDir.toFile());\n            when(aeron.clientId()).thenReturn(archiveId);\n\n            ctx.conclude();\n\n            assertEquals(archiveId, ctx.archiveId());\n        }\n        finally\n        {\n            ctx.close();\n            System.clearProperty(ARCHIVE_ID_PROP_NAME);\n        }\n    }\n\n    @Test\n    void concludeUsesAeronClientIdIfSystemPropertyIsSetToNullValue(@TempDir final Path archiveDir)\n    {\n        System.setProperty(ARCHIVE_ID_PROP_NAME, \"-1\");\n        final long archiveId = 888;\n        final Archive.Context ctx = TestContexts.localhostArchive();\n        try\n        {\n            init(ctx, archiveDir.toFile());\n            when(aeron.clientId()).thenReturn(archiveId);\n\n            ctx.conclude();\n\n            assertEquals(archiveId, ctx.archiveId());\n        }\n        finally\n        {\n            ctx.close();\n            System.clearProperty(ARCHIVE_ID_PROP_NAME);\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 119, 0, -5 })\n    void shouldPrintArchiveId(final long archiveId)\n    {\n        context.archiveId(archiveId);\n        context.conclude();\n\n        assertThat(context.toString(), containsString(\"archiveId=\" + archiveId));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -31, HEADER_LENGTH, MAX_UDP_PAYLOAD_LENGTH + 1, 69 })\n    void shouldValidateControlMtuLength(final int controlMtuLength)\n    {\n        context.controlMtuLength(controlMtuLength);\n\n        final ConfigurationException exception =\n            assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().contains(\"mtuLength=\" + controlMtuLength));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -100, 0, TERM_MIN_LENGTH - 1, TERM_MAX_LENGTH + 64, 100000 })\n    void shouldValidateControlTermBufferLength(final int controlTermBufferLength)\n    {\n        context.controlTermBufferLength(controlTermBufferLength);\n\n        final IllegalStateException exception =\n            assertThrowsExactly(IllegalStateException.class, context::conclude);\n        assertTrue(exception.getMessage().contains(\": length=\" + controlTermBufferLength));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -3, ERROR_BUFFER_LENGTH_DEFAULT - 1, Integer.MAX_VALUE })\n    void shouldValidateErrorBufferLengthSetExplicitly(final int errorBufferLength)\n    {\n        context.errorBufferLength(errorBufferLength);\n\n        final ConfigurationException exception =\n            assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\"ERROR - invalid errorBufferLength=\" + errorBufferLength, exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, ERROR_BUFFER_LENGTH_DEFAULT - 1, Integer.MAX_VALUE })\n    void shouldValidateErrorBufferLengthSetViaSystemProperty(final int errorBufferLength)\n    {\n        System.setProperty(ERROR_BUFFER_LENGTH_PROP_NAME, String.valueOf(errorBufferLength));\n        try\n        {\n            final Archive.Context ctx = TestContexts.localhostArchive();\n            ctx.archiveId(42);\n            final ConfigurationException exception =\n                assertThrowsExactly(ConfigurationException.class, ctx::conclude);\n            assertEquals(\"ERROR - invalid errorBufferLength=\" + errorBufferLength, exception.getMessage());\n        }\n        finally\n        {\n            System.clearProperty(ERROR_BUFFER_LENGTH_PROP_NAME);\n        }\n    }\n\n    @Test\n    void controlChannelEnabledReturnsTrueWhenPropertyIsNotSet()\n    {\n        System.clearProperty(CONTROL_CHANNEL_ENABLED_PROP_NAME);\n        assertTrue(Archive.Configuration.controlChannelEnabled());\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"'', false\", \"true, true\", \"True, false\", \"xyz, false\" })\n    void controlChannelEnabledReturnsTrueWhenPropertyIsNotSet(final String propValue, final boolean expected)\n    {\n        System.setProperty(CONTROL_CHANNEL_ENABLED_PROP_NAME, propValue);\n        try\n        {\n            assertEquals(expected, Archive.Configuration.controlChannelEnabled());\n        }\n        finally\n        {\n            System.clearProperty(CONTROL_CHANNEL_ENABLED_PROP_NAME);\n        }\n    }\n\n    @Test\n    void controlChannelMustBeSpecified()\n    {\n        context.controlChannel(null);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\"ERROR - Archive.Context.controlChannel must be set\", exception.getMessage());\n    }\n\n    @Test\n    void controlChannelMustBeUdpChannel()\n    {\n        context.controlChannel(\"aeron:ipc\");\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - Archive.Context.controlChannel must be UDP media: uri=\" + context.controlChannel(),\n            exception.getMessage());\n    }\n\n    @Test\n    void controlChannelMustHaveValidEndpointSpecifiedIfControlResponseChannelOfTheReplicationClientIsNotSet()\n    {\n        context.controlChannel(\"aeron:udp?endpoint=localhost\");\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - Unable to derive Archive.Context.archiveClientContext.controlResponseChannel as \" +\n            \"Archive.Context.controlChannel.endpoint=localhost and is not in the <host>:<port> format\",\n            exception.getMessage());\n    }\n\n    @Test\n    void whenControlChannelIsDisabledTheControlResponseChannelOnTheReplicationClientMustBeSet()\n    {\n        context.controlChannelEnabled(false);\n        context.controlChannel(\"rubbish\");\n        final AeronArchive.Context archiveClientContext = new AeronArchive.Context();\n        archiveClientContext.controlResponseChannel(null);\n        context.archiveClientContext(archiveClientContext);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - Archive.Context.archiveClientContext.controlResponseChannel must be set if \" +\n            \"Archive.Context.controlChannelEnabled is false\",\n            exception.getMessage());\n    }\n\n    @Test\n    void controlChannelCanBeDisabled()\n    {\n        context.controlChannelEnabled(false);\n        context.controlChannel(null);\n        final AeronArchive.Context archiveClientContext = new AeronArchive.Context();\n        final String responseChannel = \"aeron:udp?localhost:0\";\n        archiveClientContext.controlResponseChannel(responseChannel);\n        context.archiveClientContext(archiveClientContext);\n\n        context.conclude();\n\n        assertFalse(context.controlChannelEnabled());\n        assertNull(context.controlChannel());\n        assertSame(archiveClientContext, context.archiveClientContext());\n        assertEquals(responseChannel, context.archiveClientContext().controlResponseChannel());\n    }\n\n    @Test\n    void closeOrderWhenArchiveOwnsAeronClient() throws Exception\n    {\n        final Aeron aeron = mock(Aeron.class);\n        final ArchiveMarkFile archiveMarkFile = mock(ArchiveMarkFile.class);\n        final FileChannel archiveDirChannel = mock(FileChannel.class);\n        final Exception fileChannelException = throwingClose(archiveDirChannel, new IOException(\"file close\"));\n        final Catalog catalog = mock(Catalog.class);\n        final Exception catalogException = throwingClose(catalog, new IllegalStateException(\"catalog\"));\n        final Counter controlSessionsCounter = mock(Counter.class);\n        final AtomicCounter errorCounter = mock(AtomicCounter.class);\n        final ErrorHandler errorHandler = mock(ErrorHandler.class, withSettings().extraInterfaces(AutoCloseable.class));\n        final CountedErrorHandler countedErrorHandler = mock(CountedErrorHandler.class);\n        context.aeron(aeron)\n            .ownsAeronClient(true)\n            .archiveMarkFile(archiveMarkFile)\n            .archiveDirChannel(archiveDirChannel)\n            .catalog(catalog)\n            .errorHandler(errorHandler)\n            .countedErrorHandler(countedErrorHandler)\n            .controlSessionsCounter(controlSessionsCounter)\n            .errorCounter(errorCounter);\n\n        context.close();\n\n        final InOrder inOrder = inOrder(\n            aeron,\n            archiveMarkFile,\n            archiveDirChannel,\n            catalog,\n            errorHandler,\n            countedErrorHandler,\n            controlSessionsCounter,\n            errorCounter);\n        inOrder.verify(catalog).close();\n        inOrder.verify(countedErrorHandler).onError(catalogException);\n        inOrder.verify(archiveDirChannel).close();\n        inOrder.verify(countedErrorHandler).onError(fileChannelException);\n        inOrder.verify(aeron).close();\n        inOrder.verify((AutoCloseable)errorHandler).close();\n        inOrder.verify(archiveMarkFile).close();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @SuppressWarnings({ \"methodLength\", \"indentation\" })\n    @Test\n    void closeOrderWhenAeronClientIsNotOwned() throws Exception\n    {\n        final Aeron aeron = mock(Aeron.class);\n        final ArchiveMarkFile archiveMarkFile = mock(ArchiveMarkFile.class);\n        final FileChannel archiveDirChannel = mock(FileChannel.class);\n        final Exception fileChannelException = throwingClose(archiveDirChannel, new IOException(\"file close\"));\n        final Catalog catalog = mock(Catalog.class);\n        final Exception catalogException = throwingClose(catalog, new IllegalStateException(\"catalog\"));\n        final Counter controlSessionsCounter = mock(Counter.class);\n        final Exception controlSessionsCounterException = throwingClose(\n            controlSessionsCounter, new UnsupportedOperationException(\"controlSessionsCounter\"));\n        final Counter totalWriteBytesCounter = mock(Counter.class);\n        final Exception totalWriteBytesCounterException = throwingClose(\n            totalWriteBytesCounter, new IllegalStateException(\"totalWriteBytesCounter\"));\n        final Counter totalWriteTimeCounter = mock(Counter.class);\n        final Exception totalWriteTimeCounterException = throwingClose(\n            totalWriteTimeCounter, new IllegalStateException(\"totalWriteTimeCounter\"));\n        final Counter maxWriteTimeCounter = mock(Counter.class);\n        final Exception maxWriteTimeCounterException = throwingClose(\n            maxWriteTimeCounter, new NumberFormatException(\"maxWriteTimeCounter\"));\n        final Counter totalReadBytesCounter = mock(Counter.class);\n        final Exception totalReadBytesCounterException = throwingClose(\n            totalReadBytesCounter, new NumberFormatException(\"totalReadBytesCounter\"));\n        final Counter totalReadTimeCounter = mock(Counter.class);\n        final Exception totalReadTimeCounterException = throwingClose(\n            totalReadTimeCounter, new NoSuchMethodException(\"totalReadTimeCounter\"));\n        final Counter maxReadTimeCounter = mock(Counter.class);\n        final Exception maxReadTimeCounterException = throwingClose(\n            maxReadTimeCounter, new ConfigurationException(\"maxReadTimeCounter\"));\n        final AtomicCounter errorCounter = mock(AtomicCounter.class);\n        final AtomicCounter conductorDutyCycleTrackerMaxCycleTime = mock(AtomicCounter.class);\n        final Exception conductorDutyCycleTrackerMaxCycleTimeException = throwingClose(\n            conductorDutyCycleTrackerMaxCycleTime, new NoSuchFileException(\"conductorDutyCycleTrackerMaxCycleTime\"));\n        final AtomicCounter conductorDutyCycleTrackerCycleTimeThresholdExceededCount = mock(AtomicCounter.class);\n        final Exception conductorDutyCycleTrackerCycleTimeThresholdExceededCountException = throwingClose(\n            conductorDutyCycleTrackerCycleTimeThresholdExceededCount,\n            new NoSuchFileException(\"conductorDutyCycleTrackerMaxCycleTime\"));\n        final AtomicCounter recorderDutyCycleTrackerMaxCycleTime = mock(AtomicCounter.class);\n        final Exception recorderDutyCycleTrackerMaxCycleTimeException = throwingClose(\n            recorderDutyCycleTrackerMaxCycleTime,\n            new UnsupportedOperationException(\"recorderDutyCycleTrackerMaxCycleTimeException\"));\n        final AtomicCounter recorderDutyCycleTrackerCycleTimeThresholdExceededCount = mock(AtomicCounter.class);\n        final Exception recorderDutyCycleTrackerCycleTimeThresholdExceededCountException = throwingClose(\n            recorderDutyCycleTrackerCycleTimeThresholdExceededCount,\n            new UnsupportedOperationException(\"recorderDutyCycleTrackerCycleTimeThresholdExceededCount\"));\n        final AtomicCounter replayerDutyCycleTrackerMaxCycleTime = mock(AtomicCounter.class);\n        final Exception replayerDutyCycleTrackerMaxCycleTimeException = throwingClose(\n            replayerDutyCycleTrackerMaxCycleTime,\n            new UnsupportedOperationException(\"replayerDutyCycleTrackerMaxCycleTime\"));\n        final AtomicCounter replayerDutyCycleTrackerCycleTimeThresholdExceededCount = mock(AtomicCounter.class);\n        final Exception replayerDutyCycleTrackerCycleTimeThresholdExceededCountException = throwingClose(\n            replayerDutyCycleTrackerCycleTimeThresholdExceededCount,\n            new UnsupportedOperationException(\"replayerDutyCycleTrackerCycleTimeThresholdExceededCount\"));\n        final ErrorHandler errorHandler = mock(ErrorHandler.class, withSettings().extraInterfaces(AutoCloseable.class));\n        final CountedErrorHandler countedErrorHandler = mock(CountedErrorHandler.class);\n        context.aeron(aeron)\n            .archiveMarkFile(archiveMarkFile)\n            .archiveDirChannel(archiveDirChannel)\n            .catalog(catalog)\n            .errorHandler(errorHandler)\n            .errorCounter(errorCounter)\n            .countedErrorHandler(countedErrorHandler)\n            .controlSessionsCounter(controlSessionsCounter)\n            .totalWriteBytesCounter(totalWriteBytesCounter)\n            .totalWriteTimeCounter(totalWriteTimeCounter)\n            .maxWriteTimeCounter(maxWriteTimeCounter)\n            .totalReadBytesCounter(totalReadBytesCounter)\n            .totalReadTimeCounter(totalReadTimeCounter)\n            .maxReadTimeCounter(maxReadTimeCounter)\n            .conductorDutyCycleTracker(new DutyCycleStallTracker(\n            conductorDutyCycleTrackerMaxCycleTime, conductorDutyCycleTrackerCycleTimeThresholdExceededCount, 1))\n            .recorderDutyCycleTracker(new DutyCycleStallTracker(\n            recorderDutyCycleTrackerMaxCycleTime, recorderDutyCycleTrackerCycleTimeThresholdExceededCount, 1))\n            .replayerDutyCycleTracker(new DutyCycleStallTracker(\n            replayerDutyCycleTrackerMaxCycleTime, replayerDutyCycleTrackerCycleTimeThresholdExceededCount, 1));\n\n        context.close();\n\n        final InOrder inOrder = inOrder(\n            aeron,\n            archiveMarkFile,\n            archiveDirChannel,\n            catalog,\n            errorHandler,\n            countedErrorHandler,\n            controlSessionsCounter,\n            totalWriteBytesCounter,\n            totalWriteTimeCounter,\n            maxWriteTimeCounter,\n            totalReadBytesCounter,\n            totalReadTimeCounter,\n            maxReadTimeCounter,\n            conductorDutyCycleTrackerMaxCycleTime,\n            conductorDutyCycleTrackerCycleTimeThresholdExceededCount,\n            recorderDutyCycleTrackerMaxCycleTime,\n            recorderDutyCycleTrackerCycleTimeThresholdExceededCount,\n            replayerDutyCycleTrackerMaxCycleTime,\n            replayerDutyCycleTrackerCycleTimeThresholdExceededCount,\n            errorCounter);\n        inOrder.verify(catalog).close();\n        inOrder.verify(countedErrorHandler).onError(catalogException);\n        inOrder.verify(archiveDirChannel).close();\n        inOrder.verify(countedErrorHandler).onError(fileChannelException);\n        inOrder.verify(controlSessionsCounter).close();\n        inOrder.verify(countedErrorHandler).onError(controlSessionsCounterException);\n        inOrder.verify(totalWriteBytesCounter).close();\n        inOrder.verify(countedErrorHandler).onError(totalWriteBytesCounterException);\n        inOrder.verify(totalWriteTimeCounter).close();\n        inOrder.verify(countedErrorHandler).onError(totalWriteTimeCounterException);\n        inOrder.verify(maxWriteTimeCounter).close();\n        inOrder.verify(countedErrorHandler).onError(maxWriteTimeCounterException);\n        inOrder.verify(totalReadBytesCounter).close();\n        inOrder.verify(countedErrorHandler).onError(totalReadBytesCounterException);\n        inOrder.verify(totalReadTimeCounter).close();\n        inOrder.verify(countedErrorHandler).onError(totalReadTimeCounterException);\n        inOrder.verify(maxReadTimeCounter).close();\n        inOrder.verify(countedErrorHandler).onError(maxReadTimeCounterException);\n        inOrder.verify(conductorDutyCycleTrackerMaxCycleTime).close();\n        inOrder.verify(countedErrorHandler).onError(conductorDutyCycleTrackerMaxCycleTimeException);\n        inOrder.verify(conductorDutyCycleTrackerCycleTimeThresholdExceededCount).close();\n        inOrder.verify(countedErrorHandler).onError(conductorDutyCycleTrackerCycleTimeThresholdExceededCountException);\n        inOrder.verify(recorderDutyCycleTrackerMaxCycleTime).close();\n        inOrder.verify(countedErrorHandler).onError(recorderDutyCycleTrackerMaxCycleTimeException);\n        inOrder.verify(recorderDutyCycleTrackerCycleTimeThresholdExceededCount).close();\n        inOrder.verify(countedErrorHandler).onError(recorderDutyCycleTrackerCycleTimeThresholdExceededCountException);\n        inOrder.verify(replayerDutyCycleTrackerMaxCycleTime).close();\n        inOrder.verify(countedErrorHandler).onError(replayerDutyCycleTrackerMaxCycleTimeException);\n        inOrder.verify(replayerDutyCycleTrackerCycleTimeThresholdExceededCount).close();\n        inOrder.verify(countedErrorHandler).onError(replayerDutyCycleTrackerCycleTimeThresholdExceededCountException);\n        inOrder.verify(errorCounter).close();\n        inOrder.verify((AutoCloseable)errorHandler).close();\n        inOrder.verify(archiveMarkFile).close();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldNotCreateLinkToTheDefaultMarkFile(@TempDir final Path tempDir) throws IOException\n    {\n        final Path archiveDir = tempDir.resolve(\"archive-dir\");\n        Files.createDirectories(archiveDir);\n        final Path linkFile = archiveDir.resolve(ArchiveMarkFile.LINK_FILENAME);\n        Files.createFile(linkFile);\n        context.archiveDir(archiveDir.toFile());\n\n        context.conclude();\n\n        assertEquals(archiveDir.toFile().getCanonicalFile(), context.markFileDir());\n        final ArchiveMarkFile archiveMarkFile = context.archiveMarkFile();\n        assertNotNull(archiveMarkFile);\n        assertEquals(archiveDir.toFile().getCanonicalFile(), archiveMarkFile.parentDirectory());\n        final Path markFile = archiveDir.resolve(ArchiveMarkFile.FILENAME);\n        assertTrue(Files.exists(markFile));\n        assertTrue(Files.notExists(linkFile));\n    }\n\n    @Test\n    void shouldCreateALinkToTheArchiveMarkFileInAnotherDirectory(\n        @TempDir final Path archiveDir, @TempDir final Path temp2) throws IOException\n    {\n        final File markFileDirectory = temp2.resolve(\"x/y/../z/../w\").toFile();\n        context.archiveDir(archiveDir.toFile()).markFileDir(markFileDirectory);\n\n        context.conclude();\n\n        assertEquals(archiveDir.toFile().getCanonicalFile(), context.archiveDir());\n        assertEquals(markFileDirectory.getCanonicalFile(), context.markFileDir());\n        final ArchiveMarkFile archiveMarkFile = context.archiveMarkFile();\n        assertNotNull(archiveMarkFile);\n        assertEquals(markFileDirectory.getCanonicalFile(), archiveMarkFile.parentDirectory());\n        final Path markFile = markFileDirectory.getCanonicalFile().toPath().resolve(ArchiveMarkFile.FILENAME);\n        assertTrue(Files.exists(markFile));\n        final Path linkFile = archiveDir.resolve(ArchiveMarkFile.LINK_FILENAME);\n        assertTrue(Files.exists(linkFile));\n        assertEquals(markFileDirectory.getCanonicalPath(), new String(Files.readAllBytes(linkFile), US_ASCII));\n    }\n\n    @Test\n    void shouldCreateALinkToTheArchiveMarkFileWhichIsExplicitlyAssigned(\n        @TempDir final Path archiveDir,\n        @TempDir final Path markFileDir,\n        @TempDir final Path archiveMarkFileDir) throws IOException\n    {\n        final ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(\n            archiveMarkFileDir.resolve(\"my-funny-file.txt\").toFile(), 1024 * 1024, 8096, SystemEpochClock.INSTANCE, 0);\n        context\n            .archiveDir(archiveDir.toFile())\n            .markFileDir(markFileDir.toFile())\n            .archiveMarkFile(archiveMarkFile);\n\n        context.conclude();\n\n        assertEquals(archiveDir.toFile().getCanonicalFile(), context.archiveDir());\n        assertEquals(markFileDir.toFile().getCanonicalFile(), context.markFileDir());\n        assertSame(archiveMarkFile, context.archiveMarkFile());\n        assertEquals(archiveMarkFileDir.toFile(), archiveMarkFile.parentDirectory());\n        final Path linkFile = archiveDir.resolve(ArchiveMarkFile.LINK_FILENAME);\n        assertTrue(Files.exists(linkFile));\n        assertEquals(\n            archiveMarkFileDir.toFile().getCanonicalPath(), new String(Files.readAllBytes(linkFile), US_ASCII));\n    }\n\n    @Test\n    void shouldVerifyConductorInvokeModeOnAeronClient()\n    {\n        assertSame(aeron, context.aeron());\n        when(aeron.context().useConductorAgentInvoker()).thenReturn(false);\n\n        final ArchiveException exception = assertThrowsExactly(ArchiveException.class, context::conclude);\n        assertEquals(\n            \"ERROR - Aeron client instance must set Aeron.Context.useConductorInvoker(true)\",\n            exception.getMessage());\n    }\n\n    @Test\n    void shouldUseExplicitlyAssignedClient()\n    {\n        assertSame(aeron, context.aeron());\n        assertFalse(context.ownsAeronClient());\n\n        context.conclude();\n\n        assertSame(aeron, context.aeron());\n        assertFalse(context.ownsAeronClient());\n    }\n\n    @Test\n    void shouldInitializeAeronDirectoryFromTheClient()\n    {\n        context.aeronDirectoryName(null);\n        final String clientDirectory = \"target/dir\";\n        when(context.aeron().context().aeronDirectoryName()).thenReturn(clientDirectory);\n        assertNull(context.aeronDirectoryName());\n\n        context.conclude();\n\n        assertEquals(clientDirectory, context.aeronDirectoryName());\n    }\n\n    @Test\n    void shouldInitializeArchiveDirectoryNameFromArchiveDir(@TempDir final Path root) throws IOException\n    {\n        final File archiveDir = root.resolve(\"n/m/../x/./1111\").toFile();\n        context.archiveDir(archiveDir);\n\n        context.conclude();\n\n        assertEquals(archiveDir.getCanonicalPath(), context.archiveDirectoryName());\n    }\n\n    @ParameterizedTest\n    @EnumSource(ArchiveThreadingMode.class)\n    void shouldExposeThreadingModeInfoViaDutyCycleTrackers(final ArchiveThreadingMode threadingMode)\n    {\n        context\n            .conductorCycleThresholdNs(TimeUnit.MILLISECONDS.toNanos(150))\n            .recorderCycleThresholdNs(TimeUnit.SECONDS.toNanos(2))\n            .replayerCycleThresholdNs(TimeUnit.MICROSECONDS.toNanos(879))\n            .threadingMode(threadingMode)\n            .archiveId(888);\n\n        context.conclude();\n\n        final DutyCycleStallTracker conductorDutyCycleTracker =\n            (DutyCycleStallTracker)context.conductorDutyCycleTracker();\n        assertNotNull(conductorDutyCycleTracker);\n        assertEquals(\n            \"archive-conductor max cycle time in ns: \" + threadingMode + \" - archiveId=\" + context.archiveId(),\n            conductorDutyCycleTracker.maxCycleTime().label());\n        assertEquals(\n            \"archive-conductor work cycle time exceeded count: threshold=150ms \" + threadingMode +\n                \" - archiveId=\" + context.archiveId(),\n            conductorDutyCycleTracker.cycleTimeThresholdExceededCount().label());\n\n        if (ArchiveThreadingMode.DEDICATED == threadingMode)\n        {\n            final DutyCycleStallTracker recorderDutyCycleTracker =\n                (DutyCycleStallTracker)context.recorderDutyCycleTracker();\n            assertNotNull(recorderDutyCycleTracker);\n            assertEquals(\n                \"archive-recorder max cycle time in ns: \" + threadingMode + \" - archiveId=\" + context.archiveId(),\n                recorderDutyCycleTracker.maxCycleTime().label());\n            assertEquals(\n                \"archive-recorder work cycle time exceeded count: threshold=2s \" + threadingMode +\n                    \" - archiveId=\" + context.archiveId(),\n                recorderDutyCycleTracker.cycleTimeThresholdExceededCount().label());\n\n            final DutyCycleStallTracker replayerDutyCycleTracker =\n                (DutyCycleStallTracker)context.replayerDutyCycleTracker();\n            assertNotNull(replayerDutyCycleTracker);\n            assertEquals(\n                \"archive-replayer max cycle time in ns: \" + threadingMode + \" - archiveId=\" + context.archiveId(),\n                replayerDutyCycleTracker.maxCycleTime().label());\n            assertEquals(\n                \"archive-replayer work cycle time exceeded count: threshold=879us \" + threadingMode +\n                    \" - archiveId=\" + context.archiveId(),\n                replayerDutyCycleTracker.cycleTimeThresholdExceededCount().label());\n        }\n    }\n\n    @Test\n    void shouldNotSetClientNameOnAnExplicitlyAssignedAeronClient()\n    {\n        final Aeron.Context aeronContext = aeron.context();\n        when(aeronContext.clientName()).thenReturn(\"sample\");\n        context.archiveId(42);\n\n        context.conclude();\n\n        assertEquals(\"sample\", aeronContext.clientName());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = ArchiveThreadingMode.class, names = \"INVOKER\", mode = EnumSource.Mode.EXCLUDE)\n    void shouldRequireInvokerModeIfMediaDriverInvokerIsSpecified(final ArchiveThreadingMode threadingMode)\n    {\n        final AgentInvoker mediaDriverAgentInvoker = mock(AgentInvoker.class);\n        context\n            .mediaDriverAgentInvoker(mediaDriverAgentInvoker)\n            .threadingMode(threadingMode);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - Archive.Context.threadingMode(ArchiveThreadingMode.INVOKER) must be set if \" +\n            \"Archive.Context.mediaDriverAgentInvoker is set\",\n            exception.getMessage());\n    }\n\n    private Counter mockArchiveCounter(\n        final long archiveId, final int typeId, final int id, final ArgumentCaptor<DirectBuffer> tempBuffer)\n    {\n        context.archiveId(archiveId);\n        final Aeron aeron = context.aeron();\n        final Counter counter = mockCounter(aeron.countersReader(), typeId, id, \"label\");\n        when(aeron.addCounter(\n            eq(typeId), tempBuffer.capture(), eq(0), eq(SIZE_OF_LONG), any(), eq(SIZE_OF_LONG), anyInt()))\n            .thenReturn(counter);\n        return counter;\n    }\n\n    private static Counter mockCounter(\n        final CountersReader countersReader, final int typeId, final int id, final String label)\n    {\n        final Counter counter = mock(Counter.class);\n        when(counter.id()).thenReturn(id);\n        when(counter.label()).thenReturn(label);\n        when(countersReader.getCounterTypeId(id)).thenReturn(typeId);\n        return counter;\n    }\n\n    public static class TestAuthorisationSupplier implements AuthorisationServiceSupplier\n    {\n        public AuthorisationService get()\n        {\n            return new TestAuthorisationService();\n        }\n    }\n\n    static class TestAuthorisationService implements AuthorisationService\n    {\n        public boolean isAuthorised(\n            final int protocolId, final int actionId, final Object type, final byte[] encodedPrincipal)\n        {\n            return false;\n        }\n    }\n\n    private static Exception throwingClose(final AutoCloseable resource, final Exception exception) throws Exception\n    {\n        doThrow(exception).when(resource).close();\n        return exception;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveCountersTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.Counter;\nimport io.aeron.test.Tests;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.InOrder;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.concurrent.status.CountersReader.COUNTER_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.mockito.Mockito.*;\n\nclass ArchiveCountersTest\n{\n    @Test\n    void allocateCounterUsingAeronClientIdAsArchiveIdentifier()\n    {\n        final int typeId = 999;\n        final String name = \"<test counter>\";\n        final long archiveId = -1832178932131546L;\n        final String expectedLabel = name + \" - archiveId=\" + archiveId;\n        final Aeron aeron = mock(Aeron.class);\n        final MutableDirectBuffer tempBuffer = new UnsafeBuffer(new byte[200]);\n        final Counter counter = mock(Counter.class);\n        when(aeron.clientId()).thenReturn(archiveId);\n        when(aeron.addCounter(typeId, tempBuffer, 0, SIZE_OF_LONG, tempBuffer, SIZE_OF_LONG, expectedLabel.length()))\n            .thenReturn(counter);\n\n        final Counter result = ArchiveCounters.allocate(aeron, tempBuffer, typeId, name, aeron.clientId());\n\n        assertSame(counter, result);\n        final InOrder inOrder = inOrder(aeron);\n        inOrder.verify(aeron).clientId();\n        inOrder.verify(aeron).addCounter(anyInt(), any(), anyInt(), anyInt(), any(), anyInt(), anyInt());\n        inOrder.verifyNoMoreInteractions();\n        assertEquals(archiveId, tempBuffer.getLong(0));\n        assertEquals(expectedLabel, tempBuffer.getStringWithoutLengthAscii(SIZE_OF_LONG, expectedLabel.length()));\n    }\n\n    @Test\n    void allocateErrorCounter()\n    {\n        final long archiveId = 24623864;\n        final String expectedLabel = \"Archive Errors - archiveId=\" + archiveId + \" \" +\n            AeronCounters.formatVersionInfo(ArchiveVersion.VERSION, ArchiveVersion.GIT_SHA);\n        final Aeron aeron = mock(Aeron.class);\n        final MutableDirectBuffer tempBuffer = new UnsafeBuffer(new byte[200]);\n        final Counter counter = mock(Counter.class);\n        when(aeron.clientId()).thenReturn(archiveId);\n        when(aeron.addCounter(\n            AeronCounters.ARCHIVE_ERROR_COUNT_TYPE_ID,\n            tempBuffer,\n            0,\n            SIZE_OF_LONG,\n            tempBuffer,\n            SIZE_OF_LONG,\n            expectedLabel.length()))\n            .thenReturn(counter);\n\n        final Counter result = ArchiveCounters.allocateErrorCounter(aeron, tempBuffer, aeron.clientId());\n\n        assertSame(counter, result);\n        final InOrder inOrder = inOrder(aeron);\n        inOrder.verify(aeron).clientId();\n        inOrder.verify(aeron).addCounter(anyInt(), any(), anyInt(), anyInt(), any(), anyInt(), anyInt());\n        inOrder.verifyNoMoreInteractions();\n        assertEquals(archiveId, tempBuffer.getLong(0));\n        assertEquals(expectedLabel, tempBuffer.getStringWithoutLengthAscii(SIZE_OF_LONG, expectedLabel.length()));\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"5,8\", \"42,-10\", \"-19, 61312936129398123\" })\n    void findReturnsNullValueIfCounterNotFound(final int typeId, final long archiveId)\n    {\n        final CountersManager countersManager = Tests.newCountersManager(2 * COUNTER_LENGTH);\n        assertEquals(1, countersManager.maxCounterId());\n        countersManager.allocate(\n            \"test\",\n            42,\n            (keyBuffer) -> keyBuffer.putLong(0, 61312936129398123L));\n\n        assertEquals(NULL_VALUE, ArchiveCounters.find(countersManager, typeId, archiveId));\n    }\n\n    @Test\n    void findReturnsFirstMatchingCounter()\n    {\n        final CountersManager countersManager = Tests.newCountersManager(8 * COUNTER_LENGTH);\n        final int typeId = 7;\n        final long archiveId = Long.MIN_VALUE / 13;\n        countersManager.allocate(\n            \"test 1\",\n            typeId,\n            (keyBuffer) -> {});\n        countersManager.allocate(\n            \"test 2\",\n            typeId,\n            (keyBuffer) -> keyBuffer.putLong(0, 42));\n        countersManager.allocate(\n            \"test 3\",\n            21,\n            (keyBuffer) -> keyBuffer.putLong(0, archiveId));\n        final int counter4 = countersManager.allocate(\n            \"test 4\",\n            typeId,\n            (keyBuffer) -> keyBuffer.putLong(0, archiveId));\n        countersManager.allocate(\n            \"test 5\",\n            typeId,\n            (keyBuffer) -> keyBuffer.putLong(0, archiveId));\n\n        assertEquals(counter4, ArchiveCounters.find(countersManager, typeId, archiveId));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { Long.MIN_VALUE, Long.MAX_VALUE, 0, -1, 56436747823L, -1235127312317278312L })\n    void lengthOfArchiveIdLabelCountsNumberOfAsciiDigitsRequiredToEncodeArchiveId(final long archiveId)\n    {\n        final int archiveIdLength = String.valueOf(archiveId).length();\n\n        assertEquals(\n            ArchiveCounters.ARCHIVE_ID_LABEL_SUFFIX.length() + archiveIdLength,\n            ArchiveCounters.lengthOfArchiveIdLabel(archiveId));\n    }\n\n    @Test\n    void appendArchiveIdLabel()\n    {\n        final int offset = 13;\n        final long archiveId = -23462384L;\n        final UnsafeBuffer buffer = new UnsafeBuffer(new byte[100]);\n\n        final int length = ArchiveCounters.appendArchiveIdLabel(buffer, offset, archiveId);\n\n        assertEquals(ArchiveCounters.lengthOfArchiveIdLabel(archiveId), length);\n        final int prefixLength = ArchiveCounters.ARCHIVE_ID_LABEL_SUFFIX.length();\n        assertEquals(\n            ArchiveCounters.ARCHIVE_ID_LABEL_SUFFIX,\n            buffer.getStringWithoutLengthAscii(offset, prefixLength));\n        assertEquals(archiveId, buffer.parseLongAscii(offset + prefixLength, length - prefixLength));\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveMarkFileTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.mark.MarkFileHeaderDecoder;\nimport io.aeron.archive.codecs.mark.MarkFileHeaderEncoder;\nimport io.aeron.archive.codecs.mark.MessageHeaderDecoder;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.IoUtil;\nimport org.agrona.MarkFile;\nimport org.agrona.SemanticVersion;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.DisabledForJreRange;\nimport org.junit.jupiter.api.condition.JRE;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.InOrder;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.MappedByteBuffer;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PAGE_MIN_SIZE;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ArchiveMarkFileTest\n{\n    private static final int ERROR_BUFFER_LENGTH = 4096;\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    void shouldUseMarkFileDirectory(final @TempDir File tempDir)\n    {\n        final File archiveDir = new File(tempDir, \"archive_dir\");\n        final File markFileDir = new File(tempDir, \"mark-file-home\");\n        final File markFile = new File(markFileDir, ArchiveMarkFile.FILENAME);\n        assertTrue(markFileDir.mkdirs());\n        assertFalse(markFile.exists());\n\n        final Aeron aeron = mock(Aeron.class);\n        final Aeron.Context aeronCtx = mock(Aeron.Context.class);\n        when(aeronCtx.filePageSize()).thenReturn(PAGE_MIN_SIZE);\n        when(aeron.context()).thenReturn(aeronCtx);\n\n        final Archive.Context context = TestContexts.localhostArchive()\n            .archiveDir(archiveDir)\n            .markFileDir(markFileDir)\n            .aeron(aeron);\n        shouldEncodeMarkFileFromArchiveContext(context);\n\n        assertTrue(markFile.exists());\n        assertFalse(archiveDir.exists());\n    }\n\n    @Test\n    @SuppressWarnings(\"try\")\n    void shouldCreateLinkFileToMarkFileDirAndRemoveLinkFileIfArchiveDirMatchesMarkFileDir(final @TempDir File tempDir)\n    {\n        final File aeronDir = new File(tempDir, \"aeron\");\n        final File archiveDir = new File(tempDir, \"archive_dir\");\n        final File markFileDir = new File(tempDir, \"mark-file-home\");\n\n        final MediaDriver.Context driverContext = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.getAbsolutePath())\n            .threadingMode(ThreadingMode.SHARED);\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .aeronDirectoryName(driverContext.aeronDirectoryName())\n            .archiveDir(archiveDir)\n            .markFileDir(markFileDir)\n            .epochClock(SystemEpochClock.INSTANCE)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n        try (TestMediaDriver ignored = TestMediaDriver.launch(driverContext.clone(), systemTestWatcher);\n            Archive ignored1 = Archive.launch(archiveContext.clone()))\n        {\n            assertTrue(new File(markFileDir, ArchiveMarkFile.FILENAME).exists());\n            assertTrue(new File(archiveDir, ArchiveMarkFile.LINK_FILENAME).exists());\n            assertFalse(new File(archiveDir, ArchiveMarkFile.FILENAME).exists());\n        }\n\n        archiveContext.markFileDir(null);\n        try (TestMediaDriver ignored = TestMediaDriver.launch(driverContext.clone(), systemTestWatcher);\n            Archive ignored1 = Archive.launch(archiveContext.clone()))\n        {\n            assertTrue(new File(archiveDir, ArchiveMarkFile.FILENAME).exists());\n            assertFalse(new File(archiveDir, ArchiveMarkFile.LINK_FILENAME).exists());\n        }\n    }\n\n    @Test\n    @SuppressWarnings(\"try\")\n    void anErrorOnStartupShouldNotLeaveAnUninitialisedMarkFile(final @TempDir File tempDir)\n    {\n        final File aeronDir = new File(tempDir, \"aeron\");\n        final File archiveDir = new File(tempDir, \"archive_dir\");\n        final File markFileDir = new File(tempDir, \"mark/file/dir\");\n        final File archiveMarkFile = new File(markFileDir, ArchiveMarkFile.FILENAME);\n\n        final Aeron aeron = mock(Aeron.class);\n        final Aeron.Context aeronCtx = mock(Aeron.Context.class);\n        when(aeronCtx.aeronDirectoryName()).thenReturn(\"/dev/shm/aeron\");\n        when(aeronCtx.filePageSize()).thenReturn(PAGE_MIN_SIZE);\n        when(aeronCtx.useConductorAgentInvoker()).thenReturn(false);\n        when(aeron.context()).thenReturn(aeronCtx);\n\n        final MediaDriver.Context driverContext = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.getAbsolutePath())\n            .threadingMode(ThreadingMode.SHARED);\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .aeronDirectoryName(driverContext.aeronDirectoryName())\n            .archiveDir(archiveDir)\n            .markFileDir(markFileDir)\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .epochClock(SystemEpochClock.INSTANCE)\n            .aeron(aeron);\n\n        // Force an error on startup by attempting to use invalid Aeron client\n        try (Archive ignored = Archive.launch(archiveContext.clone()))\n        {\n            fail(\"Expected archive to timeout as no media driver is running.\");\n        }\n        catch (final ArchiveException ex)\n        {\n            // Should be able to read the mark file and the activity timestamp should not have been set.\n            try (ArchiveMarkFile testMarkFile = new ArchiveMarkFile(\n                markFileDir,\n                ArchiveMarkFile.FILENAME,\n                SystemEpochClock.INSTANCE,\n                10_000,\n                null))\n            {\n                assertEquals(ArchiveMarkFile.SEMANTIC_VERSION, testMarkFile.decoder().version());\n                assertEquals(NULL_VALUE, testMarkFile.decoder().activityTimestamp());\n            }\n        }\n\n        // Should be able to successfully start the archive\n        try (TestMediaDriver ignored = TestMediaDriver.launch(driverContext.clone(), systemTestWatcher);\n            Archive archive = Archive.launch(archiveContext.clone().aeron(null)))\n        {\n            assertTrue(archiveMarkFile.exists());\n            final long activityTimestamp = archive.context().archiveMarkFile().activityTimestampVolatile();\n            assertThat(activityTimestamp, greaterThan(0L));\n        }\n    }\n\n    @Test\n    @DisabledForJreRange(min = JRE.JAVA_21)\n    void shouldCallForceIfMarkFileIsNotClosed()\n    {\n        final MarkFile markFile = mock(MarkFile.class);\n        final MappedByteBuffer mappedByteBuffer = mock(MappedByteBuffer.class);\n        when(markFile.mappedByteBuffer()).thenReturn(mappedByteBuffer);\n        when(markFile.buffer()).thenReturn(new UnsafeBuffer(new byte[128]));\n        try (ArchiveMarkFile clusterMarkFile = new ArchiveMarkFile(markFile))\n        {\n            clusterMarkFile.force();\n\n            final InOrder inOrder = inOrder(markFile, mappedByteBuffer);\n            inOrder.verify(markFile).isClosed();\n            inOrder.verify(markFile).mappedByteBuffer();\n            inOrder.verify(mappedByteBuffer).force();\n            inOrder.verifyNoMoreInteractions();\n        }\n    }\n\n    @Test\n    @DisabledForJreRange(min = JRE.JAVA_21)\n    void shouldNotCallForceIfMarkFileIsClosed()\n    {\n        final MarkFile markFile = mock(MarkFile.class);\n        final MappedByteBuffer mappedByteBuffer = mock(MappedByteBuffer.class);\n        when(markFile.mappedByteBuffer()).thenReturn(mappedByteBuffer);\n        when(markFile.buffer()).thenReturn(new UnsafeBuffer(new byte[128]));\n        when(markFile.isClosed()).thenReturn(true);\n        try (ArchiveMarkFile clusterMarkFile = new ArchiveMarkFile(markFile))\n        {\n            clusterMarkFile.force();\n\n            final InOrder inOrder = inOrder(markFile, mappedByteBuffer);\n            inOrder.verify(markFile).isClosed();\n            inOrder.verifyNoMoreInteractions();\n        }\n    }\n\n    @Test\n    void shouldBeAbleToReadOldMarkFileV0(final @TempDir File tempDir) throws IOException\n    {\n        final int version = SemanticVersion.compose(ArchiveMarkFile.MAJOR_VERSION, 28, 16);\n        final long activityTimestamp = 234783974944L;\n        final int pid = -55555;\n        final int controlStreamId = 1010;\n        final int localControlStreamId = -9;\n        final int eventsStreamId = 111;\n        final String controlChannel = \"aeron:udp?endpoint=localhost:8010\";\n        final String localControlChannel = \"aeron:ipc?alias=local-control|term-length=64k\";\n        final String eventsChannels = \"aeron:ipc?alias=events\";\n        final String aeronDirectory = tempDir.toPath().resolve(\"aeron/dir/path\").toAbsolutePath().toString();\n\n        final Path file = Files.write(\n            tempDir.toPath().resolve(ArchiveMarkFile.FILENAME), new byte[1024], StandardOpenOption.CREATE_NEW);\n\n        final MarkFile markFile = new MarkFile(\n            IoUtil.mapExistingFile(file.toFile(), ArchiveMarkFile.FILENAME),\n            io.aeron.archive.codecs.mark.v0.MarkFileHeaderDecoder.versionEncodingOffset(),\n            io.aeron.archive.codecs.mark.v0.MarkFileHeaderDecoder.activityTimestampEncodingOffset());\n\n        final io.aeron.archive.codecs.mark.v0.MarkFileHeaderEncoder encoder =\n            new io.aeron.archive.codecs.mark.v0.MarkFileHeaderEncoder();\n        encoder.wrap(markFile.buffer(), 0);\n\n        encoder\n            .version(version)\n            .activityTimestamp(activityTimestamp)\n            .pid(pid)\n            .controlStreamId(controlStreamId)\n            .localControlStreamId(localControlStreamId)\n            .eventsStreamId(eventsStreamId)\n            .controlChannel(controlChannel)\n            .localControlChannel(localControlChannel)\n            .eventsChannel(eventsChannels)\n            .aeronDirectory(aeronDirectory);\n\n        try (ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(markFile))\n        {\n            assertEquals(version, archiveMarkFile.decoder().version());\n            assertEquals(activityTimestamp, archiveMarkFile.decoder().activityTimestamp());\n            assertEquals(pid, archiveMarkFile.decoder().pid());\n            assertEquals(controlStreamId, archiveMarkFile.decoder().controlStreamId());\n            assertEquals(localControlStreamId, archiveMarkFile.decoder().localControlStreamId());\n            assertEquals(eventsStreamId, archiveMarkFile.decoder().eventsStreamId());\n            assertEquals(controlChannel, archiveMarkFile.decoder().controlChannel());\n            assertEquals(localControlChannel, archiveMarkFile.decoder().localControlChannel());\n            assertEquals(eventsChannels, archiveMarkFile.decoder().eventsChannel());\n            assertEquals(aeronDirectory, archiveMarkFile.decoder().aeronDirectory());\n\n            assertEquals(aeronDirectory, archiveMarkFile.aeronDirectory());\n            assertEquals(NULL_VALUE, archiveMarkFile.archiveId());\n        }\n\n        final Aeron aeron = mock(Aeron.class);\n        final Aeron.Context aeronCtx = mock(Aeron.Context.class);\n        when(aeronCtx.filePageSize()).thenReturn(PAGE_MIN_SIZE);\n        when(aeron.context()).thenReturn(aeronCtx);\n\n        final Archive.Context context = new Archive.Context()\n            .archiveDir(new File(tempDir, \"archive\"))\n            .markFileDir(file.getParent().toFile())\n            .aeron(aeron);\n        shouldEncodeMarkFileFromArchiveContext(context);\n    }\n\n    @Test\n    void shouldBeAbleToReadOldMarkFileV1(final @TempDir File tempDir) throws IOException\n    {\n        final int version = SemanticVersion.compose(ArchiveMarkFile.MAJOR_VERSION, 4, 4);\n        final long activityTimestamp = 234783974944L;\n        final int pid = 1;\n        final int controlStreamId = 42;\n        final int localControlStreamId = 19;\n        final int eventsStreamId = -87;\n        final int errorBufferLength = 8192;\n        final String controlChannel = \"aeron:udp?endpoint=localhost:8010\";\n        final String localControlChannel = \"aeron:ipc?alias=local-control|term-length=64k\";\n        final String eventsChannels = \"aeron:ipc?alias=events\";\n        final String aeronDirectory = tempDir.toPath().resolve(\"aeron/dir/path\").toAbsolutePath().toString();\n\n        final Path file = Files.write(\n            tempDir.toPath().resolve(ArchiveMarkFile.FILENAME), new byte[32 * 1024], StandardOpenOption.CREATE_NEW);\n\n        final MarkFile markFile = new MarkFile(\n            IoUtil.mapExistingFile(file.toFile(), ArchiveMarkFile.FILENAME),\n            io.aeron.archive.codecs.mark.v1.MarkFileHeaderDecoder.versionEncodingOffset(),\n            io.aeron.archive.codecs.mark.v1.MarkFileHeaderDecoder.activityTimestampEncodingOffset());\n\n        final io.aeron.archive.codecs.mark.v1.MarkFileHeaderEncoder encoder =\n            new io.aeron.archive.codecs.mark.v1.MarkFileHeaderEncoder();\n        encoder.wrap(markFile.buffer(), 0);\n\n        encoder\n            .version(version)\n            .activityTimestamp(activityTimestamp)\n            .pid(pid)\n            .controlStreamId(controlStreamId)\n            .localControlStreamId(localControlStreamId)\n            .eventsStreamId(eventsStreamId)\n            .headerLength(ArchiveMarkFile.HEADER_LENGTH)\n            .errorBufferLength(errorBufferLength)\n            .controlChannel(controlChannel)\n            .localControlChannel(localControlChannel)\n            .eventsChannel(eventsChannels)\n            .aeronDirectory(aeronDirectory);\n\n        try (ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(markFile))\n        {\n            assertEquals(version, archiveMarkFile.decoder().version());\n            assertEquals(activityTimestamp, archiveMarkFile.decoder().activityTimestamp());\n            assertEquals(pid, archiveMarkFile.decoder().pid());\n            assertEquals(controlStreamId, archiveMarkFile.decoder().controlStreamId());\n            assertEquals(localControlStreamId, archiveMarkFile.decoder().localControlStreamId());\n            assertEquals(eventsStreamId, archiveMarkFile.decoder().eventsStreamId());\n            assertEquals(ArchiveMarkFile.HEADER_LENGTH, archiveMarkFile.decoder().headerLength());\n            assertEquals(errorBufferLength, archiveMarkFile.decoder().errorBufferLength());\n            assertEquals(controlChannel, archiveMarkFile.decoder().controlChannel());\n            assertEquals(localControlChannel, archiveMarkFile.decoder().localControlChannel());\n            assertEquals(eventsChannels, archiveMarkFile.decoder().eventsChannel());\n            assertEquals(aeronDirectory, archiveMarkFile.decoder().aeronDirectory());\n\n            assertEquals(aeronDirectory, archiveMarkFile.aeronDirectory());\n            assertEquals(NULL_VALUE, archiveMarkFile.archiveId());\n        }\n\n        final Aeron aeron = mock(Aeron.class);\n        final Aeron.Context aeronCtx = mock(Aeron.Context.class);\n        when(aeronCtx.filePageSize()).thenReturn(PAGE_MIN_SIZE);\n        when(aeron.context()).thenReturn(aeronCtx);\n\n        final Archive.Context context = new Archive.Context()\n            .archiveDir(new File(tempDir, \"archive/a/long/path/to\"))\n            .markFileDir(file.getParent().toFile())\n            .aeron(aeron)\n            .errorBufferLength(errorBufferLength);\n        shouldEncodeMarkFileFromArchiveContext(context);\n    }\n\n    @Test\n    void shouldUnmapMarkFileBufferUponClose(final @TempDir Path tempDir)\n    {\n        final ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(\n            tempDir.resolve(\"archive.mark\").toFile(),\n            ArchiveMarkFile.HEADER_LENGTH + ERROR_BUFFER_LENGTH,\n            ERROR_BUFFER_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100);\n        assertFalse(archiveMarkFile.isClosed());\n\n        final MarkFileHeaderEncoder encoder = archiveMarkFile.encoder();\n        final MarkFileHeaderDecoder decoder = archiveMarkFile.decoder();\n        final AtomicBuffer errorBuffer = archiveMarkFile.errorBuffer();\n\n        assertSame(errorBuffer.byteBuffer(), encoder.buffer().byteBuffer());\n        assertSame(errorBuffer.byteBuffer(), decoder.buffer().byteBuffer());\n\n        archiveMarkFile.close();\n\n        assertTrue(archiveMarkFile.isClosed());\n        assertNull(errorBuffer.byteBuffer());\n        assertEquals(0, errorBuffer.capacity());\n        assertNull(encoder.buffer().byteBuffer());\n        assertEquals(0, encoder.buffer().capacity());\n        assertNull(decoder.buffer().byteBuffer());\n        assertEquals(0, decoder.buffer().capacity());\n\n        archiveMarkFile.close();\n        assertTrue(archiveMarkFile.isClosed());\n    }\n\n    @Test\n    void activityTimestamp(final @TempDir Path tempDir)\n    {\n        final ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(\n            tempDir.resolve(\"archive.mark\").toFile(),\n            ArchiveMarkFile.HEADER_LENGTH + ERROR_BUFFER_LENGTH,\n            ERROR_BUFFER_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100);\n\n        assertEquals(0, archiveMarkFile.activityTimestampVolatile());\n        assertEquals(0, archiveMarkFile.decoder().activityTimestamp());\n\n        final long activityTimestamp = 7383439454305L;\n        archiveMarkFile.updateActivityTimestamp(activityTimestamp);\n        assertEquals(activityTimestamp, archiveMarkFile.activityTimestampVolatile());\n        assertEquals(activityTimestamp, archiveMarkFile.decoder().activityTimestamp());\n\n        archiveMarkFile.close();\n\n        archiveMarkFile.updateActivityTimestamp(1111);\n        assertEquals(NULL_VALUE, archiveMarkFile.activityTimestampVolatile());\n    }\n\n    @Test\n    void archiveId(final @TempDir Path tempDir)\n    {\n        final ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(\n            tempDir.resolve(\"archive.mark\").toFile(),\n            ArchiveMarkFile.HEADER_LENGTH + ERROR_BUFFER_LENGTH,\n            ERROR_BUFFER_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100);\n\n        assertEquals(0, archiveMarkFile.archiveId());\n        assertEquals(0, archiveMarkFile.decoder().archiveId());\n\n        final long archiveId = -462348234343L;\n        archiveMarkFile.encoder().archiveId(archiveId);\n        assertEquals(archiveId, archiveMarkFile.archiveId());\n        assertEquals(archiveId, archiveMarkFile.decoder().archiveId());\n\n        archiveMarkFile.close();\n\n        assertEquals(NULL_VALUE, archiveMarkFile.archiveId());\n    }\n\n    @Test\n    void signalReady(final @TempDir Path tempDir)\n    {\n        final ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(\n            tempDir.resolve(\"archive.mark\").toFile(),\n            ArchiveMarkFile.HEADER_LENGTH + ERROR_BUFFER_LENGTH,\n            ERROR_BUFFER_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100);\n\n        assertEquals(0, archiveMarkFile.decoder().version());\n\n        archiveMarkFile.signalReady(5555);\n\n        assertEquals(ArchiveMarkFile.SEMANTIC_VERSION, archiveMarkFile.decoder().version());\n        assertEquals(5555, archiveMarkFile.decoder().activityTimestamp());\n\n        archiveMarkFile.close();\n\n        archiveMarkFile.signalReady(7777);\n        assertTrue(archiveMarkFile.isClosed());\n    }\n\n    @Test\n    void shouldAlignMarkFileLengthToFilePageSizeFromAeronClient(final @TempDir Path tempDir) throws IOException\n    {\n        final Path aeronDir = tempDir.resolve(\"aeron\");\n        final Path archiveDir = tempDir.resolve(\"archive\");\n        final Path markFileDir = tempDir.resolve(\"mark\");\n        Files.createDirectories(aeronDir);\n        Files.createDirectories(archiveDir);\n        Files.createDirectories(markFileDir);\n\n        final Archive.Context context = new Archive.Context();\n        final Aeron aeron = mock(Aeron.class);\n        final Aeron.Context aeronContext = mock(Aeron.Context.class);\n        final int filePageSize = 16 * 1024;\n        when(aeronContext.filePageSize()).thenReturn(filePageSize);\n        when(aeronContext.aeronDirectory()).thenReturn(aeronDir.toFile());\n        when(aeronContext.aeronDirectoryName()).thenReturn(aeronDir.toString());\n        when(aeron.context()).thenReturn(aeronContext);\n        context\n            .aeron(aeron)\n            .archiveDir(archiveDir.toFile())\n            .markFileDir(markFileDir.toFile())\n            .epochClock(SystemEpochClock.INSTANCE);\n\n        try (ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(context))\n        {\n            assertEquals(\n                BitUtil.align(ArchiveMarkFile.HEADER_LENGTH + context.errorBufferLength(), filePageSize),\n                archiveMarkFile.buffer().capacity());\n        }\n\n        verify(aeronContext).filePageSize();\n    }\n\n    @Test\n    void shouldAlignMarkFileLengthTo4KIfAeronClientReturnsZero(final @TempDir Path tempDir) throws IOException\n    {\n        final Path aeronDir = tempDir.resolve(\"aeron\");\n        final Path archiveDir = tempDir.resolve(\"archive\");\n        final Path markFileDir = tempDir.resolve(\"mark\");\n        Files.createDirectories(aeronDir);\n        Files.createDirectories(archiveDir);\n        Files.createDirectories(markFileDir);\n\n        final Archive.Context context = new Archive.Context();\n        final Aeron aeron = mock(Aeron.class);\n        final Aeron.Context aeronContext = mock(Aeron.Context.class);\n        when(aeronContext.filePageSize()).thenReturn(PAGE_MIN_SIZE);\n        when(aeronContext.aeronDirectory()).thenReturn(aeronDir.toFile());\n        when(aeronContext.aeronDirectoryName()).thenReturn(aeronDir.toString());\n        when(aeron.context()).thenReturn(aeronContext);\n        context\n            .aeron(aeron)\n            .archiveDir(archiveDir.toFile())\n            .markFileDir(markFileDir.toFile())\n            .epochClock(SystemEpochClock.INSTANCE)\n            .errorBufferLength(1111111);\n\n        try (ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(context))\n        {\n            assertEquals(\n                BitUtil.align(ArchiveMarkFile.HEADER_LENGTH + context.errorBufferLength(), 4096),\n                archiveMarkFile.buffer().capacity());\n        }\n\n        verify(aeronContext).filePageSize();\n    }\n\n\n    @Test\n    @SuppressWarnings(\"try\")\n    void shouldAlignMarkFileLengthToFilePageSizeFromMediaDriver(final @TempDir Path tempDir) throws IOException\n    {\n        final Path archiveDir = tempDir.resolve(\"archive\");\n        Files.createDirectories(archiveDir);\n\n        final int filePageSize = 2 * 1024 * 1024;\n        final MediaDriver.Context mediaDriverContext = new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .filePageSize(filePageSize);\n\n        final Archive.Context context = new Archive.Context();\n        context\n            .aeronDirectoryName(mediaDriverContext.aeronDirectoryName())\n            .markFileDir(new File(mediaDriverContext.aeronDirectoryName()))\n            .archiveDir(archiveDir.toFile())\n            .epochClock(SystemEpochClock.INSTANCE)\n            .errorBufferLength(1234567);\n\n        try (TestMediaDriver ignored = TestMediaDriver.launch(mediaDriverContext, systemTestWatcher);\n            ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(context))\n        {\n            assertEquals(filePageSize, archiveMarkFile.buffer().capacity());\n        }\n    }\n\n    private static void shouldEncodeMarkFileFromArchiveContext(final Archive.Context ctx)\n    {\n        final CachedEpochClock epochClock = new CachedEpochClock();\n        epochClock.advance(12345678909876L);\n        ctx\n            .controlStreamId(42)\n            .localControlStreamId(-118)\n            .recordingEventsStreamId(85858585)\n            .archiveId(46238467823468L)\n            .controlChannel(\"aeron:udp?endpoint=localhost:55555|alias=control\")\n            .localControlChannel(AeronArchive.Configuration.localControlChannel())\n            .recordingEventsChannel(\"aeron:udp?endpoint=localhost:0\")\n            .epochClock(epochClock);\n\n        try (ArchiveMarkFile archiveMarkFile = new ArchiveMarkFile(ctx))\n        {\n            final long time = epochClock.time();\n            archiveMarkFile.signalReady(time);\n\n            final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n            messageHeaderDecoder.wrap(archiveMarkFile.buffer(), 0);\n            assertEquals(MarkFileHeaderDecoder.BLOCK_LENGTH, messageHeaderDecoder.blockLength());\n            assertEquals(MarkFileHeaderDecoder.SCHEMA_ID, messageHeaderDecoder.schemaId());\n            assertEquals(MarkFileHeaderDecoder.TEMPLATE_ID, messageHeaderDecoder.templateId());\n            assertEquals(MarkFileHeaderDecoder.SCHEMA_VERSION, messageHeaderDecoder.version());\n\n            assertEquals(ArchiveMarkFile.SEMANTIC_VERSION, archiveMarkFile.decoder().version());\n            assertEquals(time, archiveMarkFile.decoder().startTimestamp());\n            assertEquals(SystemUtil.getPid(), archiveMarkFile.decoder().pid());\n            assertEquals(ctx.controlStreamId(), archiveMarkFile.decoder().controlStreamId());\n            assertEquals(ctx.localControlStreamId(), archiveMarkFile.decoder().localControlStreamId());\n            assertEquals(ctx.recordingEventsStreamId(), archiveMarkFile.decoder().eventsStreamId());\n            assertEquals(ArchiveMarkFile.HEADER_LENGTH, archiveMarkFile.decoder().headerLength());\n            assertEquals(ctx.errorBufferLength(), archiveMarkFile.decoder().errorBufferLength());\n            assertEquals(ctx.archiveId(), archiveMarkFile.decoder().archiveId());\n            assertEquals(ctx.controlChannel(), archiveMarkFile.decoder().controlChannel());\n            assertEquals(ctx.localControlChannel(), archiveMarkFile.decoder().localControlChannel());\n            assertEquals(ctx.recordingEventsChannel(), archiveMarkFile.decoder().eventsChannel());\n            assertEquals(ctx.aeronDirectoryName(), archiveMarkFile.decoder().aeronDirectory());\n\n            assertInstanceOf(MarkFileHeaderDecoder.class, archiveMarkFile.decoder());\n            assertInstanceOf(MarkFileHeaderEncoder.class, archiveMarkFile.encoder());\n            assertEquals(ctx.archiveId(), archiveMarkFile.archiveId());\n\n            assertEquals(ctx.aeronDirectoryName(), archiveMarkFile.aeronDirectory());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveMigrationUtils.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.client.ArchiveException;\nimport org.agrona.BufferUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.SemanticVersion;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.archive.Catalog.MIN_CAPACITY;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.nio.channels.FileChannel.MapMode.READ_WRITE;\nimport static java.nio.file.StandardOpenOption.*;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\nfinal class ArchiveMigrationUtils\n{\n    static final int VERSION_2_1_0 = SemanticVersion.compose(2, 1, 0);\n\n    private static final int MARK_FILE_HEADER_LENGTH_V2 = 8 * 1024;\n    static final int RECORDING_FRAME_LENGTH_V2 = 1024;\n\n    private ArchiveMigrationUtils()\n    {\n    }\n\n    static ArchiveMarkFile createArchiveMarkFileInVersion2(final File archiveDir, final EpochClock epochClock)\n    {\n        final Path markFile = archiveDir.toPath().resolve(ArchiveMarkFile.FILENAME);\n        try (FileChannel channel = FileChannel.open(markFile, CREATE_NEW, READ, WRITE, SPARSE))\n        {\n            final int errorBufferLength = 100;\n            final String controlChannel = \"ctx.controlChannel()\";\n            final String localControlChannel = \"ctx.localControlChannel()\";\n            final String eventsChannel = \"ctx.recordingEventsChannel()\";\n            final String aeronDirectory = \"ctx.aeronDirectoryName()\";\n\n            final MappedByteBuffer mappedByteBuffer = channel.map(\n                READ_WRITE,\n                0,\n                totalMarkFileLengthInVersion2(\n                controlChannel, localControlChannel, eventsChannel, aeronDirectory, errorBufferLength));\n            mappedByteBuffer.order(LITTLE_ENDIAN);\n            try\n            {\n                final UnsafeBuffer buffer = new UnsafeBuffer(mappedByteBuffer);\n                final long currentTime = epochClock.time();\n\n                int index = 0;\n                buffer.putInt(index, VERSION_2_1_0); // version\n                index += SIZE_OF_INT;\n\n                buffer.putLong(index, currentTime, LITTLE_ENDIAN); // activityTimestamp\n                index += SIZE_OF_LONG;\n\n                buffer.putLong(index, currentTime, LITTLE_ENDIAN); // startTimestamp\n                index += SIZE_OF_LONG;\n\n                buffer.putLong(index, 1, LITTLE_ENDIAN); // pid\n                index += SIZE_OF_LONG;\n\n                buffer.putInt(index, 777, LITTLE_ENDIAN); // controlStreamId\n                index += SIZE_OF_INT;\n\n                buffer.putInt(index, 42, LITTLE_ENDIAN); // localControlStreamId\n                index += SIZE_OF_INT;\n\n                buffer.putInt(index, 13, LITTLE_ENDIAN); // eventsStreamId\n                index += SIZE_OF_INT;\n\n                buffer.putInt(index, MARK_FILE_HEADER_LENGTH_V2, LITTLE_ENDIAN);\n                index += SIZE_OF_INT;\n\n                buffer.putInt(index, errorBufferLength, LITTLE_ENDIAN);\n                index += SIZE_OF_INT;\n\n                index += buffer.putStringAscii(index, controlChannel, LITTLE_ENDIAN);\n\n                index += buffer.putStringAscii(index, localControlChannel, LITTLE_ENDIAN);\n\n                index += buffer.putStringAscii(index, eventsChannel, LITTLE_ENDIAN);\n\n                // aeronDirectory\n                buffer.putStringAscii(index, aeronDirectory, LITTLE_ENDIAN);\n            }\n            finally\n            {\n                BufferUtil.free(mappedByteBuffer);\n            }\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return new ArchiveMarkFile(\n            archiveDir,\n            ArchiveMarkFile.FILENAME,\n            epochClock,\n            TimeUnit.SECONDS.toMillis(5),\n            (version) -> {},\n            null);\n    }\n\n    static Catalog createCatalogInVersion2(\n        final File archiveDir, final EpochClock epochClock, final List<RecordingDescriptorV2> recordings)\n    {\n        final Path catalogFile = archiveDir.toPath().resolve(Archive.Configuration.CATALOG_FILE_NAME);\n        try (FileChannel channel = FileChannel.open(catalogFile, CREATE_NEW, READ, WRITE, SPARSE))\n        {\n            final MappedByteBuffer mappedByteBuffer = channel.map(\n                READ_WRITE,\n                0,\n                RECORDING_FRAME_LENGTH_V2 + (recordings.size() * (long)RECORDING_FRAME_LENGTH_V2));\n            mappedByteBuffer.order(LITTLE_ENDIAN);\n            try\n            {\n                final UnsafeBuffer buffer = new UnsafeBuffer(mappedByteBuffer);\n\n                int index = 0;\n                buffer.putInt(index, VERSION_2_1_0); // version\n                index += SIZE_OF_INT;\n\n                buffer.putInt(index, RECORDING_FRAME_LENGTH_V2); // entryLength\n\n                index = RECORDING_FRAME_LENGTH_V2;\n\n                for (final RecordingDescriptorV2 recording : recordings)\n                {\n                    writeRecording(buffer, index, recording);\n                    index += RECORDING_FRAME_LENGTH_V2;\n                }\n            }\n            finally\n            {\n                BufferUtil.free(mappedByteBuffer);\n            }\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return new Catalog(archiveDir, epochClock, MIN_CAPACITY, true, null, (version) -> {});\n    }\n\n    private static int totalMarkFileLengthInVersion2(\n        final String controlChannel,\n        final String localControlChannel,\n        final String eventsChannel,\n        final String aeronDirectory,\n        final int errorBufferLength)\n    {\n        final int headerContentLength =\n            128 + 4 * SIZE_OF_INT +\n            controlChannel.length() +\n            localControlChannel.length() +\n            eventsChannel.length() +\n            aeronDirectory.length();\n\n        if (headerContentLength > MARK_FILE_HEADER_LENGTH_V2)\n        {\n            throw new ArchiveException(\n                \"MarkFile length required \" + headerContentLength + \" greater than \" + MARK_FILE_HEADER_LENGTH_V2);\n        }\n\n        return MARK_FILE_HEADER_LENGTH_V2 + errorBufferLength;\n    }\n\n    private static void writeRecording(\n        final UnsafeBuffer buffer, final int index, final RecordingDescriptorV2 recording)\n    {\n        int offset = index + 32;\n\n        // RecordingDescriptor\n        buffer.putLong(offset, recording.controlSessionId, LITTLE_ENDIAN);\n        offset += SIZE_OF_LONG;\n\n        buffer.putLong(offset, recording.correlationId, LITTLE_ENDIAN);\n        offset += SIZE_OF_LONG;\n\n        buffer.putLong(offset, recording.recordingId, LITTLE_ENDIAN);\n        offset += SIZE_OF_LONG;\n\n        buffer.putLong(offset, recording.startTimestamp, LITTLE_ENDIAN);\n        offset += SIZE_OF_LONG;\n\n        buffer.putLong(offset, recording.stopTimestamp, LITTLE_ENDIAN);\n        offset += SIZE_OF_LONG;\n\n        buffer.putLong(offset, recording.startPosition, LITTLE_ENDIAN);\n        offset += SIZE_OF_LONG;\n\n        buffer.putLong(offset, recording.stopPosition, LITTLE_ENDIAN);\n        offset += SIZE_OF_LONG;\n\n        buffer.putInt(offset, recording.initialTermId, LITTLE_ENDIAN);\n        offset += SIZE_OF_INT;\n\n        buffer.putInt(offset, recording.segmentFileLength, LITTLE_ENDIAN);\n        offset += SIZE_OF_INT;\n\n        buffer.putInt(offset, recording.termBufferLength, LITTLE_ENDIAN);\n        offset += SIZE_OF_INT;\n\n        buffer.putInt(offset, recording.mtuLength, LITTLE_ENDIAN);\n        offset += SIZE_OF_INT;\n\n        buffer.putInt(offset, recording.sessionId, LITTLE_ENDIAN);\n        offset += SIZE_OF_INT;\n\n        buffer.putInt(offset, recording.streamId, LITTLE_ENDIAN);\n        offset += SIZE_OF_INT;\n\n        offset += buffer.putStringAscii(offset, recording.strippedChannel, LITTLE_ENDIAN);\n\n        offset += buffer.putStringAscii(offset, recording.originalChannel, LITTLE_ENDIAN);\n\n        offset += buffer.putStringAscii(offset, recording.sourceIdentity, LITTLE_ENDIAN);\n\n        final int totalLength = offset - index;\n        if (totalLength > RECORDING_FRAME_LENGTH_V2)\n        {\n            throw new IllegalArgumentException(\"recordingId=\" + recording.recordingId + \"encoded length is \" +\n                totalLength + \" bytes which exceeds max recording length of \" + RECORDING_FRAME_LENGTH_V2 + \" bytes\");\n        }\n\n        // RecordingDescriptorHeader\n        buffer.putInt(index, totalLength - 32, LITTLE_ENDIAN); // Length of the recording w/o header\n        buffer.putByte(index + SIZE_OF_INT, recording.valid);\n    }\n\n    static final class RecordingDescriptorV2\n    {\n        // RecordingDescriptorHeader\n        final byte valid;\n\n        // RecordingDescriptor\n        final long controlSessionId;\n        final long correlationId;\n        final long recordingId;\n        final long startTimestamp;\n        final long stopTimestamp;\n        final long startPosition;\n        final long stopPosition;\n        final int initialTermId;\n        final int segmentFileLength;\n        final int termBufferLength;\n        final int mtuLength;\n        final int sessionId;\n        final int streamId;\n        final String strippedChannel;\n        final String originalChannel;\n        final String sourceIdentity;\n\n        RecordingDescriptorV2(\n            final byte valid,\n            final long controlSessionId,\n            final long correlationId,\n            final long recordingId,\n            final long startTimestamp,\n            final long stopTimestamp,\n            final long startPosition,\n            final long stopPosition,\n            final int initialTermId,\n            final int segmentFileLength,\n            final int termBufferLength,\n            final int mtuLength,\n            final int sessionId,\n            final int streamId,\n            final String strippedChannel,\n            final String originalChannel,\n            final String sourceIdentity)\n        {\n            this.valid = valid;\n            this.controlSessionId = controlSessionId;\n            this.correlationId = correlationId;\n            this.recordingId = recordingId;\n            this.startTimestamp = startTimestamp;\n            this.stopTimestamp = stopTimestamp;\n            this.startPosition = startPosition;\n            this.stopPosition = stopPosition;\n            this.initialTermId = initialTermId;\n            this.segmentFileLength = segmentFileLength;\n            this.termBufferLength = termBufferLength;\n            this.mtuLength = mtuLength;\n            this.sessionId = sessionId;\n            this.streamId = streamId;\n            this.strippedChannel = strippedChannel;\n            this.originalChannel = originalChannel;\n            this.sourceIdentity = sourceIdentity;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveMigration_2_3Test.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport org.agrona.BufferUtil;\nimport org.agrona.IoUtil;\nimport org.agrona.SemanticVersion;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.*;\nimport org.junit.jupiter.api.*;\n\nimport java.io.*;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.FileAlreadyExistsException;\nimport java.nio.file.Path;\nimport java.util.List;\n\nimport static io.aeron.archive.ArchiveMigrationUtils.*;\nimport static io.aeron.archive.MigrationUtils.createMigrationTimestampFile;\nimport static io.aeron.archive.MigrationUtils.migrationTimestampFileName;\nimport static io.aeron.test.Tests.generateStringWithSuffix;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.nio.channels.FileChannel.MapMode.READ_ONLY;\nimport static java.nio.file.Files.exists;\nimport static java.nio.file.Files.size;\nimport static java.nio.file.StandardOpenOption.READ;\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.emptyList;\nimport static org.agrona.BitUtil.*;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass ArchiveMigration_2_3Test\n{\n    private static final byte INVALID = (byte)0;\n    private static final byte VALID = (byte)1;\n\n    private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();\n    private final PrintStream originalErr = System.err;\n    private final File archiveDir = ArchiveTests.makeTestDirectory();\n    private final CachedEpochClock clock = new CachedEpochClock();\n\n    private final ArchiveMigration_2_3 migration = new ArchiveMigration_2_3();\n\n    @BeforeEach\n    void before()\n    {\n        clock.update(1);\n        System.setErr(new PrintStream(errContent));\n    }\n\n    @AfterEach\n    void after()\n    {\n        System.setErr(originalErr);\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    void minimumVersion()\n    {\n        assertEquals(SemanticVersion.compose(3, 0, 0), migration.minimumVersion());\n    }\n\n    @Test\n    void shouldThrowExceptionIfMigrationTimestampFileAlreadyExists() throws IOException\n    {\n        try (ArchiveMarkFile markFile = createArchiveMarkFileInVersion2(archiveDir, clock);\n            Catalog catalog = createCatalogInVersion2(archiveDir, clock, emptyList());\n            FileChannel timestampFile = createMigrationTimestampFile(\n                archiveDir, markFile.decoder().version(), migration.minimumVersion()))\n        {\n            assertNotNull(timestampFile);\n\n            final FileAlreadyExistsException exception = assertThrows(\n                FileAlreadyExistsException.class, () -> migration.migrate(System.out, markFile, catalog, archiveDir));\n            assertThat(\n                exception.getMessage(),\n                containsString(migrationTimestampFileName(VERSION_2_1_0, migration.minimumVersion())));\n        }\n    }\n\n    @Test\n    void migrateEmptyCatalogFile() throws IOException\n    {\n        final Path catalogFile = archiveDir.toPath().resolve(Archive.Configuration.CATALOG_FILE_NAME);\n\n        try (ArchiveMarkFile markFile = createArchiveMarkFileInVersion2(archiveDir, clock);\n            Catalog catalog = createCatalogInVersion2(archiveDir, clock, emptyList()))\n        {\n            assertEquals(RECORDING_FRAME_LENGTH_V2, size(catalogFile));\n\n            migration.migrate(System.out, markFile, catalog, archiveDir);\n\n            assertEquals(migration.minimumVersion(), markFile.decoder().version());\n\n            assertEquals(32, size(catalogFile));\n            assertTrue(catalog.isClosed());\n\n            verifyCatalogHeader(catalogFile, 0);\n        }\n\n        final String migrationTimestampFileName =\n            migrationTimestampFileName(VERSION_2_1_0, migration.minimumVersion());\n        assertTrue(exists(archiveDir.toPath().resolve(migrationTimestampFileName)));\n    }\n\n    @Test\n    @SuppressWarnings({ \"methodLength\", \"indentation\" })\n    void migrateRemovesGapsBetweenRecordings() throws IOException\n    {\n        final Path catalogFile = archiveDir.toPath().resolve(Archive.Configuration.CATALOG_FILE_NAME);\n\n        final List<RecordingDescriptorV2> recordings = asList(\n            new RecordingDescriptorV2(\n            VALID,\n            42,\n            21,\n            0,\n            123,\n            456,\n            0,\n            1024,\n            3,\n            500,\n            1024,\n            1408,\n            8,\n            55,\n            \"strCh\",\n            \"aeron:udp?endpoint=localhost:9090\",\n            \"sourceA\"),\n            new RecordingDescriptorV2(\n            VALID,\n            41,\n            14,\n            4,\n            400,\n            4000,\n            44,\n            444,\n            4,\n            44,\n            44444,\n            4000,\n            44,\n            4040,\n            \"ch1024\",\n            \"aeron:udp?endpoint=localhost:44444\",\n            generateStringWithSuffix(\"source\", \"B\", 854)),\n            new RecordingDescriptorV2(\n            INVALID,\n            333,\n            333,\n            56,\n            100,\n            200,\n            999,\n            100999,\n            8,\n            2048,\n            4096,\n            9000,\n            -10,\n            1,\n            \"ch\",\n            \"channel\",\n            \"sourceC\")\n        );\n\n        try (ArchiveMarkFile markFile = createArchiveMarkFileInVersion2(archiveDir, clock);\n            Catalog catalog = createCatalogInVersion2(archiveDir, clock, recordings))\n        {\n            assertEquals(RECORDING_FRAME_LENGTH_V2 * 4, size(catalogFile));\n\n            migration.migrate(System.out, markFile, catalog, archiveDir);\n\n            assertEquals(migration.minimumVersion(), markFile.decoder().version());\n\n            assertEquals(1440, size(catalogFile));\n            assertTrue(catalog.isClosed());\n\n            verifyCatalogHeader(catalogFile, 57);\n        }\n\n        final String migrationTimestampFileName =\n            migrationTimestampFileName(VERSION_2_1_0, migration.minimumVersion());\n        assertTrue(exists(archiveDir.toPath().resolve(migrationTimestampFileName)));\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, 1024, clock, null, null))\n        {\n            final MutableInteger index = new MutableInteger();\n            catalog.forEach(\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                {\n                    final RecordingDescriptorV2 recording = recordings.get(index.getAndIncrement());\n\n                    assertNotEquals(RECORDING_FRAME_LENGTH_V2, headerDecoder.length());\n                    assertEquals(recording.valid, headerDecoder.state().value());\n\n                    assertEquals(recording.controlSessionId, descriptorDecoder.controlSessionId());\n                    assertEquals(recording.correlationId, descriptorDecoder.correlationId());\n                    assertEquals(recording.recordingId, descriptorDecoder.recordingId());\n                    assertEquals(recording.startTimestamp, descriptorDecoder.startTimestamp());\n                    assertEquals(recording.stopTimestamp, descriptorDecoder.stopTimestamp());\n                    assertEquals(recording.startPosition, descriptorDecoder.startPosition());\n                    assertEquals(recording.stopPosition, descriptorDecoder.stopPosition());\n                    assertEquals(recording.initialTermId, descriptorDecoder.initialTermId());\n                    assertEquals(recording.segmentFileLength, descriptorDecoder.segmentFileLength());\n                    assertEquals(recording.termBufferLength, descriptorDecoder.termBufferLength());\n                    assertEquals(recording.mtuLength, descriptorDecoder.mtuLength());\n                    assertEquals(recording.sessionId, descriptorDecoder.sessionId());\n                    assertEquals(recording.streamId, descriptorDecoder.streamId());\n                    assertEquals(recording.strippedChannel, descriptorDecoder.strippedChannel());\n                    assertEquals(recording.originalChannel, descriptorDecoder.originalChannel());\n                    assertEquals(recording.sourceIdentity, descriptorDecoder.sourceIdentity());\n                });\n\n            assertEquals(recordings.size(), index.get());\n        }\n    }\n\n    private void verifyCatalogHeader(final Path catalogFile, final long nextRecordingId) throws IOException\n    {\n        final int catalogHeaderLength = 32;\n        try (FileChannel channel = FileChannel.open(catalogFile, READ))\n        {\n            final MappedByteBuffer mappedByteBuffer = channel.map(READ_ONLY, 0, catalogHeaderLength);\n            mappedByteBuffer.order(LITTLE_ENDIAN);\n            try\n            {\n                final UnsafeBuffer buffer = new UnsafeBuffer(mappedByteBuffer);\n                int index = 0;\n\n                // version\n                assertEquals(migration.minimumVersion(), buffer.getInt(index, LITTLE_ENDIAN));\n                index += SIZE_OF_INT;\n\n                // length\n                assertEquals(catalogHeaderLength, buffer.getInt(index, LITTLE_ENDIAN));\n                index += SIZE_OF_INT;\n\n                // nextRecordingId\n                assertEquals(nextRecordingId, buffer.getLong(index, LITTLE_ENDIAN));\n                index += SIZE_OF_LONG;\n\n                // alignment\n                assertEquals(CACHE_LINE_LENGTH, buffer.getInt(index, LITTLE_ENDIAN));\n            }\n            finally\n            {\n                BufferUtil.free(mappedByteBuffer);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.Counter;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.Archive.Context;\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.AeronArchiveVersion;\nimport io.aeron.archive.client.ArchiveEvent;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.client.RecordingSubscriptionDescriptorConsumer;\nimport io.aeron.archive.codecs.TruncateRecordingRequestDecoder;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.PublisherPos;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.security.AuthorisationServiceSupplier;\nimport io.aeron.security.CredentialsSupplier;\nimport io.aeron.status.HeartbeatTimestamp;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.IoUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ManyToOneConcurrentLinkedQueue;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.ArgumentCaptor;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.FileStore;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.IntConsumer;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.CONTROL_MODE_RESPONSE;\nimport static io.aeron.CommonContext.IPC_MEDIA;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_PARAM_NAME;\nimport static io.aeron.CommonContext.SESSION_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.generateRandomDirName;\nimport static io.aeron.archive.ArchiveThreadingMode.DEDICATED;\nimport static io.aeron.archive.ArchiveThreadingMode.SHARED;\nimport static io.aeron.archive.Catalog.MIN_CAPACITY;\nimport static io.aeron.archive.client.AeronArchive.segmentFileBasePosition;\nimport static io.aeron.archive.codecs.SourceLocation.LOCAL;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.status.HeartbeatTimestamp.HEARTBEAT_TYPE_ID;\nimport static io.aeron.test.TestContexts.LOCALHOST_CONTROL_REQUEST_CHANNEL;\nimport static io.aeron.test.TestContexts.LOCALHOST_CONTROL_RESPONSE_CHANNEL;\nimport static io.aeron.test.TestContexts.LOCALHOST_REPLICATION_CHANNEL;\nimport static org.agrona.BitUtil.align;\nimport static org.agrona.concurrent.status.CountersReader.KEY_OFFSET;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_RECLAIMED;\nimport static org.agrona.concurrent.status.CountersReader.metaDataOffset;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.timeout;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(InterruptingTestCallback.class)\n@InterruptAfter(10)\n@SuppressWarnings(\"try\")\nclass ArchiveTest\n{\n    private static final FragmentHandler NO_OP_FRAGMENT_HANDLER = (buffer, offset, length, header) -> {};\n\n    @TempDir\n    Path tmpDir;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    void shouldGenerateRecordingName()\n    {\n        final long recordingId = 1L;\n        final long segmentPosition = 2 * 64 * 1024;\n        final String expected = \"1-\" + (2 * 64 * 1024) + \".rec\";\n\n        final String actual = Archive.segmentFileName(recordingId, segmentPosition);\n\n        assertEquals(expected, actual);\n    }\n\n    @Test\n    void shouldCalculateCorrectSegmentFilePosition()\n    {\n        final int termLength = LogBufferDescriptor.TERM_MIN_LENGTH;\n        final int segmentLength = termLength * 8;\n\n        long startPosition = 0;\n        long position = 0;\n        assertEquals(0L, segmentFileBasePosition(startPosition, position, termLength, segmentLength));\n\n        startPosition = 0;\n        position = termLength * 2;\n        assertEquals(0L, segmentFileBasePosition(startPosition, position, termLength, segmentLength));\n\n        startPosition = 0;\n        position = segmentLength;\n        assertEquals(segmentLength, segmentFileBasePosition(startPosition, position, termLength, segmentLength));\n\n        startPosition = termLength;\n        position = termLength;\n        assertEquals(startPosition, segmentFileBasePosition(startPosition, position, termLength, segmentLength));\n\n        startPosition = termLength * 4;\n        position = termLength * 4;\n        assertEquals(startPosition, segmentFileBasePosition(startPosition, position, termLength, segmentLength));\n\n        startPosition = termLength;\n        position = termLength + segmentLength;\n        assertEquals(\n            termLength + segmentLength, segmentFileBasePosition(startPosition, position, termLength, segmentLength));\n\n        startPosition = termLength;\n        position = termLength + segmentLength - FrameDescriptor.FRAME_ALIGNMENT;\n        assertEquals(termLength, segmentFileBasePosition(startPosition, position, termLength, segmentLength));\n\n        startPosition = termLength + FrameDescriptor.FRAME_ALIGNMENT;\n        position = termLength + segmentLength;\n        assertEquals(\n            termLength + segmentLength, segmentFileBasePosition(startPosition, position, termLength, segmentLength));\n    }\n\n    @Test\n    void shouldAllowMultipleConnectionsInParallel() throws InterruptedException\n    {\n        final int numberOfArchiveClients = 5;\n        final long connectTimeoutNs = TimeUnit.SECONDS.toNanos(10);\n        final CountDownLatch latch = new CountDownLatch(numberOfArchiveClients);\n        final ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(numberOfArchiveClients);\n        final ManyToOneConcurrentLinkedQueue<AeronArchive> archiveClientQueue = new ManyToOneConcurrentLinkedQueue<>();\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .clientLivenessTimeoutNs(connectTimeoutNs)\n            .dirDeleteOnStart(true)\n            .publicationUnblockTimeoutNs(connectTimeoutNs * 2)\n            .threadingMode(ThreadingMode.SHARED);\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .threadingMode(SHARED)\n            .connectTimeoutNs(connectTimeoutNs);\n        executor.prestartAllCoreThreads();\n\n        try (ArchivingMediaDriver driver = ArchivingMediaDriver.launch(driverCtx, archiveCtx))\n        {\n            for (int i = 0; i < numberOfArchiveClients; i++)\n            {\n                executor.execute(\n                    () ->\n                    {\n                        final AeronArchive.Context ctx = TestContexts.localhostAeronArchive()\n                            .messageTimeoutNs(connectTimeoutNs);\n                        final AeronArchive archive = AeronArchive.connect(ctx);\n                        archiveClientQueue.add(archive);\n                        latch.countDown();\n                    });\n            }\n\n            assertTrue(latch.await(driver.archive().context().connectTimeoutNs() * 2, TimeUnit.NANOSECONDS));\n\n            AeronArchive archiveClient;\n            while (null != (archiveClient = archiveClientQueue.poll()))\n            {\n                CloseHelper.quietClose(archiveClient);\n            }\n        }\n        finally\n        {\n            executor.shutdownNow();\n            archiveCtx.deleteDirectory();\n            driverCtx.deleteDirectory();\n        }\n    }\n\n    @Test\n    void shouldRecoverRecordingWithNonZeroStartPosition()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n        final Archive.Context archiveCtx = TestContexts.localhostArchive().threadingMode(SHARED);\n\n        long resultingPosition;\n        final int initialPosition = DataHeaderFlyweight.HEADER_LENGTH * 9;\n        final long recordingId;\n\n        try (ArchivingMediaDriver ignore = ArchivingMediaDriver.launch(driverCtx.clone(), archiveCtx.clone());\n            AeronArchive archive = AeronArchive.connect(TestContexts.localhostAeronArchive()))\n        {\n            final int termLength = 128 * 1024;\n            final int initialTermId = 29;\n\n            final String channel = new ChannelUriStringBuilder()\n                .media(IPC_MEDIA)\n                .initialPosition(initialPosition, initialTermId, termLength)\n                .build();\n\n            final Publication publication = archive.addRecordedExclusivePublication(channel, 1);\n            final DirectBuffer buffer = new UnsafeBuffer(\"Hello World\".getBytes(StandardCharsets.US_ASCII));\n\n            while ((resultingPosition = publication.offer(buffer)) <= 0)\n            {\n                Tests.yield();\n            }\n\n            final Aeron aeron = archive.context().aeron();\n\n            int counterId;\n            final int sessionId = publication.sessionId();\n            final CountersReader countersReader = aeron.countersReader();\n            while (NULL_VALUE == (counterId = RecordingPos.findCounterIdBySession(\n                countersReader, sessionId, archive.archiveId())))\n            {\n                Tests.yield();\n            }\n\n            recordingId = RecordingPos.getRecordingId(countersReader, counterId);\n\n            while (countersReader.getCounterValue(counterId) < resultingPosition)\n            {\n                Tests.yield();\n            }\n        }\n\n        try (Catalog catalog = openCatalog(archiveCtx.archiveDirectoryName()))\n        {\n            final Catalog.CatalogEntryProcessor catalogEntryProcessor =\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                descriptorEncoder.stopPosition(Aeron.NULL_VALUE);\n\n            assertTrue(catalog.forEntry(recordingId, catalogEntryProcessor));\n        }\n\n        final Archive.Context archiveCtxClone = archiveCtx.clone();\n        final MediaDriver.Context driverCtxClone = driverCtx.clone();\n        try (ArchivingMediaDriver ignore = ArchivingMediaDriver.launch(driverCtxClone, archiveCtxClone);\n            AeronArchive archive = AeronArchive.connect(TestContexts.localhostAeronArchive()))\n        {\n            assertEquals(initialPosition, archive.getStartPosition(recordingId));\n            assertEquals(resultingPosition, archive.getStopPosition(recordingId));\n        }\n        finally\n        {\n            archiveCtxClone.deleteDirectory();\n            driverCtxClone.deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldListRegisteredRecordingSubscriptions()\n    {\n        final int expectedStreamId = 7;\n        final String channelOne = \"aeron:ipc\";\n        final String channelTwo = \"aeron:udp?endpoint=localhost:5678\";\n        final String channelThree = \"aeron:udp?endpoint=localhost:4321\";\n\n        final ArrayList<SubscriptionDescriptor> descriptors = new ArrayList<>();\n        @SuppressWarnings(\"Indentation\") final RecordingSubscriptionDescriptorConsumer consumer =\n            (controlSessionId, correlationId, subscriptionId, streamId, strippedChannel) ->\n                descriptors.add(new SubscriptionDescriptor(\n                    controlSessionId, correlationId, subscriptionId, streamId, strippedChannel));\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n        final Archive.Context archiveCtx = TestContexts.localhostArchive().threadingMode(SHARED);\n        assertNull(archiveCtx.aeron());\n        assertFalse(archiveCtx.ownsAeronClient());\n\n        try (ArchivingMediaDriver archivingMediaDriver = ArchivingMediaDriver.launch(driverCtx, archiveCtx);\n            AeronArchive aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive()))\n        {\n            final Context archiveContext = archivingMediaDriver.archive().context();\n            assertNotNull(archiveContext.aeron());\n            assertTrue(archiveContext.ownsAeronClient());\n            assertTrue(archiveContext.aeron().context().useConductorAgentInvoker());\n\n            final AeronArchive.Context clientContext = aeronArchive.context();\n            assertNotNull(clientContext.aeron());\n            assertTrue(clientContext.ownsAeronClient());\n\n            final long subIdOne = aeronArchive.startRecording(channelOne, expectedStreamId, LOCAL);\n            final long subIdTwo = aeronArchive.startRecording(channelTwo, expectedStreamId + 1, LOCAL);\n            final long subOdThree = aeronArchive.startRecording(channelThree, expectedStreamId + 2, LOCAL);\n\n            final int countOne = aeronArchive.listRecordingSubscriptions(\n                0, 5, \"ipc\", expectedStreamId, true, consumer);\n\n            assertEquals(1, descriptors.size());\n            assertEquals(1, countOne);\n\n            descriptors.clear();\n            final int countTwo = aeronArchive.listRecordingSubscriptions(\n                0, 5, \"\", expectedStreamId, false, consumer);\n\n            assertEquals(3, descriptors.size());\n            assertEquals(3, countTwo);\n\n            aeronArchive.stopRecording(subIdTwo);\n\n            descriptors.clear();\n            final int countThree = aeronArchive.listRecordingSubscriptions(\n                0, 5, \"\", expectedStreamId, false, consumer);\n\n            assertEquals(2, descriptors.size());\n            assertEquals(2, countThree);\n\n            assertEquals(1L, descriptors.stream().filter((sd) -> sd.subscriptionId == subIdOne).count());\n            assertEquals(1L, descriptors.stream().filter((sd) -> sd.subscriptionId == subOdThree).count());\n        }\n        finally\n        {\n            archiveCtx.deleteDirectory();\n            driverCtx.deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldErrorOnLowSpace() throws IOException\n    {\n        final int streamId = 7;\n        final String channel = \"aeron:ipc\";\n        final long usableSpace = 100;\n        final long threshold = 101;\n        final FileStore fileStore = mock(FileStore.class);\n        when(fileStore.getUsableSpace()).thenReturn(usableSpace);\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .archiveFileStore(fileStore)\n            .lowStorageSpaceThreshold(threshold)\n            .deleteArchiveOnStart(true)\n            .threadingMode(SHARED);\n\n        try (ArchivingMediaDriver ignore = ArchivingMediaDriver.launch(driverCtx, archiveCtx);\n            AeronArchive archive = AeronArchive.connect(TestContexts.localhostAeronArchive()))\n        {\n            try\n            {\n                archive.startRecording(channel, streamId, LOCAL);\n            }\n            catch (final ArchiveException ex)\n            {\n                assertEquals(ArchiveException.STORAGE_SPACE, ex.errorCode());\n                return;\n            }\n\n            fail(\"Expected exception\");\n        }\n        finally\n        {\n            archiveCtx.deleteDirectory();\n            driverCtx.deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldErrorWhenUnauthorised()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        final AuthorisationServiceSupplier authorisationServiceSupplier = () ->\n            (protocolId, actionId, type, encodedPrincipal) -> actionId != TruncateRecordingRequestDecoder.TEMPLATE_ID;\n\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .deleteArchiveOnStart(true)\n            .authorisationServiceSupplier(authorisationServiceSupplier)\n            .threadingMode(SHARED);\n\n        try (ArchivingMediaDriver ignore = ArchivingMediaDriver.launch(driverCtx, archiveCtx);\n            AeronArchive archive = AeronArchive.connect(TestContexts.localhostAeronArchive()))\n        {\n            try\n            {\n                archive.truncateRecording(0, 0);\n            }\n            catch (final ArchiveException ex)\n            {\n                assertEquals(ArchiveException.UNAUTHORISED_ACTION, ex.errorCode());\n                return;\n            }\n\n            fail(\"Expected exception\");\n        }\n        finally\n        {\n            archiveCtx.deleteDirectory();\n            driverCtx.deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldResolveArchiveId(@TempDir final Path dir)\n    {\n        final Path aeronDir = dir.resolve(\"driver\");\n        final Path archive1Dir = dir.resolve(\"archive1\");\n        final Path archive2Dir = dir.resolve(\"archive2\");\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .aeronDirectoryName(aeronDir.toString());\n\n        try (TestMediaDriver driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n            Archive archive1 = Archive.launch(\n                new Archive.Context()\n                .controlChannel(LOCALHOST_CONTROL_REQUEST_CHANNEL)\n                .replicationChannel(LOCALHOST_REPLICATION_CHANNEL)\n                .deleteArchiveOnStart(true)\n                .threadingMode(SHARED)\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .archiveDir(archive1Dir.toFile())\n                .archiveId(42));\n            Archive archive2 = Archive.launch(\n                new Archive.Context()\n                .controlChannel(\"aeron:udp?endpoint=localhost:8011\")\n                .replicationChannel(LOCALHOST_REPLICATION_CHANNEL)\n                .deleteArchiveOnStart(true)\n                .threadingMode(SHARED)\n                .aeronDirectoryName(aeronDir.toString())\n                .archiveDir(archive2Dir.toFile()));\n            AeronArchive client1 = AeronArchive.connect(new AeronArchive.Context()\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .controlRequestChannel(archive1.context().controlChannel())\n                .controlResponseChannel(LOCALHOST_CONTROL_RESPONSE_CHANNEL));\n            AeronArchive client2 = AeronArchive.connect(new AeronArchive.Context()\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .controlRequestChannel(archive2.context().controlChannel())\n                .controlResponseChannel(LOCALHOST_CONTROL_RESPONSE_CHANNEL)))\n        {\n            assertEquals(42, client1.archiveId());\n            assertEquals(archive2.context().archiveId(), client2.archiveId());\n        }\n    }\n\n    @Test\n    void dataBufferIsAllocatedOnDemand()\n    {\n        final Context context = new Context();\n\n        final UnsafeBuffer buffer = context.dataBuffer();\n\n        assertNotNull(buffer);\n        assertEquals(context.fileIoMaxLength(), buffer.capacity());\n        assertSame(buffer, context.dataBuffer());\n    }\n\n    @Test\n    void dataBufferReturnsValueAssigned()\n    {\n        final UnsafeBuffer buffer = mock(UnsafeBuffer.class);\n        final Context context = new Context();\n        context.dataBuffer(buffer);\n\n        assertSame(buffer, context.dataBuffer());\n    }\n\n    @Test\n    void replayBufferIsAllocatedOnDemandIfThreadingModeIsDEDICATED()\n    {\n        final Context context = new Context().threadingMode(DEDICATED);\n\n        final UnsafeBuffer buffer = context.replayBuffer();\n\n        assertNotNull(buffer);\n        assertEquals(context.fileIoMaxLength(), buffer.capacity());\n        assertSame(buffer, context.replayBuffer());\n        assertNotSame(context.dataBuffer(), buffer);\n    }\n\n    @Test\n    void replayBufferReturnsValueAssignedIfThreadingModeIsDEDICATED()\n    {\n        final UnsafeBuffer buffer = mock(UnsafeBuffer.class);\n        final Archive.Context context = new Archive.Context().threadingMode(DEDICATED);\n        context.replayBuffer(buffer);\n\n        assertSame(buffer, context.replayBuffer());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = ArchiveThreadingMode.class, mode = EXCLUDE, names = \"DEDICATED\")\n    void replayBufferReturnsDataBufferIfThreadingModeIsNotDEDICATED(final ArchiveThreadingMode threadingMode)\n    {\n        final Archive.Context context = new Archive.Context().threadingMode(threadingMode);\n\n        final UnsafeBuffer buffer = context.replayBuffer();\n\n        assertSame(context.dataBuffer(), buffer);\n    }\n\n    @Test\n    void recordChecksumBufferReturnsNullIfRecordChecksumIsNull()\n    {\n        final Archive.Context context = new Archive.Context();\n        assertNull(context.recordChecksumBuffer());\n    }\n\n    @Test\n    void recordChecksumBufferIsAllocatedOnDemandIfThreadingModeIsDEDICATED()\n    {\n        final Checksum recordChecksum = mock(Checksum.class);\n        final Archive.Context context = new Archive.Context().recordChecksum(recordChecksum).threadingMode(DEDICATED);\n\n        final UnsafeBuffer buffer = context.recordChecksumBuffer();\n\n        assertNotNull(buffer);\n        assertEquals(context.fileIoMaxLength(), buffer.capacity());\n        assertSame(buffer, context.recordChecksumBuffer());\n        assertNotSame(context.dataBuffer(), buffer);\n    }\n\n    @Test\n    void recordChecksumBufferReturnsValueAssignedIfThreadingModeIsDEDICATED()\n    {\n        final UnsafeBuffer buffer = mock(UnsafeBuffer.class);\n        final Checksum recordChecksum = mock(Checksum.class);\n        final Archive.Context context = new Archive.Context().recordChecksum(recordChecksum).threadingMode(DEDICATED);\n        context.recordChecksumBuffer(buffer);\n\n        assertSame(buffer, context.recordChecksumBuffer());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = ArchiveThreadingMode.class, mode = EXCLUDE, names = \"DEDICATED\")\n    void recordChecksumBufferReturnsDataBufferIfThreadingModeIsNotDEDICATED(final ArchiveThreadingMode threadingMode)\n    {\n        final Checksum recordChecksum = mock(Checksum.class);\n        final Archive.Context context = new Archive.Context()\n            .recordChecksum(recordChecksum)\n            .threadingMode(threadingMode);\n\n        final UnsafeBuffer buffer = context.recordChecksumBuffer();\n\n        assertSame(context.dataBuffer(), buffer);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"localhost:0\", \"localhost:8888\" })\n    void shouldResolveControlResponseEndpointAddress(final String endpoint)\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n        final Archive.Context archiveCtx = TestContexts.localhostArchive().threadingMode(SHARED);\n        final String controlResponseChannel = \"aeron:udp?endpoint=\" + endpoint;\n        final AeronArchive.Context clientContext = TestContexts.localhostAeronArchive()\n            .controlResponseChannel(controlResponseChannel);\n\n        try (ArchivingMediaDriver ignore = ArchivingMediaDriver.launch(driverCtx, archiveCtx);\n            AeronArchive archive = AeronArchive.connect(clientContext))\n        {\n            final int count = archive.listRecordings(0, 10,\n                (controlSessionId,\n                correlationId,\n                recordingId,\n                startTimestamp,\n                stopTimestamp,\n                startPosition,\n                stopPosition,\n                initialTermId,\n                segmentFileLength,\n                termBufferLength,\n                mtuLength,\n                sessionId,\n                streamId,\n                strippedChannel,\n                originalChannel,\n                sourceIdentity) -> {});\n\n            assertEquals(0, count);\n        }\n        finally\n        {\n            archiveCtx.deleteDirectory();\n            driverCtx.deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRunWithControlChannelDisabled()\n    {\n        final int streamId = 7;\n        final String channel = \"aeron:ipc\";\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .controlChannelEnabled(false)\n            .controlChannel(null)\n            .archiveClientContext(new AeronArchive.Context().controlResponseChannel(\"aeron:udp?endpoint=localhost:0\"))\n            .deleteArchiveOnStart(true)\n            .threadingMode(SHARED);\n\n        try (ArchivingMediaDriver ignore = ArchivingMediaDriver.launch(driverCtx, archiveCtx);\n            AeronArchive archive = AeronArchive.connect(new AeronArchive.Context()\n                .controlRequestChannel(archiveCtx.localControlChannel())\n                .controlResponseChannel(archiveCtx.localControlChannel()));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverCtx.aeronDirectoryName())))\n        {\n            final long subscriptionId = archive.startRecording(channel, streamId, LOCAL);\n            try (Publication publication = aeron.addExclusivePublication(channel, streamId))\n            {\n                final int sessionId = publication.sessionId();\n\n                final CountersReader counters = aeron.countersReader();\n                final int counterId = Tests.awaitRecordingCounterId(counters, sessionId, archive.archiveId());\n\n                final BufferClaim bufferClaim = new BufferClaim();\n                for (int i = 0; i < 111; i++)\n                {\n                    while (publication.tryClaim(4, bufferClaim) < 0)\n                    {\n                        Tests.yield();\n                    }\n                    bufferClaim.buffer().putInt(bufferClaim.offset(), i);\n                    bufferClaim.commit();\n                }\n\n                Tests.awaitPosition(counters, counterId, publication.position());\n            }\n            archive.stopRecording(subscriptionId);\n        }\n        finally\n        {\n            archiveCtx.deleteDirectory();\n            driverCtx.deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReplayPaddingGreaterThanMaxMessageLengthDueToFragmentationNearEndOfTerm() throws IOException\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n        final Aeron.Context aeronCtx = new Aeron.Context()\n            .aeronDirectoryName(driverCtx.aeronDirectoryName());\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .deleteArchiveOnStart(true)\n            .threadingMode(SHARED);\n\n        try (ArchivingMediaDriver archivingDriver = ArchivingMediaDriver.launch(driverCtx, archiveCtx);\n            Aeron aeron = Aeron.connect(aeronCtx);\n            AeronArchive archive = AeronArchive.connect(TestContexts.localhostAeronArchive()))\n        {\n            final long recordingId;\n            final long finalPubPos;\n            final long finalSubPos;\n\n            final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n\n            final String channel = new ChannelUriStringBuilder()\n                .media(IPC_MEDIA)\n                .termLength(256 * 1024)\n                .build();\n\n            try (Publication publication = archive.addRecordedExclusivePublication(channel, 1))\n            {\n                int counterId;\n                final int sessionId = publication.sessionId();\n                final CountersReader countersReader = aeron.countersReader();\n\n                while (NULL_VALUE == (counterId = RecordingPos.findCounterIdBySession(\n                    countersReader, sessionId, archive.archiveId())))\n                {\n                    Tests.yield();\n                }\n\n                recordingId = RecordingPos.getRecordingId(countersReader, counterId);\n\n                final UnsafeBuffer smallMessage = new UnsafeBuffer(new byte[0]);\n                final int maxMessageLength = publication.maxMessageLength();\n                final int requiredLength = calculateFragmentedMessageLength(publication, maxMessageLength);\n                while (publication.position() + requiredLength <= publication.termBufferLength())\n                {\n                    if (publication.offer(smallMessage) < 0)\n                    {\n                        idleStrategy.idle();\n                        Tests.checkInterruptStatus();\n                    }\n                }\n\n                final UnsafeBuffer bigFragmentedMessage = new UnsafeBuffer(new byte[maxMessageLength]);\n                while (publication.offer(bigFragmentedMessage) < 0)\n                {\n                    idleStrategy.idle();\n                    Tests.checkInterruptStatus();\n                }\n\n                finalPubPos = publication.position();\n            }\n\n            try (Subscription subscription = archive.replay(recordingId, 0, NULL_VALUE, channel, 2))\n            {\n                while (subscription.imageCount() == 0)\n                {\n                    idleStrategy.idle();\n                    Tests.checkInterruptStatus();\n                }\n\n                final Image image = subscription.imageAtIndex(0);\n                final AtomicCounter errorCounter = archivingDriver.archive().context().errorCounter();\n\n                while (!image.isClosed() && !image.isEndOfStream())\n                {\n                    final int fragmentCount = image.poll(NO_OP_FRAGMENT_HANDLER, 10);\n                    idleStrategy.idle(fragmentCount);\n\n                    if (fragmentCount == 0 && errorCounter.get() > 0)\n                    {\n                        try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream();\n                            PrintStream outStream = new PrintStream(byteStream))\n                        {\n                            ArchiveTool.printErrors(outStream, archivingDriver.archive().context().archiveDir());\n                            fail(byteStream.toString(StandardCharsets.UTF_8));\n                        }\n                    }\n                }\n\n                finalSubPos = image.position();\n            }\n\n            assertEquals(finalPubPos, finalSubPos);\n        }\n        finally\n        {\n            archiveCtx.deleteDirectory();\n            driverCtx.deleteDirectory();\n        }\n    }\n\n    @Test\n    void shouldRejectArchiveCreationIfAnotherArchiveWithTheSameArchiveIdIsAlreadyRunning(@TempDir final Path root)\n    {\n        final Path aeronDir = root.resolve(\"media-driver\");\n        final Path archiveDir1 = root.resolve(\"archive1\");\n        final Path archiveDir2 = root.resolve(\"archive2\");\n        final long archiveId = -432946792374923L;\n        try (TestMediaDriver driver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(aeronDir.toString()), systemTestWatcher);\n            Archive archive = Archive.launch(TestContexts.localhostArchive()\n                .archiveId(archiveId)\n                .archiveDir(archiveDir1.toFile())\n                .aeronDirectoryName(driver.context().aeronDirectoryName())))\n        {\n            final Context archiveContext2 = TestContexts.localhostArchive()\n                .archiveId(archive.context().archiveId())\n                .archiveDir(archiveDir2.toFile())\n                .aeronDirectoryName(driver.context().aeronDirectoryName());\n            try\n            {\n                final ArchiveException exception =\n                    assertThrowsExactly(ArchiveException.class, archiveContext2::conclude);\n                assertEquals(\"ERROR - found existing archive for archiveId=\" + archiveId, exception.getMessage());\n            }\n            finally\n            {\n                archiveContext2.close();\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -1, 888 })\n    @InterruptAfter(10)\n    void shouldAssignClientName(final int archiveId) throws IOException\n    {\n        final Path root = Files.createTempDirectory(\"test\");\n        final String aeronDir = root.resolve(\"media-driver\").toString();\n        try (TestMediaDriver driver =\n            TestMediaDriver.launch(new MediaDriver.Context().aeronDirectoryName(aeronDir), systemTestWatcher);\n            Archive archive = Archive.launch(TestContexts.localhostArchive()\n                .archiveId(archiveId)\n                .archiveDir(root.resolve(\"archive1\").toFile())\n                .aeronDirectoryName(driver.aeronDirectoryName()));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDir)))\n        {\n            final long archiveClientId = archive.context().aeron().clientId();\n            assertNotEquals(aeron.clientId(), archiveClientId);\n            final CountersReader countersReader = aeron.countersReader();\n            int counterId = NULL_COUNTER_ID;\n            String counterLabel = null;\n            while (true)\n            {\n                if (NULL_COUNTER_ID == counterId)\n                {\n                    counterId = HeartbeatTimestamp.findCounterIdByRegistrationId(\n                        countersReader, HEARTBEAT_TYPE_ID, archiveClientId);\n                }\n                else if (null == counterLabel || !counterLabel.contains(\"name=\"))\n                {\n                    counterLabel = countersReader.getCounterLabel(counterId);\n                }\n                else\n                {\n                    assertThat(\n                        counterLabel,\n                        CoreMatchers.containsString(\"name=archive archiveId=\" + archive.context().archiveId()));\n                    break;\n                }\n                Tests.checkInterruptStatus();\n            }\n        }\n        finally\n        {\n            IoUtil.delete(root.toFile(), true);\n        }\n    }\n\n    private static int calculateFragmentedMessageLength(final Publication publication, final int maxMessageLength)\n    {\n        final int maxPayloadLength = publication.maxPayloadLength();\n        final int numMaxPayloads = maxMessageLength / maxPayloadLength;\n        final int remainingPayload = maxMessageLength % maxPayloadLength;\n        final int lastFrameLength = remainingPayload > 0 ? align(remainingPayload + HEADER_LENGTH, FRAME_ALIGNMENT) : 0;\n        return (numMaxPayloads * (maxPayloadLength + HEADER_LENGTH)) + lastFrameLength;\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    @ParameterizedTest\n    @CsvSource({\n        \"aeron:udp?endpoint=localhost:8010, aeron:udp?endpoint=localhost:0\",\n        \"aeron:ipc, aeron:ipc\",\n        \"aeron:udp?endpoint=localhost:8010, aeron:udp?control=localhost:9090|control-mode=response\" })\n    @InterruptAfter(15)\n    void shouldTimeoutInactiveArchiveClients(final String controlRequestChannel, final String controlResponseChannel)\n    {\n        final long archiveId = -743746574;\n        final ErrorHandler errorHandler = mock(ErrorHandler.class);\n        try (TestMediaDriver driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(generateRandomDirName())\n            .dirDeleteOnShutdown(true)\n            .statusMessageTimeoutNs(TimeUnit.MILLISECONDS.toNanos(80))\n            .imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(1000))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100)), systemTestWatcher);\n            Archive archive = Archive.launch(TestContexts.localhostArchive()\n                .controlChannel(\"aeron:udp?endpoint=localhost:8010\")\n                .localControlChannel(\"aeron:ipc?term-length=64k\")\n                .controlStreamId(888)\n                .localControlStreamId(888)\n                .archiveId(archiveId)\n                .archiveDir(tmpDir.resolve(\"archive\").toFile())\n                .aeronDirectoryName(driver.context().aeronDirectoryName())\n                .connectTimeoutNs(TimeUnit.MILLISECONDS.toNanos(678))\n                .sessionLivenessCheckIntervalNs(TimeUnit.MILLISECONDS.toNanos(1))\n                .errorHandler(errorHandler)))\n        {\n            final AeronArchive.Context ctx = new AeronArchive.Context()\n                .aeronDirectoryName(driver.context().aeronDirectoryName())\n                .controlRequestChannel(controlRequestChannel)\n                .controlRequestStreamId(archive.context().controlStreamId())\n                .controlResponseChannel(controlResponseChannel);\n            try (AeronArchive client1 = AeronArchive.connect(ctx.clone().controlResponseStreamId(777));\n                AeronArchive client2 = AeronArchive.connect(ctx.clone().controlResponseStreamId(999)))\n            {\n                assertEquals(archiveId, client1.archiveId());\n                assertEquals(archiveId, client2.archiveId());\n                assertNotEquals(client1.controlSessionId(), client2.controlSessionId());\n\n                final long timeToFillResponseWindowNs =\n                    (ctx.controlTermBufferLength() / 2 / 64) * archive.context().sessionLivenessCheckIntervalNs();\n\n                final Counter sessionsCounter = archive.context().controlSessionsCounter();\n\n                final long startNs = System.nanoTime();\n                while (2 == sessionsCounter.get())\n                {\n                    assertNull(client1.pollForErrorResponse());\n                    Tests.sleep(1);\n                }\n                final long endNs = System.nanoTime();\n\n                assertThat(\n                    endNs - startNs,\n                    greaterThanOrEqualTo(timeToFillResponseWindowNs + archive.context().connectTimeoutNs()));\n\n                Tests.await(() -> 1 == archive.context().errorCounter().get());\n                final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);\n                verify(errorHandler, timeout(1000)).onError(captor.capture());\n                final ArchiveEvent event = assertInstanceOf(ArchiveEvent.class, captor.getValue());\n                assertEquals(AeronException.Category.WARN, event.category());\n                final ChannelUri parsedResponseChannel = ChannelUri.parse(client2.context().controlResponseChannel());\n                assertThat(event.getMessage(), allOf(\n                    Matchers.startsWith(\"WARN - controlSessionId=\" + client2.controlSessionId() + \" (\"),\n                    Matchers.endsWith(\") terminated: failed to send response for more than connectTimeoutMs=\" +\n                    TimeUnit.NANOSECONDS.toMillis(archive.context().connectTimeoutNs())),\n                    Matchers.containsString(\"controlResponseStreamId=999\"),\n                    Matchers.containsString(\"controlResponseChannel=aeron:\" + parsedResponseChannel.media())));\n\n                if (CONTROL_MODE_RESPONSE.equals(parsedResponseChannel.get(MDC_CONTROL_MODE_PARAM_NAME)))\n                {\n                    assertThat(event.getMessage(), allOf(\n                        Matchers.containsString(\"control-mode=response\"),\n                        Matchers.containsString(\"response-correlation-id=\")));\n                }\n                else\n                {\n                    assertThat(\n                        event.getMessage(),\n                        Matchers.containsString(\"session-id=\" + parsedResponseChannel.get(SESSION_ID_PARAM_NAME)));\n                }\n\n                while (AeronArchive.State.CONNECTED == client2.state())\n                {\n                    assertNull(client1.pollForErrorResponse());\n                    Tests.sleep(1);\n                }\n\n                // Closed via UnavailableImageHandler\n                assertEquals(AeronArchive.State.DISCONNECTED, client2.state());\n                assertFalse(client2.controlResponsePoller().subscription().isConnected());\n                Tests.await(() -> !client2.archiveProxy().publication().isConnected());\n\n                assertEquals(AeronArchive.State.CONNECTED, client1.state());\n                assertTrue(client1.archiveProxy().publication().isConnected());\n                assertTrue(client1.controlResponsePoller().subscription().isConnected());\n            }\n        }\n    }\n\n    @Test\n    void archiveMarkFileShouldContainArchiveIdSetOnContextConclude()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n        final Archive.Context archiveCtx = TestContexts.localhostArchive().threadingMode(SHARED);\n\n        assertEquals(NULL_VALUE, archiveCtx.archiveId()); // <-- Should not have been set yet.\n        try (ArchivingMediaDriver ignored = ArchivingMediaDriver.launch(driverCtx, archiveCtx);\n            AeronArchive archiveClient = AeronArchive.connect(TestContexts.localhostAeronArchive()))\n        {\n            final long archiveId = archiveCtx.archiveId(); // <-- Should have been set now as the context is concluded.\n            assertNotEquals(NULL_VALUE, archiveId);\n            assertEquals(archiveId, archiveClient.archiveId());\n            assertEquals(archiveId, archiveCtx.archiveMarkFile().archiveId());\n        }\n        finally\n        {\n            archiveCtx.deleteDirectory();\n            driverCtx.deleteDirectory();\n        }\n    }\n\n    @Test\n    void closedArchiveClientShouldBeInStateClosed(@TempDir final Path temp)\n    {\n        try (TestMediaDriver driver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(generateRandomDirName()), systemTestWatcher);\n            Archive archive = Archive.launch(TestContexts.localhostArchive()\n                .archiveDir(temp.toFile())\n                .archiveId(ThreadLocalRandom.current().nextLong())\n                .aeronDirectoryName(driver.context().aeronDirectoryName()));\n            AeronArchive aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive()\n                .aeronDirectoryName(driver.context().aeronDirectoryName())))\n        {\n            assertEquals(archive.context().archiveId(), aeronArchive.archiveId());\n\n            final Publication publication = aeronArchive.archiveProxy().publication();\n            final Subscription subscription = aeronArchive.controlResponsePoller().subscription();\n\n            aeronArchive.close();\n\n            Tests.await(publication::isClosed);\n            Tests.await(subscription::isClosed);\n            assertTrue(aeronArchive.context().aeron().isClosed());\n            assertEquals(AeronArchive.State.CLOSED, aeronArchive.state());\n        }\n    }\n\n    @Test\n    void closedArchiveClientShouldBeInStateClosedCustomAeronClient(@TempDir final Path temp)\n    {\n        try (TestMediaDriver driver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(generateRandomDirName()), systemTestWatcher);\n            Archive archive = Archive.launch(TestContexts.localhostArchive()\n                .archiveDir(temp.toFile())\n                .archiveId(ThreadLocalRandom.current().nextLong())\n                .aeronDirectoryName(driver.context().aeronDirectoryName()));\n            Aeron aeron = Aeron.connect(new Aeron.Context()\n                .aeronDirectoryName(driver.context().aeronDirectoryName())\n                .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE));\n            AeronArchive aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive()\n                .aeronDirectoryName(null)\n                .aeron(aeron)))\n        {\n            assertEquals(archive.context().archiveId(), aeronArchive.archiveId());\n\n            final Publication publication = aeronArchive.archiveProxy().publication();\n            final Subscription subscription = aeronArchive.controlResponsePoller().subscription();\n\n            aeronArchive.close();\n\n            Tests.await(publication::isClosed);\n            Tests.await(subscription::isClosed);\n            assertFalse(aeron.isClosed());\n            assertFalse(aeronArchive.context().aeron().isClosed());\n            assertEquals(AeronArchive.State.CLOSED, aeronArchive.state());\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"\", \"test client 5\" })\n    void shouldCreateControlSessionCounter(final String clientName, @TempDir final Path temp)\n    {\n        try (TestMediaDriver driver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(generateRandomDirName()), systemTestWatcher);\n            Archive archive = Archive.launch(TestContexts.localhostArchive()\n                .archiveDir(temp.toFile())\n                .archiveId(519)\n                .aeronDirectoryName(driver.context().aeronDirectoryName()));\n            AeronArchive aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive()\n                .aeronDirectoryName(driver.context().aeronDirectoryName())\n                .clientName(clientName)))\n        {\n            final CountersReader countersReader = archive.context().aeron().countersReader();\n            assertEquals(archive.context().archiveId(), aeronArchive.archiveId());\n\n            final int counterId = ControlSessionCounter.findByControlSessionId(\n                countersReader, aeronArchive.archiveId(), aeronArchive.controlSessionId());\n            assertNotEquals(NULL_COUNTER_ID, counterId);\n            assertEquals(aeronArchive.controlSessionId(), countersReader.getCounterValue(counterId));\n\n            final int keyOffset = metaDataOffset(counterId) + KEY_OFFSET;\n            final AtomicBuffer metaDataBuffer = countersReader.metaDataBuffer();\n            assertEquals(\n                aeronArchive.archiveId(),\n                metaDataBuffer.getLong(keyOffset + ControlSessionCounter.ARCHIVE_ID_KEY_OFFSET));\n            assertEquals(\n                aeronArchive.controlSessionId(),\n                metaDataBuffer.getLong(keyOffset + ControlSessionCounter.CONTROL_SESSION_ID_KEY_OFFSET));\n\n            final long responsePubRegistrationId = countersReader.getCounterReferenceId(counterId);\n            assertNotEquals(NULL_VALUE, responsePubRegistrationId);\n            final int responsePubCounterId = countersReader.findByTypeIdAndRegistrationId(\n                PublisherPos.PUBLISHER_POS_TYPE_ID, responsePubRegistrationId);\n            assertNotEquals(NULL_COUNTER_ID, responsePubCounterId);\n\n            final Publication requestPublication = aeronArchive.archiveProxy().publication();\n            final String localAddress = LocalSocketAddressStatus.findAddress(\n                countersReader, requestPublication.channelStatus(), requestPublication.channelStatusId());\n            assertEquals(ControlSessionCounter.NAME + \": name=\" + clientName + \" \" +\n                AeronCounters.formatVersionInfo(AeronArchiveVersion.VERSION, AeronArchiveVersion.GIT_SHA) +\n                \" sourceIdentity=\" + localAddress +\n                \" sessionId=\" + requestPublication.sessionId() +\n                ArchiveCounters.ARCHIVE_ID_LABEL_SUFFIX + aeronArchive.archiveId(),\n                countersReader.getCounterLabel(counterId));\n\n            aeronArchive.close();\n\n            Tests.await(() -> countersReader.getCounterState(counterId) == RECORD_RECLAIMED);\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"\", \"test client 42\" })\n    void shouldCreateControlSessionCounterIpcConnection(final String clientName, @TempDir final Path temp)\n    {\n        try (TestMediaDriver driver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(generateRandomDirName()), systemTestWatcher);\n            Archive archive = Archive.launch(TestContexts.localhostArchive()\n                .archiveDir(temp.toFile())\n                .archiveId(-187)\n                .aeronDirectoryName(driver.context().aeronDirectoryName()));\n            AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()\n                .aeronDirectoryName(driver.context().aeronDirectoryName())\n                .credentialsSupplier(new CredentialsSupplier()\n                {\n                    public byte[] encodedCredentials()\n                    {\n                        return \"admin:test\".getBytes(StandardCharsets.US_ASCII);\n                    }\n\n                    public byte[] onChallenge(final byte[] encodedChallenge)\n                    {\n                        return \"challenged\".getBytes(StandardCharsets.US_ASCII);\n                    }\n                })\n                .clientName(clientName)))\n        {\n            final CountersReader countersReader = archive.context().aeron().countersReader();\n            assertEquals(archive.context().archiveId(), aeronArchive.archiveId());\n\n            final int counterId = ControlSessionCounter.findByControlSessionId(\n                countersReader, aeronArchive.archiveId(), aeronArchive.controlSessionId());\n            assertNotEquals(NULL_COUNTER_ID, counterId);\n            assertEquals(aeronArchive.controlSessionId(), countersReader.getCounterValue(counterId));\n\n            final int keyOffset = metaDataOffset(counterId) + KEY_OFFSET;\n            final AtomicBuffer metaDataBuffer = countersReader.metaDataBuffer();\n            assertEquals(\n                aeronArchive.archiveId(),\n                metaDataBuffer.getLong(keyOffset + ControlSessionCounter.ARCHIVE_ID_KEY_OFFSET));\n            assertEquals(\n                aeronArchive.controlSessionId(),\n                metaDataBuffer.getLong(keyOffset + ControlSessionCounter.CONTROL_SESSION_ID_KEY_OFFSET));\n\n            final long responsePubRegistrationId = countersReader.getCounterReferenceId(counterId);\n            assertNotEquals(NULL_VALUE, responsePubRegistrationId);\n            final int responsePubCounterId = countersReader.findByTypeIdAndRegistrationId(\n                PublisherPos.PUBLISHER_POS_TYPE_ID, responsePubRegistrationId);\n            assertNotEquals(NULL_COUNTER_ID, responsePubCounterId);\n\n            final Publication requestPublication = aeronArchive.archiveProxy().publication();\n            assertEquals(ControlSessionCounter.NAME + \": name=\" + clientName + \" \" +\n                AeronCounters.formatVersionInfo(AeronArchiveVersion.VERSION, AeronArchiveVersion.GIT_SHA) +\n                \" sourceIdentity=aeron:ipc\" +\n                \" sessionId=\" + requestPublication.sessionId() +\n                ArchiveCounters.ARCHIVE_ID_LABEL_SUFFIX + aeronArchive.archiveId(),\n                countersReader.getCounterLabel(counterId));\n\n            aeronArchive.close();\n\n            Tests.await(() -> countersReader.getCounterState(counterId) == RECORD_RECLAIMED);\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=localhost:0\",\n        \"aeron:udp?control=localhost:10000|control-mode=response\" })\n    void shouldConnectUsingInvokersWithAsyncConnect(final String responseChannel, @TempDir final Path temp)\n    {\n        final String requestChannel = \"aeron:udp?endpoint=localhost:8888\";\n        try (TestMediaDriver driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(generateRandomDirName())\n            .threadingMode(ThreadingMode.INVOKER), systemTestWatcher);\n            Archive archive = Archive.launch(new Archive.Context()\n                .threadingMode(ArchiveThreadingMode.INVOKER)\n                .mediaDriverAgentInvoker(driver.sharedAgentInvoker())\n                .archiveDir(temp.toFile())\n                .archiveId(42)\n                .controlChannel(requestChannel)\n                .controlStreamId(1919)\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .aeronDirectoryName(driver.context().aeronDirectoryName()));\n            Aeron aeron = Aeron.connect(new Aeron.Context()\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .useConductorAgentInvoker(true)\n                .driverAgentInvoker(driver.sharedAgentInvoker())\n                .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE)))\n        {\n            final AeronArchive.AsyncConnect asyncConnect = AeronArchive.asyncConnect(\n                new AeronArchive.Context()\n                    .controlRequestChannel(requestChannel)\n                    .controlRequestStreamId(archive.context().controlStreamId())\n                    .controlResponseChannel(responseChannel)\n                    .controlResponseStreamId(9191)\n                    .aeron(aeron)\n                    .agentInvoker(archive.invoker()));\n\n            AeronArchive aeronArchive;\n            while (null == (aeronArchive = asyncConnect.poll()))\n            {\n                Tests.yield();\n            }\n\n            assertEquals(archive.context().archiveId(), aeronArchive.archiveId());\n        }\n    }\n\n    private static Catalog openCatalog(final String archiveDirectoryName)\n    {\n        final IntConsumer intConsumer = (version) ->\n        {\n        };\n        return new Catalog(\n            new File(archiveDirectoryName),\n            new SystemEpochClock(),\n            MIN_CAPACITY,\n            true,\n            null,\n            intConsumer);\n    }\n\n    static final class SubscriptionDescriptor\n    {\n        final long controlSessionId;\n        final long correlationId;\n        final long subscriptionId;\n        final int streamId;\n        final String strippedChannel;\n\n        SubscriptionDescriptor(\n            final long controlSessionId,\n            final long correlationId,\n            final long subscriptionId,\n            final int streamId,\n            final String strippedChannel)\n        {\n            this.controlSessionId = controlSessionId;\n            this.correlationId = correlationId;\n            this.subscriptionId = subscriptionId;\n            this.streamId = streamId;\n            this.strippedChannel = strippedChannel;\n        }\n\n        public String toString()\n        {\n            return \"SubscriptionDescriptor{\" +\n                \"controlSessionId=\" + controlSessionId +\n                \", correlationId=\" + correlationId +\n                \", subscriptionId=\" + subscriptionId +\n                \", streamId=\" + streamId +\n                \", strippedChannel='\" + strippedChannel + '\\'' +\n                '}';\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveTests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.ControlResponseAdapter;\nimport io.aeron.archive.codecs.ControlResponseCode;\nimport io.aeron.test.Tests;\nimport org.agrona.IoUtil;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.collections.MutableLong;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.LongConsumer;\n\nclass ArchiveTests\n{\n    private static final long TIMEOUT_NS = TimeUnit.SECONDS.toNanos(5);\n\n    static File makeTestDirectory()\n    {\n        final File archiveDir = new File(SystemUtil.tmpDirName(), \"archive-test\");\n        if (archiveDir.exists())\n        {\n            System.err.println(\"warning - archive directory exists, deleting: \" + archiveDir.getAbsolutePath());\n            IoUtil.delete(archiveDir, false);\n        }\n\n        if (!archiveDir.mkdirs())\n        {\n            throw new IllegalStateException(\"failed to make archive test directory: \" + archiveDir.getAbsolutePath());\n        }\n\n        return archiveDir;\n    }\n\n    static void awaitConnectResponse(\n        final Subscription controlResponse, final long expectedCorrelationId, final LongConsumer receiveSessionId)\n    {\n        final MutableBoolean hasResponse = new MutableBoolean();\n        final ControlResponseAdapter controlResponseAdapter = new ControlResponseAdapter(\n            new FailControlResponseListener()\n            {\n                public void onResponse(\n                    final long controlSessionId,\n                    final long correlationId,\n                    final long relevantId,\n                    final ControlResponseCode code,\n                    final String errorMessage)\n                {\n                    if (correlationId == expectedCorrelationId)\n                    {\n                        if (ControlResponseCode.OK != code)\n                        {\n                            throw new IllegalStateException(\"expected=\" + ControlResponseCode.OK + \" actual=\" + code);\n                        }\n\n                        receiveSessionId.accept(controlSessionId);\n                        hasResponse.set(true);\n                    }\n                }\n            },\n            controlResponse,\n            1\n        );\n\n        Tests.executeUntil(\n            hasResponse::get,\n            (j) ->\n            {\n                if (0 == controlResponseAdapter.poll())\n                {\n                    Thread.yield();\n                }\n            },\n            Integer.MAX_VALUE,\n            TIMEOUT_NS);\n    }\n\n    static long awaitOk(final Subscription controlResponse, final long expectedCorrelationId)\n    {\n        final MutableBoolean hasResponse = new MutableBoolean();\n        final MutableLong relevantIdCapture = new MutableLong(Aeron.NULL_VALUE);\n        final ControlResponseAdapter controlResponseAdapter = new ControlResponseAdapter(\n            new FailControlResponseListener()\n            {\n                public void onResponse(\n                    final long controlSessionId,\n                    final long correlationId,\n                    final long relevantId,\n                    final ControlResponseCode code,\n                    final String errorMessage)\n                {\n                    if (correlationId == expectedCorrelationId)\n                    {\n                        if (ControlResponseCode.OK != code)\n                        {\n                            System.out.println(errorMessage);\n                            throw new IllegalStateException(\"expected=\" + ControlResponseCode.OK + \" actual=\" + code);\n                        }\n\n                        relevantIdCapture.set(relevantId);\n                        hasResponse.set(true);\n                    }\n                }\n            },\n            controlResponse,\n            1\n        );\n\n        Tests.executeUntil(\n            hasResponse::get,\n            (j) ->\n            {\n                if (0 == controlResponseAdapter.poll())\n                {\n                    Thread.yield();\n                }\n            },\n            Integer.MAX_VALUE,\n            TIMEOUT_NS);\n\n        return relevantIdCapture.get();\n    }\n\n    static long awaitResponse(final Subscription controlResponse, final long expectedCorrelationId)\n    {\n        final MutableBoolean hasResponse = new MutableBoolean();\n        final MutableLong relevantIdCapture = new MutableLong(Aeron.NULL_VALUE);\n        final ControlResponseAdapter controlResponseAdapter = new ControlResponseAdapter(\n            new FailControlResponseListener()\n            {\n                public void onResponse(\n                    final long controlSessionId,\n                    final long correlationId,\n                    final long relevantId,\n                    final ControlResponseCode code,\n                    final String errorMessage)\n                {\n                    if (correlationId == expectedCorrelationId)\n                    {\n                        relevantIdCapture.set(relevantId);\n                        hasResponse.set(true);\n                    }\n                }\n            },\n            controlResponse,\n            1\n        );\n\n        Tests.executeUntil(\n            hasResponse::get,\n            (j) ->\n            {\n                if (0 == controlResponseAdapter.poll())\n                {\n                    Thread.yield();\n                }\n            },\n            Integer.MAX_VALUE,\n            TIMEOUT_NS);\n\n        return relevantIdCapture.get();\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveToolCliTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.codecs.RecordingState;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport java.io.*;\n\nimport static io.aeron.archive.Catalog.PAGE_SIZE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.AeronArchive.NULL_TIMESTAMP;\nimport static io.aeron.archive.codecs.RecordingState.*;\nimport static org.hamcrest.CoreMatchers.*;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ArchiveToolCliTest\n{\n    private static final int MTU_LENGTH = PAGE_SIZE * 4;\n    private static final int TERM_LENGTH = MTU_LENGTH * 8;\n    private static final int SEGMENT_LENGTH = TERM_LENGTH * 4;\n\n    private @TempDir File archiveDir;\n    private ArchiveMarkFile markFile;\n    private long currentTimeMillis = 0;\n    private final EpochClock epochClock = () -> currentTimeMillis += 100;\n\n    @BeforeEach\n    void before()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, 1024, epochClock, null, null))\n        {\n            catalog.addNewRecording(0, NULL_POSITION, 15, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 2, 2, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            catalog.addNewRecording(0, NULL_POSITION, 15, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 2, 2, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            catalog.addNewRecording(0, NULL_POSITION, 15, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 2, 2, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            catalog.addNewRecording(0, NULL_POSITION, 15, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 2, 2, \"ch2\", \"ch2?tag=OK\", \"src2\");\n        }\n\n        final SystemEpochClock clock = SystemEpochClock.INSTANCE;\n        markFile = new ArchiveMarkFile(\n            new File(archiveDir, ArchiveMarkFile.FILENAME), 1024 * 64, 8192, clock, 1000);\n        markFile.signalReady(clock.time());\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.close(markFile);\n    }\n\n    @ParameterizedTest\n    @EnumSource(RecordingState.class)\n    void describeRecordingShouldDescribeExistingRecording(final RecordingState state)\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, 1024, epochClock, null, null))\n        {\n            assertTrue(catalog.changeState(1, state));\n        }\n\n        final OutputConsole console = runArchiveTool(\"describe\", \"1\");\n        assertThat(console.systemOutText(), containsString(\"|recordingId=1|\"));\n    }\n\n    @Test\n    void describeRecordingShouldThrowExceptionOnUnknownRecording()\n    {\n        final AeronException exception =\n            assertThrowsExactly(AeronException.class, () -> runArchiveTool(\"describe\", \"10\"));\n        assertEquals(\"ERROR - no recording found with recordingId: 10\", exception.getMessage());\n    }\n\n    @Test\n    void describeRecordingsShouldNotShowNonValidRecordings()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, 1024, epochClock, null, null))\n        {\n            assertTrue(catalog.changeState(1, DELETED));\n            assertTrue(catalog.changeState(3, INVALID));\n            assertTrue(catalog.changeState(0, NULL_VAL));\n        }\n\n        final OutputConsole console = runArchiveTool(\"describe\");\n\n        final String consoleText = console.systemOutText();\n        assertThat(consoleText, allOf(\n            containsString(\"|recordingId=2|\"),\n            not(containsString(\"|recordingId=0|\")),\n            not(containsString(\"|recordingId=1|\")),\n            not(containsString(\"|recordingId=3|\"))));\n    }\n\n    @Test\n    void describeAllRecordingsShouldShowAllRecordings()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, 1024, epochClock, null, null))\n        {\n            assertTrue(catalog.changeState(1, DELETED));\n            assertTrue(catalog.changeState(2, INVALID));\n            assertTrue(catalog.changeState(3, NULL_VAL));\n        }\n\n        final OutputConsole console = runArchiveTool(\"describe-all\");\n\n        final String consoleText = console.systemOutText();\n        assertThat(consoleText, allOf(\n            containsString(\"|recordingId=0|\"),\n            containsString(\"|recordingId=1|\"),\n            containsString(\"|recordingId=2|\"),\n            containsString(\"|recordingId=3|\")));\n    }\n\n    private OutputConsole runArchiveTool(final String... args)\n    {\n        final PrintStream originalSystemOut = System.out;\n        try\n        {\n            final OutputConsole outputConsole = new OutputConsole();\n            System.setOut(outputConsole.systemOut());\n\n            final String[] mainArgs = new String[args.length + 1];\n            mainArgs[0] = archiveDir.getAbsolutePath();\n            System.arraycopy(args, 0, mainArgs, 1, args.length);\n\n            ArchiveTool.main(mainArgs);\n            return outputConsole;\n        }\n        finally\n        {\n            System.setOut(originalSystemOut);\n        }\n    }\n\n    private static final class OutputConsole\n    {\n        private final OutputStream outputBytes = new ByteArrayOutputStream();\n        private final PrintStream sysOut = new PrintStream(outputBytes);\n\n        public PrintStream systemOut()\n        {\n            return sysOut;\n        }\n\n        public String systemOutText()\n        {\n            return outputBytes.toString();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveToolSeparateMarkFileTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.util.Objects;\n\nimport static io.aeron.CommonContext.generateRandomDirName;\n\nclass ArchiveToolSeparateMarkFileTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    void shouldDescribe(@TempDir final File archiveDir, @TempDir final File markFileDir)\n    {\n        final MediaDriver.Context driverContext = new MediaDriver.Context()\n            .aeronDirectoryName(generateRandomDirName());\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .aeronDirectoryName(driverContext.aeronDirectoryName())\n            .archiveDir(archiveDir)\n            .markFileDir(markFileDir);\n\n        try (TestMediaDriver driver = TestMediaDriver.launch(driverContext, systemTestWatcher);\n            Archive archive = Archive.launch(archiveContext))\n        {\n            Objects.requireNonNull(driver);\n            Objects.requireNonNull(archive);\n\n            ArchiveTool.describe(new PrintStream(new ByteArrayOutputStream()), archiveDir);\n        }\n    }\n\n    @Test\n    void shouldDescribeRecordings(@TempDir final File archiveDir, @TempDir final File markFileDir)\n    {\n        final MediaDriver.Context driverContext = new MediaDriver.Context()\n            .aeronDirectoryName(generateRandomDirName());\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .aeronDirectoryName(driverContext.aeronDirectoryName())\n            .archiveDir(archiveDir)\n            .markFileDir(markFileDir);\n\n        try (TestMediaDriver driver = TestMediaDriver.launch(driverContext, systemTestWatcher);\n            Archive archive = Archive.launch(archiveContext))\n        {\n            Objects.requireNonNull(driver);\n            Objects.requireNonNull(archive);\n\n            ArchiveTool.describeAll(new PrintStream(new ByteArrayOutputStream()), archiveDir);\n        }\n    }\n\n    @Test\n    void shouldSetCapacity(@TempDir final File archiveDir, @TempDir final File markFileDir)\n    {\n        final MediaDriver.Context driverContext = new MediaDriver.Context()\n            .aeronDirectoryName(generateRandomDirName());\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .aeronDirectoryName(driverContext.aeronDirectoryName())\n            .archiveDir(archiveDir)\n            .markFileDir(markFileDir);\n\n        try (TestMediaDriver driver = TestMediaDriver.launch(driverContext, systemTestWatcher);\n            Archive archive = Archive.launch(archiveContext))\n        {\n            Objects.requireNonNull(driver);\n            Objects.requireNonNull(archive);\n\n            ArchiveTool.capacity(archiveDir, 1 << 16);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ArchiveToolTests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.codecs.RecordingState;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.IoUtil;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.concurrent.EpochClock;\nimport org.junit.jupiter.api.*;\nimport org.junit.jupiter.api.function.Executable;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.Mockito;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static io.aeron.archive.Archive.Configuration.CATALOG_FILE_NAME;\nimport static io.aeron.archive.Archive.Configuration.RECORDING_SEGMENT_SUFFIX;\nimport static io.aeron.archive.Archive.segmentFileName;\nimport static io.aeron.archive.ArchiveTool.*;\nimport static io.aeron.archive.ArchiveTool.VerifyOption.APPLY_CHECKSUM;\nimport static io.aeron.archive.ArchiveTool.VerifyOption.VERIFY_ALL_SEGMENT_FILES;\nimport static io.aeron.archive.Catalog.*;\nimport static io.aeron.archive.checksum.Checksums.crc32;\nimport static io.aeron.archive.client.AeronArchive.*;\nimport static io.aeron.archive.codecs.RecordingState.*;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computeTermIdFromPosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.positionBitsToShift;\nimport static io.aeron.protocol.DataHeaderFlyweight.*;\nimport static java.nio.ByteBuffer.allocate;\nimport static java.nio.file.Files.createTempDirectory;\nimport static java.nio.file.Files.deleteIfExists;\nimport static java.nio.file.StandardOpenOption.READ;\nimport static java.nio.file.StandardOpenOption.WRITE;\nimport static java.util.Collections.emptySet;\nimport static java.util.EnumSet.allOf;\nimport static java.util.EnumSet.of;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.times;\n\nclass ArchiveToolTests\n{\n    private static final int MTU_LENGTH = PAGE_SIZE * 4;\n    private static final int TERM_LENGTH = MTU_LENGTH * 8;\n    private static final int SEGMENT_LENGTH = TERM_LENGTH * 4;\n    private static Path validationDir;\n\n    private long invalidRecording0;\n    private long invalidRecording1;\n    private long invalidRecording2;\n    private long invalidRecording3;\n    private long invalidRecording4;\n    private long invalidRecording5;\n    private long invalidRecording6;\n    private long invalidRecording7;\n    private long invalidRecording8;\n    private long invalidRecording9;\n    private long invalidRecording10;\n    private long invalidRecording11;\n    private long invalidRecording12;\n    private long invalidRecording13;\n    private long invalidRecording14;\n    private long validRecording0;\n    private long validRecording1;\n    private long validRecording2;\n    private long validRecording3;\n    private long validRecording4;\n    private long validRecording51;\n    private long validRecording52;\n    private long validRecording53;\n    private long validRecording6;\n\n    private long currentTimeMillis = 0;\n    private final EpochClock epochClock = () -> currentTimeMillis += 100;\n    private final PrintStream out = Mockito.mock(PrintStream.class);\n    private File archiveDir;\n\n    @BeforeAll\n    static void beforeAll() throws IOException\n    {\n        validationDir = createTempDirectory(\"validation-tests\");\n    }\n\n    @AfterAll\n    static void afterAll() throws IOException\n    {\n        deleteIfExists(validationDir);\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    @BeforeEach\n    void before() throws IOException\n    {\n        archiveDir = ArchiveTests.makeTestDirectory();\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, 1024, epochClock, null, null))\n        {\n            invalidRecording0 = catalog.addNewRecording(NULL_POSITION, NULL_POSITION, 1, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording1 = catalog.addNewRecording(FRAME_ALIGNMENT - 7, NULL_POSITION, 2, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording2 = catalog.addNewRecording(1024, FRAME_ALIGNMENT * 2, 3, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording3 = catalog.addNewRecording(0, FRAME_ALIGNMENT * 5 + 11, 4, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording4 = catalog.addNewRecording(SEGMENT_LENGTH, NULL_POSITION, 5, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording5 = catalog.addNewRecording(0, SEGMENT_LENGTH, 6, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording6 = catalog.addNewRecording(0, NULL_POSITION, 7, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording7 = catalog.addNewRecording(0, NULL_POSITION, 8, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording8 = catalog.addNewRecording(0, NULL_POSITION, 9, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording9 = catalog.addNewRecording(0, NULL_POSITION, 10, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording10 = catalog.addNewRecording(128, NULL_POSITION, 11, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording11 = catalog.addNewRecording(0, NULL_POSITION, 12, NULL_TIMESTAMP, 5,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording12 = catalog.addNewRecording(0, NULL_POSITION, 13, NULL_TIMESTAMP, 9,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 6, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording13 = catalog.addNewRecording(0, NULL_POSITION, 14, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 13, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            invalidRecording14 = catalog.addNewRecording(128, NULL_POSITION, -14, 41, -14,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 0, \"ch1\", \"ch1?tag=ERR\", \"src1\");\n            validRecording0 = catalog.addNewRecording(0, NULL_POSITION, 15, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 2, 2, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            validRecording1 = catalog.addNewRecording(1024, NULL_POSITION, 16, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 2, 2, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            validRecording2 = catalog.addNewRecording(3 * TERM_LENGTH + 96, NULL_POSITION, 17, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 2, 2, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            validRecording3 = catalog.addNewRecording(\n                7 * TERM_LENGTH + 96, 7 * TERM_LENGTH + 128, 18, NULL_TIMESTAMP, 7,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 999, 13, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            validRecording4 = catalog.addNewRecording(\n                21 * TERM_LENGTH + (TERM_LENGTH - 64), 22 * TERM_LENGTH + 992, 19, 1, -25,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, -999, 7, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            validRecording51 = catalog.addNewRecording(0, 64 + PAGE_SIZE, 20, 777, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, -1, 20, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            validRecording52 = catalog.addNewRecording(0, NULL_POSITION, 21, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 52, 52, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            validRecording53 = catalog.addNewRecording(0, NULL_POSITION, 22, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 53, 53, \"ch2\", \"ch2?tag=OK\", \"src2\");\n            validRecording6 = catalog.addNewRecording(352, NULL_POSITION, 23, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 10, 6, \"ch2\", \"ch2?tag=OK\", \"src2\");\n        }\n\n        writeToSegmentFile(\n            createFile(segmentFileName(invalidRecording4, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) -> fileChannel.write(byteBuffer));\n\n        writeToSegmentFile(\n            createFile(segmentFileName(invalidRecording5, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) -> fileChannel.write(byteBuffer));\n\n        createFile(invalidRecording6 + \"-\" + RECORDING_SEGMENT_SUFFIX);\n\n        createFile(invalidRecording7 + \"-this-will-not-parse\" + RECORDING_SEGMENT_SUFFIX);\n\n        createFile(segmentFileName(invalidRecording8, -1024));\n\n        createDirectory(segmentFileName(invalidRecording9, 0));\n\n        writeToSegmentFile(\n            createFile(segmentFileName(invalidRecording10, 0)),\n            150,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) -> fileChannel.write(byteBuffer));\n\n        writeToSegmentFile(\n            createFile(segmentFileName(invalidRecording11, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(64);\n                dataHeaderFlyweight.streamId(1);\n                dataHeaderFlyweight.termOffset(0);\n                dataHeaderFlyweight.termId(5);\n                fileChannel.write(byteBuffer);\n\n                byteBuffer.clear();\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(40);\n                dataHeaderFlyweight.termId(-1);\n                fileChannel.write(byteBuffer, 64);\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(invalidRecording12, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(64);\n                dataHeaderFlyweight.streamId(6);\n                dataHeaderFlyweight.termOffset(0);\n                dataHeaderFlyweight.termId(9);\n                fileChannel.write(byteBuffer);\n\n                byteBuffer.clear();\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(50);\n                dataHeaderFlyweight.termOffset(-1);\n                fileChannel.write(byteBuffer, 64);\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(invalidRecording13, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(64);\n                dataHeaderFlyweight.streamId(13);\n                dataHeaderFlyweight.termOffset(0);\n                dataHeaderFlyweight.termId(0);\n                fileChannel.write(byteBuffer);\n\n                byteBuffer.clear();\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(60);\n                dataHeaderFlyweight.streamId(-1);\n                fileChannel.write(byteBuffer, 64);\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(invalidRecording14, 0)),\n            228,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(100);\n                dataHeaderFlyweight.streamId(0);\n                dataHeaderFlyweight.termOffset(128);\n                dataHeaderFlyweight.termId(-14);\n                fileChannel.write(byteBuffer, 128);\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording0, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                for (int i = 0, termOffset = 0; i < TERM_LENGTH / MTU_LENGTH - 1; i++, termOffset += MTU_LENGTH)\n                {\n                    byteBuffer.clear().limit(MTU_LENGTH);\n                    dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                    dataHeaderFlyweight.streamId(2);\n                    dataHeaderFlyweight.frameLength(MTU_LENGTH);\n                    dataHeaderFlyweight.sessionId(876814387);\n                    dataHeaderFlyweight.termOffset(termOffset);\n                    fileChannel.write(byteBuffer, termOffset);\n                }\n\n                byteBuffer.clear().limit(256);\n                dataHeaderFlyweight.frameLength(256);\n                dataHeaderFlyweight.termOffset(TERM_LENGTH - MTU_LENGTH);\n                dataHeaderFlyweight.sessionId(1025596259);\n                fileChannel.write(byteBuffer, TERM_LENGTH - MTU_LENGTH);\n\n                byteBuffer.clear().limit(MTU_LENGTH - 256);\n                dataHeaderFlyweight.headerType(HDR_TYPE_PAD);\n                dataHeaderFlyweight.sessionId(-1); // should not be checked\n                dataHeaderFlyweight.frameLength(MTU_LENGTH - 256);\n                dataHeaderFlyweight.termOffset(TERM_LENGTH - MTU_LENGTH + 256);\n                fileChannel.write(byteBuffer, TERM_LENGTH - MTU_LENGTH + 256);\n\n                byteBuffer.clear().limit(64);\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(64);\n                dataHeaderFlyweight.termOffset(0);\n                dataHeaderFlyweight.termId(1);\n                dataHeaderFlyweight.sessionId(420107693);\n                fileChannel.write(byteBuffer, TERM_LENGTH);\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording2, 3 * TERM_LENGTH)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(0);\n                fileChannel.write(byteBuffer, 96);\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording3, 7 * TERM_LENGTH)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                int fileOffset = 96;\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(128);\n                dataHeaderFlyweight.streamId(13);\n                dataHeaderFlyweight.termId(14);\n                dataHeaderFlyweight.termOffset(96);\n                dataHeaderFlyweight.setMemory(HEADER_LENGTH, 128 - HEADER_LENGTH, (byte)13);\n                fileChannel.write(byteBuffer, fileOffset);\n                fileOffset += 128;\n\n                final int segmentFileBasePosition = 7 * TERM_LENGTH;\n                final int positionBitsToShift = positionBitsToShift(TERM_LENGTH);\n                for (int i = 0; i < (SEGMENT_LENGTH / MTU_LENGTH) - 1; i++)\n                {\n                    byteBuffer.clear();\n                    final int termId = computeTermIdFromPosition(\n                        segmentFileBasePosition + fileOffset, positionBitsToShift, 7);\n                    dataHeaderFlyweight.frameLength(MTU_LENGTH);\n                    dataHeaderFlyweight.termId(termId);\n                    dataHeaderFlyweight.termOffset(fileOffset & (TERM_LENGTH - 1));\n                    dataHeaderFlyweight.setMemory(HEADER_LENGTH, MTU_LENGTH - HEADER_LENGTH, (byte)i);\n                    fileChannel.write(byteBuffer, fileOffset);\n                    fileOffset += MTU_LENGTH;\n                }\n\n                final int lastFrameLength = MTU_LENGTH - 224;\n                assertEquals(SEGMENT_LENGTH - lastFrameLength, fileOffset);\n                byteBuffer.clear();\n                dataHeaderFlyweight.frameLength(lastFrameLength);\n                dataHeaderFlyweight.termOffset(fileOffset & (TERM_LENGTH - 1));\n                for (int i = 0; i < lastFrameLength - HEADER_LENGTH; i++)\n                {\n                    dataHeaderFlyweight.setMemory(HEADER_LENGTH + i, 1, (byte)i);\n                }\n                dataHeaderFlyweight.setMemory(HEADER_LENGTH, 1, (byte)-128);\n                dataHeaderFlyweight.setMemory(lastFrameLength - 1, 1, (byte)127);\n                fileChannel.write(byteBuffer, fileOffset);\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording3, 11 * TERM_LENGTH)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(180);\n                dataHeaderFlyweight.streamId(13);\n                dataHeaderFlyweight.termId(18);\n                dataHeaderFlyweight.termOffset(0);\n                fileChannel.write(byteBuffer);\n\n                byteBuffer.clear();\n                dataHeaderFlyweight.frameLength(128);\n                dataHeaderFlyweight.termId(18);\n                dataHeaderFlyweight.termOffset(192);\n                fileChannel.write(byteBuffer, 192);\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording4, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(64);\n                dataHeaderFlyweight.streamId(Integer.MAX_VALUE);\n                fileChannel.write(byteBuffer);\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording4, 21 * TERM_LENGTH)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_PAD);\n                dataHeaderFlyweight.frameLength(64);\n                dataHeaderFlyweight.streamId(7);\n                dataHeaderFlyweight.termId(-4);\n                dataHeaderFlyweight.termOffset(TERM_LENGTH - 64);\n                fileChannel.write(byteBuffer, TERM_LENGTH - 64);\n\n                byteBuffer.clear();\n                dataHeaderFlyweight.frameLength(992);\n                dataHeaderFlyweight.termId(-3);\n                dataHeaderFlyweight.termOffset(0);\n                fileChannel.write(byteBuffer, TERM_LENGTH);\n            });\n\n        // Page straddle: valid checksum\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording51, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(64);\n                dataHeaderFlyweight.streamId(20);\n                dataHeaderFlyweight.sessionId(420107693);\n                fileChannel.write(byteBuffer);\n\n                byteBuffer.clear();\n                dataHeaderFlyweight.frameLength(PAGE_SIZE);\n                dataHeaderFlyweight.termOffset(64);\n                dataHeaderFlyweight.sessionId(2057703623);\n                fileChannel.write(byteBuffer, dataHeaderFlyweight.termOffset());\n            });\n\n        // Page straddle: non-zero bytes in every page since the straddle\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording52, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(111);\n                dataHeaderFlyweight.streamId(52);\n                fileChannel.write(byteBuffer);\n\n                byteBuffer.clear();\n                dataHeaderFlyweight.frameLength(MTU_LENGTH);\n                dataHeaderFlyweight.termOffset(128);\n                dataHeaderFlyweight.putBytes(PAGE_SIZE + 1, new byte[]{ 0, 0, -1, 0, 7 });\n                dataHeaderFlyweight.putByte(2 * PAGE_SIZE + 512, (byte)127);\n                dataHeaderFlyweight.putByte(MTU_LENGTH - 128 - 1, (byte)1);\n                dataHeaderFlyweight.putByte(MTU_LENGTH - 1, (byte)-128);\n                fileChannel.write(byteBuffer, dataHeaderFlyweight.termOffset());\n            });\n\n        // Page straddle: all zeroes in one of the pages after the straddle\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording53, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(60);\n                dataHeaderFlyweight.streamId(53);\n                fileChannel.write(byteBuffer);\n\n                byteBuffer.clear();\n                dataHeaderFlyweight.frameLength(3 * PAGE_SIZE);\n                dataHeaderFlyweight.termOffset(64);\n                dataHeaderFlyweight.setMemory(PAGE_SIZE - 64, PAGE_SIZE, (byte)111);\n                dataHeaderFlyweight.setMemory(3 * PAGE_SIZE - 64, 64, (byte)-128);\n                fileChannel.write(byteBuffer, dataHeaderFlyweight.termOffset());\n            });\n\n        writeToSegmentFile(\n            createFile(segmentFileName(validRecording6, 0)),\n            SEGMENT_LENGTH,\n            (byteBuffer, dataHeaderFlyweight, fileChannel) ->\n            {\n                dataHeaderFlyweight.headerType(HDR_TYPE_DATA);\n                dataHeaderFlyweight.frameLength(64);\n                dataHeaderFlyweight.streamId(6);\n                dataHeaderFlyweight.termOffset(352);\n                dataHeaderFlyweight.sessionId(-1960800604); // CRC32\n                dataHeaderFlyweight.setMemory(HEADER_LENGTH, 20, (byte)1);\n                fileChannel.write(byteBuffer, 352);\n\n                byteBuffer.clear();\n                dataHeaderFlyweight.frameLength(544);\n                dataHeaderFlyweight.termOffset(416);\n                dataHeaderFlyweight.sessionId(-327206874); // CRC32\n                dataHeaderFlyweight.setMemory(HEADER_LENGTH, 256, (byte)11);\n                dataHeaderFlyweight.setMemory(HEADER_LENGTH + 256, 256, (byte)-20);\n                fileChannel.write(byteBuffer, 416);\n            });\n    }\n\n    @AfterEach\n    void after()\n    {\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfStartPositionIsNegative()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording0, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording0, INVALID, 0, NULL_POSITION, NULL_POSITION, 1, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfStartPositionIsNotFrameAligned()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording1, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording1, INVALID, 0, FRAME_ALIGNMENT - 7, NULL_POSITION, 2,\n                NULL_TIMESTAMP, 0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfStopPositionIsBeforeStartPosition()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording2, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording2, INVALID, 0, 1024, FRAME_ALIGNMENT * 2, 3, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfStopPositionIsNotFrameAligned()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording3, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording3, INVALID, 0, 0, FRAME_ALIGNMENT * 5 + 11, 4, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfStartPositionIsOutOfRangeForTheMaxSegmentFile()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording4, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording4, INVALID, 0, SEGMENT_LENGTH, NULL_POSITION, 5, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfStopPositionIsOutOfRangeForTheMaxSegmentFile()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording5, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording5, INVALID, 0, 0, SEGMENT_LENGTH, 6, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfSegmentFileNameContainsNoPositionInformation()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording6, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording6, INVALID, 0, 0, NULL_POSITION, 7, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfSegmentFileNameContainsNonIntegerPositionInformation()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording7, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording7, INVALID, 0, 0, NULL_POSITION, 8, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfSegmentFileNameContainsNegativePositionInformation()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording8, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording8, INVALID, 0, 0, NULL_POSITION, 9, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfCannotReadFromSegmentFile()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording9, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording9, INVALID, 0, 0, NULL_POSITION, 10, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfCannotReadFrameFromSegmentFileAtGivenOffset()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording10, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording10, INVALID, 0, 128, NULL_POSITION, 11, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfSegmentFileContainsAFrameWithWrongTermId()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording11, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording11, INVALID, 0, 0, NULL_POSITION, 12, NULL_TIMESTAMP,\n                5, 1, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfSegmentFileContainsAFrameWithWrongTermOffset()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording12, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording12, INVALID, 0, 0, NULL_POSITION, 13, NULL_TIMESTAMP,\n                9, 6, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfSegmentFileContainsAFrameWithWrongStreamId()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording13, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording13, INVALID, 0, 0, NULL_POSITION, 14, NULL_TIMESTAMP,\n                0, 13, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfSegmentFileContainsIncompleteFrame()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, invalidRecording14, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording14, INVALID, 0, 128, NULL_POSITION,\n                -14, 41, -14, 0, \"ch1\", \"src1\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingShouldComputeStopPositionFromZeroStartPosition()\n    {\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording0, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording0, VALID, 0, 0, TERM_LENGTH + 64, 15, 100, 0, 2, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingShouldUseStartPositionIfNoSegmentFilesExist()\n    {\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording1, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording1, VALID, 0, 1024, 1024, 16, 100, 0, 2, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingShouldUseStartPositionWhenNoDataInTheMaxSegmentFileAtAGivenOffset()\n    {\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording2, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording2, VALID, 0, TERM_LENGTH * 3 + 96, TERM_LENGTH * 3 + 96, 17, 100,\n                0, 2, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingShouldComputeStopPositionWhenStartingAtALaterSegment()\n    {\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording3, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording3, VALID, 0, 7 * TERM_LENGTH + 96, 11 * TERM_LENGTH + 320,\n                18, 100, 7, 13, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingShouldNotUpdateStopPositionIfAlreadyCorrect()\n    {\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording4, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording4, VALID, 0, 21 * TERM_LENGTH + (TERM_LENGTH - 64),\n                22 * TERM_LENGTH + 992, 19, 1, -25, 7, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingValidateAllSegmentFiles()\n    {\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording3, of(VERIFY_ALL_SEGMENT_FILES), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording3, VALID, 0, 7 * TERM_LENGTH + 96, 11 * TERM_LENGTH + 320,\n                18, 100, 7, 13, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingInvalidRecordingValidateAllSegmentFiles()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, validRecording4, of(VERIFY_ALL_SEGMENT_FILES), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording4, INVALID, 0, 21 * TERM_LENGTH + (TERM_LENGTH - 64),\n                22 * TERM_LENGTH + 992, 19, 1, -25, 7, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingTruncateSegmentFileOnPageStraddleValidChecksum()\n    {\n        assertTrue(verifyRecording(out, archiveDir, validRecording51, emptySet(), crc32(), epochClock, (file) -> true));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording51, VALID, 0, 0, 64 + PAGE_SIZE, 20, 777,\n                0, 20, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingTruncateSegmentFileOnPageStraddleNonZeroBytesInEveryPageAfterTheStraddle()\n    {\n        assertTrue(verifyRecording(out, archiveDir, validRecording52, emptySet(), crc32(), epochClock, (file) -> true));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording52, VALID, 0, 0, 128 + MTU_LENGTH, 21, 100,\n                0, 52, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingTruncateSegmentFileOnPageStraddle()\n    {\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording53, emptySet(), null, epochClock, (file) -> true));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording53, VALID, 0, 0, 64, 22, 100, 0, 53, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingDoNotTruncateSegmentFileOnPageStraddle()\n    {\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording53, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording53, VALID, 0, 0, 64 + 3 * PAGE_SIZE, 22, 100, 0, 53, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingValidRecordingPerformCRC()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, checksum, null))\n        {\n            assertRecording(catalog,\n                validRecording6,\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                catalog.updateChecksum(recordingDescriptorOffset));\n        }\n\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording6, of(APPLY_CHECKSUM), checksum, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording6, VALID, -175549265, 352, 960, 23, 100, 0, 6, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingShouldMarkRecordingAsInvalidIfCatalogChecksumIsWrong()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, validRecording6, of(APPLY_CHECKSUM), crc32(), epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording6, INVALID, 0, 352, NULL_POSITION, 23,\n                NULL_TIMESTAMP, 0, 6, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void verifyRecordingInvalidRecordingPerformCRC()\n    {\n        assertFalse(verifyRecording(\n            out, archiveDir, validRecording3, of(APPLY_CHECKSUM), crc32(), epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording3, INVALID, 0, 7 * TERM_LENGTH + 96, 7 * TERM_LENGTH + 128,\n                18, NULL_TIMESTAMP, 7, 13, \"ch2\", \"src2\");\n        }\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = RecordingState.class, mode = EnumSource.Mode.EXCLUDE, names = { \"VALID\", \"INVALID\" })\n    void verifyRecordingShouldSkipRecordingIfStateIsDeletedOrUnknown(final RecordingState skipState)\n    {\n        final long recordingId = validRecording3;\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, null, null))\n        {\n            assertTrue(catalog.changeState(recordingId, skipState));\n        }\n\n        assertTrue(verifyRecording(\n            out, archiveDir, recordingId, of(APPLY_CHECKSUM), crc32(), epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, recordingId, skipState, 0, 7 * TERM_LENGTH + 96, 7 * TERM_LENGTH + 128,\n                18, NULL_TIMESTAMP, 7, 13, \"ch2\", \"src2\");\n        }\n        Mockito.verify(out).println(\"(recordingId=\" + recordingId + \") skipping: \" + skipState);\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = {-1000, Long.MAX_VALUE})\n    void verifyRecordingShouldThrowExceptionIfRecordingIsUnknown(final long recordingId)\n    {\n        final AeronException exception = assertThrowsExactly(AeronException.class, () -> verifyRecording(\n            out, archiveDir, recordingId, of(APPLY_CHECKSUM), crc32(), epochClock, (file) -> false));\n        assertEquals(\"ERROR - no recording found with recordingId: \" + recordingId, exception.getMessage());\n    }\n\n    @Test\n    void verifyNoOptionsDoNotTruncateOnPageStraddle()\n    {\n        assertFalse(verify(out, archiveDir, emptySet(), null, epochClock, (file) -> false));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording0, INVALID, 0, NULL_POSITION, NULL_POSITION, 1, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording1, INVALID, 0, FRAME_ALIGNMENT - 7, NULL_POSITION, 2,\n                NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording2, INVALID, 0, 1024, FRAME_ALIGNMENT * 2, 3, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording3, INVALID, 0, 0, FRAME_ALIGNMENT * 5 + 11, 4, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording4, INVALID, 0, SEGMENT_LENGTH, NULL_POSITION, 5, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording5, INVALID, 0, 0, SEGMENT_LENGTH, 6, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording6, INVALID, 0, 0, NULL_POSITION, 7, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording7, INVALID, 0, 0, NULL_POSITION, 8, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording8, INVALID, 0, 0, NULL_POSITION, 9, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording9, INVALID, 0, 0, NULL_POSITION, 10, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording10, INVALID, 0, 128, NULL_POSITION, 11, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording11, INVALID, 0, 0, NULL_POSITION, 12, NULL_TIMESTAMP,\n                5, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording12, INVALID, 0, 0, NULL_POSITION, 13, NULL_TIMESTAMP,\n                9, 6, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording13, INVALID, 0, 0, NULL_POSITION, 14, NULL_TIMESTAMP,\n                0, 13, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording14, INVALID, 0, 128, NULL_POSITION, -14,\n                41, -14, 0, \"ch1\", \"src1\");\n            assertRecording(catalog, validRecording0, VALID, 0, 0, TERM_LENGTH + 64, 15, 100,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording1, VALID, 0, 1024, 1024, 16, 200,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording2, VALID, 0, TERM_LENGTH * 3 + 96, TERM_LENGTH * 3 + 96,\n                17, 300, 0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording3, VALID, 0, 7 * TERM_LENGTH + 96,\n                11 * TERM_LENGTH + 320, 18, 400, 7, 13, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording4, VALID, 0, 21 * TERM_LENGTH + (TERM_LENGTH - 64),\n                22 * TERM_LENGTH + 992, 19, 1, -25, 7, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording51, VALID, 0, 0, 64 + PAGE_SIZE, 20, 777,\n                0, 20, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording52, VALID, 0, 0, 128 + MTU_LENGTH, 21, 500,\n                0, 52, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording53, VALID, 0, 0, 64 + 3 * PAGE_SIZE, 22, 600,\n                0, 53, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording6, VALID, 0, 352, 960, 23, 700, 0, 6, \"ch2\", \"src2\");\n        }\n\n        Mockito.verify(out, times(24)).println(any(String.class));\n    }\n\n    @Test\n    void verifyAllOptionsTruncateOnPageStraddle()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, checksum, null))\n        {\n            catalog.forEach(\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                catalog.updateChecksum(recordingDescriptorOffset));\n        }\n\n        assertFalse(verify(out, archiveDir, allOf(VerifyOption.class), checksum, epochClock, (file) -> true));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording0, INVALID, -119969720, NULL_POSITION, NULL_POSITION, 1,\n                NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording1, INVALID, 768794941, FRAME_ALIGNMENT - 7, NULL_POSITION, 2,\n                NULL_TIMESTAMP, 0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording2, INVALID, -1340428433, 1024, FRAME_ALIGNMENT * 2,\n                3, NULL_TIMESTAMP, 0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording3, INVALID, 1464972620, 0, FRAME_ALIGNMENT * 5 + 11,\n                4, NULL_TIMESTAMP, 0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording4, INVALID, 21473288, SEGMENT_LENGTH, NULL_POSITION, 5,\n                NULL_TIMESTAMP, 0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording5, INVALID, -2119992405, 0, SEGMENT_LENGTH, 6, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording6, INVALID, 2054096463, 0, NULL_POSITION, 7, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording7, INVALID, -1050175867, 0, NULL_POSITION, 8, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording8, INVALID, -504693275, 0, NULL_POSITION, 9, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording9, INVALID, -2036430506, 0, NULL_POSITION, 10, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording10, INVALID, -414736820, 128, NULL_POSITION, 11, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording11, INVALID, 1983095657, 0, NULL_POSITION, 12, NULL_TIMESTAMP,\n                5, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording12, INVALID, -1308504240, 0, NULL_POSITION, 13, NULL_TIMESTAMP,\n                9, 6, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording13, INVALID, -273182324, 0, NULL_POSITION, 14, NULL_TIMESTAMP,\n                0, 13, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording14, INVALID, 213018412, 128, NULL_POSITION, -14,\n                41, -14, 0, \"ch1\", \"src1\");\n            assertRecording(catalog, validRecording0, VALID, 356725588, 0, TERM_LENGTH + 64, 15, 100,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording1, VALID, -1571032591, 1024, 1024, 16, 200,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording2, VALID, 114203747, TERM_LENGTH * 3 + 96, TERM_LENGTH * 3 + 96,\n                17, 300, 0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording3, INVALID, 963969455, 7 * TERM_LENGTH + 96, 7 * TERM_LENGTH + 128,\n                18, NULL_TIMESTAMP, 7, 13, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording4, INVALID, 162247708, 21 * TERM_LENGTH + (TERM_LENGTH - 64),\n                22 * TERM_LENGTH + 992, 19, 1, -25, 7, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording51, VALID, -940881948, 0, 64 + PAGE_SIZE, 20, 777,\n                0, 20, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording52, INVALID, 1046083782, 0, NULL_POSITION, 21, NULL_TIMESTAMP,\n                0, 52, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording53, INVALID, 428178649, 0, NULL_POSITION, 22, NULL_TIMESTAMP,\n                0, 53, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording6, VALID, -175549265, 352, 960, 23, 400, 0, 6, \"ch2\", \"src2\");\n        }\n\n        Mockito.verify(out, times(24)).println(any(String.class));\n    }\n\n    @Test\n    void verifyAllShouldSkipDeletedRecordings()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, checksum, null))\n        {\n            catalog.forEach(\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                catalog.updateChecksum(recordingDescriptorOffset));\n            assertTrue(catalog.changeState(validRecording0, DELETED));\n            assertTrue(catalog.changeState(validRecording3, NULL_VAL));\n            assertTrue(catalog.changeState(validRecording52, DELETED));\n        }\n\n        assertFalse(verify(out, archiveDir, allOf(VerifyOption.class), checksum, epochClock, (file) -> true));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, invalidRecording0, INVALID, -119969720, NULL_POSITION, NULL_POSITION, 1,\n                NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording1, INVALID, 768794941, FRAME_ALIGNMENT - 7, NULL_POSITION, 2,\n                NULL_TIMESTAMP, 0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording2, INVALID, -1340428433, 1024, FRAME_ALIGNMENT * 2,\n                3, NULL_TIMESTAMP, 0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording3, INVALID, 1464972620, 0, FRAME_ALIGNMENT * 5 + 11,\n                4, NULL_TIMESTAMP, 0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording4, INVALID, 21473288, SEGMENT_LENGTH, NULL_POSITION, 5,\n                NULL_TIMESTAMP, 0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording5, INVALID, -2119992405, 0, SEGMENT_LENGTH, 6, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording6, INVALID, 2054096463, 0, NULL_POSITION, 7, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording7, INVALID, -1050175867, 0, NULL_POSITION, 8, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording8, INVALID, -504693275, 0, NULL_POSITION, 9, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording9, INVALID, -2036430506, 0, NULL_POSITION, 10, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording10, INVALID, -414736820, 128, NULL_POSITION, 11, NULL_TIMESTAMP,\n                0, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording11, INVALID, 1983095657, 0, NULL_POSITION, 12, NULL_TIMESTAMP,\n                5, 1, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording12, INVALID, -1308504240, 0, NULL_POSITION, 13, NULL_TIMESTAMP,\n                9, 6, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording13, INVALID, -273182324, 0, NULL_POSITION, 14, NULL_TIMESTAMP,\n                0, 13, \"ch1\", \"src1\");\n            assertRecording(catalog, invalidRecording14, INVALID, 213018412, 128, NULL_POSITION, -14,\n                41, -14, 0, \"ch1\", \"src1\");\n            assertRecording(catalog, validRecording0, DELETED, 356725588, 0, NULL_POSITION, 15, NULL_TIMESTAMP,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording1, VALID, -1571032591, 1024, 1024, 16, 100,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording2, VALID, 114203747, TERM_LENGTH * 3 + 96, TERM_LENGTH * 3 + 96,\n                17, 200, 0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording3, NULL_VAL, 963969455, 7 * TERM_LENGTH + 96, 7 * TERM_LENGTH + 128,\n                18, NULL_TIMESTAMP, 7, 13, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording4, INVALID, 162247708, 21 * TERM_LENGTH + (TERM_LENGTH - 64),\n                22 * TERM_LENGTH + 992, 19, 1, -25, 7, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording51, VALID, -940881948, 0, 64 + PAGE_SIZE, 20, 777,\n                0, 20, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording52, DELETED, 1046083782, 0, NULL_POSITION, 21, NULL_TIMESTAMP,\n                0, 52, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording53, INVALID, 428178649, 0, NULL_POSITION, 22, NULL_TIMESTAMP,\n                0, 53, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording6, VALID, -175549265, 352, 960, 23, 300, 0, 6, \"ch2\", \"src2\");\n        }\n\n        Mockito.verify(out, times(24)).println(any(String.class));\n        Mockito.verify(out).println(\"(recordingId=\" + validRecording0 + \") skipping: DELETED\");\n        Mockito.verify(out).println(\"(recordingId=\" + validRecording3 + \") skipping: NULL_VAL\");\n        Mockito.verify(out).println(\"(recordingId=\" + validRecording52 + \") skipping: DELETED\");\n    }\n\n    @Test\n    void verifyChecksum()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, checksum, null))\n        {\n            assertRecording(catalog,\n                validRecording51,\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                catalog.updateChecksum(recordingDescriptorOffset));\n\n            assertRecording(catalog,\n                validRecording6,\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                catalog.updateChecksum(recordingDescriptorOffset));\n        }\n\n        assertFalse(verify(out, archiveDir, of(APPLY_CHECKSUM), checksum, epochClock, (file) -> true));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording0, INVALID, 0, 0, NULL_POSITION, 15, NULL_TIMESTAMP,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording1, INVALID, 0, 1024, NULL_POSITION, 16, NULL_TIMESTAMP,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording2, INVALID, 0, TERM_LENGTH * 3 + 96, NULL_POSITION,\n                17, NULL_TIMESTAMP, 0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording3, INVALID, 0, 7 * TERM_LENGTH + 96, 7 * TERM_LENGTH + 128,\n                18, NULL_TIMESTAMP, 7, 13, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording4, INVALID, 0, 21 * TERM_LENGTH + (TERM_LENGTH - 64),\n                22 * TERM_LENGTH + 992, 19, 1, -25, 7, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording51, VALID, -940881948, 0, 64 + PAGE_SIZE, 20, 777,\n                0, 20, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording52, INVALID, 0, 0, NULL_POSITION, 21, NULL_TIMESTAMP,\n                0, 52, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording53, INVALID, 0, 0, NULL_POSITION, 22, NULL_TIMESTAMP,\n                0, 53, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording6, VALID, -175549265, 352, 960, 23, 100, 0, 6, \"ch2\", \"src2\");\n        }\n\n        Mockito.verify(out, times(24)).println(any(String.class));\n    }\n\n    @Test\n    void checksumRecordingLastSegmentFile()\n    {\n        checksumRecording(out, archiveDir, validRecording3, false, crc32(), epochClock);\n\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording3, of(APPLY_CHECKSUM), crc32(), epochClock, (file) -> false));\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording3, VALID, 963969455, 7 * TERM_LENGTH + 96, 11 * TERM_LENGTH + 320,\n                18, 100, 7, 13, \"ch2\", \"src2\");\n        }\n\n        verifyRecording(\n            out, archiveDir, validRecording3, allOf(VerifyOption.class), crc32(), epochClock, (file) -> false);\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording3, INVALID, 963969455, 7 * TERM_LENGTH + 96, 11 * TERM_LENGTH + 320,\n                18, 100, 7, 13, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void checksumRecordingAllSegmentFiles()\n    {\n        checksumRecording(out, archiveDir, validRecording3, true, crc32(), epochClock);\n\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording3, allOf(VerifyOption.class), crc32(), epochClock, (file) -> false));\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording3, VALID, 963969455, 7 * TERM_LENGTH + 96, 11 * TERM_LENGTH + 320,\n                18, 100, 7, 13, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void checksumLastSegmentFile()\n    {\n        checksum(out, archiveDir, false, crc32(), epochClock);\n\n        assertFalse(verify(out, archiveDir, allOf(VerifyOption.class), crc32(), epochClock, (file) -> false));\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording0, VALID, 356725588, 0, TERM_LENGTH + 64, 15, 100,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording1, VALID, -1571032591, 1024, 1024, 16, 200,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording2, VALID, 114203747, TERM_LENGTH * 3 + 96, TERM_LENGTH * 3 + 96,\n                17, 300, 0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording3, INVALID, 963969455, 7 * TERM_LENGTH + 96, 7 * TERM_LENGTH + 128,\n                18, NULL_TIMESTAMP, 7, 13, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording4, INVALID, 162247708, 21 * TERM_LENGTH + (TERM_LENGTH - 64),\n                22 * TERM_LENGTH + 992, 19, 1, -25, 7, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording51, VALID, -940881948, 0, 64 + PAGE_SIZE, 20, 777,\n                0, 20, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording6, VALID, -175549265, 352, 960, 23, 600, 0, 6, \"ch2\", \"src2\");\n        }\n    }\n\n    @Test\n    void checksumAllSegmentFile()\n    {\n        checksum(out, archiveDir, true, crc32(), epochClock);\n\n        assertFalse(verify(out, archiveDir, allOf(VerifyOption.class), crc32(), epochClock, (file) -> false));\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecording(catalog, validRecording0, VALID, 356725588, 0, TERM_LENGTH + 64, 15, 100,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording1, VALID, -1571032591, 1024, 1024, 16, 200,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording2, VALID, 114203747, TERM_LENGTH * 3 + 96, TERM_LENGTH * 3 + 96,\n                17, 300, 0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording3, VALID, 963969455, 7 * TERM_LENGTH + 96, 11 * TERM_LENGTH + 320,\n                18, 400, 7, 13, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording4, INVALID, 162247708, 21 * TERM_LENGTH + (TERM_LENGTH - 64),\n                22 * TERM_LENGTH + 992, 19, 1, -25, 7, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording51, VALID, -940881948, 0, 64 + PAGE_SIZE, 20, 777,\n                0, 20, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording6, VALID, -175549265, 352, 960, 23, 700, 0, 6, \"ch2\", \"src2\");\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"verifyChecksumClassValidation\")\n    void verifyWithChecksumFlagThrowsIllegalArgumentExceptionIfClassNameNotSpecified(final String[] args)\n    {\n        assertChecksumClassNameRequired(args);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"checksumClassValidation\")\n    void checksumThrowsIllegalArgumentExceptionIfClassNameNotSpecified(final String[] args)\n    {\n        assertChecksumClassNameRequired(args);\n    }\n\n    @Test\n    void verifyWithoutChecksumClassNameShouldNotVerifyChecksums()\n    {\n        assertFalse(verify(out, archiveDir, emptySet(), null, (file) -> true));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecordingState(catalog, validRecording4, VALID);\n        }\n    }\n\n    @Test\n    void verifyShouldNotMarkRecordingAsValidIfNoSegmentFilesAreAttached()\n    {\n        final long recordingId = validRecording4;\n        final ArrayList<String> segmentFiles = listSegmentFiles(archiveDir, recordingId);\n        for (final String segmentFile : segmentFiles)\n        {\n            IoUtil.deleteIfExists(new File(archiveDir, segmentFile));\n        }\n\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, null, null))\n        {\n            assertTrue(catalog.changeState(recordingId, INVALID));\n        }\n\n        assertFalse(verify(out, archiveDir, emptySet(), null, (file) -> true));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecordingState(catalog, recordingId, INVALID);\n        }\n    }\n\n    @Test\n    void verifyRecordingWithoutChecksumClassNameShouldNotVerifyChecksums()\n    {\n        assertTrue(verifyRecording(\n            out, archiveDir, validRecording4, emptySet(), null, (file) -> true));\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertRecordingState(catalog, validRecording4, VALID);\n        }\n    }\n\n    @Test\n    void verifyThrowsIllegalArgumentExceptionIfApplyChecksumIsSetWithoutChecksumClassName()\n    {\n        assertChecksumClassNameValidation(() -> verify(out, archiveDir, of(APPLY_CHECKSUM), null, (file) -> true));\n    }\n\n    @Test\n    void verifyRecordingThrowsIllegalArgumentExceptionIfApplyChecksumIsSetWithoutChecksumClassName()\n    {\n        assertChecksumClassNameValidation(\n            () -> verifyRecording(out, archiveDir, validRecording4, of(APPLY_CHECKSUM), null, (file) -> true));\n    }\n\n    @Test\n    void compactDeletesRecordingsInStateInvalidAndDeletedAndDeletesTheCorrespondingSegmentFiles()\n    {\n        final File catalogFile = new File(archiveDir, CATALOG_FILE_NAME);\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, null, null))\n        {\n            assertTrue(catalog.changeState(validRecording1, NULL_VAL));\n            assertTrue(catalog.changeState(validRecording3, INVALID));\n            assertTrue(catalog.changeState(validRecording6, DELETED));\n        }\n\n        final List<String> segmentFiles = new ArrayList<>();\n        segmentFiles.addAll(listSegmentFiles(archiveDir, validRecording1));\n        segmentFiles.addAll(listSegmentFiles(archiveDir, validRecording3));\n        segmentFiles.addAll(listSegmentFiles(archiveDir, validRecording6));\n\n        assertTrue(segmentFiles.stream().allMatch((file) -> new File(archiveDir, file).exists()),\n            \"Non-existing segment files\");\n\n        final long fileLengthBeforeCompact = catalogFile.length();\n        compact(out, archiveDir, epochClock);\n        final long fileLengthAfterCompact = catalogFile.length();\n        assertTrue(fileLengthAfterCompact < fileLengthBeforeCompact);\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertEquals(catalog.capacity(), fileLengthAfterCompact);\n            assertNoRecording(catalog, validRecording1);\n            assertNoRecording(catalog, validRecording3);\n            assertNoRecording(catalog, validRecording6);\n\n            assertEquals(21, catalog.entryCount());\n            assertRecording(catalog, validRecording0, VALID, 0, 0, NULL_POSITION, 15, NULL_TIMESTAMP,\n                0, 2, \"ch2\", \"src2\");\n            assertRecording(catalog, validRecording51, VALID, 0, 0, 64 + PAGE_SIZE, 20, 777,\n                0, 20, \"ch2\", \"src2\");\n        }\n\n        assertTrue(segmentFiles.stream().noneMatch(file -> new File(archiveDir, file).exists()),\n            \"Segment files not deleted\");\n        Mockito.verify(out).println(\"Compaction result: deleted 3 records and reclaimed 576 bytes\");\n    }\n\n    @Test\n    void capacityReturnsCurrentCapacityInBytesOfTheCatalog()\n    {\n        final long capacity;\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            capacity = catalog.capacity();\n        }\n\n        assertEquals(capacity, capacity(archiveDir));\n    }\n\n    @Test\n    void capacityIncreasesCapacityOfTheCatalog()\n    {\n        final long capacity;\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            capacity = catalog.capacity();\n        }\n\n        assertEquals(capacity * 2, capacity(archiveDir, capacity * 2));\n    }\n\n    @Test\n    void deleteOrphanedSegmentsDeletesSegmentFilesForAllRecordings() throws IOException\n    {\n        final long rec1;\n        final long rec2;\n        try (Catalog catalog = new Catalog(archiveDir, epochClock, 1024, true, null, null))\n        {\n            rec1 = catalog.addNewRecording(0, NULL_POSITION, NULL_TIMESTAMP, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 42, 5, \"some ch\", \"some ch\", \"rec1\");\n\n            rec2 = catalog.addNewRecording(1_000_000, 1024 * 1024 * 1024, NULL_TIMESTAMP, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch2\", \"ch2\", \"rec2\");\n            assertTrue(catalog.changeState(rec2, INVALID));\n        }\n\n        final File file11 = createFile(segmentFileName(rec1, -1));\n        final File file12 = createFile(segmentFileName(rec1, 0));\n        final File file13 = createFile(segmentFileName(rec1, Long.MAX_VALUE));\n        final File file14 = createFile(rec1 + \"-will-be-deleted.rec\");\n        final File file15 = createFile(rec1 + \"-will-be-skipped.txt\");\n        final File file16 = createFile(rec1 + \"-.rec\");\n        final File file17 = createFile(rec1 + \"invalid_file_name.rec\");\n\n        final File file21 = createFile(segmentFileName(rec2, 0));\n        final File file22 = createFile(segmentFileName(\n            rec2, segmentFileBasePosition(1_000_000, 1_000_000, TERM_LENGTH, SEGMENT_LENGTH)));\n        final File file23 = createFile(segmentFileName(\n            rec2, segmentFileBasePosition(1_000_000, 5_000_000, TERM_LENGTH, SEGMENT_LENGTH)));\n        final File file24 = createFile(segmentFileName(\n            rec2, segmentFileBasePosition(1_000_000, 1024 * 1024 * 1024, TERM_LENGTH, SEGMENT_LENGTH)));\n        final File file25 = createFile(segmentFileName(\n            rec2, segmentFileBasePosition(1_000_000, Long.MAX_VALUE, TERM_LENGTH, SEGMENT_LENGTH)));\n\n        deleteOrphanedSegments(out, archiveDir);\n\n        assertFileExists(file12, file13, file15, file17);\n        assertFileDoesNotExist(file11, file14, file16);\n\n        assertFileExists(file22, file23, file24);\n        assertFileDoesNotExist(file21, file25);\n    }\n\n    @ParameterizedTest\n    @EnumSource(RecordingState.class)\n    void deleteOrphanedSegmentsDeletesSegmentFilesOfTargetRecording(final RecordingState state) throws IOException\n    {\n        final long rec1;\n        final long rec2;\n        try (Catalog catalog = new Catalog(archiveDir, epochClock, 1024, true, null, null))\n        {\n            rec1 = catalog.addNewRecording(0, NULL_POSITION, NULL_TIMESTAMP, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 42, 5, \"some ch\", \"some ch\", \"rec1\");\n\n            rec2 = catalog.addNewRecording(1_000_000, 1024 * 1024 * 1024, NULL_TIMESTAMP, NULL_TIMESTAMP, 0,\n                SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 1, 1, \"ch2\", \"ch2\", \"rec2\");\n            assertTrue(catalog.changeState(rec2, state));\n        }\n\n        final File file11 = createFile(segmentFileName(rec1, -1));\n        final File file12 = createFile(segmentFileName(rec1, 0));\n        final File file13 = createFile(segmentFileName(rec1, Long.MAX_VALUE));\n        final File file14 = createFile(rec1 + \"-will-be-deleted.rec\");\n        final File file15 = createFile(rec1 + \"-will-be-skipped.txt\");\n        final File file16 = createFile(rec1 + \"-.rec\");\n        final File file17 = createFile(rec1 + \"invalid_file_name.rec\");\n\n        final File file21 = createFile(segmentFileName(rec2, 0));\n        final File file22 = createFile(segmentFileName(\n            rec2, segmentFileBasePosition(1_000_000, 1_000_000, TERM_LENGTH, SEGMENT_LENGTH)));\n        final File file23 = createFile(segmentFileName(\n            rec2, segmentFileBasePosition(1_000_000, 5_000_000, TERM_LENGTH, SEGMENT_LENGTH)));\n        final File file24 = createFile(segmentFileName(\n            rec2, segmentFileBasePosition(1_000_000, 1024 * 1024 * 1024, TERM_LENGTH, SEGMENT_LENGTH)));\n        final File file25 = createFile(segmentFileName(\n            rec2, segmentFileBasePosition(1_000_000, Long.MAX_VALUE, TERM_LENGTH, SEGMENT_LENGTH)));\n\n        deleteOrphanedSegments(out, archiveDir, epochClock, rec2);\n\n        assertFileExists(file12, file13, file15, file17, file11, file14, file16);\n\n        assertFileExists(file22, file23, file24);\n        assertFileDoesNotExist(file21, file25);\n    }\n\n    @Test\n    void deleteOrphanedSegmentsOfAnUnknownRecording()\n    {\n        final int recordingId = 55555555;\n        final AeronException exception =\n            assertThrowsExactly(AeronException.class, () -> deleteOrphanedSegments(out, archiveDir, recordingId));\n        assertEquals(\"ERROR - no recording found with recordingId: \" + recordingId, exception.getMessage());\n    }\n\n    @Test\n    void markInvalidInvalidatesAnExistingRecording()\n    {\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertTrue(catalog.hasRecording(validRecording3));\n        }\n\n        markRecordingInvalid(out, archiveDir, validRecording3);\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertFalse(catalog.hasRecording(validRecording3));\n            assertRecordingState(catalog, validRecording3, INVALID);\n        }\n    }\n\n    @Test\n    void markInvalidThrowsExceptionIfRecordingIsUnknown()\n    {\n        final int recordingId = 100_000;\n        final AeronException exception =\n            assertThrows(AeronException.class, () -> markRecordingInvalid(out, archiveDir, recordingId));\n        assertEquals(\"ERROR - no recording found with recordingId: \" + recordingId, exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = RecordingState.class, mode = EnumSource.Mode.EXCLUDE, names = { \"VALID\", \"INVALID\" })\n    void markInvalidThrowsExceptionIfRecordingIsNotValid(final RecordingState offendingState)\n    {\n        final long recordingId = validRecording3;\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, null, null))\n        {\n            assertTrue(catalog.changeState(recordingId, offendingState));\n        }\n\n        final AeronException exception =\n            assertThrows(AeronException.class, () -> markRecordingInvalid(out, archiveDir, recordingId));\n        assertEquals(\"ERROR - (recordingId=\" + recordingId + \") state transition \" + offendingState +\n            \" -> INVALID is not allowed\", exception.getMessage());\n    }\n\n    @Test\n    void markValidValidatesAnExistingRecording()\n    {\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, null, null))\n        {\n            assertTrue(catalog.changeState(validRecording6, INVALID));\n            assertRecordingState(catalog, validRecording6, INVALID);\n        }\n\n        markRecordingValid(out, archiveDir, validRecording6);\n\n        try (Catalog catalog = openCatalogReadOnly(archiveDir, epochClock))\n        {\n            assertTrue(catalog.hasRecording(validRecording6));\n        }\n    }\n\n    @Test\n    void markValidThrowsExceptionIfRecordingIsUnknown()\n    {\n        final long recordingId = Long.MIN_VALUE;\n        final AeronException exception =\n            assertThrows(AeronException.class, () -> markRecordingValid(out, archiveDir, recordingId));\n        assertEquals(\"ERROR - no recording found with recordingId: \" + recordingId, exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = RecordingState.class, mode = EnumSource.Mode.EXCLUDE, names = { \"VALID\", \"INVALID\" })\n    void markValidThrowsExceptionIfRecordingIsNotValid(final RecordingState offendingState)\n    {\n        final long recordingId = validRecording3;\n        try (Catalog catalog = openCatalogReadWrite(archiveDir, epochClock, MIN_CAPACITY, null, null))\n        {\n            assertTrue(catalog.changeState(recordingId, offendingState));\n        }\n\n        final AeronException exception =\n            assertThrows(AeronException.class, () -> markRecordingValid(out, archiveDir, recordingId));\n        assertEquals(\"ERROR - (recordingId=\" + recordingId + \") state transition \" + offendingState +\n            \" -> VALID is not allowed\", exception.getMessage());\n    }\n\n    private static List<Arguments> verifyChecksumClassValidation()\n    {\n        final String testDir = validationDir.toAbsolutePath().toString();\n        return Arrays.asList(\n            Arguments.of((Object)new String[]{ testDir, \"verify\", \"-checksum\", \"\\t\" }),\n            Arguments.of((Object)new String[]{ testDir, \"verify\", \"-a\", \"-checksum\", \" \" }),\n            Arguments.of((Object)new String[]{ testDir, \"verify\", \"42\", \"-checksum\", \"\" }),\n            Arguments.of((Object)new String[]{ testDir, \"verify\", \"42\", \"-a\", \"-checksum\", \"\\r\\n\" })\n        );\n    }\n\n    private static List<Arguments> checksumClassValidation()\n    {\n        final String testDir = validationDir.toAbsolutePath().toString();\n        return Arrays.asList(\n            Arguments.of((Object)new String[]{ testDir, \"checksum\", \"\\n\" }),\n            Arguments.of((Object)new String[]{ testDir, \"checksum\", \"\", \"42\" }),\n            Arguments.of((Object)new String[]{ testDir, \"checksum\", \"\\t \", \"-a\" }),\n            Arguments.of((Object)new String[]{ testDir, \"checksum\", \" \", \"42\", \"-a\" })\n        );\n    }\n\n    private void assertChecksumClassNameRequired(final String[] args)\n    {\n        final IllegalArgumentException ex =\n            assertThrows(IllegalArgumentException.class, () -> main(args));\n        assertEquals(\"Checksum class name must be specified!\", ex.getMessage());\n    }\n\n    private void assertChecksumClassNameValidation(final Executable executable)\n    {\n        final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, executable);\n        assertEquals(\"Checksum class name is required when \" + APPLY_CHECKSUM + \" option is specified!\",\n            ex.getMessage());\n    }\n\n    @FunctionalInterface\n    interface SegmentWriter\n    {\n        void write(ByteBuffer byteBuffer, DataHeaderFlyweight dataHeaderFlyweight, FileChannel channel)\n            throws IOException;\n    }\n\n    private File createFile(final String name) throws IOException\n    {\n        final File file = new File(archiveDir, name);\n        assertTrue(file.createNewFile());\n        return file;\n    }\n\n    private void createDirectory(final String name)\n    {\n        final File file = new File(archiveDir, name);\n        assertTrue(file.mkdir());\n    }\n\n    private void writeToSegmentFile(\n        final File file, final int fileLength, final SegmentWriter segmentWriter) throws IOException\n    {\n        final ByteBuffer byteBuffer = allocate(MTU_LENGTH);\n        final DataHeaderFlyweight dataHeaderFlyweight = new DataHeaderFlyweight(byteBuffer);\n        try (FileChannel channel = FileChannel.open(file.toPath(), READ, WRITE))\n        {\n            segmentWriter.write(byteBuffer, dataHeaderFlyweight, channel);\n            final long size = channel.size();\n            if (fileLength != size)\n            {\n                channel.truncate(fileLength);\n                if (size < fileLength)\n                {\n                    byteBuffer.put(0, (byte)0).limit(1).position(0);\n                    channel.write(byteBuffer, fileLength - 1);\n                }\n            }\n        }\n    }\n\n    private void assertRecording(\n        final Catalog catalog, final long recordingId, final CatalogEntryProcessor catalogEntryProcessor)\n    {\n        final MutableBoolean found = new MutableBoolean();\n        catalog\n            .forEach((recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n            {\n                if (recordingId == descriptorDecoder.recordingId())\n                {\n                    found.set(true);\n\n                    catalogEntryProcessor.accept(\n                        recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder);\n                }\n            });\n\n        assertTrue(found.get(), () -> \"recordingId=\" + recordingId + \" was not found\");\n    }\n\n    private void assertRecording(\n        final Catalog catalog,\n        final long recordingId,\n        final RecordingState state,\n        final int checksum,\n        final long startPosition,\n        final long stopPosition,\n        final long startTimestamp,\n        final long stopTimeStamp,\n        final int initialTermId,\n        final int streamId,\n        final String strippedChannel,\n        final String sourceIdentity)\n    {\n        assertRecording(\n            catalog,\n            recordingId,\n            (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n            {\n                assertEquals(state, headerDecoder.state());\n                assertEquals(checksum, headerDecoder.checksum());\n\n                assertEquals(startPosition, descriptorDecoder.startPosition());\n                assertEquals(stopPosition, descriptorDecoder.stopPosition());\n                assertEquals(startTimestamp, descriptorDecoder.startTimestamp());\n                assertEquals(stopTimeStamp, descriptorDecoder.stopTimestamp());\n                assertEquals(initialTermId, descriptorDecoder.initialTermId());\n                assertEquals(MTU_LENGTH, descriptorDecoder.mtuLength());\n                assertEquals(SEGMENT_LENGTH, descriptorDecoder.segmentFileLength());\n                assertEquals(streamId, descriptorDecoder.streamId());\n                assertEquals(strippedChannel, descriptorDecoder.strippedChannel());\n                assertNotNull(descriptorDecoder.originalChannel());\n                assertEquals(sourceIdentity, descriptorDecoder.sourceIdentity());\n            });\n    }\n\n    private void assertRecordingState(final Catalog catalog, final long recordingId, final RecordingState expectedState)\n    {\n        assertRecording(catalog, recordingId,\n            (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n            assertEquals(expectedState, headerDecoder.state()));\n    }\n\n    private void assertNoRecording(final Catalog catalog, final long recordingId)\n    {\n        final MutableBoolean found = new MutableBoolean();\n        catalog\n            .forEach((recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n            {\n                if (recordingId == descriptorDecoder.recordingId())\n                {\n                    found.set(true);\n                }\n            });\n\n        assertFalse(found.get(), () -> \"recordingId=\" + recordingId + \" was found\");\n    }\n\n    private void assertFileExists(final File... files)\n    {\n        for (final File file : files)\n        {\n            assertTrue(file.exists(), () -> file.getName() + \" does not exist\");\n        }\n    }\n\n    private void assertFileDoesNotExist(final File... files)\n    {\n        for (final File file : files)\n        {\n            assertFalse(file.exists(), () -> file.getName() + \" was not deleted\");\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/CatalogIndexTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.Random;\n\nimport static io.aeron.archive.CatalogIndex.DEFAULT_INDEX_SIZE;\nimport static io.aeron.archive.CatalogIndex.NULL_VALUE;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass CatalogIndexTest\n{\n    private final CatalogIndex catalogIndex = new CatalogIndex();\n\n    @Test\n    void defaultIndexCapacityIsTenEntries()\n    {\n        final long[] emptyIndex = new long[20];\n\n        assertArrayEquals(emptyIndex, catalogIndex.index());\n    }\n\n    @Test\n    void sizeReturnsZeroForAnEmptyIndex()\n    {\n        assertEquals(0, catalogIndex.size());\n    }\n\n    @Test\n    void addThrowsIllegalArgumentExceptionIfRecordingIdIsNegative()\n    {\n        assertThrows(IllegalArgumentException.class, () -> catalogIndex.add(-1, 0));\n    }\n\n    @Test\n    void addThrowsIllegalArgumentExceptionIfRecordingOffsetIsNegative()\n    {\n        assertThrows(IllegalArgumentException.class, () -> catalogIndex.add(1024, Integer.MIN_VALUE));\n    }\n\n    @Test\n    void addOneRecording()\n    {\n        final long recordingId = 3;\n        final long recordingOffset = 100;\n\n        catalogIndex.add(recordingId, recordingOffset);\n\n        final long[] expected = new long[20];\n        expected[0] = recordingId;\n        expected[1] = recordingOffset;\n        assertArrayEquals(expected, catalogIndex.index());\n        assertEquals(1, catalogIndex.size());\n    }\n\n    @Test\n    void addAppendsToTheEndOfTheIndex()\n    {\n        final long recordingId1 = 6;\n        final long recordingOffset1 = 50;\n        final long recordingId2 = 21;\n        final long recordingOffset2 = 64;\n\n        catalogIndex.add(recordingId1, recordingOffset1);\n        catalogIndex.add(recordingId2, recordingOffset2);\n\n        final long[] expected = new long[20];\n        expected[0] = recordingId1;\n        expected[1] = recordingOffset1;\n        expected[2] = recordingId2;\n        expected[3] = recordingOffset2;\n        assertArrayEquals(expected, catalogIndex.index());\n        assertEquals(2, catalogIndex.size());\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 5, 100 })\n    void addAppendsThrowsIllegalArgumentExceptionIfNewRecordingIdIsLessThanOrEqualToTheExistingRecordingId(\n        final long recordingId2)\n    {\n        catalogIndex.add(100, 0);\n\n        final IllegalArgumentException exception =\n            assertThrows(IllegalArgumentException.class, () -> catalogIndex.add(recordingId2, 200));\n        assertEquals(\"recordingId \" + recordingId2 + \" is less than or equal to the last recordingId 100\",\n            exception.getMessage());\n    }\n\n    @Test\n    void addExpandsIndexWhenFull()\n    {\n        final long[] expectedIndex = new long[(DEFAULT_INDEX_SIZE + (DEFAULT_INDEX_SIZE >> 1)) << 1];\n\n        int pos = 0;\n        for (int i = 0; i < DEFAULT_INDEX_SIZE; i++, pos += 2)\n        {\n            expectedIndex[pos] = i;\n            expectedIndex[pos + 1] = i;\n            catalogIndex.add(i, i);\n        }\n        expectedIndex[pos] = Long.MAX_VALUE;\n        expectedIndex[pos + 1] = Long.MAX_VALUE;\n        catalogIndex.add(Long.MAX_VALUE, Long.MAX_VALUE);\n\n        assertArrayEquals(expectedIndex, catalogIndex.index());\n        assertEquals(DEFAULT_INDEX_SIZE + 1, catalogIndex.size());\n    }\n\n    @Test\n    void getThrowsIllegalArgumentExceptionIfRecordingIdIsNegative()\n    {\n        assertThrows(IllegalArgumentException.class, () -> catalogIndex.recordingOffset(-100));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 0, 1, DEFAULT_INDEX_SIZE, DEFAULT_INDEX_SIZE << 1, 100, Long.MAX_VALUE })\n    void getReturnsNullValueOnAnEmptyIndex(final long recordingId)\n    {\n        assertEquals(NULL_VALUE, catalogIndex.recordingOffset(recordingId));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 0, 199, 201, Long.MAX_VALUE })\n    void getReturnsNullValueIfRecordingIdIsNotFoundSingleEntry(final long recordingId)\n    {\n        catalogIndex.add(200, 0);\n\n        assertEquals(NULL_VALUE, catalogIndex.recordingOffset(recordingId));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 9, 101, 1_000_000_000, 4002662252L, Long.MAX_VALUE })\n    void getReturnsNullValueIfRecordingIdIsNotFoundMultipleEntries(final long recordingId)\n    {\n        for (int i = 10; i < 100; i++)\n        {\n            catalogIndex.add(i, i);\n        }\n\n        assertEquals(NULL_VALUE, catalogIndex.recordingOffset(recordingId));\n    }\n\n    @ParameterizedTest\n    @CsvSource(value = { \"0,0\", \"1,1\", \"2,3\", \"100,777\", \"1000,2000\", \"1001,2002\" })\n    void getReturnsOffsetAssociatedWithRecordingId(final long recordingId, final long recordingOffset)\n    {\n        catalogIndex.add(0, 0);\n        catalogIndex.add(1, 1);\n        catalogIndex.add(2, 3);\n        catalogIndex.add(100, 777);\n        catalogIndex.add(1000, 2_000);\n        catalogIndex.add(1001, 2_002);\n\n        assertEquals(recordingOffset, catalogIndex.recordingOffset(recordingId));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 0, 100, Long.MAX_VALUE })\n    void getSingleElement(final long recordingId)\n    {\n        catalogIndex.add(recordingId, recordingId * 3);\n\n        assertEquals(recordingId * 3, catalogIndex.recordingOffset(recordingId));\n    }\n\n    @Test\n    void getFirstElement()\n    {\n        catalogIndex.add(0, 10);\n        catalogIndex.add(1, 20);\n        catalogIndex.add(2, 30);\n\n        assertEquals(10, catalogIndex.recordingOffset(0));\n    }\n\n    @Test\n    void getLastElement()\n    {\n        catalogIndex.add(1, 10);\n        catalogIndex.add(5, 20);\n        catalogIndex.add(12, 30);\n\n        assertEquals(30, catalogIndex.recordingOffset(12));\n    }\n\n    @Test\n    void getLastElementWhenIndexIsFull()\n    {\n        final long[] values = new Random(8327434)\n            .longs(0, Long.MAX_VALUE)\n            .distinct()\n            .limit(DEFAULT_INDEX_SIZE)\n            .sorted()\n            .toArray();\n\n        for (int i = 0; i < DEFAULT_INDEX_SIZE; i++)\n        {\n            catalogIndex.add(values[i], i + 1);\n        }\n\n        final long lastValue = values[values.length - 1];\n        assertEquals(DEFAULT_INDEX_SIZE, catalogIndex.recordingOffset(lastValue));\n    }\n\n    @Test\n    void removeThrowsIllegalArgumentExceptionIfNegativeRecordingIdIsProvided()\n    {\n        assertThrows(IllegalArgumentException.class, () -> catalogIndex.remove(-1));\n    }\n\n    @Test\n    void removeReturnsNullValueWhenIndexIsEmpty()\n    {\n        assertEquals(NULL_VALUE, catalogIndex.remove(1));\n    }\n\n    @Test\n    void removeReturnsNullValueWhenNonExistingRecordingIdIsProvided()\n    {\n        catalogIndex.add(1, 0);\n        catalogIndex.add(20, 10);\n\n        assertEquals(NULL_VALUE, catalogIndex.remove(7));\n\n        assertEquals(2, catalogIndex.size());\n    }\n\n    @Test\n    void removeTheOnlyEntryFromTheIndex()\n    {\n        final long recordingId = 0;\n        final long recordingOffset = 1024;\n        catalogIndex.add(recordingId, recordingOffset);\n\n        assertEquals(recordingOffset, catalogIndex.remove(recordingId));\n        assertEquals(0, catalogIndex.size());\n\n        assertEquals(NULL_VALUE, catalogIndex.recordingOffset(recordingId));\n    }\n\n    @Test\n    void removeFirstElement()\n    {\n        catalogIndex.add(0, 500);\n        catalogIndex.add(1, 1000);\n        catalogIndex.add(2, 1500);\n\n        assertEquals(500, catalogIndex.remove(0));\n        assertEquals(2, catalogIndex.size());\n\n        assertEquals(NULL_VALUE, catalogIndex.recordingOffset(0));\n        assertEquals(1000, catalogIndex.recordingOffset(1));\n        assertEquals(1500, catalogIndex.recordingOffset(2));\n    }\n\n    @Test\n    void removeLastElement()\n    {\n        catalogIndex.add(0, 500);\n        catalogIndex.add(1, 1000);\n        catalogIndex.add(2, 1500);\n\n        assertEquals(1500, catalogIndex.remove(2));\n        assertEquals(2, catalogIndex.size());\n\n        assertEquals(NULL_VALUE, catalogIndex.recordingOffset(2));\n        assertEquals(500, catalogIndex.recordingOffset(0));\n        assertEquals(1000, catalogIndex.recordingOffset(1));\n    }\n\n    @Test\n    void removeMiddleElement()\n    {\n        catalogIndex.add(0, 500);\n        catalogIndex.add(10, 1000);\n        catalogIndex.add(20, 1500);\n        catalogIndex.add(30, 7777);\n\n        assertEquals(1000, catalogIndex.remove(10));\n        assertEquals(3, catalogIndex.size());\n\n        assertEquals(NULL_VALUE, catalogIndex.recordingOffset(10));\n        assertEquals(500, catalogIndex.recordingOffset(0));\n        assertEquals(1500, catalogIndex.recordingOffset(20));\n        assertEquals(7777, catalogIndex.recordingOffset(30));\n    }\n\n    @Test\n    void removeLastElementFromTheFullIndex()\n    {\n        for (int i = 1; i <= DEFAULT_INDEX_SIZE; i++)\n        {\n            catalogIndex.add(i, i);\n        }\n\n        assertEquals(DEFAULT_INDEX_SIZE, catalogIndex.remove(DEFAULT_INDEX_SIZE));\n        assertEquals(DEFAULT_INDEX_SIZE - 1, catalogIndex.size());\n\n        assertEquals(NULL_VALUE, catalogIndex.recordingOffset(DEFAULT_INDEX_SIZE));\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/CatalogTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.CatalogHeaderEncoder;\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderDecoder;\nimport io.aeron.archive.codecs.RecordingState;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.BufferUtil;\nimport org.agrona.IoUtil;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.util.stream.Stream;\n\nimport static io.aeron.archive.Archive.Configuration.CATALOG_FILE_NAME;\nimport static io.aeron.archive.Archive.Configuration.FILE_IO_MAX_LENGTH_DEFAULT;\nimport static io.aeron.archive.Archive.segmentFileName;\nimport static io.aeron.archive.Catalog.*;\nimport static io.aeron.archive.checksum.Checksums.crc32;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.AeronArchive.NULL_TIMESTAMP;\nimport static io.aeron.archive.codecs.RecordingState.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static java.nio.ByteBuffer.allocate;\nimport static java.nio.channels.FileChannel.MapMode.READ_WRITE;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static java.nio.file.StandardOpenOption.*;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass CatalogTest\n{\n    private static final long CAPACITY = 1024;\n    private static final int TERM_LENGTH = 2 * PAGE_SIZE;\n    private static final int SEGMENT_LENGTH = 2 * TERM_LENGTH;\n    private static final int MTU_LENGTH = 1024;\n\n    private final UnsafeBuffer segmentFileBuffer = new UnsafeBuffer(allocate(FILE_IO_MAX_LENGTH_DEFAULT));\n    private final UnsafeBuffer unsafeBuffer = new UnsafeBuffer();\n    private final RecordingDescriptorHeaderDecoder recordingDescriptorHeaderDecoder =\n        new RecordingDescriptorHeaderDecoder();\n    private final RecordingDescriptorDecoder recordingDescriptorDecoder = new RecordingDescriptorDecoder();\n    private final File archiveDir = ArchiveTests.makeTestDirectory();\n\n    private long currentTimeMs = 1;\n    private final EpochClock clock = () -> currentTimeMs;\n\n    private long recordingOneId;\n    private long recordingTwoId;\n    private long recordingThreeId;\n\n    @BeforeEach\n    void before()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            recordingOneId = catalog.addNewRecording(\n                0L, 0L, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 6, 1, \"channelG\", \"channelG?tag=f\", \"sourceA\");\n            recordingTwoId = catalog.addNewRecording(\n                0L, 0L, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 7, 2, \"channelH\", \"channelH?tag=f\", \"sourceV\");\n            recordingThreeId = catalog.addNewRecording(\n                0L,\n                0L,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH, MTU_LENGTH,\n                8,\n                3,\n                \"channelThatIsVeryLongAndShouldNotBeTruncated\",\n                \"channelThatIsVeryLongAndShouldNotBeTruncated?tag=f\",\n                \"source can also be a very very very long String and it will not be truncated even \" +\n                    \"if gets very very long\");\n        }\n    }\n\n    @AfterEach\n    void after()\n    {\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    void shouldUse1KBAlignmentWhenReadingFromOldCatalogFile() throws IOException\n    {\n        final int oldRecordLength = 1024;\n\n        final File catalogFile = new File(archiveDir, CATALOG_FILE_NAME);\n        IoUtil.deleteIfExists(catalogFile);\n        Files.write(catalogFile.toPath(), new byte[oldRecordLength], CREATE_NEW);\n\n        try (Catalog catalog = new Catalog(archiveDir, clock, MIN_CAPACITY, true, null, (version) -> {}))\n        {\n            assertEquals(oldRecordLength, catalog.alignment());\n        }\n    }\n\n    @Test\n    void shouldComputeNextRecordingIdIfValueInHeaderIsZero() throws IOException\n    {\n        setNextRecordingId(0);\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            assertEquals(recordingThreeId + 1, catalog.nextRecordingId());\n        }\n    }\n\n    @Test\n    void shouldThrowArchiveExceptionIfNextRecordingIdIsSmallerThanTheActualLastRecordInTheCatalog() throws IOException\n    {\n        setNextRecordingId(recordingTwoId);\n\n        final ArchiveException exception = assertThrows(ArchiveException.class,\n            () -> new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer));\n        assertEquals(\n            \"ERROR - invalid nextRecordingId: expected value greater or equal to \" + (recordingThreeId + 1) +\n            \", was \" + recordingTwoId,\n            exception.getMessage());\n    }\n\n    @Test\n    void shouldThrowArchiveExceptionIfNextRecordingIdIsInvalidWriteableCatalog() throws IOException\n    {\n        setNextRecordingId(-1);\n\n        final ArchiveException exception = assertThrows(ArchiveException.class,\n            () -> new Catalog(archiveDir, clock, MIN_CAPACITY, true, null, null));\n        assertEquals(\n            \"ERROR - invalid nextRecordingId: expected value greater or equal to \" + (recordingThreeId + 1) +\n            \", was -1\",\n            exception.getMessage());\n    }\n\n    @Test\n    void shouldNotThrowArchiveExceptionWhenNextRecordingIdIsInvalidIfCatalogIsReadOnly()\n        throws IOException\n    {\n        setNextRecordingId(recordingTwoId);\n\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            assertEquals(recordingTwoId, catalog.nextRecordingId());\n        }\n    }\n\n    @Test\n    void shouldReadNextRecordingIdFromCatalogHeader() throws IOException\n    {\n        final long nextRecordingId = 10101010;\n        setNextRecordingId(nextRecordingId);\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            assertEquals(nextRecordingId, catalog.nextRecordingId());\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { -1, 0, MIN_CAPACITY - 1 })\n    void shouldThrowIllegalArgumentExceptionIfCatalogCapacityIsLessThanMinimalCapacity(final long capacity)\n    {\n        final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n            () -> new Catalog(archiveDir, null, 0, capacity, clock, null, segmentFileBuffer));\n\n        assertEquals(\n            \"Invalid catalog capacity provided: expected value >= \" + MIN_CAPACITY + \", got \" + capacity,\n            exception.getMessage());\n    }\n\n    @Test\n    void shouldReloadExistingIndex()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            verifyRecordingForId(\n                catalog,\n                recordingOneId,\n                160,\n                0L,\n                NULL_POSITION,\n                0L,\n                NULL_TIMESTAMP,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                6,\n                1,\n                \"channelG\",\n                \"channelG?tag=f\",\n                \"sourceA\");\n\n            verifyRecordingForId(\n                catalog,\n                recordingTwoId,\n                160,\n                0L,\n                NULL_POSITION,\n                0L,\n                NULL_TIMESTAMP,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                7,\n                2,\n                \"channelH\",\n                \"channelH?tag=f\",\n                \"sourceV\");\n\n            verifyRecordingForId(\n                catalog,\n                recordingThreeId,\n                352,\n                0L,\n                NULL_POSITION,\n                0L,\n                NULL_TIMESTAMP,\n                0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 8,\n                3,\n                \"channelThatIsVeryLongAndShouldNotBeTruncated\",\n                \"channelThatIsVeryLongAndShouldNotBeTruncated?tag=f\",\n                \"source can also be a very very very long String and it will not be truncated even if gets \" +\n                \"very very long\");\n        }\n    }\n\n    @Test\n    void shouldAppendToExistingIndex()\n    {\n        final long newRecordingId;\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, () -> 3L, null, segmentFileBuffer))\n        {\n            newRecordingId = catalog.addNewRecording(\n                32,\n                128,\n                21,\n                42,\n                5,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                9,\n                4,\n                \"channelJ\",\n                \"channelJ?tag=f\",\n                \"sourceN\");\n        }\n\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            verifyRecordingForId(\n                catalog,\n                recordingOneId,\n                160,\n                0L,\n                0L, // updated from NULL_POSITION when Catalog was created for write\n                0L,\n                3L, // updated from NULL_TIMESTAMP when Catalog was created for write\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                6,\n                1,\n                \"channelG\",\n                \"channelG?tag=f\",\n                \"sourceA\");\n\n            verifyRecordingForId(\n                catalog,\n                newRecordingId,\n                160,\n                32,\n                128,\n                21,\n                42,\n                5,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                9,\n                4,\n                \"channelJ\",\n                \"channelJ?tag=f\",\n                \"sourceN\");\n        }\n    }\n\n    @Test\n    void shouldAllowMultipleInstancesForSameStream()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            assertEquals(CAPACITY, catalog.capacity());\n            final long newRecordingId = newRecording();\n            assertNotEquals(recordingOneId, newRecordingId);\n        }\n    }\n\n    @Test\n    void shouldIncreaseCapacity()\n    {\n        final long newCapacity = CAPACITY * 2;\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, newCapacity, clock, null, segmentFileBuffer))\n        {\n            assertEquals(newCapacity, catalog.capacity());\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { MIN_CAPACITY, CAPACITY - 1, CAPACITY })\n    void shouldNotDecreaseCapacity(final long newCapacity)\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, newCapacity, clock, null, segmentFileBuffer))\n        {\n            assertEquals(CAPACITY, catalog.capacity());\n        }\n    }\n\n    @Test\n    void shouldFixTimestampForEmptyRecordingAfterFailure()\n    {\n        final long newRecordingId = newRecording();\n\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            final CatalogEntryProcessor entryProcessor =\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                assertEquals(NULL_TIMESTAMP, descriptorDecoder.stopTimestamp());\n\n            assertTrue(catalog.forEntry(newRecordingId, entryProcessor));\n        }\n\n        currentTimeMs = 42L;\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            final CatalogEntryProcessor entryProcessor =\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                assertEquals(42L, descriptorDecoder.stopTimestamp());\n\n            assertTrue(catalog.forEntry(newRecordingId, entryProcessor));\n        }\n    }\n\n    @Test\n    void shouldFixTimestampAndPositionAfterFailureSamePage() throws Exception\n    {\n        final long newRecordingId = newRecording();\n\n        assertTrue(new File(archiveDir, segmentFileName(newRecordingId, 0)).createNewFile());\n        assertTrue(new File(archiveDir, segmentFileName(newRecordingId, SEGMENT_LENGTH)).createNewFile());\n        assertTrue(new File(archiveDir, segmentFileName(newRecordingId, 2 * SEGMENT_LENGTH)).createNewFile());\n        final File segmentFile = new File(archiveDir, segmentFileName(newRecordingId, 3 * SEGMENT_LENGTH));\n\n        try (FileChannel log = FileChannel.open(segmentFile.toPath(), READ, WRITE, CREATE))\n        {\n            final ByteBuffer bb = allocate(HEADER_LENGTH);\n            final DataHeaderFlyweight flyweight = new DataHeaderFlyweight(bb);\n            flyweight.frameLength(1024);\n            log.write(bb);\n\n            bb.clear();\n            flyweight.frameLength(128);\n            log.write(bb, 1024);\n\n            bb.clear();\n            flyweight.frameLength(0);\n            log.write(bb, 1024 + 128);\n        }\n\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            assertTrue(catalog.forEntry(\n                newRecordingId,\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                {\n                    assertEquals(NULL_TIMESTAMP, descriptorDecoder.stopTimestamp());\n                    assertEquals(NULL_POSITION, descriptorDecoder.stopPosition());\n                }));\n        }\n\n        currentTimeMs = 42L;\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            assertTrue(catalog.forEntry(\n                newRecordingId,\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                {\n                    assertEquals(42L, descriptorDecoder.stopTimestamp());\n                    assertEquals(SEGMENT_LENGTH * 3 + 1024L + 128L, descriptorDecoder.stopPosition());\n                }));\n        }\n    }\n\n    @Test\n    void shouldThrowExceptionAfterFailureOnPageStraddle() throws Exception\n    {\n        final long newRecordingId = newRecording();\n        final File segmentFile = new File(archiveDir, segmentFileName(newRecordingId, 0));\n        try (FileChannel log = FileChannel.open(segmentFile.toPath(), READ, WRITE, CREATE))\n        {\n            final ByteBuffer bb = allocate(HEADER_LENGTH);\n            final DataHeaderFlyweight flyweight = new DataHeaderFlyweight(bb);\n            flyweight.frameLength(PAGE_SIZE - 128);\n            log.write(bb);\n\n            bb.clear();\n            flyweight.frameLength(256);\n            log.write(bb, PAGE_SIZE - 128);\n\n            bb.clear();\n            bb.put(0, (byte)0).limit(1).position(0);\n            log.write(bb, PAGE_SIZE + 127);\n        }\n\n        final ArchiveException exception = assertThrows(\n            ArchiveException.class,\n            () ->\n            {\n                final Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer);\n                catalog.close();\n            });\n        assertThat(exception.getMessage(), containsString(segmentFile.getAbsolutePath()));\n    }\n\n    @Test\n    void shouldUseChecksumToVerifyLastFragmentAfterPageStraddle() throws Exception\n    {\n        final long newRecordingId = newRecording();\n        final File segmentFile = new File(archiveDir, segmentFileName(newRecordingId, 0));\n        try (FileChannel log = FileChannel.open(segmentFile.toPath(), READ, WRITE, CREATE))\n        {\n            final ByteBuffer bb = allocate(HEADER_LENGTH);\n            final DataHeaderFlyweight flyweight = new DataHeaderFlyweight(bb);\n            flyweight.frameLength(PAGE_SIZE - 128);\n            log.write(bb);\n\n            bb.clear();\n            flyweight.frameLength(256);\n            flyweight.sessionId(1025596259);\n            log.write(bb, PAGE_SIZE - 128);\n\n            bb.clear();\n            bb.put(0, (byte)0).limit(1).position(0);\n            log.write(bb, PAGE_SIZE + 127);\n        }\n\n        currentTimeMs = 42L;\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, crc32(), null))\n        {\n            assertTrue(catalog.forEntry(\n                newRecordingId,\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                {\n                    assertEquals(42L, descriptorDecoder.stopTimestamp());\n                    assertEquals(PAGE_SIZE + 128, descriptorDecoder.stopPosition());\n                }));\n        }\n    }\n\n    private long newRecording()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            return catalog.addNewRecording(\n                0L,\n                0L,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                6,\n                1,\n                \"channelG\",\n                \"channelG?tag=f\",\n                \"sourceA\");\n        }\n    }\n\n    @Test\n    void shouldFixTimestampAndPositionAfterFailureFullSegment() throws Exception\n    {\n        final long newRecordingId = newRecording();\n\n        final File segmentFile = new File(archiveDir, segmentFileName(newRecordingId, 0));\n        try (FileChannel log = FileChannel.open(segmentFile.toPath(), READ, WRITE, CREATE))\n        {\n            final ByteBuffer bb = allocate(HEADER_LENGTH);\n            final DataHeaderFlyweight flyweight = new DataHeaderFlyweight(bb);\n            flyweight.frameLength(SEGMENT_LENGTH - 128);\n            log.write(bb);\n\n            bb.clear();\n            flyweight.frameLength(128);\n            log.write(bb, SEGMENT_LENGTH - 128);\n            log.truncate(SEGMENT_LENGTH);\n        }\n\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            assertTrue(catalog.forEntry(\n                newRecordingId,\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                {\n                    assertThat(descriptorDecoder.stopTimestamp(), is(NULL_TIMESTAMP));\n                    assertThat(descriptorDecoder.stopPosition(), is(NULL_POSITION));\n                }));\n        }\n\n        currentTimeMs = 42L;\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            assertTrue(catalog.forEntry(\n                newRecordingId,\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                {\n                    assertThat(descriptorDecoder.stopTimestamp(), is(42L));\n                    assertThat(descriptorDecoder.stopPosition(), is((long)SEGMENT_LENGTH));\n                }));\n        }\n    }\n\n    @Test\n    void shouldNotGrowCatalogWhenReachingFullIfRecordingsFit()\n    {\n        after();\n        final File archiveDir = ArchiveTests.makeTestDirectory();\n        final long capacity = 384 + CatalogHeaderEncoder.BLOCK_LENGTH;\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, capacity, clock, null, segmentFileBuffer))\n        {\n            for (int i = 0; i < 2; i++)\n            {\n                recordingOneId = catalog.addNewRecording(\n                    0L,\n                    0L,\n                    0,\n                    SEGMENT_LENGTH,\n                    TERM_LENGTH,\n                    MTU_LENGTH,\n                    6,\n                    1,\n                    \"channelG\",\n                    \"channelG?tag=f\",\n                    \"sourceA\");\n            }\n        }\n\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            assertEquals(2, catalog.entryCount());\n            assertEquals(capacity, catalog.capacity());\n        }\n    }\n\n    @Test\n    void shouldGrowCatalogWhenMaxCapacityReached()\n    {\n        after();\n        final File archiveDir = ArchiveTests.makeTestDirectory();\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, MIN_CAPACITY, clock, null, segmentFileBuffer))\n        {\n            for (int i = 0; i < 4; i++)\n            {\n                recordingOneId = catalog.addNewRecording(\n                    0L,\n                    0L,\n                    0,\n                    SEGMENT_LENGTH,\n                    TERM_LENGTH,\n                    MTU_LENGTH,\n                    6,\n                    1,\n                    \"channelG\",\n                    \"channelG?tag=f\",\n                    \"sourceA\");\n            }\n        }\n\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            assertEquals(4, catalog.entryCount());\n            assertEquals(819, catalog.capacity());\n        }\n    }\n\n    @Test\n    void growCatalogThrowsArchiveExceptionIfCatalogIsFull()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            final ArchiveException exception = assertThrows(\n                ArchiveException.class, () -> catalog.growCatalog(CAPACITY, (int)(CAPACITY + 1)));\n            assertEquals(\"ERROR - catalog is full, max capacity reached: \" + CAPACITY, exception.getMessage());\n        }\n    }\n\n    @Test\n    void growCatalogThrowsArchiveExceptionIfRecordingIsTooBig()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            final ArchiveException exception = assertThrows(\n                ArchiveException.class, () -> catalog.growCatalog(CAPACITY * 2, Integer.MAX_VALUE));\n            assertEquals(\n                \"ERROR - recording is too big: total recording length is \" + Integer.MAX_VALUE +\n                \" bytes, available space is \" + (CAPACITY * 2 - 800) + \" bytes\",\n                exception.getMessage());\n        }\n    }\n\n    @Test\n    void growCatalogShouldNotExceedMaxCatalogCapacity()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            final long maxCatalogCapacity = CAPACITY * 1024;\n            catalog.growCatalog(maxCatalogCapacity, (int)(maxCatalogCapacity - 10_000));\n            assertEquals(maxCatalogCapacity, catalog.capacity());\n        }\n    }\n\n    @Test\n    void shouldNotThrowWhenOldRecordingLogsAreDeleted() throws IOException\n    {\n        final File segmentFile = new File(archiveDir, segmentFileName(recordingThreeId, SEGMENT_LENGTH * 2));\n        try (FileChannel log = FileChannel.open(segmentFile.toPath(), READ, WRITE, CREATE))\n        {\n            final ByteBuffer bb = allocate(HEADER_LENGTH);\n            final DataHeaderFlyweight flyweight = new DataHeaderFlyweight(bb);\n            flyweight.frameLength(256);\n            log.write(bb);\n        }\n\n        final Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer);\n        catalog.close();\n    }\n\n    @Test\n    void shouldContainChannelFragment()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            final String originalChannel = \"aeron:udp?endpoint=localhost:7777|tags=777|alias=TestString\";\n            final String strippedChannel = \"strippedChannelUri\";\n            final long recordingId = catalog.addNewRecording(\n                0L,\n                0L,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                6,\n                1,\n                strippedChannel,\n                originalChannel,\n                \"sourceA\");\n\n            assertTrue(catalog.wrapDescriptor(recordingId, unsafeBuffer));\n\n            recordingDescriptorDecoder.wrap(\n                unsafeBuffer,\n                RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.SCHEMA_VERSION);\n\n            assertTrue(originalChannelContains(recordingDescriptorDecoder, ArrayUtil.EMPTY_BYTE_ARRAY));\n\n            final byte[] originalChannelBytes = originalChannel.getBytes(US_ASCII);\n            assertTrue(originalChannelContains(recordingDescriptorDecoder, originalChannelBytes));\n\n            final byte[] tagsBytes = \"tags=777\".getBytes(US_ASCII);\n            assertTrue(originalChannelContains(recordingDescriptorDecoder, tagsBytes));\n\n            final byte[] testBytes = \"TestString\".getBytes(US_ASCII);\n            assertTrue(originalChannelContains(recordingDescriptorDecoder, testBytes));\n\n            final byte[] wrongBytes = \"wrong\".getBytes(US_ASCII);\n            assertFalse(originalChannelContains(recordingDescriptorDecoder, wrongBytes));\n        }\n    }\n\n    @ParameterizedTest(name = \"fragmentCrossesPageBoundary({0}, {1}, {2})\")\n    @MethodSource(\"pageBoundaryTestData\")\n    void detectPageBoundaryStraddle(final int fragmentOffset, final int fragmentLength, final boolean expected)\n    {\n        assertEquals(expected, fragmentStraddlesPageBoundary(fragmentOffset, fragmentLength));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { -1, 4, Long.MAX_VALUE })\n    void findLastReturnsNullRecordingIdIfMinRecordingIdIsOutOfRange(final long minRecordingId)\n    {\n        try (Catalog catalog = new Catalog(archiveDir, clock))\n        {\n            assertEquals(NULL_RECORD_ID, catalog.findLast(minRecordingId, 6, 1, \"channelG?tag=f\".getBytes(US_ASCII)));\n        }\n    }\n\n    @Test\n    void findLastReturnsLastFoundRecordingMatchingGivenCriteria()\n    {\n        final int sessionId = 6;\n        final int streamId = 1;\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            final long recordingF = catalog.addNewRecording(\n                0L,\n                0L,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                sessionId,\n                streamId,\n                \"F\",\n                \"channelG?tag=f\",\n                \"sourceA\");\n            catalog.addNewRecording(\n                0L,\n                0L,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                sessionId,\n                streamId,\n                \"X\",\n                \"channelG?tag=x\",\n                \"sourceA\");\n            catalog.addNewRecording(\n                0L,\n                0L,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                sessionId,\n                streamId + 1,\n                \"F\",\n                \"channelG?tag=f\",\n                \"sourceA\");\n            catalog.addNewRecording(\n                0L,\n                0L,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                sessionId + 1,\n                streamId,\n                \"F\",\n                \"channelG?tag=f\",\n                \"sourceA\");\n\n            assertEquals(recordingF,\n                catalog.findLast(recordingOneId, sessionId, streamId, \"channelG?tag=f\".getBytes(US_ASCII)));\n        }\n    }\n\n    @Test\n    void findLastReturnsNullRecordingIdIfRecordingIsInTheInvalidState()\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            assertTrue(catalog.changeState(recordingOneId, INVALID));\n\n            assertEquals(NULL_RECORD_ID, catalog.findLast(0, 6, 1, \"channelG?tag=f\".getBytes(US_ASCII)));\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { -1, Long.MAX_VALUE })\n    void changeStateIsANoOpIfUnknownRecordingStateIdIsSpecified(final long recordingId)\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            assertFalse(catalog.changeState(recordingId, NULL_VAL));\n        }\n    }\n\n    @Test\n    void changeStateOfTheIndexFirstEntry()\n    {\n        testChangeState(recordingOneId, DELETED);\n    }\n\n    @Test\n    void changeStateOfTheIndexMiddleEntry()\n    {\n        testChangeState(recordingTwoId, NULL_VAL);\n    }\n\n    @Test\n    void changeStateOfTheIndexLastEntry()\n    {\n        testChangeState(recordingThreeId, INVALID);\n    }\n\n    @Test\n    void shouldComputeChecksumOfTheRecordingDescriptorUponAddingToTheCatalog()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            final long recordingId = catalog.addNewRecording(\n                0L, 0L, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 6, 1, \"channelNew\", \"channelNew?tag=X\", \"sourceX\");\n            final long recordingId2 = catalog.addNewRecording(\n                1,\n                100,\n                2,\n                222,\n                111,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                16,\n                12,\n                \"channelNew2\",\n                \"channelNew?tag=X2\",\n                \"sourceX2\");\n\n            catalog.forEach(\n                (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n                {\n                    if (recordingId == descriptorDecoder.recordingId())\n                    {\n                        assertEquals(1691549102, headerDecoder.checksum());\n                    }\n                    else if (recordingId2 == descriptorDecoder.recordingId())\n                    {\n                        assertEquals(1452384985, headerDecoder.checksum());\n                    }\n                    else\n                    {\n                        assertEquals(0, headerDecoder.checksum());\n                    }\n                });\n        }\n    }\n\n    @Test\n    void recordingStoppedShouldUpdateChecksum()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            assertChecksum(catalog, recordingOneId, 160, 0, null);\n\n            catalog.recordingStopped(recordingOneId, 140, 231723682323L);\n\n            assertChecksum(catalog, recordingOneId, 160, 1656993099, checksum);\n        }\n    }\n\n    @Test\n    void stopPositionShouldUpdateChecksum()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            assertChecksum(catalog, recordingTwoId, 160, 0, null);\n\n            catalog.stopPosition(recordingTwoId, 7777);\n\n            assertChecksum(catalog, recordingTwoId, 160, -1985007076, checksum);\n        }\n    }\n\n    @Test\n    void startPositionShouldUpdateChecksum()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            assertChecksum(catalog, recordingThreeId, 352, 0, null);\n\n            catalog.startPosition(recordingThreeId, 123);\n\n            assertChecksum(catalog, recordingThreeId, 352, -160510802, checksum);\n        }\n    }\n\n    @Test\n    void extendRecordingShouldUpdateChecksum()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            final long recordingId = catalog.addNewRecording(\n                0L, 0L, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 6, 1, \"channelNew\", \"channelNew?tag=X\", \"sourceX\");\n            assertChecksum(catalog, recordingId, 160, 1691549102, checksum);\n\n            catalog.extendRecording(recordingId, 555, 13, 31);\n\n            assertChecksum(catalog, recordingId, 160, -1694749833, checksum);\n        }\n    }\n\n    @Test\n    void replaceThrowsArchiveExceptionIfRecordingIdIsUnknown()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            catalog.startPosition(recordingOneId, 0);\n            catalog.startPosition(recordingTwoId, 0);\n            catalog.startPosition(recordingThreeId, 0);\n            assertChecksum(catalog, recordingOneId, 160, -866186973, checksum);\n            assertChecksum(catalog, recordingTwoId, 160, -1947831311, checksum);\n            assertChecksum(catalog, recordingThreeId, 352, -529628341, checksum);\n        }\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            final int unknownRecordingId = 1_000_000;\n            final ArchiveException exception = assertThrowsExactly(\n                ArchiveException.class, () -> catalog.replaceRecording(\n                unknownRecordingId,\n                1,\n                2,\n                3,\n                4,\n                5,\n                6,\n                7,\n                8,\n                9,\n                10,\n                \"11\",\n                \"12\",\n                \"13\"));\n            assertEquals(\"ERROR - unknown recording id: \" + unknownRecordingId, exception.getMessage());\n\n            assertEquals(1024, catalog.capacity());\n            assertEquals(3, catalog.index().size());\n            assertEquals(recordingThreeId + 1, catalog.nextRecordingId());\n            assertChecksum(catalog, recordingOneId, 160, -866186973, checksum);\n            assertChecksum(catalog, recordingTwoId, 160, -1947831311, checksum);\n            assertChecksum(catalog, recordingThreeId, 352, -529628341, checksum);\n        }\n    }\n\n    @Test\n    void replaceRecordingUpdateCatalogFileWhenNewMetadataDoesNotFit()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            catalog.startPosition(recordingOneId, 0);\n            catalog.startPosition(recordingTwoId, 0);\n            catalog.startPosition(recordingThreeId, 0);\n            assertChecksum(catalog, recordingOneId, 160, -866186973, checksum);\n            assertChecksum(catalog, recordingTwoId, 160, -1947831311, checksum);\n            assertChecksum(catalog, recordingThreeId, 352, -529628341, checksum);\n        }\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            final long oldCapacity = catalog.capacity();\n\n            final String newSourceIdentity = addSuffix(\n                \"and the source identity changes as well and is also quite a long one funny thing: \", \"!\", 2000);\n            catalog.replaceRecording(\n                recordingTwoId,\n                1024,\n                16 * 1024,\n                3252535612L,\n                6238423648L,\n                777,\n                SEGMENT_LENGTH * 4,\n                TERM_LENGTH * 2,\n                1344,\n                -19,\n                42,\n                \"suppose to be a short description of the channel but can be whatever and surprising. Veni, vidi, vici\",\n                \"aeron:ipc?tag=15|alias=that is very very very long and will overflow the originally assigned length \" +\n                \"for sure and then some\",\n                newSourceIdentity);\n\n            assertTrue(catalog.capacity() > oldCapacity);\n            assertEquals(recordingThreeId + 1, catalog.nextRecordingId());\n            verifyRecordingForId(\n                catalog,\n                recordingOneId,\n                160,\n                0L,\n                0L,\n                0L,\n                1L,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                6,\n                1,\n                \"channelG\",\n                \"channelG?tag=f\",\n                \"sourceA\");\n            assertChecksum(catalog, recordingOneId, 160, -866186973, checksum);\n\n            verifyRecordingForId(\n                catalog,\n                recordingTwoId,\n                2400,\n                1024,\n                16 * 1024,\n                3252535612L,\n                6238423648L,\n                777,\n                SEGMENT_LENGTH * 4,\n                TERM_LENGTH * 2,\n                1344,\n                -19,\n                42,\n                \"suppose to be a short description of the channel but can be whatever and surprising. Veni, vidi, vici\",\n                \"aeron:ipc?tag=15|alias=that is very very very long and will overflow the originally assigned length \" +\n                \"for sure and then some\",\n                newSourceIdentity);\n            assertChecksum(catalog, recordingTwoId, 2400, -2076878182, checksum);\n\n            verifyRecordingForId(\n                catalog,\n                recordingThreeId,\n                352,\n                0L,\n                0L,\n                0L,\n                1L,\n                0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 8,\n                3,\n                \"channelThatIsVeryLongAndShouldNotBeTruncated\",\n                \"channelThatIsVeryLongAndShouldNotBeTruncated?tag=f\",\n                \"source can also be a very very very long String and it will not be truncated even if gets \" +\n                \"very very long\");\n            assertChecksum(catalog, recordingThreeId, 352, -529628341, checksum);\n        }\n    }\n\n    @Test\n    void replaceRecordingUpdateCatalogFileWhenNewMetadataDoesNotFitAndDifferenceInSizeIsSmall()\n    {\n        final Checksum checksum = crc32();\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            catalog.startPosition(recordingOneId, 0);\n            catalog.startPosition(recordingTwoId, 0);\n            catalog.startPosition(recordingThreeId, 0);\n            assertChecksum(catalog, recordingOneId, 160, -866186973, checksum);\n            assertChecksum(catalog, recordingTwoId, 160, -1947831311, checksum);\n            assertChecksum(catalog, recordingThreeId, 352, -529628341, checksum);\n        }\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            catalog.replaceRecording(\n                recordingTwoId,\n                1024,\n                16 * 1024,\n                3252535612L,\n                6238423648L,\n                777,\n                SEGMENT_LENGTH * 4,\n                TERM_LENGTH * 2,\n                1344,\n                -19,\n                42,\n                \"channelH\",\n                \"channelH?tag=f\",\n                \"to source or not to source that is the question\");\n\n            assertEquals(recordingThreeId + 1, catalog.nextRecordingId());\n            verifyRecordingForId(\n                catalog,\n                recordingOneId,\n                160,\n                0L,\n                0L,\n                0L,\n                1L,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                6,\n                1,\n                \"channelG\",\n                \"channelG?tag=f\",\n                \"sourceA\");\n            assertChecksum(catalog, recordingOneId, 160, -866186973, checksum);\n\n            verifyRecordingForId(\n                catalog,\n                recordingTwoId,\n                224,\n                1024,\n                16 * 1024,\n                3252535612L,\n                6238423648L,\n                777,\n                SEGMENT_LENGTH * 4,\n                TERM_LENGTH * 2,\n                1344,\n                -19,\n                42,\n                \"channelH\",\n                \"channelH?tag=f\",\n                \"to source or not to source that is the question\");\n            assertChecksum(catalog, recordingTwoId, 224, -1059517110, checksum);\n\n            verifyRecordingForId(\n                catalog,\n                recordingThreeId,\n                352,\n                0L,\n                0L,\n                0L,\n                1L,\n                0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 8,\n                3,\n                \"channelThatIsVeryLongAndShouldNotBeTruncated\",\n                \"channelThatIsVeryLongAndShouldNotBeTruncated?tag=f\",\n                \"source can also be a very very very long String and it will not be truncated even if gets \" +\n                \"very very long\");\n            assertChecksum(catalog, recordingThreeId, 352, -529628341, checksum);\n        }\n    }\n\n    @Test\n    void replaceRecordingShouldUpdateMetadataInPlaceWhenShorterThanTheOldOne()\n    {\n        final long updateTime = 555555L;\n        final Checksum checksum = crc32();\n        try (Catalog catalog =\n            new Catalog(archiveDir, null, 0, CAPACITY, () -> updateTime, checksum, segmentFileBuffer))\n        {\n            catalog.startPosition(recordingOneId, 0);\n            catalog.startPosition(recordingTwoId, 0);\n            catalog.startPosition(recordingThreeId, 0);\n            assertChecksum(catalog, recordingOneId, 160, 729050496, checksum);\n            assertChecksum(catalog, recordingTwoId, 160, 1825380178, checksum);\n            assertChecksum(catalog, recordingThreeId, 352, -1553583482, checksum);\n        }\n\n        try (Catalog catalog =\n            new Catalog(archiveDir, null, 0, CAPACITY, clock, checksum, segmentFileBuffer))\n        {\n            final long originalCapacity = catalog.capacity();\n\n            catalog.replaceRecording(\n                recordingOneId,\n                128,\n                512,\n                111L,\n                222L,\n                -19091,\n                SEGMENT_LENGTH * 16,\n                TERM_LENGTH * 4,\n                1372,\n                21,\n                8,\n                \"A\",\n                \"B\",\n                \"C\");\n\n            assertEquals(recordingThreeId + 1, catalog.nextRecordingId());\n            assertEquals(originalCapacity, catalog.capacity());\n            final int oldFrameLength = 160;\n            verifyRecordingForId(\n                catalog,\n                recordingOneId,\n                oldFrameLength,\n                128,\n                512,\n                111L,\n                222L,\n                -19091,\n                SEGMENT_LENGTH * 16,\n                TERM_LENGTH * 4,\n                1372,\n                21,\n                8,\n                \"A\",\n                \"B\",\n                \"C\");\n            final int newFrameLength = 96;\n            verifyOldExcessiveDataWasErased(oldFrameLength, newFrameLength);\n            assertChecksum(catalog, recordingOneId, newFrameLength, 1488471744, checksum);\n\n            verifyRecordingForId(\n                catalog,\n                recordingTwoId,\n                160,\n                0L,\n                0L,\n                0L,\n                updateTime,\n                0,\n                SEGMENT_LENGTH,\n                TERM_LENGTH,\n                MTU_LENGTH,\n                7,\n                2,\n                \"channelH\",\n                \"channelH?tag=f\",\n                \"sourceV\");\n            assertChecksum(catalog, recordingTwoId, 160, 1825380178, checksum);\n\n            verifyRecordingForId(\n                catalog,\n                recordingThreeId,\n                352,\n                0L,\n                0L,\n                0L,\n                updateTime,\n                0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 8,\n                3,\n                \"channelThatIsVeryLongAndShouldNotBeTruncated\",\n                \"channelThatIsVeryLongAndShouldNotBeTruncated?tag=f\",\n                \"source can also be a very very very long String and it will not be truncated even if gets \" +\n                \"very very long\");\n            assertChecksum(catalog, recordingThreeId, 352, -1553583482, checksum);\n        }\n    }\n\n    private void verifyOldExcessiveDataWasErased(final int oldFrameLength, final int newFrameLength)\n    {\n        assertTrue(oldFrameLength > newFrameLength);\n        for (int i = newFrameLength; i < oldFrameLength; i++)\n        {\n            assertEquals(0, unsafeBuffer.getByte(RecordingDescriptorHeaderDecoder.BLOCK_LENGTH + i));\n        }\n    }\n\n    private static void assertChecksum(\n        final Catalog catalog,\n        final long recordingId,\n        final int alignedChecksumLength,\n        final int expectedChecksum,\n        final Checksum checksum)\n    {\n        catalog.forEntry(recordingId,\n            (recordingDescriptorOffset, headerEncoder, headerDecoder, descriptorEncoder, descriptorDecoder) ->\n            {\n                assertEquals(expectedChecksum, headerDecoder.checksum());\n                if (null != checksum)\n                {\n                    final int computedChecksum = checksum.compute(\n                        descriptorDecoder.buffer().addressOffset(),\n                        RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n                        alignedChecksumLength);\n                    assertEquals(expectedChecksum, computedChecksum);\n                }\n            });\n    }\n\n    private void testChangeState(final long recordingId, final RecordingState newState)\n    {\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, segmentFileBuffer))\n        {\n            final int entries = catalog.entryCount();\n\n            assertTrue(catalog.wrapDescriptor(recordingId, unsafeBuffer));\n\n            recordingDescriptorHeaderDecoder.wrap(\n                unsafeBuffer,\n                0,\n                RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n                RecordingDescriptorHeaderDecoder.SCHEMA_VERSION);\n\n            assertTrue(catalog.changeState(recordingId, newState));\n\n            assertEquals(newState, recordingDescriptorHeaderDecoder.state());\n            assertEquals(entries - 1, catalog.entryCount());\n            assertFalse(catalog.hasRecording(recordingId));\n        }\n    }\n\n    private void verifyRecordingForId(\n        final Catalog catalog,\n        final long id,\n        final int length,\n        final long startPosition,\n        final long stopPosition,\n        final long startTimestamp,\n        final long stopTimestamp,\n        final int initialTermId,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int mtuLength,\n        final int sessionId,\n        final int streamId,\n        final String strippedChannel,\n        final String originalChannel,\n        final String sourceIdentity)\n    {\n        assertTrue(catalog.wrapDescriptor(id, unsafeBuffer));\n\n        recordingDescriptorHeaderDecoder.wrap(\n            unsafeBuffer,\n            0,\n            RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n            RecordingDescriptorHeaderDecoder.SCHEMA_VERSION);\n\n        assertEquals(VALID, recordingDescriptorHeaderDecoder.state());\n        assertEquals(length, recordingDescriptorHeaderDecoder.length());\n\n        recordingDescriptorDecoder.wrap(\n            unsafeBuffer,\n            RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n            RecordingDescriptorDecoder.BLOCK_LENGTH,\n            RecordingDescriptorDecoder.SCHEMA_VERSION);\n\n        assertEquals(id, recordingDescriptorDecoder.recordingId());\n        assertEquals(startPosition, recordingDescriptorDecoder.startPosition());\n        assertEquals(stopPosition, recordingDescriptorDecoder.stopPosition());\n        assertEquals(startTimestamp, recordingDescriptorDecoder.startTimestamp());\n        assertEquals(stopTimestamp, recordingDescriptorDecoder.stopTimestamp());\n        assertEquals(initialTermId, recordingDescriptorDecoder.initialTermId());\n        assertEquals(segmentFileLength, recordingDescriptorDecoder.segmentFileLength());\n        assertEquals(termBufferLength, recordingDescriptorDecoder.termBufferLength());\n        assertEquals(mtuLength, recordingDescriptorDecoder.mtuLength());\n        assertEquals(sessionId, recordingDescriptorDecoder.sessionId());\n        assertEquals(streamId, recordingDescriptorDecoder.streamId());\n        assertEquals(strippedChannel, recordingDescriptorDecoder.strippedChannel());\n        assertEquals(originalChannel, recordingDescriptorDecoder.originalChannel());\n        assertEquals(sourceIdentity, recordingDescriptorDecoder.sourceIdentity());\n    }\n\n    private static Stream<Arguments> pageBoundaryTestData()\n    {\n        return Stream.of(\n            Arguments.of(0, 64, false),\n            Arguments.of(100, 300, false),\n            Arguments.of(0, PAGE_SIZE, false),\n            Arguments.of(1, PAGE_SIZE - 1, false),\n            Arguments.of(PAGE_SIZE - 1, 1, false),\n            Arguments.of(PAGE_SIZE * 7 + 32, 256, false),\n            Arguments.of(PAGE_SIZE * 3 + 111, PAGE_SIZE - 111, false),\n            Arguments.of(0, PAGE_SIZE + 1, true),\n            Arguments.of(PAGE_SIZE - 1, 2, true),\n            Arguments.of(PAGE_SIZE * 4 + 11, PAGE_SIZE - 10, true));\n    }\n\n    private void setNextRecordingId(final long nextRecordingId) throws IOException\n    {\n        try (FileChannel channel = FileChannel.open(archiveDir.toPath().resolve(CATALOG_FILE_NAME), READ, WRITE))\n        {\n            final MappedByteBuffer mappedByteBuffer = channel.map(READ_WRITE, 0, CatalogHeaderEncoder.BLOCK_LENGTH);\n            mappedByteBuffer.order(CatalogHeaderEncoder.BYTE_ORDER);\n            try\n            {\n                new CatalogHeaderEncoder()\n                    .wrap(new UnsafeBuffer(mappedByteBuffer), 0)\n                    .nextRecordingId(nextRecordingId);\n            }\n            finally\n            {\n                BufferUtil.free(mappedByteBuffer);\n            }\n        }\n    }\n\n    private static String addSuffix(final String prefix, final String suffix, final int times)\n    {\n        final StringBuilder buff = new StringBuilder(prefix.length() + suffix.length() * times);\n        buff.append(prefix);\n\n        for (int i = 0; i < times; i++)\n        {\n            buff.append(suffix);\n        }\n\n        return buff.toString();\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/CatalogViewTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.RecordingDescriptorConsumer;\nimport org.agrona.IoUtil;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.*;\n\nclass CatalogViewTest\n{\n    private static final long CAPACITY = 1024 * 1024;\n    private static final int TERM_LENGTH = 2 * Catalog.PAGE_SIZE;\n    private static final int SEGMENT_LENGTH = 2 * TERM_LENGTH;\n    private static final int MTU_LENGTH = 1024;\n\n    private final File archiveDir = ArchiveTests.makeTestDirectory();\n    private final CachedEpochClock clock = new CachedEpochClock();\n\n    private long recordingOneId;\n    private long recordingTwoId;\n    private long recordingThreeId;\n    private final RecordingDescriptorConsumer mockRecordingDescriptorConsumer = mock(RecordingDescriptorConsumer.class);\n\n    @BeforeEach\n    void before()\n    {\n        clock.update(1);\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, null))\n        {\n            recordingOneId = catalog.addNewRecording(\n                10L, 4L, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 7, 1, \"channelG\", \"channelG?tag=f\", \"sourceA\");\n            recordingTwoId = catalog.addNewRecording(\n                11L, 5L, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 8, 2, \"channelH\", \"channelH?tag=f\", \"sourceV\");\n            recordingThreeId = catalog.addNewRecording(\n                12L, 6L, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 9, 3, \"channelK\", \"channelK?tag=f\", \"sourceB\");\n        }\n    }\n\n    @AfterEach\n    void after()\n    {\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    void shouldListAllRecordingsInCatalog()\n    {\n        final int count = CatalogView.listRecordings(archiveDir, mockRecordingDescriptorConsumer);\n        assertEquals(3, count);\n\n        verify(mockRecordingDescriptorConsumer).onRecordingDescriptor(\n            Aeron.NULL_VALUE, Aeron.NULL_VALUE, recordingOneId, 4L, Aeron.NULL_VALUE, 10L,\n            Aeron.NULL_VALUE, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 7, 1,\n            \"channelG\", \"channelG?tag=f\", \"sourceA\");\n\n        verify(mockRecordingDescriptorConsumer).onRecordingDescriptor(\n            Aeron.NULL_VALUE, Aeron.NULL_VALUE, recordingTwoId, 5L, Aeron.NULL_VALUE, 11L,\n            Aeron.NULL_VALUE, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 8, 2,\n            \"channelH\", \"channelH?tag=f\", \"sourceV\");\n\n        verify(mockRecordingDescriptorConsumer).onRecordingDescriptor(\n            Aeron.NULL_VALUE, Aeron.NULL_VALUE, recordingThreeId, 6L, Aeron.NULL_VALUE, 12L,\n            Aeron.NULL_VALUE, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 9, 3,\n            \"channelK\", \"channelK?tag=f\", \"sourceB\");\n\n        verifyNoMoreInteractions(mockRecordingDescriptorConsumer);\n    }\n\n    @Test\n    void shouldListRecordingByRecordingId()\n    {\n        final boolean found = CatalogView.listRecording(archiveDir, recordingTwoId, mockRecordingDescriptorConsumer);\n        assertTrue(found);\n\n        verify(mockRecordingDescriptorConsumer).onRecordingDescriptor(\n            Aeron.NULL_VALUE, Aeron.NULL_VALUE, recordingTwoId, 5L, Aeron.NULL_VALUE, 11L,\n            Aeron.NULL_VALUE, 0, SEGMENT_LENGTH, TERM_LENGTH, MTU_LENGTH, 8, 2,\n            \"channelH\", \"channelH?tag=f\", \"sourceV\");\n\n        verifyNoMoreInteractions(mockRecordingDescriptorConsumer);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ControlSessionAdapterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.*;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.security.AuthorisationService;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.charset.StandardCharsets;\n\nimport static org.mockito.Mockito.*;\n\nclass ControlSessionAdapterTest\n{\n\n    public static final long CONTROL_SESSION_ID = 928374L;\n    private final ArchiveConductor mockConductor = mock(ArchiveConductor.class);\n    private final Subscription mockControlSubsciption = mock(Subscription.class);\n    private final Subscription mockLocalControlSubsciption = mock(Subscription.class);\n    private final AuthorisationService mockAuthorisationService = mock(AuthorisationService.class);\n    private final Header mockHeader = mock(Header.class);\n    private final ControlSession mockSession = mock(ControlSession.class);\n\n    public static final int SCHEMA_VERSION_6 = 6;\n\n    @BeforeEach\n    void before()\n    {\n        final Image image = mock(Image.class);\n        when(mockHeader.context()).thenReturn(image);\n    }\n\n    @Test\n    void shouldHandleReplicationRequest2()\n    {\n        final ControlSessionAdapter controlSessionAdapter = new ControlSessionAdapter(\n            new ControlRequestDecoders(),\n            mockControlSubsciption,\n            mockLocalControlSubsciption,\n            mockConductor,\n            mockAuthorisationService);\n        setupControlSession(controlSessionAdapter, CONTROL_SESSION_ID);\n\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n        final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n        final ReplicateRequest2Encoder replicateRequest2Encoder = new ReplicateRequest2Encoder();\n\n        replicateRequest2Encoder.wrapAndApplyHeader(buffer, 0, headerEncoder);\n\n        final byte[] encodedCredentials = \"some password\".getBytes(StandardCharsets.US_ASCII);\n        replicateRequest2Encoder\n            .controlSessionId(928374L)\n            .correlationId(9382475L)\n            .srcRecordingId(1234234L)\n            .dstRecordingId(2532453245L)\n            .stopPosition(2315345L)\n            .channelTagId(234L)\n            .subscriptionTagId(235L)\n            .srcControlStreamId(982374)\n            .fileIoMaxLength(4096)\n            .srcControlChannel(\"src\")\n            .liveDestination(\"live\")\n            .replicationChannel(\"replication\")\n            .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length)\n            .srcResponseChannel(\"response\");\n        final int replicateRequestLength = replicateRequest2Encoder.encodedLength();\n\n        controlSessionAdapter.onFragment(buffer, 0, replicateRequestLength, mockHeader);\n\n        final ReplicateRequest2Decoder expected = new ReplicateRequest2Decoder()\n            .wrapAndApplyHeader(buffer, 0, new MessageHeaderDecoder());\n\n        verify(mockSession).onReplicate(\n            expected.correlationId(),\n            expected.srcRecordingId(),\n            expected.dstRecordingId(),\n            expected.stopPosition(),\n            expected.channelTagId(),\n            expected.subscriptionTagId(),\n            expected.srcControlStreamId(),\n            expected.fileIoMaxLength(),\n            expected.replicationSessionId(),\n            expected.srcControlChannel(),\n            expected.liveDestination(),\n            expected.replicationChannel(),\n            encodedCredentials(expected),\n            expected.srcResponseChannel());\n    }\n\n    private static byte[] encodedCredentials(final ReplicateRequest2Decoder decoder)\n    {\n        final byte[] credentials = new byte[decoder.encodedCredentialsLength()];\n        decoder.getEncodedCredentials(credentials, 0, credentials.length);\n        return credentials;\n    }\n\n    @Test\n    void shouldHandleReplayRequest()\n    {\n        final ControlSessionAdapter controlSessionAdapter = new ControlSessionAdapter(\n            new ControlRequestDecoders(),\n            mockControlSubsciption,\n            mockLocalControlSubsciption,\n            mockConductor,\n            mockAuthorisationService);\n        setupControlSession(controlSessionAdapter, CONTROL_SESSION_ID);\n\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n        final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n        final ReplayRequestEncoder replayRequestEncoder = new ReplayRequestEncoder();\n\n        replayRequestEncoder.wrapAndApplyHeader(buffer, 0, headerEncoder);\n\n        replayRequestEncoder\n            .controlSessionId(928374L)\n            .correlationId(9382475L)\n            .recordingId(9827345897L)\n            .position(982374L)\n            .fileIoMaxLength(4096)\n            .replayStreamId(9832475)\n            .replayChannel(\"aeron:ipc\");\n\n        final int replicateRequestLength = replayRequestEncoder.encodedLength();\n\n        controlSessionAdapter.onFragment(buffer, 0, replicateRequestLength, mockHeader);\n\n        final ReplayRequestDecoder expected = new ReplayRequestDecoder()\n            .wrapAndApplyHeader(buffer, 0, new MessageHeaderDecoder());\n\n        verify(mockSession).onStartReplay(\n            expected.correlationId(),\n            expected.recordingId(),\n            expected.position(),\n            expected.length(),\n            expected.fileIoMaxLength(),\n            expected.replayStreamId(),\n            expected.replayChannel());\n    }\n\n    @Test\n    void shouldHandleBoundedReplayRequest()\n    {\n        final ControlSessionAdapter controlSessionAdapter = new ControlSessionAdapter(\n            new ControlRequestDecoders(),\n            mockControlSubsciption,\n            mockLocalControlSubsciption,\n            mockConductor,\n            mockAuthorisationService);\n        setupControlSession(controlSessionAdapter, CONTROL_SESSION_ID);\n\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n        final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n        final BoundedReplayRequestEncoder replayRequestEncoder = new BoundedReplayRequestEncoder();\n\n        replayRequestEncoder.wrapAndApplyHeader(buffer, 0, headerEncoder);\n\n        replayRequestEncoder\n            .controlSessionId(928374L)\n            .correlationId(9382475L)\n            .recordingId(9827345897L)\n            .position(982374L)\n            .limitCounterId(92734)\n            .replayStreamId(9832475)\n            .fileIoMaxLength(4096)\n            .replayChannel(\"aeron:ipc?alias=replay\");\n\n        final int replicateRequestLength = replayRequestEncoder.encodedLength();\n\n        controlSessionAdapter.onFragment(buffer, 0, replicateRequestLength, mockHeader);\n\n        final BoundedReplayRequestDecoder expected = new BoundedReplayRequestDecoder()\n            .wrapAndApplyHeader(buffer, 0, new MessageHeaderDecoder());\n\n        verify(mockSession).onStartBoundedReplay(\n            expected.correlationId(),\n            expected.recordingId(),\n            expected.position(),\n            expected.length(),\n            expected.limitCounterId(),\n            expected.fileIoMaxLength(),\n            expected.replayStreamId(),\n            expected.replayChannel());\n    }\n\n    @Test\n    void shouldHandleReplayTokenRequest()\n    {\n        final ControlSessionAdapter controlSessionAdapter = new ControlSessionAdapter(\n            new ControlRequestDecoders(),\n            mockControlSubsciption,\n            mockLocalControlSubsciption,\n            mockConductor,\n            mockAuthorisationService);\n        setupControlSession(controlSessionAdapter, CONTROL_SESSION_ID);\n\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n        final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n        final ReplayTokenRequestEncoder replayTokenRequestEncoder = new ReplayTokenRequestEncoder();\n\n        replayTokenRequestEncoder.wrapAndApplyHeader(buffer, 0, headerEncoder);\n\n        final long recordingId = 9827345897L;\n\n        replayTokenRequestEncoder\n            .controlSessionId(CONTROL_SESSION_ID)\n            .correlationId(9382475L)\n            .recordingId(recordingId);\n\n        controlSessionAdapter.onFragment(buffer, 0, replayTokenRequestEncoder.encodedLength(), mockHeader);\n\n        verify(mockConductor).generateReplayToken(mockSession, recordingId);\n    }\n\n    private void setupControlSession(final ControlSessionAdapter controlSessionAdapter, final long controlSessionId)\n    {\n        final MutableDirectBuffer buffer = new ExpandableArrayBuffer();\n        final MessageHeaderEncoder headerEncoder2 = new MessageHeaderEncoder();\n        final ConnectRequestEncoder connectRequestEncoder = new ConnectRequestEncoder();\n        connectRequestEncoder.wrapAndApplyHeader(buffer, 0, headerEncoder2);\n        connectRequestEncoder\n            .correlationId(100)\n            .responseStreamId(100)\n            .version(SCHEMA_VERSION_6)\n            .responseChannel(\"foo\");\n        final int connectRequestLength = connectRequestEncoder.encodedLength();\n\n        doReturn(mockSession).when(mockConductor).newControlSession(\n            any(), anyLong(), anyInt(), anyInt(), anyString(), any(), anyString(), any());\n        doReturn(controlSessionId).when(mockSession).sessionId();\n        doReturn(true).when(mockAuthorisationService).isAuthorised(anyInt(), anyInt(), any(), any());\n\n        controlSessionAdapter.onFragment(buffer, 0, connectRequestLength, mockHeader);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ControlSessionAdapterV6Test.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.v6.*;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.security.NullCredentialsSupplier;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.*;\n\nclass ControlSessionAdapterV6Test\n{\n\n    public static final long CONTROL_SESSION_ID = 928374L;\n    private final ArchiveConductor mockConductor = mock(ArchiveConductor.class);\n    private final Subscription mockControlSubsciption = mock(Subscription.class);\n    private final Subscription mockLocalControlSubsciption = mock(Subscription.class);\n    private final AuthorisationService mockAuthorisationService = mock(AuthorisationService.class);\n    private final Header mockHeader = mock(Header.class);\n    private final ControlSession mockSession = mock(ControlSession.class);\n\n    public static final int SCHEMA_VERSION_6 = 6;\n\n    @BeforeEach\n    void before()\n    {\n        final Image image = mock(Image.class);\n        when(mockHeader.context()).thenReturn(image);\n    }\n\n    @Test\n    void shouldHandleVersion6ReplicationRequest2()\n    {\n        final ControlSessionAdapter controlSessionAdapter = new ControlSessionAdapter(\n            new ControlRequestDecoders(),\n            mockControlSubsciption,\n            mockLocalControlSubsciption,\n            mockConductor,\n            mockAuthorisationService);\n        setupControlSession(controlSessionAdapter);\n\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n        final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n        final ReplicateRequest2Encoder replicateRequest2Encoder = new ReplicateRequest2Encoder();\n\n        replicateRequest2Encoder.wrapAndApplyHeader(buffer, 0, headerEncoder);\n\n        final int fileIoMaxLength = Aeron.NULL_VALUE; // Since v7\n        final int sessionId = Aeron.NULL_VALUE; // Since v8\n        final byte[] encodedCredentials = NullCredentialsSupplier.NULL_CREDENTIAL; // Since v8\n\n        replicateRequest2Encoder\n            .controlSessionId(928374L)\n            .correlationId(9382475L)\n            .srcRecordingId(1234234L)\n            .dstRecordingId(2532453245L)\n            .stopPosition(2315345L)\n            .channelTagId(234L)\n            .subscriptionTagId(235L)\n            .srcControlStreamId(982374)\n            .srcControlChannel(\"src\")\n            .liveDestination(\"live\")\n            .replicationChannel(\"replication\");\n        final int replicateRequestLength = replicateRequest2Encoder.encodedLength();\n\n        controlSessionAdapter.onFragment(buffer, 0, replicateRequestLength, mockHeader);\n\n        final ReplicateRequest2Decoder expected = new ReplicateRequest2Decoder()\n            .wrapAndApplyHeader(buffer, 0, new MessageHeaderDecoder());\n        verify(mockSession).onReplicate(\n            expected.correlationId(),\n            expected.srcRecordingId(),\n            expected.dstRecordingId(),\n            expected.stopPosition(),\n            expected.channelTagId(),\n            expected.subscriptionTagId(),\n            expected.srcControlStreamId(),\n            fileIoMaxLength,\n            sessionId,\n            expected.srcControlChannel(),\n            expected.liveDestination(),\n            expected.replicationChannel(),\n            encodedCredentials,\n            \"\");\n    }\n\n    @Test\n    void shouldHandleVersion6ReplayRequest2()\n    {\n        final ControlSessionAdapter controlSessionAdapter = new ControlSessionAdapter(\n            new ControlRequestDecoders(),\n            mockControlSubsciption,\n            mockLocalControlSubsciption,\n            mockConductor,\n            mockAuthorisationService);\n        setupControlSession(controlSessionAdapter);\n\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n        final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n        final ReplayRequestEncoder replayRequestEncoder = new ReplayRequestEncoder();\n\n        replayRequestEncoder.wrapAndApplyHeader(buffer, 0, headerEncoder);\n\n        final int fileIoMaxLength = Aeron.NULL_VALUE; // Since v7\n\n        replayRequestEncoder\n            .controlSessionId(928374L)\n            .correlationId(9382475L)\n            .recordingId(9827345897L)\n            .position(982374L)\n            .replayStreamId(9832475)\n            .replayChannel(\"aeron:ipc\");\n\n        final int replicateRequestLength = replayRequestEncoder.encodedLength();\n\n        controlSessionAdapter.onFragment(buffer, 0, replicateRequestLength, mockHeader);\n\n        final ReplayRequestDecoder expected = new ReplayRequestDecoder()\n            .wrapAndApplyHeader(buffer, 0, new MessageHeaderDecoder());\n\n        verify(mockSession).onStartReplay(\n            expected.correlationId(),\n            expected.recordingId(),\n            expected.position(),\n            expected.length(),\n            fileIoMaxLength,\n            expected.replayStreamId(),\n            expected.replayChannel());\n    }\n\n    @Test\n    void shouldHandleVersion6BoundedReplayRequest()\n    {\n        final ControlSessionAdapter controlSessionAdapter = new ControlSessionAdapter(\n            new ControlRequestDecoders(),\n            mockControlSubsciption,\n            mockLocalControlSubsciption,\n            mockConductor,\n            mockAuthorisationService);\n        setupControlSession(controlSessionAdapter);\n\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n        final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n        final BoundedReplayRequestEncoder replayRequestEncoder = new BoundedReplayRequestEncoder();\n\n        replayRequestEncoder.wrapAndApplyHeader(buffer, 0, headerEncoder);\n\n        replayRequestEncoder\n            .controlSessionId(928374L)\n            .correlationId(9382475L)\n            .recordingId(9827345897L)\n            .position(982374L)\n            .limitCounterId(92734)\n            .replayStreamId(9832475)\n            .replayChannel(\"aeron:ipc?alias=replay\");\n\n        final int replicateRequestLength = replayRequestEncoder.encodedLength();\n\n        controlSessionAdapter.onFragment(buffer, 0, replicateRequestLength, mockHeader);\n\n        final BoundedReplayRequestDecoder expected = new BoundedReplayRequestDecoder()\n            .wrapAndApplyHeader(buffer, 0, new MessageHeaderDecoder());\n\n        verify(mockSession).onStartBoundedReplay(\n            expected.correlationId(),\n            expected.recordingId(),\n            expected.position(),\n            expected.length(),\n            expected.limitCounterId(),\n            Aeron.NULL_VALUE,\n            expected.replayStreamId(),\n            expected.replayChannel());\n    }\n\n    private void setupControlSession(final ControlSessionAdapter controlSessionAdapter)\n    {\n        final MutableDirectBuffer buffer = new ExpandableArrayBuffer();\n        final MessageHeaderEncoder headerEncoder2 = new MessageHeaderEncoder();\n        final ConnectRequestEncoder connectRequestEncoder = new ConnectRequestEncoder();\n        connectRequestEncoder.wrapAndApplyHeader(buffer, 0, headerEncoder2);\n        connectRequestEncoder\n            .correlationId(100)\n            .responseStreamId(100)\n            .version(SCHEMA_VERSION_6)\n            .responseChannel(\"foo\");\n        final int connectRequestLength = connectRequestEncoder.encodedLength();\n\n        doReturn(mockSession).when(mockConductor).newControlSession(\n            any(), anyLong(), anyInt(), anyInt(), anyString(), any(), anyString(), any());\n        doReturn(CONTROL_SESSION_ID).when(mockSession).sessionId();\n        doReturn(true).when(mockAuthorisationService).isAuthorised(anyInt(), anyInt(), any(), any());\n\n        controlSessionAdapter.onFragment(buffer, 0, connectRequestLength, mockHeader);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ControlSessionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.archive.client.ArchiveEvent;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.AuthorisationService;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.ArgumentCaptor;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.archive.ControlSession.State.DONE;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass ControlSessionTest\n{\n    private static final long CONTROL_PUBLICATION_ID = 777;\n    private static final long COUNTER_REGISTRATION_ID = 333;\n    private static final long CONNECT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);\n    private static final long SESSION_LIVENESS_CHECK_INTERVAL_NS = TimeUnit.MILLISECONDS.toNanos(100);\n    private static final int CONTROL_SESSION_ID = 42;\n    private static final int CORRELATION_ID = 2;\n    private static final int CONTROL_PUBLICATION_STREAM_ID = 5555;\n    private static final String CONTROL_PUBLICATION_CHANNEL = \"aeron:ipc?alias=test-control-response\";\n\n    private final ControlSessionAdapter mockDemuxer = mock(ControlSessionAdapter.class);\n    private ArchiveConductor archiveConductor;\n    private final CountedErrorHandler mockErrorHandler = mock(CountedErrorHandler.class);\n    private final Aeron mockAeron = mock(Aeron.class);\n    private final ExclusivePublication mockControlPublication = mock(ExclusivePublication.class);\n    private final ControlResponseProxy mockProxy = mock(ControlResponseProxy.class);\n    private final ControlSessionProxy mockSessionProxy = mock(ControlSessionProxy.class);\n    private final Authenticator mockAuthenticator = mock(Authenticator.class);\n    private final AuthorisationService mockAuthorisationService = mock(AuthorisationService.class);\n    private final CachedEpochClock cachedEpochClock = new CachedEpochClock();\n    private ControlSession session;\n\n    @BeforeEach\n    void before()\n    {\n        archiveConductor = new SharedModeArchiveConductor(new Archive.Context()\n            .countedErrorHandler(mockErrorHandler)\n            .nanoClock(SystemNanoClock.INSTANCE)\n            .epochClock(SystemEpochClock.INSTANCE)\n            .aeron(mockAeron)\n            .authenticatorSupplier(() -> mockAuthenticator)\n            .authorisationServiceSupplier(() -> mockAuthorisationService)\n            .controlChannelEnabled(false));\n\n        session = new ControlSession(\n            CONTROL_SESSION_ID,\n            CORRELATION_ID,\n            CONNECT_TIMEOUT_MS,\n            SESSION_LIVENESS_CHECK_INTERVAL_NS,\n            CONTROL_PUBLICATION_ID,\n            COUNTER_REGISTRATION_ID,\n            CONTROL_PUBLICATION_CHANNEL,\n            CONTROL_PUBLICATION_STREAM_ID,\n            null,\n            mockDemuxer,\n            mockAeron,\n            archiveConductor,\n            cachedEpochClock,\n            mockProxy,\n            mockAuthenticator,\n            mockSessionProxy);\n\n        when(mockAeron.getExclusivePublication(CONTROL_PUBLICATION_ID)).thenReturn(mockControlPublication);\n    }\n\n    @Test\n    void shouldTimeoutIfConnectSentButPublicationNotConnected()\n    {\n        when(mockControlPublication.isClosed()).thenReturn(false);\n        when(mockControlPublication.isConnected()).thenReturn(false);\n\n        session.doWork();\n\n        cachedEpochClock.update(CONNECT_TIMEOUT_MS + 1L);\n        session.doWork();\n        assertEquals(DONE, session.state());\n        assertTrue(session.isDone());\n    }\n\n    @Test\n    void shouldTimeoutIfConnectSentButPublicationFailsToSend()\n    {\n        when(mockControlPublication.isClosed()).thenReturn(false);\n        when(mockControlPublication.isConnected()).thenReturn(true);\n\n        session.doWork();\n        session.sendOkResponse(1L);\n        session.doWork();\n\n        cachedEpochClock.update(CONNECT_TIMEOUT_MS + 1L);\n        session.doWork();\n        assertEquals(DONE, session.state());\n        assertTrue(session.isDone());\n    }\n\n    @Test\n    void shouldChangeStateAndCaptureAbortReason()\n    {\n        assertFalse(session.isDone());\n        assertFalse(session.hasActiveListing());\n\n        final String reason = \"stop execution\";\n        session.abort(reason);\n\n        assertTrue(session.isDone());\n        assertEquals(DONE, session.state());\n        assertSame(reason, session.abortReason());\n    }\n\n    @Test\n    void shouldChangeStateCaptureAbortReasonAndAbortActiveListing()\n    {\n        assertFalse(session.isDone());\n        assertFalse(session.hasActiveListing());\n        final Session listingSession = mock(Session.class);\n        session.activeListing(listingSession);\n\n        final String reason = \"stop execution\";\n        session.abort(reason);\n\n        assertTrue(session.isDone());\n        assertEquals(DONE, session.state());\n        assertSame(reason, session.abortReason());\n        verify(listingSession).abort(reason);\n    }\n\n    @Test\n    void abortIsANoOpIfSessionIsDone()\n    {\n        assertFalse(session.isDone());\n        assertFalse(session.hasActiveListing());\n        final Session listingSession = mock(Session.class);\n        session.activeListing(listingSession);\n\n        session.state(ControlSession.State.DONE, \"test\");\n\n        session.abort(ControlSession.SESSION_CLOSED_MSG);\n\n        assertTrue(session.isDone());\n        assertEquals(DONE, session.state());\n        assertNull(session.abortReason());\n        verifyNoInteractions(listingSession);\n    }\n\n    @Test\n    void abortShouldIgnoreAdditionalCalls()\n    {\n        assertFalse(session.isDone());\n        assertFalse(session.hasActiveListing());\n        final Session listingSession = mock(Session.class);\n        session.activeListing(listingSession);\n\n        final String reason1 = \"call1\";\n        session.abort(reason1);\n        session.abort(\"call2\");\n        session.abort(\"call3\");\n\n        assertTrue(session.isDone());\n        assertEquals(DONE, session.state());\n        assertEquals(reason1, session.abortReason());\n        verify(listingSession).abort(reason1);\n    }\n\n    @Test\n    void cleanTerminationCannotBeAborted()\n    {\n        assertFalse(session.isDone());\n        assertFalse(session.hasActiveListing());\n        final Session listingSession = mock(Session.class);\n        session.activeListing(listingSession);\n\n        session.abort(ControlSession.SESSION_CLOSED_MSG);\n        session.abort(\"call2\");\n        session.abort(\"call3\");\n\n        assertTrue(session.isDone());\n        assertEquals(DONE, session.state());\n        assertEquals(ControlSession.SESSION_CLOSED_MSG, session.abortReason());\n        verify(listingSession).abort(ControlSession.SESSION_CLOSED_MSG);\n    }\n\n    @Test\n    void cleanTerminationCanOverwritePreviousAbortMessage()\n    {\n        assertFalse(session.isDone());\n        assertFalse(session.hasActiveListing());\n        final Session listingSession = mock(Session.class);\n        session.activeListing(listingSession);\n\n        session.abort(\"call1\");\n        session.abort(ControlSession.SESSION_CLOSED_MSG);\n        session.abort(\"call3\");\n\n        assertTrue(session.isDone());\n        assertEquals(DONE, session.state());\n        assertEquals(ControlSession.SESSION_CLOSED_MSG, session.abortReason());\n        verify(listingSession).abort(ControlSession.SESSION_CLOSED_MSG);\n        verify(listingSession).abort(\"call1\");\n    }\n\n    @Test\n    void shouldLogWarningIfSessionIsAborted()\n    {\n        final String abortReason = \"test abort reason\";\n        session.abort(abortReason);\n\n        session.close();\n\n        final ArgumentCaptor<Throwable> error = ArgumentCaptor.forClass(Throwable.class);\n        verify(mockErrorHandler).onError(error.capture());\n        final Throwable throwable = error.getValue();\n        final ArchiveEvent event = assertInstanceOf(ArchiveEvent.class, throwable);\n        assertEquals(AeronException.Category.WARN, event.category());\n        assertEquals(\"WARN - controlSessionId=\" + CONTROL_SESSION_ID + \" (controlResponseStreamId=\" +\n            CONTROL_PUBLICATION_STREAM_ID + \" controlResponseChannel=\" + CONTROL_PUBLICATION_CHANNEL +\n            \") terminated: \" + abortReason, event.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        ControlSession.SESSION_CLOSED_MSG,\n        ControlSession.RESPONSE_NOT_CONNECTED_MSG,\n        ControlSession.REQUEST_IMAGE_NOT_AVAILABLE_MSG + \" : abc\" })\n    void shouldNotLogWarningIfDisconnectedOrClosed(final String reason)\n    {\n        session.abort(reason);\n\n        session.close();\n\n        verifyNoInteractions(mockErrorHandler);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/DeleteSegmentsSessionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport org.agrona.ErrorHandler;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\n\nimport java.io.File;\nimport java.util.ArrayDeque;\nimport java.util.List;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.Archive.segmentFileName;\nimport static io.aeron.archive.codecs.RecordingSignal.DELETE;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nclass DeleteSegmentsSessionTest\n{\n    private final ControlSession controlSession = mock(ControlSession.class);\n    private final ControlResponseProxy controlResponseProxy = mock(ControlResponseProxy.class);\n    private final ErrorHandler errorHandler = mock(ErrorHandler.class);\n\n    @Test\n    void shouldComputeMaxDeletePosition()\n    {\n        final long recordingId = 18;\n        final long correlationId = 42;\n        final ArrayDeque<File> files = new ArrayDeque<>(List.of(\n            new File(segmentFileName(recordingId, 1024 * 1024)),\n            new File(segmentFileName(recordingId, 0) + \".del\"),\n            new File(segmentFileName(recordingId, correlationId)),\n            new File(segmentFileName(recordingId, -9999999999L)),\n            new File(segmentFileName(recordingId, 12345000000L) + \".del\"),\n            new File(segmentFileName(recordingId, 56678))));\n\n        final DeleteSegmentsSession deleteSegmentsSession = new DeleteSegmentsSession(\n            recordingId, correlationId, files, controlSession, errorHandler);\n\n        assertEquals(12345000000L, deleteSegmentsSession.maxDeletePosition());\n    }\n\n    @Test\n    void shouldRemoveSessionUponClose()\n    {\n        final long recordingId = 0;\n        final long correlationId = -4732948723L;\n        final ArrayDeque<File> files = new ArrayDeque<>(List.of());\n        final ArchiveConductor conductor = mock(ArchiveConductor.class);\n        when(controlSession.archiveConductor()).thenReturn(conductor);\n\n        final DeleteSegmentsSession deleteSegmentsSession = new DeleteSegmentsSession(\n            recordingId, correlationId, files, controlSession, errorHandler);\n\n        deleteSegmentsSession.close();\n\n        final InOrder inOrder = inOrder(controlSession, conductor);\n        inOrder.verify(controlSession).archiveConductor();\n        inOrder.verify(conductor).removeDeleteSegmentsSession(deleteSegmentsSession);\n        inOrder.verify(controlSession).sendSignal(correlationId, recordingId, NULL_VALUE, NULL_VALUE, DELETE);\n        inOrder.verifyNoMoreInteractions();\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/FailControlResponseListener.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.ControlResponseListener;\nimport io.aeron.archive.codecs.ControlResponseCode;\n\nimport static org.junit.jupiter.api.Assertions.fail;\n\nclass FailControlResponseListener implements ControlResponseListener\n{\n    public void onResponse(\n        final long controlSessionId,\n        final long correlationId,\n        final long relevantId,\n        final ControlResponseCode code,\n        final String errorMessage)\n    {\n        if (Aeron.NULL_VALUE != correlationId)\n        {\n            fail(\"controlSessionId=\" + controlSessionId + \", correlationId=\" + correlationId + \", relevantId=\" +\n                relevantId + \", responseCode=\" + code + \", errorMessage=\" + errorMessage);\n        }\n    }\n\n    public void onRecordingDescriptor(\n        final long controlSessionId,\n        final long correlationId,\n        final long recordingId,\n        final long startTimestamp,\n        final long stopTimestamp,\n        final long startPosition,\n        final long stopPosition,\n        final int initialTermId,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int mtuLength,\n        final int sessionId,\n        final int streamId,\n        final String strippedChannel,\n        final String originalChannel,\n        final String sourceIdentity)\n    {\n        fail(\"controlSessionId=\" + controlSessionId +\n            \", correlationId=\" + correlationId +\n            \", recordingId=\" + recordingId +\n            \", startTimestamp=\" + startTimestamp +\n            \", stopTimestamp=\" + stopTimestamp +\n            \", startPosition=\" + startPosition +\n            \", stopPosition=\" + stopPosition +\n            \", initialTermId=\" + initialTermId +\n            \", segmentFileLength=\" + segmentFileLength +\n            \", termBufferLength=\" + termBufferLength +\n            \", mtuLength=\" + mtuLength +\n            \", sessionId=\" + sessionId +\n            \", streamId=\" + streamId +\n            \", strippedChannel=\" + strippedChannel +\n            \", originalChannel=\" + originalChannel +\n            \", sourceIdentity=\" + sourceIdentity);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/FailRecordingEventsListener.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.client.RecordingEventsListener;\n\nimport static org.junit.jupiter.api.Assertions.fail;\n\nclass FailRecordingEventsListener implements RecordingEventsListener\n{\n    public void onStart(\n        final long recordingId,\n        final long startPosition,\n        final int sessionId,\n        final int streamId,\n        final String channel,\n        final String sourceIdentity)\n    {\n        fail(\"recordingId=\" + recordingId + \", startPosition=\" + startPosition + \", sessionId=\" + sessionId +\n            \", streamId=\" + streamId + \", channel=\" + channel + \", sourceIdentity=\" + sourceIdentity);\n    }\n\n    public void onProgress(final long recordingId, final long startPosition, final long position)\n    {\n        fail(\"recordingId=\" + recordingId + \", startPosition=\" + startPosition + \", position=\" + position);\n    }\n\n    public void onStop(final long recordingId, final long startPosition, final long stopPosition)\n    {\n        fail(\"recordingId=\" + recordingId + \", startPosition=\" + startPosition + \", stopPosition=\" + stopPosition);\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ListRecordingByIdSessionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderDecoder;\nimport io.aeron.archive.codecs.RecordingState;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.stubbing.Answer;\n\nimport java.io.File;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass ListRecordingByIdSessionTest\n{\n    private static final long CAPACITY = 1024 * 1024;\n    private static final int SEGMENT_FILE_SIZE = 128 * 1024 * 1024;\n    private final RecordingDescriptorDecoder recordingDescriptorDecoder = new RecordingDescriptorDecoder();\n    private final long[] recordingIds = new long[3];\n    private final File archiveDir = ArchiveTests.makeTestDirectory();\n    private final EpochClock clock = mock(EpochClock.class);\n\n    private Catalog catalog;\n    private final ControlSession controlSession = mock(ControlSession.class);\n    private final UnsafeBuffer descriptorBuffer = new UnsafeBuffer();\n\n    @BeforeEach\n    void before()\n    {\n        catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, null);\n        recordingIds[0] = catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 6, 1, \"channelG\", \"channelG?tag=f\", \"sourceA\");\n        recordingIds[1] = catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 7, 2, \"channelH\", \"channelH?tag=f\", \"sourceV\");\n        recordingIds[2] = catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 8, 3, \"channelK\", \"channelK?tag=f\", \"sourceB\");\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.close(catalog);\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    void shouldSendDescriptor()\n    {\n        final long correlationId = -53465834;\n        final ListRecordingByIdSession session =\n            new ListRecordingByIdSession(correlationId, recordingIds[1], catalog, controlSession, descriptorBuffer);\n\n        doAnswer(verifySendDescriptor())\n            .when(controlSession)\n            .sendDescriptor(eq(correlationId), any());\n\n        session.doWork();\n        verify(controlSession).sendDescriptor(eq(correlationId), any());\n        verifyNoMoreInteractions(controlSession);\n    }\n\n    @Test\n    void shouldSendRecordingUnknownOnFirst()\n    {\n        final long correlationId = 42;\n        final long unknownRecordingId = 17777;\n        final ListRecordingByIdSession session =\n            new ListRecordingByIdSession(correlationId, unknownRecordingId, catalog, controlSession, descriptorBuffer);\n\n        session.doWork();\n\n        verify(controlSession).sendRecordingUnknown(eq(correlationId), eq(unknownRecordingId));\n        verifyNoMoreInteractions(controlSession);\n    }\n\n    @Test\n    void shouldSendRecordingUnknownOnInvalidRecordingId()\n    {\n        final long correlationId = 42;\n        final long invalidatedRecordingId = recordingIds[1];\n        assertTrue(catalog.changeState(invalidatedRecordingId, RecordingState.INVALID));\n\n        final ListRecordingByIdSession session = new ListRecordingByIdSession(\n            correlationId, invalidatedRecordingId, catalog, controlSession, descriptorBuffer);\n\n        session.doWork();\n\n        verify(controlSession).sendRecordingUnknown(eq(correlationId), eq(invalidatedRecordingId));\n        verifyNoMoreInteractions(controlSession);\n    }\n\n    @Test\n    void shouldRetrySendingDescriptorUntilSuccess()\n    {\n        final long correlationId = 19;\n        final long recordingId = recordingIds[2];\n        when(controlSession.sendDescriptor(eq(correlationId), any())).thenReturn(false, false, false, true);\n\n        final ListRecordingByIdSession session =\n            new ListRecordingByIdSession(correlationId, recordingId, catalog, controlSession, descriptorBuffer);\n\n        while (!session.isDone())\n        {\n            assertEquals(1, session.doWork());\n        }\n        assertTrue(session.isDone());\n\n        assertEquals(0, session.doWork());\n\n        verify(controlSession, times(4)).sendDescriptor(eq(correlationId), any());\n        verifyNoMoreInteractions(controlSession);\n    }\n\n    @Test\n    void shouldSendRecordingDescriptorUnknownIfRecordingIsInvalidateBetweenRetries()\n    {\n        final long correlationId = 19;\n        final long recordingId = recordingIds[2];\n        when(controlSession.sendDescriptor(eq(correlationId), any())).thenReturn(false);\n\n        final ListRecordingByIdSession session =\n            new ListRecordingByIdSession(correlationId, recordingId, catalog, controlSession, descriptorBuffer);\n\n        assertEquals(1, session.doWork());\n        assertFalse(session.isDone());\n\n        assertTrue(catalog.changeState(recordingId, RecordingState.INVALID));\n\n        assertEquals(1, session.doWork());\n        assertTrue(session.isDone());\n\n        verify(controlSession).sendDescriptor(eq(correlationId), any());\n        verify(controlSession).sendRecordingUnknown(eq(correlationId), eq(recordingId));\n        verifyNoMoreInteractions(controlSession);\n    }\n\n    @Test\n    void shouldCloseActiveListing()\n    {\n        final ListRecordingByIdSession session =\n            new ListRecordingByIdSession(1, 111, catalog, controlSession, descriptorBuffer);\n\n        session.close();\n\n        verify(controlSession).activeListing(null);\n        verifyNoMoreInteractions(controlSession);\n    }\n\n    private Answer<Object> verifySendDescriptor()\n    {\n        return (invocation) ->\n        {\n            final UnsafeBuffer buffer = invocation.getArgument(1);\n\n            recordingDescriptorDecoder.wrap(\n                buffer,\n                RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.SCHEMA_VERSION);\n\n            return true;\n        };\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ListRecordingsForUriSessionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderDecoder;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.stubbing.Answer;\n\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nclass ListRecordingsForUriSessionTest\n{\n    private static final long CAPACITY = 1024 * 1024;\n    private static final int SEGMENT_FILE_SIZE = 128 * 1024 * 1024;\n    public static final byte[] LOCALHOST_BYTES = \"localhost\".getBytes(StandardCharsets.US_ASCII);\n    private final UnsafeBuffer descriptorBuffer = new UnsafeBuffer();\n    private final RecordingDescriptorDecoder recordingDescriptorDecoder = new RecordingDescriptorDecoder();\n    private final long[] matchingRecordingIds = new long[3];\n    private final File archiveDir = ArchiveTests.makeTestDirectory();\n    private final EpochClock clock = mock(EpochClock.class);\n\n    private Catalog catalog;\n    private final long correlationId = 1;\n    private final ControlSession controlSession = mock(ControlSession.class);\n\n    @BeforeEach\n    void before()\n    {\n        catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, null);\n        matchingRecordingIds[0] = catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 6, 1, \"localhost\", \"localhost?tag=f\", \"sourceA\");\n        catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 7, 1, \"channelA\", \"channel?tag=f\", \"sourceV\");\n        matchingRecordingIds[1] = catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 8, 1, \"localhost\", \"localhost?tag=f\", \"sourceB\");\n        catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 8, 1, \"channelB\", \"channelB?tag=f\", \"sourceB\");\n        matchingRecordingIds[2] = catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 8, 1, \"localhost\", \"localhost?tag=f\", \"sourceB\");\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.close(catalog);\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    void shouldSendAllDescriptors()\n    {\n        final ListRecordingsForUriSession session = new ListRecordingsForUriSession(\n            correlationId,\n            0,\n            3,\n            LOCALHOST_BYTES,\n            1,\n            catalog,\n            controlSession,\n            descriptorBuffer,\n            recordingDescriptorDecoder);\n\n        final MutableLong counter = new MutableLong(0);\n        doAnswer(verifySendDescriptor(counter))\n            .when(controlSession)\n            .sendDescriptor(eq(correlationId), any());\n\n        assertEquals(5, session.doWork());\n        verify(controlSession, times(3)).sendDescriptor(eq(correlationId), any());\n    }\n\n    @Test\n    void shouldSend2Descriptors()\n    {\n        final long fromRecordingId = 1;\n        final ListRecordingsForUriSession session = new ListRecordingsForUriSession(\n            correlationId,\n            fromRecordingId,\n            2,\n            LOCALHOST_BYTES,\n            1,\n            catalog,\n            controlSession,\n            descriptorBuffer,\n            recordingDescriptorDecoder);\n\n        final MutableLong counter = new MutableLong(fromRecordingId);\n        doAnswer(verifySendDescriptor(counter))\n            .when(controlSession)\n            .sendDescriptor(eq(correlationId), any());\n\n        assertEquals(4, session.doWork());\n        verify(controlSession, times(2)).sendDescriptor(eq(correlationId), any());\n    }\n\n    @Test\n    void shouldSend2DescriptorsAndRecordingUnknown()\n    {\n        final ListRecordingsForUriSession session = new ListRecordingsForUriSession(\n            correlationId,\n            1,\n            5,\n            LOCALHOST_BYTES,\n            1,\n            catalog,\n            controlSession,\n            descriptorBuffer,\n            recordingDescriptorDecoder);\n\n        final MutableLong counter = new MutableLong(1);\n        doAnswer(verifySendDescriptor(counter))\n            .when(controlSession)\n            .sendDescriptor(eq(correlationId), any());\n\n        assertEquals(4, session.doWork());\n        verify(controlSession, times(2)).sendDescriptor(eq(correlationId), any());\n        verify(controlSession).sendRecordingUnknown(eq(correlationId), eq(5L));\n    }\n\n    @Test\n    void shouldSendRecordingUnknown()\n    {\n        final ListRecordingsForUriSession session = new ListRecordingsForUriSession(\n            correlationId,\n            1,\n            3,\n            \"notChannel\".getBytes(StandardCharsets.US_ASCII),\n            1,\n            catalog,\n            controlSession,\n            descriptorBuffer,\n            recordingDescriptorDecoder);\n\n        assertEquals(4, session.doWork());\n\n        verify(controlSession, never()).sendDescriptor(eq(correlationId), any());\n        verify(controlSession).sendRecordingUnknown(eq(correlationId), eq(5L));\n    }\n\n    @Test\n    void shouldSendUnknownOnFirst()\n    {\n        when(controlSession.maxPayloadLength()).thenReturn(4096 - 32);\n\n        final ListRecordingsForUriSession session = new ListRecordingsForUriSession(\n            correlationId,\n            5,\n            3,\n            LOCALHOST_BYTES,\n            1,\n            catalog,\n            controlSession,\n            descriptorBuffer,\n            recordingDescriptorDecoder);\n\n        assertEquals(0, session.doWork());\n\n        verify(controlSession, never()).sendDescriptor(eq(correlationId), any());\n        verify(controlSession).sendRecordingUnknown(eq(correlationId), eq(5L));\n    }\n\n    private Answer<Object> verifySendDescriptor(final MutableLong counter)\n    {\n        return (invocation) ->\n        {\n            final UnsafeBuffer buffer = invocation.getArgument(1);\n            recordingDescriptorDecoder.wrap(\n                buffer,\n                RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.SCHEMA_VERSION);\n\n            final int i = counter.intValue();\n            assertEquals(matchingRecordingIds[i], recordingDescriptorDecoder.recordingId());\n            counter.set(i + 1);\n\n            return true;\n        };\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ListRecordingsSessionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.codecs.RecordingDescriptorDecoder;\nimport io.aeron.archive.codecs.RecordingDescriptorHeaderDecoder;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.stubbing.Answer;\n\nimport java.io.File;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nclass ListRecordingsSessionTest\n{\n    private static final long CAPACITY = 1024 * 1024;\n    private static final int SEGMENT_FILE_SIZE = 128 * 1024 * 1024;\n    private final RecordingDescriptorDecoder recordingDescriptorDecoder = new RecordingDescriptorDecoder();\n    private final long[] recordingIds = new long[3];\n    private final File archiveDir = ArchiveTests.makeTestDirectory();\n    private final EpochClock clock = mock(EpochClock.class);\n\n    private Catalog catalog;\n    private final long correlationId = 1;\n    private final ControlSession controlSession = mock(ControlSession.class);\n    private final UnsafeBuffer descriptorBuffer = new UnsafeBuffer();\n\n    @BeforeEach\n    void before()\n    {\n        catalog = new Catalog(archiveDir, null, 0, CAPACITY, clock, null, null);\n        recordingIds[0] = catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 6, 1, \"channelG\", \"channelG?tag=f\", \"sourceA\");\n        recordingIds[1] = catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 7, 2, \"channelH\", \"channelH?tag=f\", \"sourceV\");\n        recordingIds[2] = catalog.addNewRecording(\n            0L, 0L, 0, SEGMENT_FILE_SIZE, 4096, 1024, 8, 3, \"channelK\", \"channelK?tag=f\", \"sourceB\");\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.close(catalog);\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    void shouldSendAllDescriptors()\n    {\n        final ListRecordingsSession session = new ListRecordingsSession(\n            correlationId,\n            0,\n            3,\n            catalog,\n            controlSession,\n            descriptorBuffer\n        );\n\n        final MutableLong counter = new MutableLong(0);\n        doAnswer(verifySendDescriptor(counter))\n            .when(controlSession)\n            .sendDescriptor(eq(correlationId), any());\n\n        session.doWork();\n        verify(controlSession, times(3)).sendDescriptor(eq(correlationId), any());\n    }\n\n    @Test\n    void shouldSend2Descriptors()\n    {\n        final int fromId = 1;\n        final ListRecordingsSession session = new ListRecordingsSession(\n            correlationId,\n            fromId,\n            2,\n            catalog,\n            controlSession,\n            descriptorBuffer);\n\n        final MutableLong counter = new MutableLong(fromId);\n        doAnswer(verifySendDescriptor(counter))\n            .when(controlSession)\n            .sendDescriptor(eq(correlationId), any());\n\n        session.doWork();\n        verify(controlSession, times(2)).sendDescriptor(eq(correlationId), any());\n    }\n\n    @Test\n    void shouldSendTwoDescriptorsThenRecordingUnknown()\n    {\n        final ListRecordingsSession session = new ListRecordingsSession(\n            correlationId,\n            1,\n            3,\n            catalog,\n            controlSession,\n            descriptorBuffer);\n\n        final MutableLong counter = new MutableLong(1);\n        doAnswer(verifySendDescriptor(counter))\n            .when(controlSession)\n            .sendDescriptor(eq(correlationId), any());\n\n        session.doWork();\n\n        verify(controlSession, times(2)).sendDescriptor(eq(correlationId), any());\n        verify(controlSession).sendRecordingUnknown(eq(correlationId), eq(3L));\n    }\n\n    @Test\n    void shouldSendRecordingUnknownOnFirst()\n    {\n        final ListRecordingsSession session = new ListRecordingsSession(\n            correlationId,\n            3,\n            3,\n            catalog,\n            controlSession,\n            descriptorBuffer);\n\n        session.doWork();\n\n        verify(controlSession, never()).sendDescriptor(eq(correlationId), any());\n        verify(controlSession).sendRecordingUnknown(eq(correlationId), eq(3L));\n    }\n\n    private Answer<Object> verifySendDescriptor(final MutableLong counter)\n    {\n        return (invocation) ->\n        {\n            final UnsafeBuffer buffer = invocation.getArgument(1);\n\n            recordingDescriptorDecoder.wrap(\n                buffer,\n                RecordingDescriptorHeaderDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.BLOCK_LENGTH,\n                RecordingDescriptorDecoder.SCHEMA_VERSION);\n\n            final int i = counter.intValue();\n            assertEquals(recordingIds[i], recordingDescriptorDecoder.recordingId());\n            counter.set(i + 1);\n\n            return true;\n        };\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/RecordingSessionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Counter;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.logbuffer.BlockHandler;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.Archive.segmentFileName;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static java.nio.file.StandardOpenOption.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass RecordingSessionTest\n{\n    private static final int RECORDED_BLOCK_LENGTH = 100;\n    private static final long RECORDING_ID = 12345;\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int SEGMENT_LENGTH = TERM_BUFFER_LENGTH;\n\n    private static final String CHANNEL = \"channel\";\n    private static final String SOURCE_IDENTITY = \"sourceIdentity\";\n    private static final int STREAM_ID = 54321;\n\n    private static final int SESSION_ID = 12345;\n    private static final int TERM_OFFSET = 1024;\n    private static final int MTU_LENGTH = 1024;\n    private static final long START_POSITION = TERM_OFFSET;\n    private static final int INITIAL_TERM_ID = 0;\n    private static final ControlSession CONTROL_SESSION = mock(ControlSession.class);\n\n    private final RecordingEventsProxy recordingEventsProxy = mock(RecordingEventsProxy.class);\n    private final Counter mockPosition = mock(Counter.class);\n    private final Image image = mockImage(mockSubscription());\n    private final File archiveDir = ArchiveTests.makeTestDirectory();\n    private FileChannel mockLogBufferChannel;\n    private UnsafeBuffer mockLogBufferMapped;\n    private Path termFilePath;\n    private final EpochClock epochClock = mock(EpochClock.class);\n    private final NanoClock nanoClock = new NanoClock()\n    {\n        private long timeNs = 1;\n\n        public long nanoTime()\n        {\n            timeNs = timeNs * 2;\n            return timeNs;\n        }\n    };\n    private Archive.Context context;\n    private long positionLong;\n\n    @BeforeEach\n    void before() throws Exception\n    {\n        when(mockPosition.getPlain()).then((invocation) -> positionLong);\n        when(mockPosition.get()).then((invocation) -> positionLong);\n        doAnswer(\n            (invocation) ->\n            {\n                positionLong = invocation.getArgument(0);\n                return null;\n            })\n            .when(mockPosition).setRelease(anyLong());\n\n        termFilePath = Files.createTempFile(\"test.rec\", \"sourceIdentity\");\n\n        mockLogBufferChannel = FileChannel.open(termFilePath, CREATE, READ, WRITE);\n        mockLogBufferMapped = new UnsafeBuffer(\n            mockLogBufferChannel.map(FileChannel.MapMode.READ_WRITE, 0, TERM_BUFFER_LENGTH));\n\n        final DataHeaderFlyweight headerFlyweight = new DataHeaderFlyweight();\n        headerFlyweight.wrap(mockLogBufferMapped, TERM_OFFSET, DataHeaderFlyweight.HEADER_LENGTH);\n        headerFlyweight\n            .termOffset(TERM_OFFSET)\n            .sessionId(SESSION_ID)\n            .streamId(STREAM_ID)\n            .headerType(DataHeaderFlyweight.HDR_TYPE_DATA)\n            .frameLength(RECORDED_BLOCK_LENGTH);\n\n        context = new Archive.Context()\n            .segmentFileLength(SEGMENT_LENGTH)\n            .archiveDir(archiveDir)\n            .epochClock(epochClock)\n            .nanoClock(nanoClock);\n\n        when(CONTROL_SESSION.sessionId()).thenReturn(123L);\n    }\n\n    @AfterEach\n    void after()\n    {\n        IoUtil.unmap(mockLogBufferMapped.byteBuffer());\n        CloseHelper.close(mockLogBufferChannel);\n        IoUtil.delete(archiveDir, false);\n        IoUtil.delete(termFilePath.toFile(), false);\n    }\n\n    @Test\n    void shouldRecordFragmentsFromImage()\n    {\n        final RecordingSession session = new RecordingSession(\n            NULL_VALUE,\n            RECORDING_ID,\n            START_POSITION,\n            SEGMENT_LENGTH,\n            CHANNEL,\n            recordingEventsProxy,\n            image,\n            mockPosition,\n            context,\n            CONTROL_SESSION,\n            false,\n            mock(ArchiveConductor.Recorder.class));\n\n        assertEquals(RECORDING_ID, session.sessionId());\n\n        session.doWork();\n\n        when(image.blockPoll(any(), anyInt())).thenAnswer(\n            (invocation) ->\n            {\n                final BlockHandler handle = invocation.getArgument(0);\n                if (null == handle)\n                {\n                    return 0;\n                }\n\n                handle.onBlock(\n                    mockLogBufferMapped,\n                    TERM_OFFSET,\n                    RECORDED_BLOCK_LENGTH,\n                    SESSION_ID,\n                    0);\n\n                return RECORDED_BLOCK_LENGTH;\n            });\n\n        assertNotEquals(0, session.doWork(), \"Expect some work\");\n        assertNotEquals(0, session.doWork(), \"Expect some work\");\n\n        final File segmentFile = new File(archiveDir, segmentFileName(RECORDING_ID, 0));\n        assertTrue(segmentFile.exists());\n\n        final RecordingSummary recordingSummary = new RecordingSummary();\n        recordingSummary.recordingId = RECORDING_ID;\n        recordingSummary.startPosition = START_POSITION;\n        recordingSummary.segmentFileLength = context.segmentFileLength();\n        recordingSummary.initialTermId = INITIAL_TERM_ID;\n        recordingSummary.termBufferLength = TERM_BUFFER_LENGTH;\n        recordingSummary.streamId = STREAM_ID;\n        recordingSummary.sessionId = SESSION_ID;\n        recordingSummary.stopPosition = START_POSITION + RECORDED_BLOCK_LENGTH;\n\n        try (RecordingReader reader = new RecordingReader(\n            recordingSummary, archiveDir, NULL_POSITION, AeronArchive.NULL_LENGTH))\n        {\n            final int fragments = reader.poll(\n                (buffer, offset, length, frameType, flags, reservedValue) ->\n                {\n                    final int frameOffset = offset - DataHeaderFlyweight.HEADER_LENGTH;\n                    assertEquals(TERM_OFFSET, frameOffset);\n                    assertEquals(RECORDED_BLOCK_LENGTH, FrameDescriptor.frameLength(buffer, frameOffset));\n                    assertEquals(RECORDED_BLOCK_LENGTH - DataHeaderFlyweight.HEADER_LENGTH, length);\n                },\n                1);\n\n            assertEquals(1, fragments);\n        }\n\n        when(image.blockPoll(any(), anyInt())).thenReturn(0);\n        assertEquals(0, session.doWork(), \"Expect no work\");\n\n        when(image.isClosed()).thenReturn(true);\n        session.doWork();\n        session.doWork();\n        assertTrue(session.isDone());\n        session.close();\n    }\n\n    private static Subscription mockSubscription()\n    {\n        final Subscription subscription = mock(Subscription.class);\n\n        when(subscription.channel()).thenReturn(CHANNEL);\n        when(subscription.streamId()).thenReturn(STREAM_ID);\n\n        return subscription;\n    }\n\n    private static Image mockImage(final Subscription subscription)\n    {\n        final Image image = mock(Image.class);\n\n        when(image.sessionId()).thenReturn(SESSION_ID);\n        when(image.initialTermId()).thenReturn(INITIAL_TERM_ID);\n        when(image.sourceIdentity()).thenReturn(SOURCE_IDENTITY);\n        when(image.termBufferLength()).thenReturn(TERM_BUFFER_LENGTH);\n        when(image.mtuLength()).thenReturn(MTU_LENGTH);\n        when(image.joinPosition()).thenReturn(START_POSITION);\n        when(image.subscription()).thenReturn(subscription);\n\n        return image;\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/RecordingWriterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Image;\nimport io.aeron.archive.Archive.Context;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.BitUtil;\nimport org.agrona.IoUtil;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport static io.aeron.archive.Archive.segmentFileName;\nimport static io.aeron.archive.checksum.Checksums.crc32;\nimport static io.aeron.archive.client.ArchiveException.GENERIC;\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_DATA;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_PAD;\nimport static java.nio.ByteBuffer.allocate;\nimport static java.nio.ByteBuffer.allocateDirect;\nimport static java.nio.file.Files.delete;\nimport static java.nio.file.Files.readAllBytes;\nimport static java.util.Arrays.fill;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass RecordingWriterTest\n{\n    private static final int TERM_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int SEGMENT_LENGTH = TERM_LENGTH * 4;\n\n    private File archiveDir;\n\n    @BeforeEach\n    void before()\n    {\n        archiveDir = ArchiveTests.makeTestDirectory();\n    }\n\n    @AfterEach\n    void after()\n    {\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    void initShouldOpenAFileChannel() throws IOException\n    {\n        final Image image = mockImage(0L);\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            1, 0, SEGMENT_LENGTH, image, new Context().archiveDir(archiveDir), null);\n        final File segmentFile = segmentFile(1, 0);\n        assertFalse(segmentFile.exists());\n\n        try\n        {\n            recordingWriter.init();\n            assertTrue(segmentFile.exists());\n        }\n        finally\n        {\n            recordingWriter.close();\n        }\n    }\n\n    @Test\n    void initThrowsIOExceptionIfItCannotOpenAFileChannel() throws IOException\n    {\n        final File notADirectory = new File(archiveDir, \"dummy.txt\");\n        assertTrue(notADirectory.createNewFile());\n\n        final Image image = mockImage(0L);\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            1, 0, SEGMENT_LENGTH, image, new Context().archiveDir(notADirectory), null);\n\n        assertThrows(IOException.class, recordingWriter::init);\n    }\n\n    @Test\n    void closeShouldCloseTheUnderlyingFile() throws IOException\n    {\n        final Image image = mockImage(0L);\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            1, 0, SEGMENT_LENGTH, image, new Context().archiveDir(archiveDir), null);\n        recordingWriter.init();\n\n        recordingWriter.close();\n\n        final File segmentFile = segmentFile(1, 0);\n        assertTrue(segmentFile.exists());\n        delete(segmentFile.toPath()); // can delete since the underlying FileChannel was already closed\n    }\n\n    @Test\n    void onBlockThrowsNullPointerExceptionIfInitWasNotCalled()\n    {\n        final Image image = mockImage(0L);\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            1,\n            0,\n            SEGMENT_LENGTH,\n            image,\n            new Context().archiveDir(archiveDir),\n            mock(ArchiveConductor.Recorder.class));\n\n        assertThrows(\n            NullPointerException.class,\n            () -> recordingWriter.onBlock(new UnsafeBuffer(allocate(32)), 0, 10, 5, 8));\n    }\n\n    @Test\n    void onBlockThrowsArchiveExceptionIfCurrentThreadWasInterrupted() throws IOException\n    {\n        final Image image = mockImage(0L);\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            1, 0, SEGMENT_LENGTH, image, new Context().archiveDir(archiveDir)\n            .nanoClock(SystemNanoClock.INSTANCE), mock(ArchiveConductor.Recorder.class));\n        recordingWriter.init();\n\n        assertFalse(Thread.interrupted());\n        try\n        {\n            Thread.currentThread().interrupt();\n            final ArchiveException exception = assertThrows(\n                ArchiveException.class,\n                () -> recordingWriter.onBlock(new UnsafeBuffer(allocate(32)), 0, 10, 5, 8));\n            assertEquals(GENERIC, exception.errorCode());\n            assertEquals(\"ERROR - file closed by interrupt, recording aborted\", exception.getMessage());\n        }\n        finally\n        {\n            assertTrue(Thread.interrupted());\n        }\n    }\n\n    @Test\n    void onBlockShouldWriteHeaderAndContentsOfTheNonPaddingFrame() throws IOException\n    {\n        final Image image = mockImage(0L);\n        final NanoClock clock = mock(NanoClock.class);\n        when(clock.nanoTime()).thenReturn(3L, 150L, 200L, 999L);\n        final ArchiveConductor.Recorder mockRecorder = mock(ArchiveConductor.Recorder.class);\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            1, 0, SEGMENT_LENGTH, image, new Context().archiveDir(archiveDir)\n            .nanoClock(clock), mockRecorder);\n        recordingWriter.init();\n        final UnsafeBuffer termBuffer = new UnsafeBuffer(allocate(128));\n        frameType(termBuffer, 0, HDR_TYPE_DATA);\n        frameLengthOrdered(termBuffer, 0, 128);\n        final byte[] data = new byte[96];\n        fill(data, (byte)7);\n        termBuffer.putBytes(HEADER_LENGTH, data);\n\n        recordingWriter.onBlock(termBuffer, 0, 128, -1, -1);\n\n        recordingWriter.close();\n\n        verify(mockRecorder).bytesWritten(128);\n        verify(mockRecorder).writeTimeNs(147);\n\n        final File segmentFile = segmentFile(1, 0);\n        assertTrue(segmentFile.exists());\n        assertEquals(SEGMENT_LENGTH, segmentFile.length());\n\n        final UnsafeBuffer fileBuffer = new UnsafeBuffer();\n        fileBuffer.wrap(readAllBytes(segmentFile.toPath()));\n        assertEquals(HDR_TYPE_DATA, frameType(fileBuffer, 0));\n        assertEquals(128, frameLength(fileBuffer, 0));\n        assertEquals(0, frameSessionId(fileBuffer, 0));\n\n        final byte[] fileBytes = new byte[96];\n        fileBuffer.getBytes(HEADER_LENGTH, fileBytes, 0, 96);\n        assertArrayEquals(data, fileBytes);\n    }\n\n    @Test\n    void onBlockShouldWriteHeaderOfThePaddingFrameAndAdvanceFilePositionByThePaddingLength() throws IOException\n    {\n        final int segmentOffset = 96;\n        final long startPosition = 7 * TERM_LENGTH + segmentOffset;\n        final Image image = mockImage(startPosition);\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            5,\n            startPosition,\n            SEGMENT_LENGTH,\n            image,\n            new Context().archiveDir(archiveDir).nanoClock(SystemNanoClock.INSTANCE),\n            mock(ArchiveConductor.Recorder.class));\n        recordingWriter.init();\n        final UnsafeBuffer termBuffer = new UnsafeBuffer(allocate(1024));\n        frameType(termBuffer, 0, HDR_TYPE_PAD);\n        frameLengthOrdered(termBuffer, 0, 1024);\n        frameSessionId(termBuffer, 0, 111);\n\n        final byte[] data = new byte[992];\n        fill(data, (byte)-1);\n        termBuffer.putBytes(HEADER_LENGTH, data);\n\n        recordingWriter.onBlock(termBuffer, 0, 1024, -1, -1);\n\n        recordingWriter.close();\n        final File segmentFile = segmentFile(5, startPosition);\n        assertTrue(segmentFile.exists());\n        assertEquals(SEGMENT_LENGTH, segmentFile.length());\n\n        final UnsafeBuffer fileBuffer = new UnsafeBuffer();\n        fileBuffer.wrap(readAllBytes(segmentFile.toPath()));\n\n        final byte[] preamble = new byte[segmentOffset];\n        fileBuffer.getBytes(0, preamble, 0, segmentOffset);\n        assertArrayEquals(new byte[segmentOffset], preamble);\n        assertEquals(HDR_TYPE_PAD, frameType(fileBuffer, segmentOffset));\n        assertEquals(1024, frameLength(fileBuffer, segmentOffset));\n        assertEquals(111, frameSessionId(fileBuffer, segmentOffset));\n\n        final byte[] fileBytes = new byte[992];\n        fileBuffer.getBytes(segmentOffset + HEADER_LENGTH, fileBytes, 0, 992);\n        assertArrayEquals(new byte[992], fileBytes);\n    }\n\n    @Test\n    void onBlockShouldRollOverToTheNextSegmentFile() throws IOException\n    {\n        final Image image = mockImage(0L);\n        final NanoClock clock = mock(NanoClock.class);\n        final MutableLong time = new MutableLong(100);\n        doAnswer((invocation) ->\n            time.addAndGet(BitUtil.isEven(time.get()) ? 111 : 224)).when(clock).nanoTime();\n        final ArchiveConductor.Recorder mockRecorder = mock(ArchiveConductor.Recorder.class);\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            13, 0, SEGMENT_LENGTH, image, new Context().archiveDir(archiveDir)\n            .nanoClock(clock), mockRecorder);\n        recordingWriter.init();\n\n        final byte[] data1 = new byte[992];\n        fill(data1, (byte)13);\n\n        final UnsafeBuffer termBuffer = new UnsafeBuffer(allocate(TERM_LENGTH));\n        frameType(termBuffer, 0, HDR_TYPE_DATA);\n        frameLengthOrdered(termBuffer, 0, 1024);\n        termBuffer.putBytes(HEADER_LENGTH, data1);\n\n        for (int i = 0; i < SEGMENT_LENGTH / 1024; i++)\n        {\n            recordingWriter.onBlock(termBuffer, 0, 1024, -1, -1);\n        }\n\n        frameType(termBuffer, 0, HDR_TYPE_DATA);\n        frameLengthOrdered(termBuffer, 0, 192);\n        final byte[] data2 = new byte[160];\n        fill(data2, (byte)22);\n        termBuffer.putBytes(HEADER_LENGTH, data2);\n\n        recordingWriter.onBlock(termBuffer, 0, 192, -1, -1);\n        recordingWriter.close();\n\n        final File segmentFile1 = segmentFile(13, 0);\n        assertTrue(segmentFile1.exists());\n        assertEquals(SEGMENT_LENGTH, segmentFile1.length());\n\n        final UnsafeBuffer fileBuffer = new UnsafeBuffer();\n        fileBuffer.wrap(readAllBytes(segmentFile1.toPath()));\n        assertEquals(HDR_TYPE_DATA, frameType(fileBuffer, 0));\n        assertEquals(1024, frameLength(fileBuffer, 0));\n\n        byte[] fileBytes = new byte[992];\n        fileBuffer.getBytes(HEADER_LENGTH, fileBytes, 0, 992);\n        assertArrayEquals(data1, fileBytes);\n\n        final File segmentFile2 = segmentFile(13, SEGMENT_LENGTH);\n        assertTrue(segmentFile2.exists());\n        assertEquals(SEGMENT_LENGTH, segmentFile2.length());\n        fileBuffer.wrap(readAllBytes(segmentFile2.toPath()));\n        assertEquals(HDR_TYPE_DATA, frameType(fileBuffer, 0));\n        assertEquals(192, frameLength(fileBuffer, 0));\n        fileBytes = new byte[160];\n        fileBuffer.getBytes(HEADER_LENGTH, fileBytes, 0, 160);\n        assertArrayEquals(data2, fileBytes);\n    }\n\n    @Test\n    void onBlockShouldComputeCrcUsingTheChecksumBuffer() throws IOException\n    {\n        final Image image = mockImage(0L);\n        final Context ctx = new Context()\n            .archiveDir(archiveDir)\n            .recordChecksumBuffer(new UnsafeBuffer(allocateDirect(512)))\n            .recordChecksum(crc32())\n            .nanoClock(SystemNanoClock.INSTANCE);\n\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            1, 0, SEGMENT_LENGTH, image, ctx, mock(ArchiveConductor.Recorder.class));\n\n        recordingWriter.init();\n\n        final UnsafeBuffer termBuffer = new UnsafeBuffer(allocateDirect(512));\n        frameType(termBuffer, 96, HDR_TYPE_DATA);\n        frameTermId(termBuffer, 96, 96);\n        frameLengthOrdered(termBuffer, 96, 64);\n        frameSessionId(termBuffer, 96, 96);\n        termBuffer.setMemory(96 + HEADER_LENGTH, 32, (byte)96);\n        frameType(termBuffer, 160, HDR_TYPE_DATA);\n        frameTermId(termBuffer, 160, 160);\n        frameLengthOrdered(termBuffer, 160, 288);\n        frameSessionId(termBuffer, 160, 160);\n        termBuffer.setMemory(160 + HEADER_LENGTH, 256, (byte)160);\n\n        recordingWriter.onBlock(termBuffer, 96, 352, -1, -1);\n        recordingWriter.close();\n\n        final File segmentFile = segmentFile(1, 0);\n        assertTrue(segmentFile.exists());\n        assertEquals(SEGMENT_LENGTH, segmentFile.length());\n\n        final UnsafeBuffer fileBuffer = new UnsafeBuffer();\n        fileBuffer.wrap(readAllBytes(segmentFile.toPath()));\n        assertEquals(HDR_TYPE_DATA, frameType(fileBuffer, 0));\n        assertEquals(96, frameTermId(fileBuffer, 0));\n        assertEquals(64, frameLength(fileBuffer, 0));\n        assertEquals(\n            ctx.recordChecksum().compute(termBuffer.addressOffset(), 96 + HEADER_LENGTH, 32),\n            frameSessionId(fileBuffer, 0));\n        assertEquals(HDR_TYPE_DATA, frameType(fileBuffer, 64));\n        assertEquals(160, frameTermId(fileBuffer, 64));\n        assertEquals(288, frameLength(fileBuffer, 64));\n        assertEquals(\n            ctx.recordChecksum().compute(termBuffer.addressOffset(), 160 + HEADER_LENGTH, 256),\n            frameSessionId(fileBuffer, 64));\n        // Ensure that the source buffer was not modified\n        assertEquals(96, frameSessionId(termBuffer, 96));\n        assertEquals(160, frameSessionId(termBuffer, 160));\n    }\n\n    @Test\n    void onBlockShouldNotComputeCrcForThePaddingFrame() throws IOException\n    {\n        final Image image = mockImage(0L);\n        final Context ctx = new Context()\n            .archiveDir(archiveDir)\n            .recordChecksumBuffer(new UnsafeBuffer(allocateDirect(512)))\n            .recordChecksum(crc32())\n            .nanoClock(SystemNanoClock.INSTANCE);\n        final RecordingWriter recordingWriter = new RecordingWriter(\n            1, 0, SEGMENT_LENGTH, image, ctx, mock(ArchiveConductor.Recorder.class));\n\n        recordingWriter.init();\n\n        final UnsafeBuffer termBuffer = new UnsafeBuffer(allocate(512));\n        final int length = 128;\n        final byte[] data = new byte[length - HEADER_LENGTH];\n\n        final int sessionId = 5;\n        final int termId = 18;\n        fill(data, (byte)99);\n        frameType(termBuffer, 0, HDR_TYPE_PAD);\n        frameTermId(termBuffer, 0, termId);\n        frameLengthOrdered(termBuffer, 0, length);\n        frameSessionId(termBuffer, 0, sessionId);\n        termBuffer.putBytes(HEADER_LENGTH, data);\n\n        recordingWriter.onBlock(termBuffer, 0, HEADER_LENGTH, -1, -1);\n        recordingWriter.close();\n\n        final File segmentFile = segmentFile(1, 0);\n        assertTrue(segmentFile.exists());\n        assertEquals(SEGMENT_LENGTH, segmentFile.length());\n\n        final UnsafeBuffer fileBuffer = new UnsafeBuffer();\n        fileBuffer.wrap(readAllBytes(segmentFile.toPath()));\n        assertEquals(HDR_TYPE_PAD, frameType(fileBuffer, 0));\n        assertEquals(termId, frameTermId(fileBuffer, 0));\n        assertEquals(length, frameLength(fileBuffer, 0));\n        assertEquals(sessionId, frameSessionId(fileBuffer, 0));\n    }\n\n    private Image mockImage(final long joinPosition)\n    {\n        final Image image = mock(Image.class);\n        when(image.termBufferLength()).thenReturn(TERM_LENGTH);\n        when(image.joinPosition()).thenReturn(joinPosition);\n        return image;\n    }\n\n    private File segmentFile(final int recordingId, final long position)\n    {\n        final long segmentBasePosition = position - (position & (TERM_LENGTH - 1));\n        return new File(archiveDir, segmentFileName(recordingId, segmentBasePosition));\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/ReplaySessionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.checksum.Checksums;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.IoUtil;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Publication.BACK_PRESSURED;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.logbuffer.FrameDescriptor.BEGIN_FRAG_FLAG;\nimport static io.aeron.logbuffer.FrameDescriptor.END_FRAG_FLAG;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.UNFRAGMENTED;\nimport static io.aeron.logbuffer.FrameDescriptor.frameFlags;\nimport static io.aeron.logbuffer.FrameDescriptor.frameLength;\nimport static io.aeron.logbuffer.FrameDescriptor.frameType;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_DATA;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_PAD;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.DataHeaderFlyweight.RESERVED_VALUE_OFFSET;\nimport static io.aeron.protocol.DataHeaderFlyweight.SESSION_ID_FIELD_OFFSET;\nimport static io.aeron.protocol.DataHeaderFlyweight.STREAM_ID_FIELD_OFFSET;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.align;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ReplaySessionTest\n{\n    private static final int RECORDING_ID = 0;\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int SEGMENT_LENGTH = TERM_BUFFER_LENGTH;\n    private static final int INITIAL_TERM_ID = 8231773;\n    private static final int INITIAL_TERM_OFFSET = 1024;\n    private static final long REPLAY_ID = 1;\n    private static final long START_POSITION = INITIAL_TERM_OFFSET;\n    private static final long JOIN_POSITION = START_POSITION;\n    private static final long RECORDING_POSITION = INITIAL_TERM_OFFSET;\n    private static final long TIME = 0;\n    private static final long CONNECT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);\n    private static final int MTU_LENGTH = 4096;\n    private static final int FRAME_LENGTH = 1024;\n    private static final int SESSION_ID = 1;\n    private static final int STREAM_ID = 1001;\n\n    private final Image mockImage = mock(Image.class);\n    private final ExclusivePublication mockReplayPub = mock(ExclusivePublication.class);\n    private final ControlSession mockControlSession = mock(ControlSession.class);\n    private final ExclusivePublication mockPublication = mock(ExclusivePublication.class);\n    private final ArchiveConductor mockArchiveConductor = mock(ArchiveConductor.class);\n    private final Counter recordingPositionCounter = mock(Counter.class);\n    private final UnsafeBuffer replayBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_BUFFER_LENGTH));\n    private int messageCounter = 0;\n    private int offerBlockOffset = 0;\n\n    private final RecordingSummary recordingSummary = new RecordingSummary();\n    private final File archiveDir = ArchiveTests.makeTestDirectory();\n    private final ControlResponseProxy proxy = mock(ControlResponseProxy.class);\n    private final CachedEpochClock epochClock = new CachedEpochClock();\n    private final NanoClock nanoClock = new NanoClock()\n    {\n        private long time = 10;\n\n        public long nanoTime()\n        {\n            final long result = time;\n            time *= 2;\n            return result;\n        }\n    };\n    private final CountersReader mockCountersReader = mock(CountersReader.class);\n    private final CountedErrorHandler countedErrorHandler = mock(CountedErrorHandler.class);\n    private Archive.Context context;\n    private long recordingPosition;\n\n    @BeforeEach\n    void before() throws IOException\n    {\n        context = new Archive.Context()\n            .segmentFileLength(SEGMENT_LENGTH)\n            .archiveDir(archiveDir)\n            .epochClock(epochClock)\n            .nanoClock(nanoClock)\n            .countedErrorHandler(countedErrorHandler);\n\n        when(recordingPositionCounter.get()).then((invocation) -> recordingPosition);\n        when(mockControlSession.archiveConductor()).thenReturn(mockArchiveConductor);\n        when(mockControlSession.controlPublication()).thenReturn(mockPublication);\n        when(mockPublication.channel()).thenReturn(\"{some channel}\");\n        when(mockArchiveConductor.context()).thenReturn(context);\n        when(mockReplayPub.termBufferLength()).thenReturn(TERM_BUFFER_LENGTH);\n        when(mockReplayPub.positionBitsToShift())\n            .thenReturn(LogBufferDescriptor.positionBitsToShift(TERM_BUFFER_LENGTH));\n        when(mockReplayPub.initialTermId()).thenReturn(INITIAL_TERM_ID);\n        when(mockReplayPub.availableWindow()).thenReturn((long)TERM_BUFFER_LENGTH / 2);\n        when(mockImage.termBufferLength()).thenReturn(TERM_BUFFER_LENGTH);\n        when(mockImage.joinPosition()).thenReturn(JOIN_POSITION);\n\n        recordingSummary.recordingId = RECORDING_ID;\n        recordingSummary.startPosition = START_POSITION;\n        recordingSummary.segmentFileLength = context.segmentFileLength();\n        recordingSummary.initialTermId = INITIAL_TERM_ID;\n        recordingSummary.termBufferLength = TERM_BUFFER_LENGTH;\n        recordingSummary.mtuLength = MTU_LENGTH;\n        recordingSummary.streamId = STREAM_ID;\n        recordingSummary.sessionId = SESSION_ID;\n\n        final RecordingWriter writer = new RecordingWriter(\n            RECORDING_ID,\n            START_POSITION,\n            SEGMENT_LENGTH,\n            mockImage,\n            context,\n            mock(ArchiveConductor.Recorder.class));\n\n        writer.init();\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_BUFFER_LENGTH));\n\n        final DataHeaderFlyweight headerFwt = new DataHeaderFlyweight();\n        final Header header = new Header(INITIAL_TERM_ID, Integer.numberOfLeadingZeros(TERM_BUFFER_LENGTH));\n        header.buffer(buffer);\n\n        recordFragment(writer, buffer, headerFwt, header, INITIAL_TERM_OFFSET, FRAME_LENGTH, 0, UNFRAGMENTED,\n            HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, INITIAL_TERM_OFFSET + FRAME_LENGTH, FRAME_LENGTH, 1,\n            BEGIN_FRAG_FLAG, HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, INITIAL_TERM_OFFSET + FRAME_LENGTH * 2, FRAME_LENGTH, 2,\n            END_FRAG_FLAG, HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, INITIAL_TERM_OFFSET + FRAME_LENGTH * 3, FRAME_LENGTH, 3,\n            UNFRAGMENTED, HDR_TYPE_PAD, SESSION_ID);\n\n        writer.close();\n        recordingSummary.stopPosition = START_POSITION + 4 * FRAME_LENGTH;\n    }\n\n    @AfterEach\n    void after()\n    {\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    void verifyRecordingFile()\n    {\n        try (RecordingReader reader = new RecordingReader(\n            recordingSummary, archiveDir, NULL_POSITION, AeronArchive.NULL_LENGTH))\n        {\n            int fragments = reader.poll(\n                (buffer, offset, length, frameType, flags, reservedValue) ->\n                {\n                    final int frameOffset = offset - HEADER_LENGTH;\n                    assertEquals(offset, INITIAL_TERM_OFFSET + HEADER_LENGTH);\n                    assertEquals(length, FRAME_LENGTH - HEADER_LENGTH);\n                    assertEquals(frameType(buffer, frameOffset), HDR_TYPE_DATA);\n                    assertEquals(frameFlags(buffer, frameOffset), UNFRAGMENTED);\n                },\n                1);\n\n            assertEquals(1, fragments);\n\n            fragments = reader.poll(\n                (buffer, offset, length, frameType, flags, reservedValue) ->\n                {\n                    final int frameOffset = offset - HEADER_LENGTH;\n                    assertEquals(offset, INITIAL_TERM_OFFSET + FRAME_LENGTH + HEADER_LENGTH);\n                    assertEquals(length, FRAME_LENGTH - HEADER_LENGTH);\n                    assertEquals(frameType(buffer, frameOffset), HDR_TYPE_DATA);\n                    assertEquals(frameFlags(buffer, frameOffset), BEGIN_FRAG_FLAG);\n                },\n                1);\n\n            assertEquals(1, fragments);\n\n            fragments = reader.poll(\n                (buffer, offset, length, frameType, flags, reservedValue) ->\n                {\n                    final int frameOffset = offset - HEADER_LENGTH;\n                    assertEquals(offset, INITIAL_TERM_OFFSET + 2 * FRAME_LENGTH + HEADER_LENGTH);\n                    assertEquals(length, FRAME_LENGTH - HEADER_LENGTH);\n                    assertEquals(frameType(buffer, frameOffset), HDR_TYPE_DATA);\n                    assertEquals(frameFlags(buffer, frameOffset), END_FRAG_FLAG);\n                },\n                1);\n\n            assertEquals(1, fragments);\n\n            fragments = reader.poll(\n                (buffer, offset, length, frameType, flags, reservedValue) ->\n                {\n                    final int frameOffset = offset - HEADER_LENGTH;\n                    assertEquals(offset, INITIAL_TERM_OFFSET + 3 * FRAME_LENGTH + HEADER_LENGTH);\n                    assertEquals(length, FRAME_LENGTH - HEADER_LENGTH);\n                    assertEquals(frameType(buffer, frameOffset), HDR_TYPE_PAD);\n                    assertEquals(frameFlags(buffer, frameOffset), UNFRAGMENTED);\n                },\n                1);\n\n            assertEquals(1, fragments);\n        }\n    }\n\n    @Test\n    void shouldReplayPartialDataFromFile()\n    {\n        final long correlationId = 1L;\n        final int sessionId = Integer.MAX_VALUE;\n        final int streamId = Integer.MIN_VALUE;\n\n        try (ReplaySession replaySession = replaySession(\n            RECORDING_POSITION,\n            FRAME_LENGTH,\n            correlationId,\n            mockReplayPub,\n            mockControlSession,\n            recordingPositionCounter,\n            null))\n        {\n            when(mockReplayPub.isClosed()).thenReturn(false);\n            when(mockReplayPub.isConnected()).thenReturn(false);\n            when(mockReplayPub.sessionId()).thenReturn(sessionId);\n            when(mockReplayPub.streamId()).thenReturn(streamId);\n\n            replaySession.doWork();\n\n            assertEquals(replaySession.state(), ReplaySession.State.INIT);\n\n            when(mockReplayPub.isConnected()).thenReturn(true);\n\n            final UnsafeBuffer termBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(4096));\n            mockPublication(mockReplayPub, termBuffer);\n            assertNotEquals(0, replaySession.doWork());\n            assertThat(messageCounter, is(1));\n\n            validateFrame(termBuffer, 0, FRAME_LENGTH, 0, UNFRAGMENTED, sessionId, streamId);\n            assertTrue(replaySession.isDone());\n        }\n    }\n\n    @Test\n    void shouldNotReplayPartialUnalignedDataFromFile()\n    {\n        final long correlationId = 1L;\n        final ReplaySession replaySession = replaySession(\n            RECORDING_POSITION + 1,\n            FRAME_LENGTH,\n            correlationId,\n            mockReplayPub,\n            mockControlSession,\n            recordingPositionCounter,\n            null);\n\n        final ArchiveException exception = assertThrowsExactly(ArchiveException.class, replaySession::doWork);\n        assertEquals(AeronException.Category.ERROR, exception.category());\n        assertEquals(ArchiveException.GENERIC, exception.errorCode());\n        assertEquals(\"ERROR - replayPosition=1025 (segmentFilePosition=0, segmentOffset=0, termOffset=1025, \" +\n            \"frameOffset=0) does not point to a valid frame, recordingId=0,\" +\n            \" replaySessionId=1, segmentFile=0-0.rec\", exception.getMessage());\n        assertEquals(ReplaySession.State.INACTIVE, replaySession.state());\n\n        replaySession.sendPendingError();\n        verify(mockControlSession).sendErrorResponse(eq(correlationId), anyLong(), anyString());\n    }\n\n    @Test\n    void shouldReplayFullDataFromFile()\n    {\n        final long length = 4 * FRAME_LENGTH;\n        final long correlationId = 1L;\n\n        try (ReplaySession replaySession = replaySession(\n            RECORDING_POSITION,\n            length,\n            correlationId,\n            mockReplayPub,\n            mockControlSession,\n            null,\n            null))\n        {\n            when(mockReplayPub.isClosed()).thenReturn(false);\n            when(mockReplayPub.isConnected()).thenReturn(false);\n\n            replaySession.doWork();\n            assertEquals(replaySession.state(), ReplaySession.State.INIT);\n\n            when(mockReplayPub.isConnected()).thenReturn(true);\n\n            final UnsafeBuffer termBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(4096));\n            mockPublication(mockReplayPub, termBuffer);\n\n            assertNotEquals(0, replaySession.doWork());\n            assertThat(messageCounter, is(2));\n\n            validateFrame(termBuffer, 0, FRAME_LENGTH, 0, UNFRAGMENTED, 0, 0);\n            validateFrame(termBuffer, FRAME_LENGTH, FRAME_LENGTH, 1, BEGIN_FRAG_FLAG, 0, 0);\n            validateFrame(termBuffer, 2 * FRAME_LENGTH, FRAME_LENGTH, 2, END_FRAG_FLAG, 0, 0);\n\n            verify(mockReplayPub).appendPadding(FRAME_LENGTH - HEADER_LENGTH);\n            assertTrue(replaySession.isDone());\n        }\n    }\n\n    @Test\n    void shouldGiveUpIfPublishersAreNotConnectedAfterTimeout()\n    {\n        final long length = 1024L;\n        final long correlationId = 1L;\n        final String replayChannel = \"aeron:udp?endpoint=localhost:5555\";\n        final int replayStreamId = 876;\n        try (ReplaySession replaySession = replaySession(\n            RECORDING_POSITION,\n            length,\n            correlationId,\n            mockReplayPub,\n            mockControlSession,\n            recordingPositionCounter,\n            null))\n        {\n            when(mockReplayPub.channel()).thenReturn(replayChannel);\n            when(mockReplayPub.streamId()).thenReturn(replayStreamId);\n            when(mockReplayPub.isClosed()).thenReturn(false);\n            when(mockReplayPub.isConnected()).thenReturn(false);\n\n            replaySession.doWork();\n\n            epochClock.update(CONNECT_TIMEOUT_MS + TIME + 1L);\n\n            final ArchiveException exception = assertThrowsExactly(ArchiveException.class, replaySession::doWork);\n            assertEquals(AeronException.Category.ERROR, exception.category());\n            assertEquals(ArchiveException.GENERIC, exception.errorCode());\n            assertEquals(\"ERROR - no connection established for\" +\n                \" replayChannel=\" + replayChannel +\n                \", replayStreamId=\" + replayStreamId +\n                \", recordingId=0\" +\n                \", replaySessionId=1\" +\n                \", segmentFile=0-0.rec\", exception.getMessage());\n            assertEquals(ReplaySession.State.INACTIVE, replaySession.state());\n        }\n    }\n\n    @Test\n    void shouldReplayFromActiveRecording() throws IOException\n    {\n        final UnsafeBuffer termBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(4096));\n\n        final int recordingId = RECORDING_ID + 1;\n        recordingSummary.recordingId = recordingId;\n        recordingSummary.stopPosition = NULL_POSITION;\n\n        recordingPosition = START_POSITION;\n\n        context.recordChecksumBuffer(new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_BUFFER_LENGTH)));\n        final RecordingWriter writer = new RecordingWriter(\n            recordingId,\n            START_POSITION,\n            SEGMENT_LENGTH,\n            mockImage,\n            context,\n            mock(ArchiveConductor.Recorder.class));\n\n        writer.init();\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_BUFFER_LENGTH));\n\n        final DataHeaderFlyweight headerFwt = new DataHeaderFlyweight();\n        final Header header = new Header(INITIAL_TERM_ID, Integer.numberOfLeadingZeros(TERM_BUFFER_LENGTH));\n        header.buffer(buffer);\n\n        recordFragment(writer, buffer, headerFwt, header, 0, FRAME_LENGTH, 0, UNFRAGMENTED,\n            HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, FRAME_LENGTH, FRAME_LENGTH, 1, BEGIN_FRAG_FLAG,\n            HDR_TYPE_DATA, SESSION_ID);\n\n        final long length = 5 * FRAME_LENGTH;\n        final long correlationId = 1L;\n        final int sessionId = 42;\n        final int streamId = 21;\n\n        try (ReplaySession replaySession = replaySession(\n            RECORDING_POSITION,\n            length,\n            correlationId,\n            mockReplayPub,\n            mockControlSession,\n            recordingPositionCounter,\n            null))\n        {\n            when(mockReplayPub.isClosed()).thenReturn(false);\n            when(mockReplayPub.isConnected()).thenReturn(false);\n            when(mockReplayPub.sessionId()).thenReturn(sessionId);\n            when(mockReplayPub.streamId()).thenReturn(streamId);\n\n            replaySession.doWork();\n\n            assertEquals(replaySession.state(), ReplaySession.State.INIT);\n\n            when(mockReplayPub.isConnected()).thenReturn(true);\n\n            mockPublication(mockReplayPub, termBuffer);\n\n            assertNotEquals(0, replaySession.doWork());\n            assertThat(messageCounter, is(1));\n\n            validateFrame(termBuffer, 0, FRAME_LENGTH, 0, UNFRAGMENTED, sessionId, streamId);\n            validateFrame(termBuffer, FRAME_LENGTH, FRAME_LENGTH, 1, BEGIN_FRAG_FLAG, sessionId, streamId);\n\n            assertEquals(0, replaySession.doWork());\n\n            recordFragment(writer, buffer, headerFwt, header, FRAME_LENGTH * 2, FRAME_LENGTH, 2,\n                END_FRAG_FLAG, HDR_TYPE_DATA, SESSION_ID);\n            recordFragment(writer, buffer, headerFwt, header, FRAME_LENGTH * 3, FRAME_LENGTH, 3,\n                UNFRAGMENTED, HDR_TYPE_PAD, SESSION_ID);\n\n            writer.close();\n\n            when(recordingPositionCounter.isClosed()).thenReturn(true);\n            assertNotEquals(0, replaySession.doWork());\n\n            validateFrame(termBuffer, 2 * FRAME_LENGTH, FRAME_LENGTH, 2, END_FRAG_FLAG, sessionId, streamId);\n            verify(mockReplayPub).appendPadding(FRAME_LENGTH - HEADER_LENGTH);\n\n            assertTrue(replaySession.isDone());\n        }\n    }\n\n    @Test\n    void shouldThrowArchiveExceptionIfCrcFails()\n    {\n        final long length = 4 * FRAME_LENGTH;\n        final long correlationId = 1L;\n\n        final Checksum checksum = Checksums.crc32c();\n        try (ReplaySession replaySession = replaySession(\n            RECORDING_POSITION + 2 * FRAME_LENGTH,\n            length,\n            correlationId,\n            mockReplayPub,\n            mockControlSession,\n            null,\n            checksum))\n        {\n            when(mockReplayPub.isClosed()).thenReturn(false);\n            when(mockReplayPub.isConnected()).thenReturn(false);\n\n            replaySession.doWork();\n            assertEquals(ReplaySession.State.INIT, replaySession.state());\n\n            when(mockReplayPub.isConnected()).thenReturn(true);\n\n            final ArchiveException exception = assertThrows(ArchiveException.class, replaySession::doWork);\n            assertEquals(ArchiveException.GENERIC, exception.errorCode());\n            assertThat(\n                exception.getMessage(),\n                Matchers.startsWith(\"ERROR - CRC checksum mismatch at position=3072 (segmentFilePosition=0, \" +\n                    \"segmentOffset=0, termOffset=3072, frameOffset=0)\"));\n            verify(mockReplayPub, never()).tryClaim(anyInt(), any(BufferClaim.class));\n        }\n    }\n\n    @Test\n    void shouldDoCrcForEachDataFrame() throws IOException\n    {\n        context.recordChecksum(Checksums.crc32c());\n        context.recordChecksumBuffer(new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_BUFFER_LENGTH)));\n        final RecordingWriter writer = new RecordingWriter(\n            RECORDING_ID,\n            START_POSITION,\n            SEGMENT_LENGTH,\n            mockImage,\n            context,\n            mock(ArchiveConductor.Recorder.class));\n\n        writer.init();\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_BUFFER_LENGTH));\n        final DataHeaderFlyweight headerFwt = new DataHeaderFlyweight();\n        final Header header = new Header(INITIAL_TERM_ID, Integer.numberOfLeadingZeros(TERM_BUFFER_LENGTH));\n        header.buffer(buffer);\n\n        recordFragment(writer, buffer, headerFwt, header, 0, FRAME_LENGTH, 10, UNFRAGMENTED,\n            HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, FRAME_LENGTH, FRAME_LENGTH, 20, BEGIN_FRAG_FLAG,\n            HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, FRAME_LENGTH * 2, FRAME_LENGTH, 30, END_FRAG_FLAG,\n            HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, FRAME_LENGTH * 3, FRAME_LENGTH, 40, UNFRAGMENTED,\n            HDR_TYPE_PAD, SESSION_ID);\n\n        writer.close();\n\n        final long length = 4 * FRAME_LENGTH;\n        final long correlationId = 1L;\n        final int sessionId = Integer.MIN_VALUE;\n        final int streamId = Integer.MAX_VALUE;\n\n        try (ReplaySession replaySession = replaySession(\n            RECORDING_POSITION,\n            length,\n            correlationId,\n            mockReplayPub,\n            mockControlSession,\n            null,\n            context.recordChecksum()))\n        {\n            when(mockReplayPub.isClosed()).thenReturn(false);\n            when(mockReplayPub.isConnected()).thenReturn(false);\n            when(mockReplayPub.sessionId()).thenReturn(sessionId);\n            when(mockReplayPub.streamId()).thenReturn(streamId);\n\n            replaySession.doWork();\n            assertEquals(replaySession.state(), ReplaySession.State.INIT);\n\n            when(mockReplayPub.isConnected()).thenReturn(true);\n\n            final UnsafeBuffer termBuffer = new UnsafeBuffer(new byte[4096]);\n            mockPublication(mockReplayPub, termBuffer);\n\n            assertNotEquals(0, replaySession.doWork());\n            assertThat(messageCounter, is(2));\n\n            validateFrame(termBuffer, 0, FRAME_LENGTH, 10, UNFRAGMENTED, sessionId, streamId);\n            validateFrame(termBuffer, FRAME_LENGTH, FRAME_LENGTH, 20, BEGIN_FRAG_FLAG, sessionId, streamId);\n            validateFrame(termBuffer, 2 * FRAME_LENGTH, FRAME_LENGTH, 30, END_FRAG_FLAG, sessionId, streamId);\n\n            verify(mockReplayPub).appendPadding(FRAME_LENGTH - HEADER_LENGTH);\n            assertTrue(replaySession.isDone());\n        }\n    }\n\n    @Test\n    void shouldNotWritePaddingIfOfferBlockFails()\n    {\n        try (ReplaySession replaySession = replaySession(\n            RECORDING_POSITION,\n            4 * FRAME_LENGTH,\n            1L,\n            mockReplayPub,\n            mockControlSession,\n            null,\n            null))\n        {\n            when(mockReplayPub.isConnected()).thenReturn(true);\n            when(mockReplayPub.offerBlock(any(MutableDirectBuffer.class), anyInt(), anyInt()))\n                .thenReturn(BACK_PRESSURED);\n\n            assertEquals(1, replaySession.doWork());\n\n            verify(mockReplayPub).offerBlock(any(MutableDirectBuffer.class), anyInt(), anyInt());\n            verify(mockReplayPub, never()).appendPadding(anyInt());\n        }\n    }\n\n    @Test\n    void shouldNotWritePaddingIfReplayLimitReached()\n    {\n        try (ReplaySession replaySession = replaySession(\n            RECORDING_POSITION,\n            1500,\n            1L,\n            mockReplayPub,\n            mockControlSession,\n            null,\n            null))\n        {\n            when(mockReplayPub.isConnected()).thenReturn(true);\n            final UnsafeBuffer termBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(4096));\n            mockPublication(mockReplayPub, termBuffer);\n\n            assertEquals(2, replaySession.doWork());\n\n            validateFrame(termBuffer, 0, FRAME_LENGTH, 0, UNFRAGMENTED, 0, 0);\n            validateFrame(termBuffer, FRAME_LENGTH, FRAME_LENGTH, 1, BEGIN_FRAG_FLAG, 0, 0);\n            validateFrame(termBuffer, FRAME_LENGTH * 2, 0, 0, (byte)0, 0, 0);\n            verify(mockReplayPub).offerBlock(any(MutableDirectBuffer.class), eq(0), eq(2 * FRAME_LENGTH));\n            verify(mockReplayPub, never()).appendPadding(anyInt());\n            assertTrue(replaySession.isDone());\n        }\n    }\n\n    @Test\n    void shouldCalculateBlockSizeBasedOnFullFragments() throws IOException\n    {\n        context.recordChecksumBuffer(new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_BUFFER_LENGTH)));\n        final RecordingWriter writer = new RecordingWriter(\n            RECORDING_ID,\n            START_POSITION,\n            SEGMENT_LENGTH,\n            mockImage,\n            context,\n            mock(ArchiveConductor.Recorder.class));\n\n        writer.init();\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_BUFFER_LENGTH));\n        final DataHeaderFlyweight headerFwt = new DataHeaderFlyweight();\n        final Header header = new Header(INITIAL_TERM_ID, Integer.numberOfLeadingZeros(TERM_BUFFER_LENGTH));\n        header.buffer(buffer);\n\n        recordFragment(writer, buffer, headerFwt, header, 0, 100, 11, UNFRAGMENTED, HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, 128, 200, 22, UNFRAGMENTED, HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, 352, 300, 33, UNFRAGMENTED, HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, 672, 400, 44, UNFRAGMENTED, HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, 1088, 500, 55, UNFRAGMENTED, HDR_TYPE_DATA, SESSION_ID);\n        recordFragment(writer, buffer, headerFwt, header, 1600, 6000, 66, UNFRAGMENTED, HDR_TYPE_DATA, SESSION_ID);\n\n        writer.close();\n\n        final int sessionId = 555;\n        final int streamId = 777;\n\n        try (ReplaySession replaySession = replaySession(\n            RECORDING_POSITION,\n            1_000_000,\n            -1,\n            mockReplayPub,\n            mockControlSession,\n            null,\n            null))\n        {\n            when(mockReplayPub.isClosed()).thenReturn(false);\n            when(mockReplayPub.isConnected()).thenReturn(false);\n            when(mockReplayPub.sessionId()).thenReturn(sessionId);\n            when(mockReplayPub.streamId()).thenReturn(streamId);\n\n            replaySession.doWork();\n            assertEquals(replaySession.state(), ReplaySession.State.INIT);\n\n            when(mockReplayPub.isConnected()).thenReturn(true);\n\n            final UnsafeBuffer termBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(4096));\n            mockPublication(mockReplayPub, termBuffer);\n\n            assertEquals(2, replaySession.doWork());\n            assertThat(messageCounter, is(1));\n\n            validateFrame(termBuffer, 0, 100, 11, UNFRAGMENTED, sessionId, streamId);\n            validateFrame(termBuffer, 128, 200, 22, UNFRAGMENTED, sessionId, streamId);\n            validateFrame(termBuffer, 352, 300, 33, UNFRAGMENTED, sessionId, streamId);\n            validateFrame(termBuffer, 672, 400, 44, UNFRAGMENTED, sessionId, streamId);\n            validateFrame(termBuffer, 1088, 500, 55, UNFRAGMENTED, sessionId, streamId);\n\n            verify(mockReplayPub, never()).appendPadding(anyInt());\n        }\n    }\n\n    private void recordFragment(\n        final RecordingWriter recordingWriter,\n        final UnsafeBuffer buffer,\n        final DataHeaderFlyweight headerFlyweight,\n        final Header header,\n        final int offset,\n        final int frameLength,\n        final int message,\n        final byte flags,\n        final int type,\n        final int sessionId)\n    {\n        headerFlyweight.wrap(buffer, offset, HEADER_LENGTH);\n        headerFlyweight\n            .streamId(STREAM_ID)\n            .sessionId(sessionId)\n            .termOffset(offset)\n            .termId(INITIAL_TERM_ID)\n            .reservedValue(message)\n            .headerType(type)\n            .flags(flags)\n            .frameLength(frameLength);\n\n        buffer.setMemory(\n            offset + HEADER_LENGTH,\n            frameLength - HEADER_LENGTH,\n            (byte)message);\n\n        header.offset(offset);\n\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        recordingWriter.onBlock(buffer, offset, alignedLength, SESSION_ID, INITIAL_TERM_ID);\n        recordingPosition += alignedLength;\n    }\n\n    private void mockPublication(final ExclusivePublication replay, final UnsafeBuffer termBuffer)\n    {\n        when(replay.offerBlock(any(MutableDirectBuffer.class), anyInt(), anyInt())).then(\n            (invocation) ->\n            {\n                final MutableDirectBuffer buffer = invocation.getArgument(0);\n                final int offset = invocation.getArgument(1);\n                final int length = invocation.getArgument(2);\n                termBuffer.putBytes(offerBlockOffset, buffer, offset, length);\n                messageCounter++;\n                offerBlockOffset += length;\n                return (long)length;\n            });\n\n        when(replay.appendPadding(anyInt())).then(\n            (invocation) ->\n            {\n                final int claimedSize = invocation.getArgument(0);\n                messageCounter++;\n\n                return (long)claimedSize;\n            });\n    }\n\n    private ReplaySession replaySession(\n        final long position,\n        final long length,\n        final long correlationId,\n        final ExclusivePublication replay,\n        final ControlSession controlSession,\n        final Counter recordingPositionCounter,\n        final Checksum checksum)\n    {\n        return new ReplaySession(\n            correlationId,\n            recordingSummary.recordingId,\n            position,\n            length,\n            recordingSummary.startPosition,\n            recordingSummary.stopPosition,\n            recordingSummary.segmentFileLength,\n            recordingSummary.termBufferLength,\n            recordingSummary.streamId,\n            REPLAY_ID,\n            CONNECT_TIMEOUT_MS,\n            controlSession,\n            replayBuffer,\n            archiveDir,\n            epochClock,\n            nanoClock,\n            replay,\n            mockCountersReader,\n            recordingPositionCounter,\n            checksum,\n            mock(ArchiveConductor.Replayer.class));\n    }\n\n    static void validateFrame(\n        final UnsafeBuffer buffer,\n        final int offset,\n        final int frameLength,\n        final int message,\n        final byte flags,\n        final int sessionId,\n        final int streamId)\n    {\n\n        assertEquals(frameLength, frameLength(buffer, offset));\n        assertEquals(flags, frameFlags(buffer, offset));\n        assertEquals(sessionId, buffer.getInt(offset + SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN));\n        assertEquals(streamId, buffer.getInt(offset + STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN));\n        assertEquals(message, buffer.getLong(offset + RESERVED_VALUE_OFFSET));\n        assertEquals(message, buffer.getByte(offset + HEADER_LENGTH));\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/checksum/ChecksumsTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.checksum;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass ChecksumsTest\n{\n    @Test\n    void crc32()\n    {\n        final Checksum instance = Checksums.crc32();\n        assertNotNull(instance);\n        assertSame(instance, Checksums.crc32());\n    }\n\n    @Test\n    void crc32c()\n    {\n        final Checksum instance = Checksums.crc32c();\n        assertNotNull(instance);\n        assertSame(instance, Checksums.crc32c());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"CRC-32\", \"io.aeron.archive.checksum.Crc32\", \"org.agrona.checksum.Crc32\" })\n    void newInstanceReturnsSameInstanceOfCrc32(final String alias)\n    {\n        assertSame(Checksums.crc32(), Checksums.newInstance(alias));\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"CRC-32C\", \"io.aeron.archive.checksum.Crc32c\", \"org.agrona.checksum.Crc32c\" })\n    void newInstanceReturnsSameInstanceOfCrc32c(final String alias)\n    {\n        assertSame(Checksums.crc32c(), Checksums.newInstance(alias));\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void newInstanceThrowsNullPointerExceptionIfClassNameIsNull(final String alias)\n    {\n        assertThrows(IllegalArgumentException.class, () -> Checksums.newInstance(alias));\n    }\n\n    @Test\n    void newInstanceThrowsIllegalArgumentExceptionIfClassIsNotFound()\n    {\n        final IllegalArgumentException exception = assertThrows(\n            IllegalArgumentException.class, () -> Checksums.newInstance(\"a.b.c.MissingClass\"));\n        assertEquals(ClassNotFoundException.class, exception.getCause().getClass());\n    }\n\n    @Test\n    void newInstanceThrowsIllegalArgumentExceptionIfInstanceCannotBeCreated()\n    {\n        final IllegalArgumentException exception = assertThrows(\n            IllegalArgumentException.class, () -> Checksums.newInstance(TimeUnit.class.getName()));\n        assertEquals(NoSuchMethodException.class, exception.getCause().getClass());\n    }\n\n    @Test\n    void newInstanceThrowsClassCastExceptionIfCreatedInstanceDoesNotImplementChecksumInterface()\n    {\n        assertThrows(ClassCastException.class, () -> Checksums.newInstance(Object.class.getName()));\n    }\n\n    @Test\n    void newInstanceCreatesANewInstanceOfTheSpecifiedClass()\n    {\n        final Checksum instance1 = Checksums.newInstance(TestChecksum.class.getName());\n        final Checksum instance2 = Checksums.newInstance(TestChecksum.class.getName());\n        assertInstanceOf(TestChecksum.class, instance1);\n        assertInstanceOf(TestChecksum.class, instance2);\n        assertNotSame(instance1, instance2);\n    }\n\n    static class TestChecksum implements Checksum\n    {\n        public int compute(final long address, final int offset, final int length)\n        {\n            return 0;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/client/AeronArchiveTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.AvailableImageHandler;\nimport io.aeron.ChannelUri;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.UnavailableImageHandler;\nimport io.aeron.archive.client.AeronArchive.Context;\nimport io.aeron.exceptions.ConfigurationException;\nimport org.agrona.BitUtil;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.NoOpIdleStrategy;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.InOrder;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.MTU_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.SESSION_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.SPARSE_PARAM_NAME;\nimport static io.aeron.CommonContext.TERM_LENGTH_PARAM_NAME;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.argThat;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.nullable;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\nclass AeronArchiveTest\n{\n    private final Aeron aeron = mock(Aeron.class);\n    private final ControlResponsePoller controlResponsePoller = mock(ControlResponsePoller.class);\n    private final ArchiveProxy archiveProxy = mock(ArchiveProxy.class);\n    private final ErrorHandler errorHandler = mock(ErrorHandler.class);\n\n    @Test\n    void asyncConnectedShouldConcludeContext()\n    {\n        final Context ctx = mock(Context.class);\n        final IllegalStateException expectedException = new IllegalStateException(\"test\");\n        doThrow(expectedException).when(ctx).conclude();\n\n        final IllegalStateException actualException =\n            assertThrowsExactly(IllegalStateException.class, () -> AeronArchive.asyncConnect(ctx));\n        assertSame(expectedException, actualException);\n\n        verify(ctx).conclude();\n        verifyNoMoreInteractions(ctx);\n    }\n\n    @Test\n    void asyncConnectedShouldCloseContext()\n    {\n        final String responseChannel = \"aeron:udp?endpoint=localhost:1234\";\n        final int responseStreamId = 49;\n        final Context ctx = mock(Context.class);\n        when(ctx.aeron()).thenReturn(aeron);\n        when(ctx.controlResponseChannel()).thenReturn(responseChannel);\n        when(ctx.controlResponseStreamId()).thenReturn(responseStreamId);\n        final RuntimeException error = new RuntimeException(\"subscription\");\n        when(aeron.asyncAddSubscription(\n            eq(responseChannel),\n            eq(responseStreamId),\n            nullable(AvailableImageHandler.class),\n            any(UnavailableImageHandler.class))).thenThrow(error);\n\n        final RuntimeException actualException =\n            assertThrowsExactly(RuntimeException.class, () -> AeronArchive.asyncConnect(ctx));\n        assertSame(error, actualException);\n\n        final InOrder inOrder = inOrder(ctx, aeron);\n        inOrder.verify(ctx).conclude();\n        inOrder.verify(ctx).aeron();\n        inOrder.verify(ctx).controlResponseChannel();\n        inOrder.verify(ctx).controlResponseStreamId();\n        inOrder.verify(aeron).asyncAddSubscription(\n            eq(responseChannel),\n            eq(responseStreamId),\n            nullable(AvailableImageHandler.class),\n            any(UnavailableImageHandler.class));\n        inOrder.verify(ctx).close();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void asyncConnectedShouldCloseResourceInCaseOfExceptionUponStartup()\n    {\n        final String responseChannel = \"aeron:udp?endpoint=localhost:0\";\n        final int responseStreamId = 49;\n        final String requestChannel = \"aeron:udp?endpoint=localhost:1234\";\n        final int requestStreamId = -15;\n        final long subscriptionId = -3275938475934759L;\n\n        final Context ctx = mock(Context.class);\n        when(ctx.aeron()).thenReturn(aeron);\n        when(ctx.controlResponseChannel()).thenReturn(responseChannel);\n        when(ctx.controlResponseStreamId()).thenReturn(responseStreamId);\n        when(ctx.controlRequestChannel()).thenReturn(requestChannel);\n        when(ctx.controlRequestStreamId()).thenReturn(requestStreamId);\n        when(aeron.asyncAddSubscription(\n            eq(responseChannel),\n            eq(responseStreamId),\n            nullable(AvailableImageHandler.class),\n            any(UnavailableImageHandler.class))).thenReturn(subscriptionId);\n        final IndexOutOfBoundsException error = new IndexOutOfBoundsException(\"exception\");\n        when(aeron.context()).thenThrow(error);\n\n        final IndexOutOfBoundsException actualException =\n            assertThrowsExactly(IndexOutOfBoundsException.class, () -> AeronArchive.asyncConnect(ctx));\n        assertSame(error, actualException);\n\n        final InOrder inOrder = inOrder(ctx, aeron);\n        inOrder.verify(ctx).conclude();\n        inOrder.verify(ctx).aeron();\n        inOrder.verify(ctx).controlResponseChannel();\n        inOrder.verify(ctx).controlResponseStreamId();\n        inOrder.verify(aeron).asyncAddSubscription(\n            eq(responseChannel),\n            eq(responseStreamId),\n            nullable(AvailableImageHandler.class),\n            any(UnavailableImageHandler.class));\n        inOrder.verify(aeron).asyncRemoveSubscription(subscriptionId);\n        inOrder.verify(ctx).close();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void closeNotOwningAeronClient()\n    {\n        final long controlSessionId = 42;\n        final long archiveId = -190;\n\n        final Aeron.Context aeronContext = mock(Aeron.Context.class);\n        when(aeronContext.nanoClock()).thenReturn(SystemNanoClock.INSTANCE);\n        when(aeron.context()).thenReturn(aeronContext);\n        final IllegalMonitorStateException aeronException = new IllegalMonitorStateException(\"aeron closed\");\n        doThrow(aeronException).when(aeron).close();\n\n        final Publication publication = mock(Publication.class);\n        when(publication.isConnected()).thenReturn(true);\n        final IllegalStateException publicationException = new IllegalStateException(\"publication is closed\");\n        doThrow(publicationException).when(publication).close();\n\n        final Subscription subscription = mock(Subscription.class);\n        when(controlResponsePoller.subscription()).thenReturn(subscription);\n        final IndexOutOfBoundsException subscriptionException = new IndexOutOfBoundsException(\"subscription\");\n        doThrow(subscriptionException).when(subscription).close();\n\n        when(archiveProxy.publication()).thenReturn(publication);\n        final IndexOutOfBoundsException closeSessionException = new IndexOutOfBoundsException();\n        when(archiveProxy.closeSession(controlSessionId)).thenThrow(closeSessionException);\n\n        final Context context = new Context()\n            .aeron(aeron)\n            .idleStrategy(NoOpIdleStrategy.INSTANCE)\n            .messageTimeoutNs(100)\n            .lock(NoOpLock.INSTANCE)\n            .errorHandler(errorHandler)\n            .ownsAeronClient(false);\n        final AeronArchive aeronArchive =\n            new AeronArchive(context, controlResponsePoller, archiveProxy, controlSessionId, archiveId);\n\n        aeronArchive.close();\n\n        final InOrder inOrder = inOrder(errorHandler);\n        inOrder.verify(errorHandler).onError(argThat(\n            ex ->\n            {\n                final Throwable[] suppressed = ex.getSuppressed();\n                return closeSessionException == ex &&\n                    publicationException == suppressed[0] &&\n                    subscriptionException == suppressed[1];\n            }));\n        inOrder.verifyNoMoreInteractions();\n        verify(publication).close();\n        verify(subscription).close();\n    }\n\n    @Test\n    void closeOwningAeronClient()\n    {\n        final long controlSessionId = 42;\n        final long archiveId = 555;\n\n        final Aeron.Context aeronContext = mock(Aeron.Context.class);\n        when(aeronContext.nanoClock()).thenReturn(SystemNanoClock.INSTANCE);\n        when(aeron.context()).thenReturn(aeronContext);\n        final IllegalMonitorStateException aeronException = new IllegalMonitorStateException(\"aeron closed\");\n        doThrow(aeronException).when(aeron).close();\n\n        final Publication publication = mock(Publication.class);\n        when(publication.isConnected()).thenReturn(true);\n        doThrow(new IllegalStateException(\"publication is closed\")).when(publication).close();\n\n        final Subscription subscription = mock(Subscription.class);\n        when(controlResponsePoller.subscription()).thenReturn(subscription);\n        doThrow(new IndexOutOfBoundsException(\"subscription\")).when(subscription).close();\n\n        when(archiveProxy.publication()).thenReturn(publication);\n        final IndexOutOfBoundsException closeSessionException = new IndexOutOfBoundsException();\n        when(archiveProxy.closeSession(controlSessionId)).thenThrow(closeSessionException);\n\n        final Context context = new Context()\n            .aeron(aeron)\n            .idleStrategy(NoOpIdleStrategy.INSTANCE)\n            .messageTimeoutNs(100)\n            .lock(NoOpLock.INSTANCE)\n            .errorHandler(errorHandler)\n            .ownsAeronClient(true);\n        final AeronArchive aeronArchive =\n            new AeronArchive(context, controlResponsePoller, archiveProxy, controlSessionId, archiveId);\n\n        final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, aeronArchive::close);\n\n        assertSame(closeSessionException, ex);\n        final InOrder inOrder = inOrder(errorHandler);\n        inOrder.verify(errorHandler).onError(closeSessionException);\n        inOrder.verifyNoMoreInteractions();\n\n        assertEquals(aeronException, ex.getSuppressed()[0]);\n    }\n\n    @Test\n    void shouldClose() throws Exception\n    {\n        final Exception previousException = new Exception();\n        final Exception thrownException = new Exception();\n\n        final AutoCloseable throwingCloseable = mock(AutoCloseable.class);\n        final AutoCloseable nonThrowingCloseable = mock(AutoCloseable.class);\n        doThrow(thrownException).when(throwingCloseable).close();\n\n        assertNull(AeronArchive.quietClose(null, nonThrowingCloseable));\n        assertEquals(previousException, AeronArchive.quietClose(previousException, nonThrowingCloseable));\n        final Exception ex = AeronArchive.quietClose(previousException, throwingCloseable);\n        assertEquals(previousException, ex);\n        assertEquals(thrownException, ex.getSuppressed()[0]);\n        assertEquals(thrownException, AeronArchive.quietClose(null, throwingCloseable));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { NULL_VALUE, Long.MAX_VALUE, Long.MIN_VALUE, 0, 4468236482L })\n    void shouldReturnAssignedArchiveId(final long archiveId)\n    {\n        final long controlSessionId = -3924293;\n        when(aeron.context()).thenReturn(new Aeron.Context());\n        final Context context = new Context()\n            .aeron(aeron)\n            .idleStrategy(NoOpIdleStrategy.INSTANCE)\n            .messageTimeoutNs(100)\n            .lock(NoOpLock.INSTANCE)\n            .errorHandler(errorHandler)\n            .ownsAeronClient(true);\n\n        final AeronArchive aeronArchive =\n            new AeronArchive(context, controlResponsePoller, archiveProxy, controlSessionId, archiveId);\n\n        assertEquals(archiveId, aeronArchive.archiveId());\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"aeron:udp?endpoint=localhost:3388|mtu=2048, \" +\n            \"aeron:udp?session-id=5|endpoint=localhost:0|sparse=true|mtu=1024\",\n        \"aeron:udp?endpoint=localhost:3388, \" +\n            \"aeron:udp?control=localhost:10000|control-mode=dynamic\",\n        \"aeron:udp?endpoint=localhost:3388, aeron:udp?control-mode=manual\",\n        \"aeron:ipc?alias=request|ssc=false|linger=0|session-id=42|sparse=false, \" +\n            \"aeron:ipc?term-length=64k|alias=response\",\n    })\n    void shouldAddAUniqueSessionIdParameterToBothRequestAndResponseChannels(\n        final String requestChannel, final String responseChannel)\n    {\n        final int requestStreamId = 42;\n        final int responseStreamId = -19;\n        final int sessionId = BitUtil.generateRandomisedId();\n        when(aeron.nextSessionId(requestStreamId)).thenReturn(sessionId);\n\n        final Context context = new Context()\n            .aeron(aeron)\n            .ownsAeronClient(false)\n            .errorHandler(errorHandler)\n            .controlRequestChannel(requestChannel)\n            .controlRequestStreamId(requestStreamId)\n            .controlResponseChannel(responseChannel)\n            .controlResponseStreamId(responseStreamId)\n            .controlTermBufferSparse(false)\n            .controlTermBufferLength(128 * 1024)\n            .controlMtuLength(4096);\n\n        assertEquals(requestChannel, context.controlRequestChannel());\n        assertEquals(requestStreamId, context.controlRequestStreamId());\n        assertEquals(responseChannel, context.controlResponseChannel());\n        assertEquals(responseStreamId, context.controlResponseStreamId());\n\n        context.conclude();\n\n        verify(aeron).nextSessionId(requestStreamId);\n        assertEquals(requestStreamId, context.controlRequestStreamId());\n        assertEquals(responseStreamId, context.controlResponseStreamId());\n\n        final ChannelUri actualRequestChannel = ChannelUri.parse(context.controlRequestChannel());\n        final ChannelUri actualResponseChannel = ChannelUri.parse(context.controlResponseChannel());\n        assertTrue(actualRequestChannel.containsKey(SESSION_ID_PARAM_NAME), \"session-id was not added\");\n        assertEquals(Integer.toString(sessionId), actualRequestChannel.get(SESSION_ID_PARAM_NAME));\n        assertEquals(Integer.toString(sessionId), actualResponseChannel.get(SESSION_ID_PARAM_NAME));\n\n        ChannelUri.parse(requestChannel).forEachParameter((key, value) ->\n        {\n            if (!SESSION_ID_PARAM_NAME.equals(key))\n            {\n                assertEquals(value, actualRequestChannel.get(key));\n            }\n        });\n\n        ChannelUri.parse(responseChannel).forEachParameter((key, value) ->\n        {\n            if (!SESSION_ID_PARAM_NAME.equals(key))\n            {\n                assertEquals(value, actualResponseChannel.get(key));\n            }\n        });\n    }\n\n    @Test\n    void shouldNotAddASessionIdIfControlModeResponseIsSpecifiedOnTheResponseChannel()\n    {\n        final int requestStreamId = 100;\n        final int responseStreamId = 200;\n        final String requestChannel = \"aeron:udp?endpoint=localhost:8080\";\n        final String responseChannel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n        final Context context = new Context()\n            .aeron(aeron)\n            .ownsAeronClient(false)\n            .errorHandler(errorHandler)\n            .controlRequestChannel(requestChannel)\n            .controlRequestStreamId(requestStreamId)\n            .controlResponseChannel(responseChannel)\n            .controlResponseStreamId(responseStreamId);\n\n        assertEquals(requestChannel, context.controlRequestChannel());\n        assertEquals(requestStreamId, context.controlRequestStreamId());\n        assertEquals(responseChannel, context.controlResponseChannel());\n        assertEquals(responseStreamId, context.controlResponseStreamId());\n\n        context.conclude();\n\n        assertEquals(requestStreamId, context.controlRequestStreamId());\n        assertEquals(responseStreamId, context.controlResponseStreamId());\n\n        final ChannelUri actualRequestChannel = ChannelUri.parse(context.controlRequestChannel());\n        final ChannelUri actualResponseChannel = ChannelUri.parse(context.controlResponseChannel());\n        assertNull(actualRequestChannel.get(SESSION_ID_PARAM_NAME), \"unexpected session-id on request channel\");\n        assertNull(actualResponseChannel.get(SESSION_ID_PARAM_NAME), \"unexpected session-id on response channel\");\n\n        ChannelUri.parse(requestChannel)\n            .forEachParameter((key, value) -> assertEquals(value, actualRequestChannel.get(key)));\n\n        ChannelUri.parse(responseChannel)\n            .forEachParameter((key, value) -> assertEquals(value, actualResponseChannel.get(key)));\n    }\n\n    @Test\n    void shouldAddDefaultUriParametersIfNotSpecified()\n    {\n        final int requestStreamId = 10;\n        final int responseStreamId = 20;\n        final String requestChannel = \"aeron:udp?endpoint=localhost:8080\";\n        final String responseChannel = \"aeron:udp?endpoint=localhost:0\";\n        final Context context = new Context()\n            .aeron(aeron)\n            .ownsAeronClient(false)\n            .errorHandler(errorHandler)\n            .controlRequestChannel(requestChannel)\n            .controlRequestStreamId(requestStreamId)\n            .controlResponseChannel(responseChannel)\n            .controlResponseStreamId(responseStreamId)\n            .controlMtuLength(2048)\n            .controlTermBufferLength(256 * 1024)\n            .controlTermBufferSparse(true);\n\n        assertEquals(requestChannel, context.controlRequestChannel());\n        assertEquals(requestStreamId, context.controlRequestStreamId());\n        assertEquals(responseChannel, context.controlResponseChannel());\n        assertEquals(responseStreamId, context.controlResponseStreamId());\n\n        context.conclude();\n\n        assertEquals(requestStreamId, context.controlRequestStreamId());\n        assertEquals(responseStreamId, context.controlResponseStreamId());\n\n        final ChannelUri actualRequestChannel = ChannelUri.parse(context.controlRequestChannel());\n        final ChannelUri actualResponseChannel = ChannelUri.parse(context.controlResponseChannel());\n        assertEquals(String.valueOf(context.controlMtuLength()), actualRequestChannel.get(MTU_LENGTH_PARAM_NAME));\n        assertEquals(String.valueOf(context.controlMtuLength()), actualResponseChannel.get(MTU_LENGTH_PARAM_NAME));\n        assertEquals(\n            String.valueOf(context.controlTermBufferLength()), actualRequestChannel.get(TERM_LENGTH_PARAM_NAME));\n        assertEquals(\n            String.valueOf(context.controlTermBufferLength()), actualResponseChannel.get(TERM_LENGTH_PARAM_NAME));\n        assertEquals(String.valueOf(context.controlTermBufferSparse()), actualRequestChannel.get(SPARSE_PARAM_NAME));\n        assertEquals(String.valueOf(context.controlTermBufferSparse()), actualResponseChannel.get(SPARSE_PARAM_NAME));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { Integer.MIN_VALUE, -1, 0 })\n    void shouldRejectInvalidRetryAttempts(final int retryAttempts)\n    {\n        final Context context = new Context()\n            .aeron(aeron)\n            .controlRequestChannel(\"aeron:udp\")\n            .controlResponseChannel(\"aeron:udp\")\n            .messageRetryAttempts(retryAttempts);\n        assertEquals(retryAttempts, context.messageRetryAttempts());\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - AeronArchive.Context.messageRetryAttempts must be > 0, got: \" + retryAttempts,\n            exception.getMessage());\n    }\n\n    @Test\n    void maxRetryAttemptsDefaultValue()\n    {\n        final Context context = new Context();\n        assertEquals(AeronArchive.Configuration.MESSAGE_RETRY_ATTEMPTS_DEFAULT, context.messageRetryAttempts());\n    }\n\n    @Test\n    void maxRetryAttemptsSystemProperty()\n    {\n        System.setProperty(AeronArchive.Configuration.MESSAGE_RETRY_ATTEMPTS_PROP_NAME, \"111\");\n        try\n        {\n            final Context context = new Context();\n            assertEquals(111, context.messageRetryAttempts());\n        }\n        finally\n        {\n            System.clearProperty(AeronArchive.Configuration.MESSAGE_RETRY_ATTEMPTS_PROP_NAME);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/client/ArchiveExceptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.client;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass ArchiveExceptionTest\n{\n    @ParameterizedTest\n    @CsvSource({\n        \"0,GENERIC\",\n        \"1,ACTIVE_LISTING\",\n        \"2,ACTIVE_RECORDING\",\n        \"3,ACTIVE_SUBSCRIPTION\",\n        \"4,UNKNOWN_SUBSCRIPTION\",\n        \"5,UNKNOWN_RECORDING\",\n        \"6,UNKNOWN_REPLAY\",\n        \"7,MAX_REPLAYS\",\n        \"8,MAX_RECORDINGS\",\n        \"9,INVALID_EXTENSION\",\n        \"10,AUTHENTICATION_REJECTED\",\n        \"11,STORAGE_SPACE\",\n        \"12,UNKNOWN_REPLICATION\",\n        \"13,UNAUTHORISED_ACTION\",\n        \"14,REPLICATION_CONNECTION_FAILURE\" })\n    void errorCodeAsString(final int errorCode, final String expected)\n    {\n        assertEquals(expected, ArchiveException.errorCodeAsString(errorCode));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = {-1, 1111111, 54})\n    void shouldHandlerUnknownErrorCodes(final int errorCode)\n    {\n        assertEquals(\"unknown error code: \" + errorCode, ArchiveException.errorCodeAsString(errorCode));\n    }\n}\n"
  },
  {
    "path": "aeron-archive/src/test/java/io/aeron/archive/status/RecordingPosTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive.status;\n\nimport io.aeron.Aeron;\nimport io.aeron.Counter;\nimport io.aeron.archive.ArchiveCounters;\nimport org.agrona.BitUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.Test;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.status.RecordingPos.*;\nimport static io.aeron.test.Tests.generateStringWithSuffix;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.concurrent.status.CountersReader.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass RecordingPosTest\n{\n    @Test\n    void allocateMaxKeyAndLabel()\n    {\n        final long archiveId = Long.MIN_VALUE;\n        final long recordingId = 54311;\n        final int sessionId = 42;\n        final int streamId = -13;\n        final String strippedChannel = generateStringWithSuffix(\"stripped channel\", \".\", 1000);\n        final String sourceIdentity = generateStringWithSuffix(\"source identity\", \"X\", 5000);\n        final UnsafeBuffer tempBuffer = new UnsafeBuffer(new byte[METADATA_LENGTH]);\n        final Counter counter = mock(Counter.class);\n        final Aeron aeron = mock(Aeron.class);\n        when(aeron.addCounter(\n            eq(RECORDING_POSITION_TYPE_ID),\n            eq(tempBuffer),\n            eq(0),\n            eq(MAX_KEY_LENGTH),\n            eq(tempBuffer),\n            eq(MAX_KEY_LENGTH),\n            eq(MAX_LABEL_LENGTH)))\n            .thenReturn(counter);\n\n        final Counter result = RecordingPos.allocate(\n            aeron,\n            tempBuffer,\n            archiveId,\n            recordingId,\n            sessionId,\n            streamId,\n            strippedChannel,\n            sourceIdentity);\n\n        assertSame(counter, result);\n        int offset = 0;\n        assertEquals(recordingId, tempBuffer.getLong(offset));\n        offset += SIZE_OF_LONG;\n        assertEquals(sessionId, tempBuffer.getInt(offset));\n        offset += SIZE_OF_INT;\n        final int expectedSourceIdentityLength = MAX_KEY_LENGTH - offset - SIZE_OF_INT - SIZE_OF_LONG;\n        assertEquals(expectedSourceIdentityLength, tempBuffer.getInt(offset));\n        assertTrue(expectedSourceIdentityLength < sourceIdentity.length());\n        offset += SIZE_OF_INT;\n        assertEquals(\n            sourceIdentity.substring(0, expectedSourceIdentityLength),\n            tempBuffer.getStringWithoutLengthAscii(offset, expectedSourceIdentityLength));\n        offset += expectedSourceIdentityLength;\n        assertEquals(archiveId, tempBuffer.getLong(offset));\n        offset += SIZE_OF_LONG;\n\n        offset = BitUtil.align(offset, SIZE_OF_INT);\n        final String expectedPrefix = \"rec-pos: 54311 42 -13 \";\n        assertEquals(expectedPrefix, tempBuffer.getStringWithoutLengthAscii(offset, expectedPrefix.length()));\n        offset += expectedPrefix.length();\n        final int expectedStrippedChannelLength =\n            MAX_LABEL_LENGTH - expectedPrefix.length() - ArchiveCounters.lengthOfArchiveIdLabel(archiveId);\n        assertTrue(expectedStrippedChannelLength < strippedChannel.length());\n        assertEquals(\n            strippedChannel.substring(0, expectedStrippedChannelLength),\n            tempBuffer.getStringWithoutLengthAscii(offset, expectedStrippedChannelLength));\n        offset += expectedStrippedChannelLength;\n        assertEquals(\n            \" - archiveId=-9223372036854775808\",\n            tempBuffer.getStringWithoutLengthAscii(offset, ArchiveCounters.lengthOfArchiveIdLabel(archiveId)));\n    }\n\n    @Test\n    void allocateShouldAlignLabelByFourBytes()\n    {\n        final long archiveId = 888;\n        final long recordingId = 1;\n        final int sessionId = 30;\n        final int streamId = 222;\n        final String strippedChannel = \"channel\";\n        final String sourceIdentity = \"source\";\n        final UnsafeBuffer tempBuffer = new UnsafeBuffer(new byte[METADATA_LENGTH]);\n        final Counter counter = mock(Counter.class);\n        final Aeron aeron = mock(Aeron.class);\n        when(aeron.addCounter(\n            eq(RECORDING_POSITION_TYPE_ID),\n            eq(tempBuffer),\n            eq(0),\n            eq(30),\n            eq(tempBuffer),\n            eq(32),\n            eq(41)))\n            .thenReturn(counter);\n\n        final Counter result = RecordingPos.allocate(\n            aeron,\n            tempBuffer,\n            archiveId,\n            recordingId,\n            sessionId,\n            streamId,\n            strippedChannel,\n            sourceIdentity);\n\n        assertSame(counter, result);\n        int offset = 0;\n        assertEquals(recordingId, tempBuffer.getLong(offset));\n        offset += SIZE_OF_LONG;\n        assertEquals(sessionId, tempBuffer.getInt(offset));\n        offset += SIZE_OF_INT;\n        assertEquals(sourceIdentity.length(), tempBuffer.getInt(offset));\n        offset += SIZE_OF_INT;\n        assertEquals(sourceIdentity, tempBuffer.getStringWithoutLengthAscii(offset, sourceIdentity.length()));\n        offset += sourceIdentity.length();\n        assertEquals(archiveId, tempBuffer.getLong(offset));\n        offset += SIZE_OF_LONG;\n\n        offset = BitUtil.align(offset, SIZE_OF_INT);\n        final String expectedLabelPrefix = \"rec-pos: 1 30 222 \";\n        assertEquals(expectedLabelPrefix, tempBuffer.getStringWithoutLengthAscii(offset, expectedLabelPrefix.length()));\n        offset += expectedLabelPrefix.length();\n        assertEquals(strippedChannel, tempBuffer.getStringWithoutLengthAscii(offset, strippedChannel.length()));\n        offset += strippedChannel.length();\n        assertEquals(\n            \" - archiveId=888\",\n            tempBuffer.getStringWithoutLengthAscii(offset, ArchiveCounters.lengthOfArchiveIdLabel(archiveId)));\n    }\n\n    @Test\n    void shouldFindByRecordingIdAndArchiveId()\n    {\n        final long recordingId = 42;\n        final long archiveId = 19;\n        final int sourceIdentityLength = 10;\n        final CountersReader countersReader = mock(CountersReader.class);\n        when(countersReader.maxCounterId()).thenReturn(5);\n        when(countersReader.getCounterState(anyInt())).thenReturn(RECORD_ALLOCATED);\n        when(countersReader.getCounterTypeId(0)).thenReturn(0);\n        when(countersReader.getCounterTypeId(2)).thenReturn(0);\n        when(countersReader.getCounterTypeId(1)).thenReturn(RECORDING_POSITION_TYPE_ID);\n        when(countersReader.getCounterTypeId(3)).thenReturn(RECORDING_POSITION_TYPE_ID);\n        final AtomicBuffer metaBuffer = mock(AtomicBuffer.class);\n        when(countersReader.metaDataBuffer()).thenReturn(metaBuffer);\n        when(metaBuffer.getLong(METADATA_LENGTH + KEY_OFFSET + RECORDING_ID_OFFSET)).thenReturn(recordingId);\n        final int keyOffset = 3 * METADATA_LENGTH + KEY_OFFSET;\n        when(metaBuffer.getLong(keyOffset + RECORDING_ID_OFFSET)).thenReturn(recordingId);\n        when(metaBuffer.getInt(keyOffset + SOURCE_IDENTITY_LENGTH_OFFSET)).thenReturn(sourceIdentityLength);\n        when(metaBuffer.getLong(keyOffset + SOURCE_IDENTITY_OFFSET + sourceIdentityLength)).thenReturn(archiveId);\n\n        assertEquals(3, RecordingPos.findCounterIdByRecording(countersReader, recordingId, archiveId));\n\n        assertEquals(\n            NULL_RECORDING_ID,\n            RecordingPos.findCounterIdByRecording(countersReader, recordingId, Long.MIN_VALUE));\n\n        assertEquals(1, RecordingPos.findCounterIdByRecording(countersReader, recordingId, NULL_VALUE));\n    }\n\n    @Test\n    void shouldFindBySessionIdAndArchiveId()\n    {\n        final int sessionId = 888;\n        final long archiveId = 19;\n        final int sourceIdentityLength = 3;\n        final CountersReader countersReader = mock(CountersReader.class);\n        when(countersReader.maxCounterId()).thenReturn(5);\n        when(countersReader.getCounterState(anyInt())).thenReturn(RECORD_ALLOCATED);\n        when(countersReader.getCounterTypeId(anyInt())).thenReturn(RECORDING_POSITION_TYPE_ID);\n        final AtomicBuffer metaBuffer = mock(AtomicBuffer.class);\n        when(countersReader.metaDataBuffer()).thenReturn(metaBuffer);\n        when(metaBuffer.getInt(METADATA_LENGTH + KEY_OFFSET + SESSION_ID_OFFSET)).thenReturn(sessionId);\n        final int keyOffset = 2 * METADATA_LENGTH + KEY_OFFSET;\n        when(metaBuffer.getInt(keyOffset + SESSION_ID_OFFSET)).thenReturn(sessionId);\n        when(metaBuffer.getInt(keyOffset + SOURCE_IDENTITY_LENGTH_OFFSET)).thenReturn(sourceIdentityLength);\n        when(metaBuffer.getLong(keyOffset + SOURCE_IDENTITY_OFFSET + sourceIdentityLength)).thenReturn(archiveId);\n\n        assertEquals(2, RecordingPos.findCounterIdBySession(countersReader, sessionId, archiveId));\n\n        assertEquals(\n            NULL_RECORDING_ID,\n            RecordingPos.findCounterIdBySession(countersReader, sessionId, Long.MIN_VALUE));\n\n        assertEquals(1, RecordingPos.findCounterIdBySession(countersReader, sessionId, NULL_VALUE));\n    }\n}\n"
  },
  {
    "path": "aeron-client/README.md",
    "content": "Aeron Client\n===\n\n[![Javadocs](http://www.javadoc.io/badge/io.aeron/aeron-all.svg)](http://www.javadoc.io/doc/io.aeron/aeron-all)\n\nAeron clients are used to communicate to the media driver for the publishing of messages via publications and consuming messages of replicated publication images via subscriptions.\n\nClients communicate over IPC to the media driver thus allowing the media driver to run out of process and be in a different language to the clients."
  },
  {
    "path": "aeron-client/src/main/c/CMakeLists.txt",
    "content": "# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ninclude(CheckSymbolExists)\n\nif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n    set(CMAKE_REQUIRED_DEFINITIONS \"-D_GNU_SOURCE\")\n    add_definitions(-D_DEFAULT_SOURCE)\nendif ()\n\nif (MSVC AND \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\")\n    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)\n    set(BUILD_SHARED_LIBS ON)\n    set(AERON_LIB_WINSOCK_LIBS wsock32 ws2_32 Iphlpapi)\n    set(WSAPOLL_PROTOTYPE_EXISTS True)\nendif ()\n\nif (NOT MSVC)\n    message(STATUS \"Looking up: /dev/urandom\")\n    execute_process(\n        COMMAND ls /dev/urandom\n        RESULT_VARIABLE DEV_URANDOM_EXISTS\n        OUTPUT_QUIET\n    )\nendif ()\n\nif (MSVC)\n    set(AERON_STATIC_LIB_LINK_OPTS \"\")\nelse()\n    # Because dlsym() is used to load strategies.\n    set(AERON_STATIC_LIB_LINK_OPTS \"-rdynamic\")\nendif()\n\nif (WSAPOLL_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_WSAPOLL)\nendif ()\n\ncheck_include_file(\"bsd/stdlib.h\" BSDSTDLIB_H_EXISTS)\nfind_library(LIBBSD_EXISTS NAMES bsd libbsd)\n\nif (LIBBSD_EXISTS)\n    set(CMAKE_REQUIRED_LIBRARIES \"${CMAKE_REQUIRED_LIBRARIES} -lbsd\")\nendif ()\n\nif (NOT BSDSTDLIB_H_EXISTS)\n    check_symbol_exists(arc4random \"stdlib.h\" ARC4RANDOM_PROTOTYPE_EXISTS)\n    check_symbol_exists(posix_memalign \"stdlib.h\" POSIX_MEMALIGN_PROTOTYPE_EXISTS)\n    check_symbol_exists(reallocf \"stdlib.h\" REALLOCF_PROTOTYPE_EXISTS)\nelse ()\n    add_definitions(-DHAVE_BSDSTDLIB_H)\n    check_symbol_exists(arc4random \"bsd/stdlib.h\" ARC4RANDOM_PROTOTYPE_EXISTS)\n    check_symbol_exists(posix_memalign \"bsd/stdlib.h\" POSIX_MEMALIGN_PROTOTYPE_EXISTS)\n    check_symbol_exists(reallocf \"bsd/stdlib.h\" REALLOCF_PROTOTYPE_EXISTS)\nendif ()\n\ncheck_symbol_exists(fallocate \"fcntl.h\" FALLOCATE_PROTOTYPE_EXISTS)\ncheck_symbol_exists(posix_fallocate \"fcntl.h\" POSIX_FALLOCATE_PROTOTYPE_EXISTS)\ncheck_symbol_exists(F_PREALLOCATE \"fcntl.h\" F_PREALLOCATE_PROTOTYPE_EXISTS)\n\nif (FALLOCATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_FALLOCATE)\nendif ()\n\nif (POSIX_FALLOCATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_POSIX_FALLOCATE)\nendif ()\n\nif (F_PREALLOCATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_F_PREALLOCATE)\nendif ()\n\nif (ARC4RANDOM_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_ARC4RANDOM)\nelse ()\n    message(WARNING \"Could not find arc4random. If on Linux, is libbsd installed?\")\nendif ()\n\nif (DEV_URANDOM_EXISTS EQUAL 0)\n    message(STATUS \"Found: /dev/urandom\")\n    add_definitions(-DHAVE_DEV_URANDOM)\nendif ()\n\nif (POSIX_MEMALIGN_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_POSIX_MEMALIGN)\nendif ()\n\nif (REALLOCF_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_REALLOCF)\nendif ()\n\nset(SOURCE\n    collections/aeron_array_to_ptr_hash_map.c\n    collections/aeron_bit_set.c\n    collections/aeron_hashing.c\n    collections/aeron_int64_counter_map.c\n    collections/aeron_int64_to_ptr_hash_map.c\n    collections/aeron_int64_to_tagged_ptr_hash_map.c\n    collections/aeron_linked_queue.c\n    collections/aeron_map.c\n    collections/aeron_str_to_ptr_hash_map.c\n    concurrent/aeron_atomic.c\n    concurrent/aeron_blocking_linked_queue.c\n    concurrent/aeron_broadcast_receiver.c\n    concurrent/aeron_broadcast_transmitter.c\n    concurrent/aeron_counters_manager.c\n    concurrent/aeron_distinct_error_log.c\n    concurrent/aeron_executor.c\n    concurrent/aeron_logbuffer_descriptor.c\n    concurrent/aeron_mpsc_concurrent_array_queue.c\n    concurrent/aeron_mpsc_rb.c\n    concurrent/aeron_spsc_concurrent_array_queue.c\n    concurrent/aeron_spsc_rb.c\n    concurrent/aeron_term_gap_filler.c\n    concurrent/aeron_term_gap_scanner.c\n    concurrent/aeron_term_rebuilder.c\n    concurrent/aeron_term_scanner.c\n    concurrent/aeron_term_unblocker.c\n    concurrent/aeron_thread.c\n    protocol/aeron_udp_protocol.c\n    reports/aeron_loss_reporter.c\n    status/aeron_local_sockaddr.c\n    util/aeron_arrayutil.c\n    util/aeron_bitutil.c\n    util/aeron_clock.c\n    util/aeron_deque.c\n    util/aeron_dlopen.c\n    util/aeron_env.c\n    util/aeron_error.c\n    util/aeron_fileutil.c\n    util/aeron_http_util.c\n    util/aeron_math.c\n    util/aeron_netutil.c\n    util/aeron_parse_util.c\n    util/aeron_properties_util.c\n    util/aeron_strutil.c\n    util/aeron_symbol_table.c\n    uri/aeron_uri.c\n    uri/aeron_uri_string_builder.c\n    aeron_agent.c\n    aeron_alloc.c\n    aeron_client.c\n    aeron_client_conductor.c\n    aeron_cnc.c\n    aeron_cnc_file_descriptor.c\n    aeron_context.c\n    aeron_counter.c\n    aeron_exclusive_publication.c\n    aeron_fragment_assembler.c\n    aeron_image.c\n    aeron_log_buffer.c\n    aeron_publication.c\n    aeron_socket.c\n    aeron_subscription.c\n    aeron_windows.c\n    aeronc.c\n    aeron_version.c)\n\nset(HEADERS\n    collections/aeron_array_to_ptr_hash_map.h\n    collections/aeron_bit_set.h\n    collections/aeron_hashing.h\n    collections/aeron_int64_counter_map.h\n    collections/aeron_int64_to_ptr_hash_map.h\n    collections/aeron_int64_to_tagged_ptr_hash_map.h\n    collections/aeron_linked_queue.h\n    collections/aeron_map.h\n    collections/aeron_str_to_ptr_hash_map.h\n    command/aeron_control_protocol.h\n    concurrent/aeron_atomic.h\n    concurrent/aeron_atomic64_gcc_x86_64.h\n    concurrent/aeron_atomic64_msvc.h\n    concurrent/aeron_atomic64_c11.h\n    concurrent/aeron_blocking_linked_queue.h\n    concurrent/aeron_broadcast_descriptor.h\n    concurrent/aeron_broadcast_receiver.h\n    concurrent/aeron_broadcast_transmitter.h\n    concurrent/aeron_concurrent_array_queue.h\n    concurrent/aeron_counters_manager.h\n    concurrent/aeron_distinct_error_log.h\n    concurrent/aeron_executor.h\n    concurrent/aeron_logbuffer_descriptor.h\n    concurrent/aeron_mpsc_concurrent_array_queue.h\n    concurrent/aeron_mpsc_rb.h\n    concurrent/aeron_rb.h\n    concurrent/aeron_spsc_concurrent_array_queue.h\n    concurrent/aeron_spsc_rb.h\n    concurrent/aeron_term_gap_filler.h\n    concurrent/aeron_term_gap_scanner.h\n    concurrent/aeron_term_rebuilder.h\n    concurrent/aeron_term_scanner.h\n    concurrent/aeron_term_unblocker.h\n    concurrent/aeron_thread.h\n    protocol/aeron_udp_protocol.h\n    reports/aeron_loss_reporter.h\n    status/aeron_local_sockaddr.h\n    util/aeron_arrayutil.h\n    util/aeron_bitutil.h\n    util/aeron_clock.h\n    util/aeron_deque.h\n    util/aeron_dlopen.h\n    util/aeron_env.h\n    util/aeron_error.h\n    util/aeron_fileutil.h\n    util/aeron_http_util.h\n    util/aeron_math.h\n    util/aeron_netutil.h\n    util/aeron_parse_util.h\n    util/aeron_platform.h\n    util/aeron_properties_util.h\n    util/aeron_strutil.h\n    util/aeron_symbol_table.h\n    uri/aeron_uri.h\n    uri/aeron_uri_string_builder.h\n    aeron_agent.h\n    aeron_alloc.h\n    aeron_client.h\n    aeron_client_conductor.h\n    aeron_cnc_file_descriptor.h\n    aeron_common.h\n    aeron_context.h\n    aeron_counter.h\n    aeron_counters.h\n    aeron_exclusive_publication.h\n    aeron_fragment_assembler.h\n    aeron_image.h\n    aeron_log_buffer.h\n    aeron_publication.h\n    aeron_socket.h\n    aeron_subscription.h\n    aeron_windows.h\n    aeronc.h)\n\nadd_library(aeron SHARED ${SOURCE} ${HEADERS})\nadd_library(aeron::aeron ALIAS aeron)\ntarget_include_directories(aeron\n    PUBLIC \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\" \"$<INSTALL_INTERFACE:include/aeron>\")\n\nadd_library(aeron_static STATIC ${SOURCE} ${HEADERS})\nadd_library(aeron::aeron_static ALIAS aeron_static)\ntarget_include_directories(aeron_static\n    PUBLIC \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\" \"$<INSTALL_INTERFACE:include/aeron>\")\n\nset(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -DDISABLE_BOUNDS_CHECKS\")\n\nif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n    set(AERON_LIB_M_LIBS m)\n\n    if (LIBBSD_EXISTS)\n        set(AERON_LIB_BSD_LIBS bsd)\n    endif ()\n\n    if (\"${CMAKE_SYSTEM_PROCESSOR}\" MATCHES \"aarch64\")\n        set(AERON_LIB_ATOMIC_LIBS atomic)\n    endif ()\nendif ()\n\ntarget_link_libraries(\n    aeron\n    ${CMAKE_DL_LIBS}\n    ${AERON_LIB_M_LIBS}\n    ${AERON_LIB_BSD_LIBS}\n    ${AERON_LIB_ATOMIC_LIBS}\n    ${CMAKE_THREAD_LIBS_INIT}\n    ${AERON_LIB_WINSOCK_LIBS})\n\ntarget_link_libraries(\n    aeron_static INTERFACE\n    ${CMAKE_DL_LIBS}\n    ${AERON_LIB_M_LIBS}\n    ${AERON_LIB_BSD_LIBS}\n    ${AERON_LIB_ATOMIC_LIBS}\n    ${CMAKE_THREAD_LIBS_INIT}\n    ${AERON_LIB_WINSOCK_LIBS}\n    ${AERON_STATIC_LIB_LINK_OPTS}\n    )\n\nif (AERON_INSTALL_TARGETS)\n    install(\n        TARGETS aeron aeron_static\n        EXPORT aeron-targets\n        RUNTIME DESTINATION lib\n        LIBRARY DESTINATION lib\n        ARCHIVE DESTINATION lib)\n    install(DIRECTORY ./ DESTINATION include/aeron FILES_MATCHING PATTERN \"*.h\")\nendif ()\n"
  },
  {
    "path": "aeron-client/src/main/c/README.md",
    "content": "# C API\n\nHere you will find the source for the C API for Aeron. The build process builds\na library for the client and places it in the following location\n\n    ${CMAKE_CURRENT_BINARY_DIR}/lib/libaeron.so or libaeron.dylib\n\n## Dependencies\n\nThe C API library requires the following dependencies.\n\n- Aeron C API Library, source of which is included here and built and placed in `${CMAKE_CURRENT_BINARY_DIR}/lib`\n- Linux Dependencies:\n    - C Library (for the system built on)\n    - `-lpthread` - pthread Library\n    - `-ldl` - DL Library\n    - `-lm` - Math Library\n- Windows Dependencies:\n\t- Windows Version >= Vista \n\t- MSVC >= v141 (Visual Studio 2017)\n\n## Documentation and Samples\n\nThe C API has a single header that contains the API documentation \n[here](https://github.com/aeron-io/aeron/blob/master/aeron-client/src/main/c/aeronc.h)\n\nThe System Tests for the API and the C driver can be found \n[here](https://github.com/aeron-io/aeron/blob/master/aeron-driver/src/test/c/aeron_c_system_test.cpp).\n\t\nSamples of usage of the C API are in development.\n\t\n## Configuration\n\nConfiguration for the C API can be done programmatically and/or via environment variables. The variables are directly related to the Java properties\nfor the Java API. The environment variables simply have `_` in the place of `.`. For example, setting the environment variable `AERON_DIR` is equivalent\nto setting `aeron.dir` in the Java API.\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_agent.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n#include <errno.h>\n\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_dlopen.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"util/aeron_symbol_table.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_alloc.h\"\n\nvoid aeron_idle_strategy_sleeping_idle(void *state, int work_count)\n{\n    uint64_t *duration_ns = (uint64_t *)state;\n\n    if (work_count > 0)\n    {\n        return;\n    }\n\n    aeron_nano_sleep(*duration_ns);\n}\n\nint aeron_idle_strategy_sleeping_init_args(void **state, const char *env_var, const char *init_args)\n{\n    if (aeron_alloc(state, sizeof(uint64_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate sleeping state\");\n        return -1;\n    }\n\n    uint64_t *duration_ns = (uint64_t *)*state;\n    if (NULL == init_args)\n    {\n        *duration_ns = 1;\n    }\n    else\n    {\n        return aeron_parse_duration_ns(init_args, duration_ns);\n    }\n\n    return 0;\n}\n\nvoid aeron_idle_strategy_yielding_idle(void *state, int work_count)\n{\n    if (work_count > 0)\n    {\n        return;\n    }\n\n    sched_yield();\n}\n\nvoid aeron_idle_strategy_busy_spinning_idle(void *state, int work_count)\n{\n    if (work_count > 0)\n    {\n        return;\n    }\n\n    proc_yield();\n}\n\nvoid aeron_idle_strategy_noop_idle(void *state, int work_count)\n{\n}\n\n#define AERON_IDLE_STRATEGY_BACKOFF_STATE_NOT_IDLE 0\n#define AERON_IDLE_STRATEGY_BACKOFF_STATE_SPINNING 1\n#define AERON_IDLE_STRATEGY_BACKOFF_STATE_YIELDING 2\n#define AERON_IDLE_STRATEGY_BACKOFF_STATE_PARKING 3\n\ntypedef struct aeron_idle_strategy_backoff_state_stct\n{\n    uint8_t pre_pad[AERON_CACHE_LINE_LENGTH - sizeof(uint64_t)];\n    uint64_t max_spins;\n    uint64_t max_yields;\n    uint64_t min_park_period_ns;\n    uint64_t max_park_period_ns;\n    uint64_t spins;\n    uint64_t yields;\n    uint64_t park_period_ns;\n    uint8_t state;\n    uint8_t post_pad[AERON_CACHE_LINE_LENGTH];\n}\naeron_idle_strategy_backoff_state_t;\n\nvoid aeron_idle_strategy_backoff_idle(void *state, int work_count)\n{\n    aeron_idle_strategy_backoff_state_t *backoff_state = (aeron_idle_strategy_backoff_state_t *)state;\n\n    if (work_count > 0)\n    {\n        backoff_state->spins = 0;\n        backoff_state->yields = 0;\n        backoff_state->park_period_ns = backoff_state->min_park_period_ns;\n        backoff_state->state = AERON_IDLE_STRATEGY_BACKOFF_STATE_NOT_IDLE;\n    }\n    else\n    {\n        switch (backoff_state->state)\n        {\n            case AERON_IDLE_STRATEGY_BACKOFF_STATE_NOT_IDLE:\n                backoff_state->state = AERON_IDLE_STRATEGY_BACKOFF_STATE_SPINNING;\n                backoff_state->spins++;\n\n                break;\n\n            case AERON_IDLE_STRATEGY_BACKOFF_STATE_SPINNING:\n                proc_yield();\n                if (++backoff_state->spins > backoff_state->max_spins)\n                {\n                    backoff_state->state = AERON_IDLE_STRATEGY_BACKOFF_STATE_YIELDING;\n                    backoff_state->yields = 0;\n                }\n                break;\n\n            case AERON_IDLE_STRATEGY_BACKOFF_STATE_YIELDING:\n                if (++backoff_state->yields > backoff_state->max_yields)\n                {\n                    backoff_state->state = AERON_IDLE_STRATEGY_BACKOFF_STATE_PARKING;\n                    backoff_state->park_period_ns = backoff_state->min_park_period_ns;\n                }\n                else\n                {\n                    sched_yield();\n                }\n                break;\n\n            case AERON_IDLE_STRATEGY_BACKOFF_STATE_PARKING:\n            default:\n                aeron_nano_sleep(backoff_state->park_period_ns);\n                backoff_state->park_period_ns =\n                    ((backoff_state->park_period_ns * 2) < backoff_state->max_park_period_ns) ?\n                        backoff_state->park_period_ns * 2 : backoff_state->max_park_period_ns;\n                break;\n        }\n    }\n}\n\nint aeron_idle_strategy_backoff_state_init(\n    void **state, uint64_t max_spins, uint64_t max_yields, uint64_t min_park_period_ns, uint64_t max_park_period_ns)\n{\n    if (aeron_alloc(state, sizeof(aeron_idle_strategy_backoff_state_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate backoff state\");\n        return -1;\n    }\n\n    aeron_idle_strategy_backoff_state_t *backoff_state = (aeron_idle_strategy_backoff_state_t *)*state;\n\n    backoff_state->max_spins = max_spins;\n    backoff_state->max_yields = max_yields;\n    backoff_state->min_park_period_ns = min_park_period_ns;\n    backoff_state->max_park_period_ns = max_park_period_ns;\n    backoff_state->spins = 0;\n    backoff_state->yields = 0;\n    backoff_state->park_period_ns = backoff_state->min_park_period_ns;\n\n    return 0;\n}\n\nstatic int aeron_idle_strategy_backoff_state_init_args(void **state, const char *env_var, const char *init_args)\n{\n    if (NULL == init_args)\n    {\n        return aeron_idle_strategy_backoff_state_init(\n            state,\n            AERON_IDLE_STRATEGY_BACKOFF_MAX_SPINS,\n            AERON_IDLE_STRATEGY_BACKOFF_MAX_YIELDS,\n            AERON_IDLE_STRATEGY_BACKOFF_MIN_PARK_PERIOD_NS,\n            AERON_IDLE_STRATEGY_BACKOFF_MAX_PARK_PERIOD_NS);\n    }\n\n    char spins_str[17], yields_str[17], min_park_str[17], max_park_str[17];\n\n    int matches = sscanf(\n        init_args,\n        \"%16[,.0-9]-%16[,.0-9]-%16[,.0-9mus]-%16[,.0-9mus]\",\n        spins_str,\n        yields_str,\n        min_park_str,\n        max_park_str);\n\n    if (4 != matches)\n    {\n        AERON_SET_ERR(EINVAL, \"init args malformed, 4 values required, found: %d for args: %s\", matches, init_args);\n        return -1;\n    }\n\n    errno = 0;\n    char *end_ptr = NULL;\n    uint64_t max_spins = strtoull(spins_str, &end_ptr, 10);\n    if ((0 == max_spins && 0 != errno) || end_ptr == spins_str)\n    {\n        AERON_SET_ERR(errno, \"max spins not parseable: %s\", spins_str);\n        return -1;\n    }\n\n    errno = 0;\n    end_ptr = NULL;\n    uint64_t max_yields = strtoull(yields_str, &end_ptr, 10);\n    if ((0 == max_yields && 0 != errno) || end_ptr == yields_str)\n    {\n        AERON_SET_ERR(errno, \"max yields not parseable: %s\", max_yields);\n        return -1;\n    }\n\n    uint64_t min_park_ns;\n    if (aeron_parse_duration_ns(min_park_str, &min_park_ns) < 0)\n    {\n        AERON_SET_ERR(EINVAL, \"min park period ns not parseable: %s\", min_park_str);\n        return -1;\n    }\n\n    uint64_t max_park_ns;\n    if (aeron_parse_duration_ns(max_park_str, &max_park_ns) < 0)\n    {\n        AERON_SET_ERR(EINVAL, \"max park period ns not parseable: %s\", max_park_str);\n        return -1;\n    }\n\n    return aeron_idle_strategy_backoff_state_init(state, max_spins, max_yields, min_park_ns, max_park_ns);\n}\n\nint aeron_idle_strategy_init_null(void **state, const char *env_var, const char *init_args)\n{\n    *state = NULL;\n    return 0;\n}\n\naeron_idle_strategy_t aeron_idle_strategy_sleeping =\n    {\n        aeron_idle_strategy_sleeping_idle,\n        aeron_idle_strategy_sleeping_init_args\n    };\n\naeron_idle_strategy_t aeron_idle_strategy_yielding =\n    {\n        aeron_idle_strategy_yielding_idle,\n        aeron_idle_strategy_init_null\n    };\n\naeron_idle_strategy_t aeron_idle_strategy_busy_spinning =\n    {\n        aeron_idle_strategy_busy_spinning_idle,\n        aeron_idle_strategy_init_null\n    };\n\naeron_idle_strategy_t aeron_idle_strategy_noop =\n    {\n        aeron_idle_strategy_noop_idle,\n        aeron_idle_strategy_init_null\n    };\n\naeron_idle_strategy_t aeron_idle_strategy_backoff =\n    {\n        aeron_idle_strategy_backoff_idle,\n        aeron_idle_strategy_backoff_state_init_args\n    };\n\nstatic const aeron_symbol_table_obj_t aeron_idle_strategy_table[] =\n    {\n        { \"sleeping\", \"aeron_idle_strategy_sleeping\", &aeron_idle_strategy_sleeping },\n        { \"sleep-ns\", \"aeron_idle_strategy_sleeping\", &aeron_idle_strategy_sleeping },\n        { \"yield\", \"aeron_idle_strategy_yielding\", &aeron_idle_strategy_yielding },\n        { \"spin\", \"aeron_idle_strategy_busy_spinning\", &aeron_idle_strategy_busy_spinning },\n        { \"noop\", \"aeron_idle_strategy_noop\", &aeron_idle_strategy_noop },\n        { \"backoff\", \"aeron_idle_strategy_backoff\", &aeron_idle_strategy_backoff },\n    };\n\nstatic const size_t aeron_idle_strategy_table_length =\n    sizeof(aeron_idle_strategy_table) / sizeof (aeron_symbol_table_obj_t);\n\naeron_idle_strategy_func_t aeron_idle_strategy_load(\n    const char *idle_strategy_name,\n    void **idle_strategy_state,\n    const char *env_var,\n    const char *init_args)\n{\n    aeron_idle_strategy_func_t idle_func = NULL;\n\n    if (NULL == idle_strategy_name || NULL == idle_strategy_state)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"invalid idle object name or state\");\n        return NULL;\n    }\n\n    aeron_idle_strategy_t *idle_strategy = aeron_symbol_table_obj_load(\n        aeron_idle_strategy_table, aeron_idle_strategy_table_length, idle_strategy_name, \"idle strategy\");\n\n    if (NULL == idle_strategy)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return NULL;\n    }\n\n    *idle_strategy_state = NULL;\n    idle_func = idle_strategy->idle;\n    aeron_idle_strategy_init_func_t idle_init_func = idle_strategy->init;\n\n    void *idle_state = NULL;\n    if (idle_init_func(&idle_state, env_var, init_args) < 0)\n    {\n        return NULL;\n    }\n\n    *idle_strategy_state = idle_state;\n\n    return idle_func;\n}\n\naeron_agent_on_start_func_t aeron_agent_on_start_load(const char *name)\n{\n    aeron_agent_on_start_func_t func = NULL;\n\n    if ((*(void **)(&func) = aeron_dlsym(RTLD_DEFAULT, name)) == NULL)\n    {\n        AERON_SET_ERR(EINVAL, \"could not find agent on_start func %s: dlsym - %s\", name, aeron_dlerror());\n        return NULL;\n    }\n\n    return func;\n}\n\nint aeron_agent_init(\n    aeron_agent_runner_t *runner,\n    const char *role_name,\n    void *state,\n    aeron_agent_on_start_func_t on_start,\n    void *on_start_state,\n    aeron_agent_do_work_func_t do_work,\n    aeron_agent_on_close_func_t on_close,\n    aeron_idle_strategy_func_t idle_strategy_func,\n    void *idle_strategy_state)\n{\n    if (NULL == runner || NULL == do_work || NULL == idle_strategy_func || role_name == NULL)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, runner: %s, do_work: %s, idle_strategy_func: %s, role_name: %s\",\n            AERON_NULL_STR(runner),\n            AERON_NULL_STR(do_work),\n            AERON_NULL_STR(idle_strategy_func),\n            AERON_NULL_STR(role_name));\n        return -1;\n    }\n\n    runner->agent_state = state;\n    runner->on_start = on_start;\n    runner->on_start_state = on_start_state;\n    runner->do_work = do_work;\n    runner->on_close = on_close;\n\n    size_t role_name_length = strlen(role_name) + 1;\n    if (aeron_alloc((void **)&runner->role_name, role_name_length) < 0)\n    {\n        AERON_APPEND_ERR(\"Failed to allocate role_name for runner: %s\", role_name);\n        return -1;\n    }\n    memcpy((char *)runner->role_name, role_name, role_name_length);\n\n    runner->idle_strategy_state = idle_strategy_state;\n    runner->idle_strategy = idle_strategy_func;\n    runner->running = true;\n    runner->state = AERON_AGENT_STATE_INITED;\n\n    return 0;\n}\n\nstatic void *agent_main(void *arg)\n{\n    aeron_agent_runner_t *runner = (aeron_agent_runner_t *)arg;\n\n    aeron_thread_set_name(runner->role_name);\n\n    if (NULL != runner->on_start)\n    {\n        runner->on_start(runner->on_start_state, runner->role_name);\n    }\n\n    while (aeron_agent_is_running(runner))\n    {\n        runner->idle_strategy(runner->idle_strategy_state, runner->do_work(runner->agent_state));\n    }\n\n    return NULL;\n}\n\nint aeron_agent_start(aeron_agent_runner_t *runner)\n{\n    if (NULL == runner)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"runner is null\");\n        return -1;\n    }\n\n    aeron_thread_attr_t attr;\n    int pthread_result;\n    if ((pthread_result = aeron_thread_attr_init(&attr)) != 0)\n    {\n        AERON_SET_ERR(pthread_result, \"%s\", \"Failed aeron_thread_attr_init\");\n        return -1;\n    }\n\n    if ((pthread_result = aeron_thread_create(&runner->thread, &attr, agent_main, runner)) != 0)\n    {\n        AERON_SET_ERR(pthread_result, \"%s\", \"Failed aeron_thread_create\");\n        return -1;\n    }\n\n    runner->state = AERON_AGENT_STATE_STARTED;\n\n    return 0;\n}\n\nint aeron_agent_stop(aeron_agent_runner_t *runner)\n{\n    if (NULL == runner)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"invalid argument\");\n        return -1;\n    }\n\n    AERON_SET_RELEASE(runner->running, false);\n\n    if (AERON_AGENT_STATE_STARTED == runner->state)\n    {\n        runner->state = AERON_AGENT_STATE_STOPPING;\n\n        int pthread_result;\n        if ((pthread_result = aeron_thread_join(runner->thread, NULL)))\n        {\n            AERON_SET_ERR(pthread_result, \"aeron_thread_join: %s\", strerror(pthread_result));\n            return -1;\n        }\n\n        runner->state = AERON_AGENT_STATE_STOPPED;\n    }\n    else if (AERON_AGENT_STATE_MANUAL == runner->state)\n    {\n        runner->state = AERON_AGENT_STATE_STOPPED;\n    }\n\n    return 0;\n}\n\nint aeron_agent_close(aeron_agent_runner_t *runner)\n{\n    if (NULL == runner)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"runner is null\");\n        return -1;\n    }\n\n    aeron_free((char *)runner->role_name);\n\n    if (NULL != runner->on_close)\n    {\n        runner->on_close(runner->agent_state);\n    }\n\n    return 0;\n}\n\nextern int aeron_agent_do_work(aeron_agent_runner_t *runner);\n\nextern bool aeron_agent_is_running(aeron_agent_runner_t *runner);\n\nextern void aeron_agent_idle(aeron_agent_runner_t *runner, int work_count);\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_agent.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AGENT_H\n#define AERON_AGENT_H\n\n#include \"concurrent/aeron_thread.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"aeron_common.h\"\n#include \"aeronc.h\"\n\ntypedef int (*aeron_agent_do_work_func_t)(void *);\n\ntypedef void (*aeron_agent_on_close_func_t)(void *);\n\n#define AERON_AGENT_STATE_UNUSED 0\n#define AERON_AGENT_STATE_INITED 1\n#define AERON_AGENT_STATE_STARTED 2\n#define AERON_AGENT_STATE_MANUAL 3\n#define AERON_AGENT_STATE_STOPPING 4\n#define AERON_AGENT_STATE_STOPPED 5\n\ntypedef struct aeron_idle_strategy_stct\n{\n    aeron_idle_strategy_func_t idle;\n    aeron_idle_strategy_init_func_t init;\n}\naeron_idle_strategy_t;\n\n#define AERON_IDLE_STRATEGY_BACKOFF_MAX_SPINS (10)\n#define AERON_IDLE_STRATEGY_BACKOFF_MAX_YIELDS (20)\n#define AERON_IDLE_STRATEGY_BACKOFF_MIN_PARK_PERIOD_NS (1000LL)\n#define AERON_IDLE_STRATEGY_BACKOFF_MAX_PARK_PERIOD_NS (1 * 1000 * 1000LL)\n\nvoid aeron_idle_strategy_sleeping_idle(void *state, int work_count);\n\nvoid aeron_idle_strategy_yielding_idle(void *state, int work_count);\n\nvoid aeron_idle_strategy_busy_spinning_idle(void *state, int work_count);\n\nvoid aeron_idle_strategy_noop_idle(void *state, int work_count);\n\nvoid aeron_idle_strategy_backoff_idle(void *state, int work_count);\n\nint aeron_idle_strategy_backoff_state_init(\n    void **state, uint64_t max_spins, uint64_t max_yields, uint64_t min_park_period_ns, uint64_t max_park_period_ns);\n\nint aeron_idle_strategy_init_null(void **state, const char *env_var, const char *load_args);\n\ntypedef struct aeron_agent_runner_stct\n{\n    const char *role_name;\n    void *agent_state;\n    void *idle_strategy_state;\n    void *on_start_state;\n    aeron_agent_on_start_func_t on_start;\n    aeron_agent_do_work_func_t do_work;\n    aeron_agent_on_close_func_t on_close;\n    aeron_idle_strategy_func_t idle_strategy;\n    aeron_thread_t thread;\n    volatile bool running;\n    uint8_t state;\n}\naeron_agent_runner_t;\n\naeron_idle_strategy_func_t aeron_idle_strategy_load(\n    const char *idle_strategy_name,\n    void **idle_strategy_state,\n    const char *env_var,\n    const char *load_args);\n\naeron_agent_on_start_func_t aeron_agent_on_start_load(const char *name);\n\nint aeron_agent_init(\n    aeron_agent_runner_t *runner,\n    const char *role_name,\n    void *state,\n    aeron_agent_on_start_func_t on_start,\n    void *on_start_state,\n    aeron_agent_do_work_func_t do_work,\n    aeron_agent_on_close_func_t on_close,\n    aeron_idle_strategy_func_t idle_strategy_func,\n    void *idle_strategy_state);\n\nint aeron_agent_start(aeron_agent_runner_t *runner);\n\ninline int aeron_agent_do_work(aeron_agent_runner_t *runner)\n{\n    return runner->do_work(runner->agent_state);\n}\n\ninline bool aeron_agent_is_running(aeron_agent_runner_t *runner)\n{\n    bool running;\n    AERON_GET_ACQUIRE(running, runner->running);\n    return running;\n}\n\ninline void aeron_agent_idle(aeron_agent_runner_t *runner, int work_count)\n{\n    runner->idle_strategy(runner->idle_strategy_state, work_count);\n}\n\nint aeron_agent_stop(aeron_agent_runner_t *runner);\n\nint aeron_agent_close(aeron_agent_runner_t *runner);\n\n#endif //AERON_AGENT_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_alloc.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#ifdef HAVE_BSDSTDLIB_H\n#include <bsd/stdlib.h>\n#endif\n#endif\n\n#include \"util/aeron_platform.h\"\n\n#include <stdlib.h>\n#include <errno.h>\n#include <inttypes.h>\n\n#if defined(AERON_COMPILER_MSVC)\n#include <windows.h>\n#endif\n\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_alloc.h\"\n\nint aeron_alloc(void **ptr, size_t size)\n{\n    void *bytes = malloc(size);\n    if (NULL == bytes)\n    {\n        *ptr = NULL;\n        AERON_SET_ERR(ENOMEM, \"Failed to allocate %\" PRIu64 \" bytes\", (uint64_t)size);\n        return -1;\n    }\n\n    memset(bytes, 0, size);\n    *ptr = bytes;\n\n    return 0;\n}\n\nint aeron_alloc_aligned(void **ptr, size_t *offset, size_t size, size_t alignment)\n{\n    if (!(AERON_IS_POWER_OF_TWO(alignment)))\n    {\n        AERON_SET_ERR(EINVAL, \"Alignment must be a power of two: %\" PRIu64, (uint64_t)alignment);\n        return -1;\n    }\n\n#ifdef HAVE_POSIX_MEMALIGN\n    int rc = posix_memalign(ptr, alignment, size);\n    if (rc != 0)\n    {\n        // any non zero value is a failure.\n        AERON_SET_ERR(rc, \"Failed to allocate %\" PRIu64 \" bytes, alignment %\" PRIu64, (uint64_t)size, (uint64_t)alignment);\n        return -1;\n    }\n    memset(*ptr, 0, size);\n    *offset = 0;\n#else\n    if (aeron_alloc(ptr, size + alignment) < 0)\n    {\n        AERON_APPEND_ERR(\"alignment=%\" PRIu64, alignment);\n        return -1;\n    }\n\n    intptr_t addr = (intptr_t)*ptr;\n    *offset = alignment - (addr & (alignment - 1));\n#endif\n\n    return 0;\n}\n\nint aeron_reallocf(void **ptr, size_t size)\n{\n    if (0 == size)\n    {\n        aeron_free(*ptr);\n        *ptr = NULL;\n        return 0;\n    }\n\n#ifdef HAVE_REALLOCF\n    if (NULL == (*ptr = reallocf(*ptr, size)))\n    {\n        AERON_SET_ERR(ENOMEM, \"Failed to re-allocate memory with a new size %\" PRIu64, (uint64_t)size);\n        return -1;\n    }\n#else\n    void *new_ptr = NULL;\n    /* mimic reallocf */\n    if (NULL == (new_ptr = realloc(*ptr, size)))\n    {\n        free(*ptr);\n        *ptr = NULL;\n        AERON_SET_ERR(ENOMEM, \"Failed to re-allocate memory with a new size %\" PRIu64, (uint64_t)size);\n        return -1;\n    }\n\n    *ptr = new_ptr;\n#endif\n    return 0;\n}\n\nvoid aeron_free(void *ptr)\n{\n    free(ptr);\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_alloc.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ALLOC_H\n#define AERON_ALLOC_H\n\n#include <stddef.h>\n\nint aeron_alloc(void **ptr, size_t size);\nint aeron_alloc_aligned(void **ptr, size_t *offset, size_t size, size_t alignment);\nint aeron_reallocf(void **ptr, size_t size);\nvoid aeron_free(void *ptr);\n\n#endif //AERON_ALLOC_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_client.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#ifdef HAVE_BSDSTDLIB_H\n#include <bsd/stdlib.h>\n#endif\n#endif\n\n#include <stdio.h>\n#include <stdint.h>\n#include <inttypes.h>\n\n#include \"aeronc.h\"\n#include \"aeron_client.h\"\n\nstatic int aeron_do_work(void *clientd)\n{\n    aeron_t *client = (aeron_t *)clientd;\n    return aeron_client_conductor_do_work(&client->conductor);\n}\n\nstatic void aeron_on_close(void *clientd)\n{\n    aeron_t *client = (aeron_t *)clientd;\n    aeron_client_conductor_on_close(&client->conductor);\n}\n\nstatic const char* aeron_client_managed_resource_type_to_string(\n    const aeron_client_managed_resource_type_t resource_type)\n{\n    switch (resource_type)\n    {\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION:\n            return \"publication\";\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION:\n            return \"exclusive_publication\";\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION:\n            return \"subscription\";\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER:\n            return \"counter\";\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_DESTINATION:\n            return \"destination\";\n        default:\n            return \"unknown\";\n    }\n}\n\nstatic int aeron_async_resource_poll(\n    void **resource,\n    const aeron_client_managed_resource_type_t resource_type,\n    aeron_client_registering_resource_t *async)\n{\n    if (NULL == resource || NULL == async)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, resource: %s, async: %s\",\n            AERON_NULL_STR(resource),\n            AERON_NULL_STR(async));\n        return -1;\n    }\n\n    *resource = NULL;\n\n    if (resource_type != async->type)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must be valid, async->type: %d (expected: %d)\",\n            (int)(async->type),\n            (int)resource_type);\n        return -1;\n    }\n\n    aeron_client_registration_status_t registration_status;\n    AERON_GET_ACQUIRE(registration_status, async->registration_status);\n\n    switch (registration_status)\n    {\n        case AERON_CLIENT_REGISTRATION_STATUS_AWAITING:\n        {\n            return 0;\n        }\n\n        case AERON_CLIENT_REGISTRATION_STATUS_ERRORED:\n        {\n            AERON_SET_ERR(\n                -async->error_code,\n                \"async_add_%s registration error\\n%.*s\",\n                aeron_client_managed_resource_type_to_string(resource_type),\n                (int)async->error_message_length,\n                async->error_message);\n            aeron_async_cmd_free(async);\n            return -1;\n        }\n\n        case AERON_CLIENT_REGISTRATION_STATUS_REGISTERED:\n        {\n            switch (resource_type)\n            {\n                case AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION:\n                    *resource = async->resource.publication;\n                    break;\n                case AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION:\n                    *resource = async->resource.exclusive_publication;\n                    break;\n                case AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION:\n                    *resource = async->resource.subscription;\n                    break;\n                case AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER:\n                    *resource = async->resource.counter;\n                    break;\n                default:\n                    break;\n            }\n            aeron_async_cmd_free(async);\n            return 1;\n        }\n\n        case AERON_CLIENT_REGISTRATION_STATUS_TIMED_OUT:\n        {\n            AERON_SET_ERR(\n                AERON_CLIENT_ERROR_DRIVER_TIMEOUT,\n                \"async_add_%s no response from media driver\",\n                aeron_client_managed_resource_type_to_string(resource_type));\n            aeron_async_cmd_free(async);\n            return -1;\n        }\n\n        default:\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"async_add_%s unknown registration_status: %d\",\n                 aeron_client_managed_resource_type_to_string(resource_type),\n                 registration_status);\n            aeron_async_cmd_free(async);\n            return -1;\n        }\n    }\n}\n\nstatic int aeron_async_destination_poll(aeron_async_destination_t *async)\n{\n    void *tmp;\n    return aeron_async_resource_poll(&tmp, AERON_CLIENT_MANAGED_RESOURCE_TYPE_DESTINATION, async);\n}\n\nint aeron_init(aeron_t **client, aeron_context_t *context)\n{\n    aeron_t *_client = NULL;\n\n    if (NULL == client || NULL == context)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, client: %s, context: %d\",\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(context));\n        goto error;\n    }\n\n    if (aeron_alloc((void **)&_client, sizeof(aeron_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate aeron_client\");\n        goto error;\n    }\n\n    _client->context = context;\n\n    _client->runner.state = AERON_AGENT_STATE_UNUSED;\n    _client->runner.role_name = NULL;\n    _client->runner.on_close = NULL;\n\n    if (aeron_client_connect_to_driver(&context->cnc_map, context) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_client_conductor_init(&_client->conductor, context) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_agent_init(\n        &_client->runner,\n        \"aeron-client-conductor\",\n        _client,\n        _client->context->agent_on_start_func,\n        _client->context->agent_on_start_state,\n        aeron_do_work,\n        aeron_on_close,\n        _client->context->idle_strategy_func,\n        _client->context->idle_strategy_state) < 0)\n    {\n        goto error;\n    }\n\n    *client = _client;\n    return 0;\n\nerror:\n    if (NULL != _client)\n    {\n        aeron_free(_client);\n    }\n    return -1;\n}\n\nint aeron_start(aeron_t *client)\n{\n    if (NULL == client)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"client must not be null\");\n        return -1;\n    }\n\n    if (!client->context->use_conductor_agent_invoker)\n    {\n        if (aeron_agent_start(&client->runner) < 0)\n        {\n            return -1;\n        }\n    }\n    else\n    {\n        if (NULL != client->runner.on_start)\n        {\n            client->runner.on_start(client->runner.on_start_state, client->runner.role_name);\n        }\n\n        client->runner.state = AERON_AGENT_STATE_MANUAL;\n    }\n\n    return 0;\n}\n\nint aeron_main_do_work(aeron_t *client)\n{\n    if (NULL == client)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"client is null\");\n        return -1;\n    }\n\n    if (!client->context->use_conductor_agent_invoker)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"client is not configured to use agent invoker\");\n        return -1;\n    }\n\n    return aeron_agent_do_work(&client->runner);\n}\n\nvoid aeron_main_idle_strategy(aeron_t *client, int work_count)\n{\n    if (NULL != client)\n    {\n        aeron_agent_idle(&client->runner, work_count);\n    }\n}\n\nint aeron_close(aeron_t *client)\n{\n    if (NULL != client)\n    {\n        if (aeron_agent_stop(&client->runner) < 0)\n        {\n            return -1;\n        }\n\n        if (aeron_agent_close(&client->runner) < 0)\n        {\n            return -1;\n        }\n\n        aeron_free(client);\n    }\n\n    return 0;\n}\n\nbool aeron_is_closed(aeron_t *client)\n{\n    return aeron_client_conductor_is_closed(&client->conductor);\n}\n\ntypedef struct aeron_print_counters_stream_out_stct\n{\n    void (*stream_out)(const char *);\n}\naeron_print_counters_stream_out_t;\n\nvoid aeron_print_counters_format(\n    int64_t value,\n    int32_t id,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const char *label,\n    size_t label_length,\n    void *clientd)\n{\n    char buffer[AERON_COUNTERS_MANAGER_METADATA_LENGTH];\n    aeron_print_counters_stream_out_t *out = (aeron_print_counters_stream_out_t *)clientd;\n\n    snprintf(buffer, sizeof(buffer), \"%3\" PRId32 \": %20\" PRId64 \" - %.*s\\r\\n\",\n        id, value, (int)label_length, label);\n    out->stream_out(buffer);\n}\n\nvoid aeron_print_counters(aeron_t *client, void (*stream_out)(const char *))\n{\n    if (NULL == client || NULL == stream_out)\n    {\n        return;\n    }\n\n    aeron_print_counters_stream_out_t out = { .stream_out = stream_out };\n\n    aeron_counters_reader_foreach_counter(&client->conductor.counters_reader, aeron_print_counters_format, &out);\n}\n\naeron_context_t *aeron_context(aeron_t *client)\n{\n    if (NULL == client)\n    {\n        errno = EINVAL;\n        AERON_SET_ERR(EINVAL, \"aeron_context(NULL): %s\", strerror(EINVAL));\n        return NULL;\n    }\n\n    return client->context;\n}\n\nint64_t aeron_client_id(aeron_t *client)\n{\n    if (NULL == client)\n    {\n        errno = EINVAL;\n        AERON_SET_ERR(EINVAL, \"aeron_client_id(NULL): %s\", strerror(EINVAL));\n        return -1;\n    }\n\n    return client->conductor.client_id;\n}\n\nint64_t aeron_next_correlation_id(aeron_t *client)\n{\n    if (NULL == client)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"client is null\");\n        return -1;\n    }\n\n    return aeron_mpsc_rb_next_correlation_id(&client->conductor.to_driver_buffer);\n}\n\naeron_counters_reader_t *aeron_counters_reader(aeron_t *client)\n{\n    if (NULL == client)\n    {\n        errno = EINVAL;\n        AERON_SET_ERR(EINVAL, \"aeron_counters_reader(NULL): %s\", strerror(EINVAL));\n        return NULL;\n    }\n\n    return &client->conductor.counters_reader;\n}\n\nint64_t aeron_async_add_counter_get_registration_id(aeron_async_add_counter_t *add_counter)\n{\n    return add_counter->registration_id;\n}\n\nint64_t aeron_async_add_publication_get_registration_id(aeron_async_add_publication_t *add_publication)\n{\n    return add_publication->registration_id;\n}\n\n// deprecated\nint64_t aeron_async_add_exclusive_exclusive_publication_get_registration_id(\n    aeron_async_add_exclusive_publication_t *add_exclusive_publication)\n{\n    return add_exclusive_publication->registration_id;\n}\n\nint64_t aeron_async_add_exclusive_publication_get_registration_id(\n    aeron_async_add_exclusive_publication_t *add_exclusive_publication)\n{\n    return add_exclusive_publication->registration_id;\n}\n\nint64_t aeron_async_add_subscription_get_registration_id(aeron_async_add_subscription_t *add_subscription)\n{\n    return add_subscription->registration_id;\n}\n\nint64_t aeron_async_destination_get_registration_id(aeron_async_destination_t *async_destination)\n{\n    return async_destination->registration_id;\n}\n\nint aeron_async_add_publication(\n    aeron_async_add_publication_t **async, aeron_t *client, const char *uri, int32_t stream_id)\n{\n    if (NULL == async || NULL == client || NULL == uri)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must not be null, async: %s, client: %s, uri: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(uri));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_add_publication(async, &client->conductor, uri, stream_id);\n}\n\nint aeron_async_add_publication_poll(aeron_publication_t **publication, aeron_async_add_publication_t *async)\n{\n    return aeron_async_resource_poll((void **)publication, AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION, async);\n}\n\nint aeron_async_add_exclusive_publication(\n    aeron_async_add_exclusive_publication_t **async, aeron_t *client, const char *uri, int32_t stream_id)\n{\n    if (NULL == async || NULL == client || NULL == uri)\n    {\n        AERON_SET_ERR(EINVAL, \"aeron_async_add_exclusive_publication: %s\", strerror(EINVAL));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_add_exclusive_publication(async, &client->conductor, uri, stream_id);\n}\n\nint aeron_async_add_exclusive_publication_poll(\n    aeron_exclusive_publication_t **publication, aeron_async_add_exclusive_publication_t *async)\n{\n    return aeron_async_resource_poll((void **)publication, AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION, async);\n}\n\nint aeron_async_add_subscription(\n    aeron_async_add_subscription_t **async,\n    aeron_t *client,\n    const char *uri,\n    int32_t stream_id,\n    aeron_on_available_image_t on_available_image_handler,\n    void *on_available_image_clientd,\n    aeron_on_unavailable_image_t on_unavailable_image_handler,\n    void *on_unavailable_image_clientd)\n{\n    if (NULL == async || NULL == client || NULL == uri)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must be valid, async: %s, client: %s, uri: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(uri));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_add_subscription(\n        async,\n        &client->conductor,\n        uri,\n        stream_id,\n        on_available_image_handler,\n        on_available_image_clientd,\n        on_unavailable_image_handler,\n        on_unavailable_image_clientd);\n}\n\nint aeron_async_add_subscription_poll(aeron_subscription_t **subscription, aeron_async_add_subscription_t *async)\n{\n    return aeron_async_resource_poll((void **)subscription, AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION, async);\n}\n\nint aeron_async_next_session_id(aeron_async_get_next_available_session_id_t **async, aeron_t *client, int32_t stream_id)\n{\n    if (NULL == async || NULL == client)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_get_next_available_session_id(async, &client->conductor, stream_id);\n}\n\nint aeron_async_next_session_id_poll(int32_t *next_session_id, aeron_async_get_next_available_session_id_t *async)\n{\n    if (NULL == next_session_id || NULL == async)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, next_session_id: %s, async: %s\",\n            AERON_NULL_STR(next_session_id),\n            AERON_NULL_STR(async));\n        return -1;\n    }\n\n    if (AERON_CLIENT_MANAGED_RESOURCE_TYPE_NEXT_AVAILABLE_SESSION_ID != async->type)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must be valid, async->type: %d (expected: %d)\",\n            (int)async->type,\n            (int)AERON_CLIENT_MANAGED_RESOURCE_TYPE_NEXT_AVAILABLE_SESSION_ID);\n        return -1;\n    }\n\n    aeron_client_registration_status_t registration_status;\n    AERON_GET_ACQUIRE(registration_status, async->registration_status);\n\n    switch (registration_status)\n    {\n        case AERON_CLIENT_REGISTRATION_STATUS_AWAITING:\n        {\n            return 0;\n        }\n\n        case AERON_CLIENT_REGISTRATION_STATUS_ERRORED:\n        {\n            AERON_SET_ERR(\n                -async->error_code,\n                \"aeron_async_next_session_id registration\\n== Driver Error ==\\n%.*s\",\n                (int)async->error_message_length,\n                async->error_message);\n            aeron_async_cmd_free(async);\n            return -1;\n        }\n\n        case AERON_CLIENT_REGISTRATION_STATUS_REGISTERED:\n        {\n            *next_session_id = async->resource.next_session_id;\n            aeron_async_cmd_free(async);\n            return 1;\n        }\n\n        case AERON_CLIENT_REGISTRATION_STATUS_TIMED_OUT:\n        {\n            AERON_SET_ERR(\n                AERON_CLIENT_ERROR_DRIVER_TIMEOUT, \"%s\", \"aeron_async_next_session_id no response from media driver\");\n            aeron_async_cmd_free(async);\n            return -1;\n        }\n\n        default:\n        {\n            AERON_SET_ERR(EINVAL, \"aeron_async_next_session_id async status %s\", \"unknown\");\n            aeron_async_cmd_free(async);\n            return -1;\n        }\n    }\n}\n\nint aeron_async_add_counter(\n    aeron_async_add_counter_t **async,\n    aeron_t *client,\n    int32_t type_id,\n    const uint8_t *key_buffer,\n    size_t key_buffer_length,\n    const char *label_buffer,\n    size_t label_buffer_length)\n{\n    if (NULL == async || NULL == client)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_add_counter(\n        async,\n        &client->conductor,\n        type_id,\n        key_buffer,\n        key_buffer_length,\n        label_buffer,\n        label_buffer_length);\n}\n\nint aeron_async_add_counter_poll(aeron_counter_t **counter, aeron_async_add_counter_t *async)\n{\n    return aeron_async_resource_poll((void **)counter, AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER, async);\n}\n\nint aeron_async_add_static_counter(\n    aeron_async_add_counter_t **async,\n    aeron_t *client,\n    int32_t type_id,\n    const uint8_t *key_buffer,\n    size_t key_buffer_length,\n    const char *label_buffer,\n    size_t label_buffer_length,\n    int64_t registration_id)\n{\n    if (NULL == async || NULL == client)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_add_static_counter(\n        async,\n        &client->conductor,\n        type_id,\n        key_buffer,\n        key_buffer_length,\n        label_buffer,\n        label_buffer_length,\n        registration_id);\n}\n\nint aeron_publication_async_add_destination(\n    aeron_async_destination_t **async, aeron_t *client, aeron_publication_t *publication, const char *uri)\n{\n    if (NULL == async || NULL == client || NULL == publication || uri == NULL)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %d, publication: %s, uri: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(uri));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_add_publication_destination(async, &client->conductor, publication, uri);\n}\n\nint aeron_publication_async_destination_poll(aeron_async_destination_t *async)\n{\n    return aeron_async_destination_poll(async);\n}\n\nint aeron_publication_async_remove_destination(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_publication_t *publication,\n    const char *uri)\n{\n    if (NULL == async || NULL == client || NULL == publication || uri == NULL)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %s, publication: %s, uri: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(uri));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_remove_publication_destination(async, &client->conductor, publication, uri);\n}\n\nint aeron_publication_async_remove_destination_by_id(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_publication_t *publication,\n    int64_t destination_registration_id)\n{\n    if (NULL == async || NULL == client || NULL == publication)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %s, publication: %s, destination_registration_id: %\" PRId64,\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(publication),\n            destination_registration_id);\n        return -1;\n    }\n\n    return aeron_client_conductor_async_remove_publication_destination_by_id(\n        async, &client->conductor, publication, destination_registration_id);\n}\n\nint aeron_subscription_async_add_destination(\n    aeron_async_destination_t **async, aeron_t *client, aeron_subscription_t *subscription, const char *uri)\n{\n    if (NULL == async || NULL == client || NULL == subscription || uri == NULL)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %d, subscription: %s, uri: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(subscription),\n            AERON_NULL_STR(uri));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_add_subscription_destination(async, &client->conductor, subscription, uri);\n}\n\nint aeron_subscription_async_destination_poll(aeron_async_destination_t *async)\n{\n    return aeron_async_destination_poll(async);\n}\n\nint aeron_subscription_async_remove_destination(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_subscription_t *subscription,\n    const char *uri)\n{\n    if (NULL == async || NULL == client || NULL == subscription || uri == NULL)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %d, subscription: %s, uri: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(subscription),\n            AERON_NULL_STR(uri));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_remove_subscription_destination(async, &client->conductor, subscription, uri);\n}\n\nint aeron_exclusive_publication_async_add_destination(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_exclusive_publication_t *publication,\n    const char *uri)\n{\n    if (NULL == async || NULL == client || NULL == publication || uri == NULL)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %s, publication: %s, uri: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(uri));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_add_exclusive_publication_destination(\n        async, &client->conductor, publication, uri);\n}\n\nint aeron_exclusive_publication_async_destination_poll(aeron_async_destination_t *async)\n{\n    return aeron_async_destination_poll(async);\n}\n\nint aeron_exclusive_publication_async_remove_destination(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_exclusive_publication_t *publication,\n    const char *uri)\n{\n    if (NULL == async || NULL == client || NULL == publication || uri == NULL)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %s, publication: %s, uri: %s\",\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(uri));\n        return -1;\n    }\n\n    return aeron_client_conductor_async_remove_exclusive_publication_destination(\n        async, &client->conductor, publication, uri);\n}\n\nint aeron_exclusive_publication_async_remove_destination_by_id(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_exclusive_publication_t *publication,\n    int64_t destination_registration_id)\n{\n    if (NULL == async || NULL == client || NULL == publication)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, async: %s, client: %s, publication: %s, destination_registration_id: %\" PRId64,\n            AERON_NULL_STR(async),\n            AERON_NULL_STR(client),\n            AERON_NULL_STR(publication),\n            destination_registration_id);\n        return -1;\n    }\n\n    return aeron_client_conductor_async_remove_exclusive_publication_destination_by_id(\n        async, &client->conductor, publication, destination_registration_id);\n}\n\nint aeron_add_available_counter_handler(aeron_t *client, aeron_on_available_counter_pair_t *pair)\n{\n    if (NULL == client || NULL == pair)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must not be null, client: %s, pair: %s\", AERON_NULL_STR(client), AERON_NULL_STR(pair));\n        return -1;\n    }\n\n    if (aeron_client_conductor_is_closed(&client->conductor))\n    {\n        AERON_SET_ERR(EPERM, \"%s\", \"client conductor is closed\");\n        return -1;\n    }\n\n    aeron_client_handler_cmd_t cmd;\n    cmd.type = AERON_CLIENT_HANDLER_ADD_AVAILABLE_COUNTER;\n    cmd.handler.on_available_counter = pair->handler;\n    cmd.clientd = pair->clientd;\n\n    return aeron_client_conductor_async_handler(&client->conductor, &cmd);\n}\n\nint aeron_remove_available_counter_handler(aeron_t *client, aeron_on_available_counter_pair_t *pair)\n{\n    if (NULL == client || NULL == pair)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must not be null, client: %s, pair: %s\", AERON_NULL_STR(client), AERON_NULL_STR(pair));\n        return -1;\n    }\n\n    if (aeron_client_conductor_is_closed(&client->conductor))\n    {\n        AERON_SET_ERR(EPERM, \"%s\", \"client conductor is closed\");\n        return -1;\n    }\n\n    aeron_client_handler_cmd_t cmd;\n    cmd.type = AERON_CLIENT_HANDLER_REMOVE_AVAILABLE_COUNTER;\n    cmd.handler.on_available_counter = pair->handler;\n    cmd.clientd = pair->clientd;\n\n    return aeron_client_conductor_async_handler(&client->conductor, &cmd);\n}\n\nint aeron_add_unavailable_counter_handler(aeron_t *client, aeron_on_unavailable_counter_pair_t *pair)\n{\n    if (NULL == client || NULL == pair)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must not be null, client: %s, pair: %s\", AERON_NULL_STR(client), AERON_NULL_STR(pair));\n        return -1;\n    }\n\n    if (aeron_client_conductor_is_closed(&client->conductor))\n    {\n        AERON_SET_ERR(EPERM, \"%s\", \"client conductor is closed\");\n        return -1;\n    }\n\n    aeron_client_handler_cmd_t cmd;\n    cmd.type = AERON_CLIENT_HANDLER_ADD_UNAVAILABLE_COUNTER;\n    cmd.handler.on_unavailable_counter = pair->handler;\n    cmd.clientd = pair->clientd;\n\n    return aeron_client_conductor_async_handler(&client->conductor, &cmd);\n}\n\nint aeron_remove_unavailable_counter_handler(aeron_t *client, aeron_on_unavailable_counter_pair_t *pair)\n{\n    if (NULL == client || NULL == pair)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must not be null, client: %s, pair: %s\", AERON_NULL_STR(client), AERON_NULL_STR(pair));\n        return -1;\n    }\n\n    if (aeron_client_conductor_is_closed(&client->conductor))\n    {\n        AERON_SET_ERR(EPERM, \"%s\", \"client conductor is closed\");\n        return -1;\n    }\n\n    aeron_client_handler_cmd_t cmd;\n    cmd.type = AERON_CLIENT_HANDLER_REMOVE_UNAVAILABLE_COUNTER;\n    cmd.handler.on_unavailable_counter = pair->handler;\n    cmd.clientd = pair->clientd;\n\n    return aeron_client_conductor_async_handler(&client->conductor, &cmd);\n}\n\nint aeron_add_close_handler(aeron_t *client, aeron_on_close_client_pair_t *pair)\n{\n    if (NULL == client || NULL == pair)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must not be null, client: %s, pair: %s\", AERON_NULL_STR(client), AERON_NULL_STR(pair));\n        return -1;\n    }\n\n    if (aeron_client_conductor_is_closed(&client->conductor))\n    {\n        AERON_SET_ERR(EPERM, \"%s\", \"client conductor is closed\");\n        return -1;\n    }\n\n    aeron_client_handler_cmd_t cmd;\n    cmd.type = AERON_CLIENT_HANDLER_ADD_CLOSE_HANDLER;\n    cmd.handler.on_close_handler = pair->handler;\n    cmd.clientd = pair->clientd;\n\n    return aeron_client_conductor_async_handler(&client->conductor, &cmd);\n}\n\nint aeron_remove_close_handler(aeron_t *client, aeron_on_close_client_pair_t *pair)\n{\n    if (NULL == client || NULL == pair)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must not be null, client: %s, pair: %s\", AERON_NULL_STR(client), AERON_NULL_STR(pair));\n        return -1;\n    }\n\n    if (aeron_client_conductor_is_closed(&client->conductor))\n    {\n        AERON_SET_ERR(EPERM, \"%s\", \"client conductor is closed\");\n        return -1;\n    }\n\n    aeron_client_handler_cmd_t cmd;\n    cmd.type = AERON_CLIENT_HANDLER_REMOVE_CLOSE_HANDLER;\n    cmd.handler.on_close_handler = pair->handler;\n    cmd.clientd = pair->clientd;\n\n    return aeron_client_conductor_async_handler(&client->conductor, &cmd);\n}\n\nvoid aeron_async_cmd_free(aeron_client_registering_resource_t *async)\n{\n    if (NULL != async)\n    {\n        aeron_free(async->error_message);\n        aeron_free(async->uri);\n\n        if (AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER == async->type)\n        {\n            aeron_free((void *)async->counter.key_buffer);\n            aeron_free((void *)async->counter.label_buffer);\n        }\n\n        aeron_free(async);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_client.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CLIENT_H\n#define AERON_CLIENT_H\n\n#include \"aeronc.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_context.h\"\n#include \"aeron_client_conductor.h\"\n\ntypedef struct aeron_stct\n{\n    aeron_client_conductor_t conductor;\n    aeron_agent_runner_t runner;\n    aeron_context_t *context;\n}\naeron_t;\n\nint aeron_client_connect_to_driver(aeron_mapped_file_t *cnc_mmap, aeron_context_t *context);\n\n#endif //AERON_CLIENT_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_client_conductor.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <string.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#include \"aeron_client_conductor.h\"\n#include \"aeron_publication.h\"\n#include \"aeron_exclusive_publication.h\"\n#include \"aeron_subscription.h\"\n#include \"aeron_log_buffer.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"aeron_cnc_file_descriptor.h\"\n#include \"aeron_image.h\"\n#include \"aeron_counter.h\"\n#include \"aeron_counters.h\"\n\n#define AERON_ON_HANDLER_ADD(p, c, m, t) \\\n{ \\\nint ensure_capacity_result = 0; \\\nAERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, (c)->m, t) \\\nif (ensure_capacity_result < 0) \\\n{ \\\nchar err_buffer[AERON_ERROR_MAX_TOTAL_LENGTH]; \\\nsnprintf(err_buffer, sizeof(err_buffer) - 1, \"add \" #t \": %s\", aeron_errmsg()); \\\nconductor->error_handler(conductor->error_handler_clientd, aeron_errcode(), err_buffer); \\\n(p) = NULL; \\\n} \\\n(p) = &(c)->m.array[(c)->m.length++]; \\\n}\n\n_Static_assert(\n    sizeof(aeron_publication_error_t) == sizeof(aeron_publication_error_values_t),\n    \"sizeof(aeron_publication_error_t) must be equal to sizeof(aeron_publication_error_values_t)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, registration_id) == offsetof(aeron_publication_error_values_t, registration_id),\n    \"offsetof(aeron_publication_error_t, registration_id) must match offsetof(aeron_publication_error_values_t, registration_id)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, destination_registration_id) == offsetof(aeron_publication_error_values_t, destination_registration_id),\n    \"offsetof(aeron_publication_error_t, destination_registration_id) must match offsetof(aeron_publication_error_values_t, destination_registration_id)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, session_id) == offsetof(aeron_publication_error_values_t, session_id),\n    \"offsetof(aeron_publication_error_t, session_id) must match offsetof(aeron_publication_error_values_t, session_id)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, stream_id) == offsetof(aeron_publication_error_values_t, stream_id),\n    \"offsetof(aeron_publication_error_t, stream_id) must match offsetof(aeron_publication_error_values_t, stream_id)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, receiver_id) == offsetof(aeron_publication_error_values_t, receiver_id),\n    \"offsetof(aeron_publication_error_t, receiver_id) must match offsetof(aeron_publication_error_values_t, receiver_id)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, group_tag) == offsetof(aeron_publication_error_values_t, group_tag),\n    \"offsetof(aeron_publication_error_t, group_tag) must match offsetof(aeron_publication_error_values_t, group_tag)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, address_type) == offsetof(aeron_publication_error_values_t, address_type),\n    \"offsetof(aeron_publication_error_t, address_type) must match offsetof(aeron_publication_error_values_t, address_type)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, source_port) == offsetof(aeron_publication_error_values_t, source_port),\n    \"offsetof(aeron_publication_error_t, address_port) must match offsetof(aeron_publication_error_values_t, address_port)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, source_address) == offsetof(aeron_publication_error_values_t, source_address),\n    \"offsetof(aeron_publication_error_t, source_address) must match offsetof(aeron_publication_error_values_t, source_address)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, error_code) == offsetof(aeron_publication_error_values_t, error_code),\n    \"offsetof(aeron_publication_error_t, error_code) must match offsetof(aeron_publication_error_values_t, error_code)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, error_message_length) == offsetof(aeron_publication_error_values_t, error_message_length),\n    \"offsetof(aeron_publication_error_t, error_message_length) must match offsetof(aeron_publication_error_values_t, error_message_length)\");\n_Static_assert(\n    offsetof(aeron_publication_error_t, error_message) == offsetof(aeron_publication_error_values_t, error_message),\n    \"offsetof(aeron_publication_error_t, error_message) must match offsetof(aeron_publication_error_values_t, error_message)\");\n\nstruct aeron_client_conductor_clientd_stct\n{\n    aeron_client_conductor_t *conductor;\n    void *clientd;\n};\ntypedef struct aeron_client_conductor_clientd_stct aeron_client_conductor_clientd_t;\n\nstatic int aeron_client_conductor_command_offer(aeron_mpsc_concurrent_array_queue_t *command_queue, void *cmd)\n{\n    int fail_count = 0;\n\n    while (AERON_OFFER_SUCCESS != aeron_mpsc_concurrent_array_queue_offer(command_queue, cmd))\n    {\n        if (++fail_count > AERON_CLIENT_COMMAND_QUEUE_FAIL_THRESHOLD)\n        {\n            AERON_SET_ERR(AERON_CLIENT_ERROR_BUFFER_FULL, \"%s\", \"could not offer to conductor command queue\");\n            return -1;\n        }\n\n        sched_yield();\n    }\n\n    return 0;\n}\n\nstatic void aeron_client_conductor_remove_registering_resource(\n    aeron_client_conductor_t *conductor,\n    aeron_client_registering_resource_t *resource,\n    size_t index,\n    size_t last_index,\n    aeron_client_registration_status_t registration_status)\n{\n    aeron_array_fast_unordered_remove(\n        (uint8_t *)conductor->registering_resources.array,\n        sizeof(aeron_client_registering_resource_entry_t),\n        index,\n        last_index);\n    conductor->registering_resources.length--;\n\n    AERON_SET_RELEASE(resource->registration_status, registration_status);\n}\n\nstatic void aeron_client_conductor_on_resource_registration_error(\n    aeron_client_conductor_t *conductor,\n    aeron_client_registering_resource_t *resource,\n    size_t index,\n    size_t last_index,\n    int32_t error_code,\n    const size_t error_length,\n    const char *error)\n{\n    resource->error_code = error_code;\n    resource->error_message_length = 0;\n    resource->error_message = NULL;\n\n    if (0 == aeron_alloc((void**)&resource->error_message, error_length + 1))\n    {\n        resource->error_message_length = (int32_t)error_length;\n        memcpy(resource->error_message, error, error_length);\n        resource->error_message[error_length] = '\\0';\n    }\n\n    aeron_client_conductor_remove_registering_resource(\n        conductor, resource, index, last_index, AERON_CLIENT_REGISTRATION_STATUS_ERRORED);\n}\n\nstatic int32_t aeron_client_conductor_try_claim_driver_command(\n    aeron_client_conductor_t *conductor,\n    int32_t msg_type_id,\n    size_t command_length)\n{\n    int32_t offset;\n    int rb_offer_fail_count = 0;\n    while ((offset = aeron_mpsc_rb_try_claim( &conductor->to_driver_buffer, msg_type_id, command_length)) < 0)\n    {\n        if (++rb_offer_fail_count > AERON_CLIENT_COMMAND_RB_FAIL_THRESHOLD)\n        {\n            char err_buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n            snprintf(\n                err_buffer,\n                sizeof(err_buffer) - 1,\n                \"failed to send command to the driver: msg_type_id=%\" PRIi32 \" command_length=%\" PRIu64 \" (%s:%d)\",\n                msg_type_id,\n                (uint64_t)command_length,\n                __FILE__,\n                __LINE__);\n            conductor->error_handler(conductor->error_handler_clientd, AERON_CLIENT_ERROR_BUFFER_FULL, err_buffer);\n            return -1;\n        }\n\n        sched_yield();\n    }\n\n    return offset;\n}\n\nstatic int32_t aeron_client_conductor_add_registering_resource(\n    aeron_client_conductor_t *conductor,\n    aeron_client_registering_resource_t *resource,\n    int32_t msg_type_id,\n    size_t command_length)\n{\n    int result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(\n        result, conductor->registering_resources, aeron_client_registering_resource_entry_t)\n    if (result < 0)\n    {\n        char err_buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n\n        snprintf(err_buffer, sizeof(err_buffer) - 1, \"failed to grow registering_resources array: %s\", aeron_errmsg());\n        conductor->error_handler(conductor->error_handler_clientd, aeron_errcode(), err_buffer);\n        return -1;\n    }\n\n    size_t index = conductor->registering_resources.length;\n    conductor->registering_resources.array[index].resource = resource;\n    conductor->registering_resources.length++;\n    resource->registration_deadline_ns = (int64_t)(conductor->nano_clock() + conductor->driver_timeout_ns);\n\n    int32_t offset = aeron_client_conductor_try_claim_driver_command(conductor, msg_type_id, command_length);\n    if (offset < 0)\n    {\n        const char* msg = \"failed to send command to the driver\";\n        aeron_client_conductor_on_resource_registration_error(\n            conductor, resource, index, index, AERON_CLIENT_ERROR_BUFFER_FULL, strlen(msg), msg);\n    }\n\n    return offset;\n}\n\nstatic int aeron_client_conductor_release_log_buffer(aeron_client_conductor_t *conductor, aeron_log_buffer_t *log_buffer)\n{\n    if (--log_buffer->refcnt <= 0)\n    {\n        int64_t correlationId = log_buffer->correlation_id;\n        aeron_int64_to_ptr_hash_map_remove(&conductor->log_buffer_by_id_map, correlationId);\n\n        aeron_log_buffer_delete(log_buffer);\n    }\n\n    return 0;\n}\n\nstatic void aeron_client_conductor_on_command(void *clientd, void *item)\n{\n    aeron_client_command_base_t *cmd = (aeron_client_command_base_t *)item;\n    cmd->func(clientd, cmd);\n}\n\nstatic void aeron_client_conductor_forward_error(void *clientd, int64_t key, void *value)\n{\n    aeron_client_conductor_clientd_t *conductor_clientd = (aeron_client_conductor_clientd_t *)clientd;\n    aeron_client_conductor_t *conductor = conductor_clientd->conductor;\n    aeron_publication_error_t *response = (aeron_publication_error_t *)conductor_clientd->clientd;\n    aeron_client_command_base_t *resource = (aeron_client_command_base_t *)value;\n\n    const bool is_publication = AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION == resource->type &&\n        ((aeron_publication_t *)resource)->original_registration_id == response->registration_id;\n    const bool is_exclusive_publication = AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION == resource->type &&\n        ((aeron_exclusive_publication_t *)resource)->original_registration_id == response->registration_id;\n\n    if (is_publication || is_exclusive_publication)\n    {\n        conductor->error_frame_handler(\n            conductor->error_frame_handler_clientd, (aeron_publication_error_values_t *)response);\n    }\n}\n\nstatic int aeron_client_conductor_on_error_frame(aeron_client_conductor_t *conductor, aeron_publication_error_t *response)\n{\n    aeron_client_conductor_clientd_t clientd = {\n        .conductor = conductor,\n        .clientd = response\n    };\n\n    aeron_int64_to_ptr_hash_map_for_each(\n        &conductor->resource_by_id_map, aeron_client_conductor_forward_error, (void *)&clientd);\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_error(aeron_client_conductor_t *conductor, aeron_error_response_t *response)\n{\n    for (size_t i = 0, size = conductor->registering_resources.length, last_index = size - 1; i < size; i++)\n    {\n        aeron_client_registering_resource_t *resource = conductor->registering_resources.array[i].resource;\n\n        if (response->offending_command_correlation_id == resource->registration_id)\n        {\n            aeron_client_conductor_on_resource_registration_error(\n                conductor,\n                resource,\n                i,\n                last_index,\n                response->error_code,\n                (size_t)response->error_message_length,\n                (const char *)response + sizeof(aeron_error_response_t));\n            break;\n        }\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_get_or_create_log_buffer(\n    aeron_client_conductor_t *conductor,\n    aeron_log_buffer_t **log_buffer,\n    const char *log_file,\n    int64_t original_registration_id,\n    bool pre_touch)\n{\n    if (NULL == (*log_buffer = aeron_int64_to_ptr_hash_map_get(\n        &conductor->log_buffer_by_id_map, original_registration_id)))\n    {\n        if (aeron_log_buffer_create(log_buffer, log_file, original_registration_id, pre_touch) < 0)\n        {\n            return -1;\n        }\n\n        if (aeron_int64_to_ptr_hash_map_put(\n            &conductor->log_buffer_by_id_map, original_registration_id, *log_buffer) < 0)\n        {\n            AERON_SET_ERR(errno, \"%s\", \"Unable to insert into log_buffer_by_id_map\");\n            aeron_log_buffer_delete(*log_buffer);\n            return -1;\n        }\n    }\n\n    (*log_buffer)->refcnt++;\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_offer_remove_publication_command(\n    aeron_client_conductor_t *conductor, int64_t registration_id, bool revoke)\n{\n    int32_t offset = aeron_client_conductor_try_claim_driver_command(\n       conductor, AERON_COMMAND_REMOVE_PUBLICATION, sizeof(aeron_remove_publication_command_t));\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    aeron_remove_publication_command_t *command = (aeron_remove_publication_command_t *)(conductor->to_driver_buffer.buffer + offset);\n\n    command->correlated.correlation_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    command->correlated.client_id = conductor->client_id;\n    command->registration_id = registration_id;\n    command->flags = revoke ? AERON_COMMAND_REMOVE_PUBLICATION_FLAG_REVOKE : 0;\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n\n    return 0;\n}\n\nstatic void aeron_client_conductor_on_publication_ready_error(\n    aeron_client_conductor_t *conductor,\n    aeron_client_registering_resource_t *resource,\n    size_t index,\n    size_t last_index)\n{\n    const char* err = \"failed to add publication\";\n    aeron_client_conductor_on_resource_registration_error(\n        conductor, resource, index, last_index, EINVAL,strlen(err), err);\n    aeron_client_conductor_offer_remove_publication_command(conductor, resource->registration_id, true);\n}\n\nstatic int aeron_client_conductor_on_publication_ready(\n    aeron_client_conductor_t *conductor, aeron_publication_buffers_ready_t *response)\n{\n    for (size_t i = 0, size = conductor->registering_resources.length, last_index = size - 1; i < size; i++)\n    {\n        aeron_client_registering_resource_t *resource = conductor->registering_resources.array[i].resource;\n\n        if (response->correlation_id == resource->registration_id)\n        {\n            char log_file[AERON_MAX_PATH];\n            const char *channel = resource->uri;\n            bool is_exclusive = (AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION == resource->type) ? true : false;\n\n            memcpy(\n                log_file,\n                (const char *)response + sizeof(aeron_publication_buffers_ready_t),\n                (size_t)response->log_file_length);\n            log_file[response->log_file_length] = '\\0';\n\n            aeron_log_buffer_t *log_buffer;\n\n            if (aeron_client_conductor_get_or_create_log_buffer(\n                conductor, &log_buffer, log_file, response->registration_id, conductor->pre_touch) < 0)\n            {\n                aeron_client_conductor_on_publication_ready_error(conductor, resource, i, last_index);\n                return -1;\n            }\n\n            if (is_exclusive)\n            {\n                aeron_exclusive_publication_t *publication = NULL;\n                int64_t *position_limit_addr = aeron_counters_reader_addr(\n                    &conductor->counters_reader, response->position_limit_counter_id);\n                int64_t *channel_status_indicator_addr = 0 <= response->channel_status_indicator_id ?\n                    aeron_counters_reader_addr(&conductor->counters_reader, response->channel_status_indicator_id) :\n                    NULL;\n\n                if (aeron_exclusive_publication_create(\n                    &publication,\n                    conductor,\n                    resource->uri,\n                    resource->stream_id,\n                    response->session_id,\n                    response->position_limit_counter_id,\n                    position_limit_addr,\n                    response->channel_status_indicator_id,\n                    channel_status_indicator_addr,\n                    log_buffer,\n                    response->registration_id,\n                    response->correlation_id) < 0)\n                {\n                    aeron_client_conductor_on_publication_ready_error(conductor, resource, i, last_index);\n                    return -1;\n                }\n\n                resource->uri = NULL;\n                resource->resource.exclusive_publication = publication;\n\n                if (aeron_int64_to_ptr_hash_map_put(\n                    &conductor->resource_by_id_map, resource->registration_id, publication) < 0)\n                {\n                    aeron_client_conductor_on_publication_ready_error(conductor, resource, i, last_index);\n                    return -1;\n                }\n            }\n            else\n            {\n                aeron_publication_t *publication = NULL;\n                int64_t *position_limit_addr = aeron_counters_reader_addr(\n                    &conductor->counters_reader, response->position_limit_counter_id);\n                int64_t *channel_status_indicator_addr = aeron_counters_reader_addr(\n                    &conductor->counters_reader, response->channel_status_indicator_id);\n\n                if (aeron_publication_create(\n                    &publication,\n                    conductor,\n                    resource->uri,\n                    resource->stream_id,\n                    response->session_id,\n                    response->position_limit_counter_id,\n                    position_limit_addr,\n                    response->channel_status_indicator_id,\n                    channel_status_indicator_addr,\n                    log_buffer,\n                    response->registration_id,\n                    response->correlation_id) < 0)\n                {\n                    aeron_client_conductor_on_publication_ready_error(conductor, resource, i, last_index);\n                    return -1;\n                }\n\n                resource->uri = NULL;\n                resource->resource.publication = publication;\n\n                if (aeron_int64_to_ptr_hash_map_put(\n                    &conductor->resource_by_id_map, resource->registration_id, publication) < 0)\n                {\n                    aeron_client_conductor_on_publication_ready_error(conductor, resource, i, last_index);\n                    return -1;\n                }\n            }\n\n            aeron_client_conductor_remove_registering_resource(\n                conductor, resource, i, last_index, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n\n            if (is_exclusive)\n            {\n                if (NULL != conductor->on_new_exclusive_publication)\n                {\n                    conductor->on_new_exclusive_publication(\n                        conductor->on_new_exclusive_publication_clientd,\n                        resource,\n                        channel,\n                        response->stream_id,\n                        response->session_id,\n                        response->correlation_id);\n                }\n            }\n            else if (NULL != conductor->on_new_publication)\n            {\n                conductor->on_new_publication(\n                    conductor->on_new_publication_clientd,\n                    resource,\n                    channel,\n                    response->stream_id,\n                    response->session_id,\n                    response->correlation_id);\n            }\n            break;\n        }\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_offer_remove_subscription_command(\n    aeron_client_conductor_t *conductor, int64_t registration_id)\n{\n    int32_t offset = aeron_client_conductor_try_claim_driver_command(\n        conductor, AERON_COMMAND_REMOVE_SUBSCRIPTION, sizeof(aeron_remove_subscription_command_t));\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    aeron_remove_subscription_command_t *command = (aeron_remove_subscription_command_t *)(conductor->to_driver_buffer.buffer + offset);\n\n    command->correlated.correlation_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    command->correlated.client_id = conductor->client_id;\n    command->registration_id = registration_id;\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n\n    return 0;\n}\n\nstatic void aeron_client_conductor_on_subscription_ready_error(\n    aeron_client_conductor_t *conductor,\n    aeron_client_registering_resource_t *resource,\n    size_t index,\n    size_t last_index)\n{\n    const char* err = \"failed to add subscription\";\n    aeron_client_conductor_on_resource_registration_error(\n        conductor, resource, index, last_index, EINVAL,strlen(err), err);\n    aeron_client_conductor_offer_remove_subscription_command(conductor, resource->registration_id);\n}\n\nstatic int aeron_client_conductor_on_subscription_ready(\n    aeron_client_conductor_t *conductor, aeron_subscription_ready_t *response)\n{\n    for (size_t i = 0, size = conductor->registering_resources.length, last_index = size - 1; i < size; i++)\n    {\n        aeron_client_registering_resource_t *resource = conductor->registering_resources.array[i].resource;\n\n        if (response->correlation_id == resource->registration_id)\n        {\n            const char *channel = resource->uri;\n            aeron_subscription_t *subscription;\n            int64_t *channel_status_indicator_addr = 0 <= response->channel_status_indicator_id ?\n                aeron_counters_reader_addr(&conductor->counters_reader, response->channel_status_indicator_id) :\n                NULL;\n            int32_t stream_id = resource->stream_id;\n\n            if (aeron_subscription_create(\n                &subscription,\n                conductor,\n                resource->uri,\n                stream_id,\n                resource->registration_id,\n                response->channel_status_indicator_id,\n                channel_status_indicator_addr,\n                resource->on_available_image,\n                resource->on_available_image_clientd,\n                resource->on_unavailable_image,\n                resource->on_unavailable_image_clientd) < 0)\n            {\n                aeron_client_conductor_on_subscription_ready_error(conductor, resource, i, last_index);\n                return -1;\n            }\n\n            if (aeron_int64_to_ptr_hash_map_put(\n                &conductor->resource_by_id_map, resource->registration_id, subscription) < 0)\n            {\n                aeron_client_conductor_on_subscription_ready_error(conductor, resource, i, last_index);\n                return -1;\n            }\n\n            resource->uri = NULL;\n            resource->resource.subscription = subscription;\n\n            aeron_client_conductor_remove_registering_resource(\n                conductor, resource, i, last_index, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n\n            if (NULL != conductor->on_new_subscription)\n            {\n                conductor->on_new_subscription(\n                    conductor->on_new_subscription_clientd,\n                    resource,\n                    channel,\n                    stream_id,\n                    response->correlation_id);\n            }\n            break;\n        }\n    }\n\n    return 0;\n}\n\nstatic aeron_subscription_t *aeron_client_conductor_find_subscription_by_id(\n    aeron_client_conductor_t *conductor, int64_t registration_id)\n{\n    aeron_subscription_t *subscription = aeron_int64_to_ptr_hash_map_get(\n        &conductor->resource_by_id_map, registration_id);\n\n    if (NULL != subscription && AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION == subscription->command_base.type)\n    {\n        return subscription;\n    }\n\n    return NULL;\n}\n\nstatic int aeron_client_conductor_on_available_image(\n    aeron_client_conductor_t *conductor,\n    aeron_image_buffers_ready_t *response,\n    int32_t log_file_length,\n    const char *log_file,\n    int32_t source_identity_length,\n    const char *source_identity)\n{\n    aeron_subscription_t *subscription = aeron_client_conductor_find_subscription_by_id(\n        conductor, response->subscriber_registration_id);\n\n    if (NULL != subscription)\n    {\n        char log_file_str[AERON_MAX_PATH], source_identity_str[AERON_MAX_PATH];\n\n        memcpy(log_file_str, log_file, (size_t)log_file_length);\n        log_file_str[log_file_length] = '\\0';\n        memcpy(source_identity_str, source_identity, (size_t)source_identity_length);\n        source_identity_str[source_identity_length] = '\\0';\n\n        aeron_log_buffer_t *log_buffer;\n\n        if (aeron_client_conductor_get_or_create_log_buffer(\n            conductor, &log_buffer, log_file_str, response->correlation_id, conductor->pre_touch) < 0)\n        {\n            return -1;\n        }\n\n        aeron_image_t *image;\n        int64_t *subscriber_position = aeron_counters_reader_addr(\n            &conductor->counters_reader, response->subscriber_position_id);\n\n        if (aeron_image_create(\n            &image,\n            subscription,\n            conductor,\n            log_buffer,\n            response->subscriber_position_id,\n            subscriber_position,\n            response->correlation_id,\n            response->session_id,\n            source_identity_str,\n            (size_t)source_identity_length) < 0)\n        {\n            return -1;\n        }\n\n        if (aeron_array_to_ptr_hash_map_put(\n            &conductor->image_by_key_map,\n            (const uint8_t *)&image->key,\n            sizeof(aeron_image_key_t),\n            image) < 0)\n        {\n            return -1;\n        }\n\n        if (aeron_client_conductor_subscription_add_image(subscription, image) < 0)\n        {\n            return -1;\n        }\n\n        aeron_client_conductor_subscription_prune_image_lists(subscription);\n\n        if (NULL != subscription->on_available_image)\n        {\n            subscription->on_available_image(subscription->on_available_image_clientd, subscription, image);\n        }\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_linger_image(aeron_client_conductor_t *conductor, aeron_image_t *image)\n{\n    int ensure_capacity_result = 0;\n\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, conductor->lingering_resources, aeron_client_managed_resource_t)\n    if (ensure_capacity_result < 0)\n    {\n        char err_buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n\n        snprintf(err_buffer, sizeof(err_buffer) - 1, \"lingering image: %s\", aeron_errmsg());\n        conductor->error_handler(conductor->error_handler_clientd, aeron_errcode(), err_buffer);\n        return -1;\n    }\n\n    aeron_client_managed_resource_t *resource =\n        &conductor->lingering_resources.array[conductor->lingering_resources.length++];\n    resource->type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_IMAGE;\n    resource->time_of_last_state_change_ns = conductor->nano_clock();\n    resource->registration_id = image->key.correlation_id;\n    resource->resource.image = image;\n    image->is_lingering = true;\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_unavailable_image(aeron_client_conductor_t *conductor, aeron_image_message_t *response)\n{\n    aeron_subscription_t *subscription = aeron_client_conductor_find_subscription_by_id(\n        conductor, response->subscription_registration_id);\n\n    if (NULL != subscription)\n    {\n        aeron_image_key_t key =\n            {\n                .correlation_id = response->correlation_id,\n                .subscription_registration_id = response->subscription_registration_id,\n            };\n\n        aeron_image_t *image = aeron_array_to_ptr_hash_map_remove(\n            &conductor->image_by_key_map,\n            (const uint8_t *)&key,\n            sizeof(aeron_image_key_t));\n\n        if (NULL != image)\n        {\n            if (aeron_client_conductor_subscription_remove_image(subscription, image) < 0)\n            {\n                return -1;\n            }\n\n            aeron_client_conductor_subscription_prune_image_lists(subscription);\n\n            if (NULL != subscription->on_unavailable_image)\n            {\n                subscription->on_unavailable_image(subscription->on_unavailable_image_clientd, subscription, image);\n            }\n\n            if (aeron_client_conductor_linger_image(conductor, image) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_offer_remove_counter_command(\n    aeron_client_conductor_t *conductor, int64_t registration_id)\n{\n    int32_t offset = aeron_client_conductor_try_claim_driver_command(\n        conductor, AERON_COMMAND_REMOVE_COUNTER, sizeof(aeron_remove_counter_command_t));\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    aeron_remove_counter_command_t *command = (aeron_remove_counter_command_t *)(conductor->to_driver_buffer.buffer + offset);\n\n    command->correlated.correlation_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    command->correlated.client_id = conductor->client_id;\n    command->registration_id = registration_id;\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n    return 0;\n}\n\nstatic void aeron_client_conductor_on_counter_ready_error(\n    aeron_client_conductor_t *conductor,\n    aeron_client_registering_resource_t *resource,\n    size_t index,\n    size_t last_index)\n{\n    const char* err = \"failed to add counter\";\n    aeron_client_conductor_on_resource_registration_error(\n        conductor, resource, index, last_index, EINVAL,strlen(err), err);\n    aeron_client_conductor_offer_remove_counter_command(conductor, resource->registration_id);\n}\n\nstatic int aeron_client_conductor_on_counter_ready(aeron_client_conductor_t *conductor, aeron_counter_update_t *response)\n{\n    for (size_t i = 0, size = conductor->registering_resources.length, last_index = size - 1; i < size; i++)\n    {\n        aeron_client_registering_resource_t *resource = conductor->registering_resources.array[i].resource;\n\n        if (response->correlation_id == resource->registration_id)\n        {\n            aeron_counter_t *counter;\n            int64_t *counter_addr = aeron_counters_reader_addr(&conductor->counters_reader, response->counter_id);\n\n            if (aeron_counter_create(\n                &counter,\n                conductor,\n                response->correlation_id,\n                response->counter_id,\n                counter_addr) < 0)\n            {\n                aeron_client_conductor_on_counter_ready_error(conductor, resource, i, last_index);\n                return -1;\n            }\n\n            if (aeron_int64_to_ptr_hash_map_put(\n                &conductor->resource_by_id_map, resource->registration_id, counter) < 0)\n            {\n                aeron_client_conductor_on_counter_ready_error(conductor, resource, i, last_index);\n                return -1;\n            }\n\n            resource->resource.counter = counter;\n\n            aeron_client_conductor_remove_registering_resource(\n                conductor, resource, i, last_index, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n            break;\n        }\n    }\n\n    for (size_t i = 0, length = conductor->available_counter_handlers.length; i < length; i++)\n    {\n        aeron_on_available_counter_pair_t *pair = &conductor->available_counter_handlers.array[i];\n        pair->handler(pair->clientd, &conductor->counters_reader, response->correlation_id, response->counter_id);\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_unavailable_counter(aeron_client_conductor_t *conductor, aeron_counter_update_t *response)\n{\n    for (size_t i = 0, length = conductor->unavailable_counter_handlers.length; i < length; i++)\n    {\n        aeron_on_unavailable_counter_pair_t *pair = &conductor->unavailable_counter_handlers.array[i];\n        pair->handler(pair->clientd, &conductor->counters_reader, response->correlation_id, response->counter_id);\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_static_counter(aeron_client_conductor_t *conductor, aeron_static_counter_response_t *response)\n{\n    for (size_t i = 0, size = conductor->registering_resources.length, last_index = size - 1; i < size; i++)\n    {\n        aeron_client_registering_resource_t *resource = conductor->registering_resources.array[i].resource;\n\n        if (response->correlation_id == resource->registration_id)\n        {\n            aeron_counter_t *counter;\n            int64_t *counter_addr = aeron_counters_reader_addr(&conductor->counters_reader, response->counter_id);\n\n            if (aeron_counter_create(\n                &counter,\n                conductor,\n                resource->counter.registration_id,\n                response->counter_id,\n                counter_addr) < 0)\n            {\n                const char* err = \"failed to add static counter\";\n                aeron_client_conductor_on_resource_registration_error(\n                    conductor, resource, i, last_index, EINVAL,strlen(err), err);\n                return -1;\n            }\n\n            resource->resource.counter = counter;\n\n            aeron_client_conductor_remove_registering_resource(\n                conductor, resource, i, last_index, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n            break;\n        }\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_next_available_session_id(\n    aeron_client_conductor_t *conductor, aeron_next_available_session_id_response_t *response)\n{\n    for (size_t i = 0, size = conductor->registering_resources.length, last_index = size - 1; i < size; i++)\n    {\n        aeron_client_registering_resource_t *resource = conductor->registering_resources.array[i].resource;\n\n        if (response->correlation_id == resource->registration_id)\n        {\n            resource->resource.next_session_id = response->next_session_id;\n\n            aeron_client_conductor_remove_registering_resource(\n                conductor, resource, i, last_index, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n            break;\n        }\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_operation_success(\n    aeron_client_conductor_t *conductor, aeron_operation_succeeded_t *response)\n{\n    for (size_t i = 0, size = conductor->registering_resources.length, last_index = size - 1; i < size; i++)\n    {\n        aeron_client_registering_resource_t *resource = conductor->registering_resources.array[i].resource;\n\n        if (response->correlation_id == resource->registration_id)\n        {\n            aeron_client_conductor_remove_registering_resource(\n                conductor, resource, i, last_index, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n            break;\n        }\n    }\n\n    return 0;\n}\n\nstatic void aeron_client_conductor_on_driver_response(int32_t type_id, uint8_t *buffer, size_t length, void *clientd)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    int result = 0;\n    char error_message[AERON_ERROR_MAX_TOTAL_LENGTH] = \"\\0\";\n\n    switch (type_id)\n    {\n        case AERON_RESPONSE_ON_ERROR:\n        {\n            aeron_error_response_t *response = (aeron_error_response_t *)buffer;\n\n            if (length < sizeof(aeron_error_response_t) ||\n                length < (sizeof(aeron_error_response_t) + response->error_message_length))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_error(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_PUBLICATION_READY:\n        case AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY:\n        {\n            aeron_publication_buffers_ready_t *response = (aeron_publication_buffers_ready_t *)buffer;\n\n            if (length < sizeof(aeron_publication_buffers_ready_t) ||\n                length < (sizeof(aeron_publication_buffers_ready_t) + response->log_file_length))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_publication_ready(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_SUBSCRIPTION_READY:\n        {\n            aeron_subscription_ready_t *response = (aeron_subscription_ready_t *)buffer;\n\n            if (length < sizeof(aeron_subscription_ready_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_subscription_ready(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_AVAILABLE_IMAGE:\n        {\n            aeron_image_buffers_ready_t *response = (aeron_image_buffers_ready_t *)buffer;\n            uint8_t *ptr = buffer + sizeof(aeron_image_buffers_ready_t);\n            int32_t log_file_length, source_identity_length, aligned_log_file_length;\n            const char *log_file = (const char *)(ptr + sizeof(int32_t));\n            const char *source_identity;\n\n            memcpy(&log_file_length, ptr, sizeof(int32_t));\n\n            if (length < (sizeof(aeron_image_buffers_ready_t) + 2 * sizeof(int32_t)) ||\n                length < (sizeof(aeron_image_buffers_ready_t) + 2 * sizeof(int32_t) + log_file_length))\n            {\n                goto malformed_command;\n            }\n\n            aligned_log_file_length = AERON_ALIGN(log_file_length, sizeof(int32_t));\n            ptr += sizeof(int32_t) + aligned_log_file_length;\n            memcpy(&source_identity_length, ptr, sizeof(int32_t));\n\n            if (length <\n                (sizeof(aeron_image_buffers_ready_t) +\n                2 * sizeof(int32_t) +\n                aligned_log_file_length +\n                source_identity_length))\n            {\n                goto malformed_command;\n            }\n\n            source_identity = (const char *)(ptr + sizeof(int32_t));\n\n            result = aeron_client_conductor_on_available_image(\n                conductor, response, log_file_length, log_file, source_identity_length, source_identity);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_UNAVAILABLE_IMAGE:\n        {\n            aeron_image_message_t *response = (aeron_image_message_t *)buffer;\n\n            if (length < sizeof(aeron_image_message_t) ||\n                length < (sizeof(aeron_image_message_t) + response->channel_length))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_unavailable_image(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_COUNTER_READY:\n        {\n            aeron_counter_update_t *response = (aeron_counter_update_t *)buffer;\n\n            if (length < sizeof(aeron_counter_update_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_counter_ready(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_UNAVAILABLE_COUNTER:\n        {\n            aeron_counter_update_t *response = (aeron_counter_update_t *)buffer;\n\n            if (length < sizeof(aeron_counter_update_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_unavailable_counter(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_CLIENT_TIMEOUT:\n        {\n            aeron_client_timeout_t *response = (aeron_client_timeout_t *)buffer;\n\n            if (length < sizeof(aeron_client_timeout_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_client_timeout(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_OPERATION_SUCCESS:\n        {\n            aeron_operation_succeeded_t *response = (aeron_operation_succeeded_t *)buffer;\n\n            if (length < sizeof(aeron_operation_succeeded_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_operation_success(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_STATIC_COUNTER:\n        {\n            aeron_static_counter_response_t *response = (aeron_static_counter_response_t *)buffer;\n\n            if (length < sizeof(aeron_static_counter_response_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_static_counter(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_PUBLICATION_ERROR:\n        {\n            aeron_publication_error_t *response = (aeron_publication_error_t *)buffer;\n\n            if (length < sizeof(aeron_publication_error_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_error_frame(conductor, response);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_NEXT_AVAILABLE_SESSION_ID:\n        {\n            aeron_next_available_session_id_response_t *response = (aeron_next_available_session_id_response_t *)buffer;\n\n            if (length < sizeof(aeron_next_available_session_id_response_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_client_conductor_on_next_available_session_id(conductor, response);\n            break;\n        }\n\n        default:\n        {\n            snprintf(error_message, sizeof(error_message) - 1, \"response=0x%x unknown\", type_id);\n            conductor->error_handler(\n                conductor->error_handler_clientd, AERON_ERROR_CODE_UNKNOWN_COMMAND_TYPE_ID, error_message);\n            break;\n        }\n    }\n\n    if (result < 0)\n    {\n        int os_errno = aeron_errcode();\n        int code = os_errno < 0 ? -os_errno : AERON_ERROR_CODE_GENERIC_ERROR;\n        conductor->error_handler(conductor->error_handler_clientd, code, aeron_errmsg());\n    }\n\n    return;\n\nmalformed_command:\n    snprintf(\n        error_message, sizeof(error_message) - 1, \"command=0x%x too short: length=%\" PRIu64, type_id, (uint64_t)length);\n    conductor->error_handler(conductor->error_handler_clientd, AERON_ERROR_CODE_MALFORMED_COMMAND, error_message);\n}\n\nstatic int aeron_counter_heartbeat_timestamp_find_counter_id_by_registration_id(\n    aeron_counters_reader_t *counters_reader, int32_t type_id, int64_t registration_id)\n{\n    for (size_t i = 0, size = (size_t)counters_reader->max_counter_id; i < size; i++)\n    {\n        aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)(\n            counters_reader->metadata + AERON_COUNTER_METADATA_OFFSET(i));\n        int32_t record_state;\n\n        AERON_GET_ACQUIRE(record_state, metadata->state);\n\n        if (AERON_COUNTER_RECORD_ALLOCATED == record_state && type_id == metadata->type_id)\n        {\n            int64_t counter_registration_id;\n            memcpy(&counter_registration_id, metadata->key, sizeof(int64_t));\n\n            if (registration_id == counter_registration_id)\n            {\n                return (int)i;\n            }\n        }\n    }\n\n    return AERON_NULL_COUNTER_ID;\n}\n\nstatic bool aeron_counter_heartbeat_timestamp_is_active(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int32_t type_id, int64_t registration_id)\n{\n    aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)(\n        counters_reader->metadata + AERON_COUNTER_METADATA_OFFSET(counter_id));\n    int64_t counter_registration_id;\n    int32_t record_state;\n\n    AERON_GET_ACQUIRE(record_state, metadata->state);\n\n    memcpy(&counter_registration_id, metadata->key, sizeof(int64_t));\n\n    return\n        AERON_COUNTER_RECORD_ALLOCATED == record_state &&\n        type_id == metadata->type_id &&\n        registration_id == counter_registration_id;\n}\n\nstatic void aeron_client_conductor_force_close_image(void *clientd, const uint8_t *key, size_t key_len, void *value)\n{\n    aeron_image_close((aeron_image_t *)value);\n}\n\nstatic void aeron_client_conductor_force_close_resource(void *clientd, int64_t key, void *value)\n{\n    aeron_client_command_base_t *base = (aeron_client_command_base_t *)value;\n\n    switch (base->type)\n    {\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION:\n            aeron_publication_force_close((aeron_publication_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION:\n            aeron_exclusive_publication_force_close((aeron_exclusive_publication_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION:\n            aeron_subscription_force_close((aeron_subscription_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER:\n            aeron_counter_force_close((aeron_counter_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_IMAGE:\n            aeron_image_close((aeron_image_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_LOG_BUFFER:\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_DESTINATION:\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_NEXT_AVAILABLE_SESSION_ID:\n            break;\n    }\n}\n\nstatic void aeron_client_conductor_force_close_resources(aeron_client_conductor_t *conductor)\n{\n    /*\n     * loop will be terminating. So, will not process lingering, etc. Let the aeron_close() cleanup\n     * everything when the app is ready, but we want to mark everything as closed so that it won't be used.\n     */\n    AERON_SET_RELEASE(conductor->is_closed, true);\n\n    aeron_array_to_ptr_hash_map_for_each(\n        &conductor->image_by_key_map, aeron_client_conductor_force_close_image, NULL);\n    aeron_int64_to_ptr_hash_map_for_each(\n        &conductor->resource_by_id_map, aeron_client_conductor_force_close_resource, NULL);\n}\n\nstatic int aeron_client_conductor_check_liveness(aeron_client_conductor_t *conductor, int64_t now_ns)\n{\n    if (now_ns > (conductor->time_of_last_keepalive_ns + (int64_t)conductor->keepalive_interval_ns))\n    {\n        const int64_t last_keepalive_ms = aeron_mpsc_rb_consumer_heartbeat_time_value(&conductor->to_driver_buffer);\n        const int64_t now_ms = conductor->epoch_clock();\n\n        if (AERON_NULL_VALUE == last_keepalive_ms)\n        {\n            char buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n\n            conductor->is_terminating = true;\n            aeron_client_conductor_force_close_resources(conductor);\n            snprintf(buffer, sizeof(buffer) - 1, \"MediaDriver has been shutdown\");\n            conductor->error_handler(conductor->error_handler_clientd, AERON_CLIENT_ERROR_DRIVER_TIMEOUT, buffer);\n        }\n        else if (now_ms > (last_keepalive_ms + (int64_t)conductor->driver_timeout_ms))\n        {\n            char buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n\n            conductor->is_terminating = true;\n            aeron_client_conductor_force_close_resources(conductor);\n            snprintf(buffer, sizeof(buffer) - 1,\n                \"MediaDriver keepalive: age=%\" PRId64 \"ms > timeout=%\" PRId64 \"ms\",\n                (int64_t)(now_ms - last_keepalive_ms),\n                (int64_t)conductor->driver_timeout_ms);\n            conductor->error_handler(conductor->error_handler_clientd, AERON_CLIENT_ERROR_DRIVER_TIMEOUT, buffer);\n        }\n\n        if (AERON_NULL_COUNTER_ID == conductor->heartbeat_timestamp.counter_id)\n        {\n            const int32_t id = aeron_counter_heartbeat_timestamp_find_counter_id_by_registration_id(\n                &conductor->counters_reader, AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID, conductor->client_id);\n\n            if (AERON_NULL_COUNTER_ID != id)\n            {\n                aeron_counter_metadata_descriptor_t *counter_metadata = (aeron_counter_metadata_descriptor_t *)(\n                    conductor->counters_reader.metadata + AERON_COUNTER_METADATA_OFFSET(id));\n\n                char *extended_info;\n                size_t available_label_length = AERON_COUNTER_MAX_LABEL_LENGTH - counter_metadata->label_length;\n                if (aeron_alloc((void **)&extended_info, available_label_length) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"\");\n                    return -1;\n                }\n                size_t total_length = snprintf(\n                    extended_info,\n                    available_label_length,\n                    \" name=%s version=%s commit=%s\",\n                    conductor->client_name,\n                    aeron_version_text(),\n                    aeron_version_gitsha());\n                size_t extended_info_length = AERON_MIN(total_length, available_label_length);\n\n                memcpy(counter_metadata->label + counter_metadata->label_length, extended_info, extended_info_length);\n                counter_metadata->label_length += (int32_t)extended_info_length;\n\n                aeron_free(extended_info);\n\n                conductor->heartbeat_timestamp.counter_id = id;\n                conductor->heartbeat_timestamp.addr = aeron_counters_reader_addr(\n                    &conductor->counters_reader, conductor->heartbeat_timestamp.counter_id);\n\n                aeron_counter_set_release(conductor->heartbeat_timestamp.addr, now_ms);\n                conductor->time_of_last_keepalive_ns = now_ns;\n            }\n        }\n        else\n        {\n            const int32_t id = conductor->heartbeat_timestamp.counter_id;\n            if (!aeron_counter_heartbeat_timestamp_is_active(\n                &conductor->counters_reader, id, AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID, conductor->client_id))\n            {\n                char buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n\n                conductor->is_terminating = true;\n                aeron_client_conductor_force_close_resources(conductor);\n                snprintf(buffer, sizeof(buffer) - 1, \"unexpected close of heartbeat timestamp counter: %\" PRId32, id);\n                conductor->error_handler(conductor->error_handler_clientd, ETIMEDOUT, buffer);\n                AERON_SET_ERR(ETIMEDOUT, \"%s\", buffer);\n                return -1;\n            }\n\n            aeron_counter_set_release(conductor->heartbeat_timestamp.addr, now_ms);\n            conductor->time_of_last_keepalive_ns = now_ns;\n        }\n\n        return 1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_check_lingering_resources(aeron_client_conductor_t *conductor, int64_t now_ns)\n{\n    int work_count = 0;\n\n    /*\n     * Currently, only images are lingered and only until refcnt is 0. Even in the case of client timeout,\n     * etc. we let the application call aeron_close to clean up and shutdown the thread, etc.\n     */\n    for (size_t size = conductor->lingering_resources.length, last_index = size - 1, i = size; i > 0; i--)\n    {\n        size_t index = i - 1;\n        aeron_client_managed_resource_t *resource = &conductor->lingering_resources.array[index];\n\n        if (AERON_CLIENT_MANAGED_RESOURCE_TYPE_IMAGE == resource->type)\n        {\n            aeron_image_t *image = resource->resource.image;\n\n            if (INT64_MIN != image->removal_change_number)\n            {\n                if (!aeron_image_is_in_use_by_subscription(\n                    image, aeron_subscription_last_image_list_change_number(image->subscription)))\n                {\n                    aeron_client_conductor_subscription_prune_image_lists(image->subscription);\n                    aeron_image_decr_refcnt(image);\n                    image->subscription = NULL;\n                    image->removal_change_number = INT64_MIN;\n                }\n            }\n\n            if (aeron_image_refcnt_acquire(image) <= 0)\n            {\n                aeron_client_conductor_release_log_buffer(conductor, image->log_buffer);\n                aeron_image_delete(image);\n\n                aeron_array_fast_unordered_remove(\n                    (uint8_t *)conductor->lingering_resources.array,\n                    sizeof(aeron_client_managed_resource_t),\n                    index,\n                    last_index);\n                conductor->lingering_resources.length--;\n                last_index--;\n                work_count++;\n            }\n        }\n    }\n\n    return work_count;\n}\n\nstatic int aeron_client_conductor_check_registering_resources(aeron_client_conductor_t *conductor, int64_t now_ns)\n{\n    int work_count = 0;\n\n    for (size_t size = conductor->registering_resources.length, last_index = size - 1, i = size; i > 0; i--)\n    {\n        size_t index = i - 1;\n        aeron_client_registering_resource_t *resource = conductor->registering_resources.array[index].resource;\n\n        if (now_ns > resource->registration_deadline_ns)\n        {\n            aeron_client_conductor_remove_registering_resource(\n                conductor, resource, index, last_index, AERON_CLIENT_REGISTRATION_STATUS_TIMED_OUT);\n            last_index--;\n            work_count++;\n        }\n    }\n\n    return work_count;\n}\n\nstatic int aeron_client_conductor_on_check_timeouts(aeron_client_conductor_t *conductor)\n{\n    int work_count = 0, result = 0;\n    const int64_t now_ns = conductor->nano_clock();\n\n    if (now_ns > (conductor->time_of_last_service_ns + (int64_t)conductor->idle_sleep_duration_ns))\n    {\n        if (now_ns > (conductor->time_of_last_service_ns + (int64_t)conductor->inter_service_timeout_ns))\n        {\n            char buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n\n            conductor->is_terminating = true;\n            aeron_client_conductor_force_close_resources(conductor);\n            snprintf(buffer, sizeof(buffer) - 1,\n                \"service interval exceeded in ns: timeout=%\" PRId64 \", interval=%\" PRId64,\n                (int64_t)conductor->inter_service_timeout_ns,\n                (int64_t)(now_ns - conductor->time_of_last_service_ns));\n            conductor->error_handler(\n                conductor->error_handler_clientd, AERON_CLIENT_ERROR_CONDUCTOR_SERVICE_TIMEOUT, buffer);\n            return -1;\n        }\n\n        conductor->time_of_last_service_ns = now_ns;\n\n        if ((result = aeron_client_conductor_check_liveness(conductor, now_ns)) < 0)\n        {\n            return -1;\n        }\n        work_count += result;\n\n        if ((result = aeron_client_conductor_check_lingering_resources(conductor, now_ns)) < 0)\n        {\n            return -1;\n        }\n        work_count += result;\n\n        if ((result = aeron_client_conductor_check_registering_resources(conductor, now_ns)) < 0)\n        {\n            return -1;\n        }\n        work_count += result;\n    }\n\n    return work_count;\n}\n\nstatic void aeron_client_conductor_delete_log_buffer(void *clientd, int64_t key, void *value)\n{\n    aeron_log_buffer_delete((aeron_log_buffer_t *)value);\n}\n\nstatic void aeron_client_conductor_delete_image(void *clientd, const uint8_t *key, size_t key_len, void *value)\n{\n    aeron_image_delete((aeron_image_t *)value);\n}\n\nstatic void aeron_client_conductor_delete_resource(void *clientd, int64_t key, void *value)\n{\n    aeron_client_command_base_t *base = (aeron_client_command_base_t *)value;\n\n    switch (base->type)\n    {\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION:\n            aeron_publication_delete((aeron_publication_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION:\n            aeron_exclusive_publication_delete((aeron_exclusive_publication_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION:\n            aeron_subscription_delete((aeron_subscription_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER:\n            aeron_counter_delete((aeron_counter_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_IMAGE:\n            aeron_image_delete((aeron_image_t *)value);\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_LOG_BUFFER:\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_DESTINATION:\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_NEXT_AVAILABLE_SESSION_ID:\n            break;\n    }\n}\n\nstatic void aeron_client_conductor_delete_lingering_resource(aeron_client_managed_resource_t *resource)\n{\n    if (AERON_CLIENT_MANAGED_RESOURCE_TYPE_IMAGE == resource->type)\n    {\n        aeron_image_delete(resource->resource.image);\n    }\n}\n\nstatic void aeron_client_conductor_notify_close_handlers(aeron_client_conductor_t *conductor)\n{\n    for (size_t i = 0, length = conductor->close_handlers.length; i < length; i++)\n    {\n        aeron_on_close_client_pair_t *pair = &conductor->close_handlers.array[i];\n\n        pair->handler(pair->clientd);\n    }\n}\n\nstatic int aeron_client_conductor_on_cmd_client_close(aeron_client_conductor_t *conductor)\n{\n    int32_t offset = aeron_client_conductor_try_claim_driver_command(\n        conductor, AERON_COMMAND_CLIENT_CLOSE, sizeof(aeron_correlated_command_t));\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = conductor->to_driver_buffer.buffer + offset;\n    aeron_correlated_command_t *command = (aeron_correlated_command_t *)ptr;\n    command->client_id = conductor->client_id;\n    command->correlation_id = AERON_NULL_VALUE;\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n    return 0;\n}\n\nstatic int aeron_client_conductor_linger_or_delete_all_images(\n    aeron_client_conductor_t *conductor, aeron_subscription_t *subscription)\n{\n    volatile aeron_image_list_t *current_image_list = aeron_client_conductor_subscription_image_list(subscription);\n\n    for (size_t i = 0; i < current_image_list->length; i++)\n    {\n        aeron_image_t *image = current_image_list->array[i];\n        int64_t refcnt = aeron_image_decr_refcnt(image);\n\n        aeron_array_to_ptr_hash_map_remove(\n            &conductor->image_by_key_map,\n            (const uint8_t *)&image->key,\n            sizeof(aeron_image_key_t));\n\n        if (refcnt <= 0)\n        {\n            aeron_image_close(image);\n            if (NULL != subscription->on_unavailable_image)\n            {\n                subscription->on_unavailable_image(subscription->on_unavailable_image_clientd, subscription, image);\n            }\n\n            aeron_client_conductor_release_log_buffer(conductor, image->log_buffer);\n            aeron_image_delete(image);\n        }\n        else if (!image->is_lingering)\n        {\n            if (aeron_client_conductor_linger_image(conductor, image) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    for (size_t i = 0, length = conductor->lingering_resources.length; i < length; i++)\n    {\n        aeron_client_managed_resource_t *resource = &conductor->lingering_resources.array[i];\n\n        if (AERON_CLIENT_MANAGED_RESOURCE_TYPE_IMAGE == resource->type)\n        {\n            aeron_image_t *lingering_image = resource->resource.image;\n\n            if (subscription == lingering_image->subscription)\n            {\n                if (INT64_MIN != lingering_image->removal_change_number)\n                {\n                    aeron_image_decr_refcnt(lingering_image);\n                    lingering_image->subscription = NULL;\n                    lingering_image->removal_change_number = INT64_MIN;\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_cmd_add_publication(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_async_add_publication_t *async = (aeron_async_add_publication_t *)item;\n    const size_t command_length = sizeof(aeron_publication_command_t) + async->uri_length;\n\n    int32_t offset = aeron_client_conductor_add_registering_resource(\n        conductor, async, AERON_COMMAND_ADD_PUBLICATION, command_length);\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset);\n    aeron_publication_command_t *command = (aeron_publication_command_t *)ptr;\n    command->correlated.correlation_id = async->registration_id;\n    command->correlated.client_id = conductor->client_id;\n    command->stream_id = async->stream_id;\n    command->channel_length = async->uri_length;\n    memcpy(ptr + sizeof(aeron_publication_command_t), async->uri, (size_t)async->uri_length);\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_cmd_close_publication(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_publication_t *publication = (aeron_publication_t *)item;\n    aeron_notification_t on_close_complete = publication->on_close_complete;\n    void *on_close_complete_clientd = publication->on_close_complete_clientd;\n\n    int result = 0;\n    if (NULL != aeron_int64_to_ptr_hash_map_remove(&conductor->resource_by_id_map, publication->registration_id))\n    {\n        result = aeron_client_conductor_offer_remove_publication_command(conductor, publication->registration_id, false);\n    }\n\n    aeron_client_conductor_release_log_buffer(conductor, publication->log_buffer);\n    aeron_publication_delete(publication);\n\n    if (NULL != on_close_complete)\n    {\n        on_close_complete(on_close_complete_clientd);\n    }\n\n    return result;\n}\n\nstatic int aeron_client_conductor_on_cmd_add_exclusive_publication(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_async_add_exclusive_publication_t *async = (aeron_async_add_exclusive_publication_t *)item;\n    const size_t command_length = sizeof(aeron_publication_command_t) + async->uri_length;\n\n    int32_t offset = aeron_client_conductor_add_registering_resource(\n        conductor, async, AERON_COMMAND_ADD_EXCLUSIVE_PUBLICATION, command_length);\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset);\n    aeron_publication_command_t *command = (aeron_publication_command_t *)ptr;\n    command->correlated.correlation_id = async->registration_id;\n    command->correlated.client_id = conductor->client_id;\n    command->stream_id = async->stream_id;\n    command->channel_length = async->uri_length;\n    memcpy(ptr + sizeof(aeron_publication_command_t), async->uri, (size_t)async->uri_length);\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_cmd_close_exclusive_publication(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_exclusive_publication_t *publication = (aeron_exclusive_publication_t *)item;\n    aeron_notification_t on_close_complete = publication->on_close_complete;\n    void *on_close_complete_clientd = publication->on_close_complete_clientd;\n\n    int result = 0;\n    if (NULL != aeron_int64_to_ptr_hash_map_remove(&conductor->resource_by_id_map, publication->registration_id))\n    {\n        result = aeron_client_conductor_offer_remove_publication_command(\n            conductor, publication->registration_id, publication->revoke_on_close);\n    }\n\n    aeron_client_conductor_release_log_buffer(conductor, publication->log_buffer);\n    aeron_exclusive_publication_delete(publication);\n\n    if (NULL != on_close_complete)\n    {\n        on_close_complete(on_close_complete_clientd);\n    }\n\n    return result;\n}\n\nstatic int aeron_client_conductor_on_cmd_add_subscription(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_async_add_subscription_t *async = (aeron_async_add_subscription_t *)item;\n    const size_t command_length = sizeof(aeron_subscription_command_t) + async->uri_length;\n\n    int32_t offset = aeron_client_conductor_add_registering_resource(\n        conductor, async, AERON_COMMAND_ADD_SUBSCRIPTION, command_length);\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset);\n    aeron_subscription_command_t *command = (aeron_subscription_command_t *)ptr;\n    command->correlated.correlation_id = async->registration_id;\n    command->correlated.client_id = conductor->client_id;\n    command->stream_id = async->stream_id;\n    command->channel_length = async->uri_length;\n    memcpy(ptr + sizeof(aeron_subscription_command_t), async->uri, (size_t)async->uri_length);\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_cmd_close_subscription(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_subscription_t *subscription = (aeron_subscription_t *)item;\n    aeron_notification_t on_close_complete = subscription->on_close_complete;\n    void *on_close_complete_clientd = subscription->on_close_complete_clientd;\n\n    int result = 0;\n    if (NULL != aeron_int64_to_ptr_hash_map_remove(&conductor->resource_by_id_map, subscription->registration_id))\n    {\n        result = aeron_client_conductor_offer_remove_subscription_command(conductor, subscription->registration_id);\n    }\n\n    aeron_client_conductor_linger_or_delete_all_images(conductor, subscription);\n    aeron_subscription_delete(subscription);\n\n    if (NULL != on_close_complete)\n    {\n        on_close_complete(on_close_complete_clientd);\n    }\n\n    return result;\n}\n\nstatic int aeron_client_conductor_on_cmd_add_counter(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_async_add_counter_t *async = (aeron_async_add_counter_t *)item;\n\n    const size_t command_length =\n        sizeof(aeron_counter_command_t) +\n        sizeof(int32_t) +\n        (size_t)(AERON_ALIGN(async->counter.key_buffer_length, sizeof(int32_t))) +\n        sizeof(int32_t) +\n        (size_t)async->counter.label_buffer_length;\n\n    int32_t offset = aeron_client_conductor_add_registering_resource(\n        conductor, async, AERON_COMMAND_ADD_COUNTER, command_length);\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset);\n    aeron_counter_command_t *command = (aeron_counter_command_t *)ptr;\n    char *cursor = (char *)(ptr + sizeof(aeron_counter_command_t));\n\n    command->correlated.correlation_id = async->registration_id;\n    command->correlated.client_id = conductor->client_id;\n    command->type_id = async->counter.type_id;\n\n    memcpy(cursor, &async->counter.key_buffer_length, sizeof(int32_t));\n    cursor += sizeof(int32_t);\n    memcpy(cursor, async->counter.key_buffer, (size_t)async->counter.key_buffer_length);\n    cursor += AERON_ALIGN(async->counter.key_buffer_length, sizeof(int32_t));\n    memcpy(cursor, &async->counter.label_buffer_length, sizeof(int32_t));\n    cursor += sizeof(int32_t);\n    memcpy(cursor, async->counter.label_buffer, (size_t)async->counter.label_buffer_length);\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_cmd_close_counter(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_counter_t *counter = (aeron_counter_t *)item;\n    aeron_notification_t on_close_complete = counter->on_close_complete;\n    void *on_close_complete_clientd = counter->on_close_complete_clientd;\n\n    int result = 0;\n    if (NULL != aeron_int64_to_ptr_hash_map_remove(&conductor->resource_by_id_map, counter->registration_id))\n    {\n        result = aeron_client_conductor_offer_remove_counter_command(conductor, counter->registration_id);\n    }\n\n    aeron_counter_delete(counter);\n\n    if (NULL != on_close_complete)\n    {\n        on_close_complete(on_close_complete_clientd);\n    }\n\n    return result;\n}\n\nstatic int aeron_client_conductor_on_cmd_add_static_counter(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_async_add_counter_t *async = (aeron_async_add_counter_t *)item;\n\n    const size_t command_length =\n        sizeof(aeron_static_counter_command_t) +\n        sizeof(int32_t) +\n        (size_t)(AERON_ALIGN(async->counter.key_buffer_length, sizeof(int32_t))) +\n        sizeof(int32_t) +\n        (size_t)async->counter.label_buffer_length;\n\n    int32_t offset = aeron_client_conductor_add_registering_resource(\n        conductor, async, AERON_COMMAND_ADD_STATIC_COUNTER, command_length);\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset);\n    aeron_static_counter_command_t *command = (aeron_static_counter_command_t *)ptr;\n    char *cursor = (char *)(ptr + sizeof(aeron_static_counter_command_t));\n\n    command->correlated.correlation_id = async->registration_id;\n    command->correlated.client_id = conductor->client_id;\n    command->registration_id = async->counter.registration_id;\n    command->type_id = async->counter.type_id;\n\n    memcpy(cursor, &async->counter.key_buffer_length, sizeof(int32_t));\n    cursor += sizeof(int32_t);\n    memcpy(cursor, async->counter.key_buffer, (size_t)async->counter.key_buffer_length);\n    cursor += AERON_ALIGN(async->counter.key_buffer_length, sizeof(int32_t));\n    memcpy(cursor, &async->counter.label_buffer_length, sizeof(int32_t));\n    cursor += sizeof(int32_t);\n    memcpy(cursor, async->counter.label_buffer, (size_t)async->counter.label_buffer_length);\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n    return 0;\n}\n\nstatic int aeron_client_conductor_get_async_registration_id(\n    aeron_client_conductor_t *conductor,\n    aeron_client_registering_resource_t *async,\n    int64_t *resource_registration_id)\n{\n    int result = 0;\n\n    switch (async->resource.base_resource->type)\n    {\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION:\n            *resource_registration_id = async->resource.publication->registration_id;\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION:\n            *resource_registration_id = async->resource.exclusive_publication->registration_id;\n            break;\n\n        case AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION:\n            *resource_registration_id = async->resource.subscription->registration_id;\n            break;\n\n        default:\n        {\n            char err_buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n            snprintf(\n                err_buffer, sizeof(err_buffer) - 1, \"unknown resource type: %d\", async->resource.base_resource->type);\n            conductor->error_handler(conductor->error_handler_clientd, EINVAL, err_buffer);\n            result = -1;\n            break;\n        }\n    }\n\n    return result;\n}\n\nstatic int aeron_client_conductor_on_cmd_destination(const void *clientd, const void *item, int32_t msg_type_id)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_async_destination_t *async = (aeron_async_destination_t *)item;\n\n    int64_t resource_registration_id = 0;\n    if (aeron_client_conductor_get_async_registration_id(conductor, async, &resource_registration_id) < 0)\n    {\n        return -1;\n    }\n\n    const size_t command_length = sizeof(aeron_destination_command_t) + async->uri_length;\n    int32_t offset = aeron_client_conductor_add_registering_resource(\n        conductor, async, msg_type_id, command_length);\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset);\n    aeron_destination_command_t *command = (aeron_destination_command_t *)ptr;\n    command->correlated.correlation_id = async->registration_id;\n    command->correlated.client_id = conductor->client_id;\n    command->registration_id = resource_registration_id;\n    command->channel_length = async->uri_length;\n    memcpy(ptr + sizeof(aeron_destination_command_t), async->uri, (size_t)async->uri_length);\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_cmd_destination_by_id(const void *clientd, const void *item, int32_t msg_type_id)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_async_destination_t *async = (aeron_async_destination_t *)item;\n\n    int64_t resource_registration_id = 0;\n    if (aeron_client_conductor_get_async_registration_id(conductor, async, &resource_registration_id) < 0)\n    {\n        return -1;\n    }\n\n    const size_t command_length = sizeof(aeron_destination_by_id_command_t);\n\n    int32_t offset = aeron_client_conductor_add_registering_resource(\n        conductor, async, msg_type_id, command_length);\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset);\n    aeron_destination_by_id_command_t *command = (aeron_destination_by_id_command_t *)ptr;\n    command->correlated.correlation_id = async->registration_id;\n    command->correlated.client_id = conductor->client_id;\n    command->resource_registration_id = resource_registration_id;\n    command->destination_registration_id = resource_registration_id;\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n    return 0;\n}\n\nstatic int aeron_client_conductor_async_destination(\n    aeron_async_destination_t **async,\n    union aeron_client_registering_resource_un *resource,\n    const char *uri,\n    aeron_client_conductor_t *conductor,\n    int (*destination_func)(void *clientd, void *command))\n{\n    aeron_async_destination_t *cmd = NULL;\n    char *uri_copy = NULL;\n    size_t uri_length = strlen(uri);\n\n    *async = NULL;\n\n    if (aeron_alloc((void **)&uri_copy, uri_length + 1) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate uri_copy\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&cmd, sizeof(aeron_async_destination_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate destination\");\n        aeron_free(uri_copy);\n        return -1;\n    }\n\n    memcpy(uri_copy, uri, uri_length);\n    uri_copy[uri_length] = '\\0';\n\n    cmd->command_base.func = destination_func;\n    cmd->command_base.item = NULL;\n    memcpy(&cmd->resource, resource, sizeof(union aeron_client_registering_resource_un));\n    cmd->error_message = NULL;\n    cmd->error_message_length = 0;\n    cmd->uri = uri_copy;\n    cmd->uri_length = (int32_t)uri_length;\n    cmd->registration_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    cmd->registration_status = AERON_CLIENT_REGISTRATION_STATUS_AWAITING;\n    cmd->type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_DESTINATION;\n\n    if (conductor->invoker_mode)\n    {\n        if (destination_func(conductor, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n    else\n    {\n        if (aeron_client_conductor_command_offer(conductor->command_queue, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n    *async = cmd;\n\n    return 0;\n\nerror:\n    aeron_free(cmd->uri);\n    aeron_free(cmd);\n    return -1;\n}\n\nstatic int aeron_client_conductor_async_destination_by_id(\n    aeron_async_destination_t **async,\n    union aeron_client_registering_resource_un *resource,\n    int64_t destination_registration_id,\n    aeron_client_conductor_t *conductor,\n    int (*destination_func)(void *clientd, void *command))\n{\n    aeron_async_destination_by_id_t *cmd = NULL;\n    *async = NULL;\n\n    if (aeron_alloc((void **)&cmd, sizeof(aeron_async_destination_by_id_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate destination\");\n        return -1;\n    }\n\n    cmd->command_base.func = destination_func;\n    cmd->command_base.item = NULL;\n    memcpy(&cmd->resource, resource, sizeof(union aeron_client_registering_resource_un));\n    cmd->error_message = NULL;\n    cmd->error_message_length = 0;\n    cmd->destination_registration_id = destination_registration_id;\n    cmd->registration_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    cmd->registration_status = AERON_CLIENT_REGISTRATION_STATUS_AWAITING;\n    cmd->type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_DESTINATION;\n\n    if (conductor->invoker_mode)\n    {\n        if (destination_func(conductor, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n    else\n    {\n        if (aeron_client_conductor_command_offer(conductor->command_queue, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n\n    *async = cmd;\n    return 0;\n\nerror:\n    aeron_free(cmd->uri);\n    aeron_free(cmd);\n    return -1;\n}\n\nstatic int aeron_client_conductor_ond_cmd_get_next_available_session_id(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_async_get_next_available_session_id_t *async = (aeron_async_get_next_available_session_id_t *)item;\n\n    const size_t command_length = sizeof(aeron_get_next_available_session_id_command_t);\n\n    int32_t offset = aeron_client_conductor_add_registering_resource(\n        conductor, async, AERON_COMMAND_GET_NEXT_AVAILABLE_SESSION_ID, command_length);\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset);\n    aeron_get_next_available_session_id_command_t *command = (aeron_get_next_available_session_id_command_t *)ptr;\n    command->correlated.correlation_id = async->registration_id;\n    command->correlated.client_id = conductor->client_id;\n    command->stream_id = async->stream_id;\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n    return 0;\n}\n\nstatic int aeron_client_conductor_on_cmd_add_destination(void *clientd, void *item)\n{\n    return aeron_client_conductor_on_cmd_destination(clientd, item, AERON_COMMAND_ADD_DESTINATION);\n}\n\nstatic int aeron_client_conductor_on_cmd_remove_destination(void *clientd, void *item)\n{\n    return aeron_client_conductor_on_cmd_destination(clientd, item, AERON_COMMAND_REMOVE_DESTINATION);\n}\n\nstatic int aeron_client_conductor_on_cmd_remove_destination_by_id(void *clientd, void *item)\n{\n    return aeron_client_conductor_on_cmd_destination_by_id(clientd, item, AERON_COMMAND_REMOVE_DESTINATION_BY_ID);\n}\n\nstatic int aeron_client_conductor_on_cmd_add_rcv_destination(void *clientd, void *item)\n{\n    return aeron_client_conductor_on_cmd_destination(clientd, item, AERON_COMMAND_ADD_RCV_DESTINATION);\n}\n\nstatic int aeron_client_conductor_on_cmd_remove_rcv_destination(void *clientd, void *item)\n{\n    return aeron_client_conductor_on_cmd_destination(clientd, item, AERON_COMMAND_REMOVE_RCV_DESTINATION);\n}\n\nstatic int aeron_client_conductor_on_cmd_handler(void *clientd, void *item)\n{\n    aeron_client_conductor_t *conductor = (aeron_client_conductor_t *)clientd;\n    aeron_client_handler_cmd_t *cmd = (aeron_client_handler_cmd_t *)item;\n    int result = 0;\n\n    switch (cmd->type)\n    {\n        case AERON_CLIENT_HANDLER_ADD_AVAILABLE_COUNTER:\n        {\n            aeron_on_available_counter_pair_t *pair;\n\n            AERON_ON_HANDLER_ADD(pair, conductor, available_counter_handlers, aeron_on_available_counter_pair_t);\n            if (NULL != pair)\n            {\n                pair->handler = cmd->handler.on_available_counter;\n                pair->clientd = cmd->clientd;\n            }\n            else\n            {\n                result = -1;\n            }\n            break;\n        }\n\n        case AERON_CLIENT_HANDLER_REMOVE_AVAILABLE_COUNTER:\n        {\n            for (size_t i = 0, size = conductor->available_counter_handlers.length, last_index = size - 1;\n                i < size;\n                i++)\n            {\n                aeron_on_available_counter_pair_t *pair = &conductor->available_counter_handlers.array[i];\n\n                if (cmd->handler.on_available_counter == pair->handler && cmd->clientd == pair->clientd)\n                {\n                    aeron_array_fast_unordered_remove(\n                        (uint8_t *)conductor->available_counter_handlers.array,\n                        sizeof(aeron_on_available_counter_pair_t),\n                        i,\n                        last_index);\n                    conductor->available_counter_handlers.length--;\n                    break;\n                }\n            }\n            break;\n        }\n\n        case AERON_CLIENT_HANDLER_ADD_UNAVAILABLE_COUNTER:\n        {\n            aeron_on_unavailable_counter_pair_t *pair;\n\n            AERON_ON_HANDLER_ADD(pair, conductor, unavailable_counter_handlers, aeron_on_unavailable_counter_pair_t);\n            if (NULL != pair)\n            {\n                pair->handler = cmd->handler.on_unavailable_counter;\n                pair->clientd = cmd->clientd;\n            }\n            else\n            {\n                result = -1;\n            }\n            break;\n        }\n\n        case AERON_CLIENT_HANDLER_REMOVE_UNAVAILABLE_COUNTER:\n        {\n            for (size_t i = 0, size = conductor->unavailable_counter_handlers.length, last_index = size - 1;\n                i < size;\n                i++)\n            {\n                aeron_on_unavailable_counter_pair_t *pair = &conductor->unavailable_counter_handlers.array[i];\n\n                if (cmd->handler.on_unavailable_counter == pair->handler && cmd->clientd == pair->clientd)\n                {\n                    aeron_array_fast_unordered_remove(\n                        (uint8_t *)conductor->unavailable_counter_handlers.array,\n                        sizeof(aeron_on_unavailable_counter_pair_t),\n                        i,\n                        last_index);\n                    conductor->unavailable_counter_handlers.length--;\n                    break;\n                }\n            }\n            break;\n        }\n\n        case AERON_CLIENT_HANDLER_ADD_CLOSE_HANDLER:\n        {\n            aeron_on_close_client_pair_t *pair;\n\n            AERON_ON_HANDLER_ADD(pair, conductor, close_handlers, aeron_on_close_client_pair_t);\n            if (NULL != pair)\n            {\n                pair->handler = cmd->handler.on_close_handler;\n                pair->clientd = cmd->clientd;\n            }\n            else\n            {\n                result = -1;\n            }\n            break;\n        }\n\n        case AERON_CLIENT_HANDLER_REMOVE_CLOSE_HANDLER:\n        {\n            for (size_t i = 0, size = conductor->close_handlers.length, last_index = size - 1; i < size; i++)\n            {\n                aeron_on_close_client_pair_t *pair = &conductor->close_handlers.array[i];\n\n                if (cmd->handler.on_close_handler == pair->handler && cmd->clientd == pair->clientd)\n                {\n                    aeron_array_fast_unordered_remove(\n                        (uint8_t *)conductor->close_handlers.array,\n                        sizeof(aeron_on_close_client_pair_t),\n                        i,\n                        last_index);\n                    conductor->close_handlers.length--;\n                    break;\n                }\n            }\n            break;\n        }\n    }\n\n    AERON_SET_RELEASE(cmd->processed, true);\n    return result;\n}\n\nint aeron_client_conductor_init(aeron_client_conductor_t *conductor, aeron_context_t *context)\n{\n    aeron_cnc_metadata_t *metadata = (aeron_cnc_metadata_t *)context->cnc_map.addr;\n\n    if (aeron_broadcast_receiver_init(\n        &conductor->to_client_buffer, aeron_cnc_to_clients_buffer(metadata), (size_t)metadata->to_clients_buffer_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init to_client_buffer\");\n        return -1;\n    }\n\n    if (aeron_mpsc_rb_init(\n        &conductor->to_driver_buffer, aeron_cnc_to_driver_buffer(metadata), (size_t)metadata->to_driver_buffer_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init to_driver_buffer\");\n        goto error;\n    }\n\n    if (aeron_counters_reader_init(\n        &conductor->counters_reader,\n        aeron_cnc_counters_metadata_buffer(metadata),\n        (size_t)metadata->counter_metadata_buffer_length,\n        aeron_cnc_counters_values_buffer(metadata),\n        (size_t)metadata->counter_values_buffer_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init counters_reader\");\n        goto error;\n    }\n\n    if (aeron_int64_to_ptr_hash_map_init(\n        &conductor->log_buffer_by_id_map, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init log_buffer_by_id_map\");\n        goto error;\n    }\n\n    if (aeron_int64_to_ptr_hash_map_init(\n        &conductor->resource_by_id_map, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init resource_by_id_map\");\n        goto error;\n    }\n\n    if (aeron_array_to_ptr_hash_map_init(\n        &conductor->image_by_key_map, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init image_by_key_map\");\n        goto error;\n    }\n\n    conductor->client_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    conductor->client_name = aeron_context_get_client_name(context);\n\n    conductor->available_counter_handlers.array = NULL;\n    conductor->available_counter_handlers.capacity = 0;\n    conductor->available_counter_handlers.length = 0;\n\n    if (NULL != context->on_available_counter)\n    {\n        int result = 0;\n        AERON_ARRAY_ENSURE_CAPACITY(result, conductor->available_counter_handlers, aeron_on_available_counter_pair_t)\n        if (result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Unable to ensure capacity for available_counter_handlers\");\n            goto error;\n        }\n\n        aeron_on_available_counter_pair_t *pair = &conductor->available_counter_handlers.array[0];\n\n        pair->handler = context->on_available_counter;\n        pair->clientd = context->on_available_counter_clientd;\n        conductor->available_counter_handlers.length++;\n    }\n\n    conductor->unavailable_counter_handlers.array = NULL;\n    conductor->unavailable_counter_handlers.capacity = 0;\n    conductor->unavailable_counter_handlers.length = 0;\n\n    if (NULL != context->on_unavailable_counter)\n    {\n        int result = 0;\n        AERON_ARRAY_ENSURE_CAPACITY(\n            result, conductor->unavailable_counter_handlers, aeron_on_unavailable_counter_pair_t)\n        if (result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Unable to ensure capacity for unavailable_counter_handlers\");\n            goto error;\n        }\n\n        aeron_on_unavailable_counter_pair_t *pair = &conductor->unavailable_counter_handlers.array[0];\n\n        pair->handler = context->on_unavailable_counter;\n        pair->clientd = context->on_unavailable_counter_clientd;\n        conductor->unavailable_counter_handlers.length++;\n    }\n\n    conductor->close_handlers.array = NULL;\n    conductor->close_handlers.capacity = 0;\n    conductor->close_handlers.length = 0;\n\n    if (NULL != context->on_close_client)\n    {\n        int result = 0;\n        AERON_ARRAY_ENSURE_CAPACITY(result, conductor->close_handlers, aeron_on_close_client_pair_t)\n        if (result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Unable to ensure capacity for close_handlers\");\n            goto error;\n        }\n\n        aeron_on_close_client_pair_t *pair = &conductor->close_handlers.array[0];\n\n        pair->handler = context->on_close_client;\n        pair->clientd = context->on_close_client_clientd;\n        conductor->close_handlers.length++;\n    }\n\n    conductor->registering_resources.array = NULL;\n    conductor->registering_resources.capacity = 0;\n    conductor->registering_resources.length = 0;\n\n    conductor->lingering_resources.array = NULL;\n    conductor->lingering_resources.capacity = 0;\n    conductor->lingering_resources.length = 0;\n\n    conductor->heartbeat_timestamp.addr = NULL;\n    conductor->heartbeat_timestamp.counter_id = AERON_NULL_COUNTER_ID;\n\n    conductor->command_queue = &context->command_queue;\n    conductor->epoch_clock = context->epoch_clock;\n    conductor->nano_clock = context->nano_clock;\n\n    conductor->error_handler = context->error_handler;\n    conductor->error_handler_clientd = context->error_handler_clientd;\n\n    conductor->error_frame_handler = context->error_frame_handler;\n    conductor->error_frame_handler_clientd = context->error_frame_handler_clientd;\n\n    conductor->on_new_publication = context->on_new_publication;\n    conductor->on_new_publication_clientd = context->on_new_publication_clientd;\n\n    conductor->on_new_exclusive_publication = context->on_new_exclusive_publication;\n    conductor->on_new_exclusive_publication_clientd = context->on_new_exclusive_publication_clientd;\n\n    conductor->on_new_subscription = context->on_new_subscription;\n    conductor->on_new_subscription_clientd = context->on_new_subscription_clientd;\n\n    conductor->driver_timeout_ms = context->driver_timeout_ms;\n    conductor->driver_timeout_ns = context->driver_timeout_ms * 1000000;\n    conductor->inter_service_timeout_ns = (uint64_t)metadata->client_liveness_timeout;\n    conductor->keepalive_interval_ns = context->keepalive_interval_ns;\n    conductor->resource_linger_duration_ns = context->resource_linger_duration_ns;\n    conductor->idle_sleep_duration_ns = context->idle_sleep_duration_ns;\n\n    conductor->control_protocol_version = 0;\n\n    if (AERON_SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION == aeron_counters_reader_find_by_type_id_and_registration_id(\n        &conductor->counters_reader,\n        AERON_COUNTER_SYSTEM_COUNTER_TYPE_ID,\n        AERON_SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION))\n    {\n        int64_t *addr = aeron_counters_reader_addr(\n            &conductor->counters_reader,\n            AERON_SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION);\n        conductor->control_protocol_version = (int32_t)aeron_counter_get_acquire(addr);\n    }\n\n    int64_t now_ns = context->nano_clock();\n\n    conductor->time_of_last_keepalive_ns = now_ns;\n    conductor->time_of_last_service_ns = now_ns;\n\n    conductor->invoker_mode = context->use_conductor_agent_invoker;\n    conductor->pre_touch = context->pre_touch_mapped_memory;\n    conductor->is_terminating = false;\n\n    return 0;\n\nerror:\n    aeron_broadcast_receiver_close(&conductor->to_client_buffer);\n    aeron_int64_to_ptr_hash_map_delete(&conductor->log_buffer_by_id_map);\n    aeron_int64_to_ptr_hash_map_delete(&conductor->resource_by_id_map);\n    aeron_array_to_ptr_hash_map_delete(&conductor->image_by_key_map);\n    aeron_free(conductor->available_counter_handlers.array);\n    aeron_free(conductor->unavailable_counter_handlers.array);\n    aeron_free(conductor->close_handlers.array);\n    return -1;\n}\n\nint aeron_client_conductor_do_work(aeron_client_conductor_t *conductor)\n{\n    int work_count = 0, result = 0;\n\n    if (conductor->is_terminating)\n    {\n        return 0;\n    }\n\n    work_count += (int)aeron_mpsc_concurrent_array_queue_drain(\n        conductor->command_queue, aeron_client_conductor_on_command, conductor, 1);\n\n    work_count += aeron_broadcast_receiver_receive(\n        &conductor->to_client_buffer, aeron_client_conductor_on_driver_response, conductor);\n\n    if ((result = aeron_client_conductor_on_check_timeouts(conductor)) < 0)\n    {\n        return work_count;\n    }\n    work_count += result;\n\n    return work_count;\n}\n\nvoid aeron_client_conductor_on_close(aeron_client_conductor_t *conductor)\n{\n    aeron_client_conductor_notify_close_handlers(conductor);\n\n    aeron_int64_to_ptr_hash_map_for_each(\n        &conductor->log_buffer_by_id_map, aeron_client_conductor_delete_log_buffer, NULL);\n    aeron_int64_to_ptr_hash_map_for_each(\n        &conductor->resource_by_id_map, aeron_client_conductor_delete_resource, NULL);\n    aeron_array_to_ptr_hash_map_for_each(\n        &conductor->image_by_key_map, aeron_client_conductor_delete_image, NULL);\n\n    for (size_t i = 0, length = conductor->lingering_resources.length; i < length; i++)\n    {\n        aeron_client_conductor_delete_lingering_resource(&conductor->lingering_resources.array[i]);\n    }\n\n    aeron_client_conductor_on_cmd_client_close(conductor);\n    aeron_broadcast_receiver_close(&conductor->to_client_buffer);\n\n    aeron_int64_to_ptr_hash_map_delete(&conductor->log_buffer_by_id_map);\n    aeron_int64_to_ptr_hash_map_delete(&conductor->resource_by_id_map);\n    aeron_array_to_ptr_hash_map_delete(&conductor->image_by_key_map);\n    aeron_free(conductor->registering_resources.array);\n    aeron_free(conductor->lingering_resources.array);\n    aeron_free(conductor->available_counter_handlers.array);\n    aeron_free(conductor->unavailable_counter_handlers.array);\n    aeron_free(conductor->close_handlers.array);\n}\n\nint aeron_client_conductor_async_add_publication(\n    aeron_async_add_publication_t **async, aeron_client_conductor_t *conductor, const char *uri, int32_t stream_id)\n{\n    aeron_async_add_publication_t *cmd = NULL;\n    char *uri_copy = NULL;\n    size_t uri_length = strlen(uri);\n\n    *async = NULL;\n\n    if (aeron_alloc((void **)&cmd, sizeof(aeron_async_add_publication_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate publication\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&uri_copy, uri_length + 1) < 0)\n    {\n        aeron_free(cmd);\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate uri_copy\");\n        return -1;\n    }\n\n    memcpy(uri_copy, uri, uri_length);\n    uri_copy[uri_length] = '\\0';\n\n    cmd->command_base.func = aeron_client_conductor_on_cmd_add_publication;\n    cmd->command_base.item = NULL;\n    cmd->resource.publication = NULL;\n    cmd->error_message = NULL;\n    cmd->error_message_length = 0;\n    cmd->uri = uri_copy;\n    cmd->uri_length = (int32_t)uri_length;\n    cmd->stream_id = stream_id;\n    cmd->registration_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    cmd->registration_status = AERON_CLIENT_REGISTRATION_STATUS_AWAITING;\n    cmd->type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION;\n\n    if (conductor->invoker_mode)\n    {\n        if (aeron_client_conductor_on_cmd_add_publication(conductor, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n    else\n    {\n        if (aeron_client_conductor_command_offer(conductor->command_queue, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n    *async = cmd;\n\n    return 0;\n\nerror:\n    aeron_free(cmd->uri);\n    aeron_free(cmd);\n    return -1;\n}\n\nint aeron_client_conductor_async_close_publication(\n    aeron_client_conductor_t *conductor,\n    aeron_publication_t *publication,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd)\n{\n    publication->command_base.func = aeron_client_conductor_on_cmd_close_publication;\n    publication->command_base.item = NULL;\n    publication->on_close_complete = on_close_complete;\n    publication->on_close_complete_clientd = on_close_complete_clientd;\n\n    if (conductor->invoker_mode)\n    {\n        return aeron_client_conductor_on_cmd_close_publication(conductor, publication);\n    }\n\n    return aeron_client_conductor_command_offer(conductor->command_queue, publication);\n}\n\nint aeron_client_conductor_async_add_exclusive_publication(\n    aeron_async_add_exclusive_publication_t **async,\n    aeron_client_conductor_t *conductor,\n    const char *uri,\n    int32_t stream_id)\n{\n    aeron_async_add_exclusive_publication_t *cmd = NULL;\n    char *uri_copy = NULL;\n    size_t uri_length = strlen(uri);\n\n    *async = NULL;\n\n    if (aeron_alloc((void **)&cmd, sizeof(aeron_async_add_exclusive_publication_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate exclusive_publication\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&uri_copy, uri_length + 1) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate uri_copy\");\n        aeron_free(cmd);\n        return -1;\n    }\n\n    memcpy(uri_copy, uri, uri_length);\n    uri_copy[uri_length] = '\\0';\n\n    cmd->command_base.func = aeron_client_conductor_on_cmd_add_exclusive_publication;\n    cmd->command_base.item = NULL;\n    cmd->resource.exclusive_publication = NULL;\n    cmd->error_message = NULL;\n    cmd->error_message_length = 0;\n    cmd->uri = uri_copy;\n    cmd->uri_length = (int32_t)uri_length;\n    cmd->stream_id = stream_id;\n    cmd->registration_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    cmd->registration_status = AERON_CLIENT_REGISTRATION_STATUS_AWAITING;\n    cmd->type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION;\n\n    if (conductor->invoker_mode)\n    {\n        if (aeron_client_conductor_on_cmd_add_exclusive_publication(conductor, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n    else\n    {\n        if (aeron_client_conductor_command_offer(conductor->command_queue, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n\n    *async = cmd;\n    return 0;\n\nerror:\n    aeron_free(cmd->uri);\n    aeron_free(cmd);\n    return -1;\n}\n\nint aeron_client_conductor_async_close_exclusive_publication(\n    aeron_client_conductor_t *conductor,\n    aeron_exclusive_publication_t *publication,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd)\n{\n    publication->command_base.func = aeron_client_conductor_on_cmd_close_exclusive_publication;\n    publication->command_base.item = NULL;\n    publication->on_close_complete = on_close_complete;\n    publication->on_close_complete_clientd = on_close_complete_clientd;\n\n    if (conductor->invoker_mode)\n    {\n        return aeron_client_conductor_on_cmd_close_exclusive_publication(conductor, publication);\n    }\n\n    return aeron_client_conductor_command_offer(conductor->command_queue, publication);\n}\n\nint aeron_client_conductor_async_add_subscription(\n    aeron_async_add_subscription_t **async,\n    aeron_client_conductor_t *conductor,\n    const char *uri,\n    int32_t stream_id,\n    aeron_on_available_image_t on_available_image_handler,\n    void *on_available_image_clientd,\n    aeron_on_unavailable_image_t on_unavailable_image_handler,\n    void *on_unavailable_image_clientd)\n{\n    aeron_async_add_subscription_t *cmd = NULL;\n    char *uri_copy = NULL;\n    size_t uri_length = strlen(uri);\n\n    *async = NULL;\n\n    if (aeron_alloc((void **)&cmd, sizeof(aeron_async_add_subscription_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate subscription\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&uri_copy, uri_length + 1) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate uri_copy\");\n        aeron_free(cmd);\n        return -1;\n    }\n\n    memcpy(uri_copy, uri, uri_length);\n    uri_copy[uri_length] = '\\0';\n\n    cmd->command_base.func = aeron_client_conductor_on_cmd_add_subscription;\n    cmd->command_base.item = NULL;\n    cmd->resource.subscription = NULL;\n    cmd->error_message = NULL;\n    cmd->error_message_length = 0;\n    cmd->uri = uri_copy;\n    cmd->uri_length = (int32_t)uri_length;\n    cmd->stream_id = stream_id;\n    cmd->registration_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    cmd->on_available_image = on_available_image_handler;\n    cmd->on_available_image_clientd = on_available_image_clientd;\n    cmd->on_unavailable_image = on_unavailable_image_handler;\n    cmd->on_unavailable_image_clientd = on_unavailable_image_clientd;\n    cmd->registration_status = AERON_CLIENT_REGISTRATION_STATUS_AWAITING;\n    cmd->type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION;\n\n    if (conductor->invoker_mode)\n    {\n        if (aeron_client_conductor_on_cmd_add_subscription(conductor, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n    else\n    {\n        if (aeron_client_conductor_command_offer(conductor->command_queue, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n\n    *async = cmd;\n    return 0;\n\nerror:\n    aeron_free(cmd->uri);\n    aeron_free(cmd);\n    return -1;\n}\n\nint aeron_client_conductor_async_close_subscription(\n    aeron_client_conductor_t *conductor,\n    aeron_subscription_t *subscription,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd)\n{\n    subscription->command_base.func = aeron_client_conductor_on_cmd_close_subscription;\n    subscription->command_base.item = NULL;\n    subscription->on_close_complete = on_close_complete;\n    subscription->on_close_complete_clientd = on_close_complete_clientd;\n\n    if (conductor->invoker_mode)\n    {\n        return aeron_client_conductor_on_cmd_close_subscription(conductor, subscription);\n    }\n\n    return aeron_client_conductor_command_offer(conductor->command_queue, subscription);\n}\n\nint aeron_client_conductor_async_add_counter(\n    aeron_async_add_counter_t **async,\n    aeron_client_conductor_t *conductor,\n    int32_t type_id,\n    const uint8_t *key_buffer,\n    size_t key_buffer_length,\n    const char *label_buffer,\n    size_t label_buffer_length)\n{\n    aeron_async_add_counter_t *cmd = NULL;\n    uint8_t *key_buffer_copy = NULL;\n    char *label_buffer_copy = NULL;\n\n    *async = NULL;\n\n    if (aeron_alloc((void **)&cmd, sizeof(aeron_async_add_counter_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate command\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&key_buffer_copy, key_buffer_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate key buffer\");\n        goto error;\n    }\n\n    if (aeron_alloc((void **)&label_buffer_copy, label_buffer_length + 1) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate label buffer\");\n        goto error;\n    }\n\n    if (key_buffer && key_buffer_length > 0)\n    {\n        memcpy(key_buffer_copy, key_buffer, key_buffer_length);\n    }\n\n    if (label_buffer && label_buffer_length > 0)\n    {\n        memcpy(label_buffer_copy, label_buffer, label_buffer_length);\n    }\n\n    label_buffer_copy[label_buffer_length] = '\\0';\n\n    cmd->command_base.func = aeron_client_conductor_on_cmd_add_counter;\n    cmd->command_base.item = NULL;\n    cmd->resource.counter = NULL;\n    cmd->error_message = NULL;\n    cmd->error_message_length = 0;\n    cmd->uri = NULL;\n    cmd->counter.key_buffer = key_buffer_copy;\n    cmd->counter.label_buffer = label_buffer_copy;\n    cmd->counter.key_buffer_length = key_buffer_length;\n    cmd->counter.label_buffer_length = label_buffer_length;\n    cmd->counter.type_id = type_id;\n    cmd->registration_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    cmd->registration_status = AERON_CLIENT_REGISTRATION_STATUS_AWAITING;\n    cmd->type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER;\n\n    if (conductor->invoker_mode)\n    {\n        if (aeron_client_conductor_on_cmd_add_counter(conductor, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n    else\n    {\n        if (aeron_client_conductor_command_offer(conductor->command_queue, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n\n    *async = cmd;\n    return 0;\n\nerror:\n    aeron_free(cmd);\n    aeron_free(key_buffer_copy);\n    aeron_free(label_buffer_copy);\n    return -1;\n}\n\nint aeron_client_conductor_async_close_counter(\n    aeron_client_conductor_t *conductor,\n    aeron_counter_t *counter,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd)\n{\n    counter->command_base.func = aeron_client_conductor_on_cmd_close_counter;\n    counter->command_base.item = NULL;\n    counter->on_close_complete = on_close_complete;\n    counter->on_close_complete_clientd = on_close_complete_clientd;\n\n    if (conductor->invoker_mode)\n    {\n        return aeron_client_conductor_on_cmd_close_counter(conductor, counter);\n    }\n\n    return aeron_client_conductor_command_offer(conductor->command_queue, counter);\n}\n\nint aeron_client_conductor_async_add_static_counter(\n    aeron_async_add_counter_t **async,\n    aeron_client_conductor_t *conductor,\n    int32_t type_id,\n    const uint8_t *key_buffer,\n    size_t key_buffer_length,\n    const char *label_buffer,\n    size_t label_buffer_length,\n    int64_t registration_id)\n{\n    aeron_async_add_counter_t *cmd = NULL;\n    uint8_t *key_buffer_copy = NULL;\n    char *label_buffer_copy = NULL;\n\n    *async = NULL;\n\n    if (aeron_alloc((void **)&cmd, sizeof(aeron_async_add_counter_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate static counter command\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&key_buffer_copy, key_buffer_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate key buffer\");\n        aeron_free(cmd);\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&label_buffer_copy, label_buffer_length + 1) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate label buffer\");\n        aeron_free(cmd);\n        aeron_free(key_buffer_copy);\n        return -1;\n    }\n\n    if (key_buffer && key_buffer_length > 0)\n    {\n        memcpy(key_buffer_copy, key_buffer, key_buffer_length);\n    }\n\n    if (label_buffer && label_buffer_length > 0)\n    {\n        memcpy(label_buffer_copy, label_buffer, label_buffer_length);\n    }\n\n    label_buffer_copy[label_buffer_length] = '\\0';\n\n    cmd->command_base.func = aeron_client_conductor_on_cmd_add_static_counter;\n    cmd->command_base.item = NULL;\n    cmd->resource.counter = NULL;\n    cmd->error_message = NULL;\n    cmd->error_message_length = 0;\n    cmd->uri = NULL;\n    cmd->counter.key_buffer = key_buffer_copy;\n    cmd->counter.label_buffer = label_buffer_copy;\n    cmd->counter.key_buffer_length = key_buffer_length;\n    cmd->counter.label_buffer_length = label_buffer_length;\n    cmd->counter.type_id = type_id;\n    cmd->counter.registration_id = registration_id;\n    cmd->registration_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    cmd->registration_status = AERON_CLIENT_REGISTRATION_STATUS_AWAITING;\n    cmd->type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER;\n\n    if (conductor->invoker_mode)\n    {\n        if (aeron_client_conductor_on_cmd_add_static_counter(conductor, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n    else\n    {\n        if (aeron_client_conductor_command_offer(conductor->command_queue, cmd) < 0)\n        {\n            goto error;\n        }\n    }\n\n    *async = cmd;\n    return 0;\n\nerror:\n    aeron_free(cmd);\n    aeron_free(key_buffer_copy);\n    aeron_free(label_buffer_copy);\n    return -1;\n}\n\nint aeron_client_conductor_async_add_publication_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_publication_t *publication,\n    const char *uri)\n{\n    union aeron_client_registering_resource_un resource;\n    resource.publication = publication;\n\n    return aeron_client_conductor_async_destination(\n        async, &resource, uri, conductor, aeron_client_conductor_on_cmd_add_destination);\n}\n\nint aeron_client_conductor_async_remove_publication_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_publication_t *publication,\n    const char *uri)\n{\n    union aeron_client_registering_resource_un resource;\n    resource.publication = publication;\n\n    return aeron_client_conductor_async_destination(\n        async, &resource, uri, conductor, aeron_client_conductor_on_cmd_remove_destination);\n}\n\nint aeron_client_conductor_async_remove_publication_destination_by_id(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_publication_t *publication,\n    int64_t destination_registration_id)\n{\n    union aeron_client_registering_resource_un resource;\n    resource.publication = publication;\n\n    return aeron_client_conductor_async_destination_by_id(\n        async,\n        &resource,\n        destination_registration_id,\n        conductor,\n        aeron_client_conductor_on_cmd_remove_destination_by_id);\n}\n\nint aeron_client_conductor_async_add_exclusive_publication_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_exclusive_publication_t *publication,\n    const char *uri)\n{\n    union aeron_client_registering_resource_un resource;\n    resource.exclusive_publication = publication;\n\n    return aeron_client_conductor_async_destination(\n        async, &resource, uri, conductor, aeron_client_conductor_on_cmd_add_destination);\n}\n\nint aeron_client_conductor_async_remove_exclusive_publication_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_exclusive_publication_t *publication,\n    const char *uri)\n{\n    union aeron_client_registering_resource_un resource;\n    resource.exclusive_publication = publication;\n\n    return aeron_client_conductor_async_destination(\n        async, &resource, uri, conductor, aeron_client_conductor_on_cmd_remove_destination);\n}\n\nint aeron_client_conductor_async_remove_exclusive_publication_destination_by_id(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_exclusive_publication_t *publication,\n    int64_t destination_registration_id)\n{\n    union aeron_client_registering_resource_un resource;\n    resource.exclusive_publication = publication;\n\n    return aeron_client_conductor_async_destination_by_id(\n        async,\n        &resource,\n        destination_registration_id,\n        conductor,\n        aeron_client_conductor_on_cmd_remove_destination_by_id);\n}\n\nint aeron_client_conductor_async_add_subscription_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_subscription_t *subscription,\n    const char *uri)\n{\n    union aeron_client_registering_resource_un resource;\n    resource.subscription = subscription;\n\n    return aeron_client_conductor_async_destination(\n        async, &resource, uri, conductor, aeron_client_conductor_on_cmd_add_rcv_destination);\n}\n\nint aeron_client_conductor_async_remove_subscription_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_subscription_t *subscription,\n    const char *uri)\n{\n    union aeron_client_registering_resource_un resource;\n    resource.subscription = subscription;\n\n    return aeron_client_conductor_async_destination(\n        async, &resource, uri, conductor, aeron_client_conductor_on_cmd_remove_rcv_destination);\n}\n\nint aeron_client_conductor_async_get_next_available_session_id(\n    aeron_async_get_next_available_session_id_t **async,\n    aeron_client_conductor_t *conductor,\n    int32_t stream_id)\n{\n    aeron_async_get_next_available_session_id_t *cmd = NULL;\n\n    *async = NULL;\n\n    if (aeron_alloc((void **)&cmd, sizeof(aeron_async_get_next_available_session_id_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_async_get_next_available_session_id_t\");\n        return -1;\n    }\n\n    cmd->command_base.func = aeron_client_conductor_ond_cmd_get_next_available_session_id;\n    cmd->command_base.item = NULL;\n    cmd->error_message = NULL;\n    cmd->error_message_length = 0;\n    cmd->stream_id = stream_id;\n    cmd->registration_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_buffer);\n    cmd->registration_status = AERON_CLIENT_REGISTRATION_STATUS_AWAITING;\n    cmd->type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_NEXT_AVAILABLE_SESSION_ID;\n\n    if (conductor->control_protocol_version >= aeron_semantic_version_compose(1, 0, 0))\n    {\n        if (conductor->invoker_mode)\n        {\n            if (aeron_client_conductor_ond_cmd_get_next_available_session_id(conductor, cmd) < 0)\n            {\n                goto error;\n            }\n        }\n        else\n        {\n            if (aeron_client_conductor_command_offer(conductor->command_queue, cmd) < 0)\n            {\n                goto error;\n            }\n        }\n    }\n    else\n    {\n        cmd->resource.next_session_id = aeron_randomised_int32();\n        cmd->registration_status = AERON_CLIENT_REGISTRATION_STATUS_REGISTERED;\n    }\n\n    *async = cmd;\n\n    return 0;\n\nerror:\n    aeron_free(cmd);\n    return -1;\n}\n\nint aeron_client_conductor_async_handler(aeron_client_conductor_t *conductor, aeron_client_handler_cmd_t *cmd)\n{\n    cmd->command_base.func = aeron_client_conductor_on_cmd_handler;\n    cmd->command_base.item = NULL;\n    cmd->processed = false;\n\n    if (conductor->invoker_mode)\n    {\n        return aeron_client_conductor_on_cmd_handler(conductor, cmd);\n    }\n\n    int64_t deadline_ns = (int64_t)(aeron_nano_clock() + conductor->driver_timeout_ns);\n    while (aeron_client_conductor_command_offer(conductor->command_queue, cmd) < 0)\n    {\n        if (aeron_nano_clock() >= deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s\", \"time out waiting for client conductor thread to process message\");\n            return -1;\n        }\n\n        sched_yield();\n    }\n\n    bool processed;\n    AERON_GET_ACQUIRE(processed, cmd->processed);\n    while (!processed)\n    {\n        if (aeron_nano_clock() >= deadline_ns)\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"%s\", \"time out waiting for client conductor thread to process message\");\n            return -1;\n        }\n\n        sched_yield();\n        AERON_GET_ACQUIRE(processed, cmd->processed);\n    }\n\n    return 0;\n}\n\nint aeron_publication_error_values_copy(aeron_publication_error_values_t **dst, aeron_publication_error_values_t *src)\n{\n    if (NULL == src)\n    {\n        AERON_SET_ERR(-1, \"%s\", \"src must not be NULL\");\n        return -1;\n    }\n\n    if (NULL == dst)\n    {\n        AERON_SET_ERR(-1, \"%s\", \"dst must not be NULL\");\n        return -1;\n    }\n\n    size_t error_values_size = sizeof(*src) + (size_t)src->error_message_length;\n    if (aeron_alloc((void **)dst, error_values_size) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    memcpy((void *)*dst, (void *)src, error_values_size);\n    return 0;\n}\n\nvoid aeron_publication_error_values_delete(aeron_publication_error_values_t *to_delete)\n{\n    aeron_free(to_delete);\n}\n\nint aeron_client_conductor_on_client_timeout(aeron_client_conductor_t *conductor, aeron_client_timeout_t *response)\n{\n    if (response->client_id == conductor->client_id)\n    {\n        char err_buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n\n        conductor->is_terminating = true;\n        aeron_client_conductor_force_close_resources(conductor);\n        snprintf(err_buffer, sizeof(err_buffer) - 1, \"%s\", \"client timeout from driver\");\n        conductor->error_handler(conductor->error_handler_clientd, AERON_CLIENT_ERROR_CLIENT_TIMEOUT, err_buffer);\n    }\n\n    return 0;\n}\n\nint aeron_client_conductor_reject_image(\n    aeron_client_conductor_t *conductor, int64_t image_correlation_id, int64_t position, const char *reason)\n{\n    size_t reason_length = strlen(reason);\n    const size_t command_length = sizeof(aeron_reject_image_command_t) + reason_length + 1;\n\n    int32_t offset = aeron_client_conductor_try_claim_driver_command(\n        conductor, AERON_COMMAND_REJECT_IMAGE, command_length);\n    if (offset < 0)\n    {\n        return -1;\n    }\n\n    uint8_t *ptr = (conductor->to_driver_buffer.buffer + offset);\n    aeron_reject_image_command_t *command = (aeron_reject_image_command_t *)ptr;\n    command->image_correlation_id = image_correlation_id;\n    command->position = position;\n    command->reason_length = (int32_t)reason_length;\n    memcpy(ptr + offsetof(aeron_reject_image_command_t, reason_text), reason, reason_length);\n    command->reason_text[reason_length] = '\\0';\n\n    aeron_mpsc_rb_commit(&conductor->to_driver_buffer, offset);\n\n    return 0;\n}\n\nextern bool aeron_client_conductor_is_closed(aeron_client_conductor_t *conductor);\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_client_conductor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_CLIENT_CONDUCTOR_H\n#define AERON_C_CLIENT_CONDUCTOR_H\n\n#include \"concurrent/aeron_mpsc_concurrent_array_queue.h\"\n#include \"concurrent/aeron_broadcast_receiver.h\"\n#include \"concurrent/aeron_mpsc_rb.h\"\n#include \"command/aeron_control_protocol.h\"\n#include \"aeronc.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"collections/aeron_array_to_ptr_hash_map.h\"\n#include \"collections/aeron_int64_to_ptr_hash_map.h\"\n\n#define AERON_CLIENT_COMMAND_QUEUE_FAIL_THRESHOLD (10)\n#define AERON_CLIENT_COMMAND_RB_FAIL_THRESHOLD (10)\n\ntypedef enum aeron_client_registration_status_en\n{\n    AERON_CLIENT_REGISTRATION_STATUS_AWAITING,\n    AERON_CLIENT_REGISTRATION_STATUS_REGISTERED,\n    AERON_CLIENT_REGISTRATION_STATUS_ERRORED,\n    AERON_CLIENT_REGISTRATION_STATUS_TIMED_OUT\n}\naeron_client_registration_status_t;\n\ntypedef enum aeron_client_managed_resource_type_en\n{\n    AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION,\n    AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION,\n    AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION,\n    AERON_CLIENT_MANAGED_RESOURCE_TYPE_IMAGE,\n    AERON_CLIENT_MANAGED_RESOURCE_TYPE_LOG_BUFFER,\n    AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER,\n    AERON_CLIENT_MANAGED_RESOURCE_TYPE_DESTINATION,\n    AERON_CLIENT_MANAGED_RESOURCE_TYPE_NEXT_AVAILABLE_SESSION_ID,\n}\naeron_client_managed_resource_type_t;\n\ntypedef struct aeron_client_command_base_stct\n{\n    int (*func)(void *clientd, void *command);\n    void *item;\n    aeron_client_managed_resource_type_t type;\n}\naeron_client_command_base_t;\n\ntypedef struct aeron_client_registering_resource_stct\n{\n    aeron_client_command_base_t command_base;\n    union aeron_client_registering_resource_un\n    {\n        aeron_publication_t *publication;\n        aeron_exclusive_publication_t *exclusive_publication;\n        aeron_subscription_t *subscription;\n        aeron_counter_t *counter;\n        aeron_client_command_base_t *base_resource;\n        int32_t next_session_id;\n    }\n    resource;\n\n    aeron_on_available_image_t on_available_image;\n    void *on_available_image_clientd;\n    aeron_on_unavailable_image_t on_unavailable_image;\n    void *on_unavailable_image_clientd;\n\n    char *error_message;\n    char *uri;\n    int64_t destination_registration_id;\n    int64_t registration_id;\n    int64_t registration_deadline_ns;\n    int32_t error_code;\n    int32_t error_message_length;\n    int32_t uri_length;\n    int32_t stream_id;\n    struct aeron_client_registering_counter_stct\n    {\n        const uint8_t *key_buffer;\n        const char *label_buffer;\n        uint64_t key_buffer_length;\n        uint64_t label_buffer_length;\n        int64_t registration_id;\n        int32_t type_id;\n    }\n    counter;\n    volatile aeron_client_registration_status_t registration_status;\n    aeron_client_managed_resource_type_t type;\n}\naeron_client_registering_resource_t;\n\ntypedef struct aeron_client_registering_resource_entry_stct\n{\n    aeron_client_registering_resource_t *resource;\n}\naeron_client_registering_resource_entry_t;\n\ntypedef struct aeron_client_managed_resource_stct\n{\n    union aeron_client_managed_resource_un\n    {\n        aeron_publication_t *publication;\n        aeron_exclusive_publication_t *exclusive_publication;\n        aeron_subscription_t *subscription;\n        aeron_image_t *image;\n        aeron_counter_t *counter;\n        aeron_log_buffer_t *log_buffer;\n    }\n    resource;\n    aeron_client_managed_resource_type_t type;\n    int64_t time_of_last_state_change_ns;\n    int64_t registration_id;\n}\naeron_client_managed_resource_t;\n\ntypedef enum aeron_client_handler_cmd_type_en\n{\n    AERON_CLIENT_HANDLER_ADD_AVAILABLE_COUNTER,\n    AERON_CLIENT_HANDLER_REMOVE_AVAILABLE_COUNTER,\n    AERON_CLIENT_HANDLER_ADD_UNAVAILABLE_COUNTER,\n    AERON_CLIENT_HANDLER_REMOVE_UNAVAILABLE_COUNTER,\n    AERON_CLIENT_HANDLER_ADD_CLOSE_HANDLER,\n    AERON_CLIENT_HANDLER_REMOVE_CLOSE_HANDLER\n}\naeron_client_handler_cmd_type_t;\n\ntypedef struct aeron_client_handler_cmd_stct\n{\n    aeron_client_command_base_t command_base;\n    union aeron_client_handler_un\n    {\n        aeron_on_available_counter_t on_available_counter;\n        aeron_on_unavailable_counter_t on_unavailable_counter;\n        aeron_on_close_client_t on_close_handler;\n    }\n    handler;\n    void *clientd;\n    aeron_client_handler_cmd_type_t type;\n    volatile bool processed;\n}\naeron_client_handler_cmd_t;\n\ntypedef struct aeron_client_conductor_stct\n{\n    aeron_broadcast_receiver_t to_client_buffer;\n    aeron_mpsc_rb_t to_driver_buffer;\n    aeron_counters_reader_t counters_reader;\n\n    aeron_int64_to_ptr_hash_map_t log_buffer_by_id_map;\n    aeron_int64_to_ptr_hash_map_t resource_by_id_map;\n    aeron_array_to_ptr_hash_map_t image_by_key_map;\n\n    struct available_counter_handlers_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_on_available_counter_pair_t *array;\n    }\n    available_counter_handlers;\n\n    struct unavailable_counter_handlers_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_on_unavailable_counter_pair_t *array;\n    }\n    unavailable_counter_handlers;\n\n    struct close_handlers_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_on_close_client_pair_t *array;\n    }\n    close_handlers;\n\n    struct lingering_resources_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_client_managed_resource_t *array;\n    }\n    lingering_resources;\n\n    struct registering_resources_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_client_registering_resource_entry_t *array;\n    }\n    registering_resources;\n\n    struct heartbeat_timestamp_stct\n    {\n        int64_t *addr;\n        int32_t counter_id;\n    }\n    heartbeat_timestamp;\n\n    aeron_mpsc_concurrent_array_queue_t *command_queue;\n\n    uint64_t driver_timeout_ms;\n    uint64_t driver_timeout_ns;\n    uint64_t inter_service_timeout_ns;\n    uint64_t keepalive_interval_ns;\n    uint64_t resource_linger_duration_ns;\n    uint64_t idle_sleep_duration_ns;\n\n    int64_t time_of_last_service_ns;\n    int64_t time_of_last_keepalive_ns;\n\n    int32_t control_protocol_version;\n\n    int64_t client_id;\n    const char* client_name;\n\n    aeron_error_handler_t error_handler;\n    void *error_handler_clientd;\n\n    aeron_publication_error_frame_handler_t error_frame_handler;\n    void *error_frame_handler_clientd;\n\n    aeron_on_new_publication_t on_new_publication;\n    void *on_new_publication_clientd;\n\n    aeron_on_new_publication_t on_new_exclusive_publication;\n    void *on_new_exclusive_publication_clientd;\n\n    aeron_on_new_subscription_t on_new_subscription;\n    void *on_new_subscription_clientd;\n\n    aeron_clock_func_t nano_clock;\n    aeron_clock_func_t epoch_clock;\n    bool invoker_mode;\n    bool pre_touch;\n    bool is_terminating;\n    volatile bool is_closed;\n}\naeron_client_conductor_t;\n\nint aeron_client_conductor_init(aeron_client_conductor_t *conductor, aeron_context_t *context);\nint aeron_client_conductor_do_work(aeron_client_conductor_t *conductor);\nvoid aeron_client_conductor_on_close(aeron_client_conductor_t *conductor);\n\nint aeron_client_conductor_async_add_publication(\n    aeron_async_add_publication_t **async, aeron_client_conductor_t *conductor, const char *uri, int32_t stream_id);\n\nint aeron_client_conductor_async_close_publication(\n    aeron_client_conductor_t *conductor,\n    aeron_publication_t *publication,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd);\n\nint aeron_client_conductor_async_add_exclusive_publication(\n    aeron_async_add_exclusive_publication_t **async,\n    aeron_client_conductor_t *conductor,\n    const char *uri,\n    int32_t stream_id);\n\nint aeron_client_conductor_async_close_exclusive_publication(\n    aeron_client_conductor_t *conductor,\n    aeron_exclusive_publication_t *publication,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd);\n\nint aeron_client_conductor_async_add_subscription(\n    aeron_async_add_subscription_t **async,\n    aeron_client_conductor_t *conductor,\n    const char *uri,\n    int32_t stream_id,\n    aeron_on_available_image_t on_available_image_handler,\n    void *on_available_image_clientd,\n    aeron_on_unavailable_image_t on_unavailable_image_handler,\n    void *on_unavailable_image_clientd);\n\nint aeron_client_conductor_async_close_subscription(\n    aeron_client_conductor_t *conductor,\n    aeron_subscription_t *subscription,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd);\n\nint aeron_client_conductor_async_add_counter(\n    aeron_async_add_counter_t **async,\n    aeron_client_conductor_t *conductor,\n    int32_t type_id,\n    const uint8_t *key_buffer,\n    size_t key_buffer_length,\n    const char *label_buffer,\n    size_t label_buffer_length);\n\nint aeron_client_conductor_async_close_counter(\n    aeron_client_conductor_t *conductor,\n    aeron_counter_t *counter,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd);\n\nint aeron_client_conductor_async_add_static_counter(\n    aeron_async_add_counter_t **async,\n    aeron_client_conductor_t *conductor,\n    int32_t type_id,\n    const uint8_t *key_buffer,\n    size_t key_buffer_length,\n    const char *label_buffer,\n    size_t label_buffer_length,\n    int64_t registration_id);\n\nint aeron_client_conductor_async_add_publication_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_publication_t *publication,\n    const char *uri);\n\nint aeron_client_conductor_async_remove_publication_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_publication_t *publication,\n    const char *uri);\n\nint aeron_client_conductor_async_remove_publication_destination_by_id(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_publication_t *publication,\n    int64_t destination_registration_id);\n\nint aeron_client_conductor_async_add_exclusive_publication_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_exclusive_publication_t *publication,\n    const char *uri);\n\nint aeron_client_conductor_async_remove_exclusive_publication_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_exclusive_publication_t *publication,\n    const char *uri);\n\nint aeron_client_conductor_async_remove_exclusive_publication_destination_by_id(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_exclusive_publication_t *publication,\n    int64_t destination_registration_id);\n\nint aeron_client_conductor_async_add_subscription_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_subscription_t *subscription,\n    const char *uri);\n\nint aeron_client_conductor_async_remove_subscription_destination(\n    aeron_async_destination_t **async,\n    aeron_client_conductor_t *conductor,\n    aeron_subscription_t *subscription,\n    const char *uri);\n\nint aeron_client_conductor_async_get_next_available_session_id(\n    aeron_async_get_next_available_session_id_t **async,\n    aeron_client_conductor_t *conductor,\n    int32_t stream_id);\n\nint aeron_client_conductor_async_handler(aeron_client_conductor_t *conductor, aeron_client_handler_cmd_t *cmd);\n\nint aeron_client_conductor_on_client_timeout(aeron_client_conductor_t *conductor, aeron_client_timeout_t *response);\n\nint aeron_client_conductor_reject_image(\n    aeron_client_conductor_t *conductor,\n    int64_t image_correlation_id,\n    int64_t position,\n    const char *reason);\n\ninline bool aeron_client_conductor_is_closed(aeron_client_conductor_t *conductor)\n{\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, conductor->is_closed);\n    return is_closed;\n}\n\n#endif //AERON_C_CLIENT_CONDUCTOR_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_cnc.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <inttypes.h>\n\n#include \"aeron_alloc.h\"\n#include \"concurrent/aeron_thread.h\"\n#include \"concurrent/aeron_distinct_error_log.h\"\n#include \"concurrent/aeron_mpsc_rb.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_fileutil.h\"\n#include \"reports/aeron_loss_reporter.h\"\n#include \"aeron_cnc_file_descriptor.h\"\n\ntypedef struct aeron_cnc_stct\n{\n    aeron_mapped_file_t cnc_mmap;\n    aeron_cnc_metadata_t *metadata;\n    aeron_counters_reader_t counters_reader;\n    char base_path[AERON_MAX_PATH];\n    char filename[AERON_MAX_PATH];\n}\naeron_cnc_t;\n\nint aeron_cnc_init(aeron_cnc_t **aeron_cnc, const char *base_path, int64_t timeout_ms)\n{\n    aeron_cnc_t *_aeron_cnc;\n\n    if (aeron_alloc((void **)&_aeron_cnc, sizeof(aeron_cnc_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"Failed to allocate aeron_cnc, cnc filename: %s\", base_path);\n        return AERON_CNC_LOAD_FAILED;\n    }\n\n    strncpy(_aeron_cnc->base_path, base_path, sizeof(_aeron_cnc->base_path) - 1);\n    aeron_cnc_resolve_filename(base_path, _aeron_cnc->filename, sizeof(_aeron_cnc->filename));\n\n    int64_t deadline_ms = aeron_epoch_clock() + timeout_ms;\n    while (true)\n    {\n        aeron_cnc_load_result_t result = aeron_cnc_map_file_and_load_metadata(\n            base_path, &_aeron_cnc->cnc_mmap, &_aeron_cnc->metadata);\n\n        if (AERON_CNC_LOAD_SUCCESS == result)\n        {\n            break;\n        }\n        else if (AERON_CNC_LOAD_FAILED == result)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to load aeron_cnc_t\");\n            goto error;\n        }\n        else\n        {\n            if (deadline_ms <= aeron_epoch_clock())\n            {\n                AERON_SET_ERR(\n                    AERON_CLIENT_ERROR_DRIVER_TIMEOUT,\n                    \"Timed out waiting for CnC file to become available after %\" PRId64 \"ms\",\n                    timeout_ms);\n                goto error;\n            }\n\n            aeron_micro_sleep(16 * 1000);\n        }\n    }\n\n    aeron_counters_reader_init(\n        &_aeron_cnc->counters_reader,\n        aeron_cnc_counters_metadata_buffer(_aeron_cnc->metadata),\n        _aeron_cnc->metadata->counter_metadata_buffer_length,\n        aeron_cnc_counters_values_buffer(_aeron_cnc->metadata),\n        _aeron_cnc->metadata->counter_values_buffer_length);\n\n    *aeron_cnc = _aeron_cnc;\n    return 0;\n\nerror:\n    aeron_free(_aeron_cnc);\n    return -1;\n}\n\nconst char *aeron_cnc_filename(aeron_cnc_t *aeron_cnc)\n{\n    return aeron_cnc->filename;\n}\n\nint aeron_cnc_constants(aeron_cnc_t *aeron_cnc, aeron_cnc_constants_t *constants)\n{\n    if (NULL == aeron_cnc || NULL == constants)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, aeron_cnc: %s, constants: %s\",\n            AERON_NULL_STR(aeron_cnc),\n            AERON_NULL_STR(constants));\n        return -1;\n    }\n\n    // A memcpy would be faster, but this allows the metadata to evolve independently of\n    // the constants. This is also not a critical performance path.\n    constants->cnc_version = aeron_cnc->metadata->cnc_version;\n    constants->to_driver_buffer_length = aeron_cnc->metadata->to_driver_buffer_length;\n    constants->to_clients_buffer_length = aeron_cnc->metadata->to_clients_buffer_length;\n    constants->counter_metadata_buffer_length = aeron_cnc->metadata->counter_metadata_buffer_length;\n    constants->counter_values_buffer_length = aeron_cnc->metadata->counter_values_buffer_length;\n    constants->error_log_buffer_length = aeron_cnc->metadata->error_log_buffer_length;\n    constants->client_liveness_timeout = aeron_cnc->metadata->client_liveness_timeout;\n    constants->start_timestamp = aeron_cnc->metadata->start_timestamp;\n    constants->pid = aeron_cnc->metadata->pid;\n    constants->file_page_size = aeron_cnc->metadata->file_page_size;\n\n    return 0;\n}\n\nint64_t aeron_cnc_to_driver_heartbeat(aeron_cnc_t *aeron_cnc)\n{\n    uint8_t *to_driver_buffer = aeron_cnc_to_driver_buffer(aeron_cnc->metadata);\n    aeron_mpsc_rb_t to_driver_rb = { 0 };\n    if (aeron_mpsc_rb_init(&to_driver_rb, to_driver_buffer, aeron_cnc->metadata->to_driver_buffer_length) < 0)\n    {\n        return -1;\n    }\n\n    return aeron_mpsc_rb_consumer_heartbeat_time_value(&to_driver_rb);\n}\n\nsize_t aeron_cnc_error_log_read(\n    aeron_cnc_t *aeron_cnc,\n    aeron_error_log_reader_func_t callback,\n    void *clientd,\n    int64_t since_timestamp)\n{\n    uint8_t *error_buffer = aeron_cnc_error_log_buffer(aeron_cnc->metadata);\n\n    return aeron_error_log_read(\n        error_buffer, aeron_cnc->metadata->error_log_buffer_length, callback, clientd, since_timestamp);\n}\n\naeron_counters_reader_t *aeron_cnc_counters_reader(aeron_cnc_t *aeron_cnc)\n{\n    return &aeron_cnc->counters_reader;\n}\n\nint aeron_cnc_loss_reporter_read(\n    aeron_cnc_t *aeron_cnc,\n    aeron_loss_reporter_read_entry_func_t entry_func,\n    void *clientd)\n{\n    char loss_report_filename[AERON_MAX_PATH];\n\n    if (aeron_loss_reporter_resolve_filename(\n        aeron_cnc->base_path, loss_report_filename, sizeof(loss_report_filename)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to resolve loss report file name\");\n        return -1;\n    }\n\n    aeron_mapped_file_t loss_mmap;\n    if (aeron_map_existing_file(&loss_mmap, loss_report_filename) < 0) \n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to map loss report\");\n        return -1;\n    }\n\n    int result = (int)aeron_loss_reporter_read(loss_mmap.addr, loss_mmap.length, entry_func, clientd);\n\n    aeron_unmap(&loss_mmap);\n\n    return result;\n}\n\nvoid aeron_cnc_close(aeron_cnc_t *aeron_cnc)\n{\n    if (NULL != aeron_cnc)\n    {\n        aeron_unmap(&aeron_cnc->cnc_mmap);\n        aeron_free(aeron_cnc);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_cnc_file_descriptor.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#ifdef HAVE_BSDSTDLIB_H\n#include <bsd/stdlib.h>\n#endif\n#endif\n\n#include <stdint.h>\n#include <errno.h>\n\n#include \"aeron_common.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_cnc_file_descriptor.h\"\n\nint32_t aeron_cnc_version_volatile(aeron_cnc_metadata_t *metadata)\n{\n    int32_t cnc_version;\n    AERON_GET_ACQUIRE(cnc_version, metadata->cnc_version);\n    return cnc_version;\n}\n\nstatic bool aeron_cnc_map_file_is_retry_err(int err)\n{\n#if defined(AERON_COMPILER_MSVC)\n    return\n        ERROR_FILE_NOT_FOUND == err ||\n        ERROR_PATH_NOT_FOUND == err ||\n        ERROR_ACCESS_DENIED == err ||\n        ERROR_SHARING_VIOLATION == err;\n#else\n    return\n        ENOENT == err ||\n        EACCES == err;\n#endif\n}\n\naeron_cnc_load_result_t aeron_cnc_map_file_and_load_metadata(\n    const char *dir, aeron_mapped_file_t *cnc_mmap, aeron_cnc_metadata_t **metadata)\n{\n    if (NULL == metadata)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"CnC metadata pointer must not be NULL\");\n        return AERON_CNC_LOAD_FAILED;\n    }\n\n    char filename[AERON_MAX_PATH];\n    if (aeron_cnc_resolve_filename(dir, filename, sizeof(filename)) < 0)\n    {\n        AERON_APPEND_ERR(\"Failed to resolve CnC file path: dir=%s, filename=%s\", dir, filename);\n        return AERON_CNC_LOAD_FAILED;\n    }\n\n    if (aeron_file_length(filename) <= (int64_t)AERON_CNC_VERSION_AND_META_DATA_LENGTH)\n    {\n        return AERON_CNC_LOAD_AWAIT_FILE;\n    }\n\n    if (aeron_map_existing_file(cnc_mmap, filename) < 0)\n    {\n        if (aeron_cnc_map_file_is_retry_err(aeron_errcode()))\n        {\n            aeron_err_clear();\n            return AERON_CNC_LOAD_AWAIT_FILE;\n        }\n\n        AERON_APPEND_ERR(\"CnC file could not be memory mapped: %s\", filename);\n        return AERON_CNC_LOAD_FAILED;\n    }\n\n    if (cnc_mmap->length <= (int64_t)AERON_CNC_VERSION_AND_META_DATA_LENGTH)\n    {\n        aeron_unmap(cnc_mmap);\n        return AERON_CNC_LOAD_AWAIT_MMAP;\n    }\n\n    aeron_cnc_metadata_t *_metadata = (aeron_cnc_metadata_t *)cnc_mmap->addr;\n    int32_t cnc_version = aeron_cnc_version_volatile(_metadata);\n\n    if (0 == cnc_version)\n    {\n        aeron_unmap(cnc_mmap);\n        return AERON_CNC_LOAD_AWAIT_VERSION;\n    }\n\n    if (aeron_semantic_version_major(AERON_CNC_VERSION) != aeron_semantic_version_major(cnc_version))\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"CnC version not compatible: app=%d.%d.%d file=%d.%d.%d\",\n            (int)aeron_semantic_version_major(AERON_CNC_VERSION),\n            (int)aeron_semantic_version_minor(AERON_CNC_VERSION),\n            (int)aeron_semantic_version_patch(AERON_CNC_VERSION),\n            (int)aeron_semantic_version_major(cnc_version),\n            (int)aeron_semantic_version_minor(cnc_version),\n            (int)aeron_semantic_version_patch(cnc_version));\n        aeron_unmap(cnc_mmap);\n\n        return AERON_CNC_LOAD_FAILED;\n    }\n\n    if (!aeron_cnc_is_file_length_sufficient(cnc_mmap))\n    {\n        aeron_unmap(cnc_mmap);\n        return AERON_CNC_LOAD_AWAIT_CNC_DATA;\n    }\n\n    *metadata = _metadata;\n\n    return AERON_CNC_LOAD_SUCCESS;\n}\n\nint aeron_cnc_resolve_filename(const char *directory, char *filename_buffer, size_t filename_buffer_length)\n{\n    return aeron_file_resolve(directory, AERON_CNC_FILE, filename_buffer, filename_buffer_length);\n}\n\nextern uint8_t *aeron_cnc_to_driver_buffer(aeron_cnc_metadata_t *metadata);\nextern uint8_t *aeron_cnc_to_clients_buffer(aeron_cnc_metadata_t *metadata);\nextern uint8_t *aeron_cnc_counters_metadata_buffer(aeron_cnc_metadata_t *metadata);\nextern uint8_t *aeron_cnc_counters_values_buffer(aeron_cnc_metadata_t *metadata);\nextern uint8_t *aeron_cnc_error_log_buffer(aeron_cnc_metadata_t *metadata);\nextern size_t aeron_cnc_computed_length(size_t total_length_of_buffers, size_t alignment);\nextern bool aeron_cnc_is_file_length_sufficient(aeron_mapped_file_t *cnc_mmap);\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_cnc_file_descriptor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_CNC_FILE_DESCRIPTOR_H\n#define AERON_C_CNC_FILE_DESCRIPTOR_H\n\n#include \"aeronc.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_fileutil.h\"\n\n#define AERON_CNC_FILE \"cnc.dat\"\n#define AERON_CNC_VERSION (aeron_semantic_version_compose(0, 2, 0))\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_cnc_metadata_stct\n{\n    volatile int32_t cnc_version;\n    int32_t to_driver_buffer_length;\n    int32_t to_clients_buffer_length;\n    int32_t counter_metadata_buffer_length;\n    int32_t counter_values_buffer_length;\n    int32_t error_log_buffer_length;\n    int64_t client_liveness_timeout;\n    int64_t start_timestamp;\n    int64_t pid;\n    int32_t file_page_size;\n}\naeron_cnc_metadata_t;\n#pragma pack(pop)\n\ntypedef enum aeron_cnc_load_result_stct\n{\n    AERON_CNC_LOAD_FAILED = -1,\n    AERON_CNC_LOAD_SUCCESS = 0,\n    AERON_CNC_LOAD_AWAIT_FILE = 1,\n    AERON_CNC_LOAD_AWAIT_MMAP = 2,\n    AERON_CNC_LOAD_AWAIT_VERSION = 3,\n    AERON_CNC_LOAD_AWAIT_CNC_DATA = 4,\n}\naeron_cnc_load_result_t;\n\n#define AERON_CNC_VERSION_AND_META_DATA_LENGTH UINT32_C(AERON_CACHE_LINE_LENGTH * 2)\n\ninline uint8_t *aeron_cnc_to_driver_buffer(aeron_cnc_metadata_t *metadata)\n{\n    return (uint8_t *)metadata + AERON_CNC_VERSION_AND_META_DATA_LENGTH;\n}\n\ninline uint8_t *aeron_cnc_to_clients_buffer(aeron_cnc_metadata_t *metadata)\n{\n    return (uint8_t *)metadata + AERON_CNC_VERSION_AND_META_DATA_LENGTH + metadata->to_driver_buffer_length;\n}\n\ninline uint8_t *aeron_cnc_counters_metadata_buffer(aeron_cnc_metadata_t *metadata)\n{\n    return (uint8_t *)metadata + AERON_CNC_VERSION_AND_META_DATA_LENGTH +\n        metadata->to_driver_buffer_length +\n        metadata->to_clients_buffer_length;\n}\n\ninline uint8_t *aeron_cnc_counters_values_buffer(aeron_cnc_metadata_t *metadata)\n{\n    return (uint8_t *)metadata + AERON_CNC_VERSION_AND_META_DATA_LENGTH +\n        metadata->to_driver_buffer_length +\n        metadata->to_clients_buffer_length +\n        metadata->counter_metadata_buffer_length;\n}\n\ninline uint8_t *aeron_cnc_error_log_buffer(aeron_cnc_metadata_t *metadata)\n{\n    return (uint8_t *)metadata + AERON_CNC_VERSION_AND_META_DATA_LENGTH +\n        metadata->to_driver_buffer_length +\n        metadata->to_clients_buffer_length +\n        metadata->counter_metadata_buffer_length +\n        metadata->counter_values_buffer_length;\n}\n\ninline size_t aeron_cnc_computed_length(size_t total_length_of_buffers, size_t alignment)\n{\n    return AERON_ALIGN(AERON_CNC_VERSION_AND_META_DATA_LENGTH + total_length_of_buffers, alignment);\n}\n\ninline bool aeron_cnc_is_file_length_sufficient(aeron_mapped_file_t *cnc_mmap)\n{\n    if (cnc_mmap->length < AERON_CNC_VERSION_AND_META_DATA_LENGTH)\n    {\n        return false;\n    }\n\n    aeron_cnc_metadata_t *metadata = (aeron_cnc_metadata_t *)cnc_mmap->addr;\n    size_t cnc_length = AERON_CNC_VERSION_AND_META_DATA_LENGTH +\n        (size_t)metadata->to_driver_buffer_length +\n        (size_t)metadata->to_clients_buffer_length +\n        (size_t)metadata->counter_metadata_buffer_length +\n        (size_t)metadata->counter_values_buffer_length +\n        (size_t)metadata->error_log_buffer_length;\n\n    return cnc_mmap->length >= cnc_length;\n}\n\nint32_t aeron_cnc_version_volatile(aeron_cnc_metadata_t *metadata);\n\naeron_cnc_load_result_t aeron_cnc_map_file_and_load_metadata(\n    const char *dir, aeron_mapped_file_t *mapped_file, aeron_cnc_metadata_t **metadata);\n\nint aeron_cnc_resolve_filename(const char *directory, char *filename_buffer, size_t filename_buffer_length);\n\n#endif //AERON_C_CNC_FILE_DESCRIPTOR_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_common.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_COMMON_H\n#define AERON_COMMON_H\n\n#include <stdint.h>\n#include <stdbool.h>\n#include <stddef.h>\n\n#define AERON_MAX_PATH (4096) // max path name including NUL character\n\ntypedef void (*aeron_idle_strategy_func_t)(void *state, int work_count);\ntypedef int (*aeron_idle_strategy_init_func_t)(void **state, const char *env_var, const char *init_args);\n\nint32_t aeron_semantic_version_compose(uint8_t major, uint8_t minor, uint8_t patch);\n\nuint8_t aeron_semantic_version_major(int32_t version);\n\nuint8_t aeron_semantic_version_minor(int32_t version);\n\nuint8_t aeron_semantic_version_patch(int32_t version);\n\ntypedef void (*aeron_fptr_t)(void);\n\n#endif //AERON_COMMON_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_context.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <inttypes.h>\n\n#include \"concurrent/aeron_mpsc_rb.h\"\n#include \"util/aeron_fileutil.h\"\n#include \"aeron_cnc_file_descriptor.h\"\n#include \"command/aeron_control_protocol.h\"\n\n#include \"aeron_alloc.h\"\n#include \"aeron_context.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"util/aeron_strutil.h\"\n\n#define AERON_CONTEXT_USE_CONDUCTOR_AGENT_INVOKER_DEFAULT (false)\n#define AERON_CONTEXT_DRIVER_TIMEOUT_MS_DEFAULT (10 * 1000L)\n#define AERON_CONTEXT_KEEPALIVE_INTERVAL_NS_DEFAULT (500 * 1000 * 1000LL)\n#define AERON_CONTEXT_RESOURCE_LINGER_DURATION_NS_DEFAULT (3 * 1000 * 1000 * 1000LL)\n#define AERON_CONTEXT_IDLE_SLEEP_DURATION_NS_DEFAULT (16 * 1000 * 1000LL)\n#define AERON_CONTEXT_PRE_TOUCH_MAPPED_MEMORY_DEFAULT (false)\n\nvoid aeron_default_error_handler(void *clientd, int errcode, const char *message)\n{\n    fprintf(stderr, \"ERROR: (%d): %s\\n\", errcode, message);\n    exit(EXIT_FAILURE);\n}\n\nvoid aeron_default_error_frame_handler(void *clientd, aeron_publication_error_values_t *message)\n{\n}\n\nint aeron_context_init(aeron_context_t **context)\n{\n    aeron_context_t *_context = NULL;\n\n    if (NULL == context)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"aeron_context_init(NULL)\");\n        goto error;\n    }\n\n    if (aeron_alloc((void **)&_context, sizeof(aeron_context_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate aeron_context\");\n        goto error;\n    }\n\n    if (aeron_mpsc_concurrent_array_queue_init(&_context->command_queue, AERON_CLIENT_COMMAND_QUEUE_CAPACITY) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init command_queue\");\n        goto error;\n    }\n\n    if (aeron_default_path(_context->aeron_dir, sizeof(_context->aeron_dir)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to resolve default aeron directory path\");\n        goto error;\n    }\n\n    _context->client_name[0] = '\\0';\n    _context->error_handler = aeron_default_error_handler;\n    _context->error_handler_clientd = NULL;\n    _context->error_frame_handler = aeron_default_error_frame_handler;\n    _context->on_new_publication = NULL;\n    _context->on_new_publication_clientd = NULL;\n    _context->on_new_exclusive_publication = NULL;\n    _context->on_new_exclusive_publication_clientd = NULL;\n    _context->on_new_subscription = NULL;\n    _context->on_new_subscription_clientd = NULL;\n    _context->on_available_counter = NULL;\n    _context->on_available_counter_clientd = NULL;\n    _context->on_unavailable_counter = NULL;\n    _context->on_unavailable_counter_clientd = NULL;\n    _context->on_close_client = NULL;\n    _context->on_close_client_clientd = NULL;\n\n    _context->idle_strategy_name = NULL;\n    _context->idle_strategy_init_args = NULL;\n    _context->idle_strategy_state = NULL;\n    _context->idle_strategy_func = NULL;\n\n    _context->use_conductor_agent_invoker = AERON_CONTEXT_USE_CONDUCTOR_AGENT_INVOKER_DEFAULT;\n    _context->agent_on_start_func = NULL;\n    _context->agent_on_start_state = NULL;\n\n    _context->driver_timeout_ms = AERON_CONTEXT_DRIVER_TIMEOUT_MS_DEFAULT;\n    _context->keepalive_interval_ns = AERON_CONTEXT_KEEPALIVE_INTERVAL_NS_DEFAULT;\n    _context->resource_linger_duration_ns = AERON_CONTEXT_RESOURCE_LINGER_DURATION_NS_DEFAULT;\n    _context->idle_sleep_duration_ns = AERON_CONTEXT_IDLE_SLEEP_DURATION_NS_DEFAULT;\n\n    _context->epoch_clock = aeron_epoch_clock;\n    _context->nano_clock = aeron_nano_clock;\n\n    char *value = NULL;\n\n    if ((value = getenv(AERON_DIR_ENV_VAR)))\n    {\n        if (aeron_context_set_dir(_context, value) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    if ((value = getenv(AERON_CLIENT_NAME_ENV_VAR)))\n    {\n        if (aeron_context_set_client_name(_context, value) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n\n    if ((value = getenv(AERON_DRIVER_TIMEOUT_ENV_VAR)))\n    {\n        errno = 0;\n        char *end_ptr = NULL;\n        uint64_t result = strtoull(value, &end_ptr, 0);\n\n        if ((0 == result && 0 != errno) || '\\0' != *end_ptr)\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse driver timeout: %s=%s\", AERON_DRIVER_TIMEOUT_ENV_VAR, value);\n            goto error;\n        }\n\n        _context->driver_timeout_ms = result;\n    }\n\n    if ((value = getenv(AERON_CLIENT_RESOURCE_LINGER_DURATION_ENV_VAR)))\n    {\n        uint64_t result;\n        if (aeron_parse_duration_ns(value, &result) < 0)\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse: %s=%s\", AERON_CLIENT_RESOURCE_LINGER_DURATION_ENV_VAR, value);\n            goto error;\n        }\n\n        _context->resource_linger_duration_ns = result;\n    }\n\n    if ((value = getenv(AERON_CLIENT_IDLE_SLEEP_DURATION_ENV_VAR)))\n    {\n        uint64_t result;\n        if (aeron_parse_duration_ns(value, &result) < 0)\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse: %s=%s\", AERON_CLIENT_IDLE_SLEEP_DURATION_ENV_VAR, value);\n            goto error;\n        }\n\n        _context->idle_sleep_duration_ns = result;\n    }\n\n    _context->pre_touch_mapped_memory = aeron_parse_bool(\n        getenv(AERON_CLIENT_PRE_TOUCH_MAPPED_MEMORY_ENV_VAR), AERON_CONTEXT_PRE_TOUCH_MAPPED_MEMORY_DEFAULT);\n\n    _context->idle_strategy_name = aeron_strndup(\"sleep-ns\", AERON_MAX_PATH);\n\n    size_t init_args_size = 21; // 20 (UINT64_MAX) + 1\n    if (aeron_alloc((void**)&_context->idle_strategy_init_args, init_args_size) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    int rc = snprintf(_context->idle_strategy_init_args, init_args_size, \"%\" PRIu64, _context->idle_sleep_duration_ns);\n    if (rc < 0 || rc >= (int)init_args_size)\n    {\n        AERON_SET_ERR(EINVAL, \"error formatting idle_strategy_init_args=%d\", rc);\n        goto error;\n    }\n\n    if ((_context->idle_strategy_func = aeron_idle_strategy_load(\n        _context->idle_strategy_name,\n        &_context->idle_strategy_state,\n        NULL,\n        _context->idle_strategy_init_args)) == NULL)\n    {\n        goto error;\n    }\n\n    *context = _context;\n    return 0;\n\nerror:\n    aeron_context_close(_context);\n    return -1;\n}\n\nint aeron_context_close(aeron_context_t *context)\n{\n    if (NULL != context)\n    {\n        aeron_unmap(&context->cnc_map);\n\n        aeron_mpsc_concurrent_array_queue_close(&context->command_queue);\n\n        aeron_free(context->idle_strategy_state);\n        aeron_free(context->idle_strategy_init_args);\n        aeron_free((void*)context->idle_strategy_name);\n        aeron_free(context);\n    }\n\n    return 0;\n}\n\n#define AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(r, a) \\\ndo \\\n{ \\\n    if (NULL == (a)) \\\n    { \\\n        AERON_SET_ERR(EINVAL, \"%s is null\", \"##a\"); \\\n        return (r); \\\n    } \\\n} \\\nwhile (false) \\\n\n\nint aeron_context_set_dir(aeron_context_t *context, const char *value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    snprintf(context->aeron_dir, sizeof(context->aeron_dir), \"%s\", value);\n    return 0;\n}\n\nconst char *aeron_context_get_dir(aeron_context_t *context)\n{\n    return NULL != context ? context->aeron_dir : NULL;\n}\n\nint aeron_context_set_client_name(aeron_context_t *context, const char *value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    size_t copy_length = 0;\n    if (!aeron_str_length(value, AERON_COUNTER_MAX_CLIENT_NAME_LENGTH + 1, &copy_length))\n    {\n        AERON_SET_ERR(EINVAL, \"client_name length must <= %d\", AERON_COUNTER_MAX_CLIENT_NAME_LENGTH);\n        return -1;\n    }\n\n    memcpy(context->client_name, value, copy_length);\n    context->client_name[copy_length] = '\\0';\n    return 0;\n}\n\nconst char *aeron_context_get_client_name(aeron_context_t *context)\n{\n    return NULL != context ? context->client_name : \"\";\n}\n\nint aeron_context_set_driver_timeout_ms(aeron_context_t *context, uint64_t value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->driver_timeout_ms = value;\n    return 0;\n}\n\nuint64_t aeron_context_get_driver_timeout_ms(aeron_context_t *context)\n{\n    return NULL == context ? AERON_CONTEXT_DRIVER_TIMEOUT_MS_DEFAULT : context->driver_timeout_ms;\n}\n\nint aeron_context_set_keepalive_interval_ns(aeron_context_t *context, uint64_t value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->keepalive_interval_ns = value;\n    return 0;\n}\n\nuint64_t aeron_context_get_keepalive_interval_ns(aeron_context_t *context)\n{\n    return NULL == context ? AERON_CONTEXT_KEEPALIVE_INTERVAL_NS_DEFAULT : context->keepalive_interval_ns;\n}\n\nint aeron_context_set_resource_linger_duration_ns(aeron_context_t *context, uint64_t value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->resource_linger_duration_ns = value;\n    return 0;\n}\n\nuint64_t aeron_context_get_resource_linger_duration_ns(aeron_context_t *context)\n{\n    return NULL == context ? AERON_CONTEXT_RESOURCE_LINGER_DURATION_NS_DEFAULT : context->resource_linger_duration_ns;\n}\n\nint aeron_context_set_idle_sleep_duration_ns(aeron_context_t *context, uint64_t value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->idle_sleep_duration_ns = value;\n    return 0;\n}\n\nuint64_t aeron_context_get_idle_sleep_duration_ns(aeron_context_t *context)\n{\n    return NULL == context ? AERON_CONTEXT_IDLE_SLEEP_DURATION_NS_DEFAULT : context->idle_sleep_duration_ns;\n}\n\nint aeron_context_set_idle_strategy_init_args(aeron_context_t *context, const char *value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    aeron_free(context->idle_strategy_init_args);\n    context->idle_strategy_init_args = NULL == value ? NULL : aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_context_get_idle_strategy_init_args(aeron_context_t *context)\n{\n    return NULL != context ? context->idle_strategy_init_args : NULL;\n}\n\nint aeron_context_set_idle_strategy(aeron_context_t *context, const char *value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    aeron_free(context->idle_strategy_state);\n    aeron_free((void *)context->idle_strategy_name);\n\n    context->idle_strategy_state = NULL;\n    context->idle_strategy_name = NULL;\n\n    if ((context->idle_strategy_func = aeron_idle_strategy_load(\n        value,\n        &context->idle_strategy_state,\n        NULL,\n        context->idle_strategy_init_args)) == NULL)\n    {\n        return -1;\n    }\n\n    context->idle_strategy_name = aeron_strndup(value, AERON_MAX_PATH);\n    return 0;\n}\n\nconst char *aeron_context_get_idle_strategy(aeron_context_t *context)\n{\n    return NULL != context ? context->idle_strategy_name : \"sleep-ns\";\n}\n\nint aeron_context_set_pre_touch_mapped_memory(aeron_context_t *context, bool value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->pre_touch_mapped_memory = value;\n    return 0;\n}\n\nbool aeron_context_get_pre_touch_mapped_memory(aeron_context_t *context)\n{\n    return NULL != context ? context->pre_touch_mapped_memory : AERON_CONTEXT_PRE_TOUCH_MAPPED_MEMORY_DEFAULT;\n}\n\nint aeron_context_set_error_handler(aeron_context_t *context, aeron_error_handler_t handler, void *clientd)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->error_handler = handler;\n    context->error_handler_clientd = clientd;\n    return 0;\n}\n\naeron_error_handler_t aeron_context_get_error_handler(aeron_context_t *context)\n{\n    return NULL != context ? context->error_handler : aeron_default_error_handler;\n}\n\nvoid *aeron_context_get_error_handler_clientd(aeron_context_t *context)\n{\n    return NULL != context ? context->error_handler_clientd : NULL;\n}\n\nint aeron_context_set_publication_error_frame_handler(aeron_context_t *context, aeron_publication_error_frame_handler_t handler, void *clientd)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->error_frame_handler = handler;\n    context->error_frame_handler_clientd = clientd;\n    return 0;\n}\n\naeron_publication_error_frame_handler_t aeron_context_get_publication_error_frame_handler(aeron_context_t *context)\n{\n    return NULL != context ? context->error_frame_handler : NULL;\n}\n\nvoid *aeron_context_get_publication_error_frame_handler_clientd(aeron_context_t *context)\n{\n    return NULL != context ? context->error_frame_handler_clientd : NULL;\n}\n\n\nint aeron_context_set_on_new_publication(aeron_context_t *context, aeron_on_new_publication_t handler, void *clientd)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->on_new_publication = handler;\n    context->on_new_publication_clientd = clientd;\n    return 0;\n}\n\naeron_on_new_publication_t aeron_context_get_on_new_publication(aeron_context_t *context)\n{\n    return NULL != context ? context->on_new_publication : NULL;\n}\n\nvoid *aeron_context_get_on_new_publication_clientd(aeron_context_t *context)\n{\n    return NULL != context ? context->on_new_publication_clientd : NULL;\n}\n\nint aeron_context_set_on_new_exclusive_publication(\n    aeron_context_t *context, aeron_on_new_publication_t handler, void *clientd)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->on_new_exclusive_publication = handler;\n    context->on_new_exclusive_publication_clientd = clientd;\n    return 0;\n}\n\naeron_on_new_publication_t aeron_context_get_on_new_exclusive_publication(aeron_context_t *context)\n{\n    return NULL != context ? context->on_new_exclusive_publication : NULL;\n}\n\nvoid *aeron_context_get_on_new_exclusive_publication_clientd(aeron_context_t *context)\n{\n    return NULL != context ? context->on_new_exclusive_publication_clientd : NULL;\n}\n\nint aeron_context_set_on_new_subscription(\n    aeron_context_t *context, aeron_on_new_subscription_t handler, void *clientd)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->on_new_subscription = handler;\n    context->on_new_subscription_clientd = clientd;\n    return 0;\n}\n\naeron_on_new_subscription_t aeron_context_get_on_new_subscription(aeron_context_t *context)\n{\n    return NULL != context ? context->on_new_subscription : NULL;\n}\n\nvoid *aeron_context_get_on_new_subscription_clientd(aeron_context_t *context)\n{\n    return NULL != context ? context->on_new_subscription_clientd : NULL;\n}\n\nint aeron_context_set_on_available_counter(\n    aeron_context_t *context, aeron_on_available_counter_t handler, void *clientd)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->on_available_counter = handler;\n    context->on_available_counter_clientd = clientd;\n    return 0;\n}\n\naeron_on_available_counter_t aeron_context_get_on_available_counter(aeron_context_t *context)\n{\n    return NULL != context ? context->on_available_counter : NULL;\n}\n\nvoid *aeron_context_get_on_available_counter_clientd(aeron_context_t *context)\n{\n    return NULL != context ? context->on_available_counter_clientd : NULL;\n}\n\nint aeron_context_set_on_unavailable_counter(\n    aeron_context_t *context, aeron_on_unavailable_counter_t handler, void *clientd)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->on_unavailable_counter = handler;\n    context->on_unavailable_counter_clientd = clientd;\n    return 0;\n}\n\naeron_on_unavailable_counter_t aeron_context_get_on_unavailable_counter(aeron_context_t *context)\n{\n    return NULL != context ? context->on_unavailable_counter : NULL;\n}\n\nvoid *aeron_context_get_on_unavailable_counter_clientd(aeron_context_t *context)\n{\n    return NULL != context ? context->on_unavailable_counter_clientd : NULL;\n}\n\nint aeron_context_set_on_close_client(\n    aeron_context_t *context, aeron_on_close_client_t handler, void *clientd)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->on_close_client = handler;\n    context->on_close_client_clientd = clientd;\n    return 0;\n}\n\naeron_on_close_client_t aeron_context_get_on_close_client(aeron_context_t *context)\n{\n    return NULL != context ? context->on_close_client : NULL;\n}\n\nvoid *aeron_context_get_on_close_client_clientd(aeron_context_t *context)\n{\n    return NULL != context ? context->on_close_client_clientd : NULL;\n}\n\nint aeron_context_set_use_conductor_agent_invoker(aeron_context_t *context, bool value)\n{\n    AERON_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->use_conductor_agent_invoker = value;\n    return 0;\n}\n\nbool aeron_context_get_use_conductor_agent_invoker(aeron_context_t *context)\n{\n    return NULL != context ? context->use_conductor_agent_invoker : AERON_CONTEXT_USE_CONDUCTOR_AGENT_INVOKER_DEFAULT;\n}\n\nint aeron_context_request_driver_termination(const char *directory, const uint8_t *token_buffer, size_t token_length)\n{\n    size_t min_length = AERON_CNC_VERSION_AND_META_DATA_LENGTH;\n\n    if (AERON_MAX_PATH < token_length)\n    {\n        AERON_SET_ERR(EINVAL, \"Token too long: %lu\", (unsigned long)token_length);\n        return -1;\n    }\n\n    char filename[AERON_MAX_PATH] = { 0 };\n    if (aeron_cnc_resolve_filename(directory, filename, sizeof(filename)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to get cnc filename\");\n        return -1;\n    }\n\n    int64_t file_length_result = aeron_file_length(filename);\n    if (file_length_result < 0)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"Invalid file length\");\n        return -1;\n    }\n\n    size_t file_length = (size_t)file_length_result;\n    if (file_length > min_length)\n    {\n        aeron_mapped_file_t cnc_mmap;\n        if (aeron_map_existing_file(&cnc_mmap, filename) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to map cnc for driver termination\");\n            return aeron_errcode();\n        }\n\n        if (cnc_mmap.length > min_length)\n        {\n            int result = 1;\n\n            aeron_cnc_metadata_t *metadata = (aeron_cnc_metadata_t *)cnc_mmap.addr;\n            int32_t cnc_version = aeron_cnc_version_volatile(metadata);\n\n            if (cnc_version <= 0)\n            {\n                AERON_SET_ERR(EINVAL, \"%s\", \"Aeron CnC not initialised\");\n\n                result = -1;\n                goto cleanup;\n            }\n\n            if (aeron_semantic_version_major(cnc_version) != aeron_semantic_version_major(AERON_CNC_VERSION))\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"Aeron CnC version does not match: client=%\" PRId32 \", file=%\" PRId32,\n                    AERON_CNC_VERSION,\n                    cnc_version);\n\n                result = -1;\n                goto cleanup;\n            }\n\n            if (aeron_semantic_version_minor(cnc_version) < aeron_semantic_version_minor(AERON_CNC_VERSION))\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"Driver version insufficient: client=%\" PRId32 \", file=%\" PRId32,\n                    AERON_CNC_VERSION,\n                    cnc_version);\n\n                result = -1;\n                goto cleanup;\n            }\n\n            if (!aeron_cnc_is_file_length_sufficient(&cnc_mmap))\n            {\n                AERON_SET_ERR(EINVAL, \"Aeron CnC file length not sufficient: length=%\" PRId64, file_length_result);\n                result = -1;\n                goto cleanup;\n            }\n\n            aeron_mpsc_rb_t rb;\n            if (aeron_mpsc_rb_init(\n                &rb, aeron_cnc_to_driver_buffer(metadata), (size_t)metadata->to_driver_buffer_length) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"Failed to setup ring buffer for termination\");\n                result = -1;\n                goto cleanup;\n            }\n\n            const size_t command_length = sizeof(aeron_terminate_driver_command_t) + token_length;\n            const int32_t offset = aeron_mpsc_rb_try_claim(&rb, AERON_COMMAND_TERMINATE_DRIVER, command_length);\n            if (offset < 0)\n            {\n                AERON_SET_ERR(AERON_CLIENT_ERROR_BUFFER_FULL, \"%s\", \"Unable to write to driver ring buffer\");\n                result = -1;\n            }\n\n            aeron_terminate_driver_command_t *command = (aeron_terminate_driver_command_t *)(rb.buffer + offset);\n\n            command->correlated.client_id = aeron_mpsc_rb_next_correlation_id(&rb);\n            command->correlated.correlation_id = aeron_mpsc_rb_next_correlation_id(&rb);\n            command->token_length = (int32_t)token_length;\n            memcpy((void *)(command + 1), token_buffer, token_length);\n\n            aeron_mpsc_rb_commit(&rb, offset);\n\ncleanup:\n            aeron_unmap(&cnc_mmap);\n            return result;\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_context.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_CONTEXT_H\n#define AERON_C_CONTEXT_H\n\n#include \"aeronc.h\"\n#include \"aeron_agent.h\"\n#include \"util/aeron_fileutil.h\"\n#include \"concurrent/aeron_mpsc_concurrent_array_queue.h\"\n\n#define AERON_CNC_FILE \"cnc.dat\"\n\n#define AERON_CLIENT_COMMAND_QUEUE_CAPACITY (256)\n\ntypedef struct aeron_context_stct\n{\n    char aeron_dir[AERON_MAX_PATH];\n    char client_name[AERON_COUNTER_MAX_CLIENT_NAME_LENGTH];\n\n    aeron_error_handler_t error_handler;\n    void *error_handler_clientd;\n\n    aeron_on_new_publication_t on_new_publication;\n    void *on_new_publication_clientd;\n\n    aeron_on_new_publication_t on_new_exclusive_publication;\n    void *on_new_exclusive_publication_clientd;\n\n    aeron_on_new_subscription_t on_new_subscription;\n    void *on_new_subscription_clientd;\n\n    aeron_on_available_image_t on_available_image;\n    void *on_available_image_clientd;\n\n    aeron_on_unavailable_image_t on_unavailable_image;\n    void *on_unavailable_image_clientd;\n\n    aeron_on_available_counter_t on_available_counter;\n    void *on_available_counter_clientd;\n\n    aeron_on_unavailable_counter_t on_unavailable_counter;\n    void *on_unavailable_counter_clientd;\n\n    aeron_publication_error_frame_handler_t error_frame_handler;\n    void *error_frame_handler_clientd;\n\n    aeron_agent_on_start_func_t agent_on_start_func;\n    void *agent_on_start_state;\n\n    aeron_on_close_client_t on_close_client;\n    void *on_close_client_clientd;\n\n    aeron_idle_strategy_func_t idle_strategy_func;\n    void *idle_strategy_state;\n    char *idle_strategy_init_args;\n    const char *idle_strategy_name;\n\n    uint64_t driver_timeout_ms;\n    uint64_t keepalive_interval_ns;\n    uint64_t resource_linger_duration_ns;\n    uint64_t idle_sleep_duration_ns;\n\n    bool use_conductor_agent_invoker;\n    bool pre_touch_mapped_memory;\n\n    aeron_clock_func_t nano_clock;\n    aeron_clock_func_t epoch_clock;\n\n    aeron_mapped_file_t cnc_map;\n\n    aeron_mpsc_concurrent_array_queue_t command_queue;\n}\naeron_context_t;\n\n#endif //AERON_C_CONTEXT_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_counter.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_counter.h\"\n\nint aeron_counter_create(\n    aeron_counter_t **counter,\n    aeron_client_conductor_t *conductor,\n    int64_t registration_id,\n    int32_t counter_id,\n    int64_t *counter_addr)\n{\n    aeron_counter_t *_counter;\n\n    *counter = NULL;\n    if (aeron_alloc((void **)&_counter, sizeof(aeron_counter_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate counter\");\n        return -1;\n    }\n\n    _counter->command_base.type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_COUNTER;\n\n    _counter->counter_addr = counter_addr;\n\n    _counter->conductor = conductor;\n    _counter->registration_id = registration_id;\n    _counter->counter_id = counter_id;\n    _counter->is_closed = false;\n\n    *counter = _counter;\n    return 0;\n}\n\nint aeron_counter_delete(aeron_counter_t *counter)\n{\n    aeron_free(counter);\n    return 0;\n}\n\nvoid aeron_counter_force_close(aeron_counter_t *counter)\n{\n    AERON_SET_RELEASE(counter->is_closed, true);\n}\n\nint64_t *aeron_counter_addr(aeron_counter_t *counter)\n{\n    return counter->counter_addr;\n}\n\nint aeron_counter_constants(aeron_counter_t *counter, aeron_counter_constants_t *constants)\n{\n    if (NULL == counter || NULL == constants)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, counter: %s, constants: %s\",\n            AERON_NULL_STR(counter),\n            AERON_NULL_STR(constants));\n        return -1;\n    }\n\n    constants->registration_id = counter->registration_id;\n    constants->counter_id = counter->counter_id;\n\n    return 0;\n}\n\nint aeron_counter_close(\n    aeron_counter_t *counter, aeron_notification_t on_close_complete, void *on_close_complete_clientd)\n{\n    if (NULL != counter)\n    {\n        bool is_closed;\n\n        AERON_GET_ACQUIRE(is_closed, counter->is_closed);\n        if (!is_closed)\n        {\n            AERON_SET_RELEASE(counter->is_closed, true);\n            return aeron_client_conductor_async_close_counter(\n                counter->conductor, counter, on_close_complete, on_close_complete_clientd);\n        }\n    }\n\n    return 0;\n}\n\nbool aeron_counter_is_closed(aeron_counter_t *counter)\n{\n    bool is_closed = false;\n    if (NULL != counter)\n    {\n        AERON_GET_ACQUIRE(is_closed, counter->is_closed);\n    }\n\n    return is_closed;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_counter.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_COUNTER_H\n#define AERON_C_COUNTER_H\n\n#include \"aeronc.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_context.h\"\n#include \"aeron_client_conductor.h\"\n\ntypedef struct aeron_counter_stct\n{\n    aeron_client_command_base_t command_base;\n    aeron_client_conductor_t *conductor;\n\n    int64_t *counter_addr;\n    int64_t registration_id;\n    int32_t counter_id;\n\n    aeron_notification_t on_close_complete;\n    void *on_close_complete_clientd;\n    volatile bool is_closed;\n}\naeron_counter_t;\n\nint aeron_counter_create(\n    aeron_counter_t **counter,\n    aeron_client_conductor_t *conductor,\n    int64_t registration_id,\n    int32_t counter_id,\n    int64_t *counter_addr);\n\nint aeron_counter_delete(aeron_counter_t *counter);\nvoid aeron_counter_force_close(aeron_counter_t *counter);\n\n#endif //AERON_C_COUNTER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_counters.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_COUNTERS_H\n#define AERON_C_COUNTERS_H\n\n// System counters\n#define AERON_SYSTEM_COUNTER_ID_BYTES_SENT (0)\n#define AERON_SYSTEM_COUNTER_ID_BYTES_RECEIVED (1)\n#define AERON_SYSTEM_COUNTER_ID_RECEIVER_PROXY_FAILS (2)\n#define AERON_SYSTEM_COUNTER_ID_SENDER_PROXY_FAILS (3)\n#define AERON_SYSTEM_COUNTER_ID_CONDUCTOR_PROXY_FAILS (4)\n#define AERON_SYSTEM_COUNTER_ID_NAK_MESSAGES_SENT (5)\n#define AERON_SYSTEM_COUNTER_ID_NAK_MESSAGES_RECEIVED (6)\n#define AERON_SYSTEM_COUNTER_ID_STATUS_MESSAGES_SENT (7)\n#define AERON_SYSTEM_COUNTER_ID_STATUS_MESSAGES_RECEIVED (8)\n#define AERON_SYSTEM_COUNTER_ID_HEARTBEATS_SENT (9)\n#define AERON_SYSTEM_COUNTER_ID_HEARTBEATS_RECEIVED (10)\n#define AERON_SYSTEM_COUNTER_ID_RETRANSMITS_SENT (11)\n#define AERON_SYSTEM_COUNTER_ID_FLOW_CONTROL_UNDER_RUNS (12)\n#define AERON_SYSTEM_COUNTER_ID_FLOW_CONTROL_OVER_RUNS (13)\n#define AERON_SYSTEM_COUNTER_ID_INVALID_PACKETS (14)\n#define AERON_SYSTEM_COUNTER_ID_ERRORS (15)\n#define AERON_SYSTEM_COUNTER_ID_SHORT_SENDS (16)\n#define AERON_SYSTEM_COUNTER_ID_FREE_FAILS (17)\n#define AERON_SYSTEM_COUNTER_ID_SENDER_FLOW_CONTROL_LIMITS (18)\n#define AERON_SYSTEM_COUNTER_ID_UNBLOCKED_PUBLICATIONS (19)\n#define AERON_SYSTEM_COUNTER_ID_UNBLOCKED_COMMANDS (20)\n#define AERON_SYSTEM_COUNTER_ID_POSSIBLE_TTL_ASYMMETRY (21)\n#define AERON_SYSTEM_COUNTER_ID_CONTROLLABLE_IDLE_STRATEGY (22)\n#define AERON_SYSTEM_COUNTER_ID_LOSS_GAP_FILLS (23)\n#define AERON_SYSTEM_COUNTER_ID_CLIENT_TIMEOUTS (24)\n#define AERON_SYSTEM_COUNTER_ID_RESOLUTION_CHANGES (25)\n#define AERON_SYSTEM_COUNTER_ID_CONDUCTOR_MAX_CYCLE_TIME (26)\n#define AERON_SYSTEM_COUNTER_ID_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED (27)\n#define AERON_SYSTEM_COUNTER_ID_SENDER_MAX_CYCLE_TIME (28)\n#define AERON_SYSTEM_COUNTER_ID_SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED (29)\n#define AERON_SYSTEM_COUNTER_ID_RECEIVER_MAX_CYCLE_TIME (30)\n#define AERON_SYSTEM_COUNTER_ID_RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED (31)\n#define AERON_SYSTEM_COUNTER_ID_NAME_RESOLVER_MAX_TIME (32)\n#define AERON_SYSTEM_COUNTER_ID_NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED (33)\n#define AERON_SYSTEM_COUNTER_ID_AERON_VERSION (34)\n#define AERON_SYSTEM_COUNTER_ID_BYTES_CURRENTLY_MAPPED (35)\n#define AERON_SYSTEM_COUNTER_ID_RETRANSMITTED_BYTES (36)\n#define AERON_SYSTEM_COUNTER_ID_RETRANSMIT_OVERFLOW (37)\n#define AERON_SYSTEM_COUNTER_ID_ERROR_FRAMES_RECEIVED (38)\n#define AERON_SYSTEM_COUNTER_ID_ERROR_FRAMES_SENT (39)\n#define AERON_SYSTEM_COUNTER_ID_PUBLICATIONS_REVOKED (40)\n#define AERON_SYSTEM_COUNTER_ID_PUBLICATION_IMAGES_REVOKED (41)\n#define AERON_SYSTEM_COUNTER_ID_IMAGES_REJECTED (42)\n#define AERON_SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION (43)\n\n// Driver counters\n#define AERON_COUNTER_SYSTEM_COUNTER_TYPE_ID (0)\n\n#define AERON_COUNTER_PUBLISHER_LIMIT_NAME \"pub-lmt\"\n#define AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID (1)\n\n#define AERON_COUNTER_SENDER_POSITION_NAME \"snd-pos\"\n#define AERON_COUNTER_SENDER_POSITION_TYPE_ID (2)\n\n#define AERON_COUNTER_RECEIVER_HWM_NAME \"rcv-hwm\"\n#define AERON_COUNTER_RECEIVER_HWM_TYPE_ID (3)\n\n#define AERON_COUNTER_SUBSCRIPTION_POSITION_NAME \"sub-pos\"\n#define AERON_COUNTER_SUBSCRIPTION_POSITION_TYPE_ID (4)\n\n#define AERON_COUNTER_RECEIVER_POSITION_NAME \"rcv-pos\"\n#define AERON_COUNTER_RECEIVER_POSITION_TYPE_ID (5)\n\n#define AERON_COUNTER_SEND_CHANNEL_STATUS_NAME \"snd-channel\"\n#define AERON_COUNTER_SEND_CHANNEL_STATUS_TYPE_ID (6)\n\n#define AERON_COUNTER_RECEIVE_CHANNEL_STATUS_NAME \"rcv-channel\"\n#define AERON_COUNTER_RECEIVE_CHANNEL_STATUS_TYPE_ID (7)\n\n#define AERON_COUNTER_SENDER_LIMIT_NAME \"snd-lmt\"\n#define AERON_COUNTER_SENDER_LIMIT_TYPE_ID (9)\n\n#define AERON_COUNTER_PER_IMAGE_TYPE_ID (10)\n\n#define AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_NAME \"client-heartbeat\"\n#define AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID (11)\n\n#define AERON_COUNTER_PUBLISHER_POSITION_NAME_CONCURRENT \"pub-pos (concurrent)\"\n#define AERON_COUNTER_PUBLISHER_POSITION_NAME_EXCLUSIVE \"pub-pos (exclusive)\"\n#define AERON_COUNTER_PUBLISHER_POSITION_TYPE_ID (12)\n\n#define AERON_COUNTER_SENDER_BPE_NAME \"snd-bpe\"\n#define AERON_COUNTER_SENDER_BPE_TYPE_ID  (13)\n\n#define AERON_COUNTER_RCV_LOCAL_SOCKADDR_NAME \"rcv-local-sockaddr\"\n#define AERON_COUNTER_SND_LOCAL_SOCKADDR_NAME \"snd-local-sockaddr\"\n#define AERON_COUNTER_LOCAL_SOCKADDR_TYPE_ID (14)\n\n#define AERON_COUNTER_NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID (15)\n\n#define AERON_COUNTER_NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID (16)\n\n#define AERON_COUNTER_FC_NUM_RECEIVERS_TYPE_ID (17)\n\n#define AERON_COUNTER_CHANNEL_MDC_NUM_DESTINATIONS_NAME \"mdc-num-dest\"\n#define AERON_COUNTER_CHANNEL_NUM_DESTINATIONS_TYPE_ID (18)\n\n#define AERON_COUNTER_SENDER_NAKS_RECEIVED_NAME \"snd-naks-received\"\n#define AERON_COUNTER_SENDER_NAKS_RECEIVED_TYPE_ID  (19)\n\n#define AERON_COUNTER_RECEIVER_NAKS_SENT_NAME \"rcv-naks-sent\"\n#define AERON_COUNTER_RECEIVER_NAKS_SENT_TYPE_ID  (20)\n\n#define AERON_COUNTER_EF_VI_PORT_INFO_TYPE_ID (50)\n#define AERON_COUNTER_EF_VI_TRANSPORT_TYPE_ID (51)\n#define AERON_COUNTER_EF_VI_TX_NOBUFS_TYPE_ID (52)\n#define AERON_COUNTER_EF_VI_TX_EAGAIN_TYPE_ID (53)\n#define AERON_COUNTER_EF_VI_TX_ERROR_TYPE_ID (54)\n#define AERON_COUNTER_EF_VI_RX_DISCARD_TYPE_ID (55)\n#define AERON_COUNTER_EF_VI_RX_INVALID_TYPE_ID (56)\n#define AERON_COUNTER_EF_VI_RX_PKTS_TYPE_ID (57)\n#define AERON_COUNTER_EF_VI_RX_BYTES_TYPE_ID (58)\n#define AERON_COUNTER_EF_VI_TX_PKTS_TYPE_ID (59)\n#define AERON_COUNTER_EF_VI_TX_BYTES_TYPE_ID (60)\n\n#define AERON_COUNTER_VMA_TRANSPORTS_TYPE_ID (61)\n#define AERON_COUNTER_VMA_RX_ZERO_COPY_BYTES_TYPE_ID (62)\n#define AERON_COUNTER_VMA_RX_DATA_COPY_BYTES_TYPE_ID (63)\n\n#define AERON_COUNTER_ATS_TRANSPORTS_TYPE_ID (65)\n#define AERON_COUNTER_ATS_DISCARDS_NON_ATS_TYPE_ID (66)\n#define AERON_COUNTER_ATS_BYTES_ENCRYPTED_TYPE_ID (67)\n#define AERON_COUNTER_ATS_BYTES_DECRYPTED_TYPE_ID (68)\n#define AERON_COUNTER_ATS_AEAD_ERRORS_TYPE_ID (69)\n#define AERON_COUNTER_ATS_RSA_KEY_UNKNOWN_TYPE_ID (70)\n#define AERON_COUNTER_ATS_EC_KEY_SIG_ERRORS_TYPE_ID (71)\n#define AERON_COUNTER_ATS_UNICAST_RE_KEYINGS_TYPE_ID (72)\n#define AERON_COUNTER_ATS_UNICAST_RE_KEYING_RSA_KEY_MISMATCH_TYPE_ID (73)\n#define AERON_COUNTER_ATS_DROPPED_SM_TYPE_ID (74)\n\n#define AERON_COUNTER_DPDK_PORT_INFO_TYPE_ID (75)\n#define AERON_COUNTER_DPDK_TRANSPORT_TYPE_ID (76)\n#define AERON_COUNTER_DPDK_NOBUFS_TYPE_ID (77)\n#define AERON_COUNTER_DPDK_TX_EAGAIN_TYPE_ID (78)\n#define AERON_COUNTER_DPDK_ERROR_TYPE_ID (79)\n#define AERON_COUNTER_DPDK_PKTS_TYPE_ID (82)\n#define AERON_COUNTER_DPDK_BYTES_TYPE_ID (83)\n#define AERON_COUNTER_DPDK_MISSED_PACKETS_TYPE_ID (84)\n#define AERON_COUNTER_DPDK_ARP_MISS_TYPE_ID (85)\n#define AERON_COUNTER_DPDK_RX_SENDER_DISCARD_TYPE_ID (86)\n#define AERON_COUNTER_DPDK_POLLER_TYPE_ID (87)\n#define AERON_COUNTER_DPDK_QUEUE_DROP_COUNT_TYPE_ID (88)\n#define AERON_COUNTER_DPDK_CHECKSUM_FAILURE_TYPE_ID (89)\n#define AERON_COUNTER_DPDK_FRAGMENTED_PACKETS_TYPE_ID (90)\n#define AERON_COUNTER_DPDK_MEMPOOL_AVAILABLE_TYPE_ID (91)\n#define AERON_COUNTER_DPDK_EXTENDED_STATS_TYPE_ID (92)\n#define AERON_COUNTER_DPDK_RX_UNSUPPORTED_ETHERNET_TYPE_TYPE_ID (93)\n#define AERON_COUNTER_DPDK_RX_UNSUPPORTED_PROTOCOL_TYPE_ID (94)\n#define AERON_COUNTER_DPDK_RX_RECEIVER_DISCARD_TYPE_ID (95)\n\n// Archive counters\n\n#define AERON_COUNTER_ARCHIVE_RECORDING_POSITION_TYPE_ID (100);\n\n#define AERON_COUNTER_ARCHIVE_ERROR_COUNT_TYPE_ID (101);\n\n#define AERON_COUNTER_ARCHIVE_CONTROL_SESSIONS_TYPE_ID (102);\n\n#define AERON_COUNTER_ARCHIVE_MAX_CYCLE_TIME_TYPE_ID (103);\n\n#define AERON_COUNTER_ARCHIVE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID (104);\n\n#define AERON_COUNTER_ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID (105);\n\n#define AERON_COUNTER_ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID (106);\n\n#define AERON_COUNTER_ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID (107);\n\n#define AERON_COUNTER_ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID (108);\n\n#define AERON_COUNTER_ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID (109);\n\n#define AERON_COUNTER_ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID (110);\n\n#define AERON_COUNTER_ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID (112);\n\n#define AERON_COUNTER_ARCHIVE_CONTROL_SESSION_TYPE_ID (113);\n\n// Cluster counters\n\n#define AERON_COUNTER_CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID (200)\n\n#define AERON_COUNTER_CLUSTER_NODE_ROLE_TYPE_ID (201)\n\n#define AERON_COUNTER_CLUSTER_CONTROL_TOGGLE_TYPE_ID (202)\n\n#define AERON_COUNTER_CLUSTER_COMMIT_POSITION_TYPE_ID (203)\n\n#define AERON_COUNTER_CLUSTER_RECOVERY_STATE_TYPE_ID (204)\n\n#define AERON_COUNTER_CLUSTER_SNAPSHOT_COUNTER_TYPE_ID (205)\n\n#define AERON_COUNTER_CLUSTER_ELECTION_STATE_TYPE_ID (207)\n\n#define AERON_COUNTER_CLUSTER_BACKUP_STATE_TYPE_ID (208)\n\n#define AERON_COUNTER_CLUSTER_BACKUP_LIVE_LOG_POSITION_TYPE_ID (209)\n\n#define AERON_COUNTER_CLUSTER_BACKUP_QUERY_DEADLINE_TYPE_ID (210)\n\n#define AERON_COUNTER_CLUSTER_BACKUP_ERROR_COUNT_TYPE_ID (211)\n\n#define AERON_COUNTER_CLUSTER_CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID (212)\n\n#define AERON_COUNTER_CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID (213)\n\n#define AERON_COUNTER_CLUSTER_INVALID_REQUEST_COUNT_TYPE_ID (214)\n\n#define AERON_COUNTER_CLUSTER_CLUSTERED_SERVICE_ERROR_COUNT_TYPE_ID (215)\n\n#define AERON_COUNTER_CLUSTER_MAX_CYCLE_TIME_TYPE_ID (216)\n\n#define AERON_COUNTER_CLUSTER_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID (217)\n\n#define AERON_COUNTER_CLUSTER_CLUSTERED_SERVICE_MAX_CYCLE_TIME_TYPE_ID (218)\n\n#define AERON_COUNTER_CLUSTER_CLUSTERED_SERVICE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID (219)\n\n#define AERON_COUNTER_CLUSTER_STANDBY_STATE_TYPE_ID (220)\n\n#define AERON_COUNTER_CLUSTER_STANDBY_ERROR_COUNT_TYPE_ID (221)\n\n#define AERON_COUNTER_CLUSTER_STANDBY_HEARTBEAT_RESPONSE_COUNT_TYPE_ID (222)\n\n#define AERON_COUNTER_CLUSTER_STANDBY_CONTROL_TOGGLE_TYPE_ID (223)\n\n#define AERON_COUNTER_CLUSTER_TRANSITION_MODULE_STATE_TYPE_ID (224)\n\n//#define AERON_COUNTER_CLUSTER_TRANSITION_MODULE_CONTROL_TOGGLE_TYPE_ID (225)\n\n#define AERON_COUNTER_CLUSTER_TRANSITION_MODULE_ERROR_COUNT_TYPE_ID (226)\n\n#define AERON_COUNTER_CLUSTER_STANDBY_MAX_CYCLE_TIME_TYPE_ID (227)\n\n#define AERON_COUNTER_CLUSTER_STANDBY_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID (228)\n\n#define AERON_COUNTER_CLUSTER_TRANSITION_MODULE_MAX_CYCLE_TIME_TYPE_ID (229)\n\n#define AERON_COUNTER_CLUSTER_TRANSITION_MODULE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID (230)\n\n#define AERON_COUNTER_CLUSTER_STANDBY_SOURCE_MEMBER_ID_TYPE_ID (231)\n\n#define AERON_COUNTER_CLUSTER_TOTAL_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID (235)\n\n#define AERON_COUNTER_CLUSTERED_SERVICE_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID (237)\n\n#define AERON_COUNTER_CLUSTER_ELECTION_COUNT_TYPE_ID (238)\n\n#define AERON_COUNTER_CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID (239)\n\n// Aeron Selector counters\n#define AERON_COUNTER_SELECTOR_CLIENTS_COUNTER_TYPE_ID (300)\n#define AERON_COUNTER_SELECTOR_SUBSCRIPTIONS_COUNTER_TYPE_ID (301)\n#define AERON_COUNTER_SELECTOR_MAX_CYCLE_TIME_TYPE_ID (302)\n#define AERON_COUNTER_SELECTOR_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID (303)\n\n// Sequencer counters\n\n#define AERON_COUNTER_SEQUENCER_INDEX_COUNTER_TYPE_ID (500)\n\n#define AERON_COUNTER_SEQUENCER_GROUP_HWM_COUNTER_TYPE_ID (501)\n\n#define AERON_COUNTER_SEQUENCER_SESSION_GREATEST_MESSAGE_ID_COUNTER_TYPE_ID (502)\n\n#define AERON_COUNTER_SEQUENCER_SESSION_MESSAGES_COUNTER_TYPE_ID (503)\n\n#define AERON_COUNTER_SEQUENCER_SESSION_GREATEST_MESSAGE_TIMESTAMP_COUNTER_TYPE_ID (504)\n\n#define AERON_COUNTER_SEQUENCER_CLIENT_SNAPSHOT_ID_COUNTER_TYPE_ID (505)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_SEQUENCE_INDEX_COUNTER_TYPE_ID (507)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_STATE_COUNTER_TYPE_ID (508)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_ERROR_COUNT_TYPE_ID (509)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_MAX_SERVICE_TIME_TYPE_ID (510)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_SERVICE_TIME_THRESHOLD_EXCEEDED_COUNT_TYPE_ID (511)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_INTERVAL_SERVICE_TIME_TYPE_ID (512)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_INTERVAL_MAX_SERVICE_TIME_TYPE_ID (513)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_INTERVAL_TOTAL_INVOCATIONS_TYPE_ID (514)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_SNAPSHOT_LOAD_TIME_TYPE_ID (515)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_SNAPSHOT_STORE_TIME_TYPE_ID (516)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_TAKE_SNAPSHOT_FAILURES_TYPE_ID (517)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_TAKE_SNAPSHOT_COUNT_TYPE_ID (518)\n\n#define AERON_COUNTER_SEQUENCER_APPLICATION_SESSION_ID_TYPE_ID (519)\n\n#define AERON_COUNTER_SEQUENCER_REPLAY_INDEX_MIN_SEQUENCE_INDEX_COUNTER_TYPE_ID (520)\n\n#define AERON_COUNTER_SEQUENCER_REPLAY_INDEX_MIN_SEQUENCE_LOG_POSITION_COUNTER_TYPE_ID (521)\n\n#define AERON_COUNTER_SEQUENCER_REPLAY_INDEX_MAX_SEQUENCE_INDEX_COUNTER_TYPE_ID (522)\n\n#define AERON_COUNTER_SEQUENCER_REPLAY_INDEX_MAX_SEQUENCE_LOG_POSITION_COUNTER_TYPE_ID (523)\n\n#define AERON_COUNTER_SEQUENCER_REPLAY_INDEX_INITIAL_SEQUENCE_INDEX_COUNTER_TYPE_ID (524)\n\n#define AERON_COUNTER_SEQUENCER_REPLAY_INDEX_INITIAL_SEQUENCE_LOG_POSITION_COUNTER_TYPE_ID (525)\n\n#endif //AERON_C_COUNTERS_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_exclusive_publication.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <inttypes.h>\n\n#include \"aeronc.h\"\n#include \"aeron_exclusive_publication.h\"\n#include \"aeron_log_buffer.h\"\n#include \"status/aeron_local_sockaddr.h\"\n\nstatic inline void aeron_put_raw_tail_release(volatile int64_t *addr, int32_t term_id, int32_t term_offset)\n{\n    AERON_SET_RELEASE(*addr, ((uint64_t)term_id << 32 | term_offset));\n}\n\nstatic inline void aeron_header_write(\n    aeron_mapped_buffer_t *term_buffer,\n    int32_t offset,\n    size_t length,\n    int32_t term_id,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    aeron_data_header_t *header = (aeron_data_header_t *)(term_buffer->addr + offset);\n\n    AERON_SET_RELEASE(header->frame_header.frame_length, (-(int32_t)length));\n    aeron_release();\n\n    header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    header->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n    header->frame_header.type = AERON_HDR_TYPE_DATA;\n    header->term_offset = offset;\n    header->session_id = session_id;\n    header->stream_id = stream_id;\n    header->term_id = term_id;\n}\n\nstatic inline int32_t aeron_handle_end_of_log_condition(\n    aeron_mapped_buffer_t *term_buffer,\n    int32_t term_offset,\n    int32_t term_length,\n    int32_t term_id,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    if (term_offset < term_length)\n    {\n        const int32_t padding_length = term_length - term_offset;\n        aeron_data_header_t *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n\n        aeron_header_write(term_buffer, term_offset, (size_t)padding_length, term_id, session_id, stream_id);\n        header->frame_header.type = AERON_HDR_TYPE_PAD;\n        AERON_SET_RELEASE(header->frame_header.frame_length, padding_length);\n    }\n\n    return -2;\n}\n\nstatic inline int32_t aeron_append_unfragmented_message(\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    int32_t term_offset,\n    const uint8_t *buffer,\n    size_t length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd,\n    int32_t term_id,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    const size_t frame_length = length + AERON_DATA_HEADER_LENGTH;\n    const int32_t aligned_frame_length = (int32_t)AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_length = (int32_t)term_buffer->length;\n\n    int32_t resulting_offset = term_offset + aligned_frame_length;\n    aeron_put_raw_tail_release(term_tail_counter, term_id, resulting_offset);\n\n    if (resulting_offset > term_length)\n    {\n        resulting_offset = aeron_handle_end_of_log_condition(\n            term_buffer, term_offset, term_length, term_id, session_id, stream_id);\n    }\n    else\n    {\n        aeron_header_write(term_buffer, term_offset, frame_length, term_id, session_id, stream_id);\n        memcpy(term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, buffer, length);\n\n        aeron_data_header_t *data_header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n\n        if (NULL != reserved_value_supplier)\n        {\n            data_header->reserved_value = reserved_value_supplier(\n                clientd, term_buffer->addr + term_offset, frame_length);\n        }\n\n        AERON_SET_RELEASE(data_header->frame_header.frame_length, (int32_t)frame_length);\n    }\n\n    return resulting_offset;\n}\n\nstatic inline int32_t aeron_append_fragmented_message(\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    int32_t term_offset,\n    const uint8_t *buffer,\n    size_t length,\n    size_t max_payload_length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd,\n    int32_t term_id,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    const size_t framed_length = aeron_logbuffer_compute_fragmented_length(length, max_payload_length);\n    const int32_t term_length = (int32_t)term_buffer->length;\n\n    int32_t resulting_offset = term_offset + (int32_t)framed_length;\n    aeron_put_raw_tail_release(term_tail_counter, term_id, resulting_offset);\n\n    if (resulting_offset > term_length)\n    {\n        resulting_offset = aeron_handle_end_of_log_condition(\n            term_buffer, term_offset, term_length, term_id, session_id, stream_id);\n    }\n    else\n    {\n        uint8_t flags = AERON_DATA_HEADER_BEGIN_FLAG;\n        size_t remaining = length;\n        int32_t frame_offset = term_offset;\n\n        do\n        {\n            size_t bytes_to_write = remaining < max_payload_length ? remaining : max_payload_length;\n            size_t frame_length = bytes_to_write + AERON_DATA_HEADER_LENGTH;\n            size_t aligned_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n            aeron_header_write(term_buffer, frame_offset, frame_length, term_id, session_id, stream_id);\n            memcpy(\n                term_buffer->addr + frame_offset + AERON_DATA_HEADER_LENGTH,\n                buffer + (length - remaining),\n                bytes_to_write);\n\n            if (remaining <= max_payload_length)\n            {\n                flags |= AERON_DATA_HEADER_END_FLAG;\n            }\n\n            aeron_data_header_t *data_header = (aeron_data_header_t *)(term_buffer->addr + frame_offset);\n            data_header->frame_header.flags = flags;\n\n            if (NULL != reserved_value_supplier)\n            {\n                data_header->reserved_value = reserved_value_supplier(\n                    clientd, term_buffer->addr + term_offset, frame_length);\n            }\n\n            AERON_SET_RELEASE(data_header->frame_header.frame_length, (int32_t)frame_length);\n\n            flags = 0;\n            frame_offset += (int32_t)aligned_length;\n            remaining -= bytes_to_write;\n        }\n        while (remaining > 0);\n    }\n\n    return resulting_offset;\n}\n\nstatic inline int32_t aeron_append_unfragmented_messagev(\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    int32_t term_offset,\n    aeron_iovec_t *iov,\n    size_t length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd,\n    int32_t term_id,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    const size_t frame_length = length + AERON_DATA_HEADER_LENGTH;\n    const int32_t aligned_frame_length = (int32_t)AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_length = (int32_t)term_buffer->length;\n\n    int32_t resulting_offset = term_offset + aligned_frame_length;\n    aeron_put_raw_tail_release(term_tail_counter, term_id, resulting_offset);\n\n    if (resulting_offset > term_length)\n    {\n        resulting_offset = aeron_handle_end_of_log_condition(\n            term_buffer, term_offset, term_length, term_id, session_id, stream_id);\n    }\n    else\n    {\n        aeron_header_write(term_buffer, term_offset, frame_length, term_id, session_id, stream_id);\n\n        aeron_data_header_t *data_header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n        int32_t offset = (int32_t)(term_offset + AERON_DATA_HEADER_LENGTH);\n        size_t i = 0;\n\n        for (int32_t ending_offset = offset + (int32_t)length;\n             offset < ending_offset;\n             offset += (int32_t)iov[i].iov_len, i++)\n        {\n            memcpy(term_buffer->addr + offset, iov[i].iov_base, iov[i].iov_len);\n        }\n\n        if (NULL != reserved_value_supplier)\n        {\n            data_header->reserved_value = reserved_value_supplier(\n                clientd, term_buffer->addr + term_offset, frame_length);\n        }\n\n        AERON_SET_RELEASE(data_header->frame_header.frame_length, (int32_t)frame_length);\n    }\n\n    return resulting_offset;\n}\n\nstatic inline int32_t aeron_append_fragmented_messagev(\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    int32_t term_offset,\n    aeron_iovec_t *iov,\n    size_t length,\n    size_t max_payload_length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd,\n    int32_t term_id,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    const size_t framed_length = aeron_logbuffer_compute_fragmented_length(length, max_payload_length);\n    const int32_t term_length = (int32_t)term_buffer->length;\n\n    int32_t resulting_offset = term_offset + (int32_t)framed_length;\n    aeron_put_raw_tail_release(term_tail_counter, term_id, resulting_offset);\n\n    if (resulting_offset > term_length)\n    {\n        resulting_offset = aeron_handle_end_of_log_condition(\n            term_buffer, term_offset, term_length, term_id, session_id, stream_id);\n    }\n    else\n    {\n        uint8_t flags = AERON_DATA_HEADER_BEGIN_FLAG;\n        size_t remaining = length, i = 0;\n        int32_t frame_offset = term_offset;\n        int32_t current_buffer_offset = 0;\n\n        do\n        {\n            int32_t bytes_to_write = remaining < max_payload_length ?\n                (int32_t)remaining : (int32_t)max_payload_length;\n            size_t frame_length = bytes_to_write + AERON_DATA_HEADER_LENGTH;\n            size_t aligned_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n            aeron_header_write(term_buffer, frame_offset, frame_length, term_id, session_id, stream_id);\n\n            int32_t bytes_written = 0;\n            int32_t payload_offset = (int32_t)(frame_offset + AERON_DATA_HEADER_LENGTH);\n\n            do\n            {\n                int32_t current_buffer_remaining = (int32_t)iov[i].iov_len - current_buffer_offset;\n                int32_t num_bytes = (bytes_to_write - bytes_written) < current_buffer_remaining ?\n                    (bytes_to_write - bytes_written) : current_buffer_remaining;\n                memcpy(term_buffer->addr + payload_offset, iov[i].iov_base + current_buffer_offset, (size_t)num_bytes);\n\n                bytes_written += num_bytes;\n                payload_offset += num_bytes;\n                current_buffer_offset += num_bytes;\n\n                if (current_buffer_remaining <= num_bytes)\n                {\n                    i++;\n                    current_buffer_offset = 0;\n                }\n            }\n            while (bytes_written < bytes_to_write);\n\n            if (remaining <= max_payload_length)\n            {\n                flags |= AERON_DATA_HEADER_END_FLAG;\n            }\n\n            aeron_data_header_t *data_header = (aeron_data_header_t *)(term_buffer->addr + frame_offset);\n            data_header->frame_header.flags = flags;\n\n            if (NULL != reserved_value_supplier)\n            {\n                data_header->reserved_value = reserved_value_supplier(\n                    clientd, term_buffer->addr + frame_offset, frame_length);\n            }\n\n            AERON_SET_RELEASE(data_header->frame_header.frame_length, ((int32_t)frame_length));\n\n            flags = 0;\n            frame_offset += (int32_t)aligned_length;\n            remaining -= bytes_to_write;\n        }\n        while (remaining > 0);\n    }\n\n    return resulting_offset;\n}\n\nstatic inline int32_t aeron_claim(\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    int32_t term_offset,\n    size_t length,\n    aeron_buffer_claim_t *buffer_claim,\n    int32_t term_id,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    const size_t frame_length = length + AERON_DATA_HEADER_LENGTH;\n    const int32_t aligned_frame_length = (int32_t)AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_length = (int32_t)term_buffer->length;\n\n    int32_t resulting_offset = term_offset + aligned_frame_length;\n    aeron_put_raw_tail_release(term_tail_counter, term_id, resulting_offset);\n\n    if (resulting_offset > term_length)\n    {\n        resulting_offset = aeron_handle_end_of_log_condition(\n            term_buffer, term_offset, term_length, term_id, session_id, stream_id);\n    }\n    else\n    {\n        aeron_header_write(term_buffer, term_offset, frame_length, term_id, session_id, stream_id);\n        buffer_claim->frame_header = term_buffer->addr + term_offset;\n        buffer_claim->data = buffer_claim->frame_header + AERON_DATA_HEADER_LENGTH;\n        buffer_claim->length = length;\n    }\n\n    return resulting_offset;\n}\n\nstatic inline int32_t aeron_append_padding(\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    int32_t term_offset,\n    size_t length,\n    int32_t term_id,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    const size_t frame_length = length + AERON_DATA_HEADER_LENGTH;\n    const int32_t aligned_frame_length = (int32_t)AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_length = (int32_t)term_buffer->length;\n\n    int32_t resulting_offset = term_offset + aligned_frame_length;\n    aeron_put_raw_tail_release(term_tail_counter, term_id, resulting_offset);\n\n    if (resulting_offset > term_length)\n    {\n        resulting_offset = aeron_handle_end_of_log_condition(\n            term_buffer, term_offset, term_length, term_id, session_id, stream_id);\n    }\n    else\n    {\n        aeron_header_write(term_buffer, term_offset, frame_length, term_id, session_id, stream_id);\n        aeron_data_header_t *data_header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n\n        data_header->frame_header.type = AERON_HDR_TYPE_PAD;\n        AERON_SET_RELEASE(data_header->frame_header.frame_length, (int32_t)frame_length);\n    }\n\n    return resulting_offset;\n}\n\nstatic inline int32_t aeron_append_block(\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    int32_t term_offset,\n    const uint8_t *buffer,\n    size_t length,\n    int32_t term_id)\n{\n    int32_t resulting_offset = term_offset + (int32_t)length;\n    aeron_put_raw_tail_release(term_tail_counter, term_id, resulting_offset);\n\n    aeron_data_header_as_longs_t *dest_hdr_as_longs =\n        (aeron_data_header_as_longs_t *)(term_buffer->addr + term_offset);\n    aeron_data_header_as_longs_t *src_hdr_as_longs = (aeron_data_header_as_longs_t *)buffer;\n\n    memcpy(\n        term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH,\n        buffer + AERON_DATA_HEADER_LENGTH,\n        length - AERON_DATA_HEADER_LENGTH);\n\n    dest_hdr_as_longs->hdr[3] = src_hdr_as_longs->hdr[3];\n    dest_hdr_as_longs->hdr[2] = src_hdr_as_longs->hdr[2];\n    dest_hdr_as_longs->hdr[1] = src_hdr_as_longs->hdr[1];\n\n    AERON_SET_RELEASE(dest_hdr_as_longs->hdr[0], src_hdr_as_longs->hdr[0]);\n\n    return resulting_offset;\n}\n\nint aeron_exclusive_publication_create(\n    aeron_exclusive_publication_t **publication,\n    aeron_client_conductor_t *conductor,\n    const char *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t position_limit_counter_id,\n    int64_t *position_limit_addr,\n    int32_t channel_status_indicator_id,\n    int64_t *channel_status_addr,\n    aeron_log_buffer_t *log_buffer,\n    int64_t original_registration_id,\n    int64_t registration_id)\n{\n    aeron_exclusive_publication_t *_publication;\n\n    *publication = NULL;\n    if (aeron_alloc((void **)&_publication, sizeof(aeron_exclusive_publication_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate exclusive_publication\");\n        return -1;\n    }\n\n    _publication->command_base.type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_EXCLUSIVE_PUBLICATION;\n\n    _publication->log_buffer = log_buffer;\n    _publication->log_meta_data = (aeron_logbuffer_metadata_t *)log_buffer->mapped_raw_log.log_meta_data.addr;\n\n    _publication->position_limit_counter_id = position_limit_counter_id;\n    _publication->position_limit = position_limit_addr;\n    _publication->channel_status_indicator_id = channel_status_indicator_id;\n    _publication->channel_status_indicator = channel_status_addr;\n\n    size_t term_length = (size_t)_publication->log_meta_data->term_length;\n    int32_t term_count = aeron_logbuffer_active_term_count(_publication->log_meta_data);\n    size_t index = aeron_logbuffer_index_by_term_count(term_count);\n    int64_t raw_tail = _publication->log_meta_data->term_tail_counters[index];\n    _publication->position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes((int32_t)term_length);\n    _publication->initial_term_id = _publication->log_meta_data->initial_term_id;\n\n    _publication->active_partition_index = index;\n    _publication->term_id = aeron_logbuffer_term_id(raw_tail);\n    _publication->term_offset = (int32_t)(raw_tail & 0xFFFFFFFFL);\n    _publication->term_begin_position = aeron_logbuffer_compute_term_begin_position(\n        _publication->term_id, _publication->position_bits_to_shift, _publication->initial_term_id);\n\n    _publication->conductor = conductor;\n    _publication->channel = channel;\n    _publication->registration_id = registration_id;\n    _publication->original_registration_id = original_registration_id;\n    _publication->stream_id = stream_id;\n    _publication->session_id = session_id;\n    _publication->is_closed = false;\n    _publication->revoke_on_close = false;\n\n    _publication->max_possible_position = ((int64_t)term_length << 31);\n    _publication->max_payload_length = (size_t)(_publication->log_meta_data->mtu_length - AERON_DATA_HEADER_LENGTH);\n    _publication->max_message_length = aeron_compute_max_message_length(term_length);\n    _publication->term_buffer_length = (int32_t)term_length;\n\n    *publication = _publication;\n    return 0;\n}\n\nint aeron_exclusive_publication_delete(aeron_exclusive_publication_t *publication)\n{\n    aeron_free((void *)publication->channel);\n    aeron_free(publication);\n\n    return 0;\n}\n\nvoid aeron_exclusive_publication_force_close(aeron_exclusive_publication_t *publication)\n{\n    AERON_SET_RELEASE(publication->is_closed, true);\n}\n\nint aeron_exclusive_publication_close(\n    aeron_exclusive_publication_t *publication,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd)\n{\n    if (NULL != publication)\n    {\n        bool is_closed;\n        AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n        if (!is_closed)\n        {\n            AERON_SET_RELEASE(publication->is_closed, true);\n            return aeron_client_conductor_async_close_exclusive_publication(\n                publication->conductor, publication, on_close_complete, on_close_complete_clientd);\n        }\n    }\n\n    return 0;\n}\n\nvoid aeron_exclusive_publication_revoke_on_close(aeron_exclusive_publication_t *publication)\n{\n    AERON_SET_RELEASE(publication->revoke_on_close, true);\n}\n\nint aeron_exclusive_publication_revoke(\n    aeron_exclusive_publication_t *publication, aeron_notification_t on_close_complete, void *on_close_complete_clientd)\n{\n    if (NULL != publication)\n    {\n        AERON_SET_RELEASE(publication->revoke_on_close, true);\n\n        return aeron_exclusive_publication_close(publication, on_close_complete, on_close_complete_clientd);\n    }\n\n    return 0;\n}\n\nint64_t aeron_exclusive_publication_offer(\n    aeron_exclusive_publication_t *publication,\n    const uint8_t *buffer,\n    size_t length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd)\n{\n    int64_t new_position = AERON_PUBLICATION_CLOSED;\n\n    if (NULL == publication || NULL == buffer)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, publication: %s, buffer: %s\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(buffer));\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (!is_closed)\n    {\n        const int64_t limit = aeron_counter_get_acquire(publication->position_limit);\n        const int32_t term_offset = publication->term_offset;\n        const int64_t position = publication->term_begin_position + term_offset;\n        const size_t index = publication->active_partition_index;\n\n        if (position < limit)\n        {\n            int32_t resulting_offset;\n            if (length <= publication->max_payload_length)\n            {\n                resulting_offset = aeron_append_unfragmented_message(\n                    &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                    &publication->log_meta_data->term_tail_counters[index],\n                    term_offset,\n                    buffer,\n                    length,\n                    reserved_value_supplier,\n                    clientd,\n                    publication->term_id,\n                    publication->session_id,\n                    publication->stream_id);\n            }\n            else\n            {\n                if (length > publication->max_message_length)\n                {\n                    AERON_SET_ERR(\n                        EINVAL,\n                        \"aeron_exclusive_publication_offer: length=%\" PRIu64 \" > max_message_length=%\" PRIu64,\n                        (uint64_t)length,\n                        (uint64_t)publication->max_message_length);\n                    return AERON_PUBLICATION_ERROR;\n                }\n\n                resulting_offset = aeron_append_fragmented_message(\n                    &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                    &publication->log_meta_data->term_tail_counters[index],\n                    term_offset,\n                    buffer,\n                    length,\n                    publication->max_payload_length,\n                    reserved_value_supplier,\n                    clientd,\n                    publication->term_id,\n                    publication->session_id,\n                    publication->stream_id);\n            }\n\n            new_position = aeron_exclusive_publication_new_position(publication, resulting_offset);\n        }\n        else\n        {\n            new_position = aeron_exclusive_publication_back_pressure_status(publication, position, (int32_t)length);\n        }\n    }\n\n    return new_position;\n}\n\nint64_t aeron_exclusive_publication_offerv(\n    aeron_exclusive_publication_t *publication,\n    aeron_iovec_t *iov,\n    size_t iovcnt,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd)\n{\n    int64_t new_position = AERON_PUBLICATION_CLOSED;\n\n    if (NULL == publication || NULL == iov)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, publication: %s, iov: %s\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(iov));\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    size_t length = 0;\n    for (size_t i = 0; i < iovcnt; i++)\n    {\n        length += iov[i].iov_len;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (!is_closed)\n    {\n        const int64_t limit = aeron_counter_get_acquire(publication->position_limit);\n        const int32_t term_offset = publication->term_offset;\n        const int64_t position = publication->term_begin_position + term_offset;\n        const size_t index = publication->active_partition_index;\n\n        if (position < limit)\n        {\n            int32_t resulting_offset;\n            if (length <= publication->max_payload_length)\n            {\n                resulting_offset = aeron_append_unfragmented_messagev(\n                    &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                    &publication->log_meta_data->term_tail_counters[index],\n                    term_offset,\n                    iov,\n                    length,\n                    reserved_value_supplier,\n                    clientd,\n                    publication->term_id,\n                    publication->session_id,\n                    publication->stream_id);\n            }\n            else\n            {\n                if (length > publication->max_message_length)\n                {\n                    AERON_SET_ERR(\n                        EINVAL,\n                        \"aeron_exclusive_publication_offerv: length=%\" PRIu64 \" > max_message_length=%\" PRIu64,\n                        (uint64_t)length,\n                        (uint64_t)publication->max_message_length);\n                    return AERON_PUBLICATION_ERROR;\n                }\n\n                resulting_offset = aeron_append_fragmented_messagev(\n                    &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                    &publication->log_meta_data->term_tail_counters[index],\n                    term_offset,\n                    iov,\n                    length,\n                    publication->max_payload_length,\n                    reserved_value_supplier,\n                    clientd,\n                    publication->term_id,\n                    publication->session_id,\n                    publication->stream_id);\n            }\n\n            new_position = aeron_exclusive_publication_new_position(publication, resulting_offset);\n        }\n        else\n        {\n            new_position = aeron_exclusive_publication_back_pressure_status(publication, position, (int32_t)length);\n        }\n    }\n\n    return new_position;\n}\n\nint64_t aeron_exclusive_publication_try_claim(\n    aeron_exclusive_publication_t *publication, size_t length, aeron_buffer_claim_t *buffer_claim)\n{\n    int64_t new_position = AERON_PUBLICATION_CLOSED;\n\n    if (NULL == publication || NULL == buffer_claim)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, publication: %s, buffer_claim: %s\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(buffer_claim));\n        return AERON_PUBLICATION_ERROR;\n    }\n    else if (length > publication->max_payload_length)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"aeron_exclusive_publication_try_claim: length=%\" PRIu64 \" > max_payload_length=%\" PRIu64,\n            (uint64_t)length,\n            (uint64_t)publication->max_payload_length);\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (!is_closed)\n    {\n        const int64_t limit = aeron_counter_get_acquire(publication->position_limit);\n        const int32_t term_offset = publication->term_offset;\n        const int64_t position = publication->term_begin_position + term_offset;\n        const size_t index = publication->active_partition_index;\n\n        if (position < limit)\n        {\n            int32_t resulting_offset = aeron_claim(\n                &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                &publication->log_meta_data->term_tail_counters[index],\n                term_offset,\n                length,\n                buffer_claim,\n                publication->term_id,\n                publication->session_id,\n                publication->stream_id);\n\n            new_position = aeron_exclusive_publication_new_position(publication, resulting_offset);\n        }\n        else\n        {\n            new_position = aeron_exclusive_publication_back_pressure_status(publication, position, (int32_t)length);\n        }\n    }\n\n    return new_position;\n}\n\nint64_t aeron_exclusive_publication_append_padding(aeron_exclusive_publication_t *publication, size_t length)\n{\n    int64_t new_position = AERON_PUBLICATION_CLOSED;\n\n    if (NULL == publication)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"aeron_exclusive_publication_append_padding(NULL)\");\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    if (length > publication->max_message_length)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"aeron_exclusive_publication_append_padding: length=%\" PRIu64 \" > max_message_length=%\" PRIu64,\n            (uint64_t)length,\n            (uint64_t)publication->max_message_length);\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (!is_closed)\n    {\n        const int64_t limit = aeron_counter_get_acquire(publication->position_limit);\n        const int32_t term_offset = publication->term_offset;\n        const int64_t position = publication->term_begin_position + term_offset;\n        const size_t index = publication->active_partition_index;\n\n        if (position < limit)\n        {\n            int32_t resulting_offset = aeron_append_padding(\n                &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                &publication->log_meta_data->term_tail_counters[index],\n                term_offset,\n                length,\n                publication->term_id,\n                publication->session_id,\n                publication->stream_id);\n\n            new_position = aeron_exclusive_publication_new_position(publication, resulting_offset);\n        }\n        else\n        {\n            new_position = aeron_exclusive_publication_back_pressure_status(publication, position, (int32_t)length);\n        }\n    }\n\n    return new_position;\n}\n\nint64_t aeron_exclusive_publication_offer_block(\n    aeron_exclusive_publication_t *publication, const uint8_t *buffer, size_t length)\n{\n    if (NULL == publication)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"aeron_exclusive_publication_offer_block(NULL)\");\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    if (NULL == buffer)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"aeron_exclusive_publication_offer_block buffer is NULL\");\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (is_closed)\n    {\n        return AERON_PUBLICATION_CLOSED;\n    }\n\n    if (publication->term_offset >= publication->term_buffer_length)\n    {\n        aeron_exclusive_publication_rotate_term(publication);\n    }\n\n    const int64_t limit = aeron_counter_get_acquire(publication->position_limit);\n    const int32_t term_offset = publication->term_offset;\n    const int64_t position = publication->term_begin_position + term_offset;\n    const size_t index = publication->active_partition_index;\n\n    if (position < limit)\n    {\n        aeron_data_header_t *first_frame = (aeron_data_header_t *)buffer;\n\n        if (length > (size_t)(publication->term_buffer_length - publication->term_offset))\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"aeron_exclusive_publication_offer_block: invalid block length %\" PRIu64 \", remaining space in term is %\" PRIu64,\n                (uint64_t)length,\n                (uint64_t)(publication->term_buffer_length - publication->term_offset));\n            return AERON_PUBLICATION_ERROR;\n        }\n\n        if (first_frame->term_offset != publication->term_offset ||\n            first_frame->session_id != publication->session_id ||\n            first_frame->stream_id != publication->stream_id ||\n            first_frame->term_id != publication->term_id ||\n            first_frame->frame_header.type != AERON_HDR_TYPE_DATA)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"aeron_exclusive_publication_offer_block improperly formatted block:\"\n                \" term_offset=%\" PRId32 \" (expected=%\" PRId32 \"),\"\n                \" session_id=%\" PRId32 \" (expected=%\" PRId32 \"),\"\n                \" stream_id=%\" PRId32 \" (expected=%\" PRId32 \"),\"\n                \" term_id=%\" PRId32 \" (expected=%\" PRId32 \"),\"\n                \" frame_type=%\" PRId32 \" (expected=%\" PRId32 \")\",\n                first_frame->term_offset, publication->term_offset,\n                first_frame->session_id, publication->session_id,\n                first_frame->stream_id, publication->stream_id,\n                first_frame->term_id, publication->term_id,\n                first_frame->frame_header.type, AERON_HDR_TYPE_DATA);\n            return AERON_PUBLICATION_ERROR;\n        }\n\n        int32_t result = aeron_append_block(\n            &publication->log_buffer->mapped_raw_log.term_buffers[index],\n            &publication->log_meta_data->term_tail_counters[index],\n            term_offset,\n            buffer,\n            length,\n            publication->term_id);\n\n        return aeron_exclusive_publication_new_position(publication, result);\n    }\n    else\n    {\n        return aeron_exclusive_publication_back_pressure_status(publication, position, (int32_t)length);\n    }\n}\n\nbool aeron_exclusive_publication_is_closed(aeron_exclusive_publication_t *publication)\n{\n    bool is_closed = true;\n\n    if (NULL != publication)\n    {\n        AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    }\n\n    return is_closed;\n}\n\nbool aeron_exclusive_publication_is_connected(aeron_exclusive_publication_t *publication)\n{\n    if (NULL != publication && !aeron_exclusive_publication_is_closed(publication))\n    {\n        int32_t is_connected;\n\n        AERON_GET_ACQUIRE(is_connected, publication->log_meta_data->is_connected);\n        return 1 == is_connected;\n    }\n\n    return false;\n}\n\nint aeron_exclusive_publication_constants(\n    aeron_exclusive_publication_t *publication, aeron_publication_constants_t *constants)\n{\n    if (NULL == publication || NULL == constants)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, publication: %s, constants: %s\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(constants));\n        return -1;\n    }\n\n    constants->channel = publication->channel;\n    constants->original_registration_id = publication->original_registration_id;\n    constants->registration_id = publication->registration_id;\n    constants->max_possible_position = publication->max_possible_position;\n    constants->position_bits_to_shift = publication->position_bits_to_shift;\n    constants->term_buffer_length = (size_t)publication->log_meta_data->term_length;\n    constants->max_message_length = publication->max_message_length;\n    constants->max_payload_length = publication->max_payload_length;\n    constants->stream_id = publication->stream_id;\n    constants->session_id = publication->session_id;\n    constants->initial_term_id = publication->initial_term_id;\n    constants->publication_limit_counter_id = publication->position_limit_counter_id;\n    constants->channel_status_indicator_id = publication->channel_status_indicator_id;\n\n    return 0;\n}\n\nint64_t aeron_exclusive_publication_channel_status(aeron_exclusive_publication_t *publication)\n{\n    if (NULL != publication &&\n        NULL != publication->channel_status_indicator &&\n        !aeron_exclusive_publication_is_closed(publication))\n    {\n        int64_t value;\n        AERON_GET_ACQUIRE(value, *publication->channel_status_indicator);\n\n        return value;\n    }\n\n    return AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_NO_ID_ALLOCATED;\n}\n\nint64_t aeron_exclusive_publication_position(aeron_exclusive_publication_t *publication)\n{\n    if (NULL == publication)\n    {\n        AERON_SET_ERR(EINVAL, \"Parameters must not be null, publication: %s\", AERON_NULL_STR(publication));\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (is_closed)\n    {\n        return AERON_PUBLICATION_CLOSED;\n    }\n\n    return publication->term_begin_position + publication->term_offset;\n}\n\nint64_t aeron_exclusive_publication_position_limit(aeron_exclusive_publication_t *publication)\n{\n    if (NULL == publication)\n    {\n        AERON_SET_ERR(EINVAL, \"Parameters must not be null, publication: %s\", AERON_NULL_STR(publication));\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (is_closed)\n    {\n        return AERON_PUBLICATION_CLOSED;\n    }\n\n    return aeron_counter_get_acquire(publication->position_limit);\n}\n\nint aeron_exclusive_publication_local_sockaddrs(\n    aeron_exclusive_publication_t *publication, aeron_iovec_t *address_vec, size_t address_vec_len)\n{\n    if (NULL == publication || NULL == address_vec || address_vec_len < 1)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must correct, publication: %s, address_vec: %s, address_vec_len: (%\" PRIu64 \") < 1\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(address_vec),\n            (uint64_t)address_vec_len);\n        return -1;\n    }\n\n    return aeron_local_sockaddr_find_addrs(\n        &publication->conductor->counters_reader,\n        publication->channel_status_indicator_id,\n        address_vec,\n        address_vec_len);\n}\n\nextern void aeron_exclusive_publication_rotate_term(aeron_exclusive_publication_t *publication);\n\nextern int64_t aeron_exclusive_publication_new_position(\n    aeron_exclusive_publication_t *publication, int32_t resulting_offset);\n\nextern int64_t aeron_exclusive_publication_back_pressure_status(\n    aeron_exclusive_publication_t *publication, int64_t current_position, int32_t message_length);\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_exclusive_publication.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_EXCLUSIVE_PUBLICATION_H\n#define AERON_C_EXCLUSIVE_PUBLICATION_H\n\n#include \"aeronc.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_context.h\"\n#include \"aeron_client_conductor.h\"\n\ntypedef struct aeron_exclusive_publication_stct\n{\n    aeron_client_command_base_t command_base;\n    aeron_client_conductor_t *conductor;\n    const char *channel;\n\n    aeron_log_buffer_t *log_buffer;\n    aeron_logbuffer_metadata_t *log_meta_data;\n\n    volatile int64_t *position_limit;\n    volatile int64_t *channel_status_indicator;\n\n    int64_t registration_id;\n    int64_t original_registration_id;\n    int32_t stream_id;\n    int32_t session_id;\n\n    int64_t max_possible_position;\n    size_t max_payload_length;\n    size_t max_message_length;\n    size_t position_bits_to_shift;\n    int32_t initial_term_id;\n    int32_t term_buffer_length;\n\n    int32_t position_limit_counter_id;\n    int32_t channel_status_indicator_id;\n    aeron_notification_t on_close_complete;\n    void *on_close_complete_clientd;\n\n    volatile bool is_closed;\n    volatile bool revoke_on_close;\n\n    uint8_t pre_fields_padding[AERON_CACHE_LINE_LENGTH];\n    int64_t term_begin_position;\n    int32_t term_offset;\n    int32_t term_id;\n    size_t active_partition_index;\n    uint8_t post_fields_padding[AERON_CACHE_LINE_LENGTH];\n}\naeron_exclusive_publication_t;\n\nint aeron_exclusive_publication_create(\n    aeron_exclusive_publication_t **publication,\n    aeron_client_conductor_t *conductor,\n    const char *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t position_limit_counter_id,\n    int64_t *position_limit_addr,\n    int32_t channel_status_indicator_id,\n    int64_t *channel_status_addr,\n    aeron_log_buffer_t *log_buffer,\n    int64_t original_registration_id,\n    int64_t registration_id);\n\nint aeron_exclusive_publication_delete(aeron_exclusive_publication_t *publication);\nvoid aeron_exclusive_publication_force_close(aeron_exclusive_publication_t *publication);\n\ninline void aeron_exclusive_publication_rotate_term(aeron_exclusive_publication_t *publication)\n{\n    int32_t next_term_id = publication->term_id + 1;\n    int32_t next_term_count = aeron_logbuffer_compute_term_count(next_term_id, publication->initial_term_id);\n    size_t next_index = aeron_logbuffer_index_by_term(publication->initial_term_id, next_term_id);\n\n    publication->active_partition_index = next_index;\n    publication->term_offset = 0;\n    publication->term_id = next_term_id;\n    publication->term_begin_position += publication->term_buffer_length;\n\n    publication->log_meta_data->term_tail_counters[next_index] = (int64_t)((uint64_t)next_term_id << 32);\n    AERON_SET_RELEASE(publication->log_meta_data->active_term_count, next_term_count);\n}\n\ninline int64_t aeron_exclusive_publication_new_position(\n    aeron_exclusive_publication_t *publication, int32_t resulting_offset)\n{\n    if (resulting_offset > 0)\n    {\n        publication->term_offset = resulting_offset;\n        return publication->term_begin_position + resulting_offset;\n    }\n\n    if ((publication->term_begin_position + publication->term_buffer_length) >= publication->max_possible_position)\n    {\n        return AERON_PUBLICATION_MAX_POSITION_EXCEEDED;\n    }\n\n    aeron_exclusive_publication_rotate_term(publication);\n\n    return AERON_PUBLICATION_ADMIN_ACTION;\n}\n\ninline int64_t aeron_exclusive_publication_back_pressure_status(\n    aeron_exclusive_publication_t *publication, int64_t current_position, int32_t message_length)\n{\n    if ((current_position + message_length) >= publication->max_possible_position)\n    {\n        return AERON_PUBLICATION_MAX_POSITION_EXCEEDED;\n    }\n\n    int32_t is_connected;\n    AERON_GET_ACQUIRE(is_connected, publication->log_meta_data->is_connected);\n    if (1 == is_connected)\n    {\n        return AERON_PUBLICATION_BACK_PRESSURED;\n    }\n\n    return AERON_PUBLICATION_NOT_CONNECTED;\n}\n\n#endif //AERON_C_EXCLUSIVE_PUBLICATION_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_fragment_assembler.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <inttypes.h>\n\n#include \"aeron_fragment_assembler.h\"\n#include \"aeron_image.h\"\n#include \"util/aeron_bitutil.h\"\n\n#define AERON_BUFFER_BUILDER_MIN_ALLOCATED_CAPACITY (4096)\n#define AERON_BUFFER_BUILDER_MAX_CAPACITY (INT32_MAX - 8)\n\nint aeron_buffer_builder_create(aeron_buffer_builder_t **buffer_builder)\n{\n    aeron_buffer_builder_t *_buffer_builder;\n    aeron_data_header_t *_frame;\n\n    if (aeron_alloc((void **)&_buffer_builder, sizeof(aeron_buffer_builder_t)) < 0 ||\n        aeron_alloc((void **)&_frame, sizeof(aeron_data_header_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate buffer_builder\");\n        return -1;\n    }\n\n    _buffer_builder->buffer = NULL;\n    _buffer_builder->buffer_length = 0;\n    _buffer_builder->limit = 0;\n    _buffer_builder->next_term_offset = -1;\n    _buffer_builder->header.frame = _frame;\n\n    *buffer_builder = _buffer_builder;\n    return 0;\n}\n\nint aeron_buffer_builder_find_suitable_capacity(size_t current_capacity, size_t required_capacity)\n{\n    size_t capacity = current_capacity;\n\n    do\n    {\n        const size_t candidate_capacity = capacity + (capacity >> 1u);\n        const size_t new_capacity = candidate_capacity > AERON_BUFFER_BUILDER_MIN_ALLOCATED_CAPACITY ?\n            candidate_capacity : AERON_BUFFER_BUILDER_MIN_ALLOCATED_CAPACITY;\n\n        if (new_capacity > AERON_BUFFER_BUILDER_MAX_CAPACITY)\n        {\n            if (AERON_BUFFER_BUILDER_MAX_CAPACITY == capacity)\n            {\n                AERON_SET_ERR(EINVAL, \"max capacity reached: %\" PRId32, AERON_BUFFER_BUILDER_MAX_CAPACITY);\n                return -1;\n            }\n\n            capacity = AERON_BUFFER_BUILDER_MAX_CAPACITY;\n        }\n        else\n        {\n            capacity = new_capacity;\n        }\n    }\n    while (capacity < required_capacity);\n\n    return (int)capacity;\n}\n\nint aeron_buffer_builder_ensure_capacity(aeron_buffer_builder_t *buffer_builder, size_t additional_capacity)\n{\n    size_t required_capacity = buffer_builder->limit + additional_capacity;\n\n    if (required_capacity > buffer_builder->buffer_length)\n    {\n        int suitable_capacity = aeron_buffer_builder_find_suitable_capacity(\n            buffer_builder->buffer_length, required_capacity);\n\n        if (suitable_capacity < 0)\n        {\n            return -1;\n        }\n\n        if (aeron_reallocf((void **)&buffer_builder->buffer, (size_t)suitable_capacity) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Unable to reallocate buffer_builder->builder\");\n            return -1;\n        }\n\n        buffer_builder->buffer_length = (size_t)suitable_capacity;\n    }\n\n    return 0;\n}\n\nvoid aeron_buffer_builder_delete(aeron_buffer_builder_t *buffer_builder)\n{\n    if (buffer_builder)\n    {\n        aeron_free(buffer_builder->buffer);\n        aeron_free(buffer_builder->header.frame);\n        aeron_free(buffer_builder);\n    }\n}\n\nint aeron_image_fragment_assembler_create(\n    aeron_image_fragment_assembler_t **assembler,\n    aeron_fragment_handler_t delegate,\n    void *delegate_clientd)\n{\n    aeron_image_fragment_assembler_t *_assembler;\n\n    if (aeron_alloc((void **)&_assembler, sizeof(aeron_image_fragment_assembler_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate assembler\");\n        return -1;\n    }\n\n    if (aeron_buffer_builder_create(&_assembler->buffer_builder) < 0)\n    {\n        return -1;\n    }\n\n    _assembler->delegate = delegate;\n    _assembler->delegate_clientd = delegate_clientd;\n\n    *assembler = _assembler;\n    return 0;\n}\n\nint aeron_image_fragment_assembler_delete(aeron_image_fragment_assembler_t *assembler)\n{\n    if (assembler)\n    {\n        aeron_buffer_builder_delete(assembler->buffer_builder);\n        aeron_free(assembler);\n    }\n\n    return 0;\n}\n\nvoid aeron_image_fragment_assembler_handler(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_image_fragment_assembler_t *assembler = (aeron_image_fragment_assembler_t *)clientd;\n    aeron_buffer_builder_t *buffer_builder = assembler->buffer_builder;\n    uint8_t flags = header->frame->frame_header.flags;\n\n    if ((flags & AERON_DATA_HEADER_UNFRAGMENTED) == AERON_DATA_HEADER_UNFRAGMENTED)\n    {\n        assembler->delegate(assembler->delegate_clientd, buffer, length, header);\n    }\n    else if ((flags & AERON_DATA_HEADER_BEGIN_FLAG) == AERON_DATA_HEADER_BEGIN_FLAG)\n    {\n        aeron_buffer_builder_reset(buffer_builder);\n        aeron_buffer_builder_capture_header(buffer_builder, header);\n        aeron_buffer_builder_append(buffer_builder, buffer, length);\n        int32_t next_term_offset = aeron_header_next_term_offset(header);\n        aeron_buffer_builder_next_term_offset(buffer_builder, next_term_offset);\n    }\n    else if (buffer_builder->next_term_offset == header->frame->term_offset)\n    {\n        aeron_buffer_builder_append(buffer_builder, buffer, length);\n\n        if ((flags & AERON_DATA_HEADER_END_FLAG) == AERON_DATA_HEADER_END_FLAG)\n        {\n            assembler->delegate(\n                assembler->delegate_clientd,\n                buffer_builder->buffer,\n                buffer_builder->limit,\n                aeron_buffer_builder_complete_header(buffer_builder, header));\n            aeron_buffer_builder_reset(buffer_builder);\n        }\n        else\n        {\n            int32_t next_term_offset = aeron_header_next_term_offset(header);\n            aeron_buffer_builder_next_term_offset(buffer_builder, next_term_offset);\n        }\n    }\n    else\n    {\n        aeron_buffer_builder_reset(buffer_builder);\n    }\n}\n\nint aeron_image_controlled_fragment_assembler_create(\n    aeron_image_controlled_fragment_assembler_t **assembler,\n    aeron_controlled_fragment_handler_t delegate,\n    void *delegate_clientd)\n{\n    aeron_image_controlled_fragment_assembler_t *_assembler;\n\n    if (aeron_alloc((void **)&_assembler, sizeof(aeron_image_controlled_fragment_assembler_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate assembler\");\n        return -1;\n    }\n\n    if (aeron_buffer_builder_create(&_assembler->buffer_builder) < 0)\n    {\n        return -1;\n    }\n\n    _assembler->delegate = delegate;\n    _assembler->delegate_clientd = delegate_clientd;\n\n    *assembler = _assembler;\n    return 0;\n}\n\nint aeron_image_controlled_fragment_assembler_delete(aeron_image_controlled_fragment_assembler_t *assembler)\n{\n    if (assembler)\n    {\n        aeron_buffer_builder_delete(assembler->buffer_builder);\n        aeron_free(assembler);\n    }\n\n    return 0;\n}\n\naeron_controlled_fragment_handler_action_t aeron_image_controlled_fragment_assembler_handler(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_image_controlled_fragment_assembler_t *assembler = (aeron_image_controlled_fragment_assembler_t *)clientd;\n    aeron_buffer_builder_t *buffer_builder = assembler->buffer_builder;\n    uint8_t flags = header->frame->frame_header.flags;\n    aeron_controlled_fragment_handler_action_t action = AERON_ACTION_CONTINUE;\n\n    if ((flags & AERON_DATA_HEADER_UNFRAGMENTED) == AERON_DATA_HEADER_UNFRAGMENTED)\n    {\n        action = assembler->delegate(assembler->delegate_clientd, buffer, length, header);\n    }\n    else if ((flags & AERON_DATA_HEADER_BEGIN_FLAG) == AERON_DATA_HEADER_BEGIN_FLAG)\n    {\n        aeron_buffer_builder_reset(buffer_builder);\n        aeron_buffer_builder_capture_header(buffer_builder, header);\n        aeron_buffer_builder_append(buffer_builder, buffer, length);\n        int32_t next_term_offset = aeron_header_next_term_offset(header);\n        aeron_buffer_builder_next_term_offset(buffer_builder, next_term_offset);\n    }\n    else if (buffer_builder->next_term_offset == header->frame->term_offset)\n    {\n        size_t limit = buffer_builder->limit;\n        aeron_buffer_builder_append(buffer_builder, buffer, length);\n\n        if ((flags & AERON_DATA_HEADER_END_FLAG) == AERON_DATA_HEADER_END_FLAG)\n        {\n            action = assembler->delegate(\n                assembler->delegate_clientd,\n                buffer_builder->buffer,\n                buffer_builder->limit,\n                aeron_buffer_builder_complete_header(buffer_builder, header));\n\n            if (AERON_ACTION_ABORT == action)\n            {\n                buffer_builder->limit = limit;\n            }\n            else\n            {\n                aeron_buffer_builder_reset(buffer_builder);\n            }\n        }\n        else\n        {\n            int32_t next_term_offset = aeron_header_next_term_offset(header);\n            aeron_buffer_builder_next_term_offset(buffer_builder, next_term_offset);\n        }\n    }\n    else\n    {\n        aeron_buffer_builder_reset(buffer_builder);\n    }\n\n    return action;\n}\n\nint aeron_fragment_assembler_create(\n    aeron_fragment_assembler_t **assembler,\n    aeron_fragment_handler_t delegate,\n    void *delegate_clientd)\n{\n    aeron_fragment_assembler_t *_assembler;\n\n    if (aeron_alloc((void **)&_assembler, sizeof(aeron_fragment_assembler_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate assembler\");\n        return -1;\n    }\n\n    if (aeron_int64_to_ptr_hash_map_init(\n        &_assembler->builder_by_session_id_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init builder_by_session_id_map\");\n        return -1;\n    }\n\n    _assembler->delegate = delegate;\n    _assembler->delegate_clientd = delegate_clientd;\n\n    *assembler = _assembler;\n    return 0;\n}\n\nvoid aeron_fragment_assembler_entry_delete(void *clientd, int64_t key, void *value)\n{\n    aeron_buffer_builder_t *builder = (aeron_buffer_builder_t *)value;\n\n    aeron_buffer_builder_delete(builder);\n}\n\nint aeron_fragment_assembler_delete(aeron_fragment_assembler_t *assembler)\n{\n    if (assembler)\n    {\n        aeron_int64_to_ptr_hash_map_for_each(\n            &assembler->builder_by_session_id_map, aeron_fragment_assembler_entry_delete, NULL);\n        aeron_int64_to_ptr_hash_map_delete(&assembler->builder_by_session_id_map);\n        aeron_free(assembler);\n    }\n\n    return 0;\n}\n\nvoid aeron_fragment_assembler_handler(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_fragment_assembler_t *assembler = (aeron_fragment_assembler_t *)clientd;\n    uint8_t flags = header->frame->frame_header.flags;\n\n    if ((flags & AERON_DATA_HEADER_UNFRAGMENTED) == AERON_DATA_HEADER_UNFRAGMENTED)\n    {\n        assembler->delegate(assembler->delegate_clientd, buffer, length, header);\n    }\n    else\n    {\n        aeron_buffer_builder_t *buffer_builder = aeron_int64_to_ptr_hash_map_get(\n            &assembler->builder_by_session_id_map, header->frame->session_id);\n        int32_t next_term_offset = aeron_header_next_term_offset(header);\n\n        if ((flags & AERON_DATA_HEADER_BEGIN_FLAG) == AERON_DATA_HEADER_BEGIN_FLAG)\n        {\n            if (NULL == buffer_builder)\n            {\n                if (aeron_buffer_builder_create(&buffer_builder) < 0 ||\n                    aeron_int64_to_ptr_hash_map_put(\n                        &assembler->builder_by_session_id_map, header->frame->session_id, buffer_builder) < 0)\n                {\n                    return;\n                }\n            }\n\n            aeron_buffer_builder_reset(buffer_builder);\n            aeron_buffer_builder_capture_header(buffer_builder, header);\n            aeron_buffer_builder_append(buffer_builder, buffer, length);\n            aeron_buffer_builder_next_term_offset(buffer_builder, next_term_offset);\n        }\n        else if (NULL != buffer_builder)\n        {\n            if (buffer_builder->next_term_offset == header->frame->term_offset)\n            {\n                aeron_buffer_builder_append(buffer_builder, buffer, length);\n\n                if ((flags & AERON_DATA_HEADER_END_FLAG) == AERON_DATA_HEADER_END_FLAG)\n                {\n                    assembler->delegate(\n                        assembler->delegate_clientd,\n                        buffer_builder->buffer,\n                        buffer_builder->limit,\n                        aeron_buffer_builder_complete_header(buffer_builder, header));\n                    aeron_buffer_builder_reset(buffer_builder);\n                }\n                else\n                {\n                    aeron_buffer_builder_next_term_offset(buffer_builder, next_term_offset);\n                }\n            }\n            else\n            {\n                aeron_buffer_builder_reset(buffer_builder);\n            }\n        }\n    }\n}\n\nint aeron_controlled_fragment_assembler_create(\n    aeron_controlled_fragment_assembler_t **assembler,\n    aeron_controlled_fragment_handler_t delegate,\n    void *delegate_clientd)\n{\n    aeron_controlled_fragment_assembler_t *_assembler;\n\n    if (aeron_alloc((void **)&_assembler, sizeof(aeron_controlled_fragment_assembler_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate assembler\");\n        return -1;\n    }\n\n    if (aeron_int64_to_ptr_hash_map_init(\n        &_assembler->builder_by_session_id_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init builder_by_session_id_map\");\n        return -1;\n    }\n\n    _assembler->delegate = delegate;\n    _assembler->delegate_clientd = delegate_clientd;\n\n    *assembler = _assembler;\n    return 0;\n}\n\nvoid aeron_controlled_fragment_assembler_entry_delete(void *clientd, int64_t key, void *value)\n{\n    aeron_buffer_builder_t *builder = (aeron_buffer_builder_t *)value;\n\n    aeron_buffer_builder_delete(builder);\n}\n\nint aeron_controlled_fragment_assembler_delete(aeron_controlled_fragment_assembler_t *assembler)\n{\n    if (assembler)\n    {\n        aeron_int64_to_ptr_hash_map_for_each(\n            &assembler->builder_by_session_id_map, aeron_controlled_fragment_assembler_entry_delete, NULL);\n        aeron_int64_to_ptr_hash_map_delete(&assembler->builder_by_session_id_map);\n        aeron_free(assembler);\n    }\n\n    return 0;\n}\n\naeron_controlled_fragment_handler_action_t aeron_controlled_fragment_assembler_handler(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_controlled_fragment_assembler_t *assembler = (aeron_controlled_fragment_assembler_t *)clientd;\n    uint8_t flags = header->frame->frame_header.flags;\n    aeron_controlled_fragment_handler_action_t action = AERON_ACTION_CONTINUE;\n\n    if ((flags & AERON_DATA_HEADER_UNFRAGMENTED) == AERON_DATA_HEADER_UNFRAGMENTED)\n    {\n        action = assembler->delegate(assembler->delegate_clientd, buffer, length, header);\n    }\n    else\n    {\n        aeron_buffer_builder_t *buffer_builder = aeron_int64_to_ptr_hash_map_get(\n            &assembler->builder_by_session_id_map, header->frame->session_id);\n\n        if ((flags & AERON_DATA_HEADER_BEGIN_FLAG) == AERON_DATA_HEADER_BEGIN_FLAG)\n        {\n            if (NULL == buffer_builder)\n            {\n                if (aeron_buffer_builder_create(&buffer_builder) < 0 ||\n                    aeron_int64_to_ptr_hash_map_put(\n                        &assembler->builder_by_session_id_map, header->frame->session_id, buffer_builder) < 0)\n                {\n                    return AERON_ACTION_ABORT;\n                }\n            }\n\n            aeron_buffer_builder_reset(buffer_builder);\n            aeron_buffer_builder_capture_header(buffer_builder, header);\n            aeron_buffer_builder_append(buffer_builder, buffer, length);\n            aeron_buffer_builder_next_term_offset(buffer_builder, aeron_header_next_term_offset(header));\n        }\n        else if (NULL != buffer_builder)\n        {\n            if (buffer_builder->next_term_offset == header->frame->term_offset)\n            {\n                size_t limit = buffer_builder->limit;\n                aeron_buffer_builder_append(buffer_builder, buffer, length);\n\n                if ((flags & AERON_DATA_HEADER_END_FLAG) == AERON_DATA_HEADER_END_FLAG)\n                {\n                    action = assembler->delegate(\n                        assembler->delegate_clientd,\n                        buffer_builder->buffer,\n                        buffer_builder->limit,\n                        aeron_buffer_builder_complete_header(buffer_builder, header));\n\n                    if (AERON_ACTION_ABORT == action)\n                    {\n                        buffer_builder->limit = limit;\n                    }\n                    else\n                    {\n                        aeron_buffer_builder_reset(buffer_builder);\n                    }\n                }\n                else\n                {\n                    aeron_buffer_builder_next_term_offset(buffer_builder, aeron_header_next_term_offset(header));\n                }\n            }\n            else\n            {\n                aeron_buffer_builder_reset(buffer_builder);\n            }\n        }\n    }\n\n    return action;\n}\n\nextern void aeron_buffer_builder_reset(aeron_buffer_builder_t *buffer_builder);\n\nextern void aeron_buffer_builder_next_term_offset(aeron_buffer_builder_t *buffer_builder, int32_t next_term_offset);\n\nextern int aeron_buffer_builder_append(\n    aeron_buffer_builder_t *buffer_builder, const uint8_t *buffer, size_t length);\n\nextern void aeron_buffer_builder_capture_header(aeron_buffer_builder_t *buffer_builder, aeron_header_t *header);\n\nextern aeron_header_t *aeron_buffer_builder_complete_header(\n    aeron_buffer_builder_t *buffer_builder, aeron_header_t *header);\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_fragment_assembler.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_IMAGE_FRAGMENT_ASSEMBLER_H\n#define AERON_C_IMAGE_FRAGMENT_ASSEMBLER_H\n\n#include <string.h>\n\n#include \"aeronc.h\"\n#include \"collections/aeron_int64_to_ptr_hash_map.h\"\n#include \"aeron_image.h\"\n\ntypedef struct aeron_buffer_builder_stct\n{\n    uint8_t *buffer;\n    size_t buffer_length;\n    size_t limit;\n    int32_t next_term_offset;\n    int32_t first_frame_length;\n    aeron_header_t header;\n}\naeron_buffer_builder_t;\n\ntypedef struct aeron_image_fragment_assembler_stct\n{\n    aeron_fragment_handler_t delegate;\n    void *delegate_clientd;\n    aeron_buffer_builder_t *buffer_builder;\n}\naeron_image_fragment_assembler_t;\n\ntypedef struct aeron_image_controlled_fragment_assembler_stct\n{\n    aeron_controlled_fragment_handler_t delegate;\n    void *delegate_clientd;\n    aeron_buffer_builder_t *buffer_builder;\n}\naeron_image_controlled_fragment_assembler_t;\n\ntypedef struct aeron_fragment_assembler_stct\n{\n    aeron_fragment_handler_t delegate;\n    void *delegate_clientd;\n    aeron_int64_to_ptr_hash_map_t builder_by_session_id_map;\n}\naeron_fragment_assembler_t;\n\ntypedef struct aeron_controlled_fragment_assembler_stct\n{\n    aeron_controlled_fragment_handler_t delegate;\n    void *delegate_clientd;\n    aeron_int64_to_ptr_hash_map_t builder_by_session_id_map;\n}\naeron_controlled_fragment_assembler_t;\n\nint aeron_buffer_builder_create(aeron_buffer_builder_t **buffer_builder);\nint aeron_buffer_builder_find_suitable_capacity(size_t current_capacity, size_t required_capacity);\nint aeron_buffer_builder_ensure_capacity(aeron_buffer_builder_t *buffer_builder, size_t additional_capacity);\nvoid aeron_buffer_builder_delete(aeron_buffer_builder_t *buffer_builder);\n\ninline void aeron_buffer_builder_reset(aeron_buffer_builder_t *buffer_builder)\n{\n    buffer_builder->limit = 0;\n    buffer_builder->next_term_offset = -1;\n    buffer_builder->header.fragmented_frame_length = AERON_NULL_VALUE;\n    buffer_builder->header.context = NULL;\n}\n\ninline void aeron_buffer_builder_next_term_offset(aeron_buffer_builder_t *buffer_builder, int32_t next_term_offset)\n{\n    buffer_builder->next_term_offset = next_term_offset;\n}\n\ninline int aeron_buffer_builder_append(\n    aeron_buffer_builder_t *buffer_builder, const uint8_t *buffer, size_t length)\n{\n    if (aeron_buffer_builder_ensure_capacity(buffer_builder, length) < 0)\n    {\n        return -1;\n    }\n\n    memcpy(buffer_builder->buffer + buffer_builder->limit, buffer, length);\n    buffer_builder->limit += length;\n    return 0;\n}\n\ninline void aeron_buffer_builder_capture_header(aeron_buffer_builder_t *buffer_builder, aeron_header_t *header)\n{\n    buffer_builder->header.initial_term_id = header->initial_term_id;\n    buffer_builder->header.position_bits_to_shift = header->position_bits_to_shift;\n    buffer_builder->first_frame_length = header->frame->frame_header.frame_length;\n    memcpy(buffer_builder->header.frame, header->frame, sizeof(aeron_data_header_t));\n}\n\ninline aeron_header_t* aeron_buffer_builder_complete_header(aeron_buffer_builder_t *buffer_builder, aeron_header_t *header)\n{\n    buffer_builder->header.context = header->context;\n    aeron_frame_header_t *frame_header = &buffer_builder->header.frame->frame_header;\n\n    int32_t max_payload_length = buffer_builder->first_frame_length - (int32_t)AERON_DATA_HEADER_LENGTH;\n    int32_t fragmented_frame_length = (int32_t)aeron_logbuffer_compute_fragmented_length(\n        buffer_builder->limit, max_payload_length);\n    buffer_builder->header.fragmented_frame_length = fragmented_frame_length;\n\n    frame_header->frame_length = (int32_t)AERON_DATA_HEADER_LENGTH + (int32_t)buffer_builder->limit;\n    frame_header->flags |= header->frame->frame_header.flags;\n\n    return &buffer_builder->header;\n}\n\n#endif //AERON_C_IMAGE_FRAGMENT_ASSEMBLER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_image.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n\n#include \"aeron_image.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_log_buffer.h\"\n#include \"aeron_subscription.h\"\n\n_Static_assert(\n    sizeof(aeron_header_values_frame_t) == sizeof(aeron_data_header_t),\n    \"sizeof(aeron_header_values_frame_t) must match sizeof(aeron_data_header_t)\");\n_Static_assert(\n    offsetof(aeron_header_values_frame_t, frame_length) == offsetof(aeron_frame_header_t, frame_length),\n    \"offsetof(aeron_header_values_frame_t, frame_length) must match offsetof(aeron_frame_header_t, frame_length)\");\n_Static_assert(\n    offsetof(aeron_header_values_frame_t, version) == offsetof(aeron_frame_header_t, version),\n    \"offsetof(aeron_header_values_frame_t, version) must match offsetof(aeron_frame_header_t, version)\");\n_Static_assert(\n    offsetof(aeron_header_values_frame_t, flags) == offsetof(aeron_frame_header_t, flags),\n    \"offsetof(aeron_header_values_frame_t, flags) == offsetof(aeron_frame_header_t, flags)\");\n_Static_assert(\n    offsetof(aeron_header_values_frame_t, type) == offsetof(aeron_frame_header_t, type),\n    \"offsetof(aeron_header_values_frame_t, type) == offsetof(aeron_frame_header_t, type)\");\n_Static_assert(\n    offsetof(aeron_header_values_frame_t, term_offset) == offsetof(aeron_data_header_t, term_offset),\n    \"offsetof(aeron_header_values_frame_t, term_offset) == offsetof(aeron_data_header_t, term_offset)\");\n_Static_assert(\n    offsetof(aeron_header_values_frame_t, session_id) == offsetof(aeron_data_header_t, session_id),\n    \"offsetof(aeron_header_values_frame_t, session_id) == offsetof(aeron_data_header_t, session_id)\");\n_Static_assert(\n    offsetof(aeron_header_values_frame_t, stream_id) == offsetof(aeron_data_header_t, stream_id),\n    \"offsetof(aeron_header_values_frame_t, stream_id) == offsetof(aeron_data_header_t, stream_id)\");\n_Static_assert(\n    offsetof(aeron_header_values_frame_t, term_id) == offsetof(aeron_data_header_t, term_id),\n    \"offsetof(aeron_header_values_frame_t, term_id) == offsetof(aeron_data_header_t, term_id)\");\n_Static_assert(\n    offsetof(aeron_header_values_frame_t, reserved_value) == offsetof(aeron_data_header_t, reserved_value),\n    \"offsetof(aeron_header_values_frame_t, reserved_value) == offsetof(aeron_data_header_t, reserved_value)\");\n\nint aeron_image_create(\n    aeron_image_t **image,\n    aeron_subscription_t *subscription,\n    aeron_client_conductor_t *conductor,\n    aeron_log_buffer_t *log_buffer,\n    int32_t subscriber_position_id,\n    int64_t *subscriber_position,\n    int64_t correlation_id,\n    int32_t session_id,\n    const char *source_identity,\n    size_t source_identity_length)\n{\n    aeron_image_t *_image;\n\n    *image = NULL;\n    if (aeron_alloc((void **)&_image, sizeof(aeron_image_t)) < 0 ||\n        aeron_alloc((void **)&_image->source_identity, source_identity_length + 1) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate aeron_image and source_identity\");\n        return -1;\n    }\n\n    _image->command_base.type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_IMAGE;\n\n    memcpy(_image->source_identity, source_identity, source_identity_length);\n    _image->source_identity[source_identity_length] = '\\0';\n\n    _image->subscription = subscription;\n    _image->log_buffer = log_buffer;\n\n    _image->subscriber_position = subscriber_position;\n    _image->subscriber_position_id = subscriber_position_id;\n\n    _image->conductor = conductor;\n    _image->key.correlation_id = correlation_id;\n    _image->key.subscription_registration_id = subscription->registration_id;\n    _image->session_id = session_id;\n    _image->removal_change_number = INT64_MAX;\n    _image->final_position = 0;\n    _image->join_position = *subscriber_position;\n    _image->eos_position = INT64_MAX;\n    _image->refcnt = 1;\n\n    _image->metadata =\n        (aeron_logbuffer_metadata_t *)log_buffer->mapped_raw_log.log_meta_data.addr;\n    int32_t term_length = _image->metadata->term_length;\n\n    _image->term_length_mask = term_length - 1;\n    _image->position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes(term_length);\n\n    _image->is_closed = false;\n    _image->is_lingering = false;\n\n    *image = _image;\n\n    return 0;\n}\n\nint aeron_image_delete(aeron_image_t *image)\n{\n    aeron_free((void *)image->source_identity);\n    aeron_free(image);\n\n    return 0;\n}\n\nvoid aeron_image_close(aeron_image_t *image)\n{\n    AERON_GET_ACQUIRE(image->eos_position, image->metadata->end_of_stream_position);\n    image->final_position = aeron_counter_get_acquire(image->subscriber_position);\n    image->is_eos = image->final_position >= image->eos_position;\n    AERON_SET_RELEASE(image->is_closed, true);\n}\n\nint aeron_image_constants(aeron_image_t *image, aeron_image_constants_t *constants)\n{\n    if (NULL == image || NULL == constants)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, image: %s, constants: %s\",\n            AERON_NULL_STR(image),\n            AERON_NULL_STR(constants));\n        return -1;\n    }\n\n    constants->subscription = image->subscription;\n    constants->source_identity = image->source_identity;\n    constants->correlation_id = image->key.correlation_id;\n    constants->join_position = image->join_position;\n    constants->position_bits_to_shift = image->position_bits_to_shift;\n    constants->term_buffer_length = (size_t)image->term_length_mask + 1;\n    constants->mtu_length = (size_t)image->metadata->mtu_length;\n    constants->session_id = image->session_id;\n    constants->initial_term_id = image->metadata->initial_term_id;\n    constants->subscriber_position_id = image->subscriber_position_id;\n\n    return 0;\n}\n\nint64_t aeron_image_position(aeron_image_t *image)\n{\n    if (NULL == image)\n    {\n        AERON_SET_ERR(EINVAL, \"Parameters must not be null, image: %s\", AERON_NULL_STR(image));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return image->final_position;\n    }\n\n    return *image->subscriber_position;\n}\n\nint aeron_image_set_position(aeron_image_t *image, int64_t position)\n{\n    if (NULL == image)\n    {\n        AERON_SET_ERR(EINVAL, \"Parameters must not be null, image: %s\", AERON_NULL_STR(image));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (!is_closed)\n    {\n        if (aeron_image_validate_position(image, position) < 0)\n        {\n            return -1;\n        }\n\n        AERON_SET_RELEASE(*image->subscriber_position, position);\n    }\n\n    return 0;\n}\n\nbool aeron_image_is_end_of_stream(aeron_image_t *image)\n{\n    if (NULL == image)\n    {\n        AERON_SET_ERR(EINVAL, \"Parameters must not be null, image: %s\", AERON_NULL_STR(image));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return image->is_eos;\n    }\n\n    int64_t end_of_stream_position, subscriber_position;\n    AERON_GET_ACQUIRE(end_of_stream_position, image->metadata->end_of_stream_position);\n    AERON_GET_ACQUIRE(subscriber_position, *image->subscriber_position);\n\n    return subscriber_position >= end_of_stream_position;\n}\n\nint64_t aeron_image_end_of_stream_position(aeron_image_t *image)\n{\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return image->eos_position;\n    }\n\n    int64_t end_of_stream_position;\n    AERON_GET_ACQUIRE(end_of_stream_position, image->metadata->end_of_stream_position);\n\n    return end_of_stream_position;\n}\n\nint aeron_image_active_transport_count(aeron_image_t *image)\n{\n    if (NULL == image)\n    {\n        AERON_SET_ERR(EINVAL, \"Parameters must not be null, image: %s\", AERON_NULL_STR(image));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return 0;\n    }\n\n    int32_t active_transport_count;\n    AERON_GET_ACQUIRE(active_transport_count, image->metadata->active_transport_count);\n\n    return (int)active_transport_count;\n}\n\nbool aeron_image_is_publication_revoked(aeron_image_t *image)\n{\n    if (NULL == image)\n    {\n        AERON_SET_ERR(EINVAL, \"Parameters must not be null, image: %s\", AERON_NULL_STR(image));\n        return -1;\n    }\n\n    bool is_publication_revoked;\n\n    AERON_GET_ACQUIRE(is_publication_revoked, image->metadata->is_publication_revoked);\n\n    return is_publication_revoked;\n}\n\nint aeron_image_poll(aeron_image_t *image, aeron_fragment_handler_t handler, void *clientd, size_t fragment_limit)\n{\n    if (NULL == image || NULL == handler)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, image: %s, handler: %s\",\n            AERON_NULL_STR(image),\n            AERON_NULL_STR(handler));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return 0;\n    }\n\n    size_t fragments_read = 0;\n    const int64_t initial_position = *image->subscriber_position;\n    const size_t index = aeron_logbuffer_index_by_position(initial_position, image->position_bits_to_shift);\n    const uint8_t *term_buffer = image->log_buffer->mapped_raw_log.term_buffers[index].addr;\n    const int32_t initial_offset = (int32_t)initial_position & image->term_length_mask;\n    const int32_t capacity = (const int32_t)image->log_buffer->mapped_raw_log.term_length;\n    int32_t offset = initial_offset;\n\n    while (fragments_read < fragment_limit && offset < capacity)\n    {\n        AERON_GET_ACQUIRE(is_closed, image->is_closed);\n        if (is_closed)\n        {\n            break;\n        }\n\n        aeron_data_header_t *frame = (aeron_data_header_t *)(term_buffer + offset);\n        int32_t frame_length, frame_offset;\n\n        AERON_GET_ACQUIRE(frame_length, frame->frame_header.frame_length);\n\n        if (frame_length <= 0)\n        {\n            break;\n        }\n\n        frame_offset = offset;\n        offset += AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n        if (AERON_HDR_TYPE_PAD != frame->frame_header.type)\n        {\n            aeron_header_t header =\n                {\n                    frame,\n                    image->metadata->initial_term_id,\n                    image->position_bits_to_shift,\n                    AERON_NULL_VALUE,\n                    (void *)image\n                };\n\n            ++fragments_read;\n            handler(\n                clientd,\n                term_buffer + frame_offset + AERON_DATA_HEADER_LENGTH,\n                frame_length - AERON_DATA_HEADER_LENGTH,\n                &header);\n        }\n    }\n\n    int64_t new_position = initial_position + (offset - initial_offset);\n    if (new_position > initial_position)\n    {\n        aeron_counter_set_release(image->subscriber_position, new_position);\n    }\n\n    return (int)fragments_read;\n}\n\nint aeron_image_controlled_poll(\n    aeron_image_t *image, aeron_controlled_fragment_handler_t handler, void *clientd, size_t fragment_limit)\n{\n    if (NULL == image || NULL == handler)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, image: %s, handler: %s\",\n            AERON_NULL_STR(image),\n            AERON_NULL_STR(handler));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return 0;\n    }\n\n    size_t fragments_read = 0;\n    int64_t initial_position = *image->subscriber_position;\n    const size_t index = aeron_logbuffer_index_by_position(initial_position, image->position_bits_to_shift);\n    const uint8_t *term_buffer = image->log_buffer->mapped_raw_log.term_buffers[index].addr;\n    const int32_t capacity = (const int32_t)image->log_buffer->mapped_raw_log.term_length;\n    int32_t initial_offset = (int32_t)initial_position & image->term_length_mask;\n    int32_t offset = initial_offset;\n\n    while (fragments_read < fragment_limit && offset < capacity)\n    {\n        AERON_GET_ACQUIRE(is_closed, image->is_closed);\n        if (is_closed)\n        {\n            break;\n        }\n\n        aeron_data_header_t *frame = (aeron_data_header_t *)(term_buffer + offset);\n        int32_t frame_length, frame_offset, aligned_frame_length;\n\n        AERON_GET_ACQUIRE(frame_length, frame->frame_header.frame_length);\n\n        if (frame_length <= 0)\n        {\n            break;\n        }\n\n        frame_offset = offset;\n        aligned_frame_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n        offset += aligned_frame_length;\n\n        if (AERON_HDR_TYPE_PAD == frame->frame_header.type)\n        {\n            continue;\n        }\n\n        aeron_header_t header =\n            {\n                frame,\n                image->metadata->initial_term_id,\n                image->position_bits_to_shift,\n                AERON_NULL_VALUE,\n                (void *)image\n            };\n\n        ++fragments_read;\n        aeron_controlled_fragment_handler_action_t action = handler(\n            clientd,\n            term_buffer + frame_offset + AERON_DATA_HEADER_LENGTH,\n            frame_length - AERON_DATA_HEADER_LENGTH,\n            &header);\n\n        if (AERON_ACTION_ABORT == action)\n        {\n            --fragments_read;\n            offset -= aligned_frame_length;\n            break;\n        }\n\n        if (AERON_ACTION_BREAK == action)\n        {\n            break;\n        }\n\n        if (AERON_ACTION_COMMIT == action)\n        {\n            initial_position += (offset - initial_offset);\n            initial_offset = offset;\n            aeron_counter_set_release(image->subscriber_position, initial_position);\n        }\n    }\n\n    int64_t new_position = initial_position + (offset - initial_offset);\n    if (new_position > initial_position)\n    {\n        aeron_counter_set_release(image->subscriber_position, new_position);\n    }\n\n    return (int)fragments_read;\n}\n\nint aeron_image_bounded_poll(\n    aeron_image_t *image,\n    aeron_fragment_handler_t handler,\n    void *clientd,\n    int64_t limit_position,\n    size_t fragment_limit)\n{\n    if (NULL == image || NULL == handler)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, image: %s, handler: %s\",\n            AERON_NULL_STR(image),\n            AERON_NULL_STR(handler));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return 0;\n    }\n\n    const int64_t initial_position = *image->subscriber_position;\n    if (initial_position >= limit_position)\n    {\n        return 0;\n    }\n\n    size_t fragments_read = 0;\n    const size_t index = aeron_logbuffer_index_by_position(initial_position, image->position_bits_to_shift);\n    const uint8_t *term_buffer = image->log_buffer->mapped_raw_log.term_buffers[index].addr;\n    const int32_t initial_offset = (int32_t)initial_position & image->term_length_mask;\n    const int32_t capacity = (const int32_t)image->log_buffer->mapped_raw_log.term_length;\n    const int64_t high_limit_offset = limit_position - initial_position + initial_offset;\n    const int32_t limit_offset = (int64_t)capacity < high_limit_offset ? capacity : (int32_t)high_limit_offset;\n    int32_t offset = initial_offset;\n\n    while (fragments_read < fragment_limit && offset < limit_offset)\n    {\n        AERON_GET_ACQUIRE(is_closed, image->is_closed);\n        if (is_closed)\n        {\n            break;\n        }\n\n        aeron_data_header_t *frame = (aeron_data_header_t *)(term_buffer + offset);\n        int32_t frame_length, frame_offset;\n\n        AERON_GET_ACQUIRE(frame_length, frame->frame_header.frame_length);\n\n        if (frame_length <= 0)\n        {\n            break;\n        }\n\n        frame_offset = offset;\n        offset += AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n        if (AERON_HDR_TYPE_PAD != frame->frame_header.type)\n        {\n            aeron_header_t header =\n                {\n                    frame,\n                    image->metadata->initial_term_id,\n                    image->position_bits_to_shift,\n                    AERON_NULL_VALUE,\n                    (void *)image\n                };\n\n            ++fragments_read;\n            handler(\n                clientd,\n                term_buffer + frame_offset + AERON_DATA_HEADER_LENGTH,\n                frame_length - AERON_DATA_HEADER_LENGTH,\n                &header);\n        }\n    }\n\n    int64_t new_position = initial_position + (offset - initial_offset);\n    if (new_position > initial_position)\n    {\n        aeron_counter_set_release(image->subscriber_position, new_position);\n    }\n\n    return (int)fragments_read;\n}\n\nint aeron_image_bounded_controlled_poll(\n    aeron_image_t *image,\n    aeron_controlled_fragment_handler_t handler,\n    void *clientd,\n    int64_t limit_position,\n    size_t fragment_limit)\n{\n    if (NULL == image || NULL == handler)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, image: %s, handler: %s\",\n            AERON_NULL_STR(image),\n            AERON_NULL_STR(handler));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return 0;\n    }\n\n    int64_t initial_position = *image->subscriber_position;\n    if (initial_position >= limit_position)\n    {\n        return 0;\n    }\n\n    size_t fragments_read = 0;\n    const size_t index = aeron_logbuffer_index_by_position(initial_position, image->position_bits_to_shift);\n    const uint8_t *term_buffer = image->log_buffer->mapped_raw_log.term_buffers[index].addr;\n    const int32_t capacity = (const int32_t)image->log_buffer->mapped_raw_log.term_length;\n    int32_t initial_offset = (int32_t)initial_position & image->term_length_mask;\n    const int64_t high_limit_offset = limit_position - initial_position + initial_offset;\n    const int32_t limit_offset = (int64_t)capacity < high_limit_offset ? capacity : (int32_t)high_limit_offset;\n    int32_t offset = initial_offset;\n\n    while (fragments_read < fragment_limit && offset < limit_offset)\n    {\n        AERON_GET_ACQUIRE(is_closed, image->is_closed);\n        if (is_closed)\n        {\n            break;\n        }\n\n        aeron_data_header_t *frame = (aeron_data_header_t *)(term_buffer + offset);\n        int32_t frame_length, frame_offset, aligned_frame_length;\n\n        AERON_GET_ACQUIRE(frame_length, frame->frame_header.frame_length);\n\n        if (frame_length <= 0)\n        {\n            break;\n        }\n\n        frame_offset = offset;\n        aligned_frame_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n        offset += aligned_frame_length;\n\n        if (AERON_HDR_TYPE_PAD == frame->frame_header.type)\n        {\n            continue;\n        }\n\n        aeron_header_t header =\n            {\n                frame,\n                image->metadata->initial_term_id,\n                image->position_bits_to_shift,\n                AERON_NULL_VALUE,\n                (void *)image\n            };\n\n        ++fragments_read;\n        aeron_controlled_fragment_handler_action_t action = handler(\n            clientd,\n            term_buffer + frame_offset + AERON_DATA_HEADER_LENGTH,\n            frame_length - AERON_DATA_HEADER_LENGTH,\n            &header);\n\n        if (AERON_ACTION_ABORT == action)\n        {\n            --fragments_read;\n            offset -= aligned_frame_length;\n            break;\n        }\n\n        if (AERON_ACTION_BREAK == action)\n        {\n            break;\n        }\n\n        if (AERON_ACTION_COMMIT == action)\n        {\n            initial_position += (offset - initial_offset);\n            initial_offset = offset;\n            aeron_counter_set_release(image->subscriber_position, initial_position);\n        }\n    }\n\n    int64_t new_position = initial_position + (offset - initial_offset);\n    if (new_position > initial_position)\n    {\n        aeron_counter_set_release(image->subscriber_position, new_position);\n    }\n\n    return (int)fragments_read;\n}\n\nint64_t aeron_image_controlled_peek(\n    aeron_image_t *image,\n    int64_t initial_position,\n    aeron_controlled_fragment_handler_t handler,\n    void *clientd,\n    int64_t limit_position)\n{\n    if (NULL == image || NULL == handler)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, image: %s, handler: %s\",\n            AERON_NULL_STR(image),\n            AERON_NULL_STR(handler));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return initial_position;\n    }\n\n    if (aeron_image_validate_position(image, initial_position) < 0)\n    {\n        return -1;\n    }\n\n    if (initial_position >= limit_position)\n    {\n        return initial_position;\n    }\n\n    int64_t position = initial_position, resulting_position = initial_position;\n    const size_t index = aeron_logbuffer_index_by_position(initial_position, image->position_bits_to_shift);\n    const uint8_t *term_buffer = image->log_buffer->mapped_raw_log.term_buffers[index].addr;\n    const int32_t capacity = (const int32_t)image->log_buffer->mapped_raw_log.term_length;\n    int32_t initial_offset = (int32_t)initial_position & image->term_length_mask;\n    const int64_t high_limit_offset = limit_position - initial_position + initial_offset;\n    const int32_t limit_offset = (int64_t)capacity < high_limit_offset ? capacity : (int32_t)high_limit_offset;\n    int32_t offset = initial_offset;\n\n    while (offset < limit_offset)\n    {\n        AERON_GET_ACQUIRE(is_closed, image->is_closed);\n        if (is_closed)\n        {\n            break;\n        }\n\n        aeron_data_header_t *frame = (aeron_data_header_t *)(term_buffer + offset);\n        int32_t frame_length, frame_offset;\n\n        AERON_GET_ACQUIRE(frame_length, frame->frame_header.frame_length);\n\n        if (frame_length <= 0)\n        {\n            break;\n        }\n\n        frame_offset = offset;\n        offset += AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n        if (AERON_HDR_TYPE_PAD == frame->frame_header.type)\n        {\n            position += (offset - initial_offset);\n            initial_offset = offset;\n            resulting_position = position;\n\n            continue;\n        }\n\n        aeron_header_t header =\n            {\n                frame,\n                image->metadata->initial_term_id,\n                image->position_bits_to_shift,\n                AERON_NULL_VALUE,\n                (void *)image\n            };\n\n        aeron_controlled_fragment_handler_action_t action = handler(\n            clientd,\n            term_buffer + frame_offset + AERON_DATA_HEADER_LENGTH,\n            frame_length - AERON_DATA_HEADER_LENGTH,\n            &header);\n\n        if (AERON_ACTION_ABORT == action)\n        {\n            break;\n        }\n\n        position += (offset - initial_offset);\n        initial_offset = offset;\n\n        if (frame->frame_header.flags & AERON_DATA_HEADER_END_FLAG)\n        {\n            resulting_position = position;\n        }\n\n        if (AERON_ACTION_BREAK == action)\n        {\n            break;\n        }\n    }\n\n    return resulting_position;\n}\n\nint aeron_image_block_poll(\n    aeron_image_t *image, aeron_block_handler_t handler, void *clientd, size_t block_length_limit)\n{\n    if (NULL == image || NULL == handler)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, image: %s, handler: %s\",\n            AERON_NULL_STR(image),\n            AERON_NULL_STR(handler));\n        return -1;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    if (is_closed)\n    {\n        return 0;\n    }\n\n    const int64_t position = *image->subscriber_position;\n    const size_t index = aeron_logbuffer_index_by_position(position, image->position_bits_to_shift);\n    const uint8_t *term_buffer = image->log_buffer->mapped_raw_log.term_buffers[index].addr;\n    const int32_t offset = (int32_t)position & image->term_length_mask;\n    const int32_t capacity = (const int32_t)image->log_buffer->mapped_raw_log.term_length;\n    const int64_t high_limit_offset = (int64_t)(offset + block_length_limit);\n    const int32_t limit_offset = (int64_t)capacity < high_limit_offset ? capacity : (int32_t)high_limit_offset;\n    int32_t scan_offset = offset;\n\n    while (scan_offset < limit_offset)\n    {\n        aeron_data_header_t *frame = (aeron_data_header_t *)(term_buffer + scan_offset);\n        int32_t frame_length, aligned_frame_length;\n\n        AERON_GET_ACQUIRE(frame_length, frame->frame_header.frame_length);\n\n        if (frame_length <= 0)\n        {\n            break;\n        }\n\n        aligned_frame_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n        if (AERON_HDR_TYPE_PAD == frame->frame_header.type)\n        {\n            if (offset == scan_offset)\n            {\n                scan_offset += aligned_frame_length;\n            }\n\n            break;\n        }\n\n        if (scan_offset + aligned_frame_length > limit_offset)\n        {\n            break;\n        }\n\n        scan_offset += aligned_frame_length;\n    }\n\n    int32_t resulting_offset = scan_offset;\n    int32_t length = resulting_offset - offset;\n\n    if (resulting_offset > offset)\n    {\n        int32_t term_id = ((aeron_data_header_t *)(term_buffer + offset))->term_id;\n\n        handler(\n            clientd,\n            term_buffer + offset,\n            (size_t)length,\n            image->session_id,\n            term_id);\n    }\n\n    aeron_counter_set_release(image->subscriber_position, position + length);\n\n    return (int)length;\n}\n\nbool aeron_image_is_closed(aeron_image_t *image)\n{\n    bool is_closed = true;\n\n    if (NULL != image)\n    {\n        AERON_GET_ACQUIRE(is_closed, image->is_closed);\n    }\n\n    return is_closed;\n}\n\nint aeron_image_reject(aeron_image_t *image, const char *reason)\n{\n    return aeron_subscription_reject_image(\n        image->subscription,\n        image->key.correlation_id,\n        aeron_image_position(image),\n        reason);\n}\n\nextern int64_t aeron_image_removal_change_number(aeron_image_t *image);\n\nextern bool aeron_image_is_in_use_by_subscription(aeron_image_t *image, int64_t last_change_number);\n\nextern int aeron_image_validate_position(aeron_image_t *image, int64_t position);\n\nextern int64_t aeron_image_incr_refcnt(aeron_image_t *image);\n\nextern int64_t aeron_image_decr_refcnt(aeron_image_t *image);\n\nextern int64_t aeron_image_refcnt_acquire(aeron_image_t *image);\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_image.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_IMAGE_H\n#define AERON_C_IMAGE_H\n\n#include <inttypes.h>\n\n#include \"aeron_agent.h\"\n#include \"aeron_context.h\"\n#include \"aeron_client_conductor.h\"\n\ntypedef struct aeron_image_key_stct\n{\n    int64_t correlation_id;\n    int64_t subscription_registration_id;\n}\naeron_image_key_t;\n\ntypedef struct aeron_image_stct\n{\n    aeron_client_command_base_t command_base;\n    aeron_client_conductor_t *conductor;\n    char *source_identity;\n\n    aeron_subscription_t *subscription;\n    aeron_log_buffer_t *log_buffer;\n    aeron_logbuffer_metadata_t *metadata;\n\n    volatile int64_t *subscriber_position;\n\n    aeron_image_key_t key;\n    int64_t removal_change_number;\n    int64_t join_position;\n    int64_t final_position;\n    int64_t eos_position;\n    volatile int64_t refcnt;\n\n    int32_t session_id;\n    int32_t term_length_mask;\n\n    int32_t subscriber_position_id;\n\n    size_t position_bits_to_shift;\n\n    volatile bool is_closed;\n    bool is_eos;\n    bool is_lingering;\n}\naeron_image_t;\n\ntypedef struct aeron_header_stct\n{\n    aeron_data_header_t *frame;\n    int32_t initial_term_id;\n    size_t position_bits_to_shift;\n    int32_t fragmented_frame_length;\n    void *context;\n}\naeron_header_t;\n\nint aeron_image_create(\n    aeron_image_t **image,\n    aeron_subscription_t *subscription,\n    aeron_client_conductor_t *conductor,\n    aeron_log_buffer_t *log_buffer,\n    int32_t subscriber_position_id,\n    int64_t *subscriber_position,\n    int64_t correlation_id,\n    int32_t session_id,\n    const char *source_identity,\n    size_t source_identity_length);\n\nint aeron_image_delete(aeron_image_t *image);\nvoid aeron_image_close(aeron_image_t *image);\n\ninline int64_t aeron_image_removal_change_number(aeron_image_t *image)\n{\n    return image->removal_change_number;\n}\n\ninline bool aeron_image_is_in_use_by_subscription(aeron_image_t *image, int64_t last_change_number)\n{\n    return image->removal_change_number > last_change_number;\n}\n\ninline int aeron_image_validate_position(aeron_image_t *image, int64_t position)\n{\n    const int64_t current_position = *image->subscriber_position;\n    const int64_t limit_position =\n        (current_position - (current_position & image->term_length_mask)) + image->term_length_mask + 1;\n\n    if (position < current_position ||  position > limit_position)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"%\" PRId64 \" position out of range %\" PRId64 \"-%\" PRId64,\n            position,\n            current_position,\n            limit_position);\n        return -1;\n    }\n\n    if (0 != (position & (AERON_LOGBUFFER_FRAME_ALIGNMENT - 1)))\n    {\n        AERON_SET_ERR(EINVAL, \"position (%\" PRId64 \") not aligned to FRAME_ALIGNMENT\", position);\n        return -1;\n    }\n\n    return 0;\n}\n\ninline int64_t aeron_image_incr_refcnt(aeron_image_t *image)\n{\n    int64_t result;\n    AERON_GET_AND_ADD_INT64(result, image->refcnt, INT64_C(1));\n    return result + 1;\n}\n\ninline int64_t aeron_image_decr_refcnt(aeron_image_t *image)\n{\n    int64_t result;\n    AERON_GET_AND_ADD_INT64(result, image->refcnt, INT64_C(-1));\n    return result - 1;\n}\n\ninline int64_t aeron_image_refcnt_acquire(aeron_image_t *image)\n{\n    int64_t value;\n    AERON_GET_ACQUIRE(value, image->refcnt);\n    return value;\n}\n\n#endif //AERON_C_IMAGE_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_log_buffer.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <inttypes.h>\n\n#include \"aeron_log_buffer.h\"\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n\nint aeron_log_buffer_create(\n    aeron_log_buffer_t **log_buffer, const char *log_file, int64_t correlation_id, bool pre_touch)\n{\n    aeron_log_buffer_t *_log_buffer = NULL;\n\n    *log_buffer = NULL;\n    if (aeron_alloc((void **)&_log_buffer, sizeof(aeron_log_buffer_t)) < 0)\n    {\n        AERON_APPEND_ERR(\n            \"Unable to allocate log buffer, log_file: %s, correlation_id: %\" PRId64, log_file, correlation_id);\n        return -1;\n    }\n\n    if (aeron_raw_log_map_existing(&_log_buffer->mapped_raw_log, log_file, pre_touch) < 0)\n    {\n        AERON_APPEND_ERR(\"Unable to map raw log for log buffer, correlation_id: %\" PRId64, correlation_id);\n        aeron_free(_log_buffer);\n        return -1;\n    }\n\n    _log_buffer->correlation_id = correlation_id;\n    _log_buffer->refcnt = 0;\n\n    *log_buffer = _log_buffer;\n    return 0;\n}\n\nint aeron_log_buffer_delete(aeron_log_buffer_t *log_buffer)\n{\n    if (NULL != log_buffer)\n    {\n        aeron_raw_log_close(&log_buffer->mapped_raw_log, NULL);\n        aeron_free(log_buffer);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_log_buffer.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_LOG_BUFFER_H\n#define AERON_C_LOG_BUFFER_H\n\n#include \"aeronc.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_context.h\"\n\ntypedef struct aeron_log_buffer_stct\n{\n    aeron_mapped_raw_log_t mapped_raw_log;\n\n    int64_t correlation_id;\n    int64_t refcnt;\n}\naeron_log_buffer_t;\n\nint aeron_log_buffer_create(\n    aeron_log_buffer_t **log_buffer, const char *log_file, int64_t correlation_id, bool pre_touch);\nint aeron_log_buffer_delete(aeron_log_buffer_t *log_buffer);\n\n#endif //AERON_C_LOG_BUFFER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_publication.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <inttypes.h>\n\n#include \"aeronc.h\"\n#include \"aeron_publication.h\"\n#include \"aeron_log_buffer.h\"\n#include \"status/aeron_local_sockaddr.h\"\n\nint aeron_publication_create(\n    aeron_publication_t **publication,\n    aeron_client_conductor_t *conductor,\n    const char *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t position_limit_counter_id,\n    int64_t *position_limit_addr,\n    int32_t channel_status_indicator_id,\n    int64_t *channel_status_addr,\n    aeron_log_buffer_t *log_buffer,\n    int64_t original_registration_id,\n    int64_t registration_id)\n{\n    aeron_publication_t *_publication;\n\n    *publication = NULL;\n    if (aeron_alloc((void **)&_publication, sizeof(aeron_publication_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"Unable to allocate publication, registration_id: %\" PRId64, registration_id);\n        return -1;\n    }\n\n    _publication->command_base.type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_PUBLICATION;\n\n    _publication->log_buffer = log_buffer;\n    _publication->log_meta_data = (aeron_logbuffer_metadata_t *)log_buffer->mapped_raw_log.log_meta_data.addr;\n\n    _publication->position_limit_counter_id = position_limit_counter_id;\n    _publication->position_limit = position_limit_addr;\n    _publication->channel_status_indicator_id = channel_status_indicator_id;\n    _publication->channel_status_indicator = channel_status_addr;\n\n    _publication->conductor = conductor;\n    _publication->channel = channel;\n    _publication->registration_id = registration_id;\n    _publication->original_registration_id = original_registration_id;\n    _publication->stream_id = stream_id;\n    _publication->session_id = session_id;\n    _publication->is_closed = false;\n\n    size_t term_length = (size_t)_publication->log_meta_data->term_length;\n\n    _publication->max_possible_position = ((int64_t)term_length << 31);\n    _publication->max_payload_length = (size_t)(_publication->log_meta_data->mtu_length - AERON_DATA_HEADER_LENGTH);\n    _publication->max_message_length = aeron_compute_max_message_length(term_length);\n    _publication->position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes((int32_t)term_length);\n    _publication->initial_term_id = _publication->log_meta_data->initial_term_id;\n\n    *publication = _publication;\n    return 0;\n}\n\nint aeron_publication_delete(aeron_publication_t *publication)\n{\n    aeron_free((void *)publication->channel);\n    aeron_free(publication);\n\n    return 0;\n}\n\nvoid aeron_publication_force_close(aeron_publication_t *publication)\n{\n    AERON_SET_RELEASE(publication->is_closed, true);\n}\n\nint aeron_publication_close(\n    aeron_publication_t *publication, aeron_notification_t on_close_complete, void *on_close_complete_clientd)\n{\n    if (NULL != publication)\n    {\n        bool is_closed;\n        AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n        if (!is_closed)\n        {\n            AERON_SET_RELEASE(publication->is_closed, true);\n            return aeron_client_conductor_async_close_publication(\n                publication->conductor, publication, on_close_complete, on_close_complete_clientd);\n        }\n    }\n\n    return 0;\n}\n\nstatic int64_t aeron_publication_get_and_add_raw_tail(volatile int64_t *addr, size_t aligned_length)\n{\n    int64_t result;\n    AERON_GET_AND_ADD_INT64(result, *addr, (int64_t)aligned_length);\n    return result;\n}\n\nstatic int64_t aeron_publication_raw_tail_volatile(volatile int64_t *addr)\n{\n    int64_t raw_tail;\n    AERON_GET_ACQUIRE(raw_tail, *addr);\n    return raw_tail;\n}\n\nstatic void aeron_publication_header_write(\n    aeron_publication_t *publication,\n    aeron_mapped_buffer_t *term_buffer,\n    int32_t offset,\n    size_t length,\n    int32_t term_id)\n{\n    aeron_data_header_t *header = (aeron_data_header_t *)(term_buffer->addr + offset);\n\n    AERON_SET_RELEASE(header->frame_header.frame_length, (-(int32_t)length));\n    aeron_release();\n\n    header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    header->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n    header->frame_header.type = AERON_HDR_TYPE_DATA;\n    header->term_offset = offset;\n    header->session_id = publication->session_id;\n    header->stream_id = publication->stream_id;\n    header->term_id = term_id;\n}\n\nstatic int64_t aeron_publication_handle_end_of_log_condition(\n    aeron_publication_t *publication,\n    aeron_mapped_buffer_t *term_buffer,\n    int32_t term_offset,\n    int32_t term_length,\n    int32_t term_id,\n    int64_t position)\n{\n    if (term_offset < term_length)\n    {\n        const int32_t padding_length = term_length - term_offset;\n        aeron_data_header_t *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n\n        aeron_publication_header_write(publication, term_buffer, term_offset, (size_t)padding_length, term_id);\n        header->frame_header.type = AERON_HDR_TYPE_PAD;\n        AERON_SET_RELEASE(header->frame_header.frame_length, padding_length);\n    }\n\n    if (position >= publication->max_possible_position)\n    {\n        return AERON_PUBLICATION_MAX_POSITION_EXCEEDED;\n    }\n\n    int32_t term_count = aeron_logbuffer_compute_term_count(term_id, publication->initial_term_id);\n    aeron_logbuffer_rotate_log(publication->log_meta_data, term_count, term_id);\n\n    return AERON_PUBLICATION_ADMIN_ACTION;\n}\n\nstatic int64_t aeron_publication_claim(\n    aeron_publication_t *publication,\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    size_t length,\n    aeron_buffer_claim_t *buffer_claim)\n{\n    const size_t frame_length = length + AERON_DATA_HEADER_LENGTH;\n    const int32_t aligned_frame_length = (int32_t)AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t raw_tail = aeron_publication_get_and_add_raw_tail(\n        term_tail_counter, (size_t)aligned_frame_length);\n    const int32_t term_length = (int32_t)term_buffer->length;\n    const int32_t term_offset = aeron_logbuffer_term_offset(raw_tail, term_length);\n    const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n\n    int32_t resulting_offset = term_offset + aligned_frame_length;\n    int64_t position = aeron_logbuffer_compute_position(\n        term_id, resulting_offset, publication->position_bits_to_shift, publication->initial_term_id);\n    if (resulting_offset > term_length)\n    {\n        return aeron_publication_handle_end_of_log_condition(\n            publication, term_buffer, term_offset, term_length, term_id, position);\n    }\n    else\n    {\n        aeron_publication_header_write(publication, term_buffer, term_offset, frame_length, term_id);\n        buffer_claim->frame_header = term_buffer->addr + term_offset;\n        buffer_claim->data = buffer_claim->frame_header + AERON_DATA_HEADER_LENGTH;\n        buffer_claim->length = length;\n    }\n\n    return position;\n}\n\nstatic int64_t aeron_publication_append_unfragmented_message(\n    aeron_publication_t *publication,\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    const uint8_t *buffer,\n    size_t length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd)\n{\n    const size_t frame_length = length + AERON_DATA_HEADER_LENGTH;\n    const int32_t aligned_frame_length = (int32_t)AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t raw_tail = aeron_publication_get_and_add_raw_tail(\n        term_tail_counter, (size_t)aligned_frame_length);\n    const int32_t term_length = (int32_t)term_buffer->length;\n    const int32_t term_offset = aeron_logbuffer_term_offset(raw_tail, term_length);\n    const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n\n    int32_t resulting_offset = term_offset + aligned_frame_length;\n    int64_t position = aeron_logbuffer_compute_position(\n        term_id, resulting_offset, publication->position_bits_to_shift, publication->initial_term_id);\n    if (resulting_offset > term_length)\n    {\n        return aeron_publication_handle_end_of_log_condition(\n            publication, term_buffer, term_offset, term_length, term_id, position);\n    }\n    else\n    {\n        aeron_publication_header_write(publication, term_buffer, term_offset, frame_length, term_id);\n        memcpy(term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, buffer, length);\n\n        aeron_data_header_t *data_header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n\n        if (NULL != reserved_value_supplier)\n        {\n            data_header->reserved_value = reserved_value_supplier(\n                clientd, term_buffer->addr + term_offset, frame_length);\n        }\n\n        AERON_SET_RELEASE(data_header->frame_header.frame_length, (int32_t)frame_length);\n    }\n\n    return position;\n}\n\nstatic int64_t aeron_publication_append_fragmented_message(\n    aeron_publication_t *publication,\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    const uint8_t *buffer,\n    size_t length,\n    size_t max_payload_length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd)\n{\n    const size_t framed_length = aeron_logbuffer_compute_fragmented_length(length, max_payload_length);\n    const int64_t raw_tail = aeron_publication_get_and_add_raw_tail(term_tail_counter, framed_length);\n    const int32_t term_length = (int32_t)term_buffer->length;\n    const int32_t term_offset = aeron_logbuffer_term_offset(raw_tail, term_length);\n    const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n\n    int32_t resulting_offset = term_offset + (int32_t)framed_length;\n    int64_t position = aeron_logbuffer_compute_position(\n        term_id, resulting_offset, publication->position_bits_to_shift, publication->initial_term_id);\n    if (resulting_offset > term_length)\n    {\n        return aeron_publication_handle_end_of_log_condition(\n            publication, term_buffer, term_offset, term_length, term_id, position);\n    }\n    else\n    {\n        uint8_t flags = AERON_DATA_HEADER_BEGIN_FLAG;\n        size_t remaining = length;\n        int32_t frame_offset = term_offset;\n\n        do\n        {\n            size_t bytes_to_write = remaining < max_payload_length ? remaining : max_payload_length;\n            size_t frame_length = bytes_to_write + AERON_DATA_HEADER_LENGTH;\n            size_t aligned_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n            aeron_publication_header_write(publication, term_buffer, frame_offset, frame_length, term_id);\n            memcpy(\n                term_buffer->addr + frame_offset + AERON_DATA_HEADER_LENGTH,\n                buffer + (length - remaining),\n                bytes_to_write);\n\n            if (remaining <= max_payload_length)\n            {\n                flags |= AERON_DATA_HEADER_END_FLAG;\n            }\n\n            aeron_data_header_t *data_header = (aeron_data_header_t *)(term_buffer->addr + frame_offset);\n            data_header->frame_header.flags = flags;\n\n            if (NULL != reserved_value_supplier)\n            {\n                data_header->reserved_value = reserved_value_supplier(\n                    clientd, term_buffer->addr + term_offset, frame_length);\n            }\n\n            AERON_SET_RELEASE(data_header->frame_header.frame_length, (int32_t)frame_length);\n\n            flags = 0;\n            frame_offset += (int32_t)aligned_length;\n            remaining -= bytes_to_write;\n        }\n        while (remaining > 0);\n    }\n\n    return position;\n}\n\nstatic int64_t aeron_publication_append_unfragmented_messagev(\n    aeron_publication_t *publication,\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    aeron_iovec_t *iov,\n    size_t length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd)\n{\n    const size_t frame_length = length + AERON_DATA_HEADER_LENGTH;\n    const int32_t aligned_frame_length = (int32_t)AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t raw_tail = aeron_publication_get_and_add_raw_tail(\n        term_tail_counter, (size_t)aligned_frame_length);\n    const int32_t term_length = (int32_t)term_buffer->length;\n    const int32_t term_offset = aeron_logbuffer_term_offset(raw_tail, term_length);\n    const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n\n    int32_t resulting_offset = term_offset + aligned_frame_length;\n    int64_t position = aeron_logbuffer_compute_position(\n        term_id, resulting_offset, publication->position_bits_to_shift, publication->initial_term_id);\n    if (resulting_offset > term_length)\n    {\n        return aeron_publication_handle_end_of_log_condition(\n            publication, term_buffer, term_offset, term_length, term_id, position);\n    }\n    else\n    {\n        aeron_publication_header_write(publication, term_buffer, term_offset, frame_length, term_id);\n\n        aeron_data_header_t *data_header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n        int32_t offset = (int32_t)(term_offset + AERON_DATA_HEADER_LENGTH);\n        size_t i = 0;\n\n        for (int32_t ending_offset = offset + (int32_t)length;\n            offset < ending_offset;\n            offset += (int32_t)iov[i].iov_len, i++)\n        {\n            memcpy(term_buffer->addr + offset, iov[i].iov_base, iov[i].iov_len);\n        }\n\n        if (NULL != reserved_value_supplier)\n        {\n            data_header->reserved_value = reserved_value_supplier(\n                clientd, term_buffer->addr + term_offset, frame_length);\n        }\n\n        AERON_SET_RELEASE(data_header->frame_header.frame_length, (int32_t)frame_length);\n    }\n\n    return position;\n}\n\nstatic int64_t aeron_publication_append_fragmented_messagev(\n    aeron_publication_t *publication,\n    aeron_mapped_buffer_t *term_buffer,\n    volatile int64_t *term_tail_counter,\n    aeron_iovec_t *iov,\n    size_t length,\n    size_t max_payload_length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd)\n{\n    const size_t framed_length = aeron_logbuffer_compute_fragmented_length(length, max_payload_length);\n    const int64_t raw_tail = aeron_publication_get_and_add_raw_tail(term_tail_counter, framed_length);\n    const int32_t term_length = (int32_t)term_buffer->length;\n    const int32_t term_offset = aeron_logbuffer_term_offset(raw_tail, term_length);\n    const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n\n    int32_t resulting_offset = term_offset + (int32_t)framed_length;\n    int64_t position = aeron_logbuffer_compute_position(\n        term_id, resulting_offset, publication->position_bits_to_shift, publication->initial_term_id);\n    if (resulting_offset > term_length)\n    {\n        return aeron_publication_handle_end_of_log_condition(\n            publication, term_buffer, term_offset, term_length, term_id, position);\n    }\n    else\n    {\n        uint8_t flags = AERON_DATA_HEADER_BEGIN_FLAG;\n        size_t remaining = length, i = 0;\n        int32_t frame_offset = term_offset;\n        int32_t current_buffer_offset = 0;\n\n        do\n        {\n            int32_t bytes_to_write = remaining < max_payload_length ?\n                (int32_t)remaining : (int32_t)max_payload_length;\n            size_t frame_length = bytes_to_write + AERON_DATA_HEADER_LENGTH;\n            size_t aligned_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n            aeron_publication_header_write(publication, term_buffer, frame_offset, frame_length, term_id);\n\n            int32_t bytes_written = 0;\n            int32_t payload_offset = (int32_t)(frame_offset + AERON_DATA_HEADER_LENGTH);\n\n            do\n            {\n                int32_t current_buffer_remaining = (int32_t)iov[i].iov_len - current_buffer_offset;\n                int32_t num_bytes = (bytes_to_write - bytes_written) < current_buffer_remaining ?\n                    (bytes_to_write - bytes_written) : current_buffer_remaining;\n                memcpy(term_buffer->addr + payload_offset, iov[i].iov_base + current_buffer_offset, (size_t)num_bytes);\n\n                bytes_written += num_bytes;\n                payload_offset += num_bytes;\n                current_buffer_offset += num_bytes;\n\n                if (current_buffer_remaining <= num_bytes)\n                {\n                    i++;\n                    current_buffer_offset = 0;\n                }\n            }\n            while (bytes_written < bytes_to_write);\n\n            if (remaining <= max_payload_length)\n            {\n                flags |= AERON_DATA_HEADER_END_FLAG;\n            }\n\n            aeron_data_header_t *data_header = (aeron_data_header_t *)(term_buffer->addr + frame_offset);\n            data_header->frame_header.flags = flags;\n\n            if (NULL != reserved_value_supplier)\n            {\n                data_header->reserved_value = reserved_value_supplier(\n                    clientd, term_buffer->addr + frame_offset, frame_length);\n            }\n\n            AERON_SET_RELEASE(data_header->frame_header.frame_length, ((int32_t)frame_length));\n\n            flags = 0;\n            frame_offset += (int32_t)aligned_length;\n            remaining -= bytes_to_write;\n        }\n        while (remaining > 0);\n    }\n\n    return position;\n}\n\nint64_t aeron_publication_offer(\n    aeron_publication_t *publication,\n    const uint8_t *buffer,\n    size_t length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd)\n{\n    int64_t new_position = AERON_PUBLICATION_CLOSED;\n\n    if (NULL == publication || NULL == buffer)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, publication: %s, buffer: %s\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(buffer));\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (!is_closed)\n    {\n        const int64_t limit = aeron_counter_get_acquire(publication->position_limit);\n        const int32_t term_count = aeron_logbuffer_active_term_count(publication->log_meta_data);\n        const size_t index = aeron_logbuffer_index_by_term_count(term_count);\n        const int64_t raw_tail = aeron_publication_raw_tail_volatile(\n            &publication->log_meta_data->term_tail_counters[index]);\n        const int32_t term_length = publication->log_meta_data->term_length;\n        const int32_t term_offset = aeron_logbuffer_term_offset(raw_tail, term_length);\n        const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n\n        if (term_count != aeron_logbuffer_compute_term_count(term_id, publication->initial_term_id))\n        {\n            return AERON_PUBLICATION_ADMIN_ACTION;\n        }\n\n        const int64_t position = aeron_logbuffer_compute_position(\n            term_id, term_offset, publication->position_bits_to_shift, publication->initial_term_id);\n        if (position < limit)\n        {\n            if (length <= publication->max_payload_length)\n            {\n                new_position = aeron_publication_append_unfragmented_message(\n                    publication,\n                    &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                    &publication->log_meta_data->term_tail_counters[index],\n                    buffer,\n                    length,\n                    reserved_value_supplier,\n                    clientd);\n            }\n            else\n            {\n                if (length > publication->max_message_length)\n                {\n                    AERON_SET_ERR(\n                        EINVAL,\n                        \"aeron_publication_offer: length=%\" PRIu64 \" > max_message_length=%\" PRIu64,\n                        (uint64_t)length,\n                        (uint64_t)publication->max_message_length);\n                    return AERON_PUBLICATION_ERROR;\n                }\n\n                new_position = aeron_publication_append_fragmented_message(\n                    publication,\n                    &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                    &publication->log_meta_data->term_tail_counters[index],\n                    buffer,\n                    length,\n                    publication->max_payload_length,\n                    reserved_value_supplier,\n                    clientd);\n            }\n        }\n        else\n        {\n            new_position = aeron_publication_back_pressure_status(publication, position, (int32_t)length);\n        }\n    }\n\n    return new_position;\n}\n\nint64_t aeron_publication_offerv(\n    aeron_publication_t *publication,\n    aeron_iovec_t *iov,\n    size_t iovcnt,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd)\n{\n    int64_t new_position = AERON_PUBLICATION_CLOSED;\n\n    if (NULL == publication || NULL == iov)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, publication: %s, iov: %s\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(iov));\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    size_t length = 0;\n    for (size_t i = 0; i < iovcnt; i++)\n    {\n        length += iov[i].iov_len;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (!is_closed)\n    {\n        const int64_t limit = aeron_counter_get_acquire(publication->position_limit);\n        const int32_t term_count = aeron_logbuffer_active_term_count(publication->log_meta_data);\n        const size_t index = aeron_logbuffer_index_by_term_count(term_count);\n        const int64_t raw_tail = aeron_publication_raw_tail_volatile(\n            &publication->log_meta_data->term_tail_counters[index]);\n        const int32_t term_length = publication->log_meta_data->term_length;\n        const int32_t term_offset = aeron_logbuffer_term_offset(raw_tail, term_length);\n        const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n\n        if (term_count != aeron_logbuffer_compute_term_count(term_id, publication->initial_term_id))\n        {\n            return AERON_PUBLICATION_ADMIN_ACTION;\n        }\n\n        const int64_t position = aeron_logbuffer_compute_position(\n            term_id, term_offset, publication->position_bits_to_shift, publication->initial_term_id);\n\n        if (position < limit)\n        {\n            if (length <= publication->max_payload_length)\n            {\n                new_position = aeron_publication_append_unfragmented_messagev(\n                    publication,\n                    &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                    &publication->log_meta_data->term_tail_counters[index],\n                    iov,\n                    length,\n                    reserved_value_supplier,\n                    clientd);\n            }\n            else\n            {\n                if (length > publication->max_message_length)\n                {\n                    AERON_SET_ERR(\n                        EINVAL,\n                        \"aeron_publication_offerv: length=%\" PRIu64 \" > max_message_length=%\" PRIu64,\n                        (uint64_t)length,\n                        (uint64_t)publication->max_message_length);\n                    return AERON_PUBLICATION_ERROR;\n                }\n\n                new_position = aeron_publication_append_fragmented_messagev(\n                    publication,\n                    &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                    &publication->log_meta_data->term_tail_counters[index],\n                    iov,\n                    length,\n                    publication->max_payload_length,\n                    reserved_value_supplier,\n                    clientd);\n            }\n        }\n        else\n        {\n            new_position = aeron_publication_back_pressure_status(publication, position, (int32_t)length);\n        }\n    }\n\n    return new_position;\n}\n\nint64_t aeron_publication_try_claim(aeron_publication_t *publication, size_t length, aeron_buffer_claim_t *buffer_claim)\n{\n    int64_t new_position = AERON_PUBLICATION_CLOSED;\n\n    if (NULL == publication || NULL == buffer_claim)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, publication: %s, buffer_claim: %s\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(buffer_claim));\n        return AERON_PUBLICATION_ERROR;\n    }\n    else if (length > publication->max_payload_length)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"aeron_publication_try_claim: length=%\" PRIu64 \" > max_payload_length=%\" PRIu64,\n            (uint64_t)length,\n            (uint64_t)publication->max_payload_length);\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (!is_closed)\n    {\n        const int64_t limit = aeron_counter_get_acquire(publication->position_limit);\n        const int32_t term_count = aeron_logbuffer_active_term_count(publication->log_meta_data);\n        const size_t index = aeron_logbuffer_index_by_term_count(term_count);\n        const int64_t raw_tail = aeron_publication_raw_tail_volatile(\n            &publication->log_meta_data->term_tail_counters[index]);\n        const int32_t term_length = publication->log_meta_data->term_length;\n        const int32_t term_offset = aeron_logbuffer_term_offset(raw_tail, term_length);\n        const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n\n        if (term_count != aeron_logbuffer_compute_term_count(term_id, publication->initial_term_id))\n        {\n            return AERON_PUBLICATION_ADMIN_ACTION;\n        }\n\n        const int64_t position = aeron_logbuffer_compute_position(\n            term_id, term_offset, publication->position_bits_to_shift, publication->initial_term_id);\n        if (position < limit)\n        {\n            new_position = aeron_publication_claim(\n                publication,\n                &publication->log_buffer->mapped_raw_log.term_buffers[index],\n                &publication->log_meta_data->term_tail_counters[index],\n                length,\n                buffer_claim);\n        }\n        else\n        {\n            new_position = aeron_publication_back_pressure_status(publication, position, (int32_t)length);\n        }\n    }\n\n    return new_position;\n}\n\nbool aeron_publication_is_closed(aeron_publication_t *publication)\n{\n    bool is_closed = true;\n\n    if (NULL != publication)\n    {\n        AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    }\n\n    return is_closed;\n}\n\nbool aeron_publication_is_connected(aeron_publication_t *publication)\n{\n    if (NULL != publication && !aeron_publication_is_closed(publication))\n    {\n        int32_t is_connected;\n\n        AERON_GET_ACQUIRE(is_connected, publication->log_meta_data->is_connected);\n        return 1 == is_connected;\n    }\n\n    return false;\n}\n\nint aeron_publication_constants(aeron_publication_t *publication, aeron_publication_constants_t *constants)\n{\n    if (NULL == publication || NULL == constants)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, publication: %s, constants: %s\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(constants));\n        return -1;\n    }\n\n    constants->channel = publication->channel;\n    constants->original_registration_id = publication->original_registration_id;\n    constants->registration_id = publication->registration_id;\n    constants->max_possible_position = publication->max_possible_position;\n    constants->position_bits_to_shift = publication->position_bits_to_shift;\n    constants->term_buffer_length = (size_t)publication->log_meta_data->term_length;\n    constants->max_message_length = publication->max_message_length;\n    constants->max_payload_length = publication->max_payload_length;\n    constants->stream_id = publication->stream_id;\n    constants->session_id = publication->session_id;\n    constants->initial_term_id = publication->initial_term_id;\n    constants->publication_limit_counter_id = publication->position_limit_counter_id;\n    constants->channel_status_indicator_id = publication->channel_status_indicator_id;\n\n    return 0;\n}\n\nint64_t aeron_publication_channel_status(aeron_publication_t *publication)\n{\n    if (NULL != publication && NULL != publication->channel_status_indicator &&\n        !aeron_publication_is_closed(publication))\n    {\n        int64_t value;\n        AERON_GET_ACQUIRE(value, *publication->channel_status_indicator);\n\n        return value;\n    }\n\n    return AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_NO_ID_ALLOCATED;\n}\n\nint64_t aeron_publication_position(aeron_publication_t *publication)\n{\n    if (NULL == publication)\n    {\n        AERON_SET_ERR(EINVAL, \"Parameters must not be null, publication: %s\", AERON_NULL_STR(publication));\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (is_closed)\n    {\n        return AERON_PUBLICATION_CLOSED;\n    }\n\n    const int32_t term_count = aeron_logbuffer_active_term_count(publication->log_meta_data);\n    const size_t index = aeron_logbuffer_index_by_term_count(term_count);\n    const int64_t raw_tail = aeron_publication_raw_tail_volatile(\n        &publication->log_meta_data->term_tail_counters[index]);\n    const int32_t term_length = publication->log_meta_data->term_length;\n    const int32_t term_offset = aeron_logbuffer_term_offset(raw_tail, term_length);\n    const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n    const int64_t position = aeron_logbuffer_compute_position(\n        term_id, term_offset, publication->position_bits_to_shift, publication->initial_term_id);\n\n    return position;\n}\n\nint64_t aeron_publication_position_limit(aeron_publication_t *publication)\n{\n    if (NULL == publication)\n    {\n        AERON_SET_ERR(EINVAL, \"Parameters must not be null, publication: %s\", AERON_NULL_STR(publication));\n        return AERON_PUBLICATION_ERROR;\n    }\n\n    bool is_closed;\n    AERON_GET_ACQUIRE(is_closed, publication->is_closed);\n    if (is_closed)\n    {\n        return AERON_PUBLICATION_CLOSED;\n    }\n\n    return aeron_counter_get_acquire(publication->position_limit);\n}\n\nextern int64_t aeron_publication_new_position(\n    aeron_publication_t *publication,\n    int32_t term_count,\n    int32_t term_offset,\n    int32_t term_id,\n    int64_t position,\n    int32_t resulting_offset);\n\nextern int64_t aeron_publication_back_pressure_status(\n    aeron_publication_t *publication, int64_t current_position, int32_t message_length);\n\nconst char *aeron_publication_channel(aeron_publication_t *publication)\n{\n    return publication->channel;\n}\n\nint32_t aeron_publication_stream_id(aeron_publication_t *publication)\n{\n    return publication->stream_id;\n}\n\nint32_t aeron_publication_session_id(aeron_publication_t *publication)\n{\n    return publication->session_id;\n}\n\nint aeron_publication_local_sockaddrs(\n    aeron_publication_t *publication, aeron_iovec_t *address_vec, size_t address_vec_len)\n{\n    if (NULL == publication || NULL == address_vec)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, publication: %s, address_vec: %s\",\n            AERON_NULL_STR(publication),\n            AERON_NULL_STR(address_vec));\n        return -1;\n    }\n    if (address_vec_len < 1)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must be valid, address_vec_len (%\" PRIu64 \") < 1\", (uint64_t)address_vec_len);\n        return -1;\n    }\n\n    return aeron_local_sockaddr_find_addrs(\n        &publication->conductor->counters_reader,\n        publication->channel_status_indicator_id,\n        address_vec,\n        address_vec_len);\n}\n\nint aeron_buffer_claim_commit(aeron_buffer_claim_t *buffer_claim)\n{\n    if (NULL != buffer_claim && NULL != buffer_claim->frame_header)\n    {\n        aeron_data_header_t *data_header = (aeron_data_header_t *)buffer_claim->frame_header;\n\n        AERON_SET_RELEASE(\n            data_header->frame_header.frame_length, (int32_t)buffer_claim->length + AERON_DATA_HEADER_LENGTH);\n    }\n\n    return 0;\n}\n\nint aeron_buffer_claim_abort(aeron_buffer_claim_t *buffer_claim)\n{\n    if (NULL != buffer_claim)\n    {\n        aeron_data_header_t *data_header = (aeron_data_header_t *)buffer_claim->frame_header;\n\n        data_header->frame_header.type = AERON_HDR_TYPE_PAD;\n        AERON_SET_RELEASE(\n            data_header->frame_header.frame_length, (int32_t)buffer_claim->length + AERON_DATA_HEADER_LENGTH);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_publication.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_PUBLICATION_H\n#define AERON_C_PUBLICATION_H\n\n#include \"aeronc.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_context.h\"\n#include \"aeron_client_conductor.h\"\n\ntypedef struct aeron_publication_stct\n{\n    aeron_client_command_base_t command_base;\n    aeron_client_conductor_t *conductor;\n    const char *channel;\n\n    aeron_log_buffer_t *log_buffer;\n    aeron_logbuffer_metadata_t *log_meta_data;\n\n    volatile int64_t *position_limit;\n    volatile int64_t *channel_status_indicator;\n\n    int64_t registration_id;\n    int64_t original_registration_id;\n    int32_t stream_id;\n    int32_t session_id;\n\n    int64_t max_possible_position;\n    size_t max_payload_length;\n    size_t max_message_length;\n    size_t position_bits_to_shift;\n    int32_t initial_term_id;\n\n    int32_t position_limit_counter_id;\n    int32_t channel_status_indicator_id;\n\n    aeron_notification_t on_close_complete;\n    void *on_close_complete_clientd;\n\n    volatile bool is_closed;\n}\naeron_publication_t;\n\nint aeron_publication_create(\n    aeron_publication_t **publication,\n    aeron_client_conductor_t *conductor,\n    const char *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t position_limit_counter_id,\n    int64_t *position_limit_addr,\n    int32_t channel_status_indicator_id,\n    int64_t *channel_status_addr,\n    aeron_log_buffer_t *log_buffer,\n    int64_t original_registration_id,\n    int64_t registration_id);\n\nint aeron_publication_delete(aeron_publication_t *publication);\nvoid aeron_publication_force_close(aeron_publication_t *publication);\n\ninline int64_t aeron_publication_back_pressure_status(\n    aeron_publication_t *publication, int64_t current_position, int32_t message_length)\n{\n    if ((current_position +\n        (int32_t)AERON_ALIGN(message_length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT)) >=\n        publication->max_possible_position)\n    {\n        return AERON_PUBLICATION_MAX_POSITION_EXCEEDED;\n    }\n\n    int32_t is_connected;\n    AERON_GET_ACQUIRE(is_connected, publication->log_meta_data->is_connected);\n    if (1 == is_connected)\n    {\n        return AERON_PUBLICATION_BACK_PRESSURED;\n    }\n\n    return AERON_PUBLICATION_NOT_CONNECTED;\n}\n\n#endif //AERON_C_PUBLICATION_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_socket.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_socket.h\"\n#include \"util/aeron_error.h\"\n#include \"command/aeron_control_protocol.h\"\n#include \"aeron_alloc.h\"\n#include \"util/aeron_netutil.h\"\n\n#if defined(AERON_COMPILER_GCC)\n\n#include <fcntl.h>\n#include <unistd.h>\n#include <sys/socket.h>\n#include <errno.h>\n#include <poll.h>\n\nint aeron_net_init(void)\n{\n    return 0;\n}\n\nint aeron_set_socket_non_blocking(aeron_socket_t fd)\n{\n    int flags;\n    if ((flags = fcntl(fd, F_GETFL, 0)) < 0)\n    {\n        AERON_SET_ERR(errno, \"failed to fcntl(fd=%d, cmd=F_GETFL, 0)\", fd);\n        return -1;\n    }\n\n    flags |= O_NONBLOCK;\n    if (fcntl(fd, F_SETFL, flags) < 0)\n    {\n        AERON_SET_ERR(errno, \"failed to fcntl(fd=%d, cmd=F_SETFL, %d)\", fd, flags);\n        return -1;\n    }\n\n    return 0;\n}\n\naeron_socket_t aeron_socket(int domain, int type, int protocol)\n{\n    int socket_fd = socket(domain, type, protocol);\n\n    if (socket_fd < 0)\n    {\n        AERON_SET_ERR(errno, \"failed to socket(domain=%d, type=%d, protocol=%d)\", domain, type, protocol);\n        return -1;\n    }\n\n    return socket_fd;\n}\n\nvoid aeron_close_socket(aeron_socket_t socket)\n{\n    close(socket);\n}\n\nint aeron_connect(aeron_socket_t fd, struct sockaddr *address, socklen_t address_length)\n{\n    if (connect(fd, address, address_length) < 0)\n    {\n        char addr_str[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n        aeron_format_source_identity(addr_str, sizeof(addr_str), (struct sockaddr_storage *)address);\n        AERON_SET_ERR(errno, \"failed to connect to address: %s\", addr_str);\n\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_bind(aeron_socket_t fd, struct sockaddr *address, socklen_t address_length)\n{\n    if (bind(fd, address, address_length) < 0)\n    {\n        char buffer[AERON_NETUTIL_FORMATTED_MAX_LENGTH] = { 0 };\n        aeron_format_source_identity(buffer, AERON_NETUTIL_FORMATTED_MAX_LENGTH, (struct sockaddr_storage *)address);\n        AERON_SET_ERR(errno, \"failed to bind(%d, %s)\", fd, buffer);\n        return -1;\n    }\n\n    return 0;\n}\n\n\nint aeron_getifaddrs(struct ifaddrs **ifap)\n{\n    if (getifaddrs(ifap) < 0)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Failed getifaddrs(...)\");\n        return -1;\n    }\n\n    return 0;\n}\n\nvoid aeron_freeifaddrs(struct ifaddrs *ifa)\n{\n    freeifaddrs(ifa);\n}\n\nssize_t aeron_sendmsg(aeron_socket_t fd, struct msghdr *msghdr, int flags)\n{\n    ssize_t result = sendmsg(fd, msghdr, flags);\n\n    if (result < 0)\n    {\n        if (EAGAIN == errno || EWOULDBLOCK == errno || ECONNREFUSED == errno || EINTR == errno)\n        {\n            return 0;\n        }\n        else\n        {\n            AERON_SET_ERR(errno, \"failed sendmsg(fd=%d,...)\", fd);\n            return -1;\n        }\n    }\n\n    return result;\n}\n\nssize_t aeron_send(aeron_socket_t fd, const void *buf, size_t len, int flags)\n{\n    ssize_t result = send(fd, buf, len, flags);\n\n    if (result < 0)\n    {\n        if (EAGAIN == errno || EWOULDBLOCK == errno || ECONNREFUSED == errno || EINTR == errno)\n        {\n            return 0;\n        }\n        else\n        {\n            AERON_SET_ERR(errno, \"failed send(fd=%d,...)\", fd);\n            return -1;\n        }\n    }\n\n    return result;\n}\n\nssize_t aeron_recvmsg(aeron_socket_t fd, struct msghdr *msghdr, int flags)\n{\n    ssize_t result = recvmsg(fd, msghdr, flags);\n\n    if (result < 0)\n    {\n        if (EAGAIN == errno || EWOULDBLOCK == errno || ECONNREFUSED == errno || EINTR == errno)\n        {\n            return 0;\n        }\n\n        AERON_SET_ERR(errno, \"failed recvmsg(fd=%d,...)\", fd);\n        return -1;\n    }\n\n    return result;\n}\n\nint aeron_poll(struct pollfd *fds, unsigned long nfds, int timeout)\n{\n    int result = poll(fds, (nfds_t)nfds, timeout);\n    if (result < 0)\n    {\n        if (EAGAIN == errno || EWOULDBLOCK == errno || EINTR == errno)\n        {\n            result = 0;\n        }\n        else\n        {\n            AERON_SET_ERR(errno, \"%s\", \"failed to poll(...)\");\n            return -1;\n        }\n    }\n\n    return result;\n}\n\nint aeron_getsockopt(aeron_socket_t fd, int level, int optname, void *optval, socklen_t *optlen)\n{\n    if (getsockopt(fd, level, optname, optval, optlen) < 0)\n    {\n        AERON_SET_ERR(errno, \"getsockopt(fd=%d,...)\", fd);\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_setsockopt(aeron_socket_t fd, int level, int optname, const void *optval, socklen_t optlen)\n{\n    if (setsockopt(fd, level, optname, optval, optlen) < 0)\n    {\n        AERON_SET_ERR(errno, \"setsockopt(fd=%d,...)\", fd);\n        return -1;\n    }\n\n    return 0;\n}\n\n#elif defined(AERON_COMPILER_MSVC)\n\n#if _WIN32_WINNT < 0x0600\n#error Unsupported windows version\n#endif\n#if UNICODE\n#error Unicode errors not supported\n#endif\n\n\n#include <ws2ipdef.h>\n#include <iphlpapi.h>\n\nint aeron_net_init()\n{\n    static int started = -1;\n\n    if (-1 == started)\n    {\n        WORD wVersionRequested = MAKEWORD(2, 2);\n        WSADATA buffer = { 0 };\n        int err = WSAStartup(wVersionRequested, &buffer);\n\n        if (0 != err)\n        {\n            AERON_SET_ERR_WIN(err, \"%s\", \"WSAStartup(...)\");\n            return -1;\n        }\n\n        started = 0;\n    }\n\n    return 0;\n}\n\nint aeron_set_socket_non_blocking(aeron_socket_t fd)\n{\n    u_long mode = 1;\n    const int result = ioctlsocket(fd, FIONBIO, &mode);\n    if (SOCKET_ERROR == result)\n    {\n        AERON_SET_ERR_WIN(WSAGetLastError(), \"ioctlsocket(fd=%d,...)\", fd);\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_getifaddrs(struct ifaddrs **ifap)\n{\n    DWORD max_tries = 2;\n    DWORD adapters_addresses_size = 10 * sizeof(IP_ADAPTER_ADDRESSES);\n    IP_ADAPTER_ADDRESSES *adapters_addresses = NULL;\n\n    /* loop to handle interfaces coming online causing a buffer overflow\n     * between first call to list buffer length and second call to enumerate.\n     */\n    for (unsigned i = max_tries; i; i--)\n    {\n        if (aeron_alloc((void **)&adapters_addresses, adapters_addresses_size) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"unable to allocate IP_ADAPTER_ADDRESSES\");\n            return -1;\n        }\n\n        DWORD result = GetAdaptersAddresses(\n            AF_UNSPEC,\n            GAA_FLAG_INCLUDE_PREFIX |\n                GAA_FLAG_SKIP_ANYCAST |\n                GAA_FLAG_SKIP_DNS_SERVER |\n                GAA_FLAG_SKIP_FRIENDLY_NAME |\n                GAA_FLAG_SKIP_MULTICAST,\n            NULL,\n            adapters_addresses,\n            &adapters_addresses_size);\n\n        if (ERROR_BUFFER_OVERFLOW == result)\n        {\n            aeron_free(adapters_addresses);\n            adapters_addresses = NULL;\n        }\n        else if (ERROR_SUCCESS == result)\n        {\n            break;\n        }\n        else\n        {\n            aeron_free(adapters_addresses);\n            AERON_SET_ERR_WIN(result, \"%s\", \"GetAdaptersAddresses(...)\");\n            return -1;\n        }\n    }\n\n    struct ifaddrs *head = NULL;\n    struct ifaddrs *tail = NULL;\n\n    /* now populate list */\n    for (IP_ADAPTER_ADDRESSES *adapter = adapters_addresses; adapter; adapter = adapter->Next)\n    {\n        int unicast_index = 0;\n        for (IP_ADAPTER_UNICAST_ADDRESS *unicast = adapter->FirstUnicastAddress;\n            unicast;\n            unicast = unicast->Next, ++unicast_index)\n        {\n            /* ensure IP adapter */\n            if (AF_INET != unicast->Address.lpSockaddr->sa_family &&\n                AF_INET6 != unicast->Address.lpSockaddr->sa_family)\n            {\n                continue;\n            }\n\n            struct ifaddrs *current = NULL;\n            DWORD supplemental_data_length = 2 * unicast->Address.iSockaddrLength + IF_NAMESIZE;\n\n            if (aeron_alloc((void **)&current, sizeof(struct ifaddrs) + supplemental_data_length) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"unable to allocate ifaddrs\");\n                aeron_freeifaddrs(head);\n                aeron_free(adapters_addresses);\n                return -1;\n            }\n\n            if (NULL == head)\n            {\n                head = current;\n                tail = head;\n            }\n            else\n            {\n                tail->ifa_next = current;\n                tail = current;\n            }\n\n            uint8_t *supplemental_data = (uint8_t *)(current + 1);\n            current->ifa_addr = (struct sockaddr *)(supplemental_data);\n            current->ifa_netmask = (struct sockaddr *)(supplemental_data + unicast->Address.iSockaddrLength);\n            current->ifa_name = (char *)(supplemental_data + (2 * unicast->Address.iSockaddrLength));\n\n            /* address */\n            memcpy(current->ifa_addr, unicast->Address.lpSockaddr, unicast->Address.iSockaddrLength);\n\n            /* name */\n            strncpy_s(current->ifa_name, IF_NAMESIZE, adapter->AdapterName, _TRUNCATE);\n\n            /* flags */\n            current->ifa_flags = 0;\n            if (IfOperStatusUp == adapter->OperStatus)\n            {\n                current->ifa_flags |= IFF_UP;\n            }\n\n            if (IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType)\n            {\n                current->ifa_flags |= IFF_LOOPBACK;\n            }\n\n            if (!(adapter->Flags & IP_ADAPTER_NO_MULTICAST))\n            {\n                current->ifa_flags |= IFF_MULTICAST;\n            }\n\n            /* netmask */\n            ULONG prefixLength = unicast->OnLinkPrefixLength;\n\n            /* map prefix to netmask */\n            current->ifa_netmask->sa_family = unicast->Address.lpSockaddr->sa_family;\n\n            switch (unicast->Address.lpSockaddr->sa_family)\n            {\n                case AF_INET:\n                    if (0 == prefixLength || prefixLength > 32)\n                    {\n                        prefixLength = 32;\n                    }\n\n                    ULONG Mask;\n                    ConvertLengthToIpv4Mask(prefixLength, &Mask);\n                    ((struct sockaddr_in *)current->ifa_netmask)->sin_addr.s_addr = htonl(Mask);\n                    break;\n\n                case AF_INET6:\n                    if (0 == prefixLength || prefixLength > 128)\n                    {\n                        prefixLength = 128;\n                    }\n\n                    for (LONG i = (LONG)prefixLength, j = 0; i > 0; i -= 8, ++j)\n                    {\n                        ((struct sockaddr_in6 *)current->ifa_netmask)->sin6_addr.s6_addr[j] = i >= 8 ?\n                            0xff : (ULONG)((0xffU << (8 - i)) & 0xffU);\n                    }\n                    break;\n\n                default:\n                    break;\n            }\n        }\n    }\n\n    aeron_free(adapters_addresses);\n    *ifap = head;\n\n    return 0;\n}\n\nvoid aeron_freeifaddrs(struct ifaddrs *current)\n{\n    if (NULL != current)\n    {\n        while (1)\n        {\n            struct ifaddrs *next = current->ifa_next;\n            aeron_free(current);\n            current = next;\n\n            if (NULL == current)\n            {\n                break;\n            }\n        }\n    }\n}\n\nssize_t aeron_recvmsg(aeron_socket_t fd, struct msghdr *msghdr, int flags)\n{\n    DWORD size = 0;\n    const int result = WSARecvFrom(\n        fd,\n        (LPWSABUF)msghdr->msg_iov,\n        msghdr->msg_iovlen,\n        &size,\n        &msghdr->msg_flags,\n        msghdr->msg_name,\n        &msghdr->msg_namelen,\n        NULL,\n        NULL);\n\n    if (SOCKET_ERROR == result)\n    {\n        const int err = WSAGetLastError();\n        if (WSAEWOULDBLOCK == err || WSAEINTR == err || WSAECONNRESET == err)\n        {\n            return 0;\n        }\n\n        AERON_SET_ERR_WIN(err, \"WSARecvFrom(fd=%d,...)\", fd);\n        return -1;\n    }\n\n    return size;\n}\n\nssize_t aeron_send(aeron_socket_t fd, const void *buf, size_t len, int flags)\n{\n    const DWORD size = send(fd, (const char *)buf, (int)len, flags);\n\n    if (SOCKET_ERROR == size)\n    {\n        const int err = WSAGetLastError();\n        if (WSAEWOULDBLOCK == err || WSAEINTR == err)\n        {\n            return 0;\n        }\n\n        AERON_SET_ERR_WIN(err, \"send(fd=%d,...)\", fd);\n        return -1;\n    }\n\n    return size;\n}\n\nssize_t aeron_sendmsg(aeron_socket_t fd, struct msghdr *msghdr, int flags)\n{\n    DWORD size = 0;\n    const int result = WSASendTo(\n        fd,\n        (LPWSABUF)msghdr->msg_iov,\n        msghdr->msg_iovlen,\n        &size,\n        msghdr->msg_flags,\n        (const struct sockaddr *)msghdr->msg_name,\n        msghdr->msg_namelen,\n        NULL,\n        NULL);\n\n    if (SOCKET_ERROR == result)\n    {\n        const int err = WSAGetLastError();\n        if (WSAEWOULDBLOCK == err || WSAEINTR == err)\n        {\n            return 0;\n        }\n\n        AERON_SET_ERR_WIN(err, \"WSASendTo(fd=%d,...)\", fd);\n        return -1;\n    }\n\n    return size;\n}\n\nint aeron_poll(struct pollfd *fds, unsigned long nfds, int timeout)\n{\n    int result = WSAPoll(fds, (ULONG)nfds, timeout);\n\n    if (SOCKET_ERROR == result)\n    {\n        const int err = WSAGetLastError();\n\n        AERON_SET_ERR_WIN(err, \"%s\", \"WSAPoll(...)\");\n        return -1;\n    }\n\n    return result;\n}\n\naeron_socket_t aeron_socket(int domain, int type, int protocol)\n{\n    aeron_net_init();\n    const SOCKET handle = socket(domain, type, protocol);\n    if (INVALID_SOCKET == handle)\n    {\n        AERON_SET_ERR_WIN(\n            WSAGetLastError(), \"failed to socket(domain=%d, type=%d, protocol=%d)\", domain, type, protocol);\n        return (aeron_socket_t)-1;\n    }\n\n    return (aeron_socket_t)handle;\n}\n\nvoid aeron_close_socket(aeron_socket_t socket)\n{\n    closesocket(socket);\n}\n\nint aeron_connect(aeron_socket_t fd, struct sockaddr *address, socklen_t address_length)\n{\n    if (SOCKET_ERROR == connect(fd, address, address_length))\n    {\n        char addr_str[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n        aeron_format_source_identity(addr_str, sizeof(addr_str), (struct sockaddr_storage *)address);\n        struct sockaddr_in *a = (struct sockaddr_in *) address;\n        printf(\"addr: %lu, %d\\n\", a->sin_addr.s_addr, a->sin_port);\n        AERON_SET_ERR_WIN(WSAGetLastError(), \"failed to connect to address: %s\", addr_str);\n\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_bind(aeron_socket_t fd, struct sockaddr *address, socklen_t address_length)\n{\n    if (SOCKET_ERROR == bind(fd, address, address_length))\n    {\n        char addr_str[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n        aeron_format_source_identity(addr_str, sizeof(addr_str), (struct sockaddr_storage *)address);\n        AERON_SET_ERR_WIN(WSAGetLastError(), \"failed to bind to address: %s\", addr_str);\n\n        return -1;\n    }\n\n    return 0;\n}\n\n/* aeron_getsockopt and aeron_setsockopt ensure a consistent signature between platforms\n * (MSVC uses char * instead of void * for optval, which causes warnings)\n */\nint aeron_getsockopt(aeron_socket_t fd, int level, int optname, void *optval, socklen_t *optlen)\n{\n    if (SOCKET_ERROR == getsockopt(fd, level, optname, optval, optlen))\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"getsockopt(fd=%d,...)\", fd);\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_setsockopt(aeron_socket_t fd, int level, int optname, const void *optval, socklen_t optlen)\n{\n    if (SOCKET_ERROR == setsockopt(fd, level, optname, optval, optlen))\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"setsockopt(fd=%d,...)\", fd);\n        return -1;\n    }\n\n    return 0;\n}\n\n#else\n#error Unsupported platform!\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_socket.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_SOCKET_H\n#define AERON_SOCKET_H\n\n#include <stdint.h>\n#include \"util/aeron_platform.h\"\n\n#if defined(AERON_COMPILER_GCC)\n\n#include <netinet/in.h>\n#include <sys/socket.h>\n#include <net/if.h>\n#include <netinet/ip.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n#include <ifaddrs.h>\n#include <poll.h>\n\ntypedef int aeron_socket_t;\n\n#elif defined(AERON_COMPILER_MSVC)\n\n#include <WinSock2.h>\n#include <Windows.h>\n#include <ws2ipdef.h>\n#include <WS2tcpip.h>\n#include <iphlpapi.h>\n\n// SOCKET is uint64_t but we need a signed type to match the Linux version\ntypedef int64_t aeron_socket_t;\n\nstruct iovec\n{\n    ULONG iov_len;\n    void *iov_base;\n};\n\n// must match _WSAMSG\nstruct msghdr {\n    void *msg_name;\n    INT msg_namelen;\n    struct iovec *msg_iov;\n    ULONG msg_iovlen;\n    ULONG msg_controllen;\n    void *msg_control;\n    ULONG msg_flags;\n};\n\nstruct ifaddrs\n{\n    struct ifaddrs *ifa_next;\n    char *ifa_name;\n    unsigned int ifa_flags;\n\n    struct sockaddr *ifa_addr;\n    struct sockaddr *ifa_netmask;\n    union\n    {\n        struct sockaddr *ifu_broadaddr;\n        struct sockaddr *ifu_dstaddr;\n    }\n    ifa_ifu;\n\n# ifndef ifa_broadaddr\n#  define ifa_broadaddr      ifa_ifu.ifu_broadaddr\n# endif\n# ifndef ifa_dstaddr\n#  define ifa_dstaddr        ifa_ifu.ifu_dstaddr\n# endif\n\n    void *ifa_data;\n};\n\ntypedef SSIZE_T ssize_t;\n\n#else\n#error Unsupported platform!\n#endif\n\nint aeron_set_socket_non_blocking(aeron_socket_t fd);\n\naeron_socket_t aeron_socket(int domain, int type, int protocol);\n\nvoid aeron_close_socket(aeron_socket_t socket);\n\nint aeron_connect(aeron_socket_t fd, struct sockaddr *address, socklen_t address_length);\n\nint aeron_bind(aeron_socket_t fd, struct sockaddr *address, socklen_t address_length);\n\nint aeron_net_init(void);\n\nint aeron_getsockopt(aeron_socket_t fd, int level, int optname, void *optval, socklen_t *optlen);\n\nint aeron_setsockopt(aeron_socket_t fd, int level, int optname, const void *optval, socklen_t optlen);\n\nint aeron_getifaddrs(struct ifaddrs **ifap);\n\nvoid aeron_freeifaddrs(struct ifaddrs *ifa);\n\nssize_t aeron_sendmsg(aeron_socket_t fd, struct msghdr *msghdr, int flags);\n\nssize_t aeron_send(aeron_socket_t fd, const void *buf, size_t len, int flags);\n\nssize_t aeron_recvmsg(aeron_socket_t fd, struct msghdr *msghdr, int flags);\n\nint aeron_poll(struct pollfd *fds, unsigned long nfds, int timeout);\n\n#endif //AERON_SOCKET_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_subscription.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n\n#include \"aeron_subscription.h\"\n#include \"aeron_image.h\"\n#include \"status/aeron_local_sockaddr.h\"\n#include \"uri/aeron_uri.h\"\n\nint aeron_subscription_create(\n    aeron_subscription_t **subscription,\n    aeron_client_conductor_t *conductor,\n    const char *channel,\n    int32_t stream_id,\n    int64_t registration_id,\n    int32_t channel_status_indicator_id,\n    int64_t *channel_status_indicator_addr,\n    aeron_on_available_image_t on_available_image,\n    void *on_available_image_clientd,\n    aeron_on_unavailable_image_t on_unavailable_image,\n    void *on_unavailable_image_clientd)\n{\n    aeron_subscription_t *_subscription;\n\n    *subscription = NULL;\n    if (aeron_alloc((void **)&_subscription, sizeof(aeron_subscription_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"Unable to allocate subscription, registration_id: %\" PRId64, registration_id);\n        return -1;\n    }\n\n    _subscription->command_base.type = AERON_CLIENT_MANAGED_RESOURCE_TYPE_SUBSCRIPTION;\n\n    _subscription->conductor_fields.image_lists_head.next_list = NULL;\n    _subscription->conductor_fields.next_change_number = 0;\n    _subscription->last_image_list_change_number = -1;\n\n    if (aeron_subscription_alloc_image_list(&_subscription->conductor_fields.image_lists_head.next_list, 0) < 0)\n    {\n        return -1;\n    }\n\n    _subscription->channel_status_indicator_id = channel_status_indicator_id;\n    _subscription->channel_status_indicator = channel_status_indicator_addr;\n\n    _subscription->conductor = conductor;\n    _subscription->channel = channel;\n    _subscription->registration_id = registration_id;\n    _subscription->stream_id = stream_id;\n    _subscription->on_available_image = on_available_image;\n    _subscription->on_available_image_clientd = on_available_image_clientd;\n    _subscription->on_unavailable_image = on_unavailable_image;\n    _subscription->on_unavailable_image_clientd = on_unavailable_image_clientd;\n\n    _subscription->round_robin_index = 0;\n    _subscription->is_closed = false;\n\n    *subscription = _subscription;\n\n    return 0;\n}\n\nint aeron_subscription_delete(aeron_subscription_t *subscription)\n{\n    aeron_image_list_t *volatile prune_lists_head = &subscription->conductor_fields.image_lists_head;\n\n    while (NULL != prune_lists_head->next_list)\n    {\n        aeron_image_list_t *volatile prune_list = prune_lists_head->next_list;\n\n        prune_lists_head->next_list = prune_list->next_list;\n        aeron_free((void *)prune_list);\n    }\n\n    aeron_free((void *)subscription->channel);\n    aeron_free(subscription);\n\n    return 0;\n}\n\nvoid aeron_subscription_force_close(aeron_subscription_t *subscription)\n{\n    AERON_SET_RELEASE(subscription->is_closed, true);\n}\n\nint aeron_subscription_close(\n    aeron_subscription_t *subscription, aeron_notification_t on_close_complete, void *on_close_complete_clientd)\n{\n    if (NULL != subscription)\n    {\n        bool is_closed;\n\n        AERON_GET_ACQUIRE(is_closed, subscription->is_closed);\n        if (!is_closed)\n        {\n            AERON_SET_RELEASE(subscription->is_closed, true);\n            return aeron_client_conductor_async_close_subscription(\n                subscription->conductor, subscription, on_close_complete, on_close_complete_clientd);\n        }\n    }\n\n    return 0;\n}\n\nint aeron_subscription_alloc_image_list(aeron_image_list_t *volatile *image_list, size_t length)\n{\n    aeron_image_list_t *_image_list;\n\n    *image_list = NULL;\n    if (aeron_alloc((void **)&_image_list, AERON_IMAGE_LIST_ALLOC_SIZE(length)) < 0)\n    {\n        AERON_APPEND_ERR(\"Unable to allocate image list, length: %\" PRIu64, (uint64_t)length);\n        return -1;\n    }\n\n    _image_list->change_number = -1;\n    _image_list->array = 0 == length ? NULL : (aeron_image_t **)((uint8_t *)_image_list + sizeof(aeron_image_list_t));\n    _image_list->length = (uint32_t)length;\n    _image_list->next_list = NULL;\n\n    *image_list = _image_list;\n\n    return 0;\n}\n\nint aeron_client_conductor_subscription_add_image(aeron_subscription_t *subscription, aeron_image_t *image)\n{\n    aeron_image_list_t *volatile current_image_list = subscription->conductor_fields.image_lists_head.next_list;\n    aeron_image_list_t *volatile new_image_list;\n    size_t old_length = current_image_list->length;\n\n    if (aeron_subscription_alloc_image_list(&new_image_list, old_length + 1) < 0)\n    {\n        return -1;\n    }\n\n    for (size_t i = 0; i < old_length; i++)\n    {\n        new_image_list->array[i] = current_image_list->array[i];\n    }\n\n    new_image_list->array[old_length] = image;\n\n    return aeron_client_conductor_subscription_install_new_image_list(subscription, new_image_list);\n}\n\nint aeron_client_conductor_subscription_remove_image(aeron_subscription_t *subscription, aeron_image_t *image)\n{\n    aeron_image_list_t *volatile current_image_list = subscription->conductor_fields.image_lists_head.next_list;\n    aeron_image_list_t *volatile new_image_list;\n    size_t old_length = current_image_list->length;\n    int image_index = aeron_subscription_find_image_index(current_image_list, image);\n\n    if (-1 == image_index || 0 == old_length)\n    {\n        return 0;\n    }\n\n    if (aeron_subscription_alloc_image_list(&new_image_list, old_length - 1) < 0)\n    {\n        return -1;\n    }\n\n    size_t j = 0;\n    for (size_t i = 0; i < old_length; i++)\n    {\n        if (image != current_image_list->array[i])\n        {\n            new_image_list->array[j++] = current_image_list->array[i];\n        }\n    }\n\n    image->removal_change_number = subscription->conductor_fields.next_change_number;\n    aeron_image_close(image);\n\n    return aeron_client_conductor_subscription_install_new_image_list(subscription, new_image_list);\n}\n\nint aeron_client_conductor_subscription_install_new_image_list(\n    aeron_subscription_t *subscription, aeron_image_list_t *volatile image_list)\n{\n    /*\n     * Called from the client conductor to add/remove images to the image list. A new image list is passed each time.\n     */\n    image_list->change_number = subscription->conductor_fields.next_change_number++;\n    image_list->next_list = subscription->conductor_fields.image_lists_head.next_list;\n\n    AERON_SET_RELEASE(subscription->conductor_fields.image_lists_head.next_list, image_list);\n\n    return 0;\n}\n\nint aeron_client_conductor_subscription_prune_image_lists(aeron_subscription_t *subscription)\n{\n    /*\n     * Called from the client conductor to prune old image lists and free them up. Does not free Images.\n     */\n    aeron_image_list_t *volatile prune_lists_head = &subscription->conductor_fields.image_lists_head;\n    int64_t last_change_number;\n    int pruned_lists_count = 0;\n\n    AERON_GET_ACQUIRE(last_change_number, subscription->last_image_list_change_number);\n\n    while (NULL != prune_lists_head->next_list)\n    {\n        if (prune_lists_head->next_list->change_number >= last_change_number)\n        {\n            prune_lists_head = prune_lists_head->next_list;\n        }\n        else\n        {\n            aeron_image_list_t *volatile prune_list = prune_lists_head->next_list;\n\n            prune_lists_head->next_list = prune_list->next_list;\n            aeron_free((void *)prune_list);\n            pruned_lists_count++;\n        }\n    }\n\n    return pruned_lists_count;\n}\n\nbool aeron_subscription_is_connected(aeron_subscription_t *subscription)\n{\n    aeron_image_list_t *volatile image_list;\n    bool result = false;\n\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    for (size_t i = 0, length = image_list->length; i < length; i++)\n    {\n        if (!aeron_image_is_closed(image_list->array[i]))\n        {\n            result = true;\n            break;\n        }\n    }\n\n    aeron_subscription_propose_last_image_change_number(subscription, image_list->change_number);\n\n    return result;\n}\n\nint aeron_subscription_constants(aeron_subscription_t *subscription, aeron_subscription_constants_t *constants)\n{\n    if (NULL == subscription || NULL == constants)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, subscription: %s, constants: %s\",\n            AERON_NULL_STR(subscription),\n            AERON_NULL_STR(constants));\n        return -1;\n    }\n\n    constants->channel = subscription->channel;\n    constants->registration_id = subscription->registration_id;\n    constants->stream_id = subscription->stream_id;\n    constants->on_available_image = subscription->on_available_image;\n    constants->on_unavailable_image = subscription->on_unavailable_image;\n    constants->channel_status_indicator_id = subscription->channel_status_indicator_id;\n\n    return 0;\n}\n\nint aeron_subscription_image_count(aeron_subscription_t *subscription)\n{\n    aeron_image_list_t *volatile image_list;\n\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    aeron_subscription_propose_last_image_change_number(subscription, image_list->change_number);\n\n    return (int)image_list->length;\n}\n\naeron_image_t *aeron_subscription_image_by_session_id(aeron_subscription_t *subscription, int32_t session_id)\n{\n    aeron_image_list_t *volatile image_list;\n    aeron_image_t *result = NULL;\n\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    for (size_t i = 0, length = image_list->length; i < length; i++)\n    {\n        if (session_id == image_list->array[i]->session_id)\n        {\n            result = image_list->array[i];\n            break;\n        }\n    }\n\n    if (NULL != result)\n    {\n        aeron_image_incr_refcnt(result);\n    }\n\n    aeron_subscription_propose_last_image_change_number(subscription, image_list->change_number);\n\n    return result;\n}\n\naeron_image_t *aeron_subscription_image_at_index(aeron_subscription_t *subscription, size_t index)\n{\n    aeron_image_list_t *volatile image_list;\n    aeron_image_t *result = NULL;\n\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    if (index < image_list->length)\n    {\n        result = image_list->array[index];\n        aeron_image_incr_refcnt(result);\n    }\n\n    aeron_subscription_propose_last_image_change_number(subscription, image_list->change_number);\n\n    return result;\n}\n\nvoid aeron_subscription_for_each_image(\n    aeron_subscription_t *subscription, void (*handler)(aeron_image_t *image, void *clientd), void *clientd)\n{\n    aeron_image_list_t *volatile image_list;\n\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    for (size_t i = 0, length = image_list->length; i < length; i++)\n    {\n        aeron_image_t *image = image_list->array[i];\n\n        aeron_image_incr_refcnt(image);\n        handler(image_list->array[i], clientd);\n        aeron_image_decr_refcnt(image);\n    }\n\n    aeron_subscription_propose_last_image_change_number(subscription, image_list->change_number);\n}\n\nint aeron_subscription_image_retain(aeron_subscription_t *subscription, aeron_image_t *image)\n{\n    if (NULL == subscription || NULL == image)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, subscription: %s, image: %s\",\n            AERON_NULL_STR(subscription),\n            AERON_NULL_STR(image));\n        return -1;\n    }\n\n    aeron_image_list_t *volatile image_list;\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    if (-1 != aeron_subscription_find_image_index(image_list, image))\n    {\n        aeron_image_incr_refcnt(image);\n    }\n\n    return 0;\n}\n\nint aeron_subscription_image_release(aeron_subscription_t *subscription, aeron_image_t *image)\n{\n    if (NULL == subscription || NULL == image)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, subscription: %s, image: %s\",\n            AERON_NULL_STR(subscription),\n            AERON_NULL_STR(image));\n        return -1;\n    }\n\n    aeron_image_list_t *volatile image_list;\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    if (-1 != aeron_subscription_find_image_index(image_list, image))\n    {\n        aeron_image_decr_refcnt(image);\n    }\n\n    return 0;\n}\n\nbool aeron_subscription_is_closed(aeron_subscription_t *subscription)\n{\n    bool is_closed = false;\n\n    if (NULL != subscription)\n    {\n        AERON_GET_ACQUIRE(is_closed, subscription->is_closed);\n    }\n\n    return is_closed;\n}\n\nint64_t aeron_subscription_channel_status(aeron_subscription_t *subscription)\n{\n    if (NULL != subscription && NULL != subscription->channel_status_indicator &&\n        !aeron_subscription_is_closed(subscription))\n    {\n        int64_t value;\n        AERON_GET_ACQUIRE(value, *subscription->channel_status_indicator);\n\n        return value;\n    }\n\n    return AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_NO_ID_ALLOCATED;\n}\n\nint aeron_subscription_poll(\n    aeron_subscription_t *subscription, aeron_fragment_handler_t handler, void *clientd, size_t fragment_limit)\n{\n    aeron_image_list_t *volatile image_list;\n\n    if (NULL == handler)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"handler must not be null %s\",\n            AERON_NULL_STR(handler));\n        return -1;\n    }\n\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    size_t length = image_list->length;\n    size_t fragments_read = 0;\n    size_t starting_index = subscription->round_robin_index++;\n    if (starting_index >= length)\n    {\n        subscription->round_robin_index = starting_index = 0;\n    }\n\n    for (size_t i = starting_index; i < length && fragments_read < fragment_limit; i++)\n    {\n        if (NULL != image_list->array[i])\n        {\n            fragments_read += (size_t)aeron_image_poll(\n                image_list->array[i], handler, clientd, fragment_limit - fragments_read);\n        }\n    }\n\n    for (size_t i = 0; i < starting_index && fragments_read < fragment_limit; i++)\n    {\n        if (NULL != image_list->array[i])\n        {\n            fragments_read += (size_t)aeron_image_poll(\n                image_list->array[i], handler, clientd, fragment_limit - fragments_read);\n        }\n    }\n\n    aeron_subscription_propose_last_image_change_number(subscription, image_list->change_number);\n\n    return (int)fragments_read;\n}\n\nint aeron_subscription_controlled_poll(\n    aeron_subscription_t *subscription,\n    aeron_controlled_fragment_handler_t handler,\n    void *clientd,\n    size_t fragment_limit)\n{\n    aeron_image_list_t *volatile image_list;\n\n    if (NULL == handler)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"handler must not be null %s\",\n            AERON_NULL_STR(handler));\n        return -1;\n    }\n\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    size_t length = image_list->length;\n    size_t fragments_read = 0;\n    size_t starting_index = subscription->round_robin_index++;\n    if (starting_index >= length)\n    {\n        subscription->round_robin_index = starting_index = 0;\n    }\n\n    for (size_t i = starting_index; i < length && fragments_read < fragment_limit; i++)\n    {\n        if (NULL != image_list->array[i])\n        {\n            fragments_read += (size_t)aeron_image_controlled_poll(\n                image_list->array[i], handler, clientd, fragment_limit - fragments_read);\n        }\n    }\n\n    for (size_t i = 0; i < starting_index && fragments_read < fragment_limit; i++)\n    {\n        if (NULL != image_list->array[i])\n        {\n            fragments_read += (size_t)aeron_image_controlled_poll(\n                image_list->array[i], handler, clientd, fragment_limit - fragments_read);\n        }\n    }\n\n    aeron_subscription_propose_last_image_change_number(subscription, image_list->change_number);\n\n    return (int)fragments_read;\n}\n\nlong aeron_subscription_block_poll(\n    aeron_subscription_t *subscription, aeron_block_handler_t handler, void *clientd, size_t block_length_limit)\n{\n    aeron_image_list_t *volatile image_list;\n    long bytes_consumed = 0;\n\n    if (NULL == handler)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"handler must not be null %s\",\n            AERON_NULL_STR(handler));\n        return -1;\n    }\n\n    AERON_GET_ACQUIRE(image_list, subscription->conductor_fields.image_lists_head.next_list);\n\n    for (size_t i = 0, length = image_list->length; i < length; i++)\n    {\n        if (NULL != image_list->array[i])\n        {\n            bytes_consumed += aeron_image_block_poll(\n                image_list->array[i], handler, clientd, block_length_limit);\n        }\n    }\n\n    aeron_subscription_propose_last_image_change_number(subscription, image_list->change_number);\n\n    return bytes_consumed;\n}\n\nint aeron_header_values(aeron_header_t *header, aeron_header_values_t *values)\n{\n    if (NULL == header || NULL == values)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, header: %s, values: %s\",\n            AERON_NULL_STR(header),\n            AERON_NULL_STR(values));\n        return -1;\n    }\n\n    memcpy(&values->frame, header->frame, sizeof(aeron_header_values_frame_t));\n    values->initial_term_id = header->initial_term_id;\n    values->position_bits_to_shift = header->position_bits_to_shift;\n\n    return 0;\n}\n\nint64_t aeron_header_position(aeron_header_t *header)\n{\n    const int32_t next_term_offset = aeron_header_next_term_offset(header);\n    return aeron_logbuffer_compute_position(\n        header->frame->term_id, next_term_offset, header->position_bits_to_shift, header->initial_term_id);\n}\n\nsize_t aeron_header_position_bits_to_shift(aeron_header_t *header)\n{\n    return header->position_bits_to_shift;\n}\n\nint32_t aeron_header_next_term_offset(aeron_header_t *header)\n{\n    const int32_t term_occupancy_length = header->fragmented_frame_length < header->frame->frame_header.frame_length ?\n        header->frame->frame_header.frame_length : header->fragmented_frame_length;\n    return AERON_ALIGN(\n        header->frame->term_offset + term_occupancy_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n}\n\nvoid *aeron_header_context(aeron_header_t *header)\n{\n    return header->context;\n}\n\nint aeron_subscription_local_sockaddrs(\n    aeron_subscription_t *subscription, aeron_iovec_t *address_vec, size_t address_vec_len)\n{\n    if (NULL == subscription || NULL == address_vec)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, subscription: %s, address_vec: %s\",\n            AERON_NULL_STR(subscription),\n            AERON_NULL_STR(address_vec));\n        return -1;\n    }\n\n    if (address_vec_len < 1)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must be valid, address_vec_len (%\" PRIu64 \") < 1\", (uint64_t)address_vec_len);\n        return -1;\n    }\n\n    return aeron_local_sockaddr_find_addrs(\n        &subscription->conductor->counters_reader,\n        subscription->channel_status_indicator_id,\n        address_vec,\n        address_vec_len);\n}\n\nint aeron_subscription_resolved_endpoint(\n    aeron_subscription_t *subscription, const char *address, size_t address_len)\n{\n    if (NULL == subscription || NULL == address)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, subscription: %s, address: %s\",\n            AERON_NULL_STR(subscription),\n            AERON_NULL_STR(address));\n        return -1;\n    }\n\n    if (address_len < 1)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must be valid, address_len (%\" PRIu64 \") < 1\", (uint64_t)address_len);\n        return -1;\n    }\n\n    aeron_iovec_t addr_vec =\n        {\n            .iov_base = (uint8_t *)address,\n            .iov_len = address_len\n        };\n\n    return aeron_local_sockaddr_find_addrs(\n        &subscription->conductor->counters_reader,\n        subscription->channel_status_indicator_id,\n        &addr_vec,\n        1);\n}\n\nstatic bool aeron_subscription_should_replace_wildcard_port(aeron_uri_t *uri)\n{\n    if (AERON_URI_UDP != uri->type ||\n        NULL == uri->params.udp.endpoint ||\n        (NULL != uri->params.udp.control_mode &&\n        0 == strcmp(uri->params.udp.control_mode, AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL_VALUE)))\n    {\n        return false;\n    }\n\n    char *port_suffix = strrchr(uri->params.udp.endpoint, ':');\n    return 0 == strcmp(port_suffix, \":0\");\n}\n\nint aeron_subscription_try_resolve_channel_endpoint_port(\n    aeron_subscription_t *subscription, char *uri, size_t uri_len)\n{\n    if (NULL == subscription || NULL == uri)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters must not be null, subscription: %s, uri: %s\",\n            AERON_NULL_STR(subscription),\n            AERON_NULL_STR(uri));\n        return -1;\n    }\n\n    if (uri_len < 1)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Parameters must be valid, uri_len (%\" PRIu64 \") < 1\", (uint64_t)uri_len);\n        return -1;\n    }\n\n    int result = -1;\n    aeron_uri_t temp_uri;\n    memset(&temp_uri, 0, sizeof(aeron_uri_t));\n\n    if (aeron_uri_parse(strlen(subscription->channel), subscription->channel, &temp_uri) >= 0)\n    {\n        if (aeron_subscription_should_replace_wildcard_port(&temp_uri))\n        {\n            char resolved_endpoint[AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN] = { 0 };\n            int resolve_result = aeron_subscription_resolved_endpoint(subscription, resolved_endpoint, sizeof(resolved_endpoint));\n            if (0 < resolve_result)\n            {\n                temp_uri.params.udp.endpoint = resolved_endpoint;\n                result = aeron_uri_sprint(&temp_uri, uri, uri_len);\n            }\n            else if (0 == resolve_result)\n            {\n                uri[0] = '\\0';\n                result = 0;\n            }\n        }\n        else\n        {\n            strcpy(uri, subscription->channel);\n            result = 1;\n        }\n    }\n\n    aeron_uri_close(&temp_uri);\n\n    return result;\n}\n\nint aeron_subscription_reject_image(\n    aeron_subscription_t *subscription, int64_t image_correlation_id, int64_t position, const char *reason)\n{\n    if (aeron_client_conductor_reject_image(\n        subscription->conductor, image_correlation_id, position, reason) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nextern int aeron_subscription_find_image_index(aeron_image_list_t *volatile image_list, aeron_image_t *image);\nextern int64_t aeron_subscription_last_image_list_change_number(aeron_subscription_t *subscription);\nextern void aeron_subscription_propose_last_image_change_number(\n    aeron_subscription_t *subscription, int64_t change_number);\nextern volatile aeron_image_list_t *aeron_client_conductor_subscription_image_list(aeron_subscription_t *subscription);\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_subscription.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_SUBSCRIPTION_H\n#define AERON_C_SUBSCRIPTION_H\n\n#include \"aeronc.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_context.h\"\n#include \"aeron_client_conductor.h\"\n\ntypedef struct aeron_image_list_stct\n{\n    int64_t change_number;\n    uint32_t length;\n    struct aeron_image_list_stct *volatile next_list;\n    aeron_image_t **array;\n}\naeron_image_list_t;\n\n#define AERON_IMAGE_LIST_ALLOC_SIZE(l) (sizeof(aeron_image_list_t) + ((l) * sizeof(aeron_image_t *)))\n\ntypedef struct aeron_subscription_stct\n{\n    aeron_client_command_base_t command_base;\n    aeron_client_conductor_t *conductor;\n    const char *channel;\n\n    struct subscription_conductor_fields_stct\n    {\n        uint8_t pre_fields_padding[AERON_CACHE_LINE_LENGTH];\n        aeron_image_list_t image_lists_head;\n        int64_t next_change_number;\n        uint8_t post_fields_padding[AERON_CACHE_LINE_LENGTH];\n    }\n    conductor_fields;\n\n    volatile int64_t *channel_status_indicator;\n\n    volatile int64_t last_image_list_change_number;\n\n    aeron_on_available_image_t on_available_image;\n    void *on_available_image_clientd;\n    aeron_on_unavailable_image_t on_unavailable_image;\n    void *on_unavailable_image_clientd;\n    aeron_notification_t on_close_complete;\n    void *on_close_complete_clientd;\n\n    int64_t registration_id;\n    int32_t stream_id;\n    int32_t channel_status_indicator_id;\n    size_t round_robin_index;\n\n    volatile bool is_closed;\n    uint8_t post_fields_padding[AERON_CACHE_LINE_LENGTH];\n}\naeron_subscription_t;\n\nint aeron_subscription_create(\n    aeron_subscription_t **subscription,\n    aeron_client_conductor_t *conductor,\n    const char *channel,\n    int32_t stream_id,\n    int64_t registration_id,\n    int32_t channel_status_indicator_id,\n    int64_t *channel_status_indicator_addr,\n    aeron_on_available_image_t on_available_image,\n    void *on_available_image_clientd,\n    aeron_on_unavailable_image_t on_unavailable_image,\n    void *on_unavailable_image_clientd);\n\nint aeron_subscription_delete(aeron_subscription_t *subscription);\nvoid aeron_subscription_force_close(aeron_subscription_t *subscription);\n\nint aeron_subscription_alloc_image_list(aeron_image_list_t *volatile *image_list, size_t length);\n\nint aeron_client_conductor_subscription_add_image(aeron_subscription_t *subscription, aeron_image_t *image);\nint aeron_client_conductor_subscription_remove_image(aeron_subscription_t *subscription, aeron_image_t *image);\n\ninline volatile aeron_image_list_t *aeron_client_conductor_subscription_image_list(aeron_subscription_t *subscription)\n{\n    return subscription->conductor_fields.image_lists_head.next_list;\n}\n\nint aeron_client_conductor_subscription_install_new_image_list(\n    aeron_subscription_t *subscription, aeron_image_list_t *volatile image_list);\n\nint aeron_client_conductor_subscription_prune_image_lists(aeron_subscription_t *subscription);\n\nint aeron_subscription_reject_image(\n    aeron_subscription_t *subscription, int64_t image_correlation_id, int64_t position, const char *reason);\n\ninline int aeron_subscription_find_image_index(aeron_image_list_t *volatile image_list, aeron_image_t *image)\n{\n    size_t length = NULL == image_list ? 0 : image_list->length;\n\n    for (size_t i = 0; i < length; i++)\n    {\n        if (image == image_list->array[i])\n        {\n            return (int)i;\n        }\n    }\n\n    return -1;\n}\n\ninline int64_t aeron_subscription_last_image_list_change_number(aeron_subscription_t *subscription)\n{\n    int64_t last_image_list_change_number;\n\n    AERON_GET_ACQUIRE(last_image_list_change_number, subscription->last_image_list_change_number);\n\n    return last_image_list_change_number;\n}\n\ninline void aeron_subscription_propose_last_image_change_number(\n    aeron_subscription_t *subscription, int64_t change_number)\n{\n    if (change_number > subscription->last_image_list_change_number)\n    {\n        AERON_SET_RELEASE(subscription->last_image_list_change_number, change_number);\n    }\n}\n\n#endif //AERON_C_SUBSCRIPTION_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_version.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\")\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdint.h>\n\nconst char aeron_version_full_str[] = \"aeron version=\" AERON_VERSION_TXT \" commit=\" AERON_VERSION_GITSHA;\n\nconst char *aeron_version_full(void)\n{\n    return aeron_version_full_str;\n}\n\nconst char *aeron_version_text(void)\n{\n    return AERON_VERSION_TXT;\n}\n\nint aeron_version_major(void)\n{\n    return AERON_VERSION_MAJOR;\n}\n\nint aeron_version_minor(void)\n{\n    return AERON_VERSION_MINOR;\n}\n\nint aeron_version_patch(void)\n{\n    return AERON_VERSION_PATCH;\n}\n\nconst char *aeron_version_gitsha(void)\n{\n    return AERON_VERSION_GITSHA;\n}\n\nint32_t aeron_semantic_version_compose(uint8_t major, uint8_t minor, uint8_t patch)\n{\n    return (major << 16) | (minor << 8) | patch;\n}\n\nuint8_t aeron_semantic_version_major(int32_t version)\n{\n    return (uint8_t)((version >> 16) & 0xFF);\n}\n\nuint8_t aeron_semantic_version_minor(int32_t version)\n{\n    return (uint8_t)((version >> 8) & 0xFF);\n}\n\nuint8_t aeron_semantic_version_patch(int32_t version)\n{\n    return (uint8_t)(version & 0xFF);\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_windows.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include \"util/aeron_platform.h\"\n\n#if defined(AERON_COMPILER_MSVC)\n#include \"aeron_windows.h\"\n#include \"util/aeron_error.h\"\n\n#include <WinSock2.h>\n#include <Windows.h>\n#include <time.h>\n#include <intrin.h>\n\n#include \"concurrent/aeron_thread.h\"\n#include \"aeron_alloc.h\"\n\n#define __builtin_bswap32 _byteswap_ulong\n#define __builtin_bswap64 _byteswap_uint64\n#define __builtin_popcount __popcnt\n#define __builtin_popcountll __popcnt64\n\nBOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)\n{\n    switch (fdwReason)\n    {\n        case DLL_PROCESS_ATTACH:\n            if (!aeron_error_dll_process_attach())\n            {\n                return FALSE;\n            }\n            break;\n\n        case DLL_THREAD_DETACH:\n            aeron_error_dll_thread_detach();\n            break;\n\n        case DLL_PROCESS_DETACH:\n            aeron_error_dll_process_detach();\n            break;\n\n        default:\n            break;\n    }\n\n    return TRUE;\n}\n\ntypedef struct { UINT64 q[2]; } aeron_uint128_t;\n\naeron_uint128_t make_aeron_uint128_t(UINT64 x)\n{\n    aeron_uint128_t result;\n    result.q[0] = x;\n    result.q[1] = 0;\n    return result;\n}\n\naeron_uint128_t aeron_uint128_bitwise_negate(aeron_uint128_t x)\n{\n    aeron_uint128_t r;\n    r.q[0] = ~x.q[0];\n    r.q[1] = ~x.q[1];\n    return r;\n}\n\nBOOL aeron_uint128_equals(const aeron_uint128_t lhs, const aeron_uint128_t rhs)\n{\n    return lhs.q[0] == rhs.q[0] && lhs.q[1] == rhs.q[1];\n}\n\naeron_uint128_t aeron_uint128_bitwise_shift_left(const aeron_uint128_t lhs, size_t n)\n{\n    aeron_uint128_t result = lhs;\n\n    if (n >= 128)\n    {\n        result.q[1] = 0;\n        result.q[0] = 0;\n    }\n    else\n    {\n        const unsigned int halfsize = 128 / 2;\n\n        if (n >= halfsize)\n        {\n            n -= halfsize;\n            result.q[1] = result.q[0];\n            result.q[0] = 0;\n        }\n\n        if (n != 0)\n        {\n            // shift high half\n            result.q[1] <<= n;\n\n            const UINT64 mask = (~((UINT64)-1) >> n);\n\n            // and add them to high half\n            result.q[1] |= (result.q[0] & mask) >> (halfsize - n);\n\n            // and finally shift also low half\n            result.q[0] <<= n;\n        }\n    }\n\n    return result;\n}\n\naeron_uint128_t aeron_uint128_sub(const aeron_uint128_t lhs, const aeron_uint128_t rhs)\n{\n    aeron_uint128_t result = lhs;\n\n    result.q[1] -= rhs.q[1];\n    result.q[0] -= rhs.q[0];\n\n    if (rhs.q[0] >= lhs.q[0])\n    {\n        result.q[1] -= 1;\n    }\n\n    return result;\n}\n\naeron_uint128_t aeron_uint128_bitwise_and(const aeron_uint128_t lhs, const aeron_uint128_t rhs)\n{\n    aeron_uint128_t result;\n    result.q[0] = lhs.q[0] & rhs.q[0];\n    result.q[1] = lhs.q[1] & rhs.q[1];\n    return result;\n}\n\naeron_uint128_t aeron_ipv6_netmask_from_prefixlen(size_t prefixlen)\n{\n    aeron_uint128_t netmask;\n\n    if (0 == prefixlen)\n    {\n        netmask.q[1] = 0;\n        netmask.q[0] = 0;\n    }\n    else\n    {\n        netmask = aeron_uint128_bitwise_negate(aeron_uint128_sub(aeron_uint128_bitwise_shift_left(\n            make_aeron_uint128_t(1), (128 - prefixlen)), make_aeron_uint128_t(1)));\n    }\n\n#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\n    UINT64 lo = netmask.q[0];\n    netmask.q[0] = __builtin_bswap64(netmask.q[1]);\n    netmask.q[1] = __builtin_bswap64(lo);\n#endif\n\n    return netmask;\n}\n\nBOOL aeron_ipv6_does_prefix_match(struct in6_addr *in6_addr1, struct in6_addr *in6_addr2, size_t prefixlen)\n{\n    aeron_uint128_t addr1;\n    aeron_uint128_t addr2;\n    const aeron_uint128_t netmask = aeron_ipv6_netmask_from_prefixlen(prefixlen);\n\n    memcpy(&addr1, in6_addr1, sizeof(addr1));\n    memcpy(&addr2, in6_addr2, sizeof(addr2));\n\n    return aeron_uint128_equals(aeron_uint128_bitwise_and(addr1, netmask), aeron_uint128_bitwise_and(addr2, netmask));\n}\n\nvoid aeron_srand48(uint64_t aeron_nano_clock)\n{\n    srand((unsigned int)aeron_nano_clock);\n}\n\ndouble aeron_drand48()\n{\n    return rand() / (double)(RAND_MAX + 1);\n}\n\ndouble aeron_erand48(unsigned short xsubi[3])\n{\n    return rand() / (double)(RAND_MAX + 1);\n}\n\nvoid localtime_r(const time_t *timep, struct tm *result)\n{\n    localtime_s(result, timep);\n}\n\n#else\n\ntypedef int aeron_make_into_non_empty_translation_unit_t;\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/c/aeron_windows.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_WINDOWS_H\n#define AERON_WINDOWS_H\n\n#include \"util/aeron_platform.h\"\n\n#if defined(AERON_COMPILER_GCC)\n\n#define aeron_erand48 erand48\n#define aeron_srand48 srand48\n#define aeron_drand48 drand48\n\n#elif defined(AERON_COMPILER_MSVC)\n\n#include <stddef.h>\n#include <stdint.h>\n\ndouble aeron_erand48(unsigned short xsubi[3]);\nvoid aeron_srand48(uint64_t aeron_nano_clock);\ndouble aeron_drand48();\nvoid localtime_r(const time_t *timep, struct tm *result);\n\n#endif\n\n#endif //AERON_WINDOWS_H\n"
  },
  {
    "path": "aeron-client/src/main/c/aeronc.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#ifdef HAVE_BSDSTDLIB_H\n#include <bsd/stdlib.h>\n#endif\n#endif\n\n#include <inttypes.h>\n\n#include \"aeron_common.h\"\n#include \"util/aeron_fileutil.h\"\n#include \"aeronc.h\"\n#include \"aeron_context.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_cnc_file_descriptor.h\"\n#include \"concurrent/aeron_mpsc_rb.h\"\n\nint aeron_client_connect_to_driver(aeron_mapped_file_t *cnc_mmap, aeron_context_t *context)\n{\n    long long start_ms = context->epoch_clock();\n    long long deadline_ms = start_ms + (long long)context->driver_timeout_ms;\n    char filename[AERON_MAX_PATH];\n    if (aeron_cnc_resolve_filename(context->aeron_dir, filename, sizeof(filename)) < 0)\n    {\n        AERON_APPEND_ERR(\"Failed to resolve CnC file path: dir=%s, filename=%s\", context->aeron_dir, filename);\n        return -1;\n    }\n\n    while (true)\n    {\n        aeron_cnc_metadata_t *metadata;\n        aeron_cnc_load_result_t result = aeron_cnc_map_file_and_load_metadata(context->aeron_dir, cnc_mmap, &metadata);\n        switch (result)\n        {\n            case AERON_CNC_LOAD_FAILED:\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return -1;\n\n            case AERON_CNC_LOAD_AWAIT_FILE:\n                if (context->epoch_clock() > deadline_ms)\n                {\n                    AERON_SET_ERR(AERON_CLIENT_ERROR_DRIVER_TIMEOUT, \"CnC file not created: %s\", filename);\n                    return -1;\n                }\n                aeron_micro_sleep(16 * 1000);\n                continue;\n\n            case AERON_CNC_LOAD_AWAIT_MMAP:\n                aeron_micro_sleep(1000);\n                continue;\n\n            case AERON_CNC_LOAD_AWAIT_VERSION:\n                if (context->epoch_clock() > deadline_ms)\n                {\n                    AERON_SET_ERR(\n                        AERON_CLIENT_ERROR_CLIENT_TIMEOUT, \"CnC file is created but not initialised: %s\", filename);\n                    aeron_unmap(cnc_mmap);\n                    return -1;\n                }\n                aeron_micro_sleep(1000);\n                continue;\n\n            case AERON_CNC_LOAD_AWAIT_CNC_DATA:\n                aeron_micro_sleep(1000);\n                continue;\n\n            case AERON_CNC_LOAD_SUCCESS:\n            default:\n                break;\n        }\n\n        aeron_mpsc_rb_t rb;\n\n        if (aeron_mpsc_rb_init(\n            &rb, aeron_cnc_to_driver_buffer(metadata), (size_t)metadata->to_driver_buffer_length) != 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Unable to initialise to_driver_buffer\");\n            aeron_unmap(cnc_mmap);\n            return -1;\n        }\n\n        while (0 == aeron_mpsc_rb_consumer_heartbeat_time_value(&rb))\n        {\n            if (context->epoch_clock() > deadline_ms)\n            {\n                AERON_SET_ERR(\n                    AERON_CLIENT_ERROR_DRIVER_TIMEOUT,\n                    \"no driver heartbeat detected after: %\" PRIu64 \"ms\",\n                    context->driver_timeout_ms);\n                aeron_unmap(cnc_mmap);\n                return -1;\n            }\n\n            aeron_micro_sleep(1000);\n        }\n\n        long long now_ms = context->epoch_clock();\n        if (aeron_mpsc_rb_consumer_heartbeat_time_value(&rb) < (now_ms - (long long)context->driver_timeout_ms))\n        {\n            if (now_ms > deadline_ms)\n            {\n                AERON_SET_ERR(\n                    AERON_CLIENT_ERROR_DRIVER_TIMEOUT,\n                    \"no driver heartbeat detected after: %\" PRIu64 \"ms\",\n                    context->driver_timeout_ms);\n                aeron_unmap(cnc_mmap);\n                return -1;\n            }\n\n            aeron_unmap(cnc_mmap);\n\n            aeron_micro_sleep(100 * 1000);\n            continue;\n        }\n\n        break;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/aeronc.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_C_H\n#define AERON_C_H\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdlib.h>\n\n#define AERON_NULL_VALUE (-1)\n\n#define AERON_CLIENT_ERROR_DRIVER_TIMEOUT (-1000)\n#define AERON_CLIENT_ERROR_CLIENT_TIMEOUT (-1001)\n#define AERON_CLIENT_ERROR_CONDUCTOR_SERVICE_TIMEOUT (-1002)\n#define AERON_CLIENT_ERROR_BUFFER_FULL (-1003)\n#define AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN (64)\n\n#define AERON_RESPONSE_ADDRESS_TYPE_IPV4 (0x1)\n#define AERON_RESPONSE_ADDRESS_TYPE_IPV6 (0x2)\n\ntypedef struct aeron_context_stct aeron_context_t;\ntypedef struct aeron_stct aeron_t;\ntypedef struct aeron_buffer_claim_stct aeron_buffer_claim_t;\ntypedef struct aeron_publication_stct aeron_publication_t;\ntypedef struct aeron_exclusive_publication_stct aeron_exclusive_publication_t;\ntypedef struct aeron_header_stct aeron_header_t;\n\n#pragma pack(push)\n#pragma pack(4)\nstruct aeron_header_values_frame_stct\n{\n    int32_t frame_length;\n    int8_t version;\n    uint8_t flags;\n    int16_t type;\n    int32_t term_offset;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t term_id;\n    int64_t reserved_value;\n};\ntypedef struct aeron_header_values_frame_stct aeron_header_values_frame_t;\n\ntypedef struct aeron_header_values_stct\n{\n    aeron_header_values_frame_t frame;\n    int32_t initial_term_id;\n    size_t position_bits_to_shift;\n}\naeron_header_values_t;\n\nstruct aeron_publication_error_values_stct\n{\n    int64_t registration_id;\n    int64_t destination_registration_id;\n    int32_t session_id;\n    int32_t stream_id;\n    int64_t receiver_id;\n    int64_t group_tag;\n    int16_t address_type;\n    uint16_t source_port;\n    uint8_t source_address[16];\n    int32_t error_code;\n    int32_t error_message_length;\n    uint8_t error_message[1];\n};\ntypedef struct aeron_publication_error_values_stct aeron_publication_error_values_t;\n#pragma pack(pop)\n\ntypedef struct aeron_subscription_stct aeron_subscription_t;\ntypedef struct aeron_image_stct aeron_image_t;\ntypedef struct aeron_counter_stct aeron_counter_t;\ntypedef struct aeron_log_buffer_stct aeron_log_buffer_t;\n\ntypedef struct aeron_counters_reader_stct aeron_counters_reader_t;\n\ntypedef struct aeron_client_registering_resource_stct aeron_async_add_publication_t;\ntypedef struct aeron_client_registering_resource_stct aeron_async_add_exclusive_publication_t;\ntypedef struct aeron_client_registering_resource_stct aeron_async_add_subscription_t;\ntypedef struct aeron_client_registering_resource_stct aeron_async_add_counter_t;\ntypedef struct aeron_client_registering_resource_stct aeron_async_destination_t;\ntypedef struct aeron_client_registering_resource_stct aeron_async_destination_by_id_t;\ntypedef struct aeron_client_registering_resource_stct aeron_async_get_next_available_session_id_t;\n\ntypedef struct aeron_image_fragment_assembler_stct aeron_image_fragment_assembler_t;\ntypedef struct aeron_image_controlled_fragment_assembler_stct aeron_image_controlled_fragment_assembler_t;\ntypedef struct aeron_fragment_assembler_stct aeron_fragment_assembler_t;\ntypedef struct aeron_controlled_fragment_assembler_stct aeron_controlled_fragment_assembler_t;\n\n\n/**\n * Environment variables and functions used for setting values of an aeron_context_t.\n */\n\n/**\n * The top level Aeron directory used for communication between a Media Driver and client.\n */\n#define AERON_DIR_ENV_VAR \"AERON_DIR\"\n\nint aeron_context_set_dir(aeron_context_t *context, const char *value);\nconst char *aeron_context_get_dir(aeron_context_t *context);\n\n#define AERON_DRIVER_TIMEOUT_ENV_VAR \"AERON_DRIVER_TIMEOUT\"\n\nint aeron_context_set_driver_timeout_ms(aeron_context_t *context, uint64_t value);\nuint64_t aeron_context_get_driver_timeout_ms(aeron_context_t *context);\n\nint aeron_context_set_keepalive_interval_ns(aeron_context_t *context, uint64_t value);\nuint64_t aeron_context_get_keepalive_interval_ns(aeron_context_t *context);\n\n#define AERON_CLIENT_RESOURCE_LINGER_DURATION_ENV_VAR \"AERON_CLIENT_RESOURCE_LINGER_DURATION\"\n\nint aeron_context_set_resource_linger_duration_ns(aeron_context_t *context, uint64_t value);\nuint64_t aeron_context_get_resource_linger_duration_ns(aeron_context_t *context);\n\n#define AERON_CLIENT_IDLE_SLEEP_DURATION_ENV_VAR \"AERON_CLIENT_IDLE_SLEEP_DURATION\"\n\nint aeron_context_set_idle_sleep_duration_ns(aeron_context_t *context, uint64_t value);\nuint64_t aeron_context_get_idle_sleep_duration_ns(aeron_context_t *context);\n\nint aeron_context_set_idle_strategy_init_args(aeron_context_t *context, const char *value);\nconst char *aeron_context_get_idle_strategy_init_args(aeron_context_t *context);\n\nint aeron_context_set_idle_strategy(aeron_context_t *context, const char *value);\nconst char *aeron_context_get_idle_strategy(aeron_context_t *context);\n\n#define AERON_CLIENT_PRE_TOUCH_MAPPED_MEMORY_ENV_VAR \"AERON_CLIENT_PRE_TOUCH_MAPPED_MEMORY\"\n\nint aeron_context_set_pre_touch_mapped_memory(aeron_context_t *context, bool value);\nbool aeron_context_get_pre_touch_mapped_memory(aeron_context_t *context);\n\n#define AERON_CLIENT_NAME_ENV_VAR \"AERON_CLIENT_NAME\"\n\nint aeron_context_set_client_name(aeron_context_t *context, const char *value);\nconst char *aeron_context_get_client_name(aeron_context_t *context);\n\n/**\n * The error handler to be called when an error occurs.\n */\ntypedef void (*aeron_error_handler_t)(void *clientd, int errcode, const char *message);\n\n/**\n * The error frame handler to be called when the driver notifies the client about an error frame being received.\n * The data passed to this callback will only be valid for the lifetime of the callback. The user should use\n * <code>aeron_publication_error_values_copy</code> if they require the data to live longer than that.\n */\ntypedef void (*aeron_publication_error_frame_handler_t)(void *clientd, aeron_publication_error_values_t *error_frame);\n\n/**\n * Copy an existing aeron_publication_error_values_t to the supplied pointer. The caller is responsible for freeing the\n * allocated memory using aeron_publication_error_values_delete when the copy is not longer required.\n *\n * @param dst to copy the values to.\n * @param src to copy the values from.\n * @return 0 if this is successful, -1 otherwise. Will set aeron_errcode() and aeron_errmsg() on failure.\n */\nint aeron_publication_error_values_copy(aeron_publication_error_values_t **dst, aeron_publication_error_values_t *src);\n\n/**\n * Delete a instance of aeron_publication_error_values_t that was created when making a copy\n * (aeron_publication_error_values_copy). This should not be use on the pointer received via the aeron_frame_handler_t.\n * @param to_delete to be deleted.\n */\nvoid aeron_publication_error_values_delete(aeron_publication_error_values_t *to_delete);\n\n/**\n * Generalised notification callback.\n */\ntypedef void (*aeron_notification_t)(void *clientd);\n\nint aeron_context_set_error_handler(aeron_context_t *context, aeron_error_handler_t handler, void *clientd);\naeron_error_handler_t aeron_context_get_error_handler(aeron_context_t *context);\nvoid *aeron_context_get_error_handler_clientd(aeron_context_t *context);\n\nint aeron_context_set_publication_error_frame_handler(aeron_context_t *context, aeron_publication_error_frame_handler_t handler, void *clientd);\naeron_publication_error_frame_handler_t aeron_context_get_publication_error_frame_handler(aeron_context_t *context);\nvoid *aeron_context_get_publication_error_frame_handler_clientd(aeron_context_t *context);\n\n/**\n * Function called by aeron_client_t to deliver notification that the media driver has added an aeron_publication_t\n * or aeron_exclusive_publication_t successfully.\n *\n * Implementations should do the minimum work for passing off state to another thread for later processing.\n *\n * @param clientd to be returned in the call\n * @param async associated with the original add publication call\n * @param channel of the publication\n * @param stream_id within the channel of the publication\n * @param session_id of the publication\n * @param correlation_id used by the publication\n */\ntypedef void (*aeron_on_new_publication_t)(\n    void *clientd,\n    aeron_async_add_publication_t *async,\n    const char *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t correlation_id);\n\nint aeron_context_set_on_new_publication(aeron_context_t *context, aeron_on_new_publication_t handler, void *clientd);\naeron_on_new_publication_t aeron_context_get_on_new_publication(aeron_context_t *context);\nvoid *aeron_context_get_on_new_publication_clientd(aeron_context_t *context);\n\nint aeron_context_set_on_new_exclusive_publication(\n    aeron_context_t *context, aeron_on_new_publication_t handler, void *clientd);\naeron_on_new_publication_t aeron_context_get_on_new_exclusive_publication(aeron_context_t *context);\nvoid *aeron_context_get_on_new_exclusive_publication_clientd(aeron_context_t *context);\n\n/**\n * Function called by aeron_client_t to deliver notification that the media driver has added an aeron_subscription_t\n * successfully.\n *\n * Implementations should do the minimum work for handing off state to another thread for later processing.\n *\n * @param clientd to be returned in the call\n * @param async associated with the original aeron_add_async_subscription call\n * @param channel of the subscription\n * @param stream_id within the channel of the subscription\n * @param session_id of the subscription\n * @param correlation_id used by the subscription\n */\ntypedef void (*aeron_on_new_subscription_t)(\n    void *clientd,\n    aeron_async_add_subscription_t *async,\n    const char *channel,\n    int32_t stream_id,\n    int64_t correlation_id);\n\nint aeron_context_set_on_new_subscription(\n    aeron_context_t *context, aeron_on_new_subscription_t handler, void *clientd);\naeron_on_new_subscription_t aeron_context_get_on_new_subscription(aeron_context_t *context);\nvoid *aeron_context_get_on_new_subscription_clientd(aeron_context_t *context);\n\n/**\n * Function called by aeron_client_t to deliver notifications that an aeron_image_t was added.\n *\n * @param clientd to be returned in the call.\n * @param subscription that image is part of.\n * @param image that has become available.\n */\ntypedef void (*aeron_on_available_image_t)(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image);\n\n/**\n * Function called by aeron_client_t to deliver notifications that an aeron_image_t has been removed from use and\n * should not be used any longer.\n *\n * @param clientd to be returned in the call.\n * @param subscription that image is part of.\n * @param image that has become unavailable.\n */\ntypedef void (*aeron_on_unavailable_image_t)(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image);\n\n/**\n * Function called by aeron_client_t to deliver notifications that a counter has been added to the driver.\n *\n * @param clientd to be returned in the call.\n * @param counters_reader that holds the counter.\n * @param registration_id of the counter.\n * @param counter_id of the counter.\n */\ntypedef void (*aeron_on_available_counter_t)(\n    void *clientd, aeron_counters_reader_t *counters_reader, int64_t registration_id, int32_t counter_id);\n\nint aeron_context_set_on_available_counter(\n    aeron_context_t *context, aeron_on_available_counter_t handler, void *clientd);\naeron_on_available_counter_t aeron_context_get_on_available_counter(aeron_context_t *context);\nvoid *aeron_context_get_on_available_counter_clientd(aeron_context_t *context);\n\n/**\n * Function called by aeron_client_t to deliver notifications that a counter has been removed from the driver.\n *\n * @param clientd to be returned in the call.\n * @param counters_reader that holds the counter.\n * @param registration_id of the counter.\n * @param counter_id of the counter.\n */\ntypedef void (*aeron_on_unavailable_counter_t)(\n    void *clientd, aeron_counters_reader_t *counters_reader, int64_t registration_id, int32_t counter_id);\n\nint aeron_context_set_on_unavailable_counter(\n    aeron_context_t *context, aeron_on_unavailable_counter_t handler, void *clientd);\naeron_on_unavailable_counter_t aeron_context_get_on_unavailable_counter(aeron_context_t *context);\nvoid *aeron_context_get_on_unavailable_counter_clientd(aeron_context_t *context);\n\n/**\n * Function called by aeron_client_t to deliver notifications that the client is closing.\n *\n * @param clientd to be returned in the call.\n */\ntypedef void (*aeron_on_close_client_t)(void *clientd);\n\nint aeron_context_set_on_close_client(\n    aeron_context_t *context, aeron_on_close_client_t handler, void *clientd);\naeron_on_close_client_t aeron_context_get_on_close_client(aeron_context_t *context);\nvoid *aeron_context_get_on_close_client_clientd(aeron_context_t *context);\n\n/**\n * Whether to use an invoker to control the conductor agent or spawn a thread.\n */\nint aeron_context_set_use_conductor_agent_invoker(aeron_context_t *context, bool value);\nbool aeron_context_get_use_conductor_agent_invoker(aeron_context_t *context);\n\n/**\n * Function name to call on start of each agent.\n */\n#define AERON_AGENT_ON_START_FUNCTION_ENV_VAR \"AERON_AGENT_ON_START_FUNCTION\"\n\ntypedef void (*aeron_agent_on_start_func_t)(void *state, const char *role_name);\n\nint aeron_context_set_agent_on_start_function(\n    aeron_context_t *context, aeron_agent_on_start_func_t value, void *state);\naeron_agent_on_start_func_t aeron_context_get_agent_on_start_function(aeron_context_t *context);\nvoid *aeron_context_get_agent_on_start_state(aeron_context_t *context);\n\n/**\n * Create a aeron_context_t struct and initialize with default values.\n *\n * @param context to create and initialize\n * @return 0 for success and -1 for error.\n */\nint aeron_context_init(aeron_context_t **context);\n\n/**\n * Close and delete aeron_context_t struct.\n *\n * @param context to close and delete\n * @return 0 for success and -1 for error.\n */\nint aeron_context_close(aeron_context_t *context);\n\n/**\n * Create a aeron_t client struct and initialize from the aeron_context_t struct.\n *\n * The given aeron_context_t struct will be used exclusively by the client. Do not reuse between clients.\n *\n * @param aeron  client to create and initialize.\n * @param context to use for initialization.\n * @return 0 for success and -1 for error.\n */\nint aeron_init(aeron_t **client, aeron_context_t *context);\n\n/**\n * Start an aeron_t. This may spawn a thread for the Client Conductor.\n *\n * @param client to start.\n * @return 0 for success and -1 for error.\n */\nint aeron_start(aeron_t *client);\n\n/**\n * Call the Conductor main do_work duty cycle once.\n *\n * Client must have been created with use conductor invoker set to true.\n *\n * @param client to call do_work duty cycle on.\n * @return 0 for success and -1 for error.\n */\nint aeron_main_do_work(aeron_t *client);\n\n/**\n * Call the Conductor Idle Strategy.\n *\n * @param client to idle.\n * @param work_count to pass to idle strategy.\n */\nvoid aeron_main_idle_strategy(aeron_t *client, int work_count);\n\n/**\n * Close and delete aeron_t struct.\n *\n * @param client to close and delete\n * @return 0 for success and -1 for error.\n */\nint aeron_close(aeron_t *client);\n\n/**\n * Determines if the client has been closed, e.g. via a driver timeout. Don't call this method after calling\n * aeron_close as that will have already freed the associated memory.\n *\n * @param client to check if closed.\n * @return true if it has been closed, false otherwise.\n */\nbool aeron_is_closed(aeron_t *client);\n\n/**\n * Aeron API functions\n */\n\n/**\n * Call stream_out to print the counter labels and values.\n *\n * @param client to get the counters from.\n * @param stream_out to call for each label and value.\n */\nvoid aeron_print_counters(aeron_t *client, void (*stream_out)(const char *));\n\n/**\n * Return the aeron_context_t that is in use by the given client.\n *\n * @param client to return the aeron_context_t for.\n * @return the aeron_context_t for the given client or NULL for an error.\n */\naeron_context_t *aeron_context(aeron_t *client);\n\n/**\n * Return the client id in use by the client.\n *\n * @param client to return the client id for.\n * @return id value or -1 for an error.\n */\nint64_t aeron_client_id(aeron_t *client);\n\n/**\n * Return a unique correlation id from the driver.\n *\n * @param client to use to get the id.\n * @return unique correlation id or -1 for an error.\n */\nint64_t aeron_next_correlation_id(aeron_t *client);\n\n/**\n * Asynchronously request next available session from the media driver. The session id will be unique for the\n * connected media driver and given {@code stream_id}.\n *\n * @param async object to use for polling completion.\n * @param client connected to the media driver.\n * @param stream_id for which a new session id is requested. Media driver only checks for session clashes at the\n *                 stream level.\n * @return 0 for success or -1 for an error.\n */\nint aeron_async_next_session_id(\n    aeron_async_get_next_available_session_id_t **async, aeron_t *client, int32_t stream_id);\n\n/**\n * Poll the completion of the aeron_async_next_session_id call.\n *\n * @param next_session_id to set if completed successfully.\n * @param async to check for completion.\n * @return 0 for not complete (try again), 1 for completed successfully, or -1 for an error.\n */\nint aeron_async_next_session_id_poll(int32_t *next_session_id, aeron_async_get_next_available_session_id_t *async);\n\n/**\n * Asynchronously add a publication using the given client and return an object to use to determine when the\n * publication is available.\n *\n * @param async object to use for polling completion.\n * @param client to add the publication to.\n * @param uri for the channel of the publication.\n * @param stream_id for the publication.\n * @return 0 for success or -1 for an error.\n */\nint aeron_async_add_publication(\n    aeron_async_add_publication_t **async, aeron_t *client, const char *uri, int32_t stream_id);\n\n/**\n * Poll the completion of the aeron_async_add_publication call.\n *\n * @param publication to set if completed successfully.\n * @param async to check for completion.\n * @return 0 for not complete (try again), 1 for completed successfully, or -1 for an error.\n */\nint aeron_async_add_publication_poll(aeron_publication_t **publication, aeron_async_add_publication_t *async);\n\n/**\n * Asynchronously add an exclusive publication using the given client and return an object to use to determine when the\n * publication is available.\n *\n * @param async object to use for polling completion.\n * @param client to add the publication to.\n * @param uri for the channel of the publication.\n * @param stream_id for the publication.\n * @return 0 for success or -1 for an error.\n */\nint aeron_async_add_exclusive_publication(\n    aeron_async_add_exclusive_publication_t **async, aeron_t *client, const char *uri, int32_t stream_id);\n\n/**\n * Poll the completion of the aeron_async_add_exclusive_publication call.\n *\n * @param publication to set if completed successfully.\n * @param async to check for completion.\n * @return 0 for not complete (try again), 1 for completed successfully, or -1 for an error.\n */\nint aeron_async_add_exclusive_publication_poll(\n    aeron_exclusive_publication_t **publication, aeron_async_add_exclusive_publication_t *async);\n\n/**\n * Asynchronously add a subscription using the given client and return an object to use to determine when the\n * subscription is available.\n *\n * @param async object to use for polling completion.\n * @param client to add the subscription to.\n * @param uri for the channel of the subscription.\n * @param stream_id for the subscription.\n * @param on_available_image_handler to be called when images become available on the subscription.\n * @param on_available_image_clientd to be passed when images become available on the subscription.\n * @param on_unavailable_image_handler to be called when images go unavailable on the subscription.\n * @param on_unavailable_image_clientd to be passed when images go unavailable on the subscription.\n * @return 0 for success or -1 for an error.\n */\nint aeron_async_add_subscription(\n    aeron_async_add_subscription_t **async,\n    aeron_t *client,\n    const char *uri,\n    int32_t stream_id,\n    aeron_on_available_image_t on_available_image_handler,\n    void *on_available_image_clientd,\n    aeron_on_unavailable_image_t on_unavailable_image_handler,\n    void *on_unavailable_image_clientd);\n\n/**\n * Poll the completion of the aeron_async_add_subscription call.\n *\n * @param subscription to set if completed successfully.\n * @param async to check for completion.\n * @return 0 for not complete (try again), 1 for completed successfully, or -1 for an error.\n */\nint aeron_async_add_subscription_poll(aeron_subscription_t **subscription, aeron_async_add_subscription_t *async);\n\n/**\n * Return a reference to the counters reader of the given client.\n *\n * The aeron_counters_reader_t is maintained by the client. And should not be freed.\n *\n * @param client that contains the counters reader.\n * @return aeron_counters_reader_t or NULL for error.\n */\naeron_counters_reader_t *aeron_counters_reader(aeron_t *client);\n\n/**\n * Asynchronously add a counter using the given client and return an object to use to determine when the\n * counter is available.\n *\n * @param async object to use for polling completion.\n * @param client to add the counter to.\n * @param type_id for the counter.\n * @param key_buffer for the counter.\n * @param key_buffer_length for the counter.\n * @param label_buffer for the counter.\n * @param label_buffer_length for the counter.\n * @return 0 for success or -1 for an error.\n */\nint aeron_async_add_counter(\n    aeron_async_add_counter_t **async,\n    aeron_t *client,\n    int32_t type_id,\n    const uint8_t *key_buffer,\n    size_t key_buffer_length,\n    const char *label_buffer,\n    size_t label_buffer_length);\n\n/**\n * Poll the completion of the <code>aeron_async_add_counter</code> or <code>aeron_async_add_static_counter</code> calls.\n *\n * @param counter to set if completed successfully.\n * @param async to check for completion.\n * @return 0 for not complete (try again), 1 for completed successfully, or -1 for an error.\n */\nint aeron_async_add_counter_poll(aeron_counter_t **counter, aeron_async_add_counter_t *async);\n\n/**\n * Asynchronously allocates or returns an existing static counter instance using specified <code>type_id</code> and\n * <code>registration_id</code>. Such counter cannot be deleted and its lifecycle is decoupled from the client that created it.\n * Returns an object to use to determine when the counter is available.\n *\n * @param async object to use for polling completion.\n * @param client to add the counter to.\n * @param type_id for the counter.\n * @param key_buffer for the counter.\n * @param key_buffer_length for the counter.\n * @param label_buffer for the counter.\n * @param label_buffer_length for the counter.\n * @param registration_id that uniquely identifies the counter.\n * @return 0 for success or -1 for an error.\n */\nint aeron_async_add_static_counter(\n    aeron_async_add_counter_t **async,\n    aeron_t *client,\n    int32_t type_id,\n    const uint8_t *key_buffer,\n    size_t key_buffer_length,\n    const char *label_buffer,\n    size_t label_buffer_length,\n    int64_t registration_id);\n\n/**\n * Free async command object that was used for polling asynchronously added resource. This method is invoked at the end\n * of the poll when resource reached the terminal state (e.g. error, success). However, if polling is aborted early and\n * <code>aeron_client</code> instance is closed the unpolled async instances must be manually freed.\n *\n * @param async object to free.\n */\nvoid aeron_async_cmd_free(struct aeron_client_registering_resource_stct *async);\n\ntypedef struct aeron_on_available_counter_pair_stct\n{\n    aeron_on_available_counter_t handler;\n    void *clientd;\n}\naeron_on_available_counter_pair_t;\n\ntypedef struct aeron_on_unavailable_counter_pair_stct\n{\n    aeron_on_unavailable_counter_t handler;\n    void *clientd;\n}\naeron_on_unavailable_counter_pair_t;\n\ntypedef struct aeron_on_close_client_pair_stct\n{\n    aeron_on_close_client_t handler;\n    void *clientd;\n}\naeron_on_close_client_pair_t;\n\n/**\n * Add a handler to be called when a new counter becomes available.\n *\n * NOTE: This function blocks until the handler is added by the client conductor thread.\n *\n * @param client for the counter\n * @param pair holding the handler to call and a clientd to pass when called.\n * @return 0 for success and -1 for error\n */\nint aeron_add_available_counter_handler(aeron_t *client, aeron_on_available_counter_pair_t *pair);\n\n/**\n * Remove a previously added handler to be called when a new counter becomes available.\n *\n * NOTE: This function blocks until the handler is removed by the client conductor thread.\n *\n * @param client for the counter\n * @param pair holding the handler to call and a clientd to pass when called.\n * @return 0 for success and -1 for error\n */\nint aeron_remove_available_counter_handler(aeron_t *client, aeron_on_available_counter_pair_t *pair);\n\n/**\n * Add a handler to be called when a new counter becomes unavailable or goes away.\n *\n * NOTE: This function blocks until the handler is added by the client conductor thread.\n *\n * @param client for the counter\n * @param pair holding the handler to call and a clientd to pass when called.\n * @return 0 for success and -1 for error\n */\nint aeron_add_unavailable_counter_handler(aeron_t *client, aeron_on_unavailable_counter_pair_t *pair);\n\n/**\n * Remove a previously added handler to be called when a new counter becomes unavailable or goes away.\n *\n * NOTE: This function blocks until the handler is removed by the client conductor thread.\n *\n * @param client for the counter\n * @param pair holding the handler to call and a clientd to pass when called.\n * @return 0 for success and -1 for error\n */\nint aeron_remove_unavailable_counter_handler(aeron_t *client, aeron_on_unavailable_counter_pair_t *pair);\n\n/**\n * Add a handler to be called when client is closed.\n *\n * NOTE: This function blocks until the handler is added by the client conductor thread.\n *\n * @param client for the counter\n * @param pair holding the handler to call and a clientd to pass when called.\n * @return 0 for success and -1 for error\n */\nint aeron_add_close_handler(aeron_t *client, aeron_on_close_client_pair_t *pair);\n\n/**\n * Remove a previously added handler to be called when client is closed.\n *\n * NOTE: This function blocks until the handler is removed by the client conductor thread.\n *\n * @param client for the counter\n * @param pair holding the handler to call and a clientd to pass when called.\n * @return 0 for success and -1 for error\n */\nint aeron_remove_close_handler(aeron_t *client, aeron_on_close_client_pair_t *pair);\n\n/**\n * Counters Reader functions and definitions\n */\n\n// Separate definition to avoid needing aeron_bitutil.h\n#define AERON_COUNTER_CACHE_LINE_LENGTH (64u)\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_counter_value_descriptor_stct\n{\n    volatile int64_t counter_value;\n    volatile int64_t registration_id;\n    int64_t owner_id;\n    int64_t reference_id;\n    uint8_t pad1[(2 * AERON_COUNTER_CACHE_LINE_LENGTH) - (4 * sizeof(int64_t))];\n}\naeron_counter_value_descriptor_t;\n\ntypedef struct aeron_counter_metadata_descriptor_stct\n{\n    volatile int32_t state;\n    int32_t type_id;\n    volatile int64_t free_for_reuse_deadline_ms;\n    uint8_t key[(2 * AERON_COUNTER_CACHE_LINE_LENGTH) - (2 * sizeof(int32_t)) - sizeof(int64_t)];\n    volatile int32_t label_length;\n    uint8_t label[(6 * AERON_COUNTER_CACHE_LINE_LENGTH) - sizeof(int32_t)];\n}\naeron_counter_metadata_descriptor_t;\n#pragma pack(pop)\n\n\n#define AERON_COUNTER_VALUE_LENGTH sizeof(aeron_counter_value_descriptor_t)\n#define AERON_COUNTER_REGISTRATION_ID_OFFSET offsetof(aeron_counter_value_descriptor_t, registration_id)\n\n#define AERON_COUNTER_METADATA_LENGTH sizeof(aeron_counter_metadata_descriptor_t)\n#define AERON_COUNTER_TYPE_ID_OFFSET offsetof(aeron_counter_metadata_descriptor_t, type_id)\n#define AERON_COUNTER_FREE_FOR_REUSE_DEADLINE_OFFSET offsetof(aeron_counter_metadata_descriptor_t, free_for_reuse_deadline_ms)\n#define AERON_COUNTER_KEY_OFFSET offsetof(aeron_counter_metadata_descriptor_t, key)\n#define AERON_COUNTER_LABEL_LENGTH_OFFSET offsetof(aeron_counter_metadata_descriptor_t, label)\n\n#define AERON_COUNTER_MAX_LABEL_LENGTH sizeof(((aeron_counter_metadata_descriptor_t *)NULL)->label)\n#define AERON_COUNTER_MAX_KEY_LENGTH sizeof(((aeron_counter_metadata_descriptor_t *)NULL)->key)\n#define AERON_COUNTER_MAX_CLIENT_NAME_LENGTH (100)\n\n#define AERON_COUNTER_RECORD_UNUSED (0)\n#define AERON_COUNTER_RECORD_ALLOCATED (1)\n#define AERON_COUNTER_RECORD_RECLAIMED (-1)\n\n#define AERON_COUNTER_REGISTRATION_ID_DEFAULT INT64_C(0)\n#define AERON_COUNTER_NOT_FREE_TO_REUSE (INT64_MAX)\n#define AERON_COUNTER_OWNER_ID_DEFAULT INT64_C(0)\n#define AERON_COUNTER_REFERENCE_ID_DEFAULT INT64_C(0)\n\n#define AERON_NULL_COUNTER_ID (-1)\n\n#define AERON_COUNTER_OFFSET(id) ((id) * AERON_COUNTER_VALUE_LENGTH)\n#define AERON_COUNTER_METADATA_OFFSET(id) ((id) * AERON_COUNTER_METADATA_LENGTH)\n\ntypedef struct aeron_counters_reader_buffers_stct\n{\n    uint8_t *values;\n    uint8_t *metadata;\n    size_t values_length;\n    size_t metadata_length;\n}\naeron_counters_reader_buffers_t;\n\n/**\n * Get buffer pointers and lengths for the counters reader.\n *\n * @param reader reader containing the buffers.\n * @param buffers output structure to return the buffers.\n * @return -1 on failure, 0 on success.\n */\nint aeron_counters_reader_get_buffers(aeron_counters_reader_t *reader, aeron_counters_reader_buffers_t *buffers);\n\n/**\n * Function called by aeron_counters_reader_foreach_counter for each counter in the aeron_counters_reader_t.\n *\n * @param value of the counter.\n * @param id of the counter.\n * @param label for the counter.\n * @param label_length for the counter.\n * @param clientd to be returned in the call\n */\ntypedef void (*aeron_counters_reader_foreach_counter_func_t)(\n    int64_t value,\n    int32_t id,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const char *label,\n    size_t label_length,\n    void *clientd);\n\n/**\n * Iterate over the counters in the counters_reader and call the given function for each counter.\n *\n * @param counters_reader to iterate over.\n * @param func to call for each counter.\n * @param clientd to pass for each call to func.\n */\nvoid aeron_counters_reader_foreach_counter(\n    aeron_counters_reader_t *counters_reader, aeron_counters_reader_foreach_counter_func_t func, void *clientd);\n\n/**\n * Iterate over allocated counters and find the first matching a given type id and registration id.\n *\n * @param counters_reader\n * @param type_id to find.\n * @param registration_id to find.\n * @return the counter id if found otherwise AERON_NULL_COUNTER_ID.\n */\nint32_t aeron_counters_reader_find_by_type_id_and_registration_id(\n    aeron_counters_reader_t *counters_reader, int32_t type_id, int64_t registration_id);\n\n/**\n * Get the current max counter id.\n *\n * @param reader to query\n * @return -1 on failure, max counter id on success.\n */\nint32_t aeron_counters_reader_max_counter_id(aeron_counters_reader_t *reader);\n\n/**\n * Get the address for a counter.\n *\n * @param counters_reader that contains the counter\n * @param counter_id to find\n * @return address of the counter value\n */\nint64_t *aeron_counters_reader_addr(aeron_counters_reader_t *counters_reader, int32_t counter_id);\n\n/**\n * Get the registration id assigned to a counter.\n *\n * @param counters_reader representing the this pointer.\n * @param counter_id      for which the registration id is requested.\n * @param registration_id pointer for value to be set on success.\n * @return -1 on failure, 0 on success.\n */\nint aeron_counters_reader_counter_registration_id(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t *registration_id);\n\n/**\n * Get the owner id assigned to a counter which will typically be the client id.\n *\n * @param counters_reader representing the this pointer.\n * @param counter_id      for which the owner id is requested.\n * @param owner_id        pointer for value to be set on success.\n * @return -1 on failure, 0 on success.\n */\nint aeron_counters_reader_counter_owner_id(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t *owner_id);\n\n/**\n * Get the reference id assigned to a counter which will typically be the registration id of an associated Image,\n * Subscription, Publication, etc.\n *\n * @param counters_reader representing the this pointer.\n * @param counter_id      for which the reference id is requested.\n * @param reference_id    pointer for value to be set on success.\n * @return -1 on failure, 0 on success.\n */\nint aeron_counters_reader_counter_reference_id(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t *reference_id);\n\n/**\n * Get the state for a counter.\n *\n * @param counters_reader that contains the counter\n * @param counter_id to find\n * @param state out pointer for the current state to be stored in.\n * @return -1 on failure, 0 on success.\n */\nint aeron_counters_reader_counter_state(aeron_counters_reader_t *counters_reader, int32_t counter_id, int32_t *state);\n\n/**\n * Get the type id for a counter.\n *\n * @param counters_reader that contains the counter\n * @param counter_id to find\n * @param type id out pointer for the current state to be stored in.\n * @return -1 on failure, 0 on success.\n */\nint aeron_counters_reader_counter_type_id(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int32_t *type_id);\n\n/**\n * Get a pointer to the key of a counter's metadata\n *\n * @param counters_reader that contains the counter\n * @param counter_id to find\n * @param key_p out pointer set to location of metadata key\n * @return -1 on failure, 0 on success.\n */\nint aeron_counters_reader_metadata_key(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, uint8_t **key_p);\n\n/**\n * Get the label for a counter.\n *\n * @param counters_reader that contains the counter\n * @param counter_id to find\n * @param buffer to store the counter in.\n * @param buffer_length length of the output buffer\n * @return -1 on failure, number of characters copied to buffer on success.\n */\nint aeron_counters_reader_counter_label(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, char *buffer, size_t buffer_length);\n\n/**\n * Get the free for reuse deadline (ms) for a counter.\n *\n * @param counters_reader that contains the counter.\n * @param counter_id to find.\n * @param deadline_ms output value to store the deadline.\n * @return -1 on failure, 0 on success.\n */\nint aeron_counters_reader_free_for_reuse_deadline_ms(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t *deadline_ms);\n\n/**\n * Publication functions\n */\n\n/**\n * The publication is not connected to a subscriber, this can be an intermittent state as subscribers come and go.\n */\n#define AERON_PUBLICATION_NOT_CONNECTED (-1L)\n\n/**\n * The offer failed due to back pressure from the subscribers preventing further transmission.\n */\n#define AERON_PUBLICATION_BACK_PRESSURED (-2L)\n\n/**\n * The offer failed due to an administration action and should be retried.\n * The action is an operation such as log rotation which is likely to have succeeded by the next retry attempt.\n */\n#define AERON_PUBLICATION_ADMIN_ACTION (-3L)\n\n/**\n * The publication has been closed and should no longer be used.\n */\n#define AERON_PUBLICATION_CLOSED (-4L)\n\n/**\n * The offer failed due to reaching the maximum position of the stream given term buffer length times the total\n * possible number of terms.\n * <p>\n * If this happens then the publication should be closed and a new one added. To make it less likely to happen then\n * increase the term buffer length.\n */\n#define AERON_PUBLICATION_MAX_POSITION_EXCEEDED (-5L)\n\n/**\n * An error has occurred. Such as a bad argument.\n */\n#define AERON_PUBLICATION_ERROR (-6L)\n\n/**\n * Function called when filling in the reserved value field of a message.\n *\n * @param clientd passed to the offer function.\n * @param buffer of the entire frame, including Aeron data header.\n * @param frame_length of the entire frame.\n */\ntypedef int64_t (*aeron_reserved_value_supplier_t)(void *clientd, uint8_t *buffer, size_t frame_length);\n\n/**\n * Structure to hold pointer to a buffer and the buffer length.\n */\n#if !defined(AERON_IOVEC)\ntypedef struct aeron_iovec_stct\n{\n    uint8_t *iov_base;\n    size_t iov_len;\n}\naeron_iovec_t;\n#else\ntypedef struct iov aeron_iovec_t;\n#endif\n\n/**\n * Structure used to hold information for a try_claim function call.\n */\ntypedef struct aeron_buffer_claim_stct\n{\n    uint8_t *frame_header;\n    uint8_t *data;\n    size_t length;\n}\naeron_buffer_claim_t;\n\n/**\n * Commit the given buffer_claim as a complete message available for consumption.\n *\n * @param buffer_claim to commit.\n * @return 0 for success or -1 for error.\n */\nint aeron_buffer_claim_commit(aeron_buffer_claim_t *buffer_claim);\n\n/**\n * Abort the given buffer_claim and assign its position as padding.\n *\n * @param buffer_claim to abort.\n * @return 0 for success or -1 for error.\n */\nint aeron_buffer_claim_abort(aeron_buffer_claim_t *buffer_claim);\n\n/**\n * Configuration for a publication that does not change during it's lifetime.\n */\ntypedef struct aeron_publication_constants_stct\n{\n    /**\n     * Media address for delivery to the channel.\n     *\n     * This returns a pointer only valid for the lifetime of the publication.\n     */\n    const char *channel;\n\n    /**\n     * The registration used to register this Publication with the media driver by the first publisher.\n     */\n    int64_t original_registration_id;\n\n    /**\n     * Get the registration id used to register this Publication with the media driver.\n     *\n     * If this value is different from the original_registration_id then a previous active registration exists.\n     */\n    int64_t registration_id;\n\n    /**\n     * The maximum possible position this stream can reach due to its term buffer length.\n     *\n     * Maximum possible position is term-length times 2^31 in bytes.\n     */\n    int64_t max_possible_position;\n\n    /**\n     * Number of bits to right shift a position to get a term count for how far the stream has progressed.\n     */\n    size_t position_bits_to_shift;\n\n    /**\n     * Get the length in bytes for each term partition in the log buffer.\n     */\n    size_t term_buffer_length;\n\n    /**\n     * Maximum message length supported in bytes. Messages may be made of multiple fragments if greater than\n     * MTU length.\n     */\n    size_t max_message_length;\n\n    /**\n     * Maximum length of a message payload that fits within a message fragment.\n     *\n     * This is the MTU length minus the message fragment header length.\n     */\n    size_t max_payload_length;\n\n    /**\n     * Stream id of the publication.\n     */\n    int32_t stream_id;\n\n    /**\n     * Session id of the publication.\n     */\n    int32_t session_id;\n\n    /**\n     * The initial term id assigned when this publication was created. This can be used to determine how many\n     * terms have passed since creation.\n     */\n    int32_t initial_term_id;\n\n    /**\n     * Counter id for the publication limit.\n     */\n    int32_t publication_limit_counter_id;\n\n    /**\n     * Counter id for the channel status indicator\n     */\n    int32_t channel_status_indicator_id;\n}\naeron_publication_constants_t;\n\n/**\n * Non-blocking publish of a buffer containing a message.\n *\n * @param publication to publish on.\n * @param buffer to publish.\n * @param length of the buffer.\n * @param reserved_value_supplier to use for setting the reserved value field or NULL.\n * @param clientd to pass to the reserved_value_supplier.\n * @return the new stream position otherwise a negative error value.\n */\nint64_t aeron_publication_offer(\n    aeron_publication_t *publication,\n    const uint8_t *buffer,\n    size_t length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd);\n\n/**\n * Non-blocking publish by gathering buffer vectors into a message.\n *\n * @param publication to publish on.\n * @param iov array for the vectors\n * @param iovcnt of the number of vectors\n * @param reserved_value_supplier to use for setting the reserved value field or NULL.\n * @param clientd to pass to the reserved_value_supplier.\n * @return the new stream position otherwise a negative error value.\n */\nint64_t aeron_publication_offerv(\n    aeron_publication_t *publication,\n    aeron_iovec_t *iov,\n    size_t iovcnt,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd);\n\n/**\n * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n * Once the message has been written then aeron_buffer_claim_commit should be called thus making it available.\n * A claim length cannot be greater than max payload length.\n * <p>\n * <b>Note:</b> This method can only be used for message lengths less than MTU length minus header.\n * If the claim is held for more than the aeron.publication.unblock.timeout system property then the driver will\n * assume the publication thread is dead and will unblock the claim thus allowing other threads to make progress\n * and other claims to be sent to reach end-of-stream (EOS).\n *\n * @code\n * aeron_buffer_claim_t buffer_claim;\n *\n * if (aeron_publication_try_claim(publication, length, &buffer_claim) > 0L)\n * {\n *     // work with buffer_claim->data directly.\n *     aeron_buffer_claim_commit(&buffer_claim);\n * }\n * @endcode\n *\n * @param publication to publish to.\n * @param length of the message.\n * @param buffer_claim to be populated if the claim succeeds.\n * @return the new stream position otherwise a negative error value.\n */\nint64_t aeron_publication_try_claim(\n    aeron_publication_t *publication, size_t length, aeron_buffer_claim_t *buffer_claim);\n\n/**\n * Get the status of the media channel for this publication.\n * <p>\n * The status will be ERRORED (-1) if a socket exception occurs on setup and ACTIVE (1) if all is well.\n *\n * @param publication to check status of.\n * @return 1 for ACTIVE, -1 for ERRORED\n */\nint64_t aeron_publication_channel_status(aeron_publication_t *publication);\n\n/**\n * Has the publication closed?\n *\n * @param publication to check\n * @return true if this publication is closed.\n */\nbool aeron_publication_is_closed(aeron_publication_t *publication);\n\n/**\n * Has the publication seen an active Subscriber recently?\n *\n * @param publication to check.\n * @return true if this publication has recently seen an active subscriber otherwise false.\n */\nbool aeron_publication_is_connected(aeron_publication_t *publication);\n\n/**\n * Fill in a structure with the constants in use by a publication.\n *\n * @param publication to get the constants for.\n * @param constants structure to fill in with the constants\n * @return 0 for success and -1 for error.\n */\nint aeron_publication_constants(aeron_publication_t *publication, aeron_publication_constants_t *constants);\n\n/**\n * Get the current position to which the publication has advanced for this stream.\n *\n * @param publication to query.\n * @return the current position to which the publication has advanced for this stream or a negative error value.\n */\nint64_t aeron_publication_position(aeron_publication_t *publication);\n\n/**\n * Get the position limit beyond which this publication will be back pressured.\n *\n * This should only be used as a guide to determine when back pressure is likely to be applied.\n *\n * @param publication to query.\n * @return the position limit beyond which this publication will be back pressured or a negative error value.\n */\nint64_t aeron_publication_position_limit(aeron_publication_t *publication);\n\n/**\n * Add a destination manually to a multi-destination-cast publication.\n *\n * @param async object to use for polling completion.\n * @param publication to add destination to.\n * @param uri for the destination to add.\n * @return 0 for success and -1 for error.\n */\nint aeron_publication_async_add_destination(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_publication_t *publication,\n    const char *uri);\n\n/**\n * Remove a destination manually from a multi-destination-cast publication.\n *\n * @param async object to use for polling completion.\n * @param publication to remove destination from.\n * @param uri for the destination to remove.\n * @return 0 for success and -1 for error.\n */\nint aeron_publication_async_remove_destination(\n    aeron_async_destination_t **async, aeron_t *client, aeron_publication_t *publication, const char *uri);\n\n/**\n * Remove a destination manually from a multi-destination-cast publication.\n *\n * @param async object to use for polling completion.\n * @param publication to remove destination from.\n * @param destination_registration_id for the destination to remove.\n * @return 0 for success and -1 for error.\n */\nint aeron_publication_async_remove_destination_by_id(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_publication_t *publication,\n    int64_t destination_registration_id);\n\n/**\n * Poll the completion of the add/remove of a destination to/from a publication.\n *\n * @param async to check for completion.\n * @return 0 for not complete (try again), 1 for completed successfully, or -1 for an error.\n */\nint aeron_publication_async_destination_poll(aeron_async_destination_t *async);\n\n/**\n * Add a destination manually to a multi-destination-cast exclusive publication.\n *\n * @param async object to use for polling completion.\n * @param publication to add destination to.\n * @param uri for the destination to add.\n * @return 0 for success and -1 for error.\n */\nint aeron_exclusive_publication_async_add_destination(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_exclusive_publication_t *publication,\n    const char *uri);\n\n/**\n * Remove a destination manually from a multi-destination-cast exclusive publication.\n *\n * @param async object to use for polling completion.\n * @param publication to remove destination from.\n * @param uri for the destination to remove.\n * @return 0 for success and -1 for error.\n */\nint aeron_exclusive_publication_async_remove_destination(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_exclusive_publication_t *publication,\n    const char *uri);\n\n/**\n * Remove a destination manually from a multi-destination-cast publication.\n *\n * @param async object to use for polling completion.\n * @param publication to remove destination from.\n * @param destination_registration_id for the destination to remove.\n * @return 0 for success and -1 for error.\n */\nint aeron_exclusive_publication_async_remove_destination_by_id(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_exclusive_publication_t *publication,\n    int64_t destination_registration_id);\n\n/**\n * Poll the completion of the add/remove of a destination to/from an exclusive publication.\n *\n * @param async to check for completion.\n * @return 0 for not complete (try again), 1 for completed successfully, or -1 for an error.\n */\nint aeron_exclusive_publication_async_destination_poll(aeron_async_destination_t *async);\n\n/**\n * Asynchronously close the publication. Will callback on the on_complete notification when the publication is closed.\n * The callback is optional, use NULL for the on_complete callback if not required.\n *\n * @param publication to close\n * @param on_close_complete optional callback to execute once the publication has been closed and freed. This may\n * happen on a separate thread, so the caller should ensure that clientd has the appropriate lifetime.\n * @param on_close_complete_clientd parameter to pass to the on_complete callback.\n * @return 0 for success or -1 for error.\n */\nint aeron_publication_close(\n    aeron_publication_t *publication,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd);\n\n/**\n * Get the publication's channel\n *\n * @param publication this\n * @return channel uri string\n */\nconst char *aeron_publication_channel(aeron_publication_t *publication);\n\n/**\n * Get the publication's stream id\n *\n * @param publication this\n * @return stream id\n */\nint32_t aeron_publication_stream_id(aeron_publication_t *publication);\n\n/**\n * Get the publication's session id\n * @param publication this\n * @return session id\n */\nint32_t aeron_publication_session_id(aeron_publication_t *publication);\n\n/**\n * Get all of the local socket addresses for this publication. Typically only one representing the control address.\n *\n * @param subscription to query\n * @param address_vec to hold the received addresses\n * @param address_vec_len available length of the vector to hold the addresses\n * @return number of addresses found or -1 if there is an error.\n * @see aeron_subscription_local_sockaddrs\n */\nint aeron_publication_local_sockaddrs(\n    aeron_publication_t *publication,\n    aeron_iovec_t *address_vec,\n    size_t address_vec_len);\n\n/*\n * Exclusive Publication functions\n */\n\n/**\n * Non-blocking publish of a buffer containing a message.\n *\n * @param publication to publish on.\n * @param buffer to publish.\n * @param length of the buffer.\n * @param reserved_value_supplier to use for setting the reserved value field or NULL.\n * @param clientd to pass to the reserved_value_supplier.\n * @return the new stream position otherwise a negative error value.\n */\nint64_t aeron_exclusive_publication_offer(\n    aeron_exclusive_publication_t *publication,\n    const uint8_t *buffer,\n    size_t length,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd);\n\n/**\n * Non-blocking publish by gathering buffer vectors into a message.\n *\n * @param publication to publish on.\n * @param iov array for the vectors\n * @param iovcnt of the number of vectors\n * @param reserved_value_supplier to use for setting the reserved value field or NULL.\n * @param clientd to pass to the reserved_value_supplier.\n * @return the new stream position otherwise a negative error value.\n */\nint64_t aeron_exclusive_publication_offerv(\n    aeron_exclusive_publication_t *publication,\n    aeron_iovec_t *iov,\n    size_t iovcnt,\n    aeron_reserved_value_supplier_t reserved_value_supplier,\n    void *clientd);\n\n/**\n * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n * Once the message has been written then aeron_buffer_claim_commit should be called thus making it available.\n * A claim length cannot be greater than max payload length.\n * <p>\n * <b>Note:</b> This method can only be used for message lengths less than MTU length minus header.\n *\n * @code\n * aeron_buffer_claim_t buffer_claim;\n *\n * if (aeron_exclusive_publication_try_claim(publication, length, &buffer_claim) > 0L)\n * {\n *     // work with buffer_claim->data directly.\n *     aeron_buffer_claim_commit(&buffer_claim);\n * }\n * @endcode\n *\n * @param publication to publish to.\n * @param length of the message.\n * @param buffer_claim to be populated if the claim succeeds.\n * @return the new stream position otherwise a negative error value.\n */\nint64_t aeron_exclusive_publication_try_claim(\n    aeron_exclusive_publication_t *publication, size_t length, aeron_buffer_claim_t *buffer_claim);\n\n/**\n * Append a padding record log of a given length to make up the log to a position.\n *\n * @param length of the range to claim, in bytes.\n * @return the new stream position otherwise a negative error value.\n */\nint64_t aeron_exclusive_publication_append_padding(aeron_exclusive_publication_t *publication, size_t length);\n\n/**\n * Offer a block of pre-formatted message fragments directly into the current term.\n *\n * @param buffer containing the pre-formatted block of message fragments.\n * @param offset offset in the buffer at which the first fragment begins.\n * @param length in bytes of the encoded block.\n * @return the new stream position otherwise a negative error value.\n */\nint64_t aeron_exclusive_publication_offer_block(\n    aeron_exclusive_publication_t *publication, const uint8_t *buffer, size_t length);\n\n/**\n * Get the status of the media channel for this publication.\n * <p>\n * The status will be ERRORED (-1) if a socket exception occurs on setup and ACTIVE (1) if all is well.\n *\n * @param publication to check status of.\n * @return 1 for ACTIVE, -1 for ERRORED\n */\nint64_t aeron_exclusive_publication_channel_status(aeron_exclusive_publication_t *publication);\n\n/**\n * Fill in a structure with the constants in use by a publication.\n *\n * @param publication to get the constants for.\n * @param constants structure to fill in with the constants\n * @return 0 for success and -1 for error.\n */\nint aeron_exclusive_publication_constants(\n    aeron_exclusive_publication_t *publication, aeron_publication_constants_t *constants);\n\n/**\n * Get the current position to which the publication has advanced for this stream.\n *\n * @param publication to query.\n * @return the current position to which the publication has advanced for this stream or a negative error value.\n */\nint64_t aeron_exclusive_publication_position(aeron_exclusive_publication_t *publication);\n\n/**\n * Get the position limit beyond which this publication will be back pressured.\n *\n * This should only be used as a guide to determine when back pressure is likely to be applied.\n *\n * @param publication to query.\n * @return the position limit beyond which this publication will be back pressured or a negative error value.\n */\nint64_t aeron_exclusive_publication_position_limit(aeron_exclusive_publication_t *publication);\n\n/**\n * Asynchronously close the publication.\n *\n * @param publication to close\n * @return 0 for success or -1 for error.\n */\nint aeron_exclusive_publication_close(\n    aeron_exclusive_publication_t *publication,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd);\n\n/**\n * Revoke this publication when it's closed.\n *\n * @param publication to revoke on close\n */\nvoid aeron_exclusive_publication_revoke_on_close(aeron_exclusive_publication_t *publication);\n\n/**\n * Asynchronously revoke and close the publication. Will callback on the on_complete notification when the publicaiton is closed.\n * The callback is optional, use NULL for the on_complete callback if not required.\n *\n * @param publication to revoke and close\n * @param on_close_complete optional callback to execute once the publication has been revoked, closed and freed. This may\n * happen on a separate thread, so the caller should ensure that clientd has the appropriate lifetime.\n * @param on_close_complete_clientd parameter to pass to the on_complete callback.\n * @return 0 for success or -1 for error.\n */\nint aeron_exclusive_publication_revoke(\n    aeron_exclusive_publication_t *publication,\n    aeron_notification_t on_close_complete,\n    void *on_close_complete_clientd);\n\n/**\n * Has the exclusive publication closed?\n *\n * @param publication to check\n * @return true if this publication is closed.\n */\nbool aeron_exclusive_publication_is_closed(aeron_exclusive_publication_t *publication);\n\n/**\n * Has the exclusive publication seen an active Subscriber recently?\n *\n * @param publication to check.\n * @return true if this publication has recently seen an active subscriber otherwise false.\n */\nbool aeron_exclusive_publication_is_connected(aeron_exclusive_publication_t *publication);\n\n/**\n * Get all of the local socket addresses for this exclusive publication. Typically only one representing the control\n * address.\n *\n * @see aeron_subscription_local_sockaddrs\n * @param subscription to query\n * @param address_vec to hold the received addresses\n * @param address_vec_len available length of the vector to hold the addresses\n * @return number of addresses found or -1 if there is an error.\n */\nint aeron_exclusive_publication_local_sockaddrs(\n    aeron_exclusive_publication_t *publication, aeron_iovec_t *address_vec, size_t address_vec_len);\n\n/**\n * Subscription functions\n *\n * Aeron Subscriber API for receiving a reconstructed image for a stream of messages from publishers on\n * a given channel and stream id pair. Images are aggregated under a subscription.\n * <p>\n * Subscription are created via an aeron_t object, and received messages are delivered\n * to the fragment handler.\n * <p>\n * By default fragmented messages are not reassembled before delivery. If an application must\n * receive whole messages, whether or not they were fragmented, then the subscriber\n * should be created with a fragment assembler or a custom implementation.\n * <p>\n * It is an application's responsibility to poll the subscription for new messages.\n * <p>\n * <b>Note:</b>Subscriptions are not threadsafe and should not be shared between subscribers.\n */\n\n/**\n * Callback for handling fragments of data being read from a log.\n *\n * The frame will either contain a whole message or a fragment of a message to be reassembled. Messages are fragmented\n * if greater than the frame for MTU in length.\n *\n * @param clientd passed to the poll function.\n * @param buffer containing the data.\n * @param length of the data in bytes.\n * @param header representing the meta data for the data.\n */\ntypedef void (*aeron_fragment_handler_t)(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\n\ntypedef enum aeron_controlled_fragment_handler_action_en\n{\n    /**\n     * Abort the current polling operation and do not advance the position for this fragment.\n     */\n    AERON_ACTION_ABORT = 1,\n\n    /**\n     * Break from the current polling operation and commit the position as of the end of the current fragment\n     * being handled.\n     */\n    AERON_ACTION_BREAK = 2,\n\n    /**\n     * Continue processing but commit the position as of the end of the current fragment so that\n     * flow control is applied to this point.\n     */\n    AERON_ACTION_COMMIT = 3,\n\n    /**\n     * Continue processing until fragment limit or no fragments with position commit at end of poll as in\n     * aeron_fragment_handler_t.\n     */\n    AERON_ACTION_CONTINUE = 4\n}\naeron_controlled_fragment_handler_action_t;\n\n/**\n * Callback for handling fragments of data being read from a log.\n *\n * Handler for reading data that is coming from a log buffer. The frame will either contain a whole message\n * or a fragment of a message to be reassembled. Messages are fragmented if greater than the frame for MTU in length.\n *\n * @param clientd passed to the controlled poll function.\n * @param buffer containing the data.\n * @param length of the data in bytes.\n * @param header representing the meta data for the data.\n * @return The action to be taken with regard to the stream position after the callback.\n */\ntypedef aeron_controlled_fragment_handler_action_t (*aeron_controlled_fragment_handler_t)(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\n\n/**\n * Callback for handling a block of messages being read from a log.\n *\n * @param clientd passed to the block poll function.\n * @param buffer containing the block of message fragments.\n * @param offset at which the block begins, including any frame headers.\n * @param length of the block in bytes, including any frame headers that is aligned.\n * @param session_id of the stream containing this block of message fragments.\n * @param term_id of the stream containing this block of message fragments.\n */\ntypedef void (*aeron_block_handler_t)(\n    void *clientd, const uint8_t *buffer, size_t length, int32_t session_id, int32_t term_id);\n\n/**\n * Get all of the field values from the header. This will do a memcpy into the supplied header_values_t pointer.\n *\n * @param header to read values from.\n * @param values to copy values to, must not be null.\n * @return 0 on success, -1 on failure.\n */\nint aeron_header_values(aeron_header_t *header, aeron_header_values_t *values);\n\n/**\n * Get the current position to which the Image has advanced on reading this message.\n *\n * @param header the current header message\n * @return the current position to which the Image has advanced on reading this message.\n */\nint64_t aeron_header_position(aeron_header_t *header);\n\n/**\n * Get the number of times to left shift the term count to multiply by term length.\n *\n * @return number of times to left shift the term count to multiply by term length.\n */\nsize_t aeron_header_position_bits_to_shift(aeron_header_t *header);\n\n/**\n * Calculates the offset of the frame immediately after this one.\n *\n * @return the offset of the next frame.\n */\nint32_t aeron_header_next_term_offset(aeron_header_t *header);\n\n/**\n * Get a pointer to the context associated with this message. Only valid during poll handling. Is normally a\n * pointer to an Image instance.\n *\n * @return a pointer to the context associated with this message.\n */\nvoid *aeron_header_context(aeron_header_t *header);\n\ntypedef struct aeron_subscription_constants_stct\n{\n    /**\n     * Media address for delivery to the channel.\n     *\n     * This returns a pointer only valid for the lifetime of the subscription.\n     */\n    const char *channel;\n\n    /**\n     * Callback used to indicate when an Image becomes available under this Subscription.\n     */\n    aeron_on_available_image_t on_available_image;\n\n    /**\n     * Callback used to indicate when an Image goes unavailable under this Subscription.\n     */\n    aeron_on_unavailable_image_t on_unavailable_image;\n\n    /**\n     * Return the registration id used to register this Subscription with the media driver.\n     */\n    int64_t registration_id;\n\n    /**\n     * Stream identity for scoping within the channel media address.\n     */\n    int32_t stream_id;\n\n    /**\n     * Counter id for the channel status indicator\n     */\n    int32_t channel_status_indicator_id;\n}\naeron_subscription_constants_t;\n\n/**\n * Poll the images under the subscription for available message fragments.\n * <p>\n * Each fragment read will be a whole message if it is under MTU length. If larger than MTU then it will come\n * as a series of fragments ordered within a session.\n * <p>\n * To assemble messages that span multiple fragments then use aeron_fragment_assembler_t.\n *\n * @param subscription to poll.\n * @param handler for handling each message fragment as it is read.\n * @param fragment_limit number of message fragments to limit when polling across multiple images.\n * @return the number of fragments received or -1 for error.\n */\nint aeron_subscription_poll(\n    aeron_subscription_t *subscription, aeron_fragment_handler_t handler, void *clientd, size_t fragment_limit);\n\n/**\n * Poll in a controlled manner the images under the subscription for available message fragments.\n * Control is applied to fragments in the stream. If more fragments can be read on another stream\n * they will even if BREAK or ABORT is returned from the fragment handler.\n * <p>\n * Each fragment read will be a whole message if it is under MTU length. If larger than MTU then it will come\n * as a series of fragments ordered within a session.\n * <p>\n * To assemble messages that span multiple fragments then use aeron_controlled_fragment_assembler_t.\n *\n * @param subscription to poll.\n * @param handler for handling each message fragment as it is read.\n * @param fragment_limit number of message fragments to limit when polling across multiple images.\n * @return the number of fragments received or -1 for error.\n */\nint aeron_subscription_controlled_poll(\n    aeron_subscription_t *subscription,\n    aeron_controlled_fragment_handler_t handler,\n    void *clientd,\n    size_t fragment_limit);\n\n/**\n * Poll the images under the subscription for available message fragments in blocks.\n * <p>\n * This method is useful for operations like bulk archiving and messaging indexing.\n *\n * @param subscription to poll.\n * @param handler to receive a block of fragments from each image.\n * @param block_length_limit for each image polled.\n * @return the number of bytes consumed or -1 for error.\n */\nlong aeron_subscription_block_poll(\n    aeron_subscription_t *subscription, aeron_block_handler_t handler, void *clientd, size_t block_length_limit);\n\n/**\n * Is this subscription connected by having at least one open publication image.\n *\n * @param subscription to check.\n * @return true if this subscription connected by having at least one open publication image.\n */\nbool aeron_subscription_is_connected(aeron_subscription_t *subscription);\n\n/**\n * Fill in a structure with the constants in use by a subscription.\n *\n * @param subscription to get the constants for.\n * @param constants structure to fill in with the constants\n * @return 0 for success and -1 for error.\n */\nint aeron_subscription_constants(aeron_subscription_t *subscription, aeron_subscription_constants_t *constants);\n\n/**\n * Count of images associated to this subscription.\n *\n * @param subscription to count images for.\n * @return count of count associated to this subscription or -1 for error.\n */\nint aeron_subscription_image_count(aeron_subscription_t *subscription);\n\n/**\n * Return the image associated with the given session_id under the given subscription.\n *\n * Note: the returned image is considered retained by the application and thus must be released via\n * aeron_image_release when finished or if the image becomes unavailable.\n *\n * @param subscription to search.\n * @param session_id associated with the image.\n * @return image associated with the given session_id or NULL if no image exists.\n */\naeron_image_t *aeron_subscription_image_by_session_id(aeron_subscription_t *subscription, int32_t session_id);\n\n/**\n * Return the image at the given index.\n *\n * Note: the returned image is considered retained by the application and thus must be released via\n * aeron_image_release when finished or if the image becomes unavailable.\n *\n * @param subscription to search.\n * @param index for the image.\n * @return image at the given index or NULL if no image exists.\n */\naeron_image_t *aeron_subscription_image_at_index(aeron_subscription_t *subscription, size_t index);\n\n/**\n * Iterate over the images for this subscription calling the given function.\n *\n * @param subscription to iterate over.\n * @param handler to be called for each image.\n * @param clientd to be passed to the handler.\n */\nvoid aeron_subscription_for_each_image(\n    aeron_subscription_t *subscription, void (*handler)(aeron_image_t *image, void *clientd), void *clientd);\n\n/**\n * Retain the given image for access in the application.\n *\n * Note: A retain call must have a corresponding release call.\n * Note: Subscriptions are not threadsafe and should not be shared between subscribers.\n *\n * @param subscription that image is part of.\n * @param image to retain\n * @return 0 for success and -1 for error.\n */\nint aeron_subscription_image_retain(aeron_subscription_t *subscription, aeron_image_t *image);\n\n/**\n * Release the given image and relinquish desire to use the image directly.\n *\n * Note: Subscriptions are not threadsafe and should not be shared between subscribers.\n *\n * @param subscription that image is part of.\n * @param image to release\n * @return 0 for success and -1 for error.\n */\nint aeron_subscription_image_release(aeron_subscription_t *subscription, aeron_image_t *image);\n\n/**\n * Is the subscription closed.\n *\n * @param subscription to be checked.\n * @return true if it has been closed otherwise false.\n */\nbool aeron_subscription_is_closed(aeron_subscription_t *subscription);\n\n/**\n * Get the status of the media channel for this subscription.\n * <p>\n * The status will be ERRORED (-1) if a socket exception occurs on setup and ACTIVE (1) if all is well.\n *\n * @param subscription to check status of.\n * @return 1 for ACTIVE, -1 for ERRORED\n */\nint64_t aeron_subscription_channel_status(aeron_subscription_t *subscription);\n\n/**\n * Add a destination manually to a multi-destination-subscription.\n *\n * @param async object to use for polling completion.\n * @param subscription to add destination to.\n * @param uri for the destination to add.\n * @return 0 for success and -1 for error.\n */\nint aeron_subscription_async_add_destination(\n    aeron_async_destination_t **async,\n    aeron_t *client,\n    aeron_subscription_t *subscription,\n    const char *uri);\n\n/**\n * Remove a destination manually from a multi-destination-subscription.\n *\n * @param async object to use for polling completion.\n * @param subscription to remove destination from.\n * @param uri for the destination to remove.\n * @return 0 for success and -1 for error.\n */\nint aeron_subscription_async_remove_destination(\n    aeron_async_destination_t **async, aeron_t *client, aeron_subscription_t *subscription, const char *uri);\n\n/**\n * Poll the completion of add/remove of a destination to/from a subscription.\n *\n * @param async to check for completion.\n * @return 0 for not complete (try again), 1 for completed successfully, or -1 for an error.\n */\nint aeron_subscription_async_destination_poll(aeron_async_destination_t *async);\n\n/**\n * Asynchronously close the subscription. Will callback on the on_complete notification when the subscription is\n * closed. The callback is optional, use NULL for the on_complete callback if not required.\n *\n * @param subscription to close\n * @param on_close_complete optional callback to execute once the subscription has been closed and freed. This may\n * happen on a separate thread, so the caller should ensure that clientd has the appropriate lifetime.\n * @param on_close_complete_clientd parameter to pass to the on_complete callback.\n * @return 0 for success or -1 for error.\n */\nint aeron_subscription_close(\n    aeron_subscription_t *subscription, aeron_notification_t on_close_complete, void *on_close_complete_clientd);\n\n/**\n * Get all of the local socket addresses for this subscription. Multiple addresses can occur if this is a\n * multi-destination subscription. Addresses will a string representation in numeric form. IPv6 addresses will be\n * surrounded by '[' and ']' so that the ':' that separate the parts are distinguishable from the port delimiter.\n * E.g. [fe80::7552:c06e:6bf4:4160]:12345. As of writing the maximum length for a formatted address is 54 bytes\n * including the NULL terminator. AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN is defined to provide enough space to fit the\n * returned string. Returned strings will be NULL terminated. If the buffer to hold the address can not hold enough\n * of the message it will be truncated and the last character will be null.\n *\n * If the address_vec_len is less the total number of addresses available then the first addresses found up to that\n * length will be placed into the address_vec. However the function will return the total number of addresses available\n * so if if that is larger than the input array then the client code may wish to re-query with a larger array to get\n * them all.\n *\n * @param subscription to query\n * @param address_vec to hold the received addresses\n * @param address_vec_len available length of the vector to hold the addresses\n * @return number of addresses found or -1 if there is an error.\n */\nint aeron_subscription_local_sockaddrs(\n    aeron_subscription_t *subscription, aeron_iovec_t *address_vec, size_t address_vec_len);\n\n/**\n * Retrieves the first local socket address for this subscription. If this is not MDS then it will be the one\n * representing endpoint for this subscription.\n *\n * @see aeron_subscription_local_sockaddrs\n * @param subscription to query\n * @param address for the received address\n * @param address_len available length for the copied address.\n * @return -1 on error, 0 if address not found, 1 if address is found.\n */\nint aeron_subscription_resolved_endpoint(aeron_subscription_t *subscription, const char *address, size_t address_len);\n\n/**\n * Retrieves the channel URI for this subscription with any wildcard ports filled in. If the channel is not UDP or\n * does not have a wildcard port (<code>0</code>), then it will return the original URI.\n *\n * @param subscription to query\n * @param uri buffer to hold the resolved uri\n * @param uri_len length of the buffer\n * @return -1 on failure or the number of bytes written to the buffer (excluding the NULL terminator). Writing is done\n * on a per key basis, so if the buffer was truncated before writing completed, it will only include the byte count up\n * to the key that overflowed. However, the invariant that if the number returned >= uri_len, then output will have been\n * truncated.\n */\nint aeron_subscription_try_resolve_channel_endpoint_port(\n    aeron_subscription_t *subscription, char *uri, size_t uri_len);\n\n/**\n * Image Functions\n *\n * Represents a replicated publication image from a publisher to a subscription.\n * Each image identifies a source publisher by session id.\n * <p>\n * By default fragmented messages are not reassembled before delivery. If an application must\n * receive whole messages, whether or not they were fragmented, then the subscriber\n * should be created with a fragment assembler or a custom implementation.\n * <p>\n * It is an application's responsibility to poll the image for new messages.\n * <p>\n * <b>Note:</b>Images are not threadsafe and should not be shared between subscribers.\n */\n\n/**\n * Configuration for an image that does not change during it's lifetime.\n */\ntypedef struct aeron_image_constants_stct\n{\n    /**\n     * The subscription to which this image belongs.\n     */\n    aeron_subscription_t *subscription;\n\n    /**\n     * The source identity of the sending publisher as an abstract concept appropriate for the media.\n     */\n    const char *source_identity;\n\n    /**\n     * The correlationId for identification of the image with the media driver.\n     */\n    int64_t correlation_id;\n\n    /**\n     * Get the position the subscriber joined this stream at.\n     */\n    int64_t join_position;\n\n    /**\n     * Number of bits to right shift a position to get a term count for how far the stream has progressed.\n     */\n    size_t position_bits_to_shift;\n\n    /**\n     * Get the length in bytes for each term partition in the log buffer.\n     */\n    size_t term_buffer_length;\n\n    /**\n     * The length in bytes of the MTU (Maximum Transmission Unit) the Sender used for the datagram.\n     */\n    size_t mtu_length;\n\n    /**\n     * The sessionId for the steam of messages. Sessions are unique within a subscription and unique across\n     * all publications from a source identity.\n     */\n    int32_t session_id;\n\n    /**\n     * The initial term at which the stream started for this session.\n     */\n    int32_t initial_term_id;\n\n    /**\n     * Counter id that refers to the subscriber position for this image.\n     */\n    int32_t subscriber_position_id;\n}\naeron_image_constants_t;\n\n/**\n * Fill in a structure with the constants in use by a image.\n *\n * @param image to get the constants for.\n * @param constants structure to fill in with the constants\n * @return 0 for success and -1 for error.\n */\nint aeron_image_constants(aeron_image_t *image, aeron_image_constants_t *constants);\n\n/**\n * The position this image has been consumed to by the subscriber.\n *\n * @param image to query position of.\n * @return the position this image has been consumed to by the subscriber.\n */\nint64_t aeron_image_position(aeron_image_t *image);\n\n/**\n * Set the subscriber position for this image to indicate where it has been consumed to.\n *\n * @param image to set the position of.\n * @param new_position for the consumption point.\n */\nint aeron_image_set_position(aeron_image_t *image, int64_t position);\n\n/**\n * Is the current consumed position at the end of the stream?\n *\n * @param image to check.\n * @return true if at the end of the stream or false if not.\n */\nbool aeron_image_is_end_of_stream(aeron_image_t *image);\n\n/**\n * The position the stream reached when EOS was received from the publisher. The position will be\n * INT64_MAX until the stream ends and EOS is set.\n *\n * @param image to check.\n * @return position the stream reached when EOS was received from the publisher.\n */\nint64_t aeron_image_end_of_stream_position(aeron_image_t *image);\n\n/**\n * Count of observed active transports within the image liveness timeout.\n *\n * If the image is closed, then this is 0. This may also be 0 if no actual datagrams have arrived. IPC\n * Images also will be 0.\n *\n * @param image to check.\n * @return count of active transports - 0 if Image is closed, no datagrams yet, or IPC. Or -1 for error.\n */\nint aeron_image_active_transport_count(aeron_image_t *image);\n\n/**\n * Was the associated publication revoked?\n *\n * @param image to check\n * @return true if the associated publication was revoked.\n */\nbool aeron_image_is_publication_revoked(aeron_image_t *image);\n\n/**\n * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n * will be delivered to the handler up to a limited number of fragments as specified.\n * <p>\n * Use a fragment assembler to assemble messages which span multiple fragments.\n *\n * @param image to poll.\n * @param handler to which message fragments are delivered.\n * @param clientd to pass to the handler.\n * @param fragment_limit for the number of fragments to be consumed during one polling operation.\n * @return the number of fragments that have been consumed or -1 for error.\n */\nint aeron_image_poll(aeron_image_t *image, aeron_fragment_handler_t handler, void *clientd, size_t fragment_limit);\n\n/**\n * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n * will be delivered to the handler up to a limited number of fragments as specified.\n * <p>\n * Use a controlled fragment assembler to assemble messages which span multiple fragments.\n *\n * @param image to poll.\n * @param handler to which message fragments are delivered.\n * @param clientd to pass to the handler.\n * @param fragment_limit for the number of fragments to be consumed during one polling operation.\n * @return the number of fragments that have been consumed or -1 for error.\n */\nint aeron_image_controlled_poll(\n    aeron_image_t *image, aeron_controlled_fragment_handler_t handler, void *clientd, size_t fragment_limit);\n\n/**\n * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n * will be delivered to the handler up to a limited number of fragments as specified or the maximum position specified.\n * <p>\n * Use a fragment assembler to assemble messages which span multiple fragments.\n *\n * @param image to poll.\n * @param handler to which message fragments are delivered.\n * @param clientd to pass to the handler.\n * @param limit_position to consume messages up to.\n * @param fragment_limit for the number of fragments to be consumed during one polling operation.\n * @return the number of fragments that have been consumed or -1 for error.\n */\nint aeron_image_bounded_poll(\n    aeron_image_t *image,\n    aeron_fragment_handler_t handler,\n    void *clientd,\n    int64_t limit_position,\n    size_t fragment_limit);\n\n/**\n * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n * will be delivered to the handler up to a limited number of fragments as specified or the maximum position specified.\n * <p>\n * Use a controlled fragment assembler to assemble messages which span multiple fragments.\n *\n * @param image to poll.\n * @param handler to which message fragments are delivered.\n * @param clientd to pass to the handler.\n * @param limit_position to consume messages up to.\n * @param fragment_limit for the number of fragments to be consumed during one polling operation.\n * @return the number of fragments that have been consumed or -1 for error.\n */\nint aeron_image_bounded_controlled_poll(\n    aeron_image_t *image,\n    aeron_controlled_fragment_handler_t handler,\n    void *clientd,\n    int64_t limit_position,\n    size_t fragment_limit);\n\n/**\n * Peek for new messages in a stream by scanning forward from an initial position. If new messages are found then\n * they will be delivered to the handler up to a limited position.\n * <p>\n * Use a controlled fragment assembler to assemble messages which span multiple fragments. Scans must also\n * start at the beginning of a message so that the assembler is reset.\n *\n * @param image to peek.\n * @param initial_position from which to peek forward.\n * @param handler to which message fragments are delivered.\n * @param clientd to pass to the handler.\n * @param limit_position up to which can be scanned.\n * @return the resulting position after the scan terminates which is a complete message or -1 for error.\n */\nint64_t aeron_image_controlled_peek(\n    aeron_image_t *image,\n    int64_t initial_position,\n    aeron_controlled_fragment_handler_t handler,\n    void *clientd,\n    int64_t limit_position);\n\n/**\n * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n * will be delivered to the handler up to a limited number of bytes.\n * <p>\n * A scan will terminate if a padding frame is encountered. If first frame in a scan is padding then a block\n * for the padding is notified. If the padding comes after the first frame in a scan then the scan terminates\n * at the offset the padding frame begins. Padding frames are delivered singularly in a block.\n * <p>\n * Padding frames may be for a greater range than the limit offset but only the header needs to be valid so\n * relevant length of the frame is data header length.\n *\n * @param image to poll.\n * @param handler to which block is delivered.\n * @param clientd to pass to the handler.\n * @param block_length_limit up to which a block may be in length.\n * @return the number of bytes that have been consumed or -1 for error.\n */\nint aeron_image_block_poll(\n    aeron_image_t *image, aeron_block_handler_t handler, void *clientd, size_t block_length_limit);\n\nbool aeron_image_is_closed(aeron_image_t *image);\n\nint aeron_image_reject(aeron_image_t *image, const char *reason);\n\n/**\n * A fragment handler that sits in a chain-of-responsibility pattern that reassembles fragmented messages\n * so that the next handler in the chain only sees whole messages.\n * <p>\n * Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary\n * buffer for reassembly before delegation.\n * <p>\n * The aeron_header_t passed to the delegate on assembling a message will be that of the last fragment.\n * <p>\n * Session based buffers will be allocated and grown as necessary based on the length of messages to be assembled.\n */\n\n/**\n * Create an image fragment assembler for use with a single image.\n *\n * @param assembler to be set when created successfully.\n * @param delegate to call on completed.\n * @param delegate_clientd to pass to delegate handler.\n * @return 0 for success and -1 for error.\n */\nint aeron_image_fragment_assembler_create(\n    aeron_image_fragment_assembler_t **assembler, aeron_fragment_handler_t delegate, void *delegate_clientd);\n\n/**\n * Delete an image fragment assembler.\n *\n * @param assembler to delete.\n * @return 0 for success or -1 for error.\n */\nint aeron_image_fragment_assembler_delete(aeron_image_fragment_assembler_t *assembler);\n\n/**\n * Handler function to be passed for handling fragment assembly.\n *\n * @param clientd passed in the poll call (must be a aeron_image_fragment_assembler_t)\n * @param buffer containing the data.\n * @param length of the data in bytes.\n * @param header representing the meta data for the data.\n */\nvoid aeron_image_fragment_assembler_handler(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\n\n/**\n * Create an image controlled fragment assembler for use with a single image.\n *\n * @param assembler to be set when created successfully.\n * @param delegate to call on completed\n * @param delegate_clientd to pass to delegate handler.\n * @return 0 for success and -1 for error.\n */\nint aeron_image_controlled_fragment_assembler_create(\n    aeron_image_controlled_fragment_assembler_t **assembler,\n    aeron_controlled_fragment_handler_t delegate,\n    void *delegate_clientd);\n\n/**\n * Delete an image controlled fragment assembler.\n *\n * @param assembler to delete.\n * @return 0 for success or -1 for error.\n */\nint aeron_image_controlled_fragment_assembler_delete(aeron_image_controlled_fragment_assembler_t *assembler);\n\n/**\n * Handler function to be passed for handling fragment assembly.\n *\n * @param clientd passed in the poll call (must be a aeron_image_controlled_fragment_assembler_t)\n * @param buffer containing the data.\n * @param length of the data in bytes.\n * @param header representing the meta data for the data.\n * @return The action to be taken with regard to the stream position after the callback.\n */\naeron_controlled_fragment_handler_action_t aeron_image_controlled_fragment_assembler_handler(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\n\n/**\n * Create a fragment assembler for use with a subscription.\n *\n * @param assembler to be set when created successfully.\n * @param delegate to call on completed\n * @param delegate_clientd to pass to delegate handler.\n * @return 0 for success and -1 for error.\n */\nint aeron_fragment_assembler_create(\n    aeron_fragment_assembler_t **assembler, aeron_fragment_handler_t delegate, void *delegate_clientd);\n\n/**\n * Delete a fragment assembler.\n *\n * @param assembler to delete.\n * @return 0 for success or -1 for error.\n */\nint aeron_fragment_assembler_delete(aeron_fragment_assembler_t *assembler);\n\n/**\n * Handler function to be passed for handling fragment assembly.\n *\n * @param clientd passed in the poll call (must be a aeron_fragment_assembler_t)\n * @param buffer containing the data.\n * @param length of the data in bytes.\n * @param header representing the meta data for the data.\n */\nvoid aeron_fragment_assembler_handler(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\n\n/**\n * Create a controlled fragment assembler for use with a subscription.\n *\n * @param assembler to be set when created successfully.\n * @param delegate to call on completed\n * @param delegate_clientd to pass to delegate handler.\n * @return 0 for success and -1 for error.\n */\nint aeron_controlled_fragment_assembler_create(\n    aeron_controlled_fragment_assembler_t **assembler,\n    aeron_controlled_fragment_handler_t delegate,\n    void *delegate_clientd);\n\n/**\n * Delete a controlled fragment assembler.\n *\n * @param assembler to delete.\n * @return 0 for success or -1 for error.\n */\nint aeron_controlled_fragment_assembler_delete(aeron_controlled_fragment_assembler_t *assembler);\n\n/**\n * Handler function to be passed for handling fragment assembly.\n *\n * @param clientd passed in the poll call (must be a aeron_controlled_fragment_assembler_t)\n * @param buffer containing the data.\n * @param length of the data in bytes.\n * @param header representing the meta data for the data.\n * @return The action to be taken with regard to the stream position after the callback.\n */\naeron_controlled_fragment_handler_action_t aeron_controlled_fragment_assembler_handler(\n    void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\n\n/**\n * Counter functions\n */\n\n/**\n * Return a pointer to the counter value.\n *\n * @param counter to pointer to.\n * @return pointer to the counter value.\n */\nint64_t *aeron_counter_addr(aeron_counter_t *counter);\n\n/**\n * Configuration for a counter that does not change during it's lifetime.\n */\ntypedef struct aeron_counter_constants_stct\n{\n    /**\n     * Return the registration id used to register this counter with the media driver.\n     */\n    int64_t registration_id;\n\n    /**\n     * Identity for the counter within the counters reader and counters manager.\n     */\n    int32_t counter_id;\n}\naeron_counter_constants_t;\n\n/**\n * Fill in a structure with the constants in use by a counter.\n *\n * @param counter to get the constants for.\n * @param constants structure to fill in with the constants.\n * @return 0 for success and -1 for error.\n */\nint aeron_counter_constants(aeron_counter_t *counter, aeron_counter_constants_t *constants);\n\n/**\n * Asynchronously close the counter.\n *\n * @param counter to close.\n * @return 0 for success or -1 for error.\n */\nint aeron_counter_close(\n    aeron_counter_t *counter, aeron_notification_t on_close_complete, void *on_close_complete_clientd);\n\n/**\n * Check if the counter is closed\n * @param counter to check\n * @return true if closed, false otherwise.\n */\nbool aeron_counter_is_closed(aeron_counter_t *counter);\n\n/**\n * Return full version and build string.\n *\n * @return full version and build string.\n */\nconst char *aeron_version_full(void);\n\n/**\n * Return version text.\n *\n * @return version text.\n */\nconst char *aeron_version_text(void);\n\n/**\n * Return major version number.\n *\n * @return major version number.\n */\nint aeron_version_major(void);\n\n/**\n * Return minor version number.\n *\n * @return minor version number.\n */\nint aeron_version_minor(void);\n\n/**\n * Return patch version number.\n *\n * @return patch version number.\n */\nint aeron_version_patch(void);\n\n/**\n * Return the git sha for the current build.\n *\n * @return git version\n */\nconst char *aeron_version_gitsha(void);\n\n/**\n * Clock function used by aeron.\n */\ntypedef int64_t (*aeron_clock_func_t)(void);\n\n/**\n * Return time in nanoseconds for machine. Is not wall clock time.\n *\n * @return nanoseconds since epoch for machine.\n */\nint64_t aeron_nano_clock(void);\n\n/**\n * Return time in milliseconds since epoch. Is wall clock time.\n *\n * @return milliseconds since epoch.\n */\nint64_t aeron_epoch_clock(void);\n\n/**\n * Function to return logging information.\n */\ntypedef void (*aeron_log_func_t)(const char *);\n\n/**\n * Determine if an aeron driver is using a given aeron directory.\n *\n * @param dirname  for aeron directory\n * @param timeout_ms  to use to determine activity for aeron directory\n * @param log_func to call during activity check to log diagnostic information.\n * @return true for active driver or false for no active driver.\n */\nbool aeron_is_driver_active(const char *dirname, int64_t timeout_ms, aeron_log_func_t log_func);\n\n/**\n * Load properties from a string containing name=value pairs and set appropriate environment variables for the\n * process so that subsequent calls to aeron_driver_context_init will use those values.\n *\n * @param buffer containing properties and values.\n * @return 0 for success and -1 for error.\n */\nint aeron_properties_buffer_load(const char *buffer);\n\n/**\n * Load properties file and set appropriate environment variables for the process so that subsequent\n * calls to aeron_driver_context_init will use those values.\n *\n * @param filename to load.\n * @return 0 for success and -1 for error.\n */\nint aeron_properties_file_load(const char *filename);\n\n/**\n * Load properties from HTTP URL and set environment variables for the process so that subsequent\n * calls to aeron_driver_context_init will use those values.\n *\n * @param url to attempt to retrieve and load.\n * @return 0 for success and -1 for error.\n */\nint aeron_properties_http_load(const char *url);\n\n/**\n * Load properties based on URL or filename. If string contains file or http URL, it will attempt\n * to load properties from a file or http as indicated. If not a URL, then it will try to load the string\n * as a filename.\n *\n * @param url_or_filename to load properties from.\n * @return 0 for success and -1 for error.\n */\nint aeron_properties_load(const char *url_or_filename);\n\n/**\n * Return current aeron error code (errno) for calling thread.\n *\n * @return aeron error code for calling thread.\n */\nint aeron_errcode(void);\n\n/**\n * Return the current aeron error message for calling thread.\n *\n * @return aeron error message for calling thread.\n */\nconst char *aeron_errmsg(void);\n\n/**\n * Get the default path used by the Aeron media driver.\n *\n * @param path buffer to store the path.\n * @param path_length space available in the buffer\n * @return -1 if there is an issue or the number of bytes written to path excluding the terminator <code>\\0</code>. If this\n * is equal to or greater than the path_length then the path has been truncated.\n */\nint aeron_default_path(char *path, size_t path_length);\n\n/**\n * Gets the registration id for addition of the counter. Note that using this after a call to poll the succeeds or\n * errors is undefined behaviour. As the async_add_counter_t may have been freed.\n *\n * @param add_counter used to check for completion.\n * @return registration id for the counter.\n */\nint64_t aeron_async_add_counter_get_registration_id(aeron_async_add_counter_t *add_counter);\n\n/**\n * Gets the registration id for addition of the publication. Note that using this after a call to poll the succeeds or\n * errors is undefined behaviour. As the async_add_publication_t may have been freed.\n *\n * @param add_publication used to check for completion.\n * @return registration id for the publication.\n */\nint64_t aeron_async_add_publication_get_registration_id(aeron_async_add_publication_t *add_publication);\n\n/**\n * Gets the registration id for addition of the exclusive_publication. Note that using this after a call to poll the\n * succeeds or errors is undefined behaviour. As the async_add_exclusive_publication_t may have been freed.\n *\n * @param add_exclusive_publication used to check for completion.\n * @return registration id for the exclusive_publication.\n * @deprecated Use aeron_async_add_exclusive_publication_get_registration_id instead.\n */\nint64_t aeron_async_add_exclusive_exclusive_publication_get_registration_id(\n    aeron_async_add_exclusive_publication_t *add_exclusive_publication);\n\n/**\n * Gets the registration id for addition of the exclusive_publication. Note that using this after a call to poll the\n * succeeds or errors is undefined behaviour. As the async_add_exclusive_publication_t may have been freed.\n *\n * @param add_exclusive_publication used to check for completion.\n * @return registration id for the exclusive_publication.\n */\nint64_t aeron_async_add_exclusive_publication_get_registration_id(\n    aeron_async_add_exclusive_publication_t *add_exclusive_publication);\n\n/**\n * Gets the registration id for addition of the subscription. Note that using this after a call to poll the succeeds or\n * errors is undefined behaviour. As the async_add_subscription_t may have been freed.\n *\n * @param add_subscription used to check for completion.\n * @return registration id for the subscription.\n */\nint64_t aeron_async_add_subscription_get_registration_id(aeron_async_add_subscription_t *add_subscription);\n\n/**\n * Gets the registration_id for the destination command supplied. Note that this is the correlation_id used for\n * the specified destination command, not the registration_id for the original parent resource (publication,\n * subscription).\n *\n * @param async_destination tracking the current destination command.\n * @return correlation_id sent to driver.\n */\nint64_t aeron_async_destination_get_registration_id(aeron_async_destination_t *async_destination);\n\n/**\n * Request the media driver terminates operation and closes all resources.\n *\n * @param directory    in which the media driver is running.\n * @param token_buffer containing the authentication token confirming the client is allowed to terminate the driver.\n * @param token_length of the token in the buffer.\n * @return\n */\nint aeron_context_request_driver_termination(const char *directory, const uint8_t *token_buffer, size_t token_length);\n\ntypedef struct aeron_cnc_stct aeron_cnc_t;\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_cnc_constants_stct\n{\n    int32_t cnc_version;\n    int32_t to_driver_buffer_length;\n    int32_t to_clients_buffer_length;\n    int32_t counter_metadata_buffer_length;\n    int32_t counter_values_buffer_length;\n    int32_t error_log_buffer_length;\n    int64_t client_liveness_timeout;\n    int64_t start_timestamp;\n    int64_t pid;\n    int32_t file_page_size;\n}\naeron_cnc_constants_t;\n#pragma pack(pop)\n\n/**\n * Initialise an aeron_cnc, which gives user level access to the command and control file used to communicate\n * with the media driver. Will wait until the media driver has loaded and the cnc file is created, up to timeout_ms.\n * Use a value of 0 for a non-blocking initialisation.\n *\n * @param aeron_cnc to hold the loaded aeron_cnc\n * @param base_path media driver's base path\n * @param timeout_ms Number of milliseconds to wait before timing out.\n * @return 0 on success, -1 on failure.\n */\nint aeron_cnc_init(aeron_cnc_t **aeron_cnc, const char *base_path, int64_t timeout_ms);\n\n/**\n * Fetch the sets of constant values associated with this command and control file.\n *\n * @param aeron_cnc to query\n * @param constants user supplied structure to hold return values.\n * @return 0 on success, -1 on failure.\n */\nint aeron_cnc_constants(aeron_cnc_t *aeron_cnc, aeron_cnc_constants_t *constants);\n\n/**\n * Get the current file name of the cnc file.\n *\n * @param aeron_cnc to query\n * @return name of the cnc file\n */\nconst char *aeron_cnc_filename(aeron_cnc_t *aeron_cnc);\n\n/**\n * Gets the timestamp of the last heartbeat sent to the media driver from any client.\n *\n * @param aeron_cnc to query\n * @return last heartbeat timestamp in ms.\n */\nint64_t aeron_cnc_to_driver_heartbeat(aeron_cnc_t *aeron_cnc);\n\ntypedef void (*aeron_error_log_reader_func_t)(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd);\n\n/**\n * Reads the current error log for this driver.\n *\n * @param aeron_cnc to query\n * @param callback called for every distinct error observation\n * @param clientd client data to be passed to the callback\n * @param since_timestamp only return errors after this timestamp (0 returns all)\n * @return the number of distinct errors seen\n */\nsize_t aeron_cnc_error_log_read(\n    aeron_cnc_t *aeron_cnc,\n    aeron_error_log_reader_func_t callback,\n    void *clientd,\n    int64_t since_timestamp);\n\n/**\n * Gets a counters reader for this command and control file. This does not need to be closed manually, resources\n * are tied to the instance of aeron_cnc.\n *\n * @param aeron_cnc to query\n * @return pointer to a counters reader.\n */\naeron_counters_reader_t *aeron_cnc_counters_reader(aeron_cnc_t *aeron_cnc);\n\ntypedef void (*aeron_loss_reporter_read_entry_func_t)(\n    void *clientd,\n    int64_t observation_count,\n    int64_t total_bytes_lost,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    int32_t session_id,\n    int32_t stream_id,\n    const char *channel,\n    int32_t channel_length,\n    const char *source,\n    int32_t source_length);\n\n/**\n * Read all of the data loss observations from the report in the same media driver instances as the cnc file.\n *\n * @param aeron_cnc to query\n * @param entry_func callback for each observation found\n * @param clientd client data to be passed to the callback.\n * @return -1 on failure, number of observations on success (could be 0).\n */\nint aeron_cnc_loss_reporter_read(\n    aeron_cnc_t *aeron_cnc, aeron_loss_reporter_read_entry_func_t entry_func, void *clientd);\n\n/**\n * Closes the instance of the aeron cnc and frees its resources.\n *\n * @param aeron_cnc to close\n */\nvoid aeron_cnc_close(aeron_cnc_t *aeron_cnc);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif //AERON_C_H\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_array_to_ptr_hash_map.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"collections/aeron_array_to_ptr_hash_map.h\"\n\nextern uint64_t aeron_array_hash(const uint8_t *arr, size_t length);\n\nextern size_t aeron_array_to_ptr_hash_map_hash_key(uint64_t key, size_t mask);\n\nextern bool aeron_array_to_ptr_hash_map_compare(\n    aeron_array_to_ptr_hash_map_key_t *key, const uint8_t *key_arr, size_t key_arr_len, uint64_t key_hash_code);\n\nextern int aeron_array_to_ptr_hash_map_init(\n    aeron_array_to_ptr_hash_map_t *map, size_t initial_capacity, float load_factor);\n\nextern void aeron_array_to_ptr_hash_map_delete(aeron_array_to_ptr_hash_map_t *map);\n\nextern int aeron_array_to_ptr_hash_map_rehash(aeron_array_to_ptr_hash_map_t *map, size_t new_capacity);\n\nextern int aeron_array_to_ptr_hash_map_put(\n    aeron_array_to_ptr_hash_map_t *map, const uint8_t *key, size_t key_len, void *value);\n\nextern void *aeron_array_to_ptr_hash_map_get(aeron_array_to_ptr_hash_map_t *map, const uint8_t *key, size_t key_len);\n\nextern void aeron_array_to_ptr_hash_map_compact_chain(aeron_array_to_ptr_hash_map_t *map, size_t delete_index);\n\nextern void *aeron_array_to_ptr_hash_map_remove(aeron_array_to_ptr_hash_map_t *map, const uint8_t *key, size_t key_len);\n\nextern void aeron_array_to_ptr_hash_map_for_each(\n    aeron_array_to_ptr_hash_map_t *map, aeron_array_to_ptr_hash_map_for_each_func_t func, void *clientd);\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_array_to_ptr_hash_map.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARRAY_TO_PTR_HASH_MAP_H\n#define AERON_ARRAY_TO_PTR_HASH_MAP_H\n\n#include <string.h>\n#include <errno.h>\n\n#include \"util/aeron_platform.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_error.h\"\n#include \"collections/aeron_hashing.h\"\n#include \"collections/aeron_map.h\"\n#include \"aeron_alloc.h\"\n\ntypedef struct aeron_array_to_ptr_hash_map_key_stct\n{\n    const uint8_t *arr;\n    uint64_t hash_code;\n    size_t arr_length;\n}\naeron_array_to_ptr_hash_map_key_t;\n\ntypedef struct aeron_array_to_ptr_hash_map_stct\n{\n    aeron_array_to_ptr_hash_map_key_t *keys;\n    void **values;\n    float load_factor;\n    size_t capacity;\n    size_t size;\n    size_t resize_threshold;\n}\naeron_array_to_ptr_hash_map_t;\n\ninline uint64_t aeron_array_hash(const uint8_t *arr, size_t length)\n{\n    /*\n     * should be good enough to avoid most clumps.\n     */\n    return aeron_fnv_64a_buf((uint8_t *)arr, length);\n}\n\ninline size_t aeron_array_to_ptr_hash_map_hash_key(uint64_t key_hash_code, size_t mask)\n{\n    uint64_t hash = key_hash_code;\n\n    if (mask <= UINT32_MAX)\n    {\n        hash = (uint32_t)hash ^ (uint32_t)(hash >> 32u);\n    }\n\n    return (size_t)(hash & mask);\n}\n\ninline bool aeron_array_to_ptr_hash_map_compare(\n    aeron_array_to_ptr_hash_map_key_t *key, const uint8_t *key_arr, size_t key_arr_len, uint64_t key_hash_code)\n{\n    /*\n     * memcmp is not ideal for this, but hashcode should keep them from being called too much\n     */\n    return (key->hash_code == key_hash_code &&\n        key->arr_length == key_arr_len &&\n        memcmp(key->arr, key_arr, key_arr_len) == 0);\n}\n\ninline int aeron_array_to_ptr_hash_map_init(\n    aeron_array_to_ptr_hash_map_t *map, size_t initial_capacity, float load_factor)\n{\n    size_t capacity = (size_t)aeron_find_next_power_of_two((int32_t)initial_capacity);\n\n    map->load_factor = load_factor;\n    map->resize_threshold = (size_t)(load_factor * (float)capacity);\n    map->keys = NULL;\n    map->values = NULL;\n    map->capacity = capacity;\n    map->size = 0;\n\n    if (aeron_alloc((void **)&map->keys, (capacity * sizeof(aeron_array_to_ptr_hash_map_key_t))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate keys\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&map->values, (capacity * sizeof(void *))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate values\");\n        return -1;\n    }\n\n    return 0;\n}\n\ninline void aeron_array_to_ptr_hash_map_delete(aeron_array_to_ptr_hash_map_t *map)\n{\n    if (NULL != map->keys)\n    {\n        aeron_free(map->keys);\n    }\n\n    if (NULL != map->values)\n    {\n        aeron_free(map->values);\n    }\n}\n\ninline int aeron_array_to_ptr_hash_map_rehash(aeron_array_to_ptr_hash_map_t *map, size_t new_capacity)\n{\n    size_t mask = new_capacity - 1;\n    map->resize_threshold = (size_t)((float)new_capacity * map->load_factor);\n\n    aeron_array_to_ptr_hash_map_key_t *tmp_keys;\n    void **tmp_values;\n\n    if (aeron_alloc((void **)&tmp_keys, (new_capacity * sizeof(aeron_array_to_ptr_hash_map_key_t))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate new keys\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&tmp_values, (new_capacity * sizeof(void *))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate new values\");\n        return -1;\n    }\n\n    for (size_t i = 0, size = map->capacity; i < size; i++)\n    {\n        void *value = map->values[i];\n\n        if (NULL != value)\n        {\n            aeron_array_to_ptr_hash_map_key_t *key = &map->keys[i];\n            size_t new_hash = aeron_array_to_ptr_hash_map_hash_key(key->hash_code, mask);\n\n            while (NULL != tmp_values[new_hash])\n            {\n                new_hash = (new_hash + 1) & mask;\n            }\n\n            tmp_keys[new_hash].arr = key->arr;\n            tmp_keys[new_hash].arr_length = key->arr_length;\n            tmp_keys[new_hash].hash_code = key->hash_code;\n            tmp_values[new_hash] = value;\n        }\n    }\n\n    aeron_free(map->keys);\n    aeron_free(map->values);\n\n    map->keys = tmp_keys;\n    map->values = tmp_values;\n    map->capacity = new_capacity;\n\n    return 0;\n}\n\ninline int aeron_array_to_ptr_hash_map_put(\n    aeron_array_to_ptr_hash_map_t *map, const uint8_t *key, size_t key_len, void *value)\n{\n    if (NULL == value)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"value is null\");\n        return -1;\n    }\n\n    uint64_t hash_code = aeron_array_hash((uint8_t *)key, key_len);\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_array_to_ptr_hash_map_hash_key(hash_code, mask);\n\n    void *old_value = NULL;\n    while (NULL != map->values[index])\n    {\n        if (aeron_array_to_ptr_hash_map_compare(&map->keys[index], key, key_len, hash_code))\n        {\n            old_value = map->values[index];\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    if (NULL == old_value)\n    {\n        ++map->size;\n        map->keys[index].hash_code = hash_code;\n        map->keys[index].arr_length = key_len;\n    }\n\n    map->keys[index].arr = key;\n    map->values[index] = value;\n\n    if (map->size > map->resize_threshold)\n    {\n        size_t new_capacity = map->capacity << 1;\n\n        if (aeron_array_to_ptr_hash_map_rehash(map, new_capacity) < 0)\n        {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\ninline void *aeron_array_to_ptr_hash_map_get(aeron_array_to_ptr_hash_map_t *map, const uint8_t *key, size_t key_len)\n{\n    uint64_t hash_code = aeron_array_hash((uint8_t *)key, key_len);\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_array_to_ptr_hash_map_hash_key(hash_code, mask);\n\n    void *value;\n    while (NULL != (value = map->values[index]))\n    {\n        if (aeron_array_to_ptr_hash_map_compare(&map->keys[index], key, key_len, hash_code))\n        {\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    return value;\n}\n\ninline void aeron_array_to_ptr_hash_map_compact_chain(aeron_array_to_ptr_hash_map_t *map, size_t delete_index)\n{\n    size_t mask = map->capacity - 1;\n    size_t index = delete_index;\n\n    while (true)\n    {\n        index = (index + 1) & mask;\n        if (NULL == map->values[index])\n        {\n            break;\n        }\n\n        size_t hash = aeron_array_to_ptr_hash_map_hash_key(map->keys[index].hash_code, mask);\n\n        if ((index < hash && (hash <= delete_index || delete_index <= index)) ||\n            (hash <= delete_index && delete_index <= index))\n        {\n            memcpy(&map->keys[delete_index], &map->keys[index], sizeof(aeron_array_to_ptr_hash_map_key_t));\n            map->values[delete_index] = map->values[index];\n\n            map->values[index] = NULL;\n            delete_index = index;\n        }\n    }\n}\n\ninline void *aeron_array_to_ptr_hash_map_remove(aeron_array_to_ptr_hash_map_t *map, const uint8_t *key, size_t key_len)\n{\n    uint64_t hash_code = aeron_array_hash((uint8_t *)key, key_len);\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_array_to_ptr_hash_map_hash_key(hash_code, mask);\n\n    void *value;\n    while (NULL != (value = map->values[index]))\n    {\n        if (aeron_array_to_ptr_hash_map_compare(&map->keys[index], key, key_len, hash_code))\n        {\n            map->values[index] = NULL;\n            --map->size;\n\n            aeron_array_to_ptr_hash_map_compact_chain(map, index);\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    return value;\n}\n\ntypedef void (*aeron_array_to_ptr_hash_map_for_each_func_t)(void *clientd, const uint8_t *key, size_t key_len, void *value);\n\ninline void aeron_array_to_ptr_hash_map_for_each(\n    aeron_array_to_ptr_hash_map_t *map, aeron_array_to_ptr_hash_map_for_each_func_t func, void *clientd)\n{\n    for (size_t i = 0; i < map->capacity; i++)\n    {\n        if (map->values[i] != NULL)\n        {\n            func(clientd, map->keys[i].arr, map->keys[i].arr_length, map->values[i]);\n        }\n    }\n}\n\n#endif //AERON_ARRAY_TO_PTR_HASH_MAP_H\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_bit_set.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"collections/aeron_bit_set.h\"\n\nextern int aeron_bit_set_stack_alloc(\n    size_t bit_set_length, uint64_t *static_array, size_t static_array_len, aeron_bit_set_t *bit_set);\n\nextern int aeron_bit_set_stack_init(\n    size_t bit_set_length,\n    uint64_t *static_array,\n    size_t static_array_len,\n    bool initial_value,\n    aeron_bit_set_t *bit_set);\n\nextern void aeron_bit_set_heap_free(aeron_bit_set_t *bit_set);\n\nextern void aeron_bit_set_stack_free(aeron_bit_set_t *bit_set);\n\nextern int aeron_bit_set_init(aeron_bit_set_t *bit_set, bool initial_value);\n\nextern int aeron_bit_set_get(aeron_bit_set_t *bit_set, size_t bit_index, bool *value);\n\nextern int aeron_bit_set_set(aeron_bit_set_t *bit_set, size_t bit_index, bool value);\n\nextern int aeron_bit_set_find_first(aeron_bit_set_t *bit_set, bool value, size_t *bit_index);\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_bit_set.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_BIT_SET_H\n#define AERON_AERON_BIT_SET_H\n\n#include <stdbool.h>\n#include <string.h>\n#include <errno.h>\n\n#include \"util/aeron_platform.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_alloc.h\"\n\nstruct aeron_bit_set_stct\n{\n    size_t bit_set_length;\n    uint64_t *bits;\n    uint64_t *static_array;\n};\n\ntypedef struct aeron_bit_set_stct aeron_bit_set_t;\n\ninline int aeron_bit_set_init(aeron_bit_set_t *bit_set, bool initial_value)\n{\n    const size_t memory_size = ((bit_set->bit_set_length + 63) / 64) * sizeof(uint64_t);\n    int c = initial_value ? 0xFF : 0;\n    memset(bit_set->bits, c, memory_size);\n\n    return 0;\n}\n\ninline int aeron_bit_set_stack_alloc(\n    size_t bit_set_length, uint64_t *static_array, size_t static_array_len, aeron_bit_set_t *bit_set)\n{\n    bit_set->bit_set_length = bit_set_length;\n    bit_set->static_array = static_array;\n\n    const size_t u64_len = ((bit_set_length + 63) / 64);\n    if (NULL != static_array && u64_len <= static_array_len)\n    {\n        bit_set->bits = static_array;\n    }\n    else\n    {\n        if (aeron_alloc((void **)&bit_set->bits, sizeof(uint64_t) * u64_len) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Unable to allocate overflow bit set\");\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\ninline int aeron_bit_set_heap_alloc(size_t bit_set_length, aeron_bit_set_t **bit_set)\n{\n    if (NULL == bit_set)\n    {\n        return -EINVAL;\n    }\n\n    if (aeron_alloc((void **)bit_set, sizeof(aeron_bit_set_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate bit set\");\n        return -1;\n    }\n\n    return aeron_bit_set_stack_alloc(bit_set_length, NULL, 0, *bit_set);\n}\n\ninline int aeron_bit_set_stack_init(\n    size_t bit_set_length,\n    uint64_t *static_array,\n    size_t static_array_len,\n    bool initial_value,\n    aeron_bit_set_t *bit_set)\n{\n    int result;\n    if (0 != (result = aeron_bit_set_stack_alloc(bit_set_length, static_array, static_array_len, bit_set)))\n    {\n        return result;\n    }\n\n    return aeron_bit_set_init(bit_set, initial_value);\n}\n\ninline int aeron_bit_set_heap_init(size_t bit_set_length, bool initial_value, aeron_bit_set_t **bit_set)\n{\n    int result;\n    if (0 != (result = aeron_bit_set_heap_alloc(bit_set_length, bit_set)))\n    {\n        return result;\n    }\n\n    return aeron_bit_set_init(*bit_set, initial_value);\n}\n\ninline void aeron_bit_set_stack_free(aeron_bit_set_t *bit_set)\n{\n    if (bit_set->static_array != bit_set->bits)\n    {\n        aeron_free(bit_set->bits);\n    }\n\n    bit_set->bits = NULL;\n    bit_set->static_array = NULL;\n}\n\ninline void aeron_bit_set_heap_free(aeron_bit_set_t *bit_set)\n{\n    aeron_bit_set_stack_free(bit_set);\n    aeron_free(bit_set);\n}\n\ninline int aeron_bit_set_get(aeron_bit_set_t *bit_set, size_t bit_index, bool *value)\n{\n    if (NULL == bit_set || bit_set->bit_set_length <= bit_index || value == NULL)\n    {\n        return -EINVAL;\n    }\n\n    const size_t entry = bit_index / 64;\n    const size_t offset = bit_index % 64;\n\n    *value = (0 != (bit_set->bits[entry] & (UINT64_C(1) << offset)));\n\n    return 0;\n}\n\ninline int aeron_bit_set_set(aeron_bit_set_t *bit_set, size_t bit_index, bool value)\n{\n    if (NULL == bit_set || bit_set->bit_set_length <= bit_index)\n    {\n        return -EINVAL;\n    }\n\n    const size_t entry = bit_index / 64;\n    const size_t offset = bit_index % 64;\n\n    uint64_t mask = UINT64_C(1) << offset;\n\n    if (value)\n    {\n        bit_set->bits[entry] |= mask;\n    }\n    else\n    {\n        bit_set->bits[entry] &= ~mask;\n    }\n\n    return 0;\n}\n\ninline int aeron_bit_set_find_first(aeron_bit_set_t *bit_set, bool value, size_t *bit_index)\n{\n    const uint64_t entry_empty_value = value ? 0 : UINT64_C(0xFFFFFFFFFFFFFFFF);\n    size_t num_entries = (bit_set->bit_set_length + 63) / 64;\n\n    for (size_t i = 0; i < num_entries; i++)\n    {\n        if (entry_empty_value != bit_set->bits[i])\n        {\n            uint64_t bits_to_search = value ? bit_set->bits[i] : ~bit_set->bits[i];\n            *bit_index = (i * 64 + (size_t)aeron_number_of_trailing_zeroes_u64(bits_to_search));\n\n            return *bit_index < bit_set->bit_set_length ? 0 : -1;\n        }\n    }\n\n    return -1;\n}\n\n#endif //AERON_AERON_BIT_SET_H\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_hashing.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"collections/aeron_hashing.h\"\n\nextern uint64_t aeron_hash_code(uint64_t value);\n\nextern size_t aeron_hash(uint64_t value, size_t mask);\n\nextern size_t aeron_even_hash(uint64_t value, size_t mask);\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_hashing.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_HASHING_H\n#define AERON_HASHING_H\n\n#include <stddef.h>\n#include <stdint.h>\n\ninline uint64_t aeron_hash_code(uint64_t value)\n{\n    uint64_t x = value;\n\n    x = (x ^ (x >> 30u)) * UINT64_C(0xbf58476d1ce4e5b9);\n    x = (x ^ (x >> 27u)) * UINT64_C(0x94d049bb133111eb);\n    x = x ^ (x >> 31u);\n\n    return x;\n}\n\ninline size_t aeron_hash(uint64_t value, size_t mask)\n{\n    uint64_t hash = aeron_hash_code(value);\n\n    if (mask <= UINT32_MAX)\n    {\n        hash = (uint32_t)hash ^ (uint32_t)(hash >> 32u);\n    }\n\n    return (size_t)(hash & mask);\n}\n\ninline size_t aeron_even_hash(uint64_t value, size_t mask)\n{\n    uint64_t hash = aeron_hash_code(value);\n    uint32_t folded_hash = (uint32_t)hash ^ (uint32_t)(hash >> 32u);\n    uint32_t even_hash = (folded_hash << 1u) - (folded_hash << 8u);\n\n    return (size_t)(even_hash & mask);\n}\n\n#endif //AERON_HASHING_H\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_int64_counter_map.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"collections/aeron_int64_counter_map.h\"\n\nextern size_t aeron_int64_counter_map_hash_key(int64_t key, size_t mask);\n\nextern int aeron_int64_counter_map_init(\n    aeron_int64_counter_map_t *map, int64_t initial_value, size_t initial_capacity, float load_factor);\n\nextern void aeron_int64_counter_map_delete(aeron_int64_counter_map_t *map);\n\nextern int aeron_int64_counter_map_rehash(aeron_int64_counter_map_t *map, size_t new_entries_length);\n\nextern int aeron_int64_counter_map_put(\n    aeron_int64_counter_map_t *map, const int64_t key, const int64_t value, int64_t *existing_value);\n\nextern int64_t aeron_int64_counter_map_get(aeron_int64_counter_map_t *map, const int64_t key);\n\nextern void aeron_int64_counter_map_compact_chain(aeron_int64_counter_map_t *map, size_t delete_index);\n\nextern int64_t aeron_int64_counter_map_remove(aeron_int64_counter_map_t *map, int64_t key);\n\nextern int aeron_int64_counter_map_add_and_get(\n    aeron_int64_counter_map_t *map, const int64_t key, int64_t delta, int64_t *value);\n\nextern int aeron_int64_counter_map_get_and_add(\n    aeron_int64_counter_map_t *map, const int64_t key, const int64_t delta, int64_t *value);\n\nextern int aeron_int64_counter_map_inc_and_get(aeron_int64_counter_map_t *map, const int64_t key, int64_t *value);\n\nextern int aeron_int64_counter_map_dec_and_get(aeron_int64_counter_map_t *map, const int64_t key, int64_t *value);\n\nextern int aeron_int64_counter_map_get_and_inc(aeron_int64_counter_map_t *map, const int64_t key, int64_t *value);\n\nextern int aeron_int64_counter_map_get_and_dec(aeron_int64_counter_map_t *map, const int64_t key, int64_t *value);\n\nextern void aeron_int64_counter_map_for_each(\n    aeron_int64_counter_map_t *map, aeron_int64_counter_map_for_each_func_t func, void *clientd);\n\nextern void aeron_int64_counter_map_remove_if(\n    aeron_int64_counter_map_t *map, aeron_int64_counter_map_predicate_func_t func, void *clientd);\n\n\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_int64_counter_map.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_INT64_COUNTER_MAP_H\n#define AERON_INT64_COUNTER_MAP_H\n\n#include <stdbool.h>\n#include <errno.h>\n\n#include \"util/aeron_error.h\"\n#include \"util/aeron_platform.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"collections/aeron_hashing.h\"\n#include \"collections/aeron_map.h\"\n#include \"aeron_alloc.h\"\n\ntypedef struct aeron_int64_counter_map_stct\n{\n    int64_t *entries;\n    float load_factor;\n    size_t entries_length;\n    size_t size;\n    size_t resize_threshold;\n    int64_t initial_value;\n}\naeron_int64_counter_map_t;\n\ninline size_t aeron_int64_counter_map_hash_key(int64_t key, size_t mask)\n{\n    return aeron_even_hash((uint64_t)key, mask);\n}\n\ninline int aeron_int64_counter_map_init(\n    aeron_int64_counter_map_t *map, int64_t initial_value, size_t initial_capacity, float load_factor)\n{\n    size_t capacity = (size_t)aeron_find_next_power_of_two((int32_t)initial_capacity);\n\n    map->load_factor = load_factor;\n    map->resize_threshold = (size_t)(load_factor * (float)capacity);\n    map->entries = NULL;\n    map->entries_length = 2 * capacity;\n    map->initial_value = initial_value;\n    map->size = 0;\n\n    if (aeron_alloc((void **)&map->entries, (map->entries_length * sizeof(int64_t))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n    for (size_t i = 0, size = map->entries_length; i < size; i++)\n    {\n        map->entries[i] = map->initial_value;\n    }\n\n    return 0;\n}\n\ninline void aeron_int64_counter_map_delete(aeron_int64_counter_map_t *map)\n{\n    if (NULL != map->entries)\n    {\n        aeron_free(map->entries);\n    }\n}\n\ninline int aeron_int64_counter_map_rehash(aeron_int64_counter_map_t *map, size_t new_entries_length)\n{\n    size_t mask = new_entries_length - 1;\n\n    int64_t *tmp_entries;\n\n    if (aeron_alloc((void **)&tmp_entries, (new_entries_length * sizeof(int64_t))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n    for (size_t i = 0, size = new_entries_length; i < size; i++)\n    {\n        tmp_entries[i] = map->initial_value;\n    }\n\n    for (size_t i = 0, size = map->entries_length; i < size; i += 2)\n    {\n        int64_t value = map->entries[i + 1];\n\n        if (map->initial_value != value)\n        {\n            int64_t key = map->entries[i];\n            size_t new_hash = aeron_int64_counter_map_hash_key(key, mask);\n\n            while (map->initial_value != tmp_entries[new_hash])\n            {\n                new_hash = (new_hash + 2) & mask;\n            }\n\n            tmp_entries[new_hash] = key;\n            tmp_entries[new_hash + 1] = value;\n        }\n    }\n\n    aeron_free(map->entries);\n\n    map->entries = tmp_entries;\n    map->entries_length = new_entries_length;\n    map->resize_threshold = (size_t)(((float)new_entries_length / 2) * map->load_factor);\n\n    return 0;\n}\n\ninline void aeron_int64_counter_map_compact_chain(aeron_int64_counter_map_t *map, size_t delete_index)\n{\n    size_t mask = map->entries_length - 1;\n    size_t index = delete_index;\n\n    while (true)\n    {\n        index = (index + 2) & mask;\n        if (map->initial_value == map->entries[index + 1])\n        {\n            break;\n        }\n\n        size_t hash = aeron_int64_counter_map_hash_key(map->entries[index], mask);\n\n        if ((index < hash && (hash <= delete_index || delete_index <= index)) ||\n            (hash <= delete_index && delete_index <= index))\n        {\n            map->entries[delete_index] = map->entries[index];\n            map->entries[delete_index + 1] = map->entries[index + 1];\n            map->entries[index + 1] = map->initial_value;\n\n            delete_index = index;\n        }\n    }\n}\n\ninline int64_t aeron_int64_counter_map_remove(aeron_int64_counter_map_t *map, int64_t key)\n{\n    size_t mask = map->entries_length - 1;\n    size_t index = aeron_int64_counter_map_hash_key(key, mask);\n\n    int64_t value;\n    while (map->initial_value != (value = map->entries[index + 1]))\n    {\n        if (key == map->entries[index])\n        {\n            map->entries[index + 1] = map->initial_value;\n            --map->size;\n\n            aeron_int64_counter_map_compact_chain(map, index);\n            break;\n        }\n\n        index = (index + 2) & mask;\n    }\n\n    return value;\n}\n\ninline int aeron_int64_counter_map_put(\n    aeron_int64_counter_map_t *map, const int64_t key, const int64_t value, int64_t *existing_value)\n{\n    if (map->initial_value == value)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"cannot accept initialValue\");\n        return -1;\n    }\n\n    size_t mask = map->entries_length - 1;\n    size_t index = aeron_int64_counter_map_hash_key(key, mask);\n\n    int64_t old_value;\n    while (map->initial_value != (old_value = map->entries[index + 1]))\n    {\n        if (key == map->entries[index])\n        {\n            break;\n        }\n\n        index = (index + 2) & mask;\n    }\n\n    if (old_value == map->initial_value)\n    {\n        map->size++;\n        map->entries[index] = key;\n    }\n\n    map->entries[index + 1] = value;\n\n    if (map->size > map->resize_threshold)\n    {\n        size_t new_entries_length = map->entries_length << 1;\n\n        if (aeron_int64_counter_map_rehash(map, new_entries_length) < 0)\n        {\n            return -1;\n        }\n    }\n\n    if (NULL != existing_value)\n    {\n        *existing_value = old_value;\n    }\n\n    return 0;\n}\n\ninline int64_t aeron_int64_counter_map_get(aeron_int64_counter_map_t *map, const int64_t key)\n{\n    size_t mask = map->entries_length - 1;\n    size_t index = aeron_int64_counter_map_hash_key(key, mask);\n\n    int64_t value;\n    while (map->initial_value != (value = map->entries[index + 1]))\n    {\n        if (key == map->entries[index])\n        {\n            break;\n        }\n\n        index = (index + 2) & mask;\n    }\n\n    return value;\n}\n\ninline int aeron_int64_counter_map_get_and_add(\n    aeron_int64_counter_map_t *map, const int64_t key, const int64_t delta, int64_t *value)\n{\n    size_t mask = map->entries_length - 1;\n    size_t index = aeron_int64_counter_map_hash_key(key, mask);\n\n    int64_t old_value;\n    while (map->initial_value != (old_value = map->entries[index + 1]))\n    {\n        if (key == map->entries[index])\n        {\n            break;\n        }\n\n        index = (index + 2) & mask;\n    }\n\n    if (delta != 0)\n    {\n        int64_t new_value = old_value + delta;\n        map->entries[index + 1] = new_value;\n\n        if (map->initial_value == old_value)\n        {\n            map->size++;\n            map->entries[index] = key;\n\n            if (map->size > map->resize_threshold)\n            {\n                size_t new_entries_length = map->entries_length << 1;\n\n                if (aeron_int64_counter_map_rehash(map, new_entries_length) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"\");\n                    return -1;\n                }\n            }\n        }\n        else if (map->initial_value == new_value)\n        {\n            map->size--;\n            aeron_int64_counter_map_compact_chain(map, index);\n        }\n    }\n\n    if (NULL != value)\n    {\n        *value = old_value;\n    }\n\n    return 0;\n}\n\ninline int aeron_int64_counter_map_add_and_get(\n    aeron_int64_counter_map_t *map, const int64_t key, int64_t delta, int64_t *value)\n{\n    int64_t existing_value = 0;\n    int result = aeron_int64_counter_map_get_and_add(map, key, delta, &existing_value);\n    if (NULL != value)\n    {\n        *value = (existing_value + delta);\n    }\n    return result;\n}\n\ninline int aeron_int64_counter_map_inc_and_get(aeron_int64_counter_map_t *map, const int64_t key, int64_t *value)\n{\n    return aeron_int64_counter_map_add_and_get(map, key, 1, value);\n}\n\ninline int aeron_int64_counter_map_dec_and_get(aeron_int64_counter_map_t *map, const int64_t key, int64_t *value)\n{\n    return aeron_int64_counter_map_add_and_get(map, key, -1, value);\n}\n\ninline int aeron_int64_counter_map_get_and_inc(aeron_int64_counter_map_t *map, const int64_t key, int64_t *value)\n{\n    return aeron_int64_counter_map_get_and_add(map, key, 1, value);\n}\n\ninline int aeron_int64_counter_map_get_and_dec(aeron_int64_counter_map_t *map, const int64_t key, int64_t *value)\n{\n    return aeron_int64_counter_map_get_and_add(map, key, -1, value);\n}\n\ntypedef void (*aeron_int64_counter_map_for_each_func_t)(void *clientd, int64_t key, int64_t value);\ntypedef bool (*aeron_int64_counter_map_predicate_func_t)(void *clientd, int64_t key, int64_t value);\n\ninline void aeron_int64_counter_map_for_each(\n    aeron_int64_counter_map_t *map, aeron_int64_counter_map_for_each_func_t func, void *clientd)\n{\n    for (size_t i = 0, size = map->entries_length; i < size; i += 2)\n    {\n        if (map->initial_value != map->entries[i + 1])\n        {\n            func(clientd, map->entries[i], map->entries[i + 1]);\n        }\n    }\n}\n\ninline void aeron_int64_counter_map_remove_if(\n    aeron_int64_counter_map_t *map, aeron_int64_counter_map_predicate_func_t func, void *clientd)\n{\n    size_t i = 0;\n    size_t remaining = map->size;\n\n    while (i < map->entries_length && remaining > 0)\n    {\n        bool is_removed = false;\n        int64_t key = map->entries[i];\n        int64_t value = map->entries[i + 1];\n        if (map->initial_value != value)\n        {\n            if (func(clientd, key, value))\n            {\n                is_removed = (map->initial_value != aeron_int64_counter_map_remove(map, key));\n            }\n        }\n\n        if (is_removed)\n        {\n            --remaining;\n        }\n        else\n        {\n            i += 2;\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_int64_to_ptr_hash_map.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"collections/aeron_int64_to_ptr_hash_map.h\"\n\nextern size_t aeron_int64_to_ptr_hash_map_hash_key(int64_t key, size_t mask);\n\nextern int aeron_int64_to_ptr_hash_map_init(\n    aeron_int64_to_ptr_hash_map_t *map, size_t initial_capacity, float load_factor);\nextern void aeron_int64_to_ptr_hash_map_delete(aeron_int64_to_ptr_hash_map_t *map);\nextern int aeron_int64_to_ptr_hash_map_rehash(aeron_int64_to_ptr_hash_map_t *map, size_t new_capacity);\nextern int aeron_int64_to_ptr_hash_map_put(aeron_int64_to_ptr_hash_map_t *map, const int64_t key, void *value);\nextern void *aeron_int64_to_ptr_hash_map_get(aeron_int64_to_ptr_hash_map_t *map, const int64_t key);\n\nextern void aeron_int64_to_ptr_hash_map_compact_chain(aeron_int64_to_ptr_hash_map_t *map, size_t delete_index);\n\nextern void *aeron_int64_to_ptr_hash_map_remove(aeron_int64_to_ptr_hash_map_t *map, int64_t key);\nextern void aeron_int64_to_ptr_hash_map_for_each(\n    aeron_int64_to_ptr_hash_map_t *map, aeron_int64_to_ptr_hash_map_for_each_func_t func, void *clientd);\nextern void aeron_int64_to_ptr_hash_map_remove_if(\n    aeron_int64_to_ptr_hash_map_t *map, aeron_int64_to_ptr_hash_map_predicate_func_t func, void *clientd);\n\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_int64_to_ptr_hash_map.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_INT64_TO_PTR_HASH_MAP_H\n#define AERON_INT64_TO_PTR_HASH_MAP_H\n\n#include <errno.h>\n\n#include \"util/aeron_platform.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_error.h\"\n#include \"collections/aeron_hashing.h\"\n#include \"collections/aeron_map.h\"\n#include \"aeron_alloc.h\"\n\ntypedef struct aeron_int64_to_ptr_hash_map_stct\n{\n    int64_t *keys;\n    void **values;\n    float load_factor;\n    size_t capacity;\n    size_t size;\n    size_t resize_threshold;\n}\naeron_int64_to_ptr_hash_map_t;\n\ninline size_t aeron_int64_to_ptr_hash_map_hash_key(int64_t key, size_t mask)\n{\n    return aeron_hash((uint64_t)key, mask);\n}\n\ninline int aeron_int64_to_ptr_hash_map_init(\n    aeron_int64_to_ptr_hash_map_t *map, size_t initial_capacity, float load_factor)\n{\n    size_t capacity = (size_t)aeron_find_next_power_of_two((int32_t)initial_capacity);\n\n    map->load_factor = load_factor;\n    map->resize_threshold = (size_t)(load_factor * (float)capacity);\n    map->keys = NULL;\n    map->values = NULL;\n    map->capacity = capacity;\n    map->size = 0;\n\n    if (aeron_alloc((void **)&map->keys, (capacity * sizeof(int64_t))) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&map->values, (capacity * sizeof(void *))) < 0)\n    {\n        return -1;\n    }\n\n    return 0;\n}\n\ninline void aeron_int64_to_ptr_hash_map_delete(aeron_int64_to_ptr_hash_map_t *map)\n{\n    if (NULL != map->keys)\n    {\n        aeron_free(map->keys);\n    }\n\n    if (NULL != map->values)\n    {\n        aeron_free(map->values);\n    }\n}\n\ninline int aeron_int64_to_ptr_hash_map_rehash(aeron_int64_to_ptr_hash_map_t *map, size_t new_capacity)\n{\n    size_t mask = new_capacity - 1;\n    map->resize_threshold = (size_t)((float)new_capacity * map->load_factor);\n\n    int64_t *tmp_keys;\n    void **tmp_values;\n\n    if (aeron_alloc((void **)&tmp_keys, new_capacity * sizeof(int64_t)) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&tmp_values, new_capacity * sizeof(void *)) < 0)\n    {\n        aeron_free(tmp_keys);\n        return -1;\n    }\n\n    for (size_t i = 0, size = map->capacity; i < size; i++)\n    {\n        void *value = map->values[i];\n\n        if (NULL != value)\n        {\n            int64_t key = map->keys[i];\n            size_t new_hash = aeron_int64_to_ptr_hash_map_hash_key(key, mask);\n\n            while (NULL != tmp_values[new_hash])\n            {\n                new_hash = (new_hash + 1) & mask;\n            }\n\n            tmp_keys[new_hash] = key;\n            tmp_values[new_hash] = value;\n        }\n    }\n\n    aeron_free(map->keys);\n    aeron_free(map->values);\n\n    map->keys = tmp_keys;\n    map->values = tmp_values;\n    map->capacity = new_capacity;\n\n    return 0;\n}\n\ninline int aeron_int64_to_ptr_hash_map_put(aeron_int64_to_ptr_hash_map_t *map, const int64_t key, void *value)\n{\n    if (NULL == value)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"value must not be null\");\n        return -1;\n    }\n\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_int64_to_ptr_hash_map_hash_key(key, mask);\n\n    void *old_value = NULL;\n    while (NULL != map->values[index])\n    {\n        if (key == map->keys[index])\n        {\n            old_value = map->values[index];\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    if (NULL == old_value)\n    {\n        ++map->size;\n        map->keys[index] = key;\n    }\n\n    map->values[index] = value;\n\n    if (map->size > map->resize_threshold)\n    {\n        size_t new_capacity = map->capacity << 1;\n\n        if (aeron_int64_to_ptr_hash_map_rehash(map, new_capacity) < 0)\n        {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\ninline void *aeron_int64_to_ptr_hash_map_get(aeron_int64_to_ptr_hash_map_t *map, const int64_t key)\n{\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_int64_to_ptr_hash_map_hash_key(key, mask);\n\n    void *value;\n    while (NULL != (value = map->values[index]))\n    {\n        if (key == map->keys[index])\n        {\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    return value;\n}\n\ninline void aeron_int64_to_ptr_hash_map_compact_chain(aeron_int64_to_ptr_hash_map_t *map, size_t delete_index)\n{\n    size_t mask = map->capacity - 1;\n    size_t index = delete_index;\n\n    while (true)\n    {\n        index = (index + 1) & mask;\n        if (NULL == map->values[index])\n        {\n            break;\n        }\n\n        size_t hash = aeron_int64_to_ptr_hash_map_hash_key(map->keys[index], mask);\n\n        if ((index < hash && (hash <= delete_index || delete_index <= index)) ||\n            (hash <= delete_index && delete_index <= index))\n        {\n            map->keys[delete_index] = map->keys[index];\n            map->values[delete_index] = map->values[index];\n\n            map->values[index] = NULL;\n            delete_index = index;\n        }\n    }\n}\n\ninline void *aeron_int64_to_ptr_hash_map_remove(aeron_int64_to_ptr_hash_map_t *map, int64_t key)\n{\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_int64_to_ptr_hash_map_hash_key(key, mask);\n\n    void *value;\n    while (NULL != (value = map->values[index]))\n    {\n        if (key == map->keys[index])\n        {\n            map->values[index] = NULL;\n            --map->size;\n\n            aeron_int64_to_ptr_hash_map_compact_chain(map, index);\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    return value;\n}\n\ntypedef void (*aeron_int64_to_ptr_hash_map_for_each_func_t)(void *clientd, int64_t key, void *value);\ntypedef bool (*aeron_int64_to_ptr_hash_map_predicate_func_t)(void *clientd, int64_t key, void *value);\n\ninline void aeron_int64_to_ptr_hash_map_for_each(\n    aeron_int64_to_ptr_hash_map_t *map, aeron_int64_to_ptr_hash_map_for_each_func_t func, void *clientd)\n{\n    for (size_t i = 0; i < map->capacity; i++)\n    {\n        if (NULL != map->values[i])\n        {\n            func(clientd, map->keys[i], map->values[i]);\n        }\n    }\n}\n\ninline void aeron_int64_to_ptr_hash_map_remove_if(\n    aeron_int64_to_ptr_hash_map_t *map, aeron_int64_to_ptr_hash_map_predicate_func_t func, void *clientd)\n{\n    size_t i = 0;\n    size_t remaining = map->size;\n\n    while (i < map->capacity && remaining > 0)\n    {\n        bool is_removed = false;\n        if (NULL != map->values[i])\n        {\n            if (func(clientd, map->keys[i], map->values[i]))\n            {\n                is_removed = NULL != aeron_int64_to_ptr_hash_map_remove(map, map->keys[i]);\n            }\n        }\n\n        if (is_removed)\n        {\n            --remaining;\n        }\n        else\n        {\n            ++i;\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_int64_to_tagged_ptr_hash_map.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"collections/aeron_int64_to_tagged_ptr_hash_map.h\"\n\nextern size_t aeron_int64_to_tagged_ptr_hash_map_hash_key(int64_t key, size_t mask);\n\nextern int aeron_int64_to_tagged_ptr_hash_map_init(aeron_int64_to_tagged_ptr_hash_map_t *map, size_t initial_capacity, float load_factor);\nextern void aeron_int64_to_tagged_ptr_hash_map_delete(aeron_int64_to_tagged_ptr_hash_map_t *map);\nextern int aeron_int64_to_tagged_ptr_hash_map_rehash(aeron_int64_to_tagged_ptr_hash_map_t *map, size_t new_capacity);\nextern int aeron_int64_to_tagged_ptr_hash_map_put(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, const int64_t key, int32_t tag, void *value);\nextern bool aeron_int64_to_tagged_ptr_hash_map_get(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, const int64_t key, uint32_t *tag, void **value);\n\nextern void aeron_int64_to_tagged_ptr_hash_map_compact_chain(aeron_int64_to_tagged_ptr_hash_map_t *map, size_t delete_index);\n\nextern bool aeron_int64_to_tagged_ptr_hash_map_remove(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, int64_t key, uint32_t *tag, void **value);\nextern void aeron_int64_to_tagged_ptr_hash_map_for_each(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, aeron_int64_to_tagged_ptr_hash_map_for_each_func_t func, void *clientd);\nextern void aeron_int64_to_tagged_ptr_hash_map_remove_if(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, aeron_int64_to_tagged_ptr_hash_map_predicate_func_t func, void *clientd);\n\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_int64_to_tagged_ptr_hash_map.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_INT32_TO_TAGGED_PTR_HASH_MAP_H\n#define AERON_INT32_TO_TAGGED_PTR_HASH_MAP_H\n\n#include <stdbool.h>\n#include <string.h>\n#include <errno.h>\n\n#include \"util/aeron_platform.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"collections/aeron_hashing.h\"\n#include \"collections/aeron_map.h\"\n#include \"aeron_alloc.h\"\n\ntypedef struct aeron_int64_to_tagged_ptr_entry_stct\n{\n    void *value;\n    uint32_t internal_flags;\n    uint32_t tag;\n}\naeron_int64_to_tagged_ptr_entry_t;\n\ntypedef struct aeron_int64_to_tagged_ptr_hash_map_stct\n{\n    int64_t *keys;\n    aeron_int64_to_tagged_ptr_entry_t *entries;\n    float load_factor;\n    size_t capacity;\n    size_t size;\n    size_t resize_threshold;\n}\naeron_int64_to_tagged_ptr_hash_map_t;\n\n#define AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT UINT32_C(1)\n#define AERON_INT64_TO_TAGGED_PTR_VALUE_ABSENT UINT32_C(0)\n\ninline size_t aeron_int64_to_tagged_ptr_hash_map_hash_key(int64_t key, size_t mask)\n{\n    return aeron_hash((uint64_t)key, mask);\n}\n\ninline int aeron_int64_to_tagged_ptr_hash_map_init(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, size_t initial_capacity, float load_factor)\n{\n    size_t capacity = (size_t)aeron_find_next_power_of_two((int32_t)initial_capacity);\n\n    map->load_factor = load_factor;\n    map->resize_threshold = (size_t)(load_factor * (float)capacity);\n    map->keys = NULL;\n    map->entries = NULL;\n    map->capacity = capacity;\n    map->size = 0;\n\n    if (aeron_alloc((void **)&map->keys, (capacity * sizeof(int64_t))) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&map->entries, (capacity * sizeof(aeron_int64_to_tagged_ptr_entry_t))) < 0)\n    {\n        return -1;\n    }\n\n    return 0;\n}\n\ninline void aeron_int64_to_tagged_ptr_hash_map_delete(aeron_int64_to_tagged_ptr_hash_map_t *map)\n{\n    if (NULL != map->keys)\n    {\n        aeron_free(map->keys);\n    }\n\n    if (NULL != map->entries)\n    {\n        aeron_free(map->entries);\n    }\n}\n\ninline int aeron_int64_to_tagged_ptr_hash_map_rehash(aeron_int64_to_tagged_ptr_hash_map_t *map, size_t new_capacity)\n{\n    size_t mask = new_capacity - 1;\n    map->resize_threshold = (size_t)((float)new_capacity * map->load_factor);\n\n    int64_t *tmp_keys;\n    aeron_int64_to_tagged_ptr_entry_t *tmp_entries;\n\n    if (aeron_alloc((void **)&tmp_keys, (new_capacity * sizeof(int64_t))) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&tmp_entries, (new_capacity * sizeof(aeron_int64_to_tagged_ptr_entry_t))) < 0)\n    {\n        return -1;\n    }\n\n    for (size_t i = 0, size = map->capacity; i < size; i++)\n    {\n        aeron_int64_to_tagged_ptr_entry_t *entry = &map->entries[i];\n\n        if (AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT == entry->internal_flags)\n        {\n            int64_t key = map->keys[i];\n            size_t new_hash = aeron_int64_to_tagged_ptr_hash_map_hash_key(key, mask);\n\n            while (AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT == tmp_entries[new_hash].internal_flags)\n            {\n                new_hash = (new_hash + 1) & mask;\n            }\n\n            tmp_keys[new_hash] = key;\n            memcpy(&tmp_entries[new_hash], entry, sizeof(aeron_int64_to_tagged_ptr_entry_t));\n        }\n    }\n\n    aeron_free(map->keys);\n    aeron_free(map->entries);\n\n    map->keys = tmp_keys;\n    map->entries = tmp_entries;\n    map->capacity = new_capacity;\n\n    return 0;\n}\n\ninline int aeron_int64_to_tagged_ptr_hash_map_put(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, const int64_t key, int32_t tag, void *value)\n{\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_int64_to_tagged_ptr_hash_map_hash_key(key, mask);\n\n    aeron_int64_to_tagged_ptr_entry_t *old_value = NULL;\n    while (AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT == map->entries[index].internal_flags)\n    {\n        if (key == map->keys[index])\n        {\n            old_value = &map->entries[index];\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    if (NULL == old_value)\n    {\n        ++map->size;\n        map->keys[index] = key;\n    }\n\n    map->entries[index].internal_flags = AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT;\n    map->entries[index].tag = (uint32_t)tag;\n    map->entries[index].value = value;\n\n    if (map->size > map->resize_threshold)\n    {\n        size_t new_capacity = map->capacity << 1;\n\n        if (aeron_int64_to_tagged_ptr_hash_map_rehash(map, new_capacity) < 0)\n        {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\ninline bool aeron_int64_to_tagged_ptr_hash_map_get(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, const int64_t key, uint32_t *tag, void **value)\n{\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_int64_to_tagged_ptr_hash_map_hash_key(key, mask);\n\n    while (AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT == map->entries[index].internal_flags)\n    {\n        if (key == map->keys[index])\n        {\n            if (NULL != value)\n            {\n                *value = map->entries[index].value;\n            }\n            if (NULL != tag)\n            {\n                *tag = map->entries[index].tag;\n            }\n            return true;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    return false;\n}\n\ninline void aeron_int64_to_tagged_ptr_hash_map_compact_chain(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, size_t delete_index)\n{\n    size_t mask = map->capacity - 1;\n    size_t index = delete_index;\n\n    while (true)\n    {\n        index = (index + 1) & mask;\n        if (AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT != map->entries[index].internal_flags)\n        {\n            break;\n        }\n\n        size_t hash = aeron_int64_to_tagged_ptr_hash_map_hash_key(map->keys[index], mask);\n\n        if ((index < hash && (hash <= delete_index || delete_index <= index)) ||\n            (hash <= delete_index && delete_index <= index))\n        {\n            map->keys[delete_index] = map->keys[index];\n            memcpy(&map->entries[delete_index], &map->entries[index], sizeof(aeron_int64_to_tagged_ptr_entry_t));\n\n            map->entries[index].internal_flags = AERON_INT64_TO_TAGGED_PTR_VALUE_ABSENT;\n            map->entries[index].tag = 0;\n            map->entries[index].value = NULL;\n            delete_index = index;\n        }\n    }\n}\n\ninline bool aeron_int64_to_tagged_ptr_hash_map_remove(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, int64_t key, uint32_t *tag, void **value)\n{\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_int64_to_tagged_ptr_hash_map_hash_key(key, mask);\n\n    while (AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT == map->entries[index].internal_flags)\n    {\n        if (key == map->keys[index])\n        {\n            if (NULL != value)\n            {\n                *value = map->entries[index].value;\n            }\n            if (NULL != tag)\n            {\n                *tag = map->entries[index].tag;\n            }\n\n            map->entries[index].internal_flags = AERON_INT64_TO_TAGGED_PTR_VALUE_ABSENT;\n            map->entries[index].tag = 0;\n            map->entries[index].value = NULL;\n            --map->size;\n\n            aeron_int64_to_tagged_ptr_hash_map_compact_chain(map, index);\n\n            return true;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    return false;\n}\n\ntypedef void (*aeron_int64_to_tagged_ptr_hash_map_for_each_func_t)(void *clientd, int64_t key, uint32_t tag, void *value);\ntypedef bool (*aeron_int64_to_tagged_ptr_hash_map_predicate_func_t)(void *clientd, int64_t key, uint32_t tag, void *value);\n\ninline void aeron_int64_to_tagged_ptr_hash_map_for_each(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, aeron_int64_to_tagged_ptr_hash_map_for_each_func_t func, void *clientd)\n{\n    for (size_t i = 0; i < map->capacity; i++)\n    {\n        if (AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT == map->entries[i].internal_flags)\n        {\n            func(clientd, map->keys[i], map->entries[i].tag, map->entries[i].value);\n        }\n    }\n}\n\ninline void aeron_int64_to_tagged_ptr_hash_map_remove_if(\n    aeron_int64_to_tagged_ptr_hash_map_t *map, aeron_int64_to_tagged_ptr_hash_map_predicate_func_t func, void *clientd)\n{\n    size_t i = 0;\n    size_t remaining = map->size;\n\n    while (i < map->capacity && remaining > 0)\n    {\n        bool is_removed = false;\n        if (AERON_INT64_TO_TAGGED_PTR_VALUE_PRESENT == map->entries[i].internal_flags)\n        {\n            if (func(clientd, map->keys[i], map->entries[i].tag, map->entries[i].value))\n            {\n                is_removed = aeron_int64_to_tagged_ptr_hash_map_remove(map, map->keys[i], NULL, NULL);\n            }\n        }\n\n        if (is_removed)\n        {\n            --remaining;\n        }\n        else\n        {\n            ++i;\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_linked_queue.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include \"aeron_alloc.h\"\n#include \"collections/aeron_linked_queue.h\"\n#include \"util/aeron_error.h\"\n\nstruct aeron_linked_queue_node_stct\n{\n    aeron_linked_queue_node_t *next;\n    void *element;\n};\n\nint aeron_linked_queue_init(aeron_linked_queue_t *queue)\n{\n    queue->head = NULL;\n    queue->tail = NULL;\n\n    return 0;\n}\n\nint aeron_linked_queue_close(aeron_linked_queue_t *queue)\n{\n    if (aeron_linked_queue_is_empty(queue))\n    {\n        return 0;\n    }\n\n    // the queue is NOT empty\n    AERON_SET_ERR(EINVAL, \"%s\", \"queue must be empty to be deleted\");\n    return -1;\n}\n\nint aeron_linked_queue_offer(aeron_linked_queue_t *queue, void *element)\n{\n    aeron_linked_queue_node_t *node = NULL;\n    if (aeron_alloc((void **)&node, sizeof(aeron_linked_queue_node_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n    node->element = element;\n    node->next = NULL;\n\n    if (aeron_linked_queue_is_empty(queue))\n    {\n        queue->head = node;\n        queue->tail = node;\n    }\n    else\n    {\n        queue->head->next = node;\n        queue->head = node;\n    }\n\n    return 0;\n}\n\nvoid *aeron_linked_queue_peek(aeron_linked_queue_t *queue)\n{\n    return queue->tail == NULL ? NULL : queue->tail->element;\n}\n\nvoid *aeron_linked_queue_poll(aeron_linked_queue_t *queue)\n{\n    aeron_linked_queue_node_t *next;\n\n    next = queue->tail;\n\n    if (NULL == next)\n    {\n        return NULL;\n    }\n\n    void *element = next->element;\n\n    queue->tail = next->next;\n\n    if (queue->tail == NULL)\n    {\n        queue->head = NULL;\n    }\n\n    aeron_free(next);\n\n    return element;\n}\n\nbool aeron_linked_queue_is_empty(aeron_linked_queue_t *queue)\n{\n    return NULL == queue->head && NULL == queue->tail;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_linked_queue.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_LINKED_QUEUE_H\n#define AERON_LINKED_QUEUE_H\n#include <stdbool.h>\n\ntypedef struct aeron_linked_queue_node_stct aeron_linked_queue_node_t;\n\ntypedef struct aeron_linked_queue_stct\n{\n    aeron_linked_queue_node_t *head;\n    aeron_linked_queue_node_t *tail;\n}\naeron_linked_queue_t;\n\n\nint aeron_linked_queue_init(aeron_linked_queue_t *queue);\n\nint aeron_linked_queue_close(aeron_linked_queue_t *queue);\n\nint aeron_linked_queue_offer(aeron_linked_queue_t *queue, void *element);\n\nvoid *aeron_linked_queue_peek(aeron_linked_queue_t *queue);\n\nvoid *aeron_linked_queue_poll(aeron_linked_queue_t *queue);\n\nbool aeron_linked_queue_is_empty(aeron_linked_queue_t *queue);\n\n#endif //AERON_LINKED_QUEUE_H\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_map.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"collections/aeron_map.h\"\n\nextern int64_t aeron_map_compound_key(int32_t high, int32_t low);\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_map.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_MAP_H\n#define AERON_AERON_MAP_H\n\n#define AERON_MAP_DEFAULT_LOAD_FACTOR (0.65f)\n\n#include <stdint.h>\n\ninline int64_t aeron_map_compound_key(int32_t high, int32_t low)\n{\n    return (int64_t)(((uint64_t)high << 32) | (low));\n}\n\n#endif //AERON_AERON_MAP_H\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_str_to_ptr_hash_map.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"collections/aeron_str_to_ptr_hash_map.h\"\n\nextern size_t aeron_str_to_ptr_hash_map_hash_key(uint64_t key, size_t mask);\n\nextern bool aeron_str_to_ptr_hash_map_compare(\n    aeron_str_to_ptr_hash_map_key_t *key, const char *key_str, size_t key_str_len, uint64_t key_hash_code);\n\nextern int aeron_str_to_ptr_hash_map_init(aeron_str_to_ptr_hash_map_t *map, size_t initial_capacity, float load_factor);\n\nextern void aeron_str_to_ptr_hash_map_delete(aeron_str_to_ptr_hash_map_t *map);\n\nextern int aeron_str_to_ptr_hash_map_rehash(aeron_str_to_ptr_hash_map_t *map, size_t new_capacity);\n\nextern int aeron_str_to_ptr_hash_map_put(\n    aeron_str_to_ptr_hash_map_t *map, const char *key, size_t key_len, void *value);\n\nextern void *aeron_str_to_ptr_hash_map_get(aeron_str_to_ptr_hash_map_t *map, const char *key, size_t key_len);\n\nextern void aeron_str_to_ptr_hash_map_compact_chain(aeron_str_to_ptr_hash_map_t *map, size_t delete_index);\n\nextern void *aeron_str_to_ptr_hash_map_remove(aeron_str_to_ptr_hash_map_t *map, const char *key, size_t key_len);\n\nextern void aeron_str_to_ptr_hash_map_for_each(\n    aeron_str_to_ptr_hash_map_t *map, aeron_str_to_ptr_hash_map_for_each_func_t func, void *clientd);\n"
  },
  {
    "path": "aeron-client/src/main/c/collections/aeron_str_to_ptr_hash_map.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_STR_TO_PTR_HASH_MAP_H\n#define AERON_STR_TO_PTR_HASH_MAP_H\n\n#include <string.h>\n#include <errno.h>\n\n#include \"util/aeron_platform.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_error.h\"\n#include \"collections/aeron_hashing.h\"\n#include \"collections/aeron_map.h\"\n#include \"aeron_alloc.h\"\n\ntypedef struct aeron_str_to_ptr_hash_map_key_stct\n{\n    const char *str;\n    uint64_t hash_code;\n    size_t str_length;\n}\naeron_str_to_ptr_hash_map_key_t;\n\ntypedef struct aeron_str_to_ptr_hash_map_stct\n{\n    aeron_str_to_ptr_hash_map_key_t *keys;\n    void **values;\n    float load_factor;\n    size_t capacity;\n    size_t size;\n    size_t resize_threshold;\n}\naeron_str_to_ptr_hash_map_t;\n\ninline size_t aeron_str_to_ptr_hash_map_hash_key(uint64_t key_hash_code, size_t mask)\n{\n    uint64_t hash = key_hash_code;\n\n    if (mask <= UINT32_MAX)\n    {\n        hash = (uint32_t)hash ^ (uint32_t)(hash >> 32u);\n    }\n\n    return (size_t)(hash & mask);\n}\n\ninline bool aeron_str_to_ptr_hash_map_compare(\n    aeron_str_to_ptr_hash_map_key_t *key, const char *key_str, size_t key_str_len, uint64_t key_hash_code)\n{\n    return key->hash_code == key_hash_code &&\n        key->str_length == key_str_len &&\n        strncmp(key->str, key_str, key_str_len) == 0;\n}\n\ninline int aeron_str_to_ptr_hash_map_init(aeron_str_to_ptr_hash_map_t *map, size_t initial_capacity, float load_factor)\n{\n    size_t capacity = (size_t)aeron_find_next_power_of_two((int32_t)initial_capacity);\n\n    map->load_factor = load_factor;\n    map->resize_threshold = (size_t)(load_factor * (float)capacity);\n    map->keys = NULL;\n    map->values = NULL;\n    map->capacity = capacity;\n    map->size = 0;\n\n    if (aeron_alloc((void **)&map->keys, (capacity * sizeof(aeron_str_to_ptr_hash_map_key_t))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate keys\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&map->values, (capacity * sizeof(void *))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate values\");\n        return -1;\n    }\n\n    return 0;\n}\n\ninline void aeron_str_to_ptr_hash_map_delete(aeron_str_to_ptr_hash_map_t *map)\n{\n    if (NULL != map->keys)\n    {\n        aeron_free(map->keys);\n    }\n\n    if (NULL != map->values)\n    {\n        aeron_free(map->values);\n    }\n}\n\ninline int aeron_str_to_ptr_hash_map_rehash(aeron_str_to_ptr_hash_map_t *map, size_t new_capacity)\n{\n    size_t mask = new_capacity - 1;\n    map->resize_threshold = (size_t)((float)new_capacity * map->load_factor);\n\n    aeron_str_to_ptr_hash_map_key_t *tmp_keys;\n    void **tmp_values;\n\n    if (aeron_alloc((void **)&tmp_keys, (new_capacity * sizeof(aeron_str_to_ptr_hash_map_key_t))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate new keys\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&tmp_values, (new_capacity * sizeof(void *))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate new values\");\n        return -1;\n    }\n\n    for (size_t i = 0, size = map->capacity; i < size; i++)\n    {\n        void *value = map->values[i];\n\n        if (NULL != value)\n        {\n            aeron_str_to_ptr_hash_map_key_t *key = &map->keys[i];\n            size_t new_hash = aeron_str_to_ptr_hash_map_hash_key(key->hash_code, mask);\n\n            while (NULL != tmp_values[new_hash])\n            {\n                new_hash = (new_hash + 1) & mask;\n            }\n\n            tmp_keys[new_hash].str = key->str;\n            tmp_keys[new_hash].str_length = key->str_length;\n            tmp_keys[new_hash].hash_code = key->hash_code;\n            tmp_values[new_hash] = value;\n        }\n    }\n\n    aeron_free(map->keys);\n    aeron_free(map->values);\n\n    map->keys = tmp_keys;\n    map->values = tmp_values;\n    map->capacity = new_capacity;\n\n    return 0;\n}\n\ninline int aeron_str_to_ptr_hash_map_put(aeron_str_to_ptr_hash_map_t *map, const char *key, size_t key_len, void *value)\n{\n    if (NULL == value)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"value is null\");\n        return -1;\n    }\n\n    uint64_t hash_code = aeron_fnv_64a_buf((uint8_t *)key, key_len);\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_str_to_ptr_hash_map_hash_key(hash_code, mask);\n\n    void *old_value = NULL;\n    while (NULL != map->values[index])\n    {\n        if (aeron_str_to_ptr_hash_map_compare(&map->keys[index], key, key_len, hash_code))\n        {\n            old_value = map->values[index];\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    if (NULL == old_value)\n    {\n        ++map->size;\n        map->keys[index].str = key;\n        map->keys[index].hash_code = hash_code;\n        map->keys[index].str_length = key_len;\n    }\n\n    map->values[index] = value;\n\n    if (map->size > map->resize_threshold)\n    {\n        size_t new_capacity = map->capacity << 1;\n\n        if (aeron_str_to_ptr_hash_map_rehash(map, new_capacity) < 0)\n        {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\ninline void *aeron_str_to_ptr_hash_map_get(aeron_str_to_ptr_hash_map_t *map, const char *key, size_t key_len)\n{\n    uint64_t hash_code = aeron_fnv_64a_buf((uint8_t *)key, key_len);\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_str_to_ptr_hash_map_hash_key(hash_code, mask);\n\n    void *value;\n    while (NULL != (value = map->values[index]))\n    {\n        if (aeron_str_to_ptr_hash_map_compare(&map->keys[index], key, key_len, hash_code))\n        {\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    return value;\n}\n\ninline void aeron_str_to_ptr_hash_map_compact_chain(aeron_str_to_ptr_hash_map_t *map, size_t delete_index)\n{\n    size_t mask = map->capacity - 1;\n    size_t index = delete_index;\n\n    while (true)\n    {\n        index = (index + 1) & mask;\n        if (NULL == map->values[index])\n        {\n            break;\n        }\n\n        size_t hash = aeron_str_to_ptr_hash_map_hash_key(map->keys[index].hash_code, mask);\n\n        if ((index < hash && (hash <= delete_index || delete_index <= index)) ||\n            (hash <= delete_index && delete_index <= index))\n        {\n            memcpy(&map->keys[delete_index], &map->keys[index], sizeof(aeron_str_to_ptr_hash_map_key_t));\n            map->values[delete_index] = map->values[index];\n\n            map->values[index] = NULL;\n            delete_index = index;\n        }\n    }\n}\n\ninline void *aeron_str_to_ptr_hash_map_remove(aeron_str_to_ptr_hash_map_t *map, const char *key, size_t key_len)\n{\n    uint64_t hash_code = aeron_fnv_64a_buf((uint8_t *)key, key_len);\n    size_t mask = map->capacity - 1;\n    size_t index = aeron_str_to_ptr_hash_map_hash_key(hash_code, mask);\n\n    void *value;\n    while (NULL != (value = map->values[index]))\n    {\n        if (aeron_str_to_ptr_hash_map_compare(&map->keys[index], key, key_len, hash_code))\n        {\n            map->values[index] = NULL;\n            --map->size;\n\n            aeron_str_to_ptr_hash_map_compact_chain(map, index);\n            break;\n        }\n\n        index = (index + 1) & mask;\n    }\n\n    return value;\n}\n\ntypedef void (*aeron_str_to_ptr_hash_map_for_each_func_t)(void *clientd, const char *key, size_t key_len, void *value);\n\ninline void aeron_str_to_ptr_hash_map_for_each(\n    aeron_str_to_ptr_hash_map_t *map, aeron_str_to_ptr_hash_map_for_each_func_t func, void *clientd)\n{\n    for (size_t i = 0; i < map->capacity; i++)\n    {\n        if (map->values[i] != NULL)\n        {\n            func(clientd, map->keys[i].str, map->keys[i].str_length, map->values[i]);\n        }\n    }\n}\n\n#endif //AERON_STR_TO_PTR_HASH_MAP_H\n"
  },
  {
    "path": "aeron-client/src/main/c/command/aeron_control_protocol.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONTROL_PROTOCOL_H\n#define AERON_CONTROL_PROTOCOL_H\n\n#include <stdint.h>\n#define AERON_CONTROL_PROTOCOL_MAJOR_VERSION (1)\n#define AERON_CONTROL_PROTOCOL_MINOR_VERSION (0)\n#define AERON_CONTROL_PROTOCOL_PATCH_VERSION (0)\n\n#define AERON_COMMAND_ADD_PUBLICATION (0x01)\n#define AERON_COMMAND_REMOVE_PUBLICATION (0x02)\n#define AERON_COMMAND_ADD_EXCLUSIVE_PUBLICATION (0x03)\n\n#define AERON_COMMAND_ADD_SUBSCRIPTION (0x04)\n#define AERON_COMMAND_REMOVE_SUBSCRIPTION (0x05)\n#define AERON_COMMAND_CLIENT_KEEPALIVE (0x06)\n#define AERON_COMMAND_ADD_DESTINATION (0x07)\n#define AERON_COMMAND_REMOVE_DESTINATION (0x08)\n#define AERON_COMMAND_ADD_COUNTER (0x09)\n#define AERON_COMMAND_REMOVE_COUNTER (0x0A)\n#define AERON_COMMAND_CLIENT_CLOSE (0x0B)\n#define AERON_COMMAND_ADD_RCV_DESTINATION (0x0C)\n#define AERON_COMMAND_REMOVE_RCV_DESTINATION (0x0D)\n#define AERON_COMMAND_TERMINATE_DRIVER (0x0E)\n#define AERON_COMMAND_ADD_STATIC_COUNTER (0x0F)\n#define AERON_COMMAND_REJECT_IMAGE (0x10)\n#define AERON_COMMAND_REMOVE_DESTINATION_BY_ID (0x11)\n#define AERON_COMMAND_GET_NEXT_AVAILABLE_SESSION_ID (0x12)\n\n#define AERON_RESPONSE_ON_ERROR (0x0F01)\n#define AERON_RESPONSE_ON_AVAILABLE_IMAGE (0x0F02)\n#define AERON_RESPONSE_ON_PUBLICATION_READY (0x0F03)\n#define AERON_RESPONSE_ON_OPERATION_SUCCESS (0x0F04)\n#define AERON_RESPONSE_ON_UNAVAILABLE_IMAGE (0x0F05)\n#define AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY (0x0F06)\n#define AERON_RESPONSE_ON_SUBSCRIPTION_READY (0x0F07)\n#define AERON_RESPONSE_ON_COUNTER_READY (0x0F08)\n#define AERON_RESPONSE_ON_UNAVAILABLE_COUNTER (0x0F09)\n#define AERON_RESPONSE_ON_CLIENT_TIMEOUT (0x0F0A)\n#define AERON_RESPONSE_ON_STATIC_COUNTER (0x0F0B)\n#define AERON_RESPONSE_ON_PUBLICATION_ERROR (0x0F0C)\n#define AERON_RESPONSE_ON_NEXT_AVAILABLE_SESSION_ID (0x0F0D)\n\n/* error codes */\n#define AERON_ERROR_CODE_UNKNOWN_CODE_VALUE (-1)\n#define AERON_ERROR_CODE_UNUSED (0)\n#define AERON_ERROR_CODE_INVALID_CHANNEL (1)\n#define AERON_ERROR_CODE_UNKNOWN_SUBSCRIPTION (2)\n#define AERON_ERROR_CODE_UNKNOWN_PUBLICATION (3)\n#define AERON_ERROR_CODE_CHANNEL_ENDPOINT_ERROR (4)\n#define AERON_ERROR_CODE_UNKNOWN_COUNTER (5)\n#define AERON_ERROR_CODE_UNKNOWN_COMMAND_TYPE_ID (6)\n#define AERON_ERROR_CODE_MALFORMED_COMMAND (7)\n#define AERON_ERROR_CODE_NOT_SUPPORTED (8)\n#define AERON_ERROR_CODE_UNKNOWN_HOST (9)\n#define AERON_ERROR_CODE_RESOURCE_TEMPORARILY_UNAVAILABLE (10)\n#define AERON_ERROR_CODE_GENERIC_ERROR (11)\n#define AERON_ERROR_CODE_STORAGE_SPACE (12)\n#define AERON_ERROR_CODE_IMAGE_REJECTED (13)\n#define AERON_ERROR_CODE_PUBLICATION_REVOKED (14)\n\n#define AERON_COMMAND_REMOVE_PUBLICATION_FLAG_REVOKE (0x1)\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_correlated_command_stct\n{\n    int64_t client_id;\n    int64_t correlation_id;\n}\naeron_correlated_command_t;\n\ntypedef struct aeron_publication_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int32_t stream_id;\n    int32_t channel_length;\n}\naeron_publication_command_t;\n\ntypedef struct aeron_publication_buffers_ready_stct\n{\n    int64_t correlation_id;\n    int64_t registration_id;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t position_limit_counter_id;\n    int32_t channel_status_indicator_id;\n    int32_t log_file_length;\n}\naeron_publication_buffers_ready_t;\n\ntypedef struct aeron_subscription_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int64_t registration_correlation_id;\n    int32_t stream_id;\n    int32_t channel_length;\n}\naeron_subscription_command_t;\n\ntypedef struct aeron_subscription_ready_stct\n{\n    int64_t correlation_id;\n    int32_t channel_status_indicator_id;\n}\naeron_subscription_ready_t;\n\ntypedef struct aeron_image_buffers_ready_stct\n{\n    int64_t correlation_id;\n    int32_t session_id;\n    int32_t stream_id;\n    int64_t subscriber_registration_id;\n    int32_t subscriber_position_id;\n}\naeron_image_buffers_ready_t;\n\ntypedef struct aeron_operation_succeeded_stct\n{\n    int64_t correlation_id;\n}\naeron_operation_succeeded_t;\n\ntypedef struct aeron_error_response_stct\n{\n    int64_t offending_command_correlation_id;\n    int32_t error_code;\n    int32_t error_message_length;\n}\naeron_error_response_t;\n\ntypedef struct aeron_remove_counter_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int64_t registration_id;\n}\naeron_remove_counter_command_t;\n\ntypedef struct aeron_remove_publication_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int64_t registration_id;\n    uint64_t flags;\n}\naeron_remove_publication_command_t;\n\ntypedef struct aeron_remove_subscription_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int64_t registration_id;\n}\naeron_remove_subscription_command_t;\n\ntypedef struct aeron_image_message_stct\n{\n    int64_t correlation_id;\n    int64_t subscription_registration_id;\n    int32_t stream_id;\n    int32_t channel_length;\n}\naeron_image_message_t;\n\ntypedef struct aeron_destination_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int64_t registration_id;\n    int32_t channel_length;\n}\naeron_destination_command_t;\n\ntypedef struct aeron_destination_by_id_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int64_t resource_registration_id;\n    int64_t destination_registration_id;\n}\naeron_destination_by_id_command_t;\n\ntypedef struct aeron_counter_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int32_t type_id;\n}\naeron_counter_command_t;\n\ntypedef struct aeron_counter_update_stct\n{\n    int64_t correlation_id;\n    int32_t counter_id;\n}\naeron_counter_update_t;\n\ntypedef struct aeron_static_counter_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int64_t registration_id;\n    int32_t type_id;\n}\naeron_static_counter_command_t;\n\ntypedef struct aeron_static_counter_response_stct\n{\n    int64_t correlation_id;\n    int32_t counter_id;\n}\naeron_static_counter_response_t;\n\ntypedef struct aeron_client_timeout_stct\n{\n    int64_t client_id;\n}\naeron_client_timeout_t;\n\ntypedef struct aeron_terminate_driver_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int32_t token_length;\n}\naeron_terminate_driver_command_t;\n\ntypedef struct aeron_reject_image_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int64_t image_correlation_id;\n    int64_t position;\n    int32_t reason_length;\n    uint8_t reason_text[1];\n}\naeron_reject_image_command_t;\n\nstruct aeron_publication_error_stct\n{\n    int64_t registration_id;\n    int64_t destination_registration_id;\n    int32_t session_id;\n    int32_t stream_id;\n    int64_t receiver_id;\n    int64_t group_tag;\n    int16_t address_type;\n    uint16_t source_port;\n    uint8_t source_address[16];\n    int32_t error_code;\n    int32_t error_message_length;\n    uint8_t error_message[1];\n};\ntypedef struct aeron_publication_error_stct aeron_publication_error_t;\n\ntypedef struct aeron_get_next_available_session_id_command_stct\n{\n    aeron_correlated_command_t correlated;\n    int32_t stream_id;\n}\naeron_get_next_available_session_id_command_t;\n\ntypedef struct aeron_next_available_session_id_response_stct\n{\n    int64_t correlation_id;\n    int32_t next_session_id;\n}\naeron_next_available_session_id_response_t;\n\n#pragma pack(pop)\n\n#endif //AERON_CONTROL_PROTOCOL_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_atomic.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"concurrent/aeron_atomic.h\"\n\nextern bool aeron_cas_int64(volatile int64_t *dst, int64_t expected, int64_t desired);\n\nextern bool aeron_cas_uint64(volatile uint64_t *dst, uint64_t expected, uint64_t desired);\n\nextern bool aeron_cas_int32(volatile int32_t *dst, int32_t expected, int32_t desired);\n\nextern void aeron_acquire(void);\n\nextern void aeron_release(void);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_atomic.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ATOMIC_H\n#define AERON_ATOMIC_H\n\n#include \"util/aeron_platform.h\"\n\n#define AERON_ATOMIC_STR2(x) #x\n#define AERON_ATOMIC_STR(x)  AERON_ATOMIC_STR2(x)\n#define AERON_ATOMIC_LOC __FILE__ \":\" AERON_ATOMIC_STR(__LINE__)\n\n#if defined(__cplusplus)\n\n#include <type_traits>\n\n/*\n * C++ version:\n *  - decltype((expr)) yields T& / volatile T& for lvalues\n *  - require lvalue reference\n *  - require volatile on the referred-to type\n */\n#define AERON_ATOMIC_ASSERT_VOLATILE_LVALUE(expr, msg)                               \\\n_Static_assert(                                                                      \\\n    std::is_lvalue_reference<decltype((expr))>::value &&                             \\\n    std::is_volatile<typename std::remove_reference<decltype((expr))>::type>::value, \\\n    msg \" violation at \" AERON_ATOMIC_LOC \" (expr: \" #expr \")\");\n\n#elif defined(AERON_COMPILER_GCC)\n\n#define AERON_ATOMIC_ASSERT_VOLATILE_LVALUE(expr, msg)                               \\\n_Static_assert(                                                                      \\\n    __builtin_types_compatible_p(__typeof__(&(expr)),                                \\\n    volatile __typeof__(expr) *),                                                    \\\n    msg \" violation at \" AERON_ATOMIC_LOC \" (expr: \" #expr \")\");\n\n#else\n\n#define AERON_ATOMIC_ASSERT_VOLATILE_LVALUE(expr, msg)                               \\\ndo                                                                                   \\\n{                                                                                    \\\n    (void)(expr);                                                                    \\\n}                                                                                    \\\nwhile (false)\n\n#endif\n\n#if defined(AERON_COMPILER_GCC) && defined(AERON_CPU_X64)\n    #include \"concurrent/aeron_atomic64_gcc_x86_64.h\"\n#elif defined(AERON_COMPILER_GCC) && defined(AERON_CPU_ARM)\n    #include \"concurrent/aeron_atomic64_c11.h\"\n#elif defined(AERON_COMPILER_MSVC) && defined(AERON_CPU_X64)\n    #include \"concurrent/aeron_atomic64_msvc.h\"\n#else\n    #error Unsupported platform!\n#endif\n\n#endif //AERON_ATOMIC_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_atomic64_c11.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ATOMIC64_C11_H\n#define AERON_ATOMIC64_C11_H\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdatomic.h>\n\n#if defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wc11-extensions\"\n#endif\n\n#define AERON_GET_ACQUIRE(dst, src)                                           \\\ndo                                                                            \\\n{                                                                             \\\n    AERON_ATOMIC_ASSERT_VOLATILE_LVALUE(                                      \\\n        src,                                                                  \\\n        \"AERON_GET_ACQUIRE: src must be a volatile lvalue\"); \\\n    dst = (src);                                                              \\\n    atomic_thread_fence(memory_order_acquire);                                \\\n}                                                                             \\\nwhile (false)\n\n#define AERON_SET_RELEASE(dst, src)                                           \\\ndo                                                                            \\\n{                                                                             \\\n    AERON_ATOMIC_ASSERT_VOLATILE_LVALUE(                                      \\\n        dst,                                                                  \\\n        \"AERON_SET_RELEASE: dst must be a volatile lvalue\"); \\\n    atomic_thread_fence(memory_order_release);                                \\\n    (dst) = (src);                                                            \\\n}                                                                             \\\nwhile (false)\n\n#define AERON_GET_AND_ADD_INT64(original, dst, value)                         \\\ndo                                                                            \\\n{                                                                             \\\n    original = atomic_fetch_add((_Atomic(int64_t) *)&dst, value);             \\\n}                                                                             \\\nwhile (false)                                                                 \\\n\n#define AERON_GET_AND_ADD_INT32(original, dst, value)                         \\\ndo                                                                            \\\n{                                                                             \\\n    original = atomic_fetch_add((_Atomic(int32_t) *)&dst, value);             \\\n}                                                                             \\\nwhile (false)                                                                 \\\n\ninline bool aeron_cas_int64(volatile int64_t *dst, int64_t expected, int64_t desired)\n{\n    return atomic_compare_exchange_strong((_Atomic(int64_t) *)dst, &expected, desired);\n}\n\ninline bool aeron_cas_uint64(volatile uint64_t *dst, uint64_t expected, uint64_t desired)\n{\n    return atomic_compare_exchange_strong((_Atomic(uint64_t) *)dst, &expected, desired);\n}\n\ninline bool aeron_cas_int32(volatile int32_t *dst, int32_t expected, int32_t desired)\n{\n    return atomic_compare_exchange_strong((_Atomic(int32_t) *)dst, &expected, desired);\n}\n\ninline void aeron_acquire(void)\n{\n    atomic_thread_fence(memory_order_acquire);\n}\n\ninline void aeron_release(void)\n{\n    atomic_thread_fence(memory_order_release);\n}\n\n// intentionally commented out, but kept in if we ever make the GET_AND_FETCH into inline functions, then can be used.\n//#if defined(__clang__)\n//#pragma clang diagnostic pop\n//#endif\n\n/*-------------------------------------\n *  Alignment\n *-------------------------------------\n * Note: May not work on local variables.\n * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=24691\n */\n#define AERON_DECL_ALIGNED(declaration, amt) declaration __attribute__((aligned(amt)))\n\n#endif //AERON_ATOMIC64_C11_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_atomic64_gcc_x86_64.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ATOMIC64_GCC_X86_64_H\n#define AERON_ATOMIC64_GCC_X86_64_H\n\n#include <stdbool.h>\n#include <stdint.h>\n\n#define AERON_GET_ACQUIRE(dst, src)                                           \\\ndo                                                                            \\\n{                                                                             \\\n    AERON_ATOMIC_ASSERT_VOLATILE_LVALUE(                                      \\\n        src,                                                                  \\\n        \"AERON_GET_ACQUIRE: src must be a volatile lvalue\");                  \\\n    dst = (src);                                                              \\\n    __asm__ __volatile__(\"\" ::: \"memory\");                                    \\\n}                                                                             \\\nwhile (false)\n\n#define AERON_SET_RELEASE(dst, src)                                           \\\ndo                                                                            \\\n{                                                                             \\\n    AERON_ATOMIC_ASSERT_VOLATILE_LVALUE(                                      \\\n        dst,                                                                  \\\n        \"AERON_SET_RELEASE: dst must be a volatile lvalue\");                  \\\n    __asm__ __volatile__(\"\" ::: \"memory\");                                    \\\n    (dst) = (src);                                                            \\\n}                                                                             \\\nwhile (false)\n\n#define AERON_GET_AND_ADD_INT64(original, dst, value)                         \\\ndo                                                                            \\\n{                                                                             \\\n    __asm__ __volatile__(                                                     \\\n        \"lock; xaddq %0, %1\"                                                  \\\n        : \"=r\"(original), \"+m\"(dst)                                           \\\n        : \"0\"((int64_t)value)                                                 \\\n        : \"memory\", \"cc\"                                                      \\\n        );                                                                    \\\n}                                                                             \\\nwhile (false)\n\n#define AERON_GET_AND_ADD_INT32(original, dst, value)                         \\\ndo                                                                            \\\n{                                                                             \\\n    __asm__ __volatile__(                                                     \\\n        \"lock; xaddl %0, %1\"                                                  \\\n        : \"=r\"(original), \"+m\"(dst)                                           \\\n        : \"0\"(value)                                                          \\\n        : \"memory\", \"cc\"                                                      \\\n        );                                                                    \\\n}                                                                             \\\nwhile (false)\n\ninline bool aeron_cas_int64(volatile int64_t *dst, int64_t expected, int64_t desired)\n{\n    int64_t original;\n    __asm__ __volatile__(\n        \"lock; cmpxchgq %2, %1\"\n        : \"=a\"(original), \"+m\"(*dst)\n        : \"r\"(desired), \"0\"(expected)\n        : \"memory\", \"cc\"\n        );\n\n    return original == expected;\n}\n\ninline bool aeron_cas_uint64(volatile uint64_t *dst, uint64_t expected, uint64_t desired)\n{\n    uint64_t original;\n    __asm__ __volatile__(\n        \"lock; cmpxchgq %2, %1\"\n        : \"=a\"(original), \"+m\"(*dst)\n        : \"r\"(desired), \"0\"(expected)\n        : \"memory\", \"cc\"\n        );\n\n    return original == expected;\n}\n\ninline bool aeron_cas_int32(volatile int32_t *dst, int32_t expected, int32_t desired)\n{\n    int32_t original;\n    __asm__ __volatile__(\n        \"lock; cmpxchgl %2, %1\"\n        : \"=a\"(original), \"+m\"(*dst)\n        : \"r\"(desired), \"0\"(expected)\n        : \"memory\", \"cc\"\n        );\n\n    return original == expected;\n}\n\ninline void aeron_acquire(void)\n{\n    __asm__ __volatile__(\"\" ::: \"memory\");\n}\n\ninline void aeron_release(void)\n{\n    __asm__ __volatile__(\"\" ::: \"memory\");\n}\n\n\n/*-------------------------------------\n *  Alignment\n *-------------------------------------\n * Note: May not work on local variables.\n * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=24691\n */\n#define AERON_DECL_ALIGNED(declaration, amt) declaration __attribute__((aligned(amt)))\n\n#endif //AERON_ATOMIC64_GCC_X86_64_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_atomic64_msvc.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ATOMIC64_MSVC_H\n#define AERON_ATOMIC64_MSVC_H\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <winsock2.h>\n#include <windows.h>\n#include <intrin.h>\n\n#define AERON_GET_ACQUIRE(dst, src) \\\ndo \\\n{ \\\n    dst = src; \\\n    _ReadWriteBarrier(); \\\n} \\\nwhile (false) \\\n\n#define AERON_SET_RELEASE(dst, src) \\\ndo \\\n{ \\\n    _ReadWriteBarrier(); \\\n    dst = src; \\\n} \\\nwhile (false) \\\n\n#define AERON_GET_AND_ADD_INT64(original, current, value) \\\ndo \\\n{ \\\n    original = _InlineInterlockedAdd64((long long volatile *)&current, (long long)value) - value; \\\n} \\\nwhile (false) \\\n\n#define AERON_GET_AND_ADD_INT32(original, current, value) \\\ndo \\\n{ \\\n    original = _InlineInterlockedAdd((long volatile *)&current, (long)value) - value; \\\n} \\\nwhile (false) \\\n\ninline bool aeron_cas_int64(volatile int64_t *dst, int64_t expected, int64_t desired)\n{\n    int64_t original = _InterlockedCompareExchange64(\n        (long long volatile *)dst, (long long)desired, (long long)expected);\n\n    return original == expected;\n}\n\ninline bool aeron_cas_uint64(volatile uint64_t *dst, uint64_t expected, uint64_t desired)\n{\n    uint64_t original = _InterlockedCompareExchange64(\n        (long long volatile *)dst, (long long)desired, (long long)expected);\n\n    return original == expected;\n}\n\ninline bool aeron_cas_int32(volatile int32_t *dst, int32_t expected, int32_t desired)\n{\n    int32_t original = _InterlockedCompareExchange((long volatile *)dst, (long)desired, (long)expected);\n\n    return original == expected;\n}\n\ninline void aeron_acquire(void)\n{\n    _ReadWriteBarrier();\n}\n\ninline void aeron_release(void)\n{\n    _ReadWriteBarrier();\n}\n\n#define AERON_DECL_ALIGNED(declaration, amt) __declspec(align(amt))  declaration\n\n#endif //AERON_ATOMIC64_MSVC_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_blocking_linked_queue.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include \"concurrent/aeron_blocking_linked_queue.h\"\n#include \"util/aeron_error.h\"\n\nstatic void *aeron_blocking_linked_queue_retrieve(aeron_blocking_linked_queue_t *queue, bool block)\n{\n    void *element = NULL;\n\n    aeron_mutex_lock(&queue->mutex);\n\n    element = aeron_linked_queue_poll(&queue->queue);\n\n    if (NULL == element && block)\n    {\n        aeron_cond_wait(&queue->cv, &queue->mutex);\n\n        element = aeron_linked_queue_poll(&queue->queue);\n    }\n\n    aeron_mutex_unlock(&queue->mutex);\n\n    return element;\n}\n\nint aeron_blocking_linked_queue_init(aeron_blocking_linked_queue_t *queue)\n{\n    if (aeron_linked_queue_init(&queue->queue) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    aeron_mutex_init(&queue->mutex);\n    aeron_cond_init(&queue->cv, NULL);\n\n    return 0;\n}\n\nint aeron_blocking_linked_queue_close(aeron_blocking_linked_queue_t *queue)\n{\n    aeron_mutex_lock(&queue->mutex);\n\n    if (aeron_linked_queue_close(&queue->queue) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    aeron_cond_destroy(&queue->cv);\n    aeron_mutex_unlock(&queue->mutex);\n    aeron_mutex_destroy(&queue->mutex);\n\n    return 0;\nerror:\n    aeron_mutex_unlock(&queue->mutex);\n    return -1;\n}\n\nint aeron_blocking_linked_queue_offer(aeron_blocking_linked_queue_t *queue, void *element)\n{\n    int rc;\n\n    aeron_mutex_lock(&queue->mutex);\n\n    rc = aeron_linked_queue_offer(&queue->queue, element);\n\n    if (rc == 0)\n    {\n        aeron_cond_signal(&queue->cv);\n    }\n\n    aeron_mutex_unlock(&queue->mutex);\n\n    return rc;\n}\n\nvoid *aeron_blocking_linked_queue_poll(aeron_blocking_linked_queue_t *queue)\n{\n    return aeron_blocking_linked_queue_retrieve(queue, false);\n}\n\nvoid *aeron_blocking_linked_queue_take(aeron_blocking_linked_queue_t *queue)\n{\n    return aeron_blocking_linked_queue_retrieve(queue, true);\n}\n\nbool aeron_blocking_linked_queue_is_empty(aeron_blocking_linked_queue_t *queue)\n{\n    bool is_empty;\n\n    aeron_mutex_lock(&queue->mutex);\n\n    is_empty = aeron_linked_queue_is_empty(&queue->queue);\n\n    aeron_mutex_unlock(&queue->mutex);\n\n    return is_empty;\n}\n\nvoid aeron_blocking_linked_queue_unblock(aeron_blocking_linked_queue_t *queue)\n{\n    aeron_cond_signal(&queue->cv);\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_blocking_linked_queue.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_BLOCKING_LINKED_QUEUE_H\n#define AERON_BLOCKING_LINKED_QUEUE_H\n\n#include <stdbool.h>\n#include \"collections/aeron_linked_queue.h\"\n#include \"aeron_thread.h\"\n\ntypedef struct aeron_blocking_linked_queue_stct\n{\n    aeron_linked_queue_t queue;\n    aeron_mutex_t mutex;\n    aeron_cond_t cv;\n}\naeron_blocking_linked_queue_t;\n\nint aeron_blocking_linked_queue_init(aeron_blocking_linked_queue_t *queue);\n\nint aeron_blocking_linked_queue_close(aeron_blocking_linked_queue_t *queue);\n\nint aeron_blocking_linked_queue_offer(aeron_blocking_linked_queue_t *queue, void *element);\n\nvoid *aeron_blocking_linked_queue_poll(aeron_blocking_linked_queue_t *queue);\n\nvoid *aeron_blocking_linked_queue_take(aeron_blocking_linked_queue_t *queue);\n\nbool aeron_blocking_linked_queue_is_empty(aeron_blocking_linked_queue_t *queue);\n\nvoid aeron_blocking_linked_queue_unblock(aeron_blocking_linked_queue_t *queue);\n\n#endif //AERON_BLOCKING_LINKED_QUEUE_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_broadcast_descriptor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_BROADCAST_DESCRIPTOR_H\n#define AERON_BROADCAST_DESCRIPTOR_H\n\n#include \"util/aeron_bitutil.h\"\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_broadcast_descriptor_stct\n{\n    volatile int64_t tail_intent_counter;\n    volatile int64_t tail_counter;\n    volatile int64_t latest_counter;\n    uint8_t pad[(2 * AERON_CACHE_LINE_LENGTH) - (3 * sizeof(int64_t))];\n}\naeron_broadcast_descriptor_t;\n\ntypedef struct aeron_broadcast_record_descriptor_stct\n{\n    int32_t length;\n    int32_t msg_type_id;\n}\naeron_broadcast_record_descriptor_t;\n#pragma pack(pop)\n\n#define AERON_BROADCAST_BUFFER_TRAILER_LENGTH (sizeof(aeron_broadcast_descriptor_t))\n\n#define AERON_BROADCAST_IS_CAPACITY_VALID(capacity) AERON_IS_POWER_OF_TWO(capacity)\n#define AERON_BROADCAST_MAX_MESSAGE_LENGTH(capacity) ((capacity) / 8)\n#define AERON_BROADCAST_INVALID_MSG_TYPE_ID(id) (id < 1)\n#define AERON_BROADCAST_PADDING_MSG_TYPE_ID (-1)\n\n#define AERON_BROADCAST_RECORD_HEADER_LENGTH (sizeof(aeron_broadcast_record_descriptor_t))\n#define AERON_BROADCAST_RECORD_ALIGNMENT (sizeof(aeron_broadcast_record_descriptor_t))\n\n#endif //AERON_BROADCAST_DESCRIPTOR_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_broadcast_receiver.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string.h>\n#include <errno.h>\n#include <inttypes.h>\n#include \"aeron_alloc.h\"\n#include \"concurrent/aeron_broadcast_receiver.h\"\n#include \"util/aeron_error.h\"\n\nint aeron_broadcast_receiver_init(aeron_broadcast_receiver_t *receiver, void *buffer, size_t length)\n{\n    const size_t capacity = length - AERON_BROADCAST_BUFFER_TRAILER_LENGTH;\n    receiver->scratch_buffer = NULL;\n    receiver->scratch_buffer_capacity = 0;\n\n    if (AERON_BROADCAST_IS_CAPACITY_VALID(capacity))\n    {\n        size_t scratch_buffer_capacity = AERON_BROADCAST_SCRATCH_BUFFER_LENGTH_DEFAULT;\n        uint8_t *scratch_buffer;\n        if (aeron_alloc((void**)&scratch_buffer, scratch_buffer_capacity) < 0)\n        {\n            AERON_APPEND_ERR(\"failed to allocate scratch buffer of capacity: %\" PRIu64, scratch_buffer_capacity);\n            return -1;\n        }\n\n        receiver->scratch_buffer = scratch_buffer;\n        receiver->scratch_buffer_capacity = scratch_buffer_capacity;\n        receiver->buffer = buffer;\n        receiver->capacity = capacity;\n        receiver->mask = capacity - 1u;\n        receiver->descriptor = (aeron_broadcast_descriptor_t *)(receiver->buffer + receiver->capacity);\n\n        int64_t latest;\n        AERON_GET_ACQUIRE(latest, receiver->descriptor->latest_counter);\n\n        receiver->cursor = latest;\n        receiver->next_record = latest;\n        receiver->record_offset = (size_t)latest & receiver->mask;\n        receiver->lapped_count = 0;\n\n        return 0;\n    }\n    else\n    {\n        AERON_SET_ERR(EINVAL, \"Capacity: %\" PRIu64 \" invalid, must be power of two\", (uint64_t)capacity);\n        return -1;\n    }\n}\n\nint aeron_broadcast_receiver_close(aeron_broadcast_receiver_t *receiver)\n{\n    aeron_free(receiver->scratch_buffer);\n    receiver->scratch_buffer = NULL;\n    receiver->scratch_buffer_capacity = 0;\n    return 0;\n}\n\nint aeron_broadcast_receiver_receive(\n    aeron_broadcast_receiver_t *receiver, aeron_broadcast_receiver_handler_t handler, void *clientd)\n{\n    int messages_received = 0;\n    const long last_seen_lapped_count = receiver->lapped_count;\n\n    if (aeron_broadcast_receiver_receive_next(receiver))\n    {\n        if (last_seen_lapped_count != receiver->lapped_count)\n        {\n            AERON_SET_ERR(EINVAL, \"%s\", \"unable to keep up with broadcast\");\n            return -1;\n        }\n\n        aeron_broadcast_record_descriptor_t *record =\n            (aeron_broadcast_record_descriptor_t *)(receiver->buffer + receiver->record_offset);\n\n        const size_t length = (size_t)record->length - AERON_BROADCAST_RECORD_HEADER_LENGTH;\n\n        if (length > receiver->scratch_buffer_capacity)\n        {\n            size_t new_scratch_buffer_capacity = receiver->scratch_buffer_capacity;\n            while (new_scratch_buffer_capacity < length)\n            {\n                new_scratch_buffer_capacity += (new_scratch_buffer_capacity >> 1);\n            }\n\n            uint8_t *new_scratch_buffer;\n            if (aeron_alloc((void**)&new_scratch_buffer, new_scratch_buffer_capacity) < 0)\n            {\n                AERON_APPEND_ERR(\"failed to allocate scratch buffer of capacity: %\" PRIu64, new_scratch_buffer_capacity);\n                return -1;\n            }\n\n            aeron_free(receiver->scratch_buffer);\n            receiver->scratch_buffer = new_scratch_buffer;\n            receiver->scratch_buffer_capacity = new_scratch_buffer_capacity;\n        }\n\n        const int32_t type_id = record->msg_type_id;\n        memcpy(\n            (void *)receiver->scratch_buffer,\n            receiver->buffer + receiver->record_offset + AERON_BROADCAST_RECORD_HEADER_LENGTH,\n            length);\n\n        if (!aeron_broadcast_receiver_validate(receiver))\n        {\n            AERON_SET_ERR(EINVAL, \"%s\", \"unable to keep up with broadcast\");\n            return -1;\n        }\n\n        handler(type_id, (uint8_t *)receiver->scratch_buffer, length, clientd);\n\n        messages_received = 1;\n    }\n\n    return messages_received;\n}\n\nextern bool aeron_broadcast_receiver_validate(aeron_broadcast_receiver_t *receiver);\n\nextern bool aeron_broadcast_receiver_validate_at(aeron_broadcast_receiver_t *receiver, int64_t cursor);\n\nextern bool aeron_broadcast_receiver_receive_next(aeron_broadcast_receiver_t *receiver);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_broadcast_receiver.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_BROADCAST_RECEIVER_H\n#define AERON_BROADCAST_RECEIVER_H\n\n#include \"util/aeron_bitutil.h\"\n#include \"aeron_atomic.h\"\n#include \"aeron_broadcast_descriptor.h\"\n\n#define AERON_BROADCAST_SCRATCH_BUFFER_LENGTH_DEFAULT (4096u)\n\ntypedef struct aeron_broadcast_receiver_stct\n{\n    uint8_t *scratch_buffer;\n    uint8_t *buffer;\n    aeron_broadcast_descriptor_t *descriptor;\n    size_t capacity;\n    size_t mask;\n    size_t scratch_buffer_capacity;\n\n    size_t record_offset;\n    int64_t cursor;\n    int64_t next_record;\n    long lapped_count;\n}\naeron_broadcast_receiver_t;\n\ntypedef void (*aeron_broadcast_receiver_handler_t)(int32_t type_id, uint8_t *buffer, size_t length, void *clientd);\n\nint aeron_broadcast_receiver_init(aeron_broadcast_receiver_t *receiver, void *buffer, size_t length);\nint aeron_broadcast_receiver_close(aeron_broadcast_receiver_t *receiver);\n\ninline bool aeron_broadcast_receiver_validate_at(aeron_broadcast_receiver_t *receiver, int64_t cursor)\n{\n    int64_t tail_intent_counter;\n    AERON_GET_ACQUIRE(tail_intent_counter, receiver->descriptor->tail_intent_counter);\n\n    return (cursor + (int64_t)receiver->capacity) > tail_intent_counter;\n}\n\ninline bool aeron_broadcast_receiver_validate(aeron_broadcast_receiver_t *receiver)\n{\n    aeron_acquire();\n\n    return aeron_broadcast_receiver_validate_at(receiver, receiver->cursor);\n}\n\ninline bool aeron_broadcast_receiver_receive_next(aeron_broadcast_receiver_t *receiver)\n{\n    bool is_available = false;\n    int64_t tail;\n    int64_t cursor = receiver->next_record;\n\n    AERON_GET_ACQUIRE(tail, receiver->descriptor->tail_counter);\n\n    if (tail > cursor)\n    {\n        size_t record_offset = (uint32_t)cursor & (receiver->capacity - 1u);\n\n        if (!aeron_broadcast_receiver_validate_at(receiver, cursor))\n        {\n            receiver->lapped_count++;\n            AERON_GET_ACQUIRE(cursor, receiver->descriptor->latest_counter);\n            record_offset = (uint32_t)cursor & (receiver->capacity - 1u);\n        }\n\n        aeron_broadcast_record_descriptor_t *record =\n            (aeron_broadcast_record_descriptor_t *)(receiver->buffer + record_offset);\n\n        receiver->cursor = cursor;\n        receiver->next_record = cursor + AERON_ALIGN(record->length, AERON_BROADCAST_RECORD_ALIGNMENT);\n\n        if (AERON_BROADCAST_PADDING_MSG_TYPE_ID == record->msg_type_id)\n        {\n            aeron_broadcast_record_descriptor_t *new_record =\n                (aeron_broadcast_record_descriptor_t *)(receiver->buffer + 0);\n\n            record_offset = 0;\n            receiver->cursor = receiver->next_record;\n            receiver->next_record += AERON_ALIGN(new_record->length, AERON_BROADCAST_RECORD_ALIGNMENT);\n        }\n\n        receiver->record_offset = record_offset;\n        is_available = true;\n    }\n\n    return is_available;\n}\n\nint aeron_broadcast_receiver_receive(\n    aeron_broadcast_receiver_t *receiver, aeron_broadcast_receiver_handler_t handler, void *clientd);\n\n#endif //AERON_BROADCAST_RECEIVER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_broadcast_transmitter.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string.h>\n#include <errno.h>\n#include <inttypes.h>\n#include \"concurrent/aeron_broadcast_transmitter.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_error.h\"\n\nint aeron_broadcast_transmitter_init(aeron_broadcast_transmitter_t *transmitter, void *buffer, size_t length)\n{\n    const size_t capacity = length - AERON_BROADCAST_BUFFER_TRAILER_LENGTH;\n    int result = -1;\n\n    if (AERON_BROADCAST_IS_CAPACITY_VALID(capacity))\n    {\n        transmitter->buffer = buffer;\n        transmitter->capacity = capacity;\n        transmitter->descriptor = (aeron_broadcast_descriptor_t *) (transmitter->buffer + transmitter->capacity);\n        transmitter->max_message_length = AERON_BROADCAST_MAX_MESSAGE_LENGTH(transmitter->capacity);\n        result = 0;\n    }\n    else\n    {\n        AERON_SET_ERR(EINVAL, \"Capacity: %\" PRIu64 \" invalid, must be power of two\", (uint64_t)capacity);\n    }\n\n    return result;\n}\n\ninline static void signal_tail_intent(aeron_broadcast_descriptor_t *descriptor, int64_t new_tail)\n{\n    AERON_SET_RELEASE(descriptor->tail_intent_counter, new_tail);\n    aeron_release();  /* storeFence */\n}\n\ninline static void insert_padding_record(aeron_broadcast_record_descriptor_t *record, int32_t length)\n{\n    record->msg_type_id = AERON_BROADCAST_PADDING_MSG_TYPE_ID;\n    record->length = length;\n}\n\nint aeron_broadcast_transmitter_transmit(\n    aeron_broadcast_transmitter_t *transmitter, int32_t msg_type_id, const void *msg, size_t length)\n{\n    if (length > transmitter->max_message_length || AERON_BROADCAST_INVALID_MSG_TYPE_ID(msg_type_id))\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"length (%\" PRIu64 \") > transmitter->max_message_length (%\" PRIu64 \") || msg_type_id (%\" PRId32 \") < 1\",\n            (uint64_t)length,\n            (uint64_t)transmitter->max_message_length,\n            msg_type_id);\n        return -1;\n    }\n\n    int64_t current_tail = transmitter->descriptor->tail_counter;\n    size_t record_offset = (uint32_t)current_tail & (transmitter->capacity - 1);\n    const size_t record_length = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    const size_t aligned_record_length = AERON_ALIGN(record_length, AERON_BROADCAST_RECORD_ALIGNMENT);\n    const int64_t new_tail = current_tail + (int64_t)aligned_record_length;\n\n    const size_t to_end_of_buffer = (uint32_t)transmitter->capacity - record_offset;\n\n    if (to_end_of_buffer < aligned_record_length)\n    {\n        signal_tail_intent(transmitter->descriptor, new_tail + (int64_t)to_end_of_buffer);\n        insert_padding_record(\n            (aeron_broadcast_record_descriptor_t *)(transmitter->buffer + record_offset), (int32_t)to_end_of_buffer);\n\n        current_tail += (int64_t)to_end_of_buffer;\n        record_offset = 0;\n    }\n    else\n    {\n        signal_tail_intent(transmitter->descriptor, new_tail);\n    }\n\n    aeron_broadcast_record_descriptor_t *record =\n        (aeron_broadcast_record_descriptor_t *)(transmitter->buffer + record_offset);\n\n    record->length = (int32_t)record_length;\n    record->msg_type_id = msg_type_id;\n\n    memcpy(transmitter->buffer + record_offset + AERON_BROADCAST_RECORD_HEADER_LENGTH, msg, length);\n\n    AERON_SET_RELEASE(transmitter->descriptor->latest_counter, current_tail);\n    AERON_SET_RELEASE(transmitter->descriptor->tail_counter, current_tail + aligned_record_length);\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_broadcast_transmitter.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_BROADCAST_TRANSMITTER_H\n#define AERON_BROADCAST_TRANSMITTER_H\n\n#include \"util/aeron_bitutil.h\"\n#include \"aeron_broadcast_descriptor.h\"\n\ntypedef struct aeron_broadcast_transmitter_stct\n{\n    uint8_t *buffer;\n    aeron_broadcast_descriptor_t *descriptor;\n    size_t capacity;\n    size_t max_message_length;\n}\naeron_broadcast_transmitter_t;\n\nint aeron_broadcast_transmitter_init(aeron_broadcast_transmitter_t *transmitter, void *buffer, size_t length);\n\nint aeron_broadcast_transmitter_transmit(\n    aeron_broadcast_transmitter_t *transmitter, int32_t msg_type_id, const void *msg, size_t length);\n\n#endif //AERON_BROADCAST_TRANSMITTER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_concurrent_array_queue.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONCURRENT_ARRAY_QUEUE_H\n#define AERON_CONCURRENT_ARRAY_QUEUE_H\n\ntypedef enum aeron_queue_offer_result_stct\n{\n    AERON_OFFER_SUCCESS = 0,\n    AERON_OFFER_ERROR = -2,\n    AERON_OFFER_FULL = -1\n}\naeron_queue_offer_result_t;\n\ntypedef void (*aeron_queue_drain_func_t)(void *clientd, void *item);\n\n#endif //AERON_CONCURRENT_ARRAY_QUEUE_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_counters_manager.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string.h>\n#include <math.h>\n#include <errno.h>\n#include <inttypes.h>\n\n#include \"aeron_alloc.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"util/aeron_error.h\"\n\n_Static_assert(\n    sizeof(aeron_counters_manager_t) > sizeof(aeron_counters_reader_t),\n    \"sizeof(aeron_counters_manager_t) must be greater than sizeof(aeron_counters_reader_t)\");\n_Static_assert(\n    offsetof(aeron_counters_manager_t, values) == offsetof(aeron_counters_reader_t, values),\n    \"offsetof(aeron_counters_manager_t, values) must match offsetof(aeron_counters_reader_t, values)\");\n_Static_assert(\n    offsetof(aeron_counters_manager_t, metadata) == offsetof(aeron_counters_reader_t, metadata),\n    \"offsetof(aeron_counters_manager_t, metadata) must match offsetof(aeron_counters_reader_t, metadata)\");\n_Static_assert(\n    offsetof(aeron_counters_manager_t, values_length) == offsetof(aeron_counters_reader_t, values_length),\n    \"offsetof(aeron_counters_manager_t, values_length) must match offsetof(aeron_counters_reader_t, values_length)\");\n_Static_assert(\n    offsetof(aeron_counters_manager_t, metadata_length) == offsetof(aeron_counters_reader_t, metadata_length),\n    \"offsetof(aeron_counters_manager_t, metadata_length) must match offsetof(aeron_counters_reader_t, metadata_length)\");\n_Static_assert(\n    offsetof(aeron_counters_manager_t, max_counter_id) == offsetof(aeron_counters_reader_t, max_counter_id),\n    \"offsetof(aeron_counters_manager_t, max_counter_id) must match offsetof(aeron_counters_reader_t, max_counter_id)\");\n\nint aeron_counters_manager_init(\n    aeron_counters_manager_t *manager,\n    uint8_t *metadata_buffer,\n    size_t metadata_length,\n    uint8_t *values_buffer,\n    size_t values_length,\n    aeron_clock_cache_t *cached_clock,\n    int64_t free_to_reuse_timeout_ms)\n{\n    int result = -1;\n\n    if (AERON_COUNTERS_MANAGER_IS_BUFFER_LENGTHS_VALID(metadata_length, values_length))\n    {\n        manager->metadata = metadata_buffer;\n        manager->metadata_length = metadata_length;\n        manager->values = values_buffer;\n        manager->values_length = values_length;\n        manager->max_counter_id = (int32_t)((values_length / AERON_COUNTERS_MANAGER_VALUE_LENGTH) - 1);\n        manager->id_high_water_mark = -1;\n        manager->free_list_index = -1;\n        manager->free_list_length = 2;\n        manager->cached_clock = cached_clock;\n        manager->free_to_reuse_timeout_ms = free_to_reuse_timeout_ms;\n        result = aeron_alloc((void **)&manager->free_list, sizeof(int32_t) * manager->free_list_length);\n    }\n    else\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Counter buffer lengths invalid, metadata_length: %\" PRIu64 \", values_length: %\" PRIu64,\n            (uint64_t)metadata_length,\n            (uint64_t)values_length);\n    }\n\n    return result;\n}\n\nvoid aeron_counters_manager_close(aeron_counters_manager_t *manager)\n{\n    aeron_free(manager->free_list);\n}\n\nint32_t aeron_counters_manager_allocate(\n    aeron_counters_manager_t *manager,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const char *label,\n    size_t label_length)\n{\n    const int32_t counter_id = aeron_counters_manager_next_counter_id(manager);\n    if (counter_id < 0)\n    {\n        AERON_APPEND_ERR(\n            \"Unable to allocate counter: type: %\" PRId32 \", label: %.*s\",\n            type_id,\n            (int)label_length,\n            label);\n        return -1;\n    }\n\n    aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)\n        (manager->metadata + (counter_id * AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n\n    metadata->type_id = type_id;\n    metadata->free_for_reuse_deadline_ms = AERON_COUNTER_NOT_FREE_TO_REUSE;\n\n    if (NULL != key && key_length > 0)\n    {\n        memcpy(metadata->key, key, (size_t)fmin((double)sizeof(metadata->key), (double)key_length));\n    }\n\n    size_t length = (size_t)fmin((double)sizeof(metadata->label), (double)label_length);\n\n    memcpy(metadata->label, label, length);\n    metadata->label_length = (int32_t)length;\n    AERON_SET_RELEASE(metadata->state, AERON_COUNTER_RECORD_ALLOCATED);\n\n    return counter_id;\n}\n\nint aeron_counters_reader_get_buffers(aeron_counters_reader_t *reader, aeron_counters_reader_buffers_t *buffers)\n{\n    buffers->values = reader->values;\n    buffers->metadata = reader->metadata;\n    buffers->values_length = reader->values_length;\n    buffers->metadata_length = reader->metadata_length;\n\n    return 0;\n}\n\nint32_t aeron_counters_reader_max_counter_id(aeron_counters_reader_t *reader)\n{\n    return reader->max_counter_id;\n}\n\nvoid aeron_counters_manager_counter_registration_id(\n    aeron_counters_manager_t *manager, int32_t counter_id, int64_t registration_id)\n{\n    aeron_counter_value_descriptor_t *value_descriptor = (aeron_counter_value_descriptor_t *)(\n        manager->values + AERON_COUNTER_OFFSET(counter_id));\n\n    AERON_SET_RELEASE(value_descriptor->registration_id, registration_id);\n}\n\nvoid aeron_counters_manager_counter_owner_id(\n    aeron_counters_manager_t *manager, int32_t counter_id, int64_t owner_id)\n{\n    aeron_counter_value_descriptor_t *value_descriptor = (aeron_counter_value_descriptor_t *)(\n        manager->values + AERON_COUNTER_OFFSET(counter_id));\n\n    value_descriptor->owner_id = owner_id;\n}\n\nvoid aeron_counters_manager_counter_reference_id(\n    aeron_counters_manager_t *manager, int32_t counter_id, int64_t reference_id)\n{\n    aeron_counter_value_descriptor_t *value_descriptor = (aeron_counter_value_descriptor_t *)(\n        manager->values + AERON_COUNTER_OFFSET(counter_id));\n\n    value_descriptor->reference_id = reference_id;\n}\n\nvoid aeron_counters_manager_update_label(\n    aeron_counters_manager_t *manager, int32_t counter_id, size_t label_length, const char *label)\n{\n    aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)\n        (manager->metadata + (counter_id * AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n\n    size_t length = AERON_MIN(sizeof(metadata->label), label_length);\n\n    memcpy(metadata->label, label, length);\n    AERON_SET_RELEASE(metadata->label_length, (int32_t)length);\n}\n\nvoid aeron_counters_manager_append_to_label(\n    aeron_counters_manager_t *manager, int32_t counter_id, size_t length, const char *value)\n{\n    aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)\n        (manager->metadata + (counter_id * AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n\n    int32_t label_length;\n    AERON_GET_ACQUIRE(label_length, metadata->label_length);\n\n    size_t current_length = (size_t)label_length;\n    size_t available_length = sizeof(metadata->label) - current_length;\n    size_t copy_length = AERON_MIN(length, available_length);\n\n    memcpy(metadata->label + current_length, value, copy_length);\n    AERON_SET_RELEASE(metadata->label_length, (int32_t)(current_length + copy_length));\n}\n\nvoid aeron_counters_manager_remove_free_list_index(aeron_counters_manager_t *manager, int index)\n{\n    for (int i = index; i < manager->free_list_index; i++)\n    {\n        manager->free_list[i] = manager->free_list[i + 1];\n    }\n\n    manager->free_list_index--;\n}\n\nint32_t aeron_counters_manager_next_counter_id(aeron_counters_manager_t *manager)\n{\n    if (manager->free_list_index > -1)\n    {\n        int64_t now_ms = aeron_clock_cached_epoch_time(manager->cached_clock);\n\n        for (int i = 0; i <= manager->free_list_index; i++)\n        {\n            int32_t counter_id = manager->free_list[i];\n            aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)\n                (manager->metadata + (counter_id * AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n\n            int64_t deadline_ms;\n            AERON_GET_ACQUIRE(deadline_ms, metadata->free_for_reuse_deadline_ms);\n\n            if (now_ms >= deadline_ms)\n            {\n                aeron_counters_manager_remove_free_list_index(manager, i);\n                aeron_counter_value_descriptor_t *value = (aeron_counter_value_descriptor_t *)\n                    (manager->values + (counter_id * AERON_COUNTERS_MANAGER_VALUE_LENGTH));\n                AERON_SET_RELEASE(value->registration_id, AERON_COUNTER_REGISTRATION_ID_DEFAULT);\n                value->owner_id = AERON_COUNTER_OWNER_ID_DEFAULT;\n                value->reference_id = AERON_COUNTER_REFERENCE_ID_DEFAULT;\n                AERON_SET_RELEASE(value->counter_value, INT64_C(0));\n\n                return counter_id;\n            }\n        }\n    }\n\n    if ((manager->id_high_water_mark + 1) > manager->max_counter_id)\n    {\n        AERON_SET_ERR(ENOMEM, \"Max counter id (%\" PRId32 \") exceeded\", manager->max_counter_id);\n        return -1;\n    }\n\n    return ++manager->id_high_water_mark;\n}\n\nint aeron_counters_manager_free(aeron_counters_manager_t *manager, int32_t counter_id)\n{\n    if (counter_id < 0 || counter_id > manager->max_counter_id)\n    {\n        return -1;\n    }\n\n    aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)\n        (manager->metadata + (counter_id * AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n\n    if (AERON_COUNTER_RECORD_ALLOCATED != metadata->state)\n    {\n        return -1;\n    }\n\n    AERON_SET_RELEASE(metadata->state, AERON_COUNTER_RECORD_RECLAIMED);\n    memset(metadata->key, 0, sizeof(metadata->key));\n    metadata->free_for_reuse_deadline_ms =\n        aeron_clock_cached_epoch_time(manager->cached_clock) + manager->free_to_reuse_timeout_ms;\n\n    if ((manager->free_list_index + 1) >= (int32_t)manager->free_list_length)\n    {\n        size_t new_length = manager->free_list_length + (manager->free_list_length >> 1u);\n\n        if (aeron_reallocf((void **)&manager->free_list, sizeof(int32_t) * new_length) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        manager->free_list_length = new_length;\n    }\n\n    manager->free_list[++manager->free_list_index] = counter_id;\n\n    return 0;\n}\n\nvoid aeron_counters_reader_foreach_metadata(\n    uint8_t *metadata_buffer,\n    size_t metadata_length,\n    aeron_counters_reader_foreach_metadata_func_t func,\n    void *clientd)\n{\n    int32_t id = 0;\n\n    for (size_t i = 0; i < metadata_length; i += AERON_COUNTERS_MANAGER_METADATA_LENGTH)\n    {\n        aeron_counter_metadata_descriptor_t *record = (aeron_counter_metadata_descriptor_t *)(metadata_buffer + i);\n        int32_t record_state;\n\n        AERON_GET_ACQUIRE(record_state, record->state);\n\n        if (AERON_COUNTER_RECORD_ALLOCATED == record_state)\n        {\n            int32_t label_length;\n\n            AERON_GET_ACQUIRE(label_length, record->label_length);\n\n            func(\n                id,\n                record->type_id,\n                record->key,\n                sizeof(record->key),\n                record->label,\n                (size_t)label_length,\n                clientd);\n        }\n        else if (AERON_COUNTER_RECORD_UNUSED == record_state)\n        {\n            break;\n        }\n\n        id++;\n    }\n}\n\nvoid aeron_counters_reader_foreach_counter(\n    aeron_counters_reader_t *counters_reader, aeron_counters_reader_foreach_counter_func_t func, void *clientd)\n{\n    int32_t id = 0;\n\n    for (size_t i = 0; i < counters_reader->metadata_length; i += AERON_COUNTERS_MANAGER_METADATA_LENGTH)\n    {\n        aeron_counter_metadata_descriptor_t *record = (aeron_counter_metadata_descriptor_t *)(\n            counters_reader->metadata + i);\n        int32_t record_state;\n\n        AERON_GET_ACQUIRE(record_state, record->state);\n\n        if (AERON_COUNTER_RECORD_ALLOCATED == record_state)\n        {\n            int64_t *value_addr = (int64_t *)(counters_reader->values + AERON_COUNTER_OFFSET(id));\n            int32_t label_length;\n\n            AERON_GET_ACQUIRE(label_length, record->label_length);\n\n            func(\n                aeron_counter_get_acquire(value_addr),\n                id,\n                record->type_id,\n                (const uint8_t *)record->key,\n                sizeof(record->key),\n                (const char *)record->label,\n                (size_t)label_length,\n                clientd);\n        }\n        else if (AERON_COUNTER_RECORD_UNUSED == record_state)\n        {\n            break;\n        }\n\n        id++;\n    }\n}\n\nint32_t aeron_counters_reader_find_by_type_id_and_registration_id(\n    aeron_counters_reader_t *counters_reader,\n    int32_t type_id,\n    int64_t registration_id)\n{\n    int32_t counter_id = 0;\n    for (size_t i = 0; i < counters_reader->metadata_length; i += AERON_COUNTERS_MANAGER_METADATA_LENGTH)\n    {\n        aeron_counter_metadata_descriptor_t *record = (aeron_counter_metadata_descriptor_t *)(counters_reader->metadata + i);\n        int32_t record_state;\n\n        AERON_GET_ACQUIRE(record_state, record->state);\n\n        if (AERON_COUNTER_RECORD_ALLOCATED == record_state)\n        {\n            if (type_id == record->type_id)\n            {\n                int64_t counter_registration_id;\n                aeron_counter_value_descriptor_t *value_descriptor = (aeron_counter_value_descriptor_t *)(\n                    counters_reader->values + AERON_COUNTER_OFFSET(counter_id));\n\n                AERON_GET_ACQUIRE(counter_registration_id, value_descriptor->registration_id);\n\n                if (registration_id == counter_registration_id)\n                {\n                    return counter_id;\n                }\n            }\n        }\n        else if (AERON_COUNTER_RECORD_UNUSED == record_state)\n        {\n            break;\n        }\n\n        counter_id++;\n    }\n\n    return AERON_NULL_COUNTER_ID;\n}\n\nint64_t *aeron_counters_reader_addr(aeron_counters_reader_t *counters_reader, int32_t counter_id)\n{\n    if (counter_id < 0)\n    {\n        return NULL;\n    }\n\n    return (int64_t *)(counters_reader->values + AERON_COUNTER_OFFSET(counter_id));\n}\n\nint aeron_counters_reader_counter_registration_id(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t *registration_id)\n{\n    if (counter_id < 0 || counter_id > counters_reader->max_counter_id)\n    {\n        return -1;\n    }\n\n    aeron_counter_value_descriptor_t *value_descriptor = (aeron_counter_value_descriptor_t *)(\n        counters_reader->values + AERON_COUNTER_OFFSET(counter_id));\n\n    AERON_GET_ACQUIRE(*registration_id, value_descriptor->registration_id);\n\n    return 0;\n}\n\nint aeron_counters_reader_counter_owner_id(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t *owner_id)\n{\n    if (counter_id < 0 || counter_id > counters_reader->max_counter_id)\n    {\n        return -1;\n    }\n\n    aeron_counter_value_descriptor_t *value_descriptor = (aeron_counter_value_descriptor_t *)(\n        counters_reader->values + AERON_COUNTER_OFFSET(counter_id));\n\n    *owner_id = value_descriptor->owner_id;\n\n    return 0;\n}\n\nint aeron_counters_reader_counter_reference_id(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t *reference_id)\n{\n    if (counter_id < 0 || counter_id > counters_reader->max_counter_id)\n    {\n        return -1;\n    }\n\n    aeron_counter_value_descriptor_t *value_descriptor = (aeron_counter_value_descriptor_t *)(\n        counters_reader->values + AERON_COUNTER_OFFSET(counter_id));\n\n    *reference_id = value_descriptor->reference_id;\n\n    return 0;\n}\n\nint aeron_counters_reader_counter_state(aeron_counters_reader_t *counters_reader, int32_t counter_id, int32_t *state)\n{\n    if (counter_id < 0 || counter_id > counters_reader->max_counter_id)\n    {\n        return -1;\n    }\n\n    aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)(\n        counters_reader->metadata + AERON_COUNTER_METADATA_OFFSET(counter_id));\n\n    AERON_GET_ACQUIRE(*state, metadata->state);\n\n    return 0;\n}\n\nint aeron_counters_reader_counter_type_id(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int32_t *type_id)\n{\n    if (counter_id < 0 || counter_id > counters_reader->max_counter_id)\n    {\n        return -1;\n    }\n\n    aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)(\n        counters_reader->metadata + AERON_COUNTER_METADATA_OFFSET(counter_id));\n\n    *type_id = metadata->type_id;\n\n    return 0;\n}\n\nint aeron_counters_reader_metadata_key(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, uint8_t **key_p)\n{\n    if (NULL == key_p || counter_id < 0 || counter_id > counters_reader->max_counter_id)\n    {\n        return -1;\n    }\n\n    *key_p = counters_reader->metadata + AERON_COUNTER_METADATA_OFFSET(counter_id) + AERON_COUNTER_KEY_OFFSET;\n\n    return 0;\n}\n\nint aeron_counters_reader_counter_label(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, char *buffer, size_t buffer_length)\n{\n    if (counter_id < 0 || counter_id > counters_reader->max_counter_id)\n    {\n        return -1;\n    }\n\n    aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)(\n        counters_reader->metadata + AERON_COUNTER_METADATA_OFFSET(counter_id));\n\n    int32_t label_length;\n    AERON_GET_ACQUIRE(label_length, metadata->label_length);\n\n    size_t copy_length = (size_t)label_length < buffer_length ? (size_t)label_length : buffer_length;\n    memcpy(buffer, metadata->label, copy_length);\n\n    return (int)copy_length;\n}\n\nint aeron_counters_reader_free_for_reuse_deadline_ms(\n    aeron_counters_reader_t *counters_reader, int32_t counter_id, int64_t *deadline_ms)\n{\n    if (counter_id < 0 || counter_id > counters_reader->max_counter_id)\n    {\n        return -1;\n    }\n\n    aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)(\n        counters_reader->metadata + AERON_COUNTER_METADATA_OFFSET(counter_id));\n\n    *deadline_ms = metadata->free_for_reuse_deadline_ms;\n\n    return 0;\n}\n\nextern int aeron_counters_reader_init(\n    aeron_counters_reader_t *reader,\n    uint8_t *metadata_buffer,\n    size_t metadata_length,\n    uint8_t *values_buffer,\n    size_t values_length);\n\nextern int64_t *aeron_counters_manager_addr(aeron_counters_manager_t *counters_manager, int32_t counter_id);\n\nextern void aeron_counter_set_release(volatile int64_t *addr, int64_t value);\n\nextern int64_t aeron_counter_get_plain(volatile int64_t *addr);\nextern int64_t aeron_counter_get_acquire(volatile int64_t *addr);\n\nextern int64_t aeron_counter_increment(volatile int64_t *addr);\nextern int64_t aeron_counter_increment_release(volatile int64_t *addr);\nextern int64_t aeron_counter_increment_plain(volatile int64_t *addr);\n\nextern int64_t aeron_counter_get_and_add(volatile int64_t *addr, int64_t value);\nextern int64_t aeron_counter_get_and_add_release(volatile int64_t *addr, int64_t value);\nextern int64_t aeron_counter_get_and_add_plain(volatile int64_t *addr, int64_t value);\n\nextern bool aeron_counter_propose_max_release(volatile int64_t *addr, int64_t proposed_value);\nextern bool aeron_counter_propose_max_plain(volatile int64_t *addr, int64_t proposed_value);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_counters_manager.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_COUNTERS_MANAGER_H\n#define AERON_COUNTERS_MANAGER_H\n\n#include \"aeronc.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_clock.h\"\n#include \"aeron_atomic.h\"\n\n#define AERON_COUNTERS_MANAGER_VALUE_LENGTH (sizeof(aeron_counter_value_descriptor_t))\n#define AERON_COUNTERS_MANAGER_METADATA_LENGTH (sizeof(aeron_counter_metadata_descriptor_t))\n\n#define AERON_COUNTERS_METADATA_BUFFER_LENGTH(v) \\\n((v) * (AERON_COUNTERS_MANAGER_METADATA_LENGTH / AERON_COUNTERS_MANAGER_VALUE_LENGTH))\n\n#define AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_INITIALIZING (0)\n#define AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_ERRORED (-1)\n#define AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_NO_ID_ALLOCATED (-1)\n#define AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_ACTIVE INT64_C(1)\n#define AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_CLOSING INT64_C(2)\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_stream_position_counter_key_layout_stct\n{\n    int64_t registration_id;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t channel_length;\n    char channel[sizeof(((aeron_counter_metadata_descriptor_t *)0)->key) - (sizeof(int64_t) + 3 * sizeof(int32_t))];\n}\naeron_stream_position_counter_key_layout_t;\n\ntypedef struct aeron_channel_endpoint_status_key_layout_stct\n{\n    int32_t channel_length;\n    char channel[sizeof(((aeron_counter_metadata_descriptor_t *)0)->key) - sizeof(int32_t)];\n}\naeron_channel_endpoint_status_key_layout_t;\n\ntypedef struct aeron_heartbeat_timestamp_key_layout_stct\n{\n    int64_t registration_id;\n}\naeron_heartbeat_timestamp_key_layout_t;\n\ntypedef struct aeron_local_sockaddr_key_layout_stct\n{\n    int32_t channel_status_id;\n    int32_t local_sockaddr_len;\n    char local_sockaddr[sizeof(((aeron_counter_metadata_descriptor_t *)0)->key) - (2 * sizeof(int32_t))];\n}\naeron_local_sockaddr_key_layout_t;\n\n#pragma pack(pop)\n\ntypedef struct aeron_counters_manager_stct\n{\n    uint8_t *values;\n    uint8_t *metadata;\n    size_t values_length;\n    size_t metadata_length;\n\n    int32_t max_counter_id;\n    int32_t id_high_water_mark;\n    int32_t *free_list;\n    int32_t free_list_index;\n    size_t free_list_length;\n\n    aeron_clock_cache_t *cached_clock;\n    int64_t free_to_reuse_timeout_ms;\n}\naeron_counters_manager_t;\n\ntypedef struct aeron_counters_reader_stct\n{\n    uint8_t *values;\n    uint8_t *metadata;\n    size_t values_length;\n    size_t metadata_length;\n    int32_t max_counter_id;\n}\naeron_counters_reader_t;\n\n#define AERON_COUNTERS_MANAGER_IS_BUFFER_LENGTHS_VALID(metadata, values) \\\n    ((metadata) >= ((values) * (AERON_COUNTERS_MANAGER_METADATA_LENGTH / AERON_COUNTERS_MANAGER_VALUE_LENGTH)))\n\nint aeron_counters_manager_init(\n    aeron_counters_manager_t *manager,\n    uint8_t *metadata_buffer,\n    size_t metadata_length,\n    uint8_t *values_buffer,\n    size_t values_length,\n    aeron_clock_cache_t *cached_clock,\n    int64_t free_to_reuse_timeout_ms);\n\nvoid aeron_counters_manager_close(aeron_counters_manager_t *manager);\n\nint32_t aeron_counters_manager_allocate(\n    aeron_counters_manager_t *manager,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const char *label,\n    size_t label_length);\n\nvoid aeron_counters_manager_counter_registration_id(\n    aeron_counters_manager_t *manager, int32_t counter_id, int64_t registration_id);\n\nvoid aeron_counters_manager_counter_owner_id(\n    aeron_counters_manager_t *manager, int32_t counter_id, int64_t owner_id);\n\nvoid aeron_counters_manager_counter_reference_id(\n    aeron_counters_manager_t *manager, int32_t counter_id, int64_t reference_id);\n\nvoid aeron_counters_manager_update_label(\n    aeron_counters_manager_t *manager, int32_t counter_id, size_t label_length, const char *label);\n\nvoid aeron_counters_manager_append_to_label(\n    aeron_counters_manager_t *manager, int32_t counter_id, size_t length, const char *value);\n\nint32_t aeron_counters_manager_next_counter_id(aeron_counters_manager_t *manager);\n\nint aeron_counters_manager_free(aeron_counters_manager_t *manager, int32_t counter_id);\n\ntypedef void (*aeron_counters_reader_foreach_metadata_func_t)(\n    int32_t id,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const uint8_t *label,\n    size_t label_length,\n    void *clientd);\n\nvoid aeron_counters_reader_foreach_metadata(\n    uint8_t *metadata_buffer,\n    size_t metadata_length,\n    aeron_counters_reader_foreach_metadata_func_t func,\n    void *clientd);\n\ninline int64_t *aeron_counters_manager_addr(aeron_counters_manager_t *counters_manager, int32_t counter_id)\n{\n    return (int64_t *)(counters_manager->values + AERON_COUNTER_OFFSET(counter_id));\n}\n\ninline int aeron_counters_reader_init(\n    aeron_counters_reader_t *reader,\n    uint8_t *metadata_buffer,\n    size_t metadata_length,\n    uint8_t *values_buffer,\n    size_t values_length)\n{\n    reader->metadata = metadata_buffer;\n    reader->metadata_length = metadata_length;\n    reader->values = values_buffer;\n    reader->values_length = values_length;\n    reader->max_counter_id = (int32_t)((values_length / AERON_COUNTERS_MANAGER_VALUE_LENGTH) - 1);\n\n    return 0;\n}\n\ninline void aeron_counter_set_release(volatile int64_t *addr, int64_t value)\n{\n    AERON_SET_RELEASE(*addr, value);\n}\n\ninline int64_t aeron_counter_get_plain(volatile int64_t *addr)\n{\n    return *addr;\n}\n\ninline int64_t aeron_counter_get_acquire(volatile int64_t *addr)\n{\n    int64_t value;\n    AERON_GET_ACQUIRE(value, *addr);\n    return value;\n}\n\ninline int64_t aeron_counter_increment(volatile int64_t *addr)\n{\n    int64_t result;\n    AERON_GET_AND_ADD_INT64(result, *addr, 1);\n    return result;\n}\n\ninline int64_t aeron_counter_increment_release(volatile int64_t *addr)\n{\n    int64_t current_value = *addr;\n    AERON_SET_RELEASE(*addr, current_value + 1);\n    return current_value;\n}\n\ninline int64_t aeron_counter_increment_plain(volatile int64_t *addr)\n{\n    int64_t current_value = *addr;\n    *addr = current_value + 1;\n    return current_value;\n}\n\ninline int64_t aeron_counter_get_and_add(volatile int64_t *addr, int64_t value)\n{\n    int64_t result;\n    AERON_GET_AND_ADD_INT64(result, *addr, value);\n    return result;\n}\n\ninline int64_t aeron_counter_get_and_add_release(volatile int64_t *addr, int64_t value)\n{\n    int64_t current = *addr;\n    AERON_SET_RELEASE(*addr, current + value);\n    return current;\n}\n\ninline int64_t aeron_counter_get_and_add_plain(volatile int64_t *addr, int64_t value)\n{\n    int64_t current = *addr;\n    *addr = current + value;\n    return current;\n}\n\ninline bool aeron_counter_propose_max_release(volatile int64_t *addr, int64_t proposed_value)\n{\n    bool updated = false;\n\n    if (*addr < proposed_value)\n    {\n        AERON_SET_RELEASE(*addr, proposed_value);\n        updated = true;\n    }\n\n    return updated;\n}\n\ninline bool aeron_counter_propose_max_plain(volatile int64_t *addr, int64_t proposed_value)\n{\n    bool updated = false;\n\n    if (*addr < proposed_value)\n    {\n        *addr = proposed_value;\n        updated = true;\n    }\n\n    return updated;\n}\n\n#endif //AERON_COUNTERS_MANAGER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_distinct_error_log.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <string.h>\n#include <stdio.h>\n#include <errno.h>\n#include \"util/aeron_error.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_distinct_error_log.h\"\n\nint aeron_distinct_error_log_init(\n    aeron_distinct_error_log_t *log, uint8_t *buffer, size_t buffer_size, aeron_clock_func_t clock)\n{\n    if (NULL == log || NULL == clock)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Parameters can not be null, log: %s, clock: %s\",\n            NULL == log ? \"NULL\" : \"OK\",\n            NULL == clock ? \"NULL\" : \"OK\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&log->observation_list, sizeof(aeron_distinct_error_log_observation_list_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate distinct error log\");\n        return -1;\n    }\n\n    log->buffer = buffer;\n    log->buffer_capacity = buffer_size;\n    log->clock = clock;\n    log->next_offset = 0;\n    log->observation_list->num_observations = 0;\n    log->observation_list->observations = NULL;\n    aeron_mutex_init(&log->mutex);\n\n    return 0;\n}\n\nvoid aeron_distinct_error_log_close(aeron_distinct_error_log_t *log)\n{\n    aeron_mutex_lock(&log->mutex);\n\n    aeron_distinct_error_log_observation_list_t *list = log->observation_list;\n    aeron_distinct_observation_t *observations = list->observations;\n    size_t num_observations = (size_t)list->num_observations;\n\n    for (size_t i = 0; i < num_observations; i++)\n    {\n        aeron_free((void *)observations[i].description);\n    }\n\n    aeron_free((void *)log->observation_list);\n\n    aeron_mutex_unlock(&log->mutex);\n    aeron_mutex_destroy(&log->mutex);\n}\n\nstatic aeron_distinct_observation_t *aeron_distinct_error_log_find_observation(\n    aeron_distinct_observation_t *observations, size_t num_observations, int error_code, const char *description)\n{\n    for (size_t i = 0; i < num_observations; i++)\n    {\n        if (observations[i].error_code == error_code &&\n            strncmp(observations[i].description, description, observations[i].description_length) == 0)\n        {\n            return &observations[i];\n        }\n    }\n\n    return NULL;\n}\n\nstatic int aeron_distinct_error_log_observation_list_alloc(\n    aeron_distinct_error_log_observation_list_t **list, uint64_t num_observations)\n{\n    *list = NULL;\n    size_t alloc_length =\n        sizeof(aeron_distinct_error_log_observation_list_t) +\n        ((size_t)num_observations * sizeof(aeron_distinct_observation_t));\n\n    int result = aeron_alloc((void **)list, alloc_length);\n    if (result >= 0)\n    {\n        (*list)->observations =\n            (aeron_distinct_observation_t *)((uint8_t *)*list + sizeof(aeron_distinct_error_log_observation_list_t));\n        (*list)->num_observations = num_observations;\n    }\n\n    return result;\n}\n\nstatic aeron_distinct_observation_t *aeron_distinct_error_log_new_observation(\n    aeron_distinct_error_log_t *log, int64_t timestamp, int error_code, const char *description)\n{\n    aeron_distinct_error_log_observation_list_t *list = log->observation_list;\n    size_t num_observations = (size_t)list->num_observations;\n    aeron_distinct_observation_t *observations = list->observations;\n\n    size_t description_length = strlen(description);\n    size_t length = AERON_ERROR_LOG_HEADER_LENGTH + description_length;\n    aeron_distinct_error_log_observation_list_t *new_list = NULL;\n    char *new_description = NULL;\n    size_t offset = log->next_offset;\n    aeron_error_log_entry_t *entry = (aeron_error_log_entry_t *)(log->buffer + offset);\n\n    if ((offset + length) > log->buffer_capacity ||\n        aeron_distinct_error_log_observation_list_alloc(&new_list, num_observations + 1) < 0 ||\n        aeron_alloc((void **)&new_description, description_length + 1) < 0)\n    {\n        return NULL;\n    }\n\n    memcpy(log->buffer + offset + AERON_ERROR_LOG_HEADER_LENGTH, description, description_length);\n    entry->first_observation_timestamp = timestamp;\n    entry->observation_count = 0;\n\n    log->next_offset = AERON_ALIGN(offset + length, AERON_ERROR_LOG_RECORD_ALIGNMENT);\n\n    aeron_distinct_observation_t *new_array = new_list->observations;\n\n    new_array[0].error_code = error_code;\n    new_array[0].description = new_description;\n    strncpy(new_description, description, description_length + 1);\n    new_array[0].description_length = description_length;\n    new_array[0].offset = offset;\n\n    if (num_observations != 0)\n    {\n        memcpy(&new_array[1], observations, sizeof(aeron_distinct_observation_t) * num_observations);\n    }\n\n    log->observation_list = new_list;\n    aeron_free(list);\n\n    AERON_SET_RELEASE(entry->length, (int32_t)length);\n\n    return &new_array[0];\n}\n\nint aeron_distinct_error_log_record(aeron_distinct_error_log_t *log, int error_code, const char *description)\n{\n    if (NULL == log)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"log is null\");\n        return -1;\n    }\n\n    aeron_mutex_lock(&log->mutex);\n\n    int64_t timestamp = log->clock();\n    aeron_distinct_error_log_observation_list_t *list = log->observation_list;\n    size_t num_observations = (size_t)list->num_observations;\n    aeron_distinct_observation_t *observation = aeron_distinct_error_log_find_observation(\n        list->observations, num_observations, error_code, description);\n\n    if (NULL == observation)\n    {\n        observation = aeron_distinct_error_log_new_observation(log, timestamp, error_code, description);\n\n        if (NULL == observation)\n        {\n            aeron_mutex_unlock(&log->mutex);\n\n            char buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n\n            aeron_format_date(buffer, sizeof(buffer), timestamp);\n            fprintf(stderr, \"%s - unrecordable error %s\\n\", buffer, description);\n            aeron_set_errno(ENOMEM);\n            return -1;\n        }\n    }\n\n    size_t entry_offset = observation->offset;\n    aeron_mutex_unlock(&log->mutex);\n\n    aeron_error_log_entry_t *entry = (aeron_error_log_entry_t *)(log->buffer + entry_offset);\n\n    int32_t dest;\n    AERON_GET_AND_ADD_INT32(dest, entry->observation_count, 1);\n    AERON_SET_RELEASE(entry->last_observation_timestamp, timestamp);\n    (void)dest; // silence variable 'dest' set but not used\n    return 0;\n}\n\nbool aeron_error_log_exists(const uint8_t *buffer, size_t buffer_size)\n{\n    aeron_error_log_entry_t *entry = (aeron_error_log_entry_t *)buffer;\n    int32_t length;\n\n    AERON_GET_ACQUIRE(length, entry->length);\n\n    return 0 != length;\n}\n\nsize_t aeron_error_log_read(\n    const uint8_t *buffer,\n    size_t buffer_size,\n    aeron_error_log_reader_func_t reader,\n    void *clientd,\n    int64_t since_timestamp)\n{\n    size_t entries = 0;\n    size_t offset = 0;\n\n    while (offset < buffer_size)\n    {\n        aeron_error_log_entry_t *entry = (aeron_error_log_entry_t *)(buffer + offset);\n        int32_t length;\n\n        AERON_GET_ACQUIRE(length, entry->length);\n        if (0 == length)\n        {\n            break;\n        }\n\n        int64_t last_observation_timestamp;\n        AERON_GET_ACQUIRE(last_observation_timestamp, entry->last_observation_timestamp);\n\n        if (last_observation_timestamp >= since_timestamp)\n        {\n            ++entries;\n\n            reader(\n                entry->observation_count,\n                entry->first_observation_timestamp,\n                last_observation_timestamp,\n                (const char *)(buffer + offset + AERON_ERROR_LOG_HEADER_LENGTH),\n                length - AERON_ERROR_LOG_HEADER_LENGTH,\n                clientd);\n        }\n\n        offset += AERON_ALIGN(length, AERON_ERROR_LOG_RECORD_ALIGNMENT);\n    }\n\n    return entries;\n}\n\nsize_t aeron_distinct_error_log_num_observations(aeron_distinct_error_log_t *log)\n{\n    aeron_mutex_lock(&log->mutex);\n\n    aeron_distinct_error_log_observation_list_t *list = log->observation_list;\n    size_t result = (size_t)list->num_observations;\n\n    aeron_mutex_unlock(&log->mutex);\n\n    return result;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_distinct_error_log.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DISTINCT_ERROR_LOG_H\n#define AERON_DISTINCT_ERROR_LOG_H\n\n#include \"aeron_alloc.h\"\n#include \"aeronc.h\"\n#include \"util/aeron_clock.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"concurrent/aeron_thread.h\"\n#include \"concurrent/aeron_atomic.h\"\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_error_log_entry_stct\n{\n    volatile int32_t length;\n    volatile int32_t observation_count;\n    volatile int64_t last_observation_timestamp;\n    int64_t first_observation_timestamp;\n}\naeron_error_log_entry_t;\n#pragma pack(pop)\n\n#define AERON_ERROR_LOG_HEADER_LENGTH (sizeof(aeron_error_log_entry_t))\n#define AERON_ERROR_LOG_RECORD_ALIGNMENT (sizeof(int64_t))\n\ntypedef struct aeron_distinct_observation_stct\n{\n    const char *description;\n    int error_code;\n    size_t offset;\n    size_t description_length;\n}\naeron_distinct_observation_t;\n\ntypedef struct aeron_distinct_error_log_observation_list_stct\n{\n    uint64_t num_observations;\n    aeron_distinct_observation_t *observations;\n}\naeron_distinct_error_log_observation_list_t;\n\ntypedef struct aeron_distinct_error_log_stct\n{\n    uint8_t *buffer;\n    aeron_distinct_error_log_observation_list_t *observation_list;\n    size_t buffer_capacity;\n    size_t next_offset;\n    aeron_clock_func_t clock;\n    aeron_mutex_t mutex;\n}\naeron_distinct_error_log_t;\n\nint aeron_distinct_error_log_init(\n    aeron_distinct_error_log_t *log, uint8_t *buffer, size_t buffer_size, aeron_clock_func_t clock);\n\nvoid aeron_distinct_error_log_close(aeron_distinct_error_log_t *log);\n\nint aeron_distinct_error_log_record(aeron_distinct_error_log_t *log, int error_code, const char *description);\n\nbool aeron_error_log_exists(const uint8_t *buffer, size_t buffer_size);\n\nsize_t aeron_error_log_read(\n    const uint8_t *buffer,\n    size_t buffer_size,\n    aeron_error_log_reader_func_t reader,\n    void *clientd,\n    int64_t since_timestamp);\n\nsize_t aeron_distinct_error_log_num_observations(aeron_distinct_error_log_t *log);\n\n#endif //AERON_DISTINCT_ERROR_LOG_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_executor.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_executor.h\"\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_atomic.h\"\n\nstatic aeron_executor_task_t *aeron_executor_task_allocate(\n    aeron_executor_t *executor,\n    aeron_executor_task_on_execute_func_t on_execute,\n    aeron_executor_task_on_complete_func_t on_complete,\n    aeron_executor_task_on_cancel_func_t on_cancel,\n    void *clientd)\n{\n    aeron_executor_task_t *task;\n\n    if (aeron_alloc((void **)&task, sizeof(aeron_executor_task_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return NULL;\n    }\n\n    task->executor = executor;\n    task->on_execute = on_execute;\n    task->on_complete = on_complete;\n    task->on_cancel = on_cancel;\n    task->clientd = clientd;\n    task->result = -1;\n\n    return task;\n}\n\nstatic int aeron_executor_dispatch(void *state)\n{\n    aeron_executor_t *executor = (aeron_executor_t *)state;\n    aeron_executor_task_t *task = (aeron_executor_task_t *)aeron_blocking_linked_queue_poll(&executor->queue);\n\n    if (NULL == task)\n    {\n        return 0;\n    }\n\n    task->result = (NULL == task->on_execute) ? 0 : task->on_execute(task->clientd, executor->clientd);\n\n    if (task->result < 0)\n    {\n        task->errcode = aeron_errcode();\n        memcpy(task->errmsg, aeron_errmsg(), strlen(aeron_errmsg()));\n        aeron_err_clear();\n    }\n\n    if (NULL == executor->on_execution_complete)\n    {\n        aeron_blocking_linked_queue_offer(&executor->return_queue, task);\n    }\n    else\n    {\n        executor->on_execution_complete(task, executor->clientd);\n        aeron_free(task);\n    }\n\n    return 1;\n}\n\nstatic void aeron_executor_cancel_all_tasks_and_close_queue(aeron_blocking_linked_queue_t *queue)\n{\n    while (true)\n    {\n        aeron_executor_task_t *task = aeron_blocking_linked_queue_poll(queue);\n        if (NULL == task)\n        {\n            break;\n        }\n        task->on_cancel(task->clientd, task->executor->clientd);\n        aeron_free(task);\n    }\n\n    aeron_blocking_linked_queue_close(queue); // queue is empty at this point\n}\n\nstatic void aeron_executor_drain_and_close_submit_queue(void *state)\n{\n    aeron_executor_t *executor = (aeron_executor_t *)state;\n    aeron_executor_cancel_all_tasks_and_close_queue(&executor->queue);\n}\n\nint aeron_executor_init(\n    aeron_executor_t *executor,\n    bool async,\n    aeron_executor_on_execution_complete_func_t on_execution_complete,\n    void *clientd)\n{\n    executor->async = async,\n        executor->on_execution_complete = on_execution_complete;\n    executor->clientd = clientd;\n\n    executor->runner.state = AERON_AGENT_STATE_UNUSED;\n    executor->runner.role_name = NULL;\n    executor->runner.on_close = NULL;\n\n    executor->idle_strategy_func = NULL;\n    executor->idle_strategy_state = NULL;\n\n    if (async)\n    {\n        if (NULL == executor->on_execution_complete)\n        {\n            if (aeron_blocking_linked_queue_init(&executor->return_queue) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return -1;\n            }\n        }\n\n        if (aeron_blocking_linked_queue_init(&executor->queue) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        if ((executor->idle_strategy_func = aeron_idle_strategy_load(\n            \"sleep-ns\",\n            &executor->idle_strategy_state,\n            \"AERON_EXECUTOR_IDLE_STRATEGY\",\n            \"1ms\")) == NULL)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to load idle strategy\");\n            return -1;\n        }\n\n        if (aeron_agent_init(\n            &executor->runner,\n            \"async-executor\",\n            executor,\n            NULL,\n            NULL,\n            aeron_executor_dispatch,\n            aeron_executor_drain_and_close_submit_queue,\n            executor->idle_strategy_func,\n            executor->idle_strategy_state) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to init agent runner\");\n            return -1;\n        }\n\n        if (aeron_agent_start(&executor->runner) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to start agent runner\");\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nint aeron_executor_close(aeron_executor_t *executor)\n{\n    if (executor->async)\n    {\n        if (aeron_agent_stop(&executor->runner))\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to stop agent runner\");\n            return -1;\n        }\n\n        if (aeron_agent_close(&executor->runner))\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to close agent runner\");\n            return -1;\n        }\n\n        aeron_free(executor->idle_strategy_state);\n\n        if (NULL == executor->on_execution_complete)\n        {\n            aeron_executor_cancel_all_tasks_and_close_queue(&executor->return_queue);\n        }\n    }\n    return 0;\n}\n\nint aeron_executor_submit(\n    aeron_executor_t *executor,\n    aeron_executor_task_on_execute_func_t on_execute,\n    aeron_executor_task_on_complete_func_t on_complete,\n    aeron_executor_task_on_cancel_func_t on_cancel,\n    void *clientd)\n{\n    if (executor->async)\n    {\n        aeron_executor_task_t *task;\n\n        task = aeron_executor_task_allocate(executor, on_execute, on_complete, on_cancel, clientd);\n        if (NULL == task)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        return aeron_blocking_linked_queue_offer(&executor->queue, task);\n    }\n\n    /* not async, so just run execute and complete back to back */\n    int result = on_execute(clientd, executor->clientd);\n\n    /* error handling must be done inside the on_complete function */\n    on_complete(\n        result,\n        aeron_errcode(),\n        aeron_errmsg(),\n        clientd,\n        executor->clientd);\n\n    return 0;\n}\n\nint aeron_executor_process_completions(aeron_executor_t *executor, int limit)\n{\n    if (!executor->async || NULL != executor->on_execution_complete)\n    {\n        return 0;\n    }\n\n    aeron_executor_task_t *task;\n    int count = 0;\n    for (; count < limit; count++)\n    {\n        task = aeron_blocking_linked_queue_poll(&executor->return_queue);\n        if (NULL == task)\n        {\n            break;\n        }\n\n        task->on_complete(\n            task->result,\n            task->errcode,\n            task->errmsg,\n            task->clientd,\n            task->executor->clientd);\n\n        aeron_free(task);\n    }\n\n    return count;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_executor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_EXECUTOR_H\n#define AERON_EXECUTOR_H\n\n#include \"aeron_agent.h\"\n#include \"concurrent/aeron_blocking_linked_queue.h\"\n#include \"util/aeron_error.h\"\n\ntypedef struct aeron_executor_task_stct aeron_executor_task_t;\n\ntypedef int (*aeron_executor_on_execution_complete_func_t)(aeron_executor_task_t *task, void *executor_clientd);\n\ntypedef struct aeron_executor_stct\n{\n    bool async;\n    aeron_executor_on_execution_complete_func_t on_execution_complete;\n    void *clientd;\n    aeron_blocking_linked_queue_t queue;\n    aeron_blocking_linked_queue_t return_queue;\n    aeron_agent_runner_t runner;\n    aeron_idle_strategy_func_t idle_strategy_func;\n    void *idle_strategy_state;\n}\naeron_executor_t;\n\ntypedef int (*aeron_executor_task_on_execute_func_t)(void *task_clientd, void *executor_clientd);\ntypedef void (*aeron_executor_task_on_cancel_func_t)(void *task_clientd, void *executor_clientd);\ntypedef void (*aeron_executor_task_on_complete_func_t)(\n    int execution_result, int errcode, const char *errmsg, void *task_clientd, void *executor_clientd);\n\ntypedef struct aeron_executor_task_stct\n{\n    aeron_executor_t *executor;\n    aeron_executor_task_on_execute_func_t on_execute;\n    aeron_executor_task_on_complete_func_t on_complete;\n    aeron_executor_task_on_cancel_func_t on_cancel;\n    void *clientd;\n    int result;\n    int errcode;\n    char errmsg[AERON_ERROR_MAX_TOTAL_LENGTH];\n}\naeron_executor_task_t;\n\nint aeron_executor_init(\n    aeron_executor_t *executor,\n    bool async,\n    aeron_executor_on_execution_complete_func_t on_execution_complete,\n    void *clientd);\n\nint aeron_executor_close(aeron_executor_t *executor);\n\nint aeron_executor_submit(\n    aeron_executor_t *executor,\n    aeron_executor_task_on_execute_func_t on_execute,\n    aeron_executor_task_on_complete_func_t on_complete,\n    aeron_executor_task_on_cancel_func_t on_cancel,\n    void *clientd);\n\nint aeron_executor_process_completions(aeron_executor_t *executor, int limit);\n\nvoid aeron_executor_task_do_complete(aeron_executor_task_t *task);\n\n#endif //AERON_EXECUTOR_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_logbuffer_descriptor.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <inttypes.h>\n#include \"util/aeron_error.h\"\n#include \"concurrent/aeron_logbuffer_descriptor.h\"\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, term_tail_counters) == 0,\n    \"offsetof(aeron_logbuffer_metadata_t, term_tail_counters) is wrong\");\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, active_term_count) == 24,\n    \"offsetof(aeron_logbuffer_metadata_t, active_term_count) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, end_of_stream_position) == 128,\n    \"offsetof(aeron_logbuffer_metadata_t, end_of_stream_position) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, is_connected) == 136,\n    \"offsetof(aeron_logbuffer_metadata_t, is_connected) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, active_transport_count) == 140,\n    \"offsetof(aeron_logbuffer_metadata_t, active_transport_count) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, correlation_id) == 256,\n    \"offsetof(aeron_logbuffer_metadata_t, correlation_id) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, initial_term_id) == 264,\n    \"offsetof(aeron_logbuffer_metadata_t, initial_term_id) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, default_frame_header_length) == 268,\n    \"offsetof(aeron_logbuffer_metadata_t, default_frame_header_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, mtu_length) == 272,\n    \"offsetof(aeron_logbuffer_metadata_t, mtu_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, term_length) == 276,\n    \"offsetof(aeron_logbuffer_metadata_t, term_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, page_size) == 280,\n    \"offsetof(aeron_logbuffer_metadata_t, page_size) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, publication_window_length) == 284,\n    \"offsetof(aeron_logbuffer_metadata_t, publication_window_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, receiver_window_length) == 288,\n    \"offsetof(aeron_logbuffer_metadata_t, receiver_window_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, socket_sndbuf_length) == 292,\n    \"offsetof(aeron_logbuffer_metadata_t, socket_sndbuf_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, os_default_socket_sndbuf_length) == 296,\n    \"offsetof(aeron_logbuffer_metadata_t, os_default_socket_sndbuf_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, os_max_socket_sndbuf_length) == 300,\n    \"offsetof(aeron_logbuffer_metadata_t, os_max_socket_sndbuf_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, socket_rcvbuf_length) == 304,\n    \"offsetof(aeron_logbuffer_metadata_t, socket_rcvbuf_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, os_default_socket_rcvbuf_length) == 308,\n    \"offsetof(aeron_logbuffer_metadata_t, os_default_socket_rcvbuf_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, os_max_socket_rcvbuf_length) == 312,\n    \"offsetof(aeron_logbuffer_metadata_t, os_max_socket_rcvbuf_length) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, max_resend) == 316,\n    \"offsetof(aeron_logbuffer_metadata_t, max_resend) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, default_header) == 320,\n    \"offsetof(aeron_logbuffer_metadata_t, default_header) is wrong\");\n\n_Static_assert(\n    AERON_LOGBUFFER_DEFAULT_FRAME_HEADER_MAX_LENGTH >= AERON_DATA_HEADER_LENGTH,\n    \"AERON_LOGBUFFER_DEFAULT_FRAME_HEADER_MAX_LENGTH < AERON_DATA_HEADER_LENGTH\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, entity_tag) == 448,\n    \"offsetof(aeron_logbuffer_metadata_t, entity_tag) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, entity_tag) % sizeof(int64_t) == 0,\n    \"offsetof(aeron_logbuffer_metadata_t, entity_tag) not aligned\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, response_correlation_id) == 456,\n    \"offsetof(aeron_logbuffer_metadata_t, response_correlation_id) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, linger_timeout_ns) == 464,\n    \"offsetof(aeron_logbuffer_metadata_t, linger_timeout_ns) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, untethered_window_limit_timeout_ns) == 472,\n    \"offsetof(aeron_logbuffer_metadata_t, untethered_window_limit_timeout_ns) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, untethered_resting_timeout_ns) == 480,\n    \"offsetof(aeron_logbuffer_metadata_t, untethered_resting_timeout_ns) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, group) == 488,\n    \"offsetof(aeron_logbuffer_metadata_t, group) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, is_response) == 489,\n    \"offsetof(aeron_logbuffer_metadata_t, is_response) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, rejoin) == 490,\n    \"offsetof(aeron_logbuffer_metadata_t, rejoin) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, reliable) == 491,\n    \"offsetof(aeron_logbuffer_metadata_t, reliable) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, sparse) == 492,\n    \"offsetof(aeron_logbuffer_metadata_t, sparse) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, signal_eos) == 493,\n    \"offsetof(aeron_logbuffer_metadata_t, signal_eos) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, spies_simulate_connection) == 494,\n    \"offsetof(aeron_logbuffer_metadata_t, spies_simulate_connection) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, tether) == 495,\n    \"offsetof(aeron_logbuffer_metadata_t, tether) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, is_publication_revoked) == 496,\n    \"offsetof(aeron_logbuffer_metadata_t, is_publication_revoked) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, type) == 497,\n    \"offsetof(aeron_logbuffer_metadata_t, type) is wrong\");\n\n_Static_assert(\n    offsetof(aeron_logbuffer_metadata_t, untethered_linger_timeout_ns) == 500,\n    \"offsetof(aeron_logbuffer_metadata_t, is_publication_revoked) is wrong\");\n\n_Static_assert(\n    sizeof(aeron_logbuffer_metadata_t) == 508,\n    \"sizeof(aeron_logbuffer_metadata_t) is wrong\");\n\n_Static_assert(\n    AERON_LOGBUFFER_META_DATA_LENGTH == AERON_PAGE_MIN_SIZE,\n    \"AERON_LOGBUFFER_META_DATA_LENGTH != AERON_PAGE_MIN_SIZE\");\n\nint aeron_logbuffer_check_term_length(uint64_t term_length)\n{\n    if (term_length < AERON_LOGBUFFER_TERM_MIN_LENGTH)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"term length less than min length of %\" PRIu64 \": length=%\" PRIu64,\n            AERON_LOGBUFFER_TERM_MIN_LENGTH, term_length);\n        return -1;\n    }\n\n    if (term_length > AERON_LOGBUFFER_TERM_MAX_LENGTH)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"term length greater than max length of %\" PRIu64 \": length=%\" PRIu64,\n            AERON_LOGBUFFER_TERM_MAX_LENGTH, term_length);\n        return -1;\n    }\n\n    if (!AERON_IS_POWER_OF_TWO(term_length))\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"term length not a power of 2: length=%\" PRIu64,\n            term_length);\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_logbuffer_check_page_size(uint64_t page_size)\n{\n    if (page_size < AERON_PAGE_MIN_SIZE)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"page size less than min size of %\" PRIu64 \": size=%\" PRIu64,\n            AERON_PAGE_MIN_SIZE, page_size);\n        return -1;\n    }\n\n    if (page_size > AERON_PAGE_MAX_SIZE)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"page size greater than max size of %\" PRIu64 \": size=%\" PRIu64,\n            AERON_PAGE_MAX_SIZE, page_size);\n        return -1;\n    }\n\n    if (!AERON_IS_POWER_OF_TWO(page_size))\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"page size not a power of 2: size=%\" PRIu64,\n            page_size);\n        return -1;\n    }\n\n    return 0;\n}\n\nextern int32_t aeron_logbuffer_compute_term_count(int32_t term_id, int32_t initial_term_id);\nextern uint64_t aeron_logbuffer_compute_log_length(uint64_t term_length, uint64_t page_size);\nextern int32_t aeron_logbuffer_term_offset(int64_t raw_tail, int32_t term_length);\nextern int32_t aeron_logbuffer_term_id(int64_t raw_tail);\nextern size_t aeron_logbuffer_index_by_position(int64_t position, size_t position_bits_to_shift);\nextern size_t aeron_logbuffer_index_by_term(int32_t initial_term_id, int32_t active_term_id);\nextern size_t aeron_logbuffer_index_by_term_count(int32_t term_count);\nextern int64_t aeron_logbuffer_compute_position(\n    int32_t active_term_id, int32_t term_offset, size_t position_bits_to_shift, int32_t initial_term_id);\nextern int64_t aeron_logbuffer_compute_term_begin_position(\n    int32_t active_term_id, size_t position_bits_to_shift, int32_t initial_term_id);\nextern int32_t aeron_logbuffer_compute_term_id_from_position(\n    int64_t position, size_t position_bits_to_shift, int32_t initial_term_id);\nextern int32_t aeron_logbuffer_compute_term_offset_from_position(int64_t position, size_t position_bits_to_shift);\nextern bool aeron_logbuffer_cas_raw_tail(\n    aeron_logbuffer_metadata_t *log_meta_data,\n    size_t partition_index,\n    int64_t expected_raw_tail,\n    int64_t update_raw_tail);\nextern int32_t aeron_logbuffer_active_term_count(aeron_logbuffer_metadata_t *log_meta_data);\nextern bool aeron_logbuffer_cas_active_term_count(\n    aeron_logbuffer_metadata_t *log_meta_data,\n    int32_t expected_term_count,\n    int32_t update_term_count);\nextern bool aeron_logbuffer_rotate_log(\n    aeron_logbuffer_metadata_t *log_meta_data, int32_t current_term_count, int32_t current_term_id);\nextern void aeron_logbuffer_fill_default_header(\n    uint8_t *log_meta_data_buffer, int32_t session_id, int32_t stream_id, int32_t initial_term_id);\nextern void aeron_logbuffer_metadata_init(\n    uint8_t *log_meta_data_buffer,\n    int64_t end_of_stream_position,\n    int32_t is_connected,\n    int32_t active_transport_count,\n    int64_t correlation_id,\n    int32_t initial_term_id,\n    int32_t mtu_length,\n    int32_t term_length,\n    int32_t page_size,\n    int32_t publication_window_length,\n    int32_t receiver_window_length,\n    int32_t socket_sndbuf_length,\n    int32_t os_default_socket_sndbuf_length,\n    int32_t os_max_socket_sndbuf_length,\n    int32_t socket_rcvbuf_length,\n    int32_t os_default_socket_rcvbuf_length,\n    int32_t os_max_socket_rcvbuf_length,\n    int32_t max_resend,\n    int32_t session_id,\n    int32_t stream_id,\n    int64_t entity_tag,\n    int64_t response_correlation_id,\n    int64_t linger_timeout_ns,\n    int64_t untethered_window_limit_timeout_ns,\n    int64_t untethered_linger_timeout_ns,\n    int64_t untethered_resting_timeout_ns,\n    uint8_t group,\n    uint8_t is_response,\n    uint8_t rejoin,\n    uint8_t reliable,\n    uint8_t sparse,\n    uint8_t signal_eos,\n    uint8_t spies_simulate_connection,\n    uint8_t tether,\n    uint8_t is_exclusive_publication);\nextern void aeron_logbuffer_apply_default_header(uint8_t *log_meta_data_buffer, uint8_t *buffer);\nextern size_t aeron_logbuffer_compute_fragmented_length(size_t length, size_t max_payload_length);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_logbuffer_descriptor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_LOGBUFFER_DESCRIPTOR_H\n#define AERON_LOGBUFFER_DESCRIPTOR_H\n\n#include <string.h>\n\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_math.h\"\n#include \"concurrent/aeron_atomic.h\"\n\n#define AERON_LOGBUFFER_PARTITION_COUNT (3)\n#define AERON_LOGBUFFER_TERM_MIN_LENGTH (64 * 1024)\n#define AERON_LOGBUFFER_TERM_MAX_LENGTH (1024 * 1024 * 1024)\n#define AERON_PAGE_MIN_SIZE UINT32_C(4 * 1024)\n#define AERON_PAGE_MAX_SIZE UINT32_C(1024 * 1024 * 1024)\n#define AERON_LOGBUFFER_PADDING_SIZE UINT32_C(64)\n#define AERON_LOGBUFFER_DEFAULT_FRAME_HEADER_MAX_LENGTH (AERON_CACHE_LINE_LENGTH * 2)\n#define AERON_LOGBUFFER_TYPE_CONCURRENT_PUBLICATION UINT8_C(0)\n#define AERON_LOGBUFFER_TYPE_EXCLUSIVE_PUBLICATION UINT8_C(1)\n#define AERON_LOGBUFFER_TYPE_PUBLICATION_IMAGE UINT8_C(2)\n\n#define AERON_MAX_UDP_PAYLOAD_LENGTH (65504)\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_logbuffer_metadata_stct\n{\n    volatile int64_t term_tail_counters[AERON_LOGBUFFER_PARTITION_COUNT];\n    volatile int32_t active_term_count;\n\n    uint8_t pad1[(2 * AERON_LOGBUFFER_PADDING_SIZE) - ((AERON_LOGBUFFER_PARTITION_COUNT * sizeof(int64_t)) + sizeof(int32_t))];\n    volatile int64_t end_of_stream_position;\n    volatile int32_t is_connected;\n    volatile int32_t active_transport_count;\n\n    uint8_t pad2[(2 * AERON_LOGBUFFER_PADDING_SIZE) - (sizeof(int64_t) + (2 * sizeof(int32_t)))];\n    int64_t correlation_id;\n    int32_t initial_term_id;\n    int32_t default_frame_header_length;\n    int32_t mtu_length;\n    int32_t term_length;\n    int32_t page_size;\n    int32_t publication_window_length;\n    int32_t receiver_window_length;\n    int32_t socket_sndbuf_length;\n    int32_t os_default_socket_sndbuf_length;\n    int32_t os_max_socket_sndbuf_length;\n    int32_t socket_rcvbuf_length;\n    int32_t os_default_socket_rcvbuf_length;\n    int32_t os_max_socket_rcvbuf_length;\n    int32_t max_resend;\n    uint8_t default_header[AERON_LOGBUFFER_DEFAULT_FRAME_HEADER_MAX_LENGTH];\n    int64_t entity_tag;\n    int64_t response_correlation_id;\n    int64_t linger_timeout_ns;\n    int64_t untethered_window_limit_timeout_ns;\n    int64_t untethered_resting_timeout_ns;\n    uint8_t group;\n    uint8_t is_response;\n    uint8_t rejoin;\n    uint8_t reliable;\n    uint8_t sparse;\n    uint8_t signal_eos;\n    uint8_t spies_simulate_connection;\n    uint8_t tether;\n    volatile uint8_t is_publication_revoked;\n    uint8_t type;\n    uint8_t pad3[2 * sizeof(uint8_t)];\n    int64_t untethered_linger_timeout_ns;\n}\naeron_logbuffer_metadata_t;\n#pragma pack(pop)\n\n#define AERON_LOGBUFFER_META_DATA_LENGTH (AERON_PAGE_MIN_SIZE)\n\n#define AERON_LOGBUFFER_FRAME_ALIGNMENT (32)\n\n#define AERON_LOGBUFFER_RAWTAIL_VOLATILE(d, m) \\\ndo \\\n{ \\\n    int32_t active_term_count; \\\n    AERON_GET_ACQUIRE(active_term_count, ((m)->active_term_count)); \\\n    size_t partition = (size_t)(active_term_count % AERON_LOGBUFFER_PARTITION_COUNT); \\\n    AERON_GET_ACQUIRE(d, (m)->term_tail_counters[partition]); \\\n} \\\nwhile (false)\n\nint aeron_logbuffer_check_term_length(uint64_t term_length);\nint aeron_logbuffer_check_page_size(uint64_t page_size);\n\ninline uint64_t aeron_logbuffer_compute_log_length(uint64_t term_length, uint64_t page_size)\n{\n    return AERON_ALIGN(((term_length * AERON_LOGBUFFER_PARTITION_COUNT) + AERON_LOGBUFFER_META_DATA_LENGTH), page_size);\n}\n\ninline int32_t aeron_logbuffer_term_offset(int64_t raw_tail, int32_t term_length)\n{\n    int64_t offset = raw_tail & 0xFFFFFFFFL;\n    return offset < term_length ? (int32_t)offset : term_length;\n}\n\ninline int32_t aeron_logbuffer_term_id(int64_t raw_tail)\n{\n    return (int32_t)(raw_tail >> 32);\n}\n\ninline int32_t aeron_logbuffer_compute_term_count(int32_t term_id, int32_t initial_term_id)\n{\n    return aeron_sub_wrap_i32(term_id, initial_term_id);\n}\n\ninline size_t aeron_logbuffer_index_by_position(int64_t position, size_t position_bits_to_shift)\n{\n    return (size_t)((position >> position_bits_to_shift) % AERON_LOGBUFFER_PARTITION_COUNT);\n}\n\ninline size_t aeron_logbuffer_index_by_term(int32_t initial_term_id, int32_t active_term_id)\n{\n    int32_t term_count = aeron_logbuffer_compute_term_count(active_term_id, initial_term_id);\n    return (size_t)(term_count % AERON_LOGBUFFER_PARTITION_COUNT);\n}\n\ninline size_t aeron_logbuffer_index_by_term_count(int32_t term_count)\n{\n    return (size_t)(term_count % AERON_LOGBUFFER_PARTITION_COUNT);\n}\n\ninline int64_t aeron_logbuffer_compute_position(\n    int32_t active_term_id, int32_t term_offset, size_t position_bits_to_shift, int32_t initial_term_id)\n{\n    int64_t term_count = aeron_logbuffer_compute_term_count(active_term_id, initial_term_id);\n    return (term_count << position_bits_to_shift) + term_offset;\n}\n\ninline int64_t aeron_logbuffer_compute_term_begin_position(\n    int32_t active_term_id, size_t position_bits_to_shift, int32_t initial_term_id)\n{\n    return aeron_logbuffer_compute_position(active_term_id, 0, position_bits_to_shift, initial_term_id);\n}\n\ninline int32_t aeron_logbuffer_compute_term_id_from_position(\n    int64_t position, size_t position_bits_to_shift, int32_t initial_term_id)\n{\n    return aeron_add_wrap_i32((int32_t)(position >> position_bits_to_shift), initial_term_id);\n}\n\ninline int32_t aeron_logbuffer_compute_term_offset_from_position(int64_t position, size_t position_bits_to_shift)\n{\n    int64_t mask = (1u << position_bits_to_shift) - 1;\n\n    return (int32_t)(position & mask);\n}\n\ninline bool aeron_logbuffer_cas_raw_tail(\n    aeron_logbuffer_metadata_t *log_meta_data,\n    size_t partition_index,\n    int64_t expected_raw_tail,\n    int64_t update_raw_tail)\n{\n    return aeron_cas_int64(&log_meta_data->term_tail_counters[partition_index], expected_raw_tail, update_raw_tail);\n}\n\ninline int32_t aeron_logbuffer_active_term_count(aeron_logbuffer_metadata_t *log_meta_data)\n{\n    int32_t active_term_count;\n    AERON_GET_ACQUIRE(active_term_count, log_meta_data->active_term_count);\n    return active_term_count;\n}\n\ninline bool aeron_logbuffer_cas_active_term_count(\n    aeron_logbuffer_metadata_t *log_meta_data,\n    int32_t expected_term_count,\n    int32_t update_term_count)\n{\n    return aeron_cas_int32(&log_meta_data->active_term_count, expected_term_count, update_term_count);\n}\n\ninline bool aeron_logbuffer_rotate_log(\n    aeron_logbuffer_metadata_t *log_meta_data, int32_t current_term_count, int32_t current_term_id)\n{\n    const int32_t next_term_id = current_term_id + 1;\n    const int32_t next_term_count = current_term_count + 1;\n    const size_t next_index = aeron_logbuffer_index_by_term_count(next_term_count);\n    const int32_t expected_term_id = next_term_id - AERON_LOGBUFFER_PARTITION_COUNT;\n\n    int64_t raw_tail;\n    do\n    {\n        AERON_GET_ACQUIRE(raw_tail, log_meta_data->term_tail_counters[next_index]);\n        if (expected_term_id != aeron_logbuffer_term_id(raw_tail))\n        {\n            break;\n        }\n    }\n    while (!aeron_logbuffer_cas_raw_tail(\n        log_meta_data, next_index, raw_tail, (int64_t)((uint64_t)next_term_id << 32u)));\n\n    return aeron_logbuffer_cas_active_term_count(log_meta_data, current_term_count, next_term_count);\n}\n\ninline void aeron_logbuffer_fill_default_header(\n    uint8_t *log_meta_data_buffer, int32_t session_id, int32_t stream_id, int32_t initial_term_id)\n{\n    aeron_logbuffer_metadata_t *log_meta_data = (aeron_logbuffer_metadata_t *)log_meta_data_buffer;\n    aeron_data_header_t *data_header = (aeron_data_header_t *)(log_meta_data->default_header);\n\n    log_meta_data->default_frame_header_length = AERON_DATA_HEADER_LENGTH;\n    data_header->frame_header.frame_length = 0;\n    data_header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    data_header->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n    data_header->stream_id = stream_id;\n    data_header->session_id = session_id;\n    data_header->term_id = initial_term_id;\n    data_header->term_offset = 0;\n    data_header->reserved_value = AERON_DATA_HEADER_DEFAULT_RESERVED_VALUE;\n}\n\n/*\n * Does NOT initialize the following fields:\n * - term_tail_counters\n * - active_term_count\n */\ninline void aeron_logbuffer_metadata_init(\n    uint8_t *log_meta_data_buffer,\n    int64_t end_of_stream_position,\n    int32_t is_connected,\n    int32_t active_transport_count,\n    int64_t correlation_id,\n    int32_t initial_term_id,\n    int32_t mtu_length,\n    int32_t term_length,\n    int32_t page_size,\n    int32_t publication_window_length,\n    int32_t receiver_window_length,\n    int32_t socket_sndbuf_length,\n    int32_t os_default_socket_sndbuf_length,\n    int32_t os_max_socket_sndbuf_length,\n    int32_t socket_rcvbuf_length,\n    int32_t os_default_socket_rcvbuf_length,\n    int32_t os_max_socket_rcvbuf_length,\n    int32_t max_resend,\n    int32_t session_id,\n    int32_t stream_id,\n    int64_t entity_tag,\n    int64_t response_correlation_id,\n    int64_t linger_timeout_ns,\n    int64_t untethered_window_limit_timeout_ns,\n    int64_t untethered_linger_timeout_ns,\n    int64_t untethered_resting_timeout_ns,\n    uint8_t group,\n    uint8_t is_response,\n    uint8_t rejoin,\n    uint8_t reliable,\n    uint8_t sparse,\n    uint8_t signal_eos,\n    uint8_t spies_simulate_connection,\n    uint8_t tether,\n    uint8_t type)\n{\n    aeron_logbuffer_metadata_t *log_meta_data = (aeron_logbuffer_metadata_t *)log_meta_data_buffer;\n\n    log_meta_data->end_of_stream_position = end_of_stream_position;\n    log_meta_data->is_connected = is_connected;\n    log_meta_data->active_transport_count = active_transport_count;\n\n    log_meta_data->correlation_id = correlation_id;\n    log_meta_data->initial_term_id = initial_term_id;\n    log_meta_data->mtu_length = mtu_length;\n    log_meta_data->term_length = term_length;\n    log_meta_data->page_size = page_size;\n\n    log_meta_data->publication_window_length = publication_window_length;\n    log_meta_data->receiver_window_length = receiver_window_length;\n    log_meta_data->socket_sndbuf_length = socket_sndbuf_length;\n    log_meta_data->os_default_socket_sndbuf_length = os_default_socket_sndbuf_length;\n    log_meta_data->os_max_socket_sndbuf_length = os_max_socket_sndbuf_length;\n    log_meta_data->socket_rcvbuf_length = socket_rcvbuf_length;\n    log_meta_data->os_default_socket_rcvbuf_length = os_default_socket_rcvbuf_length;\n    log_meta_data->os_max_socket_rcvbuf_length = os_max_socket_rcvbuf_length;\n    log_meta_data->max_resend = max_resend;\n\n    aeron_logbuffer_fill_default_header(log_meta_data_buffer, session_id, stream_id, initial_term_id);\n\n    log_meta_data->entity_tag = entity_tag;\n    log_meta_data->response_correlation_id = response_correlation_id;\n    log_meta_data->linger_timeout_ns = linger_timeout_ns;\n    log_meta_data->untethered_window_limit_timeout_ns = untethered_window_limit_timeout_ns;\n    log_meta_data->untethered_linger_timeout_ns = untethered_linger_timeout_ns;\n    log_meta_data->untethered_resting_timeout_ns = untethered_resting_timeout_ns;\n    log_meta_data->group = group;\n    log_meta_data->is_response = is_response;\n    log_meta_data->rejoin = rejoin;\n    log_meta_data->reliable = reliable;\n    log_meta_data->sparse = sparse;\n    log_meta_data->signal_eos = signal_eos;\n    log_meta_data->spies_simulate_connection = spies_simulate_connection;\n    log_meta_data->tether = tether;\n    log_meta_data->is_publication_revoked = (uint8_t)false;\n    log_meta_data->type = type;\n}\n\ninline void aeron_logbuffer_apply_default_header(uint8_t *log_meta_data_buffer, uint8_t *buffer)\n{\n    aeron_logbuffer_metadata_t *log_meta_data = (aeron_logbuffer_metadata_t *)log_meta_data_buffer;\n\n    memcpy(buffer, log_meta_data->default_header, (size_t)log_meta_data->default_frame_header_length);\n}\n\ninline size_t aeron_logbuffer_compute_fragmented_length(size_t length, size_t max_payload_length)\n{\n    const size_t num_max_payloads = length / max_payload_length;\n    const size_t remaining_payload = length % max_payload_length;\n    const size_t last_frame_length = (remaining_payload > 0) ?\n        AERON_ALIGN(remaining_payload + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT) : 0;\n\n    return (num_max_payloads * (max_payload_length + AERON_DATA_HEADER_LENGTH)) + last_frame_length;\n}\n\n#endif //AERON_LOGBUFFER_DESCRIPTOR_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_mpsc_concurrent_array_queue.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_alloc.h\"\n#include \"concurrent/aeron_mpsc_concurrent_array_queue.h\"\n\nint aeron_mpsc_concurrent_array_queue_init(aeron_mpsc_concurrent_array_queue_t *queue, size_t length)\n{\n    length = (size_t)aeron_find_next_power_of_two((int32_t)length);\n\n    if (aeron_alloc((void **)&queue->buffer, sizeof(void *) * length) < 0)\n    {\n        return -1;\n    }\n\n    for (size_t i = 0; i < length; i++)\n    {\n        queue->buffer[i] = NULL;\n    }\n\n    queue->capacity = length;\n    queue->mask = length - 1;\n    queue->producer.head_cache = 0;\n    queue->producer.shared_head_cache = 0;\n    AERON_SET_RELEASE(queue->producer.tail, (uint64_t)0);\n    AERON_SET_RELEASE(queue->consumer.head, (uint64_t)0);\n\n    return 0;\n}\n\nint aeron_mpsc_concurrent_array_queue_close(aeron_mpsc_concurrent_array_queue_t *queue)\n{\n    aeron_free((void *)queue->buffer);\n    return 0;\n}\n\nextern aeron_queue_offer_result_t aeron_mpsc_concurrent_array_queue_offer(\n    aeron_mpsc_concurrent_array_queue_t *queue, void *element);\n\nextern size_t aeron_mpsc_concurrent_array_queue_drain(\n    aeron_mpsc_concurrent_array_queue_t *queue, aeron_queue_drain_func_t func, void *clientd, size_t limit);\n\nextern size_t aeron_mpsc_concurrent_array_queue_drain_all(\n    aeron_mpsc_concurrent_array_queue_t *queue, aeron_queue_drain_func_t func, void *clientd);\n\nextern size_t aeron_mpsc_concurrent_array_queue_size(aeron_mpsc_concurrent_array_queue_t *queue);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_mpsc_concurrent_array_queue.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_MPSC_CONCURRENT_ARRAY_QUEUE_H\n#define AERON_MPSC_CONCURRENT_ARRAY_QUEUE_H\n\n#include \"util/aeron_bitutil.h\"\n#include \"aeron_atomic.h\"\n#include \"aeron_concurrent_array_queue.h\"\n\ntypedef struct aeron_mpsc_concurrent_array_queue_stct\n{\n    int8_t padding1[AERON_CACHE_LINE_LENGTH];\n\n    struct\n    {\n        volatile uint64_t tail;\n        uint64_t head_cache;\n        volatile uint64_t shared_head_cache;\n    }\n    producer;\n\n    int8_t padding2[AERON_CACHE_LINE_LENGTH];\n\n    struct\n    {\n        volatile uint64_t head;\n    }\n    consumer;\n\n    int8_t padding3[AERON_CACHE_LINE_LENGTH];\n\n    size_t capacity;\n    size_t mask;\n    void * volatile *buffer;\n}\naeron_mpsc_concurrent_array_queue_t;\n\nint aeron_mpsc_concurrent_array_queue_init(aeron_mpsc_concurrent_array_queue_t *queue, size_t length);\n\nint aeron_mpsc_concurrent_array_queue_close(aeron_mpsc_concurrent_array_queue_t *queue);\n\ninline aeron_queue_offer_result_t aeron_mpsc_concurrent_array_queue_offer(\n    aeron_mpsc_concurrent_array_queue_t *queue, void *element)\n{\n    if (NULL == element)\n    {\n        return AERON_OFFER_ERROR;\n    }\n\n    uint64_t current_head;\n    AERON_GET_ACQUIRE(current_head, queue->producer.shared_head_cache);\n    uint64_t buffer_limit = current_head + queue->capacity;\n    uint64_t current_tail;\n\n    do\n    {\n        AERON_GET_ACQUIRE(current_tail, queue->producer.tail);\n        if (current_tail >= buffer_limit)\n        {\n            AERON_GET_ACQUIRE(current_head, queue->consumer.head);\n            buffer_limit = current_head + queue->capacity;\n\n            if (current_tail >= buffer_limit)\n            {\n                return AERON_OFFER_FULL;\n            }\n\n            AERON_SET_RELEASE(queue->producer.shared_head_cache, current_head);\n        }\n    }\n    while (!aeron_cas_uint64(&queue->producer.tail, current_tail, current_tail + 1));\n\n    const size_t index = (size_t)(current_tail & queue->mask);\n    AERON_SET_RELEASE(queue->buffer[index], element);\n\n    return AERON_OFFER_SUCCESS;\n}\n\ninline size_t aeron_mpsc_concurrent_array_queue_drain(\n    aeron_mpsc_concurrent_array_queue_t *queue, aeron_queue_drain_func_t func, void *clientd, size_t limit)\n{\n    uint64_t current_head = queue->consumer.head;\n    uint64_t next_sequence = current_head;\n    const uint64_t limit_sequence = next_sequence + limit;\n\n    while (next_sequence < limit_sequence)\n    {\n        const size_t index = (size_t)(next_sequence & queue->mask);\n        volatile void *item;\n        AERON_GET_ACQUIRE(item, queue->buffer[index]);\n\n        if (NULL == item)\n        {\n            break;\n        }\n\n        AERON_SET_RELEASE(queue->buffer[index], NULL);\n        next_sequence++;\n        AERON_SET_RELEASE(queue->consumer.head, next_sequence);\n        func(clientd, (void *)item);\n    }\n\n    return (size_t)(next_sequence - current_head);\n}\n\ninline size_t aeron_mpsc_concurrent_array_queue_drain_all(\n    aeron_mpsc_concurrent_array_queue_t *queue, aeron_queue_drain_func_t func, void *clientd)\n{\n    uint64_t current_head = queue->consumer.head;\n    uint64_t current_tail;\n    AERON_GET_ACQUIRE(current_tail, queue->producer.tail);\n\n    return aeron_mpsc_concurrent_array_queue_drain(queue, func, clientd, current_tail - current_head);\n}\n\ninline size_t aeron_mpsc_concurrent_array_queue_size(aeron_mpsc_concurrent_array_queue_t *queue)\n{\n    uint64_t current_head_before;\n    uint64_t current_tail;\n    uint64_t current_head_after;\n\n    AERON_GET_ACQUIRE(current_head_after, queue->consumer.head);\n\n    do\n    {\n        current_head_before = current_head_after;\n        AERON_GET_ACQUIRE(current_tail, queue->producer.tail);\n        AERON_GET_ACQUIRE(current_head_after, queue->consumer.head);\n    }\n    while (current_head_after != current_head_before);\n\n    size_t size = (size_t)(current_tail - current_head_after);\n    if ((int64_t)size < 0)\n    {\n        return 0;\n    }\n    else if (size > queue->capacity)\n    {\n        return queue->capacity;\n    }\n\n    return size;\n}\n\n#endif //AERON_MPSC_CONCURRENT_ARRAY_QUEUE_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_mpsc_rb.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <inttypes.h>\n#include \"aeron_mpsc_rb.h\"\n#include \"util/aeron_error.h\"\n\nint aeron_mpsc_rb_init(aeron_mpsc_rb_t *ring_buffer, void *buffer, size_t length)\n{\n    const size_t capacity = length - AERON_RB_TRAILER_LENGTH;\n    int result = -1;\n\n    if (AERON_RB_IS_CAPACITY_VALID(capacity, AERON_MPSC_RB_MIN_CAPACITY))\n    {\n        ring_buffer->buffer = buffer;\n        ring_buffer->capacity = capacity;\n        ring_buffer->descriptor = (aeron_rb_descriptor_t *)(ring_buffer->buffer + ring_buffer->capacity);\n        ring_buffer->max_message_length = AERON_RB_MAX_MESSAGE_LENGTH(ring_buffer->capacity, AERON_MPSC_RB_MIN_CAPACITY);\n        result = 0;\n    }\n    else\n    {\n        AERON_SET_ERR(EINVAL, \"Invalid capacity: %\" PRIu64, (uint64_t)capacity);\n    }\n\n    return result;\n}\n\ninline static int32_t aeron_mpsc_rb_claim_capacity(aeron_mpsc_rb_t *ring_buffer, const size_t record_length)\n{\n    const size_t required_capacity = AERON_ALIGN(record_length, AERON_RB_ALIGNMENT);\n    const size_t mask = ring_buffer->capacity - 1;\n    int64_t head = 0;\n    int64_t tail = 0;\n    size_t tail_index = 0;\n    size_t padding = 0;\n\n    AERON_GET_ACQUIRE(head, ring_buffer->descriptor->head_cache_position);\n\n    do\n    {\n        AERON_GET_ACQUIRE(tail, ring_buffer->descriptor->tail_position);\n\n        int32_t available_capacity;\n        available_capacity = (int32_t)ring_buffer->capacity - (int32_t)(tail - head);\n\n        if ((int32_t)required_capacity > available_capacity)\n        {\n            AERON_GET_ACQUIRE(head, ring_buffer->descriptor->head_position);\n\n            if (required_capacity > (ring_buffer->capacity - (int32_t)(tail - head)))\n            {\n                return -1;\n            }\n\n            AERON_SET_RELEASE(ring_buffer->descriptor->head_cache_position, head);\n        }\n\n        padding = 0;\n        tail_index = (size_t)(tail & mask);\n        size_t to_buffer_end_length = ring_buffer->capacity - tail_index;\n\n        if (required_capacity > to_buffer_end_length)\n        {\n            size_t head_index = (size_t)(head & mask);\n\n            if (required_capacity > head_index)\n            {\n                AERON_GET_ACQUIRE(head, ring_buffer->descriptor->head_position);\n                head_index = (size_t)(head & mask);\n\n                if (required_capacity > head_index)\n                {\n                    return -1;\n                }\n\n                AERON_SET_RELEASE(ring_buffer->descriptor->head_cache_position, head);\n            }\n\n            padding = to_buffer_end_length;\n        }\n    }\n    while (!aeron_cas_int64(\n        &(ring_buffer->descriptor->tail_position),\n        tail,\n        tail + (int32_t)required_capacity + (int32_t)padding));\n\n    if (0 != padding)\n    {\n        aeron_rb_record_descriptor_t *record_header =\n            (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + tail_index);\n        AERON_SET_RELEASE(record_header->length, -(int32_t)padding);\n\n        record_header->msg_type_id = AERON_RB_PADDING_MSG_TYPE_ID;\n        AERON_SET_RELEASE(record_header->length, (int32_t)padding);\n        tail_index = 0;\n    }\n\n    return (int32_t)tail_index;\n}\n\naeron_rb_write_result_t aeron_mpsc_rb_write(\n    aeron_mpsc_rb_t *ring_buffer, int32_t msg_type_id, const void *msg, size_t length)\n{\n    if (length > ring_buffer->max_message_length || AERON_RB_INVALID_MSG_TYPE_ID(msg_type_id))\n    {\n        return AERON_RB_ERROR;\n    }\n\n    const size_t record_length = length + AERON_RB_RECORD_HEADER_LENGTH;\n    const int32_t record_index = aeron_mpsc_rb_claim_capacity(ring_buffer, record_length);\n\n    if (-1 != record_index)\n    {\n        aeron_rb_record_descriptor_t *record_header =\n            (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n        AERON_SET_RELEASE(record_header->length, -(int32_t)record_length);\n\n        memcpy(ring_buffer->buffer + AERON_RB_MESSAGE_OFFSET(record_index), msg, length);\n        record_header->msg_type_id = msg_type_id;\n        AERON_SET_RELEASE(record_header->length, (int32_t)record_length);\n\n        return AERON_RB_SUCCESS;\n    }\n\n    return AERON_RB_FULL;\n}\n\nint32_t aeron_mpsc_rb_try_claim(aeron_mpsc_rb_t *ring_buffer, int32_t msg_type_id, size_t length)\n{\n    if (length > ring_buffer->max_message_length || AERON_RB_INVALID_MSG_TYPE_ID(msg_type_id))\n    {\n        return AERON_RB_ERROR;\n    }\n\n    const size_t record_length = length + AERON_RB_RECORD_HEADER_LENGTH;\n    const int32_t record_index = aeron_mpsc_rb_claim_capacity(ring_buffer, record_length);\n    if (-1 != record_index)\n    {\n        aeron_rb_record_descriptor_t *record_header =\n            (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n        AERON_SET_RELEASE(record_header->length, -(int32_t)record_length);\n        record_header->msg_type_id = msg_type_id;\n\n        return AERON_RB_MESSAGE_OFFSET(record_index);\n    }\n\n    return AERON_RB_FULL;\n}\n\nint aeron_mpsc_rb_commit(aeron_mpsc_rb_t *ring_buffer, int32_t offset)\n{\n    const int32_t record_index = offset - (int32_t)AERON_RB_RECORD_HEADER_LENGTH;\n    if (record_index < 0 || record_index > (int32_t)(ring_buffer->capacity - AERON_RB_RECORD_HEADER_LENGTH))\n    {\n        return -1;\n    }\n\n    aeron_rb_record_descriptor_t *record_header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n    const int32_t length = record_header->length;\n    if (length < 0)\n    {\n        AERON_SET_RELEASE(record_header->length, -length);\n        return 0;\n    }\n\n    return -1;\n}\n\nint aeron_mpsc_rb_abort(aeron_mpsc_rb_t *ring_buffer, int32_t offset)\n{\n    const int32_t record_index = offset - (int32_t)AERON_RB_RECORD_HEADER_LENGTH;\n    if (record_index < 0 || record_index > (int32_t)(ring_buffer->capacity - AERON_RB_RECORD_HEADER_LENGTH))\n    {\n        return -1;\n    }\n\n    aeron_rb_record_descriptor_t *record_header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n    const int32_t length = record_header->length;\n    if (length < 0)\n    {\n        record_header->msg_type_id = AERON_RB_PADDING_MSG_TYPE_ID;\n        AERON_SET_RELEASE(record_header->length, -length);\n        return 0;\n    }\n\n    return -1;\n}\n\nsize_t aeron_mpsc_rb_read(\n    aeron_mpsc_rb_t *ring_buffer, aeron_rb_handler_t handler, void *clientd, size_t message_count_limit)\n{\n    const int64_t head = ring_buffer->descriptor->head_position;\n    const size_t head_index = (size_t)(head & (ring_buffer->capacity - 1));\n    const size_t contiguous_block_length = ring_buffer->capacity - head_index;\n    size_t messages_read = 0;\n    size_t bytes_read = 0;\n\n    while ((bytes_read < contiguous_block_length) && (messages_read < message_count_limit))\n    {\n        const size_t record_index = head_index + bytes_read;\n        aeron_rb_record_descriptor_t *header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n\n        int32_t record_length;\n        AERON_GET_ACQUIRE(record_length, header->length);\n\n        if (record_length <= 0)\n        {\n            break;\n        }\n\n        bytes_read += AERON_ALIGN(record_length, AERON_RB_ALIGNMENT);\n        int32_t msg_type_id = header->msg_type_id;\n\n        if (AERON_RB_PADDING_MSG_TYPE_ID == msg_type_id)\n        {\n            continue;\n        }\n\n        ++messages_read;\n        handler(\n            msg_type_id,\n            ring_buffer->buffer + AERON_RB_MESSAGE_OFFSET(record_index),\n            record_length - AERON_RB_RECORD_HEADER_LENGTH,\n            clientd);\n    }\n\n    if (0 != bytes_read)\n    {\n        memset(ring_buffer->buffer + head_index, 0, bytes_read);\n        AERON_SET_RELEASE(ring_buffer->descriptor->head_position, head + bytes_read);\n    }\n\n    return messages_read;\n}\n\nsize_t aeron_mpsc_rb_controlled_read(\n    aeron_mpsc_rb_t *ring_buffer,\n    aeron_rb_controlled_handler_t handler,\n    void *clientd,\n    size_t message_count_limit)\n{\n    int64_t head = ring_buffer->descriptor->head_position;\n    size_t head_index = (size_t)(head & (ring_buffer->capacity - 1));\n    const size_t contiguous_block_length = ring_buffer->capacity - head_index;\n    size_t messages_read = 0;\n    size_t bytes_read = 0;\n\n    while ((bytes_read < contiguous_block_length) && (messages_read < message_count_limit))\n    {\n        const size_t record_index = head_index + bytes_read;\n        aeron_rb_record_descriptor_t *header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n\n        int32_t record_length;\n        AERON_GET_ACQUIRE(record_length, header->length);\n\n        if (record_length <= 0)\n        {\n            break;\n        }\n\n        const size_t aligned_length = AERON_ALIGN(record_length, AERON_RB_ALIGNMENT);\n        bytes_read += aligned_length;\n        int32_t msg_type_id = header->msg_type_id;\n\n        if (AERON_RB_PADDING_MSG_TYPE_ID == msg_type_id)\n        {\n            continue;\n        }\n\n        aeron_rb_read_action_t action = handler(\n            msg_type_id,\n            ring_buffer->buffer + AERON_RB_MESSAGE_OFFSET(record_index),\n            record_length - AERON_RB_RECORD_HEADER_LENGTH,\n            clientd);\n\n        if (AERON_RB_ABORT == action)\n        {\n            bytes_read -= aligned_length;\n            break;\n        }\n\n        ++messages_read;\n\n        if (AERON_RB_BREAK == action)\n        {\n            break;\n        }\n        if (AERON_RB_COMMIT == action)\n        {\n            memset(ring_buffer->buffer + head_index, 0, bytes_read);\n            AERON_SET_RELEASE(ring_buffer->descriptor->head_position, head + bytes_read);\n            head_index += bytes_read;\n            head += (int64_t)bytes_read;\n            bytes_read = 0;\n        }\n    }\n\n    if (0 != bytes_read)\n    {\n        memset(ring_buffer->buffer + head_index, 0, bytes_read);\n        AERON_SET_RELEASE(ring_buffer->descriptor->head_position, head + bytes_read);\n    }\n\n    return messages_read;\n}\n\nint64_t aeron_mpsc_rb_next_correlation_id(aeron_mpsc_rb_t *ring_buffer)\n{\n    int64_t result;\n\n    // this is aligned as far as usage goes. And should perform fine.\n#if defined(__clang__) && defined(AERON_CPU_ARM)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Watomic-alignment\"\n#endif\n    AERON_GET_AND_ADD_INT64(result, ring_buffer->descriptor->correlation_counter, INT64_C(1));\n#if defined(__clang__) && defined(AERON_CPU_ARM)\n#pragma clang diagnostic pop\n#endif\n\n    return result;\n}\n\nvoid aeron_mpsc_rb_consumer_heartbeat_time(aeron_mpsc_rb_t *ring_buffer, int64_t now_ms)\n{\n    AERON_SET_RELEASE(ring_buffer->descriptor->consumer_heartbeat, now_ms);\n}\n\nint64_t aeron_mpsc_rb_consumer_heartbeat_time_value(aeron_mpsc_rb_t *ring_buffer)\n{\n    int64_t value;\n    AERON_GET_ACQUIRE(value, ring_buffer->descriptor->consumer_heartbeat);\n    return value;\n}\n\ninline static bool scan_back_to_confirm_still_zeroed(const uint8_t *buffer, size_t from, size_t limit)\n{\n    size_t i = from - AERON_RB_ALIGNMENT;\n    bool all_zeroes = true;\n\n    while (i >= limit)\n    {\n        const aeron_rb_record_descriptor_t *record = (aeron_rb_record_descriptor_t *)(buffer + i);\n        int32_t length;\n        AERON_GET_ACQUIRE(length, record->length);\n        if (0 != length)\n        {\n            all_zeroes = false;\n            break;\n        }\n\n        i -= AERON_RB_ALIGNMENT;\n    }\n\n    return all_zeroes;\n}\n\nbool aeron_mpsc_rb_unblock(aeron_mpsc_rb_t *ring_buffer)\n{\n    int64_t head;\n    AERON_GET_ACQUIRE(head, ring_buffer->descriptor->head_position);\n    int64_t tail;\n    AERON_GET_ACQUIRE(tail, ring_buffer->descriptor->tail_position);\n\n    if (head == tail)\n    {\n        return false;\n    }\n\n    bool unblocked = false;\n    const size_t mask = ring_buffer->capacity - 1;\n    size_t consumer_index = (size_t)(head & mask);\n    size_t producer_index = (size_t)(tail & mask);\n\n    int32_t length;\n    aeron_rb_record_descriptor_t *record = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + consumer_index);\n\n    AERON_GET_ACQUIRE(length, record->length);\n    if (length < 0)\n    {\n        record->msg_type_id = AERON_RB_PADDING_MSG_TYPE_ID;\n        AERON_SET_RELEASE(record->length, -length);\n        unblocked = true;\n    }\n    else if (0 == length)\n    {\n        const size_t limit = producer_index > consumer_index ? producer_index : ring_buffer->capacity;\n        size_t i = consumer_index + AERON_RB_ALIGNMENT;\n\n        do\n        {\n            record = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + i);\n            AERON_GET_ACQUIRE(length, record->length);\n            if (0 != length)\n            {\n                if (scan_back_to_confirm_still_zeroed(ring_buffer->buffer, i, consumer_index))\n                {\n                    record = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + consumer_index);\n                    record->msg_type_id = AERON_RB_PADDING_MSG_TYPE_ID;\n                    AERON_SET_RELEASE(record->length, (int32_t)(i - consumer_index));\n                    unblocked = true;\n                }\n\n                break;\n            }\n\n            i += AERON_RB_ALIGNMENT;\n        }\n        while (i < limit);\n    }\n\n    return unblocked;\n}\n\nextern int64_t aeron_mpsc_rb_consumer_position(aeron_mpsc_rb_t *ring_buffer);\n\nextern int64_t aeron_mpsc_rb_producer_position(aeron_mpsc_rb_t *ring_buffer);\n\nextern int64_t aeron_mpsc_rb_size(aeron_mpsc_rb_t *ring_buffer);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_mpsc_rb.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_MPSC_RB_H\n#define AERON_MPSC_RB_H\n\n#include \"concurrent/aeron_rb.h\"\n\n#define AERON_MPSC_RB_MIN_CAPACITY (AERON_RB_RECORD_HEADER_LENGTH)\n\nstruct aeron_mpsc_rb_stct\n{\n    uint8_t *buffer;\n    aeron_rb_descriptor_t *descriptor;\n    size_t capacity;\n    size_t max_message_length;\n};\ntypedef struct aeron_mpsc_rb_stct aeron_mpsc_rb_t;\n\nint aeron_mpsc_rb_init(aeron_mpsc_rb_t *ring_buffer, void *buffer, size_t length);\n\naeron_rb_write_result_t aeron_mpsc_rb_write(\n    aeron_mpsc_rb_t *ring_buffer,\n    int32_t msg_type_id,\n    const void *msg,\n    size_t length);\n\nint32_t aeron_mpsc_rb_try_claim(aeron_mpsc_rb_t *ring_buffer, int32_t msg_type_id, size_t length);\n\nint aeron_mpsc_rb_commit(aeron_mpsc_rb_t *ring_buffer, int32_t offset);\n\nint aeron_mpsc_rb_abort(aeron_mpsc_rb_t *ring_buffer, int32_t offset);\n\nsize_t aeron_mpsc_rb_read(\n    aeron_mpsc_rb_t *ring_buffer,\n    aeron_rb_handler_t handler,\n    void *clientd,\n    size_t message_count_limit);\n\nsize_t aeron_mpsc_rb_controlled_read(\n    aeron_mpsc_rb_t *ring_buffer,\n    aeron_rb_controlled_handler_t handler,\n    void *clientd,\n    size_t message_count_limit);\n\nint64_t aeron_mpsc_rb_next_correlation_id(aeron_mpsc_rb_t *ring_buffer);\n\nvoid aeron_mpsc_rb_consumer_heartbeat_time(aeron_mpsc_rb_t *ring_buffer, int64_t now_ms);\nint64_t aeron_mpsc_rb_consumer_heartbeat_time_value(aeron_mpsc_rb_t *ring_buffer);\n\nbool aeron_mpsc_rb_unblock(aeron_mpsc_rb_t *ring_buffer);\n\ninline int64_t aeron_mpsc_rb_consumer_position(aeron_mpsc_rb_t *ring_buffer)\n{\n    int64_t position;\n    AERON_GET_ACQUIRE(position, ring_buffer->descriptor->head_position);\n    return position;\n}\n\ninline int64_t aeron_mpsc_rb_producer_position(aeron_mpsc_rb_t *ring_buffer)\n{\n    int64_t position;\n    AERON_GET_ACQUIRE(position, ring_buffer->descriptor->tail_position);\n    return position;\n}\n\ninline int64_t aeron_mpsc_rb_size(aeron_mpsc_rb_t *ring_buffer)\n{\n    int64_t consumer_position_before;\n    int64_t producer_position;\n    int64_t consumer_position_after;\n\n    do\n    {\n        consumer_position_before = aeron_mpsc_rb_consumer_position(ring_buffer);\n        producer_position = aeron_mpsc_rb_producer_position(ring_buffer);\n        consumer_position_after = aeron_mpsc_rb_consumer_position(ring_buffer);\n    }\n    while (consumer_position_before != consumer_position_after);\n\n    const int64_t size = producer_position - consumer_position_after;\n\n    if (size < 0)\n    {\n        return 0;\n    }\n    else if (size > (int64_t)ring_buffer->capacity)\n    {\n        return (int64_t)ring_buffer->capacity;\n    }\n\n    return size;\n}\n\n#endif //AERON_MPSC_RB_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_rb.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_RB_H\n#define AERON_RB_H\n\n#include <string.h>\n#include \"util/aeron_bitutil.h\"\n#include \"concurrent/aeron_atomic.h\"\n\n#pragma pack(push)\n#pragma pack(4)\nstruct aeron_rb_descriptor_stct\n{\n    uint8_t begin_pad[(2 * AERON_CACHE_LINE_LENGTH)];\n    volatile int64_t tail_position;\n    uint8_t tail_pad[(2 * AERON_CACHE_LINE_LENGTH) - sizeof(int64_t)];\n    volatile int64_t head_cache_position;\n    uint8_t head_cache_pad[(2 * AERON_CACHE_LINE_LENGTH) - sizeof(int64_t)];\n    volatile int64_t head_position;\n    uint8_t head_pad[(2 * AERON_CACHE_LINE_LENGTH) - sizeof(int64_t)];\n    volatile int64_t correlation_counter;\n    uint8_t correlation_counter_pad[(2 * AERON_CACHE_LINE_LENGTH) - sizeof(int64_t)];\n    volatile int64_t consumer_heartbeat;\n    uint8_t consumer_heartbeat_pad[(2 * AERON_CACHE_LINE_LENGTH) - sizeof(int64_t)];\n};\ntypedef struct aeron_rb_descriptor_stct aeron_rb_descriptor_t;\n\nstruct aeron_rb_record_descriptor_stct\n{\n    volatile int32_t length;\n    int32_t msg_type_id;\n};\ntypedef struct aeron_rb_record_descriptor_stct aeron_rb_record_descriptor_t;\n#pragma pack(pop)\n\n#define AERON_RB_TRAILER_LENGTH (sizeof(aeron_rb_descriptor_t))\n\n#define AERON_RB_ALIGNMENT (2 * sizeof(int32_t))\n\n#define AERON_RB_MESSAGE_OFFSET(index) ((index) + sizeof(aeron_rb_record_descriptor_t))\n#define AERON_RB_RECORD_HEADER_LENGTH (sizeof(aeron_rb_record_descriptor_t))\n\n#define AERON_RB_MAX_MESSAGE_LENGTH(capacity, min_capacity) (capacity) == (min_capacity) ? 0 : ((capacity) / 8)\n#define AERON_RB_INVALID_MSG_TYPE_ID(id) ((id) < 1)\n#define AERON_RB_PADDING_MSG_TYPE_ID (-1)\n\nenum aeron_rb_write_result_stct\n{\n    AERON_RB_SUCCESS = 0,\n    AERON_RB_ERROR = -2,\n    AERON_RB_FULL = -1\n};\ntypedef enum aeron_rb_write_result_stct aeron_rb_write_result_t;\n\ntypedef void (*aeron_rb_handler_t)(int32_t, const void *, size_t, void *);\n\nenum aeron_rb_read_action_stct\n{\n    AERON_RB_ABORT, AERON_RB_BREAK, AERON_RB_COMMIT, AERON_RB_CONTINUE\n};\ntypedef enum aeron_rb_read_action_stct aeron_rb_read_action_t;\n\ntypedef aeron_rb_read_action_t (*aeron_rb_controlled_handler_t)(int32_t, const void *, size_t, void *);\n\n#define AERON_RB_IS_CAPACITY_VALID(capacity, min_capacity) AERON_IS_POWER_OF_TWO(capacity) && (capacity) >= (min_capacity)\n\n#endif //AERON_RB_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_spsc_concurrent_array_queue.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_alloc.h\"\n#include \"concurrent/aeron_spsc_concurrent_array_queue.h\"\n\nint aeron_spsc_concurrent_array_queue_init(aeron_spsc_concurrent_array_queue_t *queue, size_t length)\n{\n    length = (size_t)aeron_find_next_power_of_two((int32_t)length);\n\n    if (aeron_alloc((void **)&queue->buffer, sizeof(void *) * length) < 0)\n    {\n        return -1;\n    }\n\n    for (size_t i = 0; i < length; i++)\n    {\n        queue->buffer[i] = NULL;\n    }\n\n    queue->capacity = length;\n    queue->mask = length - 1;\n    queue->producer.head_cache = 0;\n    AERON_SET_RELEASE(queue->producer.tail, (uint64_t)0);\n    AERON_SET_RELEASE(queue->consumer.head, (uint64_t)0);\n\n    return 0;\n}\n\nint aeron_spsc_concurrent_array_queue_close(aeron_spsc_concurrent_array_queue_t *queue)\n{\n    aeron_free((void *)queue->buffer);\n    return 0;\n}\n\nextern aeron_queue_offer_result_t aeron_spsc_concurrent_array_queue_offer(\n    aeron_spsc_concurrent_array_queue_t *queue, void *element);\n\nextern void *aeron_spsc_concurrent_array_queue_poll(aeron_spsc_concurrent_array_queue_t *queue);\n\nextern size_t aeron_spsc_concurrent_array_queue_drain(\n    aeron_spsc_concurrent_array_queue_t *queue, aeron_queue_drain_func_t func, void *clientd, size_t limit);\n\nextern size_t aeron_spsc_concurrent_array_queue_drain_all(\n    aeron_spsc_concurrent_array_queue_t *queue, aeron_queue_drain_func_t func, void *clientd);\n\nextern size_t aeron_spsc_concurrent_array_queue_size(aeron_spsc_concurrent_array_queue_t *queue);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_spsc_concurrent_array_queue.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_SPSC_CONCURRENT_ARRAY_QUEUE_H\n#define AERON_SPSC_CONCURRENT_ARRAY_QUEUE_H\n\n#include \"util/aeron_bitutil.h\"\n#include \"aeron_atomic.h\"\n#include \"aeron_concurrent_array_queue.h\"\n\ntypedef struct aeron_spsc_concurrent_array_queue_stct\n{\n\n    int8_t padding1[AERON_CACHE_LINE_LENGTH];\n\n    struct\n    {\n        volatile uint64_t tail;\n        uint64_t head_cache;\n    }\n    producer;\n\n    int8_t padding2[AERON_CACHE_LINE_LENGTH];\n\n    struct\n    {\n        volatile uint64_t head;\n    }\n    consumer;\n\n    int8_t padding3[AERON_CACHE_LINE_LENGTH];\n\n    size_t capacity;\n    size_t mask;\n    void * volatile *buffer;\n}\naeron_spsc_concurrent_array_queue_t;\n\nint aeron_spsc_concurrent_array_queue_init(aeron_spsc_concurrent_array_queue_t *queue, size_t length);\n\nint aeron_spsc_concurrent_array_queue_close(aeron_spsc_concurrent_array_queue_t *queue);\n\ninline aeron_queue_offer_result_t aeron_spsc_concurrent_array_queue_offer(\n    aeron_spsc_concurrent_array_queue_t *queue, void *element)\n{\n    if (NULL == element)\n    {\n        return AERON_OFFER_ERROR;\n    }\n\n    uint64_t current_head = queue->producer.head_cache;\n    uint64_t buffer_limit = current_head + queue->capacity;\n    uint64_t current_tail = queue->producer.tail;\n\n    if (current_tail >= buffer_limit)\n    {\n        AERON_GET_ACQUIRE(current_head, queue->consumer.head);\n        buffer_limit = current_head + queue->capacity;\n\n        if (current_tail >= buffer_limit)\n        {\n            return AERON_OFFER_FULL;\n        }\n\n        queue->producer.head_cache = current_head;\n    }\n\n    const size_t index = (size_t)(current_tail & queue->mask);\n\n    AERON_SET_RELEASE(queue->buffer[index], element);\n    AERON_SET_RELEASE(queue->producer.tail, current_tail + 1);\n\n    return AERON_OFFER_SUCCESS;\n}\n\ninline void *aeron_spsc_concurrent_array_queue_poll(aeron_spsc_concurrent_array_queue_t *queue)\n{\n    const uint64_t current_head = queue->consumer.head;\n    const size_t index = (size_t)(current_head & queue->mask);\n\n    volatile void *item;\n    AERON_GET_ACQUIRE(item, queue->buffer[index]);\n\n    if (NULL != item)\n    {\n        AERON_SET_RELEASE(queue->buffer[index], NULL);\n        AERON_SET_RELEASE(queue->consumer.head, current_head + 1);\n    }\n\n    return (void *)item;\n}\n\ninline size_t aeron_spsc_concurrent_array_queue_drain(\n    aeron_spsc_concurrent_array_queue_t *queue, aeron_queue_drain_func_t func, void *clientd, size_t limit)\n{\n    uint64_t current_head = queue->consumer.head;\n    uint64_t next_sequence = current_head;\n    const uint64_t limit_sequence = next_sequence + limit;\n\n    while (next_sequence < limit_sequence)\n    {\n        const size_t index = (size_t)(next_sequence & queue->mask);\n        volatile void *item;\n        AERON_GET_ACQUIRE(item, queue->buffer[index]);\n\n        if (NULL == item)\n        {\n            break;\n        }\n\n        AERON_SET_RELEASE(queue->buffer[index], NULL);\n        next_sequence++;\n        AERON_SET_RELEASE(queue->consumer.head, next_sequence);\n        func(clientd, (void *)item);\n    }\n\n    return next_sequence - current_head;\n}\n\ninline size_t aeron_spsc_concurrent_array_queue_drain_all(\n    aeron_spsc_concurrent_array_queue_t *queue, aeron_queue_drain_func_t func, void *clientd)\n{\n    uint64_t current_head = queue->consumer.head;\n    uint64_t current_tail;\n    AERON_GET_ACQUIRE(current_tail, queue->producer.tail);\n\n    return aeron_spsc_concurrent_array_queue_drain(queue, func, clientd, current_tail - current_head);\n}\n\ninline size_t aeron_spsc_concurrent_array_queue_size(aeron_spsc_concurrent_array_queue_t *queue)\n{\n    uint64_t current_head_before;\n    uint64_t current_tail;\n    uint64_t current_head_after;\n\n    AERON_GET_ACQUIRE(current_head_after, queue->consumer.head);\n\n    do\n    {\n        current_head_before = current_head_after;\n        AERON_GET_ACQUIRE(current_tail, queue->producer.tail);\n        AERON_GET_ACQUIRE(current_head_after, queue->consumer.head);\n    }\n    while (current_head_after != current_head_before);\n\n    size_t size = (size_t)(current_tail - current_head_after);\n    if ((int64_t)size < 0)\n    {\n        return 0;\n    }\n    else if (size > queue->capacity)\n    {\n        return queue->capacity;\n    }\n\n    return size;\n}\n\n#endif //AERON_SPSC_CONCURRENT_ARRAY_QUEUE_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_spsc_rb.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <inttypes.h>\n#include \"aeron_spsc_rb.h\"\n#include \"util/aeron_error.h\"\n\nint aeron_spsc_rb_init(aeron_spsc_rb_t *ring_buffer, void *buffer, size_t length)\n{\n    const size_t capacity = length - AERON_RB_TRAILER_LENGTH;\n    int result = -1;\n\n    if (AERON_RB_IS_CAPACITY_VALID(capacity, AERON_SPSC_RB_MIN_CAPACITY))\n    {\n        ring_buffer->buffer = buffer;\n        ring_buffer->capacity = capacity;\n        ring_buffer->descriptor = (aeron_rb_descriptor_t *)(ring_buffer->buffer + ring_buffer->capacity);\n        ring_buffer->max_message_length = AERON_RB_MAX_MESSAGE_LENGTH(ring_buffer->capacity, AERON_SPSC_RB_MIN_CAPACITY);\n        result = 0;\n    }\n    else\n    {\n        AERON_SET_ERR(EINVAL, \"Invalid capacity: %\" PRIu64, (uint64_t)capacity);\n    }\n\n    return result;\n}\n\naeron_rb_write_result_t aeron_spsc_rb_write(\n    aeron_spsc_rb_t *ring_buffer, int32_t msg_type_id, const void *msg, size_t length)\n{\n    struct iovec vec[1];\n    vec[0].iov_len = length;\n    vec[0].iov_base = (void *)msg;\n\n    return aeron_spsc_rb_writev(ring_buffer, msg_type_id, vec, 1);\n}\n\ninline static int32_t aeron_spsc_rb_claim_capacity(aeron_spsc_rb_t *ring_buffer, const size_t record_length)\n{\n    const size_t aligned_record_length = AERON_ALIGN(record_length, AERON_RB_ALIGNMENT);\n    const size_t required_capacity = aligned_record_length + AERON_RB_RECORD_HEADER_LENGTH;\n    const size_t mask = ring_buffer->capacity - 1;\n\n    int64_t head = ring_buffer->descriptor->head_cache_position;\n    int64_t tail = ring_buffer->descriptor->tail_position;\n    const int32_t available_capacity = (int32_t)ring_buffer->capacity - (int32_t)(tail - head);\n\n    size_t padding = 0;\n    size_t record_index = (size_t)tail & mask;\n    const size_t to_buffer_end_length = ring_buffer->capacity - record_index;\n    aeron_rb_record_descriptor_t *record_header, *next_header = NULL;\n\n    if ((int32_t)required_capacity > available_capacity)\n    {\n        AERON_GET_ACQUIRE(head, ring_buffer->descriptor->head_position);\n\n        if (required_capacity > (ring_buffer->capacity - (size_t)(tail - head)))\n        {\n            return -1;\n        }\n\n        ring_buffer->descriptor->head_cache_position = head;\n    }\n\n    int64_t next_tail = tail + (int64_t)aligned_record_length;\n    int32_t write_index = (int32_t)record_index;\n    if (aligned_record_length == to_buffer_end_length) // message fits within the end of the buffer\n    {\n        AERON_SET_RELEASE(ring_buffer->descriptor->tail_position, next_tail);\n        // pre-zero next message header\n        next_header = (aeron_rb_record_descriptor_t *)ring_buffer->buffer;\n\n        next_header->length = 0;\n        next_header->msg_type_id = 0;\n        return (int32_t)record_index;\n    }\n    else if (required_capacity > to_buffer_end_length)\n    {\n        write_index = 0;\n        size_t head_index = (size_t)(head & mask);\n\n        if (required_capacity > head_index)\n        {\n            AERON_GET_ACQUIRE(head, ring_buffer->descriptor->head_position);\n            head_index = (size_t)(head & mask);\n\n            if (required_capacity > head_index)\n            {\n                write_index = -1;\n                next_tail = tail;\n            }\n\n            ring_buffer->descriptor->head_cache_position = head;\n        }\n\n        padding = to_buffer_end_length;\n        next_tail += (int64_t)padding;\n    }\n\n    AERON_SET_RELEASE(ring_buffer->descriptor->tail_position, next_tail);\n\n    if (0 != padding)\n    {\n        record_header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n        next_header = (aeron_rb_record_descriptor_t *)ring_buffer->buffer;\n\n        next_header->length = 0;\n        next_header->msg_type_id = 0;\n        AERON_SET_RELEASE(record_header->length, -(int32_t)padding);\n        record_header->msg_type_id = AERON_RB_PADDING_MSG_TYPE_ID;\n        AERON_SET_RELEASE(record_header->length, (int32_t)padding);\n        record_index = 0;\n    }\n\n    if (-1 != write_index)\n    {\n        next_header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + write_index + aligned_record_length);\n\n        next_header->length = 0;\n        next_header->msg_type_id = 0;\n    }\n\n    return (int32_t)record_index;\n}\n\naeron_rb_write_result_t aeron_spsc_rb_writev(\n    aeron_spsc_rb_t *ring_buffer, int32_t msg_type_id, const struct iovec *iov, int iovcnt)\n{\n    size_t length = 0;\n    for (int i = 0; i < iovcnt; i++)\n    {\n        length += iov[i].iov_len;\n    }\n\n    if (length > ring_buffer->max_message_length || AERON_RB_INVALID_MSG_TYPE_ID(msg_type_id))\n    {\n        return AERON_RB_ERROR;\n    }\n\n    const size_t record_length = length + AERON_RB_RECORD_HEADER_LENGTH;\n    const int32_t record_index = aeron_spsc_rb_claim_capacity(ring_buffer, record_length);\n    if (-1 != record_index)\n    {\n        aeron_rb_record_descriptor_t *record_header =\n            (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n        AERON_SET_RELEASE(record_header->length, -(int32_t)record_length);\n\n        size_t current_vector_offset = 0;\n        for (int i = 0; i < iovcnt; i++)\n        {\n            uint8_t *offset = ring_buffer->buffer + AERON_RB_MESSAGE_OFFSET(record_index) + current_vector_offset;\n            memcpy(offset, iov[i].iov_base, iov[i].iov_len);\n            current_vector_offset += iov[i].iov_len;\n        }\n\n        record_header->msg_type_id = msg_type_id;\n        AERON_SET_RELEASE(record_header->length, (int32_t)record_length);\n\n        return AERON_RB_SUCCESS;\n    }\n\n    return AERON_RB_FULL;\n}\n\nint32_t aeron_spsc_rb_try_claim(aeron_spsc_rb_t *ring_buffer, int32_t msg_type_id, size_t length)\n{\n    if (length > ring_buffer->max_message_length || AERON_RB_INVALID_MSG_TYPE_ID(msg_type_id))\n    {\n        return AERON_RB_ERROR;\n    }\n\n    const size_t record_length = length + AERON_RB_RECORD_HEADER_LENGTH;\n    const int32_t record_index = aeron_spsc_rb_claim_capacity(ring_buffer, record_length);\n    if (-1 != record_index)\n    {\n        aeron_rb_record_descriptor_t *record_header =\n            (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n        AERON_SET_RELEASE(record_header->length, -(int32_t)record_length);\n        record_header->msg_type_id = msg_type_id;\n\n        return AERON_RB_MESSAGE_OFFSET(record_index);\n    }\n\n    return AERON_RB_FULL;\n}\n\nint aeron_spsc_rb_commit(aeron_spsc_rb_t *ring_buffer, int32_t offset)\n{\n    const int32_t record_index = offset - (int32_t)AERON_RB_RECORD_HEADER_LENGTH;\n    if (record_index < 0 || record_index > (int32_t)(ring_buffer->capacity - AERON_RB_RECORD_HEADER_LENGTH))\n    {\n        return -1;\n    }\n\n    aeron_rb_record_descriptor_t *record_header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n    const int32_t length = record_header->length;\n    if (length < 0)\n    {\n        AERON_SET_RELEASE(record_header->length, -length);\n        return 0;\n    }\n\n    return -1;\n}\n\nint aeron_spsc_rb_abort(aeron_spsc_rb_t *ring_buffer, int32_t offset)\n{\n    const int32_t record_index = offset - (int32_t)AERON_RB_RECORD_HEADER_LENGTH;\n    if (record_index < 0 || record_index > (int32_t)(ring_buffer->capacity - AERON_RB_RECORD_HEADER_LENGTH))\n    {\n        return -1;\n    }\n\n    aeron_rb_record_descriptor_t *record_header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n    const int32_t length = record_header->length;\n    if (length < 0)\n    {\n        record_header->msg_type_id = AERON_RB_PADDING_MSG_TYPE_ID;\n        AERON_SET_RELEASE(record_header->length, -length);\n        return 0;\n    }\n\n    return -1;\n}\n\nsize_t aeron_spsc_rb_read(\n    aeron_spsc_rb_t *ring_buffer, aeron_rb_handler_t handler, void *clientd, size_t message_count_limit)\n{\n    const int64_t head = ring_buffer->descriptor->head_position;\n    const size_t head_index = (size_t)(head & (ring_buffer->capacity - 1));\n    const size_t contiguous_block_length = ring_buffer->capacity - head_index;\n    size_t messages_read = 0;\n    size_t bytes_read = 0;\n\n    while ((bytes_read < contiguous_block_length) && (messages_read < message_count_limit))\n    {\n        const size_t record_index = head_index + bytes_read;\n        aeron_rb_record_descriptor_t *header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n        int32_t record_length;\n        AERON_GET_ACQUIRE(record_length, header->length);\n\n        if (record_length <= 0)\n        {\n            break;\n        }\n\n        bytes_read += AERON_ALIGN(record_length, AERON_RB_ALIGNMENT);\n        int32_t msg_type_id = header->msg_type_id;\n\n        if (AERON_RB_PADDING_MSG_TYPE_ID == msg_type_id)\n        {\n            continue;\n        }\n\n        ++messages_read;\n        handler(\n            msg_type_id,\n            ring_buffer->buffer + AERON_RB_MESSAGE_OFFSET(record_index),\n            record_length - AERON_RB_RECORD_HEADER_LENGTH,\n            clientd);\n    }\n\n    if (0 != bytes_read)\n    {\n        AERON_SET_RELEASE(ring_buffer->descriptor->head_position, head + bytes_read);\n    }\n\n    return messages_read;\n}\n\nsize_t aeron_spsc_rb_controlled_read(\n    aeron_spsc_rb_t *ring_buffer, aeron_rb_controlled_handler_t handler, void *clientd, size_t message_count_limit)\n{\n    int64_t head = ring_buffer->descriptor->head_position;\n    size_t head_index = (size_t)(head & (ring_buffer->capacity - 1));\n    const size_t contiguous_block_length = ring_buffer->capacity - head_index;\n    size_t messages_read = 0;\n    size_t bytes_read = 0;\n\n    while ((bytes_read < contiguous_block_length) && (messages_read < message_count_limit))\n    {\n        const size_t record_index = head_index + bytes_read;\n        aeron_rb_record_descriptor_t *header = (aeron_rb_record_descriptor_t *)(ring_buffer->buffer + record_index);\n        int32_t record_length;\n        AERON_GET_ACQUIRE(record_length, header->length);\n\n        if (record_length <= 0)\n        {\n            break;\n        }\n\n        const size_t aligned_length = AERON_ALIGN(record_length, AERON_RB_ALIGNMENT);\n        bytes_read += aligned_length;\n        int32_t msg_type_id = header->msg_type_id;\n\n        if (AERON_RB_PADDING_MSG_TYPE_ID == msg_type_id)\n        {\n            continue;\n        }\n\n        aeron_rb_read_action_t action = handler(\n            msg_type_id,\n            ring_buffer->buffer + AERON_RB_MESSAGE_OFFSET(record_index),\n            record_length - AERON_RB_RECORD_HEADER_LENGTH,\n            clientd);\n\n        if (AERON_RB_ABORT == action)\n        {\n            bytes_read -= aligned_length;\n            break;\n        }\n        \n        ++messages_read;\n\n        if (AERON_RB_BREAK == action)\n        {\n            break;\n        }\n        if (AERON_RB_COMMIT == action)\n        {\n            AERON_SET_RELEASE(ring_buffer->descriptor->head_position, head + bytes_read);\n            head_index += bytes_read;\n            head += (int64_t)bytes_read;\n            bytes_read = 0;\n        }\n    }\n\n    if (0 != bytes_read)\n    {\n        AERON_SET_RELEASE(ring_buffer->descriptor->head_position, head + bytes_read);\n    }\n\n    return messages_read;\n}\n\nint64_t aeron_spsc_rb_next_correlation_id(aeron_spsc_rb_t *ring_buffer)\n{\n    int64_t result;\n\n    // this is aligned as far as usage goes. And should perform fine.\n#if defined(__clang__) && defined(AERON_CPU_ARM)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Watomic-alignment\"\n#endif\n    AERON_GET_AND_ADD_INT64(result, ring_buffer->descriptor->correlation_counter, INT64_C(1));\n#if defined(__clang__) && defined(AERON_CPU_ARM)\n#pragma clang diagnostic pop\n#endif\n\n    return result;\n}\n\nvoid aeron_spsc_rb_consumer_heartbeat_time(aeron_spsc_rb_t *ring_buffer, int64_t time_ms)\n{\n    AERON_SET_RELEASE(ring_buffer->descriptor->consumer_heartbeat, time_ms);\n}\n\nextern int64_t aeron_spsc_rb_consumer_position(aeron_spsc_rb_t *ring_buffer);\n\nextern int64_t aeron_spsc_rb_producer_position(aeron_spsc_rb_t *ring_buffer);\n\nextern int64_t aeron_spsc_rb_size(aeron_spsc_rb_t *ring_buffer);\n\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_spsc_rb.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_SPSC_RB_H\n#define AERON_SPSC_RB_H\n\n#include \"concurrent/aeron_rb.h\"\n\n#if !defined(_MSC_VER)\n#include <sys/uio.h>\n#else\nstruct iovec\n{\n    void  *iov_base;\n    size_t iov_len;\n};\n#endif\n\n#define AERON_SPSC_RB_MIN_CAPACITY (2 * AERON_RB_RECORD_HEADER_LENGTH)\n\nstruct aeron_spsc_rb_stct\n{\n    uint8_t *buffer;\n    aeron_rb_descriptor_t *descriptor;\n    size_t capacity;\n    size_t max_message_length;\n};\ntypedef struct aeron_spsc_rb_stct aeron_spsc_rb_t;\n\nint aeron_spsc_rb_init(aeron_spsc_rb_t *ring_buffer, void *buffer, size_t length);\n\naeron_rb_write_result_t aeron_spsc_rb_write(\n    aeron_spsc_rb_t *ring_buffer, int32_t msg_type_id, const void *msg, size_t length);\n\naeron_rb_write_result_t aeron_spsc_rb_writev(\n    aeron_spsc_rb_t *ring_buffer, int32_t msg_type_id, const struct iovec* iov, int iovcnt);\n\nint32_t aeron_spsc_rb_try_claim(aeron_spsc_rb_t *ring_buffer, int32_t msg_type_id, size_t length);\n\nint aeron_spsc_rb_commit(aeron_spsc_rb_t *ring_buffer, int32_t offset);\n\nint aeron_spsc_rb_abort(aeron_spsc_rb_t *ring_buffer, int32_t offset);\n\nsize_t aeron_spsc_rb_read(\n    aeron_spsc_rb_t *ring_buffer, aeron_rb_handler_t handler, void *clientd, size_t message_count_limit);\n\nsize_t aeron_spsc_rb_controlled_read(\n    aeron_spsc_rb_t *ring_buffer, aeron_rb_controlled_handler_t handler, void *clientd, size_t message_count_limit);\n\nint64_t aeron_spsc_rb_next_correlation_id(aeron_spsc_rb_t *ring_buffer);\n\nvoid aeron_spsc_rb_consumer_heartbeat_time(aeron_spsc_rb_t *ring_buffer, int64_t time_ms);\n\ninline int64_t aeron_spsc_rb_consumer_position(aeron_spsc_rb_t *ring_buffer)\n{\n    int64_t position;\n    AERON_GET_ACQUIRE(position, ring_buffer->descriptor->head_position);\n    return position;\n}\n\ninline int64_t aeron_spsc_rb_producer_position(aeron_spsc_rb_t *ring_buffer)\n{\n    int64_t position;\n    AERON_GET_ACQUIRE(position, ring_buffer->descriptor->tail_position);\n    return position;\n}\n\ninline int64_t aeron_spsc_rb_size(aeron_spsc_rb_t *ring_buffer)\n{\n    int64_t consumer_position_before;\n    int64_t producer_position;\n    int64_t consumer_position_after;\n\n    do\n    {\n        consumer_position_before = aeron_spsc_rb_consumer_position(ring_buffer);\n        producer_position = aeron_spsc_rb_producer_position(ring_buffer);\n        consumer_position_after = aeron_spsc_rb_consumer_position(ring_buffer);\n    }\n    while (consumer_position_before != consumer_position_after);\n\n    const int64_t size = producer_position - consumer_position_after;\n\n    if (size < 0)\n    {\n        return 0;\n    }\n    else if (size > (int64_t)ring_buffer->capacity)\n    {\n        return (int64_t)ring_buffer->capacity;\n    }\n\n    return size;\n}\n\n#endif //AERON_SPSC_RB_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_gap_filler.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"concurrent/aeron_term_gap_filler.h\"\n\nbool aeron_term_gap_filler_try_fill_gap(\n    aeron_logbuffer_metadata_t *log_meta_data,\n    uint8_t *buffer,\n    int32_t term_id,\n    int32_t gap_offset,\n    int32_t gap_length)\n{\n    int32_t offset = (gap_offset + gap_length) - AERON_LOGBUFFER_FRAME_ALIGNMENT;\n\n    while (offset >= gap_offset)\n    {\n        aeron_frame_header_t *frame_header = (aeron_frame_header_t *)(buffer + offset);\n        if (0 != frame_header->frame_length)\n        {\n            return false;\n        }\n\n        offset -= AERON_LOGBUFFER_FRAME_ALIGNMENT;\n    }\n\n    aeron_logbuffer_apply_default_header((uint8_t *)log_meta_data, buffer + gap_offset);\n    aeron_data_header_t *data_header = (aeron_data_header_t *)(buffer + gap_offset);\n\n    data_header->frame_header.type = AERON_HDR_TYPE_PAD;\n    data_header->term_offset = gap_offset;\n    data_header->term_id = term_id;\n    AERON_SET_RELEASE(data_header->frame_header.frame_length, gap_length);\n\n    return true;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_gap_filler.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_TERM_GAP_FILLER_H\n#define AERON_TERM_GAP_FILLER_H\n\n#include <stdbool.h>\n#include \"aeron_logbuffer_descriptor.h\"\n\nbool aeron_term_gap_filler_try_fill_gap(\n    aeron_logbuffer_metadata_t *log_meta_data,\n    uint8_t *buffer,\n    int32_t term_id,\n    int32_t gap_offset,\n    int32_t gap_length);\n\n#endif //AERON_TERM_GAP_FILLER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_gap_scanner.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"concurrent/aeron_term_gap_scanner.h\"\n\nextern int32_t aeron_term_gap_scanner_scan_for_gap(\n    const uint8_t *buffer,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t limit_offset,\n    aeron_term_gap_scanner_on_gap_detected_func_t on_gap_detected,\n    void *clientd);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_gap_scanner.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_TERM_GAP_SCANNER_H\n#define AERON_TERM_GAP_SCANNER_H\n\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"util/aeron_bitutil.h\"\n#include \"aeron_logbuffer_descriptor.h\"\n\ntypedef void (*aeron_term_gap_scanner_on_gap_detected_func_t)(void *clientd, int32_t term_id, int32_t term_offset, size_t length);\n\ninline int32_t aeron_term_gap_scanner_scan_for_gap(\n    const uint8_t *buffer,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t limit_offset,\n    aeron_term_gap_scanner_on_gap_detected_func_t on_gap_detected,\n    void *clientd)\n{\n    int32_t offset = term_offset;\n\n    do\n    {\n        aeron_frame_header_t *hdr = (aeron_frame_header_t *)(buffer + offset);\n        int32_t frame_length;\n\n        AERON_GET_ACQUIRE(frame_length, hdr->frame_length);\n        if (frame_length <= 0)\n        {\n            break;\n        }\n\n        offset += AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    }\n    while (offset < limit_offset);\n\n    const int32_t gap_begin_offset = offset;\n    if (offset < limit_offset)\n    {\n        offset += AERON_DATA_HEADER_LENGTH;\n        while (offset < limit_offset)\n        {\n            aeron_frame_header_t *hdr = (aeron_frame_header_t *)(buffer + offset);\n            int32_t frame_length;\n            AERON_GET_ACQUIRE(frame_length, hdr->frame_length);\n\n            if (0 != frame_length)\n            {\n                break;\n            }\n            offset += AERON_DATA_HEADER_LENGTH;\n        }\n\n        const size_t gap_length = offset - gap_begin_offset;\n        on_gap_detected(clientd, term_id, gap_begin_offset, gap_length);\n    }\n\n    return gap_begin_offset;\n}\n\n#endif //AERON_TERM_GAP_SCANNER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_rebuilder.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"concurrent/aeron_term_rebuilder.h\"\n\nextern void aeron_term_rebuilder_insert(uint8_t *dest, const uint8_t *src, size_t length);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_rebuilder.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_TERM_REBUILDER_H\n#define AERON_TERM_REBUILDER_H\n\n#include <string.h>\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"aeron_atomic.h\"\n\ninline void aeron_term_rebuilder_insert(uint8_t *dest, const uint8_t *src, size_t length)\n{\n    aeron_data_header_t *hdr_dest = (aeron_data_header_t *)dest;\n    aeron_data_header_as_longs_t *dest_hdr_as_longs = (aeron_data_header_as_longs_t *)dest;\n    aeron_data_header_as_longs_t *src_hdr_as_longs = (aeron_data_header_as_longs_t *)src;\n\n    if (0 == hdr_dest->frame_header.frame_length)\n    {\n        memcpy(dest + AERON_DATA_HEADER_LENGTH, src + AERON_DATA_HEADER_LENGTH, length - AERON_DATA_HEADER_LENGTH);\n\n        dest_hdr_as_longs->hdr[3] = src_hdr_as_longs->hdr[3];\n        dest_hdr_as_longs->hdr[2] = src_hdr_as_longs->hdr[2];\n        dest_hdr_as_longs->hdr[1] = src_hdr_as_longs->hdr[1];\n\n        AERON_SET_RELEASE(dest_hdr_as_longs->hdr[0], src_hdr_as_longs->hdr[0]);\n    }\n}\n\n#endif //AERON_TERM_REBUILDER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_scanner.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"concurrent/aeron_term_scanner.h\"\n\nextern int32_t aeron_term_scanner_scan_for_availability(\n    const uint8_t *buffer, int32_t term_length_left, int32_t max_length, int32_t *padding);\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_scanner.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_TERM_SCANNER_H\n#define AERON_TERM_SCANNER_H\n\n#include <stdint.h>\n#include <stddef.h>\n#include \"util/aeron_bitutil.h\"\n#include \"aeron_logbuffer_descriptor.h\"\n\ninline int32_t aeron_term_scanner_scan_for_availability(\n    const uint8_t *buffer, int32_t term_length_left, int32_t max_length, int32_t *padding)\n{\n    const int32_t limit = max_length < term_length_left ? max_length : term_length_left;\n    int32_t available = 0;\n    *padding = 0;\n\n    do\n    {\n        const uint8_t *ptr = buffer + available;\n        const aeron_frame_header_t *frame_header = (aeron_frame_header_t *)ptr;\n\n        int32_t frame_length;\n        AERON_GET_ACQUIRE(frame_length, frame_header->frame_length);\n        if (0 >= frame_length)\n        {\n            break;\n        }\n\n        int32_t aligned_frame_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n        if (AERON_HDR_TYPE_PAD == frame_header->type)\n        {\n            *padding = aligned_frame_length - (int32_t)AERON_DATA_HEADER_LENGTH;\n            aligned_frame_length = AERON_DATA_HEADER_LENGTH;\n        }\n\n        available += aligned_frame_length;\n\n        if (available > limit)\n        {\n            available = aligned_frame_length == available ? -available : available - aligned_frame_length;\n            *padding = 0;\n            break;\n        }\n    }\n    while (0 == *padding && available < limit);\n\n    return available;\n}\n\n#endif //AERON_TERM_SCANNER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_unblocker.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string.h>\n#include \"concurrent/aeron_term_unblocker.h\"\n\nvoid aeron_term_unblocker_reset_header(\n    aeron_data_header_t *data_header,\n    aeron_logbuffer_metadata_t *log_meta_data,\n    int32_t term_offset,\n    int32_t term_id,\n    int32_t frame_length)\n{\n    aeron_logbuffer_apply_default_header((uint8_t *)log_meta_data, (uint8_t *)data_header);\n    data_header->frame_header.type = AERON_HDR_TYPE_PAD;\n    data_header->term_offset = term_offset;\n    data_header->term_id = term_id;\n    AERON_SET_RELEASE(data_header->frame_header.frame_length, frame_length);\n}\n\nbool aeron_term_unblocker_scan_back_to_confirm_zeroed(\n    uint8_t *buffer,\n    int32_t from,\n    int32_t limit)\n{\n    int32_t i = from - AERON_LOGBUFFER_FRAME_ALIGNMENT;\n    bool all_zeroes = true;\n\n    while (i >= limit)\n    {\n        aeron_frame_header_t *frame_header = (aeron_frame_header_t *)(buffer + i);\n        int32_t frame_length;\n\n        AERON_GET_ACQUIRE(frame_length, frame_header->frame_length);\n\n        if (0 != frame_length)\n        {\n            all_zeroes = false;\n            break;\n        }\n\n        i -= AERON_LOGBUFFER_FRAME_ALIGNMENT;\n    }\n\n    return all_zeroes;\n}\n\naeron_term_unblocker_status_t aeron_term_unblocker_unblock(\n    aeron_logbuffer_metadata_t *log_meta_data,\n    uint8_t *buffer,\n    size_t term_length,\n    int32_t blocked_offset,\n    int32_t tail_offset,\n    int32_t term_id)\n{\n    aeron_term_unblocker_status_t status = AERON_TERM_UNBLOCKER_STATUS_NO_ACTION;\n    aeron_data_header_t *data_header = (aeron_data_header_t *)(buffer + blocked_offset);\n    int32_t frame_length;\n\n    AERON_GET_ACQUIRE(frame_length, data_header->frame_header.frame_length);\n    if (frame_length < 0)\n    {\n        aeron_term_unblocker_reset_header(data_header, log_meta_data, blocked_offset, term_id, -frame_length);\n        status = AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED;\n    }\n    else if (0 == frame_length)\n    {\n        int32_t current_offset = blocked_offset + AERON_LOGBUFFER_FRAME_ALIGNMENT;\n\n        while (current_offset < tail_offset)\n        {\n            data_header = (aeron_data_header_t *)(buffer + current_offset);\n            AERON_GET_ACQUIRE(frame_length, data_header->frame_header.frame_length);\n            if (frame_length != 0)\n            {\n                if (aeron_term_unblocker_scan_back_to_confirm_zeroed(buffer, current_offset, blocked_offset))\n                {\n                    const int32_t length = current_offset - blocked_offset;\n                    aeron_term_unblocker_reset_header(\n                        (aeron_data_header_t *)(buffer + blocked_offset), log_meta_data, blocked_offset, term_id, length);\n                    status = AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED;\n                }\n                break;\n            }\n\n            current_offset += AERON_LOGBUFFER_FRAME_ALIGNMENT;\n        }\n\n        if (current_offset == (int32_t)term_length)\n        {\n            data_header = (aeron_data_header_t *)(buffer + blocked_offset);\n            AERON_GET_ACQUIRE(frame_length, data_header->frame_header.frame_length);\n            if (0 == frame_length)\n            {\n                const int32_t length = current_offset - blocked_offset;\n                aeron_term_unblocker_reset_header(data_header, log_meta_data, blocked_offset, term_id, length);\n                status = AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED_TO_END;\n            }\n        }\n    }\n\n    return status;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_term_unblocker.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_TERM_UNBLOCKER_H\n#define AERON_TERM_UNBLOCKER_H\n\n#include \"aeron_logbuffer_descriptor.h\"\n\ntypedef enum aeron_term_unblocker_status_enum\n{\n    AERON_TERM_UNBLOCKER_STATUS_NO_ACTION,\n    AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED,\n    AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED_TO_END\n}\naeron_term_unblocker_status_t;\n\naeron_term_unblocker_status_t aeron_term_unblocker_unblock(\n    aeron_logbuffer_metadata_t *log_meta_data,\n    uint8_t *buffer,\n    size_t term_length,\n    int32_t blocked_offset,\n    int32_t tail_offset,\n    int32_t term_id);\n\n#endif //AERON_TERM_UNBLOCKER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_thread.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <inttypes.h>\n#include <stdlib.h>\n#include \"aeron_alloc.h\"\n#include \"concurrent/aeron_thread.h\"\n\n#include <assert.h>\n\n#include \"util/aeron_error.h\"\n\n#if !defined(_WIN32)\n#include <unistd.h>\n#else\n#include <Windows.h>\n#include <mmsystem.h>\n#pragma comment(lib, \"winmm.lib\")\n\nstruct aeron_thread_stct\n{\n    HANDLE handle;\n    void *(*callback)(void *);\n    void *arg0;\n    void *result;\n};\n\n#endif\n\n#define SECOND_AS_NANOSECONDS (1000 * 1000 * 1000LL)\n\nvoid aeron_nano_sleep(uint64_t nanoseconds)\n{\n#ifdef AERON_COMPILER_MSVC\n    timeBeginPeriod(1);\n\n    HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL);\n    if (!timer)\n    {\n        goto cleanup;\n    }\n\n    LARGE_INTEGER li;\n    li.QuadPart = -(int64_t)(nanoseconds / 100);\n\n    if (!SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE))\n    {\n        CloseHandle(timer);\n        goto cleanup;\n    }\n\n    WaitForSingleObject(timer, INFINITE);\n    CloseHandle(timer);\n\ncleanup:\n    timeEndPeriod(1);\n#else\n    time_t seconds = nanoseconds / SECOND_AS_NANOSECONDS;\n    struct timespec ts =\n    {\n        .tv_sec = seconds,\n        .tv_nsec = (long)nanoseconds - (seconds * SECOND_AS_NANOSECONDS)\n    };\n\n    nanosleep(&ts, NULL);\n#endif\n}\n\nvoid aeron_micro_sleep(unsigned int microseconds)\n{\n#ifdef _WIN32\n    aeron_nano_sleep(UINT64_C(1000) * microseconds);\n#else\n    usleep(microseconds);\n#endif\n}\n\nint aeron_thread_set_affinity(const char *role_name, uint8_t cpu_affinity_no)\n{\n#if defined(__linux__)\n    cpu_set_t mask;\n    const size_t size = sizeof(mask);\n    CPU_ZERO(&mask);\n    CPU_SET(cpu_affinity_no, &mask);\n    if (sched_setaffinity(0, size, &mask) < 0)\n    {\n        AERON_SET_ERR(errno, \"failed to set thread affinity role_name=%s, cpu_affinity_no=%\" PRIu8, role_name, cpu_affinity_no);\n        return -1;\n    }\n    return 0;\n#else\n    AERON_SET_ERR(EINVAL, \"%s\", \"thread affinity not supported\");\n    return -1;\n#endif\n}\n\n#if defined(AERON_COMPILER_GCC)\n\nvoid aeron_thread_set_name(const char *role_name)\n{\n#if defined(__APPLE__)\n    pthread_setname_np(role_name);\n#else\n    pthread_setname_np(pthread_self(), role_name);\n#endif\n}\n\nint aeron_mutex_init(aeron_mutex_t *mutex)\n{\n    pthread_mutexattr_t mutex_attr;\n    int rc = pthread_mutexattr_init(&mutex_attr);\n    if (0 != rc)\n    {\n        AERON_SET_ERR(rc, \"%s\", \"pthread_mutexattr_init failed\");\n        return -1;\n    }\n\n    rc = pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);\n    if (0 != rc)\n    {\n        AERON_SET_ERR(rc, \"%s\", \"pthread_mutexattr_settype failed\");\n        goto error;\n    }\n\n    rc = pthread_mutex_init(mutex, &mutex_attr);\n    if (0 != rc)\n    {\n        AERON_SET_ERR(rc, \"%s\", \"failed to create mutex\");\n        goto error;\n    }\n\n    pthread_mutexattr_destroy(&mutex_attr);\n    return 0;\n\nerror:\n    pthread_mutexattr_destroy(&mutex_attr);\n    return -1;\n}\n\nint aeron_mutex_destroy(aeron_mutex_t *mutex)\n{\n    return pthread_mutex_destroy(mutex);\n}\n\nint aeron_mutex_lock(aeron_mutex_t *mutex)\n{\n    return pthread_mutex_lock(mutex);\n}\n\nint aeron_mutex_unlock(aeron_mutex_t *mutex)\n{\n    return pthread_mutex_unlock(mutex);\n}\n\n#elif defined(AERON_COMPILER_MSVC)\n\nstatic BOOL WINAPI aeron_thread_once_callback(PINIT_ONCE init_once, void (*callback)(void), void **context)\n{\n    callback();\n    return TRUE;\n}\n\nvoid aeron_thread_once(AERON_INIT_ONCE *s_init_once, void *callback)\n{\n    InitOnceExecuteOnce((PINIT_ONCE)s_init_once, (PINIT_ONCE_FN)aeron_thread_once_callback, callback, NULL);\n}\n\nint aeron_mutex_init(aeron_mutex_t *mutex)\n{\n    InitializeCriticalSection(mutex);\n    return 0;\n}\n\nint aeron_mutex_lock(aeron_mutex_t *mutex)\n{\n    EnterCriticalSection(mutex);\n    return 0;\n}\n\nint aeron_mutex_unlock(aeron_mutex_t *mutex)\n{\n    LeaveCriticalSection(mutex);\n    return 0;\n}\n\nint aeron_mutex_destroy(aeron_mutex_t *mutex)\n{\n    if (mutex)\n    {\n        DeleteCriticalSection(mutex);\n    }\n\n    return 0;\n}\n\nint aeron_thread_attr_init(aeron_thread_attr_t *attr)\n{\n    return 0;\n}\n\nstatic DWORD WINAPI aeron_thread_proc(LPVOID parameter)\n{\n    aeron_thread_t *thread = (aeron_thread_t *)parameter;\n    (*thread)->result = (*thread)->callback((*thread)->arg0);\n\n    return 0;\n}\n\nint aeron_thread_create(aeron_thread_t *thread_ptr, void *attr, void *(*callback)(void *), void *arg0)\n{\n    if (NULL == thread_ptr)\n    {\n        return -1;\n    }\n\n    if (aeron_alloc((void **)thread_ptr, sizeof(struct aeron_thread_stct)) < 0)\n    {\n        return -1;\n    }\n\n    (*thread_ptr)->callback = callback;\n    (*thread_ptr)->arg0 = arg0;\n    DWORD id;\n\n    (*thread_ptr)->handle = CreateThread(\n        NULL,  // default security attributes\n        0,         // use default stack size\n        aeron_thread_proc,    // thread function name\n        thread_ptr,           // argument to thread function\n        0,      // use default creation flags\n        &id);                 // returns the thread identifier\n\n    if (!(*thread_ptr)->handle)\n    {\n        aeron_free(*thread_ptr);\n        return -1;\n    }\n\n    return 0;\n}\n\nvoid aeron_thread_set_name(const char *role_name)\n{\n    size_t wchar_count = mbstowcs(NULL, role_name, 0);\n    wchar_t *buf;\n    if (aeron_alloc((void **)&buf, sizeof(wchar_t) * (wchar_count + 1)) < 0)  // value-initialize to 0 (see below)\n    {\n        return;\n    }\n\n    mbstowcs(buf, role_name, wchar_count + 1);\n    SetThreadDescription(GetCurrentThread(), buf);\n\n    aeron_free(buf);\n}\n\nint aeron_thread_join(aeron_thread_t thread, void **value_ptr)\n{\n    if (!thread)\n    {\n        return EINVAL;\n    }\n\n    int result = 0;\n    if (thread->handle)\n    {\n        WaitForSingleObject(thread->handle, INFINITE);\n        CloseHandle(thread->handle);\n        if (value_ptr)\n        {\n            *value_ptr = thread->result;\n        }\n    }\n    else\n    {\n        result = EINVAL;\n    }\n\n    aeron_free(thread);\n\n    return result;\n}\n\nint aeron_thread_key_create(pthread_key_t *key_ptr, void (*destr_func)(void *))\n{\n    DWORD dkey = TlsAlloc();\n    if (dkey != TLS_OUT_OF_INDEXES)\n    {\n        *key_ptr = dkey;\n        return 0;\n    }\n    else\n    {\n        return EAGAIN;\n    }\n}\n\nint aeron_thread_key_delete(pthread_key_t key)\n{\n    if (TlsFree(key))\n    {\n        return 0;\n    }\n    else\n    {\n        return EINVAL;\n    }\n}\n\nint aeron_thread_set_specific(pthread_key_t key, const void *pointer)\n{\n    if (TlsSetValue(key, (LPVOID)pointer))\n    {\n        return 0;\n    }\n    else\n    {\n        return EINVAL;\n    }\n}\n\nvoid *aeron_thread_get_specific(pthread_key_t key)\n{\n    return TlsGetValue(key);\n}\n\nint sched_yield(void)\n{\n    SwitchToThread();\n    return 0;\n}\n\nint aeron_cond_init(aeron_cond_t *cv, void *attr)\n{\n    InitializeConditionVariable(cv);\n    return 0;\n}\n\nint aeron_cond_destroy(aeron_cond_t *cv)\n{\n    // there's no delete for windows condition variables\n    return 0;\n}\n\nint aeron_cond_wait(aeron_cond_t *cv, aeron_mutex_t *mutex)\n{\n    SleepConditionVariableCS(cv, mutex, INFINITE);\n    return 0;\n}\n\nint aeron_cond_signal(aeron_cond_t *cv)\n{\n    WakeConditionVariable(cv);\n    return 0;\n}\n\n#else\n#error Unsupported platform!\n#endif\n\n // sched\n\n#if defined(AERON_COMPILER_GCC)\n\n#include <sched.h>\n\nvoid proc_yield(void)\n{\n#if !defined(AERON_CPU_ARM)\n    __asm__ __volatile__(\"pause\\n\": : : \"memory\");\n#endif\n}\n\n#elif defined(AERON_COMPILER_MSVC)\n\n#else\n#error Unsupported platform!\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/c/concurrent/aeron_thread.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_THREAD_H\n#define AERON_THREAD_H\n\n#include <stdint.h>\n#include <stddef.h>\n\n#include \"util/aeron_platform.h\"\n\nvoid aeron_thread_set_name(const char *role_name);\n\nvoid aeron_nano_sleep(uint64_t nanoseconds);\nvoid aeron_micro_sleep(unsigned int microseconds);\nint aeron_thread_set_affinity(const char *role_name, uint8_t cpu_affinity_no);\n\n#if defined(AERON_COMPILER_GCC)\n\n#include <pthread.h>\ntypedef pthread_mutex_t aeron_mutex_t;\n#define AERON_INIT_ONCE pthread_once_t\n#define AERON_INIT_ONCE_VALUE PTHREAD_ONCE_INIT\n\ntypedef pthread_t aeron_thread_t;\ntypedef pthread_attr_t aeron_thread_attr_t;\n\nint aeron_mutex_init(aeron_mutex_t *mutex);\nint aeron_mutex_destroy(aeron_mutex_t *mutex);\nint aeron_mutex_lock(aeron_mutex_t *mutex);\nint aeron_mutex_unlock(aeron_mutex_t *mutex);\n\n#define aeron_thread_once pthread_once\n#define aeron_thread_attr_init pthread_attr_init\n#define aeron_thread_create pthread_create\n#define aeron_thread_join pthread_join\n#define aeron_thread_key_create pthread_key_create\n#define aeron_thread_key_delete pthread_key_delete\n#define aeron_thread_get_specific pthread_getspecific\n#define aeron_thread_set_specific pthread_setspecific\n\ntypedef pthread_cond_t aeron_cond_t;\n#define aeron_cond_init pthread_cond_init\n#define aeron_cond_destroy pthread_cond_destroy\n#define aeron_cond_signal pthread_cond_signal\n#define aeron_cond_wait pthread_cond_wait\n\n#elif defined(AERON_COMPILER_MSVC)\n\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n\ntypedef CRITICAL_SECTION aeron_mutex_t;\n\nstruct aeron_thread_stct;\ntypedef struct aeron_thread_stct *aeron_thread_t;\n\ntypedef union aeron_init_once_union\n{\n    void *ptr;\n}\nAERON_INIT_ONCE;\n\ntypedef unsigned long aeron_thread_attr_t;\ntypedef unsigned long pthread_key_t;\n\ntypedef CONDITION_VARIABLE aeron_cond_t;\n\n#define AERON_INIT_ONCE_VALUE {0}\n\nvoid aeron_thread_once(AERON_INIT_ONCE *s_init_once, void *callback);\n\nint aeron_mutex_init(aeron_mutex_t *mutex);\nint aeron_mutex_destroy(aeron_mutex_t *mutex);\nint aeron_mutex_lock(aeron_mutex_t *mutex);\nint aeron_mutex_unlock(aeron_mutex_t *mutex);\n\nint aeron_thread_attr_init(aeron_thread_attr_t *attr);\n\nint aeron_thread_create(aeron_thread_t *thread_ptr, void *attr, void *(*callback)(void *), void *arg0);\n\nint aeron_thread_join(aeron_thread_t thread, void **value_ptr);\n\nint aeron_thread_key_create(pthread_key_t *key_ptr, void (*destr_func)(void *));\n\nint aeron_thread_key_delete(pthread_key_t key);\n\nint aeron_thread_set_specific(pthread_key_t key, const void *pointer);\n\nvoid *aeron_thread_get_specific(pthread_key_t key);\n\nint aeron_cond_init(aeron_cond_t *cv, void *attr);\n\nint aeron_cond_destroy(aeron_cond_t *cv);\n\nint aeron_cond_wait(aeron_cond_t *cv, aeron_mutex_t *mutex);\n\nint aeron_cond_signal(aeron_cond_t *cv);\n\n#else\n#error Unsupported platform!\n#endif\n\n// sched\n\n#if defined(AERON_COMPILER_GCC)\n\nvoid proc_yield(void);\n\n#elif defined(AERON_COMPILER_MSVC)\n\nint sched_yield(void);\n#define proc_yield _mm_pause\n\n#else\n#error Unsupported platform!\n#endif\n\n#endif //AERON_THREAD_H\n"
  },
  {
    "path": "aeron-client/src/main/c/protocol/aeron_udp_protocol.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string.h>\n\n#include \"util/aeron_bitutil.h\"\n#include \"aeron_udp_protocol.h\"\n\n_Static_assert(\n    sizeof(aeron_data_header_as_longs_t) == sizeof(aeron_data_header_t),\n    \"sizeof(aeron_data_header_as_longs_t) must match sizeof(aeron_data_header_t)\");\n\nint aeron_udp_protocol_group_tag(aeron_status_message_header_t *sm, int64_t *group_tag)\n{\n    const size_t group_tag_offset = sizeof(aeron_status_message_header_t) +\n        offsetof(aeron_status_message_optional_header_t, group_tag);\n    const size_t group_tag_size = sizeof(*group_tag);\n    const size_t frame_length_with_group_tag = group_tag_offset + group_tag_size;\n\n    if (sm->frame_header.frame_length == (int32_t)frame_length_with_group_tag)\n    {\n        const uint8_t *sm_ptr = (const uint8_t *)sm + group_tag_offset;\n        memcpy(group_tag, sm_ptr, group_tag_size);\n\n        return (int)group_tag_size;\n    }\n\n    *group_tag = 0;\n\n    return (int)((size_t)sm->frame_header.frame_length - group_tag_offset);\n}\n\nextern bool aeron_is_frame_valid(const aeron_frame_header_t *header, size_t frame_length);\n\nextern size_t aeron_res_header_address_length(int8_t res_type);\n\nextern size_t aeron_compute_max_message_length(size_t term_length);\n\nsize_t aeron_res_header_entry_length_ipv4(aeron_resolution_header_ipv4_t *header)\n{\n    return AERON_ALIGN(sizeof(aeron_resolution_header_ipv4_t) + header->name_length, sizeof(int64_t));\n}\n\nsize_t aeron_res_header_entry_length_ipv6(aeron_resolution_header_ipv6_t *header)\n{\n    return AERON_ALIGN(sizeof(aeron_resolution_header_ipv6_t) + header->name_length, sizeof(int64_t));\n}\n\nint aeron_res_header_entry_length(void *res, size_t remaining)\n{\n    if (remaining < sizeof(aeron_resolution_header_t))\n    {\n        return -1;\n    }\n\n    aeron_resolution_header_t *res_header = (aeron_resolution_header_t *)res;\n\n    int result = -1;\n    switch (res_header->res_type)\n    {\n        case AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD:\n        {\n            if (sizeof(aeron_resolution_header_ipv6_t) <= remaining)\n            {\n                aeron_resolution_header_ipv6_t *res_ipv6 = (aeron_resolution_header_ipv6_t *)res_header;\n                size_t entry_length = aeron_res_header_entry_length_ipv6(res_ipv6);\n                result = entry_length <= remaining ? (int)entry_length : -1;\n            }\n            break;\n        }\n\n        case AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD:\n        {\n            if (sizeof(aeron_resolution_header_ipv4_t) <= remaining)\n            {\n                aeron_resolution_header_ipv4_t *res_ipv4 = (aeron_resolution_header_ipv4_t *)res_header;\n                size_t entry_length = aeron_res_header_entry_length_ipv4(res_ipv4);\n                result = entry_length <= remaining ? (int)entry_length : -1;\n            }\n            break;\n        }\n\n        default:\n            break;\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/protocol/aeron_udp_protocol.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_UDP_PROTOCOL_H\n#define AERON_UDP_PROTOCOL_H\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stddef.h>\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_frame_header_stct\n{\n    volatile int32_t frame_length;\n    int8_t version;\n    uint8_t flags;\n    int16_t type;\n}\naeron_frame_header_t;\n\ntypedef struct aeron_data_header_as_longs_stct\n{\n    volatile uint64_t hdr[4];\n}\naeron_data_header_as_longs_t;\n\ntypedef struct aeron_setup_header_stct\n{\n    aeron_frame_header_t frame_header;\n    int32_t term_offset;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t initial_term_id;\n    int32_t active_term_id;\n    int32_t term_length;\n    int32_t mtu;\n    int32_t ttl;\n}\naeron_setup_header_t;\n\ntypedef struct aeron_data_header_stct\n{\n    aeron_frame_header_t frame_header;\n    int32_t term_offset;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t term_id;\n    int64_t reserved_value;\n}\naeron_data_header_t;\n\ntypedef struct aeron_nak_header_stct\n{\n    aeron_frame_header_t frame_header;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t term_id;\n    int32_t term_offset;\n    int32_t length;\n}\naeron_nak_header_t;\n\ntypedef struct aeron_status_message_header_stct\n{\n    aeron_frame_header_t frame_header;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t consumption_term_id;\n    int32_t consumption_term_offset;\n    int32_t receiver_window;\n    int64_t receiver_id;\n}\naeron_status_message_header_t;\n\ntypedef struct aeron_status_message_optional_header_stct\n{\n    int64_t group_tag;\n}\naeron_status_message_optional_header_t;\n\nstruct aeron_error_stct\n{\n    aeron_frame_header_t frame_header;\n    int32_t session_id;\n    int32_t stream_id;\n    int64_t receiver_id;\n    int64_t group_tag;\n    int32_t error_code;\n    int32_t error_length;\n};\ntypedef struct aeron_error_stct aeron_error_t;\n\ntypedef struct aeron_rttm_header_stct\n{\n    aeron_frame_header_t frame_header;\n    int32_t session_id;\n    int32_t stream_id;\n    int64_t echo_timestamp;\n    int64_t reception_delta;\n    int64_t receiver_id;\n}\naeron_rttm_header_t;\n\n#pragma pack(pop)\n\n#define AERON_RES_HEADER_ADDRESS_LENGTH_IP4 (4u)\n#define AERON_RES_HEADER_ADDRESS_LENGTH_IP6 (16u)\n\n#pragma pack(push)\n#pragma pack(1)\ntypedef struct aeron_resolution_header_stct\n{\n    int8_t res_type;\n    uint8_t res_flags;\n    uint16_t udp_port;\n    int32_t age_in_ms;\n}\naeron_resolution_header_t;\n\ntypedef struct aeron_resolution_header_ipv4_stct\n{\n    aeron_resolution_header_t resolution_header;\n    uint8_t addr[AERON_RES_HEADER_ADDRESS_LENGTH_IP4];\n    int16_t name_length;\n}\naeron_resolution_header_ipv4_t;\n\ntypedef struct aeron_resolution_header_ipv6_stct\n{\n    aeron_resolution_header_t resolution_header;\n    uint8_t addr[AERON_RES_HEADER_ADDRESS_LENGTH_IP6];\n    int16_t name_length;\n}\naeron_resolution_header_ipv6_t;\n\ntypedef struct aeron_option_header_stct\n{\n    uint16_t option_length;\n    uint16_t type;\n}\naeron_option_header_t;\n\ntypedef struct aeron_response_setup_header_stct\n{\n    aeron_frame_header_t frame_header;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t response_session_id;\n}\naeron_response_setup_header_t;\n#pragma pack(pop)\n\nint aeron_udp_protocol_group_tag(aeron_status_message_header_t *sm, int64_t *group_tag);\n\n#define AERON_FRAME_HEADER_VERSION (0)\n\n#define AERON_HDR_TYPE_PAD (INT16_C(0x00))\n#define AERON_HDR_TYPE_DATA (INT16_C(0x01))\n#define AERON_HDR_TYPE_NAK (INT16_C(0x02))\n#define AERON_HDR_TYPE_SM (INT16_C(0x03))\n#define AERON_HDR_TYPE_ERR (INT16_C(0x04))\n#define AERON_HDR_TYPE_SETUP (INT16_C(0x05))\n#define AERON_HDR_TYPE_RTTM (INT16_C(0x06))\n#define AERON_HDR_TYPE_RES (INT16_C(0x07))\n#define AERON_HDR_TYPE_ATS_DATA (INT16_C(0x08))\n#define AERON_HDR_TYPE_ATS_SETUP (INT16_C(0x09))\n#define AERON_HDR_TYPE_ATS_SM (INT16_C(0x0A))\n#define AERON_HDR_TYPE_RSP_SETUP (INT16_C(0x0B))\n#define AERON_HDR_TYPE_EXT (INT16_C(-1))\n\n#define AERON_FRAME_HEADER_LENGTH (sizeof(aeron_frame_header_t))\n#define AERON_DATA_HEADER_LENGTH (sizeof(aeron_data_header_t))\n\n#define AERON_DATA_HEADER_BEGIN_FLAG (UINT8_C(0x80))\n#define AERON_DATA_HEADER_END_FLAG (UINT8_C(0x40))\n#define AERON_DATA_HEADER_EOS_FLAG (UINT8_C(0x20))\n#define AERON_DATA_HEADER_REVOKED_FLAG (UINT8_C(0x10))\n\n#define AERON_DATA_HEADER_UNFRAGMENTED (AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG)\n\n#define AERON_DATA_HEADER_DEFAULT_RESERVED_VALUE (INT64_C(0))\n\n#define AERON_STATUS_MESSAGE_HEADER_SEND_SETUP_FLAG (UINT8_C(0x80))\n#define AERON_STATUS_MESSAGE_HEADER_EOS_FLAG (UINT8_C(0x40))\n\n#define AERON_SETUP_HEADER_SEND_RESPONSE_FLAG (UINT8_C(0x80))\n#define AERON_SETUP_HEADER_GROUP_FLAG (UINT8_C(0x40))\n\n#define AERON_RTTM_HEADER_REPLY_FLAG (UINT8_C(0x80))\n\n#define AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD (0x01)\n#define AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD (0x02)\n#define AERON_RES_HEADER_SELF_FLAG (UINT8_C(0x80))\n\n#define AERON_FRAME_MAX_MESSAGE_LENGTH (16u * 1024u * 1024u)\n\n#define AERON_OPTION_HEADER_IGNORE_FLAG (UINT16_C(0x8000))\n\n#define AERON_OPT_HDR_TYPE_ATS_SUITE (UINT16_C(0x0001))\n#define AERON_OPT_HDR_TYPE_ATS_RSA_KEY (UINT16_C(0x0002))\n#define AERON_OPT_HDR_TYPE_ATS_RSA_KEY_ID (UINT16_C(0x0003))\n#define AERON_OPT_HDR_TYPE_ATS_EC_KEY (UINT16_C(0x0004))\n#define AERON_OPT_HDR_TYPE_ATS_EC_SIG (UINT16_C(0x0005))\n#define AERON_OPT_HDR_TYPE_ATS_SECRET (UINT16_C(0x0006))\n#define AERON_OPT_HDR_TYPE_ATS_GROUP_TAG (UINT16_C(0x0007))\n\n#define AERON_OPT_HDR_ALIGNMENT (4u)\n\n#define AERON_ERROR_MAX_TEXT_LENGTH (1023)\n#define AERON_ERROR_MAX_FRAME_LENGTH (sizeof(aeron_error_t) + AERON_ERROR_MAX_TEXT_LENGTH)\n#define AERON_ERROR_HAS_GROUP_TAG_FLAG (0x08)\n\ninline bool aeron_is_frame_valid(const aeron_frame_header_t *header, const size_t frame_length)\n{\n    return frame_length >= AERON_FRAME_HEADER_LENGTH && AERON_FRAME_HEADER_VERSION == header->version;\n}\n\ninline size_t aeron_res_header_address_length(int8_t res_type)\n{\n    return AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD == res_type ?\n        AERON_RES_HEADER_ADDRESS_LENGTH_IP6 : AERON_RES_HEADER_ADDRESS_LENGTH_IP4;\n}\n\ninline size_t aeron_compute_max_message_length(size_t term_length)\n{\n    size_t max_length_for_term = term_length >> 3;\n    return max_length_for_term < AERON_FRAME_MAX_MESSAGE_LENGTH ? max_length_for_term : AERON_FRAME_MAX_MESSAGE_LENGTH;\n}\n\nsize_t aeron_res_header_entry_length_ipv4(aeron_resolution_header_ipv4_t *header);\n\nsize_t aeron_res_header_entry_length_ipv6(aeron_resolution_header_ipv6_t *header);\n\nint aeron_res_header_entry_length(void *res, size_t remaining);\n\n#endif //AERON_UDP_PROTOCOL_H\n"
  },
  {
    "path": "aeron-client/src/main/c/reports/aeron_loss_reporter.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include \"reports/aeron_loss_reporter.h\"\n#include \"util/aeron_fileutil.h\"\n\nint aeron_loss_reporter_init(aeron_loss_reporter_t *reporter, uint8_t *buffer, size_t length)\n{\n    reporter->buffer = buffer;\n    reporter->next_record_offset = 0;\n    reporter->capacity = length;\n\n    return 0;\n}\n\naeron_loss_reporter_entry_offset_t aeron_loss_reporter_create_entry(\n    aeron_loss_reporter_t *reporter,\n    int64_t initial_bytes_lost,\n    int64_t timestamp_ms,\n    int32_t session_id,\n    int32_t stream_id,\n    const char *channel,\n    size_t channel_length,\n    const char *source,\n    size_t source_length)\n{\n    aeron_loss_reporter_entry_offset_t entry_offset = -1;\n    const size_t required_capacity =\n        sizeof(aeron_loss_reporter_entry_t) +\n        AERON_ALIGN((sizeof(int32_t) + channel_length), sizeof(int32_t)) +\n        (sizeof(int32_t) + source_length);\n\n    if (required_capacity <= (reporter->capacity - reporter->next_record_offset))\n    {\n        uint8_t *ptr = reporter->buffer + reporter->next_record_offset;\n        aeron_loss_reporter_entry_t *entry = (aeron_loss_reporter_entry_t *)ptr;\n\n        entry->total_bytes_lost = initial_bytes_lost;\n        entry->first_observation_timestamp = timestamp_ms;\n        entry->last_observation_timestamp = timestamp_ms;\n        entry->session_id = session_id;\n        entry->stream_id = stream_id;\n\n        ptr += sizeof(aeron_loss_reporter_entry_t);\n        *(int32_t *)ptr = (int32_t)channel_length;\n        ptr += sizeof(int32_t);\n        memcpy(ptr, channel, channel_length);\n\n        ptr += AERON_ALIGN(channel_length, sizeof(int32_t));\n        *(int32_t *)ptr = (int32_t)source_length;\n        ptr += sizeof(int32_t);\n        memcpy(ptr, source, source_length);\n\n        AERON_SET_RELEASE(entry->observation_count, 1);\n\n        entry_offset = (aeron_loss_reporter_entry_offset_t)reporter->next_record_offset;\n        reporter->next_record_offset += AERON_ALIGN(required_capacity, AERON_LOSS_REPORTER_ENTRY_ALIGNMENT);\n    }\n    else\n    {\n        AERON_SET_ERR(ENOMEM, \"could not create loss report entry: %s\", strerror(ENOMEM));\n    }\n\n    return entry_offset;\n}\n\nvoid aeron_loss_reporter_record_observation(\n    aeron_loss_reporter_t *reporter,\n    aeron_loss_reporter_entry_offset_t offset,\n    int64_t bytes_lost,\n    int64_t timestamp_ms)\n{\n    if (offset >= 0)\n    {\n        uint8_t *ptr = reporter->buffer + offset;\n        aeron_loss_reporter_entry_t *entry = (aeron_loss_reporter_entry_t *)ptr;\n\n        AERON_SET_RELEASE(entry->last_observation_timestamp, timestamp_ms);\n\n        // this is aligned as far as usage goes. And should perform fine.\n#if defined(__clang__) && defined(AERON_CPU_ARM)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Watomic-alignment\"\n#endif\n        int64_t dest;\n        AERON_GET_AND_ADD_INT64(dest, entry->total_bytes_lost, bytes_lost);\n        AERON_GET_AND_ADD_INT64(dest, entry->observation_count, INT64_C(1));\n        (void)dest; // silence variable 'dest' set but not used\n#if defined(__clang__) && defined(AERON_CPU_ARM)\n#pragma clang diagnostic pop\n#endif\n    }\n}\n\nint aeron_loss_reporter_resolve_filename(const char *directory, char *filename_buffer, size_t filename_buffer_length)\n{\n    return aeron_file_resolve(directory, AERON_LOSS_REPORT_FILE, filename_buffer, filename_buffer_length);\n}\n\nsize_t aeron_loss_reporter_read(\n    const uint8_t *buffer, size_t capacity, aeron_loss_reporter_read_entry_func_t entry_func, void *clientd)\n{\n    size_t records_read = 0;\n    size_t offset = 0;\n\n    while (offset < capacity)\n    {\n        const uint8_t *ptr = buffer + offset;\n        aeron_loss_reporter_entry_t *entry = (aeron_loss_reporter_entry_t *)ptr;\n\n        int64_t observation_count;\n        AERON_GET_ACQUIRE(observation_count, entry->observation_count);\n        if (observation_count <= 0)\n        {\n            break;\n        }\n\n        ++records_read;\n\n        ptr += sizeof(aeron_loss_reporter_entry_t);\n        int32_t channel_length = *(int32_t *)ptr;\n        ptr += sizeof(int32_t);\n        const char *channel = (const char *)ptr;\n\n        ptr += AERON_ALIGN(channel_length, sizeof(int32_t));\n        int32_t source_length = *(int32_t *)ptr;\n        ptr += sizeof(int32_t);\n        const char *source = (const char *)ptr;\n\n        int64_t total_bytes_lost;\n        AERON_GET_ACQUIRE(total_bytes_lost, entry->total_bytes_lost);\n\n        int64_t last_observation_timestamp;\n        AERON_GET_ACQUIRE(last_observation_timestamp, entry->last_observation_timestamp);\n\n        entry_func(\n            clientd,\n            observation_count,\n            total_bytes_lost,\n            entry->first_observation_timestamp,\n            last_observation_timestamp,\n            entry->session_id,\n            entry->stream_id,\n            channel,\n            channel_length,\n            source,\n            source_length);\n\n        const size_t record_length =\n            sizeof(aeron_loss_reporter_entry_t) + (2 * sizeof(int32_t)) + channel_length + source_length;\n        offset += AERON_ALIGN(record_length, AERON_LOSS_REPORTER_ENTRY_ALIGNMENT);\n    }\n\n    return records_read;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/reports/aeron_loss_reporter.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_LOSS_REPORTER_H\n#define AERON_LOSS_REPORTER_H\n\n#include <stdint.h>\n#include <string.h>\n#include <errno.h>\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_bitutil.h\"\n\n#define AERON_LOSS_REPORT_FILE \"loss-report.dat\"\n\n#pragma pack(push)\n#pragma pack(4)\ntypedef struct aeron_loss_reporter_entry_stct\n{\n    volatile int64_t observation_count;\n    volatile int64_t total_bytes_lost;\n    int64_t first_observation_timestamp;\n    volatile int64_t last_observation_timestamp;\n    int32_t session_id;\n    int32_t stream_id;\n}\naeron_loss_reporter_entry_t;\n#pragma pack(pop)\n\n#define AERON_LOSS_REPORTER_ENTRY_ALIGNMENT (AERON_CACHE_LINE_LENGTH)\n\ntypedef struct aeron_loss_reporter_stct\n{\n    uint8_t *buffer;\n    size_t next_record_offset;\n    size_t capacity;\n}\naeron_loss_reporter_t;\n\ntypedef int64_t aeron_loss_reporter_entry_offset_t;\n\nint aeron_loss_reporter_init(aeron_loss_reporter_t *reporter, uint8_t *buffer, size_t length);\n\naeron_loss_reporter_entry_offset_t aeron_loss_reporter_create_entry(\n    aeron_loss_reporter_t *reporter,\n    int64_t initial_bytes_lost,\n    int64_t timestamp_ms,\n    int32_t session_id,\n    int32_t stream_id,\n    const char *channel,\n    size_t channel_length,\n    const char *source,\n    size_t source_length);\n\nvoid aeron_loss_reporter_record_observation(\n    aeron_loss_reporter_t *reporter,\n    aeron_loss_reporter_entry_offset_t offset,\n    int64_t bytes_lost,\n    int64_t timestamp_ms);\n\nint aeron_loss_reporter_resolve_filename(const char *directory, char *filename_buffer, size_t filename_buffer_length);\n\nsize_t aeron_loss_reporter_read(\n    const uint8_t *buffer, size_t capacity, aeron_loss_reporter_read_entry_func_t entry_func, void *clientd);\n\n#endif //AERON_LOSS_REPORTER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/status/aeron_local_sockaddr.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string.h>\n\n#include \"aeron_local_sockaddr.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"aeron_counters.h\"\n#include \"aeron_counters.h\"\n\ntypedef struct aeron_lock_sockaddr_find_clientd_stct\n{\n    int32_t channel_status_indicator_id;\n    aeron_iovec_t *address_vec;\n    size_t address_vec_len;\n    size_t current_address;\n    aeron_counters_reader_t *reader;\n}\naeron_lock_sockaddr_find_clientd_t;\n\nstatic void aeron_local_sockaddr_find_address_counter_metadata_func(\n    int32_t id,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const uint8_t *label,\n    size_t label_length,\n    void *clientd)\n{\n    aeron_lock_sockaddr_find_clientd_t *find_addr = (aeron_lock_sockaddr_find_clientd_t *)clientd;\n\n    if (AERON_COUNTER_LOCAL_SOCKADDR_TYPE_ID != type_id)\n    {\n        return;\n    }\n\n    int32_t status_indicator_id;\n    memcpy(&status_indicator_id, key, sizeof(status_indicator_id));\n\n    if (status_indicator_id != find_addr->channel_status_indicator_id)\n    {\n        return;\n    }\n\n    int64_t *status_indicator_addr = aeron_counters_reader_addr(find_addr->reader, status_indicator_id);\n    int64_t status;\n    AERON_GET_ACQUIRE(status, *(volatile int64_t *)status_indicator_addr);\n\n    if (AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_ACTIVE != status)\n    {\n        return;\n    }\n\n    int32_t addr_len;\n\n    memcpy(&addr_len, key + sizeof(status_indicator_id), sizeof(addr_len));\n    char *addr = (char *)(key + sizeof(status_indicator_id) + sizeof(addr_len));\n\n    if (addr_len <= 0)\n    {\n        return;\n    }\n\n    size_t current_address = find_addr->current_address;\n    find_addr->current_address++;\n\n    if (find_addr->address_vec_len <= current_address)\n    {\n        return;\n    }\n\n    aeron_iovec_t *iov = &find_addr->address_vec[current_address];\n    size_t buffer_len = iov->iov_len;\n    size_t addr_len_sz = (size_t)addr_len;\n\n    size_t to_copy = addr_len_sz < (buffer_len - 1) ? addr_len_sz : buffer_len - 1;\n    memcpy(iov->iov_base, addr, to_copy);\n    iov->iov_base[to_copy] = UINT8_C(0);\n}\n\nint aeron_local_sockaddr_find_addrs(\n    aeron_counters_reader_t *reader,\n    int32_t channel_status_indicator_id,\n    aeron_iovec_t *address_vec,\n    size_t address_vec_len)\n{\n    volatile int64_t *status_indicator_addr = aeron_counters_reader_addr(reader, channel_status_indicator_id);\n\n    if (NULL == status_indicator_addr)\n    {\n        return 0;\n    }\n\n    int64_t status;\n    AERON_GET_ACQUIRE(status, *status_indicator_addr);\n\n    if (AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_ACTIVE != status)\n    {\n        return 0;\n    }\n\n    aeron_lock_sockaddr_find_clientd_t find_clientd =\n        {\n            .channel_status_indicator_id = channel_status_indicator_id,\n            .address_vec = address_vec,\n            .address_vec_len = address_vec_len,\n            .current_address = 0,\n            .reader = reader\n        };\n\n    aeron_counters_reader_foreach_metadata(\n        reader->metadata,\n        reader->metadata_length,\n        aeron_local_sockaddr_find_address_counter_metadata_func,\n        &find_clientd);\n\n    return (int)find_clientd.current_address;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/status/aeron_local_sockaddr.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_LOCAL_SOCKADDR_H\n#define AERON_AERON_LOCAL_SOCKADDR_H\n\n#include <stdint.h>\n#include <aeronc.h>\n\nint aeron_local_sockaddr_find_addrs(\n    aeron_counters_reader_t *reader,\n    int32_t channel_status_indicator_id,\n    aeron_iovec_t *address_vec,\n    size_t address_vec_len);\n\n#endif //AERON_AERON_LOCAL_SOCKADDR_H\n"
  },
  {
    "path": "aeron-client/src/main/c/uri/aeron_uri.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include \"command/aeron_control_protocol.h\"\n#include \"uri/aeron_uri.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"util/aeron_parse_util.h\"\n\n#define AERON_URI_SCHEME \"aeron:\"\n#define AERON_URI_UDP_TRANSPORT \"udp\"\n#define AERON_URI_IPC_TRANSPORT \"ipc\"\n\ntypedef enum aeron_uri_parser_state_enum\n{\n    PARAM_KEY, PARAM_VALUE\n}\naeron_uri_parser_state_t;\n\nint aeron_uri_parse_params(char *uri, aeron_uri_parse_callback_t param_func, void *clientd)\n{\n    aeron_uri_parser_state_t state = PARAM_KEY;\n    char *param_key = NULL, *param_value = NULL;\n\n    for (size_t i = 0; uri[i] != '\\0'; i++)\n    {\n        char c = uri[i];\n\n        switch (state)\n        {\n            case PARAM_KEY:\n                switch (c)\n                {\n                    case '=':\n                        if (NULL == param_key)\n                        {\n                            AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL, \"%s\", \"empty key not allowed\");\n                            return -1;\n                        }\n                        uri[i] = '\\0';\n                        param_value = NULL;\n                        state = PARAM_VALUE;\n                        break;\n\n                    case '|':\n                        AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL, \"%s\", \"invalid end of key\");\n                        return -1;\n\n                    default:\n                        if (NULL == param_key)\n                        {\n                            param_key = &uri[i];\n                        }\n                        break;\n                }\n                break;\n\n            case PARAM_VALUE:\n                switch (c)\n                {\n                    case '|':\n                        if (NULL == param_value)\n                        {\n                            AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL, \"%s\", \"empty value not allowed\");\n                            return -1;\n                        }\n                        uri[i] = '\\0';\n                        if (param_func(clientd, param_key, param_value) < 0)\n                        {\n                            return -1;\n                        }\n\n                        param_key = NULL;\n                        state = PARAM_KEY;\n                        break;\n\n                    default:\n                        if (NULL == param_value)\n                        {\n                            param_value = &uri[i];\n                        }\n                        break;\n                }\n                break;\n        }\n    }\n\n    if (state == PARAM_VALUE)\n    {\n        if (param_func(clientd, param_key, param_value) < 0)\n        {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nvoid aeron_uri_close(aeron_uri_t *params)\n{\n    if (params != NULL)\n    {\n        if (params->type == AERON_URI_UDP)\n        {\n            aeron_free(params->params.udp.additional_params.array);\n            params->params.udp.additional_params.array = NULL;\n        }\n        else if (params->type == AERON_URI_IPC)\n        {\n            aeron_free(params->params.ipc.additional_params.array);\n            params->params.ipc.additional_params.array = NULL;\n        }\n    }\n}\n\nint aeron_uri_params_ensure_capacity(aeron_uri_params_t *params)\n{\n    if (aeron_array_ensure_capacity(\n        (uint8_t **)&params->array, sizeof(aeron_uri_param_t), params->length, params->length + 1) >= 0)\n    {\n        params->length++;\n        return 0;\n    }\n\n    return -1;\n}\n\nstatic int aeron_udp_uri_params_func(void *clientd, const char *key, const char *value)\n{\n    aeron_udp_channel_params_t *params = (aeron_udp_channel_params_t *)clientd;\n\n    if (strcmp(key, AERON_UDP_CHANNEL_ENDPOINT_KEY) == 0)\n    {\n        params->endpoint = value;\n    }\n    else if (strcmp(key, AERON_UDP_CHANNEL_INTERFACE_KEY) == 0)\n    {\n        params->bind_interface = value;\n    }\n    else if (strcmp(key, AERON_UDP_CHANNEL_TTL_KEY) == 0)\n    {\n        params->ttl = value;\n    }\n    else if (strcmp(key, AERON_UDP_CHANNEL_CONTROL_KEY) == 0)\n    {\n        params->control = value;\n    }\n    else if (strcmp(key, AERON_UDP_CHANNEL_CONTROL_MODE_KEY) == 0)\n    {\n        params->control_mode = value;\n    }\n    else if (strcmp(key, AERON_URI_TAGS_KEY) == 0)\n    {\n        char *ptr = strchr(value, ',');\n\n        if (NULL != ptr)\n        {\n            *ptr++ = '\\0';\n            params->entity_tag = '\\0' == *ptr ? NULL : ptr;\n        }\n\n        params->channel_tag = '\\0' == *value ? NULL : value;\n    }\n    else\n    {\n        size_t index = params->additional_params.length;\n        if (aeron_uri_params_ensure_capacity(&params->additional_params) < 0)\n        {\n            return -1;\n        }\n\n        aeron_uri_param_t *param = &params->additional_params.array[index];\n\n        param->key = key;\n        param->value = value;\n    }\n\n    return 0;\n}\n\nstatic int aeron_ipc_uri_params_func(void *clientd, const char *key, const char *value)\n{\n    aeron_ipc_channel_params_t *params = (aeron_ipc_channel_params_t *)clientd;\n\n    if (strcmp(key, AERON_URI_TAGS_KEY) == 0)\n    {\n        char *ptr = strchr(value, ',');\n\n        if (NULL != ptr)\n        {\n            *ptr++ = '\\0';\n            params->entity_tag = '\\0' == *ptr ? NULL : ptr;\n        }\n\n        params->channel_tag = '\\0' == *value ? NULL : value;\n    }\n    else if (strcmp(key, AERON_UDP_CHANNEL_CONTROL_MODE_KEY) == 0)\n    {\n        params->control_mode = value;\n    }\n    else\n    {\n        size_t index = params->additional_params.length;\n        if (aeron_uri_params_ensure_capacity(&params->additional_params) < 0)\n        {\n            return -1;\n        }\n\n        aeron_uri_param_t *param = &params->additional_params.array[index];\n\n        param->key = key;\n        param->value = value;\n    }\n\n    return 0;\n}\n\nstatic void aeron_uri_reset(aeron_uri_t *params)\n{\n    params->mutable_uri[0] = '\\0';\n    params->type = AERON_URI_UNKNOWN;\n\n    params->params.udp.additional_params.length = 0;\n    params->params.udp.additional_params.array = NULL;\n    params->params.udp.endpoint = NULL;\n    params->params.udp.bind_interface = NULL;\n    params->params.udp.ttl = NULL;\n    params->params.udp.control = NULL;\n    params->params.udp.control_mode = NULL;\n    params->params.udp.channel_tag = NULL;\n    params->params.udp.entity_tag = NULL;\n\n    params->params.ipc.additional_params.length = 0;\n    params->params.ipc.additional_params.array = NULL;\n    params->params.ipc.channel_tag = NULL;\n    params->params.ipc.entity_tag = NULL;\n}\n\nint aeron_uri_parse(size_t uri_length, const char *uri, aeron_uri_t *params)\n{\n    if (NULL == params)\n    {\n        AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL, \"%s\", \"params is NULL\");\n        return -1;\n    }\n\n    aeron_uri_reset(params);\n\n    if (NULL == uri)\n    {\n        AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL, \"%s\", \"channel URI is NULL\");\n        return -1;\n    }\n\n    if (uri_length >= AERON_URI_MAX_LENGTH)\n    {\n        AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL,\n            \"URI length (%\" PRIu64 \") exceeds max supported length (%d): %.*s\",\n            uri_length, (int32_t)AERON_URI_MAX_LENGTH - 1, AERON_URI_MAX_LENGTH, uri);\n        return -1;\n    }\n\n    memcpy(params->mutable_uri, uri, uri_length);\n    params->mutable_uri[uri_length] = '\\0';\n\n    char *ptr = params->mutable_uri;\n\n    if (strncmp(ptr, AERON_URI_SCHEME, strlen(AERON_URI_SCHEME)) == 0)\n    {\n        ptr += strlen(AERON_URI_SCHEME);\n\n        if (strncmp(ptr, AERON_URI_UDP_TRANSPORT, strlen(AERON_URI_UDP_TRANSPORT)) == 0)\n        {\n            ptr += strlen(AERON_URI_UDP_TRANSPORT);\n\n            if (*ptr++ == '?')\n            {\n                params->type = AERON_URI_UDP;\n                int result = aeron_uri_parse_params(ptr, aeron_udp_uri_params_func, &params->params.udp);\n                if (result < 0)\n                {\n                    AERON_APPEND_ERR(\"%.*s\", (int)uri_length, uri);\n                }\n                return result;\n            }\n        }\n        else if (strncmp(ptr, AERON_URI_IPC_TRANSPORT, strlen(AERON_URI_IPC_TRANSPORT)) == 0)\n        {\n            ptr += strlen(AERON_URI_IPC_TRANSPORT);\n\n            if (*ptr == '\\0' || *ptr++ == '?')\n            {\n                params->type = AERON_URI_IPC;\n                int result = aeron_uri_parse_params(ptr, aeron_ipc_uri_params_func, &params->params.ipc);\n                if (result < 0)\n                {\n                    AERON_APPEND_ERR(\"%.*s\", (int)uri_length, uri);\n                }\n                return result;\n            }\n        }\n    }\n\n    AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL, \"invalid URI scheme or transport: %s\", uri);\n\n    return -1;\n}\n\nuint8_t aeron_uri_multicast_ttl(aeron_uri_t *uri)\n{\n    uint8_t result = 0;\n\n    if (AERON_URI_UDP == uri->type && NULL != uri->params.udp.ttl)\n    {\n        uint64_t value = strtoull(uri->params.udp.ttl, NULL, 0);\n        result = value > 255u ? (uint8_t)255 : (uint8_t)value;\n    }\n\n    return result;\n}\n\nconst char *aeron_uri_find_param_value(const aeron_uri_params_t *uri_params, const char *key)\n{\n    size_t key_len = strlen(key);\n\n    for (size_t i = 0, length = uri_params->length; i < length; i++)\n    {\n        aeron_uri_param_t *param = &uri_params->array[i];\n\n        if (strncmp(key, param->key, key_len) == 0)\n        {\n            return param->value;\n        }\n    }\n\n    return NULL;\n}\n\nint aeron_uri_get_int32(aeron_uri_params_t *uri_params, const char *key, int32_t *retval)\n{\n    const char *value_str;\n    if ((value_str = aeron_uri_find_param_value(uri_params, key)) == NULL)\n    {\n        *retval = 0;\n        return 0;\n    }\n\n    char *end_ptr = \"\";\n    errno = 0;\n    const long value = strtol(value_str, &end_ptr, 0);\n\n    if (0 != errno || '\\0' != *end_ptr)\n    {\n        AERON_SET_ERR(EINVAL, \"could not parse %s=%s as int32_t in URI: %s\", key, value_str, strerror(errno));\n        return -1;\n    }\n    else if (value < INT32_MIN || INT32_MAX < value)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"could not parse %s=%s as int32_t in URI: Numerical result out of range\", key, value_str);\n        return -1;\n    }\n\n    *retval = (int32_t)value;\n\n    return 1;\n}\n\nint aeron_uri_get_int64(aeron_uri_params_t *uri_params, const char *key, int64_t default_val, int64_t *retval)\n{\n    const char *value_str;\n    if ((value_str = aeron_uri_find_param_value(uri_params, key)) == NULL)\n    {\n        *retval = default_val;\n        return 0;\n    }\n\n    char *end_ptr;\n    int64_t value;\n\n    errno = 0;\n    value = strtoll(value_str, &end_ptr, 0);\n    if (0 != errno || '\\0' != *end_ptr)\n    {\n        AERON_SET_ERR(EINVAL, \"could not parse %s=%s as int64_t in URI: %s\", key, value_str, strerror(errno));\n        return -1;\n    }\n\n    *retval = value;\n\n    return 1;\n}\n\nint aeron_uri_get_bool(aeron_uri_params_t *uri_params, const char *key, bool *retval)\n{\n    const char *value_str = aeron_uri_find_param_value(uri_params, key);\n    if (value_str != NULL)\n    {\n        if (strncmp(\"true\", value_str, strlen(\"true\")) == 0)\n        {\n            *retval = true;\n        }\n        else if (strncmp(\"false\", value_str, strlen(\"false\")) == 0)\n        {\n            *retval = false;\n        }\n        else\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse %s=%s as bool from URI\", key, value_str);\n            return -1;\n        }\n    }\n\n    return 1;\n}\n\nint aeron_uri_get_size_t(aeron_uri_params_t *uri_params, const char *key, size_t *value)\n{\n    const char *value_str = aeron_uri_find_param_value(uri_params, key);\n    int result = 0;\n\n    if (NULL != value_str)\n    {\n        uint64_t temp_value = 0;\n\n        if (-1 == aeron_parse_size64(value_str, &temp_value))\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse %s=%s in URI\", key, value_str);\n            return -1;\n        }\n\n        *value = (size_t)temp_value;\n\n        result = 1;\n    }\n\n    return result;\n}\n\nint aeron_uri_get_ats(aeron_uri_params_t *uri_params, aeron_uri_ats_status_t *uri_ats_status)\n{\n    const char *value_str = aeron_uri_find_param_value(uri_params, AERON_URI_ATS_KEY);\n    *uri_ats_status = AERON_URI_ATS_STATUS_DEFAULT;\n    if (value_str != NULL)\n    {\n        if (strncmp(\"true\", value_str, strlen(\"true\")) == 0)\n        {\n            *uri_ats_status = AERON_URI_ATS_STATUS_ENABLED;\n        }\n        else if (strncmp(\"false\", value_str, strlen(\"false\")) == 0)\n        {\n            *uri_ats_status = AERON_URI_ATS_STATUS_DISABLED;\n        }\n        else\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse %s=%s as bool from URI\", AERON_URI_ATS_KEY, value_str);\n            return -1;\n        }\n    }\n\n    return 1;\n}\n\nint aeron_uri_get_socket_buf_lengths(\n    aeron_uri_params_t *uri_params, size_t *socket_sndbuf_length, size_t *socket_rcvbuf_length)\n{\n    int result = aeron_uri_get_size_t(uri_params, AERON_URI_SOCKET_SNDBUF_KEY, socket_sndbuf_length);\n\n    if (result < 0)\n    {\n        return result;\n    }\n\n    return aeron_uri_get_size_t(uri_params, AERON_URI_SOCKET_RCVBUF_KEY, socket_rcvbuf_length);\n}\n\nint aeron_uri_get_receiver_window_length(aeron_uri_params_t *uri_params, size_t *receiver_window_length)\n{\n    return aeron_uri_get_size_t(uri_params, AERON_URI_RECEIVER_WINDOW_KEY, receiver_window_length);\n}\n\nint aeron_uri_get_timeout(aeron_uri_params_t *uri_params, const char *param_name, uint64_t *timeout_ns)\n{\n    const char *value_str;\n\n    if ((value_str = aeron_uri_find_param_value(uri_params, param_name)) != NULL)\n    {\n        uint64_t value;\n\n        if (-1 == aeron_parse_duration_ns(value_str, &value))\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse %s=%s in URI\", param_name, value_str);\n            return -1;\n        }\n\n        *timeout_ns = value;\n    }\n\n    return 0;\n}\n\nint64_t aeron_uri_parse_tag(const char *tag_str)\n{\n    errno = 0;\n    char *end_ptr = NULL;\n    unsigned long value = strtoul(tag_str, &end_ptr, 10);\n\n    if ((0 == value && 0 != errno) || end_ptr == tag_str)\n    {\n        return AERON_URI_INVALID_TAG;\n    }\n\n    return (int64_t)value;\n}\n\ntypedef struct aeron_uri_print_context_stct\n{\n    char *buffer;\n    size_t buffer_len;\n    size_t offset;\n    const char *delimiter;\n}\naeron_uri_print_context_t;\n\nstatic int aeron_uri_print_next(aeron_uri_print_context_t *print_ctx, const char *key, const char *value)\n{\n    int result = 0;\n    if (print_ctx->offset < print_ctx->buffer_len && NULL != value)\n    {\n        char *dest = print_ctx->buffer + print_ctx->offset;\n        size_t dest_len = print_ctx->buffer_len - print_ctx->offset;\n        result = snprintf(dest, dest_len, \"%s%s=%s\", print_ctx->delimiter, key, value);\n\n        if (0 < result)\n        {\n            print_ctx->offset += result;\n            print_ctx->delimiter = \"|\";\n        }\n        else if (result < 0)\n        {\n            AERON_SET_ERR(result, \"Failed to print next uri item: %s\", key);\n        }\n    }\n\n    return result;\n}\n\nint aeron_uri_ipc_sprint(aeron_uri_t *uri, char *buffer, size_t buffer_len)\n{\n    aeron_uri_print_context_t ctx = { 0 };\n    ctx.buffer = buffer;\n    ctx.buffer_len = buffer_len;\n    ctx.offset = 0;\n    ctx.delimiter = \"?\";\n    const char *tags = NULL;\n    char tag_buffer[64];\n\n    aeron_ipc_channel_params_t *ipc_params = &uri->params.ipc;\n\n    if (ctx.offset < buffer_len)\n    {\n        int num_chars = snprintf(ctx.buffer, ctx.buffer_len - ctx.offset, \"aeron:ipc\");\n        ctx.offset += num_chars;\n    }\n\n    if (NULL != ipc_params->channel_tag)\n    {\n        if (NULL != ipc_params->entity_tag)\n        {\n            snprintf(tag_buffer, sizeof(tag_buffer), \"%s,%s\", ipc_params->channel_tag, ipc_params->entity_tag);\n            tags = tag_buffer;\n        }\n        else\n        {\n            tags = ipc_params->channel_tag;\n        }\n\n        if (aeron_uri_print_next(&ctx, AERON_URI_TAGS_KEY, tags) < 0)\n        {\n            return -1;\n        }\n    }\n\n    aeron_uri_params_t *additional = &ipc_params->additional_params;\n    for (size_t i = 0, n = additional->length; i < n; i++)\n    {\n        aeron_uri_print_next(&ctx, additional->array[i].key, additional->array[i].value);\n    }\n\n    return (int)ctx.offset;\n}\n\nint aeron_uri_udp_sprint(aeron_uri_t *uri, char *buffer, size_t buffer_len)\n{\n    aeron_uri_print_context_t ctx = { 0 };\n    ctx.buffer = buffer;\n    ctx.buffer_len = buffer_len;\n    ctx.offset = 0;\n    ctx.delimiter = \"?\";\n    char tag_buffer[64];\n    const char *tags = NULL;\n\n    aeron_udp_channel_params_t *udp_params = &uri->params.udp;\n\n    if (ctx.offset < buffer_len)\n    {\n        int num_chars = snprintf(ctx.buffer, ctx.buffer_len - ctx.offset, \"aeron:udp\");\n        ctx.offset += num_chars;\n    }\n\n    if (aeron_uri_print_next(&ctx, AERON_UDP_CHANNEL_ENDPOINT_KEY, udp_params->endpoint) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_print_next(&ctx, AERON_UDP_CHANNEL_INTERFACE_KEY, udp_params->bind_interface) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_print_next(&ctx, AERON_UDP_CHANNEL_CONTROL_KEY, udp_params->control) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_print_next(&ctx, AERON_UDP_CHANNEL_CONTROL_MODE_KEY, udp_params->control_mode) < 0)\n    {\n        return -1;\n    }\n    \n    if (NULL != udp_params->channel_tag)\n    {\n        if (NULL != udp_params->entity_tag)\n        {\n            snprintf(tag_buffer, sizeof(tag_buffer), \"%s,%s\", udp_params->channel_tag, udp_params->entity_tag);\n            tags = tag_buffer;\n        }\n        else\n        {\n            tags = udp_params->channel_tag;\n        }\n        \n        if (aeron_uri_print_next(&ctx, AERON_URI_TAGS_KEY, tags) < 0)\n        {\n            return -1;\n        }\n    }\n\n    if (aeron_uri_print_next(&ctx, AERON_UDP_CHANNEL_TTL_KEY, udp_params->ttl) < 0)\n    {\n        return -1;\n    }\n\n    aeron_uri_params_t *additional = &udp_params->additional_params;\n    for (size_t i = 0, n = additional->length; i < n; i++)\n    {\n        aeron_uri_print_next(&ctx, additional->array[i].key, additional->array[i].value);\n    }\n\n    return (int)ctx.offset;\n}\n\nint aeron_uri_sprint(aeron_uri_t *uri, char *buffer, size_t buffer_len)\n{\n    switch (uri->type)\n    {\n        case AERON_URI_UDP:\n            return aeron_uri_udp_sprint(uri, buffer, buffer_len);\n\n        case AERON_URI_IPC:\n            return aeron_uri_ipc_sprint(uri, buffer, buffer_len);\n\n        case AERON_URI_UNKNOWN:\n            return snprintf(buffer, buffer_len, \"aeron:unknown\");\n\n        default:\n            AERON_SET_ERR(EINVAL, \"Invalid URI type: %d\", (int)uri->type);\n            return -1;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/uri/aeron_uri.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_URI_H\n#define AERON_URI_H\n\n#include \"aeron_common.h\"\n\ntypedef struct aeron_uri_param_stct\n{\n    const char *key;\n    const char *value;\n}\naeron_uri_param_t;\n\ntypedef struct aeron_uri_params_stct\n{\n    size_t length;\n    aeron_uri_param_t *array;\n}\naeron_uri_params_t;\n\n#define AERON_SPY_PREFIX \"aeron-spy:\"\n#define AERON_SPY_PREFIX_LEN strlen(AERON_SPY_PREFIX)\n\n#define AERON_IPC_CHANNEL \"aeron:ipc\"\n#define AERON_IPC_CHANNEL_LEN strlen(AERON_IPC_CHANNEL)\n\n#define AERON_UDP_CHANNEL_RELIABLE_KEY \"reliable\"\n#define AERON_UDP_CHANNEL_TTL_KEY \"ttl\"\n\n#define AERON_UDP_CHANNEL_ENDPOINT_KEY \"endpoint\"\n#define AERON_UDP_CHANNEL_INTERFACE_KEY \"interface\"\n#define AERON_UDP_CHANNEL_CONTROL_KEY \"control\"\n#define AERON_UDP_CHANNEL_CONTROL_MODE_KEY \"control-mode\"\n#define AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL_VALUE \"manual\"\n#define AERON_UDP_CHANNEL_CONTROL_MODE_DYNAMIC_VALUE \"dynamic\"\n#define AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE \"response\"\n\n#define AERON_URI_INITIAL_TERM_ID_KEY \"init-term-id\"\n#define AERON_URI_TERM_ID_KEY \"term-id\"\n#define AERON_URI_TERM_OFFSET_KEY \"term-offset\"\n\n#define AERON_URI_ALIAS_KEY \"alias\"\n#define AERON_URI_TERM_LENGTH_KEY \"term-length\"\n#define AERON_URI_LINGER_TIMEOUT_KEY \"linger\"\n#define AERON_URI_MTU_LENGTH_KEY \"mtu\"\n#define AERON_URI_SPARSE_TERM_KEY \"sparse\"\n#define AERON_URI_EOS_KEY \"eos\"\n#define AERON_URI_TETHER_KEY \"tether\"\n#define AERON_URI_TAGS_KEY \"tags\"\n#define AERON_URI_SESSION_ID_KEY \"session-id\"\n#define AERON_URI_GROUP_KEY \"group\"\n#define AERON_URI_REJOIN_KEY \"rejoin\"\n#define AERON_URI_FC_KEY \"fc\"\n#define AERON_URI_GTAG_KEY \"gtag\"\n#define AERON_URI_CC_KEY \"cc\"\n#define AERON_URI_SPIES_SIMULATE_CONNECTION_KEY \"ssc\"\n#define AERON_URI_ATS_KEY \"ats\"\n#define AERON_URI_SOCKET_SNDBUF_KEY \"so-sndbuf\"\n#define AERON_URI_SOCKET_RCVBUF_KEY \"so-rcvbuf\"\n#define AERON_URI_RECEIVER_WINDOW_KEY \"rcv-wnd\"\n#define AERON_URI_MEDIA_RCV_TIMESTAMP_OFFSET_KEY \"media-rcv-ts-offset\"\n#define AERON_URI_CHANNEL_RCV_TIMESTAMP_OFFSET_KEY \"channel-rcv-ts-offset\"\n#define AERON_URI_CHANNEL_SND_TIMESTAMP_OFFSET_KEY \"channel-snd-ts-offset\"\n#define AERON_URI_TIMESTAMP_OFFSET_RESERVED \"reserved\"\n#define AERON_URI_RESPONSE_CORRELATION_ID_PROTOTYPE \"prototype\"\n#define AERON_URI_RESPONSE_CORRELATION_ID_KEY \"response-correlation-id\"\n#define AERON_URI_NAK_DELAY_KEY \"nak-delay\"\n#define AERON_URI_UNTETHERED_WINDOW_LIMIT_TIMEOUT_KEY \"untethered-window-limit-timeout\"\n#define AERON_URI_UNTETHERED_LINGER_TIMEOUT_KEY \"untethered-linger-timeout\"\n#define AERON_URI_UNTETHERED_RESTING_TIMEOUT_KEY \"untethered-resting-timeout\"\n#define AERON_URI_MAX_RESEND_KEY \"max-resend\"\n#define AERON_URI_STREAM_ID_KEY \"stream-id\"\n#define AERON_URI_PUBLICATION_WINDOW_KEY \"pub-wnd\"\n#define AERON_URI_INVALID_TAG (-1)\n\n#define AERON_URI_MAX_LENGTH (4096) // max channel uri length including NUL character\n\ntypedef struct aeron_udp_channel_params_stct\n{\n    const char *endpoint;\n    const char *bind_interface;\n    const char *control;\n    const char *control_mode;\n    const char *channel_tag;\n    const char *entity_tag;\n    const char *ttl;\n    aeron_uri_params_t additional_params;\n}\naeron_udp_channel_params_t;\n\ntypedef struct aeron_ipc_channel_params_stct\n{\n    const char *channel_tag;\n    const char *entity_tag;\n    const char *control_mode;\n    aeron_uri_params_t additional_params;\n}\naeron_ipc_channel_params_t;\n\ntypedef enum aeron_uri_type_enum\n{\n    AERON_URI_UDP, AERON_URI_IPC, AERON_URI_UNKNOWN\n}\naeron_uri_type_t;\n\ntypedef struct aeron_uri_stct\n{\n    char mutable_uri[AERON_URI_MAX_LENGTH];\n    aeron_uri_type_t type;\n\n    union\n    {\n        aeron_udp_channel_params_t udp;\n        aeron_ipc_channel_params_t ipc;\n    }\n    params;\n}\naeron_uri_t;\n\ntypedef enum aeron_uri_ats_status_en\n{\n    AERON_URI_ATS_STATUS_DEFAULT,\n    AERON_URI_ATS_STATUS_ENABLED,\n    AERON_URI_ATS_STATUS_DISABLED\n}\naeron_uri_ats_status_t;\n\ntypedef int (*aeron_uri_parse_callback_t)(void *clientd, const char *key, const char *value);\n\nint aeron_uri_parse_params(char *uri, aeron_uri_parse_callback_t param_func, void *clientd);\n\nint aeron_uri_parse(size_t uri_length, const char *uri, aeron_uri_t *params);\n\nvoid aeron_uri_close(aeron_uri_t *params);\n\nuint8_t aeron_uri_multicast_ttl(aeron_uri_t *uri);\n\nconst char *aeron_uri_find_param_value(const aeron_uri_params_t *uri_params, const char *key);\nint aeron_uri_get_int32(aeron_uri_params_t *uri_params, const char *key, int32_t *retval);\nint aeron_uri_get_int64(aeron_uri_params_t *uri_params, const char *key, int64_t default_val, int64_t *retval);\nint aeron_uri_get_bool(aeron_uri_params_t *uri_params, const char *key, bool *retval);\nint aeron_uri_get_ats(aeron_uri_params_t *uri_params, aeron_uri_ats_status_t *uri_ats_status);\nint aeron_uri_get_timeout(aeron_uri_params_t *uri_params, const char *param_name, uint64_t *timeout_ns);\nint aeron_uri_sprint(aeron_uri_t *uri, char *buffer, size_t buffer_len);\nint aeron_uri_get_socket_buf_lengths(\n    aeron_uri_params_t *uri_params, size_t *socket_sndbuf_length, size_t *socket_rcvbuf_length);\nint aeron_uri_get_receiver_window_length(aeron_uri_params_t *uri_params, size_t *receiver_window_length);\n\nint64_t aeron_uri_parse_tag(const char *tag_str);\n\n#endif //AERON_URI_H\n"
  },
  {
    "path": "aeron-client/src/main/c/uri/aeron_uri_string_builder.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <inttypes.h>\n\n#include \"uri/aeron_uri_string_builder.h\"\n#include \"concurrent/aeron_logbuffer_descriptor.h\"\n\ntypedef struct aeron_uri_string_builder_entry_stct\n{\n    const char *key;\n    const char *value;\n}\naeron_uri_string_builder_entry_t;\n\nint aeron_uri_string_builder_init_new(aeron_uri_string_builder_t *builder)\n{\n    aeron_str_to_ptr_hash_map_init(&builder->params, 64, AERON_MAP_DEFAULT_LOAD_FACTOR);\n\n    builder->closed = false;\n\n    return 0;\n}\n\nstatic int aeron_uri_string_builder_params_func(void *clientd, const char *key, const char *value)\n{\n    return aeron_uri_string_builder_put((aeron_uri_string_builder_t *)clientd, key, value);\n}\n\nint aeron_uri_string_builder_init_on_string(aeron_uri_string_builder_t *builder, const char *uri)\n{\n    int rc = 0;\n    aeron_uri_string_builder_init_new(builder);\n\n    size_t uri_length = strlen(uri);\n\n    char *buffer;\n    aeron_alloc((void **)&buffer, uri_length + 1);\n    strncpy(buffer, uri, uri_length + 1);\n\n    char *ptr = buffer;\n    char *end_ptr = NULL;\n\n    if (strncmp(\"aeron:\", ptr, 6) != 0)\n    {\n        end_ptr = strchr(ptr, ':');\n        if (end_ptr == NULL)\n        {\n            AERON_SET_ERR(EINVAL, \"%s\", \"uri must start with '[prefix:]aeron:[media]'\");\n            goto error_cleanup;\n        }\n\n        // replace ':' after prefix with NULL character\n        *end_ptr = '\\0';\n\n        aeron_uri_string_builder_put(builder, AERON_URI_STRING_BUILDER_PREFIX_KEY, ptr);\n\n        // move ptr past the prefix\n        ptr = end_ptr + 1;\n    }\n\n    if (strncmp(\"aeron:\", ptr, 6) != 0)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"uri found without 'aeron:'\");\n        goto error_cleanup;\n    }\n\n    // *ptr == \"aeron:\"\n    ptr = strchr(ptr, ':');\n    ptr++;\n\n    // *ptr == [media] (up to possible ?)\n    end_ptr = strchr(ptr, '?');\n\n    if (NULL != end_ptr)\n    {\n        // replace '?' after media with NULL character\n        *end_ptr = '\\0';\n    }\n\n    aeron_uri_string_builder_put(builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, ptr);\n\n    if (NULL == end_ptr)\n    {\n        goto cleanup;\n    }\n\n    if (aeron_uri_parse_params(end_ptr + 1, aeron_uri_string_builder_params_func, builder) == 0)\n    {\n        goto cleanup;\n    }\n\nerror_cleanup:\n    aeron_uri_string_builder_close(builder);\n\n    rc = -1;\n\ncleanup:\n    aeron_free(buffer);\n\n    return rc;\n}\n\nstatic void aeron_uri_string_builder_entry_delete(void *clientd, const char *key, size_t key_len, void *value)\n{\n    aeron_free(value);\n}\n\nint aeron_uri_string_builder_close(aeron_uri_string_builder_t *builder)\n{\n    if (!builder->closed)\n    {\n        aeron_str_to_ptr_hash_map_for_each(&builder->params, aeron_uri_string_builder_entry_delete, NULL);\n\n        aeron_str_to_ptr_hash_map_delete(&builder->params);\n    }\n\n    builder->closed = true;\n\n    return 0;\n}\n\nint aeron_uri_string_builder_put(aeron_uri_string_builder_t *builder, const char *key, const char *value)\n{\n    if (NULL == builder)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"builder must not be NULL\");\n        return -1;\n    }\n\n    if (NULL == key)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"key must not be NULL\");\n        return -1;\n    }\n\n    if (NULL != strchr(key, '?') ||\n        NULL != strchr(key, '|') ||\n        NULL != strchr(key, '='))\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"key cannot contain '?', '|' or '='\");\n        return -1;\n    }\n\n    if (NULL != value)\n    {\n        if (NULL != strchr(value, '?') ||\n            NULL != strchr(value, '|') ||\n            NULL != strchr(value, '='))\n        {\n            AERON_SET_ERR(EINVAL, \"%s\", \"value cannot contain '?', '|' or '='\");\n            return -1;\n        }\n    }\n\n    size_t key_len = strlen(key);\n\n    aeron_uri_string_builder_entry_t *entry = NULL;\n\n    entry = aeron_str_to_ptr_hash_map_remove(&builder->params, key, key_len);\n    if (NULL != entry)\n    {\n        aeron_free(entry);\n    }\n\n    if (NULL == value)\n    {\n        return 0;\n    }\n\n    size_t value_len = strlen(value);\n\n    // entry struct + key string + value string (plus trailing '\\0' for both strings\n    aeron_alloc((void **)&entry, sizeof(aeron_uri_string_builder_entry_t) + key_len + 1 + value_len + 1);\n\n    entry->key = ((const char *)entry + sizeof(aeron_uri_string_builder_entry_t));\n    entry->value = entry->key + key_len + 1;\n\n    strncpy((char *)entry->key, key, key_len + 1);\n    strncpy((char *)entry->value, value, value_len + 1);\n\n    return aeron_str_to_ptr_hash_map_put(&builder->params, entry->key, key_len, entry);\n}\n\nint aeron_uri_string_builder_put_int32(aeron_uri_string_builder_t *builder, const char *key, int32_t value)\n{\n    char buffer[12];\n\n    snprintf(buffer, sizeof(buffer), \"%\" PRIi32, value);\n\n    return aeron_uri_string_builder_put(builder, key, buffer);\n}\n\nint aeron_uri_string_builder_put_int64(aeron_uri_string_builder_t *builder, const char *key, int64_t value)\n{\n    char buffer[21];\n\n    snprintf(buffer, sizeof(buffer), \"%\" PRIi64, value);\n\n    return aeron_uri_string_builder_put(builder, key, buffer);\n}\n\nconst char *aeron_uri_string_builder_get(aeron_uri_string_builder_t *builder, const char *key)\n{\n    aeron_uri_string_builder_entry_t *entry = NULL;\n\n    entry = aeron_str_to_ptr_hash_map_get(&builder->params, key, strlen(key));\n\n    if (entry == NULL)\n    {\n        return NULL;\n    }\n\n    return entry->value;\n}\n\ntypedef struct aeron_uri_string_builder_print_context_stct\n{\n    char *buffer;\n    size_t buffer_len;\n    size_t offset;\n    int result;\n    const char *delimiter;\n}\naeron_uri_string_builder_print_context_t;\n\nstatic void aeron_uri_string_builder_print(void *clientd, const char *key, size_t key_len, void *value)\n{\n    aeron_uri_string_builder_print_context_t *ctx = (aeron_uri_string_builder_print_context_t *)clientd;\n    aeron_uri_string_builder_entry_t *entry = (aeron_uri_string_builder_entry_t *)value;\n\n    if (ctx->result < 0 ||\n        strcmp(AERON_URI_STRING_BUILDER_PREFIX_KEY, entry->key) == 0 ||\n        strcmp(AERON_URI_STRING_BUILDER_MEDIA_KEY, entry->key) == 0)\n    {\n        return;\n    }\n\n    int result = snprintf(\n        ctx->buffer + ctx->offset, ctx->buffer_len - ctx->offset, \"%s%s=%s\", ctx->delimiter, entry->key, entry->value);\n\n    if (result < 0)\n    {\n        ctx->result = result;\n        AERON_SET_ERR(result, \"Failed to print next uri item: %s\", entry->key);\n        return;\n    }\n\n    ctx->offset += (size_t)result;\n    ctx->delimiter = \"|\";\n}\n\nint aeron_uri_string_builder_sprint(aeron_uri_string_builder_t *builder, char *buffer, size_t buffer_len)\n{\n    aeron_uri_string_builder_print_context_t ctx;\n\n    ctx.buffer = buffer;\n    ctx.buffer_len = buffer_len;\n    ctx.offset = 0;\n    ctx.result = 0;\n    ctx.delimiter = \"?\";\n\n    aeron_uri_string_builder_entry_t *entry;\n\n    entry = aeron_str_to_ptr_hash_map_get(\n        &builder->params,\n        AERON_URI_STRING_BUILDER_PREFIX_KEY,\n        strlen(AERON_URI_STRING_BUILDER_PREFIX_KEY));\n\n    if (NULL != entry)\n    {\n        int result = snprintf(ctx.buffer + ctx.offset, ctx.buffer_len - ctx.offset, \"%s:\", entry->value);\n        if (result < 0)\n        {\n            AERON_SET_ERR(result, \"Failed to print uri prefix: %s\", entry->value);\n            return -1;\n        }\n\n        ctx.offset += (size_t)result;\n    }\n\n    entry = aeron_str_to_ptr_hash_map_get(\n        &builder->params,\n        AERON_URI_STRING_BUILDER_MEDIA_KEY,\n        strlen(AERON_URI_STRING_BUILDER_MEDIA_KEY));\n\n    if (NULL == entry)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"No media defined in the uri\");\n        return -1;\n    }\n\n    int result = snprintf(ctx.buffer + ctx.offset, ctx.buffer_len - ctx.offset, \"aeron:%s\", entry->value);\n    if (result < 0)\n    {\n        AERON_SET_ERR(result, \"Failed to print uri media: %s\", entry->value);\n        return -1;\n    }\n\n    ctx.offset += (size_t)result;\n\n    aeron_str_to_ptr_hash_map_for_each(&builder->params, aeron_uri_string_builder_print, &ctx);\n\n    return ctx.result;\n}\n\nint aeron_uri_string_builder_set_initial_position(\n    aeron_uri_string_builder_t *builder,\n    int64_t position,\n    int32_t initial_term_id,\n    int32_t term_length)\n{\n    if (position < 0 ||\n        (position & (AERON_LOGBUFFER_FRAME_ALIGNMENT - 1)))\n    {\n        AERON_SET_ERR(EINVAL, \"position not multiple of FRAME_ALIGNMENT: %\" PRIi64, position);\n        return -1;\n    }\n\n    if (aeron_logbuffer_check_term_length(term_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    int bits_to_shift = aeron_number_of_trailing_zeroes_u64(term_length);\n\n    if (aeron_uri_string_builder_put_int32(builder, AERON_URI_TERM_LENGTH_KEY, term_length) < 0 ||\n        aeron_uri_string_builder_put_int32(builder, AERON_URI_INITIAL_TERM_ID_KEY, initial_term_id) < 0 ||\n        aeron_uri_string_builder_put_int32(\n            builder,\n            AERON_URI_TERM_ID_KEY,\n            (position >> bits_to_shift) + initial_term_id) < 0 ||\n        aeron_uri_string_builder_put_int32(builder, AERON_URI_TERM_OFFSET_KEY, position & (term_length - 1)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/uri/aeron_uri_string_builder.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_URI_STRING_BUILDER_H\n#define AERON_URI_STRING_BUILDER_H\n\n#include \"aeron_common.h\"\n#include \"collections/aeron_str_to_ptr_hash_map.h\"\n#include \"uri/aeron_uri.h\"\n\ntypedef struct aeron_uri_string_builder_stct\n{\n    aeron_str_to_ptr_hash_map_t params;\n    bool closed;\n}\naeron_uri_string_builder_t;\n\n#define AERON_URI_STRING_BUILDER_PREFIX_KEY \"__prefix\"\n#define AERON_URI_STRING_BUILDER_MEDIA_KEY \"__media\"\n\nint aeron_uri_string_builder_init_new(aeron_uri_string_builder_t *builder);\nint aeron_uri_string_builder_init_on_string(aeron_uri_string_builder_t *builder, const char *uri);\n/* TODO:\nint aeron_uri_string_builder_init_on_uri(aeron_uri_string_builder_t *builder, aeron_uri_t *uri);\n */\n\nint aeron_uri_string_builder_close(aeron_uri_string_builder_t *builder);\n\n// a _put with a value of NULL will clear the entry\nint aeron_uri_string_builder_put(aeron_uri_string_builder_t *builder, const char *key, const char *value);\nint aeron_uri_string_builder_put_int32(aeron_uri_string_builder_t *builder, const char *key, int32_t value);\nint aeron_uri_string_builder_put_int64(aeron_uri_string_builder_t *builder, const char *key, int64_t value);\n\nconst char *aeron_uri_string_builder_get(aeron_uri_string_builder_t *builder, const char *key);\n\nint aeron_uri_string_builder_sprint(aeron_uri_string_builder_t *builder, char *buffer, size_t buffer_len);\n\nint aeron_uri_string_builder_set_initial_position(\n    aeron_uri_string_builder_t *builder,\n    int64_t position,\n    int32_t initial_term_id,\n    int32_t term_length);\n\n#endif //AERON_URI_STRING_BUILDER_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_arrayutil.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"util/aeron_arrayutil.h\"\n\nextern int aeron_array_ensure_capacity(\n    uint8_t **array, size_t element_size, size_t old_capacity, size_t new_capacity);\n\nextern void aeron_array_fast_unordered_remove(\n    uint8_t *restrict array, size_t element_size, size_t index, size_t last_index);\n\nextern int aeron_array_add(\n    uint8_t **array, size_t element_size, size_t new_length, uint8_t *restrict element_to_add);\n\nextern int aeron_array_remove(\n    uint8_t **array, size_t element_size, size_t index, size_t old_length);\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_arrayutil.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ARRAYUTIL_H\n#define AERON_ARRAYUTIL_H\n\n#include \"aeron_platform.h\"\n\n#if defined(AERON_COMPILER_MSVC)\n#define restrict __restrict\n#endif\n\n#include <inttypes.h>\n#include <stddef.h>\n#include <string.h>\n#include <errno.h>\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n\n#define AERON_ARRAY_ENSURE_CAPACITY(r, a, t) \\\nif (a.length >= a.capacity) \\\n{ \\\n    size_t new_capacity = 0 == a.capacity ? 2 : (a.capacity + (a.capacity / 2)); \\\n    r = aeron_array_ensure_capacity((uint8_t **)&a.array, sizeof(t), a.capacity, new_capacity); \\\n    if (r >= 0) \\\n    { \\\n       a.capacity = new_capacity; \\\n    } \\\n}\n\ninline int aeron_array_ensure_capacity(uint8_t **array, size_t element_size, size_t old_capacity, size_t new_capacity)\n{\n    if (aeron_reallocf((void **)array, new_capacity * element_size) < 0)\n    {\n        AERON_APPEND_ERR(\"could not ensure capacity of: %\" PRIu64, (uint64_t)(new_capacity * element_size));\n        return -1;\n    }\n\n    memset(*array + (old_capacity * element_size), 0, (new_capacity - old_capacity) * element_size);\n    return 0;\n}\n\ninline void aeron_array_fast_unordered_remove(\n    uint8_t *restrict array, size_t element_size, size_t index, size_t last_index)\n{\n    memcpy(array + (index * element_size), array + (last_index * element_size), element_size);\n}\n\ninline int aeron_array_add(uint8_t **array, size_t element_size, size_t new_length, uint8_t *restrict element_to_add)\n{\n    if (aeron_reallocf((void **)array, element_size * new_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"could not array add\");\n        return -1;\n    }\n\n    memcpy(*array + ((new_length - 1) * element_size), element_to_add, element_size);\n    return 0;\n}\n\ninline int aeron_array_remove(uint8_t **array, size_t element_size, size_t index, size_t old_length)\n{\n    for (size_t i = index; i < (old_length - 1); i++)\n    {\n        memcpy(*array + (i * element_size), *array + ((i + 1) * element_size), element_size);\n    }\n\n    if (aeron_reallocf((void **)array, (old_length - 1) * element_size) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"could not array remove realloc\");\n        return -1;\n    }\n\n    return 0;\n}\n\n#endif //AERON_ARRAYUTIL_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_bitutil.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#ifdef HAVE_BSDSTDLIB_H\n#include <bsd/stdlib.h>\n#endif\n#endif\n\n#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#if defined(__linux__) || defined(__APPLE__)\n#include <unistd.h>\n#endif\n\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_platform.h\"\n\n#if defined(AERON_COMPILER_MSVC)\n#define _CRT_RAND_S\n#endif\n\n#ifndef HAVE_ARC4RANDOM\n#ifdef HAVE_DEV_URANDOM\n#include \"concurrent/aeron_thread.h\"\n\nstatic AERON_INIT_ONCE aeron_dev_urandom_is_initialized = AERON_INIT_ONCE_VALUE;\nstatic int aeron_dev_urandom_fd = -1;\n\nstatic void init_dev_urandom(void)\n{\n    if ((aeron_dev_urandom_fd = open(\"/dev/urandom\", O_RDONLY)) < 0)\n    {\n        fprintf(stderr, \"could not open /dev/urandom (%d): %s\\n\", errno, strerror(errno));\n        exit(EXIT_FAILURE);\n    }\n}\n#endif\n#endif\n\nint32_t aeron_randomised_int32(void)\n{\n    int32_t result;\n\n#ifdef HAVE_ARC4RANDOM\n    uint32_t value = arc4random();\n\n    memcpy(&result, &value, sizeof(int32_t));\n#elif defined(HAVE_DEV_URANDOM)\n    aeron_thread_once(&aeron_dev_urandom_is_initialized, init_dev_urandom);\n\n    if (sizeof(result) != read(aeron_dev_urandom_fd, &result, sizeof(result)))\n    {\n        fprintf(stderr, \"Failed to read from aeron_dev_random (%d): %s\\n\", errno, strerror(errno));\n        exit(EXIT_FAILURE);\n    }\n#elif defined(AERON_COMPILER_MSVC)\n    uint32_t value;\n    if (0 == rand_s(&value))\n    {\n        memcpy(&result, &value, sizeof(int32_t));\n    }\n    else\n    {\n        result = (int32_t)(rand() / (double)RAND_MAX * INT_MAX);\n    }\n#else\n    result = (int32_t)(rand() / (double)RAND_MAX * INT_MAX);\n#endif\n    return result;\n}\n\nextern uint8_t *aeron_cache_line_align_buffer(uint8_t *buffer);\nextern int aeron_number_of_trailing_zeroes(int32_t value);\nextern int aeron_number_of_trailing_zeroes_u64(uint64_t value);\nextern int aeron_number_of_leading_zeroes(uint32_t value);\nextern int aeron_number_of_leading_zeroes_u64(uint64_t value);\nextern int32_t aeron_find_next_power_of_two(int32_t value);\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_bitutil.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_BITUTIL_H\n#define AERON_BITUTIL_H\n\n#include <assert.h>\n#include <stdint.h>\n#include <stddef.h>\n#include \"util/aeron_platform.h\"\n\n#if defined(_MSC_VER)\n#include <intrin.h>\n#pragma intrinsic(_BitScanForward)\n#pragma intrinsic(_BitScanReverse)\n#pragma intrinsic(_BitScanForward64)\n#pragma intrinsic(_BitScanReverse64)\n#endif\n\n#define AERON_CACHE_LINE_LENGTH (64u)\n\n#define AERON_ALIGN(value, alignment) (((value) + ((alignment) - 1u)) & ~((alignment) - 1u))\n\n#define AERON_PADDED_SIZEOF(_struct) AERON_ALIGN(sizeof(_struct), sizeof(int32_t))\n\n#define AERON_IS_POWER_OF_TWO(value) ((value) > 0 && (((value) & (~(value) + 1u)) == (value)))\n\n#define AERON_MIN(a, b) ((a) < (b) ? (a) : (b))\n\n#if defined(__GNUC__)\n#define AERON_C_COND_EXPECT(exp, c) (__builtin_expect((exp), c))\n#else\n#define AERON_C_COND_EXPECT(exp, c) (exp)\n#endif\n\ninline uint8_t *aeron_cache_line_align_buffer(uint8_t *buffer)\n{\n    size_t remainder = ((size_t)buffer) % AERON_CACHE_LINE_LENGTH;\n    return 0 == remainder ? buffer : (buffer + (AERON_CACHE_LINE_LENGTH - remainder));\n}\n\n/* Taken from Hacker's Delight as ntz10 at http://www.hackersdelight.org/hdcodetxt/ntz.c.txt */\ninline int aeron_number_of_trailing_zeroes(int32_t value)\n{\n    if (0 == value)\n    {\n        return 32;\n    }\n#if defined(__GNUC__)\n    return __builtin_ctz(value);\n#elif defined(_MSC_VER)\n    unsigned long r;\n    _BitScanForward(&r, (unsigned long)value);\n    return (int)r;\n#else\n    // Hacker's Delight. Figure 5-26.\n    char table[32] =\n    {\n        0, 1, 2, 24, 3, 19, 6, 25,\n        22, 4, 20, 10, 16, 7, 12, 26,\n        31, 23, 18, 5, 21, 9, 15, 11,\n        30, 17, 8, 14, 29, 13, 28, 27\n    };\n\n    uint32_t index = (uint32_t)((value & -value) * 0x04D7651F);\n\n    return table[index >> 27u];\n#endif\n}\n\ninline int aeron_number_of_trailing_zeroes_u64(uint64_t value)\n{\n    if (0 == value)\n    {\n        return 64;\n    }\n#if defined(__GNUC__)\n    return __builtin_ctzll(value);\n#elif defined(_MSC_VER)\n    unsigned long r;\n    _BitScanForward64(&r, (__int64)value);\n    return (int)r;\n#else\n    int lower_tzc = aeron_number_of_trailing_zeroes((int32_t) (value & UINT64_C(0xFFFFFFFF)));\n    if (32 != lower_tzc)\n    {\n        return lower_tzc;\n    }\n    int upper_tzc = aeron_number_of_trailing_zeroes((int32_t) ((value >> 32u) & UINT64_C(0xFFFFFFFF)));\n    return lower_tzc + upper_tzc;\n#endif\n}\n\ninline int aeron_number_of_leading_zeroes(uint32_t value)\n{\n    if (0 == value)\n    {\n        return 32;\n    }\n#if defined(__GNUC__)\n    return __builtin_clz(value);\n#elif defined(_MSC_VER)\n    unsigned long r;\n    _BitScanReverse(&r, (unsigned long)value);\n    return 31 - (int)r;\n#else\n    // Hacker's Delight. Figure 5-18.\n    char table[64] =\n    {\n        32, 20, 19, -1, -1, 18, -1, 7, 10, 17, -1, -1, 14, -1, 6, -1,\n        -1, 9, -1, 16, -1, -1, 1, 26, -1, 13, -1, -1, 24, 5, -1, -1,\n        -1, 21, -1, 8, 11, -1, 15, -1,  -1,  -1,  -1, 2, 27, 0, 25, -1,\n        22, -1, 12, -1, -1, 3, 28, -1, 23, -1, 4, 29, -1, -1, 30, 31\n    };\n\n    value = value | (value >> 1);\n    value = value | (value >> 2);\n    value = value | (value >> 4);\n    value = value | (value >> 8);\n    value = value & ~(value >> 16);\n    value = value * 0xFD7049FF;\n\n    return table[value >> 26];\n#endif\n}\n\ninline int aeron_number_of_leading_zeroes_u64(uint64_t value)\n{\n    if (0 == value)\n    {\n        return 64;\n    }\n#if defined(__GNUC__)\n    return __builtin_clzll(value);\n#elif defined(_MSC_VER)\n    unsigned long r;\n    _BitScanReverse64(&r, (__int64)value);\n    return 63 - (int)r;\n#else\n    int upper_lzc = aeron_number_of_leading_zeroes((int32_t) ((value >> 32u) & UINT64_C(0xFFFFFFFF)));\n    if (32 != upper_lzc)\n    {\n        return upper_lzc;\n    }\n    int lower_lzc = aeron_number_of_leading_zeroes((int32_t) (value & UINT64_C(0xFFFFFFFF)));\n    return upper_lzc + lower_lzc;\n#endif\n}\n\ninline int32_t aeron_find_next_power_of_two(int32_t value)\n{\n    value--;\n\n    /*\n     * Set all bits below the leading one using binary expansion\n     * http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2\n     */\n    for (size_t i = 1; i < sizeof(value) * 8; i = i * 2)\n    {\n        value |= (value >> i);\n    }\n\n    return value + 1;\n}\n\nint32_t aeron_randomised_int32(void);\n\n#endif //AERON_BITUTIL_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_clock.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include \"aeron_alloc.h\"\n#include \"util/aeron_clock.h\"\n#include \"concurrent/aeron_atomic.h\"\n\n#if defined(AERON_COMPILER_MSVC)\n\n#include <windows.h>\n\n#define MS_PER_SEC  1000ULL     // MS = milliseconds\n#define US_PER_MS   1000ULL     // US = microseconds\n#define HNS_PER_US  10ULL       // HNS = hundred-nanoseconds (e.g., 1 hns = 100 ns)\n#define NS_PER_US   1000ULL\n\n#define HNS_PER_SEC (MS_PER_SEC * US_PER_MS * HNS_PER_US)\n#define NS_PER_HNS  (100ULL)    // NS = nanoseconds\n#define NS_PER_SEC  (MS_PER_SEC * US_PER_MS * NS_PER_US)\n\nint aeron_clock_gettime_monotonic(struct timespec *tv)\n{\n    static LARGE_INTEGER ticksPerSec;\n    LARGE_INTEGER ticks;\n\n    if (!ticksPerSec.QuadPart)\n    {\n        QueryPerformanceFrequency(&ticksPerSec);\n        if (!ticksPerSec.QuadPart)\n        {\n            errno = ENOTSUP;\n            return -1;\n        }\n    }\n\n    QueryPerformanceCounter(&ticks);\n\n    double seconds = (double)ticks.QuadPart / (double)ticksPerSec.QuadPart;\n    tv->tv_sec = (time_t)seconds;\n    tv->tv_nsec = (long)((ULONGLONG)(seconds * NS_PER_SEC) % NS_PER_SEC);\n\n    return 0;\n}\n\nint aeron_clock_gettime_realtime(struct timespec *tv)\n{\n    FILETIME ft;\n    ULARGE_INTEGER hnsTime;\n\n    GetSystemTimeAsFileTime(&ft);\n\n    hnsTime.LowPart = ft.dwLowDateTime;\n    hnsTime.HighPart = ft.dwHighDateTime;\n\n    hnsTime.QuadPart -= (11644473600ULL * HNS_PER_SEC);\n\n    tv->tv_nsec = (long)((hnsTime.QuadPart % HNS_PER_SEC) * NS_PER_HNS);\n    tv->tv_sec = (long)(hnsTime.QuadPart / HNS_PER_SEC);\n\n    return 0;\n}\n\nstatic inline int aeron_clock_gettime_realtime_coarse(struct timespec *tv)\n{\n    return aeron_clock_gettime_realtime(tv);\n}\n\n#else\n\nint aeron_clock_gettime_monotonic(struct timespec *tp)\n{\n#if defined(__CYGWIN__) || defined(__linux__)\n    return clock_gettime(CLOCK_MONOTONIC, tp);\n#else\n    return clock_gettime(CLOCK_MONOTONIC_RAW, tp);\n#endif\n}\n\nstatic int aeron_clock_gettime_realtime_coarse(struct timespec *time)\n{\n#if defined(CLOCK_REALTIME_COARSE)\n    return clock_gettime(CLOCK_REALTIME_COARSE, time);\n#else\n    return clock_gettime(CLOCK_REALTIME, time);\n#endif\n}\n\nint aeron_clock_gettime_realtime(struct timespec *time)\n{\n    return clock_gettime(CLOCK_REALTIME, time);\n}\n\n#endif\n\nint64_t aeron_nano_clock(void)\n{\n    struct timespec ts;\n    if (aeron_clock_gettime_monotonic(&ts) < 0)\n    {\n        return -1;\n    }\n\n    return ((int64_t)ts.tv_sec * 1000000000) + ts.tv_nsec;\n}\n\nint64_t aeron_epoch_clock(void)\n{\n    struct timespec ts;\n    if (aeron_clock_gettime_realtime_coarse(&ts) < 0)\n    {\n        return -1;\n    }\n\n    return ((int64_t)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);\n}\n\nvoid aeron_clock_update_cached_time(aeron_clock_cache_t *cached_clock, int64_t epoch_time, int64_t nano_time)\n{\n    AERON_SET_RELEASE(cached_clock->cached_epoch_time, epoch_time);\n    AERON_SET_RELEASE(cached_clock->cached_nano_time, nano_time);\n}\n\nvoid aeron_clock_update_cached_epoch_time(aeron_clock_cache_t *cached_clock, int64_t epoch_time)\n{\n    AERON_SET_RELEASE(cached_clock->cached_epoch_time, epoch_time);\n}\n\nvoid aeron_clock_update_cached_nano_time(aeron_clock_cache_t *cached_clock, int64_t nano_time)\n{\n    AERON_SET_RELEASE(cached_clock->cached_nano_time, nano_time);\n}\n\nint64_t aeron_clock_cached_epoch_time(aeron_clock_cache_t *cached_clock)\n{\n    int64_t epoch_time;\n    AERON_GET_ACQUIRE(epoch_time, cached_clock->cached_epoch_time);\n    return epoch_time;\n}\n\nint64_t aeron_clock_cached_nano_time(aeron_clock_cache_t *cached_clock)\n{\n    int64_t nano_time;\n    AERON_GET_ACQUIRE(nano_time, cached_clock->cached_nano_time);\n    return nano_time;\n}\n\nint aeron_clock_cache_alloc(aeron_clock_cache_t **cached_clock)\n{\n    return aeron_alloc((void **)cached_clock, sizeof(aeron_clock_cache_t));\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_clock.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_CLOCK_H\n#define AERON_AERON_CLOCK_H\n\n#include <time.h>\n#include \"util/aeron_bitutil.h\"\n\ntypedef struct aeron_clock_cache_stct\n{\n    uint8_t pre_pad[AERON_CACHE_LINE_LENGTH - sizeof(int64_t)];\n    volatile int64_t cached_epoch_time;\n    volatile int64_t cached_nano_time;\n    uint8_t post_pad[AERON_CACHE_LINE_LENGTH - sizeof(int64_t)];\n}\naeron_clock_cache_t;\n\n/**\n * Update the cached clock with the current epoch and nano time values.\n *\n * @param cached_clock 'this'\n * @param epoch_time current ms since epoch.\n * @param nano_time current ns time.\n */\nvoid aeron_clock_update_cached_time(aeron_clock_cache_t *cached_clock, int64_t epoch_time, int64_t nano_time);\n\n/**\n * Update the cached clock with the current epoch time value.\n *\n * @param cached_clock 'this'\n * @param epoch_time current ms since epoch.\n */\nvoid aeron_clock_update_cached_epoch_time(aeron_clock_cache_t *cached_clock, int64_t epoch_time);\n\n/**\n * Update the cached clock with the current nano time value.\n *\n * @param cached_clock 'this'\n * @param nano_time current ns time.\n */\nvoid aeron_clock_update_cached_nano_time(aeron_clock_cache_t *cached_clock, int64_t nano_time);\n\n/**\n * Retrieves the cached epoch time from supplied cached clock.\n *\n * @param cached_clock 'this'\n * @return The current cached value for the epoch time.\n */\nint64_t aeron_clock_cached_epoch_time(aeron_clock_cache_t *cached_clock);\n\n/**\n * Retrieves the cached nano time from supplied cached clock.\n *\n * @param cached_clock 'this'\n * @return The current cached value for the nano time.\n */\nint64_t aeron_clock_cached_nano_time(aeron_clock_cache_t *cached_clock);\n\n/**\n * Allocate a cached clock.\n *\n * @param cached_clock Pointer to the pointer to be initialised with the new cached clock\n * @return -1 if allocation fails, e.g. out of memory.\n */\nint aeron_clock_cache_alloc(aeron_clock_cache_t **cached_clock);\n\n/**\n * Get the realtime from the system in timespec format\n *\n * @param time value to fill with the current time\n * @return 0 on success, -1 on failure.\n */\nint aeron_clock_gettime_realtime(struct timespec *time);\n\n#endif //AERON_AERON_CLOCK_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_deque.c",
    "content": "//\n// Created by mike on 23/01/23.\n//\n\n#include <errno.h>\n#include <inttypes.h>\n\n#include \"aeron_deque.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_error.h\"\n\n\nstatic inline size_t aeron_deque_size(aeron_deque_t *deque)\n{\n    return deque->last_element >= deque->first_element ? deque->last_element - deque->first_element :\n        deque->element_count - (deque->first_element - deque->last_element);\n}\n\nstatic inline bool aeron_deque_has_capacity(aeron_deque_t *deque)\n{\n    return aeron_deque_size(deque) < (deque->element_count - 1);\n}\n\nint aeron_deque_init(aeron_deque_t *deque, size_t initial_element_count, size_t element_size)\n{\n    size_t initial_size = initial_element_count * element_size;\n    if (aeron_alloc((void **)&deque->data, initial_size) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    deque->element_count = initial_element_count;\n    deque->element_size = element_size;\n    deque->first_element = 0;\n    deque->last_element = 0;\n\n    return 0;\n}\n\nvoid aeron_deque_close(aeron_deque_t *deque)\n{\n    aeron_free(deque->data);\n}\n\nstatic int aeron_deque_reallocate(aeron_deque_t *deque)\n{\n    if ((SIZE_MAX >> 1) <= deque->element_count)\n    {\n        AERON_SET_ERR(ENOMEM, \"increased size will exceed %\" PRIu64, SIZE_MAX);\n        return -1;\n    }\n\n    if (deque->first_element == deque->last_element)\n    {\n        return 0;\n    }\n\n    size_t new_element_count = deque->element_count << 1;\n    size_t new_size = new_element_count * deque->element_size;\n    void *new_data = NULL;\n\n    if (aeron_alloc(&new_data, new_size) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    size_t size = aeron_deque_size(deque);\n    if (deque->first_element < deque->last_element)\n    {\n        size_t first_offset = deque->first_element * deque->element_size;\n        uint8_t *first_ptr = deque->data + first_offset;\n        size_t mem_size = size * deque->element_size;\n\n        memcpy(new_data, first_ptr, mem_size);\n    }\n    else if (deque->first_element > deque->last_element)\n    {\n        size_t first_offset = deque->first_element * deque->element_size;\n        uint8_t *first_ptr = deque->data + first_offset;\n        size_t num_bytes_first_to_end_of_buffer = (deque->element_count - deque->first_element) * deque->element_size;\n        size_t num_bytes_begin_of_buffer_to_last = deque->last_element * deque->element_size;\n        uint8_t *dest_last_ptr = (uint8_t *)new_data + num_bytes_first_to_end_of_buffer;\n\n        memcpy(new_data, (void *)first_ptr, num_bytes_first_to_end_of_buffer);\n        memcpy((void *)dest_last_ptr, deque->data, num_bytes_begin_of_buffer_to_last);\n    }\n\n    void *old_data = (void *)deque->data;\n    deque->data = new_data;\n    deque->first_element = 0;\n    deque->last_element = size;\n    deque->element_count = new_element_count;\n\n    aeron_free(old_data);\n\n    return 0;\n}\n\nint aeron_deque_add_last(aeron_deque_t *deque, void *value)\n{\n    if (!aeron_deque_has_capacity(deque))\n    {\n        if (aeron_deque_reallocate(deque) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n    }\n\n    size_t last_offset = deque->last_element * deque->element_size;\n    uint8_t *last_ptr = deque->data + last_offset;\n    memcpy(last_ptr, (uint8_t *)value, deque->element_size);\n    ++deque->last_element;\n    if (deque->last_element == deque->element_count)\n    {\n        deque->last_element = 0;\n    }\n\n    return 1;\n}\n\nint aeron_deque_remove_first(aeron_deque_t *deque, void *value)\n{\n    if (aeron_deque_size(deque) == 0)\n    {\n        return 0;\n    }\n    size_t first_offset = deque->first_element * deque->element_size;\n    uint8_t *first_ptr = deque->data + first_offset;\n    memcpy(value, (void *)first_ptr, deque->element_size);\n    ++deque->first_element;\n    if (deque->first_element == deque->element_count)\n    {\n        deque->first_element = 0;\n    }\n\n    return 1;\n}\n\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_deque.h",
    "content": "//\n// Created by mike on 23/01/23.\n//\n\n#ifndef AERON_DEQUE_H\n#define AERON_DEQUE_H\n\n#include <stdint.h>\n#include <string.h>\n\nstruct aeron_deque_stct\n{\n    uint8_t *data;\n    size_t element_count;\n    size_t element_size;\n    size_t first_element;\n    size_t last_element;\n};\ntypedef struct aeron_deque_stct aeron_deque_t;\n\nint aeron_deque_init(aeron_deque_t *deque, size_t initial_element_count, size_t element_size);\n\nvoid aeron_deque_close(aeron_deque_t *deque);\n\n/**\n * Add value into the deque as the last element.  Will memcpy into the deque from the void pointer provided using\n * the specified element size.  May need to allocate in order to increase the size of the dequeue.\n *\n * Errors:\n *   ENOMEM if growing the array fails.\n *\n * @param deque to add the value too.\n * @param value value to be added.\n * @return 0 on success, -1 on failure.\n */\nint aeron_deque_add_last(aeron_deque_t *deque, void *value);\n\nint aeron_deque_remove_first(aeron_deque_t *deque, void *value);\n\n#endif //AERON_DEQUE_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_dlopen.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdio.h>\n#include <string.h>\n#include <errno.h>\n#include <inttypes.h>\n\n#include \"util/aeron_dlopen.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_alloc.h\"\n\n#if defined(AERON_COMPILER_GCC)\n\nconst char *aeron_dlinfo(const void *addr, char *buffer, size_t max_buffer_length)\n{\n    buffer[0] = '\\0';\n    Dl_info info;\n\n    if (dladdr(addr, &info) <= 0)\n    {\n        return buffer;\n    }\n\n    snprintf(buffer, max_buffer_length - 1, \"(%s:%s)\", info.dli_fname, info.dli_sname);\n    return buffer;\n}\n\nconst char *aeron_dlinfo_func(void (*func)(void), char *buffer, size_t max_buffer_length)\n{\n#if defined(AERON_COMPILER_GCC)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wpedantic\"\n#endif\n    void *addr = (void *)func;\n#if defined(AERON_COMPILER_GCC)\n#pragma GCC diagnostic pop\n#endif\n    return aeron_dlinfo(addr, buffer, max_buffer_length);\n}\n\n#elif defined(AERON_COMPILER_MSVC)\n\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"aeronc.h\"\n#include <Windows.h>\n\nvoid *aeron_dlsym_fallback(LPCSTR name)\n{\n    return NULL;\n}\n\nstatic HMODULE *modules = NULL;\nstatic size_t modules_size = 0;\nstatic size_t modules_capacity = 10;\n\nHMODULE GetCurrentModule()\n{\n    HMODULE hModule = NULL;\n    if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)GetCurrentModule, &hModule))\n    {\n        return hModule;\n    }\n\n    return NULL;\n}\n\nvoid aeron_init_dlopen_support()\n{\n    if (NULL == modules)\n    {\n        modules = (HMODULE*)malloc(sizeof(HMODULE) * modules_capacity);\n        memset(modules, 0, sizeof(HMODULE) * modules_capacity);\n        modules[0] = GetCurrentModule();\n        modules_size = modules[0] != NULL;\n    }\n}\n\nvoid *aeron_dlsym(void *module, const char *name)\n{\n    aeron_init_dlopen_support();\n\n    if (RTLD_DEFAULT == module)\n    {\n        for (size_t i = 1; i <= modules_size; i++)\n        {\n            void *res = aeron_dlsym(modules[modules_size - i], name);\n            if (NULL != res)\n            {\n                return res;\n            }\n        }\n\n        return aeron_dlsym_fallback(name);\n    }\n\n    if (RTLD_NEXT == module)\n    {\n        BOOL firstFound = FALSE;\n        for (size_t i = 1; i <= modules_size; i++)\n        {\n            void *res = aeron_dlsym(modules[modules_size - i], name);\n            if (NULL != res && firstFound)\n            {\n                return res;\n            }\n\n            if (NULL != res && !firstFound)\n            {\n                firstFound = TRUE;\n            }\n        }\n\n        return aeron_dlsym_fallback(name);\n    }\n\n    return GetProcAddress((HMODULE)module, name);\n}\n\nvoid *aeron_dlopen(const char *filename)\n{\n    aeron_init_dlopen_support();\n\n    HMODULE module = LoadLibraryA(filename);\n\n    if (modules_size == modules_capacity)\n    {\n        modules_capacity = modules_capacity * 2;\n        modules = (HMODULE*)realloc(modules, sizeof(HMODULE) * modules_capacity);\n    }\n\n    modules[modules_size++] = module;\n\n    return module;\n}\n\nchar *aeron_dlerror()\n{\n    DWORD errorMessageID = GetLastError();\n    LPSTR messageBuffer = NULL;\n    FormatMessageA(\n        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,\n        NULL,\n        errorMessageID,\n        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n        (LPSTR)&messageBuffer,\n        0,\n        NULL);\n\n    // Leak\n\n    return messageBuffer;\n}\n\nconst char *aeron_dlinfo_func(void (*func)(void), char *buffer, size_t max_buffer_length)\n{\n    buffer[0] = '\\0';\n    return buffer;\n}\n\nconst char *aeron_dlinfo(const void *addr, char *buffer, size_t max_buffer_length)\n{\n    buffer[0] = '\\0';\n    return buffer;\n}\n\n#else\n#error Unsupported platform!\n#endif\n\n#define AERON_MAX_DL_LIBS_LEN (4094)\n#define AERON_MAX_DL_LIB_NAMES (10)\n\nint aeron_dl_load_libs(aeron_dl_loaded_libs_state_t **state, const char *libs)\n{\n    char libs_dup[AERON_MAX_DL_LIBS_LEN] = { 0 };\n    char *lib_names[AERON_MAX_DL_LIB_NAMES] = { 0 };\n    aeron_dl_loaded_libs_state_t *_state;\n    const size_t libs_length = strlen(libs);\n\n    *state = NULL;\n\n    if (NULL == aeron_dlsym(RTLD_DEFAULT, \"aeron_driver_context_init\"))\n    {\n        AERON_SET_ERR(EPERM, \"%s\", \"dynamic loading of libraries not supported with a statically link driver\");\n        return -1;\n    }\n\n    if (libs_length >= (size_t)AERON_MAX_DL_LIBS_LEN)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"dl libs list too long, must have: %\" PRIu64 \" < %d\",\n            (uint64_t)libs_length, AERON_MAX_DL_LIBS_LEN);\n        return -1;\n    }\n\n    strncpy(libs_dup, libs, AERON_MAX_DL_LIBS_LEN - 1);\n\n    const int num_libs = aeron_tokenise(libs_dup, ',', AERON_MAX_DL_LIB_NAMES, lib_names);\n\n    if (-ERANGE == num_libs)\n    {\n        AERON_SET_ERR(EINVAL, \"Too many dl libs defined, limit %d: %s\", AERON_MAX_DL_LIB_NAMES, libs);\n        return -1;\n    }\n    else if (num_libs < 0)\n    {\n        AERON_SET_ERR(EINVAL, \"Failed to parse dl libs: %s\", libs != NULL ? libs : \"(null)\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&_state, sizeof(aeron_dl_loaded_libs_state_t)) < 0 ||\n        aeron_alloc((void **)&_state->libs, sizeof(aeron_dl_loaded_lib_state_t) * num_libs) < 0)\n    {\n        AERON_APPEND_ERR(\"could not allocate dl_loaded_libs, num_libs: %d\", num_libs);\n        return -1;\n    }\n    _state->num_libs = (size_t)num_libs;\n\n    for (int i = 0; i < num_libs; i++)\n    {\n        const char *lib_name = lib_names[i];\n        aeron_dl_loaded_lib_state_t *lib = &_state->libs[i];\n\n        if (NULL == (lib->handle = aeron_dlopen(lib_name)))\n        {\n            AERON_SET_ERR(EINVAL, \"failed to load dl_lib %s: %s\", lib_name, aeron_dlerror());\n            return -1;\n        }\n    }\n\n    *state = _state;\n    return 0;\n}\n\nint aeron_dl_load_libs_delete(aeron_dl_loaded_libs_state_t *state)\n{\n    if (NULL != state)\n    {\n        for (size_t i = 0; i < state->num_libs; i++)\n        {\n            aeron_dl_loaded_lib_state_t *lib = &state->libs[i];\n\n#if defined(AERON_COMPILER_GCC)\n            dlclose(lib->handle);\n#elif defined(AERON_COMPILER_MSVC) && defined(AERON_CPU_X64)\n            FreeLibrary(lib->handle);\n#else\n#error Unsupported platform!\n#endif\n        }\n\n        aeron_free(state->libs);\n        aeron_free(state);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_dlopen.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DLOPEN_H\n#define AERON_DLOPEN_H\n\n#include <util/aeron_platform.h>\n\n#if defined(AERON_COMPILER_GCC)\n\n#include <dlfcn.h>\n#include <stddef.h>\n\n#define aeron_dlsym dlsym\n#define aeron_dlopen(x) dlopen(x, RTLD_LAZY | RTLD_GLOBAL)\n#define aeron_dlerror dlerror\n\nconst char *aeron_dlinfo(const void *, char *buffer, size_t max_buffer_length);\nconst char *aeron_dlinfo_func(void (*func)(void), char *buffer, size_t max_buffer_length);\n\n#elif defined(AERON_COMPILER_MSVC)\n\n#define RTLD_DEFAULT ((void *)-123)\n#define RTLD_NEXT ((void *)-124)\n\nvoid *aeron_dlsym(void *module, const char *name);\nvoid *aeron_dlopen(const char *filename);\nchar *aeron_dlerror();\nconst char *aeron_dlinfo(const void *addr, char *buffer, size_t max_buffer_length);\nconst char *aeron_dlinfo_func(void (*func)(void), char *buffer, size_t max_buffer_length);\n\n#else\n#error Unsupported platform!\n#endif\n\ntypedef struct aeron_dl_loaded_lib_state_stct\n{\n    void *handle;\n}\naeron_dl_loaded_lib_state_t;\n\ntypedef struct aeron_dl_loaded_libs_state_stct\n{\n    aeron_dl_loaded_lib_state_t *libs;\n    size_t num_libs;\n}\naeron_dl_loaded_libs_state_t;\n\nint aeron_dl_load_libs(aeron_dl_loaded_libs_state_t **state, const char *libs);\nint aeron_dl_load_libs_delete(aeron_dl_loaded_libs_state_t *state);\n\n#endif //AERON_DLOPEN_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_env.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#endif\n\n#include <stdlib.h>\n\n#include \"util/aeron_env.h\"\n\nint aeron_env_set(const char *key, const char *val)\n{\n#if defined(WIN32)\n    return _putenv_s(key, val);\n#else\n    return setenv(key, val, 1);\n#endif\n}\n\nint aeron_env_unset(const char *key)\n{\n#if defined(WIN32)\n    return _putenv_s(key, \"\");\n#else\n    return unsetenv(key);\n#endif\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_env.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_ENV_H\n#define AERON_AERON_ENV_H\n\nint aeron_env_set(const char *key, const char *val);\n\nint aeron_env_unset(const char *key);\n\n#endif //AERON_AERON_ENV_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_error.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _POSIX_C_SOURCE 200112L\n#endif\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdarg.h>\n#include <memory.h>\n#include <errno.h>\n\n#include \"concurrent/aeron_thread.h\"\n#include \"util/aeron_error.h\"\n#include \"command/aeron_control_protocol.h\"\n\n#define AERON_ERR_TRAILER \"...\\n\"\n#define AERON_ERR_DESCRIPTION_UNAVAILABLE \"<Unable to get error description>\";\n\n#if defined(AERON_COMPILER_GCC)\n\nconst char *aeron_strerror_r(int errcode, char *buffer, size_t length)\n{\n    int result = strerror_r(errcode, buffer, length);\n    if (result != 0)\n    {\n        return AERON_ERR_DESCRIPTION_UNAVAILABLE;\n    }\n\n    return buffer;\n}\n\n#elif defined(AERON_COMPILER_MSVC)\n#include <windows.h>\n\nconst char *aeron_strerror_r(int errcode, char *buffer, size_t length)\n{\n    errno_t result = strerror_s(buffer, length, errcode);\n\n    if (0 != result)\n    {\n        return AERON_ERR_DESCRIPTION_UNAVAILABLE;\n    }\n    else\n    {\n        for (int i = (int)result; i > -1; i--)\n        {\n            if ('\\0' == buffer[i] || isspace(buffer[i]))\n            {\n                buffer[i] = '\\0';\n            }\n            else\n            {\n                break;\n            }\n        }\n    }\n\n    return buffer;\n}\n\n#else\n#error Unsupported platform!\n#endif\n\nstatic AERON_INIT_ONCE error_is_initialized = AERON_INIT_ONCE_VALUE;\n\n#if defined(AERON_COMPILER_MSVC)\nstatic pthread_key_t error_key = TLS_OUT_OF_INDEXES;\n#else\nstatic pthread_key_t error_key;\n#endif\n\nstatic void initialize_per_thread_error(void)\n{\n    if (aeron_thread_key_create(&error_key, free))\n    {\n        fprintf(stderr, \"could not create per thread error key, exiting.\\n\");\n        exit(EXIT_FAILURE);\n    }\n}\n\nstatic void initialize_error(void)\n{\n#if defined(AERON_COMPILER_MSVC)\n    if (error_key != TLS_OUT_OF_INDEXES)\n    {\n        return;\n    }\n#endif\n\n    aeron_thread_once(&error_is_initialized, initialize_per_thread_error);\n}\n\nint aeron_errcode(void)\n{\n    initialize_error();\n\n    aeron_per_thread_error_t *error_state = aeron_thread_get_specific(error_key);\n    int result = 0;\n\n    if (NULL != error_state)\n    {\n        result = error_state->errcode;\n    }\n\n    return result;\n}\n\nconst char *aeron_errmsg(void)\n{\n    initialize_error();\n    aeron_per_thread_error_t *error_state = aeron_thread_get_specific(error_key);\n\n    if (NULL != error_state)\n    {\n        return error_state->errmsg;\n    }\n    else\n    {\n        return \"no error\";\n    }\n}\n\nstatic aeron_per_thread_error_t *get_required_error_state(void)\n{\n    initialize_error();\n\n    aeron_per_thread_error_t *error_state = aeron_thread_get_specific(error_key);\n\n    if (NULL == error_state)\n    {\n        error_state = malloc(sizeof(aeron_per_thread_error_t));\n        if (NULL == error_state)\n        {\n            fprintf(stderr, \"could not create per thread error state, exiting.\\n\");\n            exit(EXIT_FAILURE);\n        }\n\n        if (aeron_thread_set_specific(error_key, error_state))\n        {\n            fprintf(stderr, \"could not associate per thread error key, exiting.\\n\");\n            exit(EXIT_FAILURE);\n        }\n    }\n\n    return error_state;\n}\n\nvoid aeron_set_errno(int errcode)\n{\n    errno = errcode;\n#if defined(AERON_COMPILER_MSVC)\n    switch (errcode)\n    {\n        case 0:\n            SetLastError(ERROR_SUCCESS);\n            break;\n\n        case EINVAL:\n            SetLastError(ERROR_BAD_ARGUMENTS);\n            break;\n\n        case ENOMEM:\n            SetLastError(ERROR_OUTOFMEMORY);\n            break;\n\n        default:\n            break;\n    }\n#endif\n}\n\nconst char *aeron_error_code_str(int errcode)\n{\n    switch (errcode)\n    {\n        case AERON_ERROR_CODE_UNUSED:\n        case AERON_ERROR_CODE_GENERIC_ERROR:\n            return \"generic error, see message\";\n\n        case AERON_ERROR_CODE_INVALID_CHANNEL:\n            return \"invalid channel\";\n\n        case AERON_ERROR_CODE_UNKNOWN_SUBSCRIPTION:\n            return \"unknown subscription\";\n\n        case AERON_ERROR_CODE_UNKNOWN_PUBLICATION:\n            return \"unknown publication\";\n\n        case AERON_ERROR_CODE_CHANNEL_ENDPOINT_ERROR:\n            return \"channel endpoint error\";\n\n        case AERON_ERROR_CODE_UNKNOWN_COUNTER:\n            return \"unknown counter\";\n\n        case AERON_ERROR_CODE_UNKNOWN_COMMAND_TYPE_ID:\n            return \"unknown command type id\";\n\n        case AERON_ERROR_CODE_MALFORMED_COMMAND:\n            return \"malformed command\";\n\n        case AERON_ERROR_CODE_NOT_SUPPORTED:\n            return \"not supported\";\n\n        case AERON_ERROR_CODE_UNKNOWN_HOST:\n            return \"unknown host\";\n\n        case AERON_ERROR_CODE_RESOURCE_TEMPORARILY_UNAVAILABLE:\n            return \"resource temporarily unavailable\";\n\n        case AERON_ERROR_CODE_STORAGE_SPACE:\n            return \"insufficient storage space\";\n\n        case AERON_ERROR_CODE_IMAGE_REJECTED:\n            return \"image rejected\";\n\n        case AERON_ERROR_CODE_PUBLICATION_REVOKED:\n            return \"publication revoked\";\n\n        default:\n            return \"unknown error code\";\n    }\n}\n\nstatic void aeron_err_vprintf(\n    aeron_per_thread_error_t *error_state,\n    const char *format,\n    va_list args)\n{\n    if (error_state->offset >= sizeof(error_state->errmsg))\n    {\n        return;\n    }\n\n    int result = vsnprintf(\n        &error_state->errmsg[(int)error_state->offset], sizeof(error_state->errmsg) - error_state->offset, format, args);\n\n    if (result < 0)\n    {\n        fprintf(stderr, \"Failed to update err_msg: %d\\n\", result);\n    }\n\n    error_state->offset += result;\n}\n\nstatic void aeron_err_printf(aeron_per_thread_error_t *error_state, const char *format, ...)\n{\n    va_list args;\n    va_start(args, format);\n    aeron_err_vprintf(error_state, format, args);\n    va_end(args);\n}\n\nstatic void aeron_err_update_entry(\n    aeron_per_thread_error_t *error_state,\n    const char *function,\n    const char *filename,\n    int line_number,\n    const char *format,\n    va_list args)\n{\n    aeron_err_printf(error_state, \"[%s, %s:%d] \", function, filename, line_number);\n    aeron_err_vprintf(error_state, format, args);\n    aeron_err_printf(error_state, \"%s\", \"\\n\");\n    strcpy(error_state->errmsg + (sizeof(error_state->errmsg) - (strlen(AERON_ERR_TRAILER) + 2)), AERON_ERR_TRAILER);\n}\n\nvoid aeron_err_set(int errcode, const char *function, const char *filename, int line_number, const char *format, ...)\n{\n    aeron_per_thread_error_t *error_state = get_required_error_state();\n\n    error_state->errcode = errcode;\n    aeron_set_errno(errcode);\n    error_state->offset = 0;\n\n    char err_buf[1024] = { 0 };\n    const char *system_err_message;\n    const int error_code = aeron_errcode();\n    if (error_code <= 0)\n    {\n        system_err_message = aeron_error_code_str(-error_code);\n    }\n    else\n    {\n        int length = 1024;\n        system_err_message = aeron_strerror_r(error_code, &err_buf[0], length);\n    }\n\n    const char *err_str = system_err_message;\n    aeron_err_printf(error_state, \"(%d) %s\\n\", error_code, err_str);\n    va_list args;\n    va_start(args, format);\n    aeron_err_update_entry(error_state, function, filename, line_number, format, args);\n    va_end(args);\n}\n\nvoid aeron_err_append(const char *function, const char *filename, int line_number, const char *format, ...)\n{\n    aeron_per_thread_error_t *error_state = get_required_error_state();\n    va_list args;\n    va_start(args, format);\n    aeron_err_update_entry(error_state, function, filename, line_number, format, args);\n    va_end(args);\n}\n\nvoid aeron_err_clear(void)\n{\n    aeron_per_thread_error_t *error_state = get_required_error_state();\n\n    aeron_set_errno(0);\n    error_state->errcode = 0;\n    error_state->offset = 0;\n    strcpy(error_state->errmsg, \"no error\");\n}\n\n#if defined(AERON_COMPILER_MSVC)\n\nbool aeron_error_dll_process_attach()\n{\n    if (error_key != TLS_OUT_OF_INDEXES)\n    {\n        return false;\n    }\n\n    error_key = TlsAlloc();\n\n    return error_key != TLS_OUT_OF_INDEXES;\n}\n\nvoid aeron_error_dll_thread_detach()\n{\n    if (error_key == TLS_OUT_OF_INDEXES)\n    {\n        return;\n    }\n\n    aeron_per_thread_error_t *error_state = aeron_thread_get_specific(error_key);\n\n    if (NULL != error_state)\n    {\n        aeron_free(error_state);\n        aeron_thread_set_specific(error_key, NULL);\n    }\n}\n\nvoid aeron_error_dll_process_detach()\n{\n    if (error_key == TLS_OUT_OF_INDEXES)\n    {\n        return;\n    }\n\n    aeron_error_dll_thread_detach();\n\n    aeron_thread_key_delete(error_key);\n    error_key = TLS_OUT_OF_INDEXES;\n}\n\nvoid aeron_err_set_windows(int errcode, const char *function, const char *filename, int line_number, const char *format, ...)\n{\n    aeron_per_thread_error_t *error_state = get_required_error_state();\n\n    error_state->errcode = errcode;\n    error_state->offset = 0;\n\n    char err_buf[1024] = { 0 };\n    const char *system_err_message;\n    const int error_code = aeron_errcode();\n\n    if (error_code <= 0)\n    {\n        system_err_message = aeron_error_code_str(-error_code);\n    }\n    else\n    {\n        DWORD num_chars = FormatMessage(\n            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,\n            NULL,\n            error_code,\n            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n            (LPTSTR)err_buf,\n            1024,\n            NULL);\n\n        if (0 == num_chars)\n        {\n            system_err_message = \"<error text unavailable>\";\n        }\n        else\n        {\n            for (int i = (int)num_chars; i > -1; i--)\n            {\n                if ('\\0' == err_buf[i] || isspace(err_buf[i]))\n                {\n                    err_buf[i] = '\\0';\n                }\n                else\n                {\n                    break;\n                }\n            }\n\n            system_err_message = err_buf;\n        }\n    }\n\n    aeron_err_printf(error_state, \"(%d) %s\\n\", error_code, system_err_message);\n    va_list args;\n    va_start(args, format);\n    aeron_err_update_entry(error_state, function, filename, line_number, format, args);\n    va_end(args);\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_error.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_ERROR_H\n#define AERON_ERROR_H\n\n#include <string.h>\n#include <stdbool.h>\n\n#include \"aeron_common.h\"\n#include \"util/aeron_platform.h\"\n\n#define AERON_ERROR_MAX_TOTAL_LENGTH (8192)\n\ntypedef struct aeron_per_thread_error_stct\n{\n    int errcode;\n    size_t offset;\n    char errmsg[AERON_ERROR_MAX_TOTAL_LENGTH];\n}\naeron_per_thread_error_t;\n\nint aeron_errcode(void);\nconst char *aeron_errmsg(void);\nvoid aeron_set_errno(int errcode);\nconst char *aeron_error_code_str(int errcode);\nvoid aeron_err_set(int errcode, const char *function, const char *filename, int line_number, const char *format, ...);\nvoid aeron_err_append(const char *function, const char *filename, int line_number, const char *format, ...);\nvoid aeron_err_clear(void);\n\n#if defined(AERON_COMPILER_MSVC)\nbool aeron_error_dll_process_attach();\nvoid aeron_error_dll_thread_detach();\nvoid aeron_error_dll_process_detach();\nvoid aeron_err_set_windows(\n    int errcode, const char *function, const char *filename, int line_number, const char *format, ...);\n#define __FILENAME__ (strrchr(__FILE__, '\\\\') ? strrchr(__FILE__, '\\\\') + 1 : __FILE__)\n#define AERON_SET_ERR_WIN(errcode, fmt, ...) aeron_err_set_windows(errcode, __func__, __FILENAME__, __LINE__, fmt, __VA_ARGS__)\n#else\n#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)\n#endif\n\n#define AERON_SET_ERR(errcode, fmt, ...) aeron_err_set(errcode, __func__, __FILENAME__, __LINE__, fmt, __VA_ARGS__)\n#define AERON_APPEND_ERR(fmt, ...) aeron_err_append(__func__, __FILENAME__, __LINE__, fmt, __VA_ARGS__)\n#define AERON_NULL_STR(v) NULL == v ? \"NULL\" : \"OK\"\n\n#endif //AERON_ERROR_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_fileutil.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#if defined(__linux__) || defined(AERON_COMPILER_MSVC)\n#define AERON_NATIVE_PRETOUCH\n#endif\n\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <string.h>\n#include <stdlib.h>\n#include <errno.h>\n\n#include \"aeron_platform.h\"\n#include \"aeron_error.h\"\n#include \"aeron_fileutil.h\"\n\n#ifdef _MSC_VER\n#define AERON_FILE_SEP '\\\\'\n#else\n#define AERON_FILE_SEP '/'\n#endif\n\n#ifdef _MSC_VER\n#define AERON_FILE_SEP_STR \"\\\\\"\n#else\n#define AERON_FILE_SEP_STR \"/\"\n#endif\n\n#if defined(AERON_COMPILER_MSVC)\n\n#include <windows.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <io.h>\n#include <direct.h>\n\n#define PROT_READ  1\n#define PROT_WRITE 2\n#define MAP_FAILED ((void *)-1)\n\n#define MAP_SHARED 0x01\n#define S_IRUSR _S_IREAD\n#define S_IWUSR _S_IWRITE\n#define S_IRGRP 0\n#define S_IWGRP 0\n#define S_IROTH 0\n#define S_IWOTH 0\n\nstatic int aeron_mmap(aeron_mapped_file_t *mapping, int fd, bool pre_touch)\n{\n    HANDLE hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), 0, PAGE_READWRITE, 0, 0, 0);\n\n    if (!hmap)\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"%s\", \"CreateFileMapping failed\");\n        return -1;\n    }\n\n    mapping->addr = MapViewOfFileEx(hmap, FILE_MAP_WRITE, 0, 0, mapping->length, NULL);\n\n    if (!mapping->addr)\n    {\n        mapping->addr = MAP_FAILED;\n        AERON_SET_ERR_WIN(GetLastError(), \"%s\", \"MapViewOfFileEx failed\");\n\n        if (!CloseHandle(hmap))\n        {\n            AERON_APPEND_ERR(\"(%d) Failed to close file mapping handle\", GetLastError());\n        }\n        _close(fd);\n        return -1;\n    }\n\n    if (!CloseHandle(hmap))\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"%s\", \"Failed to close file mapping handle\");\n        if (0 != aeron_unmap(mapping))\n        {\n            AERON_APPEND_ERR(\"(%d) Failed to unmap\", GetLastError());\n        }\n        _close(fd);\n        return -1;\n    }\n\n    _close(fd);\n\n    if (pre_touch && MAP_FAILED != mapping->addr)\n    {\n        WIN32_MEMORY_RANGE_ENTRY entry;\n        entry.NumberOfBytes = mapping->length;\n        entry.VirtualAddress = mapping->addr;\n\n        if (!PrefetchVirtualMemory(GetCurrentProcess(), 1, &entry, 0))\n        {\n            AERON_SET_ERR_WIN(GetLastError(), \"%s\", \"PrefetchVirtualMemory failed\");\n            if (0 != aeron_unmap(mapping))\n            {\n                AERON_APPEND_ERR(\"(%d) Failed to unmap\", GetLastError());\n            }\n            mapping->addr = MAP_FAILED;\n        }\n    }\n\n    return MAP_FAILED == mapping->addr ? -1 : 0;\n}\n\nstatic int aeron_delete_path(const char *path, FILEOP_FLAGS flags)\n{\n    char buffer[(AERON_MAX_PATH * 2) + 2] = {0 };\n\n    size_t path_length = strlen(path);\n    if (path_length > (AERON_MAX_PATH * 2))\n    {\n        AERON_SET_ERR_WIN(EINVAL, \"Path is too long: %s\", path);\n        return -1;\n    }\n\n    memcpy(buffer, path, path_length);\n    buffer[path_length] = '\\0';\n    buffer[path_length + 1] = '\\0';\n\n    SHFILEOPSTRUCT file_op =\n            {\n                    NULL,\n                    FO_DELETE,\n                    buffer,\n                    NULL,\n                    flags,\n                    false,\n                    NULL,\n                    NULL\n            };\n\n    int result = SHFileOperation(&file_op);\n    if (0 == result)\n    {\n        if (file_op.fAnyOperationsAborted)\n        {\n            AERON_SET_ERR_WIN(EINVAL, \"Delete was aborted: %s\", path);\n            return -1;\n        }\n\n        return 0;\n    }\n\n    AERON_SET_ERR_WIN(GetLastError(), \"Delete failed: %s\", path);\n    return -1;\n}\n\nint aeron_unmap(aeron_mapped_file_t *mapped_file)\n{\n    if (NULL != mapped_file->addr)\n    {\n        return UnmapViewOfFile(mapped_file->addr) ? 0 : -1;\n    }\n\n    return 0;\n}\n\nint aeron_msync(void *addr, size_t length)\n{\n    if (NULL != addr && 0 == FlushViewOfFile(addr, length))\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"%s\", \"FlushViewOfFile failed\");\n        return -1;\n    }\n    return 0;\n}\n\nint aeron_mkdir(const char *path, int permission)\n{\n    return _mkdir(path);\n}\n\nint64_t aeron_file_length(const char *path)\n{\n    WIN32_FILE_ATTRIBUTE_DATA fad;\n\n    if (GetFileAttributesEx(path, GetFileExInfoStandard, &fad) == 0)\n    {\n        return -1;\n    }\n\n    ULARGE_INTEGER file_size;\n    file_size.LowPart = fad.nFileSizeLow;\n    file_size.HighPart = fad.nFileSizeHigh;\n\n    return (int64_t)file_size.QuadPart;\n}\n\nuint64_t aeron_usable_fs_space(const char *path)\n{\n    ULARGE_INTEGER lpAvailableToCaller, lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes;\n\n    if (!GetDiskFreeSpaceEx(path, &lpAvailableToCaller, &lpTotalNumberOfBytes, &lpTotalNumberOfFreeBytes))\n    {\n        return 0;\n    }\n\n    return (uint64_t)lpAvailableToCaller.QuadPart;\n}\n\nint aeron_create_file(const char *path, size_t length, bool sparse_file)\n{\n    HANDLE hfile = CreateFile(\n            path,\n            FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE,\n            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n            NULL,\n            CREATE_NEW,\n            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_POSIX_SEMANTICS,\n            NULL);\n\n    if (INVALID_HANDLE_VALUE == hfile)\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"Failed to create file: %s\", path);\n        return -1;\n    }\n\n    if (sparse_file)\n    {\n        DWORD bytesReturned;\n        if (!DeviceIoControl(hfile, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &bytesReturned, NULL))\n        {\n            AERON_SET_ERR_WIN(GetLastError(), \"Failed to mark file as sparse: %s\", path);\n            goto error;\n        }\n    }\n\n    LARGE_INTEGER file_size;\n    file_size.QuadPart = (LONGLONG)length;\n\n    if (!SetFilePointerEx(hfile, file_size, NULL, FILE_BEGIN) ||\n        !SetEndOfFile(hfile))\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"Failed to truncate file: %s\", path);\n        goto error;\n    }\n\n    int fd = _open_osfhandle((intptr_t)hfile, _O_RDWR);\n    if (fd < 0)\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"Failed to obtain file descriptor: %s\", path);\n        goto error;\n    }\n\n    return fd;\n\nerror:\n    CloseHandle(hfile);\n    if (-1 == aeron_delete_file(path))\n    {\n        AERON_APPEND_ERR(\"(%d) Failed to remove file: %s\", GetLastError(), path);\n    }\n    return -1;\n}\n\nint aeron_open_file_rw(const char *path)\n{\n    HANDLE hfile = CreateFile(\n            path,\n            FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE,\n            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n            NULL,\n            OPEN_EXISTING,\n            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_POSIX_SEMANTICS,\n            NULL);\n\n    if (INVALID_HANDLE_VALUE == hfile)\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"Failed to open file: %s\", path);\n        return -1;\n    }\n\n    int fd = _open_osfhandle((intptr_t)hfile, _O_RDWR);\n    if (fd < 0)\n    {\n        AERON_SET_ERR_WIN(GetLastError(), \"Failed to obtain file descriptor: %s\", path);\n        return -1;\n    }\n\n    return fd;\n}\n\nint aeron_delete_directory(const char *dir)\n{\n    return aeron_delete_path(dir, FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT);\n}\n\nint aeron_is_directory(const char *path)\n{\n    const DWORD attributes = GetFileAttributes(path);\n    return INVALID_FILE_ATTRIBUTES != attributes && (attributes & FILE_ATTRIBUTE_DIRECTORY);\n}\n\nint aeron_delete_file(const char *dir)\n{\n    return aeron_delete_path(dir, FOF_NORECURSION | FOF_FILESONLY | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT);\n}\n\n#else\n#include <unistd.h>\n#include <sys/mman.h>\n#include <sys/statvfs.h>\n#include <ftw.h>\n#include <stdio.h>\n#include <pwd.h>\n\n#define aeron_delete_file remove\n\nstatic int aeron_mmap(aeron_mapped_file_t *mapping, int fd, bool pre_touch)\n{\n    int flags = MAP_SHARED;\n\n#ifdef __linux__\n    if (pre_touch)\n    {\n        flags = flags | MAP_POPULATE;\n    }\n#else\n    (void)pre_touch;\n#endif\n\n    mapping->addr = mmap(NULL, mapping->length, PROT_READ | PROT_WRITE, flags, fd, 0);\n\n    if (MAP_FAILED == mapping->addr)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Failed to mmap\");\n        close(fd);\n        return -1;\n    }\n\n    close(fd);\n    return 0;\n}\n\nint aeron_unmap(aeron_mapped_file_t *mapped_file)\n{\n    if (NULL != mapped_file->addr)\n    {\n        return munmap(mapped_file->addr, mapped_file->length);\n    }\n\n    return 0;\n}\n\nint aeron_msync(void *addr, size_t length)\n{\n    if (NULL != addr && 0 != msync(addr, length, MS_SYNC | MS_INVALIDATE))\n    {\n        AERON_SET_ERR(errno, \"%s\", \"msync failed\");\n        return -1;\n    }\n    return 0;\n}\n\nstatic int unlink_func(const char *path, const struct stat *sb, int type_flag, struct FTW *ftw)\n{\n    if (remove(path) != 0)\n    {\n        AERON_SET_ERR(errno, \"could not remove %s\", path);\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_delete_directory(const char *dirname)\n{\n    return nftw(dirname, unlink_func, 64, FTW_DEPTH | FTW_PHYS);\n}\n\nint aeron_is_directory(const char *dirname)\n{\n    struct stat sb;\n    return stat(dirname, &sb) == 0 && S_ISDIR(sb.st_mode);\n}\n\nint64_t aeron_file_length(const char *path)\n{\n    struct stat sb;\n    return stat(path, &sb) == 0 ? sb.st_size : -1;\n}\n\nuint64_t aeron_usable_fs_space(const char *path)\n{\n    struct statvfs vfs;\n    uint64_t result = 0;\n\n    if (statvfs(path, &vfs) == 0)\n    {\n        result = vfs.f_frsize * vfs.f_bavail;\n    }\n\n    return result;\n}\n\nint aeron_create_file(const char *path, size_t length, bool sparse_file)\n{\n    int fd = open(path, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);\n    if (fd < 0)\n    {\n        AERON_SET_ERR(errno, \"Failed to create file: %s\", path);\n        return -1;\n    }\n\n    if (sparse_file)\n    {\n        if (0 != ftruncate(fd, (off_t)length))\n        {\n            AERON_SET_ERR(errno, \"Failed to truncate file: %s\", path);\n            goto error;\n        }\n    }\n    else\n    {\n#if HAVE_FALLOCATE\n        if (0 != fallocate(fd, 0, 0, (off_t)length))\n        {\n            AERON_SET_ERR(errno, \"Failed to allocate file space: %s\", path);\n            goto error;\n        }\n#elif HAVE_POSIX_FALLOCATE\n        if (0 != posix_fallocate(fd, 0, (off_t)length))\n        {\n            AERON_SET_ERR(errno, \"Failed to allocate file space: %s\", path);\n            goto error;\n        }\n#elif HAVE_F_PREALLOCATE\n        fstore_t flags = {\n            F_ALLOCATEALL,\n            F_PEOFPOSMODE,\n            0,\n            (off_t)length,\n            0};\n        if (-1 == fcntl(fd, F_PREALLOCATE, &flags)) // changes physical file size\n        {\n            AERON_SET_ERR(errno, \"Failed to allocate file space: %s\", path);\n            goto error;\n        }\n\n        if (0 != ftruncate(fd, (off_t)length)) // changes logical file size\n        {\n            AERON_SET_ERR(errno, \"Failed to truncate file: %s\", path);\n            goto error;\n        }\n#else\n        if (0 != ftruncate(fd, (off_t)length))\n        {\n            AERON_SET_ERR(errno, \"Failed to truncate file: %s\", path);\n            goto error;\n        }\n#endif\n    }\n\n    return fd;\n\nerror:\n    close(fd);\n    if (-1 == remove(path))\n    {\n        AERON_APPEND_ERR(\"(%d) Failed to remove file\", errno);\n    }\n    return -1;\n}\n\nint aeron_open_file_rw(const char *path)\n{\n    int fd = open(path, O_RDWR);\n    if (-1 == fd)\n    {\n        AERON_SET_ERR(errno, \"Failed to open file: %s\", path);\n        return -1;\n    }\n    return fd;\n}\n#endif\n\nint aeron_mkdir_recursive(const char *pathname, int permission)\n{\n    if (aeron_mkdir(pathname, permission) == 0)\n    {\n        return 0;\n    }\n\n    if (errno != ENOENT)\n    {\n        AERON_SET_ERR(errno, \"aeron_mkdir failed for %s\", pathname);\n        return -1;\n    }\n\n    char *_pathname = strdup(pathname);\n    char *p;\n\n    for (p = _pathname + strlen(_pathname) - 1; p != _pathname; p--)\n    {\n        if (*p == AERON_FILE_SEP)\n        {\n            *p = '\\0';\n\n            // _pathname is now the parent directory of the original pathname\n            int rc = aeron_mkdir_recursive(_pathname, permission);\n\n            free(_pathname);\n\n            if (0 == rc)\n            {\n                // if rc is 0, then we were able to create the parent directory\n                // so retry the original pathname\n                return aeron_mkdir(pathname, permission);\n            }\n            else\n            {\n                AERON_APPEND_ERR(\"pathname=%s\", pathname);\n                return rc;\n            }\n        }\n    }\n\n    free(_pathname);\n\n    AERON_SET_ERR(EINVAL, \"aeron_mkdir_recursive failed to find parent directory in %s\", pathname);\n\n    return -1;\n}\n\n#include <inttypes.h>\n\n#define AERON_BLOCK_SIZE (4 * 1024)\n\n#ifndef AERON_NATIVE_PRETOUCH\nstatic void aeron_touch_pages(volatile uint8_t *base, size_t length, size_t page_size)\n{\n    for (size_t i = 0; i < length; i += page_size)\n    {\n        volatile uint8_t *first_page_byte = base + i;\n        *first_page_byte = 0;\n    }\n}\n#endif\n\nint aeron_map_new_file(aeron_mapped_file_t *mapped_file, const char *path, bool fill_with_zeroes)\n{\n    int fd = aeron_create_file(path, mapped_file->length, !fill_with_zeroes);\n    if (-1 == fd)\n    {\n        return -1;\n    }\n\n    if (0 != aeron_mmap(mapped_file, fd, fill_with_zeroes))\n    {\n        AERON_APPEND_ERR(\"file: %s\", path);\n        if (-1 == remove(path))\n        {\n            AERON_APPEND_ERR(\"Failed to remove file: %s\", path);\n        }\n        return -1;\n    }\n\n#ifndef AERON_NATIVE_PRETOUCH\n    if (fill_with_zeroes)\n    {\n        aeron_touch_pages(mapped_file->addr, mapped_file->length, AERON_BLOCK_SIZE);\n    }\n#endif\n\n    return 0;\n}\n\nint aeron_map_existing_file(aeron_mapped_file_t *mapped_file, const char *path)\n{\n    int fd = aeron_open_file_rw(path);\n    if (fd < 0)\n    {\n        return -1;\n    }\n\n    const int64_t file_length = aeron_file_length(path);\n    if (-1 == file_length)\n    {\n#if !defined(_MSC_VER)\n        AERON_SET_ERR(errno, \"Failed to determine the size of the file: %s\", path);\n        close(fd);\n#else\n        AERON_SET_ERR_WIN(GetLastError(), \"Failed to determine the size of the file: %s\", path);\n        _close(fd);\n#endif\n        return -1;\n    }\n\n    mapped_file->length = (size_t)file_length;\n\n    if (0 != aeron_mmap(mapped_file, fd, false))\n    {\n        AERON_APPEND_ERR(\"file: %s\", path);\n        return -1;\n    }\n\n    return 0;\n}\n\nuint64_t aeron_usable_fs_space_disabled(const char *path)\n{\n    return UINT64_MAX;\n}\n\nint aeron_ipc_publication_location(char *dst, size_t length, const char *aeron_dir, int64_t correlation_id)\n{\n    return snprintf(\n        dst, length,\n        \"%s/\" AERON_PUBLICATIONS_DIR \"/%\" PRId64 \".logbuffer\",\n        aeron_dir, correlation_id);\n}\n\nint aeron_network_publication_location(char *dst, size_t length, const char *aeron_dir, int64_t correlation_id)\n{\n    return snprintf(\n        dst, length,\n        \"%s/\" AERON_PUBLICATIONS_DIR \"/%\" PRId64 \".logbuffer\",\n        aeron_dir, correlation_id);\n}\n\nint aeron_publication_image_location(char *dst, size_t length, const char *aeron_dir, int64_t correlation_id)\n{\n    return snprintf(\n        dst, length,\n        \"%s\" AERON_FILE_SEP_STR AERON_IMAGES_DIR AERON_FILE_SEP_STR \"%\" PRId64 \".logbuffer\",\n        aeron_dir, correlation_id);\n}\n\nsize_t aeron_temp_filename(char *filename, size_t length)\n{\n#if !defined(_MSC_VER)\n    char rawname[] = \"/tmp/aeron-c.XXXXXXX\";\n    int fd = mkstemp(rawname);\n    close(fd);\n    unlink(rawname);\n\n    strncpy(filename, rawname, length);\n\n    return strlen(filename);\n#else\n    char tmpdir[MAX_PATH + 1];\n    char tmpfile[MAX_PATH];\n\n    if (GetTempPath(MAX_PATH, &tmpdir[0]) > 0)\n    {\n        if (GetTempFileName(tmpdir, TEXT(\"aeron-c\"), 101, &tmpfile[0]) != 0)\n        {\n            strncpy(filename, tmpfile, length);\n            return strlen(filename);\n        }\n    }\n\n    return 0;\n#endif\n}\n\nint aeron_raw_log_map(\n    aeron_mapped_raw_log_t *mapped_raw_log,\n    const char *path,\n    bool use_sparse_files,\n    uint64_t term_length,\n    uint64_t page_size)\n{\n    const uint64_t log_length = aeron_logbuffer_compute_log_length(term_length, page_size);\n\n    int fd = aeron_create_file(path, (size_t)log_length, use_sparse_files);\n    if (-1 == fd)\n    {\n        return -1;\n    }\n\n    mapped_raw_log->mapped_file.length = (size_t)log_length;\n    mapped_raw_log->mapped_file.addr = NULL;\n\n    if (0 != aeron_mmap(&mapped_raw_log->mapped_file, fd, !use_sparse_files))\n    {\n        AERON_APPEND_ERR(\"filename: %s\", path);\n        if (-1 == remove(path))\n        {\n            AERON_APPEND_ERR(\"Failed to remove raw log, filename: %s\", path);\n        }\n        return -1;\n    }\n\n#ifndef AERON_NATIVE_PRETOUCH\n    if (!use_sparse_files)\n    {\n        aeron_touch_pages(mapped_raw_log->mapped_file.addr, (size_t)log_length, (size_t)page_size);\n    }\n#endif\n\n    for (size_t i = 0; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n    {\n        mapped_raw_log->term_buffers[i].addr = (uint8_t *)mapped_raw_log->mapped_file.addr + (i * term_length);\n        mapped_raw_log->term_buffers[i].length = (size_t)term_length;\n    }\n\n    mapped_raw_log->log_meta_data.addr =\n        (uint8_t *)mapped_raw_log->mapped_file.addr + (log_length - AERON_LOGBUFFER_META_DATA_LENGTH);\n    mapped_raw_log->log_meta_data.length = AERON_LOGBUFFER_META_DATA_LENGTH;\n    mapped_raw_log->term_length = (size_t)term_length;\n\n    return 0;\n}\n\nint aeron_raw_log_map_existing(aeron_mapped_raw_log_t *mapped_raw_log, const char *path, bool pre_touch)\n{\n    int fd = aeron_open_file_rw(path);\n    if (fd < 0)\n    {\n        return -1;\n    }\n\n    const int64_t file_length = aeron_file_length(path);\n    if (-1 == file_length)\n    {\n#if !defined(_MSC_VER)\n        AERON_SET_ERR(errno, \"Failed to determine the size of the existing raw log, filename: %s\", path);\n        close(fd);\n#else\n        AERON_SET_ERR_WIN(GetLastError(), \"Failed to determine the size of the existing raw log, filename: %s\", path);\n        _close(fd);\n#endif\n        return -1;\n    }\n\n    mapped_raw_log->mapped_file.length = file_length;\n    mapped_raw_log->mapped_file.addr = NULL;\n\n    if (0 != aeron_mmap(&mapped_raw_log->mapped_file, fd, pre_touch))\n    {\n        AERON_APPEND_ERR(\"filename: %s\", path);\n        return -1;\n    }\n\n    mapped_raw_log->log_meta_data.addr =\n        (uint8_t *)mapped_raw_log->mapped_file.addr +\n        (mapped_raw_log->mapped_file.length - AERON_LOGBUFFER_META_DATA_LENGTH);\n    mapped_raw_log->log_meta_data.length = AERON_LOGBUFFER_META_DATA_LENGTH;\n\n    aeron_logbuffer_metadata_t *log_meta_data = (aeron_logbuffer_metadata_t *)mapped_raw_log->log_meta_data.addr;\n    size_t term_length = (size_t)log_meta_data->term_length;\n    size_t page_size = (size_t)log_meta_data->page_size;\n\n    if (aeron_logbuffer_check_term_length(term_length) < 0 || aeron_logbuffer_check_page_size(page_size) < 0)\n    {\n        AERON_APPEND_ERR(\"Raw log metadata invalid, unmapping, filename: %s\", path);\n        aeron_unmap(&mapped_raw_log->mapped_file);\n        return -1;\n    }\n\n    mapped_raw_log->term_length = term_length;\n\n    for (size_t i = 0; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n    {\n        mapped_raw_log->term_buffers[i].addr = (uint8_t *)mapped_raw_log->mapped_file.addr + (i * term_length);\n        mapped_raw_log->term_buffers[i].length = term_length;\n    }\n\n#ifndef AERON_NATIVE_PRETOUCH\n    if (pre_touch)\n    {\n        aeron_touch_pages(mapped_raw_log->mapped_file.addr, (size_t)file_length, (size_t)page_size);\n    }\n#endif\n\n    return 0;\n}\n\nint aeron_raw_log_close(aeron_mapped_raw_log_t *mapped_raw_log, const char *filename)\n{\n    if (!aeron_raw_log_free(mapped_raw_log, filename))\n    {\n        AERON_SET_ERR(errno, \"Failed to close raw log, filename: %s\", filename);\n        return -1;\n    }\n\n    return 0;\n}\n\nbool aeron_raw_log_free(aeron_mapped_raw_log_t *mapped_raw_log, const char *filename)\n{\n    if (NULL != mapped_raw_log->mapped_file.addr)\n    {\n        if (aeron_unmap(&mapped_raw_log->mapped_file) < 0)\n        {\n            return false;\n        }\n\n        mapped_raw_log->mapped_file.addr = NULL;\n    }\n\n    if (NULL != filename && mapped_raw_log->mapped_file.length > 0)\n    {\n        if (aeron_delete_file(filename) < 0 && aeron_file_length(filename) > 0)\n        {\n            return false;\n        }\n\n        mapped_raw_log->mapped_file.length = 0;\n    }\n\n    return true;\n}\n\n#if defined(__clang__)\n    #pragma clang diagnostic push\n    #pragma clang diagnostic ignored \"-Wunused-function\"\n#endif\n\ninline static const char *tmp_dir(void)\n{\n#if defined(_MSC_VER)\n    static char buff[MAX_PATH + 1];\n\n    if (GetTempPath(MAX_PATH, &buff[0]) > 0)\n    {\n        return buff;\n    }\n\n    return NULL;\n#else\n    const char *dir = \"/tmp\";\n    const char *tmp_dir = getenv(\"TMPDIR\");\n\n    if (NULL != tmp_dir)\n    {\n        dir = tmp_dir;\n    }\n\n    return dir;\n#endif\n}\n\ninline static bool has_file_separator_at_end(const char *path)\n{\n#if defined(_MSC_VER)\n    const char last = path[strlen(path) - 1];\n    return last == '\\\\' || last == '/';\n#else\n    return path[strlen(path) - 1] == '/';\n#endif\n}\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\ninline static const char *username(void)\n{\n#if (_MSC_VER)\n    const char *username = getenv(\"USER\");\n\n    if (NULL == username)\n    {\n        username = getenv(\"USERNAME\");\n        if (NULL == username)\n        {\n             username = \"default\";\n        }\n    }\n\n    return username;\n#else\n    static char static_buffer[16384];\n    const char *username = getenv(\"USER\");\n\n    if (NULL == username)\n    {\n        uid_t uid = getuid(); // using uid instead of euid as that is what the JVM seems to do.\n        struct passwd pw, *pw_result = NULL;\n\n        int e = getpwuid_r(uid, &pw, static_buffer, sizeof(static_buffer), &pw_result);\n        username = (0 == e && NULL != pw_result && NULL != pw_result->pw_name && '\\0' != *(pw_result->pw_name)) ?\n            pw_result->pw_name : \"default\";\n    }\n\n    return username;\n#endif\n}\n\nint aeron_default_path(char *path, size_t path_length)\n{\n#if defined(__linux__)\n    return snprintf(path, path_length, \"/dev/shm/aeron-%s\", username());\n#elif defined(_MSC_VER)\n    return snprintf(\n        path, path_length, \"%s%saeron-%s\", tmp_dir(), has_file_separator_at_end(tmp_dir()) ? \"\" : \"\\\\\", username());\n#else\n    return snprintf(\n        path, path_length, \"%s%saeron-%s\", tmp_dir(), has_file_separator_at_end(tmp_dir()) ? \"\" : \"/\", username());\n#endif\n}\n\nint aeron_file_resolve(const char *parent, const char *child, char *buffer, size_t buffer_len)\n{\n    int result = snprintf(buffer, buffer_len, \"%s%c%s\", parent, AERON_FILE_SEP, child);\n    buffer[buffer_len - 1] = '\\0';\n\n    if (result < 0)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Failed to format resolved path\");\n        return -1;\n    }\n    else if ((int)buffer_len <= result)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Path name was truncated, required: %d, supplied: %d, result: %s\",\n            result,\n            (int)buffer_len,\n            buffer);\n        return -1;\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_fileutil.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_FILEUTIL_H\n#define AERON_FILEUTIL_H\n\n#include <stddef.h>\n#include <sys/types.h>\n\n#include \"util/aeron_platform.h\"\n#include \"concurrent/aeron_logbuffer_descriptor.h\"\n\ntypedef struct aeron_mapped_file_stct\n{\n    void *addr;\n    size_t length;\n}\naeron_mapped_file_t;\n\ntypedef struct aeron_mapped_buffer_stct\n{\n    uint8_t *addr;\n    size_t length;\n}\naeron_mapped_buffer_t;\n\nint aeron_is_directory(const char *path);\nint aeron_delete_directory(const char *directory);\nint aeron_mkdir_recursive(const char *pathname, int permission);\n\nint aeron_map_new_file(aeron_mapped_file_t *mapped_file, const char *path, bool fill_with_zeroes);\nint aeron_map_existing_file(aeron_mapped_file_t *mapped_file, const char *path);\nint aeron_unmap(aeron_mapped_file_t *mapped_file);\n\nint aeron_msync(void *addr, size_t length);\nint aeron_delete_file(const char *path);\n\n#if defined(AERON_COMPILER_GCC)\n#include <unistd.h>\n\n#define AERON_FILEUTIL_ERROR_ENOSPC ENOSPC\n\n#define aeron_mkdir mkdir\n#elif defined(AERON_COMPILER_MSVC)\n#define _CRT_RAND_S\n#include <io.h>\n#include <direct.h>\n#include <process.h>\n#include <winsock2.h>\n#include <windows.h>\n\n#define S_IRWXU 0\n#define S_IRWXG 0\n#define S_IRWXO 0\n\n#define AERON_FILEUTIL_ERROR_ENOSPC ERROR_DISK_FULL\n\nint aeron_mkdir(const char *path, int permission);\n#endif\n\ntypedef uint64_t (*aeron_usable_fs_space_func_t)(const char *path);\n\nint64_t aeron_file_length(const char *path);\nuint64_t aeron_usable_fs_space(const char *path);\nuint64_t aeron_usable_fs_space_disabled(const char *path);\n\ntypedef struct aeron_mapped_raw_log_stct\n{\n    aeron_mapped_buffer_t term_buffers[AERON_LOGBUFFER_PARTITION_COUNT];\n    aeron_mapped_buffer_t log_meta_data;\n    aeron_mapped_file_t mapped_file;\n    size_t term_length;\n}\naeron_mapped_raw_log_t;\n\n#define AERON_PUBLICATIONS_DIR \"publications\"\n#define AERON_IMAGES_DIR \"images\"\n\nint aeron_ipc_publication_location(char *dst, size_t length, const char *aeron_dir, int64_t correlation_id);\n\nint aeron_network_publication_location(char *dst, size_t length, const char *aeron_dir, int64_t correlation_id);\n\nint aeron_publication_image_location(char *dst, size_t length, const char *aeron_dir, int64_t correlation_id);\n\nsize_t aeron_temp_filename(char *filename, size_t length);\n\ntypedef int (*aeron_raw_log_map_func_t)(aeron_mapped_raw_log_t *, const char *, bool, uint64_t, uint64_t);\ntypedef int (*aeron_raw_log_close_func_t)(aeron_mapped_raw_log_t *, const char *filename);\ntypedef bool (*aeron_raw_log_free_func_t)(aeron_mapped_raw_log_t *, const char *filename);\n\nint aeron_raw_log_map(\n    aeron_mapped_raw_log_t *mapped_raw_log,\n    const char *path,\n    bool use_sparse_files,\n    uint64_t term_length,\n    uint64_t page_size);\n\nint aeron_raw_log_map_existing(aeron_mapped_raw_log_t *mapped_raw_log, const char *path, bool pre_touch);\n\nint aeron_raw_log_close(aeron_mapped_raw_log_t *mapped_raw_log, const char *filename);\n\nbool aeron_raw_log_free(aeron_mapped_raw_log_t *mapped_raw_log, const char *filename);\n\nint aeron_file_resolve(const char *parent, const char *child, char *buffer, size_t buffer_len);\n\n#endif //AERON_FILEUTIL_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_http_util.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <string.h>\n#include <stdlib.h>\n#include <inttypes.h>\n\n#include \"aeron_http_util.h\"\n#include \"aeron_error.h\"\n#include \"aeron_netutil.h\"\n#include \"aeron_socket.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_arrayutil.h\"\n#include \"concurrent/aeron_thread.h\"\n#include \"aeronc.h\"\n\nint aeron_http_parse_url(const char *url, aeron_http_parsed_url_t *parsed_url)\n{\n    const char *pb = url;\n    char c;\n    int i = 0, at_index = -1, first_slash_index = -1, end_index, length;\n\n    // codeql[cpp/user-controlled-bypass]\n    if (strncmp(url, \"http://\", strlen(\"http://\")) != 0)\n    {\n        AERON_SET_ERR(EINVAL, \"URL %s does not have supported scheme\", url);\n        return -1;\n    }\n\n    pb += strlen(\"http://\");\n\n    while ((c = *(pb + i)) != '\\0')\n    {\n        if ('@' == c)\n        {\n            at_index = i;\n        }\n        else if (-1 == first_slash_index && '/' == c)\n        {\n            first_slash_index = i;\n        }\n\n        i++;\n    }\n    end_index = i;\n\n    parsed_url->userinfo[0] = '\\0';\n    parsed_url->host_and_port[0] = '\\0';\n    parsed_url->path_and_query[0] = '/';\n    parsed_url->path_and_query[1] = '\\0';\n\n    if (-1 != first_slash_index)\n    {\n        length = end_index - first_slash_index;\n\n        if (length > (int)sizeof(parsed_url->path_and_query))\n        {\n            AERON_SET_ERR(EINVAL, \"URL %s has too long path\", url);\n            return -1;\n        }\n\n        memcpy(parsed_url->path_and_query, pb + first_slash_index, (size_t)length);\n        parsed_url->path_and_query[length] = '\\0';\n        end_index = first_slash_index;\n    }\n\n    if (-1 != at_index)\n    {\n        length = at_index;\n\n        if (length > (int)sizeof(parsed_url->userinfo))\n        {\n            AERON_SET_ERR(EINVAL, \"URL %s has too long userinfo\", url);\n            return -1;\n        }\n\n        memcpy(parsed_url->userinfo, pb, (size_t)length);\n        parsed_url->userinfo[length] = '\\0';\n        pb += length;\n    }\n\n    length = end_index - (-1 == at_index ? 0 : at_index);\n\n    if (length > (int)sizeof(parsed_url->host_and_port))\n    {\n        AERON_SET_ERR(EINVAL, \"URL %s has too long host and port\", url);\n        return -1;\n    }\n\n    memcpy(parsed_url->host_and_port, pb, (size_t)length);\n    parsed_url->host_and_port[length] = '\\0';\n\n    aeron_parsed_address_t parsed_address;\n\n    if (-1 == aeron_address_split(parsed_url->host_and_port, &parsed_address))\n    {\n        return -1;\n    }\n\n    int port = aeron_udp_port_resolver(parsed_address.port, true);\n\n    port = 0 == port ? 80 : port;\n\n    int result;\n    if (6  == parsed_address.ip_version_hint)\n    {\n        result = aeron_ipv6_addr_resolver(parsed_address.host, IPPROTO_TCP, &parsed_url->address);\n        ((struct sockaddr_in6 *)&parsed_url->address)->sin6_port = htons((uint16_t)port);\n    }\n    else\n    {\n        result = aeron_ipv4_addr_resolver(parsed_address.host, IPPROTO_TCP, &parsed_url->address);\n        ((struct sockaddr_in *)&parsed_url->address)->sin_port = htons((uint16_t)port);\n    }\n\n    parsed_url->ip_version_hint = parsed_address.ip_version_hint;\n\n    return result;\n}\n\nint aeron_http_response_ensure_capacity(aeron_http_response_t *response, size_t new_capacity)\n{\n    if (new_capacity > response->capacity)\n    {\n        new_capacity = (size_t)aeron_find_next_power_of_two((int32_t)new_capacity);\n\n        if (aeron_array_ensure_capacity((uint8_t **)&response->buffer, 1, response->capacity, new_capacity) < 0)\n        {\n            return -1;\n        }\n\n        response->capacity = new_capacity;\n\n        return 0;\n    }\n\n    return 0;\n}\n\nint aeron_http_parse_response(aeron_http_response_t *response)\n{\n    char line[AERON_HTTP_MAX_HEADER_LENGTH];\n    int line_result;\n\n    do\n    {\n        if (0 == response->status_code)\n        {\n            if ((line_result = aeron_parse_get_line(line, sizeof(line), response->buffer)) == -1)\n            {\n                response->parse_err = true;\n                return true;\n            }\n            else if (0 == line_result)\n            {\n                break;\n            }\n            else if (0 < line_result)\n            {\n                char version[9], code_str[4], reason_phrase[1024];\n                int matches = sscanf(line, \"%8s %3[0-9] %s\\r\\n\", version, code_str, reason_phrase);\n\n                if (3 == matches)\n                {\n                    errno = 0;\n                    unsigned long code = strtoul(code_str, NULL, 10);\n\n                    if (0 == code)\n                    {\n                        AERON_SET_ERR(EINVAL, \"http response code <%s> parsed to 0, errno=%d\", code_str, errno);\n                        response->parse_err = true;\n                        return -1;\n                    }\n\n                    response->status_code = (size_t)code;\n                    response->cursor = (size_t)line_result;\n                    response->headers_offset = (size_t)line_result;\n                }\n                else\n                {\n                    AERON_SET_ERR(EINVAL, \"could not parse response line: <%s>\", line);\n                    response->parse_err = true;\n                    return -1;\n                }\n            }\n        }\n        else if (0 == response->body_offset)\n        {\n            if ((line_result = aeron_parse_get_line(line, sizeof(line), response->buffer + response->cursor)) == -1)\n            {\n                AERON_APPEND_ERR(\"%s\", \"failed to get http line\");\n                response->parse_err = true;\n                return -1;\n            }\n            else if (0 == line_result)\n            {\n                break;\n            }\n            else if (0 < line_result)\n            {\n                if (strncmp(line, \"\\r\\n\", 2) == 0)\n                {\n                    response->body_offset = response->cursor + line_result;\n                }\n                else if (strncmp(line, \"Content-Length:\", strlen(\"Content-Length:\")) == 0)\n                {\n                    errno = 0;\n                    unsigned long content_length = strtoul(line + strlen(\"Content-Length:\"), NULL, 10);\n\n                    if (0 == content_length)\n                    {\n                        AERON_SET_ERR(EINVAL, \"http Content-Length <%s> parsed to 0, errno=%d\", line, errno);\n                        response->parse_err = true;\n                        return -1;\n                    }\n\n                    response->content_length = (size_t)content_length;\n                }\n\n                response->cursor += line_result;\n            }\n        }\n        else\n        {\n            if (0 < response->content_length && response->content_length <= (response->length - response->body_offset))\n            {\n                response->is_complete = true;\n            }\n            break;\n        }\n    }\n    while (true);\n\n    return 0;\n}\n\n#define AERON_HTTP_UTIL_EMPTY \"\"\n\nstatic char aeron_http_request_format[] =\n    \"%s %s HTTP/1.1\\r\\n\"\n    \"Host: %s\\r\\n\"\n    \"Accept: text/plain, text/*, */*\\r\\n\"\n    \"Accept-Encoding: identity\\r\\n\"\n    \"%s\"\n    \"%s\"\n    \"\\r\\n\";\n\nstatic int aeron_http_send_request(\n    aeron_http_response_t **response,\n    const char *url,\n    int64_t timeout_ns,\n    const char *method,\n    const char *headers,\n    const char *body)\n{\n    aeron_http_parsed_url_t parsed_url;\n    aeron_socket_t sock;\n    aeron_http_response_t *_response = NULL;\n\n    *response = NULL;\n    if (aeron_http_parse_url(url, &parsed_url) == -1)\n    {\n        return -1;\n    }\n\n    if ((sock = aeron_socket(parsed_url.address.ss_family, SOCK_STREAM, 0)) == -1)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    bool is_ipv6 = AF_INET6 == parsed_url.address.ss_family;\n    socklen_t addr_len = is_ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);\n\n    if (aeron_connect(sock, (struct sockaddr *)&parsed_url.address, addr_len) < 0)\n    {\n        AERON_APPEND_ERR(\"http connect: %s\", url);\n        goto error;\n    }\n\n    char request[sizeof(parsed_url.path_and_query) + sizeof(aeron_http_request_format) + 1];\n    int length = snprintf(\n        request, sizeof(request) - 1,\n        aeron_http_request_format,\n        method, parsed_url.path_and_query, parsed_url.host_and_port, headers, body);\n    ssize_t sent_length = 0;\n\n    if (length < 0 || (sent_length = send(sock, request, length, 0)) < length)\n    {\n        AERON_SET_ERR(errno, \"http sent %\" PRIu64 \"/%d bytes\", (uint64_t)sent_length, length);\n        goto error;\n    }\n\n    if (aeron_set_socket_non_blocking(sock) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_alloc((void **)&_response, sizeof(aeron_http_response_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"Failed to allocate response for url: %s\", url);\n        goto error;\n    }\n\n    _response->buffer = NULL;\n    _response->headers_offset = 0;\n    _response->cursor = 0;\n    _response->body_offset = 0;\n    _response->length = 0;\n    _response->capacity = 0;\n    _response->status_code = 0;\n    _response->content_length = 0;\n    _response->parse_err = false;\n\n    const int64_t start_ns = aeron_nano_clock();\n\n    do\n    {\n        const int64_t now_ns = aeron_nano_clock();\n\n        if (-1 != timeout_ns && now_ns > (start_ns + timeout_ns))\n        {\n            AERON_SET_ERR(ETIMEDOUT, \"http recv timeout: %s\", strerror(ETIMEDOUT));\n            goto error;\n        }\n\n        if (aeron_http_response_ensure_capacity(_response, _response->length + AERON_HTTP_RESPONSE_RECV_LENGTH + 1) < 0)\n        {\n            goto error;\n        }\n\n        ssize_t recv_length = recv(sock, _response->buffer + _response->length, AERON_HTTP_RESPONSE_RECV_LENGTH, 0);\n        if (recv_length < 0)\n        {\n            int errcode = errno;\n\n            if (EINTR == errcode || EAGAIN == errcode)\n            {\n                sched_yield();\n                continue;\n            }\n\n            AERON_SET_ERR(errno, \"http recv: %s\", url);\n            goto error;\n        }\n\n        if (0 == recv_length)\n        {\n            break;\n        }\n\n        _response->length += recv_length;\n        _response->buffer[_response->length] = '\\0';\n\n        if (aeron_http_parse_response(_response) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n    }\n    while (!_response->is_complete);\n\n    if (_response->parse_err)\n    {\n        goto error;\n    }\n\n    *response = _response;\n    return 0;\n\nerror:\n    if (-1 != sock)\n    {\n        aeron_close_socket(sock);\n    }\n\n    aeron_http_response_delete(_response);\n\n    return -1;\n}\n\nint aeron_http_retrieve(aeron_http_response_t **response, const char *url, int64_t timeout_ns)\n{\n    return aeron_http_send_request(response, url, timeout_ns, \"GET\", AERON_HTTP_UTIL_EMPTY, AERON_HTTP_UTIL_EMPTY);\n}\n\nint aeron_http_get(\n    aeron_http_response_t **response, const char *url, int64_t timeout_ns, const char *headers)\n{\n    return aeron_http_send_request(response, url, timeout_ns, \"GET\", headers, AERON_HTTP_UTIL_EMPTY);\n}\n\nint aeron_http_put(\n    aeron_http_response_t **response, const char *url, int64_t timeout_ns, const char *headers, const char *body)\n{\n    return aeron_http_send_request(response, url, timeout_ns, \"PUT\", headers, body);\n}\n\nint aeron_http_header_get(aeron_http_response_t *response, const char *header_name, char *line, size_t max_length)\n{\n    size_t header_name_length = strlen(header_name);\n    size_t cursor = response->headers_offset;\n\n    while (cursor < response->body_offset)\n    {\n        int line_result = aeron_parse_get_line(line, max_length, response->buffer + cursor);\n        if (-1 == line_result)\n        {\n            return -1;\n        }\n        else if (0 == line_result)\n        {\n            break;\n        }\n\n        if (strncmp(line, header_name, header_name_length) == 0)\n        {\n            return 1;\n        }\n\n        cursor += line_result;\n    }\n\n    line[0] = '\\0';\n\n    return 0;\n}\n\nextern void aeron_http_response_delete(aeron_http_response_t *response);\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_http_util.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_HTTP_UTIL_H\n#define AERON_HTTP_UTIL_H\n\n#include <stddef.h>\n\n#include \"aeron_socket.h\"\n#include \"aeron_parse_util.h\"\n#include \"aeron_bitutil.h\"\n#include \"aeron_alloc.h\"\n\n#define AERON_MAX_HTTP_USERINFO_LENGTH (384)\n#define AERON_MAX_HTTP_PATH_AND_QUERY_LENGTH (512)\n\n#define AERON_MAX_HTTP_URL_LENGTH (AERON_MAX_HTTP_USERINFO_LENGTH + \\\nAERON_MAX_HOST_LENGTH + \\\nAERON_MAX_PORT_LENGTH + \\\nAERON_MAX_HTTP_PATH_AND_QUERY_LENGTH + 9)\n\ntypedef struct aeron_http_parsed_url_stct\n{\n    char userinfo[AERON_MAX_HTTP_USERINFO_LENGTH];\n    char host_and_port[AERON_MAX_HOST_LENGTH + 1 + AERON_MAX_PORT_LENGTH];\n    char path_and_query[AERON_MAX_HTTP_PATH_AND_QUERY_LENGTH];\n    struct sockaddr_storage address;\n    int ip_version_hint;\n}\naeron_http_parsed_url_t;\n\nint aeron_http_parse_url(const char *url, aeron_http_parsed_url_t *parsed_url);\n\ntypedef struct aeron_http_response_stct\n{\n    char *buffer;\n    size_t cursor;\n    size_t headers_offset;\n    size_t body_offset;\n    size_t length;\n    size_t capacity;\n    size_t status_code;\n    size_t content_length;\n    bool is_complete;\n    bool parse_err;\n}\naeron_http_response_t;\n\n#define AERON_HTTP_RESPONSE_RECV_LENGTH (4 * 1024)\n#define AERON_HTTP_MAX_HEADER_LENGTH (1024)\n\ninline void aeron_http_response_delete(aeron_http_response_t *response)\n{\n    if (NULL != response)\n    {\n        aeron_free(response->buffer);\n        aeron_free(response);\n    }\n}\n\nint aeron_http_retrieve(aeron_http_response_t **response, const char *url, int64_t timeout_ns);\n\nint aeron_http_get(\n    aeron_http_response_t **response, const char *url, int64_t timeout_ns, const char *header);\n\nint aeron_http_put(\n    aeron_http_response_t **response, const char *url, int64_t timeout_ns, const char *header, const char *body);\n\nint aeron_http_header_get(aeron_http_response_t *response, const char *header_name, char *line, size_t max_length);\n\n#endif //AERON_HTTP_UTIL_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_math.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_math.h\"\n\nextern int32_t aeron_add_wrap_i32(int32_t a, int32_t b);\nextern int32_t aeron_sub_wrap_i32(int32_t a, int32_t b);\nextern int32_t aeron_mul_wrap_i32(int32_t a, int32_t b);\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_math.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_MATH_H\n#define AERON_MATH_H\n\n#include <stdint.h>\n\ninline int32_t aeron_add_wrap_i32(int32_t a, int32_t b)\n{\n    const int64_t a_widened = a;\n    const int64_t b_widened = b;\n    const int64_t sum = a_widened + b_widened;\n\n    return (int32_t)(sum & INT64_C(0xFFFFFFFF));\n}\n\ninline int32_t aeron_sub_wrap_i32(int32_t a, int32_t b)\n{\n    const int64_t a_widened = a;\n    const int64_t b_widened = b;\n    const int64_t difference = a_widened - b_widened;\n\n    return (int32_t)(difference & INT64_C(0xFFFFFFFF));\n}\n\ninline int32_t aeron_mul_wrap_i32(int32_t a, int32_t b)\n{\n    const int64_t a_widened = a;\n    const int64_t b_widened = b;\n    const int64_t product = a_widened * b_widened;\n\n    return (int32_t)(product & INT64_C(0xFFFFFFFF));\n}\n\n#endif //AERON_MATH_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_netutil.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <string.h>\n#include <errno.h>\n#include <stdlib.h>\n#include \"util/aeron_netutil.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"aeron_socket.h\"\n#include \"command/aeron_control_protocol.h\"\n\n#if defined(AERON_COMPILER_GCC)\n\n#elif defined(AERON_COMPILER_MSVC)\n\n#include <intrin.h>\n\n#define __builtin_bswap32 _byteswap_ulong\n#define __builtin_bswap64 _byteswap_uint64\n#define __builtin_popcount __popcnt\n\n#if defined(AERON_CPU_X64)\n#define __builtin_popcountll __popcnt64\n#else\n__inline DWORD64 __builtin_popcountll(DWORD64 operand)\n{\n    return __popcnt((DWORD)(operand >> 32)) + __popcnt((DWORD)(operand & UINT32_MAX));\n}\n#endif\n\n#else\n#error Unsupported platform!\n#endif\n\n#define AERON_NETUTIL_NAMED_INTERFACE_OPENING_CHAR '{'\n\nint aeron_ip_addr_resolver(const char *host, struct sockaddr_storage *sockaddr, int family_hint, int protocol)\n{\n    if (-1 == aeron_net_init())\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to init networking\");\n        return -1;\n    }\n\n    struct addrinfo hints;\n    struct addrinfo *info = NULL;\n\n    memset(&hints, 0, sizeof(struct addrinfo));\n    hints.ai_family = family_hint;\n    hints.ai_socktype = IPPROTO_UDP == protocol ? SOCK_DGRAM : SOCK_STREAM;\n    hints.ai_protocol = protocol;\n\n    int error, result = -1;\n    if ((error = getaddrinfo(host, NULL, &hints, &info)) != 0)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_UNKNOWN_HOST, \"Unable to resolve host=(%s): (%d) %s\", host, error, gai_strerror(error));\n        return -1;\n    }\n\n    if (AF_INET == info->ai_family)\n    {\n        memcpy(sockaddr, info->ai_addr, sizeof(struct sockaddr_in));\n        sockaddr->ss_family = AF_INET;\n        result = 0;\n    }\n    else if (AF_INET6 == info->ai_family)\n    {\n        memcpy(sockaddr, info->ai_addr, sizeof(struct sockaddr_in6));\n        sockaddr->ss_family = AF_INET6;\n        result = 0;\n    }\n    else\n    {\n        AERON_SET_ERR(EINVAL, \"Only IPv4 and IPv6 hosts are supported: family=%d\", info->ai_family);\n    }\n\n    freeaddrinfo(info);\n\n    return result;\n}\n\nbool aeron_try_parse_ipv4(const char *host, struct sockaddr_storage *sockaddr)\n{\n    struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr;\n\n    if (inet_pton(AF_INET, host, &addr->sin_addr))\n    {\n        sockaddr->ss_family = AF_INET;\n        return true;\n    }\n\n    return false;\n}\n\nint aeron_ipv4_addr_resolver(const char *host, int protocol, struct sockaddr_storage *sockaddr)\n{\n    struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr;\n\n    if (inet_pton(AF_INET, host, &addr->sin_addr))\n    {\n        sockaddr->ss_family = AF_INET;\n        return 0;\n    }\n\n    return aeron_ip_addr_resolver(host, sockaddr, AF_INET, protocol);\n}\n\nbool aeron_try_parse_ipv6(const char *host, struct sockaddr_storage *sockaddr)\n{\n    struct sockaddr_in6 *addr = (struct sockaddr_in6 *)sockaddr;\n\n    if (inet_pton(AF_INET6, host, &addr->sin6_addr))\n    {\n        sockaddr->ss_family = AF_INET6;\n        return true;\n    }\n\n    return false;\n}\n\nint aeron_ipv6_addr_resolver(const char *host, int protocol, struct sockaddr_storage *sockaddr)\n{\n    struct sockaddr_in6 *addr = (struct sockaddr_in6 *)sockaddr;\n\n    if (inet_pton(AF_INET6, host, &addr->sin6_addr))\n    {\n        sockaddr->ss_family = AF_INET6;\n        return 0;\n    }\n\n    return aeron_ip_addr_resolver(host, sockaddr, AF_INET6, protocol);\n}\n\nint aeron_udp_port_resolver(const char *port_str, bool optional)\n{\n    if (':' == *port_str)\n    {\n        port_str++;\n    }\n\n    if ('\\0' == *port_str)\n    {\n        if (optional)\n        {\n            return 0;\n        }\n    }\n\n    errno = 0;\n    char *end_ptr = NULL;\n    unsigned long value = strtoul(port_str, &end_ptr, 0);\n\n    if ((0 == value && 0 != errno) || end_ptr == port_str)\n    {\n        AERON_SET_ERR(EINVAL, \"port invalid: %s\", port_str);\n        return -1;\n    }\n    else if (value > UINT16_MAX)\n    {\n        AERON_SET_ERR(EINVAL, \"port out of range: %s\", port_str);\n        return -1;\n    }\n\n    return (int)value;\n}\n\nint aeron_prefixlen_resolver(const char *prefixlen, unsigned long max)\n{\n    if ('\\0' == *prefixlen)\n    {\n        return (int)max;\n    }\n\n    if ('/' == *prefixlen)\n    {\n        prefixlen++;\n    }\n\n    if (strcmp(\"0\", prefixlen) == 0)\n    {\n        return 0;\n    }\n\n    errno = 0;\n    char *end_ptr = NULL;\n    unsigned long value = strtoul(prefixlen, &end_ptr, 0);\n\n    if ((0 == value && 0 != errno) || end_ptr == prefixlen)\n    {\n        AERON_SET_ERR(EINVAL, \"prefixlen invalid: %s\", prefixlen);\n        return -1;\n    }\n    else if (value > max)\n    {\n        AERON_SET_ERR(EINVAL, \"prefixlen out of range: %s\", prefixlen);\n        return -1;\n    }\n\n    return (int)value;\n}\n\nint aeron_host_port_prefixlen_resolver(\n    const char *host_str,\n    const char *port_str,\n    const char *prefixlen_str,\n    struct sockaddr_storage *sockaddr,\n    size_t *prefixlen,\n    int family_hint)\n{\n    int host_result = -1, prefixlen_result = -1, port_result = aeron_udp_port_resolver(port_str, true);\n\n    if (AF_INET == family_hint)\n    {\n        host_result = aeron_ipv4_addr_resolver(host_str, IPPROTO_UDP, sockaddr);\n        ((struct sockaddr_in *)sockaddr)->sin_port = htons((uint16_t)port_result);\n    }\n    else if (AF_INET6 == family_hint)\n    {\n        host_result = aeron_ipv6_addr_resolver(host_str, IPPROTO_UDP, sockaddr);\n        ((struct sockaddr_in6 *)sockaddr)->sin6_port = htons((uint16_t)port_result);\n    }\n\n    if (host_result >= 0 && port_result >= 0)\n    {\n        prefixlen_result = aeron_prefixlen_resolver(prefixlen_str, sockaddr->ss_family == AF_INET6 ? 128 : 32);\n        if (prefixlen_result >= 0)\n        {\n            *prefixlen = (size_t)prefixlen_result;\n        }\n    }\n\n    return prefixlen_result >= 0 ? 0 : prefixlen_result;\n}\n\nint aeron_interface_parse_and_resolve(const char *interface_str, struct sockaddr_storage *sockaddr, size_t *prefixlen)\n{\n    aeron_parsed_interface_t parsed_interface;\n\n    if (-1 == aeron_interface_split(interface_str, &parsed_interface))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (6 == parsed_interface.ip_version_hint)\n    {\n        return aeron_host_port_prefixlen_resolver(\n            parsed_interface.host, parsed_interface.port, parsed_interface.prefix, sockaddr, prefixlen, AF_INET6);\n    }\n\n    return aeron_host_port_prefixlen_resolver(\n        parsed_interface.host, parsed_interface.port, parsed_interface.prefix, sockaddr, prefixlen, AF_INET);\n}\n\nstatic aeron_getifaddrs_func_t aeron_getifaddrs_func = aeron_getifaddrs;\n\nstatic aeron_freeifaddrs_func_t aeron_freeifaddrs_func = aeron_freeifaddrs;\n\nvoid aeron_set_getifaddrs(aeron_getifaddrs_func_t get_func, aeron_freeifaddrs_func_t free_func)\n{\n    aeron_getifaddrs_func = get_func;\n    aeron_freeifaddrs_func = free_func;\n}\n\nint aeron_lookup_interfaces(aeron_ifaddr_func_t func, void *clientd)\n{\n    struct ifaddrs *ifaddrs = NULL;\n    int result = -1;\n\n    if (aeron_getifaddrs_func(&ifaddrs) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return result;\n    }\n\n    result = aeron_lookup_interfaces_from_ifaddrs(func, clientd, ifaddrs);\n    aeron_freeifaddrs_func(ifaddrs);\n\n    return result;\n}\n\nint aeron_lookup_interfaces_from_ifaddrs(aeron_ifaddr_func_t func, void *clientd, struct ifaddrs *ifaddrs)\n{\n    int result = 0;\n    for (struct ifaddrs *ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next)\n    {\n        if (NULL == ifa->ifa_addr)\n        {\n            continue;\n        }\n\n        result += func(\n            clientd,\n            ifa->ifa_name,\n            ifa->ifa_addr,\n            ifa->ifa_netmask,\n            ifa->ifa_flags);\n    }\n\n    return result;\n}\n\nuint32_t aeron_ipv4_netmask_from_prefixlen(size_t prefixlen)\n{\n    uint32_t value;\n\n    if (0 == prefixlen)\n    {\n        value = ~(-1);\n    }\n    else\n    {\n        value = ~((UINT32_C(1) << (32 - prefixlen)) - 1);\n    }\n\n#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\n    value = __builtin_bswap32(value);\n#endif\n\n    return value;\n}\n\nbool aeron_ipv4_does_prefix_match(struct in_addr *in_addr1, struct in_addr *in_addr2, size_t prefixlen)\n{\n    uint32_t addr1;\n    uint32_t addr2;\n    uint32_t netmask = aeron_ipv4_netmask_from_prefixlen(prefixlen);\n\n    memcpy(&addr1, in_addr1, sizeof(addr1));\n    memcpy(&addr2, in_addr2, sizeof(addr2));\n\n    return (addr1 & netmask) == (addr2 & netmask);\n}\n\nsize_t aeron_ipv4_netmask_to_prefixlen(struct in_addr *netmask)\n{\n    return __builtin_popcount(netmask->s_addr);\n}\n\nvoid aeron_set_ipv4_wildcard_host_and_port(struct sockaddr_storage *sockaddr)\n{\n    struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr;\n\n    sockaddr->ss_family = AF_INET;\n    addr->sin_port = htons(0);\n    addr->sin_addr.s_addr = INADDR_ANY;\n}\n\nvoid aeron_set_ipv6_wildcard_host_and_port(struct sockaddr_storage *sockaddr)\n{\n    struct sockaddr_in6 *addr = (struct sockaddr_in6 *)sockaddr;\n    memset(addr, 0, sizeof(struct sockaddr_in6));\n\n    sockaddr->ss_family = AF_INET6;\n    addr->sin6_port = htons(0);\n    addr->sin6_addr = in6addr_any;\n}\n\n#if defined(AERON_COMPILER_GCC)\nunion aeron_128b_as_64b\n{\n    __uint128_t value;\n    uint64_t q[2];\n};\n\n__uint128_t aeron_ipv6_netmask_from_prefixlen(size_t prefixlen)\n{\n    union aeron_128b_as_64b netmask;\n\n    if (0 == prefixlen)\n    {\n        netmask.value = ~(-1);\n    }\n    else\n    {\n        netmask.value = ~(((__uint128_t)1 << (128 - prefixlen)) - (__uint128_t)1);\n    }\n\n#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\n    uint64_t q1 = netmask.q[1];\n    netmask.q[1] = __builtin_bswap64(netmask.q[0]);\n    netmask.q[0] = __builtin_bswap64(q1);\n#endif\n\n    return netmask.value;\n}\n\nbool aeron_ipv6_does_prefix_match(struct in6_addr *in6_addr1, struct in6_addr *in6_addr2, size_t prefixlen)\n{\n    __uint128_t addr1;\n    __uint128_t addr2;\n    __uint128_t netmask = aeron_ipv6_netmask_from_prefixlen(prefixlen);\n\n    memcpy(&addr1, in6_addr1, sizeof(addr1));\n    memcpy(&addr2, in6_addr2, sizeof(addr2));\n\n    return (addr1 & netmask) == (addr2 & netmask);\n}\n#else\nunion aeron_128b_as_64b\n{\n    uint64_t q[2];\n};\n#endif\n\nsize_t aeron_ipv6_netmask_to_prefixlen(struct in6_addr *netmask)\n{\n    union aeron_128b_as_64b value;\n\n    memcpy(&value, netmask, sizeof(value));\n\n    return __builtin_popcountll(value.q[0]) + __builtin_popcountll(value.q[1]);\n}\n\nbool aeron_ip_does_prefix_match(struct sockaddr *addr1, struct sockaddr *addr2, size_t prefixlen)\n{\n    bool result = false;\n\n    if (addr1->sa_family == addr2->sa_family)\n    {\n        if (AF_INET6 == addr1->sa_family)\n        {\n            result = aeron_ipv6_does_prefix_match(\n                &((struct sockaddr_in6 *)addr1)->sin6_addr,\n                &((struct sockaddr_in6 *)addr2)->sin6_addr,\n                prefixlen);\n        }\n        else if (AF_INET == addr1->sa_family)\n        {\n            result = aeron_ipv4_does_prefix_match(\n                &((struct sockaddr_in *)addr1)->sin_addr,\n                &((struct sockaddr_in *)addr2)->sin_addr,\n                prefixlen);\n        }\n    }\n\n    return result;\n}\n\nsize_t aeron_ip_netmask_to_prefixlen(struct sockaddr *netmask)\n{\n    return AF_INET6 == netmask->sa_family ?\n        aeron_ipv6_netmask_to_prefixlen(&((struct sockaddr_in6 *)netmask)->sin6_addr) :\n        aeron_ipv4_netmask_to_prefixlen(&((struct sockaddr_in *)netmask)->sin_addr);\n}\n\nstruct lookup_state\n{\n    struct sockaddr_storage lookup_addr;\n    struct sockaddr_storage *if_addr;\n    unsigned int if_index;\n    unsigned int if_flags;\n    size_t prefixlen;\n    size_t if_prefixlen;\n    bool found;\n};\n\nint aeron_ip_lookup_func(\n    void *clientd, const char *name, struct sockaddr *addr, struct sockaddr *netmask, unsigned int flags)\n{\n    if (flags & IFF_UP)\n    {\n        struct lookup_state *state = (struct lookup_state *)clientd;\n\n        if (aeron_ip_does_prefix_match((struct sockaddr *)&state->lookup_addr, addr, state->prefixlen))\n        {\n            size_t addr_len = AF_INET6 == addr->sa_family ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);\n\n            if ((flags & IFF_LOOPBACK) && !state->found)\n            {\n                memcpy(state->if_addr, addr, addr_len);\n                state->if_index = if_nametoindex(name);\n                state->found = true;\n                return 1;\n            }\n            else if (flags & IFF_MULTICAST)\n            {\n                size_t current_if_prefixlen = aeron_ip_netmask_to_prefixlen(netmask);\n\n                if (current_if_prefixlen > state->if_prefixlen)\n                {\n                    memcpy(state->if_addr, addr, addr_len);\n                    state->if_index = if_nametoindex(name);\n                    state->if_prefixlen = current_if_prefixlen;\n                }\n\n                state->found = true;\n                return 1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nstruct lookup_by_name_and_family_state\n{\n    char *name;\n    int family;\n    struct sockaddr_storage *if_addr;\n    unsigned int if_index;\n    bool found;\n    unsigned int name_matches;\n};\n\nstatic int aeron_ip_lookup_by_name_and_family_func(\n    void *clientd, const char *name, struct sockaddr *addr, struct sockaddr *netmask, unsigned int flags)\n{\n    if (flags & IFF_UP)\n    {\n        struct lookup_by_name_and_family_state *state = (struct lookup_by_name_and_family_state *)clientd;\n\n        if (0 == strcmp(name, state->name))\n        {\n            state->name_matches++;\n\n            if (!state->found && state->family == addr->sa_family)\n            {\n                size_t addr_len = AF_INET6 == addr->sa_family ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);\n\n                memcpy(state->if_addr, addr, addr_len);\n                state->if_index = if_nametoindex(name);\n                state->found = true;\n\n                return 1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nvoid aeron_ip_copy_port(struct sockaddr_storage *dest_addr, struct sockaddr_storage *src_addr)\n{\n    if (AF_INET6 == src_addr->ss_family)\n    {\n        struct sockaddr_in6 *dest = (struct sockaddr_in6 *)dest_addr;\n        struct sockaddr_in6 *src = (struct sockaddr_in6 *)src_addr;\n\n        dest->sin6_port = src->sin6_port;\n    }\n    else if (AF_INET == src_addr->ss_family)\n    {\n        struct sockaddr_in *dest = (struct sockaddr_in *)dest_addr;\n        struct sockaddr_in *src = (struct sockaddr_in *)src_addr;\n\n        dest->sin_port = src->sin_port;\n    }\n}\n\nint aeron_parse_named_interface(const char *interface_str, aeron_named_interface_t *out)\n{\n    if (interface_str[0] != AERON_NETUTIL_NAMED_INTERFACE_OPENING_CHAR)\n    {\n        return -1;\n    }\n\n    int close_idx = 0;\n    int len = 1;\n    while (true)\n    {\n        char c = interface_str[len];\n        if (c == '\\0')\n        {\n            break;\n        }\n        else if (c == '}')\n        {\n            close_idx = len;\n        }\n        len++;\n    }\n\n    int name_len = close_idx - 1;\n    if (name_len <= 0 || name_len >= IF_NAMESIZE)\n    {\n        AERON_SET_ERR(EINVAL, \"name length %d out of bounds\", name_len);\n        return -1;\n    }\n\n    int port = 0;\n    int idx = close_idx + 1;\n    if (idx < len)\n    {\n        if (interface_str[idx] != ':')\n        {\n            AERON_SET_ERR(EINVAL, \"%s\", \"unexpected character after name closing brace\");\n            return -1;\n        }\n\n        idx++;\n\n        int port_len = len - idx;\n        if (port_len <= 0 || port_len > 5)\n        {\n            AERON_SET_ERR(EINVAL, \"port length %d out of bounds\", port_len);\n            return -1;\n        }\n\n        port = aeron_udp_port_resolver(interface_str + idx, false);\n        if (port < 0)\n        {\n            return -1;\n        }\n    }\n\n    strncpy(out->name, interface_str + 1, name_len);\n    out->name[name_len] = '\\0';\n    out->port = port;\n\n    return 0;\n}\n\nstatic int aeron_find_interface_by_name_and_family(\n    int family, const char *interface_str, struct sockaddr_storage *if_addr, unsigned int *if_index)\n{\n    aeron_named_interface_t named_interface;\n\n    if (0 > aeron_parse_named_interface(interface_str, &named_interface))\n    {\n        AERON_APPEND_ERR(\"could not parse interface='%s'\", interface_str);\n        return -1;\n    }\n\n    struct lookup_by_name_and_family_state state = {\n        .name = named_interface.name,\n        .family = family,\n        .if_addr = if_addr,\n    };\n\n    int result = aeron_lookup_interfaces(aeron_ip_lookup_by_name_and_family_func, &state);\n    if (0 > result)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (0 == result)\n    {\n        if (!state.name_matches)\n        {\n            AERON_SET_ERR(EINVAL, \"unknown interface %s\", named_interface.name);\n        }\n        else\n        {\n            char buffer[20];\n            switch (family)\n            {\n                case AF_INET:\n                    snprintf(buffer, sizeof(buffer), \"INET\");\n                    break;\n                case AF_INET6:\n                    snprintf(buffer, sizeof(buffer), \"INET6\");\n                    break;\n                default:\n                    snprintf(buffer, sizeof(buffer), \"family %d\", family);\n                    break;\n            }\n            AERON_SET_ERR(EINVAL, \"no %s addresses found on interface %s\", buffer, named_interface.name);\n        }\n        return -1;\n    }\n\n    if (AF_INET6 == if_addr->ss_family)\n    {\n        struct sockaddr_in6 *dest = (struct sockaddr_in6 *)if_addr;\n\n        dest->sin6_port = htons(named_interface.port);\n    }\n    else if (AF_INET == if_addr->ss_family)\n    {\n        struct sockaddr_in *dest = (struct sockaddr_in *)if_addr;\n\n        dest->sin_port = htons(named_interface.port);\n    }\n\n    *if_index = state.if_index;\n\n    return 0;\n}\n\nstatic int aeron_find_interface_by_address(\n    const char *interface_str, struct sockaddr_storage *if_addr, unsigned int *if_index)\n{\n    struct lookup_state state = { 0 };\n\n    if (aeron_interface_parse_and_resolve(interface_str, &state.lookup_addr, &state.prefixlen) < 0)\n    {\n        AERON_APPEND_ERR(\"could not parse interface='%s'\", interface_str);\n        return -1;\n    }\n\n    state.if_addr = if_addr;\n\n    int result = aeron_lookup_interfaces(aeron_ip_lookup_func, &state);\n    if (result < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (0 == result)\n    {\n        AERON_SET_ERR(EINVAL, \"could not find matching interface='%s'\", interface_str);\n        return -1;\n    }\n\n    aeron_ip_copy_port(if_addr, &state.lookup_addr);\n    *if_index = state.if_index;\n\n    return 0;\n}\n\nstatic bool aeron_is_named_interface(const char *interface_str)\n{\n    return interface_str[0] == AERON_NETUTIL_NAMED_INTERFACE_OPENING_CHAR;\n}\n\nint aeron_find_interface(\n    int family, const char *interface_str, struct sockaddr_storage *if_addr, unsigned int *if_index)\n{\n    if (aeron_is_named_interface(interface_str))\n    {\n        return aeron_find_interface_by_name_and_family(family, interface_str, if_addr, if_index);\n    }\n\n    return aeron_find_interface_by_address(interface_str, if_addr, if_index);\n}\n\nint aeron_find_unicast_interface(\n    int family, const char *interface_str, struct sockaddr_storage *interface_addr, unsigned int *interface_index)\n{\n    *interface_index = 0;\n\n    if (NULL != interface_str)\n    {\n        struct sockaddr_storage tmp_addr;\n        size_t prefixlen = 0;\n\n        if (!aeron_is_named_interface(interface_str) &&\n            aeron_interface_parse_and_resolve(interface_str, &tmp_addr, &prefixlen) >= 0 &&\n            aeron_is_wildcard_addr(&tmp_addr))\n        {\n            memcpy(interface_addr, &tmp_addr, sizeof(tmp_addr));\n            return 0;\n        }\n\n        return aeron_find_interface(family, interface_str, interface_addr, interface_index);\n    }\n    else if (AF_INET6 == family)\n    {\n        interface_addr->ss_family = AF_INET6;\n        struct sockaddr_in6 *addr = (struct sockaddr_in6 *)interface_addr;\n        addr->sin6_addr = in6addr_any;\n        addr->sin6_port = htons(0);\n    }\n    else\n    {\n        interface_addr->ss_family = AF_INET;\n        struct sockaddr_in *addr = (struct sockaddr_in *)interface_addr;\n        addr->sin_addr.s_addr = INADDR_ANY;\n        addr->sin_port = htons(0);\n    }\n\n    return 0;\n}\n\nbool aeron_is_addr_multicast(struct sockaddr_storage *addr)\n{\n    bool result = false;\n\n    if (AF_INET6 == addr->ss_family)\n    {\n        struct sockaddr_in6 *a = (struct sockaddr_in6 *)addr;\n\n        result = IN6_IS_ADDR_MULTICAST(&a->sin6_addr);\n    }\n    else if (AF_INET == addr->ss_family)\n    {\n        struct sockaddr_in *a = (struct sockaddr_in *)addr;\n\n        result = IN_MULTICAST(ntohl(a->sin_addr.s_addr));\n    }\n\n    return result;\n}\n\nbool aeron_is_wildcard_addr(struct sockaddr_storage *addr)\n{\n    bool result = false;\n\n    if (AF_INET6 == addr->ss_family)\n    {\n        struct sockaddr_in6 *a = (struct sockaddr_in6 *)addr;\n\n        return memcmp(&a->sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 ? true : false;\n    }\n    else if (AF_INET == addr->ss_family)\n    {\n        struct sockaddr_in *a = (struct sockaddr_in *)addr;\n\n        result = a->sin_addr.s_addr == INADDR_ANY;\n    }\n\n    return result;\n}\n\nbool aeron_is_wildcard_port(struct sockaddr_storage *addr)\n{\n    bool result = false;\n\n    if (AF_INET6 == addr->ss_family)\n    {\n        struct sockaddr_in6 *a = (struct sockaddr_in6 *)addr;\n\n        return 0 == a->sin6_port;\n    }\n    else if (AF_INET == addr->ss_family)\n    {\n        struct sockaddr_in *a = (struct sockaddr_in *)addr;\n\n        result = 0 == a->sin_port;\n    }\n\n    return result;\n}\n\nint aeron_format_source_identity(char *buffer, size_t length, struct sockaddr_storage *addr)\n{\n    char addr_str[INET6_ADDRSTRLEN] = \"\";\n\n    if (length < AERON_NETUTIL_FORMATTED_MAX_LENGTH)\n    {\n        return -ENOSPC;\n    }\n\n    int total = 0;\n    if (AF_INET6 == addr->ss_family)\n    {\n        struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)addr;\n\n        inet_ntop(addr->ss_family, &in6->sin6_addr, addr_str, sizeof(addr_str));\n        unsigned short port = ntohs(in6->sin6_port);\n        total = snprintf(buffer, length, \"[%s]:%d\", addr_str, port);\n    }\n    else if (AF_INET == addr->ss_family)\n    {\n        struct sockaddr_in *in4 = (struct sockaddr_in *)addr;\n\n        inet_ntop(addr->ss_family, &in4->sin_addr, addr_str, sizeof(addr_str));\n        unsigned short port = ntohs(in4->sin_port);\n        total = snprintf(buffer, length, \"%s:%d\", addr_str, port);\n    }\n\n    if (total < 0)\n    {\n        return 0;\n    }\n\n    return total;\n}\n\nint aeron_netutil_get_so_buf_lengths(size_t *default_so_rcvbuf, size_t *default_so_sndbuf)\n{\n    int result = -1;\n\n    aeron_socket_t fd = aeron_socket(PF_INET, SOCK_DGRAM, 0);\n    if (fd < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to probe socket for buffer lengths\");\n        goto done;\n    }\n\n    socklen_t optlen = sizeof(size_t);\n\n    if (aeron_getsockopt(fd, SOL_SOCKET, SO_RCVBUF, default_so_rcvbuf, &optlen) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to get SOL_SOCKET/SO_RCVBUF option\");\n        goto done;\n    }\n\n    if (aeron_getsockopt(fd, SOL_SOCKET, SO_SNDBUF, default_so_sndbuf, &optlen) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to get SOL_SOCKET/SO_SNDBUF option\");\n        goto done;\n    }\n\n    result = 0;\n\ndone:\n    if (fd > 0)\n    {\n        aeron_close_socket(fd);\n    }\n\n    return result;\n}\n\nint aeron_sockaddr_storage_cmp(struct sockaddr_storage *a, struct sockaddr_storage *b, bool *result)\n{\n    if (a->ss_family != b->ss_family)\n    {\n        *result = false;\n    }\n    else if (AF_INET == a->ss_family)\n    {\n        struct sockaddr_in *a_in = (struct sockaddr_in *)a;\n        struct sockaddr_in *b_in = (struct sockaddr_in *)b;\n\n        *result = (a_in->sin_addr.s_addr == b_in->sin_addr.s_addr) && (a_in->sin_port == b_in->sin_port);\n    }\n    else if (AF_INET6 == a->ss_family)\n    {\n        struct sockaddr_in6 *a_in6 = (struct sockaddr_in6 *)a;\n        struct sockaddr_in6 *b_in6 = (struct sockaddr_in6 *)b;\n\n        *result = 0 == memcmp(&a_in6->sin6_addr, &b_in6->sin6_addr, sizeof(struct sockaddr_in6)) &&\n            (a_in6->sin6_port == b_in6->sin6_port);\n    }\n    else\n    {\n        *result = false;\n        AERON_SET_ERR(EINVAL, \"%s\", \"Unsupported address family\");\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_netutil.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_NETUTIL_H\n#define AERON_NETUTIL_H\n\n#include <stdbool.h>\n#include <stdio.h>\n#include \"aeron_socket.h\"\n#include \"aeron_common.h\"\n\n// This includes '[' and ']' around the address and the ':<port>'.\n// The system header already includes the null terminator\n#define AERON_NETUTIL_FORMATTED_MAX_LENGTH (INET6_ADDRSTRLEN + 8)\n\nstruct ifaddrs;\nstruct addrinfo;\n\ntypedef int (*aeron_uri_hostname_resolver_func_t)\n    (void *clientd, const char *host, struct addrinfo *hints, struct addrinfo **info);\n\ntypedef int (*aeron_getifaddrs_func_t)(struct ifaddrs **);\n\ntypedef void (*aeron_freeifaddrs_func_t)(struct ifaddrs *);\n\ntypedef int (*aeron_ifaddr_func_t)\n    (void *clientd, const char *name, struct sockaddr *addr, struct sockaddr *netmask, unsigned int flags);\n\n#define AERON_ADDR_LEN(a) (AF_INET6 == (a)->ss_family ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))\n\nint aeron_ip_addr_resolver(const char *host, struct sockaddr_storage *sockaddr, int family_hint, int protocol);\n\nint aeron_udp_port_resolver(const char *port_str, bool optional);\n\nbool aeron_try_parse_ipv4(const char *host, struct sockaddr_storage *sockaddr);\n\nint aeron_ipv4_addr_resolver(const char *host, int protocol, struct sockaddr_storage *sockaddr);\n\nbool aeron_try_parse_ipv6(const char *host, struct sockaddr_storage *sockaddr);\n\nint aeron_ipv6_addr_resolver(const char *host, int protocol, struct sockaddr_storage *sockaddr);\n\nint aeron_lookup_interfaces(aeron_ifaddr_func_t func, void *clientd);\n\nint aeron_lookup_interfaces_from_ifaddrs(aeron_ifaddr_func_t func, void *clientd, struct ifaddrs *ifaddrs);\n\nvoid aeron_set_getifaddrs(aeron_getifaddrs_func_t get_func, aeron_freeifaddrs_func_t free_func);\n\nint aeron_interface_parse_and_resolve(const char *interface_str, struct sockaddr_storage *sockaddr, size_t *prefixlen);\n\nvoid aeron_set_ipv4_wildcard_host_and_port(struct sockaddr_storage *sockaddr);\n\nvoid aeron_set_ipv6_wildcard_host_and_port(struct sockaddr_storage *sockaddr);\n\nbool aeron_ipv4_does_prefix_match(struct in_addr *in_addr1, struct in_addr *in_addr2, size_t prefixlen);\n\nbool aeron_ipv6_does_prefix_match(struct in6_addr *in6_addr1, struct in6_addr *in6_addr2, size_t prefixlen);\n\nsize_t aeron_ipv4_netmask_to_prefixlen(struct in_addr *netmask);\n\nuint32_t aeron_ipv4_netmask_from_prefixlen(size_t prefixlen);\n\nsize_t aeron_ipv6_netmask_to_prefixlen(struct in6_addr *netmask);\n\nint aeron_find_interface(\n    int family, const char *interface_str, struct sockaddr_storage *if_addr, unsigned int *if_index);\n\nint aeron_find_unicast_interface(\n    int family, const char *interface_str, struct sockaddr_storage *interface_addr, unsigned int *interface_index);\n\nbool aeron_is_addr_multicast(struct sockaddr_storage *addr);\n\nbool aeron_is_wildcard_addr(struct sockaddr_storage *addr);\n\nbool aeron_is_wildcard_port(struct sockaddr_storage *addr);\n\nint aeron_format_source_identity(char *buffer, size_t length, struct sockaddr_storage *addr);\n\nint aeron_netutil_get_so_buf_lengths(size_t *default_so_rcvbuf, size_t *default_so_sndbuf);\n\nint aeron_sockaddr_storage_cmp(struct sockaddr_storage *a, struct sockaddr_storage *b, bool *result);\n\ntypedef struct aeron_named_interface_stct\n{\n    char name[IF_NAMESIZE];\n    int port;\n}\naeron_named_interface_t;\n\nint aeron_parse_named_interface(const char *interface_str, aeron_named_interface_t *out);\n\n#endif //AERON_NETUTIL_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_parse_util.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <errno.h>\n#include <limits.h>\n#include <ctype.h>\n#include <inttypes.h>\n#include <string.h>\n#include \"util/aeron_parse_util.h\"\n#include \"util/aeron_error.h\"\n\nconst uint64_t AERON_ONE_GIGABYTE = 1073741824ULL;\nconst uint64_t AERON_ONE_MEGABYTE = 1048576ULL;\nconst uint64_t AERON_ONE_KILOBYTE = 1024ULL;\nconst uint64_t AERON_MAX_G_VALUE = 8589934591ULL;\nconst uint64_t AERON_MAX_M_VALUE = 8796093022207ULL;\nconst uint64_t AERON_MAX_K_VALUE = 9007199254740991ULL;\n\nconst uint64_t AERON_ONE_MICROSECOND_NS = 1000ULL;\nconst uint64_t AERON_ONE_MILLISECOND_NS = 1000000ULL;\nconst uint64_t AERON_ONE_SECOND_NS = 1000000000ULL;\nconst uint64_t AERON_MAX_MICROSECONDS = 9223372036854775ULL;\nconst uint64_t AERON_MAX_MILLISECONDS = 9223372036854ULL;\nconst uint64_t AERON_MAX_SECONDS = 9223372036ULL;\n\nint aeron_parse_size64(const char *str, uint64_t *result)\n{\n    if (NULL == str)\n    {\n        return -1;\n    }\n\n    errno = 0;\n    char *end = \"\";\n    const int64_t v = strtoll(str, &end, 10);\n\n    if (0 == v && 0 != errno)\n    {\n        return -1;\n    }\n\n    if (v < 0 || end == str)\n    {\n        return -1;\n    }\n\n    const uint64_t value = (uint64_t)v;\n\n    if ('\\0' != *end)\n    {\n        switch (*end)\n        {\n            case 'k':\n            case 'K':\n                if (value > AERON_MAX_K_VALUE)\n                {\n                    return -1;\n                }\n                *result = value * AERON_ONE_KILOBYTE;\n                break;\n\n            case 'm':\n            case 'M':\n                if (value > AERON_MAX_M_VALUE)\n                {\n                    return -1;\n                }\n                *result = value * AERON_ONE_MEGABYTE;\n                break;\n\n            case 'g':\n            case 'G':\n                if (value > AERON_MAX_G_VALUE)\n                {\n                    return -1;\n                }\n                *result = value * AERON_ONE_GIGABYTE;\n                break;\n\n            default:\n                return -1;\n        }\n    }\n    else\n    {\n        *result = value;\n    }\n\n    return 0;\n}\n\nint aeron_format_size64(uint64_t value, char *buffer, size_t buffer_size)\n{\n    if (value > LLONG_MAX)\n    {\n        AERON_SET_ERR(EINVAL, \"value is out of range: %\" PRIu64, value);\n        return -1;\n    }\n\n    if (value >= AERON_ONE_GIGABYTE)\n    {\n        uint64_t result = value / AERON_ONE_GIGABYTE;\n        if (value == result * AERON_ONE_GIGABYTE)\n        {\n            int rc = snprintf(buffer, buffer_size, \"%\" PRIu64 \"g\", result);\n            if (rc < 0)\n            {\n                AERON_SET_ERR(rc, \"failed to format value: %\" PRIu64, value);\n                return -1;\n            }\n            return rc;\n        }\n    }\n\n    if (value >= AERON_ONE_MEGABYTE)\n    {\n        uint64_t result = value / AERON_ONE_MEGABYTE;\n        if (value == result * AERON_ONE_MEGABYTE)\n        {\n            int rc = snprintf(buffer, buffer_size, \"%\" PRIu64 \"m\", result);\n            if (rc < 0)\n            {\n                AERON_SET_ERR(rc, \"failed to format value: %\" PRIu64, value);\n                return -1;\n            }\n            return rc;\n        }\n    }\n\n    if (value >= AERON_ONE_KILOBYTE)\n    {\n        uint64_t result = value / AERON_ONE_KILOBYTE;\n        if (value == result * AERON_ONE_KILOBYTE)\n        {\n            int rc = snprintf(buffer, buffer_size, \"%\" PRIu64 \"k\", result);\n            if (rc < 0)\n            {\n                AERON_SET_ERR(rc, \"failed to format value: %\" PRIu64, value);\n                return -1;\n            }\n            return rc;\n        }\n    }\n\n    int rc = snprintf(buffer, buffer_size, \"%\" PRIu64, value);\n    if (rc < 0)\n    {\n        AERON_SET_ERR(rc, \"failed to format value: %\" PRIu64, value);\n        return -1;\n    }\n    return rc;\n}\n\nint aeron_parse_duration_ns(const char *str, uint64_t *result)\n{\n    if (NULL == str)\n    {\n        return -1;\n    }\n\n    errno = 0;\n    char *end = \"\";\n    const int64_t v = strtoll(str, &end, 10);\n\n    if (0 == v && 0 != errno)\n    {\n        return -1;\n    }\n\n    if (v < 0 || end == str)\n    {\n        return -1;\n    }\n\n    const uint64_t value = (uint64_t)v;\n\n    if ('\\0' != *end)\n    {\n        switch (*end)\n        {\n            case 's':\n            case 'S':\n                if ('\\0' != *(end + 1))\n                {\n                    return -1;\n                }\n\n                if (value > AERON_MAX_SECONDS)\n                {\n                    *result = LLONG_MAX;\n                }\n                else\n                {\n                    *result = value * AERON_ONE_SECOND_NS;\n                }\n                break;\n\n            case 'm':\n            case 'M':\n                if ('s' != *(end + 1) && 'S' != *(end + 1) && '\\0' != *(end + 2))\n                {\n                    return -1;\n                }\n\n                if (value > AERON_MAX_MILLISECONDS)\n                {\n                    *result = LLONG_MAX;\n                }\n                else\n                {\n                    *result = value * AERON_ONE_MILLISECOND_NS;\n                }\n                break;\n\n            case 'u':\n            case 'U':\n                if ('s' != *(end + 1) && 'S' != *(end + 1) && '\\0' != *(end + 2))\n                {\n                    return -1;\n                }\n\n                if (value > AERON_MAX_MICROSECONDS)\n                {\n                    *result = LLONG_MAX;\n                }\n                else\n                {\n                    *result = value * AERON_ONE_MICROSECOND_NS;\n                }\n                break;\n\n            case 'n':\n            case 'N':\n                if ('s' != *(end + 1) && 'S' != *(end + 1) && '\\0' != *(end + 2))\n                {\n                    return -1;\n                }\n\n                *result = value;\n                break;\n\n            default:\n                return -1;\n        }\n    }\n    else\n    {\n        *result = value;\n    }\n\n    return 0;\n}\n\nint aeron_format_duration_ns(uint64_t duration_ns, char *buffer, size_t buffer_size)\n{\n    if (duration_ns > LLONG_MAX)\n    {\n        AERON_SET_ERR(EINVAL, \"duration is out of range: %\" PRIu64, duration_ns);\n        return -1;\n    }\n\n    if (duration_ns >= AERON_ONE_SECOND_NS)\n    {\n        uint64_t result = duration_ns / AERON_ONE_SECOND_NS;\n        if (duration_ns == result * AERON_ONE_SECOND_NS)\n        {\n            int rc = snprintf(buffer, buffer_size, \"%\" PRIu64 \"s\", result);\n            if (rc < 0)\n            {\n                AERON_SET_ERR(rc, \"failed to format value: %\" PRIu64, duration_ns);\n                return -1;\n            }\n            return rc;\n        }\n    }\n\n    if (duration_ns >= AERON_ONE_MILLISECOND_NS)\n    {\n        uint64_t result = duration_ns / AERON_ONE_MILLISECOND_NS;\n        if (duration_ns == result * AERON_ONE_MILLISECOND_NS)\n        {\n            int rc = snprintf(buffer, buffer_size, \"%\" PRIu64 \"ms\", result);\n            if (rc < 0)\n            {\n                AERON_SET_ERR(rc, \"failed to format value: %\" PRIu64, duration_ns);\n                return -1;\n            }\n            return rc;\n        }\n    }\n\n    if (duration_ns >= AERON_ONE_MICROSECOND_NS)\n    {\n        uint64_t result = duration_ns / AERON_ONE_MICROSECOND_NS;\n        if (duration_ns == result * AERON_ONE_MICROSECOND_NS)\n        {\n            int rc = snprintf(buffer, buffer_size, \"%\" PRIu64 \"us\", result);\n            if (rc < 0)\n            {\n                AERON_SET_ERR(rc, \"failed to format value: %\" PRIu64, duration_ns);\n                return -1;\n            }\n            return rc;\n        }\n    }\n\n    int rc = snprintf(buffer, buffer_size, \"%\" PRIu64 \"ns\", duration_ns);\n    if (rc < 0)\n    {\n        AERON_SET_ERR(rc, \"failed to format value: %\" PRIu64, duration_ns);\n        return -1;\n    }\n    return rc;\n}\n\nbool aeron_parse_bool(const char *str, bool def)\n{\n    if (NULL != str)\n    {\n        if (strncmp(str, \"1\", 1) == 0 || strncmp(str, \"on\", 2) == 0 || strncmp(str, \"true\", 4) == 0)\n        {\n            return true;\n        }\n\n        if (strncmp(str, \"0\", 1) == 0 || strncmp(str, \"off\", 3) == 0 || strncmp(str, \"false\", 5) == 0)\n        {\n            return false;\n        }\n    }\n\n    return def;\n}\n\nint aeron_address_split(const char *address_str, aeron_parsed_address_t *parsed_address)\n{\n    if (NULL == address_str || '\\0' == *address_str)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"no address value\");\n        return -1;\n    }\n\n    int percent_index = -1;\n    int colon_index = -1;\n    int l_brace_index = -1;\n    int r_brace_index = -1;\n\n    int i = 0;\n    char c = *address_str;\n    while ('\\0' != c)\n    {\n        if (':' == c)\n        {\n            colon_index = i;\n        }\n        else if ('[' == c)\n        {\n            l_brace_index = i;\n        }\n        else if (']' == c)\n        {\n            r_brace_index = i;\n        }\n        else if ('%'  == c)\n        {\n            percent_index = i;\n        }\n\n        ++i;\n        c = *(address_str + i);\n    }\n\n    bool is_ipv6 = false;\n    if (l_brace_index > 0 || r_brace_index > 0)\n    {\n        if (l_brace_index < 0 || r_brace_index < 0 || r_brace_index < l_brace_index)\n        {\n            AERON_SET_ERR(EINVAL, \"host address invalid: %s\", address_str);\n            return -1;\n        }\n\n        is_ipv6 = true;\n        parsed_address->ip_version_hint = 6;\n    }\n    else\n    {\n        parsed_address->ip_version_hint = 4;\n    }\n\n    *parsed_address->port = '\\0';\n    if (colon_index >= 0 && r_brace_index < colon_index)\n    {\n        if (i - 1 == colon_index)\n        {\n            AERON_SET_ERR(EINVAL, \"port invalid: %s\", address_str);\n            return -1;\n        }\n\n        int port_begin = colon_index + 1;\n        int length = i - port_begin;\n\n        if (length >= AERON_MAX_PORT_LENGTH)\n        {\n            AERON_SET_ERR(EINVAL, \"port invalid: %s\", address_str);\n            return -1;\n        }\n\n        strncpy(parsed_address->port, address_str + port_begin, (size_t)length);\n        *(parsed_address->port + length) = '\\0';\n    }\n\n    int length = i;\n\n    if (colon_index >= 0 && colon_index > r_brace_index)\n    {\n        length = colon_index;\n    }\n\n    bool is_scoped = false;\n    if (percent_index >= 0 && percent_index < r_brace_index)\n    {\n        is_scoped = true;\n        length = percent_index;\n    }\n\n    const char *host = is_ipv6 ? address_str + 1 : address_str;\n    if (is_ipv6)\n    {\n        length =  is_scoped ? length -1 : length - 2;\n    }\n\n    if (length >= AERON_MAX_HOST_LENGTH)\n    {\n        AERON_SET_ERR(EINVAL, \"host address invalid: %s\", address_str);\n        return -1;\n    }\n\n    strncpy(parsed_address->host, host, (size_t)length);\n    *(parsed_address->host + length) = '\\0';\n\n    return 0;\n}\n\nint aeron_interface_split(const char *interface_str, aeron_parsed_interface_t *parsed_interface)\n{\n    if (NULL == interface_str || '\\0' == *interface_str)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"no interface value\");\n        return -1;\n    }\n\n    int percent_index = -1;\n    int colon_index = -1;\n    int l_brace_index = -1;\n    int r_brace_index = -1;\n    int slash_index = -1;\n\n    int i = 0;\n    char c = *interface_str;\n    while ('\\0' != c)\n    {\n        if (':' == c)\n        {\n            colon_index = i;\n        }\n        else if ('[' == c)\n        {\n            l_brace_index = i;\n        }\n        else if (']' == c)\n        {\n            r_brace_index = i;\n        }\n        else if ('/'  == c)\n        {\n            slash_index = i;\n        }\n        else if ('%'  == c)\n        {\n            percent_index = i;\n        }\n\n        ++i;\n        c = *(interface_str + i);\n    }\n\n    bool is_ipv6 = false;\n    if (l_brace_index > 0 || r_brace_index > 0)\n    {\n        if (l_brace_index < 0 || r_brace_index < 0 || r_brace_index < l_brace_index)\n        {\n            AERON_SET_ERR(EINVAL, \"host address invalid: %s\", interface_str);\n            return -1;\n        }\n\n        is_ipv6 = true;\n        parsed_interface->ip_version_hint = 6;\n    }\n    else\n    {\n        parsed_interface->ip_version_hint = 4;\n    }\n\n    *parsed_interface->prefix = '\\0';\n    if (slash_index >= 0)\n    {\n        int length = i - slash_index;\n        if (length <= 0)\n        {\n            AERON_SET_ERR(EINVAL, \"subnet prefix invalid: %s\", interface_str);\n            return -1;\n        }\n\n        if (length >= AERON_MAX_PREFIX_LENGTH)\n        {\n            AERON_SET_ERR(EINVAL, \"subnet prefix invalid: %s\", interface_str);\n            return -1;\n        }\n\n        strncpy(parsed_interface->prefix, interface_str + slash_index + 1, (size_t)length);\n        *(parsed_interface->prefix + length) = '\\0';\n    }\n\n    *parsed_interface->port = '\\0';\n    if (colon_index >= 0 && r_brace_index < colon_index)\n    {\n        if (i - 1 == colon_index)\n        {\n            AERON_SET_ERR(EINVAL, \"port invalid: %s\", interface_str);\n            return -1;\n        }\n\n        int port_begin = colon_index + 1;\n        int length = slash_index > 0 ? slash_index - port_begin : i - port_begin;\n\n        if (length >= AERON_MAX_PORT_LENGTH)\n        {\n            AERON_SET_ERR(EINVAL, \"port invalid: %s\", interface_str);\n            return -1;\n        }\n\n        strncpy(parsed_interface->port, interface_str + port_begin, (size_t)length);\n        *(parsed_interface->port + length) = '\\0';\n    }\n\n    int length = i;\n\n    if (slash_index >= 0)\n    {\n        length = slash_index;\n    }\n\n    if (colon_index >= 0 && colon_index > r_brace_index)\n    {\n        length = colon_index;\n    }\n\n    bool is_scoped = false;\n    if (percent_index >= 0 && percent_index < r_brace_index)\n    {\n        length = percent_index;\n        is_scoped = true;\n    }\n\n    const char *host = is_ipv6 ? interface_str + 1 : interface_str;\n    if (is_ipv6)\n    {\n        length =  is_scoped ? length -1 : length - 2;\n    }\n\n    if (length >= AERON_MAX_HOST_LENGTH)\n    {\n        AERON_SET_ERR(EINVAL, \"host address invalid: %s\", interface_str);\n        return -1;\n    }\n\n    strncpy(parsed_interface->host, host, (size_t)length);\n    *(parsed_interface->host + length) = '\\0';\n\n    return 0;\n}\n\nint aeron_parse_get_line(char *str, size_t max_length, const char *buffer)\n{\n    size_t i;\n\n    for (i = 0; i < max_length - 1; i++)\n    {\n        str[i] = buffer[i];\n        if ('\\0' == buffer[i])\n        {\n            return 0;\n        }\n\n        if ('\\n' == buffer[i])\n        {\n            str[i + 1] = '\\0';\n            return (int)(i + 1);\n        }\n    }\n\n    AERON_SET_ERR(EINVAL, \"line too long: %\" PRIu64 \"/%\" PRIu64, (uint64_t)i, (uint64_t)max_length);\n    return -1;\n}\n\nvoid aeron_config_prop_warning(const char *name, const char *str)\n{\n    char buffer[AERON_ERROR_MAX_TOTAL_LENGTH];\n    snprintf(buffer, sizeof(buffer) - 1, \"WARNING: %s=%s is invalid, using default\\n\", name, str);\n    fprintf(stderr, \"%s\", buffer);\n}\n\nuint64_t aeron_config_parse_uint64(const char *name, const char *str, uint64_t def, uint64_t min, uint64_t max)\n{\n    uint64_t result = def;\n\n    if (NULL != str)\n    {\n        errno = 0;\n        char *end_ptr = NULL;\n        uint64_t value = strtoull(str, &end_ptr, 0);\n\n        if ((0 == value && 0 != errno) || end_ptr == str)\n        {\n            aeron_config_prop_warning(name, str);\n            value = def;\n        }\n\n        result = value;\n        result = result > max ? max : result;\n        result = result < min ? min : result;\n    }\n\n    return result;\n}\n\nint32_t aeron_config_parse_int32(const char *name, const char *str, int32_t def, int32_t min, int32_t max)\n{\n    int32_t result = def;\n\n    if (NULL != str)\n    {\n        errno = 0;\n        char *end_ptr = NULL;\n        long long value = strtoll(str, &end_ptr, 0);\n\n        if ((0 == value && 0 != errno) || '\\0' != *end_ptr || value < INT32_MIN || INT32_MAX < value)\n        {\n            aeron_config_prop_warning(name, str);\n            value = def;\n        }\n\n        result = value > max ? max : (int32_t)value;\n        result = value < min ? min : result;\n    }\n\n    return result;\n}\n\nint64_t aeron_config_parse_int64(const char *name, const char *str, int64_t def, int64_t min, int64_t max)\n{\n    int64_t result = def;\n\n    if (NULL != str)\n    {\n        errno = 0;\n        char *end_ptr = NULL;\n        int64_t value = strtoll(str, &end_ptr, 0);\n\n        if ((0 == value && 0 != errno) || '\\0' != *end_ptr)\n        {\n            aeron_config_prop_warning(name, str);\n            value = def;\n        }\n\n        result = value > max ? max : value;\n        result = value < min ? min : result;\n    }\n\n    return result;\n}\n\nuint32_t aeron_config_parse_uint32(const char *name, const char *str, uint32_t def, uint32_t min, uint32_t max)\n{\n    return (uint32_t)aeron_config_parse_int64(name, str, def, min, max);\n}\n\nuint64_t aeron_config_parse_size64(const char *name, const char *str, uint64_t def, uint64_t min, uint64_t max)\n{\n    uint64_t result = def;\n\n    if (NULL != str)\n    {\n        uint64_t value = 0;\n\n        if (-1 == aeron_parse_size64(str, &value))\n        {\n            aeron_config_prop_warning(name, str);\n        }\n        else\n        {\n            result = value;\n            result = result > max ? max : result;\n            result = result < min ? min : result;\n        }\n    }\n\n    return result;\n}\n\nuint64_t aeron_config_parse_duration_ns(const char *name, const char *str, uint64_t def, uint64_t min, uint64_t max)\n{\n    uint64_t result = def;\n\n    if (NULL != str)\n    {\n        uint64_t value = 0;\n\n        if (-1 == aeron_parse_duration_ns(str, &value))\n        {\n            aeron_config_prop_warning(name, str);\n        }\n        else\n        {\n            result = value;\n            result = result > max ? max : result;\n            result = result < min ? min : result;\n        }\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_parse_util.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_PARSE_UTIL_H\n#define AERON_PARSE_UTIL_H\n\n#include <stdint.h>\n#include <stdbool.h>\n#include <stddef.h>\n\n#define AERON_MAX_HOST_LENGTH (384)\n#define AERON_MAX_PORT_LENGTH (8)\n#define AERON_MAX_PREFIX_LENGTH (8)\n\ntypedef struct aeron_parsed_address_stct\n{\n    char host[AERON_MAX_HOST_LENGTH];\n    char port[AERON_MAX_PORT_LENGTH];\n    int ip_version_hint;\n}\naeron_parsed_address_t;\n\ntypedef struct aeron_parsed_interface_stct\n{\n    char host[AERON_MAX_HOST_LENGTH];\n    char port[AERON_MAX_PORT_LENGTH];\n    char prefix[AERON_MAX_PREFIX_LENGTH];\n    int ip_version_hint;\n}\naeron_parsed_interface_t;\n\nint aeron_parse_size64(const char *str, uint64_t *result);\nint aeron_format_size64(uint64_t value, char *buffer, size_t buffer_size);\n\nint aeron_parse_duration_ns(const char *str, uint64_t *result);\nint aeron_format_duration_ns(uint64_t duration_ns, char *buffer, size_t buffer_size);\n\nbool aeron_parse_bool(const char *str, bool def);\n\nint aeron_address_split(const char *address_str, aeron_parsed_address_t *parsed_address);\n\nint aeron_interface_split(const char *interface_str, aeron_parsed_interface_t *parsed_interface);\n\nint aeron_parse_get_line(char *line, size_t max_length, const char *buffer);\n\nvoid aeron_config_prop_warning(const char *name, const char *str);\n\nuint64_t aeron_config_parse_uint64(const char *name, const char *str, uint64_t def, uint64_t min, uint64_t max);\n\nint32_t aeron_config_parse_int32(const char *name, const char *str, int32_t def, int32_t min, int32_t max);\n\nint64_t aeron_config_parse_int64(const char *name, const char *str, int64_t def, int64_t min, int64_t max);\n\nuint32_t aeron_config_parse_uint32(const char *name, const char *str, uint32_t def, uint32_t min, uint32_t max);\n\nuint64_t aeron_config_parse_size64(const char *name, const char *str, uint64_t def, uint64_t min, uint64_t max);\n\nuint64_t aeron_config_parse_duration_ns(const char *name, const char *str, uint64_t def, uint64_t min, uint64_t max);\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_platform.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_DRIVER_PLATFORM_H\n#define AERON_DRIVER_PLATFORM_H\n\n/*\n * Determine platform, compiler, and CPU and set defines to be used later.\n * Also, error out here if on a platform that is not supported.\n */\n\n#if defined(_MSC_VER)\n#define AERON_COMPILER_MSVC 1\n\n#if defined(_M_X64)\n#define AERON_CPU_X64 1\n#else\n#error Unsupported CPU!\n#endif\n\n#define _Static_assert static_assert\n#elif defined(__GNUC__)\n#define AERON_COMPILER_GCC 1\n\n#if defined(__llvm__)\n#define AERON_COMPILER_LLVM 1\n#endif\n\n#if defined(__x86_64__)\n#define AERON_CPU_X64 1\n#endif\n\n#if defined(__aarch64__)\n#define AERON_CPU_ARM 1\n#if defined(__STDC_NO_ATOMICS__)\n#error C11 atomics are required to compile for aarch64!\n#endif\n#endif\n\n#if defined(__cplusplus)\n#define _Static_assert static_assert\n#endif\n\n#else\n#error Unsupported compiler!\n#endif\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_properties_util.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <inttypes.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <ctype.h>\n#include \"aeron_properties_util.h\"\n#include \"aeron_error.h\"\n#include \"aeron_parse_util.h\"\n#include \"aeron_http_util.h\"\n#include \"util/aeron_env.h\"\n\nint aeron_next_non_whitespace(const char *buffer, size_t start, size_t end)\n{\n    for (size_t i = start; i <= end; i++)\n    {\n        const char c = buffer[i];\n\n        if (c == ' ' || c == '\\t')\n        {\n            continue;\n        }\n\n        return '\\0' == c ? -1 : (int)i;\n    }\n\n    return -1;\n}\n\n/*\n * Format taken from\n * https://docs.oracle.com/cd/E23095_01/Platform.93/ATGProgGuide/html/s0204propertiesfileformat01.html\n */\nint aeron_properties_parse_line(\n    aeron_properties_parser_state_t *state,\n    const char *line,\n    size_t length,\n    aeron_properties_file_handler_func_t handler,\n    void *clientd)\n{\n    bool in_name = 0 < state->name_end ? false : true;\n    int value_start = 0, result = 0;\n\n    if (length >= (sizeof(state->property_str) - state->value_end))\n    {\n        AERON_SET_ERR(\n            EINVAL, \"line length of %\" PRIu64 \" too long for parser state\", (uint64_t)(length + state->value_end));\n        return -1;\n    }\n\n    if (in_name)\n    {\n        int cursor = aeron_next_non_whitespace(line, 0, length - 1);\n\n        if (-1 == cursor || '!' == line[cursor] || '#' == line[cursor])\n        {\n            return 0;\n        }\n\n        for (size_t i = (size_t)cursor; i < length; i++)\n        {\n            const char c = line[i];\n\n            if (':' == c || '=' == c)\n            {\n                state->property_str[state->name_end] = '\\0';\n                value_start = (int)i + 1;\n\n                /* trim back for whitespace after name */\n                for (int j = (int)i - 1; j >= 0; j--)\n                {\n                    if (' ' != line[j] && '\\t' != line[j])\n                    {\n                        break;\n                    }\n\n                    state->property_str[--state->name_end] = '\\0';\n                }\n\n                state->value_end = state->name_end + 1;\n                break;\n            }\n\n            state->property_str[state->name_end++] = c;\n        }\n\n        if (0 == state->value_end || 0 == state->name_end)\n        {\n            AERON_SET_ERR(EINVAL, \"%s\", \"malformed line\");\n            aeron_properties_parse_init(state);\n            return -1;\n        }\n\n        value_start = aeron_next_non_whitespace(line, (size_t)value_start, length - 1);\n\n        if (-1 == value_start)\n        {\n            state->property_str[state->value_end++] = '\\0';\n\n            result = handler(clientd, state->property_str, state->property_str + state->name_end + 1);\n\n            aeron_properties_parse_init(state);\n            return result;\n        }\n    }\n    else\n    {\n        value_start = aeron_next_non_whitespace(line, (size_t)value_start, length - 1);\n\n        if (-1 == value_start || '!' == line[value_start] || '#' == line[value_start])\n        {\n            return 0;\n        }\n    }\n\n    if ('\\\\' == line[length - 1])\n    {\n        memcpy(state->property_str + state->value_end, line + value_start, length - value_start - 1);\n        state->value_end += length - value_start - 1;\n    }\n    else\n    {\n        memcpy(state->property_str + state->value_end, line + value_start, length - value_start);\n        state->value_end += length - value_start;\n        state->property_str[state->value_end++] = '\\0';\n\n        result = handler(clientd, state->property_str, state->property_str + state->name_end + 1);\n\n        aeron_properties_parse_init(state);\n    }\n\n    return result;\n}\n\nint aeron_properties_setenv(const char *name, const char *value)\n{\n    char env_name[AERON_PROPERTIES_MAX_LENGTH];\n\n    for (size_t i = 0; i < sizeof(env_name); i++)\n    {\n        const char c = name[i];\n\n        if ('.' == c)\n        {\n            env_name[i] = '_';\n        }\n        else if ('\\0' == c)\n        {\n            env_name[i] = c;\n            break;\n        }\n        else\n        {\n            env_name[i] = (char)toupper(c);\n        }\n    }\n\n    if ('\\0' == *value)\n    {\n        aeron_env_unset(env_name);\n    }\n    else\n    {\n        aeron_env_set(env_name, value);\n    }\n\n    return 0;\n}\n\nint aeron_properties_setenv_property(void *clientd, const char *name, const char *value)\n{\n    return aeron_properties_setenv(name, value);\n}\n\nint aeron_properties_file_load(const char *filename)\n{\n    return aeron_properties_parse_file(filename, aeron_properties_setenv_property, NULL);\n}\n\nint aeron_properties_parse_file(const char *filename, aeron_properties_file_handler_func_t handler, void *clientd)\n{\n    int result = -1, lineno = 1;\n    char line[AERON_PROPERTIES_MAX_LENGTH];\n    aeron_properties_parser_state_t state;\n\n    FILE *fpin = fopen(filename, \"r\");\n    if (NULL == fpin)\n    {\n        AERON_SET_ERR(errno, \"could not open properties file: %s\", filename);\n        return -1;\n    }\n\n    aeron_properties_parse_init(&state);\n\n    while (fgets(line, sizeof(line), fpin) != NULL)\n    {\n        size_t length = strlen(line);\n\n        if ('\\n' == line[length - 1])\n        {\n            line[length - 1] = '\\0';\n            length--;\n\n            if ('\\r' == line[length - 1])\n            {\n                line[length - 1] = '\\0';\n                length--;\n            }\n\n            if (aeron_properties_parse_line(&state, line, length, handler, clientd) < 0)\n            {\n                AERON_SET_ERR(EINVAL, \"properties file line %\" PRId32 \" malformed\", lineno);\n                goto cleanup;\n            }\n        }\n        else\n        {\n            AERON_SET_ERR(EINVAL, \"properties file line %\" PRId32 \" too long or does not end with newline\", lineno);\n            goto cleanup;\n        }\n\n        lineno++;\n    }\n\n    if (!feof(fpin))\n    {\n        AERON_SET_ERR(errno, \"error reading file: %s\", filename);\n        goto cleanup;\n    }\n    else\n    {\n        result = 0;\n    }\n\n    cleanup:\n    fclose(fpin);\n\n    return result;\n}\n\nint aeron_properties_buffer_load(const char *buffer)\n{\n    char line[AERON_PROPERTIES_MAX_LENGTH];\n    int line_length, cursor = 0, lineno = 1;\n    aeron_properties_parser_state_t state;\n\n    aeron_properties_parse_init(&state);\n\n    while ((line_length = aeron_parse_get_line(line, sizeof(line), buffer + cursor)) > 0)\n    {\n        cursor += line_length;\n\n        if ('\\n' == line[line_length - 1])\n        {\n            line[line_length - 1] = '\\0';\n            line_length--;\n\n            if ('\\r' == line[line_length - 1])\n            {\n                line[line_length - 1] = '\\0';\n                line_length--;\n            }\n\n            if (aeron_properties_parse_line(\n                &state, line, (size_t)line_length, aeron_properties_setenv_property, NULL) < 0)\n            {\n                AERON_SET_ERR(EINVAL, \"properties buffer line %\" PRId32 \" malformed\", lineno);\n                return -1;\n            }\n        }\n        else\n        {\n            AERON_SET_ERR(EINVAL, \"properties buffer line %\" PRId32 \" too long or does not end with newline\", lineno);\n            return -1;\n        }\n\n        lineno++;\n    }\n\n    if (line_length < 0)\n    {\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_properties_http_load(const char *url)\n{\n    char new_url[AERON_MAX_HTTP_URL_LENGTH];\n    aeron_http_response_t *response = NULL;\n    int remaining_redirects = 1, result = -1;\n\n    do\n    {\n        if (aeron_http_retrieve(&response, url, AERON_HTTP_PROPERTIES_TIMEOUT_NS) < 0)\n        {\n            return -1;\n        }\n\n        if (200 == response->status_code)\n        {\n            break;\n        }\n        else if (301 == response->status_code || 302 == response->status_code)\n        {\n            if (remaining_redirects-- > 0)\n            {\n\n                switch (aeron_http_header_get(response, \"Location:\", new_url, sizeof(new_url)))\n                {\n                    case -1:\n                        goto cleanup;\n\n                    case 0:\n                        AERON_SET_ERR(EINVAL, \"%s\", \"redirect specified, but no Location header found\");\n                        goto cleanup;\n\n                    default:\n                    {\n                        url = new_url + strlen(\"Location:\");\n\n                        while ('\\0' != *url && (' ' == *url || '\\t' == *url))\n                        {\n                            url++;\n                        }\n\n                        aeron_http_response_delete(response);\n                        response = NULL;\n                        break;\n                    }\n                }\n            }\n            else\n            {\n                AERON_SET_ERR(EINVAL, \"%s\", \"too many redirects for URL\");\n                goto cleanup;\n            }\n        }\n        else\n        {\n            AERON_SET_ERR(EINVAL, \"status code %\" PRIu64 \" from HTTP GET\", (uint64_t)response->status_code);\n            goto cleanup;\n        }\n    }\n    while (true);\n\n    result = aeron_properties_buffer_load(response->buffer + response->body_offset);\n\ncleanup:\n    aeron_http_response_delete(response);\n\n    return result;\n}\n\nint aeron_properties_load(const char *url_or_filename)\n{\n    int result;\n\n    if (strncmp(\"file://\", url_or_filename, strlen(\"file://\")) == 0)\n    {\n        result = aeron_properties_file_load(url_or_filename + strlen(\"file://\"));\n    }\n    // codeql[cpp/user-controlled-bypass]\n    else if (strncmp(\"http://\", url_or_filename, strlen(\"http://\")) == 0)\n    {\n        result = aeron_properties_http_load(url_or_filename);\n    }\n    else\n    {\n        result = aeron_properties_file_load(url_or_filename);\n    }\n\n    return result;\n}\n\nextern void aeron_properties_parse_init(aeron_properties_parser_state_t *state);\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_properties_util.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_PROPERTIES_UTIL_H\n#define AERON_PROPERTIES_UTIL_H\n\n#include <stddef.h>\n#include <stdbool.h>\n#include <stdint.h>\n\n#define AERON_PROPERTIES_MAX_LENGTH 2048\n\ntypedef struct aeron_properties_parser_state_stct\n{\n    char property_str[AERON_PROPERTIES_MAX_LENGTH];\n    size_t name_end;\n    size_t value_end;\n}\naeron_properties_parser_state_t;\n\ntypedef int (*aeron_properties_file_handler_func_t)(\n    void *clientd, const char *property_name, const char *property_value);\n\ninline void aeron_properties_parse_init(aeron_properties_parser_state_t *state)\n{\n    state->name_end = 0;\n    state->value_end = 0;\n}\n\nint aeron_properties_parse_line(\n    aeron_properties_parser_state_t *state,\n    const char *line,\n    size_t length,\n    aeron_properties_file_handler_func_t handler,\n    void *clientd);\n\nint aeron_properties_parse_file(const char *filename, aeron_properties_file_handler_func_t handler, void *clientd);\n\nint aeron_properties_setenv(const char *name, const char *value);\n\n#define AERON_HTTP_PROPERTIES_TIMEOUT_NS (10 * 1000 * 1000 * 1000LL)\n\n#endif //AERON_PROPERTIES_UTIL_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_strutil.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <time.h>\n#include <stdio.h>\n#include <inttypes.h>\n#include <errno.h>\n#include <string.h>\n#include <locale.h>\n#include <stdlib.h>\n\n#define AERON_DLL_EXPORTS\n\n#include \"util/aeron_strutil.h\"\n\nvoid aeron_format_date(char *str, size_t count, int64_t timestamp)\n{\n    char time_buffer[80];\n    char msec_buffer[8];\n    char tz_buffer[8];\n    struct tm time;\n    time_t just_seconds = timestamp / 1000;\n    int64_t msec_after_sec = timestamp % 1000;\n\n    localtime_r(&just_seconds, &time);\n\n    strftime(time_buffer, sizeof(time_buffer) - 1, \"%Y-%m-%d %H:%M:%S.\", &time);\n    snprintf(msec_buffer, sizeof(msec_buffer) - 1, \"%03\" PRId64, msec_after_sec);\n    strftime(tz_buffer, sizeof(tz_buffer) - 1, \"%z\", &time);\n\n    snprintf(str, count, \"%s%s%s\", time_buffer, msec_buffer, tz_buffer);\n}\n\nstatic size_t aeron_format_min(size_t a, size_t b)\n{\n    return a < b ? a : b;\n}\n\nstatic size_t aeron_format_number_next(long long value, const char *sep, char *buffer, size_t buffer_len)\n{\n    if (0 <= value && value < 1000)\n    {\n        return snprintf(buffer, aeron_format_min(4, buffer_len), \"%lld\", value);\n    }\n    else if (-1000 < value && value < 0)\n    {\n        return snprintf(buffer, aeron_format_min(5, buffer_len), \"%lld\", value);\n    }\n    else\n    {\n        size_t printed_len = aeron_format_number_next(value / 1000, sep, buffer, buffer_len);\n        return printed_len + snprintf(\n            buffer + printed_len, aeron_format_min(5, buffer_len - printed_len), \"%s%03lld\", sep, llabs(value % 1000));\n    }\n}\n\n/**\n * Uses locale specific thousands separator to format the number, or \",\" if none found. Will truncate to buffer_len - 1\n * and null terminate. Use AERON_FORMAT_NUMBER_TO_LOCALE_STR_LEN for the buffer size to prevent truncations. Works for\n * string lengths up to INT64_MIN.\n *\n * @param value Value for format\n * @param buffer buffer to use\n * @param buffer_len length of buffer\n * @return NULL terminated buffer containing formatted string.\n */\nchar *aeron_format_number_to_locale(long long value, char *buffer, size_t buffer_len)\n{\n    setlocale(LC_NUMERIC, \"\");\n    const char *sep = 1 == strlen(localeconv()->thousands_sep) ? localeconv()->thousands_sep : \",\";\n    aeron_format_number_next(value, sep, buffer, buffer_len);\n    buffer[buffer_len - 1] = '\\0';\n    return buffer;\n}\n\nvoid aeron_format_to_hex(char *str, size_t str_length, const uint8_t *data, size_t data_len)\n{\n    static char table[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };\n\n    size_t j = 0;\n\n    for (size_t i = 0; i < data_len && j < str_length; i++)\n    {\n        char c_high = table[(data[i] >> 4) & 0x0F];\n        char c_low = table[data[i] & 0x0F];\n\n        str[j++] = c_high;\n        str[j++] = c_low;\n    }\n\n    str[j] = '\\0';\n}\n\nint aeron_tokenise(char *input, char delimiter, int max_tokens, char **tokens)\n{\n    if (NULL == input)\n    {\n        return -EINVAL;\n    }\n\n    const size_t len = strlen(input);\n\n    if (INT32_MAX < len)\n    {\n        return -EINVAL;\n    }\n\n    if (0 == len)\n    {\n        return 0;\n    }\n\n    int num_tokens = 0;\n\n    for (int i = (int)len; --i != -1;)\n    {\n        if (delimiter == input[i])\n        {\n            input[i] = '\\0';\n        }\n\n        if (0 == i && '\\0' != input[i])\n        {\n            if (max_tokens <= num_tokens)\n            {\n                num_tokens = -ERANGE;\n                break;\n            }\n\n            tokens[num_tokens] = &input[i];\n            num_tokens++;\n        }\n        else if ('\\0' == input[i] && '\\0' != input[i + 1])\n        {\n            if (max_tokens <= num_tokens)\n            {\n                num_tokens = -ERANGE;\n                break;\n            }\n\n            tokens[num_tokens] = &input[i + 1];\n            num_tokens++;\n        }\n    }\n\n    return num_tokens;\n}\n\nint aeron_digit_count(uint32_t value)\n{\n    static uint64_t table[] = {\n        4294967296,  8589934582,  8589934582,  8589934582,  12884901788,\n        12884901788, 12884901788, 17179868184, 17179868184, 17179868184,\n        21474826480, 21474826480, 21474826480, 21474826480, 25769703776,\n        25769703776, 25769703776, 30063771072, 30063771072, 30063771072,\n        34349738368, 34349738368, 34349738368, 34349738368, 38554705664,\n        38554705664, 38554705664, 41949672960, 41949672960, 41949672960,\n        42949672960, 42949672960 };\n    int log2 = 63 - aeron_number_of_leading_zeroes_u64((uint64_t)value | 1);\n    return (int)((value + table[log2]) >> 32);\n}\n\n#if defined(AERON_COMPILER_MSVC)\n\nchar *aeron_strndup(const char *value, size_t length)\n{\n    size_t str_length = strlen(value);\n    char *dup = NULL;\n\n    str_length = (str_length > length) ? length : str_length;\n    if (aeron_alloc((void **)&dup, str_length + 1) < 0)\n    {\n        errno = ENOMEM;\n        return NULL;\n    }\n\n    strncpy(dup, value, str_length);\n    dup[str_length] = '\\0';\n    return dup;\n}\n\n#ifndef AERON_NO_GETOPT\n\n// Taken and modified from https://www.codeproject.com/KB/cpp/xgetopt/XGetopt_demo.zip\n// *****************************************************************\n//\n// Author:  Hans Dietrich\n//          hdietrich2@hotmail.com\n//\n// This software is released into the public domain.\n// You are free to use it in any way you like.\n//\n// This software is provided \"as is\" with no expressed\n// or implied warranty.  I accept no liability for any\n// damage or loss of business that this software may cause.\n//\n// ******************************************************************\n\nAERON_EXPORT char *optarg = NULL;\nAERON_EXPORT int optind = 1;\n\nint getopt(int argc, char *const argv[], const char *opt_string)\n{\n    static char *next = NULL;\n    if (optind == 0)\n    {\n        next = NULL;\n    }\n\n    optarg = NULL;\n\n    if (next == NULL || *next == '\\0')\n    {\n        if (optind == 0)\n        {\n            optind++;\n        }\n\n        if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\\0')\n        {\n            optarg = NULL;\n            if (optind < argc)\n            {\n                optarg = argv[optind];\n            }\n\n            return EOF;\n        }\n\n        if (strcmp(argv[optind], \"--\") == 0)\n        {\n            optind++;\n            optarg = NULL;\n            if (optind < argc)\n            {\n                optarg = argv[optind];\n            }\n\n            return EOF;\n        }\n\n        next = argv[optind];\n        next++;\n        optind++;\n    }\n\n    char c = *next++;\n    char *cp = strchr(opt_string, c);\n\n    if (cp == NULL || c == ':')\n    {\n        return '?';\n    }\n\n    cp++;\n    if (*cp == ':')\n    {\n        if (*next != '\\0')\n        {\n            optarg = next;\n            next = NULL;\n        }\n        else if (optind < argc)\n        {\n            optarg = argv[optind];\n            optind++;\n        }\n        else\n        {\n            return '?';\n        }\n    }\n\n    return c;\n}\n#endif\n\n#endif\n\nextern uint64_t aeron_fnv_64a_buf(uint8_t *buf, size_t len);\nextern bool aeron_str_length(const char *str, size_t length_bound, size_t *length);\nextern void aeron_str_null_terminate(uint8_t *text, int index);\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_strutil.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_STRUTIL_H\n#define AERON_STRUTIL_H\n\n#include <stdint.h>\n#include <stddef.h>\n#include <stdbool.h>\n\n#include \"aeron_bitutil.h\"\n#include \"util/aeron_platform.h\"\n\n#define AERON_FORMAT_DATE_MAX_LENGTH (100)\n#define AERON_FORMAT_NUMBER_TO_LOCALE_STR_LEN (32)\n#define AERON_FORMAT_HEX_LENGTH(b) ((2 * (b)) + 1)\n\n#if defined(AERON_DLL_EXPORTS)\n#define AERON_EXPORT __declspec(dllexport)\n#else\n#define AERON_EXPORT __declspec(dllimport)\n#endif\n\n#if defined(AERON_COMPILER_GCC)\n\n#define aeron_strndup strndup\n\n#elif defined(AERON_COMPILER_MSVC)\n\nchar *aeron_strndup(const char *value, size_t length);\n\n#ifndef AERON_NO_GETOPT\nAERON_EXPORT extern char *optarg;\nAERON_EXPORT extern int optind;\n\nint getopt(int argc, char *const argv[], const char *opt_string);\n#endif\n\n#endif\n\nvoid aeron_format_date(char *str, size_t count, int64_t timestamp);\n\nchar *aeron_format_number_to_locale(long long value, char *buffer, size_t buffer_len);\n\nvoid aeron_format_to_hex(char *str, size_t str_length, const uint8_t *data, size_t data_len);\n\n/*\n * FNV-1a hash function\n *\n * http://www.isthe.com/chongo/tech/comp/fnv/index.html\n */\ninline uint64_t aeron_fnv_64a_buf(uint8_t *buf, size_t len)\n{\n    uint8_t *bp = buf;\n    uint8_t *be = bp + len;\n    uint64_t hval = 0xcbf29ce484222325ULL;\n\n    while (bp < be)\n    {\n        hval ^= (uint64_t)*bp++;\n#if defined(__GNUC__)\n        hval += (hval << 1) + (hval << 4) + (hval << 5) + (hval << 7) + (hval << 8) + (hval << 40);\n#else\n        hval *= ((uint64_t)0x100000001b3ULL);\n#endif\n    }\n\n    return hval;\n}\n\n/*\n * Splits a null terminated string using the delimiter specified, which is replaced with \\0 characters.\n * Each of the tokens is stored in reverse order in the tokens array.\n *\n * Returns the number of tokens found. Or a value < 0 for an error:\n * ERANGE: number of tokens is greater than max_tokens.\n */\nint aeron_tokenise(char *input, char delimiter, int max_tokens, char **tokens);\n\n/**\n * Checks that the string length is strictly less than the specified bound.\n *\n * @param str           String to be compared.\n * @param length_bound  Limit to the length of the string being checked.\n * @param length        Out parameter for the actual length of the string (if non-null and less than length_bound).\n *                      NULL values for this parameter are permitted.  If str is NULL this value is unmodified.  If\n *                      the function returns false, the value is also unmodified.\n * @return              true if less than the specified bound, NULL is always true.\n */\ninline bool aeron_str_length(const char *str, size_t length_bound, size_t *length)\n{\n    if (NULL == str)\n    {\n        return true;\n    }\n\n    size_t i = 0;\n    bool result = false;\n    for (; i < length_bound; i++)\n    {\n        if ('\\0' == str[i])\n        {\n            result = true;\n            break;\n        }\n    }\n\n    if (result && NULL != length)\n    {\n        *length = i;\n    }\n\n    return result;\n}\n\ninline void aeron_str_null_terminate(uint8_t *text, int index)\n{\n    text[index] = '\\0';\n}\n\nint aeron_digit_count(uint32_t value);\n\n#endif //AERON_STRUTIL_H\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_symbol_table.c",
    "content": "/*\n * Copyright 2014-2021 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdbool.h>\n#include <errno.h>\n\n#include \"util/aeron_dlopen.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_symbol_table.h\"\n\n\nstatic void* aeron_symbol_table_obj_scan(const aeron_symbol_table_obj_t *table, size_t table_size, const char *symbol)\n{\n    void* result = NULL;\n\n    for (size_t i = 0; i < table_size; i++)\n    {\n        const char *alias = table[(int)i].alias;\n        const char *name = table[(int)i].name;\n\n        if (NULL == alias || NULL == name)\n        {\n            break;\n        }\n\n        if (0 == strncmp(alias, symbol, AERON_SYMBOL_TABLE_NAME_MAX_LENGTH + 1) ||\n            0 == strncmp(name, symbol, AERON_SYMBOL_TABLE_NAME_MAX_LENGTH + 1))\n        {\n            result = table[i].object;\n            break;\n        }\n    }\n\n    return result;\n}\n\nvoid* aeron_symbol_table_obj_load(\n    const aeron_symbol_table_obj_t *table, size_t table_length, const char *name, const char *component_name)\n{\n    if (NULL == name)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"name must not be null\");\n        return NULL;\n    }\n\n    if (NULL == component_name)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"component_name must not be null\");\n        return NULL;\n    }\n\n    if (!aeron_str_length(name, AERON_SYMBOL_TABLE_NAME_MAX_LENGTH + 1, NULL))\n    {\n        AERON_SET_ERR(EINVAL, \"name must not exceed %d characters\", AERON_SYMBOL_TABLE_NAME_MAX_LENGTH);\n        return NULL;\n    }\n\n    void *obj = aeron_symbol_table_obj_scan(table, table_length, name);\n    if (NULL == obj)\n    {\n        char copied_name[AERON_SYMBOL_TABLE_NAME_MAX_LENGTH + 1] = { 0 };\n        snprintf(copied_name, sizeof(copied_name), \"%s\", name);\n        obj = aeron_dlsym(RTLD_DEFAULT, copied_name);\n    }\n\n    if (NULL == obj)\n    {\n        AERON_SET_ERR(EINVAL, \"could not find %s object %s: dlsym - %s\", component_name, name, aeron_dlerror());\n        return NULL;\n    }\n\n    return obj;\n}\n\n\nstatic aeron_fptr_t aeron_symbol_table_func_scan(\n    const aeron_symbol_table_func_t *table, size_t table_length, const char *symbol)\n{\n    aeron_fptr_t result = NULL;\n\n    for (size_t i = 0; i < table_length; i++)\n    {\n        const char *alias = table[(int)i].alias;\n        const char *name = table[(int)i].name;\n\n        if (NULL == alias || NULL == name)\n        {\n            break;\n        }\n\n        if (0 == strncmp(alias, symbol, AERON_SYMBOL_TABLE_NAME_MAX_LENGTH + 1) ||\n            0 == strncmp(name, symbol, AERON_SYMBOL_TABLE_NAME_MAX_LENGTH + 1))\n        {\n            result = table[i].function;\n            break;\n        }\n    }\n\n    return result;\n}\n\naeron_fptr_t aeron_symbol_table_func_load(\n    const aeron_symbol_table_func_t *table,\n    size_t table_length,\n    const char *name,\n    const char *component_name)\n{\n    if (NULL == name)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"name must not be null\");\n        return NULL;\n    }\n\n    if (NULL == component_name)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"component_name must not be null\");\n        return NULL;\n    }\n\n    if (!aeron_str_length(name, AERON_SYMBOL_TABLE_NAME_MAX_LENGTH + 1, NULL))\n    {\n        AERON_SET_ERR(EINVAL, \"name must not exceed %d characters\", AERON_SYMBOL_TABLE_NAME_MAX_LENGTH);\n        return NULL;\n    }\n\n    aeron_fptr_t func = aeron_symbol_table_func_scan(table, table_length, name);\n\n    if (NULL == func)\n    {\n        *(void **)(&func) = aeron_dlsym(RTLD_DEFAULT, name);\n    }\n\n    if (NULL == func)\n    {\n        AERON_SET_ERR(EINVAL, \"could not find %s %s: dlsym - %s\", component_name, name, aeron_dlerror());\n    }\n\n    return func;\n}\n"
  },
  {
    "path": "aeron-client/src/main/c/util/aeron_symbol_table.h",
    "content": "/*\n * Copyright 2014-2021 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_SYMBOL_TABLE_H\n#define AERON_SYMBOL_TABLE_H\n\n#include \"aeron_common.h\"\n\n#define AERON_SYMBOL_TABLE_NAME_MAX_LENGTH (1023)\n\nstruct aeron_symbol_table_obj_stct\n{\n    const char *alias;\n    const char *name;\n    void *object;\n};\ntypedef struct aeron_symbol_table_obj_stct aeron_symbol_table_obj_t;\n\nvoid* aeron_symbol_table_obj_load(\n    const aeron_symbol_table_obj_t *table, size_t table_length, const char *name, const char *component_name);\n\nstruct aeron_symbol_table_func_stct\n{\n    const char *alias;\n    const char *name;\n    aeron_fptr_t function;\n};\ntypedef struct aeron_symbol_table_func_stct aeron_symbol_table_func_t;\n\naeron_fptr_t aeron_symbol_table_func_load(\n    const aeron_symbol_table_func_t *table,\n    size_t table_length,\n    const char *name,\n    const char *component_name);\n\n#endif // AERON_SYMBOL_TABLE_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/Aeron.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef INCLUDED_AERON_H\n#define INCLUDED_AERON_H\n\n#include <unordered_map>\n#include <mutex>\n\n#include \"util/Exceptions.h\"\n#include \"concurrent/AgentInvoker.h\"\n#include \"Publication.h\"\n#include \"ExclusivePublication.h\"\n#include \"Subscription.h\"\n#include \"Context.h\"\n#include \"Counter.h\"\n#include \"ClientConductor.h\"\n\n#include \"aeronc.h\"\n\n/// Top namespace for Aeron C++ API\nnamespace aeron\n{\n\nusing namespace aeron::util;\nusing namespace aeron::concurrent;\n\nusing AsyncAddPublication = aeron_async_add_publication_t;\nusing AsyncAddExclusivePublication = aeron_async_add_exclusive_publication_t;\nusing AsyncAddCounter = aeron_async_add_counter_t;\n\n/**\n * @example BasicPublisher.cpp\n * An example of a basic publishing application\n *\n * @example BasicSubscriber.cpp\n * An example of a basic subscribing application\n */\n\n/**\n * Aeron entry point for communicating to the Media Driver for creating {@link Publication}s and {@link Subscription}s.\n * Use a {@link Context} to configure the Aeron object.\n * <p>\n * A client application requires only one Aeron object per Media Driver.\n */\nclass Aeron\n{\npublic:\n    /**\n     * Create an Aeron instance and connect to the media driver.\n     * <p>\n     * Threads required for interacting with the media driver are created and managed within the Aeron instance.\n     * <p>\n     * Note that the ownership of the Context object is passed over to the Aeron instance and the context object is\n     * invalid after calling this constructor.\n     *\n     * @param context for configuration of the client.\n     */\n    explicit Aeron(Context &context) :\n        m_context(context.conclude()),\n        m_aeron(Aeron::init_aeron(m_context, &m_aeron_context)),\n        m_countersReader(aeron_counters_reader(m_aeron)),\n        m_clientConductor(m_aeron),\n        m_conductorInvoker(m_clientConductor, m_context.m_exceptionHandler)\n    {\n        aeron_start(m_aeron);\n    }\n\n    explicit Aeron(aeron_t *aeron) :\n        m_aeron_context(aeron_context(aeron)),\n        m_aeron(aeron),\n        m_countersReader(aeron_counters_reader(m_aeron)),\n        m_clientConductor(m_aeron),\n        m_conductorInvoker(m_clientConductor, m_context.m_exceptionHandler)\n    {\n    }\n\n    ~Aeron()\n    {\n        aeron_on_close_client_pair_t closePair = {emptyCallback, nullptr};\n        aeron_add_close_handler(m_aeron, &closePair);\n        aeron_close(m_aeron);\n        aeron_context_close(m_aeron_context);\n\n        for (const std::pair<const std::int64_t, AsyncAddCounter *>& e : m_pendingCounters)\n        {\n            aeron_async_cmd_free(e.second);\n        }\n\n        for (const std::pair<const std::int64_t, AsyncAddPublication *>& e : m_pendingPublications)\n        {\n            aeron_async_cmd_free(e.second);\n        }\n\n        for (const std::pair<const std::int64_t, AsyncAddExclusivePublication *>& e : m_pendingExclusivePublications)\n        {\n            aeron_async_cmd_free(e.second);\n        }\n\n        for (const std::pair<const std::int64_t, AsyncAddSubscription *>& e : m_pendingSubscriptions)\n        {\n            delete e.second;\n        }\n\n        m_availableCounterHandlers.clear();\n        m_unavailableCounterHandlers.clear();\n        m_closeClientHandlers.clear();\n        m_pendingCounters.clear();\n        m_pendingPublications.clear();\n        m_pendingExclusivePublications.clear();\n        m_pendingSubscriptions.clear();\n    }\n\n    /**\n     * Indicate if the instance is closed and can not longer be used.\n     *\n     * @return true is the instance is closed and can no longer be used, otherwise false.\n     */\n    inline bool isClosed()\n    {\n        return aeron_is_closed(m_aeron);\n    }\n\n    /**\n     * Create an Aeron instance and connect to the media driver.\n     * <p>\n     * Threads required for interacting with the media driver are created and managed within the Aeron instance.\n     *\n     * @param context for configuration of the client.\n     * @return the new Aeron instance connected to the Media Driver.\n     */\n    inline static std::shared_ptr<Aeron> connect(Context &context)\n    {\n        auto aeron = std::make_shared<Aeron>(context);\n        aeron->m_self = aeron;\n        return aeron;\n    }\n\n    /**\n     * Create an Aeron instance and connect to the media driver.\n     * <p>\n     * Threads required for interacting with the media driver are created and managed within the Aeron instance.\n     *\n     * @return the new Aeron instance connected to the Media Driver.\n     */\n    inline static std::shared_ptr<Aeron> connect()\n    {\n        Context ctx;\n        return connect(ctx);\n    }\n\n    /**\n     * Add a {@link Publication} for publishing messages to subscribers\n     *\n     * This function returns immediately and does not wait for the response from the media driver. The returned\n     * registration id is to be used to determine the status of the command with the media driver.\n     *\n     * @param channel for sending the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return id to resolve the AsyncAddPublication for the publication\n     */\n    inline std::int64_t addPublication(const std::string &channel, std::int32_t streamId)\n    {\n        AsyncAddPublication *addPublication = addPublicationAsync(channel, streamId);\n        std::int64_t registrationId = aeron_async_add_publication_get_registration_id(addPublication);\n\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n        m_pendingPublications[registrationId] = addPublication;\n\n        return registrationId;\n    }\n\n    /**\n     * Retrieve the Publication associated with the given registrationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the registrationId is unknown, then a nullptr is returned.\n     * - If the media driver has not answered the add command, then a nullptr is returned.\n     * - If the media driver has successfully added the Publication then what is returned is the Publication.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Aeron::addPublication\n     *\n     * @param registrationId of the Publication returned by Aeron::addPublication\n     * @return Publication associated with the registrationId\n     */\n    inline std::shared_ptr<Publication> findPublication(std::int64_t registrationId)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto search = m_pendingPublications.find(registrationId);\n        if (search == m_pendingPublications.end())\n        {\n            throw IllegalArgumentException(\n                std::string(\"Unknown registration id: \").append(std::to_string(registrationId)), SOURCEINFO);\n        }\n\n        try\n        {\n            std::shared_ptr<Publication> publication = findPublication(search->second);\n            if (nullptr != publication)\n            {\n                m_pendingPublications.erase(registrationId);\n            }\n            return publication;\n        }\n        catch (...)\n        {\n            m_pendingPublications.erase(registrationId);\n            throw;\n        }\n    }\n\n    /**\n     * Add a {@link Publication} for publishing messages to subscribers\n     *\n     * This function returns immediately and does not wait for the response from the media driver. The returned\n     * AsyncAddPublication is to be used to determine the status of the command with the media driver.\n     *\n     * @param channel for sending the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return AsyncAddPublication for the publication.\n     */\n    inline AsyncAddPublication *addPublicationAsync(const std::string &channel, std::int32_t streamId)\n    {\n        aeron_async_add_publication_t *addPublication;\n        if (aeron_async_add_publication(&addPublication, m_aeron, channel.c_str(), streamId) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return addPublication;\n    }\n\n    /**\n     * Retrieve the Publication associated with the given AsyncAddPublication.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the registrationId is unknown, then a nullptr is returned.\n     * - If the media driver has not answered the add command, then a nullptr is returned.\n     * - If the media driver has successfully added the Publication then what is returned is the Publication.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Aeron::addPublicationAsync\n     *\n     * @param addPublication for the Publication returned by Aeron::addPublicationAsync\n     * @return Publication associated with the addPublication\n     */\n    inline std::shared_ptr<Publication> findPublication(AsyncAddPublication *addPublication)\n    {\n        aeron_publication_t *publication;\n        int result = aeron_async_add_publication_poll(&publication, addPublication);\n        if (result < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n        else if (0 == result)\n        {\n            return nullptr;\n        }\n        else\n        {\n            return std::make_shared<Publication>(m_self.lock(), m_aeron, publication);\n        }\n    }\n\n    /**\n     * Gets the registration id for addition of the publication. Note that using this after a call to poll the\n     * succeeds or errors is undefined behaviour. As the AsyncAddPublication may have been freed.\n     * <p>\n     * <em>Note:<em> The return value cannot be used to call `findPublication` method.\n     *\n     * @param addPublication used to check for completion.\n     * @return registration id for the publication.\n     */\n    inline std::int64_t addPublicationAsyncGetRegistrationId(AsyncAddPublication *addPublication)\n    {\n        return aeron_async_add_publication_get_registration_id(addPublication);\n    }\n\n    /**\n     * Add an {@link ExclusivePublication} for publishing messages to subscribers from a single thread.\n     *\n     * @param channel  for sending the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return id to resolve AsyncAddExclusivePublication for the publication\n     */\n    inline std::int64_t addExclusivePublication(const std::string &channel, std::int32_t streamId)\n    {\n        AsyncAddExclusivePublication *addExclusivePublication = addExclusivePublicationAsync(channel, streamId);\n        std::int64_t registrationId = aeron_async_add_exclusive_exclusive_publication_get_registration_id(\n            addExclusivePublication);\n\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n        m_pendingExclusivePublications[registrationId] = addExclusivePublication;\n\n        return registrationId;\n    }\n\n    /**\n     * Retrieve the ExclusivePublication associated with the given registrationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the registrationId is unknown, then a nullptr is returned.\n     * - If the media driver has not answered the add command, then a nullptr is returned.\n     * - If the media driver has successfully added the ExclusivePublication then what is returned is the\n     *  ExclusivePublication.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Aeron::addExclusivePublication\n     *\n     * @param registrationId of the ExclusivePublication returned by Aeron::addExclusivePublication\n     * @return ExclusivePublication associated with the registrationId\n     */\n    inline std::shared_ptr<ExclusivePublication> findExclusivePublication(std::int64_t registrationId)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto search = m_pendingExclusivePublications.find(registrationId);\n        if (search == m_pendingExclusivePublications.end())\n        {\n            throw IllegalArgumentException(\n                std::string(\"Unknown registration id: \").append(std::to_string(registrationId)), SOURCEINFO);\n        }\n\n        try\n        {\n            std::shared_ptr<ExclusivePublication> publication = findExclusivePublication(search->second);\n            if (nullptr != publication)\n            {\n                m_pendingExclusivePublications.erase(registrationId);\n            }\n            return publication;\n        }\n        catch (...)\n        {\n            m_pendingExclusivePublications.erase(registrationId);\n            throw;\n        }\n    }\n\n    /**\n     * Add a {@link ExclusivePublication} for publishing messages to subscribers\n     *\n     * This function returns immediately and does not wait for the response from the media driver. The returned\n     * AsyncAddExclusivePublication is to be used to determine the status of the command with the media driver.\n     *\n     * @param channel for sending the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return AsyncAddExclusivePublication for the publication.\n     */\n    inline AsyncAddExclusivePublication *addExclusivePublicationAsync(const std::string &channel, std::int32_t streamId)\n    {\n        aeron_async_add_exclusive_publication_t *addPublication;\n        if (aeron_async_add_exclusive_publication(&addPublication, m_aeron, channel.c_str(), streamId) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return addPublication;\n    }\n\n    /**\n     * Retrieve the ExclusivePublication associated with the given AsyncAddPublication.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the registrationId is unknown, then a nullptr is returned.\n     * - If the media driver has not answered the add command, then a nullptr is returned.\n     * - If the media driver has successfully added the ExclusivePublication then what is returned is the\n     *  ExclusivePublication.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Aeron::addExclusivePublicationAsync\n     *\n     * @param addPublication for the ExclusivePublication returned by Aeron::addExclusivePublicationAsync\n     * @return ExclusivePublication associated with the addPublication\n     */\n    inline std::shared_ptr<ExclusivePublication> findExclusivePublication(AsyncAddExclusivePublication *addPublication)\n    {\n        aeron_exclusive_publication_t *publication;\n        int result = aeron_async_add_exclusive_publication_poll(&publication, addPublication);\n        if (result < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n        else if (0 == result)\n        {\n            return nullptr;\n        }\n        else\n        {\n            return std::make_shared<ExclusivePublication>(m_self.lock(), m_aeron, publication);\n        }\n    }\n\n    /**\n     * Gets the registration id for addition of the exclusive publication. Note that using this after a call to poll the\n     * succeeds or errors is undefined behaviour. As the AsyncAddExclusivePublication may have been freed.\n     * <p>\n     * <em>Note:<em> The return value cannot be used to call `findPublication` method.\n     *\n     * @param addPublication used to check for completion.\n     * @return registration id for the exclusive publication.\n     */\n    inline std::int64_t addExclusivePublicationAsyncGetRegistrationId(AsyncAddExclusivePublication *addPublication)\n    {\n        return aeron_async_add_exclusive_publication_get_registration_id(addPublication);\n    }\n\n    /**\n     * Add a new {@link Subscription} for subscribing to messages from publishers.\n     *\n     * This function returns immediately and does not wait for the response from the media driver. The returned\n     * registration id is to be used to determine the status of the command with the media driver.\n     *\n     * @param channel  for receiving the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return registration id for the subscription\n     */\n    inline std::int64_t addSubscription(const std::string &channel, std::int32_t streamId)\n    {\n        return addSubscription(\n            channel, streamId, m_context.m_onAvailableImageHandler, m_context.m_onUnavailableImageHandler);\n    }\n\n    /**\n     * Add a new {@link Subscription} for subscribing to messages from publishers.\n     *\n     * This method will override the default handlers from the {@link Context}.\n     *\n     * @param channel                 for receiving the messages known to the media layer.\n     * @param streamId                within the channel scope.\n     * @param availableImageHandler   called when {@link Image}s become available for consumption.\n     * @param unavailableImageHandler called when {@link Image}s go unavailable for consumption.\n     * @return id to resolve the AsyncAddSubscription for the subscription\n     */\n    std::int64_t addSubscription(\n        const std::string &channel,\n        std::int32_t streamId,\n        const on_available_image_t &onAvailableImageHandler,\n        const on_unavailable_image_t &onUnavailableImageHandler)\n    {\n        AsyncAddSubscription *addSubscription = addSubscriptionAsync(\n            channel, streamId, onAvailableImageHandler, onUnavailableImageHandler);\n        std::int64_t registrationId = aeron_async_add_subscription_get_registration_id(addSubscription->m_async);\n\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n        m_pendingSubscriptions[registrationId] = addSubscription;\n\n        return registrationId;\n    }\n\n    /**\n     * Retrieve the Subscription associated with the given registrationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the registrationId is unknown, then a nullptr is returned.\n     * - If the media driver has not answered the add command, then a nullptr is returned.\n     * - If the media driver has successfully added the Subscription then what is returned is the Subscription.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Aeron::addSubscription\n     *\n     * @param registrationId of the Subscription returned by Aeron::addSubscription\n     * @return Subscription associated with the registrationId\n     */\n    inline std::shared_ptr<Subscription> findSubscription(std::int64_t registrationId)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto search = m_pendingSubscriptions.find(registrationId);\n        if (search == m_pendingSubscriptions.end())\n        {\n            throw IllegalArgumentException(\n                std::string(\"Unknown registration id: \").append(std::to_string(registrationId)), SOURCEINFO);\n        }\n\n        AsyncAddSubscription* addSubscription = search->second;\n        try\n        {\n            std::shared_ptr<Subscription> subscription = findSubscription(addSubscription);\n            if (nullptr != subscription)\n            {\n                m_pendingSubscriptions.erase(registrationId);\n            }\n            return subscription;\n        }\n        catch (...)\n        {\n            m_pendingSubscriptions.erase(registrationId);\n            delete addSubscription;\n            throw;\n        }\n    }\n\n    /**\n     * Add a new {@link Subscription} for subscribing to messages from publishers.\n     *\n     * This method will override the default handlers from the {@link Context}.\n     *\n     * @param channel                 for receiving the messages known to the media layer.\n     * @param streamId                within the channel scope.\n     * @param availableImageHandler   called when {@link Image}s become available for consumption.\n     * @param unavailableImageHandler called when {@link Image}s go unavailable for consumption.\n     * @return AsyncAddSubscription object to track the addition of the subscription\n     */\n    inline AsyncAddSubscription *addSubscriptionAsync(\n        const std::string &channel,\n        std::int32_t streamId,\n        const on_available_image_t &onAvailableImageHandler,\n        const on_unavailable_image_t &onUnavailableImageHandler)\n    {\n        auto *addSubscription = new AsyncAddSubscription(onAvailableImageHandler, onUnavailableImageHandler);\n        try\n        {\n            void *availableClientd =\n                const_cast<void *>(reinterpret_cast<const void *>(&addSubscription->m_onAvailableImage));\n            void *unavailableClientd =\n                const_cast<void *>(reinterpret_cast<const void *>(&addSubscription->m_onUnavailableImage));\n\n            if (aeron_async_add_subscription(\n                &addSubscription->m_async,\n                m_aeron,\n                channel.c_str(),\n                streamId,\n                onAvailableImageCallback,\n                availableClientd,\n                onUnavailableImageCallback,\n                unavailableClientd) < 0)\n            {\n                AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n            }\n\n            return addSubscription;\n        }\n        catch (...)\n        {\n            delete addSubscription;\n            throw;\n        }\n    }\n\n    /**\n     * Add a new {@link Subscription} for subscribing to messages from publishers.\n     *\n     * This method will override the default handlers from the {@link Context}.\n     *\n     * @param channel  for receiving the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return AsyncAddSubscription object to track the addition of the subscription\n     */\n    inline AsyncAddSubscription *addSubscriptionAsync(const std::string &channel, std::int32_t streamId)\n    {\n        return addSubscriptionAsync(\n            channel, streamId, m_context.m_onAvailableImageHandler, m_context.m_onUnavailableImageHandler);\n    }\n\n    /**\n     * Retrieve the Subscription associated with the given registrationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the registrationId is unknown, then a nullptr is returned.\n     * - If the media driver has not answered the add command, then a nullptr is returned.\n     * - If the media driver has successfully added the Subscription then what is returned is the Subscription.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Aeron::addSubscription\n     *\n     * @param registrationId of the Subscription returned by Aeron::addSubscription\n     * @return Subscription associated with the registrationId\n     */\n    inline std::shared_ptr<Subscription> findSubscription(AsyncAddSubscription *addSubscription)\n    {\n        aeron_subscription_t *subscription;\n        int result = aeron_async_add_subscription_poll(&subscription, addSubscription->m_async);\n        if (result < 0)\n        {\n            addSubscription->m_async = nullptr;\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n        else if (0 == result)\n        {\n            return nullptr;\n        }\n        else\n        {\n            addSubscription->m_async = nullptr;\n            return std::make_shared<Subscription>(m_self.lock(), m_aeron, subscription, addSubscription);\n        }\n    }\n\n    /**\n     * Gets the registration id for addition of the subscription. Note that using this after a call to poll the\n     * succeeds or errors is undefined behaviour. As the AsyncAddSubscription may have been freed.\n     * <p>\n     * <em>Note:<em> The return value cannot be used to call `findPublication` method.\n     *\n     * @param addSubscription used to check for completion.\n     * @return registration id for the subscription.\n     */\n    inline std::int64_t addSubscriptionAsyncGetRegistrationId(AsyncAddSubscription *addSubscription)\n    {\n        return aeron_async_add_subscription_get_registration_id(addSubscription->m_async);\n    }\n\n    /**\n     * Generate the next correlation id that is unique for the connected Media Driver.\n     *\n     * This is useful generating correlation identifiers for pairing requests with responses in a clients own\n     * application protocol.\n     *\n     * This method is thread safe and will work across processes that all use the same media driver.\n     *\n     * @return next correlation id that is unique for the Media Driver.\n     */\n    inline std::int64_t nextCorrelationId()\n    {\n        return aeron_next_correlation_id(m_aeron);\n    }\n\n    /**\n     * Allocate a counter on the media driver and return a {@link Counter} for it.\n     *\n     * @param typeId      for the counter.\n     * @param keyBuffer   containing the optional key for the counter.\n     * @param keyLength   of the key in the keyBuffer.\n     * @param label       for the counter.\n     * @return id to resolve the AsyncAddCounter for the Counter\n     */\n    std::int64_t addCounter(\n        std::int32_t typeId,\n        const std::uint8_t *keyBuffer,\n        std::size_t keyLength,\n        const std::string &label)\n    {\n        AsyncAddCounter *addCounter = addCounterAsync(typeId, keyBuffer, keyLength, label);\n        std::int64_t registrationId = aeron_async_add_counter_get_registration_id(addCounter);\n\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        m_pendingCounters[registrationId] = addCounter;\n        return registrationId;\n    }\n\n    /**\n     * Retrieve the Counter associated with the given registrationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the registrationId is unknown, then a nullptr is returned.\n     * - If the media driver has not answered the add command, then a nullptr is returned.\n     * - If the media driver has successfully added the Counter then what is returned is the Counter.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Aeron::addCounter\n     * @see Aeron::addStaticCounter\n     *\n     * @param registrationId of the Counter returned by Aeron::addCounter\n     * @return Counter associated with the registrationId\n     */\n    inline std::shared_ptr<Counter> findCounter(std::int64_t registrationId)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto search = m_pendingCounters.find(registrationId);\n        if (search == m_pendingCounters.end())\n        {\n            throw IllegalArgumentException(\n                std::string(\"Unknown registration id: \").append(std::to_string(registrationId)), SOURCEINFO);\n        }\n\n        try\n        {\n            std::shared_ptr<Counter> counter = findCounter(search->second);\n            if (nullptr != counter)\n            {\n                m_pendingCounters.erase(registrationId);\n            }\n            return counter;\n        }\n        catch (...)\n        {\n            m_pendingCounters.erase(registrationId);\n            throw;\n        }\n    }\n\n    /**\n     * Allocate a counter on the media driver and return a {@link Counter} for it.\n     *\n     * @param typeId      for the counter.\n     * @param keyBuffer   containing the optional key for the counter.\n     * @param keyLength   of the key in the keyBuffer.\n     * @param label       for the counter.\n     * @return AsyncAddCounter to find the counter\n     */\n    inline AsyncAddCounter *addCounterAsync(\n        std::int32_t typeId,\n        const std::uint8_t *keyBuffer,\n        std::size_t keyLength,\n        const std::string &label)\n    {\n        aeron_async_add_counter_t *addCounter;\n        if (aeron_async_add_counter(\n            &addCounter, m_aeron, typeId, keyBuffer, keyLength, label.c_str(), label.length()) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return addCounter;\n    }\n\n    /**\n     * Retrieve the Counter associated with the given registrationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the registrationId is unknown, then a nullptr is returned.\n     * - If the media driver has not answered the add command, then a nullptr is returned.\n     * - If the media driver has successfully added the Counter then what is returned is the Counter.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Aeron::addCounter\n     * @see Aeron::addStaticCounter\n     *\n     * @param registrationId of the Counter returned by Aeron::addCounter\n     * @return Counter associated with the registrationId\n     */\n    inline std::shared_ptr<Counter> findCounter(AsyncAddCounter *addCounter)\n    {\n        aeron_counter_t *counter;\n        int result = aeron_async_add_counter_poll(&counter, addCounter);\n        if (result < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n        else if (0 == result)\n        {\n            return nullptr;\n        }\n        else\n        {\n            aeron_counter_constants_t counter_constants = {};\n            aeron_counter_constants(counter, &counter_constants);\n            return std::make_shared<Counter>(\n                m_self.lock(), m_countersReader, counter, counter_constants.registration_id);\n        }\n    }\n\n    /**\n     * Gets the registration id for addition of the counter. Note that using this after a call to poll the\n     * succeeds or errors is undefined behaviour. As the AsyncAddCounter may have been freed.\n     * <p>\n     * <em>Note:<em> The return value cannot be used to call `findPublication` method.\n     *\n     * @param addCounter used to check for completion.\n     * @return registration id for the counter.\n     */\n    inline std::int64_t addCounterAsyncGetRegistrationId(AsyncAddCounter *addCounter)\n    {\n        return aeron_async_add_counter_get_registration_id(addCounter);\n    }\n\n    /**\n     * Allocates or returns an existing static counter instance using specified <code>typeId</code> and\n     * <code>registrationId</code> pair. Such counter cannot be deleted and its lifecycle is decoupled from this\n     * <code>Aeron</code> instance, i.e. won't be freed when this instance is closed or times out.\n     *\n     * @param typeId         for the counter.\n     * @param keyBuffer      containing the optional key for the counter.\n     * @param keyLength      of the key in the keyBuffer.\n     * @param label          for the counter.\n     * @param registrationId that uniquely identifies the static counter for a given <code>typeId</code>\n     * @return correlation id to resolve the AsyncAddCounter for the <code>Counter</code>.\n     */\n    std::int64_t addStaticCounter(\n        std::int32_t typeId,\n        const std::uint8_t *keyBuffer,\n        std::size_t keyLength,\n        const std::string &label,\n        std::int64_t registrationId)\n    {\n        AsyncAddCounter *addCounter = addStaticCounterAsync(typeId, keyBuffer, keyLength, label, registrationId);\n        std::int64_t correlationId = aeron_async_add_counter_get_registration_id(addCounter);\n\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        m_pendingCounters[correlationId] = addCounter;\n        return correlationId;\n    }\n\n    /**\n     * Allocates or returns an existing static counter instance using specified <code>typeId</code> and\n     * <code>registrationId</code> pair. Such counter cannot be deleted and its lifecycle is decoupled from this\n     * <code>Aeron</code> instance, i.e. won't be freed when this instance is closed or times out.\n     *\n     * @param typeId         for the counter.\n     * @param keyBuffer      containing the optional key for the counter.\n     * @param keyLength      of the key in the keyBuffer.\n     * @param label          for the counter.\n     * @param registrationId that uniquely identifies the static counter for a given <code>typeId</code>.\n     * @return AsyncAddCounter to find the counter.\n     */\n    inline AsyncAddCounter *addStaticCounterAsync(\n        std::int32_t typeId,\n        const std::uint8_t *keyBuffer,\n        std::size_t keyLength,\n        const std::string &label,\n        std::int64_t registrationId)\n    {\n        aeron_async_add_counter_t *addCounter;\n        if (aeron_async_add_static_counter(\n            &addCounter, m_aeron, typeId, keyBuffer, keyLength, label.c_str(), label.length(), registrationId) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return addCounter;\n    }\n\n    /**\n     * Add a handler to the list to be called when a counter becomes available.\n     *\n     * @param handler to be added to the available counters list.\n     * @return registration id to use to remove the handler.\n     */\n    inline std::int64_t addAvailableCounterHandler(const on_available_counter_t &handler)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto handler_ptr = std::make_shared<on_available_counter_t>(handler);\n\n        std::int64_t registrationId = aeron_next_correlation_id(m_aeron);\n        m_availableCounterHandlers.emplace_back(registrationId, handler_ptr);\n        auto &storedHandler = m_availableCounterHandlers.back();\n\n        aeron_on_available_counter_pair_t counterPair;\n        counterPair.handler = onAvailableCounterCallback;\n        counterPair.clientd = reinterpret_cast<void *>(storedHandler.second.get());\n\n        if (aeron_add_available_counter_handler(m_aeron, &counterPair) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return registrationId;\n    }\n\n    /**\n     * Remove a handler from the list to be called when a counter becomes available.\n     *\n     * @param registrationId id for the handler to be removed from the available counters list.\n     */\n    inline void removeAvailableCounterHandler(std::int64_t registrationId)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto &v = m_availableCounterHandlers;\n        auto predicate =\n            [registrationId](const std::pair<std::int64_t, std::shared_ptr<on_available_counter_t>> &item)\n            {\n                return item.first == registrationId;\n            };\n\n        auto storedHandler = std::find_if(v.begin(), v.end(), predicate);\n        if (storedHandler == v.end())\n        {\n            return;\n        }\n\n        aeron_on_available_counter_pair_t counterPair;\n        counterPair.handler = onAvailableCounterCallback;\n        counterPair.clientd = reinterpret_cast<void *>(storedHandler->second.get());\n\n        if (aeron_remove_available_counter_handler(m_aeron, &counterPair) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        v.erase(storedHandler);\n    }\n\n    /**\n     * Add a handler to the list to be called when a counter becomes unavailable.\n     *\n     * @param handler to be added to the unavailable counters list.\n     * @return registration id to use to remove the handler.\n     */\n    inline std::int64_t addUnavailableCounterHandler(const on_unavailable_counter_t &handler)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto handler_ptr = std::make_shared<on_unavailable_counter_t>(handler);\n\n        std::int64_t registrationId = aeron_next_correlation_id(m_aeron);\n        m_unavailableCounterHandlers.emplace_back(registrationId, handler_ptr);\n        auto &storedHandler = m_unavailableCounterHandlers.back();\n\n        aeron_on_unavailable_counter_pair_t counterPair;\n        counterPair.handler = onUnavailableCounterCallback;\n        counterPair.clientd = reinterpret_cast<void *>(storedHandler.second.get());\n\n        if (aeron_add_unavailable_counter_handler(m_aeron, &counterPair) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return registrationId;\n    }\n\n    /**\n     * Remove a handler from the list to be called when a counter becomes unavailable.\n     *\n     * @param registrationId id for the handler to be removed from the unavailable counter handlers list.\n     */\n    inline void removeUnavailableCounterHandler(std::int64_t registrationId)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto &v = m_unavailableCounterHandlers;\n        auto predicate =\n            [registrationId](const std::pair<std::int64_t, std::shared_ptr<on_unavailable_counter_t>> &item)\n            {\n                return item.first == registrationId;\n            };\n\n        auto storedHandler = std::find_if(v.begin(), v.end(), predicate);\n        if (storedHandler == v.end())\n        {\n            return;\n        }\n\n        aeron_on_unavailable_counter_pair_t counterPair;\n        counterPair.handler = onUnavailableCounterCallback;\n        counterPair.clientd = reinterpret_cast<void *>(storedHandler->second.get());\n\n        if (aeron_remove_unavailable_counter_handler(m_aeron, &counterPair) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        v.erase(storedHandler);\n    }\n\n    /**\n     * Add a handler to the list to be called when the client is closed.\n     *\n     * @param handler to be added to the close client handlers list.\n     * @return registration id to use to remove the handler.\n     */\n    inline std::int64_t addCloseClientHandler(const on_close_client_t &handler)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n        \n        std::shared_ptr<on_close_client_t> handler_ptr = std::make_shared<on_close_client_t>(handler);\n\n        std::int64_t registrationId = aeron_next_correlation_id(m_aeron);\n        m_closeClientHandlers.emplace_back(registrationId, handler_ptr);\n\n        aeron_on_close_client_pair_t counterPair;\n        counterPair.handler = onCloseClientCallback;\n        counterPair.clientd = reinterpret_cast<void *>(handler_ptr.get());\n\n        if (aeron_add_close_handler(m_aeron, &counterPair) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return registrationId;\n    }\n\n    /**\n     * Remove a handler from the list to be called when the client is closed.\n     *\n     * @param registrationId id for the handler to be removed from the close client handlers list.\n     */\n    inline void removeCloseClientHandler(std::int64_t registrationId)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto &v = m_closeClientHandlers;\n        auto predicate =\n            [registrationId](const std::pair<std::int64_t, std::shared_ptr<on_close_client_t>> &item)\n            {\n                return item.first == registrationId;\n            };\n\n        auto storedHandler = std::find_if(v.begin(), v.end(), predicate);\n        if (storedHandler == v.end())\n        {\n            return;\n        }\n\n        aeron_on_close_client_pair_t counterPair;\n        counterPair.handler = onCloseClientCallback;\n        counterPair.clientd = reinterpret_cast<void *>(storedHandler->second.get());\n\n        if (aeron_remove_close_handler(m_aeron, &counterPair) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        v.erase(storedHandler);\n    }\n\n    /**\n     * Return the AgentInvoker for the client conductor.\n     *\n     * @return AgentInvoker for the conductor.\n     */\n    inline AgentInvoker<ClientConductor> &conductorAgentInvoker()\n    {\n        if (!usesAgentInvoker())\n        {\n            throw IllegalStateException(\"Not configured to use agent invoker\", SOURCEINFO);\n        }\n\n        m_conductorInvoker.start();\n        return m_conductorInvoker;\n    }\n\n    /**\n     * Return whether the AgentInvoker is used or not.\n     *\n     * @return true if AgentInvoker used or false if not.\n     */\n    inline bool usesAgentInvoker() const\n    {\n        return m_context.m_useConductorAgentInvoker;\n    }\n\n    /**\n     * Get the CountersReader for the Aeron media driver counters.\n     *\n     * @return CountersReader for the Aeron media driver in use.\n     */\n    inline CountersReader &countersReader()\n    {\n        return m_countersReader;\n    }\n\n    /**\n     * Get the client identity that has been allocated for communicating with the media driver.\n     *\n     * @return the client identity that has been allocated for communicating with the media driver.\n     */\n    inline std::int64_t clientId() const\n    {\n        return aeron_client_id(m_aeron);\n    }\n\n    /**\n     * Get the Aeron Context object used in construction of the Aeron instance.\n     *\n     * @return Context instance in use.\n     */\n    inline Context &context()\n    {\n        return m_context;\n    }\n\n    inline const Context &context() const\n    {\n        return m_context;\n    }\n\n    /**\n     * Get the underlying C Aeron client. Applications should not need to use this method.\n     *\n     * @return the underlying C Aeron client.\n     */\n    inline aeron_t *aeron() const\n    {\n        return m_aeron;\n    }\n\n    /**\n     * Return the static version and build string for the binary library.\n     *\n     * @return static version and build string for the binary library.\n     */\n    static std::string version()\n    {\n        return { aeron_version_full() };\n    }\n\n\nprivate:\n    Context m_context;\n    aeron_context_t *m_aeron_context;\n    aeron_t *m_aeron = nullptr;\n    CountersReader m_countersReader;\n    std::unordered_map<std::int64_t, AsyncAddPublication *> m_pendingPublications;\n    std::unordered_map<std::int64_t, AsyncAddExclusivePublication *> m_pendingExclusivePublications;\n    std::unordered_map<std::int64_t, AsyncAddSubscription *> m_pendingSubscriptions;\n    std::unordered_map<std::int64_t, AsyncAddCounter *> m_pendingCounters;\n    std::vector<std::pair<std::int64_t, std::shared_ptr<on_available_counter_t>>> m_availableCounterHandlers;\n    std::vector<std::pair<std::int64_t, std::shared_ptr<on_unavailable_counter_t>>> m_unavailableCounterHandlers;\n    std::vector<std::pair<std::int64_t, std::shared_ptr<on_close_client_t>>> m_closeClientHandlers;\n    std::recursive_mutex m_adminLock;\n    ClientConductor m_clientConductor;\n    AgentInvoker<ClientConductor> m_conductorInvoker;\n    std::weak_ptr<Aeron> m_self; // used to ensure destruction order\n\n    static aeron_t *init_aeron(Context &context, aeron_context_t **aeron_context)\n    {\n        aeron_context_t *_context;\n        aeron_context_init(&_context);\n\n        aeron_t *aeron;\n        try\n        {\n            context.attachCallbacksToContext(_context);\n        }\n        catch (IllegalArgumentException&)\n        {\n            aeron_context_close(_context);\n            throw;\n        }\n\n        if (aeron_init(&aeron, _context) < 0)\n        {\n            aeron_context_close(_context);\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        *aeron_context = _context;\n\n        return aeron;\n    }\n\n    static void onAvailableImageCallback(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image)\n    {\n        on_available_image_t &callback = *reinterpret_cast<on_available_image_t *>(clientd);\n        Image imageWrapper(subscription, image);\n        callback(imageWrapper);\n    }\n\n    static void onUnavailableImageCallback(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image)\n    {\n        on_unavailable_image_t &callback = *reinterpret_cast<on_unavailable_image_t *>(clientd);\n        Image imageWrapper(subscription, image);\n        callback(imageWrapper);\n    }\n\n    static void onAvailableCounterCallback(\n        void *clientd, aeron_counters_reader_t *counters_reader, std::int64_t registration_id, std::int32_t counter_id)\n    {\n        CountersReader reader = CountersReader(counters_reader);\n        on_available_counter_t &callback = *reinterpret_cast<on_available_counter_t *>(clientd);\n        callback(reader, registration_id, counter_id);\n    }\n\n    static void onUnavailableCounterCallback(\n        void *clientd, aeron_counters_reader_t *counters_reader, std::int64_t registration_id, std::int32_t counter_id)\n    {\n        CountersReader reader = CountersReader(counters_reader);\n        on_unavailable_counter_t &callback = *reinterpret_cast<on_unavailable_counter_t *>(clientd);\n        callback(reader, registration_id, counter_id);\n    }\n\n    static void onCloseClientCallback(void *clientd)\n    {\n        on_close_client_t &callback = *reinterpret_cast<on_close_client_t *>(clientd);\n        callback();\n    }\n\n    static void emptyCallback(void * /* clientd */)\n    {\n    }\n};\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/AeronCounters.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERONCOUNTERS_H\n#define AERON_AERONCOUNTERS_H\n\n#include <cstdint>\n\nnamespace aeron\n{\n\nnamespace AeronCounters\n{\n    /**\n    * System-wide counters for monitoring. These are separate from counters used for position tracking on streams.\n    */\n    const std::int32_t DRIVER_SYSTEM_COUNTER_TYPE_ID = 0;\n\n    /**\n     * The limit as a position in bytes applied to publishers on a session-channel-stream tuple. Publishers will\n     * experience back pressure when this position is passed as a means of flow control.\n     */\n    const std::int32_t DRIVER_PUBLISHER_LIMIT_TYPE_ID = 1;\n\n    /**\n     * The position the Sender has reached for sending data to the media on a session-channel-stream tuple.\n     */\n    const std::int32_t DRIVER_SENDER_POSITION_TYPE_ID = 2;\n\n    /**\n     * The highest position the Receiver has observed on a session-channel-stream tuple while rebuilding the stream.\n     * It is possible the stream is not complete to this point if the stream has experienced loss.\n     */\n    const std::int32_t DRIVER_RECEIVER_HWM_TYPE_ID = 3;\n    /**\n     * The position an individual Subscriber has reached on a session-channel-stream tuple. It is possible to have\n     * multiple\n     */\n    const std::int32_t DRIVER_SUBSCRIBER_POSITION_TYPE_ID = 4;\n\n    /**\n     * The highest position the Receiver has rebuilt up to on a session-channel-stream tuple while rebuilding the\n     * stream.\n     * The stream is complete up to this point.\n     */\n    const std::int32_t DRIVER_RECEIVER_POS_TYPE_ID = 5;\n\n    /**\n     * The status of a send-channel-endpoint represented as a counter value.\n     */\n    const std::int32_t DRIVER_SEND_CHANNEL_STATUS_TYPE_ID = 6;\n\n    /**\n     * The status of a receive-channel-endpoint represented as a counter value.\n     */\n    const std::int32_t DRIVER_RECEIVE_CHANNEL_STATUS_TYPE_ID = 7;\n\n    /**\n     * The position the Sender can immediately send up-to on a session-channel-stream tuple.\n     */\n    const std::int32_t DRIVER_SENDER_LIMIT_TYPE_ID = 9;\n\n    /**\n     * A counter per Image indicating presence of the congestion control.\n     */\n    const std::int32_t DRIVER_PER_IMAGE_TYPE_ID = 10;\n\n    /**\n     * A counter for tracking the last heartbeat of an entity with a given registration id.\n     */\n    const std::int32_t DRIVER_HEARTBEAT_TYPE_ID = 11;\n\n    /**\n     * The position in bytes a publication has reached appending to the log.\n     * <p>\n     * <b>Note:</b> This is a not a real-time value like the other and is updated one per second for monitoring\n     * purposes.\n     */\n    const std::int32_t DRIVER_PUBLISHER_POS_TYPE_ID = 12;\n\n    /**\n     * Count of back-pressure events (BPE)s a sender has experienced on a stream.\n     */\n    const std::int32_t DRIVER_SENDER_BPE_TYPE_ID = 13;\n\n    /**\n     * Count of media driver neighbors for name resolution.\n     */\n    const std::int32_t NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID = 15;\n\n    /**\n     * Count of entries in the name resolver cache.\n     */\n    const std::int32_t NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID = 16;\n\n    /**\n     * Counter used to store the status of a bind address and port for the local end of a channel.\n     * <p>\n     * When the value is {@link ChannelEndpointStatus#ACTIVE} then the key value and label will be updated with the\n     * socket address and port which is bound.\n     */\n    const std::int32_t DRIVER_LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID = 14;\n\n    /**\n     * Count of number of active receivers for flow control strategy.\n     */\n    const std::int32_t FLOW_CONTROL_RECEIVERS_COUNTER_TYPE_ID = 17;\n\n    /**\n     * Count of number of destinations for multi-destination cast channels.\n     */\n    const std::int32_t MDC_DESTINATIONS_COUNTER_TYPE_ID = 18;\n\n    // Archive counters\n    /**\n     * The position a recording has reached when being archived.\n     */\n    const std::int32_t ARCHIVE_RECORDING_POSITION_TYPE_ID = 100;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the number of errors that have occurred.\n     */\n    const std::int32_t ARCHIVE_ERROR_COUNT_TYPE_ID = 101;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of concurrent control sessions.\n     */\n    const std::int32_t ARCHIVE_CONTROL_SESSIONS_TYPE_ID = 102;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of an archive agent.\n     */\n    const std::int32_t ARCHIVE_MAX_CYCLE_TIME_TYPE_ID = 103;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * an archive agent.\n     */\n    const std::int32_t ARCHIVE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 104;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max time it took recoder to write a block of\n     * data to the storage.\n     */\n    const std::int32_t ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID = 105;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the total number of bytes written by the recorder\n     * to the storage.\n     */\n    const std::int32_t ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID = 106;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the total time the recorder spent writing data to\n     * the storage.\n     */\n    const std::int32_t ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID = 107;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max time it took replayer to read a block from\n     * the storage.\n     */\n    const std::int32_t ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID = 108;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the total number of bytes read by the replayer from\n     * the storage.\n     */\n    const std::int32_t ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID = 109;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the total time the replayer spent reading data from\n     * the storage.\n     */\n    const std::int32_t ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID = 110;\n\n    /**\n     * The type id of the {@link Counter} used for tracking the count of active recording sessions.\n     */\n    const std::int32_t ARCHIVE_RECORDING_SESSION_COUNT_TYPE_ID = 111;\n\n    /**\n     * The type id of the {@link Counter} used for tracking the count of active replay sessions.\n     */\n    const std::int32_t ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID = 112;\n\n    // Cluster counters\n\n    /**\n     * Counter type id for the consensus module state.\n     */\n    const std::int32_t CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID = 200;\n\n    /**\n     * Counter type id for the cluster node role.\n     */\n    const std::int32_t CLUSTER_NODE_ROLE_TYPE_ID = 201;\n\n    /**\n     * Counter type id for the control toggle.\n     */\n    const std::int32_t CLUSTER_CONTROL_TOGGLE_TYPE_ID = 202;\n\n    /**\n     * Counter type id of the commit position.\n     */\n    const std::int32_t CLUSTER_COMMIT_POSITION_TYPE_ID = 203;\n\n    /**\n     * Counter representing the Recovery State for the cluster.\n     */\n    const std::int32_t CLUSTER_RECOVERY_STATE_TYPE_ID = 204;\n\n    /**\n     * Counter type id for count of snapshots taken.\n     */\n    const std::int32_t CLUSTER_SNAPSHOT_COUNTER_TYPE_ID = 205;\n\n    /**\n     * Counter type for count of standby snapshots received.\n     */\n    const std::int32_t CLUSTER_STANDBY_SNAPSHOT_COUNTER_TYPE_ID = 232;\n\n    /**\n     * Type id for election state counter.\n     */\n    const std::int32_t CLUSTER_ELECTION_STATE_TYPE_ID = 207;\n\n    /**\n     * The type id of the {@link Counter} used for the backup state.\n     */\n    const std::int32_t CLUSTER_BACKUP_STATE_TYPE_ID = 208;\n\n    /**\n     * The type id of the {@link Counter} used for the live log position counter.\n     */\n    const std::int32_t CLUSTER_BACKUP_LIVE_LOG_POSITION_TYPE_ID = 209;\n\n    /**\n     * The type id of the {@link Counter} used for the next query deadline counter.\n     */\n    const std::int32_t CLUSTER_BACKUP_QUERY_DEADLINE_TYPE_ID = 210;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the number of errors that have occurred.\n     */\n    const std::int32_t CLUSTER_BACKUP_ERROR_COUNT_TYPE_ID = 211;\n\n    /**\n     * Counter type id for the consensus module error count.\n     */\n    const std::int32_t CLUSTER_CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID = 212;\n\n    /**\n     * Counter type id for the number of cluster clients which have been timed out.\n     */\n    const std::int32_t CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID = 213;\n\n    /**\n     * Counter type id for the number of invalid requests which the cluster has received.\n     */\n    const std::int32_t CLUSTER_INVALID_REQUEST_COUNT_TYPE_ID = 214;\n\n    /**\n     * Counter type id for the clustered service error count.\n     */\n    const std::int32_t CLUSTER_CLUSTERED_SERVICE_ERROR_COUNT_TYPE_ID = 215;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the consensus module.\n     */\n    const std::int32_t CLUSTER_MAX_CYCLE_TIME_TYPE_ID = 216;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * the consensus module.\n     */\n    const std::int32_t CLUSTER_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 217;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the service container.\n     */\n    const std::int32_t CLUSTER_CLUSTERED_SERVICE_MAX_CYCLE_TIME_TYPE_ID = 218;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * the service container.\n     */\n    const std::int32_t CLUSTER_CLUSTERED_SERVICE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 219;\n\n    /**\n     * The type id of the {@link Counter} used for the cluster standby state.\n     */\n    const std::int32_t CLUSTER_STANDBY_STATE_TYPE_ID = 220;\n\n    /**\n     * Counter type id for the clustered service error count.\n     */\n    const std::int32_t CLUSTER_STANDBY_ERROR_COUNT_TYPE_ID = 221;\n\n    /**\n     * Counter type for responses to heartbeat request from the cluster.\n     */\n    const std::int32_t CLUSTER_STANDBY_HEARTBEAT_RESPONSE_COUNT_TYPE_ID = 222;\n\n    /**\n     * Standby control toggle type id\n     */\n    const std::int32_t CLUSTER_STANDBY_CONTROL_TOGGLE_TYPE_ID = 223;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the cluster standby.\n     */\n    const std::int32_t CLUSTER_STANDBY_MAX_CYCLE_TIME_TYPE_ID = 227;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * the cluster standby.\n     */\n    const std::int32_t CLUSTER_STANDBY_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 228;\n\n    /**\n     * The type id of the {@link Counter} to make visible the memberId that the cluster standby is currently using to\n     * as a source for the cluster log.\n     */\n    const std::int32_t CLUSTER_STANDBY_SOURCE_MEMBER_ID_TYPE_ID = 231;\n\n    /**\n     * Counter type id for the transition module error count.\n     */\n    const std::int32_t TRANSITION_MODULE_ERROR_COUNT_TYPE_ID = 226;\n\n    /**\n     * The type if of the {@link Counter} used for transition module state\n     */\n    const std::int32_t TRANSITION_MODULE_STATE_TYPE_ID = 224;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the transition module.\n     */\n    const std::int32_t TRANSITION_MODULE_MAX_CYCLE_TIME_TYPE_ID = 229;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * the transition module.\n     */\n    const std::int32_t TRANSITION_MODULE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 230;\n\n    /**\n     * The type of the {@link Counter} used for handling node specific operations.\n     */\n    const std::int32_t NODE_CONTROL_TOGGLE_TYPE_ID = 233;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the maximum total snapshot duration.\n     */\n    const std::int32_t CLUSTER_TOTAL_MAX_SNAPSHOT_DURATION_TYPE_ID = 234;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count total snapshot duration\n     * has exceeded the threshold.\n     */\n    const std::int32_t CLUSTER_TOTAL_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID = 235;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the maximum snapshot duration\n     * for a given clustered service.\n     */\n    const std::int32_t CLUSTERED_SERVICE_MAX_SNAPSHOT_DURATION_TYPE_ID = 236;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count snapshot duration\n     * has exceeded the threshold for a given clustered service.\n     */\n    const std::int32_t CLUSTERED_SERVICE_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID = 237;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the number of elections that have occurred.\n     */\n    const std::int32_t CLUSTER_ELECTION_COUNT_TYPE_ID = 238;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the Cluster leadership term id.\n     */\n    const std::int32_t CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID = 239;\n}\n\n}\n\n\n\n#endif //AERON_AERONCOUNTERS_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/BufferBuilder.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_BUFFERBUILDER_H\n#define AERON_BUFFERBUILDER_H\n\n#include <limits>\n\n#include \"Aeron.h\"\n\nnamespace aeron\n{\n\nstatic constexpr std::uint32_t BUFFER_BUILDER_MAX_CAPACITY = std::numeric_limits<std::int32_t>::max() - 8;\nstatic constexpr std::uint32_t BUFFER_BUILDER_INIT_MIN_CAPACITY = 4096;\n\nclass BufferBuilder\n{\npublic:\n    using this_t = BufferBuilder;\n\n    explicit BufferBuilder(std::uint32_t initialCapacity = 0) :\n        m_capacity(initialCapacity),\n        m_buffer(new std::uint8_t[m_capacity])\n    {\n        if (BUFFER_BUILDER_MAX_CAPACITY < m_capacity)\n        {\n            throw IllegalArgumentException(\n                \"initialCapacity outside range 0 - \" + std::to_string(BUFFER_BUILDER_MAX_CAPACITY) +\n                \": capacity=\" + std::to_string(m_capacity), SOURCEINFO);\n        }\n    }\n\n    BufferBuilder(BufferBuilder &&builder) noexcept:\n        m_capacity(builder.m_capacity),\n        m_limit(builder.m_limit),\n        m_nextTermOffset(builder.m_nextTermOffset),\n        m_buffer(std::move(builder.m_buffer))\n    {\n    }\n\n    std::uint8_t *buffer() const\n    {\n        return &m_buffer[0];\n    }\n\n    std::uint32_t limit() const\n    {\n        return m_limit;\n    }\n\n    std::uint32_t capacity() const\n    {\n        return m_capacity;\n    }\n\n    void limit(std::uint32_t limit)\n    {\n        if (limit >= m_capacity)\n        {\n            throw IllegalArgumentException(\n                \"limit outside range: capacity=\" + std::to_string(m_capacity) + \" limit=\" + std::to_string(limit),\n                SOURCEINFO);\n        }\n\n        m_limit = limit;\n    }\n\n    util::index_t nextTermOffset() const\n    {\n        return m_nextTermOffset;\n    }\n\n    void nextTermOffset(util::index_t offset)\n    {\n        m_nextTermOffset = offset;\n    }\n\n    this_t &reset()\n    {\n        m_limit = 0;\n        m_nextTermOffset = NULL_VALUE;\n        return *this;\n    }\n\n    this_t &compact()\n    {\n        const std::uint32_t newCapacity =\n            BUFFER_BUILDER_INIT_MIN_CAPACITY < m_limit ? m_limit : BUFFER_BUILDER_INIT_MIN_CAPACITY;\n        if (newCapacity < m_capacity)\n        {\n            resize(newCapacity);\n        }\n\n        return *this;\n    }\n\n    this_t &append(AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &)\n    {\n        ensureCapacity(static_cast<std::uint32_t>(length));\n\n        ::memcpy(&m_buffer[0] + m_limit, buffer.buffer() + offset, static_cast<std::uint32_t>(length));\n        m_limit += length;\n        return *this;\n    }\n\n    this_t &append(AtomicBuffer &buffer, util::index_t offset, util::index_t length)\n    {\n        ensureCapacity(static_cast<std::uint32_t>(length));\n\n        ::memcpy(&m_buffer[0] + m_limit, buffer.buffer() + offset, static_cast<std::uint32_t>(length));\n        m_limit += length;\n        return *this;\n    }\n\n    this_t &captureHeader(Header &header)\n    {\n        return *this;\n    }\n\nprivate:\n    std::uint32_t m_capacity = 0;\n    std::uint32_t m_limit = 0;\n    util::index_t m_nextTermOffset = NULL_VALUE;\n    std::unique_ptr<std::uint8_t[]> m_buffer;\n\n    inline static std::uint32_t findSuitableCapacity(\n        std::uint32_t currentCapacity, std::uint32_t requiredCapacity) noexcept\n    {\n        std::uint32_t newCapacity = currentCapacity < BUFFER_BUILDER_INIT_MIN_CAPACITY ?\n            BUFFER_BUILDER_INIT_MIN_CAPACITY : currentCapacity;\n\n        while (newCapacity < requiredCapacity)\n        {\n            newCapacity = newCapacity + (newCapacity / 2);\n            if (newCapacity > BUFFER_BUILDER_MAX_CAPACITY)\n            {\n                newCapacity = BUFFER_BUILDER_MAX_CAPACITY;\n            }\n        }\n\n        return newCapacity;\n    }\n\n    void ensureCapacity(std::uint32_t additionalCapacity)\n    {\n        const std::uint32_t requiredCapacity = m_limit + additionalCapacity;\n\n        if (requiredCapacity > m_capacity)\n        {\n            if (requiredCapacity > BUFFER_BUILDER_MAX_CAPACITY)\n            {\n                throw util::IllegalStateException(\n                    \"max capacity reached: \" + std::to_string(BUFFER_BUILDER_MAX_CAPACITY), SOURCEINFO);\n            }\n\n            const std::uint32_t newCapacity = findSuitableCapacity(m_capacity, requiredCapacity);\n            resize(newCapacity);\n        }\n    }\n\n    void resize(std::uint32_t newCapacity)\n    {\n        std::unique_ptr<std::uint8_t[]> newBuffer(new std::uint8_t[newCapacity]);\n\n        ::memcpy(&newBuffer[0], &m_buffer[0], m_limit);\n        m_buffer = std::move(newBuffer);\n        m_capacity = newCapacity;\n    }\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nif (MSVC AND \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\")\n    set(BUILD_SHARED_LIBS ON)\nendif ()\n\nSET(HEADERS\n    ${CMAKE_CURRENT_SOURCE_DIR}/Aeron.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/AeronCounters.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/BufferBuilder.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/ChannelUri.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/ChannelUriStringBuilder.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/ClientConductor.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/CncFileDescriptor.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/CncFileReader.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/Context.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/ControlledFragmentAssembler.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/Counter.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/ExclusivePublication.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/FragmentAssembler.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/HeartbeatTimestamp.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/Image.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/ImageControlledFragmentAssembler.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/ImageFragmentAssembler.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/Publication.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/Subscription.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/AgentInvoker.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/AgentRunner.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/Atomic64.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/AtomicBuffer.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/AtomicCounter.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/BackOffIdleStrategy.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/BusySpinIdleStrategy.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/CountersReader.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/NoOpIdleStrategy.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/SleepingIdleStrategy.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/YieldingIdleStrategy.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/atomic/Atomic64_gcc_cpp11.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/atomic/Atomic64_gcc_x86_64.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/atomic/Atomic64_msvc.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/errors/ErrorLogDescriptor.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/errors/ErrorLogReader.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/logbuffer/BufferClaim.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/logbuffer/DataFrameHeader.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/logbuffer/FrameDescriptor.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/logbuffer/Header.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/logbuffer/LogBufferDescriptor.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/logbuffer/TermReader.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/status/Position.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/status/ReadablePosition.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/status/StatusIndicatorReader.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/concurrent/status/UnsafeBufferPosition.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/status/PublicationErrorFrame.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/BitUtil.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/CommandOption.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/CommandOptionParser.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/Exceptions.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/Index.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/LangUtil.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/MacroUtil.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/MemoryMappedFile.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/Platform.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/ScopeUtils.h\n    ${CMAKE_CURRENT_SOURCE_DIR}/util/StringUtil.h)\n\n# header only library\nadd_library(aeron_client_wrapper INTERFACE)\nadd_library(aeron::aeron_client_wrapper ALIAS aeron_client_wrapper)\ntarget_include_directories(aeron_client_wrapper\n    INTERFACE \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>\" \"$<INSTALL_INTERFACE:include/wrapper>\")\n\ntarget_sources(aeron_client_wrapper INTERFACE \"$<BUILD_INTERFACE:${HEADERS}>\")\n\nif (MSVC)\n    string(REPLACE \"/\" \"\\\\\\\\\" NATIVE_PROJECT_SOURCE_DIR \"${PROJECT_SOURCE_DIR}\")\nelse ()\n    set(NATIVE_PROJECT_SOURCE_DIR \"${PROJECT_SOURCE_DIR}\")\nendif ()\n\nif (NOT WIN32)\n    set(CMAKE_THREAD_PREFER_PTHREAD TRUE)\n    set(THREADS_PREFER_PTHREAD_FLAG TRUE)\nendif ()\n\ntarget_link_libraries(aeron_client_wrapper INTERFACE ${CMAKE_THREAD_LIBS_INIT})\n\nif (AERON_INSTALL_TARGETS)\n    install(\n        TARGETS aeron_client_wrapper\n        EXPORT aeron-targets)\n    install(DIRECTORY ./ DESTINATION include/wrapper FILES_MATCHING PATTERN \"*.h\")\nendif ()\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/ChannelUri.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CHANNEL_URI_H\n#define AERON_CHANNEL_URI_H\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n\n#include \"util/StringUtil.h\"\n\nnamespace aeron\n{\n\nstatic constexpr const char SPY_QUALIFIER[] = \"aeron-spy\";\nstatic constexpr const char AERON_SCHEME[] = \"aeron\";\nstatic constexpr const char AERON_PREFIX[] = \"aeron:\";\n\nstatic constexpr const char IPC_MEDIA[] = \"ipc\";\nstatic constexpr const char UDP_MEDIA[] = \"udp\";\nstatic constexpr const char IPC_CHANNEL[] = \"aeron:ipc\";\nstatic constexpr const char SPY_PREFIX[] = \"aeron-spy:\";\nstatic constexpr const char ENDPOINT_PARAM_NAME[] = \"endpoint\";\nstatic constexpr const char INTERFACE_PARAM_NAME[] = \"interface\";\nstatic constexpr const char INITIAL_TERM_ID_PARAM_NAME[] = \"init-term-id\";\nstatic constexpr const char TERM_ID_PARAM_NAME[] = \"term-id\";\nstatic constexpr const char TERM_OFFSET_PARAM_NAME[] = \"term-offset\";\nstatic constexpr const char TERM_LENGTH_PARAM_NAME[] = \"term-length\";\nstatic constexpr const char MTU_LENGTH_PARAM_NAME[] = \"mtu\";\nstatic constexpr const char TTL_PARAM_NAME[] = \"ttl\";\nstatic constexpr const char MDC_CONTROL_PARAM_NAME[] = \"control\";\nstatic constexpr const char MDC_CONTROL_MODE_PARAM_NAME[] = \"control-mode\";\nstatic constexpr const char MDC_CONTROL_MODE_MANUAL[] = \"manual\";\nstatic constexpr const char MDC_CONTROL_MODE_DYNAMIC[] = \"dynamic\";\nstatic constexpr const char CONTROL_MODE_RESPONSE[] = \"response\";\nstatic constexpr const char SESSION_ID_PARAM_NAME[] = \"session-id\";\nstatic constexpr const char LINGER_PARAM_NAME[] = \"linger\";\nstatic constexpr const char RELIABLE_STREAM_PARAM_NAME[] = \"reliable\";\nstatic constexpr const char TAGS_PARAM_NAME[] = \"tags\";\nstatic constexpr const char TAG_PREFIX[] = \"tag:\";\nstatic constexpr const char SPARSE_PARAM_NAME[] = \"sparse\";\nstatic constexpr const char ALIAS_PARAM_NAME[] = \"alias\";\nstatic constexpr const char EOS_PARAM_NAME[] = \"eos\";\nstatic constexpr const char TETHER_PARAM_NAME[] = \"tether\";\nstatic constexpr const char GROUP_PARAM_NAME[] = \"group\";\nstatic constexpr const char REJOIN_PARAM_NAME[] = \"rejoin\";\nstatic constexpr const char CONGESTION_CONTROL_PARAM_NAME[] = \"cc\";\nstatic constexpr const char FLOW_CONTROL_PARAM_NAME[] = \"fc\";\nstatic constexpr const char GROUP_TAG_PARAM_NAME[] = \"gtag\";\nstatic constexpr const char SPIES_SIMULATE_CONNECTION_PARAM_NAME[] = \"ssc\";\nstatic constexpr const char SOCKET_SNDBUF_PARAM_NAME[] = \"so-sndbuf\";\nstatic constexpr const char SOCKET_RCVBUF_PARAM_NAME[] = \"so-rcvbuf\";\nstatic constexpr const char RECEIVER_WINDOW_LENGTH_PARAM_NAME[] = \"rcv-wnd\";\nstatic constexpr const char MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME[] = \"media-rcv-ts-offset\";\nstatic constexpr const char CHANNEL_RCV_TIMESTAMP_OFFSET_PARAM_NAME[] = \"channel-rcv-ts-offset\";\nstatic constexpr const char CHANNEL_SND_TIMESTAMP_OFFSET_PARAM_NAME[] = \"channel-snd-ts-offset\";\nstatic constexpr const char RESPONSE_CORRELATION_ID_PARAM_NAME[] = \"response-correlation-id\";\nstatic constexpr const char NAK_DELAY_PARAM_NAME[] = \"nak-delay\";\nstatic constexpr const char UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME[] = \"untethered-window-limit-timeout\";\nstatic constexpr const char UNTETHERED_RESTING_TIMEOUT_PARAM_NAME[] = \"untethered-resting-timeout\";\nstatic constexpr const char MAX_RESEND_PARAM_NAME[] = \"max-resend\";\n\nusing namespace aeron::util;\n\nclass ChannelUri\n{\npublic:\n    using this_t = ChannelUri;\n\n    enum State : int\n    {\n        MEDIA,\n        PARAMS_KEY,\n        PARAMS_VALUE\n    };\n\n    ChannelUri(\n        const std::string &prefix,\n        const std::string &media,\n        std::unique_ptr<std::unordered_map<std::string, std::string>> params) :\n        m_prefix(prefix),\n        m_media(media),\n        m_params(std::move(params))\n    {\n    }\n\n    inline std::string prefix()\n    {\n        return m_prefix;\n    }\n\n    inline this_t &prefix(const std::string &prefix)\n    {\n        m_prefix = prefix;\n        return *this;\n    }\n\n    inline std::string media()\n    {\n        return m_media;\n    }\n\n    inline this_t &media(const std::string &media)\n    {\n        if (media != IPC_MEDIA && media != UDP_MEDIA)\n        {\n            throw IllegalArgumentException(\"unknown media: \" + media, SOURCEINFO);\n        }\n        m_media = media;\n        return *this;\n    }\n\n    inline std::string scheme()\n    {\n        return AERON_SCHEME;\n    }\n\n    inline std::string get(const std::string &key)\n    {\n        auto it = m_params->find(key);\n\n        if (it != m_params->end())\n        {\n            return it->second;\n        }\n\n        return {};\n    }\n\n    inline std::string get(const std::string &key, const std::string &defaultValue)\n    {\n        auto it = m_params->find(key);\n\n        if (it != m_params->end())\n        {\n            return it->second;\n        }\n\n        return defaultValue;\n    }\n\n    inline void put(const std::string &key, const std::string &value)\n    {\n        (*m_params)[key] = value;\n    }\n\n    inline std::string remove(const std::string &key)\n    {\n        std::string result;\n        auto it = m_params->find(key);\n\n        if (it != m_params->end())\n        {\n            result = it->second;\n            m_params->erase(it);\n        }\n\n        return result;\n    }\n\n    inline bool containsKey(const std::string &key)\n    {\n        return m_params->find(key) != m_params->end();\n    }\n    \n    inline bool hasControlModeResponse()\n    {\n        const std::string &controlMode = get(MDC_CONTROL_MODE_PARAM_NAME);\n        return !controlMode.empty() && CONTROL_MODE_RESPONSE == controlMode;\n    }\n\n    std::string toString()\n    {\n        std::string sb;\n        if (m_prefix.length() == 0)\n        {\n            sb.reserve((m_params->size() * 20) + 10);\n        }\n        else\n        {\n            sb.reserve((m_params->size() * 20) + 20);\n            sb += m_prefix;\n            if (':' != m_prefix.back())\n            {\n                sb += ':';\n            }\n        }\n\n        sb += AERON_PREFIX;\n        sb += m_media;\n\n        if (!m_params->empty())\n        {\n            sb += '?';\n\n            for (const auto &i : *m_params)\n            {\n                sb += i.first;\n                sb += '=';\n                sb += i.second;\n                sb += '|';\n            }\n\n            sb.pop_back();\n        }\n\n        return sb;\n    }\n\n    static std::shared_ptr<ChannelUri> parse(const std::string &uri)\n    {\n        std::size_t position = 0;\n        std::string prefix;\n        if (startsWith(uri, 0, SPY_PREFIX))\n        {\n            prefix = SPY_QUALIFIER;\n            position = sizeof(SPY_PREFIX) - 1;\n        }\n        else\n        {\n            prefix = \"\";\n        }\n\n        if (!startsWith(uri, position, AERON_PREFIX))\n        {\n            throw IllegalArgumentException(\"Aeron URIs must start with 'aeron:', found: \" + uri, SOURCEINFO);\n        }\n        else\n        {\n            position += sizeof(AERON_PREFIX) - 1;\n        }\n\n        std::string builder;\n        std::unique_ptr<std::unordered_map<std::string, std::string>> params(\n            new std::unordered_map<std::string, std::string>());\n        std::string media;\n        std::string key;\n        State state = State::MEDIA;\n\n        for (std::size_t i = position; i < uri.length(); i++)\n        {\n            char c = uri[i];\n\n            switch (state)\n            {\n                case State::MEDIA:\n                    switch (c)\n                    {\n                        case '?':\n                            media = builder;\n                            builder.clear();\n                            state = State::PARAMS_KEY;\n                            break;\n\n                        case ':':\n                        case '|':\n                        case '=':\n                            throw IllegalStateException(\n                                \"encountered '\" + std::to_string(c) + \"' within media definition at index \" +\n                                    std::to_string(i) + \" in \" + uri, SOURCEINFO);\n\n                        default:\n                            builder += c;\n                    }\n                    break;\n\n                case PARAMS_KEY:\n                    if ('=' == c)\n                    {\n                        if (0 == builder.length())\n                        {\n                            throw IllegalStateException(\n                                \"empty key not allowed at index \" + std::to_string(i) + \" in \" + uri, SOURCEINFO);\n\n                        }\n                        key = builder;\n                        builder.clear();\n                        state = State::PARAMS_VALUE;\n                    }\n                    else\n                    {\n                        if (c == '|')\n                        {\n                            throw IllegalStateException(\n                                \"invalid end of key at index \" + std::to_string(i) + \" in \" + uri, SOURCEINFO);\n                        }\n\n                        builder += c;\n                    }\n                    break;\n\n                case PARAMS_VALUE:\n                    if ('|' == c)\n                    {\n                        params->emplace(key, builder);\n                        builder.clear();\n                        state = State::PARAMS_KEY;\n                    }\n                    else\n                    {\n                        builder += c;\n                    }\n                    break;\n            }\n        }\n\n        switch (state)\n        {\n            case State::MEDIA:\n                media = builder;\n                if (media != IPC_MEDIA && media != UDP_MEDIA)\n                {\n                    throw IllegalArgumentException(\"unknown media: \" + media, SOURCEINFO);\n                }\n                break;\n\n            case PARAMS_VALUE:\n                params->emplace(key, builder);\n                break;\n\n            default:\n                throw IllegalArgumentException(\"no more input found, state=\" + std::to_string(state), SOURCEINFO);\n        }\n\n        return std::make_shared<ChannelUri>(prefix, media, std::move(params));\n    }\n\n    inline static std::string addSessionId(const std::string &channel, std::int32_t sessionId)\n    {\n        std::shared_ptr<ChannelUri> channelUri = ChannelUri::parse(channel);\n\n        channelUri->put(SESSION_ID_PARAM_NAME, std::to_string(sessionId));\n        return channelUri->toString();\n    }\n\n    inline static std::string addAliasIfAbsent(const std::string& uri, const std::string& alias)\n    {\n        if (!alias.empty())\n        {\n            std::shared_ptr<ChannelUri> channelUri = ChannelUri::parse(uri);\n            if (!channelUri->containsKey(ALIAS_PARAM_NAME))\n            {\n                channelUri->put(ALIAS_PARAM_NAME, alias);\n                return channelUri->toString();\n            }\n        }\n        return uri;\n    }\n\nprivate:\n    std::string m_prefix;\n    std::string m_media;\n    std::unique_ptr<std::unordered_map<std::string, std::string>> m_params;\n};\n\n}\n#endif //AERON_CHANNEL_URI_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/ChannelUriStringBuilder.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CHANNEL_URI_STRING_BUILDER_H\n#define AERON_CHANNEL_URI_STRING_BUILDER_H\n\n#include <memory>\n#include <string>\n#include <sstream>\n\n#include \"ChannelUri.h\"\n#include \"concurrent/logbuffer/FrameDescriptor.h\"\n#include \"concurrent/logbuffer/LogBufferDescriptor.h\"\n\nnamespace aeron\n{\n\nusing namespace aeron::util;\n\nclass ChannelUriStringBuilder\n{\npublic:\n    using this_t = ChannelUriStringBuilder;\n\n    inline this_t &clear()\n    {\n        m_prefix.reset(nullptr);\n        m_media.reset(nullptr);\n        m_endpoint.reset(nullptr);\n        m_networkInterface.reset(nullptr);\n        m_controlEndpoint.reset(nullptr);\n        m_controlMode.reset(nullptr);\n        m_tags.reset(nullptr);\n        m_alias.reset(nullptr);\n        m_cc.reset(nullptr);\n        m_fc.reset(nullptr);\n        m_gtag.reset(nullptr);\n        m_reliable.reset(nullptr);\n        m_ttl.reset(nullptr);\n        m_mtu.reset(nullptr);\n        m_termLength.reset(nullptr);\n        m_initialTermId.reset(nullptr);\n        m_termId.reset(nullptr);\n        m_termOffset.reset(nullptr);\n        m_sessionId.reset(nullptr);\n        m_linger.reset(nullptr);\n        m_sparse.reset(nullptr);\n        m_eos.reset(nullptr);\n        m_tether.reset(nullptr);\n        m_group.reset(nullptr);\n        m_rejoin.reset(nullptr);\n        m_ssc.reset(nullptr);\n        m_socketRcvbufLength.reset(nullptr);\n        m_socketSndbufLength.reset(nullptr);\n        m_receiverWindowLength.reset(nullptr);\n        m_isSessionIdTagged = false;\n        m_mediaReceiveTimestampOffset.reset(nullptr);\n        m_channelReceiveTimestampOffset.reset(nullptr);\n        m_channelSendTimestampOffset.reset(nullptr);\n        m_responseCorrelationId.reset(nullptr);\n        m_nakDelay.reset(nullptr);\n        m_untetheredWindowLimitTimeout.reset(nullptr);\n        m_untetheredRestingTimeout.reset(nullptr);\n        m_maxResend.reset(nullptr);\n\n        return *this;\n    }\n\n    inline this_t &prefix(const std::string &prefix)\n    {\n        if (!prefix.empty() && prefix != SPY_QUALIFIER)\n        {\n            throw IllegalArgumentException(\"invalid prefix: \" + prefix, SOURCEINFO);\n        }\n\n        m_prefix.reset(new std::string(prefix));\n        return *this;\n    }\n\n    inline this_t &prefix(std::nullptr_t nullp)\n    {\n        m_prefix.reset(nullptr);\n        return *this;\n    }\n\n    inline this_t &media(const std::string &media)\n    {\n        if (media != UDP_MEDIA && media != IPC_MEDIA)\n        {\n            throw IllegalArgumentException(\"invalid media: \" + media, SOURCEINFO);\n        }\n\n        m_media.reset(new std::string(media));\n        return *this;\n    }\n\n    inline this_t &endpoint(const std::string &endpoint)\n    {\n        m_endpoint.reset(new std::string(endpoint));\n        return *this;\n    }\n\n    inline this_t &networkInterface(const std::string &networkInterface)\n    {\n        m_networkInterface.reset(new std::string(networkInterface));\n        return *this;\n    }\n\n    inline this_t &controlEndpoint(const std::string &controlEndpoint)\n    {\n        m_controlEndpoint.reset(new std::string(controlEndpoint));\n        return *this;\n    }\n\n    inline this_t &controlMode(const std::string &controlMode)\n    {\n        if (controlMode != MDC_CONTROL_MODE_MANUAL &&\n            controlMode != MDC_CONTROL_MODE_DYNAMIC &&\n            controlMode != CONTROL_MODE_RESPONSE)\n        {\n            throw IllegalArgumentException(\"invalid control mode: \" + controlMode, SOURCEINFO);\n        }\n\n        m_controlMode.reset(new std::string(controlMode));\n        return *this;\n    }\n\n    inline this_t &tags(const std::string &tags)\n    {\n        m_tags.reset(new std::string(tags));\n        return *this;\n    }\n\n    inline this_t &alias(const std::string &alias)\n    {\n        m_alias.reset(new std::string(alias));\n        return *this;\n    }\n\n    inline this_t &congestionControl(const std::string &congestionControl)\n    {\n        m_cc.reset(new std::string(congestionControl));\n        return *this;\n    }\n\n    inline this_t &flowControl(const std::string &flowControl)\n    {\n        m_fc.reset(new std::string(flowControl));\n        return *this;\n    }\n\n    inline this_t &groupTag(std::int64_t gtag)\n    {\n        m_gtag.reset(new Value(gtag));\n        return *this;\n    }\n\n    inline this_t &reliable(bool reliable)\n    {\n        m_reliable.reset(new Value(reliable ? 1 : 0));\n        return *this;\n    }\n\n    inline this_t &reliable(std::nullptr_t nullp)\n    {\n        m_reliable.reset(nullptr);\n        return *this;\n    }\n\n    inline this_t &ttl(std::uint8_t ttl)\n    {\n        m_ttl.reset(new Value(ttl));\n        return *this;\n    }\n\n    inline this_t &mtu(std::uint32_t mtu)\n    {\n        if (mtu < 32 || mtu > 65504)\n        {\n            throw IllegalArgumentException(\"MTU not in range 32-65504: \" + std::to_string(mtu), SOURCEINFO);\n        }\n\n        if (0 != (mtu & static_cast<std::uint32_t>(concurrent::logbuffer::FrameDescriptor::FRAME_ALIGNMENT - 1)))\n        {\n            throw IllegalArgumentException(\n                \"MTU not a multiple of FRAME_ALIGNMENT: mtu=\" + std::to_string(mtu), SOURCEINFO);\n        }\n\n        m_mtu.reset(new Value(mtu));\n        return *this;\n    }\n\n    inline this_t &termLength(std::int32_t termLength)\n    {\n        concurrent::logbuffer::LogBufferDescriptor::checkTermLength(termLength);\n        m_termLength.reset(new Value(termLength));\n        return *this;\n    }\n\n    inline this_t &initialTermId(std::int32_t initialTermId)\n    {\n        m_initialTermId.reset(new Value(initialTermId));\n        return *this;\n    }\n\n    inline this_t &termId(std::int32_t termId)\n    {\n        m_termId.reset(new Value(termId));\n        return *this;\n    }\n\n    inline this_t &termOffset(std::uint32_t termOffset)\n    {\n        if (termOffset > concurrent::logbuffer::LogBufferDescriptor::TERM_MAX_LENGTH)\n        {\n            throw IllegalArgumentException(\"term offset not in range 0-1g: \" + std::to_string(termOffset), SOURCEINFO);\n        }\n\n        if (0 != (termOffset & static_cast<std::uint32_t>(concurrent::logbuffer::FrameDescriptor::FRAME_ALIGNMENT - 1)))\n        {\n            throw IllegalArgumentException(\n                \"term offset not multiple of FRAME_ALIGNMENT: \" + std::to_string(termOffset), SOURCEINFO);\n        }\n\n        m_termOffset.reset(new Value(termOffset));\n        return *this;\n    }\n\n    inline this_t &sessionId(std::int32_t sessionId)\n    {\n        m_sessionId.reset(new Value(sessionId));\n        return *this;\n    }\n\n    inline this_t &linger(std::int64_t lingerNs)\n    {\n        if (lingerNs < 0)\n        {\n            throw IllegalArgumentException(\"linger value cannot be negative: \" + std::to_string(lingerNs), SOURCEINFO);\n        }\n\n        m_linger.reset(new Value(lingerNs));\n        return *this;\n    }\n\n    inline this_t &sparse(bool sparse)\n    {\n        m_sparse.reset(new Value(sparse ? 1 : 0));\n        return *this;\n    }\n\n    inline this_t &eos(bool eos)\n    {\n        m_eos.reset(new Value(eos ? 1 : 0));\n        return *this;\n    }\n\n    inline this_t &tether(bool tether)\n    {\n        m_tether.reset(new Value(tether ? 1 : 0));\n        return *this;\n    }\n\n    inline this_t &group(bool group)\n    {\n        m_group.reset(new Value(group ? 1 : 0));\n        return *this;\n    }\n\n    inline this_t &rejoin(bool rejoin)\n    {\n        m_rejoin.reset(new Value(rejoin ? 1 : 0));\n        return *this;\n    }\n\n    inline this_t &rejoin(std::nullptr_t nullp)\n    {\n        m_reliable.reset(nullptr);\n        return *this;\n    }\n\n    inline this_t &spiesSimulateConnection(bool spiesSimulateConnection)\n    {\n        m_ssc.reset(new Value(spiesSimulateConnection ? 1 : 0));\n        return *this;\n    }\n\n    inline this_t &spiesSimulateConnection(std::nullptr_t nullp)\n    {\n        m_ssc.reset(nullptr);\n        return *this;\n    }\n\n    inline this_t &isSessionIdTagged(bool isSessionIdTagged)\n    {\n        m_isSessionIdTagged = isSessionIdTagged;\n        return *this;\n    }\n\n    inline this_t &socketSndbufLength(std::uint32_t socketSndbufLength)\n    {\n        m_socketSndbufLength.reset(new Value(socketSndbufLength));\n        return *this;\n    }\n\n    inline this_t &socketSndbufLength(std::nullptr_t socketSndbufLength)\n    {\n        m_socketSndbufLength.reset(nullptr);\n        return *this;\n    }\n\n    inline this_t &socketRcvbufLength(std::uint32_t socketRcvbufLength)\n    {\n        m_socketRcvbufLength.reset(new Value(socketRcvbufLength));\n        return *this;\n    }\n\n    inline this_t &socketRcvbufLength(std::nullptr_t)\n    {\n        m_socketRcvbufLength.reset(nullptr);\n        return *this;\n    }\n\n    inline this_t &receiverWindowLength(std::uint32_t receiverWindowLength)\n    {\n        m_receiverWindowLength.reset(new Value(receiverWindowLength));\n        return *this;\n    }\n\n    inline this_t &receiverWindowLength(std::nullptr_t)\n    {\n        m_receiverWindowLength.reset(nullptr);\n        return *this;\n    }\n\n    inline this_t &initialPosition(std::int64_t position, std::int32_t initialTermId, std::int32_t termLength)\n    {\n        if (position < 0 || 0 != (position & (aeron::concurrent::logbuffer::FrameDescriptor::FRAME_ALIGNMENT - 1)))\n        {\n            throw IllegalArgumentException(\n                \"position not multiple of FRAME_ALIGNMENT: \" + std::to_string(position), SOURCEINFO);\n        }\n\n        aeron::concurrent::logbuffer::LogBufferDescriptor::checkTermLength(termLength);\n        int bitsToShift = BitUtil::numberOfTrailingZeroes(termLength);\n\n        m_termLength.reset(new Value(termLength));\n        m_initialTermId.reset(new Value(initialTermId));\n        m_termId.reset(new Value((position >> bitsToShift) + initialTermId));\n        m_termOffset.reset(new Value(position & (termLength - 1)));\n\n        return *this;\n    }\n\n    inline this_t &mediaReceiveTimestampOffset(const std::string &mediaReceiveTimestampOffset)\n    {\n        m_mediaReceiveTimestampOffset.reset(new std::string(mediaReceiveTimestampOffset));\n        return *this;\n    }\n\n    inline this_t &channelReceiveTimestampOffset(const std::string &receiveTimestampOffset)\n    {\n        m_channelReceiveTimestampOffset.reset(new std::string(receiveTimestampOffset));\n        return *this;\n    }\n\n    inline this_t &channelSendTimestampOffset(const std::string &sendTimestampOffset)\n    {\n        m_channelSendTimestampOffset.reset(new std::string(sendTimestampOffset));\n        return *this;\n    }\n\n    inline this_t &responseCorrelationId(std::int64_t responseCorrelationId)\n    {\n        m_responseCorrelationId.reset(new Value(responseCorrelationId));\n        return *this;\n    }\n\n    inline this_t &nakDelay(std::int64_t nakDelay)\n    {\n        m_nakDelay.reset(new Value(nakDelay));\n        return *this;\n    }\n\n    inline this_t &untetheredWindowLimitTimeout(std::int64_t timeout)\n    {\n        m_untetheredWindowLimitTimeout.reset(new Value(timeout));\n        return *this;\n    }\n\n    inline this_t &untetheredRestingTimeout(std::int64_t timeout)\n    {\n        m_untetheredRestingTimeout.reset(new Value(timeout));\n        return *this;\n    }\n\n    inline this_t &maxResend(std::int32_t maxResend)\n    {\n        m_maxResend.reset(new Value(maxResend));\n        return *this;\n    }\n\n    std::string build()\n    {\n        std::ostringstream sb;\n\n        if (m_prefix && !m_prefix->empty())\n        {\n            sb << *m_prefix << ':';\n        }\n\n        sb << AERON_SCHEME << ':' << *m_media << '?';\n\n        append(sb, TAGS_PARAM_NAME, m_tags);\n        append(sb, ENDPOINT_PARAM_NAME, m_endpoint);\n        append(sb, INTERFACE_PARAM_NAME, m_networkInterface);\n        append(sb, MDC_CONTROL_PARAM_NAME, m_controlEndpoint);\n        append(sb, MDC_CONTROL_MODE_PARAM_NAME, m_controlMode);\n        append(sb, MTU_LENGTH_PARAM_NAME, m_mtu);\n        append(sb, TERM_LENGTH_PARAM_NAME, m_termLength);\n        append(sb, INITIAL_TERM_ID_PARAM_NAME, m_initialTermId);\n        append(sb, TERM_ID_PARAM_NAME, m_termId);\n        append(sb, TERM_OFFSET_PARAM_NAME, m_termOffset);\n\n        if (m_sessionId)\n        {\n            sb << SESSION_ID_PARAM_NAME << '=' << prefixTag(m_isSessionIdTagged, *m_sessionId) << '|';\n        }\n\n        append(sb, TTL_PARAM_NAME, m_ttl);\n\n        if (m_reliable)\n        {\n            sb << RELIABLE_STREAM_PARAM_NAME << '=' << (m_reliable->value == 1 ? \"true\" : \"false\") << '|';\n        }\n\n        append(sb, LINGER_PARAM_NAME, m_linger);\n\n        if (m_alias)\n        {\n            sb << ALIAS_PARAM_NAME << '=' << *m_alias << '|';\n        }\n\n        if (m_cc)\n        {\n            sb << CONGESTION_CONTROL_PARAM_NAME << '=' << *m_cc << '|';\n        }\n\n        if (m_fc)\n        {\n            sb << FLOW_CONTROL_PARAM_NAME << '=' << *m_fc << '|';\n        }\n\n        append(sb, GROUP_TAG_PARAM_NAME, m_gtag);\n\n        if (m_sparse)\n        {\n            sb << SPARSE_PARAM_NAME << '=' << (m_sparse->value == 1 ? \"true\" : \"false\") << '|';\n        }\n\n        if (m_eos)\n        {\n            sb << EOS_PARAM_NAME << '=' << (m_eos->value == 1 ? \"true\" : \"false\") << '|';\n        }\n\n        if (m_tether)\n        {\n            sb << TETHER_PARAM_NAME << '=' << (m_tether->value == 1 ? \"true\" : \"false\") << '|';\n        }\n\n        if (m_group)\n        {\n            sb << GROUP_PARAM_NAME << '=' << (m_group->value == 1 ? \"true\" : \"false\") << '|';\n        }\n\n        if (m_rejoin)\n        {\n            sb << REJOIN_PARAM_NAME << '=' << (m_rejoin->value == 1 ? \"true\" : \"false\") << '|';\n        }\n\n        if (m_ssc)\n        {\n            sb << SPIES_SIMULATE_CONNECTION_PARAM_NAME << '=' << (m_ssc->value == 1 ? \"true\" : \"false\") << '|';\n        }\n\n        append(sb, SOCKET_SNDBUF_PARAM_NAME, m_socketSndbufLength);\n        append(sb, SOCKET_RCVBUF_PARAM_NAME, m_socketRcvbufLength);\n        append(sb, RECEIVER_WINDOW_LENGTH_PARAM_NAME, m_receiverWindowLength);\n        append(sb, MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME, m_mediaReceiveTimestampOffset);\n        append(sb, CHANNEL_RCV_TIMESTAMP_OFFSET_PARAM_NAME, m_channelReceiveTimestampOffset);\n        append(sb, CHANNEL_SND_TIMESTAMP_OFFSET_PARAM_NAME, m_channelSendTimestampOffset);\n        append(sb, RESPONSE_CORRELATION_ID_PARAM_NAME, m_responseCorrelationId);\n        append(sb, NAK_DELAY_PARAM_NAME, m_nakDelay);\n        append(sb, UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME, m_untetheredWindowLimitTimeout);\n        append(sb, UNTETHERED_RESTING_TIMEOUT_PARAM_NAME, m_untetheredRestingTimeout);\n        append(sb, MAX_RESEND_PARAM_NAME, m_maxResend);\n\n        std::string result = sb.str();\n        const char lastChar = result.back();\n\n        if (lastChar == '|' || lastChar == '?')\n        {\n            result.pop_back();\n        }\n\n        return result;\n    }\n\nprivate:\n    struct Value\n    {\n        std::int64_t value;\n\n        explicit Value(std::int64_t v)\n        {\n            value = v;\n        }\n    };\n\n    std::unique_ptr<std::string> m_prefix;\n    std::unique_ptr<std::string> m_media;\n    std::unique_ptr<std::string> m_endpoint;\n    std::unique_ptr<std::string> m_networkInterface;\n    std::unique_ptr<std::string> m_controlEndpoint;\n    std::unique_ptr<std::string> m_controlMode;\n    std::unique_ptr<std::string> m_tags;\n    std::unique_ptr<std::string> m_alias;\n    std::unique_ptr<std::string> m_cc;\n    std::unique_ptr<std::string> m_fc;\n    std::unique_ptr<Value> m_reliable;\n    std::unique_ptr<Value> m_ttl;\n    std::unique_ptr<Value> m_mtu;\n    std::unique_ptr<Value> m_termLength;\n    std::unique_ptr<Value> m_initialTermId;\n    std::unique_ptr<Value> m_termId;\n    std::unique_ptr<Value> m_termOffset;\n    std::unique_ptr<Value> m_sessionId;\n    std::unique_ptr<Value> m_gtag;\n    std::unique_ptr<Value> m_linger;\n    std::unique_ptr<Value> m_sparse;\n    std::unique_ptr<Value> m_eos;\n    std::unique_ptr<Value> m_tether;\n    std::unique_ptr<Value> m_group;\n    std::unique_ptr<Value> m_rejoin;\n    std::unique_ptr<Value> m_ssc;\n    std::unique_ptr<Value> m_socketSndbufLength;\n    std::unique_ptr<Value> m_socketRcvbufLength;\n    std::unique_ptr<Value> m_receiverWindowLength;\n    std::unique_ptr<Value> m_responseCorrelationId;\n    std::unique_ptr<Value> m_nakDelay;\n    std::unique_ptr<Value> m_untetheredWindowLimitTimeout;\n    std::unique_ptr<Value> m_untetheredRestingTimeout;\n    std::unique_ptr<Value> m_maxResend;\n    std::unique_ptr<std::string> m_mediaReceiveTimestampOffset;\n    std::unique_ptr<std::string> m_channelReceiveTimestampOffset;\n    std::unique_ptr<std::string> m_channelSendTimestampOffset;\n    bool m_isSessionIdTagged = false;\n\n    inline static std::string prefixTag(bool isTagged, Value &value)\n    {\n        return isTagged ? (std::string(TAG_PREFIX) + std::to_string(value.value)) : std::to_string(value.value);\n    }\n\n    inline static void append(std::ostringstream &sb, const char *name, std::unique_ptr<Value> &value)\n    {\n        if (value)\n        {\n            sb << name << '=' << value->value << '|';\n        }\n    }\n\n    inline static void append(std::ostringstream &sb, const char *name, std::unique_ptr<std::string> &value)\n    {\n        if (value)\n        {\n            sb << name << '=' << *value << '|';\n        }\n    }\n};\n\n}\n#endif //AERON_CHANNEL_URI_STRING_BUILDER_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/ClientConductor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CLIENT_CONDUCTOR_H\n#define AERON_CLIENT_CONDUCTOR_H\n\n#include <chrono>\n#include \"aeronc.h\"\n\nnamespace aeron\n{\n\ntypedef std::function<long long()> epoch_clock_t;\ntypedef std::function<long long()> nano_clock_t;\n\nclass ClientConductor\n{\npublic:\n    explicit ClientConductor(aeron_t *aeron) : m_aeron(aeron)\n    {\n    }\n\n    inline void onStart()\n    {\n    }\n\n    int doWork()\n    {\n        return aeron_main_do_work(m_aeron);\n    }\n\n    void onClose()\n    {\n\n    }\n\nprivate:\n    aeron_t *m_aeron;\n};\n\ninline long long currentTimeMillis()\n{\n    using namespace std::chrono;\n\n    system_clock::time_point now = system_clock::now();\n    milliseconds ms = duration_cast<milliseconds>(now.time_since_epoch());\n\n    return ms.count();\n}\n\ninline long long systemNanoClock()\n{\n    using namespace std::chrono;\n\n    high_resolution_clock::time_point now = high_resolution_clock::now();\n    nanoseconds ns = duration_cast<nanoseconds>(now.time_since_epoch());\n\n    return ns.count();\n}\n\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/CncFileDescriptor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CNC_FILE_DESCRIPTOR_H\n#define AERON_CNC_FILE_DESCRIPTOR_H\n\n#include \"util/Index.h\"\n#include \"util/MacroUtil.h\"\n#include \"concurrent/AtomicBuffer.h\"\n\nnamespace aeron\n{\n\nusing namespace aeron::util;\nusing namespace aeron::concurrent;\n\n/**\n* Description of the command and control file used between driver and clients\n*\n* File Layout\n* <pre>\n*  +-----------------------------+\n*  |          Meta Data          |\n*  +-----------------------------+\n*  |      to-driver Buffer       |\n*  +-----------------------------+\n*  |      to-clients Buffer      |\n*  +-----------------------------+\n*  |   Counters Metadata Buffer  |\n*  +-----------------------------+\n*  |    Counters Values Buffer   |\n*  +-----------------------------+\n*  |          Error Log          |\n*  +-----------------------------+\n* </pre>\n* <p>\n* Meta Data Layout {@link #CNC_VERSION}\n* <pre>\n*   0                   1                   2                   3\n*   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n*  |                      Aeron CnC Version                        |\n*  +---------------------------------------------------------------+\n*  |                   to-driver buffer length                     |\n*  +---------------------------------------------------------------+\n*  |                  to-clients buffer length                     |\n*  +---------------------------------------------------------------+\n*  |               Counters Metadata buffer length                 |\n*  +---------------------------------------------------------------+\n*  |                Counters Values buffer length                  |\n*  +---------------------------------------------------------------+\n*  |                   Error Log buffer length                     |\n*  +---------------------------------------------------------------+\n*  |                   Client Liveness Timeout                     |\n*  |                                                               |\n*  +---------------------------------------------------------------+\n*  |                    Driver Start Timestamp                     |\n*  |                                                               |\n*  +---------------------------------------------------------------+\n*  |                         Driver PID                            |\n*  |                                                               |\n*  +---------------------------------------------------------------+\n* </pre>\n*/\nnamespace CncFileDescriptor\n{\nstatic const std::string CNC_FILE = \"cnc.dat\";\n}\n}\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/CncFileReader.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CNCFILEREADER_H\n#define AERON_CNCFILEREADER_H\n\n#include \"CncFileDescriptor.h\"\n#include \"concurrent/CountersReader.h\"\n#include \"concurrent/errors/ErrorLogReader.h\"\n\n#include \"aeronc.h\"\n\nnamespace aeron\n{\nusing namespace aeron::util;\nusing namespace aeron::concurrent;\nusing namespace aeron::concurrent::errors;\n\nclass CncFileReader\n{\npublic:\n    static CncFileReader mapExisting(const char *baseDirectory)\n    {\n        aeron_cnc_t *aeron_cnc;\n        if (aeron_cnc_init(&aeron_cnc, baseDirectory, 10000) < 0)\n        {\n            throw IOException(std::string(\"failed to open existing file cnc file in: \") + baseDirectory, SOURCEINFO);\n        }\n\n        return { aeron_cnc };\n    }\n\n    CountersReader countersReader() const\n    {\n        return CountersReader(aeron_cnc_counters_reader(m_aeron_cnc));\n    }\n\n    int readErrorLog(const ErrorLogReader::error_consumer_t &consumer, std::int64_t sinceTimestamp) const\n    {\n        void *clientd = const_cast<void *>(reinterpret_cast<const void *>(&consumer));\n        return (int)aeron_cnc_error_log_read(m_aeron_cnc, errorCallback, clientd, sinceTimestamp);\n    }\n\n    ~CncFileReader()\n    {\n        aeron_cnc_close(m_aeron_cnc);\n    }\n\nprivate:\n    aeron_cnc_t *m_aeron_cnc;\n\n    CncFileReader(aeron_cnc_t *aeron_cnc) : m_aeron_cnc(aeron_cnc)\n    {\n    }\n\n    static void errorCallback(\n        int32_t observation_count,\n        int64_t first_observation_timestamp,\n        int64_t last_observation_timestamp,\n        const char *error,\n        size_t error_length,\n        void *clientd)\n    {\n        ErrorLogReader::error_consumer_t &consumer = *reinterpret_cast<ErrorLogReader::error_consumer_t *>(clientd);\n        consumer(observation_count, first_observation_timestamp, last_observation_timestamp, std::string(error, error_length));\n    }\n};\n}\n\n#endif //AERON_CNCFILEREADER_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/Context.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONTEXT_H\n#define AERON_CONTEXT_H\n\n#include <memory>\n#include <iostream>\n\n#include \"aeron_common.h\"\n#include \"aeronc.h\"\n#include \"CncFileDescriptor.h\"\n#include \"concurrent/AgentRunner.h\"\n#include \"concurrent/CountersReader.h\"\n#include \"status/PublicationErrorFrame.h\"\n#include \"util/Exceptions.h\"\n\nnamespace aeron\n{\n\nusing namespace aeron::concurrent::logbuffer;\nusing namespace aeron::concurrent;\n\nclass Image;\n\n/**\n * Used to represent a null value for when some value is not yet set.\n */\nstatic constexpr std::int32_t NULL_VALUE = -1;\n\n/**\n * Function called by Aeron to deliver notification of an available image.\n *\n * The Image passed may not be the image used internally, but may be copied or moved freely.\n *\n * Implementations should do the minimum work for passing off state to another thread for later processing\n * and should not make a reentrant call back into the Aeron instance.\n *\n * @param image that has become available.\n */\ntypedef std::function<void(Image &image)> on_available_image_t;\n\n/**\n * Function called by Aeron to deliver notification that an Image has become unavailable for polling.\n *\n * The Image passed is not guaranteed to be valid after the callback.\n *\n * Implementations should do the minimum work for passing off state to another thread for later processing\n * and should not make a reentrant call back into the Aeron instance.\n *\n * @param image that has become unavailable\n */\ntypedef std::function<void(Image &image)> on_unavailable_image_t;\n\n/**\n * Function called by Aeron to deliver notification that the media driver has added a Publication successfully.\n *\n * Implementations should do the minimum work for passing off state to another thread for later processing\n * and should not make a reentrant call back into the Aeron instance.\n *\n * @param channel of the Publication\n * @param streamId within the channel of the Publication\n * @param sessionId of the Publication\n * @param correlationId used by the Publication for adding. Aka the registrationId returned by Aeron::addPublication\n */\ntypedef std::function<void(\n    const std::string &channel,\n    std::int32_t streamId,\n    std::int32_t sessionId,\n    std::int64_t correlationId)> on_new_publication_t;\n\n/**\n * Function called by Aeron to deliver notification that the media driver has added a Subscription successfully.\n *\n * Implementations should do the minimum work for passing off state to another thread for later processing\n * and should not make a reentrant call back into the Aeron instance.\n *\n * @param channel of the Subscription\n * @param streamId within the channel of the Subscription\n * @param correlationId used by the Subscription for adding. Aka the registrationId returned by Aeron::addSubscription\n */\ntypedef std::function<void(\n    const std::string &channel,\n    std::int32_t streamId,\n    std::int64_t correlationId)> on_new_subscription_t;\n\n/**\n * Function called by Aeron to deliver notification of a Counter being available.\n *\n * Implementations should do the minimum work for passing off state to another thread for later processing\n * and should not make a reentrant call back into the Aeron instance.\n *\n * @param countersReader for more detail on the counter.\n * @param registrationId for the counter.\n * @param counterId      that is available.\n */\n\ntypedef std::function<void(\n    CountersReader &countersReader,\n    std::int64_t registrationId,\n    std::int32_t counterId)> on_available_counter_t;\n\n/**\n * Function called by Aeron to deliver notification of counter being removed.\n *\n * Implementations should do the minimum work for passing off state to another thread for later processing\n * and should not make a reentrant call back into the Aeron instance.\n *\n * @param countersReader for more counter details.\n * @param registrationId for the counter.\n * @param counterId      that is unavailable.\n */\ntypedef std::function<void(\n    CountersReader &countersReader,\n    std::int64_t registrationId,\n    std::int32_t counterId)> on_unavailable_counter_t;\n\n/**\n * Function called when the Aeron client is closed to notify that the client or any of it associated resources\n * should not be used after this event.\n */\ntypedef std::function<void()> on_close_client_t;\n\ntypedef std::function<void(aeron::status::PublicationErrorFrame &errorFrame)> on_publication_error_frame_t;\n\nconst static long NULL_TIMEOUT = -1;\nconst static long DEFAULT_MEDIA_DRIVER_TIMEOUT_MS = 10000;\nconst static long DEFAULT_RESOURCE_LINGER_MS = 5000;\nconst static long DEFAULT_IDLE_SLEEP_DURATION_MS = 16;\nconst static int MAX_CLIENT_NAME_LENGTH = 100;\n\n/**\n * The Default handler for Aeron runtime exceptions.\n *\n * When a DriverTimeoutException is encountered, this handler will exit the program.\n *\n * The error handler can be overridden by supplying an {@link Context} with a custom handler.\n *\n * @see Context#errorHandler\n */\ninline void defaultErrorHandler(const std::exception &exception)\n{\n    std::cerr << \"ERROR: \" << exception.what();\n\n    try\n    {\n        const auto &sourcedException = dynamic_cast<const SourcedException &>(exception);\n        std::cerr << \" : \" << sourcedException.where();\n    }\n    catch (const std::bad_cast &)\n    {\n        // ignore\n    }\n\n    std::cerr << std::endl;\n    ::exit(-1);\n}\n\ninline void defaultOnNewPublicationHandler(const std::string &, std::int32_t, std::int32_t, std::int64_t)\n{\n}\n\ninline void defaultOnAvailableImageHandler(Image &)\n{\n}\n\ninline void defaultOnNewSubscriptionHandler(const std::string &, std::int32_t, std::int64_t)\n{\n}\n\ninline void defaultOnUnavailableImageHandler(Image &)\n{\n}\n\ninline void defaultOnAvailableCounterHandler(CountersReader &, std::int64_t, std::int32_t)\n{\n}\n\ninline void defaultOnUnavailableCounterHandler(CountersReader &, std::int64_t, std::int32_t)\n{\n}\n\ninline void defaultOnCloseClientHandler()\n{\n}\n\ninline void defaultOnErrorFrameHandler(aeron::status::PublicationErrorFrame &)\n{\n}\n\n/**\n * This class provides configuration for the {@link Aeron} class via the {@link Aeron::Aeron} or {@link Aeron::connect}\n * methods and its overloads. It gives applications some control over the interactions with the Aeron Media Driver.\n * It can also set up error handling as well as application callbacks for connection information from the\n * Media Driver.\n */\nclass Context\n{\n    friend class Aeron;\n\npublic:\n    using this_t = Context;\n\n    Context() = default;\n    Context(const Context &other) = default;\n    Context& operator=(const Context& other) = default;\n\n    /// @cond HIDDEN_SYMBOLS\n    this_t &conclude()\n    {\n        if (!m_isOnNewExclusivePublicationHandlerSet)\n        {\n            newExclusivePublicationHandler(m_onNewPublicationHandler);\n        }\n        return *this;\n    }\n    /// @endcond\n\n    /**\n     * Set the directory that the Aeron client will use to communicate with the media driver.\n     *\n     * @param directory to use\n     * @return reference to this Context instance\n     */\n    inline this_t &aeronDir(const std::string &directory)\n    {\n        m_dirName = directory;\n        return *this;\n    }\n\n    /**\n     * Get the directory that the Aeron client will use to communicate with the media driver.\n     *\n     * @return aeron directory\n     */\n    inline std::string aeronDir()\n    {\n        return m_dirName;\n    }\n\n    /**\n     * Set the name for this Aeron client.\n     *\n     * @param clientName to set.\n     * @return reference to this Context instance.\n     */\n    inline this_t &clientName(const std::string &clientName)\n    {\n        m_clientName = clientName;\n        return *this;\n    }\n\n    /**\n     * Get the name of this Aeron client.\n     *\n     * @return client name or empty string.\n     */\n    inline std::string clientName()\n    {\n        return m_clientName;\n    }\n\n    /**\n     * Return the path to the CnC file used by the Aeron client for communication with the media driver.\n     *\n     * @return path of the CnC file\n     */\n    inline std::string cncFileName() const\n    {\n        return m_dirName + std::string(1, AERON_FILE_SEP) + CncFileDescriptor::CNC_FILE;\n    }\n\n    /**\n     * Set the handler for exceptions from the Aeron client.\n     *\n     * @param handler called when exceptions arise\n     * @return reference to this Context instance\n     *\n     * @see defaultErrorHandler for how the default behavior is handled\n     */\n    inline this_t &errorHandler(const exception_handler_t &handler)\n    {\n        m_exceptionHandler = handler;\n        return *this;\n    }\n\n    /**\n     * Set the handler for successful Aeron::addPublication notifications.\n     *\n     * @param handler called when add is completed successfully\n     * @return reference to this Context instance\n     */\n    inline this_t &newPublicationHandler(const on_new_publication_t &handler)\n    {\n        m_onNewPublicationHandler = handler;\n        return *this;\n    }\n\n    /**\n     * Set the handler for successful Aeron::addExclusivePublication notifications.\n     *\n     * If not set, then will use newPublicationHandler instead.\n     *\n     * @param handler called when add is completed successfully\n     * @return reference to this Context instance\n     */\n    inline this_t &newExclusivePublicationHandler(const on_new_publication_t &handler)\n    {\n        m_onNewExclusivePublicationHandler = handler;\n        m_isOnNewExclusivePublicationHandlerSet = true;\n        return *this;\n    }\n\n    /**\n     * Set the handler for successful Aeron::addSubscription notifications.\n     *\n     * @param handler called when add is completed successfully\n     * @return reference to this Context instance\n     */\n    inline this_t &newSubscriptionHandler(const on_new_subscription_t &handler)\n    {\n        m_onNewSubscriptionHandler = handler;\n        return *this;\n    }\n\n    /**\n     * Set the handler for available image notifications.\n     *\n     * @param handler called when event occurs\n     * @return reference to this Context instance\n     */\n    inline this_t &availableImageHandler(const on_available_image_t &handler)\n    {\n        m_onAvailableImageHandler = handler;\n        return *this;\n    }\n\n    /**\n     * Set the handler for inactive image notifications.\n     *\n     * @param handler called when event occurs\n     * @return reference to this Context instance\n     */\n    inline this_t &unavailableImageHandler(const on_unavailable_image_t &handler)\n    {\n        m_onUnavailableImageHandler = handler;\n        return *this;\n    }\n\n    /**\n     * Set the handler for available counter notifications.\n     *\n     * @param handler called when event occurs\n     * @return reference to this Context instance\n     */\n    inline this_t &availableCounterHandler(const on_available_counter_t &handler)\n    {\n        m_onAvailableCounterHandler = handler;\n        return *this;\n    }\n\n    /**\n     * Set the handler for inactive counter notifications.\n     *\n     * @param handler called when event occurs\n     * @return reference to this Context instance\n     */\n    inline this_t &unavailableCounterHandler(const on_unavailable_counter_t &handler)\n    {\n        m_onUnavailableCounterHandler = handler;\n        return *this;\n    }\n\n    /**\n     * Set the handler to be called when the Aeron client is closed and not longer active.\n     *\n     * @param handler to be called when the Aeron client is closed.\n     * @return reference to this Context instance.\n     */\n    inline this_t &closeClientHandler(const on_close_client_t &handler)\n    {\n        m_onCloseClientHandler = handler;\n        return *this;\n    }\n\n    /**\n     * Set the amount of time, in milliseconds, that this client will wait until it determines the\n     * Media Driver is unavailable. When this happens a DriverTimeoutException will be generated for the error handler.\n     *\n     * @param value Number of milliseconds.\n     * @return reference to this Context instance\n     * @see errorHandler\n     */\n    inline this_t &mediaDriverTimeout(long value)\n    {\n        if (value < 0)\n        {\n            throw IllegalArgumentException(\"timeout less than 0\", SOURCEINFO);\n        }\n\n        m_mediaDriverTimeout = value;\n        return *this;\n    }\n\n    /**\n     * Get the amount of time, in milliseconds, that this client will wait until it determines the\n     * Media Driver is unavailable. When this happens a DriverTimeoutException will be generated for the error handler.\n     *\n     * @return value in number of milliseconds.\n     * @see errorHandler\n     */\n    long mediaDriverTimeout() const\n    {\n        return m_mediaDriverTimeout;\n    }\n\n    /**\n     * Set the amount of time, in milliseconds, that this client will to linger inactive connections and internal\n     * arrays before they are freed.\n     *\n     * @param value Number of milliseconds.\n     * @return reference to this Context instance\n     */\n    inline this_t &resourceLingerTimeout(long value)\n    {\n        if (value < 0)\n        {\n            throw IllegalArgumentException(\"timeout less than 0\", SOURCEINFO);\n        }\n\n        m_resourceLingerTimeout = value;\n\n        return *this;\n    }\n\n    /**\n     * Get the amount of time, in milliseconds, that the client conductor will sleep when idle.\n     *\n     * @return value in number of milliseconds.\n     * @see errorHandler\n     */\n    long idleSleepDuration() const\n    {\n        return m_idleSleepDuration;\n    }\n\n    /**\n     * Set the amount of time, in milliseconds, that the client conductor will sleep when idle.\n     *\n     * @param value Number of milliseconds.\n     * @return reference to this Context instance\n     */\n    inline this_t &idleSleepDuration(long value)\n    {\n        if (value < 0)\n        {\n            throw IllegalArgumentException(\"idle sleep less than 0\", SOURCEINFO);\n        }\n\n        m_idleSleepDuration = value;\n\n        return *this;\n    }\n\n    /**\n     * Set whether to use an invoker to control the conductor agent or spawn a thread.\n     *\n     * @param useConductorAgentInvoker to use an invoker or not.\n     * @return reference to this Context instance\n     */\n    inline this_t &useConductorAgentInvoker(bool useConductorAgentInvoker)\n    {\n        m_useConductorAgentInvoker = useConductorAgentInvoker;\n\n        return *this;\n    }\n\n    /**\n     * Set whether memory mapped files should be pre-touched so they are pre-loaded to avoid later page faults.\n     *\n     * @param preTouchMappedMemory true to pre-touch memory otherwise false.\n     * @return reference to this Context instance\n     */\n    inline this_t &preTouchMappedMemory(bool preTouchMappedMemory)\n    {\n        m_preTouchMappedMemory = preTouchMappedMemory;\n\n        return *this;\n    }\n\n    /**\n     * Set handler to receive notifications when a publication error is received for a publication that this client\n     * is interested in.\n     *\n     * @param handler\n     * @return reference to this Context instance.\n     */\n    inline this_t &errorFrameHandler(on_publication_error_frame_t &handler)\n    {\n        m_onErrorFrameHandler = handler;\n        return *this;\n    }\n\n    static bool requestDriverTermination(\n        const std::string &directory, const std::uint8_t *tokenBuffer, std::size_t tokenLength)\n    {\n        int result = aeron_context_request_driver_termination(directory.c_str(), tokenBuffer, tokenLength);\n        if (result < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return 1 == result;\n    }\n\n    static std::string defaultAeronPath()\n    {\n        char path[AERON_MAX_PATH];\n        int result = aeron_default_path(path, sizeof(path));\n\n        if (result < 0)\n        {\n            std::string errMsg = std::string(\"Failed to get default path, result: \") += std::to_string(result);\n            throw IllegalStateException(errMsg, SOURCEINFO);\n        }\n        else if (AERON_MAX_PATH <= static_cast<std::size_t>(result))\n        {\n            std::string errMsg = std::string(\"Path information was truncated, buffer length: \");\n            errMsg += std::to_string(AERON_MAX_PATH);\n            errMsg += \", path length: \";\n            errMsg += std::to_string(result);\n            errMsg += \", truncated path: \";\n            errMsg += std::string(path, 0, AERON_MAX_PATH);\n\n            throw IllegalStateException(errMsg, SOURCEINFO);\n        }\n\n        return { path, 0, (std::size_t)result };\n    }\n\nprivate:\n    std::string m_dirName = defaultAeronPath();\n    std::string m_clientName;\n    on_available_image_t m_onAvailableImageHandler = defaultOnAvailableImageHandler;\n    on_unavailable_image_t m_onUnavailableImageHandler = defaultOnUnavailableImageHandler;\n    exception_handler_t m_exceptionHandler = defaultErrorHandler;\n    on_new_publication_t m_onNewPublicationHandler = defaultOnNewPublicationHandler;\n    bool m_isOnNewExclusivePublicationHandlerSet = false;\n    on_new_publication_t m_onNewExclusivePublicationHandler = defaultOnNewPublicationHandler;\n    on_new_subscription_t m_onNewSubscriptionHandler = defaultOnNewSubscriptionHandler;\n    on_available_counter_t m_onAvailableCounterHandler = defaultOnAvailableCounterHandler;\n    on_unavailable_counter_t m_onUnavailableCounterHandler = defaultOnUnavailableCounterHandler;\n    on_close_client_t m_onCloseClientHandler = defaultOnCloseClientHandler;\n    on_publication_error_frame_t m_onErrorFrameHandler = defaultOnErrorFrameHandler;\n    long m_idleSleepDuration = DEFAULT_IDLE_SLEEP_DURATION_MS;\n    long m_mediaDriverTimeout = DEFAULT_MEDIA_DRIVER_TIMEOUT_MS;\n    long m_resourceLingerTimeout = DEFAULT_RESOURCE_LINGER_MS;\n    bool m_useConductorAgentInvoker = false;\n    bool m_preTouchMappedMemory = false;\n\n    void attachCallbacksToContext(aeron_context_t *context)\n    {\n        if (aeron_context_set_dir(context, m_dirName.c_str()) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_client_name(context, m_clientName.c_str()) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_driver_timeout_ms(context, static_cast<std::uint64_t>(m_mediaDriverTimeout)) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        std::uint64_t resource_duration_ns = static_cast<std::uint64_t>(m_resourceLingerTimeout) * 1000000;\n        if (aeron_context_set_resource_linger_duration_ns(context, resource_duration_ns) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        std::uint64_t sleep_duration_ns = static_cast<std::uint64_t>(m_idleSleepDuration) * 1000000;\n        if (aeron_context_set_idle_sleep_duration_ns(context, sleep_duration_ns) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_idle_strategy_init_args(context, std::to_string(sleep_duration_ns).c_str()) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_idle_strategy(context, \"sleep-ns\") < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        aeron_context_set_use_conductor_agent_invoker(context, m_useConductorAgentInvoker);\n        aeron_context_set_pre_touch_mapped_memory(context, m_preTouchMappedMemory);\n\n        if (aeron_context_set_error_handler(\n            context,\n            errorHandlerCallback,\n            const_cast<void *>(reinterpret_cast<const void *>(&m_exceptionHandler))) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_on_new_publication(\n            context,\n            newPublicationHandlerCallback,\n            const_cast<void *>(reinterpret_cast<const void *>(&m_onNewPublicationHandler))) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_on_new_exclusive_publication(\n            context,\n            newPublicationHandlerCallback,\n            const_cast<void *>(reinterpret_cast<const void *>(&m_onNewExclusivePublicationHandler))) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_on_available_counter(\n            context,\n            availableCounterHandlerCallback,\n            const_cast<void *>(reinterpret_cast<const void *>(&m_onAvailableCounterHandler))) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_on_unavailable_counter(\n            context,\n            availableCounterHandlerCallback,\n            const_cast<void *>(reinterpret_cast<const void *>(&m_onUnavailableCounterHandler))) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_on_close_client(\n            context,\n            closeClientHandlerCallback,\n            const_cast<void *>(reinterpret_cast<const void *>(&m_onCloseClientHandler))) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_on_new_subscription(\n            context,\n            newSubscriptionHandlerCallback,\n            const_cast<void *>(reinterpret_cast<const void *>(&m_onNewSubscriptionHandler))) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n\n        if (aeron_context_set_publication_error_frame_handler(\n            context,\n            errorFrameHandlerCallback,\n            const_cast<void *>(reinterpret_cast<const void *>(&m_onErrorFrameHandler))) < 0)\n        {\n            throw IllegalArgumentException(std::string(aeron_errmsg()), SOURCEINFO);\n        }\n    }\n\n    static void errorHandlerCallback(void *clientd, int errcode, const char *message)\n    {\n        try\n        {\n            AERON_MAP_TO_SOURCED_EXCEPTION_AND_THROW(errcode, message);\n        }\n        catch (SourcedException &exception)\n        {\n            exception_handler_t &handler = *reinterpret_cast<exception_handler_t *>(clientd);\n            handler(exception);\n        }\n    }\n\n    static void newPublicationHandlerCallback(\n        void *clientd,\n        aeron_async_add_publication_t * /* async */,\n        const char *channel,\n        std::int32_t stream_id,\n        std::int32_t session_id,\n        std::int64_t correlation_id)\n    {\n        on_new_publication_t &handler = *reinterpret_cast<on_new_publication_t *>(clientd);\n        handler(std::string(channel), stream_id, session_id, correlation_id);\n    }\n\n    static void newSubscriptionHandlerCallback(\n        void *clientd,\n        aeron_async_add_subscription_t * /* async */,\n        const char *channel,\n        std::int32_t stream_id,\n        std::int64_t correlation_id)\n    {\n        on_new_subscription_t &handler = *reinterpret_cast<on_new_subscription_t *>(clientd);\n        handler(std::string(channel), stream_id, correlation_id);\n    }\n\n    static void availableCounterHandlerCallback(\n        void *clientd, aeron_counters_reader_t *counters_reader, std::int64_t registration_id, std::int32_t counter_id)\n    {\n        on_available_counter_t &handler = *reinterpret_cast<on_available_counter_t *>(clientd);\n        CountersReader countersReader(counters_reader);\n        handler(countersReader, registration_id, counter_id);\n    }\n\n    static void closeClientHandlerCallback(void *clientd)\n    {\n        on_close_client_t &handler = *reinterpret_cast<on_close_client_t *>(clientd);\n        handler();\n    }\n\n    static void errorFrameHandlerCallback(void *clientd, aeron_publication_error_values_t *error_frame)\n    {\n        on_publication_error_frame_t &handler = *reinterpret_cast<on_publication_error_frame_t *>(clientd);\n        aeron::status::PublicationErrorFrame errorFrame{error_frame};\n        handler(errorFrame);\n    }\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/ControlledFragmentAssembler.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CONTROLLEDFRAGMENTASSEMBLER_H\n#define AERON_CONTROLLEDFRAGMENTASSEMBLER_H\n\n#include <unordered_map>\n\n#include \"Aeron.h\"\n\nnamespace aeron\n{\n\nstatic constexpr std::size_t DEFAULT_CONTROLLED_FRAGMENT_ASSEMBLY_BUFFER_LENGTH = 4096;\n\n/**\n * A handler that sits in a chain-of-responsibility pattern that reassembles fragmented messages\n * so that the next handler in the chain only sees whole messages.\n * <p>\n * Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary\n * buffer for reassembly before delegation.\n * <p>\n * The Header passed to the delegate on assembling a message will be that of the last fragment.\n * <p>\n * Session based buffers will be allocated and grown as necessary based on the length of messages to be assembled.\n * When sessions go inactive see {@link on_unavailable_image_t}, it is possible to free the buffer by calling\n * {@link #deleteSessionBuffer(std::int32_t)}.\n */\nclass ControlledFragmentAssembler\n{\npublic:\n    /**\n     * Construct an adapter to reassembly message fragments and delegate on only whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for each session.\n     */\n    explicit ControlledFragmentAssembler(\n        const controlled_poll_fragment_handler_t &delegate,\n        std::size_t initialBufferLength = DEFAULT_CONTROLLED_FRAGMENT_ASSEMBLY_BUFFER_LENGTH) :\n        m_delegate(delegate)\n    {\n        aeron_controlled_fragment_assembler_create(&m_fragment_assembler, handlerCallback, reinterpret_cast<void *>(this));\n    }\n\n    ~ControlledFragmentAssembler()\n    {\n        aeron_controlled_fragment_assembler_delete(m_fragment_assembler);\n    }\n\n    ControlledFragmentAssembler(ControlledFragmentAssembler& other) = delete;\n    ControlledFragmentAssembler(ControlledFragmentAssembler&& other) = delete;\n    ControlledFragmentAssembler& operator=(ControlledFragmentAssembler& other) = delete;\n    ControlledFragmentAssembler& operator=(ControlledFragmentAssembler&& other) = delete;\n\n    /**\n     * Compose a controlled_poll_fragment_handler_t that calls the this ControlledFragmentAssembler instance for\n     * reassembly. Suitable for passing to Subscription::controlledPoll(controlled_poll_fragment_handler_t, int).\n     *\n     * @return controlled_poll_fragment_handler_t composed with the ControlledFragmentAssembler instance\n     */\n    controlled_poll_fragment_handler_t handler()\n    {\n        return [this](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            return this->onFragment(buffer, offset, length, header);\n        };\n    }\n\n    /**\n     * Free an existing session buffer to reduce memory pressure when an Image goes inactive or no more\n     * large messages are expected.\n     *\n     * @param sessionId to have its buffer freed\n     */\n    void deleteSessionBuffer(std::int32_t sessionId)\n    {\n    }\n\nprivate:\n    controlled_poll_fragment_handler_t m_delegate;\n    aeron_controlled_fragment_assembler_t *m_fragment_assembler = nullptr;\n\n    static aeron_controlled_fragment_handler_action_t handlerCallback(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto *assembler = reinterpret_cast<ControlledFragmentAssembler *>(clientd);\n        Header headerWrapper{header};\n        AtomicBuffer bufferWrapper{const_cast<uint8_t *>(buffer), length};\n        ControlledPollAction action = assembler->m_delegate(bufferWrapper, 0, (util::index_t)length, headerWrapper);\n\n        switch (action)\n        {\n            case ControlledPollAction::ABORT:\n                return AERON_ACTION_ABORT;\n                break;\n            case ControlledPollAction::BREAK:\n                return AERON_ACTION_BREAK;\n                break;\n            case ControlledPollAction::COMMIT:\n                return AERON_ACTION_COMMIT;\n                break;\n            case ControlledPollAction::CONTINUE:\n                return AERON_ACTION_CONTINUE;\n                break;\n        }\n\n        throw IllegalArgumentException(\"unknown action\", SOURCEINFO);\n    }\n\n    ControlledPollAction onFragment(AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n    {\n        aeron_controlled_fragment_handler_action_t action = aeron_controlled_fragment_assembler_handler(\n            m_fragment_assembler,\n            buffer.buffer() + offset,\n            length,\n            header.hdr());\n\n        switch (action)\n        {\n            case AERON_ACTION_ABORT:\n                return ControlledPollAction::ABORT;\n                break;\n            case AERON_ACTION_BREAK:\n                return ControlledPollAction::BREAK;\n                break;\n            case AERON_ACTION_COMMIT:\n                return ControlledPollAction::COMMIT;\n                break;\n            case AERON_ACTION_CONTINUE:\n                return ControlledPollAction::CONTINUE;\n                break;\n        }\n\n        throw IllegalArgumentException(\"unknown action\", SOURCEINFO);\n    }\n};\n\n}\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/Counter.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_COUNTER_H\n#define AERON_COUNTER_H\n\n#include <cstdint>\n#include <memory>\n\n#include \"concurrent/AtomicCounter.h\"\n#include \"concurrent/CountersReader.h\"\n\nnamespace aeron\n{\n\nusing namespace aeron::concurrent;\n\nclass Aeron;\nclass ClientConductor;\n\nclass Counter : public AtomicCounter\n{\npublic:\n    /// @cond HIDDEN_SYMBOLS\n    Counter(\n        const std::shared_ptr<Aeron> &aeronRef,\n        CountersReader &reader,\n        aeron_counter_t *counter,\n        std::int64_t registrationId) :\n        AtomicCounter(counter),\n        m_aeronRef(aeronRef),\n        m_reader(reader),\n        m_registrationId(registrationId)\n    {\n    }\n    /// @endcond\n\n    Counter(CountersReader &reader, std::int64_t registrationId, std::int32_t counterId) :\n        AtomicCounter(reader.getCounterAddress(counterId), registrationId, counterId),\n        m_reader(reader),\n        m_registrationId(registrationId)\n    {\n    }\n\n    inline std::int64_t registrationId() const\n    {\n        return m_registrationId;\n    }\n\n    std::int32_t state() const\n    {\n        return m_reader.getCounterState(id());\n    }\n\n    std::string label() const\n    {\n        return m_reader.getCounterLabel(id());\n    }\n\n    bool isClosed() const\n    {\n        return aeron_counter_is_closed(counter());\n    }\n\nprivate:\n    std::shared_ptr<Aeron> m_aeronRef; // ensure Aeron instance is being deleted after its children\n    CountersReader &m_reader;\n    std::int64_t m_registrationId;\n};\n\n}\n#endif //AERON_COUNTER_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/ExclusivePublication.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_EXCLUSIVE_PUBLICATION_H\n#define AERON_EXCLUSIVE_PUBLICATION_H\n\n#include <array>\n#include <memory>\n#include <string>\n\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/logbuffer/BufferClaim.h\"\n#include \"concurrent/status/UnsafeBufferPosition.h\"\n#include \"util/Exceptions.h\"\n#include \"Publication.h\"\n\n#include \"aeronc.h\"\n\nnamespace aeron\n{\n\nusing namespace aeron::concurrent::status;\n\nclass Aeron;\n\n/**\n * Aeron Publisher API for sending messages to subscribers of a given channel and streamId pair. ExclusivePublications\n * each get their own session id so multiple can be concurrently active on the same media driver as independent streams.\n *\n * {@link ExclusivePublication}s are created via the {@link Aeron#addExclusivePublication(String, int)} method,\n * and messages are sent via one of the {@link #offer(DirectBuffer)} methods, or a\n * {@link #tryClaim(int, ExclusiveBufferClaim)} and {@link ExclusiveBufferClaim#commit()} method combination.\n *\n * {@link ExclusivePublication}s have the potential to provide greater throughput than {@link Publication}s.\n *\n * The APIs tryClaim and offer are non-blocking.\n *\n * <b>Note:</b> ExclusivePublication instances are NOT threadsafe.\n *\n * @see Aeron#addExclusivePublication(String, int)\n * @see BufferClaim\n */\nclass ExclusivePublication\n{\npublic:\n\n    /// @cond HIDDEN_SYMBOLS\n    ExclusivePublication(\n        const std::shared_ptr<Aeron> &aeronRef, aeron_t *aeron, aeron_exclusive_publication_t *publication) :\n        m_aeronRef(aeronRef),\n        m_aeron(aeron),\n        m_publication(publication)\n    {\n        if (aeron_exclusive_publication_constants(m_publication, &m_constants) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n        m_channel.append(m_constants.channel);\n    }\n    /// @endcond\n\n    ~ExclusivePublication()\n    {\n        aeron_exclusive_publication_close(m_publication, nullptr, nullptr);\n\n        for (const std::pair<const std::int64_t, AsyncDestination *>& e : m_pendingDestinations)\n        {\n            aeron_async_cmd_free(e.second);\n        }\n    }\n\n    inline void revokeOnClose()\n    {\n        aeron_exclusive_publication_revoke_on_close(m_publication);\n    }\n\n    inline void revoke()\n    {\n        if (aeron_exclusive_publication_revoke(m_publication, nullptr, nullptr) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        m_publication = nullptr;\n    }\n\n    /**\n     * Media address for delivery to the channel.\n     *\n     * @return Media address for delivery to the channel.\n     */\n    inline const std::string &channel() const\n    {\n        return m_channel;\n    }\n\n    /**\n     * Stream identity for scoping within the channel media address.\n     *\n     * @return Stream identity for scoping within the channel media address.\n     */\n    inline std::int32_t streamId() const\n    {\n        return m_constants.stream_id;\n    }\n\n    /**\n     * Session under which messages are published. Identifies this Publication instance.\n     *\n     * @return the session id for this publication.\n     */\n    inline std::int32_t sessionId() const\n    {\n        return m_constants.session_id;\n    }\n\n    /**\n     * The initial term id assigned when this Publication was created. This can be used to determine how many\n     * terms have passed since creation.\n     *\n     * @return the initial term id.\n     */\n    inline std::int32_t initialTermId() const\n    {\n        return m_constants.initial_term_id;\n    }\n\n    /**\n     * Get the original registration used to register this Publication with the media driver by the first publisher.\n     *\n     * @return the original registrationId of the publication.\n     */\n    inline std::int64_t originalRegistrationId() const\n    {\n        return m_constants.original_registration_id;\n    }\n\n    /**\n     * Registration Id returned by Aeron::addPublication when this Publication was added.\n     *\n     * @return the registrationId of the publication.\n     */\n    inline std::int64_t registrationId() const\n    {\n        return m_constants.registration_id;\n    }\n\n    /**\n     * ExclusivePublication instances are always original.\n     *\n     * @return true.\n     */\n    static constexpr bool isOriginal()\n    {\n        return true;\n    }\n\n    /**\n     * Maximum message length supported in bytes.\n     *\n     * @return maximum message length supported in bytes.\n     */\n    inline util::index_t maxMessageLength() const\n    {\n        return static_cast<util::index_t>(m_constants.max_message_length);\n    }\n\n    /**\n     * Maximum length of a message payload that fits within a message fragment.\n     *\n     * This is the MTU length minus the message fragment header length.\n     *\n     * @return maximum message fragment payload length.\n     */\n    inline util::index_t maxPayloadLength() const\n    {\n        return static_cast<util::index_t>(m_constants.max_payload_length);\n    }\n\n    /**\n     * Get the length in bytes for each term partition in the log buffer.\n     *\n     * @return the length in bytes for each term partition in the log buffer.\n     */\n    inline std::int32_t termBufferLength() const\n    {\n        return static_cast<std::int32_t>(m_constants.term_buffer_length);\n    }\n\n    /**\n     * Number of bits to right shift a position to get a term count for how far the stream has progressed.\n     *\n     * @return of bits to right shift a position to get a term count for how far the stream has progressed.\n     */\n    inline std::int32_t positionBitsToShift() const\n    {\n        return static_cast<std::int32_t>(m_constants.position_bits_to_shift);\n    }\n\n    /**\n     * Has this Publication seen an active subscriber recently?\n     *\n     * @return true if this Publication has seen an active subscriber recently.\n     */\n    inline bool isConnected() const\n    {\n        return aeron_exclusive_publication_is_connected(m_publication);\n    }\n\n    /**\n     * Has this object been closed and should no longer be used?\n     *\n     * @return true if it has been closed otherwise false.\n     */\n    inline bool isClosed() const\n    {\n        return aeron_exclusive_publication_is_closed(m_publication);\n    }\n\n    /**\n     * Get the max possible position the stream can reach given term length.\n     *\n     * @return the max possible position the stream can reach given term length.\n     */\n    inline std::int64_t maxPossiblePosition() const\n    {\n        return m_constants.max_possible_position;\n    }\n\n    /**\n     * Get the current position to which the publication has advanced for this stream.\n     *\n     * @return the current position to which the publication has advanced for this stream or {@link CLOSED}.\n     */\n    inline std::int64_t position() const\n    {\n        std::int64_t position = aeron_exclusive_publication_position(m_publication);\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            if (nullptr == m_publication)\n            {\n                return AERON_PUBLICATION_CLOSED;\n            }\n\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Get the position limit beyond which this {@link Publication} will be back pressured.\n     *\n     * This should only be used as a guide to determine when back pressure is likely to be applied.\n     *\n     * @return the position limit beyond which this {@link Publication} will be back pressured.\n     */\n    inline std::int64_t publicationLimit() const\n    {\n        std::int64_t limit = aeron_exclusive_publication_position_limit(m_publication);\n        if (AERON_PUBLICATION_ERROR == limit)\n        {\n            if (nullptr == m_publication)\n            {\n                return AERON_PUBLICATION_CLOSED;\n            }\n\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return limit;\n    }\n\n    /**\n     * Get the counter id used to represent the publication limit.\n     *\n     * @return the counter id used to represent the publication limit.\n     */\n    inline std::int32_t publicationLimitId() const\n    {\n        return m_constants.publication_limit_counter_id;\n    }\n\n    /**\n     * Available window for offering into a publication before the {@link #positionLimit()} is reached.\n     *\n     * @return  window for offering into a publication before the {@link #positionLimit()} is reached. If\n     * the publication is closed then {@link #CLOSED} will be returned.\n     */\n    inline std::int64_t availableWindow() const\n    {\n        const std::int64_t limit = publicationLimit();\n        return AERON_PUBLICATION_CLOSED != limit ? limit - position() : AERON_PUBLICATION_CLOSED;\n    }\n\n    /**\n     * Get the counter id used to represent the channel status.\n     *\n     * @return the counter id used to represent the channel status.\n     */\n    inline std::int32_t channelStatusId() const\n    {\n        return m_constants.channel_status_indicator_id;\n    }\n\n    /**\n     * Get the status for the channel of this {@link Publication}\n     *\n     * @return status code for this channel\n     */\n    std::int64_t channelStatus() const;\n\n    /**\n     * Fetches the local socket addresses for this publication. If the channel is not\n     * {@link aeron::concurrent::status::ChannelEndpointStatus::CHANNEL_ENDPOINT_ACTIVE}, then this will return an\n     * empty list.\n     *\n     * The format is as follows:\n     * <br>\n     * <br>\n     * IPv4: <code>ip address:port</code>\n     * <br>\n     * IPv6: <code>[ip6 address]:port</code>\n     * <br>\n     * <br>\n     * This is to match the formatting used in the Aeron URI\n     *\n     * @return local socket address for this subscription.\n     * @see #channelStatus()\n     */\n    std::vector<std::string> localSocketAddresses() const\n    {\n        std::vector<std::string> localAddresses;\n        std::uint8_t buffer[AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN];\n        // Publications only have a single local address.\n        aeron_iovec_t iov;\n        iov.iov_base = buffer;\n        iov.iov_len = sizeof(buffer);\n\n        if (aeron_exclusive_publication_local_sockaddrs(m_publication, &iov, 1) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        localAddresses.push_back(std::string(reinterpret_cast<char *>(buffer)));\n\n        return localAddresses;\n    }\n\n    /**\n     * Non-blocking publish of a buffer containing a message.\n     *\n     * @param buffer containing message.\n     * @param offset offset in the buffer at which the encoded message begins.\n     * @param length in bytes of the encoded message.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    inline std::int64_t offer(\n        const concurrent::AtomicBuffer &buffer,\n        util::index_t offset,\n        util::index_t length,\n        const on_reserved_value_supplier_t &reservedValueSupplier)\n    {\n        std::int64_t position = aeron_exclusive_publication_offer(\n            m_publication,\n            buffer.buffer() + offset,\n            static_cast<std::size_t>(length),\n            reservedValueSupplierCallback,\n            (void *)&reservedValueSupplier);\n\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            if (nullptr == m_publication)\n            {\n                return AERON_PUBLICATION_CLOSED;\n            }\n\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Non-blocking publish of a buffer containing a message.\n     *\n     * @param buffer containing message.\n     * @param offset offset in the buffer at which the encoded message begins.\n     * @param length in bytes of the encoded message.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    inline std::int64_t offer(const concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length)\n    {\n        std::int64_t position = aeron_exclusive_publication_offer(\n            m_publication, buffer.buffer() + offset, static_cast<std::size_t>(length), nullptr, nullptr);\n\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            if (nullptr == m_publication)\n            {\n                return AERON_PUBLICATION_CLOSED;\n            }\n\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Non-blocking publish of a buffer containing a message.\n     *\n     * @param buffer containing message.\n     * @return The new stream position on success, otherwise {@link BACK_PRESSURED} or {@link NOT_CONNECTED}.\n     */\n    inline std::int64_t offer(const concurrent::AtomicBuffer &buffer)\n    {\n        return offer(buffer, 0, buffer.capacity());\n    }\n\n    /**\n     * Non-blocking publish of buffers containing a message.\n     *\n     * @param startBuffer containing part of the message.\n     * @param lastBuffer after the message.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    template<class BufferIterator> std::int64_t offer(\n        BufferIterator startBuffer,\n        BufferIterator lastBuffer,\n        const on_reserved_value_supplier_t &reservedValueSupplier = DEFAULT_RESERVED_VALUE_SUPPLIER)\n    {\n        std::vector<aeron_iovec_t> iov;\n        std::size_t length = 0;\n        for (BufferIterator it = startBuffer; it != lastBuffer; ++it)\n        {\n            if (AERON_COND_EXPECT(length + it->capacity() < 0, false))\n            {\n                throw aeron::util::IllegalStateException(\n                    \"length overflow: \" + std::to_string(length) + \" + \" + std::to_string(it->capacity()) +\n                    \" > \" + std::to_string(length + it->capacity()),\n                    SOURCEINFO);\n            }\n\n            aeron_iovec_t buf;\n            buf.iov_base = it->buffer();\n            buf.iov_len = it->capacity();\n            iov.push_back(buf);\n        }\n\n        const std::int64_t position = aeron_exclusive_publication_offerv(\n            m_publication, iov.data(), iov.size(), reservedValueSupplierCallback, (void *)&reservedValueSupplier);\n\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            if (nullptr == m_publication)\n            {\n                return AERON_PUBLICATION_CLOSED;\n            }\n\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Non-blocking publish of array of buffers containing a message.\n     *\n     * @param buffers containing parts of the message.\n     * @param length of the array of buffers.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    // codeql[cpp/array-in-interface]\n    std::int64_t offer(\n        const concurrent::AtomicBuffer buffers[],\n        std::size_t length,\n        const on_reserved_value_supplier_t &reservedValueSupplier = DEFAULT_RESERVED_VALUE_SUPPLIER)\n    {\n        return offer(buffers, buffers + length, reservedValueSupplier);\n    }\n\n    /**\n     * Non-blocking publish of array of buffers containing a message.\n     *\n     * @param buffers containing parts of the message.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    template<std::size_t N> std::int64_t offer(\n        const std::array<concurrent::AtomicBuffer, N> &buffers,\n        const on_reserved_value_supplier_t &reservedValueSupplier = DEFAULT_RESERVED_VALUE_SUPPLIER)\n    {\n        return offer(buffers.begin(), buffers.end(), reservedValueSupplier);\n    }\n\n    /**\n     * Non-blocking publish of a simple const data buffer and length containing a message\n     *\n     * @param buffer contain the message.\n     * @param length of the message.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    std::int64_t offer(\n        const std::uint8_t *buffer,\n        std::size_t length,\n        const on_reserved_value_supplier_t &reservedValueSupplier = DEFAULT_RESERVED_VALUE_SUPPLIER)\n    {\n        std::int64_t position = aeron_exclusive_publication_offer(\n            m_publication,\n            buffer,\n            static_cast<std::size_t>(length),\n            reservedValueSupplierCallback,\n            (void *)&reservedValueSupplier);\n\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            if (nullptr == m_publication)\n            {\n                return AERON_PUBLICATION_CLOSED;\n            }\n\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n     * Once the message has been written then {@link BufferClaim#commit()} should be called thus making it available.\n     * <p>\n     * <b>Note:</b> This method can only be used for message lengths less than MTU length minus header.\n     *\n     * @code\n     *     BufferClaim bufferClaim; // Can be stored and reused to avoid allocation\n     *\n     *     if (publication->tryClaim(messageLength, bufferClaim) > 0)\n     *     {\n     *         try\n     *         {\n     *              AtomicBuffer &buffer = bufferClaim.buffer();\n     *              const index_t offset = bufferClaim.offset();\n     *\n     *              // Work with buffer directly or wrap with a flyweight\n     *         }\n     *         finally\n     *         {\n     *             bufferClaim.commit();\n     *         }\n     *     }\n     * @endcode\n     *\n     * @param length      of the range to claim, in bytes..\n     * @param bufferClaim to be populate if the claim succeeds.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     * @throws IllegalArgumentException if the length is greater than max payload length within an MTU.\n     * @see BufferClaim::commit\n     * @see BufferClaim::abort\n     */\n    inline std::int64_t tryClaim(util::index_t length, concurrent::logbuffer::BufferClaim &bufferClaim)\n    {\n        aeron_buffer_claim_t temp_claim;\n        const std::int64_t position = aeron_exclusive_publication_try_claim(\n            m_publication, static_cast<std::size_t>(length), &temp_claim);\n\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            if (nullptr == m_publication)\n            {\n                return AERON_PUBLICATION_CLOSED;\n            }\n\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        bufferClaim.wrap(temp_claim.frame_header, DataFrameHeader::LENGTH + length);\n\n        return position;\n    }\n\n    /**\n     * Add a destination manually to a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to add\n     * @return async object to track the progress of the command\n     */\n    AsyncDestination *addDestinationAsync(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = nullptr;\n        if (aeron_exclusive_publication_async_add_destination(&async, m_aeron, m_publication, endpointChannel.c_str()) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return async;\n    }\n\n    /**\n     * Add a destination manually to a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to add\n     * @return correlation id for the add command\n     */\n    std::int64_t addDestination(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = addDestinationAsync(endpointChannel);\n        std::int64_t correlationId = aeron_async_destination_get_registration_id(async);\n\n        m_pendingDestinations[correlationId] = async;\n\n        return correlationId;\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to remove\n     * @return async object to track the progress of the command\n     */\n    AsyncDestination *removeDestinationAsync(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = nullptr;\n        if (aeron_exclusive_publication_async_remove_destination(\n            &async, m_aeron, m_publication, endpointChannel.c_str()) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return async;\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to remove\n     * @return correlation id for the remove command\n     */\n    std::int64_t removeDestination(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = removeDestinationAsync(endpointChannel);\n        std::int64_t correlationId = aeron_async_destination_get_registration_id(async);\n\n        m_pendingDestinations[correlationId] = async;\n\n        return correlationId;\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication.\n     *\n     * @param destinationRegistrationId for the destination to remove\n     * @return correlation id for the remove command\n     */\n    std::int64_t removeDestination(std::int64_t destinationRegistrationId)\n    {\n        AsyncDestination *async = removeDestinationAsync(destinationRegistrationId);\n        std::int64_t correlationId = aeron_async_destination_get_registration_id(async);\n\n        m_pendingDestinations[correlationId] = async;\n\n        return correlationId;\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication.\n     *\n     * @param destinationRegistrationId for the destination to remove\n     * @return async object to track the progress of the command\n     */\n    AsyncDestination *removeDestinationAsync(std::int64_t destinationRegistrationId)\n    {\n        AsyncDestination *async = nullptr;\n        if (aeron_exclusive_publication_async_remove_destination_by_id(\n            &async, m_aeron, m_publication, destinationRegistrationId) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return async;\n    }\n\n    /**\n     * Retrieve the status of the associated add or remove destination operation with the given correlationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the correlationId is unknown, then an exception is thrown.\n     * - If the media driver has not answered the add/remove command, then a false is returned.\n     * - If the media driver has successfully added or removed the destination then true is returned.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Publication::addDestination\n     * @see Publication::removeDestination\n     *\n     * @param async used to track the progress of the destination command.\n     * @return true for added or false if not.\n     */\n    bool findDestinationResponse(AsyncDestination *async)\n    {\n        int result = aeron_exclusive_publication_async_destination_poll(async);\n        if (result < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return 0 < result;\n    }\n\n    /**\n     * Retrieve the status of the associated add or remove destination operation with the given correlationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the correlationId is unknown, then an exception is thrown.\n     * - If the media driver has not answered the add/remove command, then a false is returned.\n     * - If the media driver has successfully added or removed the destination then true is returned.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Publication::addDestination\n     * @see Publication::removeDestination\n     *\n     * @param correlationId of the add/remove command returned by Publication::addDestination\n     * or Publication::removeDestination\n     * @return true for added or false if not.\n     */\n    bool findDestinationResponse(std::int64_t correlationId)\n    {\n        auto search = m_pendingDestinations.find(correlationId);\n        if (search == m_pendingDestinations.end())\n        {\n            throw IllegalArgumentException(\"Unknown correlation id\", SOURCEINFO);\n        }\n\n        auto async = search->second;\n        try\n        {\n            bool result = findDestinationResponse(async);\n            if (result)\n            {\n                m_pendingDestinations.erase(correlationId);\n            }\n            return result;\n        }\n        catch (...)\n        {\n            m_pendingDestinations.erase(correlationId);\n            throw;\n        }\n    }\n\n    /// @cond HIDDEN_SYMBOLS\n    aeron_exclusive_publication_t *publication()\n    {\n        return m_publication;\n    }\n    /// @endcond\n\nprivate:\n    std::shared_ptr<Aeron> m_aeronRef; // ensure Aeron instance is being deleted after its children\n    aeron_t *m_aeron = nullptr;\n    aeron_exclusive_publication_t *m_publication = nullptr;\n    aeron_publication_constants_t m_constants = {};\n    std::string m_channel = {};\n    std::unordered_map<std::int64_t, AsyncDestination *> m_pendingDestinations = {};\n\n    static std::int64_t reservedValueSupplierCallback(void *clientd, std::uint8_t *buffer, std::size_t frame_length)\n    {\n        on_reserved_value_supplier_t &supplier = *static_cast<on_reserved_value_supplier_t *>(clientd);\n        AtomicBuffer atomicBuffer(buffer, frame_length);\n        return supplier(atomicBuffer, 0, static_cast<util::index_t>(frame_length));\n    }\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/FragmentAssembler.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_FRAGMENT_ASSEMBLER_H\n#define AERON_FRAGMENT_ASSEMBLER_H\n\n#include <unordered_map>\n\n#include \"Aeron.h\"\n\nnamespace aeron\n{\n\nstatic constexpr std::size_t DEFAULT_FRAGMENT_ASSEMBLY_BUFFER_LENGTH = 4096;\n\n/**\n * A handler that sits in a chain-of-responsibility pattern that reassembles fragmented messages\n * so that the next handler in the chain only sees whole messages.\n * <p>\n * Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary\n * buffer for reassembly before delegation.\n * <p>\n * The Header passed to the delegate on assembling a message will be that of the last fragment.\n * <p>\n * Session based buffers will be allocated and grown as necessary based on the length of messages to be assembled.\n * When sessions go inactive see {@link on_unavailable_image_t}, it is possible to free the buffer by calling\n * {@link #deleteSessionBuffer(std::int32_t)}.\n */\nclass FragmentAssembler\n{\npublic:\n\n    /**\n     * Construct an adapter to reassembly message fragments and delegate on only whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for each session.\n     */\n    explicit FragmentAssembler(\n        const fragment_handler_t &delegate, std::size_t initialBufferLength = DEFAULT_FRAGMENT_ASSEMBLY_BUFFER_LENGTH) :\n        m_delegate(delegate)\n    {\n        aeron_fragment_assembler_create(&m_fragment_assembler, handlerCallback, reinterpret_cast<void *>(this));\n    }\n\n    ~FragmentAssembler()\n    {\n        aeron_fragment_assembler_delete(m_fragment_assembler);\n    }\n\n    FragmentAssembler(FragmentAssembler& other) = delete;\n    FragmentAssembler(FragmentAssembler&& other) = delete;\n    FragmentAssembler& operator=(FragmentAssembler& other) = delete;\n    FragmentAssembler& operator=(FragmentAssembler&& other) = delete;\n\n    /**\n     * Compose a fragment_handler_t that calls the this FragmentAssembler instance for reassembly. Suitable for\n     * passing to Subscription::poll(fragment_handler_t, int).\n     *\n     * @return fragment_handler_t composed with the FragmentAssembler instance\n     */\n    fragment_handler_t handler()\n    {\n        return [this](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            this->onFragment(buffer, offset, length, header);\n        };\n    }\n\n    /**\n     * Free an existing session buffer to reduce memory pressure when an Image goes inactive or no more\n     * large messages are expected.\n     *\n     * @param sessionId to have its buffer freed\n     */\n    void deleteSessionBuffer(std::int32_t sessionId)\n    {\n        // No-op???\n    }\n\nprivate:\n    aeron_fragment_assembler_t *m_fragment_assembler = nullptr;\n    fragment_handler_t m_delegate;\n\n    static void handlerCallback(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto *assembler = reinterpret_cast<FragmentAssembler *>(clientd);\n        Header headerWrapper{header};\n        AtomicBuffer buffer1{const_cast<uint8_t *>(buffer), length};\n        assembler->m_delegate(buffer1, 0, (util::index_t)length, headerWrapper);\n    }\n\n    inline void onFragment(AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n    {\n        aeron_fragment_assembler_handler(m_fragment_assembler, buffer.buffer() + offset, length, header.hdr());\n    }\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/HeartbeatTimestamp.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_HEARTBEAT_TIMESTAMP_H\n#define AERON_HEARTBEAT_TIMESTAMP_H\n\n#include \"concurrent/CountersReader.h\"\n\nnamespace aeron\n{\n\n/**\n * The heartbeat as a timestamp for an entity to indicate liveness.\n * <p>\n * Key has the following layout:\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                       Registration ID                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n * </pre>\n */\nnamespace HeartbeatTimestamp\n{\n\n/// Counter type id of a client heartbeat timestamp\nstatic constexpr std::int32_t CLIENT_HEARTBEAT_TYPE_ID = 11;\n\n#pragma pack(push)\n#pragma pack(4)\nstruct HeartbeatTimestampKeyDefn\n{\n    std::int64_t registrationId;\n};\n#pragma pack(pop)\n\n/**\n * Find the active counter id for an entity with a type id and registration id.\n *\n * @param countersReader to search within.\n * @param counterTypeId  to match against.\n * @param registrationId for the active entity.\n * @return the counter id if found otherwise #NULL_COUNTER_ID.\n */\ninline static std::int32_t findCounterIdByRegistrationId(\n    CountersReader &countersReader, std::int32_t counterTypeId, std::int64_t registrationId)\n{\n    AtomicBuffer buffer = countersReader.metaDataBuffer();\n\n    for (std::int32_t i = 0, size = countersReader.maxCounterId(); i < size; i++)\n    {\n        if (countersReader.getCounterState(i) == CountersReader::RECORD_ALLOCATED)\n        {\n            const util::index_t recordOffset = CountersReader::metadataOffset(i);\n            auto key = buffer.overlayStruct<HeartbeatTimestampKeyDefn>(\n                recordOffset + CountersReader::KEY_OFFSET);\n\n            if (registrationId == key.registrationId &&\n                buffer.getInt32(recordOffset + CountersReader::TYPE_ID_OFFSET) == counterTypeId)\n            {\n                return i;\n            }\n        }\n    }\n\n    return CountersReader::NULL_COUNTER_ID;\n}\n\n/**\n * Is the counter still active.\n *\n * @param countersReader to search within.\n * @param counterId      to search for.\n * @param counterTypeId  to match for counter type.\n * @param registrationId to match the entity key.\n * @return true if the counter is still active otherwise false.\n */\ninline static bool isActive(\n    CountersReader &countersReader, std::int32_t counterId, std::int32_t counterTypeId, std::int64_t registrationId)\n{\n    AtomicBuffer buffer = countersReader.metaDataBuffer();\n    const util::index_t recordOffset = CountersReader::metadataOffset(counterId);\n    auto key = buffer.overlayStruct<HeartbeatTimestampKeyDefn>(recordOffset + CountersReader::KEY_OFFSET);\n\n    return\n        registrationId == key.registrationId &&\n        buffer.getInt32(recordOffset + CountersReader::TYPE_ID_OFFSET) == counterTypeId &&\n        countersReader.getCounterState(counterId) == CountersReader::RECORD_ALLOCATED;\n}\n\n}\n}\n#endif //AERON_HEARTBEAT_TIMESTAMP_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/Image.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_IMAGE_H\n#define AERON_IMAGE_H\n\n#include <algorithm>\n#include <array>\n#include <vector>\n#include <atomic>\n#include <cassert>\n#include <functional>\n#include <memory>\n\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/logbuffer/Header.h\"\n\n#include \"aeronc.h\"\n\nnamespace aeron\n{\n\nusing namespace aeron::concurrent;\nusing namespace aeron::concurrent::logbuffer;\n\nenum class ControlledPollAction : int\n{\n    /**\n     * Abort the current polling operation and do not advance the position for this fragment.\n     */\n    ABORT = AERON_ACTION_ABORT,\n\n    /**\n     * Break from the current polling operation and commit the position as of the end of the current fragment\n     * being handled.\n     */\n    BREAK = AERON_ACTION_BREAK,\n\n    /**\n     * Continue processing but commit the position as of the end of the current fragment so that\n     * flow control is applied to this point.\n     */\n    COMMIT = AERON_ACTION_COMMIT,\n\n    /**\n     * Continue processing taking the same approach as the in fragment_handler_t.\n     */\n    CONTINUE = AERON_ACTION_CONTINUE\n};\n\n/**\n * Callback for handling fragments of data being read from a log.\n *\n * @param buffer containing the data.\n * @param offset at which the data begins.\n * @param length of the data in bytes.\n * @param header representing the meta data for the data.\n * @return The action to be taken with regard to the stream position after the callback.\n */\ntypedef std::function<ControlledPollAction(\n    concurrent::AtomicBuffer &buffer,\n    util::index_t offset,\n    util::index_t length,\n    concurrent::logbuffer::Header &header)> controlled_poll_fragment_handler_t;\n\n\ntemplate<typename H>\nstatic void doPoll(void *clientd, const std::uint8_t *buffer, std::size_t length, aeron_header_t *header)\n{\n    H &handler = *reinterpret_cast<H *>(clientd);\n    AtomicBuffer atomicBuffer(const_cast<std::uint8_t *>(buffer), length);\n    Header headerWrapper{header};\n    handler(atomicBuffer, static_cast<util::index_t>(0), static_cast<util::index_t>(length), headerWrapper);\n}\n\ntemplate<typename H>\nstatic aeron_controlled_fragment_handler_action_t doControlledPoll(\n    void *clientd, const std::uint8_t *buffer, std::size_t length, aeron_header_t *header)\n{\n    H &handler = *reinterpret_cast<H *>(clientd);\n    AtomicBuffer atomicBuffer(const_cast<std::uint8_t *>(buffer), length);\n    Header headerWrapper{header};\n\n    ControlledPollAction action = handler(atomicBuffer, 0, static_cast<std::int32_t>(length), headerWrapper);\n    return static_cast<aeron_controlled_fragment_handler_action_t>(action);\n}\n\ntemplate<typename H>\nstatic void doBlockPoll(\n    void *clientd, const std::uint8_t *buffer, std::size_t length, std::int32_t session_id, std::int32_t term_id)\n{\n    H &handler = *reinterpret_cast<H *>(clientd);\n    AtomicBuffer atomicBuffer(const_cast<std::uint8_t *>(buffer), length);\n    handler(atomicBuffer, 0, static_cast<std::int32_t>(length), session_id, term_id);\n}\n\n/**\n * Represents a replicated publication {@link Image} from a publisher to a {@link Subscription}.\n * Each {@link Image} identifies a source publisher by session id.\n *\n * Is an overlay on the LogBuffers and Position. So, can be effectively copied and moved.\n */\nclass Image\n{\npublic:\n    typedef Image this_t;\n    typedef std::vector<std::shared_ptr<Image>> list_t;\n    typedef std::shared_ptr<Image> *array_t;\n\n    Image(aeron_subscription_t *subscription, aeron_image_t *image) :\n        m_subscription(subscription), m_image(image), m_sourceIdentity()\n    {\n        aeron_image_constants(m_image, &m_constants);\n        m_sourceIdentity.append(m_constants.source_identity);\n        aeron_subscription_image_retain(m_subscription, m_image);\n    }\n\n    ~Image()\n    {\n        aeron_subscription_image_release(m_subscription, m_image);\n    }\n\n    /**\n     * Get the length in bytes for each term partition in the log buffer.\n     *\n     * @return the length in bytes for each term partition in the log buffer.\n     */\n    inline std::int32_t termBufferLength() const\n    {\n        return static_cast<std::int32_t>(m_constants.term_buffer_length);\n    }\n\n    /**\n     * Number of bits to right shift a position to get a term count for how far the stream has progressed.\n     *\n     * @return of bits to right shift a position to get a term count for how far the stream has progressed.\n     */\n    inline std::int32_t positionBitsToShift() const\n    {\n        return static_cast<std::int32_t>(m_constants.position_bits_to_shift);\n    }\n\n    /**\n     * The sessionId for the steam of messages.\n     *\n     * @return the sessionId for the steam of messages.\n     */\n    inline std::int32_t sessionId() const\n    {\n        return m_constants.session_id;\n    }\n\n    /**\n     * The correlationId for identification of the image with the media driver.\n     *\n     * @return the correlationId for identification of the image with the media driver.\n     */\n    inline std::int64_t correlationId() const\n    {\n        return m_constants.correlation_id;\n    }\n\n    /**\n     * The registrationId for the Subscription of the Image.\n     *\n     * @return the registrationId for the Subscription of the Image.\n     */\n    inline std::int64_t subscriptionRegistrationId() const\n    {\n        aeron_subscription_constants_t constants;\n        aeron_subscription_constants(m_subscription, &constants);\n        return constants.registration_id;\n    }\n\n    /**\n     * The position at which this stream was joined.\n     *\n     * @return the position at which this stream was joined.\n     */\n    inline std::int64_t joinPosition() const\n    {\n        return m_constants.join_position;\n    }\n\n    /**\n     * The initial term at which the stream started for this session.\n     *\n     * @return the initial term id.\n     */\n    inline std::int32_t initialTermId() const\n    {\n        return m_constants.initial_term_id;\n    }\n\n    /**\n     * The source identity of the sending publisher as an abstract concept appropriate for the media.\n     *\n     * @return source identity of the sending publisher as an abstract concept appropriate for the media.\n     */\n    inline std::string sourceIdentity() const\n    {\n        return m_constants.source_identity;\n    }\n\n    /**\n     * Has this object been closed and should no longer be used?\n     *\n     * @return true if it has been closed otherwise false.\n     */\n    inline bool isClosed() const\n    {\n        return aeron_image_is_closed(m_image);\n    }\n\n    inline bool isPublicationRevoked() const\n    {\n        return aeron_image_is_publication_revoked(m_image);\n    }\n\n    /**\n     * The position this Image has been consumed to by the subscriber.\n     *\n     * @return the position this Image has been consumed to by the subscriber or CLOSED if closed\n     */\n    inline std::int64_t position() const\n    {\n        std::int64_t position = aeron_image_position(m_image);\n        if (position < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Get the counter id used to represent the subscriber position.\n     *\n     * @return the counter id used to represent the subscriber position.\n     */\n    inline std::int32_t subscriberPositionId() const\n    {\n        return m_constants.subscriber_position_id;\n    }\n\n    /**\n     * Set the subscriber position for this Image to indicate where it has been consumed to.\n     *\n     * @param newPosition for the consumption point.\n     */\n    inline void position(std::int64_t newPosition)\n    {\n        if (aeron_image_set_position(m_image, newPosition) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Is the current consumed position at the end of the stream?\n     *\n     * @return true if at the end of the stream or false if not.\n     */\n    inline bool isEndOfStream() const\n    {\n        return aeron_image_is_end_of_stream(m_image);\n    }\n\n    /**\n     * The position the stream reached when EOS was received from the publisher. The position will be\n     * INT64_MAX until the stream ends and EOS is set.\n     *\n     * @return position the stream reached when EOS was received from the publisher.\n     */\n    inline std::int64_t endOfStreamPosition() const\n    {\n        return aeron_image_end_of_stream_position(m_image);\n    }\n\n    inline void reject(std::string reason)\n    {\n        if (aeron_image_reject(m_image, reason.c_str()) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * A count of observed active transports within the Image liveness timeout.\n     *\n     * If the Image is closed, then this is 0. This may also be 0 if no actual datagrams have arrived. IPC\n     * Images also will be 0.\n     *\n     * @return count of active transports - or 0 if Image is closed, no datagrams yet, or IPC.\n     */\n    inline std::int32_t activeTransportCount() const\n    {\n        int count = aeron_image_active_transport_count(m_image);\n        if (count < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return count;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered via the fragment_handler_t up to a limited number of fragments as specified.\n     *\n     * @param fragmentHandler to which messages are delivered.\n     * @param fragmentLimit   for the number of fragments to be consumed during one polling operation.\n     * @return the number of fragments that have been consumed.\n     *\n     * @see fragment_handler_t\n     */\n    template<typename F>\n    inline int poll(F &&fragmentHandler, int fragmentLimit)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = fragmentHandler;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n        int numFragments = aeron_image_poll(\n            m_image, doPoll<handler_type>, handler_ptr, static_cast<std::size_t>(fragmentLimit));\n\n        if (numFragments < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return numFragments;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered via the fragment_handler_t up to a limited number of fragments as specified or the\n     * maximum position specified.\n     *\n     * @param fragmentHandler to which messages are delivered.\n     * @param limitPosition   to consume messages up to.\n     * @param fragmentLimit   for the number of fragments to be consumed during one polling operation.\n     * @return the number of fragments that have been consumed.\n     *\n     * @see fragment_handler_t\n     */\n    template<typename F>\n    inline int boundedPoll(F &&fragmentHandler, std::int64_t limitPosition, int fragmentLimit)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = fragmentHandler;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n\n        int numFragments = aeron_image_bounded_poll(\n            m_image, doPoll<handler_type>, handler_ptr, limitPosition, static_cast<std::size_t>(fragmentLimit));\n        if (numFragments < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return numFragments;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered to the controlled_poll_fragment_handler_t up to a limited number of fragments as specified.\n     *\n     * To assemble messages that span multiple fragments then use ControlledFragmentAssembler.\n     *\n     * @param fragmentHandler to which message fragments are delivered.\n     * @param fragmentLimit   for the number of fragments to be consumed during one polling operation.\n     * @return the number of fragments that have been consumed.\n     *\n     * @see controlled_poll_fragment_handler_t\n     */\n    template<typename F>\n    inline int controlledPoll(F &&fragmentHandler, int fragmentLimit)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = fragmentHandler;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n\n        int numFragments = aeron_image_controlled_poll(\n            m_image, doControlledPoll<handler_type>, handler_ptr, static_cast<std::size_t>(fragmentLimit));\n        if (numFragments < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return numFragments;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered to the controlled_poll_fragment_handler_t up to a limited number of fragments as specified\n     * or the maximum position specified.\n     *\n     * To assemble messages that span multiple fragments then use ControlledFragmentAssembler.\n     *\n     * @param fragmentHandler to which message fragments are delivered.\n     * @param limitPosition   to consume messages up to.\n     * @param fragmentLimit   for the number of fragments to be consumed during one polling operation.\n     * @return the number of fragments that have been consumed.\n     * @see controlled_poll_fragment_handler_t\n     */\n    template<typename F>\n    inline int boundedControlledPoll(F &&fragmentHandler, std::int64_t limitPosition, int fragmentLimit)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = fragmentHandler;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n\n        int numFragments = aeron_image_bounded_controlled_poll(\n            m_image,\n            doControlledPoll<handler_type>,\n            handler_ptr,\n            limitPosition,\n            static_cast<std::size_t>(fragmentLimit));\n        if (numFragments < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return numFragments;\n    }\n\n    /**\n     * Peek for new messages in a stream by scanning forward from an initial position. If new messages are found then\n     * they will be delivered to the controlled_poll_fragment_handler_t up to a limited position.\n     * <p>\n     * To assemble messages that span multiple fragments then use ControlledFragmentAssembler. Scans must also\n     * start at the beginning of a message so that the assembler is reset.\n     *\n     * @param initialPosition from which to peek forward.\n     * @param fragmentHandler to which message fragments are delivered.\n     * @param limitPosition   up to which can be scanned.\n     * @return the resulting position after the scan terminates which is a complete message.\n     * @see controlled_poll_fragment_handler_t\n     */\n    template<typename F>\n    inline std::int64_t controlledPeek(std::int64_t initialPosition, F &&fragmentHandler, std::int64_t limitPosition)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = fragmentHandler;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n\n        std::int64_t bytesPeeked = aeron_image_controlled_peek(\n            m_image, initialPosition, doControlledPoll<handler_type>, handler_ptr, limitPosition);\n\n        if (bytesPeeked < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return bytesPeeked;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered via the block_handler_t up to a limited number of bytes.\n     *\n     * A scan will terminate if a padding frame is encountered. If first frame in a scan is padding then a block\n     * for the padding is notified. If the padding comes after the first frame in a scan then the scan terminates\n     * at the offset the padding frame begins. Padding frames are delivered singularly in a block.\n     *\n     * Padding frames may be for a greater range than the limit offset but only the header needs to be valid so\n     * relevant length of the frame is sizeof DataHeaderDefn.\n     *\n     * @param blockHandler     to which block is delivered.\n     * @param blockLengthLimit up to which a block may be in length.\n     * @return the number of bytes that have been consumed.\n     *\n     * @see block_handler_t\n     */\n    template<typename F>\n    inline int blockPoll(F &&blockHandler, int blockLengthLimit)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = blockHandler;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n\n        int numFragments = aeron_image_block_poll(\n            m_image, doBlockPoll<handler_type>, handler_ptr, static_cast<std::size_t>(blockLengthLimit));\n        if (numFragments < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return numFragments;\n    }\n\nprivate:\n    aeron_subscription_t *m_subscription = nullptr;\n    aeron_image_t *m_image = nullptr;\n    aeron_image_constants_t m_constants = {};\n    std::string m_sourceIdentity;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/ImageControlledFragmentAssembler.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_IMAGE_CONTROLLEDFRAGMENTASSEMBLER_H\n#define AERON_IMAGE_CONTROLLEDFRAGMENTASSEMBLER_H\n\n#include \"Aeron.h\"\n\nnamespace aeron\n{\n\nstatic constexpr std::size_t DEFAULT_IMAGE_CONTROLLED_FRAGMENT_ASSEMBLY_BUFFER_LENGTH = 4096;\n\n/**\n * A handler that sits in a chain-of-responsibility pattern that reassembles fragmented messages\n * so that the next handler in the chain only sees whole messages.\n * <p>\n * Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary\n * buffer for reassembly before delegation.\n * <p>\n * The Header passed to the delegate on assembling a message will be that of the last fragment.\n * <p>\n * This handler is not session aware and must only be used when polling a single Image.\n */\nclass ImageControlledFragmentAssembler\n{\npublic:\n\n    /**\n     * Construct an adapter to reassembly message fragments and delegate on only whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for rebuilding.\n     */\n    explicit ImageControlledFragmentAssembler(\n        const controlled_poll_fragment_handler_t &delegate,\n        std::size_t initialBufferLength = DEFAULT_IMAGE_CONTROLLED_FRAGMENT_ASSEMBLY_BUFFER_LENGTH) :\n        m_delegate(delegate)\n    {\n        aeron_image_controlled_fragment_assembler_create(\n            &m_fragment_assembler, handlerCallback, reinterpret_cast<void *>(this));\n    }\n\n    ~ImageControlledFragmentAssembler()\n    {\n        aeron_image_controlled_fragment_assembler_delete(m_fragment_assembler);\n    }\n\n    ImageControlledFragmentAssembler(ImageControlledFragmentAssembler& other) = delete;\n    ImageControlledFragmentAssembler(ImageControlledFragmentAssembler&& other) = delete;\n    ImageControlledFragmentAssembler& operator=(ImageControlledFragmentAssembler& other) = delete;\n    ImageControlledFragmentAssembler& operator=(ImageControlledFragmentAssembler&& other) = delete;\n\n    /**\n     * Compose a controlled_poll_fragment_handler_t that calls the ImageControlledFragmentAssembler instance for\n     * reassembly. Suitable for passing to Image::controlledPoll(controlled_poll_fragment_handler_t, int).\n     *\n     * @return controlled_poll_fragment_handler_t composed with the ImageControlledFragmentAssembler instance\n     */\n    controlled_poll_fragment_handler_t handler()\n    {\n        return [this](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            return this->onFragment(buffer, offset, length, header);\n        };\n    }\n\nprivate:\n    controlled_poll_fragment_handler_t m_delegate;\n    aeron_image_controlled_fragment_assembler_t *m_fragment_assembler = nullptr;\n\n    static aeron_controlled_fragment_handler_action_t handlerCallback(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto assembler = reinterpret_cast<ImageControlledFragmentAssembler *>(clientd);\n        Header headerWrapper{header};\n        AtomicBuffer bufferWrapper{const_cast<uint8_t *>(buffer), length};\n        ControlledPollAction action = assembler->m_delegate(bufferWrapper, 0, (util::index_t)length, headerWrapper);\n\n        switch (action)\n        {\n            case ControlledPollAction::ABORT:\n                return AERON_ACTION_ABORT;\n                break;\n            case ControlledPollAction::BREAK:\n                return AERON_ACTION_BREAK;\n                break;\n            case ControlledPollAction::COMMIT:\n                return AERON_ACTION_COMMIT;\n                break;\n            case ControlledPollAction::CONTINUE:\n                return AERON_ACTION_CONTINUE;\n                break;\n        }\n\n        throw IllegalArgumentException(\"unknown action\", SOURCEINFO);\n    }\n\n    ControlledPollAction onFragment(AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n    {\n        aeron_controlled_fragment_handler_action_t action = aeron_image_controlled_fragment_assembler_handler(\n            m_fragment_assembler,\n            buffer.buffer() + offset,\n            length,\n            header.hdr());\n\n        switch (action)\n        {\n            case AERON_ACTION_ABORT:\n                return ControlledPollAction::ABORT;\n                break;\n            case AERON_ACTION_BREAK:\n                return ControlledPollAction::BREAK;\n                break;\n            case AERON_ACTION_COMMIT:\n                return ControlledPollAction::COMMIT;\n                break;\n            case AERON_ACTION_CONTINUE:\n                return ControlledPollAction::CONTINUE;\n                break;\n        }\n\n        throw IllegalArgumentException(\"unknown action\", SOURCEINFO);\n    }\n};\n\n}\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/ImageFragmentAssembler.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_IMAGE_FRAGMENT_ASSEMBLER_H\n#define AERON_IMAGE_FRAGMENT_ASSEMBLER_H\n\n#include \"Aeron.h\"\n\nnamespace aeron\n{\n\nstatic constexpr std::size_t DEFAULT_IMAGE_FRAGMENT_ASSEMBLY_BUFFER_LENGTH = 4096;\n\n/**\n * A handler that sits in a chain-of-responsibility pattern that reassembles fragmented messages\n * so that the next handler in the chain only sees whole messages.\n * <p>\n * Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary\n * buffer for reassembly before delegation.\n * <p>\n * The Header passed to the delegate on assembling a message will be that of the last fragment.\n * <p>\n * This handler is not session aware and must only be used when polling a single Image.\n */\nclass ImageFragmentAssembler\n{\npublic:\n\n    /**\n     * Construct an adapter to reassembly message fragments and delegate on only whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for rebuilding.\n     */\n    explicit ImageFragmentAssembler(\n        const fragment_handler_t &delegate,\n        std::size_t initialBufferLength = DEFAULT_IMAGE_FRAGMENT_ASSEMBLY_BUFFER_LENGTH) :\n        m_delegate(delegate)\n    {\n        aeron_image_fragment_assembler_create(&m_fragment_assembler, handlerCallback, reinterpret_cast<void *>(this));\n    }\n\n    ~ImageFragmentAssembler()\n    {\n        aeron_image_fragment_assembler_delete(m_fragment_assembler);\n    }\n\n    ImageFragmentAssembler(ImageFragmentAssembler& other) = delete;\n    ImageFragmentAssembler(ImageFragmentAssembler&& other) = delete;\n    ImageFragmentAssembler& operator=(ImageFragmentAssembler& other) = delete;\n    ImageFragmentAssembler& operator=(ImageFragmentAssembler&& other) = delete;\n\n    /**\n     * Compose a fragment_handler_t that calls the ImageFragmentAssembler instance for reassembly. Suitable for\n     * passing to Image::poll(fragment_handler_t, int).\n     *\n     * @return fragment_handler_t composed with the FragmentAssembler instance\n     */\n    fragment_handler_t handler()\n    {\n        return [this](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            this->onFragment(buffer, offset, length, header);\n        };\n    }\n\nprivate:\n    aeron_image_fragment_assembler_t *m_fragment_assembler = nullptr;\n    fragment_handler_t m_delegate;\n\n    static void handlerCallback(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto *assembler = reinterpret_cast<ImageFragmentAssembler *>(clientd);\n        Header headerWrapper{header};\n        AtomicBuffer bufferWrapper{const_cast<uint8_t *>(buffer), length};\n        assembler->m_delegate(bufferWrapper, 0, (util::index_t)length, headerWrapper);\n    }\n\n    inline void onFragment(AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n    {\n        aeron_image_fragment_assembler_handler(m_fragment_assembler, buffer.buffer() + offset, length, header.hdr());\n    }\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/Publication.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_PUBLICATION_H\n#define AERON_PUBLICATION_H\n\n#include <array>\n#include <atomic>\n#include <memory>\n#include <string>\n#include <vector>\n#include <mutex>\n\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/logbuffer/BufferClaim.h\"\n#include \"concurrent/status/UnsafeBufferPosition.h\"\n#include \"concurrent/status/StatusIndicatorReader.h\"\n#include \"util/Exceptions.h\"\n\n#include \"aeronc.h\"\n\nnamespace aeron\n{\n\nusing namespace aeron::concurrent::status;\nusing namespace aeron::concurrent::logbuffer;\nusing namespace aeron::concurrent;\nusing namespace aeron::util;\n\nstatic constexpr std::int64_t NOT_CONNECTED = -1;\nstatic constexpr std::int64_t BACK_PRESSURED = -2;\nstatic constexpr std::int64_t ADMIN_ACTION = -3;\nstatic constexpr std::int64_t PUBLICATION_CLOSED = -4;\nstatic constexpr std::int64_t MAX_POSITION_EXCEEDED = -5;\n\n/**\n * Supplies the reserved value field for a data frame header. The returned value will be set in the header as\n * Little Endian format.\n *\n * This will be called as the last action of encoding a data frame right before the length is set. All other fields\n * in the header plus the body of the frame will have been written at the point of supply.\n *\n * @param termBuffer for the message\n * @param termOffset of the start of the message\n * @param length of the message in bytes\n */\ntypedef std::function<std::int64_t(\n    AtomicBuffer &termBuffer,\n    util::index_t termOffset,\n    util::index_t length)> on_reserved_value_supplier_t;\n\nstatic const on_reserved_value_supplier_t DEFAULT_RESERVED_VALUE_SUPPLIER =\n    [](AtomicBuffer &, util::index_t, util::index_t) -> std::int64_t\n    {\n        return 0;\n    };\n\nusing AsyncDestination = aeron_async_destination_t;\n\nclass Aeron;\n\n/**\n * @example BasicPublisher.cpp\n */\n/**\n * Aeron Publisher API for sending messages to subscribers of a given channel and streamId pair. Publishers\n * are created via an {@link Aeron} object, and messages are sent via an offer method or a claim and commit\n * method combination.\n * <p>\n * The APIs for tryClaim and offer are non-blocking and threadsafe.\n * <p>\n * Note: Publication instances are threadsafe and can be shared between publisher threads.\n * @see Aeron#addPublication\n * @see Aeron#findPublication\n */\nclass Publication\n{\npublic:\n\n    /// @cond HIDDEN_SYMBOLS\n    Publication(const std::shared_ptr<Aeron> &aeronRef, aeron_t *aeron, aeron_publication_t *publication) :\n        m_aeronRef(aeronRef),\n        m_aeron(aeron),\n        m_publication(publication)\n    {\n        if (aeron_publication_constants(m_publication, &m_constants) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n        m_channel.append(m_constants.channel);\n    }\n    /// @endcond\n\n    ~Publication()\n    {\n        aeron_publication_close(m_publication, nullptr, nullptr);\n\n        for (const std::pair<const std::int64_t, AsyncDestination *>& e : m_pendingDestinations)\n        {\n            aeron_async_cmd_free(e.second);\n        }\n    }\n\n    /**\n     * Media address for delivery to the channel.\n     *\n     * @return Media address for delivery to the channel.\n     */\n    inline const std::string &channel() const\n    {\n        return m_channel;\n    }\n\n    /**\n     * Stream identity for scoping within the channel media address.\n     *\n     * @return Stream identity for scoping within the channel media address.\n     */\n    inline std::int32_t streamId() const\n    {\n        return m_constants.stream_id;\n    }\n\n    /**\n     * Session under which messages are published. Identifies this Publication instance.\n     *\n     * @return the session id for this publication.\n     */\n    inline std::int32_t sessionId() const\n    {\n        return m_constants.session_id;\n    }\n\n    /**\n     * The initial term id assigned when this Publication was created. This can be used to determine how many\n     * terms have passed since creation.\n     *\n     * @return the initial term id.\n     */\n    inline std::int32_t initialTermId() const\n    {\n        return m_constants.initial_term_id;\n    }\n\n    /**\n     * Get the original registration used to register this Publication with the media driver by the first publisher.\n     *\n     * @return the original registrationId of the publication.\n     */\n    inline std::int64_t originalRegistrationId() const\n    {\n        return m_constants.original_registration_id;\n    }\n\n    /**\n     * Registration Id returned by Aeron::addPublication when this Publication was added.\n     *\n     * @return the registrationId of the publication.\n     */\n    inline std::int64_t registrationId() const\n    {\n        return m_constants.registration_id;\n    }\n\n    /**\n     * Is this Publication the original instance added to the driver? If not then it was added after another client\n     * has already added the publication.\n     *\n     * @return true if this instance is the first added otherwise false.\n     */\n    inline bool isOriginal() const\n    {\n        return originalRegistrationId() == registrationId();\n    }\n\n    /**\n     * Maximum message length supported in bytes.\n     *\n     * @return maximum message length supported in bytes.\n     */\n    inline util::index_t maxMessageLength() const\n    {\n        return static_cast<util::index_t>(m_constants.max_message_length);\n    }\n\n    /**\n     * Maximum length of a message payload that fits within a message fragment.\n     *\n     * This is the MTU length minus the message fragment header length.\n     *\n     * @return maximum message fragment payload length.\n     */\n    inline util::index_t maxPayloadLength() const\n    {\n        return static_cast<util::index_t>(m_constants.max_payload_length);\n    }\n\n    /**\n     * Get the length in bytes for each term partition in the log buffer.\n     *\n     * @return the length in bytes for each term partition in the log buffer.\n     */\n    inline std::int32_t termBufferLength() const\n    {\n        return static_cast<std::int32_t>(m_constants.term_buffer_length);\n    }\n\n    /**\n     * Number of bits to right shift a position to get a term count for how far the stream has progressed.\n     *\n     * @return of bits to right shift a position to get a term count for how far the stream has progressed.\n     */\n    inline std::int32_t positionBitsToShift() const\n    {\n        return static_cast<std::int32_t>(m_constants.position_bits_to_shift);\n    }\n\n    /**\n     * Has this Publication seen an active subscriber recently?\n     *\n     * @return true if this Publication has seen an active subscriber recently.\n     */\n    inline bool isConnected() const\n    {\n        return aeron_publication_is_connected(m_publication);\n    }\n\n    /**\n     * Has this object been closed and should no longer be used?\n     *\n     * @return true if it has been closed otherwise false.\n     */\n    inline bool isClosed() const\n    {\n        return aeron_publication_is_closed(m_publication);\n    }\n\n    /**\n     * Get the max possible position the stream can reach given term length.\n     *\n     * @return the max possible position the stream can reach given term length.\n     */\n    inline std::int64_t maxPossiblePosition() const\n    {\n        return m_constants.max_possible_position;\n    }\n\n    /**\n     * Get the current position to which the publication has advanced for this stream.\n     *\n     * @return the current position to which the publication has advanced for this stream or {@link CLOSED}.\n     */\n    inline std::int64_t position() const\n    {\n        std::int64_t position = aeron_publication_position(m_publication);\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Get the position limit beyond which this {@link Publication} will be back pressured.\n     *\n     * This should only be used as a guide to determine when back pressure is likely to be applied.\n     *\n     * @return the position limit beyond which this {@link Publication} will be back pressured.\n     */\n    inline std::int64_t publicationLimit() const\n    {\n        std::int64_t limit = aeron_publication_position_limit(m_publication);\n        if (AERON_PUBLICATION_ERROR == limit)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return limit;\n    }\n\n    /**\n     * Get the counter id used to represent the publication limit.\n     *\n     * @return the counter id used to represent the publication limit.\n     */\n    inline std::int32_t publicationLimitId() const\n    {\n        return m_constants.publication_limit_counter_id;\n    }\n\n    /**\n     * Available window for offering into a publication before the {@link #positionLimit()} is reached.\n     *\n     * @return  window for offering into a publication before the {@link #positionLimit()} is reached. If\n     * the publication is closed then {@link #CLOSED} will be returned.\n     */\n    inline std::int64_t availableWindow() const\n    {\n        const std::int64_t limit = publicationLimit();\n        return AERON_PUBLICATION_CLOSED != limit ? limit - position() : AERON_PUBLICATION_CLOSED;\n    }\n\n    /**\n     * Get the counter id used to represent the channel status.\n     *\n     * @return the counter id used to represent the channel status.\n     */\n    inline std::int32_t channelStatusId() const\n    {\n        return m_constants.channel_status_indicator_id;\n    }\n\n    /**\n     * Get the status for the channel of this {@link Publication}\n     *\n     * @return status code for this channel\n     */\n    std::int64_t channelStatus() const\n    {\n        return aeron_publication_channel_status(m_publication);\n    }\n\n    /**\n     * Fetches the local socket addresses for this publication. If the channel is not\n     * {@link aeron::concurrent::status::ChannelEndpointStatus::CHANNEL_ENDPOINT_ACTIVE}, then this will return an\n     * empty list.\n     *\n     * The format is as follows:\n     * <br>\n     * <br>\n     * IPv4: <code>ip address:port</code>\n     * <br>\n     * IPv6: <code>[ip6 address]:port</code>\n     * <br>\n     * <br>\n     * This is to match the formatting used in the Aeron URI\n     *\n     * @return local socket address for this subscription.\n     * @see #channelStatus()\n     */\n    std::vector<std::string> localSocketAddresses() const\n    {\n        std::vector<std::string> localAddresses;\n        std::uint8_t buffer[AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN];\n        // Publications only have a single local address.\n        aeron_iovec_t iov;\n        iov.iov_base = buffer;\n        iov.iov_len = sizeof(buffer);\n\n        if (aeron_publication_local_sockaddrs(m_publication, &iov, 1) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        localAddresses.push_back(std::string(reinterpret_cast<char *>(buffer)));\n\n        return localAddresses;\n    }\n\n    /**\n     * Non-blocking publish of a buffer containing a message.\n     *\n     * @param buffer containing message.\n     * @param offset offset in the buffer at which the encoded message begins.\n     * @param length in bytes of the encoded message.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    inline std::int64_t offer(\n        const concurrent::AtomicBuffer &buffer,\n        util::index_t offset,\n        util::index_t length,\n        const on_reserved_value_supplier_t &reservedValueSupplier)\n    {\n        std::int64_t position = aeron_publication_offer(\n            m_publication,\n            buffer.buffer() + offset,\n            static_cast<std::size_t>(length),\n            reservedValueSupplierCallback,\n            (void *)&reservedValueSupplier);\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Non-blocking publish of a buffer containing a message.\n     *\n     * @param buffer containing message.\n     * @param offset offset in the buffer at which the encoded message begins.\n     * @param length in bytes of the encoded message.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    inline std::int64_t offer(const concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length)\n    {\n        return offer(buffer, offset, length, DEFAULT_RESERVED_VALUE_SUPPLIER);\n    }\n\n    /**\n     * Non-blocking publish of a buffer containing a message.\n     *\n     * @param buffer containing message.\n     * @return The new stream position on success, otherwise {@link BACK_PRESSURED} or {@link NOT_CONNECTED}.\n     */\n    inline std::int64_t offer(const concurrent::AtomicBuffer &buffer)\n    {\n        return offer(buffer, 0, buffer.capacity());\n    }\n\n    /**\n     * Non-blocking publish of buffers containing a message.\n     *\n     * @param startBuffer containing part of the message.\n     * @param lastBuffer after the message.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    template<class BufferIterator> std::int64_t offer(\n        BufferIterator startBuffer,\n        BufferIterator lastBuffer,\n        const on_reserved_value_supplier_t &reservedValueSupplier = DEFAULT_RESERVED_VALUE_SUPPLIER)\n    {\n        std::vector<aeron_iovec_t> iov;\n        std::size_t length = 0;\n        for (BufferIterator it = startBuffer; it != lastBuffer; ++it)\n        {\n            if (AERON_COND_EXPECT(length + it->capacity() < 0, false))\n            {\n                throw aeron::util::IllegalStateException(\n                    \"length overflow: \" + std::to_string(length) + \" + \" + std::to_string(it->capacity()) +\n                        \" > \" + std::to_string(length + it->capacity()),\n                    SOURCEINFO);\n            }\n\n            aeron_iovec_t buf;\n            buf.iov_base = it->buffer();\n            buf.iov_len = it->capacity();\n            iov.push_back(buf);\n        }\n\n        const std::int64_t position = aeron_publication_offerv(\n            m_publication, iov.data(), iov.size(), reservedValueSupplierCallback, (void *)&reservedValueSupplier);\n\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Non-blocking publish of array of buffers containing a message.\n     *\n     * @param buffers containing parts of the message.\n     * @param length of the array of buffers.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    std::int64_t offer(\n        const concurrent::AtomicBuffer *buffers,\n        std::size_t length,\n        const on_reserved_value_supplier_t &reservedValueSupplier = DEFAULT_RESERVED_VALUE_SUPPLIER)\n    {\n        return offer(buffers, buffers + length, reservedValueSupplier);\n    }\n\n    /**\n     * Non-blocking publish of array of buffers containing a message.\n     *\n     * @param buffers containing parts of the message.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    template<std::size_t N>\n    std::int64_t offer(\n        const std::array<concurrent::AtomicBuffer, N> &buffers,\n        const on_reserved_value_supplier_t &reservedValueSupplier = DEFAULT_RESERVED_VALUE_SUPPLIER)\n    {\n        return offer(buffers.begin(), buffers.end(), reservedValueSupplier);\n    }\n\n    /**\n     * Non-blocking publish of a simple const data buffer and length containing a message\n     *\n     * @param buffer contain the message.\n     * @param length of the message.\n     * @param reservedValueSupplier for the frame.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     */\n    std::int64_t offer(\n        const std::uint8_t *buffer,\n        std::size_t length,\n        const on_reserved_value_supplier_t &reservedValueSupplier = DEFAULT_RESERVED_VALUE_SUPPLIER)\n    {\n        std::int64_t position = aeron_publication_offer(\n            m_publication,\n            buffer,\n            static_cast<std::size_t>(length),\n            reservedValueSupplierCallback,\n            (void *)&reservedValueSupplier);\n\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return position;\n    }\n\n    /**\n     * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n     * Once the message has been written then {@link BufferClaim#commit()} should be called thus making it available.\n     * <p>\n     * <b>Note:</b> This method can only be used for message lengths less than MTU length minus header.\n     *\n     * @code\n     *     BufferClaim bufferClaim; // Can be stored and reused to avoid allocation\n     *\n     *     if (publication->tryClaim(messageLength, bufferClaim) > 0)\n     *     {\n     *         try\n     *         {\n     *              AtomicBuffer& buffer = bufferClaim.buffer();\n     *              const index_t offset = bufferClaim.offset();\n     *\n     *              // Work with buffer directly or wrap with a flyweight\n     *         }\n     *         finally\n     *         {\n     *             bufferClaim.commit();\n     *         }\n     *     }\n     * @endcode\n     *\n     * @param length      of the range to claim, in bytes..\n     * @param bufferClaim to be populate if the claim succeeds.\n     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},\n     * {@link #ADMIN_ACTION} or {@link #CLOSED}.\n     * @throws IllegalArgumentException if the length is greater than max payload length within an MTU.\n     * @see BufferClaim::commit\n     */\n    inline std::int64_t tryClaim(util::index_t length, concurrent::logbuffer::BufferClaim &bufferClaim)\n    {\n        aeron_buffer_claim_t temp_claim;\n        const std::int64_t position = aeron_publication_try_claim(\n            m_publication, static_cast<std::size_t>(length), &temp_claim);\n        if (AERON_PUBLICATION_ERROR == position)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        // This is slight cheat, we are assuming that the frame_header and data pointers index into the same\n        // contiguous block of memory.\n        bufferClaim.wrap(temp_claim.frame_header, DataFrameHeader::LENGTH + length);\n\n        return position;\n    }\n\n    /**\n     * Add a destination manually to a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to add\n     * @return async object to track the progress of the command\n     */\n    AsyncDestination *addDestinationAsync(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = nullptr;\n        if (aeron_publication_async_add_destination(&async, m_aeron, m_publication, endpointChannel.c_str()) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return async;\n    }\n\n    /**\n     * Add a destination manually to a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to add\n     * @return correlation id for the add command\n     */\n    std::int64_t addDestination(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = addDestinationAsync(endpointChannel);\n        std::int64_t correlationId = aeron_async_destination_get_registration_id(async);\n\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n        m_pendingDestinations[correlationId] = async;\n\n        return correlationId;\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to remove\n     * @return async object to track the progress of the command\n     */\n    AsyncDestination *removeDestinationAsync(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = nullptr;\n        if (aeron_publication_async_remove_destination(&async, m_aeron, m_publication, endpointChannel.c_str()) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return async;\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to remove\n     * @return correlation id for the remove command\n     */\n    std::int64_t removeDestination(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = removeDestinationAsync(endpointChannel);\n        std::int64_t correlationId = aeron_async_destination_get_registration_id(async);\n\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n        m_pendingDestinations[correlationId] = async;\n\n        return correlationId;\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication.\n     *\n     * @param destinationRegistrationId for the destination to remove\n     * @return async object to track the progress of the command\n     */\n    AsyncDestination *removeDestinationAsync(std::int64_t destinationRegistrationId)\n    {\n        AsyncDestination *async = nullptr;\n        if (aeron_publication_async_remove_destination_by_id(\n            &async, m_aeron, m_publication, destinationRegistrationId) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return async;\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication by destinationRegistrationId.\n     *\n     * @param destinationRegistrationId for the destination to remove\n     * @return correlation id for the remove command\n     */\n    std::int64_t removeDestination(std::int64_t destinationRegistrationId)\n    {\n        AsyncDestination *async = removeDestinationAsync(destinationRegistrationId);\n        std::int64_t correlationId = aeron_async_destination_get_registration_id(async);\n\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n        m_pendingDestinations[correlationId] = async;\n\n        return correlationId;\n    }\n\n    /**\n     * Retrieve the status of the associated add or remove destination operation with the given correlationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the correlationId is unknown, then an exception is thrown.\n     * - If the media driver has not answered the add/remove command, then a false is returned.\n     * - If the media driver has successfully added or removed the destination then true is returned.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Publication::addDestination\n     * @see Publication::removeDestination\n     *\n     * @param async used to track the progress of the destination command.\n     * @return true for added or false if not.\n     */\n    bool findDestinationResponse(AsyncDestination *async)\n    {\n        int result = aeron_publication_async_destination_poll(async);\n        if (result < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return 0 < result;\n    }\n\n    /**\n     * Retrieve the status of the associated add or remove destination operation with the given correlationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the correlationId is unknown, then an exception is thrown.\n     * - If the media driver has not answered the add/remove command, then a false is returned.\n     * - If the media driver has successfully added or removed the destination then true is returned.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Publication::addDestination\n     * @see Publication::removeDestination\n     *\n     * @param correlationId of the add/remove command returned by Publication::addDestination\n     * or Publication::removeDestination\n     * @return true for added or false if not.\n     */\n    bool findDestinationResponse(std::int64_t correlationId)\n    {\n        std::lock_guard<std::recursive_mutex> lock(m_adminLock);\n\n        auto search = m_pendingDestinations.find(correlationId);\n        if (search == m_pendingDestinations.end())\n        {\n            throw IllegalArgumentException(\"Unknown correlation id\", SOURCEINFO);\n        }\n\n        auto async = search->second;\n        try\n        {\n            bool result = findDestinationResponse(async);\n            if (result)\n            {\n                m_pendingDestinations.erase(correlationId);\n            }\n            return result;\n        }\n        catch (...)\n        {\n            m_pendingDestinations.erase(correlationId);\n            throw;\n        }\n    }\n\n    /// @cond HIDDEN_SYMBOLS\n    aeron_publication_t *publication()\n    {\n        return m_publication;\n    }\n    /// @endcond\n\nprivate:\n    std::shared_ptr<Aeron> m_aeronRef; // ensure Aeron instance is being deleted after its children\n    aeron_t *m_aeron = nullptr;\n    aeron_publication_t *m_publication = nullptr;\n    aeron_publication_constants_t m_constants = {};\n    std::string m_channel;\n    std::unordered_map<std::int64_t, AsyncDestination *> m_pendingDestinations = {};\n    std::recursive_mutex m_adminLock = {};\n\n    static std::int64_t reservedValueSupplierCallback(void *clientd, std::uint8_t *buffer, std::size_t frame_length)\n    {\n        on_reserved_value_supplier_t &supplier = *static_cast<on_reserved_value_supplier_t *>(clientd);\n        AtomicBuffer atomicBuffer(buffer, frame_length);\n        return supplier(atomicBuffer, 0, static_cast<util::index_t>(frame_length));\n    }\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/Subscription.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_SUBSCRIPTION_H\n#define AERON_SUBSCRIPTION_H\n\n#include <cstdint>\n#include <iostream>\n#include <memory>\n#include <mutex>\n#include <iterator>\n#include <stdexcept>\n\n#include \"Image.h\"\n#include \"concurrent/CountersReader.h\"\n#include \"Context.h\"\n#include \"ChannelUri.h\"\n\nextern \"C\"\n{\n#include \"aeron_common.h\"\n#include \"uri/aeron_uri.h\"\n}\n\nnamespace aeron\n{\n\nusing namespace aeron::concurrent::logbuffer;\nusing AsyncDestination = aeron_async_destination_t;\n\nclass Aeron;\n\nclass AsyncAddSubscription\n{\n    friend class Aeron;\nprivate:\n    AsyncAddSubscription(\n        const on_available_image_t &onAvailableImage,\n        const on_unavailable_image_t &onUnavailableImage) :\n        m_onAvailableImage(onAvailableImage),\n        m_onUnavailableImage(onUnavailableImage)\n    {\n    }\n\n    aeron_async_add_subscription_t *m_async = nullptr;\n    const on_available_image_t m_onAvailableImage;\n    const on_unavailable_image_t m_onUnavailableImage;\n\npublic:\n    ~AsyncAddSubscription() noexcept\n    {\n        aeron_async_cmd_free(m_async);\n    }\n\n    void static remove(void *clientd)\n    {\n        auto *addSubscription = static_cast<AsyncAddSubscription *>(clientd);\n        delete addSubscription;\n    }\n};\n\n/**\n * Aeron Subscriber API for receiving messages from publishers on a given channel and streamId pair.\n * Subscribers are created via an {@link Aeron} object, and received messages are delivered\n * to the {@link fragment_handler_t}.\n * <p>\n * By default, fragmented messages are not reassembled before delivery. If an application must\n * receive whole messages, whether or not they were fragmented, then the Subscriber\n * should be created with a {@link FragmentAssembler} or a custom implementation.\n * <p>\n * It is an applications responsibility to {@link Subscription#poll} the Subscriber for new messages.\n * <p>\n * <b>Note:</b> Subscriptions are not threadsafe and should not be shared between subscribers.\n *\n * @see FragmentAssembler\n */\nclass Subscription\n{\npublic:\n    /// @cond HIDDEN_SYMBOLS\n    Subscription(\n        const std::shared_ptr<Aeron> &aeronRef,\n        aeron_t *aeron,\n        aeron_subscription_t *subscription,\n        AsyncAddSubscription *addSubscription) :\n        m_aeronRef(aeronRef),\n        m_aeron(aeron),\n        m_subscription(subscription),\n        m_addSubscription(addSubscription)\n    {\n        if (aeron_subscription_constants(m_subscription, &m_constants) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n        m_channel.append(m_constants.channel);\n    }\n    /// @endcond\n\n    ~Subscription()\n    {\n        if (aeron_subscription_is_closed(m_subscription)) // driver or client timeout\n        {\n            delete m_addSubscription;\n        }\n        else if (0 != aeron_subscription_close(m_subscription, AsyncAddSubscription::remove, m_addSubscription))\n        {\n            // failed to submit close request: the underlying `aeron_subscription_t` will be eventually closed when\n            // `aeron_t` is closed but the `AsyncAddSubscription` needs to be manually freed as no `on_close_complete`\n            // callback will be invoked in this case.\n            if (aeron_errcode() == AERON_CLIENT_ERROR_BUFFER_FULL)\n            {\n                delete m_addSubscription;\n            }\n        }\n\n        for (const std::pair<const std::int64_t, AsyncDestination *>& e : m_pendingDestinations)\n        {\n            aeron_async_cmd_free(e.second);\n        }\n    }\n\n    /**\n     * Media address for delivery to the channel.\n     *\n     * @return Media address for delivery to the channel.\n     */\n    inline const std::string &channel() const\n    {\n        return m_channel;\n    }\n\n    /**\n     * Stream identity for scoping within the channel media address.\n     *\n     * @return Stream identity for scoping within the channel media address.\n     */\n    inline std::int32_t streamId() const\n    {\n        return m_constants.stream_id;\n    }\n\n    /**\n     * Registration Id returned by Aeron::addSubscription when this Subscription was added.\n     *\n     * @return the registrationId of the subscription.\n     */\n    inline std::int64_t registrationId() const\n    {\n        return m_constants.registration_id;\n    }\n\n    /**\n     * Get the counter id used to represent the channel status.\n     *\n     * @return the counter id used to represent the channel status.\n     */\n    inline std::int32_t channelStatusId() const\n    {\n        return m_constants.channel_status_indicator_id;\n    }\n\n    /**\n     * Get the status for the channel of this {@link Subscription}\n     *\n     * @return status code for this channel\n     */\n    std::int64_t channelStatus() const\n    {\n        return aeron_subscription_channel_status(m_subscription);\n    }\n\n    /**\n     * Fetches the local socket addresses for this subscription. If the channel is not\n     * {@link aeron::concurrent::status::ChannelEndpointStatus::CHANNEL_ENDPOINT_ACTIVE}, then this will return an\n     * empty list.\n     *\n     * The format is as follows:\n     * <br>\n     * <br>\n     * IPv4: <code>ip address:port</code>\n     * <br>\n     * IPv6: <code>[ip6 address]:port</code>\n     * <br>\n     * <br>\n     * This is to match the formatting used in the Aeron URI\n     *\n     * @return local socket address for this subscription.\n     * @see #channelStatus()\n     */\n    std::vector<std::string> localSocketAddresses() const\n    {\n        const int initialVectorSize = 16;\n        std::uint8_t buffers[initialVectorSize][AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN];\n        aeron_iovec_t initialIovecs[initialVectorSize];\n        std::unique_ptr<std::uint8_t[]> overflowBuffers;\n        std::unique_ptr<aeron_iovec_t[]> overflowIovecs;\n\n        for (size_t i = 0, n = 16; i < n; i++)\n        {\n            initialIovecs[i].iov_base = buffers[i];\n            initialIovecs[i].iov_len = AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN;\n        }\n        aeron_iovec_t *iovecs = initialIovecs;\n\n        int initialResult = aeron_subscription_local_sockaddrs(m_subscription, initialIovecs, 16);\n        if (initialResult < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n        int addressCount = initialResult;\n\n        if (initialVectorSize < initialResult)\n        {\n            const int overflowVectorSize = initialResult;\n\n            overflowBuffers = std::unique_ptr<std::uint8_t[]>(\n                new std::uint8_t[overflowVectorSize * AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN]);\n            overflowIovecs = std::unique_ptr<aeron_iovec_t[]>(new aeron_iovec_t[overflowVectorSize]);\n\n            for (int i = 0; i < overflowVectorSize; i++)\n            {\n                overflowIovecs[i].iov_base = &overflowBuffers[i * AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN];\n                overflowIovecs[i].iov_len = AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN;\n            }\n\n            int overflowResult = aeron_subscription_local_sockaddrs(\n                m_subscription, overflowIovecs.get(), static_cast<std::size_t>(overflowVectorSize));\n            if (overflowResult < 0)\n            {\n                AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n            }\n\n            addressCount = overflowResult < overflowVectorSize ? overflowResult : overflowVectorSize;\n            iovecs = overflowIovecs.get();\n        }\n\n        std::vector<std::string> localAddresses;\n        for (int i = 0; i < addressCount; i++)\n        {\n            localAddresses.push_back(std::string(reinterpret_cast<char *>(iovecs[i].iov_base)));\n        }\n\n        return localAddresses;\n    }\n\n    /**\n     * Resolve channel endpoint and replace it with the port from the ephemeral range when 0 was provided. If there are\n     * no addresses, or if there is more than one, returned from {@link #localSocketAddresses()} then the original\n     * {@link #channel()} is returned.\n     * <p>\n     * If the channel is not {@link aeron::concurrent::status::ChannelEndpointStatus::CHANNEL_ENDPOINT_ACTIVE}, then an\n     * empty string will be returned.\n     *\n     * @return channel URI string with an endpoint being resolved to the allocated port.\n     * @see #channelStatus()\n     * @see #localSocketAddresses()\n     */\n    std::string tryResolveChannelEndpointPort() const\n    {\n        char uri_buffer[AERON_URI_MAX_LENGTH] = { 0 };\n\n        if (aeron_subscription_try_resolve_channel_endpoint_port(m_subscription, uri_buffer, sizeof(uri_buffer)) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return { uri_buffer };\n    }\n\n    /**\n     * Find the resolved endpoint for the channel. This may be null of MDS is used and no destination is yet added.\n     * The result will similar to taking the first element returned from {@link #localSocketAddresses()}. If more than\n     * one destination is added then the first found is returned.\n     * <p>\n     * If the channel is not {@link aeron::concurrent::status::ChannelEndpointStatus::CHANNEL_ENDPOINT_ACTIVE}, then an\n     * empty string will be returned.\n     *\n     * @return The resolved endpoint or an empty string if not found.\n     * @see #channelStatus()\n     * @see #localSocketAddresses()\n     */\n    std::string resolvedEndpoint() const\n    {\n        char buffer[AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN] = { 0 };\n\n        int result = aeron_subscription_resolved_endpoint(m_subscription, buffer, sizeof(buffer));\n        if (result < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return 0 < result ? std::string(buffer) : std::string{};\n    }\n\n    AsyncDestination *addDestinationAsync(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = nullptr;\n        if (aeron_subscription_async_add_destination(&async, m_aeron, m_subscription, endpointChannel.c_str()))\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return async;\n    }\n\n    /**\n     * Add a destination manually to a multi-destination Subscription.\n     *\n     * @param endpointChannel for the destination to add.\n     * @return correlation id for the add command\n     */\n    std::int64_t addDestination(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = addDestinationAsync(endpointChannel);\n        std::int64_t correlationId = aeron_async_destination_get_registration_id(async);\n\n        m_pendingDestinations[correlationId] = async;\n\n        return correlationId;\n    }\n\n    AsyncDestination *removeDestinationAsync(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = nullptr;\n        if (aeron_subscription_async_remove_destination(&async, m_aeron, m_subscription, endpointChannel.c_str()))\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return async;\n    }\n\n    /**\n     * Remove a previously added destination from a multi-destination Subscription.\n     *\n     * @param endpointChannel for the destination to remove.\n     * @return correlation id for the remove command\n     */\n    std::int64_t removeDestination(const std::string &endpointChannel)\n    {\n        AsyncDestination *async = removeDestinationAsync(endpointChannel);\n        std::int64_t correlationId = aeron_async_destination_get_registration_id(async);\n\n        m_pendingDestinations[correlationId] = async;\n\n        return correlationId;\n    }\n\n    bool findDestinationResponse(AsyncDestination *async)\n    {\n        int result = aeron_publication_async_destination_poll(async);\n        if (result < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return 0 < result;\n    }\n\n    /**\n     * Retrieve the status of the associated add or remove destination operation with the given correlationId.\n     *\n     * This method is non-blocking.\n     *\n     * The value returned is dependent on what has occurred with respect to the media driver:\n     *\n     * - If the correlationId is unknown, then an exception is thrown.\n     * - If the media driver has not answered the add/remove command, then a false is returned.\n     * - If the media driver has successfully added or removed the destination then true is returned.\n     * - If the media driver has returned an error, this method will throw the error returned.\n     *\n     * @see Subscription::addDestination\n     * @see Subscription::removeDestination\n     *\n     * @param correlationId of the add/remove command returned by Subscription::addDestination\n     * or Subscription::removeDestination\n     * @return true for added or false if not.\n     */\n    bool findDestinationResponse(std::int64_t correlationId)\n    {\n        auto search = m_pendingDestinations.find(correlationId);\n        if (search == m_pendingDestinations.end())\n        {\n            throw IllegalArgumentException(\"Unknown correlation id\", SOURCEINFO);\n        }\n\n        auto async = search->second;\n        try\n        {\n            bool result = findDestinationResponse(async);\n            if (result)\n            {\n                m_pendingDestinations.erase(correlationId);\n            }\n            return result;\n        }\n        catch (...)\n        {\n            m_pendingDestinations.erase(correlationId);\n            throw;\n        }\n    }\n\n    /**\n     * Poll the {@link Image}s under the subscription for available message fragments.\n     * <p>\n     * Each fragment read will be a whole message if it is under MTU length. If larger than MTU then it will come\n     * as a series of fragments ordered withing a session.\n     *\n     * @param fragmentHandler callback for handling each message fragment as it is read.\n     * @param fragmentLimit   number of message fragments to limit for the poll across multiple Image s.\n     * @return the number of fragments received\n     *\n     * @see fragment_handler_t\n     */\n    template<typename F>\n    inline int poll(F &&fragmentHandler, int fragmentLimit)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = fragmentHandler;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n\n        int numFragments = aeron_subscription_poll(\n            m_subscription, doPoll<handler_type>, handler_ptr, static_cast<std::size_t>(fragmentLimit));\n        if (numFragments < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return numFragments;\n    }\n\n    /**\n     * Poll in a controlled manner the Image s under the subscription for available message fragments.\n     * Control is applied to fragments in the stream. If more fragments can be read on another stream\n     * they will even if BREAK or ABORT is returned from the fragment handler.\n     * <p>\n     * Each fragment read will be a whole message if it is under MTU length. If larger than MTU then it will come\n     * as a series of fragments ordered within a session.\n     * <p>\n     * To assemble messages that span multiple fragments then use controlled_poll_fragment_handler_t.\n     *\n     * @param fragmentHandler callback for handling each message fragment as it is read.\n     * @param fragmentLimit   number of message fragments to limit for the poll operation across multiple Image s.\n     * @return the number of fragments received\n     * @see controlled_poll_fragment_handler_t\n     */\n    template<typename F>\n    inline int controlledPoll(F &&fragmentHandler, int fragmentLimit)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = fragmentHandler;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n\n        int numFragments = aeron_subscription_controlled_poll(\n            m_subscription, doControlledPoll<handler_type>, handler_ptr, static_cast<std::size_t>(fragmentLimit));\n        if (numFragments < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return numFragments;\n    }\n\n    /**\n     * Poll the Image s under the subscription for available message fragments in blocks.\n     *\n     * @param blockHandler     to receive a block of fragments from each Image.\n     * @param blockLengthLimit for each individual block.\n     * @return the number of bytes consumed.\n     */\n    template<typename F>\n    inline long blockPoll(F &&blockHandler, int blockLengthLimit)\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = blockHandler;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n\n        long numFragments = aeron_subscription_block_poll(\n            m_subscription, doBlockPoll<handler_type>, handler_ptr, static_cast<std::size_t>(blockLengthLimit));\n        if (numFragments < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return numFragments;\n    }\n\n    /**\n     * Is the subscription connected by having at least one open image available.\n     *\n     * @return true if the subscription has more than one open image available.\n     */\n    inline bool isConnected() const\n    {\n        return aeron_subscription_is_connected(m_subscription);\n    }\n\n    /**\n     * Count of images associated with this subscription.\n     *\n     * @return count of images associated with this subscription.\n     */\n    inline int imageCount() const\n    {\n        return aeron_subscription_image_count(m_subscription);\n    }\n\n    /**\n     * Return the {@link Image} associated with the given sessionId.\n     *\n     * This method returns a share_ptr to the underlying Image and must be released before the Image may be fully\n     * reclaimed.\n     *\n     * @param sessionId associated with the Image.\n     * @return Image associated with the given sessionId or nullptr if no Image exist.\n     */\n    inline std::shared_ptr<Image> imageBySessionId(std::int32_t sessionId) const\n    {\n        aeron_image_t *image = aeron_subscription_image_by_session_id(m_subscription, sessionId);\n        if (nullptr == image)\n        {\n            return nullptr;\n        }\n\n        return createImage(image);\n    }\n\n    /**\n     * Return the {@link Image} associated with the given index.\n     *\n     * This method returns a share_ptr to the underlying Image and must be released before the Image may be fully\n     * reclaimed.\n     *\n     * @param index in the array\n     * @return image at given index or exception if out of range.\n     */\n    inline std::shared_ptr<Image> imageByIndex(std::size_t index) const\n    {\n        aeron_image_t *image = aeron_subscription_image_at_index(m_subscription, index);\n        if (nullptr == image)\n        {\n            throw std::logic_error(\"index out of range\");\n        }\n\n        return createImage(image);\n    }\n\n    /**\n     * Get a std::vector of active std::shared_ptr of {@link Image}s that match this subscription.\n     *\n     * This method will create a new std::vector<std::shared_ptr<Image>> populated with the underlying {@link Image}s.\n     *\n     * @return a std::vector of active std::shared_ptr of {@link Image}s that match this subscription\n     */\n    inline std::shared_ptr<std::vector<std::shared_ptr<Image>>> copyOfImageList() const\n    {\n        auto images = std::make_shared<std::vector<std::shared_ptr<Image>>>();\n        auto subscriptionAndImages = std::make_pair(m_subscription, images.get());\n\n        aeron_subscription_for_each_image(\n            m_subscription, Subscription::copyToVector, static_cast<void *>(&subscriptionAndImages));\n\n        return images;\n    }\n\n    /**\n     * Iterate over Image list and call passed in function.\n     *\n     * @return length of Image list\n     */\n    template<typename F>\n    inline int forEachImage(F &&func) const\n    {\n        auto imageList = copyOfImageList();\n        for (auto &image : *imageList)\n        {\n            func(image);\n        }\n        \n        return static_cast<int>(imageList->size());\n    }\n\n    /**\n     * Has this object been closed and should no longer be used?\n     *\n     * @return true if it has been closed otherwise false.\n     */\n    inline bool isClosed() const\n    {\n        return aeron_subscription_is_closed(m_subscription);\n    }\n\n    /**\n     * Get the underlying C Aeron client subscription. Applications should not need to use this method.\n     *\n     * @return the underlying C Aeron client subscription.\n     */\n    inline aeron_subscription_t *subscription() const\n    {\n        return m_subscription;\n    }\nprivate:\n    std::shared_ptr<Aeron> m_aeronRef; // ensure Aeron instance is being deleted after its children\n    aeron_t *m_aeron = nullptr;\n    aeron_subscription_t *m_subscription = nullptr;\n    AsyncAddSubscription *m_addSubscription = nullptr;\n    aeron_subscription_constants_t m_constants = {};\n    std::string m_channel;\n    std::unordered_map<std::int64_t, AsyncDestination *> m_pendingDestinations = {};\n\n    static void copyToVector(aeron_image_t *image, void *clientd)\n    {\n        auto subscriptionAndImages =\n            static_cast<std::pair<aeron_subscription_t *, std::vector<std::shared_ptr<Image>> *> *>(clientd);\n        subscriptionAndImages->second->push_back(std::make_shared<Image>(subscriptionAndImages->first, image));\n    }\n\n    inline std::shared_ptr<Image> createImage(aeron_image_t *image) const\n    {\n        std::shared_ptr<Image> result;\n        try {\n            result = std::make_shared<Image>(m_subscription, image);\n        }\n        catch (...)\n        {\n            aeron_subscription_image_release(m_subscription, image);\n            throw;\n        }\n        aeron_subscription_image_release(m_subscription, image);\n        return result;\n    }\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/AgentInvoker.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_AGENT_INVOKER_H\n#define AERON_AGENT_INVOKER_H\n\n#include <functional>\n#include <thread>\n#include <atomic>\n\n#include \"util/Exceptions.h\"\n#include \"concurrent/logbuffer/TermReader.h\"\n\nnamespace aeron {\n\nnamespace concurrent {\n\ntemplate<typename Agent>\nclass AgentInvoker\n{\npublic:\n    AgentInvoker(Agent &agent, util::exception_handler_t &exceptionHandler) :\n        m_agent(agent),\n        m_exceptionHandler(exceptionHandler)\n    {\n    }\n\n    /**\n     * Has the Agent been started?\n     *\n     * @return has the Agent been started?\n     */\n    inline bool isStarted() const\n    {\n        return m_isStarted;\n    }\n\n    /**\n     * Is the Agent running?\n     *\n     * @return is the Agent been started successfully and not closed?\n     */\n    inline bool isRunning() const\n    {\n        return m_isRunning;\n    }\n\n    /**\n     * Has the Agent been closed?\n     *\n     * @return has the Agent been closed?\n     */\n    inline bool isClosed() const\n    {\n        return m_isClosed;\n    }\n\n    /**\n     * Mark the invoker as started and call the Agent::onStart() method.\n     * <p>\n     * Startup logic will only be performed once.\n     */\n    inline void start()\n    {\n        try\n        {\n            if (!m_isStarted)\n            {\n                m_isStarted = true;\n                m_agent.onStart();\n                m_isRunning = true;\n            }\n        }\n        catch (const util::SourcedException &exception)\n        {\n            m_exceptionHandler(exception);\n            close();\n        }\n    }\n\n    /**\n     * Invoke the Agent::doWork() method and return the work count.\n     *\n     * If not successfully started or after closed then this method will return without invoking the {@link Agent}.\n     *\n     * @return the work count for the Agent::doWork() method.\n     */\n    inline int invoke()\n    {\n        int workCount = 0;\n\n        if (m_isRunning)\n        {\n            try\n            {\n                workCount = m_agent.doWork();\n            }\n            catch (const util::SourcedException &exception)\n            {\n                m_exceptionHandler(exception);\n            }\n        }\n\n        return workCount;\n    }\n\n    /**\n     * Mark the invoker as closed and call the Agent::onClose() logic for clean up.\n     *\n     * The clean up logic will only be performed once.\n     */\n    inline void close()\n    {\n        try\n        {\n            if (!m_isClosed)\n            {\n                m_isRunning = false;\n                m_isClosed = true;\n                m_agent.onClose();\n            }\n        }\n        catch (const util::SourcedException &exception)\n        {\n            m_exceptionHandler(exception);\n        }\n    }\n\nprivate:\n    Agent &m_agent;\n    util::exception_handler_t &m_exceptionHandler;\n    bool m_isStarted = false;\n    bool m_isRunning = false;\n    bool m_isClosed = false;\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/AgentRunner.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_AGENT_RUNNER_H\n#define AERON_AGENT_RUNNER_H\n\n#include <string>\n#include <functional>\n#include <thread>\n#include <atomic>\n\n#include \"util/Exceptions.h\"\n#include \"util/ScopeUtils.h\"\n#include \"concurrent/logbuffer/TermReader.h\"\n\n#if !defined(AERON_COMPILER_MSVC)\n#include <pthread.h>\n#endif\n\nnamespace aeron {\n\nnamespace concurrent {\n\ntemplate <typename Agent, typename IdleStrategy>\nclass AgentRunner\n{\npublic:\n    AgentRunner(Agent &agent, IdleStrategy &idleStrategy, util::exception_handler_t &exceptionHandler) :\n        m_agent(agent),\n        m_idleStrategy(idleStrategy),\n        m_exceptionHandler(exceptionHandler),\n        m_name(\"aeron-agent\")\n    {\n    }\n\n    AgentRunner(\n        Agent &agent,\n        IdleStrategy &idleStrategy,\n        util::exception_handler_t &exceptionHandler,\n        const std::string &name) :\n        m_agent(agent),\n        m_idleStrategy(idleStrategy),\n        m_exceptionHandler(exceptionHandler),\n        m_name(name)\n    {\n    }\n\n    /**\n     * Name given to the thread running the agent.\n     *\n     * @return the name given to the thread running the agent.\n     */\n    inline const std::string &name() const\n    {\n        return m_name;\n    }\n\n    /**\n     * Is the Agent started?\n     *\n     * @return is the Agent started?\n     */\n    inline bool isStarted() const\n    {\n        return m_isStarted.load(std::memory_order_acquire);\n    }\n\n    /**\n     * Is the Agent running?\n     *\n     * @return is the Agent started successfully and not closed?\n     */\n    inline bool isRunning() const\n    {\n        return m_isRunning.load(std::memory_order_acquire);\n    }\n\n    /**\n     * Has the Agent been closed?\n     *\n     * @return has the Agent been closed?\n     */\n    inline bool isClosed() const\n    {\n        return m_isClosed.load(std::memory_order_acquire);\n    }\n\n    /**\n     * Start the Agent running. Start may be called only once and is invalid after close has been called.\n     *\n     * Will spawn a std::thread.\n     */\n    inline void start()\n    {\n        if (m_isClosed.load(std::memory_order_acquire))\n        {\n            throw util::IllegalStateException(std::string(\"AgentRunner closed\"), SOURCEINFO);\n        }\n\n        bool expected = false;\n        if (!m_isStarted.compare_exchange_strong(expected, true, std::memory_order_seq_cst))\n        {\n            throw util::IllegalStateException(std::string(\"AgentRunner already started\"), SOURCEINFO);\n        }\n\n        m_thread = std::thread(\n            [&]()\n            {\n#if defined(AERON_COMPILER_MSVC)\n#elif defined(__APPLE__)\n                pthread_setname_np(m_name.c_str());\n#else\n                char threadName[16UL] = {};\n                std::copy(m_name.begin(), m_name.begin() + std::min(m_name.size(), 15UL), threadName);\n                pthread_setname_np(pthread_self(), threadName);\n#endif\n                run();\n            });\n    }\n\n    /**\n     * Run the Agent duty cycle until closed.\n     */\n    inline void run()\n    {\n        m_isRunning.store(true, std::memory_order_release);\n        bool isRunning = true;\n\n        util::OnScopeExit tidy(\n            [&]()\n            {\n                m_isRunning.store(false, std::memory_order_release);\n            });\n\n        try\n        {\n            m_agent.onStart();\n        }\n        catch (const util::SourcedException &exception)\n        {\n            isRunning = false;\n            m_exceptionHandler(exception);\n        }\n\n        if (isRunning)\n        {\n            while (!m_isClosed.load(std::memory_order_acquire))\n            {\n                try\n                {\n                    m_idleStrategy.idle(m_agent.doWork());\n                }\n                catch (const util::SourcedException &exception)\n                {\n                    m_exceptionHandler(exception);\n                }\n            }\n        }\n\n        try\n        {\n            m_agent.onClose();\n        }\n        catch (const util::SourcedException &exception)\n        {\n            m_exceptionHandler(exception);\n        }\n    }\n\n    /**\n     * Close the agent and stop the associated thread from running. This method waits for the thread to join.\n     */\n    inline void close()\n    {\n        bool expected = false;\n        if (m_isClosed.compare_exchange_strong(expected, true, std::memory_order_seq_cst))\n        {\n            if (m_thread.joinable())\n            {\n                m_thread.join();\n            }\n        }\n    }\n\nprivate:\n    Agent &m_agent;\n    IdleStrategy &m_idleStrategy;\n    util::exception_handler_t &m_exceptionHandler;\n    std::atomic<bool> m_isStarted = { false };\n    std::atomic<bool> m_isRunning = { false };\n    std::atomic<bool> m_isClosed = { false };\n    std::thread m_thread;\n    const std::string m_name;\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/Atomic64.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CONCURRENT_ATOMIC64_H\n#define AERON_CONCURRENT_ATOMIC64_H\n\n#include \"util/Platform.h\"\n\n#if defined(AERON_COMPILER_GCC) && defined(AERON_CPU_X64)\n    #include \"concurrent/atomic/Atomic64_gcc_x86_64.h\"\n#elif defined(AERON_COMPILER_GCC) && defined(AERON_CPU_ARM)\n    #include \"concurrent/atomic/Atomic64_gcc_cpp11.h\"\n#elif defined(AERON_COMPILER_MSVC) && defined(AERON_CPU_X64)\n    #include \"concurrent/atomic/Atomic64_msvc.h\"\n#else\n    #error Unsupported platform!\n#endif\n\n/**\n * Set of Operations to support atomic operations in C++ that are\n * consistent with the same semantics in the JVM.\n */\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/AtomicBuffer.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONCURRENT_ATOMIC_BUFFER_H\n#define AERON_CONCURRENT_ATOMIC_BUFFER_H\n\n#include <cstring>\n#include <string>\n#include <array>\n\n#include \"util/Index.h\"\n#include \"util/Exceptions.h\"\n#include \"util/StringUtil.h\"\n#include \"util/MacroUtil.h\"\n#include \"concurrent/Atomic64.h\"\n\nnamespace aeron { namespace concurrent {\n\n/**\n * Wraps, but does not own, a buffer of memory for providing atomic operations. This is for providing a view.\n */\nclass AtomicBuffer\n{\npublic:\n    constexpr AtomicBuffer() noexcept = default;\n\n    /**\n     * Wrap a buffer of memory for a given length.\n     *\n     * @param buffer to be wrapped.\n     * @param length of the buffer for bounds checking.\n     */\n    AtomicBuffer(std::uint8_t *buffer, std::size_t length) :\n        m_buffer(buffer),\n        m_length(static_cast<length_t>(length))\n    {\n#if !defined(DISABLE_BOUNDS_CHECKS)\n        if (AERON_COND_EXPECT(length > static_cast<std::size_t>(std::numeric_limits<util::index_t>::max()), true))\n        {\n            throw aeron::util::OutOfBoundsException(\n                aeron::util::strPrintf(\"length out of bounds[%p]: length=%lld\", this, static_cast<long long>(length)),\n                SOURCEINFO);\n        }\n#endif\n    }\n\n    /**\n     * Wrap a buffer of memory for a given length and initialise the contents.\n     *\n     * @param buffer       to be wrapped.\n     * @param length       of the buffer for bounds checking.\n     * @param initialValue to set the memory too.\n     */\n    AtomicBuffer(std::uint8_t *buffer, std::size_t length, std::uint8_t initialValue) :\n        m_buffer(buffer),\n        m_length(static_cast<length_t>(length))\n    {\n#if !defined(DISABLE_BOUNDS_CHECKS)\n        if (AERON_COND_EXPECT(length > static_cast<std::size_t>(std::numeric_limits<util::index_t>::max()), true))\n        {\n            throw aeron::util::OutOfBoundsException(\n                aeron::util::strPrintf(\"length out of bounds[%p]. length=%lld\", this, static_cast<long long>(length)),\n                SOURCEINFO);\n        }\n#endif\n\n        setMemory(0, static_cast<std::size_t>(length), initialValue);\n    }\n\n    template<std::size_t N>\n    explicit AtomicBuffer(std::array<std::uint8_t, N> &buffer)\n    {\n        wrap(buffer);\n    }\n\n    template<std::size_t N>\n    AtomicBuffer(std::array<std::uint8_t, N> &buffer, std::uint8_t initialValue)\n    {\n        wrap(buffer);\n        buffer.fill(initialValue);\n    }\n\n#if COND_MOCK\n    AtomicBuffer(const AtomicBuffer &) = default;\n    AtomicBuffer& operator=(const AtomicBuffer &) = default;\n    virtual ~AtomicBuffer() = default;\n#endif\n\n    /**\n     * Wrap a buffer of memory for a given length.\n     *\n     * @param buffer to be wrapped.\n     * @param length of the buffer for bounds checking.\n     */\n    inline void wrap(std::uint8_t *buffer, std::size_t length)\n    {\n#if !defined(DISABLE_BOUNDS_CHECKS)\n        if (AERON_COND_EXPECT(length > static_cast<std::size_t>(std::numeric_limits<util::index_t>::max()), true))\n        {\n            throw aeron::util::OutOfBoundsException(\n                aeron::util::strPrintf(\"length out of bounds[%p]: length=%lld\", this, static_cast<long long>(length)),\n                SOURCEINFO);\n        }\n#endif\n\n        m_buffer = buffer;\n        m_length = static_cast<length_t>(length);\n    }\n\n    /**\n     * Wrap an existing AtomicBuffer.\n     *\n     * @param buffer from which the address and length are used.\n     */\n    inline void wrap(const AtomicBuffer &buffer)\n    {\n        m_buffer = buffer.m_buffer;\n        m_length = buffer.m_length;\n    }\n\n    template<std::size_t N>\n    inline void wrap(std::array<std::uint8_t, N> &buffer)\n    {\n        static_assert(\n            N <= std::numeric_limits<util::index_t>::max(),\n            \"requires the array to have a size that fits in an index_t\");\n\n        m_buffer = buffer.data();\n        m_length = static_cast<length_t>(N);\n    }\n\n    /**\n     * The capacity of the underlying buffer.\n     *\n     * @return the capacity of the underlying buffer.\n     */\n    inline util::index_t capacity() const\n    {\n        return static_cast<util::index_t>(m_length);\n    }\n\n    /**\n     * Update the capacity of the underlying buffer.\n     *\n     * @param length to be used for the new capacity.\n     */\n    inline void capacity(std::size_t length)\n    {\n#if !defined(DISABLE_BOUNDS_CHECKS)\n        if (AERON_COND_EXPECT(length > static_cast<std::size_t>(std::numeric_limits<util::index_t>::max()), true))\n        {\n            throw aeron::util::OutOfBoundsException(\n                aeron::util::strPrintf(\"length out of bounds[%p]: length=%lld\", this, static_cast<long long>(length)),\n                SOURCEINFO);\n        }\n#endif\n\n        m_length = static_cast<length_t>(length);\n    }\n\n    /**\n     * Get a pointer to the underlying buffer.\n     *\n     * @return a pointer to the underlying buffer\n     */\n    inline std::uint8_t *buffer() const\n    {\n        return m_buffer;\n    }\n\n    inline char *sbeData() const\n    {\n        return reinterpret_cast<char *>(m_buffer);\n    }\n\n    template <typename struct_t>\n    struct_t &overlayStruct()\n    {\n        boundsCheck(0, sizeof(struct_t));\n        return *reinterpret_cast<struct_t *>(m_buffer);\n    }\n\n    template <typename struct_t>\n    struct_t &overlayStruct(util::index_t offset)\n    {\n        boundsCheck(offset, sizeof(struct_t));\n        return *reinterpret_cast<struct_t *>(m_buffer + offset);\n    }\n\n    template <typename struct_t>\n    const struct_t &overlayStruct(util::index_t offset) const\n    {\n        boundsCheck(offset, sizeof(struct_t));\n        return *reinterpret_cast<struct_t *>(m_buffer + offset);\n    }\n\n    inline COND_MOCK_VIRTUAL void putInt64(util::index_t offset, std::int64_t v)\n    {\n        boundsCheck(offset, sizeof(std::int64_t));\n        *reinterpret_cast<std::int64_t *>(m_buffer + offset) = v;\n    }\n\n    inline COND_MOCK_VIRTUAL std::int64_t getInt64(util::index_t offset) const\n    {\n        boundsCheck(offset, sizeof(std::int64_t));\n        return *reinterpret_cast<std::int64_t *>(m_buffer + offset);\n    }\n\n    inline COND_MOCK_VIRTUAL void putInt32(util::index_t offset, std::int32_t v)\n    {\n        boundsCheck(offset, sizeof(std::int32_t));\n        *reinterpret_cast<std::int32_t *>(m_buffer + offset) = v;\n    }\n\n    inline COND_MOCK_VIRTUAL std::int32_t getInt32(util::index_t offset) const\n    {\n        boundsCheck(offset, sizeof(std::int32_t));\n        return *reinterpret_cast<std::int32_t *>(m_buffer + offset);\n    }\n\n    inline COND_MOCK_VIRTUAL std::int16_t getInt16(util::index_t offset) const\n    {\n        boundsCheck(offset, sizeof(std::int16_t));\n        return *reinterpret_cast<std::int16_t *>(m_buffer + offset);\n    }\n\n    inline COND_MOCK_VIRTUAL void putInt16(util::index_t offset, std::int16_t v)\n    {\n        boundsCheck(offset, sizeof(std::int16_t));\n        *reinterpret_cast<std::int16_t *>(m_buffer + offset) = v;\n    }\n\n    inline COND_MOCK_VIRTUAL std::uint16_t getUInt16(util::index_t offset) const\n    {\n        boundsCheck(offset, sizeof(std::uint16_t));\n        return *reinterpret_cast<std::uint16_t *>(m_buffer + offset);\n    }\n\n    inline COND_MOCK_VIRTUAL void putUInt16(util::index_t offset, std::uint16_t v)\n    {\n        boundsCheck(offset, sizeof(std::uint16_t));\n        *reinterpret_cast<std::uint16_t *>(m_buffer + offset) = v;\n    }\n\n    inline std::uint8_t getUInt8(util::index_t offset) const\n    {\n        boundsCheck(offset, sizeof(std::uint8_t));\n        return *reinterpret_cast<std::uint8_t *>(m_buffer + offset);\n    }\n\n    inline COND_MOCK_VIRTUAL void putUInt8(util::index_t offset, std::uint8_t v)\n    {\n        boundsCheck(offset, sizeof(std::uint8_t));\n        *reinterpret_cast<std::uint8_t *>(m_buffer + offset) = v;\n    }\n\n    inline COND_MOCK_VIRTUAL void putInt64Ordered(util::index_t offset, std::int64_t v)\n    {\n        boundsCheck(offset, sizeof(std::int64_t));\n        atomic::putInt64Ordered((volatile std::int64_t *)(m_buffer + offset), v);\n    }\n\n    inline COND_MOCK_VIRTUAL std::int64_t getInt64Volatile(util::index_t offset) const\n    {\n        boundsCheck(offset, sizeof(std::int64_t));\n        return atomic::getInt64Volatile((volatile std::int64_t *)(m_buffer + offset));\n    }\n\n    inline COND_MOCK_VIRTUAL void putInt32Ordered(util::index_t offset, std::int32_t v)\n    {\n        boundsCheck(offset, sizeof(std::int32_t));\n        atomic::putInt32Ordered((volatile std::int32_t *)(m_buffer + offset), v);\n    }\n\n    inline COND_MOCK_VIRTUAL std::int32_t getInt32Volatile(util::index_t offset) const\n    {\n        boundsCheck(offset, sizeof(std::int32_t));\n        return atomic::getInt32Volatile((volatile std::int32_t *)(m_buffer + offset));\n    }\n\n    inline void putInt64Atomic(util::index_t offset, std::int64_t v)\n    {\n        boundsCheck(offset, sizeof(std::int64_t));\n        atomic::putInt64Atomic((volatile std::int64_t *)(m_buffer + offset), v);\n    }\n\n    inline void putInt32Atomic(util::index_t offset, std::int32_t v)\n    {\n        boundsCheck(offset, sizeof(std::int32_t));\n        atomic::putInt32Atomic((volatile std::int32_t *)(m_buffer + offset), v);\n    }\n\n    /**\n     * Single threaded increment with release semantics.\n     *\n     * @param offset in the buffer of the word.\n     * @param delta  for to be applied to the value.\n     */\n    inline void addInt64Ordered(util::index_t offset, std::int64_t delta)\n    {\n        boundsCheck(offset, sizeof(std::int64_t));\n\n        const std::int64_t value = getInt64(offset);\n        atomic::putInt64Ordered((volatile std::int64_t *)(m_buffer + offset), value + delta);\n    }\n\n    inline bool compareAndSetInt64(util::index_t offset, std::int64_t expectedValue, std::int64_t updateValue)\n    {\n        boundsCheck(offset, sizeof(std::int64_t));\n        std::int64_t original = atomic::cmpxchg((volatile std::int64_t *)(m_buffer + offset), expectedValue, updateValue);\n        return original == expectedValue;\n    }\n\n    inline std::int64_t getAndSetInt64(util::index_t offset, std::int64_t value)\n    {\n        boundsCheck(offset, sizeof(std::int64_t));\n        return atomic::xchg((volatile std::int64_t *)(m_buffer + offset), value);\n    }\n\n    inline std::int32_t getAndSetInt32(util::index_t offset, std::int32_t value)\n    {\n        boundsCheck(offset, sizeof(std::int32_t));\n        return atomic::xchg((volatile std::int32_t *)(m_buffer + offset), value);\n    }\n\n    /**\n     * Multi threaded increment.\n     *\n     * @param offset in the buffer of the word.\n     * @param delta  for to be applied to the value.\n     * @return the value before applying the delta.\n     */\n    inline COND_MOCK_VIRTUAL std::int64_t getAndAddInt64(util::index_t offset, std::int64_t delta)\n    {\n        boundsCheck(offset, sizeof(std::int64_t));\n        return atomic::getAndAddInt64((volatile std::int64_t *)(m_buffer + offset), delta);\n    }\n\n    /**\n     * Single threaded add with release semantics.\n     *\n     * @param offset in the buffer of the word.\n     * @param delta  for to be applied to the value.\n     */\n    inline void addInt32Ordered(util::index_t offset, std::int32_t delta)\n    {\n        boundsCheck(offset, sizeof(std::int32_t));\n\n        const std::int32_t value = getInt32(offset);\n        atomic::putInt32Ordered((volatile std::int32_t *)(m_buffer + offset), value + delta);\n    }\n\n    inline bool compareAndSetInt32(util::index_t offset, std::int32_t expectedValue, std::int32_t updateValue)\n    {\n        boundsCheck(offset, sizeof(std::int32_t));\n        std::int32_t original = atomic::cmpxchg((volatile std::int32_t *)(m_buffer + offset), expectedValue, updateValue);\n        return original == expectedValue;\n    }\n\n    /**\n     * Multi threaded increment.\n     *\n     * @param offset in the buffer of the word.\n     * @param delta  for to be applied to the value.\n     * @return the value before applying the delta.\n     */\n    inline COND_MOCK_VIRTUAL std::int32_t getAndAddInt32(util::index_t offset, std::int32_t delta)\n    {\n        boundsCheck(offset, sizeof(std::int32_t));\n        return atomic::getAndAddInt32((volatile std::int32_t *)(m_buffer + offset), delta);\n    }\n\n    inline COND_MOCK_VIRTUAL void putBytes(\n        util::index_t index, const concurrent::AtomicBuffer &srcBuffer, util::index_t srcIndex, util::index_t length)\n    {\n        boundsCheck(index, length);\n        srcBuffer.boundsCheck(srcIndex, length);\n        ::memcpy(m_buffer + index, srcBuffer.m_buffer + srcIndex, static_cast<std::size_t>(length));\n    }\n\n    inline COND_MOCK_VIRTUAL void putBytes(util::index_t index, const std::uint8_t *srcBuffer, util::index_t length)\n    {\n        boundsCheck(index, length);\n        ::memcpy(m_buffer + index, srcBuffer, static_cast<std::size_t>(length));\n    }\n\n    inline void getBytes(util::index_t index, std::uint8_t *dst, util::index_t length) const\n    {\n        boundsCheck(index, length);\n        ::memcpy(dst, m_buffer + index, static_cast<std::size_t>(length));\n    }\n\n    inline void setMemory(util::index_t offset, std::size_t length, std::uint8_t value)\n    {\n        boundsCheck(offset, length);\n        ::memset(m_buffer + offset, value, length);\n    }\n\n    inline std::string getString(util::index_t offset) const\n    {\n        std::int32_t length;\n\n        boundsCheck(offset, sizeof(length));\n        ::memcpy(reinterpret_cast<char *>(&length), m_buffer + offset, sizeof(length));\n\n        return getStringWithoutLength(offset + sizeof(std::int32_t), static_cast<std::size_t>(length));\n    }\n\n    inline std::string getStringWithoutLength(util::index_t offset, std::size_t length) const\n    {\n        boundsCheck(offset, length);\n        return { m_buffer + static_cast<std::size_t>(offset), m_buffer + static_cast<std::size_t>(offset) + length };\n    }\n\n    inline std::int32_t getStringLength(util::index_t offset) const\n    {\n        std::int32_t length;\n\n        boundsCheck(offset, sizeof(length));\n        ::memcpy(reinterpret_cast<char *>(&length), m_buffer + offset, sizeof(length));\n\n        return length;\n    }\n\n    std::int32_t putString(util::index_t offset, const std::string &value)\n    {\n        auto length = static_cast<std::int32_t>(value.length());\n\n        boundsCheck(offset, value.length() + sizeof(length));\n\n        ::memcpy(m_buffer + offset, reinterpret_cast<const char *>(&length), sizeof(length));\n        ::memcpy(m_buffer + offset + sizeof(length), value.c_str(), value.length());\n\n        return static_cast<std::int32_t>(sizeof(std::int32_t)) + length;\n    }\n\n    std::int32_t COND_MOCK_VIRTUAL putStringWithoutLength(util::index_t offset, const std::string &value)\n    {\n        boundsCheck(offset, value.length());\n        ::memcpy(m_buffer + offset, value.c_str(), value.length());\n        return static_cast<std::int32_t>(value.length());\n    }\n\n#if !defined(DISABLE_BOUNDS_CHECKS)\n    inline void boundsCheck(util::index_t index, std::uint64_t length) const\n    {\n        if (AERON_COND_EXPECT(index < 0 || (static_cast<std::uint64_t>(m_length) - index) < length, false))\n        {\n            throw aeron::util::OutOfBoundsException(\n                aeron::util::strPrintf(\n                    \"index out of bounds[%p]: index=%lld + %lld, capacity=%lld\",\n                    this, static_cast<long long>(index), length, static_cast<long long>(m_length)),\n                SOURCEINFO);\n        }\n    }\n#else\n    inline void boundsCheck(util::index_t, std::uint64_t) const {}\n#endif\n\nprivate:\n    // The internal length type used by the atomic buffer\n    typedef std::uint32_t length_t;\n\n    std::uint8_t *m_buffer = nullptr;\n    length_t m_length = 0;\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/AtomicCounter.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CONCURRENT_ATOMIC_COUNTER_H\n#define AERON_CONCURRENT_ATOMIC_COUNTER_H\n\n#include <cstdint>\n#include <memory>\n\n#include \"aeronc.h\"\n\n#include \"util/Index.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/CountersReader.h\"\n\nnamespace aeron { namespace concurrent {\n\nclass AtomicCounter\n{\npublic:\n    explicit AtomicCounter(aeron_counter_t *counter) : m_counter(counter), m_ptr(aeron_counter_addr(counter))\n    {\n        aeron_counter_constants(m_counter, &m_constants);\n    }\n\n    AtomicCounter(std::int64_t *ptr, std::int64_t registrationId, std::int32_t counterId) :\n        m_ptr(ptr)\n    {\n        m_constants.registration_id = registrationId;\n        m_constants.counter_id = counterId;\n    }\n\n    ~AtomicCounter()\n    {\n        if (nullptr != m_counter)\n        {\n            aeron_counter_close(m_counter, nullptr, nullptr);\n        }\n    }\n\n    inline std::int32_t id() const\n    {\n        return m_constants.counter_id;\n    }\n\n    inline void increment()\n    {\n        atomic::getAndAddInt64(m_ptr, 1);\n    }\n\n    inline void incrementOrdered()\n    {\n        std::int64_t currentValue = *m_ptr;\n        atomic::putInt64Ordered(m_ptr, currentValue + 1);\n    }\n\n    inline void set(std::int64_t value)\n    {\n        atomic::putInt64Atomic(m_ptr, value);\n    }\n\n    inline void setOrdered(std::int64_t value)\n    {\n        atomic::putInt64Ordered(m_ptr, value);\n    }\n\n    inline void setWeak(std::int64_t value)\n    {\n        *m_ptr = value;\n    }\n\n    inline std::int64_t getAndAdd(std::int64_t value)\n    {\n        return atomic::getAndAddInt64(m_ptr, value);\n    }\n\n    inline std::int64_t getAndAddOrdered(std::int64_t increment)\n    {\n        std::int64_t currentValue = *m_ptr;\n        atomic::putInt64Ordered(m_ptr, currentValue + increment);\n        return currentValue;\n    }\n\n    inline std::int64_t getAndSet(std::int64_t value)\n    {\n        return atomic::xchg(m_ptr, value);\n    }\n\n    inline bool compareAndSet(std::int64_t expectedValue, std::int64_t updateValue)\n    {\n        std::int64_t original = atomic::cmpxchg(m_ptr, expectedValue, updateValue);\n        return original == expectedValue;\n    }\n\n    inline std::int64_t get() const\n    {\n        return atomic::getInt64Volatile(m_ptr);\n    }\n\n    inline std::int64_t getWeak() const\n    {\n        return *m_ptr;\n    }\n\nprotected:\n    aeron_counter_t *counter() const\n    {\n        return m_counter;\n    }\n\nprivate:\n    aeron_counter_t *m_counter = nullptr;\n    std::int64_t *m_ptr = nullptr;\n    aeron_counter_constants_t m_constants = {};\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/BackOffIdleStrategy.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_BACKOFF_IDLE_STRATEGY_H\n#define AERON_BACKOFF_IDLE_STRATEGY_H\n\n#include <thread>\n#include <chrono>\n#include <algorithm>\n\n#include \"concurrent/Atomic64.h\"\n#include \"util/BitUtil.h\"\n\nnamespace aeron { namespace concurrent {\n\nstatic constexpr std::uint8_t BACKOFF_STATE_NOT_IDLE = 0;\nstatic constexpr std::uint8_t BACKOFF_STATE_SPINNING = 1;\nstatic constexpr std::uint8_t BACKOFF_STATE_YIELDING = 2;\nstatic constexpr std::uint8_t BACKOFF_STATE_PARKING = 3;\n\nclass BackoffIdleStrategy\n{\npublic:\n    explicit BackoffIdleStrategy(\n        std::int64_t maxSpins = 10,\n        std::int64_t maxYields = 20,\n        std::chrono::duration<long, std::nano> minParkPeriodNs = std::chrono::duration<long, std::nano>(1000),\n        std::chrono::duration<long, std::nano> maxParkPeriodNs = std::chrono::duration<long, std::milli>(1)) :\n        m_prePad(),\n        m_maxSpins(maxSpins),\n        m_maxYields(maxYields),\n        m_minParkPeriodNs(minParkPeriodNs),\n        m_maxParkPeriodNs(maxParkPeriodNs),\n        m_spins(0),\n        m_yields(0),\n        m_parkPeriodNs(minParkPeriodNs),\n        m_state(BACKOFF_STATE_NOT_IDLE),\n        m_postPad()\n    {\n    }\n\n    inline void idle(int workCount)\n    {\n        if (workCount > 0)\n        {\n            reset();\n        }\n        else\n        {\n            idle();\n        }\n    }\n\n    inline void reset()\n    {\n        m_spins = 0;\n        m_yields = 0;\n        m_parkPeriodNs = m_minParkPeriodNs;\n        m_state = BACKOFF_STATE_NOT_IDLE;\n    }\n\n    inline void idle()\n    {\n        switch(m_state)\n        {\n            case BACKOFF_STATE_NOT_IDLE:\n                m_state = BACKOFF_STATE_SPINNING;\n                m_spins++;\n                break;\n\n            case BACKOFF_STATE_SPINNING:\n                atomic::cpu_pause();\n                if (++m_spins > m_maxSpins)\n                {\n                    m_state = BACKOFF_STATE_YIELDING;\n                    m_yields = 0;\n                }\n                break;\n\n            case BACKOFF_STATE_YIELDING:\n                if (++m_yields > m_maxYields)\n                {\n                    m_state = BACKOFF_STATE_PARKING;\n                    m_parkPeriodNs = m_minParkPeriodNs;\n                }\n                else\n                {\n                    std::this_thread::yield();\n                }\n                break;\n\n            case BACKOFF_STATE_PARKING:\n            default:\n                std::this_thread::sleep_for(m_parkPeriodNs);\n                m_parkPeriodNs = std::min(m_parkPeriodNs * 2, m_maxParkPeriodNs);\n                break;\n        }\n    }\n\nprotected:\n    std::uint8_t m_prePad[aeron::util::BitUtil::CACHE_LINE_LENGTH - sizeof(std::int64_t)];\n    std::int64_t m_maxSpins;\n    std::int64_t m_maxYields;\n    std::chrono::duration<long, std::nano> m_minParkPeriodNs;\n    std::chrono::duration<long, std::nano> m_maxParkPeriodNs;\n    std::int64_t m_spins;\n    std::int64_t m_yields;\n    std::chrono::duration<long, std::nano> m_parkPeriodNs;\n    std::uint8_t m_state;\n    std::uint8_t m_postPad[aeron::util::BitUtil::CACHE_LINE_LENGTH];\n};\n\n}}\n\n#endif //AERON_BACKOFF_IDLE_STRATEGY_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/BusySpinIdleStrategy.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_BUSY_SPIN_IDLE_STRATEGY_H\n#define AERON_BUSY_SPIN_IDLE_STRATEGY_H\n\n#include \"concurrent/Atomic64.h\"\n\nnamespace aeron { namespace concurrent {\n\nclass BusySpinIdleStrategy\n{\npublic:\n    BusySpinIdleStrategy() = default;\n\n    inline void idle(int workCount)\n    {\n        if (workCount > 0)\n        {\n            return;\n        }\n\n        pause();\n    }\n\n    inline void reset()\n    {\n    }\n\n    inline void idle()\n    {\n        pause();\n    }\n\n    inline static void pause()\n    {\n        atomic::cpu_pause();\n    }\n\nprivate:\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/CountersReader.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_COUNTERS_READER_H\n#define AERON_COUNTERS_READER_H\n\n#include <functional>\n\n#include \"util/Exceptions.h\"\n#include \"util/BitUtil.h\"\n#include \"concurrent/AtomicBuffer.h\"\n\n#include \"aeronc.h\"\n\nnamespace aeron { namespace concurrent {\n\n/**\n * Reads the counters metadata and values buffers.\n *\n * This class is threadsafe.\n *\n * <b>Values Buffer</b>\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                        Counter Value                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Registration Id                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                          Owner Id                             |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                     104 bytes of padding                     ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                   Repeats to end of buffer                   ...\n *  |                                                               |\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n *\n * <b>Meta Data Buffer</b>\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                        Record State                           |\n *  +---------------------------------------------------------------+\n *  |                          Type Id                              |\n *  +---------------------------------------------------------------+\n *  |                  Free-for-reuse Deadline (ms)                 |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                      112 bytes for key                       ...\n * ...                                                              |\n *  +-+-------------------------------------------------------------+\n *  |R|                      Label Length                           |\n *  +-+-------------------------------------------------------------+\n *  |                  380 bytes of Label in ASCII                 ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                   Repeats to end of buffer                   ...\n *  |                                                               |\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\n\ntypedef std::function<void(std::int32_t, std::int32_t, const AtomicBuffer&, const std::string&)> on_counters_metadata_t;\n\nusing namespace aeron::util;\n\nclass CountersReader\n{\n\npublic:\n    inline explicit CountersReader(aeron_counters_reader_t *countersReader) :\n        m_countersReader(countersReader), m_buffers{}\n    {\n        aeron_counters_reader_get_buffers(m_countersReader, &m_buffers);\n    }\n\n    template <typename F>\n    void forEach(F &&onCountersMetadata) const\n    {\n        using handler_type = typename std::remove_reference<F>::type;\n        handler_type &handler = onCountersMetadata;\n        void *handler_ptr = const_cast<void *>(reinterpret_cast<const void *>(&handler));\n\n        aeron_counters_reader_foreach_counter(m_countersReader, forEachCounter<handler_type>, handler_ptr);\n    }\n\n    inline std::int32_t findByTypeIdAndRegistrationId(\n        const std::int32_t typeId, const std::int64_t registrationId) const\n    {\n        std::int32_t id = NULL_COUNTER_ID;\n\n        forEach(\n            [&](std::int32_t counterId, std::int32_t counterTypeId, const AtomicBuffer &keyBuffer, const std::string &label)\n            {\n                if (NULL_COUNTER_ID == id && typeId == counterTypeId && registrationId == getCounterRegistrationId(counterId))\n                {\n                    id = counterId;\n                }\n            });\n\n        return id;\n    }\n\n    inline std::int32_t findByRegistrationId(const std::int64_t registrationId) const\n    {\n        std::int32_t id = NULL_COUNTER_ID;\n\n        forEach(\n            [&](std::int32_t counterId, std::int32_t counterTypeId, const AtomicBuffer &keyBuffer, const std::string &label)\n            {\n                if (NULL_COUNTER_ID == id && registrationId == getCounterRegistrationId(counterId))\n                {\n                    id = counterId;\n                }\n            });\n\n        return id;\n    }\n\n    inline std::int32_t maxCounterId() const\n    {\n        return aeron_counters_reader_max_counter_id(m_countersReader);\n    }\n\n    inline std::int64_t getCounterValue(std::int32_t id) const\n    {\n        validateCounterId(id);\n        std::int64_t *counter_addr = getCounterAddress(id);\n        return *counter_addr;\n    }\n\n    /// @cond HIDDEN_SYMBOLS\n    inline std::int64_t *getCounterAddress(std::int32_t id) const\n    {\n        return aeron_counters_reader_addr(m_countersReader, id);\n    }\n    /// @endcond\n\n    inline std::int64_t getCounterRegistrationId(std::int32_t id) const\n    {\n        validateCounterId(id);\n\n        std::int64_t registrationId;\n        if (aeron_counters_reader_counter_registration_id(m_countersReader, id, &registrationId) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return registrationId;\n    }\n\n    inline std::int64_t getCounterOwnerId(std::int32_t id) const\n    {\n        validateCounterId(id);\n\n        std::int64_t ownerId;\n        if (aeron_counters_reader_counter_owner_id(m_countersReader, id, &ownerId) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        return ownerId;\n    }\n\n    inline std::int32_t getCounterState(std::int32_t id) const\n    {\n        std::int32_t state;\n        if (aeron_counters_reader_counter_state(m_countersReader, id, &state) < 0)\n        {\n            throw util::IllegalArgumentException(\n                \"counter id \" + std::to_string(id) +\n                \" out of range: maxCounterId=\" + std::to_string(maxCounterId()),\n                SOURCEINFO);\n        }\n\n        return state;\n    }\n\n    inline std::int32_t getCounterTypeId(std::int32_t id) const\n    {\n        std::int32_t typeId;\n        if (aeron_counters_reader_counter_type_id(m_countersReader, id, &typeId) < 0)\n        {\n            throw util::IllegalArgumentException(\n                \"counter id \" + std::to_string(id) +\n                \" out of range: maxCounterId=\" + std::to_string(maxCounterId()),\n                SOURCEINFO);\n        }\n\n        return typeId;\n    }\n\n    inline std::int64_t getFreeForReuseDeadline(std::int32_t id) const\n    {\n        std::int64_t deadline;\n        if (aeron_counters_reader_free_for_reuse_deadline_ms(m_countersReader, id, &deadline))\n        {\n            throw util::IllegalArgumentException(\n                \"counter id \" + std::to_string(id) +\n                \" out of range: maxCounterId=\" + std::to_string(maxCounterId()),\n                SOURCEINFO);\n        }\n\n        return deadline;\n    }\n\n    inline std::string getCounterLabel(std::int32_t id) const\n    {\n        char buffer[AERON_COUNTER_MAX_LABEL_LENGTH];\n        int length = aeron_counters_reader_counter_label(m_countersReader, id, buffer, sizeof(buffer));\n        if (length < 0)\n        {\n            throw util::IllegalArgumentException(\n                \"counter id \" + std::to_string(id) +\n                \" out of range: maxCounterId=\" + std::to_string(maxCounterId()),\n                SOURCEINFO);\n        }\n\n        return { buffer, static_cast<std::size_t>(length) };\n    }\n\n    inline static util::index_t counterOffset(std::int32_t counterId)\n    {\n        return AERON_COUNTER_OFFSET(counterId);\n    }\n\n    inline static util::index_t metadataOffset(std::int32_t counterId)\n    {\n        return AERON_COUNTER_METADATA_OFFSET(counterId);\n    }\n\n    inline AtomicBuffer valuesBuffer() const\n    {\n        return { m_buffers.values, m_buffers.values_length };\n    }\n\n    inline AtomicBuffer metaDataBuffer() const\n    {\n        return { m_buffers.metadata, m_buffers.metadata_length };\n    }\n\n    static constexpr std::int32_t NULL_COUNTER_ID = AERON_NULL_COUNTER_ID;\n\n    static constexpr std::int32_t RECORD_UNUSED = AERON_COUNTER_RECORD_UNUSED;\n    static constexpr std::int32_t RECORD_ALLOCATED = AERON_COUNTER_RECORD_ALLOCATED;\n    static constexpr std::int32_t RECORD_RECLAIMED = AERON_COUNTER_RECORD_RECLAIMED;\n\n    static constexpr std::int64_t DEFAULT_REGISTRATION_ID = AERON_COUNTER_REGISTRATION_ID_DEFAULT;\n    static constexpr std::int64_t NOT_FREE_TO_REUSE = AERON_COUNTER_NOT_FREE_TO_REUSE;\n\n    static constexpr util::index_t COUNTER_LENGTH = AERON_COUNTER_VALUE_LENGTH;\n    static constexpr util::index_t REGISTRATION_ID_OFFSET = AERON_COUNTER_REGISTRATION_ID_OFFSET;\n\n    static constexpr util::index_t METADATA_LENGTH = AERON_COUNTER_METADATA_LENGTH;\n    static constexpr util::index_t TYPE_ID_OFFSET = AERON_COUNTER_TYPE_ID_OFFSET;\n    static constexpr util::index_t FREE_FOR_REUSE_DEADLINE_OFFSET = AERON_COUNTER_FREE_FOR_REUSE_DEADLINE_OFFSET;\n    static constexpr util::index_t KEY_OFFSET = AERON_COUNTER_KEY_OFFSET;\n    static constexpr util::index_t LABEL_LENGTH_OFFSET = AERON_COUNTER_LABEL_LENGTH_OFFSET;\n\n    static constexpr std::int32_t MAX_LABEL_LENGTH = AERON_COUNTER_MAX_LABEL_LENGTH;\n    static constexpr std::int32_t MAX_KEY_LENGTH = AERON_COUNTER_MAX_KEY_LENGTH;\n\n    inline aeron_counters_reader_t *countersReader() const\n    {\n        return m_countersReader;\n    }\n\nprotected:\n    aeron_counters_reader_t *m_countersReader;\n    aeron_counters_reader_buffers_t m_buffers;\n\n    void validateCounterId(std::int32_t counterId) const\n    {\n        if (counterId < 0 || counterId > maxCounterId())\n        {\n            throw util::IllegalArgumentException(\n                \"counter id \" + std::to_string(counterId) +\n                \" out of range: maxCounterId=\" + std::to_string(maxCounterId()),\n                SOURCEINFO);\n        }\n    }\n\n    template<typename H>\n    static void forEachCounter(\n        std::int64_t /* value */,\n        std::int32_t id,\n        std::int32_t typeId,\n        const std::uint8_t *key,\n        std::size_t key_length,\n        const char *label,\n        std::size_t label_length,\n        void *clientd)\n    {\n        H &handler = *reinterpret_cast<H *>(clientd);\n        AtomicBuffer keyBuffer = { const_cast<std::uint8_t *>(key), key_length };\n        std::string labelStr = { const_cast<char *>(label), label_length };\n\n        handler(id, typeId, keyBuffer, labelStr);\n    }\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/NoOpIdleStrategy.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_NO_OP_IDLE_STRATEGY_H\n#define AERON_NO_OP_IDLE_STRATEGY_H\n\nnamespace aeron { namespace concurrent {\n\nclass NoOpIdleStrategy\n{\npublic:\n    NoOpIdleStrategy() = default;\n\n    inline void idle(int workCount)\n    {\n    }\n\n    inline void reset()\n    {\n    }\n\n    inline void idle()\n    {\n    }\n\nprivate:\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/SleepingIdleStrategy.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_SLEEPING_IDLE_STRATEGY_H\n#define AERON_SLEEPING_IDLE_STRATEGY_H\n\n#include <thread>\n#include <chrono>\n\nnamespace aeron { namespace concurrent {\n\nclass SleepingIdleStrategy\n{\npublic:\n    explicit SleepingIdleStrategy(const std::chrono::duration<long, std::milli> duration) :\n        m_duration(duration)\n    {\n    }\n\n    inline void idle(int workCount)\n    {\n        if (0 == workCount)\n        {\n            std::this_thread::sleep_for(m_duration);\n        }\n    }\n\n    inline void reset()\n    {\n    }\n\n    inline void idle()\n    {\n        std::this_thread::sleep_for(m_duration);\n    }\n\nprivate:\n    std::chrono::duration<long, std::milli> m_duration;\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/YieldingIdleStrategy.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_YIELDING_IDLE_STRATEGY_H\n#define AERON_YIELDING_IDLE_STRATEGY_H\n\n#include <thread>\n\nnamespace aeron { namespace concurrent {\n\nclass YieldingIdleStrategy\n{\npublic:\n    YieldingIdleStrategy() = default;\n\n    inline void idle(int workCount)\n    {\n        if (workCount > 0)\n        {\n            return;\n        }\n\n        std::this_thread::yield();\n    }\n\n    inline void reset()\n    {\n    }\n\n    inline void idle()\n    {\n        std::this_thread::yield();\n    }\n\nprivate:\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/atomic/Atomic64_gcc_cpp11.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CONCURRENT_ATOMIC64_GCC_CPP11_H\n#define AERON_CONCURRENT_ATOMIC64_GCC_CPP11_H\n\n#include <atomic>\n#include <thread>\n#include <cstdint>\n\n// Implement all operations using C++11 standard library atomics and GCC intrinsics.\n// Not as fast as the x64 specializations, but allows Aeron to work on other platforms (e.g. ARM).\n// See: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html\n\nnamespace aeron { namespace concurrent { namespace atomic {\n\ninline void thread_fence()\n{\n    std::atomic_thread_fence(std::memory_order_acq_rel);\n}\n\ninline void fence()\n{\n    std::atomic_thread_fence(std::memory_order_seq_cst);\n}\n\ninline void acquire()\n{\n    std::atomic_thread_fence(std::memory_order_acquire);\n}\n\ninline void release()\n{\n    std::atomic_thread_fence(std::memory_order_release);\n}\n\ninline void cpu_pause()\n{\n}\n\ninline std::int32_t getInt32Volatile(volatile std::int32_t *source)\n{\n    std::int32_t sequence = *reinterpret_cast<volatile std::int32_t *>(source);\n    acquire();\n\n    return sequence;\n}\n\ninline void putInt32Volatile(volatile std::int32_t *source, std::int32_t value)\n{\n    __atomic_store(source, &value, __ATOMIC_SEQ_CST);\n}\n\ninline void putInt32Ordered(volatile std::int32_t *source, std::int32_t value)\n{\n    release();\n    *reinterpret_cast<volatile std::int32_t *>(source) = value;\n}\n\ninline void putInt32Atomic(volatile std::int32_t *address, std::int32_t value)\n{\n    __atomic_store(address, &value, __ATOMIC_SEQ_CST);\n}\n\ninline std::int64_t getInt64Volatile(volatile std::int64_t *source)\n{\n    std::int64_t sequence = *reinterpret_cast<volatile std::int64_t *>(source);\n    acquire();\n\n    return sequence;\n}\n\ninline void putInt64Volatile(volatile std::int64_t *address, std::int64_t value)\n{\n    __atomic_store(address, &value, __ATOMIC_SEQ_CST);\n}\n\ninline void putInt64Ordered(volatile std::int64_t *address, std::int64_t value)\n{\n    release();\n    *reinterpret_cast<volatile std::int64_t *>(address) = value;\n}\n\ninline void putInt64Atomic(volatile std::int64_t *address, std::int64_t value)\n{\n    __atomic_store(address, &value, __ATOMIC_SEQ_CST);\n}\n\ninline std::int64_t getAndAddInt64(volatile std::int64_t *address, std::int64_t value)\n{\n    return __atomic_fetch_add(address, value, __ATOMIC_SEQ_CST);\n}\n\ninline std::int32_t getAndAddInt32(volatile std::int32_t *address, std::int32_t value)\n{\n    return __atomic_fetch_add(address, value, __ATOMIC_SEQ_CST);\n}\n\ninline std::int32_t xchg(volatile std::int32_t *address, std::int32_t value)\n{\n    std::int32_t original;\n    __atomic_exchange(address, &value, &original, __ATOMIC_SEQ_CST);\n    return original;\n}\n\ninline std::int64_t xchg(volatile std::int64_t *address, std::int64_t value)\n{\n    std::int64_t original;\n    __atomic_exchange(address, &value, &original, __ATOMIC_SEQ_CST);\n    return original;\n}\n\ninline std::int32_t cmpxchg(volatile std::int32_t *address, std::int32_t expected, std::int32_t desired)\n{\n    if (__atomic_compare_exchange(address, &expected, &desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))\n    {\n        return expected;\n    }\n    else\n    {\n        return *address;\n    }\n}\n\ninline std::int64_t cmpxchg(volatile std::int64_t *address, std::int64_t expected, std::int64_t desired)\n{\n    if (__atomic_compare_exchange(address, &expected, &desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))\n    {\n        return expected;\n    }\n    else\n    {\n        return *address;\n    }\n}\n\n//-------------------------------------\n//  Alignment\n//-------------------------------------\n// Note: May not work on local variables.\n// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=24691\n#define AERON_DECL_ALIGNED(declaration, amt) declaration __attribute__((aligned(amt)))\n\n}}}\n\n#endif"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/atomic/Atomic64_gcc_x86_64.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CONCURRENT_ATOMIC64_GCC_X86_64_H\n#define AERON_CONCURRENT_ATOMIC64_GCC_X86_64_H\n\n#include <cstdint>\n#include <atomic>\n\nnamespace aeron { namespace concurrent { namespace atomic {\n\ninline void thread_fence()\n{\n    std::atomic_thread_fence(std::memory_order_acq_rel);\n}\n\ninline void fence()\n{\n    std::atomic_thread_fence(std::memory_order_seq_cst);\n}\n\ninline void acquire()\n{\n#if (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 8)\n    volatile std::int64_t *dummy;\n    asm volatile(\"movq 0(%%rsp), %0\" : \"=r\" (dummy) :: \"memory\");\n#else\n    std::atomic_thread_fence(std::memory_order_acquire);\n#endif\n}\n\ninline void release()\n{\n    std::atomic_thread_fence(std::memory_order_release);\n}\n\ninline void cpu_pause()\n{\n    asm volatile(\"pause\\n\" ::: \"memory\");\n}\n\ninline std::int32_t getInt32Volatile(volatile std::int32_t *source)\n{\n    std::int32_t sequence = *reinterpret_cast<volatile std::int32_t *>(source);\n    acquire();\n\n    return sequence;\n}\n\ninline void putInt32Volatile(volatile std::int32_t *address, std::int32_t value)\n{\n    asm volatile(\"xchgl (%2), %0\"\n        : \"=r\" (value)\n        : \"0\" (value), \"r\" (address)\n        : \"memory\");\n}\n\ninline void putInt32Ordered(volatile std::int32_t *address, std::int32_t value)\n{\n    release();\n    *reinterpret_cast<volatile std::int32_t *>(address) = value;\n}\n\ninline void putInt32Atomic(volatile std::int32_t *address, std::int32_t value)\n{\n    asm volatile(\"xchgl (%2), %0\"\n        : \"=r\" (value)\n        : \"0\" (value), \"r\" (address)\n        : \"memory\");\n}\n\ninline std::int64_t getInt64Volatile(volatile std::int64_t *source)\n{\n    std::int64_t sequence = *reinterpret_cast<volatile std::int64_t *>(source);\n    acquire();\n\n    return sequence;\n}\n\ninline void putInt64Volatile(volatile std::int64_t *address, std::int64_t value)\n{\n    asm volatile(\"xchgq (%2), %0\"\n        : \"=r\" (value)\n        : \"0\" (value), \"r\" (address)\n        : \"memory\");\n}\n\ninline void putInt64Ordered(volatile std::int64_t *address, std::int64_t value)\n{\n    release();\n    *reinterpret_cast<volatile std::int64_t *>(address) = value;\n}\n\ninline void putInt64Atomic(volatile std::int64_t *address, std::int64_t value)\n{\n    asm volatile(\"xchgq (%2), %0\"\n        : \"=r\" (value)\n        : \"0\" (value), \"r\" (address)\n        : \"memory\");\n}\n\ninline std::int64_t getAndAddInt64(volatile std::int64_t *address, std::int64_t value)\n{\n    std::int64_t original;\n    asm volatile(\"lock; xaddq %0, %1\"\n        : \"=r\"(original), \"+m\"(*address)\n        : \"0\"(value));\n\n    return original;\n}\n\ninline std::int32_t getAndAddInt32(volatile std::int32_t *address, std::int32_t value)\n{\n    std::int32_t original;\n    asm volatile(\"lock; xaddl %0, %1\"\n        : \"=r\" (original), \"+m\" (*address)\n        : \"0\" (value));\n\n    return original;\n}\n\ninline std::int32_t xchg(volatile std::int32_t *address, std::int32_t value)\n{\n    asm volatile(\"xchgl %1, %0\"\n        : \"=r\" (value)\n        : \"m\" (*address), \"0\" (value)\n        : \"memory\");\n\n    return value;\n}\n\ninline std::int64_t xchg(volatile std::int64_t *address, std::int64_t value)\n{\n    asm volatile(\"xchgq %1, %0\"\n        : \"=r\" (value)\n        : \"m\" (*address), \"0\" (value)\n        : \"memory\");\n\n    return value;\n}\n\ninline std::int32_t cmpxchg(volatile std::int32_t *address, std::int32_t expected, std::int32_t desired)\n{\n    std::int32_t original;\n    asm volatile(\"lock; cmpxchgl %2, %1\"\n        : \"=a\" (original), \"+m\" (*address)\n        : \"q\" (desired), \"0\" (expected));\n\n    return original;\n}\n\ninline std::int64_t cmpxchg(volatile std::int64_t *address, std::int64_t expected, std::int64_t desired)\n{\n    std::int64_t original;\n    asm volatile(\"lock; cmpxchgq %2, %1\"\n        : \"=a\" (original), \"+m\" (*address)\n        : \"q\" (desired), \"0\" (expected));\n\n    return original;\n}\n\n//-------------------------------------\n//  Alignment\n//-------------------------------------\n// Note: May not work on local variables.\n// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=24691\n#define AERON_DECL_ALIGNED(declaration, amt) declaration __attribute__((aligned(amt)))\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/atomic/Atomic64_msvc.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CONCURRENT_ATOMIC64_MSVC_H\n#define AERON_CONCURRENT_ATOMIC64_MSVC_H\n\n#include <atomic>\n#include <cstdint>\n#include <intrin.h>\n\nnamespace aeron { namespace concurrent { namespace atomic {\n\ninline void thread_fence()\n{\n    std::atomic_thread_fence(std::memory_order_acq_rel);\n}\n\ninline void fence()\n{\n    std::atomic_thread_fence(std::memory_order_seq_cst);\n}\n\ninline void acquire()\n{\n    std::atomic_thread_fence(std::memory_order_acquire);\n}\n\ninline void release()\n{\n    std::atomic_thread_fence(std::memory_order_release);\n}\n\ninline void cpu_pause()\n{\n    _mm_pause();\n}\n\ninline std::int32_t getInt32Volatile(volatile std::int32_t *source)\n{\n    std::int32_t sequence = *reinterpret_cast<volatile std::int32_t *>(source);\n    acquire();\n\n    return sequence;\n}\n\ninline void putInt32Ordered(volatile std::int32_t *source, std::int32_t value)\n{\n    release();\n    *reinterpret_cast<volatile std::int32_t *>(source) = value;\n}\n\ninline void putInt32Atomic(volatile std::int32_t *address, std::int32_t value)\n{\n    _InterlockedExchange(reinterpret_cast<volatile long *>(address), value);\n}\n\ninline void putInt32Volatile(volatile std::int32_t *address, std::int32_t value)\n{\n    _InterlockedExchange(reinterpret_cast<volatile long *>(address), value);\n}\n\ninline std::int64_t getInt64Volatile(volatile std::int64_t *source)\n{\n    std::int64_t sequence = *reinterpret_cast<volatile std::int64_t *>(source);\n    acquire();\n\n    return sequence;\n}\n\ninline void putInt64Ordered(volatile std::int64_t *address, std::int64_t value)\n{\n    release();\n    *reinterpret_cast<volatile std::int64_t *>(address) = value;\n}\n\ninline void putInt64Atomic(volatile std::int64_t *address, std::int64_t value)\n{\n    _InterlockedExchange64(address, value);\n}\n\ninline void putInt64Volatile(volatile std::int64_t *address, std::int64_t value)\n{\n    _InterlockedExchange64(address, value);\n}\n\ninline std::int64_t getAndAddInt64(volatile std::int64_t *address, std::int64_t value)\n{\n    return _InterlockedExchangeAdd64(address, value);\n}\n\ninline std::int32_t getAndAddInt32(volatile std::int32_t *address, std::int32_t value)\n{\n    return _InterlockedExchangeAdd((volatile long *)address, value);\n}\n\ninline std::int32_t xchg(volatile std::int32_t *address, std::int32_t value)\n{\n    return _InterlockedExchange((volatile long *)address, value);\n}\n\ninline std::int64_t xchg(volatile std::int64_t *address, std::int64_t value)\n{\n    return _InterlockedExchange64(address, value);\n}\n\ninline std::int32_t cmpxchg(volatile std::int32_t *address, std::int32_t expected, std::int32_t desired)\n{\n    return _InterlockedCompareExchange((volatile long *)address, desired, expected);\n}\n\ninline std::int64_t cmpxchg(volatile std::int64_t *address, std::int64_t expected, std::int64_t desired)\n{\n    return _InterlockedCompareExchange64(address, desired, expected);\n}\n\n//-------------------------------------\n//  Alignment\n//-------------------------------------\n// Note: May not work on local variables.\n#define AERON_DECL_ALIGNED(declaration, amt) __declspec(align(amt)) declaration\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/errors/ErrorLogDescriptor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CONCURRENT_ERROR_LOG_DESCRIPTOR_H\n#define AERON_CONCURRENT_ERROR_LOG_DESCRIPTOR_H\n\n#include <cstddef>\n\n#include \"util/Index.h\"\n\nnamespace aeron { namespace concurrent { namespace errors {\n\n/**\n * Distinct record of error observations. Rather than grow a record indefinitely when many errors of the same type\n * are logged, this log takes the approach of only recording distinct errors of the same type type and stack trace\n * and keeping a count and time of observation so that the record only grows with new distinct observations.\n *\n * The provided {@link AtomicBuffer} can wrap a memory-mapped file so logging can be out of process. This provides\n * the benefit that if a crash or lockup occurs then the log can be read externally without loss of data.\n *\n * This class is threadsafe to be used from multiple logging threads.\n *\n * The error records are recorded to the memory mapped buffer in the following format.\n *\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |R|                         Length                              |\n *  +-+-------------------------------------------------------------+\n *  |R|                     Observation Count                       |\n *  +-+-------------------------------------------------------------+\n *  |R|                Last Observation Timestamp                   |\n *  |                                                               |\n *  +-+-------------------------------------------------------------+\n *  |R|               First Observation Timestamp                   |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                     ASCII Encoded Error                      ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\n\nnamespace ErrorLogDescriptor {\n\n#pragma pack(push)\n#pragma pack(4)\nstruct ErrorLogEntryDefn\n{\n    std::int32_t length;\n    std::int32_t observationCount;\n    std::int64_t lastObservationTimestamp;\n    std::int64_t firstObservationTimestamp;\n};\n#pragma pack(pop)\n\nstatic const util::index_t LENGTH_OFFSET = offsetof(ErrorLogEntryDefn, length);\nstatic const util::index_t OBSERVATION_COUNT_OFFSET = offsetof(ErrorLogEntryDefn, observationCount);\nstatic const util::index_t LAST_OBSERVATION_TIMESTAMP_OFFSET = offsetof(ErrorLogEntryDefn, lastObservationTimestamp);\nstatic const util::index_t FIRST_OBSERVATION_TIMESTAMP_OFFSET = offsetof(ErrorLogEntryDefn, firstObservationTimestamp);\nstatic const util::index_t ENCODED_ERROR_OFFSET = sizeof(ErrorLogEntryDefn);\nstatic const util::index_t HEADER_LENGTH = sizeof(ErrorLogEntryDefn);\n\nstatic const util::index_t RECORD_ALIGNMENT = sizeof(std::int64_t);\n\n}\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/errors/ErrorLogReader.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CONCURRENT_ERROR_LOG_READER_H\n#define AERON_CONCURRENT_ERROR_LOG_READER_H\n\n#include <functional>\n#include \"util/BitUtil.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/errors/ErrorLogDescriptor.h\"\n\nnamespace aeron { namespace concurrent { namespace errors {\n\nnamespace ErrorLogReader {\n\nusing namespace aeron::concurrent;\n\ntypedef std::function<void(\n    std::int32_t observationCount,\n    std::int64_t firstObservationTimestamp,\n    std::int64_t lastObservationTimestamp,\n    const std::string &encodedException)> error_consumer_t;\n\ninline static int read(AtomicBuffer &buffer, const error_consumer_t &consumer, std::int64_t sinceTimestamp)\n{\n    int entries = 0;\n    int offset = 0;\n    const int capacity = buffer.capacity();\n\n    while (offset < capacity)\n    {\n        const std::int32_t length = buffer.getInt32Volatile(offset + ErrorLogDescriptor::LENGTH_OFFSET);\n        if (0 == length)\n        {\n            break;\n        }\n\n        const std::int64_t lastObservationTimestamp =\n            buffer.getInt64Volatile(offset + ErrorLogDescriptor::LAST_OBSERVATION_TIMESTAMP_OFFSET);\n\n        if (lastObservationTimestamp >= sinceTimestamp)\n        {\n            auto &entry = buffer.overlayStruct<ErrorLogDescriptor::ErrorLogEntryDefn>(offset);\n\n            ++entries;\n\n            consumer(\n                entry.observationCount,\n                entry.firstObservationTimestamp,\n                lastObservationTimestamp,\n                buffer.getStringWithoutLength(\n                    offset + ErrorLogDescriptor::ENCODED_ERROR_OFFSET,\n                    static_cast<std::size_t>(length - ErrorLogDescriptor::HEADER_LENGTH)));\n        }\n\n        offset += util::BitUtil::align(length, ErrorLogDescriptor::RECORD_ALIGNMENT);\n    }\n\n    return entries;\n}\n\n}}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/logbuffer/BufferClaim.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONCURRENT_BUFFER_CLAIM_H\n#define AERON_CONCURRENT_BUFFER_CLAIM_H\n\n#include \"util/Index.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/logbuffer/DataFrameHeader.h\"\n\nnamespace aeron { namespace concurrent { namespace logbuffer {\n\n/**\n * Represents a claimed range in a buffer to be used for recording a message without copy semantics for later commit.\n * <p>\n * The claimed space is in {@link #buffer()} between {@link #offset()} and {@link #offset()} + {@link #length()}.\n * When the buffer is filled with message data, use {@link #commit()} to make it available to subscribers.\n */\nclass BufferClaim\n{\npublic:\n    typedef BufferClaim this_t;\n\n    BufferClaim() = default;\n\n    /// @cond HIDDEN_SYMBOLS\n    inline void wrap(std::uint8_t *buffer, util::index_t length)\n    {\n        m_buffer.wrap(buffer, static_cast<std::size_t>(length));\n    }\n    /// @endcond\n\n\n    /**\n     * The referenced buffer to be used.\n     *\n     * @return the referenced buffer to be used..\n     */\n    inline AtomicBuffer &buffer()\n    {\n        return m_buffer;\n    }\n\n    /**\n     * The offset in the buffer at which the claimed range begins.\n     *\n     * @return offset in the buffer at which the range begins.\n     */\n    inline util::index_t offset() const\n    {\n        return DataFrameHeader::LENGTH;\n    }\n\n    /**\n     * The length of the claimed range in the buffer.\n     *\n     * @return length of the range in the buffer.\n     */\n    inline util::index_t length() const\n    {\n        return m_buffer.capacity() - DataFrameHeader::LENGTH;\n    }\n\n    /**\n     * Get the value of the flags field.\n     *\n     * @return the value of the header flags field.\n     */\n    inline std::uint8_t flags() const\n    {\n        return m_buffer.getUInt8(DataFrameHeader::FLAGS_FIELD_OFFSET);\n    }\n\n    /**\n     * Set the value of the header flags field.\n     *\n     * @param flags value to be set in the header.\n     * @return this for a fluent API.\n     */\n    inline this_t &flags(const std::uint8_t flags)\n    {\n        m_buffer.putUInt8(DataFrameHeader::FLAGS_FIELD_OFFSET, flags);\n\n        return *this;\n    }\n\n    /**\n     * Get the value of the header type field.\n     *\n     * @return the value of the header type field.\n     */\n    inline std::uint16_t headerType() const\n    {\n        return m_buffer.getUInt16(DataFrameHeader::TYPE_FIELD_OFFSET);\n    }\n\n    /**\n     * Set the value of the header type field.\n     *\n     * @param type value to be set in the header.\n     * @return this for a fluent API.\n     */\n    inline this_t &headerType(const std::uint16_t type)\n    {\n        m_buffer.putUInt16(DataFrameHeader::TYPE_FIELD_OFFSET, type);\n\n        return *this;\n    }\n\n    /**\n     * Get the value stored in the reserve space at the end of a data frame header.\n     *\n     * @return the value stored in the reserve space at the end of a data frame header.\n     */\n    inline std::int64_t reservedValue() const\n    {\n        return m_buffer.getInt64(DataFrameHeader::RESERVED_VALUE_FIELD_OFFSET);\n    }\n\n    /**\n     * Write the provided value into the reserved space at the end of the data frame header.\n     *\n     * @param value to be stored in the reserve space at the end of a data frame header.\n     * @return this for fluent API semantics.\n     */\n    inline this_t &reservedValue(const std::int64_t value)\n    {\n        m_buffer.putInt64(DataFrameHeader::RESERVED_VALUE_FIELD_OFFSET, value);\n        return *this;\n    }\n\n    /**\n     * Commit the message to the log buffer so that is it available to subscribers.\n     */\n    inline void commit()\n    {\n        m_buffer.putInt32Ordered(0, m_buffer.capacity());\n    }\n\n    /**\n     * Abort a claim of the message space to the log buffer so that log can progress ignoring this claim.\n     */\n    inline void abort()\n    {\n        m_buffer.putUInt16(DataFrameHeader::TYPE_FIELD_OFFSET, DataFrameHeader::HDR_TYPE_PAD);\n        m_buffer.putInt32Ordered(0, m_buffer.capacity());\n    }\n\nprotected:\n    AtomicBuffer m_buffer;\n};\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/logbuffer/DataFrameHeader.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONCURRENT_DATA_FRAME_HEADER_H\n#define AERON_CONCURRENT_DATA_FRAME_HEADER_H\n\n#include <cstddef>\n\n#include \"util/Index.h\"\n#include \"aeronc.h\"\n\nnamespace aeron { namespace concurrent { namespace logbuffer {\n\nnamespace DataFrameHeader {\n\nstatic constexpr util::index_t FRAME_LENGTH_FIELD_OFFSET = offsetof(aeron_header_values_frame_t, frame_length);\nstatic constexpr util::index_t VERSION_FIELD_OFFSET = offsetof(aeron_header_values_frame_t, version);\nstatic constexpr util::index_t FLAGS_FIELD_OFFSET = offsetof(aeron_header_values_frame_t, flags);\nstatic constexpr util::index_t TYPE_FIELD_OFFSET = offsetof(aeron_header_values_frame_t, type);\nstatic constexpr util::index_t TERM_OFFSET_FIELD_OFFSET = offsetof(aeron_header_values_frame_t, term_offset);\nstatic constexpr util::index_t SESSION_ID_FIELD_OFFSET = offsetof(aeron_header_values_frame_t, session_id);\nstatic constexpr util::index_t STREAM_ID_FIELD_OFFSET = offsetof(aeron_header_values_frame_t, stream_id);\nstatic constexpr util::index_t TERM_ID_FIELD_OFFSET = offsetof(aeron_header_values_frame_t, term_id);\nstatic constexpr util::index_t RESERVED_VALUE_FIELD_OFFSET = offsetof(aeron_header_values_frame_t, reserved_value);\nstatic constexpr util::index_t DATA_OFFSET = sizeof(aeron_header_values_frame_t);\n\nstatic constexpr util::index_t LENGTH = DATA_OFFSET;\n\nstatic constexpr std::uint16_t HDR_TYPE_PAD = 0x00;\nstatic constexpr std::uint16_t HDR_TYPE_DATA = 0x01;\nstatic constexpr std::uint16_t HDR_TYPE_NAK = 0x02;\nstatic constexpr std::uint16_t HDR_TYPE_SM = 0x03;\nstatic constexpr std::uint16_t HDR_TYPE_ERR = 0x04;\nstatic constexpr std::uint16_t HDR_TYPE_SETUP = 0x05;\nstatic constexpr std::uint16_t HDR_TYPE_EXT = 0xFFFF;\n\nstatic constexpr std::int8_t CURRENT_VERSION = 0x0;\n\n}\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/logbuffer/FrameDescriptor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONCURRENT_LOGBUFFER_FRAME_DESCRIPTOR_H\n#define AERON_CONCURRENT_LOGBUFFER_FRAME_DESCRIPTOR_H\n\n#include \"util/Index.h\"\n#include \"util/StringUtil.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/logbuffer/DataFrameHeader.h\"\n\nnamespace aeron { namespace concurrent { namespace logbuffer\n{\n\n/**\n* Description of the structure for message framing in a log buffer.\n*\n* All messages are logged in frames that have a minimum header layout as follows plus a reserve then\n* the encoded message follows:\n*\n* <pre>\n*   0                   1                   2                   3\n*   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n*  |R|                       Frame Length                          |\n*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------------------------+\n*  |  Version      |B|E| Flags     |             Type              |\n*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------------------------+\n*  |R|                       Term Offset                           |\n*  +-+-------------------------------------------------------------+\n*  |                      Additional Fields                       ...\n* ...                                                              |\n*  +---------------------------------------------------------------+\n*  |                        Encoded Message                       ...\n* ...                                                              |\n*  +---------------------------------------------------------------+\n* </pre>\n*\n* The (B)egin and (E)nd flags are used for message fragmentation. (R) is for reserved bit.\n* Both are set for a message that does not span frames.\n*/\n\nnamespace FrameDescriptor\n{\n\nstatic constexpr util::index_t FRAME_ALIGNMENT = 32;\n\nstatic constexpr std::uint8_t BEGIN_FRAG = 0x80;\nstatic constexpr std::uint8_t END_FRAG = 0x40;\nstatic constexpr std::uint8_t UNFRAGMENTED = BEGIN_FRAG | END_FRAG;\n\nstatic constexpr util::index_t ALIGNED_HEADER_LENGTH = 32;\n\nstatic constexpr util::index_t VERSION_OFFSET = 4;\nstatic constexpr util::index_t FLAGS_OFFSET = 5;\nstatic constexpr util::index_t TYPE_OFFSET = 6;\nstatic constexpr util::index_t LENGTH_OFFSET = 0;\nstatic constexpr util::index_t TERM_OFFSET = 8;\n\nstatic constexpr util::index_t MAX_MESSAGE_LENGTH = 16 * 1024 * 1024;\n\ninline static void checkHeaderLength(util::index_t length)\n{\n    if (length != DataFrameHeader::LENGTH)\n    {\n        throw util::IllegalStateException(\n            \"frame header length \" + std::to_string(length) +\n            \" must be equal to \" + std::to_string(DataFrameHeader::LENGTH), SOURCEINFO);\n    }\n}\n\ninline static void checkMaxFrameLength(util::index_t length)\n{\n    if ((length & (FRAME_ALIGNMENT - 1)) != 0)\n    {\n        throw util::IllegalStateException(\n            \"max frame length must be a multiple of \" + std::to_string(FRAME_ALIGNMENT) +\n            \", length=\" + std::to_string(length), SOURCEINFO);\n    }\n}\n\ninline static util::index_t computeMaxMessageLength(util::index_t capacity)\n{\n    return std::min(capacity / 8, MAX_MESSAGE_LENGTH);\n}\n\ninline static util::index_t typeOffset(util::index_t frameOffset)\n{\n    return frameOffset + DataFrameHeader::TYPE_FIELD_OFFSET;\n}\n\ninline static util::index_t flagsOffset(util::index_t frameOffset)\n{\n    return frameOffset + DataFrameHeader::FLAGS_FIELD_OFFSET;\n}\n\ninline static util::index_t lengthOffset(util::index_t frameOffset)\n{\n    return frameOffset + DataFrameHeader::FRAME_LENGTH_FIELD_OFFSET;\n}\n\ninline static util::index_t termOffsetOffset(util::index_t frameOffset)\n{\n    return frameOffset + DataFrameHeader::TERM_OFFSET_FIELD_OFFSET;\n}\n\ninline static void frameType(AtomicBuffer &logBuffer, util::index_t frameOffset, std::uint16_t type)\n{\n    logBuffer.putUInt16(typeOffset(frameOffset), type);\n}\n\ninline static std::uint16_t frameType(const AtomicBuffer &logBuffer, util::index_t frameOffset)\n{\n    return logBuffer.getUInt16(frameOffset);\n}\n\ninline static void frameFlags(AtomicBuffer &logBuffer, util::index_t frameOffset, std::uint8_t flags)\n{\n    logBuffer.putUInt8(flagsOffset(frameOffset), flags);\n}\n\ninline static void frameTermOffset(AtomicBuffer &logBuffer, util::index_t frameOffset, std::int32_t termOffset)\n{\n    logBuffer.putInt32(termOffsetOffset(frameOffset), termOffset);\n}\n\ninline static bool isPaddingFrame(const AtomicBuffer &logBuffer, util::index_t frameOffset)\n{\n    return logBuffer.getUInt16(typeOffset(frameOffset)) == DataFrameHeader::HDR_TYPE_PAD;\n}\n\ninline static std::int32_t frameLengthVolatile(const AtomicBuffer &logBuffer, util::index_t frameOffset)\n{\n    return logBuffer.getInt32Volatile(lengthOffset(frameOffset));\n}\n\ninline static void frameLengthOrdered(AtomicBuffer &logBuffer, util::index_t frameOffset, std::int32_t frameLength)\n{\n    logBuffer.putInt32Ordered(lengthOffset(frameOffset), frameLength);\n}\n\ninline static std::uint8_t frameVersion(const AtomicBuffer &logBuffer, util::index_t frameOffset)\n{\n    return logBuffer.getUInt8(frameOffset);\n}\n\n}\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/logbuffer/Header.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONCURRENT_LOGBUFFER_HEADER_H\n#define AERON_CONCURRENT_LOGBUFFER_HEADER_H\n\n#include \"util/Index.h\"\n#include \"util/BitUtil.h\"\n#include \"util/Exceptions.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/logbuffer/DataFrameHeader.h\"\n\n#include \"aeronc.h\"\n\nnamespace aeron { namespace concurrent { namespace logbuffer {\n\nusing namespace aeron::util;\n\n/**\n * Represents the header of the data frame for accessing meta data fields.\n */\nclass Header\n{\npublic:\n    explicit Header(aeron_header_t *header) : m_header(header)\n    {\n        if (aeron_header_values(m_header, &m_headerValues) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    /**\n     * Get the initial term id this stream started at.\n     *\n     * @return the initial term id this stream started at.\n     */\n    inline std::int32_t initialTermId() const\n    {\n        return m_headerValues.initial_term_id;\n    }\n\n    /**\n     * The total length of the frame including the header.\n     *\n     * @return the total length of the frame including the header.\n     */\n    inline std::int32_t frameLength() const\n    {\n        return m_headerValues.frame.frame_length;\n    }\n\n    /**\n     * The session ID to which the frame belongs.\n     *\n     * @return the session ID to which the frame belongs.\n     */\n    inline std::int32_t sessionId() const\n    {\n        return m_headerValues.frame.session_id;\n    }\n\n    /**\n     * The stream ID to which the frame belongs.\n     *\n     * @return the stream ID to which the frame belongs.\n     */\n    inline std::int32_t streamId() const\n    {\n        return m_headerValues.frame.stream_id;\n    }\n\n    /**\n     * The term ID to which the frame belongs.\n     *\n     * @return the term ID to which the frame belongs.\n     */\n    inline std::int32_t termId() const\n    {\n        return m_headerValues.frame.term_id;\n    }\n\n    /**\n     * The offset in the term at which the frame begins. This will be the same as {@link #offset()}\n     *\n     * @return the offset in the term at which the frame begins.\n     */\n    inline std::int32_t termOffset() const\n    {\n        return m_headerValues.frame.term_offset;\n    }\n\n    /**\n     * The type of the the frame which should always be {@link DataFrameHeader::HDR_TYPE_DATA}\n     *\n     * @return type of the the frame which should always be {@link DataFrameHeader::HDR_TYPE_DATA}\n     */\n    inline std::uint16_t type() const\n    {\n        // C and Java API declare this as int16_t.\n        return static_cast<std::uint16_t>(m_headerValues.frame.type);\n    }\n\n    /**\n     * The flags for this frame. Valid flags are {@link DataFrameHeader::BEGIN_FLAG}\n     * and {@link DataFrameHeader::END_FLAG}. A convenience flag {@link DataFrameHeader::BEGIN_AND_END_FLAGS}\n     * can be used for both flags.\n     *\n     * @return the flags for this frame.\n     */\n    inline std::uint8_t flags() const\n    {\n        return m_headerValues.frame.flags;\n    }\n\n    /**\n     * Get the current position to which the Image has advanced on reading this message.\n     *\n     * @return the current position to which the Image has advanced on reading this message.\n     */\n    inline std::int64_t position() const\n    {\n        return aeron_header_position(m_header);\n    }\n\n    /**\n     * The number of times to left shift the term count to multiply by term length.\n     *\n     * @return number of times to left shift the term count to multiply by term length.\n     */\n    inline std::int32_t positionBitsToShift() const\n    {\n        return static_cast<std::int32_t>(m_headerValues.position_bits_to_shift);\n    }\n\n    /**\n     * Get the value stored in the reserve space at the end of a data frame header.\n     *\n     * @return the value stored in the reserve space at the end of a data frame header.\n     */\n    inline std::int64_t reservedValue() const\n    {\n        return m_headerValues.frame.reserved_value;\n    }\n\n    /**\n     * Get a pointer to the context associated with this message. Only valid during poll handling. Is normally a\n     * pointer to an Image instance.\n     *\n     * @return a pointer to the context associated with this message.\n     */\n    inline void *context() const\n    {\n        return aeron_header_context(m_header);\n    }\n\n    aeron_header_t *hdr()\n    {\n        return m_header;\n    }\n\nprivate:\n    aeron_header_t *m_header = nullptr;\n    aeron_header_values_t m_headerValues = {};\n};\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/logbuffer/LogBufferDescriptor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONCURRENT_LOGBUFFER_DESCRIPTOR_H\n#define AERON_CONCURRENT_LOGBUFFER_DESCRIPTOR_H\n\n#include \"util/Index.h\"\n#include \"util/StringUtil.h\"\n#include \"util/BitUtil.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/logbuffer/FrameDescriptor.h\"\n#include \"concurrent/logbuffer/DataFrameHeader.h\"\n\nnamespace aeron { namespace concurrent { namespace logbuffer { namespace LogBufferDescriptor\n{\n\nconst std::int32_t TERM_MIN_LENGTH = 64 * 1024;\nconst std::int32_t TERM_MAX_LENGTH = 1024 * 1024 * 1024;\n\n#if defined(__GNUC__) || _MSC_VER >= 1900\nstatic constexpr const int PARTITION_COUNT = 3;\n#else\n// Visual Studio 2013 doesn't like constexpr without an update\n// https://msdn.microsoft.com/en-us/library/vstudio/hh567368.aspx\n// https://www.microsoft.com/en-us/download/details.aspx?id=41151\nstatic const int PARTITION_COUNT = 3;\n#endif\n\n/*\n * Layout description for log buffers which contains partitions of terms with associated term meta data,\n * plus ending with overall log meta data.\n *\n * <pre>\n *  +----------------------------+\n *  |           Term 0           |\n *  +----------------------------+\n *  |           Term 1           |\n *  +----------------------------+\n *  |           Term 2           |\n *  +----------------------------+\n *  |        Log Meta Data       |\n *  +----------------------------+\n * </pre>\n */\n\nconst util::index_t LOG_META_DATA_SECTION_INDEX = PARTITION_COUNT;\n\n/**\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                       Tail Counter 0                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Tail Counter 1                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Tail Counter 2                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                      Active Term Count                        |\n *  +---------------------------------------------------------------+\n *  |                      Cache Line Padding                      ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                    End of Stream Position                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Is Connected                           |\n *  +---------------------------------------------------------------+\n *  |                    Active Transport Count                     |\n *  +---------------------------------------------------------------+\n *  |                      Cache Line Padding                      ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                 Registration / Correlation ID                 |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Initial Term Id                        |\n *  +---------------------------------------------------------------+\n *  |                  Default Frame Header Length                  |\n *  +---------------------------------------------------------------+\n *  |                          MTU Length                           |\n *  +---------------------------------------------------------------+\n *  |                         Term Length                           |\n *  +---------------------------------------------------------------+\n *  |                          Page Size                            |\n *  +---------------------------------------------------------------+\n *  |                      Cache Line Padding                      ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                    Default Frame Header                      ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\n\ninline void checkTermLength(std::int32_t termLength)\n{\n    if (termLength < TERM_MIN_LENGTH)\n    {\n        throw util::IllegalStateException(\n            \"term length less than min size of \" + std::to_string(TERM_MIN_LENGTH) +\n            \", length=\" + std::to_string(termLength), SOURCEINFO);\n    }\n\n    if (termLength > TERM_MAX_LENGTH)\n    {\n        throw util::IllegalStateException(\n            \"term length greater than max size of \" + std::to_string(TERM_MAX_LENGTH) +\n            \", length=\" + std::to_string(termLength), SOURCEINFO);\n    }\n\n    if (!util::BitUtil::isPowerOfTwo(termLength))\n    {\n        throw util::IllegalStateException(\n            \"term length not a power of 2, length=\" + std::to_string(termLength), SOURCEINFO);\n    }\n}\n\ninline std::int32_t computeTermCount(std::int32_t termId, std::int32_t initialTermId) noexcept\n{\n    const std::int64_t difference = static_cast<std::int64_t>(termId) - static_cast<std::int64_t>(initialTermId);\n    return static_cast<std::int32_t>(difference & 0xFFFFFFFF);\n}\n\ninline std::int64_t computePosition(\n    std::int32_t activeTermId,\n    std::int32_t termOffset,\n    std::int32_t positionBitsToShift,\n    std::int32_t initialTermId) noexcept\n{\n    const auto termCount = static_cast<std::int64_t>(computeTermCount(activeTermId, initialTermId));\n    return (termCount << positionBitsToShift) + termOffset;\n}\n\n/**\n * Compute frame length for a message that is fragmented into chunks of {@code maxPayloadSize}.\n *\n * @param length of the message.\n * @param maxPayloadSize fragment size without the header.\n * @return message length after fragmentation.\n */\ninline static util::index_t computeFragmentedFrameLength(\n    const util::index_t length,\n    const util::index_t maxPayloadLength)\n{\n    const int numMaxPayloads = length / maxPayloadLength;\n    const util::index_t remainingPayload = length % maxPayloadLength;\n    const util::index_t lastFrameLength = remainingPayload > 0 ?\n        util::BitUtil::align(remainingPayload + DataFrameHeader::LENGTH, FrameDescriptor::FRAME_ALIGNMENT) : 0;\n\n    return (numMaxPayloads * (maxPayloadLength + DataFrameHeader::LENGTH)) + lastFrameLength;\n}\n\n}\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/logbuffer/TermReader.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONCURRENT_LOGBUFFER_TERM_READER_H\n#define AERON_CONCURRENT_LOGBUFFER_TERM_READER_H\n\n#include <functional>\n\n#include \"util/Index.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/logbuffer/Header.h\"\n\nnamespace aeron { namespace concurrent { namespace logbuffer {\n\n/**\n * Callback for handling fragments of data being read from a log.\n *\n * Handler for reading data that is coming from a log buffer. The frame will either contain a whole message\n * or a fragment of a message to be reassembled. Messages are fragmented if greater than the frame for MTU in length.\n\n * @param buffer containing the data.\n * @param offset at which the data begins.\n * @param length of the data in bytes.\n * @param header representing the meta data for the data.\n */\ntypedef std::function<void(\n    concurrent::AtomicBuffer &buffer,\n    util::index_t offset,\n    util::index_t length,\n    Header &header)> fragment_handler_t;\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/status/Position.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_POSITION_H\n#define AERON_POSITION_H\n\n#include \"concurrent/status/ReadablePosition.h\"\n\nnamespace aeron { namespace concurrent { namespace status {\n\ntemplate <class X>\nclass Position : public ReadablePosition<X>\n{\npublic:\n    explicit Position(X &impl) : ReadablePosition<X>(impl)\n    {\n    }\n\n    inline void set(std::int64_t value)\n    {\n        ReadablePosition<X>::m_impl.set(value);\n    }\n\n    inline void setOrdered(std::int64_t value)\n    {\n        ReadablePosition<X>::m_impl.setOrdered(value);\n    }\n};\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/status/ReadablePosition.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_READONLY_POSITION_H\n#define AERON_READONLY_POSITION_H\n\n#include <cstdint>\n\nnamespace aeron { namespace concurrent { namespace status {\n\ntemplate <class X>\nclass ReadablePosition\n{\npublic:\n    explicit ReadablePosition(X &impl) : m_impl(impl)\n    {\n    }\n\n    inline void wrap(const ReadablePosition<X> &position)\n    {\n        m_impl.wrap(position.m_impl);\n    }\n\n    inline std::int32_t id() const\n    {\n        return m_impl.id();\n    }\n\n    inline std::int64_t get() const\n    {\n        return m_impl.get();\n    }\n\n    inline std::int64_t getVolatile() const\n    {\n        return m_impl.getVolatile();\n    }\n\n    inline void close()\n    {\n        m_impl.close();\n    }\n\nprotected:\n    X m_impl;\n};\n\n}}}\n\n#endif //AERON_READONLY_POSITION_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/status/StatusIndicatorReader.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_STATUS_INDICATOR_READER_H\n#define AERON_STATUS_INDICATOR_READER_H\n\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/CountersReader.h\"\n\nnamespace aeron { namespace concurrent { namespace status {\n\nnamespace ChannelEndpointStatus\n{\nstatic constexpr std::int64_t CHANNEL_ENDPOINT_INITIALIZING = 0;\nstatic constexpr std::int64_t CHANNEL_ENDPOINT_ERRORED = -1;\nstatic constexpr std::int64_t CHANNEL_ENDPOINT_ACTIVE = 1;\nstatic constexpr std::int64_t CHANNEL_ENDPOINT_CLOSING = 2;\n\nstatic constexpr std::int32_t NO_ID_ALLOCATED = -1;\n}\n\nclass StatusIndicatorReader\n{\npublic:\n    StatusIndicatorReader(AtomicBuffer &buffer, std::int32_t id) :\n        m_staticBuffer(),\n        m_id(id),\n        m_offset(CountersReader::counterOffset(id))\n    {\n        if (ChannelEndpointStatus::NO_ID_ALLOCATED == m_id)\n        {\n            m_buffer.wrap(m_staticBuffer);\n            m_offset = 0;\n            m_buffer.putInt64Ordered(0, ChannelEndpointStatus::CHANNEL_ENDPOINT_ACTIVE);\n        }\n        else\n        {\n            m_buffer.wrap(buffer);\n        }\n    }\n\n    StatusIndicatorReader(const StatusIndicatorReader &indicatorReader) :\n        m_staticBuffer(indicatorReader.m_staticBuffer),\n        m_id(indicatorReader.m_id),\n        m_offset(indicatorReader.m_offset)\n    {\n        if (ChannelEndpointStatus::NO_ID_ALLOCATED == m_id)\n        {\n            m_buffer.wrap(m_staticBuffer);\n        }\n        else\n        {\n            m_buffer.wrap(indicatorReader.m_buffer);\n        }\n\n        m_buffer.putInt64Ordered(m_offset, indicatorReader.m_buffer.getInt64Volatile(indicatorReader.m_offset));\n    }\n\n    StatusIndicatorReader &operator=(const StatusIndicatorReader &indicatorReader)\n    {\n        m_staticBuffer = indicatorReader.m_staticBuffer;\n        m_id = indicatorReader.m_id;\n        m_offset = indicatorReader.m_offset;\n\n        if (ChannelEndpointStatus::NO_ID_ALLOCATED == m_id)\n        {\n            m_buffer.wrap(m_staticBuffer);\n        }\n        else\n        {\n            m_buffer.wrap(indicatorReader.m_buffer);\n        }\n\n        m_buffer.putInt64Ordered(m_offset, indicatorReader.m_buffer.getInt64Volatile(indicatorReader.m_offset));\n\n        return *this;\n    }\n\n    inline std::int32_t id() const\n    {\n        return m_id;\n    }\n\n    inline std::int64_t getVolatile() const\n    {\n        return m_buffer.getInt64Volatile(m_offset);\n    }\n\nprivate:\n    std::array<std::uint8_t, sizeof(std::int64_t)> m_staticBuffer;\n    AtomicBuffer m_buffer;\n    std::int32_t m_id;\n    std::int32_t m_offset;\n};\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/concurrent/status/UnsafeBufferPosition.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_UNSAFE_BUFFER_POSITION_H\n#define AERON_UNSAFE_BUFFER_POSITION_H\n\n#include \"concurrent/AtomicBuffer.h\"\n#include \"concurrent/CountersReader.h\"\n#include \"concurrent/status/Position.h\"\n\nnamespace aeron { namespace concurrent { namespace status {\n\nclass UnsafeBufferPosition\n{\npublic:\n    UnsafeBufferPosition(AtomicBuffer &buffer, std::int32_t id) :\n        m_buffer(buffer),\n        m_id(id),\n        m_offset(CountersReader::counterOffset(id))\n    {\n    }\n\n    UnsafeBufferPosition() :\n        m_id(-1),\n        m_offset(0)\n    {\n    }\n\n    inline void wrap(const UnsafeBufferPosition &position)\n    {\n        m_buffer = position.m_buffer;\n        m_id = position.m_id;\n        m_offset = position.m_offset;\n    }\n\n    inline std::int32_t id() const\n    {\n        return m_id;\n    }\n\n    inline std::int64_t get() const\n    {\n        return m_buffer.getInt64(m_offset);\n    }\n\n    inline std::int64_t getVolatile() const\n    {\n        return m_buffer.getInt64Volatile(m_offset);\n    }\n\n    inline void set(std::int64_t value)\n    {\n        m_buffer.putInt64(m_offset, value);\n    }\n\n    inline void setOrdered(std::int64_t value)\n    {\n        m_buffer.putInt64Ordered(m_offset, value);\n    }\n\n    inline void close()\n    {\n    }\n\nprivate:\n    AtomicBuffer m_buffer;\n    std::int32_t m_id;\n    std::int32_t m_offset;\n};\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/status/PublicationErrorFrame.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_PUBLICATIONERRORFRAME_H\n#define AERON_PUBLICATIONERRORFRAME_H\n\n#include \"aeronc.h\"\n\nnamespace aeron { namespace status {\n\nclass PublicationErrorFrame\n{\npublic:\n    /**\n     * Constructs from a supplied C pointer to the aeron_publication_error_values_t and wraps over the top of it.\n     * By default it won't manage the underlying memory of the C structure.\n     *\n     * @param errorValues C structure holding the actual data.\n     * @param ownsErrorValuesPtr to indicate if the destructor of this class should free the underlying C memory.\n     */\n    PublicationErrorFrame(aeron_publication_error_values_t *errorValues, bool ownsErrorValuesPtr = false) :\n        m_errorValues(errorValues), m_ownsErrorValuesPtr(ownsErrorValuesPtr)\n    {\n    }\n\n    PublicationErrorFrame(PublicationErrorFrame &other)\n    {\n        if (aeron_publication_error_values_copy(&this->m_errorValues, other.m_errorValues) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n    }\n\n    PublicationErrorFrame& operator=(PublicationErrorFrame& other)\n    {\n        if (aeron_publication_error_values_copy(&this->m_errorValues, other.m_errorValues) < 0)\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        }\n\n        m_ownsErrorValuesPtr = true;\n        return *this;\n    }\n\n    PublicationErrorFrame(PublicationErrorFrame &&other)\n        : m_errorValues(other.m_errorValues), m_ownsErrorValuesPtr(other.m_ownsErrorValuesPtr)\n    {\n        other.m_errorValues = nullptr;\n        other.m_ownsErrorValuesPtr = false;\n    }\n\n    ~PublicationErrorFrame()\n    {\n        if (m_ownsErrorValuesPtr)\n        {\n            aeron_publication_error_values_delete(this->m_errorValues);\n        }\n    }\n\n    std::int64_t registrationId()\n    {\n        return m_errorValues->registration_id;\n    }\n\n    std::int32_t sessionId()\n    {\n        return m_errorValues->session_id;\n    }\n\n    std::int32_t streamId()\n    {\n        return m_errorValues->stream_id;\n    }\n\n    std::int64_t groupTag()\n    {\n        return m_errorValues->group_tag;\n    }\n\n    std::uint16_t sourcePort()\n    {\n        return m_errorValues->source_port;\n    }\n\n    std::uint8_t* sourceAddress()\n    {\n        return m_errorValues->source_address;\n    }\n\n    std::int16_t sourceAddressType()\n    {\n        return m_errorValues->address_type;\n    }\n\n    bool isValid()\n    {\n        return nullptr != m_errorValues;\n    }\n\nprivate:\n    aeron_publication_error_values_t *m_errorValues;\n    bool m_ownsErrorValuesPtr;\n};\n\n}}\n\n#endif //AERON_PUBLICATIONERRORFRAME_H\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/BitUtil.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_UTIL_BIT_UTIL_H\n#define AERON_UTIL_BIT_UTIL_H\n\n#include <cassert>\n#include <cstdint>\n#include <type_traits>\n\n#include \"util/Exceptions.h\"\n\n#if defined(_MSC_VER)\n#include <intrin.h>\n#pragma intrinsic(_BitScanForward)\n#pragma intrinsic(_BitScanReverse)\n#endif\n\nnamespace aeron { namespace util\n{\n\n/**\n * Bit manipulation functions and constants\n */\nnamespace BitUtil\n{\n/** Length of the data blocks used by the CPU cache sub-system in bytes. */\nstatic constexpr std::size_t CACHE_LINE_LENGTH = 64;\n\ntemplate<typename value_t>\ninline bool isPowerOfTwo(value_t value) noexcept\n{\n    static_assert(std::is_integral<value_t>::value, \"isPowerOfTwo only available on integer types\");\n    return value > 0 && ((value & (~value + 1)) == value);\n}\n\ntemplate<typename value_t>\ninline value_t align(value_t value, value_t alignment) noexcept\n{\n    static_assert(std::is_integral<value_t>::value, \"align only available on integer types\");\n    return (value + (alignment - 1)) & ~(alignment - 1);\n}\n\ntemplate<typename value_t>\ninline bool isEven(value_t value) noexcept\n{\n    static_assert(std::is_integral<value_t>::value, \"isEven only available on integer types\");\n    return (value & 1) == 0;\n}\n\ntemplate<typename value_t>\ninline value_t next(value_t current, value_t max) noexcept\n{\n    static_assert(std::is_integral<value_t>::value, \"next only available on integer types\");\n    value_t next = current + 1;\n    if (next == max)\n    {\n        next = 0;\n    }\n\n    return next;\n}\n\ntemplate<typename value_t>\ninline value_t previous(value_t current, value_t max) noexcept\n{\n    static_assert(std::is_integral<value_t>::value, \"previous only available on integer types\");\n    if (0 == current)\n    {\n        return max - 1;\n    }\n\n    return current - 1;\n}\n\n/* Counts the leading number of zeros in a value.\n * (Note: this only works on 32-bit types, when compiling with GCC\n * or on newer x64 processors when compiling with Visual Studio.)\n */\ntemplate<typename value_t>\ninline int numberOfLeadingZeroes(value_t value) noexcept\n{\n#if defined(__GNUC__)\n    assert(value != 0);\n    return __builtin_clz(value);\n#elif defined(_MSC_VER)\n    unsigned long r;\n\n    if (_BitScanReverse(&r, static_cast<unsigned long>(value)))\n    {\n        return 31 - static_cast<int>(r);\n    }\n\n    return 32;\n#else\n#error \"do not understand how to clz\"\n#endif\n}\n\n/* Taken from Hacker's Delight as ntz10 at http://www.hackersdelight.org/hdcodetxt/ntz.c.txt */\ntemplate<typename value_t>\ninline int numberOfTrailingZeroes(value_t value) noexcept\n{\n#if defined(__GNUC__)\n    if (0 == value)\n    {\n        return sizeof(value) * 4;\n    }\n\n    return __builtin_ctz(value);\n#elif defined(_MSC_VER)\n    unsigned long r;\n\n    if (_BitScanForward(&r, static_cast<unsigned long>(value)))\n    {\n        return static_cast<int>(r);\n    }\n\n    return 32;\n#else\n    static_assert(std::is_integral<value_t>::value, \"numberOfTrailingZeroes only available on integral types\");\n    static_assert(sizeof(value_t) <= 4, \"numberOfTrailingZeroes only available on up to 32-bit integral types\");\n\n    static char table[32] =\n    {\n        0, 1, 2, 24, 3, 19, 6, 25,\n        22, 4, 20, 10, 16, 7, 12, 26,\n        31, 23, 18, 5, 21, 9, 15, 11,\n        30, 17, 8, 14, 29, 13, 28, 27\n    };\n\n    if (value == 0)\n    {\n        return 32;\n    }\n\n    std::uint32_t index = static_cast<std::uint32_t>((value & -value) * 0x04D7651F);\n\n    return table[index >> 27u];\n#endif\n}\n\n/*\n * Finds the next power of 2 and returns it.\n * Invalid arguments (negative, 0, or too large) always return 0 or min value of type.\n */\ntemplate<typename value_t>\ninline value_t findNextPowerOfTwo(value_t value) noexcept\n{\n    static_assert(std::is_integral<value_t>::value, \"findNextPowerOfTwo only available on integral types\");\n\n    value--;\n\n    // Set all bits below the leading one using binary expansion\n    // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2\n    for (std::size_t i = 1; i < sizeof(value) * 8; i = i * 2)\n    {\n        value |= (value >> i);\n    }\n\n    return value + 1;\n}\n\n/*\n * Hacker's Delight Section 10-3 and http://www.hackersdelight.org/divcMore.pdf\n * Solution is Figure 10-24.\n */\ntemplate<typename value_t>\ninline int fastMod3(value_t value) noexcept\n{\n    static_assert(std::is_integral<value_t>::value, \"fastMod3 only available on integral types\");\n\n    static char table[62] =\n        {\n            0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2,\n            0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2,\n            0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2,\n            0, 1, 2, 0, 1, 2, 0, 1\n        };\n\n    value = (value >> 16) + (value & 0xFFFF); // Max 0x1FFFE.\n    value = (value >> 8) + (value & 0x00FF); // Max 0x2FD.\n    value = (value >> 4) + (value & 0x000F); // Max 0x3D.\n\n    return table[value];\n}\n}\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/CommandOption.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_UTIL_COMMAND_OPTION_H\n#define AERON_UTIL_COMMAND_OPTION_H\n\n#include <iostream>\n#include <exception>\n#include <string>\n#include <vector>\n#include <map>\n\n#include \"util/Exceptions.h\"\n#include \"util/StringUtil.h\"\n\nnamespace aeron { namespace util\n{\n\nAERON_DECLARE_SOURCED_EXCEPTION (CommandOptionException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\n\nclass CommandOption\n{\n\nprivate:\n    char m_optionChar = '-';\n    std::size_t m_minParams = 0;\n    std::size_t m_maxParams = 0;\n    std::string m_helpText;\n\n    bool m_isPresent = false;\n\n    std::vector<std::string> m_params;\n\n    void checkIndex(std::size_t index) const\n    {\n        if (index > m_params.size())\n        {\n            throw CommandOptionException(\n                std::string(\"Internal Error: index out of range for option: \") + m_optionChar, SOURCEINFO);\n        }\n    }\n\npublic:\n    static constexpr char UNNAMED = -1;\n\n    CommandOption() = default;\n\n    CommandOption(char optionChar, std::size_t minParams, std::size_t maxParams, std::string helpText) :\n        m_optionChar(optionChar),\n        m_minParams(minParams),\n        m_maxParams(maxParams),\n        m_helpText(std::move(helpText))\n    {\n    }\n\n    char getOptionChar() const\n    {\n        return m_optionChar;\n    }\n\n    std::string getHelpText() const\n    {\n        return m_helpText;\n    }\n\n    void addParam(std::string p)\n    {\n        m_params.push_back(std::move(p));\n    }\n\n    void validate() const\n    {\n        if (!isPresent())\n        {\n            return;\n        }\n\n        if (m_params.size() > m_maxParams)\n        {\n            throw CommandOptionException(\n                std::string(\"option -\") + m_optionChar + \" has too many parameters specified.\", SOURCEINFO);\n        }\n\n        if (m_params.size() < m_minParams)\n        {\n            throw CommandOptionException(\n                std::string(\"option -\") + m_optionChar + \" has too few parameters specified.\", SOURCEINFO);\n        }\n    }\n\n    bool isPresent() const\n    {\n        return m_isPresent;\n    }\n\n    void setPresent()\n    {\n        m_isPresent = true;\n    }\n\n    std::size_t getNumParams() const\n    {\n        return m_params.size();\n    }\n\n    std::string getParam(std::size_t index) const\n    {\n        checkIndex(index);\n        return m_params[index];\n    }\n\n    std::string getParam(std::size_t index, std::string defaultValue) const\n    {\n        if (!isPresent())\n        {\n            return defaultValue;\n        }\n\n        return getParam(index);\n    }\n\n\n    int getParamAsInt(std::size_t index) const\n    {\n        checkIndex(index);\n        std::string param = m_params[index];\n\n        try\n        {\n            return parse<int>(param);\n        }\n        catch (const ParseException &)\n        {\n            throw CommandOptionException(\n                std::string(\"invalid numeric value: \\\"\") + param + \"\\\" on option -\" + m_optionChar, SOURCEINFO);\n        }\n    }\n\n    long long getParamAsLong(std::size_t index) const\n    {\n        checkIndex(index);\n        std::string param = m_params[index];\n\n        try\n        {\n            return parse<long long>(param);\n        }\n        catch (const ParseException &)\n        {\n            throw CommandOptionException(\n                std::string(\"invalid numeric value: \\\"\") + param + \"\\\" on option -\" + m_optionChar, SOURCEINFO);\n        }\n    }\n\n    int getParamAsInt(std::size_t index, int minValue, int maxValue, int defaultValue) const\n    {\n        if (!isPresent())\n        {\n            return defaultValue;\n        }\n\n        int value = getParamAsInt(index);\n        if (value < minValue || value > maxValue)\n        {\n            throw CommandOptionException(\n                std::string(\"value \\\"\") + toString(value) + \"\\\" out of range: [\" +\n                toString(minValue) + \"..\" + toString(maxValue) + \"] on option -\" + m_optionChar,\n                SOURCEINFO);\n        }\n\n        return value;\n    }\n\n    long long getParamAsLong(std::size_t index, long long minValue, long long maxValue, long long defaultValue) const\n    {\n        if (!isPresent())\n        {\n            return defaultValue;\n        }\n\n        long long value = getParamAsLong(index);\n        if (value < minValue || value > maxValue)\n        {\n            throw CommandOptionException(\n                std::string(\"value \\\"\") + toString(value) + \"\\\" out of range: [\" +\n                toString(minValue) + \"..\" + toString(maxValue) + \"] on option -\" + m_optionChar,\n                SOURCEINFO);\n        }\n\n        return value;\n    }\n};\n\n}}\n\n#endif"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/CommandOptionParser.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_UTIL_COMMAND_OPTION_PARSER_H\n#define AERON_UTIL_COMMAND_OPTION_PARSER_H\n\n#include <string>\n#include <vector>\n#include <map>\n#include <iostream>\n\n#include \"util/CommandOption.h\"\n\nnamespace aeron { namespace util\n{\n\nclass CommandOptionParser\n{\nprivate:\n    std::map<char, CommandOption> m_options;\n\npublic:\n    CommandOptionParser()\n    {\n        addOption(CommandOption(CommandOption::UNNAMED, 0, 0, \"Unnamed Options\"));\n    }\n\n    void parse(int argc, char **argv)\n    {\n        char currentOption = CommandOption::UNNAMED;\n        getOption(currentOption).setPresent();\n\n        for (int n = 1; n < argc; n++)\n        {\n            std::string argStr(argv[n]);\n\n            if ((argStr.size() >= 2) && (argStr[0] == '-'))\n            {\n                for (std::size_t argNum = 1; argNum < argStr.size(); argNum++)\n                {\n                    currentOption = argStr[argNum];\n\n                    auto opt = m_options.find(currentOption);\n                    if (m_options.end() == opt)\n                    {\n                        throw CommandOptionException(\n                            std::string(\"-\") + currentOption + \" is not a valid command option\", SOURCEINFO);\n                    }\n                    else\n                    {\n                        opt->second.setPresent();\n                    }\n                }\n            }\n            else\n            {\n                CommandOption &opt = getOption(currentOption);\n                opt.addParam(argStr);\n            }\n        }\n\n        for (auto &option : m_options)\n        {\n            option.second.validate();\n        }\n    }\n\n    void addOption(const CommandOption &option)\n    {\n        m_options[option.getOptionChar()] = option;\n    }\n\n    CommandOption &getOption(char optionChar)\n    {\n        auto opt = m_options.find(optionChar);\n\n        if (m_options.end() == opt)\n        {\n            throw CommandOptionException(\n                std::string(\"CommandOptionParser::getOption invalid option lookup: \") + optionChar, SOURCEINFO);\n        }\n\n        return opt->second;\n    }\n\n    void displayOptionsHelp(std::ostream &out) const\n    {\n        for (const auto &opt : m_options)\n        {\n            if (opt.first != CommandOption::UNNAMED)\n            {\n                out << \"    -\" << opt.first << \" \" << opt.second.getHelpText() << std::endl;\n            }\n        }\n    }\n};\n\n}}\n\n#endif"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/Exceptions.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_UTIL_EXCEPTIONS_FILE_H\n#define AERON_UTIL_EXCEPTIONS_FILE_H\n\n#include <cstdint>\n#include <string>\n#include <stdexcept>\n#include <functional>\n\n#include \"util/MacroUtil.h\"\n\n#include \"aeronc.h\"\n\nnamespace aeron { namespace util\n{\n\n#ifdef _MSC_VER\n#define AERON_FILE_SEP '\\\\'\n#else\n#define AERON_FILE_SEP '/'\n#endif\n\nstatic constexpr const char *past_prefix(const char * const prefix, const char * const filename)\n{\n    return *prefix == *filename ?\n        past_prefix(prefix + 1, filename + 1) : *filename == AERON_FILE_SEP ? filename + 1 : filename;\n}\n\n#ifdef __PROJECT_SOURCE_DIR__\n    #define __SHORT_FILE__ aeron::util::past_prefix(__PROJECT_SOURCE_DIR__,__FILE__)\n#else\n    #define __SHORT_FILE__ __FILE__\n#endif\n\n#ifdef _MSC_VER\n    #define SOURCEINFO __FUNCTION__,  __SHORT_FILE__, __LINE__\n#else\n    #define SOURCEINFO  __PRETTY_FUNCTION__,  __SHORT_FILE__, __LINE__\n#endif\n\n/**\n * Callback to indicate an exception has occurred.\n *\n * This handler may be called in a context of noexcept so the handler can not safely throw.\n *\n * @param exception that has occurred.\n */\ntypedef std::function<void(const std::exception &exception)> exception_handler_t;\n\nenum class ExceptionCategory : std::int64_t\n{\n    EXCEPTION_CATEGORY_FATAL = 0,\n    EXCEPTION_CATEGORY_ERROR = 1,\n    EXCEPTION_CATEGORY_WARN = 2\n};\n\nclass SourcedException : public std::exception\n{\nprivate:\n    const std::string m_where;\n    const std::string m_what;\n    const ExceptionCategory m_category;\n\npublic:\n    SourcedException(\n        ExceptionCategory category,\n        const std::string &what,\n        const std::string &function,\n        const std::string &file,\n        const int line) :\n        m_where(function + \" : \" + file + \" : \" + std::to_string(line)),\n        m_what(what),\n        m_category(category)\n    {\n    }\n\n    SourcedException(const std::string &what, const std::string &function, const std::string &file, const int line) :\n        SourcedException(ExceptionCategory::EXCEPTION_CATEGORY_ERROR, what, function, file, line)\n    {\n    }\n\n    const char *what() const noexcept override\n    {\n        return m_what.c_str();\n    }\n\n    const char *where() const noexcept\n    {\n        return m_where.c_str();\n    }\n\n    ExceptionCategory category() const noexcept\n    {\n        return m_category;\n    }\n};\n\n#define AERON_DECLARE_SOURCED_EXCEPTION(exceptionName, category) \\\nclass exceptionName : public aeron::util::SourcedException       \\\n{                                                                \\\npublic:                                                          \\\n    exceptionName(                                               \\\n        const std::string &what,                                 \\\n        const std::string &function,                             \\\n        const std::string &file,                                 \\\n        const int line) :                                        \\\n        SourcedException(category, what, function, file, line)   \\\n    {                                                            \\\n    }                                                            \\\n}                                                                \\\n\nAERON_DECLARE_SOURCED_EXCEPTION(IOException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(FormatException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(OutOfBoundsException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(ParseException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(ElementNotFound, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(IllegalArgumentException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(IllegalStateException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(DriverTimeoutException, ExceptionCategory::EXCEPTION_CATEGORY_FATAL);\nAERON_DECLARE_SOURCED_EXCEPTION(ConductorServiceTimeoutException, ExceptionCategory::EXCEPTION_CATEGORY_FATAL);\nAERON_DECLARE_SOURCED_EXCEPTION(ClientTimeoutException, ExceptionCategory::EXCEPTION_CATEGORY_FATAL);\nAERON_DECLARE_SOURCED_EXCEPTION(AeronException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(UnknownSubscriptionException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(ReentrantException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\nAERON_DECLARE_SOURCED_EXCEPTION(UnsupportedOperationException, ExceptionCategory::EXCEPTION_CATEGORY_ERROR);\n\n#define AERON_MAP_TO_SOURCED_EXCEPTION_AND_THROW_WITH_DEFAULT(code, message, defaultException)  \\\ndo                                                                                  \\\n{                                                                                   \\\n    switch (code)                                                                   \\\n    {                                                                               \\\n        case EINVAL:                                                                \\\n            throw IllegalArgumentException(message, SOURCEINFO);                    \\\n                                                                                    \\\n        case EPERM:                                                                 \\\n        case AERON_CLIENT_ERROR_BUFFER_FULL:                                        \\\n            throw IllegalStateException(message, SOURCEINFO);                       \\\n                                                                                    \\\n        case EIO:                                                                   \\\n        case ENOENT:                                                                \\\n            throw IOException(message, SOURCEINFO);                                 \\\n                                                                                    \\\n        case AERON_CLIENT_ERROR_DRIVER_TIMEOUT:                                     \\\n            throw DriverTimeoutException(message, SOURCEINFO);                      \\\n                                                                                    \\\n        case AERON_CLIENT_ERROR_CLIENT_TIMEOUT:                                     \\\n            throw ClientTimeoutException(message, SOURCEINFO);                      \\\n                                                                                    \\\n        case AERON_CLIENT_ERROR_CONDUCTOR_SERVICE_TIMEOUT:                          \\\n            throw ConductorServiceTimeoutException(message, SOURCEINFO);            \\\n                                                                                    \\\n        case ETIMEDOUT:                                                             \\\n        default:                                                                    \\\n            throw defaultException(message, SOURCEINFO);                            \\\n    }                                                                               \\\n}                                                                                   \\\nwhile (0)\n\n#define AERON_MAP_TO_SOURCED_EXCEPTION_AND_THROW(code, message) AERON_MAP_TO_SOURCED_EXCEPTION_AND_THROW_WITH_DEFAULT(code, message, AeronException)\n\n#define AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW AERON_MAP_TO_SOURCED_EXCEPTION_AND_THROW(aeron_errcode(), aeron_errmsg())\n\nclass RegistrationException : public SourcedException\n{\nprivate:\n    std::int32_t m_errorCode;\n\npublic:\n    RegistrationException(\n        std::int32_t errorCode,\n        ExceptionCategory exceptionCategory,\n        const std::string &what,\n        const std::string &function,\n        const std::string &file,\n        const int line) :\n        SourcedException(exceptionCategory, what, function, file, line),\n        m_errorCode(errorCode)\n    {\n    }\n\n    std::int32_t errorCode() const\n    {\n        return m_errorCode;\n    }\n};\n\nclass TimeoutException : public AeronException\n{\npublic:\n    TimeoutException(\n        const std::string &what,\n        const std::string &function,\n        const std::string &file,\n        const int line) :\n        AeronException(what, function, file, line)\n    {\n    }\n};\n\nclass ChannelEndpointException : public AeronException\n{\nprivate:\n    std::int32_t m_statusIndicatorCounterId;\n\npublic:\n    ChannelEndpointException(\n        std::int32_t statusIndicatorCounterId,\n        const std::string &what,\n        const std::string &function,\n        const std::string &file,\n        const int line) :\n        AeronException(what, function, file, line),\n        m_statusIndicatorCounterId(statusIndicatorCounterId)\n    {\n    }\n\n    std::int32_t statusIndicatorId() const\n    {\n        return m_statusIndicatorCounterId;\n    }\n};\n\n}}\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/Index.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_UTIL_INDEX_FILE_H\n#define AERON_UTIL_INDEX_FILE_H\n\n#include <cstddef>\n#include <cstdint>\n#include <limits>\n\nnamespace aeron { namespace util\n{\n\n/**\n * a 32-bit signed int that is used for lengths and offsets to be compatible with Java's 32-bit int.\n */\ntypedef std::int32_t index_t;\n\ninline static index_t convertSizeToIndex(std::size_t size)\n{\n    if (size > static_cast<std::size_t>(std::numeric_limits<index_t>::max()))\n    {\n        return std::numeric_limits<index_t>::max();\n    }\n\n    return static_cast<index_t>(size);\n}\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/LangUtil.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_LANG_UTIL_H\n#define AERON_LANG_UTIL_H\n\n#include <functional>\n#include <algorithm>\n\n/**\n * @file\n * Utilities related to C++ and C++ standard libraries\n */\nnamespace aeron { namespace util\n{\n\n/**\n * Bjarne Stroustrup - Make Simple Tasks Simple - https://www.youtube.com/watch?v=nesCaocNjtQ\n */\ntemplate<typename T>\nusing Iterator = typename T::iterator;\n\ntemplate<typename Container, typename Predicate>\nIterator<Container> find_if(Container &c, Predicate p)\n{\n    return std::find_if(std::begin(c), std::end(c), p);\n}\n\nclass InvokeOnScopeExit\n{\npublic:\n    using func_t = std::function<void()>;\n\n    inline explicit InvokeOnScopeExit(const func_t &func) :\n        m_func(func)\n    {\n    }\n\n    inline ~InvokeOnScopeExit()\n    {\n        m_func();\n    }\n\nprivate:\n    func_t m_func;\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/MacroUtil.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_UTIL_MACRO_UTIL_FILE_H\n#define AERON_UTIL_MACRO_UTIL_FILE_H\n\n#define STRINGIFY(x) #x\n#define TOSTRING(x) STRINGIFY(x)\n\n#define CONCAT_SYMBOLS(x, y) x##y\n\n#include <cstdint>\n#include <string>\n\nnamespace aeron { namespace util {\n\ninline static constexpr std::int32_t semanticVersionCompose(\n    std::uint8_t major, std::uint8_t minor, std::uint8_t patch) noexcept\n{\n    return (major << 16) | (minor << 8) | patch;\n}\n\ninline static constexpr std::uint8_t semanticVersionMajor(std::int32_t version) noexcept\n{\n    return static_cast<std::uint8_t>((version >> 16) & 0xFF);\n}\n\ninline static constexpr std::uint8_t semanticVersionMinor(std::int32_t version) noexcept\n{\n    return static_cast<std::uint8_t>((version >> 8) & 0xFF);\n}\n\ninline static constexpr std::uint8_t semanticVersionPatch(std::int32_t version) noexcept\n{\n    return static_cast<std::uint8_t>(version & 0xFF);\n}\n\ninline static std::string semanticVersionToString(std::int32_t version) noexcept\n{\n    return\n        std::to_string(semanticVersionMajor(version)) + \".\" +\n        std::to_string(semanticVersionMinor(version)) + \".\" +\n        std::to_string(semanticVersionPatch(version));\n}\n\n}}\n\n#if COND_MOCK == 1\n    #define COND_MOCK_VIRTUAL virtual\n#else\n    #define COND_MOCK_VIRTUAL\n#endif\n\n#if defined(__GNUC__)\n    #define AERON_COND_EXPECT(exp, c) (__builtin_expect((exp), c))\n#else\n    #define AERON_COND_EXPECT(exp, c) (exp)\n#endif\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/MemoryMappedFile.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_UTIL_MEMORY_MAPPED_FILE_H\n#define AERON_UTIL_MEMORY_MAPPED_FILE_H\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include \"Exceptions.h\"\nextern \"C\"\n{\n#include \"util/aeron_fileutil.h\"\n}\n\n#ifdef _WIN32\n    #include <cstddef>\n    typedef void * HANDLE;\n#else\n    #include <sys/types.h>\n#endif\n\nnamespace aeron { namespace util\n{\n\nclass  MemoryMappedFile\n{\npublic:\n    typedef std::shared_ptr<MemoryMappedFile> ptr_t;\n\n    static ptr_t createNew(const char *filename, std::size_t offset, std::size_t length, bool preTouch)\n    {\n        aeron_mapped_file_t mapped_file = {};\n        mapped_file.length = offset + length;\n        if (aeron_map_new_file(&mapped_file, filename, false) < 0)\n        {\n            throw IOException(\n                std::string(\"failed to map new file: \") + filename + \" \" + aeron_errmsg(), SOURCEINFO);\n        }\n\n        mapped_file.addr = static_cast<void *>(static_cast<std::uint8_t *>(mapped_file.addr) + offset);\n        return ptr_t(new MemoryMappedFile(mapped_file, false, preTouch));\n    }\n\n    static ptr_t mapExisting(\n        const char *filename, std::size_t offset, std::size_t length, bool readOnly = false, bool preTouch = false)\n    {\n        aeron_mapped_file_t mapped_file = {};\n        mapped_file.length = offset + length;\n\n        if (aeron_map_existing_file(&mapped_file, filename) < 0)\n        {\n            throw IOException(\n                std::string(\"failed to open existing file: \") + filename + \" \" + aeron_errmsg(), SOURCEINFO);\n        }\n\n        mapped_file.addr = static_cast<void *>(static_cast<std::uint8_t *>(mapped_file.addr) + offset);\n        return ptr_t(new MemoryMappedFile(mapped_file, readOnly, preTouch));\n    }\n\n    static ptr_t mapExisting(const char *filename, bool readOnly = false, bool preTouch = false);\n\n    inline static ptr_t mapExistingReadOnly(const char *filename)\n    {\n        return mapExisting(filename, 0, 0, true, false);\n    }\n\n    ~MemoryMappedFile()\n    {\n        aeron_unmap(&m_mappedFile);\n    }\n\n    std::uint8_t *getMemoryPtr() const\n    {\n        return reinterpret_cast<uint8_t *>(m_mappedFile.addr);\n    }\n    std::size_t getMemorySize() const\n    {\n        return m_mappedFile.length;\n    }\n\n    MemoryMappedFile(MemoryMappedFile const &) = delete;\n    MemoryMappedFile& operator=(MemoryMappedFile const &) = delete;\n\n    static std::size_t getPageSize() noexcept\n    {\n        return 0;\n    }\n    static std::int64_t getFileSize(const char *filename)\n    {\n        return aeron_file_length(filename);\n    }\n\nprivate:\n    struct FileHandle\n    {\n#ifdef _WIN32\n        HANDLE handle;\n#else\n        int handle = -1;\n#endif\n    };\n\n    MemoryMappedFile(aeron_mapped_file_t mappedFile, bool readOnly, bool preTouch) : m_mappedFile(mappedFile)\n    {}\n\n    aeron_mapped_file_t m_mappedFile;\n\n    static std::size_t m_page_size;\n    static bool fill(FileHandle fd, std::size_t sz, std::uint8_t value);\n\n#ifdef _WIN32\n    HANDLE m_file = nullptr;\n    HANDLE m_mapping = nullptr;\n    void cleanUp();\n#endif\n\n};\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/Platform.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_PLATFORM_H\n#define AERON_PLATFORM_H\n\n/*\n * Determine platform, compiler, and CPU and set defines to be used later.\n * Also, error out here if on a platform that is not supported.\n */\n\n#if defined(_MSC_VER)\n    #define AERON_COMPILER_MSVC 1\n\n    #if defined(_M_X64)\n        #define AERON_CPU_X64 1\n    #else\n        #error Unsupported CPU!\n    #endif\n\n#elif defined(__GNUC__)\n    #define AERON_COMPILER_GCC 1\n\n    #if defined(__llvm__)\n        #define AERON_COMPILER_LLVM 1\n    #endif\n\n    #if defined(__x86_64__)\n        #define AERON_CPU_X64 1\n    #endif\n\n    #if defined(__aarch64__)\n        #define AERON_CPU_ARM 1\n        #if defined(__STDC_NO_ATOMICS__)\n            #error C11 atomics are required to compile for aarch64!\n        #endif\n    #endif\n\n#else\n    #error Unsupported compiler!\n#endif\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/ScopeUtils.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_UTIL_SCOPE_FILE_H\n#define AERON_UTIL_SCOPE_FILE_H\n\n#include <memory>\n\nnamespace aeron { namespace util\n{\n\nclass OnScopeExit\n{\npublic:\n    template<typename func_t>\n    inline explicit OnScopeExit(const func_t &func) : m_holder(new FuncHolder<func_t>(func))\n    {\n    }\n\nprivate:\n    struct FuncHolderBase\n    {\n        virtual ~FuncHolderBase() = default;\n    };\n\n    template<typename func_t>\n    struct FuncHolder : public FuncHolderBase\n    {\n        func_t f;\n\n        inline explicit FuncHolder(const func_t &func) : f(func)\n        {\n        }\n\n        ~FuncHolder() override\n        {\n            f();\n        }\n    };\n\n    std::unique_ptr<FuncHolderBase> m_holder;\n};\n\nclass CallbackGuard\n{\npublic:\n    explicit CallbackGuard(bool &isInCallback) : m_isInCallback(isInCallback)\n    {\n        m_isInCallback = true;\n    }\n\n    ~CallbackGuard()\n    {\n        m_isInCallback = false;\n    }\n\n    CallbackGuard(const CallbackGuard &) = delete;\n\n    CallbackGuard &operator=(const CallbackGuard &) = delete;\n\nprivate:\n    bool &m_isInCallback;\n};\n\n}}\n\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/cpp_wrapper/util/StringUtil.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_UTIL_STRING_FILE_H\n#define AERON_UTIL_STRING_FILE_H\n\n#include <string>\n#include <sstream>\n#include <iostream>\n#include <type_traits>\n#include <iomanip>\n#include <locale>\n#include <cstdarg>\n\n#include \"util/Exceptions.h\"\n\nnamespace aeron { namespace util\n{\n\ninline std::string trimWSLeft(std::string str, const char *wschars = \" \\t\")\n{\n    str.erase(0, str.find_first_not_of(wschars));\n    return str;\n}\n\ninline std::string trimWSRight(std::string str, const char *wschars = \" \\t\")\n{\n    str.erase(str.find_last_not_of(wschars) + 1);\n    return str;\n}\n\ninline std::string trimWSBoth(std::string str, const char *wschars = \" \\t\")\n{\n    return trimWSLeft(trimWSRight(std::move(str), wschars), wschars);\n}\n\ninline bool startsWith(const std::string &input, std::size_t position, const std::string &prefix)\n{\n    if ((input.length() - position) < prefix.length())\n    {\n        return false;\n    }\n\n    for (std::size_t i = 0; i < prefix.length(); i++)\n    {\n        if (input[position + i] != prefix[i])\n        {\n            return false;\n        }\n    }\n\n    return true;\n}\n\ninline bool endsWith(const std::string &str, const std::string &suffix)\n{\n    return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;\n}\n\ntemplate<class valueType>\nvalueType parse(const std::string &input)\n{\n    std::string str = trimWSBoth(input);\n\n    std::istringstream stream(str);\n    valueType value;\n\n    if (std::is_integral<valueType>::value && input.length() > 2 &&\n        input[0] == '0' &&\n        (input[1] == 'x' || input[1] == 'X'))\n    {\n        stream >> std::hex >> value;\n    }\n    else\n    {\n        stream >> value;\n    }\n\n    if (stream.fail() || !stream.eof())\n    {\n        throw ParseException(std::string(\"failed to parse: \") + input, SOURCEINFO);\n    }\n\n    return value;\n}\n\ntemplate<typename value_t>\ninline std::string toString(const value_t &value)\n{\n    std::stringstream stream;\n    stream << value;\n\n    return stream.str();\n}\n\ntemplate<typename value_t>\ninline std::string toStringWithCommas(const value_t &value)\n{\n    std::stringstream stream;\n\n    stream.imbue(std::locale(\"\"));\n    stream << std::fixed << value;\n\n    return stream.str();\n}\n\ninline std::string strPrintf(const char *format, ...)\n{\n    const int BUFFER_SIZE = 128;\n    char buffer[BUFFER_SIZE];\n\n    va_list argp;\n    va_start(argp, format);\n    int len = vsnprintf(buffer, BUFFER_SIZE, format, argp);\n    va_end(argp);\n\n    if (len >= BUFFER_SIZE)\n    {\n        len++;\n        std::string output(len, ' ');\n\n        va_start(argp, format);\n        vsnprintf(&output[0], len, format, argp);\n        va_end(argp);\n\n        output.pop_back(); // remove trailing 0 char\n        return output;\n    }\n    else\n    {\n        return { buffer };\n    }\n}\n\nnamespace private_impl\n{\ntemplate<typename T>\nvoid concat(std::stringstream &s, T v)\n{\n    s << v;\n}\n\ntemplate<typename T, typename... Ts>\nvoid concat(std::stringstream &s, T v, Ts... vs)\n{\n    s << v;\n    concat(s, vs...);\n}\n}\n\ntemplate<typename... Ts>\nstd::string strconcat(Ts... vs)\n{\n    std::stringstream s;\n    private_impl::concat(s, vs...);\n\n    return s.str();\n}\n\ninline bool continuationBarrier(const std::string &label)\n{\n    bool result = false;\n    char response = '\\0';\n    std::cout << std::endl << label << \" (y/n): \";\n    std::cin >> response;\n\n    if ('y' == response || 'Y' == response)\n    {\n        result = true;\n    }\n\n    return result;\n}\n\ntemplate<typename T>\nstatic T fromString(const std::string &str)\n{\n    std::istringstream is(str);\n    T t;\n    is >> t;\n\n    return t;\n}\n\n}}\n\n#endif\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/Aeron.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.exceptions.DriverTimeoutException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.version.Versioned;\nimport org.agrona.BufferUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.Strings;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.BusySpinIdleStrategy;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.SleepingMillisIdleStrategy;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.broadcast.BroadcastReceiver;\nimport org.agrona.concurrent.broadcast.CopyBroadcastReceiver;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.nio.ByteBuffer;\nimport java.util.Objects;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport static io.aeron.Aeron.Configuration.MAX_CLIENT_NAME_LENGTH;\nimport static org.agrona.SystemUtil.getDurationInNanos;\nimport static org.agrona.SystemUtil.getProperty;\n\n/**\n * Aeron entry point for communicating to the Media Driver for creating {@link Publication}s and {@link Subscription}s.\n * Use an {@link Aeron.Context} to configure the Aeron object.\n * <p>\n * A client application requires only one Aeron object per Media Driver.\n * <p>\n * <b>Note:</b> If {@link Aeron.Context#errorHandler(ErrorHandler)} is not set and a {@link DriverTimeoutException}\n * occurs then the process will face the wrath of {@link System#exit(int)}.\n * See {@link Aeron.Configuration#DEFAULT_ERROR_HANDLER}.\n */\n@Versioned\npublic final class Aeron implements AutoCloseable\n{\n    /**\n     * Used to represent a null value for when some value is not yet set.\n     */\n    public static final int NULL_VALUE = -1;\n\n    private static final VarHandle IS_CLOSED_VH;\n\n    static\n    {\n        try\n        {\n            IS_CLOSED_VH = MethodHandles.lookup().findVarHandle(Aeron.class, \"isClosed\", boolean.class);\n        }\n        catch (final ReflectiveOperationException ex)\n        {\n            throw new ExceptionInInitializerError(ex);\n        }\n    }\n\n    private volatile boolean isClosed;\n    private final long clientId;\n    private final ClientConductor conductor;\n    private final RingBuffer commandBuffer;\n    private final AgentInvoker conductorInvoker;\n    private final AgentRunner conductorRunner;\n    private final Context ctx;\n\n    Aeron(final Context ctx)\n    {\n        try\n        {\n            ctx.conclude();\n\n            this.ctx = ctx;\n            clientId = ctx.clientId();\n            commandBuffer = ctx.toDriverBuffer();\n            conductor = new ClientConductor(ctx, this);\n\n            if (ctx.useConductorAgentInvoker())\n            {\n                conductorInvoker = new AgentInvoker(ctx.errorHandler(), null, conductor);\n                conductorRunner = null;\n            }\n            else\n            {\n                conductorInvoker = null;\n                conductorRunner = new AgentRunner(ctx.idleStrategy(), ctx.errorHandler(), null, conductor);\n            }\n        }\n        catch (final ConcurrentConcludeException ex)\n        {\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietClose(ctx::close);\n            throw ex;\n        }\n    }\n\n    /**\n     * Create an Aeron instance and connect to the media driver with a default {@link Context}.\n     * <p>\n     * Threads required for interacting with the media driver are created and managed within the Aeron instance.\n     *\n     * @return the new {@link Aeron} instance connected to the Media Driver.\n     */\n    public static Aeron connect()\n    {\n        return connect(new Context());\n    }\n\n    /**\n     * Create an Aeron instance and connect to the media driver.\n     * <p>\n     * Threads required for interacting with the media driver are created and managed within the Aeron instance.\n     * <p>\n     * If an exception occurs while trying to establish a connection then the {@link Context#close()} method\n     * will be called on the passed context.\n     *\n     * @param ctx for configuration of the client.\n     * @return the new {@link Aeron} instance connected to the Media Driver.\n     */\n    public static Aeron connect(final Context ctx)\n    {\n        try\n        {\n            final Aeron aeron = new Aeron(ctx);\n\n            if (ctx.useConductorAgentInvoker())\n            {\n                aeron.conductorInvoker.start();\n            }\n            else\n            {\n                AgentRunner.startOnThread(aeron.conductorRunner, ctx.threadFactory());\n            }\n\n            return aeron;\n        }\n        catch (final ConcurrentConcludeException ex)\n        {\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            ctx.close();\n            throw ex;\n        }\n    }\n\n    /**\n     * Print out the values from {@link #countersReader()} which can be useful for debugging.\n     *\n     * @param out to where the counters get printed.\n     */\n    public void printCounters(final PrintStream out)\n    {\n        final CountersReader counters = countersReader();\n        counters.forEach((value, id, label) -> out.format(\"%3d: %,20d - %s%n\", id, value, label));\n    }\n\n    /**\n     * Has the client been closed? If not then the CnC file may not be unmapped.\n     *\n     * @return true if the client has been explicitly closed otherwise false.\n     */\n    public boolean isClosed()\n    {\n        return isClosed;\n    }\n\n    /**\n     * Get the {@link Aeron.Context} that is used by this client.\n     *\n     * @return the {@link Aeron.Context} that is use by this client.\n     */\n    public Context context()\n    {\n        return ctx;\n    }\n\n    /**\n     * Get the client identity that has been allocated for communicating with the media driver.\n     *\n     * @return the client identity that has been allocated for communicating with the media driver.\n     */\n    public long clientId()\n    {\n        return clientId;\n    }\n\n    /**\n     * Get the {@link AgentInvoker} for the client conductor.\n     *\n     * @return the {@link AgentInvoker} for the client conductor.\n     */\n    public AgentInvoker conductorAgentInvoker()\n    {\n        return conductorInvoker;\n    }\n\n    /**\n     * Is the command still active for a given correlation id.\n     *\n     * @param correlationId to check if it is still active.\n     * @return true in the command is still in active processing or false if completed successfully or errored.\n     * @see Publication#asyncAddDestination(String)\n     * @see Subscription#asyncAddDestination(String)\n     * @see #hasActiveCommands()\n     */\n    public boolean isCommandActive(final long correlationId)\n    {\n        return conductor.isCommandActive(correlationId);\n    }\n\n    /**\n     * Does the client have any active asynchronous commands?\n     * <p>\n     * When close operations are performed on {@link Publication}s, {@link Subscription}s, and {@link Counter}s the\n     * commands are sent asynchronously to the driver. The client tracks active commands in case errors need to be\n     * reported. If you wish to wait for acknowledgement of close operations then wait for this method to return false.\n     *\n     * @return true if any commands are currently active otherwise false.\n     */\n    public boolean hasActiveCommands()\n    {\n        return conductor.hasActiveCommands();\n    }\n\n    /**\n     * Clean up and release all Aeron client resources and shutdown conductor thread if not using\n     * {@link Context#useConductorAgentInvoker(boolean)}.\n     * <p>\n     * This will close all currently open {@link Publication}s, {@link Subscription}s, and {@link Counter}s created\n     * from this client. To check for the command being acknowledged by the driver\n     */\n    public void close()\n    {\n        if (IS_CLOSED_VH.compareAndSet(this, false, true))\n        {\n            final ErrorHandler errorHandler = ctx.errorHandler();\n            if (null != conductorRunner)\n            {\n                CloseHelper.close(errorHandler, conductorRunner);\n            }\n            else\n            {\n                CloseHelper.close(errorHandler, conductorInvoker);\n            }\n        }\n    }\n\n    /**\n     * Add a {@link Publication} for publishing messages to subscribers. The publication returned is threadsafe.\n     *\n     * @param channel  for sending the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return a new {@link ConcurrentPublication}.\n     */\n    public ConcurrentPublication addPublication(final String channel, final int streamId)\n    {\n        return conductor.addPublication(channel, streamId);\n    }\n\n    /**\n     * Add an {@link ExclusivePublication} for publishing messages to subscribers from a single thread.\n     *\n     * @param channel  for sending the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return a new {@link ExclusivePublication}.\n     */\n    public ExclusivePublication addExclusivePublication(final String channel, final int streamId)\n    {\n        return conductor.addExclusivePublication(channel, streamId);\n    }\n\n    /**\n     * Asynchronously add a {@link Publication} for publishing messages to subscribers. The added publication returned\n     * is threadsafe.\n     *\n     * @param channel  for sending the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return the registration id of the publication which can be used to get the added publication.\n     * @see #getPublication(long)\n     */\n    public long asyncAddPublication(final String channel, final int streamId)\n    {\n        return conductor.asyncAddPublication(channel, streamId);\n    }\n\n    /**\n     * Asynchronously add a {@link Publication} for publishing messages to subscribers from a single thread.\n     *\n     * @param channel  for sending the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return the registration id of the publication which can be used to get the added exclusive publication.\n     * @see #getExclusivePublication(long)\n     */\n    public long asyncAddExclusivePublication(final String channel, final int streamId)\n    {\n        return conductor.asyncAddExclusivePublication(channel, streamId);\n    }\n\n    /**\n     * Asynchronously remove a {@link Publication}.\n     *\n     * @param registrationId of the publication to be removed.\n     * @see #asyncAddPublication(String, int)\n     * @see #asyncAddExclusivePublication(String, int)\n     */\n    public void asyncRemovePublication(final long registrationId)\n    {\n        conductor.removePublication(registrationId);\n    }\n\n    /**\n     * Get a {@link Publication} for publishing messages to subscribers. The publication returned is threadsafe.\n     *\n     * @param registrationId returned from {@link #asyncAddPublication(String, int)}.\n     * @return a new {@link ConcurrentPublication} when available otherwise null.\n     * @see #asyncAddPublication(String, int)\n     */\n    public ConcurrentPublication getPublication(final long registrationId)\n    {\n        return conductor.getPublication(registrationId);\n    }\n\n    /**\n     * Get a single threaded {@link Publication} for publishing messages to subscribers.\n     *\n     * @param registrationId returned from {@link #asyncAddExclusivePublication(String, int)}.\n     * @return a new {@link ExclusivePublication} when available otherwise null.\n     * @see #asyncAddExclusivePublication(String, int)\n     */\n    public ExclusivePublication getExclusivePublication(final long registrationId)\n    {\n        return conductor.getExclusivePublication(registrationId);\n    }\n\n    /**\n     * Add a new {@link Subscription} for subscribing to messages from publishers.\n     * <p>\n     * The method will set up the {@link Subscription} to use the\n     * {@link Aeron.Context#availableImageHandler(AvailableImageHandler)} and\n     * {@link Aeron.Context#unavailableImageHandler(UnavailableImageHandler)} from the {@link Aeron.Context}.\n     *\n     * @param channel  for receiving the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return the {@link Subscription} for the channel and streamId pair.\n     */\n    public Subscription addSubscription(final String channel, final int streamId)\n    {\n        return conductor.addSubscription(channel, streamId);\n    }\n\n    /**\n     * Add a new {@link Subscription} for subscribing to messages from publishers.\n     * <p>\n     * This method will override the default handlers from the {@link Aeron.Context}, i.e.\n     * {@link Aeron.Context#availableImageHandler(AvailableImageHandler)} and\n     * {@link Aeron.Context#unavailableImageHandler(UnavailableImageHandler)}. Null values are valid and will\n     * result in no action being taken.\n     *\n     * @param channel                 for receiving the messages known to the media layer.\n     * @param streamId                within the channel scope.\n     * @param availableImageHandler   called when {@link Image}s become available for consumption. Null is valid if no\n     *                                action is to be taken.\n     * @param unavailableImageHandler called when {@link Image}s go unavailable for consumption. Null is valid if no\n     *                                action is to be taken.\n     * @return the {@link Subscription} for the channel and streamId pair.\n     */\n    public Subscription addSubscription(\n        final String channel,\n        final int streamId,\n        final AvailableImageHandler availableImageHandler,\n        final UnavailableImageHandler unavailableImageHandler)\n    {\n        return conductor.addSubscription(channel, streamId, availableImageHandler, unavailableImageHandler);\n    }\n\n    /**\n     * Add a new {@link Subscription} for subscribing to messages from publishers.\n     *\n     * @param channel                 for receiving the messages known to the media layer.\n     * @param streamId                within the channel scope.\n     * @param availableImageHandler   called when {@link Image}s become available for consumption. Null is valid if no\n     *                                action is to be taken.\n     * @param unavailableImageHandler called when {@link Image}s go unavailable for consumption. Null is valid if no\n     *                                action is to be taken.\n     * @return the registration id of the subscription which can be used to get the added subscription.\n     * @see Aeron#addSubscription(String, int, AvailableImageHandler, UnavailableImageHandler)\n     * @see Aeron#getSubscription(long)\n     */\n    public long asyncAddSubscription(\n        final String channel,\n        final int streamId,\n        final AvailableImageHandler availableImageHandler,\n        final UnavailableImageHandler unavailableImageHandler)\n    {\n        return conductor.asyncAddSubscription(channel, streamId, availableImageHandler, unavailableImageHandler);\n    }\n\n    /**\n     * Add a new {@link Subscription} for subscribing to messages from publishers.\n     *\n     * @param channel  for receiving the messages known to the media layer.\n     * @param streamId within the channel scope.\n     * @return the registration id of the subscription which can be used to get the added subscription.\n     * @see Aeron#addSubscription(String, int)\n     * @see Aeron#getSubscription(long)\n     */\n    public long asyncAddSubscription(final String channel, final int streamId)\n    {\n        return conductor.asyncAddSubscription(channel, streamId);\n    }\n\n    /**\n     * Asynchronously remove a {@link Subscription}.\n     *\n     * @param registrationId to be of the subscription removed.\n     * @see #asyncAddSubscription(String, int)\n     * @see #asyncAddSubscription(String, int, AvailableImageHandler, UnavailableImageHandler)\n     */\n    public void asyncRemoveSubscription(final long registrationId)\n    {\n        conductor.removeSubscription(registrationId);\n    }\n\n    /**\n     * Get a {@link Subscription} for subscribing to messages from publishers.\n     *\n     * @param registrationId returned from\n     *                       {@link #asyncAddSubscription(String, int, AvailableImageHandler, UnavailableImageHandler)}\n     *                       or {@link #asyncAddSubscription(String, int)}\n     * @return a new {@link Subscription} when available otherwise null.\n     * @see #asyncAddSubscription(String, int)\n     * @see #asyncAddSubscription(String, int, AvailableImageHandler, UnavailableImageHandler)\n     */\n    public Subscription getSubscription(final long registrationId)\n    {\n        return conductor.getSubscription(registrationId);\n    }\n\n    /**\n     * Generate the next correlation id that is unique for the connected media driver.\n     * <p>\n     * This is useful generating correlation identifiers for pairing requests with responses in a clients own\n     * application protocol.\n     * <p>\n     * This method is thread safe and will work across processes that all use the same media driver.\n     *\n     * @return next correlation id that is unique for the media driver.\n     */\n    public long nextCorrelationId()\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"client is closed\");\n        }\n\n        return commandBuffer.nextCorrelationId();\n    }\n\n    /**\n     * Get next available session id from the media driver. The session id will be unique for the connected media\n     * driver and given {@code streamId}.\n     * <p>\n     * If media driver's version is 1.49.0 or higher, then the session id is returned by the media driver. Otherwise,\n     * a random session id is generated.\n     *\n     * @param streamId for which a new session id is requested. Media driver only checks for session clashes at the\n     *                 stream level.\n     * @return next available session id that is unique for the media driver and given {@code streamId}.\n     * @since 1.49.0\n     */\n    public int nextSessionId(final int streamId)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"client is closed\");\n        }\n\n        return conductor.nextSessionId(streamId);\n    }\n\n    /**\n     * Get the {@link CountersReader} for the Aeron media driver counters.\n     *\n     * @return new {@link CountersReader} for the Aeron media driver in use.\n     */\n    public CountersReader countersReader()\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"client is closed\");\n        }\n\n        return conductor.countersReader();\n    }\n\n    /**\n     * Allocate a counter on the media driver and return a {@link Counter} for it.\n     * <p>\n     * The counter should be freed by calling {@link Counter#close()}.\n     * <p>\n     * The typeId should be 1000 or greater. Values lower than that are reserved for use by Aeron.\n     *\n     * @param typeId      for the counter.\n     * @param keyBuffer   containing the optional key for the counter.\n     * @param keyOffset   within the keyBuffer at which the key begins.\n     * @param keyLength   of the key in the keyBuffer.\n     * @param labelBuffer containing the mandatory label for the counter. The label should not be length prefixed.\n     * @param labelOffset within the labelBuffer at which the label begins.\n     * @param labelLength of the label in the labelBuffer.\n     * @return the newly allocated counter.\n     * @see org.agrona.concurrent.status.CountersManager#allocate(int, DirectBuffer, int, int, DirectBuffer, int, int)\n     */\n    public Counter addCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength)\n    {\n        return conductor.addCounter(typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength);\n    }\n\n    /**\n     * Allocate a counter on the media driver and return a {@link Counter} for it.\n     * <p>\n     * The counter should be freed by calling {@link Counter#close()}.\n     * <p>\n     * The typeId should be 1000 or greater. Values lower than that are reserved for use by Aeron.\n     *\n     * @param typeId for the counter.\n     * @param label  for the counter. It should be US-ASCII.\n     * @return the newly allocated counter.\n     * @see org.agrona.concurrent.status.CountersManager#allocate(String, int)\n     */\n    public Counter addCounter(final int typeId, final String label)\n    {\n        return conductor.addCounter(typeId, label);\n    }\n\n    /**\n     * Allocates or returns an existing static counter instance using specified {@code typeId} and\n     * {@code registrationId} pair. Such a counter cannot be deleted and its lifecycle is decoupled from this\n     * {@link Aeron} instance, i.e. won't be closed when this instance is closed or times out.\n     * <p>\n     * <em><strong>Note:</strong> calling {@link Counter#close()} will only close the counter instance itself but will\n     * not free the counter in the CnC file.</em>\n     * <p>\n     * The typeId should be 1000 or greater. Values lower than that are reserved for use by Aeron.\n     *\n     * @param typeId         for the counter.\n     * @param keyBuffer      containing the optional key for the counter.\n     * @param keyOffset      within the keyBuffer at which the key begins.\n     * @param keyLength      of the key in the keyBuffer.\n     * @param labelBuffer    containing the mandatory label for the counter. The label should not be length prefixed.\n     * @param labelOffset    within the labelBuffer at which the label begins.\n     * @param labelLength    of the label in the labelBuffer.\n     * @param registrationId that uniquely identifies the static counter for a given {@code typeId}.\n     * @return the static counter instance.\n     * @see org.agrona.concurrent.status.CountersManager#allocate(int, DirectBuffer, int, int, DirectBuffer, int, int)\n     * @since 1.45.0\n     */\n    public Counter addStaticCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength,\n        final long registrationId)\n    {\n        return conductor.addStaticCounter(\n            typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength, registrationId);\n    }\n\n    /**\n     * Allocates or returns an existing static counter instance using specified {@code typeId} and\n     * {@code registrationId} pair. Such a counter cannot be deleted and its lifecycle is decoupled from this\n     * {@link Aeron} instance, i.e. won't be closed when this instance is closed or times out.\n     * <p>\n     * <em><strong>Note:</strong> calling {@link Counter#close()} will only close the counter instance itself but will\n     * not free the counter in the CnC file.</em>\n     * <p>\n     * The typeId should be 1000 or greater. Values lower than that are reserved for use by Aeron.\n     *\n     * @param typeId         for the counter.\n     * @param label          for the counter. It should be US-ASCII.\n     * @param registrationId that uniquely identifies the static counter for a given {@code typeId}.\n     * @return the static counter.\n     * @see org.agrona.concurrent.status.CountersManager#allocate(String, int)\n     * @since 1.45.0\n     */\n    public Counter addStaticCounter(final int typeId, final String label, final long registrationId)\n    {\n        return conductor.addStaticCounter(typeId, label, registrationId);\n    }\n\n    /**\n     * Asynchronously allocate a counter on the media driver.\n     * <p>\n     * The typeId should be 1000 or greater. Values lower than that are reserved for use by Aeron.\n     *\n     * @param typeId for the counter.\n     * @param label  for the counter. It should be US-ASCII.\n     * @return the registration id of the counter which can be used to get it by calling {@link #getCounter(long)}\n     * method.\n     * @see #getCounter(long)\n     * @since 1.49.0\n     */\n    public long asyncAddCounter(final int typeId, final String label)\n    {\n        return conductor.asyncAddCounter(typeId, label);\n    }\n\n    /**\n     * Asynchronously allocate a counter on the media driver.\n     * <p>\n     * The typeId should be 1000 or greater. Values lower than that are reserved for use by Aeron.\n     *\n     * @param typeId      for the counter.\n     * @param keyBuffer   containing the optional key for the counter.\n     * @param keyOffset   within the keyBuffer at which the key begins.\n     * @param keyLength   of the key in the keyBuffer.\n     * @param labelBuffer containing the mandatory label for the counter. The label should not be length prefixed.\n     * @param labelOffset within the labelBuffer at which the label begins.\n     * @param labelLength of the label in the labelBuffer.\n     * @return the registration id of the counter which can be used to get it by calling {@link #getCounter(long)}\n     * method.\n     * @see #getCounter(long)\n     * @since 1.49.0\n     */\n    public long asyncAddCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength)\n    {\n        return conductor.asyncAddCounter(\n            typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength);\n    }\n\n    /**\n     * Asynchronously allocates or returns an existing static counter instance using specified {@code typeId} and\n     * {@code registrationId} pair. Such a counter cannot be deleted and its lifecycle is decoupled from this\n     * {@link Aeron} instance, i.e. won't be closed when this instance is closed or times out.\n     * <p>\n     * <em><strong>Note:</strong> calling {@link Counter#close()} will only close the counter instance itself but will\n     * not free the counter in the CnC file.</em>\n     * <p>\n     * The typeId should be 1000 or greater. Values lower than that are reserved for use by Aeron.\n     *\n     * @param typeId         for the counter.\n     * @param label          for the counter. It should be US-ASCII.\n     * @param registrationId that uniquely identifies the static counter for a given {@code typeId}.\n     * @return the correlation id of the command which can be used to get the counter by calling\n     * {@link #getCounter(long)} method.\n     * @see #getCounter(long)\n     * @since 1.49.0\n     */\n    public long asyncAddStaticCounter(final int typeId, final String label, final long registrationId)\n    {\n        return conductor.asyncAddStaticCounter(typeId, label, registrationId);\n    }\n\n    /**\n     * Asynchronously allocates or returns an existing static counter instance using specified {@code typeId} and\n     * {@code registrationId} pair. Such a counter cannot be deleted and its lifecycle is decoupled from this\n     * {@link Aeron} instance, i.e. won't be closed when this instance is closed or times out.\n     * <p>\n     * <em><strong>Note:</strong> calling {@link Counter#close()} will only close the counter instance itself but will\n     * not free the counter in the CnC file.</em>\n     * <p>\n     * The typeId should be 1000 or greater. Values lower than that are reserved for use by Aeron.\n     *\n     * @param typeId         for the counter.\n     * @param keyBuffer      containing the optional key for the counter.\n     * @param keyOffset      within the keyBuffer at which the key begins.\n     * @param keyLength      of the key in the keyBuffer.\n     * @param labelBuffer    containing the mandatory label for the counter. The label should not be length prefixed.\n     * @param labelOffset    within the labelBuffer at which the label begins.\n     * @param labelLength    of the label in the labelBuffer.\n     * @param registrationId that uniquely identifies the static counter for a given {@code typeId}.\n     * @return the correlation id of the command which can be used to get the counter by calling\n     * {@link #getCounter(long)} method.\n     * @see #getCounter(long)\n     * @since 1.49.0\n     */\n    public long asyncAddStaticCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength,\n        final long registrationId)\n    {\n        return conductor.asyncAddStaticCounter(\n            typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength, registrationId);\n    }\n\n    /**\n     * Get a {@link Counter} that was created asynchronously.\n     *\n     * @param correlationId returned from one of the async methods:\n     *                      {@link #asyncAddCounter(int, String)},\n     *                      {@link #asyncAddCounter(int, DirectBuffer, int, int, DirectBuffer, int, int)},\n     *                      {@link #asyncAddStaticCounter(int, String, long)} or\n     *                      {@link #asyncAddStaticCounter(int, DirectBuffer, int, int, DirectBuffer, int, int, long)}.\n     * @return a new {@link Counter} when available, otherwise {@code null}.\n     * @see #asyncAddCounter(int, String)\n     * @see #asyncAddCounter(int, DirectBuffer, int, int, DirectBuffer, int, int)\n     * @see #asyncAddStaticCounter(int, String, long)\n     * @see #asyncAddStaticCounter(int, DirectBuffer, int, int, DirectBuffer, int, int, long)\n     * @since 1.49.0\n     */\n    public Counter getCounter(final long correlationId)\n    {\n        return conductor.getCounter(correlationId);\n    }\n\n    /**\n     * Asynchronously remove a {@link Counter}.\n     *\n     * @param registrationId of the counter to be removed.\n     * @see #asyncAddCounter(int, String)\n     * @see #asyncAddCounter(int, DirectBuffer, int, int, DirectBuffer, int, int)\n     * @since 1.49.0\n     */\n    public void asyncRemoveCounter(final long registrationId)\n    {\n        conductor.asyncRemoveCounter(registrationId);\n    }\n\n    /**\n     * Add a handler to the list be called when {@link Counter}s become available.\n     *\n     * @param handler to be called when {@link Counter}s become available.\n     * @return registration id for the handler which can be used to remove it.\n     */\n    public long addAvailableCounterHandler(final AvailableCounterHandler handler)\n    {\n        return conductor.addAvailableCounterHandler(handler);\n    }\n\n    /**\n     * Remove a previously added handler to the list be called when {@link Counter}s become available.\n     *\n     * @param registrationId to be removed which was returned from add method.\n     * @return true if found and removed otherwise false.\n     */\n    public boolean removeAvailableCounterHandler(final long registrationId)\n    {\n        return conductor.removeAvailableCounterHandler(registrationId);\n    }\n\n    /**\n     * Remove a previously added handler to the list be called when {@link Counter}s become available.\n     *\n     * @param handler to be removed.\n     * @return true if found and removed otherwise false.\n     * @deprecated please use {@link #removeAvailableCounterHandler(long)}.\n     */\n    @Deprecated\n    public boolean removeAvailableCounterHandler(final AvailableCounterHandler handler)\n    {\n        return conductor.removeAvailableCounterHandler(handler);\n    }\n\n    /**\n     * Add a handler to the list be called when {@link Counter}s become unavailable.\n     *\n     * @param handler to be called when {@link Counter}s become unavailable.\n     * @return registration id for the handler which can be used to remove it.\n     */\n    public long addUnavailableCounterHandler(final UnavailableCounterHandler handler)\n    {\n        return conductor.addUnavailableCounterHandler(handler);\n    }\n\n    /**\n     * Remove a previously added handler to the list be called when {@link Counter}s become unavailable.\n     *\n     * @param registrationId to be removed which was returned from add method.\n     * @return true if found and removed otherwise false.\n     */\n    public boolean removeUnavailableCounterHandler(final long registrationId)\n    {\n        return conductor.removeUnavailableCounterHandler(registrationId);\n    }\n\n    /**\n     * Remove a previously added handler to the list be called when {@link Counter}s become unavailable.\n     *\n     * @param handler to be removed.\n     * @return true if found and removed otherwise false.\n     * @deprecated please use {@link #removeUnavailableCounterHandler(long)}.\n     */\n    @Deprecated\n    public boolean removeUnavailableCounterHandler(final UnavailableCounterHandler handler)\n    {\n        return conductor.removeUnavailableCounterHandler(handler);\n    }\n\n    /**\n     * Add a handler to the list be called when the Aeron client is closed.\n     *\n     * @param handler to be called when the Aeron client is closed.\n     * @return registration id for the handler which can be used to remove it.\n     */\n    public long addCloseHandler(final Runnable handler)\n    {\n        return conductor.addCloseHandler(handler);\n    }\n\n    /**\n     * Remove a previously added handler to the list be called when the Aeron client is closed.\n     *\n     * @param registrationId of the handler from when it was added.\n     * @return true if found and removed otherwise false.\n     */\n    public boolean removeCloseHandler(final long registrationId)\n    {\n        return conductor.removeCloseHandler(registrationId);\n    }\n\n    /**\n     * Remove a previously added handler to the list be called when the Aeron client is closed.\n     *\n     * @param handler to be removed.\n     * @return true if found and removed otherwise false.\n     * @deprecated please use {@link #removeCloseHandler(long)}.\n     */\n    @Deprecated\n    public boolean removeCloseHandler(final Runnable handler)\n    {\n        return conductor.removeCloseHandler(handler);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"Aeron{\" +\n            \"isClosed=\" + isClosed +\n            \", clientId=\" + clientId +\n            '}';\n    }\n\n    /**\n     * Called by the {@link ClientConductor} if the client should be terminated due to timeout.\n     */\n    void internalClose()\n    {\n        isClosed = true;\n    }\n\n    /**\n     * Configuration options for the {@link Aeron} client.\n     */\n    public static final class Configuration\n    {\n        private Configuration()\n        {\n        }\n\n        /**\n         * Duration in milliseconds for which the client conductor will sleep between duty cycles.\n         */\n        static final long IDLE_SLEEP_DEFAULT_MS = 16;\n\n        /**\n         * Duration in milliseconds for which the client will sleep when awaiting a response from the driver.\n         */\n        static final long AWAITING_IDLE_SLEEP_MS = 1;\n\n        /**\n         * Duration to sleep when idle.\n         */\n        @Config(expectedCDefaultFieldName = \"AERON_CONTEXT_IDLE_SLEEP_DURATION_NS_DEFAULT\")\n        public static final String IDLE_SLEEP_DURATION_PROP_NAME = \"aeron.client.idle.sleep.duration\";\n\n        /**\n         * Duration in nanoseconds for which the client conductor will sleep between work cycles when idle.\n         */\n        @Config(id = \"IDLE_SLEEP_DURATION\", defaultType = DefaultType.LONG, defaultLong = 16_000_000)\n        static final long IDLE_SLEEP_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(IDLE_SLEEP_DEFAULT_MS);\n\n        /**\n         * Default interval between sending keepalive control messages to the driver.\n         */\n        static final long KEEPALIVE_INTERVAL_NS = TimeUnit.MILLISECONDS.toNanos(500);\n\n        /**\n         * Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources\n         * such as memory mapped files.\n         */\n        @Config(expectedCDefaultFieldName = \"AERON_CONTEXT_RESOURCE_LINGER_DURATION_NS_DEFAULT\")\n        public static final String RESOURCE_LINGER_DURATION_PROP_NAME = \"aeron.client.resource.linger.duration\";\n\n        /**\n         * Default duration a resource should linger before deletion.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 3_000_000_000L)\n        public static final long RESOURCE_LINGER_DURATION_DEFAULT_NS = TimeUnit.SECONDS.toNanos(3);\n\n        /**\n         * Duration to linger on close so that publishers subscribers have time to notice closed resources.\n         * This value can be set to a few seconds if the application is likely to experience CPU starvation or\n         * long GC pauses.\n         */\n        @Config(existsInC = false)\n        public static final String CLOSE_LINGER_DURATION_PROP_NAME = \"aeron.client.close.linger.duration\";\n\n        /**\n         * Default duration to linger on close so that publishers subscribers have time to notice closed resources.\n         */\n        @Config\n        public static final long CLOSE_LINGER_DURATION_DEFAULT_NS = 0;\n\n        /**\n         * Should memory-mapped files be pre-touched so that they are already faulted into a process.\n         * <p>\n         * Pre-touching files can result in it taking longer for resources to become available in\n         * return for avoiding later pauses due to page faults.\n         */\n        @Config(\n            expectedCEnvVarFieldName = \"AERON_CLIENT_PRE_TOUCH_MAPPED_MEMORY_ENV_VAR\",\n            expectedCEnvVar = \"AERON_CLIENT_PRE_TOUCH_MAPPED_MEMORY\",\n            expectedCDefaultFieldName = \"AERON_CONTEXT_PRE_TOUCH_MAPPED_MEMORY_DEFAULT\")\n        public static final String PRE_TOUCH_MAPPED_MEMORY_PROP_NAME = \"aeron.pre.touch.mapped.memory\";\n\n        /**\n         * Default for if a memory-mapped filed should be pre-touched to fault it into a process.\n         */\n        @Config\n        public static final boolean PRE_TOUCH_MAPPED_MEMORY_DEFAULT = false;\n\n        /**\n         * System property to name Aeron client. Default to empty string.\n         *\n         * @since 1.44.0\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\", skipCDefaultValidation = true)\n        public static final String CLIENT_NAME_PROP_NAME = \"aeron.client.name\";\n\n        /**\n         * The Default handler for Aeron runtime exceptions.\n         * When a {@link DriverTimeoutException} is encountered, this handler will exit the program.\n         * <p>\n         * The error handler can be overridden by supplying an {@link Context} with a custom handler.\n         *\n         * @see Context#errorHandler(ErrorHandler)\n         */\n        public static final ErrorHandler DEFAULT_ERROR_HANDLER =\n            (throwable) ->\n            {\n                synchronized (System.err)\n                {\n                    System.err.println(System.currentTimeMillis() + \" Exception:\");\n                    throwable.printStackTrace(System.err);\n                }\n\n                if (throwable instanceof DriverTimeoutException)\n                {\n                    System.err.printf(\"%n***%n*** Media Driver timeout - is it running? exiting client...%n***%n\");\n\n                    final Thread t = new Thread(() -> Runtime.getRuntime().exit(-1));\n\n                    t.setName(\"runtime-exit-runner\");\n                    t.setDaemon(true);\n                    t.start();\n                }\n            };\n\n        /**\n         * Duration in nanoseconds for which the client conductor will sleep between work cycles when idle.\n         *\n         * @return duration in nanoseconds to wait when idle in client conductor.\n         * @see #IDLE_SLEEP_DURATION_PROP_NAME\n         */\n        @Config\n        public static long idleSleepDurationNs()\n        {\n            return getDurationInNanos(IDLE_SLEEP_DURATION_PROP_NAME, IDLE_SLEEP_DEFAULT_NS);\n        }\n\n        /**\n         * Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources\n         * such as memory mapped files.\n         *\n         * @return duration in nanoseconds to wait before deleting an expired resource.\n         * @see #RESOURCE_LINGER_DURATION_PROP_NAME\n         */\n        @Config\n        public static long resourceLingerDurationNs()\n        {\n            return getDurationInNanos(RESOURCE_LINGER_DURATION_PROP_NAME, RESOURCE_LINGER_DURATION_DEFAULT_NS);\n        }\n\n        /**\n         * Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources\n         * such as memory mapped files.\n         *\n         * @return duration in nanoseconds to wait before deleting an expired resource.\n         * @see #RESOURCE_LINGER_DURATION_PROP_NAME\n         */\n        @Config\n        public static long closeLingerDurationNs()\n        {\n            return getDurationInNanos(CLOSE_LINGER_DURATION_PROP_NAME, CLOSE_LINGER_DURATION_DEFAULT_NS);\n        }\n\n        /**\n         * Should memory-mapped files be pre-touched so that they are already faulted into a process.\n         *\n         * @return true if memory mappings should be pre-touched, otherwise false.\n         * @see #PRE_TOUCH_MAPPED_MEMORY_PROP_NAME\n         */\n        @Config\n        public static boolean preTouchMappedMemory()\n        {\n            final String value = System.getProperty(PRE_TOUCH_MAPPED_MEMORY_PROP_NAME);\n            if (null != value)\n            {\n                return Boolean.parseBoolean(value);\n            }\n\n            return PRE_TOUCH_MAPPED_MEMORY_DEFAULT;\n        }\n\n        /**\n         * Get the configured client name.\n         *\n         * @return specified client name or empty string if not set.\n         * @see #CLIENT_NAME_PROP_NAME\n         */\n        @Config\n        public static String clientName()\n        {\n            return getProperty(CLIENT_NAME_PROP_NAME, \"\");\n        }\n\n        /**\n         * Limit to the number of characters allowed in the client name.\n         */\n        public static final int MAX_CLIENT_NAME_LENGTH = 100;\n    }\n\n    /**\n     * Provides a means to override configuration for an {@link Aeron} client via the\n     * {@link Aeron#connect(Aeron.Context)} method and its overloads. It gives applications some control over\n     * the interactions with the Aeron Media Driver. It can also set up error handling as well as application\n     * callbacks for image information from the Media Driver.\n     * <p>\n     * A number of the properties are for testing and should not be set by end users.\n     * <p>\n     * <b>Note:</b> Do not reuse instances of the context across different {@link Aeron} clients.\n     * <p>\n     * The context will be owned by {@link ClientConductor} after a successful\n     * {@link Aeron#connect(Context)} and closed via {@link Aeron#close()}.\n     */\n    public static final class Context extends CommonContext\n    {\n        private long clientId;\n        private String clientName = Configuration.clientName();\n        private boolean useConductorAgentInvoker = false;\n        private boolean preTouchMappedMemory = Configuration.preTouchMappedMemory();\n        private AgentInvoker driverAgentInvoker;\n        private Lock clientLock;\n        private EpochClock epochClock;\n        private NanoClock nanoClock;\n        private IdleStrategy idleStrategy;\n        private IdleStrategy awaitingIdleStrategy;\n        private CopyBroadcastReceiver toClientBuffer;\n        private RingBuffer toDriverBuffer;\n        private DriverProxy driverProxy;\n        private ByteBuffer cncByteBuffer;\n        private AtomicBuffer cncMetaDataBuffer;\n        private LogBuffersFactory logBuffersFactory;\n        private ErrorHandler errorHandler;\n        private ErrorHandler subscriberErrorHandler;\n        private AvailableImageHandler availableImageHandler;\n        private UnavailableImageHandler unavailableImageHandler;\n        private AvailableCounterHandler availableCounterHandler;\n        private UnavailableCounterHandler unavailableCounterHandler;\n        private PublicationErrorFrameHandler publicationErrorFrameHandler = PublicationErrorFrameHandler.NO_OP;\n        private Runnable closeHandler;\n        private long keepAliveIntervalNs = Configuration.KEEPALIVE_INTERVAL_NS;\n        private long interServiceTimeoutNs;\n        private long idleSleepDurationNs = Configuration.idleSleepDurationNs();\n        private long resourceLingerDurationNs = Configuration.resourceLingerDurationNs();\n        private long closeLingerDurationNs = Configuration.closeLingerDurationNs();\n        private int filePageSize;\n\n        private ThreadFactory threadFactory = Thread::new;\n\n        /**\n         * Construct a Context using default values and loading from system properties.\n         */\n        public Context()\n        {\n        }\n\n        /**\n         * Perform a shallow copy of the object.\n         *\n         * @return a shallow copy of the object.\n         */\n        public Context clone()\n        {\n            return (Context)super.clone();\n        }\n\n        /**\n         * This is called automatically by {@link Aeron#connect(Aeron.Context)} and its overloads.\n         * There is no need to call it from a client application. It is responsible for providing default\n         * values for options that are not individually changed through field setters.\n         *\n         * @return this for a fluent API.\n         */\n        @SuppressWarnings(\"checkstyle:methodlength\")\n        public Context conclude()\n        {\n            super.conclude();\n\n            if (null == clientLock)\n            {\n                clientLock = new ReentrantLock();\n            }\n            else if (clientLock instanceof NoOpLock && !useConductorAgentInvoker)\n            {\n                throw new ConfigurationException(\n                    \"Must use Aeron.Context.useConductorAgentInvoker(true) when Aeron.Context.clientLock(...) \" +\n                    \"is using a NoOpLock\");\n            }\n\n            if (null != driverAgentInvoker && !useConductorAgentInvoker)\n            {\n                throw new ConfigurationException(\n                    \"Must use Aeron.Context.useConductorAgentInvoker(true) when Aeron.Context.driverAgentInvoker() \" +\n                    \"is set\");\n            }\n\n            if (clientName.length() > MAX_CLIENT_NAME_LENGTH)\n            {\n                throw new ConfigurationException(\"clientName length must <= \" + MAX_CLIENT_NAME_LENGTH);\n            }\n\n            if (null == epochClock)\n            {\n                epochClock = SystemEpochClock.INSTANCE;\n            }\n\n            if (null == nanoClock)\n            {\n                nanoClock = SystemNanoClock.INSTANCE;\n            }\n\n            if (idleSleepDurationNs < 0 || idleSleepDurationNs > TimeUnit.SECONDS.toNanos(1))\n            {\n                throw new ConfigurationException(\"Invalid idle sleep duration: \" + idleSleepDurationNs + \"ns\");\n            }\n\n            if (null == idleStrategy)\n            {\n                idleStrategy = new SleepingMillisIdleStrategy(TimeUnit.NANOSECONDS.toMillis(idleSleepDurationNs));\n            }\n\n            if (null == awaitingIdleStrategy)\n            {\n                awaitingIdleStrategy = new SleepingMillisIdleStrategy(Configuration.AWAITING_IDLE_SLEEP_MS);\n            }\n\n            connectToDriver();\n            filePageSize = driverFilePageSize(cncMetaDataBuffer);\n\n            interServiceTimeoutNs = CncFileDescriptor.clientLivenessTimeoutNs(cncMetaDataBuffer);\n            if (interServiceTimeoutNs <= keepAliveIntervalNs)\n            {\n                throw new ConfigurationException(\"interServiceTimeoutNs=\" + interServiceTimeoutNs +\n                    \" <= keepAliveIntervalNs=\" + keepAliveIntervalNs);\n            }\n\n            if (null == toDriverBuffer)\n            {\n                toDriverBuffer = new ManyToOneRingBuffer(\n                    CncFileDescriptor.createToDriverBuffer(cncByteBuffer, cncMetaDataBuffer));\n            }\n\n            if (null == toClientBuffer)\n            {\n                toClientBuffer = new CopyBroadcastReceiver(new BroadcastReceiver(\n                    CncFileDescriptor.createToClientsBuffer(cncByteBuffer, cncMetaDataBuffer)),\n                    new ExpandableArrayBuffer(CopyBroadcastReceiver.SCRATCH_BUFFER_LENGTH));\n            }\n\n            if (countersMetaDataBuffer() == null)\n            {\n                countersMetaDataBuffer(\n                    CncFileDescriptor.createCountersMetaDataBuffer(cncByteBuffer, cncMetaDataBuffer));\n            }\n\n            if (countersValuesBuffer() == null)\n            {\n                countersValuesBuffer(CncFileDescriptor.createCountersValuesBuffer(cncByteBuffer, cncMetaDataBuffer));\n            }\n\n            if (null == logBuffersFactory)\n            {\n                logBuffersFactory = new MappedLogBuffersFactory();\n            }\n\n            if (null == errorHandler)\n            {\n                errorHandler = Configuration.DEFAULT_ERROR_HANDLER;\n            }\n\n            if (null == subscriberErrorHandler)\n            {\n                subscriberErrorHandler = errorHandler;\n            }\n\n            if (null == driverProxy)\n            {\n                clientId = toDriverBuffer.nextCorrelationId();\n                driverProxy = new DriverProxy(toDriverBuffer, clientId);\n            }\n\n            return this;\n        }\n\n        /**\n         * Get the client identity that has been allocated for communicating with the media driver.\n         *\n         * @return the client identity that has been allocated for communicating with the media driver.\n         */\n        public long clientId()\n        {\n            return clientId;\n        }\n\n        /**\n         * Sets the name used to identify this client among other clients connected to the media driver.\n         *\n         * @param clientName to use.\n         * @return this for a fluent API.\n         * @see Configuration#CLIENT_NAME_PROP_NAME\n         * @since 1.44.0\n         */\n        public Context clientName(final String clientName)\n        {\n            this.clientName = Strings.isEmpty(clientName) ? \"\" : clientName;\n            return this;\n        }\n\n        /**\n         * Returns the name of this client.\n         *\n         * @return name of this client or empty String if not configured.\n         * @see Configuration#CLIENT_NAME_PROP_NAME\n         * @since 1.44.0\n         */\n        public String clientName()\n        {\n            return clientName;\n        }\n\n        /**\n         * Should an {@link AgentInvoker} be used for running the {@link ClientConductor} rather than run it on\n         * a thread with a {@link AgentRunner}.\n         *\n         * @param useConductorAgentInvoker use {@link AgentInvoker} be used for running the {@link ClientConductor}?\n         * @return this for a fluent API.\n         */\n        public Context useConductorAgentInvoker(final boolean useConductorAgentInvoker)\n        {\n            this.useConductorAgentInvoker = useConductorAgentInvoker;\n            return this;\n        }\n\n        /**\n         * Should an {@link AgentInvoker} be used for running the {@link ClientConductor} rather than run it on\n         * a thread with a {@link AgentRunner}.\n         *\n         * @return true if the {@link ClientConductor} will be run with an {@link AgentInvoker} otherwise false.\n         */\n        public boolean useConductorAgentInvoker()\n        {\n            return useConductorAgentInvoker;\n        }\n\n        /**\n         * Should mapped-memory be pre-touched to avoid soft page faults.\n         *\n         * @param preTouchMappedMemory true if mapped-memory should be pre-touched otherwise false.\n         * @return this for a fluent API.\n         * @see Configuration#PRE_TOUCH_MAPPED_MEMORY_PROP_NAME\n         */\n        public Context preTouchMappedMemory(final boolean preTouchMappedMemory)\n        {\n            this.preTouchMappedMemory = preTouchMappedMemory;\n            return this;\n        }\n\n        /**\n         * Should mapped-memory be pre-touched to avoid soft page faults.\n         *\n         * @return true if mapped-memory should be pre-touched otherwise false.\n         * @see Configuration#PRE_TOUCH_MAPPED_MEMORY_PROP_NAME\n         */\n        public boolean preTouchMappedMemory()\n        {\n            return preTouchMappedMemory;\n        }\n\n        /**\n         * Set the {@link AgentInvoker} for the Media Driver to be used while awaiting a synchronous response.\n         * <p>\n         * Useful for when running on a low thread count scenario.\n         *\n         * @param driverAgentInvoker to be invoked while awaiting a response in the client.\n         * @return this for a fluent API.\n         */\n        public Context driverAgentInvoker(final AgentInvoker driverAgentInvoker)\n        {\n            this.driverAgentInvoker = driverAgentInvoker;\n            return this;\n        }\n\n        /**\n         * Get the {@link AgentInvoker} that is used to run the Media Driver while awaiting a synchronous response.\n         *\n         * @return the {@link AgentInvoker} that is used for running the Media Driver.\n         */\n        public AgentInvoker driverAgentInvoker()\n        {\n            return driverAgentInvoker;\n        }\n\n        /**\n         * The {@link Lock} that is used to provide mutual exclusion in the Aeron client.\n         * <p>\n         * If the {@link #useConductorAgentInvoker()} is set and only one thread accesses the client\n         * then the lock can be set to {@link NoOpLock} to elide the lock overhead.\n         *\n         * @param lock that is used to provide mutual exclusion in the Aeron client.\n         * @return this for a fluent API.\n         */\n        public Context clientLock(final Lock lock)\n        {\n            clientLock = lock;\n            return this;\n        }\n\n        /**\n         * Get the {@link Lock} that is used to provide mutual exclusion in the Aeron client.\n         *\n         * @return the {@link Lock} that is used to provide mutual exclusion in the Aeron client.\n         */\n        public Lock clientLock()\n        {\n            return clientLock;\n        }\n\n        /**\n         * Set the {@link EpochClock} to be used for tracking wall clock time when interacting with the driver.\n         *\n         * @param clock {@link EpochClock} to be used for tracking wall clock time when interacting with the driver.\n         * @return this for a fluent API.\n         */\n        public Context epochClock(final EpochClock clock)\n        {\n            this.epochClock = clock;\n            return this;\n        }\n\n        /**\n         * Get the {@link EpochClock} used by the client for the epoch time in milliseconds.\n         *\n         * @return the {@link EpochClock} used by the client for the epoch time in milliseconds.\n         */\n        public EpochClock epochClock()\n        {\n            return epochClock;\n        }\n\n        /**\n         * Set the {@link NanoClock} to be used for tracking high resolution time.\n         *\n         * @param clock {@link NanoClock} to be used for tracking high resolution time.\n         * @return this for a fluent API.\n         */\n        public Context nanoClock(final NanoClock clock)\n        {\n            this.nanoClock = clock;\n            return this;\n        }\n\n        /**\n         * Get the {@link NanoClock} to be used for tracking high resolution time.\n         *\n         * @return the {@link NanoClock} to be used for tracking high resolution time.\n         */\n        public NanoClock nanoClock()\n        {\n            return nanoClock;\n        }\n\n        /**\n         * Provides an {@link IdleStrategy} for the thread responsible for the client duty cycle.\n         *\n         * @param idleStrategy Thread idle strategy for the client duty cycle.\n         * @return this for a fluent API.\n         */\n        public Context idleStrategy(final IdleStrategy idleStrategy)\n        {\n            this.idleStrategy = idleStrategy;\n            return this;\n        }\n\n        /**\n         * Get the {@link IdleStrategy} employed by the client for the client duty cycle.\n         *\n         * @return the {@link IdleStrategy} employed by the client for the client duty cycle.\n         */\n        public IdleStrategy idleStrategy()\n        {\n            return idleStrategy;\n        }\n\n        /**\n         * Provides an {@link IdleStrategy} to be used when awaiting a response from the Media Driver.\n         *\n         * @param idleStrategy Thread idle strategy for awaiting a response from the Media Driver.\n         * @return this for a fluent API.\n         */\n        public Context awaitingIdleStrategy(final IdleStrategy idleStrategy)\n        {\n            this.awaitingIdleStrategy = idleStrategy;\n            return this;\n        }\n\n        /**\n         * The {@link IdleStrategy} to be used when awaiting a response from the Media Driver.\n         * <p>\n         * This can be changed to a {@link BusySpinIdleStrategy} or {@link YieldingIdleStrategy} for lower response\n         * time, especially for adding counters or releasing resources, at the expense of CPU usage.\n         *\n         * @return the {@link IdleStrategy} to be used when awaiting a response from the Media Driver.\n         */\n        public IdleStrategy awaitingIdleStrategy()\n        {\n            return awaitingIdleStrategy;\n        }\n\n        /**\n         * This method is used for testing and debugging.\n         *\n         * @param toClientBuffer Injected CopyBroadcastReceiver\n         * @return this for a fluent API.\n         */\n        Context toClientBuffer(final CopyBroadcastReceiver toClientBuffer)\n        {\n            this.toClientBuffer = toClientBuffer;\n            return this;\n        }\n\n        /**\n         * The buffer used for communicating from the media driver to the Aeron client.\n         *\n         * @return the buffer used for communicating from the media driver to the Aeron client.\n         */\n        public CopyBroadcastReceiver toClientBuffer()\n        {\n            return toClientBuffer;\n        }\n\n        /**\n         * Get the {@link RingBuffer} used for sending commands to the media driver.\n         *\n         * @return the {@link RingBuffer} used for sending commands to the media driver.\n         */\n        public RingBuffer toDriverBuffer()\n        {\n            return toDriverBuffer;\n        }\n\n        /**\n         * Set the proxy for communicating with the media driver.\n         *\n         * @param driverProxy for communicating with the media driver.\n         * @return this for a fluent API.\n         */\n        Context driverProxy(final DriverProxy driverProxy)\n        {\n            this.driverProxy = driverProxy;\n            return this;\n        }\n\n        /**\n         * Get the proxy for communicating with the media driver.\n         *\n         * @return the proxy for communicating with the media driver.\n         */\n        public DriverProxy driverProxy()\n        {\n            return driverProxy;\n        }\n\n        /**\n         * This method is used for testing and debugging.\n         *\n         * @param logBuffersFactory Injected LogBuffersFactory\n         * @return this for a fluent API.\n         */\n        Context logBuffersFactory(final LogBuffersFactory logBuffersFactory)\n        {\n            this.logBuffersFactory = logBuffersFactory;\n            return this;\n        }\n\n        /**\n         * Get the factory for making log buffers.\n         *\n         * @return the factory for making log buffers.\n         */\n        LogBuffersFactory logBuffersFactory()\n        {\n            return logBuffersFactory;\n        }\n\n        /**\n         * Handle Aeron exceptions in a callback method. The default behavior is defined by\n         * {@link Configuration#DEFAULT_ERROR_HANDLER}. This is the error handler which will be used if an error occurs\n         * during the callback for poll operations such as {@link Subscription#poll(FragmentHandler, int)}.\n         * <p>\n         * The error handler can be reset after {@link Aeron#connect()} and the latest version will always be used\n         * so that the bootstrapping process can be performed such as replacing the default one with a\n         * {@link CountedErrorHandler}.\n         *\n         * @param errorHandler Method to handle objects of type Throwable.\n         * @return this for a fluent API.\n         * @see io.aeron.exceptions.DriverTimeoutException\n         * @see io.aeron.exceptions.RegistrationException\n         */\n        public Context errorHandler(final ErrorHandler errorHandler)\n        {\n            this.errorHandler = errorHandler;\n            return this;\n        }\n\n        /**\n         * Get the error handler that will be called for errors reported back from the media driver or during poll\n         * operations.\n         *\n         * @return the error handler that will be called for errors reported back from the media driver or poll\n         * operations.\n         */\n        public ErrorHandler errorHandler()\n        {\n            return errorHandler;\n        }\n\n        /**\n         * The error handler which will be used if an error occurs during the callback for poll operations such as\n         * {@link Subscription#poll(FragmentHandler, int)}. The default will be {@link #errorHandler()} if not set.\n         *\n         * @param errorHandler Method to handle objects of type Throwable.\n         * @return this for a fluent API.\n         * @see io.aeron.exceptions.DriverTimeoutException\n         * @see io.aeron.exceptions.RegistrationException\n         */\n        public Context subscriberErrorHandler(final ErrorHandler errorHandler)\n        {\n            this.subscriberErrorHandler = errorHandler;\n            return this;\n        }\n\n        /**\n         * This is the error handler which will be used if an error occurs during the callback for poll operations\n         * such as {@link Subscription#poll(FragmentHandler, int)}. The default will be {@link #errorHandler()} if not\n         * set. To have {@link Subscription#poll(FragmentHandler, int)} not delegate then set with\n         * {@link RethrowingErrorHandler}.\n         *\n         * @return the error handler that will be called for errors reported back from the media driver.\n         */\n        public ErrorHandler subscriberErrorHandler()\n        {\n            return subscriberErrorHandler;\n        }\n\n        /**\n         * Set up a default callback for when an {@link Image} is available.\n         *\n         * @param handler Callback method for handling available image notifications.\n         * @return this for a fluent API.\n         */\n        public Context availableImageHandler(final AvailableImageHandler handler)\n        {\n            this.availableImageHandler = handler;\n            return this;\n        }\n\n        /**\n         * Get the default callback handler for notifying when {@link Image}s become available.\n         *\n         * @return the callback handler for notifying when {@link Image}s become available.\n         */\n        public AvailableImageHandler availableImageHandler()\n        {\n            return availableImageHandler;\n        }\n\n        /**\n         * Set up a default callback for when an {@link Image} is unavailable.\n         *\n         * @param handler Callback method for handling unavailable image notifications.\n         * @return this for a fluent API.\n         */\n        public Context unavailableImageHandler(final UnavailableImageHandler handler)\n        {\n            this.unavailableImageHandler = handler;\n            return this;\n        }\n\n        /**\n         * Get the callback handler for when an {@link Image} is unavailable.\n         *\n         * @return the callback handler for when an {@link Image} is unavailable.\n         */\n        public UnavailableImageHandler unavailableImageHandler()\n        {\n            return unavailableImageHandler;\n        }\n\n        /**\n         * Set up a callback for when a counter is available. This will be added to the list first before\n         * additional handler are added with {@link Aeron#addAvailableCounterHandler(AvailableCounterHandler)}.\n         *\n         * @param handler to be called for handling available counter notifications.\n         * @return this for a fluent API.\n         */\n        public Context availableCounterHandler(final AvailableCounterHandler handler)\n        {\n            this.availableCounterHandler = handler;\n            return this;\n        }\n\n        /**\n         * Get the callback handler for when a counter is available.\n         *\n         * @return the callback handler for when a counter is available.\n         */\n        public AvailableCounterHandler availableCounterHandler()\n        {\n            return availableCounterHandler;\n        }\n\n        /**\n         * Set up a callback for when a counter is unavailable. This will be added to the list first before\n         * additional handler are added with {@link Aeron#addUnavailableCounterHandler(UnavailableCounterHandler)}.\n         *\n         * @param handler to be called for handling unavailable counter notifications.\n         * @return this for a fluent API.\n         */\n        public Context unavailableCounterHandler(final UnavailableCounterHandler handler)\n        {\n            this.unavailableCounterHandler = handler;\n            return this;\n        }\n\n        /**\n         * Get the callback handler for when a counter is unavailable.\n         *\n         * @return the callback handler for when a counter is unavailable.\n         */\n        public UnavailableCounterHandler unavailableCounterHandler()\n        {\n            return unavailableCounterHandler;\n        }\n\n        /**\n         * Set a {@link Runnable} that is called when the client is closed by timeout or normal means.\n         * <p>\n         * It is not safe to call any API functions from any threads after this hook is called. In addition, any\n         * in flight calls may still cause faults. Thus treating this as a hard error and terminate the process in\n         * this hook as soon as possible is recommended.\n         *\n         * @param handler that is called when the client is closed.\n         * @return this for a fluent API.\n         */\n        public Context closeHandler(final Runnable handler)\n        {\n            this.closeHandler = handler;\n            return this;\n        }\n\n        /**\n         * Get the {@link Runnable} that is called when the client is closed by timeout or normal means.\n         *\n         * @return the {@link Runnable} that is called when the client is closed.\n         */\n        public Runnable closeHandler()\n        {\n            return closeHandler;\n        }\n\n        /**\n         * Set the interval in nanoseconds for which the client will perform keep-alive operations.\n         *\n         * @param value the interval in nanoseconds for which the client will perform keep-alive operations.\n         * @return this for a fluent API.\n         */\n        public Context keepAliveIntervalNs(final long value)\n        {\n            keepAliveIntervalNs = value;\n            return this;\n        }\n\n        /**\n         * Get the interval in nanoseconds for which the client will perform keep-alive operations.\n         *\n         * @return the interval in nanoseconds for which the client will perform keep-alive operations.\n         */\n        public long keepAliveIntervalNs()\n        {\n            return keepAliveIntervalNs;\n        }\n\n        /**\n         * Set the amount of time, in milliseconds, that this client will wait until it determines the\n         * Media Driver is unavailable. When this happens a\n         * {@link io.aeron.exceptions.DriverTimeoutException} will be generated for the error handler.\n         *\n         * @param value Number of milliseconds.\n         * @return this for a fluent API.\n         * @see #errorHandler(ErrorHandler)\n         */\n        public Context driverTimeoutMs(final long value)\n        {\n            super.driverTimeoutMs(value);\n            return this;\n        }\n\n        /**\n         * Set the timeout between service calls the to {@link ClientConductor} duty cycles in nanoseconds.\n         * <p>\n         * <b>Note:</b> the method is used for testing only.\n         *\n         * @param interServiceTimeout the timeout (ns) between service calls the to {@link ClientConductor} duty cycle.\n         * @return this for a fluent API.\n         */\n        Context interServiceTimeoutNs(final long interServiceTimeout)\n        {\n            this.interServiceTimeoutNs = interServiceTimeout;\n            return this;\n        }\n\n        /**\n         * Return the timeout between service calls to the duty cycle for the client.\n         * <p>\n         * When exceeded, {@link #errorHandler()} will be called and the active {@link Publication}s, {@link Image}s,\n         * and {@link Counter}s will be closed.\n         * <p>\n         * This value is controlled by the driver and included in the CnC file. It can be configured by adjusting\n         * the {@code aeron.client.liveness.timeout} property set on the media driver.\n         *\n         * @return the timeout in nanoseconds between service calls as an allowed maximum.\n         */\n        public long interServiceTimeoutNs()\n        {\n            return CommonContext.checkDebugTimeout(interServiceTimeoutNs, TimeUnit.NANOSECONDS);\n        }\n\n        /**\n         * Duration to sleep when conductor is idle.\n         *\n         * @param idleSleepDurationNs to sleep when conductor is idle.\n         * @return this for a fluent API.\n         * @see Configuration#IDLE_SLEEP_DURATION_PROP_NAME\n         */\n        public Context idleSleepDurationNs(final long idleSleepDurationNs)\n        {\n            this.idleSleepDurationNs = idleSleepDurationNs;\n            return this;\n        }\n\n        /**\n         * Duration to sleep when conductor is idle.\n         *\n         * @return duration in nanoseconds to sleep when conductor is idle.\n         * @see Configuration#IDLE_SLEEP_DURATION_PROP_NAME\n         */\n        public long idleSleepDurationNs()\n        {\n            return idleSleepDurationNs;\n        }\n\n        /**\n         * Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources\n         * such as memory mapped files.\n         *\n         * @param resourceLingerDurationNs to wait before deleting an expired resource.\n         * @return this for a fluent API.\n         * @see Configuration#RESOURCE_LINGER_DURATION_PROP_NAME\n         */\n        public Context resourceLingerDurationNs(final long resourceLingerDurationNs)\n        {\n            this.resourceLingerDurationNs = resourceLingerDurationNs;\n            return this;\n        }\n\n        /**\n         * Duration to wait while lingering an entity such as an {@link Image} before deleting underlying resources\n         * such as memory mapped files.\n         *\n         * @return duration in nanoseconds to wait before deleting an expired resource.\n         * @see Configuration#RESOURCE_LINGER_DURATION_PROP_NAME\n         */\n        public long resourceLingerDurationNs()\n        {\n            return resourceLingerDurationNs;\n        }\n\n        /**\n         * Duration to linger on closing to allow publishers and subscribers time to notice closed resources.\n         * <p>\n         * This value can be increased from the default to a few seconds to better cope with long GC pauses\n         * or resource starved environments. Issues could manifest as seg faults using files after they have\n         * been unmapped from publishers or subscribers not noticing the close in a timely fashion.\n         *\n         * @param closeLingerDurationNs to wait before deleting resources when closing.\n         * @return this for a fluent API.\n         * @see Configuration#CLOSE_LINGER_DURATION_PROP_NAME\n         */\n        public Context closeLingerDurationNs(final long closeLingerDurationNs)\n        {\n            this.closeLingerDurationNs = closeLingerDurationNs;\n            return this;\n        }\n\n        /**\n         * Duration to linger on closing to allow publishers and subscribers time to notice closed resources.\n         * <p>\n         * This value can be increased from the default to a few seconds to better cope with long GC pauses\n         * or resource starved environments. Issues could manifest as seg faults using files after they have\n         * been unmapped from publishers or subscribers not noticing the close in a timely fashion.\n         *\n         * @return duration in nanoseconds to wait before deleting resources when closing.\n         * @see Configuration#CLOSE_LINGER_DURATION_PROP_NAME\n         */\n        public long closeLingerDurationNs()\n        {\n            return closeLingerDurationNs;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public Context aeronDirectoryName(final String dirName)\n        {\n            super.aeronDirectoryName(dirName);\n            return this;\n        }\n\n        /**\n         * Specify the thread factory to use when starting the conductor thread.\n         *\n         * @param threadFactory thread factory to construct the thread.\n         * @return this for a fluent API.\n         */\n        public Context threadFactory(final ThreadFactory threadFactory)\n        {\n            this.threadFactory = threadFactory;\n            return this;\n        }\n\n        /**\n         * The thread factory to be used to construct the conductor thread.\n         *\n         * @return the specified thread factory or {@link Thread#Thread(Runnable)} if none is provided.\n         */\n        public ThreadFactory threadFactory()\n        {\n            return threadFactory;\n        }\n\n        /**\n         * Set the handler to receive error frames that have been received by the local driver for publications added by\n         * this client.\n         *\n         * @param publicationErrorFrameHandler to be called back when an error frame is received.\n         * @return this for a fluent API.\n         * @since 1.47.0\n         */\n        public Context publicationErrorFrameHandler(\n            final PublicationErrorFrameHandler publicationErrorFrameHandler)\n        {\n            this.publicationErrorFrameHandler = Objects.requireNonNull(publicationErrorFrameHandler);\n            return this;\n        }\n\n        /**\n         * Get the handler to receive error frames that have been received by the local driver for publications added by\n         * this client.\n         *\n         * @return the {@link PublicationErrorFrameHandler} to call back on to.\n         * @since 1.47.0\n         */\n        public PublicationErrorFrameHandler publicationErrorFrameHandler()\n        {\n            return this.publicationErrorFrameHandler;\n        }\n\n        /**\n         * Get file page size from running media driver.\n         *\n         * @return file page size or zero (if not connected to the media driver).\n         * @since 1.48.0\n         */\n        public int filePageSize()\n        {\n            return filePageSize;\n        }\n\n        /**\n         * Clean up all resources that the client uses to communicate with the Media Driver.\n         */\n        public void close()\n        {\n            BufferUtil.free(cncByteBuffer);\n            this.cncByteBuffer = null;\n            super.close();\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"Aeron.Context\" +\n                \"\\n{\" +\n                \"\\n    isConcluded=\" + isConcluded() +\n                \"\\n    aeronDirectory=\" + aeronDirectory() +\n                \"\\n    aeronDirectoryName='\" + aeronDirectoryName() + '\\'' +\n                \"\\n    cncFile=\" + cncFile() +\n                \"\\n    countersMetaDataBuffer=\" + countersMetaDataBuffer() +\n                \"\\n    countersValuesBuffer=\" + countersValuesBuffer() +\n                \"\\n    driverTimeoutMs=\" + driverTimeoutMs() +\n                \"\\n    clientId=\" + clientId +\n                \"\\n    clientName=\" + clientName +\n                \"\\n    useConductorAgentInvoker=\" + useConductorAgentInvoker +\n                \"\\n    preTouchMappedMemory=\" + preTouchMappedMemory +\n                \"\\n    driverAgentInvoker=\" + driverAgentInvoker +\n                \"\\n    clientLock=\" + clientLock +\n                \"\\n    epochClock=\" + epochClock +\n                \"\\n    nanoClock=\" + nanoClock +\n                \"\\n    idleStrategy=\" + idleStrategy +\n                \"\\n    awaitingIdleStrategy=\" + awaitingIdleStrategy +\n                \"\\n    toClientBuffer=\" + toClientBuffer +\n                \"\\n    toDriverBuffer=\" + toDriverBuffer +\n                \"\\n    driverProxy=\" + driverProxy +\n                \"\\n    cncByteBuffer=\" + cncByteBuffer +\n                \"\\n    cncMetaDataBuffer=\" + cncMetaDataBuffer +\n                \"\\n    logBuffersFactory=\" + logBuffersFactory +\n                \"\\n    errorHandler=\" + errorHandler +\n                \"\\n    subscriberErrorHandler=\" + subscriberErrorHandler +\n                \"\\n    availableImageHandler=\" + availableImageHandler +\n                \"\\n    unavailableImageHandler=\" + unavailableImageHandler +\n                \"\\n    availableCounterHandler=\" + availableCounterHandler +\n                \"\\n    unavailableCounterHandler=\" + unavailableCounterHandler +\n                \"\\n    closeHandler=\" + closeHandler +\n                \"\\n    keepAliveIntervalNs=\" + keepAliveIntervalNs +\n                \"\\n    interServiceTimeoutNs=\" + interServiceTimeoutNs +\n                \"\\n    resourceLingerDurationNs=\" + resourceLingerDurationNs +\n                \"\\n    closeLingerDurationNs=\" + closeLingerDurationNs +\n                \"\\n    threadFactory=\" + threadFactory +\n                \"\\n}\";\n        }\n\n        private void connectToDriver()\n        {\n            final EpochClock clock = epochClock;\n            final long deadlineMs = clock.time() + driverTimeoutMs();\n            final File cncFile = cncFile();\n\n            while (null == toDriverBuffer)\n            {\n                cncMetaDataBuffer = awaitCncFileCreation(cncFile, clock, deadlineMs);\n                cncByteBuffer = cncMetaDataBuffer.byteBuffer();\n\n                if (!CncFileDescriptor.isCncFileLengthSufficient(cncMetaDataBuffer, cncByteBuffer.capacity()))\n                {\n                    BufferUtil.free(cncByteBuffer);\n                    cncByteBuffer = null;\n                    cncMetaDataBuffer = null;\n\n                    sleep(Configuration.AWAITING_IDLE_SLEEP_MS);\n                    continue;\n                }\n\n                final ManyToOneRingBuffer ringBuffer = new ManyToOneRingBuffer(\n                    CncFileDescriptor.createToDriverBuffer(cncByteBuffer, cncMetaDataBuffer));\n\n                while (0 == ringBuffer.consumerHeartbeatTime())\n                {\n                    if (clock.time() > deadlineMs)\n                    {\n                        throw new DriverTimeoutException(\"no driver heartbeat detected\");\n                    }\n\n                    sleep(Configuration.AWAITING_IDLE_SLEEP_MS);\n                }\n\n                final long timeMs = clock.time();\n                if (ringBuffer.consumerHeartbeatTime() < (timeMs - driverTimeoutMs()))\n                {\n                    if (timeMs > deadlineMs)\n                    {\n                        throw new DriverTimeoutException(\"no driver heartbeat detected\");\n                    }\n\n                    BufferUtil.free(cncByteBuffer);\n                    cncByteBuffer = null;\n                    cncMetaDataBuffer = null;\n\n                    sleep(100);\n                    continue;\n                }\n\n                toDriverBuffer = ringBuffer;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/AeronCounters.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.counter.AeronCounter;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.util.Objects;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.concurrent.status.CountersReader.LABEL_OFFSET;\nimport static org.agrona.concurrent.status.CountersReader.MAX_LABEL_LENGTH;\nimport static org.agrona.concurrent.status.CountersReader.METADATA_LENGTH;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_ALLOCATED;\nimport static org.agrona.concurrent.status.CountersReader.REFERENCE_ID_OFFSET;\nimport static org.agrona.concurrent.status.CountersReader.counterOffset;\nimport static org.agrona.concurrent.status.CountersReader.metaDataOffset;\n\n/**\n * This class serves as a registry for all counter type IDs used by Aeron.\n * <p>\n * Type IDs less than 1000 are reserved for Aeron use. Any custom counters should use a typeId of 1000 or higher.\n * <p>\n * Aeron uses the following specific ranges:\n * <ul>\n *     <li>{@code 0 - 99}: for client/driver counters.</li>\n *     <li>{@code 100 - 199}: for archive counters.</li>\n *     <li>{@code 200 - 299}: for cluster counters.</li>\n * </ul>\n */\npublic final class AeronCounters\n{\n    // System counter IDs to be accessed outside the driver.\n    /**\n     * Counter id for bytes sent over the network.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_BYTES_SENT = 0;\n\n    /**\n     * Counter id for bytes sent over the network.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_BYTES_RECEIVED = 1;\n\n    /**\n     * Counter id for failed offers to the receiver proxy.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_RECEIVER_PROXY_FAILS = 2;\n\n    /**\n     * Counter id for failed offers to the sender proxy.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_SENDER_PROXY_FAILS = 3;\n\n    /**\n     * Counter id for failed offers to the conductor proxy.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_CONDUCTOR_PROXY_FAILS = 4;\n\n    /**\n     * Counter id for NAKs sent back to senders requesting re-transmits.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_NAK_MESSAGES_SENT = 5;\n\n    /**\n     * Counter id for NAKs received from receivers requesting re-transmits.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_NAK_MESSAGES_RECEIVED = 6;\n\n    /**\n     * Counter id for status messages sent back to senders for flow control.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_STATUS_MESSAGES_SENT = 7;\n\n    /**\n     * Counter id for status messages received from receivers for flow control.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_STATUS_MESSAGES_RECEIVED = 8;\n\n    /**\n     * Counter id for heartbeat data frames sent to indicate liveness in the absence of data to send.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_HEARTBEATS_SENT = 9;\n\n    /**\n     * Counter id for heartbeat data frames received to indicate liveness in the absence of data to send.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_HEARTBEATS_RECEIVED = 10;\n\n    /**\n     * Counter id for data packets re-transmitted as a result of NAKs.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_RETRANSMITS_SENT = 11;\n\n    /**\n     * Counter id for packets received which under-run the current flow control window for images.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_FLOW_CONTROL_UNDER_RUNS = 12;\n\n    /**\n     * Counter id for packets received which over-run the current flow control window for images.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_FLOW_CONTROL_OVER_RUNS = 13;\n\n    /**\n     * Counter id for invalid packets received.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_INVALID_PACKETS = 14;\n\n    /**\n     * Counter id for errors observed by the driver and an indication to read the distinct error log.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_ERRORS = 15;\n\n    /**\n     * Counter id for socket send operation which resulted in less than the packet length being sent.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_SHORT_SENDS = 16;\n\n    /**\n     * Counter id for attempts to free log buffers no longer required by the driver which as still held by clients.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_FREE_FAILS = 17;\n\n    /**\n     * Counter id for the times a sender has entered the state of being back-pressured when it could have sent faster.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_SENDER_FLOW_CONTROL_LIMITS = 18;\n\n    /**\n     * Counter id for the times a publication has been unblocked after a client failed to complete an offer within a\n     * timeout.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_UNBLOCKED_PUBLICATIONS = 19;\n\n    /**\n     * Counter id for the times a command has been unblocked after a client failed to complete an offer within a\n     * timeout.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_UNBLOCKED_COMMANDS = 20;\n\n    /**\n     * Counter id for the times the channel endpoint detected a possible TTL asymmetry between its config and new\n     * connection.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_POSSIBLE_TTL_ASYMMETRY = 21;\n\n    /**\n     * Counter id for status of the {@link org.agrona.concurrent.ControllableIdleStrategy} if configured.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_CONTROLLABLE_IDLE_STRATEGY = 22;\n\n    /**\n     * Counter id for the times a loss gap has been filled when NAKs have been disabled.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_LOSS_GAP_FILLS = 23;\n\n    /**\n     * Counter id for the Aeron clients that have timed out without a graceful close.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_CLIENT_TIMEOUTS = 24;\n\n    /**\n     * Counter id for the times a connection endpoint has been re-resolved resulting in a change.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_RESOLUTION_CHANGES = 25;\n\n    /**\n     * Counter id for the maximum time spent by the conductor between work cycles.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_CONDUCTOR_MAX_CYCLE_TIME = 26;\n\n    /**\n     * Counter id for the number of times the cycle time threshold has been exceeded by the conductor in its work cycle.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED = 27;\n\n    /**\n     * Counter id for the maximum time spent by the sender between work cycles.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_SENDER_MAX_CYCLE_TIME = 28;\n\n    /**\n     * Counter id for the number of times the cycle time threshold has been exceeded by the sender in its work cycle.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED = 29;\n\n    /**\n     * Counter id for the maximum time spent by the receiver between work cycles.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_RECEIVER_MAX_CYCLE_TIME = 30;\n\n    /**\n     * Counter id for the number of times the cycle time threshold has been exceeded by the receiver in its work cycle.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED = 31;\n\n    /**\n     * Counter id for the maximum time spent by the NameResolver in one of its operations.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_NAME_RESOLVER_MAX_TIME = 32;\n\n    /**\n     * Counter id for the number of times the time threshold has been exceeded by the NameResolver.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED = 33;\n\n    /**\n     * Counter id for the version of the media driver.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_AERON_VERSION = 34;\n\n    /**\n     * Counter id for the total number of bytes currently mapped in log buffers, CnC file, and loss report.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_BYTES_CURRENTLY_MAPPED = 35;\n\n    /**\n     * Counter id for the minimum bound on the number of bytes re-transmitted as a result of NAKs.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_RETRANSMITTED_BYTES = 36;\n\n    /**\n     * Counter id for the number of times that the retransmit pool has been overflowed.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_RETRANSMIT_OVERFLOW = 37;\n\n    /**\n     * Counter id for the number of error frames received by this driver.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_ERROR_FRAMES_RECEIVED = 38;\n\n    /**\n     * Counter id for the number of error frames sent by this driver.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_ERROR_FRAMES_SENT = 39;\n\n    /**\n     * Counter id for the number of publications that have been revoked.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_PUBLICATIONS_REVOKED = 40;\n\n    /**\n     * Counter id for the number of publication images that have been revoked.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_PUBLICATION_IMAGES_REVOKED = 41;\n\n    /**\n     * Counter id for the number of images that have been rejected.\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_IMAGES_REJECTED = 42;\n\n    /**\n     * Counter id for the control protocol between clients and media driver.\n     *\n     * @since 1.49.0\n     */\n    @AeronCounter\n    public static final int SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION = 43;\n\n    // Client/driver counters\n    /**\n     * System-wide counters for monitoring. These are separate from counters used for position tracking on streams.\n     */\n    @AeronCounter\n    public static final int DRIVER_SYSTEM_COUNTER_TYPE_ID = 0;\n\n    /**\n     * The limit as a position in bytes applied to publishers on a session-channel-stream tuple. Publishers will\n     * experience back pressure when this position is passed as a means of flow control.\n     */\n    @AeronCounter\n    public static final int DRIVER_PUBLISHER_LIMIT_TYPE_ID = 1;\n\n    /**\n     * The position the Sender has reached for sending data to the media on a session-channel-stream tuple.\n     */\n    @AeronCounter\n    public static final int DRIVER_SENDER_POSITION_TYPE_ID = 2;\n\n    /**\n     * The highest position the Receiver has observed on a session-channel-stream tuple while rebuilding the stream.\n     * It is possible the stream is not complete to this point if the stream has experienced loss.\n     */\n    @AeronCounter\n    public static final int DRIVER_RECEIVER_HWM_TYPE_ID = 3;\n    /**\n     * The position an individual Subscriber has reached on a session-channel-stream tuple. It is possible to have\n     * multiple\n     */\n    @AeronCounter(expectedCName = \"SUBSCRIPTION_POSITION\")\n    public static final int DRIVER_SUBSCRIBER_POSITION_TYPE_ID = 4;\n\n    /**\n     * The highest position the Receiver has rebuilt up to on a session-channel-stream tuple while rebuilding the\n     * stream.The stream is complete up to this point.\n     */\n    @AeronCounter(expectedCName = \"RECEIVER_POSITION\")\n    public static final int DRIVER_RECEIVER_POS_TYPE_ID = 5;\n\n    /**\n     * The status of a send-channel-endpoint represented as a counter value.\n     */\n    @AeronCounter\n    public static final int DRIVER_SEND_CHANNEL_STATUS_TYPE_ID = 6;\n\n    /**\n     * The status of a receive-channel-endpoint represented as a counter value.\n     */\n    @AeronCounter\n    public static final int DRIVER_RECEIVE_CHANNEL_STATUS_TYPE_ID = 7;\n\n    /**\n     * The position the Sender can immediately send up-to on a session-channel-stream tuple.\n     */\n    @AeronCounter\n    public static final int DRIVER_SENDER_LIMIT_TYPE_ID = 9;\n\n    /**\n     * A counter per Image indicating presence of the congestion control.\n     */\n    @AeronCounter\n    public static final int DRIVER_PER_IMAGE_TYPE_ID = 10;\n\n    /**\n     * A counter for tracking the last heartbeat of an entity with a given registration id.\n     */\n    @AeronCounter(expectedCName = \"CLIENT_HEARTBEAT_TIMESTAMP\")\n    public static final int DRIVER_HEARTBEAT_TYPE_ID = 11;\n\n    /**\n     * The position in bytes a publication has reached appending to the log.\n     * <p>\n     * <b>Note:</b> This is a not a real-time value like the other and is updated one per second for monitoring\n     * purposes.\n     */\n    @AeronCounter(expectedCName = \"PUBLISHER_POSITION\")\n    public static final int DRIVER_PUBLISHER_POS_TYPE_ID = 12;\n\n    /**\n     * Count of back-pressure events (BPE)s a sender has experienced on a stream.\n     */\n    @AeronCounter\n    public static final int DRIVER_SENDER_BPE_TYPE_ID = 13;\n\n    /**\n     * Counter used to store the status of a bind address and port for the local end of a channel.\n     * <p>\n     * When the value is {@link ChannelEndpointStatus#ACTIVE} then the key value and label will be updated with the\n     * socket address and port which is bound.\n     */\n    @AeronCounter(expectedCName = \"LOCAL_SOCKADDR\")\n    public static final int DRIVER_LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID = 14;\n\n    /**\n     * Count of media driver neighbors for name resolution.\n     */\n    @AeronCounter\n    public static final int NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID = 15;\n\n    /**\n     * Count of entries in the name resolver cache.\n     */\n    @AeronCounter\n    public static final int NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID = 16;\n\n    /**\n     * Count of number of active receivers for flow control strategy.\n     */\n    @AeronCounter(expectedCName = \"FC_NUM_RECEIVERS\")\n    public static final int FLOW_CONTROL_RECEIVERS_COUNTER_TYPE_ID = 17;\n\n    /**\n     * Count of number of destinations for multi-destination cast channels.\n     */\n    @AeronCounter(expectedCName = \"CHANNEL_NUM_DESTINATIONS\")\n    public static final int MDC_DESTINATIONS_COUNTER_TYPE_ID = 18;\n\n    /**\n     * The number of NAK messages received by the Sender.\n     */\n    @AeronCounter\n    public static final int DRIVER_SENDER_NAKS_RECEIVED_TYPE_ID = 19;\n\n    /**\n     * The number of NAK messages sent by the Receiver.\n     */\n    @AeronCounter\n    public static final int DRIVER_RECEIVER_NAKS_SENT_TYPE_ID = 20;\n\n    // EF_VI counters\n    /**\n     * EF_VI_PORT_INFO_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_PORT_INFO_TYPE_ID = 50;\n\n    /**\n     * EF_VI_TRANSPORT_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_TRANSPORT_TYPE_ID = 51;\n\n    /**\n     * EF_VI_TX_NOBUFS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_TX_NOBUFS_TYPE_ID = 52;\n\n    /**\n     * EF_VI_TX_EAGAIN_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_TX_EAGAIN_TYPE_ID = 53;\n\n    /**\n     * EF_VI_TX_ERROR_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_TX_ERROR_TYPE_ID = 54;\n\n    /**\n     * EF_VI_RX_DISCARD_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_RX_DISCARD_TYPE_ID = 55;\n\n    /**\n     * EF_VI_RX_INVALID_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_RX_INVALID_TYPE_ID = 56;\n\n    /**\n     * EF_VI_RX_PKTS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_RX_PKTS_TYPE_ID = 57;\n\n    /**\n     * EF_VI_RX_BYTES_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_RX_BYTES_TYPE_ID = 58;\n\n    /**\n     * EF_VI_TX_PKTS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_TX_PKTS_TYPE_ID = 59;\n\n    /**\n     * EF_VI_TX_BYTES_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int EF_VI_TX_BYTES_TYPE_ID = 60;\n\n    // VMA counters\n    /**\n     * VMA_TRANSPORTS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int VMA_TRANSPORTS_TYPE_ID = 61;\n\n    /**\n     * VMA_RX_ZERO_COPY_BYTES_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int VMA_RX_ZERO_COPY_BYTES_TYPE_ID = 62;\n\n    /**\n     * VMA_RX_DATA_COPY_BYTES_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int VMA_RX_DATA_COPY_BYTES_TYPE_ID = 63;\n\n    // ATS counters\n    /**\n     * ATS_TRANSPORTS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_TRANSPORTS_TYPE_ID = 65;\n\n    /**\n     * ATS_DISCARDS_NON_ATS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_DISCARDS_NON_ATS_TYPE_ID = 66;\n\n    /**\n     * ATS_BYTES_ENCRYPTED_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_BYTES_ENCRYPTED_TYPE_ID = 67;\n\n    /**\n     * ATS_BYTES_DECRYPTED_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_BYTES_DECRYPTED_TYPE_ID = 68;\n\n    /**\n     * ATS_AEAD_ERRORS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_AEAD_ERRORS_TYPE_ID = 69;\n\n    /**\n     * ATS_RSA_KEY_UNKNOWN_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_RSA_KEY_UNKNOWN_TYPE_ID = 70;\n\n    /**\n     * ATS_EC_KEY_SIG_ERRORS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_EC_KEY_SIG_ERRORS_TYPE_ID = 71;\n\n    /**\n     * ATS_UNICAST_RE_KEYINGS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_UNICAST_RE_KEYINGS_TYPE_ID = 72;\n\n    /**\n     * ATS_UNICAST_RE_KEYING_RSA_KEY_MISMATCH_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_UNICAST_RE_KEYING_RSA_KEY_MISMATCH_TYPE_ID = 73;\n\n    /**\n     * ATS_DROPPED_SM_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int ATS_DROPPED_SM_TYPE_ID = 74;\n\n    // DPDK counters\n    /**\n     * DPDK_PORT_INFO_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_PORT_INFO_TYPE_ID = 75;\n\n    /**\n     * DPDK_TRANSPORT_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_TRANSPORT_TYPE_ID = 76;\n\n    /**\n     * DPDK_NOBUFS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_NOBUFS_TYPE_ID = 77;\n\n    /**\n     * DPDK_TX_EAGAIN_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_TX_EAGAIN_TYPE_ID = 78;\n\n    /**\n     * DPDK_ERROR_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_ERROR_TYPE_ID = 79;\n\n    /**\n     * DPDK_PKTS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_PKTS_TYPE_ID = 82;\n\n    /**\n     * DPDK_BYTES_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_BYTES_TYPE_ID = 83;\n\n    /**\n     * DPDK_MISSED_PACKETS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_MISSED_PACKETS_TYPE_ID = 84;\n\n    /**\n     * DPDK_ARP_MISS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_ARP_MISS_TYPE_ID = 85;\n\n    /**\n     * DPDK_RX_SENDER_DISCARD_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_RX_SENDER_DISCARD_TYPE_ID = 86;\n\n    /**\n     * DPDK_POLLER_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_POLLER_TYPE_ID = 87;\n\n    /**\n     * DPDK_QUEUE_DROP_COUNT_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_QUEUE_DROP_COUNT_TYPE_ID = 88;\n\n    /**\n     * DPDK_CHECKSUM_FAILURE_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_CHECKSUM_FAILURE_TYPE_ID = 89;\n\n    /**\n     * DPDK_FRAGMENTED_PACKETS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_FRAGMENTED_PACKETS_TYPE_ID = 90;\n\n    /**\n     * DPDK_MEMPOOL_AVAILABLE_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_MEMPOOL_AVAILABLE_TYPE_ID = 91;\n\n    /**\n     * DPDK_EXTENDED_STATS_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_EXTENDED_STATS_TYPE_ID = 92;\n\n    /**\n     * DPDK_RX_UNSUPPORTED_ETHERNET_TYPE_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_RX_UNSUPPORTED_ETHERNET_TYPE_TYPE_ID = 93;\n\n    /**\n     * DPDK_RX_UNSUPPORTED_PROTOCOL_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_RX_UNSUPPORTED_PROTOCOL_TYPE_ID = 94;\n\n    /**\n     * DPDK_RX_RECEIVER_DISCARD_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int DPDK_RX_RECEIVER_DISCARD_TYPE_ID = 95;\n\n    // Archive counters\n    /**\n     * The position a recording has reached when being archived.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_RECORDING_POSITION_TYPE_ID = 100;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the number of errors that have occurred.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_ERROR_COUNT_TYPE_ID = 101;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of concurrent control sessions.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_CONTROL_SESSIONS_TYPE_ID = 102;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of an archive agent.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_MAX_CYCLE_TIME_TYPE_ID = 103;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * an archive agent.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 104;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max time it took recorder to write a block of\n     * data to the storage.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_RECORDER_MAX_WRITE_TIME_TYPE_ID = 105;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the total number of bytes written by the recorder\n     * to the storage.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_RECORDER_TOTAL_WRITE_BYTES_TYPE_ID = 106;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the total time the recorder spent writing data to\n     * the storage.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_RECORDER_TOTAL_WRITE_TIME_TYPE_ID = 107;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max time it took replayer to read a block from\n     * the storage.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_REPLAYER_MAX_READ_TIME_TYPE_ID = 108;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the total number of bytes read by the replayer from\n     * the storage.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_REPLAYER_TOTAL_READ_BYTES_TYPE_ID = 109;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the total time the replayer spent reading data from\n     * the storage.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_REPLAYER_TOTAL_READ_TIME_TYPE_ID = 110;\n\n    /**\n     * The type id of the {@link Counter} used for tracking the count of active recording sessions.\n     */\n    @AeronCounter(existsInC = false)\n    public static final int ARCHIVE_RECORDING_SESSION_COUNT_TYPE_ID = 111;\n\n    /**\n     * The type id of the {@link Counter} used for tracking the count of active replay sessions.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_REPLAY_SESSION_COUNT_TYPE_ID = 112;\n\n    /**\n     * The type id of the {@link Counter} used for tracking Archive clients.\n     *\n     * @since 1.49.0.\n     */\n    @AeronCounter\n    public static final int ARCHIVE_CONTROL_SESSION_TYPE_ID = 113;\n\n    // Cluster counters\n\n    /**\n     * Counter type id for the consensus module state.\n     */\n    @AeronCounter\n    public static final int CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID = 200;\n\n    /**\n     * Counter type id for the cluster node role.\n     */\n    @AeronCounter\n    public static final int CLUSTER_NODE_ROLE_TYPE_ID = 201;\n\n    /**\n     * Counter type id for the control toggle.\n     */\n    @AeronCounter\n    public static final int CLUSTER_CONTROL_TOGGLE_TYPE_ID = 202;\n\n    /**\n     * Counter type id of the commit position.\n     */\n    @AeronCounter\n    public static final int CLUSTER_COMMIT_POSITION_TYPE_ID = 203;\n\n    /**\n     * Counter representing the Recovery State for the cluster.\n     */\n    @AeronCounter\n    public static final int CLUSTER_RECOVERY_STATE_TYPE_ID = 204;\n\n    /**\n     * Counter type id for count of snapshots taken.\n     */\n    @AeronCounter\n    public static final int CLUSTER_SNAPSHOT_COUNTER_TYPE_ID = 205;\n\n    /**\n     * Type id for election state counter.\n     */\n    @AeronCounter\n    public static final int CLUSTER_ELECTION_STATE_TYPE_ID = 207;\n\n    /**\n     * The type id of the {@link Counter} used for the backup state.\n     */\n    @AeronCounter\n    public static final int CLUSTER_BACKUP_STATE_TYPE_ID = 208;\n\n    /**\n     * The type id of the {@link Counter} used for the live log position counter.\n     */\n    @AeronCounter\n    public static final int CLUSTER_BACKUP_LIVE_LOG_POSITION_TYPE_ID = 209;\n\n    /**\n     * The type id of the {@link Counter} used for the next query deadline counter.\n     */\n    @AeronCounter\n    public static final int CLUSTER_BACKUP_QUERY_DEADLINE_TYPE_ID = 210;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the number of errors that have occurred.\n     */\n    @AeronCounter\n    public static final int CLUSTER_BACKUP_ERROR_COUNT_TYPE_ID = 211;\n\n    /**\n     * Counter type id for the consensus module error count.\n     */\n    @AeronCounter\n    public static final int CLUSTER_CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID = 212;\n\n    /**\n     * Counter type id for the number of cluster clients which have been timed out.\n     */\n    @AeronCounter\n    public static final int CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID = 213;\n\n    /**\n     * Counter type id for the number of invalid requests which the cluster has received.\n     */\n    @AeronCounter\n    public static final int CLUSTER_INVALID_REQUEST_COUNT_TYPE_ID = 214;\n\n    /**\n     * Counter type id for the clustered service error count.\n     */\n    @AeronCounter\n    public static final int CLUSTER_CLUSTERED_SERVICE_ERROR_COUNT_TYPE_ID = 215;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the consensus module.\n     */\n    @AeronCounter\n    public static final int CLUSTER_MAX_CYCLE_TIME_TYPE_ID = 216;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * the consensus module.\n     */\n    @AeronCounter\n    public static final int CLUSTER_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 217;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the service container.\n     */\n    @AeronCounter\n    public static final int CLUSTER_CLUSTERED_SERVICE_MAX_CYCLE_TIME_TYPE_ID = 218;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * the service container.\n     */\n    @AeronCounter\n    public static final int CLUSTER_CLUSTERED_SERVICE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 219;\n\n    /**\n     * The type id of the {@link Counter} used for the cluster standby state.\n     */\n    @AeronCounter\n    public static final int CLUSTER_STANDBY_STATE_TYPE_ID = 220;\n\n    /**\n     * Counter type id for the clustered service error count.\n     */\n    @AeronCounter\n    public static final int CLUSTER_STANDBY_ERROR_COUNT_TYPE_ID = 221;\n\n    /**\n     * Counter type for responses to heartbeat request from the cluster.\n     */\n    @AeronCounter\n    public static final int CLUSTER_STANDBY_HEARTBEAT_RESPONSE_COUNT_TYPE_ID = 222;\n\n    /**\n     * Standby control toggle type id.\n     */\n    @AeronCounter\n    public static final int CLUSTER_STANDBY_CONTROL_TOGGLE_TYPE_ID = 223;\n\n    /**\n     * The type if of the {@link Counter} used for transition module state.\n     */\n    @AeronCounter(expectedCName = \"CLUSTER_TRANSITION_MODULE_STATE\")\n    public static final int TRANSITION_MODULE_STATE_TYPE_ID = 224;\n\n//    public static final int TRANSITION_MODULE_CONTROL_TOGGLE_TYPE_ID = 225;\n\n    /**\n     * Counter type id for the transition module error count.\n     */\n    @AeronCounter(expectedCName = \"CLUSTER_TRANSITION_MODULE_ERROR_COUNT\")\n    public static final int TRANSITION_MODULE_ERROR_COUNT_TYPE_ID = 226;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the cluster standby.\n     */\n    @AeronCounter\n    public static final int CLUSTER_STANDBY_MAX_CYCLE_TIME_TYPE_ID = 227;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * the cluster standby.\n     */\n    @AeronCounter\n    public static final int CLUSTER_STANDBY_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 228;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the max duty cycle time of the transition module.\n     */\n    @AeronCounter(expectedCName = \"CLUSTER_TRANSITION_MODULE_MAX_CYCLE_TIME\")\n    public static final int TRANSITION_MODULE_MAX_CYCLE_TIME_TYPE_ID = 229;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count of cycle time threshold exceeded of\n     * the transition module.\n     */\n    @AeronCounter(expectedCName = \"CLUSTER_TRANSITION_MODULE_CYCLE_TIME_THRESHOLD_EXCEEDED\")\n    public static final int TRANSITION_MODULE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 230;\n\n    /**\n     * The type id of the {@link Counter} to make visible the memberId that the cluster standby is currently using to\n     * as a source for the cluster log.\n     */\n    @AeronCounter\n    public static final int CLUSTER_STANDBY_SOURCE_MEMBER_ID_TYPE_ID = 231;\n\n    /**\n     * Counter type for count of standby snapshots received.\n     */\n    @AeronCounter(existsInC = false)\n    public static final int CLUSTER_STANDBY_SNAPSHOT_COUNTER_TYPE_ID = 232;\n\n    /**\n     * The type of the {@link Counter} used for handling node specific operations.\n     */\n    @AeronCounter(existsInC = false)\n    public static final int NODE_CONTROL_TOGGLE_TYPE_ID = 233;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the maximum total snapshot duration.\n     */\n    @AeronCounter(existsInC = false)\n    public static final int CLUSTER_TOTAL_MAX_SNAPSHOT_DURATION_TYPE_ID = 234;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count total snapshot duration\n     * has exceeded the threshold.\n     */\n    @AeronCounter\n    public static final int CLUSTER_TOTAL_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID = 235;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the maximum snapshot duration\n     * for a given clustered service.\n     */\n    @AeronCounter(existsInC = false)\n    public static final int CLUSTERED_SERVICE_MAX_SNAPSHOT_DURATION_TYPE_ID = 236;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the count snapshot duration\n     * has exceeded the threshold for a given clustered service.\n     */\n    @AeronCounter\n    public static final int CLUSTERED_SERVICE_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID = 237;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the number of elections that have occurred.\n     */\n    @AeronCounter\n    public static final int CLUSTER_ELECTION_COUNT_TYPE_ID = 238;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the Cluster leadership term id.\n     */\n    @AeronCounter(existsInC = false)\n    public static final int CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID = 239;\n\n    /**\n     * The type id of the {@link Counter} used for tracking the number of snapshots downloaded.\n     */\n    @AeronCounter\n    public static final int CLUSTER_BACKUP_SNAPSHOT_RETRIEVE_COUNT_TYPE_ID = 240;\n\n    /**\n     * The type id of the {@link Counter} used for tracking Cluster clients.\n     *\n     * @since 1.49.0.\n     */\n    @AeronCounter\n    public static final int CLUSTER_SESSION_TYPE_ID = 241;\n\n    /**\n     * SELECTOR_CLIENTS_COUNTER_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int SELECTOR_CLIENTS_COUNTER_TYPE_ID = 300;\n\n    /**\n     * SELECTOR_SUBSCRIPTIONS_COUNTER_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int SELECTOR_SUBSCRIPTIONS_COUNTER_TYPE_ID = 301;\n\n    /**\n     * SELECTOR_MAX_CYCLE_TIME_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int SELECTOR_MAX_CYCLE_TIME_TYPE_ID = 302;\n\n    /**\n     * SELECTOR_MAX_CYCLE_TIME_TYPE_ID.\n     */\n    @AeronCounter\n    public static final int SELECTOR_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID = 303;\n\n    // ===================\n    // Sequencer Counters.\n    // ===================\n\n    /**\n     * Counter id for Sequencer Index.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_INDEX_COUNTER_TYPE_ID = 500;\n\n    /**\n     * Counter id for application group last message.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_GROUP_HWM_COUNTER_TYPE_ID = 501;\n\n    /**\n     * Counter id for session last message.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_SESSION_GREATEST_MESSAGE_ID_COUNTER_TYPE_ID = 502;\n\n    /**\n     * Counter id for session messages.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_SESSION_MESSAGES_COUNTER_TYPE_ID = 503;\n\n    /**\n     * Counter id for session last message timestamp.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_SESSION_GREATEST_MESSAGE_TIMESTAMP_COUNTER_TYPE_ID = 504;\n\n    /**\n     * Counter id for the next snapshot id.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_CLIENT_SNAPSHOT_ID_COUNTER_TYPE_ID = 505;\n\n    /**\n     * Counter id for sequence index.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_SEQUENCE_INDEX_COUNTER_TYPE_ID = 507;\n\n    /**\n     * Application state counter type id.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_STATE_COUNTER_TYPE_ID = 508;\n\n    /**\n     * Counter id for error count.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_ERROR_COUNT_TYPE_ID = 509;\n\n    /**\n     * Counter id for max service time.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_MAX_SERVICE_TIME_TYPE_ID = 510;\n\n    /**\n     * Counter id for the number of times the service time threshold was exceeded.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_SERVICE_TIME_THRESHOLD_EXCEEDED_COUNT_TYPE_ID = 511;\n\n    /**\n     * Counter id for the total service time during the last interval.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_INTERVAL_SERVICE_TIME_TYPE_ID = 512;\n\n    /**\n     * Counter id for the maximum individual service time during the last interval.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_INTERVAL_MAX_SERVICE_TIME_TYPE_ID = 513;\n\n    /**\n     * Counter id for the total number of invocations during the last interval.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_INTERVAL_TOTAL_INVOCATIONS_TYPE_ID = 514;\n\n    /**\n     * Counter id for the load time, in milliseconds, of a snapshot.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_SNAPSHOT_LOAD_TIME_TYPE_ID = 515;\n\n    /**\n     * Counter id for the store time, in milliseconds, of a snapshot.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_SNAPSHOT_STORE_TIME_TYPE_ID = 516;\n\n    /**\n     * Counter id for the number of 'take snapshot' failures.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_TAKE_SNAPSHOT_FAILURES_TYPE_ID = 517;\n\n    /**\n     * Counter id for the number of 'take snapshot' instances.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_TAKE_SNAPSHOT_COUNT_TYPE_ID = 518;\n\n    /**\n     * Counter id for the application service's session with the sequencer.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_APPLICATION_SESSION_ID_TYPE_ID = 519;\n\n    /**\n     * Counter id for the replay index's minimum sequence index.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_REPLAY_INDEX_MIN_SEQUENCE_INDEX_COUNTER_TYPE_ID = 520;\n\n    /**\n     * Counter id for the replay index's minimum log position.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_REPLAY_INDEX_MIN_SEQUENCE_LOG_POSITION_COUNTER_TYPE_ID = 521;\n\n    /**\n     * Counter id for the replay index's maximum sequence index.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_REPLAY_INDEX_MAX_SEQUENCE_INDEX_COUNTER_TYPE_ID = 522;\n\n    /**\n     * Counter id for the replay index's maximum log position.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_REPLAY_INDEX_MAX_SEQUENCE_LOG_POSITION_COUNTER_TYPE_ID = 523;\n\n    /**\n     * Counter id for the replay index's initial sequence index.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_REPLAY_INDEX_INITIAL_SEQUENCE_INDEX_COUNTER_TYPE_ID = 524;\n\n    /**\n     * Counter id for the replay index's initial log position.\n     */\n    @AeronCounter\n    public static final int SEQUENCER_REPLAY_INDEX_INITIAL_SEQUENCE_LOG_POSITION_COUNTER_TYPE_ID = 525;\n\n    private AeronCounters()\n    {\n    }\n\n    /**\n     * Checks that the counter specified by {@code counterId} has the counterTypeId that matches the specified value.\n     * If not it will throw a {@link io.aeron.exceptions.ConfigurationException}.\n     *\n     * @param countersReader        to look up the counter type id.\n     * @param counterId             counter to reference.\n     * @param expectedCounterTypeId the expected type id for the counter.\n     * @throws io.aeron.exceptions.ConfigurationException if the type id does not match.\n     * @throws IllegalArgumentException                   if the counterId is not valid.\n     */\n    public static void validateCounterTypeId(\n        final CountersReader countersReader,\n        final int counterId,\n        final int expectedCounterTypeId)\n    {\n        final int counterTypeId = countersReader.getCounterTypeId(counterId);\n        if (expectedCounterTypeId != counterTypeId)\n        {\n            throw new ConfigurationException(\n                \"The type for counterId=\" + counterId +\n                    \", typeId=\" + counterTypeId +\n                    \" does not match the expected=\" + expectedCounterTypeId);\n        }\n    }\n\n    /**\n     * Convenience overload for {@link AeronCounters#validateCounterTypeId(CountersReader, int, int)}.\n     *\n     * @param aeron                 to resolve a counters' reader.\n     * @param counter               to be checked for the appropriate counterTypeId.\n     * @param expectedCounterTypeId the expected type id for the counter.\n     * @throws io.aeron.exceptions.ConfigurationException if the type id does not match.\n     * @throws IllegalArgumentException                   if the counterId is not valid.\n     * @see AeronCounters#validateCounterTypeId(CountersReader, int, int)\n     */\n    public static void validateCounterTypeId(\n        final Aeron aeron,\n        final Counter counter,\n        final int expectedCounterTypeId)\n    {\n        validateCounterTypeId(aeron.countersReader(), counter.id(), expectedCounterTypeId);\n    }\n\n    /**\n     * Append version information at the end of the counter's label.\n     *\n     * @param tempBuffer     to append label to.\n     * @param offset         at which current label data ends.\n     * @param fullVersion    of the component.\n     * @param commitHashCode Git commit SHA.\n     * @return length of the suffix appended.\n     */\n    public static int appendVersionInfo(\n        final MutableDirectBuffer tempBuffer, final int offset, final String fullVersion, final String commitHashCode)\n    {\n        int length = tempBuffer.putStringWithoutLengthAscii(offset, \" \");\n        length += tempBuffer.putStringWithoutLengthAscii(\n            offset + length, formatVersionInfo(fullVersion, commitHashCode));\n        return length;\n    }\n\n    /**\n     * Append specified {@code value} at the end of the counter's label as ASCII encoded value up to the\n     * {@link CountersReader#MAX_LABEL_LENGTH}.\n     *\n     * @param metaDataBuffer containing the counter metadata.\n     * @param counterId      to append version info to.\n     * @param value          to be appended to the label.\n     * @return number of bytes that got appended.\n     * @throws IllegalArgumentException if {@code counterId} is invalid or points to non-allocated counter.\n     */\n    public static int appendToLabel(\n        final AtomicBuffer metaDataBuffer, final int counterId, final String value)\n    {\n        Objects.requireNonNull(metaDataBuffer);\n        validateCounterId(metaDataBuffer, counterId);\n\n        final int counterMetaDataOffset = metaDataOffset(counterId);\n        final int state = metaDataBuffer.getIntVolatile(counterMetaDataOffset);\n        if (RECORD_ALLOCATED != state)\n        {\n            throw new IllegalArgumentException(\"counter id \" + counterId + \" is not allocated, state: \" + state);\n        }\n\n        final int existingLabelLength = metaDataBuffer.getInt(counterMetaDataOffset + LABEL_OFFSET);\n        final int remainingLabelLength = MAX_LABEL_LENGTH - existingLabelLength;\n\n        final int writtenLength = metaDataBuffer.putStringWithoutLengthAscii(\n            counterMetaDataOffset + LABEL_OFFSET + SIZE_OF_INT + existingLabelLength,\n            value,\n            0,\n            remainingLabelLength);\n        if (writtenLength > 0)\n        {\n            metaDataBuffer.putIntRelease(\n                counterMetaDataOffset + LABEL_OFFSET, existingLabelLength + writtenLength);\n        }\n\n        return writtenLength;\n    }\n\n    /**\n     * Format version information for display purposes.\n     *\n     * @param fullVersion of the component.\n     * @param commitHash  Git commit SHA.\n     * @return formatted String.\n     */\n    public static String formatVersionInfo(final String fullVersion, final String commitHash)\n    {\n        return \"version=\" + fullVersion + \" commit=\" + commitHash;\n    }\n\n    /**\n     * Set a reference id for a given counter id.\n     *\n     * @param metaDataBuffer containing the counter metadata.\n     * @param valuesBuffer   containing the counter values.\n     * @param counterId      to be set.\n     * @param referenceId    to set for the counter.\n     * @see CountersReader#getCounterReferenceId(int)\n     * @since 1.49.0\n     */\n    public static void setReferenceId(\n        final AtomicBuffer metaDataBuffer, final AtomicBuffer valuesBuffer, final int counterId, final long referenceId)\n    {\n        Objects.requireNonNull(metaDataBuffer);\n        Objects.requireNonNull(valuesBuffer);\n        validateCounterId(metaDataBuffer, counterId);\n\n        valuesBuffer.putLongRelease(counterOffset(counterId) + REFERENCE_ID_OFFSET, referenceId);\n    }\n\n    private static void validateCounterId(final AtomicBuffer metaDataBuffer, final int counterId)\n    {\n        if (counterId < 0)\n        {\n            throw new IllegalArgumentException(\"counter id \" + counterId + \" is negative\");\n        }\n\n        final int maxCounterId = (metaDataBuffer.capacity() / METADATA_LENGTH) - 1;\n        if (counterId > maxCounterId)\n        {\n            throw new IllegalArgumentException(\n                \"counter id \" + counterId + \" out of range: 0 - maxCounterId=\" + maxCounterId);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/AvailableCounterHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.agrona.concurrent.status.CountersReader;\n\n/**\n * Interface for notification of{@link Counter}s becoming available via a {@link Aeron} client.\n */\n@FunctionalInterface\npublic interface AvailableCounterHandler\n{\n    /**\n     * Method called by Aeron to deliver notification of a {@link Counter} being available.\n     * <p>\n     * Within this callback reentrant calls to the {@link Aeron} client are not permitted and\n     * will result in undefined behaviour.\n     *\n     * @param countersReader for more detail on the counter.\n     * @param registrationId for the counter.\n     * @param counterId      that is available.\n     */\n    void onAvailableCounter(CountersReader countersReader, long registrationId, int counterId);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/AvailableImageHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\n/**\n * Interface for notification of new {@link Image}s becoming available under a {@link Subscription}.\n */\n@FunctionalInterface\npublic interface AvailableImageHandler\n{\n    /**\n     * Method called by Aeron to deliver notification of a new {@link Image} being available for polling.\n     * <p>\n     * Within this callback reentrant calls to  the {@link Aeron} client are not permitted and\n     * will result in undefined behaviour.\n     *\n     * @param image that is now available\n     */\n    void onAvailableImage(Image image);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/BufferBuilder.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\nimport java.util.Arrays;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.logbuffer.FrameDescriptor.FLAGS_OFFSET;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computeFragmentedFrameLength;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.HeaderFlyweight.FRAME_LENGTH_FIELD_OFFSET;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * Reusable Builder for appending a sequence of buffer fragments which grows internal capacity as needed.\n * <p>\n * The underlying buffer can be byte[] backed or a direct {@link ByteBuffer} if the isDirect param to the constructor\n * is true.\n * <p>\n * Similar in concept to {@link StringBuilder}.\n */\npublic final class BufferBuilder\n{\n    static final int MAX_CAPACITY = Integer.MAX_VALUE - 8;\n    static final int INIT_MIN_CAPACITY = 4096;\n\n    private final boolean isDirect;\n    private int limit;\n    private int nextTermOffset = NULL_VALUE;\n    private int firstFrameLength;\n    private final UnsafeBuffer buffer = new UnsafeBuffer();\n    final UnsafeBuffer headerBuffer = new UnsafeBuffer();\n    final Header completeHeader = new Header(0, 0);\n\n    /**\n     * Construct a buffer builder with an initial capacity of zero and isDirect false.\n     */\n    public BufferBuilder()\n    {\n        this(0, false);\n    }\n\n    /**\n     * Construct a buffer builder with an initial capacity and isDirect false.\n     *\n     * @param initialCapacity at which the capacity will start.\n     */\n    public BufferBuilder(final int initialCapacity)\n    {\n        this(initialCapacity, false);\n    }\n\n    /**\n     * Construct a buffer builder with an initial capacity.\n     *\n     * @param initialCapacity at which the capacity will start.\n     * @param isDirect        is the underlying buffer to be a direct {@link ByteBuffer}\n     */\n    public BufferBuilder(final int initialCapacity, final boolean isDirect)\n    {\n        if (initialCapacity < 0 || initialCapacity > MAX_CAPACITY)\n        {\n            throw new IllegalArgumentException(\"initialCapacity outside range 0 - \" + MAX_CAPACITY +\n                \": initialCapacity=\" + initialCapacity);\n        }\n\n        this.isDirect = isDirect;\n        if (isDirect)\n        {\n            if (initialCapacity > 0)\n            {\n                buffer.wrap(newDirectBuffer(initialCapacity));\n            }\n            headerBuffer.wrap(newDirectBuffer(HEADER_LENGTH));\n        }\n        else\n        {\n            if (initialCapacity > 0)\n            {\n                buffer.wrap(new byte[initialCapacity]);\n            }\n            headerBuffer.wrap(new byte[HEADER_LENGTH]);\n        }\n    }\n\n    /**\n     * The current capacity of the buffer.\n     *\n     * @return the current capacity of the buffer.\n     */\n    public int capacity()\n    {\n        return buffer.capacity();\n    }\n\n    /**\n     * The current limit of the buffer that has been used by append operations.\n     *\n     * @return the current limit of the buffer that has been used by append operations.\n     */\n    public int limit()\n    {\n        return limit;\n    }\n\n    /**\n     * Set this limit for this buffer as the position at which the next append operation will occur.\n     *\n     * @param limit to be the new value.\n     */\n    public void limit(final int limit)\n    {\n        if (limit < 0 || limit >= buffer.capacity())\n        {\n            throw new IllegalArgumentException(\n                \"limit outside range: capacity=\" + buffer.capacity() + \" limit=\" + limit);\n        }\n\n        this.limit = limit;\n    }\n\n    /**\n     * Get the value which the next term offset for a fragment to be assembled should begin at.\n     *\n     * @return the value which the next term offset for a fragment to be assembled should begin at.\n     */\n    public int nextTermOffset()\n    {\n        return nextTermOffset;\n    }\n\n    /**\n     * Set the value which the next term offset for a fragment to be assembled should begin at.\n     *\n     * @param offset which the next term offset for a fragment to be assembled should begin at.\n     */\n    public void nextTermOffset(final int offset)\n    {\n        nextTermOffset = offset;\n    }\n\n    /**\n     * The {@link MutableDirectBuffer} that encapsulates the internal buffer.\n     *\n     * @return the {@link MutableDirectBuffer} that encapsulates the internal buffer.\n     */\n    public MutableDirectBuffer buffer()\n    {\n        return buffer;\n    }\n\n    /**\n     * Reset the builder to restart append operations. The internal buffer does not shrink.\n     *\n     * @return the builder for fluent API usage.\n     */\n    public BufferBuilder reset()\n    {\n        limit = 0;\n        nextTermOffset = NULL_VALUE;\n        completeHeader\n            .context(null)\n            .fragmentedFrameLength(NULL_VALUE);\n        return this;\n    }\n\n    /**\n     * Compact the buffer to reclaim unused space above the limit.\n     *\n     * @return the builder for fluent API usage.\n     */\n    public BufferBuilder compact()\n    {\n        final int newCapacity = Math.max(INIT_MIN_CAPACITY, limit);\n        if (newCapacity < buffer.capacity())\n        {\n            resize(newCapacity);\n        }\n\n        return this;\n    }\n\n    /**\n     * Append a source buffer to the end of the internal buffer, resizing the internal buffer when required.\n     *\n     * @param srcBuffer from which to copy.\n     * @param srcOffset in the source buffer from which to copy.\n     * @param length    in bytes to copy from the source buffer.\n     * @return the builder for fluent API usage.\n     */\n    public BufferBuilder append(final DirectBuffer srcBuffer, final int srcOffset, final int length)\n    {\n        ensureCapacity(length);\n\n        buffer.putBytes(limit, srcBuffer, srcOffset, length);\n        limit += length;\n\n        return this;\n    }\n\n    /**\n     * Capture information available in the header of the very first frame.\n     *\n     * @param header of the first frame.\n     * @return the builder for fluent API usage.\n     */\n    public BufferBuilder captureHeader(final Header header)\n    {\n        completeHeader\n            .initialTermId(header.initialTermId())\n            .positionBitsToShift(header.positionBitsToShift())\n            .offset(0)\n            .buffer(headerBuffer);\n\n        firstFrameLength = header.frameLength();\n\n        headerBuffer.putBytes(0, header.buffer(), header.offset(), HEADER_LENGTH);\n        return this;\n    }\n\n    /**\n     * Use the information from the header of the last frame to create a header for the assembled message, i.e. fixups\n     * the flags and the frame length.\n     *\n     * @param header of the last frame.\n     * @return complete message header.\n     */\n    public Header completeHeader(final Header header)\n    {\n        // compute the `fragmented frame length` of the complete message\n        final int fragmentedFrameLength = computeFragmentedFrameLength(limit, firstFrameLength - HEADER_LENGTH);\n        completeHeader\n            .context(header.context())\n            .fragmentedFrameLength(fragmentedFrameLength);\n\n        headerBuffer.putInt(FRAME_LENGTH_FIELD_OFFSET, HEADER_LENGTH + limit, LITTLE_ENDIAN);\n        // compute complete flags\n        headerBuffer.putByte(FLAGS_OFFSET, (byte)(headerBuffer.getByte(FLAGS_OFFSET) | header.flags()));\n\n        return completeHeader;\n    }\n\n    private void ensureCapacity(final int additionalLength)\n    {\n        final long requiredCapacity = (long)limit + additionalLength;\n        final int capacity = buffer.capacity();\n\n        if (requiredCapacity > capacity)\n        {\n            if (requiredCapacity > MAX_CAPACITY)\n            {\n                throw new IllegalStateException(\n                    \"insufficient capacity: maxCapacity=\" + MAX_CAPACITY +\n                    \" limit=\" + limit +\n                    \" additionalLength=\" + additionalLength);\n            }\n\n            resize(findSuitableCapacity(capacity, requiredCapacity));\n        }\n    }\n\n    private void resize(final int newCapacity)\n    {\n        if (isDirect)\n        {\n            final ByteBuffer byteBuffer = newDirectBuffer(newCapacity);\n            buffer.getBytes(0, byteBuffer, 0, limit);\n            buffer.wrap(byteBuffer);\n        }\n        else\n        {\n            buffer.wrap(Arrays.copyOf(buffer.byteArray(), newCapacity));\n        }\n    }\n\n    private static ByteBuffer newDirectBuffer(final int newCapacity)\n    {\n        final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(newCapacity);\n        byteBuffer.order(LITTLE_ENDIAN);\n        return byteBuffer;\n    }\n\n    static int findSuitableCapacity(final int capacity, final long requiredCapacity)\n    {\n        long newCapacity = Math.max(capacity, INIT_MIN_CAPACITY);\n\n        while (newCapacity < requiredCapacity)\n        {\n            newCapacity = newCapacity + (newCapacity >> 1);\n            if (newCapacity > MAX_CAPACITY)\n            {\n                newCapacity = MAX_CAPACITY;\n                break;\n            }\n        }\n\n        return (int)newCapacity;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ChannelUri.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.Strings;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.BiConsumer;\nimport java.util.function.Function;\n\nimport static io.aeron.CommonContext.*;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static java.util.Objects.requireNonNull;\n\n/**\n * Parser for Aeron channel URIs. The format is:\n * <pre>\n * aeron-uri = \"aeron:\" media [ \"?\" param *( \"|\" param ) ]\n * media     = *( \"[^?:]\" )\n * param     = key \"=\" value\n * key       = *( \"[^=]\" )\n * value     = *( \"[^|]\" )\n * </pre>\n * <p>\n * Multiple params with the same key are allowed, the last value specified takes precedence.\n *\n * @see ChannelUriStringBuilder\n */\npublic final class ChannelUri\n{\n    @SuppressWarnings(\"JavadocVariable\")\n    private enum State\n    {\n        MEDIA, PARAMS_KEY, PARAMS_VALUE\n    }\n\n    /**\n     * URI Scheme for Aeron channels and destinations.\n     */\n    public static final String AERON_SCHEME = \"aeron\";\n\n    /**\n     * Qualifier for spy subscriptions which spy on outgoing network destined traffic efficiently.\n     */\n    public static final String SPY_QUALIFIER = \"aeron-spy\";\n\n    /**\n     * Invalid tag value returned when calling {@link #getTag(String)} and the channel is not tagged.\n     */\n    public static final long INVALID_TAG = Aeron.NULL_VALUE;\n\n    /**\n     * Max length in characters for the URI string.\n     */\n    public static final int MAX_URI_LENGTH = 4095;\n\n    private static final int CHANNEL_TAG_INDEX = 0;\n    private static final int ENTITY_TAG_INDEX = 1;\n\n    private static final String AERON_PREFIX = AERON_SCHEME + \":\";\n\n    private String prefix;\n    private String media;\n    private final Object2ObjectHashMap<String, String> params;\n    private final String[] tags;\n\n    /**\n     * Construct with the components provided to avoid parsing.\n     *\n     * @param prefix empty if no prefix is required otherwise expected to be 'aeron-spy'\n     * @param media  for the channel which is typically \"udp\" or \"ipc\".\n     * @param params for the query string as key value pairs.\n     */\n    private ChannelUri(final String prefix, final String media, final Object2ObjectHashMap<String, String> params)\n    {\n        this.prefix = prefix;\n        this.media = media;\n        this.params = params;\n        this.tags = splitTags(params.get(TAGS_PARAM_NAME));\n    }\n\n    /**\n     * The prefix for the channel.\n     *\n     * @return the prefix for the channel.\n     */\n    public String prefix()\n    {\n        return prefix;\n    }\n\n    /**\n     * Change the prefix from what has been parsed.\n     *\n     * @param prefix to replace the existing prefix.\n     * @return this for a fluent API.\n     */\n    public ChannelUri prefix(final String prefix)\n    {\n        this.prefix = prefix;\n        return this;\n    }\n\n    /**\n     * The media over which the channel operates.\n     *\n     * @return the media over which the channel operates.\n     */\n    public String media()\n    {\n        return media;\n    }\n\n    /**\n     * Set the media over which the channel operates.\n     *\n     * @param media to replace the parsed value.\n     * @return this for a fluent API.\n     */\n    public ChannelUri media(final String media)\n    {\n        validateMedia(media);\n        this.media = media;\n        return this;\n    }\n\n    /**\n     * Is the channel {@link #media()} equal to {@link CommonContext#UDP_MEDIA}.\n     *\n     * @return true the channel {@link #media()} equals {@link CommonContext#UDP_MEDIA}.\n     */\n    public boolean isUdp()\n    {\n        return UDP_MEDIA.equals(media);\n    }\n\n    /**\n     * Is the channel {@link #media()} equal to {@link CommonContext#IPC_MEDIA}.\n     *\n     * @return true the channel {@link #media()} equals {@link CommonContext#IPC_MEDIA}.\n     */\n    public boolean isIpc()\n    {\n        return IPC_MEDIA.equals(media);\n    }\n\n    /**\n     * The scheme for the URI. Must be \"aeron\".\n     *\n     * @return the scheme for the URI.\n     */\n    public String scheme()\n    {\n        return AERON_SCHEME;\n    }\n\n    /**\n     * Get a value for a given parameter key.\n     *\n     * @param key to lookup.\n     * @return the value if set for the key otherwise null.\n     */\n    public String get(final String key)\n    {\n        return params.get(key);\n    }\n\n    /**\n     * Get the value for a given parameter key or the default value provided if the key does not exist.\n     *\n     * @param key          to lookup.\n     * @param defaultValue to be returned if no key match is found.\n     * @return the value if set for the key otherwise the default value provided.\n     */\n    public String get(final String key, final String defaultValue)\n    {\n        final String value = params.get(key);\n        if (null != value)\n        {\n            return value;\n        }\n\n        return defaultValue;\n    }\n\n    /**\n     * Put a key and value pair in the map of params.\n     *\n     * @param key   of the param to be put.\n     * @param value of the param to be put.\n     * @return the existing value otherwise null.\n     */\n    public String put(final String key, final String value)\n    {\n        return params.put(key, value);\n    }\n\n    /**\n     * Remove a key pair in the map of params.\n     *\n     * @param key of the param to be removed.\n     * @return the previous value of the param or null.\n     */\n    public String remove(final String key)\n    {\n        return params.remove(key);\n    }\n\n    /**\n     * Does the URI contain a value for the given key.\n     *\n     * @param key to be lookup.\n     * @return true if the key has a value otherwise false.\n     */\n    public boolean containsKey(final String key)\n    {\n        return params.containsKey(key);\n    }\n\n    /**\n     * Get the channel tag, if it exists, that refers to another channel.\n     *\n     * @return channel tag if it exists or null if not in this URI.\n     * @see CommonContext#TAGS_PARAM_NAME\n     * @see CommonContext#TAG_PREFIX\n     */\n    public String channelTag()\n    {\n        return tags.length > CHANNEL_TAG_INDEX ? tags[CHANNEL_TAG_INDEX] : null;\n    }\n\n    /**\n     * Get the entity tag, if it exists, that refers to an entity such as subscription or publication.\n     *\n     * @return entity tag if it exists or null if not in this URI.\n     * @see CommonContext#TAGS_PARAM_NAME\n     * @see CommonContext#TAG_PREFIX\n     */\n    public String entityTag()\n    {\n        return tags.length > ENTITY_TAG_INDEX ? tags[ENTITY_TAG_INDEX] : null;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean equals(final Object o)\n    {\n        if (this == o)\n        {\n            return true;\n        }\n\n        if (!(o instanceof ChannelUri))\n        {\n            return false;\n        }\n\n        final ChannelUri that = (ChannelUri)o;\n\n        return Objects.equals(prefix, that.prefix) &&\n            Objects.equals(media, that.media) &&\n            Objects.equals(params, that.params) &&\n            Arrays.equals(tags, that.tags);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int hashCode()\n    {\n        int result = 19;\n        result = 31 * result + Objects.hashCode(prefix);\n        result = 31 * result + Objects.hashCode(media);\n        result = 31 * result + Objects.hashCode(params);\n        result = 31 * result + Arrays.hashCode(tags);\n\n        return result;\n    }\n\n    /**\n     * Generate a String representation of the URI that is valid for an Aeron channel.\n     *\n     * @return a String representation of the URI that is valid for an Aeron channel.\n     */\n    public String toString()\n    {\n        final StringBuilder sb;\n        if (prefix == null || prefix.isEmpty())\n        {\n            sb = new StringBuilder((params.size() * 20) + 10);\n        }\n        else\n        {\n            sb = new StringBuilder((params.size() * 20) + 20);\n            sb.append(prefix);\n            if (!prefix.endsWith(\":\"))\n            {\n                sb.append(':');\n            }\n        }\n\n        sb.append(AERON_PREFIX).append(media);\n\n        if (!params.isEmpty())\n        {\n            sb.append('?');\n\n            for (final Map.Entry<String, String> entry : params.entrySet())\n            {\n                sb.append(entry.getKey()).append('=').append(entry.getValue()).append('|');\n            }\n\n            sb.setLength(sb.length() - 1);\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * Initialise a channel for restarting a publication at a given position.\n     *\n     * @param position      at which the publication should be started.\n     * @param initialTermId what which the stream would start.\n     * @param termLength    for the stream.\n     */\n    public void initialPosition(final long position, final int initialTermId, final int termLength)\n    {\n        if (position < 0 || 0 != (position & (FRAME_ALIGNMENT - 1)))\n        {\n            throw new IllegalArgumentException(\"invalid position: \" + position);\n        }\n\n        final int bitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n        final int termId = LogBufferDescriptor.computeTermIdFromPosition(position, bitsToShift, initialTermId);\n        final int termOffset = (int)(position & (termLength - 1));\n\n        put(INITIAL_TERM_ID_PARAM_NAME, Integer.toString(initialTermId));\n        put(TERM_ID_PARAM_NAME, Integer.toString(termId));\n        put(TERM_OFFSET_PARAM_NAME, Integer.toString(termOffset));\n        put(TERM_LENGTH_PARAM_NAME, Integer.toString(termLength));\n    }\n\n    /**\n     * Parse a {@link CharSequence} which contains an Aeron URI.\n     *\n     * @param uri to be parsed.\n     * @return a new {@link ChannelUri} representing the URI string.\n     */\n    @SuppressWarnings(\"MethodLength\")\n    public static ChannelUri parse(final CharSequence uri)\n    {\n        final int length = uri.length();\n        if (length > MAX_URI_LENGTH)\n        {\n            throw new IllegalArgumentException(\"URI length (\" + length + \") exceeds max supported length (\" +\n                MAX_URI_LENGTH + \"): \" + uri.subSequence(0, MAX_URI_LENGTH));\n        }\n\n        int position = 0;\n        final String prefix;\n        if (startsWith(uri, 0, SPY_PREFIX))\n        {\n            prefix = SPY_QUALIFIER;\n            position = SPY_PREFIX.length();\n        }\n        else\n        {\n            prefix = \"\";\n        }\n\n        if (!startsWith(uri, position, AERON_PREFIX))\n        {\n            throw new IllegalArgumentException(\"Aeron URIs must start with 'aeron:', found: \" + uri);\n        }\n        else\n        {\n            position += AERON_PREFIX.length();\n        }\n\n        final StringBuilder builder = new StringBuilder();\n        final Object2ObjectHashMap<String, String> params = new Object2ObjectHashMap<>();\n        String media = null;\n        String key = null;\n\n        State state = State.MEDIA;\n        for (int i = position; i < length; i++)\n        {\n            final char c = uri.charAt(i);\n            switch (state)\n            {\n                case MEDIA:\n                    switch (c)\n                    {\n                        case '?':\n                            media = builder.toString();\n                            builder.setLength(0);\n                            state = State.PARAMS_KEY;\n                            break;\n\n                        case ':':\n                        case '|':\n                        case '=':\n                            throw new IllegalArgumentException(\n                                \"encountered '\" + c + \"' within media definition at index \" + i + \" in \" + uri);\n\n                        default:\n                            builder.append(c);\n                    }\n                    break;\n\n                case PARAMS_KEY:\n                    if (c == '=')\n                    {\n                        if (builder.isEmpty())\n                        {\n                            throw new IllegalStateException(\"empty key not allowed at index \" + i + \" in \" + uri);\n                        }\n                        key = builder.toString();\n                        builder.setLength(0);\n                        state = State.PARAMS_VALUE;\n                    }\n                    else\n                    {\n                        if (c == '|')\n                        {\n                            throw new IllegalStateException(\"invalid end of key at index \" + i + \" in \" + uri);\n                        }\n                        builder.append(c);\n                    }\n                    break;\n\n                case PARAMS_VALUE:\n                    if (c == '|')\n                    {\n                        params.put(key, builder.toString());\n                        builder.setLength(0);\n                        state = State.PARAMS_KEY;\n                    }\n                    else\n                    {\n                        builder.append(c);\n                    }\n                    break;\n\n                default:\n                    throw new IllegalStateException(\"unexpected state=\" + state + \" in \" + uri);\n            }\n        }\n\n        switch (state)\n        {\n            case MEDIA:\n                media = builder.toString();\n                validateMedia(media);\n                break;\n\n            case PARAMS_VALUE:\n                params.put(key, builder.toString());\n                break;\n\n            default:\n                throw new IllegalStateException(\"no more input found, state=\" + state + \" in \" + uri);\n        }\n\n        return new ChannelUri(prefix, media, params);\n    }\n\n    /**\n     * Add a sessionId to a given channel.\n     *\n     * @param channel   to add sessionId to.\n     * @param sessionId to add to channel.\n     * @return new string that represents channel with sessionId added.\n     */\n    public static String addSessionId(final String channel, final int sessionId)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(channel);\n        channelUri.put(CommonContext.SESSION_ID_PARAM_NAME, Integer.toString(sessionId));\n\n        return channelUri.toString();\n    }\n\n    /**\n     * Add alias to the uri if none exists.\n     *\n     * @param uri   to add alias to.\n     * @param alias to add to the uri.\n     * @return original uri if alias is empty or one is already defined, otherwise new uri with an alias.\n     */\n    public static String addAliasIfAbsent(final String uri, final String alias)\n    {\n        if (!Strings.isEmpty(alias))\n        {\n            final ChannelUri channelUri = ChannelUri.parse(uri);\n            if (!channelUri.containsKey(CommonContext.ALIAS_PARAM_NAME))\n            {\n                channelUri.put(CommonContext.ALIAS_PARAM_NAME, alias);\n                return channelUri.toString();\n            }\n        }\n        return uri;\n    }\n\n    /**\n     * Transforms an alias of the given uri using the given function. The function will be invoked with the original\n     * alias or null if absent. The function's return value will be used as the alias in the returned uri. If the\n     * function returns null or an empty string, the alias will be removed.\n     *\n     * @param uri      the uri to transform the alias of.\n     * @param function the transformation function.\n     * @return uri equivalent to the one passed in with its alias transformed.\n     */\n    public static String transformAlias(final String uri, final Function<String, String> function)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(uri);\n        final String originalAlias = channelUri.get(CommonContext.ALIAS_PARAM_NAME);\n        final String transformedAlias = function.apply(originalAlias);\n        if (Strings.isEmpty(transformedAlias))\n        {\n            channelUri.remove(CommonContext.ALIAS_PARAM_NAME);\n        }\n        else\n        {\n            channelUri.put(CommonContext.ALIAS_PARAM_NAME, transformedAlias);\n        }\n        return channelUri.toString();\n    }\n\n    /**\n     * Is the param value tagged? (starts with the \"tag:\" prefix).\n     *\n     * @param paramValue to check if tagged.\n     * @return true if tagged or false if not.\n     * @see CommonContext#TAGS_PARAM_NAME\n     * @see CommonContext#TAG_PREFIX\n     */\n    public static boolean isTagged(final String paramValue)\n    {\n        return startsWith(paramValue, 0, TAG_PREFIX);\n    }\n\n    /**\n     * Get the value of the tag from a given parameter value.\n     *\n     * @param paramValue to extract the tag value from.\n     * @return the value of the tag or {@link #INVALID_TAG} if not tagged.\n     * @see CommonContext#TAGS_PARAM_NAME\n     * @see CommonContext#TAG_PREFIX\n     */\n    public static long getTag(final String paramValue)\n    {\n        return isTagged(paramValue) ?\n            AsciiEncoding.parseLongAscii(paramValue, 4, paramValue.length() - 4) : INVALID_TAG;\n    }\n\n    /**\n     * Create a channel URI for a destination, i.e. a channel that uses {@code media} and {@code interface} parameters\n     * of the original channel and adds specified {@code endpoint} to it. For example given the input channel is\n     * {@code aeron:udp?mtu=1440|ttl=0|endpoint=localhost:8090|term-length=128k|interface=eth0} and the endpoint is\n     * {@code 192.168.0.14} the output of this method will be {@code aeron:udp?endpoint=192.168.0.14|interface=eth0}.\n     *\n     * @param channel  for which the destination is being added.\n     * @param endpoint for the target destination.\n     * @return new channel URI for a destination.\n     */\n    public static String createDestinationUri(final String channel, final String endpoint)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(channel);\n        final String uri = AERON_PREFIX + channelUri.media() + \"?\" + ENDPOINT_PARAM_NAME + \"=\" + endpoint;\n        final String networkInterface = channelUri.get(INTERFACE_PARAM_NAME);\n\n        if (null != networkInterface)\n        {\n            return uri + \"|\" + INTERFACE_PARAM_NAME + \"=\" + networkInterface;\n        }\n\n        return uri;\n    }\n\n    /**\n     * Uses the supplied endpoint to resolve any wildcard ports. If the existing endpoint has a value of \"0\" for then\n     * the port of this endpoint will be used instead. If the endpoint is not specified in this uri, then the whole\n     * supplied endpoint is used. If the endpoint exists and has a non-wildcard port, then the existing endpoint is\n     * retained.\n     *\n     * @param resolvedEndpoint The endpoint to supply a resolved endpoint port.\n     * @throws IllegalArgumentException if the supplied resolvedEndpoint does not have a port or the port is zero.\n     * @throws NullPointerException     if the supplied resolvedEndpoint is null\n     */\n    public void replaceEndpointWildcardPort(final String resolvedEndpoint)\n    {\n        final int portSeparatorIndex = requireNonNull(resolvedEndpoint, \"resolvedEndpoint is null\").lastIndexOf(':');\n        if (-1 == portSeparatorIndex)\n        {\n            throw new IllegalArgumentException(\"No port specified on resolvedEndpoint=\" + resolvedEndpoint);\n        }\n        if (resolvedEndpoint.endsWith(\":0\"))\n        {\n            throw new IllegalArgumentException(\"Wildcard port specified on resolvedEndpoint=\" + resolvedEndpoint);\n        }\n\n        final String existingEndpoint = get(ENDPOINT_PARAM_NAME);\n        if (null == existingEndpoint)\n        {\n            put(ENDPOINT_PARAM_NAME, resolvedEndpoint);\n        }\n        else if (existingEndpoint.endsWith(\":0\"))\n        {\n            final String endpoint = existingEndpoint.substring(0, existingEndpoint.length() - 2) +\n                resolvedEndpoint.substring(resolvedEndpoint.lastIndexOf(':'));\n            put(ENDPOINT_PARAM_NAME, endpoint);\n        }\n    }\n\n    /**\n     * Call consumer for each parameter defined in the URI.\n     *\n     * @param consumer to be invoked for each parameter.\n     */\n    public void forEachParameter(final BiConsumer<String, String> consumer)\n    {\n        params.forEach(consumer);\n    }\n\n    /**\n     * Determines if this channel has specified <code>control-mode=response</code>.\n     *\n     * @return true if this channel has specified <code>control-mode=response</code>.\n     */\n    public boolean hasControlModeResponse()\n    {\n        return CONTROL_MODE_RESPONSE.equals(get(MDC_CONTROL_MODE_PARAM_NAME));\n    }\n\n    /**\n     * Take an endpoint with the format <code>host:port</code> and replace the port with a wildcard (<code>0</code>).\n     *\n     * @param endpoint to replace with a wildcard.\n     * @return  the transformed value.\n     */\n    public static String replacePortWithWildcard(final String endpoint)\n    {\n        if (null == endpoint)\n        {\n            return null;\n        }\n\n        final int i = endpoint.lastIndexOf(':');\n        if (-1 == i)\n        {\n            return null;\n        }\n\n        return endpoint.substring(0, i + 1) + \"0\";\n    }\n\n    /**\n     * Determines if the supplied channel has specified <code>control-mode=response</code>.\n     *\n     * @param channelUri to check if the control mode is response\n     * @return true if the supplied channel has specified <code>control-mode=response</code>.\n     */\n    public static boolean isControlModeResponse(final String channelUri)\n    {\n        return parse(channelUri).hasControlModeResponse();\n    }\n\n    private static void validateMedia(final String media)\n    {\n        if (IPC_MEDIA.equals(media) || UDP_MEDIA.equals(media))\n        {\n            return;\n        }\n\n        throw new IllegalArgumentException(\"unknown media: \" + media);\n    }\n\n    private static boolean startsWith(final CharSequence input, final int position, final String prefix)\n    {\n        if ((input.length() - position) < prefix.length())\n        {\n            return false;\n        }\n\n        for (int i = 0; i < prefix.length(); i++)\n        {\n            if (input.charAt(position + i) != prefix.charAt(i))\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private static String[] splitTags(final String tagsValue)\n    {\n        String[] tags = ArrayUtil.EMPTY_STRING_ARRAY;\n\n        if (null != tagsValue)\n        {\n            final int tagCount = countTags(tagsValue);\n            if (tagCount == 1)\n            {\n                tags = new String[]{ tagsValue };\n            }\n            else\n            {\n                int tagStartPosition = 0;\n                int tagIndex = 0;\n                tags = new String[tagCount];\n\n                for (int i = 0, length = tagsValue.length(); i < length; i++)\n                {\n                    if (tagsValue.charAt(i) == ',')\n                    {\n                        tags[tagIndex++] = tagsValue.substring(tagStartPosition, i);\n                        tagStartPosition = i + 1;\n\n                        if (tagIndex >= (tagCount - 1))\n                        {\n                            tags[tagIndex] = tagsValue.substring(tagStartPosition, length);\n                        }\n                    }\n                }\n            }\n        }\n\n        return tags;\n    }\n\n    private static int countTags(final String tags)\n    {\n        int count = 1;\n\n        for (int i = 0, length = tags.length(); i < length; i++)\n        {\n            if (tags.charAt(i) == ',')\n            {\n                ++count;\n            }\n        }\n\n        return count;\n    }\n\n    Map<String, String> diff(final ChannelUri that)\n    {\n        final HashMap<String, String> differingValues = new HashMap<>();\n\n        if (!Objects.equals(prefix, that.prefix))\n        {\n            differingValues.put(\"prefix\", prefix + \" != \" + that.prefix);\n        }\n\n        if (!Objects.equals(media, that.media))\n        {\n            differingValues.put(\"media\", media + \" != \" + that.media);\n        }\n\n        if (!Objects.equals(params, that.params))\n        {\n            params.forEach(\n                (key, value) ->\n                {\n                    final String thatValue = that.params.get(key);\n                    if (!Objects.equals(value, thatValue))\n                    {\n                        differingValues.put(key, value + \" != \" + thatValue);\n                    }\n                });\n        }\n\n        if (!Arrays.equals(tags, that.tags))\n        {\n            differingValues.put(TAGS_PARAM_NAME, Arrays.toString(tags) + \" != \" + Arrays.toString(that.tags));\n        }\n\n        return differingValues;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ChannelUriStringBuilder.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.SystemUtil;\n\nimport static io.aeron.ChannelUri.SPY_QUALIFIER;\nimport static io.aeron.CommonContext.*;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MAX_LENGTH;\nimport static org.agrona.SystemUtil.*;\n\n/**\n * Typesafe means of building a channel URI associated with a {@link Publication} or {@link Subscription}.\n *\n * @see Aeron#addPublication(String, int)\n * @see Aeron#addSubscription(String, int)\n * @see ChannelUri\n */\npublic final class ChannelUriStringBuilder\n{\n    /**\n     * Can be used when the likes of session-id wants to reference another entity such as a tagged publication.\n     * <p>\n     * For example {@code session-id=tag:777} where the publication uses {@code tags=777}.\n     */\n    public static final String TAG_PREFIX = \"tag:\";\n\n    private final StringBuilder sb = new StringBuilder(64);\n\n    private String prefix;\n    private String media;\n    private String endpoint;\n    private String networkInterface;\n    private String controlEndpoint;\n    private String controlMode;\n    private String tags;\n    private String alias;\n    private String cc;\n    private String fc;\n    private String mediaReceiveTimestampOffset;\n    private String channelReceiveTimestampOffset;\n    private String channelSendTimestampOffset;\n    private String responseCorrelationId;\n    private String responseEndpoint;\n    private Boolean reliable;\n    private Boolean sparse;\n    private Boolean eos;\n    private Boolean tether;\n    private Boolean group;\n    private Boolean rejoin;\n    private Boolean ssc;\n    private Integer ttl;\n    private Integer mtu;\n    private Integer termLength;\n    private Integer initialTermId;\n    private Integer termId;\n    private Integer termOffset;\n    private Integer socketSndbufLength;\n    private Integer socketRcvbufLength;\n    private Integer receiverWindowLength;\n    private Integer maxResend;\n    private Integer streamId;\n    private Integer publicationWindowLength;\n    private Long sessionId;\n    private Long groupTag;\n    private Long linger;\n    private Long nakDelay;\n    private Long untetheredWindowLimitTimeoutNs;\n    private Long untetheredLingerTimeoutNs;\n    private Long untetheredRestingTimeoutNs;\n    private boolean isSessionIdTagged;\n\n    /**\n     * Default constructor.\n     */\n    public ChannelUriStringBuilder()\n    {\n    }\n\n    /**\n     * Constructs the ChannelUriStringBuilder with the initial values derived from the supplied URI. Will parse the\n     * incoming URI during this process, so could through an exception at this point of the URI is badly formed.\n     *\n     * @param initialUri initial values for the builder.\n     */\n    public ChannelUriStringBuilder(final String initialUri)\n    {\n        this(ChannelUri.parse(initialUri));\n    }\n\n    /**\n     * Constructs the ChannelUriStringBuilder with the initial values derived from the supplied ChannelUri.\n     *\n     * @param channelUri initial values for the builder.\n     */\n    public ChannelUriStringBuilder(final ChannelUri channelUri)\n    {\n        isSessionIdTagged = false;\n\n        prefix(channelUri);\n        media(channelUri);\n        endpoint(channelUri);\n        networkInterface(channelUri);\n        controlEndpoint(channelUri);\n        controlMode(channelUri);\n        tags(channelUri);\n        alias(channelUri);\n        congestionControl(channelUri);\n        flowControl(channelUri);\n        reliable(channelUri);\n        ttl(channelUri);\n        mtu(channelUri);\n        termLength(channelUri);\n        initialTermId(channelUri);\n        termId(channelUri);\n        termOffset(channelUri);\n        sessionId(channelUri);\n        group(channelUri);\n        linger(channelUri);\n        sparse(channelUri);\n        eos(channelUri);\n        tether(channelUri);\n        groupTag(channelUri);\n        rejoin(channelUri);\n        spiesSimulateConnection(channelUri);\n        socketRcvbufLength(channelUri);\n        socketSndbufLength(channelUri);\n        receiverWindowLength(channelUri);\n        mediaReceiveTimestampOffset(channelUri);\n        channelReceiveTimestampOffset(channelUri);\n        channelSendTimestampOffset(channelUri);\n        responseEndpoint(channelUri);\n        responseCorrelationId(channelUri);\n        nakDelay(channelUri);\n        untetheredWindowLimitTimeout(channelUri);\n        untetheredLingerTimeout(channelUri);\n        untetheredRestingTimeout(channelUri);\n        maxResend(channelUri);\n        streamId(channelUri);\n        publicationWindowLength(channelUri);\n    }\n\n    /**\n     * Clear out all the values thus setting back to the initial state.\n     *\n     * @return this for a fluent API.\n     */\n    public ChannelUriStringBuilder clear()\n    {\n        prefix = null;\n        media = null;\n        endpoint = null;\n        networkInterface = null;\n        controlEndpoint = null;\n        controlMode = null;\n        tags = null;\n        alias = null;\n        cc = null;\n        fc = null;\n        reliable = null;\n        ttl = null;\n        mtu = null;\n        termLength = null;\n        initialTermId = null;\n        termId = null;\n        termOffset = null;\n        sessionId = null;\n        groupTag = null;\n        linger = null;\n        sparse = null;\n        eos = null;\n        tether = null;\n        group = null;\n        rejoin = null;\n        isSessionIdTagged = false;\n        socketRcvbufLength = null;\n        socketSndbufLength = null;\n        receiverWindowLength = null;\n        mediaReceiveTimestampOffset = null;\n        channelReceiveTimestampOffset = null;\n        channelSendTimestampOffset = null;\n        responseEndpoint = null;\n        responseCorrelationId = null;\n        maxResend = null;\n        streamId = null;\n        publicationWindowLength = null;\n\n        return this;\n    }\n\n    /**\n     * Validates that the collection of set parameters are valid together.\n     *\n     * @return this for a fluent API.\n     * @throws IllegalArgumentException if the combination of params is invalid.\n     */\n    public ChannelUriStringBuilder validate()\n    {\n        if (null == media)\n        {\n            throw new IllegalArgumentException(\"media type is mandatory\");\n        }\n\n        if (CommonContext.UDP_MEDIA.equals(media) && (null == endpoint && null == controlEndpoint))\n        {\n            throw new IllegalArgumentException(\"either 'endpoint' or 'control' must be specified for UDP.\");\n        }\n\n        final boolean anyNonNull = null != initialTermId || null != termId || null != termOffset;\n        final boolean anyNull = null == initialTermId || null == termId || null == termOffset;\n        if (anyNonNull)\n        {\n            if (anyNull)\n            {\n                throw new IllegalArgumentException(\n                    \"either all or none of the parameters ['initialTermId', 'termId', 'termOffset'] must be provided\");\n            }\n\n            if (termId - initialTermId < 0)\n            {\n                throw new IllegalArgumentException(\n                    \"difference greater than 2^31 - 1: termId=\" + termId + \" - initialTermId=\" + initialTermId);\n            }\n\n            if (null != termLength && termOffset > termLength)\n            {\n                throw new IllegalArgumentException(\"termOffset=\" + termOffset + \" > termLength=\" + termLength);\n            }\n        }\n\n        return this;\n    }\n\n    /**\n     * Set the prefix for taking an additional action such as spying on an outgoing publication with \"aeron-spy\".\n     *\n     * @param prefix to be applied to the URI before the scheme.\n     * @return this for a fluent API.\n     * @see ChannelUri#SPY_QUALIFIER\n     */\n    public ChannelUriStringBuilder prefix(final String prefix)\n    {\n        if (null != prefix && !prefix.isEmpty() && !prefix.equals(SPY_QUALIFIER))\n        {\n            throw new IllegalArgumentException(\"invalid prefix: \" + prefix);\n        }\n\n        this.prefix = prefix;\n        return this;\n    }\n\n    /**\n     * Set the prefix value to be what is in the {@link ChannelUri}.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see ChannelUri#SPY_QUALIFIER\n     */\n    public ChannelUriStringBuilder prefix(final ChannelUri channelUri)\n    {\n        return prefix(channelUri.prefix());\n    }\n\n    /**\n     * Get the prefix for the additional action to be taken on the request.\n     *\n     * @return the prefix for the additional action to be taken on the request.\n     */\n    public String prefix()\n    {\n        return prefix;\n    }\n\n    /**\n     * Set the media for this channel. Valid values are \"udp\" and \"ipc\".\n     *\n     * @param media for this channel.\n     * @return this for a fluent API.\n     */\n    public ChannelUriStringBuilder media(final String media)\n    {\n        switch (media)\n        {\n            case CommonContext.UDP_MEDIA:\n            case CommonContext.IPC_MEDIA:\n                break;\n\n            default:\n                throw new IllegalArgumentException(\"invalid media: \" + media);\n        }\n\n        this.media = media;\n        return this;\n    }\n\n    /**\n     * Set the endpoint value to be what is in the {@link ChannelUri}.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     */\n    public ChannelUriStringBuilder media(final ChannelUri channelUri)\n    {\n        return media(channelUri.media());\n    }\n\n    /**\n     * The media over which the channel transmits.\n     *\n     * @return the media over which the channel transmits.\n     */\n    public String media()\n    {\n        return media;\n    }\n\n    /**\n     * Set the endpoint address:port pairing for the channel. This is the address the publication sends to and the\n     * address the subscription receives from.\n     *\n     * @param endpoint address and port for the channel.\n     * @return this for a fluent API.\n     * @see CommonContext#ENDPOINT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder endpoint(final String endpoint)\n    {\n        this.endpoint = endpoint;\n        return this;\n    }\n\n    /**\n     * Set the endpoint value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#ENDPOINT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder endpoint(final ChannelUri channelUri)\n    {\n        return endpoint(channelUri.get(ENDPOINT_PARAM_NAME));\n    }\n\n    /**\n     * Get the endpoint address:port pairing for the channel.\n     *\n     * @return the endpoint address:port pairing for the channel.\n     * @see CommonContext#ENDPOINT_PARAM_NAME\n     */\n    public String endpoint()\n    {\n        return endpoint;\n    }\n\n    /**\n     * Set the address of the local interface in the form host:[port]/[subnet mask] for routing traffic.\n     *\n     * @param networkInterface for routing traffic.\n     * @return this for a fluent API.\n     * @see CommonContext#INTERFACE_PARAM_NAME\n     */\n    public ChannelUriStringBuilder networkInterface(final String networkInterface)\n    {\n        this.networkInterface = networkInterface;\n        return this;\n    }\n\n    /**\n     * Set the network interface value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#INTERFACE_PARAM_NAME\n     */\n    public ChannelUriStringBuilder networkInterface(final ChannelUri channelUri)\n    {\n        return networkInterface(channelUri.get(INTERFACE_PARAM_NAME));\n    }\n\n    /**\n     * Get the address of the local interface in the form host:[port]/[subnet mask] for routing traffic.\n     *\n     * @return the address of the local interface in the form host:[port]/[subnet mask] for routing traffic.\n     * @see CommonContext#INTERFACE_PARAM_NAME\n     */\n    public String networkInterface()\n    {\n        return networkInterface;\n    }\n\n    /**\n     * Set the control address:port pair for dynamically joining a multi-destination-cast publication.\n     *\n     * @param controlEndpoint for joining MDC control socket.\n     * @return this for a fluent API.\n     * @see CommonContext#MDC_CONTROL_PARAM_NAME\n     */\n    public ChannelUriStringBuilder controlEndpoint(final String controlEndpoint)\n    {\n        this.controlEndpoint = controlEndpoint;\n        return this;\n    }\n\n    /**\n     * Set the control endpoint value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#MDC_CONTROL_PARAM_NAME\n     */\n    public ChannelUriStringBuilder controlEndpoint(final ChannelUri channelUri)\n    {\n        return controlEndpoint(channelUri.get(MDC_CONTROL_PARAM_NAME));\n    }\n\n    /**\n     * Get the control address:port pair for dynamically joining a multi-destination-cast publication.\n     *\n     * @return the control address:port pair for dynamically joining a multi-destination-cast publication.\n     * @see CommonContext#MDC_CONTROL_PARAM_NAME\n     */\n    public String controlEndpoint()\n    {\n        return controlEndpoint;\n    }\n\n    /**\n     * Set the control mode for multi-destination-cast. Set to \"manual\" for allowing control from the publication API.\n     *\n     * @param controlMode for taking control of MDC.\n     * @return this for a fluent API.\n     * @see Publication#addDestination(String)\n     * @see Publication#removeDestination(String)\n     * @see CommonContext#MDC_CONTROL_MODE_PARAM_NAME\n     * @see CommonContext#MDC_CONTROL_MODE_MANUAL\n     * @see CommonContext#MDC_CONTROL_MODE_DYNAMIC\n     * @see CommonContext#CONTROL_MODE_RESPONSE\n     */\n    public ChannelUriStringBuilder controlMode(final String controlMode)\n    {\n        if (null != controlMode &&\n            !controlMode.equals(MDC_CONTROL_MODE_MANUAL) &&\n            !controlMode.equals(MDC_CONTROL_MODE_DYNAMIC) &&\n            !controlMode.equals(CONTROL_MODE_RESPONSE))\n        {\n            throw new IllegalArgumentException(\"invalid control mode: \" + controlMode);\n        }\n\n        this.controlMode = controlMode;\n        return this;\n    }\n\n    /**\n     * Set the control mode to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#MDC_CONTROL_MODE_PARAM_NAME\n     */\n    public ChannelUriStringBuilder controlMode(final ChannelUri channelUri)\n    {\n        return controlMode(channelUri.get(MDC_CONTROL_MODE_PARAM_NAME));\n    }\n\n    /**\n     * Get the control mode for multi-destination-cast.\n     *\n     * @return the control mode for multi-destination-cast.\n     * @see CommonContext#MDC_CONTROL_MODE_PARAM_NAME\n     * @see CommonContext#MDC_CONTROL_MODE_MANUAL\n     * @see CommonContext#MDC_CONTROL_MODE_DYNAMIC\n     * @see CommonContext#CONTROL_MODE_RESPONSE\n     */\n    public String controlMode()\n    {\n        return controlMode;\n    }\n\n    /**\n     * Set the subscription semantics for if loss is acceptable, or not, for a reliable message delivery.\n     *\n     * @param isReliable false if loss can be gap-filled.\n     * @return this for a fluent API.\n     * @see CommonContext#RELIABLE_STREAM_PARAM_NAME\n     */\n    public ChannelUriStringBuilder reliable(final Boolean isReliable)\n    {\n        this.reliable = isReliable;\n        return this;\n    }\n\n    /**\n     * Set the reliable value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#RELIABLE_STREAM_PARAM_NAME\n     */\n    public ChannelUriStringBuilder reliable(final ChannelUri channelUri)\n    {\n        final String reliableValue = channelUri.get(RELIABLE_STREAM_PARAM_NAME);\n        if (null == reliableValue)\n        {\n            reliable = null;\n            return this;\n        }\n        else\n        {\n            return reliable(Boolean.valueOf(reliableValue));\n        }\n    }\n\n    /**\n     * Get the subscription semantics for if loss is acceptable, or not, for a reliable message delivery.\n     *\n     * @return the subscription semantics for if loss is acceptable, or not, for a reliable message delivery.\n     * @see CommonContext#RELIABLE_STREAM_PARAM_NAME\n     */\n    public Boolean reliable()\n    {\n        return reliable;\n    }\n\n    /**\n     * Set the Time To Live (TTL) for a multicast datagram. Valid values are 0-255 for the number of hops the datagram\n     * can progress along.\n     *\n     * @param ttl value for a multicast datagram.\n     * @return this for a fluent API.\n     * @see CommonContext#TTL_PARAM_NAME\n     */\n    public ChannelUriStringBuilder ttl(final Integer ttl)\n    {\n        if (null != ttl && (ttl < 0 || ttl > 255))\n        {\n            throw new IllegalArgumentException(\"TTL not in range 0-255: \" + ttl);\n        }\n\n        this.ttl = ttl;\n        return this;\n    }\n\n    /**\n     * Set the ttl value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#TTL_PARAM_NAME\n     */\n    public ChannelUriStringBuilder ttl(final ChannelUri channelUri)\n    {\n        final String ttlValue = channelUri.get(TTL_PARAM_NAME);\n        if (null == ttlValue)\n        {\n            ttl = null;\n            return this;\n        }\n        else\n        {\n            try\n            {\n                return ttl(Integer.valueOf(ttlValue));\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\"'ttl' must be a value integer\", ex);\n            }\n        }\n    }\n\n    /**\n     * Get the Time To Live (TTL) for a multicast datagram.\n     *\n     * @return the Time To Live (TTL) for a multicast datagram.\n     * @see CommonContext#TTL_PARAM_NAME\n     */\n    public Integer ttl()\n    {\n        return ttl;\n    }\n\n    /**\n     * Set the maximum transmission unit (MTU) including Aeron header for a datagram payload. If this is greater\n     * than the network MTU for UDP then the packet will be fragmented and can amplify the impact of loss.\n     *\n     * @param mtu the maximum transmission unit including Aeron header for a datagram payload.\n     * @return this for a fluent API.\n     * @see CommonContext#MTU_LENGTH_PARAM_NAME\n     */\n    public ChannelUriStringBuilder mtu(final Integer mtu)\n    {\n        if (null != mtu)\n        {\n            if (mtu < 32 || mtu > 65504)\n            {\n                throw new IllegalArgumentException(\"MTU not in range 32-65504: \" + mtu);\n            }\n\n            if ((mtu & (FRAME_ALIGNMENT - 1)) != 0)\n            {\n                throw new IllegalArgumentException(\"MTU not a multiple of FRAME_ALIGNMENT: mtu=\" + mtu);\n            }\n        }\n\n        this.mtu = mtu;\n        return this;\n    }\n\n    /**\n     * Set the mtu value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#MTU_LENGTH_PARAM_NAME\n     */\n    public ChannelUriStringBuilder mtu(final ChannelUri channelUri)\n    {\n        final String mtuValue = channelUri.get(MTU_LENGTH_PARAM_NAME);\n        if (null == mtuValue)\n        {\n            mtu = null;\n            return this;\n        }\n        else\n        {\n            final long value = parseSize(MTU_LENGTH_PARAM_NAME, mtuValue);\n            if (value > Integer.MAX_VALUE)\n            {\n                throw new IllegalArgumentException(MTU_LENGTH_PARAM_NAME + \" \" + value + \" > \" + Integer.MAX_VALUE);\n            }\n\n            return mtu((int)value);\n        }\n    }\n\n    /**\n     * Get the maximum transmission unit (MTU) including Aeron header for a datagram payload. If this is greater\n     * than the network MTU for UDP then the packet will be fragmented and can amplify the impact of loss.\n     *\n     * @return the maximum transmission unit (MTU) including Aeron header for a datagram payload.\n     * @see CommonContext#MTU_LENGTH_PARAM_NAME\n     */\n    public Integer mtu()\n    {\n        return mtu;\n    }\n\n    /**\n     * Set the length of buffer used for each term of the log. Valid values are powers of 2 in the 64K - 1G range.\n     *\n     * @param termLength of the buffer used for each term of the log.\n     * @return this for a fluent API.\n     * @see CommonContext#TERM_LENGTH_PARAM_NAME\n     */\n    public ChannelUriStringBuilder termLength(final Integer termLength)\n    {\n        if (null != termLength)\n        {\n            LogBufferDescriptor.checkTermLength(termLength);\n        }\n\n        this.termLength = termLength;\n        return this;\n    }\n\n    /**\n     * Set the termLength value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#TERM_LENGTH_PARAM_NAME\n     */\n    public ChannelUriStringBuilder termLength(final ChannelUri channelUri)\n    {\n        final String termLengthValue = channelUri.get(TERM_LENGTH_PARAM_NAME);\n        if (null == termLengthValue)\n        {\n            termLength = null;\n            return this;\n        }\n        else\n        {\n            final long value = parseSize(TERM_LENGTH_PARAM_NAME, termLengthValue);\n            if (value > Integer.MAX_VALUE)\n            {\n                throw new IllegalArgumentException(\n                    \"term length more than max length of \" + TERM_MAX_LENGTH + \": value=\" + value);\n            }\n\n            return termLength((int)value);\n        }\n    }\n\n    /**\n     * Get the length of buffer used for each term of the log.\n     *\n     * @return the length of buffer used for each term of the log.\n     * @see CommonContext#TERM_LENGTH_PARAM_NAME\n     */\n    public Integer termLength()\n    {\n        return termLength;\n    }\n\n    /**\n     * Set the initial term id at which a publication will start.\n     *\n     * @param initialTermId the initial term id at which a publication will start.\n     * @return this for a fluent API.\n     * @see CommonContext#INITIAL_TERM_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder initialTermId(final Integer initialTermId)\n    {\n        this.initialTermId = initialTermId;\n        return this;\n    }\n\n    /**\n     * Set the initialTermId value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#INITIAL_TERM_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder initialTermId(final ChannelUri channelUri)\n    {\n        final String initialTermIdValue = channelUri.get(INITIAL_TERM_ID_PARAM_NAME);\n        if (null == initialTermIdValue)\n        {\n            initialTermId = null;\n            return this;\n        }\n        else\n        {\n            try\n            {\n                return initialTermId(Integer.valueOf(initialTermIdValue));\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\"'initial-term-id' must be a valid integer\", ex);\n            }\n        }\n    }\n\n    /**\n     * the initial term id at which a publication will start.\n     *\n     * @return the initial term id at which a publication will start.\n     * @see CommonContext#INITIAL_TERM_ID_PARAM_NAME\n     */\n    public Integer initialTermId()\n    {\n        return initialTermId;\n    }\n\n    /**\n     * Set the current term id at which a publication will start. This when combined with the initial term can\n     * establish a starting position.\n     *\n     * @param termId at which a publication will start.\n     * @return this for a fluent API.\n     * @see CommonContext#TERM_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder termId(final Integer termId)\n    {\n        this.termId = termId;\n        return this;\n    }\n\n    /**\n     * Set the termId value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#TERM_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder termId(final ChannelUri channelUri)\n    {\n        final String termIdValue = channelUri.get(TERM_ID_PARAM_NAME);\n        if (null == termIdValue)\n        {\n            termId = null;\n            return this;\n        }\n        else\n        {\n            try\n            {\n                return termId(Integer.valueOf(termIdValue));\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\"'term-id' must be a valid integer\", ex);\n            }\n        }\n    }\n\n    /**\n     * Get the current term id at which a publication will start.\n     *\n     * @return the current term id at which a publication will start.\n     * @see CommonContext#TERM_ID_PARAM_NAME\n     */\n    public Integer termId()\n    {\n        return termId;\n    }\n\n    /**\n     * Set the offset within a term at which a publication will start. This when combined with the term id can establish\n     * a starting position.\n     *\n     * @param termOffset within a term at which a publication will start.\n     * @return this for a fluent API.\n     * @see CommonContext#TERM_OFFSET_PARAM_NAME\n     */\n    public ChannelUriStringBuilder termOffset(final Integer termOffset)\n    {\n        if (null != termOffset)\n        {\n            if ((termOffset < 0 || termOffset > TERM_MAX_LENGTH))\n            {\n                throw new IllegalArgumentException(\"term offset not in range 0-1g: \" + termOffset);\n            }\n\n            if (0 != (termOffset & (FRAME_ALIGNMENT - 1)))\n            {\n                throw new IllegalArgumentException(\"term offset not multiple of FRAME_ALIGNMENT: \" + termOffset);\n            }\n        }\n\n        this.termOffset = termOffset;\n        return this;\n    }\n\n    /**\n     * Set the termOffset value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#TERM_OFFSET_PARAM_NAME\n     */\n    public ChannelUriStringBuilder termOffset(final ChannelUri channelUri)\n    {\n        final String termOffsetValue = channelUri.get(TERM_OFFSET_PARAM_NAME);\n        if (null == termOffsetValue)\n        {\n            termOffset = null;\n            return this;\n        }\n        else\n        {\n            try\n            {\n                return termOffset(Integer.valueOf(termOffsetValue));\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\"'term-offset' must be a valid integer\", ex);\n            }\n        }\n    }\n\n    /**\n     * Get the offset within a term at which a publication will start.\n     *\n     * @return the offset within a term at which a publication will start.\n     * @see CommonContext#TERM_OFFSET_PARAM_NAME\n     */\n    public Integer termOffset()\n    {\n        return termOffset;\n    }\n\n    /**\n     * Set the session id for a publication or restricted subscription.\n     *\n     * @param sessionId for the publication or a restricted subscription.\n     * @return this for a fluent API.\n     * @see CommonContext#SESSION_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder sessionId(final Integer sessionId)\n    {\n        this.sessionId = null != sessionId ? sessionId.longValue() : null;\n        return this;\n    }\n\n    /**\n     * Set the session id for a publication or restricted subscription from a formatted string.  Supports a format of\n     * either a string encoded signed 32-bit number or 'tag:' followed by a signed 64 bit value.\n     *\n     * @param sessionIdStr for the publication or a restricted subscription.\n     * @return this for a fluent API.\n     * @see CommonContext#SESSION_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder sessionId(final String sessionIdStr)\n    {\n        if (null != sessionIdStr)\n        {\n            if (ChannelUri.isTagged(sessionIdStr))\n            {\n                taggedSessionId(ChannelUri.getTag(sessionIdStr));\n            }\n            else\n            {\n                isSessionIdTagged(false);\n                try\n                {\n                    sessionId(Integer.valueOf(sessionIdStr));\n                }\n                catch (final NumberFormatException ex)\n                {\n                    throw new IllegalArgumentException(\"'session-id' must be a valid integer\", ex);\n                }\n            }\n        }\n        else\n        {\n            sessionId((Integer)null);\n        }\n\n        return this;\n    }\n\n    /**\n     * Set the session id for a publication or restricted subscription as a tag referenced value.\n     *\n     * @param sessionId for the publication or a restricted subscription.\n     * @return this for a fluent API.\n     * @see CommonContext#SESSION_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder taggedSessionId(final Long sessionId)\n    {\n        isSessionIdTagged(true);\n        this.sessionId = sessionId;\n        return this;\n    }\n\n    /**\n     * Set the sessionId value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#SESSION_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder sessionId(final ChannelUri channelUri)\n    {\n        return sessionId(channelUri.get(SESSION_ID_PARAM_NAME));\n    }\n\n    /**\n     * Get the session id for a publication or restricted subscription.\n     *\n     * @return the session id for a publication or restricted subscription.\n     * @see CommonContext#SESSION_ID_PARAM_NAME\n     * @deprecated this method will not correctly handle tagged sessionId values that are outside the range of\n     * a signed 32-bit number.  If this is called and a tagged value outside this range is currently held in this\n     * object, then the result will be the same as {@link Long#intValue()}.\n     */\n    @Deprecated\n    public Integer sessionId()\n    {\n        return null != sessionId ? sessionId.intValue() : null;\n    }\n\n    /**\n     * Set the time a network publication will linger in nanoseconds after being drained. This time is so that tail\n     * loss can be recovered.\n     *\n     * @param lingerNs time for the publication after it is drained.\n     * @return this for a fluent API.\n     * @see CommonContext#LINGER_PARAM_NAME\n     */\n    public ChannelUriStringBuilder linger(final Long lingerNs)\n    {\n        this.linger = requireNonNegative(lingerNs, LINGER_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * Set the linger value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#LINGER_PARAM_NAME\n     */\n    public ChannelUriStringBuilder linger(final ChannelUri channelUri)\n    {\n        final String lingerValue = channelUri.get(LINGER_PARAM_NAME);\n        if (null == lingerValue)\n        {\n            linger = null;\n            return this;\n        }\n        else\n        {\n            return linger(parseDuration(LINGER_PARAM_NAME, lingerValue));\n        }\n    }\n\n    /**\n     * Get the time a network publication will linger in nanoseconds after being drained. This time is so that tail\n     * loss can be recovered.\n     *\n     * @return the  time in nanoseconds a publication will linger after being drained.\n     * @see CommonContext#LINGER_PARAM_NAME\n     */\n    public Long linger()\n    {\n        return linger;\n    }\n\n    /**\n     * Set to indicate if a term log buffer should be sparse on disk or not. Sparse saves space at the potential\n     * expense of latency.\n     *\n     * @param isSparse true if the term buffer log is sparse on disk.\n     * @return this for a fluent API.\n     * @see CommonContext#SPARSE_PARAM_NAME\n     */\n    public ChannelUriStringBuilder sparse(final Boolean isSparse)\n    {\n        this.sparse = isSparse;\n        return this;\n    }\n\n    /**\n     * Set the sparse value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#SPARSE_PARAM_NAME\n     */\n    public ChannelUriStringBuilder sparse(final ChannelUri channelUri)\n    {\n        final String sparseValue = channelUri.get(SPARSE_PARAM_NAME);\n        if (null == sparseValue)\n        {\n            sparse = null;\n            return this;\n        }\n        else\n        {\n            return sparse(Boolean.valueOf(sparseValue));\n        }\n    }\n\n    /**\n     * Should term log buffer be sparse on disk or not. Sparse saves space at the potential expense of latency.\n     *\n     * @return true if the term buffer log is sparse on disk.\n     * @see CommonContext#SPARSE_PARAM_NAME\n     */\n    public Boolean sparse()\n    {\n        return sparse;\n    }\n\n    /**\n     * Set to indicate if an EOS should be sent on the media or not.\n     *\n     * @param eos true if the EOS should be sent.\n     * @return this for a fluent API.\n     * @see CommonContext#EOS_PARAM_NAME\n     */\n    public ChannelUriStringBuilder eos(final Boolean eos)\n    {\n        this.eos = eos;\n        return this;\n    }\n\n    /**\n     * Set the eos value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#EOS_PARAM_NAME\n     */\n    public ChannelUriStringBuilder eos(final ChannelUri channelUri)\n    {\n        final String eosValue = channelUri.get(EOS_PARAM_NAME);\n        if (null == eosValue)\n        {\n            eos = null;\n            return this;\n        }\n        else\n        {\n            return eos(Boolean.valueOf(eosValue));\n        }\n    }\n\n    /**\n     * Should an EOS flag be sent on the media or not.\n     *\n     * @return true if the EOS param should be set.\n     * @see CommonContext#EOS_PARAM_NAME\n     */\n    public Boolean eos()\n    {\n        return eos;\n    }\n\n    /**\n     * Should the subscription channel be tethered or not for local flow control.\n     *\n     * @param tether value to be set for the tether param.\n     * @return this for a fluent API.\n     * @see CommonContext#TETHER_PARAM_NAME\n     */\n    public ChannelUriStringBuilder tether(final Boolean tether)\n    {\n        this.tether = tether;\n        return this;\n    }\n\n    /**\n     * Set the tether value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#TETHER_PARAM_NAME\n     */\n    public ChannelUriStringBuilder tether(final ChannelUri channelUri)\n    {\n        final String tetherValue = channelUri.get(TETHER_PARAM_NAME);\n        if (null == tetherValue)\n        {\n            tether = null;\n            return this;\n        }\n        else\n        {\n            return tether(Boolean.valueOf(tetherValue));\n        }\n    }\n\n    /**\n     * Should the subscription channel be tethered or not for local flow control.\n     *\n     * @return value of the tether param.\n     * @see CommonContext#TETHER_PARAM_NAME\n     */\n    public Boolean tether()\n    {\n        return tether;\n    }\n\n    /**\n     * Is the receiver likely to be part of a group. This informs behaviour such as loss handling.\n     *\n     * @param group value to be set for the group param.\n     * @return this for a fluent API.\n     * @see CommonContext#GROUP_PARAM_NAME\n     * @see #controlMode()\n     * @see #controlEndpoint()\n     */\n    public ChannelUriStringBuilder group(final Boolean group)\n    {\n        this.group = group;\n        return this;\n    }\n\n    /**\n     * Set the group value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#GROUP_PARAM_NAME\n     */\n    public ChannelUriStringBuilder group(final ChannelUri channelUri)\n    {\n        final String groupValue = channelUri.get(GROUP_PARAM_NAME);\n        if (null == groupValue)\n        {\n            group = null;\n            return this;\n        }\n        else\n        {\n            return group(Boolean.valueOf(groupValue));\n        }\n    }\n\n    /**\n     * Is the receiver likely to be part of a group. This informs behaviour such as loss handling.\n     *\n     * @return value of the group param.\n     * @see CommonContext#GROUP_PARAM_NAME\n     * @see #controlMode()\n     * @see #controlEndpoint()\n     */\n    public Boolean group()\n    {\n        return group;\n    }\n\n    /**\n     * Set the tags for a channel used by a publication or subscription. Tags can be used to identify or tag a\n     * channel so that a configuration can be referenced and reused.\n     *\n     * @param tags for the channel, publication or subscription.\n     * @return this for a fluent API.\n     * @see CommonContext#TAGS_PARAM_NAME\n     * @see CommonContext#TAG_PREFIX\n     */\n    public ChannelUriStringBuilder tags(final String tags)\n    {\n        this.tags = tags;\n        return this;\n    }\n\n    /**\n     * Set the tags to be value which is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#TAGS_PARAM_NAME\n     */\n    public ChannelUriStringBuilder tags(final ChannelUri channelUri)\n    {\n        return tags(channelUri.get(TAGS_PARAM_NAME));\n    }\n\n    /**\n     * Set the tags to the specified channel and publication/subscription tag {@link ChannelUri}. The\n     * publication/subscription may be null. If channel tag is null, then the pubSubTag must be null.\n     *\n     * @param channelTag optional value for the channel tag.\n     * @param pubSubTag  option value for the publication/subscription tag.\n     * @return this for a fluent API.\n     * @throws IllegalArgumentException if channelTag is null and pubSubTag is not.\n     * @see CommonContext#TAGS_PARAM_NAME\n     */\n    public ChannelUriStringBuilder tags(final Long channelTag, final Long pubSubTag)\n    {\n        if (null == channelTag && null != pubSubTag)\n        {\n            throw new IllegalArgumentException(\"null == channelTag && null != pubSubTag\");\n        }\n\n        if (null == channelTag)\n        {\n            return tags((String)null);\n        }\n\n        return tags(channelTag + (null != pubSubTag ? \",\" + pubSubTag : \"\"));\n    }\n\n    /**\n     * Get the tags for a channel used by a publication or subscription. Tags can be used to identify or tag a\n     * channel so that a configuration can be referenced and reused.\n     *\n     * @return the tags for a channel, publication or subscription.\n     * @see CommonContext#TAGS_PARAM_NAME\n     * @see CommonContext#TAG_PREFIX\n     */\n    public String tags()\n    {\n        return tags;\n    }\n\n    /**\n     * Toggle the value for {@link #sessionId()} being tagged or not.\n     *\n     * @param isSessionIdTagged for session id\n     * @return this for a fluent API.\n     * @see CommonContext#TAGS_PARAM_NAME\n     * @see CommonContext#TAG_PREFIX\n     */\n    public ChannelUriStringBuilder isSessionIdTagged(final boolean isSessionIdTagged)\n    {\n        this.isSessionIdTagged = isSessionIdTagged;\n        return this;\n    }\n\n    /**\n     * Is the value for {@link #sessionId()} a tag.\n     *\n     * @return whether the value for {@link #sessionId()} a tag reference or not.\n     * @see CommonContext#TAGS_PARAM_NAME\n     * @see CommonContext#TAG_PREFIX\n     */\n    public boolean isSessionIdTagged()\n    {\n        return isSessionIdTagged;\n    }\n\n    /**\n     * Set the alias for a URI. Aliases are not interpreted by Aeron and are to be used by the application.\n     *\n     * @param alias for the URI.\n     * @return this for a fluent API.\n     * @see CommonContext#ALIAS_PARAM_NAME\n     */\n    public ChannelUriStringBuilder alias(final String alias)\n    {\n        this.alias = alias;\n        return this;\n    }\n\n    /**\n     * Set the alias to be value which is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#TAGS_PARAM_NAME\n     */\n    public ChannelUriStringBuilder alias(final ChannelUri channelUri)\n    {\n        return alias(channelUri.get(ALIAS_PARAM_NAME));\n    }\n\n    /**\n     * Get the alias present in the URI.\n     *\n     * @return alias for the URI.\n     * @see CommonContext#ALIAS_PARAM_NAME\n     */\n    public String alias()\n    {\n        return alias;\n    }\n\n    /**\n     * Set the congestion control algorithm to be used on a stream.\n     *\n     * @param congestionControl for the URI.\n     * @return this for a fluent API.\n     * @see CommonContext#CONGESTION_CONTROL_PARAM_NAME\n     */\n    public ChannelUriStringBuilder congestionControl(final String congestionControl)\n    {\n        this.cc = congestionControl;\n        return this;\n    }\n\n    /**\n     * Set the congestion control to be value which is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#CONGESTION_CONTROL_PARAM_NAME\n     */\n    public ChannelUriStringBuilder congestionControl(final ChannelUri channelUri)\n    {\n        return congestionControl(channelUri.get(CONGESTION_CONTROL_PARAM_NAME));\n    }\n\n    /**\n     * Get the congestion control algorithm to be used on a stream.\n     *\n     * @return congestion control strategy for the channel.\n     * @see CommonContext#CONGESTION_CONTROL_PARAM_NAME\n     */\n    public String congestionControl()\n    {\n        return cc;\n    }\n\n    /**\n     * Set the flow control strategy to be used on a stream.\n     *\n     * @param flowControl for the URI.\n     * @return this for a fluent API.\n     * @see CommonContext#FLOW_CONTROL_PARAM_NAME\n     */\n    public ChannelUriStringBuilder flowControl(final String flowControl)\n    {\n        this.fc = flowControl;\n        return this;\n    }\n\n    /**\n     * Set tagged flow control settings to be used on a stream. All specified values may be null and the default\n     * specified in the MediaDriver.Context will be used instead.\n     *\n     * @param groupTag     receiver tag for this stream.\n     * @param minGroupSize group size required to allow publications for this channel to be moved to connected status.\n     * @param timeout      timeout receivers, default is ns, but allows suffixing of time units (e.g. 5s).\n     * @return this for fluent API.\n     */\n    public ChannelUriStringBuilder taggedFlowControl(\n        final Long groupTag, final Integer minGroupSize, final String timeout)\n    {\n        String flowControlValue = \"tagged\";\n\n        if (null != groupTag || null != minGroupSize)\n        {\n            flowControlValue += \",g:\";\n\n            if (null != groupTag)\n            {\n                flowControlValue += groupTag;\n            }\n\n            if (null != minGroupSize)\n            {\n                flowControlValue += (\"/\" + minGroupSize);\n            }\n        }\n\n        if (null != timeout)\n        {\n            flowControlValue += (\",t:\" + timeout);\n        }\n\n        return flowControl(flowControlValue);\n    }\n\n    /**\n     * Set min flow control settings to be used on stream. All specified values may be null and the default\n     * specified in the MediaDriver.Context will be used instead.\n     *\n     * @param minGroupSize group size required to allow publications for this stream to be moved to connected status.\n     * @param timeout      timeout receivers, default is ns, but allows suffixing of time units (e.g. 5s).\n     * @return this for fluent API.\n     */\n    public ChannelUriStringBuilder minFlowControl(final Integer minGroupSize, final String timeout)\n    {\n        String flowControlValue = \"min\";\n\n        if (null != minGroupSize)\n        {\n            flowControlValue += (\",g:/\" + minGroupSize);\n        }\n\n        if (null != timeout)\n        {\n            flowControlValue += (\",t:\" + timeout);\n        }\n\n        return flowControl(flowControlValue);\n    }\n\n    /**\n     * Set the flow control to be value which is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#FLOW_CONTROL_PARAM_NAME\n     */\n    public ChannelUriStringBuilder flowControl(final ChannelUri channelUri)\n    {\n        return flowControl(channelUri.get(FLOW_CONTROL_PARAM_NAME));\n    }\n\n    /**\n     * Get the flow control strategy to be used on a stream.\n     *\n     * @return flow control strategy for the stream.\n     * @see CommonContext#FLOW_CONTROL_PARAM_NAME\n     */\n    public String flowControl()\n    {\n        return fc;\n    }\n\n    /**\n     * Set the group tag (gtag) to be sent in SMs (Status Messages).\n     *\n     * @param groupTag to be sent in SMs\n     * @return this for fluent API.\n     * @see CommonContext#GROUP_TAG_PARAM_NAME\n     */\n    public ChannelUriStringBuilder groupTag(final Long groupTag)\n    {\n        this.groupTag = groupTag;\n        return this;\n    }\n\n    /**\n     * Set the group tag (gtag) to be the value which is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#GROUP_TAG_PARAM_NAME\n     */\n    public ChannelUriStringBuilder groupTag(final ChannelUri channelUri)\n    {\n        final String groupTagValue = channelUri.get(GROUP_TAG_PARAM_NAME);\n        if (null == groupTagValue)\n        {\n            groupTag = null;\n            return this;\n        }\n        else\n        {\n            try\n            {\n                return groupTag(Long.valueOf(groupTagValue));\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\"'gtag' must be a valid long value\", ex);\n            }\n        }\n    }\n\n    /**\n     * Get the group tag (gtag) to be sent in SMs (Status Messages).\n     *\n     * @return receiver tag to be sent in SMs.\n     * @see CommonContext#GROUP_TAG_PARAM_NAME\n     */\n    public Long groupTag()\n    {\n        return groupTag;\n    }\n\n    /**\n     * Set the subscription semantics for if a stream should be rejoined after going unavailable.\n     *\n     * @param rejoin false if stream is not to be rejoined.\n     * @return this for a fluent API.\n     * @see CommonContext#REJOIN_PARAM_NAME\n     */\n    public ChannelUriStringBuilder rejoin(final Boolean rejoin)\n    {\n        this.rejoin = rejoin;\n        return this;\n    }\n\n    /**\n     * Set the rejoin value to be what is in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#REJOIN_PARAM_NAME\n     */\n    public ChannelUriStringBuilder rejoin(final ChannelUri channelUri)\n    {\n        final String rejoinValue = channelUri.get(REJOIN_PARAM_NAME);\n        if (null == rejoinValue)\n        {\n            rejoin = null;\n            return this;\n        }\n        else\n        {\n            return rejoin(Boolean.valueOf(rejoinValue));\n        }\n    }\n\n    /**\n     * Get the subscription semantics for if a stream should be rejoined after going unavailable.\n     *\n     * @return the subscription semantics for if a stream should be rejoined after going unavailable.\n     * @see CommonContext#REJOIN_PARAM_NAME\n     */\n    public Boolean rejoin()\n    {\n        return rejoin;\n    }\n\n    /**\n     * Set the publication semantics for whether the presence of spy subscriptions simulate a connection.\n     *\n     * @param spiesSimulateConnection true if the presence of spy subscriptions simulate a connection.\n     * @return this for a fluent API.\n     * @see CommonContext#SPIES_SIMULATE_CONNECTION_PARAM_NAME\n     */\n    public ChannelUriStringBuilder spiesSimulateConnection(final Boolean spiesSimulateConnection)\n    {\n        this.ssc = spiesSimulateConnection;\n        return this;\n    }\n\n    /**\n     * Set the publication semantics for whether the presence of spy subscriptions simulate a connection to be what is\n     * in the {@link ChannelUri} which may be null.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#SPIES_SIMULATE_CONNECTION_PARAM_NAME\n     */\n    public ChannelUriStringBuilder spiesSimulateConnection(final ChannelUri channelUri)\n    {\n        final String sscValue = channelUri.get(SPIES_SIMULATE_CONNECTION_PARAM_NAME);\n        if (null == sscValue)\n        {\n            ssc = null;\n            return this;\n        }\n        else\n        {\n            return spiesSimulateConnection(Boolean.valueOf(sscValue));\n        }\n    }\n\n    /**\n     * Get the publication semantics for whether the presence of spy subscriptions simulate a connection.\n     *\n     * @return true if the presence of spy subscriptions simulate a connection, otherwise false.\n     * @see CommonContext#SPIES_SIMULATE_CONNECTION_PARAM_NAME\n     */\n    public Boolean spiesSimulateConnection()\n    {\n        return ssc;\n    }\n\n    /**\n     * Initialise a channel for restarting a publication at a given position.\n     *\n     * @param position      at which the publication should be started.\n     * @param initialTermId what which the stream would start.\n     * @param termLength    for the stream.\n     * @return this for a fluent API.\n     */\n    public ChannelUriStringBuilder initialPosition(final long position, final int initialTermId, final int termLength)\n    {\n        if (position < 0)\n        {\n            throw new IllegalArgumentException(\"invalid position=\" + position + \" < 0\");\n        }\n        if (0 != (position & (FRAME_ALIGNMENT - 1)))\n        {\n            throw new IllegalArgumentException(\n                \"invalid position=\" + position + \" does not have frame alignment=\" + FRAME_ALIGNMENT);\n        }\n\n        final int bitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n\n        this.initialTermId = initialTermId;\n        this.termId = LogBufferDescriptor.computeTermIdFromPosition(position, bitsToShift, initialTermId);\n        this.termOffset = (int)(position & (termLength - 1));\n        this.termLength = termLength;\n\n        return this;\n    }\n\n    /**\n     * Set the underlying OS send buffer length.\n     *\n     * @param socketSndbufLength parameter to be passed as SO_SNDBUF value.\n     * @return this for a fluent API.\n     * @see CommonContext#SOCKET_SNDBUF_PARAM_NAME\n     */\n    public ChannelUriStringBuilder socketSndbufLength(final Integer socketSndbufLength)\n    {\n        this.socketSndbufLength = requireNonNegative(socketSndbufLength, SOCKET_SNDBUF_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * Set the underlying OS send buffer length from an existing {@link ChannelUri} which may be (null).\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#SOCKET_SNDBUF_PARAM_NAME\n     */\n    public ChannelUriStringBuilder socketSndbufLength(final ChannelUri channelUri)\n    {\n        final String valueStr = channelUri.get(SOCKET_SNDBUF_PARAM_NAME);\n        if (null == valueStr)\n        {\n            this.socketSndbufLength = null;\n            return this;\n        }\n        else\n        {\n            final long value = parseSize(SOCKET_SNDBUF_PARAM_NAME, valueStr);\n            if (value > Integer.MAX_VALUE)\n            {\n                throw new IllegalArgumentException(\"value exceeds maximum permitted: value=\" + value);\n            }\n\n            return socketSndbufLength((int)value);\n        }\n    }\n\n    /**\n     * Get the underlying OS send buffer length setting.\n     *\n     * @return underlying OS send buffer length setting or null if not specified.\n     * @see CommonContext#SOCKET_SNDBUF_PARAM_NAME\n     */\n    public Integer socketSndbufLength()\n    {\n        return socketSndbufLength;\n    }\n\n    /**\n     * Set the underlying OS receive buffer length.\n     *\n     * @param socketRcvbufLength parameter to be passed as SO_RCVBUF value.\n     * @return this for a fluent API.\n     * @see CommonContext#SOCKET_RCVBUF_PARAM_NAME\n     */\n    public ChannelUriStringBuilder socketRcvbufLength(final Integer socketRcvbufLength)\n    {\n        this.socketRcvbufLength = requireNonNegative(socketRcvbufLength, SOCKET_RCVBUF_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * Set the underlying OS receive buffer length from an existing {@link ChannelUri}, which may have a null value for\n     * this field.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#SOCKET_RCVBUF_PARAM_NAME\n     */\n    public ChannelUriStringBuilder socketRcvbufLength(final ChannelUri channelUri)\n    {\n        final String valueStr = channelUri.get(SOCKET_RCVBUF_PARAM_NAME);\n        if (null == valueStr)\n        {\n            this.socketRcvbufLength = null;\n            return this;\n        }\n        else\n        {\n            final long value = parseSize(SOCKET_RCVBUF_PARAM_NAME, valueStr);\n            if (value > Integer.MAX_VALUE)\n            {\n                throw new IllegalArgumentException(\"value exceeds maximum permitted: value=\" + value);\n            }\n\n            return socketRcvbufLength((int)value);\n        }\n    }\n\n    /**\n     * Get the underlying OS receive buffer length setting.\n     *\n     * @return underlying OS receive buffer length setting or null if not specified.\n     * @see CommonContext#SOCKET_RCVBUF_PARAM_NAME\n     */\n    public Integer socketRcvbufLength()\n    {\n        return socketRcvbufLength;\n    }\n\n    /**\n     * Set the flow control initial receiver window length for this channel.\n     *\n     * @param receiverWindowLength initial receiver window length.\n     * @return this for a fluent API.\n     * @see CommonContext#RECEIVER_WINDOW_LENGTH_PARAM_NAME\n     */\n    public ChannelUriStringBuilder receiverWindowLength(final Integer receiverWindowLength)\n    {\n        this.receiverWindowLength = requireNonNegative(receiverWindowLength, RECEIVER_WINDOW_LENGTH_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * Set the flow control initial receiver window length for this channel from an existing {@link ChannelUri},\n     * which may have a null value for this field.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#RECEIVER_WINDOW_LENGTH_PARAM_NAME\n     */\n    public ChannelUriStringBuilder receiverWindowLength(final ChannelUri channelUri)\n    {\n        final String valueStr = channelUri.get(RECEIVER_WINDOW_LENGTH_PARAM_NAME);\n        if (null == valueStr)\n        {\n            this.receiverWindowLength = null;\n            return this;\n        }\n        else\n        {\n            final long value = parseSize(RECEIVER_WINDOW_LENGTH_PARAM_NAME, valueStr);\n            if (value > Integer.MAX_VALUE)\n            {\n                throw new IllegalArgumentException(\"value exceeds maximum permitted: value=\" + value);\n            }\n\n            return receiverWindowLength((int)value);\n        }\n    }\n\n    /**\n     * Get the receiver window length to be used as the initial receiver window for flow control.\n     *\n     * @return receiver window length.\n     * @see CommonContext#RECEIVER_WINDOW_LENGTH_PARAM_NAME\n     */\n    public Integer receiverWindowLength()\n    {\n        return receiverWindowLength;\n    }\n\n    /**\n     * Offset into a message to store the media receive timestamp. May also be the special value 'reserved' which means\n     * to store the timestamp in the reserved value field.\n     *\n     * @return current mediaReceiveTimestampOffset value either as string representation of an integer index or the\n     * special value 'reserved'\n     * @see CommonContext#MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME\n     */\n    public String mediaReceiveTimestampOffset()\n    {\n        return mediaReceiveTimestampOffset;\n    }\n\n    /**\n     * Offset into a message to store the media receive timestamp. May also be the special value 'reserved' which means\n     * to store the timestamp in the reserved value field.\n     *\n     * @param timestampOffset to use as the offset.\n     * @return this for a fluent API.\n     * @throws IllegalArgumentException if the string is not null and doesn't represent an int or the 'reserved' value.\n     * @see CommonContext#MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME\n     */\n    public ChannelUriStringBuilder mediaReceiveTimestampOffset(final String timestampOffset)\n    {\n        if (null != timestampOffset && !RESERVED_OFFSET.equals(timestampOffset))\n        {\n            try\n            {\n                Integer.parseInt(timestampOffset);\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\n                    \"mediaReceiveTimestampOffset must be a number or the value '\" + RESERVED_OFFSET + \"' found: \" +\n                    timestampOffset);\n            }\n        }\n\n        this.mediaReceiveTimestampOffset = timestampOffset;\n        return this;\n    }\n\n    /**\n     * Offset into a message to store the media receive timestamp. May also be the special value 'reserved' which means\n     * to store the timestamp in the reserved value field.\n     *\n     * @param channelUri the existing URI to extract the mediaReceiveTimestampOffset from\n     * @return this for a fluent API.\n     * @see CommonContext#MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME\n     */\n    public ChannelUriStringBuilder mediaReceiveTimestampOffset(final ChannelUri channelUri)\n    {\n        return mediaReceiveTimestampOffset(channelUri.get(MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME));\n    }\n\n    /**\n     * Offset into a message to store the channel receive timestamp. May also be the special value 'reserved' which\n     * means to store the timestamp in the reserved value field.\n     *\n     * @return current channelReceiveTimestampOffset value either as string representation of an integer index or\n     * the special value 'reserved'\n     * @see CommonContext#CHANNEL_RECEIVE_TIMESTAMP_OFFSET_PARAM_NAME\n     */\n    public String channelReceiveTimestampOffset()\n    {\n        return channelReceiveTimestampOffset;\n    }\n\n    /**\n     * Offset into a message to store the channel receive timestamp. May also be the special value 'reserved' which\n     * means to store the timestamp in the reserved value field.\n     *\n     * @param timestampOffset to use as the offset.\n     * @return this for a fluent API.\n     * @throws IllegalArgumentException if the string doesn't represent an int or the 'reserved' value.\n     * @see CommonContext#CHANNEL_RECEIVE_TIMESTAMP_OFFSET_PARAM_NAME\n     */\n    public ChannelUriStringBuilder channelReceiveTimestampOffset(final String timestampOffset)\n    {\n        if (null != timestampOffset && !RESERVED_OFFSET.equals(timestampOffset))\n        {\n            try\n            {\n                Integer.parseInt(timestampOffset);\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\n                    \"channelReceiveTimestampOffset must be a number or the value '\" + RESERVED_OFFSET + \"' found: \" +\n                    timestampOffset);\n            }\n        }\n\n        this.channelReceiveTimestampOffset = timestampOffset;\n        return this;\n    }\n\n    /**\n     * Offset into a message to store the channel receive timestamp. May also be the special value 'reserved' which\n     * means to store the timestamp in the reserved value field.\n     *\n     * @param channelUri the existing URI to extract the receiveTimestampOffset from.\n     * @return this for a fluent API.\n     * @see CommonContext#CHANNEL_RECEIVE_TIMESTAMP_OFFSET_PARAM_NAME\n     */\n    public ChannelUriStringBuilder channelReceiveTimestampOffset(final ChannelUri channelUri)\n    {\n        return channelReceiveTimestampOffset(channelUri.get(CHANNEL_RECEIVE_TIMESTAMP_OFFSET_PARAM_NAME));\n    }\n\n    /**\n     * Offset into a message to store the channel send timestamp. May also be the special value 'reserved' which means\n     * to store the timestamp in the reserved value field.\n     *\n     * @param timestampOffset to use as the offset.\n     * @return this for a fluent API.\n     * @throws IllegalArgumentException if the string is not null doesn't represent an int or the 'reserved' value.\n     * @see CommonContext#CHANNEL_SEND_TIMESTAMP_OFFSET_PARAM_NAME\n     */\n    public ChannelUriStringBuilder channelSendTimestampOffset(final String timestampOffset)\n    {\n        if (null != timestampOffset && !RESERVED_OFFSET.equals(timestampOffset))\n        {\n            try\n            {\n                Integer.parseInt(timestampOffset);\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\n                    \"channelSendTimestampOffset must be a number or the value '\" + RESERVED_OFFSET + \"' found: \" +\n                        timestampOffset);\n            }\n        }\n\n        this.channelSendTimestampOffset = timestampOffset;\n        return this;\n    }\n\n    /**\n     * Offset into a message to store the channel send timestamp. May also be the special value 'reserved' which means\n     * to store the timestamp in the reserved value field.\n     *\n     * @param channelUri the existing URI to extract the channelSendTimestampOffset from.\n     * @return this for a fluent API.\n     * @see CommonContext#CHANNEL_SEND_TIMESTAMP_OFFSET_PARAM_NAME\n     */\n    public ChannelUriStringBuilder channelSendTimestampOffset(final ChannelUri channelUri)\n    {\n        return channelSendTimestampOffset(channelUri.get(CHANNEL_SEND_TIMESTAMP_OFFSET_PARAM_NAME));\n    }\n\n    /**\n     * Offset into a message to store the channel send timestamp. May also be the special value 'reserved' which means\n     * to store the timestamp in the reserved value field.\n     *\n     * @return current sendTimestampOffset value either as string representation of an integer index or the special\n     * value 'reserved'.\n     * @see CommonContext#CHANNEL_SEND_TIMESTAMP_OFFSET_PARAM_NAME\n     */\n    public String channelSendTimestampOffset()\n    {\n        return channelSendTimestampOffset;\n    }\n\n    /**\n     * Set the response endpoint to be used for a response channel subscription or publication.\n     *\n     * @param responseEndpoint  response endpoint to be used in this channel URI.\n     * @return this for a fluent API.\n     * @see CommonContext#RESPONSE_ENDPOINT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder responseEndpoint(final String responseEndpoint)\n    {\n        this.responseEndpoint = responseEndpoint;\n        return this;\n    }\n\n    /**\n     * Set the response endpoint to be used for a response channel subscription or publication by extracting it from the\n     * ChannelUri.\n     *\n     * @param channelUri the existing URI to extract the responseEndpoint from.\n     * @return this for a fluent API.\n     * @see CommonContext#RESPONSE_ENDPOINT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder responseEndpoint(final ChannelUri channelUri)\n    {\n        return responseEndpoint(channelUri.get(RESPONSE_ENDPOINT_PARAM_NAME));\n    }\n\n    /**\n     * The response endpoint to be used for a response channel subscription or publication.\n     *\n     * @return response endpoint.\n     * @see CommonContext#RESPONSE_ENDPOINT_PARAM_NAME\n     */\n    public String responseEndpoint()\n    {\n        return this.responseEndpoint;\n    }\n\n    /**\n     * Get the correlation id from the image received on the response \"server's\" subscription to be used by a response\n     * publication.\n     *\n     * @return correlation id of an image from the response \"server's\" subscription.\n     * @see CommonContext#RESPONSE_CORRELATION_ID_PARAM_NAME\n     */\n    public String responseCorrelationId()\n    {\n        return responseCorrelationId;\n    }\n\n    /**\n     * Set the correlation id from the image received on the response \"server's\" subscription to be used by a response\n     * publication.\n     *\n     * @param responseCorrelationId correlation id of an image from the response \"server's\" subscription.\n     * @return this for a fluent API.\n     * @see CommonContext#RESPONSE_CORRELATION_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder responseCorrelationId(final Long responseCorrelationId)\n    {\n        this.responseCorrelationId = Long.toString(responseCorrelationId);\n        return this;\n    }\n\n    /**\n     * Set the correlation id from the image received on the response \"server's\" subscription to be used by a response\n     * publication.\n     *\n     * @param responseCorrelationId correlation id of an image from the response \"server's\" subscription.\n     * @return this for a fluent API.\n     * @see CommonContext#RESPONSE_CORRELATION_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder responseCorrelationId(final String responseCorrelationId)\n    {\n        if (null != responseCorrelationId && !PROTOTYPE_CORRELATION_ID.equals(responseCorrelationId))\n        {\n            try\n            {\n                if (Long.parseLong(responseCorrelationId) < -1)\n                {\n                    throw new NumberFormatException(\"responseCorrelationId must be positive\");\n                }\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\n                    \"responseCorrelationId must be a number greater than or equal to -1, or the value '\" +\n                        PROTOTYPE_CORRELATION_ID + \"' found: \" + responseCorrelationId);\n            }\n        }\n\n        this.responseCorrelationId = responseCorrelationId;\n        return this;\n    }\n\n    /**\n     * Set the correlation id from the image received on the response \"server's\" subscription to be used by a response\n     * publication extracted from the channelUri.\n     *\n     * @param channelUri the existing URI to extract the responseCorrelationId from.\n     * @return this for a fluent API.\n     * @see CommonContext#RESPONSE_CORRELATION_ID_PARAM_NAME\n     */\n    public ChannelUriStringBuilder responseCorrelationId(final ChannelUri channelUri)\n    {\n        final String responseCorrelationIdString = channelUri.get(RESPONSE_CORRELATION_ID_PARAM_NAME);\n\n        if (null != responseCorrelationIdString)\n        {\n            responseCorrelationId(responseCorrelationIdString);\n        }\n\n        return this;\n    }\n\n    /**\n     * The delay to apply before sending a NAK in response to a gap being detected by the receiver.\n     *\n     * @param nakDelay express as a numeric value with a suffix, e.g. 10ms, 100us.\n     * @return this for a fluent API.\n     * @see CommonContext#NAK_DELAY_PARAM_NAME\n     */\n    public ChannelUriStringBuilder nakDelay(final String nakDelay)\n    {\n        this.nakDelay = null != nakDelay ? parseDuration(NAK_DELAY_PARAM_NAME, nakDelay) : null;\n        return this;\n    }\n\n    /**\n     * The delay to apply before sending a NAK in response to a gap being detected by the receiver.\n     *\n     * @param nakDelayNs in nanoseconds.\n     * @return this for a fluent API.\n     * @see CommonContext#NAK_DELAY_PARAM_NAME\n     */\n    public ChannelUriStringBuilder nakDelay(final Long nakDelayNs)\n    {\n        this.nakDelay = requireNonNegative(nakDelayNs, NAK_DELAY_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * The delay to apply before sending a NAK in response to a gap being detected by the receiver.\n     *\n     * @param channelUri the existing URI to extract the nakDelay from.\n     * @return this for a fluent API.\n     * @see CommonContext#NAK_DELAY_PARAM_NAME\n     */\n    public ChannelUriStringBuilder nakDelay(final ChannelUri channelUri)\n    {\n        return nakDelay(channelUri.get(NAK_DELAY_PARAM_NAME));\n    }\n\n    /**\n     * The delay to apply before sending a NAK in response to a gap being detected by the receiver.\n     *\n     * @return the delay in nanoseconds, null if not set.\n     * @see CommonContext#NAK_DELAY_PARAM_NAME\n     */\n    public Long nakDelay()\n    {\n        return nakDelay;\n    }\n\n    /**\n     * The timeout for when an untethered subscription that is outside the window limit will participate in local\n     * flow control.\n     *\n     * @param timeout specified either in nanoseconds or using a units suffix, e.g. 1ms, 1us.\n     * @return this for a fluent API.\n     * @see CommonContext#UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder untetheredWindowLimitTimeout(final String timeout)\n    {\n        this.untetheredWindowLimitTimeoutNs = null != timeout ?\n            parseDuration(UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME, timeout) : null;\n        return this;\n    }\n\n    /**\n     * The timeout for when an untethered subscription that is outside the window limit will participate in local\n     * flow control.\n     *\n     * @param timeout specified either in nanoseconds.\n     * @return this for a fluent API.\n     * @see CommonContext#UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder untetheredWindowLimitTimeoutNs(final Long timeout)\n    {\n        this.untetheredWindowLimitTimeoutNs = requireNonNegative(timeout, UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * The timeout for when an untethered subscription that is outside the window limit will participate in local\n     * flow control.\n     *\n     * @param channelUri the existing URI to extract the untetheredWindowLimitTimeout from.\n     * @return this for a fluent API.\n     * @see CommonContext#UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder untetheredWindowLimitTimeout(final ChannelUri channelUri)\n    {\n        this.untetheredWindowLimitTimeout(channelUri.get(UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME));\n        return this;\n    }\n\n    /**\n     * The timeout for when an untethered subscription that is outside the window limit will participate in local\n     * flow control.\n     *\n     * @return the timeout in ns.\n     * @see CommonContext#UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME\n     */\n    public Long untetheredWindowLimitTimeoutNs()\n    {\n        return untetheredWindowLimitTimeoutNs;\n    }\n\n    /**\n     * The time for an untethered subscription to linger.\n     *\n     * @param timeout specified either in nanoseconds or using a units suffix, e.g. 1ms, 1us.\n     * @return this for a fluent API.\n     * @see CommonContext#UNTETHERED_LINGER_TIMEOUT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder untetheredLingerTimeout(final String timeout)\n    {\n        this.untetheredLingerTimeoutNs = null != timeout ?\n            parseDuration(UNTETHERED_LINGER_TIMEOUT_PARAM_NAME, timeout) : null;\n        return this;\n    }\n\n    /**\n     * The time for an untethered subscription to linger.\n     *\n     * @param timeout specified either in nanoseconds.\n     * @return this for a fluent API.\n     * @see CommonContext#UNTETHERED_LINGER_TIMEOUT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder untetheredLingerTimeoutNs(final Long timeout)\n    {\n        this.untetheredLingerTimeoutNs = requireNonNegative(timeout, UNTETHERED_LINGER_TIMEOUT_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * The time for an untethered subscription to linger.\n     *\n     * @param channelUri the existing URI to extract the untetheredLingerTimeout from.\n     * @return this for a fluent API.\n     * @see CommonContext#UNTETHERED_LINGER_TIMEOUT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder untetheredLingerTimeout(final ChannelUri channelUri)\n    {\n        this.untetheredLingerTimeout(channelUri.get(UNTETHERED_LINGER_TIMEOUT_PARAM_NAME));\n        return this;\n    }\n\n    /**\n     * The time for an untethered subscription to linger.\n     *\n     * @return the timeout in ns.\n     * @see CommonContext#UNTETHERED_LINGER_TIMEOUT_PARAM_NAME\n     */\n    public Long untetheredLingerTimeoutNs()\n    {\n        return untetheredLingerTimeoutNs;\n    }\n\n    /**\n     * The timeout for when an untethered subscription is resting after not being able to keep up before it is allowed\n     * to rejoin a stream.\n     *\n     * @param timeout specified either in nanoseconds or using a units suffix, e.g. 1ms, 1us.\n     * @return this for a fluent API.\n     * @see CommonContext#UNTETHERED_RESTING_TIMEOUT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder untetheredRestingTimeout(final String timeout)\n    {\n        this.untetheredRestingTimeoutNs = null != timeout ?\n            parseDuration(UNTETHERED_RESTING_TIMEOUT_PARAM_NAME, timeout) : null;\n        return this;\n    }\n\n    /**\n     * The timeout for when an untethered subscription is resting after not being able to keep up before it is allowed\n     * to rejoin a stream.\n     *\n     * @param timeout specified either in nanoseconds.\n     * @return this for a fluent API.\n     * @see CommonContext#UNTETHERED_RESTING_TIMEOUT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder untetheredRestingTimeoutNs(final Long timeout)\n    {\n        this.untetheredRestingTimeoutNs = requireNonNegative(timeout, UNTETHERED_RESTING_TIMEOUT_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * The timeout for when an untethered subscription is resting after not being able to keep up before it is allowed\n     * to rejoin a stream.\n     *\n     * @param channelUri the existing URI to extract the untetheredRestingTimeout from.\n     * @return this for a fluent API.\n     * @see CommonContext#UNTETHERED_RESTING_TIMEOUT_PARAM_NAME\n     */\n    public ChannelUriStringBuilder untetheredRestingTimeout(final ChannelUri channelUri)\n    {\n        this.untetheredRestingTimeout(channelUri.get(UNTETHERED_RESTING_TIMEOUT_PARAM_NAME));\n        return this;\n    }\n\n    /**\n     * The timeout for when an untethered subscription is resting after not being able to keep up before it is allowed\n     * to rejoin a stream.\n     *\n     * @return the timeout in ns.\n     * @see CommonContext#UNTETHERED_RESTING_TIMEOUT_PARAM_NAME\n     */\n    public Long untetheredRestingTimeoutNs()\n    {\n        return untetheredRestingTimeoutNs;\n    }\n\n    /**\n     * The max number of retransmit actions.\n     *\n     * @param maxResend the max number of retransmit actions.\n     * @return this for a fluent API.\n     * @see CommonContext#MAX_RESEND_PARAM_NAME\n     */\n    public ChannelUriStringBuilder maxResend(final Integer maxResend)\n    {\n        this.maxResend = requireNonNegative(maxResend, MAX_RESEND_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * The max number of retransmit actions.\n     *\n     * @param channelUri the existing URI to extract the maxResend from.\n     * @return this for a fluent API.\n     * @see CommonContext#MAX_RESEND_PARAM_NAME\n     */\n    public ChannelUriStringBuilder maxResend(final ChannelUri channelUri)\n    {\n        final String valueStr = channelUri.get(MAX_RESEND_PARAM_NAME);\n        if (null == valueStr)\n        {\n            this.maxResend = null;\n            return this;\n        }\n        else\n        {\n            try\n            {\n                return maxResend(Integer.parseInt(valueStr));\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(MAX_RESEND_PARAM_NAME + \" must be a number\", ex);\n            }\n        }\n    }\n\n    /**\n     * The max number of retransmit actions.\n     *\n     * @return the max number of outstanding retransmit actions\n     * @see CommonContext#MAX_RESEND_PARAM_NAME\n     */\n    public Integer maxResend()\n    {\n        return maxResend;\n    }\n\n    /**\n     * The stream id of the channel.\n     *\n     * @return the stream or null of no streamId is set.\n     */\n    public Integer streamId()\n    {\n        return streamId;\n    }\n\n    /**\n     * The stream id of the channel.\n     *\n     * @param streamId of the channel.\n     * @return this for a fluent API.\n     */\n    public ChannelUriStringBuilder streamId(final Integer streamId)\n    {\n        this.streamId = streamId;\n        return this;\n    }\n\n    /**\n     * The stream id of the channel.\n     *\n     * @param channelUri the existing URI to extract the streamId from.\n     * @return this for a fluent API.\n     */\n    public ChannelUriStringBuilder streamId(final ChannelUri channelUri)\n    {\n        final String valueStr = channelUri.get(STREAM_ID_PARAM_NAME);\n        if (null == valueStr)\n        {\n            this.streamId = null;\n            return this;\n        }\n        else\n        {\n            try\n            {\n                return streamId(Integer.parseInt(valueStr));\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(STREAM_ID_PARAM_NAME + \" must be a number\", ex);\n            }\n        }\n    }\n\n    /**\n     * Set the publication window length which defines how far ahead can publication accept offers.\n     *\n     * @param publicationWindowLength of the channel.\n     * @return this for a fluent API.\n     * @see CommonContext#PUBLICATION_WINDOW_LENGTH_PARAM_NAME\n     */\n    public ChannelUriStringBuilder publicationWindowLength(final Integer publicationWindowLength)\n    {\n        this.publicationWindowLength =\n            requireNonNegative(publicationWindowLength, PUBLICATION_WINDOW_LENGTH_PARAM_NAME);\n        return this;\n    }\n\n    /**\n     * Set the publication window length for this channel from an existing {@link ChannelUri},\n     * which may have a null value for this field.\n     *\n     * @param channelUri to read the value from.\n     * @return this for a fluent API.\n     * @see CommonContext#PUBLICATION_WINDOW_LENGTH_PARAM_NAME\n     */\n    public ChannelUriStringBuilder publicationWindowLength(final ChannelUri channelUri)\n    {\n        final String valueStr = channelUri.get(PUBLICATION_WINDOW_LENGTH_PARAM_NAME);\n        if (null == valueStr)\n        {\n            this.publicationWindowLength = null;\n            return this;\n        }\n        else\n        {\n            final long value = parseSize(PUBLICATION_WINDOW_LENGTH_PARAM_NAME, valueStr);\n            if (value > Integer.MAX_VALUE)\n            {\n                throw new IllegalArgumentException(\"value exceeds maximum permitted: value=\" + value);\n            }\n\n            return publicationWindowLength((int)value);\n        }\n    }\n\n    /**\n     * Get the publication window length.\n     *\n     * @return publication window length or {@code null} if was not set.\n     * @see CommonContext#PUBLICATION_WINDOW_LENGTH_PARAM_NAME\n     */\n    public Integer publicationWindowLength()\n    {\n        return publicationWindowLength;\n    }\n\n    /**\n     * Build a channel URI String for the given parameters.\n     *\n     * @return a channel URI String for the given parameters.\n     */\n    @SuppressWarnings({ \"MethodLength\", \"DuplicatedCode\" })\n    public String build()\n    {\n        sb.setLength(0);\n\n        if (null != prefix && !prefix.isEmpty())\n        {\n            sb.append(prefix).append(':');\n        }\n\n        sb.append(ChannelUri.AERON_SCHEME).append(':').append(media).append('?');\n\n        appendParameter(sb, TAGS_PARAM_NAME, tags);\n        appendParameter(sb, ENDPOINT_PARAM_NAME, endpoint);\n        appendParameter(sb, INTERFACE_PARAM_NAME, networkInterface);\n        appendParameter(sb, MDC_CONTROL_PARAM_NAME, controlEndpoint);\n        appendParameter(sb, MDC_CONTROL_MODE_PARAM_NAME, controlMode);\n        appendSize(sb, MTU_LENGTH_PARAM_NAME, mtu);\n        appendSize(sb, TERM_LENGTH_PARAM_NAME, termLength);\n        appendParameter(sb, INITIAL_TERM_ID_PARAM_NAME, initialTermId);\n        appendParameter(sb, TERM_ID_PARAM_NAME, termId);\n        appendParameter(sb, TERM_OFFSET_PARAM_NAME, termOffset);\n\n        if (null != sessionId)\n        {\n            appendParameter(sb, SESSION_ID_PARAM_NAME, prefixTag(isSessionIdTagged, sessionId));\n        }\n\n        appendParameter(sb, TTL_PARAM_NAME, ttl);\n        appendParameter(sb, RELIABLE_STREAM_PARAM_NAME, reliable);\n        appendDuration(sb, LINGER_PARAM_NAME, linger);\n        appendParameter(sb, ALIAS_PARAM_NAME, alias);\n        appendParameter(sb, CONGESTION_CONTROL_PARAM_NAME, cc);\n        appendParameter(sb, FLOW_CONTROL_PARAM_NAME, fc);\n        appendParameter(sb, GROUP_TAG_PARAM_NAME, groupTag);\n        appendParameter(sb, SPARSE_PARAM_NAME, sparse);\n        appendParameter(sb, EOS_PARAM_NAME, eos);\n        appendParameter(sb, TETHER_PARAM_NAME, tether);\n        appendParameter(sb, GROUP_PARAM_NAME, group);\n        appendParameter(sb, REJOIN_PARAM_NAME, rejoin);\n        appendParameter(sb, SPIES_SIMULATE_CONNECTION_PARAM_NAME, ssc);\n        appendSize(sb, SOCKET_SNDBUF_PARAM_NAME, socketSndbufLength);\n        appendSize(sb, SOCKET_RCVBUF_PARAM_NAME, socketRcvbufLength);\n        appendSize(sb, RECEIVER_WINDOW_LENGTH_PARAM_NAME, receiverWindowLength);\n        appendParameter(sb, MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME, mediaReceiveTimestampOffset);\n        appendParameter(sb, CHANNEL_RECEIVE_TIMESTAMP_OFFSET_PARAM_NAME, channelReceiveTimestampOffset);\n        appendParameter(sb, CHANNEL_SEND_TIMESTAMP_OFFSET_PARAM_NAME, channelSendTimestampOffset);\n        appendParameter(sb, RESPONSE_ENDPOINT_PARAM_NAME, responseEndpoint);\n        appendParameter(sb, RESPONSE_CORRELATION_ID_PARAM_NAME, responseCorrelationId);\n        appendDuration(sb, NAK_DELAY_PARAM_NAME, nakDelay);\n        appendDuration(sb, UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME, untetheredWindowLimitTimeoutNs);\n        appendDuration(sb, UNTETHERED_LINGER_TIMEOUT_PARAM_NAME, untetheredLingerTimeoutNs);\n        appendDuration(sb, UNTETHERED_RESTING_TIMEOUT_PARAM_NAME, untetheredRestingTimeoutNs);\n        appendParameter(sb, MAX_RESEND_PARAM_NAME, maxResend);\n        appendParameter(sb, STREAM_ID_PARAM_NAME, streamId);\n        appendSize(sb, PUBLICATION_WINDOW_LENGTH_PARAM_NAME, publicationWindowLength);\n\n        final char lastChar = sb.charAt(sb.length() - 1);\n        if (lastChar == '|' || lastChar == '?')\n        {\n            sb.setLength(sb.length() - 1);\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return build();\n    }\n\n    private static void appendParameter(final StringBuilder sb, final String paramName, final Object paramValue)\n    {\n        if (null != paramValue)\n        {\n            sb.append(paramName).append('=').append(paramValue).append('|');\n        }\n    }\n\n    private static void appendDuration(final StringBuilder sb, final String paramName, final Long valueNs)\n    {\n        if (null != valueNs)\n        {\n            sb.append(paramName).append('=').append(SystemUtil.formatDuration(valueNs)).append('|');\n        }\n    }\n\n    private static void appendSize(final StringBuilder sb, final String paramName, final Integer value)\n    {\n        if (null != value)\n        {\n            sb.append(paramName).append('=').append(SystemUtil.formatSize(value)).append('|');\n        }\n    }\n\n    private static Long requireNonNegative(final Long value, final String name)\n    {\n        if (null != value && value < 0)\n        {\n            throw new IllegalArgumentException(\"`\" + name + \"` value cannot be negative: \" + value);\n        }\n        return value;\n    }\n\n    private static Integer requireNonNegative(final Integer value, final String name)\n    {\n        if (null != value && value < 0)\n        {\n            throw new IllegalArgumentException(\"`\" + name + \"` value cannot be negative: \" + value);\n        }\n        return value;\n    }\n\n    private static String prefixTag(final boolean isTagged, final Long value)\n    {\n        return isTagged ? TAG_PREFIX + value : value.toString();\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ClientConductor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.command.PublicationErrorFrameFlyweight;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ChannelEndpointException;\nimport io.aeron.exceptions.ClientTimeoutException;\nimport io.aeron.exceptions.ConductorServiceTimeoutException;\nimport io.aeron.exceptions.DriverTimeoutException;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.status.HeartbeatTimestamp;\nimport io.aeron.status.PublicationErrorFrame;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SemanticVersion;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.collections.LongHashSet;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.Lock;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.AeronCounters.DRIVER_SYSTEM_COUNTER_TYPE_ID;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION;\nimport static io.aeron.AeronCounters.appendToLabel;\nimport static io.aeron.AeronCounters.formatVersionInfo;\nimport static io.aeron.ErrorCode.CHANNEL_ENDPOINT_ERROR;\nimport static io.aeron.status.HeartbeatTimestamp.HEARTBEAT_TYPE_ID;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_ALLOCATED;\n\n/**\n * Client conductor receives responses and notifications from Media Driver and acts on them in addition to forwarding\n * commands from the Client API to the Media Driver conductor.\n */\nfinal class ClientConductor implements Agent\n{\n    private static final long NO_CORRELATION_ID = NULL_VALUE;\n    private static final long EXPLICIT_CLOSE_LINGER_NS = TimeUnit.SECONDS.toNanos(1);\n    static final int CONTROL_PROTOCOL_VERSION_WITH_NEXT_AVAILABLE_SESSION_ID_COMMAND = SemanticVersion.compose(1, 0, 0);\n\n    final int controlProtocolVersion;\n    private final long idleSleepDurationNs;\n    private final long keepAliveIntervalNs;\n    private final long driverTimeoutMs;\n    private final long driverTimeoutNs;\n    private final long interServiceTimeoutNs;\n    private long timeOfLastKeepAliveNs;\n    private long timeOfLastServiceNs;\n    private boolean isClosed;\n    private boolean isInCallback;\n    private boolean isTerminating;\n    private RegistrationException driverException;\n\n    private final Aeron.Context ctx;\n    private final Aeron aeron;\n    private final Lock clientLock;\n    private final EpochClock epochClock;\n    private final NanoClock nanoClock;\n    private final IdleStrategy awaitingIdleStrategy;\n    private final DriverEventsAdapter driverEventsAdapter;\n    private final LogBuffersFactory logBuffersFactory;\n    private final Long2ObjectHashMap<LogBuffers> logBuffersByIdMap = new Long2ObjectHashMap<>();\n    private final ArrayList<LogBuffers> lingeringLogBuffers = new ArrayList<>();\n    final Long2ObjectHashMap<Object> resourceByRegIdMap = new Long2ObjectHashMap<>();\n    private final Long2ObjectHashMap<RegistrationException> asyncExceptionByRegIdMap = new Long2ObjectHashMap<>();\n    private final Long2ObjectHashMap<String> stashedChannelByRegistrationId = new Long2ObjectHashMap<>();\n    final LongHashSet asyncCommandIdSet = new LongHashSet();\n    private final AvailableImageHandler defaultAvailableImageHandler;\n    private final UnavailableImageHandler defaultUnavailableImageHandler;\n    private final Long2ObjectHashMap<AvailableCounterHandler> availableCounterHandlerById = new Long2ObjectHashMap<>();\n    private final Long2ObjectHashMap<UnavailableCounterHandler> unavailableCounterHandlerById =\n        new Long2ObjectHashMap<>();\n    private final Long2ObjectHashMap<Runnable> closeHandlerByIdMap = new Long2ObjectHashMap<>();\n    private final DriverProxy driverProxy;\n    private final AgentInvoker driverAgentInvoker;\n    private final UnsafeBuffer counterValuesBuffer;\n    private final CountersReader countersReader;\n    private final PublicationErrorFrame publicationErrorFrame = new PublicationErrorFrame();\n    private AtomicCounter heartbeatTimestamp;\n    private long lastResponseValue;\n\n    ClientConductor(final Aeron.Context ctx, final Aeron aeron)\n    {\n        this.ctx = ctx;\n        this.aeron = aeron;\n\n        clientLock = ctx.clientLock();\n        epochClock = ctx.epochClock();\n        nanoClock = ctx.nanoClock();\n        awaitingIdleStrategy = ctx.awaitingIdleStrategy();\n        driverProxy = ctx.driverProxy();\n        logBuffersFactory = ctx.logBuffersFactory();\n        idleSleepDurationNs = ctx.idleSleepDurationNs();\n        keepAliveIntervalNs = ctx.keepAliveIntervalNs();\n        driverTimeoutMs = ctx.driverTimeoutMs();\n        driverTimeoutNs = MILLISECONDS.toNanos(driverTimeoutMs);\n        interServiceTimeoutNs = ctx.interServiceTimeoutNs();\n        defaultAvailableImageHandler = ctx.availableImageHandler();\n        defaultUnavailableImageHandler = ctx.unavailableImageHandler();\n        driverEventsAdapter = new DriverEventsAdapter(ctx.clientId(), ctx.toClientBuffer(), this, asyncCommandIdSet);\n        driverAgentInvoker = ctx.driverAgentInvoker();\n        counterValuesBuffer = ctx.countersValuesBuffer();\n        countersReader = new CountersReader(ctx.countersMetaDataBuffer(), ctx.countersValuesBuffer(), US_ASCII);\n\n        if (null != ctx.availableCounterHandler())\n        {\n            availableCounterHandlerById.put(aeron.nextCorrelationId(), ctx.availableCounterHandler());\n        }\n\n        if (null != ctx.unavailableCounterHandler())\n        {\n            unavailableCounterHandlerById.put(aeron.nextCorrelationId(), ctx.unavailableCounterHandler());\n        }\n\n        if (null != ctx.closeHandler())\n        {\n            closeHandlerByIdMap.put(aeron.nextCorrelationId(), ctx.closeHandler());\n        }\n\n        if (RECORD_ALLOCATED == countersReader.getCounterState(SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION) &&\n            DRIVER_SYSTEM_COUNTER_TYPE_ID ==\n            countersReader.getCounterTypeId(SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION) &&\n            SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION ==\n            countersReader.getCounterRegistrationId(SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION))\n        {\n            controlProtocolVersion = (int)countersReader.getCounterValue(SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION);\n        }\n        else\n        {\n            controlProtocolVersion = 0;\n        }\n\n        final long nowNs = nanoClock.nanoTime();\n        timeOfLastKeepAliveNs = nowNs;\n        timeOfLastServiceNs = nowNs;\n    }\n\n    public void onClose()\n    {\n        boolean isInterrupted = false;\n\n        aeron.internalClose();\n\n        clientLock.lock();\n        try\n        {\n            final boolean isTerminating = this.isTerminating;\n            this.isTerminating = true;\n            forceCloseResources();\n            notifyCloseHandlers();\n\n            try\n            {\n                if (isTerminating)\n                {\n                    Thread.sleep(Aeron.Configuration.IDLE_SLEEP_DEFAULT_MS);\n                }\n\n                Thread.sleep(NANOSECONDS.toMillis(ctx.closeLingerDurationNs()));\n            }\n            catch (final InterruptedException ignore)\n            {\n                isInterrupted = true;\n            }\n\n            for (final LogBuffers lingeringLogBuffer : lingeringLogBuffers)\n            {\n                CloseHelper.close(ctx.errorHandler(), lingeringLogBuffer);\n            }\n\n            driverProxy.clientClose();\n            ctx.close();\n\n            ctx.countersMetaDataBuffer().wrap(0, 0);\n            ctx.countersValuesBuffer().wrap(0, 0);\n        }\n        finally\n        {\n            isClosed = true;\n            if (isInterrupted)\n            {\n                Thread.currentThread().interrupt();\n            }\n\n            clientLock.unlock();\n        }\n    }\n\n    public int doWork()\n    {\n        int workCount = 0;\n\n        if (clientLock.tryLock())\n        {\n            try\n            {\n                if (isTerminating)\n                {\n                    throw new AgentTerminationException();\n                }\n\n                workCount = service(NO_CORRELATION_ID);\n            }\n            finally\n            {\n                clientLock.unlock();\n            }\n        }\n\n        return workCount;\n    }\n\n    public String roleName()\n    {\n        return \"aeron-client-conductor\";\n    }\n\n    boolean isClosed()\n    {\n        return isClosed;\n    }\n\n    boolean isTerminating()\n    {\n        return isTerminating;\n    }\n\n    void onError(final long correlationId, final int codeValue, final ErrorCode errorCode, final String message)\n    {\n        driverException = new RegistrationException(correlationId, codeValue, errorCode, message);\n\n        final Object resource = resourceByRegIdMap.get(correlationId);\n        if (resource instanceof Subscription)\n        {\n            final Subscription subscription = (Subscription)resource;\n            subscription.internalClose(NULL_VALUE);\n            resourceByRegIdMap.remove(correlationId);\n        }\n    }\n\n    void onAsyncError(final long correlationId, final int codeValue, final ErrorCode errorCode, final String message)\n    {\n        stashedChannelByRegistrationId.remove(correlationId);\n        final RegistrationException ex = new RegistrationException(correlationId, codeValue, errorCode, message);\n        asyncExceptionByRegIdMap.put(correlationId, ex);\n    }\n\n    void onChannelEndpointError(final long correlationId, final String message)\n    {\n        final int statusIndicatorId = (int)correlationId;\n\n        for (final Object resource : resourceByRegIdMap.values())\n        {\n            if (resource instanceof Subscription)\n            {\n                if (((Subscription)resource).channelStatusId() == statusIndicatorId)\n                {\n                    handleError(new ChannelEndpointException(statusIndicatorId, message));\n                }\n            }\n            else if (resource instanceof Publication)\n            {\n                if (((Publication)resource).channelStatusId() == statusIndicatorId)\n                {\n                    handleError(new ChannelEndpointException(statusIndicatorId, message));\n                }\n            }\n        }\n\n        if (asyncCommandIdSet.remove(correlationId))\n        {\n            stashedChannelByRegistrationId.remove(correlationId);\n            handleError(new RegistrationException(\n                correlationId, CHANNEL_ENDPOINT_ERROR.value(), CHANNEL_ENDPOINT_ERROR, message));\n        }\n    }\n\n    void onPublicationError(final PublicationErrorFrameFlyweight errorFrameFlyweight)\n    {\n        for (final Object resource : resourceByRegIdMap.values())\n        {\n            if (resource instanceof Publication)\n            {\n                final Publication publication = (Publication)resource;\n                if (publication.originalRegistrationId() == errorFrameFlyweight.registrationId())\n                {\n                    publicationErrorFrame.set(errorFrameFlyweight);\n                    ctx.publicationErrorFrameHandler().onPublicationError(publicationErrorFrame);\n                }\n            }\n        }\n    }\n\n    void onNewPublication(\n        final long correlationId,\n        final long registrationId,\n        final int streamId,\n        final int sessionId,\n        final int publicationLimitId,\n        final int statusIndicatorId,\n        final String logFileName)\n    {\n        final String stashedChannel = stashedChannelByRegistrationId.remove(correlationId);\n        final ConcurrentPublication publication = new ConcurrentPublication(\n            this,\n            stashedChannel,\n            streamId,\n            sessionId,\n            new UnsafeBufferPosition(counterValuesBuffer, publicationLimitId),\n            statusIndicatorId,\n            logBuffers(registrationId, logFileName, stashedChannel),\n            registrationId,\n            correlationId);\n\n        resourceByRegIdMap.put(correlationId, publication);\n    }\n\n    void onNewExclusivePublication(\n        final long correlationId,\n        final long registrationId,\n        final int streamId,\n        final int sessionId,\n        final int publicationLimitId,\n        final int statusIndicatorId,\n        final String logFileName)\n    {\n        if (correlationId != registrationId)\n        {\n            handleError(new IllegalStateException(\n                \"correlationId=\" + correlationId + \" registrationId=\" + registrationId));\n        }\n\n        final String stashedChannel = stashedChannelByRegistrationId.remove(correlationId);\n        final ExclusivePublication publication = new ExclusivePublication(\n            this,\n            stashedChannel,\n            streamId,\n            sessionId,\n            new UnsafeBufferPosition(counterValuesBuffer, publicationLimitId),\n            statusIndicatorId,\n            logBuffers(registrationId, logFileName, stashedChannel),\n            registrationId,\n            correlationId);\n\n        resourceByRegIdMap.put(correlationId, publication);\n    }\n\n    void onNewSubscription(final long correlationId, final int statusIndicatorId)\n    {\n        final Object resource = resourceByRegIdMap.get(correlationId);\n        final Subscription subscription;\n        if (resource instanceof PendingSubscription)\n        {\n            subscription = ((PendingSubscription)resource).subscription;\n            resourceByRegIdMap.put(correlationId, subscription);\n        }\n        else\n        {\n            subscription = (Subscription)resource;\n        }\n\n        subscription.channelStatusId(statusIndicatorId);\n    }\n\n    void onAvailableImage(\n        final long correlationId,\n        final int sessionId,\n        final long subscriptionRegistrationId,\n        final int subscriberPositionId,\n        final String logFileName,\n        final String sourceIdentity)\n    {\n        final Subscription subscription = (Subscription)resourceByRegIdMap.get(subscriptionRegistrationId);\n        if (null != subscription)\n        {\n            final Image image = new Image(\n                subscription,\n                sessionId,\n                new UnsafeBufferPosition(counterValuesBuffer, subscriberPositionId),\n                logBuffers(correlationId, logFileName, subscription.channel()),\n                ctx.subscriberErrorHandler(),\n                sourceIdentity,\n                correlationId);\n\n            subscription.addImage(image);\n\n            final AvailableImageHandler handler = subscription.availableImageHandler();\n            if (null != handler)\n            {\n                isInCallback = true;\n                try\n                {\n                    handler.onAvailableImage(image);\n                }\n                catch (final Exception ex)\n                {\n                    handleError(ex);\n                }\n                finally\n                {\n                    isInCallback = false;\n                }\n            }\n        }\n    }\n\n    void onUnavailableImage(final long correlationId, final long subscriptionRegistrationId)\n    {\n        final Subscription subscription = (Subscription)resourceByRegIdMap.get(subscriptionRegistrationId);\n        if (null != subscription)\n        {\n            final Image image = subscription.removeImage(correlationId);\n            if (null != image)\n            {\n                final UnavailableImageHandler handler = subscription.unavailableImageHandler();\n                if (null != handler)\n                {\n                    notifyImageUnavailable(handler, image);\n                }\n            }\n        }\n    }\n\n    void onNewCounter(final long correlationId, final int counterId)\n    {\n        resourceByRegIdMap.put(correlationId, new Counter(correlationId, this, counterValuesBuffer, counterId));\n        onAvailableCounter(correlationId, counterId);\n    }\n\n    void onAvailableCounter(final long registrationId, final int counterId)\n    {\n        for (final AvailableCounterHandler handler : availableCounterHandlerById.values())\n        {\n            notifyCounterAvailable(registrationId, counterId, handler);\n        }\n    }\n\n    void onUnavailableCounter(final long registrationId, final int counterId)\n    {\n        notifyUnavailableCounterHandlers(registrationId, counterId);\n    }\n\n    void onClientTimeout()\n    {\n        if (!isClosed)\n        {\n            terminateConductor();\n            handleError(new ClientTimeoutException(\"client timeout from driver\"));\n        }\n    }\n\n    CountersReader countersReader()\n    {\n        return countersReader;\n    }\n\n    void handleError(final Throwable ex)\n    {\n        if (!isClosed)\n        {\n            ctx.errorHandler().onError(ex);\n        }\n    }\n\n    int nextSessionId(final int streamId)\n    {\n        if (controlProtocolVersion >= CONTROL_PROTOCOL_VERSION_WITH_NEXT_AVAILABLE_SESSION_ID_COMMAND)\n        {\n            clientLock.lock();\n            try\n            {\n                ensureActive();\n                ensureNotReentrant();\n\n                lastResponseValue = NULL_VALUE;\n                final long correlationId = driverProxy.nextAvailableSessionId(streamId);\n                awaitResponse(correlationId);\n\n                return (int)lastResponseValue;\n            }\n            finally\n            {\n                clientLock.unlock();\n            }\n        }\n        else\n        {\n            return BitUtil.generateRandomisedId();\n        }\n    }\n\n    ConcurrentPublication addPublication(final String channel, final int streamId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long registrationId = driverProxy.addPublication(channel, streamId);\n            stashedChannelByRegistrationId.put(registrationId, channel);\n            awaitResponse(registrationId);\n\n            return (ConcurrentPublication)resourceByRegIdMap.get(registrationId);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    ExclusivePublication addExclusivePublication(final String channel, final int streamId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long registrationId = driverProxy.addExclusivePublication(channel, streamId);\n            stashedChannelByRegistrationId.put(registrationId, channel);\n            awaitResponse(registrationId);\n\n            return (ExclusivePublication)resourceByRegIdMap.get(registrationId);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncAddPublication(final String channel, final int streamId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long registrationId = driverProxy.addPublication(channel, streamId);\n            stashedChannelByRegistrationId.put(registrationId, channel);\n            asyncCommandIdSet.add(registrationId);\n\n            return registrationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncAddExclusivePublication(final String channel, final int streamId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long registrationId = driverProxy.addExclusivePublication(channel, streamId);\n            stashedChannelByRegistrationId.put(registrationId, channel);\n            asyncCommandIdSet.add(registrationId);\n\n            return registrationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    ConcurrentPublication getPublication(final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (asyncCommandIdSet.contains(registrationId))\n            {\n                service(NO_CORRELATION_ID);\n            }\n\n            return resourceOrThrow(registrationId, ConcurrentPublication.class);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    ExclusivePublication getExclusivePublication(final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (asyncCommandIdSet.contains(registrationId))\n            {\n                service(NO_CORRELATION_ID);\n            }\n\n            return resourceOrThrow(registrationId, ExclusivePublication.class);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void removePublication(final Publication publication)\n    {\n        clientLock.lock();\n        try\n        {\n            if (isTerminating || isClosed)\n            {\n                return;\n            }\n\n            if (!publication.isClosed())\n            {\n                ensureNotReentrant();\n\n                publication.internalClose();\n                if (publication == resourceByRegIdMap.remove(publication.registrationId()))\n                {\n                    releaseLogBuffers(\n                        publication.logBuffers(), publication.originalRegistrationId(), EXPLICIT_CLOSE_LINGER_NS);\n                    asyncCommandIdSet.add(\n                        driverProxy.removePublication(publication.registrationId(), publication.revokeOnClose));\n                }\n            }\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void removePublication(final long publicationRegistrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            if (NULL_VALUE == publicationRegistrationId || isTerminating || isClosed)\n            {\n                return;\n            }\n\n            ensureNotReentrant();\n\n            final Object resource = resourceByRegIdMap.get(publicationRegistrationId);\n            if (null != resource && !(resource instanceof Publication))\n            {\n                throw new AeronException(\"registration id is not a Publication: \" +\n                    resource.getClass().getSimpleName());\n            }\n\n            final Publication publication = (Publication)resource;\n            boolean revokeOnClose = false;\n            if (null != publication)\n            {\n                resourceByRegIdMap.remove(publicationRegistrationId);\n                publication.internalClose();\n                releaseLogBuffers(\n                    publication.logBuffers(), publication.originalRegistrationId(), EXPLICIT_CLOSE_LINGER_NS);\n                revokeOnClose = publication.revokeOnClose;\n            }\n\n            if (asyncCommandIdSet.remove(publicationRegistrationId) || null != publication)\n            {\n                asyncCommandIdSet.add(\n                    driverProxy.removePublication(publicationRegistrationId, revokeOnClose));\n                stashedChannelByRegistrationId.remove(publicationRegistrationId);\n            }\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    Subscription addSubscription(final String channel, final int streamId)\n    {\n        return addSubscription(channel, streamId, defaultAvailableImageHandler, defaultUnavailableImageHandler);\n    }\n\n    Subscription addSubscription(\n        final String channel,\n        final int streamId,\n        final AvailableImageHandler availableImageHandler,\n        final UnavailableImageHandler unavailableImageHandler)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long correlationId = driverProxy.addSubscription(channel, streamId);\n            final Subscription subscription = new Subscription(\n                this,\n                channel,\n                streamId,\n                correlationId,\n                availableImageHandler,\n                unavailableImageHandler);\n\n            resourceByRegIdMap.put(correlationId, subscription);\n            awaitResponse(correlationId);\n\n            return subscription;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncAddSubscription(final String channel, final int streamId)\n    {\n        return asyncAddSubscription(channel, streamId, defaultAvailableImageHandler, defaultUnavailableImageHandler);\n    }\n\n    long asyncAddSubscription(\n        final String channel,\n        final int streamId,\n        final AvailableImageHandler availableImageHandler,\n        final UnavailableImageHandler unavailableImageHandler)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long registrationId = driverProxy.addSubscription(channel, streamId);\n            final PendingSubscription subscription = new PendingSubscription(new Subscription(\n                this,\n                channel,\n                streamId,\n                registrationId,\n                availableImageHandler,\n                unavailableImageHandler));\n\n            resourceByRegIdMap.put(registrationId, subscription);\n            asyncCommandIdSet.add(registrationId);\n\n            return registrationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    Subscription getSubscription(final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (asyncCommandIdSet.contains(registrationId))\n            {\n                service(NO_CORRELATION_ID);\n            }\n\n            return resourceOrThrow(registrationId, Subscription.class);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void removeSubscription(final Subscription subscription)\n    {\n        clientLock.lock();\n        try\n        {\n            if (isTerminating || isClosed)\n            {\n                return;\n            }\n\n            if (!subscription.isClosed())\n            {\n                ensureNotReentrant();\n\n                subscription.internalClose(EXPLICIT_CLOSE_LINGER_NS);\n                final long registrationId = subscription.registrationId();\n                if (subscription == resourceByRegIdMap.remove(registrationId))\n                {\n                    asyncCommandIdSet.add(driverProxy.removeSubscription(registrationId));\n                }\n            }\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void removeSubscription(final long subscriptionRegistrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            if (NULL_VALUE == subscriptionRegistrationId || isTerminating || isClosed)\n            {\n                return;\n            }\n\n            ensureNotReentrant();\n\n            final Object resource = resourceByRegIdMap.get(subscriptionRegistrationId);\n            if (resource != null && !(resource instanceof PendingSubscription || resource instanceof Subscription))\n            {\n                throw new AeronException(\"registration id is not a Subscription: \" +\n                    resource.getClass().getSimpleName());\n            }\n\n            final Subscription subscription = resource instanceof PendingSubscription ?\n                ((PendingSubscription)resource).subscription : (Subscription)resource;\n            if (null != subscription)\n            {\n                resourceByRegIdMap.remove(subscriptionRegistrationId);\n                subscription.internalClose(EXPLICIT_CLOSE_LINGER_NS);\n            }\n\n            if (asyncCommandIdSet.remove(subscriptionRegistrationId) || null != subscription)\n            {\n                asyncCommandIdSet.add(driverProxy.removeSubscription(subscriptionRegistrationId));\n            }\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void addDestination(final long registrationId, final String endpointChannel)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            awaitResponse(driverProxy.addDestination(registrationId, endpointChannel));\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long addDestinationWithId(final long registrationId, final String endpointChannel)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long correlationId = driverProxy.addDestination(registrationId, endpointChannel);\n            awaitResponse(correlationId);\n            return correlationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void removeDestination(final long registrationId, final String endpointChannel)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            awaitResponse(driverProxy.removeDestination(registrationId, endpointChannel));\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void removeDestination(final long publicationRegistrationId, final long destinationRegistrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            awaitResponse(driverProxy.removeDestination(publicationRegistrationId, destinationRegistrationId));\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void addRcvDestination(final long registrationId, final String endpointChannel)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            awaitResponse(driverProxy.addRcvDestination(registrationId, endpointChannel));\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void removeRcvDestination(final long registrationId, final String endpointChannel)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            awaitResponse(driverProxy.removeRcvDestination(registrationId, endpointChannel));\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncAddDestination(final long registrationId, final String endpointChannel)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long correlationId = driverProxy.addDestination(registrationId, endpointChannel);\n            asyncCommandIdSet.add(correlationId);\n            return correlationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncRemoveDestination(final long registrationId, final String endpointChannel)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long correlationId = driverProxy.removeDestination(registrationId, endpointChannel);\n            asyncCommandIdSet.add(correlationId);\n            return correlationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncRemoveDestination(final long registrationId, final long destinationRegistrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long correlationId = driverProxy.removeDestination(registrationId, destinationRegistrationId);\n            asyncCommandIdSet.add(correlationId);\n            return correlationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncAddRcvDestination(final long registrationId, final String endpointChannel)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long correlationId = driverProxy.addRcvDestination(registrationId, endpointChannel);\n            asyncCommandIdSet.add(correlationId);\n            return correlationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncRemoveRcvDestination(final long registrationId, final String endpointChannel)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long correlationId = driverProxy.removeRcvDestination(registrationId, endpointChannel);\n            asyncCommandIdSet.add(correlationId);\n            return correlationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    boolean isCommandActive(final long correlationId)\n    {\n        clientLock.lock();\n        try\n        {\n            if (isClosed)\n            {\n                return false;\n            }\n\n            ensureActive();\n\n            if (asyncCommandIdSet.contains(correlationId))\n            {\n                service(NO_CORRELATION_ID);\n            }\n\n            return asyncCommandIdSet.contains(correlationId);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    boolean hasActiveCommands()\n    {\n        clientLock.lock();\n        try\n        {\n            if (isClosed)\n            {\n                return false;\n            }\n\n            ensureActive();\n\n            return !asyncCommandIdSet.isEmpty();\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    Counter addCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (keyLength < 0 || keyLength > CountersManager.MAX_KEY_LENGTH)\n            {\n                throw new IllegalArgumentException(\"key length out of bounds: \" + keyLength);\n            }\n\n            if (labelLength < 0 || labelLength > CountersManager.MAX_LABEL_LENGTH)\n            {\n                throw new IllegalArgumentException(\"label length out of bounds: \" + labelLength);\n            }\n\n            final long registrationId = driverProxy.addCounter(\n                typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength);\n\n            awaitResponse(registrationId);\n\n            return (Counter)resourceByRegIdMap.get(registrationId);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    Counter addCounter(final int typeId, final String label)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (label.length() > CountersManager.MAX_LABEL_LENGTH)\n            {\n                throw new IllegalArgumentException(\"label length exceeds MAX_LABEL_LENGTH: \" + label.length());\n            }\n\n            final long registrationId = driverProxy.addCounter(typeId, label);\n            awaitResponse(registrationId);\n\n            return (Counter)resourceByRegIdMap.get(registrationId);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    Counter addStaticCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength,\n        final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (keyLength < 0 || keyLength > CountersManager.MAX_KEY_LENGTH)\n            {\n                throw new IllegalArgumentException(\"key length out of bounds: \" + keyLength);\n            }\n\n            if (labelLength < 0 || labelLength > CountersManager.MAX_LABEL_LENGTH)\n            {\n                throw new IllegalArgumentException(\"label length out of bounds: \" + labelLength);\n            }\n\n            final long correlationId = driverProxy.addStaticCounter(\n                typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength, registrationId);\n\n            awaitResponse(correlationId);\n\n            return (Counter)resourceByRegIdMap.get(correlationId);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    Counter addStaticCounter(final int typeId, final String label, final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (label.length() > CountersManager.MAX_LABEL_LENGTH)\n            {\n                throw new IllegalArgumentException(\"label length exceeds MAX_LABEL_LENGTH: \" + label.length());\n            }\n\n            final long correlationId = driverProxy.addStaticCounter(typeId, label, registrationId);\n            awaitResponse(correlationId);\n\n            return (Counter)resourceByRegIdMap.get(correlationId);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncAddCounter(final int typeId, final String label)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (label.length() > CountersManager.MAX_LABEL_LENGTH)\n            {\n                throw new IllegalArgumentException(\"label length exceeds MAX_LABEL_LENGTH: \" + label.length());\n            }\n\n            final long registrationId = driverProxy.addCounter(typeId, label);\n            asyncCommandIdSet.add(registrationId);\n            return registrationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncAddCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (keyLength < 0 || keyLength > CountersManager.MAX_KEY_LENGTH)\n            {\n                throw new IllegalArgumentException(\"key length out of bounds: \" + keyLength);\n            }\n\n            if (labelLength < 0 || labelLength > CountersManager.MAX_LABEL_LENGTH)\n            {\n                throw new IllegalArgumentException(\"label length out of bounds: \" + labelLength);\n            }\n\n            final long registrationId = driverProxy.addCounter(\n                typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength);\n            asyncCommandIdSet.add(registrationId);\n            return registrationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncAddStaticCounter(final int typeId, final String label, final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (label.length() > CountersManager.MAX_LABEL_LENGTH)\n            {\n                throw new IllegalArgumentException(\"label length exceeds MAX_LABEL_LENGTH: \" + label.length());\n            }\n\n            final long correlationId = driverProxy.addStaticCounter(typeId, label, registrationId);\n            asyncCommandIdSet.add(correlationId);\n            return correlationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long asyncAddStaticCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength,\n        final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (keyLength < 0 || keyLength > CountersManager.MAX_KEY_LENGTH)\n            {\n                throw new IllegalArgumentException(\"key length out of bounds: \" + keyLength);\n            }\n\n            if (labelLength < 0 || labelLength > CountersManager.MAX_LABEL_LENGTH)\n            {\n                throw new IllegalArgumentException(\"label length out of bounds: \" + labelLength);\n            }\n\n            final long correlationId = driverProxy.addStaticCounter(\n                typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength, registrationId);\n            asyncCommandIdSet.add(correlationId);\n            return correlationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void asyncRemoveCounter(final long counterRegistrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            if (NULL_VALUE == counterRegistrationId || isTerminating || isClosed)\n            {\n                return;\n            }\n\n            ensureNotReentrant();\n\n            final Object resource = resourceByRegIdMap.get(counterRegistrationId);\n            if (null != resource && !(resource instanceof Counter))\n            {\n                throw new AeronException(\"registration id is not a Counter: \" +\n                    resource.getClass().getSimpleName());\n            }\n\n            final Counter counter = (Counter)resource;\n            if (null != counter)\n            {\n                resourceByRegIdMap.remove(counterRegistrationId);\n                counter.internalClose();\n            }\n\n            if (asyncCommandIdSet.remove(counterRegistrationId) || null != counter)\n            {\n                asyncCommandIdSet.add(driverProxy.removeCounter(counterRegistrationId));\n            }\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    Counter getCounter(final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            if (asyncCommandIdSet.contains(registrationId))\n            {\n                service(NO_CORRELATION_ID);\n            }\n\n            return resourceOrThrow(registrationId, Counter.class);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long addAvailableCounterHandler(final AvailableCounterHandler handler)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long registrationId = aeron.nextCorrelationId();\n            availableCounterHandlerById.put(registrationId, handler);\n            return registrationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    boolean removeAvailableCounterHandler(final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            return availableCounterHandlerById.remove(registrationId) != null;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    boolean removeAvailableCounterHandler(final AvailableCounterHandler handler)\n    {\n        clientLock.lock();\n        try\n        {\n            if (isTerminating || isClosed)\n            {\n                return false;\n            }\n\n            ensureNotReentrant();\n\n            final Long2ObjectHashMap<AvailableCounterHandler>.ValueIterator iterator =\n                availableCounterHandlerById.values().iterator();\n            while (iterator.hasNext())\n            {\n                if (handler == iterator.next())\n                {\n                    iterator.remove();\n                    return true;\n                }\n            }\n\n            return false;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long addUnavailableCounterHandler(final UnavailableCounterHandler handler)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long registrationId = aeron.nextCorrelationId();\n            unavailableCounterHandlerById.put(registrationId, handler);\n            return registrationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    boolean removeUnavailableCounterHandler(final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            return unavailableCounterHandlerById.remove(registrationId) != null;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    boolean removeUnavailableCounterHandler(final UnavailableCounterHandler handler)\n    {\n        clientLock.lock();\n        try\n        {\n            if (isTerminating || isClosed)\n            {\n                return false;\n            }\n\n            ensureNotReentrant();\n\n            final Long2ObjectHashMap<UnavailableCounterHandler>.ValueIterator iterator =\n                unavailableCounterHandlerById.values().iterator();\n            while (iterator.hasNext())\n            {\n                if (handler == iterator.next())\n                {\n                    iterator.remove();\n                    return true;\n                }\n            }\n\n            return false;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    long addCloseHandler(final Runnable handler)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long registrationId = aeron.nextCorrelationId();\n            closeHandlerByIdMap.put(registrationId, handler);\n            return registrationId;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    boolean removeCloseHandler(final long registrationId)\n    {\n        clientLock.lock();\n        try\n        {\n            return closeHandlerByIdMap.remove(registrationId) != null;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    boolean removeCloseHandler(final Runnable handler)\n    {\n        clientLock.lock();\n        try\n        {\n            if (isTerminating || isClosed)\n            {\n                return false;\n            }\n\n            ensureNotReentrant();\n\n            final Long2ObjectHashMap<Runnable>.ValueIterator iterator = closeHandlerByIdMap.values().iterator();\n            while (iterator.hasNext())\n            {\n                if (handler == iterator.next())\n                {\n                    iterator.remove();\n                    return true;\n                }\n            }\n\n            return false;\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void releaseCounter(final Counter counter)\n    {\n        clientLock.lock();\n        try\n        {\n            if (isTerminating || isClosed)\n            {\n                return;\n            }\n\n            ensureNotReentrant();\n\n            final long registrationId = counter.registrationId();\n            if (counter == resourceByRegIdMap.remove(registrationId))\n            {\n                asyncCommandIdSet.add(driverProxy.removeCounter(registrationId));\n            }\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void releaseLogBuffers(\n        final LogBuffers logBuffers, final long registrationId, final long lingerDurationNs)\n    {\n        if (logBuffers.decRef() == 0)\n        {\n            lingeringLogBuffers.add(logBuffers);\n            logBuffersByIdMap.remove(registrationId);\n\n            final long lingerNs = NULL_VALUE == lingerDurationNs ? ctx.resourceLingerDurationNs() : lingerDurationNs;\n            logBuffers.lingerDeadlineNs(nanoClock.nanoTime() + lingerNs);\n        }\n    }\n\n    DriverEventsAdapter driverListenerAdapter()\n    {\n        return driverEventsAdapter;\n    }\n\n    long channelStatus(final int channelStatusId)\n    {\n        return switch (channelStatusId)\n        {\n            case 0 -> ChannelEndpointStatus.INITIALIZING;\n            case ChannelEndpointStatus.NO_ID_ALLOCATED -> ChannelEndpointStatus.ACTIVE;\n            default -> countersReader.getCounterValue(channelStatusId);\n        };\n    }\n\n    void closeImages(final Image[] images, final UnavailableImageHandler unavailableImageHandler, final long lingerNs)\n    {\n        for (final Image image : images)\n        {\n            image.close();\n        }\n\n        for (final Image image : images)\n        {\n            releaseLogBuffers(image.logBuffers(), image.correlationId(), lingerNs);\n        }\n\n        if (null != unavailableImageHandler)\n        {\n            for (final Image image : images)\n            {\n                notifyImageUnavailable(unavailableImageHandler, image);\n            }\n        }\n    }\n\n    void onStaticCounter(final long correlationId, final int counterId)\n    {\n        final CountersReader countersReader = aeron.countersReader();\n        resourceByRegIdMap.put(\n            correlationId,\n            new Counter(countersReader, countersReader.getCounterRegistrationId(counterId), counterId));\n    }\n\n    void rejectImage(final long correlationId, final long position, final String reason)\n    {\n        clientLock.lock();\n        try\n        {\n            ensureActive();\n            ensureNotReentrant();\n\n            final long registrationId = driverProxy.rejectImage(correlationId, position, reason);\n            awaitResponse(registrationId);\n        }\n        finally\n        {\n            clientLock.unlock();\n        }\n    }\n\n    void onNextAvailableSessionId(final int nextSessionId)\n    {\n        lastResponseValue = nextSessionId;\n    }\n\n    private void ensureActive()\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Aeron client is closed\");\n        }\n\n        if (isTerminating)\n        {\n            throw new AeronException(\"Aeron client is terminating\");\n        }\n    }\n\n    private void ensureNotReentrant()\n    {\n        if (isInCallback)\n        {\n            throw new AeronException(\"reentrant calls not permitted during callbacks\");\n        }\n    }\n\n    private LogBuffers logBuffers(final long registrationId, final String logFileName, final String channel)\n    {\n        LogBuffers logBuffers = logBuffersByIdMap.get(registrationId);\n        if (null == logBuffers)\n        {\n            try\n            {\n                logBuffers = logBuffersFactory.map(logFileName);\n\n                if (ctx.preTouchMappedMemory())\n                {\n                    logBuffers.preTouch();\n                }\n\n                logBuffersByIdMap.put(registrationId, logBuffers);\n            }\n            catch (final Exception ex)\n            {\n                throw new AeronException(\"[clientId=\" + ctx.clientId() + \", clientName=\" + ctx.clientName() +\n                    \"] Failed to map log buffer with registrationId=\" + registrationId + \", channel=\" + channel, ex);\n            }\n        }\n\n        logBuffers.incRef();\n\n        return logBuffers;\n    }\n\n    private int service(final long correlationId)\n    {\n        int workCount = 0;\n\n        try\n        {\n            workCount += checkTimeouts(nanoClock.nanoTime());\n            workCount += driverEventsAdapter.receive(correlationId);\n        }\n        catch (final AgentTerminationException ex)\n        {\n            if (isClientApiCall(correlationId))\n            {\n                terminateConductor();\n            }\n\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            if (driverEventsAdapter.isInvalid())\n            {\n                terminateConductor();\n\n                if (!isClientApiCall(correlationId))\n                {\n                    throw new AeronException(\"Driver events adapter is invalid\", ex);\n                }\n            }\n\n            if (isClientApiCall(correlationId))\n            {\n                throw ex;\n            }\n\n            handleError(ex);\n        }\n\n        return workCount;\n    }\n\n    private void terminateConductor()\n    {\n        isTerminating = true;\n        forceCloseResources();\n    }\n\n    private void awaitResponse(final long correlationId)\n    {\n        final long nowNs = nanoClock.nanoTime();\n        final long deadlineNs = nowNs + driverTimeoutNs;\n        checkTimeouts(nowNs);\n\n        awaitingIdleStrategy.reset();\n        do\n        {\n            if (null == driverAgentInvoker)\n            {\n                awaitingIdleStrategy.idle();\n            }\n            else\n            {\n                driverAgentInvoker.invoke();\n            }\n\n            service(correlationId);\n\n            if (driverEventsAdapter.receivedCorrelationId() == correlationId)\n            {\n                stashedChannelByRegistrationId.remove(correlationId);\n                final RegistrationException ex = driverException;\n                if (null != ex)\n                {\n                    driverException = null;\n                    throw ex;\n                }\n\n                return;\n            }\n\n            if (Thread.currentThread().isInterrupted())\n            {\n                terminateConductor();\n                throw new AeronException(\"unexpected interrupt\");\n            }\n        }\n        while (deadlineNs - nanoClock.nanoTime() > 0);\n\n        throw new DriverTimeoutException(\"no response from MediaDriver within \" +\n            SystemUtil.formatDuration(driverTimeoutNs));\n    }\n\n    private int checkTimeouts(final long nowNs)\n    {\n        int workCount = 0;\n\n        if ((timeOfLastServiceNs + idleSleepDurationNs) - nowNs < 0)\n        {\n            checkServiceInterval(nowNs);\n            timeOfLastServiceNs = nowNs;\n\n            workCount += checkLiveness(nowNs);\n            workCount += checkLingeringResources(nowNs);\n        }\n\n        return workCount;\n    }\n\n    private void checkServiceInterval(final long nowNs)\n    {\n        if ((timeOfLastServiceNs + interServiceTimeoutNs) - nowNs < 0)\n        {\n            terminateConductor();\n\n            throw new ConductorServiceTimeoutException(\n                \"service interval exceeded: timeout=\" + SystemUtil.formatDuration(interServiceTimeoutNs) +\n                \", interval=\" + SystemUtil.formatDuration(nowNs - timeOfLastServiceNs));\n        }\n    }\n\n    private int checkLiveness(final long nowNs)\n    {\n        if ((timeOfLastKeepAliveNs + keepAliveIntervalNs) - nowNs < 0)\n        {\n            final long nowMs = epochClock.time();\n            final long lastKeepAliveMs = driverProxy.timeOfLastDriverKeepaliveMs();\n\n            if (nowMs > (lastKeepAliveMs + driverTimeoutMs))\n            {\n                terminateConductor();\n\n                if (Aeron.NULL_VALUE == lastKeepAliveMs)\n                {\n                    throw new DriverTimeoutException(\n                        \"MediaDriver (\" + aeron.context().aeronDirectoryName() + \") has been shutdown\");\n                }\n\n                throw new DriverTimeoutException(\n                    \"MediaDriver (\" + aeron.context().aeronDirectoryName() + \") keepalive: age=\" +\n                        (nowMs - lastKeepAliveMs) + \"ms > timeout=\" + driverTimeoutMs + \"ms\");\n            }\n\n            if (null == heartbeatTimestamp)\n            {\n                final int counterId = HeartbeatTimestamp.findCounterIdByRegistrationId(\n                    countersReader, HEARTBEAT_TYPE_ID, ctx.clientId());\n\n                if (NULL_COUNTER_ID != counterId)\n                {\n                    try\n                    {\n                        heartbeatTimestamp = new AtomicCounter(counterValuesBuffer, counterId);\n                        heartbeatTimestamp.setRelease(nowMs);\n                        appendToLabel(\n                            countersReader.metaDataBuffer(),\n                            counterId,\n                            \" name=\" + ctx.clientName() + \" \" +\n                            formatVersionInfo(AeronVersion.VERSION, AeronVersion.GIT_SHA));\n                        timeOfLastKeepAliveNs = nowNs;\n                    }\n                    catch (final RuntimeException ex)  // a race caused by the driver timing out the client\n                    {\n                        terminateConductor();\n                        throw new AeronException(\"unexpected close of heartbeat timestamp counter: \" + counterId, ex);\n                    }\n                }\n            }\n            else\n            {\n                final int counterId = heartbeatTimestamp.id();\n                if (!HeartbeatTimestamp.isActive(countersReader, counterId, HEARTBEAT_TYPE_ID, ctx.clientId()))\n                {\n                    terminateConductor();\n                    throw new AeronException(\"unexpected close of heartbeat timestamp counter: \" + counterId);\n                }\n\n                heartbeatTimestamp.setRelease(nowMs);\n                timeOfLastKeepAliveNs = nowNs;\n            }\n\n            return 1;\n        }\n\n        return 0;\n    }\n\n    private int checkLingeringResources(final long nowNs)\n    {\n        int workCount = 0;\n\n        for (int lastIndex = lingeringLogBuffers.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final LogBuffers logBuffers = lingeringLogBuffers.get(i);\n            if (logBuffers.lingerDeadlineNs() - nowNs < 0)\n            {\n                ArrayListUtil.fastUnorderedRemove(lingeringLogBuffers, i, lastIndex--);\n                CloseHelper.close(ctx.errorHandler(), logBuffers);\n\n                workCount += 1;\n            }\n        }\n\n        return workCount;\n    }\n\n    private void forceCloseResources()\n    {\n        for (final Object resource : resourceByRegIdMap.values())\n        {\n            if (resource instanceof Subscription subscription)\n            {\n                subscription.internalClose(NULL_VALUE);\n            }\n            else if (resource instanceof Publication publication)\n            {\n                publication.internalClose();\n                releaseLogBuffers(publication.logBuffers(), publication.originalRegistrationId(), NULL_VALUE);\n            }\n            else if (resource instanceof Counter counter && this == counter.clientConductor())\n            {\n                counter.internalClose();\n                notifyUnavailableCounterHandlers(counter.registrationId(), counter.id());\n            }\n        }\n\n        resourceByRegIdMap.clear();\n    }\n\n    private void notifyUnavailableCounterHandlers(final long registrationId, final int counterId)\n    {\n        for (final UnavailableCounterHandler handler : unavailableCounterHandlerById.values())\n        {\n            isInCallback = true;\n            try\n            {\n                handler.onUnavailableCounter(countersReader, registrationId, counterId);\n            }\n            catch (final AgentTerminationException ex)\n            {\n                if (!isTerminating)\n                {\n                    throw ex;\n                }\n                handleError(ex);\n            }\n            catch (final Exception ex)\n            {\n                handleError(ex);\n            }\n            finally\n            {\n                isInCallback = false;\n            }\n        }\n    }\n\n    private void notifyImageUnavailable(final UnavailableImageHandler handler, final Image image)\n    {\n        isInCallback = true;\n        try\n        {\n            handler.onUnavailableImage(image);\n        }\n        catch (final AgentTerminationException ex)\n        {\n            if (!isTerminating)\n            {\n                throw ex;\n            }\n            handleError(ex);\n        }\n        catch (final Exception ex)\n        {\n            handleError(ex);\n        }\n        finally\n        {\n            isInCallback = false;\n        }\n    }\n\n    private void notifyCounterAvailable(\n        final long registrationId, final int counterId, final AvailableCounterHandler handler)\n    {\n        isInCallback = true;\n        try\n        {\n            handler.onAvailableCounter(countersReader, registrationId, counterId);\n        }\n        catch (final AgentTerminationException ex)\n        {\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            handleError(ex);\n        }\n        finally\n        {\n            isInCallback = false;\n        }\n    }\n\n    private void notifyCloseHandlers()\n    {\n        for (final Runnable closeHandler : closeHandlerByIdMap.values())\n        {\n            isInCallback = true;\n            try\n            {\n                closeHandler.run();\n            }\n            catch (final Exception ex)\n            {\n                handleError(ex);\n            }\n            finally\n            {\n                isInCallback = false;\n            }\n        }\n    }\n\n    private <T> T resourceOrThrow(final long registrationId, final Class<T> resourceClass)\n    {\n        final Object resource = resourceByRegIdMap.get(registrationId);\n        if (resourceClass.isInstance(resource))\n        {\n            return resourceClass.cast(resource);\n        }\n\n        final RegistrationException ex = asyncExceptionByRegIdMap.remove(registrationId);\n        if (null != ex)\n        {\n            throw new RegistrationException(ex);\n        }\n\n        return null;\n    }\n\n    private static boolean isClientApiCall(final long correlationId)\n    {\n        return correlationId != NO_CORRELATION_ID;\n    }\n\n    static final class PendingSubscription\n    {\n        final Subscription subscription;\n\n        private PendingSubscription(final Subscription subscription)\n        {\n            this.subscription = subscription;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/CncFileDescriptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SemanticVersion;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.BasicFileAttributes;\n\nimport static org.agrona.BitUtil.*;\n\n/**\n * Description of the command and control file used between driver and clients.\n * <p>\n * File Layout\n * <pre>\n *  +-----------------------------+\n *  |          Meta Data          |\n *  +-----------------------------+\n *  |      to-driver Buffer       |\n *  +-----------------------------+\n *  |      to-clients Buffer      |\n *  +-----------------------------+\n *  |   Counters Metadata Buffer  |\n *  +-----------------------------+\n *  |    Counters Values Buffer   |\n *  +-----------------------------+\n *  |          Error Log          |\n *  +-----------------------------+\n * </pre>\n * <p>\n * Metadata Layout {@link #CNC_VERSION}\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                      Aeron CnC Version                        |\n *  +---------------------------------------------------------------+\n *  |                   to-driver buffer length                     |\n *  +---------------------------------------------------------------+\n *  |                  to-clients buffer length                     |\n *  +---------------------------------------------------------------+\n *  |               Counters Metadata buffer length                 |\n *  +---------------------------------------------------------------+\n *  |                Counters Values buffer length                  |\n *  +---------------------------------------------------------------+\n *  |                   Error Log buffer length                     |\n *  +---------------------------------------------------------------+\n *  |                   Client Liveness Timeout                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Driver Start Timestamp                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                         Driver PID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        File page size                         |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic final class CncFileDescriptor\n{\n    /**\n     * Name used for CnC file in the Aeron directory.\n     */\n    public static final String CNC_FILE = \"cnc.dat\";\n\n    /**\n     * Version of the CnC file using semantic versioning ({@link SemanticVersion}) stored as an 32-bit integer.\n     */\n    public static final int CNC_VERSION = SemanticVersion.compose(0, 2, 0);\n\n    /**\n     * Offset at which the version field can be found.\n     */\n    public static final int CNC_VERSION_FIELD_OFFSET;\n\n    /**\n     * Offset at which the length field can be found for the command ring buffer to the driver.\n     */\n    public static final int TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET;\n\n    /**\n     * Offset at which the length field can be found for the broadcast buffer to the clients can be found.\n     */\n    public static final int TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET;\n\n    /**\n     * Offset at which the length field can be found for counter metadata, e.g. labels, can be found.\n     */\n    public static final int COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET;\n\n    /**\n     * Offset at which the length field can be found for the counters values can be found.\n     */\n    public static final int COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET;\n\n    /**\n     * Offset at which the client liveness timeout value can be found.\n     */\n    public static final int CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET;\n\n    /**\n     * Offset at which the length field can be found for buffer containing the error log can be found.\n     */\n    public static final int ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET;\n\n    /**\n     * Offset at which the start timestamp value for the driver can be found.\n     */\n    public static final int START_TIMESTAMP_FIELD_OFFSET;\n\n    /**\n     * Offset at which the PID value for the driver can be found.\n     */\n    public static final int PID_FIELD_OFFSET;\n\n    /**\n     * Offset at which the file page size value for the driver can be found.\n     */\n    public static final int FILE_PAGE_SIZE_FIELD_OFFSET;\n\n    static\n    {\n        CNC_VERSION_FIELD_OFFSET = 0;\n        TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET = CNC_VERSION_FIELD_OFFSET + SIZE_OF_INT;\n        TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET = TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET + SIZE_OF_INT;\n        COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET = TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET + SIZE_OF_INT;\n        COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET = COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET + SIZE_OF_INT;\n        ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET = COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET + SIZE_OF_INT;\n        CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET = ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET + SIZE_OF_INT;\n        START_TIMESTAMP_FIELD_OFFSET = CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET + SIZE_OF_LONG;\n        PID_FIELD_OFFSET = START_TIMESTAMP_FIELD_OFFSET + SIZE_OF_LONG;\n        FILE_PAGE_SIZE_FIELD_OFFSET = PID_FIELD_OFFSET + SIZE_OF_LONG;\n    }\n\n    private CncFileDescriptor()\n    {\n    }\n\n    /**\n     * Length of the metadata header for the CnC file.\n     */\n    public static final int META_DATA_LENGTH = CACHE_LINE_LENGTH * 2;\n\n    /**\n     * The offset of the first byte past the metadata header which is aligned on a cache-line boundary.\n     */\n    public static final int END_OF_METADATA_OFFSET = META_DATA_LENGTH;\n\n    /**\n     * Compute the length of the cnc file and return it.\n     *\n     * @param totalLengthOfBuffers in bytes.\n     * @param alignment            for file length to adhere to.\n     * @return cnc file length in bytes.\n     */\n    public static int computeCncFileLength(final int totalLengthOfBuffers, final int alignment)\n    {\n        return align(META_DATA_LENGTH + totalLengthOfBuffers, alignment);\n    }\n\n    /**\n     * Offset in the buffer at which the version field exists.\n     *\n     * @param baseOffset for the start of the metadata.\n     * @return offset in the buffer at which the version field exists.\n     */\n    public static int cncVersionOffset(final int baseOffset)\n    {\n        return baseOffset + CNC_VERSION_FIELD_OFFSET;\n    }\n\n    /**\n     * Offset in the buffer at which the to driver buffer length field exists.\n     *\n     * @param baseOffset for the start of the metadata.\n     * @return offset in the buffer at which the to driver buffer length field exists.\n     */\n    public static int toDriverBufferLengthOffset(final int baseOffset)\n    {\n        return baseOffset + TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET;\n    }\n\n    /**\n     * Offset in the buffer at which the to clients buffer length field exists.\n     *\n     * @param baseOffset for the start of the metadata.\n     * @return offset in the buffer at which the to clients buffer length field exists.\n     */\n    public static int toClientsBufferLengthOffset(final int baseOffset)\n    {\n        return baseOffset + TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET;\n    }\n\n    /**\n     * Offset in the buffer at which the counter metadata buffer length field exists.\n     *\n     * @param baseOffset for the start of the metadata.\n     * @return offset in the buffer at which the counter metadata buffer length field exists.\n     */\n    public static int countersMetaDataBufferLengthOffset(final int baseOffset)\n    {\n        return baseOffset + COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET;\n    }\n\n    /**\n     * Offset in the buffer at which the counter value buffer length field exists.\n     *\n     * @param baseOffset for the start of the metadata.\n     * @return offset in the buffer at which the counter value buffer length field exists.\n     */\n    public static int countersValuesBufferLengthOffset(final int baseOffset)\n    {\n        return baseOffset + COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET;\n    }\n\n    /**\n     * Offset in the buffer at which the client liveness timeout field exists.\n     *\n     * @param baseOffset for the start of the metadata.\n     * @return offset in the buffer at which the client liveness timeout field exists.\n     */\n    public static int clientLivenessTimeoutOffset(final int baseOffset)\n    {\n        return baseOffset + CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET;\n    }\n\n    /**\n     * Offset in the buffer at which the error buffer length field exists.\n     *\n     * @param baseOffset for the start of the metadata.\n     * @return offset in the buffer at which the error buffer length field exists.\n     */\n    public static int errorLogBufferLengthOffset(final int baseOffset)\n    {\n        return baseOffset + ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET;\n    }\n\n    /**\n     * Offset in the buffer at which the driver start time timestamp field exists.\n     *\n     * @param baseOffset for the start of the metadata.\n     * @return offset in the buffer at which the driver start time timestamp field exists.\n     */\n    public static int startTimestampOffset(final int baseOffset)\n    {\n        return baseOffset + START_TIMESTAMP_FIELD_OFFSET;\n    }\n\n    /**\n     * Offset in the buffer at which the driver process PID field exists.\n     *\n     * @param baseOffset for the start of the metadata.\n     * @return offset in the buffer at which the driver process PID field exists.\n     */\n    public static int pidOffset(final int baseOffset)\n    {\n        return baseOffset + PID_FIELD_OFFSET;\n    }\n\n    /**\n     * Fill the CnC file with metadata to define its sections.\n     *\n     * @param cncMetaDataBuffer           that wraps the metadata section of the CnC file.\n     * @param toDriverBufferLength        for sending commands to the driver.\n     * @param toClientsBufferLength       for broadcasting events to the clients.\n     * @param counterMetaDataBufferLength buffer length for counters metadata.\n     * @param counterValuesBufferLength   buffer length for counter values.\n     * @param clientLivenessTimeoutNs     timeout value in nanoseconds for client liveness and inter-service interval.\n     * @param errorLogBufferLength        for recording the distinct error log.\n     * @param startTimestampMs            epoch at which the driver started.\n     * @param pid                         for the process hosting the driver.\n     * @param filePageSize                for alignment of all files.\n     */\n    public static void fillMetaData(\n        final UnsafeBuffer cncMetaDataBuffer,\n        final int toDriverBufferLength,\n        final int toClientsBufferLength,\n        final int counterMetaDataBufferLength,\n        final int counterValuesBufferLength,\n        final long clientLivenessTimeoutNs,\n        final int errorLogBufferLength,\n        final long startTimestampMs,\n        final long pid,\n        final int filePageSize)\n    {\n        cncMetaDataBuffer.putInt(TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET, toDriverBufferLength);\n        cncMetaDataBuffer.putInt(TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET, toClientsBufferLength);\n        cncMetaDataBuffer.putInt(COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET, counterMetaDataBufferLength);\n        cncMetaDataBuffer.putInt(COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET, counterValuesBufferLength);\n        cncMetaDataBuffer.putInt(ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET, errorLogBufferLength);\n        cncMetaDataBuffer.putLong(CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET, clientLivenessTimeoutNs);\n        cncMetaDataBuffer.putLong(START_TIMESTAMP_FIELD_OFFSET, startTimestampMs);\n        cncMetaDataBuffer.putLong(PID_FIELD_OFFSET, pid);\n        cncMetaDataBuffer.putInt(FILE_PAGE_SIZE_FIELD_OFFSET, filePageSize);\n    }\n\n    /**\n     * Signal that the CnC file is ready for use by client by writing the version into the CnC file.\n     *\n     * @param cncMetaDataBuffer for the CnC file.\n     */\n    public static void signalCncReady(final UnsafeBuffer cncMetaDataBuffer)\n    {\n        cncMetaDataBuffer.putIntVolatile(CNC_VERSION_FIELD_OFFSET, CNC_VERSION);\n    }\n\n    /**\n     * Create the buffer which wraps the area in the CnC file for the metadata about the CnC file itself.\n     *\n     * @param buffer for the CnC file.\n     * @return the buffer which wraps the area in the CnC file for the metadata about the CnC file itself.\n     */\n    public static UnsafeBuffer createMetaDataBuffer(final ByteBuffer buffer)\n    {\n        return new UnsafeBuffer(buffer, 0, META_DATA_LENGTH);\n    }\n\n    /**\n     * Create the buffer which wraps the area in the CnC file for the command buffer from clients to the driver.\n     *\n     * @param buffer         for the CnC file.\n     * @param metaDataBuffer within the CnC file.\n     * @return a buffer which wraps the section in the CnC file for the command buffer from clients to the driver.\n     */\n    public static UnsafeBuffer createToDriverBuffer(final ByteBuffer buffer, final DirectBuffer metaDataBuffer)\n    {\n        return new UnsafeBuffer(\n            buffer, META_DATA_LENGTH, metaDataBuffer.getInt(TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET));\n    }\n\n    /**\n     * Create the buffer which wraps the section in the CnC file for the broadcast buffer from the driver to clients.\n     *\n     * @param buffer         for the CnC file.\n     * @param metaDataBuffer within the CnC file.\n     * @return a buffer which wraps the section in the CnC file for the broadcast buffer from the driver to clients.\n     */\n    public static UnsafeBuffer createToClientsBuffer(final ByteBuffer buffer, final DirectBuffer metaDataBuffer)\n    {\n        final int offset = META_DATA_LENGTH + metaDataBuffer.getInt(TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET);\n\n        return new UnsafeBuffer(buffer, offset, metaDataBuffer.getInt(TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET));\n    }\n\n    /**\n     * Create the buffer which wraps the section in the CnC file for the counter's metadata.\n     *\n     * @param buffer         for the CnC file.\n     * @param metaDataBuffer within the CnC file.\n     * @return a buffer which wraps the section in the CnC file for the counter's metadata.\n     */\n    public static UnsafeBuffer createCountersMetaDataBuffer(final ByteBuffer buffer, final DirectBuffer metaDataBuffer)\n    {\n        final int offset = META_DATA_LENGTH +\n            metaDataBuffer.getInt(TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET);\n\n        return new UnsafeBuffer(buffer, offset, metaDataBuffer.getInt(COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET));\n    }\n\n    /**\n     * Create the buffer which wraps the section in the CnC file for the counter values.\n     *\n     * @param buffer         for the CnC file.\n     * @param metaDataBuffer within the CnC file.\n     * @return a buffer which wraps the section in the CnC file for the counter values.\n     */\n    public static UnsafeBuffer createCountersValuesBuffer(final ByteBuffer buffer, final DirectBuffer metaDataBuffer)\n    {\n        final int offset = META_DATA_LENGTH +\n            metaDataBuffer.getInt(TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET);\n\n        return new UnsafeBuffer(buffer, offset, metaDataBuffer.getInt(COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET));\n    }\n\n    /**\n     * Create the buffer which wraps the section in the CnC file for the error log.\n     *\n     * @param buffer         for the CnC file.\n     * @param metaDataBuffer within the CnC file.\n     * @return a buffer which wraps the section in the CnC file for the error log.\n     */\n    public static UnsafeBuffer createErrorLogBuffer(final ByteBuffer buffer, final DirectBuffer metaDataBuffer)\n    {\n        final int offset = META_DATA_LENGTH +\n            metaDataBuffer.getInt(TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET);\n\n        return new UnsafeBuffer(buffer, offset, metaDataBuffer.getInt(ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET));\n    }\n\n    /**\n     * Get the timeout in nanoseconds for tracking client liveness and inter-service timeout.\n     *\n     * @param metaDataBuffer for the CnC file.\n     * @return the timeout in milliseconds for tracking client liveness.\n     */\n    public static long clientLivenessTimeoutNs(final DirectBuffer metaDataBuffer)\n    {\n        return metaDataBuffer.getLong(CLIENT_LIVENESS_TIMEOUT_FIELD_OFFSET);\n    }\n\n    /**\n     * Get the start timestamp in milliseconds for the media driver.\n     *\n     * @param metaDataBuffer for the CnC file.\n     * @return the start timestamp in milliseconds for the media driver.\n     */\n    public static long startTimestampMs(final DirectBuffer metaDataBuffer)\n    {\n        return metaDataBuffer.getLong(START_TIMESTAMP_FIELD_OFFSET);\n    }\n\n    /**\n     * Get the process PID hosting the driver.\n     *\n     * @param metaDataBuffer for the CnC file.\n     * @return the process PID hosting the driver.\n     */\n    public static long pid(final DirectBuffer metaDataBuffer)\n    {\n        return metaDataBuffer.getLong(PID_FIELD_OFFSET);\n    }\n\n    /**\n     * Get the file page size.\n     *\n     * @param metaDataBuffer for the CnC file.\n     * @return the file page size.\n     */\n    public static int filePageSize(final DirectBuffer metaDataBuffer)\n    {\n        return metaDataBuffer.getInt(FILE_PAGE_SIZE_FIELD_OFFSET);\n    }\n\n    /**\n     * Check the version of the CnC file is compatible with application.\n     *\n     * @param cncVersion of the CnC file.\n     * @throws AeronException if the major versions are not compatible.\n     */\n    public static void checkVersion(final int cncVersion)\n    {\n        if (SemanticVersion.major(CNC_VERSION) != SemanticVersion.major(cncVersion))\n        {\n            throw new AeronException(\"CnC version not compatible:\" +\n                \" app=\" + SemanticVersion.toString(CNC_VERSION) +\n                \" file=\" + SemanticVersion.toString(cncVersion));\n        }\n    }\n\n    /**\n     * Is the provided length for the CnC file sufficient given what is stored in the metadata.\n     *\n     * @param metaDataBuffer for the CnC file.\n     * @param cncFileLength  to check if it is sufficient based on what is stored in the metadata.\n     * @return true is the length is correct otherwise false.\n     */\n    public static boolean isCncFileLengthSufficient(final DirectBuffer metaDataBuffer, final int cncFileLength)\n    {\n        final int metadataRequiredLength =\n            META_DATA_LENGTH +\n            metaDataBuffer.getInt(TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(TO_CLIENTS_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(COUNTERS_METADATA_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(COUNTERS_VALUES_BUFFER_LENGTH_FIELD_OFFSET) +\n            metaDataBuffer.getInt(ERROR_LOG_BUFFER_LENGTH_FIELD_OFFSET);\n\n        return cncFileLength >= metadataRequiredLength;\n    }\n\n    /**\n     * Determines if this path name matches the cnc file name pattern.\n     *\n     * @param path    to examine.\n     * @param ignored only needed for bi-predicate signature matching.\n     * @return true if the name matches.\n     */\n    public static boolean isCncFile(final Path path, final BasicFileAttributes ignored)\n    {\n        return path.getFileName().toString().equals(CNC_FILE);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/CommonContext.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.exceptions.DriverTimeoutException;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.BufferUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.IoUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.MarkFile;\nimport org.agrona.SemanticVersion;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\nimport org.agrona.concurrent.errors.ErrorConsumer;\nimport org.agrona.concurrent.errors.ErrorLogReader;\nimport org.agrona.concurrent.errors.LoggingErrorHandler;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBufferDescriptor;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.PrintStream;\nimport java.io.UncheckedIOException;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.nio.ByteBuffer;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.AccessDeniedException;\nimport java.nio.file.FileSystemException;\nimport java.nio.file.NoSuchFileException;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\n\nimport static io.aeron.CncFileDescriptor.CNC_FILE;\nimport static io.aeron.CncFileDescriptor.TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET;\nimport static io.aeron.CncFileDescriptor.cncVersionOffset;\nimport static io.aeron.CncFileDescriptor.createToDriverBuffer;\nimport static java.lang.Long.getLong;\nimport static java.lang.System.getProperty;\nimport static java.nio.channels.FileChannel.MapMode.READ_WRITE;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static java.nio.file.StandardOpenOption.READ;\nimport static java.nio.file.StandardOpenOption.WRITE;\n\n/**\n * This class provides the Media Driver and client with common configuration for the Aeron directory.\n * <p>\n * This class should have {@link #conclude()} called before the methods are used or at least\n * {@link #concludeAeronDirectory()} to avoid NPEs.\n * <p>\n * Properties:\n * <ul>\n * <li><code>aeron.dir</code>: Use value as directory name for Aeron buffers and status.</li>\n * </ul>\n */\npublic class CommonContext implements Cloneable\n{\n    /**\n     * Condition to specify a triple state conditional of always override to be true, always override to be false,\n     * or infer value.\n     */\n    public enum InferableBoolean\n    {\n        /**\n         * Force the conditional to be false.\n         */\n        FORCE_FALSE,\n\n        /**\n         * Force the conditional to be true.\n         */\n        FORCE_TRUE,\n\n        /**\n         * Try to infer if true or false is most appropriate.\n         */\n        INFER;\n\n        /**\n         * Parse the string looking for {@code true}, {@code false}, or {@code infer}.\n         *\n         * @param value to be parsed.\n         * @return {@link InferableBoolean} which matches the string.\n         */\n        public static InferableBoolean parse(final String value)\n        {\n            if (null == value || \"infer\".equals(value))\n            {\n                return INFER;\n            }\n\n            return \"true\".equals(value) ? FORCE_TRUE : FORCE_FALSE;\n        }\n    }\n\n    /**\n     * Should a component print its configuration on start to {@link System#out}.\n     */\n    @Config(\n        expectedCEnvVarFieldName = \"AERON_PRINT_CONFIGURATION_ON_START_ENV_VAR\",\n        defaultType = DefaultType.BOOLEAN,\n        defaultBoolean = false)\n    public static final String PRINT_CONFIGURATION_ON_START_PROP_NAME = \"aeron.print.configuration\";\n\n    /**\n     * Property name for driver timeout after which the driver is considered inactive.\n     */\n    @Config\n    public static final String DRIVER_TIMEOUT_PROP_NAME = \"aeron.driver.timeout\";\n\n    /**\n     * Property name for the timeout to use in debug mode. By default, this is not set and the configured\n     * timeouts will be used. Setting this value adjusts timeouts to make debugging easier.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = 0, existsInC = false)\n    public static final String DEBUG_TIMEOUT_PROP_NAME = \"aeron.debug.timeout\";\n\n    /**\n     * Default timeout in which the driver is expected to respond or heartbeat.\n     */\n    @Config(\n        id = \"DRIVER_TIMEOUT\",\n        timeUnit = TimeUnit.MILLISECONDS,\n        expectedCDefaultFieldName = \"AERON_CONTEXT_DRIVER_TIMEOUT_MS_DEFAULT\")\n    public static final long DEFAULT_DRIVER_TIMEOUT_MS = 10_000;\n\n    /**\n     * Timeout in which the driver is expected to respond or heartbeat.\n     */\n    public static final long DRIVER_TIMEOUT_MS = getLong(DRIVER_TIMEOUT_PROP_NAME, DEFAULT_DRIVER_TIMEOUT_MS);\n\n    /**\n     * Value to represent a sessionId that is not to be used.\n     */\n    public static final int NULL_SESSION_ID = Aeron.NULL_VALUE;\n\n    /**\n     * The top level Aeron directory used for communication between a Media Driver and client.\n     */\n    @Config(skipCDefaultValidation = true)\n    public static final String AERON_DIR_PROP_NAME = \"aeron.dir\";\n\n    /**\n     * The value of the top level Aeron directory unless overridden by {@link #aeronDirectoryName(String)}.\n     */\n    @Config(id = \"AERON_DIR\", defaultType = DefaultType.STRING, defaultString = \"OS specific\")\n    public static final String AERON_DIR_PROP_DEFAULT;\n\n    /**\n     * Should new/experimental features be enabled.\n     *\n     * @since 1.44.0\n     */\n    @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false, existsInC = false)\n    public static final String ENABLE_EXPERIMENTAL_FEATURES_PROP_NAME = \"aeron.enable.experimental.features\";\n\n    /**\n     * Property name for a fallback {@link PrintStream} based logger when it is not possible to use the error logging\n     * callback. Supported values are stdout, stderr, no_op (stderr is the default).\n     */\n    @Config(defaultType = DefaultType.STRING, defaultString = \"stderr\", existsInC = false)\n    public static final String FALLBACK_LOGGER_PROP_NAME = \"aeron.fallback.logger\";\n\n    /**\n     * Media type used for IPC shared memory from {@link Publication} to {@link Subscription} channels.\n     */\n    public static final String IPC_MEDIA = \"ipc\";\n\n    /**\n     * Media type used for UDP sockets from {@link Publication} to {@link Subscription} channels.\n     */\n    public static final String UDP_MEDIA = \"udp\";\n\n    /**\n     * URI base used for IPC channels for {@link Publication}s and {@link Subscription}s.\n     */\n    public static final String IPC_CHANNEL = \"aeron:ipc\";\n\n    /**\n     * URI base used for UDP channels for {@link Publication}s and {@link Subscription}s.\n     */\n    public static final String UDP_CHANNEL = \"aeron:udp\";\n\n    /**\n     * URI used for Spy {@link Subscription}s whereby an outgoing unicast or multicast publication can be spied on\n     * by IPC without receiving it again via the network.\n     */\n    public static final String SPY_PREFIX = \"aeron-spy:\";\n\n    /**\n     * The address and port used for a UDP channel. For the publisher it is the socket to send to,\n     * for the subscriber it is the socket to receive from.\n     */\n    public static final String ENDPOINT_PARAM_NAME = \"endpoint\";\n\n    /**\n     * The network interface via which the socket will be routed.\n     */\n    public static final String INTERFACE_PARAM_NAME = \"interface\";\n\n    /**\n     * Initial term id to be used when creating an {@link ExclusivePublication}.\n     */\n    public static final String INITIAL_TERM_ID_PARAM_NAME = \"init-term-id\";\n\n    /**\n     * Current term id to be used when creating an {@link ExclusivePublication}.\n     */\n    public static final String TERM_ID_PARAM_NAME = \"term-id\";\n\n    /**\n     * Current term offset to be used when creating an {@link ExclusivePublication}.\n     */\n    public static final String TERM_OFFSET_PARAM_NAME = \"term-offset\";\n\n    /**\n     * The param name to be used for the term length as a channel URI param.\n     */\n    public static final String TERM_LENGTH_PARAM_NAME = \"term-length\";\n\n    /**\n     * MTU length parameter name for using as a channel URI param. If this is greater than the network MTU for UDP\n     * then the packet will be fragmented and can amplify the impact of loss.\n     */\n    public static final String MTU_LENGTH_PARAM_NAME = \"mtu\";\n\n    /**\n     * Time To Live param for a multicast datagram.\n     */\n    public static final String TTL_PARAM_NAME = \"ttl\";\n\n    /**\n     * The param for the control channel IP address and port for multi-destination-cast semantics.\n     */\n    public static final String MDC_CONTROL_PARAM_NAME = \"control\";\n\n    /**\n     * Key for the mode of control that such be used for multi-destination-cast semantics.\n     */\n    public static final String MDC_CONTROL_MODE_PARAM_NAME = \"control-mode\";\n\n    /**\n     * Valid value for {@link #MDC_CONTROL_MODE_PARAM_NAME} when manual control is desired.\n     */\n    public static final String MDC_CONTROL_MODE_MANUAL = \"manual\";\n\n    /**\n     * Valid value for {@link #MDC_CONTROL_MODE_PARAM_NAME} when dynamic control is desired. Default value.\n     */\n    public static final String MDC_CONTROL_MODE_DYNAMIC = \"dynamic\";\n\n    /**\n     * Valid value for {@link #MDC_CONTROL_MODE_PARAM_NAME} when response control is desired.\n     */\n    public static final String CONTROL_MODE_RESPONSE = \"response\";\n\n    /**\n     * Key for the session id for a publication or restricted subscription.\n     */\n    public static final String SESSION_ID_PARAM_NAME = \"session-id\";\n\n    /**\n     * Key for timeout a publication to linger after draining in nanoseconds.\n     */\n    public static final String LINGER_PARAM_NAME = \"linger\";\n\n    /**\n     * Parameter name for channel URI param to indicate if a subscribed stream must be reliable or not.\n     * Value is boolean with true to recover loss and false to gap fill.\n     */\n    public static final String RELIABLE_STREAM_PARAM_NAME = \"reliable\";\n\n    /**\n     * Key for the tags for a channel.\n     */\n    public static final String TAGS_PARAM_NAME = \"tags\";\n\n    /**\n     * Qualifier for a value which is a tag for reference. This prefix is use in the param value.\n     */\n    public static final String TAG_PREFIX = \"tag:\";\n\n    /**\n     * Parameter name for channel URI param to indicate if term buffers should be sparse. Value is boolean.\n     */\n    public static final String SPARSE_PARAM_NAME = \"sparse\";\n\n    /**\n     * Parameter name for channel URI param to indicate an alias for the given URI. Value not interpreted by Aeron.\n     * <p>\n     * This is a reserved application level param used to identify a particular channel for application purposes.\n     */\n    public static final String ALIAS_PARAM_NAME = \"alias\";\n\n    /**\n     * Parameter name for channel URI param to indicate if End of Stream (EOS) should be sent or not. Value is boolean.\n     */\n    public static final String EOS_PARAM_NAME = \"eos\";\n\n    /**\n     * Parameter name for channel URI param to indicate if a subscription should tether for local flow control.\n     * Value is boolean. A tether only applies when there is more than one matching active subscription. If tether is\n     * true then that subscription is included in flow control. If only one subscription then it tethers pace.\n     */\n    public static final String TETHER_PARAM_NAME = \"tether\";\n\n    /**\n     * Parameter name for channel URI param to indicate if a Subscription represents a group member or individual\n     * from the perspective of message reception. This can inform loss handling and similar semantics.\n     * <p>\n     * When configuring a subscription for an MDC publication then should be added as this is effective multicast.\n     *\n     * @see CommonContext#MDC_CONTROL_MODE_PARAM_NAME\n     * @see CommonContext#MDC_CONTROL_PARAM_NAME\n     */\n    public static final String GROUP_PARAM_NAME = \"group\";\n\n    /**\n     * Parameter name for Subscription URI param to indicate if Images that go unavailable should be allowed to\n     * rejoin after a short cooldown or not.\n     */\n    public static final String REJOIN_PARAM_NAME = \"rejoin\";\n\n    /**\n     * Parameter name for Subscription URI param to indicate the congestion control algorithm to be used.\n     * Options include {@code static} and {@code cubic}.\n     */\n    public static final String CONGESTION_CONTROL_PARAM_NAME = \"cc\";\n\n    /**\n     * Parameter name for Publication URI param to indicate the flow control strategy to be used.\n     * Options include {@code min}, {@code max}, and {@code pref}.\n     */\n    public static final String FLOW_CONTROL_PARAM_NAME = \"fc\";\n\n    /**\n     * Parameter name for Subscription URI param to indicate the receiver tag to be sent in SMs.\n     */\n    public static final String GROUP_TAG_PARAM_NAME = \"gtag\";\n\n    /**\n     * Parameter name for Publication URI param to indicate whether spy subscriptions should simulate a connection.\n     */\n    public static final String SPIES_SIMULATE_CONNECTION_PARAM_NAME = \"ssc\";\n\n    /**\n     * Parameter name for the underlying OS socket send buffer length.\n     */\n    public static final String SOCKET_SNDBUF_PARAM_NAME = \"so-sndbuf\";\n\n    /**\n     * Parameter name for the underlying OS socket receive buffer length.\n     */\n    public static final String SOCKET_RCVBUF_PARAM_NAME = \"so-rcvbuf\";\n\n    /**\n     * Parameter name for the congestion control's initial receiver window length.\n     */\n    public static final String RECEIVER_WINDOW_LENGTH_PARAM_NAME = \"rcv-wnd\";\n\n    /**\n     * Parameter name of the offset for the media receive timestamp to be inserted into the incoming message on a\n     * subscription. The special value of 'reserved' can be used to insert into the reserved value field. Media\n     * receive timestamp is taken as the earliest possible point after the packet is received from the network. This\n     * is only supported in the C media driver, the Java Media Driver will generate an error if used.\n     */\n    public static final String MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME = \"media-rcv-ts-offset\";\n\n    /**\n     * Parameter name of the offset for the channel receive timestamp to be inserted into the incoming message on a\n     * subscription. The special value of 'reserved' can be used to insert into the reserved value field. Channel\n     * receive timestamp is taken as soon a possible after the packet is received by Aeron receiver from the transport\n     * bindings.\n     */\n    public static final String CHANNEL_RECEIVE_TIMESTAMP_OFFSET_PARAM_NAME = \"channel-rcv-ts-offset\";\n\n    /**\n     * Parameter name of the offset for the channel send timestamp to be inserted into the outgoing message\n     * on a publication. The special value of 'reserved' can be used to insert into the reserved value\n     * field. Channel send timestamp is taken shortly before passing the message over to the configured transport\n     * bindings.\n     */\n    public static final String CHANNEL_SEND_TIMESTAMP_OFFSET_PARAM_NAME = \"channel-snd-ts-offset\";\n\n    /**\n     * Placeholder value to use in URIs to specify that a timestamp should be stored in the reserved value field.\n     */\n    public static final String RESERVED_OFFSET = \"reserved\";\n\n    /**\n     * Parameter name for the field that will be used to specify the response endpoint on a subscription and publication\n     * used in a response \"server\".\n     *\n     * @since 1.44.0\n     */\n    public static final String RESPONSE_ENDPOINT_PARAM_NAME = \"response-endpoint\";\n\n    /**\n     * Parameter name for the field that will be used to specify the correlation id used on a publication to connect it\n     * to a subscription's image in order to set up a response stream.\n     *\n     * @since 1.44.0\n     */\n    public static final String RESPONSE_CORRELATION_ID_PARAM_NAME = \"response-correlation-id\";\n\n    /**\n     * Placeholder value to use in response channels where the publication is to be pre-created to reserve and hold\n     * onto the local port.\n     */\n    public static final String PROTOTYPE_CORRELATION_ID = \"prototype\";\n\n    /**\n     * Parameter name to set explicit NAK delay (e.g. {@code nak-delay=200ms}).\n     *\n     * @since 1.44.0\n     */\n    public static final String NAK_DELAY_PARAM_NAME = \"nak-delay\";\n\n    /**\n     * Parameter name to set explicit untethered window limit timeout, e.g. {@code untethered-window-limit-timeout=10s}.\n     *\n     * @since 1.45.0\n     */\n    public static final String UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME = \"untethered-window-limit-timeout\";\n\n    /**\n     * Parameter name to set explicit untethered linger timeout, e.g. {@code untethered-linger-timeout=10s}.\n     *\n     * @since 1.48.0\n     */\n    public static final String UNTETHERED_LINGER_TIMEOUT_PARAM_NAME = \"untethered-linger-timeout\";\n\n    /**\n     * Parameter name to set explicit untethered resting timeout, e.g. {@code untethered-resting-timeout=10s}.\n     *\n     * @since 1.45.0\n     */\n    public static final String UNTETHERED_RESTING_TIMEOUT_PARAM_NAME = \"untethered-resting-timeout\";\n\n    /**\n     * Parameter name to set the max number of outstanding active retransmits for a publication.\n     *\n     * @since 1.45.0\n     */\n    public static final String MAX_RESEND_PARAM_NAME = \"max-resend\";\n\n    /**\n     * Parameter name to set the stream id for the channel.\n     *\n     * @since 1.47.0\n     */\n    public static final String STREAM_ID_PARAM_NAME = \"stream-id\";\n\n    /**\n     * Parameter name for the publication window length, i.e. how far ahead can publication accept offers.\n     *\n     * @since 1.47.0\n     */\n    public static final String PUBLICATION_WINDOW_LENGTH_PARAM_NAME = \"pub-wnd\";\n\n    /**\n     * Property name to use to set the secure random algorithm to be used by the Aeron component.\n     */\n    public static final String SECURE_RANDOM_ALGORITHM_PROP_NAME = \"aeron.secure.random.algorithm\";\n\n    /**\n     * The default secure random algorithm to be used.\n     */\n    public static final String SECURE_RANDOM_ALGORITHM_DEFAULT =\n        SystemUtil.isWindows() ? \"Windows-PRNG\" : \"NativePRNGNonBlocking\";\n\n    /**\n     * Should a component's configuration be printed on start.\n     *\n     * @return {@code true} if the configuration should be printed on start.\n     * @see #PRINT_CONFIGURATION_ON_START_PROP_NAME\n     */\n    public static boolean shouldPrintConfigurationOnStart()\n    {\n        return \"true\".equals(getProperty(PRINT_CONFIGURATION_ON_START_PROP_NAME));\n    }\n\n    /**\n     * Get the configured value for the secure random algorithm, falling back to the default if not supplied.\n     *\n     * @return the secure random algorithm\n     * @see #SECURE_RANDOM_ALGORITHM_PROP_NAME\n     * @see #SECURE_RANDOM_ALGORITHM_DEFAULT\n     */\n    public static String getSecureRandomAlgorithm()\n    {\n        return System.getProperty(SECURE_RANDOM_ALGORITHM_PROP_NAME, SECURE_RANDOM_ALGORITHM_DEFAULT);\n    }\n\n    /**\n     * Get the current fallback logger based on the supplied property.\n     *\n     * @return the configured PrintStream.\n     */\n    @Config\n    public static PrintStream fallbackLogger()\n    {\n        final String fallbackLoggerName = getProperty(FALLBACK_LOGGER_PROP_NAME, \"stderr\");\n        return switch (fallbackLoggerName)\n        {\n            case \"stdout\" -> System.out;\n            case \"no_op\" -> NO_OP_LOGGER;\n            default -> System.err;\n        };\n    }\n\n    private static final PrintStream NO_OP_LOGGER = new PrintStream(\n        new OutputStream()\n        {\n            public void write(final int b)\n            {\n                // No-op\n            }\n        });\n    private static final Map<String, Boolean> DEBUG_FIELDS_SEEN = new ConcurrentHashMap<>();\n    private static final VarHandle IS_CONCLUDED_VH;\n\n    static\n    {\n        try\n        {\n            IS_CONCLUDED_VH = MethodHandles.lookup().findVarHandle(CommonContext.class, \"isConcluded\", boolean.class);\n        }\n        catch (final ReflectiveOperationException ex)\n        {\n            throw new ExceptionInInitializerError(ex);\n        }\n    }\n\n    private volatile boolean isConcluded;\n    private long driverTimeoutMs = DRIVER_TIMEOUT_MS;\n    private String aeronDirectoryName = getAeronDirectoryName();\n    private File aeronDirectory;\n    private File cncFile;\n    private UnsafeBuffer countersMetaDataBuffer;\n    private UnsafeBuffer countersValuesBuffer;\n    private boolean enableExperimentalFeatures = Boolean.getBoolean(ENABLE_EXPERIMENTAL_FEATURES_PROP_NAME);\n\n    static\n    {\n        String baseDirName = null;\n\n        if (SystemUtil.isLinux())\n        {\n            final File devShmDir = new File(\"/dev/shm\");\n            if (devShmDir.exists())\n            {\n                baseDirName = \"/dev/shm/aeron\";\n            }\n        }\n\n        if (null == baseDirName)\n        {\n            baseDirName = SystemUtil.tmpDirName() + \"aeron\";\n        }\n\n        AERON_DIR_PROP_DEFAULT = baseDirName + '-' + System.getProperty(\"user.name\", \"default\");\n    }\n\n    /**\n     * Construct a CommonContext using default values and loading from system properties.\n     */\n    public CommonContext()\n    {\n    }\n\n    /**\n     * Perform a shallow copy of the object.\n     *\n     * @return a shallow copy of the object.\n     */\n    public CommonContext clone()\n    {\n        try\n        {\n            return (CommonContext)super.clone();\n        }\n        catch (final CloneNotSupportedException ex)\n        {\n            throw new RuntimeException(ex);\n        }\n    }\n\n    /**\n     * Get the default directory name to be used if {@link #aeronDirectoryName(String)} is not set. This will take\n     * the {@link #AERON_DIR_PROP_NAME} if set and if not then {@link #AERON_DIR_PROP_DEFAULT}.\n     *\n     * @return the default directory name to be used if {@link #aeronDirectoryName(String)} is not set.\n     */\n    @Config(id = \"AERON_DIR\")\n    public static String getAeronDirectoryName()\n    {\n        return getProperty(AERON_DIR_PROP_NAME, AERON_DIR_PROP_DEFAULT);\n    }\n\n    /**\n     * Convert the default Aeron directory name to be a random name for use with embedded drivers.\n     *\n     * @return random directory name with default directory name as base\n     */\n    public static String generateRandomDirName()\n    {\n        return AERON_DIR_PROP_DEFAULT + \"-\" + UUID.randomUUID();\n    }\n\n    /**\n     * This completes initialization of the CommonContext object. It is automatically called by subclasses.\n     *\n     * @return this Object for method chaining.\n     */\n    public CommonContext conclude()\n    {\n        if ((boolean)IS_CONCLUDED_VH.getAndSet(this, true))\n        {\n            throw new ConcurrentConcludeException();\n        }\n\n        concludeAeronDirectory();\n\n        cncFile = new File(aeronDirectory, CncFileDescriptor.CNC_FILE);\n\n        return this;\n    }\n\n    /**\n     * Has the context had the {@link #conclude()} method called.\n     *\n     * @return true of the {@link #conclude()} method has been called.\n     */\n    public boolean isConcluded()\n    {\n        return isConcluded;\n    }\n\n    /**\n     * Conclude the {@link #aeronDirectory()} so it does not need to keep being recreated.\n     *\n     * @return this for a fluent API.\n     */\n    public CommonContext concludeAeronDirectory()\n    {\n        if (null == aeronDirectory)\n        {\n            try\n            {\n                aeronDirectory = new File(aeronDirectoryName).getCanonicalFile();\n            }\n            catch (final IOException e)\n            {\n                throw new UncheckedIOException(e);\n            }\n        }\n\n        return this;\n    }\n\n    /**\n     * Get the top level Aeron directory used for communication between the client and Media Driver, and\n     * the location of the data buffers.\n     *\n     * @return The top level Aeron directory.\n     */\n    public String aeronDirectoryName()\n    {\n        return aeronDirectoryName;\n    }\n\n    /**\n     * Get the directory in which the aeron config files are stored.\n     * <p>\n     * This is valid after a call to {@link #conclude()} or {@link #concludeAeronDirectory()}.\n     *\n     * @return the directory in which the aeron config files are stored.\n     * @see #aeronDirectoryName()\n     */\n    public File aeronDirectory()\n    {\n        return aeronDirectory;\n    }\n\n    /**\n     * Set the top level Aeron directory used for communication between the client and Media Driver, and the location\n     * of the data buffers.\n     *\n     * @param dirName New top level Aeron directory.\n     * @return this for a fluent API.\n     */\n    public CommonContext aeronDirectoryName(final String dirName)\n    {\n        this.aeronDirectoryName = dirName;\n        return this;\n    }\n\n    /**\n     * Create a new command and control file in the administration directory.\n     *\n     * @return The newly created File.\n     */\n    public static File newDefaultCncFile()\n    {\n        return new File(getProperty(AERON_DIR_PROP_NAME, AERON_DIR_PROP_DEFAULT), CncFileDescriptor.CNC_FILE);\n    }\n\n    /**\n     * Create a new command and control file in the administration directory.\n     *\n     * @param aeronDirectoryName name of the aeronDirectory that containing the cnc file.\n     * @return The newly created File.\n     */\n    public static File newCncFile(final String aeronDirectoryName)\n    {\n        return new File(aeronDirectoryName, CncFileDescriptor.CNC_FILE);\n    }\n\n    /**\n     * Get the buffer containing the counter metadata. These counters are R/W for the driver, read only for all\n     * other users.\n     *\n     * @return The buffer storing the counter metadata.\n     */\n    public UnsafeBuffer countersMetaDataBuffer()\n    {\n        return countersMetaDataBuffer;\n    }\n\n    /**\n     * Set the buffer containing the counter metadata. Testing/internal purposes only.\n     *\n     * @param countersMetaDataBuffer The new counter metadata buffer.\n     * @return this for a fluent API.\n     */\n    public CommonContext countersMetaDataBuffer(final UnsafeBuffer countersMetaDataBuffer)\n    {\n        this.countersMetaDataBuffer = countersMetaDataBuffer;\n        return this;\n    }\n\n    /**\n     * Get the buffer containing the counters. These counters are R/W for the driver, read only for all other users.\n     *\n     * @return The buffer storing the counters.\n     */\n    public UnsafeBuffer countersValuesBuffer()\n    {\n        return countersValuesBuffer;\n    }\n\n    /**\n     * Set the buffer containing the counters. Testing/internal purposes only.\n     *\n     * @param countersValuesBuffer The new counters buffer.\n     * @return this for a fluent API.\n     */\n    public CommonContext countersValuesBuffer(final UnsafeBuffer countersValuesBuffer)\n    {\n        this.countersValuesBuffer = countersValuesBuffer;\n        return this;\n    }\n\n    /**\n     * Get the command and control file.\n     *\n     * @return The command and control file.\n     */\n    public File cncFile()\n    {\n        return cncFile;\n    }\n\n    /**\n     * Set the driver timeout in milliseconds.\n     *\n     * @param driverTimeoutMs to indicate liveness of driver\n     * @return this for a fluent API.\n     */\n    public CommonContext driverTimeoutMs(final long driverTimeoutMs)\n    {\n        this.driverTimeoutMs = driverTimeoutMs;\n        return this;\n    }\n\n    /**\n     * Get the driver timeout in milliseconds.\n     *\n     * @return driver timeout in milliseconds.\n     */\n    @Config(id = \"DRIVER_TIMEOUT\")\n    public long driverTimeoutMs()\n    {\n        return checkDebugTimeout(driverTimeoutMs, TimeUnit.MILLISECONDS);\n    }\n\n    /**\n     * Should experimental features for the driver be enabled.\n     *\n     * @return <code>true</code> if enabled, <code>false</code> otherwise.\n     * @see #enableExperimentalFeatures(boolean)\n     * @since 1.44.0\n     */\n    @Config\n    public boolean enableExperimentalFeatures()\n    {\n        return enableExperimentalFeatures;\n    }\n\n    /**\n     * Should experimental features for the driver be enabled.\n     *\n     * @param enableExperimentalFeatures indicate whether experimental features for the driver should be enabled.\n     * @return this for a fluent API\n     * @see #ENABLE_EXPERIMENTAL_FEATURES_PROP_NAME\n     * @see #enableExperimentalFeatures()\n     * @since 1.44.0\n     */\n    public CommonContext enableExperimentalFeatures(final boolean enableExperimentalFeatures)\n    {\n        this.enableExperimentalFeatures = enableExperimentalFeatures;\n        return this;\n    }\n\n    /**\n     * Override the supplied timeout with the debug value if it has been set, and we are in debug mode.\n     *\n     * @param timeout  The timeout value currently in use.\n     * @param timeUnit The units of the timeout value. Debug timeout is specified in ns, so will be converted to this\n     *                 unit.\n     * @return The debug timeout if specified, and we are being debugged or the supplied value if not. Will be in\n     * timeUnit units.\n     */\n    public static long checkDebugTimeout(final long timeout, final TimeUnit timeUnit)\n    {\n        return checkDebugTimeout(timeout, timeUnit, 1.0);\n    }\n\n    /**\n     * Override the supplied timeout with the debug value if it has been set, and we are in debug mode.\n     *\n     * @param timeout  The timeout value currently in use.\n     * @param timeUnit The units of the timeout value. Debug timeout is specified in ns, so will be converted to this\n     *                 unit.\n     * @param factor   to multiply the debug timeout by. Required when some timeouts need to be larger than others in\n     *                 order to pass validation. E.g. clientLiveness and publicationUnblock.\n     * @return The debug timeout if specified, and we are being debugged or the supplied value if not. Will be in\n     * timeUnit units.\n     */\n    public static long checkDebugTimeout(final long timeout, final TimeUnit timeUnit, final double factor)\n    {\n        final String debugTimeoutString = getProperty(DEBUG_TIMEOUT_PROP_NAME);\n        if (null == debugTimeoutString || !SystemUtil.isDebuggerAttached())\n        {\n            return timeout;\n        }\n\n        try\n        {\n            final long debugTimeoutNs =\n                (long)(factor * SystemUtil.parseDuration(DEBUG_TIMEOUT_PROP_NAME, debugTimeoutString));\n            final long debugTimeout = timeUnit.convert(debugTimeoutNs, TimeUnit.NANOSECONDS);\n            final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();\n\n            String debugFieldName = \"<unknown>\";\n            for (int i = 0; i < stackTrace.length; i++)\n            {\n                final String methodName = stackTrace[i].getMethodName();\n                if (!\"checkDebugTimeout\".equals(methodName) && !\"getStackTrace\".equals(methodName))\n                {\n                    final String className = stackTrace[i].getClassName();\n                    debugFieldName = className + \".\" + methodName;\n                    break;\n                }\n            }\n\n            if (null == DEBUG_FIELDS_SEEN.putIfAbsent(debugFieldName, true))\n            {\n                final String message = \"Using debug timeout [\" + debugTimeout + \"] for \" + debugFieldName +\n                    \" replacing [\" + timeout + \"]\";\n                System.out.println(message);\n            }\n\n            return debugTimeout;\n        }\n        catch (final NumberFormatException ignore)\n        {\n            return timeout;\n        }\n    }\n\n    /**\n     * Delete the current Aeron directory, throwing errors if not possible.\n     */\n    public void deleteAeronDirectory()\n    {\n        IoUtil.delete(aeronDirectory, false);\n    }\n\n    /**\n     * Map the CnC file if it exists.\n     *\n     * @param logger for feedback\n     * @return a new mapping for the file if it exists otherwise null;\n     */\n    public MappedByteBuffer mapExistingCncFile(final Consumer<String> logger)\n    {\n        final File cncFile = new File(aeronDirectory, CncFileDescriptor.CNC_FILE);\n\n        if (cncFile.exists() && cncFile.length() > CncFileDescriptor.META_DATA_LENGTH)\n        {\n            if (null != logger)\n            {\n                logger.accept(\"INFO: Aeron CnC file exists: \" + cncFile);\n            }\n\n            return IoUtil.mapExistingFile(cncFile, CncFileDescriptor.CNC_FILE);\n        }\n\n        return null;\n    }\n\n    /**\n     * Is a media driver active in the given directory?\n     *\n     * @param directory       to check.\n     * @param driverTimeoutMs for the driver liveness check.\n     * @param logger          for feedback as liveness checked.\n     * @return true if a driver is active or false if not.\n     */\n    public static boolean isDriverActive(\n        final File directory, final long driverTimeoutMs, final Consumer<String> logger)\n    {\n        final File cncFile = new File(directory, CncFileDescriptor.CNC_FILE);\n\n        if (cncFile.exists() && cncFile.length() > CncFileDescriptor.META_DATA_LENGTH)\n        {\n            logger.accept(\"INFO: Aeron CnC file exists: \" + cncFile);\n\n            final MappedByteBuffer cncByteBuffer = IoUtil.mapExistingFile(cncFile, \"CnC file\");\n            try\n            {\n                return isDriverActive(driverTimeoutMs, logger, cncByteBuffer);\n            }\n            finally\n            {\n                BufferUtil.free(cncByteBuffer);\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Is a media driver active in the current Aeron directory?\n     *\n     * @param driverTimeoutMs for the driver liveness check.\n     * @param logger          for feedback as liveness checked.\n     * @return true if a driver is active or false if not.\n     */\n    public boolean isDriverActive(final long driverTimeoutMs, final Consumer<String> logger)\n    {\n        final MappedByteBuffer cncByteBuffer = mapExistingCncFile(logger);\n        try\n        {\n            return isDriverActive(driverTimeoutMs, logger, cncByteBuffer);\n        }\n        finally\n        {\n            BufferUtil.free(cncByteBuffer);\n        }\n    }\n\n    /**\n     * Is a media driver active in the current mapped CnC buffer? If the driver is starting then it will wait for\n     * up to the driverTimeoutMs by checking for the cncVersion being set.\n     *\n     * @param driverTimeoutMs for the driver liveness check.\n     * @param logger          for feedback as liveness checked.\n     * @param cncByteBuffer   for the existing CnC file.\n     * @return true if a driver is active or false if not.\n     */\n    public static boolean isDriverActive(\n        final long driverTimeoutMs, final Consumer<String> logger, final ByteBuffer cncByteBuffer)\n    {\n        if (null == cncByteBuffer)\n        {\n            return false;\n        }\n\n        final UnsafeBuffer cncMetaDataBuffer = CncFileDescriptor.createMetaDataBuffer(cncByteBuffer);\n\n        final long startTimeMs = System.currentTimeMillis();\n        int cncVersion;\n        while (0 == (cncVersion = cncMetaDataBuffer.getIntVolatile(CncFileDescriptor.cncVersionOffset(0))))\n        {\n            if (System.currentTimeMillis() > (startTimeMs + driverTimeoutMs))\n            {\n                throw new DriverTimeoutException(\"CnC file is created but not initialised.\");\n            }\n\n            sleep(1);\n        }\n\n        CncFileDescriptor.checkVersion(cncVersion);\n\n        final ManyToOneRingBuffer toDriverBuffer = new ManyToOneRingBuffer(\n            CncFileDescriptor.createToDriverBuffer(cncByteBuffer, cncMetaDataBuffer));\n\n        final long timestampMs = toDriverBuffer.consumerHeartbeatTime();\n        final long nowMs = System.currentTimeMillis();\n        final long timestampAgeMs = nowMs - timestampMs;\n\n        logger.accept(\"INFO: Aeron toDriver consumer heartbeat age is (ms): \" + timestampAgeMs);\n\n        return timestampAgeMs <= driverTimeoutMs;\n    }\n\n    /**\n     * Request a driver to run its termination hook.\n     *\n     * @param directory   for the driver.\n     * @param tokenBuffer containing the optional token for the request.\n     * @param tokenOffset within the tokenBuffer at which the token begins.\n     * @param tokenLength of the token in the tokenBuffer.\n     * @return true if request was sent or false if request could not be sent.\n     */\n    public static boolean requestDriverTermination(\n        final File directory,\n        final DirectBuffer tokenBuffer,\n        final int tokenOffset,\n        final int tokenLength)\n    {\n        final File cncFile = new File(directory, CncFileDescriptor.CNC_FILE);\n\n        if (cncFile.exists() && cncFile.length() > CncFileDescriptor.META_DATA_LENGTH)\n        {\n            final MappedByteBuffer cncByteBuffer = IoUtil.mapExistingFile(cncFile, \"CnC file\");\n            try\n            {\n                final UnsafeBuffer cncMetaDataBuffer = CncFileDescriptor.createMetaDataBuffer(cncByteBuffer);\n                final int cncVersion = cncMetaDataBuffer.getIntVolatile(cncVersionOffset(0));\n\n                if (cncVersion > 0)\n                {\n                    CncFileDescriptor.checkVersion(cncVersion);\n\n                    final ManyToOneRingBuffer toDriverBuffer = new ManyToOneRingBuffer(\n                        CncFileDescriptor.createToDriverBuffer(cncByteBuffer, cncMetaDataBuffer));\n                    final long clientId = toDriverBuffer.nextCorrelationId();\n                    final DriverProxy driverProxy = new DriverProxy(toDriverBuffer, clientId);\n\n                    return driverProxy.terminateDriver(tokenBuffer, tokenOffset, tokenLength);\n                }\n            }\n            finally\n            {\n                BufferUtil.free(cncByteBuffer);\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Read the error log to a given {@link PrintStream}.\n     *\n     * @param out to write the error log contents to.\n     * @return the number of observations from the error log.\n     */\n    public int saveErrorLog(final PrintStream out)\n    {\n        final MappedByteBuffer cncByteBuffer = mapExistingCncFile(null);\n        try\n        {\n            return saveErrorLog(out, cncByteBuffer);\n        }\n        finally\n        {\n            BufferUtil.free(cncByteBuffer);\n        }\n    }\n\n    /**\n     * Read the error log to a given {@link PrintStream}.\n     *\n     * @param out           to write the error log contents to.\n     * @param cncByteBuffer containing the error log.\n     * @return the number of observations from the error log.\n     */\n    public int saveErrorLog(final PrintStream out, final ByteBuffer cncByteBuffer)\n    {\n        if (null == cncByteBuffer)\n        {\n            return 0;\n        }\n\n        return printErrorLog(errorLogBuffer(cncByteBuffer), out);\n    }\n\n    /**\n     * Release resources used by the CommonContext.\n     */\n    public void close()\n    {\n    }\n\n    /**\n     * Print the contents of an error log to a {@link PrintStream} in human-readable format.\n     *\n     * @param errorBuffer to read errors from.\n     * @param out         print the errors to.\n     * @return number of distinct errors observed.\n     */\n    public static int printErrorLog(final AtomicBuffer errorBuffer, final PrintStream out)\n    {\n        int distinctErrorCount = 0;\n\n        if (ErrorLogReader.hasErrors(errorBuffer))\n        {\n            final SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSSZ\");\n            final ErrorConsumer errorConsumer =\n                (count, firstTimestamp, lastTimestamp, encodedException) ->\n                {\n                    final String fromDate = dateFormat.format(new Date(firstTimestamp));\n                    final String toDate = dateFormat.format(new Date(lastTimestamp));\n\n                    out.println();\n                    out.println(count + \" observations from \" + fromDate + \" to \" + toDate + \" for:\");\n                    out.println(encodedException);\n                };\n\n            distinctErrorCount = ErrorLogReader.read(errorBuffer, errorConsumer);\n            out.println();\n            out.println(distinctErrorCount + \" distinct errors observed.\");\n        }\n        else\n        {\n            out.println();\n            out.println(\"O distinct errors observed\");\n        }\n\n        return distinctErrorCount;\n    }\n\n    /**\n     * Save the existing errors from a {@link MarkFile} to a file in the same directory as the original {@link MarkFile}\n     * and optionally print location of such file to the supplied {@link PrintStream}.\n     *\n     * @param markFile        which contains the error buffer.\n     * @param errorBuffer     which wraps the error log.\n     * @param logger          to which the existing errors will be printed.\n     * @param errorFilePrefix to add to the generated error file.\n     */\n    public static void saveExistingErrors(\n        final File markFile,\n        final AtomicBuffer errorBuffer,\n        final PrintStream logger,\n        final String errorFilePrefix)\n    {\n        try\n        {\n            final ByteArrayOutputStream baos = new ByteArrayOutputStream();\n            final int observations = printErrorLog(errorBuffer, new PrintStream(baos, false, US_ASCII));\n            if (observations > 0)\n            {\n                final SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd-HH-mm-ss-SSSZ\");\n                final File errorLogFile = new File(\n                    markFile.getParentFile(), errorFilePrefix + '-' + dateFormat.format(new Date()) + \"-error.log\");\n\n                if (null != logger)\n                {\n                    logger.println(\"WARNING: existing errors saved to: \" + errorLogFile);\n                }\n\n                try (FileOutputStream out = new FileOutputStream(errorLogFile))\n                {\n                    baos.writeTo(out);\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * Get an {@link AtomicBuffer} which wraps the error log in the CnC file.\n     *\n     * @param cncByteBuffer which contains the error log.\n     * @return an {@link AtomicBuffer} which wraps the error log in the CnC file.\n     */\n    public static AtomicBuffer errorLogBuffer(final ByteBuffer cncByteBuffer)\n    {\n        final DirectBuffer cncMetaDataBuffer = CncFileDescriptor.createMetaDataBuffer(cncByteBuffer);\n        final int cncVersion = cncMetaDataBuffer.getInt(cncVersionOffset(0));\n\n        CncFileDescriptor.checkVersion(cncVersion);\n\n        return CncFileDescriptor.createErrorLogBuffer(cncByteBuffer, cncMetaDataBuffer);\n    }\n\n    /**\n     * Wrap a user ErrorHandler so that error will continue to write to the errorLog.\n     *\n     * @param userErrorHandler the user specified ErrorHandler, can be null.\n     * @param errorLog         the configured errorLog, either the default or user supplied.\n     * @return an error handler that will delegate to both the userErrorHandler and the errorLog.\n     */\n    public static ErrorHandler setupErrorHandler(final ErrorHandler userErrorHandler, final DistinctErrorLog errorLog)\n    {\n        return setupErrorHandler(userErrorHandler, errorLog, fallbackLogger());\n    }\n\n    /**\n     * Connect to the media driver and extract file page size from the C'n'C file.\n     *\n     * @param aeronDirectory where driver is running.\n     * @param clock          to use.\n     * @param timeoutMs      for awaiting connection.\n     * @return file page size from running media driver or {@link LogBufferDescriptor#PAGE_MIN_SIZE} if driver is old.\n     * @since 1.48.0\n     */\n    public static int driverFilePageSize(final File aeronDirectory, final EpochClock clock, final long timeoutMs)\n    {\n        final UnsafeBuffer metadata =\n            awaitCncFileCreation(new File(aeronDirectory, CNC_FILE), clock, clock.time() + timeoutMs);\n        try\n        {\n            return driverFilePageSize(metadata);\n        }\n        finally\n        {\n            BufferUtil.free(metadata);\n        }\n    }\n\n    /**\n     * Connect to the media driver and get the {@code nextCorrelationId}.\n     *\n     * @param aeronDirectory where driver is running.\n     * @param clock          to use.\n     * @param timeoutMs      for awaiting connection.\n     * @return next correlation id.\n     * @since 1.48.0\n     */\n    public static long nextCorrelationId(final File aeronDirectory, final EpochClock clock, final long timeoutMs)\n    {\n        final UnsafeBuffer metadata =\n            awaitCncFileCreation(new File(aeronDirectory, CNC_FILE), clock, clock.time() + timeoutMs);\n        try\n        {\n            final int correlationIdOffset =\n                metadata.getInt(TO_DRIVER_BUFFER_LENGTH_FIELD_OFFSET) - RingBufferDescriptor.TRAILER_LENGTH +\n                RingBufferDescriptor.CORRELATION_COUNTER_OFFSET;\n            final UnsafeBuffer toDriverBuffer = createToDriverBuffer(metadata.byteBuffer(), metadata);\n            return toDriverBuffer.getAndAddLong(correlationIdOffset, 1);\n        }\n        finally\n        {\n            BufferUtil.free(metadata);\n        }\n    }\n\n    static int driverFilePageSize(final DirectBuffer metadata)\n    {\n        final int pageSize = CncFileDescriptor.filePageSize(metadata);\n        return 0 != pageSize ? pageSize : LogBufferDescriptor.PAGE_MIN_SIZE;\n    }\n\n    @SuppressWarnings(\"try\")\n    static UnsafeBuffer awaitCncFileCreation(\n        final File cncFile, final EpochClock clock, final long deadlineMs)\n    {\n        while (true)\n        {\n            while (!cncFile.exists() || cncFile.length() < CncFileDescriptor.META_DATA_LENGTH)\n            {\n                if (clock.time() > deadlineMs)\n                {\n                    throw new DriverTimeoutException(\"CnC file not created: \" + cncFile.getAbsolutePath());\n                }\n\n                sleep(Aeron.Configuration.IDLE_SLEEP_DEFAULT_MS);\n            }\n\n            try (FileChannel fileChannel = FileChannel.open(cncFile.toPath(), READ, WRITE))\n            {\n                final long fileSize = fileChannel.size();\n                if (fileSize < CncFileDescriptor.META_DATA_LENGTH)\n                {\n                    if (clock.time() > deadlineMs)\n                    {\n                        throw new DriverTimeoutException(\n                            \"CnC file is created but not populated: \" + cncFile.getAbsolutePath());\n                    }\n\n                    fileChannel.close();\n                    sleep(Aeron.Configuration.IDLE_SLEEP_DEFAULT_MS);\n                    continue;\n                }\n\n                final UnsafeBuffer metaDataBuffer =\n                    CncFileDescriptor.createMetaDataBuffer(fileChannel.map(READ_WRITE, 0, fileSize));\n\n                int cncVersion;\n                while (0 == (cncVersion = metaDataBuffer.getIntVolatile(CncFileDescriptor.cncVersionOffset(0))))\n                {\n                    if (clock.time() > deadlineMs)\n                    {\n                        throw new DriverTimeoutException(\"CnC file is created but not initialised: \" +\n                            cncFile.getAbsolutePath());\n                    }\n\n                    sleep(Aeron.Configuration.AWAITING_IDLE_SLEEP_MS);\n                }\n\n                CncFileDescriptor.checkVersion(cncVersion);\n                if (SemanticVersion.minor(cncVersion) < SemanticVersion.minor(CncFileDescriptor.CNC_VERSION))\n                {\n                    throw new AeronException(\"driverVersion=\" + SemanticVersion.toString(cncVersion) +\n                        \" insufficient for clientVersion=\" +\n                        SemanticVersion.toString(CncFileDescriptor.CNC_VERSION));\n                }\n\n                return metaDataBuffer;\n            }\n            catch (final NoSuchFileException | AccessDeniedException ignore)\n            {\n            }\n            catch (final FileSystemException ex)\n            {\n                // JDK exception translation does not handle `ERROR_SHARING_VIOLATION (32)` and returns\n                // FileSystemException with the error \"The process cannot access the file because it is being\n                // used by another process.\". Our current thinking is that matching by text is too brittle due\n                // to error message being locale-sensitive on Windows. Therefore, we are going to retry on any\n                // FileSystemException when running on Windows.\n                if (SystemUtil.isWindows())\n                {\n                    continue;\n                }\n\n                throw new AeronException(cncFileErrorMessage(cncFile, ex), ex);\n            }\n            catch (final IOException ex)\n            {\n                throw new AeronException(cncFileErrorMessage(cncFile, ex), ex);\n            }\n        }\n    }\n\n    static ErrorHandler setupErrorHandler(\n        final ErrorHandler userErrorHandler, final DistinctErrorLog errorLog, final PrintStream fallbackErrorStream)\n    {\n        final LoggingErrorHandler loggingErrorHandler = new LoggingErrorHandler(errorLog, fallbackErrorStream);\n        if (null == userErrorHandler)\n        {\n            return loggingErrorHandler;\n        }\n        else\n        {\n            return new ErrorHandlerWrapper(loggingErrorHandler, userErrorHandler);\n        }\n    }\n\n    static void sleep(final long durationMs)\n    {\n        try\n        {\n            Thread.sleep(durationMs);\n        }\n        catch (final InterruptedException ex)\n        {\n            Thread.currentThread().interrupt();\n            throw new AeronException(\"unexpected interrupt\", ex);\n        }\n    }\n\n    private static String cncFileErrorMessage(final File file, final Exception ex)\n    {\n        return \"cannot open CnC file: \" + file.getAbsolutePath() + \" reason=\" + ex;\n    }\n\n    private static final class ErrorHandlerWrapper implements ErrorHandler, AutoCloseable\n    {\n        private final LoggingErrorHandler loggingErrorHandler;\n        private final ErrorHandler userErrorHandler;\n\n        private ErrorHandlerWrapper(final LoggingErrorHandler loggingErrorHandler, final ErrorHandler userErrorHandler)\n        {\n            this.loggingErrorHandler = loggingErrorHandler;\n            this.userErrorHandler = userErrorHandler;\n        }\n\n        public void close()\n        {\n            loggingErrorHandler.close();\n            if (userErrorHandler instanceof AutoCloseable)\n            {\n                CloseHelper.quietClose((AutoCloseable)userErrorHandler);\n            }\n        }\n\n        public void onError(final Throwable throwable)\n        {\n            loggingErrorHandler.onError(throwable);\n            userErrorHandler.onError(throwable);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ConcurrentPublication.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.ReadablePosition;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.DataHeaderFlyweight.RESERVED_VALUE_OFFSET;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.BitUtil.align;\n\n/**\n * Aeron publisher API for sending messages to subscribers of a given channel and streamId pair. {@link Publication}s\n * are created via the {@link Aeron#addPublication(String, int)} method, and messages are sent via one of the\n * {@link #offer(DirectBuffer)} methods, or a {@link #tryClaim(int, BufferClaim)} and {@link BufferClaim#commit()}\n * method combination.\n * <p>\n * The APIs for tryClaim and offer are non-blocking and thread safe.\n * <p>\n * <b>Note:</b> Instances are threadsafe and can be shared between publishing threads.\n *\n * @see Aeron#addPublication(String, int)\n * @see BufferClaim\n */\npublic final class ConcurrentPublication extends Publication\n{\n    ConcurrentPublication(\n        final ClientConductor clientConductor,\n        final String channel,\n        final int streamId,\n        final int sessionId,\n        final ReadablePosition positionLimit,\n        final int channelStatusId,\n        final LogBuffers logBuffers,\n        final long originalRegistrationId,\n        final long registrationId)\n    {\n        super(\n            clientConductor,\n            channel,\n            streamId,\n            sessionId,\n            positionLimit,\n            channelStatusId,\n            logBuffers,\n            originalRegistrationId,\n            registrationId);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long availableWindow()\n    {\n        if (isClosed)\n        {\n            return CLOSED;\n        }\n\n        return positionLimit.getVolatile() - position();\n    }\n\n    /**\n     * Non-blocking publish of a partial buffer containing a message.\n     *\n     * @param buffer                containing message.\n     * @param offset                offset in the buffer at which the encoded message begins.\n     * @param length                in bytes of the encoded message.\n     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public long offer(\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        long newPosition = CLOSED;\n        if (!isClosed)\n        {\n            final long limit = positionLimit.getVolatile();\n            final int termCount = activeTermCount(logMetaDataBuffer);\n            final int index = indexByTermCount(termCount);\n            final UnsafeBuffer termBuffer = termBuffers[index];\n            final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (index * SIZE_OF_LONG);\n            final long rawTail = logMetaDataBuffer.getLongVolatile(tailCounterOffset);\n            final int termOffset = termOffset(rawTail, termBuffer.capacity());\n            final int termId = termId(rawTail);\n\n            if (termCount != (termId - initialTermId))\n            {\n                return ADMIN_ACTION;\n            }\n\n            final long position = computePosition(termId, termOffset, positionBitsToShift, initialTermId);\n            if (position < limit)\n            {\n                if (length <= maxPayloadLength)\n                {\n                    checkPositiveLength(length);\n                    newPosition = appendUnfragmentedMessage(\n                        termBuffer, tailCounterOffset, buffer, offset, length, reservedValueSupplier);\n                }\n                else\n                {\n                    checkMaxMessageLength(length);\n                    newPosition = appendFragmentedMessage(\n                        termBuffer, tailCounterOffset, buffer, offset, length, reservedValueSupplier);\n                }\n            }\n            else\n            {\n                newPosition = backPressureStatus(position, length);\n            }\n        }\n\n        return newPosition;\n    }\n\n\n    /**\n     * Non-blocking publish of a message composed of two parts, e.g. a header and encapsulated payload.\n     *\n     * @param bufferOne             containing the first part of the message.\n     * @param offsetOne             at which the first part of the message begins.\n     * @param lengthOne             of the first part of the message.\n     * @param bufferTwo             containing the second part of the message.\n     * @param offsetTwo             at which the second part of the message begins.\n     * @param lengthTwo             of the second part of the message.\n     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public long offer(\n        final DirectBuffer bufferOne,\n        final int offsetOne,\n        final int lengthOne,\n        final DirectBuffer bufferTwo,\n        final int offsetTwo,\n        final int lengthTwo,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        long newPosition = CLOSED;\n        if (!isClosed)\n        {\n            final long limit = positionLimit.getVolatile();\n            final int termCount = activeTermCount(logMetaDataBuffer);\n            final int index = indexByTermCount(termCount);\n            final UnsafeBuffer termBuffer = termBuffers[index];\n            final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (index * SIZE_OF_LONG);\n            final long rawTail = logMetaDataBuffer.getLongVolatile(tailCounterOffset);\n            final int termOffset = termOffset(rawTail, termBuffer.capacity());\n            final int termId = termId(rawTail);\n\n            if (termCount != (termId - initialTermId))\n            {\n                return ADMIN_ACTION;\n            }\n\n            final long position = computePosition(termId, termOffset, positionBitsToShift, initialTermId);\n\n            final int length = validateAndComputeLength(lengthOne, lengthTwo);\n            if (position < limit)\n            {\n                if (length <= maxPayloadLength)\n                {\n                    newPosition = appendUnfragmentedMessage(\n                        termBuffer,\n                        tailCounterOffset,\n                        bufferOne,\n                        offsetOne,\n                        lengthOne,\n                        bufferTwo,\n                        offsetTwo,\n                        lengthTwo,\n                        reservedValueSupplier);\n                }\n                else\n                {\n                    checkMaxMessageLength(length);\n                    newPosition = appendFragmentedMessage(\n                        termBuffer,\n                        tailCounterOffset,\n                        bufferOne,\n                        offsetOne,\n                        lengthOne,\n                        bufferTwo,\n                        offsetTwo,\n                        lengthTwo,\n                        reservedValueSupplier);\n                }\n            }\n            else\n            {\n                newPosition = backPressureStatus(position, length);\n            }\n        }\n\n        return newPosition;\n    }\n\n    /**\n     * Non-blocking publish by gathering buffer vectors into a message.\n     *\n     * @param vectors               which make up the message.\n     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public long offer(final DirectBufferVector[] vectors, final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int length = DirectBufferVector.validateAndComputeLength(vectors);\n        long newPosition = CLOSED;\n\n        if (!isClosed)\n        {\n            final long limit = positionLimit.getVolatile();\n            final int termCount = activeTermCount(logMetaDataBuffer);\n            final int index = indexByTermCount(termCount);\n            final UnsafeBuffer termBuffer = termBuffers[index];\n            final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (index * SIZE_OF_LONG);\n            final long rawTail = logMetaDataBuffer.getLongVolatile(tailCounterOffset);\n            final int termOffset = termOffset(rawTail, termBuffer.capacity());\n            final int termId = termId(rawTail);\n\n            if (termCount != (termId - initialTermId))\n            {\n                return ADMIN_ACTION;\n            }\n\n            final long position = computePosition(termId, termOffset, positionBitsToShift, initialTermId);\n\n            if (position < limit)\n            {\n                if (length <= maxPayloadLength)\n                {\n                    newPosition = appendUnfragmentedMessage(\n                        termBuffer, tailCounterOffset, vectors, length, reservedValueSupplier);\n                }\n                else\n                {\n                    checkMaxMessageLength(length);\n                    newPosition = appendFragmentedMessage(\n                        termBuffer, tailCounterOffset, vectors, length, reservedValueSupplier);\n                }\n            }\n            else\n            {\n                newPosition = backPressureStatus(position, length);\n            }\n        }\n\n        return newPosition;\n    }\n\n    /**\n     * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n     * Once the message has been written then {@link BufferClaim#commit()} should be called thus making it available.\n     * <p>\n     * <b>Note:</b> This method can only be used for message lengths less than MTU length minus header.\n     * If the claim is held for more than the aeron.publication.unblock.timeout system property then the driver will\n     * assume the publication thread is dead and will unblock the claim thus allowing other threads to make progress or\n     * to reach end-of-stream (EOS).\n     * <pre>{@code\n     *     final BufferClaim bufferClaim = new BufferClaim(); // Can be stored and reused to avoid allocation\n     *\n     *     if (publication.tryClaim(messageLength, bufferClaim) > 0L)\n     *     {\n     *         try\n     *         {\n     *              final MutableDirectBuffer buffer = bufferClaim.buffer();\n     *              final int offset = bufferClaim.offset();\n     *\n     *              // Work with buffer directly or wrap with a flyweight\n     *         }\n     *         finally\n     *         {\n     *             bufferClaim.commit();\n     *         }\n     *     }\n     * }</pre>\n     *\n     * @param length      of the range to claim, in bytes.\n     * @param bufferClaim to be populated if the claim succeeds.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     * @throws IllegalArgumentException if the length is greater than {@link #maxPayloadLength()} within an MTU.\n     * @see BufferClaim#commit()\n     * @see BufferClaim#abort()\n     */\n    public long tryClaim(final int length, final BufferClaim bufferClaim)\n    {\n        checkPayloadLength(length);\n        long newPosition = CLOSED;\n\n        if (!isClosed)\n        {\n            final long limit = positionLimit.getVolatile();\n            final int termCount = activeTermCount(logMetaDataBuffer);\n            final int index = indexByTermCount(termCount);\n            final UnsafeBuffer termBuffer = termBuffers[index];\n            final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (index * SIZE_OF_LONG);\n            final long rawTail = logMetaDataBuffer.getLongVolatile(tailCounterOffset);\n            final int termOffset = termOffset(rawTail, termBuffer.capacity());\n            final int termId = termId(rawTail);\n\n            if (termCount != (termId - initialTermId))\n            {\n                return ADMIN_ACTION;\n            }\n\n            final long position = computePosition(termId, termOffset, positionBitsToShift, initialTermId);\n\n            if (position < limit)\n            {\n                newPosition = claim(termBuffer, tailCounterOffset, length, bufferClaim);\n            }\n            else\n            {\n                newPosition = backPressureStatus(position, length);\n            }\n        }\n\n        return newPosition;\n    }\n\n    private long appendUnfragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int frameLength = length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termLength = termBuffer.capacity();\n\n        final long rawTail = logMetaDataBuffer.getAndAddLong(tailCounterOffset, alignedLength);\n        final int termId = termId(rawTail);\n        final int termOffset = termOffset(rawTail, termLength);\n\n        final int resultingOffset = termOffset + alignedLength;\n        final long position = computePosition(termId, resultingOffset, positionBitsToShift, initialTermId);\n        if (resultingOffset > termLength)\n        {\n            return handleEndOfLog(termBuffer, termLength, termId, termOffset, position);\n        }\n        else\n        {\n            headerWriter.write(termBuffer, termOffset, frameLength, termId);\n            termBuffer.putBytes(termOffset + HEADER_LENGTH, buffer, offset, length);\n\n            if (null != reservedValueSupplier)\n            {\n                final long reservedValue = reservedValueSupplier.get(termBuffer, termOffset, frameLength);\n                termBuffer.putLong(termOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n            }\n\n            frameLengthOrdered(termBuffer, termOffset, frameLength);\n        }\n\n        return position;\n    }\n\n    private long appendFragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int framedLength = computeFragmentedFrameLength(length, maxPayloadLength);\n        final int termLength = termBuffer.capacity();\n\n        final long rawTail = logMetaDataBuffer.getAndAddLong(tailCounterOffset, framedLength);\n        final int termId = termId(rawTail);\n        final int termOffset = termOffset(rawTail, termLength);\n\n        final int resultingOffset = termOffset + framedLength;\n        final long position = computePosition(termId, resultingOffset, positionBitsToShift, initialTermId);\n        if (resultingOffset > termLength)\n        {\n            return handleEndOfLog(termBuffer, termLength, termId, termOffset, position);\n        }\n        else\n        {\n            int frameOffset = termOffset;\n            byte flags = BEGIN_FRAG_FLAG;\n            int remaining = length;\n\n            do\n            {\n                final int bytesToWrite = Math.min(remaining, maxPayloadLength);\n                final int frameLength = bytesToWrite + HEADER_LENGTH;\n                final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n\n                headerWriter.write(termBuffer, frameOffset, frameLength, termId);\n                termBuffer.putBytes(\n                    frameOffset + HEADER_LENGTH,\n                    buffer,\n                    offset + (length - remaining),\n                    bytesToWrite);\n\n                if (remaining <= maxPayloadLength)\n                {\n                    flags |= END_FRAG_FLAG;\n                }\n\n                frameFlags(termBuffer, frameOffset, flags);\n\n                if (null != reservedValueSupplier)\n                {\n                    final long reservedValue = reservedValueSupplier.get(termBuffer, frameOffset, frameLength);\n                    termBuffer.putLong(frameOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n                }\n\n                frameLengthOrdered(termBuffer, frameOffset, frameLength);\n\n                flags = 0;\n                frameOffset += alignedLength;\n                remaining -= bytesToWrite;\n            }\n            while (remaining > 0);\n        }\n\n        return position;\n    }\n\n    private long appendUnfragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBuffer bufferOne,\n        final int offsetOne,\n        final int lengthOne,\n        final DirectBuffer bufferTwo,\n        final int offsetTwo,\n        final int lengthTwo,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int frameLength = lengthOne + lengthTwo + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termLength = termBuffer.capacity();\n\n        final long rawTail = logMetaDataBuffer.getAndAddLong(tailCounterOffset, alignedLength);\n        final int termId = termId(rawTail);\n        final int termOffset = termOffset(rawTail, termLength);\n\n        final int resultingOffset = termOffset + alignedLength;\n        final long position = computePosition(termId, resultingOffset, positionBitsToShift, initialTermId);\n        if (resultingOffset > termLength)\n        {\n            return handleEndOfLog(termBuffer, termLength, termId, termOffset, position);\n        }\n        else\n        {\n            headerWriter.write(termBuffer, termOffset, frameLength, termId);\n            termBuffer.putBytes(termOffset + HEADER_LENGTH, bufferOne, offsetOne, lengthOne);\n            termBuffer.putBytes(termOffset + HEADER_LENGTH + lengthOne, bufferTwo, offsetTwo, lengthTwo);\n\n            if (null != reservedValueSupplier)\n            {\n                final long reservedValue = reservedValueSupplier.get(termBuffer, termOffset, frameLength);\n                termBuffer.putLong(termOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n            }\n\n            frameLengthOrdered(termBuffer, termOffset, frameLength);\n        }\n\n        return position;\n    }\n\n    private long appendFragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBuffer bufferOne,\n        final int offsetOne,\n        final int lengthOne,\n        final DirectBuffer bufferTwo,\n        final int offsetTwo,\n        final int lengthTwo,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int length = lengthOne + lengthTwo;\n        final int framedLength = computeFragmentedFrameLength(length, maxPayloadLength);\n        final int termLength = termBuffer.capacity();\n\n        final long rawTail = logMetaDataBuffer.getAndAddLong(tailCounterOffset, framedLength);\n        final int termId = termId(rawTail);\n        final int termOffset = termOffset(rawTail, termLength);\n\n        final int resultingOffset = termOffset + framedLength;\n        final long position = computePosition(termId, resultingOffset, positionBitsToShift, initialTermId);\n        if (resultingOffset > termLength)\n        {\n            return handleEndOfLog(termBuffer, termLength, termId, termOffset, position);\n        }\n        else\n        {\n            int frameOffset = termOffset;\n            byte flags = BEGIN_FRAG_FLAG;\n            int remaining = length;\n            int positionOne = 0;\n            int positionTwo = 0;\n\n            do\n            {\n                final int bytesToWrite = Math.min(remaining, maxPayloadLength);\n                final int frameLength = bytesToWrite + HEADER_LENGTH;\n                final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n\n                headerWriter.write(termBuffer, frameOffset, frameLength, termId);\n\n                int bytesWritten = 0;\n                int payloadOffset = frameOffset + HEADER_LENGTH;\n                do\n                {\n                    final int remainingOne = lengthOne - positionOne;\n                    if (remainingOne > 0)\n                    {\n                        final int numBytes = Math.min(bytesToWrite - bytesWritten, remainingOne);\n                        termBuffer.putBytes(payloadOffset, bufferOne, offsetOne + positionOne, numBytes);\n\n                        bytesWritten += numBytes;\n                        payloadOffset += numBytes;\n                        positionOne += numBytes;\n                    }\n                    else\n                    {\n                        final int numBytes = Math.min(bytesToWrite - bytesWritten, lengthTwo - positionTwo);\n                        termBuffer.putBytes(payloadOffset, bufferTwo, offsetTwo + positionTwo, numBytes);\n\n                        bytesWritten += numBytes;\n                        payloadOffset += numBytes;\n                        positionTwo += numBytes;\n                    }\n                }\n                while (bytesWritten < bytesToWrite);\n\n                if (remaining <= maxPayloadLength)\n                {\n                    flags |= END_FRAG_FLAG;\n                }\n\n                frameFlags(termBuffer, frameOffset, flags);\n\n                if (null != reservedValueSupplier)\n                {\n                    final long reservedValue = reservedValueSupplier.get(termBuffer, frameOffset, frameLength);\n                    termBuffer.putLong(frameOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n                }\n\n                frameLengthOrdered(termBuffer, frameOffset, frameLength);\n\n                flags = 0;\n                frameOffset += alignedLength;\n                remaining -= bytesToWrite;\n            }\n            while (remaining > 0);\n        }\n\n        return position;\n    }\n\n    private long appendUnfragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBufferVector[] vectors,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int frameLength = length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termLength = termBuffer.capacity();\n\n        final long rawTail = logMetaDataBuffer.getAndAddLong(tailCounterOffset, alignedLength);\n        final int termId = termId(rawTail);\n        final int termOffset = termOffset(rawTail, termLength);\n\n        final int resultingOffset = termOffset + alignedLength;\n        final long position = computePosition(termId, resultingOffset, positionBitsToShift, initialTermId);\n        if (resultingOffset > termLength)\n        {\n            return handleEndOfLog(termBuffer, termLength, termId, termOffset, position);\n        }\n        else\n        {\n            headerWriter.write(termBuffer, termOffset, frameLength, termId);\n\n            int offset = termOffset + HEADER_LENGTH;\n            for (final DirectBufferVector vector : vectors)\n            {\n                termBuffer.putBytes(offset, vector.buffer(), vector.offset(), vector.length());\n                offset += vector.length();\n            }\n\n            if (null != reservedValueSupplier)\n            {\n                final long reservedValue = reservedValueSupplier.get(termBuffer, termOffset, frameLength);\n                termBuffer.putLong(termOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n            }\n\n            frameLengthOrdered(termBuffer, termOffset, frameLength);\n        }\n\n        return position;\n    }\n\n    private long appendFragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBufferVector[] vectors,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int framedLength = computeFragmentedFrameLength(length, maxPayloadLength);\n        final int termLength = termBuffer.capacity();\n\n        final long rawTail = logMetaDataBuffer.getAndAddLong(tailCounterOffset, framedLength);\n        final int termId = termId(rawTail);\n        final int termOffset = termOffset(rawTail, termLength);\n\n        final int resultingOffset = termOffset + framedLength;\n        final long position = computePosition(termId, resultingOffset, positionBitsToShift, initialTermId);\n        if (resultingOffset > termLength)\n        {\n            return handleEndOfLog(termBuffer, termLength, termId, termOffset, position);\n        }\n        else\n        {\n            int frameOffset = termOffset;\n            byte flags = BEGIN_FRAG_FLAG;\n            int remaining = length;\n            int vectorIndex = 0;\n            int vectorOffset = 0;\n\n            do\n            {\n                final int bytesToWrite = Math.min(remaining, maxPayloadLength);\n                final int frameLength = bytesToWrite + HEADER_LENGTH;\n                final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n\n                headerWriter.write(termBuffer, frameOffset, frameLength, termId);\n\n                int bytesWritten = 0;\n                int payloadOffset = frameOffset + HEADER_LENGTH;\n                do\n                {\n                    final DirectBufferVector vector = vectors[vectorIndex];\n                    final int vectorRemaining = vector.length() - vectorOffset;\n                    final int numBytes = Math.min(bytesToWrite - bytesWritten, vectorRemaining);\n\n                    termBuffer.putBytes(payloadOffset, vector.buffer(), vector.offset() + vectorOffset, numBytes);\n\n                    bytesWritten += numBytes;\n                    payloadOffset += numBytes;\n                    vectorOffset += numBytes;\n\n                    if (vectorRemaining <= numBytes)\n                    {\n                        vectorIndex++;\n                        vectorOffset = 0;\n                    }\n                }\n                while (bytesWritten < bytesToWrite);\n\n                if (remaining <= maxPayloadLength)\n                {\n                    flags |= END_FRAG_FLAG;\n                }\n\n                frameFlags(termBuffer, frameOffset, flags);\n\n                if (null != reservedValueSupplier)\n                {\n                    final long reservedValue = reservedValueSupplier.get(termBuffer, frameOffset, frameLength);\n                    termBuffer.putLong(frameOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n                }\n\n                frameLengthOrdered(termBuffer, frameOffset, frameLength);\n\n                flags = 0;\n                frameOffset += alignedLength;\n                remaining -= bytesToWrite;\n            }\n            while (remaining > 0);\n        }\n\n        return position;\n    }\n\n    private long claim(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final int length,\n        final BufferClaim bufferClaim)\n    {\n        final int frameLength = length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termLength = termBuffer.capacity();\n\n        final long rawTail = logMetaDataBuffer.getAndAddLong(tailCounterOffset, alignedLength);\n        final int termId = termId(rawTail);\n        final int termOffset = termOffset(rawTail, termLength);\n\n        final int resultingOffset = termOffset + alignedLength;\n        final long position = computePosition(termId, resultingOffset, positionBitsToShift, initialTermId);\n        if (resultingOffset > termLength)\n        {\n            return handleEndOfLog(termBuffer, termLength, termId, termOffset, position);\n        }\n        else\n        {\n            headerWriter.write(termBuffer, termOffset, frameLength, termId);\n            bufferClaim.wrap(termBuffer, termOffset, frameLength);\n        }\n\n        return position;\n    }\n\n    private long handleEndOfLog(\n        final UnsafeBuffer termBuffer,\n        final int termLength,\n        final int termId,\n        final int termOffset,\n        final long position)\n    {\n        if (termOffset < termLength)\n        {\n            final int paddingLength = termLength - termOffset;\n            headerWriter.write(termBuffer, termOffset, paddingLength, termId);\n            frameType(termBuffer, termOffset, PADDING_FRAME_TYPE);\n            frameLengthOrdered(termBuffer, termOffset, paddingLength);\n        }\n\n        if (position >= maxPossiblePosition)\n        {\n            return MAX_POSITION_EXCEEDED;\n        }\n\n        rotateLog(logMetaDataBuffer, termId - initialTermId, termId);\n\n        return ADMIN_ACTION;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ControlledFragmentAssembler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.Int2ObjectHashMap;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\n\n/**\n * A {@link ControlledFragmentHandler} that sits in a chain-of-responsibility pattern that reassembles fragmented\n * messages so that the next handler in the chain only sees whole messages.\n * <p>\n * Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary\n * buffer for reassembly before delegation.\n * <p>\n * The {@link Header} passed to the delegate on assembling a message will be that of the last fragment.\n * <p>\n * Session based buffers will be allocated and grown as necessary based on the length of messages to be assembled.\n * When sessions go inactive see {@link UnavailableImageHandler}, it is possible to free the buffer by calling\n * {@link #freeSessionBuffer(int)}.\n *\n * @see Subscription#controlledPoll(ControlledFragmentHandler, int)\n * @see Image#controlledPoll(ControlledFragmentHandler, int)\n * @see Image#controlledPeek(long, ControlledFragmentHandler, long)\n */\npublic class ControlledFragmentAssembler implements ControlledFragmentHandler\n{\n    private final boolean isDirectByteBuffer;\n    private final int initialBufferLength;\n    private final ControlledFragmentHandler delegate;\n    private final Int2ObjectHashMap<BufferBuilder> builderBySessionIdMap = new Int2ObjectHashMap<>();\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on whole messages.\n     *\n     * @param delegate onto which whole messages are forwarded.\n     */\n    public ControlledFragmentAssembler(final ControlledFragmentHandler delegate)\n    {\n        this(delegate, 0, false);\n    }\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for each session.\n     */\n    public ControlledFragmentAssembler(final ControlledFragmentHandler delegate, final int initialBufferLength)\n    {\n        this(delegate, initialBufferLength, false);\n    }\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for each session.\n     * @param isDirectByteBuffer  is the underlying buffer to be a direct {@link java.nio.ByteBuffer}?\n     */\n    public ControlledFragmentAssembler(\n        final ControlledFragmentHandler delegate, final int initialBufferLength, final boolean isDirectByteBuffer)\n    {\n        this.initialBufferLength = initialBufferLength;\n        this.delegate = delegate;\n        this.isDirectByteBuffer = isDirectByteBuffer;\n    }\n\n    /**\n     * Get the delegate unto which assembled messages are delegated.\n     *\n     * @return the delegate unto which assembled messages are delegated.\n     */\n    public ControlledFragmentHandler delegate()\n    {\n        return delegate;\n    }\n\n    /**\n     * Is the underlying buffer used to assemble fragments a direct {@link java.nio.ByteBuffer}?\n     *\n     * @return true if the underlying buffer used to assemble fragments is a direct {@link java.nio.ByteBuffer}\n     */\n    public boolean isDirectByteBuffer()\n    {\n        return isDirectByteBuffer;\n    }\n\n    /**\n     * The implementation of {@link ControlledFragmentHandler} that reassembles and forwards whole messages.\n     *\n     * @param buffer containing the data.\n     * @param offset at which the data begins.\n     * @param length of the data in bytes.\n     * @param header representing the metadata for the data.\n     * @return {@link io.aeron.logbuffer.ControlledFragmentHandler.Action} to be taken after processing fragment.\n     */\n    public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final byte flags = header.flags();\n        Action action = Action.CONTINUE;\n\n        if ((flags & UNFRAGMENTED) == UNFRAGMENTED)\n        {\n            action = delegate.onFragment(buffer, offset, length, header);\n        }\n        else if ((flags & BEGIN_FRAG_FLAG) == BEGIN_FRAG_FLAG)\n        {\n            final BufferBuilder builder = getBufferBuilder(header.sessionId());\n            builder.reset()\n                .captureHeader(header)\n                .append(buffer, offset, length)\n                .nextTermOffset(header.nextTermOffset());\n        }\n        else\n        {\n            final BufferBuilder builder = builderBySessionIdMap.get(header.sessionId());\n            if (null != builder)\n            {\n                if (header.termOffset() == builder.nextTermOffset())\n                {\n                    final int limit = builder.limit();\n\n                    builder.append(buffer, offset, length);\n\n                    if ((flags & END_FRAG_FLAG) == END_FRAG_FLAG)\n                    {\n                        action = delegate.onFragment(\n                            builder.buffer(), 0, builder.limit(), builder.completeHeader(header));\n\n                        if (Action.ABORT == action)\n                        {\n                            builder.limit(limit);\n                        }\n                        else\n                        {\n                            builder.reset();\n                        }\n                    }\n                    else\n                    {\n                        builder.nextTermOffset(header.nextTermOffset());\n                    }\n                }\n                else\n                {\n                    builder.reset();\n                }\n            }\n        }\n\n        return action;\n    }\n\n    /**\n     * Free an existing session buffer to reduce memory pressure when an image goes inactive or no more\n     * large messages are expected.\n     *\n     * @param sessionId to have its buffer freed\n     * @return true if a buffer has been freed otherwise false.\n     */\n    public boolean freeSessionBuffer(final int sessionId)\n    {\n        return null != builderBySessionIdMap.remove(sessionId);\n    }\n\n    /**\n     * Clear down the cache of buffers by session for reassembling messages.\n     */\n    public void clear()\n    {\n        builderBySessionIdMap.clear();\n    }\n\n    private BufferBuilder getBufferBuilder(final int sessionId)\n    {\n        BufferBuilder bufferBuilder = builderBySessionIdMap.get(sessionId);\n\n        if (null == bufferBuilder)\n        {\n            bufferBuilder = new BufferBuilder(initialBufferLength, isDirectByteBuffer);\n            builderBySessionIdMap.put(sessionId, bufferBuilder);\n        }\n\n        return bufferBuilder;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/Counter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\n\n/**\n * Counter stored in a file managed by the media driver which can be observed with AeronStat.\n */\npublic final class Counter extends AtomicCounter\n{\n    private static final VarHandle IS_CLOSED_VH;\n    static\n    {\n        try\n        {\n            IS_CLOSED_VH = MethodHandles.lookup().findVarHandle(Counter.class, \"isClosed\", boolean.class);\n        }\n        catch (final ReflectiveOperationException ex)\n        {\n            throw new ExceptionInInitializerError(ex);\n        }\n    }\n\n    private volatile boolean isClosed;\n    private final long registrationId;\n    private final ClientConductor clientConductor;\n\n    Counter(\n        final long registrationId,\n        final ClientConductor clientConductor,\n        final AtomicBuffer buffer,\n        final int counterId)\n    {\n        super(buffer, counterId);\n\n        this.registrationId = registrationId;\n        this.clientConductor = clientConductor;\n    }\n\n    /**\n     * Construct a read-write view of an existing counter.\n     *\n     * @param countersReader for getting access to the buffers.\n     * @param registrationId assigned by the driver for the counter or {@link Aeron#NULL_VALUE} if not known.\n     * @param counterId      for the counter to be viewed.\n     * @throws AeronException if the id has for the counter has not been allocated.\n     */\n    public Counter(final CountersReader countersReader, final long registrationId, final int counterId)\n    {\n        super(countersReader.valuesBuffer(), counterId);\n\n        if (countersReader.getCounterState(counterId) != CountersReader.RECORD_ALLOCATED)\n        {\n            throw new AeronException(\"Counter id is not allocated: \" + counterId);\n        }\n\n        this.registrationId = registrationId;\n        this.clientConductor = null;\n    }\n\n    /**\n     * Return the registration id used to register this counter with the media driver.\n     *\n     * @return the registration id used to register this counter with the media driver.\n     */\n    public long registrationId()\n    {\n        return registrationId;\n    }\n\n    /**\n     * Close the counter, releasing the resource managed by the media driver if this was the creator of the Counter.\n     * <p>\n     * This method is idempotent and thread safe.\n     */\n    public void close()\n    {\n        if (IS_CLOSED_VH.compareAndSet(this, false, true))\n        {\n            super.close();\n            if (null != clientConductor)\n            {\n                clientConductor.releaseCounter(this);\n            }\n        }\n    }\n\n    /**\n     * Has this object been closed and should no longer be used?\n     *\n     * @return true if it has been closed otherwise false.\n     */\n    public boolean isClosed()\n    {\n        return isClosed;\n    }\n\n    void internalClose()\n    {\n        super.close();\n        isClosed = true;\n    }\n\n    ClientConductor clientConductor()\n    {\n        return clientConductor;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/CounterProvider.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Functional interface that abstracts creation of the counters.\n */\n@FunctionalInterface\npublic interface CounterProvider\n{\n    /**\n     * Allocate a counter record and wrap it with a new {@link AtomicCounter} for use.\n     * <p>\n     * If the keyBuffer is null then a copy of the key is not attempted.\n     *\n     * @param typeId      for the counter.\n     * @param keyBuffer   containing the optional key for the counter.\n     * @param keyOffset   within the keyBuffer at which the key begins.\n     * @param keyLength   of the key in the keyBuffer.\n     * @param labelBuffer containing the mandatory label for the counter.\n     * @param labelOffset within the labelBuffer at which the label begins.\n     * @param labelLength of the label in the labelBuffer.\n     * @return the counter object.\n     */\n    AtomicCounter newCounter(\n        int typeId,\n        DirectBuffer keyBuffer,\n        int keyOffset,\n        int keyLength,\n        DirectBuffer labelBuffer,\n        int labelOffset,\n        int labelLength);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/DirectBufferVector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.agrona.DirectBuffer;\n\nimport java.util.Arrays;\n\n/**\n * Vector into a {@link DirectBuffer} to be used for gathering IO as and offset and length.\n */\npublic final class DirectBufferVector\n{\n    private DirectBuffer buffer;\n    private int offset;\n    private int length;\n\n    /**\n     * Default constructor so the fluent API can be used.\n     */\n    public DirectBufferVector()\n    {\n    }\n\n    /**\n     * Construct a new vector as a subset of a buffer.\n     *\n     * @param buffer which is the super set.\n     * @param offset at which the vector begins.\n     * @param length of the vector.\n     */\n    public DirectBufferVector(final DirectBuffer buffer, final int offset, final int length)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n        this.length = length;\n    }\n\n    /**\n     * Reset the values.\n     *\n     * @param buffer which is the super set.\n     * @param offset at which the vector begins.\n     * @param length of the vector.\n     * @return this for a fluent API.\n     */\n    public DirectBufferVector reset(final DirectBuffer buffer, final int offset, final int length)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n        this.length = length;\n\n        return this;\n    }\n\n    /**\n     * The buffer which the vector applies to.\n     *\n     * @return buffer which the vector applies to.\n     */\n    public DirectBuffer buffer()\n    {\n        return buffer;\n    }\n\n    /**\n     * The buffer which the vector applies to.\n     *\n     * @param buffer which the vector applies to.\n     * @return this for a fluent API.\n     */\n    public DirectBufferVector buffer(final DirectBuffer buffer)\n    {\n        this.buffer = buffer;\n        return this;\n    }\n\n    /**\n     * Offset in the buffer at which the vector starts.\n     *\n     * @return offset in the buffer at which the vector starts.\n     */\n    public int offset()\n    {\n        return offset;\n    }\n\n    /**\n     * Offset in the buffer at which the vector starts.\n     *\n     * @param offset in the buffer at which the vector starts.\n     * @return this for a fluent API.\n     */\n    public DirectBufferVector offset(final int offset)\n    {\n        this.offset = offset;\n        return this;\n    }\n\n    /**\n     * Length of the vector in the buffer starting at the offset.\n     *\n     * @return length of the vector in the buffer starting at the offset.\n     */\n    public int length()\n    {\n        return length;\n    }\n\n    /**\n     * Length of the vector in the buffer starting at the offset.\n     *\n     * @param length of the vector in the buffer starting at the offset.\n     * @return this for a fluent API.\n     */\n    public DirectBufferVector length(final int length)\n    {\n        this.length = length;\n        return this;\n    }\n\n    /**\n     * Ensure the vector is valid for the buffer.\n     *\n     * @throws NullPointerException if the buffer is null.\n     * @throws IllegalArgumentException if the offset is out of range for the buffer.\n     * @throws IllegalArgumentException if the length is out of range for the buffer.\n     * @return this for a fluent API.\n     */\n    public DirectBufferVector validate()\n    {\n        final int capacity = buffer.capacity();\n        if (offset < 0 || offset >= capacity)\n        {\n            throw new IllegalArgumentException(\"offset=\" + offset + \" capacity=\" + capacity);\n        }\n\n        if (length < 0 || length > (capacity - offset))\n        {\n            throw new IllegalArgumentException(\"offset=\" + offset + \" capacity=\" + capacity + \" length=\" + length);\n        }\n\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"DirectBufferVector{\" +\n            \"buffer=\" + buffer +\n            \", offset=\" + offset +\n            \", length=\" + length +\n            '}';\n    }\n\n    /**\n     * Validate an array of vectors to make up a message and compute the total length.\n     *\n     * @param vectors to be validated summed.\n     * @return the sum of the vector lengths.\n     */\n    public static int validateAndComputeLength(final DirectBufferVector[] vectors)\n    {\n        int messageLength = 0;\n        for (final DirectBufferVector vector : vectors)\n        {\n            vector.validate();\n            messageLength += vector.length;\n\n            if (messageLength < 0)\n            {\n                throw new IllegalStateException(\"length overflow: \" + Arrays.toString(vectors));\n            }\n        }\n\n        return messageLength;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/DriverEventsAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.command.*;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.LongHashSet;\nimport org.agrona.concurrent.MessageHandler;\nimport org.agrona.concurrent.broadcast.CopyBroadcastReceiver;\n\nimport static io.aeron.ErrorCode.CHANNEL_ENDPOINT_ERROR;\nimport static io.aeron.command.ControlProtocolEvents.*;\n\n/**\n * Analogue of {@link DriverProxy} on the client side for dispatching driver events to the client conductor.\n */\nclass DriverEventsAdapter implements MessageHandler\n{\n    private final ErrorResponseFlyweight errorResponse = new ErrorResponseFlyweight();\n    private final PublicationErrorFrameFlyweight publicationErrorFrame = new PublicationErrorFrameFlyweight();\n    private final PublicationBuffersReadyFlyweight publicationReady = new PublicationBuffersReadyFlyweight();\n    private final SubscriptionReadyFlyweight subscriptionReady = new SubscriptionReadyFlyweight();\n    private final ImageBuffersReadyFlyweight imageReady = new ImageBuffersReadyFlyweight();\n    private final OperationSucceededFlyweight operationSucceeded = new OperationSucceededFlyweight();\n    private final ImageMessageFlyweight imageMessage = new ImageMessageFlyweight();\n    private final CounterUpdateFlyweight counterUpdate = new CounterUpdateFlyweight();\n    private final ClientTimeoutFlyweight clientTimeout = new ClientTimeoutFlyweight();\n    private final StaticCounterFlyweight staticCounter = new StaticCounterFlyweight();\n    private final NextAvailableSessionIdFlyweight nextSessionId = new NextAvailableSessionIdFlyweight();\n    private final CopyBroadcastReceiver receiver;\n    private final ClientConductor conductor;\n    private final LongHashSet asyncCommandIdSet;\n    private final long clientId;\n\n    private long activeCorrelationId;\n    private long receivedCorrelationId;\n    private boolean isInvalid;\n\n    DriverEventsAdapter(\n        final long clientId,\n        final CopyBroadcastReceiver receiver,\n        final ClientConductor conductor,\n        final LongHashSet asyncCommandIdSet)\n    {\n        this.clientId = clientId;\n        this.receiver = receiver;\n        this.conductor = conductor;\n        this.asyncCommandIdSet = asyncCommandIdSet;\n    }\n\n    int receive(final long activeCorrelationId)\n    {\n        this.activeCorrelationId = activeCorrelationId;\n        this.receivedCorrelationId = Aeron.NULL_VALUE;\n\n        try\n        {\n            return receiver.receive(this);\n        }\n        catch (final IllegalStateException ex)\n        {\n            isInvalid = true;\n            throw ex;\n        }\n    }\n\n    long receivedCorrelationId()\n    {\n        return receivedCorrelationId;\n    }\n\n    boolean isInvalid()\n    {\n        return isInvalid;\n    }\n\n    long clientId()\n    {\n        return clientId;\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    public void onMessage(final int msgTypeId, final MutableDirectBuffer buffer, final int index, final int length)\n    {\n        switch (msgTypeId)\n        {\n            case ON_ERROR:\n            {\n                errorResponse.wrap(buffer, index);\n\n                final long correlationId = errorResponse.offendingCommandCorrelationId();\n                final int errorCodeValue = errorResponse.errorCodeValue();\n                final ErrorCode errorCode = ErrorCode.get(errorCodeValue);\n                boolean notProcessed = true;\n\n                if (CHANNEL_ENDPOINT_ERROR == errorCode)\n                {\n                    // This code is left for backwards compatibility with older media driver versions\n                    notProcessed = false;\n                    conductor.onChannelEndpointError(correlationId, errorResponse.errorMessage());\n                }\n                else if (correlationId == activeCorrelationId)\n                {\n                    notProcessed = false;\n                    receivedCorrelationId = correlationId;\n                    conductor.onError(correlationId, errorCodeValue, errorCode, errorResponse.errorMessage());\n                }\n\n                if (asyncCommandIdSet.remove(correlationId) && notProcessed)\n                {\n                    conductor.onAsyncError(correlationId, errorCodeValue, errorCode, errorResponse.errorMessage());\n                }\n                break;\n            }\n\n            case ON_AVAILABLE_IMAGE:\n            {\n                imageReady.wrap(buffer, index);\n\n                conductor.onAvailableImage(\n                    imageReady.correlationId(),\n                    imageReady.sessionId(),\n                    imageReady.subscriptionRegistrationId(),\n                    imageReady.subscriberPositionId(),\n                    imageReady.logFileName(),\n                    imageReady.sourceIdentity());\n                break;\n            }\n\n            case ON_PUBLICATION_READY:\n            {\n                publicationReady.wrap(buffer, index);\n\n                final long correlationId = publicationReady.correlationId();\n                if (correlationId == activeCorrelationId || asyncCommandIdSet.remove(correlationId))\n                {\n                    receivedCorrelationId = correlationId;\n                    conductor.onNewPublication(\n                        correlationId,\n                        publicationReady.registrationId(),\n                        publicationReady.streamId(),\n                        publicationReady.sessionId(),\n                        publicationReady.publicationLimitCounterId(),\n                        publicationReady.channelStatusCounterId(),\n                        publicationReady.logFileName());\n                }\n                break;\n            }\n\n            case ON_SUBSCRIPTION_READY:\n            {\n                subscriptionReady.wrap(buffer, index);\n\n                final long correlationId = subscriptionReady.correlationId();\n                if (correlationId == activeCorrelationId || asyncCommandIdSet.remove(correlationId))\n                {\n                    receivedCorrelationId = correlationId;\n                    conductor.onNewSubscription(correlationId, subscriptionReady.channelStatusCounterId());\n                }\n                break;\n            }\n\n            case ON_OPERATION_SUCCESS:\n            {\n                operationSucceeded.wrap(buffer, index);\n\n                final long correlationId = operationSucceeded.correlationId();\n                asyncCommandIdSet.remove(correlationId);\n                if (correlationId == activeCorrelationId)\n                {\n                    receivedCorrelationId = correlationId;\n                }\n                break;\n            }\n\n            case ON_UNAVAILABLE_IMAGE:\n            {\n                imageMessage.wrap(buffer, index);\n\n                conductor.onUnavailableImage(\n                    imageMessage.correlationId(),\n                    imageMessage.subscriptionRegistrationId());\n                break;\n            }\n\n            case ON_EXCLUSIVE_PUBLICATION_READY:\n            {\n                publicationReady.wrap(buffer, index);\n\n                final long correlationId = publicationReady.correlationId();\n                if (correlationId == activeCorrelationId || asyncCommandIdSet.remove(correlationId))\n                {\n                    receivedCorrelationId = correlationId;\n                    conductor.onNewExclusivePublication(\n                        correlationId,\n                        publicationReady.registrationId(),\n                        publicationReady.streamId(),\n                        publicationReady.sessionId(),\n                        publicationReady.publicationLimitCounterId(),\n                        publicationReady.channelStatusCounterId(),\n                        publicationReady.logFileName());\n                }\n                break;\n            }\n\n            case ON_COUNTER_READY:\n            {\n                counterUpdate.wrap(buffer, index);\n\n                final int counterId = counterUpdate.counterId();\n                final long correlationId = counterUpdate.correlationId();\n                if (correlationId == activeCorrelationId || asyncCommandIdSet.remove(correlationId))\n                {\n                    receivedCorrelationId = correlationId;\n                    conductor.onNewCounter(correlationId, counterId);\n                }\n                else\n                {\n                    conductor.onAvailableCounter(correlationId, counterId);\n                }\n                break;\n            }\n\n            case ON_UNAVAILABLE_COUNTER:\n            {\n                counterUpdate.wrap(buffer, index);\n\n                conductor.onUnavailableCounter(counterUpdate.correlationId(), counterUpdate.counterId());\n                break;\n            }\n\n            case ON_CLIENT_TIMEOUT:\n            {\n                clientTimeout.wrap(buffer, index);\n\n                if (clientTimeout.clientId() == clientId)\n                {\n                    conductor.onClientTimeout();\n                }\n                break;\n            }\n\n            case ON_STATIC_COUNTER:\n            {\n                staticCounter.wrap(buffer, index);\n\n                final long correlationId = staticCounter.correlationId();\n                if (correlationId == activeCorrelationId || asyncCommandIdSet.remove(correlationId))\n                {\n                    final int counterId = staticCounter.counterId();\n                    receivedCorrelationId = correlationId;\n                    conductor.onStaticCounter(correlationId, counterId);\n                }\n                break;\n            }\n\n            case ON_PUBLICATION_ERROR:\n            {\n                publicationErrorFrame.wrap(buffer, index);\n\n                conductor.onPublicationError(publicationErrorFrame);\n                break;\n            }\n\n            case ON_NEXT_AVAILABLE_SESSION_ID:\n            {\n                nextSessionId.wrap(buffer, index);\n\n                final long correlationId = nextSessionId.correlationId();\n                if (correlationId == activeCorrelationId)\n                {\n                    receivedCorrelationId = correlationId;\n                    conductor.onNextAvailableSessionId(nextSessionId.nextSessionId());\n                }\n                break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/DriverProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.command.*;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\n\nimport static io.aeron.command.ControlProtocolEvents.*;\n\n/**\n * Separates the concern of communicating with the client conductor away from the rest of the client.\n * <p>\n * For writing commands into the client conductor buffer.\n * <p>\n * <b>Note:</b> this class is not thread safe and is expecting to be called within {@link Aeron.Context#clientLock()}.\n */\npublic final class DriverProxy\n{\n    private final long clientId;\n    private final PublicationMessageFlyweight publicationMessageFlyweight = new PublicationMessageFlyweight();\n    private final SubscriptionMessageFlyweight subscriptionMessageFlyweight = new SubscriptionMessageFlyweight();\n    private final RemoveCounterFlyweight removeCounterFlyweight = new RemoveCounterFlyweight();\n    private final RemovePublicationFlyweight removePublicationFlyweight = new RemovePublicationFlyweight();\n    private final RemoveSubscriptionFlyweight removeSubscriptionFlyweight = new RemoveSubscriptionFlyweight();\n    private final DestinationMessageFlyweight destinationMessageFlyweight = new DestinationMessageFlyweight();\n    private final DestinationByIdMessageFlyweight destinationByIdMessageFlyweight =\n        new DestinationByIdMessageFlyweight();\n    private final CounterMessageFlyweight counterMessageFlyweight = new CounterMessageFlyweight();\n    private final StaticCounterMessageFlyweight staticCounterMessageFlyweight = new StaticCounterMessageFlyweight();\n    private final RejectImageFlyweight rejectImageFlyweight = new RejectImageFlyweight();\n    private final GetNextAvailableSessionIdMessageFlyweight getNextAvailableSessionIdMessageFlyweight =\n        new GetNextAvailableSessionIdMessageFlyweight();\n    private final RingBuffer toDriverCommandBuffer;\n\n    /**\n     * Create a proxy to a media driver which sends commands via a {@link RingBuffer}.\n     *\n     * @param toDriverCommandBuffer to send commands via.\n     * @param clientId              to represent the client.\n     */\n    public DriverProxy(final RingBuffer toDriverCommandBuffer, final long clientId)\n    {\n        this.toDriverCommandBuffer = toDriverCommandBuffer;\n        this.clientId = clientId;\n    }\n\n    /**\n     * Time of the last heartbeat to indicate the driver is alive.\n     *\n     * @return time of the last heartbeat to indicate the driver is alive.\n     */\n    public long timeOfLastDriverKeepaliveMs()\n    {\n        return toDriverCommandBuffer.consumerHeartbeatTime();\n    }\n\n    /**\n     * Instruct the driver to add a concurrent publication.\n     *\n     * @param channel  uri in string format.\n     * @param streamId within the channel.\n     * @return the correlation id for the command.\n     */\n    public long addPublication(final String channel, final int streamId)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = PublicationMessageFlyweight.computeLength(channel.length());\n        final int index = toDriverCommandBuffer.tryClaim(ADD_PUBLICATION, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write add publication command\");\n        }\n\n        publicationMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .streamId(streamId)\n            .channel(channel)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Instruct the driver to add a non-concurrent, i.e. exclusive, publication.\n     *\n     * @param channel  uri in string format.\n     * @param streamId within the channel.\n     * @return the correlation id for the command.\n     */\n    public long addExclusivePublication(final String channel, final int streamId)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = PublicationMessageFlyweight.computeLength(channel.length());\n        final int index = toDriverCommandBuffer.tryClaim(ADD_EXCLUSIVE_PUBLICATION, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write add exclusive publication command\");\n        }\n\n        publicationMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .streamId(streamId)\n            .channel(channel)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Instruct the driver to remove a publication by its registration id.\n     *\n     * @param registrationId for the publication to be removed.\n     * @param revoke whether the publication is being revoked.\n     * @return the correlation id for the command.\n     */\n    public long removePublication(final long registrationId, final boolean revoke)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int index = toDriverCommandBuffer.tryClaim(REMOVE_PUBLICATION, RemovePublicationFlyweight.length());\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write remove publication command\");\n        }\n\n        removePublicationFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .revoke(revoke)\n            .registrationId(registrationId)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Instruct the driver to add a subscription.\n     *\n     * @param channel  uri in string format.\n     * @param streamId within the channel.\n     * @return the correlation id for the command.\n     */\n    public long addSubscription(final String channel, final int streamId)\n    {\n        final long registrationId = Aeron.NULL_VALUE;\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = SubscriptionMessageFlyweight.computeLength(channel.length());\n        final int index = toDriverCommandBuffer.tryClaim(ADD_SUBSCRIPTION, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write add subscription command\");\n        }\n\n        subscriptionMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .registrationCorrelationId(registrationId)\n            .streamId(streamId)\n            .channel(channel)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Instruct the driver to remove a subscription by its registration id.\n     *\n     * @param registrationId for the subscription to be removed.\n     * @return the correlation id for the command.\n     */\n    public long removeSubscription(final long registrationId)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int index = toDriverCommandBuffer.tryClaim(REMOVE_SUBSCRIPTION, RemoveSubscriptionFlyweight.length());\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write remove subscription command\");\n        }\n\n        removeSubscriptionFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .registrationId(registrationId)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Add a destination to the send channel of an existing MDC Publication.\n     *\n     * @param registrationId  of the Publication.\n     * @param endpointChannel for the destination.\n     * @return the correlation id for the command.\n     */\n    public long addDestination(final long registrationId, final String endpointChannel)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = DestinationMessageFlyweight.computeLength(endpointChannel.length());\n        final int index = toDriverCommandBuffer.tryClaim(ADD_DESTINATION, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write add destination command\");\n        }\n\n        destinationMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .registrationCorrelationId(registrationId)\n            .channel(endpointChannel)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Remove a destination from the send channel of an existing MDC Publication.\n     *\n     * @param registrationId  of the Publication.\n     * @param endpointChannel used for the {@link #addDestination(long, String)} command.\n     * @return the correlation id for the command.\n     */\n    public long removeDestination(final long registrationId, final String endpointChannel)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = DestinationMessageFlyweight.computeLength(endpointChannel.length());\n        final int index = toDriverCommandBuffer.tryClaim(REMOVE_DESTINATION, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write remove destination command\");\n        }\n\n        destinationMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .registrationCorrelationId(registrationId)\n            .channel(endpointChannel)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Remove a destination from the send channel of an existing MDC Publication.\n     *\n     * @param publicationRegistrationId  of the Publication.\n     * @param destinationRegistrationId used for the {@link #addDestination(long, String)} command.\n     * @return the correlation id for the command.\n     */\n    public long removeDestination(final long publicationRegistrationId, final long destinationRegistrationId)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int index = toDriverCommandBuffer.tryClaim(\n            REMOVE_DESTINATION_BY_ID, DestinationByIdMessageFlyweight.MESSAGE_LENGTH);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write remove destination command\");\n        }\n\n        destinationByIdMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .resourceRegistrationId(publicationRegistrationId)\n            .destinationRegistrationId(destinationRegistrationId)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Add a destination to the receive channel endpoint of an existing MDS Subscription.\n     *\n     * @param registrationId  of the Subscription.\n     * @param endpointChannel for the destination.\n     * @return the correlation id for the command.\n     */\n    public long addRcvDestination(final long registrationId, final String endpointChannel)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = DestinationMessageFlyweight.computeLength(endpointChannel.length());\n        final int index = toDriverCommandBuffer.tryClaim(ADD_RCV_DESTINATION, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write add rcv destination command\");\n        }\n\n        destinationMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .registrationCorrelationId(registrationId)\n            .channel(endpointChannel)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Remove a destination from the receive channel endpoint of an existing MDS Subscription.\n     *\n     * @param registrationId  of the Subscription.\n     * @param endpointChannel used for the {@link #addRcvDestination(long, String)} command.\n     * @return the correlation id for the command.\n     */\n    public long removeRcvDestination(final long registrationId, final String endpointChannel)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = DestinationMessageFlyweight.computeLength(endpointChannel.length());\n        final int index = toDriverCommandBuffer.tryClaim(REMOVE_RCV_DESTINATION, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write remove rcv destination command\");\n        }\n\n        destinationMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .registrationCorrelationId(registrationId)\n            .channel(endpointChannel)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Add a new counter with a type id plus the label and key are provided in buffers.\n     *\n     * @param typeId      for associating with the counter.\n     * @param keyBuffer   containing the metadata key.\n     * @param keyOffset   offset at which the key begins.\n     * @param keyLength   length in bytes for the key.\n     * @param labelBuffer containing the label.\n     * @param labelOffset offset at which the label begins.\n     * @param labelLength length in bytes for the label.\n     * @return the correlation id for the command.\n     */\n    public long addCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = CounterMessageFlyweight.computeLength(keyLength, labelLength);\n        final int index = toDriverCommandBuffer.tryClaim(ADD_COUNTER, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write add counter command\");\n        }\n\n        counterMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .keyBuffer(keyBuffer, keyOffset, keyLength)\n            .labelBuffer(labelBuffer, labelOffset, labelLength)\n            .typeId(typeId)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Add a new counter with a type id and label, the key will be blank.\n     *\n     * @param typeId for associating with the counter.\n     * @param label  that is human-readable for the counter.\n     * @return the correlation id for the command.\n     */\n    public long addCounter(final int typeId, final String label)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = CounterMessageFlyweight.computeLength(0, label.length());\n        final int index = toDriverCommandBuffer.tryClaim(ADD_COUNTER, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write add counter command\");\n        }\n\n        counterMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .keyBuffer(null, 0, 0)\n            .label(label)\n            .typeId(typeId)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Instruct the media driver to remove an existing counter by its registration id.\n     *\n     * @param registrationId of counter to remove.\n     * @return the correlation id for the command.\n     */\n    public long removeCounter(final long registrationId)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int index = toDriverCommandBuffer.tryClaim(REMOVE_COUNTER, RemoveCounterFlyweight.length());\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write remove counter command\");\n        }\n\n        removeCounterFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .registrationId(registrationId)\n            .clientId(clientId)\n            .correlationId(correlationId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    /**\n     * Notify the media driver that this client is closing.\n     */\n    public void clientClose()\n    {\n        final int index = toDriverCommandBuffer.tryClaim(CLIENT_CLOSE, CorrelatedMessageFlyweight.LENGTH);\n        if (index > 0)\n        {\n            new CorrelatedMessageFlyweight()\n                .wrap(toDriverCommandBuffer.buffer(), index)\n                .clientId(clientId)\n                .correlationId(Aeron.NULL_VALUE);\n\n            toDriverCommandBuffer.commit(index);\n        }\n    }\n\n    /**\n     * Instruct the media driver to terminate.\n     *\n     * @param tokenBuffer containing the authentication token.\n     * @param tokenOffset at which the token begins.\n     * @param tokenLength in bytes.\n     * @return true is successfully sent.\n     */\n    public boolean terminateDriver(final DirectBuffer tokenBuffer, final int tokenOffset, final int tokenLength)\n    {\n        final int length = TerminateDriverFlyweight.computeLength(tokenLength);\n        final int index = toDriverCommandBuffer.tryClaim(TERMINATE_DRIVER, length);\n        if (index > 0)\n        {\n            new TerminateDriverFlyweight()\n                .wrap(toDriverCommandBuffer.buffer(), index)\n                .tokenBuffer(tokenBuffer, tokenOffset, tokenLength)\n                .clientId(clientId)\n                .correlationId(Aeron.NULL_VALUE);\n\n            toDriverCommandBuffer.commit(index);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Reject a specific image.\n     *\n     * @param imageCorrelationId of the image to be invalidated\n     * @param position      of the image when invalidation occurred\n     * @param reason        user supplied reason for invalidation, reported back to publication\n     * @return              the correlationId of the request for invalidation.\n     */\n    public long rejectImage(\n        final long imageCorrelationId,\n        final long position,\n        final String reason)\n    {\n        final int length = RejectImageFlyweight.computeLength(reason);\n        final int index = toDriverCommandBuffer.tryClaim(REJECT_IMAGE, length);\n\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write reject image command\");\n        }\n\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n\n        rejectImageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .clientId(clientId)\n            .correlationId(correlationId)\n            .imageCorrelationId(imageCorrelationId)\n            .position(position)\n            .reason(reason);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"DriverProxy{\" +\n            \"clientId=\" + clientId +\n            '}';\n    }\n\n    long addStaticCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength,\n        final long registrationId)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = StaticCounterMessageFlyweight.computeLength(keyLength, labelLength);\n        final int index = toDriverCommandBuffer.tryClaim(ADD_STATIC_COUNTER, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write add counter command\");\n        }\n\n        staticCounterMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .keyBuffer(keyBuffer, keyOffset, keyLength)\n            .labelBuffer(labelBuffer, labelOffset, labelLength)\n            .typeId(typeId)\n            .registrationId(registrationId)\n            .correlationId(correlationId)\n            .clientId(clientId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    long addStaticCounter(final int typeId, final String label, final long registrationId)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int length = StaticCounterMessageFlyweight.computeLength(0, label.length());\n        final int index = toDriverCommandBuffer.tryClaim(ADD_STATIC_COUNTER, length);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write add counter command\");\n        }\n\n        staticCounterMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .keyBuffer(null, 0, 0)\n            .label(label)\n            .typeId(typeId)\n            .registrationId(registrationId)\n            .correlationId(correlationId)\n            .clientId(clientId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n\n    long nextAvailableSessionId(final int streamId)\n    {\n        final long correlationId = toDriverCommandBuffer.nextCorrelationId();\n        final int index = toDriverCommandBuffer.tryClaim(\n            GET_NEXT_AVAILABLE_SESSION_ID, GetNextAvailableSessionIdMessageFlyweight.LENGTH);\n        if (index < 0)\n        {\n            throw new AeronException(\"failed to write next session id command\");\n        }\n\n        getNextAvailableSessionIdMessageFlyweight\n            .wrap(toDriverCommandBuffer.buffer(), index)\n            .streamId(streamId)\n            .correlationId(correlationId)\n            .clientId(clientId);\n\n        toDriverCommandBuffer.commit(index);\n\n        return correlationId;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ErrorCode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.exceptions.AeronException;\n\n/**\n * Error codes between media driver and client and the on-wire protocol.\n */\npublic enum ErrorCode\n{\n    /**\n     * Old generic value, no longer used (0 value clashes with success). Retained for version compatibility.\n     */\n    UNUSED(0),\n\n    /**\n     * A failure occurred creating a new channel or parsing the channel string.\n     */\n    INVALID_CHANNEL(1),\n\n    /**\n     * Attempted to reference a subscription, but it was not found.\n     */\n    UNKNOWN_SUBSCRIPTION(2),\n\n    /**\n     * Attempted to reference a publication, but it was not found.\n     */\n    UNKNOWN_PUBLICATION(3),\n\n    /**\n     * Channel Endpoint could not be successfully opened.\n     */\n    CHANNEL_ENDPOINT_ERROR(4),\n\n    /**\n     * Attempted to reference a counter, but it was not found.\n     */\n    UNKNOWN_COUNTER(5),\n\n    /**\n     * Attempted to send a command unknown by the driver.\n     */\n    UNKNOWN_COMMAND_TYPE_ID(6),\n\n    /**\n     * Attempted to send a command that is malformed. Typically, too short.\n     */\n    MALFORMED_COMMAND(7),\n\n    /**\n     * Attempted to send a command known by the driver, but not currently supported.\n     */\n    NOT_SUPPORTED(8),\n\n    /**\n     * Attempted to send a command that had a hostname that could not be resolved.\n     */\n    UNKNOWN_HOST(9),\n\n    /**\n     * Attempted to send a command that referred to a resource that currently was unavailable.\n     */\n    RESOURCE_TEMPORARILY_UNAVAILABLE(10),\n\n    /**\n     * Aeron encountered an error condition.\n     */\n    GENERIC_ERROR(11),\n\n    /**\n     * Aeron encountered insufficient storage space while adding a resource.\n     */\n    STORAGE_SPACE(12),\n\n    /**\n     * An image was rejected.\n     */\n    IMAGE_REJECTED(13),\n\n    /**\n     * A publication was revoked.\n     */\n    PUBLICATION_REVOKED(14),\n\n    // *** Insert new codes above here.\n\n    /**\n     * A code value returned was not known.\n     */\n    UNKNOWN_CODE_VALUE(-1);\n\n    static final ErrorCode[] ERROR_CODES;\n\n    static\n    {\n        final ErrorCode[] errorCodes = values();\n        ERROR_CODES = new ErrorCode[errorCodes.length];\n\n        for (final ErrorCode errorCode : errorCodes)\n        {\n            final int value = errorCode.value();\n\n            if (value == UNKNOWN_CODE_VALUE.value())\n            {\n                continue;\n            }\n\n            if (null != ERROR_CODES[value])\n            {\n                throw new AeronException(\"value already in use: \" + value);\n            }\n\n            ERROR_CODES[value] = errorCode;\n        }\n    }\n\n    private final int value;\n\n    ErrorCode(final int value)\n    {\n        this.value = value;\n    }\n\n    /**\n     * Get the value of this ErrorCode.\n     *\n     * @return The value.\n     */\n    public int value()\n    {\n        return value;\n    }\n\n    /**\n     * Get the ErrorCode that corresponds to the given value.\n     *\n     * @param value of the ErrorCode\n     * @return ErrorCode\n     */\n    public static ErrorCode get(final int value)\n    {\n        if (0 <= value && value <= (ERROR_CODES.length - 2))\n        {\n            return ERROR_CODES[value];\n        }\n\n        return UNKNOWN_CODE_VALUE;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ExclusivePublication.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.ReadablePosition;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.*;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.BitUtil.align;\n\n@SuppressWarnings(\"unused\")\nabstract class ExclusivePublicationLhsPadding extends Publication\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n\n    ExclusivePublicationLhsPadding(\n        final ClientConductor clientConductor,\n        final String channel,\n        final int streamId,\n        final int sessionId,\n        final ReadablePosition positionLimit,\n        final int channelStatusId,\n        final LogBuffers logBuffers,\n        final long originalRegistrationId,\n        final long registrationId)\n    {\n        super(\n            clientConductor,\n            channel,\n            streamId,\n            sessionId,\n            positionLimit,\n            channelStatusId,\n            logBuffers,\n            originalRegistrationId,\n            registrationId);\n    }\n}\n\nabstract class ExclusivePublicationValues extends ExclusivePublicationLhsPadding\n{\n    int termOffset;\n    int termId;\n    int activePartitionIndex;\n    long termBeginPosition;\n\n    ExclusivePublicationValues(\n        final ClientConductor clientConductor,\n        final String channel,\n        final int streamId,\n        final int sessionId,\n        final ReadablePosition positionLimit,\n        final int channelStatusId,\n        final LogBuffers logBuffers,\n        final long originalRegistrationId,\n        final long registrationId)\n    {\n        super(\n            clientConductor,\n            channel,\n            streamId,\n            sessionId,\n            positionLimit,\n            channelStatusId,\n            logBuffers,\n            originalRegistrationId,\n            registrationId);\n    }\n}\n\n/**\n * Aeron publisher API for sending messages to subscribers of a given channel and streamId pair. ExclusivePublications\n * each get their own session id so multiple can be concurrently active on the same media driver as independent streams.\n * <p>\n * {@link ExclusivePublication}s are created via the {@link Aeron#addExclusivePublication(String, int)} method,\n * and messages are sent via one of the {@link #offer(DirectBuffer)} methods, or a\n * {@link #tryClaim(int, BufferClaim)} and {@link BufferClaim#commit()} method combination.\n * <p>\n * {@link ExclusivePublication}s have the potential to provide greater throughput than the default {@link Publication}\n * which supports concurrent access.\n * <p>\n * The APIs for tryClaim and offer are non-blocking.\n * <p>\n * <b>Note:</b> Instances are NOT threadsafe for offer and tryClaim methods but are for the others.\n *\n * @see Aeron#addExclusivePublication(String, int)\n */\npublic final class ExclusivePublication extends ExclusivePublicationValues\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n\n    ExclusivePublication(\n        final ClientConductor clientConductor,\n        final String channel,\n        final int streamId,\n        final int sessionId,\n        final ReadablePosition positionLimit,\n        final int channelStatusId,\n        final LogBuffers logBuffers,\n        final long originalRegistrationId,\n        final long registrationId)\n    {\n        super(\n            clientConductor,\n            channel,\n            streamId,\n            sessionId,\n            positionLimit,\n            channelStatusId,\n            logBuffers,\n            originalRegistrationId,\n            registrationId);\n\n        final UnsafeBuffer logMetaDataBuffer = logBuffers.metaDataBuffer();\n        final int termCount = activeTermCount(logMetaDataBuffer);\n        final int index = indexByTermCount(termCount);\n        activePartitionIndex = index;\n\n        final long rawTail = rawTail(logMetaDataBuffer, index);\n        termId = LogBufferDescriptor.termId(rawTail);\n        termOffset = LogBufferDescriptor.termOffset(rawTail);\n        termBeginPosition = computeTermBeginPosition(termId, positionBitsToShift, initialTermId);\n    }\n\n    /**\n     * Mark the publication to be revoked when {@link #close()} is called.  See  {@link #revoke()}\n     */\n    public void revokeOnClose()\n    {\n        revokeOnClose = true;\n    }\n\n    /**\n     * Immediately revoke and {@link #close()} the publication.\n     *\n     * Revoking disposes of resources as soon as possible. On the publication side the log buffer won't linger,\n     * while on the subscription side the image will go unavailable without requiring all data to be drained.\n     * Hence, it should be used only when it's known that all subscribers have received all the data,\n     * or if it doesn't matter if they have.\n     */\n    public void revoke()\n    {\n        if (!isClosed)\n        {\n            revokeOnClose = true;\n            close();\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long position()\n    {\n        if (isClosed)\n        {\n            return CLOSED;\n        }\n\n        return termBeginPosition + termOffset;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long availableWindow()\n    {\n        if (isClosed)\n        {\n            return CLOSED;\n        }\n\n        return positionLimit.getVolatile() - (termBeginPosition + termOffset);\n    }\n\n    /**\n     * The current term-id of the publication.\n     *\n     * @return the current term-id of the publication.\n     */\n    public int termId()\n    {\n        return termId;\n    }\n\n    /**\n     * The current term-offset of the publication.\n     *\n     * @return the current term-offset of the publication.\n     */\n    public int termOffset()\n    {\n        return termOffset;\n    }\n\n    /**\n     * Non-blocking publish of a partial buffer containing a message.\n     *\n     * @param buffer                containing message.\n     * @param offset                offset in the buffer at which the encoded message begins.\n     * @param length                in bytes of the encoded message.\n     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public long offer(\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        long newPosition = CLOSED;\n        if (!isClosed)\n        {\n            final long limit = positionLimit.getVolatile();\n            final long position = termBeginPosition + termOffset;\n\n            if (position < limit)\n            {\n                final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (activePartitionIndex * SIZE_OF_LONG);\n                final UnsafeBuffer termBuffer = termBuffers[activePartitionIndex];\n                final int result;\n\n                if (length <= maxPayloadLength)\n                {\n                    checkPositiveLength(length);\n                    result = appendUnfragmentedMessage(\n                        tailCounterOffset, termBuffer, buffer, offset, length, reservedValueSupplier);\n                }\n                else\n                {\n                    checkMaxMessageLength(length);\n                    result = appendFragmentedMessage(\n                        termBuffer, tailCounterOffset, buffer, offset, length, reservedValueSupplier);\n                }\n\n                newPosition = newPosition(result);\n            }\n            else\n            {\n                newPosition = backPressureStatus(position, length);\n            }\n        }\n\n        return newPosition;\n    }\n\n    /**\n     * Non-blocking publish of a message composed of two parts, e.g. a header and encapsulated payload.\n     *\n     * @param bufferOne             containing the first part of the message.\n     * @param offsetOne             at which the first part of the message begins.\n     * @param lengthOne             of the first part of the message.\n     * @param bufferTwo             containing the second part of the message.\n     * @param offsetTwo             at which the second part of the message begins.\n     * @param lengthTwo             of the second part of the message.\n     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public long offer(\n        final DirectBuffer bufferOne,\n        final int offsetOne,\n        final int lengthOne,\n        final DirectBuffer bufferTwo,\n        final int offsetTwo,\n        final int lengthTwo,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        long newPosition = CLOSED;\n        if (!isClosed)\n        {\n            final long limit = positionLimit.getVolatile();\n            final long position = termBeginPosition + termOffset;\n            final int length = validateAndComputeLength(lengthOne, lengthTwo);\n\n            if (position < limit)\n            {\n                final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (activePartitionIndex * SIZE_OF_LONG);\n                final UnsafeBuffer termBuffer = termBuffers[activePartitionIndex];\n                final int result;\n\n                if (length <= maxPayloadLength)\n                {\n                    checkPositiveLength(length);\n                    result = appendUnfragmentedMessage(\n                        termBuffer,\n                        tailCounterOffset,\n                        bufferOne, offsetOne, lengthOne,\n                        bufferTwo, offsetTwo, lengthTwo,\n                        reservedValueSupplier);\n                }\n                else\n                {\n                    checkMaxMessageLength(length);\n                    result = appendFragmentedMessage(\n                        termBuffer,\n                        tailCounterOffset,\n                        bufferOne, offsetOne, lengthOne,\n                        bufferTwo, offsetTwo, lengthTwo,\n                        maxPayloadLength,\n                        reservedValueSupplier);\n                }\n\n                newPosition = newPosition(result);\n            }\n            else\n            {\n                newPosition = backPressureStatus(position, length);\n            }\n        }\n\n        return newPosition;\n    }\n\n    /**\n     * Non-blocking publish by gathering buffer vectors into a message.\n     *\n     * @param vectors               which make up the message.\n     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public long offer(final DirectBufferVector[] vectors, final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int length = DirectBufferVector.validateAndComputeLength(vectors);\n        long newPosition = CLOSED;\n\n        if (!isClosed)\n        {\n            final long limit = positionLimit.getVolatile();\n            final long position = termBeginPosition + termOffset;\n\n            if (position < limit)\n            {\n                final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (activePartitionIndex * SIZE_OF_LONG);\n                final UnsafeBuffer termBuffer = termBuffers[activePartitionIndex];\n                final int result;\n\n                if (length <= maxPayloadLength)\n                {\n                    result = appendUnfragmentedMessage(\n                        termBuffer, tailCounterOffset, vectors, length, reservedValueSupplier);\n                }\n                else\n                {\n                    checkMaxMessageLength(length);\n                    result = appendFragmentedMessage(\n                        termBuffer, tailCounterOffset, vectors, length, reservedValueSupplier);\n                }\n\n                newPosition = newPosition(result);\n            }\n            else\n            {\n                newPosition = backPressureStatus(position, length);\n            }\n        }\n\n        return newPosition;\n    }\n\n    /**\n     * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n     * Once the message has been written then {@link BufferClaim#commit()} should be called thus making it\n     * available.\n     * <p>\n     * <b>Note:</b> This method can only be used for message lengths less than MTU length minus header.\n     * If the claim is held after the publication is closed, or the client dies, then it will be unblocked to reach\n     * end-of-stream (EOS).\n     * <pre>{@code\n     *     final BufferClaim bufferClaim = new BufferClaim();\n     *\n     *     if (publication.tryClaim(messageLength, bufferClaim) > 0L)\n     *     {\n     *         try\n     *         {\n     *              final MutableDirectBuffer buffer = bufferClaim.buffer();\n     *              final int offset = bufferClaim.offset();\n     *\n     *              // Work with buffer directly or wrap with a flyweight\n     *         }\n     *         finally\n     *         {\n     *             bufferClaim.commit();\n     *         }\n     *     }\n     * }</pre>\n     *\n     * @param length      of the range to claim, in bytes.\n     * @param bufferClaim to be populated if the claim succeeds.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     * @throws IllegalArgumentException if the length is greater than {@link #maxPayloadLength()} within an MTU.\n     * @see BufferClaim#commit()\n     * @see BufferClaim#abort()\n     */\n    public long tryClaim(final int length, final BufferClaim bufferClaim)\n    {\n        checkPayloadLength(length);\n        long newPosition = CLOSED;\n\n        if (!isClosed)\n        {\n            final long limit = positionLimit.getVolatile();\n            final long position = termBeginPosition + termOffset;\n\n            if (position < limit)\n            {\n                final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (activePartitionIndex * SIZE_OF_LONG);\n                final UnsafeBuffer termBuffer = termBuffers[activePartitionIndex];\n                final int result = claim(termBuffer, tailCounterOffset, length, bufferClaim);\n\n                newPosition = newPosition(result);\n            }\n            else\n            {\n                newPosition = backPressureStatus(position, length);\n            }\n        }\n\n        return newPosition;\n    }\n\n    /**\n     * Append a padding record to log of a given length to make up the log to a position.\n     *\n     * @param length of the range to claim, in bytes.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     * @throws IllegalArgumentException if the length is greater than {@link #maxMessageLength() framed}.\n     */\n    public long appendPadding(final int length)\n    {\n        if (length > maxFramedLength)\n        {\n            throw new IllegalArgumentException(\n                \"padding exceeds maxFramedLength of \" + maxFramedLength + \", length=\" + length);\n        }\n\n        long newPosition = CLOSED;\n        if (!isClosed)\n        {\n            final long limit = positionLimit.getVolatile();\n            final long position = termBeginPosition + termOffset;\n\n            if (position < limit)\n            {\n                checkPositiveLength(length);\n                final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (activePartitionIndex * SIZE_OF_LONG);\n                final UnsafeBuffer termBuffer = termBuffers[activePartitionIndex];\n                final int result = appendPadding(termBuffer, tailCounterOffset, length);\n\n                newPosition = newPosition(result);\n            }\n            else\n            {\n                newPosition = backPressureStatus(position, length);\n            }\n        }\n\n        return newPosition;\n    }\n\n    /**\n     * Offer a block of pre-formatted message fragments directly into the current term.\n     *\n     * @param buffer containing the pre-formatted block of message fragments.\n     * @param offset offset in the buffer at which the first fragment begins.\n     * @param length in bytes of the encoded block.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     * @throws IllegalArgumentException if the length is greater than remaining size of the current term.\n     * @throws IllegalArgumentException if the first frame within the block is not properly formatted, i.e. if the\n     *                                  {@code streamId} is not equal to the value returned by the {@link #streamId()}\n     *                                  method or if the {@code sessionId} is not equal to the value returned by the\n     *                                  {@link #sessionId()} method or if the frame type is not equal to the\n     *                                  {@link io.aeron.protocol.HeaderFlyweight#HDR_TYPE_DATA}.\n     */\n    public long offerBlock(final MutableDirectBuffer buffer, final int offset, final int length)\n    {\n        if (isClosed)\n        {\n            return CLOSED;\n        }\n\n        if (termOffset >= termBufferLength)\n        {\n            rotateTerm();\n        }\n\n        final long limit = positionLimit.getVolatile();\n        final long position = termBeginPosition + termOffset;\n\n        if (position < limit)\n        {\n            checkBlockLength(length);\n            checkFirstFrame(buffer, offset);\n\n            final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (activePartitionIndex * SIZE_OF_LONG);\n            final UnsafeBuffer termBuffer = termBuffers[activePartitionIndex];\n            final int result = appendBlock(termBuffer, tailCounterOffset, buffer, offset, length);\n\n            return newPosition(result);\n        }\n        else\n        {\n            return backPressureStatus(position, length);\n        }\n    }\n\n    private void checkBlockLength(final int length)\n    {\n        final int remaining = termBufferLength - termOffset;\n        if (length > remaining)\n        {\n            throw new IllegalArgumentException(\n                \"invalid block length \" + length + \", remaining space in term is \" + remaining);\n        }\n    }\n\n    private void checkFirstFrame(final MutableDirectBuffer buffer, final int offset)\n    {\n        final int frameType = HDR_TYPE_DATA;\n        final int blockTermOffset = buffer.getInt(offset + TERM_OFFSET_FIELD_OFFSET, LITTLE_ENDIAN);\n        final int blockSessionId = buffer.getInt(offset + SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n        final int blockStreamId = buffer.getInt(offset + STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n        final int blockTermId = buffer.getInt(offset + TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n        final int blockFrameType = buffer.getShort(offset + TYPE_FIELD_OFFSET, LITTLE_ENDIAN) & 0xFFFF;\n\n        if (blockTermOffset != termOffset ||\n            blockSessionId != sessionId ||\n            blockStreamId != streamId ||\n            blockTermId != termId ||\n            frameType != blockFrameType)\n        {\n            throw new IllegalArgumentException(\"improperly formatted block:\" +\n                \" termOffset=\" + blockTermOffset + \" (expected=\" + termOffset + \"),\" +\n                \" sessionId=\" + blockSessionId + \" (expected=\" + sessionId + \"),\" +\n                \" streamId=\" + blockStreamId + \" (expected=\" + streamId + \"),\" +\n                \" termId=\" + blockTermId + \" (expected=\" + termId + \"),\" +\n                \" frameType=\" + blockFrameType + \" (expected=\" + frameType + \")\");\n        }\n    }\n\n    private long newPosition(final int resultingOffset)\n    {\n        if (resultingOffset > 0)\n        {\n            termOffset = resultingOffset;\n            return termBeginPosition + resultingOffset;\n        }\n\n        if ((termBeginPosition + termBufferLength) >= maxPossiblePosition)\n        {\n            return MAX_POSITION_EXCEEDED;\n        }\n\n        rotateTerm();\n\n        return ADMIN_ACTION;\n    }\n\n    private void rotateTerm()\n    {\n        final int nextIndex = nextPartitionIndex(activePartitionIndex);\n        final int nextTermId = termId + 1;\n\n        activePartitionIndex = nextIndex;\n        termOffset = 0;\n        termId = nextTermId;\n        termBeginPosition += termBufferLength;\n\n        final int termCount = nextTermId - initialTermId;\n\n        initialiseTailWithTermId(logMetaDataBuffer, nextIndex, nextTermId);\n        activeTermCountOrdered(logMetaDataBuffer, termCount);\n    }\n\n    private int handleEndOfLog(final UnsafeBuffer termBuffer, final int termLength)\n    {\n        if (termOffset < termLength)\n        {\n            final int offset = termOffset;\n            final int paddingLength = termLength - offset;\n            headerWriter.write(termBuffer, offset, paddingLength, termId);\n            frameType(termBuffer, offset, PADDING_FRAME_TYPE);\n            frameLengthOrdered(termBuffer, offset, paddingLength);\n        }\n\n        return -1;\n    }\n\n    private int appendUnfragmentedMessage(\n        final int tailCounterOffset,\n        final UnsafeBuffer termBuffer,\n        final DirectBuffer srcBuffer,\n        final int srcOffset,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int frameLength = length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termLength = termBuffer.capacity();\n\n        int resultingOffset = termOffset + alignedLength;\n        logMetaDataBuffer.putLongRelease(tailCounterOffset, packTail(termId, resultingOffset));\n\n        if (resultingOffset > termLength)\n        {\n            resultingOffset = handleEndOfLog(termBuffer, termLength);\n        }\n        else\n        {\n            headerWriter.write(termBuffer, termOffset, frameLength, termId);\n            termBuffer.putBytes(termOffset + HEADER_LENGTH, srcBuffer, srcOffset, length);\n\n            if (null != reservedValueSupplier)\n            {\n                final long reservedValue = reservedValueSupplier.get(termBuffer, termOffset, frameLength);\n                termBuffer.putLong(termOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n            }\n\n            frameLengthOrdered(termBuffer, termOffset, frameLength);\n        }\n\n        return resultingOffset;\n    }\n\n    private int appendFragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBuffer srcBuffer,\n        final int srcOffset,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int framedLength = computeFragmentedFrameLength(length, maxPayloadLength);\n        final int termLength = termBuffer.capacity();\n\n        int resultingOffset = termOffset + framedLength;\n        logMetaDataBuffer.putLongRelease(tailCounterOffset, packTail(termId, resultingOffset));\n\n        if (resultingOffset > termLength)\n        {\n            resultingOffset = handleEndOfLog(termBuffer, termLength);\n        }\n        else\n        {\n            int frameOffset = termOffset;\n            byte flags = BEGIN_FRAG_FLAG;\n            int remaining = length;\n            do\n            {\n                final int bytesToWrite = Math.min(remaining, maxPayloadLength);\n                final int frameLength = bytesToWrite + HEADER_LENGTH;\n                final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n\n                headerWriter.write(termBuffer, frameOffset, frameLength, termId);\n                termBuffer.putBytes(\n                    frameOffset + HEADER_LENGTH,\n                    srcBuffer,\n                    srcOffset + (length - remaining),\n                    bytesToWrite);\n\n                if (remaining <= maxPayloadLength)\n                {\n                    flags |= END_FRAG_FLAG;\n                }\n\n                frameFlags(termBuffer, frameOffset, flags);\n\n                if (null != reservedValueSupplier)\n                {\n                    final long reservedValue = reservedValueSupplier.get(termBuffer, frameOffset, frameLength);\n                    termBuffer.putLong(frameOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n                }\n\n                frameLengthOrdered(termBuffer, frameOffset, frameLength);\n\n                flags = 0;\n                frameOffset += alignedLength;\n                remaining -= bytesToWrite;\n            }\n            while (remaining > 0);\n        }\n\n        return resultingOffset;\n    }\n\n    private int appendUnfragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBuffer bufferOne,\n        final int offsetOne,\n        final int lengthOne,\n        final DirectBuffer bufferTwo,\n        final int offsetTwo,\n        final int lengthTwo,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int frameLength = lengthOne + lengthTwo + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termLength = termBuffer.capacity();\n\n        int resultingOffset = termOffset + alignedLength;\n        logMetaDataBuffer.putLongRelease(tailCounterOffset, packTail(termId, resultingOffset));\n\n        if (resultingOffset > termLength)\n        {\n            resultingOffset = handleEndOfLog(termBuffer, termLength);\n        }\n        else\n        {\n            headerWriter.write(termBuffer, termOffset, frameLength, termId);\n            termBuffer.putBytes(termOffset + HEADER_LENGTH, bufferOne, offsetOne, lengthOne);\n            termBuffer.putBytes(termOffset + HEADER_LENGTH + lengthOne, bufferTwo, offsetTwo, lengthTwo);\n\n            if (null != reservedValueSupplier)\n            {\n                final long reservedValue = reservedValueSupplier.get(termBuffer, termOffset, frameLength);\n                termBuffer.putLong(termOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n            }\n\n            frameLengthOrdered(termBuffer, termOffset, frameLength);\n        }\n\n        return resultingOffset;\n    }\n\n    private int appendFragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBuffer bufferOne,\n        final int offsetOne,\n        final int lengthOne,\n        final DirectBuffer bufferTwo,\n        final int offsetTwo,\n        final int lengthTwo,\n        final int maxPayloadLength,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int length = lengthOne + lengthTwo;\n        final int framedLength = computeFragmentedFrameLength(length, maxPayloadLength);\n        final int termLength = termBuffer.capacity();\n\n        int resultingOffset = termOffset + framedLength;\n        logMetaDataBuffer.putLongRelease(tailCounterOffset, packTail(termId, resultingOffset));\n\n        if (resultingOffset > termLength)\n        {\n            resultingOffset = handleEndOfLog(termBuffer, termLength);\n        }\n        else\n        {\n            int frameOffset = termOffset;\n            byte flags = BEGIN_FRAG_FLAG;\n            int remaining = length;\n            int positionOne = 0;\n            int positionTwo = 0;\n\n            do\n            {\n                final int bytesToWrite = Math.min(remaining, maxPayloadLength);\n                final int frameLength = bytesToWrite + HEADER_LENGTH;\n                final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n\n                headerWriter.write(termBuffer, frameOffset, frameLength, termId);\n\n                int bytesWritten = 0;\n                int payloadOffset = frameOffset + HEADER_LENGTH;\n                do\n                {\n                    final int remainingOne = lengthOne - positionOne;\n                    if (remainingOne > 0)\n                    {\n                        final int numBytes = Math.min(bytesToWrite - bytesWritten, remainingOne);\n                        termBuffer.putBytes(payloadOffset, bufferOne, offsetOne + positionOne, numBytes);\n\n                        bytesWritten += numBytes;\n                        payloadOffset += numBytes;\n                        positionOne += numBytes;\n                    }\n                    else\n                    {\n                        final int numBytes = Math.min(bytesToWrite - bytesWritten, lengthTwo - positionTwo);\n                        termBuffer.putBytes(payloadOffset, bufferTwo, offsetTwo + positionTwo, numBytes);\n\n                        bytesWritten += numBytes;\n                        payloadOffset += numBytes;\n                        positionTwo += numBytes;\n                    }\n                }\n                while (bytesWritten < bytesToWrite);\n\n                if (remaining <= maxPayloadLength)\n                {\n                    flags |= END_FRAG_FLAG;\n                }\n\n                frameFlags(termBuffer, frameOffset, flags);\n\n                if (null != reservedValueSupplier)\n                {\n                    final long reservedValue = reservedValueSupplier.get(termBuffer, frameOffset, frameLength);\n                    termBuffer.putLong(frameOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n                }\n\n                frameLengthOrdered(termBuffer, frameOffset, frameLength);\n\n                flags = 0;\n                frameOffset += alignedLength;\n                remaining -= bytesToWrite;\n            }\n            while (remaining > 0);\n        }\n\n        return resultingOffset;\n    }\n\n    private int appendUnfragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBufferVector[] vectors,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int frameLength = length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termLength = termBuffer.capacity();\n\n        int resultingOffset = termOffset + alignedLength;\n        logMetaDataBuffer.putLongRelease(tailCounterOffset, packTail(termId, resultingOffset));\n\n        if (resultingOffset > termLength)\n        {\n            resultingOffset = handleEndOfLog(termBuffer, termLength);\n        }\n        else\n        {\n            headerWriter.write(termBuffer, termOffset, frameLength, termId);\n\n            int offset = termOffset + HEADER_LENGTH;\n            for (final DirectBufferVector vector : vectors)\n            {\n                termBuffer.putBytes(offset, vector.buffer(), vector.offset(), vector.length());\n                offset += vector.length();\n            }\n\n            if (null != reservedValueSupplier)\n            {\n                final long reservedValue = reservedValueSupplier.get(termBuffer, termOffset, frameLength);\n                termBuffer.putLong(termOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n            }\n\n            frameLengthOrdered(termBuffer, termOffset, frameLength);\n        }\n\n        return resultingOffset;\n    }\n\n    private int appendFragmentedMessage(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final DirectBufferVector[] vectors,\n        final int length,\n        final ReservedValueSupplier reservedValueSupplier)\n    {\n        final int framedLength = computeFragmentedFrameLength(length, maxPayloadLength);\n        final int termLength = termBuffer.capacity();\n\n        int resultingOffset = termOffset + framedLength;\n        logMetaDataBuffer.putLongRelease(tailCounterOffset, packTail(termId, resultingOffset));\n\n        if (resultingOffset > termLength)\n        {\n            resultingOffset = handleEndOfLog(termBuffer, termLength);\n        }\n        else\n        {\n            int frameOffset = termOffset;\n            byte flags = BEGIN_FRAG_FLAG;\n            int remaining = length;\n            int vectorIndex = 0;\n            int vectorOffset = 0;\n\n            do\n            {\n                final int bytesToWrite = Math.min(remaining, maxPayloadLength);\n                final int frameLength = bytesToWrite + HEADER_LENGTH;\n                final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n\n                headerWriter.write(termBuffer, frameOffset, frameLength, termId);\n\n                int bytesWritten = 0;\n                int payloadOffset = frameOffset + HEADER_LENGTH;\n                do\n                {\n                    final DirectBufferVector vector = vectors[vectorIndex];\n                    final int vectorRemaining = vector.length() - vectorOffset;\n                    final int numBytes = Math.min(bytesToWrite - bytesWritten, vectorRemaining);\n\n                    termBuffer.putBytes(payloadOffset, vector.buffer(), vector.offset() + vectorOffset, numBytes);\n\n                    bytesWritten += numBytes;\n                    payloadOffset += numBytes;\n                    vectorOffset += numBytes;\n\n                    if (vectorRemaining <= numBytes)\n                    {\n                        vectorIndex++;\n                        vectorOffset = 0;\n                    }\n                }\n                while (bytesWritten < bytesToWrite);\n\n                if (remaining <= maxPayloadLength)\n                {\n                    flags |= END_FRAG_FLAG;\n                }\n\n                frameFlags(termBuffer, frameOffset, flags);\n\n                if (null != reservedValueSupplier)\n                {\n                    final long reservedValue = reservedValueSupplier.get(termBuffer, frameOffset, frameLength);\n                    termBuffer.putLong(frameOffset + RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n                }\n\n                frameLengthOrdered(termBuffer, frameOffset, frameLength);\n\n                flags = 0;\n                frameOffset += alignedLength;\n                remaining -= bytesToWrite;\n            }\n            while (remaining > 0);\n        }\n\n        return resultingOffset;\n    }\n\n    int claim(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final int length,\n        final BufferClaim bufferClaim)\n    {\n        final int frameLength = length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termLength = termBuffer.capacity();\n\n        int resultingOffset = termOffset + alignedLength;\n        logMetaDataBuffer.putLongRelease(tailCounterOffset, packTail(termId, resultingOffset));\n\n        if (resultingOffset > termLength)\n        {\n            resultingOffset = handleEndOfLog(termBuffer, termLength);\n        }\n        else\n        {\n            headerWriter.write(termBuffer, termOffset, frameLength, termId);\n            bufferClaim.wrap(termBuffer, termOffset, frameLength);\n        }\n\n        return resultingOffset;\n    }\n\n    private int appendPadding(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final int length)\n    {\n        final int frameLength = length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termLength = termBuffer.capacity();\n\n        int resultingOffset = termOffset + alignedLength;\n        logMetaDataBuffer.putLongRelease(tailCounterOffset, packTail(termId, resultingOffset));\n\n        if (resultingOffset > termLength)\n        {\n            resultingOffset = handleEndOfLog(termBuffer, termLength);\n        }\n        else\n        {\n            headerWriter.write(termBuffer, termOffset, frameLength, termId);\n            frameType(termBuffer, termOffset, PADDING_FRAME_TYPE);\n            frameLengthOrdered(termBuffer, termOffset, frameLength);\n        }\n\n        return resultingOffset;\n    }\n\n    private int appendBlock(\n        final UnsafeBuffer termBuffer,\n        final int tailCounterOffset,\n        final MutableDirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        final int resultingOffset = termOffset + length;\n\n        logMetaDataBuffer.putLongRelease(tailCounterOffset, packTail(termId, resultingOffset));\n\n        termBuffer.putBytes(termOffset + HEADER_LENGTH, buffer, offset + HEADER_LENGTH, length - HEADER_LENGTH);\n        termBuffer.putLong(termOffset + 24, buffer.getLong(offset + 24));\n        termBuffer.putLong(termOffset + 16, buffer.getLong(offset + 16));\n        termBuffer.putLong(termOffset + 8, buffer.getLong(offset + 8));\n        termBuffer.putLongRelease(termOffset, buffer.getLong(offset));\n\n        return resultingOffset;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/FragmentAssembler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.Int2ObjectHashMap;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\n\n/**\n * A {@link FragmentHandler} that sits in a chain-of-responsibility pattern that reassembles fragmented messages\n * so that the next handler in the chain only sees whole messages.\n * <p>\n * Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary\n * buffer for reassembly before delegation.\n * <p>\n * The {@link Header} passed to the delegate on assembling a message will be that of the last fragment.\n * <p>\n * Session based buffers will be allocated and grown as necessary based on the length of messages to be assembled.\n * When sessions go inactive see {@link UnavailableImageHandler}, it is possible to free the buffer by calling\n * {@link #freeSessionBuffer(int)}.\n *\n * @see Subscription#poll(FragmentHandler, int)\n * @see Image#poll(FragmentHandler, int)\n */\npublic class FragmentAssembler implements FragmentHandler\n{\n    private final boolean isDirectByteBuffer;\n    private final int initialBufferLength;\n    private final FragmentHandler delegate;\n    private final Int2ObjectHashMap<BufferBuilder> builderBySessionIdMap = new Int2ObjectHashMap<>();\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on whole messages.\n     *\n     * @param delegate onto which whole messages are forwarded.\n     */\n    public FragmentAssembler(final FragmentHandler delegate)\n    {\n        this(delegate, 0, false);\n    }\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for each session.\n     */\n    public FragmentAssembler(final FragmentHandler delegate, final int initialBufferLength)\n    {\n        this(delegate, initialBufferLength, false);\n    }\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for each session.\n     * @param isDirectByteBuffer  is the underlying buffer to be a direct {@link java.nio.ByteBuffer}?\n     */\n    public FragmentAssembler(\n        final FragmentHandler delegate, final int initialBufferLength, final boolean isDirectByteBuffer)\n    {\n        this.initialBufferLength = initialBufferLength;\n        this.delegate = delegate;\n        this.isDirectByteBuffer = isDirectByteBuffer;\n    }\n\n    /**\n     * Get the delegate unto which assembled messages are delegated.\n     *\n     * @return the delegate unto which assembled messages are delegated.\n     */\n    public FragmentHandler delegate()\n    {\n        return delegate;\n    }\n\n    /**\n     * Is the underlying buffer used to assemble fragments a direct {@link java.nio.ByteBuffer}?\n     *\n     * @return true if the underlying buffer used to assemble fragments is a direct {@link java.nio.ByteBuffer}\n     */\n    public boolean isDirectByteBuffer()\n    {\n        return isDirectByteBuffer;\n    }\n\n    /**\n     * The implementation of {@link FragmentHandler} that reassembles and forwards whole messages.\n     *\n     * @param buffer containing the data.\n     * @param offset at which the data begins.\n     * @param length of the data in bytes.\n     * @param header representing the metadata for the data.\n     */\n    public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final byte flags = header.flags();\n\n        if ((flags & UNFRAGMENTED) == UNFRAGMENTED)\n        {\n            delegate.onFragment(buffer, offset, length, header);\n        }\n        else\n        {\n            handleFragment(buffer, offset, length, header, flags);\n        }\n    }\n\n    private void handleFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header, final byte flags)\n    {\n        if ((flags & BEGIN_FRAG_FLAG) == BEGIN_FRAG_FLAG)\n        {\n            final BufferBuilder builder = getBufferBuilder(header.sessionId());\n            builder.reset()\n                .captureHeader(header)\n                .append(buffer, offset, length)\n                .nextTermOffset(header.nextTermOffset());\n        }\n        else\n        {\n            final BufferBuilder builder = builderBySessionIdMap.get(header.sessionId());\n            if (null != builder)\n            {\n                if (header.termOffset() == builder.nextTermOffset())\n                {\n                    builder.append(buffer, offset, length);\n\n                    if ((flags & END_FRAG_FLAG) == END_FRAG_FLAG)\n                    {\n                        delegate.onFragment(\n                            builder.buffer(), 0, builder.limit(), builder.completeHeader(header));\n                        builder.reset();\n                    }\n                    else\n                    {\n                        builder.nextTermOffset(header.nextTermOffset());\n                    }\n                }\n                else\n                {\n                    builder.reset();\n                }\n            }\n        }\n    }\n\n    /**\n     * Free an existing session buffer to reduce memory pressure when an image goes inactive or no more\n     * large messages are expected.\n     *\n     * @param sessionId to have its buffer freed\n     * @return true if a buffer has been freed otherwise false.\n     */\n    public boolean freeSessionBuffer(final int sessionId)\n    {\n        return null != builderBySessionIdMap.remove(sessionId);\n    }\n\n    /**\n     * Clear down the cache of buffers by session for reassembling messages.\n     */\n    public void clear()\n    {\n        builderBySessionIdMap.clear();\n    }\n\n    private BufferBuilder getBufferBuilder(final int sessionId)\n    {\n        BufferBuilder bufferBuilder = builderBySessionIdMap.get(sessionId);\n\n        if (null == bufferBuilder)\n        {\n            bufferBuilder = new BufferBuilder(initialBufferLength, isDirectByteBuffer);\n            builderBySessionIdMap.put(sessionId, bufferBuilder);\n        }\n\n        return bufferBuilder;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/Image.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.BlockHandler;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.ControlledFragmentHandler.Action;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.logbuffer.RawBlockHandler;\nimport io.aeron.logbuffer.TermBlockScanner;\nimport org.agrona.BitUtil;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.Position;\n\nimport java.nio.channels.FileChannel;\n\nimport static io.aeron.logbuffer.ControlledFragmentHandler.Action.*;\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.DataHeaderFlyweight.TERM_ID_FIELD_OFFSET;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * Represents a replicated {@link Publication} from a which matches a {@link Subscription}.\n * Each {@link Image} identifies a source {@link Publication} by {@link #sessionId()}.\n * <p>\n * By default, fragmented messages are not reassembled before delivery. If an application must\n * receive whole messages, whether they were fragmented or not, then the Subscriber\n * should be created with a {@link FragmentAssembler} or a custom implementation.\n * <p>\n * It is an application's responsibility to {@link #poll} the {@link Image} for new messages.\n * <p>\n * <b>Note:</b>Images are not threadsafe and should not be shared between subscribers.\n */\npublic final class Image\n{\n    private final long correlationId;\n    private final long joinPosition;\n    private final int sessionId;\n    private final int initialTermId;\n    private final int termLengthMask;\n    private final int positionBitsToShift;\n    private final int mtu;\n\n    private long finalPosition;\n    private long eosPosition = Long.MAX_VALUE;\n    private boolean isEos;\n    private boolean isRevoked;\n    private volatile boolean isClosed;\n\n    private final Position subscriberPosition;\n    private final UnsafeBuffer[] termBuffers;\n    private final Header header;\n    private final ErrorHandler errorHandler;\n    private final LogBuffers logBuffers;\n    private final String sourceIdentity;\n    private final Subscription subscription;\n\n    /**\n     * Construct a new image over a log to represent a stream of messages from a {@link Publication}.\n     *\n     * @param subscription       to which this {@link Image} belongs.\n     * @param sessionId          of the stream of messages.\n     * @param subscriberPosition for indicating the position of the subscriber in the stream.\n     * @param logBuffers         containing the stream of messages.\n     * @param errorHandler       to be called if an error occurs when polling for messages.\n     * @param sourceIdentity     of the source sending the stream of messages.\n     * @param correlationId      of the request to the media driver.\n     */\n    public Image(\n        final Subscription subscription,\n        final int sessionId,\n        final Position subscriberPosition,\n        final LogBuffers logBuffers,\n        final ErrorHandler errorHandler,\n        final String sourceIdentity,\n        final long correlationId)\n    {\n        this.subscription = subscription;\n        this.sessionId = sessionId;\n        this.subscriberPosition = subscriberPosition;\n        this.logBuffers = logBuffers;\n        this.errorHandler = errorHandler;\n        this.sourceIdentity = sourceIdentity;\n        this.correlationId = correlationId;\n        this.joinPosition = subscriberPosition.get();\n\n        termBuffers = logBuffers.duplicateTermBuffers();\n\n        final int termLength = logBuffers.termLength();\n        termLengthMask = termLength - 1;\n        positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n        initialTermId = LogBufferDescriptor.initialTermId(logBuffers.metaDataBuffer());\n        mtu = LogBufferDescriptor.mtuLength(logBuffers.metaDataBuffer());\n        header = new Header(initialTermId, positionBitsToShift, this);\n    }\n\n    /**\n     * Number of bits to right shift a position to get a term count for how far the stream has progressed.\n     *\n     * @return of bits to right shift a position to get a term count for how far the stream has progressed.\n     */\n    public int positionBitsToShift()\n    {\n        return positionBitsToShift;\n    }\n\n    /**\n     * Get the length in bytes for each term partition in the log buffer.\n     *\n     * @return the length in bytes for each term partition in the log buffer.\n     */\n    public int termBufferLength()\n    {\n        return termLengthMask + 1;\n    }\n\n    /**\n     * The sessionId for the steam of messages. Sessions are unique within a {@link Subscription} and unique across\n     * all {@link Publication}s from a {@link #sourceIdentity()}.\n     *\n     * @return the sessionId for the steam of messages.\n     */\n    public int sessionId()\n    {\n        return sessionId;\n    }\n\n    /**\n     * The source identity of the sending publisher as an abstract concept appropriate for the media.\n     *\n     * @return source identity of the sending publisher as an abstract concept appropriate for the media.\n     */\n    public String sourceIdentity()\n    {\n        return sourceIdentity;\n    }\n\n    /**\n     * The length in bytes of the MTU (Maximum Transmission Unit) the Sender used for the datagram.\n     *\n     * @return length in bytes of the MTU (Maximum Transmission Unit) the Sender used for the datagram.\n     */\n    public int mtuLength()\n    {\n        return mtu;\n    }\n\n    /**\n     * The initial term at which the stream started for this session.\n     *\n     * @return the initial term id.\n     */\n    public int initialTermId()\n    {\n        return initialTermId;\n    }\n\n    /**\n     * The correlationId for identification of the image with the media driver.\n     *\n     * @return the correlationId for identification of the image with the media driver.\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * Get the {@link Subscription} to which this {@link Image} belongs.\n     *\n     * @return the {@link Subscription} to which this {@link Image} belongs.\n     */\n    public Subscription subscription()\n    {\n        return subscription;\n    }\n\n    /**\n     * Has this object been closed and should no longer be used?\n     *\n     * @return true if it has been closed otherwise false.\n     */\n    public boolean isClosed()\n    {\n        return isClosed;\n    }\n\n    /**\n     * Get the position the subscriber joined this stream at.\n     *\n     * @return the position the subscriber joined this stream at.\n     */\n    public long joinPosition()\n    {\n        return joinPosition;\n    }\n\n    /**\n     * The position this {@link Image} has been consumed to by the subscriber.\n     *\n     * @return the position this {@link Image} has been consumed to by the subscriber.\n     */\n    public long position()\n    {\n        if (isClosed)\n        {\n            return finalPosition;\n        }\n\n        return subscriberPosition.get();\n    }\n\n    /**\n     * Set the subscriber position for this {@link Image} to indicate where it has been consumed to.\n     *\n     * @param newPosition for the consumption point.\n     */\n    public void position(final long newPosition)\n    {\n        if (!isClosed)\n        {\n            validatePosition(newPosition);\n            subscriberPosition.setRelease(newPosition);\n        }\n    }\n\n    /**\n     * The counter id for the subscriber position counter.\n     *\n     * @return the id for the subscriber position counter.\n     */\n    public int subscriberPositionId()\n    {\n        return subscriberPosition.id();\n    }\n\n    /**\n     * Is the current consumed position at the end of the stream?\n     *\n     * @return true if at the end of the stream or false if not.\n     */\n    public boolean isEndOfStream()\n    {\n        if (isClosed)\n        {\n            return isEos;\n        }\n\n        return subscriberPosition.get() >= LogBufferDescriptor.endOfStreamPosition(logBuffers.metaDataBuffer());\n    }\n\n    /**\n     * The position the stream reached when EOS was received from the publisher. The position will be\n     * {@link Long#MAX_VALUE} until the stream ends and EOS is set.\n     *\n     * @return position the stream reached when EOS was received from the publisher.\n     */\n    public long endOfStreamPosition()\n    {\n        if (isClosed)\n        {\n            return eosPosition;\n        }\n\n        return LogBufferDescriptor.endOfStreamPosition(logBuffers.metaDataBuffer());\n    }\n\n    /**\n     * Count of observed active transports within the image liveness timeout.\n     * <p>\n     * If the image is closed, then this is 0. This may also be 0 if no actual datagrams have arrived. IPC\n     * Images also will be 0.\n     *\n     * @return count of active transports - 0 if Image is closed, no datagrams yet, or IPC.\n     */\n    public int activeTransportCount()\n    {\n        if (isClosed)\n        {\n            return 0;\n        }\n\n        return LogBufferDescriptor.activeTransportCount(logBuffers.metaDataBuffer());\n    }\n\n    /**\n     * Has the associated publication been revoked?\n     *\n     * @return true if the associated publication was revoked otherwise false.\n     */\n    public boolean isPublicationRevoked()\n    {\n        if (isClosed)\n        {\n            return isRevoked;\n        }\n\n        return LogBufferDescriptor.isPublicationRevoked(logBuffers.metaDataBuffer());\n    }\n\n    /**\n     * The {@link FileChannel} to the raw log of the Image.\n     *\n     * @return the {@link FileChannel} to the raw log of the Image.\n     */\n    public FileChannel fileChannel()\n    {\n        return logBuffers.fileChannel();\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered to the {@link FragmentHandler} up to a limited number of fragments as specified.\n     * <p>\n     * Use a {@link FragmentAssembler} to assemble messages which span multiple fragments.\n     *\n     * @param fragmentHandler to which message fragments are delivered.\n     * @param fragmentLimit   for the number of fragments to be consumed during one polling operation.\n     * @return the number of fragments that have been consumed.\n     * @see FragmentAssembler\n     * @see ImageFragmentAssembler\n     */\n    public int poll(final FragmentHandler fragmentHandler, final int fragmentLimit)\n    {\n        if (isClosed)\n        {\n            return 0;\n        }\n\n        int fragmentsRead = 0;\n        final long initialPosition = subscriberPosition.get();\n        final int initialOffset = (int)initialPosition & termLengthMask;\n        int offset = initialOffset;\n        final UnsafeBuffer termBuffer = activeTermBuffer(initialPosition);\n        final int capacity = termBuffer.capacity();\n        final Header header = this.header;\n        header.buffer(termBuffer);\n\n        try\n        {\n            while (fragmentsRead < fragmentLimit && offset < capacity && !isClosed)\n            {\n                final int frameLength = frameLengthVolatile(termBuffer, offset);\n                if (frameLength <= 0)\n                {\n                    break;\n                }\n\n                final int frameOffset = offset;\n                offset += BitUtil.align(frameLength, FRAME_ALIGNMENT);\n\n                if (!isPaddingFrame(termBuffer, frameOffset))\n                {\n                    ++fragmentsRead;\n                    header.offset(frameOffset);\n                    fragmentHandler.onFragment(\n                        termBuffer, frameOffset + HEADER_LENGTH, frameLength - HEADER_LENGTH, header);\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n        finally\n        {\n            final long newPosition = initialPosition + (offset - initialOffset);\n            if (newPosition > initialPosition && !isClosed)\n            {\n                subscriberPosition.setRelease(newPosition);\n            }\n        }\n\n        return fragmentsRead;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered to the {@link ControlledFragmentHandler} up to a limited number of fragments as specified.\n     * <p>\n     * Use a {@link ControlledFragmentAssembler} to assemble messages which span multiple fragments.\n     *\n     * @param handler       to which message fragments are delivered.\n     * @param fragmentLimit for the number of fragments to be consumed during one polling operation.\n     * @return the number of fragments that have been consumed.\n     * @see ControlledFragmentAssembler\n     * @see ImageControlledFragmentAssembler\n     */\n    public int controlledPoll(final ControlledFragmentHandler handler, final int fragmentLimit)\n    {\n        if (isClosed)\n        {\n            return 0;\n        }\n\n        int fragmentsRead = 0;\n        long initialPosition = subscriberPosition.get();\n        int initialOffset = (int)initialPosition & termLengthMask;\n        int offset = initialOffset;\n        final UnsafeBuffer termBuffer = activeTermBuffer(initialPosition);\n        final int capacity = termBuffer.capacity();\n        final Header header = this.header;\n        header.buffer(termBuffer);\n\n        try\n        {\n            while (fragmentsRead < fragmentLimit && offset < capacity && !isClosed)\n            {\n                final int length = frameLengthVolatile(termBuffer, offset);\n                if (length <= 0)\n                {\n                    break;\n                }\n\n                final int frameOffset = offset;\n                final int alignedLength = BitUtil.align(length, FRAME_ALIGNMENT);\n                offset += alignedLength;\n\n                if (isPaddingFrame(termBuffer, frameOffset))\n                {\n                    continue;\n                }\n\n                ++fragmentsRead;\n                header.offset(frameOffset);\n\n                final Action action = handler.onFragment(\n                    termBuffer, frameOffset + HEADER_LENGTH, length - HEADER_LENGTH, header);\n\n                if (ABORT == action)\n                {\n                    --fragmentsRead;\n                    offset -= alignedLength;\n                    break;\n                }\n\n                if (BREAK == action)\n                {\n                    break;\n                }\n\n                if (COMMIT == action)\n                {\n                    initialPosition += (offset - initialOffset);\n                    initialOffset = offset;\n                    if (!isClosed)\n                    {\n                        subscriberPosition.setRelease(initialPosition);\n                    }\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n        finally\n        {\n            final long resultingPosition = initialPosition + (offset - initialOffset);\n            if (resultingPosition > initialPosition && !isClosed)\n            {\n                subscriberPosition.setRelease(resultingPosition);\n            }\n        }\n\n        return fragmentsRead;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered to the {@link FragmentHandler} up to a limited number of fragments as specified or\n     * the maximum position specified.\n     * <p>\n     * Use a {@link FragmentAssembler} to assemble messages which span multiple fragments.\n     *\n     * @param handler       to which message fragments are delivered.\n     * @param limitPosition to consume messages up to.\n     * @param fragmentLimit for the number of fragments to be consumed during one polling operation.\n     * @return the number of fragments that have been consumed.\n     * @see FragmentAssembler\n     * @see ImageFragmentAssembler\n     */\n    public int boundedPoll(final FragmentHandler handler, final long limitPosition, final int fragmentLimit)\n    {\n        if (isClosed)\n        {\n            return 0;\n        }\n\n        final long initialPosition = subscriberPosition.get();\n        if (initialPosition >= limitPosition)\n        {\n            return 0;\n        }\n\n        int fragmentsRead = 0;\n        final int initialOffset = (int)initialPosition & termLengthMask;\n        int offset = initialOffset;\n        final UnsafeBuffer termBuffer = activeTermBuffer(initialPosition);\n        final int limitOffset = (int)Math.min(termBuffer.capacity(), (limitPosition - initialPosition) + offset);\n        final Header header = this.header;\n        header.buffer(termBuffer);\n\n        try\n        {\n            while (fragmentsRead < fragmentLimit && offset < limitOffset && !isClosed)\n            {\n                final int length = frameLengthVolatile(termBuffer, offset);\n                if (length <= 0)\n                {\n                    break;\n                }\n\n                final int frameOffset = offset;\n                final int alignedLength = BitUtil.align(length, FRAME_ALIGNMENT);\n                offset += alignedLength;\n\n                if (isPaddingFrame(termBuffer, frameOffset))\n                {\n                    continue;\n                }\n\n                ++fragmentsRead;\n                header.offset(frameOffset);\n                handler.onFragment(termBuffer, frameOffset + HEADER_LENGTH, length - HEADER_LENGTH, header);\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n        finally\n        {\n            final long resultingPosition = initialPosition + (offset - initialOffset);\n            if (resultingPosition > initialPosition && !isClosed)\n            {\n                subscriberPosition.setRelease(resultingPosition);\n            }\n        }\n\n        return fragmentsRead;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered to the {@link ControlledFragmentHandler} up to a limited number of fragments as specified or\n     * the maximum position specified.\n     * <p>\n     * Use a {@link ControlledFragmentAssembler} to assemble messages which span multiple fragments.\n     *\n     * @param handler       to which message fragments are delivered.\n     * @param limitPosition to consume messages up to.\n     * @param fragmentLimit for the number of fragments to be consumed during one polling operation.\n     * @return the number of fragments that have been consumed.\n     * @see ControlledFragmentAssembler\n     * @see ImageControlledFragmentAssembler\n     */\n    public int boundedControlledPoll(\n        final ControlledFragmentHandler handler, final long limitPosition, final int fragmentLimit)\n    {\n        if (isClosed)\n        {\n            return 0;\n        }\n\n        final Position subscriberPosition = this.subscriberPosition;\n        long initialPosition = subscriberPosition.get();\n        if (initialPosition >= limitPosition)\n        {\n            return 0;\n        }\n\n        int fragmentsRead = 0;\n        int initialOffset = (int)initialPosition & termLengthMask;\n        int offset = initialOffset;\n        final UnsafeBuffer termBuffer = activeTermBuffer(initialPosition);\n        final int limitOffset = (int)Math.min(termBuffer.capacity(), (limitPosition - initialPosition) + offset);\n        final Header header = this.header;\n        header.buffer(termBuffer);\n\n        try\n        {\n            while (fragmentsRead < fragmentLimit && offset < limitOffset && !isClosed)\n            {\n                final int length = frameLengthVolatile(termBuffer, offset);\n                if (length <= 0)\n                {\n                    break;\n                }\n\n                final int frameOffset = offset;\n                final int alignedLength = BitUtil.align(length, FRAME_ALIGNMENT);\n                offset += alignedLength;\n\n                if (isPaddingFrame(termBuffer, frameOffset))\n                {\n                    continue;\n                }\n\n                ++fragmentsRead;\n                header.offset(frameOffset);\n\n                final Action action = handler.onFragment(\n                    termBuffer, frameOffset + HEADER_LENGTH, length - HEADER_LENGTH, header);\n\n                if (ABORT == action)\n                {\n                    --fragmentsRead;\n                    offset -= alignedLength;\n                    break;\n                }\n\n                if (BREAK == action)\n                {\n                    break;\n                }\n\n                if (COMMIT == action)\n                {\n                    initialPosition += (offset - initialOffset);\n                    initialOffset = offset;\n                    if (!isClosed)\n                    {\n                        subscriberPosition.setRelease(initialPosition);\n                    }\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n        finally\n        {\n            final long resultingPosition = initialPosition + (offset - initialOffset);\n            if (resultingPosition > initialPosition && !isClosed)\n            {\n                subscriberPosition.setRelease(resultingPosition);\n            }\n        }\n\n        return fragmentsRead;\n    }\n\n    /**\n     * Peek for new messages in a stream by scanning forward from an initial position. If new messages are found then\n     * they will be delivered to the {@link ControlledFragmentHandler} up to a limited position.\n     * <p>\n     * Use a {@link ControlledFragmentAssembler} to assemble messages which span multiple fragments. Scans must also\n     * start at the beginning of a message so that the assembler is reset.\n     *\n     * @param initialPosition from which to peek forward.\n     * @param handler         to which message fragments are delivered.\n     * @param limitPosition   up to which can be scanned.\n     * @return the resulting position after the scan terminates which is a complete message.\n     * @see ControlledFragmentAssembler\n     * @see ImageControlledFragmentAssembler\n     */\n    public long controlledPeek(\n        final long initialPosition, final ControlledFragmentHandler handler, final long limitPosition)\n    {\n        if (isClosed)\n        {\n            return initialPosition;\n        }\n\n        validatePosition(initialPosition);\n        if (initialPosition >= limitPosition)\n        {\n            return initialPosition;\n        }\n\n        int initialOffset = (int)initialPosition & termLengthMask;\n        int offset = initialOffset;\n        long position = initialPosition;\n        final UnsafeBuffer termBuffer = activeTermBuffer(initialPosition);\n        final Header header = this.header;\n        final int limitOffset = (int)Math.min(termBuffer.capacity(), (limitPosition - initialPosition) + offset);\n        header.buffer(termBuffer);\n        long resultingPosition = initialPosition;\n\n        try\n        {\n            while (offset < limitOffset && !isClosed)\n            {\n                final int length = frameLengthVolatile(termBuffer, offset);\n                if (length <= 0)\n                {\n                    break;\n                }\n\n                final int frameOffset = offset;\n                offset += BitUtil.align(length, FRAME_ALIGNMENT);\n\n                if (isPaddingFrame(termBuffer, frameOffset))\n                {\n                    position += (offset - initialOffset);\n                    initialOffset = offset;\n                    resultingPosition = position;\n\n                    continue;\n                }\n\n                header.offset(frameOffset);\n\n                final Action action = handler.onFragment(\n                    termBuffer, frameOffset + HEADER_LENGTH, length - HEADER_LENGTH, header);\n\n                if (ABORT == action)\n                {\n                    break;\n                }\n\n                position += (offset - initialOffset);\n                initialOffset = offset;\n\n                if ((header.flags() & END_FRAG_FLAG) == END_FRAG_FLAG)\n                {\n                    resultingPosition = position;\n                }\n\n                if (BREAK == action)\n                {\n                    break;\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n\n        return resultingPosition;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered to the {@link BlockHandler} up to a limited number of bytes.\n     * <p>\n     * A scan will terminate if a padding frame is encountered. If first frame in a scan is padding then a block\n     * for the padding is notified. If the padding comes after the first frame in a scan then the scan terminates\n     * at the offset the padding frame begins. Padding frames are delivered singularly in a block.\n     * <p>\n     * Padding frames may be for a greater range than the limit offset but only the header needs to be valid so\n     * relevant length of the frame is {@link io.aeron.protocol.DataHeaderFlyweight#HEADER_LENGTH}.\n     *\n     * @param handler          to which block is delivered.\n     * @param blockLengthLimit up to which a block may be in length.\n     * @return the number of bytes that have been consumed.\n     */\n    public int blockPoll(final BlockHandler handler, final int blockLengthLimit)\n    {\n        if (isClosed)\n        {\n            return 0;\n        }\n\n        final long position = subscriberPosition.get();\n        final int offset = (int)position & termLengthMask;\n        final int limitOffset = Math.min(offset + blockLengthLimit, termLengthMask + 1);\n        final UnsafeBuffer termBuffer = activeTermBuffer(position);\n        final int resultingOffset = TermBlockScanner.scan(termBuffer, offset, limitOffset);\n        final int length = resultingOffset - offset;\n\n        if (resultingOffset > offset)\n        {\n            try\n            {\n                final int termId = termBuffer.getInt(offset + TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n                handler.onBlock(termBuffer, offset, length, sessionId, termId);\n            }\n            catch (final Exception ex)\n            {\n                errorHandler.onError(ex);\n            }\n            finally\n            {\n                if (!isClosed)\n                {\n                    subscriberPosition.setRelease(position + length);\n                }\n            }\n        }\n\n        return length;\n    }\n\n    /**\n     * Poll for new messages in a stream. If new messages are found beyond the last consumed position then they\n     * will be delivered to the {@link RawBlockHandler} up to a limited number of bytes.\n     * <p>\n     * This method is useful for operations like bulk archiving a stream to file.\n     * <p>\n     * A scan will terminate if a padding frame is encountered. If first frame in a scan is padding then a block\n     * for the padding is notified. If the padding comes after the first frame in a scan then the scan terminates\n     * at the offset the padding frame begins. Padding frames are delivered singularly in a block.\n     * <p>\n     * Padding frames may be for a greater range than the limit offset but only the header needs to be valid so\n     * relevant length of the frame is {@link io.aeron.protocol.DataHeaderFlyweight#HEADER_LENGTH}.\n     *\n     * @param handler          to which block is delivered.\n     * @param blockLengthLimit up to which a block may be in length.\n     * @return the number of bytes that have been consumed.\n     */\n    public int rawPoll(final RawBlockHandler handler, final int blockLengthLimit)\n    {\n        if (isClosed)\n        {\n            return 0;\n        }\n\n        final long position = subscriberPosition.get();\n        final int offset = (int)position & termLengthMask;\n        final int activeIndex = LogBufferDescriptor.indexByPosition(position, positionBitsToShift);\n        final UnsafeBuffer termBuffer = termBuffers[activeIndex];\n        final int capacity = termBuffer.capacity();\n        final int limitOffset = Math.min(offset + blockLengthLimit, capacity);\n        final int resultingOffset = TermBlockScanner.scan(termBuffer, offset, limitOffset);\n        final int length = resultingOffset - offset;\n\n        if (resultingOffset > offset)\n        {\n            try\n            {\n                final long fileOffset = ((long)capacity * activeIndex) + offset;\n                final int termId = termBuffer.getInt(offset + TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n\n                handler.onBlock(logBuffers.fileChannel(), fileOffset, termBuffer, offset, length, sessionId, termId);\n            }\n            catch (final Exception ex)\n            {\n                errorHandler.onError(ex);\n            }\n            finally\n            {\n                if (!isClosed)\n                {\n                    subscriberPosition.setRelease(position + length);\n                }\n            }\n        }\n\n        return length;\n    }\n\n    /**\n     * Reject this image.\n     *\n     * @param reason a String indicating the reason why this image is being rejected.\n     */\n    public void reject(final String reason)\n    {\n        subscription.rejectImage(correlationId, position(), reason);\n    }\n\n    private UnsafeBuffer activeTermBuffer(final long position)\n    {\n        return termBuffers[LogBufferDescriptor.indexByPosition(position, positionBitsToShift)];\n    }\n\n    private void validatePosition(final long position)\n    {\n        final long currentPosition = subscriberPosition.get();\n        final long limitPosition = (currentPosition - (currentPosition & termLengthMask)) + termLengthMask + 1;\n        if (position < currentPosition || position > limitPosition)\n        {\n            throw new IllegalArgumentException(\n                position + \" position out of range: \" + currentPosition + \"-\" + limitPosition);\n        }\n\n        if (0 != (position & (FRAME_ALIGNMENT - 1)))\n        {\n            throw new IllegalArgumentException(position + \" position not aligned to FRAME_ALIGNMENT\");\n        }\n    }\n\n    LogBuffers logBuffers()\n    {\n        return logBuffers;\n    }\n\n    void close()\n    {\n        finalPosition = subscriberPosition.getVolatile();\n        eosPosition = LogBufferDescriptor.endOfStreamPosition(logBuffers.metaDataBuffer());\n        isEos = finalPosition >= eosPosition;\n        isRevoked = LogBufferDescriptor.isPublicationRevoked(logBuffers.metaDataBuffer());\n        isClosed = true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"Image{\" +\n            \"correlationId=\" + correlationId +\n            \", sessionId=\" + sessionId +\n            \", isClosed=\" + isClosed +\n            \", isEos=\" + isEndOfStream() +\n            \", initialTermId=\" + initialTermId +\n            \", termLength=\" + termBufferLength() +\n            \", joinPosition=\" + joinPosition +\n            \", position=\" + position() +\n            \", endOfStreamPosition=\" + endOfStreamPosition() +\n            \", activeTransportCount=\" + activeTransportCount() +\n            \", sourceIdentity='\" + sourceIdentity + '\\'' +\n            \", subscription=\" + subscription +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ImageControlledFragmentAssembler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\n\n/**\n * A {@link ControlledFragmentHandler} that sits in a chain-of-responsibility pattern that reassembles fragmented\n * messages so that the next handler in the chain only sees whole messages. This is for a single session on an\n * {@link Image} and not for multiple session {@link Image}s in a {@link Subscription}.\n * <p>\n * Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary\n * buffer for reassembly before delegation.\n * <p>\n * The {@link Header} passed to the delegate on assembling a message will be that of the last fragment.\n *\n * @see Image#controlledPoll(ControlledFragmentHandler, int)\n * @see Image#controlledPeek(long, ControlledFragmentHandler, long)\n */\npublic class ImageControlledFragmentAssembler implements ControlledFragmentHandler\n{\n    private final ControlledFragmentHandler delegate;\n    private final BufferBuilder builder;\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on whole messages.\n     *\n     * @param delegate onto which whole messages are forwarded.\n     */\n    public ImageControlledFragmentAssembler(final ControlledFragmentHandler delegate)\n    {\n        this(delegate, 0, false);\n    }\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for the session.\n     */\n    public ImageControlledFragmentAssembler(final ControlledFragmentHandler delegate, final int initialBufferLength)\n    {\n        this(delegate, initialBufferLength, false);\n    }\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for the session.\n     * @param isDirectByteBuffer  is the underlying buffer to be a direct {@link java.nio.ByteBuffer}?\n     */\n    public ImageControlledFragmentAssembler(\n        final ControlledFragmentHandler delegate, final int initialBufferLength, final boolean isDirectByteBuffer)\n    {\n        this.delegate = delegate;\n        this.builder = new BufferBuilder(initialBufferLength, isDirectByteBuffer);\n    }\n\n    /**\n     * Get the delegate unto which assembled messages are delegated.\n     *\n     * @return the delegate unto which assembled messages are delegated.\n     */\n    public ControlledFragmentHandler delegate()\n    {\n        return delegate;\n    }\n\n    /**\n     * Get the {@link BufferBuilder} for resetting this assembler.\n     *\n     * @return the {@link BufferBuilder} for resetting this assembler.\n     */\n    BufferBuilder bufferBuilder()\n    {\n        return builder;\n    }\n\n    /**\n     * The implementation of {@link ControlledFragmentHandler} that reassembles and forwards whole messages.\n     *\n     * @param buffer containing the data.\n     * @param offset at which the data begins.\n     * @param length of the data in bytes.\n     * @param header representing the metadata for the data.\n     * @return {@link io.aeron.logbuffer.ControlledFragmentHandler.Action} to be taken after processing fragment.\n     */\n    public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        Action action = Action.CONTINUE;\n        final byte flags = header.flags();\n\n        if ((flags & UNFRAGMENTED) == UNFRAGMENTED)\n        {\n            action = delegate.onFragment(buffer, offset, length, header);\n        }\n        else if ((flags & BEGIN_FRAG_FLAG) == BEGIN_FRAG_FLAG)\n        {\n            builder.reset()\n                .captureHeader(header)\n                .append(buffer, offset, length)\n                .nextTermOffset(header.nextTermOffset());\n        }\n        else if (header.termOffset() == builder.nextTermOffset())\n        {\n            final int limit = builder.limit();\n\n            builder.append(buffer, offset, length);\n\n            if ((flags & END_FRAG_FLAG) == END_FRAG_FLAG)\n            {\n                action = delegate.onFragment(\n                    builder.buffer(), 0, builder.limit(), builder.completeHeader(header));\n\n                if (Action.ABORT == action)\n                {\n                    builder.limit(limit);\n                }\n                else\n                {\n                    builder.reset();\n                }\n            }\n            else\n            {\n                builder.nextTermOffset(header.nextTermOffset());\n            }\n        }\n        else\n        {\n            builder.reset();\n        }\n\n        return action;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ImageFragmentAssembler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\n\n/**\n * A {@link FragmentHandler} that sits in a chain-of-responsibility pattern that reassembles fragmented messages\n * so that the next handler in the chain only sees whole messages. This is for a single session on an {@link Image}\n * and not for multiple session {@link Image}s in a {@link Subscription}.\n * <p>\n * Unfragmented messages are delegated without copy. Fragmented messages are copied to a temporary\n * buffer for reassembly before delegation.\n * <p>\n * The {@link Header} passed to the delegate on assembling a message will be that of the last fragment.\n */\npublic class ImageFragmentAssembler implements FragmentHandler\n{\n    private final FragmentHandler delegate;\n    private final BufferBuilder builder;\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on only whole messages.\n     *\n     * @param delegate onto which whole messages are forwarded.\n     */\n    public ImageFragmentAssembler(final FragmentHandler delegate)\n    {\n        this(delegate, 0, false);\n    }\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on only whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for the session.\n     */\n    public ImageFragmentAssembler(final FragmentHandler delegate, final int initialBufferLength)\n    {\n        this.delegate = delegate;\n        this.builder = new BufferBuilder(initialBufferLength, false);\n    }\n\n    /**\n     * Construct an adapter to reassemble message fragments and delegate on only whole messages.\n     *\n     * @param delegate            onto which whole messages are forwarded.\n     * @param initialBufferLength to be used for the session.\n     * @param isDirectByteBuffer  is the underlying buffer to be a direct {@link java.nio.ByteBuffer}?\n     */\n    public ImageFragmentAssembler(\n        final FragmentHandler delegate, final int initialBufferLength, final boolean isDirectByteBuffer)\n    {\n        this.delegate = delegate;\n        this.builder = new BufferBuilder(initialBufferLength, isDirectByteBuffer);\n    }\n\n    /**\n     * Get the delegate unto which assembled messages are delegated.\n     *\n     * @return the delegate unto which assembled messages are delegated.\n     */\n    public FragmentHandler delegate()\n    {\n        return delegate;\n    }\n\n    /**\n     * Get the {@link BufferBuilder} for resetting this assembler.\n     *\n     * @return the {@link BufferBuilder} for resetting this assembler.\n     */\n    public BufferBuilder bufferBuilder()\n    {\n        return builder;\n    }\n\n    /**\n     * The implementation of {@link FragmentHandler} that reassembles and forwards whole messages.\n     *\n     * @param buffer containing the data.\n     * @param offset at which the data begins.\n     * @param length of the data in bytes.\n     * @param header representing the metadata for the data.\n     */\n    public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final byte flags = header.flags();\n\n        if ((flags & UNFRAGMENTED) == UNFRAGMENTED)\n        {\n            delegate.onFragment(buffer, offset, length, header);\n        }\n        else\n        {\n            handleFragment(buffer, offset, length, header, flags);\n        }\n    }\n\n    private void handleFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header, final byte flags)\n    {\n        if ((flags & BEGIN_FRAG_FLAG) == BEGIN_FRAG_FLAG)\n        {\n            builder.reset()\n                .captureHeader(header)\n                .append(buffer, offset, length)\n                .nextTermOffset(header.nextTermOffset());\n        }\n        else if (header.termOffset() == builder.nextTermOffset())\n        {\n            builder.append(buffer, offset, length);\n\n            if ((flags & END_FRAG_FLAG) == END_FRAG_FLAG)\n            {\n                delegate.onFragment(builder.buffer(), 0, builder.limit(), builder.completeHeader(header));\n                builder.reset();\n            }\n            else\n            {\n                builder.nextTermOffset(header.nextTermOffset());\n            }\n        }\n        else\n        {\n            builder.reset();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/LogBuffers.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.BufferUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Paths;\nimport java.nio.file.StandardOpenOption;\nimport java.util.EnumSet;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_META_DATA_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_META_DATA_SECTION_INDEX;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PARTITION_COUNT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MAX_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.checkPageSize;\nimport static io.aeron.logbuffer.LogBufferDescriptor.checkTermLength;\nimport static java.nio.channels.FileChannel.MapMode.READ_WRITE;\nimport static java.nio.file.StandardOpenOption.READ;\nimport static java.nio.file.StandardOpenOption.SPARSE;\nimport static java.nio.file.StandardOpenOption.WRITE;\n\n/**\n * Takes a log file name and maps the file into memory and wraps it with {@link UnsafeBuffer}s as appropriate.\n *\n * @see io.aeron.logbuffer.LogBufferDescriptor\n */\npublic final class LogBuffers implements AutoCloseable\n{\n    private static final EnumSet<StandardOpenOption> FILE_OPTIONS = EnumSet.of(READ, WRITE, SPARSE);\n\n    private long lingerDeadlineNs = Long.MAX_VALUE;\n    private int refCount;\n    private final int termLength;\n    private final FileChannel fileChannel;\n    private final ByteBuffer[] termBuffers = new ByteBuffer[PARTITION_COUNT];\n    private final UnsafeBuffer logMetaDataBuffer;\n    private final MappedByteBuffer[] mappedByteBuffers;\n\n    /**\n     * Construct the log buffers for a given log file.\n     *\n     * @param logFileName to be mapped.\n     */\n    public LogBuffers(final String logFileName)\n    {\n        int termLength = 0;\n        FileChannel fileChannel = null;\n        UnsafeBuffer logMetaDataBuffer = null;\n        MappedByteBuffer[] mappedByteBuffers = null;\n\n        try\n        {\n            fileChannel = FileChannel.open(Paths.get(logFileName), FILE_OPTIONS);\n            final long logLength = fileChannel.size();\n            if (logLength < LOG_META_DATA_LENGTH)\n            {\n                throw new IllegalStateException(\n                    \"Log file length less than min length of \" + LOG_META_DATA_LENGTH + \": length=\" + logLength);\n            }\n\n            if (logLength < Integer.MAX_VALUE)\n            {\n                final MappedByteBuffer mappedBuffer = fileChannel.map(READ_WRITE, 0, logLength);\n                mappedBuffer.order(ByteOrder.LITTLE_ENDIAN);\n                mappedByteBuffers = new MappedByteBuffer[]{ mappedBuffer };\n\n                logMetaDataBuffer = new UnsafeBuffer(\n                    mappedBuffer, (int)(logLength - LOG_META_DATA_LENGTH), LOG_META_DATA_LENGTH);\n\n                termLength = LogBufferDescriptor.termLength(logMetaDataBuffer);\n                final int pageSize = LogBufferDescriptor.pageSize(logMetaDataBuffer);\n\n                checkTermLength(termLength);\n                checkPageSize(pageSize);\n\n                for (int i = 0; i < PARTITION_COUNT; i++)\n                {\n                    final int offset = i * termLength;\n                    mappedBuffer.limit(offset + termLength).position(offset);\n\n                    termBuffers[i] = mappedBuffer.slice();\n                }\n            }\n            else\n            {\n                mappedByteBuffers = new MappedByteBuffer[PARTITION_COUNT + 1];\n\n                final int assumedTermLength = TERM_MAX_LENGTH;\n\n                final long metaDataSectionOffset = assumedTermLength * (long)PARTITION_COUNT;\n                final long metaDataMappingLength = logLength - metaDataSectionOffset;\n\n                final MappedByteBuffer metaDataMappedBuffer = fileChannel.map(\n                    READ_WRITE, metaDataSectionOffset, metaDataMappingLength);\n                metaDataMappedBuffer.order(ByteOrder.LITTLE_ENDIAN);\n\n                mappedByteBuffers[LOG_META_DATA_SECTION_INDEX] = metaDataMappedBuffer;\n\n                logMetaDataBuffer = new UnsafeBuffer(\n                    metaDataMappedBuffer,\n                    (int)metaDataMappingLength - LOG_META_DATA_LENGTH,\n                    LOG_META_DATA_LENGTH);\n\n                final int metaDataTermLength = LogBufferDescriptor.termLength(logMetaDataBuffer);\n                final int pageSize = LogBufferDescriptor.pageSize(logMetaDataBuffer);\n\n                checkPageSize(pageSize);\n                if (metaDataTermLength != assumedTermLength)\n                {\n                    throw new IllegalStateException(\n                        \"assumed term length \" + assumedTermLength +\n                        \" does not match metadata: termLength=\" + metaDataTermLength);\n                }\n\n                termLength = assumedTermLength;\n\n                for (int i = 0; i < PARTITION_COUNT; i++)\n                {\n                    final long position = assumedTermLength * (long)i;\n                    final MappedByteBuffer mappedBuffer = fileChannel.map(READ_WRITE, position, assumedTermLength);\n                    mappedBuffer.order(ByteOrder.LITTLE_ENDIAN);\n                    mappedByteBuffers[i] = mappedBuffer;\n                    termBuffers[i] = mappedBuffer;\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            close(fileChannel, logMetaDataBuffer, mappedByteBuffers);\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        this.termLength = termLength;\n        this.fileChannel = fileChannel;\n        this.logMetaDataBuffer = logMetaDataBuffer;\n        this.mappedByteBuffers = mappedByteBuffers;\n    }\n\n    /**\n     * Duplicate the underlying {@link ByteBuffer}s and wrap them for thread local access.\n     *\n     * @return duplicates of the wrapped underlying {@link ByteBuffer}s.\n     */\n    public UnsafeBuffer[] duplicateTermBuffers()\n    {\n        final UnsafeBuffer[] buffers = new UnsafeBuffer[PARTITION_COUNT];\n\n        for (int i = 0; i < PARTITION_COUNT; i++)\n        {\n            buffers[i] = new UnsafeBuffer(termBuffers[i].duplicate().order(ByteOrder.LITTLE_ENDIAN));\n        }\n\n        return buffers;\n    }\n\n    /**\n     * Get the buffer which holds the log metadata.\n     *\n     * @return the buffer which holds the log metadata.\n     */\n    public UnsafeBuffer metaDataBuffer()\n    {\n        return logMetaDataBuffer;\n    }\n\n    /**\n     * The {@link FileChannel} for the mapped log.\n     *\n     * @return the {@link FileChannel} for the mapped log.\n     */\n    public FileChannel fileChannel()\n    {\n        return fileChannel;\n    }\n\n    /**\n     * Pre touch memory pages, so they are faulted in to be available before access.\n     */\n    public void preTouch()\n    {\n        final int value = 0;\n        final int pageSize = LogBufferDescriptor.pageSize(logMetaDataBuffer);\n        final UnsafeBuffer atomicBuffer = new UnsafeBuffer();\n\n        for (final MappedByteBuffer buffer : mappedByteBuffers)\n        {\n            atomicBuffer.wrap(buffer);\n\n            for (int i = 0, length = atomicBuffer.capacity(); i < length; i += pageSize)\n            {\n                atomicBuffer.compareAndSetInt(i, value, value);\n            }\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        close(fileChannel, logMetaDataBuffer, mappedByteBuffers);\n    }\n\n    /**\n     * The length of the term buffer in each log partition.\n     *\n     * @return length of the term buffer in each log partition.\n     */\n    public int termLength()\n    {\n        return termLength;\n    }\n\n    /**\n     * Increment reference count.\n     *\n     * @return current reference count after increment.\n     */\n    public int incRef()\n    {\n        return ++refCount;\n    }\n\n    /**\n     * Decrement reference count.\n     *\n     * @return current reference counter after decrement.\n     */\n    public int decRef()\n    {\n        return --refCount;\n    }\n\n    /**\n     * Set the deadline for how long to linger around once unreferenced.\n     *\n     * @param timeNs the deadline for how long to linger around once unreferenced.\n     */\n    public void lingerDeadlineNs(final long timeNs)\n    {\n        lingerDeadlineNs = timeNs;\n    }\n\n    /**\n     * The deadline for how long to linger around once unreferenced.\n     *\n     * @return the deadline for how long to linger around once unreferenced.\n     */\n    public long lingerDeadlineNs()\n    {\n        return lingerDeadlineNs;\n    }\n\n    private static void close(\n        final FileChannel fileChannel, final UnsafeBuffer logMetaDataBuffer, final MappedByteBuffer[] mappedByteBuffers)\n    {\n        Throwable error = null;\n        try\n        {\n            CloseHelper.close(fileChannel);\n        }\n        catch (final Exception ex)\n        {\n            error = ex;\n        }\n\n        if (logMetaDataBuffer != null)\n        {\n            logMetaDataBuffer.wrap(0, 0);\n        }\n\n        if (null != mappedByteBuffers)\n        {\n            for (int i = 0, length = mappedByteBuffers.length; i < length; i++)\n            {\n                final MappedByteBuffer mappedByteBuffer = mappedByteBuffers[i];\n                mappedByteBuffers[i] = null;\n                BufferUtil.free(mappedByteBuffer);\n            }\n        }\n\n        if (error != null)\n        {\n            LangUtil.rethrowUnchecked(error);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/LogBuffersFactory.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\n\n/**\n * Interface for encapsulating the strategy of mapping {@link LogBuffers} at a giving file location.\n */\ninterface LogBuffersFactory\n{\n    /**\n     * Map a log file into memory and wrap each section with a {@link org.agrona.concurrent.UnsafeBuffer}.\n     *\n     * @param logFileName to be mapped into memory.\n     * @return a representation of the mapped log buffer.\n     */\n    LogBuffers map(String logFileName);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/MappedLogBuffersFactory.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\n/**\n * Default factory for mapping log buffers in the client.\n */\nclass MappedLogBuffersFactory implements LogBuffersFactory\n{\n    public LogBuffers map(final String logFileName)\n    {\n        return new LogBuffers(logFileName);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/Publication.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.HeaderWriter;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.ReadablePosition;\n\nimport java.util.List;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.BitUtil.align;\n\n/**\n * Aeron publisher API for sending messages to subscribers of a given channel and streamId pair. {@link Publication}s\n * are created via the {@link Aeron#addPublication(String, int)} {@link Aeron#addExclusivePublication(String, int)}\n * methods, and messages are sent via one of the {@link #offer(DirectBuffer)} methods.\n * <p>\n * The APIs used for tryClaim and offer are non-blocking.\n * <p>\n * <b>Note:</b> All methods are threadsafe except offer and tryClaim for the subclass\n * {@link ExclusivePublication}. In the case of {@link ConcurrentPublication} all methods are threadsafe.\n *\n * @see ConcurrentPublication\n * @see ExclusivePublication\n * @see Aeron#addPublication(String, int)\n * @see Aeron#addExclusivePublication(String, int)\n */\npublic abstract class Publication implements AutoCloseable\n{\n    /**\n     * The publication is not connected to a subscriber, this can be an intermittent state as subscribers come and go.\n     */\n    public static final long NOT_CONNECTED = -1;\n\n    /**\n     * The offer failed due to back pressure from the subscribers preventing further transmission.\n     */\n    public static final long BACK_PRESSURED = -2;\n\n    /**\n     * The offer failed due to an administration action and should be retried.\n     * The action is an operation such as log rotation which is likely to have succeeded by the next retry attempt.\n     */\n    public static final long ADMIN_ACTION = -3;\n\n    /**\n     * The {@link Publication} has been closed and should no longer be used.\n     */\n    public static final long CLOSED = -4;\n\n    /**\n     * The offer failed due to reaching the maximum position of the stream given term buffer length times the total\n     * possible number of terms.\n     * <p>\n     * If this happens then the publication should be closed and a new one added. To make it less likely to happen then\n     * increase the term buffer length.\n     */\n    public static final long MAX_POSITION_EXCEEDED = -5;\n\n    final long originalRegistrationId;\n    final long registrationId;\n    final long maxPossiblePosition;\n    final int channelStatusId;\n    final int streamId;\n    final int sessionId;\n    final int maxMessageLength;\n    final int maxFramedLength;\n    final int initialTermId;\n    final int maxPayloadLength;\n    final int positionBitsToShift;\n    final int termBufferLength;\n    volatile boolean isClosed = false;\n    boolean revokeOnClose = false;\n\n    final ReadablePosition positionLimit;\n    final UnsafeBuffer[] termBuffers;\n    final UnsafeBuffer logMetaDataBuffer;\n    final HeaderWriter headerWriter;\n    final LogBuffers logBuffers;\n    final ClientConductor conductor;\n    final String channel;\n\n    Publication(\n        final ClientConductor clientConductor,\n        final String channel,\n        final int streamId,\n        final int sessionId,\n        final ReadablePosition positionLimit,\n        final int channelStatusId,\n        final LogBuffers logBuffers,\n        final long originalRegistrationId,\n        final long registrationId)\n    {\n        final UnsafeBuffer logMetaDataBuffer = logBuffers.metaDataBuffer();\n        this.termBufferLength = logBuffers.termLength();\n        this.maxMessageLength = FrameDescriptor.computeMaxMessageLength(termBufferLength);\n        this.maxPayloadLength = LogBufferDescriptor.mtuLength(logMetaDataBuffer) - HEADER_LENGTH;\n        this.maxFramedLength = computeFragmentedFrameLength(maxMessageLength, maxPayloadLength);\n        this.maxPossiblePosition = termBufferLength * (1L << 31);\n        this.conductor = clientConductor;\n        this.channel = channel;\n        this.streamId = streamId;\n        this.sessionId = sessionId;\n        this.initialTermId = LogBufferDescriptor.initialTermId(logMetaDataBuffer);\n        this.termBuffers = logBuffers.duplicateTermBuffers();\n        this.logMetaDataBuffer = logMetaDataBuffer;\n        this.logBuffers = logBuffers;\n        this.originalRegistrationId = originalRegistrationId;\n        this.registrationId = registrationId;\n        this.positionLimit = positionLimit;\n        this.channelStatusId = channelStatusId;\n        this.positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termBufferLength);\n        this.headerWriter = HeaderWriter.newInstance(defaultFrameHeader(logMetaDataBuffer));\n\n        for (int i = 0; i < PARTITION_COUNT; i++)\n        {\n            final int tailCounterOffset = TERM_TAIL_COUNTERS_OFFSET + (i * SIZE_OF_LONG);\n            logMetaDataBuffer.boundsCheck(tailCounterOffset, SIZE_OF_LONG);\n        }\n    }\n\n    /**\n     * Number of bits to right shift a position to get a term count for how far the stream has progressed.\n     *\n     * @return of bits to right shift a position to get a term count for how far the stream has progressed.\n     */\n    public int positionBitsToShift()\n    {\n        return positionBitsToShift;\n    }\n\n    /**\n     * Get the length in bytes for each term partition in the log buffer.\n     *\n     * @return the length in bytes for each term partition in the log buffer.\n     */\n    public int termBufferLength()\n    {\n        return termBufferLength;\n    }\n\n    /**\n     * The maximum possible position this stream can reach due to its term buffer length.\n     * <p>\n     * Maximum possible position is term-length times 2^31 in bytes.\n     *\n     * @return the maximum possible position this stream can reach due to it term buffer length.\n     */\n    public long maxPossiblePosition()\n    {\n        return maxPossiblePosition;\n    }\n\n    /**\n     * Media address for delivery to the channel.\n     *\n     * @return Media address for delivery to the channel.\n     */\n    public String channel()\n    {\n        return channel;\n    }\n\n    /**\n     * Stream identity for scoping within the channel media address.\n     *\n     * @return Stream identity for scoping within the channel media address.\n     */\n    public int streamId()\n    {\n        return streamId;\n    }\n\n    /**\n     * Session under which messages are published. Identifies this Publication instance. Sessions are unique across\n     * all active publications on a driver instance.\n     *\n     * @return the session id for this publication.\n     */\n    public int sessionId()\n    {\n        return sessionId;\n    }\n\n    /**\n     * The initial term id assigned when this {@link Publication} was created. This can be used to determine how many\n     * terms have passed since creation.\n     *\n     * @return the initial term id.\n     */\n    public int initialTermId()\n    {\n        return initialTermId;\n    }\n\n    /**\n     * Maximum message length supported in bytes. Messages may be made of multiple fragments if greater than\n     * MTU length.\n     *\n     * @return maximum message length supported in bytes.\n     */\n    public int maxMessageLength()\n    {\n        return maxMessageLength;\n    }\n\n    /**\n     * Maximum length of a message payload that fits within a message fragment.\n     * <p>\n     * This is the MTU length minus the message fragment header length.\n     *\n     * @return maximum message fragment payload length.\n     */\n    public int maxPayloadLength()\n    {\n        return maxPayloadLength;\n    }\n\n    /**\n     * Get the registration used to register this Publication with the media driver by the first publisher.\n     *\n     * @return original registration id\n     */\n    public long originalRegistrationId()\n    {\n        return originalRegistrationId;\n    }\n\n    /**\n     * Is this Publication the original instance added to the driver? If not then it was added after another client\n     * has already added the publication.\n     *\n     * @return true if this instance is the first added otherwise false.\n     */\n    public boolean isOriginal()\n    {\n        return originalRegistrationId == registrationId;\n    }\n\n    /**\n     * Get the registration id used to register this Publication with the media driver.\n     * <p>\n     * If this value is different from the {@link #originalRegistrationId()} then a previous active registration exists.\n     *\n     * @return registration id\n     */\n    public long registrationId()\n    {\n        return registrationId;\n    }\n\n    /**\n     * Has the {@link Publication} seen an active Subscriber recently?\n     *\n     * @return true if this {@link Publication} has recently seen an active subscriber otherwise false.\n     */\n    public boolean isConnected()\n    {\n        return !isClosed && LogBufferDescriptor.isConnected(logMetaDataBuffer);\n    }\n\n    /**\n     * Remove resources used by this Publication when there are no more references.\n     * <p>\n     * Publications are reference counted and are only truly closed when the ref count reaches zero.\n     */\n    public void close()\n    {\n        if (!isClosed)\n        {\n            conductor.removePublication(this);\n        }\n    }\n\n    /**\n     * Has this object been closed and should no longer be used?\n     *\n     * @return true if it has been closed otherwise false.\n     */\n    public boolean isClosed()\n    {\n        return isClosed;\n    }\n\n    /**\n     * Get the status of the media channel for this Publication.\n     * <p>\n     * The status will be {@link io.aeron.status.ChannelEndpointStatus#ERRORED} if a socket exception occurs on setup\n     * and {@link io.aeron.status.ChannelEndpointStatus#ACTIVE} if all is well.\n     *\n     * @return status for the channel as one of the constants from {@link ChannelEndpointStatus} with it being\n     * {@link ChannelEndpointStatus#NO_ID_ALLOCATED} if the publication is closed.\n     * @see io.aeron.status.ChannelEndpointStatus\n     */\n    public long channelStatus()\n    {\n        if (isClosed)\n        {\n            return ChannelEndpointStatus.NO_ID_ALLOCATED;\n        }\n\n        return conductor.channelStatus(channelStatusId);\n    }\n\n    /**\n     * Get the counter used to represent the channel status for this publication.\n     *\n     * @return the counter used to represent the channel status for this publication.\n     */\n    public int channelStatusId()\n    {\n        return channelStatusId;\n    }\n\n    /**\n     * Fetches the local socket address for this publication. If the channel is not\n     * {@link io.aeron.status.ChannelEndpointStatus#ACTIVE}, then this will return an empty list.\n     * <p>\n     * The format is as follows:\n     * <br>\n     * <br>\n     * IPv4: <code>ip address:port</code>\n     * <br>\n     * IPv6: <code>[ip6 address]:port</code>\n     * <br>\n     * <br>\n     * This is to match the formatting used in the Aeron URI. For publications this will be the control address and\n     * is likely to only contain a single entry.\n     *\n     * @return local socket addresses for this publication.\n     * @see #channelStatus()\n     */\n    public List<String> localSocketAddresses()\n    {\n        return LocalSocketAddressStatus.findAddresses(conductor.countersReader(), channelStatus(), channelStatusId);\n    }\n\n    /**\n     * Get the current position to which the publication has advanced for this stream.\n     *\n     * @return the current position to which the publication has advanced for this stream or {@link #CLOSED}.\n     */\n    public long position()\n    {\n        if (isClosed)\n        {\n            return CLOSED;\n        }\n\n        final long rawTail = rawTailVolatile(logMetaDataBuffer);\n        final int termOffset = termOffset(rawTail, termBufferLength);\n\n        return computePosition(termId(rawTail), termOffset, positionBitsToShift, initialTermId);\n    }\n\n    /**\n     * Get the position limit beyond which this {@link Publication} will be back pressured.\n     * <p>\n     * This should only be used as a guide to determine when back pressure is likely to be applied.\n     *\n     * @return the position limit beyond which this {@link Publication} will be back pressured.\n     */\n    public long positionLimit()\n    {\n        if (isClosed)\n        {\n            return CLOSED;\n        }\n\n        return positionLimit.getVolatile();\n    }\n\n    /**\n     * Get the counter id for the position limit after which the publication will be back pressured.\n     *\n     * @return the counter id for the position limit after which the publication will be back pressured.\n     */\n    public int positionLimitId()\n    {\n        return positionLimit.id();\n    }\n\n    /**\n     * Available window for offering into a publication before the {@link #positionLimit()} is reached.\n     *\n     * @return window for offering into a publication before the {@link #positionLimit()} is reached. If\n     * the publication is closed then {@link #CLOSED} will be returned.\n     */\n    public abstract long availableWindow();\n\n    /**\n     * Non-blocking publish of a buffer containing a message.\n     *\n     * @param buffer containing message.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public final long offer(final DirectBuffer buffer)\n    {\n        return offer(buffer, 0, buffer.capacity());\n    }\n\n    /**\n     * Non-blocking publish of a partial buffer containing a message.\n     *\n     * @param buffer containing message.\n     * @param offset offset in the buffer at which the encoded message begins.\n     * @param length in bytes of the encoded message.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public final long offer(final DirectBuffer buffer, final int offset, final int length)\n    {\n        return offer(buffer, offset, length, null);\n    }\n\n    /**\n     * Non-blocking publish of a partial buffer containing a message.\n     *\n     * @param buffer                containing message.\n     * @param offset                offset in the buffer at which the encoded message begins.\n     * @param length                in bytes of the encoded message.\n     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public abstract long offer(\n        DirectBuffer buffer, int offset, int length, ReservedValueSupplier reservedValueSupplier);\n\n    /**\n     * Non-blocking publish of a message composed of two parts, e.g. a header and encapsulated payload.\n     *\n     * @param bufferOne containing the first part of the message.\n     * @param offsetOne at which the first part of the message begins.\n     * @param lengthOne of the first part of the message.\n     * @param bufferTwo containing the second part of the message.\n     * @param offsetTwo at which the second part of the message begins.\n     * @param lengthTwo of the second part of the message.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public final long offer(\n        final DirectBuffer bufferOne,\n        final int offsetOne,\n        final int lengthOne,\n        final DirectBuffer bufferTwo,\n        final int offsetTwo,\n        final int lengthTwo)\n    {\n        return offer(bufferOne, offsetOne, lengthOne, bufferTwo, offsetTwo, lengthTwo, null);\n    }\n\n    /**\n     * Non-blocking publish of a message composed of two parts, e.g. a header and encapsulated payload.\n     *\n     * @param bufferOne             containing the first part of the message.\n     * @param offsetOne             at which the first part of the message begins.\n     * @param lengthOne             of the first part of the message.\n     * @param bufferTwo             containing the second part of the message.\n     * @param offsetTwo             at which the second part of the message begins.\n     * @param lengthTwo             of the second part of the message.\n     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public abstract long offer(\n        DirectBuffer bufferOne,\n        int offsetOne,\n        int lengthOne,\n        DirectBuffer bufferTwo,\n        int offsetTwo,\n        int lengthTwo,\n        ReservedValueSupplier reservedValueSupplier);\n\n    /**\n     * Non-blocking publish by gathering buffer vectors into a message.\n     *\n     * @param vectors which make up the message.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public final long offer(final DirectBufferVector[] vectors)\n    {\n        return offer(vectors, null);\n    }\n\n    /**\n     * Non-blocking publish by gathering buffer vectors into a message.\n     *\n     * @param vectors               which make up the message.\n     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     */\n    public abstract long offer(DirectBufferVector[] vectors, ReservedValueSupplier reservedValueSupplier);\n\n    /**\n     * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n     * Once the message has been written then {@link BufferClaim#commit()} should be called thus making it available.\n     * A claim length cannot be greater than {@link #maxPayloadLength()}.\n     * <p>\n     * <b>Note:</b> This method can only be used for message lengths less than MTU length minus header.\n     * If the claim is held for more than the aeron.publication.unblock.timeout system property then the driver will\n     * assume the publication thread is dead and will unblock the claim thus allowing other threads to make progress\n     * for {@link ConcurrentPublication} and other claims to be sent to reach end-of-stream (EOS).\n     * <pre>{@code\n     *     final BufferClaim bufferClaim = new BufferClaim(); // Can be stored and reused to avoid allocation\n     *\n     *     if (publication.tryClaim(messageLength, bufferClaim) > 0L)\n     *     {\n     *         try\n     *         {\n     *              final MutableDirectBuffer buffer = bufferClaim.buffer();\n     *              final int offset = bufferClaim.offset();\n     *\n     *              // Work with buffer directly or wrap with a flyweight\n     *         }\n     *         finally\n     *         {\n     *             bufferClaim.commit();\n     *         }\n     *     }\n     * }</pre>\n     *\n     * @param length      of the range to claim, in bytes.\n     * @param bufferClaim to be populated if the claim succeeds.\n     * @return The new stream position, otherwise a negative error value of {@link #NOT_CONNECTED},\n     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, {@link #CLOSED}, or {@link #MAX_POSITION_EXCEEDED}.\n     * @throws IllegalArgumentException if the length is greater than {@link #maxPayloadLength()} within an MTU.\n     * @see BufferClaim#commit()\n     * @see BufferClaim#abort()\n     */\n    public abstract long tryClaim(int length, BufferClaim bufferClaim);\n\n    /**\n     * Add a destination manually to a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to add.\n     */\n    public void addDestination(final String endpointChannel)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Publication is closed\");\n        }\n\n        conductor.addDestination(originalRegistrationId, endpointChannel);\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication.\n     *\n     * @param endpointChannel for the destination to remove.\n     */\n    public void removeDestination(final String endpointChannel)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Publication is closed\");\n        }\n\n        conductor.removeDestination(originalRegistrationId, endpointChannel);\n    }\n\n    /**\n     * Remove a previously added destination manually from a multi-destination-cast Publication.\n     *\n     * @param registrationId for the destination to remove.\n     */\n    public void removeDestination(final long registrationId)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Publication is closed\");\n        }\n\n        conductor.removeDestination(originalRegistrationId, registrationId);\n    }\n\n    /**\n     * Asynchronously add a destination manually to a multi-destination-cast Publication.\n     * <p>\n     * Errors will be delivered asynchronously to the {@link Aeron.Context#errorHandler()}. Completion can be\n     * tracked by passing the returned correlation id to {@link Aeron#isCommandActive(long)}.\n     *\n     * @param endpointChannel for the destination to add.\n     * @return the correlationId for the command.\n     */\n    public long asyncAddDestination(final String endpointChannel)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Publication is closed\");\n        }\n\n        return conductor.asyncAddDestination(registrationId, endpointChannel);\n    }\n\n    /**\n     * Asynchronously remove a previously added destination from a multi-destination-cast Publication.\n     * <p>\n     * Errors will be delivered asynchronously to the {@link Aeron.Context#errorHandler()}. Completion can be\n     * tracked by passing the returned correlation id to {@link Aeron#isCommandActive(long)}.\n     *\n     * @param endpointChannel for the destination to remove.\n     * @return the correlationId for the command.\n     */\n    public long asyncRemoveDestination(final String endpointChannel)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Publication is closed\");\n        }\n\n        return conductor.asyncRemoveDestination(registrationId, endpointChannel);\n    }\n\n    /**\n     * Asynchronously remove a previously added destination from a multi-destination-cast Publication by registrationId.\n     * <p>\n     * Errors will be delivered asynchronously to the {@link Aeron.Context#errorHandler()}. Completion can be\n     * tracked by passing the returned correlation id to {@link Aeron#isCommandActive(long)}.\n     *\n     * @param destinationRegistrationId for the destination to remove.\n     * @return the correlationId for the command.\n     */\n    public long asyncRemoveDestination(final long destinationRegistrationId)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Publication is closed\");\n        }\n\n        return conductor.asyncRemoveDestination(registrationId, destinationRegistrationId);\n    }\n\n    void internalClose()\n    {\n        isClosed = true;\n    }\n\n    LogBuffers logBuffers()\n    {\n        return logBuffers;\n    }\n\n    final long backPressureStatus(final long currentPosition, final int messageLength)\n    {\n        if ((currentPosition + align(messageLength + HEADER_LENGTH, FRAME_ALIGNMENT)) >= maxPossiblePosition)\n        {\n            return MAX_POSITION_EXCEEDED;\n        }\n\n        if (LogBufferDescriptor.isConnected(logMetaDataBuffer))\n        {\n            return BACK_PRESSURED;\n        }\n\n        return NOT_CONNECTED;\n    }\n\n    final void checkPositiveLength(final int length)\n    {\n        if (length < 0)\n        {\n            throw new IllegalArgumentException(\"invalid length: \" + length);\n        }\n    }\n\n    final void checkPayloadLength(final int length)\n    {\n        if (length < 0)\n        {\n            throw new IllegalArgumentException(\"invalid length: \" + length);\n        }\n\n        if (length > maxPayloadLength)\n        {\n            throw new IllegalArgumentException(\n                \"claim exceeds maxPayloadLength of \" + maxPayloadLength + \", length=\" + length);\n        }\n    }\n\n    final void checkMaxMessageLength(final int length)\n    {\n        if (length > maxMessageLength)\n        {\n            throw new IllegalArgumentException(\n                \"message exceeds maxMessageLength of \" + maxMessageLength + \", length=\" + length);\n        }\n    }\n\n    static int validateAndComputeLength(final int lengthOne, final int lengthTwo)\n    {\n        if (lengthOne < 0)\n        {\n            throw new IllegalArgumentException(\"lengthOne < 0: \" + lengthOne);\n        }\n\n        if (lengthTwo < 0)\n        {\n            throw new IllegalArgumentException(\"lengthTwo < 0: \" + lengthTwo);\n        }\n\n        final int totalLength = lengthOne + lengthTwo;\n        if (totalLength < 0)\n        {\n            throw new IllegalArgumentException(\"overflow totalLength=\" + totalLength);\n        }\n\n        return totalLength;\n    }\n\n    /**\n     * Returns a string representation of a position. Generally used for errors. If the position is a valid error then\n     * String name of the error will be returned. If the value is 0 or greater the text will be \"NONE\". If the position\n     * is negative, but not a known error code then \"UNKNOWN\" will be returned.\n     *\n     * @param position position value returned from a call to offer.\n     * @return String representation of the error.\n     */\n    public static String errorString(final long position)\n    {\n        if (MAX_POSITION_EXCEEDED <= position && position < 0)\n        {\n            final int errorCode = (int)position;\n            switch (errorCode)\n            {\n                case (int)NOT_CONNECTED:\n                    return \"NOT_CONNECTED\";\n                case (int)BACK_PRESSURED:\n                    return \"BACK_PRESSURED\";\n                case (int)ADMIN_ACTION:\n                    return \"ADMIN_ACTION\";\n                case (int)CLOSED:\n                    return \"CLOSED\";\n                case (int)MAX_POSITION_EXCEEDED:\n                    return \"MAX_POSITION_EXCEEDED\";\n                default:\n                    return \"UNKNOWN\";\n            }\n        }\n        else if (0 <= position)\n        {\n            return \"NONE\";\n        }\n        else\n        {\n            return \"UNKNOWN\";\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"Publication{\" +\n            \"originalRegistrationId=\" + originalRegistrationId +\n            \", registrationId=\" + registrationId +\n            \", isClosed=\" + isClosed +\n            \", isConnected=\" + isConnected() +\n            \", initialTermId=\" + initialTermId +\n            \", termBufferLength=\" + termBufferLength +\n            \", sessionId=\" + sessionId +\n            \", streamId=\" + streamId +\n            \", channel='\" + channel + '\\'' +\n            \", position=\" + position() +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/PublicationErrorFrameHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.status.PublicationErrorFrame;\n\n/**\n * Interface for handling various error frame messages for publications.\n * @since 1.47.0\n */\npublic interface PublicationErrorFrameHandler\n{\n    /**\n     * Default no-op error frame handler.\n     */\n    PublicationErrorFrameHandler NO_OP = errorFrame -> {};\n\n    /**\n     * Called when an error frame for a publication is received by the local driver and needs to be propagated to the\n     * appropriate clients. E.g. when an image is invalidated. This callback will reuse the {@link\n     * PublicationErrorFrame} instance, so data is only valid for the lifetime of the callback. If the user needs to\n     * pass the data onto another thread or hold in another location for use later, then the user needs to make use of\n     * the {@link PublicationErrorFrame#clone()} method to create a copy for their own use.\n     * <p>\n     * This callback will be executed on the client conductor thread, similar to image availability notifications.\n     * <p>\n     * This notification will only be propagated to clients that have added an instance of the Publication that received\n     * the error frame (i.e. the originalRegistrationId matches the registrationId on the error frame).\n     *\n     * @param errorFrame containing the relevant information about the publication and the error message.\n     */\n    void onPublicationError(PublicationErrorFrame errorFrame);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/ReservedValueSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.agrona.DirectBuffer;\n\n/**\n * Supplies the reserved value field for a data frame header. The returned value will be set in the header as\n * {@link java.nio.ByteOrder#LITTLE_ENDIAN} format.\n * <p>\n * This will be called as the last action of encoding a data frame right before the length is set. All other fields\n * in the header plus the body of the frame will have been written at the point of supply.\n * <p>\n * The reserved value can be used for carrying out of band data with message fragments such a checksums or timestamps.\n */\n@FunctionalInterface\npublic interface ReservedValueSupplier\n{\n    /**\n     * Callback to provide the reserved value to be encoded with each message fragment as the last action\n     * before the length field is set which commits the fragment for sending to the media.\n     *\n     * @param termBuffer  containing the encoding message fragment.\n     * @param termOffset  at which the header of the frame begins.\n     * @param frameLength Total length of the frame including header.\n     * @return the value to be used for storing in the reserved value field.\n     */\n    long get(DirectBuffer termBuffer, int termOffset, int frameLength);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/RethrowingErrorHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.agrona.ErrorHandler;\nimport org.agrona.LangUtil;\n\n/**\n * Error handler that will rethrow a {@link Throwable} as an unchecked exception.\n */\npublic final class RethrowingErrorHandler implements ErrorHandler\n{\n    /**\n     * Default constructor.\n     */\n    public RethrowingErrorHandler()\n    {\n    }\n\n    /**\n     * Singleton instance to avoid allocation.\n     */\n    public static final RethrowingErrorHandler INSTANCE = new RethrowingErrorHandler();\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onError(final Throwable ex)\n    {\n        LangUtil.rethrowUnchecked(ex);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/Subscription.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.logbuffer.BlockHandler;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.RawBlockHandler;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport org.agrona.collections.ArrayUtil;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_MANUAL;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_PARAM_NAME;\nimport static io.aeron.CommonContext.SPY_PREFIX;\n\nabstract class SubscriptionLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nabstract class SubscriptionFields extends SubscriptionLhsPadding\n{\n    static final Image[] EMPTY_IMAGES = new Image[0];\n\n    final long registrationId;\n    final int streamId;\n    int roundRobinIndex = 0;\n    volatile boolean isClosed = false;\n    volatile Image[] images = EMPTY_IMAGES;\n    final ClientConductor conductor;\n    final String channel;\n    final AvailableImageHandler availableImageHandler;\n    final UnavailableImageHandler unavailableImageHandler;\n    ChannelUri channelUri;\n    String resolvedChannel;\n    String resolvedEndpoint;\n    int channelStatusId = ChannelEndpointStatus.NO_ID_ALLOCATED;\n\n    SubscriptionFields(\n        final long registrationId,\n        final int streamId,\n        final ClientConductor clientConductor,\n        final String channel,\n        final AvailableImageHandler availableImageHandler,\n        final UnavailableImageHandler unavailableImageHandler)\n    {\n        this.registrationId = registrationId;\n        this.streamId = streamId;\n        this.conductor = clientConductor;\n        this.channel = channel;\n        this.availableImageHandler = availableImageHandler;\n        this.unavailableImageHandler = unavailableImageHandler;\n    }\n}\n\n/**\n * Aeron Subscriber API for receiving a reconstructed {@link Image} for a stream of messages from publishers on\n * a given channel and streamId pair, i.e. a {@link Publication}. {@link Image}s are aggregated under a\n * {@link Subscription}.\n * <p>\n * {@link Subscription}s are created via an {@link Aeron} object, and received messages are delivered\n * to the {@link FragmentHandler}.\n * <p>\n * By default, fragmented messages are not reassembled before delivery. If an application must\n * receive whole messages, even if they were fragmented, then the Subscriber\n * should be created with a {@link FragmentAssembler} or a custom implementation.\n * <p>\n * It is an application's responsibility to {@link #poll} the {@link Subscription} for new messages.\n * <p>\n * <b>Note:</b>Subscriptions are not threadsafe and should not be shared between subscribers.\n *\n * @see FragmentAssembler\n * @see ControlledFragmentHandler\n * @see Aeron#addSubscription(String, int)\n * @see Aeron#addSubscription(String, int, AvailableImageHandler, UnavailableImageHandler)\n */\npublic final class Subscription extends SubscriptionFields implements AutoCloseable\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n\n    Subscription(\n        final ClientConductor conductor,\n        final String channel,\n        final int streamId,\n        final long registrationId,\n        final AvailableImageHandler availableImageHandler,\n        final UnavailableImageHandler unavailableImageHandler)\n    {\n        super(\n            registrationId,\n            streamId,\n            conductor,\n            channel,\n            availableImageHandler,\n            unavailableImageHandler);\n    }\n\n    /**\n     * Media address for delivery to the channel.\n     *\n     * @return Media address for delivery to the channel.\n     */\n    public String channel()\n    {\n        return channel;\n    }\n\n    /**\n     * Stream identity for scoping within the channel media address.\n     *\n     * @return Stream identity for scoping within the channel media address.\n     */\n    public int streamId()\n    {\n        return streamId;\n    }\n\n    /**\n     * Return the registration id used to register this Subscription with the media driver.\n     *\n     * @return registration id\n     */\n    public long registrationId()\n    {\n        return registrationId;\n    }\n\n    /**\n     * Callback used to indicate when an {@link Image} becomes available under this {@link Subscription}.\n     *\n     * @return callback used to indicate when an {@link Image} becomes available under this {@link Subscription}.\n     */\n    public AvailableImageHandler availableImageHandler()\n    {\n        return availableImageHandler;\n    }\n\n    /**\n     * Callback used to indicate when an {@link Image} goes unavailable under this {@link Subscription}.\n     *\n     * @return Callback used to indicate when an {@link Image} goes unavailable under this {@link Subscription}.\n     */\n    public UnavailableImageHandler unavailableImageHandler()\n    {\n        return unavailableImageHandler;\n    }\n\n    /**\n     * Poll the {@link Image}s under the subscription for available message fragments.\n     * <p>\n     * Each fragment read will be a whole message if it is under MTU length. If larger than MTU then it will come\n     * as a series of fragments ordered within a session.\n     * <p>\n     * To assemble messages that span multiple fragments then use {@link FragmentAssembler}.\n     *\n     * @param fragmentHandler callback for handling each message fragment as it is read.\n     * @param fragmentLimit   number of message fragments to limit when polling across multiple {@link Image}s.\n     * @return the number of fragments received.\n     */\n    public int poll(final FragmentHandler fragmentHandler, final int fragmentLimit)\n    {\n        final Image[] images = this.images;\n        final int length = images.length;\n        int fragmentsRead = 0;\n\n        int startingIndex = roundRobinIndex++;\n        if (startingIndex >= length)\n        {\n            roundRobinIndex = startingIndex = 0;\n        }\n\n        for (int i = startingIndex; i < length && fragmentsRead < fragmentLimit; i++)\n        {\n            fragmentsRead += images[i].poll(fragmentHandler, fragmentLimit - fragmentsRead);\n        }\n\n        for (int i = 0; i < startingIndex && fragmentsRead < fragmentLimit; i++)\n        {\n            fragmentsRead += images[i].poll(fragmentHandler, fragmentLimit - fragmentsRead);\n        }\n\n        return fragmentsRead;\n    }\n\n    /**\n     * Poll in a controlled manner the {@link Image}s under the subscription for available message fragments.\n     * Control is applied to message fragments in the stream. If more fragments can be read on another stream\n     * they will even if BREAK or ABORT is returned from the fragment handler.\n     * <p>\n     * Each fragment read will be a whole message if it is under MTU length. If larger than MTU then it will come\n     * as a series of fragments ordered within a session.\n     * <p>\n     * To assemble messages that span multiple fragments then use {@link ControlledFragmentAssembler}.\n     *\n     * @param fragmentHandler callback for handling each message fragment as it is read.\n     * @param fragmentLimit   number of message fragments to limit when polling across multiple {@link Image}s.\n     * @return the number of fragments received.\n     * @see ControlledFragmentHandler\n     */\n    public int controlledPoll(final ControlledFragmentHandler fragmentHandler, final int fragmentLimit)\n    {\n        final Image[] images = this.images;\n        final int length = images.length;\n        int fragmentsRead = 0;\n\n        int startingIndex = roundRobinIndex++;\n        if (startingIndex >= length)\n        {\n            roundRobinIndex = startingIndex = 0;\n        }\n\n        for (int i = startingIndex; i < length && fragmentsRead < fragmentLimit; i++)\n        {\n            fragmentsRead += images[i].controlledPoll(fragmentHandler, fragmentLimit - fragmentsRead);\n        }\n\n        for (int i = 0; i < startingIndex && fragmentsRead < fragmentLimit; i++)\n        {\n            fragmentsRead += images[i].controlledPoll(fragmentHandler, fragmentLimit - fragmentsRead);\n        }\n\n        return fragmentsRead;\n    }\n\n    /**\n     * Poll the {@link Image}s under the subscription for available message fragments in blocks.\n     * <p>\n     * This method is useful for operations like bulk archiving and messaging indexing.\n     *\n     * @param blockHandler     to receive a block of fragments from each {@link Image}.\n     * @param blockLengthLimit for each {@link Image} polled.\n     * @return the number of bytes consumed.\n     */\n    public long blockPoll(final BlockHandler blockHandler, final int blockLengthLimit)\n    {\n        long bytesConsumed = 0;\n        for (final Image image : images)\n        {\n            bytesConsumed += image.blockPoll(blockHandler, blockLengthLimit);\n        }\n\n        return bytesConsumed;\n    }\n\n    /**\n     * Poll the {@link Image}s under the subscription for available message fragments in blocks.\n     * <p>\n     * This method is useful for operations like bulk archiving a stream to file.\n     *\n     * @param rawBlockHandler  to receive a block of fragments from each {@link Image}.\n     * @param blockLengthLimit for each {@link Image} polled.\n     * @return the number of bytes consumed.\n     */\n    public long rawPoll(final RawBlockHandler rawBlockHandler, final int blockLengthLimit)\n    {\n        long bytesConsumed = 0;\n        for (final Image image : images)\n        {\n            bytesConsumed += image.rawPoll(rawBlockHandler, blockLengthLimit);\n        }\n\n        return bytesConsumed;\n    }\n\n    /**\n     * Is this subscription connected by having at least one open publication {@link Image}.\n     *\n     * @return true if this subscription connected by having at least one open publication {@link Image}.\n     */\n    public boolean isConnected()\n    {\n        for (final Image image : images)\n        {\n            if (!image.isClosed())\n            {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Has this subscription currently no {@link Image}s?\n     *\n     * @return has subscription currently no {@link Image}s?\n     */\n    public boolean hasNoImages()\n    {\n        return images.length == 0;\n    }\n\n    /**\n     * Count of {@link Image}s associated to this subscription.\n     *\n     * @return count of {@link Image}s associated to this subscription.\n     */\n    public int imageCount()\n    {\n        return images.length;\n    }\n\n    /**\n     * Return the {@link Image} associated with the given sessionId.\n     *\n     * @param sessionId associated with the {@link Image}.\n     * @return Image associated with the given sessionId or null if no Image exist.\n     */\n    public Image imageBySessionId(final int sessionId)\n    {\n        Image result = null;\n\n        for (final Image image : images)\n        {\n            if (sessionId == image.sessionId())\n            {\n                result = image;\n                break;\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * Get the {@link Image} at the given index from the images array.\n     *\n     * @param index in the array.\n     * @return image at given index.\n     * @throws ArrayIndexOutOfBoundsException if the index is not valid.\n     */\n    public Image imageAtIndex(final int index)\n    {\n        return images[index];\n    }\n\n    /**\n     * Get a {@link List} of active {@link Image}s that match this subscription.\n     *\n     * @return an unmodifiable {@link List} of active {@link Image}s that match this subscription.\n     */\n    public List<Image> images()\n    {\n        return Collections.unmodifiableList(Arrays.asList(images));\n    }\n\n    /**\n     * Iterate over the {@link Image}s for this subscription.\n     *\n     * @param consumer to handle each {@link Image}.\n     */\n    public void forEachImage(final Consumer<Image> consumer)\n    {\n        for (final Image image : images)\n        {\n            consumer.accept(image);\n        }\n    }\n\n    /**\n     * Close the Subscription so that associated {@link Image}s can be released.\n     * <p>\n     * This method is idempotent.\n     */\n    public void close()\n    {\n        if (!isClosed)\n        {\n            conductor.removeSubscription(this);\n        }\n    }\n\n    /**\n     * Has this object been closed and should no longer be used?\n     *\n     * @return true if it has been closed otherwise false.\n     */\n    public boolean isClosed()\n    {\n        return isClosed;\n    }\n\n    /**\n     * Get the status of the media channel for this Subscription.\n     * <p>\n     * The status will be {@link io.aeron.status.ChannelEndpointStatus#ERRORED} if a socket exception occurs on setup\n     * and {@link io.aeron.status.ChannelEndpointStatus#ACTIVE} if all is well.\n     *\n     * @return status for the channel as one of the constants from {@link ChannelEndpointStatus} with it being\n     * {@link ChannelEndpointStatus#NO_ID_ALLOCATED} if the subscription is closed.\n     * @see io.aeron.status.ChannelEndpointStatus\n     */\n    public long channelStatus()\n    {\n        if (isClosed)\n        {\n            return ChannelEndpointStatus.NO_ID_ALLOCATED;\n        }\n\n        return conductor.channelStatus(channelStatusId);\n    }\n\n    /**\n     * Get the counter used to represent the channel status for this Subscription.\n     *\n     * @return the counter used to represent the channel status for this Subscription.\n     */\n    public int channelStatusId()\n    {\n        return channelStatusId;\n    }\n\n    /**\n     * Fetches the local socket addresses for this subscription. If the channel is not\n     * {@link io.aeron.status.ChannelEndpointStatus#ACTIVE}, then this will return an empty list.\n     * <p>\n     * The format is as follows:\n     * <br>\n     * <br>\n     * IPv4: <code>ip address:port</code>\n     * <br>\n     * IPv6: <code>[ip6 address]:port</code>\n     * <br>\n     * <br>\n     * This is to match the formatting used in the Aeron URI.\n     *\n     * @return {@link List} of local socket addresses for this subscription.\n     * @see #channelStatus()\n     */\n    public List<String> localSocketAddresses()\n    {\n        return LocalSocketAddressStatus.findAddresses(conductor.countersReader(), channelStatus(), channelStatusId);\n    }\n\n    /**\n     * Add a destination manually to a multi-destination Subscription.\n     *\n     * @param endpointChannel for the destination to add.\n     */\n    public void addDestination(final String endpointChannel)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Subscription is closed\");\n        }\n\n        conductor.addRcvDestination(registrationId, endpointChannel);\n    }\n\n    /**\n     * Remove a previously added destination from a multi-destination Subscription.\n     *\n     * @param endpointChannel for the destination to remove.\n     */\n    public void removeDestination(final String endpointChannel)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Subscription is closed\");\n        }\n\n        conductor.removeRcvDestination(registrationId, endpointChannel);\n    }\n\n    /**\n     * Asynchronously add a destination manually to a multi-destination Subscription.\n     * <p>\n     * Errors will be delivered asynchronously to the {@link Aeron.Context#errorHandler()}. Completion can be\n     * tracked by passing the returned correlation id to {@link Aeron#isCommandActive(long)}.\n     *\n     * @param endpointChannel for the destination to add.\n     * @return the correlationId for the command.\n     */\n    public long asyncAddDestination(final String endpointChannel)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Subscription is closed\");\n        }\n\n        return conductor.asyncAddRcvDestination(registrationId, endpointChannel);\n    }\n\n    /**\n     * Asynchronously remove a previously added destination from a multi-destination Subscription.\n     * <p>\n     * Errors will be delivered asynchronously to the {@link Aeron.Context#errorHandler()}. Completion can be\n     * tracked by passing the returned correlation id to {@link Aeron#isCommandActive(long)}.\n     *\n     * @param endpointChannel for the destination to remove.\n     * @return the correlationId for the command.\n     */\n    public long asyncRemoveDestination(final String endpointChannel)\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Subscription is closed\");\n        }\n\n        return conductor.asyncRemoveRcvDestination(registrationId, endpointChannel);\n    }\n\n    /**\n     * Resolve channel endpoint and replace it with the port from the ephemeral range when 0 was provided.\n     *\n     * @return original channel URI string if it does not have an endpoint (IPC, response channel, MDS) or if endpoint\n     * is not using an ephemeral port, channel URI where ephemeral port is resolved to the actual bind port or\n     * {@code null} if the channel is not {@link io.aeron.status.ChannelEndpointStatus#ACTIVE}.\n     * @see #channelStatus()\n     * @see #localSocketAddresses()\n     */\n    public String tryResolveChannelEndpointPort()\n    {\n        if (null == resolvedChannel)\n        {\n            if (null == channelUri)\n            {\n                final ChannelUri uri = ChannelUri.parse(channel);\n                final String endpoint = uri.get(ENDPOINT_PARAM_NAME);\n                if (null == endpoint ||\n                    !endpoint.endsWith(\":0\") ||\n                    MDC_CONTROL_MODE_MANUAL.equals(uri.get(MDC_CONTROL_MODE_PARAM_NAME)))\n                {\n                    resolvedChannel = channel;\n                    return channel;\n                }\n                channelUri = uri;\n            }\n\n            final String endpoint = resolvedEndpoint();\n            if (null != endpoint)\n            {\n                channelUri.replaceEndpointWildcardPort(endpoint);\n                resolvedChannel = channelUri.toString();\n            }\n        }\n        return resolvedChannel;\n    }\n\n    /**\n     * Find the resolved endpoint for the channel. This may be {@code null} if MDS is used and no destination is yet\n     * added. The result will similar to taking the first element returned from {@link #localSocketAddresses()}. If more\n     * than one destination is added then the first found is returned.\n     *\n     * @return resolved endpoint or {@code null} if not found.\n     * @see #channelStatus()\n     * @see #localSocketAddresses()\n     */\n    public String resolvedEndpoint()\n    {\n        if (null == resolvedEndpoint)\n        {\n            resolvedEndpoint =\n                LocalSocketAddressStatus.findAddress(conductor.countersReader(), channelStatus(), channelStatusId);\n        }\n        return resolvedEndpoint;\n    }\n\n    void channelStatusId(final int id)\n    {\n        channelStatusId = id;\n    }\n\n    void internalClose(final long lingerDurationNs)\n    {\n        final Image[] images = this.images;\n        this.images = EMPTY_IMAGES;\n        isClosed = true;\n        conductor.closeImages(images, unavailableImageHandler, lingerDurationNs);\n    }\n\n    void addImage(final Image image)\n    {\n        images = ArrayUtil.add(images, image);\n    }\n\n    Image removeImage(final long correlationId)\n    {\n        final Image[] oldArray = images;\n        Image removedImage = null;\n\n        int i = 0;\n        for (final Image image : oldArray)\n        {\n            if (image.correlationId() == correlationId)\n            {\n                removedImage = image;\n                break;\n            }\n\n            i++;\n        }\n\n        if (null != removedImage)\n        {\n            removedImage.close();\n            images = oldArray.length == 1 ? EMPTY_IMAGES : ArrayUtil.remove(oldArray, i);\n            conductor.releaseLogBuffers(removedImage.logBuffers(), correlationId, NULL_VALUE);\n        }\n\n        return removedImage;\n    }\n\n    void rejectImage(final long correlationId, final long position, final String reason)\n    {\n        if (channel().startsWith(SPY_PREFIX))\n        {\n            throw new AeronException(\"spies can not reject images\");\n        }\n\n        conductor.rejectImage(correlationId, position, reason);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"Subscription{\" +\n            \"registrationId=\" + registrationId +\n            \", isClosed=\" + isClosed +\n            \", isConnected=\" + isConnected() +\n            \", streamId=\" + streamId +\n            \", channel='\" + channel + '\\'' +\n            \", localSocketAddresses=\" + localSocketAddresses() +\n            \", imageCount=\" + imageCount() +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/UnavailableCounterHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.agrona.concurrent.status.CountersReader;\n\n/**\n * Interface for notification of {@link Counter}s being removed via an {@link Aeron} client.\n */\n@FunctionalInterface\npublic interface UnavailableCounterHandler\n{\n    /**\n     * Method called by Aeron to deliver notification of counter being removed.\n     * <p>\n     * Within this callback reentrant calls to the {@link Aeron} client are not permitted and\n     * will result in undefined behaviour.\n     *\n     * @param countersReader for more counter details.\n     * @param registrationId for the counter.\n     * @param counterId      that is unavailable.\n     */\n    void onUnavailableCounter(CountersReader countersReader, long registrationId, int counterId);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/UnavailableImageHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\n/**\n * Interface for delivery of inactive image notification to a {@link Subscription}.\n */\n@FunctionalInterface\npublic interface UnavailableImageHandler\n{\n    /**\n     * Method called by Aeron to deliver notification that an {@link Image} is no longer available for polling.\n     * <p>\n     * Within this callback reentrant calls to the {@link Aeron} client are not permitted and\n     * will result in undefined behaviour.\n     *\n     * @param image that is no longer available for polling.\n     */\n    void onUnavailableImage(Image image);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/ClientTimeoutFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Indicate a client has timed out by the driver.\n *\n * @see ControlProtocolEvents\n * <pre>\n * 0                   1                   2                   3\n * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n * |                         Client Id                             |\n * |                                                               |\n * +---------------------------------------------------------------+\n * </pre>\n */\npublic class ClientTimeoutFlyweight\n{\n    /**\n     * Length of the header.\n     */\n    public static final int LENGTH = SIZE_OF_LONG;\n    private static final int CLIENT_ID_FIELD_OFFSET = 0;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public ClientTimeoutFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final ClientTimeoutFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * Get client id field.\n     *\n     * @return client id field.\n     */\n    public long clientId()\n    {\n        return buffer.getLong(offset + CLIENT_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set client id field.\n     *\n     * @param clientId field value.\n     * @return this for a fluent API.\n     */\n    public ClientTimeoutFlyweight clientId(final long clientId)\n    {\n        buffer.putLong(offset + CLIENT_ID_FIELD_OFFSET, clientId);\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/ControlProtocolEvents.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.SemanticVersion;\n\n/**\n * List of events used in the control protocol between client and the media driver.\n */\npublic final class ControlProtocolEvents\n{\n    private ControlProtocolEvents()\n    {\n    }\n\n    /**\n     * Major version of the control protocol between client and the media driver.\n     *\n     * @since 1.49.0\n     */\n    public static final int CONTROL_PROTOCOL_MAJOR_VERSION = 1;\n\n    /**\n     * Minor version of the control protocol between client and the media driver.\n     *\n     * @since 1.49.0\n     */\n    public static final int CONTROL_PROTOCOL_MINOR_VERSION = 0;\n\n    /**\n     * Patch version of the control protocol between client and the media driver.\n     *\n     * @since 1.49.0\n     */\n    public static final int CONTROL_PROTOCOL_PATCH_VERSION = 0;\n\n    /**\n     * Semantic version of the control protocol between clients and media driver.\n     *\n     * @since 1.49.0\n     */\n    public static final int CONTROL_PROTOCOL_SEMANTIC_VERSION = SemanticVersion.compose(\n        CONTROL_PROTOCOL_MAJOR_VERSION, CONTROL_PROTOCOL_MINOR_VERSION, CONTROL_PROTOCOL_PATCH_VERSION);\n\n    // Clients to Media Driver\n\n    /**\n     * Add a Publication.\n     */\n    public static final int ADD_PUBLICATION = 0x01;\n\n    /**\n     * Remove a Publication.\n     */\n    public static final int REMOVE_PUBLICATION = 0x02;\n\n    /**\n     * Add an Exclusive Publication.\n     */\n    public static final int ADD_EXCLUSIVE_PUBLICATION = 0x03;\n\n    /**\n     * Add a Subscriber.\n     */\n    public static final int ADD_SUBSCRIPTION = 0x04;\n\n    /**\n     * Remove a Subscriber.\n     */\n    public static final int REMOVE_SUBSCRIPTION = 0x05;\n\n    /**\n     * Keepalive from Client.\n     */\n    public static final int CLIENT_KEEPALIVE = 0x06;\n\n    /**\n     * Add Destination to an existing Publication.\n     */\n    public static final int ADD_DESTINATION = 0x07;\n\n    /**\n     * Remove Destination from an existing Publication.\n     */\n    public static final int REMOVE_DESTINATION = 0x08;\n\n    /**\n     * Add a Counter to the counters-manager.\n     */\n    public static final int ADD_COUNTER = 0x09;\n\n    /**\n     * Remove a Counter from the counters-manager.\n     */\n    public static final int REMOVE_COUNTER = 0x0A;\n\n    /**\n     * Close indication from Client.\n     */\n    public static final int CLIENT_CLOSE = 0x0B;\n\n    /**\n     * Add Destination for existing Subscription.\n     */\n    public static final int ADD_RCV_DESTINATION = 0x0C;\n\n    /**\n     * Remove Destination for existing Subscription.\n     */\n    public static final int REMOVE_RCV_DESTINATION = 0x0D;\n\n    /**\n     * Request the driver to terminate.\n     */\n    public static final int TERMINATE_DRIVER = 0x0E;\n\n    /**\n     * Add or return a static Counter, i.e. the Counter that cannot be deleted and whose lifecycle is decoupled from\n     * the Aeron instance that created it.\n     *\n     * @since 1.45.0\n     */\n    public static final int ADD_STATIC_COUNTER = 0x0F;\n\n    /**\n     * Invalidate an image.\n     *\n     * @since 1.47.0\n     */\n    public static final int REJECT_IMAGE = 0x10;\n\n    /**\n     * Remove a destination by registration id.\n     *\n     * @since 1.47.0\n     */\n    public static final int REMOVE_DESTINATION_BY_ID = 0x11;\n\n    /**\n     * Get next available session id from the media driver.\n     *\n     * @since 1.49.0\n     */\n    public static final int GET_NEXT_AVAILABLE_SESSION_ID = 0x12;\n\n    // Media Driver to Clients\n\n    /**\n     * Error Response as a result of attempting to process a client command operation.\n     */\n    public static final int ON_ERROR = 0x0F01;\n\n    /**\n     * Subscribed Image buffers are available notification.\n     */\n    public static final int ON_AVAILABLE_IMAGE = 0x0F02;\n\n    /**\n     * New Publication buffers are ready notification.\n     */\n    public static final int ON_PUBLICATION_READY = 0x0F03;\n\n    /**\n     * Operation has succeeded.\n     */\n    public static final int ON_OPERATION_SUCCESS = 0x0F04;\n\n    /**\n     * Inform client of timeout and removal of an inactive Image.\n     */\n    public static final int ON_UNAVAILABLE_IMAGE = 0x0F05;\n\n    /**\n     * New Exclusive Publication buffers are ready notification.\n     */\n    public static final int ON_EXCLUSIVE_PUBLICATION_READY = 0x0F06;\n\n    /**\n     * New Subscription is ready notification.\n     */\n    public static final int ON_SUBSCRIPTION_READY = 0x0F07;\n\n    /**\n     * New counter is ready notification.\n     */\n    public static final int ON_COUNTER_READY = 0x0F08;\n\n    /**\n     * Inform clients of removal of counter.\n     */\n    public static final int ON_UNAVAILABLE_COUNTER = 0x0F09;\n\n    /**\n     * Inform clients of client timeout.\n     */\n    public static final int ON_CLIENT_TIMEOUT = 0x0F0A;\n\n    /**\n     * A response to {@link #ADD_STATIC_COUNTER} command.\n     *\n     * @since 1.45.0\n     */\n    public static final int ON_STATIC_COUNTER = 0x0F0B;\n\n    /**\n     * Inform clients of error frame received by publication.\n     *\n     * @since 1.47.0\n     */\n    public static final int ON_PUBLICATION_ERROR = 0x0F0C;\n\n    /**\n     * A response to the {@link #GET_NEXT_AVAILABLE_SESSION_ID} command.\n     *\n     * @since 1.49.0\n     */\n    public static final int ON_NEXT_AVAILABLE_SESSION_ID = 0x0F0D;\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/CorrelatedMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Base flyweight that can be extended to track a client request.\n * <p>\n * 0                   1                   2                   3\n * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n * |                         Client ID                             |\n * |                                                               |\n * +---------------------------------------------------------------+\n * |                       Correlation ID                          |\n * |                                                               |\n * +---------------------------------------------------------------+\n */\npublic class CorrelatedMessageFlyweight\n{\n    /**\n     * Length of the header.\n     */\n    public static final int LENGTH = 2 * SIZE_OF_LONG;\n    private static final int CLIENT_ID_FIELD_OFFSET = 0;\n    static final int CORRELATION_ID_FIELD_OFFSET = CLIENT_ID_FIELD_OFFSET + SIZE_OF_LONG;\n\n    MutableDirectBuffer buffer;\n    int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public CorrelatedMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public CorrelatedMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * Get client id field.\n     *\n     * @return client id field.\n     */\n    public long clientId()\n    {\n        return buffer.getLong(offset + CLIENT_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set client id field.\n     *\n     * @param clientId field value.\n     * @return this for a fluent API.\n     */\n    public CorrelatedMessageFlyweight clientId(final long clientId)\n    {\n        buffer.putLong(offset + CLIENT_ID_FIELD_OFFSET, clientId);\n\n        return this;\n    }\n\n    /**\n     * Get correlation id field.\n     *\n     * @return correlation id field.\n     */\n    public long correlationId()\n    {\n        return buffer.getLong(offset + CORRELATION_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public CorrelatedMessageFlyweight correlationId(final long correlationId)\n    {\n        buffer.putLong(offset + CORRELATION_ID_FIELD_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/CounterMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.*;\n\n/**\n * Message to denote a new counter.\n * <p>\n * <b>Note:</b> Layout should be SBE 2.0 compliant so that the label length is aligned.\n *\n * @see ControlProtocolEvents\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Correlation ID                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Counter Type ID                        |\n *  +---------------------------------------------------------------+\n *  |                           Key Length                          |\n *  +---------------------------------------------------------------+\n *  |                           Key Buffer                         ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                          Label Length                         |\n *  +---------------------------------------------------------------+\n *  |                          Label (ASCII)                       ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class CounterMessageFlyweight extends CorrelatedMessageFlyweight\n{\n    private static final int COUNTER_TYPE_ID_FIELD_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int KEY_LENGTH_OFFSET = COUNTER_TYPE_ID_FIELD_OFFSET + SIZE_OF_INT;\n    static final int KEY_BUFFER_OFFSET = KEY_LENGTH_OFFSET + SIZE_OF_INT;\n    private static final int MINIMUM_LENGTH = KEY_BUFFER_OFFSET + SIZE_OF_INT;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public CounterMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public CounterMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n\n        return this;\n    }\n\n    /**\n     * Get type id field.\n     *\n     * @return type id field.\n     */\n    public int typeId()\n    {\n        return buffer.getInt(offset + COUNTER_TYPE_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set counter type id field.\n     *\n     * @param typeId field value.\n     * @return this for a fluent API.\n     */\n    public CounterMessageFlyweight typeId(final int typeId)\n    {\n        buffer.putInt(offset + COUNTER_TYPE_ID_FIELD_OFFSET, typeId);\n        return this;\n    }\n\n    /**\n     * Relative offset of the key buffer.\n     *\n     * @return relative offset of the key buffer.\n     */\n    public int keyBufferOffset()\n    {\n        return KEY_BUFFER_OFFSET;\n    }\n\n    /**\n     * Length of the key buffer in bytes.\n     *\n     * @return length of key buffer in bytes.\n     */\n    public int keyBufferLength()\n    {\n        return buffer.getInt(offset + KEY_LENGTH_OFFSET);\n    }\n\n    /**\n     * Fill the key buffer.\n     *\n     * @param keyBuffer containing the optional key for the counter.\n     * @param keyOffset within the keyBuffer at which the key begins.\n     * @param keyLength of the key in the keyBuffer.\n     * @return this for a fluent API.\n     */\n    public CounterMessageFlyweight keyBuffer(final DirectBuffer keyBuffer, final int keyOffset, final int keyLength)\n    {\n        buffer.putInt(offset + KEY_LENGTH_OFFSET, keyLength);\n        if (null != keyBuffer && keyLength > 0)\n        {\n            buffer.putBytes(offset + KEY_BUFFER_OFFSET, keyBuffer, keyOffset, keyLength);\n        }\n\n        return this;\n    }\n\n    /**\n     * Relative offset of label buffer.\n     *\n     * @return relative offset of label buffer.\n     */\n    public int labelBufferOffset()\n    {\n        return labelLengthOffset() + SIZE_OF_INT;\n    }\n\n    /**\n     * Length of label buffer in bytes.\n     *\n     * @return length of label buffer in bytes.\n     */\n    public int labelBufferLength()\n    {\n        return buffer.getInt(offset + labelLengthOffset());\n    }\n\n    /**\n     * Fill the label buffer.\n     *\n     * @param labelBuffer containing the mandatory label for the counter.\n     * @param labelOffset within the labelBuffer at which the label begins.\n     * @param labelLength of the label in the labelBuffer.\n     * @return this for a fluent API.\n     */\n    public CounterMessageFlyweight labelBuffer(\n        final DirectBuffer labelBuffer, final int labelOffset, final int labelLength)\n    {\n        final int labelLengthOffset = labelLengthOffset();\n        buffer.putInt(offset + labelLengthOffset, labelLength);\n        if (null != labelBuffer && labelLength > 0)\n        {\n            buffer.putBytes(offset + labelLengthOffset + SIZE_OF_INT, labelBuffer, labelOffset, labelLength);\n        }\n\n        return this;\n    }\n\n    /**\n     * Fill the label.\n     *\n     * @param label for the counter.\n     * @return this for a fluent API.\n     */\n    public CounterMessageFlyweight label(final String label)\n    {\n        buffer.putStringAscii(offset + labelLengthOffset(), label);\n\n        return this;\n    }\n\n    /**\n     * Get the length of the current message.\n     * <p>\n     * NB: must be called after the data is written in order to be accurate.\n     *\n     * @return the length of the current message\n     */\n    public int length()\n    {\n        final int labelOffset = labelLengthOffset();\n        return labelOffset + SIZE_OF_INT + labelBufferLength();\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length    of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < MINIMUM_LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n\n        final int labelOffset = labelLengthOffset();\n        if ((length - labelOffset) < SIZE_OF_INT)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short for key: length=\" + length);\n        }\n\n        final int encodedLength = length();\n        if (length < encodedLength)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND,\n                \"command=\" + msgTypeId + \" too short for label: length=\" + length + \" encodedLength=\" + encodedLength);\n        }\n    }\n\n    /**\n     * Compute the length of the command message given key and label length.\n     *\n     * @param keyLength   to be appended.\n     * @param labelLength to be appended.\n     * @return the length of the command message given key and label length.\n     */\n    public static int computeLength(final int keyLength, final int labelLength)\n    {\n        return MINIMUM_LENGTH + align(keyLength, SIZE_OF_INT) + SIZE_OF_INT + labelLength;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"CounterMessageFlyweight{\" +\n            \"clientId=\" + clientId() +\n            \", correlationId=\" + correlationId() +\n            \", typeId=\" + typeId() +\n            \", keyBufferOffset=\" + keyBufferOffset() +\n            \", keyBufferLength=\" + keyBufferLength() +\n            \", labelLengthOffset=\" + labelLengthOffset() +\n            \", labelBufferOffset=\" + labelBufferOffset() +\n            \", labelBufferLength=\" + labelBufferLength() +\n            \", length=\" + length() +\n            \"}\";\n    }\n\n    private int labelLengthOffset()\n    {\n        return KEY_BUFFER_OFFSET + align(keyBufferLength(), SIZE_OF_INT);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/CounterUpdateFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Message to denote that a Counter has become available or unavailable.\n *\n * @see ControlProtocolEvents\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                         Correlation ID                        |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                           Counter ID                          |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class CounterUpdateFlyweight\n{\n    /**\n     * Length of the header.\n     */\n    public static final int LENGTH = SIZE_OF_LONG + SIZE_OF_INT;\n    private static final int CORRELATION_ID_OFFSET = 0;\n    private static final int COUNTER_ID_OFFSET = CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public CounterUpdateFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final CounterUpdateFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * Get the correlation id field.\n     *\n     * @return correlation id field.\n     */\n    public long correlationId()\n    {\n        return buffer.getLong(offset + CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public CounterUpdateFlyweight correlationId(final long correlationId)\n    {\n        buffer.putLong(offset + CORRELATION_ID_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * The counter id.\n     *\n     * @return counter id.\n     */\n    public int counterId()\n    {\n        return buffer.getInt(offset + COUNTER_ID_OFFSET);\n    }\n\n    /**\n     * Set counter id field.\n     *\n     * @param counterId field value.\n     * @return this for a fluent API.\n     */\n    public CounterUpdateFlyweight counterId(final int counterId)\n    {\n        buffer.putInt(offset + COUNTER_ID_OFFSET, counterId);\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/DestinationByIdMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Control message for removing a destination for a Publication in multi-destination-cast or a Subscription\n * in multi-destination Subscription.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Command Correlation ID                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Resource Correlation ID                    |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                   Destination Correlation ID                  |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class DestinationByIdMessageFlyweight extends CorrelatedMessageFlyweight\n{\n    private static final int RESOURCE_REGISTRATION_ID_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int DESTINATION_REGISTRATION_ID_OFFSET = RESOURCE_REGISTRATION_ID_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * Length of the encoded message in bytes.\n     */\n    public static final int MESSAGE_LENGTH = LENGTH + (2 * SIZE_OF_LONG);\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public DestinationByIdMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public DestinationByIdMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n\n        return this;\n    }\n\n    /**\n     * Get the registration id used for the resource that the destination has been registered to. Typically, a\n     * subscription or publication.\n     *\n     * @return resource registration id field.\n     */\n    public long resourceRegistrationId()\n    {\n        return buffer.getLong(offset + RESOURCE_REGISTRATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the registration id used for the resource that the destination has been registered to. Typically, a\n     * subscription or publication.\n     *\n     * @param registrationId field value.\n     * @return this for a fluent API.\n     */\n    public DestinationByIdMessageFlyweight resourceRegistrationId(final long registrationId)\n    {\n        buffer.putLong(offset + RESOURCE_REGISTRATION_ID_OFFSET, registrationId);\n\n        return this;\n    }\n\n    /**\n     * Returns the registration id for the destination.\n     *\n     * @return destination registration id.\n     */\n    public long destinationRegistrationId()\n    {\n        return buffer.getLong(offset + DESTINATION_REGISTRATION_ID_OFFSET);\n    }\n\n    /**\n     * Sets the registration id for the destination.\n     *\n     * @param destinationRegistrationId to reference the destination.\n     * @return this for a fluent API.\n     */\n    public DestinationByIdMessageFlyweight destinationRegistrationId(final long destinationRegistrationId)\n    {\n        buffer.putLong(offset + DESTINATION_REGISTRATION_ID_OFFSET, destinationRegistrationId);\n        return this;\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < MESSAGE_LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/DestinationMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Control message for adding or removing a destination for a Publication in multi-destination-cast or a Subscription\n * in multi-destination Subscription.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Command Correlation ID                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                  Registration Correlation ID                  |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Channel Length                          |\n *  +---------------------------------------------------------------+\n *  |                       Channel (ASCII)                        ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class DestinationMessageFlyweight extends CorrelatedMessageFlyweight\n{\n    private static final int REGISTRATION_CORRELATION_ID_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int CHANNEL_OFFSET = REGISTRATION_CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n    private static final int MINIMUM_LENGTH = CHANNEL_OFFSET + SIZE_OF_INT;\n\n    private int lengthOfChannel;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public DestinationMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public DestinationMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n\n        return this;\n    }\n\n    /**\n     * return correlation id used in registration field.\n     *\n     * @return correlation id field.\n     */\n    public long registrationCorrelationId()\n    {\n        return buffer.getLong(offset + REGISTRATION_CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * set registration correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public DestinationMessageFlyweight registrationCorrelationId(final long correlationId)\n    {\n        buffer.putLong(offset + REGISTRATION_CORRELATION_ID_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * Get the channel field in ASCII.\n     *\n     * @return channel field.\n     */\n    public String channel()\n    {\n        return buffer.getStringAscii(offset + CHANNEL_OFFSET);\n    }\n\n    /**\n     * Append the channel value to an {@link Appendable}.\n     *\n     * @param appendable to append channel to.\n     */\n    public void appendChannel(final Appendable appendable)\n    {\n        buffer.getStringAscii(offset + CHANNEL_OFFSET, appendable);\n    }\n\n    /**\n     * Set channel field in ASCII.\n     *\n     * @param channel field value\n     * @return this for a fluent API.\n     */\n    public DestinationMessageFlyweight channel(final String channel)\n    {\n        lengthOfChannel = buffer.putStringAscii(offset + CHANNEL_OFFSET, channel);\n\n        return this;\n    }\n\n    /**\n     * Length of the frame in bytes.\n     *\n     * @return length of the frame in bytes.\n     */\n    public int length()\n    {\n        return CHANNEL_OFFSET + lengthOfChannel;\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < MINIMUM_LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n\n        if ((length - MINIMUM_LENGTH) < buffer.getInt(offset + CHANNEL_OFFSET))\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short for channel: length=\" + length);\n        }\n    }\n\n    /**\n     * Compute the length of the command message for a given channel length.\n     *\n     * @param channelLength to be appended to the header.\n     * @return the length of the command message for a given channel length.\n     */\n    public static int computeLength(final int channelLength)\n    {\n        return MINIMUM_LENGTH + channelLength;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/ErrorResponseFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.ErrorCode;\nimport org.agrona.*;\n\n/**\n * Control message flyweight for any errors sent from driver to clients\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |              Offending Command Correlation ID                 |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                         Error Code                            |\n *  +---------------------------------------------------------------+\n *  |                   Error Message Length                        |\n *  +---------------------------------------------------------------+\n *  |                       Error Message                          ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class ErrorResponseFlyweight\n{\n    private static final int OFFENDING_COMMAND_CORRELATION_ID_OFFSET = 0;\n    private static final int ERROR_CODE_OFFSET = OFFENDING_COMMAND_CORRELATION_ID_OFFSET + BitUtil.SIZE_OF_LONG;\n    private static final int ERROR_MESSAGE_OFFSET = ERROR_CODE_OFFSET + BitUtil.SIZE_OF_INT;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public ErrorResponseFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final ErrorResponseFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * Return correlation ID of the offending command.\n     *\n     * @return correlation ID of the offending command\n     */\n    public long offendingCommandCorrelationId()\n    {\n        return buffer.getLong(offset + OFFENDING_COMMAND_CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set correlation ID of the offending command.\n     *\n     * @param correlationId of the offending command.\n     * @return this for a fluent API.\n     */\n    public ErrorResponseFlyweight offendingCommandCorrelationId(final long correlationId)\n    {\n        buffer.putLong(offset + OFFENDING_COMMAND_CORRELATION_ID_OFFSET, correlationId);\n        return this;\n    }\n\n    /**\n     * Error code for the command.\n     *\n     * @return error code for the command.\n     */\n    public ErrorCode errorCode()\n    {\n        return ErrorCode.get(buffer.getInt(offset + ERROR_CODE_OFFSET));\n    }\n\n    /**\n     * Error code value for the command.\n     *\n     * @return error code value for the command.\n     */\n    public int errorCodeValue()\n    {\n        return buffer.getInt(offset + ERROR_CODE_OFFSET);\n    }\n\n    /**\n     * Set the error code for the command.\n     *\n     * @param code for the error.\n     * @return this for a fluent API.\n     */\n    public ErrorResponseFlyweight errorCode(final ErrorCode code)\n    {\n        buffer.putInt(offset + ERROR_CODE_OFFSET, code.value());\n        return this;\n    }\n\n    /**\n     * Error message associated with the error.\n     *\n     * @return error message\n     */\n    public String errorMessage()\n    {\n        return buffer.getStringAscii(offset + ERROR_MESSAGE_OFFSET);\n    }\n\n    /**\n     * Append the error message to an appendable without allocation.\n     *\n     * @param appendable to append error message to.\n     * @return number bytes copied.\n     */\n    public int appendMessage(final Appendable appendable)\n    {\n        return buffer.getStringAscii(offset + ERROR_MESSAGE_OFFSET, appendable);\n    }\n\n    /**\n     * Set the error message.\n     *\n     * @param message to associate with the error.\n     * @return this for a fluent API.\n     */\n    public ErrorResponseFlyweight errorMessage(final String message)\n    {\n        buffer.putStringAscii(offset + ERROR_MESSAGE_OFFSET, message);\n        return this;\n    }\n\n    /**\n     * Length of the error response in bytes.\n     *\n     * @return length of the error response in bytes.\n     */\n    public int length()\n    {\n        return ERROR_MESSAGE_OFFSET + BitUtil.SIZE_OF_INT + buffer.getInt(offset + ERROR_MESSAGE_OFFSET);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/GetNextAvailableSessionIdMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\n\n/**\n * Control message for getting next available session id from the media driver\n * ({@link ControlProtocolEvents#GET_NEXT_AVAILABLE_SESSION_ID}).\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Command Correlation ID                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                         Stream Id                             |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic final class GetNextAvailableSessionIdMessageFlyweight extends CorrelatedMessageFlyweight\n{\n    private static final int STREAM_ID_OFFSET = CorrelatedMessageFlyweight.LENGTH;\n    /**\n     * Length of the header.\n     */\n    public static final int LENGTH = STREAM_ID_OFFSET + SIZE_OF_INT;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public GetNextAvailableSessionIdMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public GetNextAvailableSessionIdMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n\n        return this;\n    }\n\n    /**\n     * Get the stream id.\n     *\n     * @return the stream id.\n     */\n    public int streamId()\n    {\n        return buffer.getInt(offset + STREAM_ID_OFFSET);\n    }\n\n    /**\n     * Set the stream id.\n     *\n     * @param streamId the channel id.\n     * @return this for a fluent API.\n     */\n    public GetNextAvailableSessionIdMessageFlyweight streamId(final int streamId)\n    {\n        buffer.putInt(offset + STREAM_ID_OFFSET, streamId);\n\n        return this;\n    }\n\n    /**\n     * Length of the message in bytes. Only valid after the channel is set.\n     *\n     * @return length of the message in bytes.\n     */\n    public int length()\n    {\n        return LENGTH;\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/ImageBuffersReadyFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.BitUtil;\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Message to denote that new buffers for a publication image are ready for a subscription.\n * <p>\n * <b>Note:</b> Layout should be SBE 2.0 compliant so that the source identity length is aligned.\n *\n * @see ControlProtocolEvents\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                       Correlation ID                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                         Session ID                            |\n *  +---------------------------------------------------------------+\n *  |                          Stream ID                            |\n *  +---------------------------------------------------------------+\n *  |                 Subscription Registration Id                  |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Subscriber Position Id                     |\n *  +---------------------------------------------------------------+\n *  |                       Log File Length                         |\n *  +---------------------------------------------------------------+\n *  |                     Log File Name (ASCII)                    ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                    Source identity Length                     |\n *  +---------------------------------------------------------------+\n *  |                    Source identity (ASCII)                   ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class ImageBuffersReadyFlyweight\n{\n    private static final int CORRELATION_ID_OFFSET = 0;\n    private static final int SESSION_ID_OFFSET = CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n    private static final int STREAM_ID_FIELD_OFFSET = SESSION_ID_OFFSET + SIZE_OF_INT;\n    private static final int SUBSCRIPTION_REGISTRATION_ID_OFFSET = STREAM_ID_FIELD_OFFSET + SIZE_OF_INT;\n    private static final int SUBSCRIBER_POSITION_ID_OFFSET = SUBSCRIPTION_REGISTRATION_ID_OFFSET + SIZE_OF_LONG;\n    private static final int LOG_FILE_NAME_OFFSET = SUBSCRIBER_POSITION_ID_OFFSET + SIZE_OF_INT;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public ImageBuffersReadyFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final ImageBuffersReadyFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * The correlation id field.\n     *\n     * @return correlation id field.\n     */\n    public long correlationId()\n    {\n        return buffer.getLong(offset + CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public ImageBuffersReadyFlyweight correlationId(final long correlationId)\n    {\n        buffer.putLong(offset + CORRELATION_ID_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * Get the session id field.\n     *\n     * @return session id field.\n     */\n    public int sessionId()\n    {\n        return buffer.getInt(offset + SESSION_ID_OFFSET);\n    }\n\n    /**\n     * Set session id field.\n     *\n     * @param sessionId field value.\n     * @return this for a fluent API.\n     */\n    public ImageBuffersReadyFlyweight sessionId(final int sessionId)\n    {\n        buffer.putInt(offset + SESSION_ID_OFFSET, sessionId);\n\n        return this;\n    }\n\n    /**\n     * The stream id field.\n     *\n     * @return stream id field.\n     */\n    public int streamId()\n    {\n        return buffer.getInt(offset + STREAM_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set stream id field.\n     *\n     * @param streamId field value.\n     * @return this for a fluent API.\n     */\n    public ImageBuffersReadyFlyweight streamId(final int streamId)\n    {\n        buffer.putInt(offset + STREAM_ID_FIELD_OFFSET, streamId);\n\n        return this;\n    }\n\n    /**\n     * Set the position counter Id for the subscriber.\n     *\n     * @param id for the subscriber position counter.\n     * @return this for a fluent API.\n     */\n    public ImageBuffersReadyFlyweight subscriberPositionId(final int id)\n    {\n        buffer.putInt(offset + SUBSCRIBER_POSITION_ID_OFFSET, id);\n\n        return this;\n    }\n\n    /**\n     * The position counter Id for the subscriber.\n     *\n     * @return position counter Id for the subscriber.\n     */\n    public int subscriberPositionId()\n    {\n        return buffer.getInt(offset + SUBSCRIBER_POSITION_ID_OFFSET);\n    }\n\n    /**\n     * Set the registration Id for the Subscription.\n     *\n     * @param id for the Subscription.\n     * @return this for a fluent API.\n     */\n    public ImageBuffersReadyFlyweight subscriptionRegistrationId(final long id)\n    {\n        buffer.putLong(offset + SUBSCRIPTION_REGISTRATION_ID_OFFSET, id);\n\n        return this;\n    }\n\n    /**\n     * Return the registration Id for the Subscription.\n     *\n     * @return registration Id for the Subscription.\n     */\n    public long subscriptionRegistrationId()\n    {\n        return buffer.getLong(offset + SUBSCRIPTION_REGISTRATION_ID_OFFSET);\n    }\n\n    /**\n     * The Log Filename in ASCII.\n     *\n     * @return log filename.\n     */\n    public String logFileName()\n    {\n        return buffer.getStringAscii(offset + LOG_FILE_NAME_OFFSET);\n    }\n\n    /**\n     * Append the log file name to an {@link Appendable}.\n     *\n     * @param appendable to append log file name to.\n     */\n    public void appendLogFileName(final Appendable appendable)\n    {\n        buffer.getStringAscii(offset + LOG_FILE_NAME_OFFSET, appendable);\n    }\n\n    /**\n     * Set the log filename in ASCII.\n     *\n     * @param logFileName for the image.\n     * @return this for a fluent API.\n     */\n    public ImageBuffersReadyFlyweight logFileName(final String logFileName)\n    {\n        buffer.putStringAscii(offset + LOG_FILE_NAME_OFFSET, logFileName);\n        return this;\n    }\n\n    /**\n     * The source identity string in ASCII.\n     *\n     * @return source identity string.\n     */\n    public String sourceIdentity()\n    {\n        return buffer.getStringAscii(offset + sourceIdentityOffset());\n    }\n\n    /**\n     * Append the source identity to an {@link Appendable}.\n     *\n     * @param appendable to append source identity to.\n     */\n    public void appendSourceIdentity(final Appendable appendable)\n    {\n        buffer.getStringAscii(offset + sourceIdentityOffset(), appendable);\n    }\n\n    /**\n     * Set the source identity string in ASCII.\n     * <p>Note: Can be called only after log file name was set!</p>\n     *\n     * @param value for the source identity.\n     * @return this for a fluent API.\n     * @see #logFileName(String)\n     */\n    public ImageBuffersReadyFlyweight sourceIdentity(final String value)\n    {\n        buffer.putStringAscii(offset + sourceIdentityOffset(), value);\n        return this;\n    }\n\n    /**\n     * Get the length of the current message.\n     * <p>\n     * NB: must be called after the data is written in order to be accurate.\n     *\n     * @return the length of the current message.\n     */\n    public int length()\n    {\n        final int sourceIdentityOffset = sourceIdentityOffset();\n        return sourceIdentityOffset + buffer.getInt(offset + sourceIdentityOffset) + SIZE_OF_INT;\n    }\n\n    private int sourceIdentityOffset()\n    {\n        final int alignedLength = BitUtil.align(buffer.getInt(offset + LOG_FILE_NAME_OFFSET), SIZE_OF_INT);\n\n        return LOG_FILE_NAME_OFFSET + SIZE_OF_INT + alignedLength;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/ImageMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Control message flyweight for any message that needs to represent a connection.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                       Correlation ID                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                 Subscription Registration ID                  |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                          Stream ID                            |\n *  +---------------------------------------------------------------+\n *  |                       Channel Length                          |\n *  +---------------------------------------------------------------+\n *  |                       Channel (ASCII)                        ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class ImageMessageFlyweight\n{\n    private static final int CORRELATION_ID_OFFSET = 0;\n    private static final int SUBSCRIPTION_REGISTRATION_ID_OFFSET = CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n    private static final int STREAM_ID_FIELD_OFFSET = SUBSCRIPTION_REGISTRATION_ID_OFFSET + SIZE_OF_LONG;\n    private static final int CHANNEL_OFFSET = STREAM_ID_FIELD_OFFSET + SIZE_OF_INT;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n    private int lengthOfChannel;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public ImageMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final ImageMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * The correlation id field.\n     *\n     * @return correlation id field.\n     */\n    public long correlationId()\n    {\n        return buffer.getLong(offset + CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public ImageMessageFlyweight correlationId(final long correlationId)\n    {\n        buffer.putLong(offset + CORRELATION_ID_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * Registration ID for the subscription.\n     *\n     * @return registration ID for the subscription.\n     */\n    public long subscriptionRegistrationId()\n    {\n        return buffer.getLong(offset + SUBSCRIPTION_REGISTRATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the registration ID for the subscription.\n     *\n     * @param registrationId for the subscription.\n     * @return this for a fluent API.\n     */\n    public ImageMessageFlyweight subscriptionRegistrationId(final long registrationId)\n    {\n        buffer.putLong(offset + SUBSCRIPTION_REGISTRATION_ID_OFFSET, registrationId);\n\n        return this;\n    }\n\n    /**\n     * The stream id field.\n     *\n     * @return stream id field.\n     */\n    public int streamId()\n    {\n        return buffer.getInt(offset + STREAM_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set the stream id field.\n     *\n     * @param streamId field value.\n     * @return this for a fluent API.\n     */\n    public ImageMessageFlyweight streamId(final int streamId)\n    {\n        buffer.putInt(offset + STREAM_ID_FIELD_OFFSET, streamId);\n\n        return this;\n    }\n\n    /**\n     * Get the channel field as ASCII.\n     *\n     * @return channel field.\n     */\n    public String channel()\n    {\n        final int length = buffer.getInt(offset + CHANNEL_OFFSET);\n        lengthOfChannel = SIZE_OF_INT + length;\n\n        return buffer.getStringAscii(offset + CHANNEL_OFFSET, length);\n    }\n\n    /**\n     * Append the channel value to an {@link Appendable}.\n     *\n     * @param appendable to append channel to.\n     */\n    public void appendChannel(final Appendable appendable)\n    {\n        final int length = buffer.getInt(offset + CHANNEL_OFFSET);\n        lengthOfChannel = SIZE_OF_INT + length;\n\n        buffer.getStringAscii(offset + CHANNEL_OFFSET, appendable);\n    }\n\n    /**\n     * Set the channel field as ASCII.\n     *\n     * @param channel field value.\n     * @return this for a fluent API.\n     */\n    public ImageMessageFlyweight channel(final String channel)\n    {\n        lengthOfChannel = buffer.putStringAscii(offset + CHANNEL_OFFSET, channel);\n\n        return this;\n    }\n\n    /**\n     * Get the length of the current message.\n     * <p>\n     * NB: must be called after the data is written in order to be accurate.\n     *\n     * @return the length of the current message.\n     */\n    public int length()\n    {\n        return CHANNEL_OFFSET + lengthOfChannel;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/NextAvailableSessionIdFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Message to denote a response to get next session id command\n * ({@link ControlProtocolEvents#ON_NEXT_AVAILABLE_SESSION_ID}).\n *\n * @see ControlProtocolEvents\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                         Correlation ID                        |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n*  |                         Next Session ID                        |\n *  +---------------------------------------------------------------+\n * </pre>\n * @since 1.49.0\n */\npublic class NextAvailableSessionIdFlyweight\n{\n    private static final int CORRELATION_ID_OFFSET = 0;\n    private static final int SESSION_ID_OFFSET = CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n    /**\n     * Length of the header.\n     */\n    public static final int LENGTH = SESSION_ID_OFFSET + SIZE_OF_INT;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public NextAvailableSessionIdFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final NextAvailableSessionIdFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * Get the correlation id field.\n     *\n     * @return correlation id field.\n     */\n    public long correlationId()\n    {\n        return buffer.getLong(offset + CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public NextAvailableSessionIdFlyweight correlationId(final long correlationId)\n    {\n        buffer.putLong(offset + CORRELATION_ID_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * The session id.\n     *\n     * @return session id.\n     */\n    public int nextSessionId()\n    {\n        return buffer.getInt(offset + SESSION_ID_OFFSET);\n    }\n\n    /**\n     * Set session id field.\n     *\n     * @param sessionId field value.\n     * @return this for a fluent API.\n     */\n    public NextAvailableSessionIdFlyweight nextSessionId(final int sessionId)\n    {\n        buffer.putInt(offset + SESSION_ID_OFFSET, sessionId);\n\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"NextSessionIdFlyweight{\" +\n            \"correlationId=\" + correlationId() +\n            \", sessionId=\" + nextSessionId() +\n            \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/OperationSucceededFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Indicate a given operation is done and has succeeded.\n *\n * @see ControlProtocolEvents\n * <pre>\n * 0                   1                   2                   3\n * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n * |                         Correlation ID                        |\n * |                                                               |\n * +---------------------------------------------------------------+\n * </pre>\n */\npublic class OperationSucceededFlyweight\n{\n    /**\n     * Length of the header.\n     */\n    public static final int LENGTH = SIZE_OF_LONG;\n    private static final int CORRELATION_ID_FIELD_OFFSET = 0;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public OperationSucceededFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final OperationSucceededFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * The correlation id field.\n     *\n     * @return correlation id field.\n     */\n    public long correlationId()\n    {\n        return buffer.getLong(offset + CORRELATION_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set the correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public OperationSucceededFlyweight correlationId(final long correlationId)\n    {\n        buffer.putLong(offset + CORRELATION_ID_FIELD_OFFSET, correlationId);\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/PublicationBuffersReadyFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Message to denote that new buffers have been created for a publication.\n *\n * @see ControlProtocolEvents\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                         Correlation ID                        |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Registration ID                        |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                          Session ID                           |\n *  +---------------------------------------------------------------+\n *  |                           Stream ID                           |\n *  +---------------------------------------------------------------+\n *  |                  Publication Limit Counter ID                 |\n *  +---------------------------------------------------------------+\n *  |                  Channel Status Indicator ID                  |\n *  +---------------------------------------------------------------+\n *  |                        Log File Length                        |\n *  +---------------------------------------------------------------+\n *  |                     Log File Name (ASCII)                    ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class PublicationBuffersReadyFlyweight\n{\n    private static final int CORRELATION_ID_OFFSET = 0;\n    private static final int REGISTRATION_ID_OFFSET = CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n    private static final int SESSION_ID_OFFSET = REGISTRATION_ID_OFFSET + SIZE_OF_LONG;\n    private static final int STREAM_ID_FIELD_OFFSET = SESSION_ID_OFFSET + SIZE_OF_INT;\n    private static final int PUBLICATION_LIMIT_COUNTER_ID_OFFSET = STREAM_ID_FIELD_OFFSET + SIZE_OF_INT;\n    private static final int CHANNEL_STATUS_INDICATOR_ID_OFFSET = PUBLICATION_LIMIT_COUNTER_ID_OFFSET + SIZE_OF_INT;\n    private static final int LOGFILE_FIELD_OFFSET = CHANNEL_STATUS_INDICATOR_ID_OFFSET + SIZE_OF_INT;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public PublicationBuffersReadyFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final PublicationBuffersReadyFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * Get the correlation id field.\n     *\n     * @return correlation id field.\n     */\n    public long correlationId()\n    {\n        return buffer.getLong(offset + CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationBuffersReadyFlyweight correlationId(final long correlationId)\n    {\n        buffer.putLong(offset + CORRELATION_ID_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * Get the registration id field.\n     *\n     * @return registration id field.\n     */\n    public long registrationId()\n    {\n        return buffer.getLong(offset + REGISTRATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the correlation id field.\n     *\n     * @param registrationId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationBuffersReadyFlyweight registrationId(final long registrationId)\n    {\n        buffer.putLong(offset + REGISTRATION_ID_OFFSET, registrationId);\n\n        return this;\n    }\n\n    /**\n     * Get the session id field.\n     *\n     * @return session id field.\n     */\n    public int sessionId()\n    {\n        return buffer.getInt(offset + SESSION_ID_OFFSET);\n    }\n\n    /**\n     * Set the session id field.\n     *\n     * @param sessionId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationBuffersReadyFlyweight sessionId(final int sessionId)\n    {\n        buffer.putInt(offset + SESSION_ID_OFFSET, sessionId);\n\n        return this;\n    }\n\n    /**\n     * Get the stream id field.\n     *\n     * @return stream id field.\n     */\n    public int streamId()\n    {\n        return buffer.getInt(offset + STREAM_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set the stream id field.\n     *\n     * @param streamId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationBuffersReadyFlyweight streamId(final int streamId)\n    {\n        buffer.putInt(offset + STREAM_ID_FIELD_OFFSET, streamId);\n\n        return this;\n    }\n\n    /**\n     * The publication limit counter id.\n     *\n     * @return publication limit counter id.\n     */\n    public int publicationLimitCounterId()\n    {\n        return buffer.getInt(offset + PUBLICATION_LIMIT_COUNTER_ID_OFFSET);\n    }\n\n    /**\n     * set position counter id field.\n     *\n     * @param positionCounterId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationBuffersReadyFlyweight publicationLimitCounterId(final int positionCounterId)\n    {\n        buffer.putInt(offset + PUBLICATION_LIMIT_COUNTER_ID_OFFSET, positionCounterId);\n\n        return this;\n    }\n\n    /**\n     * The channel status counter id.\n     *\n     * @return channel status counter id.\n     */\n    public int channelStatusCounterId()\n    {\n        return buffer.getInt(offset + CHANNEL_STATUS_INDICATOR_ID_OFFSET);\n    }\n\n    /**\n     * Set channel status counter id field.\n     *\n     * @param counterId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationBuffersReadyFlyweight channelStatusCounterId(final int counterId)\n    {\n        buffer.putInt(offset + CHANNEL_STATUS_INDICATOR_ID_OFFSET, counterId);\n\n        return this;\n    }\n\n    /**\n     * Get the log file name in ASCII.\n     *\n     * @return the log file name in ASCII.\n     */\n    public String logFileName()\n    {\n        return buffer.getStringAscii(offset + LOGFILE_FIELD_OFFSET);\n    }\n\n    /**\n     * Append the log file name to an {@link Appendable}.\n     *\n     * @param appendable to append log file name to.\n     */\n    public void appendLogFileName(final Appendable appendable)\n    {\n        buffer.getStringAscii(offset + LOGFILE_FIELD_OFFSET, appendable);\n    }\n\n    /**\n     * Set the log file name in ASCII.\n     *\n     * @param logFileName for the publication buffers.\n     * @return the log file name in ASCII.\n     */\n    public PublicationBuffersReadyFlyweight logFileName(final String logFileName)\n    {\n        buffer.putStringAscii(offset + LOGFILE_FIELD_OFFSET, logFileName);\n        return this;\n    }\n\n    /**\n     * Get the length of the current message\n     * <p>\n     * NB: must be called after the data is written in order to be accurate.\n     *\n     * @return the length of the current message\n     */\n    public int length()\n    {\n        return buffer.getInt(offset + LOGFILE_FIELD_OFFSET) + LOGFILE_FIELD_OFFSET + SIZE_OF_INT;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/PublicationErrorFrameFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.ErrorCode;\nimport org.agrona.BitUtil;\nimport org.agrona.MutableDirectBuffer;\n\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.UnknownHostException;\n\n/**\n * Control message flyweight error frames received by a publication to be reported to the client.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                 Publication Registration Id                   |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                 Destination Registration Id                   |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                          Session ID                           |\n *  +---------------------------------------------------------------+\n *  |                           Stream ID                           |\n *  +---------------------------------------------------------------+\n *  |                          Receiver ID                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                           Group Tag                           |\n *  |                                                               |\n *  +-------------------------------+-------------------------------+\n *  |          Address Type         |            UDP Port           |\n *  +-------------------------------+-------------------------------+\n *  |           IPv4 or IPv6 Address padded out to 16 bytes         |\n *  |                                                               |\n *  |                                                               |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                          Error Code                           |\n *  +---------------------------------------------------------------+\n *  |                      Error Message Length                     |\n *  +---------------------------------------------------------------+\n *  |                         Error Message                        ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n * @since 1.47.0\n */\npublic class PublicationErrorFrameFlyweight\n{\n    private static final int REGISTRATION_ID_OFFSET = 0;\n    private static final int IPV6_ADDRESS_LENGTH = 16;\n    private static final int IPV4_ADDRESS_LENGTH = BitUtil.SIZE_OF_INT;\n    private static final int DESTINATION_REGISTRATION_ID_OFFSET = REGISTRATION_ID_OFFSET + BitUtil.SIZE_OF_LONG;\n    private static final int SESSION_ID_OFFSET = DESTINATION_REGISTRATION_ID_OFFSET + BitUtil.SIZE_OF_LONG;\n    private static final int STREAM_ID_OFFSET = SESSION_ID_OFFSET + BitUtil.SIZE_OF_INT;\n    private static final int RECEIVER_ID_OFFSET = STREAM_ID_OFFSET + BitUtil.SIZE_OF_INT;\n    private static final int GROUP_TAG_OFFSET = RECEIVER_ID_OFFSET + BitUtil.SIZE_OF_LONG;\n    private static final int ADDRESS_TYPE_OFFSET = GROUP_TAG_OFFSET + BitUtil.SIZE_OF_LONG;\n    private static final int ADDRESS_PORT_OFFSET = ADDRESS_TYPE_OFFSET + BitUtil.SIZE_OF_SHORT;\n    private static final int ADDRESS_OFFSET = ADDRESS_PORT_OFFSET + BitUtil.SIZE_OF_SHORT;\n    private static final int ERROR_CODE_OFFSET = ADDRESS_OFFSET + IPV6_ADDRESS_LENGTH;\n    private static final int ERROR_MESSAGE_OFFSET = ERROR_CODE_OFFSET + BitUtil.SIZE_OF_INT;\n    private static final short ADDRESS_TYPE_IPV4 = 1;\n    private static final short ADDRESS_TYPE_IPV6 = 2;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public PublicationErrorFrameFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final PublicationErrorFrameFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * Return registration ID of the publication that received the error frame.\n     *\n     * @return registration ID of the publication.\n     */\n    public long registrationId()\n    {\n        return buffer.getLong(offset + REGISTRATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the registration ID of the publication that received the error frame.\n     *\n     * @param registrationId of the publication.\n     * @return this for a fluent API.\n     */\n    public PublicationErrorFrameFlyweight registrationId(final long registrationId)\n    {\n        buffer.putLong(offset + REGISTRATION_ID_OFFSET, registrationId);\n        return this;\n    }\n\n    /**\n     * Return registration id of the destination that received the error frame. This will only be set if the publication\n     * is using manual MDC.\n     *\n     * @return registration ID of the publication or {@link io.aeron.Aeron#NULL_VALUE}.\n     */\n    public long destinationRegistrationId()\n    {\n        return buffer.getLong(offset + DESTINATION_REGISTRATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the registration ID of the destination that received the error frame. Use {@link io.aeron.Aeron#NULL_VALUE}\n     * if not set.\n     *\n     * @param registrationId of the destination.\n     * @return this for a fluent API.\n     */\n    public PublicationErrorFrameFlyweight destinationRegistrationId(final long registrationId)\n    {\n        buffer.putLong(offset + DESTINATION_REGISTRATION_ID_OFFSET, registrationId);\n        return this;\n    }\n\n    /**\n     * Get the stream id field.\n     *\n     * @return stream id field.\n     */\n    public int streamId()\n    {\n        return buffer.getInt(offset + STREAM_ID_OFFSET);\n    }\n\n    /**\n     * Set the stream id field.\n     *\n     * @param streamId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationErrorFrameFlyweight streamId(final int streamId)\n    {\n        buffer.putInt(offset + STREAM_ID_OFFSET, streamId);\n\n        return this;\n    }\n\n    /**\n     * Get the session id field.\n     *\n     * @return session id field.\n     */\n    public int sessionId()\n    {\n        return buffer.getInt(offset + SESSION_ID_OFFSET);\n    }\n\n    /**\n     * Set session id field.\n     *\n     * @param sessionId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationErrorFrameFlyweight sessionId(final int sessionId)\n    {\n        buffer.putInt(offset + SESSION_ID_OFFSET, sessionId);\n\n        return this;\n    }\n\n    /**\n     * Get the receiver id field.\n     *\n     * @return get the receiver id field.\n     */\n    public long receiverId()\n    {\n        return buffer.getLong(offset + RECEIVER_ID_OFFSET);\n    }\n\n    /**\n     * Set receiver id field.\n     *\n     * @param receiverId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationErrorFrameFlyweight receiverId(final long receiverId)\n    {\n        buffer.putLong(offset + RECEIVER_ID_OFFSET, receiverId);\n\n        return this;\n    }\n\n    /**\n     * Get the group tag field.\n     *\n     * @return the group tag field.\n     */\n    public long groupTag()\n    {\n        return buffer.getLong(offset + GROUP_TAG_OFFSET);\n    }\n\n    /**\n     * Set the group tag field.\n     *\n     * @param groupTag the group tag value.\n     * @return this for a fluent API.\n     */\n    public PublicationErrorFrameFlyweight groupTag(final long groupTag)\n    {\n        buffer.putLong(offset + GROUP_TAG_OFFSET, groupTag);\n\n        return this;\n    }\n\n    /**\n     * Set the source address for the error frame. Store the type (IPv4 or IPv6), port and address as bytes.\n     *\n     * @param sourceAddress of the error frame.\n     * @return this for a fluent API\n     */\n    public PublicationErrorFrameFlyweight sourceAddress(final InetSocketAddress sourceAddress)\n    {\n        final short sourcePort = (short)(sourceAddress.getPort() & 0xFFFF);\n        final InetAddress address = sourceAddress.getAddress();\n\n        buffer.putShort(offset + ADDRESS_PORT_OFFSET, sourcePort);\n        buffer.putBytes(offset + ADDRESS_OFFSET, address.getAddress());\n        if (address instanceof Inet4Address)\n        {\n            buffer.putShort(offset + ADDRESS_TYPE_OFFSET, ADDRESS_TYPE_IPV4);\n            buffer.setMemory(\n                offset + ADDRESS_OFFSET + IPV4_ADDRESS_LENGTH, IPV6_ADDRESS_LENGTH - IPV4_ADDRESS_LENGTH, (byte)0);\n        }\n        else if (address instanceof Inet6Address)\n        {\n            buffer.putShort(offset + ADDRESS_TYPE_OFFSET, ADDRESS_TYPE_IPV6);\n        }\n        else\n        {\n            throw new IllegalArgumentException(\"Unknown address type:\" + address.getClass().getSimpleName());\n        }\n\n        return this;\n    }\n\n    /**\n     * Get the source address of this error frame.\n     *\n     * @return source address of the error frame.\n     */\n    public InetSocketAddress sourceAddress()\n    {\n        final short addressType = buffer.getShort(offset + ADDRESS_TYPE_OFFSET);\n        final int port = buffer.getShort(offset + ADDRESS_PORT_OFFSET) & 0xFFFF;\n\n        final byte[] address;\n        if (ADDRESS_TYPE_IPV4 == addressType)\n        {\n            address = new byte[IPV4_ADDRESS_LENGTH];\n        }\n        else if (ADDRESS_TYPE_IPV6 == addressType)\n        {\n            address = new byte[IPV6_ADDRESS_LENGTH];\n        }\n        else\n        {\n            throw new IllegalArgumentException(\"Unknown address type:\" + addressType);\n        }\n\n        buffer.getBytes(offset + ADDRESS_OFFSET, address);\n        try\n        {\n            return new InetSocketAddress(Inet4Address.getByAddress(address), port);\n        }\n        catch (final UnknownHostException ex)\n        {\n            throw new IllegalArgumentException(\"Unknown address type:\" + addressType, ex);\n        }\n\n    }\n\n    /**\n     * Error code for the command.\n     *\n     * @return error code for the command.\n     */\n    public ErrorCode errorCode()\n    {\n        return ErrorCode.get(buffer.getInt(offset + ERROR_CODE_OFFSET));\n    }\n\n    /**\n     * Error code value for the command.\n     *\n     * @return error code value for the command.\n     */\n    public int errorCodeValue()\n    {\n        return buffer.getInt(offset + ERROR_CODE_OFFSET);\n    }\n\n    /**\n     * Set the error code for the command.\n     *\n     * @param code for the error.\n     * @return this for a fluent API.\n     */\n    public PublicationErrorFrameFlyweight errorCode(final ErrorCode code)\n    {\n        buffer.putInt(offset + ERROR_CODE_OFFSET, code.value());\n        return this;\n    }\n\n    /**\n     * Error message associated with the error.\n     *\n     * @return error message.\n     */\n    public String errorMessage()\n    {\n        return buffer.getStringAscii(offset + ERROR_MESSAGE_OFFSET);\n    }\n\n    /**\n     * Append the error message to an appendable without allocation.\n     *\n     * @param appendable to append error message to.\n     * @return number bytes copied.\n     */\n    public int appendMessage(final Appendable appendable)\n    {\n        return buffer.getStringAscii(offset + ERROR_MESSAGE_OFFSET, appendable);\n    }\n\n    /**\n     * Set the error message.\n     *\n     * @param message to associate with the error.\n     * @return this for a fluent API.\n     */\n    public PublicationErrorFrameFlyweight errorMessage(final String message)\n    {\n        buffer.putStringAscii(offset + ERROR_MESSAGE_OFFSET, message);\n        return this;\n    }\n\n    /**\n     * Length of the error response in bytes.\n     *\n     * @return length of the error response in bytes.\n     */\n    public int length()\n    {\n        return ERROR_MESSAGE_OFFSET + BitUtil.SIZE_OF_INT + buffer.getInt(offset + ERROR_MESSAGE_OFFSET);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/PublicationMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Control message for adding or removing a publication\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Correlation ID                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                          Stream ID                            |\n *  +---------------------------------------------------------------+\n *  |                       Channel Length                          |\n *  +---------------------------------------------------------------+\n *  |                       Channel (ASCII)                        ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class PublicationMessageFlyweight extends CorrelatedMessageFlyweight\n{\n    private static final int STREAM_ID_FIELD_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int CHANNEL_OFFSET = STREAM_ID_FIELD_OFFSET + SIZE_OF_INT;\n    private static final int MINIMUM_LENGTH = CHANNEL_OFFSET + SIZE_OF_INT;\n\n    private int lengthOfChannel;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public PublicationMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public PublicationMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n\n        return this;\n    }\n\n    /**\n     * Get the stream id field.\n     *\n     * @return stream id field.\n     */\n    public int streamId()\n    {\n        return buffer.getInt(offset + STREAM_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set the stream id field.\n     *\n     * @param streamId field value.\n     * @return this for a fluent API.\n     */\n    public PublicationMessageFlyweight streamId(final int streamId)\n    {\n        buffer.putInt(offset + STREAM_ID_FIELD_OFFSET, streamId);\n\n        return this;\n    }\n\n    /**\n     * Get the channel field in ASCII.\n     *\n     * @return channel field.\n     */\n    public String channel()\n    {\n        return buffer.getStringAscii(offset + CHANNEL_OFFSET);\n    }\n\n    /**\n     * Append the channel value to an {@link Appendable}.\n     *\n     * @param appendable to append channel to.\n     */\n    public void appendChannel(final Appendable appendable)\n    {\n        buffer.getStringAscii(offset + CHANNEL_OFFSET, appendable);\n    }\n\n    /**\n     * Set the channel field in ASCII.\n     *\n     * @param channel field value.\n     * @return this for a fluent API.\n     */\n    public PublicationMessageFlyweight channel(final String channel)\n    {\n        lengthOfChannel = buffer.putStringAscii(offset + CHANNEL_OFFSET, channel);\n\n        return this;\n    }\n\n    /**\n     * Get the length of the current message.\n     * <p>\n     * NB: must be called after the data is written in order to be accurate.\n     *\n     * @return the length of the current message.\n     */\n    public int length()\n    {\n        return CHANNEL_OFFSET + lengthOfChannel;\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < MINIMUM_LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n\n        if ((length - MINIMUM_LENGTH) < buffer.getInt(offset + CHANNEL_OFFSET))\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short for channel: length=\" + length);\n        }\n    }\n\n    /**\n     * Compute the length of the command message for a given channel length.\n     *\n     * @param channelLength to be appended to the header.\n     * @return the length of the command message for a given channel length.\n     */\n    public static int computeLength(final int channelLength)\n    {\n        return MINIMUM_LENGTH + channelLength;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/RejectImageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Control message to reject an image for a subscription.\n *\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Correlation ID                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Image Correlation ID                       |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                           Position                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Reason Length                          |\n *  +---------------------------------------------------------------+\n *  |                        Reason (ASCII)                       ...\n *  ...                                                             |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class RejectImageFlyweight extends CorrelatedMessageFlyweight\n{\n    private static final int IMAGE_CORRELATION_ID_FIELD_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int POSITION_FIELD_OFFSET = IMAGE_CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int REASON_FIELD_OFFSET = POSITION_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int MINIMUM_SIZE = REASON_FIELD_OFFSET + SIZE_OF_INT;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public RejectImageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public RejectImageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n        return this;\n    }\n\n    /**\n     * Get image correlation id field.\n     *\n     * @return image correlation id field.\n     */\n    public long imageCorrelationId()\n    {\n        return buffer.getLong(offset + IMAGE_CORRELATION_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Put image correlation id field.\n     *\n     * @param position new image correlation id value.\n     * @return this for a fluent API.\n     */\n    public RejectImageFlyweight imageCorrelationId(final long position)\n    {\n        buffer.putLong(offset + IMAGE_CORRELATION_ID_FIELD_OFFSET, position);\n        return this;\n    }\n\n    /**\n     * Get position field.\n     *\n     * @return position field.\n     */\n    public long position()\n    {\n        return buffer.getLong(offset + POSITION_FIELD_OFFSET);\n    }\n\n    /**\n     * Put position field.\n     *\n     * @param position new position value.\n     * @return this for a fluent API.\n     */\n    public RejectImageFlyweight position(final long position)\n    {\n        buffer.putLong(offset + POSITION_FIELD_OFFSET, position);\n        return this;\n    }\n\n    /**\n     * Put reason field as ASCII. Include the reason length in the message.\n     *\n     * @param reason for invalidating the image.\n     * @return this for a fluent API.\n     */\n    public RejectImageFlyweight reason(final String reason)\n    {\n        buffer.putStringAscii(offset + REASON_FIELD_OFFSET, reason);\n        return this;\n    }\n\n\n    /**\n     * Get reason field as ASCII.\n     *\n     * @return reason for invalidating the image.\n     */\n    public String reason()\n    {\n        return buffer.getStringAscii(offset + REASON_FIELD_OFFSET);\n    }\n\n    /**\n     * Length of the reason text.\n     *\n     * @return length of the reason text.\n     */\n    public int reasonBufferLength()\n    {\n        // This does make the assumption that the string is stored with the leading 4 bytes representing the length.\n        return buffer.getInt(offset + REASON_FIELD_OFFSET);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public RejectImageFlyweight clientId(final long clientId)\n    {\n        super.clientId(clientId);\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public RejectImageFlyweight correlationId(final long correlationId)\n    {\n        super.correlationId(correlationId);\n        return this;\n    }\n\n    /**\n     * Compute the length of the message based on the reason supplied.\n     *\n     * @param reason message to be return to originator.\n     * @return length of the message.\n     */\n    public static int computeLength(final String reason)\n    {\n        return MINIMUM_SIZE + reason.length();\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < MINIMUM_SIZE)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n\n        if (length < MINIMUM_SIZE + reasonBufferLength())\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/RemoveCounterFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\n/**\n * Control message for removing a Counter.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Command Correlation ID                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Registration ID                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class RemoveCounterFlyweight extends RemoveMessageFlyweight\n{\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public RemoveCounterFlyweight()\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/RemoveMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Control message for removing a Publication or Subscription.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Command Correlation ID                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Registration ID                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic abstract class RemoveMessageFlyweight extends CorrelatedMessageFlyweight\n{\n    static final int REGISTRATION_ID_FIELD_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int MINIMUM_LENGTH = REGISTRATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public RemoveMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public RemoveMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n\n        return this;\n    }\n\n    /**\n     * Get the registration id field.\n     *\n     * @return registration id field.\n     */\n    public long registrationId()\n    {\n        return buffer.getLong(offset + REGISTRATION_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set registration id field.\n     *\n     * @param registrationId field value.\n     * @return this for a fluent API.\n     */\n    public RemoveMessageFlyweight registrationId(final long registrationId)\n    {\n        buffer.putLong(offset + REGISTRATION_ID_FIELD_OFFSET, registrationId);\n\n        return this;\n    }\n\n    /**\n     * Length of the message in bytes.\n     *\n     * @return length of the message in bytes.\n     */\n    public static int length()\n    {\n        return LENGTH + SIZE_OF_LONG;\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < MINIMUM_LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/RemovePublicationFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Control message for removing a Publication.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Command Correlation ID                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Registration ID                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                           Flags                               |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class RemovePublicationFlyweight extends RemoveMessageFlyweight\n{\n    private static final int FLAGS_FIELD_OFFSET = REGISTRATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n\n    private static final long FLAG_REVOKE = 0x1;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public RemovePublicationFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public RemovePublicationFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n\n        return this;\n    }\n\n    /**\n     * Length of the message in bytes.\n     *\n     * @return length of the message in bytes.\n     */\n    public static int length()\n    {\n        return RemoveMessageFlyweight.length() + SIZE_OF_LONG;\n    }\n\n    /**\n     * Whether or not the message contains the flags field.\n     *\n     * @param messageLength the length of the message.\n     * @return true if the flags field can be read.\n     */\n    public boolean flagsFieldIsValid(final int messageLength)\n    {\n        return messageLength >= FLAGS_FIELD_OFFSET + SIZE_OF_LONG;\n    }\n\n    /**\n     * Get the value of the revoke field.\n     *\n     * @return revoked.\n     */\n    public boolean revoke()\n    {\n        return (buffer.getLong(offset + FLAGS_FIELD_OFFSET) & FLAG_REVOKE) > 0;\n    }\n\n    /**\n     * Whether or not the message contains the set revoke flag.\n     *\n     * @param messageLength the length of the message.\n     * @return true if the flags field is present AND the revoked flag is set.\n     */\n    public boolean revoke(final int messageLength)\n    {\n        return flagsFieldIsValid(messageLength) && revoke();\n    }\n\n    /**\n     * Set the value of the revoke field.\n     *\n     * @param revoke field value.\n     * @return this for a fluent API.\n     */\n    public RemovePublicationFlyweight revoke(final boolean revoke)\n    {\n        long flags = buffer.getLong(offset + FLAGS_FIELD_OFFSET);\n\n        if (revoke)\n        {\n            flags |= FLAG_REVOKE;\n        }\n        else\n        {\n            flags &= ~FLAG_REVOKE;\n        }\n\n        buffer.putLong(offset + FLAGS_FIELD_OFFSET, flags);\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/RemoveSubscriptionFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\n/**\n * Control message for removing a Subscription.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Command Correlation ID                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Registration ID                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class RemoveSubscriptionFlyweight extends RemoveMessageFlyweight\n{\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public RemoveSubscriptionFlyweight()\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/StaticCounterFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Message to denote a response to a create static counter request.\n *\n * @see ControlProtocolEvents\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                         Correlation ID                        |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                           Counter ID                          |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class StaticCounterFlyweight\n{\n    /**\n     * Length of the header.\n     */\n    public static final int LENGTH = SIZE_OF_LONG + SIZE_OF_INT;\n    private static final int CORRELATION_ID_OFFSET = 0;\n    private static final int COUNTER_ID_OFFSET = CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public StaticCounterFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final StaticCounterFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * Get the correlation id field.\n     *\n     * @return correlation id field.\n     */\n    public long correlationId()\n    {\n        return buffer.getLong(offset + CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public StaticCounterFlyweight correlationId(final long correlationId)\n    {\n        buffer.putLong(offset + CORRELATION_ID_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * The counter id.\n     *\n     * @return counter id.\n     */\n    public int counterId()\n    {\n        return buffer.getInt(offset + COUNTER_ID_OFFSET);\n    }\n\n    /**\n     * Set counter id field.\n     *\n     * @param counterId field value.\n     * @return this for a fluent API.\n     */\n    public StaticCounterFlyweight counterId(final int counterId)\n    {\n        buffer.putInt(offset + COUNTER_ID_OFFSET, counterId);\n\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"StaticCounterFlyweight{\" +\n            \"correlationId=\" + correlationId() +\n            \", counterId=\" + counterId() +\n            \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/StaticCounterMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.*;\n\n/**\n * Message to get or create a new static counter.\n * <p>\n * <b>Note:</b> Layout should be SBE 2.0 compliant so that the label length is aligned.\n *\n * @see ControlProtocolEvents\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Correlation ID                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Registration ID                        |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Counter Type ID                        |\n *  +---------------------------------------------------------------+\n *  |                           Key Length                          |\n *  +---------------------------------------------------------------+\n *  |                           Key Buffer                         ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                          Label Length                         |\n *  +---------------------------------------------------------------+\n *  |                          Label (ASCII)                       ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class StaticCounterMessageFlyweight extends CorrelatedMessageFlyweight\n{\n    private static final int REGISTRATION_ID_FIELD_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int COUNTER_TYPE_ID_FIELD_OFFSET = REGISTRATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int KEY_LENGTH_OFFSET = COUNTER_TYPE_ID_FIELD_OFFSET + SIZE_OF_INT;\n    static final int KEY_BUFFER_OFFSET = KEY_LENGTH_OFFSET + SIZE_OF_INT;\n    private static final int MINIMUM_LENGTH = KEY_BUFFER_OFFSET + SIZE_OF_INT;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public StaticCounterMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public StaticCounterMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n\n        return this;\n    }\n\n    /**\n     * Get registration id field.\n     *\n     * @return registration id field.\n     */\n    public long registrationId()\n    {\n        return buffer.getLong(offset + REGISTRATION_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set counter registration id field.\n     *\n     * @param registrationId field value.\n     * @return this for a fluent API.\n     */\n    public StaticCounterMessageFlyweight registrationId(final long registrationId)\n    {\n        buffer.putLong(offset + REGISTRATION_ID_FIELD_OFFSET, registrationId);\n        return this;\n    }\n\n    /**\n     * Get type id field.\n     *\n     * @return type id field.\n     */\n    public int typeId()\n    {\n        return buffer.getInt(offset + COUNTER_TYPE_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Set counter type id field.\n     *\n     * @param typeId field value.\n     * @return this for a fluent API.\n     */\n    public StaticCounterMessageFlyweight typeId(final int typeId)\n    {\n        buffer.putInt(offset + COUNTER_TYPE_ID_FIELD_OFFSET, typeId);\n        return this;\n    }\n\n    /**\n     * Relative offset of the key buffer.\n     *\n     * @return relative offset of the key buffer.\n     */\n    public int keyBufferOffset()\n    {\n        return KEY_BUFFER_OFFSET;\n    }\n\n    /**\n     * Length of the key buffer in bytes.\n     *\n     * @return length of key buffer in bytes.\n     */\n    public int keyBufferLength()\n    {\n        return buffer.getInt(offset + KEY_LENGTH_OFFSET);\n    }\n\n    /**\n     * Fill the key buffer.\n     *\n     * @param keyBuffer containing the optional key for the counter.\n     * @param keyOffset within the keyBuffer at which the key begins.\n     * @param keyLength of the key in the keyBuffer.\n     * @return this for a fluent API.\n     */\n    public StaticCounterMessageFlyweight keyBuffer(\n        final DirectBuffer keyBuffer, final int keyOffset, final int keyLength)\n    {\n        buffer.putInt(offset + KEY_LENGTH_OFFSET, keyLength);\n        if (null != keyBuffer && keyLength > 0)\n        {\n            buffer.putBytes(offset + KEY_BUFFER_OFFSET, keyBuffer, keyOffset, keyLength);\n        }\n\n        return this;\n    }\n\n    /**\n     * Relative offset of label buffer.\n     *\n     * @return relative offset of label buffer.\n     */\n    public int labelBufferOffset()\n    {\n        return labelLengthOffset() + SIZE_OF_INT;\n    }\n\n    /**\n     * Length of label buffer in bytes.\n     *\n     * @return length of label buffer in bytes.\n     */\n    public int labelBufferLength()\n    {\n        return buffer.getInt(offset + labelLengthOffset());\n    }\n\n    /**\n     * Fill the label buffer.\n     *\n     * @param labelBuffer containing the mandatory label for the counter.\n     * @param labelOffset within the labelBuffer at which the label begins.\n     * @param labelLength of the label in the labelBuffer.\n     * @return this for a fluent API.\n     */\n    public StaticCounterMessageFlyweight labelBuffer(\n        final DirectBuffer labelBuffer, final int labelOffset, final int labelLength)\n    {\n        final int labelLengthOffset = labelLengthOffset();\n        buffer.putInt(offset + labelLengthOffset, labelLength);\n        if (null != labelBuffer && labelLength > 0)\n        {\n            buffer.putBytes(offset + labelLengthOffset + SIZE_OF_INT, labelBuffer, labelOffset, labelLength);\n        }\n\n        return this;\n    }\n\n    /**\n     * Fill the label.\n     *\n     * @param label for the counter.\n     * @return this for a fluent API.\n     */\n    public StaticCounterMessageFlyweight label(final String label)\n    {\n        buffer.putStringAscii(offset + labelLengthOffset(), label);\n\n        return this;\n    }\n\n    /**\n     * Get the length of the current message.\n     * <p>\n     * NB: must be called after the data is written in order to be accurate.\n     *\n     * @return the length of the current message\n     */\n    public int length()\n    {\n        final int labelOffset = labelLengthOffset();\n        return labelOffset + SIZE_OF_INT + labelBufferLength();\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length    of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < MINIMUM_LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n\n        final int labelOffset = labelLengthOffset();\n        if ((length - labelOffset) < SIZE_OF_INT)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short for key: length=\" + length);\n        }\n\n        final int encodedLength = length();\n        if (length < encodedLength)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND,\n                \"command=\" + msgTypeId + \" too short for label: length=\" + length + \" encodedLength=\" + encodedLength);\n        }\n    }\n\n    /**\n     * Compute the length of the command message given key and label length.\n     *\n     * @param keyLength   to be appended.\n     * @param labelLength to be appended.\n     * @return the length of the command message given key and label length.\n     */\n    public static int computeLength(final int keyLength, final int labelLength)\n    {\n        return MINIMUM_LENGTH + align(keyLength, SIZE_OF_INT) + SIZE_OF_INT + labelLength;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"StaticCounterMessageFlyweight{\" +\n            \"clientId=\" + clientId() +\n            \", correlationId=\" + correlationId() +\n            \", registrationId=\" + registrationId() +\n            \", typeId=\" + typeId() +\n            \", keyBufferOffset=\" + keyBufferOffset() +\n            \", keyBufferLength=\" + keyBufferLength() +\n            \", labelLengthOffset=\" + labelLengthOffset() +\n            \", labelBufferOffset=\" + labelBufferOffset() +\n            \", labelBufferLength=\" + labelBufferLength() +\n            \", length=\" + length() +\n            \"}\";\n    }\n\n    private int labelLengthOffset()\n    {\n        return KEY_BUFFER_OFFSET + align(keyBufferLength(), SIZE_OF_INT);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/SubscriptionMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Control message for adding or removing a subscription.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                    Command Correlation ID                     |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                 Registration Correlation ID                   |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                         Stream Id                             |\n *  +---------------------------------------------------------------+\n *  |                       Channel Length                          |\n *  +---------------------------------------------------------------+\n *  |                       Channel (ASCII)                        ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class SubscriptionMessageFlyweight extends CorrelatedMessageFlyweight\n{\n    private static final int REGISTRATION_CORRELATION_ID_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    private static final int STREAM_ID_OFFSET = REGISTRATION_CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n    private static final int CHANNEL_OFFSET = STREAM_ID_OFFSET + SIZE_OF_INT;\n    private static final int MINIMUM_LENGTH = CHANNEL_OFFSET + SIZE_OF_INT;\n\n    private int lengthOfChannel;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public SubscriptionMessageFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public SubscriptionMessageFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n\n        return this;\n    }\n\n    /**\n     * The correlation id used in registration field.\n     *\n     * @return correlation id field.\n     */\n    public long registrationCorrelationId()\n    {\n        return buffer.getLong(offset + REGISTRATION_CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the registration correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public SubscriptionMessageFlyweight registrationCorrelationId(final long correlationId)\n    {\n        buffer.putLong(offset + REGISTRATION_CORRELATION_ID_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * Get the stream id.\n     *\n     * @return the stream id.\n     */\n    public int streamId()\n    {\n        return buffer.getInt(offset + STREAM_ID_OFFSET);\n    }\n\n    /**\n     * Set the stream id.\n     *\n     * @param streamId the channel id.\n     * @return this for a fluent API.\n     */\n    public SubscriptionMessageFlyweight streamId(final int streamId)\n    {\n        buffer.putInt(offset + STREAM_ID_OFFSET, streamId);\n\n        return this;\n    }\n\n    /**\n     * Get the channel field in ASCII.\n     *\n     * @return channel field.\n     */\n    public String channel()\n    {\n        return buffer.getStringAscii(offset + CHANNEL_OFFSET);\n    }\n\n    /**\n     * Append the channel value to an {@link Appendable}.\n     *\n     * @param appendable to append channel to.\n     */\n    public void appendChannel(final Appendable appendable)\n    {\n        buffer.getStringAscii(offset + CHANNEL_OFFSET, appendable);\n    }\n\n    /**\n     * Set channel field in ASCII.\n     *\n     * @param channel field value.\n     * @return this for a fluent API.\n     */\n    public SubscriptionMessageFlyweight channel(final String channel)\n    {\n        lengthOfChannel = buffer.putStringAscii(offset + CHANNEL_OFFSET, channel);\n\n        return this;\n    }\n\n    /**\n     * Length of the message in bytes. Only valid after the channel is set.\n     *\n     * @return length of the message in bytes.\n     */\n    public int length()\n    {\n        return CHANNEL_OFFSET + lengthOfChannel;\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < MINIMUM_LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n\n        if ((length - MINIMUM_LENGTH) < buffer.getInt(offset + CHANNEL_OFFSET))\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short for channel: length=\" + length);\n        }\n    }\n\n    /**\n     * Compute the length of the command message for a given channel length.\n     *\n     * @param channelLength to be appended to the header.\n     * @return the length of the command message for a given channel length.\n     */\n    public static int computeLength(final int channelLength)\n    {\n        return MINIMUM_LENGTH + channelLength;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/SubscriptionReadyFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.MutableDirectBuffer;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Message to denote that a Subscription has been successfully set up.\n *\n * @see ControlProtocolEvents\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                        Correlation ID                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                  Channel Status Indicator ID                  |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class SubscriptionReadyFlyweight\n{\n    /**\n     * Length of the header.\n     */\n    public static final int LENGTH = SIZE_OF_LONG + SIZE_OF_INT;\n    private static final int CORRELATION_ID_OFFSET = 0;\n    private static final int CHANNEL_STATUS_INDICATOR_ID_OFFSET = CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n\n    private MutableDirectBuffer buffer;\n    private int offset;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public SubscriptionReadyFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public final SubscriptionReadyFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        this.buffer = buffer;\n        this.offset = offset;\n\n        return this;\n    }\n\n    /**\n     * Get the correlation id field.\n     *\n     * @return correlation id field.\n     */\n    public long correlationId()\n    {\n        return buffer.getLong(offset + CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the correlation id field.\n     *\n     * @param correlationId field value.\n     * @return this for a fluent API.\n     */\n    public SubscriptionReadyFlyweight correlationId(final long correlationId)\n    {\n        buffer.putLong(offset + CORRELATION_ID_OFFSET, correlationId);\n\n        return this;\n    }\n\n    /**\n     * The channel status counter id.\n     *\n     * @return channel status counter id.\n     */\n    public int channelStatusCounterId()\n    {\n        return buffer.getInt(offset + CHANNEL_STATUS_INDICATOR_ID_OFFSET);\n    }\n\n    /**\n     * Set channel status counter id field.\n     *\n     * @param counterId field value.\n     * @return this for a fluent API.\n     */\n    public SubscriptionReadyFlyweight channelStatusCounterId(final int counterId)\n    {\n        buffer.putInt(offset + CHANNEL_STATUS_INDICATOR_ID_OFFSET, counterId);\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/TerminateDriverFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.exceptions.ControlProtocolException;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\n\nimport static io.aeron.ErrorCode.MALFORMED_COMMAND;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Command message flyweight to ask the driver process to terminate.\n *\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Client ID                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Correlation ID                         |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                         Token Length                          |\n *  +---------------------------------------------------------------+\n *  |                         Token Buffer                         ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n * @see ControlProtocolEvents\n */\npublic class TerminateDriverFlyweight extends CorrelatedMessageFlyweight\n{\n    private static final int TOKEN_LENGTH_OFFSET = CORRELATION_ID_FIELD_OFFSET + SIZE_OF_LONG;\n    static final int TOKEN_BUFFER_OFFSET = TOKEN_LENGTH_OFFSET + SIZE_OF_INT;\n    private static final int MINIMUM_LENGTH = TOKEN_LENGTH_OFFSET + SIZE_OF_INT;\n\n    /**\n     * An empty flyweight, which should be wrapped over a buffer.\n     *\n     * @see #wrap(MutableDirectBuffer, int)\n     */\n    public TerminateDriverFlyweight()\n    {\n    }\n\n    /**\n     * Wrap the buffer at a given offset for updates.\n     *\n     * @param buffer to wrap.\n     * @param offset at which the message begins.\n     * @return this for a fluent API.\n     */\n    public TerminateDriverFlyweight wrap(final MutableDirectBuffer buffer, final int offset)\n    {\n        super.wrap(buffer, offset);\n        return this;\n    }\n\n    /**\n     * Relative offset of the token buffer.\n     *\n     * @return relative offset of the token buffer.\n     */\n    public int tokenBufferOffset()\n    {\n        return TOKEN_BUFFER_OFFSET;\n    }\n\n    /**\n     * Length of the token buffer in bytes.\n     *\n     * @return length of token buffer in bytes.\n     */\n    public int tokenBufferLength()\n    {\n        return buffer.getInt(offset + TOKEN_LENGTH_OFFSET);\n    }\n\n    /**\n     * Fill the token buffer.\n     *\n     * @param tokenBuffer containing the optional token for the request.\n     * @param tokenOffset within the tokenBuffer at which the token begins.\n     * @param tokenLength of the token in the tokenBuffer.\n     * @return this for a fluent API.\n     */\n    public TerminateDriverFlyweight tokenBuffer(\n        final DirectBuffer tokenBuffer, final int tokenOffset, final int tokenLength)\n    {\n        buffer.putInt(offset + TOKEN_LENGTH_OFFSET, tokenLength);\n        if (null != tokenBuffer && tokenLength > 0)\n        {\n            buffer.putBytes(offset + tokenBufferOffset(), tokenBuffer, tokenOffset, tokenLength);\n        }\n\n        return this;\n    }\n\n    /**\n     * Get the length of the current message.\n     * <p>\n     * NB: must be called after the data is written in order to be correct.\n     *\n     * @return the length of the current message\n     */\n    public int length()\n    {\n        return TOKEN_BUFFER_OFFSET + tokenBufferLength();\n    }\n\n    /**\n     * Validate buffer length is long enough for message.\n     *\n     * @param msgTypeId type of message.\n     * @param length    of message in bytes to validate.\n     */\n    public void validateLength(final int msgTypeId, final int length)\n    {\n        if (length < MINIMUM_LENGTH)\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short: length=\" + length);\n        }\n\n        if ((length - MINIMUM_LENGTH) < buffer.getInt(offset + TOKEN_LENGTH_OFFSET))\n        {\n            throw new ControlProtocolException(\n                MALFORMED_COMMAND, \"command=\" + msgTypeId + \" too short for token buffer: length=\" + length);\n        }\n    }\n\n    /**\n     * Compute the length of the command message for a given token length.\n     *\n     * @param tokenLength to be appended to the header.\n     * @return the length of the command message for a given token length.\n     */\n    public static int computeLength(final int tokenLength)\n    {\n        if (tokenLength < 0)\n        {\n            throw new ConfigurationException(\"token length must be >= 0: \" + tokenLength);\n        }\n\n        return LENGTH + SIZE_OF_INT + tokenLength;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/command/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Command message codec flyweights for the communication protocol between Aeron clients and the Media Driver.\n */\npackage io.aeron.command;"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/AeronEvent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\n/**\n * A means to capture an event of significance that does not require a stack trace, so it can be lighter-weight\n * and take up less space in a {@link org.agrona.concurrent.errors.DistinctErrorLog}.\n */\npublic class AeronEvent extends AeronException\n{\n    private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0];\n    private static final long serialVersionUID = 2520658673245952222L;\n\n    /**\n     * Aeron event with provided message and {@link AeronException.Category#WARN}.\n     *\n     * @param message to detail the event.\n     */\n    public AeronEvent(final String message)\n    {\n        super(message, null, false, false, AeronException.Category.WARN);\n    }\n\n    /**\n     * Aeron event with provided message and {@link AeronException.Category}.\n     *\n     * @param message  to detail the event.\n     * @param category of the event.\n     */\n    public AeronEvent(final String message, final AeronException.Category category)\n    {\n        super(message, null, false, false, category);\n    }\n\n    /**\n     * Override the base implementation so no stack trace is associated.\n     * <p>\n     * <b>Note:</b> This method is not synchronized as it does not call the super class.\n     *\n     * @return a reference to this {@link AeronEvent} instance.\n     */\n    public synchronized Throwable fillInStackTrace()\n    {\n        return this;\n    }\n\n    /**\n     * Overridden to avoid creating a clone of an empty stack.\n     *\n     * @return empty stack trace.\n     */\n    public StackTraceElement[] getStackTrace()\n    {\n        return EMPTY_STACK;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/AeronException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\n/**\n * Base Aeron exception for catching all Aeron specific errors.\n */\npublic class AeronException extends RuntimeException\n{\n    private static final long serialVersionUID = -8399166154162189622L;\n\n    /**\n     * Category of {@link Exception}.\n     */\n    public enum Category\n    {\n        /**\n         * Exception indicates a fatal condition. Recommendation is to terminate process immediately to avoid\n         * state corruption.\n         */\n        FATAL,\n\n        /**\n         * Exception is an error. Corrective action is recommended if understood, otherwise treat as fatal.\n         */\n        ERROR,\n\n        /**\n         * Exception is a warning. Action has been, or will be, taken to handle the condition.\n         * Additional corrective action by the application may be needed.\n         */\n        WARN\n    }\n\n    /**\n     * {@link AeronException.Category} of the exception to help the client decide how they should proceed.\n     */\n    private final Category category;\n\n    /**\n     * Default Aeron exception as {@link AeronException.Category#ERROR}.\n     */\n    public AeronException()\n    {\n        this.category = Category.ERROR;\n    }\n\n    /**\n     * Default Aeron exception with provided {@link AeronException.Category}.\n     *\n     * @param category of this exception.\n     */\n    public AeronException(final Category category)\n    {\n        this.category = category;\n    }\n\n    /**\n     * Aeron exception with provided message and {@link AeronException.Category#ERROR}.\n     *\n     * @param message to detail the exception.\n     */\n    public AeronException(final String message)\n    {\n        super(Category.ERROR.name() + \" - \" + message);\n        this.category = Category.ERROR;\n    }\n\n    /**\n     * Aeron exception with provided cause and {@link AeronException.Category#ERROR}.\n     *\n     * @param cause of the error.\n     */\n    public AeronException(final Throwable cause)\n    {\n        super(cause);\n        this.category = Category.ERROR;\n    }\n\n    /**\n     * Aeron exception with a detailed message and provided {@link AeronException.Category}.\n     *\n     * @param message  providing detail on the error.\n     * @param category of the exception.\n     */\n    public AeronException(final String message, final Category category)\n    {\n        super(category.name() + \" - \" + message);\n        this.category = category;\n    }\n\n    /**\n     * Aeron exception with a detailed message, cause, and {@link AeronException.Category#ERROR}.\n     *\n     * @param message providing detail on the error.\n     * @param cause   of the error.\n     */\n    public AeronException(final String message, final Throwable cause)\n    {\n        super(Category.ERROR.name() + \" - \" + message, cause);\n        this.category = Category.ERROR;\n    }\n\n    /**\n     * Aeron exception with cause and provided {@link AeronException.Category}.\n     *\n     * @param cause    of the error.\n     * @param category of the exception.\n     */\n    public AeronException(final Throwable cause, final Category category)\n    {\n        super(cause);\n        this.category = category;\n    }\n\n    /**\n     * Aeron exception with a detailed message, cause, and {@link AeronException.Category}.\n     *\n     * @param message  providing detail on the error.\n     * @param cause    of the error.\n     * @param category of the exception.\n     */\n    public AeronException(final String message, final Throwable cause, final Category category)\n    {\n        super(category.name() + \" - \" + message, cause);\n        this.category = category;\n    }\n\n    /**\n     * Constructs a new Aeron exception with a detail message, cause, suppression enabled or disabled,\n     * and writable stack trace enabled or disabled, in the category {@link AeronException.Category#ERROR}.\n     *\n     * @param message            providing detail on the error.\n     * @param cause              of the error.\n     * @param enableSuppression  is suppression enabled or not.\n     * @param writableStackTrace is the stack trace writable or not.\n     */\n    public AeronException(\n        final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace)\n    {\n        super(Category.ERROR.name() + \" - \" + message, cause, enableSuppression, writableStackTrace);\n        this.category = Category.ERROR;\n    }\n\n    /**\n     * Constructs a new Aeron exception with a detail message, cause, suppression enabled or disabled,\n     * writable stack trace enabled or disabled, an {@link AeronException.Category}.\n     *\n     * @param message            providing detail on the error.\n     * @param cause              of the error.\n     * @param enableSuppression  is suppression enabled or not.\n     * @param writableStackTrace is the stack trace writable or not.\n     * @param category           of the exception.\n     */\n    public AeronException(\n        final String message,\n        final Throwable cause,\n        final boolean enableSuppression,\n        final boolean writableStackTrace,\n        final Category category)\n    {\n        super(category.name() + \" - \" + message, cause, enableSuppression, writableStackTrace);\n        this.category = category;\n    }\n\n    /**\n     * {@link AeronException.Category} of exception for determining what follow-up action can be taken.\n     *\n     * @return {@link AeronException.Category} of exception for determining what follow-up action can be taken.\n     */\n    public Category category()\n    {\n        return category;\n    }\n\n    /**\n     * Determines if a {@link Throwable} is a {@link AeronException.Category#FATAL} if an {@link AeronException}.\n     *\n     * @param t throwable to check if fatal.\n     * @return true if this is an AeronException with a category set to {@link AeronException.Category#FATAL},\n     * false otherwise.\n     */\n    public static boolean isFatal(final Throwable t)\n    {\n        return t instanceof AeronException && Category.FATAL == ((AeronException)t).category;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/ChannelEndpointException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\n/**\n * Indicates an error occurred when setting up the channel for either a {@link io.aeron.Publication} or\n * {@link io.aeron.Subscription}.\n */\npublic class ChannelEndpointException extends AeronException\n{\n    private static final long serialVersionUID = 6810249167217382358L;\n\n    /**\n     * Counter id for the status indicator of the channel.\n     */\n    private final int statusIndicatorId;\n\n    /**\n     * Construct an exception with a given status indicator counter id and detail message.\n     *\n     * @param statusIndicatorId counter id for the channel.\n     * @param message           for the exception.\n     */\n    public ChannelEndpointException(final int statusIndicatorId, final String message)\n    {\n        super(message);\n        this.statusIndicatorId = statusIndicatorId;\n    }\n\n    /**\n     * Return the id for the counter associated with the channel endpoint.\n     *\n     * @return counter id associated with the channel endpoint\n     */\n    public int statusIndicatorId()\n    {\n        return statusIndicatorId;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/ClientTimeoutException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\n/**\n * Client timeout event received from the driver for this client.\n * <p>\n * This is likely to happen as a result of a GC pause that is longer than the {@code aeron.client.liveness.timeout}\n * setting.\n */\npublic class ClientTimeoutException extends TimeoutException\n{\n    private static final long serialVersionUID = 4085394356371474876L;\n\n    /**\n     * Construct the client timeout exception with detail message.\n     *\n     * @param message detail for the exception.\n     */\n    public ClientTimeoutException(final String message)\n    {\n        super(message, Category.FATAL);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/ConcurrentConcludeException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\n/**\n * Conclude has been called concurrently on a Context. The caller that receives this should not close the\n * concluded context as it will be owned by another caller.\n */\npublic class ConcurrentConcludeException extends AeronException\n{\n    /**\n     * Create a default ConcurrentConcludeException.\n     */\n    public ConcurrentConcludeException()\n    {\n    }\n\n    private static final long serialVersionUID = 684839776662091577L;\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/ConductorServiceTimeoutException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\n/**\n * A timeout has occurred between service calls for the client conductor.\n * <p>\n * This is likely to occur due to GC or resource starvation where the client conductor thread has not being able to\n * run within the {@code aeron.client.liveness.timeout} property set on the media driver.\n */\npublic class ConductorServiceTimeoutException extends TimeoutException\n{\n    private static final long serialVersionUID = 4289404220974757441L;\n\n    /**\n     * Construct the exception for the service interval timeout with detailed message.\n     *\n     * @param message detail for the exception.\n     */\n    public ConductorServiceTimeoutException(final String message)\n    {\n        super(message, Category.FATAL);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/ConfigurationException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\n/**\n * Indicates an invalid configuration option has been provided.\n */\npublic class ConfigurationException extends AeronException\n{\n    private static final long serialVersionUID = 2545086690221965112L;\n\n    /**\n     * Construct an exception with detail for the configuration error.\n     *\n     * @param message detail for the configuration error.\n     */\n    public ConfigurationException(final String message)\n    {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/ControlProtocolException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\nimport io.aeron.ErrorCode;\n\n/**\n * Indicates an invalid use of the control protocol when sending commands from the client to driver.\n */\npublic class ControlProtocolException extends AeronException\n{\n    private static final long serialVersionUID = -6491010363479568113L;\n\n    /**\n     * The {@link ErrorCode} indicating more specific issue experienced by the media driver.\n     */\n    private final ErrorCode code;\n\n    /**\n     * Construct an exception to indicate an invalid command has been sent to the media driver.\n     *\n     * @param code for the type of error.\n     * @param msg  providing more detail.\n     */\n    public ControlProtocolException(final ErrorCode code, final String msg)\n    {\n        super(msg);\n        this.code = code;\n    }\n\n    /**\n     * Construct an exception to indicate an invalid command has been sent to the media driver.\n     *\n     * @param code      for the type of error.\n     * @param rootCause of the error.\n     */\n    public ControlProtocolException(final ErrorCode code, final Exception rootCause)\n    {\n        super(rootCause);\n        this.code = code;\n    }\n\n    /**\n     * Construct an exception to indicate an invalid command has been sent to the media driver.\n     *\n     * @param code      for the type of error.\n     * @param msg       providing more detail.\n     * @param rootCause of the error.\n     */\n    public ControlProtocolException(final ErrorCode code, final String msg, final Exception rootCause)\n    {\n        super(msg, rootCause);\n        this.code = code;\n    }\n\n    /**\n     * The {@link ErrorCode} indicating more specific issue experienced by the media driver.\n     *\n     * @return {@link ErrorCode} indicating more specific issue experienced by the media driver.\n     */\n    public ErrorCode errorCode()\n    {\n        return code;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/DriverTimeoutException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\n/**\n * A timeout has occurred while waiting on the media driver responding to an operation.\n */\npublic class DriverTimeoutException extends TimeoutException\n{\n    private static final long serialVersionUID = -334819963402642904L;\n\n    /**\n     * Construct the exception for driver timeout due to lack of heartbeat.\n     *\n     * @param message detail for the exception.\n     */\n    public DriverTimeoutException(final String message)\n    {\n        super(message, Category.FATAL);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/RegistrationException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\nimport io.aeron.*;\n\n/**\n * Caused when an error occurs during addition, modification, or release of client resources such as\n * {@link Publication}s, {@link Subscription}s, or {@link Counter}s.\n */\npublic class RegistrationException extends AeronException\n{\n    private static final long serialVersionUID = 9076689715024983035L;\n\n    /**\n     * The correlation id of the command to register the resource action.\n     */\n    private final long correlationId;\n\n    /**\n     * Value of the {@link #errorCode()} encoded as an int.\n     */\n    private final int errorCodeValue;\n\n    /**\n     * The {@link ErrorCode} for the specific exception.\n     */\n    private final ErrorCode errorCode;\n\n    /**\n     * Construct an exception to represent an error which occurred during registration of a resource such as a\n     * Publication, Subscription, or Counter.\n     *\n     * @param correlationId  of the command to register the resource.\n     * @param errorCodeValue in case the {@link ErrorCode} is unknown to the client version.\n     * @param errorCode      indicating type of error experienced by the media driver.\n     * @param msg            proving more detail.\n     */\n    public RegistrationException(\n        final long correlationId, final int errorCodeValue, final ErrorCode errorCode, final String msg)\n    {\n        super(\n            stripCategoryName(msg) + \", errorCodeValue=\" + errorCodeValue,\n            ErrorCode.RESOURCE_TEMPORARILY_UNAVAILABLE == errorCode ? Category.WARN : Category.ERROR);\n        this.correlationId = correlationId;\n        this.errorCode = errorCode;\n        this.errorCodeValue = errorCodeValue;\n    }\n\n    /**\n     * Construct a from another to be nested, useful when exceptions are reported asynchronously and need to be\n     * rethrown from other places in the code.\n     *\n     * @param cause original RegistrationException to be stored as the exception cause.\n     */\n    public RegistrationException(final RegistrationException cause)\n    {\n        super(stripCategoryName(cause.getMessage()), cause, cause.category());\n        this.correlationId = cause.correlationId;\n        this.errorCode = cause.errorCode;\n        this.errorCodeValue = cause.errorCodeValue;\n    }\n\n    /**\n     * Get the correlation id of the command to register the resource action.\n     *\n     * @return the correlation id of the command to register the resource action.\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * Get the {@link ErrorCode} for the specific exception.\n     *\n     * @return the {@link ErrorCode} for the specific exception.\n     */\n    public ErrorCode errorCode()\n    {\n        return errorCode;\n    }\n\n    /**\n     * Value of the {@link #errorCode()} encoded as an int. This can provide additional information when a\n     * {@link ErrorCode#UNKNOWN_CODE_VALUE} is returned.\n     *\n     * @return value of the errorCode encoded as an int.\n     */\n    public int errorCodeValue()\n    {\n        return errorCodeValue;\n    }\n\n    private static String stripCategoryName(final String msg)\n    {\n        if (null != msg && msg.length() > 7)\n        {\n            if (msg.startsWith(\"ERROR - \") || msg.startsWith(\"FATAL - \"))\n            {\n                return msg.substring(8);\n            }\n\n            if (msg.startsWith(\"WARN - \"))\n            {\n                return msg.substring(7);\n            }\n        }\n\n        return msg;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/StorageSpaceException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\nimport java.io.IOException;\n\n/**\n * A request to allocate a resource (e.g. log buffer) failed due to insufficient storage space available.\n */\npublic class StorageSpaceException extends AeronException\n{\n    private static final long serialVersionUID = -552384561600482276L;\n\n    /**\n     * Construct the exception for the with detailed message.\n     *\n     * @param message detail for the exception.\n     */\n    public StorageSpaceException(final String message)\n    {\n        super(message);\n    }\n\n    /**\n     * Check if given exception denotes an out of disc space error, i.e. which on Linux is represented by error code\n     * {@code ENOSPC(28)} and on Windows by error code  {@code ERROR_DISK_FULL(112)}.\n     *\n     * @param error to check.\n     * @return {@code true} if cause is {@link java.io.IOException} with a specific error.\n     */\n    public static boolean isStorageSpaceError(final Throwable error)\n    {\n        Throwable cause = error;\n        while (null != cause)\n        {\n            if (cause instanceof StorageSpaceException)\n            {\n                return true;\n            }\n            else if (cause instanceof IOException)\n            {\n                final String msg = cause.getMessage();\n                if (\"No space left on device\".equals(msg) || \"There is not enough space on the disk\".equals(msg))\n                {\n                    return true;\n                }\n            }\n            cause = cause.getCause();\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/TimeoutException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\n/**\n * Generic timeout has occurred while waiting on some action or event.\n */\npublic class TimeoutException extends AeronException\n{\n    private static final long serialVersionUID = 339608646881678251L;\n\n    /**\n     * Default timeout exception as {@link AeronException.Category#ERROR}.\n     */\n    public TimeoutException()\n    {\n    }\n\n    /**\n     * Timeout exception with provided message and {@link AeronException.Category#ERROR}.\n     *\n     * @param message to detail the exception.\n     */\n    public TimeoutException(final String message)\n    {\n        super(message);\n    }\n\n    /**\n     * Timeout Aeron exception with provided {@link AeronException.Category}.\n     *\n     * @param category of this exception.\n     */\n    public TimeoutException(final Category category)\n    {\n        super(category);\n    }\n\n    /**\n     * Timeout exception with a detailed message and provided {@link AeronException.Category}.\n     *\n     * @param message  providing detail on the error.\n     * @param category of the exception.\n     */\n    public TimeoutException(final String message, final Category category)\n    {\n        super(message, category);\n    }\n\n    /**\n     * Timeout exception with cause and provided {@link AeronException.Category}.\n     *\n     * @param cause    of the error.\n     * @param category of the exception.\n     */\n    public TimeoutException(final Throwable cause, final Category category)\n    {\n        super(cause, category);\n    }\n\n    /**\n     * Timeout exception with a detailed message, cause, and {@link AeronException.Category}.\n     *\n     * @param message  providing detail on the error.\n     * @param cause    of the error.\n     * @param category of the exception.\n     */\n    public TimeoutException(final String message, final Throwable cause, final Category category)\n    {\n        super(message, cause, category);\n    }\n\n    /**\n     * Constructs a new timeout exception with the detail message, cause, suppression enabled or disabled,\n     * writable stack trace enabled or disabled, an {@link AeronException.Category}.\n     *\n     * @param message            providing detail on the error.\n     * @param cause              of the error.\n     * @param enableSuppression  is suppression enabled or not.\n     * @param writableStackTrace is the stack trace writable or not.\n     * @param category           of the exception.\n     */\n    protected TimeoutException(\n        final String message,\n        final Throwable cause,\n        final boolean enableSuppression,\n        final boolean writableStackTrace,\n        final Category category)\n    {\n        super(message, cause, enableSuppression, writableStackTrace, category);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/exceptions/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Common exception used by Aeron which are all unchecked.\n */\npackage io.aeron.exceptions;"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/BlockHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.DirectBuffer;\n\n/**\n * Function for handling a block of message fragments scanned from the log.\n */\n@FunctionalInterface\npublic interface BlockHandler\n{\n    /**\n     * Callback for handling a block of messages being read from a log.\n     *\n     * @param buffer    containing the block of message fragments.\n     * @param offset    at which the block begins, including any frame headers.\n     * @param length    of the block in bytes, including any frame headers that is aligned up to\n     *                  {@link io.aeron.logbuffer.FrameDescriptor#FRAME_ALIGNMENT}.\n     * @param sessionId of the stream containing this block of message fragments.\n     * @param termId    of the stream containing this block of message fragments.\n     */\n    void onBlock(DirectBuffer buffer, int offset, int length, int sessionId, int termId);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/BufferClaim.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteOrder;\n\nimport static io.aeron.protocol.DataHeaderFlyweight.*;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * Represents a claimed range in a buffer to be used for recording a message without copy semantics for later commit.\n * <p>\n * The claimed space is in {@link #buffer()} between {@link #offset()} and {@link #offset()} + {@link #length()}.\n * When the buffer is filled with message data, use {@link #commit()} to make it available to subscribers.\n * <p>\n * If the claimed space is no longer required it can be aborted by calling {@link #abort()}.\n *\n * @see io.aeron.Publication#tryClaim(int, BufferClaim)\n */\npublic final class BufferClaim\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(0, 0);\n\n    /**\n     * An empty buffer claim, ready to be used with {@link io.aeron.Publication#tryClaim(int, BufferClaim)}.\n     */\n    public BufferClaim()\n    {\n    }\n\n    /**\n     * Wrap a region of an underlying log buffer so can can represent a claimed space for use by a publisher.\n     *\n     * @param buffer to be wrapped.\n     * @param offset at which the claimed region begins including space for the header.\n     * @param length length of the underlying claimed region including space for the header.\n     */\n    public void wrap(final AtomicBuffer buffer, final int offset, final int length)\n    {\n        this.buffer.wrap(buffer, offset, length);\n    }\n\n    /**\n     * The referenced buffer to be used.\n     *\n     * @return the referenced buffer to be used.\n     */\n    public MutableDirectBuffer buffer()\n    {\n        return buffer;\n    }\n\n    /**\n     * The offset in the buffer at which the claimed range begins.\n     *\n     * @return offset in the buffer at which the range begins.\n     */\n    public int offset()\n    {\n        return HEADER_LENGTH;\n    }\n\n    /**\n     * The length of the claimed range in the buffer.\n     *\n     * @return length of the range in the buffer.\n     */\n    public int length()\n    {\n        return buffer.capacity() - HEADER_LENGTH;\n    }\n\n    /**\n     * Get the value of the header type field. The lower 16 bits are valid.\n     *\n     * @return the value of the header type field.\n     * @see io.aeron.protocol.DataHeaderFlyweight\n     */\n    public int headerType()\n    {\n        return buffer.getShort(TYPE_FIELD_OFFSET, LITTLE_ENDIAN) & 0xFFFF;\n    }\n\n    /**\n     * Get the value of the flags field.\n     *\n     * @return the value of the header flags field.\n     * @see io.aeron.protocol.DataHeaderFlyweight\n     */\n    public byte flags()\n    {\n        return buffer.getByte(FLAGS_FIELD_OFFSET);\n    }\n\n    /**\n     * Set the value of the header flags field.\n     *\n     * @param flags value to be set in the header.\n     * @return this for a fluent API.\n     * @see io.aeron.protocol.DataHeaderFlyweight\n     */\n    public BufferClaim flags(final byte flags)\n    {\n        buffer.putByte(FLAGS_FIELD_OFFSET, flags);\n        return this;\n    }\n\n    /**\n     * Set the value of the header type field. The lower 16 bits are valid.\n     *\n     * @param type value to be set in the header.\n     * @return this for a fluent API.\n     * @see io.aeron.protocol.DataHeaderFlyweight\n     */\n    public BufferClaim headerType(final int type)\n    {\n        buffer.putShort(TYPE_FIELD_OFFSET, (short)type, LITTLE_ENDIAN);\n        return this;\n    }\n\n    /**\n     * Get the value stored in the reserve space at the end of a data frame header.\n     * <p>\n     * Note: The value is in {@link ByteOrder#LITTLE_ENDIAN} format.\n     *\n     * @return the value stored in the reserve space at the end of a data frame header.\n     * @see io.aeron.protocol.DataHeaderFlyweight\n     */\n    public long reservedValue()\n    {\n        return buffer.getLong(RESERVED_VALUE_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Write the provided value into the reserved space at the end of the data frame header.\n     * <p>\n     * Note: The value will be written in {@link ByteOrder#LITTLE_ENDIAN} format.\n     *\n     * @param value to be stored in the reserve space at the end of a data frame header.\n     * @return this for fluent API semantics.\n     * @see io.aeron.protocol.DataHeaderFlyweight\n     */\n    public BufferClaim reservedValue(final long value)\n    {\n        buffer.putLong(RESERVED_VALUE_OFFSET, value, LITTLE_ENDIAN);\n        return this;\n    }\n\n    /**\n     * Put bytes into the claimed buffer space for a message. To write multiple parts then use {@link #buffer()}\n     * and {@link #offset()}.\n     *\n     * @param srcBuffer to copy into the claimed space.\n     * @param srcIndex  in the source buffer from which to copy.\n     * @param length    of the source buffer to copy.\n     * @return this for a fluent API.\n     */\n    public BufferClaim putBytes(final DirectBuffer srcBuffer, final int srcIndex, final int length)\n    {\n        buffer.putBytes(HEADER_LENGTH, srcBuffer, srcIndex, length);\n        return this;\n    }\n\n    /**\n     * Commit the message to the log buffer so that is it available to subscribers.\n     */\n    public void commit()\n    {\n        int frameLength = buffer.capacity();\n        if (ByteOrder.nativeOrder() != LITTLE_ENDIAN)\n        {\n            frameLength = Integer.reverseBytes(frameLength);\n        }\n\n        buffer.putIntRelease(FRAME_LENGTH_FIELD_OFFSET, frameLength);\n    }\n\n    /**\n     * Abort a claim of the message space to the log buffer so that the log can progress by ignoring this claim.\n     */\n    public void abort()\n    {\n        int frameLength = buffer.capacity();\n        if (ByteOrder.nativeOrder() != LITTLE_ENDIAN)\n        {\n            frameLength = Integer.reverseBytes(frameLength);\n        }\n\n        buffer.putShort(TYPE_FIELD_OFFSET, (short)HDR_TYPE_PAD, LITTLE_ENDIAN);\n        buffer.putIntRelease(FRAME_LENGTH_FIELD_OFFSET, frameLength);\n    }\n}\n\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/ControlledFragmentHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.DirectBuffer;\n\n/**\n * Handler for reading data that is coming from a log buffer. The frame will either contain a whole message\n * or a fragment of a message to be reassembled. Messages are fragmented if greater than the frame for MTU in length.\n */\n@FunctionalInterface\npublic interface ControlledFragmentHandler\n{\n    /**\n     * Action to be taken on return from {@link #onFragment(DirectBuffer, int, int, Header)}.\n     */\n    enum Action\n    {\n        /**\n         * Abort the current polling operation and do not advance the position for this fragment.\n         */\n        ABORT,\n\n        /**\n         * Break from the current polling operation and commit the position as of the end of the current fragment\n         * being handled.\n         */\n        BREAK,\n\n        /**\n         * Continue processing but commit the position as of the end of the current fragment so that\n         * flow control is applied to this point.\n         */\n        COMMIT,\n\n        /**\n         * Continue processing until fragment limit or no fragments with position commit at end of poll as the in\n         * {@link #onFragment(DirectBuffer, int, int, Header)}.\n         */\n        CONTINUE,\n    }\n\n    /**\n     * Callback for handling fragments of data being read from a log.\n     * <p>\n     * Within this callback reentrant calls to the {@link io.aeron.Aeron} client are not permitted and\n     * will result in undefined behaviour.\n     *\n     * @param buffer containing the data.\n     * @param offset at which the data begins.\n     * @param length of the data in bytes.\n     * @param header representing the metadata for the data.\n     * @return The action to be taken with regard to the stream position after the callback.\n     */\n    Action onFragment(DirectBuffer buffer, int offset, int length, Header header);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/FragmentHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.DirectBuffer;\n\n/**\n * Handler for reading data that is coming from a log buffer. The frame will either contain a whole message\n * or a fragment of a message to be reassembled. Messages are fragmented if greater than the frame for MTU in length.\n */\n@FunctionalInterface\npublic interface FragmentHandler\n{\n    /**\n     * Callback for handling fragments of data being read from a log.\n     * <p>\n     * Within this callback reentrant calls to the {@link io.aeron.Aeron} client are not permitted and\n     * will result in undefined behaviour.\n     *\n     * @param buffer containing the data.\n     * @param offset at which the data begins.\n     * @param length of the data in bytes.\n     * @param header representing the metadata for the data.\n     */\n    void onFragment(DirectBuffer buffer, int offset, int length, Header header);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/FrameDescriptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport io.aeron.protocol.*;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteOrder;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * Description of the structure for message framing in a log buffer.\n * <p>\n * All messages are logged in frames that have a minimum header layout as follows plus a reserve then\n * the encoded message follows:\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |R|                       Frame Length                          |\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------------------------+\n *  |  Version      |B|E| Flags     |             Type              |\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------------------------+\n *  |R|                       Term Offset                           |\n *  +-+-------------------------------------------------------------+\n *  |                      Additional Fields                       ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                        Encoded Message                       ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n * <p>\n * The (B)egin and (E)nd flags are used for message fragmentation. R is for reserved bit.\n * Both (B)egin and (E)nd flags are set for a message that does not span frames.\n */\npublic final class FrameDescriptor\n{\n    private FrameDescriptor()\n    {\n    }\n\n    /**\n     * Set a pragmatic maximum message length regardless of term length to encourage better design.\n     * Messages larger than half the cache size should be broken up into chunks and streamed.\n     */\n    public static final int MAX_MESSAGE_LENGTH = 16 * 1024 * 1024;\n\n    /**\n     * Alignment as a multiple of bytes for each frame. The length field will store the unaligned length in bytes.\n     */\n    public static final int FRAME_ALIGNMENT = 32;\n\n    /**\n     * Beginning fragment of a frame.\n     */\n    public static final byte BEGIN_FRAG_FLAG = (byte)0b1000_0000;\n\n    /**\n     * End fragment of a frame.\n     */\n    public static final byte END_FRAG_FLAG = (byte)0b0100_0000;\n\n    /**\n     * Unfragmented frame.\n     */\n    public static final byte UNFRAGMENTED = BEGIN_FRAG_FLAG | END_FRAG_FLAG;\n\n    /**\n     * Offset within a frame at which the version field begins.\n     */\n    public static final int VERSION_OFFSET = DataHeaderFlyweight.VERSION_FIELD_OFFSET;\n\n    /**\n     * Offset within a frame at which the flags field begins.\n     */\n    public static final int FLAGS_OFFSET = DataHeaderFlyweight.FLAGS_FIELD_OFFSET;\n\n    /**\n     * Offset within a frame at which the type field begins.\n     */\n    public static final int TYPE_OFFSET = DataHeaderFlyweight.TYPE_FIELD_OFFSET;\n\n    /**\n     * Offset within a frame at which the term offset field begins.\n     */\n    public static final int TERM_OFFSET = DataHeaderFlyweight.TERM_OFFSET_FIELD_OFFSET;\n\n    /**\n     * Offset within a frame at which the term id field begins.\n     */\n    public static final int TERM_ID_OFFSET = DataHeaderFlyweight.TERM_ID_FIELD_OFFSET;\n\n    /**\n     * Offset within a frame at which the session id field begins.\n     */\n    public static final int SESSION_ID_OFFSET = DataHeaderFlyweight.SESSION_ID_FIELD_OFFSET;\n\n    /**\n     * Padding frame type to indicate the message should be ignored.\n     */\n    public static final int PADDING_FRAME_TYPE = HeaderFlyweight.HDR_TYPE_PAD;\n\n    /**\n     * Compute the maximum supported message length for a buffer of given termLength.\n     *\n     * @param termLength of the log buffer.\n     * @return the maximum supported length for a message.\n     */\n    public static int computeMaxMessageLength(final int termLength)\n    {\n        return Math.min(termLength >> 3, MAX_MESSAGE_LENGTH);\n    }\n\n    /**\n     * The buffer offset at which the length field begins.\n     *\n     * @param termOffset at which the frame begins.\n     * @return the offset at which the length field begins.\n     */\n    public static int lengthOffset(final int termOffset)\n    {\n        return termOffset;\n    }\n\n    /**\n     * The buffer offset at which the version field begins.\n     *\n     * @param termOffset at which the frame begins.\n     * @return the offset at which the version field begins.\n     */\n    public static int versionOffset(final int termOffset)\n    {\n        return termOffset + VERSION_OFFSET;\n    }\n\n    /**\n     * The buffer offset at which the flags field begins.\n     *\n     * @param termOffset at which the frame begins.\n     * @return the offset at which the flags field begins.\n     */\n    public static int flagsOffset(final int termOffset)\n    {\n        return termOffset + FLAGS_OFFSET;\n    }\n\n    /**\n     * The buffer offset at which the type field begins.\n     *\n     * @param termOffset at which the frame begins.\n     * @return the offset at which the type field begins.\n     */\n    public static int typeOffset(final int termOffset)\n    {\n        return termOffset + TYPE_OFFSET;\n    }\n\n    /**\n     * The buffer offset at which the term offset field begins.\n     *\n     * @param termOffset at which the frame begins.\n     * @return the offset at which the term offset field begins.\n     */\n    public static int termOffsetOffset(final int termOffset)\n    {\n        return termOffset + TERM_OFFSET;\n    }\n\n    /**\n     * The buffer offset at which the term id field begins.\n     *\n     * @param termOffset at which the frame begins.\n     * @return the offset at which the term id field begins.\n     */\n    public static int termIdOffset(final int termOffset)\n    {\n        return termOffset + TERM_ID_OFFSET;\n    }\n\n    /**\n     * The buffer offset at which the session id field begins.\n     *\n     * @param termOffset at which the frame begins.\n     * @return the offset at which the session id field begins.\n     */\n    public static int sessionIdOffset(final int termOffset)\n    {\n        return termOffset + SESSION_ID_OFFSET;\n    }\n\n    /**\n     * Read the type of the frame from header.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @return the value of the frame type header.\n     */\n    public static int frameVersion(final UnsafeBuffer buffer, final int termOffset)\n    {\n        return buffer.getByte(versionOffset(termOffset));\n    }\n\n    /**\n     * Get the flags field for a frame.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @return the value of the flags.\n     */\n    public static byte frameFlags(final UnsafeBuffer buffer, final int termOffset)\n    {\n        return buffer.getByte(flagsOffset(termOffset));\n    }\n\n    /**\n     * Read the type of the frame from header.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @return the value of the frame type header.\n     */\n    public static int frameType(final UnsafeBuffer buffer, final int termOffset)\n    {\n        return buffer.getShort(typeOffset(termOffset), LITTLE_ENDIAN) & 0xFFFF;\n    }\n\n    /**\n     * Is the frame starting at the termOffset a padding frame at the end of a buffer?\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @return true if the frame is a padding frame otherwise false.\n     */\n    public static boolean isPaddingFrame(final UnsafeBuffer buffer, final int termOffset)\n    {\n        return buffer.getShort(typeOffset(termOffset)) == PADDING_FRAME_TYPE;\n    }\n\n    /**\n     * Get the length of a frame from the header.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @return the value for the frame length.\n     */\n    public static int frameLength(final UnsafeBuffer buffer, final int termOffset)\n    {\n        return buffer.getInt(termOffset, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Get the term id of a frame from the header.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @return the value for the term id field.\n     */\n    public static int frameTermId(final UnsafeBuffer buffer, final int termOffset)\n    {\n        return buffer.getInt(termIdOffset(termOffset), LITTLE_ENDIAN);\n    }\n\n    /**\n     * Get the session id of a frame from the header.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @return the value for the session id field.\n     */\n    public static int frameSessionId(final UnsafeBuffer buffer, final int termOffset)\n    {\n        return buffer.getInt(sessionIdOffset(termOffset), LITTLE_ENDIAN);\n    }\n\n    /**\n     * Get the length of a frame from the header as a volatile read.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @return the value for the frame length.\n     */\n    public static int frameLengthVolatile(final UnsafeBuffer buffer, final int termOffset)\n    {\n        int frameLength = buffer.getIntVolatile(termOffset);\n\n        if (ByteOrder.nativeOrder() != LITTLE_ENDIAN)\n        {\n            frameLength = Integer.reverseBytes(frameLength);\n        }\n\n        return frameLength;\n    }\n\n    /**\n     * Write the length header for a frame in a memory ordered fashion.\n     *\n     * @param buffer      containing the frame.\n     * @param termOffset  at which a frame begins.\n     * @param frameLength field to be set for the frame.\n     */\n    public static void frameLengthOrdered(final UnsafeBuffer buffer, final int termOffset, final int frameLength)\n    {\n        int length = frameLength;\n        if (ByteOrder.nativeOrder() != LITTLE_ENDIAN)\n        {\n            length = Integer.reverseBytes(frameLength);\n        }\n\n        buffer.putIntRelease(termOffset, length);\n    }\n\n    /**\n     * Write the type field for a frame.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @param type       type value for the frame.\n     */\n    public static void frameType(final UnsafeBuffer buffer, final int termOffset, final int type)\n    {\n        buffer.putShort(typeOffset(termOffset), (short)type, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Write the flags field for a frame.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @param flags      value for the frame.\n     */\n    public static void frameFlags(final UnsafeBuffer buffer, final int termOffset, final byte flags)\n    {\n        buffer.putByte(flagsOffset(termOffset), flags);\n    }\n\n    /**\n     * Write the term offset field for a frame.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     */\n    public static void frameTermOffset(final UnsafeBuffer buffer, final int termOffset)\n    {\n        buffer.putInt(termOffsetOffset(termOffset), termOffset, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Write the term id field for a frame.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @param termId     value for the frame.\n     */\n    public static void frameTermId(final UnsafeBuffer buffer, final int termOffset, final int termId)\n    {\n        buffer.putInt(termIdOffset(termOffset), termId, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Write the session id field for a frame.\n     *\n     * @param buffer     containing the frame.\n     * @param termOffset at which a frame begins.\n     * @param sessionId  value for the frame.\n     */\n    public static void frameSessionId(final UnsafeBuffer buffer, final int termOffset, final int sessionId)\n    {\n        buffer.putInt(sessionIdOffset(termOffset), sessionId, LITTLE_ENDIAN);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/Header.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport io.aeron.Aeron;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\n\nimport java.nio.ByteOrder;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.protocol.DataHeaderFlyweight.*;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\n\n/**\n * Represents the header of the data frame for accessing metadata fields.\n */\npublic final class Header\n{\n    private Object context;\n    private int positionBitsToShift;\n    private int initialTermId;\n    private int offset = 0;\n    private DirectBuffer buffer;\n    private int fragmentedFrameLength = Aeron.NULL_VALUE;\n\n    /**\n     * Construct a header that references a buffer for the log.\n     *\n     * @param initialTermId       this stream started at.\n     * @param positionBitsToShift for calculating positions.\n     */\n    public Header(final int initialTermId, final int positionBitsToShift)\n    {\n        this(initialTermId, positionBitsToShift, null);\n    }\n\n    /**\n     * Construct a header that references a buffer for the log.\n     *\n     * @param initialTermId       this stream started at.\n     * @param positionBitsToShift for calculating positions.\n     * @param context             for storing state when which can be accessed with {@link #context()}.\n     */\n    public Header(final int initialTermId, final int positionBitsToShift, final Object context)\n    {\n        this.initialTermId = initialTermId;\n        this.positionBitsToShift = positionBitsToShift;\n        this.context = context;\n    }\n\n    /**\n     * Context for storing state related to the context of the callback where the header is used.\n     *\n     * @return context for storing state related to the context of the callback where the header is used.\n     */\n    public Object context()\n    {\n        return context;\n    }\n\n    /**\n     * Assign context for storing state related to the context of the callback where the header is used.\n     *\n     * @param context for storing state related to the context of the callback where the header is used.\n     * @return this for a fluent API.\n     */\n    public Header context(final Object context)\n    {\n        this.context = context;\n        return this;\n    }\n\n    /**\n     * Get the current position to which the image has advanced on reading this message.\n     *\n     * @return the current position to which the image has advanced on reading this message.\n     */\n    public long position()\n    {\n        return computePosition(termId(), nextTermOffset(), positionBitsToShift, initialTermId);\n    }\n\n    /**\n     * The number of times to left shift the term count to multiply by term length.\n     *\n     * @return number of times to left shift the term count to multiply by term length.\n     */\n    public int positionBitsToShift()\n    {\n        return positionBitsToShift;\n    }\n\n    /**\n     * Set the number of times to left shift the term count to multiply by term length.\n     *\n     * @param positionBitsToShift number of times to left shift the term count to multiply by term length.\n     * @return this for a fluent API.\n     */\n    public Header positionBitsToShift(final int positionBitsToShift)\n    {\n        this.positionBitsToShift = positionBitsToShift;\n        return this;\n    }\n\n    /**\n     * Get the initial term id this stream started at.\n     *\n     * @return the initial term id this stream started at.\n     */\n    public int initialTermId()\n    {\n        return initialTermId;\n    }\n\n    /**\n     * Get the initial term id this stream started at.\n     *\n     * @param initialTermId the initial term id this stream started at.\n     * @return this for a fluent API.\n     */\n    public Header initialTermId(final int initialTermId)\n    {\n        this.initialTermId = initialTermId;\n        return this;\n    }\n\n    /**\n     * Set the offset at which the header begins in the buffer.\n     *\n     * @param offset at which the header begins in the buffer.\n     * @return this for a fluent API.\n     */\n    public Header offset(final int offset)\n    {\n        this.offset = offset;\n        return this;\n    }\n\n    /**\n     * The offset at which the frame begins in the buffer.\n     *\n     * @return offset at which the frame begins in the buffer.\n     */\n    public int offset()\n    {\n        return offset;\n    }\n\n    /**\n     * The {@link org.agrona.DirectBuffer} containing the header.\n     *\n     * @return {@link org.agrona.DirectBuffer} containing the header.\n     */\n    public DirectBuffer buffer()\n    {\n        return buffer;\n    }\n\n    /**\n     * The {@link org.agrona.DirectBuffer} containing the header.\n     *\n     * @param buffer {@link org.agrona.DirectBuffer} containing the header.\n     * @return this for a fluent API.\n     */\n    public Header buffer(final DirectBuffer buffer)\n    {\n        if (buffer != this.buffer)\n        {\n            this.buffer = buffer;\n        }\n        return this;\n    }\n\n    /**\n     * The total length of the frame including the header.\n     *\n     * @return the total length of the frame including the header.\n     */\n    public int frameLength()\n    {\n        return buffer.getInt(offset, LITTLE_ENDIAN);\n    }\n\n    /**\n     * The session ID to which the frame belongs.\n     *\n     * @return the session ID to which the frame belongs.\n     */\n    public int sessionId()\n    {\n        return buffer.getInt(offset + SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * The stream ID to which the frame belongs.\n     *\n     * @return the stream ID to which the frame belongs.\n     */\n    public int streamId()\n    {\n        return buffer.getInt(offset + STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * The term ID to which the frame belongs.\n     *\n     * @return the term ID to which the frame belongs.\n     */\n    public int termId()\n    {\n        return buffer.getInt(offset + TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * The offset in the term at which the frame begins.\n     *\n     * @return the offset in the term at which the frame begins.\n     */\n    public int termOffset()\n    {\n        return buffer.getInt(offset + TERM_OFFSET_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Calculates the offset of the frame immediately after this one.\n     *\n     * @return the offset of the next frame.\n     */\n    public int nextTermOffset()\n    {\n        return BitUtil.align(termOffset() + termOccupancyLength(), FRAME_ALIGNMENT);\n    }\n\n    /**\n     * The type of the frame which should always be {@link DataHeaderFlyweight#HDR_TYPE_DATA}.\n     *\n     * @return type of the frame which should always be {@link DataHeaderFlyweight#HDR_TYPE_DATA}.\n     */\n    public int type()\n    {\n        return buffer.getShort(offset + TYPE_FIELD_OFFSET, LITTLE_ENDIAN) & 0xFFFF;\n    }\n\n    /**\n     * The flags for this frame. Valid flags are {@link DataHeaderFlyweight#BEGIN_FLAG}\n     * and {@link DataHeaderFlyweight#END_FLAG}. A convenience flag {@link DataHeaderFlyweight#BEGIN_AND_END_FLAGS}\n     * can be used for both flags.\n     *\n     * @return the flags for this frame.\n     */\n    public byte flags()\n    {\n        return buffer.getByte(offset + FLAGS_FIELD_OFFSET);\n    }\n\n    /**\n     * Get the value stored in the reserve space at the end of a data frame header.\n     * <p>\n     * Note: The value is in {@link ByteOrder#LITTLE_ENDIAN} format.\n     *\n     * @return the value stored in the reserve space at the end of a data frame header.\n     * @see DataHeaderFlyweight\n     */\n    public long reservedValue()\n    {\n        return buffer.getLong(offset + RESERVED_VALUE_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Total amount of space occupied by this message when it is within the term buffer. When fragmented this will\n     * include the length of the header for each fragment. Used when doing reassembly of fragmented packets.\n     *\n     * @param fragmentedFrameLength total fragmented length of the message.\n     */\n    public void fragmentedFrameLength(final int fragmentedFrameLength)\n    {\n        this.fragmentedFrameLength = fragmentedFrameLength;\n    }\n\n    /**\n     * Total amount of space occupied by this message when it is within the term buffer. When fragmented this\n     * will include the length of the header for each fragment. Used when doing reassembly of fragmented packets. If\n     * the packet is not fragmented this will be {@link Aeron#NULL_VALUE}.\n     *\n     * @return total fragmented length of this message or <code>Aeron.NULL_VALUE</code> if not fragmented.\n     */\n    public int fragmentedFrameLength()\n    {\n        return fragmentedFrameLength;\n    }\n\n    private int termOccupancyLength()\n    {\n        return Aeron.NULL_VALUE == fragmentedFrameLength ? frameLength() : fragmentedFrameLength;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/HeaderWriter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.lang.invoke.VarHandle;\nimport java.nio.ByteOrder;\n\nimport static java.lang.Integer.reverseBytes;\nimport static io.aeron.protocol.DataHeaderFlyweight.SESSION_ID_FIELD_OFFSET;\nimport static io.aeron.protocol.DataHeaderFlyweight.STREAM_ID_FIELD_OFFSET;\nimport static io.aeron.protocol.DataHeaderFlyweight.TERM_OFFSET_FIELD_OFFSET;\nimport static io.aeron.protocol.HeaderFlyweight.FRAME_LENGTH_FIELD_OFFSET;\nimport static io.aeron.protocol.HeaderFlyweight.VERSION_FIELD_OFFSET;\n\n/**\n * Utility for applying a header to a message in a term buffer.\n * <p>\n * This class is designed to be thread safe to be used across multiple producers and makes the header\n * visible in the correct order for consumers.\n */\npublic class HeaderWriter\n{\n    final long versionFlagsType;\n    final long sessionId;\n    final long streamId;\n\n    HeaderWriter(final long versionFlagsType, final long sessionId, final long streamId)\n    {\n        this.versionFlagsType = versionFlagsType;\n        this.sessionId = sessionId;\n        this.streamId = streamId;\n    }\n\n    HeaderWriter(final UnsafeBuffer defaultHeader)\n    {\n        versionFlagsType = ((long)defaultHeader.getInt(VERSION_FIELD_OFFSET)) << 32;\n        sessionId = ((long)defaultHeader.getInt(SESSION_ID_FIELD_OFFSET)) << 32;\n        streamId = defaultHeader.getInt(STREAM_ID_FIELD_OFFSET) & 0xFFFF_FFFFL;\n    }\n\n    /**\n     * Create a new {@link HeaderWriter} that is {@link ByteOrder} specific to the platform.\n     *\n     * @param defaultHeader for the stream.\n     * @return a new {@link HeaderWriter} that is {@link ByteOrder} specific to the platform.\n     */\n    public static HeaderWriter newInstance(final UnsafeBuffer defaultHeader)\n    {\n        if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)\n        {\n            return new HeaderWriter(defaultHeader);\n        }\n        else\n        {\n            return new NativeBigEndianHeaderWriter(defaultHeader);\n        }\n    }\n\n    /**\n     * Write a header to the term buffer in {@link ByteOrder#LITTLE_ENDIAN} format using the minimum instructions.\n     *\n     * @param termBuffer to be written to.\n     * @param offset     at which the header should be written.\n     * @param length     of the fragment including the header.\n     * @param termId     of the current term buffer.\n     */\n    public void write(final UnsafeBuffer termBuffer, final int offset, final int length, final int termId)\n    {\n        termBuffer.putLongRelease(offset + FRAME_LENGTH_FIELD_OFFSET, versionFlagsType | ((-length) & 0xFFFF_FFFFL));\n        VarHandle.storeStoreFence();\n\n        termBuffer.putLong(offset + TERM_OFFSET_FIELD_OFFSET, sessionId | offset);\n        termBuffer.putLong(offset + STREAM_ID_FIELD_OFFSET, (((long)termId) << 32) | streamId);\n    }\n}\n\nfinal class NativeBigEndianHeaderWriter extends HeaderWriter\n{\n    NativeBigEndianHeaderWriter(final UnsafeBuffer defaultHeader)\n    {\n        super(\n            defaultHeader.getInt(VERSION_FIELD_OFFSET) & 0xFFFF_FFFFL,\n            defaultHeader.getInt(SESSION_ID_FIELD_OFFSET) & 0xFFFF_FFFFL,\n            ((long)defaultHeader.getInt(STREAM_ID_FIELD_OFFSET)) << 32);\n    }\n\n    public void write(final UnsafeBuffer termBuffer, final int offset, final int length, final int termId)\n    {\n        termBuffer.putLongRelease(\n            offset + FRAME_LENGTH_FIELD_OFFSET, ((((long)reverseBytes(-length))) << 32) | versionFlagsType);\n        VarHandle.storeStoreFence();\n\n        termBuffer.putLong(offset + TERM_OFFSET_FIELD_OFFSET, ((((long)reverseBytes(offset))) << 32) | sessionId);\n        termBuffer.putLong(offset + STREAM_ID_FIELD_OFFSET, streamId | (reverseBytes(termId) & 0xFFFF_FFFFL));\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/LogBufferDescriptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.agrona.BitUtil.*;\n\n/**\n * Layout description for log buffers which contains partitions of terms with associated term metadata,\n * plus ending with overall log metadata.\n * <pre>\n *  +----------------------------+\n *  |           Term 0           |\n *  +----------------------------+\n *  |           Term 1           |\n *  +----------------------------+\n *  |           Term 2           |\n *  +----------------------------+\n *  |        Log Meta Data       |\n *  +----------------------------+\n * </pre>\n */\npublic final class LogBufferDescriptor\n{\n    private static final int PADDING_SIZE = 64;\n\n    /**\n     * The number of partitions the log is divided into.\n     */\n    public static final int PARTITION_COUNT = 3;\n\n    /**\n     * Section index for which buffer contains the log metadata.\n     */\n    public static final int LOG_META_DATA_SECTION_INDEX = PARTITION_COUNT;\n\n    /**\n     * Minimum buffer length for a log term.\n     */\n    public static final int TERM_MIN_LENGTH = 64 * 1024;\n\n    /**\n     * Maximum buffer length for a log term.\n     */\n    public static final int TERM_MAX_LENGTH = 1024 * 1024 * 1024;\n\n    /**\n     * Minimum page size.\n     */\n    public static final int PAGE_MIN_SIZE = 4 * 1024;\n\n    /**\n     * Maximum page size.\n     */\n    public static final int PAGE_MAX_SIZE = 1024 * 1024 * 1024;\n\n    /**\n     * Value for the {@code type} field to indicate a concurrent publication.\n     */\n    public static final byte LOG_BUFFER_TYPE_CONCURRENT_PUBLICATION = 0;\n\n    /**\n     * Value for the {@code type} field to indicate an exclusive publication.\n     */\n    public static final byte LOG_BUFFER_TYPE_EXCLUSIVE_PUBLICATION = 1;\n\n    /**\n     * Value for the {@code type} field to indicate a subscription.\n     */\n    public static final byte LOG_BUFFER_TYPE_PUBLICATION_IMAGE = 2;\n\n    // *******************************\n    // *** Log Meta Data Constants ***\n    // *******************************\n\n    /**\n     * Offset within the metadata where the tail values are stored.\n     */\n    public static final int TERM_TAIL_COUNTERS_OFFSET;\n\n    /**\n     * Offset within the log metadata where the active partition index is stored.\n     */\n    public static final int LOG_ACTIVE_TERM_COUNT_OFFSET;\n\n    /**\n     * Offset within the log metadata where the position of the End of Stream is stored.\n     */\n    public static final int LOG_END_OF_STREAM_POSITION_OFFSET;\n\n    /**\n     * Offset within the log metadata where whether the log is connected or not is stored.\n     */\n    public static final int LOG_IS_CONNECTED_OFFSET;\n\n    /**\n     * Offset within the log metadata where the count of active transports is stored.\n     */\n    public static final int LOG_ACTIVE_TRANSPORT_COUNT;\n\n    /**\n     * Offset within the log metadata where the active term id is stored.\n     */\n    public static final int LOG_INITIAL_TERM_ID_OFFSET;\n\n    /**\n     * Offset within the log metadata which the length field for the frame header is stored.\n     */\n    public static final int LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata which the MTU length is stored.\n     */\n    public static final int LOG_MTU_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata which the correlation id is stored.\n     */\n    public static final int LOG_CORRELATION_ID_OFFSET;\n\n    /**\n     * Offset within the log metadata which the term length is stored.\n     */\n    public static final int LOG_TERM_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata which the page size is stored.\n     */\n    public static final int LOG_PAGE_SIZE_OFFSET;\n\n    /**\n     * Offset at which the default frame headers begin.\n     */\n    public static final int LOG_DEFAULT_FRAME_HEADER_OFFSET;\n\n    /**\n     * Maximum length of a frame header.\n     */\n    public static final int LOG_DEFAULT_FRAME_HEADER_MAX_LENGTH = PADDING_SIZE * 2;\n\n    /**\n     * Offset within the log metadata where the sparse property is stored.\n     */\n    public static final int LOG_SPARSE_OFFSET;\n\n    /**\n     * Offset within the log metadata where the tether property is stored.\n     */\n    public static final int LOG_TETHER_OFFSET;\n\n    /**\n     * Offset within the log metadata where the 'publication revoked' status is indicated.\n     */\n    public static final int LOG_IS_PUBLICATION_REVOKED_OFFSET;\n\n    /**\n     * Offset within the log metadata where the type of the log buffer is stored.\n     *\n     * @see #LOG_BUFFER_TYPE_CONCURRENT_PUBLICATION\n     * @see #LOG_BUFFER_TYPE_EXCLUSIVE_PUBLICATION\n     * @see #LOG_BUFFER_TYPE_PUBLICATION_IMAGE\n     */\n    public static final int LOG_TYPE_OFFSET;\n\n    /**\n     * Offset within the log metadata where the rejoin property is stored.\n     */\n    public static final int LOG_REJOIN_OFFSET;\n\n    /**\n     * Offset within the log metadata where the reliable property is stored.\n     */\n    public static final int LOG_RELIABLE_OFFSET;\n\n    /**\n     * Offset within the log metadata where the socket receive buffer length is stored.\n     */\n    public static final int LOG_SOCKET_RCVBUF_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata where the OS default length for the socket receive buffer is stored.\n     */\n    public static final int LOG_OS_DEFAULT_SOCKET_RCVBUF_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata where the OS maximum length for the socket receive buffer is stored.\n     */\n    public static final int LOG_OS_MAX_SOCKET_RCVBUF_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata where the socket send buffer length is stored.\n     */\n    public static final int LOG_SOCKET_SNDBUF_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata where the OS default length for the socket send buffer is stored.\n     */\n    public static final int LOG_OS_DEFAULT_SOCKET_SNDBUF_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata where the OS maximum length for the socket send buffer is stored.\n     */\n    public static final int LOG_OS_MAX_SOCKET_SNDBUF_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata where the receiver window length is stored.\n     */\n    public static final int LOG_RECEIVER_WINDOW_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata where the publication window length is stored.\n     */\n    public static final int LOG_PUBLICATION_WINDOW_LENGTH_OFFSET;\n\n    /**\n     * Offset within the log metadata where the untethered window limit timeout ns is stored.\n     */\n    public static final int LOG_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_OFFSET;\n\n    /**\n     * Offset within the log metadata where the untethered linger timeout ns is stored.\n     */\n    public static final int LOG_UNTETHERED_LINGER_TIMEOUT_NS_OFFSET;\n\n    /**\n     * Offset within the log metadata where the untethered resting timeout ns is stored.\n     */\n    public static final int LOG_UNTETHERED_RESTING_TIMEOUT_NS_OFFSET;\n\n    /**\n     * Offset within the log metadata where the max resend is stored.\n     */\n    public static final int LOG_MAX_RESEND_OFFSET;\n\n    /**\n     * Offset within the log metadata where the linger timeout ns is stored.\n     */\n    public static final int LOG_LINGER_TIMEOUT_NS_OFFSET;\n\n    /**\n     * Offset within the log metadata where the signal-eos is stored.\n     */\n    public static final int LOG_SIGNAL_EOS_OFFSET;\n\n    /**\n     * Offset within the log metadata where the spies-simulate-connection is stored.\n     */\n    public static final int LOG_SPIES_SIMULATE_CONNECTION_OFFSET;\n\n    /**\n     * Offset within the log metadata where the group is stored.\n     */\n    public static final int LOG_GROUP_OFFSET;\n\n    /**\n     * Offset within the log metadata where the entity tag is stored.\n     */\n    public static final int LOG_ENTITY_TAG_OFFSET;\n\n    /**\n     * Offset within the log metadata where the response correlation id is stored.\n     */\n    public static final int LOG_RESPONSE_CORRELATION_ID_OFFSET;\n\n    /**\n     * Offset within the log metadata where is-response is stored.\n     */\n    public static final int LOG_IS_RESPONSE_OFFSET;\n\n\n    /**\n     * Total length of the log metadata buffer in bytes.\n     * <pre>\n     *   0                   1                   2                   3\n     *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n     *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n     *  |                       Tail Counter 0                          |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |                       Tail Counter 1                          |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |                       Tail Counter 2                          |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |                      Active Term Count                        |\n     *  +---------------------------------------------------------------+\n     *  |                     Cache Line Padding                       ...\n     * ...                                                              |\n     *  +---------------------------------------------------------------+\n     *  |                    End of Stream Position                     |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |                        Is Connected                           |\n     *  +---------------------------------------------------------------+\n     *  |                    Active Transport Count                     |\n     *  +---------------------------------------------------------------+\n     *  |                      Cache Line Padding                      ...\n     * ...                                                              |\n     *  +---------------------------------------------------------------+\n     *  |                 Registration / Correlation ID                 |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |                        Initial Term Id                        |\n     *  +---------------------------------------------------------------+\n     *  |                  Default Frame Header Length                  |\n     *  +---------------------------------------------------------------+\n     *  |                          MTU Length                           |\n     *  +---------------------------------------------------------------+\n     *  |                         Term Length                           |\n     *  +---------------------------------------------------------------+\n     *  |                          Page Size                            |\n     *  +---------------------------------------------------------------+\n     *  |                    Publication Window Length                  |\n     *  +---------------------------------------------------------------+\n     *  |                      Receiver Window Length                   |\n     *  +---------------------------------------------------------------+\n     *  |                    Socket Send Buffer Length                  |\n     *  +---------------------------------------------------------------+\n     *  |               OS Default Socket Send Buffer Length            |\n     *  +---------------------------------------------------------------+\n     *  |                OS Max Socket Send Buffer Length               |\n     *  +---------------------------------------------------------------+\n     *  |                  Socket Receive Buffer Length                 |\n     *  +---------------------------------------------------------------+\n     *  |              OS Default Socket Receive Buffer Length          |\n     *  +---------------------------------------------------------------+\n     *  |               OS Max Socket Receive Buffer Length             |\n     *  +---------------------------------------------------------------+\n     *  |                        Maximum Resend                         |\n     *  +---------------------------------------------------------------+\n     *  |                           Entity tag                          |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |                    Response correlation id                    |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |                     Default Frame Header                     ...\n     * ...                                                              |\n     *  +---------------------------------------------------------------+\n     *  |                        Linger Timeout (ns)                    |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |               Untethered Window Limit Timeout (ns)            |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |                 Untethered Resting Timeout (ns)               |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     *  |                            Group                              |\n     *  +---------------------------------------------------------------+\n     *  |                          Is response                          |\n     *  +---------------------------------------------------------------+\n     *  |                            Rejoin                             |\n     *  +---------------------------------------------------------------+\n     *  |                           Reliable                            |\n     *  +---------------------------------------------------------------+\n     *  |                            Sparse                             |\n     *  +---------------------------------------------------------------+\n     *  |                         Signal EOS                            |\n     *  +---------------------------------------------------------------+\n     *  |                 Spies Simulate Connection                     |\n     *  +---------------------------------------------------------------+\n     *  |                          Tether                               |\n     *  +---------------------------------------------------------------+\n     *  |                     Is publication revoked                    |\n     *  +---------------------------------------------------------------+\n     *  |                         Alignment gap                         |\n     *  +---------------------------------------------------------------+\n     *  |                  Untethered Linger Timeout (ns)               |\n     *  |                                                               |\n     *  +---------------------------------------------------------------+\n     * </pre>\n     */\n    public static final int LOG_META_DATA_LENGTH;\n\n    static\n    {\n        TERM_TAIL_COUNTERS_OFFSET = 0;\n        LOG_ACTIVE_TERM_COUNT_OFFSET = TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * PARTITION_COUNT);\n\n        LOG_END_OF_STREAM_POSITION_OFFSET = PADDING_SIZE * 2;\n        LOG_IS_CONNECTED_OFFSET = LOG_END_OF_STREAM_POSITION_OFFSET + SIZE_OF_LONG;\n        LOG_ACTIVE_TRANSPORT_COUNT = LOG_IS_CONNECTED_OFFSET + SIZE_OF_INT;\n\n        LOG_CORRELATION_ID_OFFSET = PADDING_SIZE * 4;\n        LOG_INITIAL_TERM_ID_OFFSET = LOG_CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n        LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET = LOG_INITIAL_TERM_ID_OFFSET + SIZE_OF_INT;\n        LOG_MTU_LENGTH_OFFSET = LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET + SIZE_OF_INT;\n        LOG_TERM_LENGTH_OFFSET = LOG_MTU_LENGTH_OFFSET + SIZE_OF_INT;\n        LOG_PAGE_SIZE_OFFSET = LOG_TERM_LENGTH_OFFSET + SIZE_OF_INT;\n\n        LOG_PUBLICATION_WINDOW_LENGTH_OFFSET = LOG_PAGE_SIZE_OFFSET + SIZE_OF_INT;\n        LOG_RECEIVER_WINDOW_LENGTH_OFFSET = LOG_PUBLICATION_WINDOW_LENGTH_OFFSET + SIZE_OF_INT;\n        LOG_SOCKET_SNDBUF_LENGTH_OFFSET = LOG_RECEIVER_WINDOW_LENGTH_OFFSET + SIZE_OF_INT;\n        LOG_OS_DEFAULT_SOCKET_SNDBUF_LENGTH_OFFSET = LOG_SOCKET_SNDBUF_LENGTH_OFFSET + SIZE_OF_INT;\n        LOG_OS_MAX_SOCKET_SNDBUF_LENGTH_OFFSET = LOG_OS_DEFAULT_SOCKET_SNDBUF_LENGTH_OFFSET + SIZE_OF_INT;\n        LOG_SOCKET_RCVBUF_LENGTH_OFFSET = LOG_OS_MAX_SOCKET_SNDBUF_LENGTH_OFFSET + SIZE_OF_INT;\n        LOG_OS_DEFAULT_SOCKET_RCVBUF_LENGTH_OFFSET = LOG_SOCKET_RCVBUF_LENGTH_OFFSET + SIZE_OF_INT;\n        LOG_OS_MAX_SOCKET_RCVBUF_LENGTH_OFFSET = LOG_OS_DEFAULT_SOCKET_RCVBUF_LENGTH_OFFSET + SIZE_OF_INT;\n        LOG_MAX_RESEND_OFFSET = LOG_OS_MAX_SOCKET_RCVBUF_LENGTH_OFFSET + SIZE_OF_INT;\n\n        LOG_DEFAULT_FRAME_HEADER_OFFSET = PADDING_SIZE * 5;\n        LOG_ENTITY_TAG_OFFSET = LOG_DEFAULT_FRAME_HEADER_OFFSET + LOG_DEFAULT_FRAME_HEADER_MAX_LENGTH;\n        LOG_RESPONSE_CORRELATION_ID_OFFSET = LOG_ENTITY_TAG_OFFSET + SIZE_OF_LONG;\n        LOG_LINGER_TIMEOUT_NS_OFFSET = LOG_RESPONSE_CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n        LOG_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_OFFSET = LOG_LINGER_TIMEOUT_NS_OFFSET + SIZE_OF_LONG;\n        LOG_UNTETHERED_RESTING_TIMEOUT_NS_OFFSET = LOG_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_OFFSET + SIZE_OF_LONG;\n        LOG_GROUP_OFFSET = LOG_UNTETHERED_RESTING_TIMEOUT_NS_OFFSET + SIZE_OF_LONG;\n        LOG_IS_RESPONSE_OFFSET = LOG_GROUP_OFFSET + SIZE_OF_BYTE;\n        LOG_REJOIN_OFFSET = LOG_IS_RESPONSE_OFFSET + SIZE_OF_BYTE;\n        LOG_RELIABLE_OFFSET = LOG_REJOIN_OFFSET + SIZE_OF_BYTE;\n        LOG_SPARSE_OFFSET = LOG_RELIABLE_OFFSET + SIZE_OF_BYTE;\n        LOG_SIGNAL_EOS_OFFSET = LOG_SPARSE_OFFSET + SIZE_OF_BYTE;\n        LOG_SPIES_SIMULATE_CONNECTION_OFFSET = LOG_SIGNAL_EOS_OFFSET + SIZE_OF_BYTE;\n        LOG_TETHER_OFFSET = LOG_SPIES_SIMULATE_CONNECTION_OFFSET + SIZE_OF_BYTE;\n        LOG_IS_PUBLICATION_REVOKED_OFFSET = LOG_TETHER_OFFSET + SIZE_OF_BYTE;\n        LOG_TYPE_OFFSET = LOG_IS_PUBLICATION_REVOKED_OFFSET + SIZE_OF_BYTE;\n        LOG_UNTETHERED_LINGER_TIMEOUT_NS_OFFSET = LOG_IS_PUBLICATION_REVOKED_OFFSET + SIZE_OF_INT;\n\n        LOG_META_DATA_LENGTH = PAGE_MIN_SIZE;\n    }\n\n    private LogBufferDescriptor()\n    {\n    }\n\n    /**\n     * Check that term length is valid and alignment is valid.\n     *\n     * @param termLength to be checked.\n     * @throws IllegalStateException if the length is not as expected.\n     */\n    public static void checkTermLength(final int termLength)\n    {\n        if (termLength < TERM_MIN_LENGTH)\n        {\n            throw new IllegalStateException(\n                \"Term length less than min length of \" + TERM_MIN_LENGTH + \": length=\" + termLength);\n        }\n\n        if (termLength > TERM_MAX_LENGTH)\n        {\n            throw new IllegalStateException(\n                \"Term length more than max length of \" + TERM_MAX_LENGTH + \": length=\" + termLength);\n        }\n\n        if (!BitUtil.isPowerOfTwo(termLength))\n        {\n            throw new IllegalStateException(\"Term length not a power of 2: length=\" + termLength);\n        }\n    }\n\n    /**\n     * Check that page size is valid and alignment is valid.\n     *\n     * @param pageSize to be checked.\n     * @throws IllegalStateException if the size is not as expected.\n     */\n    public static void checkPageSize(final int pageSize)\n    {\n        if (pageSize < PAGE_MIN_SIZE)\n        {\n            throw new IllegalStateException(\n                \"Page size less than min size of \" + PAGE_MIN_SIZE + \": page size=\" + pageSize);\n        }\n\n        if (pageSize > PAGE_MAX_SIZE)\n        {\n            throw new IllegalStateException(\n                \"Page size more than max size of \" + PAGE_MAX_SIZE + \": page size=\" + pageSize);\n        }\n\n        if (!BitUtil.isPowerOfTwo(pageSize))\n        {\n            throw new IllegalStateException(\"Page size not a power of 2: page size=\" + pageSize);\n        }\n    }\n\n    /**\n     * Get the value of the initial Term id used for this log.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the value of the initial Term id used for this log.\n     */\n    public static int initialTermId(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_INITIAL_TERM_ID_OFFSET);\n    }\n\n    /**\n     * Set the initial term at which this log begins. Initial should be randomised so that stream does not get\n     * reused accidentally.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param initialTermId  value to be set.\n     */\n    public static void initialTermId(final UnsafeBuffer metadataBuffer, final int initialTermId)\n    {\n        metadataBuffer.putInt(LOG_INITIAL_TERM_ID_OFFSET, initialTermId);\n    }\n\n    /**\n     * Get the value of the MTU length used for this log.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the value of the MTU length used for this log.\n     */\n    public static int mtuLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_MTU_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the MTU length used for this log.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param mtuLength      value to be set.\n     */\n    public static void mtuLength(final UnsafeBuffer metadataBuffer, final int mtuLength)\n    {\n        metadataBuffer.putInt(LOG_MTU_LENGTH_OFFSET, mtuLength);\n    }\n\n    /**\n     * Get the value of the Term Length used for this log.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the value of the term length used for this log.\n     */\n    public static int termLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_TERM_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the term length used for this log.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param termLength     value to be set.\n     */\n    public static void termLength(final UnsafeBuffer metadataBuffer, final int termLength)\n    {\n        metadataBuffer.putInt(LOG_TERM_LENGTH_OFFSET, termLength);\n    }\n\n    /**\n     * Get the value of the page size used for this log.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the value of the page size used for this log.\n     */\n    public static int pageSize(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_PAGE_SIZE_OFFSET);\n    }\n\n    /**\n     * Set the page size used for this log.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param pageSize       value to be set.\n     */\n    public static void pageSize(final UnsafeBuffer metadataBuffer, final int pageSize)\n    {\n        metadataBuffer.putInt(LOG_PAGE_SIZE_OFFSET, pageSize);\n    }\n\n    /**\n     * Get the value of the correlation ID for this log relating to the command which created it.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the value of the correlation ID used for this log.\n     */\n    public static long correlationId(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getLong(LOG_CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the correlation ID used for this log relating to the command which created it.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param id             value to be set.\n     */\n    public static void correlationId(final UnsafeBuffer metadataBuffer, final long id)\n    {\n        metadataBuffer.putLong(LOG_CORRELATION_ID_OFFSET, id);\n    }\n\n    /**\n     * Get whether the log is considered connected or not by the driver.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return whether the log is considered connected or not by the driver.\n     */\n    public static boolean isConnected(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getIntVolatile(LOG_IS_CONNECTED_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether the log is considered connected or not by the driver.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param isConnected    or not.\n     */\n    public static void isConnected(final UnsafeBuffer metadataBuffer, final boolean isConnected)\n    {\n        metadataBuffer.putIntRelease(LOG_IS_CONNECTED_OFFSET, isConnected ? 1 : 0);\n    }\n\n    /**\n     * Get the count of active transports for the Image.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return count of active transports.\n     */\n    public static int activeTransportCount(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getIntVolatile(LOG_ACTIVE_TRANSPORT_COUNT);\n    }\n\n    /**\n     * Set the number of active transports for the Image.\n     *\n     * @param metadataBuffer           containing the meta data.\n     * @param numberOfActiveTransports value to be set.\n     */\n    public static void activeTransportCount(final UnsafeBuffer metadataBuffer, final int numberOfActiveTransports)\n    {\n        metadataBuffer.putIntRelease(LOG_ACTIVE_TRANSPORT_COUNT, numberOfActiveTransports);\n    }\n\n    /**\n     * Get the value of the end of stream position.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the value of end of stream position\n     */\n    public static long endOfStreamPosition(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getLongVolatile(LOG_END_OF_STREAM_POSITION_OFFSET);\n    }\n\n    /**\n     * Set the value of the end of stream position.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param position       value of the end of stream position.\n     */\n    public static void endOfStreamPosition(final UnsafeBuffer metadataBuffer, final long position)\n    {\n        metadataBuffer.putLongRelease(LOG_END_OF_STREAM_POSITION_OFFSET, position);\n    }\n\n    /**\n     * Get the value of the active term count used by the producer of this log. Consumers may have a different\n     * active term count if they are running behind. The read is done with volatile semantics.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the value of the active term count used by the producer of this log.\n     */\n    public static int activeTermCount(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getIntVolatile(LOG_ACTIVE_TERM_COUNT_OFFSET);\n    }\n\n    /**\n     * Set the value of the current active term count for the producer using memory release semantics.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param termCount      value of the active term count used by the producer of this log.\n     */\n    public static void activeTermCountOrdered(final UnsafeBuffer metadataBuffer, final int termCount)\n    {\n        metadataBuffer.putIntRelease(LOG_ACTIVE_TERM_COUNT_OFFSET, termCount);\n    }\n\n    /**\n     * Compare and set the value of the current active term count.\n     *\n     * @param metadataBuffer    containing the meta data.\n     * @param expectedTermCount value of the active term count expected in the log.\n     * @param updateTermCount   value of the active term count to be updated in the log.\n     * @return true if successful otherwise false.\n     */\n    public static boolean casActiveTermCount(\n        final UnsafeBuffer metadataBuffer, final int expectedTermCount, final int updateTermCount)\n    {\n        return metadataBuffer.compareAndSetInt(LOG_ACTIVE_TERM_COUNT_OFFSET, expectedTermCount, updateTermCount);\n    }\n\n    /**\n     * Set the value of the current active partition index for the producer.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param termCount      value of the active term count used by the producer of this log.\n     */\n    public static void activeTermCount(final UnsafeBuffer metadataBuffer, final int termCount)\n    {\n        metadataBuffer.putInt(LOG_ACTIVE_TERM_COUNT_OFFSET, termCount);\n    }\n\n    /**\n     * Rotate to the next partition in sequence for the term id.\n     *\n     * @param currentIndex partition index.\n     * @return the next partition index.\n     */\n    public static int nextPartitionIndex(final int currentIndex)\n    {\n        return (currentIndex + 1) % PARTITION_COUNT;\n    }\n\n    /**\n     * Determine the partition index to be used given the initial term and active term ids.\n     *\n     * @param initialTermId at which the log buffer usage began.\n     * @param activeTermId  that is in current usage.\n     * @return the index of which buffer should be used.\n     */\n    public static int indexByTerm(final int initialTermId, final int activeTermId)\n    {\n        return (activeTermId - initialTermId) % PARTITION_COUNT;\n    }\n\n    /**\n     * Determine the partition index based on number of terms that have passed.\n     *\n     * @param termCount for the number of terms that have passed.\n     * @return the partition index for the term count.\n     */\n    public static int indexByTermCount(final long termCount)\n    {\n        return (int)(termCount % PARTITION_COUNT);\n    }\n\n    /**\n     * Determine the partition index given a stream position.\n     *\n     * @param position            in the stream in bytes.\n     * @param positionBitsToShift number of times to left shift the term count to multiply by term length.\n     * @return the partition index for the position.\n     */\n    public static int indexByPosition(final long position, final int positionBitsToShift)\n    {\n        return (int)((position >>> positionBitsToShift) % PARTITION_COUNT);\n    }\n\n    /**\n     * Compute the current position in absolute number of bytes.\n     *\n     * @param activeTermId        active term id.\n     * @param termOffset          in the term.\n     * @param positionBitsToShift number of times to left shift the term count to multiply by term length.\n     * @param initialTermId       the initial term id that this stream started on.\n     * @return the absolute position in bytes.\n     */\n    public static long computePosition(\n        final int activeTermId, final int termOffset, final int positionBitsToShift, final int initialTermId)\n    {\n        final long termCount = activeTermId - initialTermId; // copes with negative activeTermId on rollover\n\n        return (termCount << positionBitsToShift) + termOffset;\n    }\n\n    /**\n     * Compute the current position in absolute number of bytes for the beginning of a term.\n     *\n     * @param activeTermId        active term id.\n     * @param positionBitsToShift number of times to left shift the term count to multiply by term length.\n     * @param initialTermId       the initial term id that this stream started on.\n     * @return the absolute position in bytes.\n     */\n    public static long computeTermBeginPosition(\n        final int activeTermId, final int positionBitsToShift, final int initialTermId)\n    {\n        final long termCount = activeTermId - initialTermId; // copes with negative activeTermId on rollover\n\n        return termCount << positionBitsToShift;\n    }\n\n    /**\n     * Compute the term id from a position.\n     *\n     * @param position            to calculate from\n     * @param positionBitsToShift number of times to left shift the term count to multiply by term length.\n     * @param initialTermId       the initial term id that this stream started on.\n     * @return the term id according to the position.\n     */\n    public static int computeTermIdFromPosition(\n        final long position, final int positionBitsToShift, final int initialTermId)\n    {\n        return (int)(position >>> positionBitsToShift) + initialTermId;\n    }\n\n    /**\n     * Compute the total length of a log file given the term length.\n     * <p>\n     * Assumes {@link #TERM_MAX_LENGTH} is 1 GB and that filePageSize is 1 GB or less and a power of 2.\n     *\n     * @param termLength   on which to base the calculation.\n     * @param filePageSize to use for log.\n     * @return the total length of the log file.\n     */\n    public static long computeLogLength(final int termLength, final int filePageSize)\n    {\n        return align((PARTITION_COUNT * (long)termLength) + LOG_META_DATA_LENGTH, filePageSize);\n    }\n\n    /**\n     * Store the default frame header to the log metadata buffer.\n     *\n     * @param metadataBuffer into which the default headers should be stored.\n     * @param defaultHeader  to be stored.\n     * @throws IllegalArgumentException if the defaultHeader larger than {@link #LOG_DEFAULT_FRAME_HEADER_MAX_LENGTH}.\n     */\n    public static void storeDefaultFrameHeader(final UnsafeBuffer metadataBuffer, final DirectBuffer defaultHeader)\n    {\n        if (defaultHeader.capacity() != HEADER_LENGTH)\n        {\n            throw new IllegalArgumentException(\n                \"Default header length not equal to HEADER_LENGTH: length=\" + defaultHeader.capacity());\n        }\n\n        metadataBuffer.putInt(LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET, HEADER_LENGTH);\n        metadataBuffer.putBytes(LOG_DEFAULT_FRAME_HEADER_OFFSET, defaultHeader, 0, HEADER_LENGTH);\n    }\n\n    /**\n     * Get a wrapper around the default frame header from the log metadata.\n     *\n     * @param metadataBuffer containing the raw bytes for the default frame header.\n     * @return a buffer wrapping the raw bytes.\n     */\n    public static UnsafeBuffer defaultFrameHeader(final UnsafeBuffer metadataBuffer)\n    {\n        return new UnsafeBuffer(metadataBuffer, LOG_DEFAULT_FRAME_HEADER_OFFSET, HEADER_LENGTH);\n    }\n\n    /**\n     * Apply the default header for a message in a term.\n     *\n     * @param metadataBuffer containing the default headers.\n     * @param termBuffer     to which the default header should be applied.\n     * @param termOffset     at which the default should be applied.\n     */\n    public static void applyDefaultHeader(\n        final UnsafeBuffer metadataBuffer, final UnsafeBuffer termBuffer, final int termOffset)\n    {\n        termBuffer.putBytes(termOffset, metadataBuffer, LOG_DEFAULT_FRAME_HEADER_OFFSET, HEADER_LENGTH);\n    }\n\n    /**\n     * Rotate the log and update the tail counter for the new term.\n     * <p>\n     * This method is safe for concurrent use.\n     *\n     * @param metadataBuffer for the log.\n     * @param termCount      from which to rotate.\n     * @param termId         to be used in the default headers.\n     * @return true if log was rotated.\n     */\n    public static boolean rotateLog(final UnsafeBuffer metadataBuffer, final int termCount, final int termId)\n    {\n        final int nextTermId = termId + 1;\n        final int nextTermCount = termCount + 1;\n        final int nextIndex = indexByTermCount(nextTermCount);\n        final int expectedTermId = nextTermId - PARTITION_COUNT;\n\n        long rawTail;\n        do\n        {\n            rawTail = rawTailVolatile(metadataBuffer, nextIndex);\n            if (expectedTermId != termId(rawTail))\n            {\n                break;\n            }\n        }\n        while (!casRawTail(metadataBuffer, nextIndex, rawTail, packTail(nextTermId, 0)));\n\n        return casActiveTermCount(metadataBuffer, termCount, nextTermCount);\n    }\n\n    /**\n     * Set the initial value for the termId in the upper bits of the tail counter.\n     *\n     * @param metadataBuffer contain the tail counter.\n     * @param partitionIndex to be initialised.\n     * @param termId         to be set.\n     */\n    public static void initialiseTailWithTermId(\n        final UnsafeBuffer metadataBuffer, final int partitionIndex, final int termId)\n    {\n        metadataBuffer.putLong(TERM_TAIL_COUNTERS_OFFSET + (partitionIndex * SIZE_OF_LONG), packTail(termId, 0));\n    }\n\n    /**\n     * Get the termId from a packed raw tail value.\n     *\n     * @param rawTail containing the termId.\n     * @return the termId from a packed raw tail value.\n     */\n    public static int termId(final long rawTail)\n    {\n        return (int)(rawTail >> 32);\n    }\n\n    /**\n     * Read the termOffset from a packed raw tail value.\n     *\n     * @param rawTail    containing the termOffset.\n     * @param termLength that the offset cannot exceed.\n     * @return the termOffset value.\n     */\n    public static int termOffset(final long rawTail, final long termLength)\n    {\n        final long tail = rawTail & 0xFFFF_FFFFL;\n\n        return (int)Math.min(tail, termLength);\n    }\n\n    /**\n     * The termOffset as a result of the append operation.\n     *\n     * @param result into which the termOffset value has been packed.\n     * @return the termOffset after the append operation.\n     */\n    public static int termOffset(final long result)\n    {\n        return (int)result;\n    }\n\n    /**\n     * Pack a termId and termOffset into a raw tail value.\n     *\n     * @param termId     to be packed.\n     * @param termOffset to be packed.\n     * @return the packed value.\n     */\n    public static long packTail(final int termId, final int termOffset)\n    {\n        return ((long)termId << 32) | termOffset;\n    }\n\n    /**\n     * Set the raw value of the tail for the given partition.\n     *\n     * @param metadataBuffer containing the tail counters.\n     * @param partitionIndex for the tail counter.\n     * @param rawTail        to be stored.\n     */\n    public static void rawTail(final UnsafeBuffer metadataBuffer, final int partitionIndex, final long rawTail)\n    {\n        metadataBuffer.putLong(TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex), rawTail);\n    }\n\n    /**\n     * Get the raw value of the tail for the given partition.\n     *\n     * @param metadataBuffer containing the tail counters.\n     * @param partitionIndex for the tail counter.\n     * @return the raw value of the tail for the current active partition.\n     */\n    public static long rawTail(final UnsafeBuffer metadataBuffer, final int partitionIndex)\n    {\n        return metadataBuffer.getLong(TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex));\n    }\n\n    /**\n     * Set the raw value of the tail for the given partition.\n     *\n     * @param metadataBuffer containing the tail counters.\n     * @param partitionIndex for the tail counter.\n     * @param rawTail        to be stored.\n     */\n    public static void rawTailVolatile(final UnsafeBuffer metadataBuffer, final int partitionIndex, final long rawTail)\n    {\n        metadataBuffer.putLongVolatile(TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex), rawTail);\n    }\n\n    /**\n     * Get the raw value of the tail for the given partition.\n     *\n     * @param metadataBuffer containing the tail counters.\n     * @param partitionIndex for the tail counter.\n     * @return the raw value of the tail for the current active partition.\n     */\n    public static long rawTailVolatile(final UnsafeBuffer metadataBuffer, final int partitionIndex)\n    {\n        return metadataBuffer.getLongVolatile(TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex));\n    }\n\n    /**\n     * Get the raw value of the tail for the current active partition.\n     *\n     * @param metadataBuffer containing the tail counters.\n     * @return the raw value of the tail for the current active partition.\n     */\n    public static long rawTailVolatile(final UnsafeBuffer metadataBuffer)\n    {\n        final int partitionIndex = indexByTermCount(activeTermCount(metadataBuffer));\n        return metadataBuffer.getLongVolatile(TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex));\n    }\n\n    /**\n     * Compare and set the raw value of the tail for the given partition.\n     *\n     * @param metadataBuffer  containing the tail counters.\n     * @param partitionIndex  for the tail counter.\n     * @param expectedRawTail expected current value.\n     * @param updateRawTail   to be applied.\n     * @return true if the update was successful otherwise false.\n     */\n    public static boolean casRawTail(\n        final UnsafeBuffer metadataBuffer,\n        final int partitionIndex,\n        final long expectedRawTail,\n        final long updateRawTail)\n    {\n        final int index = TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex);\n        return metadataBuffer.compareAndSetLong(index, expectedRawTail, updateRawTail);\n    }\n\n    /**\n     * Get the number of bits to shift when dividing or multiplying by the term buffer length.\n     *\n     * @param termBufferLength to compute the number of bits to shift for.\n     * @return the number of bits to shift to divide or multiply by the term buffer length.\n     */\n    public static int positionBitsToShift(final int termBufferLength)\n    {\n        return switch (termBufferLength)\n        {\n            case 64 * 1024 -> 16;\n            case 128 * 1024 -> 17;\n            case 256 * 1024 -> 18;\n            case 512 * 1024 -> 19;\n            case 1024 * 1024 -> 20;\n            case 2 * 1024 * 1024 -> 21;\n            case 4 * 1024 * 1024 -> 22;\n            case 8 * 1024 * 1024 -> 23;\n            case 16 * 1024 * 1024 -> 24;\n            case 32 * 1024 * 1024 -> 25;\n            case 64 * 1024 * 1024 -> 26;\n            case 128 * 1024 * 1024 -> 27;\n            case 256 * 1024 * 1024 -> 28;\n            case 512 * 1024 * 1024 -> 29;\n            case 1024 * 1024 * 1024 -> 30;\n            default -> throw new IllegalArgumentException(\"invalid term buffer length: \" + termBufferLength);\n        };\n    }\n\n    /**\n     * Compute frame length for a message that is fragmented into chunks of {@code maxPayloadSize}.\n     *\n     * @param length         of the message.\n     * @param maxPayloadSize fragment size without the header.\n     * @return message length after fragmentation.\n     */\n    public static int computeFragmentedFrameLength(final int length, final int maxPayloadSize)\n    {\n        final int numMaxPayloads = length / maxPayloadSize;\n        final int remainingPayload = length % maxPayloadSize;\n        final int lastFrameLength =\n            remainingPayload > 0 ? align(remainingPayload + HEADER_LENGTH, FRAME_ALIGNMENT) : 0;\n\n        return (numMaxPayloads * (maxPayloadSize + HEADER_LENGTH)) + lastFrameLength;\n    }\n\n    /**\n     * Compute frame length for a message that has been reassembled from chunks of {@code maxPayloadSize}.\n     *\n     * @param length         of the message.\n     * @param maxPayloadSize fragment size without the header.\n     * @return message length after fragmentation.\n     */\n    public static int computeAssembledFrameLength(final int length, final int maxPayloadSize)\n    {\n        final int numMaxPayloads = length / maxPayloadSize;\n        final int remainingPayload = length % maxPayloadSize;\n\n        return HEADER_LENGTH + (numMaxPayloads * maxPayloadSize) + remainingPayload;\n    }\n\n    /**\n     * Get whether the log is sparse from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return true if the log is sparse, otherwise false.\n     */\n    public static boolean sparse(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_SPARSE_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether the log is sparse in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          true if the log is sparse, otherwise false.\n     */\n    public static void sparse(final UnsafeBuffer metadataBuffer, final boolean value)\n    {\n        metadataBuffer.putByte(LOG_SPARSE_OFFSET, (byte)(value ? 1 : 0));\n    }\n\n    /**\n     * Get whether the log is tethered from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return true if the log is tethered, otherwise false.\n     */\n    public static boolean tether(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_TETHER_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether the log is tethered in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          true if the log is tethered, otherwise false.\n     */\n    public static void tether(final UnsafeBuffer metadataBuffer, final boolean value)\n    {\n        metadataBuffer.putByte(LOG_TETHER_OFFSET, (byte)(value ? 1 : 0));\n    }\n\n    /**\n     * Get whether the log's publication was revoked.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return true if the log's publication was revoked, otherwise false.\n     */\n    public static boolean isPublicationRevoked(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_IS_PUBLICATION_REVOKED_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether the log's publication was revoked.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          true if the log's publication was revoked, otherwise false.\n     */\n    public static void isPublicationRevoked(final UnsafeBuffer metadataBuffer, final boolean value)\n    {\n        metadataBuffer.putByte(LOG_IS_PUBLICATION_REVOKED_OFFSET, (byte)(value ? 1 : 0));\n    }\n\n    /**\n     * Get type information from this log buffer.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return one of the {@link #LOG_BUFFER_TYPE_CONCURRENT_PUBLICATION},\n     * {@link #LOG_BUFFER_TYPE_EXCLUSIVE_PUBLICATION} or {@link #LOG_BUFFER_TYPE_PUBLICATION_IMAGE}\n     */\n    public static byte type(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_TYPE_OFFSET);\n    }\n\n    /**\n     * Set {@code type} information for this log buffer.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          one of the {@link #LOG_BUFFER_TYPE_CONCURRENT_PUBLICATION},\n     *                       {@link #LOG_BUFFER_TYPE_EXCLUSIVE_PUBLICATION} or\n     *                       {@link #LOG_BUFFER_TYPE_PUBLICATION_IMAGE}.\n     */\n    public static void type(final UnsafeBuffer metadataBuffer, final byte value)\n    {\n        metadataBuffer.putByte(LOG_TYPE_OFFSET, value);\n    }\n\n    /**\n     * Get whether the log is group from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return true if the log is group, otherwise false.\n     */\n    public static boolean group(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_GROUP_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether the log is group in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          true if the log is group, otherwise false.\n     */\n    public static void group(final UnsafeBuffer metadataBuffer, final boolean value)\n    {\n        metadataBuffer.putByte(LOG_GROUP_OFFSET, (byte)(value ? 1 : 0));\n    }\n\n    /**\n     * Get whether the log is response from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return true if the log is group, otherwise false.\n     */\n    public static boolean isResponse(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_IS_RESPONSE_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether the log is response in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          true if the log is group, otherwise false.\n     */\n    public static void isResponse(final UnsafeBuffer metadataBuffer, final boolean value)\n    {\n        metadataBuffer.putByte(LOG_IS_RESPONSE_OFFSET, (byte)(value ? 1 : 0));\n    }\n\n\n    /**\n     * Get whether the log is rejoining from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return true if the log is rejoining, otherwise false.\n     */\n    public static boolean rejoin(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_REJOIN_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether the log is rejoining in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          true if the log is rejoining, otherwise false.\n     */\n    public static void rejoin(final UnsafeBuffer metadataBuffer, final boolean value)\n    {\n        metadataBuffer.putByte(LOG_REJOIN_OFFSET, (byte)(value ? 1 : 0));\n    }\n\n    /**\n     * Get whether the log is reliable from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return true if the log is reliable, otherwise false.\n     */\n    public static boolean reliable(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_RELIABLE_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether the log is reliable in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          true if the log is reliable, otherwise false.\n     */\n    public static void reliable(final UnsafeBuffer metadataBuffer, final boolean value)\n    {\n        metadataBuffer.putByte(LOG_RELIABLE_OFFSET, (byte)(value ? 1 : 0));\n    }\n\n    /**\n     * Get the socket receive buffer length from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the socket receive buffer length.\n     */\n    public static int socketRcvbufLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_SOCKET_RCVBUF_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the socket receive buffer length in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the socket receive buffer length to set.\n     */\n    public static void socketRcvbufLength(final UnsafeBuffer metadataBuffer, final int value)\n    {\n        metadataBuffer.putInt(LOG_SOCKET_RCVBUF_LENGTH_OFFSET, value);\n    }\n\n    /**\n     * Get the default length in bytes for the socket receive buffer as per OS configuration from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the default length in bytes for the socket receive buffer.\n     */\n    public static int osDefaultSocketRcvbufLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_OS_DEFAULT_SOCKET_RCVBUF_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the default length for the socket receive buffer as per OS configuration in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the default length in bytes for the socket receive buffer.\n     */\n    public static void osDefaultSocketRcvbufLength(final UnsafeBuffer metadataBuffer, final int value)\n    {\n        metadataBuffer.putInt(LOG_OS_DEFAULT_SOCKET_RCVBUF_LENGTH_OFFSET, value);\n    }\n\n    /**\n     * Get the maximum length in bytes for the socket receive buffer as per OS configuration from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the maximum allowed length in bytes for the socket receive buffer.\n     */\n    public static int osMaxSocketRcvbufLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_OS_MAX_SOCKET_RCVBUF_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the maximum allowed length in bytes for the socket receive buffer as per OS configuration in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the maximum allowed length in bytes for the socket receive buffer.\n     */\n    public static void osMaxSocketRcvbufLength(final UnsafeBuffer metadataBuffer, final int value)\n    {\n        metadataBuffer.putInt(LOG_OS_MAX_SOCKET_RCVBUF_LENGTH_OFFSET, value);\n    }\n\n    /**\n     * Get the socket send buffer length from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the socket send buffer length.\n     */\n    public static int socketSndbufLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_SOCKET_SNDBUF_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the socket send buffer length in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the socket send buffer length to set.\n     */\n    public static void socketSndbufLength(final UnsafeBuffer metadataBuffer, final int value)\n    {\n        metadataBuffer.putInt(LOG_SOCKET_SNDBUF_LENGTH_OFFSET, value);\n    }\n\n    /**\n     * Get the default length in bytes for the socket send buffer as per OS configuration from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the default length in bytes for the socket send buffer.\n     */\n    public static int osDefaultSocketSndbufLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_OS_DEFAULT_SOCKET_SNDBUF_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the default length for the socket send buffer as per OS configuration in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the default length in bytes for the socket send buffer.\n     */\n    public static void osDefaultSocketSndbufLength(final UnsafeBuffer metadataBuffer, final int value)\n    {\n        metadataBuffer.putInt(LOG_OS_DEFAULT_SOCKET_SNDBUF_LENGTH_OFFSET, value);\n    }\n\n    /**\n     * Get the maximum length in bytes for the socket send buffer as per OS configuration from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the maximum allowed length in bytes for the socket send buffer.\n     */\n    public static int osMaxSocketSndbufLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_OS_MAX_SOCKET_SNDBUF_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the maximum allowed length in bytes for the socket send buffer as per OS configuration in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the maximum allowed length in bytes for the socket send buffer.\n     */\n    public static void osMaxSocketSndbufLength(final UnsafeBuffer metadataBuffer, final int value)\n    {\n        metadataBuffer.putInt(LOG_OS_MAX_SOCKET_SNDBUF_LENGTH_OFFSET, value);\n    }\n\n    /**\n     * Get the receiver window length from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the receiver window length.\n     */\n    public static int receiverWindowLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_RECEIVER_WINDOW_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the receiver window length in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the receiver window length to set.\n     */\n    public static void receiverWindowLength(final UnsafeBuffer metadataBuffer, final int value)\n    {\n        metadataBuffer.putInt(LOG_RECEIVER_WINDOW_LENGTH_OFFSET, value);\n    }\n\n    /**\n     * Get the publication window length from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the publication window length.\n     */\n    public static int publicationWindowLength(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_PUBLICATION_WINDOW_LENGTH_OFFSET);\n    }\n\n    /**\n     * Set the publication window length in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the publication window length to set.\n     */\n    public static void publicationWindowLength(final UnsafeBuffer metadataBuffer, final int value)\n    {\n        metadataBuffer.putInt(LOG_PUBLICATION_WINDOW_LENGTH_OFFSET, value);\n    }\n\n    /**\n     * Get the untethered window limit timeout in nanoseconds from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the untethered window limit timeout in nanoseconds.\n     */\n    public static long untetheredWindowLimitTimeoutNs(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getLong(LOG_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_OFFSET);\n    }\n\n    /**\n     * Set the untethered window limit timeout in nanoseconds in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the untethered window limit timeout to set.\n     */\n    public static void untetheredWindowLimitTimeoutNs(final UnsafeBuffer metadataBuffer, final long value)\n    {\n        metadataBuffer.putLong(LOG_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_OFFSET, value);\n    }\n\n    /**\n     * Get the untethered linger timeout in nanoseconds from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the untethered window limit timeout in nanoseconds.\n     */\n    public static long untetheredLingerTimeoutNs(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getLong(LOG_UNTETHERED_LINGER_TIMEOUT_NS_OFFSET);\n    }\n\n    /**\n     * Set the untethered linger timeout in nanoseconds in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the untethered linger timeout to set.\n     */\n    public static void untetheredLingerTimeoutNs(final UnsafeBuffer metadataBuffer, final long value)\n    {\n        metadataBuffer.putLong(LOG_UNTETHERED_LINGER_TIMEOUT_NS_OFFSET, value);\n    }\n\n    /**\n     * Get the untethered resting timeout in nanoseconds from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the untethered resting timeout in nanoseconds.\n     */\n    public static long untetheredRestingTimeoutNs(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getLong(LOG_UNTETHERED_RESTING_TIMEOUT_NS_OFFSET);\n    }\n\n    /**\n     * Set the untethered resting timeout in nanoseconds in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the untethered resting timeout to set.\n     */\n    public static void untetheredRestingTimeoutNs(final UnsafeBuffer metadataBuffer, final long value)\n    {\n        metadataBuffer.putLong(LOG_UNTETHERED_RESTING_TIMEOUT_NS_OFFSET, value);\n    }\n\n    /**\n     * Get the maximum resend count from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the maximum resend count.\n     */\n    public static int maxResend(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getInt(LOG_MAX_RESEND_OFFSET);\n    }\n\n    /**\n     * Set the maximum resend count in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the maximum resend count to set.\n     */\n    public static void maxResend(final UnsafeBuffer metadataBuffer, final int value)\n    {\n        metadataBuffer.putInt(LOG_MAX_RESEND_OFFSET, value);\n    }\n\n    /**\n     * Get the linger timeout in nanoseconds from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the linger timeout in nanoseconds.\n     */\n    public static long lingerTimeoutNs(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getLong(LOG_LINGER_TIMEOUT_NS_OFFSET);\n    }\n\n    /**\n     * Set the linger timeout in nanoseconds in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the linger timeout to set.\n     */\n    public static void lingerTimeoutNs(final UnsafeBuffer metadataBuffer, final long value)\n    {\n        metadataBuffer.putLong(LOG_LINGER_TIMEOUT_NS_OFFSET, value);\n    }\n\n    /**\n     * Get the entity tag  from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the entity tag in nanoseconds.\n     */\n    public static long entityTag(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getLong(LOG_ENTITY_TAG_OFFSET);\n    }\n\n    /**\n     * Set the entity tag in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the entity tag to set.\n     */\n    public static void entityTag(final UnsafeBuffer metadataBuffer, final long value)\n    {\n        metadataBuffer.putLong(LOG_ENTITY_TAG_OFFSET, value);\n    }\n\n    /**\n     * Get the response correlation id  from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return the entity tag in nanoseconds.\n     */\n    public static long responseCorrelationId(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getLong(LOG_RESPONSE_CORRELATION_ID_OFFSET);\n    }\n\n    /**\n     * Set the response correlation id in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          the response correlation id to set.\n     */\n    public static void responseCorrelationId(final UnsafeBuffer metadataBuffer, final long value)\n    {\n        metadataBuffer.putLong(LOG_RESPONSE_CORRELATION_ID_OFFSET, value);\n    }\n\n    /**\n     * Get whether the signal EOS is enabled from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return true if signal EOS is enabled, otherwise false.\n     */\n    public static boolean signalEos(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_SIGNAL_EOS_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether the signal EOS is enabled in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          true if signal EOS is enabled, otherwise false.\n     */\n    public static void signalEos(final UnsafeBuffer metadataBuffer, final boolean value)\n    {\n        metadataBuffer.putByte(LOG_SIGNAL_EOS_OFFSET, (byte)(value ? 1 : 0));\n    }\n\n    /**\n     * Get whether spies simulate connection from the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @return true if spies simulate connection, otherwise false.\n     */\n    public static boolean spiesSimulateConnection(final UnsafeBuffer metadataBuffer)\n    {\n        return metadataBuffer.getByte(LOG_SPIES_SIMULATE_CONNECTION_OFFSET) == 1;\n    }\n\n    /**\n     * Set whether spies simulate connection in the metadata.\n     *\n     * @param metadataBuffer containing the meta data.\n     * @param value          true if spies simulate connection, otherwise false.\n     */\n    public static void spiesSimulateConnection(final UnsafeBuffer metadataBuffer, final boolean value)\n    {\n        metadataBuffer.putByte(LOG_SPIES_SIMULATE_CONNECTION_OFFSET, (byte)(value ? 1 : 0));\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/LogBufferUnblocker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\n\n/**\n * Provides the functionality to unblock a log at a given position.\n */\npublic final class LogBufferUnblocker\n{\n    private LogBufferUnblocker()\n    {\n    }\n\n    /**\n     * Attempt to unblock a log buffer at given position.\n     *\n     * @param termBuffers       for current blockedOffset.\n     * @param logMetaDataBuffer for log buffer.\n     * @param blockedPosition   to attempt to unblock.\n     * @param termLength        of the buffer for each term in the log.\n     * @return whether attempt was made to unblock.\n     */\n    public static boolean unblock(\n        final UnsafeBuffer[] termBuffers,\n        final UnsafeBuffer logMetaDataBuffer,\n        final long blockedPosition,\n        final int termLength)\n    {\n        final int positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n        final int blockedTermCount = (int)(blockedPosition >> positionBitsToShift);\n        final int blockedOffset = (int)blockedPosition & (termLength - 1);\n        final int activeTermCount = activeTermCount(logMetaDataBuffer);\n\n        if (activeTermCount == (blockedTermCount - 1) && blockedOffset == 0)\n        {\n            final int currentTermId = termId(rawTailVolatile(logMetaDataBuffer, indexByTermCount(activeTermCount)));\n            rotateLog(logMetaDataBuffer, activeTermCount, currentTermId);\n            return true;\n        }\n\n        final int blockedIndex = indexByTermCount(blockedTermCount);\n        final long rawTail = rawTailVolatile(logMetaDataBuffer, blockedIndex);\n        final int termId = termId(rawTail);\n        final int tailOffset = termOffset(rawTail, termLength);\n        final UnsafeBuffer termBuffer = termBuffers[blockedIndex];\n\n        switch (TermUnblocker.unblock(logMetaDataBuffer, termBuffer, blockedOffset, tailOffset, termId))\n        {\n            case NO_ACTION:\n                break;\n\n            case UNBLOCKED_TO_END:\n                rotateLog(logMetaDataBuffer, blockedTermCount, termId);\n                return true;\n\n            case UNBLOCKED:\n                return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/RawBlockHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.channels.FileChannel;\n\n/**\n * Function for handling a raw block of fragments from the log that are contained in the underlying file.\n */\n@FunctionalInterface\npublic interface RawBlockHandler\n{\n    /**\n     * Notification of an available block of fragments.\n     *\n     * @param fileChannel containing the block of fragments.\n     * @param fileOffset  at which the block begins, including any frame headers.\n     * @param termBuffer  mapped over the block of fragments.\n     * @param termOffset  in the termBuffer at which block begins, including any frame headers.\n     * @param length      of the block in bytes, including any frame headers that is aligned up to\n     *                    {@link io.aeron.logbuffer.FrameDescriptor#FRAME_ALIGNMENT}.\n     * @param sessionId   of the stream of fragments.\n     * @param termId      of the stream of fragments.\n     */\n    void onBlock(\n        FileChannel fileChannel,\n        long fileOffset,\n        UnsafeBuffer termBuffer,\n        int termOffset,\n        int length,\n        int sessionId,\n        int termId);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/TermBlockScanner.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.frameLengthVolatile;\nimport static io.aeron.logbuffer.FrameDescriptor.isPaddingFrame;\nimport static org.agrona.BitUtil.align;\n\n/**\n * Scan a term buffer for a block of message fragments including padding. The block must include complete fragments.\n */\npublic final class TermBlockScanner\n{\n    private TermBlockScanner()\n    {\n    }\n\n    /**\n     * Scan a term buffer for a block of message fragments from an offset up to a limitOffset.\n     * <p>\n     * A scan will terminate if a padding frame is encountered. If first frame in a scan is padding then a block\n     * for the padding is notified. If the padding comes after the first frame in a scan then the scan terminates\n     * at the offset the padding frame begins. Padding frames are delivered singularly in a block.\n     * <p>\n     * Padding frames may be for a greater range than the limit offset but only the header needs to be valid so\n     * relevant length of the frame is {@link io.aeron.protocol.DataHeaderFlyweight#HEADER_LENGTH}.\n     *\n     * @param termBuffer  to scan for message fragments.\n     * @param termOffset  at which the scan should begin.\n     * @param limitOffset at which the scan should stop.\n     * @return the offset at which the scan terminated.\n     */\n    public static int scan(final UnsafeBuffer termBuffer, final int termOffset, final int limitOffset)\n    {\n        int offset = termOffset;\n\n        while (offset < limitOffset)\n        {\n            final int frameLength = frameLengthVolatile(termBuffer, offset);\n            if (frameLength <= 0)\n            {\n                break;\n            }\n\n            final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n\n            if (isPaddingFrame(termBuffer, offset))\n            {\n                if (termOffset == offset)\n                {\n                    offset += alignedFrameLength;\n                }\n\n                break;\n            }\n\n            if (offset + alignedFrameLength > limitOffset)\n            {\n                break;\n            }\n\n            offset += alignedFrameLength;\n        }\n\n        return offset;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/TermGapFiller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.applyDefaultHeader;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_PAD;\n\n/**\n * Fills a gap in a term with a padding record.\n */\npublic final class TermGapFiller\n{\n    private TermGapFiller()\n    {\n    }\n\n    /**\n     * Try to gap fill the current term at a given offset if the gap contains no data.\n     * <p>\n     * Note: the gap offset plus gap length must end on a {@link FrameDescriptor#FRAME_ALIGNMENT} boundary.\n     *\n     * @param logMetaDataBuffer containing the default headers\n     * @param termBuffer        to gap fill\n     * @param termId            for the current term.\n     * @param gapOffset         to fill from\n     * @param gapLength         to length of the gap.\n     * @return true if the gap has been filled with a padding record or false if data found.\n     */\n    public static boolean tryFillGap(\n        final UnsafeBuffer logMetaDataBuffer,\n        final UnsafeBuffer termBuffer,\n        final int termId,\n        final int gapOffset,\n        final int gapLength)\n    {\n        int offset = (gapOffset + gapLength) - FRAME_ALIGNMENT;\n\n        while (offset >= gapOffset)\n        {\n            if (0 != termBuffer.getInt(offset))\n            {\n                return false;\n            }\n\n            offset -= FRAME_ALIGNMENT;\n        }\n\n        applyDefaultHeader(logMetaDataBuffer, termBuffer, gapOffset);\n        frameType(termBuffer, gapOffset, HDR_TYPE_PAD);\n        frameTermOffset(termBuffer, gapOffset);\n        frameTermId(termBuffer, gapOffset, termId);\n        frameLengthOrdered(termBuffer, gapOffset, gapLength);\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/TermGapScanner.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.frameLengthVolatile;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.agrona.BitUtil.align;\n\n/**\n * Scans for gaps in the sequence of bytes in a replicated term buffer between the completed rebuild and the\n * high-water-mark. This can be used for detecting loss and generating a NAK message to the source.\n * <p>\n * <b>Note:</b> This class is threadsafe to be used across multiple threads.\n */\npublic final class TermGapScanner\n{\n    /**\n     * Handler for notifying of gaps in the log.\n     */\n    @FunctionalInterface\n    public interface GapHandler\n    {\n        /**\n         * Gap detected in log buffer that is being rebuilt.\n         *\n         * @param termId active term being scanned.\n         * @param offset at which the gap begins.\n         * @param length of the gap in bytes.\n         */\n        void onGap(int termId, int offset, int length);\n    }\n\n    private TermGapScanner()\n    {\n    }\n\n    /**\n     * Scan for gaps from the scanOffset up to a limit offset. Each gap will be reported to the {@link GapHandler}.\n     *\n     * @param termBuffer  to be scanned for a gap.\n     * @param termId      of the current term buffer.\n     * @param termOffset  at which to start scanning.\n     * @param limitOffset at which to stop scanning.\n     * @param handler     to call if a gap is found.\n     * @return offset of last contiguous frame\n     */\n    public static int scanForGap(\n        final UnsafeBuffer termBuffer,\n        final int termId,\n        final int termOffset,\n        final int limitOffset,\n        final GapHandler handler)\n    {\n        int offset = termOffset;\n        do\n        {\n            final int frameLength = frameLengthVolatile(termBuffer, offset);\n            if (frameLength <= 0)\n            {\n                break;\n            }\n\n            offset += align(frameLength, FRAME_ALIGNMENT);\n        }\n        while (offset < limitOffset);\n\n        final int gapBeginOffset = offset;\n        if (offset < limitOffset)\n        {\n            offset += HEADER_LENGTH;\n            while (offset < limitOffset)\n            {\n                if (0 != frameLengthVolatile(termBuffer, offset))\n                {\n                    break;\n                }\n                offset += HEADER_LENGTH;\n            }\n\n            final int gapLength = offset - gapBeginOffset;\n            handler.onGap(termId, gapBeginOffset, gapLength);\n        }\n\n        return gapBeginOffset;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/TermReader.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.*;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.Position;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\n\n/**\n * Utility functions for reading a term within a log buffer.\n */\npublic final class TermReader\n{\n    private TermReader()\n    {\n    }\n\n    /**\n     * Reads data from a term in a log buffer and updates a passed {@link Position} so progress is not lost in the\n     * event of an exception.\n     *\n     * @param termBuffer         to be read for fragments.\n     * @param termOffset         within the buffer that the read should begin.\n     * @param handler            the handler for data that has been read\n     * @param fragmentsLimit     limit the number of fragments read.\n     * @param header             to be used for mapping over the header for a given fragment.\n     * @param errorHandler       to be notified if an error occurs during the callback.\n     * @param currentPosition    prior to reading further fragments\n     * @param subscriberPosition to be updated after reading with new position\n     * @return the number of fragments read\n     */\n    public static int read(\n        final UnsafeBuffer termBuffer,\n        final int termOffset,\n        final FragmentHandler handler,\n        final int fragmentsLimit,\n        final Header header,\n        final ErrorHandler errorHandler,\n        final long currentPosition,\n        final Position subscriberPosition)\n    {\n        int fragmentsRead = 0;\n        int offset = termOffset;\n        final int capacity = termBuffer.capacity();\n        header.buffer(termBuffer);\n\n        try\n        {\n            while (fragmentsRead < fragmentsLimit && offset < capacity)\n            {\n                final int frameLength = frameLengthVolatile(termBuffer, offset);\n                if (frameLength <= 0)\n                {\n                    break;\n                }\n\n                final int frameOffset = offset;\n                offset += BitUtil.align(frameLength, FRAME_ALIGNMENT);\n\n                if (!isPaddingFrame(termBuffer, frameOffset))\n                {\n                    ++fragmentsRead;\n                    header.offset(frameOffset);\n                    handler.onFragment(termBuffer, frameOffset + HEADER_LENGTH, frameLength - HEADER_LENGTH, header);\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n        finally\n        {\n            final long newPosition = currentPosition + (offset - termOffset);\n            if (newPosition > currentPosition)\n            {\n                subscriberPosition.setRelease(newPosition);\n            }\n        }\n\n        return fragmentsRead;\n    }\n\n    /**\n     * Reads data from a term in a log buffer.\n     *\n     * @param termBuffer     to be read for fragments.\n     * @param termOffset     within the buffer that the read should begin.\n     * @param handler        the handler for data that has been read\n     * @param fragmentsLimit limit the number of fragments read.\n     * @param header         to be used for mapping over the header for a given fragment.\n     * @param errorHandler   to be notified if an error occurs during the callback.\n     * @return the number of fragments read\n     */\n    public static long read(\n        final UnsafeBuffer termBuffer,\n        final int termOffset,\n        final FragmentHandler handler,\n        final int fragmentsLimit,\n        final Header header,\n        final ErrorHandler errorHandler)\n    {\n        int fragmentsRead = 0;\n        int offset = termOffset;\n        final int capacity = termBuffer.capacity();\n        header.buffer(termBuffer);\n\n        try\n        {\n            while (fragmentsRead < fragmentsLimit && offset < capacity)\n            {\n                final int frameLength = frameLengthVolatile(termBuffer, offset);\n                if (frameLength <= 0)\n                {\n                    break;\n                }\n\n                final int frameOffset = offset;\n                offset += BitUtil.align(frameLength, FRAME_ALIGNMENT);\n\n                if (!isPaddingFrame(termBuffer, frameOffset))\n                {\n                    ++fragmentsRead;\n                    header.offset(frameOffset);\n                    handler.onFragment(termBuffer, frameOffset + HEADER_LENGTH, frameLength - HEADER_LENGTH, header);\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n\n        return pack(offset, fragmentsRead);\n    }\n\n    /**\n     * Pack the values for fragmentsRead and offset into a long for returning on the stack.\n     *\n     * @param offset        value to be packed.\n     * @param fragmentsRead value to be packed.\n     * @return a long with both ints packed into it.\n     */\n    public static long pack(final int offset, final int fragmentsRead)\n    {\n        return ((long)offset << 32) | fragmentsRead;\n    }\n\n    /**\n     * The number of fragments that have been read.\n     *\n     * @param readOutcome into which the fragments read value has been packed.\n     * @return the number of fragments that have been read.\n     */\n    public static int fragmentsRead(final long readOutcome)\n    {\n        return (int)readOutcome;\n    }\n\n    /**\n     * The offset up to which the term has progressed.\n     *\n     * @param readOutcome into which the offset value has been packed.\n     * @return the offset up to which the term has progressed.\n     */\n    public static int offset(final long readOutcome)\n    {\n        return (int)(readOutcome >>> 32);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/TermRebuilder.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\n\n/**\n * Rebuild a term buffer from received frames which can be out-of-order. The resulting data structure will only\n * monotonically increase in state.\n */\npublic final class TermRebuilder\n{\n    private TermRebuilder()\n    {\n    }\n\n    /**\n     * Insert a packet of frames into the log at the appropriate termOffset as indicated by the term termOffset header.\n     * <p>\n     * If the packet has already been inserted then this is a noop.\n     *\n     * @param termBuffer into which the packet should be inserted.\n     * @param termOffset in the term at which the packet should be inserted.\n     * @param packet     containing a sequence of frames.\n     * @param length     of the packet of frames in bytes.\n     */\n    public static void insert(\n        final UnsafeBuffer termBuffer, final int termOffset, final UnsafeBuffer packet, final int length)\n    {\n        if (0 == termBuffer.getInt(termOffset))\n        {\n            termBuffer.putBytes(termOffset + HEADER_LENGTH, packet, HEADER_LENGTH, length - HEADER_LENGTH);\n\n            termBuffer.putLong(termOffset + 24, packet.getLong(24));\n            termBuffer.putLong(termOffset + 16, packet.getLong(16));\n            termBuffer.putLong(termOffset + 8, packet.getLong(8));\n\n            termBuffer.putLongRelease(termOffset, packet.getLong(0));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/TermScanner.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.frameLengthVolatile;\nimport static io.aeron.logbuffer.FrameDescriptor.isPaddingFrame;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.agrona.BitUtil.align;\n\n/**\n * Scans a term buffer for an availability range of message fragments.\n * <p>\n * This can be used to concurrently read a term buffer which is being appended to.\n */\npublic final class TermScanner\n{\n    private TermScanner()\n    {\n    }\n\n    /**\n     * Scan the term buffer for availability of new message fragments from a given offset up to a maxLength of bytes.\n     *\n     * @param termBuffer to be scanned for new message fragments.\n     * @param offset     at which the scan should begin.\n     * @param maxLength  in bytes of how much should be scanned.\n     * @return resulting status of the scan which packs the available bytes and padding into a long. The available bytes\n     * can be negative which indicates that there was data to send.\n     */\n    public static long scanForAvailability(\n        final UnsafeBuffer termBuffer, final int offset, final int maxLength)\n    {\n        final int limit = Math.min(maxLength, termBuffer.capacity() - offset);\n        int available = 0;\n        int padding = 0;\n\n        do\n        {\n            final int termOffset = offset + available;\n            final int frameLength = frameLengthVolatile(termBuffer, termOffset);\n            if (frameLength <= 0)\n            {\n                break;\n            }\n\n            int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n            if (isPaddingFrame(termBuffer, termOffset))\n            {\n                padding = alignedFrameLength - HEADER_LENGTH;\n                alignedFrameLength = HEADER_LENGTH;\n            }\n\n            available += alignedFrameLength;\n\n            if (available > limit)\n            {\n                available = alignedFrameLength == available ? -available : available - alignedFrameLength;\n                padding = 0;\n                break;\n            }\n        }\n        while (0 == padding && available < limit);\n\n        return pack(padding, available);\n    }\n\n    /**\n     * Pack the values for available and padding into a long for returning on the stack.\n     *\n     * @param padding   value to be packed.\n     * @param available value to be packed.\n     * @return a long with both ints packed into it.\n     */\n    public static long pack(final int padding, final int available)\n    {\n        return ((long)padding << 32) | available;\n    }\n\n    /**\n     * The number of bytes that are available to be read after a scan.\n     *\n     * @param result into which the padding value has been packed.\n     * @return the count of bytes that are available to be read.\n     */\n    public static int available(final long result)\n    {\n        return (int)result;\n    }\n\n    /**\n     * The count of bytes that should be added for padding to the position on top of what is available.\n     *\n     * @param result into which the padding value has been packed.\n     * @return the count of bytes that should be added for padding to the position on top of what is available.\n     */\n    public static int padding(final long result)\n    {\n        return (int)(result >>> 32);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/TermUnblocker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.applyDefaultHeader;\nimport static io.aeron.logbuffer.TermUnblocker.Status.*;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_PAD;\n\n/**\n * Unblocks a term buffer if a publisher has died leaving the log with a partial log entry.\n */\npublic final class TermUnblocker\n{\n    /**\n     * Status result of an {@link #unblock(UnsafeBuffer, UnsafeBuffer, int, int, int)} operation.\n     */\n    public enum Status\n    {\n        /**\n         * No action has been taken during operation.\n         */\n        NO_ACTION,\n\n        /**\n         * The term has been unblocked so that the log can progress.\n         */\n        UNBLOCKED,\n\n        /**\n         * The term has been unblocked from the offset until the end of the term.\n         */\n        UNBLOCKED_TO_END,\n    }\n\n    private TermUnblocker()\n    {\n    }\n\n    /**\n     * Attempt to unblock the current term at the current offset.\n     * <ol>\n     * <li>Current position length is &gt; 0, then return</li>\n     * <li>Current position length is 0, scan forward by frame alignment until, one of the following:\n     * <ol>\n     * <li>reach a non-0 length, unblock up to indicated position (check original frame length for non-0)</li>\n     * <li>reach end of term and tail position &gt;= end of term, unblock up to end of term (check original\n     * frame length for non-0)\n     * </li>\n     * <li>reach tail position &lt; end of term, do NOT unblock</li>\n     * </ol>\n     * </li>\n     * </ol>\n     *\n     * @param logMetaDataBuffer containing the default headers\n     * @param termBuffer        to unblock\n     * @param blockedOffset     to unblock at\n     * @param tailOffset        to unblock up to\n     * @param termId            for the current term.\n     * @return whether unblocking was done, not done, or applied to end of term\n     */\n    public static Status unblock(\n        final UnsafeBuffer logMetaDataBuffer,\n        final UnsafeBuffer termBuffer,\n        final int blockedOffset,\n        final int tailOffset,\n        final int termId)\n    {\n        Status status = NO_ACTION;\n        int frameLength = frameLengthVolatile(termBuffer, blockedOffset);\n\n        if (frameLength < 0)\n        {\n            resetHeader(logMetaDataBuffer, termBuffer, blockedOffset, termId, -frameLength);\n            status = UNBLOCKED;\n        }\n        else if (0 == frameLength)\n        {\n            int currentOffset = blockedOffset + FRAME_ALIGNMENT;\n\n            while (currentOffset < tailOffset)\n            {\n                frameLength = frameLengthVolatile(termBuffer, currentOffset);\n\n                if (frameLength != 0)\n                {\n                    if (scanBackToConfirmZeroed(termBuffer, currentOffset, blockedOffset))\n                    {\n                        final int length = currentOffset - blockedOffset;\n                        resetHeader(logMetaDataBuffer, termBuffer, blockedOffset, termId, length);\n                        status = UNBLOCKED;\n                    }\n\n                    break;\n                }\n\n                currentOffset += FRAME_ALIGNMENT;\n            }\n\n            if (currentOffset == termBuffer.capacity())\n            {\n                if (0 == frameLengthVolatile(termBuffer, blockedOffset))\n                {\n                    final int length = currentOffset - blockedOffset;\n                    resetHeader(logMetaDataBuffer, termBuffer, blockedOffset, termId, length);\n                    status = UNBLOCKED_TO_END;\n                }\n            }\n        }\n\n        return status;\n    }\n\n    private static void resetHeader(\n        final UnsafeBuffer logMetaDataBuffer,\n        final UnsafeBuffer termBuffer,\n        final int termOffset,\n        final int termId,\n        final int frameLength)\n    {\n        applyDefaultHeader(logMetaDataBuffer, termBuffer, termOffset);\n        frameType(termBuffer, termOffset, HDR_TYPE_PAD);\n        frameTermOffset(termBuffer, termOffset);\n        frameTermId(termBuffer, termOffset, termId);\n        frameLengthOrdered(termBuffer, termOffset, frameLength);\n    }\n\n    private static boolean scanBackToConfirmZeroed(final UnsafeBuffer buffer, final int from, final int limit)\n    {\n        int i = from - FRAME_ALIGNMENT;\n        boolean allZeros = true;\n        while (i >= limit)\n        {\n            if (0 != frameLengthVolatile(buffer, i))\n            {\n                allZeros = false;\n                break;\n            }\n\n            i -= FRAME_ALIGNMENT;\n        }\n\n        return allZeros;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/logbuffer/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Package of classes for working with message streams encoded into the log buffers.\n */\npackage io.aeron.logbuffer;"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * {@link io.aeron.Aeron} client that is used to communicate to a local Media Driver for the publication and\n * subscription to message streams.\n * <p>\n * A client can be created by invoking {@link io.aeron.Aeron#connect()} for default setting or via\n * {@link io.aeron.Aeron#connect(io.aeron.Aeron.Context)} to override the defaults or system properties.\n */\npackage io.aeron;"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/DataHeaderFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\n\n/**\n * Flyweight for Data Frame header of a message fragment.\n * <p>\n * <a target=\"_blank\"\n *    href=\"https://github.com/aeron-io/aeron/wiki/Transport-Protocol-Specification#data-frame\">Data Frame</a>\n * wiki page.\n */\npublic class DataHeaderFlyweight extends HeaderFlyweight\n{\n    /**\n     * Length of the Data Header.\n     */\n    public static final int HEADER_LENGTH = 32;\n\n    /**\n     * (B) - Fragment that Begins a message Flag.\n     */\n    public static final short BEGIN_FLAG = 0x80;\n\n    /**\n     * (E) - Fragment that Ends a message Flag.\n     */\n    public static final short END_FLAG = 0x40;\n\n    /**\n     * Begin and End Flags.\n     */\n    public static final short BEGIN_AND_END_FLAGS = BEGIN_FLAG | END_FLAG;\n\n    /**\n     * (S) - End of Stream (EOS) Flag for heartbeats after the publication is closed.\n     */\n    public static final short EOS_FLAG = 0x20;\n\n    /**\n     * Begin, End, and EOS Flags.\n     */\n    public static final short BEGIN_END_AND_EOS_FLAGS = BEGIN_FLAG | END_FLAG | EOS_FLAG;\n\n    /**\n     * (R) - Revoked Flag for heartbeats after the publication is revoked.\n     */\n    public static final short REVOKED_FLAG = 0x10;\n\n    /**\n     * Begin, End, EOS, and Revoked Flags.\n     */\n    public static final short BEGIN_END_EOS_AND_REVOKED_FLAGS = BEGIN_FLAG | END_FLAG | EOS_FLAG | REVOKED_FLAG;\n\n    /**\n     * Default value to be placed in the reserved value field.\n     */\n    public static final long DEFAULT_RESERVE_VALUE = 0L;\n\n    /**\n     * Offset in the frame at which the term-offset field begins.\n     */\n    public static final int TERM_OFFSET_FIELD_OFFSET = 8;\n\n    /**\n     * Offset in the frame at which the session-id field begins.\n     */\n    public static final int SESSION_ID_FIELD_OFFSET = 12;\n\n    /**\n     * Offset in the frame at which the stream-id field begins.\n     */\n    public static final int STREAM_ID_FIELD_OFFSET = 16;\n\n    /**\n     * Offset in the frame at which the term-id field begins.\n     */\n    public static final int TERM_ID_FIELD_OFFSET = 20;\n\n    /**\n     * Offset in the frame at which the reserved value field begins.\n     */\n    public static final int RESERVED_VALUE_OFFSET = 24;\n\n    /**\n     * Offset in the frame at which the data payload begins.\n     */\n    public static final int DATA_OFFSET = HEADER_LENGTH;\n\n    /**\n     * Default constructor which can later be used to wrap a frame.\n     */\n    public DataHeaderFlyweight()\n    {\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public DataHeaderFlyweight(final UnsafeBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public DataHeaderFlyweight(final ByteBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Get the fragment length field from the header.\n     *\n     * @param termBuffer  containing the header.\n     * @param frameOffset in the buffer where the header starts.\n     * @return the fragment length field from the header.\n     */\n    public static int fragmentLength(final UnsafeBuffer termBuffer, final int frameOffset)\n    {\n        return termBuffer.getInt(frameOffset + FRAME_LENGTH_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Is the frame at data frame at the beginning of packet a heartbeat message?\n     *\n     * @param packet containing the data frame.\n     * @param length of the data frame.\n     * @return true if a heartbeat otherwise false.\n     */\n    public static boolean isHeartbeat(final UnsafeBuffer packet, final int length)\n    {\n        return length == HEADER_LENGTH && packet.getInt(0) == 0;\n    }\n\n    /**\n     * Does the data frame in the packet have the EOS flag set?\n     *\n     * @param packet containing the data frame.\n     * @return true if the EOS flag is set otherwise false.\n     */\n    public static boolean isEndOfStream(final UnsafeBuffer packet)\n    {\n        return 0 != (packet.getByte(FLAGS_FIELD_OFFSET) & EOS_FLAG);\n    }\n\n    /**\n     * Does the data frame in the packet have the REVOKED flag set?\n     *\n     * @param packet containing the data frame\n     * @return true if the REVOKED flag is set otherwise false.\n     */\n    public static boolean isRevoked(final UnsafeBuffer packet)\n    {\n        return 0 != (packet.getByte(FLAGS_FIELD_OFFSET) & REVOKED_FLAG);\n    }\n\n    /**\n     * Get the session-id field from the header.\n     *\n     * @return the session-id field from the header.\n     */\n    public int sessionId()\n    {\n        return getInt(SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Get the session-id field from the header.\n     *\n     * @param termBuffer  containing the header.\n     * @param frameOffset in the buffer where the header starts.\n     * @return the session-id field from the header.\n     */\n    public static int sessionId(final UnsafeBuffer termBuffer, final int frameOffset)\n    {\n        return termBuffer.getInt(frameOffset + SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the session-id field in the header.\n     *\n     * @param sessionId value to set.\n     * @return this for a fluent API.\n     */\n    public DataHeaderFlyweight sessionId(final int sessionId)\n    {\n        putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get the stream-id field from the header.\n     *\n     * @return the stream-id field from the header.\n     */\n    public int streamId()\n    {\n        return getInt(STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Get the stream-id field from the header.\n     *\n     * @param termBuffer  containing the header.\n     * @param frameOffset in the buffer where the header starts.\n     * @return the stream-id field from the header.\n     */\n    public static int streamId(final UnsafeBuffer termBuffer, final int frameOffset)\n    {\n        return termBuffer.getInt(frameOffset + STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the stream-id field in the header.\n     *\n     * @param streamId value to set.\n     * @return this for a fluent API.\n     */\n    public DataHeaderFlyweight streamId(final int streamId)\n    {\n        putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get the term-id field from the header.\n     *\n     * @return the term-id field from the header.\n     */\n    public int termId()\n    {\n        return getInt(TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Get the term-id field from the header.\n     *\n     * @param termBuffer  containing the header.\n     * @param frameOffset in the buffer where the header starts.\n     * @return the term-id field from the header.\n     */\n    public static int termId(final UnsafeBuffer termBuffer, final int frameOffset)\n    {\n        return termBuffer.getInt(frameOffset + TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the term-id field in the header.\n     *\n     * @param termId value to set.\n     * @return this for a fluent API.\n     */\n    public DataHeaderFlyweight termId(final int termId)\n    {\n        putInt(TERM_ID_FIELD_OFFSET, termId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get the term-offset field from the header.\n     *\n     * @return the term-offset field from the header.\n     */\n    public int termOffset()\n    {\n        return getInt(TERM_OFFSET_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Get the term-offset field from the header.\n     *\n     * @param termBuffer  containing the header.\n     * @param frameOffset in the buffer where the header starts.\n     * @return the term-offset field from the header.\n     */\n    public static int termOffset(final UnsafeBuffer termBuffer, final int frameOffset)\n    {\n        return termBuffer.getInt(frameOffset + TERM_OFFSET_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the term-offset field in the header.\n     *\n     * @param termOffset value to set.\n     * @return this for a fluent API.\n     */\n    public DataHeaderFlyweight termOffset(final int termOffset)\n    {\n        putInt(TERM_OFFSET_FIELD_OFFSET, termOffset, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get the reserved value in LITTLE_ENDIAN format.\n     *\n     * @return value of the reserved value.\n     */\n    public long reservedValue()\n    {\n        return getLong(RESERVED_VALUE_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Get the reserved value field from the header.\n     *\n     * @param termBuffer  containing the header.\n     * @param frameOffset in the buffer where the header starts.\n     * @return the reserved value field from the header.\n     */\n    public static long reservedValue(final UnsafeBuffer termBuffer, final int frameOffset)\n    {\n        return termBuffer.getLong(frameOffset + RESERVED_VALUE_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the reserved value in LITTLE_ENDIAN format.\n     *\n     * @param reservedValue to be stored\n     * @return flyweight\n     */\n    public DataHeaderFlyweight reservedValue(final long reservedValue)\n    {\n        putLong(RESERVED_VALUE_OFFSET, reservedValue, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Return offset in buffer for data.\n     *\n     * @return offset of data in the buffer.\n     */\n    public int dataOffset()\n    {\n        return DATA_OFFSET;\n    }\n\n    /**\n     * Return an initialised default Data Frame Header.\n     *\n     * @param sessionId for the header\n     * @param streamId  for the header\n     * @param termId    for the header\n     * @return byte array containing the header\n     */\n    public static UnsafeBuffer createDefaultHeader(final int sessionId, final int streamId, final int termId)\n    {\n        final UnsafeBuffer buffer = new UnsafeBuffer(\n            BufferUtil.allocateDirectAligned(HEADER_LENGTH, CACHE_LINE_LENGTH));\n\n        buffer.putByte(VERSION_FIELD_OFFSET, CURRENT_VERSION);\n        buffer.putByte(FLAGS_FIELD_OFFSET, (byte)BEGIN_AND_END_FLAGS);\n        buffer.putShort(TYPE_FIELD_OFFSET, (short)HDR_TYPE_DATA, LITTLE_ENDIAN);\n        buffer.putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n        buffer.putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n        buffer.putInt(TERM_ID_FIELD_OFFSET, termId, LITTLE_ENDIAN);\n        buffer.putLong(RESERVED_VALUE_OFFSET, DEFAULT_RESERVE_VALUE);\n\n        return buffer;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"DATA Header{\" +\n            \"frame-length=\" + frameLength() +\n            \" version=\" + version() +\n            \" flags=\" + String.valueOf(flagsToChars(flags())) +\n            \" type=\" + headerType() +\n            \" term-offset=\" + termOffset() +\n            \" session-id=\" + sessionId() +\n            \" stream-id=\" + streamId() +\n            \" term-id=\" + termId() +\n            \" reserved-value=\" + reservedValue() +\n            \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/ErrorFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Flyweight for general Aeron network protocol error frame\n * <pre>\n *    0                   1                   2                   3\n *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n * 0  |R|                 Frame Length (varies)                       |\n *    +---------------+---------------+-------------------------------+\n * 4  |   Version     |     Flags     |         Type (=0x04)          |\n *    +---------------+---------------+-------------------------------+\n * 8  |                          Session ID                           |\n *    +---------------------------------------------------------------+\n * 12 |                           Stream ID                           |\n *    +---------------------------------------------------------------+\n * 16 |                          Receiver ID                          |\n *    |                                                               |\n *    +---------------------------------------------------------------+\n * 24 |                           Group Tag                           |\n *    |                                                               |\n *    +---------------------------------------------------------------+\n * 32 |                          Error Code                           |\n *    +---------------------------------------------------------------+\n * 36 |                     Error String Length                       |\n *    +---------------------------------------------------------------+\n * 40 |                         Error String                        ...\n *    +---------------------------------------------------------------+\n *    ...                                                             |\n *    +---------------------------------------------------------------+\n * </pre>\n *\n * @since 1.47.0\n */\npublic class ErrorFlyweight extends HeaderFlyweight\n{\n    /**\n     * Offset in the frame at which the session-id field begins.\n     */\n    public static final int SESSION_ID_FIELD_OFFSET = MIN_HEADER_LENGTH;\n\n    /**\n     * Offset in the frame at which the stream-id field begins.\n     */\n    public static final int STREAM_ID_FIELD_OFFSET = SESSION_ID_FIELD_OFFSET + SIZE_OF_INT;\n\n    /**\n     * Offset in the frame at which the receiver-id field begins.\n     */\n    public static final int RECEIVER_ID_FIELD_OFFSET = STREAM_ID_FIELD_OFFSET + SIZE_OF_INT;\n\n    /**\n     * Offset in the frame at which the group-tag field begins.\n     */\n    public static final int GROUP_TAG_FIELD_OFFSET = RECEIVER_ID_FIELD_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * Offset in the frame at which the error code field begins.\n     */\n    public static final int ERROR_CODE_FIELD_OFFSET = GROUP_TAG_FIELD_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * Offset in the frame at which the error string field begins. Specifically this will be the length of the string\n     * using the Agrona buffer standard of using 4 bytes for the length. Followed by the variable bytes for the string.\n     */\n    public static final int ERROR_STRING_FIELD_OFFSET = ERROR_CODE_FIELD_OFFSET + SIZE_OF_INT;\n\n    /**\n     * Length of the Error Header, i.e. all fixed size fields including length of the error message string but\n     * excluding its contents.\n     */\n    public static final int HEADER_LENGTH = ERROR_STRING_FIELD_OFFSET + SIZE_OF_INT;\n\n    /**\n     * Maximum length that an error message can be. Can be short that this if configuration options have made the MTU\n     * smaller. The error message should be truncated to fit within a single MTU.\n     */\n    public static final int MAX_ERROR_MESSAGE_LENGTH = 1023;\n\n    /**\n     * Maximum length of an error frame. Captures the maximum message length and the header length.\n     */\n    public static final int MAX_ERROR_FRAME_LENGTH = HEADER_LENGTH + MAX_ERROR_MESSAGE_LENGTH;\n\n    /**\n     * Flag to indicate that the group tag field is relevant, if not set the value should be ignored.\n     */\n    public static final int HAS_GROUP_ID_FLAG = 0x08;\n\n    /**\n     * Default constructor for the ErrorFlyweight so that it can be wrapped over a buffer later.\n     */\n    public ErrorFlyweight()\n    {\n    }\n\n    /**\n     * Construct the ErrorFlyweight over an NIO ByteBuffer frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public ErrorFlyweight(final ByteBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Construct the ErrorFlyweight over an UnsafeBuffer frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public ErrorFlyweight(final UnsafeBuffer buffer)\n    {\n        super(buffer);\n    }\n\n\n    /**\n     * The session-id for the stream.\n     *\n     * @return session-id for the stream.\n     */\n    public int sessionId()\n    {\n        return getInt(SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set session-id for the stream.\n     *\n     * @param sessionId session-id for the stream.\n     * @return this for a fluent API.\n     */\n    public ErrorFlyweight sessionId(final int sessionId)\n    {\n        putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The stream-id for the stream.\n     *\n     * @return stream-id for the stream.\n     */\n    public int streamId()\n    {\n        return getInt(STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set stream-id for the stream.\n     *\n     * @param streamId stream-id for the stream.\n     * @return this for a fluent API.\n     */\n    public ErrorFlyweight streamId(final int streamId)\n    {\n        putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The receiver-id for the stream.\n     *\n     * @return receiver-id for the stream.\n     */\n    public long receiverId()\n    {\n        return getLong(RECEIVER_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set receiver-id for the stream.\n     *\n     * @param receiverId receiver-id for the stream.\n     * @return this for a fluent API.\n     */\n    public ErrorFlyweight receiverId(final long receiverId)\n    {\n        putLong(RECEIVER_ID_FIELD_OFFSET, receiverId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get the group tag for the message.\n     *\n     * @return group tag for the message.\n     */\n    public long groupTag()\n    {\n        return getLong(GROUP_TAG_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Determines if this message has the group tag flag set.\n     *\n     * @return <code>true</code> if the flag is set false otherwise.\n     */\n    public boolean hasGroupTag()\n    {\n        return HAS_GROUP_ID_FLAG == (HAS_GROUP_ID_FLAG & flags());\n    }\n\n    /**\n     * Set an optional group tag, null indicates the value should not be set. If non-null will set HAS_GROUP_TAG flag\n     * on the header. A null value will clear this flag and use a value of 0.\n     *\n     * @param groupTag optional group tag to be applied to this message.\n     * @return this for a fluent API.\n     */\n    public ErrorFlyweight groupTag(final Long groupTag)\n    {\n        if (null == groupTag)\n        {\n            flags((short)(~HAS_GROUP_ID_FLAG & flags()));\n        }\n        else\n        {\n            putLong(GROUP_TAG_FIELD_OFFSET, groupTag);\n            flags((short)(HAS_GROUP_ID_FLAG | flags()));\n        }\n\n        return this;\n    }\n\n    /**\n     * The error-code for the message.\n     *\n     * @return error-code for the message.\n     */\n    public int errorCode()\n    {\n        return getInt(ERROR_CODE_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set error-code for the message.\n     *\n     * @param errorCode for the message.\n     * @return this for a fluent API.\n     */\n    public ErrorFlyweight errorCode(final int errorCode)\n    {\n        putInt(ERROR_CODE_FIELD_OFFSET, errorCode, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get the error string for the message.\n     *\n     * @return the error string for the message.\n     */\n    public String errorMessage()\n    {\n        return getStringAscii(ERROR_STRING_FIELD_OFFSET);\n    }\n\n    /**\n     * Set the error string for the message.\n     *\n     * @param errorMessage the error string in UTF-8.\n     * @return this for a fluent API.\n     */\n    public ErrorFlyweight errorMessage(final String errorMessage)\n    {\n        final int headerAndMessageLength = putStringAscii(ERROR_STRING_FIELD_OFFSET, errorMessage, LITTLE_ENDIAN);\n        frameLength(HEADER_LENGTH + (headerAndMessageLength - STR_HEADER_LEN));\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ERROR{\" +\n            \"frame-length=\" + frameLength() +\n            \" version=\" + version() +\n            \" flags=\" + String.valueOf(flagsToChars(flags())) +\n            \" type=\" + headerType() +\n            \" session-id=\" + sessionId() +\n            \" stream-id=\" + streamId() +\n            \" error-code=\" + errorCode() +\n            \" error-message=\" + errorMessage() +\n            \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/HeaderFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_SHORT;\n\n/**\n * Flyweight for general Aeron network protocol header of a message frame.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                        Frame Length                           |\n *  +---------------------------------------------------------------+\n *  |  Version    |     Flags     |               Type              |\n *  +-------------+---------------+---------------------------------+\n *  |                       Depends on Type                        ...\n * </pre>\n */\npublic class HeaderFlyweight extends UnsafeBuffer\n{\n    /**\n     * Header type PAD.\n     */\n    public static final int HDR_TYPE_PAD = 0x00;\n\n    /**\n     * Header type DATA.\n     */\n    public static final int HDR_TYPE_DATA = 0x01;\n\n    /**\n     * Header type NAK.\n     */\n    public static final int HDR_TYPE_NAK = 0x02;\n\n    /**\n     * Header type SM.\n     */\n    public static final int HDR_TYPE_SM = 0x03;\n\n    /**\n     * Header type ERR.\n     */\n    public static final int HDR_TYPE_ERR = 0x04;\n\n    /**\n     * Header type SETUP.\n     */\n    public static final int HDR_TYPE_SETUP = 0x05;\n\n    /**\n     * Header type RTT Measurement.\n     */\n    public static final int HDR_TYPE_RTTM = 0x06;\n\n    /**\n     * Header type RESOLUTION.\n     */\n    public static final int HDR_TYPE_RES = 0x07;\n\n    /**\n     * Header type ATS Data.\n     */\n    public static final int HDR_TYPE_ATS_DATA = 0x08;\n\n    /**\n     * Header type ATS Status Message.\n     */\n    public static final int HDR_TYPE_ATS_SM = 0x09;\n\n    /**\n     * Header type ATS Setup.\n     */\n    public static final int HDR_TYPE_ATS_SETUP = 0x0A;\n\n    /**\n     * Header type Response Setup.\n     */\n    public static final int HDR_TYPE_RSP_SETUP = 0x0B;\n\n    /**\n     * Header type EXT.\n     */\n    public static final int HDR_TYPE_EXT = 0xFFFF;\n\n    /**\n     * Default version.\n     */\n    public static final byte CURRENT_VERSION = 0x0;\n\n    /**\n     * Offset in the frame at which the frame length field begins.\n     */\n    public static final int FRAME_LENGTH_FIELD_OFFSET = 0;\n\n    /**\n     * Offset in the frame at which the version field begins.\n     */\n    public static final int VERSION_FIELD_OFFSET = 4;\n\n    /**\n     * Offset in the frame at which the flags field begins.\n     */\n    public static final int FLAGS_FIELD_OFFSET = 5;\n\n    /**\n     * Offset in the frame at which the frame type field begins.\n     */\n    public static final int TYPE_FIELD_OFFSET = 6;\n\n    /**\n     * Minimum length of any Aeron frame.\n     */\n    public static final int MIN_HEADER_LENGTH = TYPE_FIELD_OFFSET + SIZE_OF_SHORT;\n\n    /**\n     * Default constructor which can later be used to wrap a frame.\n     */\n    public HeaderFlyweight()\n    {\n    }\n\n    /**\n     * Construct a flyweight which wraps a {@link UnsafeBuffer} over the frame.\n     *\n     * @param buffer to wrap for the flyweight.\n     */\n    public HeaderFlyweight(final UnsafeBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Construct a flyweight which wraps a {@link ByteBuffer} over the frame.\n     *\n     * @param buffer to wrap for the flyweight.\n     */\n    public HeaderFlyweight(final ByteBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * The version field value.\n     *\n     * @return version field value.\n     */\n    public short version()\n    {\n        return (short)(getByte(VERSION_FIELD_OFFSET) & 0xFF);\n    }\n\n    /**\n     * Set the version field value.\n     *\n     * @param version field value to be set.\n     * @return this for a fluent API.\n     */\n    public HeaderFlyweight version(final short version)\n    {\n        putByte(VERSION_FIELD_OFFSET, (byte)version);\n\n        return this;\n    }\n\n    /**\n     * The flags field value.\n     *\n     * @return the flags field value.\n     */\n    public short flags()\n    {\n        return (short)(getByte(FLAGS_FIELD_OFFSET) & 0xFF);\n    }\n\n    /**\n     * Set the flags field value.\n     *\n     * @param flags field value.\n     * @return this for a fluent API.\n     */\n    public HeaderFlyweight flags(final short flags)\n    {\n        putByte(FLAGS_FIELD_OFFSET, (byte)flags);\n\n        return this;\n    }\n\n    /**\n     * The type field value.\n     *\n     * @return the type field value.\n     */\n    public int headerType()\n    {\n        return getShort(TYPE_FIELD_OFFSET, LITTLE_ENDIAN) & 0xFFFF;\n    }\n\n    /**\n     * Set the type field value.\n     *\n     * @param type field value.\n     * @return this for a fluent API.\n     */\n    public HeaderFlyweight headerType(final int type)\n    {\n        putShort(TYPE_FIELD_OFFSET, (short)type, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The length of the frame field value.\n     *\n     * @return length of the frame field value.\n     */\n    public int frameLength()\n    {\n        return getInt(FRAME_LENGTH_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the length of the frame field value.\n     *\n     * @param length field value.\n     * @return this for a fluent API.\n     */\n    public HeaderFlyweight frameLength(final int length)\n    {\n        putInt(FRAME_LENGTH_FIELD_OFFSET, length, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Convert header flags to an array of chars to be human-readable.\n     *\n     * @param flags to be converted.\n     * @return header flags converted to an array of chars to be human-readable.\n     */\n    public static char[] flagsToChars(final short flags)\n    {\n        final char[] chars = new char[]{ '0', '0', '0', '0', '0', '0', '0', '0' };\n        final int length = chars.length;\n        short mask = (short)(1 << (length - 1));\n\n        for (int i = 0; i < length; i++)\n        {\n            if ((flags & mask) == mask)\n            {\n                chars[i] = '1';\n            }\n\n            mask >>= 1;\n        }\n\n        return chars;\n    }\n\n    /**\n     * Append header flags to an {@link Appendable} to be human-readable.\n     *\n     * @param flags      to be converted.\n     * @param appendable to append flags to.\n     */\n    public static void appendFlagsAsChars(final short flags, final Appendable appendable)\n    {\n        final int length = 8;\n        short mask = (short)(1 << (length - 1));\n\n        try\n        {\n            for (int i = 0; i < length; i++)\n            {\n                appendable.append((flags & mask) == mask ? '1' : '0');\n                mask >>= 1;\n            }\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/NakFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n// CHECKSTYLE:OFF:LineLength\n/**\n * Flyweight for a NAK Message Frame.\n * <p>\n * <a target=\"_blank\"\n *    href=\"https://github.com/aeron-io/aeron/wiki/Transport-Protocol-Specification#data-recovery-via-retransmit-request\">\n *    Data Loss Recovery</a> wiki page.\n */\n// CHECKSTYLE:ON:LineLength\npublic class NakFlyweight extends HeaderFlyweight\n{\n    /**\n     * Length of the frame in bytes.\n     */\n    public static final int HEADER_LENGTH = 28;\n\n    /**\n     * Offset in the frame at which the session-id field begins.\n     */\n    private static final int SESSION_ID_FIELD_OFFSET = 8;\n\n    /**\n     * Offset in the frame at which the stream-id field begins.\n     */\n    private static final int STREAM_ID_FIELD_OFFSET = 12;\n\n    /**\n     * Offset in the frame at which the term-id field begins.\n     */\n    private static final int TERM_ID_FIELD_OFFSET = 16;\n\n    /**\n     * Offset in the frame at which the term-offset field begins.\n     */\n    private static final int TERM_OFFSET_FIELD_OFFSET = 20;\n\n    /**\n     * Offset in the frame at which the length of the NAK range field begins.\n     */\n    private static final int LENGTH_FIELD_OFFSET = 24;\n\n    /**\n     * Default constructor which can later be used to wrap a frame.\n     */\n    public NakFlyweight()\n    {\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public NakFlyweight(final ByteBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public NakFlyweight(final UnsafeBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * The session-id for the stream.\n     *\n     * @return session-id for the stream.\n     */\n    public int sessionId()\n    {\n        return getInt(SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set session-id for the stream.\n     *\n     * @param sessionId session-id for the stream.\n     * @return this for a fluent API.\n     */\n    public NakFlyweight sessionId(final int sessionId)\n    {\n        putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The stream-id for the stream.\n     *\n     * @return stream-id for the stream.\n     */\n    public int streamId()\n    {\n        return getInt(STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set stream-id for the stream.\n     *\n     * @param streamId stream-id for the stream.\n     * @return this for a fluent API.\n     */\n    public NakFlyweight streamId(final int streamId)\n    {\n        putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The term-id for the stream.\n     *\n     * @return term-id for the stream.\n     */\n    public int termId()\n    {\n        return getInt(TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set term-id for the stream.\n     *\n     * @param termId term-id for the stream.\n     * @return this for a fluent API.\n     */\n    public NakFlyweight termId(final int termId)\n    {\n        putInt(TERM_ID_FIELD_OFFSET, termId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The term-offset for the stream.\n     *\n     * @return term-offset for the stream.\n     */\n    public int termOffset()\n    {\n        return getInt(TERM_OFFSET_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set term-offset for the stream.\n     *\n     * @param termOffset term-offset for the stream.\n     * @return for a fluent API.\n     */\n    public NakFlyweight termOffset(final int termOffset)\n    {\n        putInt(TERM_OFFSET_FIELD_OFFSET, termOffset, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The length of the encoded frame.\n     *\n     * @return length of the encoded frame.\n     */\n    public int length()\n    {\n        return getInt(LENGTH_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set length of the encoded frame.\n     *\n     * @param length of the encoded frame.\n     * @return this for a fluent API.\n     */\n    public NakFlyweight length(final int length)\n    {\n        putInt(LENGTH_FIELD_OFFSET, length, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"NAK{\" +\n            \"frame-length=\" + frameLength() +\n            \" version=\" + version() +\n            \" flags=\" + String.valueOf(flagsToChars(flags())) +\n            \" type=\" + headerType() +\n            \" term-offset=\" + termOffset() +\n            \" session-id=\" + sessionId() +\n            \" stream-id=\" + streamId() +\n            \" term-id=\" + termId() +\n            \" length=\" + length() +\n            \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/ResolutionEntryFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static java.lang.Integer.toHexString;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.*;\n\n/**\n * Flyweight for Resolution Entry header.\n * <p>\n * <a target=\"_blank\"\n *    href=\"https://github.com/aeron-io/aeron/wiki/Transport-Protocol-Specification\">Protocol Specification</a>\n * wiki page.\n */\npublic class ResolutionEntryFlyweight extends HeaderFlyweight\n{\n    /**\n     * Resolution type field flag for IPv4.\n     */\n    public static final byte RES_TYPE_NAME_TO_IP4_MD = 0x01;\n\n    /**\n     * Resolution type field flag for IPv6.\n     */\n    public static final byte RES_TYPE_NAME_TO_IP6_MD = 0x02;\n\n    /**\n     * Length of an IPv4 address in bytes.\n     */\n    public static final int ADDRESS_LENGTH_IP4 = 4;\n\n    /**\n     * Length of an IPv6 address in bytes.\n     */\n    public static final int ADDRESS_LENGTH_IP6 = 16;\n\n    /**\n     * (S) - Self flag.\n     */\n    public static final short SELF_FLAG = 0x80;\n\n    /**\n     * Offset in the frame at which the res-type (resolution type) field begins.\n     */\n    public static final int RES_TYPE_FIELD_OFFSET = 0;\n\n    /**\n     * Offset in the frame at which the res-flags field begins.\n     */\n    public static final int RES_FLAGS_FIELD_OFFSET = 1;\n\n    /**\n     * Offset in the frame at which the UDP port field begins.\n     */\n    public static final int UDP_PORT_FIELD_OFFSET = 2;\n\n    /**\n     * Offset in the frame at which the age field begins.\n     */\n    public static final int AGE_IN_MS_FIELD_OFFSET = 4;\n\n    /**\n     * Offset in the frame at which the IP address field begins.\n     */\n    public static final int ADDRESS_FIELD_OFFSET = 8;\n\n    /**\n     * Maximum length allowed for a name.\n     */\n    public static final int MAX_NAME_LENGTH = 512;\n\n    /**\n     * Default constructor which can later be used to wrap a frame.\n     */\n    public ResolutionEntryFlyweight()\n    {\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public ResolutionEntryFlyweight(final UnsafeBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public ResolutionEntryFlyweight(final ByteBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Set the protocol type of the resolution.\n     *\n     * @param type of the resolution entry protocol.\n     * @return this for a fluent API.\n     * @see #RES_TYPE_NAME_TO_IP4_MD\n     * @see #RES_TYPE_NAME_TO_IP4_MD\n     */\n    public ResolutionEntryFlyweight resType(final byte type)\n    {\n        putByte(RES_TYPE_FIELD_OFFSET, type);\n        return this;\n    }\n\n    /**\n     * Get the protocol type of the resolution entry.\n     *\n     * @return the protocol type of the resolution entry.\n     * @see #RES_TYPE_NAME_TO_IP4_MD\n     * @see #RES_TYPE_NAME_TO_IP4_MD\n     */\n    public byte resType()\n    {\n        return getByte(RES_TYPE_FIELD_OFFSET);\n    }\n\n    /**\n     * Set the flags for the resolution entry.\n     *\n     * @param flags field value.\n     * @return this for a fluent API.\n     * @see #SELF_FLAG\n     */\n    public ResolutionEntryFlyweight flags(final short flags)\n    {\n        putByte(RES_FLAGS_FIELD_OFFSET, (byte)flags);\n        return this;\n    }\n\n    /**\n     * Get the flags for the resolution entry.\n     *\n     * @return the flags for the resolution entry.\n     */\n    public short flags()\n    {\n        return (short)(getByte(RES_FLAGS_FIELD_OFFSET) & 0xFF);\n    }\n\n    /**\n     * Set the UDP port of the resolver with a given name.\n     *\n     * @param udpPort of the resolver with a given name.\n     * @return this for a fluent API.\n     */\n    public ResolutionEntryFlyweight udpPort(final int udpPort)\n    {\n        putShort(UDP_PORT_FIELD_OFFSET, (short)udpPort, LITTLE_ENDIAN);\n        return this;\n    }\n\n    /**\n     * Get the UDP port of the resolver with a given name.\n     *\n     * @return the UDP port of the resolver with a given name.\n     */\n    public int udpPort()\n    {\n        return getShort(UDP_PORT_FIELD_OFFSET, LITTLE_ENDIAN) & 0xFFFF;\n    }\n\n    /**\n     * Set the age of the entry based on last observed activity.\n     *\n     * @param ageInMs of the entry based on last observed activity.\n     * @return this for a fluent API.\n     */\n    public ResolutionEntryFlyweight ageInMs(final int ageInMs)\n    {\n        putInt(AGE_IN_MS_FIELD_OFFSET, ageInMs, LITTLE_ENDIAN);\n        return this;\n    }\n\n    /**\n     * Get the age of the entry based on last observed activity.\n     *\n     * @return the age of the entry based on last observed activity.\n     */\n    public int ageInMs()\n    {\n        return getInt(AGE_IN_MS_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Put the address into the resolution entry. It must come after calling {@link #resType(byte)}.\n     *\n     * @param address to be written for the resolution entry.\n     * @return this for a fluent API.\n     */\n    public ResolutionEntryFlyweight putAddress(final byte[] address)\n    {\n        switch (resType())\n        {\n            case RES_TYPE_NAME_TO_IP4_MD:\n                if (ADDRESS_LENGTH_IP4 != address.length)\n                {\n                    throw new IllegalArgumentException(\"Invalid address length: \" + address.length);\n                }\n                putBytes(ADDRESS_FIELD_OFFSET, address, 0, ADDRESS_LENGTH_IP4);\n                break;\n\n            case RES_TYPE_NAME_TO_IP6_MD:\n                if (ADDRESS_LENGTH_IP6 != address.length)\n                {\n                    throw new IllegalArgumentException(\"Invalid address length: \" + address.length);\n                }\n                putBytes(ADDRESS_FIELD_OFFSET, address, 0, ADDRESS_LENGTH_IP6);\n                break;\n\n            default:\n                throw new IllegalStateException(\"unknown RES_TYPE=\" + resType());\n        }\n\n        return this;\n    }\n\n    /**\n     * Get the address for the entry by copying it into the destination buffer.\n     *\n     * @param dstBuffer into which the address will be copied.\n     * @return length of the address copied.\n     */\n    public int getAddress(final byte[] dstBuffer)\n    {\n        switch (resType())\n        {\n            case RES_TYPE_NAME_TO_IP4_MD:\n                if (ADDRESS_LENGTH_IP4 > dstBuffer.length)\n                {\n                    throw new IllegalArgumentException(\"Insufficient length: \" + dstBuffer.length);\n                }\n                getBytes(ADDRESS_FIELD_OFFSET, dstBuffer, 0, ADDRESS_LENGTH_IP4);\n                return ADDRESS_LENGTH_IP4;\n\n            case RES_TYPE_NAME_TO_IP6_MD:\n                if (ADDRESS_LENGTH_IP6 > dstBuffer.length)\n                {\n                    throw new IllegalArgumentException(\"Insufficient length: \" + dstBuffer.length);\n                }\n                getBytes(ADDRESS_FIELD_OFFSET, dstBuffer, 0, ADDRESS_LENGTH_IP6);\n                return ADDRESS_LENGTH_IP6;\n        }\n\n        throw new IllegalStateException(\"unknown RES_TYPE=\" + resType());\n    }\n\n    /**\n     * Append the address to the provided {@link Appendable} in ASCII format.\n     *\n     * @param appendable to which the address will be appended.\n     */\n    public void appendAddress(final Appendable appendable)\n    {\n        try\n        {\n            switch (resType())\n            {\n                case RES_TYPE_NAME_TO_IP4_MD:\n                {\n                    final int i = ADDRESS_FIELD_OFFSET;\n                    appendable\n                        .append(String.valueOf(getByte(i) & 0xFF))\n                        .append('.')\n                        .append(String.valueOf(getByte(i + 1) & 0xFF))\n                        .append('.')\n                        .append(String.valueOf(getByte(i + 2) & 0xFF))\n                        .append('.')\n                        .append(String.valueOf(getByte(i + 3) & 0xFF));\n                    break;\n                }\n\n                case RES_TYPE_NAME_TO_IP6_MD:\n                {\n                    final int i = ADDRESS_FIELD_OFFSET;\n                    appendable\n                        .append(toHexString(((getByte(i) << 8) & 0xFF00) | getByte(i + 1) & 0xFF))\n                        .append(':')\n                        .append(toHexString(((getByte(i + 2) << 8) & 0xFF00) | getByte(i + 3) & 0xFF))\n                        .append(':')\n                        .append(toHexString(((getByte(i + 4) << 8) & 0xFF00) | getByte(i + 5) & 0xFF))\n                        .append(':')\n                        .append(toHexString(((getByte(i + 6) << 8) & 0xFF00) | getByte(i + 7) & 0xFF))\n                        .append(':')\n                        .append(toHexString(((getByte(i + 8) << 8) & 0xFF00) | getByte(i + 9) & 0xFF))\n                        .append(':')\n                        .append(toHexString(((getByte(i + 10) << 8) & 0xFF00) | getByte(i + 11) & 0xFF))\n                        .append(':')\n                        .append(toHexString(((getByte(i + 12) << 8) & 0xFF00) | getByte(i + 13) & 0xFF))\n                        .append(':')\n                        .append(toHexString(((getByte(i + 14) << 8) & 0xFF00) | getByte(i + 15) & 0xFF));\n                    break;\n                }\n\n                default:\n                    appendable.append(\"unknown RES_TYPE=\").append(String.valueOf(resType()));\n            }\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * Put the name for the resolution entry into the frame.\n     *\n     * @param name for the resolution entry.\n     * @return this for a fluent API.\n     */\n    public ResolutionEntryFlyweight putName(final byte[] name)\n    {\n        final int nameOffset = nameOffset(resType());\n\n        putShort(nameOffset, (short)name.length, LITTLE_ENDIAN);\n        putBytes(nameOffset + SIZE_OF_SHORT, name);\n        return this;\n    }\n\n    /**\n     * Get the name for the entry by copying it into the destination buffer.\n     *\n     * @param dstBuffer into which the name will be copied.\n     * @return the number of bytes copied.\n     */\n    public int getName(final byte[] dstBuffer)\n    {\n        final int nameOffset = nameOffset(resType());\n        final short nameLength = getShort(nameOffset, LITTLE_ENDIAN);\n        if (nameLength > dstBuffer.length)\n        {\n            throw new IllegalArgumentException(\"Insufficient length: \" + dstBuffer.length);\n        }\n\n        getBytes(nameOffset + SIZE_OF_SHORT, dstBuffer, 0, nameLength);\n\n        return nameLength;\n    }\n\n    /**\n     * Append the name to the provided {@link Appendable} in ASCII format.\n     *\n     * @param appendable to which the name will be appended.\n     */\n    public void appendName(final StringBuilder appendable)\n    {\n        final int nameOffset = nameOffset(resType());\n        final short nameLength = getShort(nameOffset, LITTLE_ENDIAN);\n\n        getStringWithoutLengthAscii(nameOffset + SIZE_OF_SHORT, nameLength, appendable);\n    }\n\n    /**\n     * Total length of the entry in bytes.\n     *\n     * @return total length of the entry in bytes.\n     */\n    public int entryLength()\n    {\n        final int nameOffset = nameOffset(resType());\n\n        return align(nameOffset + SIZE_OF_SHORT + getShort(nameOffset, LITTLE_ENDIAN), SIZE_OF_LONG);\n    }\n\n    /**\n     * Offset in the entry at which the name begins depending on resolution type.\n     *\n     * @param resType for the protocol family.\n     * @return offset in the entry at which the name field begins.\n     */\n    public static int nameOffset(final byte resType)\n    {\n        switch (resType)\n        {\n            case RES_TYPE_NAME_TO_IP4_MD:\n                return ADDRESS_FIELD_OFFSET + ADDRESS_LENGTH_IP4;\n\n            case RES_TYPE_NAME_TO_IP6_MD:\n                return ADDRESS_FIELD_OFFSET + ADDRESS_LENGTH_IP6;\n        }\n\n        throw new IllegalStateException(\"unknown RES_TYPE=\" + resType);\n    }\n\n    /**\n     * Calculate the length required for the entry when encoded.\n     *\n     * @param resType    of the entry.\n     * @param nameLength of the name in the entry.\n     * @return the required length of the entry in bytes.\n     */\n    public static int entryLengthRequired(final byte resType, final int nameLength)\n    {\n        return align(nameOffset(resType) + SIZE_OF_SHORT + nameLength, SIZE_OF_LONG);\n    }\n\n    /**\n     * Get the length of the address given a resolution type.\n     *\n     * @param resType to lookup address length for.\n     * @return the length of the address given a resolution type.\n     */\n    public static int addressLength(final byte resType)\n    {\n        switch (resType)\n        {\n            case RES_TYPE_NAME_TO_IP4_MD:\n                return ADDRESS_LENGTH_IP4;\n\n            case RES_TYPE_NAME_TO_IP6_MD:\n                return ADDRESS_LENGTH_IP6;\n        }\n\n        throw new IllegalStateException(\"unknown RES_TYPE=\" + resType);\n    }\n\n    /**\n     * Is the local address a match for binding a socket to ANY IP.\n     *\n     * @param address       to check which can be IPv4 or IPv6.\n     * @param addressLength of the address in bytes.\n     * @return true if address is an ANY match otherwise false.\n     */\n    public static boolean isAnyLocalAddress(final byte[] address, final int addressLength)\n    {\n        if (addressLength == ADDRESS_LENGTH_IP4)\n        {\n            return 0 == address[0] && 0 == address[1] && 0 == address[2] && 0 == address[3];\n        }\n        else if (addressLength == ADDRESS_LENGTH_IP6)\n        {\n            byte val = 0;\n\n            for (int i = 0; i < ADDRESS_LENGTH_IP6; i++)\n            {\n                val |= address[i];\n            }\n\n            return 0 == val;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/ResponseSetupFlyweight.java",
    "content": "/*\n * Copyright 2014-2023 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * HeaderFlyweight for Response Setup Message Frames.\n * <p>\n * <a target=\"_blank\" href=\"https://github.com/aeron-io/aeron/wiki/Transport-Protocol-Specification#stream-setup\">\n *     Stream Response Setup</a> wiki page.\n */\npublic class ResponseSetupFlyweight extends HeaderFlyweight\n{\n    /**\n     * Header length in bytes.\n     */\n    public static final int HEADER_LENGTH = 20;\n\n    /**\n     * Offset in the frame at which the session-id field begins.\n     */\n    private static final int SESSION_ID_FIELD_OFFSET = 8;\n\n    /**\n     * Offset in the frame at which the stream-id field begins.\n     */\n    private static final int STREAM_ID_FIELD_OFFSET = 12;\n\n    /**\n     * Offset in the frame at which the response session-id field begins.\n     */\n    private static final int RESPONSE_SESSION_ID_FIELD_OFFSET = 16;\n\n    /**\n     * Default constructor which can later be used to wrap a frame.\n     */\n    public ResponseSetupFlyweight()\n    {\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public ResponseSetupFlyweight(final UnsafeBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public ResponseSetupFlyweight(final ByteBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Get session id field.\n     *\n     * @return session id field.\n     */\n    public int sessionId()\n    {\n        return getInt(SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set session id field.\n     *\n     * @param sessionId field value.\n     * @return this for a fluent API.\n     */\n    public ResponseSetupFlyweight sessionId(final int sessionId)\n    {\n        putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get stream id field.\n     *\n     * @return stream id field.\n     */\n    public int streamId()\n    {\n        return getInt(STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set stream id field.\n     *\n     * @param streamId field value.\n     * @return this for a fluent API.\n     */\n    public ResponseSetupFlyweight streamId(final int streamId)\n    {\n        putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get response session id field.\n     *\n     * @return response session id field.\n     */\n    public int responseSessionId()\n    {\n        return getInt(RESPONSE_SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set stream id field.\n     *\n     * @param streamId field value.\n     * @return this for a fluent API.\n     */\n    public ResponseSetupFlyweight responseSessionId(final int streamId)\n    {\n        putInt(RESPONSE_SESSION_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/RttMeasurementFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * Flyweight for an RTT Measurement Frame Header.\n * <p>\n * <a target=\"_blank\"\n *    href=\"https://github.com/aeron-io/aeron/wiki/Transport-Protocol-Specification#rtt-measurement-header\">\n * RTT Measurement Frame Header</a> wiki page.\n */\npublic class RttMeasurementFlyweight extends HeaderFlyweight\n{\n    /**\n     * Flag set to indicate this is a reply message.\n     */\n    public static final short REPLY_FLAG = 0x80;\n\n    /**\n     * Length of the header of the frame.\n     */\n    public static final int HEADER_LENGTH = 40;\n\n    /**\n     * Offset in the frame at which the session-id field begins.\n     */\n    private static final int SESSION_ID_FIELD_OFFSET = 8;\n\n    /**\n     * Offset in the frame at which the stream-id field begins.\n     */\n    private static final int STREAM_ID_FIELD_OFFSET = 12;\n\n    /**\n     * Offset in the frame at which the echo timestamp field begins.\n     */\n    private static final int ECHO_TIMESTAMP_FIELD_OFFSET = 16;\n\n    /**\n     * Offset in the frame at which the reception delta field begins.\n     */\n    private static final int RECEPTION_DELTA_FIELD_OFFSET = 24;\n\n    /**\n     * Offset in the frame at which the receiver-id field begins.\n     */\n    private static final int RECEIVER_ID_FIELD_OFFSET = 32;\n\n    /**\n     * Default constructor which can later be used to wrap a frame.\n     */\n    public RttMeasurementFlyweight()\n    {\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public RttMeasurementFlyweight(final ByteBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public RttMeasurementFlyweight(final UnsafeBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * The session-id for the stream.\n     *\n     * @return session-id for the stream.\n     */\n    public int sessionId()\n    {\n        return getInt(SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set session-id for the stream.\n     *\n     * @param sessionId session-id for the stream.\n     * @return this for a fluent API.\n     */\n    public RttMeasurementFlyweight sessionId(final int sessionId)\n    {\n        putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The stream-id for the stream.\n     *\n     * @return stream-id for the stream.\n     */\n    public int streamId()\n    {\n        return getInt(STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set stream-id for the stream.\n     *\n     * @param streamId stream-id for the stream.\n     * @return this for a fluent API.\n     */\n    public RttMeasurementFlyweight streamId(final int streamId)\n    {\n        putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Timestamp to echo in a reply or the timestamp in the original RTT Measurement.\n     *\n     * @return timestamp to echo in a reply or the timestamp in the original RTT Measurement.\n     */\n    public long echoTimestampNs()\n    {\n        return getLong(ECHO_TIMESTAMP_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set timestamp to echo in a reply or the timestamp in the original RTT Measurement.\n     *\n     * @param timestampNs to echo in a reply or the timestamp in the original RTT Measurement.\n     * @return this for a fluent API.\n     */\n    public RttMeasurementFlyweight echoTimestampNs(final long timestampNs)\n    {\n        putLong(ECHO_TIMESTAMP_FIELD_OFFSET, timestampNs, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Time in nanoseconds between receiving original RTT Measurement and sending Reply RTT Measurement.\n     *\n     * @return time in nanoseconds between receiving original RTT Measurement and sending Reply RTT Measurement.\n     */\n    public long receptionDelta()\n    {\n        return getLong(RECEPTION_DELTA_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set time in nanoseconds between receiving original RTT Measurement and sending Reply RTT Measurement.\n     *\n     * @param deltaNs in nanoseconds between receiving original RTT Measurement and sending Reply RTT Measurement.\n     * @return this for a fluent API.\n     */\n    public RttMeasurementFlyweight receptionDelta(final long deltaNs)\n    {\n        putLong(RECEPTION_DELTA_FIELD_OFFSET, deltaNs, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The receiver-id which uniquely identifies a receiver of a stream.\n     *\n     * @return receiver-id which uniquely identifies a receiver of a stream.\n     */\n    public long receiverId()\n    {\n        return getLong(RECEIVER_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set receiver-id which uniquely identifies a receiver of a stream.\n     *\n     * @param id for the receiver of the stream.\n     * @return this for a fluent API.\n     */\n    public RttMeasurementFlyweight receiverId(final long id)\n    {\n        putLong(RECEIVER_ID_FIELD_OFFSET, id, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"RTTM{\" +\n            \"frame-length=\" + frameLength() +\n            \" version=\" + version() +\n            \" flags=\" + String.valueOf(flagsToChars(flags())) +\n            \" type=\" + headerType() +\n            \" session-id=\" + sessionId() +\n            \" stream-id=\" + streamId() +\n            \" echo-timestamp=\" + echoTimestampNs() +\n            \" reception-delta=\" + receptionDelta() +\n            \" receiver-id=\" + receiverId() +\n            \"}\";\n    }\n}"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/SetupFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * HeaderFlyweight for Setup Message Frames.\n * <p>\n * <a target=\"_blank\" href=\"https://github.com/aeron-io/aeron/wiki/Transport-Protocol-Specification#stream-setup\">\n *     Stream Setup</a> wiki page.\n */\npublic class SetupFlyweight extends HeaderFlyweight\n{\n    /**\n     * Length of the Setup Message Frame.\n     */\n    public static final int HEADER_LENGTH = 40;\n\n    /**\n     * Subscriber should send response channel setup message.\n     */\n    public static final short SEND_RESPONSE_SETUP_FLAG = 0x80;\n\n    /**\n     * Publication uses group/multicast semantics.\n     */\n    public static final short GROUP_FLAG = 0x40;\n\n    /**\n     * Offset in the frame at which the term-offset field begins.\n     */\n    private static final int TERM_OFFSET_FIELD_OFFSET = 8;\n\n    /**\n     * Offset in the frame at which the session-id field begins.\n     */\n    private static final int SESSION_ID_FIELD_OFFSET = 12;\n\n    /**\n     * Offset in the frame at which the stream-id field begins.\n     */\n    private static final int STREAM_ID_FIELD_OFFSET = 16;\n\n    /**\n     * Offset in the frame at which the initial-term-id field begins.\n     */\n    private static final int INITIAL_TERM_ID_FIELD_OFFSET = 20;\n\n    /**\n     * Offset in the frame at which the active-term-id field begins.\n     */\n    private static final int ACTIVE_TERM_ID_FIELD_OFFSET = 24;\n\n    /**\n     * Offset in the frame at which the term-length field begins.\n     */\n    private static final int TERM_LENGTH_FIELD_OFFSET = 28;\n\n    /**\n     * Offset in the frame at which the mtu-length field begins.\n     */\n    private static final int MTU_LENGTH_FIELD_OFFSET = 32;\n\n    /**\n     * Offset in the frame at which the multicast TTL (Time To Live) field begins.\n     */\n    private static final int TTL_FIELD_OFFSET = 36;\n\n    /**\n     * Default constructor which can later be used to wrap a frame.\n     */\n    public SetupFlyweight()\n    {\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public SetupFlyweight(final UnsafeBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public SetupFlyweight(final ByteBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Get term offset field.\n     *\n     * @return term offset field.\n     */\n    public int termOffset()\n    {\n        return getInt(TERM_OFFSET_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set term offset field.\n     *\n     * @param termOffset field value.\n     * @return this for a fluent API.\n     */\n    public SetupFlyweight termOffset(final int termOffset)\n    {\n        putInt(TERM_OFFSET_FIELD_OFFSET, termOffset, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get session id field.\n     *\n     * @return session id field.\n     */\n    public int sessionId()\n    {\n        return getInt(SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set session id field.\n     *\n     * @param sessionId field value.\n     * @return this for a fluent API.\n     */\n    public SetupFlyweight sessionId(final int sessionId)\n    {\n        putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get stream id field.\n     *\n     * @return stream id field.\n     */\n    public int streamId()\n    {\n        return getInt(STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set stream id field.\n     *\n     * @param streamId field value.\n     * @return this for a fluent API.\n     */\n    public SetupFlyweight streamId(final int streamId)\n    {\n        putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get initial term id field.\n     *\n     * @return initial term id field.\n     */\n    public int initialTermId()\n    {\n        return getInt(INITIAL_TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set initial term id field.\n     *\n     * @param termId field value.\n     * @return this for a fluent API.\n     */\n    public SetupFlyweight initialTermId(final int termId)\n    {\n        putInt(INITIAL_TERM_ID_FIELD_OFFSET, termId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get active term id field.\n     *\n     * @return term id field.\n     */\n    public int activeTermId()\n    {\n        return getInt(ACTIVE_TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set active term id field.\n     *\n     * @param termId field value.\n     * @return this for a fluent API.\n     */\n    public SetupFlyweight activeTermId(final int termId)\n    {\n        putInt(ACTIVE_TERM_ID_FIELD_OFFSET, termId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get term length field.\n     *\n     * @return term length field value.\n     */\n    public int termLength()\n    {\n        return getInt(TERM_LENGTH_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set term length field.\n     *\n     * @param termLength field value.\n     * @return this for a fluent API.\n     */\n    public SetupFlyweight termLength(final int termLength)\n    {\n        putInt(TERM_LENGTH_FIELD_OFFSET, termLength, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get MTU length field.\n     *\n     * @return MTU length field value.\n     */\n    public int mtuLength()\n    {\n        return getInt(MTU_LENGTH_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set MTU length field.\n     *\n     * @param mtuLength field value.\n     * @return this for a fluent API.\n     */\n    public SetupFlyweight mtuLength(final int mtuLength)\n    {\n        putInt(MTU_LENGTH_FIELD_OFFSET, mtuLength, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Get the TTL field.\n     *\n     * @return TTL field value.\n     */\n    public int ttl()\n    {\n        return getInt(TTL_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the TTL field.\n     *\n     * @param ttl field value.\n     * @return this for a fluent API.\n     */\n    public SetupFlyweight ttl(final int ttl)\n    {\n        putInt(TTL_FIELD_OFFSET, ttl, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"SETUP{\" +\n            \"frame-length=\" + frameLength() +\n            \" version=\" + version() +\n            \" flags=\" + String.valueOf(flagsToChars(flags())) +\n            \" type=\" + headerType() +\n            \" term-offset=\" + termOffset() +\n            \" session-id=\" + sessionId() +\n            \" stream-id=\" + streamId() +\n            \" initial-term-id=\" + initialTermId() +\n            \" active-term-id=\" + activeTermId() +\n            \" term-length=\" + termLength() +\n            \" mtu-length=\" + mtuLength() +\n            \" ttl=\" + ttl() +\n            \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/StatusMessageFlyweight.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Flyweight for a Status Message Frame.\n * <p>\n * <a target=\"_blank\" href=\"https://github.com/aeron-io/aeron/wiki/Transport-Protocol-Specification#status-messages\">\n *     Status Message</a> wiki page.\n */\npublic class StatusMessageFlyweight extends HeaderFlyweight\n{\n    /**\n     * Length of the Status Message Frame.\n     */\n    public static final int HEADER_LENGTH = 36;\n\n    /**\n     * Publisher should send SETUP frame.\n     */\n    public static final short SEND_SETUP_FLAG = 0x80;\n\n    /**\n     * Publisher should treat Subscriber as going away.\n     */\n    public static final short END_OF_STREAM_FLAG = 0x40;\n\n    /**\n     * Offset in the frame at which the session-id field begins.\n     */\n    private static final int SESSION_ID_FIELD_OFFSET = 8;\n\n    /**\n     * Offset in the frame at which the stream-id field begins.\n     */\n    private static final int STREAM_ID_FIELD_OFFSET = 12;\n\n    /**\n     * Offset in the frame at which the consumption term-id field begins.\n     */\n    private static final int CONSUMPTION_TERM_ID_FIELD_OFFSET = 16;\n\n    /**\n     * Offset in the frame at which the consumption term-offset field begins.\n     */\n    private static final int CONSUMPTION_TERM_OFFSET_FIELD_OFFSET = 20;\n\n    /**\n     * Offset in the frame at which the receiver window length field begins.\n     */\n    private static final int RECEIVER_WINDOW_FIELD_OFFSET = 24;\n\n    /**\n     * Offset in the frame at which the receiver-id field begins.\n     */\n    private static final int RECEIVER_ID_FIELD_OFFSET = 28;\n\n    /**\n     * Offset in the frame at which the ASF field begins.\n     */\n    private static final int APP_SPECIFIC_FEEDBACK_FIELD_OFFSET = 36;\n\n    /**\n     * Offset in the frame at which the gtag field begins.\n     */\n    private static final int GROUP_TAG_FIELD_OFFSET = APP_SPECIFIC_FEEDBACK_FIELD_OFFSET;\n\n    /**\n     * Default constructor which can later be used to wrap a frame.\n     */\n    public StatusMessageFlyweight()\n    {\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public StatusMessageFlyweight(final ByteBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * Construct the flyweight over a frame.\n     *\n     * @param buffer containing the frame.\n     */\n    public StatusMessageFlyweight(final UnsafeBuffer buffer)\n    {\n        super(buffer);\n    }\n\n    /**\n     * The session-id for the stream.\n     *\n     * @return session-id for the stream.\n     */\n    public int sessionId()\n    {\n        return getInt(SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the session-id of the stream.\n     *\n     * @param sessionId field value.\n     * @return this for a fluent API.\n     */\n    public StatusMessageFlyweight sessionId(final int sessionId)\n    {\n        putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The stream-id for the stream.\n     *\n     * @return the session-id for the stream.\n     */\n    public int streamId()\n    {\n        return getInt(STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the session-id for the stream.\n     *\n     * @param streamId field value.\n     * @return this for a fluent API.\n     */\n    public StatusMessageFlyweight streamId(final int streamId)\n    {\n        putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The highest consumption offset within the term.\n     *\n     * @return the highest consumption offset within the term.\n     */\n    public int consumptionTermOffset()\n    {\n        return getInt(CONSUMPTION_TERM_OFFSET_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the highest consumption offset within the term.\n     *\n     * @param termOffset field value.\n     * @return this for a fluent API.\n     */\n    public StatusMessageFlyweight consumptionTermOffset(final int termOffset)\n    {\n        putInt(CONSUMPTION_TERM_OFFSET_FIELD_OFFSET, termOffset, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The highest consumption term-id.\n     *\n     * @return highest consumption term-id.\n     */\n    public int consumptionTermId()\n    {\n        return getInt(CONSUMPTION_TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the highest consumption term-id.\n     *\n     * @param termId field value.\n     * @return this for a fluent API.\n     */\n    public StatusMessageFlyweight consumptionTermId(final int termId)\n    {\n        putInt(CONSUMPTION_TERM_ID_FIELD_OFFSET, termId, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * The receiver window length they will accept.\n     *\n     * @return receiver window length they will accept.\n     */\n    public int receiverWindowLength()\n    {\n        return getInt(RECEIVER_WINDOW_FIELD_OFFSET, LITTLE_ENDIAN);\n    }\n\n    /**\n     * Set the receiver window length they will accept.\n     *\n     * @param receiverWindowLength field value.\n     * @return this for a fluent API.\n     */\n    public StatusMessageFlyweight receiverWindowLength(final int receiverWindowLength)\n    {\n        putInt(RECEIVER_WINDOW_FIELD_OFFSET, receiverWindowLength, LITTLE_ENDIAN);\n\n        return this;\n    }\n\n    /**\n     * Identifier for the receiver to distinguish them for FlowControl strategies.\n     *\n     * @return identifier for the receiver to distinguish them for FlowControl strategies.\n     */\n    public long receiverId()\n    {\n        return getLongUnaligned(RECEIVER_ID_FIELD_OFFSET);\n    }\n\n    /**\n     * Identifier for the receiver to distinguish them for FlowControl strategies.\n     *\n     * @param id for the receiver to distinguish them for FlowControl strategies.\n     * @return this for a fluent API.\n     */\n    public StatusMessageFlyweight receiverId(final long id)\n    {\n        return putLongUnaligned(RECEIVER_ID_FIELD_OFFSET, id);\n    }\n\n    /**\n     * The length of the Application Specific Feedback (or gtag).\n     *\n     * @return length, in bytes, of the Application Specific Feedback (or gtag).\n     */\n    public int asfLength()\n    {\n        return (frameLength() - HEADER_LENGTH);\n    }\n\n    /**\n     * The group tag (if present) from the Status Message.\n     *\n     * @return the group tag value or 0 if not present.\n     */\n    public long groupTag()\n    {\n        final int frameLength = frameLength();\n\n        if (frameLength > HEADER_LENGTH)\n        {\n            if (frameLength > (HEADER_LENGTH + SIZE_OF_LONG))\n            {\n                throw new AeronException(\n                    \"SM has longer application specific feedback (\" + (frameLength - HEADER_LENGTH) + \") than gtag\");\n            }\n\n            return getLongUnaligned(GROUP_TAG_FIELD_OFFSET);\n        }\n\n        return 0;\n    }\n\n    /**\n     * Set the Receiver Group Tag for the Status Message.\n     *\n     * @param groupTag value to set if not null.\n     * @return this for a fluent API.\n     */\n    public StatusMessageFlyweight groupTag(final Long groupTag)\n    {\n        if (null != groupTag)\n        {\n            frameLength(HEADER_LENGTH + SIZE_OF_LONG);\n            putLongUnaligned(GROUP_TAG_FIELD_OFFSET, groupTag);\n        }\n        else\n        {\n            // make sure to explicitly set the frameLength in case of previous tags used.\n            frameLength(HEADER_LENGTH);\n        }\n\n        return this;\n    }\n\n    /**\n     * Return the field offset within the flyweight for the group tag field.\n     *\n     * @return offset of group tag field\n     */\n    public static int groupTagFieldOffset()\n    {\n        return GROUP_TAG_FIELD_OFFSET;\n    }\n\n    /**\n     * Get long value from a field that is not aligned on an 8 byte boundary.\n     *\n     * @param offset of the field to get.\n     * @return value of field.\n     */\n    public long getLongUnaligned(final int offset)\n    {\n        final long value;\n        if (ByteOrder.nativeOrder() == LITTLE_ENDIAN)\n        {\n            value =\n                (((long)getByte(offset + 7)) << 56) |\n                (((long)getByte(offset + 6) & 0xFF) << 48) |\n                (((long)getByte(offset + 5) & 0xFF) << 40) |\n                (((long)getByte(offset + 4) & 0xFF) << 32) |\n                (((long)getByte(offset + 3) & 0xFF) << 24) |\n                (((long)getByte(offset + 2) & 0xFF) << 16) |\n                (((long)getByte(offset + 1) & 0xFF) << 8) |\n                (((long)getByte(offset) & 0xFF));\n        }\n        else\n        {\n            value =\n                (((long)getByte(offset)) << 56) |\n                (((long)getByte(offset + 1) & 0xFF) << 48) |\n                (((long)getByte(offset + 2) & 0xFF) << 40) |\n                (((long)getByte(offset + 3) & 0xFF) << 32) |\n                (((long)getByte(offset + 4) & 0xFF) << 24) |\n                (((long)getByte(offset + 5) & 0xFF) << 16) |\n                (((long)getByte(offset + 6) & 0xFF) << 8) |\n                (((long)getByte(offset + 7) & 0xFF));\n        }\n\n        return value;\n    }\n\n    /**\n     * Set long value into a field that is not aligned on an 8 byte boundary.\n     *\n     * @param offset of the field to put.\n     * @param value of the field to put.\n     * @return this for fluent API.\n     */\n    public StatusMessageFlyweight putLongUnaligned(final int offset, final long value)\n    {\n        if (ByteOrder.nativeOrder() == LITTLE_ENDIAN)\n        {\n            putByte(offset + 7, (byte)(value >> 56));\n            putByte(offset + 6, (byte)(value >> 48));\n            putByte(offset + 5, (byte)(value >> 40));\n            putByte(offset + 4, (byte)(value >> 32));\n            putByte(offset + 3, (byte)(value >> 24));\n            putByte(offset + 2, (byte)(value >> 16));\n            putByte(offset + 1, (byte)(value >> 8));\n            putByte(offset, (byte)(value));\n        }\n        else\n        {\n            putByte(offset, (byte)(value >> 56));\n            putByte(offset + 1, (byte)(value >> 48));\n            putByte(offset + 2, (byte)(value >> 40));\n            putByte(offset + 3, (byte)(value >> 32));\n            putByte(offset + 4, (byte)(value >> 24));\n            putByte(offset + 5, (byte)(value >> 16));\n            putByte(offset + 6, (byte)(value >> 8));\n            putByte(offset + 7, (byte)(value));\n        }\n\n        return this;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"STATUS{\" +\n            \"frame-length=\" + frameLength() +\n            \" version=\" + version() +\n            \" flags=\" + String.valueOf(flagsToChars(flags())) +\n            \" type=\" + headerType() +\n            \" session-id=\" + sessionId() +\n            \" stream-id=\" + streamId() +\n            \" consumption-term-id=\" + consumptionTermId() +\n            \" consumption-term-offset=\" + consumptionTermOffset() +\n            \" receiver-window-length=\" + receiverWindowLength() +\n            \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/protocol/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Flyweight classes for encoding and decoding network protocol messages.\n */\npackage io.aeron.protocol;"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/AuthenticationException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport io.aeron.exceptions.AeronException;\n\n/**\n * Used to indicate a failed authentication attempt when connecting to a system.\n */\npublic class AuthenticationException extends AeronException\n{\n    private static final long serialVersionUID = -3205449285259494699L;\n\n    /**\n     * Default exception as {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     */\n    public AuthenticationException()\n    {\n        super();\n    }\n\n    /**\n     * Authentication exception with provided message and {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     *\n     * @param message to detail the exception.\n     */\n    public AuthenticationException(final String message)\n    {\n        super(message);\n    }\n\n    /**\n     * Authentication exception with provided cause and {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     *\n     * @param cause of the error.\n     */\n    public AuthenticationException(final Throwable cause)\n    {\n        super(cause);\n    }\n\n    /**\n     * Authentication exception with a detailed message and cause.\n     *\n     * @param message providing detail on the error.\n     * @param cause   of the error.\n     */\n    public AuthenticationException(final String message, final Throwable cause)\n    {\n        super(message, cause);\n    }\n\n    /**\n     * Constructs a new Authentication exception with a detail message, cause, suppression enabled or disabled,\n     * and writable stack trace enabled or disabled, in the category\n     * {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     *\n     * @param message            providing detail on the error.\n     * @param cause              of the error.\n     * @param enableSuppression  is suppression enabled or not.\n     * @param writableStackTrace is the stack trace writable or not.\n     */\n    protected AuthenticationException(\n        final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace)\n    {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/Authenticator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\n/**\n * Interface for an Authenticator to handle authentication of clients to a system.\n * <p>\n * The session-id refers to the authentication session and not the Aeron transport session assigned to a publication.\n *\n * @see SessionProxy\n * @see AuthenticatorSupplier\n */\npublic interface Authenticator\n{\n    /**\n     * Called upon reception of a Connect Request and will be followed up by multiple calls to\n     * {@link #onConnectedSession(SessionProxy, long)} once the response channel is connected.\n     *\n     * @param sessionId          to identify the client session connecting.\n     * @param encodedCredentials from the Connect Request. Will not be null, but may be 0 length.\n     * @param nowMs              current epoch time in milliseconds.\n     */\n    void onConnectRequest(long sessionId, byte[] encodedCredentials, long nowMs);\n\n    /**\n     * Called upon reception of a Challenge Response from an unauthenticated client.\n     *\n     * @param sessionId          to identify the client session connecting.\n     * @param encodedCredentials from the Challenge Response. Will not be null, but may be 0 length.\n     * @param nowMs              current epoch time in milliseconds.\n     */\n    void onChallengeResponse(long sessionId, byte[] encodedCredentials, long nowMs);\n\n    /**\n     * Called when a client's response channel has been connected. This method may be called multiple times until the\n     * session timeouts, is challenged, authenticated, or rejected.\n     *\n     * @param sessionProxy to use to update authentication status. Proxy is only valid for the duration of the call.\n     * @param nowMs        current epoch time in milliseconds.\n     * @see SessionProxy\n     */\n    void onConnectedSession(SessionProxy sessionProxy, long nowMs);\n\n    /**\n     * Called when a challenged client should be able to accept a response from the authenticator.\n     * <p>\n     * When this is called, there is no assumption that a Challenge Response has been received, plus this method\n     * may be called multiple times.\n     * <p>\n     * It is up to the concrete class to provide any timeout management.\n     *\n     * @param sessionProxy to use to update authentication status. Proxy is only valid for the duration of the call.\n     * @param nowMs        current epoch time in milliseconds.\n     * @see SessionProxy\n     */\n    void onChallengedSession(SessionProxy sessionProxy, long nowMs);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/AuthenticatorSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport java.util.function.Supplier;\n\n/**\n * Used to supply instances of {@link Authenticator}.\n */\n@FunctionalInterface\npublic interface AuthenticatorSupplier extends Supplier<Authenticator>\n{\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/AuthorisationService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\n/**\n * Interface for an authorisation service to handle authorisation checks on clients performing actions to a system.\n *\n * @see AuthorisationServiceSupplier\n */\n@FunctionalInterface\npublic interface AuthorisationService\n{\n    /**\n     * An {@link AuthorisationService} instance that allows every action.\n     */\n    AuthorisationService ALLOW_ALL = (protocolId, actionId, type, encodedPrincipal) -> true;\n\n    /**\n     * An {@link AuthorisationService} instance that forbids all actions.\n     */\n    AuthorisationService DENY_ALL = (protocolId, actionId, type, encodedPrincipal) -> false;\n\n    /**\n     * Special case token for authorisation service supplier that will deny all requests.\n     */\n    String DENY_ALL_NAME = \"DENY_ALL\";\n\n    /**\n     * Special case token for authorisation service supplier that allow all requests.\n     */\n    String ALLOW_ALL_NAME = \"ALLOW_ALL\";\n\n    /**\n     * Checks if the client with authenticated credentials is allowed to perform an action indicated by the\n     * given {@code actionId}.\n     *\n     * @param protocolId       of the protocol to which the action belongs, e.g. a SBE schema id.\n     * @param actionId         of the command being checked, e.g. a SBE message template id.\n     * @param type             optional type for the action being checked, may be {@code null}. For example for\n     *                         an admin request in the cluster it will contain {@code AdminRequestType} value which\n     *                         denotes the exact kind of the request.\n     * @param encodedPrincipal that has been authenticated.\n     * @return {@code true} if the client is authorised to execute the action or {@code false} otherwise.\n     */\n    boolean isAuthorised(int protocolId, int actionId, Object type, byte[] encodedPrincipal);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/AuthorisationServiceSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport java.util.function.Supplier;\n\n/**\n * Used to supply instances of {@link AuthorisationService}.\n */\n@FunctionalInterface\npublic interface AuthorisationServiceSupplier extends Supplier<AuthorisationService>\n{\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/CredentialsSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\n/**\n * Supplier of credentials for authentication with a system.\n * <p>\n * Implement this interface to supply credentials for clients. If no credentials are required then the\n * {@link NullCredentialsSupplier} can be used.\n */\npublic interface CredentialsSupplier\n{\n    /**\n     * Provide encoded credentials to be included in Session Connect message a system.\n     *\n     * @return encoded credentials to be included in the Session Connect message to a system.\n     */\n    byte[] encodedCredentials();\n\n    /**\n     * Given some encoded challenge, provide the credentials to be included in a Challenge Response as part of\n     * authentication with a system.\n     *\n     * @param encodedChallenge from the cluster to use in providing a credential.\n     * @return encoded credentials to be included in the Challenge Response to the system.\n     */\n    byte[] onChallenge(byte[] encodedChallenge);\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/DefaultAuthenticatorSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport org.agrona.collections.ArrayUtil;\n\n/**\n * Default Authenticator which authenticates all connection requests immediately.\n */\npublic class DefaultAuthenticatorSupplier implements AuthenticatorSupplier\n{\n    /**\n     * Singleton instance.\n     */\n    public static final DefaultAuthenticatorSupplier INSTANCE = new DefaultAuthenticatorSupplier();\n\n    /**\n     * The null encoded principal is an empty array of bytes.\n     */\n    public static final byte[] NULL_ENCODED_PRINCIPAL = ArrayUtil.EMPTY_BYTE_ARRAY;\n\n    /**\n     * Singleton instance which can be used to avoid allocation.\n     */\n    public static final Authenticator DEFAULT_AUTHENTICATOR = new DefaultAuthenticator();\n\n    /**\n     * Default constructor.\n     */\n    public DefaultAuthenticatorSupplier()\n    {\n    }\n\n    /**\n     * Gets the singleton instance {@link #DEFAULT_AUTHENTICATOR} which authenticates all connection requests\n     * immediately.\n     *\n     * @return {@link #DEFAULT_AUTHENTICATOR} which authenticates all connection requests immediately.\n     */\n    public Authenticator get()\n    {\n        return DEFAULT_AUTHENTICATOR;\n    }\n\n    static final class DefaultAuthenticator implements Authenticator\n    {\n        public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n        {\n        }\n\n        public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n        {\n        }\n\n        public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n        {\n            sessionProxy.authenticate(NULL_ENCODED_PRINCIPAL);\n        }\n\n        public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n        {\n            sessionProxy.authenticate(NULL_ENCODED_PRINCIPAL);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/NullCredentialsSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport org.agrona.collections.ArrayUtil;\n\n/**\n * Null provider of credentials when no authentication is required.\n */\npublic class NullCredentialsSupplier implements CredentialsSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public NullCredentialsSupplier()\n    {\n    }\n\n    /**\n     * Null credentials are an empty array of bytes.\n     */\n    public static final byte[] NULL_CREDENTIAL = ArrayUtil.EMPTY_BYTE_ARRAY;\n\n    /**\n     * {@inheritDoc}\n     */\n    public byte[] encodedCredentials()\n    {\n        return NULL_CREDENTIAL;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public byte[] onChallenge(final byte[] encodedChallenge)\n    {\n        return NULL_CREDENTIAL;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/SessionProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\n/**\n * Representation of a session during the authentication process from the perspective of an {@link Authenticator}.\n *\n * @see Authenticator\n */\npublic interface SessionProxy\n{\n    /**\n     * The identity of the potential session assigned by the system.\n     *\n     * @return identity for the potential session.\n     */\n    long sessionId();\n\n    /**\n     * Inform the system that the session requires a challenge by sending the provided encoded challenge.\n     *\n     * @param encodedChallenge to be sent to the client.\n     * @return true if challenge was accepted to be sent at present time or false if it will be retried later.\n     */\n    boolean challenge(byte[] encodedChallenge);\n\n    /**\n     * Inform the system that the session has met authentication requirements.\n     *\n     * @param encodedPrincipal that has passed authentication.\n     * @return true if authentication was accepted at present time or false if it will be retried later.\n     */\n    boolean authenticate(byte[] encodedPrincipal);\n\n    /**\n     * Inform the system that the session should be rejected.\n     */\n    void reject();\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/SimpleAuthenticator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\n/**\n * An authenticator that works off a simple principal/credential pair constructed by a builder. It only supports simple\n * authentication, not challenge/response.\n */\npublic final class SimpleAuthenticator implements Authenticator\n{\n    private final Object2ObjectHashMap<Credentials, Principal> principalsByCredentialsMap =\n        new Object2ObjectHashMap<>();\n\n    private final Long2ObjectHashMap<Principal> authenticatedSessionIdToPrincipalMap = new Long2ObjectHashMap<>();\n\n    private SimpleAuthenticator(final Builder builder)\n    {\n        for (final Principal principal : builder.principals)\n        {\n            principalsByCredentialsMap.put(principal.credentials, principal);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n    {\n        final Principal principal = principalsByCredentialsMap.get(new Credentials(encodedCredentials));\n\n        if (null != principal && principal.credentialsMatch(encodedCredentials))\n        {\n            authenticatedSessionIdToPrincipalMap.put(sessionId, principal);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n    {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n    {\n        final long sessionId = sessionProxy.sessionId();\n        final Principal principal = authenticatedSessionIdToPrincipalMap.get(sessionId);\n\n        if (null != principal)\n        {\n            if (sessionProxy.authenticate(principal.encodedPrincipal))\n            {\n                authenticatedSessionIdToPrincipalMap.remove(sessionId);\n            }\n        }\n        else\n        {\n            sessionProxy.reject();\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n    {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * Builder to create instances of SimpleAuthenticator.\n     */\n    public static class Builder\n    {\n        private final ArrayList<Principal> principals = new ArrayList<>();\n\n        /**\n         * Default constructor.\n         */\n        public Builder()\n        {\n        }\n\n        /**\n         * Add a principal/credentials pair to the list supported by this authenticator. Note that the\n         * {@link SimpleAuthenticator} keys the principals by the credentials, so the encoded credentials should\n         * include the encoded principal. The associated {@link CredentialsSupplier} used on the client should handle\n         * encoding these credentials correctly as well. For example:\n         * <pre>\n         * final SimpleAuthenticator simpleAuthenticator = new SimpleAuthenticator.Builder()\n         *     .principal(\"user\".getBytes(US_ASCII), \"user:pass\".getBytes(US_ASCII))\n         *     .newInstance();\n         * </pre>\n         *\n         * @param encodedPrincipal   principal encoded as a byte array.\n         * @param encodedCredentials credentials encoded as a byte array.\n         * @return this for a fluent API.\n         */\n        public Builder principal(final byte[] encodedPrincipal, final byte[] encodedCredentials)\n        {\n            principals.add(new Principal(encodedPrincipal, encodedCredentials));\n            return this;\n        }\n\n        /**\n         * Construct a new instance of the SimpleAuthenticator.\n         *\n         * @return a new SimpleAuthenticator instance.\n         */\n        public SimpleAuthenticator newInstance()\n        {\n            return new SimpleAuthenticator(this);\n        }\n    }\n\n    private static final class Principal\n    {\n        private final byte[] encodedPrincipal;\n        private final Credentials credentials;\n\n        private Principal(final byte[] encodedPrincipal, final byte[] encodedCredentials)\n        {\n            this.encodedPrincipal = encodedPrincipal;\n            this.credentials = new Credentials(encodedCredentials);\n        }\n\n        public boolean credentialsMatch(final byte[] encodedCredentials)\n        {\n            return Arrays.equals(credentials.encodedCredentials, encodedCredentials);\n        }\n    }\n\n    private static final class Credentials\n    {\n        private final byte[] encodedCredentials;\n\n        private Credentials(final byte[] encodedCredentials)\n        {\n            this.encodedCredentials = encodedCredentials;\n        }\n\n        public boolean equals(final Object o)\n        {\n            if (this == o)\n            {\n                return true;\n            }\n            if (o == null || getClass() != o.getClass())\n            {\n                return false;\n            }\n            final Credentials that = (Credentials)o;\n            return Arrays.equals(encodedCredentials, that.encodedCredentials);\n        }\n\n        public int hashCode()\n        {\n            return Arrays.hashCode(encodedCredentials);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/SimpleAuthorisationService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport org.agrona.collections.BiInt2ObjectMap;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Authorisation service that supports setting general and per-principal rules as well as scoping to protocol, action,\n * and type. Uses a fluent API to add authorisation rules.\n */\npublic final class SimpleAuthorisationService implements AuthorisationService\n{\n    private final AuthorisationService defaultAuthorisation;\n    private final Object2ObjectHashMap<ByteArrayAsKey, Principal> principalByKeyMap = new Object2ObjectHashMap<>();\n    private final Principal defaultPrincipal;\n\n    private SimpleAuthorisationService(final Builder builder)\n    {\n        defaultAuthorisation = builder.defaultAuthorisation;\n        principalByKeyMap.putAll(builder.principalByKeyMap);\n        defaultPrincipal = builder.defaultPrincipal;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isAuthorised(\n        final int protocolId,\n        final int actionId,\n        final Object type,\n        final byte[] encodedPrincipal)\n    {\n        Boolean isAuthorised = isAuthorised(\n            principalByKeyMap.get(new ByteArrayAsKey(encodedPrincipal)), protocolId, actionId, type);\n\n        if (null != isAuthorised)\n        {\n            return isAuthorised;\n        }\n\n        isAuthorised = isAuthorised(defaultPrincipal, protocolId, actionId, type);\n        if (null != isAuthorised)\n        {\n            return isAuthorised;\n        }\n\n        return defaultAuthorisation.isAuthorised(protocolId, actionId, type, encodedPrincipal);\n    }\n\n    private Boolean isAuthorised(\n        final Principal principal,\n        final int protocolId,\n        final int actionId,\n        final Object type)\n    {\n        if (null == principal)\n        {\n            return null;\n        }\n\n        return principal.isAuthorised(protocolId, actionId, type);\n    }\n\n    /**\n     * Builder to create the authorisation service.\n     */\n    public static class Builder\n    {\n        private AuthorisationService defaultAuthorisation = AuthorisationService.DENY_ALL;\n        private final Object2ObjectHashMap<ByteArrayAsKey, Principal> principalByKeyMap = new Object2ObjectHashMap<>();\n        private final Principal defaultPrincipal = new Principal(new byte[0]);\n\n        /**\n         * Default constructor.\n         */\n        public Builder()\n        {\n        }\n\n        /**\n         * Set the default authorisation if the authorisation request does not match any of the supplied rules.\n         *\n         * @param defaultAuthorisation an authorisation service to fall back to.\n         * @return this for a fluent API\n         * @see AuthorisationService#ALLOW_ALL\n         * @see AuthorisationService#DENY_ALL\n         */\n        public Builder defaultAuthorisation(final AuthorisationService defaultAuthorisation)\n        {\n            this.defaultAuthorisation = defaultAuthorisation;\n            return this;\n        }\n\n        /**\n         * Add rule for a specific principal that is scoped to a protocolId, actionId, and type.\n         *\n         * @param protocolId       <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param actionId         <code>sbe:message@id</code> value that the rule applies to.\n         * @param type             A parameter of the message can be used to narrow the scope of the authorisation\n         *                         rule. This is message dependent.\n         * @param encodedPrincipal The principal encoded as byte array.\n         * @param isAllowed        If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addPrincipalRule(\n            final int protocolId,\n            final int actionId,\n            final Object type,\n            final byte[] encodedPrincipal,\n            final boolean isAllowed)\n        {\n            final Principal principal = principalByKeyMap.computeIfAbsent(\n                new ByteArrayAsKey(encodedPrincipal), (key) -> new Principal(key.data));\n\n            final BiInt2ObjectMap<Set<Object>> byTypeMap = isAllowed ? principal.byProtocolActionTypeAllowed :\n                principal.byProtocolActionTypeDenied;\n\n            byTypeMap.computeIfAbsent(protocolId, actionId, (a, b) -> new HashSet<>()).add(type);\n\n            return this;\n        }\n\n        /**\n         * Add rule for a specific principal that is scoped to a protocolId, and actionId.\n         *\n         * @param protocolId       <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param actionId         <code>sbe:message@id</code> value that the rule applies to.\n         * @param encodedPrincipal The principal encoded as byte array.\n         * @param isAllowed        If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addPrincipalRule(\n            final int protocolId,\n            final int actionId,\n            final byte[] encodedPrincipal,\n            final boolean isAllowed)\n        {\n            final Principal principal = principalByKeyMap.computeIfAbsent(\n                new ByteArrayAsKey(encodedPrincipal), (key) -> new Principal(key.data));\n\n            principal.byProtocolAction.put(protocolId, actionId, isAllowed);\n            return this;\n        }\n\n        /**\n         * Add rule for a specific principal that is scoped to a protocolId.\n         *\n         * @param protocolId       <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param encodedPrincipal The principal encoded as byte array.\n         * @param isAllowed        If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addPrincipalRule(\n            final int protocolId,\n            final byte[] encodedPrincipal,\n            final boolean isAllowed)\n        {\n            final Principal principal = principalByKeyMap.computeIfAbsent(\n                new ByteArrayAsKey(encodedPrincipal), (key) -> new Principal(key.data));\n\n            principal.byProtocol.put(protocolId, Boolean.valueOf(isAllowed));\n            return this;\n        }\n\n        /**\n         * Add rule for all principals that is scoped to a protocolId, actionId, and type.\n         *\n         * @param protocolId <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param actionId   <code>sbe:message@id</code> value that the rule applies to.\n         * @param type       A parameter of the message can be used to narrow the scope of the authorisation\n         *                   rule. This is message dependent.\n         * @param isAllowed  If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addGeneralRule(\n            final int protocolId,\n            final int actionId,\n            final Object type,\n            final boolean isAllowed)\n        {\n            final BiInt2ObjectMap<Set<Object>> byTypeMap = isAllowed ? defaultPrincipal.byProtocolActionTypeAllowed :\n                defaultPrincipal.byProtocolActionTypeDenied;\n            byTypeMap.computeIfAbsent(protocolId, actionId, (a, b) -> new HashSet<>()).add(type);\n\n            return this;\n        }\n\n        /**\n         * Add rule for all principals that is scoped to a protocolId and actionId.\n         *\n         * @param protocolId <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param actionId   <code>sbe:message@id</code> value that the rule applies to.\n         * @param isAllowed  If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addGeneralRule(final int protocolId, final int actionId, final boolean isAllowed)\n        {\n            defaultPrincipal.byProtocolAction.put(protocolId, actionId, isAllowed);\n            return this;\n        }\n\n        /**\n         * Add rule for all principals that is scoped to a protocolId.\n         *\n         * @param protocolId <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param isAllowed  If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addGeneralRule(final int protocolId, final boolean isAllowed)\n        {\n            defaultPrincipal.byProtocol.put(protocolId, (Boolean)isAllowed);\n            return this;\n        }\n\n        /**\n         * Create and instance of the {@link SimpleAuthorisationService}.\n         *\n         * @return new {@link SimpleAuthorisationService}.\n         */\n        public SimpleAuthorisationService newInstance()\n        {\n            return new SimpleAuthorisationService(this);\n        }\n    }\n\n    private static final class Principal\n    {\n        private final Int2ObjectHashMap<Boolean> byProtocol = new Int2ObjectHashMap<>();\n        private final BiInt2ObjectMap<Boolean> byProtocolAction = new BiInt2ObjectMap<>();\n        private final BiInt2ObjectMap<Set<Object>> byProtocolActionTypeAllowed = new BiInt2ObjectMap<>();\n        private final BiInt2ObjectMap<Set<Object>> byProtocolActionTypeDenied = new BiInt2ObjectMap<>();\n        private final byte[] encodedPrincipal;\n\n        private Principal(final byte[] encodedPrincipal)\n        {\n            this.encodedPrincipal = encodedPrincipal;\n        }\n\n        public Boolean isAuthorised(final int protocolId, final int actionId, final Object type)\n        {\n            final Set<Object> typesAllowed = byProtocolActionTypeAllowed.getOrDefault(\n                protocolId, actionId, Collections.emptySet());\n            if (typesAllowed.contains(type))\n            {\n                return Boolean.TRUE;\n            }\n\n            final Set<Object> typesDenied = byProtocolActionTypeDenied.getOrDefault(\n                protocolId, actionId, Collections.emptySet());\n            if (typesDenied.contains(type))\n            {\n                return Boolean.FALSE;\n            }\n\n            final Boolean authorised = byProtocolAction.get(protocolId, actionId);\n            if (null != authorised)\n            {\n                return authorised;\n            }\n\n            return byProtocol.get(protocolId);\n        }\n    }\n\n    private static final class ByteArrayAsKey\n    {\n        private final byte[] data;\n\n        private ByteArrayAsKey(final byte[] data)\n        {\n            this.data = data;\n        }\n\n        public boolean equals(final Object o)\n        {\n            if (this == o)\n            {\n                return true;\n            }\n\n            if (o == null || getClass() != o.getClass())\n            {\n                return false;\n            }\n\n            final ByteArrayAsKey that = (ByteArrayAsKey)o;\n            return Arrays.equals(data, that.data);\n        }\n\n        public int hashCode()\n        {\n            return Arrays.hashCode(data);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/security/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Basic security infrastructure for authenticating sessions by delegating to a supplied instance of an\n * {@link io.aeron.security.Authenticator}.\n * <p>\n * New connection requests will be allocated a unique 64-bit session identity which is first passed to\n * {@link io.aeron.security.Authenticator#onConnectRequest(long, byte[], long)}. Once the response stream to the client\n * is connected then the system will make periodic calls to\n * {@link io.aeron.security.Authenticator#onConnectedSession(io.aeron.security.SessionProxy, long)} until the\n * {@link io.aeron.security.Authenticator} updates the status of the {@link io.aeron.security.SessionProxy} to indicate\n * if the session is authenticated, needs to be challenged, or is rejected.\n */\npackage io.aeron.security;"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/status/ChannelEndpointStatus.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.status;\n\nimport io.aeron.Aeron;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.concurrent.status.CountersReader.MAX_LABEL_LENGTH;\n\n/**\n * Status of the Aeron media channel for a {@link io.aeron.Publication} or {@link io.aeron.Subscription}.\n */\npublic final class ChannelEndpointStatus\n{\n    private ChannelEndpointStatus()\n    {\n    }\n\n    /**\n     * Channel is being initialized.\n     */\n    public static final long INITIALIZING = 0;\n\n    /**\n     * Channel has errored. Check error log for information.\n     */\n    public static final long ERRORED = -1;\n\n    /**\n     * Channel has finished initialization successfully and is active.\n     */\n    public static final long ACTIVE = 1;\n\n    /**\n     * Channel is being closed.\n     */\n    public static final long CLOSING = 2;\n\n    /**\n     * No counter ID is allocated yet.\n     */\n    public static final int NO_ID_ALLOCATED = Aeron.NULL_VALUE;\n\n    /**\n     * Offset in the key metadata for the channel of the counter.\n     */\n    public static final int CHANNEL_OFFSET = 0;\n\n    /**\n     * String representation of the channel status.\n     *\n     * @param status to be converted.\n     * @return representation of the channel status.\n     */\n    public static String status(final long status)\n    {\n        if (INITIALIZING == status)\n        {\n            return \"INITIALIZING\";\n        }\n\n        if (ERRORED == status)\n        {\n            return \"ERRORED\";\n        }\n\n        if (ACTIVE == status)\n        {\n            return \"ACTIVE\";\n        }\n\n        if (CLOSING == status)\n        {\n            return \"CLOSING\";\n        }\n\n        return \"unknown id=\" + status;\n    }\n\n    /**\n     * The maximum length in bytes of the encoded channel identity.\n     */\n    public static final int MAX_CHANNEL_LENGTH = CountersReader.MAX_KEY_LENGTH - (CHANNEL_OFFSET + SIZE_OF_INT);\n\n    /**\n     * Allocate an indicator for tracking the status of a channel endpoint.\n     *\n     * @param tempBuffer      to be used for labels and metadata.\n     * @param name            of the counter for the label.\n     * @param typeId          of the counter for classification.\n     * @param countersManager from which the underlying storage is allocated.\n     * @param registrationId  of the action the counter is associated with.\n     * @param channel         for the stream of messages.\n     * @return a new {@link AtomicCounter} for tracking the status.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final String name,\n        final int typeId,\n        final CountersManager countersManager,\n        final long registrationId,\n        final String channel)\n    {\n        final int channelLength = tempBuffer.putStringWithoutLengthAscii(\n            CHANNEL_OFFSET + SIZE_OF_INT, channel, 0, MAX_CHANNEL_LENGTH);\n        tempBuffer.putInt(CHANNEL_OFFSET, channelLength);\n\n        final int keyLength = channelLength + SIZE_OF_INT;\n\n        int labelLength = 0;\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, name);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, \": \");\n        labelLength += tempBuffer.putStringWithoutLengthAscii(\n            keyLength + labelLength, channel, 0, MAX_LABEL_LENGTH - labelLength);\n\n        if (labelLength < MAX_LABEL_LENGTH)\n        {\n            tempBuffer.putByte(keyLength + labelLength, (byte)' ');\n            labelLength += 1;\n        }\n\n        final AtomicCounter counter = countersManager.newCounter(\n            typeId, tempBuffer, 0, keyLength, tempBuffer, keyLength, labelLength);\n\n        countersManager.setCounterRegistrationId(counter.id(), registrationId);\n\n        return counter;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/status/HeartbeatTimestamp.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.concurrent.status.CountersReader.*;\n\n/**\n * Allocate a counter for tracking the last heartbeat of an entity with a given registration id.\n */\npublic final class HeartbeatTimestamp\n{\n    private HeartbeatTimestamp()\n    {\n    }\n\n    /**\n     * Type id of a heartbeat counter.\n     */\n    public static final int HEARTBEAT_TYPE_ID = AeronCounters.DRIVER_HEARTBEAT_TYPE_ID;\n\n    /**\n     * Offset in the key metadata for the registration id of the counter.\n     */\n    public static final int REGISTRATION_ID_OFFSET = 0;\n\n    /**\n     * Allocate a counter for tracking the last heartbeat of an entity.\n     *\n     * @param tempBuffer      to be used for labels and key.\n     * @param name            of the counter for the label.\n     * @param typeId          of the counter for classification.\n     * @param countersManager from which the underlying storage is allocated.\n     * @param registrationId  to be associated with the counter.\n     * @return a new {@link AtomicCounter} for tracking the last heartbeat.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final String name,\n        final int typeId,\n        final CountersManager countersManager,\n        final long registrationId)\n    {\n        return new AtomicCounter(\n            countersManager.valuesBuffer(),\n            allocateCounterId(tempBuffer, name, typeId, countersManager, registrationId),\n            countersManager);\n    }\n\n    /**\n     * Allocate a counter id for tracking the last heartbeat of an entity.\n     *\n     * @param tempBuffer      to be used for labels and key.\n     * @param name            of the counter for the label.\n     * @param typeId          of the counter for classification.\n     * @param countersManager from which the underlying storage is allocated.\n     * @param registrationId  to be associated with the counter.\n     * @return the counter id to be used.\n     */\n    public static int allocateCounterId(\n        final MutableDirectBuffer tempBuffer,\n        final String name,\n        final int typeId,\n        final CountersManager countersManager,\n        final long registrationId)\n    {\n        tempBuffer.putLong(REGISTRATION_ID_OFFSET, registrationId);\n        final int keyLength = REGISTRATION_ID_OFFSET + SIZE_OF_LONG;\n\n        final int labelOffset = BitUtil.align(keyLength, SIZE_OF_INT);\n        int labelLength = 0;\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, name);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, \": id=\");\n        labelLength += tempBuffer.putLongAscii(labelOffset + labelLength, registrationId);\n\n        return countersManager.allocate(\n            typeId,\n            tempBuffer,\n            0,\n            keyLength,\n            tempBuffer,\n            labelOffset,\n            labelLength);\n    }\n\n    /**\n     * Find the active counter id for a heartbeat timestamp.\n     *\n     * @param countersReader to search within.\n     * @param counterTypeId  to match on.\n     * @param registrationId for the active client.\n     * @return the counter id if found otherwise {@link CountersReader#NULL_COUNTER_ID}.\n     */\n    public static int findCounterIdByRegistrationId(\n        final CountersReader countersReader, final int counterTypeId, final long registrationId)\n    {\n        final DirectBuffer buffer = countersReader.metaDataBuffer();\n\n        for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n        {\n            final int counterState = countersReader.getCounterState(counterId);\n            if (counterState == RECORD_ALLOCATED)\n            {\n                if (countersReader.getCounterTypeId(counterId) == counterTypeId &&\n                    buffer.getLong(metaDataOffset(counterId) + KEY_OFFSET + REGISTRATION_ID_OFFSET) == registrationId)\n                {\n                    return counterId;\n                }\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return NULL_COUNTER_ID;\n    }\n\n    /**\n     * Is the counter active for usage? Checks to see if reclaimed or reused and matches registration id.\n     *\n     * @param countersReader to search within.\n     * @param counterId      to test.\n     * @param counterTypeId  to validate type.\n     * @param registrationId for the entity.\n     * @return true if still valid otherwise false.\n     */\n    public static boolean isActive(\n        final CountersReader countersReader, final int counterId, final int counterTypeId, final long registrationId)\n    {\n        final DirectBuffer buffer = countersReader.metaDataBuffer();\n        final int recordOffset = CountersReader.metaDataOffset(counterId);\n\n        return countersReader.getCounterTypeId(counterId) == counterTypeId &&\n            buffer.getLong(recordOffset + KEY_OFFSET + REGISTRATION_ID_OFFSET) == registrationId &&\n            countersReader.getCounterState(counterId) == RECORD_ALLOCATED;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/status/LocalSocketAddressStatus.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.agrona.concurrent.status.CountersReader.RECORD_ALLOCATED;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_UNUSED;\n\n/**\n * Counter used to store the status of a bind address and port for the local end of a channel.\n * <p>\n * When the value is {@link ChannelEndpointStatus#ACTIVE} then the key value and label will be updated with the\n * socket address and port which is bound.\n */\npublic final class LocalSocketAddressStatus\n{\n    private static final int CHANNEL_STATUS_ID_OFFSET = 0;\n    private static final int LOCAL_SOCKET_ADDRESS_LENGTH_OFFSET = CHANNEL_STATUS_ID_OFFSET + BitUtil.SIZE_OF_INT;\n    private static final int LOCAL_SOCKET_ADDRESS_STRING_OFFSET =\n        LOCAL_SOCKET_ADDRESS_LENGTH_OFFSET + BitUtil.SIZE_OF_INT;\n\n    private static final int MAX_IPV6_LENGTH = \"[ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]:65536\".length();\n\n    /**\n     * Initial length for a key, this will be expanded later when bound.\n     */\n    public static final int INITIAL_LENGTH = BitUtil.SIZE_OF_INT * 2;\n\n    /**\n     * Type of the counter used to track a local socket address and port.\n     */\n    public static final int LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID =\n        AeronCounters.DRIVER_LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID;\n\n    private LocalSocketAddressStatus()\n    {\n    }\n\n    /**\n     * Allocate a counter to represent a local socket address associated with a channel.\n     *\n     * @param tempBuffer      for building up the key and label.\n     * @param countersManager which will allocate the counter.\n     * @param registrationId  of the action the counter is associated with.\n     * @param channelStatusId with which the new counter is associated.\n     * @param name            for the counter to put in the label.\n     * @param typeId          to categorise the counter.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long registrationId,\n        final int channelStatusId,\n        final String name,\n        final int typeId)\n    {\n        tempBuffer.putInt(CHANNEL_STATUS_ID_OFFSET, channelStatusId);\n        tempBuffer.putInt(LOCAL_SOCKET_ADDRESS_LENGTH_OFFSET, 0);\n\n        final int keyLength = INITIAL_LENGTH;\n\n        int labelLength = 0;\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, name);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, \": \");\n        labelLength += tempBuffer.putIntAscii(keyLength + labelLength, channelStatusId);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, \" \");\n\n        final AtomicCounter counter = countersManager.newCounter(\n            typeId, tempBuffer, 0, keyLength, tempBuffer, keyLength, labelLength);\n\n        countersManager.setCounterRegistrationId(counter.id(), registrationId);\n\n        return counter;\n    }\n\n    /**\n     * Update the key metadata and label to contain the bound socket address once the transport is active.\n     *\n     * @param counter                representing the local socket address of the transport.\n     * @param bindAddressAndPort     in string representation.\n     * @param countersMetadataBuffer to be updated for the bound address.\n     */\n    public static void updateBindAddress(\n        final AtomicCounter counter, final String bindAddressAndPort, final UnsafeBuffer countersMetadataBuffer)\n    {\n        if (bindAddressAndPort.length() > MAX_IPV6_LENGTH)\n        {\n            throw new IllegalArgumentException(\n                \"bindAddressAndPort value too long: \" + bindAddressAndPort.length() + \" max: \" + MAX_IPV6_LENGTH);\n        }\n\n        final int keyIndex = CountersReader.metaDataOffset(counter.id()) + CountersReader.KEY_OFFSET;\n        final int addressStringIndex = keyIndex + LOCAL_SOCKET_ADDRESS_STRING_OFFSET;\n        final int length = countersMetadataBuffer.putStringWithoutLengthAscii(addressStringIndex, bindAddressAndPort);\n        final int addressLengthIndex = keyIndex + LOCAL_SOCKET_ADDRESS_LENGTH_OFFSET;\n        countersMetadataBuffer.putInt(addressLengthIndex, length);\n\n        counter.appendToLabel(bindAddressAndPort);\n    }\n\n    /**\n     * Find the list of currently bound local sockets.\n     *\n     * @param countersReader  for the connected driver.\n     * @param channelStatus   value for the channel which aggregates the transports.\n     * @param channelStatusId identity of the counter for the channel which aggregates the transports.\n     * @return the list of active bound local socket addresses.\n     */\n    public static List<String> findAddresses(\n        final CountersReader countersReader, final long channelStatus, final int channelStatusId)\n    {\n        if (channelStatus != ChannelEndpointStatus.ACTIVE)\n        {\n            return Collections.emptyList();\n        }\n\n        final ArrayList<String> bindings = new ArrayList<>(2);\n        final DirectBuffer buffer = countersReader.metaDataBuffer();\n\n        for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n        {\n            final int counterState = countersReader.getCounterState(counterId);\n            if (RECORD_ALLOCATED == counterState)\n            {\n                if (countersReader.getCounterTypeId(counterId) == LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID)\n                {\n                    final int recordOffset = CountersReader.metaDataOffset(counterId);\n                    final int keyIndex = recordOffset + CountersReader.KEY_OFFSET;\n\n                    if (channelStatusId == buffer.getInt(keyIndex + CHANNEL_STATUS_ID_OFFSET) &&\n                        ChannelEndpointStatus.ACTIVE == countersReader.getCounterValue(counterId))\n                    {\n                        final int length = buffer.getInt(keyIndex + LOCAL_SOCKET_ADDRESS_LENGTH_OFFSET);\n                        if (length > 0)\n                        {\n                            bindings.add(buffer.getStringWithoutLengthAscii(\n                                keyIndex + LOCAL_SOCKET_ADDRESS_STRING_OFFSET, length));\n                        }\n                    }\n                }\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return bindings;\n    }\n\n    /**\n     * Find the currently bound socket address for the channel. There is an expectation that only one exists when\n     * searching.\n     *\n     * @param countersReader  for the connected driver.\n     * @param channelStatus   value for the channel which aggregates the transports.\n     * @param channelStatusId identity of the counter for the channel which aggregates the transports.\n     * @return the endpoint representing the bound socket address or null if not found.\n     */\n    public static String findAddress(\n        final CountersReader countersReader, final long channelStatus, final int channelStatusId)\n    {\n        String endpoint = null;\n\n        if (channelStatus == ChannelEndpointStatus.ACTIVE)\n        {\n            final DirectBuffer buffer = countersReader.metaDataBuffer();\n\n            for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n            {\n                final int counterState = countersReader.getCounterState(counterId);\n                if (RECORD_ALLOCATED == counterState)\n                {\n                    if (countersReader.getCounterTypeId(counterId) == LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID)\n                    {\n                        final int recordOffset = CountersReader.metaDataOffset(counterId);\n                        final int keyIndex = recordOffset + CountersReader.KEY_OFFSET;\n\n                        if (channelStatusId == buffer.getInt(keyIndex + CHANNEL_STATUS_ID_OFFSET) &&\n                            ChannelEndpointStatus.ACTIVE == countersReader.getCounterValue(counterId))\n                        {\n                            final int length = buffer.getInt(keyIndex + LOCAL_SOCKET_ADDRESS_LENGTH_OFFSET);\n                            if (length > 0)\n                            {\n                                endpoint = buffer.getStringWithoutLengthAscii(\n                                    keyIndex + LOCAL_SOCKET_ADDRESS_STRING_OFFSET, length);\n                            }\n\n                            break;\n                        }\n                    }\n                }\n                else if (RECORD_UNUSED == counterState)\n                {\n                    break;\n                }\n            }\n        }\n\n        return endpoint;\n    }\n\n    /**\n     * Return number of local addresses for the given subscription registration id.\n     *\n     * @param countersReader for the connected driver.\n     * @param registrationId for the subscription.\n     * @return number of local socket addresses in use.\n     */\n    public static int findNumberOfAddressesByRegistrationId(\n        final CountersReader countersReader, final long registrationId)\n    {\n        int result = 0;\n\n        for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n        {\n            final int counterState = countersReader.getCounterState(counterId);\n            if (counterState == RECORD_ALLOCATED &&\n                countersReader.getCounterTypeId(counterId) == LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID &&\n                countersReader.getCounterRegistrationId(counterId) == registrationId)\n            {\n                result++;\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * Is a socket currently active for a channel.\n     *\n     * @param countersReader  for the connected driver.\n     * @param channelStatusId identity of the counter for the channel.\n     * @return true if the counter is active otherwise false.\n     */\n    public static boolean isActive(final CountersReader countersReader, final int channelStatusId)\n    {\n        final DirectBuffer buffer = countersReader.metaDataBuffer();\n\n        for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n        {\n            final int counterState = countersReader.getCounterState(counterId);\n            if (RECORD_ALLOCATED == counterState)\n            {\n                if (countersReader.getCounterTypeId(counterId) == LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID)\n                {\n                    final int recordOffset = CountersReader.metaDataOffset(counterId);\n                    final int keyIndex = recordOffset + CountersReader.KEY_OFFSET;\n\n                    if (channelStatusId == buffer.getInt(keyIndex + CHANNEL_STATUS_ID_OFFSET) &&\n                        ChannelEndpointStatus.ACTIVE == countersReader.getCounterValue(counterId))\n                    {\n                        return true;\n                    }\n                }\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/status/PublicationErrorFrame.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.status;\n\nimport io.aeron.command.PublicationErrorFrameFlyweight;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Encapsulates the data received when a publication receives an error frame.\n */\npublic class PublicationErrorFrame implements Cloneable\n{\n    private long registrationId;\n    private int sessionId;\n    private int streamId;\n    private long receiverId;\n    private long destinationRegistrationId;\n    private long groupTag;\n    private int errorCode;\n    private String errorMessage;\n    private InetSocketAddress sourceAddress;\n\n    /**\n     * Create an error frame flyweight that can be reused across calls.\n     */\n    public PublicationErrorFrame()\n    {\n    }\n\n    /**\n     * Registration id of the publication that received the error frame.\n     *\n     * @return registration id of the publication.\n     */\n    public long registrationId()\n    {\n        return registrationId;\n    }\n\n    /**\n     * Session id of the publication that received the error frame.\n     *\n     * @return session id of the publication.\n     */\n    public int sessionId()\n    {\n        return sessionId;\n    }\n\n    /**\n     * Stream id of the publication that received the error frame.\n     *\n     * @return stream id of the publication.\n     */\n    public int streamId()\n    {\n        return streamId;\n    }\n\n    /**\n     * Receiver id of the source that send the error frame.\n     *\n     * @return receiver id of the source that send the error frame.\n     */\n    public long receiverId()\n    {\n        return receiverId;\n    }\n\n    /**\n     * Group tag of the source that sent the error frame.\n     *\n     * @return group tag of the source that sent the error frame or {@link io.aeron.Aeron#NULL_VALUE} if the source did not have a group\n     * tag set.\n     */\n    public long groupTag()\n    {\n        return groupTag;\n    }\n\n    /**\n     * The error code of the error frame received.\n     *\n     * @return the error code.\n     */\n    public int errorCode()\n    {\n        return errorCode;\n    }\n\n    /**\n     * The error message of the error frame received.\n     *\n     * @return the error message.\n     */\n    public String errorMessage()\n    {\n        return errorMessage;\n    }\n\n    /**\n     * The address of the remote source that sent the error frame.\n     *\n     * @return address of the remote source.\n     */\n    public InetSocketAddress sourceAddress()\n    {\n        return sourceAddress;\n    }\n\n    /**\n     * The registrationId of the destination. Only used with manual MDC publications. Will be\n     * {@link io.aeron.Aeron#NULL_VALUE} otherwise.\n     *\n     * @return registrationId of the destination or {@link io.aeron.Aeron#NULL_VALUE}.\n     */\n    public long destinationRegistrationId()\n    {\n        return destinationRegistrationId;\n    }\n\n    /**\n     * Set the fields of the publication error frame from the flyweight.\n     *\n     * @param frameFlyweight that was received from the client message buffer.\n     * @return this for fluent API.\n     */\n    public PublicationErrorFrame set(final PublicationErrorFrameFlyweight frameFlyweight)\n    {\n        registrationId = frameFlyweight.registrationId();\n        sessionId = frameFlyweight.sessionId();\n        streamId = frameFlyweight.streamId();\n        receiverId = frameFlyweight.receiverId();\n        groupTag = frameFlyweight.groupTag();\n        sourceAddress = frameFlyweight.sourceAddress();\n        errorCode = frameFlyweight.errorCode().value();\n        errorMessage = frameFlyweight.errorMessage();\n        destinationRegistrationId = frameFlyweight.destinationRegistrationId();\n\n        return this;\n    }\n\n    /**\n     * Return a copy of this message. Useful if a callback is reusing an instance of this class to avoid unnecessary\n     * allocation.\n     *\n     * @return a copy of this instance's data.\n     */\n    public PublicationErrorFrame clone()\n    {\n        try\n        {\n            return (PublicationErrorFrame)super.clone();\n        }\n        catch (final CloneNotSupportedException ex)\n        {\n            throw new RuntimeException(ex);\n        }\n    }\n\n    /**\n     * Build a String representation of the error frame.\n     *\n     * @return a String representation of the error frame.\n     */\n    public String toString()\n    {\n        return \"PublicationErrorFrame{\" +\n            \"registrationId=\" + registrationId() +\n            \", sessionId=\" + sessionId() +\n            \", streamId=\" + streamId() +\n            \", receiverId=\" + receiverId() +\n            \", destinationRegistrationId=\" + destinationRegistrationId() +\n            \", groupTag=\" + groupTag() +\n            \", errorCode=\" + errorCode() +\n            \", errorMessage=\" + errorMessage() +\n            \", sourceAddress=\" + sourceAddress() +\n            \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/status/ReadableCounter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.status;\n\nimport io.aeron.Aeron;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Readonly view of an associated {@link io.aeron.Counter}.\n * <p>\n * <b>Note:</b>The user should call {@link #isClosed()} and ensure the result is false to avoid a race on reading a\n * closed {@link io.aeron.Counter}.\n */\npublic final class ReadableCounter implements AutoCloseable\n{\n    private final CountersReader countersReader;\n    private final UnsafeBuffer valueBuffer;\n    private final long registrationId;\n    private final int counterId;\n    private volatile boolean isClosed = false;\n\n    /**\n     * Construct a view of an existing counter.\n     *\n     * @param countersReader for getting access to the buffers.\n     * @param registrationId assigned by the driver for the counter or {@link Aeron#NULL_VALUE} if not known.\n     * @param counterId      for the counter to be viewed.\n     * @throws IllegalStateException if the id has for the counter has not been allocated.\n     */\n    public ReadableCounter(final CountersReader countersReader, final long registrationId, final int counterId)\n    {\n        final int counterState = countersReader.getCounterState(counterId);\n        if (counterState != CountersReader.RECORD_ALLOCATED)\n        {\n            throw new IllegalStateException(\"Counter not allocated: id=\" + counterId + \" state=\" + counterState);\n        }\n\n        this.countersReader = countersReader;\n        this.counterId = counterId;\n        this.registrationId = registrationId;\n\n        final AtomicBuffer valuesBuffer = countersReader.valuesBuffer();\n        final int counterOffset = CountersReader.counterOffset(counterId);\n        valuesBuffer.boundsCheck(counterOffset, SIZE_OF_LONG);\n\n        valueBuffer = new UnsafeBuffer(valuesBuffer, counterOffset, SIZE_OF_LONG);\n    }\n\n    /**\n     * Construct a view of an existing counter.\n     *\n     * @param countersReader for getting access to the buffers.\n     * @param counterId      for the counter to be viewed.\n     * @throws IllegalStateException if the id has for the counter has not been allocated.\n     */\n    public ReadableCounter(final CountersReader countersReader, final int counterId)\n    {\n        this(countersReader, Aeron.NULL_VALUE, counterId);\n    }\n\n    /**\n     * Return the registration id for the counter.\n     *\n     * @return registration id.\n     */\n    public long registrationId()\n    {\n        return registrationId;\n    }\n\n    /**\n     * Return the counter id.\n     *\n     * @return counter id.\n     */\n    public int counterId()\n    {\n        return counterId;\n    }\n\n    /**\n     * Return the state of the counter.\n     *\n     * @return state for the counter.\n     * @see CountersReader#RECORD_ALLOCATED\n     * @see CountersReader#RECORD_RECLAIMED\n     * @see CountersReader#RECORD_UNUSED\n     */\n    public int state()\n    {\n        return countersReader.getCounterState(counterId);\n    }\n\n    /**\n     * Return the counter label.\n     *\n     * @return the counter label.\n     */\n    public String label()\n    {\n        return countersReader.getCounterLabel(counterId);\n    }\n\n    /**\n     * Get the latest value for the counter with volatile semantics.\n     * <p>\n     * <b>Note:</b>The user should call {@link #isClosed()} and ensure the result is false to avoid a race on reading\n     * a closed counter.\n     *\n     * @return the latest value for the counter.\n     */\n    public long get()\n    {\n        return valueBuffer.getLongVolatile(0);\n    }\n\n    /**\n     * Get the value of the counter using weak ordering semantics. This is the same a standard read of a field.\n     * <p>\n     * This call is identical to {@link #getPlain()} and that method is preferred.\n     *\n     * @return the  value for the counter.\n     */\n    public long getWeak()\n    {\n        return getPlain();\n    }\n\n    /**\n     * Get the value of the counter using plain memory semantics. This is the same a standard read of a field.\n     *\n     * @return the value for the counter.\n     */\n    public long getPlain()\n    {\n        return valueBuffer.getLong(0);\n    }\n\n    /**\n     * Close this counter. This has no impact on the {@link io.aeron.Counter} it is viewing.\n     */\n    public void close()\n    {\n        isClosed = true;\n    }\n\n    /**\n     * Has this counter been closed and should it no longer be used?\n     *\n     * @return true if it has been closed otherwise false.\n     */\n    public boolean isClosed()\n    {\n        return isClosed;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/main/java/io/aeron/status/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Counters for tracking status relevant to client activity.\n */\npackage io.aeron.status;"
  },
  {
    "path": "aeron-client/src/test/c/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nif (MSVC AND \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\")\n    set(AERON_LIB_WINSOCK_LIBS wsock32 ws2_32 Iphlpapi)\nendif ()\n\ninclude_directories(${AERON_C_CLIENT_SOURCE_PATH})\n\nset(TEST_HEADERS\n    aeron_client_test_utils.h)\n\nfunction(aeron_c_client_test name file)\n        add_executable(${name} ${file} ${TEST_HEADERS})\n        add_dependencies(${name} gmock)\n        target_link_libraries(${name} aeron gmock_main ${CMAKE_THREAD_LIBS_INIT} ${AERON_LIB_WINSOCK_LIBS})\n        target_compile_definitions(${name} PUBLIC \"_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING\")\n        add_test(NAME ${name} COMMAND ${name})\nendfunction()\n\nif (AERON_UNIT_TESTS)\n    aeron_c_client_test(alloc_test aeron_alloc_test.cpp)\n    aeron_c_client_test(array_to_ptr_hash_map_test collections/aeron_array_to_ptr_hash_map_test.cpp)\n    aeron_c_client_test(int64_to_ptr_hash_map_test collections/aeron_int64_to_ptr_hash_map_test.cpp)\n    aeron_c_client_test(int64_counter_map_test collections/aeron_int64_counter_map_test.cpp)\n    aeron_c_client_test(int64_to_tagged_ptr_hash_map_test collections/aeron_int64_to_tagged_ptr_hash_map_test.cpp)\n    aeron_c_client_test(str_to_ptr_hash_map_test collections/aeron_str_to_ptr_hash_map_test.cpp)\n    aeron_c_client_test(bit_set_test collections/aeron_bit_set_test.cpp)\n    aeron_c_client_test(atomic_test concurrent/aeron_atomic_test.cpp)\n    aeron_c_client_test(aeron_thread_test concurrent/aeron_thread_test.cpp)\n    aeron_c_client_test(spsc_rb_test concurrent/aeron_spsc_rb_test.cpp)\n    aeron_c_client_test(mpsc_rb_test concurrent/aeron_mpsc_rb_test.cpp)\n    aeron_c_client_test(broadcast_receiver_test concurrent/aeron_broadcast_receiver_test.cpp)\n    aeron_c_client_test(broadcast_transmitter_test concurrent/aeron_broadcast_transmitter_test.cpp)\n\n    aeron_c_client_test(distinct_error_log_test concurrent/aeron_distinct_error_log_test.cpp)\n    set_tests_properties(distinct_error_log_test PROPERTIES TIMEOUT 30)\n    set_tests_properties(distinct_error_log_test PROPERTIES RUN_SERIAL TRUE)\n\n    aeron_c_client_test(spsc_concurrent_array_queue_test concurrent/aeron_spsc_concurrent_array_queue_test.cpp)\n    aeron_c_client_test(mpsc_concurrent_array_queue_test concurrent/aeron_mpsc_concurrent_array_queue_test.cpp)\n    aeron_c_client_test(linked_queue_test collections/aeron_linked_queue_test.cpp)\n    aeron_c_client_test(blocking_linked_queue_test concurrent/aeron_blocking_linked_queue_test.cpp)\n    aeron_c_client_test(executor_test concurrent/aeron_executor_test.cpp)\n    aeron_c_client_test(counters_test concurrent/aeron_counters_test.cpp)\n    aeron_c_client_test(client_conductor_test aeron_client_conductor_test.cpp)\n    aeron_c_client_test(publication_test aeron_publication_test.cpp)\n    aeron_c_client_test(exclusive_publication_test aeron_exclusive_publication_test.cpp)\n    aeron_c_client_test(subscription_test aeron_subscription_test.cpp)\n    aeron_c_client_test(image_test aeron_image_test.cpp)\n    aeron_c_client_test(fragment_assembler_test aeron_fragment_assembler_test.cpp)\n    aeron_c_client_test(image_fragment_assembler_test aeron_image_fragment_assembler_test.cpp)\n    aeron_c_client_test(controlled_fragment_assembler_test aeron_controlled_fragment_assembler_test.cpp)\n    aeron_c_client_test(controlled_image_fragment_assembler_test aeron_controlled_image_fragment_assembler_test.cpp)\n    aeron_c_client_test(aeron_fileutil_test util/aeron_fileutil_test.cpp)\n    aeron_c_client_test(aeron_uri_test aeron_uri_test.cpp)\n    aeron_c_client_test(version_test aeron_version_test.cpp)\n    aeron_c_client_test(bitutil_test util/aeron_bitutil_test.cpp)\n    aeron_c_client_test(deque_test util/aeron_deque_test.cpp)\n    aeron_c_client_test(fileutil_test util/aeron_fileutil_test.cpp)\n    aeron_c_client_test(math_test util/aeron_math_test.cpp)\n    aeron_c_client_test(strutil_test util/aeron_strutil_test.cpp)\n    aeron_c_client_test(error_test util/aeron_error_test.cpp)\n    aeron_c_client_test(netutil_test util/aeron_netutil_test.cpp)\n    aeron_c_client_test(symbol_table_test util/aeron_symbol_table_test.cpp)\n    aeron_c_client_test(http_util_test util/aeron_httputil_test.cpp)\nendif ()\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_alloc_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n}\n\nclass AllocTest : public testing::Test\n{\n};\n\nTEST_F(AllocTest, shouldAllocateAlignedMemory)\n{\n    char *ptr = nullptr;\n    size_t offset;\n    size_t size = 100;\n    size_t alignment = 64;\n\n    EXPECT_EQ(0, aeron_alloc_aligned(reinterpret_cast<void**>(&ptr), &offset, size, alignment));\n    EXPECT_NE(nullptr, ptr);\n    size_t actual_allocated_size;\n#if defined(AERON_COMPILER_MSVC)\n    actual_allocated_size = size + alignment;\n    EXPECT_NE(0, offset);\n#else\n    actual_allocated_size = size;\n    EXPECT_EQ(0, offset);\n#endif\n\n    EXPECT_EQ(0, ((uintptr_t)ptr + offset) % alignment);\n    for (size_t i = 0; i < actual_allocated_size; i++)\n    {\n        EXPECT_EQ(0, ptr[i]);\n    }\n\n    aeron_free(ptr);\n}\n\nTEST_F(AllocTest, shouldAllocateAndZeroOutMemory)\n{\n    char *ptr = nullptr;\n    size_t size = 100;\n\n    EXPECT_EQ(0, aeron_alloc(reinterpret_cast<void**>(&ptr), size));\n    EXPECT_NE(nullptr, ptr);\n    for (size_t i = 0; i < size; i++)\n    {\n        EXPECT_EQ(0, ptr[i]);\n    }\n\n    aeron_free(ptr);\n}\n\nTEST_F(AllocTest, shouldFailIfAlignmentIsNotAPowerOfTwo)\n{\n    char *ptr = nullptr;\n    size_t offset;\n    size_t size = 100;\n    size_t alignment = 13;\n\n    EXPECT_EQ(-1, aeron_alloc_aligned(reinterpret_cast<void**>(&ptr), &offset, size, alignment));\n    EXPECT_EQ(nullptr, ptr);\n    EXPECT_EQ(EINVAL, aeron_errcode());\n    aeron_err_clear();\n}\n\nTEST_F(AllocTest, shouldAllocateMemoryIfPointerIsNullWithoutZeroingItOut)\n{\n    char *ptr = nullptr;\n    size_t size = 32;\n\n    EXPECT_EQ(0, aeron_reallocf(reinterpret_cast<void**>(&ptr), size));\n    EXPECT_NE(nullptr, ptr);\n\n    aeron_free(ptr);\n}\n\nTEST_F(AllocTest, shouldReallocMemory)\n{\n    char *ptr = nullptr;\n    size_t original_size = 50;\n    EXPECT_EQ(0, aeron_alloc(reinterpret_cast<void**>(&ptr), original_size));\n    EXPECT_NE(nullptr, ptr);\n    memset(ptr, 'a', original_size);\n\n    size_t new_size = 120;\n    EXPECT_EQ(0, aeron_reallocf(reinterpret_cast<void**>(&ptr), new_size));\n    EXPECT_NE(nullptr, ptr);\n    memset(ptr + original_size, 'x', new_size - original_size);\n\n    char tmp[120];\n    memset(tmp, 'a', original_size);\n    memset(tmp + original_size, 'x', new_size - original_size);\n    EXPECT_EQ(0, memcmp(ptr, tmp, new_size));\n\n    new_size = 30;\n    EXPECT_EQ(0, aeron_reallocf(reinterpret_cast<void**>(&ptr), new_size));\n    EXPECT_NE(nullptr, ptr);\n    EXPECT_EQ(0, memcmp(ptr, tmp, new_size));\n\n    aeron_free(ptr);\n}\n\nTEST_F(AllocTest, shouldFreeMemoryIfSizeIsZero)\n{\n    char *ptr = nullptr;\n    size_t original_size = 50;\n    EXPECT_EQ(0, aeron_alloc(reinterpret_cast<void**>(&ptr), original_size));\n    EXPECT_NE(nullptr, ptr);\n\n    EXPECT_EQ(0, aeron_reallocf(reinterpret_cast<void**>(&ptr), 0));\n    EXPECT_EQ(nullptr, ptr);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_client_conductor_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdint>\n#include <thread>\n#include <functional>\n\n#include <gtest/gtest.h>\n\n#include \"aeron_client_test_utils.h\"\n\nextern \"C\"\n{\n#include \"aeron_publication.h\"\n#include \"aeron_exclusive_publication.h\"\n#include \"aeron_subscription.h\"\n#include \"aeron_context.h\"\n#include \"aeron_cnc_file_descriptor.h\"\n#include \"concurrent/aeron_mpsc_rb.h\"\n#include \"concurrent/aeron_broadcast_transmitter.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"aeron_client_conductor.h\"\n#include \"aeron_counter.h\"\n#include \"aeron_counters.h\"\n#include \"aeron_client.h\"\n}\n\n#define CAPACITY (16 * 1024)\n#define MAX_MESSAGE_SIZE (CAPACITY / 8 - AERON_RB_RECORD_HEADER_LENGTH)\n#define TO_DRIVER_RING_BUFFER_LENGTH (CAPACITY + AERON_RB_TRAILER_LENGTH)\n#define TO_CLIENTS_BUFFER_LENGTH (CAPACITY + AERON_BROADCAST_BUFFER_TRAILER_LENGTH)\n#define COUNTER_VALUES_BUFFER_LENGTH (1024 * 1024)\n#define COUNTER_METADATA_BUFFER_LENGTH (AERON_COUNTERS_METADATA_BUFFER_LENGTH(COUNTER_VALUES_BUFFER_LENGTH))\n#define ERROR_BUFFER_LENGTH (CAPACITY)\n#define FILE_PAGE_SIZE (4 * 1024)\n\n#define CLIENT_IDLE_SLEEP_INTERVAL (16 * 1000 * 1000LL)\n#define CLIENT_LIVENESS_TIMEOUT (5 * 1000 * 1000 * 1000LL)\n#define DRIVER_TIMEOUT_INTERVAL_MS (1 * 1000)\n#define DRIVER_TIMEOUT_INTERVAL_NS (DRIVER_TIMEOUT_INTERVAL_MS * 1000 * 1000LL)\n\n#define TIME_ADVANCE_INTERVAL_NS (1000 * 1000LL)\n\n#define URI_RESERVED \"aeron:udp?endpoint=localhost:24567\"\n#define DEST_URI \"aeron:udp?endpoint=localhost:24568\"\n#define SUB_URI \"aeron:udp?endpoint=localhost:24567\"\n#define STREAM_ID (101)\n#define SESSION_ID (110)\n#define COUNTER_TYPE_ID (102)\n\nstatic int64_t now_ms = 0;\nstatic int64_t now_ns = 0;\n\nstatic int64_t test_epoch_clock()\n{\n    return now_ms;\n}\n\nstatic int64_t test_nano_clock()\n{\n    return now_ns;\n}\n\nstatic void save_last_errorcode(void *clientd, int errcode, const char *message)\n{\n    int *last_errorcode = static_cast<int *>(clientd);\n    *last_errorcode = errcode;\n}\n\nusing namespace aeron::test;\n\nclass ClientConductorTest : public testing::Test\n{\npublic:\n\n    static void on_new_publication(\n        void *clientd,\n        aeron_async_add_publication_t *async,\n        const char *channel,\n        int32_t stream_id,\n        int32_t session_id,\n        int64_t correlation_id)\n    {\n        auto conductorTest = reinterpret_cast<ClientConductorTest *>(clientd);\n\n        if (conductorTest->m_on_new_publication)\n        {\n            conductorTest->m_on_new_publication(async, channel, stream_id, session_id, correlation_id);\n        }\n    }\n\n    static void on_new_exclusive_publication(\n        void *clientd,\n        aeron_async_add_exclusive_publication_t *async,\n        const char *channel,\n        int32_t stream_id,\n        int32_t session_id,\n        int64_t correlation_id)\n    {\n        auto conductorTest = reinterpret_cast<ClientConductorTest *>(clientd);\n\n        if (conductorTest->m_on_new_exclusive_publication)\n        {\n            conductorTest->m_on_new_exclusive_publication(async, channel, stream_id, session_id, correlation_id);\n        }\n    }\n\n    static void on_new_subscription(\n        void *clientd,\n        aeron_async_add_subscription_t *async,\n        const char *channel,\n        int32_t stream_id,\n        int64_t correlation_id)\n    {\n        auto conductorTest = reinterpret_cast<ClientConductorTest *>(clientd);\n\n        if (conductorTest->m_on_new_subscription)\n        {\n            conductorTest->m_on_new_subscription(async, channel, stream_id, correlation_id);\n        }\n    }\n\n    ClientConductorTest() :\n        m_logFileName(tempFileName()),\n        m_on_new_publication(nullptr),\n        m_on_new_exclusive_publication(nullptr)\n    {\n        now_ns = 0;\n        now_ms = 0;\n\n        if (aeron_context_init(&m_context) < 0)\n        {\n            throw std::runtime_error(\"could not init context: \" + std::string(aeron_errmsg()));\n        }\n\n        m_context->cnc_map.length = aeron_cnc_computed_length(\n            TO_DRIVER_RING_BUFFER_LENGTH +\n            TO_CLIENTS_BUFFER_LENGTH +\n            COUNTER_VALUES_BUFFER_LENGTH +\n            COUNTER_METADATA_BUFFER_LENGTH +\n            ERROR_BUFFER_LENGTH,\n            FILE_PAGE_SIZE);\n        m_cnc = std::unique_ptr<uint8_t[]>(new uint8_t[m_context->cnc_map.length]);\n        m_context->cnc_map.addr = m_cnc.get();\n        memset(m_context->cnc_map.addr, 0, m_context->cnc_map.length);\n\n        m_context->epoch_clock = test_epoch_clock;\n        m_context->nano_clock = test_nano_clock;\n        m_context->driver_timeout_ms = DRIVER_TIMEOUT_INTERVAL_MS;\n        m_context->keepalive_interval_ns = DRIVER_TIMEOUT_INTERVAL_NS;\n        m_context->idle_sleep_duration_ns = CLIENT_IDLE_SLEEP_INTERVAL;\n\n        aeron_context_set_use_conductor_agent_invoker(m_context, true);\n\n        aeron_context_set_on_new_publication(m_context, on_new_publication, this);\n        aeron_context_set_on_new_exclusive_publication(m_context, on_new_exclusive_publication, this);\n        aeron_context_set_on_new_subscription(m_context, on_new_subscription, this);\n\n        auto *metadata = (aeron_cnc_metadata_t *)m_context->cnc_map.addr;\n        metadata->to_driver_buffer_length = (int32_t)TO_DRIVER_RING_BUFFER_LENGTH;\n        metadata->to_clients_buffer_length = (int32_t)TO_CLIENTS_BUFFER_LENGTH;\n        metadata->counter_metadata_buffer_length = (int32_t)COUNTER_METADATA_BUFFER_LENGTH;\n        metadata->counter_values_buffer_length = (int32_t)COUNTER_VALUES_BUFFER_LENGTH;\n        metadata->error_log_buffer_length = (int32_t)ERROR_BUFFER_LENGTH;\n        metadata->client_liveness_timeout = (int64_t)CLIENT_LIVENESS_TIMEOUT;\n        metadata->start_timestamp = test_epoch_clock();\n        metadata->pid = 101;\n        AERON_SET_RELEASE(metadata->cnc_version, AERON_CNC_VERSION);\n\n        if (aeron_mpsc_rb_init(\n            &m_to_driver, aeron_cnc_to_driver_buffer(metadata), TO_DRIVER_RING_BUFFER_LENGTH) < 0)\n        {\n            throw std::runtime_error(\"could not init to_driver: \" + std::string(aeron_errmsg()));\n        }\n\n        if (aeron_broadcast_transmitter_init(\n            &m_to_clients, aeron_cnc_to_clients_buffer(metadata), TO_CLIENTS_BUFFER_LENGTH) < 0)\n        {\n            throw std::runtime_error(\"could not init to_clients: \" + std::string(aeron_errmsg()));\n        }\n\n        if (aeron_counters_manager_init(\n            &m_counters_manager,\n            aeron_cnc_counters_metadata_buffer(metadata),\n            (size_t)metadata->counter_metadata_buffer_length,\n            aeron_cnc_counters_values_buffer(metadata),\n            (size_t)metadata->counter_values_buffer_length,\n            &m_cached_clock,\n            1000) < 0)\n        {\n            throw std::runtime_error(\"could not init counters manager: \" + std::string(aeron_errmsg()));\n        }\n\n        if (aeron_client_conductor_init(&m_conductor, m_context) < 0)\n        {\n            throw std::runtime_error(\"could not init conductor: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    ~ClientConductorTest() override\n    {\n        aeron_client_conductor_on_close(&m_conductor);\n        aeron_counters_manager_close(&m_counters_manager);\n        m_context->cnc_map.addr = nullptr;\n        aeron_context_close(m_context);\n\n        ::unlink(m_logFileName.c_str());\n    }\n\n    static void ToDriverHandler(int32_t type_id, const void *buffer, size_t length, void *clientd)\n    {\n        auto conductorTest = reinterpret_cast<ClientConductorTest *>(clientd);\n\n        conductorTest->m_to_driver_handler(type_id, buffer, length);\n    }\n\n     static std::string allocateStringWithPrefix(\n         const std::string& prefix, const std::string suffix, const char c, const size_t length)\n    {\n        std::string result = std::string(prefix).append(suffix);\n        if (length > result.length())\n        {\n            result.append(length - result.length(), c);\n        }\n\n        return result;\n    }\n\n    int doWork(bool updateDriverHeartbeat = true)\n    {\n        int work_count;\n\n        if (updateDriverHeartbeat)\n        {\n            aeron_mpsc_rb_consumer_heartbeat_time(&m_to_driver, test_epoch_clock());\n        }\n\n        if ((work_count = aeron_client_conductor_do_work(&m_conductor)) < 0)\n        {\n            throw std::runtime_error(\"error from do_work: \" + std::string(aeron_errmsg()));\n        }\n\n        return work_count;\n    }\n\n    int doWorkForNs(\n        int64_t interval_ns, bool updateDriverHeartbeat = true, int64_t advance_interval_ns = TIME_ADVANCE_INTERVAL_NS)\n    {\n        int work_count = 0;\n        int64_t target_ns = now_ns + interval_ns;\n\n        do\n        {\n            now_ns += advance_interval_ns;\n            now_ms = now_ns / 1000000LL;\n            work_count += doWork(updateDriverHeartbeat);\n        }\n        while (now_ns < target_ns);\n\n        return work_count;\n    }\n\n    void transmitOnPublicationReady(aeron_async_add_publication_t *async, const std::string &logFile, bool isExclusive)\n    {\n        char response_buffer[sizeof(aeron_publication_buffers_ready_t) + AERON_ERROR_MAX_TOTAL_LENGTH];\n        auto response = reinterpret_cast<aeron_publication_buffers_ready_t *>(response_buffer);\n        int32_t position_limit_counter_id = 10, channel_status_indicator_id = 11;\n\n        response->correlation_id = async->registration_id;\n        response->registration_id = async->registration_id;\n        response->stream_id = async->stream_id;\n        response->session_id = SESSION_ID;\n        response->position_limit_counter_id = position_limit_counter_id;\n        response->channel_status_indicator_id = channel_status_indicator_id;\n        response->log_file_length = static_cast<int32_t>(logFile.length());\n        memcpy(response_buffer + sizeof(aeron_publication_buffers_ready_t), logFile.c_str(), logFile.length());\n\n        if (aeron_broadcast_transmitter_transmit(\n            &m_to_clients,\n            isExclusive ? AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY : AERON_RESPONSE_ON_PUBLICATION_READY,\n            response_buffer,\n            sizeof(aeron_publication_buffers_ready_t) + logFile.length()) < 0)\n        {\n            throw std::runtime_error(\"error transmitting ON_PUBLICATION_READY: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    void transmitOnOperationSuccess(aeron_async_destination_t *async)\n    {\n        char response_buffer[sizeof(aeron_operation_succeeded_t)];\n        auto response = reinterpret_cast<aeron_operation_succeeded_t *>(response_buffer);\n\n        response->correlation_id = async->registration_id;\n\n        if (aeron_broadcast_transmitter_transmit(\n            &m_to_clients,\n            AERON_RESPONSE_ON_OPERATION_SUCCESS,\n            response_buffer,\n            sizeof(aeron_operation_succeeded_t)) < 0)\n        {\n            throw std::runtime_error(\"error transmitting ON_OPERATION_SUCCESS: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    void transmitOnError(aeron_async_add_publication_t *async, int32_t errorCode, const std::string &errorMessage)\n    {\n        char response_buffer[sizeof(aeron_error_response_t) + AERON_ERROR_MAX_TOTAL_LENGTH];\n        auto response = reinterpret_cast<aeron_error_response_t *>(response_buffer);\n\n        response->offending_command_correlation_id = async->registration_id;\n        response->error_code = errorCode;\n        response->error_message_length = static_cast<int32_t>(errorMessage.length());\n        memcpy(response_buffer + sizeof(aeron_error_response_t), errorMessage.c_str(), errorMessage.length());\n\n        if (aeron_broadcast_transmitter_transmit(\n            &m_to_clients,\n            AERON_RESPONSE_ON_ERROR,\n            response_buffer,\n            sizeof(aeron_error_response_t) + errorMessage.length()) < 0)\n        {\n            throw std::runtime_error(\"error transmitting ON_ERROR: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    void transmitOnSubscriptionReady(aeron_async_add_subscription_t *async)\n    {\n        char response_buffer[sizeof(aeron_subscription_ready_t)];\n        auto response = reinterpret_cast<aeron_subscription_ready_t *>(response_buffer);\n        int32_t channel_status_indicator_id = 11;\n\n        response->correlation_id = async->registration_id;\n        response->channel_status_indicator_id = channel_status_indicator_id;\n\n        if (aeron_broadcast_transmitter_transmit(\n            &m_to_clients,\n            AERON_RESPONSE_ON_SUBSCRIPTION_READY,\n            response_buffer,\n            sizeof(aeron_subscription_ready_t)) < 0)\n        {\n            throw std::runtime_error(\"error transmitting ON_SUBSCRIPTION_READY: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    void transmitOnCounterReady(aeron_async_add_counter_t *async)\n    {\n        char response_buffer[sizeof(aeron_counter_update_t)];\n        auto response = reinterpret_cast<aeron_counter_update_t *>(response_buffer);\n        int32_t counter_id = 11;\n\n        response->correlation_id = async->registration_id;\n        response->counter_id = counter_id;\n\n        if (aeron_broadcast_transmitter_transmit(\n            &m_to_clients,\n            AERON_RESPONSE_ON_COUNTER_READY,\n            response_buffer,\n            sizeof(aeron_counter_update_t)) < 0)\n        {\n            throw std::runtime_error(\"error transmitting ON_COUNTER_READY: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    void transmitOnStaticCounter(aeron_async_add_counter_t *async, int32_t counter_id)\n    {\n        char response_buffer[sizeof(aeron_static_counter_response_t)];\n        auto response = reinterpret_cast<aeron_static_counter_response_t *>(response_buffer);\n\n        response->correlation_id = async->registration_id;\n        response->counter_id = counter_id;\n\n        if (aeron_broadcast_transmitter_transmit(\n            &m_to_clients,\n            AERON_RESPONSE_ON_STATIC_COUNTER,\n            response_buffer,\n            sizeof(aeron_static_counter_response_t)) < 0)\n        {\n            throw std::runtime_error(\"error transmitting ON_STATIC_COUNTER: \" + std::string(aeron_errmsg()));\n        }\n    }\n\nprotected:\n    aeron_context_t *m_context = nullptr;\n    aeron_client_conductor_t m_conductor = {};\n    aeron_counters_manager_t m_counters_manager = {};\n    aeron_clock_cache_t m_cached_clock = {};\n    std::unique_ptr<uint8_t[]> m_cnc;\n    aeron_mpsc_rb_t m_to_driver = {};\n    aeron_broadcast_transmitter_t m_to_clients = {};\n    std::string m_logFileName;\n\n    std::function<void(int32_t, const void *, size_t)> m_to_driver_handler;\n\n    std::function<void(aeron_async_add_publication_t *, const char *, int32_t, int32_t, int64_t)> m_on_new_publication;\n    std::function<void(aeron_async_add_exclusive_publication_t *, const char *, int32_t, int32_t, int64_t)>\n        m_on_new_exclusive_publication;\n    std::function<void(aeron_async_add_subscription_t *, const char *, int32_t, int64_t)> m_on_new_subscription;\n};\n\nTEST_F(ClientConductorTest, shouldInitAndClose)\n{\n    // nothing to do\n}\n\nTEST_F(ClientConductorTest, shouldAddPublicationSuccessfully)\n{\n    aeron_async_add_publication_t *async = nullptr;\n    aeron_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == publication);\n\n    transmitOnPublicationReady(async, m_logFileName, false);\n    createLogFile(m_logFileName);\n    doWork();\n\n    ASSERT_GT(aeron_async_add_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr != publication);\n\n    ASSERT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldAddPublicationSuccessfullyMaxMessageSize)\n{\n    aeron_async_add_publication_t *async = nullptr;\n    aeron_publication_t *publication = nullptr;\n    const size_t uri_length = MAX_MESSAGE_SIZE - sizeof(aeron_publication_command_t);\n    std::string uri = allocateStringWithPrefix(URI_RESERVED, \"|alias=\", 'X', uri_length);\n\n    ASSERT_EQ(aeron_client_conductor_async_add_publication(&async, &m_conductor, uri.c_str(), STREAM_ID), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == publication);\n\n    transmitOnPublicationReady(async, m_logFileName, false);\n    createLogFile(m_logFileName);\n    doWork();\n\n    ASSERT_GT(aeron_async_add_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr != publication);\n    EXPECT_EQ(strcmp(uri.c_str(), publication->channel), 0);\n\n    ASSERT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldErrorOnAddPublicationFromDriverError)\n{\n    aeron_async_add_publication_t *async = nullptr;\n    aeron_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == publication);\n\n    transmitOnError(async, AERON_ERROR_CODE_INVALID_CHANNEL, \"invalid channel\");\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_publication_poll(&publication, async), -1);\n    ASSERT_EQ(-AERON_ERROR_CODE_INVALID_CHANNEL, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldErrorOnAddPublicationFromDriverTimeout)\n{\n    aeron_async_add_publication_t *async = nullptr;\n    aeron_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == publication);\n\n    doWorkForNs((m_context->driver_timeout_ms + 1000) * 1000000);\n\n    ASSERT_EQ(aeron_async_add_publication_poll(&publication, async), -1);\n    ASSERT_EQ(AERON_CLIENT_ERROR_DRIVER_TIMEOUT, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldAddExclusivePublicationSuccessfully)\n{\n    aeron_async_add_exclusive_publication_t *async = nullptr;\n    aeron_exclusive_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_exclusive_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == publication);\n\n    transmitOnPublicationReady(async, m_logFileName, true);\n    createLogFile(m_logFileName);\n    doWork();\n\n    ASSERT_GT(aeron_async_add_exclusive_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr != publication);\n\n    ASSERT_EQ(aeron_exclusive_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldAddExclusivePublicationSuccessfullyMaxMessageSize)\n{\n    aeron_async_add_exclusive_publication_t *async = nullptr;\n    aeron_exclusive_publication_t *publication = nullptr;\n    const size_t uri_length = MAX_MESSAGE_SIZE - sizeof(aeron_publication_command_t);\n    std::string uri = allocateStringWithPrefix(URI_RESERVED, \"|alias=\", 'C', uri_length);\n\n    ASSERT_EQ(aeron_client_conductor_async_add_exclusive_publication(&async, &m_conductor, uri.c_str(), STREAM_ID), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == publication);\n\n    transmitOnPublicationReady(async, m_logFileName, true);\n    createLogFile(m_logFileName);\n    doWork();\n\n    ASSERT_GT(aeron_async_add_exclusive_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr != publication);\n    EXPECT_EQ(strcmp(uri.c_str(), publication->channel), 0);\n\n    ASSERT_EQ(aeron_exclusive_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldErrorOnAddExclusivePublicationFromDriverError)\n{\n    aeron_async_add_exclusive_publication_t *async = nullptr;\n    aeron_exclusive_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_exclusive_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == publication);\n\n    transmitOnError(async, AERON_ERROR_CODE_INVALID_CHANNEL, \"invalid channel\");\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication_poll(&publication, async), -1);\n}\n\nTEST_F(ClientConductorTest, shouldErrorOnAddExclusivePublicationFromDriverTimeout)\n{\n    aeron_async_add_exclusive_publication_t *async = nullptr;\n    aeron_exclusive_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_exclusive_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication_poll(&publication, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == publication);\n\n    doWorkForNs((m_context->driver_timeout_ms + 1000) * 1000000);\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication_poll(&publication, async), -1);\n    ASSERT_EQ(AERON_CLIENT_ERROR_DRIVER_TIMEOUT, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldAddSubscriptionSuccessfully)\n{\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_subscription(\n        &async, &m_conductor, SUB_URI, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_subscription_poll(&subscription, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == subscription);\n\n    transmitOnSubscriptionReady(async);\n    doWork();\n\n    ASSERT_GT(aeron_async_add_subscription_poll(&subscription, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr != subscription);\n\n    ASSERT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldAddSubscriptionSuccessfullyMaxMessageSize)\n{\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription = nullptr;\n    const size_t uri_length = MAX_MESSAGE_SIZE - sizeof(aeron_subscription_command_t);\n    std::string uri = allocateStringWithPrefix(SUB_URI, \"|alias=\", 'Z', uri_length);\n\n    ASSERT_EQ(aeron_client_conductor_async_add_subscription(\n        &async, &m_conductor, uri.c_str(), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_subscription_poll(&subscription, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == subscription);\n\n    transmitOnSubscriptionReady(async);\n    doWork();\n\n    ASSERT_GT(aeron_async_add_subscription_poll(&subscription, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr != subscription);\n    EXPECT_EQ(strcmp(uri.c_str(), subscription->channel), 0);\n\n    ASSERT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldErrorOnAddSubscriptionFromDriverError)\n{\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_subscription(\n        &async, &m_conductor, SUB_URI, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_subscription_poll(&subscription, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == subscription);\n\n    transmitOnError(async, AERON_ERROR_CODE_INVALID_CHANNEL, \"invalid channel\");\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_subscription_poll(&subscription, async), -1);\n}\n\nTEST_F(ClientConductorTest, shouldErrorOnAddSubscriptionFromDriverTimeout)\n{\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_subscription(\n        &async, &m_conductor, SUB_URI, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_subscription_poll(&subscription, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == subscription);\n\n    doWorkForNs((m_context->driver_timeout_ms + 1000) * 1000000LL);\n\n    ASSERT_EQ(aeron_async_add_subscription_poll(&subscription, async), -1);\n    ASSERT_EQ(AERON_CLIENT_ERROR_DRIVER_TIMEOUT, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldAddCounterSuccessfully)\n{\n    aeron_async_add_counter_t *async = nullptr;\n    aeron_counter_t *counter = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_counter(\n        &async, &m_conductor, COUNTER_TYPE_ID, nullptr, 0, nullptr, 0), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_counter_poll(&counter, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == counter);\n\n    transmitOnCounterReady(async);\n    doWork();\n\n    ASSERT_GT(aeron_async_add_counter_poll(&counter, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr != counter);\n\n    ASSERT_EQ(aeron_counter_close(counter, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldAddCounterSuccessfullyMaxMessageSize)\n{\n    aeron_async_add_counter_t *async = nullptr;\n    aeron_counter_t *counter = nullptr;\n    const size_t key_buffer_length = 256;\n    auto *key_buffer = new uint8_t[key_buffer_length];\n    key_buffer[5] = 13;\n    const size_t label_buffer_length =\n        MAX_MESSAGE_SIZE - key_buffer_length - 2 * sizeof(int32_t) - sizeof(aeron_counter_command_t);\n    std::string label_buffer = allocateStringWithPrefix(\"label\", \"=\", 'x', label_buffer_length);\n    ASSERT_EQ(label_buffer_length, label_buffer.length());\n\n    ASSERT_EQ(aeron_client_conductor_async_add_counter(\n        &async, &m_conductor, COUNTER_TYPE_ID, key_buffer, key_buffer_length, label_buffer.c_str(), label_buffer_length), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_counter_poll(&counter, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == counter);\n\n    transmitOnCounterReady(async);\n    doWork();\n\n    ASSERT_GT(aeron_async_add_counter_poll(&counter, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr != counter);\n\n    delete[] key_buffer;\n\n    ASSERT_EQ(aeron_counter_close(counter, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldErrorOnAddCounterFromDriverError)\n{\n    aeron_async_add_counter_t *async = nullptr;\n    aeron_counter_t *counter = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_counter(\n        &async, &m_conductor, COUNTER_TYPE_ID, nullptr, 0, nullptr, 0), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_counter_poll(&counter, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == counter);\n\n    transmitOnError(async, AERON_ERROR_CODE_GENERIC_ERROR, \"can not add counter\");\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_counter_poll(&counter, async), -1);\n}\n\nTEST_F(ClientConductorTest, shouldErrorOnAddCounterFromDriverTimeout)\n{\n    aeron_async_add_counter_t *async = nullptr;\n    aeron_counter_t *counter = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_counter(\n        &async, &m_conductor, COUNTER_TYPE_ID, nullptr, 0, nullptr, 0), 0);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_counter_poll(&counter, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == counter);\n\n    doWorkForNs((m_context->driver_timeout_ms + 1000) * 1000000LL);\n\n    ASSERT_EQ(aeron_async_add_counter_poll(&counter, async), -1);\n    ASSERT_EQ(AERON_CLIENT_ERROR_DRIVER_TIMEOUT, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldAddPublicationAndHandleOnNewPublication)\n{\n    aeron_async_add_publication_t *async = nullptr;\n    aeron_publication_t *publication = nullptr;\n    bool was_on_new_publication_called = false;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    m_on_new_publication = [&](aeron_async_add_publication_t *async,\n        const char *channel,\n        int32_t stream_id,\n        int32_t session_id,\n        int64_t correlation_id)\n    {\n        EXPECT_EQ(strcmp(channel, URI_RESERVED), 0);\n        EXPECT_EQ(stream_id, STREAM_ID);\n        EXPECT_EQ(session_id, SESSION_ID);\n        EXPECT_EQ(correlation_id, async->registration_id);\n\n        ASSERT_GT(aeron_async_add_publication_poll(&publication, async), 0) << aeron_errmsg();\n        ASSERT_TRUE(nullptr != publication);\n\n        was_on_new_publication_called = true;\n    };\n\n    transmitOnPublicationReady(async, m_logFileName, false);\n    createLogFile(m_logFileName);\n    doWork();\n\n    EXPECT_TRUE(was_on_new_publication_called);\n\n    // graceful close and reclaim for sanitize\n    ASSERT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldAddExclusivePublicationAndHandleOnNewPublication)\n{\n    aeron_async_add_exclusive_publication_t *async = nullptr;\n    aeron_exclusive_publication_t *publication = nullptr;\n    bool was_on_new_exclusive_publication_called = false;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_exclusive_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    m_on_new_exclusive_publication = [&](aeron_async_add_exclusive_publication_t *async,\n        const char *channel,\n        int32_t stream_id,\n        int32_t session_id,\n        int64_t correlation_id)\n    {\n        EXPECT_EQ(strcmp(channel, URI_RESERVED), 0);\n        EXPECT_EQ(stream_id, STREAM_ID);\n        EXPECT_EQ(session_id, SESSION_ID);\n        EXPECT_EQ(correlation_id, async->registration_id);\n\n        ASSERT_GT(aeron_async_add_exclusive_publication_poll(&publication, async), 0) << aeron_errmsg();\n        ASSERT_TRUE(nullptr != publication);\n\n        was_on_new_exclusive_publication_called = true;\n    };\n\n    transmitOnPublicationReady(async, m_logFileName, false);\n    createLogFile(m_logFileName);\n    doWork();\n\n    EXPECT_TRUE(was_on_new_exclusive_publication_called);\n\n    // graceful close and reclaim for sanitize\n    ASSERT_EQ(aeron_exclusive_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldAddSubscriptionAndHandleOnNewSubscription)\n{\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription = nullptr;\n    bool was_on_new_subscription_called = false;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_subscription(\n        &async, &m_conductor, SUB_URI, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    doWork();\n\n    m_on_new_subscription = [&](\n        aeron_async_add_subscription_t *async,\n        const char *channel,\n        int32_t stream_id,\n        int64_t correlation_id)\n    {\n        EXPECT_EQ(strcmp(channel, SUB_URI), 0);\n        EXPECT_EQ(stream_id, STREAM_ID);\n        EXPECT_EQ(correlation_id, async->registration_id);\n\n        ASSERT_GT(aeron_async_add_subscription_poll(&subscription, async), 0) << aeron_errmsg();\n        ASSERT_TRUE(nullptr != subscription);\n\n        was_on_new_subscription_called = true;\n    };\n\n    transmitOnSubscriptionReady(async);\n    doWork();\n\n    EXPECT_TRUE(was_on_new_subscription_called);\n\n    // graceful close and reclaim for sanitize\n    ASSERT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldHandlePublicationAddRemoveDestination)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_destination_t *async_add_dest = nullptr;\n    aeron_async_destination_t *async_remove_dest = nullptr;\n    aeron_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_publication(&async_pub, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    transmitOnPublicationReady(async_pub, m_logFileName, false);\n    createLogFile(m_logFileName);\n    doWork();\n    ASSERT_GT(aeron_async_add_publication_poll(&publication, async_pub), 0) << aeron_errmsg();\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_add_publication_destination(&async_add_dest, &m_conductor, publication, DEST_URI),\n        0);\n\n    transmitOnOperationSuccess(async_add_dest);\n    doWork();\n    ASSERT_EQ(async_add_dest->registration_status, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n    ASSERT_EQ(async_add_dest->resource.publication->registration_id, publication->registration_id);\n    ASSERT_GT(aeron_publication_async_destination_poll(async_add_dest), 0) << aeron_errmsg();\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_remove_publication_destination(\n            &async_remove_dest, &m_conductor, publication, DEST_URI),\n        0);\n    transmitOnOperationSuccess(async_remove_dest);\n    doWork();\n    ASSERT_GT(aeron_publication_async_destination_poll(async_remove_dest), 0) << aeron_errmsg();\n\n    // graceful close and reclaim for sanitize\n    ASSERT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldHandlePublicationAddRemoveDestinationMaxMessageSize)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_destination_t *async_add_dest = nullptr;\n    aeron_async_destination_t *async_remove_dest = nullptr;\n    aeron_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_publication(&async_pub, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    transmitOnPublicationReady(async_pub, m_logFileName, false);\n    createLogFile(m_logFileName);\n    doWork();\n    ASSERT_GT(aeron_async_add_publication_poll(&publication, async_pub), 0) << aeron_errmsg();\n\n    const size_t uri_length = MAX_MESSAGE_SIZE - sizeof(aeron_destination_command_t);\n    std::string destUri = allocateStringWithPrefix(DEST_URI, \"|alias=\", 'a', uri_length);\n    ASSERT_EQ(\n        aeron_client_conductor_async_add_publication_destination(&async_add_dest, &m_conductor, publication, destUri.c_str()),\n        0);\n\n    transmitOnOperationSuccess(async_add_dest);\n    doWork();\n    ASSERT_EQ(async_add_dest->registration_status, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n    ASSERT_EQ(async_add_dest->resource.publication->registration_id, publication->registration_id);\n    ASSERT_GT(aeron_publication_async_destination_poll(async_add_dest), 0) << aeron_errmsg();\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_remove_publication_destination(\n            &async_remove_dest, &m_conductor, publication, destUri.c_str()),\n        0);\n    transmitOnOperationSuccess(async_remove_dest);\n\n    doWork();\n    ASSERT_GT(aeron_publication_async_destination_poll(async_remove_dest), 0) << aeron_errmsg();\n\n    // graceful close and reclaim for sanitize\n    ASSERT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldHandleExclusivePublicationAddDestination)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_destination_t *async_dest = nullptr;\n    aeron_exclusive_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_exclusive_publication(&async_pub, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    transmitOnPublicationReady(async_pub, m_logFileName, false);\n    createLogFile(m_logFileName);\n    doWork();\n    ASSERT_GT(aeron_async_add_exclusive_publication_poll(&publication, async_pub), 0) << aeron_errmsg();\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_add_exclusive_publication_destination(\n            &async_dest, &m_conductor, publication, DEST_URI),\n        0);\n\n    transmitOnOperationSuccess(async_dest);\n    doWork();\n    ASSERT_EQ(async_dest->registration_status, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n    ASSERT_EQ(async_dest->resource.publication->registration_id, publication->registration_id);\n    ASSERT_GT(aeron_exclusive_publication_async_destination_poll(async_dest), 0) << aeron_errmsg();\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_remove_exclusive_publication_destination(\n            &async_dest, &m_conductor, publication, DEST_URI),\n        0);\n    transmitOnOperationSuccess(async_dest);\n    doWork();\n    ASSERT_GT(aeron_exclusive_publication_async_destination_poll(async_dest), 0) << aeron_errmsg();\n\n    // graceful close and reclaim for sanitize\n    ASSERT_EQ(aeron_exclusive_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldHandleExclusivePublicationAddDestinationMaxMessageSize)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_destination_t *async_dest = nullptr;\n    aeron_exclusive_publication_t *publication = nullptr;\n\n    ASSERT_EQ(aeron_client_conductor_async_add_exclusive_publication(&async_pub, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    transmitOnPublicationReady(async_pub, m_logFileName, false);\n    createLogFile(m_logFileName);\n    doWork();\n    ASSERT_GT(aeron_async_add_exclusive_publication_poll(&publication, async_pub), 0) << aeron_errmsg();\n\n    const size_t uri_length = MAX_MESSAGE_SIZE - sizeof(aeron_destination_command_t);\n    std::string destUri = allocateStringWithPrefix(DEST_URI, \"|alias=\", 'a', uri_length);\n    ASSERT_EQ(\n        aeron_client_conductor_async_add_exclusive_publication_destination(\n            &async_dest, &m_conductor, publication, destUri.c_str()),\n        0);\n\n    transmitOnOperationSuccess(async_dest);\n    doWork();\n    ASSERT_EQ(async_dest->registration_status, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n    ASSERT_EQ(async_dest->resource.publication->registration_id, publication->registration_id);\n    ASSERT_GT(aeron_exclusive_publication_async_destination_poll(async_dest), 0) << aeron_errmsg();\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_remove_exclusive_publication_destination(\n            &async_dest, &m_conductor, publication, destUri.c_str()),\n        0);\n\n    transmitOnOperationSuccess(async_dest);\n    doWork();\n    ASSERT_GT(aeron_exclusive_publication_async_destination_poll(async_dest), 0) << aeron_errmsg();\n\n    // graceful close and reclaim for sanitize\n    ASSERT_EQ(aeron_exclusive_publication_close(publication, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldHandleSubscriptionAddDestination)\n{\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    aeron_async_destination_t *async_dest = nullptr;\n    aeron_subscription_t *subscription = nullptr;\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_add_subscription(\n            &async_sub, &m_conductor, URI_RESERVED, STREAM_ID, nullptr, nullptr, nullptr, nullptr),\n        0);\n    doWork();\n\n    transmitOnSubscriptionReady(async_sub);\n    createLogFile(m_logFileName);\n    doWork();\n    ASSERT_GT(aeron_async_add_subscription_poll(&subscription, async_sub), 0) << aeron_errmsg();\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_add_subscription_destination(\n            &async_dest, &m_conductor, subscription, DEST_URI),\n        0);\n\n    transmitOnOperationSuccess(async_dest);\n    doWork();\n    ASSERT_EQ(async_dest->registration_status, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n    ASSERT_EQ(async_dest->resource.subscription->registration_id, subscription->registration_id);\n    ASSERT_GT(aeron_subscription_async_destination_poll(async_dest), 0) << aeron_errmsg();\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_remove_subscription_destination(&async_dest, &m_conductor, subscription, DEST_URI),\n        0);\n    transmitOnOperationSuccess(async_dest);\n    doWork();\n    ASSERT_GT(aeron_subscription_async_destination_poll(async_dest), 0) << aeron_errmsg();\n\n    // graceful close and reclaim for sanitize\n    ASSERT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldHandleSubscriptionAddDestinationMaxMessageSize)\n{\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    aeron_async_destination_t *async_dest = nullptr;\n    aeron_subscription_t *subscription = nullptr;\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_add_subscription(\n            &async_sub, &m_conductor, URI_RESERVED, STREAM_ID, nullptr, nullptr, nullptr, nullptr),\n        0);\n    doWork();\n\n    transmitOnSubscriptionReady(async_sub);\n    createLogFile(m_logFileName);\n    doWork();\n    ASSERT_GT(aeron_async_add_subscription_poll(&subscription, async_sub), 0) << aeron_errmsg();\n\n    const size_t uri_length = MAX_MESSAGE_SIZE - sizeof(aeron_destination_command_t);\n    std::string destUri = allocateStringWithPrefix(DEST_URI, \"|alias=\", 'a', uri_length);\n    ASSERT_EQ(\n        aeron_client_conductor_async_add_subscription_destination(\n            &async_dest, &m_conductor, subscription, destUri.c_str()),\n        0);\n\n    transmitOnOperationSuccess(async_dest);\n    doWork();\n    ASSERT_EQ(async_dest->registration_status, AERON_CLIENT_REGISTRATION_STATUS_REGISTERED);\n    ASSERT_EQ(async_dest->resource.subscription->registration_id, subscription->registration_id);\n    ASSERT_GT(aeron_subscription_async_destination_poll(async_dest), 0) << aeron_errmsg();\n\n    ASSERT_EQ(\n        aeron_client_conductor_async_remove_subscription_destination(&async_dest, &m_conductor, subscription, destUri.c_str()),\n        0);\n    transmitOnOperationSuccess(async_dest);\n\n    doWork();\n    ASSERT_GT(aeron_subscription_async_destination_poll(async_dest), 0) << aeron_errmsg();\n\n    // graceful close and reclaim for sanitize\n    ASSERT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldSetCloseFlagOnTimeout)\n{\n    int errorcode = 0;\n    m_conductor.error_handler_clientd = &errorcode;\n    m_conductor.error_handler = save_last_errorcode;\n\n    aeron_client_timeout_t timeout;\n    timeout.client_id = m_conductor.client_id;\n\n    aeron_client_conductor_on_client_timeout(&m_conductor, &timeout);\n\n    ASSERT_TRUE(aeron_client_conductor_is_closed(&m_conductor));\n    ASSERT_EQ(AERON_CLIENT_ERROR_CLIENT_TIMEOUT, errorcode);\n}\n\nTEST_F(ClientConductorTest, shouldCreateServiceIntervalTimeoutError)\n{\n    int errorcode = 0;\n    m_conductor.error_handler_clientd = &errorcode;\n    m_conductor.error_handler = save_last_errorcode;\n\n    doWork();\n    doWorkForNs(CLIENT_LIVENESS_TIMEOUT + 1, false, CLIENT_LIVENESS_TIMEOUT + 1);\n\n    ASSERT_EQ(AERON_CLIENT_ERROR_CONDUCTOR_SERVICE_TIMEOUT, errorcode);\n}\n\nTEST_F(ClientConductorTest, shouldCreateDriverTimeoutError)\n{\n    int errorcode = 0;\n    m_conductor.error_handler_clientd = &errorcode;\n    m_conductor.error_handler = save_last_errorcode;\n\n    doWork();\n    doWorkForNs(DRIVER_TIMEOUT_INTERVAL_NS * 2, false, DRIVER_TIMEOUT_INTERVAL_NS * 2);\n\n    ASSERT_EQ(AERON_CLIENT_ERROR_DRIVER_TIMEOUT, errorcode);\n}\n\nTEST_F(ClientConductorTest, shouldAddClientInfoToTheHeartbeatCounterLabel)\n{\n    m_conductor.client_id = 0x60001da400;\n    m_conductor.client_name = \"test client name\";\n    aeron_heartbeat_timestamp_key_layout_t key = {};\n    key.registration_id = m_conductor.client_id;\n    std::string label = std::string(\"test 42 and beyond\");\n\n    int32_t counter_id = aeron_counters_manager_allocate(\n        &m_counters_manager,\n        AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID,\n        (const uint8_t *)&key,\n        sizeof(key),\n        label.c_str(),\n        label.length());\n    ASSERT_GT(counter_id, -1);\n\n    char counterLabel[100];\n    memset(counterLabel, '\\0', sizeof(counterLabel));\n    ASSERT_EQ(label.length(), aeron_counters_reader_counter_label(\n        &m_conductor.counters_reader, counter_id, counterLabel, sizeof(counterLabel)));\n\n    ASSERT_STREQ(label.c_str(), counterLabel);\n\n    doWorkForNs((int64_t)m_context->keepalive_interval_ns * 2, true);\n\n    ASSERT_GT(aeron_counters_reader_counter_label(\n        &m_conductor.counters_reader, counter_id, counterLabel, sizeof(counterLabel)), label.length());\n    ASSERT_EQ(\n        label + \" name=test client name version=\" + aeron_version_text() + \" commit=\" + aeron_version_gitsha(),\n        std::string(counterLabel));\n}\n\nTEST_F(ClientConductorTest, shouldAddClientInfoToTheHeartbeatCounterLabelUpToMaxLength)\n{\n    m_conductor.client_id = -4373543;\n    auto clientName = std::string(\"test\").append(1000, 'p');\n    m_conductor.client_name = clientName.c_str();\n    aeron_heartbeat_timestamp_key_layout_t key = {};\n    key.registration_id = m_conductor.client_id;\n    std::string label = std::string(\"abc\");\n\n    int32_t counter_id = aeron_counters_manager_allocate(\n        &m_counters_manager,\n        AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID,\n        (const uint8_t *)&key,\n        sizeof(key),\n        label.c_str(),\n        label.length());\n    ASSERT_GT(counter_id, -1);\n\n    char counterLabel[AERON_COUNTER_MAX_LABEL_LENGTH];\n    memset(counterLabel, '\\0', sizeof(counterLabel));\n    ASSERT_EQ(label.length(), aeron_counters_reader_counter_label(\n        &m_conductor.counters_reader, counter_id, counterLabel, sizeof(counterLabel)));\n\n    ASSERT_STREQ(label.c_str(), counterLabel);\n\n    doWorkForNs((int64_t)m_context->keepalive_interval_ns * 2, true);\n\n    ASSERT_GT(aeron_counters_reader_counter_label(\n        &m_conductor.counters_reader, counter_id, counterLabel, sizeof(counterLabel)), label.length());\n    ASSERT_EQ(\n        label + std::string(\" name=test\").append(366, 'p'),\n        std::string(counterLabel));\n}\n\nTEST_F(ClientConductorTest, shouldAddStaticCounterSuccessfully)\n{\n    aeron_async_add_counter_t *async = nullptr;\n    aeron_counter_t *counter = nullptr;\n\n    const char *label = \"first static counter from C\";\n    const size_t label_length = strlen(label);\n    const int registration_id = 42;\n    ASSERT_EQ(aeron_client_conductor_async_add_static_counter(\n        &async, &m_conductor, COUNTER_TYPE_ID, nullptr, 0, label, label_length, registration_id), 0);\n    ASSERT_EQ(registration_id, async->counter.registration_id);\n    ASSERT_EQ(label_length, async->counter.label_buffer_length);\n    doWork();\n\n    ASSERT_EQ(aeron_async_add_counter_poll(&counter, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr == counter);\n\n    const int32_t counter_id = 777;\n    transmitOnStaticCounter(async, counter_id);\n    doWork();\n\n    ASSERT_GT(aeron_async_add_counter_poll(&counter, async), 0) << aeron_errmsg();\n    ASSERT_TRUE(nullptr != counter);\n    ASSERT_EQ(registration_id, counter->registration_id);\n    ASSERT_EQ(counter_id, counter->counter_id);\n\n    ASSERT_EQ(aeron_counter_close(counter, nullptr, nullptr), 0);\n    doWork();\n}\n\nTEST_F(ClientConductorTest, shouldReturnRandomSessionIdIfControlProtolVersionNotSatisfied)\n{\n    aeron_async_get_next_available_session_id_t *async = nullptr;\n\n    m_conductor.control_protocol_version = 1;\n\n    const int32_t streamId = 43;\n    ASSERT_EQ(aeron_client_conductor_async_get_next_available_session_id(&async, &m_conductor, streamId), 0);\n    ASSERT_EQ(streamId, async->stream_id);\n    ASSERT_EQ(AERON_CLIENT_REGISTRATION_STATUS_REGISTERED, async->registration_status);\n    int32_t nextSessionId = async->resource.next_session_id;\n\n    int32_t sessionId;\n    ASSERT_EQ(aeron_async_next_session_id_poll(&sessionId, async), 1) << aeron_errmsg();\n    ASSERT_EQ(nextSessionId, sessionId);\n\n    ASSERT_EQ(aeron_client_conductor_async_get_next_available_session_id(&async, &m_conductor, streamId), 0);\n    ASSERT_EQ(streamId, async->stream_id);\n    ASSERT_EQ(AERON_CLIENT_REGISTRATION_STATUS_REGISTERED, async->registration_status);\n    int32_t nextSessionId2 = async->resource.next_session_id;\n    ASSERT_EQ(aeron_async_next_session_id_poll(&sessionId, async), 1) << aeron_errmsg();\n\n    ASSERT_NE(nextSessionId, nextSessionId2);\n}\n\nTEST_F(ClientConductorTest, asyncPublicationResourceMustBeExplicitlyFreedIfPollAbandonedBeforeCompletion)\n{\n    aeron_async_add_publication_t *async = nullptr;\n    aeron_publication_t *publication = nullptr;\n\n    EXPECT_EQ(aeron_client_conductor_async_add_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    EXPECT_EQ(aeron_async_add_publication_poll(&publication, async), 0) << aeron_errmsg();\n    EXPECT_EQ(nullptr, publication);\n\n    aeron_async_cmd_free(async);\n}\n\nTEST_F(ClientConductorTest, asyncExclusivePublicationResourceMustBeExplicitlyFreedIfPollAbandonedBeforeCompletion)\n{\n    aeron_async_add_exclusive_publication_t *async = nullptr;\n    aeron_exclusive_publication_t *publication = nullptr;\n\n    EXPECT_EQ(aeron_client_conductor_async_add_exclusive_publication(&async, &m_conductor, URI_RESERVED, STREAM_ID), 0);\n    doWork();\n\n    EXPECT_EQ(aeron_async_add_exclusive_publication_poll(&publication, async), 0) << aeron_errmsg();\n    EXPECT_EQ(nullptr, publication);\n\n    aeron_async_cmd_free(async);\n}\n\nTEST_F(ClientConductorTest, asyncSubscriptionResourceMustBeExplicitlyFreedIfPollAbandonedBeforeCompletion)\n{\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription = nullptr;\n\n    EXPECT_EQ(aeron_client_conductor_async_add_subscription(&async, &m_conductor, URI_RESERVED, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    doWork();\n\n    EXPECT_EQ(aeron_async_add_subscription_poll(&subscription, async), 0) << aeron_errmsg();\n    EXPECT_EQ(nullptr, subscription);\n\n    aeron_async_cmd_free(async);\n}\n\nTEST_F(ClientConductorTest, asyncCounterResourceMustBeExplicitlyFreedIfPollAbandonedBeforeCompletion)\n{\n    aeron_async_add_counter_t *async = nullptr;\n    aeron_counter_t *counter = nullptr;\n\n    EXPECT_EQ(aeron_client_conductor_async_add_counter(&async, &m_conductor, 1000, nullptr, 0, \"test\", strlen(\"test\")), 0);\n    doWork();\n\n    EXPECT_EQ(aeron_async_add_counter_poll(&counter, async), 0) << aeron_errmsg();\n    EXPECT_EQ(nullptr, counter);\n\n    aeron_async_cmd_free(async);\n}\n\nTEST_F(ClientConductorTest, shouldSetIdleStartegy)\n{\n    auto func = m_context->idle_strategy_func;\n    EXPECT_STREQ(\"sleep-ns\", m_context->idle_strategy_name);\n    EXPECT_STREQ(\"16000000\", m_context->idle_strategy_init_args);\n    EXPECT_NE(nullptr, m_context->idle_strategy_state);\n    EXPECT_NE(nullptr, func);\n\n    EXPECT_EQ(-1, aeron_context_set_idle_strategy(m_context, \"unknown value\"));\n    EXPECT_EQ(nullptr, m_context->idle_strategy_name);\n    EXPECT_EQ(nullptr, m_context->idle_strategy_state);\n    EXPECT_EQ(nullptr, m_context->idle_strategy_func);\n    EXPECT_STREQ(\"16000000\", m_context->idle_strategy_init_args);\n\n    EXPECT_EQ(0, aeron_context_set_idle_strategy_init_args(m_context, nullptr));\n    EXPECT_EQ(nullptr, aeron_context_get_idle_strategy_init_args(m_context));\n\n    EXPECT_EQ(0, aeron_context_set_idle_strategy_init_args(m_context, \"200-50-1us-2500ns\"));\n    EXPECT_STREQ(\"200-50-1us-2500ns\", aeron_context_get_idle_strategy_init_args(m_context));\n\n    EXPECT_EQ(0, aeron_context_set_idle_strategy(m_context, \"backoff\"));\n    EXPECT_STREQ(\"backoff\", m_context->idle_strategy_name);\n    EXPECT_NE(nullptr, m_context->idle_strategy_state);\n    EXPECT_NE(func, m_context->idle_strategy_func);\n    EXPECT_STREQ(\"200-50-1us-2500ns\", m_context->idle_strategy_init_args);\n}\n\nTEST_F(ClientConductorTest, shouldRejectCloseHandlerIfClosed)\n{\n    m_conductor.is_closed = true;\n    aeron_t aeron = {};\n    aeron.conductor = m_conductor;\n    aeron_on_close_client_pair_t pair = {};\n\n    EXPECT_EQ(-1, aeron_add_close_handler(&aeron, &pair));\n    EXPECT_EQ(EPERM, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldRejectRemoveCloseHandlerIfClosed)\n{\n    m_conductor.is_closed = true;\n    aeron_t aeron = {};\n    aeron.conductor = m_conductor;\n    aeron_on_close_client_pair_t pair = {};\n\n    EXPECT_EQ(-1, aeron_remove_close_handler(&aeron, &pair));\n    EXPECT_EQ(EPERM, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldRejectAddAvailableCounterHandlerIfClosed)\n{\n    m_conductor.is_closed = true;\n    aeron_t aeron = {};\n    aeron.conductor = m_conductor;\n    aeron_on_available_counter_pair_t pair = {};\n\n    EXPECT_EQ(-1, aeron_add_available_counter_handler(&aeron, &pair));\n    EXPECT_EQ(EPERM, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldRejectRemoveAvailableCounterHandlerIfClosed)\n{\n    m_conductor.is_closed = true;\n    aeron_t aeron = {};\n    aeron.conductor = m_conductor;\n    aeron_on_available_counter_pair_t pair = {};\n\n    EXPECT_EQ(-1, aeron_remove_available_counter_handler(&aeron, &pair));\n    EXPECT_EQ(EPERM, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldRejectAddUnavailableCounterHandlerIfClosed)\n{\n    m_conductor.is_closed = true;\n    aeron_t aeron = {};\n    aeron.conductor = m_conductor;\n    aeron_on_unavailable_counter_pair_t pair = {};\n\n    EXPECT_EQ(-1, aeron_add_unavailable_counter_handler(&aeron, &pair));\n    EXPECT_EQ(EPERM, aeron_errcode());\n}\n\nTEST_F(ClientConductorTest, shouldRejectRemoveUnavailableCounterHandlerIfClosed)\n{\n    m_conductor.is_closed = true;\n    aeron_t aeron = {};\n    aeron.conductor = m_conductor;\n    aeron_on_unavailable_counter_pair_t pair = {};\n\n    EXPECT_EQ(-1, aeron_remove_unavailable_counter_handler(&aeron, &pair));\n    EXPECT_EQ(EPERM, aeron_errcode());\n}\n\nclass ClientConductorIsLengthSufficientTest : public testing::TestWithParam<std::tuple<aeron_mapped_file_t*, bool>>\n{\n};\n\nstatic aeron_mapped_file_t* mappedFileFrom(size_t length, const aeron_cnc_metadata_t *metadata)\n{\n    aeron_mapped_file_t *mappedFile;\n    if (aeron_alloc(reinterpret_cast<void **>(&mappedFile), sizeof(aeron_mapped_file_t)) < 0)\n    {\n        throw std::runtime_error(\"failed to allocate aeron_mapped_file_t\");\n    }\n    mappedFile->addr = (void*)metadata;\n    mappedFile->length = length;\n    return mappedFile;\n}\n\nstatic aeron_cnc_metadata_t* metadata(\n    int32_t to_driver_buffer_length,\n    int32_t to_clients_buffer_length,\n    int32_t counter_metadata_buffer_length,\n    int32_t counter_values_buffer_length,\n    int32_t error_log_buffer_length)\n{\n    aeron_cnc_metadata_t *metadata;\n    if (aeron_alloc(reinterpret_cast<void **>(&metadata), sizeof(aeron_cnc_metadata_t)) < 0)\n    {\n        throw std::runtime_error(\"failed to allocate aeron_cnc_metadata_t\");\n    }\n    metadata->to_driver_buffer_length = to_driver_buffer_length;\n    metadata->to_clients_buffer_length = to_clients_buffer_length;\n    metadata->counter_metadata_buffer_length = counter_metadata_buffer_length;\n    metadata->counter_values_buffer_length = counter_values_buffer_length;\n    metadata->error_log_buffer_length = error_log_buffer_length;\n    return metadata;\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    ClientConductorIsLengthSufficientTest,\n    ClientConductorIsLengthSufficientTest,\n    testing::Values(\n        std::make_tuple(mappedFileFrom(0, nullptr), false),\n        std::make_tuple(mappedFileFrom(AERON_CNC_VERSION_AND_META_DATA_LENGTH - 1, nullptr), false),\n        std::make_tuple(mappedFileFrom(AERON_CNC_VERSION_AND_META_DATA_LENGTH,\n            metadata(TO_DRIVER_RING_BUFFER_LENGTH,0,0,0,0)), false),\n        std::make_tuple(mappedFileFrom(AERON_CNC_VERSION_AND_META_DATA_LENGTH + TO_DRIVER_RING_BUFFER_LENGTH,\n            metadata(TO_DRIVER_RING_BUFFER_LENGTH,TO_CLIENTS_BUFFER_LENGTH,0,0,0)), false),\n        std::make_tuple(mappedFileFrom(AERON_CNC_VERSION_AND_META_DATA_LENGTH + TO_DRIVER_RING_BUFFER_LENGTH + TO_CLIENTS_BUFFER_LENGTH,\n            metadata(TO_DRIVER_RING_BUFFER_LENGTH,TO_CLIENTS_BUFFER_LENGTH,COUNTER_METADATA_BUFFER_LENGTH,0,0)), false),\n        std::make_tuple(mappedFileFrom(AERON_CNC_VERSION_AND_META_DATA_LENGTH + TO_DRIVER_RING_BUFFER_LENGTH + TO_CLIENTS_BUFFER_LENGTH + COUNTER_METADATA_BUFFER_LENGTH,\n            metadata(TO_DRIVER_RING_BUFFER_LENGTH,TO_CLIENTS_BUFFER_LENGTH,COUNTER_METADATA_BUFFER_LENGTH,COUNTER_VALUES_BUFFER_LENGTH,0)), false),\n        std::make_tuple(mappedFileFrom(AERON_CNC_VERSION_AND_META_DATA_LENGTH + TO_DRIVER_RING_BUFFER_LENGTH + TO_CLIENTS_BUFFER_LENGTH + COUNTER_METADATA_BUFFER_LENGTH + COUNTER_VALUES_BUFFER_LENGTH,\n            metadata(TO_DRIVER_RING_BUFFER_LENGTH,TO_CLIENTS_BUFFER_LENGTH,COUNTER_METADATA_BUFFER_LENGTH,COUNTER_VALUES_BUFFER_LENGTH,ERROR_BUFFER_LENGTH)), false),\n        std::make_tuple(mappedFileFrom(AERON_CNC_VERSION_AND_META_DATA_LENGTH + TO_DRIVER_RING_BUFFER_LENGTH + TO_CLIENTS_BUFFER_LENGTH + COUNTER_METADATA_BUFFER_LENGTH + COUNTER_VALUES_BUFFER_LENGTH + ERROR_BUFFER_LENGTH,\n            metadata(TO_DRIVER_RING_BUFFER_LENGTH,TO_CLIENTS_BUFFER_LENGTH,COUNTER_METADATA_BUFFER_LENGTH,COUNTER_VALUES_BUFFER_LENGTH,ERROR_BUFFER_LENGTH)), true),\n        std::make_tuple(mappedFileFrom(INT64_MAX,\n            metadata(TO_DRIVER_RING_BUFFER_LENGTH,TO_CLIENTS_BUFFER_LENGTH,COUNTER_METADATA_BUFFER_LENGTH,COUNTER_VALUES_BUFFER_LENGTH,ERROR_BUFFER_LENGTH)), true)\n    ));\n\nTEST_P(ClientConductorIsLengthSufficientTest, shouldCheckIfLengthIsSufficient)\n{\n    const auto mappedFile = std::get<0>(GetParam());\n    const bool expected = std::get<1>(GetParam());\n    ASSERT_EQ(expected, aeron_cnc_is_file_length_sufficient(mappedFile));\n    aeron_free(mappedFile->addr);\n    aeron_free(mappedFile);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_client_test_utils.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CLIENT_TEST_UTILS_H\n#define AERON_CLIENT_TEST_UTILS_H\n\n#include <string>\n\nextern \"C\"\n{\n#include \"util/aeron_fileutil.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_log_buffer.h\"\n}\n\n#define FILE_PAGE_SIZE (4 * 1024)\n\n#define INITIAL_TERM_ID (1234)\n\nnamespace aeron\n{\nnamespace test\n{\n\nstd::string tempFileName()\n{\n    char filename[AERON_MAX_PATH] = { 0 };\n\n    aeron_temp_filename(filename, sizeof(filename));\n    return { filename };\n}\n\nvoid createLogFile(const std::string &filename, const int32_t term_length, const int32_t term_id)\n{\n    aeron_mapped_file_t mappedFile =\n        { nullptr, term_length * 3 + AERON_LOGBUFFER_META_DATA_LENGTH };\n\n    if (aeron_map_new_file(&mappedFile, filename.c_str(), false) < 0)\n    {\n        throw std::runtime_error(\"could not create log file: \" + std::string(aeron_errmsg()));\n    }\n\n    auto metadata = reinterpret_cast<aeron_logbuffer_metadata_t *>((uint8_t *)mappedFile.addr +\n        (mappedFile.length - AERON_LOGBUFFER_META_DATA_LENGTH));\n\n    metadata->term_length = term_length;\n    metadata->page_size = FILE_PAGE_SIZE;\n    metadata->initial_term_id = term_id;\n    metadata->term_tail_counters[0] = (int64_t)term_id << 32;\n    metadata->term_tail_counters[1] = (int64_t)(term_id + 1) << 32;\n    metadata->term_tail_counters[2] = (int64_t)(term_id + 2) << 32;\n\n    aeron_unmap(&mappedFile);\n}\n\nvoid createLogFile(const std::string &filename)\n{\n    createLogFile(filename, AERON_LOGBUFFER_TERM_MIN_LENGTH, INITIAL_TERM_ID);\n}\n\n}\n}\n\n#endif //AERON_CLIENT_TEST_UTILS_H\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_controlled_fragment_assembler_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeronc.h\"\n#include \"aeron_image.h\"\n}\n\n#define STREAM_ID (-8)\n#define SESSION_ID (43)\n#define TERM_LENGTH (AERON_LOGBUFFER_TERM_MIN_LENGTH)\n#define INITIAL_TERM_ID (178343)\n#define ACTIVE_TERM_ID  (INITIAL_TERM_ID + 5)\n#define POSITION_BITS_TO_SHIFT (aeron_number_of_trailing_zeroes(TERM_LENGTH))\n#define MTU_LENGTH (2048)\n\ntypedef std::array<uint8_t, TERM_LENGTH> fragment_buffer_t;\n\nclass ControlledFragmentAssemblerTest : public testing::Test\n{\npublic:\n    ControlledFragmentAssemblerTest()\n    {\n        m_fragment.fill(0);\n        m_header.frame = (aeron_data_header_t *)m_fragment.data();\n        m_header.context = (void*)\"controlled fragment assembler test\";\n\n        if (aeron_controlled_fragment_assembler_create(&m_assembler, fragment_handler, this) < 0)\n        {\n            throw std::runtime_error(\"could not create aeron_fragment_assembler_create: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    ~ControlledFragmentAssemblerTest() override\n    {\n        aeron_controlled_fragment_assembler_delete(m_assembler);\n    }\n\n    void fillFrame(uint8_t flags, int32_t offset, size_t length, uint8_t initialPayloadValue)\n    {\n        fillFrame(\n            AERON_FRAME_HEADER_VERSION,\n            flags,\n            AERON_HDR_TYPE_DATA,\n            offset,\n            SESSION_ID,\n            STREAM_ID,\n            ACTIVE_TERM_ID,\n            0,\n            length,\n            initialPayloadValue);\n    }\n\n    void fillFrame(\n        int8_t version,\n        uint8_t flags,\n        int16_t type,\n        int32_t termOffset,\n        int32_t sessionId,\n        int32_t streamId,\n        int32_t termId,\n        int64_t reservedValue,\n        size_t length,\n        uint8_t initialPayloadValue)\n    {\n        auto frame = (aeron_data_header_t *)m_fragment.data();\n\n        frame->frame_header.frame_length = (int32_t)(AERON_DATA_HEADER_LENGTH + length);\n        frame->frame_header.version = version;\n        frame->frame_header.flags = flags;\n        frame->frame_header.type = type;\n        frame->term_offset = termOffset;\n        frame->session_id = sessionId;\n        frame->stream_id = streamId;\n        frame->term_id = termId;\n        frame->reserved_value = reservedValue;\n\n        uint8_t value = initialPayloadValue;\n        for (size_t i = 0; i < length; i++)\n        {\n            m_fragment[i + AERON_DATA_HEADER_LENGTH] = value++;\n        }\n    }\n\n    static void verifyPayload(const uint8_t *buffer, size_t length)\n    {\n        for (size_t i = 0; i < length; i++)\n        {\n            ASSERT_EQ(*(buffer + i), i % 256);\n        }\n    }\n\n    static aeron_controlled_fragment_handler_action_t fragment_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto test = reinterpret_cast<ControlledFragmentAssemblerTest *>(clientd);\n\n        if (test->m_handler)\n        {\n            return test->m_handler(buffer, length, header);\n        }\n        return AERON_ACTION_BREAK;\n    }\n\n    template<typename F>\n    aeron_controlled_fragment_handler_action_t handle_fragment(F &&handler, size_t length)\n    {\n        m_handler = handler;\n        uint8_t *buffer = m_fragment.data() + AERON_DATA_HEADER_LENGTH;\n        return ::aeron_controlled_fragment_assembler_handler(m_assembler, buffer, length, &m_header);\n    }\n\nprotected:\n    AERON_DECL_ALIGNED(fragment_buffer_t m_fragment, 16) = {};\n    aeron_header_t m_header = {};\n    std::function<aeron_controlled_fragment_handler_action_t(const uint8_t *, size_t, aeron_header_t *)> m_handler = nullptr;\n    aeron_controlled_fragment_assembler_t *m_assembler = nullptr;\n};\n\nTEST_F(ControlledFragmentAssemblerTest, shouldPassThroughUnfragmentedMessage)\n{\n    size_t fragmentLength = 158;\n    fillFrame(AERON_DATA_HEADER_UNFRAGMENTED, 0, fragmentLength, 0);\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        verifyPayload(buffer, length);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ControlledFragmentAssemblerTest, shouldReassembleFromTwoFragments)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t initialTermId = 420;\n    size_t positionBitsToShift = 20;\n    int8_t version = 42;\n    int16_t type = AERON_HDR_TYPE_ERR;\n    int32_t sessionId = 18;\n    int32_t streamId = 31;\n    int32_t termId = initialTermId + 5;\n    int64_t reservedValue = -47923742793L;\n    int32_t termOffset = 0;\n\n    const int64_t startPosition = ::aeron_logbuffer_compute_position(\n        termId, termOffset, positionBitsToShift, initialTermId);\n\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        const int64_t expectedPosition =\n            startPosition + static_cast<int64_t>(::aeron_logbuffer_compute_fragmented_length(length, fragmentLength));\n\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(initialTermId, header_values.initial_term_id);\n        EXPECT_EQ(positionBitsToShift, header_values.position_bits_to_shift);\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length, header_values.frame.frame_length);\n        EXPECT_EQ((uint8_t)(AERON_DATA_HEADER_END_FLAG | AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG), header_values.frame.flags);\n        EXPECT_EQ(version, header_values.frame.version);\n        EXPECT_EQ(type, header_values.frame.type);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(sessionId, header_values.frame.session_id);\n        EXPECT_EQ(streamId, header_values.frame.stream_id);\n        EXPECT_EQ(termId, header_values.frame.term_id);\n        EXPECT_EQ(reservedValue, header_values.frame.reserved_value);\n        EXPECT_EQ(expectedPosition, aeron_header_position(header));\n        verifyPayload(buffer, length);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    m_header.initial_term_id = initialTermId;\n    m_header.position_bits_to_shift = positionBitsToShift;\n    fillFrame(\n        version,\n        (uint8_t)(AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG),\n        type,\n        termOffset,\n        sessionId,\n        streamId,\n        termId,\n        reservedValue,\n        fragmentLength,\n        0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    m_header.initial_term_id = -1;\n    m_header.position_bits_to_shift = -7;\n    fillFrame(\n        AERON_FRAME_HEADER_VERSION,\n        AERON_DATA_HEADER_END_FLAG,\n        AERON_HDR_TYPE_EXT,\n        termOffset,\n        sessionId,\n        -13,\n        -190,\n        63456385384L,\n        fragmentLength,\n        fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ControlledFragmentAssemblerTest, shouldReassembleFromThreeFragments)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    size_t lastFragmentLength = 111;\n    int32_t initialTermId = 420;\n    size_t positionBitsToShift = 20;\n    int8_t version = 3;\n    int16_t type = AERON_HDR_TYPE_RTTM;\n    int32_t streamId = 106;\n    int32_t termId = initialTermId + 5;\n    int64_t reservedValue = 5734957345793759L;\n    int32_t termOffset = 0;\n\n    const int64_t startPosition = ::aeron_logbuffer_compute_position(\n        termId, termOffset, positionBitsToShift, initialTermId);\n\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        const int64_t expectedPosition =\n            startPosition + static_cast<int64_t>(::aeron_logbuffer_compute_fragmented_length(length, fragmentLength));\n\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2 + lastFragmentLength);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length, header_values.frame.frame_length);\n        EXPECT_EQ(0xFF, header_values.frame.flags);\n        EXPECT_EQ(version, header_values.frame.version);\n        EXPECT_EQ(type, header_values.frame.type);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(streamId, header_values.frame.stream_id);\n        EXPECT_EQ(termId, header_values.frame.term_id);\n        EXPECT_EQ(reservedValue, header_values.frame.reserved_value);\n        EXPECT_EQ(expectedPosition, aeron_header_position(header));\n        verifyPayload(buffer, length);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    m_header.initial_term_id = initialTermId;\n    m_header.position_bits_to_shift = positionBitsToShift;\n    fillFrame(\n        version,\n        (uint8_t)(AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG),\n        type,\n        termOffset,\n        SESSION_ID,\n        streamId,\n        termId,\n        reservedValue,\n        fragmentLength,\n        0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(0xE, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(0x7F, termOffset, lastFragmentLength, (fragmentLength * 2) % 256);\n    handle_fragment(handler, lastFragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ControlledFragmentAssemblerTest, shouldNotReassembleIfEndFirstFragment)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        return AERON_ACTION_CONTINUE;\n    };\n\n    fillFrame(AERON_DATA_HEADER_END_FLAG, 0, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(ControlledFragmentAssemblerTest, shouldNotReassembleIfMissingBegin)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        return AERON_ACTION_CONTINUE;\n    };\n\n    fillFrame(0, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(ControlledFragmentAssemblerTest, shouldReassembleTwoMessagesFromFourFrames)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0, messageBeginOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(messageBeginOffset, header_values.frame.term_offset);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n\n    isCalled = false;\n    messageBeginOffset = 2 * MTU_LENGTH;\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n\n    isCalled = false;\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(ControlledFragmentAssemblerTest, shouldAbortReassembly)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        return AERON_ACTION_ABORT;\n    };\n\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_ABORT, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n\n    isCalled = false;\n    EXPECT_EQ(AERON_ACTION_ABORT, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ControlledFragmentAssemblerTest, testHeaderAfterAbortingFragmentedMessage)\n{\n    m_header.initial_term_id = INITIAL_TERM_ID;\n    m_header.position_bits_to_shift = POSITION_BITS_TO_SHIFT;\n\n    uint64_t callCount = 0;\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        callCount++;\n        EXPECT_EQ(length, fragmentLength * 2);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(ACTIVE_TERM_ID, header_values.frame.term_id);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(INITIAL_TERM_ID, header->initial_term_id);\n        EXPECT_EQ(POSITION_BITS_TO_SHIFT, header->position_bits_to_shift);\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 2 * fragmentLength, header_values.frame.frame_length);\n        EXPECT_EQ(((ACTIVE_TERM_ID - INITIAL_TERM_ID) * TERM_LENGTH) + 2 * MTU_LENGTH, aeron_header_position(header));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(AERON_DATA_HEADER_UNFRAGMENTED, header_values.frame.flags & AERON_DATA_HEADER_UNFRAGMENTED);\n        return AERON_ACTION_ABORT;\n    };\n\n    int32_t termOffset = 0;\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_ABORT, handle_fragment(handler, fragmentLength));\n    EXPECT_EQ(AERON_ACTION_ABORT, handle_fragment(handler, fragmentLength));\n\n    EXPECT_EQ(2, callCount);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_controlled_image_fragment_assembler_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeronc.h\"\n#include \"aeron_image.h\"\n}\n\n#define STREAM_ID (999)\n#define SESSION_ID (-100)\n#define TERM_LENGTH (AERON_LOGBUFFER_TERM_MIN_LENGTH)\n#define INITIAL_TERM_ID (3)\n#define ACTIVE_TERM_ID  (INITIAL_TERM_ID + 4)\n#define POSITION_BITS_TO_SHIFT (aeron_number_of_trailing_zeroes(TERM_LENGTH))\n#define MTU_LENGTH (192)\n\ntypedef std::array<uint8_t, TERM_LENGTH> fragment_buffer_t;\n\nclass ControlledImageFragmentAssemblerTest : public testing::Test\n{\npublic:\n    ControlledImageFragmentAssemblerTest()\n    {\n        m_fragment.fill(0);\n        m_header.frame = (aeron_data_header_t *)m_fragment.data();\n        m_header.context = (void*)this;\n\n        if (aeron_image_controlled_fragment_assembler_create(&m_assembler, fragment_handler, this) < 0)\n        {\n            throw std::runtime_error(\"could not create aeron_fragment_assembler_create: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    ~ControlledImageFragmentAssemblerTest() override\n    {\n        aeron_image_controlled_fragment_assembler_delete(m_assembler);\n    }\n\n    void fillFrame(uint8_t flags, int32_t offset, size_t length, uint8_t initialPayloadValue)\n    {\n        fillFrame(\n            AERON_FRAME_HEADER_VERSION,\n            flags,\n            AERON_HDR_TYPE_DATA,\n            offset,\n            SESSION_ID,\n            STREAM_ID,\n            ACTIVE_TERM_ID,\n            0,\n            length,\n            initialPayloadValue);\n    }\n\n    void fillFrame(\n        int8_t version,\n        uint8_t flags,\n        int16_t type,\n        int32_t termOffset,\n        int32_t sessionId,\n        int32_t streamId,\n        int32_t termId,\n        int64_t reservedValue,\n        size_t length,\n        uint8_t initialPayloadValue)\n    {\n        auto frame = (aeron_data_header_t *)m_fragment.data();\n\n        frame->frame_header.frame_length = (int32_t)(AERON_DATA_HEADER_LENGTH + length);\n        frame->frame_header.version = version;\n        frame->frame_header.flags = flags;\n        frame->frame_header.type = type;\n        frame->term_offset = termOffset;\n        frame->session_id = sessionId;\n        frame->stream_id = streamId;\n        frame->term_id = termId;\n        frame->reserved_value = reservedValue;\n\n        uint8_t value = initialPayloadValue;\n        for (size_t i = 0; i < length; i++)\n        {\n            m_fragment[i + AERON_DATA_HEADER_LENGTH] = value++;\n        }\n    }\n\n    static void verifyPayload(const uint8_t *buffer, size_t length)\n    {\n        for (size_t i = 0; i < length; i++)\n        {\n            ASSERT_EQ(*(buffer + i), i % 256);\n        }\n    }\n\n    static aeron_controlled_fragment_handler_action_t fragment_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto test = reinterpret_cast<ControlledImageFragmentAssemblerTest *>(clientd);\n\n        if (test->m_handler)\n        {\n            return test->m_handler(buffer, length, header);\n        }\n        return AERON_ACTION_BREAK;\n    }\n\n    template<typename F>\n    aeron_controlled_fragment_handler_action_t handle_fragment(F &&handler, size_t length)\n    {\n        m_handler = handler;\n        uint8_t *buffer = m_fragment.data() + AERON_DATA_HEADER_LENGTH;\n        return ::aeron_image_controlled_fragment_assembler_handler(m_assembler, buffer, length, &m_header);\n    }\n\nprotected:\n    AERON_DECL_ALIGNED(fragment_buffer_t m_fragment, 16) = {};\n    aeron_header_t m_header = {};\n    std::function<aeron_controlled_fragment_handler_action_t(const uint8_t *, size_t, aeron_header_t *)> m_handler = nullptr;\n    aeron_image_controlled_fragment_assembler_t *m_assembler = nullptr;\n};\n\nTEST_F(ControlledImageFragmentAssemblerTest, shouldPassThroughUnfragmentedMessage)\n{\n    size_t fragmentLength = 158;\n    fillFrame(AERON_DATA_HEADER_UNFRAGMENTED, 0, fragmentLength, 0);\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        verifyPayload(buffer, length);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ControlledImageFragmentAssemblerTest, shouldReassembleFromTwoFragments)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t initialTermId = 420;\n    size_t positionBitsToShift = 20;\n    int8_t version = 42;\n    int16_t type = AERON_HDR_TYPE_ERR;\n    int32_t sessionId = 18;\n    int32_t streamId = 31;\n    int32_t termId = initialTermId + 5;\n    int64_t reservedValue = -47923742793L;\n    int32_t termOffset = 0;\n\n    const int64_t startPosition = ::aeron_logbuffer_compute_position(\n        termId, termOffset, positionBitsToShift, initialTermId);\n\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        const int64_t expectedPosition =\n            startPosition + static_cast<int64_t>(::aeron_logbuffer_compute_fragmented_length(length, fragmentLength));\n\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(initialTermId, header_values.initial_term_id);\n        EXPECT_EQ(positionBitsToShift, header_values.position_bits_to_shift);\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length, header_values.frame.frame_length);\n        EXPECT_EQ((uint8_t)(AERON_DATA_HEADER_END_FLAG | AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG), header_values.frame.flags);\n        EXPECT_EQ(version, header_values.frame.version);\n        EXPECT_EQ(type, header_values.frame.type);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(sessionId, header_values.frame.session_id);\n        EXPECT_EQ(streamId, header_values.frame.stream_id);\n        EXPECT_EQ(termId, header_values.frame.term_id);\n        EXPECT_EQ(reservedValue, header_values.frame.reserved_value);\n        EXPECT_EQ(expectedPosition, aeron_header_position(header));\n        verifyPayload(buffer, length);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    m_header.initial_term_id = initialTermId;\n    m_header.position_bits_to_shift = positionBitsToShift;\n    fillFrame(\n        version,\n        (uint8_t)(AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG),\n        type,\n        termOffset,\n        sessionId,\n        streamId,\n        termId,\n        reservedValue,\n        fragmentLength,\n        0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    m_header.initial_term_id = -1;\n    m_header.position_bits_to_shift = -7;\n    fillFrame(\n        AERON_FRAME_HEADER_VERSION,\n        AERON_DATA_HEADER_END_FLAG,\n        AERON_HDR_TYPE_EXT,\n        termOffset,\n        sessionId,\n        -13,\n        -190,\n        63456385384L,\n        fragmentLength,\n        fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ControlledImageFragmentAssemblerTest, shouldReassembleFromThreeFragments)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    size_t lastFragmentLength = 111;\n    int32_t initialTermId = 420;\n    size_t positionBitsToShift = 20;\n    int8_t version = 3;\n    int16_t type = AERON_HDR_TYPE_RTTM;\n    int32_t streamId = 106;\n    int32_t termId = initialTermId + 5;\n    int64_t reservedValue = 5734957345793759L;\n    int32_t termOffset = 0;\n\n    const int64_t startPosition = ::aeron_logbuffer_compute_position(\n        termId, termOffset, positionBitsToShift, initialTermId);\n\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        const int64_t expectedPosition =\n            startPosition + static_cast<int64_t>(::aeron_logbuffer_compute_fragmented_length(length, fragmentLength));\n\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2 + lastFragmentLength);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length, header_values.frame.frame_length);\n        EXPECT_EQ(0xFF, header_values.frame.flags);\n        EXPECT_EQ(version, header_values.frame.version);\n        EXPECT_EQ(type, header_values.frame.type);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(streamId, header_values.frame.stream_id);\n        EXPECT_EQ(termId, header_values.frame.term_id);\n        EXPECT_EQ(reservedValue, header_values.frame.reserved_value);\n        EXPECT_EQ(expectedPosition, aeron_header_position(header));\n        verifyPayload(buffer, length);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    m_header.initial_term_id = initialTermId;\n    m_header.position_bits_to_shift = positionBitsToShift;\n    fillFrame(\n        version,\n        (uint8_t)(AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG),\n        type,\n        termOffset,\n        SESSION_ID,\n        streamId,\n        termId,\n        reservedValue,\n        fragmentLength,\n        0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(0xE, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(0x7F, termOffset, lastFragmentLength, (fragmentLength * 2) % 256);\n    handle_fragment(handler, lastFragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ControlledImageFragmentAssemblerTest, shouldNotReassembleIfEndFirstFragment)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        return AERON_ACTION_CONTINUE;\n    };\n\n    fillFrame(AERON_DATA_HEADER_END_FLAG, 0, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(ControlledImageFragmentAssemblerTest, shouldNotReassembleIfMissingBegin)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        return AERON_ACTION_CONTINUE;\n    };\n\n    fillFrame(0, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(ControlledImageFragmentAssemblerTest, shouldReassembleTwoMessagesFromFourFrames)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0, messageBeginOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(messageBeginOffset, header_values.frame.term_offset);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n\n    isCalled = false;\n    messageBeginOffset = 2 * MTU_LENGTH;\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n\n    isCalled = false;\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(ControlledImageFragmentAssemblerTest, shouldAbortReassembly)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        return AERON_ACTION_ABORT;\n    };\n\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_ABORT, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n\n    isCalled = false;\n    EXPECT_EQ(AERON_ACTION_ABORT, handle_fragment(handler, fragmentLength));\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ControlledImageFragmentAssemblerTest, testHeaderAfterAbortingFragmentedMessage)\n{\n    m_header.initial_term_id = INITIAL_TERM_ID;\n    m_header.position_bits_to_shift = POSITION_BITS_TO_SHIFT;\n\n    uint64_t callCount = 0;\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        callCount++;\n        EXPECT_EQ(length, fragmentLength * 2);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(ACTIVE_TERM_ID, header_values.frame.term_id);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(INITIAL_TERM_ID, header->initial_term_id);\n        EXPECT_EQ(POSITION_BITS_TO_SHIFT, header->position_bits_to_shift);\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 2 * fragmentLength, header_values.frame.frame_length);\n        EXPECT_EQ(((ACTIVE_TERM_ID - INITIAL_TERM_ID) * TERM_LENGTH) + 2 * MTU_LENGTH, aeron_header_position(header));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(AERON_DATA_HEADER_UNFRAGMENTED, header_values.frame.flags & AERON_DATA_HEADER_UNFRAGMENTED);\n        return AERON_ACTION_ABORT;\n    };\n\n    int32_t termOffset = 0;\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    EXPECT_EQ(AERON_ACTION_CONTINUE, handle_fragment(handler, fragmentLength));\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    EXPECT_EQ(AERON_ACTION_ABORT, handle_fragment(handler, fragmentLength));\n    EXPECT_EQ(AERON_ACTION_ABORT, handle_fragment(handler, fragmentLength));\n\n    EXPECT_EQ(2, callCount);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_exclusive_publication_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <exception>\n#include <string>\n\n#include <gtest/gtest.h>\n\n#include \"aeron_client_test_utils.h\"\n\nextern \"C\"\n{\n#include \"aeron_log_buffer.h\"\n#include \"aeron_exclusive_publication.h\"\n#include \"aeron_counters.h\"\n}\n\n#define PAGE_SIZE (4 * 1024)\n#define MTU_LENGTH (4 * 1024)\n#define TERM_LENGTH (1024 * 1024)\n#define MAX_MESSAGE_SIZE (TERM_LENGTH >> 3)\n#define MAX_PAYLOAD_SIZE (MTU_LENGTH - AERON_DATA_HEADER_LENGTH)\n\n#define PUB_URI \"aeron:udp?endpoint=localhost:12345|alias=test\"\n#define STREAM_ID (101)\n#define SESSION_ID (110)\n#define REGISTRATION_ID (27)\n#define CHANNEL_STATUS_INDICATOR_ID (45)\n#define SUBSCRIBER_POSITION_ID (49)\n\nusing namespace aeron::test;\n\nclass ExclusivePublicationTest : public testing::Test\n{\npublic:\n    aeron_log_buffer_t *createLogBuffer()\n    {\n        m_filename = tempFileName();\n        aeron_log_buffer_t *log_buffer = nullptr;\n        createLogFile(m_filename, TERM_LENGTH, INITIAL_TERM_ID);\n\n        if (aeron_log_buffer_create(&log_buffer, m_filename.c_str(), 1, false) < 0)\n        {\n            throw std::runtime_error(\"could not create log_buffer: \" + std::string(aeron_errmsg()));\n        }\n\n        log_buffer->mapped_raw_log.term_length = TERM_LENGTH;\n        auto *log_meta_data = (aeron_logbuffer_metadata_t *)log_buffer->mapped_raw_log.log_meta_data.addr;\n        log_meta_data->term_length = TERM_LENGTH;\n        log_meta_data->mtu_length = MTU_LENGTH;\n        log_meta_data->initial_term_id = INITIAL_TERM_ID;\n        log_meta_data->page_size = PAGE_SIZE;\n        log_meta_data->is_connected = true;\n\n        return log_buffer;\n    }\n\n    static aeron_exclusive_publication_t *createPublication(\n        aeron_client_conductor_t *conductor,\n        aeron_log_buffer_t *log_buffer,\n        int32_t position_limit_counter_id,\n        int64_t *position_limit_addr,\n        int32_t channel_status_indicator_id,\n        int64_t *channel_status_addr)\n    {\n        aeron_exclusive_publication_t *publication = nullptr;\n\n        if (aeron_exclusive_publication_create(\n            &publication,\n            conductor,\n            ::strdup(PUB_URI),\n            STREAM_ID,\n            SESSION_ID,\n            position_limit_counter_id,\n            position_limit_addr,\n            channel_status_indicator_id,\n            channel_status_addr,\n            log_buffer,\n            REGISTRATION_ID,\n            REGISTRATION_ID) < 0)\n        {\n            throw std::runtime_error(\"could not create publication: \" + std::string(aeron_errmsg()));\n        }\n\n        return publication;\n    }\n\n    static int64_t packTail(int32_t term_id, int32_t term_offset)\n    {\n        return ((int64_t)term_id << 32) | term_offset;\n    }\n\n    static int64_t reserved_value_supplier(void *clientd, uint8_t *buffer, size_t frame_length)\n    {\n        return (int64_t)frame_length * 19;\n    }\n\n    aeron_data_header_t *verifyHeader(\n        const aeron_mapped_buffer_t *term_buffer,\n        const int32_t term_offset,\n        const int32_t expected_frame_length,\n        const int32_t expected_term_id,\n        const uint8_t expected_flags)\n    {\n        auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n        EXPECT_EQ(expected_frame_length, header->frame_header.frame_length);\n        EXPECT_EQ(AERON_HDR_TYPE_DATA, header->frame_header.type);\n        EXPECT_EQ(AERON_FRAME_HEADER_VERSION, header->frame_header.version);\n        EXPECT_EQ(expected_flags, header->frame_header.flags);\n        EXPECT_EQ(term_offset, header->term_offset);\n        EXPECT_EQ(expected_term_id, header->term_id);\n        EXPECT_EQ(m_publication->session_id, header->session_id);\n        EXPECT_EQ(m_publication->stream_id, header->stream_id);\n        return header;\n    }\n\nprotected:\n    aeron_client_conductor_t *m_conductor = nullptr;\n    aeron_log_buffer_t *m_log_buffer = nullptr;\n    aeron_logbuffer_metadata_t *m_log_meta_data = nullptr;\n    aeron_exclusive_publication_t *m_publication = nullptr;\n    std::string m_filename;\n\n    int64_t *m_position_limit_addr = nullptr;\n    int64_t *m_channel_status_addr = nullptr;\n\n    static const size_t NUM_COUNTERS = 4;\n    std::array<std::uint8_t, NUM_COUNTERS * AERON_COUNTERS_MANAGER_METADATA_LENGTH> m_counters_metadata = {};\n    std::array<std::uint8_t, NUM_COUNTERS * AERON_COUNTERS_MANAGER_VALUE_LENGTH> m_counters_values = {};\n    aeron_counters_manager_t m_counters_manager = {};\n    aeron_clock_cache_t m_cached_clock = {};\n\n    void SetUp() override\n    {\n        m_counters_metadata.fill(0);\n        m_counters_values.fill(0);\n        aeron_counters_manager_init(\n            &m_counters_manager,\n            m_counters_metadata.data(),\n            m_counters_metadata.size(),\n            m_counters_values.data(),\n            m_counters_values.size(),\n            &m_cached_clock,\n            0);\n\n        m_conductor = {};\n        m_log_buffer = createLogBuffer();\n        m_log_meta_data = reinterpret_cast<aeron_logbuffer_metadata_t *>(m_log_buffer->mapped_raw_log.log_meta_data.addr);\n    }\n\n    void createPublication(int32_t term_count, int32_t term_offset, int32_t term_id = -1)\n    {\n        if (-1 == term_id)\n        {\n            term_id = m_log_meta_data->initial_term_id + term_count;\n        }\n\n        const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n\n        m_log_meta_data->active_term_count = term_count;\n        m_log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n\n        const int32_t position_limit_counter_id = aeron_counters_manager_allocate(\n            &m_counters_manager,\n            AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID,\n            nullptr,\n            0,\n            AERON_COUNTER_PUBLISHER_LIMIT_NAME,\n            sizeof(AERON_COUNTER_PUBLISHER_LIMIT_NAME));\n\n        if (position_limit_counter_id < 0)\n        {\n            throw std::runtime_error(\"could not create counter: \" + std::string(aeron_errmsg()));\n        }\n        m_position_limit_addr = aeron_counters_manager_addr(&m_counters_manager, position_limit_counter_id);\n        aeron_counter_set_release(m_position_limit_addr, INT64_MAX);\n\n        const int32_t channel_status_indicator_id = aeron_counters_manager_allocate(\n            &m_counters_manager,\n            AERON_COUNTER_SEND_CHANNEL_STATUS_TYPE_ID,\n            nullptr,\n            0,\n            AERON_COUNTER_SEND_CHANNEL_STATUS_NAME,\n            sizeof(AERON_COUNTER_SEND_CHANNEL_STATUS_NAME));\n        if (channel_status_indicator_id < 0)\n        {\n            throw std::runtime_error(\"could not create counter: \" + std::string(aeron_errmsg()));\n        }\n        m_channel_status_addr = aeron_counters_manager_addr(&m_counters_manager, channel_status_indicator_id);\n\n        m_publication = createPublication(\n            m_conductor,\n            m_log_buffer,\n            position_limit_counter_id,\n            m_position_limit_addr,\n            channel_status_indicator_id,\n            m_channel_status_addr);\n    }\n\n    void TearDown() override\n    {\n        if (nullptr != m_publication)\n        {\n            aeron_exclusive_publication_delete(m_publication);\n        }\n        if (!m_filename.empty())\n        {\n            if (nullptr != m_log_buffer)\n            {\n                aeron_log_buffer_delete(m_log_buffer);\n            }\n\n            ::unlink(m_filename.c_str());\n        }\n        aeron_counters_manager_close(&m_counters_manager);\n    }\n};\n\nTEST_F(ExclusivePublicationTest, offerUnfragmentedMessage)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(16781376, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    const int32_t frame_length = static_cast<int32_t>(length) + static_cast<int32_t>(AERON_DATA_HEADER_LENGTH);\n    const auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        frame_length,\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ((int64_t)frame_length * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, payload, length));\n}\n\nTEST_F(ExclusivePublicationTest, offerFragmentedMessage)\n{\n    uint8_t msgBuffer[MAX_MESSAGE_SIZE];\n    memset(msgBuffer, 'x', sizeof(msgBuffer));\n    memset(msgBuffer + MAX_MESSAGE_SIZE / 2, 'a', MAX_MESSAGE_SIZE / 2);\n    const size_t length = MAX_MESSAGE_SIZE;\n    const int32_t term_count = 5;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 512;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        msgBuffer,\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(5375520, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    // first frame\n    auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        static_cast<int32_t>(MTU_LENGTH),\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG);\n    EXPECT_EQ((int64_t)MTU_LENGTH * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, msgBuffer, MTU_LENGTH - AERON_DATA_HEADER_LENGTH));\n\n    // last frame\n    const int32_t last_frame_offset = term_offset + (MAX_MESSAGE_SIZE / MAX_PAYLOAD_SIZE) * MTU_LENGTH;\n    const size_t last_frame_length = AERON_ALIGN((MAX_MESSAGE_SIZE % MAX_PAYLOAD_SIZE) + AERON_DATA_HEADER_LENGTH,\n        AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const size_t last_data_chunk_length = last_frame_length - AERON_DATA_HEADER_LENGTH;\n    header = verifyHeader(\n        term_buffer,\n        last_frame_offset,\n        static_cast<int32_t>(last_frame_length),\n        term_id,\n        AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ((int64_t)last_frame_length * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + last_frame_offset + AERON_DATA_HEADER_LENGTH,\n        msgBuffer + (MAX_MESSAGE_SIZE - last_data_chunk_length), last_data_chunk_length));\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferUnfragmentedMessage)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    const size_t length = strlen(payload);\n    aeron_iovec_t iov[2];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = 5;\n    iov[1].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload)) + 5;\n    iov[1].iov_len = length - 5;\n\n    const int32_t term_count = 113;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 3072;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        iov,\n        2,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(118492224, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    const int32_t frame_length = static_cast<int32_t>(length) + static_cast<int32_t>(AERON_DATA_HEADER_LENGTH);\n    const auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        frame_length,\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ((int64_t)frame_length * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, payload, length));\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferFragmentedMessage)\n{\n    uint8_t msgBuffer1[111];\n    uint8_t msgBuffer2[222];\n    uint8_t msgBuffer3[MAX_MESSAGE_SIZE - sizeof(msgBuffer1) - sizeof(msgBuffer2)];\n    memset(msgBuffer1, '1', sizeof(msgBuffer1));\n    memset(msgBuffer2, '2', sizeof(msgBuffer2));\n    memset(msgBuffer3, 'x', sizeof(msgBuffer3));\n    aeron_iovec_t iov[3];\n    iov[0].iov_base = msgBuffer1;\n    iov[0].iov_len = sizeof(msgBuffer1);\n    iov[1].iov_base = msgBuffer2;\n    iov[1].iov_len = sizeof(msgBuffer2);\n    iov[2].iov_base = msgBuffer3;\n    iov[2].iov_len = sizeof(msgBuffer3);\n\n    const int32_t term_count = 591;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 8192;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        iov,\n        3,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(619848736, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    // first frame\n    auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        static_cast<int32_t>(MTU_LENGTH),\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG);\n    EXPECT_EQ((int64_t)MTU_LENGTH * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, msgBuffer1, sizeof(msgBuffer1)));\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH + sizeof(msgBuffer1),\n        msgBuffer2,\n        sizeof(msgBuffer2)));\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH + sizeof(msgBuffer1) + sizeof(msgBuffer2),\n        msgBuffer3,\n        MAX_PAYLOAD_SIZE - (sizeof(msgBuffer1) + sizeof(msgBuffer2))));\n\n    // last frame\n    const int32_t last_frame_offset = term_offset + (MAX_MESSAGE_SIZE / MAX_PAYLOAD_SIZE) * MTU_LENGTH;\n    const size_t last_frame_length = AERON_ALIGN((MAX_MESSAGE_SIZE % MAX_PAYLOAD_SIZE) + AERON_DATA_HEADER_LENGTH,\n        AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const size_t last_data_chunk_length = last_frame_length - AERON_DATA_HEADER_LENGTH;\n    header = verifyHeader(\n        term_buffer,\n        last_frame_offset,\n        static_cast<int32_t>(last_frame_length),\n        term_id,\n        AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ((int64_t)last_frame_length * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + last_frame_offset + AERON_DATA_HEADER_LENGTH,\n        msgBuffer3 + (sizeof(msgBuffer3) - last_data_chunk_length), last_data_chunk_length));\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimMaxPayloadSize)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const int32_t term_count = 3;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 96;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        m_publication,\n        MAX_PAYLOAD_SIZE,\n        &buffer_claim);\n\n    ASSERT_EQ(3149920, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    const auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        -static_cast<int32_t>(MTU_LENGTH),\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ(AERON_DATA_HEADER_DEFAULT_RESERVED_VALUE, header->reserved_value);\n    EXPECT_NE(nullptr, buffer_claim.frame_header);\n    EXPECT_EQ(buffer_claim.frame_header + AERON_DATA_HEADER_LENGTH, buffer_claim.data);\n    EXPECT_EQ(MAX_PAYLOAD_SIZE, buffer_claim.length);\n}\n\nTEST_F(ExclusivePublicationTest, offerErrorIfPublicationIsNull)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n\n    createPublication(0, 0);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        nullptr,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, offerErrorIfBufferIsNull)\n{\n    createPublication(0, 0);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        nullptr,\n        10,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, offerClosed)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    createPublication(term_count, term_offset);\n\n    m_publication->is_closed = true;\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_CLOSED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\n// TEST_F(ExclusivePublicationTest, offerAdminActionIfTermCountDoesNotMatch)\n// {\n//     const char *payload = \"Aeron is awesome!\";\n//     const size_t length = strlen(payload);\n//     const int32_t term_offset = 4096;\n//     const int32_t term_count = 16;\n//     createPublication(term_count, term_offset);\n//\n//     const int64_t position = aeron_exclusive_publication_offer(\n//         m_publication,\n//         reinterpret_cast<const uint8_t *>(payload),\n//         length,\n//         reserved_value_supplier,\n//         nullptr);\n//\n//     ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n//     const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n//     auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n//     EXPECT_EQ(0, header->frame_header.frame_length);\n//     EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n// }\n\nTEST_F(ExclusivePublicationTest, offerBackPressureIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_BACK_PRESSURED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, offerNotConnectedIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = 3;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 1024;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n    m_publication->log_meta_data->is_connected = false;\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset - 32, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_NOT_CONNECTED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, offerMaxPositionExceededIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = INT32_MAX;\n    const auto term_offset = (int32_t)(TERM_LENGTH - length - 1);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n}\n\nTEST_F(ExclusivePublicationTest, offerPublicationErrorIfMessageIsLargerThanMaxMessageSize)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const int32_t term_count = 5;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)TERM_LENGTH;\n    const int32_t term_id = term_count + INITIAL_TERM_ID;\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        MAX_MESSAGE_SIZE + 1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, offerAdminActionAfterRolloingOverToTheNextTerm)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = 51;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const size_t next_partition_index = aeron_logbuffer_index_by_term_count(term_count + 1);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = term_count + INITIAL_TERM_ID;\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n    EXPECT_EQ(\n        packTail(term_id + 1, 0),\n        m_publication->log_meta_data->term_tail_counters[next_partition_index]);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 8, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, offerMaxPositionExceededAfterSuccessfulSpaceClaim)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferErrorIfPublicationIsNull)\n{\n    createPublication(0, 0);\n\n    aeron_iovec_t iov[1];\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        nullptr,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferErrorIfBufferIsNull)\n{\n    createPublication(0, 0);\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        nullptr,\n        10,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferClosed)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    createPublication(term_count, term_offset);\n\n    m_publication->is_closed = true;\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_CLOSED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\n// TEST_F(ExclusivePublicationTest, vectorOfferAdminActionIfTermCountDoesNotMatch)\n// {\n//     const char *payload = \"Aeron is awesome squared!\";\n//     aeron_iovec_t iov[1];\n//     iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n//     iov[0].iov_len = strlen(payload);\n//     const int32_t term_offset = 4096;\n//     m_publication->log_meta_data->active_term_count = 5;\n//\n//     const int64_t position = aeron_exclusive_publication_offerv(\n//         m_publication,\n//         iov,\n//         1,\n//         reserved_value_supplier,\n//         nullptr);\n//\n//     ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n//     const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n//     auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n//     EXPECT_EQ(0, header->frame_header.frame_length);\n//     EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n// }\n\nTEST_F(ExclusivePublicationTest, vectorOfferBackPressureIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_BACK_PRESSURED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferNotConnectedIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = strlen(payload);\n    const int32_t term_count = 3;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 1024;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n    m_publication->log_meta_data->is_connected = false;\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset - 32, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_NOT_CONNECTED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferMaxPositionExceededIfPublicationLimitReached)\n{\n    const char *payload = \"Test, test, test.\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = strlen(payload);\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    createPublication(term_count, term_offset, term_id);\n    aeron_counter_set_release(m_position_limit_addr, 64);\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferPublicationErrorIfMessageIsLargerThanMaxMessageSize)\n{\n    const char *payload = \"Aeron is awesome vector!\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = MAX_MESSAGE_SIZE + 1;\n    const int32_t term_count = 5;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)TERM_LENGTH;\n    const int32_t term_id = term_count + INITIAL_TERM_ID;\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferAdminActionAfterRolloingOverToTheNextTerm)\n{\n    const char *payload = \"This does not fit into the end of the buffer...\";\n    const size_t length = strlen(payload);\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = length;\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = 51;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const size_t next_partition_index = aeron_logbuffer_index_by_term_count(term_count + 1);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = term_count + INITIAL_TERM_ID;\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n    EXPECT_EQ(\n        packTail(term_id + 1, 0),\n        m_publication->log_meta_data->term_tail_counters[next_partition_index]);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 8, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, vectorOfferMaxPositionExceededAfterSuccessfulSpaceClaim)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    const size_t length = strlen(payload);\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = length;\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimErrorIfPublicationIsNull)\n{\n    createPublication(0, 0);\n\n    aeron_buffer_claim_t buffer_claim = {};\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        nullptr,\n        1,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimErrorIfBufferIsNull)\n{\n    createPublication(0, 0);\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        m_publication,\n        1,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimClosed)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 5;\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    createPublication(term_count, term_offset);\n    m_publication->is_closed = true;\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_CLOSED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimBackPressureIfPublicationLimitReached)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 5;\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_BACK_PRESSURED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimNotConnectedIfPublicationLimitReached)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 5;\n    const int32_t term_count = 3;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 1024;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n    m_publication->log_meta_data->is_connected = false;\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset - 32, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_NOT_CONNECTED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimMaxPositionExceededIfPublicationLimitReached)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 5;\n    const int32_t term_count = INT32_MAX;\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimPublicationErrorIfMessageIsLargerThanMaxPayloadSize)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const int32_t term_count = 5;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)TERM_LENGTH;\n    const int32_t term_id = term_count + (int32_t)INITIAL_TERM_ID;\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        m_publication,\n        MAX_PAYLOAD_SIZE + 1,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimAdminActionAfterRolloingOverToTheNextTerm)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 55;\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = 51;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const size_t next_partition_index = aeron_logbuffer_index_by_term_count(term_count + 1);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = term_count + (int32_t)INITIAL_TERM_ID;\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n    EXPECT_EQ(\n        packTail(term_id + 1, 0),\n        m_publication->log_meta_data->term_tail_counters[next_partition_index]);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 8, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(ExclusivePublicationTest, tryClaimMaxPositionExceededAfterSuccessfulSpaceClaim)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 19;\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    createPublication(term_count, term_offset, term_id);\n\n    const int64_t position = aeron_exclusive_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n}\n\n// Tests for aeron_exclusive_publication_offer_block\n// These tests verify the bug fix in PR #1943 where offer_block was writing\n// to offset 0 regardless of the term_offset parameter in the pre-formatted block\n\nTEST_F(ExclusivePublicationTest, offerBlockUnfragmentedMessage)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = 10;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 1024;\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, term_offset);\n\n    // Pre-format the block as caller would do\n    std::vector<uint8_t> block(AERON_DATA_HEADER_LENGTH + length);\n    aeron_data_header_t *header = (aeron_data_header_t *)block.data();\n    header->frame_header.frame_length = AERON_DATA_HEADER_LENGTH + length;\n    header->frame_header.type = AERON_HDR_TYPE_DATA;\n    header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    header->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n    header->term_offset = term_offset;\n    header->term_id = term_id;\n    header->session_id = m_publication->session_id;\n    header->stream_id = m_publication->stream_id;\n    header->reserved_value = 42;\n    memcpy(block.data() + AERON_DATA_HEADER_LENGTH, payload, length);\n\n    const int64_t position = aeron_exclusive_publication_offer_block(\n        m_publication,\n        block.data(),\n        AERON_DATA_HEADER_LENGTH + length);\n\n    const int32_t frame_length = static_cast<int32_t>(AERON_DATA_HEADER_LENGTH + length);\n    ASSERT_EQ(aeron_logbuffer_compute_position(\n        term_id, term_offset + frame_length, m_publication->position_bits_to_shift, m_publication->initial_term_id),\n        position);\n\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *written_header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(frame_length, written_header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_DATA, written_header->frame_header.type);\n    EXPECT_EQ(term_offset, written_header->term_offset);\n    EXPECT_EQ(term_id, written_header->term_id);\n    EXPECT_EQ(0, memcmp(term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, payload, length));\n}\n\nTEST_F(ExclusivePublicationTest, offerBlockMultipleBlocksInSameTerm)\n{\n    // This test specifically verifies the PR #1943 bug fix:\n    // When offering multiple blocks in the same term with increasing offsets,\n    // they should NOT overwrite each other.\n\n    const char *payload1 = \"Aeron is awesome!\";\n    const char *payload2 = \"Aeron is indeed awesome!\";\n    const size_t length1 = strlen(payload1);\n    const size_t length2 = strlen(payload2);\n\n    const int32_t term_count = 7;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    const int32_t initial_offset = 512;\n    createPublication(term_count, initial_offset);\n\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n\n    // === Offer first block ===\n    std::vector<uint8_t> block1(AERON_DATA_HEADER_LENGTH + length1);\n    aeron_data_header_t *header1 = (aeron_data_header_t *)block1.data();\n    header1->frame_header.frame_length = AERON_DATA_HEADER_LENGTH + length1;\n    header1->frame_header.type = AERON_HDR_TYPE_DATA;\n    header1->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    header1->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n    header1->term_offset = initial_offset;\n    header1->term_id = term_id;\n    header1->session_id = m_publication->session_id;\n    header1->stream_id = m_publication->stream_id;\n    header1->reserved_value = 111;\n    memcpy(block1.data() + AERON_DATA_HEADER_LENGTH, payload1, length1);\n\n    const int64_t position1 = aeron_exclusive_publication_offer_block(\n        m_publication,\n        block1.data(),\n        AERON_DATA_HEADER_LENGTH + length1);\n\n    ASSERT_GT(position1, 0);\n\n    // Verify first block is written at correct offset\n    auto *written_header1 = (aeron_data_header_t *)(term_buffer->addr + initial_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length1, written_header1->frame_header.frame_length);\n    EXPECT_EQ(0, memcmp(term_buffer->addr + initial_offset + AERON_DATA_HEADER_LENGTH, payload1, length1));\n\n    // === Offer second block ===\n    int32_t second_offset = initial_offset + AERON_ALIGN(AERON_DATA_HEADER_LENGTH + length1, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    // Update publication's term_offset to match second_offset for next write\n    m_publication->term_offset = second_offset;\n\n    std::vector<uint8_t> block2(AERON_DATA_HEADER_LENGTH + length2);\n    aeron_data_header_t *header2 = (aeron_data_header_t *)block2.data();\n    header2->frame_header.frame_length = AERON_DATA_HEADER_LENGTH + length2;\n    header2->frame_header.type = AERON_HDR_TYPE_DATA;\n    header2->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    header2->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n    header2->term_offset = second_offset;  // Different offset!\n    header2->term_id = term_id;\n    header2->session_id = m_publication->session_id;\n    header2->stream_id = m_publication->stream_id;\n    header2->reserved_value = 222;\n    memcpy(block2.data() + AERON_DATA_HEADER_LENGTH, payload2, length2);\n\n    const int64_t position2 = aeron_exclusive_publication_offer_block(\n        m_publication,\n        block2.data(),\n        AERON_DATA_HEADER_LENGTH + length2);\n\n    ASSERT_GT(position2, position1);\n\n    // === Verify both blocks are intact (NOT overwritten) ===\n    // Without PR #1943 fix, block2 would be written to offset 0,\n    // overwriting block1, and block1 would be lost.\n\n    // Check first block is STILL at correct offset and NOT overwritten\n    auto *still_header1 = (aeron_data_header_t *)(term_buffer->addr + initial_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length1, still_header1->frame_header.frame_length);\n    EXPECT_EQ(111, still_header1->reserved_value);\n    EXPECT_EQ(0, memcmp(term_buffer->addr + initial_offset + AERON_DATA_HEADER_LENGTH, payload1, length1));\n\n    // Check second block is at correct offset (NOT at offset 0)\n    auto *written_header2 = (aeron_data_header_t *)(term_buffer->addr + second_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length2, written_header2->frame_header.frame_length);\n    EXPECT_EQ(222, written_header2->reserved_value);\n    EXPECT_EQ(0, memcmp(term_buffer->addr + second_offset + AERON_DATA_HEADER_LENGTH, payload2, length2));\n\n    // Verify offset 0 still has original content (not overwritten by block2)\n    EXPECT_EQ(0, memcmp(term_buffer->addr + initial_offset + AERON_DATA_HEADER_LENGTH, payload1, length1));\n}\n\nTEST_F(ExclusivePublicationTest, offerBlockErrorIfBufferIsNull)\n{\n    createPublication(0, 0);\n\n    const int64_t position = aeron_exclusive_publication_offer_block(\n        m_publication,\n        nullptr,\n        256);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n}\n\nTEST_F(ExclusivePublicationTest, offerBlockErrorIfPublicationIsNull)\n{\n    std::vector<uint8_t> block(256);\n    memset(block.data(), 0, block.size());\n\n    const int64_t position = aeron_exclusive_publication_offer_block(\n        nullptr,\n        block.data(),\n        256);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n}\n\nTEST_F(ExclusivePublicationTest, offerBlockClosed)\n{\n    std::vector<uint8_t> block(256);\n    aeron_data_header_t *header = (aeron_data_header_t *)block.data();\n    header->frame_header.frame_length = 256;\n    header->frame_header.type = AERON_HDR_TYPE_DATA;\n    header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    header->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n    header->term_offset = 0;\n    header->term_id = INITIAL_TERM_ID;\n    header->session_id = SESSION_ID;\n    header->stream_id = STREAM_ID;\n    header->reserved_value = 0;\n    memset(block.data() + AERON_DATA_HEADER_LENGTH, 'X', 256 - AERON_DATA_HEADER_LENGTH);\n\n    const int32_t term_count = 2;\n    createPublication(term_count, 0);\n\n    m_publication->is_closed = true;\n\n    const int64_t position = aeron_exclusive_publication_offer_block(\n        m_publication,\n        block.data(),\n        256);\n\n    ASSERT_EQ(AERON_PUBLICATION_CLOSED, position);\n}\n\nTEST_F(ExclusivePublicationTest, offerBlockWithVariousOffsets)\n{\n    // Test that offer_block correctly handles blocks at different offsets\n    const int32_t term_count = 3;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_id = m_log_meta_data->initial_term_id + term_count;\n    createPublication(term_count, 0);\n\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n\n    // Test offsets: 0, 256, 512, 768\n    int32_t test_offsets[] = {\n        0,\n        AERON_DATA_HEADER_LENGTH + 64,\n        2 * (AERON_DATA_HEADER_LENGTH + 64),\n        3 * (AERON_DATA_HEADER_LENGTH + 64)\n    };\n    uint8_t test_data[] = {'A', 'B', 'C', 'D'};\n\n    for (int i = 0; i < 4; i++)\n    {\n        int32_t offset = test_offsets[i];\n        uint8_t data_value = test_data[i];\n\n        std::vector<uint8_t> block(AERON_DATA_HEADER_LENGTH + 64);\n        aeron_data_header_t *header = (aeron_data_header_t *)block.data();\n        header->frame_header.frame_length = AERON_DATA_HEADER_LENGTH + 64;\n        header->frame_header.type = AERON_HDR_TYPE_DATA;\n        header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n        header->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n        header->term_offset = offset;\n        header->term_id = term_id;\n        header->session_id = m_publication->session_id;\n        header->stream_id = m_publication->stream_id;\n        header->reserved_value = (int64_t)data_value;\n        memset(block.data() + AERON_DATA_HEADER_LENGTH, data_value, 64);\n\n        const int64_t position = aeron_exclusive_publication_offer_block(\n            m_publication,\n            block.data(),\n            AERON_DATA_HEADER_LENGTH + 64);\n\n        ASSERT_GT(position, 0) << \"Failed to offer block at offset \" << offset;\n\n        // Verify the block was written at the correct offset\n        auto *written_header = (aeron_data_header_t *)(term_buffer->addr + offset);\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 64, written_header->frame_header.frame_length)\n            << \"Block at offset \" << offset << \" has wrong frame length\";\n        EXPECT_EQ((int64_t)data_value, written_header->reserved_value)\n            << \"Block at offset \" << offset << \" has wrong reserved value\";\n\n        // Verify the payload is all the expected value\n        for (int j = 0; j < 64; j++)\n        {\n            EXPECT_EQ(data_value, *(term_buffer->addr + offset + AERON_DATA_HEADER_LENGTH + j))\n                << \"Block at offset \" << offset << \" payload byte \" << j << \" is incorrect\";\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_fragment_assembler_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeronc.h\"\n#include \"aeron_image.h\"\n}\n\n#define STREAM_ID (10)\n#define SESSION_ID (200)\n#define TERM_LENGTH (AERON_LOGBUFFER_TERM_MIN_LENGTH)\n#define INITIAL_TERM_ID (-1234)\n#define ACTIVE_TERM_ID  (INITIAL_TERM_ID + 5)\n#define POSITION_BITS_TO_SHIFT (aeron_number_of_trailing_zeroes(TERM_LENGTH))\n#define MTU_LENGTH (128)\n\ntypedef std::array<uint8_t, TERM_LENGTH> fragment_buffer_t;\n\nclass CFragmentAssemblerTest : public testing::Test\n{\npublic:\n    CFragmentAssemblerTest()\n    {\n        m_fragment.fill(0);\n        m_header.frame = (aeron_data_header_t *)m_fragment.data();\n        m_header.context = (void*)\"test\";\n\n        if (aeron_fragment_assembler_create(&m_assembler, fragment_handler, this) < 0)\n        {\n            throw std::runtime_error(\"could not create aeron_fragment_assembler_create: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    ~CFragmentAssemblerTest() override\n    {\n        aeron_fragment_assembler_delete(m_assembler);\n    }\n\n    void fillFrame(uint8_t flags, int32_t offset, size_t length, uint8_t initialPayloadValue)\n    {\n        fillFrame(\n            AERON_FRAME_HEADER_VERSION,\n            flags,\n            AERON_HDR_TYPE_DATA,\n            offset,\n            SESSION_ID,\n            STREAM_ID,\n            ACTIVE_TERM_ID,\n            0,\n            length,\n            initialPayloadValue);\n    }\n\n    void fillFrame(\n        int8_t version,\n        uint8_t flags,\n        int16_t type,\n        int32_t termOffset,\n        int32_t sessionId,\n        int32_t streamId,\n        int32_t termId,\n        int64_t reservedValue,\n        size_t length,\n        uint8_t initialPayloadValue)\n    {\n        auto frame = (aeron_data_header_t *)m_fragment.data();\n\n        frame->frame_header.frame_length = (int32_t)(AERON_DATA_HEADER_LENGTH + length);\n        frame->frame_header.version = version;\n        frame->frame_header.flags = flags;\n        frame->frame_header.type = type;\n        frame->term_offset = termOffset;\n        frame->session_id = sessionId;\n        frame->stream_id = streamId;\n        frame->term_id = termId;\n        frame->reserved_value = reservedValue;\n\n        uint8_t value = initialPayloadValue;\n        for (size_t i = 0; i < length; i++)\n        {\n            m_fragment[i + AERON_DATA_HEADER_LENGTH] = value++;\n        }\n    }\n\n    static void verifyPayload(const uint8_t *buffer, size_t length)\n    {\n        for (size_t i = 0; i < length; i++)\n        {\n            ASSERT_EQ(*(buffer + i), i % 256);\n        }\n    }\n\n    static void fragment_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto test = reinterpret_cast<CFragmentAssemblerTest *>(clientd);\n\n        if (test->m_handler)\n        {\n            test->m_handler(buffer, length, header);\n        }\n    }\n\n    template<typename F>\n    void handle_fragment(F &&handler, size_t length)\n    {\n        m_handler = handler;\n        uint8_t *buffer = m_fragment.data() + AERON_DATA_HEADER_LENGTH;\n        ::aeron_fragment_assembler_handler(m_assembler, buffer, length, &m_header);\n    }\n\nprotected:\n    AERON_DECL_ALIGNED(fragment_buffer_t m_fragment, 16) = {};\n    aeron_header_t m_header = {};\n    std::function<void(const uint8_t *, size_t, aeron_header_t *)> m_handler = nullptr;\n    aeron_fragment_assembler_t *m_assembler = nullptr;\n};\n\nTEST_F(CFragmentAssemblerTest, shouldPassThroughUnfragmentedMessage)\n{\n    size_t fragmentLength = 158;\n    fillFrame(AERON_DATA_HEADER_UNFRAGMENTED, 0, fragmentLength, 0);\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        verifyPayload(buffer, length);\n    };\n\n    handle_fragment(handler, fragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(CFragmentAssemblerTest, shouldReassembleFromTwoFragments)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t initialTermId = 420;\n    size_t positionBitsToShift = 20;\n    int8_t version = 42;\n    int16_t type = AERON_HDR_TYPE_ERR;\n    int32_t sessionId = 18;\n    int32_t streamId = 31;\n    int32_t termId = initialTermId + 5;\n    int64_t reservedValue = -47923742793L;\n    int32_t termOffset = 0;\n\n    const int64_t startPosition = ::aeron_logbuffer_compute_position(\n        termId, termOffset, positionBitsToShift, initialTermId);\n\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        const int64_t expectedPosition =\n            startPosition + static_cast<int64_t>(::aeron_logbuffer_compute_fragmented_length(length, fragmentLength));\n\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(initialTermId, header_values.initial_term_id);\n        EXPECT_EQ(positionBitsToShift, header_values.position_bits_to_shift);\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + (2 * fragmentLength), header_values.frame.frame_length);\n        EXPECT_EQ((uint8_t)(AERON_DATA_HEADER_END_FLAG | AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG), header_values.frame.flags);\n        EXPECT_EQ(version, header_values.frame.version);\n        EXPECT_EQ(type, header_values.frame.type);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(sessionId, header_values.frame.session_id);\n        EXPECT_EQ(streamId, header_values.frame.stream_id);\n        EXPECT_EQ(termId, header_values.frame.term_id);\n        EXPECT_EQ(reservedValue, header_values.frame.reserved_value);\n        EXPECT_EQ(expectedPosition, ::aeron_header_position(header));\n        verifyPayload(buffer, length);\n    };\n\n    m_header.initial_term_id = initialTermId;\n    m_header.position_bits_to_shift = positionBitsToShift;\n    fillFrame(\n        version,\n        (uint8_t)(AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG),\n        type,\n        termOffset,\n        sessionId,\n        streamId,\n        termId,\n        reservedValue,\n        fragmentLength,\n        0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    m_header.initial_term_id = -1;\n    m_header.position_bits_to_shift = -7;\n    fillFrame(\n        AERON_FRAME_HEADER_VERSION,\n        AERON_DATA_HEADER_END_FLAG,\n        AERON_HDR_TYPE_EXT,\n        termOffset,\n        sessionId,\n        -13,\n        -190,\n        63456385384L,\n        fragmentLength,\n        fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(CFragmentAssemblerTest, shouldReassembleFromThreeFragments)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    size_t lastFragmentLength = 111;\n\n    int8_t version = 3;\n    int16_t type = AERON_HDR_TYPE_RTTM;\n    int32_t streamId = 106;\n    int32_t termId = 42;\n    int32_t initialTermId = termId - 5;\n    int32_t positionBitsToShift = 16;\n    int64_t reservedValue = 5734957345793759L;\n    int32_t termOffset = 0;\n    bool isCalled = false;\n\n    const int64_t startPosition = ::aeron_logbuffer_compute_position(\n        termId, termOffset, positionBitsToShift, initialTermId);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        const int64_t expectedPosition =\n            startPosition + static_cast<int64_t>(::aeron_logbuffer_compute_fragmented_length(length, fragmentLength));\n\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2 + lastFragmentLength);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length, header_values.frame.frame_length);\n        EXPECT_EQ((uint8_t)(AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG | AERON_DATA_HEADER_END_FLAG | 0xF), header_values.frame.flags);\n        EXPECT_EQ(version, header_values.frame.version);\n        EXPECT_EQ(type, header_values.frame.type);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(streamId, header_values.frame.stream_id);\n        EXPECT_EQ(termId, header_values.frame.term_id);\n        EXPECT_EQ(reservedValue, header_values.frame.reserved_value);\n        EXPECT_EQ(expectedPosition, aeron_header_position(header));\n        verifyPayload(buffer, length);\n    };\n\n    m_header.initial_term_id = initialTermId;\n    m_header.position_bits_to_shift = positionBitsToShift;\n    fillFrame(\n        version,\n        (uint8_t)(AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG | 0x5),\n        type,\n        termOffset,\n        SESSION_ID,\n        streamId,\n        termId,\n        reservedValue,\n        fragmentLength,\n        0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(0xE, termOffset, fragmentLength, fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame((uint8_t)(AERON_DATA_HEADER_END_FLAG | 0xA), termOffset, lastFragmentLength, (fragmentLength * 2) % 256);\n    handle_fragment(handler, lastFragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(CFragmentAssemblerTest, shouldNotReassembleIfEndFirstFragment)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n    };\n\n    fillFrame(AERON_DATA_HEADER_END_FLAG, 0, fragmentLength, 0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(CFragmentAssemblerTest, shouldNotReassembleIfMissingBegin)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n    };\n\n    fillFrame(0, termOffset, fragmentLength, 0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(CFragmentAssemblerTest, shouldReassembleTwoMessagesFromFourFrames)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0, messageBeginOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(messageBeginOffset, header_values.frame.term_offset);\n    };\n\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_TRUE(isCalled);\n\n    isCalled = false;\n    messageBeginOffset = 2 * MTU_LENGTH;\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_image_fragment_assembler_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeronc.h\"\n#include \"aeron_image.h\"\n}\n\n#define STREAM_ID (8)\n#define SESSION_ID (-541)\n#define TERM_LENGTH (AERON_LOGBUFFER_TERM_MIN_LENGTH)\n#define INITIAL_TERM_ID (42)\n#define ACTIVE_TERM_ID  (INITIAL_TERM_ID + 3)\n#define POSITION_BITS_TO_SHIFT (aeron_number_of_trailing_zeroes(TERM_LENGTH))\n#define MTU_LENGTH (1408)\n\ntypedef std::array<uint8_t, TERM_LENGTH> fragment_buffer_t;\n\nclass ImageFragmentAssemblerTest : public testing::Test\n{\npublic:\n    ImageFragmentAssemblerTest()\n    {\n        m_fragment.fill(0);\n        m_header.frame = (aeron_data_header_t *)m_fragment.data();\n        m_header.context = (void*)\"test context\";\n\n        if (aeron_image_fragment_assembler_create(&m_assembler, fragment_handler, this) < 0)\n        {\n            throw std::runtime_error(\"could not create aeron_fragment_assembler_create: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    ~ImageFragmentAssemblerTest() override\n    {\n        aeron_image_fragment_assembler_delete(m_assembler);\n    }\n\n    void fillFrame(uint8_t flags, int32_t offset, size_t length, uint8_t initialPayloadValue)\n    {\n        fillFrame(\n            AERON_FRAME_HEADER_VERSION,\n            flags,\n            AERON_HDR_TYPE_DATA,\n            offset,\n            SESSION_ID,\n            STREAM_ID,\n            ACTIVE_TERM_ID,\n            0,\n            length,\n            initialPayloadValue);\n    }\n\n    void fillFrame(\n        int8_t version,\n        uint8_t flags,\n        int16_t type,\n        int32_t termOffset,\n        int32_t sessionId,\n        int32_t streamId,\n        int32_t termId,\n        int64_t reservedValue,\n        size_t length,\n        uint8_t initialPayloadValue)\n    {\n        auto frame = (aeron_data_header_t *)m_fragment.data();\n\n        frame->frame_header.frame_length = (int32_t)(AERON_DATA_HEADER_LENGTH + length);\n        frame->frame_header.version = version;\n        frame->frame_header.flags = flags;\n        frame->frame_header.type = type;\n        frame->term_offset = termOffset;\n        frame->session_id = sessionId;\n        frame->stream_id = streamId;\n        frame->term_id = termId;\n        frame->reserved_value = reservedValue;\n\n        uint8_t value = initialPayloadValue;\n        for (size_t i = 0; i < length; i++)\n        {\n            m_fragment[i + AERON_DATA_HEADER_LENGTH] = value++;\n        }\n    }\n\n    static void verifyPayload(const uint8_t *buffer, size_t length)\n    {\n        for (size_t i = 0; i < length; i++)\n        {\n            ASSERT_EQ(*(buffer + i), i % 256);\n        }\n    }\n\n    static void fragment_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto test = reinterpret_cast<ImageFragmentAssemblerTest *>(clientd);\n\n        if (test->m_handler)\n        {\n            test->m_handler(buffer, length, header);\n        }\n    }\n\n    template<typename F>\n    void handle_fragment(F &&handler, size_t length)\n    {\n        m_handler = handler;\n        uint8_t *buffer = m_fragment.data() + AERON_DATA_HEADER_LENGTH;\n        ::aeron_image_fragment_assembler_handler(m_assembler, buffer, length, &m_header);\n    }\n\nprotected:\n    AERON_DECL_ALIGNED(fragment_buffer_t m_fragment, 16) = {};\n    aeron_header_t m_header = {};\n    std::function<void(const uint8_t *, size_t, aeron_header_t *)> m_handler = nullptr;\n    aeron_image_fragment_assembler_t *m_assembler = nullptr;\n};\n\nTEST_F(ImageFragmentAssemblerTest, shouldPassThroughUnfragmentedMessage)\n{\n    size_t fragmentLength = 158;\n    fillFrame(AERON_DATA_HEADER_UNFRAGMENTED, 0, fragmentLength, 0);\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        verifyPayload(buffer, length);\n    };\n\n    handle_fragment(handler, fragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ImageFragmentAssemblerTest, shouldReassembleFromTwoFragments)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t initialTermId = 420;\n    size_t positionBitsToShift = 20;\n    int8_t version = 42;\n    int16_t type = AERON_HDR_TYPE_ERR;\n    int32_t sessionId = 18;\n    int32_t streamId = 31;\n    int32_t termId = initialTermId + 5;\n    int64_t reservedValue = -47923742793L;\n    int32_t termOffset = 0;\n\n    const int64_t startPosition = ::aeron_logbuffer_compute_position(\n        termId, termOffset, positionBitsToShift, initialTermId);\n\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        const int64_t expectedPosition =\n            startPosition + static_cast<int64_t>(::aeron_logbuffer_compute_fragmented_length(length, fragmentLength));\n\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        EXPECT_NE(nullptr, header->context);\n        EXPECT_EQ(m_header.context, header->context);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(initialTermId, header_values.initial_term_id);\n        EXPECT_EQ(positionBitsToShift, header_values.position_bits_to_shift);\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length, header_values.frame.frame_length);\n        EXPECT_EQ((uint8_t)(AERON_DATA_HEADER_END_FLAG | AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG | 0x2), header_values.frame.flags);\n        EXPECT_EQ(version, header_values.frame.version);\n        EXPECT_EQ(type, header_values.frame.type);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(sessionId, header_values.frame.session_id);\n        EXPECT_EQ(streamId, header_values.frame.stream_id);\n        EXPECT_EQ(termId, header_values.frame.term_id);\n        EXPECT_EQ(reservedValue, header_values.frame.reserved_value);\n        EXPECT_EQ(expectedPosition, aeron_header_position(header));\n        verifyPayload(buffer, length);\n    };\n\n    m_header.initial_term_id = initialTermId;\n    m_header.position_bits_to_shift = positionBitsToShift;\n    fillFrame(\n        version,\n        (uint8_t)(AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG | 0x2),\n        type,\n        termOffset,\n        sessionId,\n        streamId,\n        termId,\n        reservedValue,\n        fragmentLength,\n        0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    m_header.initial_term_id = -1;\n    m_header.position_bits_to_shift = -7;\n    fillFrame(\n        AERON_FRAME_HEADER_VERSION,\n        AERON_DATA_HEADER_END_FLAG,\n        AERON_HDR_TYPE_EXT,\n        termOffset,\n        sessionId,\n        -13,\n        -190,\n        63456385384L,\n        fragmentLength,\n        fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ImageFragmentAssemblerTest, shouldReassembleFromThreeFragments)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    size_t lastFragmentLength = 111;\n\n    int32_t initialTermId = 420;\n    size_t positionBitsToShift = 20;\n    int8_t version = 3;\n    int16_t type = AERON_HDR_TYPE_RTTM;\n    int32_t streamId = 106;\n    int32_t termId = initialTermId + 5;\n    int64_t reservedValue = 5734957345793759L;\n    int32_t termOffset = 0;\n\n    const int64_t startPosition = ::aeron_logbuffer_compute_position(\n        termId, termOffset, positionBitsToShift, initialTermId);\n\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        const int64_t expectedPosition =\n            startPosition + static_cast<int64_t>(::aeron_logbuffer_compute_fragmented_length(length, fragmentLength));\n\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2 + lastFragmentLength);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(AERON_DATA_HEADER_LENGTH + length, header_values.frame.frame_length);\n        EXPECT_EQ(0xFF, header_values.frame.flags);\n        EXPECT_EQ(version, header_values.frame.version);\n        EXPECT_EQ(type, header_values.frame.type);\n        EXPECT_EQ(0, header_values.frame.term_offset);\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(streamId, header_values.frame.stream_id);\n        EXPECT_EQ(termId, header_values.frame.term_id);\n        EXPECT_EQ(reservedValue, header_values.frame.reserved_value);\n        EXPECT_EQ(expectedPosition, aeron_header_position(header));\n        verifyPayload(buffer, length);\n    };\n\n    m_header.initial_term_id = initialTermId;\n    m_header.position_bits_to_shift = positionBitsToShift;\n    fillFrame(\n        version,\n        (uint8_t)(AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_EOS_FLAG),\n        type,\n        termOffset,\n        SESSION_ID,\n        streamId,\n        termId,\n        reservedValue,\n        fragmentLength,\n        0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(0xE, termOffset, fragmentLength, fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(0x7F, termOffset, lastFragmentLength, (fragmentLength * 2) % 256);\n    handle_fragment(handler, lastFragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_F(ImageFragmentAssemblerTest, shouldNotReassembleIfEndFirstFragment)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n    };\n\n    fillFrame(AERON_DATA_HEADER_END_FLAG, 0, fragmentLength, 0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(ImageFragmentAssemblerTest, shouldNotReassembleIfMissingBegin)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n    };\n\n    fillFrame(0, termOffset, fragmentLength, 0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n}\n\nTEST_F(ImageFragmentAssemblerTest, shouldReassembleTwoMessagesFromFourFrames)\n{\n    size_t fragmentLength = MTU_LENGTH - AERON_DATA_HEADER_LENGTH;\n    int32_t termOffset = 0, messageBeginOffset = 0;\n    bool isCalled = false;\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        isCalled = true;\n        EXPECT_EQ(length, fragmentLength * 2);\n        aeron_header_values_t header_values;\n        EXPECT_EQ(0, aeron_header_values(header, &header_values));\n        EXPECT_EQ(SESSION_ID, header_values.frame.session_id);\n        EXPECT_EQ(messageBeginOffset, header_values.frame.term_offset);\n    };\n\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_TRUE(isCalled);\n\n    isCalled = false;\n    messageBeginOffset = 2 * MTU_LENGTH;\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_BEGIN_FLAG, termOffset, fragmentLength, 0);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(AERON_DATA_HEADER_END_FLAG, termOffset, fragmentLength, fragmentLength % 256);\n    handle_fragment(handler, fragmentLength);\n    EXPECT_TRUE(isCalled);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_image_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <exception>\n#include <functional>\n#include <string>\n#include <limits>\n\n#include <gtest/gtest.h>\n\n#include \"aeron_client_test_utils.h\"\n\nextern \"C\"\n{\n#include \"aeron_subscription.h\"\n#include \"aeron_image.h\"\n}\n\n#define FILE_PAGE_SIZE (4 * 1024)\n\n#define SUB_URI \"aeron:udp?endpoint=localhost:24567\"\n#define STREAM_ID (101)\n#define SESSION_ID (110)\n#define REGISTRATION_ID (27)\n#define SUBSCRIBER_POSITION_ID (49)\n\n#define INITIAL_TERM_ID (1234)\n\nusing namespace aeron::test;\n\nclass ImageTest : public testing::Test\n{\npublic:\n    ImageTest() :\n        m_subscription(createSubscription())\n    {\n    }\n\n    ~ImageTest() override\n    {\n        if (nullptr != m_subscription)\n        {\n            aeron_subscription_delete(m_subscription);\n        }\n\n        if (!m_filename.empty())\n        {\n            aeron_log_buffer_delete(m_image->log_buffer);\n            aeron_image_delete(m_image);\n\n            ::unlink(m_filename.c_str());\n        }\n    }\n\n    static aeron_subscription_t *createSubscription()\n    {\n        aeron_subscription_t *subscription = nullptr;\n\n        if (aeron_subscription_create(\n            &subscription,\n            nullptr,\n            ::strdup(SUB_URI),\n            STREAM_ID,\n            26,\n            0,\n            nullptr,\n            nullptr,\n            nullptr,\n            nullptr,\n            nullptr) < 0)\n        {\n            throw std::runtime_error(\"could not create subscription: \" + std::string(aeron_errmsg()));\n        }\n\n        return subscription;\n    }\n\n    int64_t createImage()\n    {\n        aeron_image_t *image = nullptr;\n        aeron_log_buffer_t *log_buffer = nullptr;\n        std::string filename = tempFileName();\n\n        createLogFile(filename);\n\n        if (aeron_log_buffer_create(&log_buffer, filename.c_str(), m_correlationId, false) < 0)\n        {\n            throw std::runtime_error(\"could not create log_buffer: \" + std::string(aeron_errmsg()));\n        }\n\n        if (aeron_image_create(\n            &image,\n            m_subscription,\n            nullptr,\n            log_buffer,\n            SUBSCRIBER_POSITION_ID,\n            &m_sub_pos,\n            m_correlationId,\n            (int32_t)m_correlationId,\n            \"none\",\n            strlen(\"none\")) < 0)\n        {\n            throw std::runtime_error(\"could not create image: \" + std::string(aeron_errmsg()));\n        }\n\n        auto *metadata = (aeron_logbuffer_metadata_t *)log_buffer->mapped_raw_log.log_meta_data.addr;\n\n        m_image = image;\n        m_filename = filename;\n\n        m_term_length = metadata->term_length;\n        m_initial_term_id = metadata->initial_term_id;\n        m_position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes(metadata->term_length);\n\n        return m_correlationId++;\n    }\n\n    void appendMessage(int64_t position, size_t length)\n    {\n        auto *metadata = (aeron_logbuffer_metadata_t *)m_image->log_buffer->mapped_raw_log.log_meta_data.addr;\n        const size_t index = aeron_logbuffer_index_by_position(position, m_position_bits_to_shift);\n        aeron_mapped_buffer_t *term_buffer = &m_image->log_buffer->mapped_raw_log.term_buffers[index];\n        uint8_t buffer[1024] = { 0 };\n        int32_t term_id = aeron_logbuffer_compute_term_id_from_position(\n            position, m_position_bits_to_shift, m_initial_term_id);\n        int32_t tail_offset = (int32_t)position & (m_term_length - 1);\n\n        metadata->term_tail_counters[index] = static_cast<int64_t>(term_id) << 32 | tail_offset;\n\n        const size_t frame_length = length + AERON_DATA_HEADER_LENGTH;\n        const auto aligned_frame_length = (int32_t)AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n        int32_t resulting_offset = tail_offset + aligned_frame_length;\n        metadata->term_tail_counters[index] = static_cast<int64_t>(term_id) << 32 | resulting_offset;\n\n        auto term_length = static_cast<int32_t>(term_buffer->length);\n        auto *header = (aeron_data_header_t *)(term_buffer->addr + tail_offset);\n\n        if (resulting_offset > term_length)\n        {\n            if (tail_offset < term_length)\n            {\n                const int32_t padding_length = term_length - tail_offset;\n\n                header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n                header->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n                header->frame_header.type = AERON_HDR_TYPE_PAD;\n                header->term_offset = tail_offset;\n                header->session_id = SESSION_ID;\n                header->stream_id = STREAM_ID;\n                header->term_id = term_id;\n\n                AERON_SET_RELEASE(header->frame_header.frame_length, padding_length);\n            }\n        }\n        else\n        {\n            header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n            header->frame_header.flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n            header->frame_header.type = AERON_HDR_TYPE_DATA;\n            header->term_offset = tail_offset;\n            header->session_id = SESSION_ID;\n            header->stream_id = STREAM_ID;\n            header->term_id = term_id;\n\n            memcpy(term_buffer->addr + tail_offset + AERON_DATA_HEADER_LENGTH, buffer, length);\n\n            AERON_SET_RELEASE(header->frame_header.frame_length, (int32_t)frame_length);\n        }\n    }\n\n    static void fragment_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto image = reinterpret_cast<ImageTest *>(clientd);\n\n        if (image->m_handler)\n        {\n            image->m_handler(buffer, length, header);\n        }\n    }\n\n    static aeron_controlled_fragment_handler_action_t controlled_fragment_handler(\n        void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto image = reinterpret_cast<ImageTest *>(clientd);\n\n        if (image->m_controlled_handler)\n        {\n            return image->m_controlled_handler(buffer, length, header);\n        }\n\n        return AERON_ACTION_CONTINUE;\n    }\n\n    template<typename F>\n    int imagePoll(F &&handler, size_t fragment_limit)\n    {\n        m_handler = handler;\n        return aeron_image_poll(m_image, fragment_handler, this, fragment_limit);\n    }\n\n    template<typename F>\n    int imageControlledPoll(F &&handler, size_t fragment_limit)\n    {\n        m_controlled_handler = handler;\n        return aeron_image_controlled_poll(m_image, controlled_fragment_handler, this, fragment_limit);\n    }\n\n    template<typename F>\n    int imageControlledPeek(F &&handler, int64_t initial_position, int64_t limit_position)\n    {\n        m_controlled_handler = handler;\n        return aeron_image_controlled_peek(m_image, initial_position, controlled_fragment_handler, this, limit_position);\n    }\n\n    template<typename F>\n    int imageBoundedPoll(F &&handler, int64_t max_position, size_t fragment_limit)\n    {\n        m_handler = handler;\n        return aeron_image_bounded_poll(m_image, fragment_handler, this, max_position, fragment_limit);\n    }\n\n    template<typename F>\n    int imageBoundedControlledPoll(F &&handler, int64_t max_position, size_t fragment_limit)\n    {\n        m_controlled_handler = handler;\n        return aeron_image_bounded_controlled_poll(\n            m_image, controlled_fragment_handler, this, max_position, fragment_limit);\n    }\n\n    const uint8_t *termBuffer(size_t index)\n    {\n        return m_image->log_buffer->mapped_raw_log.term_buffers[index].addr;\n    }\n\nprotected:\n    int64_t m_correlationId = 0;\n    int64_t m_sub_pos = 0;\n\n    int32_t m_term_length = 0;\n    int32_t m_initial_term_id = 0;\n\n    size_t m_position_bits_to_shift = 0;\n\n    std::function<void(const uint8_t *, size_t, aeron_header_t *)> m_handler = nullptr;\n    std::function<aeron_controlled_fragment_handler_action_t(const uint8_t *, size_t, aeron_header_t *)>\n        m_controlled_handler = nullptr;\n\n    aeron_subscription_t *m_subscription = nullptr;\n    aeron_image_t *m_image = nullptr;\n    std::string m_filename;\n};\n\nTEST_F(ImageTest, shouldReadFirstMessage)\n{\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    createImage();\n\n    appendMessage(m_sub_pos, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        EXPECT_EQ(buffer, termBuffer(0) + m_sub_pos + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n        EXPECT_EQ(header->frame->frame_header.type, AERON_HDR_TYPE_DATA);\n    };\n\n    EXPECT_EQ(imagePoll(handler, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldNotReadPastTail)\n{\n    createImage();\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n    {\n        FAIL() << \"should not be called\";\n    };\n\n    EXPECT_EQ(imagePoll(handler, std::numeric_limits<size_t>::max()), 0);\n    EXPECT_EQ(static_cast<size_t>(m_sub_pos), 0u);\n}\n\nTEST_F(ImageTest, shouldReadOneLimitedMessage)\n{\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    createImage();\n\n    appendMessage(m_sub_pos, messageLength);\n    appendMessage(m_sub_pos + alignedMessageLength, messageLength);\n\n    auto null_handler = [&](const uint8_t *, size_t length, aeron_header_t *header) {};\n\n    EXPECT_EQ(imagePoll(null_handler, 1), 1);\n}\n\nTEST_F(ImageTest, shouldReadMultipleMessages)\n{\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    createImage();\n\n    appendMessage(m_sub_pos, messageLength);\n    appendMessage(m_sub_pos + alignedMessageLength, messageLength);\n    size_t handlerCallCount = 0;\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n    {\n        handlerCallCount++;\n    };\n\n    EXPECT_EQ(imagePoll(handler, std::numeric_limits<size_t>::max()), 2);\n    EXPECT_EQ(handlerCallCount, 2u);\n    EXPECT_EQ(m_sub_pos, alignedMessageLength * 2);\n}\n\nTEST_F(ImageTest, shouldStopReadingIfImageIsClosed)\n{\n    const size_t messageLength = 100;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    createImage();\n\n    appendMessage(m_sub_pos, messageLength);\n    appendMessage(m_sub_pos + alignedMessageLength, messageLength);\n    size_t handlerCallCount = 0;\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n    {\n        handlerCallCount++;\n        aeron_image_close(m_image);\n    };\n\n    EXPECT_EQ(imagePoll(handler, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(true, aeron_image_is_closed(m_image));\n    EXPECT_EQ(handlerCallCount, 1u);\n    EXPECT_EQ(m_sub_pos, alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldReadLastMessage)\n{\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    createImage();\n\n    m_sub_pos = m_term_length - alignedMessageLength;\n\n    appendMessage(m_sub_pos, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        EXPECT_EQ(buffer, termBuffer(0) + m_sub_pos + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n    };\n\n    EXPECT_EQ(imagePoll(handler, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, m_term_length);\n}\n\nTEST_F(ImageTest, shouldNotReadLastMessageWhenPadding)\n{\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    createImage();\n\n    m_sub_pos = m_term_length - alignedMessageLength;\n\n    // this will append padding instead of the message as it will trip over the end.\n    appendMessage(m_sub_pos, messageLength + AERON_DATA_HEADER_LENGTH);\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n    {\n        FAIL() << \"should not be called\";\n    };\n\n    EXPECT_EQ(imagePoll(handler, std::numeric_limits<size_t>::max()), 0);\n    EXPECT_EQ(m_sub_pos, m_term_length);\n}\n\nTEST_F(ImageTest, shouldAllowValidPosition)\n{\n    createImage();\n\n    int64_t expectedPosition = m_term_length - 32;\n\n    m_sub_pos = expectedPosition;\n    ASSERT_EQ(aeron_image_position(m_image), expectedPosition);\n\n    ASSERT_EQ(aeron_image_set_position(m_image, m_term_length), 0);\n    EXPECT_EQ(aeron_image_position(m_image), m_term_length);\n}\n\nTEST_F(ImageTest, shouldNotAdvancePastEndOfTerm)\n{\n    createImage();\n\n    int64_t expectedPosition = m_term_length - 32;\n\n    m_sub_pos = expectedPosition;\n    ASSERT_EQ(aeron_image_position(m_image), expectedPosition);\n\n    ASSERT_EQ(aeron_image_set_position(m_image, m_term_length + 32), -1);\n    EXPECT_EQ(aeron_image_position(m_image), expectedPosition);\n}\n\nTEST_F(ImageTest, shouldReportCorrectPositionOnReception)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    appendMessage(m_sub_pos, messageLength);\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header) {};\n\n    EXPECT_EQ(imagePoll(handler, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldReportCorrectPositionOnReceptionWithNonZeroPositionInInitialTermId)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialTermOffset = 5 * alignedMessageLength;\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, initialTermOffset, m_position_bits_to_shift, INITIAL_TERM_ID);\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(m_sub_pos, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        EXPECT_EQ(buffer, termBuffer(0) + initialTermOffset + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n    };\n\n    EXPECT_EQ(imagePoll(handler, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, initialPosition + alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldReportCorrectPositionOnReceptionWithNonZeroPositionInNonInitialTermId)\n{\n    createImage();\n\n    const int32_t activeTermId = INITIAL_TERM_ID + 1;\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialTermOffset = 5 * alignedMessageLength;\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        activeTermId, initialTermOffset, m_position_bits_to_shift, INITIAL_TERM_ID);\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(m_sub_pos, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        EXPECT_EQ(buffer, termBuffer(1) + initialTermOffset + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n    };\n\n    EXPECT_EQ(imagePoll(handler, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, initialPosition + alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldPollNoFragmentsToControlledFragmentHandler)\n{\n    createImage();\n\n    bool called = false;\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        called = true;\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageControlledPoll(handler, std::numeric_limits<size_t>::max()), 0);\n    EXPECT_FALSE(called);\n    EXPECT_EQ(static_cast<size_t>(m_sub_pos), 0u);\n}\n\nTEST_F(ImageTest, shouldPeekNoFragmentsToControlledFragmentHandler)\n{\n    createImage();\n\n    bool called = false;\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        called = true;\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageControlledPeek(handler, 0, INT64_MAX), 0);\n    EXPECT_FALSE(called);\n    EXPECT_EQ(m_sub_pos, 0);\n}\n\nTEST_F(ImageTest, shouldPollOneFragmentToControlledFragmentHandlerOnContinue)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    appendMessage(m_sub_pos, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        EXPECT_EQ(buffer, termBuffer(0) + m_sub_pos + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n        EXPECT_EQ(header->frame->frame_header.type, AERON_HDR_TYPE_DATA);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageControlledPoll(handler, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldPeekOneFragmentToControlledFragmentHandlerOnContinue)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(m_sub_pos, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        EXPECT_EQ(buffer, termBuffer(0) + m_sub_pos + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n        EXPECT_EQ(header->frame->frame_header.type, AERON_HDR_TYPE_DATA);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageControlledPeek(handler, initialPosition, INT64_MAX), initialPosition + alignedMessageLength);\n    EXPECT_EQ(m_sub_pos, initialPosition);\n}\n\nTEST_F(ImageTest, shouldStopPeekOneFragmentToControlledFragmentHandlerIfImageIsClosed)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        aeron_image_close(m_image);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageControlledPeek(handler, initialPosition, INT64_MAX), initialPosition + alignedMessageLength);\n    EXPECT_EQ(true, aeron_image_is_closed(m_image));\n    EXPECT_EQ(m_sub_pos, initialPosition);\n}\n\nTEST_F(ImageTest, shouldNotPollOneFragmentToControlledFragmentHandlerOnAbort)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(m_sub_pos, messageLength);\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        return AERON_ACTION_ABORT;\n    };\n\n    EXPECT_EQ(imageControlledPoll(handler, std::numeric_limits<size_t>::max()), 0);\n    EXPECT_EQ(m_sub_pos, initialPosition);\n}\n\nTEST_F(ImageTest, shouldPollOneFragmentToControlledFragmentHandlerOnBreak)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    appendMessage(m_sub_pos, messageLength);\n    appendMessage(m_sub_pos + alignedMessageLength, messageLength);\n    aeron_image_constants_t image_constants;\n    aeron_image_constants(m_image, &image_constants);\n\n    EXPECT_EQ(image_constants.subscriber_position_id, SUBSCRIBER_POSITION_ID);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        aeron_header_values_t values;\n        aeron_header_values(header, &values);\n\n        EXPECT_EQ(buffer, termBuffer(0) + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n        EXPECT_EQ(header->frame->frame_header.type, AERON_HDR_TYPE_DATA);\n        EXPECT_EQ(values.frame.type, AERON_HDR_TYPE_DATA);\n        EXPECT_EQ(image_constants.initial_term_id, values.initial_term_id);\n        EXPECT_EQ(image_constants.position_bits_to_shift, values.position_bits_to_shift);\n        EXPECT_EQ(alignedMessageLength, aeron_header_position(header));\n\n        return AERON_ACTION_BREAK;\n    };\n\n    EXPECT_EQ(imageControlledPoll(handler, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldPollFragmentsToControlledFragmentHandlerOnCommit)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    size_t fragmentCount = 0;\n\n    appendMessage(m_sub_pos, messageLength);\n    appendMessage(m_sub_pos + alignedMessageLength, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        fragmentCount++;\n\n        if (1 == fragmentCount)\n        {\n            EXPECT_EQ(m_sub_pos, 0);\n\n            EXPECT_EQ(buffer, termBuffer(0) + AERON_DATA_HEADER_LENGTH);\n        }\n        else if (2 == fragmentCount)\n        {\n            // testing current position here after first message commit\n            EXPECT_EQ(m_sub_pos, alignedMessageLength);\n\n            EXPECT_EQ(buffer, termBuffer(0) + alignedMessageLength + AERON_DATA_HEADER_LENGTH);\n        }\n\n        EXPECT_EQ(length, messageLength);\n        return AERON_ACTION_COMMIT;\n    };\n\n    EXPECT_EQ(imageControlledPoll(handler, std::numeric_limits<size_t>::max()), 2);\n    EXPECT_EQ(m_sub_pos, alignedMessageLength * 2);\n}\n\nTEST_F(ImageTest, shouldUpdatePositionToEndOfCommittedFragmentOnCommit)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n    size_t fragmentCount = 0;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n    appendMessage(initialPosition + (2 * alignedMessageLength), messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        fragmentCount++;\n\n        EXPECT_EQ(length, messageLength);\n\n        if (1 == fragmentCount)\n        {\n            EXPECT_EQ(m_sub_pos, initialPosition);\n\n            EXPECT_EQ(buffer, termBuffer(0) + AERON_DATA_HEADER_LENGTH);\n            return AERON_ACTION_CONTINUE;\n        }\n        else if (2 == fragmentCount)\n        {\n            // testing current position here after first message continue\n            EXPECT_EQ(m_sub_pos, initialPosition);\n\n            EXPECT_EQ(buffer, termBuffer(0) + alignedMessageLength + AERON_DATA_HEADER_LENGTH);\n            return AERON_ACTION_COMMIT;\n        }\n        else if (3 == fragmentCount)\n        {\n            // testing current position here after second message commit\n            EXPECT_EQ(m_sub_pos, initialPosition + (2 * alignedMessageLength));\n\n            EXPECT_EQ(buffer, termBuffer(0) + (2 * alignedMessageLength) + AERON_DATA_HEADER_LENGTH);\n            return AERON_ACTION_CONTINUE;\n        }\n\n        return AERON_ACTION_COMMIT;\n    };\n\n    EXPECT_EQ(imageControlledPoll(handler, std::numeric_limits<size_t>::max()), 3);\n    EXPECT_EQ(m_sub_pos, initialPosition + (3 * alignedMessageLength));\n}\n\nTEST_F(ImageTest, shouldPollFragmentsToControlledFragmentHandlerOnContinue)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n    size_t fragmentCount = 0;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        fragmentCount++;\n\n        if (1 == fragmentCount)\n        {\n            EXPECT_EQ(m_sub_pos, initialPosition);\n\n            EXPECT_EQ(buffer, termBuffer(0) + AERON_DATA_HEADER_LENGTH);\n        }\n        else if (2 == fragmentCount)\n        {\n            // testing current position here after first message continue\n            EXPECT_EQ(m_sub_pos, initialPosition);\n\n            EXPECT_EQ(buffer, termBuffer(0) + alignedMessageLength + AERON_DATA_HEADER_LENGTH);\n        }\n\n        EXPECT_EQ(length, messageLength);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageControlledPoll(handler, std::numeric_limits<size_t>::max()), 2);\n    EXPECT_EQ(m_sub_pos, initialPosition + (2 * alignedMessageLength));\n}\n\nTEST_F(ImageTest, shouldStopPollFragmentsToControlledFragmentHandlerIfImageIsClosed)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n    size_t fragmentCount = 0;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        fragmentCount++;\n        aeron_image_close(m_image);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageControlledPoll(handler, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(true, aeron_image_is_closed(m_image));\n    EXPECT_EQ(1, fragmentCount);\n    EXPECT_EQ(m_sub_pos, initialPosition + alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldPollNoFragmentsToBoundedControlledFragmentHandlerWithMaxPositionBeforeInitialPosition)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int32_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n    const int64_t maxPosition = initialPosition - (int64_t)AERON_DATA_HEADER_LENGTH;\n    bool called = false;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        called = true;\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_FALSE(called);\n    EXPECT_EQ(imageBoundedControlledPoll(handler, maxPosition, std::numeric_limits<size_t>::max()), 0);\n    EXPECT_EQ(m_sub_pos, initialPosition);\n}\n\nTEST_F(ImageTest, shouldPollFragmentsToBoundedControlledFragmentHandlerWithInitialOffsetNotZero)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, alignedMessageLength, m_position_bits_to_shift, INITIAL_TERM_ID);\n    const int64_t maxPosition = initialPosition + alignedMessageLength;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageBoundedControlledPoll(handler, maxPosition, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, maxPosition);\n}\n\nTEST_F(ImageTest, shouldPollFragmentsToBoundedControlledFragmentHandlerWithMaxPositionBeforeNextMessage)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n    const int64_t maxPosition = initialPosition + alignedMessageLength;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageBoundedControlledPoll(handler, maxPosition, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, initialPosition + alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldStopPollFragmentsToBoundedControlledFragmentHandlerIfImageIsClosed)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, alignedMessageLength, m_position_bits_to_shift, INITIAL_TERM_ID);\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n\n    auto handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        aeron_image_close(m_image);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageBoundedControlledPoll(handler, INT64_MAX, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(true, aeron_image_is_closed(m_image));\n    EXPECT_EQ(m_sub_pos, initialPosition + alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldPollFragmentsToBoundedFragmentHandlerWithMaxPositionBeforeNextMessage)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n    const int64_t maxPosition = initialPosition + alignedMessageLength;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n\n    auto null_handler = [&](const uint8_t *, size_t length, aeron_header_t *header) {};\n\n    EXPECT_EQ(imageBoundedPoll(null_handler, maxPosition, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, initialPosition + alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldStopPollFragmentsToBoundedFragmentHandlerIfImageIsClosed)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int64_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, 0, m_position_bits_to_shift, INITIAL_TERM_ID);\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength);\n\n    auto null_handler = [&](const uint8_t *, size_t length, aeron_header_t *header)\n        {\n            aeron_image_close(m_image);\n        };\n\n    EXPECT_EQ(imageBoundedPoll(null_handler, INT64_MAX, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(true, aeron_image_is_closed(m_image));\n    EXPECT_EQ(m_sub_pos, initialPosition + alignedMessageLength);\n}\n\nTEST_F(ImageTest, shouldPollFragmentsToBoundedControlledFragmentHandlerWithMaxPositionAfterEndOfTerm)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int32_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t initialOffset = m_term_length - (2 * alignedMessageLength);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, initialOffset, m_position_bits_to_shift, INITIAL_TERM_ID);\n    const int64_t maxPosition = initialPosition + m_term_length;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength * 2);  // will insert padding\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        EXPECT_EQ(buffer, termBuffer(0) + initialOffset + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageBoundedControlledPoll(handler, maxPosition, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, m_term_length);\n}\n\nTEST_F(ImageTest, shouldPollFragmentsToBoundedControlledFragmentHandlerWithMaxPositionAboveIntMaxValue)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int32_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t initialOffset = m_term_length - (2 * alignedMessageLength);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, initialOffset, m_position_bits_to_shift, INITIAL_TERM_ID);\n    const int64_t maxPosition = static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 1000L;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength * 2);  // will insert padding\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        -> aeron_controlled_fragment_handler_action_t\n    {\n        EXPECT_EQ(buffer, termBuffer(0) + initialOffset + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n        return AERON_ACTION_CONTINUE;\n    };\n\n    EXPECT_EQ(imageBoundedControlledPoll(handler, maxPosition, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, m_term_length);\n}\n\nTEST_F(ImageTest, shouldPollFragmentsToBoundedFragmentHandlerWithMaxPositionAboveIntMaxValue)\n{\n    createImage();\n\n    const size_t messageLength = 120;\n    const int32_t alignedMessageLength =\n        AERON_ALIGN(messageLength + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t initialOffset = m_term_length - (2 * alignedMessageLength);\n    const int64_t initialPosition = aeron_logbuffer_compute_position(\n        INITIAL_TERM_ID, initialOffset, m_position_bits_to_shift, INITIAL_TERM_ID);\n    const int64_t maxPosition = static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 1000L;\n\n    m_sub_pos = initialPosition;\n\n    appendMessage(initialPosition, messageLength);\n    appendMessage(initialPosition + alignedMessageLength, messageLength * 2);  // will insert padding\n\n    auto handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        EXPECT_EQ(buffer, termBuffer(0) + initialOffset + AERON_DATA_HEADER_LENGTH);\n        EXPECT_EQ(length, messageLength);\n    };\n\n    EXPECT_EQ(imageBoundedPoll(handler, maxPosition, std::numeric_limits<size_t>::max()), 1);\n    EXPECT_EQ(m_sub_pos, m_term_length);\n}\n\nTEST_F(ImageTest, shouldCorrectlyCountReferences)\n{\n    createImage();\n\n    EXPECT_EQ(1, aeron_image_refcnt_acquire(m_image));\n    EXPECT_EQ(2, aeron_image_incr_refcnt(m_image));\n    EXPECT_EQ(2, aeron_image_refcnt_acquire(m_image));\n    EXPECT_EQ(1, aeron_image_decr_refcnt(m_image));\n    EXPECT_EQ(1, aeron_image_refcnt_acquire(m_image));\n    EXPECT_EQ(0, aeron_image_decr_refcnt(m_image));\n    EXPECT_EQ(0, aeron_image_refcnt_acquire(m_image));\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_publication_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <exception>\n#include <string>\n\n#include <gtest/gtest.h>\n\n#include \"aeron_client_test_utils.h\"\n\nextern \"C\"\n{\n#include \"aeron_log_buffer.h\"\n#include \"aeron_publication.h\"\n#include \"aeron_counters.h\"\n}\n\n#define PAGE_SIZE (4 * 1024)\n#define MTU_LENGTH (4 * 1024)\n#define TERM_LENGTH (1024 * 1024)\n#define MAX_MESSAGE_SIZE (TERM_LENGTH >> 3)\n#define MAX_PAYLOAD_SIZE (MTU_LENGTH - AERON_DATA_HEADER_LENGTH)\n\n#define PUB_URI \"aeron:udp?endpoint=localhost:12345|alias=test\"\n#define STREAM_ID (101)\n#define SESSION_ID (110)\n#define REGISTRATION_ID (27)\n#define CHANNEL_STATUS_INDICATOR_ID (45)\n#define SUBSCRIBER_POSITION_ID (49)\n\nusing namespace aeron::test;\n\nclass PublicationTest : public testing::Test\n{\npublic:\n    aeron_log_buffer_t *createLogBuffer()\n    {\n        m_filename = tempFileName();\n        aeron_log_buffer_t *log_buffer = nullptr;\n        createLogFile(m_filename, TERM_LENGTH, INITIAL_TERM_ID);\n\n        if (aeron_log_buffer_create(&log_buffer, m_filename.c_str(), 1, false) < 0)\n        {\n            throw std::runtime_error(\"could not create log_buffer: \" + std::string(aeron_errmsg()));\n        }\n\n        log_buffer->mapped_raw_log.term_length = TERM_LENGTH;\n        auto *log_meta_data = (aeron_logbuffer_metadata_t *)log_buffer->mapped_raw_log.log_meta_data.addr;\n        log_meta_data->term_length = TERM_LENGTH;\n        log_meta_data->mtu_length = MTU_LENGTH;\n        log_meta_data->initial_term_id = INITIAL_TERM_ID;\n        log_meta_data->page_size = PAGE_SIZE;\n        log_meta_data->is_connected = true;\n\n        return log_buffer;\n    }\n\n    static aeron_publication_t *createPublication(\n        aeron_client_conductor_t *conductor,\n        aeron_log_buffer_t *log_buffer,\n        int32_t position_limit_counter_id,\n        int64_t *position_limit_addr,\n        int32_t channel_status_indicator_id,\n        int64_t *channel_status_addr)\n    {\n        aeron_publication_t *publication = nullptr;\n\n        if (aeron_publication_create(\n            &publication,\n            conductor,\n            ::strdup(PUB_URI),\n            STREAM_ID,\n            SESSION_ID,\n            position_limit_counter_id,\n            position_limit_addr,\n            channel_status_indicator_id,\n            channel_status_addr,\n            log_buffer,\n            REGISTRATION_ID,\n            REGISTRATION_ID) < 0)\n        {\n            throw std::runtime_error(\"could not create publication: \" + std::string(aeron_errmsg()));\n        }\n\n        return publication;\n    }\n\n    static int64_t packTail(int32_t term_id, int32_t term_offset)\n    {\n        return ((int64_t)term_id << 32) | term_offset;\n    }\n\n    static int64_t reserved_value_supplier(void *clientd, uint8_t *buffer, size_t frame_length)\n    {\n        return (int64_t)frame_length * 19;\n    }\n\n    aeron_data_header_t *verifyHeader(\n        const aeron_mapped_buffer_t *term_buffer,\n        const int32_t term_offset,\n        const int32_t expected_frame_length,\n        const int32_t expected_term_id,\n        const uint8_t expected_flags)\n    {\n        auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n        EXPECT_EQ(expected_frame_length, header->frame_header.frame_length);\n        EXPECT_EQ(AERON_HDR_TYPE_DATA, header->frame_header.type);\n        EXPECT_EQ(AERON_FRAME_HEADER_VERSION, header->frame_header.version);\n        EXPECT_EQ(expected_flags, header->frame_header.flags);\n        EXPECT_EQ(term_offset, header->term_offset);\n        EXPECT_EQ(expected_term_id, header->term_id);\n        EXPECT_EQ(m_publication->session_id, header->session_id);\n        EXPECT_EQ(m_publication->stream_id, header->stream_id);\n        return header;\n    }\n\nprotected:\n    aeron_client_conductor_t *m_conductor = nullptr;\n    aeron_log_buffer_t *m_log_buffer = nullptr;\n    aeron_publication_t *m_publication = nullptr;\n    std::string m_filename;\n\n    int64_t *m_position_limit_addr = nullptr;\n    int64_t *m_channel_status_addr = nullptr;\n\n    static const size_t NUM_COUNTERS = 4;\n    std::array<std::uint8_t, NUM_COUNTERS * AERON_COUNTERS_MANAGER_METADATA_LENGTH> m_counters_metadata = {};\n    std::array<std::uint8_t, NUM_COUNTERS * AERON_COUNTERS_MANAGER_VALUE_LENGTH> m_counters_values = {};\n    aeron_counters_manager_t m_counters_manager = {};\n    aeron_clock_cache_t m_cached_clock = {};\n\n    void SetUp() override\n    {\n        m_counters_metadata.fill(0);\n        m_counters_values.fill(0);\n        aeron_counters_manager_init(\n            &m_counters_manager,\n            m_counters_metadata.data(),\n            m_counters_metadata.size(),\n            m_counters_values.data(),\n            m_counters_values.size(),\n            &m_cached_clock,\n            0);\n\n        m_conductor = {};\n        m_log_buffer = createLogBuffer();\n\n        const int32_t position_limit_counter_id = aeron_counters_manager_allocate(\n            &m_counters_manager,\n            AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID,\n            nullptr,\n            0,\n            AERON_COUNTER_PUBLISHER_LIMIT_NAME,\n            sizeof(AERON_COUNTER_PUBLISHER_LIMIT_NAME));\n        if (position_limit_counter_id < 0)\n        {\n            throw std::runtime_error(\"could not create counter: \" + std::string(aeron_errmsg()));\n        }\n        m_position_limit_addr = aeron_counters_manager_addr(&m_counters_manager, position_limit_counter_id);\n        aeron_counter_set_release(m_position_limit_addr, INT64_MAX);\n\n        const int32_t channel_status_indicator_id = aeron_counters_manager_allocate(\n            &m_counters_manager,\n            AERON_COUNTER_SEND_CHANNEL_STATUS_TYPE_ID,\n            nullptr,\n            0,\n            AERON_COUNTER_SEND_CHANNEL_STATUS_NAME,\n            sizeof(AERON_COUNTER_SEND_CHANNEL_STATUS_NAME));\n        if (channel_status_indicator_id < 0)\n        {\n            throw std::runtime_error(\"could not create counter: \" + std::string(aeron_errmsg()));\n        }\n        m_channel_status_addr = aeron_counters_manager_addr(&m_counters_manager, channel_status_indicator_id);\n\n        m_publication = createPublication(\n            m_conductor,\n            m_log_buffer,\n            position_limit_counter_id,\n            m_position_limit_addr,\n            channel_status_indicator_id,\n            m_channel_status_addr);\n    }\n\n    void TearDown() override\n    {\n        if (nullptr != m_publication)\n        {\n            aeron_publication_delete(m_publication);\n        }\n        if (!m_filename.empty())\n        {\n            if (nullptr != m_log_buffer)\n            {\n                aeron_log_buffer_delete(m_log_buffer);\n            }\n\n            ::unlink(m_filename.c_str());\n        }\n        aeron_counters_manager_close(&m_counters_manager);\n    }\n};\n\nTEST_F(PublicationTest, offerUnfragmentedMessage)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(16781376, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    const int32_t frame_length = static_cast<int32_t>(length) + static_cast<int32_t>(AERON_DATA_HEADER_LENGTH);\n    const auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        frame_length,\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ((int64_t)frame_length * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, payload, length));\n}\n\nTEST_F(PublicationTest, offerFragmentedMessage)\n{\n    uint8_t msgBuffer[MAX_MESSAGE_SIZE];\n    memset(msgBuffer, 'x', sizeof(msgBuffer));\n    memset(msgBuffer + MAX_MESSAGE_SIZE / 2, 'a', MAX_MESSAGE_SIZE / 2);\n    const size_t length = MAX_MESSAGE_SIZE;\n    const int32_t term_count = 5;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 512;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        msgBuffer,\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(5375520, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    // first frame\n    auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        static_cast<int32_t>(MTU_LENGTH),\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG);\n    EXPECT_EQ((int64_t)MTU_LENGTH * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, msgBuffer, MTU_LENGTH - AERON_DATA_HEADER_LENGTH));\n\n    // last frame\n    const int32_t last_frame_offset = term_offset + (MAX_MESSAGE_SIZE / MAX_PAYLOAD_SIZE) * MTU_LENGTH;\n    const size_t last_frame_length = AERON_ALIGN((MAX_MESSAGE_SIZE % MAX_PAYLOAD_SIZE) + AERON_DATA_HEADER_LENGTH,\n        AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const size_t last_data_chunk_length = last_frame_length - AERON_DATA_HEADER_LENGTH;\n    header = verifyHeader(\n        term_buffer,\n        last_frame_offset,\n        static_cast<int32_t>(last_frame_length),\n        term_id,\n        AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ((int64_t)last_frame_length * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + last_frame_offset + AERON_DATA_HEADER_LENGTH,\n        msgBuffer + (MAX_MESSAGE_SIZE - last_data_chunk_length), last_data_chunk_length));\n}\n\nTEST_F(PublicationTest, vectorOfferUnfragmentedMessage)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    const size_t length = strlen(payload);\n    aeron_iovec_t iov[2];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = 5;\n    iov[1].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload)) + 5;\n    iov[1].iov_len = length - 5;\n\n    const int32_t term_count = 113;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 3072;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        2,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(118492224, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    const int32_t frame_length = static_cast<int32_t>(length) + static_cast<int32_t>(AERON_DATA_HEADER_LENGTH);\n    const auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        frame_length,\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ((int64_t)frame_length * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, payload, length));\n}\n\nTEST_F(PublicationTest, vectorOfferFragmentedMessage)\n{\n    uint8_t msgBuffer1[111];\n    uint8_t msgBuffer2[222];\n    uint8_t msgBuffer3[MAX_MESSAGE_SIZE - sizeof(msgBuffer1) - sizeof(msgBuffer2)];\n    memset(msgBuffer1, '1', sizeof(msgBuffer1));\n    memset(msgBuffer2, '2', sizeof(msgBuffer2));\n    memset(msgBuffer3, 'x', sizeof(msgBuffer3));\n    aeron_iovec_t iov[3];\n    iov[0].iov_base = msgBuffer1;\n    iov[0].iov_len = sizeof(msgBuffer1);\n    iov[1].iov_base = msgBuffer2;\n    iov[1].iov_len = sizeof(msgBuffer2);\n    iov[2].iov_base = msgBuffer3;\n    iov[2].iov_len = sizeof(msgBuffer3);\n\n    const int32_t term_count = 591;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 8192;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        3,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(619848736, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    // first frame\n    auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        static_cast<int32_t>(MTU_LENGTH),\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG);\n    EXPECT_EQ((int64_t)MTU_LENGTH * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH, msgBuffer1, sizeof(msgBuffer1)));\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH + sizeof(msgBuffer1),\n        msgBuffer2,\n        sizeof(msgBuffer2)));\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + term_offset + AERON_DATA_HEADER_LENGTH + sizeof(msgBuffer1) + sizeof(msgBuffer2),\n        msgBuffer3,\n        MAX_PAYLOAD_SIZE - (sizeof(msgBuffer1) + sizeof(msgBuffer2))));\n\n    // last frame\n    const int32_t last_frame_offset = term_offset + (MAX_MESSAGE_SIZE / MAX_PAYLOAD_SIZE) * MTU_LENGTH;\n    const size_t last_frame_length = AERON_ALIGN((MAX_MESSAGE_SIZE % MAX_PAYLOAD_SIZE) + AERON_DATA_HEADER_LENGTH,\n        AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const size_t last_data_chunk_length = last_frame_length - AERON_DATA_HEADER_LENGTH;\n    header = verifyHeader(\n        term_buffer,\n        last_frame_offset,\n        static_cast<int32_t>(last_frame_length),\n        term_id,\n        AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ((int64_t)last_frame_length * 19, header->reserved_value);\n    EXPECT_EQ(0, memcmp(\n        term_buffer->addr + last_frame_offset + AERON_DATA_HEADER_LENGTH,\n        msgBuffer3 + (sizeof(msgBuffer3) - last_data_chunk_length), last_data_chunk_length));\n}\n\nTEST_F(PublicationTest, tryClaimMaxPayloadSize)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const int32_t term_count = 3;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 96;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        MAX_PAYLOAD_SIZE,\n        &buffer_claim);\n\n    ASSERT_EQ(3149920, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    const auto header = verifyHeader(\n        term_buffer,\n        term_offset,\n        -static_cast<int32_t>(MTU_LENGTH),\n        term_id,\n        AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    EXPECT_EQ(AERON_DATA_HEADER_DEFAULT_RESERVED_VALUE, header->reserved_value);\n    EXPECT_NE(nullptr, buffer_claim.frame_header);\n    EXPECT_EQ(buffer_claim.frame_header + AERON_DATA_HEADER_LENGTH, buffer_claim.data);\n    EXPECT_EQ(MAX_PAYLOAD_SIZE, buffer_claim.length);\n}\n\nTEST_F(PublicationTest, offerErrorIfPublicationIsNull)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n\n    const int64_t position = aeron_publication_offer(\n        nullptr,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, offerErrorIfBufferIsNull)\n{\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        nullptr,\n        10,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, offerClosed)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    m_publication->is_closed = true;\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_CLOSED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, offerAdminActionIfTermCountDoesNotMatch)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_offset = 4096;\n    m_publication->log_meta_data->active_term_count = 5;\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, offerBackPressureIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_BACK_PRESSURED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, offerNotConnectedIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = 3;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 1024;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    m_publication->log_meta_data->is_connected = false;\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset - 32, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_NOT_CONNECTED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, offerMaxPositionExceededIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const auto term_offset = (int32_t)(TERM_LENGTH - length - 1);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    aeron_counter_set_release(m_position_limit_addr, 64);\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, offerPublicationErrorIfMessageIsLargerThanMaxMessageSize)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const int32_t term_count = 5;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)TERM_LENGTH;\n    const int32_t term_id = term_count + INITIAL_TERM_ID;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        MAX_MESSAGE_SIZE + 1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, offerAdminActionAfterRolloingOverToTheNextTerm)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = 51;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const size_t next_partition_index = aeron_logbuffer_index_by_term_count(term_count + 1);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = term_count + INITIAL_TERM_ID;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    m_publication->log_meta_data->term_tail_counters[next_partition_index] =\n        packTail(term_id + 1 - AERON_LOGBUFFER_PARTITION_COUNT, 999888);\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n    EXPECT_EQ(\n        packTail(term_id + 1, 0),\n        m_publication->log_meta_data->term_tail_counters[next_partition_index]);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 8, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, offerMaxPositionExceededAfterSuccessfulSpaceClaim)\n{\n    const char *payload = \"Aeron is awesome!\";\n    const size_t length = strlen(payload);\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const size_t next_partition_index = (partition_index + 1) % AERON_LOGBUFFER_PARTITION_COUNT;\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    m_publication->log_meta_data->term_tail_counters[next_partition_index] =\n        packTail(term_id + 1 - AERON_LOGBUFFER_PARTITION_COUNT, 2846386);\n\n    const int64_t position = aeron_publication_offer(\n        m_publication,\n        reinterpret_cast<const uint8_t *>(payload),\n        length,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n    EXPECT_EQ(\n        packTail(term_id + 1 - AERON_LOGBUFFER_PARTITION_COUNT, 2846386),\n        m_publication->log_meta_data->term_tail_counters[next_partition_index]);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 8, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferErrorIfPublicationIsNull)\n{\n    aeron_iovec_t iov[1];\n\n    const int64_t position = aeron_publication_offerv(\n        nullptr,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferErrorIfBufferIsNull)\n{\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        nullptr,\n        10,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferClosed)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    m_publication->is_closed = true;\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_CLOSED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferAdminActionIfTermCountDoesNotMatch)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = strlen(payload);\n    const int32_t term_offset = 4096;\n    m_publication->log_meta_data->active_term_count = 5;\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferBackPressureIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = strlen(payload);\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_BACK_PRESSURED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferNotConnectedIfPublicationLimitReached)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = strlen(payload);\n    const int32_t term_count = 3;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 1024;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    m_publication->log_meta_data->is_connected = false;\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset - 32, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_NOT_CONNECTED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferMaxPositionExceededIfPublicationLimitReached)\n{\n    const char *payload = \"Test, test, test.\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = strlen(payload);\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    aeron_counter_set_release(m_position_limit_addr, 64);\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferPublicationErrorIfMessageIsLargerThanMaxMessageSize)\n{\n    const char *payload = \"Aeron is awesome vector!\";\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = MAX_MESSAGE_SIZE + 1;\n    const int32_t term_count = 5;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)TERM_LENGTH;\n    const int32_t term_id = term_count + INITIAL_TERM_ID;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferAdminActionAfterRolloingOverToTheNextTerm)\n{\n    const char *payload = \"This does not fit into the end of the buffer...\";\n    const size_t length = strlen(payload);\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = length;\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = 51;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const size_t next_partition_index = aeron_logbuffer_index_by_term_count(term_count + 1);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = term_count + INITIAL_TERM_ID;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    m_publication->log_meta_data->term_tail_counters[next_partition_index] =\n        packTail(term_id + 1 - AERON_LOGBUFFER_PARTITION_COUNT, 999888);\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n    EXPECT_EQ(\n        packTail(term_id + 1, 0),\n        m_publication->log_meta_data->term_tail_counters[next_partition_index]);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 8, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, vectorOfferMaxPositionExceededAfterSuccessfulSpaceClaim)\n{\n    const char *payload = \"Aeron is awesome squared!\";\n    const size_t length = strlen(payload);\n    aeron_iovec_t iov[1];\n    iov[0].iov_base = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(payload));\n    iov[0].iov_len = length;\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const size_t next_partition_index = (partition_index + 1) % AERON_LOGBUFFER_PARTITION_COUNT;\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    m_publication->log_meta_data->term_tail_counters[next_partition_index] =\n        packTail(term_id + 1 - AERON_LOGBUFFER_PARTITION_COUNT, 2846386);\n\n    const int64_t position = aeron_publication_offerv(\n        m_publication,\n        iov,\n        1,\n        reserved_value_supplier,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n    EXPECT_EQ(\n        packTail(term_id + 1 - AERON_LOGBUFFER_PARTITION_COUNT, 2846386),\n        m_publication->log_meta_data->term_tail_counters[next_partition_index]);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 8, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimErrorIfPublicationIsNull)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n\n    const int64_t position = aeron_publication_try_claim(\n        nullptr,\n        1,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimErrorIfBufferIsNull)\n{\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        1,\n        nullptr);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimClosed)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 5;\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    m_publication->is_closed = true;\n\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_CLOSED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimAdminActionIfTermCountDoesNotMatch)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 13;\n    const int32_t term_offset = 4096;\n    m_publication->log_meta_data->active_term_count = 5;\n\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[0];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimBackPressureIfPublicationLimitReached)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 5;\n    const int32_t term_count = 16;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 4096;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_BACK_PRESSURED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimNotConnectedIfPublicationLimitReached)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 5;\n    const int32_t term_count = 3;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = 1024;\n    const int32_t term_id = m_publication->initial_term_id + term_count;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    m_publication->log_meta_data->is_connected = false;\n    const int64_t limit_position = aeron_logbuffer_compute_position(\n        term_id, term_offset - 32, m_publication->position_bits_to_shift, m_publication->initial_term_id);\n    aeron_counter_set_release(m_position_limit_addr, limit_position);\n\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_NOT_CONNECTED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimMaxPositionExceededIfPublicationLimitReached)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 5;\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    aeron_counter_set_release(m_position_limit_addr, 64);\n\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimPublicationErrorIfMessageIsLargerThanMaxPayloadSize)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const int32_t term_count = 5;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const int32_t term_offset = (int32_t)TERM_LENGTH;\n    const int32_t term_id = term_count + (int32_t)INITIAL_TERM_ID;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        MAX_PAYLOAD_SIZE + 1,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_ERROR, position);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(0, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimAdminActionAfterRolloingOverToTheNextTerm)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 55;\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = 51;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const size_t next_partition_index = aeron_logbuffer_index_by_term_count(term_count + 1);\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = term_count + (int32_t)INITIAL_TERM_ID;\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    m_publication->log_meta_data->term_tail_counters[next_partition_index] =\n        packTail(term_id + 1 - AERON_LOGBUFFER_PARTITION_COUNT, 999888);\n\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_ADMIN_ACTION, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n    EXPECT_EQ(\n        packTail(term_id + 1, 0),\n        m_publication->log_meta_data->term_tail_counters[next_partition_index]);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 8, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n\nTEST_F(PublicationTest, tryClaimMaxPositionExceededAfterSuccessfulSpaceClaim)\n{\n    aeron_buffer_claim_t buffer_claim = {};\n    const size_t length = 19;\n    const int32_t frame_length = AERON_ALIGN(length + AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t term_count = INT32_MAX;\n    const size_t partition_index = aeron_logbuffer_index_by_term_count(term_count);\n    const size_t next_partition_index = (partition_index + 1) % AERON_LOGBUFFER_PARTITION_COUNT;\n    const int32_t term_offset = (int32_t)(TERM_LENGTH - AERON_DATA_HEADER_LENGTH - 8);\n    const int32_t term_id = INT32_MIN + (INITIAL_TERM_ID - 1);\n    m_publication->log_meta_data->active_term_count = term_count;\n    m_publication->log_meta_data->term_tail_counters[partition_index] = packTail(term_id, term_offset);\n    m_publication->log_meta_data->term_tail_counters[next_partition_index] =\n        packTail(term_id + 1 - AERON_LOGBUFFER_PARTITION_COUNT, 2846386);\n\n    const int64_t position = aeron_publication_try_claim(\n        m_publication,\n        length,\n        &buffer_claim);\n\n    ASSERT_EQ(AERON_PUBLICATION_MAX_POSITION_EXCEEDED, position);\n    EXPECT_EQ(\n        packTail(term_id, term_offset + frame_length),\n        m_publication->log_meta_data->term_tail_counters[partition_index]);\n    EXPECT_EQ(\n        packTail(term_id + 1 - AERON_LOGBUFFER_PARTITION_COUNT, 2846386),\n        m_publication->log_meta_data->term_tail_counters[next_partition_index]);\n    const aeron_mapped_buffer_t *term_buffer = &m_publication->log_buffer->mapped_raw_log.term_buffers[partition_index];\n    auto *header = (aeron_data_header_t *)(term_buffer->addr + term_offset);\n    EXPECT_EQ(AERON_DATA_HEADER_LENGTH + 8, header->frame_header.frame_length);\n    EXPECT_EQ(AERON_HDR_TYPE_PAD, header->frame_header.type);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_subscription_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <exception>\n#include <functional>\n#include <string>\n\n#include <gtest/gtest.h>\n\n#include \"aeron_client_test_utils.h\"\n\nextern \"C\"\n{\n#include \"aeron_subscription.h\"\n#include \"aeron_image.h\"\n}\n\n#define FILE_PAGE_SIZE (4 * 1024)\n\n#define SUB_URI \"aeron:udp?endpoint=localhost:24567\"\n#define STREAM_ID (101)\n#define SESSION_ID (110)\n#define REGISTRATION_ID (27)\n#define CHANNEL_STATUS_INDICATOR_ID (45)\n#define SUBSCRIBER_POSITION_ID (49)\n\nusing namespace aeron::test;\n\nclass SubscriptionTest : public testing::Test\n{\npublic:\n    SubscriptionTest() :\n        m_conductor(nullptr),\n        m_subscription(createSubscription(m_conductor, &m_channel_status))\n    {\n    }\n\n    ~SubscriptionTest() override\n    {\n        if (nullptr != m_subscription)\n        {\n            aeron_subscription_delete(m_subscription);\n        }\n\n        for (auto &filename : m_filenames)\n        {\n            ::unlink(filename.c_str());\n        }\n    }\n\n    static aeron_subscription_t *createSubscription(aeron_client_conductor_t *conductor, int64_t *channel_status)\n    {\n        aeron_subscription_t *subscription = nullptr;\n\n        if (aeron_subscription_create(\n            &subscription,\n            conductor,\n            ::strdup(SUB_URI),\n            STREAM_ID,\n            REGISTRATION_ID,\n            CHANNEL_STATUS_INDICATOR_ID,\n            channel_status,\n            nullptr,\n            nullptr,\n            nullptr,\n            nullptr) < 0)\n        {\n            throw std::runtime_error(\"could not create subscription: \" + std::string(aeron_errmsg()));\n        }\n\n        return subscription;\n    }\n\n    int64_t createImage(int64_t *sub_pos)\n    {\n        aeron_image_t *image = nullptr;\n        aeron_log_buffer_t *log_buffer = nullptr;\n        std::string filename = tempFileName();\n\n        createLogFile(filename);\n\n        if (aeron_log_buffer_create(&log_buffer, filename.c_str(), m_correlationId, false) < 0)\n        {\n            throw std::runtime_error(\"could not create log_buffer: \" + std::string(aeron_errmsg()));\n        }\n\n        if (aeron_image_create(\n            &image,\n            m_subscription,\n            m_conductor,\n            log_buffer,\n            SUBSCRIBER_POSITION_ID,\n            sub_pos,\n            m_correlationId,\n            (int32_t)m_correlationId,\n            \"none\",\n            strlen(\"none\")) < 0)\n        {\n            throw std::runtime_error(\"could not create image: \" + std::string(aeron_errmsg()));\n        }\n\n        m_imageMap.insert(std::pair<int64_t, aeron_image_t *>(m_correlationId, image));\n        m_filenames.emplace_back(filename);\n\n        return m_correlationId++;\n    }\n\n    static void null_fragment_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n    }\n\nprotected:\n    aeron_client_conductor_t *m_conductor = nullptr;\n    aeron_subscription_t *m_subscription = nullptr;\n    int64_t m_channel_status = AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_ACTIVE;\n    int64_t m_sub_pos = 0;\n\n    int64_t m_correlationId = 0;\n\n    std::map<int64_t, aeron_image_t *> m_imageMap;\n    std::vector<std::string> m_filenames;\n};\n\nTEST_F(SubscriptionTest, shouldInitAndDelete)\n{\n}\n\nTEST_F(SubscriptionTest, shouldAddAndRemoveImageWithoutPoll)\n{\n    int64_t image_id = createImage(&m_sub_pos);\n    aeron_image_t *image = m_imageMap.find(image_id)->second;\n\n    ASSERT_EQ(aeron_client_conductor_subscription_add_image(m_subscription, image), 0);\n    EXPECT_EQ(aeron_subscription_last_image_list_change_number(m_subscription), -1);\n    EXPECT_EQ(aeron_subscription_image_count(m_subscription), 1);\n\n    ASSERT_EQ(aeron_client_conductor_subscription_remove_image(m_subscription, image), 0);\n    EXPECT_EQ(aeron_subscription_last_image_list_change_number(m_subscription), 0);\n    EXPECT_EQ(aeron_subscription_image_count(m_subscription), 0);\n\n    EXPECT_EQ(aeron_client_conductor_subscription_prune_image_lists(m_subscription), 2);\n    EXPECT_FALSE(\n        aeron_image_is_in_use_by_subscription(image, aeron_subscription_last_image_list_change_number(m_subscription)));\n\n    aeron_log_buffer_delete(image->log_buffer);\n    aeron_image_delete(image);\n}\n\nTEST_F(SubscriptionTest, shouldAddAndRemoveImageWithPollAfter)\n{\n    int64_t image_id = createImage(&m_sub_pos);\n    aeron_image_t *image = m_imageMap.find(image_id)->second;\n\n    ASSERT_EQ(aeron_client_conductor_subscription_add_image(m_subscription, image), 0);\n    ASSERT_EQ(aeron_client_conductor_subscription_remove_image(m_subscription, image), 0);\n    ASSERT_EQ(aeron_subscription_poll(m_subscription, null_fragment_handler, this, 1), 0);\n\n    EXPECT_EQ(aeron_subscription_image_count(m_subscription), 0);\n    EXPECT_EQ(aeron_client_conductor_subscription_prune_image_lists(m_subscription), 2);\n    EXPECT_FALSE(\n        aeron_image_is_in_use_by_subscription(image, aeron_subscription_last_image_list_change_number(m_subscription)));\n\n    aeron_log_buffer_delete(image->log_buffer);\n    aeron_image_delete(image);\n}\n\nTEST_F(SubscriptionTest, shouldAddAndRemoveImageWithPollBetween)\n{\n    int64_t image_id = createImage(&m_sub_pos);\n    aeron_image_t *image = m_imageMap.find(image_id)->second;\n\n    ASSERT_EQ(aeron_client_conductor_subscription_add_image(m_subscription, image), 0);\n    ASSERT_EQ(aeron_subscription_poll(m_subscription, null_fragment_handler, this, 1), 0);\n    ASSERT_EQ(aeron_client_conductor_subscription_remove_image(m_subscription, image), 0);\n    EXPECT_EQ(aeron_subscription_image_count(m_subscription), 0);\n\n    EXPECT_EQ(aeron_subscription_image_count(m_subscription), 0);\n    EXPECT_EQ(aeron_client_conductor_subscription_prune_image_lists(m_subscription), 2);\n    EXPECT_FALSE(\n        aeron_image_is_in_use_by_subscription(image, aeron_subscription_last_image_list_change_number(m_subscription)));\n\n    aeron_log_buffer_delete(image->log_buffer);\n    aeron_image_delete(image);\n}\n\nTEST_F(SubscriptionTest, shouldFetchConstants)\n{\n    aeron_subscription_constants_t constants;\n    ASSERT_EQ(0, aeron_subscription_constants(m_subscription, &constants));\n    ASSERT_NE(0, constants.channel_status_indicator_id);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_uri_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include \"gmock/gmock-matchers.h\"\n#include \"command/aeron_control_protocol.h\"\n\nextern \"C\"\n{\n#include \"aeron_alloc.h\"\n#include \"uri/aeron_uri.h\"\n#include \"uri/aeron_uri_string_builder.h\"\n#include \"util/aeron_netutil.h\"\n}\n\nstatic void assertParamsAreEqual(aeron_uri_params_t *params1, aeron_uri_params_t *params2)\n{\n    EXPECT_EQ(params1->length, params2->length);\n    for (size_t i = 0; i < params1->length; i++)\n    {\n        const auto param = params1->array[i];\n        EXPECT_STREQ(param.value, aeron_uri_find_param_value(params2, param.key));\n    }\n}\n\nclass UriTest : public testing::Test\n{\npublic:\n\n    ~UriTest() override\n    {\n        aeron_uri_close(&m_uri);\n    }\n\n    static void assertUriWasRejected(aeron_uri_t *uri)\n    {\n        EXPECT_EQ(uri->type, AERON_URI_UNKNOWN);\n\n        EXPECT_EQ(uri->params.udp.additional_params.length, 0);\n        EXPECT_EQ(uri->params.udp.additional_params.array, nullptr);\n        EXPECT_EQ(uri->params.udp.endpoint, nullptr);\n        EXPECT_EQ(uri->params.udp.bind_interface, nullptr);\n        EXPECT_EQ(uri->params.udp.ttl, nullptr);\n        EXPECT_EQ(uri->params.udp.control, nullptr);\n        EXPECT_EQ(uri->params.udp.control_mode, nullptr);\n        EXPECT_EQ(uri->params.udp.channel_tag, nullptr);\n        EXPECT_EQ(uri->params.udp.entity_tag, nullptr);\n\n        EXPECT_EQ(uri->params.ipc.additional_params.length, 0);\n        EXPECT_EQ(uri->params.ipc.additional_params.array, nullptr);\n        EXPECT_EQ(uri->params.ipc.channel_tag, nullptr);\n        EXPECT_EQ(uri->params.ipc.entity_tag, nullptr);\n    }\n\nprotected:\n    aeron_uri_t m_uri = {};\n};\n\n#define AERON_URI_PARSE(uri_str, uri) aeron_uri_parse(strlen(uri_str), uri_str, uri)\n\nTEST_F(UriTest, shouldRejectNullParams)\n{\n    EXPECT_EQ(aeron_uri_parse(9, \"aeron:ipc\", nullptr), -1);\n    EXPECT_EQ(-AERON_ERROR_CODE_INVALID_CHANNEL, aeron_errcode());\n    EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(\"params is NULL\"));\n}\n\nTEST_F(UriTest, shouldRejectNullUri)\n{\n    EXPECT_EQ(aeron_uri_parse(5, nullptr, &m_uri), -1);\n    EXPECT_EQ(-AERON_ERROR_CODE_INVALID_CHANNEL, aeron_errcode());\n    EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(\"channel URI is NULL\"));\n    assertUriWasRejected(&m_uri);\n}\n\nTEST_F(UriTest, shouldNotParseInvalidUriScheme)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aaron\", &m_uri), -1);\n    assertUriWasRejected(&m_uri);\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:\", &m_uri), -1);\n    assertUriWasRejected(&m_uri);\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aron:\", &m_uri), -1);\n    assertUriWasRejected(&m_uri);\n\n    EXPECT_EQ(AERON_URI_PARSE(\":aeron\", &m_uri), -1);\n    assertUriWasRejected(&m_uri);\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp:\", &m_uri), -1);\n    assertUriWasRejected(&m_uri);\n}\n\nTEST_F(UriTest, shouldNotParseUnknownUriTransport)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:tcp\", &m_uri), -1);\n    assertUriWasRejected(&m_uri);\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:sctp\", &m_uri), -1);\n    assertUriWasRejected(&m_uri);\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp\", &m_uri), -1);\n    assertUriWasRejected(&m_uri);\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipcsdfgfdhfgf\", &m_uri), -1);\n    assertUriWasRejected(&m_uri);\n}\n\nTEST_F(UriTest, shouldRejectWithMissingQuerySeparatorWhenFollowedWithParams)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc|sparse=true\", &m_uri), -1);\n}\n\nTEST_F(UriTest, shouldRejectWithInvalidParams)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=localhost:4652|-~@{]|=??#s!£$%====\", &m_uri), -1);\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?add|ress=224.10.9.8\", &m_uri), -1);\n}\n\nTEST_F(UriTest, shouldParseKnownUriTransportWithoutParamsIpcNoSeparator)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc\", &m_uri), 0);\n    ASSERT_EQ(m_uri.type, AERON_URI_IPC);\n    EXPECT_EQ(m_uri.params.ipc.additional_params.length, 0u);\n}\n\nTEST_F(UriTest, shouldParseKnownUriTransportWithoutParamsUdpWithSeparator)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?\", &m_uri), 0);\n    EXPECT_EQ(m_uri.type, AERON_URI_UDP);\n}\n\nTEST_F(UriTest, shouldParseKnownUriTransportWithoutParamsIpcWithSeparator)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?\", &m_uri), 0);\n    EXPECT_EQ(m_uri.type, AERON_URI_IPC);\n    EXPECT_EQ(m_uri.params.ipc.additional_params.length, 0u);\n}\n\nTEST_F(UriTest, shouldParseWithSingleParamUdpEndpoint)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8\", &m_uri), 0);\n    ASSERT_EQ(m_uri.type, AERON_URI_UDP);\n    EXPECT_EQ(std::string(m_uri.params.udp.endpoint), \"224.10.9.8\");\n    EXPECT_EQ(m_uri.params.udp.additional_params.length, 0u);\n}\n\nTEST_F(UriTest, shouldParseWithSingleParamUdpValueWithEmbeddedEquals)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.1=0.9.8\", &m_uri), 0);\n    ASSERT_EQ(m_uri.type, AERON_URI_UDP);\n    EXPECT_EQ(std::string(m_uri.params.udp.endpoint), \"224.1=0.9.8\");\n    EXPECT_EQ(m_uri.params.udp.additional_params.length, 0u);\n}\n\nTEST_F(UriTest, shouldParseWithMultipleParams)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|port=4567|interface=192.168.0.3|ttl=16\", &m_uri), 0);\n    ASSERT_EQ(m_uri.type, AERON_URI_UDP);\n    EXPECT_EQ(std::string(m_uri.params.udp.endpoint), \"224.10.9.8\");\n    EXPECT_EQ(std::string(m_uri.params.udp.bind_interface), \"192.168.0.3\");\n    EXPECT_EQ(std::string(m_uri.params.udp.ttl), \"16\");\n    EXPECT_EQ(m_uri.params.udp.additional_params.length, 1u);\n    EXPECT_EQ(std::string(m_uri.params.udp.additional_params.array[0].key), \"port\");\n    EXPECT_EQ(std::string(m_uri.params.udp.additional_params.array[0].value), \"4567\");\n}\n\nTEST_F(UriTest, shouldRejectsUriIfLengthExceedsMaxUriLength)\n{\n    EXPECT_EQ(aeron_uri_parse(1000000, \"aeron:ipc\", &m_uri), -1);\n    EXPECT_EQ(-AERON_ERROR_CODE_INVALID_CHANNEL, aeron_errcode());\n    EXPECT_NE(\n        std::string::npos,\n        std::string(aeron_errmsg()).find(\"URI length (1000000) exceeds max supported length (4095): aeron:ipc\"));\n    assertUriWasRejected(&m_uri);\n}\n\nTEST_F(UriTest, shouldRejectsUriIfLengthMatchesMaxUriLength)\n{\n    EXPECT_EQ(aeron_uri_parse(AERON_URI_MAX_LENGTH, \"aeron:ipc\", &m_uri), -1);\n    EXPECT_EQ(-AERON_ERROR_CODE_INVALID_CHANNEL, aeron_errcode());\n    EXPECT_NE(\n        std::string::npos,\n        std::string(aeron_errmsg()).find(\"URI length (4096) exceeds max supported length (4095): aeron:ipc\"));\n    assertUriWasRejected(&m_uri);\n}\n\nTEST_F(UriTest, shouldCanParseUriOfMaxAllowedLength)\n{\n    const auto base_uri = std::string(\"aeron:ipc?alias=\");\n    const auto uri = std::string(base_uri).append(AERON_URI_MAX_LENGTH - 1 - base_uri.length(), 'x');\n    ASSERT_EQ(AERON_URI_MAX_LENGTH - 1, uri.length());\n\n    EXPECT_EQ(aeron_uri_parse(uri.length(), uri.c_str(), &m_uri), 0);\n}\n\nTEST_F(UriTest, shouldParseStreamId)\n{\n    EXPECT_EQ(0, AERON_URI_PARSE(\"aeron:ipc?stream-id=42\", &m_uri));\n    EXPECT_STREQ(\"42\", aeron_uri_find_param_value(&m_uri.params.ipc.additional_params, AERON_URI_STREAM_ID_KEY));\n}\n\nTEST_F(UriTest, shouldParsePublicationWindow)\n{\n    EXPECT_EQ(0, AERON_URI_PARSE(\"aeron:udp?pub-wnd=128k\", &m_uri));\n    EXPECT_STREQ(\"128k\", aeron_uri_find_param_value(&m_uri.params.udp.additional_params, AERON_URI_PUBLICATION_WINDOW_KEY));\n}\n\n/*\n * WARNING: single threaded only due to global lookup func usage\n */\n\nstruct ifaddrs *global_ifaddrs = nullptr;\n\nclass UriLookupTest : public testing::Test\n{\npublic:\n    UriLookupTest()\n    {\n        aeron_set_getifaddrs(UriLookupTest::getifaddrs, UriLookupTest::freeifaddrs);\n    }\n\n    static void add_ifaddr(\n        int family, const char *name, const char *addr_str, const char *netmask_str, unsigned int flags)\n    {\n        void *addr = nullptr, *netmask = nullptr;\n        struct ifaddrs *entry = nullptr;\n        aeron_alloc((void **)&entry, sizeof(struct ifaddrs));\n\n        if (family == AF_INET6)\n        {\n            struct sockaddr_in6 *a = nullptr;\n            aeron_alloc((void **)&a, sizeof(struct sockaddr_in6));\n            addr = &a->sin6_addr;\n            a->sin6_family = AF_INET6;\n            entry->ifa_addr = (struct sockaddr *)a;\n\n            struct sockaddr_in6 *b = nullptr;\n            aeron_alloc((void **)&b, sizeof(struct sockaddr_in6));\n            netmask = &b->sin6_addr;\n            b->sin6_family = AF_INET6;\n            entry->ifa_netmask = (struct sockaddr *)b;\n        }\n        else\n        {\n            struct sockaddr_in *a = nullptr;\n            aeron_alloc((void **)&a, sizeof(struct sockaddr_in));\n            addr = &a->sin_addr;\n            a->sin_family = AF_INET;\n            entry->ifa_addr = (struct sockaddr *)a;\n\n            struct sockaddr_in *b = nullptr;\n            aeron_alloc((void **)&b, sizeof(struct sockaddr_in));\n            netmask = &b->sin_addr;\n            b->sin_family = AF_INET;\n            entry->ifa_netmask = reinterpret_cast<struct sockaddr *>(b);\n        }\n\n        if (inet_pton(family, addr_str, addr) != 1 || inet_pton(family, netmask_str, netmask) != 1)\n        {\n            throw std::runtime_error(\"could not convert address\");\n        }\n\n        entry->ifa_name = ::strdup(name);\n        entry->ifa_flags = flags;\n        entry->ifa_next = global_ifaddrs;\n        global_ifaddrs = entry;\n    }\n\n    static void initialize_ifaddrs()\n    {\n        if (nullptr == global_ifaddrs)\n        {\n            add_ifaddr(AF_INET, \"lo0\", \"127.0.0.1\", \"255.0.0.0\", IFF_MULTICAST | IFF_UP | IFF_LOOPBACK);\n            add_ifaddr(AF_INET, \"eth0:0\", \"192.168.0.20\", \"255.255.255.0\", IFF_MULTICAST | IFF_UP);\n            add_ifaddr(AF_INET, \"eth0:1\", \"192.168.1.21\", \"255.255.255.0\", IFF_MULTICAST | IFF_UP);\n            add_ifaddr(AF_INET6, \"eth1:0\", \"ee80:0:0:0001:0:0:0:1\", \"FFFF:FFFF:FFFF:FFFF::\", IFF_MULTICAST | IFF_UP);\n            add_ifaddr(AF_INET6, \"eth1:3\", \"fe80:1:abcd:0:0:0:0:1\", \"FFFF:FFFF:FFFF::\", IFF_MULTICAST | IFF_UP);\n            add_ifaddr(AF_INET6, \"eth1:1\", \"fe80:0:0:0:0:0:0:1\", \"FFFF::\", IFF_MULTICAST | IFF_UP);\n            add_ifaddr(AF_INET6, \"eth1:2\", \"fe80:1:0:0:0:0:0:1\", \"FFFF:FFFF::\", IFF_MULTICAST | IFF_UP);\n            add_ifaddr(AF_INET, \"vlan.13\", \"172.18.13.5\", \"255.255.255.224\", IFF_MULTICAST | IFF_UP);\n            add_ifaddr(AF_INET, \"eth2\", \"10.0.1.2\", \"255.255.255.0\", IFF_MULTICAST | IFF_UP);\n            add_ifaddr(AF_INET, \"eth2\", \"10.0.0.2\", \"255.255.255.0\", IFF_MULTICAST | IFF_UP);\n            add_ifaddr(AF_INET6, \"eth2\", \"2001:db8::1\", \"FFFF:FFFF::\", IFF_MULTICAST | IFF_UP);\n        }\n    }\n\n    static int getifaddrs(struct ifaddrs **ifaddrs)\n    {\n        initialize_ifaddrs();\n        *ifaddrs = global_ifaddrs;\n        return 0;\n    }\n\n    static void freeifaddrs(struct ifaddrs *)\n    {\n    }\n\nprotected:\n};\n\nTEST_F(UriLookupTest, shouldFindIpv4Loopback)\n{\n    char buffer[AERON_URI_MAX_LENGTH] = { 0 };\n    struct sockaddr_storage addr = {};\n    auto *addr_in = (struct sockaddr_in *)&addr;\n    unsigned int if_index;\n\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"127.0.0.0/16\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"127.0.0.1\");\n}\n\nTEST_F(UriLookupTest, shouldFindIpv4LoopbackAsLocalhost)\n{\n    char buffer[AERON_URI_MAX_LENGTH] = { 0 };\n    struct sockaddr_storage addr = {};\n    auto *addr_in = (struct sockaddr_in *)&addr;\n    unsigned int if_index;\n\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"localhost:40123\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"127.0.0.1\");\n    EXPECT_EQ(addr_in->sin_port, htons(40123));\n}\n\nTEST_F(UriLookupTest, shouldFindIpv6)\n{\n    char buffer[AERON_URI_MAX_LENGTH] = { 0 };\n    struct sockaddr_storage addr = {};\n    auto *addr_in6 = (struct sockaddr_in6 *)&addr;\n    unsigned int if_index;\n\n    ASSERT_EQ(aeron_find_interface(AF_INET6, \"[fe80:0:0:0:0:0:0:0]/16\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_EQ(addr_in6->sin6_family, AF_INET6);\n    EXPECT_STREQ(inet_ntop(AF_INET6, &addr_in6->sin6_addr, buffer, sizeof(buffer)), \"fe80:1:abcd::1\");\n}\n\nTEST_F(UriLookupTest, shouldNotFindUnknown)\n{\n    struct sockaddr_storage addr = {};\n    unsigned int if_index = 0;\n\n    ASSERT_EQ(aeron_find_interface(AF_INET6, \"[fe80:ffff:0:0:0:0:0:0]/32\", (struct sockaddr_storage *)&addr, &if_index), -1);\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"127.0.0.10/32\", (struct sockaddr_storage *)&addr, &if_index), -1);\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"172.116.1.20/12\", (struct sockaddr_storage *)&addr, &if_index), -1);\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"192.168.2.20/24\", (struct sockaddr_storage *)&addr, &if_index), -1);\n}\n\nTEST_F(UriLookupTest, shouldFindIPv4Multicast)\n{\n    char buffer[AERON_URI_MAX_LENGTH] = { 0 };\n    struct sockaddr_storage addr = {};\n    auto *addr_in = (struct sockaddr_in *)&addr;\n    unsigned int if_index = 0;\n\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"172.18.13.0/27\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"172.18.13.5\");\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"172.18.13.5\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"172.18.13.5\");\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"172.18.13.5/32\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"172.18.13.5\");\n}\n\nTEST_F(UriLookupTest, shouldFindNamedInterface)\n{\n    char buffer[AERON_URI_MAX_LENGTH] = { 0 };\n    struct sockaddr_storage addr = {};\n    auto *addr_in = (struct sockaddr_in *)&addr;\n    unsigned int if_index = 0;\n\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"{lo0}\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"127.0.0.1\");\n    EXPECT_EQ(addr_in->sin_port, htons(0));\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"{eth0:1}:65535\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"192.168.1.21\");\n    EXPECT_EQ(addr_in->sin_port, htons(65535));\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"{vlan.13}\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"172.18.13.5\");\n    EXPECT_EQ(addr_in->sin_port, htons(0));\n}\n\nTEST_F(UriLookupTest, shouldNotFindUnknownNamedInterface)\n{\n    struct sockaddr_storage addr = {};\n    unsigned int if_index = 0;\n\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"{eth13}\", (struct sockaddr_storage *)&addr, &if_index), -1);\n    ASSERT_THAT(aeron_errmsg(), testing::HasSubstr(\"unknown interface eth13\"));\n}\n\nTEST_F(UriLookupTest, shouldFindAddressOfTheGivenFamilyOnNamedInterface)\n{\n    char buffer[AERON_URI_MAX_LENGTH] = { 0 };\n    struct sockaddr_storage addr = {};\n    auto *addr_in = (struct sockaddr_in *)&addr;\n    auto *addr_in6 = (struct sockaddr_in6 *)&addr;\n    unsigned int if_index = 0;\n\n    ASSERT_EQ(aeron_find_interface(AF_INET, \"{eth2}\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"10.0.0.2\");\n\n    ASSERT_EQ(aeron_find_interface(AF_INET6, \"{eth2}\", (struct sockaddr_storage *)&addr, &if_index), 0);\n    EXPECT_STREQ(inet_ntop(AF_INET6, &addr_in6->sin6_addr, buffer, sizeof(buffer)), \"2001:db8::1\");\n}\n\nTEST_F(UriLookupTest, shouldNotFindUnavailableFamilyOnAvailableNamedInterface)\n{\n    struct sockaddr_storage addr = {};\n    unsigned int if_index = 0;\n\n    ASSERT_EQ(aeron_find_interface(AF_INET6, \"{vlan.13}\", (struct sockaddr_storage *)&addr, &if_index), -1);\n    ASSERT_THAT(aeron_errmsg(), testing::HasSubstr(\"no INET6 addresses found on interface vlan.13\"));\n}\n\nclass UriPrintTest : public testing::TestWithParam<const char *>\n{\npublic:\n    ~UriPrintTest() override\n    {\n        aeron_uri_close(&m_uri);\n    }\n\n\nprotected:\n    aeron_uri_t m_uri = {};\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    UriPrintTestWithParams, \n    UriPrintTest, \n    testing::Values(\n        \"aeron:udp?endpoint=224.10.9.8:1234|interface=192.168.0.3|control=192.168.0.3:4321|control-mode=manual|tags=1,2|ttl=16|cc=cubic\",\n        \"aeron:udp?endpoint=224.10.9.8:1234|interface=192.168.0.3\",\n        \"aeron:udp?endpoint=224.10.9.8:1234|fc=tagged|session-id=123\",\n        \"aeron:ipc?tags=2\",\n        \"aeron:ipc?tags=1,2|session-id=123\"\n        ));\n\nTEST_P(UriPrintTest, shouldPrintSimpleUri)\n{\n    char print_buffer[AERON_URI_MAX_LENGTH] = { 0 };\n    const char *uri = GetParam();\n\n    EXPECT_EQ(AERON_URI_PARSE(uri, &m_uri), 0);\n    ASSERT_GT(aeron_uri_sprint(&m_uri, print_buffer, sizeof(print_buffer)), 0);\n    EXPECT_STREQ(uri, print_buffer);\n}\n\nTEST_P(UriPrintTest, shouldPrintWithNarrowTruncation)\n{\n    char print_buffer[5] = { 0 };\n    char temp_buffer[5] = { 0 };\n    const char *uri = GetParam();\n\n    EXPECT_EQ(AERON_URI_PARSE(uri, &m_uri), 0);\n    ASSERT_GE(aeron_uri_sprint(&m_uri, print_buffer, sizeof(print_buffer)), 5);\n\n    strncpy(temp_buffer, uri, sizeof(temp_buffer) - 1);\n    temp_buffer[4] = '\\0';\n\n    EXPECT_STREQ(temp_buffer, print_buffer);\n}\n\nTEST_P(UriPrintTest, shouldPrintWithTruncation)\n{\n    char print_buffer[16] = { 0 };\n    char temp_buffer[16] = { 0 };\n    const char *uri = GetParam();\n\n    EXPECT_EQ(AERON_URI_PARSE(uri, &m_uri), 0);\n    ASSERT_GT(aeron_uri_sprint(&m_uri, print_buffer, sizeof(print_buffer)), 0);\n\n    strncpy(temp_buffer, uri, sizeof(temp_buffer) - 1);\n    temp_buffer[15] = '\\0';\n\n    EXPECT_STREQ(temp_buffer, print_buffer);\n}\n\nclass UriStringBuilderTest : public testing::Test\n{\npublic:\n\n    ~UriStringBuilderTest() override\n    {\n        aeron_uri_string_builder_close(&m_builder);\n    }\n\nprotected:\n    aeron_uri_string_builder_t m_builder = {};\n    char out_buff[AERON_URI_MAX_LENGTH];\n};\n\nTEST_F(UriStringBuilderTest, emptyUri)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(-1, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n}\n\nTEST_F(UriStringBuilderTest, setMedia)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"my-media\"));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:my-media\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, setMediaAndPrefix)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"ipc\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_PREFIX_KEY, \"ultra-prefix\"));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"ultra-prefix:aeron:ipc\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, simpleParam)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, \"param1\", \"value1\"));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=value1\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, twoParams)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, \"param1\", \"value1\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, \"param2\", \"value2\"));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=value1|param2=value2\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, int32)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put_int32(&m_builder, \"param1\", 1234));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=1234\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, int32MaxValue)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put_int32(&m_builder, \"param1\", INT32_MAX));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=2147483647\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, int32MinValue)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put_int32(&m_builder, \"param1\", INT32_MIN));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=-2147483648\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, int64)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put_int64(&m_builder, \"param1\", 1234567));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=1234567\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, int64MaxValue)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put_int64(&m_builder, \"param1\", INT64_MAX));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=9223372036854775807\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, int64MinValue)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put_int64(&m_builder, \"param1\", INT64_MIN));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=-9223372036854775808\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, overflow)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, \"param1\", \"value1\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, \"param2\", \"value2\"));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, 20));\n\n    EXPECT_STREQ(\"aeron:udp?param1=va\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, badCharacters)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(-1, aeron_uri_string_builder_put(&m_builder, \"key1\", \"u=dp\"));\n    EXPECT_EQ(-1, aeron_uri_string_builder_put(&m_builder, \"pa?ram1\", \"value1\"));\n    EXPECT_EQ(-1, aeron_uri_string_builder_put(&m_builder, \"param2\", \"valu|e2\"));\n}\n\nTEST_F(UriStringBuilderTest, unset)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, \"param1\", \"value1\"));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, \"param2\", \"value2\"));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=value1|param2=value2\", out_buff);\n\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, \"param2\", nullptr));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?param1=value1\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, initOnString)\n{\n    const char *uri = \"aeron:udp?a=b|c=d\";\n\n    EXPECT_EQ(0, aeron_uri_string_builder_init_on_string(&m_builder, uri));\n\n    EXPECT_STREQ(\"udp\", aeron_uri_string_builder_get(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY));\n    EXPECT_STREQ(\"b\", aeron_uri_string_builder_get(&m_builder, \"a\"));\n    EXPECT_STREQ(\"d\", aeron_uri_string_builder_get(&m_builder, \"c\"));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(uri, out_buff);\n}\n\nTEST_F(UriStringBuilderTest, initOnStringWithPrefix)\n{\n    const char *uri = \"some-prefix:aeron:udp\";\n\n    EXPECT_EQ(0, aeron_uri_string_builder_init_on_string(&m_builder, uri));\n\n    EXPECT_STREQ(\"some-prefix\", aeron_uri_string_builder_get(&m_builder, AERON_URI_STRING_BUILDER_PREFIX_KEY));\n    EXPECT_STREQ(\"udp\", aeron_uri_string_builder_get(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(uri, out_buff);\n}\n\nTEST_F(UriStringBuilderTest, initOnMalformedStrings)\n{\n    EXPECT_EQ(-1, aeron_uri_string_builder_init_on_string(&m_builder, \"asdf\"));\n    EXPECT_EQ(-1, aeron_uri_string_builder_init_on_string(&m_builder, \"asdf:asdf\"));\n}\n\nTEST_F(UriStringBuilderTest, initialPosition)\n{\n    EXPECT_EQ(0, aeron_uri_string_builder_init_new(&m_builder));\n    EXPECT_EQ(0, aeron_uri_string_builder_put(&m_builder, AERON_URI_STRING_BUILDER_MEDIA_KEY, \"udp\"));\n\n    int32_t term_length = 1024 * 128;\n    int64_t position = (term_length * 3) + 64;\n\n    EXPECT_EQ(0, aeron_uri_string_builder_set_initial_position(&m_builder, position, 777, term_length));\n\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    EXPECT_STREQ(\"aeron:udp?term-id=780|term-length=131072|init-term-id=777|term-offset=64\", out_buff);\n}\n\nTEST_F(UriStringBuilderTest, allParameters)\n{\n    std::string uri = \"aeron:udp?endpoint=127.0.0.1:0|interface=127.0.0.1|control=127.0.0.2:0|\"\n                      \"control-mode=manual|tags=2,4|alias=foo|cc=cubic|fc=min|reliable=false|ttl=16|mtu=8992|\"\n                      \"term-length=1048576|init-term-id=5|term-offset=64|term-id=4353|session-id=2314234|gtag=3|\"\n                      \"linger=100000055000001|sparse=true|eos=true|tether=false|group=false|ssc=true|so-sndbuf=8388608|\"\n                      \"so-rcvbuf=2097152|rcv-wnd=1048576|media-rcv-ts-offset=reserved|channel-rcv-ts-offset=0|\"\n                      \"channel-snd-ts-offset=8|response-endpoint=127.0.0.3:0|response-correlation-id=12345|\"\n                      \"nak-delay=100000|untethered-window-limit-timeout=1000|untethered-resting-timeout=5000|\"\n                      \"stream-id=87|pub-wnd=10224\";\n    ASSERT_EQ(0, aeron_uri_string_builder_init_on_string(&m_builder, uri.c_str()));\n    EXPECT_EQ(0, aeron_uri_string_builder_sprint(&m_builder, out_buff, AERON_URI_MAX_LENGTH));\n\n    aeron_uri_t original_uri;\n    EXPECT_EQ(0, aeron_uri_parse(uri.length(), uri.c_str(), &original_uri));\n    aeron_uri_t builder_uri;\n    EXPECT_EQ(0, aeron_uri_parse(uri.length(), out_buff, &builder_uri));\n\n    EXPECT_EQ(original_uri.type, builder_uri.type);\n    EXPECT_STREQ(original_uri.params.udp.endpoint, builder_uri.params.udp.endpoint);\n    EXPECT_STREQ(original_uri.params.udp.bind_interface, builder_uri.params.udp.bind_interface);\n    EXPECT_STREQ(original_uri.params.udp.control, builder_uri.params.udp.control);\n    EXPECT_STREQ(original_uri.params.udp.control_mode, builder_uri.params.udp.control_mode);\n    EXPECT_STREQ(original_uri.params.udp.channel_tag, builder_uri.params.udp.channel_tag);\n    EXPECT_STREQ(original_uri.params.udp.entity_tag, builder_uri.params.udp.entity_tag);\n    EXPECT_STREQ(original_uri.params.udp.ttl, builder_uri.params.udp.ttl);\n    assertParamsAreEqual(&original_uri.params.udp.additional_params, &builder_uri.params.udp.additional_params);\n\n    aeron_uri_close(&original_uri);\n    aeron_uri_close(&builder_uri);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/aeron_version_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\nextern \"C\"\n{\n#include \"aeron_common.h\"\n#include \"aeronc.h\"\n}\n\nclass VersionTest : public testing::Test\n{\n};\n\nTEST_F(VersionTest, shouldReturnFullVersion)\n{\n    auto full_version = std::string(aeron_version_full());\n    auto expected = std::string(\"aeron version=\")\n        .append(AERON_VERSION_TXT)\n        .append(\" commit=\")\n        .append(AERON_VERSION_GITSHA);\n\n    ASSERT_EQ(expected, full_version);\n}\n\nTEST_F(VersionTest, shouldReturnMajorVersion)\n{\n    EXPECT_EQ(AERON_VERSION_MAJOR, aeron_version_major());\n}\n\nTEST_F(VersionTest, shouldReturnMinorVersion)\n{\n    EXPECT_EQ(AERON_VERSION_MINOR, aeron_version_minor());\n}\n\nTEST_F(VersionTest, shouldReturnPatchVersion)\n{\n    EXPECT_EQ(AERON_VERSION_PATCH, aeron_version_patch());\n}\n\nTEST_F(VersionTest, shouldReturnGitSha)\n{\n    EXPECT_EQ(std::string(AERON_VERSION_GITSHA), std::string(aeron_version_gitsha()));\n}\n\nTEST_F(VersionTest, shouldCompileSemanticVersion)\n{\n    uint8_t major = 42;\n    uint8_t minor = 3;\n    uint8_t patch = 5;\n    EXPECT_EQ(0x2A0305, aeron_semantic_version_compose(major, minor, patch));\n}\n\nTEST_F(VersionTest, shouldExtractMajorVersion)\n{\n    int32_t version = 0xAE0012;\n    EXPECT_EQ(0xAE, aeron_semantic_version_major(version));\n}\n\nTEST_F(VersionTest, shouldExtractMinorVersion)\n{\n    int32_t version = 0xFF10AA;\n    EXPECT_EQ(0x10, aeron_semantic_version_minor(version));\n}\n\nTEST_F(VersionTest, shouldExtractPatchVersion)\n{\n    int32_t version = 0xBBAA03;\n    EXPECT_EQ(0x03, aeron_semantic_version_patch(version));\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/collections/aeron_array_to_ptr_hash_map_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"collections/aeron_array_to_ptr_hash_map.h\"\n#include \"aeron_alloc.h\"\n#include \"util/aeron_strutil.h\"\n}\n\nclass ArrToPtrHashMapTest : public testing::Test\n{\npublic:\n    ~ArrToPtrHashMapTest() override\n    {\n        aeron_array_to_ptr_hash_map_delete(&m_map);\n    }\n\nprotected:\n    static void for_each(void *clientd, const uint8_t *key, size_t key_len, void *value)\n    {\n        auto t = (ArrToPtrHashMapTest *)clientd;\n\n        t->m_for_each(key, key_len, value);\n    }\n\n    void for_each(const std::function<void(const uint8_t *, size_t, void *)> &func)\n    {\n        m_for_each = func;\n        aeron_array_to_ptr_hash_map_for_each(&m_map, ArrToPtrHashMapTest::for_each, this);\n    }\n\n    aeron_array_to_ptr_hash_map_t m_map = {};\n    std::function<void(const uint8_t *, size_t, void *)> m_for_each;\n};\n\nTEST_F(ArrToPtrHashMapTest, shouldDoPutAndThenGetOnEmptyMap)\n{\n    int value = 42;\n    uint8_t key[] = { 0x00, 0x18, 0xFF };\n    ASSERT_EQ(aeron_array_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_put(&m_map, key, 3, (void *)&value), 0);\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_get(&m_map, key, 3), &value);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(ArrToPtrHashMapTest, shouldReplaceExistingValueForTheSameKey)\n{\n    int value = 42, new_value = 43;\n    uint8_t key[] = { 0x03, 0x00, 0xFF };\n    ASSERT_EQ(aeron_array_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_put(&m_map, key, 3, (void *)&value), 0);\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_put(&m_map, key, 3, (void *)&new_value), 0);\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_get(&m_map, key, 3), &new_value);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(ArrToPtrHashMapTest, shouldReplaceKeyWhenReplacingValue)\n{\n    int key1 = 100, key2 = 100;\n    int value = 42, new_value = 43;\n    ASSERT_EQ(aeron_array_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_put(&m_map, (uint8_t *)&key1, 4, (void *)&value), 0);\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_put(&m_map, (uint8_t *)&key2, 4, (void *)&new_value), 0);\n    key1 = 200;\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_get(&m_map, (uint8_t *)&key2, 4), &new_value);\n}\n\nTEST_F(ArrToPtrHashMapTest, shouldGrowWhenThresholdExceeded)\n{\n    int value = 42, value_at_16 = 43;\n    uint8_t *keys[32];\n    size_t key_lengths[32];\n    uint8_t key_at_16[] = { 0x34, 0xFF, 0x45, 0xF2, 0xAD };\n    size_t key_length_at_16 = sizeof(key_at_16);\n    ASSERT_EQ(aeron_array_to_ptr_hash_map_init(&m_map, 32, 0.5f), 0);\n\n    for (size_t i = 0; i < 16; i++)\n    {\n        uint8_t tmp[80];\n        uint8_t *key;\n\n        tmp[0] = 0xF4;\n        tmp[1] = 0x00;\n        tmp[2] = 0x02;\n        snprintf((char *)(tmp + 3), sizeof(tmp) - 3, \"key %d\", (int)i);\n        size_t key_length = strlen((char *)(tmp + 3)) + 3;\n\n        if (aeron_alloc((void **)&key, sizeof(tmp)) < 0)\n        {\n            FAIL();\n        }\n\n        memcpy(key, tmp, key_length);\n        keys[i] = key;\n        key_lengths[i] = key_length;\n\n        EXPECT_EQ(aeron_array_to_ptr_hash_map_put(&m_map, keys[i], key_lengths[i], (void *)&value), 0);\n    }\n\n    EXPECT_EQ(m_map.resize_threshold, 16u);\n    EXPECT_EQ(m_map.capacity, 32u);\n    EXPECT_EQ(m_map.size, 16u);\n\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_put(&m_map, key_at_16, key_length_at_16, (void *)&value_at_16), 0);\n\n    EXPECT_EQ(m_map.resize_threshold, 32u);\n    EXPECT_EQ(m_map.capacity, 64u);\n    EXPECT_EQ(m_map.size, 17u);\n\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_get(&m_map, key_at_16, key_length_at_16), &value_at_16);\n\n    for (size_t i = 0; i < 16; i++)\n    {\n        free(keys[i]);\n    }\n}\n\nTEST_F(ArrToPtrHashMapTest, shouldRemoveEntry)\n{\n    int value = 42;\n    uint8_t key[] = { 0x03, 0x18, 0xFF };\n    ASSERT_EQ(aeron_array_to_ptr_hash_map_init(&m_map, 8, 0.5f), 0);\n\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_put(&m_map, key, 3, (void *)&value), 0);\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_remove(&m_map, key, 3), &value);\n    EXPECT_EQ(m_map.size, 0u);\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_get(&m_map, key, 3), (void *) nullptr);\n}\n\nTEST_F(ArrToPtrHashMapTest, shouldNotForEachEmptyMap)\n{\n    ASSERT_EQ(aeron_array_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](const uint8_t *key, size_t key_len, void *value_ptr)\n        {\n            called++;\n        });\n\n    ASSERT_EQ(called, 0u);\n}\n\nTEST_F(ArrToPtrHashMapTest, shouldForEachNonEmptyMap)\n{\n    int value = 42;\n    uint8_t in_key[] = { 0x03, 0x18, 0xFF };\n    ASSERT_EQ(aeron_array_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_array_to_ptr_hash_map_put(&m_map, in_key, 3, (void *)&value), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](const uint8_t *key, size_t key_len, void *value_ptr)\n        {\n            EXPECT_EQ(key_len, 3u);\n            EXPECT_EQ(memcmp(key, in_key, key_len), 0);\n            EXPECT_EQ(value_ptr, &value);\n            called++;\n        });\n\n    ASSERT_EQ(called, 1u);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/collections/aeron_bit_set_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"collections/aeron_bit_set.h\"\n}\n\nclass BitSetTest : public testing::Test\n{\npublic:\n    BitSetTest() = default;\n\n    static void assertGetAndSet(aeron_bit_set_t *bit_set)\n    {\n        bool result;\n\n        for (size_t i = 0; i < bit_set->bit_set_length; i++)\n        {\n            EXPECT_EQ(aeron_bit_set_set(bit_set, i, true), 0);\n            EXPECT_EQ(aeron_bit_set_get(bit_set, i, &result), 0);\n            EXPECT_EQ(result, true);\n\n            for (size_t j = 0; j < bit_set->bit_set_length; j++)\n            {\n                if (j != i)\n                {\n                    EXPECT_EQ(aeron_bit_set_get(bit_set, j, &result), 0);\n                    EXPECT_EQ(result, false) << \"Index: \" << j;\n                }\n            }\n\n            EXPECT_EQ(aeron_bit_set_set(bit_set, i, false), 0);\n            EXPECT_EQ(aeron_bit_set_get(bit_set, i, &result), 0);\n            EXPECT_EQ(result, false);\n\n            for (size_t j = 0; j < bit_set->bit_set_length; j++)\n            {\n                EXPECT_EQ(aeron_bit_set_get(bit_set, j, &result), 0);\n                EXPECT_EQ(result, false) << \"Index: \" << j;\n            }\n        }\n\n        EXPECT_EQ(aeron_bit_set_init(bit_set, true), 0);\n\n        for (size_t i = 0; i < bit_set->bit_set_length; i++)\n        {\n            EXPECT_EQ(aeron_bit_set_set(bit_set, i, false), 0);\n            EXPECT_EQ(aeron_bit_set_get(bit_set, i, &result), 0);\n            EXPECT_EQ(result, false);\n\n            for (size_t j = 0; j < bit_set->bit_set_length; j++)\n            {\n                if (j != i)\n                {\n                    EXPECT_EQ(aeron_bit_set_get(bit_set, j, &result), 0);\n                    EXPECT_EQ(result, true) << \"Index: \" << j;\n                }\n            }\n\n            EXPECT_EQ(aeron_bit_set_set(bit_set, i, true), 0);\n            EXPECT_EQ(aeron_bit_set_get(bit_set, i, &result), 0);\n            EXPECT_EQ(result, true);\n\n            for (size_t j = 0; j < bit_set->bit_set_length; j++)\n            {\n                EXPECT_EQ(aeron_bit_set_get(bit_set, j, &result), 0);\n                EXPECT_EQ(result, true) << \"Index: \" << j;\n            }\n        }\n    }\n};\n\n#define STATIC_ARRAY_LEN (20)\n\nTEST_F(BitSetTest, shouldSetAndGetStack)\n{\n    uint64_t bits[STATIC_ARRAY_LEN] = { 0 };\n    aeron_bit_set_t bit_set = {};\n    size_t bit_set_length = STATIC_ARRAY_LEN * 64;\n\n    EXPECT_EQ(aeron_bit_set_stack_init(bit_set_length, bits, STATIC_ARRAY_LEN, false, &bit_set), 0);\n\n    assertGetAndSet(&bit_set);\n\n    aeron_bit_set_stack_free(&bit_set);\n}\n\nTEST_F(BitSetTest, shouldSetAndGetHeap)\n{\n    aeron_bit_set_t *bit_set = nullptr;\n    size_t bit_set_length = STATIC_ARRAY_LEN * 64;\n\n    EXPECT_EQ(aeron_bit_set_heap_init(bit_set_length, false, &bit_set), 0);\n\n    assertGetAndSet(bit_set);\n\n    aeron_bit_set_heap_free(bit_set);\n}\n\nTEST_F(BitSetTest, shouldSetAndGet)\n{\n    uint64_t bits[STATIC_ARRAY_LEN] = { 0 };\n    aeron_bit_set_t bit_set = {};\n    size_t bit_set_length = STATIC_ARRAY_LEN * 64;\n    bool result = false;\n\n    EXPECT_EQ(aeron_bit_set_stack_init(bit_set_length, bits, STATIC_ARRAY_LEN, false, &bit_set), 0);\n\n    for (size_t i = 0; i < bit_set.bit_set_length; i++)\n    {\n        EXPECT_EQ(aeron_bit_set_set(&bit_set, i, true), 0);\n        EXPECT_EQ(aeron_bit_set_get(&bit_set, i, &result), 0);\n        EXPECT_EQ(result, true);\n\n        for (size_t j = 0; j < bit_set.bit_set_length; j++)\n        {\n            if (j != i)\n            {\n                EXPECT_EQ(aeron_bit_set_get(&bit_set, j, &result), 0);\n                EXPECT_EQ(result, false) << \"Index: \" << j;\n            }\n        }\n\n        EXPECT_EQ(aeron_bit_set_set(&bit_set, i, false), 0);\n        EXPECT_EQ(aeron_bit_set_get(&bit_set, i, &result), 0);\n        EXPECT_EQ(result, false);\n\n        for (size_t j = 0; j < bit_set.bit_set_length; j++)\n        {\n            EXPECT_EQ(aeron_bit_set_get(&bit_set, j, &result), 0);\n            EXPECT_EQ(result, false) << \"Index: \" << j;\n        }\n    }\n\n    EXPECT_EQ(aeron_bit_set_init(&bit_set, true), 0);\n\n    for (size_t i = 0; i < bit_set.bit_set_length; i++)\n    {\n        EXPECT_EQ(aeron_bit_set_set(&bit_set, i, false), 0);\n        EXPECT_EQ(aeron_bit_set_get(&bit_set, i, &result), 0);\n        EXPECT_EQ(result, false);\n\n        for (size_t j = 0; j < bit_set.bit_set_length; j++)\n        {\n            if (j != i)\n            {\n                EXPECT_EQ(aeron_bit_set_get(&bit_set, j, &result), 0);\n                EXPECT_EQ(result, true) << \"Index: \" << j;\n            }\n        }\n\n        EXPECT_EQ(aeron_bit_set_set(&bit_set, i, true), 0);\n        EXPECT_EQ(aeron_bit_set_get(&bit_set, i, &result), 0);\n        EXPECT_EQ(result, true);\n\n        for (size_t j = 0; j < bit_set.bit_set_length; j++)\n        {\n            EXPECT_EQ(aeron_bit_set_get(&bit_set, j, &result), 0);\n            EXPECT_EQ(result, true) << \"Index: \" << j;\n        }\n    }\n}\n\nTEST_F(BitSetTest, shouldFindFirstBit)\n{\n    uint64_t bits[STATIC_ARRAY_LEN] = { 0 };\n    aeron_bit_set_t bit_set = {};\n    size_t bit_set_length = STATIC_ARRAY_LEN * 64;\n    size_t result = 0;\n\n    EXPECT_EQ(aeron_bit_set_stack_init(bit_set_length, bits, STATIC_ARRAY_LEN, false, &bit_set), 0);\n\n    EXPECT_EQ(aeron_bit_set_find_first(&bit_set, true, &result), -1);\n\n    for (size_t i = 0; i < bit_set_length; i++)\n    {\n        EXPECT_EQ(aeron_bit_set_set(&bit_set, i, true), 0);\n        EXPECT_EQ(aeron_bit_set_find_first(&bit_set, true, &result), 0);\n        EXPECT_EQ(result, i);\n        EXPECT_EQ(aeron_bit_set_set(&bit_set, i, false), 0);\n    }\n\n    EXPECT_EQ(aeron_bit_set_init(&bit_set, true), 0);\n    for (size_t i = 0; i < bit_set_length; i++)\n    {\n        EXPECT_EQ(aeron_bit_set_set(&bit_set, i, false), 0);\n        EXPECT_EQ(aeron_bit_set_find_first(&bit_set, false, &result), 0);\n        EXPECT_EQ(result, i);\n        EXPECT_EQ(aeron_bit_set_set(&bit_set, i, true), 0);\n    }\n}\n\nTEST_F(BitSetTest, shouldHandleOutOfRangeRequests)\n{\n    uint64_t bits[STATIC_ARRAY_LEN] = { 0 };\n    aeron_bit_set_t bit_set = {};\n    size_t bit_set_length = STATIC_ARRAY_LEN * 64;\n    bool result = false;\n\n    EXPECT_EQ(aeron_bit_set_stack_init(bit_set_length, bits, STATIC_ARRAY_LEN, false, &bit_set), 0);\n    EXPECT_EQ(aeron_bit_set_set(nullptr, 0, true), -EINVAL);\n    EXPECT_EQ(aeron_bit_set_set(&bit_set, bit_set_length, true), -EINVAL);\n    EXPECT_EQ(aeron_bit_set_set(&bit_set, bit_set_length + 1, true), -EINVAL);\n    EXPECT_EQ(aeron_bit_set_set(&bit_set, -1, true), -EINVAL);\n\n    EXPECT_EQ(aeron_bit_set_get(nullptr, 0, &result), -EINVAL);\n    EXPECT_EQ(aeron_bit_set_get(&bit_set, 0, nullptr), -EINVAL);\n    EXPECT_EQ(aeron_bit_set_get(&bit_set, bit_set_length, &result), -EINVAL);\n    EXPECT_EQ(aeron_bit_set_get(&bit_set, bit_set_length + 1, &result), -EINVAL);\n    EXPECT_EQ(aeron_bit_set_get(&bit_set, -1, &result), -EINVAL);\n}\n\nTEST_F(BitSetTest, shouldHeapAllocateIfBitsRequiredIsTooLarge)\n{\n    uint64_t bits[STATIC_ARRAY_LEN] = { 0 };\n    aeron_bit_set_t bit_set = {};\n    size_t bit_set_length = STATIC_ARRAY_LEN * 64;\n\n    EXPECT_EQ(aeron_bit_set_stack_init(bit_set_length, bits, STATIC_ARRAY_LEN, true, &bit_set), 0);\n    EXPECT_EQ(bit_set.bits, bit_set.static_array);\n\n    EXPECT_EQ(aeron_bit_set_stack_init(bit_set_length + 64, bits, STATIC_ARRAY_LEN, true, &bit_set), 0);\n    EXPECT_NE(bit_set.bits, bit_set.static_array);\n\n    aeron_bit_set_stack_free(&bit_set);\n\n    EXPECT_EQ(aeron_bit_set_stack_init(bit_set_length + 64, nullptr, 0, true, &bit_set), 0);\n    EXPECT_NE(bit_set.bits, bit_set.static_array);\n\n    aeron_bit_set_stack_free(&bit_set);\n}\n\nTEST_F(BitSetTest, shouldHandleNonBase2BitSetLength)\n{\n    uint64_t bits[2] = { 0 };\n    aeron_bit_set_t bit_set = {};\n    size_t bit_set_length = 10;\n    size_t bit_index = 0;\n\n    EXPECT_EQ(aeron_bit_set_stack_init(bit_set_length, bits, 2, true, &bit_set), 0);\n    EXPECT_EQ(bit_set.bits, bit_set.static_array);\n    EXPECT_EQ(aeron_bit_set_find_first(&bit_set, false, &bit_index), -1);\n\n    for (size_t i = 0; i < bit_set_length; i++)\n    {\n        aeron_bit_set_set(&bit_set, i, false);\n    }\n\n    EXPECT_EQ(aeron_bit_set_find_first(&bit_set, true, &bit_index), -1);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/collections/aeron_int64_counter_map_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"collections/aeron_int64_counter_map.h\"\n}\n\nclass Int64CounterMapTest : public testing::Test\n{\npublic:\n    ~Int64CounterMapTest() override\n    {\n        aeron_int64_counter_map_delete(&m_map);\n    }\n\nprotected:\n    static void for_each(void *clientd, int64_t key, int64_t value)\n    {\n        auto t = (Int64CounterMapTest *)clientd;\n\n        t->m_for_each(key, value);\n    }\n\n    void for_each(const std::function<void(int64_t, int64_t)> &func)\n    {\n        m_for_each = func;\n        aeron_int64_counter_map_for_each(&m_map, Int64CounterMapTest::for_each, this);\n    }\n\n    aeron_int64_counter_map_t m_map = {};\n    std::function<void(int64_t, int64_t)> m_for_each;\n};\n\nTEST_F(Int64CounterMapTest, shouldDoPutAndThenGetOnEmptyMap)\n{\n    int64_t key = 12;\n    int64_t value = 42;\n    int64_t old_value = -1;\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -2, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, key, value, &old_value), 0);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, key), value);\n    EXPECT_EQ(old_value, m_map.initial_value);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(Int64CounterMapTest, shouldNotAllowInitialValuePut)\n{\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -2, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    int64_t old_value = -1000;\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, 42, 0, &old_value), 0);\n    EXPECT_EQ(old_value, m_map.initial_value);\n\n    old_value = -5555555;\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, 42, m_map.initial_value, &old_value), -1);\n    EXPECT_EQ(EINVAL, aeron_errcode());\n    EXPECT_EQ(-5555555, old_value);\n}\n\nTEST_F(Int64CounterMapTest, shouldReplaceExistingValueForTheSameKey)\n{\n    int64_t key = 123;\n    int64_t value = 42, new_value = 43;\n    int64_t old_value = -1;\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -2, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, key, value, nullptr), 0);\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, key, new_value, &old_value), 0);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, key), new_value);\n    EXPECT_EQ(old_value, value);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(Int64CounterMapTest, shouldIncrementAndDecrement)\n{\n    int64_t key = 123;\n    int64_t value = -1;\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, 0, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_counter_map_inc_and_get(&m_map, key, &value), 0);\n    EXPECT_EQ(1, value);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, key), 1);\n    EXPECT_EQ(aeron_int64_counter_map_inc_and_get(&m_map, key, &value), 0);\n    EXPECT_EQ(2, value);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, key), 2);\n    EXPECT_EQ(aeron_int64_counter_map_add_and_get(&m_map, key, 2, &value), 0);\n    EXPECT_EQ(4, value);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, key), 4);\n    EXPECT_EQ(aeron_int64_counter_map_dec_and_get(&m_map, key, &value), 0);\n    EXPECT_EQ(3, value);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, key), 3);\n    EXPECT_EQ(m_map.size, 1u);\n    EXPECT_EQ(aeron_int64_counter_map_add_and_get(&m_map, key, -3, &value), 0);\n    EXPECT_EQ(0, value);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, key), 0);\n    EXPECT_EQ(m_map.size, 0u);\n}\n\nTEST_F(Int64CounterMapTest, shouldGrowWhenThresholdExceeded)\n{\n    int64_t value = 42, value_at_16 = 43;\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -2, 32, 0.5f), 0);\n\n    for (int64_t i = 0; i < 16; i++)\n    {\n        EXPECT_EQ(aeron_int64_counter_map_put(&m_map, i, value, nullptr), 0);\n        EXPECT_EQ(aeron_int64_counter_map_get(&m_map, i), value);\n    }\n\n    EXPECT_EQ(m_map.resize_threshold, 16u);\n    EXPECT_EQ(m_map.entries_length, 64u);\n    EXPECT_EQ(m_map.size, 16u);\n\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, 16, value_at_16, nullptr), 0);\n\n    EXPECT_EQ(m_map.resize_threshold, 32u);\n    EXPECT_EQ(m_map.entries_length, 128u);\n    EXPECT_EQ(m_map.size, 17u);\n\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, 16), value_at_16);\n}\n\nTEST_F(Int64CounterMapTest, shouldHandleCollisionAndThenLinearProbe)\n{\n    int64_t value = 42, collision_value = 43;\n    int64_t key = 7;\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -2, 32, 0.5f), 0);\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, key, value, nullptr), 0);\n\n    auto collision_key = (int64_t)(key + m_map.entries_length);\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, collision_key, collision_value, nullptr), 0);\n\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, key), value);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, collision_key), collision_value);\n}\n\nTEST_F(Int64CounterMapTest, shouldRemoveEntry)\n{\n    int64_t value = 42;\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -2, 8, 0.5f), 0);\n\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, 7, value, nullptr), 0);\n    EXPECT_EQ(aeron_int64_counter_map_remove(&m_map, 7), value);\n    EXPECT_EQ(m_map.size, 0u);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, 7), m_map.initial_value);\n}\n\nTEST_F(Int64CounterMapTest, shouldRemoveEntryAndCompactCollisionChain)\n{\n    int64_t value_12 = 12, value_13 = 13, value_14 = 14, collision_value = 43;\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -2, 8, 0.55f), 0);\n\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, 12, value_12, nullptr), 0);\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, 13, value_13, nullptr), 0);\n\n    auto collision_key = (int64_t)(12 + m_map.entries_length);\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, collision_key, collision_value, nullptr), 0);\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, 14, value_14, nullptr), 0);\n\n    EXPECT_EQ(aeron_int64_counter_map_remove(&m_map, 12), value_12);\n}\n\nTEST_F(Int64CounterMapTest, shouldNotForEachEmptyMap)\n{\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -2, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](int64_t key, int64_t value)\n        {\n            called++;\n        });\n\n    ASSERT_EQ(called, 0u);\n}\n\nTEST_F(Int64CounterMapTest, shouldForEachNonEmptyMap)\n{\n    int64_t value = 42;\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -2, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, 7, value, nullptr), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](int64_t key, int64_t for_each_value)\n        {\n            EXPECT_EQ(key, 7);\n            EXPECT_EQ(for_each_value, value);\n            called++;\n        });\n\n    ASSERT_EQ(called, 1u);\n}\n\nTEST_F(Int64CounterMapTest, shouldRemoveIfValueMatches)\n{\n    const int64_t initialValue = -2;\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, initialValue, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    for (int64_t i = 0; i < 10; i++)\n    {\n        int64_t value = i / 2;\n        aeron_int64_counter_map_put(&m_map, i, value, nullptr);\n    }\n\n    const int64_t value_to_remove = 3;\n    aeron_int64_counter_map_remove_if(\n        &m_map,\n        [](void *clientd, int64_t key, int64_t value)\n        {\n            int64_t client_v = *(int64_t *)clientd;\n            return client_v == value;\n        },\n        (void *)&value_to_remove);\n\n    for (int64_t i = 0; i < 10; i++)\n    {\n        int64_t value = i / 2;\n        if (value_to_remove == value)\n        {\n            EXPECT_EQ(initialValue, aeron_int64_counter_map_get(&m_map, i));\n        }\n        else\n        {\n            EXPECT_EQ(value, aeron_int64_counter_map_get(&m_map, i));\n        }\n    }\n}\n\nTEST_F(Int64CounterMapTest, shouldDeleteEntryWithClashingHashCode)\n{\n    ASSERT_EQ(aeron_int64_counter_map_init(&m_map, -1, 8, 0.6f), 0);\n    const auto mask = m_map.entries_length - 1;\n\n    int64_t key = 0;\n    const size_t last_index = mask - 1;\n    while (aeron_even_hash(++key, mask) != last_index);\n\n    const auto first_key = key;\n    int64_t existing_value = 42;\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, first_key, first_key, &existing_value), 0);\n    EXPECT_EQ(m_map.initial_value, existing_value);\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, key - 1, key - 1, &existing_value), 0);\n    EXPECT_EQ(m_map.initial_value, existing_value);\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, key + 1, key + 1, &existing_value), 0);\n    EXPECT_EQ(m_map.initial_value, existing_value);\n\n    ASSERT_EQ(m_map.size, 3);\n    ASSERT_EQ(m_map.entries_length, mask + 1);\n\n    int64_t clashing_key = key;\n    while (aeron_even_hash(++clashing_key, mask) != last_index);\n    ASSERT_NE(first_key, clashing_key);\n\n    EXPECT_EQ(aeron_int64_counter_map_put(&m_map, clashing_key, clashing_key, &existing_value), 0);\n    EXPECT_EQ(m_map.initial_value, existing_value);\n    ASSERT_EQ(m_map.size, 4);\n    ASSERT_EQ(m_map.entries_length, mask + 1);\n\n    EXPECT_EQ(aeron_int64_counter_map_remove(&m_map, clashing_key), clashing_key);\n    EXPECT_EQ(aeron_int64_counter_map_get(&m_map, clashing_key), m_map.initial_value);\n    EXPECT_EQ(m_map.size, 3);\n    ASSERT_EQ(m_map.entries_length, mask + 1);\n\n    for (key = first_key - 1; key <= first_key + 1; key++)\n    {\n        EXPECT_EQ(aeron_int64_counter_map_get(&m_map, key), key);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/collections/aeron_int64_to_ptr_hash_map_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"collections/aeron_int64_to_ptr_hash_map.h\"\n}\n\nclass Int64ToPtrHashMapTest : public testing::Test\n{\npublic:\n    ~Int64ToPtrHashMapTest() override\n    {\n        aeron_int64_to_ptr_hash_map_delete(&m_map);\n    }\n\nprotected:\n    static void for_each(void *clientd, int64_t key, void *value)\n    {\n        auto *t = (Int64ToPtrHashMapTest *)clientd;\n\n        t->m_for_each(key, value);\n    }\n\n    static bool remove_if(void *clientd, int64_t key, void *value)\n    {\n        auto *t = (Int64ToPtrHashMapTest *)clientd;\n\n        return t->m_remove_if(key, value);\n    }\n\n    void for_each(const std::function<void(int64_t, void *)> &func)\n    {\n        m_for_each = func;\n        aeron_int64_to_ptr_hash_map_for_each(&m_map, Int64ToPtrHashMapTest::for_each, this);\n    }\n\n    void remove_if(const std::function<bool(int64_t, void *)> &func)\n    {\n        m_remove_if = func;\n        aeron_int64_to_ptr_hash_map_remove_if(&m_map, Int64ToPtrHashMapTest::remove_if, this);\n    }\n\n    aeron_int64_to_ptr_hash_map_t m_map = {};\n    std::function<void(int64_t, void *)> m_for_each;\n    std::function<bool(int64_t, void *)> m_remove_if;\n};\n\nTEST_F(Int64ToPtrHashMapTest, shouldDoPutAndThenGetOnEmptyMap)\n{\n    int value = 42;\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, 7, (void *)&value), 0);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_get(&m_map, 7), &value);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(Int64ToPtrHashMapTest, shouldReplaceExistingValueForTheSameKey)\n{\n    int value = 42, new_value = 43;\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, 7, (void *)&value), 0);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, 7, (void *)&new_value), 0);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_get(&m_map, 7), &new_value);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(Int64ToPtrHashMapTest, shouldGrowWhenThresholdExceeded)\n{\n    int value = 42, value_at_16 = 43;\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 32, 0.5f), 0);\n\n    for (size_t i = 0; i < 16; i++)\n    {\n        EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, i, (void *)&value), 0);\n    }\n\n    EXPECT_EQ(m_map.resize_threshold, 16u);\n    EXPECT_EQ(m_map.capacity, 32u);\n    EXPECT_EQ(m_map.size, 16u);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, 16, (void *)&value_at_16), 0);\n\n    EXPECT_EQ(m_map.resize_threshold, 32u);\n    EXPECT_EQ(m_map.capacity, 64u);\n    EXPECT_EQ(m_map.size, 17u);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_get(&m_map, 16), &value_at_16);\n}\n\nTEST_F(Int64ToPtrHashMapTest, shouldHandleCollisionAndThenLinearProbe)\n{\n    int value = 42, collision_value = 43;\n    int64_t key = 7;\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 32, 0.5f), 0);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, key, &value), 0);\n\n    auto collision_key = (int64_t)(key + m_map.capacity);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, collision_key, &collision_value), 0);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_get(&m_map, key), &value);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_get(&m_map, collision_key), &collision_value);\n}\n\nTEST_F(Int64ToPtrHashMapTest, shouldRemoveEntry)\n{\n    int value = 42;\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 8, 0.5f), 0);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, 7, (void *)&value), 0);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_remove(&m_map, 7), &value);\n    EXPECT_EQ(m_map.size, 0u);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_get(&m_map, 7), (void *)nullptr);\n}\n\nTEST_F(Int64ToPtrHashMapTest, shouldRemoveEntryAndCompactCollisionChain)\n{\n    int value_12 = 12, value_13 = 13, value_14 = 14, collision_value = 43;\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 8, 0.55f), 0);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, 12, (void *)&value_12), 0);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, 13, (void *)&value_13), 0);\n\n    auto collision_key = (int64_t)(12 + m_map.capacity);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, collision_key, &collision_value), 0);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, 14, (void *)&value_14), 0);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_remove(&m_map, 12), &value_12);\n}\n\nTEST_F(Int64ToPtrHashMapTest, shouldNotForEachEmptyMap)\n{\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](int64_t key, void *value_ptr)\n        {\n            called++;\n        });\n\n    ASSERT_EQ(called, 0u);\n}\n\nTEST_F(Int64ToPtrHashMapTest, shouldForEachNonEmptyMap)\n{\n    int value = 42;\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, 7, (void *)&value), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](int64_t key, void *value_ptr)\n        {\n            EXPECT_EQ(key, 7);\n            EXPECT_EQ(value_ptr, &value);\n            called++;\n        });\n\n    ASSERT_EQ(called, 1u);\n}\n\nTEST_F(Int64ToPtrHashMapTest, shouldNotRemoveIfEmptyMap)\n{\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    size_t called = 0;\n    remove_if(\n        [&](int64_t key, void *value_ptr)\n        {\n            called++;\n            return false;\n        });\n\n    ASSERT_EQ(called, 0u);\n}\n\nTEST_F(Int64ToPtrHashMapTest, shouldRemoveIfNonEmptyMap)\n{\n    int value0 = 42;\n    int value1 = 43;\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    for (int i = 0; i < 100; i++)\n    {\n        void *value = ((i & 1) == 0) ? &value0 : &value1;\n        EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, i, value), 0);\n    }\n\n    size_t called = 0;\n    remove_if(\n        [&](int64_t key, void *value_ptr)\n        {\n            called++;\n            int val = *((int *)value_ptr);\n            return (val & 1) == 1;\n        });\n\n    ASSERT_EQ(called, 100u);\n    ASSERT_EQ(m_map.size, 50u);\n\n    for_each(\n        [&](int64_t key, void *value_ptr)\n        {\n            int val = *((int *)value_ptr);\n            EXPECT_EQ(0, val & 1);\n            called++;\n        });\n}\n\nTEST_F(Int64ToPtrHashMapTest, removeIfWorksWhenChainCompactionBringsElementsToEndOfMap)\n{\n    ASSERT_EQ(aeron_int64_to_ptr_hash_map_init(&m_map, 16, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    int value = 1;\n\n    // Check our indices have the same hash, to ensure chained entries will wrap from 15->0\n    ASSERT_EQ(aeron_hash(-243406781, 15), 15);\n    ASSERT_EQ(aeron_hash(-333209241, 15), 15);\n\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, -243406781, &value), 0);\n    EXPECT_EQ(aeron_int64_to_ptr_hash_map_put(&m_map, -333209241, &value), 0);\n\n    size_t called = 0;\n    remove_if(\n        [&](int64_t, void*)\n        {\n            called++;\n            return true;\n        });\n\n    ASSERT_EQ(called, 2u);\n    ASSERT_EQ(m_map.size, 0u);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/collections/aeron_int64_to_tagged_ptr_hash_map_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"collections/aeron_int64_to_tagged_ptr_hash_map.h\"\n}\n\nclass Int64ToTaggedPtrHashMapTest : public testing::Test\n{\npublic:\n    ~Int64ToTaggedPtrHashMapTest() override\n    {\n        aeron_int64_to_tagged_ptr_hash_map_delete(&m_map);\n    }\n\nprotected:\n    static void for_each(void *clientd, int64_t key, uint32_t tag, void *value)\n    {\n        auto t = (Int64ToTaggedPtrHashMapTest *)clientd;\n\n        t->m_for_each(key, tag, value);\n    }\n\n    static bool remove_if(void *clientd, int64_t key, uint32_t tag, void *value)\n    {\n        auto t = (Int64ToTaggedPtrHashMapTest *)clientd;\n\n        return t->m_remove_if(key, tag, value);\n    }\n\n    void for_each(const std::function<void(int64_t, uint32_t, void *)> &func)\n    {\n        m_for_each = func;\n        aeron_int64_to_tagged_ptr_hash_map_for_each(&m_map, Int64ToTaggedPtrHashMapTest::for_each, this);\n    }\n\n    void remove_if(const std::function<bool(int64_t, uint32_t, void *)> &func)\n    {\n        m_remove_if = func;\n        aeron_int64_to_tagged_ptr_hash_map_remove_if(&m_map, Int64ToTaggedPtrHashMapTest::remove_if, this);\n    }\n\n    aeron_int64_to_tagged_ptr_hash_map_t m_map = {};\n    std::function<void(int64_t, uint32_t, void *)> m_for_each;\n    std::function<bool(int64_t, uint32_t, void *)> m_remove_if;\n};\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldDoPutAndThenGetOnEmptyMap)\n{\n    int value = 42;\n    uint32_t tag = 64234;\n    uint32_t tag_out = 0;\n    void *value_out = nullptr;\n\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 7, tag, (void *)&value), 0);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_get(&m_map, 7, &tag_out, &value_out), true);\n    EXPECT_EQ(value_out, (void *)&value);\n    EXPECT_EQ(tag_out, tag);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldAllowNullValues)\n{\n    uint32_t tag = 64234;\n    uint32_t tag_out = 0;\n    void *value_out = nullptr;\n\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 7, tag, nullptr), 0);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_get(&m_map, 7, &tag_out, &value_out), true);\n    EXPECT_EQ(value_out, (void *)nullptr);\n    EXPECT_EQ(tag_out, tag);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldReplaceExistingValueForTheSameKey)\n{\n    int value = 42, new_value = 43;\n    uint32_t new_tag = 234;\n    uint32_t tag_out = 0;\n    void *value_out = nullptr;\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 7, 0, (void *)&value), 0);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 7, new_tag, (void *)&new_value), 0);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_get(&m_map, 7, &tag_out, &value_out), true);\n    EXPECT_EQ(value_out, (void *)&new_value);\n    EXPECT_EQ(tag_out, new_tag);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldGrowWhenThresholdExceeded)\n{\n    int value = 42, value_at_16 = 43;\n    uint32_t tag = 982374;\n    uint32_t tag_at_16 = tag + 1;\n    uint32_t tag_out = 0;\n    void *value_out = nullptr;\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 32, 0.5f), 0);\n\n    for (size_t i = 0; i < 16; i++)\n    {\n        EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, i, tag, (void *)&value), 0);\n    }\n\n    EXPECT_EQ(m_map.resize_threshold, 16u);\n    EXPECT_EQ(m_map.capacity, 32u);\n    EXPECT_EQ(m_map.size, 16u);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 16, tag_at_16, (void *)&value_at_16), 0);\n\n    EXPECT_EQ(m_map.resize_threshold, 32u);\n    EXPECT_EQ(m_map.capacity, 64u);\n    EXPECT_EQ(m_map.size, 17u);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_get(&m_map, 16, &tag_out, &value_out), true);\n    EXPECT_EQ(value_out, (void *)&value_at_16);\n    EXPECT_EQ(tag_out, tag_at_16);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldHandleCollisionAndThenLinearProbe)\n{\n    int value = 42, collision_value = 43;\n    uint32_t tag = 987234, collision_tag = tag + 1;\n    int64_t key = 7;\n    uint32_t tag_out = 0;\n    void *value_out = nullptr;\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 32, 0.5f), 0);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, key, tag, &value), 0);\n\n    auto collision_key = (int64_t)(key + m_map.capacity);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, collision_key, collision_tag, &collision_value), 0);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_get(&m_map, key, &tag_out, &value_out), true);\n    EXPECT_EQ(value_out, (void *)&value);\n    EXPECT_EQ(tag_out, tag);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_get(&m_map, collision_key, &tag_out, &value_out), true);\n    EXPECT_EQ(value_out, (void *)&collision_value);\n    EXPECT_EQ(tag_out, collision_tag);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldRemoveEntry)\n{\n    int value = 42;\n    uint32_t tag_out = 0;\n    void *value_out = nullptr;\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 8, 0.5f), 0);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 7, 0, (void *)&value), 0);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_remove(&m_map, 7, nullptr, &value_out), true);\n    EXPECT_EQ(value_out, (void *)&value);\n    EXPECT_EQ(m_map.size, 0u);\n    value_out = nullptr;\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_get(&m_map, 7, &tag_out, &value_out), false);\n    EXPECT_EQ(value_out, (void *)nullptr);\n    EXPECT_EQ(tag_out, 0u);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldRemoveEntryAndCompactCollisionChain)\n{\n    int value_12 = 12, value_13 = 13, value_14 = 14, collision_value = 43;\n    void *value_out = nullptr;\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 8, 0.55f), 0);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 12, 0, (void *)&value_12), 0);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 13, 0, (void *)&value_13), 0);\n\n    auto collision_key = (int64_t)(12 + m_map.capacity);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, collision_key, 0, &collision_value), 0);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 14, 0, (void *)&value_14), 0);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_remove(&m_map, 12, nullptr, &value_out), true);\n    EXPECT_EQ(value_out, (void *)&value_12);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldNotForEachEmptyMap)\n{\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](int64_t key, uint32_t tag, void *value_ptr)\n        {\n            called++;\n        });\n\n    ASSERT_EQ(called, 0u);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldForEachNonEmptyMap)\n{\n    int value = 42;\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, 7, 0, (void *)&value), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](int64_t key, uint32_t tag, void *value_ptr)\n        {\n            EXPECT_EQ(key, 7);\n            EXPECT_EQ(value_ptr, &value);\n            called++;\n        });\n\n    ASSERT_EQ(called, 1u);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldNotRemoveIfEmptyMap)\n{\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    size_t called = 0;\n    remove_if(\n        [&](int64_t key, uint32_t tag, void *value_ptr)\n        {\n            called++;\n            return false;\n        });\n\n    ASSERT_EQ(called, 0u);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, removeIfWorksWhenChainCompactionBringsElementsToEndOfMap)\n{\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 16, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    // Check our indices have the same hash, to ensure chained entries will wrap from 15->0\n    ASSERT_EQ(aeron_hash(-243406781, 15), 15);\n    ASSERT_EQ(aeron_hash(-333209241, 15), 15);\n\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, -243406781, 0, nullptr), 0);\n    EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, -333209241, 0, nullptr), 0);\n\n\n    size_t called = 0;\n    remove_if(\n        [&](int64_t, uint32_t, void*)\n        {\n            called++;\n            return true;\n        });\n\n    ASSERT_EQ(called, 2u);\n    ASSERT_EQ(m_map.size, 0u);\n}\n\nTEST_F(Int64ToTaggedPtrHashMapTest, shouldRemoveIfNonEmptyMap)\n{\n    int value0 = 42;\n    int value1 = 43;\n    ASSERT_EQ(aeron_int64_to_tagged_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    for (int i = 0; i < 100; i++)\n    {\n        void *value = ((i & 1) == 0) ? &value0 : &value1;\n        EXPECT_EQ(aeron_int64_to_tagged_ptr_hash_map_put(&m_map, i, 0, value), 0);\n    }\n\n    size_t called = 0;\n    remove_if(\n        [&](int64_t key, uint32_t tag, void *value_ptr)\n        {\n            called++;\n            int val = *((int *)value_ptr);\n            return (val & 1) == 1;\n        });\n\n    ASSERT_EQ(called, 100u);\n    ASSERT_EQ(m_map.size, 50u);\n\n    for_each(\n        [&](int64_t key, uint32_t tag, void *value_ptr)\n        {\n            int val = *((int *)value_ptr);\n            EXPECT_EQ(0, val & 1);\n            called++;\n        });\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/collections/aeron_linked_queue_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"collections/aeron_linked_queue.h\"\n}\n\nclass LinkedQueueTest : public testing::Test\n{\npublic:\n    void SetUp() override\n    {\n        if (aeron_linked_queue_init(&m_q) < 0)\n        {\n            throw std::runtime_error(\"could not init q\");\n        }\n    }\n\n    void TearDown() override\n    {\n        ASSERT_EQ(aeron_linked_queue_close(&m_q), 0);\n    }\n\nprotected:\n    aeron_linked_queue_t m_q = {};\n};\n\nTEST_F(LinkedQueueTest, shouldInitToEmptyQueue)\n{\n    EXPECT_EQ(aeron_linked_queue_poll(&m_q), nullptr);\n}\n\nTEST_F(LinkedQueueTest, shouldOfferAndPollToEmptyQueue)\n{\n    int64_t element = 64;\n\n    EXPECT_EQ(aeron_linked_queue_offer(&m_q, (void *)element), 0);\n    EXPECT_EQ(aeron_linked_queue_poll(&m_q), (void *)element);\n    EXPECT_EQ(aeron_linked_queue_peek(&m_q), nullptr);\n}\n\nTEST_F(LinkedQueueTest, shouldFIFO)\n{\n    EXPECT_EQ(aeron_linked_queue_offer(&m_q, (void *)0x1), 0);\n    EXPECT_EQ(aeron_linked_queue_peek(&m_q), (void *)0x1);\n    EXPECT_EQ(aeron_linked_queue_offer(&m_q, (void *)0x2), 0);\n    EXPECT_EQ(aeron_linked_queue_offer(&m_q, (void *)0x3), 0);\n    EXPECT_EQ(aeron_linked_queue_peek(&m_q), (void *)0x1);\n    EXPECT_EQ(aeron_linked_queue_poll(&m_q), (void *)0x1);\n    EXPECT_EQ(aeron_linked_queue_poll(&m_q), (void *)0x2);\n    EXPECT_EQ(aeron_linked_queue_poll(&m_q), (void *)0x3);\n    EXPECT_EQ(aeron_linked_queue_peek(&m_q), nullptr);\n}\n\nTEST_F(LinkedQueueTest, shouldPollToEmptyQueueAfterOfferAndPoll)\n{\n    int64_t element = 64;\n\n    EXPECT_EQ(aeron_linked_queue_offer(&m_q, (void *)element), 0);\n    EXPECT_EQ(aeron_linked_queue_poll(&m_q), (void *)element);\n    EXPECT_EQ(aeron_linked_queue_poll(&m_q), nullptr);\n}\n\nTEST_F(LinkedQueueTest, shouldNotCloseWhenNotEmpty)\n{\n    int64_t element = 64;\n\n    EXPECT_EQ(aeron_linked_queue_offer(&m_q, (void *)element), 0);\n    EXPECT_EQ(aeron_linked_queue_close(&m_q), -1);\n    EXPECT_EQ(aeron_linked_queue_poll(&m_q), (void *)element);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/collections/aeron_str_to_ptr_hash_map_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"collections/aeron_str_to_ptr_hash_map.h\"\n#include \"util/aeron_strutil.h\"\n}\n\nclass StrToPtrHashMapTest : public testing::Test\n{\npublic:\n    ~StrToPtrHashMapTest() override\n    {\n        aeron_str_to_ptr_hash_map_delete(&m_map);\n    }\n\nprotected:\n    static void for_each(void *clientd, const char *key, size_t key_len, void *value)\n    {\n        auto *t = (StrToPtrHashMapTest *)clientd;\n\n        t->m_for_each(key, key_len, value);\n    }\n\n    void for_each(const std::function<void(const char *, size_t, void *)> &func)\n    {\n        m_for_each = func;\n        aeron_str_to_ptr_hash_map_for_each(&m_map, StrToPtrHashMapTest::for_each, this);\n    }\n\n    aeron_str_to_ptr_hash_map_t m_map = {};\n    std::function<void(const char *, size_t, void *)> m_for_each;\n};\n\nTEST_F(StrToPtrHashMapTest, shouldDoPutAndThenGetOnEmptyMap)\n{\n    int value = 42;\n    ASSERT_EQ(aeron_str_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_put(&m_map, \"key\", 3, (void *)&value), 0);\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_get(&m_map, \"key\", 3), &value);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(StrToPtrHashMapTest, shouldReplaceExistingValueForTheSameKey)\n{\n    int value = 42, new_value = 43;\n    ASSERT_EQ(aeron_str_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_put(&m_map, \"key\", 3, (void *)&value), 0);\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_put(&m_map, \"key\", 3, (void *)&new_value), 0);\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_get(&m_map, \"key\", 3), &new_value);\n    EXPECT_EQ(m_map.size, 1u);\n}\n\nTEST_F(StrToPtrHashMapTest, shouldGrowWhenThresholdExceeded)\n{\n    int value = 42, value_at_16 = 43;\n    char *keys[32];\n    ASSERT_EQ(aeron_str_to_ptr_hash_map_init(&m_map, 32, 0.5f), 0);\n\n    for (size_t i = 0; i < 16; i++)\n    {\n        char tmp[80];\n        snprintf(tmp, sizeof(tmp), \"key %d\", (int)i);\n\n        keys[i] = strdup(tmp);\n\n        EXPECT_EQ(aeron_str_to_ptr_hash_map_put(&m_map, keys[i], strlen(keys[i]), (void *)&value), 0);\n    }\n\n    EXPECT_EQ(m_map.resize_threshold, 16u);\n    EXPECT_EQ(m_map.capacity, 32u);\n    EXPECT_EQ(m_map.size, 16u);\n\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_put(&m_map, \"key 16\", 6, (void *)&value_at_16), 0);\n\n    EXPECT_EQ(m_map.resize_threshold, 32u);\n    EXPECT_EQ(m_map.capacity, 64u);\n    EXPECT_EQ(m_map.size, 17u);\n\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_get(&m_map, \"key 16\", 6), &value_at_16);\n\n    for (size_t i = 0; i < 16; i++)\n    {\n        free(keys[i]);\n    }\n}\n\nTEST_F(StrToPtrHashMapTest, shouldRemoveEntry)\n{\n    int value = 42;\n    ASSERT_EQ(aeron_str_to_ptr_hash_map_init(&m_map, 8, 0.5f), 0);\n\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_put(&m_map, \"key\", 3, (void *)&value), 0);\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_remove(&m_map, \"key\", 3), &value);\n    EXPECT_EQ(m_map.size, 0u);\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_get(&m_map, \"key\", 3), (void *)nullptr);\n}\n\nTEST_F(StrToPtrHashMapTest, shouldNotForEachEmptyMap)\n{\n    ASSERT_EQ(aeron_str_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](const char *key, size_t key_len, void *value_ptr)\n        {\n            called++;\n        });\n\n    ASSERT_EQ(called, 0u);\n}\n\nTEST_F(StrToPtrHashMapTest, shouldForEachNonEmptyMap)\n{\n    int value = 42;\n    ASSERT_EQ(aeron_str_to_ptr_hash_map_init(&m_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR), 0);\n\n    EXPECT_EQ(aeron_str_to_ptr_hash_map_put(&m_map, \"key\", 3, (void *)&value), 0);\n\n    size_t called = 0;\n    for_each(\n        [&](const char *key, size_t key_len, void *value_ptr)\n        {\n            EXPECT_EQ(std::string(key, key_len), std::string(\"key\"));\n            EXPECT_EQ(value_ptr, &value);\n            called++;\n        });\n\n    ASSERT_EQ(called, 1u);\n}"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_atomic_test.cpp",
    "content": "/*\n * Copyright 2023 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_atomic.h\"\n}\n\nTEST(AtomicTest, testGetAndAddInt64)\n{\n    int64_t var = 0;\n    int64_t res;\n\n    AERON_GET_AND_ADD_INT64(res, var, 1);\n    ASSERT_EQ(0, res);\n    ASSERT_EQ(1, var);\n\n    AERON_GET_AND_ADD_INT64(res, var, -1);\n    ASSERT_EQ(1, res);\n    ASSERT_EQ(0, var);\n\n    AERON_GET_AND_ADD_INT64(res, var, INT64_MAX);\n    ASSERT_EQ(0, res);\n    ASSERT_EQ(INT64_MAX, var);\n\n    AERON_GET_AND_ADD_INT64(res, var, -INT64_MAX);\n    ASSERT_EQ(INT64_MAX, res);\n    ASSERT_EQ(0, var);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_blocking_linked_queue_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <thread>\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_blocking_linked_queue.h\"\n}\n\nclass BlockingQueueTest : public testing::Test\n{\npublic:\n    void SetUp() override\n    {\n        if (aeron_blocking_linked_queue_init(&m_q) < 0)\n        {\n            throw std::runtime_error(\"could not init q\");\n        }\n    }\n\n    void TearDown() override\n    {\n        ASSERT_EQ(aeron_blocking_linked_queue_close(&m_q), 0);\n    }\n\nprotected:\n    aeron_blocking_linked_queue_t m_q = {};\n};\n\nTEST_F(BlockingQueueTest, shouldInitToEmptyQueue)\n{\n    EXPECT_TRUE(aeron_blocking_linked_queue_is_empty(&m_q));\n}\n\nTEST_F(BlockingQueueTest, shouldOfferAndPollToEmptyQueue)\n{\n    int64_t element = 64;\n\n    EXPECT_EQ(aeron_blocking_linked_queue_offer(&m_q, (void *)element), 0);\n    EXPECT_FALSE(aeron_blocking_linked_queue_is_empty(&m_q));\n    EXPECT_EQ(aeron_blocking_linked_queue_poll(&m_q), (void *)element);\n    EXPECT_TRUE(aeron_blocking_linked_queue_is_empty(&m_q));\n}\n\nTEST_F(BlockingQueueTest, shouldFIFO)\n{\n    EXPECT_EQ(aeron_blocking_linked_queue_offer(&m_q, (void *)0x1), 0);\n    EXPECT_EQ(aeron_blocking_linked_queue_offer(&m_q, (void *)0x2), 0);\n    EXPECT_EQ(aeron_blocking_linked_queue_offer(&m_q, (void *)0x3), 0);\n    EXPECT_FALSE(aeron_blocking_linked_queue_is_empty(&m_q));\n    EXPECT_EQ(aeron_blocking_linked_queue_poll(&m_q), (void *)0x1);\n    EXPECT_EQ(aeron_blocking_linked_queue_poll(&m_q), (void *)0x2);\n    EXPECT_EQ(aeron_blocking_linked_queue_poll(&m_q), (void *)0x3);\n    EXPECT_TRUE(aeron_blocking_linked_queue_is_empty(&m_q));\n}\n\nTEST_F(BlockingQueueTest, shouldNotCloseWhenNotEmpty)\n{\n    int64_t element = 64;\n\n    EXPECT_EQ(aeron_blocking_linked_queue_offer(&m_q, (void *)element), 0);\n    EXPECT_EQ(aeron_blocking_linked_queue_close(&m_q), -1);\n    EXPECT_EQ(aeron_blocking_linked_queue_poll(&m_q), (void *)element);\n}\n\n#define TOTAL_MESSAGES 1000\n\nTEST_F(BlockingQueueTest, shouldReceiveMessagesFromSeparateThread)\n{\n    int msgs_received = 0;\n\n    std::thread dequeue_thread = std::thread(\n        [&]()\n        {\n            void *element;\n\n            do {\n                element = aeron_blocking_linked_queue_take(&m_q);\n\n                msgs_received++;\n            }\n            while ((void *)0xff != element);\n        });\n\n    for (int i = 0; i < TOTAL_MESSAGES - 1; i++)\n    {\n        aeron_blocking_linked_queue_offer(&m_q, (void *) 0x1);\n    }\n\n    aeron_blocking_linked_queue_offer(&m_q, (void *) 0xff);\n\n    dequeue_thread.join();\n\n    ASSERT_EQ(msgs_received, TOTAL_MESSAGES);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_broadcast_receiver_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_broadcast_receiver.h\"\n}\n\n#define CAPACITY (1024u)\n#define BUFFER_SZ (CAPACITY + AERON_BROADCAST_BUFFER_TRAILER_LENGTH)\n#define MSG_TYPE_ID (101)\n\ntypedef std::array<std::uint8_t, BUFFER_SZ> buffer_t;\n\nclass BroadcastReceiverTest : public testing::Test\n{\npublic:\n\n    BroadcastReceiverTest()\n    {\n        m_buffer.fill(0);\n        m_srcBuffer.fill(0);\n    }\n\nprotected:\n    buffer_t m_buffer = {};\n    buffer_t m_srcBuffer = {};\n};\n\nTEST_F(BroadcastReceiverTest, shouldCalculateCapacityForBuffer)\n{\n    aeron_broadcast_receiver_t receiver;\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size()), 0);\n    EXPECT_EQ(receiver.capacity, BUFFER_SZ - AERON_BROADCAST_BUFFER_TRAILER_LENGTH);\n    EXPECT_EQ(receiver.scratch_buffer_capacity, AERON_BROADCAST_SCRATCH_BUFFER_LENGTH_DEFAULT);\n\n    EXPECT_EQ(0, aeron_broadcast_receiver_close(&receiver));\n}\n\nTEST_F(BroadcastReceiverTest, shouldFreeScratchBuffer)\n{\n    aeron_broadcast_receiver_t receiver;\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size()), 0);\n    ASSERT_EQ(aeron_broadcast_receiver_close(&receiver), 0);\n    EXPECT_EQ(receiver.scratch_buffer, nullptr);\n    EXPECT_EQ(receiver.scratch_buffer_capacity, 0);\n}\n\nTEST_F(BroadcastReceiverTest, shouldErrorForCapacityNotPowerOfTwo)\n{\n    aeron_broadcast_receiver_t receiver;\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size() - 1), -1);\n\n    EXPECT_EQ(receiver.scratch_buffer, nullptr);\n    EXPECT_EQ(receiver.scratch_buffer_capacity, 0);\n}\n\nTEST_F(BroadcastReceiverTest, shouldNotBeLappedBeforeReception)\n{\n    aeron_broadcast_receiver_t receiver;\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(receiver.lapped_count, 0);\n\n    EXPECT_EQ(0, aeron_broadcast_receiver_close(&receiver));\n}\n\nTEST_F(BroadcastReceiverTest, shouldNotReceiveFromEmptyBuffer)\n{\n    aeron_broadcast_receiver_t receiver;\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size()), 0);\n\n    receiver.descriptor->tail_counter = 0;\n\n    EXPECT_FALSE(aeron_broadcast_receiver_receive_next(&receiver));\n\n    EXPECT_EQ(0, aeron_broadcast_receiver_close(&receiver));\n}\n\nTEST_F(BroadcastReceiverTest, shouldReceiveFirstMessageFromBuffer)\n{\n    aeron_broadcast_receiver_t receiver;\n    size_t length = 8;\n    size_t record_length = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t aligned_record_length = AERON_ALIGN(record_length, AERON_BROADCAST_RECORD_ALIGNMENT);\n    size_t tail = aligned_record_length;\n    size_t latest_record = tail - aligned_record_length;\n    size_t record_offset = latest_record;\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size()), 0);\n\n    receiver.descriptor->tail_counter = static_cast<int64_t>(tail);\n    receiver.descriptor->tail_intent_counter = static_cast<int64_t>(tail);\n\n    auto *record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset);\n\n    record->length = (int32_t)record_length;\n    record->msg_type_id = MSG_TYPE_ID;\n\n    EXPECT_TRUE(aeron_broadcast_receiver_receive_next(&receiver));\n\n    record = (aeron_broadcast_record_descriptor_t *)(receiver.buffer + receiver.record_offset);\n\n    EXPECT_EQ(record->msg_type_id, MSG_TYPE_ID);\n    EXPECT_EQ(receiver.record_offset, record_offset);\n    EXPECT_TRUE(aeron_broadcast_receiver_validate(&receiver));\n\n    EXPECT_EQ(0, aeron_broadcast_receiver_close(&receiver));\n}\n\nTEST_F(BroadcastReceiverTest, shouldReceiveTwoMessagesFromBuffer)\n{\n    aeron_broadcast_receiver_t receiver;\n    size_t length = 8;\n    size_t record_length = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t aligned_record_length = AERON_ALIGN(record_length, AERON_BROADCAST_RECORD_ALIGNMENT);\n    size_t tail = aligned_record_length * 2;\n    size_t latest_record = tail - aligned_record_length;\n    size_t record_offset_one = 0;\n    size_t record_offset_two = latest_record;\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size()), 0);\n\n    receiver.descriptor->tail_counter = static_cast<int64_t>(tail);\n    receiver.descriptor->tail_intent_counter = static_cast<int64_t>(tail);\n\n    auto *record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset_one);\n\n    record->length = (int32_t)record_length;\n    record->msg_type_id = MSG_TYPE_ID;\n\n    record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset_two);\n\n    record->length = (int32_t)record_length;\n    record->msg_type_id = MSG_TYPE_ID;\n\n    EXPECT_TRUE(aeron_broadcast_receiver_receive_next(&receiver));\n\n    record = (aeron_broadcast_record_descriptor_t *)(receiver.buffer + receiver.record_offset);\n\n    EXPECT_EQ(record->msg_type_id, MSG_TYPE_ID);\n    EXPECT_EQ(receiver.record_offset, record_offset_one);\n    EXPECT_TRUE(aeron_broadcast_receiver_validate(&receiver));\n\n    EXPECT_TRUE(aeron_broadcast_receiver_receive_next(&receiver));\n\n    record = (aeron_broadcast_record_descriptor_t *)(receiver.buffer + receiver.record_offset);\n\n    EXPECT_EQ(record->msg_type_id, MSG_TYPE_ID);\n    EXPECT_EQ(receiver.record_offset, record_offset_two);\n    EXPECT_TRUE(aeron_broadcast_receiver_validate(&receiver));\n\n    EXPECT_EQ(0, aeron_broadcast_receiver_close(&receiver));\n}\n\nTEST_F(BroadcastReceiverTest, shouldLateJoinTransmission)\n{\n    aeron_broadcast_receiver_t receiver;\n    size_t length = 8;\n    size_t record_length = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t aligned_record_length = AERON_ALIGN(record_length, AERON_BROADCAST_RECORD_ALIGNMENT);\n    size_t tail = CAPACITY * 3 + AERON_BROADCAST_RECORD_HEADER_LENGTH + aligned_record_length;\n    size_t latest_record = tail - aligned_record_length;\n    size_t record_offset = latest_record & (CAPACITY - 1u);\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size()), 0);\n\n    receiver.descriptor->tail_counter = static_cast<int64_t>(tail);\n    receiver.descriptor->tail_intent_counter = static_cast<int64_t>(tail);\n    receiver.descriptor->latest_counter = static_cast<int64_t>(latest_record);\n\n    auto *record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset);\n\n    record->length = (int32_t)record_length;\n    record->msg_type_id = MSG_TYPE_ID;\n\n    EXPECT_TRUE(aeron_broadcast_receiver_receive_next(&receiver));\n\n    record = (aeron_broadcast_record_descriptor_t *)(receiver.buffer + receiver.record_offset);\n\n    EXPECT_EQ(record->msg_type_id, MSG_TYPE_ID);\n    EXPECT_EQ(receiver.record_offset, record_offset);\n    EXPECT_TRUE(aeron_broadcast_receiver_validate(&receiver));\n    EXPECT_GT(receiver.lapped_count, 0);\n\n    EXPECT_EQ(0, aeron_broadcast_receiver_close(&receiver));\n}\n\nTEST_F(BroadcastReceiverTest, shouldCopeWithPaddingRecordAndWrapOfBufferToNextRecord)\n{\n    aeron_broadcast_receiver_t receiver;\n    size_t length = 120;\n    size_t record_length = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t aligned_record_length = AERON_ALIGN(record_length, AERON_BROADCAST_RECORD_ALIGNMENT);\n    size_t catchup_tail = (CAPACITY * 2) - AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t post_padding_tail = catchup_tail + AERON_BROADCAST_RECORD_HEADER_LENGTH + aligned_record_length;\n    size_t latest_record = catchup_tail - aligned_record_length;\n    size_t catchup_offset = latest_record & (CAPACITY - 1u);\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size()), 0);\n\n    receiver.descriptor->tail_counter = static_cast<int64_t>(catchup_tail);\n    receiver.descriptor->tail_intent_counter = static_cast<int64_t>(catchup_tail);\n    receiver.descriptor->latest_counter = static_cast<int64_t>(latest_record);\n\n    auto *record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + catchup_offset);\n\n    record->length = (int32_t)record_length;\n    record->msg_type_id = MSG_TYPE_ID;\n\n    size_t padding_offset = catchup_tail & (CAPACITY - 1u);\n    size_t record_offset = (post_padding_tail - aligned_record_length) & (CAPACITY - 1u);\n\n    record = (aeron_broadcast_record_descriptor_t *)(receiver.buffer + padding_offset);\n    record->length = 0;\n    record->msg_type_id = AERON_BROADCAST_PADDING_MSG_TYPE_ID;\n\n    record = (aeron_broadcast_record_descriptor_t *)(receiver.buffer + record_offset);\n    record->length = (int32_t)record_length;\n    record->msg_type_id = MSG_TYPE_ID;\n\n    EXPECT_TRUE(aeron_broadcast_receiver_receive_next(&receiver));\n\n    receiver.descriptor->tail_counter = static_cast<int64_t>(post_padding_tail);\n    receiver.descriptor->tail_intent_counter = static_cast<int64_t>(post_padding_tail);\n\n    EXPECT_TRUE(aeron_broadcast_receiver_receive_next(&receiver));\n\n    EXPECT_EQ(record->msg_type_id, MSG_TYPE_ID);\n    EXPECT_EQ(receiver.record_offset, record_offset);\n    EXPECT_TRUE(aeron_broadcast_receiver_validate(&receiver));\n\n    EXPECT_EQ(0, aeron_broadcast_receiver_close(&receiver));\n}\n\nTEST_F(BroadcastReceiverTest, shouldDealWithRecordBecomingInvalidDueToOverwrite)\n{\n    aeron_broadcast_receiver_t receiver;\n    size_t length = 8;\n    size_t record_length = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t aligned_record_length = AERON_ALIGN(record_length, AERON_BROADCAST_RECORD_ALIGNMENT);\n    size_t tail = aligned_record_length;\n    size_t latest_record = tail - aligned_record_length;\n    size_t record_offset = latest_record;\n\n    ASSERT_EQ(aeron_broadcast_receiver_init(&receiver, m_buffer.data(), m_buffer.size()), 0);\n\n    receiver.descriptor->tail_counter = static_cast<int64_t>(tail);\n    receiver.descriptor->tail_intent_counter = static_cast<int64_t>(tail);\n\n    auto *record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset);\n\n    record->length = (int32_t)record_length;\n    record->msg_type_id = MSG_TYPE_ID;\n\n    EXPECT_TRUE(aeron_broadcast_receiver_receive_next(&receiver));\n\n    record = (aeron_broadcast_record_descriptor_t *)(receiver.buffer + receiver.record_offset);\n\n    EXPECT_EQ(record->msg_type_id, MSG_TYPE_ID);\n    EXPECT_EQ(receiver.record_offset, record_offset);\n\n    receiver.descriptor->tail_intent_counter = static_cast<int64_t>(tail + (CAPACITY - aligned_record_length));\n\n    EXPECT_FALSE(aeron_broadcast_receiver_validate(&receiver));\n\n    EXPECT_EQ(0, aeron_broadcast_receiver_close(&receiver));\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_broadcast_transmitter_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include <concurrent/aeron_broadcast_transmitter.h>\n}\n\n#define CAPACITY (1024u)\n#define BUFFER_SZ (CAPACITY + AERON_BROADCAST_BUFFER_TRAILER_LENGTH)\n#define MSG_TYPE_ID (101)\n\ntypedef std::array<std::uint8_t, BUFFER_SZ> buffer_t;\n\nclass BroadcastTransmitterTest : public testing::Test\n{\npublic:\n\n    BroadcastTransmitterTest()\n    {\n        m_buffer.fill(0);\n        m_srcBuffer.fill(0);\n    }\n\nprotected:\n    buffer_t m_buffer = {};\n    buffer_t m_srcBuffer = {};\n\n};\n\nTEST_F(BroadcastTransmitterTest, shouldCalculateCapacityForBuffer)\n{\n    aeron_broadcast_transmitter_t transmitter;\n\n    ASSERT_EQ(aeron_broadcast_transmitter_init(&transmitter, m_buffer.data(), m_buffer.size()), 0);\n    EXPECT_EQ(transmitter.capacity, BUFFER_SZ - AERON_BROADCAST_BUFFER_TRAILER_LENGTH);\n}\n\nTEST_F(BroadcastTransmitterTest, shouldErrorForCapacityNotPowerOfTwo)\n{\n    aeron_broadcast_transmitter_t transmitter;\n\n    ASSERT_EQ(aeron_broadcast_transmitter_init(&transmitter, m_buffer.data(), m_buffer.size() - 1), -1);\n}\n\nTEST_F(BroadcastTransmitterTest, shouldErrorWhenMaxMessageSizeExceeded)\n{\n    aeron_broadcast_transmitter_t transmitter;\n\n    ASSERT_EQ(aeron_broadcast_transmitter_init(&transmitter, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(aeron_broadcast_transmitter_transmit(\n        &transmitter, MSG_TYPE_ID, m_srcBuffer.data(), transmitter.max_message_length + 1), -1);\n}\n\nTEST_F(BroadcastTransmitterTest, shouldErrorWhenMessageTypeIdInvalid)\n{\n    aeron_broadcast_transmitter_t transmitter;\n\n    ASSERT_EQ(aeron_broadcast_transmitter_init(&transmitter, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(aeron_broadcast_transmitter_transmit(&transmitter, -1, m_srcBuffer.data(), 32), -1);\n}\n\nTEST_F(BroadcastTransmitterTest, shouldTransmitIntoEmptyBuffer)\n{\n    aeron_broadcast_transmitter_t transmitter;\n    size_t tail = 0;\n    size_t record_offset = tail;\n    size_t length = 8;\n    size_t recordLength = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_BROADCAST_RECORD_ALIGNMENT);\n\n    ASSERT_EQ(aeron_broadcast_transmitter_init(&transmitter, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(aeron_broadcast_transmitter_transmit(&transmitter, MSG_TYPE_ID, m_srcBuffer.data(), length), 0);\n\n    auto *record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset);\n\n    EXPECT_EQ(transmitter.descriptor->tail_intent_counter, (int64_t)(tail + alignedRecordLength));\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(transmitter.descriptor->latest_counter, (int64_t)tail);\n    EXPECT_EQ(transmitter.descriptor->tail_counter, (int64_t)(tail + alignedRecordLength));\n}\n\nTEST_F(BroadcastTransmitterTest, shouldTransmitIntoUsedBuffer)\n{\n    aeron_broadcast_transmitter_t transmitter;\n    size_t tail = AERON_BROADCAST_RECORD_ALIGNMENT * 3;\n    size_t record_offset = tail;\n    size_t length = 8;\n    size_t recordLength = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_BROADCAST_RECORD_ALIGNMENT);\n\n    ASSERT_EQ(aeron_broadcast_transmitter_init(&transmitter, m_buffer.data(), m_buffer.size()), 0);\n\n    transmitter.descriptor->tail_counter = (int64_t)tail;\n\n    EXPECT_EQ(aeron_broadcast_transmitter_transmit(&transmitter, MSG_TYPE_ID, m_srcBuffer.data(), length), 0);\n\n    auto *record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset);\n\n    EXPECT_EQ(transmitter.descriptor->tail_intent_counter, (int64_t)(tail + alignedRecordLength));\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(transmitter.descriptor->latest_counter, (int64_t)tail);\n    EXPECT_EQ(transmitter.descriptor->tail_counter, (int64_t)(tail + alignedRecordLength));\n}\n\nTEST_F(BroadcastTransmitterTest, shouldTransmitIntoEndOfBuffer)\n{\n    aeron_broadcast_transmitter_t transmitter;\n    size_t length = 8;\n    size_t recordLength = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_BROADCAST_RECORD_ALIGNMENT);\n    size_t tail = CAPACITY - alignedRecordLength;\n    size_t record_offset = tail;\n\n    ASSERT_EQ(aeron_broadcast_transmitter_init(&transmitter, m_buffer.data(), m_buffer.size()), 0);\n\n    transmitter.descriptor->tail_counter = (int64_t)tail;\n\n    EXPECT_EQ(aeron_broadcast_transmitter_transmit(&transmitter, MSG_TYPE_ID, m_srcBuffer.data(), length), 0);\n\n    auto *record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset);\n\n    EXPECT_EQ(transmitter.descriptor->tail_intent_counter, (int64_t)(tail + alignedRecordLength));\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(transmitter.descriptor->latest_counter, (int64_t)tail);\n    EXPECT_EQ(transmitter.descriptor->tail_counter, (int64_t)(tail + alignedRecordLength));\n}\n\nTEST_F(BroadcastTransmitterTest, shouldApplyPaddingWhenInsufficientSpaceAtEndOfBuffer)\n{\n    aeron_broadcast_transmitter_t transmitter;\n    size_t tail = CAPACITY - AERON_BROADCAST_RECORD_ALIGNMENT;\n    size_t record_offset = tail;\n    size_t length = AERON_BROADCAST_RECORD_ALIGNMENT + 8;\n    size_t recordLength = length + AERON_BROADCAST_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_BROADCAST_RECORD_ALIGNMENT);\n    const size_t toEndOfBuffer = CAPACITY - record_offset;\n\n    ASSERT_EQ(aeron_broadcast_transmitter_init(&transmitter, m_buffer.data(), m_buffer.size()), 0);\n\n    transmitter.descriptor->tail_counter = (int64_t)tail;\n\n    EXPECT_EQ(aeron_broadcast_transmitter_transmit(&transmitter, MSG_TYPE_ID, m_srcBuffer.data(), length), 0);\n\n    auto *record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset);\n\n    EXPECT_EQ(transmitter.descriptor->tail_intent_counter, (int64_t)(tail + alignedRecordLength + toEndOfBuffer));\n    EXPECT_EQ(record->length, (int32_t)toEndOfBuffer);\n    EXPECT_EQ(record->msg_type_id, AERON_BROADCAST_PADDING_MSG_TYPE_ID);\n\n    tail += toEndOfBuffer;\n    record_offset = 0;\n\n    record = (aeron_broadcast_record_descriptor_t *)(m_buffer.data() + record_offset);\n\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(transmitter.descriptor->latest_counter, (int64_t)tail);\n    EXPECT_EQ(transmitter.descriptor->tail_counter, (int64_t)(tail + alignedRecordLength));\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_counters_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_counters_manager.h\"\n}\n\n#define CAPACITY (1024)\n#define METADATA_CAPACITY (CAPACITY * 4)\n#define MSG_TYPE_ID (101)\n\ntypedef std::array<std::uint8_t, CAPACITY> values_buffer_t;\ntypedef std::array<std::uint8_t, METADATA_CAPACITY> metadata_buffer_t;\n\nstatic const int REUSE_TIMEOUT = 5000;\n\nclass CountersTest : public testing::Test\n{\npublic:\n\n    CountersTest()\n    {\n        m_valuesBuffer.fill(0);\n        m_metadataBuffer.fill(0);\n\n        aeron_counters_manager_init(\n            &m_manager,\n            m_metadataBuffer.data(),\n            m_metadataBuffer.size(),\n            m_valuesBuffer.data(),\n            m_valuesBuffer.size(),\n            &m_cached_clock,\n            REUSE_TIMEOUT);\n\n        aeron_counters_reader_init(\n            &m_reader,\n            m_metadataBuffer.data(),\n            m_metadataBuffer.size(),\n            m_valuesBuffer.data(),\n            m_valuesBuffer.size());\n    }\n\n    ~CountersTest() override\n    {\n        aeron_counters_manager_close(&m_manager);\n    }\n\nprotected:\n    values_buffer_t m_valuesBuffer = {};\n    metadata_buffer_t m_metadataBuffer = {};\n    aeron_counters_reader_t m_reader = {};\n    aeron_counters_manager_t m_manager = {};\n    aeron_clock_cache_t m_cached_clock = {};\n};\n\nTEST_F(CountersTest, shouldReadCounterState)\n{\n    int32_t state;\n\n    EXPECT_EQ(0, aeron_counters_reader_counter_state(&m_reader, 0, &state));\n    EXPECT_EQ(AERON_COUNTER_RECORD_UNUSED, state);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 1234, nullptr, 0, nullptr, 0);\n\n    EXPECT_EQ(0, aeron_counters_reader_counter_state(&m_reader, id, &state));\n    EXPECT_EQ(AERON_COUNTER_RECORD_ALLOCATED, state);\n\n    aeron_counters_manager_free(&m_manager, id);\n\n    EXPECT_EQ(0, aeron_counters_reader_counter_state(&m_reader, id, &state));\n    EXPECT_EQ(AERON_COUNTER_RECORD_RECLAIMED, state);\n\n    EXPECT_EQ(-1, aeron_counters_reader_counter_state(&m_reader, m_reader.max_counter_id + 1, &state));\n    EXPECT_EQ(-1, aeron_counters_reader_counter_state(&m_reader, -1, &state));\n}\n\nTEST_F(CountersTest, shouldReadCounterTypeId)\n{\n    int32_t typeId;\n    int32_t expectedTypeId = 1234;\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, expectedTypeId, nullptr, 0, nullptr, 0);\n\n    EXPECT_EQ(0, aeron_counters_reader_counter_type_id(&m_reader, id, &typeId));\n    EXPECT_EQ(expectedTypeId, typeId);\n}\n\nTEST_F(CountersTest, shouldReadCounterLabel)\n{\n    const char *label = \"label as text\";\n    char buffer[AERON_COUNTERS_MANAGER_METADATA_LENGTH];\n    memset(buffer, 0, AERON_COUNTERS_MANAGER_METADATA_LENGTH);\n\n    EXPECT_EQ(0, aeron_counters_reader_counter_label(&m_reader, 0, buffer, AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 1234, nullptr, 0, label, strlen(label));\n\n    EXPECT_EQ(\n        (int32_t)strlen(label),\n        aeron_counters_reader_counter_label(&m_reader, id, buffer, AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n    EXPECT_STREQ(label, buffer);\n\n    aeron_counters_manager_free(&m_manager, id);\n\n    // We don't reject or change records when freed.\n    EXPECT_EQ(13, aeron_counters_reader_counter_label(&m_reader, 0, buffer, AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n\n    EXPECT_EQ(-1, aeron_counters_reader_counter_label(&m_reader, -1, buffer, AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n    EXPECT_EQ(-1, aeron_counters_reader_counter_label(\n        &m_reader, m_reader.max_counter_id + 1, buffer, AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n}\n\nTEST_F(CountersTest, shouldReadTimeForReuseDeadline)\n{\n    int64_t deadline_ms;\n    EXPECT_EQ(0, aeron_counters_reader_free_for_reuse_deadline_ms(&m_reader, 0, &deadline_ms));\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 1234, nullptr, 0, nullptr, 0);\n\n    EXPECT_EQ(0, aeron_counters_reader_free_for_reuse_deadline_ms(&m_reader, id, &deadline_ms));\n    EXPECT_EQ(AERON_COUNTER_NOT_FREE_TO_REUSE, deadline_ms);\n\n    int64_t now_ms = aeron_epoch_clock();\n    aeron_clock_update_cached_time(&m_cached_clock, now_ms, 0);\n    aeron_counters_manager_free(&m_manager, id);\n\n    EXPECT_EQ(0, aeron_counters_reader_free_for_reuse_deadline_ms(&m_reader, id, &deadline_ms));\n    EXPECT_LE(now_ms + REUSE_TIMEOUT, deadline_ms);\n\n    EXPECT_EQ(\n        -1, aeron_counters_reader_free_for_reuse_deadline_ms(&m_reader, m_reader.max_counter_id + 1, &deadline_ms));\n    EXPECT_EQ(-1, aeron_counters_reader_free_for_reuse_deadline_ms(&m_reader, -1, &deadline_ms));\n}\n\nTEST_F(CountersTest, shouldSetRegistrationId)\n{\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, nullptr, 0);\n\n    int64_t registration_id = 999;\n    EXPECT_EQ(0, aeron_counters_reader_counter_registration_id(&m_reader, id, &registration_id));\n    EXPECT_EQ(AERON_COUNTER_REGISTRATION_ID_DEFAULT, registration_id);\n\n    int64_t expected_registration_id = 777;\n    aeron_counters_manager_counter_registration_id(&m_manager, id, expected_registration_id);\n    EXPECT_EQ(0, aeron_counters_reader_counter_registration_id(&m_reader, id, &registration_id));\n    EXPECT_EQ(expected_registration_id, registration_id);\n}\n\nTEST_F(CountersTest, shouldResetValueAndRegistrationIdIfReused)\n{\n    int32_t id_one = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, nullptr, 0);\n\n    int64_t registration_id_one = 999;\n    EXPECT_EQ(0, aeron_counters_reader_counter_registration_id(&m_reader, id_one, &registration_id_one));\n    EXPECT_EQ(AERON_COUNTER_REGISTRATION_ID_DEFAULT, registration_id_one);\n\n    int64_t expected_registration_id_one = 777;\n    aeron_counters_manager_counter_registration_id(&m_manager, id_one, expected_registration_id_one);\n    EXPECT_EQ(0, aeron_counters_reader_counter_registration_id(&m_reader, id_one, &registration_id_one));\n    EXPECT_EQ(expected_registration_id_one, registration_id_one);\n\n    m_manager.free_to_reuse_timeout_ms = 0;\n    aeron_counters_manager_free(&m_manager, id_one);\n\n    int32_t id_two = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, nullptr, 0);\n\n    int64_t registration_id_two = 999;\n    EXPECT_EQ(0, aeron_counters_reader_counter_registration_id(&m_reader, id_one, &registration_id_two));\n    EXPECT_EQ(id_one, id_two);\n    EXPECT_EQ(AERON_COUNTER_REGISTRATION_ID_DEFAULT, registration_id_two);\n\n    int64_t expected_registration_id_two = 333;\n    aeron_counters_manager_counter_registration_id(&m_manager, id_two, expected_registration_id_two);\n    EXPECT_EQ(0, aeron_counters_reader_counter_registration_id(&m_reader, id_two, &registration_id_two));\n    EXPECT_EQ(expected_registration_id_two, registration_id_two);\n}\n\nTEST_F(CountersTest, shouldSetOwnerId)\n{\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, nullptr, 0);\n\n    int64_t owner_id = 999;\n    EXPECT_EQ(0, aeron_counters_reader_counter_owner_id(&m_reader, id, &owner_id));\n    EXPECT_EQ(AERON_COUNTER_OWNER_ID_DEFAULT, owner_id);\n\n    int64_t expected_owner_id = 777;\n    aeron_counters_manager_counter_owner_id(&m_manager, id, expected_owner_id);\n    EXPECT_EQ(0, aeron_counters_reader_counter_owner_id(&m_reader, id, &owner_id));\n    EXPECT_EQ(expected_owner_id, owner_id);\n}\n\nTEST_F(CountersTest, shouldResetValueAndOwnerIdIfReused)\n{\n    int32_t id_one = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, nullptr, 0);\n\n    int64_t owner_id_one = 999;\n    EXPECT_EQ(0, aeron_counters_reader_counter_owner_id(&m_reader, id_one, &owner_id_one));\n    EXPECT_EQ(AERON_COUNTER_REGISTRATION_ID_DEFAULT, owner_id_one);\n\n    int64_t expected_owner_id_one = 777;\n    aeron_counters_manager_counter_owner_id(&m_manager, id_one, expected_owner_id_one);\n    EXPECT_EQ(0, aeron_counters_reader_counter_owner_id(&m_reader, id_one, &owner_id_one));\n    EXPECT_EQ(expected_owner_id_one, owner_id_one);\n\n    m_manager.free_to_reuse_timeout_ms = 0;\n    aeron_counters_manager_free(&m_manager, id_one);\n\n    int32_t id_two = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, nullptr, 0);\n\n    int64_t owner_id_two = 999;\n    EXPECT_EQ(0, aeron_counters_reader_counter_owner_id(&m_reader, id_one, &owner_id_two));\n    EXPECT_EQ(id_one, id_two);\n    EXPECT_EQ(AERON_COUNTER_REGISTRATION_ID_DEFAULT, owner_id_two);\n\n    int64_t expected_owner_id_two = 333;\n    aeron_counters_manager_counter_owner_id(&m_manager, id_two, expected_owner_id_two);\n    EXPECT_EQ(0, aeron_counters_reader_counter_owner_id(&m_reader, id_two, &owner_id_two));\n    EXPECT_EQ(expected_owner_id_two, owner_id_two);\n}\n\nTEST_F(CountersTest, shouldSetReferenceId)\n{\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, nullptr, 0);\n\n    int64_t reference_id = 999;\n    EXPECT_EQ(0, aeron_counters_reader_counter_reference_id(&m_reader, id, &reference_id));\n    EXPECT_EQ(AERON_COUNTER_REFERENCE_ID_DEFAULT, reference_id);\n\n    int64_t expected_reference_id = 777;\n    aeron_counters_manager_counter_reference_id(&m_manager, id, expected_reference_id);\n    EXPECT_EQ(0, aeron_counters_reader_counter_reference_id(&m_reader, id, &reference_id));\n    EXPECT_EQ(expected_reference_id, reference_id);\n}\n\nTEST_F(CountersTest, shouldResetValueAndReferenceIdIfReused)\n{\n    int32_t id_one = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, nullptr, 0);\n\n    int64_t reference_id_one = 999;\n    EXPECT_EQ(0, aeron_counters_reader_counter_reference_id(&m_reader, id_one, &reference_id_one));\n    EXPECT_EQ(AERON_COUNTER_REGISTRATION_ID_DEFAULT, reference_id_one);\n\n    int64_t expected_reference_id_one = 777;\n    aeron_counters_manager_counter_reference_id(&m_manager, id_one, expected_reference_id_one);\n    EXPECT_EQ(0, aeron_counters_reader_counter_reference_id(&m_reader, id_one, &reference_id_one));\n    EXPECT_EQ(expected_reference_id_one, reference_id_one);\n\n    m_manager.free_to_reuse_timeout_ms = 0;\n    aeron_counters_manager_free(&m_manager, id_one);\n\n    int32_t id_two = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, nullptr, 0);\n\n    int64_t reference_id_two = 999;\n    EXPECT_EQ(0, aeron_counters_reader_counter_reference_id(&m_reader, id_one, &reference_id_two));\n    EXPECT_EQ(id_one, id_two);\n    EXPECT_EQ(AERON_COUNTER_REGISTRATION_ID_DEFAULT, reference_id_two);\n\n    int64_t expected_reference_id_two = 333;\n    aeron_counters_manager_counter_reference_id(&m_manager, id_two, expected_reference_id_two);\n    EXPECT_EQ(0, aeron_counters_reader_counter_reference_id(&m_reader, id_two, &reference_id_two));\n    EXPECT_EQ(expected_reference_id_two, reference_id_two);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_distinct_error_log_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <atomic>\n#include <thread>\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_atomic.h\"\n#include \"concurrent/aeron_distinct_error_log.h\"\n}\n\n#define CAPACITY (256 * 1024)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\ntypedef std::array<std::uint8_t, 32> insufficient_buffer_t;\n\nclass DistinctErrorLogTest : public testing::Test\n{\npublic:\n    DistinctErrorLogTest()\n    {\n        m_buffer.fill(0);\n        clock_value = 7;\n        m_atomic_clock_value = 1;\n    }\n\n    ~DistinctErrorLogTest() override\n    {\n        if (m_close_log)\n        {\n            aeron_distinct_error_log_close(&m_log);\n        }\n    }\n\n    void SetUp() override\n    {\n        m_buffer.fill(0);\n    }\n\n    static int64_t clock()\n    {\n        return clock_value;\n    }\n\n    static int64_t atomic_clock()\n    {\n        return m_atomic_clock_value.fetch_add(1);\n    }\n\nprotected:\n    AERON_DECL_ALIGNED(buffer_t m_buffer, 16) = {};\n    aeron_distinct_error_log_t m_log = {};\n    bool m_close_log = true;\n    static std::atomic<int64_t> m_atomic_clock_value;\n    static int64_t clock_value;\n};\n\nint64_t DistinctErrorLogTest::clock_value;\nstd::atomic<int64_t> DistinctErrorLogTest::m_atomic_clock_value;\n\nTEST_F(DistinctErrorLogTest, shouldFailToRecordWhenInsufficientSpace)\n{\n    AERON_DECL_ALIGNED(insufficient_buffer_t buffer, 16) = {};\n\n    ASSERT_EQ(aeron_distinct_error_log_init(&m_log, buffer.data(), buffer.size(), clock), 0);\n\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description\"), -1);\n}\n\nTEST_F(DistinctErrorLogTest, shouldRecordFirstObservation)\n{\n    ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), clock), 0);\n\n    auto *entry = (aeron_error_log_entry_t *)(m_log.buffer);\n\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description\"), 0);\n\n    EXPECT_EQ(entry->first_observation_timestamp, 7);\n    EXPECT_GT(entry->length, (int32_t)AERON_ERROR_LOG_HEADER_LENGTH);\n    EXPECT_EQ(entry->observation_count, 1);\n    EXPECT_EQ(entry->last_observation_timestamp, 7);\n    EXPECT_EQ(aeron_distinct_error_log_num_observations(&m_log), (size_t)1);\n}\n\nTEST_F(DistinctErrorLogTest, shouldSummariseObservations)\n{\n    ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), clock), 0);\n\n    auto *entry = (aeron_error_log_entry_t *)(m_log.buffer);\n\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description\"), 0);\n    clock_value++;\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description\"), 0);\n\n    EXPECT_EQ(entry->first_observation_timestamp, 7);\n    EXPECT_GT(entry->length, (int32_t)AERON_ERROR_LOG_HEADER_LENGTH);\n    EXPECT_EQ(entry->observation_count, 2);\n    EXPECT_EQ(entry->last_observation_timestamp, 8);\n    EXPECT_EQ(aeron_distinct_error_log_num_observations(&m_log), (size_t)1);\n}\n\nTEST_F(DistinctErrorLogTest, shouldRecordTwoDistinctObservations)\n{\n    ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), clock), 0);\n\n    auto *entry = (aeron_error_log_entry_t *)(m_log.buffer);\n\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description 1\"), 0);\n    clock_value++;\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 2, \"description 2\"), 0);\n\n    EXPECT_EQ(entry->first_observation_timestamp, 7);\n    EXPECT_GT(entry->length, (int32_t)AERON_ERROR_LOG_HEADER_LENGTH);\n    EXPECT_EQ(entry->observation_count, 1);\n    EXPECT_EQ(entry->last_observation_timestamp, 7);\n\n    size_t length = AERON_ALIGN(entry->length, AERON_ERROR_LOG_RECORD_ALIGNMENT);\n    entry = (aeron_error_log_entry_t *)(m_log.buffer + length);\n\n    EXPECT_EQ(entry->first_observation_timestamp, 8);\n    EXPECT_GT(entry->length, (int32_t)AERON_ERROR_LOG_HEADER_LENGTH);\n    EXPECT_EQ(entry->observation_count, 1);\n    EXPECT_EQ(entry->last_observation_timestamp, 8);\n\n    EXPECT_EQ(aeron_distinct_error_log_num_observations(&m_log), (size_t)2);\n}\n\nstatic void error_log_reader_no_entries(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    FAIL();\n}\n\nTEST_F(DistinctErrorLogTest, shouldReadNoErrorsFromEmptyLog)\n{\n    ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), clock), 0);\n\n    EXPECT_EQ(aeron_error_log_read(\n        m_buffer.data(), m_buffer.size(), error_log_reader_no_entries, nullptr, 0), (size_t)0);\n}\n\nstatic void error_log_reader_first_observation(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    static int called = 0;\n\n    EXPECT_EQ(observation_count, 1);\n    EXPECT_EQ(first_observation_timestamp, 7);\n    EXPECT_EQ(last_observation_timestamp, 7);\n    EXPECT_GT(error_length, (size_t)0);\n    EXPECT_EQ(++called, 1);\n}\n\nTEST_F(DistinctErrorLogTest, shouldReadFirstObservation)\n{\n    ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), clock), 0);\n\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description 1\"), 0);\n\n    EXPECT_EQ(aeron_error_log_read(\n        m_buffer.data(), m_buffer.size(), error_log_reader_first_observation, nullptr, 0), (size_t)1);\n}\n\nstatic void error_log_reader_summarised_observation(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    static int called = 0;\n\n    EXPECT_EQ(observation_count, 2);\n    EXPECT_EQ(first_observation_timestamp, 7);\n    EXPECT_EQ(last_observation_timestamp, 8);\n    EXPECT_GT(error_length, (size_t)0);\n    EXPECT_EQ(++called, 1);\n}\n\nTEST_F(DistinctErrorLogTest, shouldReadSummarisedObservation)\n{\n    ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), clock), 0);\n\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description 1\"), 0);\n    clock_value++;\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description 1\"), 0);\n\n    EXPECT_EQ(aeron_error_log_read(\n        m_buffer.data(), m_buffer.size(), error_log_reader_summarised_observation, nullptr, 0), (size_t)1);\n}\n\nstatic void error_log_reader_two_observations(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    static int called = 0;\n\n    ++called;\n    if (called == 1)\n    {\n        EXPECT_EQ(observation_count, 1);\n        EXPECT_EQ(first_observation_timestamp, 7);\n        EXPECT_EQ(last_observation_timestamp, 7);\n        EXPECT_GT(error_length, (size_t) 0);\n    }\n    else if (called == 2)\n    {\n        EXPECT_EQ(observation_count, 1);\n        EXPECT_EQ(first_observation_timestamp, 8);\n        EXPECT_EQ(last_observation_timestamp, 8);\n        EXPECT_GT(error_length, (size_t) 0);\n    }\n    else\n    {\n        FAIL();\n    }\n}\n\nTEST_F(DistinctErrorLogTest, shouldReadTwoDistinctObservations)\n{\n    ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), clock), 0);\n\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description 1\"), 0);\n    clock_value++;\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 2, \"description 2\"), 0);\n\n    EXPECT_EQ(aeron_error_log_read(\n        m_buffer.data(), m_buffer.size(), error_log_reader_two_observations, nullptr, 0), (size_t)2);\n}\n\nstatic void error_log_reader_since_observation(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    static int called = 0;\n\n    EXPECT_EQ(observation_count, 1);\n    EXPECT_EQ(first_observation_timestamp, 8);\n    EXPECT_EQ(last_observation_timestamp, 8);\n    EXPECT_GT(error_length, (size_t)0);\n    EXPECT_EQ(++called, 1);\n}\n\nTEST_F(DistinctErrorLogTest, shouldReadOneObservationSinceTimestamp)\n{\n    ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), clock), 0);\n\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 1, \"description 1\"), 0);\n    clock_value++;\n    EXPECT_EQ(aeron_distinct_error_log_record(&m_log, 2, \"description 2\"), 0);\n\n    EXPECT_EQ(aeron_error_log_read(\n        m_buffer.data(), m_buffer.size(), error_log_reader_since_observation, nullptr, 8), (size_t)1);\n}\n\n#define APPENDS_PER_THREAD (1000)\n#define NUM_THREADS (2)\n#define ITERATIONS (200)\n\nstatic void distinct_message_log_reader(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    ASSERT_GE(observation_count, 0);\n    ASSERT_LE(observation_count, APPENDS_PER_THREAD);\n    auto counts = (std::vector<int> *)clientd;\n    char *end;\n    const auto index = strtol(error, &end, 10);\n    counts->at(index) = observation_count;\n}\n\nstatic void test_append_distinct_message(aeron_distinct_error_log_t *error_log, buffer_t *log_buffer)\n{\n    std::atomic<int> countDown(NUM_THREADS);\n    std::vector<std::thread> threads;\n\n    for (int i = 0; i < NUM_THREADS; i++)\n    {\n        threads.push_back(std::thread(\n            [&]()\n            {\n                const int err_code = countDown.fetch_sub(1) - 1;\n                while (countDown > 0)\n                {\n                    std::this_thread::yield();\n                }\n\n                const std::string err_msg = std::to_string(err_code);\n                for (int m = 0; m < APPENDS_PER_THREAD; m++)\n                {\n                    ASSERT_EQ(0, aeron_distinct_error_log_record(error_log, err_code, err_msg.c_str()));\n                }\n            }));\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n\n    std::vector<int> counts(NUM_THREADS);\n\n    size_t entries = aeron_error_log_read(\n        log_buffer->data(), log_buffer->size(), distinct_message_log_reader, &counts, 0);\n    ASSERT_EQ(entries, (size_t)NUM_THREADS) << \"invalid number of messages\";\n\n    for (int count : counts)\n    {\n        EXPECT_EQ(count, APPENDS_PER_THREAD) << \"invalid number of observations\";\n    }\n}\n\nTEST_F(DistinctErrorLogTest, concurrentAppendDistinctMessages)\n{\n    m_close_log = false;\n    for (int i = 0; i < ITERATIONS; i++)\n    {\n        m_buffer.fill(0);\n        ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), atomic_clock), 0);\n        test_append_distinct_message(&m_log, &m_buffer);\n\n        aeron_distinct_error_log_close(&m_log);\n    }\n}\n\ntypedef struct test_same_message_stct\n{\n    int32_t observation_count;\n    int64_t first_observation_timestamp;\n    int64_t last_observation_timestamp;\n}\ntest_same_message_t;\n\nstatic void same_message_log_reader(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    ASSERT_GE(observation_count, 0);\n    auto msg = (test_same_message_t *)clientd;\n    msg->observation_count = observation_count;\n\n    if (0 != msg->first_observation_timestamp)\n    {\n        ASSERT_EQ(first_observation_timestamp, msg->first_observation_timestamp);\n    }\n    else\n    {\n        msg->first_observation_timestamp = first_observation_timestamp;\n    }\n\n    if (0 != msg->last_observation_timestamp)\n    {\n        ASSERT_GE(last_observation_timestamp, msg->last_observation_timestamp);\n    }\n    msg->last_observation_timestamp = last_observation_timestamp;\n}\n\nstatic void test_update_same_message(aeron_distinct_error_log_t *error_log, buffer_t *log_buffer)\n{\n    std::atomic<int> countDown(NUM_THREADS);\n    std::vector<std::thread> threads;\n    const int err_code = 111;\n    const std::string err_msg = std::string(\"common message\");\n\n    for (int i = 0; i < NUM_THREADS; i++)\n    {\n        threads.push_back(std::thread(\n            [&]()\n            {\n                countDown.fetch_sub(1);\n                while (countDown > 0)\n                {\n                    std::this_thread::yield();\n                }\n\n                for (int m = 0; m < APPENDS_PER_THREAD; m++)\n                {\n                    ASSERT_EQ(0, aeron_distinct_error_log_record(error_log, err_code, err_msg.c_str()));\n                }\n            }));\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n\n    test_same_message_t count = {};\n    count.first_observation_timestamp = 0;\n    count.last_observation_timestamp = 0;\n    count.observation_count = 0;\n\n    size_t entries = aeron_error_log_read(log_buffer->data(), log_buffer->size(), same_message_log_reader, &count, 0);\n    ASSERT_EQ(entries, (size_t)1) << \"message appended multiple times\";\n    ASSERT_EQ(count.observation_count,  (APPENDS_PER_THREAD * NUM_THREADS)) << \"missing observations\";\n}\n\nTEST_F(DistinctErrorLogTest, concurrentAppendSameMessage)\n{\n    m_close_log = false;\n    for (int i = 0; i < ITERATIONS; i++)\n    {\n        m_buffer.fill(0);\n        ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), atomic_clock), 0);\n        test_update_same_message(&m_log, &m_buffer);\n\n        aeron_distinct_error_log_close(&m_log);\n    }\n}\n\nstatic void unique_message_log_reader(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    ASSERT_GE(observation_count, 0);\n    ASSERT_LE(observation_count, 1);\n    auto counts = (std::vector<int> *)clientd;\n    char *end;\n    const auto index = strtol(error, &end, 10);\n    counts->at(index) = 1;\n}\n\nstatic void test_append_unique_messages(aeron_distinct_error_log_t *error_log, buffer_t *log_buffer)\n{\n    std::atomic<int> countDown(NUM_THREADS);\n    std::vector<std::thread> threads;\n\n    for (int i = 0; i < NUM_THREADS; i++)\n    {\n        threads.push_back(std::thread(\n            [&]()\n            {\n                const int err_code_offset = (countDown.fetch_sub(1) - 1) * APPENDS_PER_THREAD;\n                while (countDown > 0)\n                {\n                    std::this_thread::yield();\n                }\n\n                for (int m = 0; m < APPENDS_PER_THREAD; m++)\n                {\n                    const int err_code = err_code_offset + m;\n                    const std::string err_msg = std::to_string(err_code);\n                    ASSERT_EQ(0, aeron_distinct_error_log_record(error_log, err_code, err_msg.c_str()));\n                }\n            }));\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n\n    const size_t total_number_of_entries = APPENDS_PER_THREAD * NUM_THREADS;\n    std::vector<int> counts(total_number_of_entries);\n\n    size_t entries = aeron_error_log_read(\n        log_buffer->data(), log_buffer->size(), unique_message_log_reader, &counts, 0);\n    ASSERT_EQ(entries, total_number_of_entries);\n\n    for (int count : counts)\n    {\n        EXPECT_EQ(count, 1);\n    }\n}\n\nTEST_F(DistinctErrorLogTest, concurrentAppendUniqueMessages)\n{\n    m_close_log = false;\n    for (int i = 0; i < ITERATIONS; i++)\n    {\n        m_buffer.fill(0);\n        ASSERT_EQ(aeron_distinct_error_log_init(&m_log, m_buffer.data(), m_buffer.size(), atomic_clock), 0);\n\n        test_append_unique_messages(&m_log, &m_buffer);\n\n        aeron_distinct_error_log_close(&m_log);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_executor_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include <queue>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_executor.h\"\n}\n\ntypedef struct\n{\n    int some_value;\n    int some_other_value;\n}\ntask_clientd_t;\n\nclass ExecutorTest : public testing::Test\n{\npublic:\n    void SetUp() override\n    {\n        if (aeron_executor_init(\n            &m_executor,\n            be_async(),\n            on_execution_complete_cb(),\n            this) < 0)\n        {\n            throw std::runtime_error(\"could not init q\");\n        }\n    }\n\n    void TearDown() override\n    {\n        aeron_executor_close(&m_executor);\n    }\n\n    virtual bool be_async() = 0;\n\n    virtual aeron_executor_on_execution_complete_func_t on_execution_complete_cb()\n    {\n        return nullptr;\n    }\n\n    static int on_execute(void *task_clientd, void *executor_clientd)\n    {\n        auto *e = (ExecutorTest *)executor_clientd;\n        e->m_on_execute_count++;\n        return e->m_on_execute(task_clientd, e);\n    }\n\n    static void on_complete(int result, int errcode, const char *errmsg, void *task_clientd, void *executor_clientd)\n    {\n        auto *e = (ExecutorTest *)executor_clientd;\n        e->m_on_complete(result, task_clientd, e);\n        e->m_on_complete_count++;\n    }\n\n    static void on_cancel(void *task_clientd, void *executor_clientd)\n    {\n    }\n\nprotected:\n    aeron_executor_t m_executor = {};\n    std::function<int(void *, void *)> m_on_execute;\n    std::function<int(int, void *, void *)> m_on_complete;\n    int m_on_execute_count = 0;\n    int m_on_complete_count = 0;\n};\n\nclass SyncExecutorTest : public ExecutorTest\n{\npublic:\n    bool be_async() override\n    {\n        return false;\n    }\n};\n\nTEST_F(SyncExecutorTest, shouldExecuteSynchronously)\n{\n    task_clientd_t tcd;\n\n    tcd.some_value = 0;\n\n    m_on_execute =\n        [&](void *task_clientd, void *executor_clientd)\n        {\n            ((task_clientd_t *)task_clientd)->some_value += 100;\n\n            return 1;\n        };\n\n    m_on_complete =\n        [&](int result, void *task_clientd, void *executor_clientd)\n        {\n            ((task_clientd_t *)task_clientd)->some_value += 100;\n\n            return ++result;\n        };\n\n    int result = aeron_executor_submit(\n        &m_executor,\n        SyncExecutorTest::on_execute,\n        SyncExecutorTest::on_complete,\n        SyncExecutorTest::on_cancel,\n        &tcd);\n\n    ASSERT_EQ(result, 0);\n    ASSERT_EQ(m_on_execute_count, 1);\n    ASSERT_EQ(m_on_complete_count, 1);\n    ASSERT_EQ(tcd.some_value, 200);\n}\n\n#define TOTAL_TASKS 1000\n\n\nclass AsyncExecutorTest : public ExecutorTest\n{\npublic:\n    bool be_async() override\n    {\n        return true;\n    }\n};\n\nTEST_F(AsyncExecutorTest, shouldExecuteAsynchronously)\n{\n    task_clientd_t tcd;\n\n    tcd.some_value = 0;\n    tcd.some_other_value = 0;\n\n    m_on_execute =\n        [&](void *task_clientd, void *executor_clientd)\n        {\n            ((task_clientd_t *)task_clientd)->some_value += 100;\n\n            return 1;\n        };\n\n    m_on_complete =\n        [&](int result, void *task_clientd, void *executor_clientd)\n        {\n            ((task_clientd_t *)task_clientd)->some_other_value += 50;\n\n            return ++result;\n        };\n\n    for (int i = 0; i < TOTAL_TASKS; i++)\n    {\n        int result = aeron_executor_submit(\n            &m_executor,\n            SyncExecutorTest::on_execute,\n            SyncExecutorTest::on_complete,\n            SyncExecutorTest::on_cancel,\n            &tcd);\n        ASSERT_EQ(result, 0);\n    }\n\n    int work_count = 0;\n\n    while (m_on_complete_count < TOTAL_TASKS)\n    {\n        work_count += aeron_executor_process_completions(&m_executor, 50);\n    }\n\n    ASSERT_EQ(work_count, TOTAL_TASKS);\n    ASSERT_EQ(m_on_execute_count, TOTAL_TASKS);\n    ASSERT_EQ(m_on_complete_count, TOTAL_TASKS);\n    ASSERT_EQ(tcd.some_value, TOTAL_TASKS * 100);\n    ASSERT_EQ(tcd.some_other_value, TOTAL_TASKS * 50);\n}\n\nclass AsyncNoReturnQueueExecutorTest : public AsyncExecutorTest\n{\npublic:\n    aeron_executor_on_execution_complete_func_t on_execution_complete_cb() override\n    {\n        return AsyncNoReturnQueueExecutorTest::on_execution_complete_cb;\n    };\n\n    static int on_execution_complete_cb(aeron_executor_task_t *task, void *executor_clientd)\n    {\n        auto e = ((AsyncNoReturnQueueExecutorTest *)executor_clientd);\n\n        task->on_complete(\n            task->result,\n            task->errcode,\n            task->errmsg,\n            task->clientd,\n            task->executor->clientd);\n\n        e->m_on_execution_complete_count++;\n\n        return 0;\n    }\n\nprotected:\n    int m_on_execution_complete_count = 0;\n};\n\n\nTEST_F(AsyncNoReturnQueueExecutorTest, shouldExecuteAsynchronously)\n{\n    task_clientd_t tcd;\n\n    tcd.some_value = 0;\n\n    m_on_execute =\n        [&](void *task_clientd, void *executor_clientd)\n        {\n            ((task_clientd_t *)task_clientd)->some_value += 100;\n\n            return 1;\n        };\n\n    m_on_complete =\n        [&](int result, void *task_clientd, void *executor_clientd)\n        {\n            ((task_clientd_t *)task_clientd)->some_value += 100;\n\n            return ++result;\n        };\n\n    for (int i = 0; i < TOTAL_TASKS; i++)\n    {\n        int result = aeron_executor_submit(\n            &m_executor,\n            SyncExecutorTest::on_execute,\n            SyncExecutorTest::on_complete,\n            SyncExecutorTest::on_cancel,\n            &tcd);\n        ASSERT_EQ(result, 0);\n    }\n\n    while (m_on_execution_complete_count < TOTAL_TASKS)\n    {\n        sched_yield();\n    }\n\n    ASSERT_EQ(m_on_execution_complete_count, TOTAL_TASKS);\n    ASSERT_EQ(m_on_execute_count, TOTAL_TASKS);\n    ASSERT_EQ(m_on_complete_count, TOTAL_TASKS);\n    ASSERT_EQ(tcd.some_value, TOTAL_TASKS * 200);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_mpsc_concurrent_array_queue_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n#include <thread>\n#include <atomic>\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include <concurrent/aeron_mpsc_concurrent_array_queue.h>\n}\n\n#define CAPACITY (8u)\n\nclass MpscQueueTest : public testing::Test\n{\npublic:\n    MpscQueueTest()\n    {\n        if (aeron_mpsc_concurrent_array_queue_init(&m_q, CAPACITY) < 0)\n        {\n            throw std::runtime_error(\"could not init q\");\n        }\n    }\n\n    ~MpscQueueTest() override\n    {\n        aeron_mpsc_concurrent_array_queue_close(&m_q);\n    }\n\n    static void drain_func(void *clientd, void *element)\n    {\n        auto *t = (MpscQueueTest *)clientd;\n\n        (*t).m_drain(element);\n    }\n\n    void fillQueue()\n    {\n        for (size_t i = 1; i <= CAPACITY; i++)\n        {\n            ASSERT_EQ(aeron_mpsc_concurrent_array_queue_offer(&m_q, (void *)i), AERON_OFFER_SUCCESS);\n        }\n    }\n\nprotected:\n    aeron_mpsc_concurrent_array_queue_t m_q = {};\n    std::function<void(volatile void *)> m_drain;\n};\n\nTEST_F(MpscQueueTest, shouldGetSizeWhenEmpty)\n{\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_size(&m_q), 0u);\n}\n\nTEST_F(MpscQueueTest, shouldReturnErrorWhenNullOffered)\n{\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_offer(&m_q, nullptr), AERON_OFFER_ERROR);\n}\n\nTEST_F(MpscQueueTest, shouldOfferAndDrainToEmptyQueue)\n{\n    int64_t element = 64;\n\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_offer(&m_q, (void *)element), AERON_OFFER_SUCCESS);\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_size(&m_q), 1u);\n\n    m_drain =\n        [&](volatile void *e)\n        {\n            ASSERT_EQ(e, (void *)element);\n        };\n\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_drain_all(&m_q, MpscQueueTest::drain_func, this), 1u);\n}\n\nTEST_F(MpscQueueTest, shouldFailToOfferToFullQueue)\n{\n    size_t element = CAPACITY + 1;\n\n    fillQueue();\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_size(&m_q), CAPACITY);\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_offer(&m_q, (void *)element), AERON_OFFER_FULL);\n}\n\nTEST_F(MpscQueueTest, shouldDrainSingleElementFromFullQueue)\n{\n    fillQueue();\n\n    m_drain =\n        [&](volatile void *e)\n        {\n            ASSERT_EQ(e, (void *)1);\n        };\n\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_drain(&m_q, MpscQueueTest::drain_func, this, 1), 1u);\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_size(&m_q), CAPACITY - 1);\n}\n\nTEST_F(MpscQueueTest, shouldDrainNothingFromEmptyQueue)\n{\n    m_drain =\n        [&](volatile void *e)\n        {\n            FAIL();\n        };\n\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_drain_all(&m_q, MpscQueueTest::drain_func, this), 0u);\n}\n\nTEST_F(MpscQueueTest, shouldDrainFullQueue)\n{\n    fillQueue();\n\n    int64_t counter = 1;\n    m_drain =\n        [&](volatile void *e)\n        {\n            ASSERT_EQ(e, (void *)counter);\n            counter++;\n        };\n\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_drain_all(&m_q, MpscQueueTest::drain_func, this), CAPACITY);\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_size(&m_q), 0u);\n}\n\nTEST_F(MpscQueueTest, shouldDrainFullQueueWithLimit)\n{\n    size_t limit = CAPACITY / 2;\n    fillQueue();\n\n    int64_t counter = 1;\n    m_drain =\n        [&](volatile void *e)\n        {\n            ASSERT_EQ(e, (void *)counter);\n            counter++;\n        };\n\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_drain(&m_q, MpscQueueTest::drain_func, this, limit), limit);\n    EXPECT_EQ(aeron_mpsc_concurrent_array_queue_size(&m_q), CAPACITY - limit);\n}\n\n#define NUM_MESSAGES_PER_PUBLISHER (10 * 1000 * 1000)\n#define NUM_PUBLISHERS (2)\n\ntypedef struct mpsc_concurrent_test_data_stct\n{\n    uint32_t id;\n    uint32_t num;\n}\n    mpsc_concurrent_test_data_t;\n\nstatic void mpsc_queue_concurrent_handler(void *clientd, void *element)\n{\n    auto *counts = (uint32_t *)clientd;\n    auto *data = (mpsc_concurrent_test_data_t *)element;\n\n    ASSERT_EQ(counts[data->id], data->num);\n    counts[data->id]++;\n    delete data;\n}\n\nTEST(MpscQueueConcurrentTest, shouldExchangeMessages)\n{\n    AERON_DECL_ALIGNED(aeron_mpsc_concurrent_array_queue_t q, 16);\n\n    ASSERT_EQ(aeron_mpsc_concurrent_array_queue_init(&q, CAPACITY), 0);\n\n    std::atomic<int> countDown(NUM_PUBLISHERS);\n    std::atomic<unsigned int> publisherId(0);\n\n    std::vector<std::thread> threads;\n    size_t msgCount = 0;\n    uint32_t counts[NUM_PUBLISHERS];\n\n    for (unsigned int &count : counts)\n    {\n        count = 0;\n    }\n\n    for (int i = 0; i < NUM_PUBLISHERS; i++)\n    {\n        threads.push_back(std::thread(\n            [&]()\n            {\n                uint32_t id = publisherId.fetch_add(1);\n\n                countDown--;\n                while (countDown > 0)\n                {\n                    std::this_thread::yield();\n                }\n\n                for (uint32_t m = 0; m < NUM_MESSAGES_PER_PUBLISHER; m++)\n                {\n                    auto *data = new mpsc_concurrent_test_data_t;\n\n                    data->id = id;\n                    data->num = m;\n\n                    while (AERON_OFFER_SUCCESS != aeron_mpsc_concurrent_array_queue_offer(&q, data))\n                    {\n                        std::this_thread::yield();\n                    }\n                }\n            }));\n    }\n\n    while (msgCount < (NUM_MESSAGES_PER_PUBLISHER * NUM_PUBLISHERS))\n    {\n        const size_t drainCount = aeron_mpsc_concurrent_array_queue_drain(\n            &q, mpsc_queue_concurrent_handler, counts, CAPACITY);\n\n        if (0 == drainCount)\n        {\n            std::this_thread::yield();\n        }\n\n        msgCount += drainCount;\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n\n    aeron_mpsc_concurrent_array_queue_close(&q);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_mpsc_rb_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n#include <thread>\n#include <atomic>\n#include <limits>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_mpsc_rb.h\"\n#include \"util/aeron_error.h\"\n}\n\n#undef max\n#define CAPACITY (1024)\n#define BUFFER_SZ (CAPACITY + AERON_RB_TRAILER_LENGTH)\n#define ODD_BUFFER_SZ ((CAPACITY - 1) + AERON_RB_TRAILER_LENGTH)\n#define MSG_TYPE_ID (101)\n\ntypedef std::array<std::uint8_t, BUFFER_SZ> buffer_t;\ntypedef std::array<std::uint8_t, ODD_BUFFER_SZ> odd_sized_buffer_t;\n\nclass MpscRbTest : public testing::Test\n{\npublic:\n\n    MpscRbTest()\n    {\n        m_buffer.fill(0);\n        m_srcBuffer.fill(0);\n    }\n\nprotected:\n    buffer_t m_buffer = {};\n    buffer_t m_srcBuffer = {};\n};\n\nTEST_F(MpscRbTest, shouldCalculateCapacityForBuffer)\n{\n    aeron_mpsc_rb_t rb;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    EXPECT_EQ(rb.capacity, BUFFER_SZ - AERON_RB_TRAILER_LENGTH);\n}\n\nTEST_F(MpscRbTest, shouldErrorForCapacityNotPowerOfTwo)\n{\n    aeron_mpsc_rb_t rb;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size() - 1), -1);\n}\n\nTEST_F(MpscRbTest, shouldErrorForCapacityLessThanTheMinCapacity)\n{\n    aeron_mpsc_rb_t rb;\n    const size_t capacity = (AERON_MPSC_RB_MIN_CAPACITY / 2);\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), AERON_RB_TRAILER_LENGTH + capacity), -1);\n    ASSERT_EQ(aeron_errcode(), EINVAL);\n    const std::string expected_err_msg = \"Invalid capacity: \" + std::to_string(capacity);\n    const std::string actual_err_msg = std::string(aeron_errmsg());\n    ASSERT_NE(actual_err_msg.find(expected_err_msg), std::string::npos);\n}\n\nTEST_F(MpscRbTest, shouldErrorWhenMaxMessageSizeExceeded)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(aeron_mpsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), rb.max_message_length + 1), AERON_RB_ERROR);\n}\n\nTEST_F(MpscRbTest, shouldErrorWhenMinCapacityIsUsedAndMessageSizeIsNotZero)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), AERON_RB_TRAILER_LENGTH + AERON_MPSC_RB_MIN_CAPACITY), 0);\n\n    EXPECT_EQ(rb.max_message_length, 0);\n    EXPECT_EQ(aeron_mpsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), 1), AERON_RB_ERROR);\n}\n\nTEST_F(MpscRbTest, shouldWriteAnEmptyMessageWhenMinCapacityIsUsed)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), AERON_RB_TRAILER_LENGTH + AERON_MPSC_RB_MIN_CAPACITY), 0);\n\n    EXPECT_EQ(0, rb.max_message_length);\n    EXPECT_EQ(aeron_mpsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), 0), AERON_RB_SUCCESS);\n\n    auto *record = (aeron_rb_record_descriptor_t *)(m_buffer.data());\n\n    EXPECT_EQ(record->length, (int32_t)AERON_RB_RECORD_HEADER_LENGTH);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(AERON_RB_ALIGNMENT));\n}\n\nTEST_F(MpscRbTest, shouldErrorWhenMessageTypeIsNegative)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(aeron_mpsc_rb_write(&rb, -100, m_srcBuffer.data(), 5), AERON_RB_ERROR);\n}\n\nTEST_F(MpscRbTest, shouldErrorWhenMessageTypeIsZero)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(aeron_mpsc_rb_write(&rb, 0, m_srcBuffer.data(), rb.max_message_length), AERON_RB_ERROR);\n}\n\nTEST_F(MpscRbTest, shouldWriteToEmptyBuffer)\n{\n    aeron_mpsc_rb_t rb;\n    size_t tail = 0;\n    size_t tailIndex = 0;\n    size_t length = 8;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    ASSERT_EQ(aeron_mpsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_SUCCESS);\n\n    auto *record = (aeron_rb_record_descriptor_t *)(m_buffer.data() + tailIndex);\n\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(tail + alignedRecordLength));\n}\n\nTEST_F(MpscRbTest, shouldRejectWriteWhenInsufficientSpace)\n{\n    aeron_mpsc_rb_t rb;\n    size_t length = 100;\n    size_t head = 0;\n    size_t tail = head + (CAPACITY - AERON_ALIGN(length - AERON_RB_ALIGNMENT, AERON_RB_ALIGNMENT));\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    ASSERT_EQ(aeron_mpsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_FULL);\n\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)tail);\n}\n\nTEST_F(MpscRbTest, shouldRejectWriteWhenBufferFull)\n{\n    aeron_mpsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t tail = head + CAPACITY;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    ASSERT_EQ(aeron_mpsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_FULL);\n\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)tail);\n}\n\nTEST_F(MpscRbTest, shouldInsertPaddingRecordPlusMessageOnBufferWrap)\n{\n    aeron_mpsc_rb_t rb;\n    size_t length = 100;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = CAPACITY - AERON_RB_ALIGNMENT;\n    size_t head = tail - (AERON_RB_ALIGNMENT * 4);\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    ASSERT_EQ(aeron_mpsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_SUCCESS);\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer + tail);\n    EXPECT_EQ(record->msg_type_id, (int32_t)AERON_RB_PADDING_MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)AERON_RB_ALIGNMENT);\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(tail + alignedRecordLength + AERON_RB_ALIGNMENT));\n}\n\nTEST_F(MpscRbTest, shouldInsertPaddingRecordPlusMessageOnBufferWrapWithHeadEqualToTail)\n{\n    aeron_mpsc_rb_t rb;\n    size_t length = 100;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = CAPACITY - AERON_RB_ALIGNMENT;\n    size_t head = tail;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    ASSERT_EQ(aeron_mpsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_SUCCESS);\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer + tail);\n    EXPECT_EQ(record->msg_type_id, (int32_t)AERON_RB_PADDING_MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)AERON_RB_ALIGNMENT);\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(tail + alignedRecordLength + AERON_RB_ALIGNMENT));\n}\n\nstatic void countTimesAsSizeT(int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n{\n    auto *count = (size_t *)clientd;\n\n    (*count)++; /* unused */\n}\n\nTEST_F(MpscRbTest, shouldReadNothingFromEmptyBuffer)\n{\n    aeron_mpsc_rb_t rb;\n    size_t tail = 0;\n    size_t head = 0;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_mpsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 10);\n\n    EXPECT_EQ(messagesRead, (size_t)0);\n    EXPECT_EQ(timesCalled, (size_t)0);\n}\n\nTEST_F(MpscRbTest, shouldReadSingleMessage)\n{\n    aeron_mpsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = alignedRecordLength;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_mpsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 10);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)(head + alignedRecordLength));\n\n    for (size_t i = 0; i < AERON_RB_ALIGNMENT; i += 4)\n    {\n        EXPECT_EQ(*((int32_t *)(rb.buffer + i)), 0) << \"buffer has not been zeroed between \" << i << \"-\" << i + 3;\n    }\n}\n\nTEST_F(MpscRbTest, shouldNotReadSingleMessagePartWayThroughWriting)\n{\n    aeron_mpsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t endTail = alignedRecordLength;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)endTail;\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = -((int32_t)recordLength);\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_mpsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 10);\n\n    EXPECT_EQ(messagesRead, (size_t)0);\n    EXPECT_EQ(timesCalled, (size_t)0);\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)head);\n}\n\nTEST_F(MpscRbTest, shouldReadTwoMessages)\n{\n    aeron_mpsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = alignedRecordLength * 2;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    aeron_rb_record_descriptor_t *record;\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer + alignedRecordLength);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_mpsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 10);\n\n    EXPECT_EQ(messagesRead, (size_t)2);\n    EXPECT_EQ(timesCalled, (size_t)2);\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)(head + (alignedRecordLength * 2)));\n\n    for (size_t i = 0; i < AERON_RB_ALIGNMENT * 2; i += 4)\n    {\n        EXPECT_EQ(*((int32_t *)(rb.buffer + i)), 0) << \"buffer has not been zeroed between \" << i << \"-\" << i + 3;\n    }\n}\n\nTEST_F(MpscRbTest, shouldLimitReadOfMessages)\n{\n    aeron_mpsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = alignedRecordLength * 2;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    aeron_rb_record_descriptor_t *record;\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer + alignedRecordLength);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_mpsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)(head + alignedRecordLength));\n\n    for (size_t i = 0; i < AERON_RB_ALIGNMENT; i += 4)\n    {\n        EXPECT_EQ(*((int32_t *)(rb.buffer + i)), 0) << \"buffer has not been zeroed between \" << i << \"-\" << i + 3;\n    }\n}\n\nTEST_F(MpscRbTest, shouldNotUnblockWhenEmpty)\n{\n    aeron_mpsc_rb_t rb;\n    size_t tail = AERON_RB_ALIGNMENT * 4;\n    size_t head = tail;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    EXPECT_FALSE(aeron_mpsc_rb_unblock(&rb));\n}\n\nTEST_F(MpscRbTest, shouldUnblockMessageWithHeader)\n{\n    aeron_mpsc_rb_t rb;\n    size_t message_length = AERON_RB_ALIGNMENT * 4;\n    size_t tail = message_length * 2;\n    size_t head = message_length;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer + head);\n\n    record->msg_type_id = MSG_TYPE_ID;\n    record->length = -(int32_t)message_length;\n\n    EXPECT_TRUE(aeron_mpsc_rb_unblock(&rb));\n\n    EXPECT_EQ(record->msg_type_id, AERON_RB_PADDING_MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)message_length);\n\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)message_length);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(message_length * 2));\n}\n\nTEST_F(MpscRbTest, shouldUnblockGapWithZeros)\n{\n    aeron_mpsc_rb_t rb;\n    size_t message_length = AERON_RB_ALIGNMENT * 4;\n    size_t tail = message_length * 3;\n    size_t head = message_length;\n\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer + (message_length * 2));\n\n    record->length = (int32_t)message_length;\n\n    EXPECT_TRUE(aeron_mpsc_rb_unblock(&rb));\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer + head);\n\n    EXPECT_EQ(record->msg_type_id, AERON_RB_PADDING_MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)message_length);\n\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)message_length);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(message_length * 3));\n}\n\nTEST_F(MpscRbTest, tryClaimShouldErrorWhenMessageTypeIsZero)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(AERON_RB_ERROR, aeron_mpsc_rb_try_claim(&rb, 0, 5));\n}\n\nTEST_F(MpscRbTest, tryClaimShouldErrorWhenMessageTypeIsNegative)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(AERON_RB_ERROR, aeron_mpsc_rb_try_claim(&rb, -3, 5));\n}\n\nTEST_F(MpscRbTest, tryClaimShouldErrorWhenLengthExceedMaxMessageSize)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(AERON_RB_ERROR, aeron_mpsc_rb_try_claim(&rb, 6, rb.max_message_length + 1));\n}\n\nTEST_F(MpscRbTest, tryClaimShouldErrorBufferIsFull)\n{\n    aeron_mpsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t tail = head + CAPACITY;\n\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    EXPECT_EQ(AERON_RB_FULL, aeron_mpsc_rb_try_claim(&rb, MSG_TYPE_ID, length));\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)tail);\n}\n\nTEST_F(MpscRbTest, tryClaimShouldReturnMessageOffsetUponSuccess)\n{\n    int msg_type_id = 17;\n    size_t length = 100;\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ((int32_t)AERON_RB_MESSAGE_OFFSET(0), aeron_mpsc_rb_try_claim(&rb, msg_type_id, length));\n    auto *record_header = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    EXPECT_EQ(msg_type_id, record_header->msg_type_id);\n    EXPECT_EQ(-(int32_t)(length + AERON_RB_RECORD_HEADER_LENGTH), record_header->length);\n}\n\nTEST_F(MpscRbTest, commitShouldReturnErrorIfOffsetIsNegative)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_mpsc_rb_commit(&rb, -2));\n}\n\nTEST_F(MpscRbTest, commitShouldReturnErrorIfOffsetIsExceedsBufferCapacity)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_mpsc_rb_commit(&rb, (int32_t)(m_buffer.size() + 1)));\n}\n\nTEST_F(MpscRbTest, commitShouldReturnErrorIfOffsetIsSmallerThanRecordHeader)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_mpsc_rb_commit(&rb, (int32_t)(m_buffer.size() - AERON_RB_RECORD_HEADER_LENGTH + 1)));\n}\n\nTEST_F(MpscRbTest, commitShouldReturnZeroUponSuccess)\n{\n    size_t tail = 200;\n    size_t length = 50;\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    int32_t offset = aeron_mpsc_rb_try_claim(&rb, MSG_TYPE_ID, length);\n    EXPECT_EQ((int32_t)AERON_RB_MESSAGE_OFFSET(tail), offset);\n\n    EXPECT_EQ(0, aeron_mpsc_rb_commit(&rb, offset));\n    auto *record_header = (aeron_rb_record_descriptor_t *)(rb.buffer + (offset - AERON_RB_RECORD_HEADER_LENGTH));\n    EXPECT_EQ(MSG_TYPE_ID, record_header->msg_type_id);\n    EXPECT_EQ((int32_t)(length + AERON_RB_RECORD_HEADER_LENGTH), record_header->length);\n}\n\nTEST_F(MpscRbTest, abortShouldReturnErrorIfOffsetIsNegative)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_mpsc_rb_abort(&rb, -10));\n}\n\nTEST_F(MpscRbTest, abortShouldReturnErrorIfOffsetIsExceedsBufferCapacity)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_mpsc_rb_abort(&rb, (int32_t)(m_buffer.size() + 8)));\n}\n\nTEST_F(MpscRbTest, abortShouldReturnErrorIfOffsetIsSmallerThanRecordHeader)\n{\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_mpsc_rb_abort(&rb, (int32_t)(m_buffer.size() - 1)));\n}\n\nTEST_F(MpscRbTest, abortShouldReturnZeroUponSuccess)\n{\n    size_t length = 32;\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(0, aeron_mpsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    int32_t offset = aeron_mpsc_rb_try_claim(&rb, MSG_TYPE_ID, length);\n    EXPECT_EQ((int32_t)AERON_RB_MESSAGE_OFFSET(0), offset);\n\n    EXPECT_EQ(0, aeron_mpsc_rb_abort(&rb, offset));\n    auto *record_header = (aeron_rb_record_descriptor_t *)(rb.buffer + (offset - AERON_RB_RECORD_HEADER_LENGTH));\n    EXPECT_EQ(AERON_RB_PADDING_MSG_TYPE_ID, record_header->msg_type_id);\n    EXPECT_EQ((int32_t)(length + AERON_RB_RECORD_HEADER_LENGTH), record_header->length);\n}\n\nstruct aeron_mpsc_rb_control_test_clientd_t\n{\n    int64_t value;\n    aeron_rb_read_action_t action_for_value;\n    int result_index;\n    int64_t results[10];\n};\n\naeron_rb_read_action_t controlled_read_with_action(int32_t msg_type_id, const void *data, size_t length, void *clientd)\n{\n    auto *test_clientd = static_cast<aeron_mpsc_rb_control_test_clientd_t *>(clientd);\n    int64_t value = *(int64_t*)data;\n    aeron_rb_read_action_stct action_for_value = value == test_clientd->value ? test_clientd->action_for_value :\n        AERON_RB_CONTINUE;\n\n    test_clientd->results[test_clientd->result_index] = value;\n    test_clientd->result_index++;\n\n    return action_for_value;\n}\n\nTEST_F(MpscRbTest, shouldAbortControlledRead)\n{\n    aeron_mpsc_rb_control_test_clientd_t clientd{ 3, AERON_RB_ABORT, 0, {} };\n\n    AERON_DECL_ALIGNED(buffer_t mpsc_buffer, 16) = {};\n    mpsc_buffer.fill(0);\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, mpsc_buffer.data(), mpsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    for (int i = 1; i <= 5; i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_mpsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    EXPECT_EQ(2, aeron_mpsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(3, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(0, clientd.results[3]);\n\n    clientd.action_for_value = AERON_RB_CONTINUE;\n\n    EXPECT_EQ(3, aeron_mpsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(6, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(3, clientd.results[3]);\n    EXPECT_EQ(4, clientd.results[4]);\n    EXPECT_EQ(5, clientd.results[5]);\n    EXPECT_EQ(0, clientd.results[6]);\n}\n\nTEST_F(MpscRbTest, shouldBreakControlledRead)\n{\n    aeron_mpsc_rb_control_test_clientd_t clientd{ 3, AERON_RB_BREAK, 0, {} };\n\n    AERON_DECL_ALIGNED(buffer_t mpsc_buffer, 16) = {};\n    mpsc_buffer.fill(0);\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, mpsc_buffer.data(), mpsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    for (int i = 1; i <= 5; i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_mpsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    EXPECT_EQ(3, aeron_mpsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(3, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(0, clientd.results[3]);\n\n    clientd.action_for_value = AERON_RB_CONTINUE;\n\n    EXPECT_EQ(2, aeron_mpsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(5, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(4, clientd.results[3]);\n    EXPECT_EQ(5, clientd.results[4]);\n    EXPECT_EQ(0, clientd.results[5]);\n}\n\nTEST_F(MpscRbTest, shouldContinueControlledRead)\n{\n    aeron_mpsc_rb_control_test_clientd_t clientd{ 3, AERON_RB_CONTINUE, 0, {} };\n\n    AERON_DECL_ALIGNED(buffer_t mpsc_buffer, 16) = {};\n    mpsc_buffer.fill(0);\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, mpsc_buffer.data(), mpsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    for (int i = 1; i <= 5; i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_mpsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    EXPECT_EQ(5, aeron_mpsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(5, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(4, clientd.results[3]);\n    EXPECT_EQ(5, clientd.results[4]);\n    EXPECT_EQ(0, clientd.results[5]);\n}\n\naeron_rb_read_action_t controlled_read_with_commit(int32_t msg_type_id, const void *data, size_t length, void *clientd)\n{\n    auto *rb = static_cast<aeron_mpsc_rb_t *>(clientd);\n    int64_t value = *(int64_t*)data;\n\n    aeron_rb_read_action_stct action_for_value = value == 3 ? AERON_RB_COMMIT :\n        AERON_RB_CONTINUE;\n\n    if (value <= 3)\n    {\n        EXPECT_EQ(0, aeron_mpsc_rb_consumer_position(rb));\n    }\n    else\n    {\n        EXPECT_NE(0, aeron_mpsc_rb_consumer_position(rb));\n    }\n\n    return action_for_value;\n}\n\nTEST_F(MpscRbTest, shouldCommitControlledRead)\n{\n    AERON_DECL_ALIGNED(buffer_t mpsc_buffer, 16) = {};\n    mpsc_buffer.fill(0);\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, mpsc_buffer.data(), mpsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    for (int i = 1; i <= 5; i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_mpsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    EXPECT_EQ(5, aeron_mpsc_rb_controlled_read(&rb, controlled_read_with_commit, &rb, 5));\n}\n\nTEST_F(MpscRbTest, shouldGetSize)\n{\n    AERON_DECL_ALIGNED(buffer_t mpsc_buffer, 16) = {};\n    mpsc_buffer.fill(0);\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, mpsc_buffer.data(), mpsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    size_t total_messages = CAPACITY / (AERON_RB_RECORD_HEADER_LENGTH + sizeof(data));\n    ASSERT_EQ(0, aeron_mpsc_rb_size(&rb));\n\n    for (size_t i = 0; i < (total_messages / 2); i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_mpsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    ASSERT_EQ(CAPACITY / 2, aeron_mpsc_rb_size(&rb));\n\n    aeron_rb_write_result_t result;\n    do\n    {\n        result = aeron_mpsc_rb_write(&rb, 1, &data, sizeof(data));\n    }\n    while (AERON_RB_SUCCESS == result);\n\n    ASSERT_EQ(CAPACITY, aeron_mpsc_rb_size(&rb));\n}\n\n#define NUM_MESSAGES_PER_PUBLISHER (10 * 1000 * 1000)\n#define NUM_IDS_PER_THREAD (10 * 1000 * 1000)\n#define NUM_PUBLISHERS (2)\n\nTEST(MpscRbConcurrentTest, shouldProvideCorrelationIds)\n{\n    AERON_DECL_ALIGNED(buffer_t buffer, 16) = {};\n    buffer.fill(0);\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, buffer.data(), buffer.size()), 0);\n\n    std::atomic<int> countDown(2);\n\n    std::vector<std::thread> threads;\n\n    for (int i = 0; i < NUM_PUBLISHERS; i++)\n    {\n        threads.push_back(std::thread(\n            [&]()\n            {\n                countDown--;\n                while (countDown > 0)\n                {\n                    std::this_thread::yield();\n                }\n\n                for (int m = 0; m < NUM_IDS_PER_THREAD; m++)\n                {\n                    aeron_mpsc_rb_next_correlation_id(&rb);\n                }\n            }));\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n\n    ASSERT_EQ(aeron_mpsc_rb_next_correlation_id(&rb), NUM_IDS_PER_THREAD * 2);\n}\n\ntypedef struct mpsc_concurrent_test_data_stct\n{\n    uint32_t id;\n    uint32_t num;\n}\nmpsc_concurrent_test_data_t;\n\nstatic void mpsc_rb_concurrent_handler(int32_t msg_type_id, const void *buffer, size_t length, void *clientd)\n{\n    auto *counts = (uint32_t *)clientd;\n    auto *data = (mpsc_concurrent_test_data_t *)buffer;\n\n    EXPECT_EQ(length, sizeof(mpsc_concurrent_test_data_t));\n    ASSERT_EQ(msg_type_id, MSG_TYPE_ID);\n\n    ASSERT_EQ(counts[data->id], data->num);\n    counts[data->id]++;\n}\n\nTEST(MpscRbConcurrentTest, shouldExchangeMessages)\n{\n    AERON_DECL_ALIGNED(buffer_t mpsc_buffer, 16) = {};\n    mpsc_buffer.fill(0);\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, mpsc_buffer.data(), mpsc_buffer.size()), 0);\n\n    std::atomic<int> countDown(NUM_PUBLISHERS);\n    std::atomic<unsigned int> publisherId(0);\n\n    std::vector<std::thread> threads;\n    size_t msgCount = 0;\n    uint32_t counts[NUM_PUBLISHERS];\n\n    for (unsigned int &count : counts)\n    {\n        count = 0;\n    }\n\n    for (int i = 0; i < NUM_PUBLISHERS; i++)\n    {\n        threads.push_back(std::thread(\n            [&]()\n            {\n                AERON_DECL_ALIGNED(buffer_t buffer, 16);\n                buffer.fill(0);\n                uint32_t id = publisherId.fetch_add(1);\n\n                countDown--;\n                while (countDown > 0)\n                {\n                    std::this_thread::yield();\n                }\n\n                auto *data = (mpsc_concurrent_test_data_t *)(buffer.data());\n\n                for (uint32_t m = 0; m < NUM_MESSAGES_PER_PUBLISHER; m++)\n                {\n                    data->id = id;\n                    data->num = m;\n\n                    while (AERON_RB_SUCCESS != aeron_mpsc_rb_write(\n                        &rb, MSG_TYPE_ID, buffer.data(), sizeof(mpsc_concurrent_test_data_t)))\n                    {\n                        std::this_thread::yield();\n                    }\n                }\n            }));\n    }\n\n    while (msgCount < (NUM_MESSAGES_PER_PUBLISHER * NUM_PUBLISHERS))\n    {\n        const size_t readCount = aeron_mpsc_rb_read(\n            &rb, mpsc_rb_concurrent_handler, counts, std::numeric_limits<size_t>::max());\n\n        if (0 == readCount)\n        {\n            std::this_thread::yield();\n        }\n\n        msgCount += readCount;\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n}\n\nTEST(MpscRbConcurrentTest, shouldExchangeMessagesViaTryClaim)\n{\n    AERON_DECL_ALIGNED(buffer_t mpsc_buffer, 16) = {};\n    mpsc_buffer.fill(0);\n\n    aeron_mpsc_rb_t rb;\n    ASSERT_EQ(aeron_mpsc_rb_init(&rb, mpsc_buffer.data(), mpsc_buffer.size()), 0);\n\n    std::atomic<int> countDown(NUM_PUBLISHERS);\n    std::atomic<unsigned int> publisherId(0);\n\n    std::vector<std::thread> threads;\n    size_t msgCount = 0;\n    uint32_t counts[NUM_PUBLISHERS];\n\n    for (unsigned int &count : counts)\n    {\n        count = 0;\n    }\n\n    for (int i = 0; i < NUM_PUBLISHERS; i++)\n    {\n        threads.push_back(std::thread(\n            [&]()\n            {\n                uint32_t id = publisherId.fetch_add(1);\n\n                countDown--;\n                while (countDown > 0)\n                {\n                    std::this_thread::yield();\n                }\n\n\n                for (uint32_t m = 0; m < NUM_MESSAGES_PER_PUBLISHER; m++)\n                {\n                    size_t length = sizeof(mpsc_concurrent_test_data_t);\n                    int32_t offset;\n                    while ((offset = aeron_mpsc_rb_try_claim(&rb, MSG_TYPE_ID, length)) < 0)\n                    {\n                        std::this_thread::yield();\n                    }\n\n                    auto *data = (mpsc_concurrent_test_data_t *)(rb.buffer + offset);\n                    data->id = id;\n                    data->num = m;\n\n                    aeron_mpsc_rb_commit(&rb, offset);\n                }\n            }));\n    }\n\n    while (msgCount < (NUM_MESSAGES_PER_PUBLISHER * NUM_PUBLISHERS))\n    {\n        const size_t readCount = aeron_mpsc_rb_read(\n            &rb, mpsc_rb_concurrent_handler, counts, std::numeric_limits<size_t>::max());\n\n        if (0 == readCount)\n        {\n            std::this_thread::yield();\n        }\n\n        msgCount += readCount;\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_spsc_concurrent_array_queue_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n#include <thread>\n#include <atomic>\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_spsc_concurrent_array_queue.h\"\n}\n\n#define CAPACITY (8u)\n\nclass SpscQueueTest : public testing::Test\n{\npublic:\n    SpscQueueTest()\n    {\n        if (aeron_spsc_concurrent_array_queue_init(&m_q, CAPACITY) < 0)\n        {\n            throw std::runtime_error(\"could not init q\");\n        }\n    }\n\n    ~SpscQueueTest() override\n    {\n        aeron_spsc_concurrent_array_queue_close(&m_q);\n    }\n\n    static void drain_func(void *clientd, void *element)\n    {\n        auto *t = (SpscQueueTest *)clientd;\n\n        (*t).m_drain(element);\n    }\n\n    void fillQueue()\n    {\n        for (size_t i = 1; i <= CAPACITY; i++)\n        {\n            ASSERT_EQ(aeron_spsc_concurrent_array_queue_offer(&m_q, (void *)i), AERON_OFFER_SUCCESS);\n        }\n    }\n\nprotected:\n    aeron_spsc_concurrent_array_queue_t m_q = {};\n    std::function<void(volatile void *)> m_drain;\n};\n\nTEST_F(SpscQueueTest, shouldGetSizeWhenEmpty)\n{\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_size(&m_q), 0u);\n}\n\nTEST_F(SpscQueueTest, shouldReturnErrorWhenNullOffered)\n{\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_offer(&m_q, nullptr), AERON_OFFER_ERROR);\n}\n\nTEST_F(SpscQueueTest, shouldOfferAndDrainToEmptyQueue)\n{\n    int64_t element = 64;\n\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_offer(&m_q, (void *)element), AERON_OFFER_SUCCESS);\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_size(&m_q), 1u);\n\n    m_drain =\n        [&](volatile void *e)\n        {\n            ASSERT_EQ(e, (void *)element);\n        };\n\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_drain(&m_q, SpscQueueTest::drain_func, this, UINT64_MAX), 1u);\n}\n\nTEST_F(SpscQueueTest, shouldFailToOfferToFullQueue)\n{\n    int64_t element = CAPACITY + 1;\n\n    fillQueue();\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_size(&m_q), CAPACITY);\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_offer(&m_q, (void *)element), AERON_OFFER_FULL);\n}\n\nTEST_F(SpscQueueTest, shouldDrainSingleElementFromFullQueue)\n{\n    fillQueue();\n\n    m_drain =\n        [&](volatile void *e)\n        {\n            ASSERT_EQ(e, (void *)1);\n        };\n\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_drain(&m_q, SpscQueueTest::drain_func, this, 1), 1u);\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_size(&m_q), CAPACITY - 1);\n}\n\nTEST_F(SpscQueueTest, shouldDrainNothingFromEmptyQueue)\n{\n    m_drain =\n        [&](volatile void *e)\n        {\n            FAIL();\n        };\n\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_drain(&m_q, SpscQueueTest::drain_func, this, UINT64_MAX), 0u);\n}\n\nTEST_F(SpscQueueTest, shouldDrainFullQueue)\n{\n    fillQueue();\n\n    int64_t counter = 1;\n    m_drain =\n        [&](volatile void *e)\n        {\n            ASSERT_EQ(e, (void *)counter);\n            counter++;\n        };\n\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_drain(&m_q, SpscQueueTest::drain_func, this, UINT64_MAX), CAPACITY);\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_size(&m_q), 0u);\n}\n\nTEST_F(SpscQueueTest, shouldDrainingFullQueueWithLimit)\n{\n    size_t limit = CAPACITY / 2;\n    fillQueue();\n\n    int64_t counter = 1;\n    m_drain =\n        [&](volatile void *e)\n        {\n            ASSERT_EQ(e, (void *)counter);\n            counter++;\n        };\n\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_drain(&m_q, SpscQueueTest::drain_func, this, limit), limit);\n    EXPECT_EQ(aeron_spsc_concurrent_array_queue_size(&m_q), CAPACITY - limit);\n}\n\n#define NUM_MESSAGES (10 * 1000 * 1000)\n\nstatic void spsc_queue_concurrent_handler(void *clientd, void *element)\n{\n    auto *counts = (size_t *)clientd;\n    auto messageNumber = (uint64_t)element;\n\n    EXPECT_EQ(++(*counts), (size_t)messageNumber);\n}\n\nTEST(SpscQueueConcurrentTest, shouldExchangeMessages)\n{\n    aeron_spsc_concurrent_array_queue_t q;\n    ASSERT_EQ(aeron_spsc_concurrent_array_queue_init(&q, CAPACITY), 0);\n\n    std::atomic<int> countDown(1);\n\n    std::vector<std::thread> threads;\n    size_t msgCount = 0;\n    size_t counts = 0;\n\n    threads.push_back(std::thread(\n        [&]()\n        {\n            countDown--;\n            while (countDown > 0)\n            {\n                std::this_thread::yield();\n            }\n\n            for (uint64_t m = 1; m <= NUM_MESSAGES; m++)\n            {\n                while (AERON_OFFER_SUCCESS != aeron_spsc_concurrent_array_queue_offer(&q, (void *)m))\n                {\n                    std::this_thread::yield();\n                }\n            }\n        }));\n\n    while (msgCount < NUM_MESSAGES)\n    {\n        const size_t drainCount = aeron_spsc_concurrent_array_queue_drain_all(\n            &q, spsc_queue_concurrent_handler, &counts);\n\n        if (0 == drainCount)\n        {\n            std::this_thread::yield();\n        }\n\n        msgCount += drainCount;\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n\n    aeron_spsc_concurrent_array_queue_close(&q);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_spsc_rb_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <thread>\n#include <atomic>\n#include <limits>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_spsc_rb.h\"\n#include \"util/aeron_error.h\"\n}\n#undef max\n\n#define CAPACITY (1024)\n#define BUFFER_SZ (CAPACITY + AERON_RB_TRAILER_LENGTH)\n#define ODD_BUFFER_SZ ((CAPACITY - 1) + AERON_RB_TRAILER_LENGTH)\n#define MSG_TYPE_ID (101)\n\ntypedef std::array<std::uint8_t, BUFFER_SZ> buffer_t;\ntypedef std::array<std::uint8_t, ODD_BUFFER_SZ> odd_sized_buffer_t;\n\nclass SpscRbTest : public testing::Test\n{\npublic:\n\n    SpscRbTest()\n    {\n        m_buffer.fill(0);\n        m_srcBuffer.fill(0);\n    }\n\nprotected:\n    buffer_t m_buffer = {};\n    buffer_t m_srcBuffer = {};\n};\n\nTEST_F(SpscRbTest, shouldCalculateCapacityForBuffer)\n{\n    aeron_spsc_rb_t rb;\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    EXPECT_EQ(rb.capacity, BUFFER_SZ - AERON_RB_TRAILER_LENGTH);\n}\n\nTEST_F(SpscRbTest, shouldErrorForCapacityNotPowerOfTwo)\n{\n    aeron_spsc_rb_t rb;\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size() - 1), -1);\n}\n\nTEST_F(SpscRbTest, shouldErrorForCapacityLessThanTheMinCapacity)\n{\n    aeron_spsc_rb_t rb;\n    const size_t capacity = (AERON_SPSC_RB_MIN_CAPACITY / 2);\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), AERON_RB_TRAILER_LENGTH + capacity), -1);\n    ASSERT_EQ(aeron_errcode(), EINVAL);\n    const std::string expected_err_msg = \"Invalid capacity: \" + std::to_string(capacity);\n    const std::string actual_err_msg = std::string(aeron_errmsg());\n    ASSERT_NE(actual_err_msg.find(expected_err_msg), std::string::npos);\n}\n\nTEST_F(SpscRbTest, shouldErrorWhenMaxMessageSizeExceeded)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), rb.max_message_length + 1), AERON_RB_ERROR);\n}\n\nTEST_F(SpscRbTest, shouldErrorWhenMinCapacityIsUsedAndMessageSizeIsNotZero)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), AERON_RB_TRAILER_LENGTH + AERON_SPSC_RB_MIN_CAPACITY), 0);\n\n    EXPECT_EQ(rb.max_message_length, 0);\n    EXPECT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), 1), AERON_RB_ERROR);\n}\n\nTEST_F(SpscRbTest, shouldWriteAnEmptyMessageWhenMinCapacityIsUsed)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), AERON_RB_TRAILER_LENGTH + AERON_SPSC_RB_MIN_CAPACITY), 0);\n\n    EXPECT_EQ(0, rb.max_message_length);\n    EXPECT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), 0), AERON_RB_SUCCESS);\n\n    auto *record = (aeron_rb_record_descriptor_t *)(m_buffer.data());\n\n    EXPECT_EQ(record->length, (int32_t)AERON_RB_RECORD_HEADER_LENGTH);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(AERON_RB_ALIGNMENT));\n}\n\nTEST_F(SpscRbTest, shouldErrorWhenMessageTypeIsNegative)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(aeron_spsc_rb_write(&rb, AERON_RB_PADDING_MSG_TYPE_ID, m_srcBuffer.data(), 5), AERON_RB_ERROR);\n}\n\nTEST_F(SpscRbTest, shouldErrorWhenMessageTypeIsZero)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    EXPECT_EQ(aeron_spsc_rb_write(&rb, 0, m_srcBuffer.data(), 5), AERON_RB_ERROR);\n}\n\nTEST_F(SpscRbTest, shouldWriteToEmptyBuffer)\n{\n    aeron_spsc_rb_t rb;\n    size_t tail = 0;\n    size_t tailIndex = 0;\n    size_t length = 8;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    ASSERT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_SUCCESS);\n\n    auto *record = (aeron_rb_record_descriptor_t *)(m_buffer.data() + tailIndex);\n\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(tail + alignedRecordLength));\n}\n\nTEST_F(SpscRbTest, shouldWriteVectorToEmptyBuffer)\n{\n    aeron_spsc_rb_t rb;\n    size_t tail = 0;\n    size_t tailIndex = 0;\n\n    const int vec_len = 3;\n    struct iovec vec[vec_len];\n    vec[0].iov_base = m_srcBuffer.data();\n    vec[0].iov_len = 8;\n    vec[1].iov_base = m_srcBuffer.data() + (vec[0].iov_len);\n    vec[1].iov_len = 7;\n    vec[2].iov_base = m_srcBuffer.data() + (vec[0].iov_len + vec[1].iov_len);\n    vec[2].iov_len = 11;\n    size_t length = vec[0].iov_len + vec[1].iov_len + vec[2].iov_len;\n\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    ASSERT_EQ(aeron_spsc_rb_writev(&rb, MSG_TYPE_ID, vec, vec_len), AERON_RB_SUCCESS);\n\n    auto *record = (aeron_rb_record_descriptor_t *)(m_buffer.data() + tailIndex);\n\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(tail + alignedRecordLength));\n}\n\nTEST_F(SpscRbTest, shouldRejectWriteWhenInsufficientSpace)\n{\n    aeron_spsc_rb_t rb;\n    size_t length = 100;\n    size_t head = 0;\n    size_t tail = head + (CAPACITY - AERON_ALIGN(length - AERON_RB_ALIGNMENT, AERON_RB_ALIGNMENT));\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    ASSERT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_FULL);\n\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)tail);\n}\n\nTEST_F(SpscRbTest, shouldRejectWriteVectorWhenInsufficientSpace)\n{\n    aeron_spsc_rb_t rb;\n\n    const int vec_len = 3;\n    struct iovec vec[vec_len];\n    vec[0].iov_base = m_srcBuffer.data();\n    vec[0].iov_len = 1;\n    vec[1].iov_base = m_srcBuffer.data() + (vec[0].iov_len);\n    vec[1].iov_len = 1;\n    vec[2].iov_base = m_srcBuffer.data() + (vec[0].iov_len + vec[1].iov_len);\n    vec[2].iov_len = 98;\n    size_t length = vec[0].iov_len + vec[1].iov_len + vec[2].iov_len;\n\n    size_t head = 0;\n    size_t tail = head + (CAPACITY - AERON_ALIGN(length - AERON_RB_ALIGNMENT, AERON_RB_ALIGNMENT));\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    ASSERT_EQ(aeron_spsc_rb_writev(&rb, MSG_TYPE_ID, vec, vec_len), AERON_RB_FULL);\n\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)tail);\n}\n\nTEST_F(SpscRbTest, shouldRejectWriteWhenBufferFull)\n{\n    aeron_spsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t tail = head + CAPACITY;\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    ASSERT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_FULL);\n\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)tail);\n}\n\nTEST_F(SpscRbTest, shouldInsertPaddingRecordPlusMessageOnBufferWrap)\n{\n    aeron_spsc_rb_t rb;\n    size_t length = 100;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = CAPACITY - AERON_RB_ALIGNMENT;\n    size_t head = tail - (AERON_RB_ALIGNMENT * 4);\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    ASSERT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_SUCCESS);\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer + tail);\n    EXPECT_EQ(record->msg_type_id, (int32_t)AERON_RB_PADDING_MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)AERON_RB_ALIGNMENT);\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(tail + alignedRecordLength + AERON_RB_ALIGNMENT));\n}\n\nTEST_F(SpscRbTest, shouldInsertPaddingRecordPlusMessageOnBufferWrapWithHeadEqualToTail)\n{\n    aeron_spsc_rb_t rb;\n    size_t length = 100;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = CAPACITY - AERON_RB_ALIGNMENT;\n    size_t head = tail;\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    ASSERT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), length), AERON_RB_SUCCESS);\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer + tail);\n    EXPECT_EQ(record->msg_type_id, (int32_t)AERON_RB_PADDING_MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)AERON_RB_ALIGNMENT);\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    EXPECT_EQ(record->msg_type_id, (int32_t)MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)recordLength);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(tail + alignedRecordLength + AERON_RB_ALIGNMENT));\n}\n\nstatic void countTimesAsSizeT(int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n{\n    auto *count = (size_t *)clientd;\n\n    (*count)++; /* unused */\n}\n\nTEST_F(SpscRbTest, shouldReadNothingFromEmptyBuffer)\n{\n    aeron_spsc_rb_t rb;\n    size_t tail = 0;\n    size_t head = 0;\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_spsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 10);\n\n    EXPECT_EQ(messagesRead, (size_t)0);\n    EXPECT_EQ(timesCalled, (size_t)0);\n}\n\nTEST_F(SpscRbTest, shouldReadSingleMessage)\n{\n    aeron_spsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = alignedRecordLength;\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_spsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 10);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)(head + alignedRecordLength));\n}\n\nTEST_F(SpscRbTest, shouldNotReadSingleMessagePartWayThroughWriting)\n{\n    aeron_spsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t endTail = alignedRecordLength;\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)endTail;\n\n    auto *record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = -((int32_t)recordLength);\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_spsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 10);\n\n    EXPECT_EQ(messagesRead, (size_t)0);\n    EXPECT_EQ(timesCalled, (size_t)0);\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)head);\n}\n\nTEST_F(SpscRbTest, shouldReadTwoMessages)\n{\n    aeron_spsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = alignedRecordLength * 2;\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    aeron_rb_record_descriptor_t *record;\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer + alignedRecordLength);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_spsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 10);\n\n    EXPECT_EQ(messagesRead, (size_t)2);\n    EXPECT_EQ(timesCalled, (size_t)2);\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)(head + (alignedRecordLength * 2)));\n}\n\nTEST_F(SpscRbTest, shouldLimitReadOfMessages)\n{\n    aeron_spsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t recordLength = length + AERON_RB_RECORD_HEADER_LENGTH;\n    size_t alignedRecordLength = AERON_ALIGN(recordLength, AERON_RB_ALIGNMENT);\n    size_t tail = alignedRecordLength * 2;\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    aeron_rb_record_descriptor_t *record;\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer + alignedRecordLength);\n    record->msg_type_id = (int32_t)MSG_TYPE_ID;\n    record->length = (int32_t)recordLength;\n\n    size_t timesCalled = 0;\n    const size_t messagesRead = aeron_spsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)(head + alignedRecordLength));\n}\n\nTEST_F(SpscRbTest, shouldPutMessageAtTheEndOfTheBufferWithoutPaddingAfterReadUnblocksZeroingOfTheNextHeader)\n{\n    aeron_spsc_rb_t rb;\n    const int32_t msgType = 555;\n    const size_t msgLength = (CAPACITY / 8) - AERON_RB_RECORD_HEADER_LENGTH;\n    const size_t alignedRecordLength = AERON_ALIGN(CAPACITY / 8, AERON_RB_ALIGNMENT);\n\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()), 0);\n\n    m_srcBuffer.fill(7);\n    for (int i = 0; i < 7; i++)\n    {\n        EXPECT_EQ(aeron_spsc_rb_write(&rb, msgType, m_srcBuffer.data(), msgLength), AERON_RB_SUCCESS);\n    }\n    m_srcBuffer.fill(5);\n    EXPECT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), msgLength), AERON_RB_FULL);\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_spsc_rb_read(&rb, countTimesAsSizeT, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n\n    EXPECT_EQ(aeron_spsc_rb_write(&rb, MSG_TYPE_ID, m_srcBuffer.data(), msgLength), AERON_RB_SUCCESS);\n\n    EXPECT_EQ(rb.descriptor->head_position, (int64_t)alignedRecordLength);\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)(CAPACITY));\n\n    aeron_rb_record_descriptor_t *record;\n\n    // assert that the next message header was zeroed correctly\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    EXPECT_EQ(record->msg_type_id, 0);\n    EXPECT_EQ(record->length, 0);\n\n    // second message\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer + alignedRecordLength);\n    EXPECT_EQ(record->msg_type_id, msgType);\n    EXPECT_EQ(record->length, (int32_t)(msgLength + AERON_RB_RECORD_HEADER_LENGTH));\n\n    // last message\n    record = (aeron_rb_record_descriptor_t *)(rb.buffer + (CAPACITY - alignedRecordLength));\n    EXPECT_EQ(record->msg_type_id, MSG_TYPE_ID);\n    EXPECT_EQ(record->length, (int32_t)(msgLength + AERON_RB_RECORD_HEADER_LENGTH));\n}\n\nTEST_F(SpscRbTest, tryClaimShouldErrorWhenMessageTypeIsZero)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(AERON_RB_ERROR, aeron_spsc_rb_try_claim(&rb, 0, 5));\n}\n\nTEST_F(SpscRbTest, tryClaimShouldErrorWhenMessageTypeIsNegative)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(AERON_RB_ERROR, aeron_spsc_rb_try_claim(&rb, -3, 5));\n}\n\nTEST_F(SpscRbTest, tryClaimShouldErrorWhenLengthExceedMaxMessageSize)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(AERON_RB_ERROR, aeron_spsc_rb_try_claim(&rb, 6, rb.max_message_length + 1));\n}\n\nTEST_F(SpscRbTest, tryClaimShouldErrorBufferIsFull)\n{\n    aeron_spsc_rb_t rb;\n    size_t length = 8;\n    size_t head = 0;\n    size_t tail = head + CAPACITY;\n\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n    rb.descriptor->head_position = (int64_t)head;\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    EXPECT_EQ(AERON_RB_FULL, aeron_spsc_rb_try_claim(&rb, MSG_TYPE_ID, length));\n    EXPECT_EQ(rb.descriptor->tail_position, (int64_t)tail);\n}\n\nTEST_F(SpscRbTest, tryClaimShouldReturnMessageOffsetUponSuccess)\n{\n    int msg_type_id = 17;\n    size_t length = 100;\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ((int32_t)AERON_RB_RECORD_HEADER_LENGTH, aeron_spsc_rb_try_claim(&rb, msg_type_id, length));\n    auto *record_header = (aeron_rb_record_descriptor_t *)(rb.buffer);\n    EXPECT_EQ(msg_type_id, record_header->msg_type_id);\n    EXPECT_EQ(-(int32_t)(length + AERON_RB_RECORD_HEADER_LENGTH), record_header->length);\n}\n\nTEST_F(SpscRbTest, commitShouldReturnErrorIfOffsetIsNegative)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_spsc_rb_commit(&rb, -2));\n}\n\nTEST_F(SpscRbTest, commitShouldReturnErrorIfOffsetIsExceedsBufferCapacity)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_spsc_rb_commit(&rb, (int32_t)(m_buffer.size() + 1)));\n}\n\nTEST_F(SpscRbTest, commitShouldReturnErrorIfOffsetIsSmallerThanRecordHeader)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_spsc_rb_commit(&rb, (int32_t)(m_buffer.size() - AERON_RB_RECORD_HEADER_LENGTH + 1)));\n}\n\nTEST_F(SpscRbTest, commitShouldReturnZeroUponSuccess)\n{\n    size_t tail = 200;\n    size_t length = 50;\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n    rb.descriptor->tail_position = (int64_t)tail;\n\n    int32_t offset = aeron_spsc_rb_try_claim(&rb, MSG_TYPE_ID, length);\n    EXPECT_EQ((int32_t)AERON_RB_MESSAGE_OFFSET(tail), offset);\n\n    EXPECT_EQ(0, aeron_spsc_rb_commit(&rb, offset));\n    auto *record_header = (aeron_rb_record_descriptor_t *)(rb.buffer + (offset - AERON_RB_RECORD_HEADER_LENGTH));\n    EXPECT_EQ(MSG_TYPE_ID, record_header->msg_type_id);\n    EXPECT_EQ((int32_t)(length + AERON_RB_RECORD_HEADER_LENGTH), record_header->length);\n}\n\nTEST_F(SpscRbTest, abortShouldReturnErrorIfOffsetIsNegative)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_spsc_rb_abort(&rb, -10));\n}\n\nTEST_F(SpscRbTest, abortShouldReturnErrorIfOffsetIsExceedsBufferCapacity)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_spsc_rb_abort(&rb, (int32_t)(m_buffer.size() + 8)));\n}\n\nTEST_F(SpscRbTest, abortShouldReturnErrorIfOffsetIsSmallerThanRecordHeader)\n{\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    EXPECT_EQ(-1, aeron_spsc_rb_abort(&rb, (int32_t)(m_buffer.size() - 1)));\n}\n\nTEST_F(SpscRbTest, abortShouldReturnZeroUponSuccess)\n{\n    size_t length = 32;\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(0, aeron_spsc_rb_init(&rb, m_buffer.data(), m_buffer.size()));\n\n    int32_t offset = aeron_spsc_rb_try_claim(&rb, MSG_TYPE_ID, length);\n    EXPECT_EQ((int32_t)AERON_RB_MESSAGE_OFFSET(0), offset);\n\n    EXPECT_EQ(0, aeron_spsc_rb_abort(&rb, offset));\n    auto *record_header = (aeron_rb_record_descriptor_t *)(rb.buffer + (offset - AERON_RB_RECORD_HEADER_LENGTH));\n    EXPECT_EQ(AERON_RB_PADDING_MSG_TYPE_ID, record_header->msg_type_id);\n    EXPECT_EQ((int32_t)(length + AERON_RB_RECORD_HEADER_LENGTH), record_header->length);\n}\n\n\nstruct aeron_spsc_rb_control_test_clientd_t\n{\n    int64_t value;\n    aeron_rb_read_action_t action_for_value;\n    int result_index;\n    int64_t results[10];\n};\n\naeron_rb_read_action_t controlled_read_with_action(int32_t msg_type_id, const void *data, size_t length, void *clientd)\n{\n     auto *test_clientd = static_cast<aeron_spsc_rb_control_test_clientd_t *>(clientd);\n    int64_t value = *(int64_t*)data;\n    aeron_rb_read_action_stct action_for_value = value == test_clientd->value ?\n        test_clientd->action_for_value : AERON_RB_CONTINUE;\n\n    test_clientd->results[test_clientd->result_index] = value;\n    test_clientd->result_index++;\n\n    return action_for_value;\n}\n\nTEST_F(SpscRbTest, shouldAbortControlledRead)\n{\n    aeron_spsc_rb_control_test_clientd_t clientd{ 3, AERON_RB_ABORT, 0, {} };\n\n    AERON_DECL_ALIGNED(buffer_t spsc_buffer, 16) = {};\n    spsc_buffer.fill(0);\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, spsc_buffer.data(), spsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    for (int i = 1; i <= 5; i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_spsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    EXPECT_EQ(2, aeron_spsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(3, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(0, clientd.results[3]);\n\n    clientd.action_for_value = AERON_RB_CONTINUE;\n\n    EXPECT_EQ(3, aeron_spsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(6, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(3, clientd.results[3]);\n    EXPECT_EQ(4, clientd.results[4]);\n    EXPECT_EQ(5, clientd.results[5]);\n    EXPECT_EQ(0, clientd.results[6]);\n}\n\nTEST_F(SpscRbTest, shouldBreakControlledRead)\n{\n    aeron_spsc_rb_control_test_clientd_t clientd{ 3, AERON_RB_BREAK, 0, {} };\n\n    AERON_DECL_ALIGNED(buffer_t spsc_buffer, 16) = {};\n    spsc_buffer.fill(0);\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, spsc_buffer.data(), spsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    for (int i = 1; i <= 5; i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_spsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    EXPECT_EQ(3, aeron_spsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(3, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(0, clientd.results[3]);\n\n    clientd.action_for_value = AERON_RB_CONTINUE;\n\n    EXPECT_EQ(2, aeron_spsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(5, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(4, clientd.results[3]);\n    EXPECT_EQ(5, clientd.results[4]);\n    EXPECT_EQ(0, clientd.results[5]);\n}\n\nTEST_F(SpscRbTest, shouldContinueControlledRead)\n{\n    aeron_spsc_rb_control_test_clientd_t clientd{ 3, AERON_RB_CONTINUE, 0, {} };\n\n    AERON_DECL_ALIGNED(buffer_t spsc_buffer, 16) = {};\n    spsc_buffer.fill(0);\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, spsc_buffer.data(), spsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    for (int i = 1; i <= 5; i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_spsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    EXPECT_EQ(5, aeron_spsc_rb_controlled_read(&rb, controlled_read_with_action, &clientd, 5));\n    EXPECT_EQ(5, clientd.result_index);\n    EXPECT_EQ(1, clientd.results[0]);\n    EXPECT_EQ(2, clientd.results[1]);\n    EXPECT_EQ(3, clientd.results[2]);\n    EXPECT_EQ(4, clientd.results[3]);\n    EXPECT_EQ(5, clientd.results[4]);\n    EXPECT_EQ(0, clientd.results[5]);\n}\n\naeron_rb_read_action_t controlled_read_with_commit(int32_t msg_type_id, const void *data, size_t length, void *clientd)\n{\n    auto *rb = static_cast<aeron_spsc_rb_t *>(clientd);\n    int64_t value = *(int64_t*)data;\n\n    aeron_rb_read_action_stct action_for_value = value == 3 ? AERON_RB_COMMIT : AERON_RB_CONTINUE;\n\n    if (value <= 3)\n    {\n        EXPECT_EQ(0, aeron_spsc_rb_consumer_position(rb));\n    }\n    else\n    {\n        EXPECT_NE(0, aeron_spsc_rb_consumer_position(rb));\n    }\n\n    return action_for_value;\n}\n\nTEST_F(SpscRbTest, shouldCommitControlledRead)\n{\n    AERON_DECL_ALIGNED(buffer_t spsc_buffer, 16) = {};\n    spsc_buffer.fill(0);\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, spsc_buffer.data(), spsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    for (int i = 1; i <= 5; i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_spsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    EXPECT_EQ(5, aeron_spsc_rb_controlled_read(&rb, controlled_read_with_commit, &rb, 5));\n}\n\nTEST_F(SpscRbTest, shouldGetSize)\n{\n    const int spsc_padding = 16;\n    AERON_DECL_ALIGNED(buffer_t spsc_buffer, 16) = {};\n    spsc_buffer.fill(0);\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, spsc_buffer.data(), spsc_buffer.size()), 0);\n\n    int64_t data = 1;\n    size_t total_messages = CAPACITY / (AERON_RB_RECORD_HEADER_LENGTH + sizeof(data));\n    ASSERT_EQ(0, aeron_spsc_rb_size(&rb));\n\n    for (size_t i = 0; i < (total_messages / 2); i++)\n    {\n        ASSERT_EQ(AERON_RB_SUCCESS, aeron_spsc_rb_write(&rb, 1, &data, sizeof(data)));\n        data++;\n    }\n\n    ASSERT_EQ(CAPACITY / 2, aeron_spsc_rb_size(&rb));\n\n    aeron_rb_write_result_t result;\n    do\n    {\n        result = aeron_spsc_rb_write(&rb, 1, &data, sizeof(data));\n    }\n    while (AERON_RB_SUCCESS == result);\n\n    ASSERT_EQ(CAPACITY - spsc_padding, aeron_spsc_rb_size(&rb));\n}\n\n#define NUM_MESSAGES (10 * 1000 * 1000)\n#define NUM_IDS_PER_THREAD (10 * 1000 * 1000)\n\nTEST(SpscRbConcurrentTest, shouldProvideCorrelationIds)\n{\n    AERON_DECL_ALIGNED(buffer_t buffer, 16) = {};\n    buffer.fill(0);\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, buffer.data(), buffer.size()), 0);\n\n    std::atomic<int> countDown(2);\n\n    std::vector<std::thread> threads;\n\n    for (int i = 0; i < 2; i++)\n    {\n        threads.push_back(std::thread(\n            [&]()\n            {\n                countDown--;\n                while (countDown > 0)\n                {\n                    std::this_thread::yield();\n                }\n\n                for (int m = 0; m < NUM_IDS_PER_THREAD; m++)\n                {\n                    aeron_spsc_rb_next_correlation_id(&rb);\n                }\n            }));\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n\n    ASSERT_EQ(aeron_spsc_rb_next_correlation_id(&rb), NUM_IDS_PER_THREAD * 2);\n}\n\nstatic void spsc_rb_concurrent_handler(int32_t msg_type_id, const void *buffer, size_t length, void *clientd)\n{\n    auto *counts = (size_t *)clientd;\n    const int32_t messageNumber = *((int32_t *)(buffer));\n\n    EXPECT_EQ(length, (size_t)4);\n    ASSERT_EQ(msg_type_id, MSG_TYPE_ID);\n\n    EXPECT_EQ((*counts)++, (size_t)messageNumber);\n}\n\nTEST(SpscRbConcurrentTest, shouldExchangeMessages)\n{\n    AERON_DECL_ALIGNED(buffer_t spsc_buffer, 16) = {};\n    spsc_buffer.fill(0);\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, spsc_buffer.data(), spsc_buffer.size()), 0);\n\n    std::atomic<int> countDown(1);\n\n    std::vector<std::thread> threads;\n    size_t msgCount = 0;\n    size_t counts = 0;\n\n    threads.push_back(std::thread(\n        [&]()\n        {\n            AERON_DECL_ALIGNED(buffer_t buffer, 16);\n            buffer.fill(0);\n\n            countDown--;\n            while (countDown > 0)\n            {\n                std::this_thread::yield();\n            }\n\n            for (int m = 0; m < NUM_MESSAGES; m++)\n            {\n                auto *payload = (int32_t *)(buffer.data());\n                *payload = m;\n\n                while (AERON_RB_SUCCESS != aeron_spsc_rb_write(&rb, MSG_TYPE_ID, buffer.data(), 4))\n                {\n                    std::this_thread::yield();\n                }\n            }\n        }));\n\n    while (msgCount < NUM_MESSAGES)\n    {\n        const size_t readCount = aeron_spsc_rb_read(\n            &rb, spsc_rb_concurrent_handler, &counts, std::numeric_limits<size_t>::max());\n\n        if (0 == readCount)\n        {\n            std::this_thread::yield();\n        }\n\n        msgCount += readCount;\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n}\n\nTEST(SpscRbConcurrentTest, shouldExchangeVectorMessages)\n{\n    AERON_DECL_ALIGNED(buffer_t spsc_buffer, 16) = {};\n    spsc_buffer.fill(0);\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, spsc_buffer.data(), spsc_buffer.size()), 0);\n\n    std::atomic<int> countDown(1);\n\n    std::vector<std::thread> threads;\n    size_t msgCount = 0;\n    size_t counts = 0;\n\n    threads.push_back(std::thread(\n        [&]()\n        {\n            struct iovec vec[2];\n            AERON_DECL_ALIGNED(buffer_t buffer, 16);\n            buffer.fill(0);\n\n            countDown--;\n            while (countDown > 0)\n            {\n                std::this_thread::yield();\n            }\n\n            for (int m = 0; m < NUM_MESSAGES; m++)\n            {\n                auto *payload = (int32_t *)(buffer.data());\n                *payload = m;\n\n                vec[0].iov_len = 2;\n                vec[0].iov_base = payload;\n                vec[1].iov_len = 2;\n                vec[1].iov_base = ((uint8_t *)payload) + 2;\n\n                while (AERON_RB_SUCCESS != aeron_spsc_rb_writev(&rb, MSG_TYPE_ID, vec, 2))\n                {\n                    std::this_thread::yield();\n                }\n            }\n        }));\n\n    while (msgCount < NUM_MESSAGES)\n    {\n        const size_t readCount = aeron_spsc_rb_read(\n            &rb, spsc_rb_concurrent_handler, &counts, std::numeric_limits<size_t>::max());\n\n        if (0 == readCount)\n        {\n            std::this_thread::yield();\n        }\n\n        msgCount += readCount;\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n}\n\nTEST(SpscRbConcurrentTest, shouldExchangeMessagesViaTryClaim)\n{\n    AERON_DECL_ALIGNED(buffer_t spsc_buffer, 16) = {};\n    spsc_buffer.fill(0);\n\n    aeron_spsc_rb_t rb;\n    ASSERT_EQ(aeron_spsc_rb_init(&rb, spsc_buffer.data(), spsc_buffer.size()), 0);\n\n    std::atomic<int> countDown(1);\n\n    std::vector<std::thread> threads;\n    size_t msgCount = 0;\n    size_t counts = 0;\n\n    threads.push_back(std::thread(\n        [&]()\n        {\n            countDown--;\n            while (countDown > 0)\n            {\n                std::this_thread::yield();\n            }\n\n            for (int m = 0; m < NUM_MESSAGES; m++)\n            {\n                int32_t offset;\n                int32_t length = 4;\n                while ((offset = aeron_spsc_rb_try_claim(&rb, MSG_TYPE_ID, length)) < 0)\n                {\n                    std::this_thread::yield();\n                }\n\n                auto *payload = (int32_t *)(rb.buffer + offset);\n                *payload = m;\n\n                aeron_spsc_rb_commit(&rb, offset);\n            }\n        }));\n\n    while (msgCount < NUM_MESSAGES)\n    {\n        const size_t readCount = aeron_spsc_rb_read(\n            &rb, spsc_rb_concurrent_handler, &counts, std::numeric_limits<size_t>::max());\n\n        if (0 == readCount)\n        {\n            std::this_thread::yield();\n        }\n\n        msgCount += readCount;\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/concurrent/aeron_thread_test.cpp",
    "content": "/*\n * Copyright 2023 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_thread.h\"\n}\n\nTEST(AeronThreadTest, shouldCreateReentrantMutex)\n{\n    aeron_mutex_t mutex;\n    ASSERT_EQ(0, aeron_mutex_init(&mutex));\n\n    EXPECT_EQ(0, aeron_mutex_lock(&mutex));\n    EXPECT_EQ(0, aeron_mutex_lock(&mutex));\n    EXPECT_EQ(0, aeron_mutex_unlock(&mutex));\n    EXPECT_EQ(0, aeron_mutex_unlock(&mutex));\n\n    EXPECT_EQ(0, aeron_mutex_destroy(&mutex));\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/util/aeron_bitutil_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"util/aeron_bitutil.h\"\n}\n\nclass BitutilTest : public testing::Test\n{\npublic:\n    BitutilTest() = default;\n};\n\nTEST_F(BitutilTest, shouldCountTrailingZeros64Bit)\n{\n    EXPECT_EQ(64, aeron_number_of_trailing_zeroes_u64(0));\n    for (uint64_t i = 0; i < 64; i++)\n    {\n        uint64_t value = UINT64_C(1) << i;\n        EXPECT_EQ(aeron_number_of_trailing_zeroes_u64(value), static_cast<int>(i));\n    }\n}\n\nTEST_F(BitutilTest, shouldCountTrailingZeros32Bit)\n{\n    EXPECT_EQ(32, aeron_number_of_trailing_zeroes(0));\n    for (uint32_t i = 0; i < 32; i++)\n    {\n        uint32_t value = UINT32_C(1) << i;\n        EXPECT_EQ(aeron_number_of_trailing_zeroes(value), static_cast<int>(i));\n    }\n}\n\nTEST_F(BitutilTest, shouldCountLeadingZeros32Bit)\n{\n    EXPECT_EQ(32, aeron_number_of_leading_zeroes(0));\n    for (uint64_t i = 0; i < 32; i++)\n    {\n        uint32_t value = UINT32_C(1) << i;\n        EXPECT_EQ(aeron_number_of_leading_zeroes(value), 31 - i);\n    }\n}\n\nTEST_F(BitutilTest, shouldCountLeadingZeros64Bit)\n{\n    EXPECT_EQ(64, aeron_number_of_leading_zeroes_u64(0));\n    for (uint64_t i = 0; i < 64; i++)\n    {\n        uint64_t value = UINT64_C(1) << i;\n        EXPECT_EQ(aeron_number_of_leading_zeroes_u64(value), 63 - i);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/util/aeron_deque_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"util/aeron_deque.h\"\n}\n\nclass DequeTest : public testing::Test\n{\npublic:\n    DequeTest() = default;\n};\n\nTEST_F(DequeTest, shouldAddLastAndRemoveFirst)\n{\n    aeron_deque_t deque = {};\n    uint64_t expected = 8723642836;\n    uint64_t actual = 0;\n    ASSERT_EQ(0, aeron_deque_init(&deque, 8, sizeof(uint64_t)));\n    ASSERT_EQ(1, aeron_deque_add_last(&deque, &expected));\n    ASSERT_EQ(1, aeron_deque_remove_first(&deque, &actual));\n\n    ASSERT_EQ(expected, actual);\n\n    aeron_deque_close(&deque);\n}\n\nTEST_F(DequeTest, shouldReturnErrorIfEmptyOnRemove)\n{\n    aeron_deque_t deque = {};\n    uint64_t actual = 0;\n    ASSERT_EQ(0, aeron_deque_init(&deque, 8, sizeof(uint64_t)));\n    ASSERT_EQ(0, aeron_deque_remove_first(&deque, &actual));\n\n    aeron_deque_close(&deque);\n}\n\nTEST_F(DequeTest, shouldWrapAroundWithAddsAndRemoves)\n{\n    aeron_deque_t deque = {};\n    uint64_t expected = 0;\n    uint64_t actual = 0;\n    ASSERT_EQ(0, aeron_deque_init(&deque, 8, sizeof(uint64_t)));\n\n    for (int i = 0; i < 64; i++)\n    {\n        expected++;\n\n        ASSERT_EQ(1, aeron_deque_add_last(&deque, &expected));\n        ASSERT_EQ(1, aeron_deque_remove_first(&deque, &actual));\n        ASSERT_EQ(expected, actual);\n    }\n\n    aeron_deque_close(&deque);\n}\n\nTEST_F(DequeTest, shouldExpandMultipleAddsWithWrappedBuffer)\n{\n    aeron_deque_t deque = {};\n    uint64_t expected = 0;\n    uint64_t actual = 0;\n    ASSERT_EQ(0, aeron_deque_init(&deque, 8, sizeof(uint64_t)));\n\n    for (int i = 0; i < 13; i++)\n    {\n        expected++;\n        ASSERT_EQ(1, aeron_deque_add_last(&deque, &expected));\n        ASSERT_EQ(1, aeron_deque_remove_first(&deque, &actual));\n    }\n\n    std::cout << deque.first_element << \" \" << deque.last_element << std::endl;\n\n    expected = 0;\n    for (int i = 0; i < 64; i++)\n    {\n        expected++;\n\n        ASSERT_EQ(1, aeron_deque_add_last(&deque, &expected));\n    }\n\n    expected = 0;\n    for (int i = 0; i < 64; i++)\n    {\n        expected++;\n\n        ASSERT_EQ(1, aeron_deque_remove_first(&deque, &actual)) << \"i = \" << i;\n        ASSERT_EQ(expected, actual);\n    }\n\n    aeron_deque_close(&deque);\n}\n\nTEST_F(DequeTest, shouldExpandMultipleAdds)\n{\n    aeron_deque_t deque = {};\n    uint64_t expected = 0;\n    uint64_t actual = 0;\n    ASSERT_EQ(0, aeron_deque_init(&deque, 8, sizeof(uint64_t)));\n\n    for (int i = 0; i < 64; i++)\n    {\n        expected++;\n\n        ASSERT_EQ(1, aeron_deque_add_last(&deque, &expected));\n    }\n\n    expected = 0;\n    for (int i = 0; i < 64; i++)\n    {\n        expected++;\n\n        ASSERT_EQ(1, aeron_deque_remove_first(&deque, &actual)) << \"i = \" << i;\n        ASSERT_EQ(expected, actual);\n    }\n\n    aeron_deque_close(&deque);\n}\n\n"
  },
  {
    "path": "aeron-client/src/test/c/util/aeron_error_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <atomic>\n#include <thread>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\nextern \"C\"\n{\n#include \"util/aeron_error.h\"\n}\n\nclass ErrorTest : public testing::Test\n{\npublic:\n    ErrorTest()\n    {\n        aeron_err_clear();\n    }\n};\n\nint functionA()\n{\n    AERON_SET_ERR(-EINVAL, \"this is the root error: %d\", 10);\n    return -1;\n}\n\nint functionB()\n{\n    if (functionA() < 0)\n    {\n        AERON_APPEND_ERR(\"this is another error: %d\", 20);\n        return -1;\n    }\n\n    return 0;\n}\n\nint functionC()\n{\n    if (functionB() < 0)\n    {\n        AERON_APPEND_ERR(\"this got borked: %d\", 30);\n    }\n\n    return 0;\n}\n\nstatic std::string::size_type assert_substring(\n    const std::string &value, const std::string &token, const std::string::size_type index)\n{\n    auto new_index = value.find(token, index);\n    EXPECT_NE(new_index, std::string::npos) << value;\n\n    return new_index;\n}\n\nTEST_F(ErrorTest, shouldStackErrors)\n{\n    functionC();\n\n    std::string err_msg = std::string(aeron_errmsg());\n\n    auto index = assert_substring(err_msg, \"(-22) unknown error code\", 0);\n    index = assert_substring(err_msg, \"[functionA, aeron_error_test.cpp:\", index);\n    index = assert_substring(err_msg, \"] this is the root error: 10\", index);\n    index = assert_substring(err_msg, \"[functionB, aeron_error_test.cpp:\", index);\n    index = assert_substring(err_msg, \"] this is another error: 20\", index);\n    index = assert_substring(err_msg, \"[functionC, aeron_error_test.cpp:\", index);\n    index = assert_substring(err_msg, \"] this got borked: 30\", index);\n\n    EXPECT_LT(index, err_msg.length());\n}\n\nTEST_F(ErrorTest, shouldHandleErrorsOverflow)\n{\n    AERON_SET_ERR(EINVAL, \"%s\", \"this is the root error\");\n\n    for (int i = 0; i < 1000; i++)\n    {\n        AERON_APPEND_ERR(\"this is a nested error: %d\", i);\n    }\n\n    std::string err_msg = std::string(aeron_errmsg());\n\n    auto index = assert_substring(err_msg, \"(22) Invalid argument\", 0);\n    index = assert_substring(err_msg, \"[TestBody, aeron_error_test.cpp:\", index);\n    index = assert_substring(err_msg, \"] this is the root error\", index);\n    index = assert_substring(err_msg, \"[TestBody, aeron_error_test.cpp:\", index);\n    index = assert_substring(err_msg, \"] this is a nested error: \", index);\n    index = assert_substring(err_msg, \"[TestBody, aeron_error_...\", index);\n\n    EXPECT_LT(index, err_msg.length());\n}\n\nTEST_F(ErrorTest, shouldReportZeroAsErrorForBackwardCompatibility)\n{\n    AERON_SET_ERR(0, \"%s\", \"this is the root error\");\n\n    std::string err_msg = std::string(aeron_errmsg());\n\n    auto index = assert_substring(err_msg, \"(0) generic error, see message\", 0);\n    index = assert_substring(err_msg, \"[TestBody, aeron_error_test.cpp:\", index);\n    index = assert_substring(err_msg, \"] this is the root error\", index);\n\n    EXPECT_LT(index, err_msg.length());\n}\n\nTEST_F(ErrorTest, shouldAllowToAppendAfterClearing)\n{\n    AERON_APPEND_ERR(\"%s\", \"first error\");\n    aeron_err_clear();\n    AERON_APPEND_ERR(\"%s\", \"second error\");\n\n    std::string err_msg = std::string(aeron_errmsg());\n\n    EXPECT_THAT(err_msg, testing::Not(testing::HasSubstr(\"no error\")));\n    EXPECT_THAT(err_msg, testing::Not(testing::HasSubstr(\"first error\")));\n    EXPECT_THAT(err_msg, testing::HasSubstr(\"second error\"));\n}\n\n#define CALLS_PER_THREAD (1000)\n#define NUM_THREADS (2)\n#define ITERATIONS (10)\n\nstatic void test_concurrent_access()\n{\n    std::atomic<int> countDown(NUM_THREADS);\n    std::vector<std::thread> threads;\n\n    for (int i = 0; i < NUM_THREADS; i++)\n    {\n        threads.push_back(\n            std::thread(\n                [&]()\n                {\n                    const int thread_id = countDown.fetch_sub(1);\n                    while (countDown > 0)\n                    {\n                        std::this_thread::yield();\n                    }\n\n                    const auto start(\"] [\" + std::to_string(thread_id) + \"] start\");\n                    const auto end(\"] [\" + std::to_string(thread_id) + \"] end:\");\n                    for (int m = 0; m < CALLS_PER_THREAD; m++)\n                    {\n                        AERON_SET_ERR(0, \"[%d] %s\", thread_id, \"start\");\n                        AERON_APPEND_ERR(\"[%d] end: %d\", thread_id, m);\n\n                        std::string err_msg = std::string(aeron_errmsg());\n\n                        auto index = assert_substring(err_msg, \"(0) generic error, see message\", 0);\n                        index = assert_substring(err_msg, \"[operator\", index);\n                        index = assert_substring(err_msg, start, index);\n                        index = assert_substring(err_msg, \"[operator\", index);\n                        index = assert_substring(err_msg, end, index);\n                        EXPECT_LT(index, err_msg.length());\n\n                        aeron_err_clear();\n                    }\n                }));\n    }\n\n    for (std::thread &t: threads)\n    {\n        if (t.joinable())\n        {\n            t.join();\n        }\n    }\n}\n\nTEST_F(ErrorTest, shouldAllowConcurrentAccess)\n{\n    for (int i = 0; i < ITERATIONS; i++)\n    {\n        test_concurrent_access();\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/util/aeron_fileutil_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <exception>\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"util/aeron_fileutil.h\"\n#include \"util/aeron_error.h\"\n}\n\n#if defined(AERON_COMPILER_GCC)\n#define removeDir remove\n#elif defined(AERON_COMPILER_MSVC)\n#define removeDir RemoveDirectoryA\n#endif\n\n#ifdef _MSC_VER\n#define AERON_FILE_SEP_STR \"\\\\\"\n#else\n#define AERON_FILE_SEP_STR \"/\"\n#endif\n\nclass FileUtilTest : public testing::Test {\npublic:\n    FileUtilTest() = default;\n};\n\nTEST_F(FileUtilTest, rawLogCloseShouldUnmapAndDeleteLogFile)\n{\n    aeron_mapped_raw_log_t mapped_raw_log = {};\n    const char *file = \"test_close_unused_file.log\";\n    const size_t file_length = 16384;\n    const size_t term_length = 4096;\n    ASSERT_EQ(0, aeron_raw_log_map(&mapped_raw_log, file, true, term_length, 4096)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n    EXPECT_EQ(term_length, mapped_raw_log.term_length);\n    EXPECT_NE(nullptr, mapped_raw_log.log_meta_data.addr);\n    EXPECT_EQ(AERON_LOGBUFFER_META_DATA_LENGTH, mapped_raw_log.log_meta_data.length);\n    for (auto &term_buffer : mapped_raw_log.term_buffers)\n    {\n        EXPECT_NE(nullptr, term_buffer.addr);\n        EXPECT_EQ(term_length, term_buffer.length);\n    }\n\n    ASSERT_EQ(0, aeron_raw_log_close(&mapped_raw_log, file)) << aeron_errmsg();\n\n    EXPECT_EQ(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ((size_t)0, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, rawLogFreeShouldUnmapAndDeleteLogFile)\n{\n    aeron_mapped_raw_log_t mapped_raw_log = {};\n    const char *file = \"test_free_unused_file.log\";\n    const size_t file_length = 16384;\n    ASSERT_EQ(0, aeron_raw_log_map(&mapped_raw_log, file, true, 4096, 4096)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n\n    ASSERT_EQ(true, aeron_raw_log_free(&mapped_raw_log, file)) << aeron_errmsg();\n\n    EXPECT_EQ(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ((size_t)0, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, rawLogCloseShouldNotDeleteFileIfUnmapFails)\n{\n    aeron_mapped_raw_log_t mapped_raw_log = {};\n    const char *file = \"test_close_unmap_fails.log\";\n    const size_t file_length = 16384;\n    ASSERT_EQ(0, aeron_raw_log_map(&mapped_raw_log, file, true, 4096, 4096)) << aeron_errmsg();\n    const auto mapped_addr = mapped_raw_log.mapped_file.addr;\n    mapped_raw_log.mapped_file.addr = reinterpret_cast<void *>(-1);\n\n    ASSERT_EQ(-1, aeron_raw_log_close(&mapped_raw_log, file)) << aeron_errmsg();\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n\n    mapped_raw_log.mapped_file.addr = mapped_addr;\n    ASSERT_EQ(0, aeron_raw_log_close(&mapped_raw_log, file));\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, rawLogFreeShouldNotDeleteFileIfUnmapFails)\n{\n    aeron_mapped_raw_log_t mapped_raw_log = {};\n    const char *file = \"test_free_unmap_fails.log\";\n    const size_t file_length = 16384;\n    ASSERT_EQ(0, aeron_raw_log_map(&mapped_raw_log, file, true, 4096, 4096)) << aeron_errmsg();\n    const auto mapped_addr = mapped_raw_log.mapped_file.addr;\n    mapped_raw_log.mapped_file.addr = reinterpret_cast<void *>(-1);\n\n    ASSERT_EQ(false, aeron_raw_log_free(&mapped_raw_log, file)) << aeron_errmsg();\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n\n    mapped_raw_log.mapped_file.addr = mapped_addr;\n    ASSERT_EQ(true, aeron_raw_log_free(&mapped_raw_log, file)) << aeron_errmsg();\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, resolveShouldConcatPaths)\n{\n    const char *parent = \"this_is_the_parent\";\n    const char *child = \"this_is_the_child\";\n#ifdef _MSC_VER\n    const char *expected = \"this_is_the_parent\\\\this_is_the_child\";\n#else\n    const char *expected = \"this_is_the_parent/this_is_the_child\";\n#endif\n    char result[AERON_MAX_PATH];\n\n    ASSERT_LT(0, aeron_file_resolve(parent, child, result, sizeof(result))) << aeron_errmsg();\n    ASSERT_STREQ(expected, result);\n}\n\nTEST_F(FileUtilTest, resolveShouldReportTruncatedPaths)\n{\n    const char *parent = \"this_is_the_parent\";\n    const char *child = \"this_is_the_child\";\n    char result[10];\n\n    ASSERT_EQ(-1, aeron_file_resolve(parent, child, result, sizeof(result))) << aeron_errmsg();\n    ASSERT_EQ(EINVAL, aeron_errcode());\n    ASSERT_EQ('\\0', result[sizeof(result) - 1]);\n}\n\nTEST_F(FileUtilTest, mapNewFileShouldHandleFilesBiggerThan2GB)\n{\n    aeron_mapped_file_t mapped_file = {};\n    const char *file = \"test_map_new_file_big_size.log\";\n    const size_t file_length = 3221225472;\n    mapped_file.length = file_length;\n    ASSERT_EQ(0, aeron_map_new_file(&mapped_file, file, false)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_file.length);\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n\n    ASSERT_EQ(0, aeron_unmap(&mapped_file)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_file.length);\n    EXPECT_EQ(0, remove(file));\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, mapExistingFileShouldHandleFilesBiggerThan2GB)\n{\n    aeron_mapped_file_t mapped_file = {};\n    const char *file = \"test_map_existing_file_big_size.log\";\n    const size_t file_length = 2500000000;\n    mapped_file.length = file_length;\n    ASSERT_EQ(0, aeron_map_new_file(&mapped_file, file, false)) << aeron_errmsg();\n    ASSERT_EQ(0, aeron_unmap(&mapped_file)) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_map_existing_file(&mapped_file, file)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_file.length);\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n\n    ASSERT_EQ(0, aeron_unmap(&mapped_file)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_file.length);\n    EXPECT_EQ(0, remove(file));\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, rawLogMapShouldHandleMaxTermBufferLengthAndMaxPageSize)\n{\n    aeron_mapped_raw_log_t mapped_raw_log = {};\n    const char *file = \"test_raw_log_map_new_file_max_buffer_length.log\";\n    const size_t file_length = 4294967296;\n    ASSERT_EQ(0, aeron_raw_log_map(&mapped_raw_log, file, true, AERON_LOGBUFFER_TERM_MAX_LENGTH, AERON_PAGE_MAX_SIZE)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n    EXPECT_EQ(AERON_LOGBUFFER_TERM_MAX_LENGTH, mapped_raw_log.term_length);\n    EXPECT_NE(nullptr, mapped_raw_log.log_meta_data.addr);\n    EXPECT_EQ(AERON_LOGBUFFER_META_DATA_LENGTH, mapped_raw_log.log_meta_data.length);\n    for (size_t i = 0; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n    {\n        EXPECT_NE(nullptr, mapped_raw_log.term_buffers[i].addr);\n        EXPECT_EQ(AERON_LOGBUFFER_TERM_MAX_LENGTH, mapped_raw_log.term_buffers[i].length);\n    }\n\n    ASSERT_TRUE(aeron_raw_log_free(&mapped_raw_log, file)) << aeron_errmsg();\n\n    EXPECT_EQ(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ((size_t)0, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, rawLogMapExistingShouldHandleMaxTermBufferLength)\n{\n    aeron_mapped_raw_log_t mapped_raw_log = {};\n    const char *file = \"test_raw_log_map_existing_file_max_buffer_length.log\";\n    const size_t file_length = 3223322624;\n    const size_t term_length = AERON_LOGBUFFER_TERM_MAX_LENGTH;\n    const size_t page_size = 2 * 1024 * 1024;\n    ASSERT_EQ(0, aeron_raw_log_map(&mapped_raw_log, file, true, term_length, page_size)) << aeron_errmsg();\n    auto logbuffer_metadata = (aeron_logbuffer_metadata_t *)(mapped_raw_log.log_meta_data.addr);\n    logbuffer_metadata->term_length = (int32_t)term_length;\n    logbuffer_metadata->page_size = (int32_t)page_size;\n    ASSERT_EQ(0, aeron_unmap(&mapped_raw_log.mapped_file)) << aeron_errmsg();\n\n    mapped_raw_log = {};\n    ASSERT_EQ(0, aeron_raw_log_map_existing(&mapped_raw_log, file, false)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n    EXPECT_EQ(term_length, mapped_raw_log.term_length);\n    EXPECT_NE(nullptr, mapped_raw_log.log_meta_data.addr);\n    EXPECT_EQ(AERON_LOGBUFFER_META_DATA_LENGTH, mapped_raw_log.log_meta_data.length);\n    logbuffer_metadata = (aeron_logbuffer_metadata_t *)(mapped_raw_log.log_meta_data.addr);\n    EXPECT_EQ(term_length, (size_t)logbuffer_metadata->term_length);\n    EXPECT_EQ(page_size, (size_t)logbuffer_metadata->page_size);\n    for (auto &term_buffer : mapped_raw_log.term_buffers)\n    {\n        EXPECT_NE(nullptr, term_buffer.addr);\n        EXPECT_EQ(term_length, term_buffer.length);\n    }\n\n    ASSERT_TRUE(aeron_raw_log_free(&mapped_raw_log, file)) << aeron_errmsg();\n\n    EXPECT_EQ(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ((size_t)0, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, mapNewFileShouldCreateANonSparseFile)\n{\n    aeron_mapped_file_t mapped_file = {};\n    const char *file = \"test_map_new_file_non_sparse.log\";\n    const size_t file_length = 64 * 1024;\n    mapped_file.length = file_length;\n    ASSERT_EQ(0, aeron_map_new_file(&mapped_file, file, true)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_file.length);\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n\n    ASSERT_EQ(0, aeron_unmap(&mapped_file)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_file.length);\n    EXPECT_EQ(0, remove(file));\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, rawLogMapShouldCreateNonSparseFile)\n{\n    aeron_mapped_raw_log_t mapped_raw_log = {};\n    const char *file = \"test_raw_log_map_non_sparse_file.log\";\n    const size_t file_length = 3149824;\n    const size_t term_length = 1024 * 1024;\n    ASSERT_EQ(0, aeron_raw_log_map(&mapped_raw_log, file, false, term_length, AERON_PAGE_MIN_SIZE)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n    EXPECT_EQ(term_length, mapped_raw_log.term_length);\n    EXPECT_NE(nullptr, mapped_raw_log.log_meta_data.addr);\n    EXPECT_EQ(AERON_LOGBUFFER_META_DATA_LENGTH, mapped_raw_log.log_meta_data.length);\n    for (auto &term_buffer : mapped_raw_log.term_buffers)\n    {\n        EXPECT_NE(nullptr, term_buffer.addr);\n        EXPECT_EQ(term_length, term_buffer.length);\n    }\n\n    ASSERT_TRUE(aeron_raw_log_free(&mapped_raw_log, file)) << aeron_errmsg();\n\n    EXPECT_EQ(nullptr, mapped_raw_log.mapped_file.addr);\n    EXPECT_EQ((size_t)0, mapped_raw_log.mapped_file.length);\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, shouldMsyncMappedFile)\n{\n    aeron_mapped_file_t mapped_file = {};\n    const char *file = \"test.txt\";\n    const size_t file_length = 1024 * 512;\n    mapped_file.length = file_length;\n    ASSERT_EQ(0, aeron_map_new_file(&mapped_file, file, false)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_file.length);\n    EXPECT_EQ((int64_t)file_length, aeron_file_length(file));\n\n    ASSERT_EQ(0, aeron_msync(mapped_file.addr, mapped_file.length));\n    ASSERT_EQ(0, aeron_msync(mapped_file.addr, 1));\n\n    ASSERT_EQ(0, aeron_unmap(&mapped_file)) << aeron_errmsg();\n\n    EXPECT_NE(nullptr, mapped_file.addr);\n    EXPECT_EQ(file_length, mapped_file.length);\n    EXPECT_EQ(0, remove(file));\n    EXPECT_EQ((int64_t)-1, aeron_file_length(file));\n}\n\nTEST_F(FileUtilTest, shouldErrorIfMSyncingNonMappedData)\n{\n    char data[10];\n    ASSERT_EQ(-1, aeron_msync(&data, 1));\n    ASSERT_NE(0, aeron_errcode());\n    ASSERT_NE(std::string(\"\"), aeron_errmsg());\n}\n\nTEST_F(FileUtilTest, shouldNotErrorIfAddressIsNull)\n{\n    ASSERT_EQ(0, aeron_msync(nullptr, 10));\n}\n\nTEST_F(FileUtilTest, simpleMkdir)\n{\n    const char *dirA = \"dirA\";\n    const char *dirB = \"dirA\" AERON_FILE_SEP_STR \"dirB\";\n    const char *dirC = \"dirA\" AERON_FILE_SEP_STR \"dirNOPE\" AERON_FILE_SEP_STR \"dirC\";\n\n    removeDir(\"dirA\" AERON_FILE_SEP_STR \"dirNOPE\" AERON_FILE_SEP_STR \"dirC\");\n    removeDir(\"dirA\" AERON_FILE_SEP_STR \"dirNOPE\");\n    removeDir(\"dirA\" AERON_FILE_SEP_STR \"dirB\");\n    removeDir(\"dirA\");\n\n    ASSERT_EQ(0, aeron_mkdir(dirA, S_IRWXU | S_IRWXG | S_IRWXO));\n    ASSERT_EQ(0, aeron_mkdir(dirB, S_IRWXU | S_IRWXG | S_IRWXO));\n    ASSERT_EQ(-1, aeron_mkdir(dirC, S_IRWXU | S_IRWXG | S_IRWXO));\n}\n\nTEST_F(FileUtilTest, recursiveMkdir)\n{\n    const char *dirW = \"dirW\";\n    const char *dirY = \"dirX\" AERON_FILE_SEP_STR \"dirY\";\n    const char *dirZ = \"dirW\" AERON_FILE_SEP_STR \"dirX\" AERON_FILE_SEP_STR \"dirY\" AERON_FILE_SEP_STR \"dirZ\";\n\n    removeDir(\"dirW\" AERON_FILE_SEP_STR \"dirX\" AERON_FILE_SEP_STR \"dirY\" AERON_FILE_SEP_STR \"dirZ\");\n    removeDir(\"dirW\" AERON_FILE_SEP_STR \"dirX\" AERON_FILE_SEP_STR \"dirY\");\n    removeDir(\"dirW\" AERON_FILE_SEP_STR \"dirX\");\n    removeDir(\"dirW\");\n    removeDir(\"dirX\" AERON_FILE_SEP_STR \"dirY\");\n    removeDir(\"dirX\");\n\n    ASSERT_EQ(0, aeron_mkdir_recursive(dirW, S_IRWXU | S_IRWXG | S_IRWXO));\n    ASSERT_EQ(0, aeron_mkdir_recursive(dirY, S_IRWXU | S_IRWXG | S_IRWXO));\n    ASSERT_EQ(0, aeron_mkdir_recursive(dirZ, S_IRWXU | S_IRWXG | S_IRWXO));\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/util/aeron_httputil_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include <random>\n\nextern \"C\"\n{\n#include \"util/aeron_error.h\"\n#include \"util/aeron_http_util.h\"\nint aeron_http_parse_response(aeron_http_response_t *response);\n}\n\nclass HttpUtilTest : public testing::Test\n{\npublic:\n    HttpUtilTest() = default;\n};\n\nTEST_F(HttpUtilTest, shouldParseHttpContent)\n{\n    const char *content =\n        \"HTTP/1.1 200 OK\\r\\n\"\n        \"Content-Type: text/plain\\r\\n\"\n        \"Accept-Ranges: none\\r\\n\"\n        \"Last-Modified: Thu, 05 May 2022 18:43:25 GMT\\r\\n\"\n        \"Content-Length: 10\\r\\n\"\n        \"Date: Thu, 05 May 2022 18:54:35 GMT\\r\\n\"\n        \"Server: EC2ws\\r\\n\"\n        \"Connection: close\\r\\n\"\n        \"\\r\\n\"\n        \"10.2.75.14\";\n\n    size_t total_length = strlen(content);\n\n    for (size_t offset = 1; offset < total_length; offset++)\n    {\n        char buffer[1024] = {};\n\n        aeron_http_response_t response;\n        response.buffer = buffer;\n        response.headers_offset = 0;\n        response.cursor = 0;\n        response.body_offset = 0;\n        response.length = 0;\n        response.capacity = 0;\n        response.status_code = 0;\n        response.content_length = 0;\n        response.parse_err = false;\n        response.is_complete = false;\n\n        memcpy(buffer, content, offset);\n        response.length = offset;\n\n        ASSERT_EQ(0, aeron_http_parse_response(&response));\n        ASSERT_FALSE(response.is_complete);\n\n        memcpy(buffer + offset, content + offset, total_length - offset);\n        response.length = total_length;\n\n        ASSERT_EQ(0, aeron_http_parse_response(&response));\n        ASSERT_TRUE(response.is_complete);\n\n        ASSERT_EQ(200, response.status_code);\n        ASSERT_EQ(203, response.body_offset);\n        ASSERT_EQ(10, response.content_length);\n        ASSERT_EQ(false, response.parse_err);\n        ASSERT_STREQ(\"10.2.75.14\", response.buffer + response.body_offset) << offset;\n    }\n}\n\nTEST_F(HttpUtilTest, shouldParseHttpContentWithoutContentLength)\n{\n    const char *content =\n        \"HTTP/1.1 200 OK\\r\\n\"\n        \"Content-Type: text/plain\\r\\n\"\n        \"Accept-Ranges: none\\r\\n\"\n        \"Last-Modified: Thu, 05 May 2022 18:43:25 GMT\\r\\n\"\n        \"Date: Thu, 05 May 2022 18:54:35 GMT\\r\\n\"\n        \"Server: EC2ws\\r\\n\"\n        \"Connection: close\\r\\n\"\n        \"\\r\\n\"\n        \"10.2.75.14\";\n\n    size_t total_length = strlen(content);\n\n    for (size_t offset = 1; offset < total_length; offset++)\n    {\n        char buffer[1024] = {};\n\n        aeron_http_response_t response;\n        response.buffer = buffer;\n        response.headers_offset = 0;\n        response.cursor = 0;\n        response.body_offset = 0;\n        response.length = 0;\n        response.capacity = 0;\n        response.status_code = 0;\n        response.content_length = 0;\n        response.parse_err = false;\n        response.is_complete = false;\n\n        memcpy(buffer, content, offset);\n        response.length = offset;\n\n        ASSERT_EQ(0, aeron_http_parse_response(&response));\n        ASSERT_FALSE(response.is_complete);\n\n        memcpy(buffer + offset, content + offset, total_length - offset);\n        response.length = total_length;\n\n        ASSERT_EQ(0, aeron_http_parse_response(&response));\n        ASSERT_FALSE(response.is_complete);\n\n        ASSERT_EQ(200, response.status_code);\n        ASSERT_EQ(183, response.body_offset);\n        ASSERT_EQ(0, response.content_length);\n        ASSERT_EQ(false, response.parse_err);\n        ASSERT_STREQ(\"10.2.75.14\", response.buffer + response.body_offset) << offset;\n    }\n}\n\nTEST_F(HttpUtilTest, shouldErrorWithInvalidStatusCode)\n{\n    const char *content =\n        \"HTTP/1.1 AAA OK\\r\\n\"\n        \"Content-Type: text/plain\\r\\n\"\n        \"Accept-Ranges: none\\r\\n\"\n        \"Last-Modified: Thu, 05 May 2022 18:43:25 GMT\\r\\n\"\n        \"Content-Length: 10\\r\\n\"\n        \"Date: Thu, 05 May 2022 18:54:35 GMT\\r\\n\"\n        \"Server: EC2ws\\r\\n\"\n        \"Connection: close\\r\\n\"\n        \"\\r\\n\"\n        \"10.2.75.14\";\n\n    aeron_http_response_t response = {};\n    response.buffer = const_cast<char *>(content);\n    response.length = strlen(content);\n\n    ASSERT_EQ(-1, aeron_http_parse_response(&response)) << aeron_errmsg();\n    ASSERT_FALSE(response.is_complete);\n    ASSERT_TRUE(response.parse_err);\n}\n\nTEST_F(HttpUtilTest, shouldErrorWithZeroStatusCode)\n{\n    const char *content =\n        \"HTTP/1.1 000 OK\\r\\n\"\n        \"Content-Type: text/plain\\r\\n\"\n        \"Accept-Ranges: none\\r\\n\"\n        \"Last-Modified: Thu, 05 May 2022 18:43:25 GMT\\r\\n\"\n        \"Content-Length: 10\\r\\n\"\n        \"Date: Thu, 05 May 2022 18:54:35 GMT\\r\\n\"\n        \"Server: EC2ws\\r\\n\"\n        \"Connection: close\\r\\n\"\n        \"\\r\\n\"\n        \"10.2.75.14\";\n\n    aeron_http_response_t response = {};\n    response.buffer = const_cast<char *>(content);\n    response.length = strlen(content);\n\n    ASSERT_EQ(-1, aeron_http_parse_response(&response)) << aeron_errmsg();\n    ASSERT_FALSE(response.is_complete);\n    ASSERT_TRUE(response.parse_err);\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/util/aeron_math_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"util/aeron_math.h\"\n}\n\nclass MathTest : public testing::Test\n{\npublic:\n    MathTest() = default;\n};\n\nTEST_F(MathTest, shouldApplyBasicAdditionWrapping)\n{\n    EXPECT_EQ(aeron_add_wrap_i32(INT32_MAX, 1), INT32_MIN);\n    EXPECT_EQ(aeron_add_wrap_i32(INT32_MIN, -1), INT32_MAX);\n    EXPECT_EQ(aeron_add_wrap_i32(INT32_MIN, INT32_MIN), 0);\n    EXPECT_EQ(aeron_add_wrap_i32(INT32_MAX, INT32_MAX), -2);\n    EXPECT_EQ(aeron_add_wrap_i32(INT32_MAX, -INT32_MAX), 0);\n}\n\nTEST_F(MathTest, shouldApplyBasicSubtractionWrapping)\n{\n    EXPECT_EQ(aeron_sub_wrap_i32(INT32_MIN, 1), INT32_MAX);\n    EXPECT_EQ(aeron_sub_wrap_i32(INT32_MAX, -1), INT32_MIN);\n    EXPECT_EQ(aeron_sub_wrap_i32(INT32_MIN, INT32_MIN), 0);\n}\n\nTEST_F(MathTest, shouldApplyBasicMultiplyWrapping)\n{\n    EXPECT_EQ(aeron_mul_wrap_i32(INT32_MAX / 2, INT32_MAX / 2), -2147483647);\n    EXPECT_EQ(aeron_mul_wrap_i32(INT32_MAX / 2, 5), 1073741819);\n}\n\nTEST_F(MathTest, shouldWrapAdditionFromMaxToMinPositiveOverflow)\n{\n    for (int i = 0; i < 10; i++)\n    {\n        for (int j = 0; j < 10; j++)\n        {\n            EXPECT_EQ(aeron_add_wrap_i32(INT32_MAX - j, (i + j + 1)), INT32_MIN + i);\n        }\n    }\n}\n\nTEST_F(MathTest, shouldWrapAdditionFromMinToMaxNegativeOverflow)\n{\n    for (int i = 0; i < 10; i++)\n    {\n        for (int j = 0; j < 10; j++)\n        {\n            EXPECT_EQ(aeron_add_wrap_i32(INT32_MIN + j, -(i + j + 1)), INT32_MAX - i);\n        }\n    }\n}\n\nTEST_F(MathTest, shouldWrapSubtractionFromMaxToMinPositiveOverflow)\n{\n    for (int i = 0; i < 10; i++)\n    {\n        for (int j = 0; j < 10; j++)\n        {\n            EXPECT_EQ(aeron_sub_wrap_i32(INT32_MAX - j, -(i + j + 1)), INT32_MIN + i);\n        }\n    }\n}\n\nTEST_F(MathTest, shouldWrapSubtractionFromMinToMaxNegativeOverflow)\n{\n    for (int i = 0; i < 10; i++)\n    {\n        for (int j = 0; j < 10; j++)\n        {\n            EXPECT_EQ(aeron_sub_wrap_i32(INT32_MIN + j, (i + j + 1)), INT32_MAX - i);\n        }\n    }\n}\n\nTEST_F(MathTest, shouldNotWrapWhenNoOverflow)\n{\n    for (int i = 0; i < 10; i++)\n    {\n        for (int j = 0; j < 10; j++)\n        {\n            EXPECT_EQ(aeron_add_wrap_i32(i, j), i + j);\n            EXPECT_EQ(aeron_add_wrap_i32(i, -j), i - j);\n        }\n    }\n}\n\nTEST_F(MathTest, shouldWrapMultiplyGeneratedExamples)\n{\n    //<editor-fold desc=\"Example Wrapping Values generated by Java code\">\n    int32_t examples[1000][3] =\n        {\n            { 45463405,    1725651321,  -857237627 },\n            { -1610160479, -1128590487, -645035255 },\n            { -1662394276, 1299315045,  404422732 },\n            { 1976076958,  710475788,   -1571520664 },\n            { -922934684,  852948013,   -522298476 },\n            { -229832338,  1405455347,  -1092309654 },\n            { -378750235,  2004705100,  1517516540 },\n            { 1714016155,  1732167440,  -2021099856 },\n            { -592638277,  -1214725725, 1716739089 },\n            { -847882716,  -1189655248, -548078912 },\n            { -1084342846, 767064750,   2028760540 },\n            { -1499477084, -1963220404, -1474675536 },\n            { 188213916,   -749344520,  76850976 },\n            { -1871991885, -280985570,  -868770054 },\n            { -1786561871, -763755567,  -1003684479 },\n            { 1065478283,  290449655,   -17148387 },\n            { -1746513289, -1665770435, 471755355 },\n            { 1983744750,  1196692431,  -987037582 },\n            { -1061891597, 1861794094,  429806762 },\n            { 1544350791,  -1952676451, -1292776821 },\n            { -382921346,  -1635794186, -1457642732 },\n            { -1164382449, 1999301609,  1780111783 },\n            { 675430092,   -458591754,  1356358664 },\n            { -931341966,  -2019914594, -259224484 },\n            { 1947159082,  348337307,   642805614 },\n            { -1446070735, 1785618212,  700173796 },\n            { 1393538054,  -2088566645, 1251524418 },\n            { 944922569,   -1811995986, 216845470 },\n            { 1962832564,  -1424506086, 1029882440 },\n            { -433088043,  -2113665584, -2046679536 },\n            { -1576777617, -1287854846, -1344807970 },\n            { 950394032,   1984279379,  -226124528 },\n            { -1041583715, -492055213,  1656633063 },\n            { -490817698,  246732876,   1299038184 },\n            { -862391466,  957730728,   221264496 },\n            { -1935021140, 303724395,   -821398300 },\n            { 91557415,    -1774759525, 1002104989 },\n            { -780359531,  -718692976,  1113535696 },\n            { -1130718958, -1498877062, -1060566892 },\n            { -1799387418, 1478445987,  944916082 },\n            { -9058087,    152606778,   1162369322 },\n            { 164441125,   1385605540,  1900197044 },\n            { -1234098309, -1445910357, -97806551 },\n            { 1253947311,  -821766400,  -1258292992 },\n            { -79118338,   -1910092307, -405986266 },\n            { 968449567,   1253108775,  1498540729 },\n            { 1400750703,  1220337442,  -1533639746 },\n            { 1347344118,  -281935526,  815433852 },\n            { -2003704793, 436205156,   -1676437188 },\n            { -1961852808, -523874615,  -1813418440 },\n            { 878837839,   -1868701447, 1409969367 },\n            { -2038080044, -1770800676, 1504106032 },\n            { -1486111579, 65494725,    -1928347399 },\n            { 1135170439,  521364432,   15078064 },\n            { 2034382463,  -526457601,  -2082511743 },\n            { -1475721727, 1315900806,  593342854 },\n            { 87161872,    -814950361,  1040901744 },\n            { 1313209991,  1487955095,  -410657375 },\n            { -27994647,   -972492489,  1726107151 },\n            { 1770453595,  -36729429,   866277833 },\n            { -1688591166, 410534424,   954037808 },\n            { -2015590373, 1135575051,  905730345 },\n            { 324065147,   1371704663,  1298004429 },\n            { -1238575258, -513014157,  1748575954 },\n            { -32752291,   -1569135336, 1638003128 },\n            { -1555702075, 38172972,    -1976867108 },\n            { -460506972,  -359479990,  -219114648 },\n            { -1199817785, 1474004314,  720922358 },\n            { -328362687,  449191196,   1344974876 },\n            { -208309907,  -1826777710, 1452810538 },\n            { 350455771,   -489555026,  -2002448422 },\n            { -2119358264, 523546543,   -2065721160 },\n            { 1087346967,  1434113035,  1269342205 },\n            { 1939560416,  1243470822,  1981649728 },\n            { 396853528,   -2089644969, 1765146408 },\n            { 1281061592,  569469206,   122582672 },\n            { 859092809,   -1420440508, 978049892 },\n            { -279770289,  72917604,    683698396 },\n            { -1304053159, -779403574,  1702467642 },\n            { -1990688248, -718222841,  -1585062344 },\n            { -52603769,   -1503085801, 1165571361 },\n            { 1245753036,  -1025348256, 1254516864 },\n            { -1386520545, 248350915,   1115533213 },\n            { 532980366,   2010211917,  -1630046026 },\n            { 1338953795,  1520448327,  230235029 },\n            { 2080303056,  968564157,   274969744 },\n            { 626590190,   -661544785,  282792370 },\n            { -1949178554, 924705671,   -588580374 },\n            { -1048143562, -1636933745, 910073642 },\n            { 41895608,    -1457688713, -743725176 },\n            { 453144272,   -731750349,  -156626064 },\n            { -774729684,  1527111594,  910561592 },\n            { 1011569113,  -685601511,  2101445425 },\n            { 935280451,   147834907,   -1983577071 },\n            { -597843501,  204666531,   -933967015 },\n            { -1921704221, 555558781,   -1399084585 },\n            { -959576851,  867665416,   -915458712 },\n            { 787722518,   642798922,   387808860 },\n            { -1629307258, 2007526283,  1555002562 },\n            { -1085287631, -623166181,  -1750535381 },\n            { 1980590579,  2276336,     -1958882096 },\n            { 2070741048,  -671712454,  -1818430288 },\n            { 738139520,   -1289686742, -1834985728 },\n            { 1562236288,  953495157,   -1904241792 },\n            { 699218357,   846086744,   573734968 },\n            { -1984578154, -798780620,  -2050382728 },\n            { -784504615,  -1872558350, -663932126 },\n            { 324003167,   1777056718,  1812723570 },\n            { -952921413,  1915197759,  2027937541 },\n            { 28172160,    -287507945,  81925248 },\n            { -576403281,  1993738,     -1315198250 },\n            { 416455167,   -626536899,  -42802237 },\n            { 81685975,    -1578285164, 1746477388 },\n            { -22771158,   577334996,   -757434680 },\n            { -702472285,  -827792948,  -1406437148 },\n            { 1881944759,  -297845202,  2034396898 },\n            { 2030248602,  1646609853,  602279346 },\n            { -927832020,  382451952,   1481025856 },\n            { 856053593,   -777994681,  473319343 },\n            { 1934882689,  -267807,     -208932511 },\n            { -1893073956, 455489553,   1200216476 },\n            { 550933251,   -340472059,  1143753743 },\n            { -385827588,  1868517054,  -1918563576 },\n            { 300231034,   1493113834,  -950155388 },\n            { 945282104,   -597853858,  1471239312 },\n            { 530058126,   667342144,   -109835904 },\n            { 939814327,   -597358042,  170159914 },\n            { -1148222618, -1952759377, -1741047622 },\n            { -1472728435, -985926300,  -1468354540 },\n            { 472760977,   -230421531,  -321960267 },\n            { 990761789,   103388076,   -530910212 },\n            { -1964553687, -759303699,  -1221246731 },\n            { 890732158,   -1141648553, -2083006766 },\n            { 275722549,   1602393543,  637344051 },\n            { 2076520514,  -436630227,  1466946970 },\n            { -420025009,  -1457241292, 274718988 },\n            { 2095424755,  -313462687,  546787347 },\n            { 1947173254,  1165999824,  775205088 },\n            { 1565998335,  -506709571,  271634243 },\n            { 2083796869,  -917278106,  715544830 },\n            { 1168725142,  -264885731,  598946046 },\n            { -1746581772, -1624470913, -1326597364 },\n            { -1978346574, -197464031,  -243652110 },\n            { 532352797,   747624764,   1826234316 },\n            { 740244055,   1854503197,  1554758363 },\n            { 541116114,   -28470683,   -736348966 },\n            { -1347703525, -1878762156, -425485348 },\n            { -370162034,  1816933569,  -331249394 },\n            { 393332201,   -459744410,  -1308938794 },\n            { 1536348378,  -318229337,  -402487754 },\n            { -1106629375, -1765435642, -1400003322 },\n            { -1746207502, 516635605,   2125871962 },\n            { -1791148478, 1598164719,  949533598 },\n            { -656005208,  1769411199,  537885784 },\n            { -1925169493, 1171455512,  2071562760 },\n            { 223415943,   1693287851,  816568109 },\n            { -1252923873, 543628326,   1439468698 },\n            { -806520518,  -689971525,  -417474210 },\n            { 1190084968,  -1352994527, 2012894824 },\n            { -1569254097, -463093685,  542209221 },\n            { -1192873859, -1199984534, -1816460350 },\n            { -1566054537, -647726020,  -1124867100 },\n            { 807077127,   826035948,   420791412 },\n            { -990414609,  -691194095,  -523411233 },\n            { -315947743,  1931954322,  -150915886 },\n            { -18997084,   -1840681863, 59153796 },\n            { 2020464223,  1580910840,  -311958520 },\n            { 1394086003,  -1420646100, 1087684292 },\n            { 1433670930,  -328193180,  -1329446648 },\n            { -335935504,  -1674867206, -1109725088 },\n            { -2076075063, -109378057,  1038826479 },\n            { 77844873,    -167940064,  1904943392 },\n            { 1566981913,  1119040543,  -1532119033 },\n            { 1610280426,  -138466595,  1312671490 },\n            { -1090518623, -143616945,  -10382161 },\n            { 639242657,   213841476,   -849648444 },\n            { -1478622136, -1585943310, -1553044464 },\n            { -863479547,  -755869869,  -416313441 },\n            { 1672197832,  -223892324,  1743372768 },\n            { -447392172,  -1077117627, -233355100 },\n            { 864029808,   -805013118,  -559464224 },\n            { 1936023067,  -1249075164, 584457164 },\n            { -777733851,  638895148,   452208220 },\n            { 1896592858,  268121249,   -1945393638 },\n            { 2049292969,  100585832,   -312095320 },\n            { 935758055,   665192743,   1893268017 },\n            { -201739745,  -908857471,  883602079 },\n            { 919259026,   -1558667219, 168842410 },\n            { -459812402,  -2018194767, -10708114 },\n            { 273437833,   1469012939,  -702891101 },\n            { -103259355,  -1568533293, -1746756737 },\n            { -1655626626, -75936112,   5303008 },\n            { -397858562,  1925411932,  -162016440 },\n            { 866399238,   -1643788910, -2016654996 },\n            { 518988326,   -1365643637, 1514987170 },\n            { -1285064764, 624418757,   1057940948 },\n            { 1761832989,  -1288552225, -2102400701 },\n            { -1743639866, 13354640,    -2108738720 },\n            { -1965988327, 545185225,   601538209 },\n            { 1838882606,  -1596040291, 639334710 },\n            { -2068315056, 1346928803,  -1177370896 },\n            { 1684337940,  -50237456,   -1644335424 },\n            { 1588683639,  1059032288,  839195680 },\n            { 930896811,   -797714373,  -724408215 },\n            { -1008282491, 531082371,   329980943 },\n            { 2132197538,  -204082577,  -1446991298 },\n            { -520046404,  1515872725,  -2052001684 },\n            { -817753652,  287862934,   -1403015800 },\n            { -984787479,  1953792081,  -1711231303 },\n            { 2124182786,  -1922269985, -1982929730 },\n            { 1456089833,  -1148337949, 1791478939 },\n            { -1661658552, -115766534,  9337424 },\n            { -540047249,  -1885805231, 2023585311 },\n            { -601519058,  1192127142,  -1192758828 },\n            { -2099875323, 1576935161,  1441234141 },\n            { -125492616,  -437751929,  1558656328 },\n            { -506655920,  1835576508,  1051594432 },\n            { 1884191488,  721732156,   -1214589952 },\n            { -219277929,  1174135866,  -214409162 },\n            { 476650715,   639446705,   1319849323 },\n            { 156516741,   204675647,   602055099 },\n            { -276321861,  1639121840,  -855449200 },\n            { 1003116846,  -2131550596, 1394644552 },\n            { -726842989,  -1936730178, 1606819866 },\n            { -1931114929, 1854619257,  233348439 },\n            { 1379073152,  -456308956,  -441253376 },\n            { 75970777,    869204938,   1523869242 },\n            { 1418764007,  710016843,   29206189 },\n            { -1866960464, 1800479104,  -1416591360 },\n            { -759567398,  516139853,   -981168494 },\n            { 1252554159,  -1604631326, -584097666 },\n            { -15451713,   1087542727,  755175033 },\n            { 817179887,   -725238435,  -202848813 },\n            { 1914580275,  -1447226380, 197451164 },\n            { -1744588215, -1249038408, -1080521864 },\n            { 1495121451,  993398263,   -2073253251 },\n            { 1536415512,  -1623996277, -82277880 },\n            { -1043928228, -1296353161, 2066188228 },\n            { -64905405,   -1847680527, -1880485613 },\n            { -837828643,  -1558391951, -1555900531 },\n            { -1747715718, 1995919484,  -1185351912 },\n            { 126652169,   -1094581688, 1266175112 },\n            { 2062038975,  1512360809,  -1974513065 },\n            { 1777961641,  -1840115424, -1651995104 },\n            { 1870531623,  -1527897068, -1211076852 },\n            { -1354426541, -1323549843, 2094993239 },\n            { 1252003297,  -359874386,  71184110 },\n            { 784368396,   404539334,   1142140744 },\n            { 736168567,   932908588,   -1068675468 },\n            { 1666384436,  -82342657,   -2087738932 },\n            { -1415141548, 849862018,   -1730118488 },\n            { 870651555,   1714238581,  754152575 },\n            { -1094838395, 646307856,   915331152 },\n            { 335070478,   -825111125,  -1318726054 },\n            { 1549789635,  -2135820452, -589945068 },\n            { 1388657839,  1583384341,  -791788709 },\n            { 848573104,   -1935520766, -1214995104 },\n            { -1418537691, 133275700,   1130326916 },\n            { -1405577375, -1686413336, -1978585368 },\n            { -1149335912, 1205135586,  -846425552 },\n            { -1073873892, 633835445,   -1540693044 },\n            { 723140640,   1046766062,  -785138240 },\n            { -120857758,  1302378371,  -1587261146 },\n            { 1892794471,  117953259,   -1271849843 },\n            { -1303148241, -226300206,  166607502 },\n            { 1947325580,  544958789,   943699388 },\n            { -260184449,  -268791872,  1530122304 },\n            { 1403658570,  838382740,   1735608008 },\n            { -1711126252, 1154415990,  1566470968 },\n            { 205511257,   -1023241164, -421147116 },\n            { 1310563190,  -1804673563, 1952771726 },\n            { 422186003,   -1098257899, 262626191 },\n            { -414385861,  2138684082,  -763549434 },\n            { -963224741,  254420687,   923885717 },\n            { -513848440,  -1383187090, 1499585648 },\n            { 1079992250,  -694549768,  755511344 },\n            { -328881705,  -2126284022, -556273818 },\n            { 1407974346,  -681707079,  1272082170 },\n            { -1606253830, 1923547785,  229903306 },\n            { -553560105,  -1836099425, -1491044727 },\n            { 1913901663,  201368311,   272635817 },\n            { 1743217346,  1701050542,  -2114514980 },\n            { 766829972,   -885515111,  -1365678732 },\n            { -421921496,  -128977733,  1172180024 },\n            { 2119726605,  2001219941,  103223329 },\n            { 1270821572,  -2057572101, 1673168428 },\n            { -1174136027, 1072548020,  1012465156 },\n            { 1472013841,  -1579169834, 981731638 },\n            { -1463658398, -1518790091, -1652015030 },\n            { 1859162675,  712683521,   -576609741 },\n            { 1643170812,  1251087397,  628435820 },\n            { -1406204137, -601935925,  -882845635 },\n            { 1550074272,  -2128580420, -1974062720 },\n            { -1018281000, -879070794,  2105971600 },\n            { 963947889,   1637560042,  1346099018 },\n            { -1323543708, -479998392,  -2034373600 },\n            { -1281404082, 1074422339,  -1046304406 },\n            { 2072661855,  2130353955,  268098301 },\n            { -7496102,    1195478582,  -1601219844 },\n            { -481956537,  551923539,   -1654491387 },\n            { 775122104,   1216872581,  -2106098792 },\n            { 178010584,   -1379345177, -43134488 },\n            { 278909846,   2001198832,  -1746112352 },\n            { 1598170801,  -247808062,  -266772190 },\n            { 1581752173,  -592192461,  2055040695 },\n            { 560320054,   -246584910,  -694450292 },\n            { -1925114746, 165595187,   2117322418 },\n            { 1798672824,  -1990552639, 1220510648 },\n            { -849562689,  -1631621261, 1880461261 },\n            { -1086818861, 1289014475,  -697986479 },\n            { -746649714,  89030092,    -1557243096 },\n            { -1549442793, 82083152,    -1198353872 },\n            { -967066298,  -1735174051, -678047122 },\n            { 1402806506,  -2135894901, 645048078 },\n            { 1492291225,  -481287489,  -826755545 },\n            { -553297748,  -2052984125, 252781316 },\n            { 1714601821,  -1337171075, 1180985193 },\n            { 1560330012,  -1544887072, -1904990080 },\n            { -1405996141, 37813187,    1838049785 },\n            { -303870470,  -1853930176, 98427008 },\n            { 1353802148,  1516201556,  183984592 },\n            { -431536148,  -1954441781, 2035180580 },\n            { -462052116,  -711364159,  394887660 },\n            { 2010265765,  -1988843708, -292639020 },\n            { -1416121220, -378672681,  -461803484 },\n            { -1014541501, -951344670,  846570534 },\n            { 243024411,   -330177447,  -2103572637 },\n            { 1467405048,  689537823,   -1215269880 },\n            { -1912011378, 320185582,   -761781756 },\n            { 1953513964,  635053262,   -1164821528 },\n            { 906541081,   1327451228,  64001276 },\n            { 39307874,    2078024051,  1623100422 },\n            { -422263739,  541378294,   580253774 },\n            { 555546137,   -1040963122, -1316222690 },\n            { -2088522270, 1269935699,  -1826423738 },\n            { -1152155660, -1709727815, -2101607596 },\n            { -129534385,  -958590400,  1415717312 },\n            { 1012210652,  -1786410445, 1799693524 },\n            { -2053668616, 2103336183,  -896102584 },\n            { -1998759707, -847104325,  800734535 },\n            { -327828448,  -642791792,  -879062528 },\n            { -193003089,  1864047586,  1271001470 },\n            { 651629024,   -958395465,  -444438752 },\n            { 711237390,   1907216667,  1223505018 },\n            { -1322031762, -753116258,  1691268068 },\n            { -1027342099, -263835691,  2047756337 },\n            { -4087646,    -65621707,   420627338 },\n            { -1836650129, 43315659,    -1401490939 },\n            { 1059624374,  -1109652584, 1156560400 },\n            { 371816022,   -327065446,  -894996548 },\n            { -1380599475, 770783689,   -731012491 },\n            { -1108327256, 1349380755,  1799421048 },\n            { -1414072138, 650390942,   -1812103596 },\n            { 1319127695,  426815698,   1213167950 },\n            { -1750781839, -437581316,  -1576359876 },\n            { 420115329,   -1119985009, 860612367 },\n            { -375322895,  -1853267899, -1829093643 },\n            { -95614304,   -2104034893, 1827933664 },\n            { 295448483,   566710542,   1817325034 },\n            { 148791138,   -1108792146, 1773907100 },\n            { -391816658,  1640858645,  -52027962 },\n            { -1920298892, -1680168569, 1547092268 },\n            { 544035324,   -1955320974, -945564104 },\n            { -1782013733, -1783599564, -1351694724 },\n            { -662695431,  1224902155,  2077957043 },\n            { -1670825124, 527684139,   1722970228 },\n            { 924111002,   1010799949,  479354962 },\n            { -1461467106, 1751520568,  -1926087536 },\n            { -1341896671, 283900440,   -182321896 },\n            { -223561587,  488474513,   1780474589 },\n            { 665108254,   1439182074,  1294715724 },\n            { -951440000,  1287954215,  1150250624 },\n            { -1119906504, 164201887,   -1275345464 },\n            { -980788323,  -1046180290, -1545124346 },\n            { 701257773,   -564935288,  285243624 },\n            { -1198435844, -39178433,   -1758957308 },\n            { -270949098,  2054429179,  -1250164078 },\n            { -1599469814, 1731025357,  123477250 },\n            { 1229740527,  -1720334022, 2133067558 },\n            { 270585775,   640677141,   -728464293 },\n            { -1021191337, 804781815,   2000417521 },\n            { -1641306957, 1875598186,  948884254 },\n            { -957713773,  154102790,   -976112782 },\n            { 591845981,   165143312,   -1322705712 },\n            { 696059864,   -1317687980, 151751392 },\n            { -273469860,  1162059863,  1626489156 },\n            { -155861815,  1593028119,  -1318087665 },\n            { -1876144149, 2001384683,  -674457415 },\n            { -1624954050, -1403815610, -1028382476 },\n            { -1128446417, -1709916054, -592556170 },\n            { -290567098,  -415682996,  1393113288 },\n            { 470440163,   -1990179273, -1431885115 },\n            { -1771222161, -469103369,  290651161 },\n            { -524394698,  -1323426675, 429414590 },\n            { -383661500,  1703136438,  -936 },\n            { 1497602291,  -1217644330, -1552219358 },\n            { -1006938090, 1343599363,  609708610 },\n            { 567178722,   41971005,    -1419554598 },\n            { -1773198849, -368042722,  -904929566 },\n            { 943910766,   453569410,   423071708 },\n            { 1839865635,  433484027,   1705891665 },\n            { 249780359,   227647756,   -1395913388 },\n            { -1188872903, -443897476,  -298432356 },\n            { 1614923721,  -1629831100, 377082212 },\n            { -1023061687, -613879326,  -1528857742 },\n            { -757366819,  -1996616928, 255097504 },\n            { 1394659510,  -1568865151, 2074739638 },\n            { -279447009,  -16403715,   2037825187 },\n            { 1922143871,  -675135309,  917109453 },\n            { -270829695,  1802559921,  -298450639 },\n            { 1405434049,  -681923570,  566544014 },\n            { 192363438,   558841824,   951454272 },\n            { 592622208,   -476556583,  -364491136 },\n            { 79681138,    2019429518,  -833467588 },\n            { 939287974,   -1218987077, 2030600770 },\n            { -2144067752, 1344707062,  2145498768 },\n            { 910276939,   1575051957,  -1622034425 },\n            { 1957021588,  365380505,   1650197364 },\n            { 1182128182,  -1015845039, -1505045738 },\n            { 2139006499,  1359819161,  -213582357 },\n            { 933581731,   -436987365,  793403441 },\n            { 114041392,   823557446,   -1465726688 },\n            { 502050374,   -1604291248, -1612942368 },\n            { 2108448697,  605653753,   -2058103567 },\n            { -1266805873, 1955761365,  321288699 },\n            { 1531908722,  -496521952,  -382248896 },\n            { 448053039,   1630867818,  1933854838 },\n            { 192289513,   2005820570,  1137521706 },\n            { -1132930853, 476709663,   -69545083 },\n            { 1602245098,  1323029061,  1609959442 },\n            { -1323382704, 168627974,   1082483168 },\n            { -2100615644, 2143301076,  2096097744 },\n            { 890848662,   -36596695,   717020422 },\n            { -1025642484, 63919589,    1055897276 },\n            { 981838216,   -1995996226, 1068264176 },\n            { 1436295168,  -255668910,  -1314766848 },\n            { 1192116882,  -388919143,  640280898 },\n            { -873878864,  -1509049914, 1577268256 },\n            { -619269700,  122315233,   -1398640068 },\n            { -805349447,  1285142419,  1357225531 },\n            { -1696427032, -1078084346, 852082544 },\n            { -765274478,  1499240131,  159055158 },\n            { 380042071,   1056299762,  941927998 },\n            { 693157291,   -1780198036, -1336554716 },\n            { -830767759,  -336831362,  1627937182 },\n            { 312793043,   -1242043576, -66034600 },\n            { 1663555781,  1982960167,  543645699 },\n            { -768646183,  -1136608636, 557524452 },\n            { -1994950835, 492047104,   -530297088 },\n            { -2077480330, 2061089240,  -1443292784 },\n            { -2020847369, -625107222,  -1667927098 },\n            { 125472362,   -992039977,  -856004346 },\n            { -675351968,  488269827,   -1754015968 },\n            { -612107548,  1827075976,  -1912126176 },\n            { -567170078,  1018257244,  400859960 },\n            { -726641626,  -956500428,  -569009224 },\n            { 140061904,   -1302490898, -743659168 },\n            { -434221464,  -61093247,   505123432 },\n            { -1575153921, -1840112947, 7923763 },\n            { -632303875,  1363875007,  -824823101 },\n            { 232199817,   2009230237,  640029957 },\n            { -52905036,   1527847041,  767784372 },\n            { 1184901581,  1110144069,  2072717377 },\n            { 1595989120,  -1301731473, -1319691392 },\n            { -1809431111, 126992611,   -1627155701 },\n            { 1613738843,  -1485478565, 1481549913 },\n            { 2033644107,  -338449153,  894876853 },\n            { 1837330022,  -335529583,  1392917958 },\n            { 1323170644,  325986050,   -1457667416 },\n            { -278767532,  -1319960206, -1285576344 },\n            { -709856898,  1258665704,  973893168 },\n            { 2648841,     -852699069,  -1083204773 },\n            { -1177012335, -739460044,  1164297588 },\n            { -771168131,  -1497528254, -1810409414 },\n            { -2064343055, -604301532,  -1458559772 },\n            { -1948901914, -835323654,  1085145756 },\n            { 1999167082,  -1147490010, 731663804 },\n            { 1507833110,  -1463767336, 576044688 },\n            { -151432656,  456104074,   1375915488 },\n            { -2045102047, -859164675,  -297695331 },\n            { -1306203104, -2143706248, -1765339392 },\n            { -1731679422, -525704620,  1832613288 },\n            { -1464322740, -1778856413, 699560292 },\n            { -485793538,  -1254279449, -130016974 },\n            { 1936439734,  1547487294,  198519316 },\n            { -448435062,  805975662,   1176354636 },\n            { 1865931610,  -2070190673, 1211593862 },\n            { -428059031,  2055193385,  -2016226863 },\n            { -1641787023, -506403198,  1819074914 },\n            { -840173735,  -20962321,   1390221079 },\n            { 284809204,   1372080671,  827174540 },\n            { 256023919,   2076945541,  -613982549 },\n            { 1658901342,  -671538753,  1231737122 },\n            { -896016512,  -72050893,   -1760164224 },\n            { 257944758,   1185650204,  -993884184 },\n            { 1433584323,  -1673632876, -958135876 },\n            { 1356458444,  1636471911,  -691076844 },\n            { 1348081332,  -569127250,  -488880552 },\n            { 1641694658,  -1913170305, 115722046 },\n            { 1857301306,  -572803007,  -1485919814 },\n            { -764129843,  487635551,   -1350577901 },\n            { -882293493,  1445366115,  -246770111 },\n            { -1930377667, -476832553,  1713973563 },\n            { 1309570127,  1186132044,  843224948 },\n            { -834511942,  65317758,    -1214880884 },\n            { 1754575235,  1713505646,  -1665018550 },\n            { -277257009,  -1847500314, -13500166 },\n            { -1630378848, 874524105,   -1025433184 },\n            { 1509144492,  -1912527343, 1176418924 },\n            { -2047100293, 1284066940,  -393032300 },\n            { 1485674535,  -793790428,  -336695940 },\n            { -1937740689, 90449310,    -850724990 },\n            { -387644924,  123562330,   -1513465496 },\n            { 420208184,   49929294,    1762514192 },\n            { -116337803,  895405627,   -822156809 },\n            { -1009863135, 873211400,   185243400 },\n            { 608279523,   316203020,   -1727926620 },\n            { 1443320674,  360546618,   -276347340 },\n            { -1948642578, -1496759927, 1734381406 },\n            { 993052355,   -2048627434, 796793794 },\n            { -1494647886, 234986193,   -256307118 },\n            { 1805005188,  1069263018,  -1448640088 },\n            { 533887052,   854081697,   2119215052 },\n            { -75321728,   24039213,    1544694912 },\n            { 730665372,   1748998852,  -369274000 },\n            { 858007951,   -828119654,  -1527059706 },\n            { 1201338187,  928579018,   -982237138 },\n            { -742799060,  -1290293886, -398531496 },\n            { 1471233039,  120515575,   -2116765831 },\n            { 1292684887,  1047253278,  -867027662 },\n            { -1836379603, -1993477762, 1400393510 },\n            { -1665331524, 514014371,   -2054655564 },\n            { 848120895,   -2127392785, -1026596911 },\n            { 1037593877,  -302368550,  -106202142 },\n            { 454731319,   -1414140692, -810619212 },\n            { 1959195596,  1837964015,  1574720372 },\n            { -2097866077, 1317681187,  -1959506871 },\n            { -938773021,  169450888,   -1366435944 },\n            { -1492596686, -1323107209, 1925859134 },\n            { 603118104,   139736857,   -9276328 },\n            { -1010750602, 276091008,   -446850304 },\n            { -330664893,  67895670,    -1276519966 },\n            { 1305587947,  322640438,   -312725614 },\n            { -1378348797, 1307379369,  2060471547 },\n            { 442950455,   -1255548440, 1789298904 },\n            { -1345022055, 5900037,     -1961483011 },\n            { -632964685,  -1384337294, 208388534 },\n            { -296458733,  -1982591877, 705818401 },\n            { -1078801623, -1900833618, 1956956638 },\n            { 127323052,   -1409851605, -1562572316 },\n            { 609446111,   90237994,    -1996699498 },\n            { 31999346,    511356734,   -1307721828 },\n            { -1764301356, 1777597211,  -1203955364 },\n            { 207184169,   -820606990,  1329625026 },\n            { 1884720713,  950394770,   609991842 },\n            { 608594543,   386424821,   626724155 },\n            { 1128207234,  1381927917,  1320723802 },\n            { -355842528,  -968500489,  1870719200 },\n            { 2038329293,  1019901071,  1812682627 },\n            { -1577508869, -123072390,  661056926 },\n            { -371358013,  -164567853,  -1272201543 },\n            { -664416635,  -1011880981, -1779193065 },\n            { 756876749,   -1775882435, 241763545 },\n            { 1950290221,  -1192625674, 105137726 },\n            { -287225622,  -1909183246, 621225268 },\n            { -1266676560, 1536767766,  720037664 },\n            { 1156663214,  1346814058,  2067672588 },\n            { 545368510,   868856682,   1066279596 },\n            { 1829293709,  -1018081437, -1547541625 },\n            { -487540259,  1752506319,  1630080179 },\n            { -557938599,  -864196584,  -927600552 },\n            { 2131166862,  1503172650,  1163617100 },\n            { -670917624,  1266812338,  -756243056 },\n            { 1545842349,  -1003733243, 241892449 },\n            { -406658373,  -289003522,  -628526454 },\n            { 515995165,   -1849655715, 1730817673 },\n            { 932051451,   -555175327,  -99336677 },\n            { -460330076,  -1385299527, -798691964 },\n            { -1294027195, 1282590216,  2125008936 },\n            { 375294226,   2097331582,  -1445084964 },\n            { -1387415096, -1256649885, -393056168 },\n            { -606654039,  -249063551,  1601811753 },\n            { 1484731244,  1128505043,  735578628 },\n            { 888648376,   1457489118,  624679824 },\n            { -2088826346, -237267807,  2072776662 },\n            { 437267080,   427438343,   1533358520 },\n            { -1885472726, 1255688242,  35014708 },\n            { -1358530180, -1289649633, -663376380 },\n            { -687449263,  1207298680,  1979176952 },\n            { -537058957,  -188051832,  1741000472 },\n            { -2023244677, 1705116588,  -37874780 },\n            { -1770113168, 969398202,   1616074592 },\n            { 193221671,   -1614685127, -215007057 },\n            { -181318787,  -1060905386, 1581040126 },\n            { -379842847,  1371668731,  1153734299 },\n            { -1490195765, 1355309946,  445212094 },\n            { -1671113204, -1096984920, -1351581728 },\n            { -1385783266, 644344663,   49148978 },\n            { -1812455199, -42110834,   216116430 },\n            { 852006503,   1482970367,  -1889297255 },\n            { -1978003017, -1015210370, 306391570 },\n            { -1266269774, -41510013,   -140090346 },\n            { -834755708,  -835623416,  -346049504 },\n            { 1111536194,  1007227965,  -1817599558 },\n            { -1762284637, -869508377,  -100061675 },\n            { 790984186,   2134294121,  212397962 },\n            { -1397836841, 2079361726,  1026252690 },\n            { 1413950461,  -1627949062, -99708910 },\n            { -1018040177, 1040379909,  624804555 },\n            { -210162023,  673852331,   -1981820109 },\n            { 544986987,   -1623941059, 1524708479 },\n            { 2065790816,  -651473837,  1214919712 },\n            { -50521602,   -65230485,   -1279620310 },\n            { 1560420759,  217526653,   1328106939 },\n            { -1306982187, -1857170749, 2137093183 },\n            { -30527788,   1312631095,  -1041235060 },\n            { -1038572872, 1281045666,  1275877488 },\n            { -2089586206, -877571286,  -232852204 },\n            { 946394159,   -344335423,  -1547490193 },\n            { 230540263,   1698207978,  961427750 },\n            { -1948341433, 1270672295,  983558225 },\n            { 1804986313,  -1754572913, 2055522375 },\n            { -165379179,  -1009978273, 2125649995 },\n            { -1977565236, -1918857596, -1631187664 },\n            { 838823547,   -1714262165, -896499095 },\n            { 522828159,   326936478,   -1169795742 },\n            { 1733057450,  -1034512392, -1155132752 },\n            { 1494062040,  1260137584,  -1090924928 },\n            { 461948391,   1381831250,  1386521086 },\n            { 1997071262,  -710673187,  -430569626 },\n            { -1330452464, 1277957785,  -1650585200 },\n            { 1186986503,  914741998,   -1593350014 },\n            { -24527263,   -2090855755, -1373213547 },\n            { -921955935,  1868652516,  105607780 },\n            { 334845939,   1296234374,  -303202766 },\n            { -93067916,   1079422609,  -1231958348 },\n            { 602160262,   221160800,   1051529280 },\n            { -1720445336, -803113245,  -139735496 },\n            { 41085567,    -1656411210, 575736650 },\n            { 404620004,   1168645825,  211215844 },\n            { -1593921229, 1094766612,  -1012971524 },\n            { -438700872,  -1424743399, 521324024 },\n            { 1779788645,  -1765207982, -773867942 },\n            { 1132710204,  910560313,   -466706852 },\n            { -1119363571, -1546054650, -1928513458 },\n            { -1645774919, 1288205406,  1371916782 },\n            { -1627603109, 999768378,   159806878 },\n            { 1528109574,  -1618274422, -638021316 },\n            { -977360390,  -339416994,  125106636 },\n            { 1683643254,  -169353830,  -724966660 },\n            { -715161447,  736639872,   -267677824 },\n            { -349605391,  1828800407,  1123879975 },\n            { -454031962,  -1161689927, 358001910 },\n            { -1055652512, 1638583627,  -1964281056 },\n            { 1999809372,  -909914134,  -1342962152 },\n            { -1937594034, 352387095,   -1358219774 },\n            { 840753151,   -1873642584, -230652840 },\n            { -2093495093, -1746893052, 1753104428 },\n            { -1049994650, 485254516,   400766008 },\n            { -1690876780, 885540887,   551602508 },\n            { 242887354,   671720751,   -159270362 },\n            { 815387504,   -1952165144, -1797161600 },\n            { -52063364,   1862273996,  675797712 },\n            { 1969640668,  -725327755,  805783692 },\n            { 331149004,   -878978745,  -111860076 },\n            { 100691434,   1360657459,  1679965598 },\n            { 1465328615,  582573525,   -210056653 },\n            { 995551333,   721922662,   -1397315010 },\n            { 1264873713,  540736092,   -453119844 },\n            { 972692537,   -439640143,  -878187927 },\n            { -658666252,  -59100124,   1469145680 },\n            { -588969199,  -1222255120, -1849881360 },\n            { 578053672,   -1643524145, 1109455448 },\n            { -364244414,  -1602041275, 501415882 },\n            { 842793076,   -309713018,  302115000 },\n            { 442390247,   -1011599,    1175866359 },\n            { -1852753868, 800075893,   -240619580 },\n            { 1512919116,  1589045092,  456126896 },\n            { 728132924,   1991435128,  -963155936 },\n            { 19928207,    281938710,   -1508513462 },\n            { 1034967581,  1868168470,  2102093694 },\n            { -302363772,  1860894615,  1854080732 },\n            { 1129487270,  -500938579,  703717166 },\n            { 1460202457,  295989605,   -962341475 },\n            { -372331347,  -1783960849, -1918150781 },\n            { -1629673862, 143967997,   450454162 },\n            { -1291822577, -406167668,  -1636642508 },\n            { 1481647158,  -107792460,  -1591537672 },\n            { 1851716340,  1987124784,  -2013649472 },\n            { 2008185250,  -614007866,  -1646794420 },\n            { 1592441175,  -897639760,  -1336196656 },\n            { 689206589,   1777658287,  1993648563 },\n            { -1889380667, -702507905,  -2104299589 },\n            { 172141524,   -518449991,  -1152467916 },\n            { 1842633060,  -1614681754, -2007260712 },\n            { 1507092264,  878919894,   902839152 },\n            { 1985805622,  -94281868,   -779106696 },\n            { -1302340915, 637943727,   355271971 },\n            { 723568031,   1054808626,  1744420622 },\n            { -339590316,  -340292868,  143756976 },\n            { 1234742052,  -1757761765, 574597324 },\n            { -358067284,  -1371688939, -783213284 },\n            { -1823678091, 1202785610,  -795796270 },\n            { 1382388933,  625041609,   143699629 },\n            { 1449621933,  564440181,   1121900561 },\n            { -319841419,  -169984658,  -1351242426 },\n            { 1767400663,  -865781991,  1679755775 },\n            { 180107423,   -1129468871, -513380505 },\n            { -2007459149, 529212406,   -830725886 },\n            { 1452279697,  -858304049,  2007105343 },\n            { -63805254,   1827599038,  -1454010868 },\n            { -2109931858, -2108162277, 1732259418 },\n            { 1938894176,  1149960200,  -1079416064 },\n            { -1893276084, 2003380322,  -1442051816 },\n            { -746912872,  504505482,   1568755696 },\n            { 845011423,   956316052,   -1429955604 },\n            { 213590685,   167023495,   -1372362805 },\n            { 1048393558,  234773475,   822584130 },\n            { 1395389934,  1764732038,  -688872812 },\n            { 125804741,   -1390509927, 1136629181 },\n            { 1161572761,  -136530876,  -178732892 },\n            { 882832451,   272155942,   -1070083854 },\n            { 100296380,   -1388467989, 1460907668 },\n            { 1684102677,  -45120392,   195161560 },\n            { 648469576,   71738755,    -1358094120 },\n            { -983373557,  4958607,     -732860379 },\n            { -1501319160, -862615385,  1779701048 },\n            { 1434267946,  -527231152,  -240639200 },\n            { -1265138719, 1152438908,  843215612 },\n            { -1092281206, -1526908520, -138040336 },\n            { 798440283,   -1098781217, -376769723 },\n            { -772751743,  -732490861,  -46663917 },\n            { -797995681,  -1383180067, -426265853 },\n            { -793392895,  1105813327,  278488655 },\n            { 875523650,   -1363779481, 123346062 },\n            { 970141758,   -1584885109, 1981713834 },\n            { -1767860734, -1032363194, -1998992756 },\n            { 1874781331,  -1361060651, 206537295 },\n            { 2146150094,  -1340420020, 40428840 },\n            { -1675966810, -1792707885, -360339758 },\n            { 830060491,   -384150581,  532254457 },\n            { 19020233,    -1200112928, 811440608 },\n            { -1994569707, -1953175477, -938793433 },\n            { -1334576989, 503852762,   -125369138 },\n            { -374809085,  -2009612682, 1396805474 },\n            { 1553076239,  -1657665735, -253494185 },\n            { -1917200722, -558288809,  170300706 },\n            { -609475517,  1055392612,  -362613972 },\n            { 1356999092,  993211331,   1970315292 },\n            { -2065275851, -876223681,  592881675 },\n            { 1287923001,  -1852964841, -734177249 },\n            { 126996281,   1757898248,  -1838272568 },\n            { -1084266962, 704830243,   516969034 },\n            { -873418514,  1081074275,  -1854371830 },\n            { 771657941,   -1513471643, 1497839881 },\n            { -1855162533, -548746318,  -446814650 },\n            { 19500420,    76012921,    476764004 },\n            { 1959806804,  6207658,     1986772424 },\n            { 1375227304,  -2059795760, 1785358464 },\n            { 342749986,   -1112424669, 133208998 },\n            { -60924660,   932814752,   -2031051904 },\n            { 1313421154,  999766318,   -1622489700 },\n            { 1959248731,  1077074459,  1429411993 },\n            { 1606378029,  275531628,   -1275861508 },\n            { 1306786275,  -1320301342, -1968538010 },\n            { -1394620295, -1897290595, -171149771 },\n            { -972818021,  -1606281540, -61562924 },\n            { 2047585726,  1015455351,  1780287314 },\n            { 1446815079,  -246992510,  1023784270 },\n            { -1564835364, -1194178632, 1451842080 },\n            { -657517415,  -1904294818, 689746990 },\n            { 782866747,   459618174,   346822666 },\n            { -194215493,  823485748,   -460316420 },\n            { 1368179507,  -715167274,  -962062430 },\n            { -1761353333, 271154527,   -1093365355 },\n            { 1120363182,  -963963502,  -1723298500 },\n            { 958621254,   669992352,   1711983040 },\n            { -1190400756, -562656418,  396267112 },\n            { -1294437591, 126094674,   -950929374 },\n            { 456782662,   207909397,   1500389566 },\n            { -1331903436, 1280458978,  1679439336 },\n            { 1924208981,  -1089276848, -696848752 },\n            { 1787285041,  -99949697,   756989263 },\n            { 1305583572,  -330321088,  -912969472 },\n            { 2112330484,  2102423565,  1221434980 },\n            { 1640588260,  -1051225411, -484260012 },\n            { -1848334877, 1484412254,  1029130330 },\n            { 1398176079,  1163834014,  1706675394 },\n            { 1720220856,  -1399357021, 1078468904 },\n            { 2098643695,  -2138166965, -344628475 },\n            { -1116264315, 675918112,   1742079392 },\n            { 1368272208,  411718543,   -1807990864 },\n            { 430416632,   2038068254,  -642942704 },\n            { 2105765780,  -781333070,  -608102168 },\n            { 482860000,   2038833190,  946250560 },\n            { -1091733865, 1548904902,  1370204106 },\n            { 1916605167,  -1400364949, 81578469 },\n            { 1806294610,  1145222977,  -583921454 },\n            { 104434113,   -952673913,  -390939193 },\n            { 1943477636,  1694756812,  -1870282448 },\n            { 804049436,   712545386,   -693686376 },\n            { -1137490034, -36610727,   -809470370 },\n            { 2071215225,  1001923727,  -1469658217 },\n            { -1752528738, 1377799547,  1139845610 },\n            { -1501222205, 1148927343,  1184825741 },\n            { -380714992,  -1525750287, -439279856 },\n            { -232567970,  -710104262,  1216273740 },\n            { -1188831984, -962957693,  -2114519248 },\n            { 810614890,   1127211716,  1373103400 },\n            { 1036181891,  218686481,   1575069107 },\n            { 2127066952,  -1786733379, 1320153128 },\n            { 1330622247,  1861445864,  1130599256 },\n            { -1011862436, -1462472667, 462410060 },\n            { -732167432,  328623703,   -104607160 },\n            { 388372244,   781656062,   -916303400 },\n            { -879476011,  2102817665,  -1538696107 },\n            { -391636439,  -294547390,  874958482 },\n            { -1531379357, -424913547,  -1769504449 },\n            { 34755621,    848729623,   1956724051 },\n            { 1750228017,  1363700859,  1295972235 },\n            { 1868430372,  1863325504,  -1486469888 },\n            { -726099939,  -2089868624, -1901105680 },\n            { -1295681826, 1350184151,  437320818 },\n            { -622878731,  -174772267,  -1123610151 },\n            { -1263685660, -884916935,  309951940 },\n            { 1812547936,  -883177885,  729260064 },\n            { 1039483689,  206782784,   1974022976 },\n            { 492602367,   -59114014,   -1046050274 },\n            { -162645309,  1317251255,  -465241755 },\n            { -174205233,  -48099718,   -915033946 },\n            { 1330603251,  1206161930,  -1374124162 },\n            { -1775775792, -2025442870, -1659844064 },\n            { 875988578,   -951105219,  -1550977190 },\n            { -1861727765, -1207288584, 1015590824 },\n            { 103083181,   260129311,   -1448459533 },\n            { -49173232,   -202618872,  -1678120832 },\n            { 1630807331,  -774766146,  1653519098 },\n            { 1945996446,  -234883471,  -1486035522 },\n            { 1750653901,  1641655069,  715642169 },\n            { -2135930872, -1069730579, 465325928 },\n            { 820389550,   -861461724,  992557688 },\n            { -494693964,  1906081936,  1888220480 },\n            { 719569227,   346030039,   -865727747 },\n            { -663224282,  -1345844452, -321804760 },\n            { -1517421459, -1911310646, -980046846 },\n            { 848039261,   1858752658,  426684170 },\n            { 1736336932,  -1959870764, 1794330064 },\n            { 578365565,   647492108,   -1783689252 },\n            { -1670877681, 2006030595,  -1904262867 },\n            { -2046968403, -170137142,  2144935810 },\n            { 2001045979,  -1348799281, 1532834837 },\n            { -1511696318, -129340279,  -66317486 },\n            { -75457016,   -1263311983, 1351522952 },\n            { -2073148643, -1976562661, 1513517071 },\n            { -1838633229, -2116268235, 1769813327 },\n            { -708949168,  -1606947020, 1093073984 },\n            { -802218480,  1235953710,  1775888096 },\n            { -2079422201, 1321022081,  -1371444601 },\n            { 87422838,    -1678442838, 1339385948 },\n            { 961565349,   -190265602,  2112200118 },\n            { -1581837408, 352189190,   799561152 },\n            { 1066122797,  -1407295096, 982652136 },\n            { -1022029455, 1027828100,  -1198041276 },\n            { 844780771,   -330281039,  2073476595 },\n            { 2008247416,  254692667,   327817128 },\n            { 1827069234,  83576275,    -1544762826 },\n            { -532466827,  -899174834,  126235558 },\n            { -217751856,  -91719065,   -724023888 },\n            { -1740233956, -496560288,  1069067904 },\n            { 1199883551,  -1964662121, 1053352777 },\n            { 56764696,    845608519,   1397126568 },\n            { 1477297110,  -603479023,  -315025098 },\n            { -834956462,  1802621951,  -722053970 },\n            { 1710809568,  -1358557155, -2037410208 },\n            { 808218482,   1425446880,  -601243200 },\n            { -977285500,  1795865585,  -2104240572 },\n            { 462675621,   -729724397,  -1619380161 },\n            { 38560965,    -80606653,   1801982351 },\n            { 1692426657,  1494812994,  1728107650 },\n            { -1733689078, 925743648,   -289184448 },\n            { 421814501,   -1046796402, -862477818 },\n            { 834931970,   1497714428,  -327325192 },\n            { 1681159493,  -562650426,  -1768530594 },\n            { 891741633,   711747081,   -178980407 },\n            { -398248795,  1974102997,  -1250048951 },\n            { 1734770734,  -271020853,  623955066 },\n            { -1377401084, 387723642,   539237352 },\n            { 1126199201,  2047175916,  2032391276 },\n            { -2018588740, -293297601,  -1017635004 },\n            { 46478024,    -995139252,  1635834720 },\n            { 406337287,   579426723,   1470855285 },\n            { 766438090,   2134493276,  -1891356520 },\n            { -1859944507, -1251218279, 676853949 },\n            { -1698045779, 1182246828,  1872678716 },\n            { -1259711109, 1983433013,  -695646857 },\n            { -1871348484, -652053311,  1521314300 },\n            { -514756962,  -1999118940, 2069578552 },\n            { 713725347,   -1196157140, 568286468 },\n            { -1400283374, -1156826531, 1749654922 },\n            { -690080226,  1712748503,  1515939122 },\n            { 1881413357,  328147862,   162190814 },\n            { 1147553234,  1038889377,  -12117230 },\n            { -2095249025, 1246119416,  644509192 },\n            { -208162630,  945501997,   -356514126 },\n            { -1416740714, -1671691627, -1808481458 },\n            { -169085370,  -444890633,  526771082 },\n            { -913514499,  1610352153,  1544408501 },\n            { -1057602945, -1491525977, 332874457 },\n            { -78914234,   -119169217,  -1246237126 },\n            { 357463406,   -1281742167, 2117833118 },\n            { -1756631095, 740853895,   601242367 },\n            { 1293597886,  30316285,    2095569862 },\n            { -1337197332, 360192293,   -783377892 },\n            { 1197908896,  326681675,   324562912 },\n            { 1209894051,  650907964,   -1063392972 },\n            { 251706606,   968576058,   -1642367508 },\n            { -2034970054, 314930507,   -173008642 },\n            { 730221459,   -2071360511, -628772973 },\n            { -502991534,  1858153703,  -76188418 },\n            { -1009349335, 167625789,   -524810555 },\n            { 229885736,   1374247082,  -784334704 },\n            { -1063417117, -1655643643, 1973427311 },\n            { 934445085,   -2043571349, 2058957599 },\n            { 2118161580,  -1000520389, 863018916 },\n            { -1131699854, -969305425,  -1545051922 },\n            { 1165281936,  -1353246683, 1137624784 },\n            { 1409234770,  1926552158,  -2135284708 },\n            { 1131042907,  -302379973,  226795769 },\n            { 949139221,   -1951565895, 894570797 },\n            { -1946335361, 1897238594,  2079252158 },\n            { -79036990,   593688459,   2135357014 },\n            { 1812400954,  1146999705,  -628587606 },\n            { 1410544342,  266734494,   -313748972 },\n            { -168154376,  335096530,   92008304 },\n            { 398955331,   -18574480,   -1567889840 },\n            { -509366539,  -1733908093, 1881162847 },\n            { -1707120453, 320928807,   -484374403 },\n            { 394539814,   366216542,   -1325234700 },\n            { -1239961299, -1379193919, 709763565 },\n            { 988595289,   734402175,   1680543271 },\n            { -794019185,  1705012111,  -1861322015 },\n            { 1244261274,  -296187643,  181863426 },\n            { -810469666,  2147157475,  -1914700582 },\n            { -1412854823, -603024413,  820157547 },\n            { -1291613086, -1924035832, -363357936 },\n            { 1370387913,  1920452855,  1573367023 },\n            { -2131270090, 1311830386,  -209430004 },\n            { -1200695798, -2121706916, 1572741016 },\n            { 1373686023,  1857815862,  1283164794 },\n            { -1851203191, 1757229308,  917287644 },\n            { 1896493083,  1483594427,  48701881 },\n            { -1664392153, 1271812200,  1179574232 },\n            { -368478571,  -1417398095, -1907387131 },\n            { 965088664,   1392608235,  -927294840 },\n            { 564367607,   1850879298,  -37434706 },\n            { 1676662925,  -782712613,  -300941153 },\n            { -1116480793, -1475722616, -1459496776 },\n            { 984565781,   -485545421,  1257885231 },\n            { -1785463499, -1584200130, 1815449814 },\n            { -1118554304, -980303522,  -1915516544 },\n            { 1942859804,  284154041,   1717560380 },\n            { -757659899,  -384708523,  -1150393175 },\n            { 1018158535,  -571263208,  -939029592 },\n            { -1206061467, -133472021,  1297288887 },\n            { -864753385,  1217328314,  -1109571914 },\n            { -223424305,  -873769625,  -2102411703 },\n            { 1547705507,  90839024,    1227587024 },\n            { -1363042618, 1420162643,  83279410 },\n            { -132872705,  1537953267,  -836901875 },\n            { -813220672,  -993523309,  1640669760 },\n            { 994558273,   -1704842728, 1766901784 },\n            { -675873711,  429508280,   1209335864 },\n            { -1000900415, -68639679,   -1921430271 },\n            { 2090644361,  680279190,   1423003206 },\n            { 842046751,   -1791391081, -700030135 },\n            { 570351257,   1570700307,  1618420059 },\n            { -530343543,  -2049975691, -1324690019 },\n            { 2131933116,  49617985,    -1631242564 },\n            { 1571883069,  -1116851033, -1486961717 },\n            { 1052731731,  1303243695,  -1096863555 },\n            { -452023475,  1198317779,  1081512055 },\n            { -304103854,  679928571,   793771622 },\n            { 486321569,   -1038085065, -1376132713 },\n            { -79432485,   1058122924,  -1581865180 },\n            { 2013229122,  332361620,   -860388312 },\n            { -718563545,  -1381870018, 1191425394 },\n            { -1282250795, 1490217065,  -1176527267 },\n            { 1493794477,  -809042307,  -1038057351 },\n            { -718483868,  -1359441564, -1891155184 },\n            { 1122133372,  1650169110,  261743784 },\n            { 1597582692,  -593889771,  -770518732 },\n            { 599304684,   -731807894,  959344568 },\n            { 1181549555,  -989921820,  1830333292 },\n            { -1736562571, -1947453787, -1196113559 },\n            { -218231456,  1125616197,  -984655136 },\n            { 523194334,   -1074711793, 313321474 },\n            { -1794708496, -294370263,  -1115190928 },\n            { 2085147545,  -1780827999, -1087567047 },\n            { -1874510881, -1074115401, -2012610455 },\n            { 1241227373,  -2019403557, -916343489 },\n        };\n    //</editor-fold>\n\n    for (auto &example : examples)\n    {\n        EXPECT_EQ(aeron_mul_wrap_i32(example[0], example[1]), example[2]);\n    }\n}\n\n"
  },
  {
    "path": "aeron-client/src/test/c/util/aeron_netutil_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"util/aeron_error.h\"\n#include \"util/aeron_netutil.h\"\n}\n\nclass NetutilTest : public testing::Test\n{\npublic:\n    NetutilTest() = default;\n};\n\nTEST_F(NetutilTest, shouldGetSocketBufferLengths)\n{\n    size_t default_so_rcvbuf = 0;\n    size_t default_so_sndbuf = 0;\n\n    ASSERT_GE(aeron_netutil_get_so_buf_lengths(\n        &default_so_rcvbuf, &default_so_sndbuf), 0) << aeron_errmsg();\n\n    EXPECT_NE(0U, default_so_rcvbuf);\n    EXPECT_NE(0U, default_so_sndbuf);\n}\n\nTEST_F(NetutilTest, shouldConvertPrefixLengthToMask)\n{\n    EXPECT_EQ(0, aeron_ipv4_netmask_from_prefixlen(0));\n    EXPECT_EQ(0x00000080, aeron_ipv4_netmask_from_prefixlen(1));\n    EXPECT_EQ(0x000000C0, aeron_ipv4_netmask_from_prefixlen(2));\n    EXPECT_EQ(0x000000E0, aeron_ipv4_netmask_from_prefixlen(3));\n    EXPECT_EQ(0x000000F0, aeron_ipv4_netmask_from_prefixlen(4));\n    EXPECT_EQ(0x000000F8, aeron_ipv4_netmask_from_prefixlen(5));\n    EXPECT_EQ(0x000000FC, aeron_ipv4_netmask_from_prefixlen(6));\n    EXPECT_EQ(0x000000FE, aeron_ipv4_netmask_from_prefixlen(7));\n    EXPECT_EQ(0x000000FF, aeron_ipv4_netmask_from_prefixlen(8));\n    EXPECT_EQ(0x000080FF, aeron_ipv4_netmask_from_prefixlen(9));\n    EXPECT_EQ(0x0000C0FF, aeron_ipv4_netmask_from_prefixlen(10));\n    EXPECT_EQ(0x0000E0FF, aeron_ipv4_netmask_from_prefixlen(11));\n    EXPECT_EQ(0x0000F0FF, aeron_ipv4_netmask_from_prefixlen(12));\n    EXPECT_EQ(0x0000F8FF, aeron_ipv4_netmask_from_prefixlen(13));\n    EXPECT_EQ(0x0000FCFF, aeron_ipv4_netmask_from_prefixlen(14));\n    EXPECT_EQ(0x0000FEFF, aeron_ipv4_netmask_from_prefixlen(15));\n    EXPECT_EQ(0x0000FFFF, aeron_ipv4_netmask_from_prefixlen(16));\n    EXPECT_EQ(0x0080FFFF, aeron_ipv4_netmask_from_prefixlen(17));\n    EXPECT_EQ(0x00C0FFFF, aeron_ipv4_netmask_from_prefixlen(18));\n    EXPECT_EQ(0x00E0FFFF, aeron_ipv4_netmask_from_prefixlen(19));\n    EXPECT_EQ(0x00F0FFFF, aeron_ipv4_netmask_from_prefixlen(20));\n    EXPECT_EQ(0x00F8FFFF, aeron_ipv4_netmask_from_prefixlen(21));\n    EXPECT_EQ(0x00FCFFFF, aeron_ipv4_netmask_from_prefixlen(22));\n    EXPECT_EQ(0x00FEFFFF, aeron_ipv4_netmask_from_prefixlen(23));\n    EXPECT_EQ(0x00FFFFFF, aeron_ipv4_netmask_from_prefixlen(24));\n    EXPECT_EQ(0x80FFFFFF, aeron_ipv4_netmask_from_prefixlen(25));\n    EXPECT_EQ(0xC0FFFFFF, aeron_ipv4_netmask_from_prefixlen(26));\n    EXPECT_EQ(0xE0FFFFFF, aeron_ipv4_netmask_from_prefixlen(27));\n    EXPECT_EQ(0xF0FFFFFF, aeron_ipv4_netmask_from_prefixlen(28));\n    EXPECT_EQ(0xF8FFFFFF, aeron_ipv4_netmask_from_prefixlen(29));\n    EXPECT_EQ(0xFEFFFFFF, aeron_ipv4_netmask_from_prefixlen(31));\n    EXPECT_EQ(0xFFFFFFFF, aeron_ipv4_netmask_from_prefixlen(32));\n}\n\nTEST_F(NetutilTest, shouldParseValidNamedInterfaces)\n{\n    aeron_named_interface_t named_interface;\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{eth0}\", &named_interface));\n    ASSERT_STREQ(\"eth0\", named_interface.name);\n    ASSERT_EQ(0, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{eth0}:0\", &named_interface));\n    ASSERT_STREQ(\"eth0\", named_interface.name);\n    ASSERT_EQ(0, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{eth0}:1234\", &named_interface));\n    ASSERT_STREQ(\"eth0\", named_interface.name);\n    ASSERT_EQ(1234, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{eth0:0}:0\", &named_interface));\n    ASSERT_STREQ(\"eth0:0\", named_interface.name);\n    ASSERT_EQ(0, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{eth0:1}:2000\", &named_interface));\n    ASSERT_STREQ(\"eth0:1\", named_interface.name);\n    ASSERT_EQ(2000, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{eth0.100}:80\", &named_interface));\n    ASSERT_STREQ(\"eth0.100\", named_interface.name);\n    ASSERT_EQ(80, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{x}:5678\", &named_interface));\n    ASSERT_STREQ(\"x\", named_interface.name);\n    ASSERT_EQ(5678, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{lo}:65535\", &named_interface));\n    ASSERT_STREQ(\"lo\", named_interface.name);\n    ASSERT_EQ(65535, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{Ethernet 1}\", &named_interface));\n    ASSERT_STREQ(\"Ethernet 1\", named_interface.name);\n    ASSERT_EQ(0, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{ethernet_32768}\", &named_interface));\n    ASSERT_STREQ(\"ethernet_32768\", named_interface.name);\n    ASSERT_EQ(0, named_interface.port);\n\n    ASSERT_EQ(0, aeron_parse_named_interface(\"{foo{bar-1234}}:9999\", &named_interface));\n    ASSERT_STREQ(\"foo{bar-1234}\", named_interface.name);\n    ASSERT_EQ(9999, named_interface.port);\n\n    auto long_name = std::string(IF_NAMESIZE - 1, 'x');\n    auto input = \"{\" + long_name + \"}:10000\";\n    ASSERT_EQ(0, aeron_parse_named_interface(input.c_str(), &named_interface));\n    ASSERT_STREQ(long_name.c_str(), named_interface.name);\n    ASSERT_EQ(10000, named_interface.port);\n}\n\nTEST_F(NetutilTest, shouldReturnErrorIfParsedNamedInterfaceIsInvalid)\n{\n    aeron_named_interface_t named_interface;\n\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\" \", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"eth0\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"eth0:0\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"{eth0\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"{eth0}:\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"{eth0}0\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"{eth0}:-1000\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"{eth0};1000\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"{eth0}:1000:2000\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"{eth0}:65536\", &named_interface));\n    ASSERT_EQ(-1, aeron_parse_named_interface(\"{}\", &named_interface));\n\n    auto too_long_name = std::string(IF_NAMESIZE, 'x');\n    auto input = \"{\" + too_long_name + \"}\";\n    ASSERT_EQ(-1, aeron_parse_named_interface(input.c_str(), &named_interface));\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/util/aeron_strutil_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include <stdint.h>\n#include \"aeron_alloc.h\"\n#include \"util/aeron_strutil.h\"\n}\n#undef max\n\nclass StrUtilTest : public testing::Test\n{\npublic:\n    StrUtilTest() = default;\n};\n\nTEST_F(StrUtilTest, shouldHandleSingleValue)\n{\n    const int max_tokens = 10;\n    char *tokens[max_tokens];\n    char input[] = \"single_token\";\n\n    int num_tokens = aeron_tokenise(input, ',', max_tokens, tokens);\n\n    EXPECT_EQ(num_tokens, 1);\n    EXPECT_STREQ(tokens[0], \"single_token\");\n}\n\nTEST_F(StrUtilTest, shouldHandleMultipleValues)\n{\n    const int max_tokens = 10;\n    char *tokens[max_tokens];\n    char input[] = \"token_a,token_b,token_c\";\n\n    int num_tokens = aeron_tokenise(input, ',', max_tokens, tokens);\n\n    EXPECT_EQ(num_tokens, 3);\n    EXPECT_STREQ(tokens[0], \"token_c\");\n    EXPECT_STREQ(tokens[1], \"token_b\");\n    EXPECT_STREQ(tokens[2], \"token_a\");\n}\n\nTEST_F(StrUtilTest, shouldHandleMoreThanSpecifiedTokens)\n{\n    const int max_tokens = 2;\n    char *tokens[max_tokens];\n    char input[] = \"token_a,token_b,token_c\";\n\n    int num_tokens = aeron_tokenise(input, ',', max_tokens, tokens);\n\n    EXPECT_EQ(num_tokens, -ERANGE);\n    EXPECT_STREQ(tokens[0], \"token_c\");\n    EXPECT_STREQ(tokens[1], \"token_b\");\n}\n\nTEST_F(StrUtilTest, shouldFormatNumericStrings)\n{\n    char buffer[AERON_FORMAT_NUMBER_TO_LOCALE_STR_LEN];\n\n    EXPECT_STREQ(\"999\", aeron_format_number_to_locale(999, buffer, sizeof(buffer)));\n    EXPECT_STREQ(\"-999\", aeron_format_number_to_locale(-999, buffer, sizeof(buffer)));\n\n    if (strcmp(localeconv()->thousands_sep, \".\") == 0)\n    {\n        EXPECT_STREQ(\"999.999\", aeron_format_number_to_locale(999999, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"-999.999\", aeron_format_number_to_locale(-999999, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"999.999.999\", aeron_format_number_to_locale(999999999, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"-999.999.999\", aeron_format_number_to_locale(-999999999, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"10.000.000.000\", aeron_format_number_to_locale(10000000000, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"-999.9\", aeron_format_number_to_locale(-999999999, buffer, 7));\n    }\n    else\n    {\n        EXPECT_STREQ(\"999,999\", aeron_format_number_to_locale(999999, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"-999,999\", aeron_format_number_to_locale(-999999, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"999,999,999\", aeron_format_number_to_locale(999999999, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"-999,999,999\", aeron_format_number_to_locale(-999999999, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"10,000,000,000\", aeron_format_number_to_locale(10000000000, buffer, sizeof(buffer)));\n        EXPECT_STREQ(\"-999,9\", aeron_format_number_to_locale(-999999999, buffer, 7));\n    }\n\n    EXPECT_LT(\n        strlen(aeron_format_number_to_locale(INT64_MIN, buffer, sizeof(buffer))),\n        (size_t)AERON_FORMAT_NUMBER_TO_LOCALE_STR_LEN);\n}\n\nTEST_F(StrUtilTest, shouldHandleConsecutiveDelimeters)\n{\n    const int max_tokens = 10;\n    char *tokens[max_tokens];\n    char input[] = \",,token_a,,,,token_b,,token_c\";\n\n    int num_tokens = aeron_tokenise(input, ',', max_tokens, tokens);\n\n    EXPECT_EQ(num_tokens, 3);\n    EXPECT_STREQ(tokens[0], \"token_c\");\n    EXPECT_STREQ(tokens[1], \"token_b\");\n    EXPECT_STREQ(tokens[2], \"token_a\");\n}\n\nTEST_F(StrUtilTest, shouldHandleMaxRangeWithConsecutiveDelimeters)\n{\n    const int max_tokens = 3;\n    char *tokens[max_tokens];\n    char input[] = \",,token_a,,,,token_b,,token_c\";\n\n    int num_tokens = aeron_tokenise(input, ',', max_tokens, tokens);\n\n    EXPECT_EQ(num_tokens, 3);\n    EXPECT_STREQ(tokens[0], \"token_c\");\n    EXPECT_STREQ(tokens[1], \"token_b\");\n    EXPECT_STREQ(tokens[2], \"token_a\");\n}\n\nTEST_F(StrUtilTest, shouldHandleTrailingTokens)\n{\n    const int max_tokens = 6;\n    char *tokens[max_tokens];\n    char input[] = \"token_a,token_b,token_c,,,,\";\n\n    int num_tokens = aeron_tokenise(input, ',', max_tokens, tokens);\n\n    EXPECT_EQ(num_tokens, 3);\n    EXPECT_STREQ(tokens[0], \"token_c\");\n    EXPECT_STREQ(tokens[1], \"token_b\");\n    EXPECT_STREQ(tokens[2], \"token_a\");\n}\n\nTEST_F(StrUtilTest, shouldHandleNull)\n{\n    const int max_tokens = 3;\n    char *tokens[max_tokens];\n\n    int num_tokens = aeron_tokenise(nullptr, ',', max_tokens, tokens);\n\n    EXPECT_EQ(num_tokens, -EINVAL);\n}\n\nTEST_F(StrUtilTest, checkStringLength)\n{\n    size_t length_initial_value = 1;\n    const char str1[] = {'h', 'e', 'l', 'l', 'o', '\\0'};\n    EXPECT_FALSE(aeron_str_length(str1, 5, nullptr));\n    EXPECT_TRUE(aeron_str_length(str1, 6, nullptr));\n\n    size_t length = length_initial_value;\n    EXPECT_FALSE(aeron_str_length(str1, 5, &length));\n    EXPECT_EQ(length_initial_value, length);\n\n    length = length_initial_value;\n    EXPECT_TRUE(aeron_str_length(str1, 6, &length));\n    EXPECT_EQ(5U, length);\n}\n\nTEST_F(StrUtilTest, checkStringLengthEmptyAndNull)\n{\n    size_t length_initial_value = 1;\n    const char* str1 = \"\";\n    EXPECT_TRUE(aeron_str_length(str1, 5, nullptr));\n    EXPECT_TRUE(aeron_str_length(nullptr, 5, nullptr));\n\n    size_t length = length_initial_value;\n    EXPECT_TRUE(aeron_str_length(str1, 5, &length));\n    EXPECT_EQ(0U, length);\n\n    length = length_initial_value;\n    EXPECT_TRUE(aeron_str_length(nullptr, 5, &length));\n    EXPECT_EQ(length_initial_value, length);\n}\n\nTEST_F(StrUtilTest, shouldCountNumberOfDigitsIntValue)\n{\n    EXPECT_EQ(1, aeron_digit_count(0));\n    EXPECT_EQ(1, aeron_digit_count(1));\n    EXPECT_EQ(1, aeron_digit_count(9));\n\n    EXPECT_EQ(2, aeron_digit_count(10));\n    EXPECT_EQ(2, aeron_digit_count(99));\n\n    EXPECT_EQ(3, aeron_digit_count(100));\n    EXPECT_EQ(3, aeron_digit_count(999));\n\n    EXPECT_EQ(4, aeron_digit_count(1000));\n    EXPECT_EQ(4, aeron_digit_count(9999));\n\n    EXPECT_EQ(5, aeron_digit_count(10000));\n    EXPECT_EQ(5, aeron_digit_count(99999));\n\n    EXPECT_EQ(6, aeron_digit_count(100000));\n    EXPECT_EQ(6, aeron_digit_count(999999));\n\n    EXPECT_EQ(7, aeron_digit_count(1000000));\n    EXPECT_EQ(7, aeron_digit_count(9999999));\n\n    EXPECT_EQ(8, aeron_digit_count(10000000));\n    EXPECT_EQ(8, aeron_digit_count(99999999));\n\n    EXPECT_EQ(9, aeron_digit_count(100000000));\n    EXPECT_EQ(9, aeron_digit_count(999999999));\n\n    EXPECT_EQ(10, aeron_digit_count(1000000000));\n    EXPECT_EQ(10, aeron_digit_count(UINT32_MAX));\n\n    EXPECT_EQ(10, aeron_digit_count(UINT32_MAX - 3));\n    EXPECT_EQ(3, aeron_digit_count(UINT8_MAX));\n    EXPECT_EQ(5, aeron_digit_count(UINT16_MAX));\n}\n"
  },
  {
    "path": "aeron-client/src/test/c/util/aeron_symbol_table_test.cpp",
    "content": "/*\n * Copyright 2014-2021 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"util/aeron_symbol_table.h\"\n}\n#undef max\n\nclass SymbolTableTest : public testing::Test\n{\npublic:\n    SymbolTableTest() = default;\n};\n\nvoid foo_function()\n{\n\n}\n\nvoid bar_function()\n{\n\n}\n\nconst char *foo_object = \"hello world\";\nconst char *bar_object = \"hello fairyland\";\n\naeron_symbol_table_func_t test_function_table[]\n    {\n        { \"foo\", \"foo_function\", foo_function },\n        { \"bar\", \"bar_function\", bar_function },\n    };\nstatic const size_t test_func_table_length = sizeof(test_function_table) / sizeof(aeron_symbol_table_func_t);\n\naeron_symbol_table_obj_t test_obj_table[]\n    {\n        { \"foo\", \"foo_object\", (void *)foo_object },\n        { \"bar\", \"bar_object\", (void *)bar_object },\n    };\n\nstatic const size_t test_obj_table_length = sizeof(test_obj_table) / sizeof(aeron_symbol_table_obj_t);\n\nTEST_F(SymbolTableTest, shouldFindObjects)\n{\n    EXPECT_EQ(foo_object, aeron_symbol_table_obj_load(test_obj_table, test_obj_table_length, \"foo\", \"object table\"));\n    EXPECT_EQ(foo_object, aeron_symbol_table_obj_load(\n        test_obj_table, test_obj_table_length, \"foo_object\", \"object table\"));\n    EXPECT_EQ(bar_object, aeron_symbol_table_obj_load(test_obj_table, test_obj_table_length, \"bar\", \"object table\"));\n    EXPECT_EQ(bar_object, aeron_symbol_table_obj_load(\n        test_obj_table, test_obj_table_length, \"bar_object\", \"object table\"));\n    EXPECT_EQ(nullptr, aeron_symbol_table_obj_load(test_obj_table, test_obj_table_length, \"baz\", \"object table\"));\n}\n\nTEST_F(SymbolTableTest, shouldFindFunctionPointers)\n{\n    EXPECT_EQ(foo_function, aeron_symbol_table_func_load(\n        test_function_table, test_func_table_length, \"foo\", \"function table\"));\n    EXPECT_EQ(foo_function, aeron_symbol_table_func_load(\n        test_function_table, test_func_table_length, \"foo_function\", \"function table\"));\n    EXPECT_EQ(bar_function, aeron_symbol_table_func_load(\n        test_function_table, test_func_table_length, \"bar\", \"function table\"));\n    EXPECT_EQ(bar_function, aeron_symbol_table_func_load(\n        test_function_table, test_func_table_length, \"bar_function\", \"function table\"));\n    EXPECT_EQ(nullptr, aeron_symbol_table_func_load(\n        test_function_table, test_func_table_length, \"baz\", \"function table\"));\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nset(TEST_HEADERS\n    EmbeddedMediaDriver.h\n    TestUtil.h)\n\ninclude_directories(${AERON_CLIENT_WRAPPER_SOURCE_PATH})\n\nfunction(aeron_client_native_test name file)\n    add_executable(${name} ${file} ${TEST_HEADERS})\n    add_dependencies(${name} gmock)\n    target_link_libraries(${name} aeron_driver gmock_main ${CMAKE_THREAD_LIBS_INIT})\n    target_compile_definitions(${name} PUBLIC \"_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING\")\n    add_test(NAME ${name} COMMAND ${name})\nendfunction()\n\nfunction(aeron_client_wrapper_test name file)\n    set(wrapper_name \"${name}W\")\n    add_executable(${wrapper_name} ${file} ${TEST_HEADERS})\n    add_dependencies(${wrapper_name} gmock)\n    target_link_libraries(${wrapper_name} aeron_client_wrapper aeron_driver gmock_main ${CMAKE_THREAD_LIBS_INIT})\n    target_compile_definitions(${wrapper_name} PUBLIC \"_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING\")\n    add_test(NAME ${wrapper_name} COMMAND ${wrapper_name})\nendfunction()\n\nif (AERON_UNIT_TESTS)\n    aeron_client_wrapper_test(systemTest SystemTest.cpp)\n    aeron_client_wrapper_test(wrapperSystemTest WrapperSystemTest.cpp)\n    aeron_client_native_test(systemTest SystemTest.cpp)\n\n    aeron_client_wrapper_test(pubSubTest PubSubTest.cpp)\n    aeron_client_native_test(pubSubTest PubSubTest.cpp)\n\n    aeron_client_wrapper_test(countersTest CountersTest.cpp)\n    aeron_client_native_test(countersTest CountersTest.cpp)\n\n    aeron_client_wrapper_test(imagePollTest ImageTest.cpp)\n    aeron_client_native_test(imagePollTest ImageTest.cpp)\n\n    aeron_client_wrapper_test(localAddresses LocalAddressesTest.cpp)\n    aeron_client_native_test(localAddresses LocalAddressesTest.cpp)\n\n    aeron_client_wrapper_test(multiDestinationCast MultiDestinationTest.cpp)\n    aeron_client_native_test(multiDestinationCast MultiDestinationTest.cpp)\n\n    aeron_client_wrapper_test(multiDestinationByIdTest MultiDestinationByIdTest.cpp)\n\n    aeron_client_wrapper_test(livenessTimeoutTest LivenessTimeoutTest.cpp)\n    aeron_client_native_test(livenessTimeoutTest LivenessTimeoutTest.cpp)\n\n    aeron_client_wrapper_test(responseChannelsTest ResponseChannelsTest.cpp)\n    aeron_client_native_test(responseChannelsTest ResponseChannelsTest.cpp)\n\n    aeron_client_wrapper_test(exceptionsTest ExceptionsTest.cpp)\n    aeron_client_wrapper_test(channelUriStringBuilderTest ChannelUriStringBuilderTest.cpp)\n\n    aeron_client_wrapper_test(fragmentAssemblerTest FragmentAssemblerTest.cpp)\n    aeron_client_wrapper_test(imageFragmentAssemblerTest ImageFragmentAssemblerTest.cpp)\n    aeron_client_wrapper_test(controlledFragmentAssemblerTest ControlledFragmentAssemblerTest.cpp)\n    aeron_client_wrapper_test(imageControlledFragmentAssemblerTest ImageControlledFragmentAssemblerTest.cpp)\n    aeron_client_wrapper_test(rejectImageTest RejectImageTest.cpp)\n    aeron_client_wrapper_test(publicationRevokeTest PublicationRevokeTest.cpp)\n\nendif ()"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ChannelUriStringBuilderTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"gtest/gtest.h\"\n\n#include \"ChannelUriStringBuilder.h\"\n\nusing namespace aeron;\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateBasicIpcChannel)\n{\n    ChannelUriStringBuilder builder;\n\n    builder.media(IPC_MEDIA);\n\n    ASSERT_EQ(builder.build(), \"aeron:ipc\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateBasicUdpChannel)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\");\n\n    ASSERT_EQ(builder.build(), \"aeron:udp?endpoint=localhost:9999\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateBasicUdpChannelSpy)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .prefix(SPY_QUALIFIER)\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\");\n\n    ASSERT_EQ(builder.build(), \"aeron-spy:aeron:udp?endpoint=localhost:9999\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateComplexUdpChannel)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\")\n        .ttl(9)\n        .termLength(1024 * 128);\n\n    ASSERT_EQ(builder.build(), \"aeron:udp?endpoint=localhost:9999|term-length=131072|ttl=9\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateReplayUdpChannel)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\")\n        .termLength(1024 * 128)\n        .initialTermId(777)\n        .termId(999)\n        .termOffset(64);\n\n    ASSERT_EQ(\n        builder.build(),\n        \"aeron:udp?endpoint=localhost:9999|term-length=131072|init-term-id=777|term-id=999|term-offset=64\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateInitialPosition)\n{\n    ChannelUriStringBuilder builder;\n\n    std::int32_t termLength = 1024 * 128;\n    std::int64_t position = (termLength * 3) + 64;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\")\n        .initialPosition(position, 777, termLength);\n\n    ASSERT_EQ(\n        builder.build(),\n        \"aeron:udp?endpoint=localhost:9999|term-length=131072|init-term-id=777|term-id=780|term-offset=64\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateSocketSndRcvbufLengths)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\")\n        .socketSndbufLength(8192)\n        .socketRcvbufLength(4096);\n\n    ASSERT_EQ(\n        builder.build(),\n        \"aeron:udp?endpoint=localhost:9999|so-sndbuf=8192|so-rcvbuf=4096\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateReceiverWindowLength)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\")\n        .receiverWindowLength(4096);\n\n    ASSERT_EQ(\n        builder.build(),\n        \"aeron:udp?endpoint=localhost:9999|rcv-wnd=4096\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateOffsets)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\")\n        .mediaReceiveTimestampOffset(\"reserved\")\n        .channelReceiveTimestampOffset(\"0\")\n        .channelSendTimestampOffset(\"8\");\n\n    ASSERT_EQ(\n        builder.build(),\n        \"aeron:udp?endpoint=localhost:9999|media-rcv-ts-offset=reserved|channel-rcv-ts-offset=0|channel-snd-ts-offset=8\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateReceiveTimestampOffset)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\")\n        .mediaReceiveTimestampOffset(\"reserved\");\n\n    ASSERT_EQ(\n        builder.build(),\n        \"aeron:udp?endpoint=localhost:9999|media-rcv-ts-offset=reserved\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldGenerateRxTimestampOffset)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"localhost:9999\")\n        .mediaReceiveTimestampOffset(\"reserved\");\n\n    ASSERT_EQ(\n        builder.build(),\n        \"aeron:udp?endpoint=localhost:9999|media-rcv-ts-offset=reserved\");\n}\n\nTEST(ChannelUriStringBuilderTest, shouldHandleMaxRetransmits)\n{\n    ChannelUriStringBuilder builder;\n\n    builder\n        .media(UDP_MEDIA)\n        .endpoint(\"224.10.9.8:777\")\n        .maxResend(123);\n\n    const std::string uriString = builder.build();\n\n    std::shared_ptr<ChannelUri> channelUri = ChannelUri::parse(uriString);\n    ASSERT_NE(std::string::npos, channelUri->toString().find(\"max-resend=123\"));\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ControlledFragmentAssemblerTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"ControlledFragmentAssemblerTestFixture.h\"\n\nextern \"C\"\n{\n#include \"aeron_image.h\"\n}\n\nvoid initHeader(std::uint8_t *buffer, size_t length, Header **header)\n{\n    auto *aeronHeader = new aeron_header_t{};\n    aeronHeader->frame = reinterpret_cast<aeron_data_header_t *>(buffer);\n    aeronHeader->fragmented_frame_length = NULL_VALUE;\n    aeronHeader->initial_term_id = INITIAL_TERM_ID;\n    aeronHeader->position_bits_to_shift = POSITION_BITS_TO_SHIFT;\n    aeronHeader->context = (void*)\"test context\";\n\n    *header = new Header{aeronHeader};\n}\n\nvoid freeHeader(Header *header)\n{\n    delete header->hdr();\n    delete header;\n}\n\nvoid fillHeader(Header &header, std::int32_t termOffset, std::uint8_t flags, std::int32_t payloadLength)\n{\n    aeron_data_header_t *frame = header.hdr()->frame;\n\n    frame->frame_header.frame_length = DataFrameHeader::LENGTH + payloadLength;\n    frame->frame_header.version = DataFrameHeader::CURRENT_VERSION;\n    frame->frame_header.flags = flags;\n    frame->frame_header.type = DataFrameHeader::HDR_TYPE_DATA;\n    frame->term_offset = termOffset;\n    frame->session_id = SESSION_ID;\n    frame->stream_id = STREAM_ID;\n    frame->term_id = ACTIVE_TERM_ID;\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    ControlledFragmentAssemblerParameterisedTest,\n    ControlledFragmentAssemblerParameterisedTest,\n    testing::Values(std::make_tuple(initHeader, freeHeader, fillHeader)));"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ControlledFragmentAssemblerTestFixture.h",
    "content": "//\n// Created by mike on 14/03/24.\n//\n\n#ifndef AERON_CONTROLLEDFRAGMENTASSEMBLERTESTFIXTURE_H\n#define AERON_CONTROLLEDFRAGMENTASSEMBLERTESTFIXTURE_H\n\n#include <functional>\n#include \"gtest/gtest.h\"\n#include \"gmock/gmock.h\"\n#include \"gmock/gmock-matchers.h\"\n#include \"concurrent/logbuffer/LogBufferDescriptor.h\"\n#include \"concurrent/logbuffer/Header.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"ControlledFragmentAssembler.h\"\n\nusing namespace aeron;\nusing namespace aeron::concurrent;\nusing namespace aeron::concurrent::logbuffer;\n\nstatic const std::int32_t STREAM_ID = 10;\nstatic const std::int32_t SESSION_ID = 200;\nstatic const std::int32_t TERM_OFFSET = 1024;\nstatic const std::int32_t TERM_LENGTH = LogBufferDescriptor::TERM_MIN_LENGTH;\nstatic const std::int32_t INITIAL_TERM_ID = -1234;\nstatic const std::int32_t ACTIVE_TERM_ID = INITIAL_TERM_ID + 5;\nstatic const int POSITION_BITS_TO_SHIFT = BitUtil::numberOfTrailingZeroes(TERM_LENGTH);\nstatic const util::index_t MTU_LENGTH = 128;\n\ntypedef std::array<std::uint8_t, TERM_LENGTH> fragment_buffer_t;\n\nclass ControlledFragmentAssemblerParameterisedTest : public testing::TestWithParam<std::tuple<\n    std::function<void(std::uint8_t *, size_t, Header**)>,\n    std::function<void(Header*)>,\n    std::function<void(Header&, std::int32_t, std::uint8_t, std::int32_t)>\n    >>\n{\npublic:\n    void SetUp() override\n    {\n        std::get<0>(GetParam())(&headerBuffer[0], DataFrameHeader::LENGTH, &m_header);\n    }\n\n    void TearDown() override\n    {\n        std::get<1>(GetParam())(m_header);\n    }\n\n    void fillFrame(\n        std::int32_t termOffset,\n        std::uint8_t flags,\n        std::int32_t offset,\n        std::int32_t fragmentLength,\n        std::uint8_t initialPayloadValue)\n    {\n        std::get<2>(GetParam())(*m_header, termOffset, flags, fragmentLength);\n\n        std::uint8_t value = initialPayloadValue;\n        for (int i = 0; i < fragmentLength; i++)\n        {\n            m_buffer[i + offset] = value++;\n        }\n    }\n\n    void verifyPayload(AtomicBuffer &buffer, util::index_t offset, util::index_t length)\n    {\n        std::uint8_t *ptr = buffer.buffer() + offset;\n\n        for (int i = 0; i < length; i++)\n        {\n            ASSERT_EQ(*(ptr + i), i % 256);\n        }\n    }\n\nprotected:\n    Header *m_header = nullptr;\n    fragment_buffer_t m_buffer = {};\n\nprivate:\n    std::uint8_t headerBuffer[DataFrameHeader::LENGTH] = {};\n};\n\nTEST_P(ControlledFragmentAssemblerParameterisedTest, shouldPassThroughUnfragmentedMessage)\n{\n    std::int32_t fragmentLength = 158;\n    fillFrame(TERM_OFFSET, FrameDescriptor::UNFRAGMENTED, 0, fragmentLength, 0);\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength);\n            EXPECT_NE(nullptr, header.context());\n            EXPECT_EQ(m_header->context(), header.context());\n            EXPECT_EQ(header.positionBitsToShift(), POSITION_BITS_TO_SHIFT);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + fragmentLength);\n            EXPECT_EQ(header.flags(), FrameDescriptor::UNFRAGMENTED);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    BitUtil::align(header.termOffset() + header.frameLength(), FrameDescriptor::FRAME_ALIGNMENT),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_P(ControlledFragmentAssemblerParameterisedTest, shouldReassembleFromTwoFragments)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 2);\n            EXPECT_NE(nullptr, header.context());\n            EXPECT_EQ(m_header->context(), header.context());\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (2 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(2 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::BEGIN_FRAG, 0, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, FrameDescriptor::END_FRAG, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\nTEST_P(ControlledFragmentAssemblerParameterisedTest, shouldReassembleFromThreeFragments)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 3);\n            EXPECT_EQ(header.positionBitsToShift(), POSITION_BITS_TO_SHIFT);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (3 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(3 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::BEGIN_FRAG, 0, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, 0, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + (2 * MTU_LENGTH), FrameDescriptor::END_FRAG, MTU_LENGTH * 2, fragmentLength, (fragmentLength * 2) % 256);\n    assembler.handler()(buf, (MTU_LENGTH * 2), fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\nTEST_P(ControlledFragmentAssemblerParameterisedTest, shouldNotReassembleIfEndFirstFragment)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::END_FRAG, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH + DataFrameHeader::LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n}\n\nTEST_P(ControlledFragmentAssemblerParameterisedTest, shouldNotReassembleIfMissingBegin)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, 0, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, FrameDescriptor::END_FRAG, MTU_LENGTH * 2, fragmentLength, (fragmentLength * 2) % 256);\n    assembler.handler()(buf, (MTU_LENGTH * 2), fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n}\n\nTEST_P(ControlledFragmentAssemblerParameterisedTest, shouldReassembleTwoMessagesFromFourFrames)\n{\n    util::index_t termOffset = 0;\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 2);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (2 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(2 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    termOffset = TERM_OFFSET;\n    fillFrame(termOffset, FrameDescriptor::BEGIN_FRAG, termOffset, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::END_FRAG, termOffset, fragmentLength, 1);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n\n    isCalled = false;\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::BEGIN_FRAG, termOffset, fragmentLength, 2);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::END_FRAG, termOffset, fragmentLength, 2);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\n#endif //AERON_CONTROLLEDFRAGMENTASSEMBLERTESTFIXTURE_H\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/CountersTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"TestUtil.h\"\n#include \"HeartbeatTimestamp.h\"\n\n#define COUNTER_LABEL \"counter label\"\n#define COUNTER_TYPE_ID (102)\n#define COUNTER_KEY_LENGTH (sizeof(std::int64_t) + 3)\n\nusing namespace aeron;\nusing testing::MockFunction;\nusing testing::_;\n\nclass CountersTest: public testing::Test\n{\npublic:\n    CountersTest()\n    {\n        m_driver.start();\n    }\n\n    ~CountersTest() override\n    {\n        m_driver.stop();\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n    std::string m_label = COUNTER_LABEL;\n    std::uint8_t m_key[COUNTER_KEY_LENGTH] = {};\n    std::size_t m_key_length = COUNTER_KEY_LENGTH;\n};\n\nTEST_F(CountersTest, shouldAddAndCloseCounterWithCallbacks)\n{\n    Context ctx;\n    std::int32_t counterUnavailable = 0;\n\n    MockFunction<void(\n        CountersReader &countersReader, \n        std::int64_t registrationId, \n        std::int32_t counterId)> mockOnAvailableCounter;\n    MockFunction<void(\n        CountersReader &countersReader,\n        std::int64_t registrationId,\n        std::int32_t counterId)> mockOnUnavailableCounter;\n    \n    EXPECT_CALL(mockOnAvailableCounter, Call(_, _, _)).Times(testing::AtLeast(1));\n    EXPECT_CALL(mockOnUnavailableCounter, Call(_, _, _)).WillOnce(\n        [&](CountersReader &countersReader, std::int64_t registrationId, std::int32_t counterId)\n        {\n            aeron::concurrent::atomic::putInt32Volatile(&counterUnavailable, 1);\n        });\n\n    ctx.availableCounterHandler(mockOnAvailableCounter.AsStdFunction());\n    ctx.unavailableCounterHandler(mockOnUnavailableCounter.AsStdFunction());\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t regId = INT64_C(9387628937456);\n\n    memcpy(m_key, &regId, sizeof(regId));\n    std::int64_t counterId = aeron->addCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label);\n    {\n        WAIT_FOR_NON_NULL(counter, aeron->findCounter(counterId));\n        ASSERT_EQ(counter->registrationId(), aeron->countersReader().getCounterRegistrationId(counter->id()));\n        ASSERT_EQ(aeron->clientId(), aeron->countersReader().getCounterOwnerId(counter->id()));\n        ASSERT_EQ(COUNTER_TYPE_ID, aeron->countersReader().getCounterTypeId(counter->id()));\n\n        counter->incrementOrdered();\n        counter->incrementOrdered();\n        counter->incrementOrdered();\n        counter->incrementOrdered();\n\n        Counter readOnlyCounter(aeron->countersReader(), counter->registrationId(), counter->id());\n        ASSERT_EQ(\n            readOnlyCounter.registrationId(), aeron->countersReader().getCounterRegistrationId(readOnlyCounter.id()));\n        ASSERT_EQ(counter->get(), readOnlyCounter.get());\n    }\n\n    WAIT_FOR(1 == aeron::concurrent::atomic::getInt32Volatile(&counterUnavailable));\n}\n\nTEST_F(CountersTest, shouldReadCounterChange)\n{\n    Context ctx;\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t regId = INT64_C(9387628937456);\n\n    memcpy(m_key, &regId, sizeof(regId));\n    std::int64_t counterId = aeron->addCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label);\n    WAIT_FOR_NON_NULL(counter, aeron->findCounter(counterId));\n\n    EXPECT_EQ(counter->label(), aeron->countersReader().getCounterLabel(counter->id()));\n    EXPECT_EQ(counter->state(), aeron->countersReader().getCounterState(counter->id()));\n\n    counter->increment();\n    EXPECT_EQ(counter.get()->get(), aeron->countersReader().getCounterValue(counter->id()));\n\n    counter->compareAndSet(counter->get(), 1000);\n    EXPECT_EQ(counter.get()->get(), aeron->countersReader().getCounterValue(counter->id()));\n\n    counter->set(2000);\n    EXPECT_EQ(counter.get()->getWeak(), aeron->countersReader().getCounterValue(counter->id()));\n\n    counter->getAndAdd(3000);\n    EXPECT_EQ(counter.get()->getWeak(), aeron->countersReader().getCounterValue(counter->id()));\n\n    counter->getAndAddOrdered(4000);\n    EXPECT_EQ(counter.get()->getWeak(), aeron->countersReader().getCounterValue(counter->id()));\n\n    counter->getAndSet(5000);\n    EXPECT_EQ(counter.get()->getWeak(), aeron->countersReader().getCounterValue(counter->id()));\n\n    counter->setWeak(6000);\n    EXPECT_EQ(6000, counter.get()->getWeak());\n}\n\nTEST_F(CountersTest, shouldFindCounterByTypeRegistrationId)\n{\n    Context ctx;\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    auto valuesBuffer = aeron->countersReader().valuesBuffer();\n    std::int64_t registrationId = INT64_C(-674328648234);\n    const std::int64_t nullCounterId = CountersReader::NULL_COUNTER_ID;\n\n    std::int64_t counterId1 = aeron->addCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label);\n    WAIT_FOR_NON_NULL(counter, aeron->findCounter(counterId1));\n    valuesBuffer.putInt64(CountersReader::counterOffset(counter->id()) + CountersReader::REGISTRATION_ID_OFFSET, registrationId);\n    ASSERT_EQ(registrationId, aeron->countersReader().getCounterRegistrationId(counter->id()));\n\n    std::int64_t counterId2 = aeron->addCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label);\n    WAIT_FOR_NON_NULL(counter2, aeron->findCounter(counterId2));\n    ASSERT_NE(counter->id(), counter2->id());\n    valuesBuffer.putInt64(CountersReader::counterOffset(counter2->id()) + CountersReader::REGISTRATION_ID_OFFSET, registrationId);\n    ASSERT_EQ(registrationId, aeron->countersReader().getCounterRegistrationId(counter2->id()));\n\n    ASSERT_EQ(counter->id(), aeron->countersReader().findByTypeIdAndRegistrationId(COUNTER_TYPE_ID, registrationId));\n    ASSERT_EQ(nullCounterId, aeron->countersReader().findByTypeIdAndRegistrationId(COUNTER_TYPE_ID, 0));\n    ASSERT_EQ(nullCounterId, aeron->countersReader().findByTypeIdAndRegistrationId(0, registrationId));\n}\n\nTEST_F(CountersTest, shouldFindCounterByRegistrationId)\n{\n    Context ctx;\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    auto valuesBuffer = aeron->countersReader().valuesBuffer();\n    std::int64_t registrationId = INT64_C(123456789);\n    const std::int64_t nullCounterId = CountersReader::NULL_COUNTER_ID;\n\n    std::int64_t counterId1 = aeron->addCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label);\n    WAIT_FOR_NON_NULL(counter, aeron->findCounter(counterId1));\n    valuesBuffer.putInt64(CountersReader::counterOffset(counter->id()) + CountersReader::REGISTRATION_ID_OFFSET, registrationId);\n    ASSERT_EQ(registrationId, aeron->countersReader().getCounterRegistrationId(counter->id()));\n\n    std::int64_t counterId2 = aeron->addCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label);\n    WAIT_FOR_NON_NULL(counter2, aeron->findCounter(counterId2));\n    ASSERT_NE(counter->id(), counter2->id());\n    valuesBuffer.putInt64(CountersReader::counterOffset(counter2->id()) + CountersReader::REGISTRATION_ID_OFFSET, registrationId);\n    ASSERT_EQ(registrationId, aeron->countersReader().getCounterRegistrationId(counter2->id()));\n\n    ASSERT_EQ(counter->id(), aeron->countersReader().findByRegistrationId(registrationId));\n    ASSERT_EQ(nullCounterId, aeron->countersReader().findByRegistrationId(-registrationId));\n}\n\nTEST_F(CountersTest, shouldCreateAStaticCounter)\n{\n    Context ctx;\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    const std::int64_t registrationId = 42;\n    const std::int64_t nullCounterId = CountersReader::NULL_COUNTER_ID;\n    const std::int32_t allocatedState = CountersReader::RECORD_ALLOCATED;\n\n    std::int64_t counterId = aeron->addStaticCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label, registrationId);\n    WAIT_FOR_NON_NULL(counter, aeron->findCounter(counterId));\n    ASSERT_EQ(allocatedState, aeron->countersReader().getCounterState(counter->id()));\n    ASSERT_EQ(COUNTER_TYPE_ID, aeron->countersReader().getCounterTypeId(counter->id()));\n    ASSERT_EQ(registrationId, aeron->countersReader().getCounterRegistrationId(counter->id()));\n    ASSERT_NE(nullCounterId, counter->id());\n\n    std::int64_t counterId2 = aeron->addStaticCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label, registrationId);\n    WAIT_FOR_NON_NULL(counter2, aeron->findCounter(counterId2));\n    ASSERT_EQ(counter->id(), counter2->id());\n}\n\nTEST_F(CountersTest, shouldCreateAStaticCounterUsingAsyncApi)\n{\n    Context ctx;\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    const std::int64_t registrationId = 42;\n    const std::int64_t nullCounterId = CountersReader::NULL_COUNTER_ID;\n    const std::int32_t allocatedState = CountersReader::RECORD_ALLOCATED;\n\n    auto asyncResource = aeron->addStaticCounterAsync(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label, registrationId);\n    ASSERT_NE(nullptr, asyncResource);\n    ASSERT_GT(aeron->addCounterAsyncGetRegistrationId(asyncResource), 0);\n    WAIT_FOR_NON_NULL(counter, aeron->findCounter(asyncResource));\n    ASSERT_EQ(allocatedState, aeron->countersReader().getCounterState(counter->id()));\n    ASSERT_EQ(COUNTER_TYPE_ID, aeron->countersReader().getCounterTypeId(counter->id()));\n    ASSERT_EQ(registrationId, aeron->countersReader().getCounterRegistrationId(counter->id()));\n    ASSERT_NE(nullCounterId, counter->id());\n\n    std::int64_t counterId2 = aeron->addStaticCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label, registrationId);\n    WAIT_FOR_NON_NULL(counter2, aeron->findCounter(counterId2));\n    ASSERT_EQ(counter->id(), counter2->id());\n}\n\nTEST_F(CountersTest, shouldErrorCreatingAStaticCounterIfSessionCounterAlreadyExists)\n{\n    Context ctx;\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    auto valuesBuffer = aeron->countersReader().valuesBuffer();\n    std::int64_t registrationId = INT64_C(123456789);\n\n    std::int64_t counterId = aeron->addCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label);\n    WAIT_FOR_NON_NULL(counter, aeron->findCounter(counterId));\n    valuesBuffer.putInt64(CountersReader::counterOffset(counter->id()) + CountersReader::REGISTRATION_ID_OFFSET, registrationId);\n    ASSERT_EQ(registrationId, aeron->countersReader().getCounterRegistrationId(counter->id()));\n\n    EXPECT_THROW({\n        try\n        {\n            std::int64_t counterId2 = aeron->addStaticCounter(COUNTER_TYPE_ID, m_key, COUNTER_KEY_LENGTH, m_label, registrationId);\n            WAIT_FOR_NON_NULL(counter2, aeron->findCounter(counterId2));\n        }\n        catch( const AeronException& e )\n        {\n            auto errorMsg = std::string(e.what());\n            std::cout << errorMsg << std::endl;\n            ASSERT_NE(std::string::npos, errorMsg.find(\"cannot add static counter, because a non-static counter exists\", 0));\n            throw;\n        }\n    }, AeronException );\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/EmbeddedMediaDriver.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_EMBEDDED_MEDIA_DRIVER_H\n#define AERON_EMBEDDED_MEDIA_DRIVER_H\n\n#if defined(__linux__)\n#ifndef _BSD_SOURCE\n#define _BSD_SOURCE\n#endif\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n#endif\n\n#include <string>\n#include <thread>\n#include <atomic>\n#include <stdexcept>\n\nextern \"C\"\n{\n#include \"aeronmd.h\"\n}\n\nnamespace aeron\n{\n\nclass EmbeddedMediaDriver\n{\npublic:\n    ~EmbeddedMediaDriver()\n    {\n        closeDriver();\n    }\n\n    void closeDriver()\n    {\n        bool closed = false;\n        if (m_closed.compare_exchange_strong(closed, true))\n        {\n            if (0 != aeron_driver_close(m_driver))\n            {\n                fprintf(stderr, \"ERROR: driver close (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n            }\n\n            if (0 != aeron_driver_context_close(m_context))\n            {\n                fprintf(stderr, \"ERROR: driver context close (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n            }\n        }\n    }\n\n    void driverLoop()\n    {\n        while (m_running)\n        {\n            aeron_driver_main_idle_strategy(m_driver, aeron_driver_main_do_work(m_driver));\n        }\n    }\n\n    void stop()\n    {\n        bool running = true;\n        if (m_running.compare_exchange_strong(running, false))\n        {\n            if (m_thread.joinable())\n            {\n                m_thread.join();\n            }\n        }\n    }\n\n    void start()\n    {\n        if (init() < 0)\n        {\n            throw std::runtime_error(\"could not initialize\");\n        }\n\n        m_thread = std::thread(\n            [&]()\n            {\n                driverLoop();\n            });\n    }\n\n    void livenessTimeoutNs(std::uint64_t livenessTimeoutNs)\n    {\n        m_livenessTimeoutNs = livenessTimeoutNs;\n    }\n\n    std::uint64_t livenessTimeoutNs()\n    {\n        return m_livenessTimeoutNs;\n    }\n\n    void aeronDir(std::string aeronDir)\n    {\n        m_aeronDir = aeronDir;\n    }\n\n    std::string aeronDir()\n    {\n        return m_aeronDir;\n    }\n\nprotected:\n    int init()\n    {\n        if (aeron_driver_context_init(&m_context) < 0)\n        {\n            fprintf(stderr, \"ERROR: context init (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n            return -1;\n        }\n\n        if (!m_aeronDir.empty())\n        {\n            aeron_driver_context_set_dir(m_context, m_aeronDir.c_str());\n        }\n        aeron_driver_context_set_dir_delete_on_start(m_context, true);\n        aeron_driver_context_set_dir_delete_on_shutdown(m_context, true);\n        aeron_driver_context_set_threading_mode(m_context, AERON_THREADING_MODE_SHARED);\n        aeron_driver_context_set_shared_idle_strategy(m_context, \"sleep-ns\");\n        aeron_driver_context_set_term_buffer_sparse_file(m_context, true);\n        aeron_driver_context_set_term_buffer_length(m_context, 64 * 1024);\n        aeron_driver_context_set_ipc_term_buffer_length(m_context, 64 * 1024);\n        aeron_driver_context_set_timer_interval_ns(m_context, m_livenessTimeoutNs / 100);\n        aeron_driver_context_set_client_liveness_timeout_ns(m_context, m_livenessTimeoutNs);\n        aeron_driver_context_set_publication_linger_timeout_ns(m_context, m_livenessTimeoutNs / 10);\n        aeron_driver_context_set_image_liveness_timeout_ns(m_context, m_livenessTimeoutNs / 10);\n        aeron_driver_context_set_enable_experimental_features(m_context, true);\n\n        long long debugTimeoutMs;\n        if (0 != (debugTimeoutMs = EmbeddedMediaDriver::getDebugTimeoutMs()))\n        {\n            aeron_driver_context_set_driver_timeout_ms(m_context, debugTimeoutMs);\n            aeron_driver_context_set_client_liveness_timeout_ns(m_context, debugTimeoutMs * 1000000LL);\n            aeron_driver_context_set_image_liveness_timeout_ns(m_context, debugTimeoutMs * 1000000LL);\n            aeron_driver_context_set_publication_unblock_timeout_ns(m_context, 2 * debugTimeoutMs * 1000000LL);\n        }\n\n        if (aeron_driver_init(&m_driver, m_context) < 0)\n        {\n            fprintf(stderr, \"ERROR: driver init (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n            return -1;\n        }\n\n        if (aeron_driver_start(m_driver, true) < 0)\n        {\n            fprintf(stderr, \"ERROR: driver start (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n            return -1;\n        }\n\n        return 0;\n    }\n\nprivate:\n    std::uint64_t m_livenessTimeoutNs = 5 * 1000 * 1000 * 1000LL;\n    std::string m_aeronDir;\n    std::atomic<bool> m_running = { true };\n    std::atomic<bool> m_closed = { false };\n    std::thread m_thread;\n    aeron_driver_context_t *m_context = nullptr;\n    aeron_driver_t *m_driver = nullptr;\n\n    static long long getDebugTimeoutMs()\n    {\n        const char *debug_timeout_str = getenv(\"AERON_DEBUG_TIMEOUT\");\n        return nullptr != debug_timeout_str ? strtoll(debug_timeout_str, nullptr, 10) : 0LL;\n    }\n};\n\n}\n\n#endif //AERON_EMBEDDED_MEDIA_DRIVER_H\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ExceptionsTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"util/Exceptions.h\"\nextern \"C\"\n{\n#include \"util/aeron_error.h\"\n}\n\nusing namespace aeron::util;\nusing testing::MockFunction;\n\nclass ExceptionsTest : public testing::Test\n{\npublic:\n    ExceptionsTest() = default;\n\n    ~ExceptionsTest() override = default;\n};\n\nTEST_F(ExceptionsTest, shouldThrowAppropriateType)\n{\n    AERON_SET_ERR(EINVAL, \"%s\", \"Invalid argument\");\n    ASSERT_THROW(\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        },\n        IllegalArgumentException);\n\n    AERON_SET_ERR(EPERM, \"%s\", \"Invalid argument\");\n    ASSERT_THROW(\n        {\n            AERON_MAP_ERRNO_TO_SOURCED_EXCEPTION_AND_THROW;\n        },\n        IllegalStateException);\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/FragmentAssemblerTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"FragmentAssemblerTestFixture.h\"\n\nextern \"C\"\n{\n#include \"aeron_image.h\"\n}\n\nvoid initHeader(std::uint8_t *buffer, size_t length, Header **header)\n{\n    auto *aeronHeader = new aeron_header_t{};\n    aeronHeader->frame = reinterpret_cast<aeron_data_header_t *>(buffer);\n    aeronHeader->fragmented_frame_length = NULL_VALUE;\n    aeronHeader->initial_term_id = INITIAL_TERM_ID;\n    aeronHeader->position_bits_to_shift = POSITION_BITS_TO_SHIFT;\n    aeronHeader->context = (void*)\"test context\";\n\n    *header = new Header{aeronHeader};\n}\n\nvoid freeHeader(Header *header)\n{\n    delete header->hdr();\n    delete header;\n}\n\nvoid fillHeader(Header &header, std::int32_t termOffset, std::uint8_t flags, std::int32_t payloadLength)\n{\n    aeron_data_header_t *frame = header.hdr()->frame;\n\n    frame->frame_header.frame_length = DataFrameHeader::LENGTH + payloadLength;\n    frame->frame_header.version = DataFrameHeader::CURRENT_VERSION;\n    frame->frame_header.flags = flags;\n    frame->frame_header.type = DataFrameHeader::HDR_TYPE_DATA;\n    frame->term_offset = termOffset;\n    frame->session_id = SESSION_ID;\n    frame->stream_id = STREAM_ID;\n    frame->term_id = ACTIVE_TERM_ID;\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    FragmentAssemblerParameterisedTest,\n    FragmentAssemblerParameterisedTest,\n    testing::Values(std::make_tuple(initHeader, freeHeader, fillHeader)));"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/FragmentAssemblerTestFixture.h",
    "content": "//\n// Created by mike on 14/03/24.\n//\n\n#ifndef AERON_FRAGMENTASSEMBLERTESTFIXTURE_H\n#define AERON_FRAGMENTASSEMBLERTESTFIXTURE_H\n\n#include <functional>\n#include \"gtest/gtest.h\"\n#include \"gmock/gmock.h\"\n#include \"gmock/gmock-matchers.h\"\n#include \"concurrent/logbuffer/LogBufferDescriptor.h\"\n#include \"concurrent/logbuffer/Header.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"FragmentAssembler.h\"\n\nusing namespace aeron;\nusing namespace aeron::concurrent;\nusing namespace aeron::concurrent::logbuffer;\n\nstatic const std::int32_t STREAM_ID = 10;\nstatic const std::int32_t SESSION_ID = 200;\nstatic const std::int32_t TERM_OFFSET = 1024;\nstatic const std::int32_t TERM_LENGTH = LogBufferDescriptor::TERM_MIN_LENGTH;\nstatic const std::int32_t INITIAL_TERM_ID = -1234;\nstatic const std::int32_t ACTIVE_TERM_ID = INITIAL_TERM_ID + 5;\nstatic const int POSITION_BITS_TO_SHIFT = BitUtil::numberOfTrailingZeroes(TERM_LENGTH);\nstatic const util::index_t MTU_LENGTH = 128;\n\ntypedef std::array<std::uint8_t, TERM_LENGTH> fragment_buffer_t;\n\nclass FragmentAssemblerParameterisedTest : public testing::TestWithParam<std::tuple<\n    std::function<void(std::uint8_t *, size_t, Header**)>,\n    std::function<void(Header*)>,\n    std::function<void(Header&, std::int32_t, std::uint8_t, std::int32_t)>\n    >>\n{\npublic:\n    void SetUp() override\n    {\n        std::get<0>(GetParam())(&headerBuffer[0], DataFrameHeader::LENGTH, &m_header);\n    }\n\n    void TearDown() override\n    {\n        std::get<1>(GetParam())(m_header);\n    }\n\n    void fillFrame(\n        std::int32_t termOffset,\n        std::uint8_t flags,\n        std::int32_t offset,\n        std::int32_t fragmentLength,\n        std::uint8_t initialPayloadValue)\n    {\n        std::get<2>(GetParam())(*m_header, termOffset, flags, fragmentLength);\n\n        std::uint8_t value = initialPayloadValue;\n        for (int i = 0; i < fragmentLength; i++)\n        {\n            m_buffer[i + offset] = value++;\n        }\n    }\n\n    void verifyPayload(AtomicBuffer &buffer, util::index_t offset, util::index_t length)\n    {\n        std::uint8_t *ptr = buffer.buffer() + offset;\n\n        for (int i = 0; i < length; i++)\n        {\n            ASSERT_EQ(*(ptr + i), i % 256);\n        }\n    }\n\nprotected:\n    Header *m_header = nullptr;\n    fragment_buffer_t m_buffer = {};\n\nprivate:\n    std::uint8_t headerBuffer[DataFrameHeader::LENGTH] = {};\n};\n\nTEST_P(FragmentAssemblerParameterisedTest, shouldPassThroughUnfragmentedMessage)\n{\n\n    std::int32_t fragmentLength = 158;\n    fillFrame(TERM_OFFSET, FrameDescriptor::UNFRAGMENTED, 0, fragmentLength, 0);\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength);\n            EXPECT_NE(nullptr, header.context());\n            EXPECT_EQ(m_header->context(), header.context());\n            EXPECT_EQ(header.positionBitsToShift(), POSITION_BITS_TO_SHIFT);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + fragmentLength);\n            EXPECT_EQ(header.flags(), FrameDescriptor::UNFRAGMENTED);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    BitUtil::align(header.termOffset() + header.frameLength(), FrameDescriptor::FRAME_ALIGNMENT),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n        };\n\n    FragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_P(FragmentAssemblerParameterisedTest, shouldReassembleFromTwoFragments)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 2);\n            EXPECT_NE(nullptr, header.context());\n            EXPECT_EQ(m_header->context(), header.context());\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (2 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(2 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n        };\n\n    FragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::BEGIN_FRAG, 0, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, FrameDescriptor::END_FRAG, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\nTEST_P(FragmentAssemblerParameterisedTest, shouldReassembleFromThreeFragments)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 3);\n            EXPECT_EQ(header.positionBitsToShift(), POSITION_BITS_TO_SHIFT);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (3 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(3 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n        };\n\n    FragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::BEGIN_FRAG, 0, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, 0, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + (2 * MTU_LENGTH), FrameDescriptor::END_FRAG, MTU_LENGTH * 2, fragmentLength, (fragmentLength * 2) % 256);\n    assembler.handler()(buf, (MTU_LENGTH * 2), fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\nTEST_P(FragmentAssemblerParameterisedTest, shouldNotReassembleIfEndFirstFragment)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n        };\n\n    FragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::END_FRAG, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH + DataFrameHeader::LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n}\n\nTEST_P(FragmentAssemblerParameterisedTest, shouldNotReassembleIfMissingBegin)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n        };\n\n    FragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, 0, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, FrameDescriptor::END_FRAG, MTU_LENGTH * 2, fragmentLength, (fragmentLength * 2) % 256);\n    assembler.handler()(buf, (MTU_LENGTH * 2), fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n}\n\nTEST_P(FragmentAssemblerParameterisedTest, shouldReassembleTwoMessagesFromFourFrames)\n{\n    util::index_t termOffset = 0;\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 2);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (2 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(2 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n        };\n\n    FragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    termOffset = TERM_OFFSET;\n    fillFrame(termOffset, FrameDescriptor::BEGIN_FRAG, termOffset, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::END_FRAG, termOffset, fragmentLength, 1);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n\n    isCalled = false;\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::BEGIN_FRAG, termOffset, fragmentLength, 2);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::END_FRAG, termOffset, fragmentLength, 2);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\n#endif //AERON_FRAGMENTASSEMBLERTESTFIXTURE_H\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ImageControlledFragmentAssemblerTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"ImageControlledFragmentAssemblerTestFixture.h\"\n\nextern \"C\"\n{\n#include \"aeron_image.h\"\n}\n\nvoid initHeader(std::uint8_t *buffer, size_t length, Header **header)\n{\n    auto *aeronHeader = new aeron_header_t{};\n    aeronHeader->frame = reinterpret_cast<aeron_data_header_t *>(buffer);\n    aeronHeader->fragmented_frame_length = NULL_VALUE;\n    aeronHeader->initial_term_id = INITIAL_TERM_ID;\n    aeronHeader->position_bits_to_shift = POSITION_BITS_TO_SHIFT;\n    aeronHeader->context = (void*)\"test context\";\n\n    *header = new Header{aeronHeader};\n}\n\nvoid freeHeader(Header *header)\n{\n    delete header->hdr();\n    delete header;\n}\n\nvoid fillHeader(Header &header, std::int32_t termOffset, std::uint8_t flags, std::int32_t payloadLength)\n{\n    aeron_data_header_t *frame = header.hdr()->frame;\n\n    frame->frame_header.frame_length = DataFrameHeader::LENGTH + payloadLength;\n    frame->frame_header.version = DataFrameHeader::CURRENT_VERSION;\n    frame->frame_header.flags = flags;\n    frame->frame_header.type = DataFrameHeader::HDR_TYPE_DATA;\n    frame->term_offset = termOffset;\n    frame->session_id = SESSION_ID;\n    frame->stream_id = STREAM_ID;\n    frame->term_id = ACTIVE_TERM_ID;\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    ImageControlledFragmentAssemblerParameterisedTest,\n    ImageControlledFragmentAssemblerParameterisedTest,\n    testing::Values(std::make_tuple(initHeader, freeHeader, fillHeader)));"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ImageControlledFragmentAssemblerTestFixture.h",
    "content": "//\n// Created by mike on 14/03/24.\n//\n\n#ifndef AERON_IMAGECONTROLLEDFRAGMENTASSEMBLERTESTFIXTURE_H\n#define AERON_IMAGECONTROLLEDFRAGMENTASSEMBLERTESTFIXTURE_H\n\n#include <functional>\n#include \"gtest/gtest.h\"\n#include \"gmock/gmock.h\"\n#include \"gmock/gmock-matchers.h\"\n#include \"concurrent/logbuffer/LogBufferDescriptor.h\"\n#include \"concurrent/logbuffer/Header.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"ImageControlledFragmentAssembler.h\"\n\nusing namespace aeron;\nusing namespace aeron::concurrent;\nusing namespace aeron::concurrent::logbuffer;\n\nstatic const std::int32_t STREAM_ID = 10;\nstatic const std::int32_t SESSION_ID = 200;\nstatic const std::int32_t TERM_OFFSET = 1024;\nstatic const std::int32_t TERM_LENGTH = LogBufferDescriptor::TERM_MIN_LENGTH;\nstatic const std::int32_t INITIAL_TERM_ID = -1234;\nstatic const std::int32_t ACTIVE_TERM_ID = INITIAL_TERM_ID + 5;\nstatic const int POSITION_BITS_TO_SHIFT = BitUtil::numberOfTrailingZeroes(TERM_LENGTH);\nstatic const util::index_t MTU_LENGTH = 128;\n\ntypedef std::array<std::uint8_t, TERM_LENGTH> fragment_buffer_t;\n\nclass ImageControlledFragmentAssemblerParameterisedTest : public testing::TestWithParam<std::tuple<\n    std::function<void(std::uint8_t *, size_t, Header**)>,\n    std::function<void(Header*)>,\n    std::function<void(Header&, std::int32_t, std::uint8_t, std::int32_t)>\n    >>\n{\npublic:\n    void SetUp() override\n    {\n        std::get<0>(GetParam())(&headerBuffer[0], DataFrameHeader::LENGTH, &m_header);\n    }\n\n    void TearDown() override\n    {\n        std::get<1>(GetParam())(m_header);\n    }\n\n    void fillFrame(\n        std::int32_t termOffset,\n        std::uint8_t flags,\n        std::int32_t offset,\n        std::int32_t fragmentLength,\n        std::uint8_t initialPayloadValue)\n    {\n        std::get<2>(GetParam())(*m_header, termOffset, flags, fragmentLength);\n\n        std::uint8_t value = initialPayloadValue;\n        for (int i = 0; i < fragmentLength; i++)\n        {\n            m_buffer[i + offset] = value++;\n        }\n    }\n\n    void verifyPayload(AtomicBuffer &buffer, util::index_t offset, util::index_t length)\n    {\n        std::uint8_t *ptr = buffer.buffer() + offset;\n\n        for (int i = 0; i < length; i++)\n        {\n            ASSERT_EQ(*(ptr + i), i % 256);\n        }\n    }\n\nprotected:\n    Header *m_header = nullptr;\n    fragment_buffer_t m_buffer = {};\n\nprivate:\n    std::uint8_t headerBuffer[DataFrameHeader::LENGTH] = {};\n};\n\nTEST_P(ImageControlledFragmentAssemblerParameterisedTest, shouldPassThroughUnfragmentedMessage)\n{\n\n    std::int32_t fragmentLength = 158;\n    fillFrame(TERM_OFFSET, FrameDescriptor::UNFRAGMENTED, 0, fragmentLength, 0);\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength);\n            EXPECT_NE(nullptr, header.context());\n            EXPECT_EQ(m_header->context(), header.context());\n            EXPECT_EQ(header.positionBitsToShift(), POSITION_BITS_TO_SHIFT);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + fragmentLength);\n            EXPECT_EQ(header.flags(), FrameDescriptor::UNFRAGMENTED);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    BitUtil::align(header.termOffset() + header.frameLength(), FrameDescriptor::FRAME_ALIGNMENT),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ImageControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_P(ImageControlledFragmentAssemblerParameterisedTest, shouldReassembleFromTwoFragments)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 2);\n            EXPECT_NE(nullptr, header.context());\n            EXPECT_EQ(m_header->context(), header.context());\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (2 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(2 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ImageControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::BEGIN_FRAG, 0, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, FrameDescriptor::END_FRAG, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\nTEST_P(ImageControlledFragmentAssemblerParameterisedTest, shouldReassembleFromThreeFragments)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 3);\n            EXPECT_EQ(header.positionBitsToShift(), POSITION_BITS_TO_SHIFT);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (3 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(3 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ImageControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::BEGIN_FRAG, 0, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, 0, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + (2 * MTU_LENGTH), FrameDescriptor::END_FRAG, MTU_LENGTH * 2, fragmentLength, (fragmentLength * 2) % 256);\n    assembler.handler()(buf, (MTU_LENGTH * 2), fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\nTEST_P(ImageControlledFragmentAssemblerParameterisedTest, shouldNotReassembleIfEndFirstFragment)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ImageControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::END_FRAG, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH + DataFrameHeader::LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n}\n\nTEST_P(ImageControlledFragmentAssemblerParameterisedTest, shouldNotReassembleIfMissingBegin)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ImageControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, 0, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, FrameDescriptor::END_FRAG, MTU_LENGTH * 2, fragmentLength, (fragmentLength * 2) % 256);\n    assembler.handler()(buf, (MTU_LENGTH * 2), fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n}\n\nTEST_P(ImageControlledFragmentAssemblerParameterisedTest, shouldReassembleTwoMessagesFromFourFrames)\n{\n    util::index_t termOffset = 0;\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 2);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (2 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(2 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            return ControlledPollAction::CONTINUE;\n        };\n\n    ImageControlledFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    termOffset = TERM_OFFSET;\n    fillFrame(termOffset, FrameDescriptor::BEGIN_FRAG, termOffset, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::END_FRAG, termOffset, fragmentLength, 1);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n\n    isCalled = false;\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::BEGIN_FRAG, termOffset, fragmentLength, 2);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::END_FRAG, termOffset, fragmentLength, 2);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\n#endif //AERON_IMAGECONTROLLEDFRAGMENTASSEMBLERTESTFIXTURE_H\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ImageFragmentAssemblerTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"ImageFragmentAssemblerTestFixture.h\"\n\nextern \"C\"\n{\n#include \"aeron_image.h\"\n}\n\nvoid initHeader(std::uint8_t *buffer, size_t length, Header **header)\n{\n    auto *aeronHeader = new aeron_header_t{};\n    aeronHeader->frame = reinterpret_cast<aeron_data_header_t *>(buffer);\n    aeronHeader->fragmented_frame_length = NULL_VALUE;\n    aeronHeader->initial_term_id = INITIAL_TERM_ID;\n    aeronHeader->position_bits_to_shift = POSITION_BITS_TO_SHIFT;\n    aeronHeader->context = (void*)\"test context\";\n\n    *header = new Header{aeronHeader};\n}\n\nvoid freeHeader(Header *header)\n{\n    delete header->hdr();\n    delete header;\n}\n\nvoid fillHeader(Header &header, std::int32_t termOffset, std::uint8_t flags, std::int32_t payloadLength)\n{\n    aeron_data_header_t *frame = header.hdr()->frame;\n\n    frame->frame_header.frame_length = DataFrameHeader::LENGTH + payloadLength;\n    frame->frame_header.version = DataFrameHeader::CURRENT_VERSION;\n    frame->frame_header.flags = flags;\n    frame->frame_header.type = DataFrameHeader::HDR_TYPE_DATA;\n    frame->term_offset = termOffset;\n    frame->session_id = SESSION_ID;\n    frame->stream_id = STREAM_ID;\n    frame->term_id = ACTIVE_TERM_ID;\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    ImageFragmentAssemblerParameterisedTest,\n    ImageFragmentAssemblerParameterisedTest,\n    testing::Values(std::make_tuple(initHeader, freeHeader, fillHeader)));"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ImageFragmentAssemblerTestFixture.h",
    "content": "//\n// Created by mike on 14/03/24.\n//\n\n#ifndef AERON_IMAGEFRAGMENTASSEMBLERTESTFIXTURE_H\n#define AERON_IMAGEFRAGMENTASSEMBLERTESTFIXTURE_H\n\n#include <functional>\n#include \"gtest/gtest.h\"\n#include \"gmock/gmock.h\"\n#include \"gmock/gmock-matchers.h\"\n#include \"concurrent/logbuffer/LogBufferDescriptor.h\"\n#include \"concurrent/logbuffer/Header.h\"\n#include \"concurrent/AtomicBuffer.h\"\n#include \"ImageFragmentAssembler.h\"\n\nusing namespace aeron;\nusing namespace aeron::concurrent;\nusing namespace aeron::concurrent::logbuffer;\n\nstatic const std::int32_t STREAM_ID = 10;\nstatic const std::int32_t SESSION_ID = 200;\nstatic const std::int32_t TERM_OFFSET = 1024;\nstatic const std::int32_t TERM_LENGTH = LogBufferDescriptor::TERM_MIN_LENGTH;\nstatic const std::int32_t INITIAL_TERM_ID = -1234;\nstatic const std::int32_t ACTIVE_TERM_ID = INITIAL_TERM_ID + 5;\nstatic const int POSITION_BITS_TO_SHIFT = BitUtil::numberOfTrailingZeroes(TERM_LENGTH);\nstatic const util::index_t MTU_LENGTH = 128;\n\ntypedef std::array<std::uint8_t, TERM_LENGTH> fragment_buffer_t;\n\nclass ImageFragmentAssemblerParameterisedTest : public testing::TestWithParam<std::tuple<\n    std::function<void(std::uint8_t *, size_t, Header**)>,\n    std::function<void(Header*)>,\n    std::function<void(Header&, std::int32_t, std::uint8_t, std::int32_t)>\n    >>\n{\npublic:\n    void SetUp() override\n    {\n        std::get<0>(GetParam())(&headerBuffer[0], DataFrameHeader::LENGTH, &m_header);\n    }\n\n    void TearDown() override\n    {\n        std::get<1>(GetParam())(m_header);\n    }\n\n    void fillFrame(\n        std::int32_t termOffset,\n        std::uint8_t flags,\n        std::int32_t offset,\n        std::int32_t fragmentLength,\n        std::uint8_t initialPayloadValue)\n    {\n        std::get<2>(GetParam())(*m_header, termOffset, flags, fragmentLength);\n\n        std::uint8_t value = initialPayloadValue;\n        for (int i = 0; i < fragmentLength; i++)\n        {\n            m_buffer[i + offset] = value++;\n        }\n    }\n\n    void verifyPayload(AtomicBuffer &buffer, util::index_t offset, util::index_t length)\n    {\n        std::uint8_t *ptr = buffer.buffer() + offset;\n\n        for (int i = 0; i < length; i++)\n        {\n            ASSERT_EQ(*(ptr + i), i % 256);\n        }\n    }\n\nprotected:\n    Header *m_header = nullptr;\n    fragment_buffer_t m_buffer = {};\n\nprivate:\n    std::uint8_t headerBuffer[DataFrameHeader::LENGTH] = {};\n};\n\nTEST_P(ImageFragmentAssemblerParameterisedTest, shouldPassThroughUnfragmentedMessage)\n{\n\n    std::int32_t fragmentLength = 158;\n    fillFrame(TERM_OFFSET, FrameDescriptor::UNFRAGMENTED, 0, fragmentLength, 0);\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength);\n            EXPECT_NE(nullptr, header.context());\n            EXPECT_EQ(m_header->context(), header.context());\n            EXPECT_EQ(header.positionBitsToShift(), POSITION_BITS_TO_SHIFT);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + fragmentLength);\n            EXPECT_EQ(header.flags(), FrameDescriptor::UNFRAGMENTED);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    BitUtil::align(header.termOffset() + header.frameLength(), FrameDescriptor::FRAME_ALIGNMENT),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n        };\n\n    ImageFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    EXPECT_TRUE(isCalled);\n}\n\nTEST_P(ImageFragmentAssemblerParameterisedTest, shouldReassembleFromTwoFragments)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 2);\n            EXPECT_NE(nullptr, header.context());\n            EXPECT_EQ(m_header->context(), header.context());\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (2 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(2 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n        };\n\n    ImageFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::BEGIN_FRAG, 0, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, FrameDescriptor::END_FRAG, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\nTEST_P(ImageFragmentAssemblerParameterisedTest, shouldReassembleFromThreeFragments)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 3);\n            EXPECT_EQ(header.positionBitsToShift(), POSITION_BITS_TO_SHIFT);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.termOffset(), TERM_OFFSET);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (3 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(3 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n            verifyPayload(buffer, offset, length);\n        };\n\n    ImageFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::BEGIN_FRAG, 0, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, 0, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + (2 * MTU_LENGTH), FrameDescriptor::END_FRAG, MTU_LENGTH * 2, fragmentLength, (fragmentLength * 2) % 256);\n    assembler.handler()(buf, (MTU_LENGTH * 2), fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\nTEST_P(ImageFragmentAssemblerParameterisedTest, shouldNotReassembleIfEndFirstFragment)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n        };\n\n    ImageFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, FrameDescriptor::END_FRAG, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH + DataFrameHeader::LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n}\n\nTEST_P(ImageFragmentAssemblerParameterisedTest, shouldNotReassembleIfMissingBegin)\n{\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n        };\n\n    ImageFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    fillFrame(TERM_OFFSET, 0, MTU_LENGTH, fragmentLength, fragmentLength % 256);\n    assembler.handler()(buf, MTU_LENGTH, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    fillFrame(TERM_OFFSET + MTU_LENGTH, FrameDescriptor::END_FRAG, MTU_LENGTH * 2, fragmentLength, (fragmentLength * 2) % 256);\n    assembler.handler()(buf, (MTU_LENGTH * 2), fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n}\n\nTEST_P(ImageFragmentAssemblerParameterisedTest, shouldReassembleTwoMessagesFromFourFrames)\n{\n    util::index_t termOffset = 0;\n    util::index_t fragmentLength = MTU_LENGTH - DataFrameHeader::LENGTH;\n    bool isCalled = false;\n    auto handler =\n        [&](AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            isCalled = true;\n            EXPECT_EQ(offset, 0);\n            EXPECT_EQ(length, fragmentLength * 2);\n            EXPECT_EQ(header.sessionId(), SESSION_ID);\n            EXPECT_EQ(header.streamId(), STREAM_ID);\n            EXPECT_EQ(header.termId(), ACTIVE_TERM_ID);\n            EXPECT_EQ(header.initialTermId(), INITIAL_TERM_ID);\n            EXPECT_EQ(header.frameLength(), DataFrameHeader::LENGTH + (2 * fragmentLength));\n            EXPECT_EQ(header.flags(), FrameDescriptor::BEGIN_FRAG | FrameDescriptor::END_FRAG);\n            EXPECT_EQ(\n                header.position(),\n                LogBufferDescriptor::computePosition(\n                    ACTIVE_TERM_ID,\n                    header.termOffset() + LogBufferDescriptor::computeFragmentedFrameLength(2 * fragmentLength, fragmentLength),\n                    POSITION_BITS_TO_SHIFT,\n                    INITIAL_TERM_ID));\n        };\n\n    ImageFragmentAssembler assembler(handler);\n    AtomicBuffer buf{m_buffer};\n\n    termOffset = TERM_OFFSET;\n    fillFrame(termOffset, FrameDescriptor::BEGIN_FRAG, termOffset, fragmentLength, 0);\n    assembler.handler()(buf, 0, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::END_FRAG, termOffset, fragmentLength, 1);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n\n    isCalled = false;\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::BEGIN_FRAG, termOffset, fragmentLength, 2);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_FALSE(isCalled);\n\n    termOffset += MTU_LENGTH;\n    fillFrame(termOffset, FrameDescriptor::END_FRAG, termOffset, fragmentLength, 2);\n    assembler.handler()(buf, termOffset, fragmentLength, *m_header);\n    ASSERT_TRUE(isCalled);\n}\n\n#endif //AERON_IMAGEFRAGMENTASSEMBLERTESTFIXTURE_H\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ImageTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"ChannelUriStringBuilder.h\"\n#include \"TestUtil.h\"\n\nusing namespace aeron;\nusing testing::MockFunction;\nusing testing::Return;\nusing testing::_;\n\nclass ImageTest : public testing::TestWithParam<std::tuple<const char*, const char*>>\n{\npublic:\n    ImageTest()\n    {\n        m_driver.start();\n    }\n\n    ~ImageTest() override\n    {\n        m_driver.stop();\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n};\n\ntypedef std::array<std::uint8_t, 1024> buffer_t;\n\nINSTANTIATE_TEST_SUITE_P(\n    ImageTest,\n    ImageTest,\n    testing::Values(\n        std::make_tuple(\"udp\", \"localhost:24325\"),\n        std::make_tuple(\"ipc\", nullptr)\n    ));\n\nTEST_P(ImageTest, shouldGetMultipleImages)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId1 = aeron->addExclusivePublication(channel, streamId);\n    std::int64_t pubId2 = aeron->addExclusivePublication(channel, streamId);\n    AgentInvoker<ClientConductor>& invoker = aeron->conductorAgentInvoker();\n\n    MockFunction<void(concurrent::AtomicBuffer&, util::index_t, util::index_t, Header&)> mockHandler;\n\n    EXPECT_CALL(mockHandler, Call(_, _, _, _)).Times(2);\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub1, aeron->findExclusivePublication(pubId1), invoker);\n        POLL_FOR_NON_NULL(pub2, aeron->findExclusivePublication(pubId2), invoker);\n        POLL_FOR(pub1->isConnected() && pub2->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        buffer.putString(0, message);\n        POLL_FOR(0 < pub1->offer(buffer), invoker);\n        POLL_FOR(0 < pub2->offer(buffer), invoker);\n\n        POLL_FOR(2 == sub->imageCount(), invoker);\n\n        EXPECT_NE(nullptr, sub->imageBySessionId(pub1->sessionId()));\n        EXPECT_NE(nullptr, sub->imageBySessionId(pub2->sessionId()));\n        EXPECT_NE(nullptr, sub->imageByIndex(0));\n        EXPECT_NE(nullptr, sub->imageByIndex(1));\n        const std::shared_ptr<std::vector<std::shared_ptr<Image>>> images = sub->copyOfImageList();\n        EXPECT_EQ(2U, images->size());\n\n        POLL_FOR(0 < (*images)[0]->poll(mockHandler.AsStdFunction(), 1), invoker);\n        POLL_FOR(0 < (*images)[1]->poll(mockHandler.AsStdFunction(), 1), invoker);\n    }\n\n    invoker.invoke();\n}\n\nTEST_P(ImageTest, shouldBoundedPoll)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId1 = aeron->addExclusivePublication(channel, streamId);\n    std::int64_t pubId2 = aeron->addExclusivePublication(channel, streamId);\n    AgentInvoker<ClientConductor>& invoker = aeron->conductorAgentInvoker();\n\n    MockFunction<void(concurrent::AtomicBuffer&, util::index_t, util::index_t, Header&)> mockHandler;\n\n    EXPECT_CALL(mockHandler, Call(_, _, _, _)).Times(2);\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub1, aeron->findExclusivePublication(pubId1), invoker);\n        POLL_FOR_NON_NULL(pub2, aeron->findExclusivePublication(pubId2), invoker);\n        POLL_FOR(pub1->isConnected() && pub2->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        buffer.putString(0, message);\n        POLL_FOR(0 < pub1->offer(buffer), invoker);\n        POLL_FOR(0 < pub2->offer(buffer), invoker);\n        POLL_FOR(2 == sub->imageCount(), invoker);\n\n        const std::shared_ptr<std::vector<std::shared_ptr<Image>>> images = sub->copyOfImageList();\n        EXPECT_EQ(2U, images->size());\n\n        POLL_FOR(0 < (*images)[0]->boundedPoll(mockHandler.AsStdFunction(), 10000, 1), invoker);\n        POLL_FOR(0 < (*images)[1]->boundedPoll(mockHandler.AsStdFunction(), 10000, 1), invoker);\n    }\n\n    invoker.invoke();\n}\n\nTEST_P(ImageTest, shouldControlledPoll)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId1 = aeron->addExclusivePublication(channel, streamId);\n    std::int64_t pubId2 = aeron->addExclusivePublication(channel, streamId);\n    AgentInvoker<ClientConductor>& invoker = aeron->conductorAgentInvoker();\n\n    MockFunction<ControlledPollAction(\n        concurrent::AtomicBuffer &buffer,\n        util::index_t offset,\n        util::index_t length,\n        concurrent::logbuffer::Header &header)> mockHandler;\n\n    EXPECT_CALL(mockHandler, Call(_, _, _, _)).Times(2).WillRepeatedly(Return(ControlledPollAction::CONTINUE));\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub1, aeron->findExclusivePublication(pubId1), invoker);\n        POLL_FOR_NON_NULL(pub2, aeron->findExclusivePublication(pubId2), invoker);\n        POLL_FOR(pub1->isConnected() && pub2->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        buffer.putString(0, message);\n        POLL_FOR(0 < pub1->offer(buffer), invoker);\n        POLL_FOR(0 < pub2->offer(buffer), invoker);\n        POLL_FOR(2 == sub->imageCount(), invoker);\n\n        const std::shared_ptr<std::vector<std::shared_ptr<Image>>> images = sub->copyOfImageList();\n        EXPECT_EQ(2U, images->size());\n\n        POLL_FOR(0 < (*images)[0]->controlledPoll(mockHandler.AsStdFunction(), 1), invoker);\n        POLL_FOR(0 < (*images)[1]->controlledPoll(mockHandler.AsStdFunction(), 1), invoker);\n    }\n\n    invoker.invoke();\n}\n\nTEST_P(ImageTest, shouldBoundedControlledPoll)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId1 = aeron->addExclusivePublication(channel, streamId);\n    std::int64_t pubId2 = aeron->addExclusivePublication(channel, streamId);\n    AgentInvoker<ClientConductor>& invoker = aeron->conductorAgentInvoker();\n\n    MockFunction<ControlledPollAction(\n        concurrent::AtomicBuffer &buffer,\n        util::index_t offset,\n        util::index_t length,\n        concurrent::logbuffer::Header &header)> mockHandler;\n\n    EXPECT_CALL(mockHandler, Call(_, _, _, _)).Times(2).WillRepeatedly(Return(ControlledPollAction::CONTINUE));\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub1, aeron->findExclusivePublication(pubId1), invoker);\n        POLL_FOR_NON_NULL(pub2, aeron->findExclusivePublication(pubId2), invoker);\n        POLL_FOR(pub1->isConnected() && pub2->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        buffer.putString(0, message);\n        POLL_FOR(0 < pub1->offer(buffer), invoker);\n        POLL_FOR(0 < pub2->offer(buffer), invoker);\n        POLL_FOR(2 == sub->imageCount(), invoker);\n\n        const std::shared_ptr<std::vector<std::shared_ptr<Image>>> images = sub->copyOfImageList();\n        EXPECT_EQ(2U, images->size());\n\n        POLL_FOR(0 < (*images)[0]->boundedControlledPoll(mockHandler.AsStdFunction(), 100000, 1), invoker);\n        POLL_FOR(0 < (*images)[1]->boundedControlledPoll(mockHandler.AsStdFunction(), 100000, 1), invoker);\n    }\n\n    invoker.invoke();\n}\n\nTEST_P(ImageTest, shouldControlledPeek)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId1 = aeron->addExclusivePublication(channel, streamId);\n    std::int64_t pubId2 = aeron->addExclusivePublication(channel, streamId);\n    AgentInvoker<ClientConductor>& invoker = aeron->conductorAgentInvoker();\n\n    MockFunction<ControlledPollAction(\n        concurrent::AtomicBuffer &buffer,\n        util::index_t offset,\n        util::index_t length,\n        concurrent::logbuffer::Header &header)> mockHandler;\n\n    EXPECT_CALL(mockHandler, Call(_, _, _, _)).Times(4).WillRepeatedly(Return(ControlledPollAction::CONTINUE));\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub1, aeron->findExclusivePublication(pubId1), invoker);\n        POLL_FOR_NON_NULL(pub2, aeron->findExclusivePublication(pubId2), invoker);\n        POLL_FOR(pub1->isConnected() && pub2->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        buffer.putString(0, message);\n        POLL_FOR(0 < pub1->offer(buffer), invoker);\n        POLL_FOR(0 < pub2->offer(buffer), invoker);\n        POLL_FOR(2 == sub->imageCount(), invoker);\n\n        const std::shared_ptr<Image> image1 = sub->imageBySessionId(pub1->sessionId());\n        const std::shared_ptr<Image> image2 = sub->imageBySessionId(pub2->sessionId());\n\n        POLL_FOR(pub1->position() == image1->controlledPeek(0, mockHandler.AsStdFunction(), 100000), invoker);\n        POLL_FOR(pub2->position() == image2->controlledPeek(0, mockHandler.AsStdFunction(), 100000), invoker);\n\n        POLL_FOR(pub1->position() == image1->controlledPeek(0, mockHandler.AsStdFunction(), 100000), invoker);\n        POLL_FOR(pub2->position() == image2->controlledPeek(0, mockHandler.AsStdFunction(), 100000), invoker);\n    }\n\n    invoker.invoke();\n}\n\nTEST_P(ImageTest, shouldBlockPollImage)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId1 = aeron->addExclusivePublication(channel, streamId);\n    std::int64_t pubId2 = aeron->addExclusivePublication(channel, streamId);\n    AgentInvoker<ClientConductor>& invoker = aeron->conductorAgentInvoker();\n    std::int64_t bytesConsumed = 0;\n\n    auto blockHandler = [&](\n        concurrent::AtomicBuffer& buffer,\n        util::index_t offset,\n        util::index_t length,\n        std::int32_t sessionId,\n        std::int32_t termId)\n    {\n        bytesConsumed += length;\n    };\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub1, aeron->findExclusivePublication(pubId1), invoker);\n        POLL_FOR_NON_NULL(pub2, aeron->findExclusivePublication(pubId2), invoker);\n        POLL_FOR(pub1->isConnected() && pub2->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        buffer.putString(0, message);\n        for (int i = 0; i < 3; i++)\n        {\n            POLL_FOR(0 < pub1->offer(buffer), invoker);\n            POLL_FOR(0 < pub2->offer(buffer), invoker);\n        }\n\n        POLL_FOR(2 == sub->imageCount(), invoker);\n\n        const std::shared_ptr<std::vector<std::shared_ptr<Image>>> ptr = sub->copyOfImageList();\n        EXPECT_EQ(2U, ptr->size());\n\n        const std::shared_ptr<Image> image1 = sub->imageBySessionId(pub1->sessionId());\n        const std::shared_ptr<Image> image2 = sub->imageBySessionId(pub2->sessionId());\n        std::int64_t read1 = 0;\n        std::int64_t read2 = 0;\n        POLL_FOR(pub1->position() <= (read1 += image1->blockPoll(blockHandler, 1000000)), invoker);\n        POLL_FOR(pub2->position() <= (read2 += image2->blockPoll(blockHandler, 1000000)), invoker);\n\n        EXPECT_EQ(pub1->position() + pub2->position(), bytesConsumed);\n        EXPECT_EQ(pub1->position() + pub2->position(), read1 + read2);\n\n        EXPECT_EQ(pub1->position(), image1->position());\n        EXPECT_EQ(pub2->position(), image2->position());\n    }\n\n    invoker.invoke();\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/LivenessTimeoutTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"TestUtil.h\"\n\nusing namespace aeron;\n\nclass SystemTest : public testing::Test\n{\npublic:\n    SystemTest()\n    {\n        m_driver.livenessTimeoutNs(m_livenessTimeoutNs);\n        m_driver.start();\n    }\n\n    ~SystemTest() override\n    {\n        m_driver.stop();\n    }\n\n    static std::int32_t typeId(CountersReader &reader, std::int32_t counterId)\n    {\n        const index_t offset = aeron::concurrent::CountersReader::metadataOffset(counterId);\n        return reader.metaDataBuffer().getInt32(offset + CountersReader::TYPE_ID_OFFSET);\n    }\n\nprotected:\n    std::uint64_t m_livenessTimeoutNs = 250 * 1000 * 1000LL;\n    EmbeddedMediaDriver m_driver;\n};\n\nTEST_F(SystemTest, shouldReportClosedIfTimedOut)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    ctx.errorHandler(\n        [](const std::exception &ignored)\n        {\n            // Deliberately ignored.\n        });\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor>& invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n    ASSERT_FALSE(aeron->isClosed());\n    invoker.invoke();\n    // Ideally we wouldn't sleep here, but didn't want to change the public API to inject a clock.\n    std::this_thread::sleep_for(std::chrono::nanoseconds(2 * m_livenessTimeoutNs));\n    invoker.invoke();\n    ASSERT_TRUE(aeron->isClosed());\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/LocalAddressesTest.cpp",
    "content": "\n/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"ChannelUriStringBuilder.h\"\n#include \"TestUtil.h\"\n\nusing namespace aeron;\nusing testing::MockFunction;\nusing testing::_;\n\nclass LocalAddressesTest : public testing::Test\n{\npublic:\n    LocalAddressesTest()\n    {\n        m_driver.start();\n    }\n\n    ~LocalAddressesTest() override\n    {\n        m_driver.stop();\n    }\n\n    static std::string join(std::vector<std::string> &strings)\n    {\n        std::stringstream ss;\n        ss << \"{\";\n        std::for_each(\n            strings.begin(),\n            strings.end(),\n            [&](const std::string &s)\n            {\n                ss << s;\n                ss << \", \";\n            });\n        ss.seekp(-2, std::ios_base::end);\n        ss << \"}\";\n        return ss.str();\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n};\n\nTEST_F(LocalAddressesTest, shouldGetLocalAddresses)\n{\n    std::int32_t streamId = 10001;\n    std::string channel = \"aeron:udp?endpoint=127.0.0.1:23456|control=127.0.0.1:23457\";\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId = aeron->addPublication(channel, streamId);\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        auto subAddresses = sub->localSocketAddresses();\n        ASSERT_EQ(1U, subAddresses.size()) << join(subAddresses);\n        EXPECT_NE(std::string::npos, channel.find(subAddresses[0]));\n        ASSERT_EQ(channel, sub->tryResolveChannelEndpointPort());\n\n        POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);\n        auto pubAddresses = pub->localSocketAddresses();\n        ASSERT_EQ(1U, pubAddresses.size());\n        EXPECT_NE(std::string::npos, channel.find(pubAddresses[0]));\n    }\n\n    invoker.invoke();\n}\n\nTEST_F(LocalAddressesTest, shouldGetLocalAddressesForIpc)\n{\n    std::int32_t streamId = 10001;\n    std::string channel = \"aeron:ipc\";\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        auto subAddresses = sub->localSocketAddresses();\n        ASSERT_EQ(0U, subAddresses.size()) << join(subAddresses);\n        ASSERT_EQ(channel, sub->tryResolveChannelEndpointPort());\n    }\n\n    invoker.invoke();\n}\n\nTEST_F(LocalAddressesTest, shouldGetLocalAddressesForMds)\n{\n    std::int32_t streamId = 10001;\n    std::string channel = \"aeron:udp?control-mode=manual\";\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n\n        int numDestinations = 32;\n        for (int i = 0; i < numDestinations; i++)\n        {\n            int64_t destination = sub->addDestination(\"aeron:udp?endpoint=127.0.0.1:\" + std::to_string(9000 + i));\n            POLL_FOR(sub->findDestinationResponse(destination), invoker);\n        }\n        \n        auto subAddresses = sub->localSocketAddresses();\n        ASSERT_EQ(numDestinations, (int)subAddresses.size());\n\n        for (int i = 0; i < numDestinations; i++)\n        {\n            std::string expectedAddress = \"127.0.0.1:\" + std::to_string(9000 + i);\n            ASSERT_EQ(expectedAddress, subAddresses[i]);\n        }\n    }\n\n    invoker.invoke();\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/MultiDestinationByIdTest.cpp",
    "content": "\n/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"ChannelUriStringBuilder.h\"\n#include \"TestUtil.h\"\n\nusing namespace aeron;\nusing testing::MockFunction;\nusing testing::_;\n\n#define PUB_MDC_MANUAL_URI \"aeron:udp?control-mode=manual|tags=3,4\"\n#define SUB1_MDC_MANUAL_URI \"aeron:udp?endpoint=localhost:24326|group=true\"\n#define SUB2_MDC_MANUAL_URI \"aeron:udp?endpoint=localhost:24327|group=true\"\n\n#define UNICAST_ENDPOINT_A \"localhost:24325\"\n#define UNICAST_ENDPOINT_B \"localhost:24326\"\n#define SUB_URI \"aeron:udp?control-mode=manual\"\n\n#define streamId INT32_C(1001)\n\nclass MultiDestinationByIdTest : public testing::TestWithParam<std::tuple<const char *, const char *>>\n{\npublic:\n    MultiDestinationByIdTest()\n    {\n        m_driver.start();\n    }\n\n    ~MultiDestinationByIdTest() override\n    {\n        m_driver.stop();\n    }\n\nprotected:\n    void SetUp() override\n    {\n        Context ctx;\n        ctx.useConductorAgentInvoker(true);\n        m_aeron = Aeron::connect(ctx);\n    }\n\n    void TearDown() override\n    {\n        invoker().invoke();\n        m_aeron = nullptr;\n    }\n\n    AgentInvoker<ClientConductor> invoker()\n    {\n        return m_aeron->conductorAgentInvoker();\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n    fragment_handler_t m_noOpHandler =\n        [&](concurrent::AtomicBuffer &b, util::index_t offset, util::index_t length, Header &header) {};\n    std::shared_ptr<Aeron> m_aeron;\n    std::array<std::uint8_t, 1024> buf = {};\n    AtomicBuffer buffer{buf};\n\n};\n\ntypedef std::array<std::uint8_t, 1024> buffer_t;\n\nTEST_F(MultiDestinationByIdTest, shouldAddRemoveDestinationFromPublicationById)\n{\n    auto sub1RegId = m_aeron->addSubscription(SUB1_MDC_MANUAL_URI, streamId);\n    auto sub2RegId = m_aeron->addSubscription(SUB2_MDC_MANUAL_URI, streamId);\n    auto pubRegId = m_aeron->addPublication(PUB_MDC_MANUAL_URI, streamId);\n\n    POLL_FOR_NON_NULL(sub1, m_aeron->findSubscription(sub1RegId), invoker());\n    POLL_FOR_NON_NULL(sub2, m_aeron->findSubscription(sub2RegId), invoker());\n    POLL_FOR_NON_NULL(pub, m_aeron->findPublication(pubRegId), invoker());\n\n    std::int64_t dest1CorrelationId = pub->addDestination(SUB1_MDC_MANUAL_URI);\n    std::int64_t dest2CorrelationId = pub->addDestination(SUB2_MDC_MANUAL_URI);\n\n    POLL_FOR(pub->findDestinationResponse(dest1CorrelationId), invoker());\n    POLL_FOR(pub->findDestinationResponse(dest2CorrelationId), invoker());\n\n    POLL_FOR(sub1->isConnected(), invoker());\n    POLL_FOR(sub2->isConnected(), invoker());\n\n    POLL_FOR(0 < pub->offer(buffer, 0, 128), invoker());\n\n    POLL_FOR(0 < sub1->poll(m_noOpHandler, 1), invoker());\n\n    POLL_FOR(0 < sub2->poll(m_noOpHandler, 1), invoker());\n\n    int64_t removeDestCorrelationId = pub->removeDestination(dest1CorrelationId);\n\n    POLL_FOR(pub->findDestinationResponse(removeDestCorrelationId), invoker());\n\n    POLL_FOR(0 < pub->offer(buffer, 0, 128), invoker());\n    POLL_FOR(0 < sub2->poll(m_noOpHandler, 1), invoker());\n\n    EXPECT_EQ(0, sub2->poll(m_noOpHandler, 1));\n}\n\nTEST_F(MultiDestinationByIdTest, shouldAddRemoveDestinationFromExclusivePublicationById)\n{\n    auto sub1RegId = m_aeron->addSubscription(SUB1_MDC_MANUAL_URI, streamId);\n    auto sub2RegId = m_aeron->addSubscription(SUB2_MDC_MANUAL_URI, streamId);\n    auto pubRegId = m_aeron->addExclusivePublication(PUB_MDC_MANUAL_URI, streamId);\n\n    POLL_FOR_NON_NULL(sub1, m_aeron->findSubscription(sub1RegId), invoker());\n    POLL_FOR_NON_NULL(sub2, m_aeron->findSubscription(sub2RegId), invoker());\n    POLL_FOR_NON_NULL(pub, m_aeron->findExclusivePublication(pubRegId), invoker());\n\n    std::int64_t dest1CorrelationId = pub->addDestination(SUB1_MDC_MANUAL_URI);\n    std::int64_t dest2CorrelationId = pub->addDestination(SUB2_MDC_MANUAL_URI);\n\n    POLL_FOR(pub->findDestinationResponse(dest1CorrelationId), invoker());\n    POLL_FOR(pub->findDestinationResponse(dest2CorrelationId), invoker());\n\n    POLL_FOR(sub1->isConnected(), invoker());\n    POLL_FOR(sub2->isConnected(), invoker());\n\n    POLL_FOR(0 < pub->offer(buffer, 0, 128), invoker());\n\n    POLL_FOR(0 < sub1->poll(m_noOpHandler, 1), invoker());\n\n    POLL_FOR(0 < sub2->poll(m_noOpHandler, 1), invoker());\n\n    int64_t removeDestCorrelationId = pub->removeDestination(dest1CorrelationId);\n\n    POLL_FOR(pub->findDestinationResponse(removeDestCorrelationId), invoker());\n\n    POLL_FOR(0 < pub->offer(buffer, 0, 128), invoker());\n    POLL_FOR(0 < sub2->poll(m_noOpHandler, 1), invoker());\n\n    EXPECT_EQ(0, sub2->poll(m_noOpHandler, 1));\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/MultiDestinationTest.cpp",
    "content": "\n/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"ChannelUriStringBuilder.h\"\n#include \"TestUtil.h\"\n\nusing namespace aeron;\nusing testing::MockFunction;\nusing testing::_;\n\n#define PUB_MDC_MANUAL_URI \"aeron:udp?control-mode=manual|tags=3,4\"\n#define SUB1_MDC_MANUAL_URI \"aeron:udp?endpoint=localhost:24326|group=true\"\n#define SUB2_MDC_MANUAL_URI \"aeron:udp?endpoint=localhost:24327|group=true\"\n\n#define UNICAST_ENDPOINT_A \"localhost:24325\"\n#define UNICAST_ENDPOINT_B \"localhost:24326\"\n#define SUB_URI \"aeron:udp?control-mode=manual\"\n\n#define streamId INT32_C(1001)\n\nclass MultiDestinationTest : public testing::TestWithParam<std::tuple<const char *, const char *>>\n{\npublic:\n    MultiDestinationTest()\n    {\n        m_driver.start();\n    }\n\n    ~MultiDestinationTest() override\n    {\n        m_driver.stop();\n    }\n\nprotected:\n    void SetUp() override\n    {\n        Context ctx;\n        ctx.useConductorAgentInvoker(true);\n        m_aeron = Aeron::connect(ctx);\n    }\n\n    void TearDown() override\n    {\n        invoker().invoke();\n        m_aeron = nullptr;\n    }\n\n    AgentInvoker<ClientConductor> invoker()\n    {\n        return m_aeron->conductorAgentInvoker();\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n    fragment_handler_t m_noOpHandler =\n        [&](concurrent::AtomicBuffer &b, util::index_t offset, util::index_t length, Header &header) {};\n    std::shared_ptr<Aeron> m_aeron;\n    std::array<std::uint8_t, 1024> buf = {};\n    AtomicBuffer buffer{buf};\n\n};\n\ntypedef std::array<std::uint8_t, 1024> buffer_t;\n\nTEST_F(MultiDestinationTest, shouldAddRemoveDestinationFromPublication)\n{\n    auto sub1RegId = m_aeron->addSubscription(SUB1_MDC_MANUAL_URI, streamId);\n    auto sub2RegId = m_aeron->addSubscription(SUB2_MDC_MANUAL_URI, streamId);\n    auto pubRegId = m_aeron->addPublication(PUB_MDC_MANUAL_URI, streamId);\n\n    POLL_FOR_NON_NULL(sub1, m_aeron->findSubscription(sub1RegId), invoker());\n    POLL_FOR_NON_NULL(sub2, m_aeron->findSubscription(sub2RegId), invoker());\n    POLL_FOR_NON_NULL(pub, m_aeron->findPublication(pubRegId), invoker());\n\n    std::int64_t dest1CorrelationId = pub->addDestination(SUB1_MDC_MANUAL_URI);\n    std::int64_t dest2CorrelationId = pub->addDestination(SUB2_MDC_MANUAL_URI);\n\n    POLL_FOR(pub->findDestinationResponse(dest1CorrelationId), invoker());\n    POLL_FOR(pub->findDestinationResponse(dest2CorrelationId), invoker());\n\n    POLL_FOR(sub1->isConnected(), invoker());\n    POLL_FOR(sub2->isConnected(), invoker());\n\n    POLL_FOR(0 < pub->offer(buffer, 0, 128), invoker());\n\n    POLL_FOR(0 < sub1->poll(m_noOpHandler, 1), invoker());\n\n    POLL_FOR(0 < sub2->poll(m_noOpHandler, 1), invoker());\n\n    int64_t removeDestCorrelationId = pub->removeDestination(SUB1_MDC_MANUAL_URI);\n\n    POLL_FOR(pub->findDestinationResponse(removeDestCorrelationId), invoker());\n\n    POLL_FOR(0 < pub->offer(buffer, 0, 128), invoker());\n    POLL_FOR(0 < sub2->poll(m_noOpHandler, 1), invoker());\n\n    EXPECT_EQ(0, sub2->poll(m_noOpHandler, 1));\n}\n\nTEST_F(MultiDestinationTest, shouldAddRemoveDestinationFromExclusivePublication)\n{\n#ifdef AERON_SANITIZE_ENABLED\n    GTEST_SKIP(); // Currently breaks the sanitizer due to the structure of the API.\n#endif\n\n    auto sub1RegId = m_aeron->addSubscription(SUB1_MDC_MANUAL_URI, streamId);\n    auto sub2RegId = m_aeron->addSubscription(SUB2_MDC_MANUAL_URI, streamId);\n    auto pubRegId = m_aeron->addExclusivePublication(PUB_MDC_MANUAL_URI, streamId);\n\n    POLL_FOR_NON_NULL(sub1, m_aeron->findSubscription(sub1RegId), invoker());\n    POLL_FOR_NON_NULL(sub2, m_aeron->findSubscription(sub2RegId), invoker());\n    POLL_FOR_NON_NULL(pub, m_aeron->findExclusivePublication(pubRegId), invoker());\n\n    pub->addDestination(SUB1_MDC_MANUAL_URI);\n    pub->addDestination(SUB2_MDC_MANUAL_URI);\n\n    POLL_FOR(sub1->isConnected(), invoker());\n    POLL_FOR(sub2->isConnected(), invoker());\n\n    POLL_FOR(0 < pub->offer(buffer, 0, 128), invoker());\n\n    POLL_FOR(0 < sub1->poll(m_noOpHandler, 1), invoker());\n\n    POLL_FOR(0 < sub2->poll(m_noOpHandler, 1), invoker());\n\n    pub->removeDestination(SUB1_MDC_MANUAL_URI);\n\n    // The existing C++ API for ExclusivePublications is missing the means to track the add and removal of\n    // destinations.  This is fixed in the wrapper, but the test is written for compatibility with\n    // both APIs so has to take a few liberties in order to work correctly.\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n\n    POLL_FOR(0 < pub->offer(buffer, 0, 128), invoker());\n    POLL_FOR(0 < sub2->poll(m_noOpHandler, 1), invoker());\n\n    EXPECT_EQ(0, sub2->poll(m_noOpHandler, 1));\n}\n\nTEST_F(MultiDestinationTest, shouldAddAndRemoveDestinationsFromSubscription)\n{\n    std::string channel1 = ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(UNICAST_ENDPOINT_A)\n        .build();\n    std::string channel2 = ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(UNICAST_ENDPOINT_B)\n        .build();\n\n    auto pub1RegId = m_aeron->addPublication(channel1, streamId);\n    auto pub2RegId = m_aeron->addPublication(channel2, streamId);\n    auto subRegId = m_aeron->addSubscription(SUB_URI, streamId);\n\n    POLL_FOR_NON_NULL(pub1, m_aeron->findPublication(pub1RegId), invoker());\n    POLL_FOR_NON_NULL(pub2, m_aeron->findPublication(pub2RegId), invoker());\n    POLL_FOR_NON_NULL(sub, m_aeron->findSubscription(subRegId), invoker());\n\n    std::int64_t subDest1 = sub->addDestination(channel1);\n    std::int64_t subDest2 = sub->addDestination(channel2);\n\n    POLL_FOR(sub->findDestinationResponse(subDest1), invoker());\n    POLL_FOR(sub->findDestinationResponse(subDest2), invoker());\n\n    POLL_FOR(0 < pub1->offer(buffer, 0, 128), invoker());\n    POLL_FOR(0 < sub->poll(m_noOpHandler, 1), invoker());\n    POLL_FOR(0 < pub2->offer(buffer, 0, 128), invoker());\n    POLL_FOR(0 < sub->poll(m_noOpHandler, 1), invoker());\n\n    int64_t removeCorrelationId = sub->removeDestination(channel1);\n    POLL_FOR(sub->findDestinationResponse(removeCorrelationId), invoker());\n\n\n    POLL_FOR(0 < pub1->offer(buffer, 0, 128), invoker());\n    POLL_FOR(0 < pub2->offer(buffer, 0, 128), invoker());\n    POLL_FOR(0 < sub->poll(m_noOpHandler, 1), invoker());\n\n    EXPECT_EQ(0, sub->poll(m_noOpHandler, 1));\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/PubSubTest.cpp",
    "content": "\n/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n#include <random>\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"ChannelUriStringBuilder.h\"\n#include \"TestUtil.h\"\n#include \"FragmentAssembler.h\"\n\n\nusing namespace aeron;\nusing testing::MockFunction;\nusing testing::_;\n\nclass PubSubTest : public testing::TestWithParam<std::tuple<const char *, const char *>>\n{\npublic:\n    PubSubTest()\n    {\n        m_driver.start();\n    }\n\n    ~PubSubTest() override\n    {\n        m_driver.stop();\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n};\n\ntypedef std::array<std::uint8_t, 1024> buffer_t;\nstatic const int dataHeaderLength = 32;\n\nINSTANTIATE_TEST_SUITE_P(\n    PubSubTest,\n    PubSubTest,\n    testing::Values(\n        std::make_tuple(\"udp\", \"localhost:24325\"),\n        std::make_tuple(\"udp\", \"224.20.30.39:24326\"),\n        std::make_tuple(\"ipc\", nullptr)\n    ));\n\nTEST_P(PubSubTest, shouldSubscribePublishAndReceiveContextCallbacks)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    std::int32_t sessionId = 908712342;\n    std::int32_t imageUnavailable = 0;\n\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .sessionId(sessionId)\n        .networkInterface(\"localhost\")\n        .build();\n\n    std::int64_t reservedValue = INT64_C(78923648723465);\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    MockFunction<void(\n        const std::string &channel,\n        std::int32_t streamId,\n        std::int32_t sessionId,\n        std::int64_t correlationId)> mockOnNewPublication;\n\n    MockFunction<void(\n        const std::string &channel,\n        std::int32_t streamId,\n        std::int64_t correlationId)> mockOnNewSubscription;\n\n    MockFunction<void(Image &image)> mockOnAvailableImage;\n    MockFunction<void(Image &image)> mockOnUnavailableImage;\n    MockFunction<void()> mockClientClose;\n\n    EXPECT_CALL(mockOnNewPublication, Call(channel, streamId, sessionId, _));\n    EXPECT_CALL(mockOnNewSubscription, Call(channel, streamId, _));\n    EXPECT_CALL(mockOnAvailableImage, Call(_));\n    EXPECT_CALL(mockOnUnavailableImage, Call(_)).WillOnce(\n        [&](Image &image)\n        {\n            aeron::concurrent::atomic::putInt32Volatile(&imageUnavailable, 1);\n        });;\n    EXPECT_CALL(mockClientClose, Call());\n\n    ctx.newPublicationHandler(mockOnNewPublication.AsStdFunction());\n    ctx.newSubscriptionHandler(mockOnNewSubscription.AsStdFunction());\n    ctx.availableImageHandler(mockOnAvailableImage.AsStdFunction());\n    ctx.unavailableImageHandler(mockOnUnavailableImage.AsStdFunction());\n    ctx.closeClientHandler(mockClientClose.AsStdFunction());\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId = aeron->addPublication(channel, streamId);\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        {\n            POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);\n            POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n            on_reserved_value_supplier_t reservedValueSupplier =\n                [=](AtomicBuffer &termBuffer, util::index_t termOffset, util::index_t length)\n                {\n                    return reservedValue;\n                };\n\n            std::string message = \"hello world!\";\n            std::int32_t length = buffer.putString(0, message);\n            const std::int64_t expectedPosition = util::BitUtil::align(\n                dataHeaderLength + length, FrameDescriptor::FRAME_ALIGNMENT);\n\n            POLL_FOR(0 < pub->offer(buffer, 0, length, reservedValueSupplier), invoker);\n            POLL_FOR(0 < sub->poll(\n                [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n                {\n                    EXPECT_EQ(message, buffer.getString(offset));\n                    EXPECT_EQ(reservedValue, header.reservedValue());\n                    EXPECT_EQ(sessionId, header.sessionId());\n                    EXPECT_EQ(streamId, header.streamId());\n                    EXPECT_EQ(length, header.frameLength() - dataHeaderLength);\n                    EXPECT_EQ(expectedPosition, header.position());\n                },\n                1), invoker);\n        }\n\n        POLL_FOR(1 == aeron::concurrent::atomic::getInt32Volatile(&imageUnavailable), invoker);\n    }\n\n    invoker.invoke();\n}\n\nTEST_P(PubSubTest, shouldSubscribePublishAndReceiveSubscriptionCallbacks)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .networkInterface(\"localhost\")\n        .build();\n    std::int32_t imageUnavailable = 0;\n    Context ctx;\n\n    MockFunction<void(Image &image)> mockOnAvailableImage;\n    MockFunction<void(Image &image)> mockOnUnavailableImage;\n\n    EXPECT_CALL(mockOnAvailableImage, Call(_));\n    EXPECT_CALL(mockOnUnavailableImage, Call(_)).WillOnce(\n        [&](Image &image)\n        {\n            aeron::concurrent::atomic::putInt32Volatile(&imageUnavailable, 1);\n        });\n\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(\n        channel, streamId, mockOnAvailableImage.AsStdFunction(), mockOnUnavailableImage.AsStdFunction());\n    std::int64_t pubId = aeron->addPublication(channel, streamId);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n\n    {\n        // Nest to trigger subscription cleanup\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        {\n            // Nest to trigger images becoming unavailable\n            POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);\n            POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n            std::string message = \"hello world!\";\n            buffer.putString(0, message);\n            POLL_FOR(0 < pub->offer(buffer), invoker);\n\n            POLL_FOR(\n                0 < sub->poll(\n                [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n                {\n                    EXPECT_EQ(message, buffer.getString(offset));\n                },\n                1),\n                invoker);\n        }\n\n        POLL_FOR(1 == aeron::concurrent::atomic::getInt32Volatile(&imageUnavailable), invoker);\n    }\n\n    // Allow callbacks to fire to complete cleanup and prevent sanitizer errors.\n    invoker.invoke();\n}\n\nTEST_P(PubSubTest, shouldSubscribeExclusivePublish)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    std::int32_t termId = 23;\n    std::int32_t initialTermId = 3;\n    std::int32_t termOffset = 1024;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .termId(termId)\n        .termOffset(termOffset)\n        .initialTermId(initialTermId)\n        .networkInterface(\"localhost\")\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId = aeron->addExclusivePublication(channel, streamId);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker);\n        POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        buffer.putString(0, message);\n        POLL_FOR(0 < pub->offer(buffer), invoker);\n\n        POLL_FOR(0 < sub->poll(\n            [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n            {\n                EXPECT_EQ(message, buffer.getString(offset));\n                EXPECT_EQ(termId, header.termId());\n                EXPECT_EQ(termOffset, header.termOffset());\n                EXPECT_EQ(initialTermId, header.initialTermId());\n            },\n            1), invoker);\n    }\n\n    invoker.invoke();\n}\n\nTEST_P(PubSubTest, shouldBlockPollSubscription)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .networkInterface(\"localhost\")\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId = aeron->addPublication(channel, streamId);\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);\n        POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        buffer.putString(0, message);\n        AtomicBuffer buffers[]{buffer, buffer, buffer};\n        POLL_FOR(0 < pub->offer(buffers, 3), invoker);\n\n        std::int64_t bytesReceived = 0;\n        std::int64_t bytesConsumed = 0;\n        POLL_FOR(pub->position() <= (\n            bytesConsumed += sub->blockPoll(\n                [&](concurrent::AtomicBuffer &buffer,\n                    util::index_t offset,\n                    util::index_t length,\n                    std::int32_t sessionId,\n                    std::int32_t termId)\n                {\n                    bytesReceived += length;\n                    EXPECT_EQ(pub->sessionId(), sessionId);\n                },\n                100000)), invoker);\n\n        EXPECT_EQ(pub->position(), bytesConsumed);\n        EXPECT_EQ(pub->position(), bytesReceived);\n    }\n\n    invoker.invoke();\n}\n\nTEST_P(PubSubTest, shouldTryClaimAndControlledPollSubscription)\n{\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .networkInterface(\"localhost\")\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId = aeron->addPublication(channel, streamId);\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);\n        POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n\n        BufferClaim claim;\n        POLL_FOR(0 < pub->tryClaim(16, claim), invoker);\n        claim.buffer().putString(claim.offset(), message);\n        claim.commit();\n        bool seen = false;\n\n        POLL_FOR(\n            0 < sub->controlledPoll(\n                [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n                {\n                    seen = true;\n                    return message == buffer.getString(offset) ?\n                        ControlledPollAction::COMMIT : ControlledPollAction::ABORT;\n                },\n                1),\n            invoker);\n\n        EXPECT_TRUE(seen);\n    }\n\n    invoker.invoke();\n}\n\n\nTEST_P(PubSubTest, shouldExclusivePublicationTryClaimAndControlledPollSubscription)\n{\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .networkInterface(\"localhost\")\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId = aeron->addExclusivePublication(channel, streamId);\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker);\n        POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n\n        BufferClaim claim;\n        POLL_FOR(0 < pub->tryClaim(16, claim), invoker);\n        claim.buffer().putString(claim.offset(), message);\n        claim.commit();\n        bool seen = false;\n\n        POLL_FOR(\n            0 < sub->controlledPoll(\n                [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n                {\n                    seen = true;\n                    return message == buffer.getString(offset) ?\n                    ControlledPollAction::COMMIT : ControlledPollAction::ABORT;\n                },\n                1),\n            invoker);\n\n        EXPECT_TRUE(seen);\n    }\n\n    invoker.invoke();\n}\n\nTEST_P(PubSubTest, shouldHandleEosPosition)\n{\n    buffer_t buf;\n    AtomicBuffer message(buf);\n    message.putString(0, \"hello world!\");\n\n    std::int32_t streamId = 982375;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(std::get<0>(GetParam()), std::get<1>(GetParam()), uriBuilder)\n        .networkInterface(\"localhost\")\n        .build();\n    Context ctx;\n\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId = aeron->addPublication(channel, streamId);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    const int numMessages = 10;\n    int messagesRemaining = numMessages;\n\n    {\n        // Nest to trigger subscription cleanup\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        {\n            // Nest to trigger images becoming unavailable\n            POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);\n            POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n            std::shared_ptr<Image> image = sub->imageByIndex(0);\n\n            ASSERT_FALSE(image->isEndOfStream());\n            ASSERT_EQ(INT64_MAX, image->endOfStreamPosition());\n\n            for (int i = 0; i < numMessages; i++)\n            {\n                POLL_FOR(0 < pub->offer(message), invoker);\n            }\n\n            std::int64_t tStart = aeron_epoch_clock();\n            while (0 < messagesRemaining)\n            {\n                int pollCount = sub->poll(\n                    [&](\n                        concurrent::AtomicBuffer &buffer,\n                        util::index_t offset,\n                        util::index_t length,\n                        Header &header)\n                    {}, 1);\n\n                if (0 == pollCount)\n                {\n                    invoker.invoke();\n                    ASSERT_LT(aeron_epoch_clock() - tStart, AERON_TEST_TIMEOUT) << \"Failed waiting\";\n                    std::this_thread::yield();\n                }\n\n                messagesRemaining -= pollCount;\n            }\n\n            ASSERT_FALSE(image->isEndOfStream());\n            ASSERT_EQ(INT64_MAX, image->endOfStreamPosition());\n\n            for (int i = 0; i < numMessages; i++)\n            {\n                POLL_FOR(0 < pub->offer(message), invoker);\n            }\n\n            tStart = aeron_epoch_clock();\n            messagesRemaining = numMessages;\n            while (5 < messagesRemaining)\n            {\n                int pollCount = sub->poll(\n                    [&](\n                        concurrent::AtomicBuffer &buffer,\n                        util::index_t offset,\n                        util::index_t length,\n                        Header &header)\n                    {}, 1);\n\n                if (0 == pollCount)\n                {\n                    invoker.invoke();\n                    ASSERT_LT(aeron_epoch_clock() - tStart, AERON_TEST_TIMEOUT) << \"Failed waiting\";\n                    std::this_thread::yield();\n                }\n\n                messagesRemaining -= pollCount;\n            }\n\n            ASSERT_FALSE(image->isEndOfStream());\n            ASSERT_EQ(INT64_MAX, image->endOfStreamPosition());\n        } // Close the publication by having it go out of scope.\n\n        std::shared_ptr<Image> image = sub->imageByIndex(0);\n        ASSERT_FALSE(image->isEndOfStream());\n\n        std::int64_t tStart = aeron_epoch_clock();\n        while (0 < messagesRemaining)\n        {\n            int pollCount = sub->poll([&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header) {}, 1);\n\n            if (0 == pollCount)\n            {\n                invoker.invoke();\n                ASSERT_LT(aeron_epoch_clock() - tStart, AERON_TEST_TIMEOUT) << \"Failed waiting\";\n                std::this_thread::yield();\n            }\n\n            messagesRemaining -= pollCount;\n        }\n\n        POLL_FOR(image->isEndOfStream(), invoker);\n        POLL_FOR(INT64_MAX != image->endOfStreamPosition(), invoker);\n        std::int64_t endOfStreamPosition = image->endOfStreamPosition();\n        ASSERT_EQ(endOfStreamPosition, image->position());\n    }\n\n    // Allow callbacks to fire to complete cleanup and prevent sanitizer errors.\n    invoker.invoke();\n}\n\n\n// Useful to look at error stack when manually testing error conditions.\nTEST_F(PubSubTest, DISABLED_shouldError)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t streamId = 982375;\n    std::int32_t termId = 23;\n    std::int32_t initialTermId = 3;\n    std::int32_t termOffset = 1024;\n    ChannelUriStringBuilder uriBuilder;\n    const std::string channel = setParameters(\"udp\", \"224.0.1.1:40456\", uriBuilder)\n        .termId(termId)\n        .termOffset(termOffset)\n        .initialTermId(initialTermId)\n        .networkInterface(\"localhost\")\n        .build();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    std::int64_t subId = aeron->addSubscription(channel, streamId);\n    std::int64_t pubId = aeron->addExclusivePublication(channel, streamId);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker);\n        POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        buffer.putString(0, message);\n        POLL_FOR(0 < pub->offer(buffer), invoker);\n\n        POLL_FOR(0 < sub->poll(\n            [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n            {\n                EXPECT_EQ(message, buffer.getString(offset));\n                EXPECT_EQ(termId, header.termId());\n                EXPECT_EQ(termOffset, header.termOffset());\n                EXPECT_EQ(initialTermId, header.initialTermId());\n            },\n            1), invoker);\n    }\n\n    invoker.invoke();\n}\n\nclass Exchanger\n{\n    using generator_t = std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned short>;\n\npublic:\n    explicit Exchanger(\n        const std::shared_ptr<Subscription> &subscription,\n        const std::shared_ptr<ExclusivePublication> &publication,\n        AgentInvoker<ClientConductor> &invoker) :\n        m_subscription(subscription),\n        m_publication(publication),\n        m_assembler(\n             [&](AtomicBuffer &buffer, index_t offset, index_t length, Header &header)\n             {\n                 m_innerHandler(buffer, offset, length, header);\n             }),\n        m_outerHandler(m_assembler.handler()),\n        m_generator(generator_t(m_rd())),\n        m_invoker(invoker)\n    {\n    }\n\n    void exchange(int messageSize)\n    {\n        std::vector<uint8_t> vec(messageSize);\n        std::generate(std::begin(vec), std::end(vec), [&] () { return static_cast<uint8_t>(m_generator()); } );\n\n        AtomicBuffer buffer(vec.data(), messageSize);\n        int64_t result;\n        while((result = m_publication->offer(buffer)) < 0)\n        {\n            if (AERON_PUBLICATION_BACK_PRESSURED != result && AERON_PUBLICATION_ADMIN_ACTION != result)\n            {\n                FAIL() << \"offer failed: \" << result;\n            }\n            std::this_thread::yield();\n        }\n\n        int count = 0;\n        m_innerHandler = [&](AtomicBuffer &buffer, index_t offset, index_t length, Header &header)\n        {\n            count++;\n            ASSERT_EQ(messageSize, length);\n            ASSERT_EQ(0, memcmp(buffer.buffer() + offset, vec.data(), length));\n        };\n\n        std::int64_t t0 = aeron_epoch_clock();\n        while (count == 0)\n        {\n            m_invoker.invoke();\n            m_subscription->poll(m_outerHandler, 10);\n            ASSERT_LT(aeron_epoch_clock() - t0, AERON_TEST_TIMEOUT) << \"Failed waiting for: count > 0\";\n            std::this_thread::yield();\n        }\n\n        ASSERT_EQ(1, count);\n    }\n\nprivate:\n    std::shared_ptr<Subscription> m_subscription;\n    std::shared_ptr<ExclusivePublication> m_publication;\n    FragmentAssembler m_assembler;\n    fragment_handler_t m_outerHandler;\n    fragment_handler_t m_innerHandler;\n    std::random_device m_rd;\n    generator_t m_generator;\n    AgentInvoker<ClientConductor> &m_invoker;\n};\n\nTEST_F(PubSubTest, shouldFragmentAndReassembleMessagesIfNeeded)\n{\n    const int32_t streamId = 1000;\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    int64_t subscriptionId = aeron->addSubscription(IPC_CHANNEL, streamId);\n    int64_t publicationId = aeron->addExclusivePublication(IPC_CHANNEL, streamId);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n\n    {\n        POLL_FOR_NON_NULL(subscription, aeron->findSubscription(subscriptionId), invoker);\n        POLL_FOR_NON_NULL(publication, aeron->findExclusivePublication(publicationId), invoker);\n        POLL_FOR(publication->isConnected(), invoker);\n\n        Exchanger exchanger(subscription, publication, invoker);\n        exchanger.exchange(publication->maxPayloadLength() + 1);\n        exchanger.exchange(publication->maxPayloadLength() * 3);\n        exchanger.exchange(32);\n    }\n\n    invoker.invoke();\n}\n\nTEST_F(PubSubTest, shouldCreatePublicationUsingAsyncApi)\n{\n    const int32_t streamId = 1000;\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n\n    auto asyncPublication = aeron->addPublicationAsync(IPC_CHANNEL, streamId);\n    ASSERT_NE(nullptr, asyncPublication);\n    const int64_t registrationId = aeron->addPublicationAsyncGetRegistrationId(asyncPublication);\n\n    POLL_FOR_NON_NULL(publication, aeron->findPublication(asyncPublication), invoker);\n    ASSERT_EQ(registrationId, publication->registrationId());\n}\n\nTEST_F(PubSubTest, shouldCreateExclusivePublicationUsingAsyncApi)\n{\n    const int32_t streamId = 231;\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n\n    auto asyncPublication = aeron->addExclusivePublicationAsync(IPC_CHANNEL, streamId);\n    ASSERT_NE(nullptr, asyncPublication);\n    const int64_t registrationId = aeron->addExclusivePublicationAsyncGetRegistrationId(asyncPublication);\n\n    POLL_FOR_NON_NULL(publication, aeron->findExclusivePublication(asyncPublication), invoker);\n    ASSERT_EQ(registrationId, publication->registrationId());\n}\n\nTEST_F(PubSubTest, shouldCreateSubscriptionUsingAsyncApi)\n{\n    const int32_t streamId = 231;\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n\n    auto asyncSubscription = aeron->addSubscriptionAsync(IPC_CHANNEL, streamId);\n    ASSERT_NE(nullptr, asyncSubscription);\n    const int64_t registrationId = aeron->addSubscriptionAsyncGetRegistrationId(asyncSubscription);\n\n    POLL_FOR_NON_NULL(subscription, aeron->findSubscription(asyncSubscription), invoker);\n    ASSERT_EQ(registrationId, subscription->registrationId());\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/PublicationRevokeTest.cpp",
    "content": "\n/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"AeronCounters.h\"\n#include \"concurrent/CountersReader.h\"\n#include \"TestUtil.h\"\n\n#include \"aeron_system_counters.h\"\n\nusing namespace aeron;\nusing testing::MockFunction;\nusing testing::_;\n\n#define POLL_FOR_ONE_MESSAGE \\\ndo \\\n{ \\\n    POLL_FOR(1 == sub->poll( \\\n        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header) \\\n        { \\\n            EXPECT_EQ(message, buffer.getString(offset)); \\\n            EXPECT_EQ(streamId, header.streamId()); \\\n            EXPECT_EQ(length, header.frameLength() - dataHeaderLength); \\\n        }, \\\n        1), invoker); \\\n} \\\nwhile (0)\n\nclass PublicationRevokeTest : public testing::TestWithParam<std::tuple<const char *, const char *, const std::uint64_t>>\n{\npublic:\n    PublicationRevokeTest()\n    {\n        m_driver.start();\n    }\n\n    ~PublicationRevokeTest() override\n    {\n        m_driver.stop();\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n};\n\nstatic const int streamId = 1001;\n\ntypedef std::array<std::uint8_t, 1024> buffer_t;\nstatic const int dataHeaderLength = 32;\n\nINSTANTIATE_TEST_SUITE_P(\n    PublicationRevokeTest,\n    PublicationRevokeTest,\n    testing::Values(\n        std::make_tuple(\"aeron:udp?endpoint=localhost:24325\", \"aeron:udp?endpoint=localhost:24325\", 1),\n        std::make_tuple(\"aeron:ipc\", \"aeron:ipc\", 0),\n        std::make_tuple(\"aeron-spy:aeron:udp?endpoint=localhost:24325\", \"aeron:udp?endpoint=localhost:24325|ssc=true\", 0)\n    ));\n\nTEST_P(PublicationRevokeTest, revokeTestSimple)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t imageUnavailable = 0;\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    MockFunction<void(Image &image)> mockOnUnavailableImage;\n    EXPECT_CALL(mockOnUnavailableImage, Call(_)).WillOnce(\n        [&](Image &image)\n        {\n            ASSERT_TRUE(image.isPublicationRevoked());\n            aeron::concurrent::atomic::getAndAddInt32(&imageUnavailable, 1);\n        });;\n    ctx.unavailableImageHandler(mockOnUnavailableImage.AsStdFunction());\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> invoker = aeron->conductorAgentInvoker();\n\n    const char *subscriptionChannel = std::get<0>(GetParam());\n    const char *publicationChannel = std::get<1>(GetParam());\n    std::uint64_t  expectedPublicationImagesRevoked = std::get<2>(GetParam());\n\n    std::int64_t subId = aeron->addSubscription(subscriptionChannel, streamId);\n    std::int64_t pubId = aeron->addExclusivePublication(publicationChannel, streamId);\n\n    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n    POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker);\n    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n    std::string message = \"hello world!\";\n    std::int32_t length = buffer.putString(0, message);\n\n    POLL_FOR(0 < pub->offer(buffer, 0, length), invoker);\n\n    POLL_FOR_ONE_MESSAGE;\n\n    POLL_FOR(0 < pub->offer(buffer, 0, length), invoker);\n\n    pub->revokeOnClose();\n    pub.reset();\n\n    POLL_FOR(1 == imageUnavailable, invoker);\n\n    ASSERT_EQ(0, sub->imageCount());\n\n    ASSERT_EQ(1, aeron->countersReader().getCounterValue(AERON_SYSTEM_COUNTER_PUBLICATIONS_REVOKED));\n    ASSERT_EQ(expectedPublicationImagesRevoked, aeron->countersReader().getCounterValue(AERON_SYSTEM_COUNTER_PUBLICATION_IMAGES_REVOKED));\n}\n\nTEST_P(PublicationRevokeTest, revokeTestExclusive)\n{\n    buffer_t buf;\n    AtomicBuffer buffer(buf);\n    std::int32_t imageUnavailable = 0;\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    MockFunction<void(Image &image)> mockOnAvailableImage;\n    EXPECT_CALL(mockOnAvailableImage, Call(_)).WillRepeatedly(\n        [&](Image &image)\n        {\n        });;\n\n    bool publicationShouldBeRevoked = true;\n    MockFunction<void(Image &image)> mockOnUnavailableImage;\n    EXPECT_CALL(mockOnUnavailableImage, Call(_)).WillRepeatedly(\n        [&](Image &image)\n        {\n            ASSERT_EQ(publicationShouldBeRevoked, image.isPublicationRevoked());\n            aeron::concurrent::atomic::getAndAddInt32(&imageUnavailable, 1);\n        });;\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> invoker = aeron->conductorAgentInvoker();\n\n    const char *subscriptionChannel = std::get<0>(GetParam());\n    const char *publicationChannel = std::get<1>(GetParam());\n    std::uint64_t  expectedPublicationImagesRevoked = std::get<2>(GetParam());\n\n    std::int64_t subId = aeron->addSubscription(\n        subscriptionChannel, streamId, mockOnAvailableImage.AsStdFunction(), mockOnUnavailableImage.AsStdFunction());\n    std::int64_t pubId = aeron->addExclusivePublication(publicationChannel, streamId);\n    std::int64_t pub2Id = aeron->addExclusivePublication(publicationChannel, streamId);\n\n    POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker);\n    POLL_FOR_NON_NULL(pub2, aeron->findExclusivePublication(pub2Id), invoker);\n    {\n        POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n        POLL_FOR(sub->isConnected() && pub->isConnected() && pub2->isConnected(), invoker);\n\n        std::string message = \"hello world!\";\n        std::int32_t length = buffer.putString(0, message);\n\n        POLL_FOR(0 < pub->offer(buffer, 0, length), invoker);\n        POLL_FOR(0 < pub2->offer(buffer, 0, length), invoker);\n\n        POLL_FOR_ONE_MESSAGE;\n        POLL_FOR_ONE_MESSAGE;\n\n        POLL_FOR(0 < pub->offer(buffer, 0, length), invoker);\n\n        ASSERT_EQ(2, sub->imageCount());\n\n        pub->revoke();\n\n        ASSERT_EQ(AERON_PUBLICATION_CLOSED, pub->offer(buffer, 0, length));\n\n        POLL_FOR(1 == imageUnavailable, invoker);\n\n        ASSERT_EQ(1, sub->imageCount());\n\n        POLL_FOR(0 < pub2->offer(buffer, 0, length), invoker);\n\n        POLL_FOR_ONE_MESSAGE;\n\n        publicationShouldBeRevoked = false;\n    }\n\n    POLL_FOR(2 == imageUnavailable, invoker);\n\n    pub2.reset();\n\n    ASSERT_EQ(1, aeron->countersReader().getCounterValue(AERON_SYSTEM_COUNTER_PUBLICATIONS_REVOKED));\n    ASSERT_EQ(expectedPublicationImagesRevoked, aeron->countersReader().getCounterValue(AERON_SYSTEM_COUNTER_PUBLICATION_IMAGES_REVOKED));\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/RejectImageTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"TestUtil.h\"\n#include \"aeron_socket.h\"\n\nusing namespace aeron;\n\nclass RejectImageTestBase\n{\npublic:\n    RejectImageTestBase()\n    {\n        m_driver.start();\n    }\n\n    ~RejectImageTestBase()\n    {\n        m_driver.stop();\n    }\n\n    static std::int32_t typeId(CountersReader &reader, std::int32_t counterId)\n    {\n        const index_t offset = aeron::concurrent::CountersReader::metadataOffset(counterId);\n        return reader.metaDataBuffer().getInt32(offset + CountersReader::TYPE_ID_OFFSET);\n    }\n\n    static std::string addressAsString(int16_t addressType, uint8_t *address)\n    {\n        if (AERON_RESPONSE_ADDRESS_TYPE_IPV4 == addressType)\n        {\n            char buffer[INET_ADDRSTRLEN] = {};\n            inet_ntop(AF_INET, address, buffer, sizeof(buffer));\n            return std::string{buffer};\n        }\n        else if (AERON_RESPONSE_ADDRESS_TYPE_IPV6 == addressType)\n        {\n            char buffer[INET6_ADDRSTRLEN] = {};\n            inet_ntop(AF_INET6, address, buffer, sizeof(buffer));\n            return \"[\" + std::string{buffer} + \"]\";\n        }\n\n        return \"\";\n    }\n\n    static std::shared_ptr<Aeron> connectClient(std::atomic<std::int32_t > &counter)\n    {\n        Context ctx;\n        ctx.useConductorAgentInvoker(true);\n\n        on_publication_error_frame_t errorFrameHandler =\n            [&](aeron::status::PublicationErrorFrame &errorFrame)\n            {\n                std::atomic_fetch_add(&counter, 1);\n            };\n\n        ctx.errorFrameHandler(errorFrameHandler);\n        return Aeron::connect(ctx);\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n};\n\nclass RejectImageTestSimple : public RejectImageTestBase, public testing::Test\n{\n};\n\nclass RejectImageTest : public RejectImageTestBase, public testing::TestWithParam<std::string>\n{\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    RejectImageTestWithParam, RejectImageTest, testing::Values(\"127.0.0.1\", \"[::1]\"));\n\nTEST_P(RejectImageTest, shouldRejectImage)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::string address = GetParam();\n    std::uint16_t port = 10000;\n    std::string control = address + \":\" + std::to_string(10001);\n    std::string endpoint = address + \":\" + std::to_string(port);\n\n    aeron::status::PublicationErrorFrame error{ nullptr };\n\n    on_publication_error_frame_t errorFrameHandler =\n        [&](aeron::status::PublicationErrorFrame &errorFrame)\n        {\n            error = errorFrame;\n            return;\n        };\n\n    ctx.errorFrameHandler(errorFrameHandler);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    std::int64_t groupTag = 99999;\n    std::string mdc = \"aeron:udp?control-mode=dynamic|control=\" + control + \"|fc=tagged,g:\" + std::to_string(groupTag);\n    std::string channel = \"aeron:udp?endpoint=\" + endpoint + \"|control=\" + control + \"|gtag=\" + std::to_string(groupTag);\n\n    std::int64_t pubId = aeron->addPublication(mdc, 10000);\n    std::int64_t subId = aeron->addSubscription(channel, 10000);\n    invoker.invoke();\n\n    POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);\n    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n    std::string message = \"Hello World!\";\n\n    auto *data = reinterpret_cast<const uint8_t *>(message.c_str());\n    POLL_FOR(0 < pub->offer(data, message.length()), invoker);\n    POLL_FOR(0 < sub->poll(\n        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));\n        },\n        1), invoker);\n\n    POLL_FOR(1 == sub->imageCount(), invoker);\n\n    const std::shared_ptr<Image> image = sub->imageByIndex(0);\n    image->reject(\"No Longer Valid\");\n\n    POLL_FOR(error.isValid(), invoker);\n    ASSERT_EQ(pubId, error.registrationId());\n    ASSERT_EQ(pub->sessionId(), error.sessionId());\n    ASSERT_EQ(pub->streamId(), error.streamId());\n    ASSERT_EQ(groupTag, error.groupTag());\n    ASSERT_EQ(port, error.sourcePort());\n    ASSERT_EQ(address, addressAsString(error.sourceAddressType(), error.sourceAddress()));\n}\n\nTEST_P(RejectImageTest, shouldRejectImageForExclusive)\n{\n    std::string address = GetParam();\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::atomic<std::int32_t> errorFrameCount{0};\n\n    on_publication_error_frame_t errorFrameHandler =\n        [&](aeron::status::PublicationErrorFrame &errorFrame)\n        {\n            std::atomic_fetch_add(&errorFrameCount, 1);\n            return;\n        };\n\n    ctx.errorFrameHandler(errorFrameHandler);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    std::int64_t pubId = aeron->addExclusivePublication(\"aeron:udp?endpoint=\" + address + \":10000\", 10000);\n    std::int64_t subId = aeron->addSubscription(\"aeron:udp?endpoint=\" + address + \":10000\", 10000);\n    invoker.invoke();\n\n    POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker);\n    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n    std::string message = \"Hello World!\";\n\n    auto *data = reinterpret_cast<const uint8_t *>(message.c_str());\n    POLL_FOR(0 < pub->offer(data, message.length()), invoker);\n    POLL_FOR(0 < sub->poll(\n        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));\n        },\n        1), invoker);\n\n    POLL_FOR(1 == sub->imageCount(), invoker);\n\n    const std::shared_ptr<Image> image = sub->imageByIndex(0);\n    image->reject(\"No Longer Valid\");\n\n    POLL_FOR(0 < errorFrameCount, invoker);\n}\n\nTEST_P(RejectImageTest, shouldOnlySeePublicationErrorFramesForPublicationsAddedToTheClient)\n{\n    const std::string address = GetParam();\n    const std::string channel = \"aeron:udp?endpoint=\" + address + \":10000\";\n    const int streamId = 10000;\n\n    std::atomic<std::int32_t> errorFrameCount0{0};\n    std::shared_ptr<Aeron> aeron0 = connectClient(errorFrameCount0);\n    AgentInvoker<ClientConductor> &invoker0 = aeron0->conductorAgentInvoker();\n\n    std::atomic<std::int32_t> errorFrameCount1{0};\n    std::shared_ptr<Aeron> aeron1 = connectClient(errorFrameCount1);\n    AgentInvoker<ClientConductor> &invoker1 = aeron1->conductorAgentInvoker();\n\n    std::atomic<std::int32_t> errorFrameCount2{0};\n    std::shared_ptr<Aeron> aeron2 = connectClient(errorFrameCount2);\n    AgentInvoker<ClientConductor> &invoker2 = aeron2->conductorAgentInvoker();\n\n    invoker0.start();\n    invoker1.start();\n    invoker2.start();\n\n    std::int64_t pub0Id = aeron0->addPublication(channel, streamId);\n    std::int64_t subId = aeron0->addSubscription(channel, streamId);\n    invoker0.invoke();\n\n    POLL_FOR_NON_NULL(pub0, aeron0->findPublication(pub0Id), invoker0);\n    POLL_FOR_NON_NULL(sub, aeron0->findSubscription(subId), invoker0);\n    POLL_FOR(pub0->isConnected() && sub->isConnected(), invoker0);\n    \n    std::int64_t pub1Id = aeron1->addPublication(channel, streamId);\n    invoker1.invoke();\n    POLL_FOR_NON_NULL(pub1, aeron1->findPublication(pub1Id), invoker1);\n\n    std::string message = \"Hello World!\";\n\n    auto *data = reinterpret_cast<const uint8_t *>(message.c_str());\n    POLL_FOR(0 < pub0->offer(data, message.length()), invoker0);\n    POLL_FOR(0 < sub->poll(\n        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));\n        },\n        1), invoker0);\n\n    POLL_FOR(1 == sub->imageCount(), invoker0);\n\n    const std::shared_ptr<Image> image = sub->imageByIndex(0);\n    image->reject(\"No Longer Valid\");\n\n    POLL_FOR(0 < errorFrameCount0, invoker0);\n    POLL_FOR(0 < errorFrameCount1, invoker1);\n\n    int64_t timeout_ms = aeron_epoch_clock() + 500;\n    while (aeron_epoch_clock() < timeout_ms)\n    {\n        invoker2.invoke();\n        ASSERT_EQ(0, errorFrameCount2);\n        std::this_thread::sleep_for(std::chrono::duration<long, std::milli>(1));\n    }\n}\n\nTEST_F(RejectImageTestSimple, basic_ipc_test)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::atomic<std::int32_t> errorFrameCount{0};\n\n    on_publication_error_frame_t errorFrameHandler =\n        [&](aeron::status::PublicationErrorFrame &errorFrame)\n        {\n            std::atomic_fetch_add(&errorFrameCount, 1);\n            return;\n        };\n\n    ctx.errorFrameHandler(errorFrameHandler);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    std::int64_t pubId = aeron->addPublication(\"aeron:ipc\", 10000);\n    std::int64_t subId = aeron->addSubscription(\"aeron:ipc\", 10000);\n    invoker.invoke();\n\n    POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);\n    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n    const std::shared_ptr<Image> image = sub->imageByIndex(0);\n    image->reject(\"No Longer Valid\");\n\n    invoker.invoke();\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/ResponseChannelsTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"TestUtil.h\"\n#include \"HeartbeatTimestamp.h\"\n\n#define REQUEST_STREAM_ID (10000)\n#define RESPONSE_STREAM_ID (10001)\n#define REQUEST_ENDPOINT \"localhost:10000\"\n#define RESPONSE_ENDPOINT \"localhost:10001\"\n\nusing namespace aeron;\nusing testing::MockFunction;\nusing testing::_;\n\nclass ResponseChannelsTest: public testing::Test\n{\npublic:\n    ResponseChannelsTest()\n    {\n        m_driver.start();\n    }\n\n    ~ResponseChannelsTest() override\n    {\n        m_driver.stop();\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n};\n\nclass MultiInvoker\n{\npublic:\n    MultiInvoker(AgentInvoker<ClientConductor> &invokerA, AgentInvoker<ClientConductor> &invokerB) :\n        m_invokerA(invokerA), m_invokerB(invokerB)\n    {\n    }\n\n    int invoke()\n    {\n        return m_invokerA.invoke() + m_invokerB.invoke();\n    }\n\nprivate:\n    AgentInvoker<ClientConductor> &m_invokerA;\n    AgentInvoker<ClientConductor> &m_invokerB;\n};\n\nTEST_F(ResponseChannelsTest, shouldConnectResponseChannelUsingConcurrent)\n{\n    std::string expectedMessage = \"hello world!\";\n\n    std::array<std::uint8_t, 1024> buf{};\n    AtomicBuffer message(buf);\n    message.putString(0, expectedMessage);\n\n    Context serverCtx;\n    serverCtx.useConductorAgentInvoker(true);\n\n    Context clientCtx;\n    clientCtx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> server = Aeron::connect(serverCtx);\n    AgentInvoker<ClientConductor> &serverInvoker = server->conductorAgentInvoker();\n\n    std::shared_ptr<Aeron> client = Aeron::connect(clientCtx);\n    AgentInvoker<ClientConductor> &clientInvoker = client->conductorAgentInvoker();\n    MultiInvoker invoker{clientInvoker, serverInvoker};\n\n    ChannelUriStringBuilder builder;\n    builder.media(\"udp\").endpoint(REQUEST_ENDPOINT).build();\n\n    std::int64_t subReqId = server->addSubscription(\n        builder.clear().media(\"udp\").endpoint(REQUEST_ENDPOINT).build(),\n        REQUEST_STREAM_ID);\n    std::int64_t subRspId = client->addSubscription(\n        builder.clear().media(\"udp\").controlEndpoint(RESPONSE_ENDPOINT).controlMode(CONTROL_MODE_RESPONSE).build(),\n        RESPONSE_STREAM_ID);\n\n    POLL_FOR_NON_NULL(subReq, server->findSubscription(subReqId), invoker);\n    POLL_FOR_NON_NULL(subRsp, client->findSubscription(subRspId), invoker);\n\n    std::int64_t pubReqId = client->addPublication(\n        builder.clear().media(\"udp\").endpoint(REQUEST_ENDPOINT).responseCorrelationId(subRsp->registrationId()).build(),\n        REQUEST_STREAM_ID);\n\n    POLL_FOR_NON_NULL(pubReq, client->findPublication(pubReqId), invoker);\n    POLL_FOR(pubReq->isConnected() && subReq->isConnected(), invoker);\n\n    auto image = subReq->imageByIndex(0);\n    std::int64_t pubRspId = server->addPublication(\n        builder.clear().media(\"udp\").controlMode(CONTROL_MODE_RESPONSE).controlEndpoint(RESPONSE_ENDPOINT).responseCorrelationId(image->correlationId()).build(),\n        RESPONSE_STREAM_ID);\n    POLL_FOR_NON_NULL(rspPub, server->findPublication(pubRspId), invoker);\n    POLL_FOR(rspPub->isConnected() && subRsp->isConnected(), invoker);\n\n    POLL_FOR(0 < pubReq->offer(message), invoker);\n    POLL_FOR(0 < subReq->poll(\n        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            POLL_FOR(rspPub->offer(buffer, offset, length), invoker);\n        },\n        1), invoker);\n\n    POLL_FOR(0 < subRsp->poll(\n        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            const std::string actualMessage = buffer.getString(offset);\n            ASSERT_EQ(expectedMessage, actualMessage);\n        },\n        1), invoker);\n\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/SystemTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"TestUtil.h\"\n\nextern \"C\"\n{\n#include \"aeron_image.h\"\n}\n\nusing namespace aeron;\n\nclass SystemTest : public testing::Test\n{\npublic:\n    SystemTest()\n    {\n        m_driver.start();\n    }\n\n    ~SystemTest() override\n    {\n        m_driver.stop();\n    }\n\n    static std::int32_t typeId(CountersReader &reader, std::int32_t counterId)\n    {\n        const index_t offset = aeron::concurrent::CountersReader::metadataOffset(counterId);\n        return reader.metaDataBuffer().getInt32(offset + CountersReader::TYPE_ID_OFFSET);\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n};\n\nTEST_F(SystemTest, shouldReclaimAsyncResourcesOnShutdown)\n{\n    std::shared_ptr<Aeron> aeron = Aeron::connect();\n\n    aeron->addSubscription(\"aeron:udp?endpoint=localhost:24325\", 1000);\n    aeron->addPublication(\"aeron:udp?endpoint=localhost:24325\", 1000);\n    aeron->addExclusivePublication(\"aeron:ipc\", 3000);\n    aeron->addCounter(1818, nullptr, 0, \"label\");\n    aeron->addCounter(8888, nullptr, 0, \"another\");\n}\n\nTEST_F(SystemTest, shouldGetDefaultPath)\n{\n    const std::string defaultPath = Context::defaultAeronPath();\n    EXPECT_GT(defaultPath.length(), 0U);\n}\n\nTEST_F(SystemTest, shouldAddRemoveAvailableCounterHandlers)\n{\n    const int counterTypeId = 1001;\n    int staticAvailable = 0;\n    int staticUnavailable = 0;\n    int dynamicAvailable = 0;\n    int dynamicUnavailable = 0;\n    std::uint64_t key1 = 982374234;\n    std::uint64_t key2 = key1 + 1;\n    std::uint8_t key[8];\n\n    on_available_counter_t staticAvailableHandler =\n        [&](CountersReader &countersReader, std::int64_t registrationId, std::int32_t counterId)\n        {\n            if (counterTypeId == typeId(countersReader, counterId))\n            {\n                staticAvailable++;\n            }\n        };\n\n    on_available_counter_t staticUnavailableHandler =\n        [&](CountersReader &countersReader, std::int64_t registrationId, std::int32_t counterId)\n        {\n            if (counterTypeId == typeId(countersReader, counterId))\n            {\n                staticUnavailable++;\n            }\n        };\n\n    on_available_counter_t dynamicAvailableHandler =\n        [&](CountersReader &countersReader, std::int64_t registrationId, std::int32_t counterId)\n        {\n            if (counterTypeId == typeId(countersReader, counterId))\n            {\n                dynamicAvailable++;\n            }\n        };\n\n    on_available_counter_t dynamicUnavailableHandler =\n        [&](CountersReader &countersReader, std::int64_t registrationId, std::int32_t counterId)\n        {\n            if (counterTypeId == typeId(countersReader, counterId))\n            {\n                dynamicUnavailable++;\n            }\n        };\n\n    Context ctx;\n    ctx.availableCounterHandler(staticAvailableHandler);\n    ctx.unavailableCounterHandler(staticUnavailableHandler);\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    std::int64_t availableRegId = aeron->addAvailableCounterHandler(dynamicAvailableHandler);\n    std::int64_t unavailableRegId = aeron->addUnavailableCounterHandler(dynamicUnavailableHandler);\n    invoker.invoke();\n\n    ::memcpy(key, &key1, sizeof(key));\n    const std::int64_t regId1 = aeron->addCounter(counterTypeId, key, sizeof(key), \"my label\");\n\n    POLL_FOR(1 == staticAvailable, invoker);\n    ASSERT_EQ(1, dynamicAvailable);\n\n    {\n        auto counter = aeron->findCounter(regId1);\n    }\n\n    POLL_FOR(1 == staticUnavailable, invoker);\n    ASSERT_EQ(1, dynamicUnavailable);\n\n    aeron->removeAvailableCounterHandler(availableRegId);\n    aeron->removeUnavailableCounterHandler(unavailableRegId);\n    invoker.invoke();\n\n    ::memcpy(key, &key2, sizeof(key));\n    const std::int64_t regId2 = aeron->addCounter(counterTypeId, key, sizeof(key), \"my label\");\n\n    POLL_FOR(2 == staticAvailable, invoker);\n    ASSERT_EQ(1, dynamicAvailable);\n\n    {\n        auto counter = aeron->findCounter(regId2);\n    }\n\n    POLL_FOR(2 == staticUnavailable, invoker);\n    ASSERT_EQ(1, dynamicUnavailable);\n}\n\nTEST_F(SystemTest, shouldAddRemoveCloseHandler)\n{\n    int closeCount1 = 0;\n    int closeCount2 = 0;\n\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    auto handler =\n        [&]()\n        {\n            closeCount1++;\n        };\n\n    {\n        std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n        AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n        invoker.start();\n\n        aeron->addCloseClientHandler(handler);\n        invoker.invoke();\n        std::int64_t regId2 = aeron->addCloseClientHandler(\n            [&]()\n            {\n                closeCount2++;\n            });\n        invoker.invoke();\n\n        aeron->removeCloseClientHandler(regId2);\n    }\n\n    EXPECT_EQ(1, closeCount1);\n    EXPECT_EQ(0, closeCount2);\n}\n\n//\n// These tests will fail with the sanitizer if not implemented correctly.\n//\n\nTEST_F(SystemTest, shouldFreeSubscriptionDataCorrectly)\n{\n    {\n        Context ctx;\n        ctx.useConductorAgentInvoker(false);\n\n        std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n        int64_t i = aeron->addSubscription(\"aeron:ipc\", 1000);\n        std::shared_ptr<Subscription> subscription;\n        do\n        {\n            subscription = aeron->findSubscription(i);\n        }\n        while (nullptr == subscription);\n    }\n}\n\nTEST_F(SystemTest, shouldFreeSubscriptionDataCorrectlyWithInvoker)\n{\n    {\n        Context ctx;\n        ctx.useConductorAgentInvoker(true);\n        std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n        AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n        invoker.start();\n\n        int64_t i = aeron->addSubscription(\"aeron:ipc\", 1000);\n        std::shared_ptr<Subscription> subscription;\n        do\n        {\n            invoker.invoke();\n            subscription = aeron->findSubscription(i);\n        }\n        while (nullptr == subscription);\n    }\n}\n\nclass SystemTestParameterized : public testing::TestWithParam<std::string>\n{\npublic:\n    SystemTestParameterized()\n    {\n        const auto aeronDir = Context::defaultAeronPath().append(\"-\").append(std::to_string(aeron_randomised_int32()));\n        m_driver.aeronDir(aeronDir);\n        m_driver.start();\n    }\n\n    ~SystemTestParameterized() override\n    {\n        m_driver.stop();\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    SystemTestParameterized,\n    SystemTestParameterized,\n    testing::Values(\"aeron:ipc?alias=test|term-length=64k\", \"aeron:udp?alias=test|endpoint=localhost:8092|term-length=64k\"));\n\nTEST_P(SystemTestParameterized, shouldFreeUnavailableImage)\n{\n    std::string channel = GetParam();\n    const int stream_id = 1000;\n    Context ctx;\n    ctx\n    .useConductorAgentInvoker(true)\n    .resourceLingerTimeout(10)\n    .idleSleepDuration(10)\n    .aeronDir(m_driver.aeronDir());\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    const int64_t pub_registration_id = aeron->addExclusivePublication(channel, stream_id);\n    std::shared_ptr<ExclusivePublication> publication;\n    do\n    {\n        aeron->conductorAgentInvoker().invoke();\n        publication = aeron->findExclusivePublication(pub_registration_id);\n    }\n    while (nullptr == publication);\n\n    std::atomic<int64_t> image_correlation_id;\n    image_correlation_id = -1;\n    std::atomic<bool> image_unavailable;\n    image_unavailable = false;\n    const int64_t sub_registration_id = aeron->addSubscription(\n        channel,\n        stream_id,\n        [&image_correlation_id](Image &image)\n        {\n            image_correlation_id = image.correlationId();\n        },\n        [&image_unavailable](Image &image)\n        {\n            image_unavailable = true;\n        });\n    std::shared_ptr<Subscription> subscription;\n    do\n    {\n        aeron->conductorAgentInvoker().invoke();\n        subscription = aeron->findSubscription(sub_registration_id);\n    }\n    while (nullptr == subscription);\n\n    aeron_subscription_t *raw_subscription = subscription->subscription();\n    aeron_image_t *raw_image = nullptr;\n    {\n        std::shared_ptr<Image> image;\n        do\n        {\n            aeron->conductorAgentInvoker().invoke();\n            image = subscription->imageBySessionId(publication->sessionId());\n        }\n        while (nullptr == image);\n\n        // ref_cnt == 2 - because shared_ptr<Image>\n\n        while (-1 == image_correlation_id)\n        {\n            aeron->conductorAgentInvoker().invoke();\n        }\n        ASSERT_EQ(image_correlation_id, image->correlationId());\n\n        auto image_by_index = subscription->imageByIndex(0);\n        EXPECT_NE(image, image_by_index);\n        EXPECT_EQ(image_correlation_id, image_by_index->correlationId());\n\n        // ref_cnt == 3 - because image_by_index\n\n        raw_image =\n            aeron_subscription_image_by_session_id(raw_subscription, publication->sessionId());\n        EXPECT_EQ(4, aeron_image_refcnt_acquire(raw_image));\n\n        EXPECT_EQ(0, aeron_subscription_image_release(raw_subscription, raw_image));\n        ASSERT_EQ(3, aeron_image_refcnt_acquire(raw_image));\n    }\n\n    EXPECT_EQ(1, aeron_image_refcnt_acquire(raw_image));\n    EXPECT_EQ(1, subscription->imageCount());\n    EXPECT_EQ(1, aeron_subscription_image_count(raw_subscription));\n\n    char log_buffer_file[AERON_MAX_PATH];\n    EXPECT_GT(\n        aeron_network_publication_location(log_buffer_file, sizeof(log_buffer_file), aeron->context().aeronDir().c_str(), pub_registration_id),\n        0);\n    EXPECT_GE(aeron_file_length(log_buffer_file), 0);\n    const auto pub_log_file = std::string().append(log_buffer_file);\n\n    EXPECT_GT(\n        aeron_publication_image_location(log_buffer_file, sizeof(log_buffer_file), aeron->context().aeronDir().c_str(), image_correlation_id),\n        0);\n    const auto image_log_file = -1 == aeron_file_length(log_buffer_file) ? pub_log_file : std::string().append(log_buffer_file);\n\n    publication.reset();\n\n    while (!image_unavailable)\n    {\n        aeron->conductorAgentInvoker().invoke();\n    }\n    ASSERT_TRUE(image_unavailable);\n\n    while (0 != subscription->imageCount())\n    {\n        aeron->conductorAgentInvoker().invoke();\n    }\n\n    auto deadline_ns = std::chrono::nanoseconds(m_driver.livenessTimeoutNs());\n    auto zero_ns = std::chrono::nanoseconds(0);\n    auto sleep_ms = std::chrono::milliseconds(10);\n    while (deadline_ns > zero_ns)\n    {\n      if (-1 == aeron_file_length(image_log_file.c_str()))\n      {\n          break;\n      }\n      deadline_ns -= sleep_ms;\n      std::this_thread::sleep_for(sleep_ms);\n      aeron->conductorAgentInvoker().invoke();\n    }\n    EXPECT_GT(deadline_ns, zero_ns);\n\n    EXPECT_EQ(-1, aeron_file_length(image_log_file.c_str())) << image_log_file << \" not deleted\";\n    EXPECT_EQ(-1, aeron_file_length(pub_log_file.c_str())) << pub_log_file << \" not deleted\";\n}\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/TestUtil.h",
    "content": "//\n// Created by mike on 31/07/20.\n//\n\n#ifndef AERON_TESTUTIL_H\n#define AERON_TESTUTIL_H\n\n#include \"ChannelUriStringBuilder.h\"\n\nusing namespace aeron;\n\n#define AERON_TEST_TIMEOUT (5000)\n\n#define WAIT_FOR_NON_NULL(val, op)               \\\nauto val = op;                                   \\\ndo                                               \\\n{                                                \\\n    std::int64_t t0 = aeron_epoch_clock();       \\\n    while (!val)                                 \\\n    {                                            \\\n       ASSERT_LT(aeron_epoch_clock() - t0, AERON_TEST_TIMEOUT) << \"Failed waiting for: \"  << #op; \\\n       std::this_thread::yield();                \\\n       val = op;                                 \\\n    }                                            \\\n}                                                \\\nwhile (0)                                        \\\n\n#define WAIT_FOR(op)                             \\\ndo                                               \\\n{                                                \\\n    std::int64_t t0 = aeron_epoch_clock();       \\\n    while (!(op))                                \\\n    {                                            \\\n       ASSERT_LT(aeron_epoch_clock() - t0, AERON_TEST_TIMEOUT) << \"Failed waiting for: \" << #op; \\\n       std::this_thread::yield();                \\\n    }                                            \\\n}                                                \\\nwhile (0)                                        \\\n\n#define POLL_FOR_NON_NULL(val, op, invoker) \\\nauto val = op;                              \\\ndo                                          \\\n{                                           \\\n    std::int64_t t0 = aeron_epoch_clock();  \\\n    while (!val)                            \\\n    {                                       \\\n       invoker.invoke();                    \\\n       ASSERT_LT(aeron_epoch_clock() - t0, AERON_TEST_TIMEOUT) << \"Failed waiting for: \"  << #op; \\\n       std::this_thread::yield();           \\\n       val = op;                            \\\n    }                                       \\\n}                                           \\\nwhile (0)                                   \\\n\n#define POLL_FOR(op, invoker)              \\\ndo                                         \\\n{                                          \\\n    std::int64_t t0 = aeron_epoch_clock(); \\\n    while (!(op))                          \\\n    {                                      \\\n       invoker.invoke();                   \\\n       ASSERT_LT(aeron_epoch_clock() - t0, AERON_TEST_TIMEOUT) << \"Failed waiting for: \" << #op; \\\n       std::this_thread::yield();          \\\n    }                                      \\\n}                                          \\\nwhile (0)                                  \\\n\nChannelUriStringBuilder &setParameters(const char *media, const char *endpoint, ChannelUriStringBuilder &builder)\n{\n    builder.media(media);\n    if (endpoint)\n    {\n        builder.endpoint(endpoint);\n    }\n\n    return builder;\n}\n\n#endif //AERON_TESTUTIL_H\n"
  },
  {
    "path": "aeron-client/src/test/cpp_wrapper/WrapperSystemTest.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\n#include \"EmbeddedMediaDriver.h\"\n#include \"Aeron.h\"\n#include \"TestUtil.h\"\n\nusing namespace aeron;\n\nclass WrapperSystemTest : public testing::Test\n{\npublic:\n    WrapperSystemTest()\n    {\n        m_driver.start();\n    }\n\n    ~WrapperSystemTest() override\n    {\n        m_driver.stop();\n    }\n\n    static std::int32_t typeId(CountersReader &reader, std::int32_t counterId)\n    {\n        const index_t offset = aeron::concurrent::CountersReader::metadataOffset(counterId);\n        return reader.metaDataBuffer().getInt32(offset + CountersReader::TYPE_ID_OFFSET);\n    }\n\nprotected:\n    EmbeddedMediaDriver m_driver;\n};\n\nTEST_F(WrapperSystemTest, shouldSendReceiveDataWithRawPointer)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    std::int64_t pubId = aeron->addPublication(\"aeron:ipc\", 10000);\n    std::int64_t subId = aeron->addSubscription(\"aeron:ipc\", 10000);\n    invoker.invoke();\n\n    POLL_FOR_NON_NULL(pub, aeron->findPublication(pubId), invoker);\n    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n    std::string message = \"Hello World!\";\n\n    auto *data = reinterpret_cast<const uint8_t *>(message.c_str());\n    POLL_FOR(0 < pub->offer(data, message.length()), invoker);\n    POLL_FOR(0 < sub->poll(\n        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));\n        },\n        1), invoker);\n}\n\nTEST_F(WrapperSystemTest, shouldSendReceiveDataWithRawPointerExclusive)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    std::int64_t pubId = aeron->addExclusivePublication(\"aeron:ipc\", 10000);\n    std::int64_t subId = aeron->addSubscription(\"aeron:ipc\", 10000);\n    invoker.invoke();\n\n    POLL_FOR_NON_NULL(pub, aeron->findExclusivePublication(pubId), invoker);\n    POLL_FOR_NON_NULL(sub, aeron->findSubscription(subId), invoker);\n    POLL_FOR(pub->isConnected() && sub->isConnected(), invoker);\n\n    std::string message = \"Hello World!\";\n\n    auto *data = reinterpret_cast<const uint8_t *>(message.c_str());\n    POLL_FOR(0 < pub->offer(data, message.length()), invoker);\n    POLL_FOR(0 < sub->poll(\n        [&](concurrent::AtomicBuffer &buffer, util::index_t offset, util::index_t length, Header &header)\n        {\n            EXPECT_EQ(message, buffer.getStringWithoutLength(offset, length));\n        },\n        1), invoker);\n}\n\nTEST_F(WrapperSystemTest, shouldRejectClientNameThatIsTooLong)\n{\n    std::string name =\n        \"this is a very long value that we are hoping with be reject when the value gets \"\n        \"set on the the context without causing issues will labels\";\n\n    try\n    {\n        Context ctx;\n        ctx.useConductorAgentInvoker(true);\n        ctx.clientName(name);\n\n        std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n        FAIL();\n    }\n    catch (IllegalArgumentException &ex)\n    {\n        const char *string = strstr(ex.what(), \"client_name length must <= 100\");\n        ASSERT_NE(nullptr, string) << ex.what();\n    }\n}\n\nTEST_F(WrapperSystemTest, shouldRemovePendingAsyncPublicationUponError)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    auto channel = \"aeron:udp?endpoint=localhost:99999\";\n    int stream_id = 1000;\n    int64_t registration_id = aeron->addPublication(channel, stream_id);\n\n    try\n    {\n        POLL_FOR_NON_NULL(publication2, aeron->findPublication(registration_id), invoker);\n        FAIL();\n    }\n    catch( const AeronException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(\"port out of range: 99999\", 0));\n    }\n\n    try\n    {\n        POLL_FOR_NON_NULL(publication2, aeron->findPublication(registration_id), invoker);\n        FAIL();\n    }\n    catch( const IllegalArgumentException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(std::string(\"Unknown registration id: \").append(std::to_string(registration_id)), 0));\n    }\n}\n\nTEST_F(WrapperSystemTest, shouldRemovePendingAsyncPublicationUponSuccess)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n    auto channel = \"aeron:udp?endpoint=localhost:5555\";\n    int stream_id = 1000;\n    int64_t registration_id = aeron->addPublication(channel, stream_id);\n    POLL_FOR_NON_NULL(publication, aeron->findPublication(registration_id), invoker);\n\n    try\n    {\n        POLL_FOR_NON_NULL(publication2, aeron->findPublication(registration_id), invoker);\n        FAIL();\n    }\n    catch( const IllegalArgumentException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(std::string(\"Unknown registration id: \").append(std::to_string(registration_id)), 0));\n    }\n}\n\nTEST_F(WrapperSystemTest, shouldManuallyFreeAsyncPublicationIfNotPolled)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    auto channel = \"aeron:udp?endpoint=localhost:5555\";\n    int stream_id = 1000;\n    auto async = aeron->addPublicationAsync(channel, stream_id);\n    aeron_async_cmd_free(async); // safe since client conductor is not running concurrently\n}\n\nTEST_F(WrapperSystemTest, shouldManuallyFreeAsyncPublicationIfNotPolledConductor)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(false);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    auto channel = \"aeron:udp?endpoint=localhost:5555\";\n    int stream_id = 1000;\n    auto async = aeron->addPublicationAsync(channel, stream_id);\n    EXPECT_NE(nullptr, async);\n\n    // wait for addPublicationAsync to complete\n    int64_t counterId = aeron->addCounter(1000, nullptr, 0, \"test\");\n    WAIT_FOR_NON_NULL(counter, aeron->findCounter(counterId));\n\n    aeron_async_cmd_free(async); // it is safe to delete now since client conductor processed this command\n}\n\nTEST_F(WrapperSystemTest, shouldRemovePendingAsyncExclusivePublicationUponError)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    auto channel = \"aeron:ipc?session-id=42\";\n    int stream_id = 1000;\n    int64_t registration_id = aeron->addExclusivePublication(channel, stream_id);\n    POLL_FOR_NON_NULL(publication, aeron->findExclusivePublication(registration_id), invoker);\n\n    int64_t registration_id2 = aeron->addExclusivePublication(channel, stream_id);\n    try\n    {\n        POLL_FOR_NON_NULL(publication2, aeron->findExclusivePublication(registration_id2), invoker);\n        FAIL();\n    }\n    catch( const AeronException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(\"session-id is already in exclusive use for channel\", 0));\n    }\n\n    try\n    {\n        POLL_FOR_NON_NULL(publication2, aeron->findExclusivePublication(registration_id2), invoker);\n        FAIL();\n    }\n    catch( const IllegalArgumentException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(std::string(\"Unknown registration id: \").append(std::to_string(registration_id2)), 0));\n    }\n}\n\nTEST_F(WrapperSystemTest, shouldRemovePendingAsyncSubscriptionUponError)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    auto channel = \"aeron:udp?endpoint=localhost:99999\";\n    int stream_id = 1000;\n    int64_t registration_id = aeron->addSubscription(channel, stream_id);\n\n    try\n    {\n        POLL_FOR_NON_NULL(subscription, aeron->findSubscription(registration_id), invoker);\n        FAIL();\n    }\n    catch( const AeronException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(\"port out of range: 99999\", 0));\n    }\n\n    try\n    {\n        POLL_FOR_NON_NULL(subscription, aeron->findSubscription(registration_id), invoker);\n        FAIL();\n    }\n    catch( const IllegalArgumentException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(std::string(\"Unknown registration id: \").append(std::to_string(registration_id)), 0));\n    }\n}\n\nTEST_F(WrapperSystemTest, shouldRemovePendingAsyncCounterUponError)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    int32_t typeId = 1000;\n    int64_t registration_id = aeron->addCounter(typeId, nullptr, 0, \"test\");\n    POLL_FOR_NON_NULL(counter, aeron->findCounter(registration_id), invoker);\n\n    int64_t registration_id2 = aeron->addStaticCounter(typeId, nullptr, 0, \"my static counter\", registration_id);\n    try\n    {\n        POLL_FOR_NON_NULL(staticCounter, aeron->findCounter(registration_id2), invoker);\n        FAIL();\n    }\n    catch( const AeronException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(\"cannot add static counter, because a non-static counter exists\", 0));\n    }\n\n    try\n    {\n        POLL_FOR_NON_NULL(staticCounter, aeron->findCounter(registration_id2), invoker);\n        FAIL();\n    }\n    catch( const IllegalArgumentException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(std::string(\"Unknown registration id: \").append(std::to_string(registration_id2)), 0));\n    }\n}\n\nTEST_F(WrapperSystemTest, asyncSubscriptionMustBeManuallyFreedAfterUsage)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    auto channel = \"aeron:udp?endpoint=localhost:99999\";\n    int stream_id = 1000;\n    auto async = aeron->addSubscriptionAsync(channel, stream_id);\n\n    try\n    {\n        POLL_FOR_NON_NULL(subscription, aeron->findSubscription(async), invoker);\n        FAIL();\n    }\n    catch( const AeronException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(\"port out of range: 99999\", 0));\n    }\n\n    delete async;\n}\n\nTEST_F(WrapperSystemTest, asyncSubscriptionMustBeManuallyFreedAfterUsageConductor)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(false);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    auto channel = \"aeron:udp?endpoint=localhost:99999\";\n    int stream_id = 1000;\n    auto async = aeron->addSubscriptionAsync(channel, stream_id);\n\n    try\n    {\n        WAIT_FOR_NON_NULL(subscription, aeron->findSubscription(async));\n        FAIL();\n    }\n    catch( const AeronException& e )\n    {\n        auto errorMsg = std::string(e.what());\n        EXPECT_NE(std::string::npos, errorMsg.find(\"port out of range: 99999\", 0));\n    }\n\n    delete async;\n}\n\nTEST_F(WrapperSystemTest, nonPolledAsyncSubscriptionMustBeManuallyFreedAfterUsage)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    auto channel = \"aeron:udp?endpoint=localhost:99999\";\n    int stream_id = 1000;\n    auto async = aeron->addSubscriptionAsync(channel, stream_id);\n    delete async;\n}\n\nTEST_F(WrapperSystemTest, polledSubscriptionShouldCloseAllAllocatedResourcesInvoker)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(true);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n    AgentInvoker<ClientConductor> &invoker = aeron->conductorAgentInvoker();\n    invoker.start();\n\n    auto channel = \"aeron:udp?endpoint=localhost:10000\";\n    int stream_id = 1000;\n    int64_t registration_id = aeron->addSubscription(channel, stream_id);\n\n    POLL_FOR_NON_NULL(subscription, aeron->findSubscription(registration_id), invoker);\n}\n\nTEST_F(WrapperSystemTest, polledSubscriptionShouldCloseAllAllocatedResourcesConductor)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(false);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    auto channel = \"aeron:udp?endpoint=localhost:10000\";\n    int stream_id = 1000;\n\n    int64_t registration_id = aeron->addSubscription(channel, stream_id);\n    WAIT_FOR_NON_NULL(subscription, aeron->findSubscription(registration_id));\n\n    int64_t registration_id2 = aeron->addSubscription(channel, stream_id);\n    WAIT_FOR_NON_NULL(subscription2, aeron->findSubscription(registration_id2));\n}\n\nTEST_F(WrapperSystemTest, polledSubscriptionShouldCloseAllAllocatedResourcesWhenConductorQueueIsFull)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(false).idleSleepDuration(200);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    auto channel = \"aeron:udp?endpoint=localhost:10000\";\n    int stream_id = 1000;\n    int64_t registration_id = aeron->addSubscription(channel, stream_id);\n\n    WAIT_FOR_NON_NULL(subscription, aeron->findSubscription(registration_id));\n\n    // overflow client command queue\n    while (true)\n    {\n        try\n        {\n            aeron->addCounter(1000, nullptr, 0, \"test\");\n        }\n        catch (...)\n        {\n            break;\n        }\n    }\n\n    subscription.reset();\n    aeron.reset();\n}\n\nTEST_F(WrapperSystemTest, shouldDeleteAeronInstanceLastEvenIfManuallyReleased)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(false);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    auto channel = \"aeron:udp?endpoint=localhost:10000\";\n    int stream_id = 1000;\n\n    int64_t sub_registration_id = aeron->addSubscription(channel, stream_id);\n    WAIT_FOR_NON_NULL(subscription, aeron->findSubscription(sub_registration_id));\n\n    int64_t pub_registration_id = aeron->addPublication(channel, stream_id);\n    WAIT_FOR_NON_NULL(publication, aeron->findPublication(pub_registration_id));\n\n    int64_t exclusive_pub_registration_id = aeron->addExclusivePublication(channel, stream_id + 1);\n    WAIT_FOR_NON_NULL(\n        exclusivePublication, aeron->findExclusivePublication(exclusive_pub_registration_id));\n\n    int64_t counter_registration_id = aeron->addCounter(1000, nullptr, 0, \"test\");\n    WAIT_FOR_NON_NULL(counter, aeron->findCounter(counter_registration_id));\n\n    aeron.reset();\n\n    EXPECT_EQ(sub_registration_id, subscription->registrationId());\n    EXPECT_EQ(pub_registration_id, publication->registrationId());\n    EXPECT_EQ(exclusive_pub_registration_id, exclusivePublication->registrationId());\n    EXPECT_EQ(counter_registration_id, counter->registrationId());\n}\n\nTEST_F(WrapperSystemTest, polledSubscriptionShouldCloseAllAllocatedResourcesAfterDriverWasStopped)\n{\n    Context ctx;\n    ctx\n        .useConductorAgentInvoker(false)\n        .mediaDriverTimeout(350)\n        .idleSleepDuration(1)\n        .errorHandler(\n            [](const std::exception& ignored)\n            {\n                // Deliberately ignored.\n            });\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    auto channel = \"aeron:udp?endpoint=localhost:10000\";\n    int stream_id = 1000;\n    int64_t registration_id = aeron->addSubscription(channel, stream_id);\n\n    WAIT_FOR_NON_NULL(subscription, aeron->findSubscription(registration_id));\n\n    m_driver.stop();\n    m_driver.closeDriver();\n\n    // wait for driver being stopped detected by the underlying `aeron_t` instance\n    std::this_thread::sleep_for(std::chrono::milliseconds(ctx.mediaDriverTimeout() * 2));\n\n    EXPECT_EQ(registration_id, subscription->registrationId());\n}\n\nTEST_F(WrapperSystemTest, nonPolledPendingAsyncDestinationsAreAutomaticallyFreedSubscription)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(false);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    auto channel = \"aeron:udp?control-mode=manual\";\n    auto dest1Uri = \"aeron:udp?endpoint=localhost:4554\";\n    auto dest2Uri = \"aeron:udp?endpoint=localhost:7777\";\n    auto dest3Uri = \"aeron:udp?endpoint=localhost:10000\";\n    int stream_id = 1000;\n\n    int64_t registration_id = aeron->addSubscription(channel, stream_id);\n    WAIT_FOR_NON_NULL(subscription, aeron->findSubscription(registration_id));\n\n    int64_t addDest1RegistrationId = subscription->addDestination(dest1Uri);\n    auto addDest3Async = subscription->addDestinationAsync(dest3Uri);\n    int64_t addDest2RegistrationId = subscription->addDestination(dest2Uri);\n\n    auto removeDest1Async = subscription->removeDestinationAsync(dest1Uri);\n    int64_t removeDest2RegistrationId = subscription->removeDestination(dest2Uri);\n    int64_t removeDest3RegistrationId = subscription->removeDestination(dest3Uri);\n\n    EXPECT_GT(removeDest2RegistrationId, addDest1RegistrationId);\n    EXPECT_GT(removeDest3RegistrationId, removeDest2RegistrationId);\n\n    WAIT_FOR(subscription->findDestinationResponse(addDest2RegistrationId));\n    WAIT_FOR(subscription->findDestinationResponse(removeDest3RegistrationId));\n\n    // safe to delete after commands were processed by the client conductor thread\n    aeron_async_cmd_free(addDest3Async);\n    aeron_async_cmd_free(removeDest1Async);\n}\n\nTEST_F(WrapperSystemTest, nonPolledPendingAsyncDestinationsAreAutomaticallyFreedPublication)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(false);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    auto channel = \"aeron:udp?control-mode=manual\";\n    auto dest1Uri = \"aeron:udp?endpoint=localhost:4554\";\n    auto dest2Uri = \"aeron:udp?endpoint=localhost:7777\";\n    auto dest3Uri = \"aeron:udp?endpoint=localhost:10000\";\n    int stream_id = 1000;\n\n    int64_t registration_id = aeron->addPublication(channel, stream_id);\n    WAIT_FOR_NON_NULL(publication, aeron->findPublication(registration_id));\n\n    int64_t addDest1RegistrationId = publication->addDestination(dest1Uri);\n    auto addDest3Async = publication->addDestinationAsync(dest3Uri);\n    int64_t addDest2RegistrationId = publication->addDestination(dest2Uri);\n\n    auto removeDest1Async = publication->removeDestinationAsync(dest1Uri);\n    int64_t removeDest2RegistrationId = publication->removeDestination(dest2Uri);\n    int64_t removeDest3RegistrationId = publication->removeDestination(dest3Uri);\n\n    EXPECT_GT(removeDest2RegistrationId, addDest1RegistrationId);\n    EXPECT_GT(removeDest3RegistrationId, removeDest2RegistrationId);\n\n    WAIT_FOR(publication->findDestinationResponse(addDest2RegistrationId));\n    WAIT_FOR(publication->findDestinationResponse(removeDest3RegistrationId));\n\n    // safe to delete after commands were processed by the client conductor thread\n    aeron_async_cmd_free(addDest3Async);\n    aeron_async_cmd_free(removeDest1Async);\n}\n\nTEST_F(WrapperSystemTest, nonPolledPendingAsyncDestinationsAreAutomaticallyFreedExclusivePublication)\n{\n    Context ctx;\n    ctx.useConductorAgentInvoker(false);\n\n    std::shared_ptr<Aeron> aeron = Aeron::connect(ctx);\n\n    auto channel = \"aeron:udp?control-mode=manual\";\n    auto dest1Uri = \"aeron:udp?endpoint=localhost:4554\";\n    auto dest2Uri = \"aeron:udp?endpoint=localhost:7777\";\n    auto dest3Uri = \"aeron:udp?endpoint=localhost:10000\";\n    int stream_id = 1000;\n\n    int64_t registration_id = aeron->addExclusivePublication(channel, stream_id);\n    WAIT_FOR_NON_NULL(publication, aeron->findExclusivePublication(registration_id));\n\n    int64_t addDest1RegistrationId = publication->addDestination(dest1Uri);\n    auto addDest3Async = publication->addDestinationAsync(dest3Uri);\n    int64_t addDest2RegistrationId = publication->addDestination(dest2Uri);\n\n    auto removeDest1Async = publication->removeDestinationAsync(dest1Uri);\n    int64_t removeDest2RegistrationId = publication->removeDestination(dest2Uri);\n    int64_t removeDest3RegistrationId = publication->removeDestination(dest3Uri);\n\n    EXPECT_GT(removeDest2RegistrationId, addDest1RegistrationId);\n    EXPECT_GT(removeDest3RegistrationId, removeDest2RegistrationId);\n\n    WAIT_FOR(publication->findDestinationResponse(addDest2RegistrationId));\n    WAIT_FOR(publication->findDestinationResponse(removeDest3RegistrationId));\n\n    // safe to delete after commands were processed by the client conductor thread\n    aeron_async_cmd_free(addDest3Async);\n    aeron_async_cmd_free(removeDest1Async);\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/AeronContextTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.exceptions.ConfigurationException;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.mockito.Mockito.mock;\n\nclass AeronContextTest\n{\n    @NullAndEmptySource\n    @ParameterizedTest\n    void shouldUseEmptyStringIfNameIsEmpty(final String name)\n    {\n        final Aeron.Context context = new Aeron.Context();\n        assertEquals(\"\", context.clientName());\n\n        context.clientName(name);\n        assertEquals(\"\", context.clientName());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"a\", \"gdajsdgajsd\", \"7326482374hdfy7dsyf8dyf9sd.-)\" })\n    void shouldAssignClientName(final String clientName)\n    {\n        final Aeron.Context context = new Aeron.Context();\n\n        context.clientName(clientName);\n        assertSame(clientName, context.clientName());\n    }\n\n    @Test\n    void shouldRejectClientNameThatIsTooLong()\n    {\n        final String name =\n            \"this is a very long value that we are hoping with be reject when the value gets \" +\n            \"set on the the context without causing issues will labels\";\n        final Aeron.Context context = new Aeron.Context().clientName(name);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\"ERROR - clientName length must <= 100\", exception.getMessage());\n    }\n\n    @Test\n    void shouldRequireRunningInTheInvokerModeIfDriverInvokerIsSpecified()\n    {\n        final AgentInvoker driverAgentInvoker = mock(AgentInvoker.class);\n        final Aeron.Context context = new Aeron.Context()\n            .useConductorAgentInvoker(false)\n            .driverAgentInvoker(driverAgentInvoker);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - Must use Aeron.Context.useConductorAgentInvoker(true) when \" +\n            \"Aeron.Context.driverAgentInvoker() is set\",\n            exception.getMessage());\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/AeronCountersTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.test.Tests;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.OptionalInt;\nimport java.util.function.Consumer;\nimport java.util.function.ToIntFunction;\n\nimport static org.agrona.concurrent.status.CountersReader.COUNTER_LENGTH;\nimport static org.agrona.concurrent.status.CountersReader.MAX_LABEL_LENGTH;\nimport static org.agrona.concurrent.status.CountersReader.METADATA_LENGTH;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_RECLAIMED;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_UNUSED;\nimport static org.agrona.concurrent.status.CountersReader.REFERENCE_ID_OFFSET;\nimport static org.agrona.concurrent.status.CountersReader.counterOffset;\nimport static org.agrona.concurrent.status.CountersReader.metaDataOffset;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nclass AeronCountersTest\n{\n    @Test\n    void shouldNotHaveOverlappingCounterTypeIds()\n    {\n        final Int2ObjectHashMap<Field> fieldByTypeId = new Int2ObjectHashMap<>();\n        final Int2ObjectHashMap<List<Field>> duplicates = new Int2ObjectHashMap<>();\n        final Consumer<Field> duplicateChecker =\n            (f) ->\n            {\n                try\n                {\n                    final int typeId = (Integer)f.get(null);\n                    final Field field = fieldByTypeId.putIfAbsent(typeId, f);\n                    if (null != field)\n                    {\n                        final List<Field> duplicatesForKey = duplicates.computeIfAbsent(\n                            typeId, (s) -> new ArrayList<>());\n                        if (!duplicatesForKey.contains(f))\n                        {\n                            duplicatesForKey.add(f);\n                        }\n                        duplicatesForKey.add(field);\n                    }\n                }\n                catch (final IllegalAccessException e)\n                {\n                    throw new RuntimeException(e);\n                }\n            };\n\n        Arrays.stream(AeronCounters.class.getFields())\n            .filter((f) -> Modifier.isStatic(f.getModifiers()))\n            .filter((f) -> f.getName().endsWith(\"_TYPE_ID\"))\n            .filter((f) -> Integer.TYPE.isAssignableFrom(f.getType()))\n            .forEach(duplicateChecker);\n\n        if (!duplicates.isEmpty())\n        {\n            fail(\"Duplicate typeIds: \" + duplicates);\n        }\n    }\n\n    @Test\n    @Disabled\n    void printLargestCounterId()\n    {\n        final ToIntFunction<Field> getValue = (field) ->\n        {\n            try\n            {\n                return (Integer)field.get(null);\n            }\n            catch (final IllegalAccessException e)\n            {\n                throw new RuntimeException(e);\n            }\n        };\n\n        final OptionalInt maxValue = Arrays.stream(AeronCounters.class.getFields())\n            .filter((f) -> Modifier.isStatic(f.getModifiers()))\n            .filter((f) -> f.getName().endsWith(\"_TYPE_ID\"))\n            .filter((f) -> Integer.TYPE.isAssignableFrom(f.getType()))\n            .mapToInt(getValue)\n            .max();\n\n        System.out.println(maxValue);\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"1.42.1, 8165495befc07e997a7f2f7743beab9d3846b0a5, version=1.42.1 \" +\n            \"commit=8165495befc07e997a7f2f7743beab9d3846b0a5\",\n        \"1.43.0-SNAPSHOT, abc, version=1.43.0-SNAPSHOT commit=abc\",\n        \"NIL, 12345678, version=NIL commit=12345678\" })\n    void shouldFormatVersionInfo(final String fullVersion, final String commitHash, final String expected)\n    {\n        assertEquals(expected, AeronCounters.formatVersionInfo(fullVersion, commitHash));\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"xyz, 1234567890, version=xyz commit=1234567890\",\n        \"1.43.0-SNAPSHOT, abc, version=1.43.0-SNAPSHOT commit=abc\" })\n    void shouldAppendVersionInfo(final String fullVersion, final String commitHash, final String formatted)\n    {\n        final String expected = \" \" + formatted;\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(32);\n        final int offset = 5;\n        buffer.setMemory(0, buffer.capacity(), (byte)-1);\n\n        final int length = AeronCounters.appendVersionInfo(buffer, offset, fullVersion, commitHash);\n\n        assertEquals(expected.length(), length);\n        assertEquals(expected, buffer.getStringWithoutLengthAscii(offset, length));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { Integer.MIN_VALUE, -1 })\n    void appendToLabelThrowsIllegalArgumentExceptionIfCounterIsNegative(final int counterId)\n    {\n        final IllegalArgumentException exception = assertThrowsExactly(\n            IllegalArgumentException.class, () -> AeronCounters.appendToLabel(new UnsafeBuffer(), counterId, \"test\"));\n        assertEquals(\"counter id \" + counterId + \" is negative\", exception.getMessage());\n    }\n\n    @Test\n    void appendToLabelThrowsNullPointerExceptionIfBufferIsNull()\n    {\n        assertThrowsExactly(\n            NullPointerException.class, () -> AeronCounters.appendToLabel(null, 5, \"test\"));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 1_000_000, Integer.MAX_VALUE })\n    void appendToLabelThrowsIllegalArgumentExceptionIfCounterIsOutOfRange(final int counterId)\n    {\n        final UnsafeBuffer metaDataBuffer = new UnsafeBuffer(new byte[METADATA_LENGTH * 3]);\n\n        final IllegalArgumentException exception = assertThrowsExactly(\n            IllegalArgumentException.class, () -> AeronCounters.appendToLabel(metaDataBuffer, counterId, \"test\"));\n        assertEquals(\"counter id \" + counterId + \" out of range: 0 - maxCounterId=2\", exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { RECORD_UNUSED, RECORD_RECLAIMED })\n    void appendToLabelThrowsIllegalArgumentExceptionIfCounterIsInWrongState(final int state)\n    {\n        final UnsafeBuffer metaDataBuffer = new UnsafeBuffer(new byte[METADATA_LENGTH * 2]);\n        final int counterId = 1;\n        final int metaDataOffset = metaDataOffset(counterId);\n        metaDataBuffer.putInt(metaDataOffset, state);\n\n        final IllegalArgumentException exception = assertThrowsExactly(\n            IllegalArgumentException.class, () -> AeronCounters.appendToLabel(metaDataBuffer, counterId, \"test\"));\n        assertEquals(\"counter id 1 is not allocated, state: \" + state, exception.getMessage());\n    }\n\n    @Test\n    void appendToLabelShouldAddSuffix()\n    {\n        final CountersManager countersManager = new CountersManager(\n            new UnsafeBuffer(new byte[METADATA_LENGTH]),\n            new UnsafeBuffer(ByteBuffer.allocateDirect(COUNTER_LENGTH)),\n            StandardCharsets.US_ASCII);\n        final int counterId = countersManager.allocate(\"initial value: \");\n\n        final int length = AeronCounters.appendToLabel(countersManager.metaDataBuffer(), counterId, \"test\");\n\n        assertEquals(4, length);\n        assertEquals(\"initial value: test\", countersManager.getCounterLabel(counterId));\n    }\n\n    @Test\n    void appendToLabelShouldAddAPortionOfSuffixUpToTheMaxLength()\n    {\n        final CountersManager countersManager = new CountersManager(\n            new UnsafeBuffer(new byte[METADATA_LENGTH]),\n            new UnsafeBuffer(ByteBuffer.allocateDirect(COUNTER_LENGTH)),\n            StandardCharsets.US_ASCII);\n        final String initialLabel = \"this is a test counter\";\n        final int counterId = countersManager.allocate(initialLabel);\n        final String hugeSuffix = Tests.generateStringWithSuffix(\" - 42\", \"x\", MAX_LABEL_LENGTH);\n\n        final int length = AeronCounters.appendToLabel(countersManager.metaDataBuffer(), counterId, hugeSuffix);\n\n        assertNotEquals(hugeSuffix.length(), length);\n        assertEquals(MAX_LABEL_LENGTH - initialLabel.length(), length);\n        assertEquals(initialLabel + hugeSuffix.substring(0, length), countersManager.getCounterLabel(counterId));\n    }\n\n    @Test\n    void appendToLabelIsANoOpIfThereIsNoSpaceInTheLabel()\n    {\n        final CountersManager countersManager = new CountersManager(\n            new UnsafeBuffer(new byte[METADATA_LENGTH]),\n            new UnsafeBuffer(ByteBuffer.allocateDirect(COUNTER_LENGTH)),\n            StandardCharsets.US_ASCII);\n        final String label = Tests.generateStringWithSuffix(\"\", \"a\", MAX_LABEL_LENGTH);\n        final int counterId = countersManager.allocate(label);\n\n        final int length = AeronCounters.appendToLabel(countersManager.metaDataBuffer(), counterId, \"test\");\n\n        assertEquals(0, length);\n        assertEquals(label, countersManager.getCounterLabel(counterId));\n    }\n\n    @Test\n    void setReferenceIdShouldThrowNullPointerExceptionIfMetadataBufferIsNull()\n    {\n        assertThrowsExactly(\n            NullPointerException.class, () -> AeronCounters.setReferenceId(null, new UnsafeBuffer(), 1, 123));\n    }\n\n    @Test\n    void setReferenceIdShouldThrowNullPointerExceptionIfValuesBufferIsNull()\n    {\n        assertThrowsExactly(\n            NullPointerException.class, () -> AeronCounters.setReferenceId(new UnsafeBuffer(), null, 1, 123));\n    }\n\n    @Test\n    void setReferenceIdShouldRejectNegativeCounterId()\n    {\n        final IllegalArgumentException exception = assertThrowsExactly(\n            IllegalArgumentException.class,\n            () -> AeronCounters.setReferenceId(new UnsafeBuffer(), new UnsafeBuffer(), -4, 123));\n        assertEquals(\"counter id -4 is negative\", exception.getMessage());\n    }\n\n    @Test\n    void setReferenceIdShouldRejectNegativeCounterIdWhichIsOutOfRange()\n    {\n        final IllegalArgumentException exception = assertThrowsExactly(\n            IllegalArgumentException.class,\n            () -> AeronCounters.setReferenceId(\n                new UnsafeBuffer(new byte[2 * METADATA_LENGTH]), new UnsafeBuffer(), 42, 777));\n        assertEquals(\"counter id 42 out of range: 0 - maxCounterId=1\", exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { Long.MIN_VALUE, 0, 54375943437284L, Long.MAX_VALUE })\n    void setReferenceIdShouldSetSpecifiedValue(final long referenceId)\n    {\n        final int counterId = 7;\n\n        final UnsafeBuffer metadataBuffer = new UnsafeBuffer(new byte[(counterId + 1) * METADATA_LENGTH]);\n        final UnsafeBuffer valuesBuffer = new UnsafeBuffer(new byte[(counterId + 1) * COUNTER_LENGTH]);\n\n        AeronCounters.setReferenceId(metadataBuffer, valuesBuffer, counterId, referenceId);\n\n        assertEquals(referenceId, valuesBuffer.getLong(counterOffset(counterId) + REFERENCE_ID_OFFSET));\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/BufferBuilderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.Header;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.BufferBuilder.INIT_MIN_CAPACITY;\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computeFragmentedFrameLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.HeaderFlyweight.*;\nimport static java.lang.Math.min;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass BufferBuilderTest\n{\n    private final BufferBuilder bufferBuilder = new BufferBuilder();\n\n    @ParameterizedTest\n    @CsvSource({\n        \"0,0,4096\", \"4095,1,4096\", \"4096,7,4096\", \"100,200,4096\", \"101,5000,6144\", \"5000,8080,11250\",\n        \"6666,11111,14998\", \"0,2147483639,2147483639\", \"2048,2147483647,2147483639\", \"5,1000000000000,2147483639\" })\n    void shouldFindSuitableCapacity(final int capacity, final long requiredCapacity, final int expected)\n    {\n        assertEquals(expected, BufferBuilder.findSuitableCapacity(capacity, requiredCapacity));\n    }\n\n    @Test\n    void shouldInitialiseToDefaultValues()\n    {\n        assertEquals(0, bufferBuilder.capacity());\n        assertEquals(0, bufferBuilder.limit());\n        assertEquals(Aeron.NULL_VALUE, bufferBuilder.nextTermOffset());\n        assertEquals(0, bufferBuilder.buffer().capacity());\n        assertNull(bufferBuilder.buffer().byteBuffer());\n        assertArrayEquals(ArrayUtil.EMPTY_BYTE_ARRAY, bufferBuilder.buffer().byteArray());\n    }\n\n    @Test\n    void shouldGrowDirectBuffer()\n    {\n        final BufferBuilder builder = new BufferBuilder(0, true);\n        assertEquals(0, builder.limit());\n        assertEquals(0, builder.capacity());\n        assertEquals(0, builder.buffer().capacity());\n\n        final int appendedLength = 10;\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[appendedLength]);\n        builder.append(srcBuffer, 0, srcBuffer.capacity());\n\n        assertEquals(INIT_MIN_CAPACITY, builder.capacity());\n        assertEquals(INIT_MIN_CAPACITY, builder.buffer().capacity());\n        assertEquals(appendedLength, builder.limit());\n        assertNotNull(builder.buffer().byteBuffer());\n        assertTrue(builder.buffer().byteBuffer().isDirect());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { false, true })\n    void shouldCreateHeaderBuffer(final boolean isDirect)\n    {\n        final BufferBuilder builder = new BufferBuilder(0, isDirect);\n        assertNotNull(builder.buffer());\n        assertNotNull(builder.headerBuffer);\n        assertNotSame(builder.headerBuffer, builder.buffer());\n\n        assertEquals(0, builder.capacity());\n        assertEquals(0, builder.limit());\n        assertEquals(0, builder.buffer().capacity());\n\n        assertEquals(HEADER_LENGTH, builder.headerBuffer.capacity());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -1, Integer.MAX_VALUE, Integer.MAX_VALUE - 7 })\n    void shouldThrowIllegalArgumentExceptionIfInitialCapacityIsOutOfRange(final int initialCapacity)\n    {\n        final IllegalArgumentException exception =\n            assertThrowsExactly(IllegalArgumentException.class, () -> new BufferBuilder(initialCapacity));\n        assertEquals(\n            \"initialCapacity outside range 0 - 2147483639: initialCapacity=\" + initialCapacity, exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -1, 65, 1000000 })\n    void shouldThrowIllegalArgumentExceptionIfLimitIsOutOfRange(final int limit)\n    {\n        final BufferBuilder builder = new BufferBuilder(0);\n        final IllegalArgumentException exception =\n            assertThrowsExactly(IllegalArgumentException.class, () -> builder.limit(limit));\n        assertEquals(\"limit outside range: capacity=\" + builder.capacity() + \" limit=\" + limit,\n            exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { Integer.MAX_VALUE, Integer.MAX_VALUE - 23 })\n    void shouldThrowIllegalStateExceptionIfDataExceedsMaxCapacity(final int length)\n    {\n        final UnsafeBuffer src = new UnsafeBuffer(new byte[16]);\n        src.putLong(0, Long.MAX_VALUE);\n        src.putLong(SIZE_OF_LONG, Long.MIN_VALUE);\n\n        final BufferBuilder builder = new BufferBuilder(64);\n        builder.append(src, 0, src.capacity());\n        assertEquals(src.capacity(), builder.limit());\n\n        final IllegalStateException exception =\n            assertThrowsExactly(IllegalStateException.class, () -> builder.append(src, 0, length));\n        assertEquals(\n            \"insufficient capacity: maxCapacity=2147483639 limit=\" + builder.limit() +\n            \" additionalLength=\" + length, exception.getMessage());\n    }\n\n    @Test\n    void shouldAppendNothingForZeroLength()\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[INIT_MIN_CAPACITY]);\n\n        bufferBuilder.append(srcBuffer, 0, 0);\n\n        assertEquals(0, bufferBuilder.limit());\n        assertEquals(0, bufferBuilder.capacity());\n    }\n\n    @Test\n    void shouldGrowToMultipleOfInitialCapacity()\n    {\n        final int srcCapacity = INIT_MIN_CAPACITY * 5;\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[srcCapacity]);\n\n        bufferBuilder.append(srcBuffer, 0, srcBuffer.capacity());\n\n        assertEquals(srcCapacity, bufferBuilder.limit());\n        assertThat(bufferBuilder.capacity(), greaterThanOrEqualTo(srcCapacity));\n    }\n\n    @Test\n    void shouldAppendThenReset()\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[INIT_MIN_CAPACITY]);\n\n        bufferBuilder.append(srcBuffer, 0, srcBuffer.capacity());\n        bufferBuilder.nextTermOffset(1024);\n\n        assertEquals(srcBuffer.capacity(), bufferBuilder.limit());\n        assertEquals(1024, bufferBuilder.nextTermOffset());\n\n        bufferBuilder.reset();\n\n        assertEquals(0, bufferBuilder.limit());\n        assertEquals(Aeron.NULL_VALUE, bufferBuilder.nextTermOffset());\n    }\n\n    @Test\n    void shouldAppendOneBufferWithoutResizing()\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[INIT_MIN_CAPACITY]);\n        final byte[] bytes = \"Hello World\".getBytes(StandardCharsets.UTF_8);\n        srcBuffer.putBytes(0, bytes, 0, bytes.length);\n\n        bufferBuilder.append(srcBuffer, 0, bytes.length);\n\n        final byte[] temp = new byte[bytes.length];\n        bufferBuilder.buffer().getBytes(0, temp, 0, bytes.length);\n\n        assertEquals(bytes.length, bufferBuilder.limit());\n        assertEquals(INIT_MIN_CAPACITY, bufferBuilder.capacity());\n        assertArrayEquals(temp, bytes);\n    }\n\n    @Test\n    void shouldAppendTwoBuffersWithoutResizing()\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[INIT_MIN_CAPACITY]);\n        final byte[] bytes = \"1111111122222222\".getBytes(StandardCharsets.UTF_8);\n        srcBuffer.putBytes(0, bytes, 0, bytes.length);\n\n        bufferBuilder.append(srcBuffer, 0, bytes.length / 2);\n        bufferBuilder.append(srcBuffer, bytes.length / 2, bytes.length / 2);\n\n        final byte[] temp = new byte[bytes.length];\n        bufferBuilder.buffer().getBytes(0, temp, 0, bytes.length);\n\n        assertEquals(bytes.length, bufferBuilder.limit());\n        assertEquals(INIT_MIN_CAPACITY, bufferBuilder.capacity());\n        assertArrayEquals(temp, bytes);\n    }\n\n    @Test\n    void shouldFillBufferWithoutResizing()\n    {\n        final int bufferLength = 128;\n        final byte[] buffer = new byte[bufferLength];\n        Arrays.fill(buffer, (byte)7);\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(buffer);\n\n        final BufferBuilder bufferBuilder = new BufferBuilder(bufferLength);\n        final int initialCapacity = bufferBuilder.capacity();\n\n        bufferBuilder.append(srcBuffer, 0, bufferLength);\n\n        final byte[] temp = new byte[bufferLength];\n        bufferBuilder.buffer().getBytes(0, temp, 0, bufferLength);\n\n        assertEquals(bufferLength, bufferBuilder.limit());\n        assertEquals(initialCapacity, bufferBuilder.capacity());\n        assertArrayEquals(temp, buffer);\n    }\n\n    @Test\n    void shouldResizeWhenBufferJustDoesNotFit()\n    {\n        final int bufferLength = 128;\n        final byte[] buffer = new byte[bufferLength + 1];\n        Arrays.fill(buffer, (byte)7);\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(buffer);\n\n        final BufferBuilder bufferBuilder = new BufferBuilder(bufferLength);\n\n        bufferBuilder.append(srcBuffer, 0, buffer.length);\n\n        final byte[] temp = new byte[buffer.length];\n        bufferBuilder.buffer().getBytes(0, temp, 0, buffer.length);\n\n        assertEquals(buffer.length, bufferBuilder.limit());\n        assertThat(bufferBuilder.capacity(), greaterThan(bufferLength));\n        assertArrayEquals(temp, buffer);\n    }\n\n    @Test\n    void shouldAppendTwoBuffersAndResize()\n    {\n        final int bufferLength = 128;\n        final byte[] buffer = new byte[bufferLength];\n        final int firstLength = buffer.length / 4;\n        final int secondLength = buffer.length / 2;\n        Arrays.fill(buffer, 0, firstLength + secondLength, (byte)7);\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(buffer);\n\n        final BufferBuilder bufferBuilder = new BufferBuilder(bufferLength / 2);\n\n        bufferBuilder.append(srcBuffer, 0, firstLength);\n        bufferBuilder.append(srcBuffer, firstLength, secondLength);\n\n        final byte[] temp = new byte[buffer.length];\n        bufferBuilder.buffer().getBytes(0, temp, 0, secondLength + firstLength);\n\n        assertEquals(firstLength + secondLength, bufferBuilder.limit());\n        assertThat(bufferBuilder.capacity(), greaterThanOrEqualTo(firstLength + secondLength));\n        assertArrayEquals(temp, buffer);\n    }\n\n    @Test\n    void shouldCompactBufferToLowerLimit()\n    {\n        final int bufferLength = INIT_MIN_CAPACITY / 2;\n        final byte[] buffer = new byte[bufferLength];\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(buffer);\n\n        final BufferBuilder bufferBuilder = new BufferBuilder();\n\n        final int bufferCount = 5;\n        for (int i = 0; i < bufferCount; i++)\n        {\n            bufferBuilder.append(srcBuffer, 0, buffer.length);\n        }\n\n        final int expectedLimit = buffer.length * bufferCount;\n        assertEquals(expectedLimit, bufferBuilder.limit());\n        final int expandedCapacity = bufferBuilder.capacity();\n        assertThat(expandedCapacity, greaterThan(expectedLimit));\n\n        bufferBuilder.reset();\n\n        bufferBuilder.append(srcBuffer, 0, buffer.length);\n        bufferBuilder.append(srcBuffer, 0, buffer.length);\n        bufferBuilder.append(srcBuffer, 0, buffer.length);\n\n        bufferBuilder.compact();\n\n        assertEquals(buffer.length * 3, bufferBuilder.limit());\n        assertThat(bufferBuilder.capacity(), lessThan(expandedCapacity));\n        assertEquals(bufferBuilder.limit(), bufferBuilder.capacity());\n    }\n\n    @Test\n    void captureFirstHeaderShouldCopyTheEntireHeader()\n    {\n        final long reservedValue = Long.MAX_VALUE - 117;\n        final int offset = 40;\n        final DataHeaderFlyweight headerFlyweight = new DataHeaderFlyweight();\n        headerFlyweight.wrap(new byte[100], offset, 50);\n        headerFlyweight.frameLength(512);\n        headerFlyweight.version((short)0xA);\n        headerFlyweight.flags((short)0b1110_0111);\n        headerFlyweight.headerType(HDR_TYPE_RTTM);\n        headerFlyweight.termOffset(384);\n        headerFlyweight.sessionId(-890);\n        headerFlyweight.streamId(555);\n        headerFlyweight.termId(42);\n        headerFlyweight.reservedValue(reservedValue);\n\n        final Header header = new Header(5, 3);\n        header.buffer(new UnsafeBuffer(headerFlyweight.byteArray()));\n        header.offset(headerFlyweight.wrapAdjustment());\n        final DirectBuffer originalHeaderBuffer = header.buffer();\n\n        assertSame(bufferBuilder, bufferBuilder.captureHeader(header));\n\n        assertEquals(0, bufferBuilder.capacity());\n        assertEquals(0, bufferBuilder.limit());\n        assertSame(originalHeaderBuffer, header.buffer());\n        assertEquals(headerFlyweight.wrapAdjustment(), header.offset());\n\n        headerFlyweight.wrap(bufferBuilder.headerBuffer, 0, HEADER_LENGTH);\n        assertEquals(512, bufferBuilder.completeHeader.frameLength());\n        assertEquals((short)0xA, headerFlyweight.version());\n        assertEquals((byte)0b1110_0111, bufferBuilder.completeHeader.flags());\n        assertEquals(HDR_TYPE_RTTM, headerFlyweight.headerType());\n        assertEquals(384, bufferBuilder.completeHeader.termOffset());\n        assertEquals(-890, bufferBuilder.completeHeader.sessionId());\n        assertEquals(555, bufferBuilder.completeHeader.streamId());\n        assertEquals(42, bufferBuilder.completeHeader.termId());\n        assertEquals(reservedValue, bufferBuilder.completeHeader.reservedValue());\n    }\n\n    @Test\n    void shouldPrepareCompleteHeader()\n    {\n        final UnsafeBuffer data = new UnsafeBuffer(new byte[999]);\n\n        final int frameLength = 128;\n        final int termOffset = 1024;\n        final short version = (short)15;\n        final int type = HDR_TYPE_NAK;\n        final int sessionId = 87;\n        final int streamId = -9;\n        final int termId = 10;\n        final long reservedValue = 0xCAFE_BABE_DEAD_BEEFL;\n        final DataHeaderFlyweight headerFlyweight = new DataHeaderFlyweight();\n        headerFlyweight.wrap(new byte[44], 1, 39);\n        headerFlyweight.frameLength(frameLength);\n        headerFlyweight.version(version);\n        headerFlyweight.flags((short)0b1011_1001);\n        headerFlyweight.headerType(type);\n        headerFlyweight.termOffset(termOffset);\n        headerFlyweight.sessionId(sessionId);\n        headerFlyweight.streamId(streamId);\n        headerFlyweight.termId(termId);\n        headerFlyweight.reservedValue(reservedValue);\n\n        final Header header = new Header(4, 48);\n        header.buffer(new UnsafeBuffer(headerFlyweight.byteArray()));\n        header.offset(headerFlyweight.wrapAdjustment());\n\n        assertSame(bufferBuilder, bufferBuilder.captureHeader(header));\n\n        bufferBuilder.append(data, 0, frameLength - HEADER_LENGTH);\n        bufferBuilder.append(data, 0, frameLength - HEADER_LENGTH);\n\n        headerFlyweight.wrap(new byte[88], 19, 42);\n        headerFlyweight.frameLength(frameLength);\n        headerFlyweight.version((short)0xC);\n        headerFlyweight.flags((short)0b0100_0100);\n        headerFlyweight.headerType(HDR_TYPE_ATS_SETUP);\n        headerFlyweight.termOffset(48);\n        headerFlyweight.sessionId(999);\n        headerFlyweight.streamId(4);\n        headerFlyweight.termId(39);\n        headerFlyweight.reservedValue(Long.MIN_VALUE);\n        header.buffer(new UnsafeBuffer(headerFlyweight.byteArray()));\n        header.offset(headerFlyweight.wrapAdjustment());\n        final DirectBuffer originalHeaderBuffer = header.buffer();\n\n        final Header completeHeader = bufferBuilder.completeHeader(header);\n        assertNotSame(header, completeHeader);\n\n        assertEquals(INIT_MIN_CAPACITY, bufferBuilder.capacity());\n        assertEquals(2 * (frameLength - HEADER_LENGTH), bufferBuilder.limit());\n        assertSame(originalHeaderBuffer, header.buffer());\n        assertEquals(headerFlyweight.wrapAdjustment(), header.offset());\n        assertNotSame(bufferBuilder.headerBuffer, bufferBuilder.buffer());\n        assertSame(bufferBuilder.headerBuffer, completeHeader.buffer());\n        assertEquals(0, completeHeader.offset());\n        assertEquals(4, completeHeader.initialTermId());\n        assertEquals(48, completeHeader.positionBitsToShift());\n        assertEquals(HEADER_LENGTH + (2 * (frameLength - HEADER_LENGTH)), completeHeader.frameLength());\n        assertEquals((byte)version, completeHeader.buffer().getByte(VERSION_FIELD_OFFSET));\n        assertEquals((byte)0xFD, completeHeader.flags());\n        assertNotEquals(0, completeHeader.flags() & BEGIN_FRAG_FLAG);\n        assertEquals(type, completeHeader.type());\n        assertEquals(termOffset, completeHeader.termOffset());\n        assertEquals(sessionId, completeHeader.sessionId());\n        assertEquals(streamId, completeHeader.streamId());\n        assertEquals(termId, completeHeader.termId());\n        assertEquals(reservedValue, completeHeader.reservedValue());\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"1024, 1408\",\n        \"1024, 128\",\n        \"8192, 1408\"\n    })\n    void shouldCalculatePositionAndFrameLengthWhenReassembling(final int totalPayloadLength, final int mtu)\n    {\n        final int maxPayloadLength = mtu - HEADER_LENGTH;\n        final int termOffset = 1024;\n        final int termId = 10;\n        final int initialTermId = 4;\n        final int positionBitsToShift = 16;\n\n        final int expectedFrameLength = HEADER_LENGTH + totalPayloadLength;\n        final int fragmentedFrameLength = computeFragmentedFrameLength(totalPayloadLength, maxPayloadLength);\n        final long startPosition = computePosition(termId, termOffset, positionBitsToShift, initialTermId);\n        final long expectedPosition = startPosition + fragmentedFrameLength;\n\n        final UnsafeBuffer data = new UnsafeBuffer(new byte[totalPayloadLength]);\n\n        final DataHeaderFlyweight firstHeader = new DataHeaderFlyweight();\n        firstHeader.wrap(new byte[32]);\n        firstHeader.frameLength(HEADER_LENGTH + maxPayloadLength);\n        firstHeader.version((short)15);\n        firstHeader.flags((short)0b1011_1001);\n        firstHeader.headerType(HDR_TYPE_DATA);\n        firstHeader.termOffset(termOffset);\n        firstHeader.sessionId(87);\n        firstHeader.streamId(-9);\n        firstHeader.termId(termId);\n        firstHeader.reservedValue(0xCAFE_BABE_DEAD_BEEFL);\n\n        final Header header = new Header(initialTermId, positionBitsToShift);\n        header.buffer(new UnsafeBuffer(firstHeader.byteArray()));\n        header.offset(firstHeader.wrapAdjustment());\n\n        bufferBuilder.captureHeader(header);\n\n        int lastPayloadLength = 0;\n        for (int position = 0; position < data.capacity(); position += maxPayloadLength)\n        {\n            lastPayloadLength = min(maxPayloadLength, data.capacity() - position);\n            bufferBuilder.append(data, position, lastPayloadLength);\n        }\n\n        assertNotEquals(0, lastPayloadLength);\n\n        final int valueIsIgnored = Integer.MIN_VALUE;\n\n        final DataHeaderFlyweight lastHeader = new DataHeaderFlyweight();\n        lastHeader.wrap(new byte[32]);\n        lastHeader.frameLength(HEADER_LENGTH + lastPayloadLength);\n        lastHeader.version((short)15);\n        lastHeader.flags((short)0b1011_1001);\n        lastHeader.headerType(HDR_TYPE_DATA);\n        lastHeader.termOffset(valueIsIgnored);\n        lastHeader.sessionId(87);\n        lastHeader.streamId(-9);\n        lastHeader.termId(10);\n        lastHeader.reservedValue(0xCAFE_BABE_DEAD_BEEFL);\n        header.buffer(new UnsafeBuffer(lastHeader.byteArray()));\n        header.offset(lastHeader.wrapAdjustment());\n\n        final Header completeHeader = bufferBuilder.completeHeader(header);\n\n        assertEquals(expectedFrameLength, completeHeader.frameLength());\n        assertEquals(fragmentedFrameLength, completeHeader.fragmentedFrameLength());\n        assertEquals(expectedPosition, completeHeader.position());\n    }\n\n    @Test\n    void compactEmptyBufferIsAnOp()\n    {\n        assertSame(bufferBuilder, bufferBuilder.compact());\n\n        assertEquals(0, bufferBuilder.capacity());\n        assertEquals(0, bufferBuilder.limit());\n        assertEquals(0, bufferBuilder.buffer().capacity());\n    }\n\n    @Test\n    void compactIsANoOpIfTheCapacityIsNotDecreasing()\n    {\n        assertSame(bufferBuilder, bufferBuilder.append(new UnsafeBuffer(new byte[5]), 0, 3));\n        assertEquals(3, bufferBuilder.limit());\n        assertEquals(INIT_MIN_CAPACITY, bufferBuilder.capacity());\n        final MutableDirectBuffer originalBuffer = bufferBuilder.buffer();\n        assertEquals(INIT_MIN_CAPACITY, originalBuffer.capacity());\n\n        assertSame(bufferBuilder, bufferBuilder.compact());\n\n        assertEquals(3, bufferBuilder.limit());\n        assertEquals(INIT_MIN_CAPACITY, bufferBuilder.capacity());\n        assertSame(originalBuffer, bufferBuilder.buffer());\n        assertEquals(INIT_MIN_CAPACITY, originalBuffer.capacity());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { false, true })\n    void shouldNotChangeHeaderBufferUponResize(final boolean isDirect)\n    {\n        final BufferBuilder builder = new BufferBuilder(0, isDirect);\n        final UnsafeBuffer headerBuffer = builder.headerBuffer;\n        final ByteBuffer headerByteBuffer = headerBuffer.byteBuffer();\n        final byte[] headerByteArray = headerBuffer.byteArray();\n        headerBuffer.setMemory(0, headerBuffer.capacity(), (byte)0xFA);\n\n        final UnsafeBuffer src = new UnsafeBuffer(new byte[100]);\n        ThreadLocalRandom.current().nextBytes(src.byteArray());\n        builder.append(src, 0, src.capacity());\n\n        assertSame(headerBuffer, builder.headerBuffer);\n        assertSame(headerByteBuffer, builder.headerBuffer.byteBuffer());\n        assertSame(headerByteArray, builder.headerBuffer.byteArray());\n        for (int i = 0; i < headerBuffer.capacity(); i++)\n        {\n            assertEquals((byte)0xFA, headerBuffer.getByte(i));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/ChannelUriStringBuilderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.agrona.SystemUtil;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.Collections;\nimport java.util.Map;\n\nimport static io.aeron.CommonContext.MAX_RESEND_PARAM_NAME;\nimport static io.aeron.CommonContext.PUBLICATION_WINDOW_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.STREAM_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.UNTETHERED_LINGER_TIMEOUT_PARAM_NAME;\nimport static io.aeron.CommonContext.UNTETHERED_RESTING_TIMEOUT_PARAM_NAME;\nimport static io.aeron.CommonContext.UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME;\nimport static io.aeron.CommonContext.RESPONSE_CORRELATION_ID_PARAM_NAME;\nimport static java.util.concurrent.TimeUnit.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass ChannelUriStringBuilderTest\n{\n    @Test\n    void shouldValidateMedia()\n    {\n        assertThrows(IllegalArgumentException.class,\n            () -> new ChannelUriStringBuilder().validate());\n    }\n\n    @Test\n    void shouldValidateEndpointOrControl()\n    {\n        assertThrows(IllegalArgumentException.class,\n            () -> new ChannelUriStringBuilder().media(\"udp\").validate());\n    }\n\n    @Test\n    void shouldValidateInitialPosition()\n    {\n        assertThrows(IllegalArgumentException.class,\n            () -> new ChannelUriStringBuilder().media(\"udp\").endpoint(\"address:port\").termId(999).validate());\n    }\n\n    @Test\n    void shouldGenerateBasicIpcChannel()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"ipc\");\n\n        assertEquals(\"aeron:ipc\", builder.build());\n    }\n\n    @Test\n    void shouldGenerateBasicUdpChannel()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"localhost:9999\");\n\n        assertEquals(\"aeron:udp?endpoint=localhost:9999\", builder.build());\n    }\n\n    @Test\n    void shouldGenerateBasicUdpChannelSpy()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .prefix(\"aeron-spy\")\n            .media(\"udp\")\n            .endpoint(\"localhost:9999\");\n\n        assertEquals(\"aeron-spy:aeron:udp?endpoint=localhost:9999\", builder.build());\n    }\n\n    @Test\n    void shouldGenerateComplexUdpChannel()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"localhost:9999\")\n            .ttl(9)\n            .termLength(1024 * 128);\n\n        assertEquals(\"aeron:udp?endpoint=localhost:9999|term-length=128k|ttl=9\", builder.build());\n    }\n\n    @Test\n    void shouldGenerateReplayUdpChannel()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"address:9999\")\n            .termLength(1024 * 128)\n            .initialTermId(777)\n            .termId(999)\n            .termOffset(64);\n\n        assertEquals(\n            \"aeron:udp?endpoint=address:9999|term-length=128k|init-term-id=777|term-id=999|term-offset=64\",\n            builder.build());\n    }\n\n    @Test\n    void shouldGenerateChannelWithSocketParameters()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"address:9999\")\n            .socketSndbufLength(8192)\n            .socketRcvbufLength(4096);\n\n        assertEquals(\n            \"aeron:udp?endpoint=address:9999|so-sndbuf=8k|so-rcvbuf=4k\",\n            builder.build());\n    }\n\n    @Test\n    void shouldGenerateChannelWithReceiverWindow()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"address:9999\")\n            .receiverWindowLength(8192);\n\n        assertEquals(\n            \"aeron:udp?endpoint=address:9999|rcv-wnd=8k\",\n            builder.build());\n    }\n\n    @Test\n    void shouldGenerateChannelWithLingerTimeout()\n    {\n        final Long lingerNs = 987654321123456789L;\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"ipc\")\n            .linger(lingerNs);\n\n        assertSame(lingerNs, builder.linger());\n        assertEquals(\"aeron:ipc?linger=987654321123456789ns\", builder.build());\n    }\n\n    @Test\n    void shouldGenerateChannelWithoutLingerTimeoutIfNullIsPassed()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"address:9999\")\n            .linger((Long)null);\n\n        assertNull(builder.linger());\n        assertEquals(\n            \"aeron:udp?endpoint=address:9999\",\n            builder.build());\n    }\n\n    @Test\n    void shouldRejectNegativeLingerTimeout()\n    {\n        final IllegalArgumentException exception = assertThrows(\n            IllegalArgumentException.class,\n            () -> new ChannelUriStringBuilder().media(\"udp\").endpoint(\"address:9999\").linger(-1L));\n\n        assertEquals(\"`linger` value cannot be negative: -1\", exception.getMessage());\n    }\n\n    @Test\n    void shouldCopyLingerTimeoutFromChannelUriHumanForm()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder();\n        builder.linger(ChannelUri.parse(\"aeron:ipc?linger=7200s\"));\n\n        assertEquals(HOURS.toNanos(2), builder.linger());\n    }\n\n    @Test\n    void shouldCopyLingerTimeoutFromChannelUriNanoseconds()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder();\n        builder.linger(ChannelUri.parse(\"aeron:udp?linger=19191919191919191\"));\n\n        assertEquals(19191919191919191L, builder.linger());\n    }\n\n    @Test\n    void shouldCopyLingerTimeoutFromChannelUriNoValue()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder();\n        builder.linger(ChannelUri.parse(\"aeron:udp?endpoint=localhost:8080\"));\n\n        assertNull(builder.linger());\n    }\n\n    @Test\n    void shouldCopyLingerTimeoutFromChannelUriNegativeValue()\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:udp?linger=-1000\");\n        assertThrows(IllegalArgumentException.class, () -> new ChannelUriStringBuilder().linger(channelUri));\n    }\n\n    @Test\n    void shouldBuildChannelBuilderUsingExistingStringWithAllTheFields()\n    {\n        final String uri = \"aeron-spy:aeron:udp?endpoint=127.0.0.1:0|interface=127.0.0.1|control=127.0.0.2:0|\" +\n            \"control-mode=manual|tags=2,4|alias=foo|cc=cubic|fc=min|reliable=false|ttl=16|mtu=8992|\" +\n            \"term-length=1m|init-term-id=5|term-offset=64|term-id=4353|session-id=2314234|gtag=3|\" +\n            \"linger=100000055000001ns|sparse=true|eos=true|tether=false|group=false|ssc=true|so-sndbuf=8m|\" +\n            \"so-rcvbuf=2m|rcv-wnd=1m|media-rcv-ts-offset=reserved|channel-rcv-ts-offset=0|\" +\n            \"channel-snd-ts-offset=8|response-endpoint=127.0.0.3:0|response-correlation-id=12345|nak-delay=100us|\" +\n            \"untethered-window-limit-timeout=1us|untethered-resting-timeout=5us|stream-id=87|pub-wnd=10224\";\n\n        final ChannelUri fromString = ChannelUri.parse(uri);\n        final ChannelUri fromBuilder = ChannelUri.parse(new ChannelUriStringBuilder(uri).build());\n\n        assertEquals(Collections.emptyMap(), fromString.diff(fromBuilder));\n    }\n\n    @Test\n    void shouldBuildChannelBuilderUsingExistingStringWithTaggedSessionIdAndIpc()\n    {\n        final String uri = \"aeron:ipc?session-id=tag:123456\";\n\n        final ChannelUri fromString = ChannelUri.parse(uri);\n        final ChannelUri fromBuilder = ChannelUri.parse(new ChannelUriStringBuilder(uri).build());\n\n        assertEquals(Collections.emptyMap(), fromString.diff(fromBuilder));\n    }\n\n    @Test\n    void shouldRejectInvalidOffsets()\n    {\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> new ChannelUriStringBuilder().mediaReceiveTimestampOffset(\"breserved\"));\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> new ChannelUriStringBuilder().channelReceiveTimestampOffset(\"breserved\"));\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> new ChannelUriStringBuilder().channelSendTimestampOffset(\"breserved\"));\n    }\n\n    @Test\n    void shouldRejectInvalidNakDelay()\n    {\n        assertThrows(IllegalArgumentException.class, () -> new ChannelUriStringBuilder().nakDelay(\"foo\"));\n    }\n\n    @Test\n    void shouldHandleNakDelayWithUnits()\n    {\n        assertEquals(1000L, new ChannelUriStringBuilder().nakDelay(\"1us\").nakDelay());\n        assertEquals(1L, new ChannelUriStringBuilder().nakDelay(\"1ns\").nakDelay());\n        assertEquals(1000000L, new ChannelUriStringBuilder().nakDelay(\"1ms\").nakDelay());\n    }\n\n    @Test\n    void shouldHandleUntetheredWindowLimitTimeoutWithUnits()\n    {\n        assertEquals(1000L, new ChannelUriStringBuilder()\n            .untetheredWindowLimitTimeout(\"1us\").untetheredWindowLimitTimeoutNs());\n        assertEquals(1L, new ChannelUriStringBuilder()\n            .untetheredWindowLimitTimeout(\"1ns\").untetheredWindowLimitTimeoutNs());\n        assertEquals(1000000L, new ChannelUriStringBuilder()\n            .untetheredWindowLimitTimeout(\"1ms\").untetheredWindowLimitTimeoutNs());\n    }\n\n    @Test\n    void shouldHandleUntetheredRestingTimeoutWithUnits()\n    {\n        assertEquals(1000L, new ChannelUriStringBuilder()\n            .untetheredRestingTimeout(\"1us\").untetheredRestingTimeoutNs());\n        assertEquals(1L, new ChannelUriStringBuilder()\n            .untetheredRestingTimeout(\"1ns\").untetheredRestingTimeoutNs());\n        assertEquals(1000000L, new ChannelUriStringBuilder()\n            .untetheredRestingTimeout(\"1ms\").untetheredRestingTimeoutNs());\n    }\n\n    @Test\n    void shouldHandleMaxRetransmits()\n    {\n        assertEquals(20, new ChannelUriStringBuilder()\n            .maxResend(20)\n            .maxResend());\n        assertTrue(new ChannelUriStringBuilder().maxResend(20).build()\n            .contains(MAX_RESEND_PARAM_NAME + \"=20\"));\n        assertEquals(30, new ChannelUriStringBuilder()\n            .maxResend(ChannelUri.parse(new ChannelUriStringBuilder().maxResend(30).build()))\n            .maxResend());\n    }\n\n    @Test\n    void shouldHandleStreamId()\n    {\n        assertNull(new ChannelUriStringBuilder().streamId());\n\n        final int streamId = 1234;\n        assertEquals(streamId, new ChannelUriStringBuilder().streamId(streamId).streamId());\n\n        final String uri = new ChannelUriStringBuilder().streamId(streamId).build();\n        assertEquals(Integer.toString(streamId), ChannelUri.parse(uri).get(STREAM_ID_PARAM_NAME));\n    }\n\n    @Test\n    void shouldRejectInvalidStreamId()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:ipc?stream-id=abc\");\n        assertThrows(IllegalArgumentException.class, () -> new ChannelUriStringBuilder().streamId(uri));\n    }\n\n    @Test\n    void shouldHandlePublicationWindowLength()\n    {\n        assertNull(new ChannelUriStringBuilder().publicationWindowLength());\n\n        final int pubWindowLength = 7777;\n        assertEquals(pubWindowLength,\n            new ChannelUriStringBuilder().publicationWindowLength(pubWindowLength).publicationWindowLength());\n\n        final String uri = new ChannelUriStringBuilder().publicationWindowLength(pubWindowLength).build();\n        assertEquals(\n            Integer.toString(pubWindowLength),\n            ChannelUri.parse(uri).get(PUBLICATION_WINDOW_LENGTH_PARAM_NAME));\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"abc\", \"1000000000000\" })\n    void shouldRejectInvalidPublicationWindowLength(final String pubWnd)\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:ipc\");\n        uri.put(PUBLICATION_WINDOW_LENGTH_PARAM_NAME, pubWnd);\n        assertThrows(IllegalArgumentException.class, () -> new ChannelUriStringBuilder().publicationWindowLength(uri));\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"1000,666,2002\", \"50,40,30\" })\n    void shouldHandleUntetheredParameters(\n        final long untetheredWindowLimitTimeoutNs,\n        final long untetheredLingerTimeoutNs,\n        final long untetheredRestingTimeoutNs)\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder(\"aeron:ipc\")\n            .untetheredWindowLimitTimeoutNs(untetheredWindowLimitTimeoutNs)\n            .untetheredLingerTimeoutNs(untetheredLingerTimeoutNs)\n            .untetheredRestingTimeoutNs(untetheredRestingTimeoutNs);\n\n        assertEquals(untetheredWindowLimitTimeoutNs, builder.untetheredWindowLimitTimeoutNs());\n        assertEquals(untetheredLingerTimeoutNs, builder.untetheredLingerTimeoutNs());\n        assertEquals(untetheredRestingTimeoutNs, builder.untetheredRestingTimeoutNs());\n\n        final ChannelUri uri = ChannelUri.parse(builder.build());\n        assertEquals(\n            SystemUtil.formatDuration(untetheredWindowLimitTimeoutNs),\n            uri.get(UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME));\n        assertEquals(\n            SystemUtil.formatDuration(untetheredLingerTimeoutNs),\n            uri.get(UNTETHERED_LINGER_TIMEOUT_PARAM_NAME));\n        assertEquals(\n            SystemUtil.formatDuration(untetheredRestingTimeoutNs),\n            uri.get(UNTETHERED_RESTING_TIMEOUT_PARAM_NAME));\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"this.will.not.work\", \"-2\" })\n    void shouldThrowAnExceptionOnInvalidResponseCorrelationId(final String responseCorrelationId)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:udp?\" + RESPONSE_CORRELATION_ID_PARAM_NAME +\n            \"=\" + responseCorrelationId);\n        assertThrows(IllegalArgumentException.class,\n            () -> new ChannelUriStringBuilder().responseCorrelationId(channelUri));\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"prototype\", \"2\" })\n    void shouldNotThrowAnExceptionOnValidResponseCorrelationId(final String responseCorrelationId)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:udp?\" + RESPONSE_CORRELATION_ID_PARAM_NAME +\n            \"=\" + responseCorrelationId);\n        assertDoesNotThrow(() -> new ChannelUriStringBuilder().responseCorrelationId(channelUri));\n    }\n\n    @Test\n    void shouldFormatSizeAndDurationsWhenCreatingChannelString()\n    {\n        final String channel = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"localhost:5050\")\n            .receiverWindowLength(1024)\n            .mtu(8192)\n            .termLength(4 * 1024 * 1024)\n            .socketSndbufLength(64 * 1024)\n            .socketRcvbufLength(32 * 1024)\n            .publicationWindowLength(1024 * 1024)\n            .untetheredWindowLimitTimeoutNs(MICROSECONDS.toNanos(100))\n            .untetheredLingerTimeoutNs(MILLISECONDS.toNanos(3))\n            .untetheredRestingTimeoutNs(SECONDS.toNanos(1))\n            .linger(MILLISECONDS.toNanos(50))\n            .nakDelay(123456789L)\n            .maxResend(1000)\n            .tether(true)\n            .rejoin(false)\n            .streamId(-87)\n            .initialPosition(17 * 1024 * 1024, -9, 4 * 1024 * 1024)\n            .build();\n        assertEquals(\n            Map.of(),\n            ChannelUri.parse(channel).diff(\n            ChannelUri.parse(\"aeron:udp?endpoint=localhost:5050|mtu=8k|term-length=4m|rcv-wnd=1k|so-sndbuf=64k|\" +\n                \"so-rcvbuf=32k|pub-wnd=1m|untethered-linger-timeout=3ms|untethered-window-limit-timeout=100us|\" +\n                \"untethered-resting-timeout=1s|linger=50ms|nak-delay=123456789ns|max-resend=1000|rejoin=false|\" +\n                \"tether=true|stream-id=-87|term-id=-5|init-term-id=-9|term-offset=1048576\")));\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/ChannelUriTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.test.Tests;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.List;\nimport java.util.function.BiConsumer;\nimport java.util.function.Function;\n\nimport static io.aeron.ChannelUri.MAX_URI_LENGTH;\nimport static io.aeron.ChannelUri.transformAlias;\nimport static java.util.Arrays.asList;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\nimport static org.mockito.Mockito.*;\n\nclass ChannelUriTest\n{\n    @Test\n    void shouldParseSimpleDefaultUri()\n    {\n        assertParseWithMedia(\"aeron:udp\", \"udp\");\n        assertParseWithMedia(\"aeron:ipc\", \"ipc\");\n        assertParseWithMediaAndPrefix(\"aeron-spy:aeron:ipc\", \"aeron-spy\", \"ipc\");\n    }\n\n    @Test\n    void shouldRejectUriWithoutAeronPrefix()\n    {\n        assertInvalid(\":udp\");\n        assertInvalid(\"aeron\");\n        assertInvalid(\"aron:\");\n        assertInvalid(\"eeron:\");\n    }\n\n    @Test\n    void shouldRejectWithOutOfPlaceColon()\n    {\n        assertInvalid(\"aeron:udp:\");\n    }\n\n    @Test\n    void shouldRejectWithInvalidMedia()\n    {\n        assertInvalid(\"aeron:ipcsdfgfdhfgf\");\n    }\n\n    @Test\n    void shouldRejectWithMissingQuerySeparatorWhenFollowedWithParams()\n    {\n        assertInvalid(\"aeron:ipc|sparse=true\");\n    }\n\n    @Test\n    void shouldRejectWithInvalidParams()\n    {\n        assertInvalid(\"aeron:udp?endpoint=localhost:4652|-~@{]|=??#s!£$%====\");\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { MAX_URI_LENGTH + 1, 10000 })\n    void shouldRejectUriIfLengthExceedsMaxAllowed(final int length)\n    {\n        final String uri = Tests.generateStringWithSuffix(\"aeron:ipc?alias=\", \"x\", length);\n        final IllegalArgumentException exception =\n            assertThrows(IllegalArgumentException.class, () -> ChannelUri.parse(uri));\n        assertEquals(\"URI length (\" + uri.length() + \") exceeds max supported length (\" + MAX_URI_LENGTH +\n            \"): \" + uri.substring(0, MAX_URI_LENGTH), exception.getMessage());\n    }\n\n    @Test\n    void shouldParseWithMultipleParameters()\n    {\n        assertParseWithParams(\n            \"aeron:udp?endpoint=224.10.9.8|port=4567|interface=192.168.0.3|ttl=16\",\n            \"endpoint\", \"224.10.9.8\",\n            \"port\", \"4567\",\n            \"interface\", \"192.168.0.3\",\n            \"ttl\", \"16\");\n    }\n\n    @Test\n    void shouldAllowReturnDefaultIfParamNotSpecified()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=224.10.9.8\");\n        assertNull(uri.get(\"interface\"));\n        assertEquals(\"192.168.0.0\", uri.get(\"interface\", \"192.168.0.0\"));\n    }\n\n    @Test\n    void shouldRoundTripToString()\n    {\n        final String uriString = \"aeron:udp?endpoint=224.10.9.8:777\";\n        final ChannelUri uri = ChannelUri.parse(uriString);\n\n        final String result = uri.toString();\n        assertEquals(uriString, result);\n    }\n\n    @Test\n    void shouldRoundTripToStringBuilder()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"224.10.9.8:777\");\n        final String uriString = builder.build();\n        final ChannelUri uri = ChannelUri.parse(uriString);\n\n        assertEquals(uriString, uri.toString());\n    }\n\n    @Test\n    void shouldRoundTripToStringBuilderWithPrefix()\n    {\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .prefix(ChannelUri.SPY_QUALIFIER)\n            .media(\"udp\")\n            .endpoint(\"224.10.9.8:777\");\n        final String uriString = builder.build();\n        final ChannelUri uri = ChannelUri.parse(uriString);\n\n        assertEquals(uriString, uri.toString());\n    }\n\n    @Test\n    void equalsReturnsTrueWhenTheSameInstance()\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\n            \"aeron:udp?endpoint=224.10.9.8|port=4567|interface=192.168.0.3|ttl=16\");\n\n        assertEquals(channelUri, channelUri);\n    }\n\n    @Test\n    void equalsReturnsFalseIfComparedWithNull()\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\n            \"aeron:udp?endpoint=224.10.9.8|port=4567|interface=192.168.0.3|ttl=16\");\n\n        assertNotEquals(channelUri, null);\n    }\n\n    @Test\n    void equalsReturnsFalseIfComparedAnotherClass()\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\n            \"aeron:udp?endpoint=224.10.9.8|port=4567|interface=192.168.0.3|ttl=16\");\n\n        //noinspection AssertBetweenInconvertibleTypes\n        assertNotEquals(channelUri, 123);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"equalityValues\")\n    void equality(final String uri1, final String uri2, final boolean expected)\n    {\n        final ChannelUri parsedUri1 = ChannelUri.parse(uri1);\n        final ChannelUri parsedUri2 = ChannelUri.parse(uri2);\n\n        assertEquals(expected, parsedUri1.equals(parsedUri2));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"equalityValues\")\n    void hashCode(final String uri1, final String uri2, final boolean expected)\n    {\n        final ChannelUri parsedUri1 = ChannelUri.parse(uri1);\n        final ChannelUri parsedUri2 = ChannelUri.parse(uri2);\n\n        if (expected)\n        {\n            assertEquals(parsedUri1.hashCode(), parsedUri2.hashCode());\n        }\n        else\n        {\n            assertNotEquals(parsedUri1.hashCode(), parsedUri2.hashCode());\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource(value = {\n        \"aeron:udp?endpoint=poison|interface=iface|mtu=4444, dest1, aeron:udp?endpoint=dest1|interface=iface\",\n        \"aeron:ipc, dest2, aeron:ipc?endpoint=dest2\",\n        \"aeron:udp, localhost, aeron:udp?endpoint=localhost\",\n        \"aeron:ipc?interface=here, there, aeron:ipc?endpoint=there|interface=here\",\n        \"aeron-spy:aeron:udp?eol=true|interface=none, abc, aeron:udp?endpoint=abc|interface=none\",\n        \"aeron:udp?interface=eth0|term-length=64k|ttl=0|endpoint=some, vm1, aeron:udp?endpoint=vm1|interface=eth0\" })\n    void createDestinationUriTest(final String channel, final String endpoint, final String expected)\n    {\n        final String destinationUri = ChannelUri.createDestinationUri(channel, endpoint);\n        assertEquals(expected, destinationUri);\n    }\n\n    @Test\n    void shouldSubstituteEndpoint()\n    {\n        assertSubstitution(\"aeron:udp?endpoint=localhost:12345\", \"aeron:udp?endpoint=localhost:0\", \"localhost:12345\");\n        assertSubstitution(\n            \"aeron:udp?endpoint=localhost:12345\", \"aeron:udp?endpoint=localhost:12345\", \"localhost:54321\");\n        assertSubstitution(\"aeron:udp?endpoint=localhost:12345\", \"aeron:udp?endpoint=localhost:0\", \"127.0.0.1:12345\");\n        assertSubstitution(\"aeron:udp?endpoint=127.0.0.1:12345\", \"aeron:udp\", \"127.0.0.1:12345\");\n    }\n\n    @Test\n    void shouldThrowIfResolvedEndpointInvalid()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:0\");\n        assertThrows(IllegalArgumentException.class, () -> uri.replaceEndpointWildcardPort(\"localhost:0\"));\n        assertThrows(IllegalArgumentException.class, () -> uri.replaceEndpointWildcardPort(\"localhost\"));\n        assertThrows(NullPointerException.class, () -> uri.replaceEndpointWildcardPort(null));\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    void shouldIterateOverParameters()\n    {\n        final ChannelUri uri = ChannelUri.parse(\n            \"aeron:udp?endpoint=myhost:0|ttl=1|mtu=8k|term-length=64M|alias=text|x=42\");\n        final BiConsumer<String, String> parameterConsumer = mock(BiConsumer.class);\n\n        uri.forEachParameter(parameterConsumer);\n\n        verify(parameterConsumer).accept(\"endpoint\", \"myhost:0\");\n        verify(parameterConsumer).accept(\"ttl\", \"1\");\n        verify(parameterConsumer).accept(\"mtu\", \"8k\");\n        verify(parameterConsumer).accept(\"term-length\", \"64M\");\n        verify(parameterConsumer).accept(\"alias\", \"text\");\n        verify(parameterConsumer).accept(\"x\", \"42\");\n        verifyNoMoreInteractions(parameterConsumer);\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    void shouldNotCallConsumerIfNoParameters()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:ipc\");\n        final BiConsumer<String, String> parameterConsumer = mock(BiConsumer.class);\n\n        uri.forEachParameter(parameterConsumer);\n\n        verifyNoInteractions(parameterConsumer);\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void shouldReturnOriginalUriWhenAliasIsEmpty(final String alias)\n    {\n        final String uri = \"aeron:udp\";\n        assertEquals(uri, ChannelUri.addAliasIfAbsent(uri, alias));\n    }\n\n    @Test\n    void shouldReturnOriginalUriWhenAliasIsAlreadyDefined()\n    {\n        final String uri = \"aeron:udp?alias=xyz|term-length=64k\";\n        final String alias = \"new alias\";\n        assertEquals(uri, ChannelUri.addAliasIfAbsent(uri, alias));\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?term-length=64k|ssc=false|mtu=8k\",\n        \"aeron:ipc\",\n        \"aeron:udp?custom=alias\",\n    })\n    void shouldReturnNewUriWithAnAliasAdded(final String uri)\n    {\n        final String alias = \"my alias\";\n        final ChannelUri channelUri = ChannelUri.parse(uri);\n        channelUri.put(CommonContext.ALIAS_PARAM_NAME, alias);\n\n        final String result = ChannelUri.addAliasIfAbsent(uri, alias);\n        assertNotNull(result);\n        assertEquals(channelUri, ChannelUri.parse(result));\n    }\n\n    @Test\n    void shouldTransformAlias()\n    {\n        assertEquals(\"aeron:ipc\", transformAlias(\"aeron:ipc\", (alias) -> alias));\n        assertEquals(\"aeron:ipc?alias=foo\", transformAlias(\"aeron:ipc?alias=foo\", (alias) -> alias));\n        assertEquals(\"aeron:ipc\", transformAlias(\"aeron:ipc?alias=foo\", (alias) -> null));\n        assertEquals(\"aeron:ipc\", transformAlias(\"aeron:ipc?alias=foo\", (alias) -> \"\"));\n        final Function<String, String> fun = (alias) -> null == alias ? \"default\" : alias + \"bar\";\n        assertEquals(\"aeron:ipc?alias=default\", transformAlias(\"aeron:ipc\", fun));\n        assertEquals(\"aeron:ipc?alias=foobar\", transformAlias(\"aeron:ipc?alias=foo\", fun));\n    }\n\n    private static void assertSubstitution(\n        final String expected,\n        final String originalChannel,\n        final String resolvedEndpoint)\n    {\n        final ChannelUri uri = ChannelUri.parse(originalChannel);\n        uri.replaceEndpointWildcardPort(resolvedEndpoint);\n        assertEquals(expected, uri.toString());\n    }\n\n    private static List<Arguments> equalityValues()\n    {\n        return asList(\n            arguments(\"aeron:udp?endpoint=224.10.9.8\", \"aeron:udp?endpoint=224.10.9.8\", true),\n            arguments(\n                \"aeron:udp?endpoint=224.10.9.8|port=4567|interface=192.168.0.3|ttl=16\",\n                \"aeron:udp?port=4567|endpoint=224.10.9.8|ttl=16|interface=192.168.0.3\",\n                true),\n            arguments(\n                \"aeron:udp?endpoint=224.10.9.8|tags=1,2\",\n                \"aeron:udp?tags=1,2|endpoint=224.10.9.8\",\n                true),\n            arguments(\"aeron:udp?endpoint=224.10.9.8\", \"aeron-spy:aeron:udp?endpoint=224.10.9.8\", false),\n            arguments(\"aeron:udp?endpoint=224.10.9.8\", \"aeron:ipc?endpoint=224.10.9.8\", false),\n            arguments(\"aeron:udp?endpoint=224.10.9.9\", \"aeron:udp?endpoint=224.10.8.8\", false),\n            arguments(\"aeron:udp?endpoint=224.10.9.8|ttl=16\", \"aeron:ipc?endpoint=224.10.9.8|port=4567\", false),\n            arguments(\"aeron:udp?endpoint=224.10.9.8|tags=2,1\", \"aeron:udp?endpoint=224.10.9.8|tags=1,2\", false)\n        );\n    }\n\n    private void assertParseWithParams(final String uriStr, final String... params)\n    {\n        if (params.length % 2 != 0)\n        {\n            throw new IllegalArgumentException();\n        }\n\n        final ChannelUri uri = ChannelUri.parse(uriStr);\n\n        for (int i = 0; i < params.length; i += 2)\n        {\n            assertEquals(params[i + 1], uri.get(params[i]));\n        }\n    }\n\n    private void assertParseWithMedia(final String uriStr, final String media)\n    {\n        assertParseWithMediaAndPrefix(uriStr, \"\", media);\n    }\n\n    private void assertParseWithMediaAndPrefix(final String uriStr, final String prefix, final String media)\n    {\n        final ChannelUri uri = ChannelUri.parse(uriStr);\n        assertEquals(\"aeron\", uri.scheme());\n        assertEquals(prefix, uri.prefix());\n        assertEquals(media, uri.media());\n    }\n\n    private static void assertInvalid(final String string)\n    {\n        try\n        {\n            ChannelUri.parse(string);\n            fail(IllegalArgumentException.class.getName() + \" not thrown\");\n        }\n        catch (final IllegalArgumentException | IllegalStateException ignore)\n        {\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/ClientConductorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.command.ClientTimeoutFlyweight;\nimport io.aeron.command.ControlProtocolEvents;\nimport io.aeron.command.CorrelatedMessageFlyweight;\nimport io.aeron.command.ErrorResponseFlyweight;\nimport io.aeron.command.OperationSucceededFlyweight;\nimport io.aeron.command.PublicationBuffersReadyFlyweight;\nimport io.aeron.command.SubscriptionReadyFlyweight;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ConductorServiceTimeoutException;\nimport io.aeron.exceptions.DriverTimeoutException;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport org.agrona.ErrorHandler;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.SemanticVersion;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.MessageHandler;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.NoOpIdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.broadcast.CopyBroadcastReceiver;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.Lock;\nimport java.util.function.ToIntFunction;\n\nimport static io.aeron.AeronCounters.DRIVER_SYSTEM_COUNTER_TYPE_ID;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION;\nimport static io.aeron.ErrorCode.INVALID_CHANNEL;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PARTITION_COUNT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static java.lang.Boolean.TRUE;\nimport static java.nio.ByteBuffer.allocateDirect;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.hamcrest.core.Is.is;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.atMostOnce;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.only;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ClientConductorTest\n{\n    private static final int TERM_BUFFER_LENGTH = TERM_MIN_LENGTH;\n\n    private static final int SESSION_ID_1 = 13;\n    private static final int SESSION_ID_2 = 15;\n\n    private static final String CHANNEL = \"aeron:udp?endpoint=localhost:40124\";\n    private static final int STREAM_ID_1 = 1002;\n    private static final int STREAM_ID_2 = 1004;\n    private static final int SEND_BUFFER_CAPACITY = 1024;\n    private static final int COUNTER_BUFFER_LENGTH = 1024;\n\n    private static final long CORRELATION_ID = 2000;\n    private static final long CORRELATION_ID_2 = 2002;\n    private static final long CLOSE_CORRELATION_ID = 2001;\n    private static final long UNKNOWN_CORRELATION_ID = 3000;\n\n    private static final long KEEP_ALIVE_INTERVAL = TimeUnit.MILLISECONDS.toNanos(500);\n    private static final long AWAIT_TIMEOUT = 100;\n    private static final long INTER_SERVICE_TIMEOUT_MS = 1000;\n\n    private static final int SUBSCRIPTION_POSITION_ID = 2;\n    private static final int SUBSCRIPTION_POSITION_REGISTRATION_ID = 4001;\n\n    private static final String SOURCE_INFO = \"127.0.0.1:40789\";\n\n    private final PublicationBuffersReadyFlyweight publicationReady = new PublicationBuffersReadyFlyweight();\n    private final SubscriptionReadyFlyweight subscriptionReady = new SubscriptionReadyFlyweight();\n    private final OperationSucceededFlyweight operationSuccess = new OperationSucceededFlyweight();\n    private final ErrorResponseFlyweight errorResponse = new ErrorResponseFlyweight();\n    private final ClientTimeoutFlyweight clientTimeout = new ClientTimeoutFlyweight();\n\n    private final UnsafeBuffer publicationReadyBuffer = new UnsafeBuffer(new byte[SEND_BUFFER_CAPACITY]);\n    private final UnsafeBuffer subscriptionReadyBuffer = new UnsafeBuffer(new byte[SEND_BUFFER_CAPACITY]);\n    private final UnsafeBuffer operationSuccessBuffer = new UnsafeBuffer(new byte[SEND_BUFFER_CAPACITY]);\n    private final UnsafeBuffer errorMessageBuffer = new UnsafeBuffer(new byte[SEND_BUFFER_CAPACITY]);\n    private final UnsafeBuffer clientTimeoutBuffer = new UnsafeBuffer(new byte[SEND_BUFFER_CAPACITY]);\n\n    private final CopyBroadcastReceiver mockToClientReceiver = mock(CopyBroadcastReceiver.class);\n    final UnsafeBuffer counterMetaDataBuffer = new UnsafeBuffer(\n        ByteBuffer.allocateDirect(640 * 1024), 0, 512 * 1024);\n    final UnsafeBuffer counterValuesBuffer = new UnsafeBuffer(\n        counterMetaDataBuffer.byteBuffer(),\n        counterMetaDataBuffer.capacity(),\n        counterMetaDataBuffer.byteBuffer().capacity() - counterMetaDataBuffer.capacity());\n\n    private long timeMs = 0;\n    private final EpochClock epochClock = () -> timeMs += 10;\n\n    private long timeNs = 0;\n    private final NanoClock nanoClock = () -> timeNs += 10_000_000;\n\n    private final ErrorHandler mockClientErrorHandler = spy(new PrintError());\n\n    private Aeron.Context context;\n    private ClientConductor conductor;\n    private final DriverProxy driverProxy = mock(DriverProxy.class);\n    private final AvailableImageHandler mockAvailableImageHandler = mock(AvailableImageHandler.class);\n    private final UnavailableImageHandler mockUnavailableImageHandler = mock(UnavailableImageHandler.class);\n    private final Runnable mockCloseHandler = mock(Runnable.class);\n    private final LogBuffersFactory logBuffersFactory = mock(LogBuffersFactory.class);\n    private final Lock mockClientLock = mock(Lock.class);\n    private final Aeron mockAeron = mock(Aeron.class);\n    private boolean suppressPrintError = false;\n\n    @BeforeEach\n    void setUp()\n    {\n        context = new Aeron.Context()\n            .clientLock(mockClientLock)\n            .epochClock(epochClock)\n            .nanoClock(nanoClock)\n            .awaitingIdleStrategy(new NoOpIdleStrategy())\n            .toClientBuffer(mockToClientReceiver)\n            .driverProxy(driverProxy)\n            .logBuffersFactory(logBuffersFactory)\n            .errorHandler(mockClientErrorHandler)\n            .availableImageHandler(mockAvailableImageHandler)\n            .unavailableImageHandler(mockUnavailableImageHandler)\n            .closeHandler(mockCloseHandler)\n            .keepAliveIntervalNs(KEEP_ALIVE_INTERVAL)\n            .driverTimeoutMs(AWAIT_TIMEOUT)\n            .interServiceTimeoutNs(TimeUnit.MILLISECONDS.toNanos(INTER_SERVICE_TIMEOUT_MS));\n\n        context.countersMetaDataBuffer(counterMetaDataBuffer);\n        context.countersValuesBuffer(counterValuesBuffer);\n\n        when(mockClientLock.tryLock()).thenReturn(TRUE);\n\n        when(driverProxy.addPublication(CHANNEL, STREAM_ID_1)).thenReturn(CORRELATION_ID);\n        when(driverProxy.addPublication(CHANNEL, STREAM_ID_2)).thenReturn(CORRELATION_ID_2);\n        when(driverProxy.removePublication(CORRELATION_ID, false)).thenReturn(CLOSE_CORRELATION_ID);\n        when(driverProxy.addSubscription(anyString(), anyInt())).thenReturn(CORRELATION_ID);\n        when(driverProxy.removeSubscription(CORRELATION_ID)).thenReturn(CLOSE_CORRELATION_ID);\n\n        conductor = new ClientConductor(context, mockAeron);\n\n        publicationReady.wrap(publicationReadyBuffer, 0);\n        subscriptionReady.wrap(subscriptionReadyBuffer, 0);\n        operationSuccess.wrap(operationSuccessBuffer, 0);\n        errorResponse.wrap(errorMessageBuffer, 0);\n        clientTimeout.wrap(clientTimeoutBuffer, 0);\n\n        publicationReady.correlationId(CORRELATION_ID);\n        publicationReady.registrationId(CORRELATION_ID);\n        publicationReady.sessionId(SESSION_ID_1);\n        publicationReady.streamId(STREAM_ID_1);\n        publicationReady.logFileName(SESSION_ID_1 + \"-log\");\n\n        operationSuccess.correlationId(CLOSE_CORRELATION_ID);\n\n        final UnsafeBuffer[] termBuffersSession1 = new UnsafeBuffer[PARTITION_COUNT];\n        final UnsafeBuffer[] termBuffersSession2 = new UnsafeBuffer[PARTITION_COUNT];\n\n        for (int i = 0; i < PARTITION_COUNT; i++)\n        {\n            termBuffersSession1[i] = new UnsafeBuffer(allocateDirect(TERM_BUFFER_LENGTH));\n            termBuffersSession2[i] = new UnsafeBuffer(allocateDirect(TERM_BUFFER_LENGTH));\n        }\n\n        final UnsafeBuffer logMetaDataSession1 = new UnsafeBuffer(allocateDirect(TERM_BUFFER_LENGTH));\n        final UnsafeBuffer logMetaDataSession2 = new UnsafeBuffer(allocateDirect(TERM_BUFFER_LENGTH));\n\n        final MutableDirectBuffer header1 = DataHeaderFlyweight.createDefaultHeader(SESSION_ID_1, STREAM_ID_1, 0);\n        final MutableDirectBuffer header2 = DataHeaderFlyweight.createDefaultHeader(SESSION_ID_2, STREAM_ID_2, 0);\n\n        LogBufferDescriptor.storeDefaultFrameHeader(logMetaDataSession1, header1);\n        LogBufferDescriptor.storeDefaultFrameHeader(logMetaDataSession2, header2);\n\n        final LogBuffers logBuffersSession1 = mock(LogBuffers.class);\n        final LogBuffers logBuffersSession2 = mock(LogBuffers.class);\n\n        when(logBuffersFactory.map(SESSION_ID_1 + \"-log\")).thenReturn(logBuffersSession1);\n        when(logBuffersFactory.map(SESSION_ID_2 + \"-log\")).thenReturn(logBuffersSession2);\n        when(logBuffersFactory.map(SESSION_ID_1 + \"-log\")).thenReturn(logBuffersSession1);\n        when(logBuffersFactory.map(SESSION_ID_2 + \"-log\")).thenReturn(logBuffersSession2);\n\n        when(logBuffersSession1.duplicateTermBuffers()).thenReturn(termBuffersSession1);\n        when(logBuffersSession2.duplicateTermBuffers()).thenReturn(termBuffersSession2);\n\n        when(logBuffersSession1.metaDataBuffer()).thenReturn(logMetaDataSession1);\n        when(logBuffersSession2.metaDataBuffer()).thenReturn(logMetaDataSession2);\n        when(logBuffersSession1.termLength()).thenReturn(TERM_BUFFER_LENGTH);\n        when(logBuffersSession2.termLength()).thenReturn(TERM_BUFFER_LENGTH);\n    }\n\n    // --------------------------------\n    // Publication related interactions\n    // --------------------------------\n\n    @Test\n    void addPublicationShouldNotifyMediaDriver()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY,\n            publicationReadyBuffer,\n            (buffer) -> publicationReady.length());\n\n        conductor.addPublication(CHANNEL, STREAM_ID_1);\n\n        verify(driverProxy).addPublication(CHANNEL, STREAM_ID_1);\n    }\n\n    @Test\n    void addPublicationShouldMapLogFile()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY,\n            publicationReadyBuffer,\n            (buffer) -> publicationReady.length());\n\n        conductor.addPublication(CHANNEL, STREAM_ID_1);\n\n        verify(logBuffersFactory).map(SESSION_ID_1 + \"-log\");\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void addPublicationShouldTimeoutWithoutReadyMessage()\n    {\n        assertThrows(DriverTimeoutException.class, () -> conductor.addPublication(CHANNEL, STREAM_ID_1));\n    }\n\n    @Test\n    void closingPublicationShouldNotifyMediaDriver()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY, publicationReadyBuffer, (buffer) -> publicationReady.length());\n\n        final Publication publication = conductor.addPublication(CHANNEL, STREAM_ID_1);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_OPERATION_SUCCESS,\n            operationSuccessBuffer,\n            (buffer) -> OperationSucceededFlyweight.LENGTH);\n\n        publication.close();\n\n        verify(driverProxy).removePublication(CORRELATION_ID, false);\n    }\n\n    @Test\n    void closingPublicationShouldPurgeCache()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY, publicationReadyBuffer, (buffer) -> publicationReady.length());\n\n        final Publication firstPublication = conductor.addPublication(CHANNEL, STREAM_ID_1);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_OPERATION_SUCCESS,\n            operationSuccessBuffer,\n            (buffer) -> OperationSucceededFlyweight.LENGTH);\n\n        firstPublication.close();\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY, publicationReadyBuffer, (buffer) -> publicationReady.length());\n\n        final Publication secondPublication = conductor.addPublication(CHANNEL, STREAM_ID_1);\n\n        assertThat(firstPublication, not(sameInstance(secondPublication)));\n    }\n\n    @Test\n    void shouldFailToAddPublicationOnMediaDriverError()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_ERROR,\n            errorMessageBuffer,\n            (buffer) ->\n            {\n                errorResponse.errorCode(INVALID_CHANNEL);\n                errorResponse.errorMessage(\"invalid channel\");\n                errorResponse.offendingCommandCorrelationId(CORRELATION_ID);\n                return errorResponse.length();\n            });\n\n        assertThrows(RegistrationException.class, () -> conductor.addPublication(CHANNEL, STREAM_ID_1));\n    }\n\n    @Test\n    void closingPublicationDoesNotRemoveOtherPublications()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY, publicationReadyBuffer, (buffer) -> publicationReady.length());\n\n        final Publication publication = conductor.addPublication(CHANNEL, STREAM_ID_1);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY,\n            publicationReadyBuffer,\n            (buffer) ->\n            {\n                publicationReady.streamId(STREAM_ID_2);\n                publicationReady.sessionId(SESSION_ID_2);\n                publicationReady.logFileName(SESSION_ID_2 + \"-log\");\n                publicationReady.correlationId(CORRELATION_ID_2);\n                publicationReady.registrationId(CORRELATION_ID_2);\n                return publicationReady.length();\n            });\n\n        conductor.addPublication(CHANNEL, STREAM_ID_2);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_OPERATION_SUCCESS,\n            operationSuccessBuffer,\n            (buffer) -> OperationSucceededFlyweight.LENGTH);\n\n        publication.close();\n\n        verify(driverProxy).removePublication(CORRELATION_ID, false);\n        verify(driverProxy, never()).removePublication(CORRELATION_ID_2, false);\n    }\n\n    @Test\n    void shouldNotMapBuffersForUnknownCorrelationId()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY,\n            publicationReadyBuffer,\n            (buffer) ->\n            {\n                publicationReady.correlationId(UNKNOWN_CORRELATION_ID);\n                publicationReady.registrationId(UNKNOWN_CORRELATION_ID);\n                return publicationReady.length();\n            });\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY,\n            publicationReadyBuffer,\n            (buffer) ->\n            {\n                publicationReady.correlationId(CORRELATION_ID);\n                return publicationReady.length();\n            });\n\n        final Publication publication = conductor.addPublication(CHANNEL, STREAM_ID_1);\n        conductor.doWork();\n\n        verify(logBuffersFactory, times(1)).map(anyString());\n        assertThat(publication.registrationId(), is(CORRELATION_ID));\n    }\n\n    @Test\n    void addSubscriptionShouldNotifyMediaDriver()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_SUBSCRIPTION_READY,\n            subscriptionReadyBuffer,\n            (buffer) ->\n            {\n                subscriptionReady.correlationId(CORRELATION_ID);\n                return SubscriptionReadyFlyweight.LENGTH;\n            });\n\n        conductor.addSubscription(CHANNEL, STREAM_ID_1);\n\n        verify(driverProxy).addSubscription(CHANNEL, STREAM_ID_1);\n    }\n\n    @Test\n    void closingSubscriptionShouldNotifyMediaDriver()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_SUBSCRIPTION_READY,\n            subscriptionReadyBuffer,\n            (buffer) ->\n            {\n                subscriptionReady.correlationId(CORRELATION_ID);\n                return SubscriptionReadyFlyweight.LENGTH;\n            });\n\n        final Subscription subscription = conductor.addSubscription(CHANNEL, STREAM_ID_1);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_OPERATION_SUCCESS,\n            operationSuccessBuffer,\n            (buffer) ->\n            {\n                operationSuccess.correlationId(CLOSE_CORRELATION_ID);\n                return CorrelatedMessageFlyweight.LENGTH;\n            });\n\n        subscription.close();\n\n        verify(driverProxy).removeSubscription(CORRELATION_ID);\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void addSubscriptionShouldTimeoutWithoutOperationSuccessful()\n    {\n        assertThrows(DriverTimeoutException.class, () -> conductor.addSubscription(CHANNEL, STREAM_ID_1));\n    }\n\n    @Test\n    void shouldFailToAddSubscriptionOnMediaDriverError()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_ERROR,\n            errorMessageBuffer,\n            (buffer) ->\n            {\n                errorResponse.errorCode(INVALID_CHANNEL);\n                errorResponse.errorMessage(\"invalid channel\");\n                errorResponse.offendingCommandCorrelationId(CORRELATION_ID);\n                return errorResponse.length();\n            });\n\n        assertThrows(RegistrationException.class, () -> conductor.addSubscription(CHANNEL, STREAM_ID_1));\n    }\n\n    @Test\n    void clientNotifiedOfNewImageShouldMapLogFile()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_SUBSCRIPTION_READY,\n            subscriptionReadyBuffer,\n            (buffer) ->\n            {\n                subscriptionReady.correlationId(CORRELATION_ID);\n                return SubscriptionReadyFlyweight.LENGTH;\n            });\n\n        final Subscription subscription = conductor.addSubscription(CHANNEL, STREAM_ID_1);\n\n        conductor.onAvailableImage(\n            CORRELATION_ID,\n            SESSION_ID_1,\n            subscription.registrationId(),\n            SUBSCRIPTION_POSITION_ID,\n            SESSION_ID_1 + \"-log\",\n            SOURCE_INFO);\n\n        verify(logBuffersFactory).map(eq(SESSION_ID_1 + \"-log\"));\n    }\n\n    @Test\n    void clientNotifiedOfNewAndInactiveImages()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_SUBSCRIPTION_READY,\n            subscriptionReadyBuffer,\n            (buffer) ->\n            {\n                subscriptionReady.correlationId(CORRELATION_ID);\n                return SubscriptionReadyFlyweight.LENGTH;\n            });\n\n        final Subscription subscription = conductor.addSubscription(CHANNEL, STREAM_ID_1);\n\n        conductor.onAvailableImage(\n            CORRELATION_ID,\n            SESSION_ID_1,\n            subscription.registrationId(),\n            SUBSCRIPTION_POSITION_ID,\n            SESSION_ID_1 + \"-log\",\n            SOURCE_INFO);\n\n        assertFalse(subscription.hasNoImages());\n        assertTrue(subscription.isConnected());\n        verify(mockAvailableImageHandler).onAvailableImage(any(Image.class));\n\n        conductor.onUnavailableImage(CORRELATION_ID, subscription.registrationId());\n\n        verify(mockUnavailableImageHandler).onUnavailableImage(any(Image.class));\n        assertTrue(subscription.hasNoImages());\n        assertFalse(subscription.isConnected());\n    }\n\n    @Test\n    void shouldIgnoreUnknownNewImage()\n    {\n        conductor.onAvailableImage(\n            CORRELATION_ID_2,\n            SESSION_ID_2,\n            SUBSCRIPTION_POSITION_REGISTRATION_ID,\n            SUBSCRIPTION_POSITION_ID,\n            SESSION_ID_2 + \"-log\",\n            SOURCE_INFO);\n\n        verify(logBuffersFactory, never()).map(anyString());\n        verify(mockAvailableImageHandler, never()).onAvailableImage(any(Image.class));\n    }\n\n    @Test\n    void shouldIgnoreUnknownInactiveImage()\n    {\n        conductor.onUnavailableImage(CORRELATION_ID_2, SUBSCRIPTION_POSITION_REGISTRATION_ID);\n\n        verify(logBuffersFactory, never()).map(anyString());\n        verify(mockUnavailableImageHandler, never()).onUnavailableImage(any(Image.class));\n    }\n\n    @Test\n    void shouldTimeoutInterServiceIfTooLongBetweenDoWorkCalls()\n    {\n        suppressPrintError = true;\n\n        conductor.doWork();\n\n        timeNs += (TimeUnit.MILLISECONDS.toNanos(INTER_SERVICE_TIMEOUT_MS) + 1);\n\n        conductor.doWork();\n\n        verify(mockClientErrorHandler).onError(any(ConductorServiceTimeoutException.class));\n\n        assertTrue(conductor.isTerminating());\n    }\n\n    @Test\n    void shouldTerminateAndErrorOnClientTimeoutFromDriver()\n    {\n        suppressPrintError = true;\n\n        conductor.onClientTimeout();\n        verify(mockClientErrorHandler).onError(any(TimeoutException.class));\n\n        boolean threwException = false;\n        try\n        {\n            conductor.doWork();\n        }\n        catch (final AgentTerminationException ex)\n        {\n            threwException = true;\n        }\n\n        assertTrue(threwException);\n        assertTrue(conductor.isTerminating());\n\n        conductor.onClose();\n        verify(mockCloseHandler).run();\n    }\n\n    @Test\n    void shouldNotCloseAndErrorOnClientTimeoutForAnotherClientIdFromDriver()\n    {\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_CLIENT_TIMEOUT,\n            clientTimeoutBuffer,\n            (buffer) ->\n            {\n                clientTimeout.clientId(conductor.driverListenerAdapter().clientId() + 1);\n                return ClientTimeoutFlyweight.LENGTH;\n            });\n\n        conductor.doWork();\n\n        verify(mockClientErrorHandler, never()).onError(any(TimeoutException.class));\n\n        assertFalse(conductor.isClosed());\n    }\n\n    @Test\n    void shouldRemovePendingSubscriptionByRegistrationId()\n    {\n        final String channel = \"aeron:ipc?alias=test\";\n        final int streamId = 42;\n        final long subscriptionId = -7777777777777L;\n        when(driverProxy.addSubscription(channel, streamId)).thenReturn(subscriptionId);\n\n        assertEquals(subscriptionId, conductor.asyncAddSubscription(channel, streamId));\n\n        assertTrue(conductor.asyncCommandIdSet.contains(subscriptionId));\n        final ClientConductor.PendingSubscription pendingSubscription =\n            (ClientConductor.PendingSubscription)conductor.resourceByRegIdMap.get(subscriptionId);\n        assertNotNull(pendingSubscription);\n        assertNotNull(pendingSubscription.subscription);\n        assertFalse(pendingSubscription.subscription.isClosed);\n\n        conductor.removeSubscription(subscriptionId);\n        assertFalse(conductor.asyncCommandIdSet.contains(subscriptionId));\n        assertNull(conductor.resourceByRegIdMap.get(subscriptionId));\n        verify(driverProxy, atMostOnce()).removeSubscription(subscriptionId);\n        assertTrue(pendingSubscription.subscription.isClosed());\n    }\n\n    @Test\n    void shouldRemoveSubscriptionByRegistrationId()\n    {\n        final String channel = \"aeron:ipc?alias=test\";\n        final int streamId = 42;\n        final long subscriptionId = 472394234579L;\n        final int statusIndicatorId = -444;\n        when(driverProxy.addSubscription(channel, streamId)).thenReturn(subscriptionId);\n        assertEquals(subscriptionId, conductor.asyncAddSubscription(channel, streamId));\n        conductor.onNewSubscription(subscriptionId, statusIndicatorId);\n\n\n        final Subscription subscription =\n            (Subscription)conductor.resourceByRegIdMap.get(subscriptionId);\n        assertNotNull(subscription);\n        assertFalse(subscription.isClosed);\n\n        conductor.removeSubscription(subscriptionId);\n        assertFalse(conductor.asyncCommandIdSet.contains(subscriptionId));\n        assertNull(conductor.resourceByRegIdMap.get(subscriptionId));\n        verify(driverProxy, atMostOnce()).removeSubscription(subscriptionId);\n        assertTrue(subscription.isClosed());\n    }\n\n    @Test\n    void shouldNotifyDriverWhenAsyncSubscriptionByRegistrationId()\n    {\n        final long subscriptionId = 42;\n        conductor.asyncCommandIdSet.add(subscriptionId);\n\n        conductor.removeSubscription(subscriptionId);\n        assertFalse(conductor.asyncCommandIdSet.contains(subscriptionId));\n        verify(driverProxy, only()).removeSubscription(subscriptionId);\n    }\n\n    @Test\n    void removeSubscriptionByRegistrationIdIsANoOpIfIdIsUnknown()\n    {\n        final long subscriptionId = 666;\n\n        conductor.removeSubscription(subscriptionId);\n\n        assertFalse(conductor.asyncCommandIdSet.contains(subscriptionId));\n        verify(driverProxy, never()).removeSubscription(subscriptionId);\n    }\n\n    @Test\n    void shouldThrowAeronExceptionOnAttemptToRemoveWrongResourceUsingSubscriptionRegistrationId()\n    {\n        final long registrationId = 42;\n        conductor.resourceByRegIdMap.put(registrationId, \"test resource\");\n\n        final AeronException exception =\n            assertThrowsExactly(AeronException.class, () -> conductor.removeSubscription(registrationId));\n        assertEquals(\"ERROR - registration id is not a Subscription: String\", exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=localhost:5050\",\n        \"aeron:udp?endpoint=localhost:5050|sparse=true\",\n        \"aeron:udp?endpoint=localhost:8080|sparse=false\" })\n    void shouldPreTouchLogBuffersForNewImage(final String channel)\n    {\n        final int streamId = 42;\n        final long subscriptionId = -472398432L;\n        context.preTouchMappedMemory(true);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_SUBSCRIPTION_READY,\n            subscriptionReadyBuffer,\n            (buffer) ->\n            {\n                subscriptionReady.correlationId(subscriptionId);\n                return SubscriptionReadyFlyweight.LENGTH;\n            });\n        when(driverProxy.addSubscription(channel, streamId)).thenReturn(subscriptionId);\n        final Subscription subscription = conductor.addSubscription(channel, streamId);\n\n        final String logFileName = SESSION_ID_1 + \"-log\";\n        conductor.onAvailableImage(\n            subscriptionId,\n            SESSION_ID_1,\n            subscription.registrationId(),\n            SUBSCRIPTION_POSITION_ID,\n            logFileName,\n            SOURCE_INFO);\n\n        final LogBuffers logBuffers = logBuffersFactory.map(logFileName);\n        assertNotNull(logBuffers);\n        verify(logBuffers, times(1)).preTouch();\n    }\n\n    @Test\n    void shouldNotPreTouchLogBuffersForImageIfDisabled()\n    {\n        final int streamId = 42;\n        final String channel = \"aeron:ipc?alias=test\";\n        final long subscriptionId = -472398432L;\n        context.preTouchMappedMemory(false);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_SUBSCRIPTION_READY,\n            subscriptionReadyBuffer,\n            (buffer) ->\n            {\n                subscriptionReady.correlationId(subscriptionId);\n                return SubscriptionReadyFlyweight.LENGTH;\n            });\n        when(driverProxy.addSubscription(channel, streamId)).thenReturn(subscriptionId);\n        final Subscription subscription = conductor.addSubscription(channel, streamId);\n\n        final String logFileName = SESSION_ID_1 + \"-log\";\n        conductor.onAvailableImage(\n            subscriptionId,\n            SESSION_ID_1,\n            subscription.registrationId(),\n            SUBSCRIPTION_POSITION_ID,\n            logFileName,\n            SOURCE_INFO);\n\n        final LogBuffers logBuffers = logBuffersFactory.map(logFileName);\n        assertNotNull(logBuffers);\n        verify(logBuffers, never()).preTouch();\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=localhost:5050\",\n        \"aeron:udp?endpoint=localhost:5050|sparse=true\",\n        \"aeron:udp?endpoint=localhost:8080|sparse=false\" })\n    void shouldPreTouchLogBuffersForNewPublication(final String channel)\n    {\n        final int streamId = -53453894;\n        final long publicationId = 113;\n        final String logFileName = SESSION_ID_2 + \"-log\";\n        context.preTouchMappedMemory(true);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY,\n            publicationReadyBuffer,\n            (buffer) ->\n            {\n                publicationReady.correlationId(publicationId);\n                publicationReady.registrationId(publicationId);\n                publicationReady.logFileName(logFileName);\n                return publicationReady.length();\n            });\n        when(driverProxy.addPublication(channel, streamId)).thenReturn(publicationId);\n        final ConcurrentPublication publication = conductor.addPublication(channel, streamId);\n        assertNotNull(publication);\n\n        final LogBuffers logBuffers = logBuffersFactory.map(logFileName);\n        assertNotNull(logBuffers);\n        verify(logBuffers, times(1)).preTouch();\n    }\n\n    @Test\n    void shouldNotPreTouchLogBuffersForPublicationIfDisabled()\n    {\n        final int streamId = -53453894;\n        final String channel = \"aeron:ipc?alias=test\";\n        final long publicationId = 113;\n        final String logFileName = SESSION_ID_2 + \"-log\";\n        context.preTouchMappedMemory(false);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_PUBLICATION_READY,\n            publicationReadyBuffer,\n            (buffer) ->\n            {\n                publicationReady.correlationId(publicationId);\n                publicationReady.registrationId(publicationId);\n                publicationReady.logFileName(logFileName);\n                return publicationReady.length();\n            });\n        when(driverProxy.addPublication(channel, streamId)).thenReturn(publicationId);\n        final ConcurrentPublication publication = conductor.addPublication(channel, streamId);\n        assertNotNull(publication);\n\n        final LogBuffers logBuffers = logBuffersFactory.map(logFileName);\n        assertNotNull(logBuffers);\n        verify(logBuffers, never()).preTouch();\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=localhost:5050\",\n        \"aeron:udp?endpoint=localhost:5050|sparse=true\",\n        \"aeron:udp?endpoint=localhost:8080|sparse=false\" })\n    void shouldPreTouchLogBuffersForNewExclusivePublication(final String channel)\n    {\n        final int streamId = -53453894;\n        final long publicationId = 113;\n        final String logFileName = SESSION_ID_2 + \"-log\";\n        context.preTouchMappedMemory(true);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_EXCLUSIVE_PUBLICATION_READY,\n            publicationReadyBuffer,\n            (buffer) ->\n            {\n                publicationReady.correlationId(publicationId);\n                publicationReady.registrationId(publicationId);\n                publicationReady.logFileName(logFileName);\n                return publicationReady.length();\n            });\n        when(driverProxy.addExclusivePublication(channel, streamId)).thenReturn(publicationId);\n        final ExclusivePublication publication = conductor.addExclusivePublication(channel, streamId);\n        assertNotNull(publication);\n\n        final LogBuffers logBuffers = logBuffersFactory.map(logFileName);\n        assertNotNull(logBuffers);\n        verify(logBuffers, times(1)).preTouch();\n    }\n\n    @Test\n    void shouldNotPreTouchLogBuffersForExclusivePublicationIfDisabled()\n    {\n        final int streamId = -53453894;\n        final String channel = \"aeron:ipc?alias=test\";\n        final long publicationId = 113;\n        final String logFileName = SESSION_ID_2 + \"-log\";\n        context.preTouchMappedMemory(false);\n\n        whenReceiveBroadcastOnMessage(\n            ControlProtocolEvents.ON_EXCLUSIVE_PUBLICATION_READY,\n            publicationReadyBuffer,\n            (buffer) ->\n            {\n                publicationReady.correlationId(publicationId);\n                publicationReady.registrationId(publicationId);\n                publicationReady.logFileName(logFileName);\n                return publicationReady.length();\n            });\n        when(driverProxy.addExclusivePublication(channel, streamId)).thenReturn(publicationId);\n        final ExclusivePublication publication = conductor.addExclusivePublication(channel, streamId);\n        assertNotNull(publication);\n\n        final LogBuffers logBuffers = logBuffersFactory.map(logFileName);\n        assertNotNull(logBuffers);\n        verify(logBuffers, never()).preTouch();\n    }\n\n    @Test\n    void shouldUseZeroAsControlProtocolVersionIfCounterNotAvailable()\n    {\n        assertEquals(0, conductor.controlProtocolVersion);\n    }\n\n    @Test\n    void shouldGetControlProtocolVersionFromSystemCounter()\n    {\n        final int expectedVersion = SemanticVersion.compose(123, 98, 19);\n        final CountersManager countersManager = new CountersManager(counterMetaDataBuffer, counterValuesBuffer);\n        for (int i = 0; i <= SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION; i++)\n        {\n            allocateSystemCounter(countersManager, i, \"#\" + i);\n        }\n        countersManager.setCounterValue(SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION, expectedVersion);\n\n        final ClientConductor clientConductor = new ClientConductor(context, mockAeron);\n        assertEquals(expectedVersion, clientConductor.controlProtocolVersion);\n    }\n\n    @Test\n    void shouldReturnRandomIdIfMediaDriverDoesNotSupportNextAvailableSessionIdCommand()\n    {\n        final int streamId = 42;\n\n        final int sessionId1 = conductor.nextSessionId(streamId);\n        final int sessionId2 = conductor.nextSessionId(streamId);\n        final int sessionId3 = conductor.nextSessionId(streamId);\n\n        assertNotEquals(sessionId1, sessionId2);\n        assertNotEquals(sessionId1, sessionId3);\n        assertNotEquals(sessionId1 + 1, sessionId2);\n        assertNotEquals(sessionId2 + 1, sessionId3);\n    }\n\n    @Test\n    void asyncRemoveCounterByRegistrationIdIsANoOpIfIdIsUnknown()\n    {\n        final long subscriptionId = 666;\n\n        conductor.asyncRemoveCounter(subscriptionId);\n\n        assertFalse(conductor.asyncCommandIdSet.contains(subscriptionId));\n        verify(driverProxy, never()).removeCounter(subscriptionId);\n    }\n\n    @Test\n    void shouldThrowAeronExceptionOnAttemptToRemoveWrongResourceUsingCounterRegistrationId()\n    {\n        final long registrationId = 42;\n        conductor.resourceByRegIdMap.put(registrationId, \"test resource\");\n\n        final AeronException exception =\n            assertThrowsExactly(AeronException.class, () -> conductor.asyncRemoveCounter(registrationId));\n        assertEquals(\"ERROR - registration id is not a Counter: String\", exception.getMessage());\n    }\n\n    @Test\n    void shouldRemoveCounterByRegistrationId()\n    {\n        final int typeId = 42;\n        final String label = \"test\";\n        final long counterRegistrationId = 777;\n        final int counterId = 10;\n        when(driverProxy.addCounter(typeId, label)).thenReturn(counterRegistrationId);\n        assertEquals(counterRegistrationId, conductor.asyncAddCounter(typeId, label));\n        conductor.onNewCounter(counterRegistrationId, counterId);\n\n\n        final Counter counter =\n            (Counter)conductor.resourceByRegIdMap.get(counterRegistrationId);\n        assertNotNull(counter);\n        assertFalse(counter.isClosed());\n\n        conductor.asyncRemoveCounter(counterRegistrationId);\n        assertFalse(conductor.asyncCommandIdSet.contains(counterRegistrationId));\n        assertNull(conductor.resourceByRegIdMap.get(counterRegistrationId));\n        verify(driverProxy, atMostOnce()).removeCounter(counterRegistrationId);\n        assertTrue(counter.isClosed());\n    }\n\n    @Test\n    void shouldRemovePendingCounterByRegistrationId()\n    {\n        final int typeId = 42;\n        final String label = \"test\";\n        final long counterRegistrationId = 777;\n        when(driverProxy.addCounter(typeId, label)).thenReturn(counterRegistrationId);\n        assertEquals(counterRegistrationId, conductor.asyncAddCounter(typeId, label));\n\n        assertTrue(conductor.asyncCommandIdSet.contains(counterRegistrationId));\n        assertNull(conductor.resourceByRegIdMap.get(counterRegistrationId));\n\n        conductor.asyncRemoveCounter(counterRegistrationId);\n        assertFalse(conductor.asyncCommandIdSet.contains(counterRegistrationId));\n        verify(driverProxy, atMostOnce()).removeCounter(counterRegistrationId);\n    }\n\n    private void whenReceiveBroadcastOnMessage(\n        final int msgTypeId, final MutableDirectBuffer buffer, final ToIntFunction<MutableDirectBuffer> filler)\n    {\n        doAnswer(\n            (invocation) ->\n            {\n                final int length = filler.applyAsInt(buffer);\n                conductor.driverListenerAdapter().onMessage(msgTypeId, buffer, 0, length);\n\n                return 1;\n            })\n            .when(mockToClientReceiver).receive(any(MessageHandler.class));\n    }\n\n    private static void allocateSystemCounter(\n        final CountersManager countersManager, final int counterId, final String label)\n    {\n        final int id = countersManager.allocate(\n            label,\n            DRIVER_SYSTEM_COUNTER_TYPE_ID,\n            (buffer) -> buffer.putInt(0, counterId));\n        assertEquals(counterId, id);\n        countersManager.setCounterRegistrationId(id, counterId);\n        countersManager.setCounterOwnerId(id, Aeron.NULL_VALUE);\n    }\n\n    class PrintError implements ErrorHandler\n    {\n        public void onError(final Throwable throwable)\n        {\n            if (!suppressPrintError)\n            {\n                throwable.printStackTrace();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/CommonContextTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\nimport org.agrona.concurrent.errors.LoggingErrorHandler;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.InOrder;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.file.Path;\n\nimport static io.aeron.CommonContext.FALLBACK_LOGGER_PROP_NAME;\nimport static java.nio.ByteBuffer.allocateDirect;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.AdditionalMatchers.and;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.endsWith;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.startsWith;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.withSettings;\n\nclass CommonContextTest\n{\n    @TempDir\n    private Path tempDir;\n\n    @Test\n    void shouldNotAllowConcludeMoreThanOnce()\n    {\n        final CommonContext ctx = new CommonContext();\n        ctx.conclude();\n\n        assertThrows(ConcurrentConcludeException.class, ctx::conclude);\n    }\n\n    @Test\n    void setupErrorHandlerReturnsALoggingErrorHandlerInstanceIfNoUserErrorHandlerSupplied()\n    {\n        final DistinctErrorLog distinctErrorLog = mock(DistinctErrorLog.class);\n\n        final ErrorHandler errorHandler = CommonContext.setupErrorHandler(null, distinctErrorLog);\n\n        assertNotNull(errorHandler);\n        final LoggingErrorHandler loggingErrorHandler = assertInstanceOf(LoggingErrorHandler.class, errorHandler);\n        assertSame(distinctErrorLog, loggingErrorHandler.distinctErrorLog());\n        assertSame(System.err, loggingErrorHandler.errorOverflow());\n    }\n\n    @Test\n    void setupErrorHandlerUsesAFallBackLoggingHandlerForTheOverflow()\n    {\n        System.setProperty(FALLBACK_LOGGER_PROP_NAME, \"no_op\");\n        try\n        {\n            final DistinctErrorLog distinctErrorLog = mock(DistinctErrorLog.class);\n\n            final ErrorHandler errorHandler = CommonContext.setupErrorHandler(null, distinctErrorLog);\n\n            assertNotNull(errorHandler);\n            final LoggingErrorHandler loggingErrorHandler = assertInstanceOf(LoggingErrorHandler.class, errorHandler);\n            assertSame(distinctErrorLog, loggingErrorHandler.distinctErrorLog());\n            assertSame(CommonContext.fallbackLogger(), loggingErrorHandler.errorOverflow());\n        }\n        finally\n        {\n            System.clearProperty(FALLBACK_LOGGER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void setupErrorHandlerReturnsAnErrorHandlerThatFirstInvokesLoggingErrorHandlerBeforeCallingSuppliedErrorHandler()\n    {\n        final Throwable throwable = new Throwable(\"Hello, world!\");\n        final ErrorHandler userErrorHandler = mock(ErrorHandler.class);\n        final AssertionError userHandlerError = new AssertionError(\"user handler error\");\n        doThrow(userHandlerError).when(userErrorHandler).onError(throwable);\n        final DistinctErrorLog distinctErrorLog = mock(DistinctErrorLog.class);\n        doReturn(true).when(distinctErrorLog).record(any(Throwable.class));\n        final InOrder inOrder = inOrder(userErrorHandler, distinctErrorLog);\n\n        final ErrorHandler errorHandler = CommonContext.setupErrorHandler(userErrorHandler, distinctErrorLog);\n\n        assertNotNull(errorHandler);\n        assertNotSame(userErrorHandler, errorHandler);\n\n        final AssertionError error = assertThrowsExactly(AssertionError.class, () -> errorHandler.onError(throwable));\n        assertSame(userHandlerError, error);\n\n        inOrder.verify(distinctErrorLog).record(throwable);\n        inOrder.verify(userErrorHandler).onError(throwable);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void setupErrorHandlerShouldCloseUserErrorHandlerAfterClosingTheLoggingErrorHandler() throws Exception\n    {\n        final Throwable throwable = mock(Throwable.class);\n        final ErrorHandler userErrorHandler =\n            mock(ErrorHandler.class, withSettings().extraInterfaces(AutoCloseable.class));\n        ((AutoCloseable)doThrow(new IOException(\"failed to close\")).when(userErrorHandler)).close();\n        final DistinctErrorLog distinctErrorLog = mock(DistinctErrorLog.class);\n        doReturn(true).when(distinctErrorLog).record(any(Throwable.class));\n        final PrintStream fallbackErrorStream = mock(PrintStream.class);\n\n        final InOrder inOrder = inOrder(userErrorHandler, distinctErrorLog, throwable, fallbackErrorStream);\n\n        final ErrorHandler errorHandler =\n            CommonContext.setupErrorHandler(userErrorHandler, distinctErrorLog, fallbackErrorStream);\n        assertInstanceOf(AutoCloseable.class, errorHandler);\n\n        ((AutoCloseable)errorHandler).close();\n\n        errorHandler.onError(throwable);\n\n        inOrder.verify((AutoCloseable)userErrorHandler).close();\n        inOrder.verify(fallbackErrorStream).println(\"error log is closed\");\n        inOrder.verify(throwable).printStackTrace(fallbackErrorStream);\n        inOrder.verify(userErrorHandler).onError(throwable);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void saveExistingErrorsIsANoOpIfErrorBufferIsEmpty()\n    {\n        final File markFile = tempDir.resolve(\"mark.dat\").toFile();\n        final UnsafeBuffer errorBuffer = new UnsafeBuffer(new byte[0]);\n        final PrintStream logger = mock(PrintStream.class);\n        final String errorFilePrefix = \"test-error-\";\n\n        CommonContext.saveExistingErrors(markFile, errorBuffer, logger, errorFilePrefix);\n\n        verifyNoInteractions(logger);\n    }\n\n    @Test\n    void saveExistingErrorsCreatesErrorFileInTheSameDirectoryAsTheCorrespondingMarkFile()\n    {\n        final File markFile = tempDir.resolve(\"mark.dat\").toFile();\n        final DistinctErrorLog errorLog =\n            new DistinctErrorLog(new UnsafeBuffer(allocateDirect(16 * 1024)), SystemEpochClock.INSTANCE);\n        assertTrue(errorLog.record(new Exception(\"Just to test\")));\n        final PrintStream logger = mock(PrintStream.class);\n        final String errorFilePrefix = \"my-file-\";\n\n        CommonContext.saveExistingErrors(markFile, errorLog.buffer(), logger, errorFilePrefix);\n\n        final File[] files = tempDir.toFile().listFiles(\n            (dir, name) -> name.endsWith(\"-error.log\") && name.startsWith(errorFilePrefix));\n        assertNotNull(files);\n        assertEquals(1, files.length);\n\n        verify(logger).println(and(startsWith(\"WARNING: existing errors saved to: \"), endsWith(\"-error.log\")));\n        verifyNoMoreInteractions(logger);\n    }\n\n    @Test\n    void fallbackLoggerReturnsSystemErrorIfNothingSpecified()\n    {\n        System.clearProperty(FALLBACK_LOGGER_PROP_NAME);\n        assertSame(System.err, CommonContext.fallbackLogger());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"\", \"stderr\", \"gaga\" })\n    void fallbackLoggerReturnsSystemError(final String logger)\n    {\n        System.setProperty(FALLBACK_LOGGER_PROP_NAME, logger);\n        try\n        {\n            assertSame(System.err, CommonContext.fallbackLogger());\n        }\n        finally\n        {\n            System.clearProperty(FALLBACK_LOGGER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void fallbackLoggerReturnsSystemOutIfConfigured()\n    {\n        System.setProperty(FALLBACK_LOGGER_PROP_NAME, \"stdout\");\n        try\n        {\n            assertSame(System.out, CommonContext.fallbackLogger());\n        }\n        finally\n        {\n            System.clearProperty(FALLBACK_LOGGER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void fallbackLoggerReturnsNoOpLoggerIfConfigured()\n    {\n        System.setProperty(FALLBACK_LOGGER_PROP_NAME, \"no_op\");\n        try\n        {\n            final PrintStream logger = CommonContext.fallbackLogger();\n            assertNotNull(logger);\n            assertNotSame(System.err, logger);\n            assertNotSame(System.out, logger);\n            assertSame(logger, CommonContext.fallbackLogger());\n        }\n        finally\n        {\n            System.clearProperty(FALLBACK_LOGGER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldConcludeAeronDirectory(@TempDir final Path tempDir) throws IOException\n    {\n        final Path aeronDirectory = tempDir.resolve(\"aeron.dir\");\n        final CommonContext commonContext = new CommonContext();\n        commonContext.aeronDirectoryName(aeronDirectory.toString());\n        assertNull(commonContext.aeronDirectory());\n\n        assertSame(commonContext, commonContext.concludeAeronDirectory());\n\n        final File concludedDir = commonContext.aeronDirectory();\n        assertEquals(aeronDirectory.toFile().getCanonicalFile(), concludedDir);\n\n        commonContext.concludeAeronDirectory();\n        assertSame(concludedDir, commonContext.aeronDirectory());\n    }\n\n    @Test\n    void shouldCanonicalizeAeronDirectoryPath(@TempDir final Path tempDir) throws IOException\n    {\n        final Path path = tempDir.resolve(\"one/two/../three/four/./x/y/z\");\n        final CommonContext commonContext = new CommonContext();\n        commonContext.aeronDirectoryName(path.toString());\n        assertNull(commonContext.aeronDirectory());\n\n        assertSame(commonContext, commonContext.concludeAeronDirectory());\n\n        assertEquals(\n            tempDir.resolve(\"one/three/four/x/y/z\").toFile().getCanonicalFile(),\n            commonContext.aeronDirectory());\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/DriverProxyTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.command.RemovePublicationFlyweight;\nimport io.aeron.command.RemoveSubscriptionFlyweight;\nimport org.junit.jupiter.api.Test;\nimport io.aeron.command.PublicationMessageFlyweight;\nimport org.agrona.concurrent.MessageHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.command.ControlProtocolEvents.*;\nimport static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TRAILER_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass DriverProxyTest\n{\n    public static final String CHANNEL = \"aeron:udp?interface=localhost:40123|endpoint=localhost:40124\";\n\n    private static final int STREAM_ID = 1001;\n    private static final long CORRELATION_ID = 3;\n    private static final long CLIENT_ID = 7;\n    private final RingBuffer conductorBuffer = new ManyToOneRingBuffer(\n        new UnsafeBuffer(ByteBuffer.allocateDirect(TRAILER_LENGTH + 1024)));\n    private final DriverProxy conductor = new DriverProxy(conductorBuffer, CLIENT_ID);\n\n    @Test\n    void threadSendsAddChannelMessage()\n    {\n        threadSendsChannelMessage(() -> conductor.addPublication(CHANNEL, STREAM_ID), ADD_PUBLICATION);\n    }\n\n    @Test\n    void threadSendsRemoveChannelMessage()\n    {\n        conductor.removePublication(CORRELATION_ID, false);\n        assertReadsOneMessage(\n            (msgTypeId, buffer, index, length) ->\n            {\n                final RemovePublicationFlyweight message = new RemovePublicationFlyweight();\n                message.wrap(buffer, index);\n\n                assertEquals(REMOVE_PUBLICATION, msgTypeId);\n                assertEquals(CORRELATION_ID, message.registrationId());\n            }\n        );\n    }\n\n    @Test\n    void threadSendsRemoveSubscriberMessage()\n    {\n        conductor.removeSubscription(CORRELATION_ID);\n\n        assertReadsOneMessage(\n            (msgTypeId, buffer, index, length) ->\n            {\n                final RemoveSubscriptionFlyweight removeMessage = new RemoveSubscriptionFlyweight();\n                removeMessage.wrap(buffer, index);\n\n                assertEquals(REMOVE_SUBSCRIPTION, msgTypeId);\n                assertEquals(CORRELATION_ID, removeMessage.registrationId());\n            }\n        );\n    }\n\n    private void threadSendsChannelMessage(final Runnable sendMessage, final int expectedMsgTypeId)\n    {\n        sendMessage.run();\n\n        assertReadsOneMessage(\n            (msgTypeId, buffer, index, length) ->\n            {\n                final PublicationMessageFlyweight publicationMessage = new PublicationMessageFlyweight();\n                publicationMessage.wrap(buffer, index);\n\n                assertEquals(expectedMsgTypeId, msgTypeId);\n                assertEquals(CHANNEL, publicationMessage.channel());\n                assertEquals(STREAM_ID, publicationMessage.streamId());\n            }\n        );\n    }\n\n    private void assertReadsOneMessage(final MessageHandler handler)\n    {\n        final int messageCount = conductorBuffer.read(handler);\n        assertEquals(1, messageCount);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/FlyweightTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.junit.jupiter.api.Test;\nimport io.aeron.command.PublicationMessageFlyweight;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport io.aeron.protocol.NakFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass FlyweightTest\n{\n    private final ByteBuffer buffer = ByteBuffer.allocate(512);\n\n    private final UnsafeBuffer aBuff = new UnsafeBuffer(buffer);\n    private final HeaderFlyweight encodeHeader = new HeaderFlyweight();\n    private final HeaderFlyweight decodeHeader = new HeaderFlyweight();\n    private final DataHeaderFlyweight encodeDataHeader = new DataHeaderFlyweight();\n    private final DataHeaderFlyweight decodeDataHeader = new DataHeaderFlyweight();\n    private final PublicationMessageFlyweight encodePublication = new PublicationMessageFlyweight();\n    private final PublicationMessageFlyweight decodePublication = new PublicationMessageFlyweight();\n    private final NakFlyweight encodeNakHeader = new NakFlyweight();\n    private final NakFlyweight decodeNakHeader = new NakFlyweight();\n\n    @Test\n    void shouldWriteCorrectValuesForGenericHeaderFields()\n    {\n        encodeHeader.wrap(aBuff);\n\n        encodeHeader.version((short)1);\n        encodeHeader.flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS);\n        encodeHeader.headerType(HeaderFlyweight.HDR_TYPE_DATA);\n        encodeHeader.frameLength(8);\n\n        // little endian\n        assertEquals((byte)0x08, buffer.get(0));\n        assertEquals((byte)0x00, buffer.get(1));\n        assertEquals((byte)0x00, buffer.get(2));\n        assertEquals((byte)0x00, buffer.get(3));\n        assertEquals((byte)0x01, buffer.get(4));\n        assertEquals((byte)0xC0, buffer.get(5));\n        assertEquals(HeaderFlyweight.HDR_TYPE_DATA, buffer.get(6));\n        assertEquals((byte)0x00, buffer.get(7));\n    }\n\n    @Test\n    void shouldReadWhatIsWrittenToGenericHeaderFields()\n    {\n        encodeHeader.wrap(aBuff);\n\n        encodeHeader.version((short)1);\n        encodeHeader.flags((short)0);\n        encodeHeader.headerType(HeaderFlyweight.HDR_TYPE_DATA);\n        encodeHeader.frameLength(8);\n\n        decodeHeader.wrap(aBuff);\n        assertEquals(1, decodeHeader.version());\n        assertEquals(HeaderFlyweight.HDR_TYPE_DATA, decodeHeader.headerType());\n        assertEquals(8, decodeHeader.frameLength());\n    }\n\n    @Test\n    void shouldWriteAndReadMultipleFramesCorrectly()\n    {\n        encodeHeader.wrap(aBuff);\n\n        encodeHeader.version((short)1);\n        encodeHeader.flags((short)0);\n        encodeHeader.headerType(HeaderFlyweight.HDR_TYPE_DATA);\n        encodeHeader.frameLength(8);\n\n        encodeHeader.wrap(aBuff, 8, aBuff.capacity() - 8);\n        encodeHeader.version((short)2);\n        encodeHeader.flags((short)0x01);\n        encodeHeader.headerType(HeaderFlyweight.HDR_TYPE_SM);\n        encodeHeader.frameLength(8);\n\n        decodeHeader.wrap(aBuff);\n        assertEquals(1, decodeHeader.version());\n        assertEquals(0, decodeHeader.flags());\n        assertEquals(HeaderFlyweight.HDR_TYPE_DATA, decodeHeader.headerType());\n        assertEquals(8, decodeHeader.frameLength());\n\n        decodeHeader.wrap(aBuff, 8, aBuff.capacity() - 8);\n        assertEquals(2, decodeHeader.version());\n        assertEquals(0x01, decodeHeader.flags());\n        assertEquals(HeaderFlyweight.HDR_TYPE_SM, decodeHeader.headerType());\n        assertEquals(8, decodeHeader.frameLength());\n    }\n\n    @Test\n    void shouldReadAndWriteDataHeaderCorrectly()\n    {\n        encodeDataHeader.wrap(aBuff);\n\n        encodeDataHeader.version((short)1);\n        encodeDataHeader.flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS);\n        encodeDataHeader.headerType(HeaderFlyweight.HDR_TYPE_DATA);\n        encodeDataHeader.frameLength(DataHeaderFlyweight.HEADER_LENGTH);\n        encodeDataHeader.sessionId(0xdeadbeef);\n        encodeDataHeader.streamId(0x44332211);\n        encodeDataHeader.termId(0x99887766);\n\n        decodeDataHeader.wrap(aBuff);\n        assertEquals(1, decodeDataHeader.version());\n        assertEquals(DataHeaderFlyweight.BEGIN_AND_END_FLAGS, decodeDataHeader.flags());\n        assertEquals(HeaderFlyweight.HDR_TYPE_DATA, decodeDataHeader.headerType());\n        assertEquals(DataHeaderFlyweight.HEADER_LENGTH, decodeDataHeader.frameLength());\n        assertEquals(0xdeadbeef, decodeDataHeader.sessionId());\n        assertEquals(0x44332211, decodeDataHeader.streamId());\n        assertEquals(0x99887766, decodeDataHeader.termId());\n        assertEquals(DataHeaderFlyweight.HEADER_LENGTH, decodeDataHeader.dataOffset());\n    }\n\n    @Test\n    void shouldEncodeAndDecodeNakCorrectly()\n    {\n        encodeNakHeader.wrap(aBuff);\n        encodeNakHeader.version((short)1);\n        encodeNakHeader.flags((byte)0);\n        encodeNakHeader.headerType(HeaderFlyweight.HDR_TYPE_NAK);\n        encodeNakHeader.frameLength(NakFlyweight.HEADER_LENGTH);\n        encodeNakHeader.sessionId(0xdeadbeef);\n        encodeNakHeader.streamId(0x44332211);\n        encodeNakHeader.termId(0x99887766);\n        encodeNakHeader.termOffset(0x22334);\n        encodeNakHeader.length(512);\n\n        decodeNakHeader.wrap(aBuff);\n        assertEquals(1, decodeNakHeader.version());\n        assertEquals(0, decodeNakHeader.flags());\n        assertEquals(HeaderFlyweight.HDR_TYPE_NAK, decodeNakHeader.headerType());\n        assertEquals(NakFlyweight.HEADER_LENGTH, decodeNakHeader.frameLength());\n        assertEquals(0xdeadbeef, decodeNakHeader.sessionId());\n        assertEquals(0x44332211, decodeNakHeader.streamId());\n        assertEquals(0x99887766, decodeNakHeader.termId());\n        assertEquals(0x22334, decodeNakHeader.termOffset());\n        assertEquals(512, decodeNakHeader.length());\n    }\n\n    @Test\n    void shouldEncodeAndDecodeChannelsCorrectly()\n    {\n        encodePublication.wrap(aBuff, 0);\n\n        final String channel = \"aeron:udp?endpoint=localhost:4000\";\n        encodePublication.channel(channel);\n\n        decodePublication.wrap(aBuff, 0);\n\n        assertEquals(channel, decodePublication.channel());\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/FragmentAssemblerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.BitUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass FragmentAssemblerTest\n{\n    private static final int SESSION_ID = 777;\n    private static final int INITIAL_TERM_ID = 3;\n\n    private final FragmentHandler delegateFragmentHandler = mock(FragmentHandler.class);\n    private final Header header = spy(new Header(INITIAL_TERM_ID, LogBufferDescriptor.TERM_MIN_LENGTH));\n    private final FragmentAssembler assembler = new FragmentAssembler(delegateFragmentHandler);\n    private DataHeaderFlyweight headerFlyweight;\n\n    @BeforeEach\n    void setUp()\n    {\n        final UnsafeBuffer buffer = new UnsafeBuffer(new byte[64]);\n        headerFlyweight = new DataHeaderFlyweight();\n        headerFlyweight.wrap(buffer, 16, HEADER_LENGTH);\n        header.buffer(buffer);\n        header.offset(headerFlyweight.wrapAdjustment());\n\n        headerFlyweight.sessionId(SESSION_ID);\n    }\n\n    @Test\n    void shouldPassThroughUnfragmentedMessage()\n    {\n        headerFlyweight.flags(FrameDescriptor.UNFRAGMENTED);\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[128]);\n        final int offset = 8;\n        final int length = 32;\n\n        assembler.onFragment(srcBuffer, offset, length, header);\n\n        verify(delegateFragmentHandler, times(1)).onFragment(srcBuffer, offset, length, header);\n    }\n\n    @Test\n    void shouldAssembleTwoPartMessage()\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[1024 + (2 * HEADER_LENGTH)]);\n        final int length = 512;\n\n        int offset = HEADER_LENGTH;\n        headerFlyweight.flags(FrameDescriptor.BEGIN_FRAG_FLAG);\n        assembler.onFragment(srcBuffer, offset, length, header);\n        offset = BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT);\n        headerFlyweight.flags(FrameDescriptor.END_FRAG_FLAG);\n        assembler.onFragment(srcBuffer, offset, length, header);\n\n        final ArgumentCaptor<Header> headerArg = ArgumentCaptor.forClass(Header.class);\n\n        verify(delegateFragmentHandler, times(1)).onFragment(\n            any(), eq(0), eq(length * 2), headerArg.capture());\n\n        final Header capturedHeader = headerArg.getValue();\n        assertEquals(SESSION_ID, capturedHeader.sessionId());\n        assertEquals(FrameDescriptor.UNFRAGMENTED, capturedHeader.flags());\n    }\n\n    @Test\n    void shouldAssembleFourPartMessage()\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[1024 + (4 * HEADER_LENGTH)]);\n        final int length = 256;\n\n        int offset = HEADER_LENGTH;\n        headerFlyweight.flags(FrameDescriptor.BEGIN_FRAG_FLAG);\n        assembler.onFragment(srcBuffer, offset, length, header);\n        offset = BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT);\n        headerFlyweight.flags((short)0);\n        assembler.onFragment(srcBuffer, offset, length, header);\n        offset = BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT);\n        headerFlyweight.flags((short)0);\n        assembler.onFragment(srcBuffer, offset, length, header);\n        offset = BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT);\n        headerFlyweight.flags((short)(FrameDescriptor.END_FRAG_FLAG | 0x3));\n        assembler.onFragment(srcBuffer, offset, length, header);\n\n        final ArgumentCaptor<Header> headerArg = ArgumentCaptor.forClass(Header.class);\n\n        verify(delegateFragmentHandler, times(1)).onFragment(\n            any(), eq(0), eq(length * 4), headerArg.capture());\n\n        final Header capturedHeader = headerArg.getValue();\n        assertEquals(SESSION_ID, capturedHeader.sessionId());\n        assertEquals((byte)(FrameDescriptor.UNFRAGMENTED | 0x3), capturedHeader.flags());\n    }\n\n    @Test\n    void shouldFreeSessionBuffer()\n    {\n        when(header.flags())\n            .thenReturn(FrameDescriptor.BEGIN_FRAG_FLAG)\n            .thenReturn(FrameDescriptor.END_FRAG_FLAG);\n\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[1024]);\n        final int offset = 0;\n        final int length = srcBuffer.capacity() / 2;\n\n        srcBuffer.setMemory(0, length, (byte)65);\n        srcBuffer.setMemory(length, length, (byte)66);\n\n        assertFalse(assembler.freeSessionBuffer(SESSION_ID));\n\n        assembler.onFragment(srcBuffer, offset, length, header);\n        assembler.onFragment(srcBuffer, length, length, header);\n\n        assertTrue(assembler.freeSessionBuffer(SESSION_ID));\n        assertFalse(assembler.freeSessionBuffer(SESSION_ID));\n    }\n\n    @Test\n    void shouldDoNotingIfEndArrivesWithoutBegin()\n    {\n        when(header.flags()).thenReturn(FrameDescriptor.END_FRAG_FLAG);\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[1024]);\n        final int offset = 0;\n        final int length = srcBuffer.capacity() / 2;\n\n        assembler.onFragment(srcBuffer, offset, length, header);\n\n        verify(delegateFragmentHandler, never()).onFragment(any(), anyInt(), anyInt(), any());\n    }\n\n    @Test\n    void shouldDoNotingIfMidArrivesWithoutBegin()\n    {\n        when(header.flags())\n            .thenReturn((byte)0)\n            .thenReturn(FrameDescriptor.END_FRAG_FLAG);\n\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[1024]);\n        final int offset = 0;\n        final int length = srcBuffer.capacity() / 2;\n\n        assembler.onFragment(srcBuffer, offset, length, header);\n        assembler.onFragment(srcBuffer, offset, length, header);\n\n        verify(delegateFragmentHandler, never()).onFragment(any(), anyInt(), anyInt(), any());\n    }\n\n    @Test\n    void shouldSkipOverMessagesWithLoss()\n    {\n        when(header.flags())\n            .thenReturn(FrameDescriptor.BEGIN_FRAG_FLAG)\n            .thenReturn(FrameDescriptor.END_FRAG_FLAG)\n            .thenReturn(FrameDescriptor.UNFRAGMENTED);\n\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[2048]);\n        final int length = 256;\n\n        int offset = HEADER_LENGTH;\n        assembler.onFragment(srcBuffer, offset, length, header);\n        offset = BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT);\n        offset = BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT);\n        offset = BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT);\n        assembler.onFragment(srcBuffer, offset, length, header);\n        offset = BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT);\n        assembler.onFragment(srcBuffer, offset, length, header);\n\n        final ArgumentCaptor<Header> headerArg = ArgumentCaptor.forClass(Header.class);\n\n        verify(delegateFragmentHandler, times(1)).onFragment(\n            any(), eq(offset), eq(length), headerArg.capture());\n\n        final Header capturedHeader = headerArg.getValue();\n        assertEquals(SESSION_ID, capturedHeader.sessionId());\n        assertEquals(FrameDescriptor.UNFRAGMENTED, capturedHeader.flags());\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/ImageTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler.Action;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicLongPosition;\nimport org.agrona.concurrent.status.Position;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.AdditionalMatchers;\nimport org.mockito.InOrder;\nimport org.mockito.Mockito;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static java.nio.ByteBuffer.allocateDirect;\nimport static org.agrona.BitUtil.align;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.*;\n\nclass ImageTest\n{\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int POSITION_BITS_TO_SHIFT = LogBufferDescriptor.positionBitsToShift(TERM_BUFFER_LENGTH);\n    private static final byte[] DATA = new byte[36];\n\n    static\n    {\n        for (int i = 0; i < DATA.length; i++)\n        {\n            DATA[i] = (byte)i;\n        }\n    }\n\n    private static final long CORRELATION_ID = 0xC044E1AL;\n    private static final int SESSION_ID = 0x5E55101D;\n    private static final int STREAM_ID = 0xC400E;\n    private static final String SOURCE_IDENTITY = \"ipc\";\n    private static final int INITIAL_TERM_ID = 0xEE81D;\n    private static final int MESSAGE_LENGTH = HEADER_LENGTH + DATA.length;\n    private static final int ALIGNED_FRAME_LENGTH = align(MESSAGE_LENGTH, FrameDescriptor.FRAME_ALIGNMENT);\n\n    private final UnsafeBuffer rcvBuffer = new UnsafeBuffer(new byte[ALIGNED_FRAME_LENGTH]);\n    private final DataHeaderFlyweight dataHeader = new DataHeaderFlyweight();\n    private final FragmentHandler mockFragmentHandler = mock(FragmentHandler.class);\n    private final ControlledFragmentHandler mockControlledFragmentHandler = mock(ControlledFragmentHandler.class);\n    private final Position position = spy(new AtomicLongPosition());\n    private final LogBuffers logBuffers = mock(LogBuffers.class);\n    private final ErrorHandler errorHandler = mock(ErrorHandler.class);\n    private final Subscription subscription = mock(Subscription.class);\n\n    private final UnsafeBuffer[] termBuffers = new UnsafeBuffer[PARTITION_COUNT];\n\n    @BeforeEach\n    void setUp()\n    {\n        dataHeader.wrap(rcvBuffer);\n\n        for (int i = 0; i < PARTITION_COUNT; i++)\n        {\n            termBuffers[i] = new UnsafeBuffer(allocateDirect(TERM_BUFFER_LENGTH));\n        }\n\n        final UnsafeBuffer logMetaDataBuffer = new UnsafeBuffer(allocateDirect(LOG_META_DATA_LENGTH));\n\n        when(logBuffers.duplicateTermBuffers()).thenReturn(termBuffers);\n        when(logBuffers.termLength()).thenReturn(TERM_BUFFER_LENGTH);\n        when(logBuffers.metaDataBuffer()).thenReturn(logMetaDataBuffer);\n    }\n\n    @Test\n    void shouldHandleClosedImage()\n    {\n        final Image image = createImage();\n\n        image.close();\n\n        assertTrue(image.isClosed());\n        assertThat(image.poll(mockFragmentHandler, Integer.MAX_VALUE), is(0));\n        assertThat(image.position(), is(0L));\n    }\n\n    @Test\n    void shouldAllowValidPosition()\n    {\n        final Image image = createImage();\n        final long expectedPosition = TERM_BUFFER_LENGTH - 32;\n\n        position.setRelease(expectedPosition);\n        assertThat(image.position(), is(expectedPosition));\n\n        image.position(TERM_BUFFER_LENGTH);\n        assertThat(image.position(), is((long)TERM_BUFFER_LENGTH));\n    }\n\n    @Test\n    void shouldNotAdvancePastEndOfTerm()\n    {\n        final Image image = createImage();\n        final long expectedPosition = TERM_BUFFER_LENGTH - 32;\n\n        position.setRelease(expectedPosition);\n        assertThat(image.position(), is(expectedPosition));\n\n        assertThrows(IllegalArgumentException.class, () -> image.position(TERM_BUFFER_LENGTH + 32));\n    }\n\n    @Test\n    void shouldReportCorrectPositionOnReception()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n\n        final int messages = image.poll(mockFragmentHandler, Integer.MAX_VALUE);\n        assertThat(messages, is(1));\n\n        verify(mockFragmentHandler).onFragment(\n            any(UnsafeBuffer.class),\n            eq(HEADER_LENGTH),\n            eq(DATA.length),\n            any(Header.class));\n\n        final InOrder inOrder = Mockito.inOrder(position);\n        inOrder.verify(position).setRelease(initialPosition);\n        inOrder.verify(position).setRelease(initialPosition + ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldReportCorrectPositionOnReceptionWithNonZeroPositionInInitialTermId()\n    {\n        final int initialMessageIndex = 5;\n        final int initialTermOffset = offsetForFrame(initialMessageIndex);\n        final long initialPosition = computePosition(\n            INITIAL_TERM_ID, initialTermOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(initialMessageIndex));\n\n        final int messages = image.poll(mockFragmentHandler, Integer.MAX_VALUE);\n        assertThat(messages, is(1));\n\n        verify(mockFragmentHandler).onFragment(\n            any(UnsafeBuffer.class),\n            eq(initialTermOffset + HEADER_LENGTH),\n            eq(DATA.length),\n            any(Header.class));\n\n        final InOrder inOrder = Mockito.inOrder(position);\n        inOrder.verify(position).setRelease(initialPosition);\n        inOrder.verify(position).setRelease(initialPosition + ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldReportCorrectPositionOnReceptionWithNonZeroPositionInNonInitialTermId()\n    {\n        final int activeTermId = INITIAL_TERM_ID + 1;\n        final int initialMessageIndex = 5;\n        final int initialTermOffset = offsetForFrame(initialMessageIndex);\n        final long initialPosition =\n            computePosition(activeTermId, initialTermOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(activeTermId, offsetForFrame(initialMessageIndex));\n\n        final int messages = image.poll(mockFragmentHandler, Integer.MAX_VALUE);\n        assertThat(messages, is(1));\n\n        verify(mockFragmentHandler).onFragment(\n            any(UnsafeBuffer.class),\n            eq(initialTermOffset + HEADER_LENGTH),\n            eq(DATA.length),\n            any(Header.class));\n\n        final InOrder inOrder = Mockito.inOrder(position);\n        inOrder.verify(position).setRelease(initialPosition);\n        inOrder.verify(position).setRelease(initialPosition + ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldPollNoFragmentsToControlledFragmentHandler()\n    {\n        final Image image = createImage();\n        final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(0));\n        verify(position, never()).setRelease(anyLong());\n        verify(mockControlledFragmentHandler, never()).onFragment(\n            any(UnsafeBuffer.class), anyInt(), anyInt(), any(Header.class));\n    }\n\n    @Test\n    void shouldPollOneFragmentToControlledFragmentHandlerOnContinue()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.CONTINUE);\n\n        final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(1));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(initialPosition + ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldUpdatePositionOnRethrownExceptionInControlledPoll()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenThrow(new RuntimeException());\n\n        doThrow(new RuntimeException()).when(errorHandler).onError(any());\n\n        boolean thrown = false;\n        try\n        {\n            image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);\n        }\n        catch (final Exception ignore)\n        {\n            thrown = true;\n        }\n\n        assertTrue(thrown);\n        assertThat(image.position(), is(initialPosition + ALIGNED_FRAME_LENGTH));\n\n        verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n    }\n\n    @Test\n    void shouldUpdatePositionOnRethrownExceptionInPoll()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n\n        doThrow(new RuntimeException()).when(mockFragmentHandler)\n            .onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class));\n\n        doThrow(new RuntimeException()).when(errorHandler).onError(any());\n\n        boolean thrown = false;\n        try\n        {\n            image.poll(mockFragmentHandler, Integer.MAX_VALUE);\n        }\n        catch (final Exception ignore)\n        {\n            thrown = true;\n        }\n\n        assertTrue(thrown);\n        assertThat(image.position(), is(initialPosition + ALIGNED_FRAME_LENGTH));\n\n        verify(mockFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n    }\n\n    @Test\n    void shouldNotPollOneFragmentToControlledFragmentHandlerOnAbort()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.ABORT);\n\n        final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(0));\n        assertThat(image.position(), is(initialPosition));\n\n        verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n    }\n\n    @Test\n    void shouldPollOneFragmentToControlledFragmentHandlerOnBreak()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.BREAK);\n\n        final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(1));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(initialPosition + ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldPollFragmentsToControlledFragmentHandlerOnCommit()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.COMMIT);\n\n        final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(2));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(initialPosition + ALIGNED_FRAME_LENGTH);\n\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(ALIGNED_FRAME_LENGTH + HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(initialPosition + (ALIGNED_FRAME_LENGTH * 2L));\n    }\n\n    @Test\n    void shouldUpdatePositionToEndOfCommittedFragmentOnCommit()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(2));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.CONTINUE, Action.COMMIT, Action.CONTINUE);\n\n        final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(3));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);\n        // first fragment, continue\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n\n        // second fragment, commit\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class),\n            eq(ALIGNED_FRAME_LENGTH + HEADER_LENGTH),\n            eq(DATA.length),\n            any(Header.class));\n        inOrder.verify(position).setRelease(initialPosition + (ALIGNED_FRAME_LENGTH * 2L));\n\n        // third fragment, continue, but position is updated because last\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class),\n            eq(2 * ALIGNED_FRAME_LENGTH + HEADER_LENGTH),\n            eq(DATA.length),\n            any(Header.class));\n        inOrder.verify(position).setRelease(initialPosition + (ALIGNED_FRAME_LENGTH * 3L));\n    }\n\n    @Test\n    void shouldPollFragmentsToControlledFragmentHandlerOnContinue()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.CONTINUE);\n\n        final int fragmentsRead = image.controlledPoll(mockControlledFragmentHandler, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(2));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(ALIGNED_FRAME_LENGTH + HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(initialPosition + (ALIGNED_FRAME_LENGTH * 2L));\n    }\n\n    @Test\n    void shouldPollNoFragmentsToBoundedControlledFragmentHandlerWithMaxPositionBeforeInitialPosition()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        final long maxPosition = initialPosition - HEADER_LENGTH;\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.CONTINUE);\n\n        final int fragmentsRead = image.boundedControlledPoll(\n            mockControlledFragmentHandler, maxPosition, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(0));\n\n        assertThat(position.get(), is(initialPosition));\n        verify(mockControlledFragmentHandler, never()).onFragment(\n            any(UnsafeBuffer.class), anyInt(), anyInt(), any(Header.class));\n    }\n\n    @Test\n    void shouldPollFragmentsToBoundedControlledFragmentHandlerWithInitialOffsetNotZero()\n    {\n        final long initialPosition = computePosition(\n            INITIAL_TERM_ID, offsetForFrame(1), POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        final long maxPosition = initialPosition + ALIGNED_FRAME_LENGTH;\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(2));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.CONTINUE);\n\n        final int fragmentsRead = image.boundedControlledPoll(\n            mockControlledFragmentHandler, maxPosition, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(1));\n\n        assertThat(position.get(), is(maxPosition));\n        verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), anyInt(), anyInt(), any(Header.class));\n    }\n\n    @Test\n    void shouldPollFragmentsToBoundedControlledFragmentHandlerWithMaxPositionBeforeNextMessage()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        final long maxPosition = initialPosition + ALIGNED_FRAME_LENGTH;\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.CONTINUE);\n\n        final int fragmentsRead = image.boundedControlledPoll(\n            mockControlledFragmentHandler, maxPosition, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(1));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(initialPosition + ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldPollFragmentsToBoundedFragmentHandlerWithMaxPositionBeforeNextMessage()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        final long maxPosition = initialPosition + ALIGNED_FRAME_LENGTH;\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n\n        final int fragmentsRead = image.boundedPoll(mockFragmentHandler, maxPosition, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(1));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockFragmentHandler);\n        inOrder.verify(mockFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(initialPosition + ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldPollFragmentsToBoundedControlledFragmentHandlerWithMaxPositionAfterEndOfTerm()\n    {\n        final int initialOffset = TERM_BUFFER_LENGTH - (ALIGNED_FRAME_LENGTH * 2);\n        final long initialPosition = computePosition(\n            INITIAL_TERM_ID, initialOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        final long maxPosition = initialPosition + TERM_BUFFER_LENGTH;\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, initialOffset);\n        insertPaddingFrame(INITIAL_TERM_ID, initialOffset + ALIGNED_FRAME_LENGTH);\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.CONTINUE);\n\n        final int fragmentsRead = image.boundedControlledPoll(\n            mockControlledFragmentHandler, maxPosition, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(1));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(initialOffset + HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(TERM_BUFFER_LENGTH);\n    }\n\n    @Test\n    void shouldPollFragmentsToBoundedControlledFragmentHandlerWithMaxPositionAboveIntMaxValue()\n    {\n        final int initialOffset = TERM_BUFFER_LENGTH - (ALIGNED_FRAME_LENGTH * 2);\n        final long initialPosition = computePosition(\n            INITIAL_TERM_ID, initialOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        final long maxPosition = (long)Integer.MAX_VALUE + 1000;\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, initialOffset);\n        insertPaddingFrame(INITIAL_TERM_ID, initialOffset + ALIGNED_FRAME_LENGTH);\n\n        when(mockControlledFragmentHandler.onFragment(any(DirectBuffer.class), anyInt(), anyInt(), any(Header.class)))\n            .thenReturn(Action.CONTINUE);\n\n        final int fragmentsRead = image.boundedControlledPoll(\n            mockControlledFragmentHandler, maxPosition, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(1));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockControlledFragmentHandler);\n        inOrder.verify(mockControlledFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(initialOffset + HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(TERM_BUFFER_LENGTH);\n    }\n\n    @Test\n    void shouldPollFragmentsToBoundedFragmentHandlerWithMaxPositionAboveIntMaxValue()\n    {\n        final int initialOffset = TERM_BUFFER_LENGTH - (ALIGNED_FRAME_LENGTH * 2);\n        final long initialPosition = computePosition(\n            INITIAL_TERM_ID, initialOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        final long maxPosition = (long)Integer.MAX_VALUE + 1000;\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, initialOffset);\n        insertPaddingFrame(INITIAL_TERM_ID, initialOffset + ALIGNED_FRAME_LENGTH);\n\n        final int fragmentsRead = image.boundedPoll(\n            mockFragmentHandler, maxPosition, Integer.MAX_VALUE);\n\n        assertThat(fragmentsRead, is(1));\n\n        final InOrder inOrder = Mockito.inOrder(position, mockFragmentHandler);\n        inOrder.verify(mockFragmentHandler).onFragment(\n            any(UnsafeBuffer.class), eq(initialOffset + HEADER_LENGTH), eq(DATA.length), any(Header.class));\n        inOrder.verify(position).setRelease(TERM_BUFFER_LENGTH);\n    }\n\n    @Test\n    void shouldRejectFragment()\n    {\n        final int initialOffset = TERM_BUFFER_LENGTH - (ALIGNED_FRAME_LENGTH * 2);\n        final long initialPosition = computePosition(\n            INITIAL_TERM_ID, initialOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, initialOffset);\n        insertPaddingFrame(INITIAL_TERM_ID, initialOffset + ALIGNED_FRAME_LENGTH);\n\n        assertEquals(initialPosition, image.position());\n\n        final String reason = \"this is frame is to be rejected\";\n        image.reject(reason);\n\n        verify(subscription).rejectImage(image.correlationId(), image.position(), reason);\n    }\n\n    @Test\n    void shouldExitPollIfImageIsClosed()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(2));\n\n        final int messages = image.poll((buffer, offset, length, header) -> image.close(), Integer.MAX_VALUE);\n        assertThat(messages, is(1));\n        assertThat(image.isClosed(), is(true));\n\n        final InOrder inOrder = Mockito.inOrder(position);\n        inOrder.verify(position, times(2)).get();\n        inOrder.verify(position).getVolatile();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldExitBoundedPollIfImageIsClosed()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(2));\n\n        final int messages = image.boundedPoll(\n            (buffer, offset, length, header) -> image.close(), Long.MAX_VALUE, Integer.MAX_VALUE);\n        assertThat(messages, is(1));\n        assertThat(image.isClosed(), is(true));\n\n        final InOrder inOrder = Mockito.inOrder(position);\n        inOrder.verify(position, times(2)).get();\n        inOrder.verify(position).getVolatile();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldExitControlledPollIfImageIsClosed()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(2));\n\n        final int messages = image.controlledPoll((buffer, offset, length, header) ->\n        {\n            image.close();\n            return Action.CONTINUE;\n        }, Integer.MAX_VALUE);\n        assertThat(messages, is(1));\n        assertThat(image.isClosed(), is(true));\n\n        final InOrder inOrder = Mockito.inOrder(position);\n        inOrder.verify(position, times(2)).get();\n        inOrder.verify(position).getVolatile();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldExitBoundedControlledPollIfImageIsClosed()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(2));\n\n        final int messages = image.boundedControlledPoll(\n            (buffer, offset, length, header) ->\n            {\n                image.close();\n                return Action.CONTINUE;\n            },\n            Long.MAX_VALUE,\n            Integer.MAX_VALUE);\n        assertThat(messages, is(1));\n        assertThat(image.isClosed(), is(true));\n\n        final InOrder inOrder = Mockito.inOrder(position);\n        inOrder.verify(position, times(2)).get();\n        inOrder.verify(position).getVolatile();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldExitControlledPeekIfImageIsClosed()\n    {\n        final long initialPosition = computePosition(INITIAL_TERM_ID, 0, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n        position.setRelease(initialPosition);\n        final Image image = createImage();\n\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(0));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(1));\n        insertDataFrame(INITIAL_TERM_ID, offsetForFrame(2));\n\n        final long resultingPosition = image.controlledPeek(\n            initialPosition,\n            (buffer, offset, length, header) ->\n            {\n                image.close();\n                return Action.CONTINUE;\n            },\n            Long.MAX_VALUE);\n        assertThat(resultingPosition, is(initialPosition + offsetForFrame(1)));\n        assertThat(image.isClosed(), is(true));\n\n        verify(position).setRelease(initialPosition);\n        verify(position, never()).setRelease(AdditionalMatchers.not(eq(initialPosition)));\n    }\n\n    private Image createImage()\n    {\n        return new Image(subscription, SESSION_ID, position, logBuffers, errorHandler, SOURCE_IDENTITY, CORRELATION_ID);\n    }\n\n    private void insertDataFrame(final int activeTermId, final int termOffset)\n    {\n        dataHeader\n            .termId(INITIAL_TERM_ID)\n            .streamId(STREAM_ID)\n            .sessionId(SESSION_ID)\n            .termOffset(termOffset)\n            .frameLength(DATA.length + HEADER_LENGTH)\n            .headerType(HeaderFlyweight.HDR_TYPE_DATA)\n            .flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS)\n            .version(HeaderFlyweight.CURRENT_VERSION);\n\n        rcvBuffer.putBytes(dataHeader.dataOffset(), DATA);\n\n        final int activeIndex = indexByTerm(INITIAL_TERM_ID, activeTermId);\n        TermRebuilder.insert(termBuffers[activeIndex], termOffset, rcvBuffer, ALIGNED_FRAME_LENGTH);\n    }\n\n    private void insertPaddingFrame(final int activeTermId, final int termOffset)\n    {\n        dataHeader\n            .termId(INITIAL_TERM_ID)\n            .streamId(STREAM_ID)\n            .sessionId(SESSION_ID)\n            .frameLength(TERM_BUFFER_LENGTH - termOffset)\n            .headerType(HeaderFlyweight.HDR_TYPE_PAD)\n            .flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS)\n            .version(HeaderFlyweight.CURRENT_VERSION);\n\n        final int activeIndex = indexByTerm(INITIAL_TERM_ID, activeTermId);\n        TermRebuilder.insert(termBuffers[activeIndex], termOffset, rcvBuffer, TERM_BUFFER_LENGTH - termOffset);\n    }\n\n    private static int offsetForFrame(final int index)\n    {\n        return index * ALIGNED_FRAME_LENGTH;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/LogBuffersTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static org.hamcrest.CoreMatchers.*;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass LogBuffersTest\n{\n    @ParameterizedTest\n    @ValueSource(ints = { -100, 0, TERM_MIN_LENGTH >> 1, TERM_MAX_LENGTH + 1, TERM_MAX_LENGTH - 1 })\n    void throwsIllegalStateExceptionIfTermLengthIsInvalid(final int termLength, @TempDir final Path dir)\n        throws IOException\n    {\n        final Path logFile = dir.resolve(\"test.log\");\n        assertNotNull(Files.createFile(logFile));\n        final byte[] contents = new byte[LOG_META_DATA_LENGTH];\n        final UnsafeBuffer buffer = new UnsafeBuffer(contents);\n        termLength(buffer, termLength);\n        assertNotNull(Files.write(logFile, contents));\n        assertEquals(contents.length, Files.size(logFile));\n\n        final IllegalStateException exception = assertThrowsExactly(\n            IllegalStateException.class, () -> new LogBuffers(logFile.toAbsolutePath().toString()));\n        assertThat(exception.getMessage(), allOf(startsWith(\"Term length\"), endsWith(\"length=\" + termLength)));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -100, 0, PAGE_MIN_SIZE >> 1, PAGE_MAX_SIZE + 1, PAGE_MAX_SIZE - 1 })\n    void throwsIllegalStateExceptionIfPageSizeIsInvalid(final int pageSize, @TempDir final Path dir)\n        throws IOException\n    {\n        final Path logFile = dir.resolve(\"test.log\");\n        assertNotNull(Files.createFile(logFile));\n        final byte[] contents = new byte[LOG_META_DATA_LENGTH];\n        final UnsafeBuffer buffer = new UnsafeBuffer(contents);\n        termLength(buffer, TERM_MIN_LENGTH);\n        pageSize(buffer, pageSize);\n        assertNotNull(Files.write(logFile, contents));\n        assertEquals(contents.length, Files.size(logFile));\n\n        final IllegalStateException exception = assertThrowsExactly(\n            IllegalStateException.class, () -> new LogBuffers(logFile.toAbsolutePath().toString()));\n        assertThat(exception.getMessage(), allOf(startsWith(\"Page size\"), endsWith(\"page size=\" + pageSize)));\n    }\n\n    @Test\n    void throwsIllegalStateExceptionIfLogFileSizeIsLessThanLogMetaDataLength(@TempDir final Path dir)\n        throws IOException\n    {\n        final Path logFile = dir.resolve(\"test.log\");\n        assertNotNull(Files.createFile(logFile));\n        final int fileLength = LOG_META_DATA_LENGTH - 5;\n        final byte[] contents = new byte[fileLength];\n        final UnsafeBuffer buffer = new UnsafeBuffer(contents);\n        termLength(buffer, TERM_MIN_LENGTH);\n        pageSize(buffer, PAGE_MIN_SIZE);\n        assertNotNull(Files.write(logFile, contents));\n        assertEquals(contents.length, Files.size(logFile));\n\n        final IllegalStateException exception = assertThrowsExactly(\n            IllegalStateException.class, () -> new LogBuffers(logFile.toAbsolutePath().toString()));\n        assertEquals(\"Log file length less than min length of \" + LOG_META_DATA_LENGTH + \": length=\" + fileLength,\n            exception.getMessage());\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/PublicationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.ReadablePosition;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.stubbing.Answer;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.Publication.*;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static java.nio.ByteBuffer.allocate;\nimport static java.nio.ByteBuffer.allocateDirect;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.align;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass PublicationTest\n{\n    private static final String CHANNEL = \"aeron:udp?endpoint=localhost:40124\";\n    private static final int STREAM_ID_1 = 1002;\n    private static final int SESSION_ID_1 = 13;\n    private static final int TERM_ID_1 = 1;\n    private static final int CORRELATION_ID = 2000;\n    private static final int SEND_BUFFER_CAPACITY = 1024;\n    private static final int PARTITION_INDEX = 0;\n    private static final int MTU_LENGTH = 4096;\n    private static final int PAGE_SIZE = 4 * 1024;\n    private static final int TERM_LENGTH = TERM_MIN_LENGTH * 4;\n    private static final int MAX_PAYLOAD_SIZE = MTU_LENGTH - HEADER_LENGTH;\n    private static final int MAX_MESSAGE_SIZE = FrameDescriptor.computeMaxMessageLength(TERM_LENGTH);\n    private static final int TOTAL_ALIGNED_MAX_MESSAGE_SIZE = (MAX_MESSAGE_SIZE / MAX_PAYLOAD_SIZE) * MTU_LENGTH +\n        align(MAX_MESSAGE_SIZE % MAX_PAYLOAD_SIZE + HEADER_LENGTH, FRAME_ALIGNMENT);\n    private static final int POSITION_BITS_TO_SHIFT = LogBufferDescriptor.positionBitsToShift(TERM_LENGTH);\n    private static final int DEFAULT_FRAME_TYPE = DataHeaderFlyweight.HDR_TYPE_RTTM;\n    private static final int SESSION_ID = 42;\n    private static final int STREAM_ID = 111;\n    private final UnsafeBuffer logMetaDataBuffer = spy(new UnsafeBuffer(allocate(LOG_META_DATA_LENGTH)));\n    private final UnsafeBuffer[] termBuffers = new UnsafeBuffer[PARTITION_COUNT];\n    private final ClientConductor conductor = mock(ClientConductor.class);\n    private final LogBuffers logBuffers = mock(LogBuffers.class);\n    private final ReadablePosition publicationLimit = mock(ReadablePosition.class);\n    private ConcurrentPublication publication;\n\n    @BeforeEach\n    void setUp()\n    {\n        when(publicationLimit.getVolatile()).thenReturn(2L * SEND_BUFFER_CAPACITY);\n        when(logBuffers.duplicateTermBuffers()).thenReturn(termBuffers);\n        when(logBuffers.termLength()).thenReturn(TERM_LENGTH);\n        when(logBuffers.metaDataBuffer()).thenReturn(logMetaDataBuffer);\n\n        final UnsafeBuffer defaultHeader = DataHeaderFlyweight.createDefaultHeader(SESSION_ID, STREAM_ID, TERM_ID_1);\n        defaultHeader.putShort(DataHeaderFlyweight.TYPE_FIELD_OFFSET, (short)DEFAULT_FRAME_TYPE, LITTLE_ENDIAN);\n        storeDefaultFrameHeader(logMetaDataBuffer, defaultHeader);\n\n        initialTermId(logMetaDataBuffer, TERM_ID_1);\n        mtuLength(logMetaDataBuffer, MTU_LENGTH);\n        termLength(logMetaDataBuffer, TERM_LENGTH);\n        pageSize(logMetaDataBuffer, PAGE_SIZE);\n        isConnected(logMetaDataBuffer, false);\n\n        for (int i = 0; i < PARTITION_COUNT; i++)\n        {\n            termBuffers[i] = new UnsafeBuffer(allocateDirect(TERM_LENGTH));\n        }\n\n        publication = new ConcurrentPublication(\n            conductor,\n            CHANNEL,\n            STREAM_ID_1,\n            SESSION_ID_1,\n            publicationLimit,\n            ChannelEndpointStatus.NO_ID_ALLOCATED,\n            logBuffers,\n            CORRELATION_ID,\n            CORRELATION_ID);\n\n        initialiseTailWithTermId(logMetaDataBuffer, PARTITION_INDEX, TERM_ID_1);\n\n        doAnswer(\n            (invocation) ->\n            {\n                publication.internalClose();\n                return null;\n            }).when(conductor).removePublication(publication);\n    }\n\n    @Test\n    void shouldEnsureThePublicationIsOpenBeforeReadingPosition()\n    {\n        publication.close();\n        assertEquals(CLOSED, publication.position());\n\n        verify(conductor).removePublication(publication);\n    }\n\n    @Test\n    void shouldReportThatPublicationHasNotBeenConnectedYet()\n    {\n        when(publicationLimit.getVolatile()).thenReturn(0L);\n        isConnected(logMetaDataBuffer, false);\n        assertFalse(publication.isConnected());\n    }\n\n    @Test\n    void shouldReportThatPublicationHasBeenConnectedYet()\n    {\n        isConnected(logMetaDataBuffer, true);\n        assertTrue(publication.isConnected());\n    }\n\n    @Test\n    void shouldReportInitialPosition()\n    {\n        assertEquals(0L, publication.position());\n    }\n\n    @Test\n    void shouldReportMaxMessageLength()\n    {\n        assertEquals(FrameDescriptor.computeMaxMessageLength(TERM_LENGTH), publication.maxMessageLength());\n    }\n\n    @Test\n    void shouldRemovePublicationOnClose()\n    {\n        publication.close();\n\n        verify(conductor).removePublication(publication);\n    }\n\n    @Test\n    void shouldReturnErrorMessages()\n    {\n        assertEquals(\"NOT_CONNECTED\", errorString(-1L));\n        assertEquals(\"BACK_PRESSURED\", errorString(-2L));\n        assertEquals(\"ADMIN_ACTION\", errorString(-3L));\n        assertEquals(\"CLOSED\", errorString(-4L));\n        assertEquals(\"MAX_POSITION_EXCEEDED\", errorString(-5L));\n        assertEquals(\"NONE\", errorString(0L));\n        assertEquals(\"NONE\", errorString(1L));\n        assertEquals(\"UNKNOWN\", errorString(-6L));\n        assertEquals(\"UNKNOWN\", errorString(Long.MIN_VALUE));\n    }\n\n    abstract class BaseTests\n    {\n        abstract long invoke(int length, ReservedValueSupplier reservedValueSupplier);\n\n        abstract void onError(UnsafeBuffer termBuffer, int termOffset, int length);\n\n        abstract void onSuccess(UnsafeBuffer termBuffer, int termOffset, int length);\n\n        @Test\n        void returnsClosedIfPublicationWasClosed()\n        {\n            final int length = 6;\n            final int termOffset = 0;\n            publication.close();\n\n            assertEquals(CLOSED, invoke(length, null));\n\n            onError(termBuffers[PARTITION_INDEX], termOffset, length);\n            assertFrameType(PARTITION_INDEX, termOffset, FrameDescriptor.PADDING_FRAME_TYPE);\n            assertFrameLength(PARTITION_INDEX, termOffset, 0);\n        }\n\n        @Test\n        void returnsAdminActionIfTermCountAndTermIdDoNotMatch()\n        {\n            final int termCount = 9;\n            final int termOffset = 0;\n            final int length = 10;\n            assertEquals(indexByTermCount(termCount), PARTITION_INDEX);\n            initialiseTailWithTermId(logMetaDataBuffer, PARTITION_INDEX, TERM_ID_1 + 5);\n\n            assertEquals(ADMIN_ACTION, invoke(length, null));\n\n            onError(termBuffers[PARTITION_INDEX], termOffset, length);\n            assertFrameType(PARTITION_INDEX, termOffset, FrameDescriptor.PADDING_FRAME_TYPE);\n            assertFrameLength(PARTITION_INDEX, termOffset, 0);\n        }\n\n        @Test\n        void returnsMaxPositionExceededIfPublicationLimitReached()\n        {\n            final int partitionIndex = 1;\n            final int termOffset = TERM_LENGTH - 140;\n            final int length = 100;\n            when(publicationLimit.getVolatile()).thenReturn(16L);\n            rawTail(logMetaDataBuffer, partitionIndex, packTail(Integer.MIN_VALUE, termOffset));\n            activeTermCount(logMetaDataBuffer, Integer.MAX_VALUE);\n            isConnected(logMetaDataBuffer, true);\n\n            assertEquals(MAX_POSITION_EXCEEDED, invoke(length, null));\n\n            onError(termBuffers[partitionIndex], termOffset, length);\n            assertFrameType(partitionIndex, termOffset, FrameDescriptor.PADDING_FRAME_TYPE);\n            assertFrameLength(partitionIndex, termOffset, 0);\n        }\n\n        @Test\n        void returnsBackPressuredIfPublicationLimitReached()\n        {\n            final int partitionIndex = 1;\n            final int termOffset = 64;\n            final int length = 11;\n            when(publicationLimit.getVolatile()).thenReturn(16L);\n            rawTail(logMetaDataBuffer, partitionIndex, packTail(TERM_ID_1 + 1, termOffset));\n            activeTermCount(logMetaDataBuffer, 1);\n            isConnected(logMetaDataBuffer, true);\n\n            assertEquals(BACK_PRESSURED, invoke(length, (termBuffer, termOffset1, frameLength) -> Long.MAX_VALUE));\n\n            final UnsafeBuffer termBuffer = termBuffers[partitionIndex];\n            onError(termBuffer, termOffset, length);\n            assertFrameType(partitionIndex, termOffset, FrameDescriptor.PADDING_FRAME_TYPE);\n            assertFrameLength(partitionIndex, termOffset, 0);\n            assertEquals(0, DataHeaderFlyweight.reservedValue(termBuffer, termOffset));\n        }\n\n        @Test\n        void returnsNotConnectedIfPublicationLimitReachedAndNotConnected()\n        {\n            final int partitionIndex = 1;\n            final int termOffset = 0;\n            final int length = 10;\n            when(publicationLimit.getVolatile()).thenReturn(16L);\n            initialiseTailWithTermId(logMetaDataBuffer, partitionIndex, TERM_ID_1 + 1);\n            activeTermCount(logMetaDataBuffer, 1);\n\n            assertEquals(NOT_CONNECTED, invoke(length, null));\n\n            onError(termBuffers[partitionIndex], termOffset, length);\n            assertFrameType(partitionIndex, termOffset, FrameDescriptor.PADDING_FRAME_TYPE);\n            assertFrameLength(partitionIndex, termOffset, 0);\n        }\n\n        @Test\n        void returnsAdminActionIfTermWasRotated()\n        {\n            final int termOffset = TERM_LENGTH - 64;\n            final int length = 60;\n            when(publicationLimit.getVolatile()).thenReturn(1L + Integer.MAX_VALUE);\n            rawTailVolatile(logMetaDataBuffer, 0, packTail(TERM_ID_1, termOffset));\n            rawTailVolatile(logMetaDataBuffer, 1, packTail(TERM_ID_1 + 1 - PARTITION_COUNT, 555));\n            rawTailVolatile(logMetaDataBuffer, 2, packTail(STREAM_ID, 777));\n\n            assertEquals(ADMIN_ACTION, invoke(length, null));\n\n            onError(termBuffers[0], termOffset, length);\n            assertFrameType(0, termOffset, FrameDescriptor.PADDING_FRAME_TYPE);\n            assertFrameLength(0, termOffset, 64);\n            assertEquals(packTail(TERM_ID_1, termOffset + 96), rawTail(logMetaDataBuffer, 0));\n            assertEquals(packTail(TERM_ID_1 + 1, 0), rawTail(logMetaDataBuffer, 1));\n            assertEquals(packTail(STREAM_ID, 777), rawTail(logMetaDataBuffer, 2));\n        }\n\n        @Test\n        void returnsMaxPositionExceededIfThereIsNotEnoughSpaceLeft()\n        {\n            final int partitionIndex = 1;\n            final int termOffset = TERM_LENGTH - 128;\n            final int length = 96;\n            when(publicationLimit.getVolatile()).thenReturn(1L + Integer.MAX_VALUE);\n            rawTail(logMetaDataBuffer, partitionIndex, packTail(Integer.MIN_VALUE, termOffset));\n            rawTail(logMetaDataBuffer, 0, packTail(55, 55));\n            rawTail(logMetaDataBuffer, 2, packTail(222, 222));\n            activeTermCount(logMetaDataBuffer, Integer.MAX_VALUE);\n            isConnected(logMetaDataBuffer, true);\n\n            assertEquals(MAX_POSITION_EXCEEDED, invoke(length, null));\n\n            onError(termBuffers[partitionIndex], termOffset, length);\n            assertFrameType(partitionIndex, termOffset, FrameDescriptor.PADDING_FRAME_TYPE);\n            assertFrameLength(partitionIndex, termOffset, 0);\n            assertEquals(packTail(Integer.MIN_VALUE, termOffset), rawTail(logMetaDataBuffer, partitionIndex));\n            assertEquals(packTail(55, 55), rawTail(logMetaDataBuffer, 0));\n            assertEquals(packTail(222, 222), rawTail(logMetaDataBuffer, 2));\n        }\n\n        void testPositionUponSuccess(\n            final int length,\n            final int termId,\n            final int termCount,\n            final long tailAfterUpdate,\n            final long expectedPosition)\n        {\n            when(publicationLimit.getVolatile()).thenReturn(Long.MAX_VALUE);\n            isConnected(logMetaDataBuffer, true);\n            activeTermCount(logMetaDataBuffer, termCount);\n            final int partitionIndex = indexByTermCount(termCount);\n            final long rawTail = packTail(termId, 192);\n            rawTail(logMetaDataBuffer, partitionIndex, rawTail);\n            doAnswer((Answer<Long>)invocation -> tailAfterUpdate)\n                .when(logMetaDataBuffer)\n                .getAndAddLong(anyInt(), anyLong());\n\n            final long position = invoke(length, ((termBuffer, termOffset, frameLength) -> termOffset ^ frameLength));\n\n            assertEquals(expectedPosition, position);\n            final UnsafeBuffer buffer = termBuffers[partitionIndex];\n            final int termOffset = termOffset(tailAfterUpdate);\n            assertFrameType(partitionIndex, termOffset, DEFAULT_FRAME_TYPE);\n            assertEquals(SESSION_ID, FrameDescriptor.frameSessionId(buffer, termOffset));\n            assertEquals(STREAM_ID, DataHeaderFlyweight.streamId(buffer, termOffset));\n            assertEquals(termId(tailAfterUpdate), DataHeaderFlyweight.termId(buffer, termOffset));\n            onSuccess(buffer, termOffset, length);\n        }\n    }\n\n    @Nested\n    class TryClaim extends BaseTests\n    {\n        private final BufferClaim bufferClaim = new BufferClaim();\n\n        long invoke(final int length, final ReservedValueSupplier reservedValueSupplier)\n        {\n            return publication.tryClaim(length, bufferClaim);\n        }\n\n        void onError(final UnsafeBuffer termBuffer, final int termOffset, final int length)\n        {\n            final MutableDirectBuffer buffer = bufferClaim.buffer();\n            assertEquals(0, buffer.capacity());\n        }\n\n        void onSuccess(final UnsafeBuffer termBuffer, final int termOffset, final int length)\n        {\n            assertEquals(-(length + HEADER_LENGTH), DataHeaderFlyweight.fragmentLength(termBuffer, termOffset));\n            assertEquals(0, DataHeaderFlyweight.reservedValue(termBuffer, termOffset));\n            assertEquals(length, bufferClaim.length());\n        }\n\n        @ParameterizedTest\n        @MethodSource(\"io.aeron.PublicationTest#tryClaimPositions\")\n        void tryClaimShouldReturnPositionAtWhichTheClaimedSpaceEnds(\n            final int length,\n            final int termId,\n            final int termCount,\n            final long tailAfterUpdate,\n            final long expectedPosition)\n        {\n            testPositionUponSuccess(\n                length,\n                termId,\n                termCount,\n                tailAfterUpdate,\n                expectedPosition);\n        }\n    }\n\n    abstract class OfferBase extends BaseTests\n    {\n        abstract UnsafeBuffer buffer(int processedBytes);\n\n        void onError(final UnsafeBuffer termBuffer, final int termOffset, final int length)\n        {\n            int offset = termOffset + HEADER_LENGTH;\n            for (int i = 0, capacity = termBuffer.capacity(); offset < capacity && i < length; i++, offset++)\n            {\n                assertEquals(0, termBuffer.getByte(offset));\n            }\n        }\n\n        void onSuccess(final UnsafeBuffer termBuffer, final int termOffset, final int length)\n        {\n            int index = 0, processedBytes = 0;\n            int offset = termOffset;\n            UnsafeBuffer dataBuffer = buffer(0);\n            while (processedBytes < length)\n            {\n                final int frameLength = FrameDescriptor.frameLength(termBuffer, offset);\n                if (frameLength <= 0)\n                {\n                    break;\n                }\n\n                final int chunkLength = frameLength - HEADER_LENGTH;\n                final byte frameFlags = FrameDescriptor.frameFlags(termBuffer, offset);\n\n                if (0 == processedBytes)\n                {\n                    assertEquals(FrameDescriptor.BEGIN_FRAG_FLAG, (frameFlags & FrameDescriptor.BEGIN_FRAG_FLAG));\n                }\n\n                if (length == processedBytes + chunkLength)\n                {\n                    assertEquals(FrameDescriptor.END_FRAG_FLAG, (frameFlags & FrameDescriptor.END_FRAG_FLAG));\n                }\n\n                assertEquals(offset ^ frameLength, DataHeaderFlyweight.reservedValue(termBuffer, offset));\n                offset += HEADER_LENGTH;\n\n                for (int i = 0; i < chunkLength; i++)\n                {\n                    assertEquals(dataBuffer.getByte(index++), termBuffer.getByte(offset++));\n                    final UnsafeBuffer nextBuffer = buffer(++processedBytes);\n                    if (nextBuffer != dataBuffer)\n                    {\n                        dataBuffer = nextBuffer;\n                        index = 0;\n                    }\n                }\n            }\n            assertEquals(length, processedBytes);\n        }\n\n        @ParameterizedTest\n        @MethodSource(\"io.aeron.PublicationTest#offerPositions\")\n        void returnsPositionAfterDataIsCopied(\n            final int length,\n            final int termId,\n            final int termCount,\n            final long tailAfterUpdate,\n            final long expectedPosition)\n        {\n            testPositionUponSuccess(\n                length,\n                termId,\n                termCount,\n                tailAfterUpdate,\n                expectedPosition);\n        }\n    }\n\n    @Nested\n    class Offer extends OfferBase\n    {\n        private UnsafeBuffer sendBuffer;\n\n        @BeforeEach\n        void before()\n        {\n            final byte[] bytes = new byte[MAX_MESSAGE_SIZE];\n            ThreadLocalRandom.current().nextBytes(bytes);\n            sendBuffer = new UnsafeBuffer(bytes);\n        }\n\n        UnsafeBuffer buffer(final int length)\n        {\n            return sendBuffer;\n        }\n\n        long invoke(final int length, final ReservedValueSupplier reservedValueSupplier)\n        {\n            return publication.offer(sendBuffer, 0, length, reservedValueSupplier);\n        }\n\n        @Test\n        void offerWithAnOffset()\n        {\n            final int partitionIndex = 2;\n            final int termId = TERM_ID_1 + 2;\n            final int termOffset = 978;\n            final int offset = 11;\n            final int length = 23;\n            when(publicationLimit.getVolatile()).thenReturn(Long.MAX_VALUE);\n            isConnected(logMetaDataBuffer, true);\n            rawTail(logMetaDataBuffer, partitionIndex, packTail(termId, termOffset));\n            activeTermCount(logMetaDataBuffer, 2);\n\n            final long position = publication.offer(\n                sendBuffer, offset, length, (termBuffer, frameOffset, frameLength) -> Long.MIN_VALUE);\n\n            assertEquals(525330, position);\n            final UnsafeBuffer termBuffer = termBuffers[partitionIndex];\n            assertEquals(Long.MIN_VALUE, DataHeaderFlyweight.reservedValue(termBuffer, termOffset));\n            for (int i = 0; i < length; i++)\n            {\n                assertEquals(sendBuffer.getByte(offset + i), termBuffer.getByte(termOffset + HEADER_LENGTH + i));\n            }\n        }\n    }\n\n    @Nested\n    class OfferWithTwoBuffers extends OfferBase\n    {\n        private UnsafeBuffer buffer1;\n        private UnsafeBuffer buffer2;\n\n        @BeforeEach\n        void before()\n        {\n        }\n\n        UnsafeBuffer buffer(final int processedBytes)\n        {\n            return processedBytes < buffer1.capacity() ? buffer1 : buffer2;\n        }\n\n        long invoke(final int length, final ReservedValueSupplier reservedValueSupplier)\n        {\n            final byte[] bytes = new byte[length];\n            ThreadLocalRandom.current().nextBytes(bytes);\n            final int chunk1Length = (int)(length * 0.25);\n            final int chunk2Length = length - chunk1Length;\n            buffer1 = new UnsafeBuffer(bytes, 0, chunk1Length);\n            buffer2 = new UnsafeBuffer(bytes, chunk1Length, chunk2Length);\n\n            return publication.offer(buffer1, 0, chunk1Length, buffer2, 0, chunk2Length, reservedValueSupplier);\n        }\n\n        @Test\n        void offerWithOffsets()\n        {\n            final int partitionIndex = 1;\n            final int termId = TERM_ID_1 + 1;\n            final int termOffset = 64;\n            when(publicationLimit.getVolatile()).thenReturn(Long.MAX_VALUE);\n            isConnected(logMetaDataBuffer, true);\n            rawTail(logMetaDataBuffer, partitionIndex, packTail(termId, termOffset));\n            activeTermCount(logMetaDataBuffer, 1);\n\n            final byte[] bytes = new byte[16];\n            ThreadLocalRandom.current().nextBytes(bytes);\n            final UnsafeBuffer buffer1 = new UnsafeBuffer(bytes, 0, 8);\n            final UnsafeBuffer buffer2 = new UnsafeBuffer(bytes, 8, 8);\n\n            final int lengthOne = 5;\n            final int offsetOne = 2;\n            final int lengthTwo = 3;\n            final int offsetTwo = 4;\n            final long position = publication.offer(\n                buffer1,\n                offsetOne,\n                lengthOne,\n                buffer2,\n                offsetTwo,\n                lengthTwo,\n                (termBuffer, frameOffset, frameLength) -> -1);\n\n            assertEquals(262272, position);\n            final UnsafeBuffer termBuffer = termBuffers[partitionIndex];\n            assertEquals(-1, DataHeaderFlyweight.reservedValue(termBuffer, termOffset));\n            for (int i = 0; i < lengthOne; i++)\n            {\n                assertEquals(buffer1.getByte(offsetOne + i), termBuffer.getByte(termOffset + HEADER_LENGTH + i));\n            }\n            for (int i = 0; i < lengthTwo; i++)\n            {\n                assertEquals(\n                    buffer2.getByte(offsetTwo + i), termBuffer.getByte(termOffset + HEADER_LENGTH + lengthOne + i));\n            }\n        }\n    }\n\n    @Nested\n    class VectorOffer extends OfferBase\n    {\n        private final DirectBufferVector[] vectors = new DirectBufferVector[3];\n\n        UnsafeBuffer buffer(final int processedBytes)\n        {\n            return (UnsafeBuffer)vectors[0].buffer();\n        }\n\n        long invoke(final int length, final ReservedValueSupplier reservedValueSupplier)\n        {\n            final byte[] bytes = new byte[length];\n            ThreadLocalRandom.current().nextBytes(bytes);\n            final int numVectors = vectors.length;\n            final int chunkSize = length / numVectors;\n            final UnsafeBuffer buffer = new UnsafeBuffer(bytes);\n            for (int i = 0, offset = 0; i < numVectors; i++)\n            {\n                final int size = numVectors - 1 == i ? (length - offset) : chunkSize;\n                vectors[i] = new DirectBufferVector(buffer, offset, size);\n                offset += size;\n            }\n\n            return publication.offer(vectors, reservedValueSupplier);\n        }\n    }\n\n    void assertFrameType(final int partitionIndex, final int termOffset, final int expectedFrameType)\n    {\n        assertEquals(expectedFrameType, FrameDescriptor.frameType(termBuffers[partitionIndex], termOffset));\n    }\n\n    private void assertFrameLength(final int partitionIndex, final int termOffset, final int expectedFrameLength)\n    {\n        assertEquals(expectedFrameLength, FrameDescriptor.frameLength(termBuffers[partitionIndex], termOffset));\n    }\n\n    private static List<Arguments> tryClaimPositions()\n    {\n        return Arrays.asList(\n            Arguments.of(\n                100,\n                31,\n                30,\n                packTail(31, 16 * 1024),\n                computePosition(31, 16 * 1024 + 160, POSITION_BITS_TO_SHIFT, TERM_ID_1)),\n            Arguments.of(\n                999,\n                7,\n                6,\n                packTail(212, 8192),\n                computePosition(212, 8192 + 1056, POSITION_BITS_TO_SHIFT, TERM_ID_1)));\n    }\n\n    private static List<Arguments> offerPositions()\n    {\n        return Arrays.asList(\n            Arguments.of(\n                124,\n                5,\n                4,\n                packTail(11, 3072),\n                computePosition(11, 3072 + 160, POSITION_BITS_TO_SHIFT, TERM_ID_1)),\n            Arguments.of(\n                MAX_MESSAGE_SIZE,\n                77,\n                76,\n                packTail(77, 1024),\n                computePosition(77, 1024 + TOTAL_ALIGNED_MAX_MESSAGE_SIZE, POSITION_BITS_TO_SHIFT, TERM_ID_1)));\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/SubscriptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport io.aeron.test.Tests;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.status.ChannelEndpointStatus.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass SubscriptionTest\n{\n    private static final String CHANNEL = \"aeron:udp?endpoint=localhost:40124\";\n    private static final int STREAM_ID_1 = 1002;\n    private static final int INITIAL_TERM_ID = 7;\n    private static final long SUBSCRIPTION_CORRELATION_ID = 100;\n    private static final long REGISTRATION_ID = 10;\n    private static final int READ_BUFFER_CAPACITY = 1024;\n    private static final int FRAGMENT_COUNT_LIMIT = Integer.MAX_VALUE;\n    private static final int HEADER_LENGTH = DataHeaderFlyweight.HEADER_LENGTH;\n\n    private final UnsafeBuffer atomicReadBuffer = new UnsafeBuffer(ByteBuffer.allocate(READ_BUFFER_CAPACITY));\n    private final ClientConductor conductor = mock(ClientConductor.class);\n    private final FragmentHandler fragmentHandler = mock(FragmentHandler.class);\n    private final Image imageOneMock = mock(Image.class);\n    private final Image imageTwoMock = mock(Image.class);\n    private final Header header = new Header(\n        INITIAL_TERM_ID, LogBufferDescriptor.positionBitsToShift(LogBufferDescriptor.TERM_MIN_LENGTH));\n    private final AvailableImageHandler availableImageHandlerMock = mock(AvailableImageHandler.class);\n    private final UnavailableImageHandler unavailableImageHandlerMock = mock(UnavailableImageHandler.class);\n\n    private final UnsafeBuffer tempBuffer = new UnsafeBuffer(ByteBuffer.allocate(1024));\n    private final CountersManager countersManager = Tests.newCountersManager(16 * 1024);\n\n    private Subscription subscription;\n\n    @BeforeEach\n    void setUp()\n    {\n        when(imageOneMock.correlationId()).thenReturn(1L);\n        when(imageTwoMock.correlationId()).thenReturn(2L);\n\n        when(conductor.countersReader()).thenReturn(countersManager);\n\n        subscription = new Subscription(\n            conductor,\n            CHANNEL,\n            STREAM_ID_1,\n            SUBSCRIPTION_CORRELATION_ID,\n            availableImageHandlerMock,\n            unavailableImageHandlerMock);\n\n        doAnswer(\n            (invocation) ->\n            {\n                subscription.internalClose(NULL_VALUE);\n                return null;\n            }).when(conductor).removeSubscription(subscription);\n    }\n\n    @Test\n    void shouldEnsureTheSubscriptionIsOpenWhenPolling()\n    {\n        subscription.close();\n        assertTrue(subscription.isClosed());\n\n        verify(conductor).removeSubscription(subscription);\n    }\n\n    @Test\n    void shouldReadNothingWhenNoImages()\n    {\n        assertEquals(0, subscription.poll(fragmentHandler, 1));\n    }\n\n    @Test\n    void shouldReadNothingWhenThereIsNoData()\n    {\n        subscription.addImage(imageOneMock);\n\n        assertEquals(0, subscription.poll(fragmentHandler, 1));\n    }\n\n    @Test\n    void shouldReadData()\n    {\n        subscription.addImage(imageOneMock);\n\n        when(imageOneMock.poll(any(FragmentHandler.class), anyInt())).then(\n            (invocation) ->\n            {\n                final FragmentHandler handler = (FragmentHandler)invocation.getArguments()[0];\n                handler.onFragment(atomicReadBuffer, HEADER_LENGTH, READ_BUFFER_CAPACITY - HEADER_LENGTH, header);\n\n                return 1;\n            });\n\n        assertEquals(1, subscription.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT));\n        verify(fragmentHandler).onFragment(\n            eq(atomicReadBuffer),\n            eq(HEADER_LENGTH),\n            eq(READ_BUFFER_CAPACITY - HEADER_LENGTH),\n            any(Header.class));\n    }\n\n    @Test\n    void shouldReadDataFromMultipleSources()\n    {\n        subscription.addImage(imageOneMock);\n        subscription.addImage(imageTwoMock);\n\n        when(imageOneMock.poll(any(FragmentHandler.class), anyInt())).then(\n            (invocation) ->\n            {\n                final FragmentHandler handler = (FragmentHandler)invocation.getArguments()[0];\n                handler.onFragment(atomicReadBuffer, HEADER_LENGTH, READ_BUFFER_CAPACITY - HEADER_LENGTH, header);\n\n                return 1;\n            });\n\n        when(imageTwoMock.poll(any(FragmentHandler.class), anyInt())).then(\n            (invocation) ->\n            {\n                final FragmentHandler handler = (FragmentHandler)invocation.getArguments()[0];\n                handler.onFragment(atomicReadBuffer, HEADER_LENGTH, READ_BUFFER_CAPACITY - HEADER_LENGTH, header);\n\n                return 1;\n            });\n\n        assertEquals(2, subscription.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT));\n    }\n\n    @ValueSource(longs = { INITIALIZING, ERRORED, CLOSING })\n    @ParameterizedTest\n    void tryResolveChannelEndpointPortReturnsNullIfChannelStatusIsNotActive(final long channelStatus)\n    {\n        final int channelStatusId = 555;\n        final String channel = \"aeron:udp?endpoint=localhost:0|interface=192.168.5.0/24|reliable=false\";\n        subscription = new Subscription(\n            conductor,\n            channel,\n            STREAM_ID_1,\n            SUBSCRIPTION_CORRELATION_ID,\n            availableImageHandlerMock,\n            unavailableImageHandlerMock);\n        subscription.channelStatusId(channelStatusId);\n        when(conductor.channelStatus(channelStatusId)).thenReturn(channelStatus);\n\n        assertNull(subscription.tryResolveChannelEndpointPort());\n    }\n\n    @Test\n    void tryResolveChannelEndpointPortReturnsOriginalChannelIfNoAddressesFound()\n    {\n        final int channelStatusId = 123;\n        subscription.channelStatusId(channelStatusId);\n        when(conductor.channelStatus(channelStatusId)).thenReturn(ACTIVE);\n\n        assertSame(CHANNEL, subscription.tryResolveChannelEndpointPort());\n    }\n\n    @Test\n    void tryResolveChannelEndpointPortReturnsChannelWithResolvedPort()\n    {\n        final int channelStatusId = 444;\n        final String channel = \"aeron:udp?endpoint=localhost:0|interface=192.168.5.0/24|reliable=false\";\n        subscription = new Subscription(\n            conductor,\n            channel,\n            STREAM_ID_1,\n            SUBSCRIPTION_CORRELATION_ID,\n            availableImageHandlerMock,\n            unavailableImageHandlerMock);\n        subscription.channelStatusId(channelStatusId);\n        when(conductor.channelStatus(channelStatusId)).thenReturn(ACTIVE);\n\n        allocateAddressCounter(\"localhost:21212\", channelStatusId, ERRORED);\n        allocateAddressCounter(\"127.0.0.1:19091\", channelStatusId, ACTIVE);\n\n        final String channelWithResolvedEndpoint = subscription.tryResolveChannelEndpointPort();\n\n        assertEquals(\n            ChannelUri.parse(\"aeron:udp?endpoint=localhost:19091|interface=192.168.5.0/24|reliable=false\"),\n            ChannelUri.parse(channelWithResolvedEndpoint));\n\n        assertSame(channelWithResolvedEndpoint, subscription.tryResolveChannelEndpointPort());\n    }\n\n    @Test\n    void tryResolveChannelEndpointPortReturnsNullIfChannelStatusIsActiveButThereAreNoActiveBindAddresses()\n    {\n        final int channelStatusId = 444;\n        final String channel = \"aeron:udp?endpoint=localhost:0|interface=192.168.5.0/24|reliable=false\";\n        subscription = new Subscription(\n            conductor,\n            channel,\n            STREAM_ID_1,\n            SUBSCRIPTION_CORRELATION_ID,\n            availableImageHandlerMock,\n            unavailableImageHandlerMock);\n        subscription.channelStatusId(channelStatusId);\n        when(conductor.channelStatus(channelStatusId)).thenReturn(ACTIVE);\n\n        allocateAddressCounter(\"localhost:21212\", channelStatusId, ERRORED);\n\n        assertNull(subscription.tryResolveChannelEndpointPort());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:ipc\",\n        \"aeron:udp?control-mode=response|control=localhost:5555\",\n        \"aeron:udp?endpoint=localhost:8888\",\n        \"aeron:udp?control-mode=manual|endpoint=localhost:0\",\n        \"aeron:udp?control-mode=dynamic|control=localhost:7777\"\n    })\n    void tryResolveChannelEndpointPortReturnsOriginalUriIfEndpointDoesNotNeedResolving(final String channel)\n    {\n        final int channelStatusId = 777;\n        subscription = new Subscription(\n            conductor,\n            channel,\n            STREAM_ID_1,\n            SUBSCRIPTION_CORRELATION_ID,\n            availableImageHandlerMock,\n            unavailableImageHandlerMock);\n        subscription.channelStatusId(channelStatusId);\n        when(conductor.channelStatus(channelStatusId)).thenReturn(ERRORED);\n\n        assertSame(channel, subscription.tryResolveChannelEndpointPort());\n\n        // subsequent calls return the same result\n        assertSame(channel, subscription.tryResolveChannelEndpointPort());\n    }\n\n    @ValueSource(longs = { INITIALIZING, ERRORED, CLOSING })\n    @ParameterizedTest\n    void resolvedEndpointReturnsNullIfChannelStatusIsNotActive(final long channelStatus)\n    {\n        final int channelStatusId = 555;\n        final String channel = \"aeron:udp?endpoint=localhost:0|interface=192.168.5.0/24|reliable=false\";\n        subscription = new Subscription(\n            conductor,\n            channel,\n            STREAM_ID_1,\n            SUBSCRIPTION_CORRELATION_ID,\n            availableImageHandlerMock,\n            unavailableImageHandlerMock);\n        subscription.channelStatusId(channelStatusId);\n        when(conductor.channelStatus(channelStatusId)).thenReturn(channelStatus);\n\n        allocateAddressCounter(\"127.0.0.1:19091\", channelStatusId, ACTIVE);\n\n        assertNull(subscription.resolvedEndpoint());\n    }\n\n    @Test\n    void resolvedEndpointReturnsNullIfChannelStatusIsActiveButThereIsNoActiveBindAddress()\n    {\n        final int channelStatusId = 555;\n        final String channel = \"aeron:udp?endpoint=localhost:0|interface=192.168.5.0/24|reliable=false\";\n        subscription = new Subscription(\n            conductor,\n            channel,\n            STREAM_ID_1,\n            SUBSCRIPTION_CORRELATION_ID,\n            availableImageHandlerMock,\n            unavailableImageHandlerMock);\n        subscription.channelStatusId(channelStatusId);\n        when(conductor.channelStatus(channelStatusId)).thenReturn(ACTIVE);\n\n        allocateAddressCounter(\"127.0.0.1:19091\", channelStatusId, CLOSING);\n\n        assertNull(subscription.resolvedEndpoint());\n    }\n\n    @Test\n    void resolvedEndpointReturnsFirstFoundActiveAddress()\n    {\n        final int channelStatusId = 555;\n        final String channel = \"aeron:udp?endpoint=localhost:0|interface=192.168.5.0/24|reliable=false\";\n        subscription = new Subscription(\n            conductor,\n            channel,\n            STREAM_ID_1,\n            SUBSCRIPTION_CORRELATION_ID,\n            availableImageHandlerMock,\n            unavailableImageHandlerMock);\n        subscription.channelStatusId(channelStatusId);\n        when(conductor.channelStatus(channelStatusId)).thenReturn(ACTIVE);\n\n        allocateAddressCounter(\"127.0.0.1:5555\", channelStatusId, ACTIVE);\n        allocateAddressCounter(\"127.0.0.1:19091\", channelStatusId, CLOSING);\n        allocateAddressCounter(\"localhost:12122\", channelStatusId, ACTIVE);\n\n        final String resolvedEndpoint = subscription.resolvedEndpoint();\n        assertEquals(\"127.0.0.1:5555\", resolvedEndpoint);\n\n        assertSame(resolvedEndpoint, subscription.resolvedEndpoint());\n    }\n\n    @Test\n    void shouldAcceptBrokenChannelUriAtCreationTime()\n    {\n        final String channel = \"broken uri\";\n        subscription = new Subscription(\n            conductor,\n            channel,\n            STREAM_ID_1,\n            SUBSCRIPTION_CORRELATION_ID,\n            availableImageHandlerMock,\n            unavailableImageHandlerMock);\n\n        assertEquals(channel, subscription.channel());\n    }\n\n    private void allocateAddressCounter(final String address, final int channelStatusId, final long status)\n    {\n        final AtomicCounter counter = LocalSocketAddressStatus.allocate(\n            tempBuffer,\n            countersManager,\n            REGISTRATION_ID,\n            channelStatusId,\n            \"test\",\n            LocalSocketAddressStatus.LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID);\n\n        LocalSocketAddressStatus.updateBindAddress(counter, address, (UnsafeBuffer)countersManager.metaDataBuffer());\n        counter.setRelease(status);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/TimestampUtilTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass TimestampUtilTest\n{\n    static boolean hasReachedDeadline(final long now, final long deadline)\n    {\n        return deadline - now < 0;\n    }\n\n    @Test\n    void shouldCorrectlyReachDeadline()\n    {\n        assertFalse(hasReachedDeadline(1, 2));\n        assertFalse(hasReachedDeadline(2, 2));\n        assertTrue(hasReachedDeadline(3, 2));\n        assertFalse(hasReachedDeadline(Long.MAX_VALUE, Long.MAX_VALUE));\n        //noinspection NumericOverflow\n        assertFalse(hasReachedDeadline(Long.MAX_VALUE, Long.MAX_VALUE + 1));\n        assertFalse(hasReachedDeadline(Long.MIN_VALUE, Long.MIN_VALUE + 1));\n        assertFalse(hasReachedDeadline(-1, -1 + Long.MAX_VALUE));\n        assertTrue(hasReachedDeadline(-1, Long.MAX_VALUE));\n    }\n}"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/VerifyBuildTimePropertiesTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\nimport org.junit.jupiter.api.condition.JRE;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass VerifyBuildTimePropertiesTest\n{\n    private static final String BUILD_JAVA_VERSION_ENV_VAR = \"BUILD_JAVA_VERSION\";\n\n    @Test\n    @EnabledIfEnvironmentVariable(named = BUILD_JAVA_VERSION_ENV_VAR, matches = \".+\")\n    void checkVersion()\n    {\n        String version = System.getenv(BUILD_JAVA_VERSION_ENV_VAR);\n\n        if (version.indexOf('.') > 0)\n        {\n            version = version.substring(0, version.indexOf('.'));\n        }\n\n        if (version.indexOf('-') > 0)\n        {\n            version = version.substring(0, version.indexOf('-'));\n        }\n\n        final String currentVersion = JRE.currentJre().name();\n        assertEquals(version, currentVersion.substring(currentVersion.indexOf('_') + 1));\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/command/CounterMessageFlyweightTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.command.CounterMessageFlyweight.KEY_BUFFER_OFFSET;\nimport static java.util.Arrays.fill;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass CounterMessageFlyweightTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocate(128));\n    private final CounterMessageFlyweight flyweight = new CounterMessageFlyweight();\n\n    @Test\n    void keyBuffer()\n    {\n        final int offset = 24;\n        buffer.setMemory(0, offset, (byte)15);\n        flyweight.wrap(buffer, offset);\n\n        flyweight.keyBuffer(newBuffer(16), 4, 8);\n\n        assertEquals(8, flyweight.keyBufferLength());\n        assertEquals(KEY_BUFFER_OFFSET, flyweight.keyBufferOffset());\n    }\n\n    @Test\n    void labelBuffer()\n    {\n        final int offset = 40;\n        buffer.setMemory(0, offset, (byte)-1);\n        flyweight.wrap(buffer, offset);\n        flyweight.keyBuffer(newBuffer(16), 6, 9);\n\n        flyweight.labelBuffer(newBuffer(32), 2, 21);\n\n        assertEquals(21, flyweight.labelBufferLength());\n        assertEquals(KEY_BUFFER_OFFSET + 16, flyweight.labelBufferOffset());\n        assertEquals(KEY_BUFFER_OFFSET + 37, flyweight.length());\n    }\n\n    private DirectBuffer newBuffer(final int length)\n    {\n        final byte[] bytes = new byte[length];\n        fill(bytes, (byte)1);\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocate(4 + length));\n        buffer.putBytes(4, bytes);\n\n        return buffer;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/command/TerminateDriverFlyweightTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.command;\n\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.command.TerminateDriverFlyweight.TOKEN_BUFFER_OFFSET;\nimport static java.util.Arrays.fill;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass TerminateDriverFlyweightTest\n{\n    @Test\n    void tokenBuffer()\n    {\n        final int offset = 24;\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocate(128));\n        buffer.setMemory(0, offset, (byte)15);\n        final TerminateDriverFlyweight flyweight = new TerminateDriverFlyweight();\n        flyweight.wrap(buffer, offset);\n\n        flyweight.tokenBuffer(newBuffer(16), 4, 8);\n\n        assertEquals(8, flyweight.tokenBufferLength());\n        assertEquals(TOKEN_BUFFER_OFFSET, flyweight.tokenBufferOffset());\n        assertEquals(TOKEN_BUFFER_OFFSET + 8, flyweight.length());\n    }\n\n    private DirectBuffer newBuffer(final int length)\n    {\n        final byte[] bytes = new byte[length];\n        fill(bytes, (byte)1);\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocate(4 + length));\n        buffer.putBytes(4, bytes);\n        return buffer;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/exceptions/StorageSpaceExceptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.exceptions;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static io.aeron.exceptions.StorageSpaceException.isStorageSpaceError;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass StorageSpaceExceptionTest\n{\n    @Test\n    void isStorageSpaceErrorReturnsFalseIfNull()\n    {\n        assertFalse(isStorageSpaceError(null));\n    }\n\n    @Test\n    void isStorageSpaceErrorReturnsFalseIfNotIOException()\n    {\n        assertFalse(isStorageSpaceError(new IllegalArgumentException(\"No space left on device\")));\n    }\n\n    @Test\n    void isStorageSpaceErrorReturnsFalseIfWrongMessage()\n    {\n        assertFalse(isStorageSpaceError(new IllegalArgumentException(\n            \"Es steht nicht genug Speicherplatz auf dem Datenträger zur Verfügung\")));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"errors\")\n    void isStorageSpaceErrorReturnsTrueWhenIOExceptionWithAParticularMessage(final Throwable exception)\n    {\n        assertTrue(isStorageSpaceError(exception));\n    }\n\n    private static List<Throwable> errors()\n    {\n        return Arrays.asList(\n            new StorageSpaceException(\"test\"),\n            new AeronException(new StorageSpaceException(\"test2\")),\n            new IOException(\"No space left on device\") /* Linux */,\n            new IOException(\"There is not enough space on the disk\") /* Windows */,\n            new IOException(\n                \"something else\", new UncheckedIOException(null, new IOException(\"No space left on device\"))),\n            new AeronException(new IllegalArgumentException(new IOException(\"There is not enough space on the disk\"))));\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/HeaderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport io.aeron.Aeron;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.math.BigDecimal;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass HeaderTest\n{\n    @Test\n    void constructorInitializedData()\n    {\n        final int initialTermId = 18;\n        final BigDecimal context = BigDecimal.valueOf(Long.MAX_VALUE);\n        final int positionBitsToShift = 2;\n        final Header header = new Header(initialTermId, positionBitsToShift, context);\n\n        assertEquals(initialTermId, header.initialTermId());\n        assertEquals(positionBitsToShift, header.positionBitsToShift());\n        assertEquals(context, header.context());\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"0,2,100,1024,5,1172\", \"42,16,13,4096,46,266272\", \"1,30,1024,1073741824,111,119185343488\" })\n    void positionCalculationTheEndOfTheMessageInTheLog(\n        final int initialTermId,\n        final int positionBitsToShift,\n        final int frameLength,\n        final int termOffset,\n        final int termId,\n        final long expectedPosition)\n    {\n        final DataHeaderFlyweight dataHeaderFlyweight = new DataHeaderFlyweight();\n        final Header header = new Header(initialTermId, positionBitsToShift);\n        header.buffer(dataHeaderFlyweight);\n        header.offset(0);\n        dataHeaderFlyweight.wrap(new byte[64], 16, 32);\n        dataHeaderFlyweight.frameLength(frameLength);\n        dataHeaderFlyweight.termId(termId);\n        dataHeaderFlyweight.termOffset(termOffset);\n\n        assertEquals(expectedPosition, header.position());\n        assertEquals(Aeron.NULL_VALUE, header.fragmentedFrameLength());\n    }\n\n    @Test\n    void offsetIsRelativeToTheBufferStart()\n    {\n        final Header header = new Header(42, 3, \"xyz\");\n\n        assertEquals(0, header.offset());\n\n        header.offset(142);\n\n        assertEquals(142, header.offset());\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"103,0x3,0x1A,0x6,2080,-46234,333,5,909090909090909\",\n        \"512,0x1,0xC,0x1,1073741824,42,-876,1543,-4632842384627834687\" })\n    void shouldReadDataFromTheBuffer(\n        final int frameLength,\n        final byte version,\n        final byte flags,\n        final short type,\n        final int termOffset,\n        final int sessionId,\n        final int streamId,\n        final int termId,\n        final long reservedValue)\n    {\n        final byte[] array = new byte[100];\n        final int offset = 16;\n        final DataHeaderFlyweight dataHeaderFlyweight = new DataHeaderFlyweight();\n        dataHeaderFlyweight.wrap(array, offset, 64);\n\n        dataHeaderFlyweight.frameLength(frameLength)\n            .version(version)\n            .flags(flags)\n            .headerType(type);\n\n        dataHeaderFlyweight\n            .termOffset(termOffset)\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .termId(termId)\n            .reservedValue(reservedValue);\n\n\n        final Header header = new Header(5, 22);\n        header.buffer(new UnsafeBuffer(array));\n        header.offset(offset);\n\n        assertEquals(frameLength, header.frameLength());\n        assertEquals(flags, header.flags());\n        assertEquals(type, header.type());\n        assertEquals(termOffset, header.termOffset());\n        assertEquals(sessionId, header.sessionId());\n        assertEquals(streamId, header.streamId());\n        assertEquals(termId, header.termId());\n        assertEquals(reservedValue, header.reservedValue());\n    }\n\n    @Test\n    void shouldOverrideInitialTermId()\n    {\n        final int initialTermId = -178;\n        final int newInitialTermId = 871;\n        final Header header = new Header(initialTermId, 3);\n        assertEquals(initialTermId, header.initialTermId());\n\n        header.initialTermId(newInitialTermId);\n        assertEquals(newInitialTermId, header.initialTermId());\n    }\n\n    @Test\n    void shouldOverridePositionBitsToShift()\n    {\n        final int positionBitsToShift = -6;\n        final int newPositionBitsToShift = 20;\n        final Header header = new Header(42, positionBitsToShift);\n        assertEquals(positionBitsToShift, header.positionBitsToShift());\n\n        header.positionBitsToShift(newPositionBitsToShift);\n        assertEquals(newPositionBitsToShift, header.positionBitsToShift());\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/HeaderWriterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.condition.EnabledIf;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.nio.ByteOrder;\nimport java.util.Arrays;\n\nimport static io.aeron.protocol.DataHeaderFlyweight.*;\nimport static java.nio.ByteOrder.BIG_ENDIAN;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\nclass HeaderWriterTest\n{\n    private final UnsafeBuffer defaultHeaderBuffer = new UnsafeBuffer(new byte[32]);\n    private final UnsafeBuffer termBuffer = new UnsafeBuffer(new byte[1024]);\n\n    @BeforeEach\n    void before()\n    {\n        Arrays.fill(defaultHeaderBuffer.byteArray(), (byte)-1);\n        Arrays.fill(termBuffer.byteArray(), (byte)-1);\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"100,8,5,9,352,-777,-1000,-33\",\n        \"-99,-2,7,1,8,42,3,89\",\n        \"123,0,0,0,0,0,0,0\",\n        \"32,1,-128,4,96,2147483647,-2147483648,-2147483648\",\n    })\n    @EnabledIf(\"littleEndian\")\n    void shouldEncodeHeaderUsingLittleEndianByteOrder(\n        final int frameLength,\n        final byte version,\n        final byte flags,\n        final short headerType,\n        final int termOffset,\n        final int sessionId,\n        final int streamId,\n        final int termId)\n    {\n        defaultHeaderBuffer.putByte(VERSION_FIELD_OFFSET, version);\n        defaultHeaderBuffer.putByte(FLAGS_FIELD_OFFSET, flags);\n        defaultHeaderBuffer.putShort(TYPE_FIELD_OFFSET, headerType, LITTLE_ENDIAN);\n        defaultHeaderBuffer.putInt(SESSION_ID_FIELD_OFFSET, sessionId, LITTLE_ENDIAN);\n        defaultHeaderBuffer.putInt(STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n\n        final HeaderWriter headerWriter = HeaderWriter.newInstance(defaultHeaderBuffer);\n        assertEquals(HeaderWriter.class, headerWriter.getClass());\n        headerWriter.write(termBuffer, termOffset, frameLength, termId);\n\n        assertEquals(-frameLength, termBuffer.getInt(termOffset + FRAME_LENGTH_FIELD_OFFSET, LITTLE_ENDIAN));\n        assertEquals(version, termBuffer.getByte(termOffset + VERSION_FIELD_OFFSET));\n        assertEquals(flags, termBuffer.getByte(termOffset + FLAGS_FIELD_OFFSET));\n        assertEquals(headerType, termBuffer.getShort(termOffset + TYPE_FIELD_OFFSET, LITTLE_ENDIAN));\n        assertEquals(termOffset, termBuffer.getInt(termOffset + TERM_OFFSET_FIELD_OFFSET, LITTLE_ENDIAN));\n        assertEquals(sessionId, termBuffer.getInt(termOffset + SESSION_ID_FIELD_OFFSET, LITTLE_ENDIAN));\n        assertEquals(streamId, termBuffer.getInt(termOffset + STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN));\n        assertEquals(termId, termBuffer.getInt(termOffset + TERM_ID_FIELD_OFFSET, LITTLE_ENDIAN));\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"100,8,5,9,352,-777,-1000,-33\",\n        \"-99,-2,7,1,8,42,3,89\",\n        \"123,0,0,0,0,0,0,0\",\n        \"32,1,-128,4,96,2147483647,-2147483648,-2147483648\",\n    })\n    @EnabledIf(\"bigEndian\")\n    void shouldEncodeHeaderUsingBigEndianByteOrder(\n        final int frameLength,\n        final byte version,\n        final byte flags,\n        final short headerType,\n        final int termOffset,\n        final int sessionId,\n        final int streamId,\n        final int termId)\n    {\n        defaultHeaderBuffer.putByte(VERSION_FIELD_OFFSET, version);\n        defaultHeaderBuffer.putByte(FLAGS_FIELD_OFFSET, flags);\n        defaultHeaderBuffer.putShort(TYPE_FIELD_OFFSET, headerType, BIG_ENDIAN);\n        defaultHeaderBuffer.putInt(SESSION_ID_FIELD_OFFSET, sessionId, BIG_ENDIAN);\n        defaultHeaderBuffer.putInt(STREAM_ID_FIELD_OFFSET, streamId, BIG_ENDIAN);\n\n        final HeaderWriter headerWriter = HeaderWriter.newInstance(defaultHeaderBuffer);\n        assertNotEquals(HeaderWriter.class, headerWriter.getClass());\n        headerWriter.write(termBuffer, termOffset, frameLength, termId);\n\n        assertEquals(-frameLength, termBuffer.getInt(termOffset + FRAME_LENGTH_FIELD_OFFSET, BIG_ENDIAN));\n        assertEquals(version, termBuffer.getByte(termOffset + VERSION_FIELD_OFFSET));\n        assertEquals(flags, termBuffer.getByte(termOffset + FLAGS_FIELD_OFFSET));\n        assertEquals(headerType, termBuffer.getShort(termOffset + TYPE_FIELD_OFFSET, BIG_ENDIAN));\n        assertEquals(termOffset, termBuffer.getInt(termOffset + TERM_OFFSET_FIELD_OFFSET, BIG_ENDIAN));\n        assertEquals(sessionId, termBuffer.getInt(termOffset + SESSION_ID_FIELD_OFFSET, BIG_ENDIAN));\n        assertEquals(streamId, termBuffer.getInt(termOffset + STREAM_ID_FIELD_OFFSET, BIG_ENDIAN));\n        assertEquals(termId, termBuffer.getInt(termOffset + TERM_ID_FIELD_OFFSET, BIG_ENDIAN));\n    }\n\n    private static boolean littleEndian()\n    {\n        return ByteOrder.nativeOrder() == LITTLE_ENDIAN;\n    }\n\n    private static boolean bigEndian()\n    {\n        return ByteOrder.nativeOrder() == BIG_ENDIAN;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/LogBufferDescriptorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass LogBufferDescriptorTest\n{\n    private final UnsafeBuffer metadataBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(LOG_META_DATA_LENGTH));\n\n    @Test\n    void rotateLogIsAShouldCasActiveTermCountEvenWhenTermIdDoesNotMatch()\n    {\n        final int termId = 5;\n        final int termCount = 1;\n        rawTail(metadataBuffer, 2, packTail(termId, 1024));\n        rawTail(metadataBuffer, 0, packTail(termId + 1, 2048));\n        rawTail(metadataBuffer, 1, packTail(termId + 2, 4096));\n        activeTermCount(metadataBuffer, termCount);\n\n        assertTrue(rotateLog(metadataBuffer, termCount, termId));\n\n        assertEquals(packTail(termId, 1024), rawTail(metadataBuffer, 2));\n        assertEquals(packTail(termId + 1, 2048), rawTail(metadataBuffer, 0));\n        assertEquals(packTail(termId + 2, 4096), rawTail(metadataBuffer, 1));\n        assertEquals(termCount + 1, activeTermCount(metadataBuffer));\n    }\n\n    @Test\n    void rotateLogIsAShouldCasActiveTermCountAfterSettingTailForTheNextTerm()\n    {\n        final int termId = 51;\n        final int termCount = 19;\n        rawTail(metadataBuffer, 1, packTail(termId, 1024));\n        rawTail(metadataBuffer, 2, packTail(termId + 1 - PARTITION_COUNT, 2048));\n        rawTail(metadataBuffer, 0, packTail(termId + 2 - PARTITION_COUNT, 4096));\n        activeTermCount(metadataBuffer, termCount);\n\n        assertTrue(rotateLog(metadataBuffer, termCount, termId));\n\n        assertEquals(packTail(termId, 1024), rawTail(metadataBuffer, 1));\n        assertEquals(packTail(termId + 1, 0), rawTail(metadataBuffer, 2));\n        assertEquals(packTail(termId + 2 - PARTITION_COUNT, 4096), rawTail(metadataBuffer, 0));\n        assertEquals(termCount + 1, activeTermCount(metadataBuffer));\n    }\n\n    @Test\n    void rotateLogIsANoOpIfNeitherTailNorActiveTermCountCanBeChanged()\n    {\n        final int termId = 23;\n        final int termCount = 42;\n        rawTail(metadataBuffer, 0, packTail(termId, 1024));\n        rawTail(metadataBuffer, 1, packTail(termId + 18, 2048));\n        rawTail(metadataBuffer, 2, packTail(termId - 19, 4096));\n        activeTermCount(metadataBuffer, termCount);\n\n        assertFalse(rotateLog(metadataBuffer, 3, termId));\n\n        assertEquals(packTail(termId, 1024), rawTail(metadataBuffer, 0));\n        assertEquals(packTail(termId + 18, 2048), rawTail(metadataBuffer, 1));\n        assertEquals(packTail(termId - 19, 4096), rawTail(metadataBuffer, 2));\n        assertEquals(termCount, activeTermCount(metadataBuffer));\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"0,1376,0\", \"10,1024,64\", \"2048,2048,2080\", \"4096,1024,4224\", \"7997,992,8288\" })\n    void shouldComputeFragmentedFrameLength(\n        final int length, final int maxPayloadLength, final int frameLength)\n    {\n        assertEquals(LogBufferDescriptor.computeFragmentedFrameLength(length, maxPayloadLength), frameLength);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/LogBufferUnblockerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.mockito.InOrder;\n\nimport static java.nio.ByteBuffer.allocateDirect;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\n\nclass LogBufferUnblockerTest\n{\n    private static final int TERM_LENGTH = TERM_MIN_LENGTH;\n    private static final int TERM_ID_1 = 1;\n    private static final int TERM_TAIL_COUNTER_OFFSET = TERM_TAIL_COUNTERS_OFFSET;\n\n    private final UnsafeBuffer logMetaDataBuffer = spy(new UnsafeBuffer(allocateDirect(LOG_META_DATA_LENGTH)));\n    private final UnsafeBuffer[] termBuffers = new UnsafeBuffer[PARTITION_COUNT];\n\n    private final int positionBitsToShift = LogBufferDescriptor.positionBitsToShift(TERM_LENGTH);\n\n    @BeforeEach\n    void setUp()\n    {\n        initialTermId(logMetaDataBuffer, TERM_ID_1);\n\n        for (int i = 0; i < PARTITION_COUNT; i++)\n        {\n            termBuffers[i] = spy(new UnsafeBuffer(allocateDirect(TERM_LENGTH)));\n        }\n\n        final int initialTermId = TERM_ID_1;\n        initialiseTailWithTermId(logMetaDataBuffer, 0, initialTermId);\n        for (int i = 1; i < PARTITION_COUNT; i++)\n        {\n            final int expectedTermId = (initialTermId + i) - PARTITION_COUNT;\n            initialiseTailWithTermId(logMetaDataBuffer, i, expectedTermId);\n        }\n    }\n\n    @Test\n    void shouldNotUnblockWhenPositionHasCompleteMessage()\n    {\n        final int blockedOffset = HEADER_LENGTH * 4;\n        final long blockedPosition = computePosition(TERM_ID_1, blockedOffset, positionBitsToShift, TERM_ID_1);\n        final int activeIndex = indexByPosition(blockedPosition, positionBitsToShift);\n\n        when(termBuffers[activeIndex].getIntVolatile(blockedOffset)).thenReturn(HEADER_LENGTH);\n\n        assertFalse(LogBufferUnblocker.unblock(termBuffers, logMetaDataBuffer, blockedPosition, TERM_LENGTH));\n\n        final long rawTail = rawTailVolatile(logMetaDataBuffer);\n        assertEquals(blockedPosition, computePosition(termId(rawTail), blockedOffset, positionBitsToShift, TERM_ID_1));\n    }\n\n    @Test\n    void shouldUnblockWhenPositionHasNonCommittedMessageAndTailWithinTerm()\n    {\n        final int blockedOffset = HEADER_LENGTH * 4;\n        final int messageLength = HEADER_LENGTH * 4;\n        final long blockedPosition = computePosition(TERM_ID_1, blockedOffset, positionBitsToShift, TERM_ID_1);\n        final int activeIndex = indexByPosition(blockedPosition, positionBitsToShift);\n\n        when(termBuffers[activeIndex].getIntVolatile(blockedOffset)).thenReturn(-messageLength);\n\n        assertTrue(LogBufferUnblocker.unblock(termBuffers, logMetaDataBuffer, blockedPosition, TERM_LENGTH));\n\n        final long rawTail = rawTailVolatile(logMetaDataBuffer);\n        assertEquals(\n            blockedPosition + messageLength,\n            computePosition(termId(rawTail), blockedOffset + messageLength, positionBitsToShift, TERM_ID_1));\n    }\n\n    @Test\n    void shouldUnblockWhenPositionHasNonCommittedMessageAndTailAtEndOfTerm()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int blockedOffset = TERM_LENGTH - messageLength;\n        final long blockedPosition = computePosition(TERM_ID_1, blockedOffset, positionBitsToShift, TERM_ID_1);\n        final int activeIndex = indexByPosition(blockedPosition, positionBitsToShift);\n\n        when(termBuffers[activeIndex].getIntVolatile(blockedOffset)).thenReturn(0);\n\n        logMetaDataBuffer.getAndAddLong(TERM_TAIL_COUNTER_OFFSET, TERM_LENGTH);\n\n        assertTrue(LogBufferUnblocker.unblock(termBuffers, logMetaDataBuffer, blockedPosition, TERM_LENGTH));\n\n        final long rawTail = rawTailVolatile(logMetaDataBuffer);\n        final int termId = termId(rawTail);\n        assertEquals(\n            blockedPosition + messageLength, computePosition(termId, 0, positionBitsToShift, TERM_ID_1));\n\n        verify(logMetaDataBuffer).compareAndSetInt(LOG_ACTIVE_TERM_COUNT_OFFSET, 0, 1);\n    }\n\n    @Test\n    void shouldUnblockWhenPositionHasCommittedMessageAndTailAtEndOfTermButNotRotated()\n    {\n        final long blockedPosition = TERM_LENGTH;\n\n        final int termTailCounterTwoOffset = TERM_TAIL_COUNTER_OFFSET + SIZE_OF_LONG;\n        logMetaDataBuffer.getAndAddLong(TERM_TAIL_COUNTER_OFFSET, TERM_LENGTH);\n\n        assertTrue(LogBufferUnblocker.unblock(termBuffers, logMetaDataBuffer, blockedPosition, TERM_LENGTH));\n\n        final long rawTail = rawTailVolatile(logMetaDataBuffer);\n        final int termId = termId(rawTail);\n        assertEquals(TERM_ID_1 + 1, termId);\n        assertEquals(blockedPosition, computePosition(termId, 0, positionBitsToShift, TERM_ID_1));\n\n        final InOrder inOrder = inOrder(logMetaDataBuffer);\n        inOrder.verify(logMetaDataBuffer)\n            .compareAndSetLong(termTailCounterTwoOffset, pack(TERM_ID_1 - 2, 0), pack(TERM_ID_1 + 1, 0));\n        inOrder.verify(logMetaDataBuffer).compareAndSetInt(LOG_ACTIVE_TERM_COUNT_OFFSET, 0, 1);\n    }\n\n    @Test\n    void shouldUnblockWhenPositionHasNonCommittedMessageAndTailPastEndOfTerm()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int blockedOffset = TERM_LENGTH - messageLength;\n        final long blockedPosition = computePosition(TERM_ID_1, blockedOffset, positionBitsToShift, TERM_ID_1);\n        final int activeIndex = indexByPosition(blockedPosition, positionBitsToShift);\n\n        when(termBuffers[activeIndex].getIntVolatile(blockedOffset)).thenReturn(0);\n\n        logMetaDataBuffer.putLong(TERM_TAIL_COUNTER_OFFSET, pack(TERM_ID_1, TERM_LENGTH + HEADER_LENGTH));\n\n        assertTrue(LogBufferUnblocker.unblock(termBuffers, logMetaDataBuffer, blockedPosition, TERM_LENGTH));\n\n        final long rawTail = rawTailVolatile(logMetaDataBuffer);\n        final int termId = termId(rawTail);\n        assertEquals(\n            blockedPosition + messageLength, computePosition(termId, 0, positionBitsToShift, TERM_ID_1));\n\n        verify(logMetaDataBuffer).compareAndSetInt(LOG_ACTIVE_TERM_COUNT_OFFSET, 0, 1);\n    }\n\n    private static long pack(final int termId, final int offset)\n    {\n        return (((long)termId) << 32) | offset;\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/TermBlockScannerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.agrona.BitUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.typeOffset;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_DATA;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_PAD;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.lengthOffset;\n\nclass TermBlockScannerTest\n{\n    private final UnsafeBuffer termBuffer = mock(UnsafeBuffer.class);\n\n    @BeforeEach\n    void before()\n    {\n        when(termBuffer.capacity()).thenReturn(LogBufferDescriptor.TERM_MIN_LENGTH);\n    }\n\n    @Test\n    void shouldScanEmptyBuffer()\n    {\n        final int offset = 0;\n        final int limit = termBuffer.capacity();\n\n        final int newOffset = TermBlockScanner.scan(termBuffer, offset, limit);\n        assertEquals(offset, newOffset);\n    }\n\n    @Test\n    void shouldReadFirstMessage()\n    {\n        final int offset = 0;\n        final int limit = termBuffer.capacity();\n        final int messageLength = 50;\n        final int alignedMessageLength = BitUtil.align(messageLength, FRAME_ALIGNMENT);\n\n        when(termBuffer.getIntVolatile(lengthOffset(offset))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(offset))).thenReturn((short)HDR_TYPE_DATA);\n\n        final int newOffset = TermBlockScanner.scan(termBuffer, offset, limit);\n        assertEquals(alignedMessageLength, newOffset);\n    }\n\n    @Test\n    void shouldReadBlockOfTwoMessages()\n    {\n        final int offset = 0;\n        final int limit = termBuffer.capacity();\n        final int messageLength = 50;\n        final int alignedMessageLength = BitUtil.align(messageLength, FRAME_ALIGNMENT);\n\n        when(termBuffer.getIntVolatile(lengthOffset(offset))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(offset))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(lengthOffset(alignedMessageLength))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(alignedMessageLength))).thenReturn((short)HDR_TYPE_DATA);\n\n        final int newOffset = TermBlockScanner.scan(termBuffer, offset, limit);\n        assertEquals(alignedMessageLength * 2, newOffset);\n    }\n\n    @Test\n    void shouldReadBlockOfThreeMessagesThatFillBuffer()\n    {\n        final int offset = 0;\n        final int limit = termBuffer.capacity();\n        final int messageLength = 50;\n        final int alignedMessageLength = BitUtil.align(messageLength, FRAME_ALIGNMENT);\n        final int thirdMessageLength = limit - (alignedMessageLength * 2);\n\n        when(termBuffer.getIntVolatile(lengthOffset(offset))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(offset))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(lengthOffset(alignedMessageLength))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(alignedMessageLength))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(lengthOffset(alignedMessageLength * 2))).thenReturn(thirdMessageLength);\n        when(termBuffer.getShort(typeOffset(alignedMessageLength * 2))).thenReturn((short)HDR_TYPE_DATA);\n\n        final int newOffset = TermBlockScanner.scan(termBuffer, offset, limit);\n        assertEquals(limit, newOffset);\n    }\n\n    @Test\n    void shouldReadBlockOfTwoMessagesBecauseOfLimit()\n    {\n        final int offset = 0;\n        final int messageLength = 50;\n        final int alignedMessageLength = BitUtil.align(messageLength, FRAME_ALIGNMENT);\n        final int limit = (alignedMessageLength * 2) + 1;\n\n        when(termBuffer.getIntVolatile(lengthOffset(offset))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(offset))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(lengthOffset(alignedMessageLength))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(alignedMessageLength))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(lengthOffset(alignedMessageLength * 2))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(alignedMessageLength * 2))).thenReturn((short)HDR_TYPE_DATA);\n\n        final int newOffset = TermBlockScanner.scan(termBuffer, offset, limit);\n        assertEquals(alignedMessageLength * 2, newOffset);\n    }\n\n    @Test\n    void shouldFailToReadFirstMessageBecauseOfLimit()\n    {\n        final int offset = 0;\n        final int messageLength = 50;\n        final int alignedMessageLength = BitUtil.align(messageLength, FRAME_ALIGNMENT);\n        final int limit = alignedMessageLength - 1;\n\n        when(termBuffer.getIntVolatile(lengthOffset(offset))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(offset))).thenReturn((short)HDR_TYPE_DATA);\n\n        final int newOffset = TermBlockScanner.scan(termBuffer, offset, limit);\n        assertEquals(offset, newOffset);\n    }\n\n    @Test\n    void shouldReadOneMessageOnLimit()\n    {\n        final int offset = 0;\n        final int messageLength = 50;\n        final int alignedMessageLength = BitUtil.align(messageLength, FRAME_ALIGNMENT);\n\n        when(termBuffer.getIntVolatile(lengthOffset(offset))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(offset))).thenReturn((short)HDR_TYPE_DATA);\n\n        final int newOffset = TermBlockScanner.scan(termBuffer, offset, alignedMessageLength);\n        assertEquals(alignedMessageLength, newOffset);\n    }\n\n    @Test\n    void shouldReadBlockOfOneMessageThenPadding()\n    {\n        final int offset = 0;\n        final int limit = termBuffer.capacity();\n        final int messageLength = 50;\n        final int alignedMessageLength = BitUtil.align(messageLength, FRAME_ALIGNMENT);\n\n        when(termBuffer.getIntVolatile(lengthOffset(offset))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(offset))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(lengthOffset(alignedMessageLength))).thenReturn(messageLength);\n        when(termBuffer.getShort(typeOffset(alignedMessageLength))).thenReturn((short)HDR_TYPE_PAD);\n\n        final int firstOffset = TermBlockScanner.scan(termBuffer, offset, limit);\n        assertEquals(alignedMessageLength, firstOffset);\n\n        final int secondOffset = TermBlockScanner.scan(termBuffer, firstOffset, limit);\n        assertEquals(alignedMessageLength * 2, secondOffset);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/TermGapFillerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.PADDING_FRAME_TYPE;\nimport static io.aeron.logbuffer.FrameDescriptor.UNFRAGMENTED;\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.createDefaultHeader;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass TermGapFillerTest\n{\n    private static final int INITIAL_TERM_ID = 11;\n    private static final int TERM_ID = 22;\n    private static final int SESSION_ID = 333;\n    private static final int STREAM_ID = 1007;\n\n    private final UnsafeBuffer metaDataBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(LOG_META_DATA_LENGTH));\n    private final UnsafeBuffer termBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_MIN_LENGTH));\n    private final DataHeaderFlyweight dataFlyweight = new DataHeaderFlyweight(termBuffer);\n\n    @BeforeEach\n    void setup()\n    {\n        initialTermId(metaDataBuffer, INITIAL_TERM_ID);\n        storeDefaultFrameHeader(metaDataBuffer, createDefaultHeader(SESSION_ID, STREAM_ID, INITIAL_TERM_ID));\n    }\n\n    @Test\n    void shouldFillGapAtBeginningOfTerm()\n    {\n        final int gapOffset = 0;\n        final int gapLength = 64;\n\n        assertTrue(TermGapFiller.tryFillGap(metaDataBuffer, termBuffer, TERM_ID, gapOffset, gapLength));\n\n        assertEquals(gapLength, dataFlyweight.frameLength());\n        assertEquals(gapLength, dataFlyweight.frameLength());\n        assertEquals(gapOffset, dataFlyweight.termOffset());\n        assertEquals(SESSION_ID, dataFlyweight.sessionId());\n        assertEquals(TERM_ID, dataFlyweight.termId());\n        assertEquals(PADDING_FRAME_TYPE, dataFlyweight.headerType());\n        assertEquals(UNFRAGMENTED, (byte)(dataFlyweight.flags()));\n    }\n\n    @Test\n    void shouldNotOverwriteExistingFrame()\n    {\n        final int gapOffset = 0;\n        final int gapLength = 64;\n\n        dataFlyweight.frameLength(32);\n\n        assertFalse(TermGapFiller.tryFillGap(metaDataBuffer, termBuffer, TERM_ID, gapOffset, gapLength));\n    }\n\n    @Test\n    void shouldFillGapAfterExistingFrame()\n    {\n        final int gapOffset = 128;\n        final int gapLength = 64;\n\n        dataFlyweight\n            .sessionId(SESSION_ID)\n            .termId(TERM_ID)\n            .streamId(STREAM_ID)\n            .flags(UNFRAGMENTED)\n            .frameLength(gapOffset);\n        dataFlyweight.setMemory(0, gapOffset - DataHeaderFlyweight.HEADER_LENGTH, (byte)'x');\n\n        assertTrue(TermGapFiller.tryFillGap(metaDataBuffer, termBuffer, TERM_ID, gapOffset, gapLength));\n\n        dataFlyweight.wrap(termBuffer, gapOffset, termBuffer.capacity() - gapOffset);\n        assertEquals(gapLength, dataFlyweight.frameLength());\n        assertEquals(gapOffset, dataFlyweight.termOffset());\n        assertEquals(SESSION_ID, dataFlyweight.sessionId());\n        assertEquals(TERM_ID, dataFlyweight.termId());\n        assertEquals(PADDING_FRAME_TYPE, dataFlyweight.headerType());\n        assertEquals(UNFRAGMENTED, (byte)(dataFlyweight.flags()));\n    }\n\n    @Test\n    void shouldFillGapBetweenExistingFrames()\n    {\n        final int gapOffset = 128;\n        final int gapLength = 64;\n\n        dataFlyweight\n            .sessionId(SESSION_ID)\n            .termId(TERM_ID)\n            .termOffset(0)\n            .streamId(STREAM_ID)\n            .flags(UNFRAGMENTED)\n            .frameLength(gapOffset)\n            .setMemory(0, gapOffset - DataHeaderFlyweight.HEADER_LENGTH, (byte)'x');\n\n        final int secondExistingFrameOffset = gapOffset + gapLength;\n        dataFlyweight\n            .wrap(termBuffer, secondExistingFrameOffset, termBuffer.capacity() - secondExistingFrameOffset);\n        dataFlyweight\n            .sessionId(SESSION_ID)\n            .termId(TERM_ID)\n            .termOffset(secondExistingFrameOffset)\n            .streamId(STREAM_ID)\n            .flags(UNFRAGMENTED)\n            .frameLength(64);\n\n        assertTrue(TermGapFiller.tryFillGap(metaDataBuffer, termBuffer, TERM_ID, gapOffset, gapLength));\n\n        dataFlyweight.wrap(termBuffer, gapOffset, termBuffer.capacity() - gapOffset);\n        assertEquals(gapLength, dataFlyweight.frameLength());\n        assertEquals(gapOffset, dataFlyweight.termOffset());\n        assertEquals(SESSION_ID, dataFlyweight.sessionId());\n        assertEquals(TERM_ID, dataFlyweight.termId());\n        assertEquals(PADDING_FRAME_TYPE, dataFlyweight.headerType());\n        assertEquals(UNFRAGMENTED, (byte)(dataFlyweight.flags()));\n    }\n\n    @Test\n    void shouldFillGapAtEndOfTerm()\n    {\n        final int gapOffset = termBuffer.capacity() - 64;\n        final int gapLength = 64;\n\n        dataFlyweight\n            .sessionId(SESSION_ID)\n            .termId(TERM_ID)\n            .streamId(STREAM_ID)\n            .flags(UNFRAGMENTED)\n            .frameLength(termBuffer.capacity() - gapOffset);\n        dataFlyweight.setMemory(0, gapOffset - DataHeaderFlyweight.HEADER_LENGTH, (byte)'x');\n\n        assertTrue(TermGapFiller.tryFillGap(metaDataBuffer, termBuffer, TERM_ID, gapOffset, gapLength));\n\n        dataFlyweight.wrap(termBuffer, gapOffset, termBuffer.capacity() - gapOffset);\n        assertEquals(gapLength, dataFlyweight.frameLength());\n        assertEquals(gapOffset, dataFlyweight.termOffset());\n        assertEquals(SESSION_ID, dataFlyweight.sessionId());\n        assertEquals(TERM_ID, dataFlyweight.termId());\n        assertEquals(PADDING_FRAME_TYPE, dataFlyweight.headerType());\n        assertEquals(UNFRAGMENTED, (byte)(dataFlyweight.flags()));\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/TermGapScannerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static org.agrona.BitUtil.align;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\n\nclass TermGapScannerTest\n{\n    private static final int LOG_BUFFER_CAPACITY = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int TERM_ID = 1;\n    private static final int HEADER_LENGTH = DataHeaderFlyweight.HEADER_LENGTH;\n\n    private final UnsafeBuffer termBuffer = mock(UnsafeBuffer.class);\n    private final TermGapScanner.GapHandler gapHandler = mock(TermGapScanner.GapHandler.class);\n\n    @BeforeEach\n    void setUp()\n    {\n        when(termBuffer.capacity()).thenReturn(LOG_BUFFER_CAPACITY);\n    }\n\n    @Test\n    void shouldReportGapAtBeginningOfBuffer()\n    {\n        final int frameOffset = align(HEADER_LENGTH * 3, FRAME_ALIGNMENT);\n        final int highWaterMark = frameOffset + align(HEADER_LENGTH, FRAME_ALIGNMENT);\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(HEADER_LENGTH);\n\n        assertEquals(0, TermGapScanner.scanForGap(termBuffer, TERM_ID, 0, highWaterMark, gapHandler));\n\n        verify(gapHandler).onGap(TERM_ID, 0, frameOffset);\n        verifyNoMoreInteractions(gapHandler);\n    }\n\n    @Test\n    void shouldReportSingleGapWhenBufferNotFull()\n    {\n        final int tail = align(HEADER_LENGTH, FRAME_ALIGNMENT);\n        final int highWaterMark = FRAME_ALIGNMENT * 3;\n\n        when(termBuffer.getIntVolatile(tail - align(HEADER_LENGTH, FRAME_ALIGNMENT))).thenReturn(HEADER_LENGTH);\n        when(termBuffer.getIntVolatile(tail)).thenReturn(0);\n        when(termBuffer.getIntVolatile(highWaterMark - align(HEADER_LENGTH, FRAME_ALIGNMENT)))\n            .thenReturn(HEADER_LENGTH);\n\n        assertEquals(tail, TermGapScanner.scanForGap(termBuffer, TERM_ID, tail, highWaterMark, gapHandler));\n\n        verify(gapHandler).onGap(TERM_ID, tail, align(HEADER_LENGTH, FRAME_ALIGNMENT));\n        verifyNoMoreInteractions(gapHandler);\n    }\n\n    @Test\n    void shouldReportSingleGapWhenBufferIsFull()\n    {\n        final int tail = LOG_BUFFER_CAPACITY - (align(HEADER_LENGTH, FRAME_ALIGNMENT) * 2);\n        final int highWaterMark = LOG_BUFFER_CAPACITY;\n\n        when(termBuffer.getIntVolatile(tail - align(HEADER_LENGTH, FRAME_ALIGNMENT))).thenReturn(HEADER_LENGTH);\n        when(termBuffer.getIntVolatile(tail)).thenReturn(0);\n        when(termBuffer.getIntVolatile(highWaterMark - align(HEADER_LENGTH, FRAME_ALIGNMENT)))\n            .thenReturn(HEADER_LENGTH);\n\n        assertEquals(tail, TermGapScanner.scanForGap(termBuffer, TERM_ID, tail, highWaterMark, gapHandler));\n\n        verify(gapHandler).onGap(TERM_ID, tail, align(HEADER_LENGTH, FRAME_ALIGNMENT));\n        verifyNoMoreInteractions(gapHandler);\n    }\n\n    @Test\n    void shouldReportNoGapWhenHwmIsInPadding()\n    {\n        final int paddingLength = align(HEADER_LENGTH, FRAME_ALIGNMENT) * 2;\n        final int tail = LOG_BUFFER_CAPACITY - paddingLength;\n        final int highWaterMark = LOG_BUFFER_CAPACITY - paddingLength + HEADER_LENGTH;\n\n        when(termBuffer.getIntVolatile(tail)).thenReturn(paddingLength);\n        when(termBuffer.getIntVolatile(tail + HEADER_LENGTH)).thenReturn(0);\n\n        assertEquals(\n            LOG_BUFFER_CAPACITY, TermGapScanner.scanForGap(termBuffer, TERM_ID, tail, highWaterMark, gapHandler));\n\n        verifyNoInteractions(gapHandler);\n    }\n\n    @Test\n    void shouldReportSingleHeaderGap()\n    {\n        final int offset = 8192 + 384;\n        when(termBuffer.getIntVolatile(offset)).thenReturn(0);\n        when(termBuffer.getIntVolatile(offset + HEADER_LENGTH)).thenReturn(128);\n\n        assertEquals(\n            offset, TermGapScanner.scanForGap(termBuffer, TERM_ID, offset, LOG_BUFFER_CAPACITY, gapHandler));\n\n        verify(termBuffer).getIntVolatile(offset);\n        verify(termBuffer).getIntVolatile(offset + HEADER_LENGTH);\n        verify(gapHandler).onGap(TERM_ID, offset, HEADER_LENGTH);\n        verifyNoMoreInteractions(gapHandler, termBuffer);\n    }\n\n    @Test\n    void shouldReportGapAtTheEndOfTheBuffer()\n    {\n        final int offset = LOG_BUFFER_CAPACITY - 128;\n        when(termBuffer.getIntVolatile(offset)).thenReturn(0);\n\n        assertEquals(\n            offset, TermGapScanner.scanForGap(termBuffer, TERM_ID, offset, LOG_BUFFER_CAPACITY, gapHandler));\n\n        verify(termBuffer).getIntVolatile(offset);\n        verify(termBuffer).getIntVolatile(offset + HEADER_LENGTH);\n        verify(termBuffer).getIntVolatile(offset + 2 * HEADER_LENGTH);\n        verify(termBuffer).getIntVolatile(offset + 3 * HEADER_LENGTH);\n        verify(gapHandler).onGap(TERM_ID, offset, 128);\n        verifyNoMoreInteractions(gapHandler, termBuffer);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/TermReaderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.Position;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_DATA;\nimport static org.agrona.BitUtil.align;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nclass TermReaderTest\n{\n    private static final int TERM_BUFFER_CAPACITY = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int HEADER_LENGTH = DataHeaderFlyweight.HEADER_LENGTH;\n    private static final int INITIAL_TERM_ID = 7;\n    private static final int POSITION_BITS_TO_SHIFT = LogBufferDescriptor.positionBitsToShift(TERM_BUFFER_CAPACITY);\n\n    private final Header header = new Header(INITIAL_TERM_ID, TERM_BUFFER_CAPACITY);\n    private final UnsafeBuffer termBuffer = mock(UnsafeBuffer.class);\n    private final ErrorHandler errorHandler = mock(ErrorHandler.class);\n    private final FragmentHandler handler = mock(FragmentHandler.class);\n    private final Position subscriberPosition = mock(Position.class);\n\n    @BeforeEach\n    void setUp()\n    {\n        when(termBuffer.capacity()).thenReturn(TERM_BUFFER_CAPACITY);\n    }\n\n    @Test\n    void shouldReadFirstMessage()\n    {\n        final int msgLength = 1;\n        final int frameLength = HEADER_LENGTH + msgLength;\n        final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termOffset = 0;\n\n        when(termBuffer.getIntVolatile(0)).thenReturn(frameLength);\n        when(termBuffer.getShort(typeOffset(0))).thenReturn((short)HDR_TYPE_DATA);\n\n        final int readOutcome = TermReader.read(\n            termBuffer, termOffset, handler, Integer.MAX_VALUE, header, errorHandler, 0, subscriberPosition);\n        assertEquals(1, readOutcome);\n\n        final InOrder inOrder = inOrder(termBuffer, handler, subscriberPosition);\n        inOrder.verify(termBuffer).getIntVolatile(0);\n        inOrder.verify(handler).onFragment(eq(termBuffer), eq(HEADER_LENGTH), eq(msgLength), any(Header.class));\n        inOrder.verify(subscriberPosition).setRelease(alignedFrameLength);\n    }\n\n    @Test\n    void shouldNotReadPastTail()\n    {\n        final int termOffset = 0;\n\n        final int readOutcome = TermReader.read(\n            termBuffer, termOffset, handler, Integer.MAX_VALUE, header, errorHandler, 0, subscriberPosition);\n        assertEquals(0, readOutcome);\n        verify(subscriberPosition, never()).setRelease(anyLong());\n\n        verify(termBuffer).getIntVolatile(0);\n        verify(handler, never()).onFragment(any(), anyInt(), anyInt(), any());\n    }\n\n    @Test\n    void shouldReadOneLimitedMessage()\n    {\n        final int msgLength = 1;\n        final int frameLength = HEADER_LENGTH + msgLength;\n        final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termOffset = 0;\n\n        when(termBuffer.getIntVolatile(anyInt())).thenReturn(frameLength);\n        when(termBuffer.getShort(anyInt())).thenReturn((short)HDR_TYPE_DATA);\n\n        final int readOutcome = TermReader.read(\n            termBuffer, termOffset, handler, 1, header, errorHandler, 0, subscriberPosition);\n        assertEquals(1, readOutcome);\n\n        final InOrder inOrder = inOrder(termBuffer, handler, subscriberPosition);\n        inOrder.verify(termBuffer).getIntVolatile(0);\n        inOrder.verify(handler).onFragment(eq(termBuffer), eq(HEADER_LENGTH), eq(msgLength), any(Header.class));\n        inOrder.verify(subscriberPosition).setRelease(alignedFrameLength);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldReadMultipleMessages()\n    {\n        final int msgLength = 1;\n        final int frameLength = HEADER_LENGTH + msgLength;\n        final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n        final int termOffset = 0;\n\n        when(termBuffer.getIntVolatile(0)).thenReturn(frameLength);\n        when(termBuffer.getIntVolatile(alignedFrameLength)).thenReturn(frameLength);\n        when(termBuffer.getShort(anyInt())).thenReturn((short)HDR_TYPE_DATA);\n\n        final int readOutcome = TermReader.read(\n            termBuffer, termOffset, handler, Integer.MAX_VALUE, header, errorHandler, 0, subscriberPosition);\n        assertEquals(2, readOutcome);\n\n        final InOrder inOrder = inOrder(termBuffer, handler, subscriberPosition);\n        inOrder.verify(termBuffer).getIntVolatile(0);\n        inOrder.verify(handler).onFragment(eq(termBuffer), eq(HEADER_LENGTH), eq(msgLength), any(Header.class));\n\n        inOrder.verify(termBuffer).getIntVolatile(alignedFrameLength);\n        inOrder\n            .verify(handler)\n            .onFragment(eq(termBuffer), eq(alignedFrameLength + HEADER_LENGTH), eq(msgLength), any(Header.class));\n        inOrder.verify(subscriberPosition).setRelease(alignedFrameLength * 2L);\n    }\n\n    @Test\n    void shouldReadLastMessage()\n    {\n        final int msgLength = 1;\n        final int frameLength = HEADER_LENGTH + msgLength;\n        final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n        final int frameOffset = TERM_BUFFER_CAPACITY - alignedFrameLength;\n        final long startingPosition = LogBufferDescriptor.computePosition(\n            INITIAL_TERM_ID, frameOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(frameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)HDR_TYPE_DATA);\n        when(subscriberPosition.getVolatile()).thenReturn(startingPosition);\n\n        final int readOutcome = TermReader.read(\n            termBuffer,\n            frameOffset,\n            handler,\n            Integer.MAX_VALUE,\n            header,\n            errorHandler,\n            startingPosition,\n            subscriberPosition);\n        assertEquals(1, readOutcome);\n\n        final InOrder inOrder = inOrder(termBuffer, handler, subscriberPosition);\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        inOrder.verify(handler).onFragment(\n            eq(termBuffer), eq(frameOffset + HEADER_LENGTH), eq(msgLength), any(Header.class));\n        inOrder.verify(subscriberPosition).setRelease(TERM_BUFFER_CAPACITY);\n    }\n\n    @Test\n    void shouldNotReadLastMessageWhenPadding()\n    {\n        final int msgLength = 1;\n        final int frameLength = HEADER_LENGTH + msgLength;\n        final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n        final int frameOffset = TERM_BUFFER_CAPACITY - alignedFrameLength;\n        final long currentPosition = LogBufferDescriptor.computePosition(\n            INITIAL_TERM_ID, frameOffset, POSITION_BITS_TO_SHIFT, INITIAL_TERM_ID);\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(frameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)PADDING_FRAME_TYPE);\n        when(subscriberPosition.getVolatile()).thenReturn(currentPosition);\n\n        final int readOutcome = TermReader.read(\n            termBuffer,\n            frameOffset,\n            handler,\n            Integer.MAX_VALUE,\n            header,\n            errorHandler,\n            currentPosition,\n            subscriberPosition);\n        assertEquals(0, readOutcome);\n\n        final InOrder inOrder = inOrder(termBuffer, subscriberPosition);\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        verify(handler, never()).onFragment(any(), anyInt(), anyInt(), any());\n        inOrder.verify(subscriberPosition).setRelease(TERM_BUFFER_CAPACITY);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/TermRebuilderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\nimport org.agrona.BitUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.mockito.Mockito.*;\nimport static io.aeron.logbuffer.FrameDescriptor.*;\n\nclass TermRebuilderTest\n{\n    private static final int TERM_BUFFER_CAPACITY = LogBufferDescriptor.TERM_MIN_LENGTH;\n\n    private final UnsafeBuffer termBuffer = mock(UnsafeBuffer.class);\n\n    @BeforeEach\n    void setUp()\n    {\n        when(termBuffer.capacity()).thenReturn(TERM_BUFFER_CAPACITY);\n    }\n\n    @Test\n    void shouldInsertIntoEmptyBuffer()\n    {\n        final UnsafeBuffer packet = new UnsafeBuffer(ByteBuffer.allocate(256));\n        final int termOffset = 0;\n        final int srcOffset = 0;\n        final int length = 256;\n        packet.putInt(srcOffset, length, LITTLE_ENDIAN);\n\n        TermRebuilder.insert(termBuffer, termOffset, packet, length);\n\n        final InOrder inOrder = inOrder(termBuffer);\n        inOrder.verify(termBuffer).putBytes(\n            termOffset + HEADER_LENGTH, packet, srcOffset + HEADER_LENGTH, length - HEADER_LENGTH);\n        inOrder.verify(termBuffer).putLong(termOffset + 24, packet.getLong(24));\n        inOrder.verify(termBuffer).putLong(termOffset + 16, packet.getLong(16));\n        inOrder.verify(termBuffer).putLong(termOffset + 8, packet.getLong(8));\n        inOrder.verify(termBuffer).putLongRelease(termOffset, packet.getLong(0));\n    }\n\n    @Test\n    void shouldInsertLastFrameIntoBuffer()\n    {\n        final int frameLength = BitUtil.align(256, FRAME_ALIGNMENT);\n        final int srcOffset = 0;\n        final int tail = TERM_BUFFER_CAPACITY - frameLength;\n        final int termOffset = tail;\n        final UnsafeBuffer packet = new UnsafeBuffer(ByteBuffer.allocate(frameLength));\n        packet.putShort(typeOffset(srcOffset), (short)PADDING_FRAME_TYPE, LITTLE_ENDIAN);\n        packet.putInt(srcOffset, frameLength, LITTLE_ENDIAN);\n\n        TermRebuilder.insert(termBuffer, termOffset, packet, frameLength);\n\n        verify(termBuffer).putBytes(\n            tail + HEADER_LENGTH, packet, srcOffset + HEADER_LENGTH, frameLength - HEADER_LENGTH);\n    }\n\n    @Test\n    void shouldFillSingleGap()\n    {\n        final int frameLength = 50;\n        final int alignedFrameLength = BitUtil.align(frameLength, FRAME_ALIGNMENT);\n        final int srcOffset = 0;\n        final int tail = alignedFrameLength;\n        final int termOffset = tail;\n        final UnsafeBuffer packet = new UnsafeBuffer(ByteBuffer.allocate(alignedFrameLength));\n\n        TermRebuilder.insert(termBuffer, termOffset, packet, alignedFrameLength);\n\n        verify(termBuffer).putBytes(\n            tail + HEADER_LENGTH, packet, srcOffset + HEADER_LENGTH, alignedFrameLength - HEADER_LENGTH);\n    }\n\n    @Test\n    void shouldFillAfterAGap()\n    {\n        final int frameLength = 50;\n        final int alignedFrameLength = BitUtil.align(frameLength, FRAME_ALIGNMENT);\n        final int srcOffset = 0;\n        final UnsafeBuffer packet = new UnsafeBuffer(ByteBuffer.allocate(alignedFrameLength));\n        final int termOffset = alignedFrameLength * 2;\n\n        TermRebuilder.insert(termBuffer, termOffset, packet, alignedFrameLength);\n\n        verify(termBuffer).putBytes(\n            (alignedFrameLength * 2) + HEADER_LENGTH,\n            packet,\n            srcOffset + HEADER_LENGTH,\n            alignedFrameLength - HEADER_LENGTH);\n    }\n\n    @Test\n    void shouldFillGapButNotMoveTailOrHwm()\n    {\n        final int frameLength = 50;\n        final int alignedFrameLength = BitUtil.align(frameLength, FRAME_ALIGNMENT);\n        final int srcOffset = 0;\n        final UnsafeBuffer packet = new UnsafeBuffer(ByteBuffer.allocate(alignedFrameLength));\n        final int termOffset = alignedFrameLength * 2;\n\n        TermRebuilder.insert(termBuffer, termOffset, packet, alignedFrameLength);\n\n        verify(termBuffer).putBytes(\n            (alignedFrameLength * 2) + HEADER_LENGTH,\n            packet,\n            srcOffset + HEADER_LENGTH,\n            alignedFrameLength - HEADER_LENGTH);\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/TermScannerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_DATA;\nimport static org.agrona.BitUtil.align;\n\nclass TermScannerTest\n{\n    private static final int TERM_BUFFER_CAPACITY = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int MTU_LENGTH = 1024;\n    private static final int HEADER_LENGTH = DataHeaderFlyweight.HEADER_LENGTH;\n\n    private final UnsafeBuffer termBuffer = mock(UnsafeBuffer.class);\n\n    @BeforeEach\n    void setUp()\n    {\n        when(termBuffer.capacity()).thenReturn(TERM_BUFFER_CAPACITY);\n    }\n\n    @Test\n    void shouldPackPaddingAndOffsetIntoResultingStatus()\n    {\n        final int padding = 77;\n        final int available = 65000;\n\n        final long scanOutcome = TermScanner.pack(padding, available);\n\n        assertEquals(padding, TermScanner.padding(scanOutcome));\n        assertEquals(available, TermScanner.available(scanOutcome));\n    }\n\n    @Test\n    void shouldReturnZeroOnEmptyLog()\n    {\n        final long scanOutcome = TermScanner.scanForAvailability(termBuffer, 0, MTU_LENGTH);\n        assertEquals(0, TermScanner.available(scanOutcome));\n        assertEquals(0, TermScanner.padding(scanOutcome));\n    }\n\n    @Test\n    void shouldScanSingleMessage()\n    {\n        final int msgLength = 1;\n        final int frameLength = HEADER_LENGTH + msgLength;\n        final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n        final int frameOffset = 0;\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(frameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)HDR_TYPE_DATA);\n\n        final long scanOutcome = TermScanner.scanForAvailability(termBuffer, frameOffset, MTU_LENGTH);\n        assertEquals(alignedFrameLength, TermScanner.available(scanOutcome));\n        assertEquals(0, TermScanner.padding(scanOutcome));\n\n        final InOrder inOrder = inOrder(termBuffer);\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        inOrder.verify(termBuffer).getShort(typeOffset(frameOffset));\n    }\n\n    @Test\n    void shouldFailToScanMessageLargerThanMaxLength()\n    {\n        final int msgLength = 1;\n        final int frameLength = HEADER_LENGTH + msgLength;\n        final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n        final int maxLength = alignedFrameLength - 1;\n        final int frameOffset = 0;\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(frameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)HDR_TYPE_DATA);\n\n        final long scanOutcome = TermScanner.scanForAvailability(termBuffer, frameOffset, maxLength);\n        assertEquals(-alignedFrameLength, TermScanner.available(scanOutcome));\n        assertEquals(-1, TermScanner.padding(scanOutcome));\n\n        final InOrder inOrder = inOrder(termBuffer);\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        inOrder.verify(termBuffer).getShort(typeOffset(frameOffset));\n    }\n\n    @Test\n    void shouldScanTwoMessagesThatFitInSingleMtu()\n    {\n        final int msgLength = 100;\n        final int frameLength = HEADER_LENGTH + msgLength;\n        final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n        int frameOffset = 0;\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(frameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(frameOffset + alignedFrameLength)).thenReturn(alignedFrameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset + alignedFrameLength))).thenReturn((short)HDR_TYPE_DATA);\n\n        final long scanOutcome = TermScanner.scanForAvailability(termBuffer, frameOffset, MTU_LENGTH);\n        assertEquals(alignedFrameLength * 2, TermScanner.available(scanOutcome));\n        assertEquals(0, TermScanner.padding(scanOutcome));\n\n        final InOrder inOrder = inOrder(termBuffer);\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        inOrder.verify(termBuffer).getShort(typeOffset(frameOffset));\n\n        frameOffset += alignedFrameLength;\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        inOrder.verify(termBuffer).getShort(typeOffset(frameOffset));\n    }\n\n    @Test\n    void shouldScanTwoMessagesAndStopAtMtuBoundary()\n    {\n        final int frameTwoLength = align(HEADER_LENGTH + 1, FRAME_ALIGNMENT);\n        final int frameOneLength = MTU_LENGTH - frameTwoLength;\n\n        int frameOffset = 0;\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(frameOneLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(frameOffset + frameOneLength)).thenReturn(frameTwoLength);\n        when(termBuffer.getShort(typeOffset(frameOffset + frameOneLength))).thenReturn((short)HDR_TYPE_DATA);\n\n        final long scanOutcome = TermScanner.scanForAvailability(termBuffer, frameOffset, MTU_LENGTH);\n        assertEquals(frameOneLength + frameTwoLength, TermScanner.available(scanOutcome));\n        assertEquals(0, TermScanner.padding(scanOutcome));\n\n        final InOrder inOrder = inOrder(termBuffer);\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        inOrder.verify(termBuffer).getShort(typeOffset(frameOffset));\n\n        frameOffset += frameOneLength;\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        inOrder.verify(termBuffer).getShort(typeOffset(frameOffset));\n    }\n\n    @Test\n    void shouldScanTwoMessagesAndStopAtSecondThatSpansMtu()\n    {\n        final int frameTwoLength = align(HEADER_LENGTH * 2, FRAME_ALIGNMENT);\n        final int frameOneLength = MTU_LENGTH - (frameTwoLength / 2);\n        int frameOffset = 0;\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(frameOneLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(frameOffset + frameOneLength)).thenReturn(frameTwoLength);\n        when(termBuffer.getShort(typeOffset(frameOffset + frameOneLength))).thenReturn((short)HDR_TYPE_DATA);\n\n        final long scanOutcome = TermScanner.scanForAvailability(termBuffer, frameOffset, MTU_LENGTH);\n        assertEquals(frameOneLength, TermScanner.available(scanOutcome));\n        assertEquals(0, TermScanner.padding(scanOutcome));\n\n        final InOrder inOrder = inOrder(termBuffer);\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        inOrder.verify(termBuffer).getShort(typeOffset(frameOffset));\n\n        frameOffset += frameOneLength;\n        inOrder.verify(termBuffer).getIntVolatile(frameOffset);\n        inOrder.verify(termBuffer).getShort(typeOffset(frameOffset));\n    }\n\n    @Test\n    void shouldScanLastFrameInBuffer()\n    {\n        final int alignedFrameLength = align(HEADER_LENGTH * 2, FRAME_ALIGNMENT);\n        final int frameOffset = TERM_BUFFER_CAPACITY - alignedFrameLength;\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(alignedFrameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)HDR_TYPE_DATA);\n\n        final long scanOutcome = TermScanner.scanForAvailability(termBuffer, frameOffset, MTU_LENGTH);\n        assertEquals(alignedFrameLength, TermScanner.available(scanOutcome));\n        assertEquals(0, TermScanner.padding(scanOutcome));\n    }\n\n    @Test\n    void shouldScanLastMessageInBufferPlusPadding()\n    {\n        final int alignedFrameLength = align(HEADER_LENGTH * 2, FRAME_ALIGNMENT);\n        final int paddingFrameLength = align(HEADER_LENGTH * 3, FRAME_ALIGNMENT);\n        final int frameOffset = TERM_BUFFER_CAPACITY - (alignedFrameLength + paddingFrameLength);\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(alignedFrameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(frameOffset + alignedFrameLength)).thenReturn(paddingFrameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset + alignedFrameLength))).thenReturn((short)PADDING_FRAME_TYPE);\n\n        final long scanOutcome = TermScanner.scanForAvailability(termBuffer, frameOffset, MTU_LENGTH);\n        assertEquals(alignedFrameLength + HEADER_LENGTH, TermScanner.available(scanOutcome));\n        assertEquals(paddingFrameLength - HEADER_LENGTH, TermScanner.padding(scanOutcome));\n    }\n\n    @Test\n    void shouldScanLastMessageInBufferMinusPaddingLimitedByMtu()\n    {\n        final int alignedFrameLength = align(HEADER_LENGTH, FRAME_ALIGNMENT);\n        final int frameOffset = TERM_BUFFER_CAPACITY - align(HEADER_LENGTH * 3, FRAME_ALIGNMENT);\n        final int mtu = alignedFrameLength + 8;\n\n        when(termBuffer.getIntVolatile(frameOffset)).thenReturn(alignedFrameLength);\n        when(termBuffer.getShort(typeOffset(frameOffset))).thenReturn((short)HDR_TYPE_DATA);\n        when(termBuffer.getIntVolatile(frameOffset + alignedFrameLength)).thenReturn(alignedFrameLength * 2);\n        when(termBuffer.getShort(typeOffset(frameOffset + alignedFrameLength))).thenReturn((short)PADDING_FRAME_TYPE);\n\n        final long scanOutcome = TermScanner.scanForAvailability(termBuffer, frameOffset, mtu);\n        assertEquals(alignedFrameLength, TermScanner.available(scanOutcome));\n        assertEquals(0, TermScanner.padding(scanOutcome));\n    }\n}"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/logbuffer/TermUnblockerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.logbuffer;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nimport static io.aeron.logbuffer.FrameDescriptor.termOffsetOffset;\nimport static io.aeron.logbuffer.FrameDescriptor.typeOffset;\nimport static io.aeron.logbuffer.TermUnblocker.Status.NO_ACTION;\nimport static io.aeron.logbuffer.TermUnblocker.Status.UNBLOCKED;\nimport static io.aeron.logbuffer.TermUnblocker.Status.UNBLOCKED_TO_END;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_PAD;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\n\nclass TermUnblockerTest\n{\n    private static final int TERM_BUFFER_CAPACITY = 64 * 1014;\n    private static final int TERM_ID = 7;\n\n    private final UnsafeBuffer mockTermBuffer = mock(UnsafeBuffer.class);\n    private final UnsafeBuffer mockLogMetaDataBuffer = mock(UnsafeBuffer.class);\n\n    @BeforeEach\n    void setUp()\n    {\n        when(mockTermBuffer.capacity()).thenReturn(TERM_BUFFER_CAPACITY);\n    }\n\n    @Test\n    void shouldTakeNoActionWhenMessageIsComplete()\n    {\n        final int termOffset = 0;\n        final int tailOffset = TERM_BUFFER_CAPACITY;\n        when(mockTermBuffer.getIntVolatile(termOffset)).thenReturn(HEADER_LENGTH);\n\n        assertEquals(\n            NO_ACTION, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n    }\n\n    @Test\n    void shouldTakeNoActionWhenNoUnblockedMessage()\n    {\n        final int termOffset = 0;\n        final int tailOffset = TERM_BUFFER_CAPACITY / 2;\n\n        assertEquals(\n            NO_ACTION, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n    }\n\n    @Test\n    void shouldPatchNonCommittedMessage()\n    {\n        final int termOffset = 0;\n        final int messageLength = HEADER_LENGTH * 4;\n        final int tailOffset = messageLength;\n\n        when(mockTermBuffer.getIntVolatile(termOffset)).thenReturn(-messageLength);\n\n        assertEquals(\n            UNBLOCKED, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n\n        final InOrder inOrder = inOrder(mockTermBuffer);\n        inOrder.verify(mockTermBuffer).putShort(typeOffset(termOffset), (short)HDR_TYPE_PAD, LITTLE_ENDIAN);\n        inOrder.verify(mockTermBuffer).putInt(termOffsetOffset(termOffset), termOffset, LITTLE_ENDIAN);\n        inOrder.verify(mockTermBuffer).putIntRelease(termOffset, messageLength);\n    }\n\n    @Test\n    void shouldPatchToEndOfPartition()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int termOffset = TERM_BUFFER_CAPACITY - messageLength;\n        final int tailOffset = TERM_BUFFER_CAPACITY;\n\n        when(mockTermBuffer.getIntVolatile(termOffset)).thenReturn(0);\n\n        assertEquals(\n            UNBLOCKED_TO_END,\n            TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n\n        final InOrder inOrder = inOrder(mockTermBuffer);\n        inOrder.verify(mockTermBuffer).putShort(typeOffset(termOffset), (short)HDR_TYPE_PAD, LITTLE_ENDIAN);\n        inOrder.verify(mockTermBuffer).putInt(termOffsetOffset(termOffset), termOffset, LITTLE_ENDIAN);\n        inOrder.verify(mockTermBuffer).putIntRelease(termOffset, messageLength);\n    }\n\n    @Test\n    void shouldScanForwardForNextCompleteMessage()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int termOffset = 0;\n        final int tailOffset = messageLength * 2;\n\n        when(mockTermBuffer.getIntVolatile(messageLength)).thenReturn(messageLength);\n\n        assertEquals(\n            UNBLOCKED, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n\n        final InOrder inOrder = inOrder(mockTermBuffer);\n        inOrder.verify(mockTermBuffer).putShort(typeOffset(termOffset), (short)HDR_TYPE_PAD, LITTLE_ENDIAN);\n        inOrder.verify(mockTermBuffer).putInt(termOffsetOffset(termOffset), termOffset, LITTLE_ENDIAN);\n        inOrder.verify(mockTermBuffer).putIntRelease(termOffset, messageLength);\n    }\n\n    @Test\n    void shouldScanForwardForNextNonCommittedMessage()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int termOffset = 0;\n        final int tailOffset = messageLength * 2;\n\n        when(mockTermBuffer.getIntVolatile(messageLength)).thenReturn(-messageLength);\n\n        assertEquals(\n            UNBLOCKED, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n\n        final InOrder inOrder = inOrder(mockTermBuffer);\n        inOrder.verify(mockTermBuffer).putShort(typeOffset(termOffset), (short)HDR_TYPE_PAD, LITTLE_ENDIAN);\n        inOrder.verify(mockTermBuffer).putInt(termOffsetOffset(termOffset), termOffset, LITTLE_ENDIAN);\n        inOrder.verify(mockTermBuffer).putIntRelease(termOffset, messageLength);\n    }\n\n    @Test\n    void shouldTakeNoActionIfMessageCompleteAfterScan()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int termOffset = 0;\n        final int tailOffset = messageLength * 2;\n\n        when(mockTermBuffer.getIntVolatile(termOffset))\n            .thenReturn(0)\n            .thenReturn(messageLength);\n\n        when(mockTermBuffer.getIntVolatile(messageLength))\n            .thenReturn(messageLength);\n\n        assertEquals(\n            NO_ACTION, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n    }\n\n    @Test\n    void shouldTakeNoActionIfMessageNonCommittedAfterScan()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int termOffset = 0;\n        final int tailOffset = messageLength * 2;\n\n        when(mockTermBuffer.getIntVolatile(termOffset))\n            .thenReturn(0)\n            .thenReturn(-messageLength);\n\n        when(mockTermBuffer.getIntVolatile(messageLength))\n            .thenReturn(messageLength);\n\n        assertEquals(\n            NO_ACTION, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n    }\n\n    @Test\n    void shouldTakeNoActionToEndOfPartitionIfMessageCompleteAfterScan()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int termOffset = TERM_BUFFER_CAPACITY - messageLength;\n        final int tailOffset = TERM_BUFFER_CAPACITY;\n\n        when(mockTermBuffer.getIntVolatile(termOffset))\n            .thenReturn(0)\n            .thenReturn(messageLength);\n\n        assertEquals(\n            NO_ACTION, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n    }\n\n    @Test\n    void shouldTakeNoActionToEndOfPartitionIfMessageNonCommittedAfterScan()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int termOffset = TERM_BUFFER_CAPACITY - messageLength;\n        final int tailOffset = TERM_BUFFER_CAPACITY;\n\n        when(mockTermBuffer.getIntVolatile(termOffset))\n            .thenReturn(0)\n            .thenReturn(-messageLength);\n\n        assertEquals(\n            NO_ACTION, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n    }\n\n    @Test\n    void shouldNotUnblockGapWithMessageRaceOnSecondMessageIncreasingTailThenInterrupting()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int termOffset = 0;\n        final int tailOffset = messageLength * 3;\n\n        when(mockTermBuffer.getIntVolatile(messageLength))\n            .thenReturn(0)\n            .thenReturn(messageLength);\n\n        when(mockTermBuffer.getIntVolatile(messageLength * 2))\n            .thenReturn(messageLength);\n\n        assertEquals(\n            NO_ACTION, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n    }\n\n    @Test\n    void shouldNotUnblockGapWithMessageRaceWhenScanForwardTakesAnInterrupt()\n    {\n        final int messageLength = HEADER_LENGTH * 4;\n        final int termOffset = 0;\n        final int tailOffset = messageLength * 3;\n\n        when(mockTermBuffer.getIntVolatile(messageLength))\n            .thenReturn(0)\n            .thenReturn(messageLength);\n\n        when(mockTermBuffer.getIntVolatile(messageLength + HEADER_LENGTH))\n            .thenReturn(7);\n\n        assertEquals(\n            NO_ACTION, TermUnblocker.unblock(mockLogMetaDataBuffer, mockTermBuffer, termOffset, tailOffset, TERM_ID));\n    }\n}"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/protocol/ErrorFlyweightTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.junit.jupiter.api.Test;\n\nimport static io.aeron.protocol.ErrorFlyweight.HAS_GROUP_ID_FLAG;\nimport static io.aeron.protocol.ErrorFlyweight.MAX_ERROR_FRAME_LENGTH;\nimport static java.nio.ByteBuffer.allocate;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\nclass ErrorFlyweightTest\n{\n    @Test\n    void shouldCorrectlySetFlagsForGroupTag()\n    {\n        final ErrorFlyweight errorFlyweight = new ErrorFlyweight(allocate(MAX_ERROR_FRAME_LENGTH));\n\n        assertEquals(0, errorFlyweight.flags());\n        errorFlyweight.groupTag(10L);\n        assertEquals(HAS_GROUP_ID_FLAG, errorFlyweight.flags());\n\n        errorFlyweight.groupTag(null);\n        assertNotEquals(HAS_GROUP_ID_FLAG, errorFlyweight.flags());\n    }\n\n    @Test\n    void shouldCorrectlyUpdateExistingFlagsForGroupTag()\n    {\n        final ErrorFlyweight errorFlyweight = new ErrorFlyweight(allocate(MAX_ERROR_FRAME_LENGTH));\n\n        final short initialFlags = (short)0x3;\n\n        errorFlyweight.flags(initialFlags);\n        assertEquals(initialFlags, errorFlyweight.flags());\n        errorFlyweight.groupTag(10L);\n        assertEquals(HAS_GROUP_ID_FLAG, HAS_GROUP_ID_FLAG & errorFlyweight.flags());\n        assertEquals(initialFlags, initialFlags & errorFlyweight.flags());\n\n        errorFlyweight.groupTag(null);\n        assertEquals(0, HAS_GROUP_ID_FLAG & errorFlyweight.flags());\n        assertEquals(initialFlags, errorFlyweight.flags());\n    }\n}"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/protocol/HeaderFlyweightTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.protocol;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass HeaderFlyweightTest\n{\n    @Test\n    void shouldConvertFlags()\n    {\n        final short flags = 0b01101000;\n\n        final char[] flagsAsChars = HeaderFlyweight.flagsToChars(flags);\n        assertEquals(\"01101000\", new String(flagsAsChars));\n    }\n\n    @Test\n    void shouldAppendFlags()\n    {\n        final short flags = 0b01100000;\n        final StringBuilder builder = new StringBuilder();\n\n        HeaderFlyweight.appendFlagsAsChars(flags, builder);\n        assertEquals(\"01100000\", builder.toString());\n    }\n}\n"
  },
  {
    "path": "aeron-client/src/test/java/io/aeron/security/AuthorisationServiceTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport org.agrona.ErrorHandler;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.security.AuthorisationService.ALLOW_ALL;\nimport static io.aeron.security.AuthorisationService.DENY_ALL;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verifyNoInteractions;\n\nclass AuthorisationServiceTest\n{\n    @Test\n    void shouldAllowAnyCommandIfAllowAllIsUsed()\n    {\n        final byte[] encodedCredentials = { 0x1, 0x2, 0x3 };\n        final ErrorHandler errorHandler = mock(ErrorHandler.class);\n        final int protocolId = 77;\n        final int actionId = ThreadLocalRandom.current().nextInt();\n\n        assertTrue(ALLOW_ALL.isAuthorised(protocolId, actionId, null, encodedCredentials));\n        verifyNoInteractions(errorHandler);\n    }\n\n    @Test\n    void shouldForbidAllCommandsIfDenyAllIsUsed()\n    {\n        final byte[] encodedCredentials = { 0x4, 0x5, 0x6 };\n        final ErrorHandler errorHandler = mock(ErrorHandler.class);\n        final int protocolId = 77;\n        final int actionId = ThreadLocalRandom.current().nextInt();\n\n        assertFalse(DENY_ALL.isAuthorised(protocolId, actionId, null, encodedCredentials));\n        verifyNoInteractions(errorHandler);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/README.md",
    "content": "Aeron Cluster\n===\n\n[![Javadocs](http://www.javadoc.io/badge/io.aeron/aeron-all.svg)](http://www.javadoc.io/doc/io.aeron/aeron-all)\n\nAeron Cluster provides support for fault-tolerant services as replicated state machines based on the \n[Raft](https://raft.github.io/) consensus algorithm.\n\nThe purpose of Aeron Cluster is to aggregate and sequence streams from cluster clients into a single log. A number of\nnodes will replicate and archive the log to achieve fault tolerance. Cluster services deterministically process the log\nand respond to cluster clients.\n\nAeron Cluster works on the concept of a strong leader. The leader sequences the log and is responsible for replicating\nthe log to other cluster members known as followers.\n\nA number of components make up Aeron Cluster. Central is the Consensus Module which sequences the log and\ncoordinates consensus for the recording of the sequenced log to persistent storage, and the services consuming the log\nacross cluster members. Aeron Archive records the log to durable storage. Services consume the log once a majority of\nthe cluster members have safely recorded the log to durable storage.\n\nTo enable fast recovery, the services and consensus module can take a snapshots of their state as of a given log\nposition. Snapshots enable recovery by loading the most recent snapshot and replaying logs from that point forward.\nThe Archive records snapshots for local, and remote, replay thus avoiding the need for a distributed\nfile system.\n\nUnique features to Aeron Cluster include support for reliable distributed timers, inter-service messaging, remote data\ncentre backup, and unparalleled performance.\n\n[Cluster Tutorial](https://github.com/aeron-io/aeron/wiki/Cluster-Tutorial) is a good place to start.\n\nUsage\n=====\n\nThe cluster can run in various configurations:\n\n - **Single Node:** For development, debugging, or when a sequenced and archived log on a single node is sufficient.\n - **Appointed Leader:** A leader of the cluster can be appointed via configuration without requiring an election.\n    In the event of a leader failure then a follower will have to be manually appointed the new leader. This is not the\n    recommended way to use Cluster. Automatic elections are more reliable.\n - **Automatic Elections:** Automatic elections (default) can be enabled to have a leader elected at random from the\n    members with the most up-to-date log.\n       \nThe majority of cluster members determine consensus. Clusters should typically be 3 or 5 in population size. However,\n2 node clusters are supported whereby both members must agree the log and in the event of failure the remaining member\nmust be manually reconfigured as a single node cluster to progress.\n\nAeron Cluster Protocol\n=====\n\nMessages are specified using [SBE](https://github.com/aeron-io/simple-binary-encoding) in this schema\n[aeron-cluster-codecs.xml](https://github.com/aeron-io/aeron/blob/master/aeron-cluster/src/main/resources/cluster/aeron-cluster-codecs.xml).\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/AllowBackupAndStandbyAuthorisationService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.codecs.BackupQueryDecoder;\nimport io.aeron.cluster.codecs.HeartbeatRequestDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.StandbySnapshotDecoder;\nimport io.aeron.security.AuthorisationService;\n\n/**\n * An {@link AuthorisationService} that allows the actions required by Cluster Backup\n * and Aeron Cluster Standby.\n */\npublic enum AllowBackupAndStandbyAuthorisationService implements AuthorisationService\n{\n    /**\n     * As there is no instance state then this object can be used to save on allocation.\n     */\n    INSTANCE;\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isAuthorised(\n        final int protocolId,\n        final int actionId,\n        final Object type,\n        final byte[] encodedPrincipal)\n    {\n        return MessageHeaderDecoder.SCHEMA_ID == protocolId &&\n            (BackupQueryDecoder.TEMPLATE_ID == actionId || HeartbeatRequestDecoder.TEMPLATE_ID == actionId ||\n                StandbySnapshotDecoder.TEMPLATE_ID == actionId);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/AppVersionValidator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport org.agrona.SemanticVersion;\n\n/**\n * Class to be used for determining AppVersion compatibility.\n * <p>\n * Default is to use {@link org.agrona.SemanticVersion} major version for checking compatibility.\n */\npublic final class AppVersionValidator\n{\n    private AppVersionValidator()\n    {\n    }\n\n    /**\n     * Singleton instance of {@link AppVersionValidator} version which can be used to avoid allocation.\n     */\n    public static final AppVersionValidator SEMANTIC_VERSIONING_VALIDATOR = new AppVersionValidator();\n\n    /**\n     * Check version compatibility between configured context appVersion and appVersion in\n     * new leadership term or snapshot.\n     *\n     * @param contextAppVersion   configured appVersion value from context.\n     * @param appVersionUnderTest to check against configured appVersion.\n     * @return true for compatible or false for not compatible.\n     */\n    public boolean isVersionCompatible(final int contextAppVersion, final int appVersionUnderTest)\n    {\n        return SemanticVersion.major(contextAppVersion) == SemanticVersion.major(appVersionUnderTest);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterBackup.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.Counter;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.mark.ClusterComponentType;\nimport io.aeron.cluster.service.ClusterCounters;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.security.CredentialsSupplier;\nimport io.aeron.security.NullCredentialsSupplier;\nimport io.aeron.version.Versioned;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.MarkFile;\nimport org.agrona.Strings;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.io.File;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.util.Arrays;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static io.aeron.AeronCounters.CLUSTER_BACKUP_SNAPSHOT_RETRIEVE_COUNT_TYPE_ID;\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.CommonContext.driverFilePageSize;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SERVICE_ID;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.LIVENESS_TIMEOUT_MS;\nimport static java.lang.System.getProperty;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.agrona.SystemUtil.getDurationInNanos;\n\n/**\n * Backup component which can run remote from a cluster which polls for snapshots and replicates the log.\n */\n@Versioned\npublic final class ClusterBackup implements AutoCloseable\n{\n    /**\n     * The type id of the {@link Counter} used for the backup state.\n     */\n    public static final int BACKUP_STATE_TYPE_ID = AeronCounters.CLUSTER_BACKUP_STATE_TYPE_ID;\n\n    /**\n     * The type id of the {@link Counter} used for the live log position counter.\n     */\n    public static final int LIVE_LOG_POSITION_TYPE_ID = AeronCounters.CLUSTER_BACKUP_LIVE_LOG_POSITION_TYPE_ID;\n\n    /**\n     * The type id of the {@link Counter} used for the next query deadline counter.\n     */\n    public static final int QUERY_DEADLINE_TYPE_ID = AeronCounters.CLUSTER_BACKUP_QUERY_DEADLINE_TYPE_ID;\n\n    /**\n     * The type id of the {@link Counter} used for keeping track of the number of errors that have occurred.\n     */\n    public static final int CLUSTER_BACKUP_ERROR_COUNT_TYPE_ID = AeronCounters.CLUSTER_BACKUP_ERROR_COUNT_TYPE_ID;\n\n    /**\n     * State of the cluster backup state machine.\n     */\n    public enum State\n    {\n        /**\n         * Query leader for current status for backup.\n         */\n        BACKUP_QUERY(0),\n\n        /**\n         * Retrieve a copy of the latest snapshot from the leader.\n         */\n        SNAPSHOT_RETRIEVE(1),\n\n        /**\n         * Setup recording for live log.\n         */\n        LIVE_LOG_RECORD(2),\n\n        /**\n         * Replay the current live log since snapshot and join it.\n         */\n        LIVE_LOG_REPLAY(3),\n\n        /**\n         * Update the local {@link RecordingLog} for recovery.\n         */\n        UPDATE_RECORDING_LOG(4),\n\n        /**\n         * Back up live log and track progress until next query deadline is reached.\n         */\n        BACKING_UP(5),\n\n        /**\n         * On error or progress stall the backup is reset and started over again.\n         */\n        RESET_BACKUP(6),\n\n        /**\n         * The backup is complete and closed.\n         */\n        CLOSED(7);\n\n        static final State[] STATES = values();\n\n        private final int code;\n\n        State(final int code)\n        {\n            if (code != ordinal())\n            {\n                throw new IllegalArgumentException(name() + \" - code must equal ordinal value: code=\" + code);\n            }\n\n            this.code = code;\n        }\n\n        /**\n         * Code which represents the {@link State} as an int.\n         *\n         * @return code which represents the {@link State} as an int.\n         */\n        public int code()\n        {\n            return code;\n        }\n\n        /**\n         * Get the {@link State} encoded in an {@link AtomicCounter}.\n         *\n         * @param counter to get the current state for.\n         * @return the state for the {@link ClusterBackup}.\n         * @throws ClusterException if the counter is not one of the valid values.\n         */\n        public static State get(final AtomicCounter counter)\n        {\n            if (counter.isClosed())\n            {\n                return CLOSED;\n            }\n\n            return get(counter.get());\n        }\n\n        /**\n         * Get the {@link State} with matching {@link #code()}.\n         *\n         * @param code to lookup.\n         * @return the {@link State} matching {@link #code()}.\n         */\n        public static State get(final long code)\n        {\n            if (code < 0 || code > (STATES.length - 1))\n            {\n                throw new ClusterException(\"invalid state counter code: \" + code);\n            }\n\n            return STATES[(int)code];\n        }\n    }\n\n    /**\n     * Defines the type of node that this will receive log data from.\n     */\n    public enum SourceType\n    {\n        /**\n         * Receive from any node in the cluster.\n         */\n        ANY,\n        /**\n         * Only receive data from the leader node.\n         */\n        LEADER,\n        /**\n         * Receive data from any node that is not the leader.\n         */\n        FOLLOWER\n    }\n\n    private final ClusterBackup.Context ctx;\n    private final AgentInvoker agentInvoker;\n    private final AgentRunner agentRunner;\n\n    private ClusterBackup(final ClusterBackup.Context ctx)\n    {\n        try\n        {\n            ctx.conclude();\n            this.ctx = ctx;\n\n            final ClusterBackupAgent agent = new ClusterBackupAgent(ctx);\n\n            if (ctx.useAgentInvoker())\n            {\n                agentRunner = null;\n                agentInvoker = new AgentInvoker(ctx.errorHandler(), ctx.errorCounter(), agent);\n            }\n            else\n            {\n                agentRunner = new AgentRunner(ctx.idleStrategy(), ctx.errorHandler(), ctx.errorCounter(), agent);\n                agentInvoker = null;\n            }\n        }\n        catch (final ConcurrentConcludeException ex)\n        {\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietClose(ctx::close);\n            throw ex;\n        }\n    }\n\n    /**\n     * Launch an {@link ClusterBackup} using a default configuration.\n     *\n     * @return a new instance of an {@link ClusterBackup}.\n     */\n    public static ClusterBackup launch()\n    {\n        return launch(new ClusterBackup.Context());\n    }\n\n    /**\n     * Launch an {@link ClusterBackup} by providing a configuration context.\n     *\n     * @param ctx for the configuration parameters.\n     * @return a new instance of an {@link ClusterBackup}.\n     */\n    public static ClusterBackup launch(final ClusterBackup.Context ctx)\n    {\n        final ClusterBackup clusterBackup = new ClusterBackup(ctx);\n        if (null != clusterBackup.agentRunner)\n        {\n            AgentRunner.startOnThread(clusterBackup.agentRunner, ctx.threadFactory());\n        }\n        else\n        {\n            clusterBackup.agentInvoker.start();\n        }\n\n        return clusterBackup;\n    }\n\n    /**\n     * Get the {@link ClusterBackup.Context} that is used by this {@link ClusterBackup}.\n     *\n     * @return the {@link ClusterBackup.Context} that is used by this {@link ClusterBackup}.\n     */\n    public ClusterBackup.Context context()\n    {\n        return ctx;\n    }\n\n    /**\n     * Get the {@link AgentInvoker} for the cluster backup.\n     *\n     * @return the {@link AgentInvoker} for the cluster backup.\n     */\n    public AgentInvoker conductorAgentInvoker()\n    {\n        return agentInvoker;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        final CountedErrorHandler countedErrorHandler = ctx.countedErrorHandler();\n        CloseHelper.close(countedErrorHandler, agentRunner);\n        CloseHelper.close(countedErrorHandler, agentInvoker);\n    }\n\n    /**\n     * Configuration options for {@link ClusterBackup} with defaults and constants for system properties lookup.\n     */\n    @Config(existsInC = false)\n    public static final class Configuration\n    {\n        private Configuration()\n        {\n        }\n\n        /**\n         * Channel template used for catchup and replication of log and snapshots.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String CLUSTER_BACKUP_CATCHUP_ENDPOINT_PROP_NAME = \"aeron.cluster.backup.catchup.endpoint\";\n\n        /**\n         * Channel template used for catchup and replication of log and snapshots.\n         */\n        @Config\n        public static final String CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME = \"aeron.cluster.backup.catchup.channel\";\n\n        /**\n         * Default channel template used for catchup and replication of log and snapshots.\n         */\n        @Config\n        public static final String CLUSTER_BACKUP_CATCHUP_CHANNEL_DEFAULT =\n            \"aeron:udp?alias=backup|cc=cubic|so-sndbuf=512k|so-rcvbuf=512k|rcv-wnd=512k\";\n\n        /**\n         * Interval at which a cluster backup will send backup queries.\n         */\n        @Config\n        public static final String CLUSTER_BACKUP_INTERVAL_PROP_NAME = \"aeron.cluster.backup.interval\";\n\n        /**\n         * Default interval at which a cluster backup will send backup queries.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 60L * 60 * 1000 * 1000 * 1000)\n        public static final long CLUSTER_BACKUP_INTERVAL_DEFAULT_NS = TimeUnit.HOURS.toNanos(1);\n\n        /**\n         * Timeout within which a cluster backup will expect a response from a backup query.\n         */\n        @Config\n        public static final String CLUSTER_BACKUP_RESPONSE_TIMEOUT_PROP_NAME = \"aeron.cluster.backup.response.timeout\";\n\n        /**\n         * Default timeout within which a cluster backup will expect a response from a backup query.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 5L * 1000 * 1000 * 1000)\n        public static final long CLUSTER_BACKUP_RESPONSE_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5);\n\n        /**\n         * Timeout within which a cluster backup will expect progress.\n         */\n        @Config\n        public static final String CLUSTER_BACKUP_PROGRESS_TIMEOUT_PROP_NAME = \"aeron.cluster.backup.progress.timeout\";\n\n        /**\n         * Interval at which the cluster backup is re-initialised after an exception has been thrown.\n         */\n        @Config\n        public static final String CLUSTER_BACKUP_COOL_DOWN_INTERVAL_PROP_NAME =\n            \"aeron.cluster.backup.cool.down.interval\";\n\n        /**\n         * Default interval at which the cluster back is re-initialised after an exception has been thrown.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 30L * 1000 * 1000 * 1000)\n        public static final long CLUSTER_BACKUP_COOL_DOWN_INTERVAL_DEFAULT_NS = TimeUnit.SECONDS.toNanos(30);\n\n        /**\n         * Default timeout within which a cluster backup will expect progress.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000)\n        public static final long CLUSTER_BACKUP_PROGRESS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10);\n\n        /**\n         * The source type used for the cluster backup. Should match on of the {@link SourceType} enum values.\n         */\n        @Config\n        public static final String CLUSTER_BACKUP_SOURCE_TYPE_PROP_NAME = \"aeron.cluster.backup.source.type\";\n\n        /**\n         * Default source type to receive log traffic from.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"ANY\")\n        public static final String CLUSTER_BACKUP_SOURCE_TYPE_DEFAULT = SourceType.ANY.name();\n\n        /**\n         * The value of system property {@link #CLUSTER_BACKUP_CATCHUP_ENDPOINT_PROP_NAME} if set, otherwise it will\n         * try to derive the catchup endpoint from {@link ConsensusModule.Configuration#clusterMembers()} and\n         * {@link ConsensusModule.Configuration#clusterMemberId()}. Failing that null will be returned.\n         *\n         * @return system property {@link #CLUSTER_BACKUP_CATCHUP_ENDPOINT_PROP_NAME}, the derived value, or null.\n         */\n        public static String catchupEndpoint()\n        {\n            String configuredCatchupEndpoint = System.getProperty(CLUSTER_BACKUP_CATCHUP_ENDPOINT_PROP_NAME);\n\n            if (null == configuredCatchupEndpoint && null != ConsensusModule.Configuration.clusterMembers())\n            {\n                final ClusterMember member = ClusterMember.determineMember(\n                    ClusterMember.parse(ConsensusModule.Configuration.clusterMembers()),\n                    ConsensusModule.Configuration.clusterMemberId(),\n                    ConsensusModule.Configuration.memberEndpoints());\n\n                configuredCatchupEndpoint = member.catchupEndpoint();\n            }\n\n            return configuredCatchupEndpoint;\n        }\n\n        /**\n         * The value {@link #CLUSTER_BACKUP_CATCHUP_CHANNEL_DEFAULT} or system property\n         * {@link #CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #CLUSTER_BACKUP_CATCHUP_CHANNEL_DEFAULT} or system property\n         * {@link #CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME} if set.\n         */\n        public static String catchupChannel()\n        {\n            return System.getProperty(CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME, CLUSTER_BACKUP_CATCHUP_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value of system property {@link ConsensusModule.Configuration#consensusChannel()} if set. If that channel\n         * does not have an endpoint set, then this will try to derive one using\n         * {@link ConsensusModule.Configuration#clusterMembers()} and\n         * {@link ConsensusModule.Configuration#clusterMemberId()}.\n         *\n         * @return system property {@link #CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME}, the derived value, or null.\n         */\n        public static String consensusChannel()\n        {\n            String consensusChannel = ConsensusModule.Configuration.consensusChannel();\n\n            if (null != consensusChannel && null != ConsensusModule.Configuration.clusterMembers())\n            {\n                final ChannelUri consensusUri = ChannelUri.parse(consensusChannel);\n                if (!consensusUri.containsKey(ENDPOINT_PARAM_NAME))\n                {\n                    final ClusterMember member = ClusterMember.determineMember(\n                        ClusterMember.parse(ConsensusModule.Configuration.clusterMembers()),\n                        ConsensusModule.Configuration.clusterMemberId(),\n                        ConsensusModule.Configuration.memberEndpoints());\n\n                    consensusUri.put(ENDPOINT_PARAM_NAME, member.consensusEndpoint());\n                    consensusChannel = consensusUri.toString();\n                }\n            }\n\n            return consensusChannel;\n        }\n\n        /**\n         * Interval at which a cluster backup will send backup queries.\n         *\n         * @return Interval at which a cluster backup will send backup queries.\n         * @see #CLUSTER_BACKUP_INTERVAL_PROP_NAME\n         */\n        public static long clusterBackupIntervalNs()\n        {\n            return getDurationInNanos(CLUSTER_BACKUP_INTERVAL_PROP_NAME, CLUSTER_BACKUP_INTERVAL_DEFAULT_NS);\n        }\n\n        /**\n         * Timeout within which a cluster backup will expect a response from a backup query.\n         *\n         * @return timeout within which a cluster backup will expect a response from a backup query.\n         * @see #CLUSTER_BACKUP_RESPONSE_TIMEOUT_PROP_NAME\n         */\n        public static long clusterBackupResponseTimeoutNs()\n        {\n            return getDurationInNanos(\n                CLUSTER_BACKUP_RESPONSE_TIMEOUT_PROP_NAME, CLUSTER_BACKUP_RESPONSE_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * Timeout within which a cluster backup will expect progress.\n         *\n         * @return timeout within which a cluster backup will expect progress.\n         * @see #CLUSTER_BACKUP_PROGRESS_TIMEOUT_PROP_NAME\n         */\n        public static long clusterBackupProgressTimeoutNs()\n        {\n            return getDurationInNanos(\n                CLUSTER_BACKUP_PROGRESS_TIMEOUT_PROP_NAME, CLUSTER_BACKUP_PROGRESS_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * Interval at which the cluster backup is re-initialised after an exception has been thrown.\n         *\n         * @return interval at which the cluster backup is re-initialised after an exception has been thrown.\n         * @see #CLUSTER_BACKUP_COOL_DOWN_INTERVAL_PROP_NAME\n         */\n        public static long clusterBackupCoolDownIntervalNs()\n        {\n            return getDurationInNanos(\n                CLUSTER_BACKUP_COOL_DOWN_INTERVAL_PROP_NAME, CLUSTER_BACKUP_COOL_DOWN_INTERVAL_DEFAULT_NS);\n        }\n\n        /**\n         * Returns the string representation of the {@link SourceType} that this backup instance will use depending on\n         * the value of the {@link #CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME} system property if set or\n         * {@link #CLUSTER_BACKUP_SOURCE_TYPE_DEFAULT} if not.\n         *\n         * @return the configured source type.\n         */\n        public static String clusterBackupSourceType()\n        {\n            return System.getProperty(CLUSTER_BACKUP_SOURCE_TYPE_PROP_NAME, CLUSTER_BACKUP_SOURCE_TYPE_DEFAULT);\n        }\n\n        /**\n         * Determines what position to start from when backing up the log from the cluster.\n         */\n        public enum ReplayStart\n        {\n            /**\n             * Start from the earliest available position in the cluster log. May not be 0 if the cluster log was\n             * truncated at some point.\n             */\n            BEGINNING,\n            /**\n             * Start backing up from the log position of the most recent snapshot in the log.\n             */\n            LATEST_SNAPSHOT\n        }\n\n        /**\n         * Default value for the initial cluster replay start.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"BEGINNING\")\n        public static final ReplayStart CLUSTER_INITIAL_REPLAY_START_DEFAULT = ReplayStart.BEGINNING;\n\n        /**\n         * Property name for setting the cluster replay start.\n         */\n        @Config\n        public static final String CLUSTER_INITIAL_REPLAY_START_PROP_NAME = \"cluster.backup.initial.replay.start\";\n\n        /**\n         * Get the initial value for the cluster relay start.\n         *\n         * @return enum to determine where to start replaying the log from.\n         * @see #CLUSTER_INITIAL_REPLAY_START_PROP_NAME\n         */\n        public static ReplayStart clusterInitialReplayStart()\n        {\n            final String propertyValue = getProperty(CLUSTER_INITIAL_REPLAY_START_PROP_NAME);\n            if (null == propertyValue)\n            {\n                return CLUSTER_INITIAL_REPLAY_START_DEFAULT;\n            }\n\n            return ReplayStart.valueOf(propertyValue);\n        }\n    }\n\n    /**\n     * Context for overriding default configuration for {@link ClusterBackup}.\n     */\n    public static class Context implements Cloneable\n    {\n        private static final VarHandle IS_CONCLUDED_VH;\n\n        static\n        {\n            try\n            {\n                IS_CONCLUDED_VH = MethodHandles.lookup().findVarHandle(Context.class, \"isConcluded\", boolean.class);\n            }\n            catch (final ReflectiveOperationException ex)\n            {\n                throw new ExceptionInInitializerError(ex);\n            }\n        }\n\n        private volatile boolean isConcluded;\n        private boolean ownsAeronClient = false;\n        private String aeronDirectoryName = CommonContext.getAeronDirectoryName();\n        private Aeron aeron;\n\n        private int clusterId = ClusteredServiceContainer.Configuration.clusterId();\n        private String consensusChannel = Configuration.consensusChannel();\n        private int consensusStreamId = ConsensusModule.Configuration.consensusStreamId();\n        private int consensusModuleSnapshotStreamId = ConsensusModule.Configuration.snapshotStreamId();\n        private int serviceSnapshotStreamId = ClusteredServiceContainer.Configuration.snapshotStreamId();\n        private int logStreamId = ConsensusModule.Configuration.logStreamId();\n        private String catchupEndpoint = Configuration.catchupEndpoint();\n        private String catchupChannel = Configuration.catchupChannel();\n\n        private long clusterBackupIntervalNs = Configuration.clusterBackupIntervalNs();\n        private long clusterBackupResponseTimeoutNs = Configuration.clusterBackupResponseTimeoutNs();\n        private long clusterBackupProgressTimeoutNs = Configuration.clusterBackupProgressTimeoutNs();\n        private long clusterBackupCoolDownIntervalNs = Configuration.clusterBackupCoolDownIntervalNs();\n        private int errorBufferLength = ConsensusModule.Configuration.errorBufferLength();\n\n        private boolean deleteDirOnStart = false;\n        private boolean useAgentInvoker = false;\n        private String clusterDirectoryName = ClusteredServiceContainer.Configuration.clusterDirName();\n        private File clusterDir;\n        private File markFileDir;\n        private ClusterMarkFile markFile;\n        private String clusterConsensusEndpoints = ConsensusModule.Configuration.clusterConsensusEndpoints();\n        private ThreadFactory threadFactory;\n        private EpochClock epochClock;\n        private Supplier<IdleStrategy> idleStrategySupplier;\n\n        private DistinctErrorLog errorLog;\n        private ErrorHandler errorHandler;\n        private AtomicCounter errorCounter;\n        private CountedErrorHandler countedErrorHandler;\n        private Counter stateCounter;\n        private Counter liveLogPositionCounter;\n        private Counter nextQueryDeadlineMsCounter;\n        private Counter snapshotRetrieveCounter;\n\n        private AeronArchive.Context archiveContext;\n        private AeronArchive.Context clusterArchiveContext;\n        private Runnable terminationHook;\n        private ClusterBackupEventsListener eventsListener;\n        private CredentialsSupplier credentialsSupplier;\n        private String sourceType = Configuration.clusterBackupSourceType();\n        private long replicationProgressTimeoutNs = ConsensusModule.Configuration.replicationProgressTimeoutNs();\n        private long replicationProgressIntervalNs = ConsensusModule.Configuration.replicationProgressIntervalNs();\n        private Configuration.ReplayStart initialReplayStart = Configuration.clusterInitialReplayStart();\n\n        /**\n         * Construct a Context using default values and loading from system properties.\n         */\n        public Context()\n        {\n        }\n\n        /**\n         * Perform a shallow copy of the object.\n         *\n         * @return a shallow copy of the object.\n         */\n        public Context clone()\n        {\n            try\n            {\n                return (Context)super.clone();\n            }\n            catch (final CloneNotSupportedException ex)\n            {\n                throw new RuntimeException(ex);\n            }\n        }\n\n        /**\n         * Conclude configuration by setting up defaults when specifics are not provided.\n         */\n        @SuppressWarnings(\"MethodLength\")\n        public void conclude()\n        {\n            final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n\n            if ((boolean)IS_CONCLUDED_VH.getAndSet(this, true))\n            {\n                throw new ConcurrentConcludeException();\n            }\n\n            if (null == clusterDir)\n            {\n                clusterDir = new File(clusterDirectoryName);\n            }\n            else\n            {\n                clusterDirectoryName = clusterDir.getPath();\n            }\n\n            if (deleteDirOnStart)\n            {\n                IoUtil.delete(clusterDir, false);\n            }\n\n            if (null == catchupEndpoint)\n            {\n                throw new ClusterException(\"ClusterBackup.Context.catchupEndpoint must be set\");\n            }\n\n            if (!clusterDir.exists() && !clusterDir.mkdirs())\n            {\n                throw new ClusterException(\"failed to create cluster dir: \" + clusterDir.getAbsolutePath());\n            }\n\n            if (null == markFileDir)\n            {\n                final String dir = ClusteredServiceContainer.Configuration.markFileDir();\n                markFileDir = Strings.isEmpty(dir) ? clusterDir : new File(dir);\n            }\n\n            if (!markFileDir.exists() && !markFileDir.mkdirs())\n            {\n                throw new ArchiveException(\"failed to create mark file dir: \" + markFileDir.getAbsolutePath());\n            }\n\n            if (null == epochClock)\n            {\n                epochClock = SystemEpochClock.INSTANCE;\n            }\n\n            if (Aeron.NULL_VALUE == replicationProgressIntervalNs)\n            {\n                replicationProgressIntervalNs = Math.max(replicationProgressTimeoutNs / 10, 1);\n            }\n\n            if (null == markFile)\n            {\n                final int filePageSize = null != aeron ? aeron.context().filePageSize() :\n                    driverFilePageSize(new File(aeronDirectoryName), epochClock, new CommonContext().driverTimeoutMs());\n                markFile = new ClusterMarkFile(\n                    new File(markFileDir, ClusterMarkFile.FILENAME),\n                    ClusterComponentType.BACKUP,\n                    errorBufferLength,\n                    epochClock,\n                    LIVENESS_TIMEOUT_MS,\n                    filePageSize);\n            }\n\n            MarkFile.ensureMarkFileLink(\n                clusterDir,\n                new File(markFile.parentDirectory(), ClusterMarkFile.FILENAME),\n                ClusterMarkFile.LINK_FILENAME);\n\n            if (null == errorLog)\n            {\n                errorLog = new DistinctErrorLog(markFile.errorBuffer(), epochClock, US_ASCII);\n            }\n\n            errorHandler = CommonContext.setupErrorHandler(errorHandler, errorLog);\n\n            final String clientName = \"cluster-backup clusterId=\" + clusterId;\n            if (null == aeron)\n            {\n                ownsAeronClient = true;\n\n                aeron = Aeron.connect(\n                    new Aeron.Context()\n                        .aeronDirectoryName(aeronDirectoryName)\n                        .errorHandler(errorHandler)\n                        .epochClock(epochClock)\n                        .useConductorAgentInvoker(true)\n                        .awaitingIdleStrategy(YieldingIdleStrategy.INSTANCE)\n                        .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE)\n                        .clientLock(NoOpLock.INSTANCE)\n                        .clientName(clientName));\n\n                if (null == errorCounter)\n                {\n                    errorCounter = ClusterCounters.allocateVersioned(\n                        aeron,\n                        buffer,\n                        \"ClusterBackup Errors\",\n                        CLUSTER_BACKUP_ERROR_COUNT_TYPE_ID,\n                        clusterId,\n                        ClusterBackupVersion.VERSION,\n                        ClusterBackupVersion.GIT_SHA);\n                }\n            }\n\n            if (!(aeron.context().subscriberErrorHandler() instanceof RethrowingErrorHandler))\n            {\n                throw new ClusterException(\"Aeron client must use a RethrowingErrorHandler\");\n            }\n\n            if (!aeron.context().useConductorAgentInvoker())\n            {\n                throw new ClusterException(\"Aeron client must use conductor agent invoker\");\n            }\n\n            if (null == errorCounter)\n            {\n                throw new ClusterException(\"error counter must be supplied if aeron client is\");\n            }\n\n            if (null == countedErrorHandler)\n            {\n                countedErrorHandler = new CountedErrorHandler(errorHandler, errorCounter);\n                if (ownsAeronClient)\n                {\n                    aeron.context().errorHandler(countedErrorHandler);\n                }\n            }\n\n            if (null == stateCounter)\n            {\n                stateCounter = ClusterCounters.allocate(\n                    aeron, buffer, \"ClusterBackup State\", BACKUP_STATE_TYPE_ID, clusterId);\n            }\n\n            if (null == liveLogPositionCounter)\n            {\n                liveLogPositionCounter = ClusterCounters.allocate(\n                    aeron, buffer, \"ClusterBackup live log position\", LIVE_LOG_POSITION_TYPE_ID, clusterId);\n            }\n\n            if (null == nextQueryDeadlineMsCounter)\n            {\n                nextQueryDeadlineMsCounter = ClusterCounters.allocate(\n                    aeron, buffer, \"ClusterBackup next query deadline in ms\", QUERY_DEADLINE_TYPE_ID, clusterId);\n            }\n\n            if (null == snapshotRetrieveCounter)\n            {\n                snapshotRetrieveCounter = ClusterCounters.allocate(\n                    aeron,\n                    buffer,\n                    \"ClusterBackup snapshots retrieved\",\n                    CLUSTER_BACKUP_SNAPSHOT_RETRIEVE_COUNT_TYPE_ID,\n                    clusterId);\n            }\n\n            if (null == threadFactory)\n            {\n                threadFactory = Thread::new;\n            }\n\n            if (null == idleStrategySupplier)\n            {\n                idleStrategySupplier = ClusteredServiceContainer.Configuration.idleStrategySupplier(null);\n            }\n\n            if (null == archiveContext)\n            {\n                archiveContext = new AeronArchive.Context()\n                    .controlRequestChannel(AeronArchive.Configuration.localControlChannel())\n                    .controlResponseChannel(AeronArchive.Configuration.localControlChannel())\n                    .controlRequestStreamId(AeronArchive.Configuration.localControlStreamId());\n            }\n\n            archiveContext\n                .aeron(aeron)\n                .errorHandler(errorHandler)\n                .ownsAeronClient(false)\n                .lock(NoOpLock.INSTANCE)\n                .clientName(clientName);\n\n            if (!archiveContext.controlRequestChannel().startsWith(CommonContext.IPC_CHANNEL))\n            {\n                throw new ClusterException(\"local archive control must be IPC\");\n            }\n\n            if (!archiveContext.controlResponseChannel().startsWith(CommonContext.IPC_CHANNEL))\n            {\n                throw new ClusterException(\"local archive control must be IPC\");\n            }\n\n            if (null == clusterArchiveContext)\n            {\n                clusterArchiveContext = new AeronArchive.Context();\n            }\n\n            clusterArchiveContext\n                .aeron(aeron)\n                .ownsAeronClient(false)\n                .lock(NoOpLock.INSTANCE)\n                .clientName(clientName);\n\n            if (null == terminationHook)\n            {\n                terminationHook = () -> {};\n            }\n\n            if (null == credentialsSupplier)\n            {\n                credentialsSupplier = new NullCredentialsSupplier();\n            }\n\n            try\n            {\n                SourceType.valueOf(sourceType);\n            }\n            catch (final IllegalArgumentException ex)\n            {\n                throw new ConfigurationException(\n                    \"ClusterBackup.Context.sourceType=\" + sourceType + \" is not valid. Must be one of: \" +\n                    Arrays.toString(SourceType.values()));\n            }\n\n            concludeMarkFile();\n        }\n\n        /**\n         * Has the context had the {@link #conclude()} method called.\n         *\n         * @return true of the {@link #conclude()} method has been called.\n         */\n        public boolean isConcluded()\n        {\n            return isConcluded;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * This client will be closed when the {@link ClusterBackup#close()} or {@link #close()} methods are called\n         * if {@link #ownsAeronClient()} is true.\n         *\n         * @param aeron client for communicating with the local Media Driver.\n         * @return this for a fluent API.\n         * @see Aeron#connect()\n         */\n        public Context aeron(final Aeron aeron)\n        {\n            this.aeron = aeron;\n            return this;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * If not provided then a default will be established during {@link #conclude()} by calling\n         * {@link Aeron#connect()}.\n         *\n         * @return client for communicating with the local Media Driver.\n         */\n        public Aeron aeron()\n        {\n            return aeron;\n        }\n\n        /**\n         * Set the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @param aeronDirectoryName the top level Aeron directory.\n         * @return this for a fluent API.\n         */\n        public Context aeronDirectoryName(final String aeronDirectoryName)\n        {\n            this.aeronDirectoryName = aeronDirectoryName;\n            return this;\n        }\n\n        /**\n         * Get the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @return The top level Aeron directory.\n         */\n        public String aeronDirectoryName()\n        {\n            return aeronDirectoryName;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @param ownsAeronClient does this context own the {@link #aeron()} client.\n         * @return this for a fluent API.\n         */\n        public Context ownsAeronClient(final boolean ownsAeronClient)\n        {\n            this.ownsAeronClient = ownsAeronClient;\n            return this;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @return does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         */\n        public boolean ownsAeronClient()\n        {\n            return ownsAeronClient;\n        }\n\n        /**\n         * Should the consensus module attempt to immediately delete {@link #clusterDir()} on startup.\n         *\n         * @param deleteDirOnStart Attempt deletion.\n         * @return this for a fluent API.\n         */\n        public Context deleteDirOnStart(final boolean deleteDirOnStart)\n        {\n            this.deleteDirOnStart = deleteDirOnStart;\n            return this;\n        }\n\n        /**\n         * Will the consensus module attempt to immediately delete {@link #clusterDir()} on startup.\n         *\n         * @return true when directory will be deleted, otherwise false.\n         */\n        public boolean deleteDirOnStart()\n        {\n            return deleteDirOnStart;\n        }\n\n        /**\n         * Set the id for this cluster instance.\n         *\n         * @param clusterId for this clustered instance.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_ID_PROP_NAME\n         */\n        public Context clusterId(final int clusterId)\n        {\n            this.clusterId = clusterId;\n            return this;\n        }\n\n        /**\n         * Get the id for this cluster instance.\n         *\n         * @return the id for this cluster instance.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_ID_PROP_NAME\n         */\n        public int clusterId()\n        {\n            return clusterId;\n        }\n\n        /**\n         * Set the directory name to use for the cluster directory.\n         *\n         * @param clusterDirectoryName to use.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public Context clusterDirectoryName(final String clusterDirectoryName)\n        {\n            this.clusterDirectoryName = clusterDirectoryName;\n            return this;\n        }\n\n        /**\n         * The directory name to use for the cluster directory.\n         *\n         * @return directory name for the cluster directory.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public String clusterDirectoryName()\n        {\n            return clusterDirectoryName;\n        }\n\n        /**\n         * Set the directory to use for the cluster directory.\n         *\n         * @param clusterDir to use.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public Context clusterDir(final File clusterDir)\n        {\n            this.clusterDir = clusterDir;\n            return this;\n        }\n\n        /**\n         * The directory used for the cluster directory.\n         *\n         * @return directory for the cluster directory.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public File clusterDir()\n        {\n            return clusterDir;\n        }\n\n        /**\n         * Get the directory in which the ClusterBackup will store mark file (i.e. {@code cluster-mark.dat}). It\n         * defaults to {@link #clusterDir()} if it is not set explicitly via the\n         * {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME}.\n         *\n         * @return the directory in which the ClusterBackup will store mark file (i.e. {@code cluster-mark.dat}).\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME\n         * @see #clusterDir()\n         */\n        public File markFileDir()\n        {\n            return markFileDir;\n        }\n\n        /**\n         * Set the directory in which the ClusterBackup will store mark file (i.e. {@code cluster-mark.dat}).\n         *\n         * @param markFileDir the directory in which the ClusterBackup will store mark file (i.e. {@code\n         *                    cluster-mark.dat}).\n         * @return this for a fluent API.\n         */\n        public ClusterBackup.Context markFileDir(final File markFileDir)\n        {\n            this.markFileDir = markFileDir;\n            return this;\n        }\n\n        /**\n         * Set the {@link io.aeron.archive.client.AeronArchive.Context} used for communicating with the local Archive.\n         *\n         * @param archiveContext used for communicating with the local Archive.\n         * @return this for a fluent API.\n         */\n        public Context archiveContext(final AeronArchive.Context archiveContext)\n        {\n            this.archiveContext = archiveContext;\n            return this;\n        }\n\n        /**\n         * Get the {@link io.aeron.archive.client.AeronArchive.Context} used for communicating with the local Archive.\n         *\n         * @return the {@link io.aeron.archive.client.AeronArchive.Context} used for communicating with the local\n         * Archive.\n         */\n        public AeronArchive.Context archiveContext()\n        {\n            return archiveContext;\n        }\n\n        /**\n         * Set the {@link io.aeron.archive.client.AeronArchive.Context} used for communicating with the remote Archive\n         * in the cluster being backed up.\n         *\n         * @param archiveContext used for communicating with the remote Archive in the cluster being backed up.\n         * @return this for a fluent API.\n         */\n        public Context clusterArchiveContext(final AeronArchive.Context archiveContext)\n        {\n            this.clusterArchiveContext = archiveContext;\n            return this;\n        }\n\n        /**\n         * Get the {@link io.aeron.archive.client.AeronArchive.Context} used for communicating with the remote Archive\n         * in the cluster being backed up.\n         *\n         * @return the {@link io.aeron.archive.client.AeronArchive.Context} used for communicating the remote Archive\n         * in the cluster being backed up.\n         */\n        public AeronArchive.Context clusterArchiveContext()\n        {\n            return clusterArchiveContext;\n        }\n\n        /**\n         * Get the thread factory used for creating threads.\n         *\n         * @return thread factory used for creating threads.\n         */\n        public ThreadFactory threadFactory()\n        {\n            return threadFactory;\n        }\n\n        /**\n         * Set the thread factory used for creating threads.\n         *\n         * @param threadFactory used for creating threads\n         * @return this for a fluent API.\n         */\n        public Context threadFactory(final ThreadFactory threadFactory)\n        {\n            this.threadFactory = threadFactory;\n            return this;\n        }\n\n        /**\n         * Provides an {@link IdleStrategy} supplier for the idle strategy for the agent duty cycle.\n         *\n         * @param idleStrategySupplier supplier for the idle strategy for the agent duty cycle.\n         * @return this for a fluent API.\n         */\n        public ClusterBackup.Context idleStrategySupplier(final Supplier<IdleStrategy> idleStrategySupplier)\n        {\n            this.idleStrategySupplier = idleStrategySupplier;\n            return this;\n        }\n\n        /**\n         * Get a new {@link IdleStrategy} based on configured supplier.\n         *\n         * @return a new {@link IdleStrategy} based on configured supplier.\n         */\n        public IdleStrategy idleStrategy()\n        {\n            return idleStrategySupplier.get();\n        }\n\n        /**\n         * Set the {@link EpochClock} to be used for tracking wall clock time.\n         *\n         * @param clock {@link EpochClock} to be used for tracking wall clock time.\n         * @return this for a fluent API.\n         */\n        public Context epochClock(final EpochClock clock)\n        {\n            this.epochClock = clock;\n            return this;\n        }\n\n        /**\n         * Get the {@link EpochClock} to used for tracking wall clock time.\n         *\n         * @return the {@link EpochClock} to used for tracking wall clock time.\n         */\n        public EpochClock epochClock()\n        {\n            return epochClock;\n        }\n\n        /**\n         * Get the {@link ErrorHandler} to be used by the Consensus Module.\n         *\n         * @return the {@link ErrorHandler} to be used by the Consensus Module.\n         */\n        public ErrorHandler errorHandler()\n        {\n            return errorHandler;\n        }\n\n        /**\n         * Set the {@link ErrorHandler} to be used by the Cluster Backup.\n         *\n         * @param errorHandler the error handler to be used by the Cluster Backup.\n         * @return this for a fluent API\n         */\n        public Context errorHandler(final ErrorHandler errorHandler)\n        {\n            this.errorHandler = errorHandler;\n            return this;\n        }\n\n        /**\n         * Get the error counter that will record the number of errors observed.\n         *\n         * @return the error counter that will record the number of errors observed.\n         */\n        public AtomicCounter errorCounter()\n        {\n            return errorCounter;\n        }\n\n        /**\n         * Set the error counter that will record the number of errors observed.\n         *\n         * @param errorCounter the error counter that will record the number of errors observed.\n         * @return this for a fluent API.\n         */\n        public Context errorCounter(final AtomicCounter errorCounter)\n        {\n            this.errorCounter = errorCounter;\n            return this;\n        }\n\n        /**\n         * Non-default for context.\n         *\n         * @param countedErrorHandler to override the default.\n         * @return this for a fluent API.\n         */\n        public Context countedErrorHandler(final CountedErrorHandler countedErrorHandler)\n        {\n            this.countedErrorHandler = countedErrorHandler;\n            return this;\n        }\n\n        /**\n         * The {@link #errorHandler()} that will increment {@link #errorCounter()} by default.\n         *\n         * @return {@link #errorHandler()} that will increment {@link #errorCounter()} by default.\n         */\n        public CountedErrorHandler countedErrorHandler()\n        {\n            return countedErrorHandler;\n        }\n\n        /**\n         * Set the channel parameter for the consensus communication channel.\n         *\n         * @param channel parameter for the consensus communication channel.\n         * @return this for a fluent API.\n         * @see ConsensusModule.Configuration#CONSENSUS_CHANNEL_PROP_NAME\n         */\n        public Context consensusChannel(final String channel)\n        {\n            consensusChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the consensus communication channel.\n         *\n         * @return the channel parameter for the consensus communication channel.\n         * @see ConsensusModule.Configuration#CONSENSUS_CHANNEL_PROP_NAME\n         */\n        public String consensusChannel()\n        {\n            return consensusChannel;\n        }\n\n        /**\n         * Set the stream id for the consensus channel.\n         *\n         * @param streamId for the consensus channel.\n         * @return this for a fluent API\n         * @see ConsensusModule.Configuration#CONSENSUS_STREAM_ID_PROP_NAME\n         */\n        public Context consensusStreamId(final int streamId)\n        {\n            consensusStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the consensus channel.\n         *\n         * @return the stream id for the consensus channel.\n         * @see ConsensusModule.Configuration#CONSENSUS_STREAM_ID_PROP_NAME\n         */\n        public int consensusStreamId()\n        {\n            return consensusStreamId;\n        }\n\n        /**\n         * Set the stream id for the consensus module snapshot replay.\n         *\n         * @param streamId for the consensus module snapshot replay channel.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.ConsensusModule.Context#snapshotStreamId()\n         */\n        public Context consensusModuleSnapshotStreamId(final int streamId)\n        {\n            consensusModuleSnapshotStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the consensus module snapshot replay channel.\n         *\n         * @return the stream id for the consensus module snapshot replay channel.\n         * @see io.aeron.cluster.ConsensusModule.Context#snapshotStreamId()\n         */\n        public int consensusModuleSnapshotStreamId()\n        {\n            return consensusModuleSnapshotStreamId;\n        }\n\n        /**\n         * Set the stream id for the clustered service snapshot replay.\n         *\n         * @param streamId for the clustered service snapshot replay channel.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Context#snapshotStreamId()\n         */\n        public Context serviceSnapshotStreamId(final int streamId)\n        {\n            serviceSnapshotStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the clustered service snapshot replay channel.\n         *\n         * @return the stream id for the clustered service snapshot replay channel.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Context#snapshotStreamId()\n         */\n        public int serviceSnapshotStreamId()\n        {\n            return serviceSnapshotStreamId;\n        }\n\n        /**\n         * Set the stream id for the cluster log channel.\n         *\n         * @param streamId for the cluster log channel.\n         * @return this for a fluent API\n         * @see ConsensusModule.Configuration#LOG_STREAM_ID_PROP_NAME\n         */\n        public Context logStreamId(final int streamId)\n        {\n            logStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the cluster log channel.\n         *\n         * @return the stream id for the cluster log channel.\n         * @see ConsensusModule.Configuration#LOG_STREAM_ID_PROP_NAME\n         */\n        @Config\n        public int logStreamId()\n        {\n            return logStreamId;\n        }\n\n        /**\n         * Set the endpoint that will be subscribed to in order to receive logs and snapshots.\n         *\n         * @param catchupEndpoint to use for the log retrieval.\n         * @return catchup endpoint to use for the log retrieval.\n         * @see Configuration#catchupEndpoint()\n         */\n        public Context catchupEndpoint(final String catchupEndpoint)\n        {\n            this.catchupEndpoint = catchupEndpoint;\n            return this;\n        }\n\n        /**\n         * Get the catchup endpoint to use for log retrieval.\n         *\n         * @return catchup endpoint to use for the log retrieval.\n         * @see Configuration#catchupEndpoint()\n         */\n        @Config(id = \"CLUSTER_BACKUP_CATCHUP_ENDPOINT\")\n        public String catchupEndpoint()\n        {\n            return catchupEndpoint;\n        }\n\n        /**\n         * Set the catchup channel template to use for log and snapshot retrieval.\n         *\n         * @param catchupChannel to use for the log and snapshot retrieval.\n         * @return catchup endpoint to use for the log and snapshot retrieval.\n         * @see Configuration#CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME\n         */\n        public Context catchupChannel(final String catchupChannel)\n        {\n            this.catchupChannel = catchupChannel;\n            return this;\n        }\n\n        /**\n         * Get the catchup channel template to use for log and snapshot retrieval.\n         *\n         * @return catchup endpoint to use for the log and snapshot retrieval.\n         * @see Configuration#CLUSTER_BACKUP_CATCHUP_CHANNEL_PROP_NAME\n         */\n        @Config(id = \"CLUSTER_BACKUP_CATCHUP_CHANNEL\")\n        public String catchupChannel()\n        {\n            return catchupChannel;\n        }\n\n        /**\n         * Interval at which a cluster backup will send backup queries.\n         *\n         * @param clusterBackupIntervalNs between add cluster members and snapshot recording queries.\n         * @return this for a fluent API.\n         * @see Configuration#CLUSTER_BACKUP_INTERVAL_PROP_NAME\n         * @see Configuration#CLUSTER_BACKUP_INTERVAL_DEFAULT_NS\n         */\n        public Context clusterBackupIntervalNs(final long clusterBackupIntervalNs)\n        {\n            this.clusterBackupIntervalNs = clusterBackupIntervalNs;\n            return this;\n        }\n\n        /**\n         * Interval at which a cluster backup will send backup queries.\n         *\n         * @return the interval at which a cluster backup will send backup queries.\n         * @see Configuration#CLUSTER_BACKUP_INTERVAL_PROP_NAME\n         * @see Configuration#CLUSTER_BACKUP_INTERVAL_DEFAULT_NS\n         */\n        @Config\n        public long clusterBackupIntervalNs()\n        {\n            return clusterBackupIntervalNs;\n        }\n\n        /**\n         * Timeout within which a cluster backup will expect a response from a backup query.\n         *\n         * @param clusterBackupResponseTimeoutNs within which a cluster backup will expect a response.\n         * @return this for a fluent API.\n         * @see Configuration#CLUSTER_BACKUP_RESPONSE_TIMEOUT_PROP_NAME\n         * @see Configuration#CLUSTER_BACKUP_RESPONSE_TIMEOUT_DEFAULT_NS\n         */\n        public Context clusterBackupResponseTimeoutNs(final long clusterBackupResponseTimeoutNs)\n        {\n            this.clusterBackupResponseTimeoutNs = clusterBackupResponseTimeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout within which a cluster backup will expect a response from a backup query.\n         *\n         * @return timeout within which a cluster backup will expect a response from a backup query.\n         * @see Configuration#CLUSTER_BACKUP_RESPONSE_TIMEOUT_PROP_NAME\n         * @see Configuration#CLUSTER_BACKUP_RESPONSE_TIMEOUT_DEFAULT_NS\n         */\n        @Config\n        public long clusterBackupResponseTimeoutNs()\n        {\n            return clusterBackupResponseTimeoutNs;\n        }\n\n        /**\n         * Timeout within which a cluster backup will expect progress.\n         *\n         * @param clusterBackupProgressTimeoutNs within which a cluster backup will expect a response.\n         * @return this for a fluent API.\n         * @see Configuration#CLUSTER_BACKUP_PROGRESS_TIMEOUT_PROP_NAME\n         * @see Configuration#CLUSTER_BACKUP_PROGRESS_TIMEOUT_DEFAULT_NS\n         */\n        public Context clusterBackupProgressTimeoutNs(final long clusterBackupProgressTimeoutNs)\n        {\n            this.clusterBackupProgressTimeoutNs = clusterBackupProgressTimeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout within which a cluster backup will expect progress.\n         *\n         * @return timeout within which a cluster backup will expect progress.\n         * @see Configuration#CLUSTER_BACKUP_PROGRESS_TIMEOUT_PROP_NAME\n         * @see Configuration#CLUSTER_BACKUP_PROGRESS_TIMEOUT_DEFAULT_NS\n         */\n        @Config\n        public long clusterBackupProgressTimeoutNs()\n        {\n            return clusterBackupProgressTimeoutNs;\n        }\n\n        /**\n         * Interval at which the cluster backup is re-initialised after an exception has been thrown.\n         *\n         * @param clusterBackupCoolDownIntervalNs time before the cluster backup is re-initialised.\n         * @return this for a fluent API.\n         * @see Configuration#CLUSTER_BACKUP_COOL_DOWN_INTERVAL_PROP_NAME\n         * @see Configuration#CLUSTER_BACKUP_COOL_DOWN_INTERVAL_DEFAULT_NS\n         */\n        public Context clusterBackupCoolDownIntervalNs(final long clusterBackupCoolDownIntervalNs)\n        {\n            this.clusterBackupCoolDownIntervalNs = clusterBackupCoolDownIntervalNs;\n            return this;\n        }\n\n        /**\n         * Interval at which the cluster backup is re-initialised after an exception has been thrown.\n         *\n         * @return interval at which the cluster backup is re-initialised after an exception has been thrown.\n         * @see Configuration#CLUSTER_BACKUP_COOL_DOWN_INTERVAL_PROP_NAME\n         * @see Configuration#CLUSTER_BACKUP_COOL_DOWN_INTERVAL_DEFAULT_NS\n         */\n        @Config\n        public long clusterBackupCoolDownIntervalNs()\n        {\n            return clusterBackupCoolDownIntervalNs;\n        }\n\n        /**\n         * String representing the cluster members consensus endpoints.\n         * <p>\n         * {@code \"endpoint,endpoint,endpoint\"}\n         *\n         * @param endpoints which are to be contacted for joining the cluster.\n         * @return this for a fluent API.\n         */\n        public Context clusterConsensusEndpoints(final String endpoints)\n        {\n            this.clusterConsensusEndpoints = endpoints;\n            return this;\n        }\n\n        /**\n         * The endpoints representing cluster members of the cluster to attempt to a backup from.\n         *\n         * @return members of the cluster to attempt to request a backup from.\n         */\n        public String clusterConsensusEndpoints()\n        {\n            return clusterConsensusEndpoints;\n        }\n\n        /**\n         * Set the {@link Runnable} that is called when the {@link ClusterBackup} processes a termination action.\n         *\n         * @param terminationHook that can be used to terminate.\n         * @return this for a fluent API.\n         */\n        public Context terminationHook(final Runnable terminationHook)\n        {\n            this.terminationHook = terminationHook;\n            return this;\n        }\n\n        /**\n         * Get the {@link Runnable} that is called when the {@link ClusterBackup} processes a termination action.\n         *\n         * @return the {@link Runnable} that can be used to terminate.\n         */\n        public Runnable terminationHook()\n        {\n            return terminationHook;\n        }\n\n        /**\n         * Set the {@link ClusterMarkFile} in use.\n         *\n         * @param markFile to use.\n         * @return this for a fluent API.\n         */\n        public Context clusterMarkFile(final ClusterMarkFile markFile)\n        {\n            this.markFile = markFile;\n            return this;\n        }\n\n        /**\n         * The {@link ClusterMarkFile} in use.\n         *\n         * @return {@link ClusterMarkFile} in use.\n         */\n        public ClusterMarkFile clusterMarkFile()\n        {\n            return markFile;\n        }\n\n        /**\n         * Set the error buffer length in bytes to use.\n         *\n         * @param errorBufferLength in bytes to use.\n         * @return this for a fluent API.\n         */\n        public Context errorBufferLength(final int errorBufferLength)\n        {\n            this.errorBufferLength = errorBufferLength;\n            return this;\n        }\n\n        /**\n         * The error buffer length in bytes.\n         *\n         * @return error buffer length in bytes.\n         */\n        public int errorBufferLength()\n        {\n            return errorBufferLength;\n        }\n\n        /**\n         * Set the {@link DistinctErrorLog} in use.\n         *\n         * @param errorLog to use.\n         * @return this for a fluent API.\n         */\n        public Context errorLog(final DistinctErrorLog errorLog)\n        {\n            this.errorLog = errorLog;\n            return this;\n        }\n\n        /**\n         * The {@link DistinctErrorLog} in use.\n         *\n         * @return {@link DistinctErrorLog} in use.\n         */\n        public DistinctErrorLog errorLog()\n        {\n            return errorLog;\n        }\n\n        /**\n         * Get the counter for the current state of the cluster backup.\n         *\n         * @return the counter for the current state of the cluster backup.\n         * @see ClusterBackup.State\n         */\n        public Counter stateCounter()\n        {\n            return stateCounter;\n        }\n\n        /**\n         * Set the counter for the current state of the cluster backup.\n         *\n         * @param stateCounter the counter for the current state of the cluster backup.\n         * @return this for a fluent API.\n         * @see ClusterBackup.State\n         */\n        public Context stateCounter(final Counter stateCounter)\n        {\n            this.stateCounter = stateCounter;\n            return this;\n        }\n\n        /**\n         * Get the counter for the current position of the live log.\n         *\n         * @return the counter for the current position of the live log.\n         */\n        public Counter liveLogPositionCounter()\n        {\n            return liveLogPositionCounter;\n        }\n\n        /**\n         * Set the counter for the current position of the live log.\n         *\n         * @param liveLogPositionCounter the counter for the current position of the live log.\n         * @return this for a fluent API.\n         */\n        public Context liveLogPositionCounter(final Counter liveLogPositionCounter)\n        {\n            this.liveLogPositionCounter = liveLogPositionCounter;\n            return this;\n        }\n\n        /**\n         * Set the counter for the number of snapshots retrieved by the backup from the cluster.\n         *\n         * @param snapshotRetrieveCounter the counter to use for snapshots retrieved.\n         * @return this for a fluent API.\n         */\n        public Context snapshotRetrieveCounter(final Counter snapshotRetrieveCounter)\n        {\n            this.snapshotRetrieveCounter = snapshotRetrieveCounter;\n            return this;\n        }\n\n        /**\n         * Get the counter for the number of snapshots retrieved by the backup from the cluster.\n         *\n         * @return counter for the number of snapshots retrieved by the backup from the cluster.\n         */\n        public Counter snapshotRetrieveCounter()\n        {\n            return snapshotRetrieveCounter;\n        }\n\n        /**\n         * Get the counter for the next query deadline ms.\n         *\n         * @return the counter for the next query deadline ms.\n         */\n        public Counter nextQueryDeadlineMsCounter()\n        {\n            return nextQueryDeadlineMsCounter;\n        }\n\n        /**\n         * Set the counter for the next query deadline ms.\n         *\n         * @param nextQueryDeadlineMsCounter the counter for the next query deadline ms.\n         * @return this for a fluent API.\n         */\n        public Context nextQueryDeadlineMsCounter(final Counter nextQueryDeadlineMsCounter)\n        {\n            this.nextQueryDeadlineMsCounter = nextQueryDeadlineMsCounter;\n            return this;\n        }\n\n        /**\n         * Get the {@link ClusterBackupEventsListener} in use for the backup agent.\n         *\n         * @return {@link ClusterBackupEventsListener} in use for the backup agent.\n         * @see ClusterBackupEventsListener\n         */\n        public ClusterBackupEventsListener eventsListener()\n        {\n            return eventsListener;\n        }\n\n        /**\n         * Set the {@link ClusterBackupEventsListener} to use for the backup agent.\n         *\n         * @param eventsListener to use for the backup agent.\n         * @return this for a fluent API.\n         * @see ClusterBackupEventsListener\n         */\n        public Context eventsListener(final ClusterBackupEventsListener eventsListener)\n        {\n            this.eventsListener = eventsListener;\n            return this;\n        }\n\n        /**\n         * Should an {@link AgentInvoker} be used for running the {@link ClusterBackup} rather than run it on\n         * a thread with a {@link AgentRunner}.\n         *\n         * @param useAgentInvoker use {@link AgentInvoker} be used for running the {@link ClusterBackup}?\n         * @return this for a fluent API.\n         */\n        public Context useAgentInvoker(final boolean useAgentInvoker)\n        {\n            this.useAgentInvoker = useAgentInvoker;\n            return this;\n        }\n\n        /**\n         * Should an {@link AgentInvoker} be used for running the {@link ClusterBackup} rather than run it on\n         * a thread with a {@link AgentRunner}.\n         *\n         * @return true if the {@link ClusterBackup} will be run with an {@link AgentInvoker} otherwise false.\n         */\n        public boolean useAgentInvoker()\n        {\n            return useAgentInvoker;\n        }\n\n        /**\n         * Set the {@link CredentialsSupplier} to be used for authentication with the cluster.\n         *\n         * @param credentialsSupplier to be used for authentication with the cluster.\n         * @return this for fluent API.\n         */\n        public Context credentialsSupplier(final CredentialsSupplier credentialsSupplier)\n        {\n            this.credentialsSupplier = credentialsSupplier;\n            return this;\n        }\n\n        /**\n         * Get the {@link CredentialsSupplier} to be used for authentication with the cluster.\n         *\n         * @return the {@link CredentialsSupplier} to be used for authentication with the cluster.\n         */\n        public CredentialsSupplier credentialsSupplier()\n        {\n            return this.credentialsSupplier;\n        }\n\n        /**\n         * Set the {@link SourceType} to be used for this backup instance.\n         *\n         * @param sourceType type of sources to receive log traffic from.\n         * @return this for a fluent API\n         */\n        public Context sourceType(final SourceType sourceType)\n        {\n            this.sourceType = sourceType.name();\n            return this;\n        }\n\n        /**\n         * Get the currently configured source type.\n         *\n         * @return source type for this backup instance.\n         * @throws IllegalArgumentException if the configured source type is not one of {@link SourceType}\n         */\n        @Config(id = \"CLUSTER_BACKUP_SOURCE_TYPE\")\n        public SourceType sourceType()\n        {\n            return SourceType.valueOf(sourceType);\n        }\n\n        /**\n         * Timeout when no progress it detected when performing an archive replication (typically for snapshots).\n         *\n         * @param timeoutNs timeout in nanoseconds\n         * @return this for a fluent API.\n         * @see ConsensusModule.Configuration#replicationProgressTimeoutNs()\n         */\n        public Context replicationProgressTimeoutNs(final long timeoutNs)\n        {\n            this.replicationProgressTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout when no progress it detected when performing an archive replication (typically for snapshots).\n         *\n         * @return timeout in nanoseconds.\n         * @see ConsensusModule.Configuration#replicationProgressTimeoutNs()\n         */\n        @Config(id = \"CLUSTER_REPLICATION_PROGRESS_TIMEOUT\")\n        public long replicationProgressTimeoutNs()\n        {\n            return replicationProgressTimeoutNs;\n        }\n\n        /**\n         * Interval between checks for replication progress.  Defaults to {@link Context#replicationProgressTimeoutNs()}\n         * divided by <code>10</code>.\n         *\n         * @param intervalNs timeout in nanoseconds\n         * @return this for a fluent API.\n         * @see ConsensusModule.Configuration#replicationProgressIntervalNs()\n         */\n        public Context replicationProgressIntervalNs(final long intervalNs)\n        {\n            this.replicationProgressIntervalNs = intervalNs;\n            return this;\n        }\n\n        /**\n         * Interval between checks for replication progress.\n         *\n         * @return timeout in nanoseconds.\n         * @see ConsensusModule.Configuration#replicationProgressIntervalNs()\n         */\n        @Config(id = \"CLUSTER_REPLICATION_PROGRESS_INTERVAL\")\n        public long replicationProgressIntervalNs()\n        {\n            return replicationProgressIntervalNs;\n        }\n\n        /**\n         * Where to start the replay from the cluster used for the backup. Doesn't set a specific position, but instead\n         * it will use an enum value to derive appropriate place to start. This only applies to the first replay used\n         * for this backup.\n         *\n         * @param replayStart determines the position to be used to start the backup replay from.\n         * @return this for a fluent API.\n         * @see Configuration.ReplayStart\n         * @see Configuration#CLUSTER_INITIAL_REPLAY_START_DEFAULT\n         */\n        public Context initialReplayStart(final Configuration.ReplayStart replayStart)\n        {\n            this.initialReplayStart = replayStart;\n            return this;\n        }\n\n        /**\n         * Get the place for the cluster replay to start from when no local copy of the log exists.\n         *\n         * @return the cluster replay start.\n         * @see Configuration.ReplayStart\n         * @see Configuration#CLUSTER_INITIAL_REPLAY_START_DEFAULT\n         */\n        @Config(id = \"CLUSTER_INITIAL_REPLAY_START\")\n        public Configuration.ReplayStart initialReplayStart()\n        {\n            return this.initialReplayStart;\n        }\n\n        /**\n         * Delete the cluster directory.\n         */\n        public void deleteDirectory()\n        {\n            if (null != clusterDir)\n            {\n                IoUtil.delete(clusterDir, false);\n            }\n        }\n\n        /**\n         * Close the context and free applicable resources.\n         * <p>\n         * If {@link #ownsAeronClient()} is true then the {@link #aeron()} client will be closed.\n         */\n        public void close()\n        {\n            if (ownsAeronClient)\n            {\n                CloseHelper.close(countedErrorHandler, aeron);\n            }\n            else if (!aeron.isClosed())\n            {\n                CloseHelper.close(countedErrorHandler, stateCounter);\n                CloseHelper.close(countedErrorHandler, liveLogPositionCounter);\n            }\n\n            CloseHelper.close(markFile);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"ClusterBackup.Context\" +\n                \"\\n{\" +\n                \"\\n    isConcluded=\" + isConcluded() +\n                \"\\n    ownsAeronClient=\" + ownsAeronClient +\n                \"\\n    aeronDirectoryName='\" + aeronDirectoryName + '\\'' +\n                \"\\n    aeron=\" + aeron +\n                \"\\n    clusterId=\" + clusterId +\n                \"\\n    consensusChannel='\" + consensusChannel + '\\'' +\n                \"\\n    consensusStreamId=\" + consensusStreamId +\n                \"\\n    consensusModuleSnapshotStreamId=\" + consensusModuleSnapshotStreamId +\n                \"\\n    serviceSnapshotStreamId=\" + serviceSnapshotStreamId +\n                \"\\n    logStreamId=\" + logStreamId +\n                \"\\n    catchupEndpoint='\" + catchupEndpoint + '\\'' +\n                \"\\n    catchupChannel='\" + catchupChannel + '\\'' +\n                \"\\n    clusterBackupIntervalNs=\" + clusterBackupIntervalNs +\n                \"\\n    clusterBackupResponseTimeoutNs=\" + clusterBackupResponseTimeoutNs +\n                \"\\n    clusterBackupProgressTimeoutNs=\" + clusterBackupProgressTimeoutNs +\n                \"\\n    clusterBackupCoolDownIntervalNs=\" + clusterBackupCoolDownIntervalNs +\n                \"\\n    errorBufferLength=\" + errorBufferLength +\n                \"\\n    deleteDirOnStart=\" + deleteDirOnStart +\n                \"\\n    useAgentInvoker=\" + useAgentInvoker +\n                \"\\n    clusterDirectoryName='\" + clusterDirectoryName + '\\'' +\n                \"\\n    clusterDir=\" + clusterDir +\n                \"\\n    markFile=\" + markFile +\n                \"\\n    clusterConsensusEndpoints='\" + clusterConsensusEndpoints + '\\'' +\n                \"\\n    threadFactory=\" + threadFactory +\n                \"\\n    epochClock=\" + epochClock +\n                \"\\n    idleStrategySupplier=\" + idleStrategySupplier +\n                \"\\n    errorLog=\" + errorLog +\n                \"\\n    errorHandler=\" + errorHandler +\n                \"\\n    errorCounter=\" + errorCounter +\n                \"\\n    countedErrorHandler=\" + countedErrorHandler +\n                \"\\n    stateCounter=\" + stateCounter +\n                \"\\n    liveLogPositionCounter=\" + liveLogPositionCounter +\n                \"\\n    nextQueryDeadlineMsCounter=\" + nextQueryDeadlineMsCounter +\n                \"\\n    archiveContext=\" + archiveContext +\n                \"\\n    clusterArchiveContext=\" + clusterArchiveContext +\n                \"\\n    terminationHook=\" + terminationHook +\n                \"\\n    eventsListener=\" + eventsListener +\n                \"\\n}\";\n        }\n\n        private void concludeMarkFile()\n        {\n            ClusterMarkFile.checkHeaderLength(\n                aeron.context().aeronDirectoryName(), null, null, null, null);\n\n            markFile.encoder()\n                .archiveStreamId(archiveContext.controlRequestStreamId())\n                .serviceStreamId(ClusteredServiceContainer.Configuration.serviceStreamId())\n                .consensusModuleStreamId(ClusteredServiceContainer.Configuration.consensusModuleStreamId())\n                .ingressStreamId(AeronCluster.Configuration.ingressStreamId())\n                .memberId(-1)\n                .serviceId(SERVICE_ID)\n                .clusterId(ClusteredServiceContainer.Configuration.clusterId())\n                .aeronDirectory(aeron.context().aeronDirectoryName())\n                .controlChannel(null)\n                .ingressChannel(null)\n                .serviceName(null)\n                .authenticator(null);\n\n            markFile.signalReady(epochClock.time());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterBackupAgent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.client.ControlResponsePoller;\nimport io.aeron.archive.client.RecordingSignalPoller;\nimport io.aeron.archive.codecs.ControlResponseCode;\nimport io.aeron.archive.codecs.ControlResponseDecoder;\nimport io.aeron.archive.codecs.RecordingSignalEventDecoder;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.BackupResponseDecoder;\nimport io.aeron.cluster.codecs.ChallengeDecoder;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.SessionEventDecoder;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.*;\nimport static io.aeron.archive.client.AeronArchive.NULL_LENGTH;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.AeronArchive.NULL_TIMESTAMP;\nimport static io.aeron.archive.codecs.SourceLocation.REMOTE;\nimport static io.aeron.cluster.ClusterBackup.State.BACKING_UP;\nimport static io.aeron.cluster.ClusterBackup.State.BACKUP_QUERY;\nimport static io.aeron.cluster.ClusterBackup.State.CLOSED;\nimport static io.aeron.cluster.ClusterBackup.State.LIVE_LOG_RECORD;\nimport static io.aeron.cluster.ClusterBackup.State.LIVE_LOG_REPLAY;\nimport static io.aeron.cluster.ClusterBackup.State.RESET_BACKUP;\nimport static io.aeron.cluster.ClusterBackup.State.SNAPSHOT_RETRIEVE;\nimport static io.aeron.cluster.ClusterBackup.State.UPDATE_RECORDING_LOG;\nimport static io.aeron.exceptions.AeronException.Category;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\n\n/**\n * {@link Agent} which backs up a remote cluster by replicating the log and polling for snapshots.\n */\npublic final class ClusterBackupAgent implements Agent\n{\n    /**\n     * Update interval for cluster mark file.\n     */\n    public static final long MARK_FILE_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);\n\n    private static final int SLOW_TICK_INTERVAL_MS = 10;\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final BackupResponseDecoder backupResponseDecoder = new BackupResponseDecoder();\n    private final ChallengeDecoder challengeDecoder = new ChallengeDecoder();\n    private final SessionEventDecoder sessionEventDecoder = new SessionEventDecoder();\n\n    private final ClusterBackup.Context ctx;\n    private final ClusterMarkFile markFile;\n    private final AgentInvoker aeronClientInvoker;\n    private final EpochClock epochClock;\n    private final Aeron aeron;\n    private final ConsensusPublisher consensusPublisher = new ConsensusPublisher();\n    private final ArrayList<RecordingLog.Snapshot> snapshotsToRetrieve = new ArrayList<>(4);\n    private final ArrayList<RecordingLog.Snapshot> snapshotsRetrieved = new ArrayList<>(4);\n    private final Counter stateCounter;\n    private final Counter liveLogPositionCounter;\n    private final Counter nextQueryDeadlineMsCounter;\n    private final ClusterBackupEventsListener eventsListener;\n    private final long backupResponseTimeoutMs;\n    private final long backupQueryIntervalMs;\n    private final long backupProgressTimeoutMs;\n    private final long coolDownIntervalMs;\n    private final long unavailableCounterHandlerRegistrationId;\n    private final PublicationGroup<ExclusivePublication> consensusPublicationGroup;\n    private final LogSourceValidator logSourceValidator;\n\n    private ClusterBackup.State state = BACKUP_QUERY;\n\n    private RecordingSignalPoller recordingSignalPoller;\n    private AeronArchive backupArchive;\n    private AeronArchive.AsyncConnect clusterArchiveAsyncConnect;\n    private AeronArchive clusterArchive;\n\n    private SnapshotReplication snapshotReplication;\n\n    private final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::onFragment);\n    private final Subscription consensusSubscription;\n    private ClusterMember[] clusterMembers;\n    private ClusterMember logSupplierMember;\n    private RecordingLog recordingLog;\n    private RecordingLog.Entry leaderLogEntry;\n    private RecordingLog.Entry leaderLastTermEntry;\n    private Subscription recordingSubscription;\n    private String replayChannel;\n    private String recordingChannel;\n\n    private long slowTickDeadlineMs = 0;\n    private long markFileUpdateDeadlineMs = 0;\n    private long timeOfLastBackupQueryMs = 0;\n    private long timeOfLastProgressMs = 0;\n    private long coolDownDeadlineMs = NULL_VALUE;\n    private long correlationId = NULL_VALUE;\n    private long clusterLogRecordingId = NULL_VALUE;\n    private long liveLogRecordingSubscriptionId = NULL_VALUE;\n    private long liveLogRecordingId = NULL_VALUE;\n    private long liveLogReplaySessionId = NULL_VALUE;\n    private int leaderCommitPositionCounterId = NULL_VALUE;\n    private int liveLogRecordingCounterId = NULL_COUNTER_ID;\n    private int liveLogRecordingSessionId = NULL_VALUE;\n\n    ClusterBackupAgent(final ClusterBackup.Context ctx)\n    {\n        this.ctx = ctx;\n        aeron = ctx.aeron();\n        epochClock = ctx.epochClock();\n\n        backupResponseTimeoutMs = TimeUnit.NANOSECONDS.toMillis(ctx.clusterBackupResponseTimeoutNs());\n        backupQueryIntervalMs = TimeUnit.NANOSECONDS.toMillis(ctx.clusterBackupIntervalNs());\n        backupProgressTimeoutMs = TimeUnit.NANOSECONDS.toMillis(ctx.clusterBackupProgressTimeoutNs());\n        coolDownIntervalMs = TimeUnit.NANOSECONDS.toMillis(ctx.clusterBackupCoolDownIntervalNs());\n        markFile = ctx.clusterMarkFile();\n        eventsListener = ctx.eventsListener();\n\n        final String[] clusterConsensusEndpoints = ctx.clusterConsensusEndpoints().split(\",\");\n\n        consensusPublicationGroup = new PublicationGroup<>(\n            clusterConsensusEndpoints, ctx.consensusChannel(), ctx.consensusStreamId(), Aeron::addExclusivePublication);\n        consensusPublicationGroup.shuffle();\n\n        aeronClientInvoker = aeron.conductorAgentInvoker();\n        aeronClientInvoker.invoke();\n\n        unavailableCounterHandlerRegistrationId = aeron.addUnavailableCounterHandler(this::onUnavailableCounter);\n\n        consensusSubscription = aeron.addSubscription(ctx.consensusChannel(), ctx.consensusStreamId());\n\n        stateCounter = ctx.stateCounter();\n        liveLogPositionCounter = ctx.liveLogPositionCounter();\n        nextQueryDeadlineMsCounter = ctx.nextQueryDeadlineMsCounter();\n        logSourceValidator = new LogSourceValidator(ctx.sourceType());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        recordingLog = new RecordingLog(ctx.clusterDir(), true);\n        backupArchive = AeronArchive.connect(ctx.archiveContext().clone());\n        recordingSignalPoller = new RecordingSignalPoller(\n            backupArchive.controlSessionId(),\n            backupArchive.controlResponsePoller().subscription());\n\n        final long nowMs = epochClock.time();\n        nextQueryDeadlineMsCounter.setRelease(nowMs - 1);\n        timeOfLastProgressMs = nowMs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        if (!aeron.isClosed())\n        {\n            aeron.removeUnavailableCounterHandler(unavailableCounterHandlerRegistrationId);\n\n            CloseHelper.close(ctx.countedErrorHandler(), snapshotReplication);\n            stopReplay();\n            stopRecording();\n\n            if (!ctx.ownsAeronClient())\n            {\n                CloseHelper.closeAll(\n                    (ErrorHandler)ctx.countedErrorHandler(),\n                    consensusSubscription,\n                    consensusPublicationGroup,\n                    recordingSubscription);\n            }\n\n            state(CLOSED, epochClock.time());\n        }\n\n        CloseHelper.closeAll(\n            (ErrorHandler)ctx.countedErrorHandler(),\n            backupArchive,\n            clusterArchiveAsyncConnect,\n            clusterArchive,\n            recordingLog);\n\n        markFile.signalTerminated();\n        ctx.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        final long nowMs = epochClock.time();\n        int workCount = 0;\n\n        try\n        {\n            if (nowMs > slowTickDeadlineMs)\n            {\n                slowTickDeadlineMs = nowMs + SLOW_TICK_INTERVAL_MS;\n                workCount += slowTick(nowMs);\n            }\n\n            workCount += consensusSubscription.poll(fragmentAssembler, ConsensusAdapter.FRAGMENT_LIMIT);\n\n            switch (state)\n            {\n                case BACKUP_QUERY:\n                    workCount += backupQuery(nowMs);\n                    break;\n\n                case SNAPSHOT_RETRIEVE:\n                    workCount += snapshotRetrieve(nowMs);\n                    break;\n\n                case LIVE_LOG_RECORD:\n                    workCount += liveLogRecord(nowMs);\n                    break;\n\n                case LIVE_LOG_REPLAY:\n                    workCount += liveLogReplay(nowMs);\n                    break;\n\n                case UPDATE_RECORDING_LOG:\n                    workCount += updateRecordingLog(nowMs);\n                    break;\n\n                case BACKING_UP:\n                    workCount += backingUp(nowMs);\n                    break;\n\n                case RESET_BACKUP:\n                    workCount += resetBackup(nowMs);\n                    break;\n\n                case CLOSED:\n                    return workCount;\n            }\n\n            if (hasProgressStalled(nowMs))\n            {\n                if (null != eventsListener)\n                {\n                    eventsListener.onPossibleFailure(new TimeoutException(\"progress has stalled\", Category.WARN));\n                }\n\n                state(RESET_BACKUP, nowMs);\n            }\n        }\n        catch (final AgentTerminationException ex)\n        {\n            runTerminationHook(ex);\n        }\n        catch (final Exception ex)\n        {\n            if (null != eventsListener)\n            {\n                eventsListener.onPossibleFailure(ex);\n            }\n\n            state(RESET_BACKUP, nowMs);\n            throw ex;\n        }\n\n        return workCount;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"cluster-backup\";\n    }\n\n    private void reset()\n    {\n        clusterMembers = null;\n        logSupplierMember = null;\n        leaderLogEntry = null;\n        leaderLastTermEntry = null;\n        liveLogRecordingCounterId = NULL_COUNTER_ID;\n        liveLogRecordingId = NULL_VALUE;\n        liveLogRecordingSessionId = NULL_VALUE;\n\n        snapshotsToRetrieve.clear();\n        snapshotsRetrieved.clear();\n        fragmentAssembler.clear();\n\n        CloseHelper.close(snapshotReplication);\n        snapshotReplication = null;\n\n        stopRecording();\n        stopReplay();\n\n        CloseHelper.closeAll(\n            (ErrorHandler)ctx.countedErrorHandler(),\n            consensusPublicationGroup,\n            clusterArchive,\n            clusterArchiveAsyncConnect,\n            recordingSubscription);\n\n        clusterArchive = null;\n        clusterArchiveAsyncConnect = null;\n        recordingSubscription = null;\n    }\n\n    private void stopReplay()\n    {\n        if (NULL_VALUE != liveLogReplaySessionId)\n        {\n            if (null != clusterArchive)\n            {\n                try\n                {\n                    clusterArchive.stopReplay(liveLogReplaySessionId);\n                }\n                catch (final Exception ex)\n                {\n                    ctx.countedErrorHandler().onError(new ClusterEvent(\"failed to stop log replay: \" + ex));\n                }\n            }\n            liveLogReplaySessionId = NULL_VALUE;\n        }\n    }\n\n    private void stopRecording()\n    {\n        if (NULL_VALUE != liveLogRecordingSubscriptionId)\n        {\n            try\n            {\n                backupArchive.tryStopRecording(liveLogRecordingSubscriptionId);\n            }\n            catch (final Exception ex)\n            {\n                ctx.countedErrorHandler().onError(new ClusterEvent(\"failed to stop log recording: \" + ex));\n            }\n            liveLogRecordingSubscriptionId = NULL_VALUE;\n        }\n    }\n\n    private void onUnavailableCounter(final CountersReader counters, final long registrationId, final int counterId)\n    {\n        if (counterId == liveLogRecordingCounterId)\n        {\n            if (null != eventsListener)\n            {\n                eventsListener.onPossibleFailure(new ClusterEvent(\"log recording counter unexpectedly unavailable\"));\n            }\n\n            state(RESET_BACKUP, epochClock.time());\n        }\n    }\n\n    private void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ClusterException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (messageHeaderDecoder.templateId())\n        {\n            case BackupResponseDecoder.TEMPLATE_ID:\n                backupResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final int memberId = BackupResponseDecoder.memberIdNullValue() != backupResponseDecoder.memberId() ?\n                    backupResponseDecoder.memberId() : NULL_VALUE;\n\n                onBackupResponse(\n                    backupResponseDecoder.correlationId(),\n                    backupResponseDecoder.logRecordingId(),\n                    backupResponseDecoder.logLeadershipTermId(),\n                    backupResponseDecoder.logTermBaseLogPosition(),\n                    backupResponseDecoder.lastLeadershipTermId(),\n                    backupResponseDecoder.lastTermBaseLogPosition(),\n                    backupResponseDecoder.commitPositionCounterId(),\n                    backupResponseDecoder.leaderMemberId(),\n                    memberId,\n                    backupResponseDecoder);\n                break;\n\n            case ChallengeDecoder.TEMPLATE_ID:\n                challengeDecoder.wrapAndApplyHeader(buffer, offset, messageHeaderDecoder);\n                final byte[] encodedChallenge = new byte[challengeDecoder.encodedChallengeLength()];\n                challengeDecoder.getEncodedChallenge(encodedChallenge, 0, challengeDecoder.encodedChallengeLength());\n\n                onChallenge(challengeDecoder.clusterSessionId(), encodedChallenge);\n                break;\n\n            case SessionEventDecoder.TEMPLATE_ID:\n                sessionEventDecoder.wrapAndApplyHeader(buffer, offset, messageHeaderDecoder);\n\n                final long correlationId = sessionEventDecoder.correlationId();\n                final int leaderMemberId = sessionEventDecoder.leaderMemberId();\n                final EventCode eventCode = sessionEventDecoder.code();\n                final String detail = sessionEventDecoder.detail();\n\n                onSessionEvent(correlationId, leaderMemberId, eventCode, detail);\n                break;\n        }\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private void onBackupResponse(\n        final long correlationId,\n        final long logRecordingId,\n        final long logLeadershipTermId,\n        final long logTermBaseLogPosition,\n        final long lastLeadershipTermId,\n        final long lastTermBaseLogPosition,\n        final int commitPositionCounterId,\n        final int leaderMemberId,\n        final int memberId,\n        final BackupResponseDecoder backupResponseDecoder)\n    {\n        if (NULL_VALUE == memberId)\n        {\n            ctx.errorHandler().onError(new ClusterEvent(\n                \"onBackupResponse(): memberId is null, retrying for compatible node\"));\n            state(RESET_BACKUP, epochClock.time());\n            return;\n        }\n        else if (!logSourceValidator.isAcceptable(leaderMemberId, memberId))\n        {\n            consensusPublicationGroup.closeAndExcludeCurrent();\n            state(RESET_BACKUP, epochClock.time());\n            return;\n        }\n        else if (null != logSupplierMember && logSupplierMember.id() != memberId)\n        {\n            state(RESET_BACKUP, epochClock.time());\n            return;\n        }\n\n        if (BACKUP_QUERY == state && correlationId == this.correlationId)\n        {\n            final BackupResponseDecoder.SnapshotsDecoder snapshotsDecoder = backupResponseDecoder.snapshots();\n\n            if (snapshotsDecoder.count() > 0)\n            {\n                for (final BackupResponseDecoder.SnapshotsDecoder snapshot : snapshotsDecoder)\n                {\n                    final RecordingLog.Entry entry = recordingLog.getLatestSnapshot(snapshot.serviceId());\n\n                    if (null != entry && snapshot.logPosition() == entry.logPosition)\n                    {\n                        continue;\n                    }\n\n                    snapshotsToRetrieve.add(new RecordingLog.Snapshot(\n                        snapshot.recordingId(),\n                        snapshot.leadershipTermId(),\n                        snapshot.termBaseLogPosition(),\n                        snapshot.logPosition(),\n                        snapshot.timestamp(),\n                        snapshot.serviceId()));\n                }\n            }\n\n            if (null == logSupplierMember ||\n                memberId != logSupplierMember.id() ||\n                logRecordingId != clusterLogRecordingId)\n            {\n                clusterLogRecordingId = logRecordingId;\n                leaderLogEntry = new RecordingLog.Entry(\n                    logRecordingId,\n                    logLeadershipTermId,\n                    logTermBaseLogPosition,\n                    NULL_POSITION,\n                    NULL_TIMESTAMP,\n                    NULL_VALUE,\n                    RecordingLog.ENTRY_TYPE_TERM,\n                    null,\n                    true,\n                    -1);\n            }\n\n            final RecordingLog.Entry lastTerm = recordingLog.findLastTerm();\n\n            if (null == lastTerm || lastLeadershipTermId != lastTerm.leadershipTermId)\n            {\n                leaderLastTermEntry = new RecordingLog.Entry(\n                    logRecordingId,\n                    lastLeadershipTermId,\n                    lastTermBaseLogPosition,\n                    NULL_POSITION,\n                    NULL_TIMESTAMP,\n                    NULL_VALUE,\n                    RecordingLog.ENTRY_TYPE_TERM,\n                    null,\n                    true,\n                    -1);\n            }\n\n            timeOfLastBackupQueryMs = 0;\n            this.correlationId = NULL_VALUE;\n            leaderCommitPositionCounterId = commitPositionCounterId;\n\n            clusterMembers = ClusterMember.parse(backupResponseDecoder.clusterMembers());\n            ClusterMember.setIsLeader(clusterMembers, leaderMemberId);\n\n            logSupplierMember = ClusterMember.findMember(clusterMembers, memberId);\n            if (null == logSupplierMember)\n            {\n                throw new ClusterException(memberId + \" not found in \" + Arrays.toString(clusterMembers));\n            }\n\n            logSupplierMember.leadershipTermId(logLeadershipTermId);\n\n            if (null != eventsListener)\n            {\n                eventsListener.onBackupResponse(clusterMembers, logSupplierMember, snapshotsToRetrieve);\n            }\n\n            if (null == clusterArchive)\n            {\n                CloseHelper.close(clusterArchiveAsyncConnect);\n\n                final AeronArchive.Context context = ctx.clusterArchiveContext().clone();\n                final ChannelUri logSupplierArchiveUri = ChannelUri.parse(context.controlRequestChannel());\n                logSupplierArchiveUri.put(ENDPOINT_PARAM_NAME, logSupplierMember.archiveEndpoint());\n                context.controlRequestChannel(logSupplierArchiveUri.toString());\n\n                if (null != logSupplierMember.archiveResponseEndpoint())\n                {\n                    final ChannelUri logSupplierResponseUri = ChannelUri.parse(context.controlResponseChannel());\n                    logSupplierResponseUri.put(MDC_CONTROL_MODE_PARAM_NAME, CONTROL_MODE_RESPONSE);\n                    logSupplierResponseUri.remove(ENDPOINT_PARAM_NAME);\n                    logSupplierResponseUri.put(MDC_CONTROL_PARAM_NAME, logSupplierMember.archiveResponseEndpoint());\n                    context.controlResponseChannel(logSupplierResponseUri.toString());\n                }\n\n                clusterArchiveAsyncConnect = AeronArchive.asyncConnect(context);\n            }\n\n            final long nowMs = epochClock.time();\n            timeOfLastProgressMs = nowMs;\n            state(snapshotsToRetrieve.isEmpty() ? LIVE_LOG_RECORD : SNAPSHOT_RETRIEVE, nowMs);\n        }\n    }\n\n    private void onChallenge(final long clusterSessionId, final byte[] encodedChallenge)\n    {\n        final byte[] challengeResponse = ctx.credentialsSupplier().onChallenge(encodedChallenge);\n\n        correlationId = ctx.aeron().nextCorrelationId();\n        consensusPublisher.challengeResponse(\n            consensusPublicationGroup.current(), this.correlationId, clusterSessionId, challengeResponse);\n    }\n\n    private void onSessionEvent(\n        final long correlationId,\n        final int leaderMemberId,\n        final EventCode eventCode,\n        final String detail)\n    {\n        if (this.correlationId == correlationId)\n        {\n            if (EventCode.ERROR == eventCode || EventCode.AUTHENTICATION_REJECTED == eventCode)\n            {\n                throw new ClusterException(eventCode + \": \" + detail);\n            }\n            else if (EventCode.REDIRECT == eventCode)\n            {\n                consensusPublicationGroup.closeAndExcludeCurrent();\n                state(RESET_BACKUP, epochClock.time());\n            }\n        }\n    }\n\n    private int slowTick(final long nowMs)\n    {\n        int workCount = aeronClientInvoker.invoke();\n        if (aeron.isClosed())\n        {\n            throw new AgentTerminationException(\"unexpected Aeron close\");\n        }\n\n        if (nowMs >= markFileUpdateDeadlineMs)\n        {\n            markFileUpdateDeadlineMs = nowMs + MARK_FILE_UPDATE_INTERVAL_MS;\n            markFile.updateActivityTimestamp(nowMs);\n        }\n\n        workCount += pollBackupArchiveEvents();\n\n        if (NULL_VALUE == correlationId && null != clusterArchive)\n        {\n            final String errorResponse = clusterArchive.pollForErrorResponse();\n            if (null != errorResponse)\n            {\n                ctx.countedErrorHandler().onError(new ClusterEvent(\"cluster archive error: \" + errorResponse));\n                state(RESET_BACKUP, nowMs);\n            }\n        }\n\n        return workCount;\n    }\n\n    private int resetBackup(final long nowMs)\n    {\n        timeOfLastProgressMs = nowMs;\n\n        if (NULL_VALUE == coolDownDeadlineMs)\n        {\n            coolDownDeadlineMs = nowMs + coolDownIntervalMs;\n            reset();\n            return 1;\n        }\n        else if (nowMs > coolDownDeadlineMs)\n        {\n            coolDownDeadlineMs = NULL_VALUE;\n            state(BACKUP_QUERY, nowMs);\n            return 1;\n        }\n\n        return 0;\n    }\n\n    private int backupQuery(final long nowMs)\n    {\n        if (null == consensusPublicationGroup.current() || nowMs > (timeOfLastBackupQueryMs + backupResponseTimeoutMs))\n        {\n            consensusPublicationGroup.next(aeron);\n            correlationId = NULL_VALUE;\n            timeOfLastBackupQueryMs = nowMs;\n            return 1;\n        }\n        else if (NULL_VALUE == correlationId && consensusPublicationGroup.isConnected())\n        {\n            final long correlationId = aeron.nextCorrelationId();\n\n            if (consensusPublisher.backupQuery(\n                consensusPublicationGroup.current(),\n                correlationId,\n                ctx.consensusStreamId(),\n                AeronCluster.Configuration.PROTOCOL_SEMANTIC_VERSION,\n                Long.MAX_VALUE,\n                ctx.consensusChannel(),\n                ctx.credentialsSupplier().encodedCredentials()))\n            {\n                timeOfLastBackupQueryMs = nowMs;\n                this.correlationId = correlationId;\n\n                return 1;\n            }\n        }\n\n        return 0;\n    }\n\n    private int snapshotRetrieve(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (null == clusterArchive)\n        {\n            final int step = clusterArchiveAsyncConnect.step();\n            clusterArchive = clusterArchiveAsyncConnect.poll();\n            return null == clusterArchive ? clusterArchiveAsyncConnect.step() - step : 1;\n        }\n\n        if (null == snapshotReplication)\n        {\n            final ChannelUri replicationUri = ChannelUri.parse(ctx.catchupChannel());\n            replicationUri.put(ENDPOINT_PARAM_NAME, ctx.catchupEndpoint());\n            snapshotReplication = new SnapshotReplication(\n                backupArchive,\n                clusterArchive.context().controlRequestStreamId(),\n                clusterArchive.context().controlRequestChannel(),\n                replicationUri.toString(),\n                ctx.replicationProgressTimeoutNs(),\n                ctx.replicationProgressIntervalNs());\n\n            snapshotsToRetrieve.forEach(snapshotReplication::addSnapshot);\n            workCount++;\n        }\n\n        workCount += snapshotReplication.poll(TimeUnit.MILLISECONDS.toNanos(nowMs));\n        workCount += pollBackupArchiveEvents();\n        timeOfLastProgressMs = nowMs;\n\n        if (snapshotReplication.isComplete())\n        {\n            snapshotsRetrieved.addAll(snapshotReplication.snapshotsRetrieved());\n\n            snapshotReplication.close();\n            snapshotReplication = null;\n\n            state(LIVE_LOG_RECORD, nowMs);\n            workCount++;\n        }\n\n        return workCount;\n    }\n\n    private int liveLogRecord(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == liveLogRecordingSubscriptionId)\n        {\n            if (NULL_VALUE == liveLogRecordingSessionId)\n            {\n                liveLogRecordingSessionId = BitUtil.generateRandomisedId();\n            }\n\n            final String catchupEndpoint = ctx.catchupEndpoint();\n            if (catchupEndpoint.endsWith(\":0\"))\n            {\n                if (null == recordingSubscription)\n                {\n                    final ChannelUri channelUri = ChannelUri.parse(ctx.catchupChannel());\n                    channelUri.remove(ENDPOINT_PARAM_NAME);\n                    channelUri.put(TAGS_PARAM_NAME, aeron.nextCorrelationId() + \",\" + aeron.nextCorrelationId());\n                    channelUri.put(SESSION_ID_PARAM_NAME, Integer.toString(liveLogRecordingSessionId));\n                    recordingChannel = channelUri.toString();\n\n                    channelUri.put(ENDPOINT_PARAM_NAME, catchupEndpoint);\n                    recordingSubscription = aeron.addSubscription(channelUri.toString(), ctx.logStreamId());\n                    timeOfLastProgressMs = nowMs;\n                    return 1;\n                }\n                else\n                {\n                    final String resolvedEndpoint = recordingSubscription.resolvedEndpoint();\n                    if (null == resolvedEndpoint)\n                    {\n                        return 0;\n                    }\n\n                    final ChannelUri channelUri = ChannelUri.parse(ctx.catchupChannel());\n                    channelUri.put(ENDPOINT_PARAM_NAME, catchupEndpoint);\n                    channelUri.replaceEndpointWildcardPort(resolvedEndpoint);\n                    channelUri.put(SESSION_ID_PARAM_NAME, Integer.toString(liveLogRecordingSessionId));\n\n                    replayChannel = channelUri.toString();\n                }\n            }\n            else\n            {\n                final ChannelUri channelUri = ChannelUri.parse(ctx.catchupChannel());\n                channelUri.put(ENDPOINT_PARAM_NAME, catchupEndpoint);\n                channelUri.put(SESSION_ID_PARAM_NAME, Integer.toString(liveLogRecordingSessionId));\n                replayChannel = channelUri.toString();\n                recordingChannel = replayChannel;\n            }\n\n            liveLogRecordingSubscriptionId = startLogRecording();\n        }\n\n        timeOfLastProgressMs = nowMs;\n        state(LIVE_LOG_REPLAY, nowMs);\n        workCount += 1;\n\n        return workCount;\n    }\n\n    private int liveLogReplay(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == liveLogRecordingId)\n        {\n            if (null == clusterArchive)\n            {\n                final int step = clusterArchiveAsyncConnect.step();\n                clusterArchive = clusterArchiveAsyncConnect.poll();\n                return null == clusterArchive ? clusterArchiveAsyncConnect.step() - step : 1;\n            }\n\n            if (NULL_VALUE == correlationId)\n            {\n                final RecordingLog.Entry logEntry = recordingLog.findLastTerm();\n                final long startPosition = replayStartPosition(logEntry);\n                final long replayId = ctx.aeron().nextCorrelationId();\n\n                if (clusterArchive.archiveProxy().boundedReplay(\n                    clusterLogRecordingId,\n                    startPosition,\n                    NULL_LENGTH,\n                    leaderCommitPositionCounterId,\n                    replayChannel,\n                    ctx.logStreamId(),\n                    replayId,\n                    clusterArchive.controlSessionId()))\n                {\n                    replayChannel = null;\n                    correlationId = replayId;\n                    timeOfLastProgressMs = nowMs;\n                    workCount++;\n                }\n            }\n            else if (NULL_VALUE == liveLogReplaySessionId)\n            {\n                final ControlResponsePoller poller = clusterArchive.controlResponsePoller();\n                if (0 != poller.poll() && poller.isPollComplete() &&\n                    poller.controlSessionId() == clusterArchive.controlSessionId() &&\n                    poller.correlationId() == correlationId)\n                {\n                    switch (poller.code())\n                    {\n                        case OK ->\n                        {\n                            liveLogReplaySessionId = poller.relevantId();\n                            timeOfLastProgressMs = nowMs;\n                            workCount++;\n                        }\n                        case ERROR -> throwReplayFailedException(poller);\n                        default -> throw new ClusterException(\"Live log replay failed: \" + poller.code());\n                    }\n                }\n            }\n            else if (NULL_COUNTER_ID == liveLogRecordingCounterId)\n            {\n                final CountersReader countersReader = aeron.countersReader();\n\n                liveLogRecordingCounterId = RecordingPos.findCounterIdBySession(\n                    countersReader, (int)liveLogReplaySessionId, backupArchive.archiveId());\n                if (NULL_COUNTER_ID != liveLogRecordingCounterId)\n                {\n                    liveLogPositionCounter.setRelease(countersReader.getCounterValue(liveLogRecordingCounterId));\n                    liveLogRecordingId = RecordingPos.getRecordingId(countersReader, liveLogRecordingCounterId);\n                    timeOfLastBackupQueryMs = nowMs;\n                    timeOfLastProgressMs = nowMs;\n                    state(UPDATE_RECORDING_LOG, nowMs);\n                    workCount++;\n                }\n                else\n                {\n                    final ControlResponsePoller poller = clusterArchive.controlResponsePoller();\n                    if (0 != poller.poll() && poller.isPollComplete() &&\n                        poller.controlSessionId() == clusterArchive.controlSessionId() &&\n                        ControlResponseCode.ERROR == poller.code())\n                    {\n                        throwReplayFailedException(poller);\n                    }\n                }\n            }\n        }\n        else\n        {\n            timeOfLastProgressMs = nowMs;\n            state(UPDATE_RECORDING_LOG, nowMs);\n        }\n\n        return workCount;\n    }\n\n    private void throwReplayFailedException(final ControlResponsePoller poller)\n    {\n        throw new ClusterException(\"Live log replay failed (replaySessionId=\" + liveLogReplaySessionId + \"):\" +\n            \" errorMessage=\" + poller.errorMessage() + \", errorCode=\" +\n            ArchiveException.errorCodeAsString((int)poller.relevantId()));\n    }\n\n    private int updateRecordingLog(final long nowMs)\n    {\n        boolean wasRecordingLogUpdated = false;\n        try\n        {\n            final long snapshotLeadershipTermId = snapshotsRetrieved.isEmpty() ?\n                NULL_VALUE : snapshotsRetrieved.get(0).leadershipTermId();\n\n            if (null != leaderLogEntry &&\n                recordingLog.isUnknown(leaderLogEntry.leadershipTermId) &&\n                leaderLogEntry.leadershipTermId <= snapshotLeadershipTermId)\n            {\n                recordingLog.appendTerm(\n                    liveLogRecordingId,\n                    leaderLogEntry.leadershipTermId,\n                    leaderLogEntry.termBaseLogPosition,\n                    leaderLogEntry.timestamp);\n\n                wasRecordingLogUpdated = true;\n                leaderLogEntry = null;\n            }\n\n            if (!snapshotsRetrieved.isEmpty())\n            {\n                for (int i = snapshotsRetrieved.size() - 1; i >= 0; i--)\n                {\n                    final RecordingLog.Snapshot snapshot = snapshotsRetrieved.get(i);\n\n                    recordingLog.appendSnapshot(\n                        snapshot.recordingId(),\n                        snapshot.leadershipTermId(),\n                        snapshot.termBaseLogPosition(),\n                        snapshot.logPosition(),\n                        snapshot.timestamp(),\n                        snapshot.serviceId());\n                }\n\n                wasRecordingLogUpdated = true;\n            }\n\n            if (null != leaderLastTermEntry && recordingLog.isUnknown(leaderLastTermEntry.leadershipTermId))\n            {\n                recordingLog.appendTerm(\n                    liveLogRecordingId,\n                    leaderLastTermEntry.leadershipTermId,\n                    leaderLastTermEntry.termBaseLogPosition,\n                    leaderLastTermEntry.timestamp);\n\n                wasRecordingLogUpdated = true;\n                leaderLastTermEntry = null;\n            }\n        }\n        catch (final Exception ex)\n        {\n            ctx.countedErrorHandler().onError(ex);\n            throw new AgentTerminationException(\"failed to update recording log\");\n        }\n\n        if (wasRecordingLogUpdated)\n        {\n            recordingLog.force(2);\n            if (!snapshotsRetrieved.isEmpty())\n            {\n                ctx.snapshotRetrieveCounter().incrementRelease();\n            }\n\n            if (null != eventsListener)\n            {\n                eventsListener.onUpdatedRecordingLog(recordingLog, snapshotsRetrieved);\n            }\n        }\n\n        snapshotsRetrieved.clear();\n        snapshotsToRetrieve.clear();\n\n        timeOfLastProgressMs = nowMs;\n        nextQueryDeadlineMsCounter.setRelease(nowMs + backupQueryIntervalMs);\n        state(BACKING_UP, nowMs);\n\n        return 1;\n    }\n\n    private int backingUp(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (nowMs > nextQueryDeadlineMsCounter.get())\n        {\n            timeOfLastBackupQueryMs = nowMs;\n            timeOfLastProgressMs = nowMs;\n            state(BACKUP_QUERY, nowMs);\n            workCount += 1;\n        }\n\n        if (NULL_COUNTER_ID != liveLogRecordingCounterId)\n        {\n            final long liveLogPosition = aeron.countersReader().getCounterValue(liveLogRecordingCounterId);\n\n            if (liveLogPositionCounter.proposeMaxRelease(liveLogPosition))\n            {\n                if (null != eventsListener)\n                {\n                    eventsListener.onLiveLogProgress(liveLogRecordingId, liveLogRecordingCounterId, liveLogPosition);\n                }\n\n                workCount += 1;\n            }\n        }\n\n        return workCount;\n    }\n\n    private void state(final ClusterBackup.State newState, final long nowMs)\n    {\n        logStateChange(state, newState, nowMs);\n\n        if (BACKUP_QUERY == newState && null != eventsListener)\n        {\n            eventsListener.onBackupQuery();\n        }\n\n        if (!stateCounter.isClosed())\n        {\n            stateCounter.setRelease(newState.code());\n        }\n\n        state = newState;\n        correlationId = NULL_VALUE;\n    }\n\n    private void logStateChange(\n        final ClusterBackup.State oldState, final ClusterBackup.State newState, final long nowMs)\n    {\n        //System.out.println(\"ClusterBackup: \" + oldState + \" -> \" + newState + \" nowMs=\" + nowMs);\n    }\n\n    private int pollBackupArchiveEvents()\n    {\n        int workCount = 0;\n\n        if (null != backupArchive)\n        {\n            final RecordingSignalPoller poller = this.recordingSignalPoller;\n            workCount += poller.poll();\n\n            if (poller.isPollComplete())\n            {\n                final int templateId = poller.templateId();\n\n                if (ControlResponseDecoder.TEMPLATE_ID == templateId && poller.code() == ControlResponseCode.ERROR)\n                {\n                    final ArchiveException ex = new ArchiveException(\n                        poller.errorMessage(), (int)poller.relevantId(), poller.correlationId());\n\n                    if (ex.errorCode() == ArchiveException.STORAGE_SPACE)\n                    {\n                        ctx.countedErrorHandler().onError(ex);\n                        throw new AgentTerminationException();\n                    }\n                    else\n                    {\n                        throw ex;\n                    }\n                }\n                else if (RecordingSignalEventDecoder.TEMPLATE_ID == templateId && null != snapshotReplication)\n                {\n                    snapshotReplication.onSignal(\n                        poller.correlationId(),\n                        poller.recordingId(),\n                        poller.recordingPosition(),\n                        poller.recordingSignal());\n                }\n            }\n            else if (0 == workCount && !poller.subscription().isConnected())\n            {\n                ctx.countedErrorHandler().onError(new ClusterEvent(\"local archive not connected\"));\n                throw new AgentTerminationException();\n            }\n        }\n\n        return workCount;\n    }\n\n    private long startLogRecording()\n    {\n        final RecordingLog.Entry logEntry = recordingLog.findLastTerm();\n        final int streamId = ctx.logStreamId();\n        final long recordingSubscriptionId = null == logEntry ?\n            backupArchive.startRecording(recordingChannel, streamId, REMOTE, true) :\n            backupArchive.extendRecording(logEntry.recordingId, recordingChannel, streamId, REMOTE, true);\n\n        CloseHelper.close(ctx.countedErrorHandler(), recordingSubscription);\n        recordingChannel = null;\n        recordingSubscription = null;\n\n        return recordingSubscriptionId;\n    }\n\n    private boolean hasProgressStalled(final long nowMs)\n    {\n        return (NULL_COUNTER_ID == liveLogRecordingCounterId) &&\n            (nowMs > (timeOfLastProgressMs + backupProgressTimeoutMs));\n    }\n\n    private long replayStartPosition(final RecordingLog.Entry lastTerm)\n    {\n        return replayStartPosition(lastTerm, snapshotsRetrieved, ctx.initialReplayStart(), backupArchive);\n    }\n\n    static long replayStartPosition(\n        final RecordingLog.Entry lastTerm,\n        final List<RecordingLog.Snapshot> snapshotsRetrieved,\n        final ClusterBackup.Configuration.ReplayStart replayStart,\n        final AeronArchive backupArchive)\n    {\n        if (null != lastTerm)\n        {\n            return backupArchive.getStopPosition(lastTerm.recordingId);\n        }\n\n        if (ClusterBackup.Configuration.ReplayStart.BEGINNING == replayStart)\n        {\n            return NULL_POSITION;\n        }\n\n        long replayStartPosition = NULL_POSITION;\n        for (final RecordingLog.Snapshot snapshot : snapshotsRetrieved)\n        {\n            if (ConsensusModule.Configuration.SERVICE_ID == snapshot.serviceId())\n            {\n                if (replayStartPosition < snapshot.logPosition())\n                {\n                    replayStartPosition = snapshot.logPosition();\n                }\n            }\n        }\n\n        return replayStartPosition;\n    }\n\n    private void runTerminationHook(final AgentTerminationException ex)\n    {\n        try\n        {\n            ctx.terminationHook().run();\n        }\n        catch (final Exception e)\n        {\n            ctx.countedErrorHandler().onError(e);\n        }\n\n        throw ex;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterBackupEventsListener.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport java.util.List;\n\n/**\n * Listener which can be registered via {@link ClusterBackup.Context} for tracking backup progress.\n */\npublic interface ClusterBackupEventsListener\n{\n    /**\n     * Backup has moved into backup query state. Backup process has been started.\n     */\n    void onBackupQuery();\n\n    /**\n     * Possible failure of cluster leader detected.\n     *\n     * @param ex the underlying exception.\n     */\n    void onPossibleFailure(Exception ex);\n\n    /**\n     * Backup response was received for a backup query.\n     *\n     * @param clusterMembers      in the backup response.\n     * @param logSourceMember     to be used to replicate data from.\n     * @param snapshotsToRetrieve snapshots to be retrieved.\n     */\n    void onBackupResponse(\n        ClusterMember[] clusterMembers, ClusterMember logSourceMember, List<RecordingLog.Snapshot> snapshotsToRetrieve);\n\n    /**\n     * Updated recording log.\n     *\n     * @param recordingLog       that was updated.\n     * @param snapshotsRetrieved the snapshots that were retrieved.\n     */\n    void onUpdatedRecordingLog(RecordingLog recordingLog, List<RecordingLog.Snapshot> snapshotsRetrieved);\n\n    /**\n     * Update to the live log position as recorded to the local archive.\n     *\n     * @param recordingId           of the live log.\n     * @param recordingPosCounterId {@link io.aeron.archive.status.RecordingPos} counter id for the live log.\n     * @param logPosition           of the live log.\n     */\n    void onLiveLogProgress(long recordingId, long recordingPosCounterId, long logPosition);\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterBackupMediaDriver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.Archive;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Cluster Backup media driver which is an aggregate of a {@link MediaDriver}, {@link Archive},\n * and a {@link ClusterBackup}.\n */\npublic class ClusterBackupMediaDriver implements AutoCloseable\n{\n    private final MediaDriver driver;\n    private final Archive archive;\n    private final ClusterBackup clusterBackup;\n\n    ClusterBackupMediaDriver(final MediaDriver driver, final Archive archive, final ClusterBackup clusterBackup)\n    {\n        this.driver = driver;\n        this.archive = archive;\n        this.clusterBackup = clusterBackup;\n    }\n\n    /**\n     * Launch the cluster backup media driver aggregate and await a shutdown signal.\n     *\n     * @param args command line argument which is a list for properties files as URLs or filenames.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            ClusterBackupMediaDriver ignore = launch(\n                new MediaDriver.Context().terminationHook(barrier::signalAll),\n                new Archive.Context(),\n                new ClusterBackup.Context().terminationHook(barrier::signalAll)))\n        {\n            barrier.await();\n            System.out.println(\"Shutdown ClusterBackupMediaDriver...\");\n        }\n    }\n\n    /**\n     * Launch a new {@link ClusterBackupMediaDriver} with default contexts.\n     *\n     * @return a new {@link ClusterBackupMediaDriver} with default contexts.\n     */\n    public static ClusterBackupMediaDriver launch()\n    {\n        return launch(new MediaDriver.Context(), new Archive.Context(), new ClusterBackup.Context());\n    }\n\n    /**\n     * Launch a new {@link ClusterBackupMediaDriver} with provided contexts.\n     *\n     * @param driverCtx        for configuring the {@link MediaDriver}.\n     * @param archiveCtx       for configuring the {@link Archive}.\n     * @param clusterBackupCtx for the configuration of the {@link ClusterBackup}.\n     * @return a new {@link ClusterBackupMediaDriver} with the provided contexts.\n     */\n    public static ClusterBackupMediaDriver launch(\n        final MediaDriver.Context driverCtx,\n        final Archive.Context archiveCtx,\n        final ClusterBackup.Context clusterBackupCtx)\n    {\n        MediaDriver driver = null;\n        Archive archive = null;\n        ClusterBackup clusterBackup = null;\n\n        try\n        {\n            driver = MediaDriver.launch(driverCtx\n                .spiesSimulateConnection(true));\n\n            final int errorCounterId = SystemCounterDescriptor.ERRORS.id();\n            final AtomicCounter errorCounter = null != archiveCtx.errorCounter() ?\n                archiveCtx.errorCounter() : new AtomicCounter(driverCtx.countersValuesBuffer(), errorCounterId);\n\n            final ErrorHandler errorHandler = null != archiveCtx.errorHandler() ?\n                archiveCtx.errorHandler() : driverCtx.errorHandler();\n\n            archive = Archive.launch(archiveCtx\n                .aeronDirectoryName(driverCtx.aeronDirectoryName())\n                .mediaDriverAgentInvoker(driver.sharedAgentInvoker())\n                .errorHandler(errorHandler)\n                .errorCounter(errorCounter));\n\n            clusterBackup = ClusterBackup.launch(clusterBackupCtx\n                .aeronDirectoryName(driverCtx.aeronDirectoryName()));\n\n            return new ClusterBackupMediaDriver(driver, archive, clusterBackup);\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietCloseAll(clusterBackup, archive, driver);\n            throw ex;\n        }\n    }\n\n    /**\n     * Get the {@link MediaDriver} used in the aggregate.\n     *\n     * @return the {@link MediaDriver} used in the aggregate.\n     */\n    public MediaDriver mediaDriver()\n    {\n        return driver;\n    }\n\n    /**\n     * Get the {@link Archive} used in the aggregate.\n     *\n     * @return the {@link Archive} used in the aggregate.\n     */\n    public Archive archive()\n    {\n        return archive;\n    }\n\n    /**\n     * Get the {@link ClusterBackup} used in the aggregate.\n     *\n     * @return the {@link ClusterBackup} used in the aggregate.\n     */\n    public ClusterBackup clusterBackup()\n    {\n        return clusterBackup;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.closeAll(clusterBackup, archive, driver);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterClientSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Publication;\n\n/**\n * Representation of a client session to an Aeron Cluster for use in an {@link ConsensusModuleExtension}.\n * @see ConsensusModuleControl#getClientSession(long)\n * @see ConsensusModuleControl#closeClusterSession(long)\n */\npublic interface ClusterClientSession\n{\n    /**\n     * Cluster session identifier.\n     *\n     * @return cluster session identifier.\n     */\n    long id();\n\n    /**\n     * Determine if a cluster client session is open, so it is active for operations.\n     *\n     * @return true of the session is open otherwise false.\n     */\n    boolean isOpen();\n\n    /**\n     * Authenticated principal for a session encoded in byte form.\n     *\n     * @return authenticated principal for a session encoded in byte form.\n     */\n    byte[] encodedPrincipal();\n\n    /**\n     * Response {@link Publication} to be used for sending responses privately to a client.\n     *\n     * @return response {@link Publication} to be used for sending responses privately to a client.\n     */\n    Publication responsePublication();\n\n    /**\n     * The time last activity has been recorded for a session to determine if it is active.\n     *\n     * @return time last activity has been recorded for a session to determine if it is active.\n     */\n    long timeOfLastActivityNs();\n\n    /**\n     * The time last activity recorded for a session to determine if it is active. This should be updated on valid\n     * ingress.\n     *\n     * @param timeNs of last activity recorded for a session to determine if it is active.\n     */\n    void timeOfLastActivityNs(long timeNs);\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.*;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.service.ClusterCounters;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport org.agrona.*;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.nio.MappedByteBuffer;\nimport java.nio.charset.StandardCharsets;\n\nimport static io.aeron.CncFileDescriptor.*;\n\n/**\n * Toggle control {@link ToggleState}s for a cluster node such as {@link ToggleState#SUSPEND} or\n * {@link ToggleState#RESUME}. This can only be applied to the {@link io.aeron.cluster.service.Cluster.Role#LEADER}.\n */\npublic final class ClusterControl\n{\n    /**\n     * Toggle states for controlling the cluster node once it has entered the active state after initialising.\n     * The toggle can only we switched into a new state from {@link #NEUTRAL} and will be reset by the\n     * {@link io.aeron.cluster.ConsensusModule} once the triggered action is complete.\n     */\n    public enum ToggleState\n    {\n        /**\n         * Inactive state, not accepting new actions.\n         */\n        INACTIVE(0),\n\n        /**\n         * Neutral state ready to accept a new action.\n         */\n        NEUTRAL(1),\n\n        /**\n         * Suspend processing of ingress and timers.\n         */\n        SUSPEND(2),\n\n        /**\n         * Resume processing of ingress and timers.\n         */\n        RESUME(3),\n\n        /**\n         * Take a snapshot of cluster state.\n         */\n        SNAPSHOT(4),\n\n        /**\n         * Shut down the cluster in an orderly fashion by taking a snapshot first then terminating.\n         */\n        SHUTDOWN(5),\n\n        /**\n         * Abort processing and terminate the cluster without taking a snapshot.\n         */\n        ABORT(6),\n\n        /**\n         * Trigger a snapshot that will only occur on a cluster standby.\n         */\n        STANDBY_SNAPSHOT(7);\n\n        private final int code;\n\n        private static final ToggleState[] STATES = values();\n\n        ToggleState(final int code)\n        {\n            if (code != ordinal())\n            {\n                throw new IllegalArgumentException(name() + \" - code must equal ordinal value: code=\" + code);\n            }\n\n            this.code = code;\n        }\n\n        /**\n         * Code to be used as the indicator in the control toggle counter.\n         *\n         * @return code to be used as the indicator in the control toggle counter.\n         */\n        public final int code()\n        {\n            return code;\n        }\n\n        /**\n         * Toggle the control counter to trigger the requested {@link ToggleState}.\n         * <p>\n         * This action is thread safe and will succeed if the toggle is in the {@link ToggleState#NEUTRAL} state,\n         * or if toggle is {@link ToggleState#SUSPEND} and requested state is {@link ToggleState#RESUME}.\n         *\n         * @param controlToggle to change to the trigger state.\n         * @return true if the counter toggles or false if it is in a state other than {@link ToggleState#NEUTRAL}.\n         */\n        public final boolean toggle(final AtomicCounter controlToggle)\n        {\n            if (code() == RESUME.code() && controlToggle.get() == SUSPEND.code())\n            {\n                return controlToggle.compareAndSet(SUSPEND.code(), RESUME.code());\n            }\n\n            return controlToggle.compareAndSet(NEUTRAL.code(), code());\n        }\n\n        /**\n         * Reset the toggle to the {@link #NEUTRAL} state.\n         *\n         * @param controlToggle to be reset.\n         */\n        public static void reset(final AtomicCounter controlToggle)\n        {\n            controlToggle.set(NEUTRAL.code());\n        }\n\n        /**\n         * Activate the toggle by setting it to the {@link #NEUTRAL} state.\n         *\n         * @param controlToggle to be activated.\n         */\n        public static void activate(final AtomicCounter controlToggle)\n        {\n            controlToggle.set(NEUTRAL.code());\n        }\n\n        /**\n         * Activate the toggle by setting it to the {@link #INACTIVE} state.\n         *\n         * @param controlToggle to be deactivated.\n         */\n        public static void deactivate(final AtomicCounter controlToggle)\n        {\n            controlToggle.set(INACTIVE.code());\n        }\n\n        /**\n         * Get the {@link ToggleState} for a given control toggle.\n         *\n         * @param controlToggle to get the current state for.\n         * @return the state for the current control toggle.\n         * @throws ClusterException if the counter is not one of the valid values.\n         */\n        public static ToggleState get(final AtomicCounter controlToggle)\n        {\n            if (controlToggle.isClosed())\n            {\n                throw new ClusterException(\"counter is closed\");\n            }\n\n            final long toggleValue = controlToggle.get();\n            if (toggleValue < 0 || toggleValue > (STATES.length - 1))\n            {\n                throw new ClusterException(\"invalid toggle value: \" + toggleValue);\n            }\n\n            return STATES[(int)toggleValue];\n        }\n    }\n\n    private ClusterControl()\n    {\n    }\n\n    /**\n     * Counter type id for the control toggle.\n     */\n    public static final int CONTROL_TOGGLE_TYPE_ID = AeronCounters.CLUSTER_CONTROL_TOGGLE_TYPE_ID;\n\n    /**\n     * Map a {@link CountersReader} over the provided {@link File} for the CnC file.\n     *\n     * @param cncFile for the counters.\n     * @return a {@link CountersReader} over the provided CnC file.\n     */\n    public static CountersReader mapCounters(final File cncFile)\n    {\n        final MappedByteBuffer cncByteBuffer = IoUtil.mapExistingFile(cncFile, \"cnc\");\n        final DirectBuffer cncMetaData = createMetaDataBuffer(cncByteBuffer);\n        final int cncVersion = cncMetaData.getInt(cncVersionOffset(0));\n\n        CncFileDescriptor.checkVersion(cncVersion);\n\n        return new CountersReader(\n            createCountersMetaDataBuffer(cncByteBuffer, cncMetaData),\n            createCountersValuesBuffer(cncByteBuffer, cncMetaData),\n            StandardCharsets.US_ASCII);\n    }\n\n    /**\n     * Find the control toggle counter or return null if not found.\n     *\n     * @param counters  to search within.\n     * @param clusterId to which the allocated counter belongs.\n     * @return the control toggle counter or return null if not found.\n     */\n    public static AtomicCounter findControlToggle(final CountersReader counters, final int clusterId)\n    {\n        final int counterId = ClusterCounters.find(counters, CONTROL_TOGGLE_TYPE_ID, clusterId);\n        if (Aeron.NULL_VALUE != counterId)\n        {\n            return new AtomicCounter(counters.valuesBuffer(), counterId, null);\n        }\n\n        return null;\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        checkUsage(args);\n\n        final ToggleState toggleState = ToggleState.valueOf(args[0].toUpperCase());\n\n        final File cncFile = CommonContext.newDefaultCncFile();\n        System.out.println(\"Command `n Control file \" + cncFile);\n\n        final CountersReader countersReader = mapCounters(cncFile);\n        final int clusterId = ClusteredServiceContainer.Configuration.clusterId();\n        final AtomicCounter controlToggle = findControlToggle(countersReader, clusterId);\n\n        if (null == controlToggle)\n        {\n            System.out.println(\"Failed to find control toggle\");\n            System.exit(0);\n        }\n\n        if (toggleState.toggle(controlToggle))\n        {\n            System.out.println(toggleState + \" toggled successfully\");\n        }\n        else\n        {\n            System.out.println(toggleState + \" did NOT toggle: current state=\" + ToggleState.get(controlToggle));\n        }\n    }\n\n    private static void checkUsage(final String[] args)\n    {\n        if (1 != args.length)\n        {\n            System.out.format(\"Usage: [-Daeron.dir=<directory containing CnC file> -Daeron.cluster.id=<id>] \" +\n                ClusterControl.class.getName() + \" <action>%n\");\n\n            System.exit(0);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterControlAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nfinal class ClusterControlAdapter implements AutoCloseable\n{\n    interface Listener\n    {\n        void onClusterMembersResponse(\n            long correlationId, int leaderMemberId, String activeMembers);\n\n        void onClusterMembersExtendedResponse(\n            long correlationId,\n            long currentTimeNs,\n            int leaderMemberId,\n            int memberId,\n            List<ClusterMember> activeMembers);\n    }\n\n    private final Subscription subscription;\n    private final Listener listener;\n    private final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::onFragment);\n\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final ClusterMembersResponseDecoder clusterMembersResponseDecoder = new ClusterMembersResponseDecoder();\n    private final ClusterMembersExtendedResponseDecoder clusterMembersExtendedResponseDecoder =\n        new ClusterMembersExtendedResponseDecoder();\n\n    ClusterControlAdapter(final Subscription subscription, final Listener listener)\n    {\n        this.subscription = subscription;\n        this.listener = listener;\n    }\n\n    public void close()\n    {\n        CloseHelper.close(subscription);\n    }\n\n    int poll()\n    {\n        return subscription.poll(fragmentAssembler, 1);\n    }\n\n    boolean isBound()\n    {\n        return subscription.isConnected();\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ClusterException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        final int templateId = messageHeaderDecoder.templateId();\n        if (templateId == ClusterMembersResponseDecoder.TEMPLATE_ID)\n        {\n            clusterMembersResponseDecoder.wrap(\n                buffer,\n                offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                messageHeaderDecoder.blockLength(),\n                messageHeaderDecoder.version());\n\n            final long correlationId = clusterMembersResponseDecoder.correlationId();\n            final int leaderMemberId = clusterMembersResponseDecoder.leaderMemberId();\n            final String activeMembers = clusterMembersResponseDecoder.activeMembers();\n            clusterMembersResponseDecoder.skipPassiveFollowers();\n\n            listener.onClusterMembersResponse(correlationId, leaderMemberId, activeMembers);\n        }\n        else if (templateId == ClusterMembersExtendedResponseDecoder.TEMPLATE_ID)\n        {\n            clusterMembersExtendedResponseDecoder.wrap(\n                buffer,\n                offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                messageHeaderDecoder.blockLength(),\n                messageHeaderDecoder.version());\n\n            final long correlationId = clusterMembersExtendedResponseDecoder.correlationId();\n            final long currentTimeNs = clusterMembersExtendedResponseDecoder.currentTimeNs();\n            final int leaderMemberId = clusterMembersExtendedResponseDecoder.leaderMemberId();\n            final int memberId = clusterMembersExtendedResponseDecoder.memberId();\n\n            final ArrayList<ClusterMember> activeMembers = new ArrayList<>();\n            for (final ClusterMembersExtendedResponseDecoder.ActiveMembersDecoder activeMembersDecoder :\n                clusterMembersExtendedResponseDecoder.activeMembers())\n            {\n                final int id = activeMembersDecoder.memberId();\n                final String ingressEndpoint = activeMembersDecoder.ingressEndpoint();\n                final String consensusEndpoint = activeMembersDecoder.consensusEndpoint();\n                final String logEndpoint = activeMembersDecoder.logEndpoint();\n                final String catchupEndpoint = activeMembersDecoder.catchupEndpoint();\n                final String archiveEndpoint = activeMembersDecoder.archiveEndpoint();\n                final String endpoints = String.join(\n                    \",\",\n                    ingressEndpoint,\n                    consensusEndpoint,\n                    logEndpoint,\n                    catchupEndpoint,\n                    archiveEndpoint);\n\n                activeMembers.add(new ClusterMember(\n                    id,\n                    ingressEndpoint,\n                    consensusEndpoint,\n                    logEndpoint,\n                    catchupEndpoint,\n                    archiveEndpoint,\n                    endpoints)\n                    .isLeader(id == leaderMemberId)\n                    .leadershipTermId(activeMembersDecoder.leadershipTermId())\n                    .logPosition(activeMembersDecoder.logPosition())\n                    .timeOfLastAppendPositionNs(activeMembersDecoder.timeOfLastAppendNs()));\n            }\n\n            for (final ClusterMembersExtendedResponseDecoder.PassiveMembersDecoder passiveMembersDecoder :\n                clusterMembersExtendedResponseDecoder.passiveMembers())\n            {\n                passiveMembersDecoder.sbeSkip();\n            }\n\n            listener.onClusterMembersExtendedResponse(\n                correlationId, currentTimeNs, leaderMemberId, memberId, activeMembers);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterMember.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Publication;\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.exceptions.RegistrationException;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.Int2ObjectHashMap;\n\nimport java.util.List;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.ChannelUri.replacePortWithWildcard;\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.CommonContext.MDC_CONTROL_PARAM_NAME;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\n\n/**\n * Represents a member of the cluster that participates in consensus for storing state from the perspective\n * of any single member. It is not a global view of the cluster, perspectives only exist from a vantage point.\n */\npublic final class ClusterMember\n{\n    static final ClusterMember[] EMPTY_MEMBERS = new ClusterMember[0];\n\n    private boolean isBallotSent;\n    private boolean isLeader;\n    private boolean hasTerminated;\n    private int id;\n    private long leadershipTermId = NULL_VALUE;\n    private long candidateTermId = NULL_VALUE;\n    private long catchupReplaySessionId = NULL_VALUE;\n    private long catchupReplayCorrelationId = NULL_VALUE;\n    private long changeCorrelationId = NULL_VALUE;\n    private long logPosition = NULL_POSITION;\n    private long timeOfLastAppendPositionNs = NULL_VALUE;\n    private ExclusivePublication publication;\n    private String consensusChannel;\n    private final String consensusEndpoint;\n    private final String ingressEndpoint;\n    private final String logEndpoint;\n    private final String catchupEndpoint;\n    private final String archiveEndpoint;\n    private final String archiveResponseEndpoint;\n    private final String egressResponseEndpoint;\n    private final String endpoints;\n    private Boolean vote = null;\n\n    /**\n     * Construct a new member of the cluster.\n     *\n     * @param id                unique id for the member.\n     * @param ingressEndpoint   address and port endpoint to which cluster clients send ingress.\n     * @param consensusEndpoint address and port endpoint to which other cluster members connect.\n     * @param logEndpoint       address and port endpoint to which the log is replicated.\n     * @param catchupEndpoint   address and port endpoint to which a stream is replayed for catchup to the leader.\n     * @param archiveEndpoint   address and port endpoint to which the archive control channel can be reached.\n     * @param endpoints         comma separated list of endpoints.\n     */\n    public ClusterMember(\n        final int id,\n        final String ingressEndpoint,\n        final String consensusEndpoint,\n        final String logEndpoint,\n        final String catchupEndpoint,\n        final String archiveEndpoint,\n        final String endpoints)\n    {\n        this(\n            id,\n            ingressEndpoint,\n            consensusEndpoint,\n            logEndpoint,\n            catchupEndpoint,\n            archiveEndpoint,\n            null,\n            null,\n            endpoints);\n    }\n\n    /**\n     * Construct a new member of the cluster.\n     *\n     * @param id                      unique id for the member.\n     * @param ingressEndpoint         address and port endpoint to which cluster clients send ingress.\n     * @param consensusEndpoint       address and port endpoint to which other cluster members connect.\n     * @param logEndpoint             address and port endpoint to which the log is replicated.\n     * @param catchupEndpoint         address and port endpoint to which a stream is replayed for catchup to the\n     *                                leader.\n     * @param archiveEndpoint         address and port endpoint to which the archive control channel can be reached.\n     * @param archiveResponseEndpoint address and port endpoint to which the archive control response channel can be\n     *                                reached.\n     * @param egressResponseEndpoint  address and port endpoint to which the egress response channel can be reached.\n     * @param endpoints               comma separated list of endpoints.\n     */\n    public ClusterMember(\n        final int id,\n        final String ingressEndpoint,\n        final String consensusEndpoint,\n        final String logEndpoint,\n        final String catchupEndpoint,\n        final String archiveEndpoint,\n        final String archiveResponseEndpoint,\n        final String egressResponseEndpoint,\n        final String endpoints)\n    {\n        this.id = id;\n        this.ingressEndpoint = ingressEndpoint;\n        this.consensusEndpoint = consensusEndpoint;\n        this.logEndpoint = logEndpoint;\n        this.catchupEndpoint = catchupEndpoint;\n        this.archiveEndpoint = archiveEndpoint;\n        this.archiveResponseEndpoint = archiveResponseEndpoint;\n        this.egressResponseEndpoint = egressResponseEndpoint;\n        this.endpoints = endpoints;\n    }\n\n    /**\n     * Reset the state of a cluster member, so it can be canvassed and reestablished.\n     */\n    public void reset()\n    {\n        isBallotSent = false;\n        isLeader = false;\n        hasTerminated = false;\n        vote = null;\n        candidateTermId = NULL_VALUE;\n        leadershipTermId = NULL_VALUE;\n        logPosition = NULL_POSITION;\n    }\n\n    /**\n     * Set if this member should be leader.\n     *\n     * @param isLeader value.\n     * @return this for a fluent API.\n     */\n    public ClusterMember isLeader(final boolean isLeader)\n    {\n        this.isLeader = isLeader;\n        return this;\n    }\n\n    /**\n     * Is this member currently the leader?\n     *\n     * @return true if this member is currently the leader otherwise false.\n     */\n    public boolean isLeader()\n    {\n        return isLeader;\n    }\n\n    /**\n     * Is the ballot for the current election sent to this member?\n     *\n     * @param isBallotSent is the ballot for the current election sent to this member?\n     * @return this for a fluent API.\n     */\n    public ClusterMember isBallotSent(final boolean isBallotSent)\n    {\n        this.isBallotSent = isBallotSent;\n        return this;\n    }\n\n    /**\n     * Is the ballot for the current election sent to this member?\n     *\n     * @return true if the ballot has been sent for this member in the current election.\n     */\n    public boolean isBallotSent()\n    {\n        return isBallotSent;\n    }\n\n    /**\n     * Set if this member has terminated.\n     *\n     * @param hasTerminated in notification to the leader.\n     * @return this for a fluent API.\n     */\n    public ClusterMember hasTerminated(final boolean hasTerminated)\n    {\n        this.hasTerminated = hasTerminated;\n        return this;\n    }\n\n    /**\n     * Has this member notified that it has terminated?\n     *\n     * @return has this member notified that it has terminated?\n     */\n    public boolean hasTerminated()\n    {\n        return hasTerminated;\n    }\n\n    /**\n     * Set the unique id for this member of the cluster.\n     *\n     * @param id for this member of the cluster.\n     * @return this for a fluent API.\n     */\n    public ClusterMember id(final int id)\n    {\n        this.id = id;\n        return this;\n    }\n\n    /**\n     * Unique identity for this member in the cluster.\n     *\n     * @return the unique identity for this member in the cluster.\n     */\n    public int id()\n    {\n        return id;\n    }\n\n    /**\n     * Set the result of the vote for this member. {@link Boolean#TRUE} means they voted for this member,\n     * {@link Boolean#FALSE} means they voted against this member, and null means no vote was received.\n     *\n     * @param vote for this member in the election.\n     * @return this for a fluent API.\n     */\n    public ClusterMember vote(final Boolean vote)\n    {\n        this.vote = vote;\n        return this;\n    }\n\n    /**\n     * The status of the vote for this member in an election. {@link Boolean#TRUE} means they voted for this member,\n     * {@link Boolean#FALSE} means they voted against this member, and null means no vote was received.\n     *\n     * @return the status of the vote for this member in an election.\n     */\n    public Boolean vote()\n    {\n        return vote;\n    }\n\n    /**\n     * The leadership term reached for the cluster member.\n     *\n     * @param leadershipTermId leadership term reached for the cluster member.\n     * @return this for a fluent API.\n     */\n    public ClusterMember leadershipTermId(final long leadershipTermId)\n    {\n        this.leadershipTermId = leadershipTermId;\n        return this;\n    }\n\n    /**\n     * The leadership term reached for the cluster member.\n     *\n     * @return The leadership term reached for the cluster member.\n     */\n    public long leadershipTermId()\n    {\n        return leadershipTermId;\n    }\n\n    /**\n     * The log position this member has persisted.\n     *\n     * @param logPosition this member has persisted.\n     * @return this for a fluent API.\n     */\n    public ClusterMember logPosition(final long logPosition)\n    {\n        this.logPosition = logPosition;\n        return this;\n    }\n\n    /**\n     * The log position this member has persisted.\n     *\n     * @return the log position this member has persisted.\n     */\n    public long logPosition()\n    {\n        return logPosition;\n    }\n\n    /**\n     * The candidate term id used when voting.\n     *\n     * @param candidateTermId used when voting.\n     * @return this for a fluent API.\n     */\n    public ClusterMember candidateTermId(final long candidateTermId)\n    {\n        this.candidateTermId = candidateTermId;\n        return this;\n    }\n\n    /**\n     * The candidate term id used when voting.\n     *\n     * @return the candidate term id used when voting.\n     */\n    public long candidateTermId()\n    {\n        return candidateTermId;\n    }\n\n    /**\n     * The session id for the replay when catching up to the leader.\n     *\n     * @param replaySessionId for the replay when catching up to the leader.\n     * @return this for a fluent API.\n     */\n    public ClusterMember catchupReplaySessionId(final long replaySessionId)\n    {\n        this.catchupReplaySessionId = replaySessionId;\n        return this;\n    }\n\n    /**\n     * The session id for the replay when catching up to the leader.\n     *\n     * @return the session id for the replay when catching up to the leader.\n     */\n    public long catchupReplaySessionId()\n    {\n        return catchupReplaySessionId;\n    }\n\n    /**\n     * The correlation id for the replay when catching up to the leader.\n     *\n     * @param correlationId for the replay when catching up to the leader.\n     * @return this for a fluent API.\n     */\n    public ClusterMember catchupReplayCorrelationId(final long correlationId)\n    {\n        this.catchupReplayCorrelationId = correlationId;\n        return this;\n    }\n\n    /**\n     * The correlation id for the replay when catching up to the leader.\n     *\n     * @return the correlation id for the replay when catching up to the leader.\n     */\n    public long catchupReplayCorrelationId()\n    {\n        return catchupReplayCorrelationId;\n    }\n\n    /**\n     * Correlation id assigned to the current action undertaken by the cluster member.\n     *\n     * @param correlationId assigned to the current action undertaken by the cluster member.\n     * @return this for a fluent API.\n     */\n    public ClusterMember correlationId(final long correlationId)\n    {\n        this.changeCorrelationId = correlationId;\n        return this;\n    }\n\n    /**\n     * Correlation id assigned to the current action undertaken by the cluster member.\n     *\n     * @return correlation id assigned to the current action undertaken by the cluster member.\n     */\n    public long correlationId()\n    {\n        return changeCorrelationId;\n    }\n\n    /**\n     * Time (in ns) of last received appendPosition.\n     *\n     * @param timeNs of the last received appendPosition.\n     * @return this for a fluent API.\n     */\n    public ClusterMember timeOfLastAppendPositionNs(final long timeNs)\n    {\n        this.timeOfLastAppendPositionNs = timeNs;\n        return this;\n    }\n\n    /**\n     * Time (in ns) of last received appendPosition.\n     *\n     * @return time (in ns) of last received appendPosition or {@link Aeron#NULL_VALUE} if none received.\n     */\n    public long timeOfLastAppendPositionNs()\n    {\n        return timeOfLastAppendPositionNs;\n    }\n\n    /**\n     * The address:port endpoint for this cluster member that other members connect to for achieving consensus.\n     *\n     * @return the address:port endpoint for this cluster member that other members will connect to for consensus.\n     */\n    public String consensusEndpoint()\n    {\n        return consensusEndpoint;\n    }\n\n    /**\n     * The address:port endpoint for this cluster member that clients send ingress to.\n     *\n     * @return the address:port endpoint for this cluster member that listens for ingress.\n     */\n    public String ingressEndpoint()\n    {\n        return ingressEndpoint;\n    }\n\n    /**\n     * The address:port endpoint for this cluster member that the log is replicated to.\n     *\n     * @return the address:port endpoint for this cluster member that the log is replicated to.\n     */\n    public String logEndpoint()\n    {\n        return logEndpoint;\n    }\n\n    /**\n     * The address:port endpoint for this cluster member to which a stream is replayed for catchup to the leader.\n     * <p>\n     * It is recommended a port of 0 is used, so it is system allocated to avoid potential clashes.\n     *\n     * @return the address:port endpoint for this cluster member to which a stream is replayed for catchup to the\n     * leader.\n     */\n    public String catchupEndpoint()\n    {\n        return catchupEndpoint;\n    }\n\n    /**\n     * The address:port endpoint for this cluster member that the archive can be reached.\n     *\n     * @return the address:port endpoint for this cluster member that the archive can be reached.\n     */\n    public String archiveEndpoint()\n    {\n        return archiveEndpoint;\n    }\n\n    /**\n     * The address:port endpoint for this cluster member to use on the archive to set up a response channel for\n     * control, replay and replication.\n     *\n     * @return the address:port endpoint for the archive response channel to be used by archive clients within the\n     * cluster.\n     */\n    public String archiveResponseEndpoint()\n    {\n        return archiveResponseEndpoint;\n    }\n\n    /**\n     * The address:port endpoint for the cluster member that will serve as the control address for the egress response\n     * channel that client can connect to.\n     *\n     * @return the endpoint used for the egress response channel.\n     */\n    public String egressResponseEndpoint()\n    {\n        return egressResponseEndpoint;\n    }\n\n    /**\n     * The string of endpoints for this member in a comma separated list in the same order they are parsed.\n     *\n     * @return list of endpoints for this member in a comma separated list.\n     * @see #parse(String)\n     */\n    public String endpoints()\n    {\n        return endpoints;\n    }\n\n    /**\n     * The {@link Publication} used for send status updates to the member.\n     *\n     * @return {@link Publication} used for send status updates to the member.\n     */\n    public ExclusivePublication publication()\n    {\n        return publication;\n    }\n\n    /**\n     * {@link Publication} used for send status updates to the member.\n     *\n     * @param publication used for send status updates to the member.\n     */\n    public void publication(final ExclusivePublication publication)\n    {\n        this.publication = publication;\n    }\n\n    /**\n     * Close consensus publication and null out reference.\n     *\n     * @param errorHandler to capture errors during close.\n     */\n    public void closePublication(final ErrorHandler errorHandler)\n    {\n        CloseHelper.close(errorHandler, publication);\n        publication = null;\n    }\n\n    /**\n     * Parse the details for a cluster members from a string.\n     * <p>\n     * <code>\n     * member-id,ingress:port,consensus:port,log:port,catchup:port,archive:port|1,...\n     * </code>\n     *\n     * @param value of the string to be parsed.\n     * @return An array of cluster members.\n     */\n    public static ClusterMember[] parse(final String value)\n    {\n        if (null == value || value.isEmpty())\n        {\n            return ClusterMember.EMPTY_MEMBERS;\n        }\n\n        final String[] memberValues = value.split(\"\\\\|\");\n        final int length = memberValues.length;\n        final ClusterMember[] members = new ClusterMember[length];\n\n        for (int i = 0; i < length; i++)\n        {\n            final String idAndEndpoints = memberValues[i];\n            final String[] memberAttributes = idAndEndpoints.split(\",\");\n\n            if (memberAttributes.length < 6 || 8 < memberAttributes.length)\n            {\n                throw new ClusterException(\"invalid member value: \" + idAndEndpoints + \" within: \" + value);\n            }\n\n            final int clusterMemberId;\n            try\n            {\n                clusterMemberId = Integer.parseInt(memberAttributes[0]);\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new ClusterException(\"invalid cluster member id, must be an integer value\", ex);\n            }\n\n            final String archiveResponseEndpoint = 6 < memberAttributes.length ? memberAttributes[6] : null;\n            final String egressResponseEndpoint = 7 < memberAttributes.length ? memberAttributes[7] : null;\n\n            String endpoints = String.join(\n                \",\",\n                memberAttributes[1],\n                memberAttributes[2],\n                memberAttributes[3],\n                memberAttributes[4],\n                memberAttributes[5]);\n\n            if (null != archiveResponseEndpoint)\n            {\n                endpoints += \",\" + archiveResponseEndpoint;\n            }\n\n            if (null != egressResponseEndpoint)\n            {\n                endpoints += \",\" + egressResponseEndpoint;\n            }\n\n            members[i] = new ClusterMember(\n                clusterMemberId,\n                memberAttributes[1],\n                memberAttributes[2],\n                memberAttributes[3],\n                memberAttributes[4],\n                memberAttributes[5],\n                archiveResponseEndpoint,\n                egressResponseEndpoint,\n                endpoints);\n        }\n\n        return members;\n    }\n\n    /**\n     * Parse a string containing the endpoints for a cluster node and passing to\n     * {@link #ClusterMember(int, String, String, String, String, String, String)}.\n     *\n     * @param id        of the member node.\n     * @param endpoints comma separated.\n     * @return the {@link ClusterMember} with the endpoints set.\n     */\n    public static ClusterMember parseEndpoints(final int id, final String endpoints)\n    {\n        final String[] memberAttributes = endpoints.split(\",\");\n        if (memberAttributes.length != 5)\n        {\n            throw new ClusterException(\"invalid member value: \" + endpoints);\n        }\n\n        return new ClusterMember(\n            id,\n            memberAttributes[0],\n            memberAttributes[1],\n            memberAttributes[2],\n            memberAttributes[3],\n            memberAttributes[4],\n            endpoints);\n    }\n\n    /**\n     * Encode member endpoints from a cluster members array to a String.\n     *\n     * @param clusterMembers to fill the details from.\n     * @return String representation suitable for use with {@link #parse(String)}.\n     */\n    public static String encodeAsString(final ClusterMember[] clusterMembers)\n    {\n        if (0 == clusterMembers.length)\n        {\n            return \"\";\n        }\n\n        final StringBuilder builder = new StringBuilder();\n\n        for (int i = 0, length = clusterMembers.length; i < length; i++)\n        {\n            final ClusterMember member = clusterMembers[i];\n\n            builder\n                .append(member.id)\n                .append(',')\n                .append(member.endpoints);\n\n            if ((length - 1) != i)\n            {\n                builder.append('|');\n            }\n        }\n\n        return builder.toString();\n    }\n\n    /**\n     * Encode member endpoints from a cluster members {@link List} to a String.\n     *\n     * @param clusterMembers to fill the details from.\n     * @return String representation suitable for use with {@link #parse(String)}.\n     */\n    public static String encodeAsString(final List<ClusterMember> clusterMembers)\n    {\n        if (clusterMembers.isEmpty())\n        {\n            return \"\";\n        }\n\n        final StringBuilder builder = new StringBuilder();\n\n        for (int i = 0, length = clusterMembers.size(); i < length; i++)\n        {\n            final ClusterMember member = clusterMembers.get(i);\n\n            builder\n                .append(member.id)\n                .append(',')\n                .append(member.endpoints);\n\n            if ((length - 1) != i)\n            {\n                builder.append('|');\n            }\n        }\n\n        return builder.toString();\n    }\n\n    /**\n     * Add the publications for sending consensus messages to the other members of the cluster.\n     *\n     * @param members              of the cluster.\n     * @param thisMember           this member when adding publications.\n     * @param channelTemplate      for the publications.\n     * @param streamId             for the publications.\n     * @param bindConsensusControl if the control endpoint should be bound for the publication.\n     * @param aeron                to add the publications to.\n     * @param errorHandler         to log registration exceptions to.\n     */\n    public static void addConsensusPublications(\n        final ClusterMember[] members,\n        final ClusterMember thisMember,\n        final String channelTemplate,\n        final int streamId,\n        final boolean bindConsensusControl,\n        final Aeron aeron,\n        final ErrorHandler errorHandler)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(channelTemplate);\n\n        for (final ClusterMember member : members)\n        {\n            if (member.id != thisMember.id)\n            {\n                channelUri.put(ENDPOINT_PARAM_NAME, member.consensusEndpoint);\n                setControlEndpoint(channelUri, bindConsensusControl, thisMember.consensusEndpoint);\n                member.consensusChannel = channelUri.toString();\n                tryAddPublication(member, streamId, aeron, errorHandler);\n            }\n        }\n    }\n\n    /**\n     * Add an exclusive {@link Publication} for communicating to a member on the consensus channel.\n     *\n     * @param thisMember           from which the publication is addressed.\n     * @param otherMember          to which the publication is addressed.\n     * @param channelTemplate      for the target member.\n     * @param streamId             for the target member.\n     * @param bindConsensusControl if the control endpoint should be bound for the publication.\n     * @param aeron                from which the publication will be created.\n     * @param errorHandler         to log registration exceptions to.\n     */\n    public static void addConsensusPublication(\n        final ClusterMember thisMember,\n        final ClusterMember otherMember,\n        final String channelTemplate,\n        final int streamId,\n        final boolean bindConsensusControl,\n        final Aeron aeron,\n        final ErrorHandler errorHandler)\n    {\n        if (null == otherMember.consensusChannel)\n        {\n            final ChannelUri channelUri = ChannelUri.parse(channelTemplate);\n            channelUri.put(ENDPOINT_PARAM_NAME, otherMember.consensusEndpoint);\n            setControlEndpoint(channelUri, bindConsensusControl, thisMember.consensusEndpoint);\n            otherMember.consensusChannel = channelUri.toString();\n        }\n\n        tryAddPublication(otherMember, streamId, aeron, errorHandler);\n    }\n\n    /**\n     * Try and add an exclusive {@link Publication} for communicating to a member on the consensus channel.\n     *\n     * @param member       to which the publication is added.\n     * @param streamId     for the target member.\n     * @param aeron        from which the publication will be created.\n     * @param errorHandler to log registration exceptions to.\n     */\n    public static void tryAddPublication(\n        final ClusterMember member, final int streamId, final Aeron aeron, final ErrorHandler errorHandler)\n    {\n        try\n        {\n            member.publication = aeron.addExclusivePublication(member.consensusChannel, streamId);\n        }\n        catch (final RegistrationException ex)\n        {\n            errorHandler.onError(new ClusterEvent(\n                \"failed to add consensus publication for member: \" + member.id + \" - \" + ex.getMessage()));\n        }\n    }\n\n    /**\n     * Close the publications associated with members of the cluster used for the consensus protocol.\n     *\n     * @param errorHandler   to capture errors during close.\n     * @param clusterMembers to close the publications for.\n     */\n    public static void closeConsensusPublications(final ErrorHandler errorHandler, final ClusterMember[] clusterMembers)\n    {\n        for (final ClusterMember member : clusterMembers)\n        {\n            member.closePublication(errorHandler);\n        }\n    }\n\n    /**\n     * Populate map of {@link ClusterMember}s which can be looked up by id.\n     *\n     * @param clusterMembers       to populate the map.\n     * @param clusterMemberByIdMap to be populated.\n     */\n    public static void addClusterMemberIds(\n        final ClusterMember[] clusterMembers, final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap)\n    {\n        for (final ClusterMember member : clusterMembers)\n        {\n            clusterMemberByIdMap.put(member.id, member);\n        }\n    }\n\n    /**\n     * Check if the cluster leader has an active quorum of cluster followers.\n     *\n     * @param clusterMembers for the current cluster.\n     * @param nowNs          for the current time.\n     * @param timeoutNs      after which a member is not considered active.\n     * @return true if quorum of cluster members are considered active.\n     */\n    public static boolean hasActiveQuorum(\n        final ClusterMember[] clusterMembers, final long nowNs, final long timeoutNs)\n    {\n        int threshold = quorumThreshold(clusterMembers.length);\n\n        for (final ClusterMember member : clusterMembers)\n        {\n            if (member.isActive(nowNs, timeoutNs))\n            {\n                if (--threshold <= 0)\n                {\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * The threshold of clusters members required to achieve quorum given a count of cluster members.\n     *\n     * @param memberCount for the cluster\n     * @return the threshold for achieving quorum.\n     */\n    public static int quorumThreshold(final int memberCount)\n    {\n        return (memberCount >> 1) + 1;\n    }\n\n    /**\n     * Calculate the position reached by a quorum of cluster members.\n     *\n     * @param members         of the cluster.\n     * @param rankedPositions temp array to be used for sorting the positions to avoid allocation.\n     * @param nowNs           for the current time.\n     * @param timeoutNs       after which a member is not considered active.\n     * @return the position reached by a quorum of active cluster members.\n     */\n    public static long quorumPosition(\n        final ClusterMember[] members, final long[] rankedPositions, final long nowNs, final long timeoutNs)\n    {\n        final int length = rankedPositions.length;\n        for (int i = 0; i < length; i++)\n        {\n            rankedPositions[i] = 0;\n        }\n\n        for (final ClusterMember member : members)\n        {\n            if (member.isActive(nowNs, timeoutNs))\n            {\n                long newPosition = member.logPosition;\n                for (int i = 0; i < length; i++)\n                {\n                    final long rankedPosition = rankedPositions[i];\n\n                    if (newPosition > rankedPosition)\n                    {\n                        rankedPositions[i] = newPosition;\n                        newPosition = rankedPosition;\n                    }\n                }\n            }\n        }\n\n        return rankedPositions[length - 1];\n    }\n\n    /**\n     * Has a quorum of members of appended a position to their local log.\n     *\n     * @param clusterMembers   to check.\n     * @param leadershipTermId expected of the members.\n     * @param position         to compare the {@link #logPosition()} against.\n     * @param nowNs            for the current time.\n     * @param timeoutNs        after which a member is not considered active.\n     * @return {@code true} if a quorum of members reached this position otherwise {@code false}.\n     */\n    public static boolean hasQuorumAtPosition(\n        final ClusterMember[] clusterMembers,\n        final long leadershipTermId,\n        final long position,\n        final long nowNs,\n        final long timeoutNs)\n    {\n        int votes = 0;\n\n        for (final ClusterMember member : clusterMembers)\n        {\n            if (member.hasReachedPosition(leadershipTermId, position, nowNs, timeoutNs))\n            {\n                ++votes;\n            }\n        }\n\n        return votes >= ClusterMember.quorumThreshold(clusterMembers.length);\n    }\n\n    /**\n     * Reset the state of all cluster members.\n     *\n     * @param members to reset.\n     */\n    public static void reset(final ClusterMember[] members)\n    {\n        for (final ClusterMember member : members)\n        {\n            member.reset();\n        }\n    }\n\n    /**\n     * Become a candidate by voting for yourself and resetting the other votes to {@link Aeron#NULL_VALUE}.\n     *\n     * @param members           to reset the votes for.\n     * @param candidateTermId   for the candidacy.\n     * @param candidateMemberId for the election.\n     */\n    public static void becomeCandidate(\n        final ClusterMember[] members, final long candidateTermId, final int candidateMemberId)\n    {\n        for (final ClusterMember member : members)\n        {\n            if (member.id == candidateMemberId)\n            {\n                member.vote(Boolean.TRUE)\n                    .candidateTermId(candidateTermId)\n                    .isBallotSent(true);\n            }\n            else\n            {\n                member.vote(null)\n                    .candidateTermId(NULL_VALUE)\n                    .isBallotSent(false);\n            }\n        }\n    }\n\n    /**\n     * Is a member considered unanimously to be leader after voting.\n     * <p>\n     * If a leader has been gracefully closed then it is not included in the membership for considering a unanimous\n     * position but will be considered in the membership for quorum.\n     * <p>\n     * <em>Note: all members are considered (i.e. no {@link #isActive(long, long)} check), because this method is called\n     * during {@link ElectionState#CANDIDATE_BALLOT} phase before append position is notified from the followers.</em>\n     *\n     * @param clusterMembers         to check for votes.\n     * @param candidateTermId        for the vote.\n     * @param gracefulClosedLeaderId id of a leader if gracefully closed otherwise {@link Aeron#NULL_VALUE}.\n     * @return {@code true} if all members voted positively.\n     */\n    public static boolean isUnanimousLeader(\n        final ClusterMember[] clusterMembers, final long candidateTermId, final int gracefulClosedLeaderId)\n    {\n        int votes = 0;\n\n        for (final ClusterMember member : clusterMembers)\n        {\n            if (member.id == gracefulClosedLeaderId)\n            {\n                continue;\n            }\n\n            if (candidateTermId != member.candidateTermId || !Boolean.TRUE.equals(member.vote))\n            {\n                return false;\n            }\n\n            votes++;\n        }\n\n        return votes >= ClusterMember.quorumThreshold(clusterMembers.length);\n    }\n\n    /**\n     * Is this member considered leader by a quorum of members by having positive votes being counted for a majority\n     * and no negative votes.\n     * <p>\n     * <em>Note: all members are considered (i.e. no {@link #isActive(long, long)} check), because this method is called\n     * during {@link ElectionState#CANDIDATE_BALLOT} phase before append position is notified from the followers.</em>\n     *\n     * @param clusterMembers  to check for votes.\n     * @param candidateTermId for the vote.\n     * @return {@code true} if sufficient positive votes being counted for a majority and no negative votes.\n     */\n    public static boolean isQuorumLeader(final ClusterMember[] clusterMembers, final long candidateTermId)\n    {\n        int votes = 0;\n\n        for (final ClusterMember member : clusterMembers)\n        {\n            if (candidateTermId == member.candidateTermId)\n            {\n                if (Boolean.FALSE.equals(member.vote))\n                {\n                    return false;\n                }\n\n                if (Boolean.TRUE.equals(member.vote))\n                {\n                    ++votes;\n                }\n            }\n        }\n\n        return votes >= ClusterMember.quorumThreshold(clusterMembers.length);\n    }\n\n    /**\n     * Determine which member of a cluster this is and check endpoints.\n     *\n     * @param clusterMembers  for the current cluster which can be null.\n     * @param memberId        for this member.\n     * @param memberEndpoints for this member.\n     * @return the {@link ClusterMember} determined.\n     */\n    public static ClusterMember determineMember(\n        final ClusterMember[] clusterMembers, final int memberId, final String memberEndpoints)\n    {\n        ClusterMember member = NULL_VALUE != memberId ? ClusterMember.findMember(clusterMembers, memberId) : null;\n\n        if ((null == clusterMembers || 0 == clusterMembers.length) && null == member)\n        {\n            member = ClusterMember.parseEndpoints(NULL_VALUE, memberEndpoints);\n        }\n        else\n        {\n            if (null == member)\n            {\n                throw new ClusterException(\"memberId=\" + memberId + \" not found in clusterMembers\");\n            }\n\n            if (!memberEndpoints.isEmpty())\n            {\n                ClusterMember.validateMemberEndpoints(member, memberEndpoints);\n            }\n        }\n\n        return member;\n    }\n\n    /**\n     * Check the member with the memberEndpoints.\n     *\n     * @param member          to check memberEndpoints against.\n     * @param memberEndpoints to check member against.\n     * @see ConsensusModule.Context#memberEndpoints()\n     * @see ConsensusModule.Context#clusterMembers()\n     */\n    public static void validateMemberEndpoints(final ClusterMember member, final String memberEndpoints)\n    {\n        final ClusterMember endpoints = ClusterMember.parseEndpoints(NULL_VALUE, memberEndpoints);\n\n        if (!areSameEndpoints(member, endpoints))\n        {\n            throw new ClusterException(\n                \"clusterMembers and endpoints differ: \" + member.endpoints + \" != \" + memberEndpoints);\n        }\n    }\n\n    /**\n     * Are two cluster members using the same endpoints?\n     *\n     * @param lhs to compare for equality.\n     * @param rhs to compare for equality.\n     * @return true if both are using the same endpoints or false if not.\n     */\n    public static boolean areSameEndpoints(final ClusterMember lhs, final ClusterMember rhs)\n    {\n        return lhs.ingressEndpoint.equals(rhs.ingressEndpoint) &&\n            lhs.consensusEndpoint.equals(rhs.consensusEndpoint) &&\n            lhs.logEndpoint.equals(rhs.logEndpoint) &&\n            lhs.catchupEndpoint.equals(rhs.catchupEndpoint) &&\n            lhs.archiveEndpoint.equals(rhs.archiveEndpoint);\n    }\n\n    /**\n     * Is the member considered a candidate by a unanimous view to be a suitable candidate in an election.\n     * <p>\n     * If a leader has been gracefully closed then it is not included in the membership for considering a unanimous\n     * position but will be considered in the membership for quorum.\n     *\n     * @param clusterMembers         to compare the candidate against.\n     * @param candidate              for leadership.\n     * @param gracefulClosedLeaderId id of a leader if gracefully closed otherwise {@link Aeron#NULL_VALUE}.\n     * @return true if the candidate is suitable otherwise false.\n     */\n    public static boolean isUnanimousCandidate(\n        final ClusterMember[] clusterMembers, final ClusterMember candidate, final int gracefulClosedLeaderId)\n    {\n        int possibleVotes = 0;\n\n        for (final ClusterMember member : clusterMembers)\n        {\n            if (member.id == gracefulClosedLeaderId)\n            {\n                continue;\n            }\n\n            if (!member.willVoteFor(candidate))\n            {\n                return false;\n            }\n\n            possibleVotes++;\n        }\n\n        return possibleVotes >= ClusterMember.quorumThreshold(clusterMembers.length);\n    }\n\n    /**\n     * Has the member achieved a quorum view to be a suitable candidate in an election.\n     *\n     * @param clusterMembers to compare the candidate against.\n     * @param candidate      for leadership.\n     * @return true if the candidate is suitable otherwise false.\n     */\n    public static boolean isQuorumCandidate(final ClusterMember[] clusterMembers, final ClusterMember candidate)\n    {\n        int possibleVotes = 0;\n\n        for (final ClusterMember member : clusterMembers)\n        {\n            if (member.willVoteFor(candidate))\n            {\n                ++possibleVotes;\n            }\n        }\n\n        return possibleVotes >= ClusterMember.quorumThreshold(clusterMembers.length);\n    }\n\n    /**\n     * The result is positive if lhs has the more recent log, zero if logs are equal, and negative if rhs has the more\n     * recent log.\n     *\n     * @param lhsLogLeadershipTermId term for which the position is most recent.\n     * @param lhsLogPosition         reached in the provided term.\n     * @param rhsLogLeadershipTermId term for which the position is most recent.\n     * @param rhsLogPosition         reached in the provided term.\n     * @return positive if lhs has the more recent log, zero if logs are equal, and negative if rhs has the more\n     * recent log.\n     */\n    public static int compareLog(\n        final long lhsLogLeadershipTermId,\n        final long lhsLogPosition,\n        final long rhsLogLeadershipTermId,\n        final long rhsLogPosition)\n    {\n        if (lhsLogLeadershipTermId > rhsLogLeadershipTermId)\n        {\n            return 1;\n        }\n        else if (lhsLogLeadershipTermId < rhsLogLeadershipTermId)\n        {\n            return -1;\n        }\n        else if (lhsLogPosition > rhsLogPosition)\n        {\n            return 1;\n        }\n        else if (lhsLogPosition < rhsLogPosition)\n        {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    /**\n     * The result is positive if lhs has the more recent log, zero if logs are equal, and negative if rhs has the more\n     * recent log.\n     *\n     * @param lhs member to compare.\n     * @param rhs member to compare.\n     * @return positive if lhs has the more recent log, zero if logs are equal, and negative if rhs has the more\n     * recent log.\n     */\n    public static int compareLog(final ClusterMember lhs, final ClusterMember rhs)\n    {\n        return compareLog(lhs.leadershipTermId, lhs.logPosition, rhs.leadershipTermId, rhs.logPosition);\n    }\n\n    /**\n     * Find a {@link ClusterMember} with a given id.\n     *\n     * @param clusterMembers to search.\n     * @param memberId       to search for.\n     * @return the {@link ClusterMember} if found otherwise null.\n     */\n    public static ClusterMember findMember(final ClusterMember[] clusterMembers, final int memberId)\n    {\n        for (final ClusterMember member : clusterMembers)\n        {\n            if (memberId == member.id)\n            {\n                return member;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Create a string of ingress endpoints by member id in format {@code id=endpoint,id=endpoint, ...}.\n     *\n     * @param members for which the ingress endpoints string will be generated.\n     * @return a string of ingress endpoints by id.\n     */\n    public static String ingressEndpoints(final ClusterMember[] members)\n    {\n        final StringBuilder builder = new StringBuilder(100);\n\n        for (int i = 0, length = members.length; i < length; i++)\n        {\n            if (0 != i)\n            {\n                builder.append(',');\n            }\n\n            final ClusterMember member = members[i];\n            builder.append(member.id).append('=').append(member.ingressEndpoint);\n        }\n\n        return builder.toString();\n    }\n\n    /**\n     * Run through the list of cluster members and set the isLeader field based on the supplied leaderMemberId.\n     *\n     * @param clusterMembers list of cluster members.\n     * @param leaderMemberId memberId of the current leader.\n     */\n    public static void setIsLeader(final ClusterMember[] clusterMembers, final int leaderMemberId)\n    {\n        for (final ClusterMember clusterMember : clusterMembers)\n        {\n            clusterMember.isLeader(clusterMember.id() == leaderMemberId);\n        }\n    }\n\n    static void setControlEndpoint(final ChannelUri channelUri, final boolean shouldBind, final String endpoint)\n    {\n        if (!shouldBind)\n        {\n            return;\n        }\n\n        final String controlEndpoint = replacePortWithWildcard(endpoint);\n        if (null == controlEndpoint)\n        {\n            return;\n        }\n\n        channelUri.put(MDC_CONTROL_PARAM_NAME, controlEndpoint);\n    }\n\n    boolean isActive(final long nowNs, final long timeoutNs)\n    {\n        return timeOfLastAppendPositionNs + timeoutNs > nowNs;\n    }\n\n    boolean willVoteFor(final ClusterMember candidate)\n    {\n        // we do not check `isActive()` here, because doing so breaks the nomination phase whereby nodes will no longer\n        // propose themselves as candidates when `timeOfLastAppendPositionNs` on other nodes stops advancing (i.e.\n        // node crash, network partition etc.).\n        return logPosition != NULL_POSITION && compareLog(this, candidate) <= 0;\n    }\n\n    boolean hasReachedPosition(final long leadershipTermId, final long position, final long nowNs, final long timeoutNs)\n    {\n        return isActive(nowNs, timeoutNs) && leadershipTermId == this.leadershipTermId && logPosition >= position;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ClusterMember{\" +\n            \"id=\" + id +\n            \", isBallotSent=\" + isBallotSent +\n            \", isLeader=\" + isLeader +\n            \", leadershipTermId=\" + leadershipTermId +\n            \", logPosition=\" + logPosition +\n            \", candidateTermId=\" + candidateTermId +\n            \", catchupReplaySessionId=\" + catchupReplaySessionId +\n            \", correlationId=\" + changeCorrelationId +\n            \", timeOfLastAppendPositionNs=\" + timeOfLastAppendPositionNs +\n            \", ingressEndpoint='\" + ingressEndpoint + '\\'' +\n            \", consensusEndpoint='\" + consensusEndpoint + '\\'' +\n            \", logEndpoint='\" + logEndpoint + '\\'' +\n            \", catchupEndpoint='\" + catchupEndpoint + '\\'' +\n            \", archiveEndpoint='\" + archiveEndpoint + '\\'' +\n            \", endpoints='\" + endpoints + '\\'' +\n            \", publication=\" + publication +\n            \", vote=\" + vote +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterMembership.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport java.util.List;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * Detail for the cluster membership from the perspective of a given member.\n */\npublic class ClusterMembership\n{\n    /**\n     * Create a mutable holder for membership information. Initialise all values to null/NULL_VALUE.\n     */\n    public ClusterMembership()\n    {\n    }\n\n    /**\n     * Member id that the query is run against.\n     */\n    public int memberId = NULL_VALUE;\n\n    /**\n     * Current leader id from the perspective of the member doing the query.\n     */\n    public int leaderMemberId = NULL_VALUE;\n\n    /**\n     * Current time in nanoseconds when the query was run.\n     */\n    public long currentTimeNs = NULL_VALUE;\n\n    /**\n     * List of active cluster members encoded to a String.\n     */\n    public String activeMembersStr = null;\n\n    /**\n     * Current active members of a cluster.\n     */\n    public List<ClusterMember> activeMembers = null;\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.Counter;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.cluster.service.ClusterCounters;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.Strings;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\nfinal class ClusterSession implements ClusterClientSession\n{\n    static final byte[] NULL_PRINCIPAL = ArrayUtil.EMPTY_BYTE_ARRAY;\n    static final int MAX_ENCODED_PRINCIPAL_LENGTH = 4 * 1024;\n    static final int MAX_ENCODED_MEMBERSHIP_QUERY_LENGTH = 4 * 1024;\n\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        INIT, CONNECTING, CONNECTED, CHALLENGED, AUTHENTICATED, REJECTED, OPEN, CLOSING, INVALID, CLOSED\n    }\n\n    @SuppressWarnings(\"JavadocVariable\")\n    enum Action\n    {\n        CLIENT, BACKUP, HEARTBEAT, STANDBY_SNAPSHOT\n    }\n\n    private final long id;\n    private final int clusterMemberId;\n    private final int responseStreamId;\n    private final String responseChannel;\n    private final String sessionInfo;\n    private boolean hasNewLeaderEventPending = false;\n    private boolean hasOpenEventPending = true;\n    private long correlationId;\n    private long openedLogPosition = AeronArchive.NULL_POSITION;\n    private long closedLogPosition = AeronArchive.NULL_POSITION;\n    private transient long timeOfLastActivityNs;\n    private transient long ingressImageCorrelationId = NULL_VALUE;\n    private long responsePublicationId = NULL_VALUE;\n    private long counterRegistrationId = NULL_VALUE;\n    private Publication responsePublication;\n    private Counter counter;\n    private State state;\n    private String responseDetail = null;\n    private EventCode eventCode = null;\n    private CloseReason closeReason = CloseReason.NULL_VAL;\n    private byte[] encodedPrincipal = NULL_PRINCIPAL;\n    private Action action = Action.CLIENT;\n    private Object requestInput = null;\n\n    ClusterSession(\n        final int clusterMemberId,\n        final long sessionId,\n        final int responseStreamId,\n        final String responseChannel,\n        final String sessionInfo)\n    {\n        this.id = sessionId;\n        this.clusterMemberId = clusterMemberId;\n        this.responseStreamId = responseStreamId;\n        this.responseChannel = responseChannel;\n        this.sessionInfo = sessionInfo;\n        state(State.INIT, \"\");\n    }\n\n    public void close(final Aeron aeron, final ErrorHandler errorHandler, final String reason)\n    {\n        disconnect(aeron, errorHandler);\n        state(State.CLOSED, reason);\n    }\n\n    public long id()\n    {\n        return id;\n    }\n\n    public byte[] encodedPrincipal()\n    {\n        return encodedPrincipal;\n    }\n\n    public boolean isOpen()\n    {\n        return State.OPEN == state;\n    }\n\n    public Publication responsePublication()\n    {\n        return responsePublication;\n    }\n\n    public long timeOfLastActivityNs()\n    {\n        return timeOfLastActivityNs;\n    }\n\n    public void timeOfLastActivityNs(final long timeNs)\n    {\n        timeOfLastActivityNs = timeNs;\n    }\n\n    void loadSnapshotState(\n        final long correlationId,\n        final long openedLogPosition,\n        final long timeOfLastActivityNs,\n        final CloseReason closeReason)\n    {\n        this.openedLogPosition = openedLogPosition;\n        this.timeOfLastActivityNs = timeOfLastActivityNs;\n        this.correlationId = correlationId;\n        this.closeReason = closeReason;\n\n        if (CloseReason.NULL_VAL != closeReason)\n        {\n            state(State.CLOSING, closeReason.name());\n        }\n        else\n        {\n            state(State.OPEN, \"openedLogPosition=\" + openedLogPosition);\n        }\n    }\n\n    int responseStreamId()\n    {\n        return responseStreamId;\n    }\n\n    String responseChannel()\n    {\n        return responseChannel;\n    }\n\n    void closing(final CloseReason closeReason)\n    {\n        this.closeReason = closeReason;\n        this.hasOpenEventPending = false;\n        this.hasNewLeaderEventPending = false;\n        this.timeOfLastActivityNs = 0;\n        state(State.CLOSING, closeReason.name());\n    }\n\n    CloseReason closeReason()\n    {\n        return closeReason;\n    }\n\n    void resetCloseReason()\n    {\n        closedLogPosition = AeronArchive.NULL_POSITION;\n        closeReason = CloseReason.NULL_VAL;\n    }\n\n    void asyncConnect(final Aeron aeron, final MutableDirectBuffer tempBuffer, final int clusterId)\n    {\n        counterRegistrationId = addSessionCounter(aeron, tempBuffer, clusterId);\n        responsePublicationId = aeron.asyncAddPublication(responseChannel, responseStreamId);\n    }\n\n    void connect(\n        final ErrorHandler errorHandler,\n        final Aeron aeron,\n        final MutableDirectBuffer tempBuffer,\n        final int clusterId)\n    {\n        if (null != responsePublication)\n        {\n            throw new ClusterException(\"response publication already added\");\n        }\n\n        counterRegistrationId = addSessionCounter(aeron, tempBuffer, clusterId);\n\n        try\n        {\n            responsePublication = aeron.addPublication(responseChannel, responseStreamId);\n        }\n        catch (final RegistrationException ex)\n        {\n            errorHandler.onError(new ClusterException(\n                \"failed to connect session response publication: \" + ex.getMessage(), AeronException.Category.WARN));\n        }\n    }\n\n    void disconnect(final Aeron aeron, final ErrorHandler errorHandler)\n    {\n        if (NULL_VALUE != responsePublicationId)\n        {\n            aeron.asyncRemovePublication(responsePublicationId);\n            responsePublicationId = NULL_VALUE;\n        }\n        else\n        {\n            CloseHelper.close(errorHandler, responsePublication);\n            responsePublication = null;\n        }\n        if (NULL_VALUE != counterRegistrationId)\n        {\n            aeron.asyncRemoveCounter(counterRegistrationId);\n            counterRegistrationId = NULL_VALUE;\n        }\n        else\n        {\n            CloseHelper.close(errorHandler, counter);\n            counter = null;\n        }\n    }\n\n    boolean isResponsePublicationConnected(final Aeron aeron, final long nowNs)\n    {\n        if (null == responsePublication)\n        {\n            if (!aeron.isCommandActive(responsePublicationId))\n            {\n                responsePublication = aeron.getPublication(responsePublicationId);\n                responsePublicationId = NULL_VALUE;\n\n                counter = aeron.getCounter(counterRegistrationId);\n                counterRegistrationId = NULL_VALUE;\n\n                if (null != responsePublication)\n                {\n                    if (null != counter)\n                    {\n                        AeronCounters.setReferenceId(\n                            aeron.context().countersMetaDataBuffer(),\n                            aeron.context().countersValuesBuffer(),\n                            counter.id(),\n                            responsePublication.registrationId());\n                        counter.setRelease(id);\n                    }\n\n                    timeOfLastActivityNs = nowNs;\n                    state(State.CONNECTING, \"connecting\");\n                }\n                else\n                {\n                    state(State.INVALID, \"responsePublication is null\");\n                }\n            }\n        }\n\n        return null != responsePublication && responsePublication.isConnected();\n    }\n\n    long tryClaim(final int length, final BufferClaim bufferClaim)\n    {\n        if (null == responsePublication)\n        {\n            return Publication.NOT_CONNECTED;\n        }\n        else\n        {\n            return responsePublication.tryClaim(length, bufferClaim);\n        }\n    }\n\n    long offer(final DirectBuffer buffer, final int offset, final int length)\n    {\n        if (null == responsePublication)\n        {\n            return Publication.NOT_CONNECTED;\n        }\n        else\n        {\n            return responsePublication.offer(buffer, offset, length);\n        }\n    }\n\n    State state()\n    {\n        return state;\n    }\n\n    void state(final State newState, final String reason)\n    {\n        logStateChange(clusterMemberId, id, action, state, newState, reason);\n        state = newState;\n    }\n\n    void authenticate(final byte[] encodedPrincipal)\n    {\n        if (encodedPrincipal != null)\n        {\n            this.encodedPrincipal = encodedPrincipal;\n        }\n\n        state(State.AUTHENTICATED, \"authenticated\");\n    }\n\n    void open(final long openedLogPosition)\n    {\n        this.openedLogPosition = openedLogPosition;\n        state(State.OPEN, \"openedLogPosition=\" + openedLogPosition);\n    }\n\n    boolean appendSessionToLogAndSendOpen(\n        final LogPublisher logPublisher,\n        final EgressPublisher egressPublisher,\n        final long leadershipTermId,\n        final int memberId,\n        final long nowNs,\n        final long clusterTimestamp)\n    {\n        if (responsePublication.availableWindow() > 0)\n        {\n            final long resultingPosition = logPublisher.appendSessionOpen(this, leadershipTermId, clusterTimestamp);\n            if (resultingPosition > 0)\n            {\n                open(resultingPosition);\n                timeOfLastActivityNs(nowNs);\n                sendSessionOpenEvent(egressPublisher, leadershipTermId, memberId);\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    int sendSessionOpenEvent(\n        final EgressPublisher egressPublisher,\n        final long leadershipTermId,\n        final int memberId)\n    {\n        if (egressPublisher.sendEvent(this, leadershipTermId, memberId, EventCode.OK, \"\"))\n        {\n            clearOpenEventPending();\n            return 1;\n        }\n\n        return 0;\n    }\n\n    void lastActivityNs(final long timeNs, final long correlationId)\n    {\n        timeOfLastActivityNs = timeNs;\n        this.correlationId = correlationId;\n    }\n\n    void reject(final EventCode code, final String responseDetail, final DistinctErrorLog errorLog)\n    {\n        this.eventCode = code;\n        this.responseDetail = responseDetail;\n        state(State.REJECTED, Strings.isEmpty(responseDetail) ? code.name() : code.name() + \": \" + responseDetail);\n        if (null != errorLog)\n        {\n            errorLog.record(new ClusterEvent(\n                code + \" \" + responseDetail + \", clusterMemberId=\" + clusterMemberId + \", id=\" + id));\n        }\n    }\n\n    void redirect(final String ingressEndpoints)\n    {\n        this.eventCode = EventCode.REDIRECT;\n        this.responseDetail = ingressEndpoints;\n    }\n\n    EventCode eventCode()\n    {\n        return eventCode;\n    }\n\n    String responseDetail()\n    {\n        return responseDetail;\n    }\n\n    long correlationId()\n    {\n        return correlationId;\n    }\n\n    long openedLogPosition()\n    {\n        return openedLogPosition;\n    }\n\n    void closedLogPosition(final long closedLogPosition)\n    {\n        this.closedLogPosition = closedLogPosition;\n    }\n\n    long closedLogPosition()\n    {\n        return closedLogPosition;\n    }\n\n    void hasNewLeaderEventPending(final boolean flag)\n    {\n        hasNewLeaderEventPending = flag;\n    }\n\n    boolean hasNewLeaderEventPending()\n    {\n        return hasNewLeaderEventPending;\n    }\n\n    boolean hasOpenEventPending()\n    {\n        return hasOpenEventPending;\n    }\n\n    void clearOpenEventPending()\n    {\n        hasOpenEventPending = false;\n    }\n\n    Action action()\n    {\n        return action;\n    }\n\n    void action(final Action action)\n    {\n        this.action = action;\n    }\n\n    void requestInput(final Object requestInput)\n    {\n        this.requestInput = requestInput;\n    }\n\n    Object requestInput()\n    {\n        return requestInput;\n    }\n\n    void linkIngressImage(final Header header)\n    {\n        if (NULL_VALUE == ingressImageCorrelationId)\n        {\n            ingressImageCorrelationId = ((Image)header.context()).correlationId();\n        }\n    }\n\n    void unlinkIngressImage()\n    {\n        ingressImageCorrelationId = NULL_VALUE;\n    }\n\n    long ingressImageCorrelationId()\n    {\n        return ingressImageCorrelationId;\n    }\n\n    boolean hasTimedOut(final long nowNs, final long sessionTimeoutNs)\n    {\n        return State.INIT != state() && (timeOfLastActivityNs() + sessionTimeoutNs) < nowNs;\n    }\n\n    private long addSessionCounter(final Aeron aeron, final MutableDirectBuffer tempBuffer, final int clusterId)\n    {\n        tempBuffer.putInt(0, clusterId);\n        tempBuffer.putLong(BitUtil.SIZE_OF_INT, id);\n\n        final int keyLength = BitUtil.SIZE_OF_INT + BitUtil.SIZE_OF_LONG;\n\n        int labelLength = 0;\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, \"cluster-session: \");\n        labelLength += tempBuffer.putStringWithoutLengthAscii(keyLength + labelLength, sessionInfo);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(\n            keyLength + labelLength, ClusterCounters.CLUSTER_ID_LABEL_SUFFIX);\n        labelLength += tempBuffer.putIntAscii(keyLength + labelLength, clusterId);\n\n        return aeron.asyncAddCounter(\n            AeronCounters.CLUSTER_SESSION_TYPE_ID,\n            tempBuffer,\n            0,\n            keyLength,\n            tempBuffer,\n            keyLength,\n            labelLength);\n    }\n\n    private static void logStateChange(\n        final int memberId,\n        final long sessionId,\n        final Action action,\n        final State oldState,\n        final State newState,\n        final String reason)\n    {\n//        System.out.println(\"ClusterSession: memberId=\" + memberId + \" id=\" + sessionId + \" action=\" + action + \" \" +\n//            oldState + \" -> \" + newState + \" \" + reason);\n    }\n\n    static void checkEncodedPrincipalLength(final byte[] encodedPrincipal)\n    {\n        if (null != encodedPrincipal && encodedPrincipal.length > MAX_ENCODED_PRINCIPAL_LENGTH)\n        {\n            throw new ClusterException(\n                \"encoded principal max length \" + MAX_ENCODED_PRINCIPAL_LENGTH +\n                \" exceeded: length=\" + encodedPrincipal.length);\n        }\n    }\n\n    public String toString()\n    {\n        return \"ClusterSession{\" +\n            \"id=\" + id +\n            \", clusterMemberId=\" + clusterMemberId +\n            \", responseStreamId=\" + responseStreamId +\n            \", responseChannel='\" + responseChannel + '\\'' +\n            \", sessionInfo='\" + sessionInfo + '\\'' +\n            \", hasNewLeaderEventPending=\" + hasNewLeaderEventPending +\n            \", hasOpenEventPending=\" + hasOpenEventPending +\n            \", correlationId=\" + correlationId +\n            \", openedLogPosition=\" + openedLogPosition +\n            \", closedLogPosition=\" + closedLogPosition +\n            \", timeOfLastActivityNs=\" + timeOfLastActivityNs +\n            \", ingressImageCorrelationId=\" + ingressImageCorrelationId +\n            \", responsePublicationId=\" + responsePublicationId +\n            \", counterRegistrationId=\" + counterRegistrationId +\n            \", responsePublication=\" + responsePublication +\n            \", counter=\" + counter +\n            \", state=\" + state +\n            \", responseDetail='\" + responseDetail + '\\'' +\n            \", eventCode=\" + eventCode +\n            \", closeReason=\" + closeReason +\n            \", action=\" + action +\n            \", requestInput=\" + requestInput +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterSessionProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.security.SessionProxy;\n\nimport static io.aeron.cluster.ClusterSession.State.CHALLENGED;\n\n/**\n * Proxy for a session being authenticated by an {@link io.aeron.security.Authenticator}.\n */\nfinal class ClusterSessionProxy implements SessionProxy\n{\n    private final EgressPublisher egressPublisher;\n    private ClusterSession clusterSession;\n\n    ClusterSessionProxy(final EgressPublisher egressPublisher)\n    {\n        this.egressPublisher = egressPublisher;\n    }\n\n    SessionProxy session(final ClusterSession clusterSession)\n    {\n        this.clusterSession = clusterSession;\n        return this;\n    }\n\n    public long sessionId()\n    {\n        return clusterSession.id();\n    }\n\n    public boolean challenge(final byte[] encodedChallenge)\n    {\n        if (egressPublisher.sendChallenge(clusterSession, encodedChallenge))\n        {\n            clusterSession.state(CHALLENGED, \"challenged\");\n            return true;\n        }\n\n        return false;\n    }\n\n    public boolean authenticate(final byte[] encodedPrincipal)\n    {\n        ClusterSession.checkEncodedPrincipalLength(encodedPrincipal);\n        clusterSession.authenticate(encodedPrincipal);\n\n        return true;\n    }\n\n    public void reject()\n    {\n        clusterSession.reject(\n            EventCode.AUTHENTICATION_REJECTED, ConsensusModule.Configuration.SESSION_REJECTED_MSG, null);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterTermination.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.ErrorHandler;\n\nclass ClusterTermination\n{\n    private long deadlineNs;\n    private boolean isAwaitingServices = true;\n\n    ClusterTermination(final long deadlineNs)\n    {\n        this.deadlineNs = deadlineNs;\n    }\n\n    void deadlineNs(final long deadlineNs)\n    {\n        this.deadlineNs = deadlineNs;\n    }\n\n    boolean canTerminate(final ClusterMember[] members, final long nowNs)\n    {\n        if (isAwaitingServices)\n        {\n            return false;\n        }\n\n        boolean result = true;\n\n        for (final ClusterMember member : members)\n        {\n            if (!member.isLeader() && !member.hasTerminated())\n            {\n                result = false;\n                break;\n            }\n        }\n\n        return result || nowNs >= deadlineNs;\n    }\n\n    void onServicesTerminated()\n    {\n        isAwaitingServices = false;\n    }\n\n    void terminationPosition(\n        final ErrorHandler errorHandler,\n        final ConsensusPublisher consensusPublisher,\n        final ClusterMember[] members,\n        final ClusterMember thisMember,\n        final long leadershipTermId,\n        final long position)\n    {\n        for (final ClusterMember member : members)\n        {\n            member.hasTerminated(false);\n\n            if (member != thisMember)\n            {\n                if (!consensusPublisher.terminationPosition(member.publication(), leadershipTermId, position))\n                {\n                    errorHandler.onError(new ClusterEvent(\n                        \"failed to send termination position to member=\" + member.id(), AeronException.Category.WARN));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterTool.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport static io.aeron.cluster.ClusterToolCommand.*;\nimport static io.aeron.cluster.ClusterToolOperator.*;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\nimport static org.agrona.SystemUtil.getDurationInNanos;\n\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.util.Map;\nimport java.util.function.BiConsumer;\n\nimport io.aeron.Image;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport io.aeron.Aeron;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.cluster.service.ClusterNodeControlProperties;\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\n\n/**\n * Tool for control and investigating the state of a cluster node.\n * <pre>\n * Usage: ClusterTool &#60;cluster-dir&#62; &#60;command&#62; [options]\n *                         describe: prints out all descriptors in the mark file.\n *                              pid: prints PID of cluster component.\n *                    recovery-plan: [service count] prints recovery plan of cluster component.\n *                    recording-log: prints recording log of cluster component.\n *               sort-recording-log: reorders entries in the recording log to match the order in memory.\n * seed-recording-log-from-snapshot: creates a new recording log based on the latest valid snapshot.\n *                           errors: prints Aeron and cluster component error logs.\n *                     list-members: prints leader memberId and active members.\n *                     backup-query: [delay] get, or set, time of next backup query.\n *       invalidate-latest-snapshot: marks the latest snapshot as a invalid so the previous is loaded.\n *                         snapshot: triggers a snapshot on the leader.\n *                 standby-snapshot: triggers a snapshot on cluster standby nodes.\n *                          suspend: suspends appending to the log.\n *                           resume: resumes reading from the log.\n *                         shutdown: initiates an orderly stop of the cluster with a snapshot.\n *                            abort: stops the cluster without a snapshot.\n *      describe-latest-cm-snapshot: prints the contents of the latest valid consensus module snapshot.\n *                        is-leader: returns zero if the cluster node is leader, non-zero if not\n * </pre>\n */\n@Config(existsInC = false)\npublic final class ClusterTool\n{\n    /**\n     * Timeout in nanoseconds for the tool to wait while trying to perform an operation.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = 0, hasContext = false)\n    public static final String AERON_CLUSTER_TOOL_TIMEOUT_PROP_NAME = \"aeron.cluster.tool.timeout\";\n\n    /**\n     * Delay in nanoseconds to be applied to an operation such as when the new cluster backup query will occur.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = 0, hasContext = false)\n    public static final String AERON_CLUSTER_TOOL_DELAY_PROP_NAME = \"aeron.cluster.tool.delay\";\n\n    /**\n     * Property name for setting the channel used for archive replays.\n     */\n    @Config(hasContext = false)\n    public static final String AERON_CLUSTER_TOOL_REPLAY_CHANNEL_PROP_NAME = \"aeron.cluster.tool.replay.channel\";\n\n    /**\n     * Default channel used for archive replays.\n     */\n    @Config\n    public static final String AERON_CLUSTER_TOOL_REPLAY_CHANNEL_DEFAULT = \"aeron:ipc\";\n\n    /**\n     * Channel used for archive replays.\n     */\n    public static final String AERON_CLUSTER_TOOL_REPLAY_CHANNEL = SystemUtil.getProperty(\n        AERON_CLUSTER_TOOL_REPLAY_CHANNEL_PROP_NAME, AERON_CLUSTER_TOOL_REPLAY_CHANNEL_DEFAULT);\n\n    /**\n     * Property name for setting the stream id used for archive replays.\n     */\n    @Config(hasContext = false)\n    public static final String AERON_CLUSTER_TOOL_REPLAY_STREAM_ID_PROP_NAME = \"aeron.cluster.tool.replay.stream.id\";\n\n    /**\n     * Default stream id used for archive replays.\n     */\n    @Config\n    public static final int AERON_CLUSTER_TOOL_REPLAY_STREAM_ID_DEFAULT = 103;\n\n    /**\n     * Stream id used for archive replays.\n     */\n    public static final int AERON_CLUSTER_TOOL_REPLAY_STREAM_ID = Integer.getInteger(\n        AERON_CLUSTER_TOOL_REPLAY_STREAM_ID_PROP_NAME, AERON_CLUSTER_TOOL_REPLAY_STREAM_ID_DEFAULT);\n\n    /**\n     * Timeout in milliseconds used by the tool for operations.\n     */\n    public static final long TIMEOUT_MS =\n        NANOSECONDS.toMillis(getDurationInNanos(AERON_CLUSTER_TOOL_TIMEOUT_PROP_NAME, 0));\n\n    private static final ClusterToolOperator BACKWARD_COMPATIBLE_OPERATIONS = new ClusterToolOperator(\n        AERON_CLUSTER_TOOL_REPLAY_CHANNEL,\n        AERON_CLUSTER_TOOL_REPLAY_STREAM_ID,\n        TIMEOUT_MS);\n    private static final String HELP_PREFIX = \"Usage: <cluster-dir> <command> [options]\";\n\n    private static final Object2ObjectHashMap<String, ClusterToolCommand> COMMANDS = new Object2ObjectHashMap<>();\n\n    static\n    {\n        final ClusterToolOperator operator = new ClusterToolOperator(\n            AERON_CLUSTER_TOOL_REPLAY_CHANNEL,\n            AERON_CLUSTER_TOOL_REPLAY_STREAM_ID,\n            TIMEOUT_MS);\n\n        COMMANDS.put(\"describe\", new ClusterToolCommand(\n            ignoreFailures(action(operator::describeClusterMarkFile)),\n            \"prints out all descriptors in the mark file.\"));\n\n        COMMANDS.put(\"pid\", new ClusterToolCommand(\n            action(operator::pid),\n            \"prints PID of cluster component.\"));\n\n        COMMANDS.put(\"recovery-plan\", new ClusterToolCommand(\n            (clusterDir, out, args) ->\n            {\n                if (args.length < 3)\n                {\n                    printHelp(COMMANDS, HELP_PREFIX);\n                    return -1;\n                }\n                return operator.recoveryPlan(System.out, clusterDir, Integer.parseInt(args[2]));\n            }, \"[service count] prints recovery plan of cluster component.\"));\n\n        COMMANDS.put(\"recording-log\", new ClusterToolCommand(\n            action(operator::recordingLog),\n            \"prints recording log of cluster component.\"));\n\n        COMMANDS.put(\"sort-recording-log\", new ClusterToolCommand(\n            action(operator::sortRecordingLog),\n            \"reorders entries in the recording log to match the order in memory.\"));\n\n        COMMANDS.put(\"seed-recording-log-from-snapshot\", new ClusterToolCommand(\n            action(operator::seedRecordingLogFromSnapshot),\n            \"creates a new recording log based on the latest valid snapshot.\"));\n\n        COMMANDS.put(\"errors\", new ClusterToolCommand(\n            action(operator::errors),\n            \"prints Aeron and cluster component error logs.\"));\n\n        COMMANDS.put(\"list-members\", new ClusterToolCommand(\n            action(operator::listMembers),\n            \"prints leader memberId and active members.\"));\n\n        COMMANDS.put(\"backup-query\", new ClusterToolCommand((clusterDir, out, args) ->\n        {\n            if (args.length < 3)\n            {\n                return operator.printNextBackupQuery(clusterDir, System.out);\n            }\n            else\n            {\n                return operator.nextBackupQuery(\n                    clusterDir,\n                    System.out,\n                    NANOSECONDS.toMillis(SystemUtil.parseDuration(AERON_CLUSTER_TOOL_DELAY_PROP_NAME, args[2])));\n            }\n        }, \"[delay] get, or set, time of next backup query.\"));\n\n        COMMANDS.put(\"invalidate-latest-snapshot\", new ClusterToolCommand(\n            action(operator::invalidateLatestSnapshot),\n            \"marks the latest snapshot as a invalid so the previous is loaded.\"));\n\n        COMMANDS.put(\"is-leader\", new ClusterToolCommand(\n            action(operator::isLeader),\n            \"returns zero if the cluster node is leader, non-zero if not.\"));\n\n        COMMANDS.put(\"snapshot\", new ClusterToolCommand(\n            action(operator::snapshot),\n            \"triggers a snapshot on the leader.\"));\n\n        COMMANDS.put(\"suspend\", new ClusterToolCommand(\n            action(operator::suspend),\n            \"suspends appending to the log.\"));\n\n        COMMANDS.put(\"resume\", new ClusterToolCommand(\n            action(operator::resume),\n            \"resumes appending to the log.\"));\n\n        COMMANDS.put(\"shutdown\", new ClusterToolCommand(\n            action(operator::shutdown),\n            \"initiates an orderly stop of the cluster with a snapshot.\"));\n\n        COMMANDS.put(\"abort\", new ClusterToolCommand(\n            action(operator::abort),\n            \"stops the cluster without a snapshot.\"));\n\n        COMMANDS.put(\"describe-latest-cm-snapshot\", new ClusterToolCommand(\n            action((clusterDir, listener) -> operator.describeLatestConsensusModuleSnapshot(\n            clusterDir,\n            listener,\n            null)),\n            \"prints the contents of the latest valid consensus module snapshot.\"));\n    }\n\n    private ClusterTool()\n    {\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"methodlength\")\n    public static void main(final String[] args)\n    {\n        if (args.length < 2)\n        {\n            printHelp(COMMANDS, HELP_PREFIX);\n            System.exit(-1);\n        }\n\n        final File clusterDir = new File(args[0]);\n        if (!clusterDir.exists())\n        {\n            System.err.println(\"ERR: cluster directory not found: \" + clusterDir.getAbsolutePath());\n            printHelp(COMMANDS, HELP_PREFIX);\n            System.exit(-1);\n        }\n\n        final ClusterToolCommand command = COMMANDS.get(args[1]);\n        if (null == command)\n        {\n            System.out.println(\"Unknown command: \" + args[1]);\n            printHelp(COMMANDS, HELP_PREFIX);\n            System.exit(-1);\n        }\n        else\n        {\n            final int status = command.action().act(clusterDir, System.out, args);\n            if (SUCCESS != status)\n            {\n                System.exit(status);\n            }\n        }\n    }\n\n    /**\n     * Print out the descriptors in the {@link ClusterMarkFile}s.\n     *\n     * @param out        to print the output to.\n     * @param clusterDir where the cluster is running.\n     */\n    public static void describe(final PrintStream out, final File clusterDir)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.describeClusterMarkFile(clusterDir, out);\n    }\n\n    /**\n     * Print out the PID of the cluster process.\n     *\n     * @param out        to print the output to.\n     * @param clusterDir where the cluster is running.\n     */\n    public static void pid(final PrintStream out, final File clusterDir)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.pid(clusterDir, out);\n    }\n\n    /**\n     * Print out the {@link io.aeron.cluster.RecordingLog.RecoveryPlan} for the cluster.\n     *\n     * @param out          to print the output to.\n     * @param clusterDir   where the cluster is running.\n     * @param serviceCount of services running in the containers.\n     */\n    public static void recoveryPlan(final PrintStream out, final File clusterDir, final int serviceCount)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.recoveryPlan(out, clusterDir, serviceCount);\n    }\n\n    /**\n     * Print out the {@link RecordingLog} for the cluster.\n     *\n     * @param out        to print the output to.\n     * @param clusterDir where the cluster is running.\n     */\n    public static void recordingLog(final PrintStream out, final File clusterDir)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.recordingLog(clusterDir, out);\n    }\n\n    /**\n     * Re-order entries in thee {@link RecordingLog} file on disc if they are not in a proper order.\n     *\n     * @param clusterDir where the cluster is running.\n     * @return {@code true} if file contents was changed or {@code false} if it was already in the correct order.\n     */\n    public static boolean sortRecordingLog(final File clusterDir)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.sortRecordingLog(clusterDir) == SUCCESS;\n    }\n\n    /**\n     * Create a new {@link RecordingLog} based on the latest valid snapshot whose term base and log positions are set\n     * to zero. The original recording log file is backed up as {@code recording.log.bak}.\n     *\n     * @param clusterDir where the cluster is running.\n     */\n    public static void seedRecordingLogFromSnapshot(final File clusterDir)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.seedRecordingLogFromSnapshot(clusterDir);\n    }\n\n    /**\n     * Print out the errors in the error logs for the cluster components.\n     *\n     * @param out        to print the output to.\n     * @param clusterDir where the cluster is running.\n     */\n    public static void errors(final PrintStream out, final File clusterDir)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.errors(clusterDir, out);\n    }\n\n    /**\n     * Print out a list of the current members of the cluster.\n     *\n     * @param out        to print the output to.\n     * @param clusterDir where the cluster is running.\n     */\n    public static void listMembers(final PrintStream out, final File clusterDir)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.listMembers(clusterDir, out);\n    }\n\n    /**\n     * Print out the time of the next backup query.\n     *\n     * @param out        to print the output to.\n     * @param clusterDir where the cluster backup is running.\n     */\n    public static void printNextBackupQuery(final PrintStream out, final File clusterDir)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.printNextBackupQuery(clusterDir, out);\n    }\n\n    /**\n     * Set the time of the next backup query for the cluster backup.\n     *\n     * @param out        to print the output to.\n     * @param clusterDir where the cluster is running.\n     * @param delayMs    from the current time for the next backup query.\n     */\n    public static void nextBackupQuery(final PrintStream out, final File clusterDir, final long delayMs)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.nextBackupQuery(clusterDir, out, delayMs);\n    }\n\n    /**\n     * Print out the descriptors in the {@link ClusterMarkFile}s.\n     *\n     * @param out              to print the output to.\n     * @param serviceMarkFiles to query.\n     */\n    public static void describe(final PrintStream out, final ClusterMarkFile[] serviceMarkFiles)\n    {\n        BACKWARD_COMPATIBLE_OPERATIONS.describe(out, serviceMarkFiles);\n    }\n\n    /**\n     * Determine if a given node is a leader.\n     *\n     * @param out        to print the output to.\n     * @param clusterDir where the cluster is running.\n     * @return 0 if the node is an active leader in a closed election, 1 if not,\n     * -1 if the mark file does not exist.\n     */\n    public static int isLeader(final PrintStream out, final File clusterDir)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.isLeader(clusterDir, out);\n    }\n\n    /**\n     * Does a {@link ClusterMarkFile} exist in the cluster directory.\n     *\n     * @param clusterDir to check for if a mark file exists.\n     * @return true if the cluster mark file exists.\n     */\n    public static boolean markFileExists(final File clusterDir)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.markFileExists(clusterDir);\n    }\n\n    /**\n     * List the current members of a cluster.\n     *\n     * @param clusterMembership to populate.\n     * @param clusterDir        where the cluster is running.\n     * @param timeoutMs         to wait on the query completing.\n     * @return true if successful.\n     */\n    public static boolean listMembers(\n        final ClusterMembership clusterMembership, final File clusterDir, final long timeoutMs)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.listMembers(clusterMembership, clusterDir, timeoutMs);\n    }\n\n    /**\n     * Query the membership of a cluster.\n     *\n     * @param markFile          for the cluster component.\n     * @param timeoutMs         to wait for the query.\n     * @param clusterMembership to populate.\n     * @return true if the query was successful.\n     */\n    public static boolean queryClusterMembers(\n        final ClusterMarkFile markFile, final long timeoutMs, final ClusterMembership clusterMembership)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.queryClusterMembers(markFile, timeoutMs, clusterMembership);\n    }\n\n    /**\n     * Query the membership of a cluster.\n     *\n     * @param controlProperties from a {@link ClusterMarkFile}.\n     * @param timeoutMs         to wait for the query.\n     * @param clusterMembership to populate.\n     * @return true if the query was successful.\n     */\n    public static boolean queryClusterMembers(\n        final ClusterNodeControlProperties controlProperties,\n        final long timeoutMs,\n        final ClusterMembership clusterMembership)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.queryClusterMembers(controlProperties, timeoutMs, clusterMembership);\n    }\n\n    /**\n     * Get the deadline time (MS) for the next cluster backup query.\n     *\n     * @param clusterDir where the cluster component is running.\n     * @return the deadline time (MS) for the next cluster backup query, or {@link Aeron#NULL_VALUE} not available.\n     */\n    public static long nextBackupQueryDeadlineMs(final File clusterDir)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.nextBackupQueryDeadlineMs(clusterDir);\n    }\n\n    /**\n     * Get the deadline time (MS) for the next cluster backup query.\n     *\n     * @param markFile for the cluster component.\n     * @return the deadline time (MS) for the next cluster backup query, or {@link Aeron#NULL_VALUE} not available.\n     */\n    public static long nextBackupQueryDeadlineMs(final ClusterMarkFile markFile)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.nextBackupQueryDeadlineMs(markFile);\n    }\n\n    /**\n     * Set the deadline time (MS) for the next cluster backup query.\n     *\n     * @param clusterDir where the cluster component is running.\n     * @param timeMs     to set for the next deadline.\n     * @return true if successful, otherwise false.\n     */\n    public static boolean nextBackupQueryDeadlineMs(final File clusterDir, final long timeMs)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.nextBackupQueryDeadlineMs(clusterDir, timeMs);\n    }\n\n    /**\n     * Set the deadline time (MS) for the next cluster backup query.\n     *\n     * @param markFile for the cluster component.\n     * @param timeMs   to set for the next deadline.\n     * @return true if successful, otherwise false.\n     */\n    public static boolean nextBackupQueryDeadlineMs(final ClusterMarkFile markFile, final long timeMs)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.nextBackupQueryDeadlineMs(markFile, timeMs);\n    }\n\n    /**\n     * Invalidate the latest snapshot so recovery will use an earlier one or log if no earlier one exists.\n     *\n     * @param out        to print the operation result.\n     * @param clusterDir where the cluster component is running.\n     * @return true if the latest snapshot was invalidated.\n     */\n    public static boolean invalidateLatestSnapshot(final PrintStream out, final File clusterDir)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.invalidateLatestSnapshot(clusterDir, out) == SUCCESS;\n    }\n\n    /**\n     * Print out a summary of the state captured in the latest consensus module snapshot.\n     *\n     * @param out        to print the operation result.\n     * @param clusterDir where the cluster is running.\n     * @return <code>true</code> if the snapshot was successfully described <code>false</code> otherwise.\n     */\n    public static boolean describeLatestConsensusModuleSnapshot(final PrintStream out, final File clusterDir)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.describeLatestConsensusModuleSnapshot(clusterDir, out, null) == SUCCESS;\n    }\n\n    /**\n     * Print out a summary of the state captured in the latest consensus module snapshot.\n     *\n     * @param out                         to print the operation result.\n     * @param clusterDir                  where the cluster is running.\n     * @param postConsensusImageDescriber describe the data after the snapshot used for extensions.\n     * @return <code>true</code> if the snapshot was successfully described <code>false</code> otherwise.\n     */\n    public static boolean describeLatestConsensusModuleSnapshot(\n        final PrintStream out,\n        final File clusterDir,\n        final BiConsumer<Image, Aeron> postConsensusImageDescriber)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.describeLatestConsensusModuleSnapshot(clusterDir, out, null) == SUCCESS;\n    }\n\n    /**\n     * Instruct the cluster to take a snapshot.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return true is the operation was successfully requested.\n     */\n    public static boolean snapshot(final File clusterDir, final PrintStream out)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.snapshot(clusterDir, out) == SUCCESS;\n    }\n\n    /**\n     * Instruct the cluster to suspend operation.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return true is the operation was successfully requested.\n     */\n    public static boolean suspend(final File clusterDir, final PrintStream out)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.suspend(clusterDir, out) == SUCCESS;\n    }\n\n    /**\n     * Instruct the cluster to resume operation.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return true is the operation was successfully requested.\n     */\n    public static boolean resume(final File clusterDir, final PrintStream out)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.resume(clusterDir, out) == SUCCESS;\n    }\n\n    /**\n     * Instruct the cluster to shut down.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return true is the operation was successfully requested.\n     */\n    public static boolean shutdown(final File clusterDir, final PrintStream out)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.shutdown(clusterDir, out) == SUCCESS;\n    }\n\n    /**\n     * Instruct the cluster to abort.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return true is the operation was successfully requested.\n     */\n    public static boolean abort(final File clusterDir, final PrintStream out)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.abort(clusterDir, out) == SUCCESS;\n    }\n\n    /**\n     * Finds the latest valid snapshot from the log file.\n     *\n     * @param clusterDir where the cluster node is running.\n     * @return entry or {@code null} if not found.\n     */\n    static RecordingLog.Entry findLatestValidSnapshot(final File clusterDir)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.findLatestValidSnapshot(clusterDir);\n    }\n\n    /**\n     * Load {@link ClusterNodeControlProperties} from the mark file.\n     *\n     * @param clusterDir where the cluster node is running.\n     * @return control properties.\n     */\n    static ClusterNodeControlProperties loadControlProperties(final File clusterDir)\n    {\n        return BACKWARD_COMPATIBLE_OPERATIONS.loadControlProperties(clusterDir);\n    }\n\n    /*--------------------------------------------------------------*/\n\n    /**\n     * Cluster Tool commands map.\n     * This is to allow other tools to simply extend ClusterTool\n     * <p>\n     * Note that the map is cloned and both key and value are Java immutable objects.\n     *\n     * @return a clone of Cluster Tool commands map\n     */\n    public static Map<String, ClusterToolCommand> commands()\n    {\n        return new Object2ObjectHashMap<>(COMMANDS);\n    }\n\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterToolCommand.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n */\n\npackage io.aeron.cluster;\n\nimport static io.aeron.cluster.ClusterToolOperator.SUCCESS;\n\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.function.ToIntBiFunction;\nimport java.util.function.ToIntFunction;\n\n/**\n * A command to be used by ClusterTool.\n * The command is an immutable representation of what to run (the Action), and the description for that action\n * (used in help).\n */\npublic final class ClusterToolCommand\n{\n    /**\n     * Convenience method to ignore failure exit status.\n     *\n     * @param actual actual action.\n     * @return SUCCESS\n     */\n    public static Action ignoreFailures(final Action actual)\n    {\n        return (clusterDir, out, args) ->\n        {\n            actual.act(clusterDir, out, args);\n            return SUCCESS;\n        };\n    }\n\n    /**\n     * Convenience method for actions that do not require extra args.\n     *\n     * @param actual actual action.\n     * @return SUCCESS\n     */\n    public static Action action(final ToIntBiFunction<File, PrintStream> actual)\n    {\n        return (clusterDir, out, args) -> actual.applyAsInt(clusterDir, out);\n    }\n\n    /**\n     * Print help for a tool with the specified commands.\n     *\n     * @param commands map of commands by name.\n     * @param prefix   usage description prefix.\n     */\n    public static void printHelp(final Map<String, ClusterToolCommand> commands, final String prefix)\n    {\n        System.out.format(\"%s%n\", prefix);\n        final int indentValue = Collections.max(commands.keySet().stream().map(String::length).toList()) + 1;\n        final String indentSpaces = new String(new char[indentValue + 2]).replace('\\0', ' ');\n        for (final Map.Entry<String, ClusterToolCommand> command : commands.entrySet())\n        {\n            final int indent = indentValue - command.getKey().length();\n            final String description = command.getValue().describe().replaceAll(\"%n\", \"%n\" + indentSpaces);\n            System.out.printf(\"%\" + indent + \"s\", \" \");\n            System.out.printf(\"%s: %s%n\", command.getKey(), description);\n        }\n        System.out.flush();\n    }\n\n    private final Action action;\n    private final String description;\n\n    /**\n     * Constructor.\n     *\n     * @param action      action to perform for command.\n     * @param description description of command.\n     */\n    public ClusterToolCommand(final Action action, final String description)\n    {\n        this.action = action;\n        this.description = description;\n    }\n\n    /**\n     * Convenience method for actions that only require the cluster directory.\n     *\n     * @param actual actual action.\n     * @return SUCCESS.\n     */\n    public static Action action(final ToIntFunction<File> actual)\n    {\n        return (clusterDir, out, args) -> actual.applyAsInt(clusterDir);\n    }\n\n    /**\n     * Description of command.\n     *\n     * @return description of command.\n     */\n    public String describe()\n    {\n        return description;\n    }\n\n    /**\n     * The actual action to perform when given the command.\n     *\n     * @return the actual action to perform when given the command.\n     */\n    public Action action()\n    {\n        return action;\n    }\n\n    /**\n     * Functional interface of a cluster tool operator action used in {@link ClusterToolCommand}.\n     */\n    @FunctionalInterface\n    public interface Action\n    {\n        /**\n         * An action for an operator tool to control cluster.\n         *\n         * @param clusterDir local cluster directory.\n         * @param out        Where to print the output.\n         * @param args       args to tool.\n         * @return exit value.\n         */\n        int act(File clusterDir, PrintStream out, String[] args);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusterToolOperator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n */\n\npackage io.aeron.cluster;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static java.nio.file.StandardCopyOption.*;\nimport static java.nio.file.StandardOpenOption.*;\nimport static org.agrona.Strings.isEmpty;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.io.UncheckedIOException;\nimport java.nio.ByteBuffer;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.*;\n\nimport org.agrona.BufferUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport io.aeron.*;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.mark.ClusterComponentType;\nimport io.aeron.cluster.codecs.mark.MarkFileHeaderDecoder;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.cluster.service.ClusterNodeControlProperties;\nimport io.aeron.cluster.service.ConsensusModuleProxy;\n\n/**\n * Actions for an operator tool to control cluster.\n * This class is used by {@link ClusterTool} by Aeron but also allows to be used / extended by other tools.\n *\n * @see ClusterTool\n */\npublic class ClusterToolOperator\n{\n    /**\n     * Successful action return value = exist status.\n     */\n    public static final int SUCCESS = 0;\n    private static final int MARK_FILE_VERSION_WITH_CLUSTER_SERVICES_DIR = 1;\n    private static final int FAILURE = -1;\n\n    private final String toolChannel;\n    private final int toolStreamId;\n    private final long timeoutMs;\n\n    /**\n     * Constructor.\n     *\n     * @param toolChannel  channel to use.\n     * @param toolStreamId stream id to use.\n     * @param timeoutMs    timeout in milliseconds.\n     */\n    protected ClusterToolOperator(\n        final String toolChannel,\n        final int toolStreamId,\n        final long timeoutMs)\n    {\n        this.toolChannel = toolChannel;\n        this.toolStreamId = toolStreamId;\n        this.timeoutMs = timeoutMs;\n    }\n\n    /**\n     * Print out the PID of the cluster process.\n     *\n     * @param out        to print the output to.\n     * @param clusterDir where the cluster is running.\n     * @return exit status.\n     */\n    protected int pid(final File clusterDir, final PrintStream out)\n    {\n        if (markFileExists(clusterDir) || timeoutMs > 0)\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                out.println(markFile.decoder().pid());\n            }\n        }\n        else\n        {\n            return FAILURE;\n        }\n        return SUCCESS;\n    }\n\n    /**\n     * Print out the {@link io.aeron.cluster.RecordingLog.RecoveryPlan} for the cluster.\n     *\n     * @param out          to print the output to.\n     * @param clusterDir   where the cluster is running.\n     * @param serviceCount of services running in the containers.\n     * @return exit status\n     */\n    protected int recoveryPlan(final PrintStream out, final File clusterDir, final int serviceCount)\n    {\n        try (AeronArchive archive = AeronArchive.connect(new AeronArchive.Context().clientName(\"cluster-tool\"));\n            RecordingLog recordingLog = new RecordingLog(clusterDir, false))\n        {\n            out.println(recordingLog.createRecoveryPlan(archive, serviceCount, Aeron.NULL_VALUE));\n        }\n        return SUCCESS;\n    }\n\n    /**\n     * Print out the {@link RecordingLog} for the cluster.\n     *\n     * @param clusterDir where the cluster is running.\n     * @param out        to print the output to.\n     * @return exit status\n     */\n    protected int recordingLog(final File clusterDir, final PrintStream out)\n    {\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, false))\n        {\n            out.println(recordingLog);\n        }\n        return SUCCESS;\n    }\n\n    /**\n     * Re-order entries in thee {@link RecordingLog} file on disc if they are not in a proper order.\n     *\n     * @param clusterDir where the cluster is running.\n     * @return {@code SUCCESS} if file contents was changed or {@code FAILURE} if it was already in the correct order.\n     */\n    protected int sortRecordingLog(final File clusterDir)\n    {\n        final List<RecordingLog.Entry> entries;\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, false))\n        {\n            entries = recordingLog.entries();\n            if (isRecordingLogSorted(entries))\n            {\n                return FAILURE;\n            }\n        }\n        catch (final RuntimeException ex)\n        {\n            return FAILURE;\n        }\n\n        updateRecordingLog(clusterDir, entries);\n\n        return SUCCESS;\n    }\n\n    /**\n     * Create a new {@link RecordingLog} based on the latest valid snapshot whose term base and log positions are set\n     * to zero. The original recording log file is backed up as {@code recording.log.bak}.\n     *\n     * @param clusterDir where the cluster is running.\n     * @return exit status\n     */\n    protected int seedRecordingLogFromSnapshot(final File clusterDir)\n    {\n        int snapshotIndex = Aeron.NULL_VALUE;\n        final List<RecordingLog.Entry> entries;\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, false))\n        {\n            entries = recordingLog.entries();\n            for (int i = entries.size() - 1; i >= 0; i--)\n            {\n                final RecordingLog.Entry entry = entries.get(i);\n                if (RecordingLog.isValidSnapshot(entry) && ConsensusModule.Configuration.SERVICE_ID == entry.serviceId)\n                {\n                    snapshotIndex = i;\n                    break;\n                }\n            }\n        }\n\n        final Path recordingLogBackup = clusterDir.toPath().resolve(RecordingLog.RECORDING_LOG_FILE_NAME + \".bak\");\n        try\n        {\n            Files.copy(\n                clusterDir.toPath().resolve(RecordingLog.RECORDING_LOG_FILE_NAME),\n                recordingLogBackup,\n                REPLACE_EXISTING,\n                COPY_ATTRIBUTES);\n        }\n        catch (final IOException ex)\n        {\n            throw new UncheckedIOException(ex);\n        }\n\n        if (Aeron.NULL_VALUE == snapshotIndex)\n        {\n            updateRecordingLog(clusterDir, Collections.emptyList());\n        }\n        else\n        {\n            final List<RecordingLog.Entry> truncatedEntries = new ArrayList<>();\n            int serviceId = ConsensusModule.Configuration.SERVICE_ID;\n            for (int i = snapshotIndex; i >= 0; i--)\n            {\n                final RecordingLog.Entry entry = entries.get(i);\n                if (RecordingLog.isValidSnapshot(entry) && entry.serviceId == serviceId)\n                {\n                    truncatedEntries.add(new RecordingLog.Entry(\n                        entry.recordingId,\n                        entry.leadershipTermId,\n                        0,\n                        0,\n                        entry.timestamp,\n                        entry.serviceId,\n                        entry.type,\n                        null,\n                        entry.isValid,\n                        NULL_VALUE));\n                    serviceId++;\n                }\n                else\n                {\n                    break;\n                }\n            }\n            Collections.reverse(truncatedEntries);\n\n            updateRecordingLog(clusterDir, truncatedEntries);\n        }\n        return SUCCESS;\n    }\n\n    /**\n     * Print out the errors in the error logs for the cluster components.\n     *\n     * @param clusterDir where the cluster is running.\n     * @param out        to print the output to.\n     * @return exit status\n     */\n    protected int errors(final File clusterDir, final PrintStream out)\n    {\n        File clusterServicesDir = clusterDir;\n        if (markFileExists(clusterDir) || timeoutMs > 0)\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                printTypeAndActivityTimestamp(out, markFile);\n                printErrors(out, markFile);\n\n                final String aeronDirectory = markFile.decoder().aeronDirectory();\n                out.println();\n                printDriverErrors(out, aeronDirectory);\n\n                clusterServicesDir = resolveClusterServicesDir(clusterDir, markFile.decoder());\n            }\n        }\n        else\n        {\n            out.println(ClusterMarkFile.FILENAME + \" does not exist.\");\n        }\n\n        final ClusterMarkFile[] serviceMarkFiles = openServiceMarkFiles(clusterServicesDir, out::println);\n        for (final ClusterMarkFile serviceMarkFile : serviceMarkFiles)\n        {\n            printTypeAndActivityTimestamp(out, serviceMarkFile);\n            printErrors(out, serviceMarkFile);\n            serviceMarkFile.close();\n        }\n        return SUCCESS;\n    }\n\n    /**\n     * Print out a list of the current members of the cluster.\n     *\n     * @param clusterDir where the cluster is running.\n     * @param out        to print the output to.\n     * @return exit status\n     */\n    protected int listMembers(final File clusterDir, final PrintStream out)\n    {\n        if (markFileExists(clusterDir) || timeoutMs > 0)\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                final ClusterMembership clusterMembership = new ClusterMembership();\n                final long timeoutMsToUse = Math.max(TimeUnit.SECONDS.toMillis(1), this.timeoutMs);\n\n                if (queryClusterMembers(markFile, timeoutMsToUse, clusterMembership))\n                {\n                    out.println(\n                        \"currentTimeNs=\" + clusterMembership.currentTimeNs +\n                        \", leaderMemberId=\" + clusterMembership.leaderMemberId +\n                        \", memberId=\" + clusterMembership.memberId +\n                        \", activeMembers=\" + clusterMembership.activeMembers);\n                }\n                else\n                {\n                    out.println(\"timeout waiting for response from node\");\n                    return FAILURE;\n                }\n            }\n        }\n        else\n        {\n            out.println(ClusterMarkFile.FILENAME + \" does not exist.\");\n            return FAILURE;\n        }\n        return SUCCESS;\n    }\n\n    /**\n     * Print out the time of the next backup query.\n     *\n     * @param clusterDir where the cluster backup is running.\n     * @param out        to print the output to.\n     * @return exit status\n     */\n    protected int printNextBackupQuery(final File clusterDir, final PrintStream out)\n    {\n        if (markFileExists(clusterDir) || timeoutMs > 0)\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                if (markFile.decoder().componentType() != ClusterComponentType.BACKUP)\n                {\n                    out.println(\"not a cluster backup node\");\n                    return FAILURE;\n                }\n                else\n                {\n                    out.format(\"%2$tF %1$tH:%1$tM:%1$tS next: %2$tF %2$tH:%2$tM:%2$tS%n\",\n                        new Date(),\n                        new Date(nextBackupQueryDeadlineMs(markFile)));\n                }\n            }\n        }\n        else\n        {\n            out.println(ClusterMarkFile.FILENAME + \" does not exist.\");\n            return FAILURE;\n        }\n        return SUCCESS;\n    }\n\n    /**\n     * Set the time of the next backup query for the cluster backup.\n     *\n     * @param clusterDir where the cluster is running.\n     * @param out        to print the output to.\n     * @param delayMs    from the current time for the next backup query.\n     * @return exit status\n     */\n    protected int nextBackupQuery(final File clusterDir, final PrintStream out, final long delayMs)\n    {\n        if (markFileExists(clusterDir) || timeoutMs > 0)\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                if (markFile.decoder().componentType() != ClusterComponentType.BACKUP)\n                {\n                    out.println(\"not a cluster backup node\");\n                    return FAILURE;\n                }\n                else\n                {\n                    final EpochClock epochClock = SystemEpochClock.INSTANCE;\n                    nextBackupQueryDeadlineMs(markFile, epochClock.time() + delayMs);\n                    out.format(\"%2$tF %1$tH:%1$tM:%1$tS setting next: %2$tF %2$tH:%2$tM:%2$tS%n\",\n                        new Date(),\n                        new Date(nextBackupQueryDeadlineMs(markFile)));\n                }\n            }\n        }\n        else\n        {\n            out.println(ClusterMarkFile.FILENAME + \" does not exist.\");\n            return FAILURE;\n        }\n        return SUCCESS;\n    }\n\n    /**\n     * Print out the descriptors in the {@link ClusterMarkFile}s.\n     *\n     * @param out              to print the output to.\n     * @param serviceMarkFiles to query.\n     */\n    protected void describe(final PrintStream out, final ClusterMarkFile[] serviceMarkFiles)\n    {\n        for (final ClusterMarkFile serviceMarkFile : serviceMarkFiles)\n        {\n            printTypeAndActivityTimestamp(out, serviceMarkFile);\n            out.println(serviceMarkFile.decoder());\n            serviceMarkFile.close();\n        }\n    }\n\n    /**\n     * Determine if the given node is a leader.\n     *\n     * @param clusterDir where the cluster is running.\n     * @param out        to print the output to.\n     * @return 0 if the node is an active leader in a closed election, 1 if not,\n     * -1 if the mark file does not exist.\n     */\n    protected int isLeader(final File clusterDir, final PrintStream out)\n    {\n        if (markFileExists(clusterDir))\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                final String aeronDirectoryName = markFile.decoder().aeronDirectory();\n                final MutableLong nodeRoleCounter = new MutableLong(-1);\n                final MutableLong electionStateCounter = new MutableLong(-1);\n\n                try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDirectoryName)))\n                {\n                    final CountersReader countersReader = aeron.countersReader();\n\n                    countersReader.forEach(\n                        (counterId, typeId, keyBuffer, label) ->\n                        {\n                            if (ConsensusModule.Configuration.CLUSTER_NODE_ROLE_TYPE_ID == typeId)\n                            {\n                                nodeRoleCounter.set(countersReader.getCounterValue(counterId));\n                            }\n                            else if (ConsensusModule.Configuration.ELECTION_STATE_TYPE_ID == typeId)\n                            {\n                                electionStateCounter.set(countersReader.getCounterValue(counterId));\n                            }\n                        });\n                }\n\n                if (nodeRoleCounter.get() == Cluster.Role.LEADER.code() &&\n                    electionStateCounter.get() == ElectionState.CLOSED.code())\n                {\n                    return 0;\n                }\n                else\n                {\n                    return 1;\n                }\n            }\n        }\n        else\n        {\n            out.println(ClusterMarkFile.FILENAME + \" does not exist.\");\n            return -1;\n        }\n    }\n\n    /**\n     * Does a {@link ClusterMarkFile} exist in the cluster directory.\n     *\n     * @param clusterDir to check for if a mark file exists.\n     * @return true if the cluster mark file exists.\n     */\n    protected boolean markFileExists(final File clusterDir)\n    {\n        final File markFileDir = resolveClusterMarkFileDir(clusterDir);\n        final File markFile = new File(markFileDir, ClusterMarkFile.FILENAME);\n\n        return markFile.exists();\n    }\n\n    /**\n     * List the current members of a cluster.\n     *\n     * @param clusterMembership to populate.\n     * @param clusterDir        where the cluster is running.\n     * @param timeoutMs         to wait on the query completing.\n     * @return true if successful.\n     */\n    protected boolean listMembers(\n        final ClusterMembership clusterMembership, final File clusterDir, final long timeoutMs)\n    {\n        if (markFileExists(clusterDir) || timeoutMs > 0)\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                return queryClusterMembers(markFile, timeoutMs, clusterMembership);\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Query the membership of a cluster.\n     *\n     * @param markFile          for the cluster component.\n     * @param timeoutMs         to wait for the query.\n     * @param clusterMembership to populate.\n     * @return true if the query was successful.\n     */\n    protected boolean queryClusterMembers(\n        final ClusterMarkFile markFile, final long timeoutMs, final ClusterMembership clusterMembership)\n    {\n        return queryClusterMembers(markFile.loadControlProperties(), timeoutMs, clusterMembership);\n    }\n\n    /**\n     * Query the membership of a cluster.\n     *\n     * @param controlProperties from a {@link ClusterMarkFile}.\n     * @param timeoutMs         to wait for the query.\n     * @param clusterMembership to populate.\n     * @return true if the query was successful.\n     */\n    protected boolean queryClusterMembers(\n        final ClusterNodeControlProperties controlProperties,\n        final long timeoutMs,\n        final ClusterMembership clusterMembership)\n    {\n        final MutableLong id = new MutableLong(NULL_VALUE);\n        final ClusterControlAdapter.Listener listener = new ClusterControlAdapter.Listener()\n        {\n            public void onClusterMembersResponse(\n                final long correlationId,\n                final int leaderMemberId,\n                final String activeMembers)\n            {\n                if (correlationId == id.get())\n                {\n                    clusterMembership.leaderMemberId = leaderMemberId;\n                    clusterMembership.activeMembersStr = activeMembers;\n                    id.set(NULL_VALUE);\n                }\n            }\n\n            public void onClusterMembersExtendedResponse(\n                final long correlationId,\n                final long currentTimeNs,\n                final int leaderMemberId,\n                final int memberId,\n                final List<ClusterMember> activeMembers)\n            {\n                if (correlationId == id.get())\n                {\n                    clusterMembership.currentTimeNs = currentTimeNs;\n                    clusterMembership.leaderMemberId = leaderMemberId;\n                    clusterMembership.memberId = memberId;\n                    clusterMembership.activeMembers = activeMembers;\n                    clusterMembership.activeMembersStr = ClusterMember.encodeAsString(activeMembers);\n                    id.set(NULL_VALUE);\n                }\n            }\n        };\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(controlProperties.aeronDirectoryName));\n            ClusterControlAdapter clusterControlAdapter = new ClusterControlAdapter(\n                aeron.addSubscription(controlProperties.controlChannel, controlProperties.serviceStreamId), listener);\n            ConcurrentPublication publication = aeron.addPublication(\n                controlProperties.controlChannel, controlProperties.consensusModuleStreamId);\n            ConsensusModuleProxy consensusModuleProxy = new ConsensusModuleProxy(publication))\n        {\n            final long deadlineMs = System.currentTimeMillis() + timeoutMs;\n            final long correlationId = aeron.nextCorrelationId();\n            id.set(correlationId);\n\n            while (!clusterControlAdapter.isBound() && publication.availableWindow() <= 0)\n            {\n                if (System.currentTimeMillis() > deadlineMs)\n                {\n                    return false;\n                }\n                Thread.yield();\n            }\n\n            while (!consensusModuleProxy.clusterMembersQuery(correlationId))\n            {\n                if (System.currentTimeMillis() > deadlineMs)\n                {\n                    return false;\n                }\n                Thread.yield();\n            }\n\n            while (NULL_VALUE != id.get())\n            {\n                if (0 == clusterControlAdapter.poll())\n                {\n                    if (System.currentTimeMillis() > deadlineMs)\n                    {\n                        return false;\n                    }\n                    Thread.yield();\n                }\n            }\n\n            return true;\n        }\n    }\n\n    /**\n     * Get the deadline time (MS) for the next cluster backup query.\n     *\n     * @param clusterDir where the cluster component is running.\n     * @return the deadline time (MS) for the next cluster backup query, or {@link Aeron#NULL_VALUE} not available.\n     */\n    protected long nextBackupQueryDeadlineMs(final File clusterDir)\n    {\n        if (markFileExists(clusterDir) || timeoutMs > 0)\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                return nextBackupQueryDeadlineMs(markFile);\n            }\n        }\n\n        return NULL_VALUE;\n    }\n\n    /**\n     * Get the deadline time (MS) for the next cluster backup query.\n     *\n     * @param markFile for the cluster component.\n     * @return the deadline time (MS) for the next cluster backup query, or {@link Aeron#NULL_VALUE} not available.\n     */\n    protected long nextBackupQueryDeadlineMs(final ClusterMarkFile markFile)\n    {\n        final String aeronDirectoryName = markFile.decoder().aeronDirectory();\n        final MutableLong nextQueryMs = new MutableLong(NULL_VALUE);\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDirectoryName)))\n        {\n            aeron.countersReader().forEach(\n                (counterId, typeId, keyBuffer, label) ->\n                {\n                    if (ClusterBackup.QUERY_DEADLINE_TYPE_ID == typeId)\n                    {\n                        nextQueryMs.set(aeron.countersReader().getCounterValue(counterId));\n                    }\n                });\n        }\n\n        return nextQueryMs.get();\n    }\n\n    /**\n     * Set the deadline time (MS) for the next cluster backup query.\n     *\n     * @param clusterDir where the cluster component is running.\n     * @param timeMs     to set for the next deadline.\n     * @return true if successful, otherwise false.\n     */\n    protected boolean nextBackupQueryDeadlineMs(final File clusterDir, final long timeMs)\n    {\n        if (markFileExists(clusterDir) || timeoutMs > 0)\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                return nextBackupQueryDeadlineMs(markFile, timeMs);\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Set the deadline time (MS) for the next cluster backup query.\n     *\n     * @param markFile for the cluster component.\n     * @param timeMs   to set for the next deadline.\n     * @return true if successful, otherwise false.\n     */\n    protected boolean nextBackupQueryDeadlineMs(final ClusterMarkFile markFile, final long timeMs)\n    {\n        final String aeronDirectoryName = markFile.decoder().aeronDirectory();\n        final MutableBoolean result = new MutableBoolean(false);\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDirectoryName)))\n        {\n            final CountersReader countersReader = aeron.countersReader();\n\n            countersReader.forEach(\n                (counterId, typeId, keyBuffer, label) ->\n                {\n                    if (ClusterBackup.QUERY_DEADLINE_TYPE_ID == typeId)\n                    {\n                        final AtomicCounter atomicCounter = new AtomicCounter(\n                            countersReader.valuesBuffer(), counterId, null);\n\n                        atomicCounter.setRelease(timeMs);\n                        result.value = true;\n                    }\n                });\n        }\n\n        return result.value;\n    }\n\n    /**\n     * Invalidate the latest snapshot so recovery will use an earlier one or log if no earlier one exists.\n     *\n     * @param clusterDir where the cluster component is running.\n     * @param out        to print the operation result.\n     * @return SUCCESS if the latest snapshot was invalidated, else FAILURE\n     */\n    protected int invalidateLatestSnapshot(final File clusterDir, final PrintStream out)\n    {\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, false))\n        {\n            final boolean result = recordingLog.invalidateLatestSnapshot();\n            out.println(\" invalidate latest snapshot: \" + result);\n            return result ? SUCCESS : FAILURE;\n        }\n    }\n\n    /**\n     * Print out a summary of the state captured in the latest consensus module snapshot.\n     *\n     * @param clusterDir where the cluster is running.\n     * @param out        where to print the operation result.\n     * @return SUCCESS is the operation was successfully requested, else FAILURE\n     */\n    protected int describeLatestConsensusModuleSnapshot(\n        final File clusterDir,\n        final PrintStream out)\n    {\n        return describeLatestConsensusModuleSnapshot(\n            clusterDir,\n            out,\n            null);\n    }\n\n    /**\n     * Print out a summary of the state captured in the latest consensus module snapshot.\n     *\n     * @param clusterDir                  where the cluster is running.\n     * @param out                         where to print the operation result.\n     * @param postConsensusImageDescriber describing of image after consensus module state\n     * @return SUCCESS is the operation was successfully requested, else FAILURE\n     */\n    protected int describeLatestConsensusModuleSnapshot(\n        final File clusterDir,\n        final PrintStream out,\n        final BiConsumer<Image, Aeron> postConsensusImageDescriber)\n    {\n        final RecordingLog.Entry entry = findLatestValidSnapshot(clusterDir);\n        if (null == entry)\n        {\n            out.println(\"Snapshot not found\");\n            return FAILURE;\n        }\n\n        final ClusterNodeControlProperties properties = loadControlProperties(clusterDir);\n\n        final AeronArchive.Context archiveCtx = new AeronArchive.Context()\n            .clientName(\"cluster-tool\")\n            .controlRequestChannel(\"aeron:ipc\")\n            .controlResponseChannel(\"aeron:ipc\");\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(properties.aeronDirectoryName));\n            AeronArchive archive = AeronArchive.connect(archiveCtx.aeron(aeron)))\n        {\n            final int sessionId = (int)archive.startReplay(\n                entry.recordingId, 0, AeronArchive.NULL_LENGTH, toolChannel, toolStreamId);\n\n            final String replayChannel = ChannelUri.addSessionId(toolChannel, sessionId);\n            try (Subscription subscription = aeron.addSubscription(replayChannel, toolStreamId))\n            {\n                Image image;\n                while ((image = subscription.imageBySessionId(sessionId)) == null)\n                {\n                    archive.checkForErrorResponse();\n                    Thread.yield();\n                }\n\n                final ConsensusModuleSnapshotAdapter adapter = new ConsensusModuleSnapshotAdapter(\n                    image, new ConsensusModuleSnapshotPrinter(out));\n\n                while (true)\n                {\n                    final int fragments = adapter.poll();\n                    if (adapter.isDone())\n                    {\n                        break;\n                    }\n\n                    if (0 == fragments)\n                    {\n                        if (image.isClosed())\n                        {\n                            throw new ClusterException(\"snapshot ended unexpectedly: \" + image);\n                        }\n\n                        archive.checkForErrorResponse();\n                        Thread.yield();\n                    }\n                }\n\n                out.println(\"Consensus Module Snapshot End:\" +\n                    \" memberId=\" + properties.memberId +\n                    \" recordingId=\" + entry.recordingId +\n                    \" length=\" + image.position());\n\n                if (null != postConsensusImageDescriber)\n                {\n                    postConsensusImageDescriber.accept(image, aeron);\n                }\n            }\n        }\n        return SUCCESS;\n    }\n\n    /**\n     * Instruct the cluster to take a snapshot.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return SUCCESS is the operation was successfully requested, else FAILURE\n     */\n    protected int snapshot(final File clusterDir, final PrintStream out)\n    {\n        return toggleClusterState(\n            out,\n            clusterDir,\n            ConsensusModule.State.ACTIVE,\n            ClusterControl.ToggleState.SNAPSHOT,\n            true,\n            TimeUnit.SECONDS.toMillis(30)) ? SUCCESS : FAILURE;\n    }\n\n    /**\n     * Instruct the cluster to suspend operation.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return SUCCESS is the operation was successfully requested, else FAILURE\n     */\n    protected int suspend(final File clusterDir, final PrintStream out)\n    {\n        return toggleClusterState(\n            out,\n            clusterDir,\n            ConsensusModule.State.ACTIVE,\n            ClusterControl.ToggleState.SUSPEND,\n            false,\n            TimeUnit.SECONDS.toMillis(1)) ? SUCCESS : FAILURE;\n    }\n\n    /**\n     * Instruct the cluster to resume operation.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return SUCCESS is the operation was successfully requested, else FAILURE\n     */\n    protected int resume(final File clusterDir, final PrintStream out)\n    {\n        return toggleClusterState(\n            out,\n            clusterDir,\n            ConsensusModule.State.SUSPENDED,\n            ClusterControl.ToggleState.RESUME,\n            true,\n            TimeUnit.SECONDS.toMillis(1)) ? SUCCESS : FAILURE;\n    }\n\n    /**\n     * Instruct the cluster to shut down.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return SUCCESS is the operation was successfully requested, else FAILURE\n     */\n    protected int shutdown(final File clusterDir, final PrintStream out)\n    {\n        return toggleClusterState(\n            out,\n            clusterDir,\n            ConsensusModule.State.ACTIVE,\n            ClusterControl.ToggleState.SHUTDOWN,\n            false,\n            TimeUnit.SECONDS.toMillis(1)) ? SUCCESS : FAILURE;\n    }\n\n    /**\n     * Instruct the cluster to abort.\n     *\n     * @param clusterDir where the consensus module is running.\n     * @param out        to print the result of the operation.\n     * @return SUCCESS is the operation was successfully requested, else FAILURE\n     */\n    protected int abort(final File clusterDir, final PrintStream out)\n    {\n        return toggleClusterState(\n            out,\n            clusterDir,\n            ConsensusModule.State.ACTIVE,\n            ClusterControl.ToggleState.ABORT,\n            false,\n            TimeUnit.SECONDS.toMillis(1)) ? SUCCESS : FAILURE;\n    }\n\n    /**\n     * Finds the latest valid snapshot from the log file.\n     *\n     * @param clusterDir where the cluster node is running.\n     * @return entry or {@code null} if not found.\n     */\n    protected RecordingLog.Entry findLatestValidSnapshot(final File clusterDir)\n    {\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, false))\n        {\n            return recordingLog.getLatestSnapshot(ConsensusModule.Configuration.SERVICE_ID);\n        }\n    }\n\n    /**\n     * Load {@link ClusterNodeControlProperties} from the mark file.\n     *\n     * @param clusterDir where the cluster node is running.\n     * @return control properties.\n     */\n    protected ClusterNodeControlProperties loadControlProperties(final File clusterDir)\n    {\n        final ClusterNodeControlProperties properties;\n        try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n        {\n            properties = markFile.loadControlProperties();\n        }\n        return properties;\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    boolean toggleClusterState(\n        final PrintStream out,\n        final File clusterDir,\n        final ConsensusModule.State expectedState,\n        final ClusterControl.ToggleState toggleState,\n        final boolean waitForToggleToComplete,\n        final long defaultTimeoutMs)\n    {\n        return toggleState(\n            out,\n            clusterDir,\n            true,\n            expectedState,\n            toggleState,\n            ToggleApplication.CLUSTER_CONTROL,\n            waitForToggleToComplete,\n            defaultTimeoutMs);\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    <T extends Enum<T>> boolean toggleState(\n        final PrintStream out,\n        final File clusterDir,\n        final boolean isLeaderRequired,\n        final ConsensusModule.State expectedState,\n        final T targetState,\n        final ToggleApplication<T> toggleApplication,\n        final boolean waitForToggleToComplete,\n        final long defaultTimeoutMs)\n    {\n        if (!markFileExists(clusterDir) && timeoutMs <= 0)\n        {\n            out.println(ClusterMarkFile.FILENAME + \" does not exist.\");\n            return false;\n        }\n\n        final int clusterId;\n        final ClusterNodeControlProperties clusterNodeControlProperties;\n        try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n        {\n            clusterId = markFile.clusterId();\n            clusterNodeControlProperties = markFile.loadControlProperties();\n        }\n\n        final ClusterMembership clusterMembership = new ClusterMembership();\n        final long queryTimeoutMs = Math.max(TimeUnit.SECONDS.toMillis(1), timeoutMs);\n\n        if (!queryClusterMembers(clusterNodeControlProperties, queryTimeoutMs, clusterMembership))\n        {\n            out.println(\"Timed out querying cluster.\");\n            return false;\n        }\n\n        final String prefix = \"Member [\" + clusterMembership.memberId + \"]: \";\n\n        if (isLeaderRequired && clusterMembership.leaderMemberId != clusterMembership.memberId)\n        {\n            out.println(prefix + \"Current node is not the leader (leaderMemberId = \" +\n                clusterMembership.leaderMemberId + \"), unable to \" + targetState);\n            return false;\n        }\n\n        final File cncFile = new File(clusterNodeControlProperties.aeronDirectoryName, CncFileDescriptor.CNC_FILE);\n        if (!cncFile.exists())\n        {\n            out.println(prefix + \"Unable to locate media driver. C`n`C file [\" + cncFile.getAbsolutePath() +\n                \"] does not exist.\");\n            return false;\n        }\n\n        final CountersReader countersReader = ClusterControl.mapCounters(cncFile);\n        try\n        {\n            final ConsensusModule.State moduleState = ConsensusModule.State.find(countersReader, clusterId);\n            if (null == moduleState)\n            {\n                out.println(prefix + \"Unable to resolve state of consensus module.\");\n                return false;\n            }\n\n            if (expectedState != moduleState)\n            {\n                out.println(prefix + \"Unable to \" + targetState + \" as the state of the consensus module is \" +\n                    moduleState + \", but needs to be \" + expectedState);\n                return false;\n            }\n\n            final AtomicCounter controlToggle = toggleApplication.find(countersReader, clusterId);\n            if (null == controlToggle)\n            {\n                out.println(prefix + \"Failed to find control toggle\");\n                return false;\n            }\n\n            if (!toggleApplication.apply(controlToggle, targetState))\n            {\n                out.println(prefix + \"Failed to apply \" + targetState + \", current toggle value = \" +\n                    ClusterControl.ToggleState.get(controlToggle));\n                return false;\n            }\n\n            if (waitForToggleToComplete)\n            {\n                final long toggleTimeoutMs = Math.max(defaultTimeoutMs, timeoutMs);\n                final long deadlineMs = System.currentTimeMillis() + toggleTimeoutMs;\n                T currentState = null;\n\n                do\n                {\n                    Thread.yield();\n                    if (System.currentTimeMillis() > deadlineMs)\n                    {\n                        break;\n                    }\n\n                    currentState = toggleApplication.get(controlToggle);\n                }\n                while (!toggleApplication.isNeutral(currentState));\n\n                if (!toggleApplication.isNeutral(currentState))\n                {\n                    out.println(prefix + \"Timed out after \" + toggleTimeoutMs + \"ms waiting for \" +\n                        targetState + \" to complete.\");\n                }\n            }\n\n            out.println(prefix + targetState + \" applied successfully\");\n\n            return true;\n\n        }\n        finally\n        {\n            IoUtil.unmap(countersReader.valuesBuffer().byteBuffer());\n        }\n    }\n\n    /**\n     * Print out the descriptors in the {@link ClusterMarkFile}s.\n     *\n     * @param clusterDir where the cluster is running.\n     * @param out        to print the output to.\n     * @return exit status\n     */\n    // Used by Premium Tooling.\n    @SuppressWarnings(\"UnusedReturnValue\")\n    protected int describeClusterMarkFile(final File clusterDir, final PrintStream out)\n    {\n        final File clusterServicesDir;\n        if (markFileExists(clusterDir) || timeoutMs > 0)\n        {\n            try (ClusterMarkFile markFile = openMarkFile(clusterDir))\n            {\n                final MarkFileHeaderDecoder decoder = markFile.decoder();\n\n                printTypeAndActivityTimestamp(out, markFile);\n                out.println(decoder);\n\n                clusterServicesDir = resolveClusterServicesDir(clusterDir, decoder);\n            }\n        }\n        else\n        {\n            out.println(ClusterMarkFile.FILENAME + \" does not exist.\");\n            return FAILURE;\n        }\n\n        final ClusterMarkFile[] serviceMarkFiles = openServiceMarkFiles(clusterServicesDir, out::println);\n        describe(out, serviceMarkFiles);\n\n        return SUCCESS;\n    }\n\n    File resolveClusterServicesDir(final File clusterDir, final MarkFileHeaderDecoder decoder)\n    {\n        final File clusterServicesDir;\n\n        if (MARK_FILE_VERSION_WITH_CLUSTER_SERVICES_DIR <= decoder.sbeSchemaVersion())\n        {\n            decoder.sbeRewind();\n            decoder.skipAeronDirectory();\n            decoder.skipControlChannel();\n            decoder.skipIngressChannel();\n            decoder.skipServiceName();\n            decoder.skipAuthenticator();\n\n            final String servicesClusterDir = decoder.servicesClusterDir();\n            clusterServicesDir = isEmpty(servicesClusterDir) ? clusterDir : new File(servicesClusterDir);\n        }\n        else\n        {\n            clusterServicesDir = clusterDir;\n        }\n\n        return clusterServicesDir;\n    }\n\n    ClusterMarkFile openMarkFile(final File clusterDir)\n    {\n        final File markFileDir = resolveClusterMarkFileDir(clusterDir);\n        return new ClusterMarkFile(\n            markFileDir, ClusterMarkFile.FILENAME, System::currentTimeMillis, timeoutMs, null);\n    }\n\n    private ClusterMarkFile[] openServiceMarkFiles(final File clusterDir, final Consumer<String> logger)\n    {\n        File[] clusterMarkFileNames = clusterDir.listFiles((dir, name) ->\n            name.startsWith(ClusterMarkFile.SERVICE_FILENAME_PREFIX) &&\n            (name.endsWith(ClusterMarkFile.FILE_EXTENSION) ||\n            name.endsWith(ClusterMarkFile.LINK_FILE_EXTENSION)));\n\n        if (null == clusterMarkFileNames)\n        {\n            clusterMarkFileNames = new File[0];\n        }\n\n        final ArrayList<File> resolvedMarkFileNames = new ArrayList<>();\n        resolveServiceMarkFileNames(clusterMarkFileNames, resolvedMarkFileNames);\n\n        final ClusterMarkFile[] clusterMarkFiles = new ClusterMarkFile[clusterMarkFileNames.length];\n\n        for (int i = 0, n = resolvedMarkFileNames.size(); i < n; i++)\n        {\n            final File resolvedMarkFile = resolvedMarkFileNames.get(i);\n            clusterMarkFiles[i] = new ClusterMarkFile(\n                resolvedMarkFile.getParentFile(),\n                resolvedMarkFile.getName(),\n                System::currentTimeMillis,\n                timeoutMs,\n                logger);\n        }\n\n        return clusterMarkFiles;\n    }\n\n    private void resolveServiceMarkFileNames(final File[] clusterMarkFiles, final ArrayList<File> resolvedFiles)\n    {\n        final HashSet<String> resolvedServices = new HashSet<>();\n\n        for (final File clusterMarkFile : clusterMarkFiles)\n        {\n            final String filename = clusterMarkFile.getName();\n            if (filename.endsWith(ClusterMarkFile.LINK_FILE_EXTENSION))\n            {\n                final String name = filename.substring(\n                    0, filename.length() - ClusterMarkFile.LINK_FILE_EXTENSION.length());\n\n                final File markFileDir = resolveDirectoryFromLinkFile(clusterMarkFile);\n                resolvedFiles.add(new File(markFileDir, name + ClusterMarkFile.FILE_EXTENSION));\n                resolvedServices.add(name);\n            }\n        }\n\n        for (final File clusterMarkFile : clusterMarkFiles)\n        {\n            final String filename = clusterMarkFile.getName();\n            if (filename.endsWith(ClusterMarkFile.FILE_EXTENSION))\n            {\n                final String name = filename.substring(\n                    0, filename.length() - ClusterMarkFile.FILE_EXTENSION.length());\n\n                if (!resolvedServices.contains(name))\n                {\n                    resolvedFiles.add(clusterMarkFile);\n                    resolvedServices.add(name);\n                }\n            }\n        }\n    }\n\n    void printTypeAndActivityTimestamp(final PrintStream out, final ClusterMarkFile markFile)\n    {\n        printTypeAndActivityTimestamp(\n            out,\n            markFile.decoder().componentType().toString(),\n            markFile.decoder().startTimestamp(),\n            markFile.activityTimestampVolatile());\n    }\n\n    void printTypeAndActivityTimestamp(\n        final PrintStream out,\n        final String clusterComponentType,\n        final long startTimestampMs,\n        final long activityTimestampMs)\n    {\n        out.print(\"Type: \" + clusterComponentType + \" \");\n        out.format(\n            \"%1$tH:%1$tM:%1$tS (start: %2$tF %2$tH:%2$tM:%2$tS, activity: %3$tF %3$tH:%3$tM:%3$tS)%n\",\n            new Date(),\n            new Date(startTimestampMs),\n            new Date(activityTimestampMs));\n    }\n\n    void printErrors(final PrintStream out, final Supplier<AtomicBuffer> errorBuffer, final String name)\n    {\n        out.println(name + \" component error log:\");\n        CommonContext.printErrorLog(errorBuffer.get(), out);\n    }\n\n    void printDriverErrors(final PrintStream out, final String aeronDirectory)\n    {\n        out.println(\"Aeron driver error log (directory: \" + aeronDirectory + \"):\");\n        final File cncFile = new File(aeronDirectory, CncFileDescriptor.CNC_FILE);\n\n        MappedByteBuffer cncByteBuffer = null;\n        try\n        {\n            cncByteBuffer = IoUtil.mapExistingFile(cncFile, FileChannel.MapMode.READ_ONLY, \"cnc\");\n            final DirectBuffer cncMetaDataBuffer = CncFileDescriptor.createMetaDataBuffer(cncByteBuffer);\n            final int cncVersion = cncMetaDataBuffer.getInt(CncFileDescriptor.cncVersionOffset(0));\n\n            CncFileDescriptor.checkVersion(cncVersion);\n            CommonContext.printErrorLog(CncFileDescriptor.createErrorLogBuffer(cncByteBuffer, cncMetaDataBuffer), out);\n        }\n        finally\n        {\n            if (null != cncByteBuffer)\n            {\n                IoUtil.unmap(cncByteBuffer);\n            }\n        }\n    }\n\n    File resolveClusterMarkFileDir(final File dir)\n    {\n        final File linkFile = new File(dir, ClusterMarkFile.LINK_FILENAME);\n        return linkFile.exists() ? resolveDirectoryFromLinkFile(linkFile) : dir;\n    }\n\n    File resolveDirectoryFromLinkFile(final File linkFile)\n    {\n        final File markFileDir;\n\n        try\n        {\n            final byte[] bytes = Files.readAllBytes(linkFile.toPath());\n            final String markFileDirPath = new String(bytes, US_ASCII).trim();\n            markFileDir = new File(markFileDirPath);\n        }\n        catch (final IOException ex)\n        {\n            throw new RuntimeException(\"failed to read link file=\" + linkFile, ex);\n        }\n\n        return markFileDir;\n    }\n\n    /**\n     * Channel to use.\n     *\n     * @return channel to use.\n     */\n    protected String toolChannel()\n    {\n        return toolChannel;\n    }\n\n    /**\n     * Stream id to use.\n     *\n     * @return stream id to use.\n     */\n    protected int toolStreamId()\n    {\n        return toolStreamId;\n    }\n\n    /**\n     * Timeout in milliseconds to use.\n     *\n     * @return timeout in milliseconds to use.\n     */\n    protected long timeoutMs()\n    {\n        return timeoutMs;\n    }\n\n    private void printErrors(final PrintStream out, final ClusterMarkFile markFile)\n    {\n        printErrors(out, markFile::errorBuffer, \"Cluster\");\n    }\n\n    private boolean isRecordingLogSorted(final List<RecordingLog.Entry> entries)\n    {\n        for (int i = entries.size() - 1; i >= 0; i--)\n        {\n            if (entries.get(i).entryIndex != i)\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private void updateRecordingLog(final File clusterDir, final List<RecordingLog.Entry> entries)\n    {\n        final Path recordingLog = clusterDir.toPath().resolve(RecordingLog.RECORDING_LOG_FILE_NAME);\n        try\n        {\n            if (entries.isEmpty())\n            {\n                Files.delete(recordingLog);\n            }\n            else\n            {\n                final Path newRecordingLog = clusterDir.toPath().resolve(RecordingLog.RECORDING_LOG_FILE_NAME + \".tmp\");\n                Files.deleteIfExists(newRecordingLog);\n                final ByteBuffer byteBuffer =\n                    ByteBuffer.allocateDirect(RecordingLog.MAX_ENTRY_LENGTH).order(LITTLE_ENDIAN);\n                final UnsafeBuffer buffer = new UnsafeBuffer(byteBuffer);\n                try (FileChannel fileChannel = FileChannel.open(newRecordingLog, CREATE_NEW, WRITE))\n                {\n                    long position = 0;\n                    for (final RecordingLog.Entry e : entries)\n                    {\n                        RecordingLog.writeEntryToBuffer(e, buffer);\n                        byteBuffer.limit(e.length()).position(0);\n\n                        if (e.length() != fileChannel.write(byteBuffer, position))\n                        {\n                            throw new ClusterException(\"failed to write recording\");\n                        }\n                        position += e.length();\n                    }\n                }\n                finally\n                {\n                    BufferUtil.free(byteBuffer);\n                }\n\n                Files.delete(recordingLog);\n                Files.move(newRecordingLog, recordingLog);\n            }\n        }\n        catch (final IOException ex)\n        {\n            throw new UncheckedIOException(ex);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusteredArchive.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.CommonContext;\nimport io.aeron.archive.Archive;\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Clustered archive which is an aggregate of a {@link ConsensusModule} and an {@link Archive}.\n */\npublic class ClusteredArchive implements AutoCloseable\n{\n    private final Archive archive;\n    private final ConsensusModule consensusModule;\n\n    ClusteredArchive(final Archive archive, final ConsensusModule consensusModule)\n    {\n        this.archive = archive;\n        this.consensusModule = consensusModule;\n    }\n\n    /**\n     * Launch the clustered archive aggregate and await a shutdown signal.\n     *\n     * @param args command line argument which is a list for properties files as URLs or filenames.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            ClusteredArchive ignored = launch(\n                CommonContext.getAeronDirectoryName(),\n                new Archive.Context(),\n                new ConsensusModule.Context().terminationHook(barrier::signalAll)))\n        {\n            barrier.await();\n            System.out.println(\"Shutdown ClusteredArchive...\");\n        }\n    }\n\n    /**\n     * Launch a new {@link ClusteredArchive} with default contexts.\n     *\n     * @return a new {@link ClusteredArchive} with default contexts.\n     */\n    public static ClusteredArchive launch()\n    {\n        return launch(CommonContext.getAeronDirectoryName(), new Archive.Context(), new ConsensusModule.Context());\n    }\n\n    /**\n     * Launch a new {@link ClusteredArchive} with provided contexts.\n     *\n     * @param aeronDirectoryName for connecting to the {@link MediaDriver}.\n     * @param archiveCtx         for configuring the {@link Archive}.\n     * @param consensusModuleCtx for the configuration of the {@link ConsensusModule}.\n     * @return a new {@link ClusteredArchive} with the provided contexts.\n     */\n    public static ClusteredArchive launch(\n        final String aeronDirectoryName,\n        final Archive.Context archiveCtx,\n        final ConsensusModule.Context consensusModuleCtx)\n    {\n        Archive archive = null;\n        ConsensusModule consensusModule = null;\n\n        try\n        {\n            archive = Archive.launch(archiveCtx.aeronDirectoryName(aeronDirectoryName));\n            consensusModule = ConsensusModule.launch(consensusModuleCtx.aeronDirectoryName(aeronDirectoryName));\n\n            return new ClusteredArchive(archive, consensusModule);\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietCloseAll(consensusModule, archive);\n            throw ex;\n        }\n    }\n\n    /**\n     * Get the {@link Archive} used in the aggregate.\n     *\n     * @return the {@link Archive} used in the aggregate.\n     */\n    public Archive archive()\n    {\n        return archive;\n    }\n\n    /**\n     * Get the {@link ConsensusModule} used in the aggregate.\n     *\n     * @return the {@link ConsensusModule} used in the aggregate.\n     */\n    public ConsensusModule consensusModule()\n    {\n        return consensusModule;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.closeAll(consensusModule, archive);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ClusteredMediaDriver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.Archive;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Clustered media driver which is an aggregate of a {@link MediaDriver}, {@link Archive},\n * and a {@link ConsensusModule}.\n */\npublic class ClusteredMediaDriver implements AutoCloseable\n{\n    private final MediaDriver driver;\n    private final Archive archive;\n    private final ConsensusModule consensusModule;\n\n    ClusteredMediaDriver(final MediaDriver driver, final Archive archive, final ConsensusModule consensusModule)\n    {\n        this.driver = driver;\n        this.archive = archive;\n        this.consensusModule = consensusModule;\n    }\n\n    /**\n     * Launch the clustered media driver aggregate and await a shutdown signal.\n     *\n     * @param args command line argument which is a list for properties files as URLs or filenames.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            ClusteredMediaDriver ignore = launch(\n                new MediaDriver.Context().terminationHook(barrier::signalAll),\n                new Archive.Context(),\n                new ConsensusModule.Context().terminationHook(barrier::signalAll)))\n        {\n            barrier.await();\n            System.out.println(\"Shutdown ClusteredMediaDriver...\");\n        }\n    }\n\n    /**\n     * Launch a new {@link ClusteredMediaDriver} with default contexts.\n     *\n     * @return a new {@link ClusteredMediaDriver} with default contexts.\n     */\n    public static ClusteredMediaDriver launch()\n    {\n        return launch(new MediaDriver.Context(), new Archive.Context(), new ConsensusModule.Context());\n    }\n\n    /**\n     * Launch a new {@link ClusteredMediaDriver} with provided contexts.\n     *\n     * @param driverCtx          for configuring the {@link MediaDriver}.\n     * @param archiveCtx         for configuring the {@link Archive}.\n     * @param consensusModuleCtx for the configuration of the {@link ConsensusModule}.\n     * @return a new {@link ClusteredMediaDriver} with the provided contexts.\n     */\n    public static ClusteredMediaDriver launch(\n        final MediaDriver.Context driverCtx,\n        final Archive.Context archiveCtx,\n        final ConsensusModule.Context consensusModuleCtx)\n    {\n        MediaDriver driver = null;\n        Archive archive = null;\n        ConsensusModule consensusModule = null;\n\n        try\n        {\n            driver = MediaDriver.launch(driverCtx);\n\n            final int errorCounterId = SystemCounterDescriptor.ERRORS.id();\n            final AtomicCounter errorCounter = null != archiveCtx.errorCounter() ?\n                archiveCtx.errorCounter() : new AtomicCounter(driverCtx.countersValuesBuffer(), errorCounterId);\n            final ErrorHandler errorHandler = null != archiveCtx.errorHandler() ?\n                archiveCtx.errorHandler() : driverCtx.errorHandler();\n\n            archive = Archive.launch(archiveCtx\n                .mediaDriverAgentInvoker(driver.sharedAgentInvoker())\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .errorHandler(errorHandler)\n                .errorCounter(errorCounter));\n\n            consensusModule = ConsensusModule.launch(consensusModuleCtx\n                .aeronDirectoryName(driverCtx.aeronDirectoryName()));\n\n            return new ClusteredMediaDriver(driver, archive, consensusModule);\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietCloseAll(consensusModule, archive, driver);\n            throw ex;\n        }\n    }\n\n    /**\n     * Get the {@link MediaDriver} used in the aggregate.\n     *\n     * @return the {@link MediaDriver} used in the aggregate.\n     */\n    public MediaDriver mediaDriver()\n    {\n        return driver;\n    }\n\n    /**\n     * Get the {@link Archive} used in the aggregate.\n     *\n     * @return the {@link Archive} used in the aggregate.\n     */\n    public Archive archive()\n    {\n        return archive;\n    }\n\n    /**\n     * Get the {@link ConsensusModule} used in the aggregate.\n     *\n     * @return the {@link ConsensusModule} used in the aggregate.\n     */\n    public ConsensusModule consensusModule()\n    {\n        return consensusModule;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.closeAll(consensusModule, archive, driver);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.ArrayUtil;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nclass ConsensusAdapter implements FragmentHandler, AutoCloseable\n{\n    static final int FRAGMENT_LIMIT = 10;\n\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final CanvassPositionDecoder canvassPositionDecoder = new CanvassPositionDecoder();\n    private final RequestVoteDecoder requestVoteDecoder = new RequestVoteDecoder();\n    private final VoteDecoder voteDecoder = new VoteDecoder();\n    private final NewLeadershipTermDecoder newLeadershipTermDecoder = new NewLeadershipTermDecoder();\n    private final AppendPositionDecoder appendPositionDecoder = new AppendPositionDecoder();\n    private final CommitPositionDecoder commitPositionDecoder = new CommitPositionDecoder();\n    private final CatchupPositionDecoder catchupPositionDecoder = new CatchupPositionDecoder();\n    private final StopCatchupDecoder stopCatchupDecoder = new StopCatchupDecoder();\n\n    private final TerminationPositionDecoder terminationPositionDecoder = new TerminationPositionDecoder();\n    private final TerminationAckDecoder terminationAckDecoder = new TerminationAckDecoder();\n    private final BackupQueryDecoder backupQueryDecoder = new BackupQueryDecoder();\n    private final ChallengeResponseDecoder challengeResponseDecoder = new ChallengeResponseDecoder();\n    private final HeartbeatRequestDecoder heartbeatRequestDecoder = new HeartbeatRequestDecoder();\n    private final StandbySnapshotDecoder standbySnapshotDecoder = new StandbySnapshotDecoder();\n\n    private final FragmentAssembler fragmentAssembler = new FragmentAssembler(this);\n    private final Subscription subscription;\n    private final ConsensusModuleAgent consensusModuleAgent;\n\n    ConsensusAdapter(final Subscription subscription, final ConsensusModuleAgent consensusModuleAgent)\n    {\n        this.subscription = subscription;\n        this.consensusModuleAgent = consensusModuleAgent;\n    }\n\n    public void close()\n    {\n        CloseHelper.close(subscription);\n    }\n\n    public int poll()\n    {\n        return subscription.poll(fragmentAssembler, FRAGMENT_LIMIT);\n    }\n\n    public int poll(final int limit)\n    {\n        return subscription.poll(fragmentAssembler, limit);\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ClusterException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (messageHeaderDecoder.templateId())\n        {\n            case CanvassPositionDecoder.TEMPLATE_ID:\n                canvassPositionDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onCanvassPosition(\n                    canvassPositionDecoder.logLeadershipTermId(),\n                    canvassPositionDecoder.logPosition(),\n                    canvassPositionDecoder.leadershipTermId(),\n                    canvassPositionDecoder.followerMemberId(),\n                    canvassPositionDecoder.protocolVersion());\n                break;\n\n            case RequestVoteDecoder.TEMPLATE_ID:\n                requestVoteDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onRequestVote(\n                    requestVoteDecoder.logLeadershipTermId(),\n                    requestVoteDecoder.logPosition(),\n                    requestVoteDecoder.candidateTermId(),\n                    requestVoteDecoder.candidateMemberId(),\n                    requestVoteDecoder.protocolVersion());\n                break;\n\n            case VoteDecoder.TEMPLATE_ID:\n                voteDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onVote(\n                    voteDecoder.logLeadershipTermId(),\n                    voteDecoder.logPosition(),\n                    voteDecoder.candidateTermId(),\n                    voteDecoder.candidateMemberId(),\n                    voteDecoder.followerMemberId(),\n                    voteDecoder.vote() == BooleanType.TRUE);\n                break;\n\n            case NewLeadershipTermDecoder.TEMPLATE_ID:\n                newLeadershipTermDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long rawCommitPosition = newLeadershipTermDecoder.commitPosition();\n                final long commitPosition =\n                    NewLeadershipTermDecoder.commitPositionNullValue() == rawCommitPosition ?\n                        AeronArchive.NULL_POSITION : rawCommitPosition;\n\n                consensusModuleAgent.onNewLeadershipTerm(\n                    newLeadershipTermDecoder.logLeadershipTermId(),\n                    newLeadershipTermDecoder.nextLeadershipTermId(),\n                    newLeadershipTermDecoder.nextTermBaseLogPosition(),\n                    newLeadershipTermDecoder.nextLogPosition(),\n                    newLeadershipTermDecoder.leadershipTermId(),\n                    newLeadershipTermDecoder.termBaseLogPosition(),\n                    newLeadershipTermDecoder.logPosition(),\n                    commitPosition,\n                    newLeadershipTermDecoder.leaderRecordingId(),\n                    newLeadershipTermDecoder.timestamp(),\n                    newLeadershipTermDecoder.leaderMemberId(),\n                    newLeadershipTermDecoder.logSessionId(),\n                    newLeadershipTermDecoder.appVersion(),\n                    newLeadershipTermDecoder.isStartup() == BooleanType.TRUE);\n                break;\n\n            case AppendPositionDecoder.TEMPLATE_ID:\n                appendPositionDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final short flagsDecodedValue = appendPositionDecoder.flags();\n                final short flags = AppendPositionDecoder.flagsNullValue() == flagsDecodedValue ?\n                    ConsensusModuleAgent.APPEND_POSITION_FLAG_NONE : flagsDecodedValue;\n\n                consensusModuleAgent.onAppendPosition(\n                    appendPositionDecoder.leadershipTermId(),\n                    appendPositionDecoder.logPosition(),\n                    appendPositionDecoder.followerMemberId(),\n                    flags);\n\n                break;\n\n            case CommitPositionDecoder.TEMPLATE_ID:\n                commitPositionDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onCommitPosition(\n                    commitPositionDecoder.leadershipTermId(),\n                    commitPositionDecoder.logPosition(),\n                    commitPositionDecoder.leaderMemberId());\n                break;\n\n            case CatchupPositionDecoder.TEMPLATE_ID:\n                catchupPositionDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onCatchupPosition(\n                    catchupPositionDecoder.leadershipTermId(),\n                    catchupPositionDecoder.logPosition(),\n                    catchupPositionDecoder.followerMemberId(),\n                    catchupPositionDecoder.catchupEndpoint());\n                break;\n\n            case StopCatchupDecoder.TEMPLATE_ID:\n                stopCatchupDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onStopCatchup(\n                    stopCatchupDecoder.leadershipTermId(),\n                    stopCatchupDecoder.followerMemberId());\n                break;\n\n            case TerminationPositionDecoder.TEMPLATE_ID:\n                terminationPositionDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onTerminationPosition(\n                    terminationPositionDecoder.leadershipTermId(),\n                    terminationPositionDecoder.logPosition());\n                break;\n\n            case TerminationAckDecoder.TEMPLATE_ID:\n                terminationAckDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onTerminationAck(\n                    terminationAckDecoder.leadershipTermId(),\n                    terminationAckDecoder.logPosition(),\n                    terminationAckDecoder.memberId());\n                break;\n\n            case BackupQueryDecoder.TEMPLATE_ID:\n            {\n                backupQueryDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long correlationId = backupQueryDecoder.correlationId();\n                final int responseStreamId = backupQueryDecoder.responseStreamId();\n                final int version = backupQueryDecoder.version();\n                final long logPosition =\n                    backupQueryDecoder.logPosition() == BackupQueryDecoder.logPositionNullValue() ?\n                        Aeron.NULL_VALUE :\n                        backupQueryDecoder.logPosition();\n\n                final String responseChannel = backupQueryDecoder.responseChannel();\n                final int credentialsLength = backupQueryDecoder.encodedCredentialsLength();\n                final byte[] credentials;\n                if (credentialsLength > 0)\n                {\n                    credentials = new byte[credentialsLength];\n                    backupQueryDecoder.getEncodedCredentials(credentials, 0, credentials.length);\n                }\n                else\n                {\n                    credentials = ArrayUtil.EMPTY_BYTE_ARRAY;\n                }\n\n                consensusModuleAgent.onBackupQuery(\n                    correlationId,\n                    responseStreamId,\n                    version,\n                    logPosition,\n                    responseChannel,\n                    credentials,\n                    header);\n                break;\n            }\n\n            case ChallengeResponseDecoder.TEMPLATE_ID:\n            {\n                challengeResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final byte[] credentials = new byte[challengeResponseDecoder.encodedCredentialsLength()];\n                challengeResponseDecoder.getEncodedCredentials(credentials, 0, credentials.length);\n\n                consensusModuleAgent.onConsensusChallengeResponse(\n                    challengeResponseDecoder.correlationId(),\n                    challengeResponseDecoder.clusterSessionId(),\n                    credentials);\n                break;\n            }\n\n            case HeartbeatRequestDecoder.TEMPLATE_ID:\n            {\n                heartbeatRequestDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final String responseChannel = heartbeatRequestDecoder.responseChannel();\n                final int credentialsLength = heartbeatRequestDecoder.encodedCredentialsLength();\n                final byte[] credentials;\n                if (credentialsLength > 0)\n                {\n                    credentials = new byte[credentialsLength];\n                    heartbeatRequestDecoder.getEncodedCredentials(credentials, 0, credentials.length);\n                }\n                else\n                {\n                    heartbeatRequestDecoder.skipEncodedCredentials();\n                    credentials = ArrayUtil.EMPTY_BYTE_ARRAY;\n                }\n\n                consensusModuleAgent.onHeartbeatRequest(\n                    heartbeatRequestDecoder.correlationId(),\n                    heartbeatRequestDecoder.responseStreamId(),\n                    responseChannel,\n                    credentials,\n                    header);\n\n                break;\n            }\n\n            case StandbySnapshotDecoder.TEMPLATE_ID:\n            {\n                standbySnapshotDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long correlationId = standbySnapshotDecoder.correlationId();\n                final int version = standbySnapshotDecoder.version();\n                final int responseStreamId = standbySnapshotDecoder.responseStreamId();\n                final List<StandbySnapshotEntry> standbySnapshotEntries = new ArrayList<>();\n\n                for (final StandbySnapshotDecoder.SnapshotsDecoder standbySnapshot : standbySnapshotDecoder.snapshots())\n                {\n                    standbySnapshotEntries.add(new StandbySnapshotEntry(\n                        standbySnapshot.recordingId(),\n                        standbySnapshot.leadershipTermId(),\n                        standbySnapshot.termBaseLogPosition(),\n                        standbySnapshot.logPosition(),\n                        standbySnapshot.timestamp(),\n                        standbySnapshot.serviceId(),\n                        standbySnapshot.archiveEndpoint()));\n                }\n\n                final String responseChannel = standbySnapshotDecoder.responseChannel();\n                final byte[] encodedCredentials;\n                if (0 == standbySnapshotDecoder.encodedCredentialsLength())\n                {\n                    standbySnapshotDecoder.skipEncodedCredentials();\n                    encodedCredentials = ArrayUtil.EMPTY_BYTE_ARRAY;\n                }\n                else\n                {\n                    encodedCredentials = new byte[standbySnapshotDecoder.encodedCredentialsLength()];\n                    standbySnapshotDecoder.getEncodedCredentials(encodedCredentials, 0, encodedCredentials.length);\n                }\n\n                consensusModuleAgent.onStandbySnapshot(\n                    correlationId,\n                    version,\n                    standbySnapshotEntries,\n                    responseStreamId,\n                    responseChannel,\n                    encodedCredentials,\n                    header);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusControlState.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Subscription;\n\n/**\n * The state needed to allow control of the consensus module.\n * <p>\n * This is a record object being passed to external entities.\n *\n * @see ConsensusModuleExtension\n */\npublic final class ConsensusControlState\n{\n    private final ExclusivePublication logPublication;\n    private final Subscription leaderLogSubscription;\n    private final long logRecordingId;\n    private final long leadershipTermId;\n\n    /**\n     * Record constructor.\n     *\n     * @param logPublication        publication or null.\n     * @param leaderLogSubscription null if a follower, if a leader will have an image that has joined at the log\n     *                              position.\n     * @param logRecordingId        log recording id.\n     * @param leadershipTermId      leadership term id.\n     */\n    ConsensusControlState(\n        final ExclusivePublication logPublication,\n        final Subscription leaderLogSubscription,\n        final long logRecordingId,\n        final long leadershipTermId)\n    {\n        this.logPublication = logPublication;\n        this.leaderLogSubscription = leaderLogSubscription;\n        this.logRecordingId = logRecordingId;\n        this.leadershipTermId = leadershipTermId;\n    }\n\n    /**\n     * True iff we are the leader (and have the log publication).\n     *\n     * @return true iff we are the leader (and have the log publication).\n     */\n    public boolean isLeader()\n    {\n        return null != logPublication;\n    }\n\n    /**\n     * Log publication or null if follower.\n     *\n     * @return log publication or null if follower.\n     */\n    public ExclusivePublication logPublication()\n    {\n        return logPublication;\n    }\n\n    /**\n     * Log recording id.\n     *\n     * @return log recording id.\n     */\n    public long logRecordingId()\n    {\n        return logRecordingId;\n    }\n\n    /**\n     * Leadership term id.\n     *\n     * @return leadership term id.\n     */\n    public long leadershipTermId()\n    {\n        return leadershipTermId;\n    }\n\n    /**\n     * A subscription to the log, joined at the log position of the election for a leader node, or null for a\n     * follower.\n     *\n     * @return a subscription to the log, joined at the log position of the election for a leader node, or null for a\n     * follower.\n     */\n    public Subscription leaderLogSubscription()\n    {\n        return leaderLogSubscription;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.Counter;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.AdminRequestDecoder;\nimport io.aeron.cluster.codecs.AdminRequestType;\nimport io.aeron.cluster.codecs.BackupQueryDecoder;\nimport io.aeron.cluster.codecs.HeartbeatRequestDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.StandbySnapshotDecoder;\nimport io.aeron.cluster.codecs.mark.ClusterComponentType;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.cluster.service.ClusterCounters;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.cluster.service.SnapshotDurationTracker;\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\nimport io.aeron.driver.DutyCycleTracker;\nimport io.aeron.driver.NameResolver;\nimport io.aeron.driver.status.DutyCycleStallTracker;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.AuthenticatorSupplier;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.security.AuthorisationServiceSupplier;\nimport io.aeron.security.DefaultAuthenticatorSupplier;\nimport io.aeron.version.Versioned;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.MarkFile;\nimport org.agrona.SemanticVersion;\nimport org.agrona.Strings;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Random;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport java.util.function.LongConsumer;\nimport java.util.function.Supplier;\n\nimport static io.aeron.AeronCounters.CLUSTER_ELECTION_COUNT_TYPE_ID;\nimport static io.aeron.AeronCounters.CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID;\nimport static io.aeron.AeronCounters.CLUSTER_STANDBY_SNAPSHOT_COUNTER_TYPE_ID;\nimport static io.aeron.AeronCounters.NODE_CONTROL_TOGGLE_TYPE_ID;\nimport static io.aeron.AeronCounters.validateCounterTypeId;\nimport static io.aeron.ChannelUri.addAliasIfAbsent;\nimport static io.aeron.ChannelUri.parse;\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.CommonContext.INITIAL_TERM_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.TERM_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.TERM_OFFSET_PARAM_NAME;\nimport static io.aeron.CommonContext.UDP_CHANNEL;\nimport static io.aeron.CommonContext.driverFilePageSize;\nimport static io.aeron.CommonContext.fallbackLogger;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CLUSTER_CLOCK_PROP_NAME;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CLUSTER_NODE_ROLE_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.COMMIT_POSITION_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CONSENSUS_MODULE_STATE_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CONTROL_TOGGLE_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.ELECTION_STATE_TYPE_ID;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.MAX_SERVICE_COUNT;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SERVICE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SNAPSHOT_COUNTER_TYPE_ID;\nimport static java.lang.Boolean.parseBoolean;\nimport static org.agrona.BitUtil.findNextPositivePowerOfTwo;\nimport static org.agrona.SystemUtil.getDurationInNanos;\nimport static org.agrona.SystemUtil.getSizeAsInt;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Component which resides on each node and is responsible for coordinating consensus within a cluster in concert\n * with the lifecycle of clustered services.\n */\n@Versioned\npublic final class ConsensusModule implements AutoCloseable\n{\n    /**\n     * Default set of flags when taking a snapshot.\n     */\n    public static final int CLUSTER_ACTION_FLAGS_DEFAULT = 0;\n\n    /**\n     * Flag for a snapshot taken on a standby node.\n     */\n    public static final int CLUSTER_ACTION_FLAGS_STANDBY_SNAPSHOT = 1;\n\n    /**\n     * Possible states for the {@link ConsensusModule}.\n     * These will be reflected in the {@link Context#moduleStateCounter()} counter.\n     */\n    public enum State\n    {\n        /**\n         * Starting up and recovering state.\n         */\n        INIT(0),\n\n        /**\n         * Active state with ingress and expired timers appended to the log.\n         */\n        ACTIVE(1),\n\n        /**\n         * Suspended processing of ingress and expired timers.\n         */\n        SUSPENDED(2),\n\n        /**\n         * In the process of taking a snapshot.\n         */\n        SNAPSHOT(3),\n\n        /**\n         * Quitting the cluster and shutting down as soon as services ack without taking a snapshot.\n         */\n        QUITTING(4),\n\n        /**\n         * In the process of terminating the node.\n         */\n        TERMINATING(5),\n\n        /**\n         * Terminal state.\n         */\n        CLOSED(6);\n\n        static final State[] STATES = values();\n\n        private final int code;\n\n        State(final int code)\n        {\n            if (code != ordinal())\n            {\n                throw new IllegalArgumentException(name() + \" - code must equal ordinal value: code=\" + code);\n            }\n\n            this.code = code;\n        }\n\n        /**\n         * Code to be stored in an {@link AtomicCounter} for the enum value.\n         *\n         * @return code to be stored in an {@link AtomicCounter} for the enum value.\n         */\n        public final int code()\n        {\n            return code;\n        }\n\n        /**\n         * Get the {@link State} encoded in an {@link AtomicCounter}.\n         *\n         * @param counter to get the current state for.\n         * @return the state for the {@link ConsensusModule}.\n         * @throws ClusterException if the counter is not one of the valid values.\n         */\n        public static State get(final AtomicCounter counter)\n        {\n            if (counter.isClosed())\n            {\n                return CLOSED;\n            }\n\n            return get(counter.get());\n        }\n\n        /**\n         * Get the {@link State} corresponding to a particular code.\n         *\n         * @param code representing a {@link State}.\n         * @return the {@link State} corresponding to the provided code.\n         * @throws ClusterException if the code does not correspond to a valid State.\n         */\n        public static State get(final long code)\n        {\n            if (code < 0 || code > (STATES.length - 1))\n            {\n                throw new ClusterException(\"invalid state counter code: \" + code);\n            }\n\n            return STATES[(int)code];\n        }\n\n        /**\n         * Get the current state of the {@link ConsensusModule}.\n         *\n         * @param counters  to search within.\n         * @param clusterId to which the allocated counter belongs.\n         * @return the state of the ConsensusModule or null if not found.\n         */\n        public static State find(final CountersReader counters, final int clusterId)\n        {\n            final int counterId = ClusterCounters.find(counters, CONSENSUS_MODULE_STATE_TYPE_ID, clusterId);\n            if (Aeron.NULL_VALUE != counterId)\n            {\n                return State.get(counters.getCounterValue(counterId));\n            }\n\n            return null;\n        }\n    }\n\n    /**\n     * Launch an {@link ConsensusModule} with that communicates with an out of process {@link io.aeron.archive.Archive}\n     * and {@link io.aeron.driver.MediaDriver} then awaits shutdown signal.\n     *\n     * @param args command line argument which is a list for properties files as URLs or filenames.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            ConsensusModule ignored = launch(new Context().terminationHook(barrier::signalAll)))\n        {\n            barrier.await();\n            System.out.println(\"Shutdown ConsensusModule...\");\n        }\n    }\n\n    private final Context ctx;\n    private final ConsensusModuleAgent conductor;\n    private final AgentRunner conductorRunner;\n    private final AgentInvoker conductorInvoker;\n\n    ConsensusModule(final Context ctx)\n    {\n        try\n        {\n            ctx.conclude();\n            this.ctx = ctx;\n\n            conductor = new ConsensusModuleAgent(ctx);\n\n            if (ctx.useAgentInvoker())\n            {\n                conductorInvoker = new AgentInvoker(ctx.errorHandler(), ctx.errorCounter(), conductor);\n                conductorRunner = null;\n            }\n            else\n            {\n                conductorRunner = new AgentRunner(\n                    ctx.idleStrategy(), ctx.errorHandler(), ctx.errorCounter(), conductor);\n                conductorInvoker = null;\n            }\n        }\n        catch (final ConcurrentConcludeException ex)\n        {\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            final ClusterMarkFile markFile = ctx.markFile;\n            if (null != markFile)\n            {\n                markFile.signalFailedStart();\n            }\n\n            CloseHelper.quietClose(ctx::close);\n            throw ex;\n        }\n    }\n\n    /**\n     * Launch an {@link ConsensusModule} using a default configuration.\n     *\n     * @return a new instance of an {@link ConsensusModule}.\n     */\n    public static ConsensusModule launch()\n    {\n        return launch(new Context());\n    }\n\n    /**\n     * Launch an {@link ConsensusModule} by providing a configuration context.\n     *\n     * @param ctx for the configuration parameters.\n     * @return a new instance of an {@link ConsensusModule}.\n     */\n    public static ConsensusModule launch(final Context ctx)\n    {\n        final ConsensusModule consensusModule = new ConsensusModule(ctx);\n\n        if (null != consensusModule.conductorRunner)\n        {\n            AgentRunner.startOnThread(consensusModule.conductorRunner, ctx.threadFactory());\n        }\n        else\n        {\n            consensusModule.conductorInvoker.start();\n        }\n\n        return consensusModule;\n    }\n\n    /**\n     * Get the {@link ConsensusModule.Context} that is used by this {@link ConsensusModule}.\n     *\n     * @return the {@link ConsensusModule.Context} that is used by this {@link ConsensusModule}.\n     */\n    public Context context()\n    {\n        return ctx;\n    }\n\n    /**\n     * Get the {@link AgentInvoker} for the consensus module.\n     *\n     * @return the {@link AgentInvoker} for the consensus module.\n     */\n    public AgentInvoker conductorAgentInvoker()\n    {\n        return conductorInvoker;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.closeAll(conductorRunner, conductorInvoker);\n    }\n\n    /**\n     * Configuration options for cluster.\n     */\n    @Config(existsInC = false)\n    public static final class Configuration\n    {\n        private Configuration()\n        {\n        }\n\n        /**\n         * Major version of the network protocol from consensus module to consensus module. If these don't match then\n         * consensus modules are not compatible.\n         */\n        public static final int PROTOCOL_MAJOR_VERSION = 1;\n\n        /**\n         * Minor version of the network protocol from consensus module to consensus module. If these don't match then\n         * some features may not be available.\n         */\n        public static final int PROTOCOL_MINOR_VERSION = 0;\n\n        /**\n         * Patch version of the network protocol from consensus module to consensus module. If these don't match then\n         * bug fixes may not have been applied.\n         */\n        public static final int PROTOCOL_PATCH_VERSION = 0;\n\n        /**\n         * Combined semantic version for the client to consensus module protocol.\n         *\n         * @see SemanticVersion\n         */\n        public static final int PROTOCOL_SEMANTIC_VERSION = SemanticVersion.compose(\n            PROTOCOL_MAJOR_VERSION, PROTOCOL_MINOR_VERSION, PROTOCOL_PATCH_VERSION);\n\n        /**\n         * Type of snapshot for this component.\n         */\n        public static final long SNAPSHOT_TYPE_ID = 1;\n\n        /**\n         * Property name for the limit for fragments to be consumed on each poll of ingress.\n         */\n        @Config\n        public static final String CLUSTER_INGRESS_FRAGMENT_LIMIT_PROP_NAME = \"aeron.cluster.ingress.fragment.limit\";\n\n        /**\n         * Default for the limit for fragments to be consumed on each poll of ingress.\n         */\n        @Config\n        public static final int CLUSTER_INGRESS_FRAGMENT_LIMIT_DEFAULT = 50;\n\n        /**\n         * Property name for whether IPC ingress is allowed or not.\n         */\n        @Config\n        public static final String CLUSTER_INGRESS_IPC_ALLOWED_PROP_NAME = \"aeron.cluster.ingress.ipc.allowed\";\n\n        /**\n         * Default for whether IPC ingress is allowed or not.\n         */\n        @Config\n        public static final String CLUSTER_INGRESS_IPC_ALLOWED_DEFAULT = \"false\";\n\n        /**\n         * Service ID to identify a snapshot in the {@link RecordingLog} for the consensus module.\n         */\n        public static final int SERVICE_ID = Aeron.NULL_VALUE;\n\n        /**\n         * Property name for the identity of the cluster member.\n         */\n        @Config\n        public static final String CLUSTER_MEMBER_ID_PROP_NAME = \"aeron.cluster.member.id\";\n\n        /**\n         * Default property for the cluster member identity.\n         */\n        @Config\n        public static final int CLUSTER_MEMBER_ID_DEFAULT = 0;\n\n        /**\n         * Property name for the identity of the appointed leader. This is when automated leader elections are\n         * not employed.\n         * <p>\n         * This feature is for testing and not recommended for production usage.\n         */\n        @Config\n        public static final String APPOINTED_LEADER_ID_PROP_NAME = \"aeron.cluster.appointed.leader.id\";\n\n        /**\n         * Default property for the appointed cluster leader id. A value of {@link Aeron#NULL_VALUE} means no leader\n         * has been appointed and thus an automated leader election should occur.\n         */\n        @Config\n        public static final int APPOINTED_LEADER_ID_DEFAULT = Aeron.NULL_VALUE;\n\n        /**\n         * Property name for the comma separated list of cluster member endpoints.\n         * <p>\n         * <code>\n         * 0,ingress:port,consensus:port,log:port,catchup:port,archive:port| \\\n         * 1,ingress:port,consensus:port,log:port,catchup:port,archive:port| ...\n         * </code>\n         * <p>\n         * The ingress endpoints will be used as the endpoint substituted into the\n         * {@link io.aeron.cluster.client.AeronCluster.Configuration#INGRESS_CHANNEL_PROP_NAME} if the endpoint\n         * is not provided when unicast.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String CLUSTER_MEMBERS_PROP_NAME = \"aeron.cluster.members\";\n\n        /**\n         * Property name for the comma separated list of cluster consensus endpoints used for cluster\n         * backup and cluster standby nodes.\n         */\n        @Config\n        public static final String CLUSTER_CONSENSUS_ENDPOINTS_PROP_NAME = \"aeron.cluster.consensus.endpoints\";\n\n        /**\n         * Default property for the list of cluster consensus endpoints.\n         */\n        @Config\n        public static final String CLUSTER_CONSENSUS_ENDPOINTS_DEFAULT = \"\";\n\n        /**\n         * Channel for the clustered log.\n         */\n        @Config\n        public static final String LOG_CHANNEL_PROP_NAME = \"aeron.cluster.log.channel\";\n\n        /**\n         * Channel for the clustered log. This channel can exist for a potentially long time given cluster operation\n         * so attention should be given to configuration such as term-length and mtu.\n         */\n        @Config\n        public static final String LOG_CHANNEL_DEFAULT = \"aeron:udp?term-length=64m\";\n\n        /**\n         * Property name for the comma separated list of member endpoints.\n         * <p>\n         * <code>\n         * ingress:port,consensus:port,log:port,catchup:port,archive:port\n         * </code>\n         *\n         * @see #CLUSTER_MEMBERS_PROP_NAME\n         */\n        @Config\n        public static final String MEMBER_ENDPOINTS_PROP_NAME = \"aeron.cluster.member.endpoints\";\n\n        /**\n         * Default property for member endpoints.\n         */\n        @Config\n        public static final String MEMBER_ENDPOINTS_DEFAULT = \"\";\n\n        /**\n         * Stream id within a channel for the clustered log.\n         */\n        @Config\n        public static final String LOG_STREAM_ID_PROP_NAME = \"aeron.cluster.log.stream.id\";\n\n        /**\n         * Stream id within a channel for the clustered log.\n         */\n        @Config\n        public static final int LOG_STREAM_ID_DEFAULT = 100;\n\n        /**\n         * Channel to be used for archiving snapshots.\n         */\n        public static final String SNAPSHOT_CHANNEL_DEFAULT = \"aeron:ipc?alias=snapshot\";\n\n        /**\n         * Stream id for the archived snapshots within a channel.\n         */\n        public static final int SNAPSHOT_STREAM_ID_DEFAULT = 107;\n\n        /**\n         * Message detail to be sent when max concurrent session limit is reached.\n         */\n        public static final String SESSION_LIMIT_MSG = \"concurrent session limit\";\n\n        /**\n         * Message detail to be sent when a session is rejected due to authentication.\n         */\n        public static final String SESSION_REJECTED_MSG = \"session failed authentication\";\n\n        /**\n         * Message detail to be sent when a session has an invalid client version.\n         */\n        public static final String SESSION_INVALID_VERSION_MSG = \"invalid client version\";\n\n        /**\n         * Channel to be used communicating cluster consensus to each other. This can be used for default\n         * configuration with the endpoints replaced with those provided by {@link #CLUSTER_MEMBERS_PROP_NAME}.\n         */\n        @Config\n        public static final String CONSENSUS_CHANNEL_PROP_NAME = \"aeron.cluster.consensus.channel\";\n\n        /**\n         * Channel to be used for communicating cluster consensus to each other. This can be used for default\n         * configuration with the endpoints replaced with those provided by {@link #CLUSTER_MEMBERS_PROP_NAME}.\n         */\n        @Config\n        public static final String CONSENSUS_CHANNEL_DEFAULT = \"aeron:udp\";\n\n        /**\n         * Stream id within a channel for communicating consensus messages.\n         */\n        @Config\n        public static final String CONSENSUS_STREAM_ID_PROP_NAME = \"aeron.cluster.consensus.stream.id\";\n\n        /**\n         * Stream id for the communicating consensus messages.\n         */\n        @Config\n        public static final int CONSENSUS_STREAM_ID_DEFAULT = 108;\n\n        /**\n         * Channel to be used for replicating logs and snapshots from other archives to the local one.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String REPLICATION_CHANNEL_PROP_NAME = \"aeron.cluster.replication.channel\";\n\n        /**\n         * Channel template used for replaying logs to a follower using the {@link ClusterMember#catchupEndpoint()}.\n         */\n        @Config\n        public static final String FOLLOWER_CATCHUP_CHANNEL_PROP_NAME = \"aeron.cluster.follower.catchup.channel\";\n\n        /**\n         * Default channel template used for replaying logs to a follower using the\n         * {@link ClusterMember#catchupEndpoint()}.\n         */\n        @Config\n        public static final String FOLLOWER_CATCHUP_CHANNEL_DEFAULT = UDP_CHANNEL;\n\n        /**\n         * Channel used to build the control request channel for the leader Archive.\n         */\n        @Config\n        public static final String LEADER_ARCHIVE_CONTROL_CHANNEL_PROP_NAME =\n            \"aeron.cluster.leader.archive.control.channel\";\n\n        /**\n         * Default channel used to build the control request channel for the leader Archive.\n         */\n        @Config\n        public static final String LEADER_ARCHIVE_CONTROL_CHANNEL_DEFAULT = \"aeron:udp?term-length=64k\";\n\n        /**\n         * Counter type id for the consensus module state.\n         */\n        public static final int CONSENSUS_MODULE_STATE_TYPE_ID = AeronCounters.CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID;\n\n        /**\n         * Counter type id for the cluster node role.\n         */\n        public static final int CLUSTER_NODE_ROLE_TYPE_ID =\n            ClusteredServiceContainer.Configuration.CLUSTER_NODE_ROLE_TYPE_ID;\n\n        /**\n         * Counter type id for the control toggle.\n         */\n        public static final int CONTROL_TOGGLE_TYPE_ID = ClusterControl.CONTROL_TOGGLE_TYPE_ID;\n\n        /**\n         * Counter type id of a commit position.\n         */\n        public static final int COMMIT_POSITION_TYPE_ID =\n            ClusteredServiceContainer.Configuration.COMMIT_POSITION_TYPE_ID;\n\n        /**\n         * Type id of a recovery state counter.\n         */\n        public static final int RECOVERY_STATE_TYPE_ID = AeronCounters.CLUSTER_RECOVERY_STATE_TYPE_ID;\n\n        /**\n         * Counter type id for count of snapshots taken.\n         */\n        public static final int SNAPSHOT_COUNTER_TYPE_ID = AeronCounters.CLUSTER_SNAPSHOT_COUNTER_TYPE_ID;\n\n        /**\n         * Type id for election state counter.\n         */\n        public static final int ELECTION_STATE_TYPE_ID = AeronCounters.CLUSTER_ELECTION_STATE_TYPE_ID;\n\n        /**\n         * Counter type id for the consensus module error count.\n         */\n        public static final int CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID =\n            AeronCounters.CLUSTER_CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID;\n\n        /**\n         * Counter type id for the number of cluster clients which have been timed out.\n         */\n        public static final int CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID =\n            AeronCounters.CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID;\n\n        /**\n         * Counter type id for the number of invalid requests which the cluster has received.\n         */\n        public static final int CLUSTER_INVALID_REQUEST_COUNT_TYPE_ID =\n            AeronCounters.CLUSTER_INVALID_REQUEST_COUNT_TYPE_ID;\n\n        /**\n         * The number of services in this cluster instance.\n         *\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_ID_PROP_NAME\n         */\n        @Config\n        public static final String SERVICE_COUNT_PROP_NAME = \"aeron.cluster.service.count\";\n\n        /**\n         * The default number of services in this cluster instance.\n         */\n        @Config\n        public static final int SERVICE_COUNT_DEFAULT = 1;\n\n        /**\n         * Maximum number of cluster sessions that can be active concurrently.\n         */\n        @Config\n        public static final String MAX_CONCURRENT_SESSIONS_PROP_NAME = \"aeron.cluster.max.sessions\";\n\n        /**\n         * Maximum number of cluster sessions that can be active concurrently.\n         */\n        @Config\n        public static final int MAX_CONCURRENT_SESSIONS_DEFAULT = 10;\n\n        /**\n         * Timeout for a session if no activity is observed.\n         */\n        @Config\n        public static final String SESSION_TIMEOUT_PROP_NAME = \"aeron.cluster.session.timeout\";\n\n        /**\n         * Timeout for a session if no activity is observed.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000)\n        public static final long SESSION_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10);\n\n        /**\n         * Timeout for a leader if no heartbeat is received by another member.\n         */\n        @Config\n        public static final String LEADER_HEARTBEAT_TIMEOUT_PROP_NAME = \"aeron.cluster.leader.heartbeat.timeout\";\n\n        /**\n         * Timeout for a leader if no heartbeat is received by another member.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000)\n        public static final long LEADER_HEARTBEAT_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10);\n\n        /**\n         * Interval at which a leader will send heartbeats if the log is not progressing.\n         */\n        @Config\n        public static final String LEADER_HEARTBEAT_INTERVAL_PROP_NAME = \"aeron.cluster.leader.heartbeat.interval\";\n\n        /**\n         * Interval at which a leader will send heartbeats if the log is not progressing.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 200L * 1000 * 1000)\n        public static final long LEADER_HEARTBEAT_INTERVAL_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(200);\n\n        /**\n         * Timeout after which an election vote will be attempted after startup while waiting to canvass the status\n         * of members if a majority has been heard from.\n         */\n        @Config\n        public static final String STARTUP_CANVASS_TIMEOUT_PROP_NAME = \"aeron.cluster.startup.canvass.timeout\";\n\n        /**\n         * Default timeout after which an election vote will be attempted on startup when waiting to canvass the\n         * status of all members before going for a majority if possible.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 60L * 1000 * 1000 * 1000)\n        public static final long STARTUP_CANVASS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(60);\n\n        /**\n         * Timeout after which an election fails if the candidate does not get a majority of votes.\n         */\n        @Config\n        public static final String ELECTION_TIMEOUT_PROP_NAME = \"aeron.cluster.election.timeout\";\n\n        /**\n         * Default timeout after which an election fails if the candidate does not get a majority of votes.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000)\n        public static final long ELECTION_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(1);\n\n        /**\n         * Interval at which a member will send out status updates during election phases.\n         */\n        @Config\n        public static final String ELECTION_STATUS_INTERVAL_PROP_NAME = \"aeron.cluster.election.status.interval\";\n\n        /**\n         * Default interval at which a member will send out status updates during election phases.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 100L * 1000 * 1000)\n        public static final long ELECTION_STATUS_INTERVAL_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n        /**\n         * Name of the system property for specifying a supplier of {@link Authenticator} for the cluster.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"io.aeron.security.DefaultAuthenticatorSupplier\")\n        public static final String AUTHENTICATOR_SUPPLIER_PROP_NAME = \"aeron.cluster.authenticator.supplier\";\n\n        /**\n         * Name of the system property for specifying a supplier of {@link AuthorisationService} for the cluster.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME =\n            \"aeron.cluster.authorisation.service.supplier\";\n\n        /**\n         * Default {@link AuthorisationServiceSupplier} that returns an {@link AuthorisationService} that\n         * allows the commands necessary for Cluster Backup and Aeron Cluster Standby.\n         * ({@link AllowBackupAndStandbyAuthorisationService}).\n         */\n        public static final AuthorisationServiceSupplier DEFAULT_AUTHORISATION_SERVICE_SUPPLIER =\n            () -> AllowBackupAndStandbyAuthorisationService.INSTANCE;\n\n        /**\n         * Size in bytes of the error buffer for the cluster.\n         */\n        @Config\n        public static final String ERROR_BUFFER_LENGTH_PROP_NAME = \"aeron.cluster.error.buffer.length\";\n\n        /**\n         * Size in bytes of the error buffer for the cluster.\n         */\n        @Config\n        public static final int ERROR_BUFFER_LENGTH_DEFAULT = ClusterMarkFile.ERROR_BUFFER_MIN_LENGTH;\n\n        /**\n         * Timeout a leader will wait on getting termination ACKs from followers.\n         */\n        @Config\n        public static final String TERMINATION_TIMEOUT_PROP_NAME = \"aeron.cluster.termination.timeout\";\n\n        /**\n         * Property name for threshold value for the consensus module agent work cycle threshold to track\n         * for being exceeded.\n         */\n        @Config\n        public static final String CYCLE_THRESHOLD_PROP_NAME = \"aeron.cluster.cycle.threshold\";\n\n        /**\n         * Default threshold value for the consensus module agent work cycle threshold to track for being exceeded.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 100_000_000L)\n        public static final long CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n        /**\n         * Property name for threshold value, which is used for tracking total snapshot duration breaches.\n         */\n        @Config\n        public static final String TOTAL_SNAPSHOT_DURATION_THRESHOLD_PROP_NAME =\n            \"aeron.cluster.total.snapshot.threshold\";\n\n        /**\n         * Default threshold value, which is used for tracking total snapshot duration breaches.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000)\n        public static final long TOTAL_SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS =\n            TimeUnit.MILLISECONDS.toNanos(1000);\n\n        /**\n         * Property name for the standby snapshot notification processing delay.\n         */\n        @Config\n        public static final String STANDBY_SNAPSHOT_NOTIFICATION_PROCESSING_DELAY_PROP_NAME =\n            \"aeron.cluster.standby.snapshot.notification.processing.delay\";\n\n        /**\n         * Default standby snapshot notification processing delay.\n         */\n        @Config\n        public static final long STANDBY_SNAPSHOT_NOTIFICATION_PROCESSING_DELAY_DEFAULT_NS = 0;\n\n        /**\n         * Default timeout a leader will wait on getting termination ACKs from followers.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000)\n        public static final long TERMINATION_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10);\n\n        /**\n         * Resolution in nanoseconds for each tick of the timer wheel for scheduling deadlines.\n         */\n        @Config\n        public static final String WHEEL_TICK_RESOLUTION_PROP_NAME = \"aeron.cluster.wheel.tick.resolution\";\n\n        /**\n         * Resolution in nanoseconds for each tick of the timer wheel for scheduling deadlines. Defaults to 8ms.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 4L * 1000 * 1000)\n        public static final long WHEEL_TICK_RESOLUTION_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(8);\n\n        /**\n         * Number of ticks, or spokes, on the timer wheel. Higher number of ticks reduces potential conflicts\n         * traded off against memory usage.\n         */\n        @Config\n        public static final String TICKS_PER_WHEEL_PROP_NAME = \"aeron.cluster.ticks.per.wheel\";\n\n        /**\n         * Number of ticks, or spokes, on the timer wheel. Higher number of ticks reduces potential conflicts\n         * traded off against memory usage. Defaults to 128 per wheel.\n         */\n        @Config\n        public static final int TICKS_PER_WHEEL_DEFAULT = 128;\n\n        /**\n         * The level at which files should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         */\n        @Config\n        public static final String FILE_SYNC_LEVEL_PROP_NAME = \"aeron.cluster.file.sync.level\";\n\n        /**\n         * Default file sync level of normal writes.\n         */\n        @Config\n        public static final int FILE_SYNC_LEVEL_DEFAULT = 0;\n\n        /**\n         * {@link TimerServiceSupplier} to be used for creating the {@link TimerService} used by consensus module.\n         */\n        @Config\n        public static final String TIMER_SERVICE_SUPPLIER_PROP_NAME = \"aeron.cluster.timer.service.supplier\";\n\n        /**\n         * Name of the {@link TimerServiceSupplier} that creates {@link TimerService} based on the timer wheel\n         * implementation.\n         */\n        public static final String TIMER_SERVICE_SUPPLIER_WHEEL = \"io.aeron.cluster.WheelTimerServiceSupplier\";\n\n        /**\n         * Name of the {@link TimerServiceSupplier} that creates a sequence-preserving {@link TimerService} based\n         * on a priority heap implementation.\n         */\n        public static final String TIMER_SERVICE_SUPPLIER_PRIORITY_HEAP =\n            \"io.aeron.cluster.PriorityHeapTimerServiceSupplier\";\n\n        /**\n         * Default {@link TimerServiceSupplier}.\n         */\n        @Config\n        public static final String TIMER_SERVICE_SUPPLIER_DEFAULT = TIMER_SERVICE_SUPPLIER_WHEEL;\n\n        /**\n         * Property name for the name returned from {@link Agent#roleName()} for the consensus module.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String CLUSTER_CONSENSUS_MODULE_AGENT_ROLE_NAME_PROP_NAME =\n            \"aeron.cluster.consensus.module.agent.role.name\";\n\n        /**\n         * Property name for replication progress timeout.\n         *\n         * @since 1.41.0\n         */\n        @Config\n        public static final String CLUSTER_REPLICATION_PROGRESS_TIMEOUT_PROP_NAME =\n            \"aeron.cluster.replication.progress.timeout\";\n\n        /**\n         * Default timeout for replication progress in nanoseconds.\n         *\n         * @since 1.41.0\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 10L * 1000 * 1000 * 1000)\n        public static final long CLUSTER_REPLICATION_PROGRESS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10);\n\n        /**\n         * Property name for replication progress interval.\n         *\n         * @since 1.41.0\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 0)\n        public static final String CLUSTER_REPLICATION_PROGRESS_INTERVAL_PROP_NAME =\n            \"aeron.cluster.replication.progress.interval\";\n\n        /**\n         * Property name of enabling the acceptance of standby snapshots.\n         */\n        @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false)\n        public static final String CLUSTER_ACCEPT_STANDBY_SNAPSHOTS_PROP_NAME =\n            \"aeron.cluster.accept.standby.snapshots\";\n\n        /**\n         * Property name of setting {@link ClusterClock}. Should specify a fully qualified class name.\n         * Defaults to {@link MillisecondClusterClock}.\n         *\n         * @since 1.44.0\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"io.aeron.cluster.MillisecondClusterClock\")\n        public static final String CLUSTER_CLOCK_PROP_NAME = \"aeron.cluster.clock\";\n\n        /**\n         * Property name of setting {@link ConsensusModuleExtension}. Should specify a fully qualified class name.\n         *\n         * @since 1.45.0\n         */\n        public static final String CONSENSUS_MODULE_EXTENSION_CLASS_NAME_PROP_NAME =\n            \"aeron.cluster.consensus.module.extension\";\n\n        /**\n         * Property name of the setting to control whether the consensus module should bind the control endpoint of\n         * the consensus publication to its local consensus address.\n         */\n        @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true)\n        public static final String CONSENSUS_MODULE_ENABLE_CONTROL_ON_CONSENSUS_CHANNEL_PROP_NAME =\n            \"aeron.cluster.consensus.control.enable\";\n\n        /**\n         * Property name of the setting to control whether the consensus module should bind the control endpoint of\n         * the consensus publication to its local log address.\n         */\n        @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true)\n        public static final String CONSENSUS_MODULE_ENABLE_CONTROL_ON_LOG_CHANNEL_PROP_NAME =\n            \"aeron.cluster.log.control.enable\";\n\n        /**\n         * The value {@link #CLUSTER_INGRESS_FRAGMENT_LIMIT_DEFAULT} or system property\n         * {@link #CLUSTER_INGRESS_FRAGMENT_LIMIT_PROP_NAME} if set.\n         *\n         * @return {@link #CLUSTER_INGRESS_FRAGMENT_LIMIT_DEFAULT} or system property\n         * {@link #CLUSTER_INGRESS_FRAGMENT_LIMIT_PROP_NAME} if set.\n         */\n        public static int ingressFragmentLimit()\n        {\n            return Integer.getInteger(CLUSTER_INGRESS_FRAGMENT_LIMIT_PROP_NAME, CLUSTER_INGRESS_FRAGMENT_LIMIT_DEFAULT);\n        }\n\n        /**\n         * The value {@link #CLUSTER_INGRESS_IPC_ALLOWED_DEFAULT} or system property\n         * {@link #CLUSTER_INGRESS_IPC_ALLOWED_PROP_NAME} if set.\n         *\n         * @return {@link #CLUSTER_INGRESS_IPC_ALLOWED_DEFAULT} or system property\n         * {@link #CLUSTER_INGRESS_IPC_ALLOWED_PROP_NAME} if set.\n         */\n        public static boolean isIpcIngressAllowed()\n        {\n            return \"true\".equalsIgnoreCase(System.getProperty(\n                CLUSTER_INGRESS_IPC_ALLOWED_PROP_NAME, CLUSTER_INGRESS_IPC_ALLOWED_DEFAULT));\n        }\n\n        /**\n         * The value {@link #CLUSTER_MEMBER_ID_DEFAULT} or system property\n         * {@link #CLUSTER_MEMBER_ID_PROP_NAME} if set.\n         *\n         * @return {@link #CLUSTER_MEMBER_ID_DEFAULT} or system property\n         * {@link #CLUSTER_MEMBER_ID_PROP_NAME} if set.\n         */\n        public static int clusterMemberId()\n        {\n            return Integer.getInteger(CLUSTER_MEMBER_ID_PROP_NAME, CLUSTER_MEMBER_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #APPOINTED_LEADER_ID_DEFAULT} or system property\n         * {@link #APPOINTED_LEADER_ID_PROP_NAME} if set.\n         * <p>\n         * This feature is for testing and not recommended for production usage.\n         *\n         * @return {@link #APPOINTED_LEADER_ID_DEFAULT} or system property\n         * {@link #APPOINTED_LEADER_ID_PROP_NAME} if set.\n         */\n        public static int appointedLeaderId()\n        {\n            return Integer.getInteger(APPOINTED_LEADER_ID_PROP_NAME, APPOINTED_LEADER_ID_DEFAULT);\n        }\n\n        /**\n         * The value of system property {@link #CLUSTER_MEMBERS_PROP_NAME} if set, null otherwise.\n         *\n         * @return of system property {@link #CLUSTER_MEMBERS_PROP_NAME} if set.\n         */\n        public static String clusterMembers()\n        {\n            return System.getProperty(CLUSTER_MEMBERS_PROP_NAME);\n        }\n\n        /**\n         * The value {@link #CLUSTER_CONSENSUS_ENDPOINTS_DEFAULT} or system property\n         * {@link #CLUSTER_CONSENSUS_ENDPOINTS_PROP_NAME} if set.\n         *\n         * @return {@link #CLUSTER_CONSENSUS_ENDPOINTS_DEFAULT} or system property\n         * {@link #CLUSTER_CONSENSUS_ENDPOINTS_PROP_NAME} it set.\n         */\n        public static String clusterConsensusEndpoints()\n        {\n            return System.getProperty(CLUSTER_CONSENSUS_ENDPOINTS_PROP_NAME, CLUSTER_CONSENSUS_ENDPOINTS_DEFAULT);\n        }\n\n        /**\n         * The value {@link #LOG_CHANNEL_DEFAULT} or system property {@link #LOG_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #LOG_CHANNEL_DEFAULT} or system property {@link #LOG_CHANNEL_PROP_NAME} if set.\n         */\n        public static String logChannel()\n        {\n            return System.getProperty(LOG_CHANNEL_PROP_NAME, LOG_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #LOG_STREAM_ID_DEFAULT} or system property {@link #LOG_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #LOG_STREAM_ID_DEFAULT} or system property {@link #LOG_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int logStreamId()\n        {\n            return Integer.getInteger(LOG_STREAM_ID_PROP_NAME, LOG_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #MEMBER_ENDPOINTS_DEFAULT} or system property {@link #MEMBER_ENDPOINTS_PROP_NAME} if set.\n         *\n         * @return {@link #MEMBER_ENDPOINTS_DEFAULT} or system property {@link #MEMBER_ENDPOINTS_PROP_NAME} if set.\n         */\n        public static String memberEndpoints()\n        {\n            return System.getProperty(MEMBER_ENDPOINTS_PROP_NAME, MEMBER_ENDPOINTS_DEFAULT);\n        }\n\n        /**\n         * The value {@link #SNAPSHOT_CHANNEL_DEFAULT} or system property\n         * {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SNAPSHOT_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #SNAPSHOT_CHANNEL_DEFAULT} or system property\n         * {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SNAPSHOT_CHANNEL_PROP_NAME} if set.\n         */\n        public static String snapshotChannel()\n        {\n            return System.getProperty(\n                ClusteredServiceContainer.Configuration.SNAPSHOT_CHANNEL_PROP_NAME, SNAPSHOT_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #SNAPSHOT_STREAM_ID_DEFAULT} or system property\n         * {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SNAPSHOT_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #SNAPSHOT_STREAM_ID_DEFAULT} or system property\n         * {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SNAPSHOT_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int snapshotStreamId()\n        {\n            return Integer.getInteger(\n                ClusteredServiceContainer.Configuration.SNAPSHOT_STREAM_ID_PROP_NAME, SNAPSHOT_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #SERVICE_COUNT_DEFAULT} or system property\n         * {@link #SERVICE_COUNT_PROP_NAME} if set.\n         *\n         * @return {@link #SERVICE_COUNT_DEFAULT} or system property\n         * {@link #SERVICE_COUNT_PROP_NAME} if set.\n         */\n        public static int serviceCount()\n        {\n            return Integer.getInteger(SERVICE_COUNT_PROP_NAME, SERVICE_COUNT_DEFAULT);\n        }\n\n        /**\n         * The value {@link #MAX_CONCURRENT_SESSIONS_DEFAULT} or system property\n         * {@link #MAX_CONCURRENT_SESSIONS_PROP_NAME} if set.\n         *\n         * @return {@link #MAX_CONCURRENT_SESSIONS_DEFAULT} or system property\n         * {@link #MAX_CONCURRENT_SESSIONS_PROP_NAME} if set.\n         */\n        public static int maxConcurrentSessions()\n        {\n            return Integer.getInteger(MAX_CONCURRENT_SESSIONS_PROP_NAME, MAX_CONCURRENT_SESSIONS_DEFAULT);\n        }\n\n        /**\n         * Timeout for a session if no activity is observed.\n         *\n         * @return timeout in nanoseconds to wait for activity\n         * @see #SESSION_TIMEOUT_PROP_NAME\n         */\n        public static long sessionTimeoutNs()\n        {\n            return getDurationInNanos(SESSION_TIMEOUT_PROP_NAME, SESSION_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * Timeout for a leader if no heartbeat is received by another member.\n         *\n         * @return timeout in nanoseconds to wait for heartbeat from a leader.\n         * @see #LEADER_HEARTBEAT_TIMEOUT_PROP_NAME\n         */\n        public static long leaderHeartbeatTimeoutNs()\n        {\n            return getDurationInNanos(LEADER_HEARTBEAT_TIMEOUT_PROP_NAME, LEADER_HEARTBEAT_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * Interval at which a leader will send a heartbeat if the log is not progressing.\n         *\n         * @return timeout in nanoseconds to for leader heartbeats when no log being appended.\n         * @see #LEADER_HEARTBEAT_INTERVAL_PROP_NAME\n         */\n        public static long leaderHeartbeatIntervalNs()\n        {\n            return getDurationInNanos(LEADER_HEARTBEAT_INTERVAL_PROP_NAME, LEADER_HEARTBEAT_INTERVAL_DEFAULT_NS);\n        }\n\n        /**\n         * Timeout waiting to canvass the status of cluster members before voting if a majority have been heard from.\n         *\n         * @return timeout in nanoseconds to wait for the status of other cluster members before voting.\n         * @see #STARTUP_CANVASS_TIMEOUT_PROP_NAME\n         */\n        public static long startupCanvassTimeoutNs()\n        {\n            return getDurationInNanos(STARTUP_CANVASS_TIMEOUT_PROP_NAME, STARTUP_CANVASS_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * Timeout waiting for votes to become leader in an election.\n         *\n         * @return timeout in nanoseconds to wait for votes to become leader in an election.\n         * @see #ELECTION_TIMEOUT_PROP_NAME\n         */\n        public static long electionTimeoutNs()\n        {\n            return getDurationInNanos(ELECTION_TIMEOUT_PROP_NAME, ELECTION_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * Interval at which a member will send out status messages during the election phases.\n         *\n         * @return interval at which a member will send out status messages during the election phases.\n         * @see #ELECTION_STATUS_INTERVAL_PROP_NAME\n         */\n        public static long electionStatusIntervalNs()\n        {\n            return getDurationInNanos(ELECTION_STATUS_INTERVAL_PROP_NAME, ELECTION_STATUS_INTERVAL_DEFAULT_NS);\n        }\n\n        /**\n         * Timeout waiting for follower termination by leader.\n         *\n         * @return timeout in nanoseconds to wait followers to terminate.\n         * @see #TERMINATION_TIMEOUT_PROP_NAME\n         */\n        public static long terminationTimeoutNs()\n        {\n            return getDurationInNanos(TERMINATION_TIMEOUT_PROP_NAME, TERMINATION_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * Get threshold value for the consensus module agent work cycle threshold to track for being exceeded.\n         *\n         * @return threshold value in nanoseconds.\n         */\n        public static long cycleThresholdNs()\n        {\n            return getDurationInNanos(CYCLE_THRESHOLD_PROP_NAME, CYCLE_THRESHOLD_DEFAULT_NS);\n        }\n\n        /**\n         * Get threshold value, which is used for monitoring total snapshot duration breaches of its predefined\n         * threshold.\n         *\n         * @return threshold value in nanoseconds.\n         */\n        public static long totalSnapshotDurationThresholdNs()\n        {\n            return getDurationInNanos(\n                TOTAL_SNAPSHOT_DURATION_THRESHOLD_PROP_NAME, TOTAL_SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS);\n        }\n\n        /**\n         * Get the delay before recording a standby snapshot notification.\n         * The delay starts after the commit position passes the snapshot's log position.\n         *\n         * @return the delay, in nanoseconds.\n         */\n        public static long standbySnapshotNotificationProcessingDelayNs()\n        {\n            return getDurationInNanos(\n                STANDBY_SNAPSHOT_NOTIFICATION_PROCESSING_DELAY_PROP_NAME,\n                STANDBY_SNAPSHOT_NOTIFICATION_PROCESSING_DELAY_DEFAULT_NS);\n        }\n\n        /**\n         * Size in bytes of the error buffer in the mark file.\n         *\n         * @return length of error buffer in bytes.\n         * @see #ERROR_BUFFER_LENGTH_PROP_NAME\n         */\n        public static int errorBufferLength()\n        {\n            return getSizeAsInt(ERROR_BUFFER_LENGTH_PROP_NAME, ERROR_BUFFER_LENGTH_DEFAULT);\n        }\n\n        /**\n         * The value {@link DefaultAuthenticatorSupplier#INSTANCE} or system property\n         * {@link #AUTHENTICATOR_SUPPLIER_PROP_NAME} if set.\n         *\n         * @return {@link DefaultAuthenticatorSupplier#INSTANCE} or system property\n         * {@link #AUTHENTICATOR_SUPPLIER_PROP_NAME} if set.\n         */\n        public static AuthenticatorSupplier authenticatorSupplier()\n        {\n            final String supplierClassName = System.getProperty(AUTHENTICATOR_SUPPLIER_PROP_NAME);\n            if (Strings.isEmpty(supplierClassName))\n            {\n                return DefaultAuthenticatorSupplier.INSTANCE;\n            }\n\n            AuthenticatorSupplier supplier = null;\n            try\n            {\n                supplier = (AuthenticatorSupplier)Class.forName(supplierClassName).getConstructor().newInstance();\n            }\n            catch (final Exception ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n\n            return supplier;\n        }\n\n        /**\n         * The {@link AuthorisationServiceSupplier} specified in the\n         * {@link #AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME} system property or the\n         * {@link #DEFAULT_AUTHORISATION_SERVICE_SUPPLIER}.\n         *\n         * @return system property {@link #AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME} if set or\n         * {@link #DEFAULT_AUTHORISATION_SERVICE_SUPPLIER} otherwise.\n         */\n        public static AuthorisationServiceSupplier authorisationServiceSupplier()\n        {\n            final String supplierClassName = System.getProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n            if (Strings.isEmpty(supplierClassName))\n            {\n                return DEFAULT_AUTHORISATION_SERVICE_SUPPLIER;\n            }\n            else if (AuthorisationService.DENY_ALL_NAME.equals(supplierClassName))\n            {\n                return () -> AuthorisationService.DENY_ALL;\n            }\n            else if (AuthorisationService.ALLOW_ALL_NAME.equals(supplierClassName))\n            {\n                fallbackLogger().println(\"Warning: Cluster authorisation service set to allow all requests\");\n                return () -> AuthorisationService.ALLOW_ALL;\n            }\n\n            try\n            {\n                return (AuthorisationServiceSupplier)Class.forName(supplierClassName).getConstructor().newInstance();\n            }\n            catch (final Exception ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n                return null;\n            }\n        }\n\n        /**\n         * The value {@link #CONSENSUS_CHANNEL_DEFAULT} or system property\n         * {@link #CONSENSUS_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #CONSENSUS_CHANNEL_DEFAULT} or system property\n         * {@link #CONSENSUS_CHANNEL_PROP_NAME} if set.\n         */\n        public static String consensusChannel()\n        {\n            return System.getProperty(CONSENSUS_CHANNEL_PROP_NAME, CONSENSUS_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #CONSENSUS_STREAM_ID_DEFAULT} or system property\n         * {@link #CONSENSUS_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #CONSENSUS_STREAM_ID_DEFAULT} or system property\n         * {@link #CONSENSUS_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int consensusStreamId()\n        {\n            return Integer.getInteger(CONSENSUS_STREAM_ID_PROP_NAME, CONSENSUS_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The system property for {@link #REPLICATION_CHANNEL_PROP_NAME} if set or null.\n         *\n         * @return system property {@link #REPLICATION_CHANNEL_PROP_NAME} if set or null.\n         */\n        public static String replicationChannel()\n        {\n            return System.getProperty(REPLICATION_CHANNEL_PROP_NAME);\n        }\n\n        /**\n         * The value {@link #FOLLOWER_CATCHUP_CHANNEL_DEFAULT} or system property\n         * {@link #FOLLOWER_CATCHUP_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #FOLLOWER_CATCHUP_CHANNEL_DEFAULT} or system property\n         * {@link #FOLLOWER_CATCHUP_CHANNEL_PROP_NAME} if set.\n         */\n        public static String followerCatchupChannel()\n        {\n            return System.getProperty(FOLLOWER_CATCHUP_CHANNEL_PROP_NAME, FOLLOWER_CATCHUP_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #LEADER_ARCHIVE_CONTROL_CHANNEL_DEFAULT} or system property\n         * {@link #LEADER_ARCHIVE_CONTROL_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #LEADER_ARCHIVE_CONTROL_CHANNEL_DEFAULT} or system property\n         * {@link #LEADER_ARCHIVE_CONTROL_CHANNEL_PROP_NAME} if set.\n         */\n        public static String leaderArchiveControlChannel()\n        {\n            return System.getProperty(LEADER_ARCHIVE_CONTROL_CHANNEL_PROP_NAME, LEADER_ARCHIVE_CONTROL_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #WHEEL_TICK_RESOLUTION_DEFAULT_NS} or system property\n         * {@link #WHEEL_TICK_RESOLUTION_PROP_NAME} if set.\n         *\n         * @return {@link #WHEEL_TICK_RESOLUTION_DEFAULT_NS} or system property\n         * {@link #WHEEL_TICK_RESOLUTION_PROP_NAME} if set.\n         */\n        public static long wheelTickResolutionNs()\n        {\n            return getDurationInNanos(WHEEL_TICK_RESOLUTION_PROP_NAME, WHEEL_TICK_RESOLUTION_DEFAULT_NS);\n        }\n\n        /**\n         * The value {@link #TICKS_PER_WHEEL_DEFAULT} or system property\n         * {@link #CLUSTER_MEMBER_ID_PROP_NAME} if set.\n         *\n         * @return {@link #TICKS_PER_WHEEL_DEFAULT} or system property\n         * {@link #TICKS_PER_WHEEL_PROP_NAME} if set.\n         */\n        public static int ticksPerWheel()\n        {\n            return Integer.getInteger(TICKS_PER_WHEEL_PROP_NAME, TICKS_PER_WHEEL_DEFAULT);\n        }\n\n        /**\n         * The level at which files should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         *\n         * @return level at which files should be sync'ed to disk.\n         */\n        public static int fileSyncLevel()\n        {\n            return Integer.getInteger(FILE_SYNC_LEVEL_PROP_NAME, FILE_SYNC_LEVEL_DEFAULT);\n        }\n\n        /**\n         * The name of the {@link TimerServiceSupplier} to use for supplying the {@link TimerService}.\n         *\n         * @return {@link #TIMER_SERVICE_SUPPLIER_DEFAULT} or system property.\n         * {@link #TIMER_SERVICE_SUPPLIER_PROP_NAME} if set.\n         */\n        public static String timerServiceSupplier()\n        {\n            return System.getProperty(TIMER_SERVICE_SUPPLIER_PROP_NAME, TIMER_SERVICE_SUPPLIER_DEFAULT);\n        }\n\n        /**\n         * The name to be used for the {@link Agent#roleName()} for the consensus module agent.\n         *\n         * @return name to be used for the {@link Agent#roleName()} for the consensus module agent.\n         * @see #CLUSTER_CONSENSUS_MODULE_AGENT_ROLE_NAME_PROP_NAME\n         */\n        public static String agentRoleName()\n        {\n            return System.getProperty(CLUSTER_CONSENSUS_MODULE_AGENT_ROLE_NAME_PROP_NAME);\n        }\n\n        /**\n         * The amount of time to wait to time out an archive replication when progress has stalled.\n         *\n         * @return system property {@link #CLUSTER_REPLICATION_PROGRESS_TIMEOUT_PROP_NAME} or\n         * {@link #CLUSTER_REPLICATION_PROGRESS_TIMEOUT_DEFAULT_NS}.\n         * @since 1.41.0\n         */\n        public static long replicationProgressTimeoutNs()\n        {\n            return SystemUtil.getDurationInNanos(\n                CLUSTER_REPLICATION_PROGRESS_TIMEOUT_PROP_NAME, CLUSTER_REPLICATION_PROGRESS_TIMEOUT_DEFAULT_NS);\n        }\n\n\n        /**\n         * Interval between checks for progress on an archive replication.\n         *\n         * @return system property {@link #CLUSTER_REPLICATION_PROGRESS_INTERVAL_PROP_NAME} or {@link Aeron#NULL_VALUE}\n         * if not set.\n         * @since 1.41.0\n         */\n        public static long replicationProgressIntervalNs()\n        {\n            return SystemUtil.getDurationInNanos(CLUSTER_REPLICATION_PROGRESS_INTERVAL_PROP_NAME, Aeron.NULL_VALUE);\n        }\n\n        /**\n         * If this node should accept snapshots from standby nodes.\n         *\n         * @return value from property {@link #CLUSTER_ACCEPT_STANDBY_SNAPSHOTS_PROP_NAME} or false if not set.\n         */\n        public static boolean acceptStandbySnapshots()\n        {\n            return Boolean.getBoolean(CLUSTER_ACCEPT_STANDBY_SNAPSHOTS_PROP_NAME);\n        }\n\n        /**\n         * Create a new {@link ConsensusModuleExtension} based on the configured\n         * {@link #CONSENSUS_MODULE_EXTENSION_CLASS_NAME_PROP_NAME}.\n         *\n         * @return a new {@link ConsensusModuleExtension} based on the configured\n         * {@link #CONSENSUS_MODULE_EXTENSION_CLASS_NAME_PROP_NAME}.\n         */\n        public static ConsensusModuleExtension newConsensusModuleExtension()\n        {\n            final String className = System.getProperty(Configuration.CONSENSUS_MODULE_EXTENSION_CLASS_NAME_PROP_NAME);\n            if (null != className)\n            {\n                try\n                {\n                    return (ConsensusModuleExtension)Class.forName(className).getConstructor().newInstance();\n                }\n                catch (final Exception ex)\n                {\n                    LangUtil.rethrowUnchecked(ex);\n                }\n            }\n\n            return null;\n        }\n\n        /**\n         * Determine the ConsensusModule should bind the control endpoint of the consensus publication.\n         *\n         * @return <code>true</code> if the ConsensusModule should bind the control endpoint of the consensus\n         * publication.\n         */\n        public static boolean enableControlOnConsensusChannel()\n        {\n            return parseBoolean(System.getProperty(\n                CONSENSUS_MODULE_ENABLE_CONTROL_ON_CONSENSUS_CHANNEL_PROP_NAME, \"true\"));\n        }\n\n        /**\n         * Determine the ConsensusModule should bind the control endpoint of the log publication.\n         *\n         * @return <code>true</code> if the ConsensusModule should bind the log endpoint of the consensus\n         * publication.\n         */\n        public static boolean enableControlOnLogChannel()\n        {\n            return parseBoolean(System.getProperty(CONSENSUS_MODULE_ENABLE_CONTROL_ON_LOG_CHANNEL_PROP_NAME, \"true\"));\n        }\n    }\n\n    /**\n     * Programmable overrides for configuring the {@link ConsensusModule} in a cluster.\n     * <p>\n     * The context will be owned by {@link ConsensusModuleAgent} after a successful\n     * {@link ConsensusModule#launch(Context)} and closed via {@link ConsensusModule#close()}.\n     */\n    public static final class Context implements Cloneable\n    {\n        private static final VarHandle IS_CONCLUDED_VH;\n\n        static\n        {\n            try\n            {\n                IS_CONCLUDED_VH = MethodHandles.lookup().findVarHandle(Context.class, \"isConcluded\", boolean.class);\n            }\n            catch (final ReflectiveOperationException ex)\n            {\n                throw new ExceptionInInitializerError(ex);\n            }\n        }\n\n        private volatile boolean isConcluded;\n        private boolean ownsAeronClient = false;\n        private String aeronDirectoryName = CommonContext.getAeronDirectoryName();\n        private Aeron aeron;\n\n        private boolean deleteDirOnStart = false;\n        private String clusterDirectoryName = ClusteredServiceContainer.Configuration.clusterDirName();\n        private String clusterServicesDirectoryName = ClusteredServiceContainer.Configuration.clusterServicesDirName();\n        private File clusterDir;\n        private File markFileDir;\n        private RecordingLog recordingLog;\n        private ClusterMarkFile markFile;\n        private NodeStateFile nodeStateFile;\n        private int fileSyncLevel = Archive.Configuration.fileSyncLevel();\n\n        private int appVersion = SemanticVersion.compose(0, 0, 1);\n        private int clusterId = ClusteredServiceContainer.Configuration.clusterId();\n        private int clusterMemberId = Configuration.clusterMemberId();\n        private int appointedLeaderId = Configuration.appointedLeaderId();\n        private String clusterMembers = Configuration.clusterMembers();\n        private String ingressChannel = AeronCluster.Configuration.ingressChannel();\n        private int ingressStreamId = AeronCluster.Configuration.ingressStreamId();\n        private boolean isIpcIngressAllowed = Configuration.isIpcIngressAllowed();\n        private int ingressFragmentLimit = Configuration.ingressFragmentLimit();\n        private String egressChannel = AeronCluster.Configuration.egressChannel();\n        private String logChannel = Configuration.logChannel();\n        private int logStreamId = Configuration.logStreamId();\n        private String memberEndpoints = Configuration.memberEndpoints();\n        private String replayChannel = ClusteredServiceContainer.Configuration.replayChannel();\n        private int replayStreamId = ClusteredServiceContainer.Configuration.replayStreamId();\n        private String controlChannel = ClusteredServiceContainer.Configuration.controlChannel();\n        private int consensusModuleStreamId = ClusteredServiceContainer.Configuration.consensusModuleStreamId();\n        private int serviceStreamId = ClusteredServiceContainer.Configuration.serviceStreamId();\n        private String snapshotChannel = Configuration.snapshotChannel();\n        private int snapshotStreamId = Configuration.snapshotStreamId();\n        private String consensusChannel = Configuration.consensusChannel();\n        private int consensusStreamId = Configuration.consensusStreamId();\n        private String replicationChannel = Configuration.replicationChannel();\n        private String followerCatchupChannel = Configuration.followerCatchupChannel();\n        private String leaderArchiveControlChannel = Configuration.leaderArchiveControlChannel();\n        private int logFragmentLimit = ClusteredServiceContainer.Configuration.logFragmentLimit();\n\n        private int serviceCount = Configuration.serviceCount();\n        private int errorBufferLength = Configuration.errorBufferLength();\n        private int maxConcurrentSessions = Configuration.maxConcurrentSessions();\n        private int ticksPerWheel = Configuration.ticksPerWheel();\n        private long wheelTickResolutionNs = Configuration.wheelTickResolutionNs();\n        private long sessionTimeoutNs = Configuration.sessionTimeoutNs();\n        private long leaderHeartbeatTimeoutNs = Configuration.leaderHeartbeatTimeoutNs();\n        private long leaderHeartbeatIntervalNs = Configuration.leaderHeartbeatIntervalNs();\n        private long startupCanvassTimeoutNs = Configuration.startupCanvassTimeoutNs();\n        private long electionTimeoutNs = Configuration.electionTimeoutNs();\n        private long electionStatusIntervalNs = Configuration.electionStatusIntervalNs();\n        private long terminationTimeoutNs = Configuration.terminationTimeoutNs();\n        private long cycleThresholdNs = Configuration.cycleThresholdNs();\n        private long totalSnapshotDurationThresholdNs = Configuration.totalSnapshotDurationThresholdNs();\n        private long standbySnapshotNotificationProcessingDelayNs =\n            Configuration.standbySnapshotNotificationProcessingDelayNs();\n\n        private String agentRoleName = Configuration.agentRoleName();\n        private ThreadFactory threadFactory;\n        private Supplier<IdleStrategy> idleStrategySupplier;\n        private ClusterClock clusterClock;\n        private EpochClock epochClock;\n        private Random random;\n        private TimerServiceSupplier timerServiceSupplier;\n        private Function<Context, LongConsumer> clusterTimeConsumerSupplier;\n        private ConsensusModuleExtension consensusModuleExtension;\n        private DistinctErrorLog errorLog;\n        private ErrorHandler errorHandler;\n        private AtomicCounter errorCounter;\n        private CountedErrorHandler countedErrorHandler;\n\n        private Counter moduleStateCounter;\n        private Counter electionStateCounter;\n        private Counter clusterNodeRoleCounter;\n        private Counter commitPosition;\n        private Counter clusterControlToggle;\n        private Counter nodeControlToggle;\n        private Counter snapshotCounter;\n        private Counter timedOutClientCounter;\n        private Counter standbySnapshotCounter;\n        private Counter electionCounter;\n        private Counter leadershipTermId;\n        private Runnable terminationHook;\n\n        private AeronArchive.Context archiveContext;\n        private AuthenticatorSupplier authenticatorSupplier;\n        private AuthorisationServiceSupplier authorisationServiceSupplier;\n        private LogPublisher logPublisher;\n        private EgressPublisher egressPublisher;\n        private DutyCycleTracker dutyCycleTracker;\n        private SnapshotDurationTracker totalSnapshotDurationTracker;\n        private AppVersionValidator appVersionValidator;\n        private boolean isLogMdc;\n        private boolean useAgentInvoker = false;\n        private ConsensusModuleStateExport bootstrapState = null;\n        private boolean acceptStandbySnapshots = Configuration.acceptStandbySnapshots();\n        private boolean enableControlOnConsensusChannel = Configuration.enableControlOnConsensusChannel();\n        private boolean enableControlOnLogChannel = Configuration.enableControlOnLogChannel();\n\n        /**\n         * Construct a Context using default values and loading from system properties.\n         */\n        public Context()\n        {\n        }\n\n        /**\n         * Perform a shallow copy of the object.\n         *\n         * @return a shallow copy of the object.\n         */\n        public Context clone()\n        {\n            try\n            {\n                return (Context)super.clone();\n            }\n            catch (final CloneNotSupportedException ex)\n            {\n                throw new RuntimeException(ex);\n            }\n        }\n\n        /**\n         * Conclude configuration by setting up defaults when specifics are not provided.\n         */\n        @SuppressWarnings(\"MethodLength\")\n        public void conclude()\n        {\n            if ((boolean)IS_CONCLUDED_VH.getAndSet(this, true))\n            {\n                throw new ConcurrentConcludeException();\n            }\n\n            validateLogChannel();\n\n            if (serviceCount < 0 || serviceCount > MAX_SERVICE_COUNT)\n            {\n                throw new ClusterException(\"service count of range [0, \" + MAX_SERVICE_COUNT + \"]: \" + serviceCount);\n            }\n\n            if (null == clusterDir)\n            {\n                clusterDir = new File(clusterDirectoryName);\n            }\n\n            if (null == markFileDir)\n            {\n                final String dir = ClusteredServiceContainer.Configuration.markFileDir();\n                markFileDir = Strings.isEmpty(dir) ? clusterDir : new File(dir);\n            }\n\n            try\n            {\n                clusterDir = clusterDir.getCanonicalFile();\n                clusterDirectoryName = clusterDir.getAbsolutePath();\n                markFileDir = markFileDir.getCanonicalFile();\n\n                if (Strings.isEmpty(clusterServicesDirectoryName))\n                {\n                    clusterServicesDirectoryName = clusterDirectoryName;\n                }\n                else\n                {\n                    clusterServicesDirectoryName = new File(clusterServicesDirectoryName).getCanonicalPath();\n                }\n            }\n            catch (final IOException ex)\n            {\n                throw new UncheckedIOException(ex);\n            }\n\n            if (null == clusterMembers)\n            {\n                throw new ClusterException(\"ConsensusModule.Context.clusterMembers must be set\");\n            }\n\n            if (deleteDirOnStart)\n            {\n                IoUtil.delete(clusterDir, false);\n            }\n\n            IoUtil.ensureDirectoryExists(clusterDir, \"cluster\");\n            IoUtil.ensureDirectoryExists(markFileDir, \"mark file\");\n\n            if (startupCanvassTimeoutNs / leaderHeartbeatTimeoutNs < 2)\n            {\n                throw new ClusterException(\n                    \"startupCanvassTimeoutNs=\" + startupCanvassTimeoutNs +\n                    \" must be a multiple of leaderHeartbeatTimeoutNs=\" + leaderHeartbeatTimeoutNs);\n            }\n\n            if (null == clusterClock)\n            {\n                final String clockClassName = System.getProperty(\n                    CLUSTER_CLOCK_PROP_NAME, MillisecondClusterClock.class.getName());\n                try\n                {\n                    clusterClock = (ClusterClock)Class.forName(clockClassName).getConstructor().newInstance();\n                }\n                catch (final Exception e)\n                {\n                    throw new ClusterException(\"failed to instantiate ClusterClock \" + clockClassName, e);\n                }\n            }\n\n            if (null == epochClock)\n            {\n                epochClock = SystemEpochClock.INSTANCE;\n            }\n\n            if (null == appVersionValidator)\n            {\n                appVersionValidator = AppVersionValidator.SEMANTIC_VERSIONING_VALIDATOR;\n            }\n\n            if (null == clusterTimeConsumerSupplier)\n            {\n                clusterTimeConsumerSupplier = (ctx) -> (timestamp) -> {};\n            }\n\n            if (null == markFile)\n            {\n                final int filePageSize = null != aeron ? aeron.context().filePageSize() :\n                    driverFilePageSize(new File(aeronDirectoryName), epochClock, new CommonContext().driverTimeoutMs());\n                markFile = new ClusterMarkFile(\n                    new File(markFileDir, ClusterMarkFile.FILENAME),\n                    ClusterComponentType.CONSENSUS_MODULE,\n                    errorBufferLength,\n                    epochClock,\n                    ClusteredServiceContainer.Configuration.LIVENESS_TIMEOUT_MS,\n                    filePageSize);\n            }\n\n            MarkFile.ensureMarkFileLink(\n                clusterDir,\n                new File(markFile.parentDirectory(), ClusterMarkFile.FILENAME),\n                ClusterMarkFile.LINK_FILENAME);\n\n            if (null == nodeStateFile)\n            {\n                try\n                {\n                    nodeStateFile = new NodeStateFile(clusterDir, true, fileSyncLevel());\n                }\n                catch (final IOException ex)\n                {\n                    throw new ClusterException(\"unable to create node-state file\", ex);\n                }\n            }\n\n            if (Aeron.NULL_VALUE == nodeStateFile.candidateTerm().candidateTermId() &&\n                Aeron.NULL_VALUE != markFile.candidateTermId())\n            {\n                nodeStateFile.updateCandidateTermId(markFile.candidateTermId(), Aeron.NULL_VALUE, epochClock.time());\n            }\n\n            if (null == errorLog)\n            {\n                errorLog = new DistinctErrorLog(markFile.errorBuffer(), epochClock, StandardCharsets.US_ASCII);\n            }\n\n            errorHandler = CommonContext.setupErrorHandler(errorHandler, errorLog);\n\n            if (null == recordingLog)\n            {\n                recordingLog = new RecordingLog(clusterDir, true);\n            }\n\n            if (Strings.isEmpty(agentRoleName))\n            {\n                agentRoleName = \"consensus-module-\" + clusterId + \"-\" + clusterMemberId;\n            }\n\n            final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n\n            if (null == aeron)\n            {\n                ownsAeronClient = true;\n\n                aeron = Aeron.connect(\n                    new Aeron.Context()\n                        .aeronDirectoryName(aeronDirectoryName)\n                        .errorHandler(errorHandler)\n                        .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE)\n                        .epochClock(epochClock)\n                        .useConductorAgentInvoker(true)\n                        .awaitingIdleStrategy(YieldingIdleStrategy.INSTANCE)\n                        .clientLock(NoOpLock.INSTANCE)\n                        .clientName(agentRoleName));\n\n                if (null == errorCounter)\n                {\n                    errorCounter = ClusterCounters.allocateVersioned(\n                        aeron,\n                        buffer,\n                        \"Cluster Errors\",\n                        CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID,\n                        clusterId,\n                        ConsensusModuleVersion.VERSION,\n                        ConsensusModuleVersion.GIT_SHA);\n                }\n            }\n\n            if (null == ingressChannel)\n            {\n                throw new ClusterException(\"ingressChannel must be specified\");\n            }\n\n            if (!(aeron.context().subscriberErrorHandler() instanceof RethrowingErrorHandler))\n            {\n                throw new ClusterException(\"Aeron client must use a RethrowingErrorHandler\");\n            }\n\n            if (!aeron.context().useConductorAgentInvoker())\n            {\n                throw new ClusterException(\"Aeron client must use conductor agent invoker\");\n            }\n\n            if (null == errorCounter)\n            {\n                throw new ClusterException(\"error counter must be supplied if aeron client is\");\n            }\n\n            if (null == countedErrorHandler)\n            {\n                countedErrorHandler = new CountedErrorHandler(errorHandler, errorCounter);\n                if (ownsAeronClient)\n                {\n                    aeron.context().errorHandler(countedErrorHandler);\n                }\n            }\n\n            if (null == moduleStateCounter)\n            {\n                final CountersReader counters = aeron.countersReader();\n                if (Aeron.NULL_VALUE != ClusterCounters.find(counters, CONSENSUS_MODULE_STATE_TYPE_ID, clusterId))\n                {\n                    throw new ClusterException(\"existing consensus module detected for clusterId=\" + clusterId);\n                }\n\n                moduleStateCounter = ClusterCounters.allocate(\n                    aeron, buffer, \"Consensus Module state\", CONSENSUS_MODULE_STATE_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, moduleStateCounter, CONSENSUS_MODULE_STATE_TYPE_ID);\n\n\n            if (null == electionStateCounter)\n            {\n                electionStateCounter = ClusterCounters.allocate(\n                    aeron, buffer, \"Cluster election state\", ELECTION_STATE_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, electionStateCounter, ELECTION_STATE_TYPE_ID);\n\n            if (null == electionCounter)\n            {\n                electionCounter = ClusterCounters.allocate(\n                    aeron, buffer, \"Cluster election count\", CLUSTER_ELECTION_COUNT_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, electionCounter, CLUSTER_ELECTION_COUNT_TYPE_ID);\n\n            if (null == leadershipTermId)\n            {\n                leadershipTermId = ClusterCounters.allocate(\n                    aeron, buffer, \"Cluster leadership term id\", CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, leadershipTermId, CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID);\n\n            if (null == clusterNodeRoleCounter)\n            {\n                clusterNodeRoleCounter = ClusterCounters.allocate(\n                    aeron, buffer, \"Cluster node role\", CLUSTER_NODE_ROLE_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, clusterNodeRoleCounter, CLUSTER_NODE_ROLE_TYPE_ID);\n\n            if (null == commitPosition)\n            {\n                commitPosition = ClusterCounters.allocate(\n                    aeron, buffer, \"Cluster commit-pos:\", COMMIT_POSITION_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, commitPosition, COMMIT_POSITION_TYPE_ID);\n\n            if (null == clusterControlToggle)\n            {\n                clusterControlToggle = ClusterCounters.allocate(\n                    aeron, buffer, \"Cluster control toggle\", CONTROL_TOGGLE_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, clusterControlToggle, CONTROL_TOGGLE_TYPE_ID);\n\n            if (null == nodeControlToggle)\n            {\n                nodeControlToggle = ClusterCounters.allocate(\n                    aeron, buffer, \"Node control toggle\", NODE_CONTROL_TOGGLE_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, nodeControlToggle, NODE_CONTROL_TOGGLE_TYPE_ID);\n\n            if (null == snapshotCounter)\n            {\n                snapshotCounter = ClusterCounters.allocate(\n                    aeron, buffer, \"Cluster snapshot count\", SNAPSHOT_COUNTER_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, snapshotCounter, SNAPSHOT_COUNTER_TYPE_ID);\n\n            if (null == timedOutClientCounter)\n            {\n                timedOutClientCounter = ClusterCounters.allocate(\n                    aeron, buffer, \"Cluster timed out client count\", CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID, clusterId);\n            }\n            validateCounterTypeId(aeron, timedOutClientCounter, CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID);\n\n            // TODO: Disable with configuration... (Mike)\n            if (acceptStandbySnapshots)\n            {\n                if (null == standbySnapshotCounter)\n                {\n                    standbySnapshotCounter = ClusterCounters.allocate(\n                        aeron,\n                        buffer,\n                        \"Cluster standby snapshots received\",\n                        CLUSTER_STANDBY_SNAPSHOT_COUNTER_TYPE_ID,\n                        clusterId);\n                }\n                validateCounterTypeId(aeron, standbySnapshotCounter, CLUSTER_STANDBY_SNAPSHOT_COUNTER_TYPE_ID);\n            }\n\n            if (null == dutyCycleTracker)\n            {\n                dutyCycleTracker = new DutyCycleStallTracker(\n                    ClusterCounters.allocate(\n                        aeron, buffer, \"Cluster max cycle time in ns\",\n                        AeronCounters.CLUSTER_MAX_CYCLE_TIME_TYPE_ID, clusterId),\n                    ClusterCounters.allocate(\n                        aeron, buffer, \"Cluster work cycle time exceeded count: threshold=\" +\n                            SystemUtil.formatDuration(cycleThresholdNs),\n                        AeronCounters.CLUSTER_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID, clusterId),\n                    cycleThresholdNs);\n            }\n\n            if (null == totalSnapshotDurationTracker)\n            {\n                totalSnapshotDurationTracker = new SnapshotDurationTracker(\n                    ClusterCounters.allocate(\n                        aeron,\n                        buffer,\n                        \"Total max snapshot duration in ns\",\n                        AeronCounters.CLUSTER_TOTAL_MAX_SNAPSHOT_DURATION_TYPE_ID,\n                        clusterId),\n                    ClusterCounters.allocate(\n                        aeron,\n                        buffer,\n                        \"Total max snapshot duration exceeded count: threshold=\" +\n                            SystemUtil.formatDuration(totalSnapshotDurationThresholdNs),\n                        AeronCounters.CLUSTER_TOTAL_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID,\n                        clusterId),\n                    totalSnapshotDurationThresholdNs);\n            }\n\n            if (null == threadFactory)\n            {\n                threadFactory = Thread::new;\n            }\n\n            if (null == idleStrategySupplier)\n            {\n                idleStrategySupplier = ClusteredServiceContainer.Configuration.idleStrategySupplier(null);\n            }\n\n            if (null == timerServiceSupplier)\n            {\n                timerServiceSupplier = getTimerServiceSupplierFromSystemProperty();\n            }\n\n            if (null == archiveContext)\n            {\n                archiveContext = new AeronArchive.Context()\n                    .controlRequestChannel(AeronArchive.Configuration.localControlChannel())\n                    .controlResponseChannel(AeronArchive.Configuration.localControlChannel())\n                    .controlRequestStreamId(AeronArchive.Configuration.localControlStreamId())\n                    .controlResponseStreamId(\n                        clusterId * 100 + 100 + AeronArchive.Configuration.controlResponseStreamId());\n            }\n\n            if (!archiveContext.controlRequestChannel().startsWith(CommonContext.IPC_CHANNEL))\n            {\n                throw new ClusterException(\"local archive control must be IPC\");\n            }\n\n            if (!archiveContext.controlResponseChannel().startsWith(CommonContext.IPC_CHANNEL))\n            {\n                throw new ClusterException(\"local archive control must be IPC\");\n            }\n\n            if (null == replicationChannel)\n            {\n                throw new ClusterException(\"replicationChannel must be set\");\n            }\n\n            archiveContext\n                .aeron(aeron)\n                .errorHandler(countedErrorHandler)\n                .ownsAeronClient(false)\n                .lock(NoOpLock.INSTANCE)\n                .controlRequestChannel(addAliasIfAbsent(\n                archiveContext.controlRequestChannel(),\n                \"cm-archive-ctrl-req-cluster-\" + clusterId + \"-member-\" + clusterMemberId))\n                .controlResponseChannel(addAliasIfAbsent(\n                archiveContext.controlResponseChannel(),\n                \"cm-archive-ctrl-resp-cluster-\" + clusterId + \"-member-\" + clusterMemberId))\n                .clientName(agentRoleName);\n\n            if (null == terminationHook)\n            {\n                terminationHook = () -> {};\n            }\n\n            if (null == authenticatorSupplier)\n            {\n                authenticatorSupplier = Configuration.authenticatorSupplier();\n            }\n\n            if (null == authorisationServiceSupplier)\n            {\n                authorisationServiceSupplier = Configuration.authorisationServiceSupplier();\n            }\n\n            if (null == random)\n            {\n                random = new Random();\n            }\n\n            if (null == logPublisher)\n            {\n                logPublisher = new LogPublisher(logChannel());\n            }\n\n            if (null == egressPublisher)\n            {\n                egressPublisher = new EgressPublisher(leaderHeartbeatTimeoutNs);\n            }\n\n            final ChannelUri channelUri = parse(logChannel());\n            isLogMdc = channelUri.isUdp() && null == channelUri.get(ENDPOINT_PARAM_NAME);\n\n            if (null == consensusModuleExtension)\n            {\n                consensusModuleExtension = Configuration.newConsensusModuleExtension();\n            }\n\n            if (null != consensusModuleExtension && 0 != serviceCount)\n            {\n                throw new ClusterException(\"service count must be zero when ConsensusModuleExtension is enabled\");\n            }\n            else if (null == consensusModuleExtension && 0 == serviceCount)\n            {\n                throw new ClusterException(\"zero services are only supported when ConsensusModuleExtension is enabled\");\n            }\n\n            concludeMarkFile();\n\n            if (CommonContext.shouldPrintConfigurationOnStart())\n            {\n                System.out.println(this);\n            }\n        }\n\n        /**\n         * Has the context had the {@link #conclude()} method called.\n         *\n         * @return true of the {@link #conclude()} method has been called.\n         */\n        public boolean isConcluded()\n        {\n            return isConcluded;\n        }\n\n        /**\n         * Should the consensus module attempt to immediately delete {@link #clusterDir()} on startup.\n         *\n         * @param deleteDirOnStart Attempt deletion.\n         * @return this for a fluent API.\n         */\n        public Context deleteDirOnStart(final boolean deleteDirOnStart)\n        {\n            this.deleteDirOnStart = deleteDirOnStart;\n            return this;\n        }\n\n        /**\n         * Will the consensus module attempt to immediately delete {@link #clusterDir()} on startup.\n         *\n         * @return true when directory will be deleted, otherwise false.\n         */\n        public boolean deleteDirOnStart()\n        {\n            return deleteDirOnStart;\n        }\n\n        /**\n         * Set the directory name to use for the consensus module directory.\n         *\n         * @param clusterDirectoryName to use.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public Context clusterDirectoryName(final String clusterDirectoryName)\n        {\n            this.clusterDirectoryName = clusterDirectoryName;\n            return this;\n        }\n\n        /**\n         * The directory name to use for the consensus module directory.\n         *\n         * @return directory name for the consensus module directory.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public String clusterDirectoryName()\n        {\n            return clusterDirectoryName;\n        }\n\n        /**\n         * Set the directory used by clustered service containers. By default, the cluster services will share a\n         * directory with the consensus module. However, sometimes it will be useful for these values to be different.\n         * Setting this value allows that location to be tracked in the ClusterMarkFile to allow for the ClusterTool to\n         * resolve their location when using action like <code>describe</code> or <code>errors</code>. There is an\n         * expectation that all clustered service containers will use the same directory.\n         *\n         * @param clusterServicesDirectoryName to use.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_SERVICES_DIR_PROP_NAME\n         */\n        public Context clusterServicesDirectoryName(final String clusterServicesDirectoryName)\n        {\n            this.clusterServicesDirectoryName = clusterServicesDirectoryName;\n            return this;\n        }\n\n        /**\n         * The directory used by the clustered service containers.\n         *\n         * @return directory used by the clustered service containers.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_SERVICES_DIR_PROP_NAME\n         * @see #clusterServicesDirectoryName(String)\n         */\n        @Config(id = \"CLUSTER_SERVICES_DIR\")\n        public String clusterServicesDirectoryName()\n        {\n            return clusterServicesDirectoryName;\n        }\n\n        /**\n         * Set the directory to use for the consensus module directory.\n         *\n         * @param clusterDir to use.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public Context clusterDir(final File clusterDir)\n        {\n            this.clusterDir = clusterDir;\n            return this;\n        }\n\n        /**\n         * The directory used for the consensus module directory.\n         *\n         * @return directory for the consensus module directory.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public File clusterDir()\n        {\n            return clusterDir;\n        }\n\n        /**\n         * Get the directory in which the ConsensusModule will store mark file (i.e. {@code cluster-mark.dat}). It\n         * defaults to {@link #clusterDir()} if it is not set explicitly via the\n         * {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME}.\n         *\n         * @return the directory in which the ConsensusModule will store mark file (i.e. {@code cluster-mark.dat}).\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME\n         * @see #clusterDir()\n         */\n        public File markFileDir()\n        {\n            return markFileDir;\n        }\n\n        /**\n         * Set the directory in which the ConsensusModule will store mark file (i.e. {@code cluster-mark.dat}).\n         *\n         * @param markFileDir the directory in which the Archive will store mark file (i.e. {@code cluster-mark.dat}).\n         * @return this for a fluent API.\n         */\n        public ConsensusModule.Context markFileDir(final File markFileDir)\n        {\n            this.markFileDir = markFileDir;\n            return this;\n        }\n\n        /**\n         * Set the {@link RecordingLog} for the log terms and snapshots.\n         *\n         * @param recordingLog to use.\n         * @return this for a fluent API.\n         */\n        public Context recordingLog(final RecordingLog recordingLog)\n        {\n            this.recordingLog = recordingLog;\n            return this;\n        }\n\n        /**\n         * The {@link RecordingLog} for the log terms and snapshots.\n         *\n         * @return {@link RecordingLog} for the  log terms and snapshots.\n         */\n        public RecordingLog recordingLog()\n        {\n            return recordingLog;\n        }\n\n        /**\n         * User assigned application version which appended to the log as the appVersion in new leadership events.\n         * <p>\n         * This can be validated using {@link org.agrona.SemanticVersion} to ensure only application nodes of the same\n         * major version communicate with each other.\n         *\n         * @param appVersion for user application.\n         * @return this for a fluent API.\n         */\n        public Context appVersion(final int appVersion)\n        {\n            this.appVersion = appVersion;\n            return this;\n        }\n\n        /**\n         * User assigned application version which appended to the log as the appVersion in new leadership events.\n         * <p>\n         * This can be validated using {@link org.agrona.SemanticVersion} to ensure only application nodes of the same\n         * major version communicate with each other.\n         *\n         * @return appVersion for user application.\n         */\n        public int appVersion()\n        {\n            return appVersion;\n        }\n\n        /**\n         * User assigned application version validator implementation used to check version compatibility.\n         * <p>\n         * The default validator uses {@link org.agrona.SemanticVersion} semantics.\n         *\n         * @param appVersionValidator for user application.\n         * @return this for fluent API.\n         */\n        public Context appVersionValidator(final AppVersionValidator appVersionValidator)\n        {\n            this.appVersionValidator = appVersionValidator;\n            return this;\n        }\n\n        /**\n         * User assigned application version validator implementation used to check version compatibility.\n         * <p>\n         * The default is to use {@link org.agrona.SemanticVersion} major version for checking compatibility.\n         *\n         * @return AppVersionValidator in use.\n         */\n        public AppVersionValidator appVersionValidator()\n        {\n            return appVersionValidator;\n        }\n\n        /**\n         * Get level at which files should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         *\n         * @return the level to be applied for file write.\n         * @see Configuration#FILE_SYNC_LEVEL_PROP_NAME\n         */\n        @Config\n        public int fileSyncLevel()\n        {\n            return fileSyncLevel;\n        }\n\n        /**\n         * Set level at which files should be sync'ed to disk.\n         * <ul>\n         * <li>0 - normal writes.</li>\n         * <li>1 - sync file data.</li>\n         * <li>2 - sync file data + metadata.</li>\n         * </ul>\n         *\n         * @param syncLevel to be applied for file writes.\n         * @return this for a fluent API.\n         * @see Configuration#FILE_SYNC_LEVEL_PROP_NAME\n         */\n        public Context fileSyncLevel(final int syncLevel)\n        {\n            this.fileSyncLevel = syncLevel;\n            return this;\n        }\n\n        /**\n         * Set the id for this cluster instance. This must match with the service containers.\n         *\n         * @param clusterId for this clustered instance.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_ID_PROP_NAME\n         */\n        public Context clusterId(final int clusterId)\n        {\n            this.clusterId = clusterId;\n            return this;\n        }\n\n        /**\n         * Get the id for this cluster instance. This must match with the service containers.\n         *\n         * @return the id for this cluster instance.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CLUSTER_ID_PROP_NAME\n         */\n        public int clusterId()\n        {\n            return clusterId;\n        }\n\n        /**\n         * This cluster member identity.\n         *\n         * @param clusterMemberId for this member.\n         * @return this for a fluent API.\n         * @see Configuration#CLUSTER_MEMBER_ID_PROP_NAME\n         */\n        public Context clusterMemberId(final int clusterMemberId)\n        {\n            this.clusterMemberId = clusterMemberId;\n            return this;\n        }\n\n        /**\n         * This cluster member identity.\n         *\n         * @return this cluster member identity.\n         * @see Configuration#CLUSTER_MEMBER_ID_PROP_NAME\n         */\n        @Config\n        public int clusterMemberId()\n        {\n            return clusterMemberId;\n        }\n\n        /**\n         * The cluster member id of the appointed cluster leader.\n         * <p>\n         * -1 means no leader has been appointed and an automated leader election should occur.\n         * <p>\n         * This feature is for testing and not recommended for production usage.\n         *\n         * @param appointedLeaderId for the cluster.\n         * @return this for a fluent API.\n         * @see Configuration#APPOINTED_LEADER_ID_PROP_NAME\n         */\n        public Context appointedLeaderId(final int appointedLeaderId)\n        {\n            this.appointedLeaderId = appointedLeaderId;\n            return this;\n        }\n\n        /**\n         * The cluster member id of the appointed cluster leader.\n         * <p>\n         * -1 means no leader has been appointed and an automated leader election should occur.\n         *\n         * @return cluster member id of the appointed cluster leader.\n         * @see Configuration#APPOINTED_LEADER_ID_PROP_NAME\n         */\n        @Config\n        public int appointedLeaderId()\n        {\n            return appointedLeaderId;\n        }\n\n        /**\n         * String representing the cluster members.\n         * <p>\n         * <code>\n         * 0,ingress:port,consensus:port,log:port,catchup:port,archive:port| \\\n         * 1,ingress:port,consensus:port,log:port,catchup:port,archive:port| ...\n         * </code>\n         * <p>\n         * The ingress endpoints will be used as the endpoint substituted into the {@link #ingressChannel()}\n         * if the endpoint is not provided unless it is multicast.\n         *\n         * @param clusterMembers which are all candidates to be leader.\n         * @return this for a fluent API.\n         * @see Configuration#CLUSTER_MEMBERS_PROP_NAME\n         */\n        public Context clusterMembers(final String clusterMembers)\n        {\n            this.clusterMembers = clusterMembers;\n            return this;\n        }\n\n        /**\n         * The endpoints representing members of the cluster which are all candidates to be leader.\n         * <p>\n         * The ingress endpoints will be used as the endpoint in {@link #ingressChannel()} if the endpoint is\n         * not provided in that when it is not multicast.\n         *\n         * @return members of the cluster which are all candidates to be leader.\n         * @see Configuration#CLUSTER_MEMBERS_PROP_NAME\n         */\n        @Config\n        public String clusterMembers()\n        {\n            return clusterMembers;\n        }\n\n        /**\n         * Set the channel parameter for the ingress channel.\n         *\n         * @param channel parameter for the ingress channel.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.client.AeronCluster.Configuration#INGRESS_CHANNEL_PROP_NAME\n         */\n        public Context ingressChannel(final String channel)\n        {\n            ingressChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the ingress channel.\n         *\n         * @return the channel parameter for the ingress channel.\n         * @see io.aeron.cluster.client.AeronCluster.Configuration#INGRESS_CHANNEL_PROP_NAME\n         */\n        public String ingressChannel()\n        {\n            return ingressChannel;\n        }\n\n        /**\n         * Set the stream id for the ingress channel.\n         *\n         * @param streamId for the ingress channel.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.client.AeronCluster.Configuration#INGRESS_STREAM_ID_PROP_NAME\n         */\n        public Context ingressStreamId(final int streamId)\n        {\n            ingressStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the ingress channel.\n         *\n         * @return the stream id for the ingress channel.\n         * @see io.aeron.cluster.client.AeronCluster.Configuration#INGRESS_STREAM_ID_PROP_NAME\n         */\n        public int ingressStreamId()\n        {\n            return ingressStreamId;\n        }\n\n        /**\n         * Set limit for fragments to be consumed on each poll of ingress.\n         *\n         * @param ingressFragmentLimit for the ingress channel.\n         * @return this for a fluent API\n         * @see Configuration#CLUSTER_INGRESS_FRAGMENT_LIMIT_PROP_NAME\n         */\n        public Context ingressFragmentLimit(final int ingressFragmentLimit)\n        {\n            this.ingressFragmentLimit = ingressFragmentLimit;\n            return this;\n        }\n\n        /**\n         * The limit for fragments to be consumed on each poll of ingress.\n         *\n         * @return the limit for fragments to be consumed on each poll of ingress.\n         * @see Configuration#CLUSTER_INGRESS_FRAGMENT_LIMIT_PROP_NAME\n         */\n        @Config(id = \"CLUSTER_INGRESS_FRAGMENT_LIMIT\")\n        public int ingressFragmentLimit()\n        {\n            return ingressFragmentLimit;\n        }\n\n        /**\n         * Set the template channel that is used to refine the response channels for a Cluster, i.e. egress channel,\n         * backup query response channel, heartbeat response channel etc. Defaults to {@code null} in which case the\n         * provided response channel will be used. The main use-case is the ability to define the `interface` parameter\n         * to steer the response traffic via a specific network interface.\n         *\n         * <p><em><strong>Note:</strong> each URI parameter that is defined on this channel template will\n         * <strong>override</strong> the corresponding URI parameter in the provided response channel!</em>\n         *\n         * @param channel the channel template for response channels.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.client.AeronCluster.Configuration#EGRESS_CHANNEL_PROP_NAME\n         */\n        public Context egressChannel(final String channel)\n        {\n            egressChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the template channel that is used to refine the response channels for a Cluster, i.e. egress channel,\n         * backup query response channel, heartbeat response channel etc. Defaults to {@code null} in which case the\n         * provided response channel will be used. The main use-case is the ability to define the `interface` parameter\n         * to steer the response traffic via a specific network interface.\n         *\n         * <p><em><strong>Note:</strong> each URI parameter that is defined on this channel template will\n         * <strong>override</strong> the corresponding URI parameter in the provided response channel!</em>\n         *\n         * @return the channel template for response channels.\n         * @see io.aeron.cluster.client.AeronCluster.Configuration#EGRESS_CHANNEL_PROP_NAME\n         */\n        public String egressChannel()\n        {\n            return egressChannel;\n        }\n\n        /**\n         * Set whether IPC ingress is allowed or not.\n         *\n         * @param isIpcIngressAllowed or not.\n         * @return this for a fluent API\n         * @see Configuration#CLUSTER_INGRESS_IPC_ALLOWED_PROP_NAME\n         */\n        public Context isIpcIngressAllowed(final boolean isIpcIngressAllowed)\n        {\n            this.isIpcIngressAllowed = isIpcIngressAllowed;\n            return this;\n        }\n\n        /**\n         * Get whether IPC ingress is allowed or not.\n         *\n         * @return whether IPC ingress is allowed or not.\n         * @see Configuration#CLUSTER_INGRESS_IPC_ALLOWED_PROP_NAME\n         */\n        @Config(id = \"CLUSTER_INGRESS_IPC_ALLOWED\")\n        public boolean isIpcIngressAllowed()\n        {\n            return isIpcIngressAllowed;\n        }\n\n        /**\n         * Set the channel parameter for the cluster log channel.\n         *\n         * @param channel parameter for the cluster log channel.\n         * @return this for a fluent API.\n         * @see Configuration#LOG_CHANNEL_PROP_NAME\n         */\n        public Context logChannel(final String channel)\n        {\n            logChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the cluster log channel.\n         *\n         * @return the channel parameter for the cluster channel.\n         * @see Configuration#LOG_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String logChannel()\n        {\n            return logChannel;\n        }\n\n        /**\n         * Set the stream id for the cluster log channel.\n         *\n         * @param streamId for the cluster log channel.\n         * @return this for a fluent API\n         * @see Configuration#LOG_STREAM_ID_PROP_NAME\n         */\n        public Context logStreamId(final int streamId)\n        {\n            logStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the cluster log channel.\n         *\n         * @return the stream id for the cluster log channel.\n         * @see Configuration#LOG_STREAM_ID_PROP_NAME\n         */\n        public int logStreamId()\n        {\n            return logStreamId;\n        }\n\n        /**\n         * Set the endpoints for this cluster node.\n         *\n         * @param endpoints for the cluster node.\n         * @return this for a fluent API.\n         * @see Configuration#MEMBER_ENDPOINTS_PROP_NAME\n         */\n        public Context memberEndpoints(final String endpoints)\n        {\n            memberEndpoints = endpoints;\n            return this;\n        }\n\n        /**\n         * Get the endpoints for this cluster node.\n         *\n         * @return the endpoints for the cluster node.\n         * @see Configuration#MEMBER_ENDPOINTS_PROP_NAME\n         */\n        @Config\n        public String memberEndpoints()\n        {\n            return memberEndpoints;\n        }\n\n        /**\n         * Set the channel parameter for the cluster log and snapshot replay channel.\n         *\n         * @param channel parameter for the cluster log replay channel.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#REPLAY_CHANNEL_PROP_NAME\n         */\n        public Context replayChannel(final String channel)\n        {\n            replayChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the cluster log and snapshot replay channel.\n         *\n         * @return the channel parameter for the cluster replay channel.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#REPLAY_CHANNEL_PROP_NAME\n         */\n        public String replayChannel()\n        {\n            return replayChannel;\n        }\n\n        /**\n         * Set the stream id for the cluster log and snapshot replay channel.\n         *\n         * @param streamId for the cluster log replay channel.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#REPLAY_STREAM_ID_PROP_NAME\n         */\n        public Context replayStreamId(final int streamId)\n        {\n            replayStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the cluster log and snapshot replay channel.\n         *\n         * @return the stream id for the cluster log replay channel.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#REPLAY_STREAM_ID_PROP_NAME\n         */\n        public int replayStreamId()\n        {\n            return replayStreamId;\n        }\n\n        /**\n         * Set the channel parameter for bidirectional communications between the consensus module and services.\n         *\n         * @param channel parameter for bidirectional communications between the consensus module and services.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CONTROL_CHANNEL_PROP_NAME\n         */\n        public Context controlChannel(final String channel)\n        {\n            controlChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for bidirectional communications between the consensus module and services.\n         *\n         * @return the channel parameter for bidirectional communications between the consensus module and services.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CONTROL_CHANNEL_PROP_NAME\n         */\n        public String controlChannel()\n        {\n            return controlChannel;\n        }\n\n        /**\n         * Set the stream id for communications from the consensus module and to the services.\n         *\n         * @param streamId for communications from the consensus module and to the services.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_STREAM_ID_PROP_NAME\n         */\n        public Context serviceStreamId(final int streamId)\n        {\n            serviceStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for communications from the consensus module and to the services.\n         *\n         * @return the stream id for communications from the consensus module and to the services.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_STREAM_ID_PROP_NAME\n         */\n        public int serviceStreamId()\n        {\n            return serviceStreamId;\n        }\n\n        /**\n         * Set the stream id for communications from the services to the consensus module.\n         *\n         * @param streamId for communications from the services to the consensus module.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CONSENSUS_MODULE_STREAM_ID_PROP_NAME\n         */\n        public Context consensusModuleStreamId(final int streamId)\n        {\n            consensusModuleStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for communications from the services to the consensus module.\n         *\n         * @return the stream id for communications from the services to the consensus module.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#CONSENSUS_MODULE_STREAM_ID_PROP_NAME\n         */\n        public int consensusModuleStreamId()\n        {\n            return consensusModuleStreamId;\n        }\n\n        /**\n         * Set the channel parameter for snapshot recordings.\n         *\n         * @param channel parameter for snapshot recordings\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SNAPSHOT_CHANNEL_PROP_NAME\n         */\n        public Context snapshotChannel(final String channel)\n        {\n            snapshotChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for snapshot recordings.\n         *\n         * @return the channel parameter for snapshot recordings.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SNAPSHOT_CHANNEL_PROP_NAME\n         */\n        public String snapshotChannel()\n        {\n            return snapshotChannel;\n        }\n\n        /**\n         * Set the stream id for snapshot recordings.\n         *\n         * @param streamId for snapshot recordings.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SNAPSHOT_STREAM_ID_PROP_NAME\n         */\n        public Context snapshotStreamId(final int streamId)\n        {\n            snapshotStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for snapshot recordings.\n         *\n         * @return the stream id for snapshot recordings.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SNAPSHOT_STREAM_ID_PROP_NAME\n         */\n        public int snapshotStreamId()\n        {\n            return snapshotStreamId;\n        }\n\n        /**\n         * Set the channel parameter for the consensus communication channel.\n         *\n         * @param channel parameter for the consensus communication channel.\n         * @return this for a fluent API.\n         * @see Configuration#CONSENSUS_CHANNEL_PROP_NAME\n         */\n        public Context consensusChannel(final String channel)\n        {\n            consensusChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the consensus communication channel.\n         *\n         * @return the channel parameter for the consensus communication channel.\n         * @see Configuration#CONSENSUS_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String consensusChannel()\n        {\n            return consensusChannel;\n        }\n\n        /**\n         * Set the stream id for the consensus channel.\n         *\n         * @param streamId for the consensus channel.\n         * @return this for a fluent API\n         * @see Configuration#CONSENSUS_STREAM_ID_PROP_NAME\n         */\n        public Context consensusStreamId(final int streamId)\n        {\n            consensusStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the consensus channel.\n         *\n         * @return the stream id for the consensus channel.\n         * @see Configuration#CONSENSUS_STREAM_ID_PROP_NAME\n         */\n        @Config\n        public int consensusStreamId()\n        {\n            return consensusStreamId;\n        }\n\n        /**\n         * Set the channel parameter for the replication communication channel. This is channel that the local\n         * will send to src archives to replay their recordings back to. It should contain an endpoint that other\n         * members in the cluster will be able to reach. Using port 0 for the endpoint is valid for this channel to\n         * simplify port allocation.\n         *\n         * @param channel replication communication channel to be used by the consensus module.\n         * @return this for a fluent API\n         * @see Configuration#REPLICATION_CHANNEL_PROP_NAME\n         */\n        public Context replicationChannel(final String channel)\n        {\n            replicationChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the replication channel for logs and snapshots.\n         *\n         * @return channel to receive replication responses from other node's archives when using log and snapshot\n         * replication  to catch up.\n         * @see Configuration#REPLICATION_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String replicationChannel()\n        {\n            return replicationChannel;\n        }\n\n        /**\n         * Set a channel template used for replaying logs to a follower using the\n         * {@link ClusterMember#catchupEndpoint()}.\n         *\n         * @param channel to do a catch replay to a follower.\n         * @return this for a fluent API\n         * @see Configuration#FOLLOWER_CATCHUP_CHANNEL_PROP_NAME\n         */\n        public Context followerCatchupChannel(final String channel)\n        {\n            this.followerCatchupChannel = channel;\n            return this;\n        }\n\n        /**\n         * Gets the channel template used for replaying logs to a follower using the\n         * {@link ClusterMember#catchupEndpoint()}.\n         *\n         * @return channel used for replaying older data during a catchup phase.\n         * @see Configuration#FOLLOWER_CATCHUP_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String followerCatchupChannel()\n        {\n            return followerCatchupChannel;\n        }\n\n        /**\n         * Set a channel template used to build the control request channel for the leader Archive using the\n         * {@link ClusterMember#archiveEndpoint()}.\n         *\n         * @param channel for the Archive control requests.\n         * @return this for a fluent API\n         * @see Configuration#LEADER_ARCHIVE_CONTROL_CHANNEL_PROP_NAME\n         */\n        public Context leaderArchiveControlChannel(final String channel)\n        {\n            this.leaderArchiveControlChannel = channel;\n            return this;\n        }\n\n        /**\n         * Gets the channel template used to build the control request channel for the leader Archive using the\n         * {@link ClusterMember#archiveEndpoint()}.\n         *\n         * @return channel used for replaying older data during a catchup phase.\n         * @see Configuration#LEADER_ARCHIVE_CONTROL_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String leaderArchiveControlChannel()\n        {\n            return leaderArchiveControlChannel;\n        }\n\n        /**\n         * Set the fragment limit to be used when polling the log {@link Subscription}.\n         *\n         * @param logFragmentLimit for this clustered service.\n         * @return this for a fluent API\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#LOG_FRAGMENT_LIMIT_DEFAULT\n         */\n        public Context logFragmentLimit(final int logFragmentLimit)\n        {\n            this.logFragmentLimit = logFragmentLimit;\n            return this;\n        }\n\n        /**\n         * Get the fragment limit to be used when polling the log {@link Subscription}.\n         *\n         * @return the fragment limit to be used when polling the log {@link Subscription}.\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#LOG_FRAGMENT_LIMIT_PROP_NAME\n         */\n        public int logFragmentLimit()\n        {\n            return logFragmentLimit;\n        }\n\n        /**\n         * Resolution in nanoseconds for each tick of the timer wheel for scheduling deadlines.\n         *\n         * @param wheelTickResolutionNs the resolution in nanoseconds of each tick on the timer wheel.\n         * @return this for a fluent API\n         * @see Configuration#WHEEL_TICK_RESOLUTION_PROP_NAME\n         */\n        public Context wheelTickResolutionNs(final long wheelTickResolutionNs)\n        {\n            this.wheelTickResolutionNs = wheelTickResolutionNs;\n            return this;\n        }\n\n        /**\n         * Resolution in nanoseconds for each tick of the timer wheel for scheduling deadlines.\n         *\n         * @return the resolution in nanoseconds for each tick on the timer wheel.\n         * @see Configuration#WHEEL_TICK_RESOLUTION_PROP_NAME\n         */\n        @Config\n        public long wheelTickResolutionNs()\n        {\n            return wheelTickResolutionNs;\n        }\n\n        /**\n         * Number of ticks, or spokes, on the timer wheel. Higher number of ticks reduces potential conflicts\n         * traded off against memory usage.\n         *\n         * @param ticksPerWheel the number of ticks on the timer wheel.\n         * @return this for a fluent API\n         * @see Configuration#TICKS_PER_WHEEL_PROP_NAME\n         */\n        public Context ticksPerWheel(final int ticksPerWheel)\n        {\n            this.ticksPerWheel = ticksPerWheel;\n            return this;\n        }\n\n        /**\n         * Number of ticks, or spokes, on the timer wheel. Higher number of ticks reduces potential conflicts\n         * traded off against memory usage.\n         *\n         * @return the number of ticks on the timer wheel.\n         * @see Configuration#TICKS_PER_WHEEL_PROP_NAME\n         */\n        @Config\n        public int ticksPerWheel()\n        {\n            return ticksPerWheel;\n        }\n\n        /**\n         * Set the number of clustered services in this cluster instance.\n         *\n         * @param serviceCount the number of clustered services in this cluster instance.\n         * @return this for a fluent API\n         * @see Configuration#SERVICE_COUNT_PROP_NAME\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_ID_PROP_NAME\n         */\n        public Context serviceCount(final int serviceCount)\n        {\n            this.serviceCount = serviceCount;\n            return this;\n        }\n\n        /**\n         * Get the number of clustered services in this cluster instance.\n         *\n         * @return the number of clustered services in this cluster instance.\n         * @see Configuration#SERVICE_COUNT_PROP_NAME\n         * @see io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_ID_PROP_NAME\n         */\n        @Config\n        public int serviceCount()\n        {\n            return serviceCount;\n        }\n\n        /**\n         * Set the delay before recording a standby snapshot notification.\n         * The delay starts after the commit position passes the snapshot's log position.\n         *\n         * @param standbySnapshotNotificationProcessingDelayNs the delay, in nanoseconds.\n         * @return this for a fluent API\n         */\n        public Context standbySnapshotNotificationProcessingDelayNs(\n            final long standbySnapshotNotificationProcessingDelayNs)\n        {\n            this.standbySnapshotNotificationProcessingDelayNs = standbySnapshotNotificationProcessingDelayNs;\n            return this;\n        }\n\n        /**\n         * Get the delay before recording a standby snapshot notification.\n         * The delay starts after the commit position passes the snapshot's log position.\n         *\n         * @return the delay, in nanoseconds.\n         */\n        @Config\n        public long standbySnapshotNotificationProcessingDelayNs()\n        {\n            return standbySnapshotNotificationProcessingDelayNs;\n        }\n\n        /**\n         * Set the limit for the maximum number of concurrent cluster sessions.\n         *\n         * @param maxSessions after which new sessions will be rejected.\n         * @return this for a fluent API\n         * @see Configuration#MAX_CONCURRENT_SESSIONS_PROP_NAME\n         */\n        public Context maxConcurrentSessions(final int maxSessions)\n        {\n            this.maxConcurrentSessions = maxSessions;\n            return this;\n        }\n\n        /**\n         * Get the limit for the maximum number of concurrent cluster sessions.\n         *\n         * @return the limit for the maximum number of concurrent cluster sessions.\n         * @see Configuration#MAX_CONCURRENT_SESSIONS_PROP_NAME\n         */\n        @Config\n        public int maxConcurrentSessions()\n        {\n            return maxConcurrentSessions;\n        }\n\n        /**\n         * Timeout for a session if no activity is observed.\n         *\n         * @param sessionTimeoutNs to wait for activity on a session.\n         * @return this for a fluent API.\n         * @see Configuration#SESSION_TIMEOUT_PROP_NAME\n         */\n        public Context sessionTimeoutNs(final long sessionTimeoutNs)\n        {\n            this.sessionTimeoutNs = sessionTimeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout for a session if no activity is observed.\n         *\n         * @return the timeout for a session if no activity is observed.\n         * @see Configuration#SESSION_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long sessionTimeoutNs()\n        {\n            return CommonContext.checkDebugTimeout(sessionTimeoutNs, TimeUnit.NANOSECONDS);\n        }\n\n        /**\n         * Timeout for a leader if no heartbeat is received by another member.\n         *\n         * @param heartbeatTimeoutNs to wait for heartbeat from a leader.\n         * @return this for a fluent API.\n         * @see Configuration#LEADER_HEARTBEAT_TIMEOUT_PROP_NAME\n         */\n        public Context leaderHeartbeatTimeoutNs(final long heartbeatTimeoutNs)\n        {\n            this.leaderHeartbeatTimeoutNs = heartbeatTimeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout for a leader if no heartbeat is received by another member.\n         *\n         * @return the timeout for a leader if no heartbeat is received by another member.\n         * @see Configuration#LEADER_HEARTBEAT_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long leaderHeartbeatTimeoutNs()\n        {\n            return leaderHeartbeatTimeoutNs;\n        }\n\n        /**\n         * Interval at which a leader will send heartbeats if the log is not progressing.\n         *\n         * @param heartbeatIntervalNs between leader heartbeats.\n         * @return this for a fluent API.\n         * @see Configuration#LEADER_HEARTBEAT_INTERVAL_PROP_NAME\n         */\n        @Config\n        public Context leaderHeartbeatIntervalNs(final long heartbeatIntervalNs)\n        {\n            this.leaderHeartbeatIntervalNs = heartbeatIntervalNs;\n            return this;\n        }\n\n        /**\n         * Interval at which a leader will send heartbeats if the log is not progressing.\n         *\n         * @return the interval at which a leader will send heartbeats if the log is not progressing.\n         * @see Configuration#LEADER_HEARTBEAT_INTERVAL_PROP_NAME\n         */\n        public long leaderHeartbeatIntervalNs()\n        {\n            return leaderHeartbeatIntervalNs;\n        }\n\n        /**\n         * Timeout to wait for hearing the status of all cluster members on startup after recovery before commencing\n         * an election if a majority of members has been heard from.\n         *\n         * @param timeoutNs to wait on startup after recovery before commencing an election.\n         * @return this for a fluent API.\n         * @see Configuration#STARTUP_CANVASS_TIMEOUT_PROP_NAME\n         */\n        public Context startupCanvassTimeoutNs(final long timeoutNs)\n        {\n            this.startupCanvassTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout to wait for hearing the status of all cluster members on startup after recovery before commencing\n         * an election if a majority of members has been heard from.\n         *\n         * @return the timeout to wait on startup after recovery before commencing an election.\n         * @see Configuration#STARTUP_CANVASS_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long startupCanvassTimeoutNs()\n        {\n            return startupCanvassTimeoutNs;\n        }\n\n        /**\n         * Timeout to wait for votes in an election before declaring the election void and starting over.\n         *\n         * @param timeoutNs to wait for votes in an elections.\n         * @return this for a fluent API.\n         * @see Configuration#ELECTION_TIMEOUT_PROP_NAME\n         */\n        public Context electionTimeoutNs(final long timeoutNs)\n        {\n            this.electionTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout to wait for votes in an election before declaring the election void and starting over.\n         *\n         * @return the timeout to wait for votes in an elections.\n         * @see Configuration#ELECTION_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long electionTimeoutNs()\n        {\n            return electionTimeoutNs;\n        }\n\n        /**\n         * Interval at which a member will send out status messages during the election phases.\n         *\n         * @param electionStatusIntervalNs between status message updates.\n         * @return this for a fluent API.\n         * @see Configuration#ELECTION_STATUS_INTERVAL_PROP_NAME\n         * @see Configuration#ELECTION_STATUS_INTERVAL_DEFAULT_NS\n         */\n        public Context electionStatusIntervalNs(final long electionStatusIntervalNs)\n        {\n            this.electionStatusIntervalNs = electionStatusIntervalNs;\n            return this;\n        }\n\n        /**\n         * Interval at which a member will send out status messages during the election phases.\n         *\n         * @return the interval at which a member will send out status messages during the election phases.\n         * @see Configuration#ELECTION_STATUS_INTERVAL_PROP_NAME\n         * @see Configuration#ELECTION_STATUS_INTERVAL_DEFAULT_NS\n         */\n        @Config\n        public long electionStatusIntervalNs()\n        {\n            return electionStatusIntervalNs;\n        }\n\n        /**\n         * Timeout to wait for follower termination by leader.\n         *\n         * @param terminationTimeoutNs to wait for follower termination.\n         * @return this for a fluent API.\n         * @see Configuration#TERMINATION_TIMEOUT_PROP_NAME\n         * @see Configuration#TERMINATION_TIMEOUT_DEFAULT_NS\n         */\n        public Context terminationTimeoutNs(final long terminationTimeoutNs)\n        {\n            this.terminationTimeoutNs = terminationTimeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout to wait for follower termination by leader.\n         *\n         * @return timeout to wait for follower termination by leader.\n         * @see Configuration#TERMINATION_TIMEOUT_PROP_NAME\n         * @see Configuration#TERMINATION_TIMEOUT_DEFAULT_NS\n         */\n        @Config\n        public long terminationTimeoutNs()\n        {\n            return terminationTimeoutNs;\n        }\n\n        /**\n         * Set a threshold for the consensus module agent work cycle time which when exceed it will increment the\n         * counter.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see Configuration#CYCLE_THRESHOLD_PROP_NAME\n         * @see Configuration#CYCLE_THRESHOLD_DEFAULT_NS\n         */\n        public Context cycleThresholdNs(final long thresholdNs)\n        {\n            this.cycleThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for the consensus module agent work cycle time which when exceed it will increment the\n         * counter.\n         *\n         * @return threshold to track for the consensus module agent work cycle time.\n         */\n        @Config\n        public long cycleThresholdNs()\n        {\n            return cycleThresholdNs;\n        }\n\n        /**\n         * Set a duty cycle tracker to be used for tracking the duty cycle time of the consensus module agent.\n         *\n         * @param dutyCycleTracker to use for tracking.\n         * @return this for fluent API.\n         */\n        public Context dutyCycleTracker(final DutyCycleTracker dutyCycleTracker)\n        {\n            this.dutyCycleTracker = dutyCycleTracker;\n            return this;\n        }\n\n        /**\n         * The duty cycle tracker used to track the consensus module agent duty cycle.\n         *\n         * @return the duty cycle tracker.\n         */\n        public DutyCycleTracker dutyCycleTracker()\n        {\n            return dutyCycleTracker;\n        }\n\n        /**\n         * Set a threshold for total snapshot duration which when exceeded will result in a counter increment.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see ConsensusModule.Configuration#TOTAL_SNAPSHOT_DURATION_THRESHOLD_PROP_NAME\n         * @see ConsensusModule.Configuration#TOTAL_SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS\n         */\n        public Context totalSnapshotDurationThresholdNs(final long thresholdNs)\n        {\n            this.totalSnapshotDurationThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for total snapshot duration which when exceeded it will increment the counter.\n         *\n         * @return threshold value in nanoseconds.\n         */\n        @Config\n        public long totalSnapshotDurationThresholdNs()\n        {\n            return totalSnapshotDurationThresholdNs;\n        }\n\n        /**\n         * Set snapshot duration tracker used for monitoring total snapshot duration.\n         *\n         * @param snapshotDurationTracker snapshot duration tracker.\n         * @return this for fluent API.\n         */\n        public Context totalSnapshotDurationTracker(final SnapshotDurationTracker snapshotDurationTracker)\n        {\n            this.totalSnapshotDurationTracker = snapshotDurationTracker;\n            return this;\n        }\n\n        /**\n         * Get snapshot duration tracker used for monitoring total snapshot duration.\n         *\n         * @return snapshot duration tracker\n         */\n        public SnapshotDurationTracker totalSnapshotDurationTracker()\n        {\n            return totalSnapshotDurationTracker;\n        }\n\n        /**\n         * Get the {@link Agent#roleName()} to be used for the consensus module agent. If {@code null} then one will\n         * be generated.\n         *\n         * @return the {@link Agent#roleName()} to be used for the consensus module agent.\n         */\n        @Config(id = \"CLUSTER_CONSENSUS_MODULE_AGENT_ROLE_NAME\")\n        public String agentRoleName()\n        {\n            return agentRoleName;\n        }\n\n        /**\n         * Set the {@link Agent#roleName()} to be used for the consensus module agent.\n         *\n         * @param agentRoleName to be used for the consensus module agent.\n         * @return this for a fluent API.\n         */\n        public Context agentRoleName(final String agentRoleName)\n        {\n            this.agentRoleName = agentRoleName;\n            return this;\n        }\n\n        /**\n         * Get the thread factory used for creating threads.\n         *\n         * @return thread factory used for creating threads.\n         */\n        public ThreadFactory threadFactory()\n        {\n            return threadFactory;\n        }\n\n        /**\n         * Set the thread factory used for creating threads.\n         *\n         * @param threadFactory used for creating threads\n         * @return this for a fluent API.\n         */\n        public Context threadFactory(final ThreadFactory threadFactory)\n        {\n            this.threadFactory = threadFactory;\n            return this;\n        }\n\n        /**\n         * Provides an {@link IdleStrategy} supplier for the idle strategy for the agent duty cycle.\n         *\n         * @param idleStrategySupplier supplier for the idle strategy for the agent duty cycle.\n         * @return this for a fluent API.\n         */\n        public Context idleStrategySupplier(final Supplier<IdleStrategy> idleStrategySupplier)\n        {\n            this.idleStrategySupplier = idleStrategySupplier;\n            return this;\n        }\n\n        /**\n         * Get a new {@link IdleStrategy} based on configured supplier.\n         *\n         * @return a new {@link IdleStrategy} based on configured supplier.\n         */\n        public IdleStrategy idleStrategy()\n        {\n            return idleStrategySupplier.get();\n        }\n\n        /**\n         * Set the {@link ClusterClock} to be used for timestamping messages and timers.\n         *\n         * @param clock {@link ClusterClock} to be used for timestamping messages and timers.\n         * @return this for a fluent API.\n         */\n        public Context clusterClock(final ClusterClock clock)\n        {\n            this.clusterClock = clock;\n            return this;\n        }\n\n        /**\n         * Get the {@link ClusterClock} to used for timestamping messages and timers.\n         *\n         * @return the {@link ClusterClock} to used for timestamping messages and timers.\n         */\n        @Config\n        public ClusterClock clusterClock()\n        {\n            return clusterClock;\n        }\n\n        /**\n         * Set the {@link EpochClock} to be used for tracking wall clock time.\n         *\n         * @param clock {@link EpochClock} to be used for tracking wall clock time.\n         * @return this for a fluent API.\n         */\n        public Context epochClock(final EpochClock clock)\n        {\n            this.epochClock = clock;\n            return this;\n        }\n\n        /**\n         * Get the {@link EpochClock} to used for tracking wall clock time.\n         *\n         * @return the {@link EpochClock} to used for tracking wall clock time.\n         */\n        public EpochClock epochClock()\n        {\n            return epochClock;\n        }\n\n        /**\n         * Set the supplier of a consumer of timestamps which can be used for testing time progress in a cluster. The\n         * timestamp passed to the consumer is the timestamp of the last completed {@link Agent#doWork()} cycle by the\n         * consensus module {@link Agent}. The supplier will be called after the context is concluded and can be\n         * referenced during the construction of the consumer.\n         *\n         * @param clusterTimeConsumerSupplier to which the latest timestamp will be passed.\n         * @return this for a fluent API\n         */\n        public Context clusterTimeConsumerSupplier(final Function<Context, LongConsumer> clusterTimeConsumerSupplier)\n        {\n            this.clusterTimeConsumerSupplier = clusterTimeConsumerSupplier;\n            return this;\n        }\n\n        /**\n         * Get the supplier of a consumer of timestamps which can be used for testing time progress in a cluster. The\n         * timestamp passed to the consumer is the timestamp of the last completed {@link Agent#doWork()} cycle by the\n         * consensus module {@link Agent}.\n         *\n         * @return the consumer of timestamps for completed work cycles.\n         */\n        public Function<Context, LongConsumer> clusterTimeConsumerSupplier()\n        {\n            return clusterTimeConsumerSupplier;\n        }\n\n        /**\n         * Get the {@link ErrorHandler} to be used by the Consensus Module.\n         *\n         * @return the {@link ErrorHandler} to be used by the Consensus Module.\n         */\n        public ErrorHandler errorHandler()\n        {\n            return errorHandler;\n        }\n\n        /**\n         * Set the {@link ErrorHandler} to be used by the Consensus Module.\n         *\n         * @param errorHandler the error handler to be used by the Consensus Module.\n         * @return this for a fluent API\n         */\n        public Context errorHandler(final ErrorHandler errorHandler)\n        {\n            this.errorHandler = errorHandler;\n            return this;\n        }\n\n        /**\n         * The {@link #errorHandler()} that will increment {@link #errorCounter()} by default.\n         *\n         * @param countedErrorHandler to override the default.\n         * @return this for a fluent API.\n         */\n        public Context countedErrorHandler(final CountedErrorHandler countedErrorHandler)\n        {\n            this.countedErrorHandler = countedErrorHandler;\n            return this;\n        }\n\n        /**\n         * The {@link #errorHandler()} that will increment {@link #errorCounter()} by default.\n         *\n         * @return {@link #errorHandler()} that will increment {@link #errorCounter()} by default.\n         */\n        public CountedErrorHandler countedErrorHandler()\n        {\n            return countedErrorHandler;\n        }\n\n        /**\n         * Get the error counter that will record the number of errors observed.\n         *\n         * @return the error counter that will record the number of errors observed.\n         */\n        public AtomicCounter errorCounter()\n        {\n            return errorCounter;\n        }\n\n        /**\n         * Set the error counter that will record the number of errors observed.\n         *\n         * @param errorCounter the error counter that will record the number of errors observed.\n         * @return this for a fluent API.\n         */\n        public Context errorCounter(final AtomicCounter errorCounter)\n        {\n            this.errorCounter = errorCounter;\n            return this;\n        }\n\n        /**\n         * Get the counter for the current state of the consensus module.\n         *\n         * @return the counter for the current state of the consensus module.\n         * @see ConsensusModule.State\n         */\n        public Counter moduleStateCounter()\n        {\n            return moduleStateCounter;\n        }\n\n        /**\n         * Set the counter for the current state of the consensus module.\n         *\n         * @param moduleState the counter for the current state of the consensus module.\n         * @return this for a fluent API.\n         * @see ConsensusModule.State\n         */\n        public Context moduleStateCounter(final Counter moduleState)\n        {\n            this.moduleStateCounter = moduleState;\n            return this;\n        }\n\n        /**\n         * Get the counter for the current state of an election.\n         *\n         * @return the counter for the current state of an election.\n         * @see ElectionState\n         */\n        public Counter electionStateCounter()\n        {\n            return electionStateCounter;\n        }\n\n        /**\n         * Set the counter for the current state of an election.\n         *\n         * @param electionStateCounter for the current state of an election.\n         * @return this for a fluent API.\n         * @see ElectionState\n         */\n        public Context electionStateCounter(final Counter electionStateCounter)\n        {\n            this.electionStateCounter = electionStateCounter;\n            return this;\n        }\n\n        /**\n         * Get the counter for the commit position the cluster has reached for consensus.\n         *\n         * @return the counter for the commit position the cluster has reached for consensus.\n         */\n        public Counter commitPositionCounter()\n        {\n            return commitPosition;\n        }\n\n        /**\n         * Set the counter for the commit position the cluster has reached for consensus.\n         *\n         * @param commitPosition counter for the commit position the cluster has reached for consensus.\n         * @return this for a fluent API.\n         */\n        public Context commitPositionCounter(final Counter commitPosition)\n        {\n            this.commitPosition = commitPosition;\n            return this;\n        }\n\n        /**\n         * Get the counter for representing the current {@link io.aeron.cluster.service.Cluster.Role} of the\n         * consensus module node.\n         *\n         * @return the counter for representing the current {@link io.aeron.cluster.service.Cluster.Role} of the\n         * cluster node.\n         * @see io.aeron.cluster.service.Cluster.Role\n         */\n        public Counter clusterNodeRoleCounter()\n        {\n            return clusterNodeRoleCounter;\n        }\n\n        /**\n         * Set the counter for representing the current {@link io.aeron.cluster.service.Cluster.Role} of the\n         * cluster node.\n         *\n         * @param nodeRole the counter for representing the current {@link io.aeron.cluster.service.Cluster.Role}\n         *                 of the cluster node.\n         * @return this for a fluent API.\n         * @see io.aeron.cluster.service.Cluster.Role\n         */\n        public Context clusterNodeRoleCounter(final Counter nodeRole)\n        {\n            this.clusterNodeRoleCounter = nodeRole;\n            return this;\n        }\n\n        /**\n         * Get the counter for the control toggle for triggering actions for the cluster.\n         *\n         * @return the counter for triggering cluster node actions.\n         * @see ClusterControl\n         */\n        public Counter controlToggleCounter()\n        {\n            return clusterControlToggle;\n        }\n\n        /**\n         * Set the counter for the control toggle for triggering actions for the cluster.\n         *\n         * @param controlToggle the counter for triggering cluster node actions.\n         * @return this for a fluent API.\n         * @see ClusterControl\n         */\n        public Context controlToggleCounter(final Counter controlToggle)\n        {\n            this.clusterControlToggle = controlToggle;\n            return this;\n        }\n\n        /**\n         * Get the counter for the control toggle for triggering actions on the cluster node.\n         *\n         * @return the counter for triggering cluster node actions.\n         * @see ClusterControl\n         */\n        public Counter nodeControlToggleCounter()\n        {\n            return nodeControlToggle;\n        }\n\n        /**\n         * Set the counter for the control toggle for triggering actions on the cluster node.\n         *\n         * @param nodeControlToggle the counter for triggering cluster node actions.\n         * @return this for a fluent API.\n         * @see ClusterControl\n         */\n        public Context nodeControlToggleCounter(final Counter nodeControlToggle)\n        {\n            this.nodeControlToggle = nodeControlToggle;\n            return this;\n        }\n\n        /**\n         * Get the counter for the count of snapshots taken.\n         *\n         * @return the counter for the count of snapshots taken.\n         */\n        public Counter snapshotCounter()\n        {\n            return snapshotCounter;\n        }\n\n        /**\n         * Set the counter for the count of snapshots taken.\n         *\n         * @param snapshotCounter the count of snapshots taken.\n         * @return this for a fluent API.\n         */\n        public Context snapshotCounter(final Counter snapshotCounter)\n        {\n            this.snapshotCounter = snapshotCounter;\n            return this;\n        }\n\n        /**\n         * Get the counter for the count of clients that have been timed out and disconnected.\n         *\n         * @return the counter for the count of clients that have been timed out and disconnected.\n         */\n        public Counter timedOutClientCounter()\n        {\n            return timedOutClientCounter;\n        }\n\n        /**\n         * Set the counter for the count of clients that have been timed out and disconnected.\n         *\n         * @param timedOutClientCounter the count of clients that have been timed out and disconnected.\n         * @return this for a fluent API.\n         */\n        public Context timedOutClientCounter(final Counter timedOutClientCounter)\n        {\n            this.timedOutClientCounter = timedOutClientCounter;\n            return this;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * This client will be closed when the {@link ConsensusModule#close()} or {@link #close()} methods are called\n         * if {@link #ownsAeronClient()} is true.\n         * <p>\n         * The method is mostly here in order to test with a custom Aeron instance. The recommended approach is to set\n         * the aeronDirectoryName and allow the ConsensusModule to construct the Aeron client. The supplied Aeron\n         * client must be set to {@code Aeron.Context.useConductorInvoker(true)} to work correctly with the\n         * ConsensusModule.\n         *\n         * @param aeron client for communicating with the local Media Driver.\n         * @return this for a fluent API.\n         * @see io.aeron.Aeron#connect()\n         * @see io.aeron.Aeron.Context#useConductorAgentInvoker(boolean)\n         * @see #aeronDirectoryName(String)\n         */\n        public Context aeron(final Aeron aeron)\n        {\n            this.aeron = aeron;\n            return this;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * If not provided then a default will be established during {@link #conclude()} by calling\n         * {@link Aeron#connect()}.\n         *\n         * @return client for communicating with the local Media Driver.\n         */\n        public Aeron aeron()\n        {\n            return aeron;\n        }\n\n        /**\n         * Set the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @param aeronDirectoryName the top level Aeron directory.\n         * @return this for a fluent API.\n         */\n        public Context aeronDirectoryName(final String aeronDirectoryName)\n        {\n            this.aeronDirectoryName = aeronDirectoryName;\n            return this;\n        }\n\n        /**\n         * Get the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @return The top level Aeron directory.\n         */\n        public String aeronDirectoryName()\n        {\n            return aeronDirectoryName;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @param ownsAeronClient does this context own the {@link #aeron()} client.\n         * @return this for a fluent API.\n         */\n        public Context ownsAeronClient(final boolean ownsAeronClient)\n        {\n            this.ownsAeronClient = ownsAeronClient;\n            return this;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @return does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         */\n        public boolean ownsAeronClient()\n        {\n            return ownsAeronClient;\n        }\n\n        /**\n         * Set the {@link io.aeron.archive.client.AeronArchive.Context} that should be used for communicating with the\n         * local Archive.\n         *\n         * @param archiveContext that should be used for communicating with the local Archive.\n         * @return this for a fluent API.\n         */\n        public Context archiveContext(final AeronArchive.Context archiveContext)\n        {\n            this.archiveContext = archiveContext;\n            return this;\n        }\n\n        /**\n         * Get the {@link io.aeron.archive.client.AeronArchive.Context} that should be used for communicating with\n         * the local Archive.\n         *\n         * @return the {@link io.aeron.archive.client.AeronArchive.Context} that should be used for communicating\n         * with the local Archive.\n         */\n        public AeronArchive.Context archiveContext()\n        {\n            return archiveContext;\n        }\n\n        /**\n         * Get the {@link AuthenticatorSupplier} that should be used for the consensus module.\n         *\n         * @return the {@link AuthenticatorSupplier} to be used for the consensus module.\n         */\n        @Config\n        public AuthenticatorSupplier authenticatorSupplier()\n        {\n            return authenticatorSupplier;\n        }\n\n        /**\n         * Set the {@link AuthenticatorSupplier} that will be used for the consensus module.\n         *\n         * @param authenticatorSupplier {@link AuthenticatorSupplier} to use for the consensus module.\n         * @return this for a fluent API.\n         */\n        public Context authenticatorSupplier(final AuthenticatorSupplier authenticatorSupplier)\n        {\n            this.authenticatorSupplier = authenticatorSupplier;\n            return this;\n        }\n\n        /**\n         * Get the {@link AuthorisationServiceSupplier} that should be used for the consensus module.\n         *\n         * @return the {@link AuthorisationServiceSupplier} to be used for the consensus module.\n         */\n        @Config\n        public AuthorisationServiceSupplier authorisationServiceSupplier()\n        {\n            return authorisationServiceSupplier;\n        }\n\n        /**\n         * <p>Set the {@link AuthorisationServiceSupplier} that will be used for the consensus module.</p>\n         *\n         * <p>When using an authorisation service for the ConsensusModule, then the following values for protocolId,\n         * actionId, and type should be considered.</p>\n         * <table>\n         *     <caption>Parameters for authorisation service queries from the Consensus Module</caption>\n         *     <thead>\n         *         <tr><td>Description</td><td>protocolId</td><td>actionId</td><td>type(s)</td></tr>\n         *     </thead>\n         *     <tbody>\n         *         <tr>\n         *             <td>Admin requests made through the client API</td>\n         *             <td>{@link MessageHeaderDecoder#SCHEMA_ID}</td>\n         *             <td>{@link AdminRequestDecoder#TEMPLATE_ID}</td>\n         *             <td>{@link AdminRequestType#SNAPSHOT}</td>\n         *         </tr>\n         *         <tr>\n         *             <td>Backup queries from Cluster Backup &amp; Standby</td>\n         *             <td></td>\n         *             <td>{@link BackupQueryDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Heartbeat requests from Cluster Standby</td>\n         *             <td></td>\n         *             <td>{@link HeartbeatRequestDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *         <tr>\n         *             <td>Standby snapshot notifications from Cluster Standby</td>\n         *             <td></td>\n         *             <td>{@link StandbySnapshotDecoder#TEMPLATE_ID}</td>\n         *             <td><code>(null)</code></td>\n         *         </tr>\n         *     </tbody>\n         * </table>\n         *\n         * @param authorisationServiceSupplier {@link AuthorisationServiceSupplier} to use for the consensus module.\n         * @return this for a fluent API.\n         */\n        public Context authorisationServiceSupplier(final AuthorisationServiceSupplier authorisationServiceSupplier)\n        {\n            this.authorisationServiceSupplier = authorisationServiceSupplier;\n            return this;\n        }\n\n        /**\n         * Should an {@link AgentInvoker} be used for running the {@link ConsensusModule} rather than run it on\n         * a thread with a {@link AgentRunner}.\n         *\n         * @param useAgentInvoker use {@link AgentInvoker} be used for running the {@link ConsensusModule}?\n         * @return this for a fluent API.\n         */\n        public ConsensusModule.Context useAgentInvoker(final boolean useAgentInvoker)\n        {\n            this.useAgentInvoker = useAgentInvoker;\n            return this;\n        }\n\n        /**\n         * Should an {@link AgentInvoker} be used for running the {@link ConsensusModule} rather than run it on\n         * a thread with a {@link AgentRunner}.\n         *\n         * @return true if the {@link ConsensusModule} will be run with an {@link AgentInvoker} otherwise false.\n         */\n        public boolean useAgentInvoker()\n        {\n            return useAgentInvoker;\n        }\n\n        /**\n         * Set the {@link Runnable} that is called when the {@link ConsensusModule} processes a termination action.\n         *\n         * @param terminationHook that can be used to terminate a consensus module.\n         * @return this for a fluent API.\n         */\n        public Context terminationHook(final Runnable terminationHook)\n        {\n            this.terminationHook = terminationHook;\n            return this;\n        }\n\n        /**\n         * Get the {@link Runnable} that is called when the {@link ConsensusModule} processes a termination action.\n         *\n         * @return the {@link Runnable} that can be used to terminate a consensus module.\n         */\n        public Runnable terminationHook()\n        {\n            return terminationHook;\n        }\n\n        /**\n         * Set the {@link ClusterMarkFile} in use.\n         *\n         * @param markFile to use.\n         * @return this for a fluent API.\n         */\n        public Context clusterMarkFile(final ClusterMarkFile markFile)\n        {\n            this.markFile = markFile;\n            return this;\n        }\n\n        /**\n         * The {@link ClusterMarkFile} in use.\n         *\n         * @return {@link ClusterMarkFile} in use.\n         */\n        public ClusterMarkFile clusterMarkFile()\n        {\n            return markFile;\n        }\n\n        Context nodeStateFile(final NodeStateFile nodeStateFile)\n        {\n            this.nodeStateFile = nodeStateFile;\n            return this;\n        }\n\n        /**\n         * The {@link NodeStateFile} that is used by the consensus module.\n         *\n         * @return node state file.\n         */\n        public NodeStateFile nodeStateFile()\n        {\n            return nodeStateFile;\n        }\n\n        /**\n         * Set the error buffer length in bytes to use.\n         *\n         * @param errorBufferLength in bytes to use.\n         * @return this for a fluent API.\n         * @see Configuration#ERROR_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context errorBufferLength(final int errorBufferLength)\n        {\n            this.errorBufferLength = errorBufferLength;\n            return this;\n        }\n\n        /**\n         * The error buffer length in bytes.\n         *\n         * @return error buffer length in bytes.\n         * @see Configuration#ERROR_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config\n        public int errorBufferLength()\n        {\n            return errorBufferLength;\n        }\n\n        /**\n         * Set the {@link DistinctErrorLog} in use.\n         *\n         * @param errorLog to use.\n         * @return this for a fluent API.\n         */\n        public Context errorLog(final DistinctErrorLog errorLog)\n        {\n            this.errorLog = errorLog;\n            return this;\n        }\n\n        /**\n         * The {@link DistinctErrorLog} in use.\n         *\n         * @return {@link DistinctErrorLog} in use.\n         */\n        public DistinctErrorLog errorLog()\n        {\n            return errorLog;\n        }\n\n        /**\n         * The source of random values for timeouts used in elections.\n         *\n         * @param random source of random values for timeouts used in elections.\n         * @return this for a fluent API.\n         */\n        public Context random(final Random random)\n        {\n            this.random = random;\n            return this;\n        }\n\n        /**\n         * The source of random values for timeouts used in elections.\n         *\n         * @return source of random values for timeouts used in elections.\n         */\n        public Random random()\n        {\n            return random;\n        }\n\n        /**\n         * Provides an {@link TimerService} supplier for the idle strategy for the agent duty cycle.\n         *\n         * @param timerServiceSupplier supplier for the idle strategy for the agent duty cycle.\n         * @return this for a fluent API.\n         */\n        public Context timerServiceSupplier(final TimerServiceSupplier timerServiceSupplier)\n        {\n            this.timerServiceSupplier = timerServiceSupplier;\n            return this;\n        }\n\n        /**\n         * Supplier of the {@link TimerService} instances.\n         *\n         * @return supplier of dynamically created {@link TimerService} instances.\n         */\n        @Config\n        public TimerServiceSupplier timerServiceSupplier()\n        {\n            return timerServiceSupplier;\n        }\n\n        /**\n         * Register a ConsensusModuleExtension to extend the behaviour of the\n         * consensus module instead of using ClusteredServices.\n         *\n         * @param consensusModuleExtension supplier for consensus module extension\n         * @return this for a fluent API.\n         */\n        public Context consensusModuleExtension(final ConsensusModuleExtension consensusModuleExtension)\n        {\n            this.consensusModuleExtension = consensusModuleExtension;\n            return this;\n        }\n\n        /**\n         * Registered consensus module extension.\n         *\n         * @return Registered consensus module extension or null.\n         */\n        public ConsensusModuleExtension consensusModuleExtension()\n        {\n            return consensusModuleExtension;\n        }\n\n        /**\n         * Deprecated for removal.\n         *\n         * @return {@code null}.\n         */\n        @Deprecated\n        public NameResolver nameResolver()\n        {\n            return null;\n        }\n\n        /**\n         * Deprecated for removal.\n         *\n         * @param ignore as deprecated.\n         * @return this for fluent API.\n         */\n        @Deprecated\n        public Context nameResolver(final NameResolver ignore)\n        {\n            return this;\n        }\n\n        /**\n         * Indicate whether this node should accept snapshots from standby nodes.\n         *\n         * @return <code>true</code> if this node should accept snapshots from standby nodes, <code>false</code>\n         * otherwise.\n         * @see Configuration#CLUSTER_ACCEPT_STANDBY_SNAPSHOTS_PROP_NAME\n         * @see Configuration#acceptStandbySnapshots()\n         */\n        @Config(id = \"CLUSTER_ACCEPT_STANDBY_SNAPSHOTS\")\n        public boolean acceptStandbySnapshots()\n        {\n            return acceptStandbySnapshots;\n        }\n\n        /**\n         * Indicate whether this node should accept snapshots from standby nodes.\n         *\n         * @param acceptStandbySnapshots <code>true</code> if this node should accept snapshots from standby nodes,\n         *                               <code>false</code> otherwise.\n         * @return this for a fluent API.\n         * @see Configuration#CLUSTER_ACCEPT_STANDBY_SNAPSHOTS_PROP_NAME\n         * @see Configuration#acceptStandbySnapshots()\n         */\n        public ConsensusModule.Context acceptStandbySnapshots(final boolean acceptStandbySnapshots)\n        {\n            this.acceptStandbySnapshots = acceptStandbySnapshots;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track standby snapshots accepted by this node.\n         *\n         * @return the counter for standby snapshots.\n         */\n        public Counter standbySnapshotCounter()\n        {\n            return standbySnapshotCounter;\n        }\n\n        /**\n         * Set the counter used to track standby snapshots accepted by this node.\n         *\n         * @param standbySnapshotCounter the counter for standby snapshots.\n         * @return this for a fluentAPI.\n         */\n        public Context standbySnapshotCounter(final Counter standbySnapshotCounter)\n        {\n            this.standbySnapshotCounter = standbySnapshotCounter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the number of elections on this node.\n         *\n         * @return the counter for elections.\n         * @since 1.44.0\n         */\n        public Counter electionCounter()\n        {\n            return electionCounter;\n        }\n\n        /**\n         * Set the counter used to track the number of elections on this node.\n         *\n         * @param electionCounter the counter for elections.\n         * @return this for a fluentAPI.\n         * @since 1.44.0\n         */\n        public Context electionCounter(final Counter electionCounter)\n        {\n            this.electionCounter = electionCounter;\n            return this;\n        }\n\n        /**\n         * Get the counter used to track the leadership term id.\n         *\n         * @return the counter containing the leadership term id.\n         * @since 1.44.0\n         */\n        public Counter leadershipTermIdCounter()\n        {\n            return leadershipTermId;\n        }\n\n        /**\n         * Set the counter used to track the number of elections on this node.\n         *\n         * @param leadershipTermId the counter for elections.\n         * @return this for a fluentAPI.\n         * @since 1.44.0\n         */\n        public Context leadershipTermIdCounter(final Counter leadershipTermId)\n        {\n            this.leadershipTermId = leadershipTermId;\n            return this;\n        }\n\n        /**\n         * If the ConsensusModule should set the control endpoint for the Consensus publication.\n         *\n         * @return <code>true</code> if the control endpoint should be set, <code>false</code> otherwise.\n         */\n        public boolean enableControlOnConsensusChannel()\n        {\n            return enableControlOnConsensusChannel;\n        }\n\n        /**\n         * If the ConsensusModule should bind the control endpoint for the Consensus publication.\n         *\n         * @param enableControlOnConsensusChannel <code>true</code> if the control endpoint should be set,\n         *                                        <code>false</code> otherwise.\n         * @return this for fluent API.\n         */\n        public Context enableControlOnConsensusChannel(final boolean enableControlOnConsensusChannel)\n        {\n            this.enableControlOnConsensusChannel = enableControlOnConsensusChannel;\n            return this;\n        }\n\n        /**\n         * If the ConsensusModule should set the control endpoint for the Consensus publication.\n         *\n         * @return <code>true</code> if the control endpoint should be bound, <code>false</code> otherwise.\n         */\n        public boolean enableControlOnLogControl()\n        {\n            return enableControlOnLogChannel;\n        }\n\n        /**\n         * If the LogModule should set the control endpoint for the Log publication.\n         *\n         * @param setControlOnLogChannel <code>true</code> if the control endpoint should be set, <code>false</code>\n         *                               otherwise.\n         * @return this for fluent API.\n         */\n        public Context enableControlOnLogControl(final boolean setControlOnLogChannel)\n        {\n            this.enableControlOnLogChannel = setControlOnLogChannel;\n            return this;\n        }\n\n        /**\n         * Delete the cluster directory.\n         */\n        public void deleteDirectory()\n        {\n            if (null != clusterDir)\n            {\n                IoUtil.delete(clusterDir, false);\n            }\n        }\n\n        /**\n         * Close the context and free applicable resources.\n         * <p>\n         * If {@link #ownsAeronClient()} is true then the {@link #aeron()} client will be closed.\n         */\n        public void close()\n        {\n            CloseHelper.close(countedErrorHandler, recordingLog);\n            CloseHelper.close(countedErrorHandler, nodeStateFile);\n            CloseHelper.close(countedErrorHandler, markFile);\n            if (errorHandler instanceof AutoCloseable handler)\n            {\n                CloseHelper.quietClose(handler); // Ignore error to ensure the rest will be closed\n            }\n\n            if (ownsAeronClient)\n            {\n                CloseHelper.close(aeron);\n            }\n            else if (!aeron.isClosed())\n            {\n                CloseHelper.closeAll(\n                    timedOutClientCounter,\n                    clusterControlToggle,\n                    snapshotCounter,\n                    moduleStateCounter,\n                    electionStateCounter,\n                    clusterNodeRoleCounter,\n                    commitPosition);\n            }\n        }\n\n        Context logPublisher(final LogPublisher logPublisher)\n        {\n            this.logPublisher = logPublisher;\n            return this;\n        }\n\n        LogPublisher logPublisher()\n        {\n            return logPublisher;\n        }\n\n        Context egressPublisher(final EgressPublisher egressPublisher)\n        {\n            this.egressPublisher = egressPublisher;\n            return this;\n        }\n\n        EgressPublisher egressPublisher()\n        {\n            return egressPublisher;\n        }\n\n        boolean isLogMdc()\n        {\n            return isLogMdc;\n        }\n\n        /**\n         * Start up the consensus module using pre-supplied state skipping the recovery process. Internal use only.\n         *\n         * @param bootstrapState to initialize the consensus module.\n         * @return this for a fluent API.\n         */\n        ConsensusModule.Context bootstrapState(final ConsensusModuleStateExport bootstrapState)\n        {\n            this.bootstrapState = bootstrapState;\n            return this;\n        }\n\n        ConsensusModuleStateExport bootstrapState()\n        {\n            return bootstrapState;\n        }\n\n        private void concludeMarkFile()\n        {\n            final String aeronDirectory = aeron.context().aeronDirectoryName();\n            final String authenticatorClassName = authenticatorSupplier.getClass().getName();\n            ClusterMarkFile.checkHeaderLength(\n                aeronDirectory, controlChannel(), ingressChannel, null, authenticatorClassName);\n\n            markFile.encoder()\n                .archiveStreamId(archiveContext.controlRequestStreamId())\n                .serviceStreamId(serviceStreamId)\n                .consensusModuleStreamId(consensusModuleStreamId)\n                .ingressStreamId(ingressStreamId)\n                .memberId(clusterMemberId)\n                .serviceId(SERVICE_ID)\n                .clusterId(clusterId)\n                .aeronDirectory(aeronDirectory)\n                .controlChannel(controlChannel)\n                .ingressChannel(ingressChannel)\n                .serviceName(null)\n                .authenticator(authenticatorClassName)\n                .servicesClusterDir(clusterServicesDirectoryName);\n\n            markFile.signalReady(epochClock.time());\n        }\n\n        private TimerServiceSupplier getTimerServiceSupplierFromSystemProperty()\n        {\n            final String timeServiceClassName = Configuration.timerServiceSupplier();\n            if (WheelTimerServiceSupplier.class.getName().equals(timeServiceClassName))\n            {\n                return new WheelTimerServiceSupplier(\n                    clusterClock.timeUnit(),\n                    0,\n                    findNextPositivePowerOfTwo(\n                        clusterClock.timeUnit().convert(wheelTickResolutionNs, TimeUnit.NANOSECONDS)),\n                    ticksPerWheel);\n            }\n            else if (PriorityHeapTimerServiceSupplier.class.getName().equals(timeServiceClassName))\n            {\n                return new PriorityHeapTimerServiceSupplier();\n            }\n\n            throw new ClusterException(\"invalid TimerServiceSupplier: \" + timeServiceClassName);\n        }\n\n        private void validateLogChannel()\n        {\n            final ChannelUri logChannelUri = parse(logChannel);\n            if (logChannelUri.containsKey(INITIAL_TERM_ID_PARAM_NAME))\n            {\n                throw new ConfigurationException(\"logChannel must not contain: \" + INITIAL_TERM_ID_PARAM_NAME);\n            }\n            if (logChannelUri.containsKey(TERM_ID_PARAM_NAME))\n            {\n                throw new ConfigurationException(\"logChannel must not contain: \" + TERM_ID_PARAM_NAME);\n            }\n            if (logChannelUri.containsKey(TERM_OFFSET_PARAM_NAME))\n            {\n                throw new ConfigurationException(\"logChannel must not contain: \" + TERM_OFFSET_PARAM_NAME);\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"ConsensusModule.Context\" +\n                \"\\n{\" +\n                \"\\n    isConcluded=\" + isConcluded() +\n                \"\\n    ownsAeronClient=\" + ownsAeronClient +\n                \"\\n    aeronDirectoryName='\" + aeronDirectoryName + '\\'' +\n                \"\\n    aeron=\" + aeron +\n                \"\\n    deleteDirOnStart=\" + deleteDirOnStart +\n                \"\\n    clusterDirectoryName='\" + clusterDirectoryName + '\\'' +\n                \"\\n    clusterDir=\" + clusterDir +\n                \"\\n    recordingLog=\" + recordingLog +\n                \"\\n    markFile=\" + markFile +\n                \"\\n    fileSyncLevel=\" + fileSyncLevel +\n                \"\\n    appVersion=\" + appVersion +\n                \"\\n    clusterId=\" + clusterId +\n                \"\\n    clusterMemberId=\" + clusterMemberId +\n                \"\\n    appointedLeaderId=\" + appointedLeaderId +\n                \"\\n    clusterMembers='\" + clusterMembers + '\\'' +\n                \"\\n    ingressChannel='\" + ingressChannel + '\\'' +\n                \"\\n    ingressStreamId=\" + ingressStreamId +\n                \"\\n    ingressFragmentLimit=\" + ingressFragmentLimit +\n                \"\\n    logChannel='\" + logChannel + '\\'' +\n                \"\\n    logStreamId=\" + logStreamId +\n                \"\\n    memberEndpoints='\" + memberEndpoints + '\\'' +\n                \"\\n    replayChannel='\" + replayChannel + '\\'' +\n                \"\\n    replayStreamId=\" + replayStreamId +\n                \"\\n    controlChannel='\" + controlChannel + '\\'' +\n                \"\\n    consensusModuleStreamId=\" + consensusModuleStreamId +\n                \"\\n    serviceStreamId=\" + serviceStreamId +\n                \"\\n    snapshotChannel='\" + snapshotChannel + '\\'' +\n                \"\\n    snapshotStreamId=\" + snapshotStreamId +\n                \"\\n    consensusChannel='\" + consensusChannel + '\\'' +\n                \"\\n    consensusStreamId=\" + consensusStreamId +\n                \"\\n    replicationChannel='\" + replicationChannel + '\\'' +\n                \"\\n    logFragmentLimit=\" + logFragmentLimit +\n                \"\\n    serviceCount=\" + serviceCount +\n                \"\\n    errorBufferLength=\" + errorBufferLength +\n                \"\\n    maxConcurrentSessions=\" + maxConcurrentSessions +\n                \"\\n    ticksPerWheel=\" + ticksPerWheel +\n                \"\\n    wheelTickResolutionNs=\" + wheelTickResolutionNs +\n                \"\\n    timerServiceSupplier=\" + timerServiceSupplier +\n                \"\\n    sessionTimeoutNs=\" + sessionTimeoutNs +\n                \"\\n    leaderHeartbeatTimeoutNs=\" + leaderHeartbeatTimeoutNs +\n                \"\\n    leaderHeartbeatIntervalNs=\" + leaderHeartbeatIntervalNs +\n                \"\\n    startupCanvassTimeoutNs=\" + startupCanvassTimeoutNs +\n                \"\\n    electionTimeoutNs=\" + electionTimeoutNs +\n                \"\\n    electionStatusIntervalNs=\" + electionStatusIntervalNs +\n                \"\\n    terminationTimeoutNs=\" + terminationTimeoutNs +\n                \"\\n    threadFactory=\" + threadFactory +\n                \"\\n    idleStrategySupplier=\" + idleStrategySupplier +\n                \"\\n    clusterClock=\" + clusterClock +\n                \"\\n    epochClock=\" + epochClock +\n                \"\\n    random=\" + random +\n                \"\\n    errorLog=\" + errorLog +\n                \"\\n    errorHandler=\" + errorHandler +\n                \"\\n    errorCounter=\" + errorCounter +\n                \"\\n    countedErrorHandler=\" + countedErrorHandler +\n                \"\\n    moduleStateCounter=\" + moduleStateCounter +\n                \"\\n    electionStateCounter=\" + electionStateCounter +\n                \"\\n    clusterNodeRoleCounter=\" + clusterNodeRoleCounter +\n                \"\\n    commitPosition=\" + commitPosition +\n                \"\\n    controlToggle=\" + clusterControlToggle +\n                \"\\n    nodeControlToggle=\" + nodeControlToggle +\n                \"\\n    snapshotCounter=\" + snapshotCounter +\n                \"\\n    timedOutClientCounter=\" + timedOutClientCounter +\n                \"\\n    standbySnapshotCounter=\" + standbySnapshotCounter +\n                \"\\n    electionCounter=\" + electionCounter +\n                \"\\n    leadershipTermId=\" + leadershipTermId +\n                \"\\n    terminationHook=\" + terminationHook +\n                \"\\n    archiveContext=\" + archiveContext +\n                \"\\n    authenticatorSupplier=\" + authenticatorSupplier +\n                \"\\n    authorisationServiceSupplier=\" + authorisationServiceSupplier +\n                \"\\n    logPublisher=\" + logPublisher +\n                \"\\n    egressPublisher=\" + egressPublisher +\n                \"\\n    isLogMdc=\" + isLogMdc +\n                \"\\n    useAgentInvoker=\" + useAgentInvoker +\n                \"\\n    cycleThresholdNs=\" + cycleThresholdNs +\n                \"\\n    dutyCycleTracker=\" + dutyCycleTracker +\n                \"\\n    totalSnapshotDurationThresholdNs=\" + totalSnapshotDurationThresholdNs +\n                \"\\n    totalSnapshotDurationTracker=\" + totalSnapshotDurationTracker +\n                \"\\n    acceptStandbySnapshots=\" + acceptStandbySnapshots +\n                \"\\n    bootstrapState=\" + bootstrapState +\n                \"\\n}\";\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ConsensusModule{\" +\n            \"conductor=\" + conductor +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\nfinal class ConsensusModuleAdapter implements AutoCloseable\n{\n    private static final int FRAGMENT_LIMIT = 25;\n\n    private final Subscription subscription;\n    private final ConsensusModuleAgent consensusModuleAgent;\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n    private final ScheduleTimerDecoder scheduleTimerDecoder = new ScheduleTimerDecoder();\n    private final CancelTimerDecoder cancelTimerDecoder = new CancelTimerDecoder();\n    private final ServiceAckDecoder serviceAckDecoder = new ServiceAckDecoder();\n    private final CloseSessionDecoder closeSessionDecoder = new CloseSessionDecoder();\n    private final ClusterMembersQueryDecoder clusterMembersQueryDecoder = new ClusterMembersQueryDecoder();\n    private final ControlledFragmentAssembler fragmentAssembler = new ControlledFragmentAssembler(this::onFragment);\n\n    ConsensusModuleAdapter(final Subscription subscription, final ConsensusModuleAgent consensusModuleAgent)\n    {\n        this.subscription = subscription;\n        this.consensusModuleAgent = consensusModuleAgent;\n    }\n\n    public void close()\n    {\n        subscription.close();\n    }\n\n    int poll()\n    {\n        return subscription.controlledPoll(fragmentAssembler, FRAGMENT_LIMIT);\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private ControlledFragmentHandler.Action onFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ClusterException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        ControlledFragmentHandler.Action action = ControlledFragmentHandler.Action.CONTINUE;\n\n        switch (messageHeaderDecoder.templateId())\n        {\n            case SessionMessageHeaderDecoder.TEMPLATE_ID:\n                sessionMessageHeaderDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onServiceMessage(\n                    sessionMessageHeaderDecoder.clusterSessionId(),\n                    buffer,\n                    offset + AeronCluster.SESSION_HEADER_LENGTH,\n                    length - AeronCluster.SESSION_HEADER_LENGTH);\n                break;\n\n            case CloseSessionDecoder.TEMPLATE_ID:\n                closeSessionDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onServiceCloseSession(closeSessionDecoder.clusterSessionId());\n                break;\n\n            case ScheduleTimerDecoder.TEMPLATE_ID:\n                scheduleTimerDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onScheduleTimer(\n                    scheduleTimerDecoder.correlationId(),\n                    scheduleTimerDecoder.deadline());\n                break;\n\n            case CancelTimerDecoder.TEMPLATE_ID:\n                cancelTimerDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onCancelTimer(cancelTimerDecoder.correlationId());\n                break;\n\n            case ServiceAckDecoder.TEMPLATE_ID:\n                serviceAckDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onServiceAck(\n                    serviceAckDecoder.logPosition(),\n                    serviceAckDecoder.timestamp(),\n                    serviceAckDecoder.ackId(),\n                    serviceAckDecoder.relevantId(),\n                    serviceAckDecoder.serviceId());\n\n                action = ControlledFragmentHandler.Action.BREAK;\n                break;\n\n            case ClusterMembersQueryDecoder.TEMPLATE_ID:\n                clusterMembersQueryDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                consensusModuleAgent.onClusterMembersQuery(\n                    clusterMembersQueryDecoder.correlationId(),\n                    BooleanType.TRUE == clusterMembersQueryDecoder.extended());\n                break;\n        }\n\n        return action;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleAgent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.client.RecordingSignalPoller;\nimport io.aeron.archive.client.ReplicationParams;\nimport io.aeron.archive.codecs.ControlResponseCode;\nimport io.aeron.archive.codecs.ControlResponseDecoder;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.codecs.RecordingSignalEventDecoder;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.AdminRequestDecoder;\nimport io.aeron.cluster.codecs.AdminRequestType;\nimport io.aeron.cluster.codecs.AdminResponseCode;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.codecs.ClusterAction;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderDecoder;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.cluster.service.ClusterTerminationException;\nimport io.aeron.cluster.service.RecoveryState;\nimport io.aeron.cluster.service.SnapshotDurationTracker;\nimport io.aeron.driver.DutyCycleTracker;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport io.aeron.status.ReadableCounter;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableRingBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.SemanticVersion;\nimport org.agrona.Strings;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.collections.Long2LongCounterMap;\nimport org.agrona.collections.LongArrayQueue;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.util.ArrayDeque;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport java.util.function.LongConsumer;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.ChannelUri.transformAlias;\nimport static io.aeron.CommonContext.ALIAS_PARAM_NAME;\nimport static io.aeron.CommonContext.CONTROL_MODE_RESPONSE;\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.CommonContext.EOS_PARAM_NAME;\nimport static io.aeron.CommonContext.INITIAL_TERM_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.CommonContext.LINGER_PARAM_NAME;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_MANUAL;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_PARAM_NAME;\nimport static io.aeron.CommonContext.MDC_CONTROL_PARAM_NAME;\nimport static io.aeron.CommonContext.MTU_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.REJOIN_PARAM_NAME;\nimport static io.aeron.CommonContext.RELIABLE_STREAM_PARAM_NAME;\nimport static io.aeron.CommonContext.SESSION_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.SPIES_SIMULATE_CONNECTION_PARAM_NAME;\nimport static io.aeron.CommonContext.SPY_PREFIX;\nimport static io.aeron.CommonContext.TAGS_PARAM_NAME;\nimport static io.aeron.CommonContext.TERM_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.TERM_OFFSET_PARAM_NAME;\nimport static io.aeron.CommonContext.UDP_CHANNEL;\nimport static io.aeron.archive.client.AeronArchive.NULL_LENGTH;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.ArchiveException.UNKNOWN_REPLAY;\nimport static io.aeron.archive.client.ReplayMerge.LIVE_ADD_MAX_WINDOW;\nimport static io.aeron.archive.codecs.SourceLocation.LOCAL;\nimport static io.aeron.cluster.ConsensusModule.CLUSTER_ACTION_FLAGS_DEFAULT;\nimport static io.aeron.cluster.ConsensusModule.CLUSTER_ACTION_FLAGS_STANDBY_SNAPSHOT;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SERVICE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SNAPSHOT_TYPE_ID;\nimport static io.aeron.cluster.ServiceAck.pollServiceAcks;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.MARK_FILE_UPDATE_INTERVAL_NS;\nimport static io.aeron.exceptions.AeronException.Category.WARN;\nimport static java.lang.Math.max;\nimport static java.lang.Math.min;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\n\nfinal class ConsensusModuleAgent\n    implements Agent, IdleStrategy, TimerService.TimerHandler, ConsensusModuleSnapshotListener, ConsensusModuleControl\n{\n    static final long SLOW_TICK_INTERVAL_NS = MILLISECONDS.toNanos(10);\n    static final short APPEND_POSITION_FLAG_NONE = 0;\n    static final short APPEND_POSITION_FLAG_CATCHUP = 1;\n\n    private final long leaderHeartbeatIntervalNs;\n    private final long leaderHeartbeatTimeoutNs;\n    private long unavailableCounterHandlerRegistrationId;\n    private long leadershipTermId = NULL_VALUE;\n    private long expectedAckPosition = 0;\n    private long serviceAckId = 0;\n    private long terminationPosition = NULL_POSITION;\n    private long terminationLeadershipTermId = NULL_VALUE;\n    private long notifiedCommitPosition = 0;\n    private long lastAppendPosition = NULL_POSITION;\n    private long lastQuorumBacktrackCommitPosition = NULL_POSITION;\n    private long timeOfLastLogUpdateNs = 0;\n    private long timeOfLastAppendPositionUpdateNs = 0;\n    private long timeOfLastAppendPositionSendNs = 0;\n    private long timeOfLastLeaderUpdateNs;\n    private long slowTickDeadlineNs = 0;\n    private long markFileUpdateDeadlineNs = 0;\n\n    private final ClusterMember[] activeMembers;\n    private final ClusterMember thisMember;\n    private final long[] rankedPositions;\n    private final long[] serviceClientIds;\n    private final int serviceCount;\n    private final int memberId;\n    private final Counter commitPosition;\n\n    private long logPublicationChannelTag;\n    private ReadableCounter appendPosition = null;\n    private ConsensusModule.State state = ConsensusModule.State.INIT;\n    private Cluster.Role role = Cluster.Role.FOLLOWER;\n    private ClusterMember leaderMember;\n    private final ArrayDeque<ServiceAck>[] serviceAckQueues;\n    private final Counter clusterRoleCounter;\n    private final ClusterMarkFile markFile;\n    private final AgentInvoker aeronClientInvoker;\n    private final ClusterClock clusterClock;\n    private final LongConsumer clusterTimeConsumer;\n    private final TimeUnit clusterTimeUnit;\n    private final TimerService timerService;\n    private final Counter moduleState;\n    private final Counter controlToggle;\n    private final Counter nodeControlToggle;\n    private final ConsensusModuleAdapter consensusModuleAdapter;\n    private final ServiceProxy serviceProxy;\n    private final IngressAdapter ingressAdapter;\n    private final EgressPublisher egressPublisher;\n    private final LogPublisher logPublisher;\n    private final LogAdapter logAdapter;\n    private final ConsensusAdapter consensusAdapter;\n    private final ConsensusPublisher consensusPublisher = new ConsensusPublisher();\n    private final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n    private final SessionManager sessionManager;\n    private final Long2LongCounterMap expiredTimerCountByCorrelationIdMap = new Long2LongCounterMap(0);\n    private final LongArrayQueue uncommittedTimers = new LongArrayQueue(Long.MAX_VALUE);\n    private final LongArrayQueue uncommittedPreviousState = new LongArrayQueue(Long.MAX_VALUE);\n    private final PendingServiceMessageTracker[] pendingServiceMessageTrackers;\n    private final ConsensusModuleExtension consensusModuleExtension;\n    private final AuthorisationService authorisationService;\n    private final Aeron aeron;\n    private final ConsensusModule.Context ctx;\n    private final IdleStrategy idleStrategy;\n    private final RecordingLog recordingLog;\n    private final DutyCycleTracker dutyCycleTracker;\n    private final SnapshotDurationTracker totalSnapshotDurationTracker;\n    private final ChannelUri responseChannelTemplate;\n    private RecordingLog.RecoveryPlan recoveryPlan;\n    private AeronArchive archive;\n    private AeronArchive extensionArchive;\n    private RecordingSignalPoller recordingSignalPoller;\n    private Election election;\n    private ClusterTermination clusterTermination;\n    private long logSubscriptionId = NULL_VALUE;\n    private long logRecordingId = NULL_VALUE;\n    private long logRecordingStopPosition = 0;\n    private String liveLogDestination;\n    private String catchupLogDestination;\n    private String ingressEndpoints;\n    private StandbySnapshotReplicator standbySnapshotReplicator = null;\n    private String localLogChannel;\n    private Subscription extensionLeaderSubscription = null;\n\n    ConsensusModuleAgent(final ConsensusModule.Context ctx)\n    {\n        this.ctx = ctx;\n        this.aeron = ctx.aeron();\n        this.clusterClock = ctx.clusterClock();\n        this.clusterTimeUnit = clusterClock.timeUnit();\n        this.clusterTimeConsumer = ctx.clusterTimeConsumerSupplier().apply(ctx);\n        this.timerService = ctx.timerServiceSupplier().newInstance(clusterTimeUnit, this);\n        this.leaderHeartbeatIntervalNs = ctx.leaderHeartbeatIntervalNs();\n        this.leaderHeartbeatTimeoutNs = ctx.leaderHeartbeatTimeoutNs();\n        this.egressPublisher = ctx.egressPublisher();\n        this.moduleState = ctx.moduleStateCounter();\n        this.commitPosition = ctx.commitPositionCounter();\n        this.controlToggle = ctx.controlToggleCounter();\n        this.nodeControlToggle = ctx.nodeControlToggleCounter();\n        this.logPublisher = ctx.logPublisher();\n        this.idleStrategy = ctx.idleStrategy();\n        this.activeMembers = ClusterMember.parse(ctx.clusterMembers());\n        this.memberId = ctx.clusterMemberId();\n        this.clusterRoleCounter = ctx.clusterNodeRoleCounter();\n        this.markFile = ctx.clusterMarkFile();\n        this.recordingLog = ctx.recordingLog();\n        this.serviceClientIds = new long[ctx.serviceCount()];\n        Arrays.fill(serviceClientIds, NULL_VALUE);\n        this.serviceCount = ctx.serviceCount();\n        this.serviceAckQueues = ServiceAck.newArrayOfQueues(serviceCount);\n        this.dutyCycleTracker = ctx.dutyCycleTracker();\n        this.totalSnapshotDurationTracker = ctx.totalSnapshotDurationTracker();\n\n        aeronClientInvoker = aeron.conductorAgentInvoker();\n        aeronClientInvoker.invoke();\n\n        rankedPositions = new long[ClusterMember.quorumThreshold(activeMembers.length)];\n        role(Cluster.Role.FOLLOWER);\n\n        sessionManager = new SessionManager(ctx, activeMembers, consensusPublisher);\n\n        ClusterMember.addClusterMemberIds(activeMembers, clusterMemberByIdMap);\n        thisMember = ClusterMember.determineMember(activeMembers, memberId, ctx.memberEndpoints());\n        leaderMember = thisMember;\n\n        final ChannelUri consensusUri = ChannelUri.parse(ctx.consensusChannel());\n        if (!consensusUri.containsKey(ENDPOINT_PARAM_NAME))\n        {\n            consensusUri.put(ENDPOINT_PARAM_NAME, thisMember.consensusEndpoint());\n        }\n\n        consensusAdapter = new ConsensusAdapter(\n            aeron.addSubscription(consensusUri.toString(), ctx.consensusStreamId()), this);\n\n        ingressAdapter = new IngressAdapter(ctx.ingressFragmentLimit(), this);\n        logAdapter = new LogAdapter(this, ctx.logFragmentLimit());\n\n        consensusModuleAdapter = new ConsensusModuleAdapter(\n            aeron.addSubscription(ctx.controlChannel(), ctx.consensusModuleStreamId()), this);\n        serviceProxy = new ServiceProxy(aeron.addPublication(ctx.controlChannel(), ctx.serviceStreamId()));\n\n        authorisationService = sessionManager.authorisationService();\n\n        pendingServiceMessageTrackers = new PendingServiceMessageTracker[ctx.serviceCount()];\n        for (int i = 0, size = ctx.serviceCount(); i < size; i++)\n        {\n            pendingServiceMessageTrackers[i] = new PendingServiceMessageTracker(\n                i, commitPosition, logPublisher, clusterClock);\n        }\n        this.consensusModuleExtension = ctx.consensusModuleExtension();\n        responseChannelTemplate = Strings.isEmpty(ctx.egressChannel()) ? null : ChannelUri.parse(ctx.egressChannel());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        if (!aeron.isClosed())\n        {\n            aeron.removeUnavailableCounterHandler(unavailableCounterHandlerRegistrationId);\n\n            final CountedErrorHandler errorHandler = ctx.countedErrorHandler();\n            CloseHelper.close(consensusModuleExtension);\n            CloseHelper.close(errorHandler, extensionArchive);\n\n            logPublisher.disconnect(errorHandler);\n            CloseHelper.close(logAdapter.subscription());\n            tryStopLogRecording();\n\n            CloseHelper.close(errorHandler, archive);\n\n            if (!ctx.ownsAeronClient())\n            {\n                ClusterMember.closeConsensusPublications(errorHandler, activeMembers);\n                CloseHelper.close(errorHandler, ingressAdapter);\n                CloseHelper.close(errorHandler, consensusAdapter);\n                CloseHelper.close(errorHandler, serviceProxy);\n                CloseHelper.close(errorHandler, consensusModuleAdapter);\n\n                sessionManager.closeSessions(errorHandler, this);\n            }\n\n            state(ConsensusModule.State.CLOSED, \"closed\");\n        }\n\n        markFile.signalTerminated();\n        ctx.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        archive = AeronArchive.connect(ctx.archiveContext().clone());\n        recordingSignalPoller = new RecordingSignalPoller(\n            archive.controlSessionId(), archive.controlResponsePoller().subscription());\n\n        final long lastTermRecordingId = recordingLog.findLastTermRecordingId();\n        if (NULL_VALUE != lastTermRecordingId)\n        {\n            archive.tryStopRecordingByIdentity(lastTermRecordingId);\n        }\n\n        if (null == ctx.bootstrapState())\n        {\n            replicateStandbySnapshotsForStartup();\n            recoveryPlan = recoverFromSnapshotAndLog();\n        }\n        else\n        {\n            recoveryPlan = recoverFromBootstrapState();\n        }\n\n        ClusterMember.addConsensusPublications(\n            activeMembers,\n            thisMember,\n            ctx.consensusChannel(),\n            ctx.consensusStreamId(),\n            ctx.enableControlOnConsensusChannel(),\n            aeron,\n            ctx.countedErrorHandler());\n\n        final long lastLeadershipTermId = recoveryPlan.lastLeadershipTermId();\n        final long commitPosition = this.commitPosition.getPlain();\n        final long appendedPosition = recoveryPlan.appendedLogPosition();\n        logNewElection(memberId, lastLeadershipTermId, commitPosition, appendedPosition, \"node started\");\n\n        election = new Election(\n            true,\n            NULL_VALUE,\n            lastLeadershipTermId,\n            recoveryPlan.lastTermBaseLogPosition(),\n            commitPosition,\n            appendedPosition,\n            activeMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            this);\n\n        election.doWork(clusterClock.timeNanos());\n        state(ConsensusModule.State.ACTIVE, \"started\");\n\n        if (null != consensusModuleExtension)\n        {\n            final AeronArchive.Context extensionArchiveCtx = ctx.archiveContext().clone();\n\n            final Function<String, String> suffix = (alias) -> null != alias ? alias + \"-ext\" : null;\n            extensionArchiveCtx\n                .controlRequestChannel(transformAlias(extensionArchiveCtx.controlRequestChannel(), suffix))\n                .controlResponseChannel(transformAlias(extensionArchiveCtx.controlResponseChannel(), suffix));\n\n            extensionArchive = AeronArchive.connect(extensionArchiveCtx);\n        }\n\n        unavailableCounterHandlerRegistrationId = aeron.addUnavailableCounterHandler(this::onUnavailableCounter);\n        dutyCycleTracker.update(clusterClock.timeNanos());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        final long timestamp = clusterClock.time();\n        final long nowNs = clusterClock.convertToNanos(timestamp);\n        int workCount = 0;\n\n        dutyCycleTracker.measureAndUpdate(nowNs);\n\n        try\n        {\n            if (nowNs >= slowTickDeadlineNs)\n            {\n                final int slowTickWorkCount = slowTickWork(nowNs);\n\n                workCount += slowTickWorkCount;\n                slowTickDeadlineNs = slowTickWorkCount > 0 ? nowNs + 1 : nowNs + SLOW_TICK_INTERVAL_NS;\n            }\n\n            workCount += consensusAdapter.poll();\n\n            if (null != election)\n            {\n                workCount += election.doWork(nowNs);\n            }\n            else\n            {\n                workCount += consensusWork(timestamp, nowNs);\n            }\n\n            if (null != consensusModuleExtension)\n            {\n                workCount += consensusModuleExtension.doWork(nowNs);\n            }\n        }\n        catch (final AgentTerminationException ex)\n        {\n            runTerminationHook();\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            if (null != election)\n            {\n                election.handleError(nowNs, ex);\n            }\n            else\n            {\n                throw ex;\n            }\n        }\n\n        clusterTimeConsumer.accept(timestamp);\n\n        return workCount;\n    }\n\n    public ConsensusModule.Context context()\n    {\n        return ctx;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int memberId()\n    {\n        return memberId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return ctx.agentRoleName();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long time()\n    {\n        return clusterClock.time();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public TimeUnit timeUnit()\n    {\n        return clusterTimeUnit;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public IdleStrategy idleStrategy()\n    {\n        return this;\n    }\n\n    public void idle()\n    {\n        checkInterruptStatus();\n        aeronClientInvoker.invoke();\n        if (aeron.isClosed())\n        {\n            throw new AgentTerminationException(\"unexpected Aeron close\");\n        }\n\n        idleStrategy.idle();\n        pollArchiveEvents();\n    }\n\n    public void idle(final int workCount)\n    {\n        checkInterruptStatus();\n        aeronClientInvoker.invoke();\n        if (aeron.isClosed())\n        {\n            throw new AgentTerminationException(\"unexpected Aeron close\");\n        }\n\n        idleStrategy.idle(workCount);\n\n        if (0 == workCount)\n        {\n            pollArchiveEvents();\n        }\n    }\n\n    public void reset()\n    {\n        idleStrategy.reset();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public Aeron aeron()\n    {\n        return aeron;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public AeronArchive archive()\n    {\n        return extensionArchive;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public AuthorisationService authorisationService()\n    {\n        return authorisationService;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public ClusterClientSession getClientSession(final long clusterSessionId)\n    {\n        return sessionManager.findBySessionId(clusterSessionId);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void closeClusterSession(final long clusterSessionId)\n    {\n        onServiceCloseSession(clusterSessionId);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int commitPositionCounterId()\n    {\n        return commitPosition.id();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int clusterId()\n    {\n        return ctx.clusterId();\n    }\n\n    public ClusterMember clusterMember()\n    {\n        return thisMember;\n    }\n\n    public void onLoadBeginSnapshot(\n        final int appVersion, final TimeUnit timeUnit, final DirectBuffer buffer, final int offset, final int length)\n    {\n        if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))\n        {\n            throw new ClusterException(\n                \"incompatible version: \" + SemanticVersion.toString(ctx.appVersion()) +\n                \" snapshot=\" + SemanticVersion.toString(appVersion),\n                AeronException.Category.FATAL);\n        }\n\n        if (timeUnit != clusterTimeUnit)\n        {\n            throw new ClusterException(\n                \"incompatible time unit: \" + clusterTimeUnit + \" snapshot=\" + timeUnit, AeronException.Category.FATAL);\n        }\n    }\n\n    public ControlledFragmentHandler.Action onExtensionMessage(\n        final int actingBlockLength,\n        final int templateId,\n        final int schemaId,\n        final int actingVersion,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        if (null != consensusModuleExtension)\n        {\n            return consensusModuleExtension.onIngressExtensionMessage(\n                actingBlockLength, templateId, schemaId, actingVersion, buffer, offset, length, header);\n        }\n        else\n        {\n            ctx.countedErrorHandler().onError(new ClusterEvent(\n                \"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId));\n\n            return ControlledFragmentHandler.Action.CONTINUE;\n        }\n    }\n\n    public void onLoadEndSnapshot(final DirectBuffer buffer, final int offset, final int length)\n    {\n    }\n\n    public void onLoadClusterSession(\n        final long clusterSessionId,\n        final long correlationId,\n        final long openedPosition,\n        final long timeOfLastActivity,\n        final CloseReason closeReason,\n        final int responseStreamId,\n        final String responseChannel,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        sessionManager.onLoadClusterSession(\n            clusterSessionId,\n            correlationId,\n            openedPosition,\n            timeOfLastActivity,\n            closeReason,\n            responseStreamId,\n            responseChannel);\n    }\n\n    public void onLoadConsensusModuleState(\n        final long nextSessionId,\n        final long nextServiceSessionId,\n        final long logServiceSessionId,\n        final int pendingMessageCapacity,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        sessionManager.loadNextSessionId(nextSessionId);\n\n        if (pendingServiceMessageTrackers.length > 0)\n        {\n            pendingServiceMessageTrackers[0].loadState(\n                nextServiceSessionId, logServiceSessionId, pendingMessageCapacity);\n        }\n    }\n\n    public void onLoadPendingMessageTracker(\n        final long nextServiceSessionId,\n        final long logServiceSessionId,\n        final int pendingMessageCapacity,\n        final int serviceId,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        if (serviceId < 0 || serviceId >= pendingServiceMessageTrackers.length)\n        {\n            throw new ClusterException(\n                \"serviceId=\" + serviceId + \" invalid for serviceCount=\" + pendingServiceMessageTrackers.length);\n        }\n\n        pendingServiceMessageTrackers[serviceId].loadState(\n            nextServiceSessionId, logServiceSessionId, pendingMessageCapacity);\n    }\n\n    public void onLoadPendingMessage(\n        final long clusterSessionId, final DirectBuffer buffer, final int offset, final int length)\n    {\n        final int serviceId = PendingServiceMessageTracker.serviceIdFromLogMessage(clusterSessionId);\n        pendingServiceMessageTrackers[serviceId].appendMessage(buffer, offset, length);\n    }\n\n    public void onLoadTimer(\n        final long correlationId, final long deadline, final DirectBuffer buffer, final int offset, final int length)\n    {\n        onScheduleTimer(correlationId, deadline);\n    }\n\n    public void onSessionConnect(\n        final long correlationId,\n        final int responseStreamId,\n        final int version,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final String clientInfo,\n        final Header header)\n    {\n        sessionManager.onSessionConnect(\n            correlationId,\n            responseStreamId,\n            version,\n            refineResponseChannel(responseChannel),\n            encodedCredentials,\n            clientInfo,\n            header,\n            role,\n            ingressEndpoints\n        );\n    }\n\n    void onSessionClose(final long leadershipTermId, final long clusterSessionId)\n    {\n        if (leadershipTermId == this.leadershipTermId && Cluster.Role.LEADER == role)\n        {\n            sessionManager.onSessionClose(leadershipTermId, clusterSessionId);\n        }\n    }\n\n    void onAdminRequest(\n        final long leadershipTermId,\n        final long clusterSessionId,\n        final long correlationId,\n        final AdminRequestType requestType,\n        final DirectBuffer payload,\n        final int payloadOffset,\n        final int payloadLength)\n    {\n        if (Cluster.Role.LEADER != role || leadershipTermId != this.leadershipTermId)\n        {\n            return;\n        }\n\n        final ClusterSession session = sessionManager.findBySessionId(clusterSessionId);\n        if (null == session || session.state() != ClusterSession.State.OPEN)\n        {\n            return;\n        }\n\n        if (!authorisationService.isAuthorised(\n            MessageHeaderDecoder.SCHEMA_ID, AdminRequestDecoder.TEMPLATE_ID, requestType, session.encodedPrincipal()))\n        {\n            final String msg = \"Execution of the \" + requestType + \" request was not authorised\";\n            egressPublisher.sendAdminResponse(\n                session, correlationId, requestType, AdminResponseCode.UNAUTHORISED_ACCESS, msg);\n            return;\n        }\n\n        if (AdminRequestType.SNAPSHOT == requestType)\n        {\n            if (ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle))\n            {\n                egressPublisher.sendAdminResponse(session, correlationId, requestType, AdminResponseCode.OK, \"\");\n            }\n            else\n            {\n                final String msg = \"Failed to switch ClusterControl to the ToggleState.SNAPSHOT state\";\n                egressPublisher.sendAdminResponse(session, correlationId, requestType, AdminResponseCode.ERROR, msg);\n            }\n        }\n        else\n        {\n            egressPublisher.sendAdminResponse(\n                session, correlationId, requestType, AdminResponseCode.ERROR, \"Unknown request type: \" + requestType);\n        }\n    }\n\n    ControlledFragmentAssembler.Action onIngressMessage(\n        final long leadershipTermId,\n        final long clusterSessionId,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        if (leadershipTermId == this.leadershipTermId && Cluster.Role.LEADER == role)\n        {\n            final ClusterSession session = sessionManager.findBySessionId(clusterSessionId);\n            if (null != session && session.isOpen())\n            {\n                final long timestamp = clusterClock.time();\n                if (logPublisher.appendMessage(\n                    leadershipTermId, clusterSessionId, timestamp, buffer, offset, length) > 0)\n                {\n                    session.timeOfLastActivityNs(clusterClock.convertToNanos(timestamp));\n                }\n                else\n                {\n                    return ControlledFragmentHandler.Action.ABORT;\n                }\n            }\n        }\n\n        return ControlledFragmentHandler.Action.CONTINUE;\n    }\n\n    void onSessionKeepAlive(final long leadershipTermId, final long clusterSessionId, final Header header)\n    {\n        if (leadershipTermId == this.leadershipTermId && Cluster.Role.LEADER == role)\n        {\n            final ClusterSession session = sessionManager.findBySessionId(clusterSessionId);\n            if (null != session && session.state() == ClusterSession.State.OPEN)\n            {\n                session.linkIngressImage(header);\n                session.timeOfLastActivityNs(clusterClock.timeNanos());\n            }\n        }\n    }\n\n    void onIngressChallengeResponse(\n        final long correlationId, final long clusterSessionId, final byte[] encodedCredentials)\n    {\n        if (Cluster.Role.LEADER == role)\n        {\n            sessionManager.onChallengeResponseForUserSession(correlationId, clusterSessionId, encodedCredentials);\n        }\n        else\n        {\n            consensusPublisher.challengeResponse(\n                leaderMember.publication(), correlationId, clusterSessionId, encodedCredentials);\n        }\n    }\n\n    void onConsensusChallengeResponse(\n        final long correlationId, final long clusterSessionId, final byte[] encodedCredentials)\n    {\n        sessionManager.onChallengeResponseForBackupSession(correlationId, clusterSessionId, encodedCredentials);\n    }\n\n    public boolean onTimerEvent(final long correlationId)\n    {\n        final long appendPosition = logPublisher.appendTimer(correlationId, leadershipTermId, clusterClock.time());\n        if (appendPosition > 0)\n        {\n            uncommittedTimers.offerLong(appendPosition);\n            uncommittedTimers.offerLong(correlationId);\n            return true;\n        }\n\n        return false;\n    }\n\n    void onCanvassPosition(\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long leadershipTermId,\n        final int followerMemberId,\n        final int protocolVersion)\n    {\n        logOnCanvassPosition(\n            memberId, logLeadershipTermId, logPosition, leadershipTermId, followerMemberId, protocolVersion);\n        checkFollowerForConsensusPublication(followerMemberId);\n\n        if (null != election)\n        {\n            election.onCanvassPosition(\n                logLeadershipTermId, logPosition, leadershipTermId, followerMemberId, protocolVersion);\n        }\n        else if (Cluster.Role.LEADER == role)\n        {\n            final ClusterMember follower = clusterMemberByIdMap.get(followerMemberId);\n            if (null != follower && logLeadershipTermId <= this.leadershipTermId)\n            {\n                updateMemberLogPosition(follower, logLeadershipTermId, logPosition);\n                stopExistingCatchupReplay(follower);\n\n                final RecordingLog.Entry currentTermEntry = recordingLog.getTermEntry(this.leadershipTermId);\n                final long termBaseLogPosition = currentTermEntry.termBaseLogPosition;\n                long nextLogLeadershipTermId = NULL_VALUE;\n                long nextTermBaseLogPosition = NULL_POSITION;\n                long nextLogPosition = NULL_POSITION;\n\n                if (logLeadershipTermId < this.leadershipTermId)\n                {\n                    final RecordingLog.Entry nextLogEntry = recordingLog.findTermEntry(logLeadershipTermId + 1);\n                    nextLogLeadershipTermId = null != nextLogEntry ?\n                        nextLogEntry.leadershipTermId : this.leadershipTermId;\n                    nextTermBaseLogPosition = null != nextLogEntry ?\n                        nextLogEntry.termBaseLogPosition : termBaseLogPosition;\n                    nextLogPosition = null != nextLogEntry ? nextLogEntry.logPosition : NULL_POSITION;\n                }\n\n                consensusPublisher.newLeadershipTerm(\n                    follower.publication(),\n                    logLeadershipTermId,\n                    nextLogLeadershipTermId,\n                    nextTermBaseLogPosition,\n                    nextLogPosition,\n                    this.leadershipTermId,\n                    termBaseLogPosition,\n                    logPublisher.position(),\n                    commitPosition.getPlain(),\n                    logRecordingId,\n                    clusterClock.time(),\n                    memberId,\n                    logPublisher.sessionId(),\n                    ctx.appVersion(),\n                    false);\n            }\n        }\n    }\n\n    void onRequestVote(\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateId,\n        final int protocolVersion)\n    {\n        logOnRequestVote(memberId, logLeadershipTermId, logPosition, candidateTermId, candidateId, protocolVersion);\n        if (null != election)\n        {\n            election.onRequestVote(logLeadershipTermId, logPosition, candidateTermId, candidateId, protocolVersion);\n        }\n        else if (candidateTermId > leadershipTermId)\n        {\n            enterElection(false, \"unexpected vote request:\" +\n                \" this.leadershipTermId=\" + leadershipTermId +\n                \" candidateTermId=\" + candidateTermId);\n        }\n    }\n\n    void onVote(\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateId,\n        final int voterId,\n        final boolean vote)\n    {\n        logOnVote(\n            memberId, logLeadershipTermId, logPosition, candidateTermId, candidateId, voterId, vote);\n        if (null != election)\n        {\n            election.onVote(\n                logLeadershipTermId, logPosition, candidateTermId, candidateId, voterId, vote);\n        }\n    }\n\n    void onNewLeadershipTerm(\n        final long logLeadershipTermId,\n        final long nextLeadershipTermId,\n        final long nextTermBaseLogPosition,\n        final long nextLogPosition,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long commitPosition,\n        final long leaderRecordingId,\n        final long timestamp,\n        final int leaderId,\n        final int logSessionId,\n        final int appVersion,\n        final boolean isStartup)\n    {\n        logOnNewLeadershipTerm(\n            memberId,\n            logLeadershipTermId,\n            nextLeadershipTermId,\n            nextTermBaseLogPosition,\n            nextLogPosition,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            commitPosition,\n            leaderRecordingId,\n            timestamp,\n            leaderId,\n            logSessionId,\n            appVersion,\n            isStartup);\n\n        if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))\n        {\n            final String error = \"incompatible version: \" + SemanticVersion.toString(ctx.appVersion()) +\n                \" log=\" + SemanticVersion.toString(appVersion);\n            ctx.countedErrorHandler().onError(new ClusterException(error, AeronException.Category.FATAL));\n            unexpectedTermination(error);\n        }\n\n        final long nowNs = clusterClock.timeNanos();\n        if (leadershipTermId >= this.leadershipTermId)\n        {\n            timeOfLastLeaderUpdateNs = nowNs;\n        }\n\n        if (null != election)\n        {\n            election.onNewLeadershipTerm(\n                logLeadershipTermId,\n                nextLeadershipTermId,\n                nextTermBaseLogPosition,\n                nextLogPosition,\n                leadershipTermId,\n                termBaseLogPosition,\n                logPosition,\n                commitPosition,\n                leaderRecordingId,\n                timestamp,\n                leaderId,\n                logSessionId,\n                isStartup);\n        }\n        else if (Cluster.Role.FOLLOWER == role &&\n            leadershipTermId == this.leadershipTermId &&\n            leaderId == leaderMember.id())\n        {\n            notifiedCommitPosition = max(notifiedCommitPosition, commitPosition);\n            timeOfLastLogUpdateNs = nowNs;\n        }\n        else if (leadershipTermId > this.leadershipTermId)\n        {\n            enterElection(false, \"unexpected new leadership term event:\" +\n                \" this.leadershipTermId=\" + this.leadershipTermId +\n                \" newLeadershipTermId=\" + leadershipTermId);\n        }\n    }\n\n    void onAppendPosition(\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final short flags)\n    {\n        logOnAppendPosition(memberId, leadershipTermId, logPosition, followerMemberId, flags);\n        if (null != election)\n        {\n            election.onAppendPosition(leadershipTermId, logPosition, followerMemberId, flags);\n        }\n        else if (leadershipTermId <= this.leadershipTermId && Cluster.Role.LEADER == role)\n        {\n            final ClusterMember follower = clusterMemberByIdMap.get(followerMemberId);\n            if (null != follower)\n            {\n                updateMemberLogPosition(follower, leadershipTermId, logPosition);\n                trackCatchupCompletion(follower, leadershipTermId, flags);\n            }\n        }\n    }\n\n    void updateMemberLogPosition(final ClusterMember member, final long leadershipTermId, final long logPosition)\n    {\n        member\n            .leadershipTermId(leadershipTermId)\n            .logPosition(logPosition)\n            .timeOfLastAppendPositionNs(clusterClock.timeNanos());\n    }\n\n    void onCommitPosition(final long leadershipTermId, final long logPosition, final int leaderMemberId)\n    {\n        logOnCommitPosition(memberId, leadershipTermId, logPosition, leaderMemberId);\n\n        final long nowNs = clusterClock.timeNanos();\n        if (leadershipTermId >= this.leadershipTermId)\n        {\n            timeOfLastLeaderUpdateNs = nowNs;\n        }\n\n        if (null != election)\n        {\n            election.onCommitPosition(leadershipTermId, logPosition, leaderMemberId);\n        }\n        else if (leadershipTermId == this.leadershipTermId)\n        {\n            if (leaderMember.id() == leaderMemberId && Cluster.Role.FOLLOWER == role)\n            {\n                notifiedCommitPosition = max(notifiedCommitPosition, logPosition);\n                timeOfLastLogUpdateNs = nowNs;\n            }\n        }\n        else if (leadershipTermId > this.leadershipTermId)\n        {\n            enterElection(\n                false,\n                \"unexpected commit position from new leader - \" +\n                \" memberId=\" + memberId +\n                \" this.leadershipTermId=\" + this.leadershipTermId +\n                \" this.leaderMemberId=\" + leaderMember.id() +\n                \" this.commitPosition=\" + this.commitPosition.getPlain() +\n                \" this.appendPosition=\" +\n                (null != appendPosition ? appendPosition.getPlain() : NULL_POSITION) +\n                \" newLeadershipTermId=\" + leadershipTermId +\n                \" newLeaderMemberId=\" + leaderMemberId +\n                \" newCommitPosition=\" + logPosition + \")\");\n        }\n    }\n\n    void onCatchupPosition(\n        final long leadershipTermId, final long logPosition, final int followerMemberId, final String catchupEndpoint)\n    {\n        logOnCatchupPosition(memberId, leadershipTermId, logPosition, followerMemberId, catchupEndpoint);\n        if (leadershipTermId <= this.leadershipTermId && Cluster.Role.LEADER == role)\n        {\n            final ClusterMember follower = clusterMemberByIdMap.get(followerMemberId);\n            if (null != follower && follower.catchupReplaySessionId() == NULL_VALUE)\n            {\n                final ChannelUri channel = ChannelUri.parse(ctx.followerCatchupChannel());\n                channel.put(ENDPOINT_PARAM_NAME, catchupEndpoint);\n                channel.put(SESSION_ID_PARAM_NAME, Integer.toString(logPublisher.sessionId()));\n                channel.put(LINGER_PARAM_NAME, \"0\");\n                channel.put(EOS_PARAM_NAME, \"false\");\n                channel.put(ALIAS_PARAM_NAME, \"catchup-followerId-\" + follower.id());\n\n                follower.catchupReplaySessionId(archive.startReplay(\n                    logRecordingId, logPosition, Long.MAX_VALUE, channel.toString(), ctx.logStreamId()));\n                follower.catchupReplayCorrelationId(archive.lastCorrelationId());\n            }\n        }\n    }\n\n    void onStopCatchup(final long leadershipTermId, final int followerMemberId)\n    {\n        logOnStopCatchup(memberId, leadershipTermId, followerMemberId);\n        if (leadershipTermId == this.leadershipTermId && followerMemberId == memberId)\n        {\n            if (null != catchupLogDestination)\n            {\n                logAdapter.asyncRemoveDestination(catchupLogDestination);\n                catchupLogDestination = null;\n            }\n        }\n    }\n\n    void onTerminationPosition(final long leadershipTermId, final long logPosition)\n    {\n        logOnTerminationPosition(memberId, leadershipTermId, logPosition);\n\n        if (leadershipTermId == this.leadershipTermId && Cluster.Role.FOLLOWER == role)\n        {\n            terminationPosition = logPosition;\n            terminationLeadershipTermId = leadershipTermId;\n            timeOfLastLogUpdateNs = clusterClock.timeNanos();\n        }\n    }\n\n    void onTerminationAck(final long leadershipTermId, final long logPosition, final int memberId)\n    {\n        logOnTerminationAck(this.memberId, leadershipTermId, logPosition, memberId);\n\n        if (leadershipTermId == this.leadershipTermId &&\n            logPosition >= terminationPosition &&\n            Cluster.Role.LEADER == role)\n        {\n            final ClusterMember member = clusterMemberByIdMap.get(memberId);\n            if (null != member)\n            {\n                member.hasTerminated(true);\n\n                if (clusterTermination.canTerminate(activeMembers, clusterClock.timeNanos()))\n                {\n                    recordingLog.commitLogPosition(leadershipTermId, terminationPosition);\n                    closeAndTerminate();\n                }\n            }\n        }\n    }\n\n    void onBackupQuery(\n        final long correlationId,\n        final int responseStreamId,\n        final int version,\n        final long logPosition,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final Header header)\n    {\n        if (null == election)\n        {\n            if (state == ConsensusModule.State.ACTIVE ||\n                state == ConsensusModule.State.SUSPENDED ||\n                state == ConsensusModule.State.SNAPSHOT)\n            {\n                sessionManager.onBackupQuery(\n                    correlationId,\n                    responseStreamId,\n                    version,\n                    logPosition,\n                    refineResponseChannel(responseChannel),\n                    encodedCredentials,\n                    header);\n            }\n        }\n    }\n\n    void onHeartbeatRequest(\n        final long correlationId,\n        final int responseStreamId,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final Header header)\n    {\n        if (null == election)\n        {\n            if (state == ConsensusModule.State.ACTIVE ||\n                state == ConsensusModule.State.SUSPENDED ||\n                state == ConsensusModule.State.SNAPSHOT)\n            {\n                sessionManager.onHeartbeatRequest(\n                    correlationId,\n                    responseStreamId,\n                    refineResponseChannel(responseChannel),\n                    encodedCredentials,\n                    header);\n            }\n        }\n    }\n\n    void onClusterMembersQuery(final long correlationId, final boolean isExtendedRequest)\n    {\n        if (isExtendedRequest)\n        {\n            serviceProxy.clusterMembersExtendedResponse(\n                correlationId, clusterClock.timeNanos(), leaderMember.id(), memberId, activeMembers);\n        }\n        else\n        {\n            serviceProxy.clusterMembersResponse(\n                correlationId,\n                leaderMember.id(),\n                ClusterMember.encodeAsString(activeMembers));\n        }\n    }\n\n    void onStandbySnapshot(\n        final long correlationId,\n        final int version,\n        final List<StandbySnapshotEntry> standbySnapshotEntries,\n        final int responseStreamId,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final Header header)\n    {\n        if (null == election)\n        {\n            if (state == ConsensusModule.State.ACTIVE ||\n                state == ConsensusModule.State.SUSPENDED ||\n                state == ConsensusModule.State.SNAPSHOT)\n            {\n                sessionManager.onStandbySnapshot(\n                    correlationId,\n                    version,\n                    standbySnapshotEntries,\n                    responseStreamId,\n                    refineResponseChannel(responseChannel),\n                    encodedCredentials,\n                    header);\n            }\n        }\n    }\n\n    void state(final ConsensusModule.State newState, final String reason)\n    {\n        if (newState != state)\n        {\n            logStateChange(memberId, state, newState, reason);\n            state = newState;\n            if (!moduleState.isClosed())\n            {\n                moduleState.set(newState.code());\n            }\n        }\n    }\n\n    ConsensusModule.State state()\n    {\n        return state;\n    }\n\n    private void logStateChange(\n        final int memberId,\n        final ConsensusModule.State oldState,\n        final ConsensusModule.State newState,\n        final String reason)\n    {\n        // System.out.println(\"CM State memberId=\" + memberId + \" \" + oldState + \" -> \" + newState +\n        // \" reason=\" + reason);\n    }\n\n    void role(final Cluster.Role newRole)\n    {\n        if (newRole != role)\n        {\n            logRoleChange(memberId, role, newRole);\n            role = newRole;\n            if (!clusterRoleCounter.isClosed())\n            {\n                clusterRoleCounter.set(newRole.code());\n            }\n        }\n    }\n\n    private void logRoleChange(final int memberId, final Cluster.Role oldRole, final Cluster.Role newRole)\n    {\n        //System.out.println(\"CM Role memberId=\" + memberId + \" \" + oldRole + \" -> \" + newRole);\n    }\n\n    Cluster.Role role()\n    {\n        return role;\n    }\n\n    long prepareForNewLeadership(final long logPosition, final long nowNs)\n    {\n        role(Cluster.Role.FOLLOWER);\n\n        CloseHelper.close(ctx.countedErrorHandler(), ingressAdapter);\n\n        if (null != catchupLogDestination)\n        {\n            logAdapter.asyncRemoveDestination(catchupLogDestination);\n            catchupLogDestination = null;\n        }\n\n        if (null != liveLogDestination)\n        {\n            logAdapter.asyncRemoveDestination(liveLogDestination);\n            liveLogDestination = null;\n        }\n\n        lastQuorumBacktrackCommitPosition = NULL_POSITION;\n\n        final long logSubscriptionRegistrationId = logAdapter.disconnect(ctx.countedErrorHandler());\n        logPublisher.disconnect(ctx.countedErrorHandler());\n        ClusterControl.ToggleState.deactivate(controlToggle);\n        final ReadableCounter recPos = appendPosition;\n        tryStopLogRecording();\n\n        if (RecordingPos.NULL_RECORDING_ID != logRecordingId)\n        {\n            lastAppendPosition = getLastAppendedPosition();\n            timeOfLastAppendPositionUpdateNs = nowNs;\n            recoveryPlan = recordingLog.createRecoveryPlan(archive, serviceCount, logRecordingId);\n\n            sessionManager.clearSessionsAfter(logPosition, leadershipTermId);\n            sessionManager.disconnectSessions();\n\n            commitPosition.setRelease(logPosition);\n            restoreUncommittedEntries(logPosition);\n\n            final CountersReader counters = ctx.aeron().countersReader();\n            if (null != recPos)\n            {\n                idleStrategy.reset();\n                while (CountersReader.RECORD_ALLOCATED == counters.getCounterState(recPos.counterId()) &&\n                    recPos.registrationId() == counters.getCounterRegistrationId(recPos.counterId()))\n                {\n                    idle();\n                }\n            }\n            else\n            {\n                idleStrategy.reset();\n                final long archiveId = archive.archiveId();\n                while (CountersReader.NULL_COUNTER_ID !=\n                    RecordingPos.findCounterIdByRecording(counters, logRecordingId, archiveId))\n                {\n                    idle();\n                }\n            }\n        }\n\n        if (NULL_VALUE != logSubscriptionRegistrationId)\n        {\n            awaitLocalSocketsClosed(logSubscriptionRegistrationId);\n        }\n        if (null != consensusModuleExtension)\n        {\n            consensusModuleExtension.onPrepareForNewLeadership();\n\n            CloseHelper.quietClose(extensionLeaderSubscription);\n            extensionLeaderSubscription = null;\n        }\n\n        return lastAppendPosition;\n    }\n\n    void onServiceCloseSession(final long clusterSessionId)\n    {\n        sessionManager.onServiceCloseSession(\n            clusterSessionId,\n            Cluster.Role.LEADER == role && ConsensusModule.State.ACTIVE == state,\n            leadershipTermId);\n    }\n\n    void onServiceMessage(final long clusterSessionId, final DirectBuffer buffer, final int offset, final int length)\n    {\n        final int serviceId = PendingServiceMessageTracker.serviceIdFromServiceMessage(clusterSessionId);\n        pendingServiceMessageTrackers[serviceId].enqueueMessage((MutableDirectBuffer)buffer, offset, length);\n    }\n\n    void onScheduleTimer(final long correlationId, final long deadline)\n    {\n        if (expiredTimerCountByCorrelationIdMap.get(correlationId) == 0)\n        {\n            timerService.scheduleTimerForCorrelationId(correlationId, deadline);\n        }\n        else\n        {\n            expiredTimerCountByCorrelationIdMap.decrementAndGet(correlationId);\n        }\n    }\n\n    void onCancelTimer(final long correlationId)\n    {\n        timerService.cancelTimerByCorrelationId(correlationId);\n    }\n\n    void onServiceAck(\n        final long logPosition, final long timestamp, final long ackId, final long relevantId, final int serviceId)\n    {\n        logOnServiceAck(memberId, logPosition, timestamp, clusterTimeUnit, ackId, relevantId, serviceId);\n        captureServiceAck(logPosition, ackId, relevantId, serviceId);\n\n        if (ServiceAck.hasReached(logPosition, serviceAckId, serviceAckQueues))\n        {\n            switch (state)\n            {\n                case SNAPSHOT:\n                    ++serviceAckId;\n                    final ServiceAck[] serviceAcks = pollServiceAcks(logPosition, serviceId, serviceAckQueues);\n                    snapshotOnServiceAck(logPosition, timestamp, serviceAcks);\n                    break;\n\n                case QUITTING:\n                    closeAndTerminate();\n                    break;\n\n                case TERMINATING:\n                    terminateOnServiceAck(logPosition);\n                    break;\n\n                default:\n                    break;\n            }\n        }\n    }\n\n    void onReplaySessionMessage(final long clusterSessionId, final long timestamp)\n    {\n        final ClusterSession session = sessionManager.findBySessionId(clusterSessionId);\n        if (null != session)\n        {\n            session.timeOfLastActivityNs(clusterTimeUnit.toNanos(timestamp));\n        }\n        else if (clusterSessionId < 0)\n        {\n            final int serviceId = PendingServiceMessageTracker.serviceIdFromLogMessage(clusterSessionId);\n            pendingServiceMessageTrackers[serviceId].sweepFollowerMessages(clusterSessionId);\n        }\n    }\n\n    public ControlledFragmentHandler.Action onReplayExtensionMessage(\n        final int actingBlockLength,\n        final int templateId,\n        final int schemaId,\n        final int actingVersion,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        if (null != consensusModuleExtension)\n        {\n            final int remainingMessageOffset = offset + MessageHeaderDecoder.ENCODED_LENGTH;\n            final int remainingMessageLength = length - MessageHeaderDecoder.ENCODED_LENGTH;\n\n            return consensusModuleExtension.onLogExtensionMessage(\n                actingBlockLength,\n                templateId,\n                schemaId,\n                actingVersion,\n                buffer,\n                remainingMessageOffset,\n                remainingMessageLength,\n                header);\n        }\n\n        throw new ClusterException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n    }\n\n    void onReplayTimerEvent(final long correlationId)\n    {\n        if (!timerService.cancelTimerByCorrelationId(correlationId))\n        {\n            expiredTimerCountByCorrelationIdMap.getAndIncrement(correlationId);\n        }\n    }\n\n    void onReplaySessionOpen(\n        final long logPosition,\n        final long correlationId,\n        final long clusterSessionId,\n        final long timestamp,\n        final int responseStreamId,\n        final String responseChannel)\n    {\n        sessionManager.onReplaySessionOpen(\n            logPosition, correlationId, clusterSessionId, timestamp, responseStreamId, responseChannel);\n    }\n\n    void onReplaySessionClose(final long clusterSessionId, final CloseReason closeReason)\n    {\n        sessionManager.onReplaySessionClose(clusterSessionId, closeReason);\n    }\n\n    void onReplayClusterAction(\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final ClusterAction action,\n        final int flags)\n    {\n        if (leadershipTermId == this.leadershipTermId)\n        {\n            if (ClusterAction.SUSPEND == action)\n            {\n                state(ConsensusModule.State.SUSPENDED, \"ReplayClusterAction.SUSPENDED\");\n            }\n            else if (ClusterAction.RESUME == action)\n            {\n                state(ConsensusModule.State.ACTIVE, \"ReplayClusterAction.ACTIVE\");\n            }\n            else if (ClusterAction.SNAPSHOT == action && CLUSTER_ACTION_FLAGS_DEFAULT == flags)\n            {\n                state(ConsensusModule.State.SNAPSHOT, \"ReplayClusterAction.SNAPSHOT\");\n                totalSnapshotDurationTracker.onSnapshotBegin(clusterClock.timeNanos());\n                if (0 == serviceCount)\n                {\n                    snapshotOnServiceAck(logPosition, timestamp, ServiceAck.EMPTY_SERVICE_ACKS);\n                }\n            }\n        }\n    }\n\n    void onReplayNewLeadershipTermEvent(\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final long termBaseLogPosition,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n        logOnReplayNewLeadershipTermEvent(\n            memberId,\n            null != election,\n            leadershipTermId,\n            logPosition,\n            timestamp,\n            termBaseLogPosition,\n            timeUnit,\n            appVersion);\n\n        if (timeUnit != clusterTimeUnit)\n        {\n            final String error = \"incompatible timestamp units: \" + clusterTimeUnit + \" log=\" + timeUnit;\n            ctx.countedErrorHandler().onError(new ClusterException(error, AeronException.Category.FATAL));\n            unexpectedTermination(error);\n        }\n\n        if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))\n        {\n            final String error = \"incompatible version: \" + SemanticVersion.toString(ctx.appVersion()) +\n                \" log=\" + SemanticVersion.toString(appVersion);\n            ctx.countedErrorHandler().onError(new ClusterException(error, AeronException.Category.FATAL));\n            unexpectedTermination(error);\n        }\n\n        leadershipTermId(leadershipTermId);\n\n        if (null != election)\n        {\n            election.onReplayNewLeadershipTermEvent(leadershipTermId, logPosition, timestamp, termBaseLogPosition);\n        }\n        if (null != consensusModuleExtension)\n        {\n            consensusModuleExtension.onNewLeadershipTerm(\n                new ConsensusControlState(null, null, logRecordingId, leadershipTermId));\n        }\n    }\n\n    int addLogPublication(final long appendPosition)\n    {\n        final long logPublicationTag = aeron.nextCorrelationId();\n        logPublicationChannelTag = aeron.nextCorrelationId();\n        final ChannelUri channelUri = ChannelUri.parse(ctx.logChannel());\n\n        channelUri.put(ALIAS_PARAM_NAME, \"log\");\n        channelUri.put(TAGS_PARAM_NAME, logPublicationChannelTag + \",\" + logPublicationTag);\n\n        if (channelUri.isUdp())\n        {\n            if (ctx.isLogMdc())\n            {\n                channelUri.put(MDC_CONTROL_MODE_PARAM_NAME, MDC_CONTROL_MODE_MANUAL);\n                ClusterMember.setControlEndpoint(channelUri, ctx.enableControlOnLogControl(), thisMember.logEndpoint());\n            }\n\n            channelUri.put(SPIES_SIMULATE_CONNECTION_PARAM_NAME, Boolean.toString(activeMembers.length == 1));\n        }\n\n        final RecordingLog.Log clusterLog = recoveryPlan.log();\n        if (null != clusterLog)\n        {\n            channelUri.initialPosition(appendPosition, clusterLog.initialTermId(), clusterLog.termBufferLength());\n            channelUri.put(MTU_LENGTH_PARAM_NAME, Integer.toString(clusterLog.mtuLength()));\n        }\n        else\n        {\n            ensureConsistentInitialTermId(channelUri);\n        }\n\n        final String channel = channelUri.toString();\n        final ExclusivePublication publication = aeron.addExclusivePublication(channel, ctx.logStreamId());\n        logPublisher.publication(publication);\n\n        if (ctx.isLogMdc())\n        {\n            for (final ClusterMember member : activeMembers)\n            {\n                if (member.id() != memberId)\n                {\n                    logPublisher.addDestination(member.logEndpoint());\n                }\n            }\n        }\n\n        return publication.sessionId();\n    }\n\n    void joinLogAsLeader(\n        final long leadershipTermId, final long logPosition, final int logSessionId, final boolean isStartup)\n    {\n        final boolean isIpc = ctx.logChannel().startsWith(IPC_CHANNEL);\n        final String channel = (isIpc ? IPC_CHANNEL : UDP_CHANNEL) +\n            \"?tags=\" + logPublicationChannelTag + \"|session-id=\" + logSessionId + \"|alias=log\";\n\n        leadershipTermId(leadershipTermId);\n        startLogRecording(channel, ctx.logStreamId(), SourceLocation.LOCAL);\n\n        idleStrategy.reset();\n        while (!tryCreateAppendPosition(logSessionId))\n        {\n            idle();\n        }\n\n        localLogChannel = isIpc ? channel : SPY_PREFIX + channel;\n        awaitServicesReady(\n            localLogChannel,\n            ctx.logStreamId(),\n            logSessionId,\n            logPosition,\n            Long.MAX_VALUE,\n            isStartup,\n            Cluster.Role.LEADER);\n\n        connectLeaderLogSubscriptionForExtension(logPosition);\n    }\n\n    private void connectLeaderLogSubscriptionForExtension(final long logPosition)\n    {\n        if (null != consensusModuleExtension)\n        {\n            final Subscription subscription = aeron.addSubscription(localLogChannel, ctx.logStreamId());\n\n            idleStrategy.reset();\n            while (0 == subscription.imageCount())\n            {\n                idle();\n            }\n\n            final long joinPosition = subscription.imageAtIndex(0).joinPosition();\n            if (joinPosition != logPosition)\n            {\n                throw new ClusterException(\n                    \"Extension subscription \" +\n                    \"joinPosition (\" + joinPosition + \") does not match logPosition (\" + logPosition + \")\");\n            }\n\n            this.extensionLeaderSubscription = subscription;\n        }\n    }\n\n    void liveLogDestination(final String liveLogDestination)\n    {\n        this.liveLogDestination = liveLogDestination;\n    }\n\n    String liveLogDestination()\n    {\n        return liveLogDestination;\n    }\n\n    void catchupLogDestination(final String catchupLogDestination)\n    {\n        this.catchupLogDestination = catchupLogDestination;\n    }\n\n    String catchupLogDestination()\n    {\n        return catchupLogDestination;\n    }\n\n    long notifiedCommitPosition()\n    {\n        return notifiedCommitPosition;\n    }\n\n    long timeOfLastLogUpdateNs()\n    {\n        return timeOfLastLogUpdateNs;\n    }\n\n    boolean tryJoinLogAsFollower(final Image image, final boolean isLeaderStartup, final long nowNs)\n    {\n        final Subscription logSubscription = image.subscription();\n\n        if (NULL_VALUE == logSubscriptionId)\n        {\n            startLogRecording(logSubscription.channel(), logSubscription.streamId(), SourceLocation.REMOTE);\n        }\n\n        if (tryCreateAppendPosition(image.sessionId()))\n        {\n            logAdapter.image(image);\n            lastAppendPosition = image.joinPosition();\n            timeOfLastAppendPositionUpdateNs = nowNs;\n\n            awaitServicesReady(\n                logSubscription.channel(),\n                logSubscription.streamId(),\n                image.sessionId(),\n                image.joinPosition(),\n                Long.MAX_VALUE,\n                isLeaderStartup,\n                Cluster.Role.FOLLOWER);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    void awaitServicesReady(\n        final String logChannel,\n        final int streamId,\n        final int logSessionId,\n        final long logPosition,\n        final long maxLogPosition,\n        final boolean isStartup,\n        final Cluster.Role role)\n    {\n        if (serviceCount > 0)\n        {\n            serviceProxy.joinLog(\n                logPosition,\n                maxLogPosition,\n                memberId,\n                logSessionId,\n                streamId,\n                isStartup,\n                role,\n                logChannel,\n                false);\n\n            expectedAckPosition = logPosition;\n\n            idleStrategy.reset();\n            while (!ServiceAck.hasReached(logPosition, serviceAckId, serviceAckQueues))\n            {\n                idle(consensusModuleAdapter.poll());\n                if (ConsensusModule.State.CLOSED == state)\n                {\n                    unexpectedTermination(\"State.CLOSED == state\");\n                }\n            }\n\n            ServiceAck.removeHead(serviceAckQueues);\n            ++serviceAckId;\n        }\n    }\n\n    LogReplay newLogReplay(final long logPosition, final long appendPosition)\n    {\n        return new LogReplay(archive, logRecordingId, logPosition, appendPosition, logAdapter, ctx);\n    }\n\n    int replayLogPoll(final LogAdapter logAdapter, final long stopPosition)\n    {\n        int workCount = 0;\n\n        if (ConsensusModule.State.ACTIVE == state || ConsensusModule.State.SUSPENDED == state)\n        {\n            logAdapter.poll(stopPosition);\n            final long position = logAdapter.position();\n\n            if (commitPosition.proposeMaxRelease(position))\n            {\n                workCount++;\n            }\n            else if (logAdapter.isImageClosed() && position < stopPosition)\n            {\n                throw new ClusterEvent(\"unexpected image close when replaying log: position=\" + position);\n            }\n        }\n\n        workCount += consensusModuleAdapter.poll();\n\n        return workCount;\n    }\n\n    long logRecordingId()\n    {\n        return logRecordingId;\n    }\n\n    void logRecordingId(final long recordingId)\n    {\n        if (NULL_VALUE != recordingId)\n        {\n            logRecordingId = recordingId;\n        }\n    }\n\n    void truncateLogEntry(final long leadershipTermId, final long logPosition)\n    {\n        archive.stopAllReplays(logRecordingId);\n        archive.truncateRecording(logRecordingId, logPosition);\n        if (NULL_VALUE != leadershipTermId)\n        {\n            recordingLog.commitLogPosition(leadershipTermId, logPosition);\n        }\n        logAdapter.disconnect(ctx.countedErrorHandler(), logPosition);\n        logRecordingStopPosition = logPosition;\n    }\n\n    boolean appendNewLeadershipTermEvent(final long nowNs)\n    {\n        return logPublisher.appendNewLeadershipTermEvent(\n            leadershipTermId,\n            clusterTimeUnit.convert(nowNs, NANOSECONDS),\n            election.logPosition(),\n            memberId,\n            logPublisher.sessionId(),\n            clusterTimeUnit,\n            ctx.appVersion());\n    }\n\n    void electionComplete(final long nowNs)\n    {\n        leadershipTermId(election.leadershipTermId());\n\n        if (Cluster.Role.LEADER == role)\n        {\n            timeOfLastLogUpdateNs = nowNs - leaderHeartbeatIntervalNs;\n            timerService.currentTime(clusterTimeUnit.convert(nowNs, NANOSECONDS));\n            ClusterControl.ToggleState.activate(controlToggle);\n            sessionManager.prepareSessionsForNewTerm(election.isLeaderStartup());\n        }\n        else\n        {\n            timeOfLastLogUpdateNs = nowNs;\n            timeOfLastAppendPositionUpdateNs = nowNs;\n            timeOfLastAppendPositionSendNs = nowNs;\n            localLogChannel = null;\n        }\n        NodeControl.ToggleState.activate(nodeControlToggle);\n\n        recoveryPlan = recordingLog.createRecoveryPlan(archive, serviceCount, logRecordingId);\n\n        final long logPosition = election.logPosition();\n        notifiedCommitPosition = max(notifiedCommitPosition, logPosition);\n        commitPosition.setRelease(logPosition);\n        updateMemberDetails(election.leader());\n\n        connectIngress();\n        if (null != consensusModuleExtension)\n        {\n            consensusModuleExtension.onElectionComplete(new ConsensusControlState(\n                logPublisher.publication(),\n                extensionLeaderSubscription,\n                logRecordingId,\n                leadershipTermId\n            ));\n        }\n\n        election = null;\n    }\n\n    void trackCatchupCompletion(\n        final ClusterMember follower, final long leadershipTermId, final short appendPositionFlags)\n    {\n        if (NULL_VALUE != follower.catchupReplaySessionId() || isCatchupAppendPosition(appendPositionFlags))\n        {\n            if (follower.logPosition() >= logPublisher.position())\n            {\n                if (NULL_VALUE != follower.catchupReplayCorrelationId())\n                {\n                    if (archive.archiveProxy().stopReplay(\n                        follower.catchupReplaySessionId(), aeron.nextCorrelationId(), archive.controlSessionId()))\n                    {\n                        follower.catchupReplayCorrelationId(NULL_VALUE);\n                    }\n                }\n\n                if (consensusPublisher.stopCatchup(follower.publication(), leadershipTermId, follower.id()))\n                {\n                    follower.catchupReplaySessionId(NULL_VALUE);\n                }\n            }\n        }\n    }\n\n    void catchupInitiated(final long nowNs)\n    {\n        timeOfLastAppendPositionUpdateNs = nowNs;\n        timeOfLastAppendPositionSendNs = nowNs;\n    }\n\n    int catchupPoll(final long limitPosition, final long nowNs)\n    {\n        int workCount = 0;\n\n        if (ConsensusModule.State.ACTIVE == state || ConsensusModule.State.SUSPENDED == state)\n        {\n            if (null == appendPosition)\n            {\n                throw new ClusterEvent(\n                    \"unexpected recording stop during catchup: position=\" + logAdapter.position());\n            }\n\n            final long currentAppendPosition = appendPosition.get();\n            final int fragments = logAdapter.poll(min(currentAppendPosition, limitPosition));\n            workCount += fragments;\n            if (0 == fragments && logAdapter.isImageClosed())\n            {\n                throw new ClusterEvent(\n                    \"unexpected image close during catchup: position=\" + logAdapter.position());\n            }\n\n            workCount += updateFollowerPosition(\n                election.leader().publication(),\n                nowNs,\n                leadershipTermId,\n                currentAppendPosition,\n                APPEND_POSITION_FLAG_CATCHUP);\n            commitPosition.proposeMaxRelease(logAdapter.position());\n        }\n\n        if (nowNs > (timeOfLastAppendPositionUpdateNs + leaderHeartbeatTimeoutNs) &&\n            ConsensusModule.State.ACTIVE == state)\n        {\n            throw new ClusterEvent(\n                \"no catchup progress:\" +\n                \" commitPosition=\" + commitPosition.getPlain() +\n                \" limitPosition=\" + limitPosition +\n                \" lastAppendPosition=\" + lastAppendPosition +\n                \" appendPosition=\" + (null != appendPosition ? appendPosition.getPlain() : NULL_POSITION) +\n                \" logPosition=\" + election.logPosition());\n        }\n\n        workCount += consensusModuleAdapter.poll();\n\n        return workCount;\n    }\n\n    boolean isCatchupNearLive(final long position)\n    {\n        final Image image = logAdapter.image();\n        if (null != image)\n        {\n            final long localPosition = image.position();\n            final long window = min(image.termBufferLength() >> 2, LIVE_ADD_MAX_WINDOW);\n\n            return localPosition >= (position - window);\n        }\n\n        return false;\n    }\n\n    void stopAllCatchups()\n    {\n        for (final ClusterMember member : activeMembers)\n        {\n            if (member.catchupReplaySessionId() != NULL_VALUE)\n            {\n                if (member.catchupReplayCorrelationId() != NULL_VALUE)\n                {\n                    try\n                    {\n                        archive.stopReplay(member.catchupReplaySessionId());\n                    }\n                    catch (final Exception ex)\n                    {\n                        ctx.countedErrorHandler().onError(new ClusterEvent(\"replay already stopped for catchup\"));\n                    }\n                }\n\n                member.catchupReplaySessionId(NULL_VALUE);\n                member.catchupReplayCorrelationId(NULL_VALUE);\n            }\n        }\n    }\n\n    int pollArchiveEvents()\n    {\n        int workCount = 0;\n\n        if (null != archive)\n        {\n            final RecordingSignalPoller poller = this.recordingSignalPoller;\n            workCount += poller.poll();\n\n            if (poller.isPollComplete())\n            {\n                final int templateId = poller.templateId();\n\n                if (ControlResponseDecoder.TEMPLATE_ID == templateId && poller.code() == ControlResponseCode.ERROR)\n                {\n                    for (final ClusterMember member : activeMembers)\n                    {\n                        if (member.catchupReplayCorrelationId() == poller.correlationId())\n                        {\n                            member.catchupReplaySessionId(NULL_VALUE);\n                            member.catchupReplayCorrelationId(NULL_VALUE);\n\n                            final String message = \"catchup replay failed - \" + poller.errorMessage();\n                            ctx.countedErrorHandler().onError(new ClusterEvent(message));\n                            return workCount;\n                        }\n                    }\n\n                    if (UNKNOWN_REPLAY == poller.relevantId())\n                    {\n                        final String message = \"replay no longer relevant - \" + poller.errorMessage();\n                        ctx.countedErrorHandler().onError(new ClusterEvent(message));\n                        return workCount;\n                    }\n\n                    final ArchiveException ex = new ArchiveException(\n                        poller.errorMessage(), (int)poller.relevantId(), poller.correlationId());\n\n                    if (ex.errorCode() == ArchiveException.STORAGE_SPACE)\n                    {\n                        ctx.countedErrorHandler().onError(ex);\n                        unexpectedTermination(poller.errorMessage());\n                    }\n\n                    if (null != election)\n                    {\n                        election.handleError(clusterClock.timeNanos(), ex);\n                    }\n                }\n                else if (RecordingSignalEventDecoder.TEMPLATE_ID == templateId)\n                {\n                    final long recordingId = poller.recordingId();\n                    final long position = poller.recordingPosition();\n                    final RecordingSignal signal = poller.recordingSignal();\n\n                    if (RecordingSignal.STOP == signal && recordingId == logRecordingId)\n                    {\n                        logRecordingStopPosition = position;\n\n                        if (null == election && ConsensusModule.State.ACTIVE == state)\n                        {\n                            final boolean isEos = logAdapter.isLogEndOfStreamAt(position);\n                            enterElection(isEos, \"log recording stopped: eos=\" + isEos);\n                            return workCount;\n                        }\n                    }\n\n                    if (null != election)\n                    {\n                        election.onRecordingSignal(poller.correlationId(), recordingId, position, signal);\n                    }\n                }\n            }\n            else if (0 == workCount && !poller.subscription().isConnected())\n            {\n                final String error = \"local archive is not connected\";\n                ctx.countedErrorHandler().onError(new ClusterEvent(error, AeronException.Category.ERROR));\n                unexpectedTermination(error);\n            }\n        }\n\n        return workCount;\n    }\n\n    void leadershipTermId(final long leadershipTermId)\n    {\n        this.leadershipTermId = leadershipTermId;\n        ctx.leadershipTermIdCounter().setRelease(leadershipTermId);\n        for (final PendingServiceMessageTracker tracker : pendingServiceMessageTrackers)\n        {\n            tracker.leadershipTermId(leadershipTermId);\n        }\n    }\n\n    private static void logOnNewLeadershipTerm(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long nextLeadershipTermId,\n        final long nextTermBaseLogPosition,\n        final long nextLogPosition,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long commitPosition,\n        final long leaderRecordingId,\n        final long timestamp,\n        final int leaderId,\n        final int logSessionId,\n        final int appVersion,\n        final boolean isStartup)\n    {\n    }\n\n    private static void logOnCommitPosition(\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final int leaderMemberId)\n    {\n    }\n\n    static void logAppendSessionOpen(\n        final int memberId,\n        final long id,\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final TimeUnit timeUnit)\n    {\n    }\n\n    static void logAppendSessionClose(\n        final int memberId,\n        final long id,\n        final CloseReason closeReason,\n        final long leadershipTermId,\n        final long timestamp,\n        final TimeUnit timeUnit)\n    {\n    }\n\n    private static void logOnReplayNewLeadershipTermEvent(\n        final int memberId,\n        final boolean isInElection,\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final long termBaseLogPosition,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n    }\n\n    private static void logOnRequestVote(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateId,\n        final int protocolVersion)\n    {\n    }\n\n    private static void logOnVote(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateId,\n        final int voterId,\n        final boolean vote)\n    {\n    }\n\n    private static void logOnAppendPosition(\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final short flags)\n    {\n    }\n\n    private static void logOnCanvassPosition(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long leadershipTermId,\n        final int followerMemberId,\n        final int protocolVersion)\n    {\n    }\n\n    static void logStandbySnapshotNotification(\n        final int memberId,\n        final long recordingId,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long timestamp,\n        final TimeUnit timeUnit,\n        final int serviceId,\n        final String archiveEndpoint)\n    {\n    }\n\n    private static void logOnStopCatchup(final int memberId, final long leadershipTermId, final int followerMemberId)\n    {\n    }\n\n    private static void logOnCatchupPosition(\n        final int memberId,\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final String catchupEndpoint)\n    {\n    }\n\n    private static void logOnTerminationPosition(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition)\n    {\n    }\n\n    private static void logOnTerminationAck(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final int senderMemberId)\n    {\n    }\n\n    private static void logOnServiceAck(\n        final int memberId,\n        final long logPosition,\n        final long timestamp,\n        final TimeUnit timeUnit,\n        final long ackId,\n        final long relevantId,\n        final int serviceId)\n    {\n    }\n\n    private static void logNewElection(\n        final int memberId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long appendedPosition,\n        final String reason)\n    {\n    }\n\n    static void logReplicationEnded(\n        final int memberId,\n        final String purpose,\n        final String controlUri,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long position,\n        final boolean hasSynced)\n    {\n    }\n\n    private void startLogRecording(final String channel, final int streamId, final SourceLocation sourceLocation)\n    {\n        try\n        {\n            final long logRecordingId = recordingLog.findLastTermRecordingId();\n\n            logSubscriptionId = RecordingPos.NULL_RECORDING_ID == logRecordingId ?\n                archive.startRecording(channel, streamId, sourceLocation, true) :\n                archive.extendRecording(logRecordingId, channel, streamId, sourceLocation, true);\n        }\n        catch (final ArchiveException ex)\n        {\n            if (ex.errorCode() == ArchiveException.STORAGE_SPACE)\n            {\n                ctx.countedErrorHandler().onError(ex);\n                unexpectedTermination(ex.getMessage());\n            }\n\n            throw ex;\n        }\n    }\n\n    private void updateMemberDetails(final ClusterMember newLeader)\n    {\n        leaderMember = newLeader;\n\n        for (final ClusterMember clusterMember : activeMembers)\n        {\n            clusterMember.isLeader(clusterMember.id() == leaderMember.id());\n        }\n\n        ingressEndpoints = ClusterMember.ingressEndpoints(activeMembers);\n    }\n\n    @SuppressWarnings(\"methodlength\")\n    private int slowTickWork(final long nowNs)\n    {\n        int workCount = aeronClientInvoker.invoke();\n        if (aeron.isClosed())\n        {\n            throw new AgentTerminationException(\"unexpected Aeron close\");\n        }\n        else if (ConsensusModule.State.CLOSED == state)\n        {\n            unexpectedTermination(\"State.CLOSED == state\");\n        }\n\n        if (nowNs >= markFileUpdateDeadlineNs)\n        {\n            markFileUpdateDeadlineNs = nowNs + MARK_FILE_UPDATE_INTERVAL_NS;\n            markFile.updateActivityTimestamp(clusterClock.timeMillis());\n        }\n\n        workCount += pollArchiveEvents();\n\n        workCount += sessionManager.sendRedirects(leadershipTermId, leaderMember.id(), nowNs);\n        workCount += sessionManager.sendRejections(leadershipTermId, leaderMember.id(), nowNs);\n\n        if (null == election)\n        {\n            if (Cluster.Role.LEADER == role)\n            {\n                workCount += checkClusterControlToggle(nowNs);\n\n                if (ConsensusModule.State.ACTIVE == state)\n                {\n                    workCount += sessionManager.processAllPendingSessions(nowNs, leaderMember.id(), leadershipTermId);\n\n                    workCount += sessionManager.checkSessions(\n                        nowNs, leadershipTermId, leaderMember.id(), ingressEndpoints);\n\n                    if (activeMembers.length > 1 &&\n                        !ClusterMember.hasActiveQuorum(activeMembers, nowNs, leaderHeartbeatTimeoutNs))\n                    {\n                        enterElection(false, \"inactive follower quorum\");\n                        workCount += 1;\n                    }\n                }\n                else if (ConsensusModule.State.TERMINATING == state)\n                {\n                    if (clusterTermination.canTerminate(activeMembers, nowNs))\n                    {\n                        recordingLog.commitLogPosition(leadershipTermId, terminationPosition);\n                        closeAndTerminate();\n                    }\n                }\n            }\n            else\n            {\n                if (Cluster.Role.FOLLOWER == role && ConsensusModule.State.ACTIVE == state)\n                {\n                    workCount += sessionManager.processPendingBackupSessions(\n                        nowNs, leaderMember.id(), leadershipTermId);\n                }\n\n                if (ConsensusModule.State.ACTIVE == state || ConsensusModule.State.SUSPENDED == state)\n                {\n                    if (nowNs >= (timeOfLastLogUpdateNs + leaderHeartbeatTimeoutNs) &&\n                        NULL_POSITION == terminationPosition)\n                    {\n                        enterElection(false, \"leader heartbeat timeout\");\n                        workCount += 1;\n                    }\n                }\n            }\n\n            if (ConsensusModule.State.ACTIVE == state)\n            {\n                workCount += checkNodeControlToggle();\n            }\n        }\n\n        workCount += sessionManager.processPendingStandbySnapshotNotifications(commitPosition.getPlain(), nowNs);\n\n        if (null != consensusModuleExtension)\n        {\n            workCount += consensusModuleExtension.slowTickWork(nowNs);\n        }\n\n        return workCount;\n    }\n\n    private int consensusWork(final long timestamp, final long nowNs)\n    {\n        int workCount = 0;\n\n        if (Cluster.Role.LEADER == role)\n        {\n            if (ConsensusModule.State.ACTIVE == state)\n            {\n                workCount += timerService.poll(timestamp);\n                for (final PendingServiceMessageTracker tracker : pendingServiceMessageTrackers)\n                {\n                    workCount += tracker.poll();\n                }\n                workCount += ingressAdapter.poll();\n            }\n\n            workCount += updateLeaderPosition(nowNs);\n        }\n        else\n        {\n            if (ConsensusModule.State.ACTIVE == state || ConsensusModule.State.SUSPENDED == state)\n            {\n                if (NULL_POSITION != terminationPosition && logAdapter.position() >= terminationPosition)\n                {\n                    state(ConsensusModule.State.TERMINATING, \"terminationPosition=\" + terminationPosition);\n                    if (serviceCount > 0)\n                    {\n                        serviceProxy.terminationPosition(terminationPosition, ctx.countedErrorHandler());\n                    }\n                    else\n                    {\n                        doTermination(logAdapter.position());\n                    }\n                }\n                else\n                {\n                    final long limit = null != appendPosition ? appendPosition.get() : logRecordingStopPosition;\n                    final int count = logAdapter.poll(min(notifiedCommitPosition, limit));\n                    if (0 == count && logAdapter.isImageClosed())\n                    {\n                        final boolean isEos = logAdapter.isLogEndOfStream();\n                        enterElection(isEos, \"log disconnected from leader: eos=\" + isEos);\n                        return 1;\n                    }\n\n                    commitPosition.proposeMaxRelease(logAdapter.position());\n                    workCount += ingressAdapter.poll();\n                    workCount += count;\n                }\n            }\n\n            workCount += updateFollowerPosition(nowNs);\n        }\n\n        workCount += consensusModuleAdapter.poll();\n        workCount += pollStandbySnapshotReplication(nowNs);\n        if (null != consensusModuleExtension)\n        {\n            workCount += consensusModuleExtension.consensusWork(nowNs);\n        }\n\n        return workCount;\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private int checkClusterControlToggle(final long nowNs)\n    {\n        if (ConsensusModule.State.ACTIVE == state)\n        {\n            switch (ClusterControl.ToggleState.get(controlToggle))\n            {\n                case SUSPEND:\n                {\n                    final long timestamp = clusterClock.time();\n                    if (appendAction(ClusterAction.SUSPEND, timestamp, CLUSTER_ACTION_FLAGS_DEFAULT))\n                    {\n                        offerPositionAndPreviousState(logPublisher.position(), state);\n                        state(ConsensusModule.State.SUSPENDED, \"ClusterControl.SUSPEND\");\n                    }\n                    break;\n                }\n\n                case SNAPSHOT:\n                {\n                    final long timestamp = clusterClock.time();\n                    if (appendAction(ClusterAction.SNAPSHOT, timestamp, CLUSTER_ACTION_FLAGS_DEFAULT))\n                    {\n                        offerPositionAndPreviousState(logPublisher.position(), state);\n                        state(ConsensusModule.State.SNAPSHOT, \"ClusterControl.SNAPSHOT\");\n                        totalSnapshotDurationTracker.onSnapshotBegin(nowNs);\n                    }\n                    break;\n                }\n\n                case STANDBY_SNAPSHOT:\n                {\n                    final long timestamp = clusterClock.time();\n                    if (appendAction(ClusterAction.SNAPSHOT, timestamp, CLUSTER_ACTION_FLAGS_STANDBY_SNAPSHOT))\n                    {\n                        ClusterControl.ToggleState.reset(controlToggle);\n                    }\n                    break;\n                }\n\n                case SHUTDOWN:\n                {\n                    final long timestamp = clusterClock.time();\n                    if (appendAction(ClusterAction.SNAPSHOT, timestamp, CLUSTER_ACTION_FLAGS_DEFAULT))\n                    {\n                        final long position = logPublisher.position();\n\n                        clusterTermination = new ClusterTermination(nowNs + ctx.terminationTimeoutNs());\n                        clusterTermination.terminationPosition(\n                            ctx.countedErrorHandler(),\n                            consensusPublisher,\n                            activeMembers,\n                            thisMember,\n                            leadershipTermId,\n                            position);\n                        terminationPosition = position;\n                        terminationLeadershipTermId = leadershipTermId;\n\n                        offerPositionAndPreviousState(logPublisher.position(), state);\n                        state(ConsensusModule.State.SNAPSHOT, \"ClusterControl.SHUTDOWN\");\n                        totalSnapshotDurationTracker.onSnapshotBegin(nowNs);\n                    }\n                    break;\n                }\n\n                case ABORT:\n                {\n                    final CountedErrorHandler errorHandler = ctx.countedErrorHandler();\n                    final long position = logPublisher.position();\n                    clusterTermination = new ClusterTermination(nowNs + ctx.terminationTimeoutNs());\n                    clusterTermination.terminationPosition(\n                        errorHandler, consensusPublisher, activeMembers, thisMember, leadershipTermId, position);\n                    terminationPosition = position;\n                    terminationLeadershipTermId = leadershipTermId;\n                    if (serviceCount > 0)\n                    {\n                        serviceProxy.terminationPosition(terminationPosition, errorHandler);\n                    }\n                    else\n                    {\n                        clusterTermination.onServicesTerminated();\n                    }\n                    state(ConsensusModule.State.TERMINATING, \"ClusterControl.ABORT\");\n                    break;\n                }\n\n                default:\n                    return 0;\n            }\n\n            return 1;\n        }\n        else if (ConsensusModule.State.SNAPSHOT == state)\n        {\n            if (0 == serviceCount && logPublisher.position() <= commitPosition.getWeak())\n            {\n                final long timestamp = clusterClock.time();\n                snapshotOnServiceAck(commitPosition.getWeak(), timestamp, ServiceAck.EMPTY_SERVICE_ACKS);\n            }\n        }\n        else if (ConsensusModule.State.SUSPENDED == state)\n        {\n            if (ClusterControl.ToggleState.RESUME == ClusterControl.ToggleState.get(controlToggle))\n            {\n                final long timestamp = clusterClock.time();\n                if (appendAction(ClusterAction.RESUME, timestamp, CLUSTER_ACTION_FLAGS_DEFAULT))\n                {\n                    offerPositionAndPreviousState(logPublisher.position(), state);\n                    state(ConsensusModule.State.ACTIVE, \"ClusterControl.RESUME\");\n                    ClusterControl.ToggleState.reset(controlToggle);\n                }\n\n                return 1;\n            }\n        }\n\n        return 0;\n    }\n\n    private void offerPositionAndPreviousState(final long logPublisherPosition, final ConsensusModule.State state)\n    {\n        uncommittedPreviousState.offerLong(logPublisherPosition);\n        uncommittedPreviousState.offerLong(state.code());\n    }\n\n    private int checkNodeControlToggle()\n    {\n        if (NodeControl.ToggleState.REPLICATE_STANDBY_SNAPSHOT == NodeControl.ToggleState.get(nodeControlToggle))\n        {\n            if (null == standbySnapshotReplicator)\n            {\n                standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n                    memberId,\n                    ctx.archiveContext(),\n                    recordingLog,\n                    serviceCount,\n                    ctx.leaderArchiveControlChannel(),\n                    ctx.archiveContext().controlRequestStreamId(),\n                    ctx.replicationChannel(),\n                    ctx.fileSyncLevel(),\n                    ctx.snapshotCounter());\n            }\n\n            NodeControl.ToggleState.reset(nodeControlToggle);\n\n            return 1;\n        }\n\n        return 0;\n    }\n\n    private boolean appendAction(final ClusterAction action, final long timestamp, final int flags)\n    {\n        return logPublisher.appendClusterAction(leadershipTermId, timestamp, action, flags);\n    }\n\n    private void captureServiceAck(final long logPosition, final long ackId, final long relevantId, final int serviceId)\n    {\n        if (0 == ackId && NULL_VALUE != serviceClientIds[serviceId])\n        {\n            throw new ClusterException(\n                \"initial ack already received from service: possible duplicate serviceId=\" + serviceId);\n        }\n\n        serviceAckQueues[serviceId].offerLast(new ServiceAck(ackId, logPosition, relevantId));\n    }\n\n    private boolean tryCreateAppendPosition(final int logSessionId)\n    {\n        final CountersReader counters = aeron.countersReader();\n        final int counterId = RecordingPos.findCounterIdBySession(counters, logSessionId, archive.archiveId());\n        if (CountersReader.NULL_COUNTER_ID == counterId)\n        {\n            return false;\n        }\n\n        final long registrationId = counters.getCounterRegistrationId(counterId);\n        if (0 == registrationId)\n        {\n            return false;\n        }\n\n        final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n        if (RecordingPos.NULL_RECORDING_ID == recordingId)\n        {\n            return false;\n        }\n\n        logRecordingId(recordingId);\n        appendPosition = new ReadableCounter(counters, registrationId, counterId);\n\n        return true;\n    }\n\n    private int updateFollowerPosition(final long nowNs)\n    {\n        final long recordedPosition = null != appendPosition ? appendPosition.get() : logRecordingStopPosition;\n        return updateFollowerPosition(\n            leaderMember.publication(), nowNs, leadershipTermId, recordedPosition, APPEND_POSITION_FLAG_NONE);\n    }\n\n    private int updateFollowerPosition(\n        final ExclusivePublication publication,\n        final long nowNs,\n        final long leadershipTermId,\n        final long appendPosition,\n        final short flags)\n    {\n        final long position = max(appendPosition, lastAppendPosition);\n        if (position > lastAppendPosition ||\n            nowNs >= (timeOfLastAppendPositionSendNs + leaderHeartbeatIntervalNs))\n        {\n            if (consensusPublisher.appendPosition(publication, leadershipTermId, position, memberId, flags))\n            {\n                if (position > lastAppendPosition)\n                {\n                    lastAppendPosition = position;\n                    timeOfLastAppendPositionUpdateNs = nowNs;\n                }\n                timeOfLastAppendPositionSendNs = nowNs;\n\n                return 1;\n            }\n        }\n\n        return 0;\n    }\n\n    private void loadSnapshot(final RecordingLog.Snapshot snapshot, final AeronArchive archive)\n    {\n        final String channel = ctx.replayChannel();\n        final int streamId = ctx.replayStreamId();\n        final int sessionId = (int)archive.startReplay(snapshot.recordingId(), 0, NULL_LENGTH, channel, streamId);\n        final String replayChannel = ChannelUri.addSessionId(channel, sessionId);\n\n        try (Subscription subscription = aeron.addSubscription(replayChannel, streamId))\n        {\n            final Image image = awaitImage(sessionId, subscription);\n            final ConsensusModuleSnapshotAdapter adapter = new ConsensusModuleSnapshotAdapter(image, this);\n\n            idleStrategy.reset();\n            while (true)\n            {\n                final int fragments = adapter.poll();\n                if (adapter.isDone())\n                {\n                    break;\n                }\n\n                if (0 == fragments)\n                {\n                    pollArchiveEvents();\n                    if (image.isClosed())\n                    {\n                        throw new ClusterException(\"snapshot ended unexpectedly: \" + image);\n                    }\n                }\n\n                idle(fragments);\n            }\n\n            for (final PendingServiceMessageTracker tracker : pendingServiceMessageTrackers)\n            {\n                tracker.verify();\n                tracker.reset();\n            }\n\n            if (null != consensusModuleExtension)\n            {\n                consensusModuleExtension.onStart(this, image);\n            }\n        }\n\n        timerService.currentTime(clusterClock.time());\n        commitPosition.setRelease(snapshot.logPosition());\n        leadershipTermId(snapshot.leadershipTermId());\n        expectedAckPosition = snapshot.logPosition();\n    }\n\n    private Image awaitImage(final int sessionId, final Subscription subscription)\n    {\n        idleStrategy.reset();\n        Image image;\n        while ((image = subscription.imageBySessionId(sessionId)) == null)\n        {\n            idle();\n        }\n\n        return image;\n    }\n\n    private Counter addRecoveryStateCounter(final RecordingLog.RecoveryPlan plan)\n    {\n        final int snapshotsCount = plan.snapshots().size();\n\n        if (snapshotsCount > 0)\n        {\n            final long[] serviceSnapshotRecordingIds = new long[snapshotsCount - 1];\n            final RecordingLog.Snapshot snapshot = plan.snapshots().get(0);\n\n            for (int i = 1; i < snapshotsCount; i++)\n            {\n                final RecordingLog.Snapshot serviceSnapshot = plan.snapshots().get(i);\n                serviceSnapshotRecordingIds[serviceSnapshot.serviceId()] = serviceSnapshot.recordingId();\n            }\n\n            return RecoveryState.allocate(\n                aeron,\n                snapshot.leadershipTermId(),\n                snapshot.logPosition(),\n                snapshot.timestamp(),\n                ctx.clusterId(),\n                serviceSnapshotRecordingIds);\n        }\n\n        return RecoveryState.allocate(aeron, leadershipTermId, 0, 0, ctx.clusterId());\n    }\n\n    private void captureServiceClientIds(final ServiceAck[] serviceAcks)\n    {\n        for (int i = 0, length = serviceAcks.length; i < length; i++)\n        {\n            serviceClientIds[i] = serviceAcks[i].relevantId();\n        }\n    }\n\n    private int updateLeaderPosition(final long nowNs)\n    {\n        if (null != appendPosition)\n        {\n            final long leaderAppendPosition = appendPosition.get();\n            return updateLeaderPosition(\n                nowNs, leaderAppendPosition, quorumPositionBoundedByLeaderLog(leaderAppendPosition, nowNs));\n        }\n\n        return 0;\n    }\n\n    long quorumPositionBoundedByLeaderLog(final long leaderAppendPosition, final long nowNs)\n    {\n        final long quorumPosition =\n            ClusterMember.quorumPosition(activeMembers, rankedPositions, nowNs, leaderHeartbeatTimeoutNs);\n        // there are two main cases here:\n        // 1) `quorumPosition <= leaderAppendPosition` - followers track leader\n        // 2) `quorumPosition > leaderAppendPosition` - leader's Archive is slow so that followers are able to persist\n        // log and notify their appendPosition faster\n        return min(quorumPosition, leaderAppendPosition);\n    }\n\n    long timeOfLastLeaderUpdateNs()\n    {\n        return timeOfLastLeaderUpdateNs;\n    }\n\n    int updateLeaderPosition(final long nowNs, final long appendPosition, final long quorumPosition)\n    {\n        thisMember.logPosition(appendPosition).timeOfLastAppendPositionNs(nowNs);\n\n        final long leaderCommitPosition = commitPosition.getPlain();\n        if (quorumPosition > leaderCommitPosition ||\n            nowNs >= (timeOfLastLogUpdateNs + leaderHeartbeatIntervalNs))\n        {\n            if (quorumPosition < leaderCommitPosition && leaderCommitPosition > lastQuorumBacktrackCommitPosition)\n            {\n                lastQuorumBacktrackCommitPosition = leaderCommitPosition;\n                ctx.countedErrorHandler().onError(new ClusterEvent(\"quorum position went backwards: \" +\n                    \"leaderCommitPosition=\" + leaderCommitPosition + \" quorumPosition=\" + quorumPosition));\n            }\n\n            publishCommitPosition(quorumPosition, leadershipTermId);\n\n            commitPosition.proposeMaxRelease(quorumPosition);\n            timeOfLastLogUpdateNs = nowNs;\n\n            sweepUncommittedEntriesTo(quorumPosition);\n            return 1;\n        }\n\n        return 0;\n    }\n\n    void publishCommitPosition(final long commitPosition, final long leadershipTermId)\n    {\n        for (final ClusterMember member : activeMembers)\n        {\n            if (member.id() != memberId)\n            {\n                consensusPublisher.commitPosition(member.publication(), leadershipTermId, commitPosition, memberId);\n            }\n        }\n    }\n\n    RecordingReplication newLogReplication(\n        final String leaderArchiveEndpoint,\n        final String responseArchiveEndpoint,\n        final long leaderRecordingId,\n        final long stopPosition,\n        final long nowNs)\n    {\n        String replicationChannel = ctx.replicationChannel();\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .dstRecordingId(logRecordingId)\n            .stopPosition(stopPosition)\n            .replicationSessionId((int)aeron.nextCorrelationId());\n\n        if (null != responseArchiveEndpoint)\n        {\n            final ChannelUri channelUri = ChannelUri.parse(replicationChannel);\n            channelUri.remove(ENDPOINT_PARAM_NAME);\n            channelUri.put(MDC_CONTROL_PARAM_NAME, responseArchiveEndpoint);\n            channelUri.put(MDC_CONTROL_MODE_PARAM_NAME, CONTROL_MODE_RESPONSE);\n            replicationChannel = channelUri.toString();\n\n            replicationParams.srcResponseChannel(replicationChannel);\n        }\n\n        replicationParams.replicationChannel(replicationChannel);\n\n        return new RecordingReplication(\n            archive,\n            leaderRecordingId,\n            ChannelUri.createDestinationUri(ctx.leaderArchiveControlChannel(), leaderArchiveEndpoint),\n            archive.context().controlRequestStreamId(),\n            replicationParams,\n            ctx.leaderHeartbeatTimeoutNs(),\n            ctx.leaderHeartbeatIntervalNs(),\n            nowNs);\n    }\n\n    void awaitLocalSocketsClosed(final long registrationId)\n    {\n        idleStrategy.reset();\n        final CountersReader countersReader = aeron.countersReader();\n        while (LocalSocketAddressStatus.findNumberOfAddressesByRegistrationId(countersReader, registrationId) > 0)\n        {\n            idle();\n        }\n    }\n\n    private void sweepUncommittedEntriesTo(final long commitPosition)\n    {\n        for (final PendingServiceMessageTracker tracker : pendingServiceMessageTrackers)\n        {\n            tracker.sweepLeaderMessages();\n        }\n\n        while (uncommittedTimers.peekLong() <= commitPosition)\n        {\n            uncommittedTimers.pollLong();\n            uncommittedTimers.pollLong();\n        }\n\n        sessionManager.sweepUncommittedSessions(commitPosition);\n\n        while (uncommittedPreviousState.peekLong() <= commitPosition)\n        {\n            uncommittedPreviousState.pollLong();\n            uncommittedPreviousState.pollLong();\n        }\n    }\n\n    private void restoreUncommittedEntries(final long commitPosition)\n    {\n        for (final LongArrayQueue.LongIterator i = uncommittedTimers.iterator(); i.hasNext(); )\n        {\n            final long appendPosition = i.nextValue();\n            final long correlationId = i.nextValue();\n\n            if (appendPosition > commitPosition)\n            {\n                timerService.scheduleTimerForCorrelationId(correlationId, 0);\n            }\n        }\n        uncommittedTimers.clear();\n\n        for (final PendingServiceMessageTracker tracker : pendingServiceMessageTrackers)\n        {\n            tracker.restoreUncommittedMessages();\n        }\n\n        sessionManager.restoreUncommittedSessions(commitPosition);\n\n        while (uncommittedPreviousState.peekLong() <= commitPosition)\n        {\n            uncommittedPreviousState.pollLong();\n            uncommittedPreviousState.pollLong();\n        }\n\n        if (!uncommittedPreviousState.isEmpty())\n        {\n            uncommittedPreviousState.pollLong();\n            final ConsensusModule.State committedState = ConsensusModule.State.get(uncommittedPreviousState.pollLong());\n            if (ConsensusModule.State.CLOSED != state)\n            {\n                state(committedState, \"rollback\");\n            }\n        }\n        uncommittedPreviousState.clear();\n    }\n\n    private void enterElection(final boolean isLogEndOfStream, final String reason)\n    {\n        if (null != election)\n        {\n            throw new IllegalStateException(\"election in progress\");\n        }\n\n        role(Cluster.Role.FOLLOWER);\n\n        final long leadershipTermId = this.leadershipTermId;\n        final RecordingLog.Entry termEntry = recordingLog.findTermEntry(leadershipTermId);\n        final long termBaseLogPosition = null != termEntry ?\n            termEntry.termBaseLogPosition : recoveryPlan.lastTermBaseLogPosition();\n        final long appendedPosition = null != appendPosition ?\n            appendPosition.get() : max(recoveryPlan.appendedLogPosition(), logRecordingStopPosition);\n        final long commitPosition = this.commitPosition.getPlain();\n\n        logNewElection(memberId, leadershipTermId, commitPosition, appendedPosition, reason);\n        ctx.countedErrorHandler().onError(new ClusterEvent(reason));\n\n        election = new Election(\n            false,\n            isLogEndOfStream ? leaderMember.id() : NULL_VALUE,\n            leadershipTermId,\n            termBaseLogPosition,\n            commitPosition,\n            appendedPosition,\n            activeMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            this);\n\n        election.doWork(clusterClock.timeNanos());\n    }\n\n    private static void checkInterruptStatus()\n    {\n        if (Thread.currentThread().isInterrupted())\n        {\n            throw new AgentTerminationException(\"interrupted\");\n        }\n    }\n\n    private void snapshotOnServiceAck(final long logPosition, final long timestamp, final ServiceAck[] serviceAcks)\n    {\n        if (isSnapshotSetComplete(serviceAcks))\n        {\n            try\n            {\n                takeSnapshot(timestamp, logPosition, serviceAcks);\n            }\n            catch (final RuntimeException ex)\n            {\n                ctx.countedErrorHandler().onError(new ClusterException(\"failed to take snapshot\", ex));\n                if (isTerminalError(ex))\n                {\n                    unexpectedTermination(ex.getMessage());\n                }\n            }\n        }\n\n        sessionManager.updateTimeOfLastActivity();\n\n        if (null != clusterTermination)\n        {\n            if (serviceCount > 0)\n            {\n                serviceProxy.terminationPosition(terminationPosition, ctx.countedErrorHandler());\n            }\n            else\n            {\n                clusterTermination.onServicesTerminated();\n            }\n            clusterTermination.deadlineNs(clusterClock.timeNanos() + ctx.terminationTimeoutNs());\n            state(ConsensusModule.State.TERMINATING, \"null != clusterTermination\");\n        }\n        else\n        {\n            state(ConsensusModule.State.ACTIVE, \"snapshot complete\");\n            if (Cluster.Role.LEADER == role)\n            {\n                ClusterControl.ToggleState.reset(controlToggle);\n            }\n        }\n    }\n\n    private static boolean isTerminalError(final RuntimeException ex)\n    {\n        return ex instanceof AgentTerminationException ||\n            (ex instanceof ArchiveException archiveError && archiveError.errorCode() == ArchiveException.STORAGE_SPACE);\n    }\n\n    private boolean isSnapshotSetComplete(final ServiceAck[] serviceAcks)\n    {\n        return ServiceAck.areAllRelevantIdsNonNull(\"failed to take snapshot\", serviceAcks, ctx.errorLog());\n    }\n\n    private void takeSnapshot(final long timestamp, final long logPosition, final ServiceAck[] serviceAcks)\n    {\n        final long recordingId;\n        try (ExclusivePublication publication = aeron.addExclusivePublication(\n            ctx.snapshotChannel(), ctx.snapshotStreamId()))\n        {\n            final String channel = ChannelUri.addSessionId(ctx.snapshotChannel(), publication.sessionId());\n            archive.startRecording(channel, ctx.snapshotStreamId(), LOCAL, true);\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = awaitRecordingCounter(counters, publication.sessionId(), archive.archiveId());\n            recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            snapshotState(publication, logPosition, leadershipTermId);\n\n            if (null != consensusModuleExtension)\n            {\n                consensusModuleExtension.onTakeSnapshot(publication);\n            }\n\n            awaitRecordingComplete(recordingId, publication.position(), counters, counterId);\n        }\n\n        final long termBaseLogPosition = recordingLog.getTermEntry(leadershipTermId).termBaseLogPosition;\n\n        for (int serviceId = serviceAcks.length - 1; serviceId >= 0; serviceId--)\n        {\n            final long snapshotId = serviceAcks[serviceId].relevantId();\n            recordingLog.appendSnapshot(\n                snapshotId, leadershipTermId, termBaseLogPosition, logPosition, timestamp, serviceId);\n        }\n\n        recordingLog.appendSnapshot(\n            recordingId, leadershipTermId, termBaseLogPosition, logPosition, timestamp, SERVICE_ID);\n\n        recordingLog.force(ctx.fileSyncLevel());\n        recoveryPlan = recordingLog.createRecoveryPlan(archive, serviceCount, Aeron.NULL_VALUE);\n        totalSnapshotDurationTracker.onSnapshotEnd(clusterClock.timeNanos());\n        ctx.snapshotCounter().incrementRelease();\n    }\n\n    private void awaitRecordingComplete(\n        final long recordingId, final long position, final CountersReader counters, final int counterId)\n    {\n        idleStrategy.reset();\n        while (counters.getCounterValue(counterId) < position)\n        {\n            idle();\n\n            if (!RecordingPos.isActive(counters, counterId, recordingId))\n            {\n                throw new ClusterException(\"recording has stopped unexpectedly: \" + recordingId);\n            }\n        }\n    }\n\n    private int awaitRecordingCounter(final CountersReader counters, final int sessionId, final long archiveId)\n    {\n        idleStrategy.reset();\n        int counterId = RecordingPos.findCounterIdBySession(counters, sessionId, archiveId);\n        while (CountersReader.NULL_COUNTER_ID == counterId)\n        {\n            idle();\n            counterId = RecordingPos.findCounterIdBySession(counters, sessionId, archiveId);\n        }\n\n        return counterId;\n    }\n\n    private void snapshotState(\n        final ExclusivePublication publication, final long logPosition, final long leadershipTermId)\n    {\n        final ConsensusModuleSnapshotTaker snapshotTaker = new ConsensusModuleSnapshotTaker(\n            publication, idleStrategy, aeronClientInvoker);\n\n        snapshotTaker.markBegin(SNAPSHOT_TYPE_ID, logPosition, leadershipTermId, 0, clusterTimeUnit, ctx.appVersion());\n\n        if (pendingServiceMessageTrackers.length > 0)\n        {\n            final PendingServiceMessageTracker trackerOne = pendingServiceMessageTrackers[0];\n            snapshotTaker.snapshotConsensusModuleState(\n                sessionManager.nextCommittedSessionId(),\n                trackerOne.nextServiceSessionId(),\n                trackerOne.logServiceSessionId(),\n                trackerOne.size());\n        }\n        else\n        {\n            snapshotTaker.snapshotConsensusModuleState(sessionManager.nextCommittedSessionId(), 0, 0, 0);\n        }\n\n        sessionManager.snapshotSessions(snapshotTaker);\n\n        timerService.snapshot(snapshotTaker);\n\n        for (final PendingServiceMessageTracker tracker : pendingServiceMessageTrackers)\n        {\n            snapshotTaker.snapshot(tracker, ctx.countedErrorHandler());\n        }\n\n        snapshotTaker.markEnd(SNAPSHOT_TYPE_ID, logPosition, leadershipTermId, 0, clusterTimeUnit, ctx.appVersion());\n    }\n\n    private void onUnavailableIngressImage(final Image image)\n    {\n        if (Cluster.Role.LEADER == role && ConsensusModule.State.ACTIVE == state)\n        {\n            sessionManager.timeoutOnUnavailableImage(image.correlationId(), this);\n        }\n\n        final boolean isIpc = image.subscription().channel().startsWith(IPC_CHANNEL);\n        ingressAdapter.freeSessionBuffer(image.sessionId(), isIpc);\n    }\n\n    private void onUnavailableCounter(final CountersReader counters, final long registrationId, final int counterId)\n    {\n        if (ConsensusModule.State.TERMINATING != state && ConsensusModule.State.QUITTING != state)\n        {\n            for (int i = 0; i < serviceClientIds.length; i++)\n            {\n                final long clientId = serviceClientIds[i];\n                if (registrationId == clientId)\n                {\n                    final String msg = \"Aeron client in service closed unexpectedly: serviceId=\" + i;\n                    ctx.countedErrorHandler().onError(new ClusterEvent(msg));\n                    state(ConsensusModule.State.CLOSED, msg);\n                    return;\n                }\n            }\n\n            if (null != appendPosition && appendPosition.registrationId() == registrationId)\n            {\n                appendPosition = null;\n                logSubscriptionId = NULL_VALUE;\n            }\n        }\n    }\n\n    private void closeAndTerminate()\n    {\n        tryStopLogRecording();\n        state(ConsensusModule.State.CLOSED, \"expected termination\");\n        throw new ClusterTerminationException(true);\n    }\n\n    private void unexpectedTermination(final String terminationReason)\n    {\n        aeron.removeUnavailableCounterHandler(unavailableCounterHandlerRegistrationId);\n        if (serviceCount > 0)\n        {\n            serviceProxy.terminationPosition(0, ctx.countedErrorHandler());\n        }\n        tryStopLogRecording();\n        state(ConsensusModule.State.CLOSED, terminationReason);\n        throw new ClusterTerminationException(false);\n    }\n\n    private void terminateOnServiceAck(final long logPosition)\n    {\n        if (null != clusterTermination)\n        {\n            clusterTermination.onServicesTerminated();\n        }\n\n        doTermination(logPosition);\n    }\n\n    private void doTermination(final long logPosition)\n    {\n        if (null == clusterTermination)\n        {\n            if (terminationLeadershipTermId == leadershipTermId)\n            {\n                consensusPublisher.terminationAck(\n                    leaderMember.publication(), leadershipTermId, logPosition, memberId);\n            }\n            else\n            {\n                final String message = \"termination ack not sent - different leadership term to request\";\n                ctx.countedErrorHandler().onError(new ClusterEvent(message, AeronException.Category.ERROR));\n            }\n            recordingLog.commitLogPosition(leadershipTermId, logPosition);\n            closeAndTerminate();\n        }\n        else\n        {\n            if (clusterTermination.canTerminate(activeMembers, clusterClock.timeNanos()))\n            {\n                recordingLog.commitLogPosition(leadershipTermId, logPosition);\n                closeAndTerminate();\n            }\n        }\n    }\n\n    private void tryStopLogRecording()\n    {\n        appendPosition = null;\n\n        if (NULL_VALUE != logSubscriptionId && archive.archiveProxy().publication().isConnected())\n        {\n            try\n            {\n                archive.tryStopRecording(logSubscriptionId);\n            }\n            catch (final Exception ex)\n            {\n                ctx.countedErrorHandler().onError(new ClusterException(ex, WARN));\n            }\n\n            logSubscriptionId = NULL_VALUE;\n        }\n        else if (NULL_VALUE != logRecordingId && archive.archiveProxy().publication().isConnected())\n        {\n            try\n            {\n                archive.tryStopRecordingByIdentity(logRecordingId);\n            }\n            catch (final Exception ex)\n            {\n                ctx.countedErrorHandler().onError(new ClusterException(ex, WARN));\n            }\n        }\n    }\n\n    private long getLastAppendedPosition()\n    {\n        idleStrategy.reset();\n        while (true)\n        {\n            final long appendPosition = archive.getStopPosition(logRecordingId);\n            if (NULL_POSITION != appendPosition)\n            {\n                return appendPosition;\n            }\n\n            idle();\n        }\n    }\n\n    private void connectIngress()\n    {\n        final ChannelUri ingressUri = ChannelUri.parse(ctx.ingressChannel());\n        if (!ingressUri.containsKey(ENDPOINT_PARAM_NAME))\n        {\n            ingressUri.put(ENDPOINT_PARAM_NAME, thisMember.ingressEndpoint());\n        }\n\n        if (Cluster.Role.LEADER != role && UdpChannel.isMulticastDestinationAddress(ingressUri))\n        {\n            return; // don't subscribe to ingress if follower and multicast ingress\n        }\n\n        ingressUri.put(REJOIN_PARAM_NAME, \"false\");\n        ingressUri.put(RELIABLE_STREAM_PARAM_NAME, \"true\");\n\n        final Subscription subscription = aeron.addSubscription(\n            ingressUri.toString(), ctx.ingressStreamId(), null, this::onUnavailableIngressImage);\n\n        Subscription ipcSubscription = null;\n        if (Cluster.Role.LEADER == role && ctx.isIpcIngressAllowed())\n        {\n            ipcSubscription = aeron.addSubscription(\n                IPC_CHANNEL, ctx.ingressStreamId(), null, this::onUnavailableIngressImage);\n        }\n\n        ingressAdapter.connect(subscription, ipcSubscription);\n    }\n\n    private void ensureConsistentInitialTermId(final ChannelUri channelUri)\n    {\n        channelUri.put(INITIAL_TERM_ID_PARAM_NAME, \"0\");\n        channelUri.put(TERM_ID_PARAM_NAME, \"0\");\n        channelUri.put(TERM_OFFSET_PARAM_NAME, \"0\");\n    }\n\n    private void checkFollowerForConsensusPublication(final int followerMemberId)\n    {\n        final ClusterMember follower = clusterMemberByIdMap.get(followerMemberId);\n        if (null != follower && null == follower.publication())\n        {\n            ClusterMember.addConsensusPublication(\n                thisMember,\n                follower,\n                ctx.consensusChannel(),\n                ctx.consensusStreamId(),\n                ctx.enableControlOnConsensusChannel(),\n                aeron,\n                ctx.countedErrorHandler());\n        }\n    }\n\n    private void runTerminationHook()\n    {\n        try\n        {\n            ctx.terminationHook().run();\n        }\n        catch (final Exception ex)\n        {\n            ctx.countedErrorHandler().onError(ex);\n        }\n    }\n\n    private String refineResponseChannel(final String responseChannel)\n    {\n        if (null == responseChannelTemplate)\n        {\n            return responseChannel;\n        }\n        else if (responseChannel.startsWith(IPC_CHANNEL))\n        {\n            return ctx.isIpcIngressAllowed() ? responseChannel : ctx.egressChannel();\n        }\n        else\n        {\n            final ChannelUri channelUri = ChannelUri.parse(responseChannel);\n            responseChannelTemplate.forEachParameter(channelUri::put);\n            return channelUri.toString();\n        }\n    }\n\n    private void stopExistingCatchupReplay(final ClusterMember follower)\n    {\n        if (NULL_VALUE != follower.catchupReplaySessionId())\n        {\n            if (archive.archiveProxy().stopReplay(\n                follower.catchupReplaySessionId(), aeron.nextCorrelationId(), archive.controlSessionId()))\n            {\n                follower.catchupReplaySessionId(NULL_VALUE);\n                follower.catchupReplayCorrelationId(NULL_VALUE);\n            }\n        }\n    }\n\n    private static boolean isCatchupAppendPosition(final short flags)\n    {\n        return 0 != (APPEND_POSITION_FLAG_CATCHUP & flags);\n    }\n\n    @SuppressWarnings(\"try\")\n    private RecordingLog.RecoveryPlan recoverFromSnapshotAndLog()\n    {\n        final RecordingLog.RecoveryPlan recoveryPlan = recordingLog.createRecoveryPlan(\n            archive, serviceCount, logRecordingId);\n        if (null != recoveryPlan.log())\n        {\n            logRecordingId(recoveryPlan.log().recordingId());\n        }\n\n        try (Counter ignore = addRecoveryStateCounter(recoveryPlan))\n        {\n            if (!recoveryPlan.snapshots().isEmpty())\n            {\n                loadSnapshot(recoveryPlan.snapshots().get(0), archive);\n            }\n            else if (null != consensusModuleExtension)\n            {\n                consensusModuleExtension.onStart(this, null);\n            }\n\n            idleStrategy.reset();\n            while (!ServiceAck.hasReached(expectedAckPosition, serviceAckId, serviceAckQueues))\n            {\n                idle(consensusModuleAdapter.poll());\n            }\n\n            final ServiceAck[] serviceAcks = ServiceAck.pollServiceAcks(serviceAckQueues);\n            if (!ServiceAck.areAllRelevantIdsNonNull(\"failed to start clustered service\", serviceAcks, ctx.errorLog()))\n            {\n                throw new AgentTerminationException(\"failed to start clustered service(s)\");\n            }\n\n            captureServiceClientIds(serviceAcks);\n            ++serviceAckId;\n        }\n\n        return recoveryPlan;\n    }\n\n    private RecordingLog.RecoveryPlan recoverFromBootstrapState()\n    {\n        final ConsensusModuleStateExport bootstrapState = ctx.bootstrapState();\n\n        logRecordingId(bootstrapState.logRecordingId);\n        final RecordingLog.RecoveryPlan recoveryPlan = recordingLog.createRecoveryPlan(\n            archive, serviceCount, logRecordingId);\n\n        expectedAckPosition = bootstrapState.expectedAckPosition;\n        serviceAckId = bootstrapState.serviceAckId;\n        leadershipTermId = bootstrapState.leadershipTermId;\n        sessionManager.loadNextSessionId(bootstrapState.nextSessionId);\n\n        for (final ConsensusModuleStateExport.TimerStateExport timer : bootstrapState.timers)\n        {\n            onLoadTimer(timer.correlationId, timer.deadline, null, 0, 0);\n        }\n\n        for (final ConsensusModuleStateExport.ClusterSessionStateExport sessionExport : bootstrapState.sessions)\n        {\n            onLoadClusterSession(\n                sessionExport.id,\n                sessionExport.correlationId,\n                sessionExport.openedLogPosition,\n                sessionExport.timeOfLastActivityNs,\n                sessionExport.closeReason,\n                sessionExport.responseStreamId,\n                sessionExport.responseChannel,\n                null,\n                0,\n                0);\n        }\n\n        final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n        final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n        final ExpandableRingBuffer.MessageConsumer consumer =\n            (buffer, offset, length, headOffset) ->\n            {\n                sessionMessageHeaderDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n                onLoadPendingMessage(sessionMessageHeaderDecoder.clusterSessionId(), buffer, offset, length);\n                return true;\n            };\n\n        for (final ConsensusModuleStateExport.PendingServiceMessageTrackerStateExport tracker :\n            bootstrapState.pendingMessageTrackers)\n        {\n            onLoadPendingMessageTracker(\n                tracker.nextServiceSessionId,\n                tracker.logServiceSessionId,\n                tracker.capacity,\n                tracker.serviceId,\n                null, 0, 0);\n\n            tracker.pendingMessages.forEach(consumer, Integer.MAX_VALUE);\n        }\n\n        serviceProxy.requestServiceAck(expectedAckPosition);\n\n        idleStrategy.reset();\n        while (!ServiceAck.hasReached(expectedAckPosition, serviceAckId, serviceAckQueues))\n        {\n            idle(consensusModuleAdapter.poll());\n        }\n\n        final ServiceAck[] serviceAcks = ServiceAck.pollServiceAcks(serviceAckQueues);\n\n        captureServiceClientIds(serviceAcks);\n        ++serviceAckId;\n\n        return recoveryPlan;\n    }\n\n    private void replicateStandbySnapshotsForStartup()\n    {\n        try (StandbySnapshotReplicator standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n            memberId,\n            ctx.archiveContext(),\n            recordingLog,\n            serviceCount,\n            ctx.leaderArchiveControlChannel(),\n            ctx.archiveContext().controlRequestStreamId(),\n            ctx.replicationChannel(),\n            ctx.fileSyncLevel(),\n            ctx.snapshotCounter()))\n        {\n            idleStrategy.reset();\n            while (!standbySnapshotReplicator.isComplete())\n            {\n                try\n                {\n                    idleStrategy.idle(standbySnapshotReplicator.poll(ctx.clusterClock().timeNanos()));\n                }\n                catch (final ClusterException ex)\n                {\n                    ctx.countedErrorHandler().onError(ex);\n                    break;\n                }\n\n                checkInterruptStatus();\n                aeronClientInvoker.invoke();\n                if (aeron.isClosed())\n                {\n                    throw new AgentTerminationException(\"unexpected Aeron close\");\n                }\n            }\n        }\n    }\n\n    private int pollStandbySnapshotReplication(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (null != standbySnapshotReplicator)\n        {\n            try\n            {\n                workCount += standbySnapshotReplicator.poll(nowNs);\n\n                if (standbySnapshotReplicator.isComplete())\n                {\n                    recoveryPlan = recordingLog.createRecoveryPlan(archive, ctx.serviceCount(), Aeron.NULL_VALUE);\n                    CloseHelper.quietClose(standbySnapshotReplicator);\n                    standbySnapshotReplicator = null;\n                }\n            }\n            catch (final ClusterException ex)\n            {\n                ctx.countedErrorHandler().onError(ex);\n                CloseHelper.quietClose(standbySnapshotReplicator);\n                standbySnapshotReplicator = null;\n            }\n        }\n\n        return workCount;\n    }\n\n    public String toString()\n    {\n        return \"ConsensusModuleAgent{\" +\n            \"memberId=\" + memberId +\n            \", election=\" + election +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.security.AuthorisationService;\n\nimport org.agrona.concurrent.IdleStrategy;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Control interface for performing operations on the consensus module from a {@link ConsensusModuleExtension}.\n */\npublic interface ConsensusModuleControl\n{\n    /**\n     * The unique id for the hosting member of the cluster.\n     *\n     * @return unique id for the hosting member of the cluster.\n     */\n    int memberId();\n\n    /**\n     * Cluster time as {@link #timeUnit()}s since 1 Jan 1970 UTC.\n     *\n     * @return time as {@link #timeUnit()}s since 1 Jan 1970 UTC.\n     * @see #timeUnit()\n     */\n    long time();\n\n    /**\n     * The unit of time applied when timestamping and invoking {@link #time()} operations.\n     *\n     * @return the unit of time applied when timestamping and invoking {@link #time()} operations.\n     * @see #time()\n     */\n    TimeUnit timeUnit();\n\n    /**\n     * {@link IdleStrategy} which should be used by the extension when it experiences back-pressure or is undertaking\n     * any long-running actions.\n     *\n     * @return the {@link IdleStrategy} which should be used by the extension when it experiences back-pressure or is\n     * undertaking any long-running actions.\n     */\n    IdleStrategy idleStrategy();\n\n    /**\n     * The {@link ConsensusModule.Context} under which the extension is running.\n     *\n     * @return the {@link ConsensusModule.Context} under which the extension is running.\n     */\n    ConsensusModule.Context context();\n\n    /**\n     * The {@link Aeron} client to be used by the extension.\n     *\n     * @return the {@link Aeron} client to be used by the extension.\n     */\n    Aeron aeron();\n\n    /**\n     * The {@link AeronArchive} client to be used by the extension.\n     *\n     * @return the {@link AeronArchive} client to be used by the extension.\n     */\n    AeronArchive archive();\n\n    /**\n     * The {@link AuthorisationService} used by the consensus module.\n     *\n     * @return the {@link AuthorisationService} used by the consensus module.\n     */\n    AuthorisationService authorisationService();\n\n    /**\n     * Lookup a {@link ClusterClientSession} for a given id.\n     *\n     * @param clusterSessionId for the session to lookup.\n     * @return a {@link ClusterClientSession} for a given id, otherwise {@code null} if not found.\n     */\n    ClusterClientSession getClientSession(long clusterSessionId);\n\n    /**\n     * Close a cluster session as an administrative function.\n     *\n     * @param clusterSessionId to be closed.\n     */\n    void closeClusterSession(long clusterSessionId);\n\n    /**\n     * Numeric id for the commit position counter.\n     *\n     * @return commit position counter id.\n     */\n    int commitPositionCounterId();\n\n    /**\n     * Numeric id for the cluster (used when running multiple clusters on the same media driver).\n     *\n     * @return numeric id for the cluster.\n     * @see ConsensusModule.Context#clusterId(int)\n     */\n    int clusterId();\n\n    /**\n     * The current cluster member for this node.\n     *\n     * @return cluster member for this node.\n     */\n    ClusterMember clusterMember();\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleExtension.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.api.InternalApi;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.AgentTerminationException;\n\n/**\n * Extension for handling messages from external schemas unknown to core Aeron Cluster code\n * thus providing an extension to the core ingress consensus module behaviour.\n */\n@InternalApi\npublic interface ConsensusModuleExtension extends AutoCloseable\n{\n    /**\n     * Schema supported by this extension.\n     *\n     * @return schema id supported.\n     */\n    int supportedSchemaId();\n\n    /**\n     * Start event where the extension can perform any initialisation required and load snapshot state.\n     * The snapshot image can be null if no previous snapshot exists.\n     * <p>\n     * <b>Note:</b> As this is a potentially long-running operation the implementation should use\n     * {@link Cluster#idleStrategy()} and then occasionally call {@link org.agrona.concurrent.IdleStrategy#idle()} or\n     * {@link org.agrona.concurrent.IdleStrategy#idle(int)}, especially when polling the {@link Image} returns 0.\n     *\n     * @param consensusModuleControl with which the extension can interact.\n     * @param snapshotImage          from which the extension can load its state which can be null when no snapshot.\n     */\n    void onStart(ConsensusModuleControl consensusModuleControl, Image snapshotImage);\n\n    /**\n     * An extension should implement this method to do its work. Long-running operations should be decomposed.\n     * <p>\n     * The return value is used for implementing an idle strategy that can be employed when no work is\n     * currently available for the extension to process.\n     * <p>\n     * If the extension wished to terminate and close then a {@link AgentTerminationException} can be thrown.\n     *\n     * @param nowNs is cluster time in nanoseconds.\n     * @return 0 to indicate no work was currently available, a positive value otherwise.\n     */\n    int doWork(long nowNs);\n\n    /**\n     * Similar to {@link #doWork(long)}, but executed less frequently.\n     *\n     * @param nowNs is cluster time in nanoseconds.\n     * @return 0 to indicate no work was currently available, a positive value otherwise.\n     */\n    int slowTickWork(long nowNs);\n\n    /**\n     * Similar to {@link #doWork(long)}, but executed only when there's no election in progress.\n     *\n     * @param nowNs is cluster time in nanoseconds.\n     * @return 0 to indicate no work was currently available, a positive value otherwise.\n     */\n    int consensusWork(long nowNs);\n\n    /**\n     * Cluster election is complete and new publication is added for the leadership term. If the node is a follower\n     * then the publication will be null.\n     *\n     * @param consensusControlState state to allow extension to control the consensus module.\n     */\n    void onElectionComplete(ConsensusControlState consensusControlState);\n\n    /**\n     * New Leadership term and the consensus control state has changed.\n     *\n     * @param consensusControlState state to allow extension to control the consensus module.\n     */\n    void onNewLeadershipTerm(ConsensusControlState consensusControlState);\n\n    /**\n     * Callback for handling messages received as ingress to a cluster.\n     * <p>\n     * Within this callback reentrant calls to the {@link io.aeron.Aeron} client are not permitted and\n     * will result in undefined behaviour.\n     *\n     * @param actingBlockLength acting block length.\n     * @param templateId        the message template id (already parsed from header).\n     * @param schemaId          the schema id.\n     * @param actingVersion     acting version from header\n     * @param buffer            containing the data.\n     * @param offset            at which the data begins.\n     * @param length            of the data in bytes.\n     * @param header            representing the metadata for the data.\n     * @return The action to be taken with regard to the stream position after the callback.\n     */\n    ControlledFragmentHandler.Action onIngressExtensionMessage(\n        int actingBlockLength,\n        int templateId,\n        int schemaId,\n        int actingVersion,\n        DirectBuffer buffer,\n        int offset,\n        int length,\n        Header header);\n\n    /**\n     * Callback for handling committed log messages (for follower or recovery).\n     * <p>\n     * Within this callback reentrant calls to the {@link io.aeron.Aeron} client are not permitted and\n     * will result in undefined behaviour.\n     *\n     * @param actingBlockLength acting block length.\n     * @param templateId        the message template id (already parsed from header).\n     * @param schemaId          the schema id.\n     * @param actingVersion     acting version from header\n     * @param buffer            containing the data.\n     * @param offset            at which the data begins.\n     * @param length            of the data in bytes.\n     * @param header            representing the metadata for the data.\n     * @return The action to be taken with regard to the stream position after the callback.\n     */\n    ControlledFragmentHandler.Action onLogExtensionMessage(\n        int actingBlockLength,\n        int templateId,\n        int schemaId,\n        int actingVersion,\n        DirectBuffer buffer,\n        int offset,\n        int length,\n        Header header);\n\n    /**\n     * {@inheritDoc}\n     */\n    void close();\n\n    /**\n     * Callback indicating a cluster session has opened.\n     *\n     * @param clusterSessionId of the opened session which is unique and not reused.\n     */\n    void onSessionOpened(long clusterSessionId);\n\n    /**\n     * Callback indicating a cluster session has closed.\n     *\n     * @param clusterSessionId  of the opened session which is unique and not reused.\n     * @param closeReason       reason to closing session\n     */\n    void onSessionClosed(long clusterSessionId, CloseReason closeReason);\n\n    /**\n     * Callback when preparing for a new Raft leadership term - before election.\n     */\n    void onPrepareForNewLeadership();\n\n    /**\n     * The extension should take a snapshot and store its state to the provided archive {@link ExclusivePublication}.\n     * <p>\n     * <b>Note:</b> As this is a potentially long-running operation the implementation should use\n     * {@link Cluster#idleStrategy()} and then occasionally call {@link org.agrona.concurrent.IdleStrategy#idle()} or\n     * {@link org.agrona.concurrent.IdleStrategy#idle(int)},\n     * especially when the snapshot {@link ExclusivePublication} returns {@link Publication#BACK_PRESSURED}.\n     *\n     * @param snapshotPublication to which the state should be recorded.\n     */\n    void onTakeSnapshot(ExclusivePublication snapshotPublication);\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleSnapshotAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Image;\nimport io.aeron.ImageControlledFragmentAssembler;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\nimport static io.aeron.cluster.ConsensusModule.Configuration.SNAPSHOT_TYPE_ID;\n\nclass ConsensusModuleSnapshotAdapter implements ControlledFragmentHandler\n{\n    static final int FRAGMENT_LIMIT = 10;\n\n    private boolean inSnapshot = false;\n    private boolean isDone = false;\n\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SnapshotMarkerDecoder snapshotMarkerDecoder = new SnapshotMarkerDecoder();\n    private final ClusterSessionDecoder clusterSessionDecoder = new ClusterSessionDecoder();\n    private final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n    private final TimerDecoder timerDecoder = new TimerDecoder();\n    private final ConsensusModuleDecoder consensusModuleDecoder = new ConsensusModuleDecoder();\n    private final ClusterMembersDecoder clusterMembersDecoder = new ClusterMembersDecoder();\n    private final PendingMessageTrackerDecoder pendingMessageTrackerDecoder = new PendingMessageTrackerDecoder();\n    private final ImageControlledFragmentAssembler fragmentAssembler = new ImageControlledFragmentAssembler(this);\n    private final Image image;\n    private final ConsensusModuleSnapshotListener listener;\n\n    ConsensusModuleSnapshotAdapter(final Image image, final ConsensusModuleSnapshotListener listener)\n    {\n        this.image = image;\n        this.listener = listener;\n    }\n\n    boolean isDone()\n    {\n        return isDone;\n    }\n\n    int poll()\n    {\n        return image.controlledPoll(fragmentAssembler, FRAGMENT_LIMIT);\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (MessageHeaderDecoder.SCHEMA_ID != schemaId)\n        {\n            throw new ClusterException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (messageHeaderDecoder.templateId())\n        {\n            case SessionMessageHeaderDecoder.TEMPLATE_ID:\n                sessionMessageHeaderDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                listener.onLoadPendingMessage(sessionMessageHeaderDecoder.clusterSessionId(), buffer, offset, length);\n                break;\n\n            case SnapshotMarkerDecoder.TEMPLATE_ID:\n                snapshotMarkerDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long typeId = snapshotMarkerDecoder.typeId();\n                if (SNAPSHOT_TYPE_ID != typeId)\n                {\n                    throw new ClusterException(\"unexpected snapshot type: \" + typeId);\n                }\n\n                switch (snapshotMarkerDecoder.mark())\n                {\n                    case BEGIN:\n                        if (inSnapshot)\n                        {\n                            throw new ClusterException(\"already in snapshot\");\n                        }\n                        inSnapshot = true;\n\n                        listener.onLoadBeginSnapshot(\n                            snapshotMarkerDecoder.appVersion(),\n                            ClusterClock.map(snapshotMarkerDecoder.timeUnit()),\n                            buffer,\n                            offset,\n                            length);\n                        return Action.CONTINUE;\n\n                    case END:\n                        if (!inSnapshot)\n                        {\n                            throw new ClusterException(\"missing begin snapshot\");\n                        }\n                        listener.onLoadEndSnapshot(buffer, offset, length);\n                        isDone = true;\n                        return Action.BREAK;\n\n                    case SECTION:\n                    case NULL_VAL:\n                        break;\n                }\n                break;\n\n            case ClusterSessionDecoder.TEMPLATE_ID:\n                clusterSessionDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                listener.onLoadClusterSession(\n                    clusterSessionDecoder.clusterSessionId(),\n                    clusterSessionDecoder.correlationId(),\n                    clusterSessionDecoder.openedLogPosition(),\n                    clusterSessionDecoder.timeOfLastActivity(),\n                    clusterSessionDecoder.closeReason(),\n                    clusterSessionDecoder.responseStreamId(),\n                    clusterSessionDecoder.responseChannel(),\n                    buffer,\n                    offset,\n                    length);\n                break;\n\n            case TimerDecoder.TEMPLATE_ID:\n                timerDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                listener.onLoadTimer(timerDecoder.correlationId(), timerDecoder.deadline(), buffer, offset, length);\n                break;\n\n            case ConsensusModuleDecoder.TEMPLATE_ID:\n                consensusModuleDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                listener.onLoadConsensusModuleState(\n                    consensusModuleDecoder.nextSessionId(),\n                    consensusModuleDecoder.nextServiceSessionId(),\n                    consensusModuleDecoder.logServiceSessionId(),\n                    consensusModuleDecoder.pendingMessageCapacity(),\n                    buffer,\n                    offset,\n                    length);\n                break;\n\n            case ClusterMembersDecoder.TEMPLATE_ID:\n                // Ignored\n                break;\n\n            case PendingMessageTrackerDecoder.TEMPLATE_ID:\n                pendingMessageTrackerDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                listener.onLoadPendingMessageTracker(\n                    pendingMessageTrackerDecoder.nextServiceSessionId(),\n                    pendingMessageTrackerDecoder.logServiceSessionId(),\n                    pendingMessageTrackerDecoder.pendingMessageCapacity(),\n                    pendingMessageTrackerDecoder.serviceId(),\n                    buffer,\n                    offset,\n                    length);\n                break;\n        }\n\n        return Action.CONTINUE;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleSnapshotListener.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.codecs.CloseReason;\nimport org.agrona.DirectBuffer;\n\nimport java.util.concurrent.TimeUnit;\n\ninterface ConsensusModuleSnapshotListener\n{\n    void onLoadBeginSnapshot(int appVersion, TimeUnit timeUnit, DirectBuffer buffer, int offset, int length);\n\n    void onLoadConsensusModuleState(\n        long nextSessionId,\n        long nextServiceSessionId,\n        long logServiceSessionId,\n        int pendingMessageCapacity,\n        DirectBuffer buffer,\n        int offset,\n        int length);\n\n    void onLoadPendingMessage(long clusterSessionId, DirectBuffer buffer, int offset, int length);\n\n    void onLoadClusterSession(\n        long clusterSessionId,\n        long correlationId,\n        long openedLogPosition,\n        long timeOfLastActivity,\n        CloseReason closeReason,\n        int responseStreamId,\n        String responseChannel,\n        DirectBuffer buffer,\n        int offset,\n        int length);\n\n    void onLoadTimer(long correlationId, long deadline, DirectBuffer buffer, int offset, int length);\n\n    void onLoadPendingMessageTracker(\n        long nextServiceSessionId,\n        long logServiceSessionId,\n        int pendingMessageCapacity,\n        int serviceId,\n        DirectBuffer buffer,\n        int offset,\n        int length);\n\n    void onLoadEndSnapshot(DirectBuffer buffer, int offset, int length);\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleSnapshotPrinter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.codecs.CloseReason;\nimport org.agrona.DirectBuffer;\n\nimport java.io.PrintStream;\nimport java.util.concurrent.TimeUnit;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nclass ConsensusModuleSnapshotPrinter implements ConsensusModuleSnapshotListener\n{\n    private final PrintStream out;\n\n    ConsensusModuleSnapshotPrinter(final PrintStream out)\n    {\n        this.out = out;\n    }\n\n    public void onLoadBeginSnapshot(\n        final int appVersion, final TimeUnit timeUnit, final DirectBuffer buffer, final int offset, final int length)\n    {\n        out.println(\"Snapshot:\" +\n            \" appVersion=\" + appVersion +\n            \" timeUnit=\" + timeUnit);\n    }\n\n    public void onLoadEndSnapshot(final DirectBuffer buffer, final int offset, final int length)\n    {\n        out.println(\"End Snapshot, offset=\" + offset + \", length=\" + length);\n        final byte[] b = new byte[length];\n        buffer.getBytes(offset, b, 0, length);\n        out.println(formatHexDump(b, 0, length));\n    }\n\n    public void onLoadConsensusModuleState(\n        final long nextSessionId,\n        final long nextServiceSessionId,\n        final long logServiceSessionId,\n        final int pendingMessageCapacity,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        out.println(\"Consensus Module State:\" +\n            \" nextSessionId=\" + nextSessionId +\n            \" nextServiceSessionId=\" + nextServiceSessionId +\n            \" logServiceSessionId=\" + logServiceSessionId +\n            \" pendingMessageCapacity=\" + pendingMessageCapacity);\n        final byte[] b = new byte[length];\n        buffer.getBytes(offset, b, 0, length);\n        out.println(formatHexDump(b, 0, length));\n    }\n\n    public void onLoadPendingMessage(\n        final long clusterSessionId, final DirectBuffer buffer, final int offset, final int length)\n    {\n        out.println(\"Pending Message:\" +\n            \" length=\" + length +\n            \" clusterSessionId=\" + clusterSessionId);\n    }\n\n    public void onLoadClusterSession(\n        final long clusterSessionId,\n        final long correlationId,\n        final long openedLogPosition,\n        final long timeOfLastActivity,\n        final CloseReason closeReason,\n        final int responseStreamId,\n        final String responseChannel,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        out.println(\"Cluster Session:\" +\n            \" clusterSessionId=\" + clusterSessionId +\n            \" correlationId=\" + correlationId +\n            \" openedLogPosition=\" + openedLogPosition +\n            \" timeOfLastActivity=\" + timeOfLastActivity +\n            \" closeReason=\" + closeReason +\n            \" responseStreamId=\" + responseStreamId +\n            \" responseChannel=\" + responseChannel);\n    }\n\n    public void onLoadTimer(\n        final long correlationId, final long deadline, final DirectBuffer buffer, final int offset, final int length)\n    {\n        out.println(\"Timer:\" +\n            \" correlationId=\" + correlationId +\n            \" deadline=\" + deadline);\n    }\n\n    public void onLoadPendingMessageTracker(\n        final long nextServiceSessionId,\n        final long logServiceSessionId,\n        final int pendingMessageCapacity,\n        final int serviceId,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        out.println(\"Pending Message Tracker:\" +\n            \" nextServiceSessionId=\" + nextServiceSessionId +\n            \" logServiceSessionId=\" + logServiceSessionId +\n            \" pendingMessageCapacity=\" + pendingMessageCapacity +\n            \" serviceId=\" + serviceId);\n    }\n\n    private static String formatHexDump(final byte[] array, final int offset, final int length)\n    {\n        final int width = 16;\n\n        final StringBuilder builder = new StringBuilder();\n\n        for (int rowOffset = offset; rowOffset < offset + length; rowOffset += width)\n        {\n            builder.append(String.format(\"%06d:  \", rowOffset));\n\n            for (int index = 0; index < width; index++)\n            {\n                if (rowOffset + index < array.length)\n                {\n                    builder.append(String.format(\"%02x \", array[rowOffset + index]));\n                }\n                else\n                {\n                    builder.append(\"   \");\n                }\n            }\n\n            if (rowOffset < array.length)\n            {\n                final int asciiWidth = Math.min(width, array.length - rowOffset);\n                builder.append(\"  |  \");\n                builder.append(new String(array, rowOffset, asciiWidth, UTF_8)\n                    .replaceAll(\"\\r\\n\", \" \").replaceAll(\"\\n\", \" \"));\n            }\n\n            builder.append(String.format(\"%n\"));\n        }\n\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleSnapshotTaker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.cluster.service.SnapshotTaker;\nimport io.aeron.exceptions.AeronEvent;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.ExpandableRingBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.IdleStrategy;\n\nclass ConsensusModuleSnapshotTaker\n    extends SnapshotTaker\n    implements ExpandableRingBuffer.MessageConsumer, TimerService.TimerSnapshotTaker\n{\n    private static final int ENCODED_TIMER_LENGTH = MessageHeaderEncoder.ENCODED_LENGTH + TimerEncoder.BLOCK_LENGTH;\n\n    private final ExpandableArrayBuffer offerBuffer = new ExpandableArrayBuffer(1024);\n    private final ClusterSessionEncoder clusterSessionEncoder = new ClusterSessionEncoder();\n    private final TimerEncoder timerEncoder = new TimerEncoder();\n    private final ConsensusModuleEncoder consensusModuleEncoder = new ConsensusModuleEncoder();\n\n    private final PendingMessageTrackerEncoder pendingMessageTrackerEncoder = new PendingMessageTrackerEncoder();\n\n    ConsensusModuleSnapshotTaker(\n        final ExclusivePublication publication, final IdleStrategy idleStrategy, final AgentInvoker aeronClientInvoker)\n    {\n        super(publication, idleStrategy, aeronClientInvoker);\n    }\n\n    public boolean onMessage(final MutableDirectBuffer buffer, final int offset, final int length, final int headOffset)\n    {\n        offer(buffer, offset, length);\n        return true;\n    }\n\n    void snapshotConsensusModuleState(\n        final long nextSessionId,\n        final long nextServiceSessionId,\n        final long logServiceSessionId,\n        final int pendingMessageCapacity)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ConsensusModuleEncoder.BLOCK_LENGTH;\n\n        idleStrategy.reset();\n        while (true)\n        {\n            final long result = publication.tryClaim(length, bufferClaim);\n            if (result > 0)\n            {\n                consensusModuleEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .nextSessionId(nextSessionId)\n                    .nextServiceSessionId(nextServiceSessionId)\n                    .logServiceSessionId(logServiceSessionId)\n                    .pendingMessageCapacity(pendingMessageCapacity);\n\n                bufferClaim.commit();\n                break;\n            }\n\n            checkResultAndIdle(result);\n        }\n    }\n\n    void snapshotSession(final ClusterSession session)\n    {\n        final String responseChannel = session.responseChannel();\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ClusterSessionEncoder.BLOCK_LENGTH +\n            ClusterSessionEncoder.responseChannelHeaderLength() + responseChannel.length();\n\n        if (length <= publication.maxPayloadLength())\n        {\n            idleStrategy.reset();\n            while (true)\n            {\n                final long result = publication.tryClaim(length, bufferClaim);\n                if (result > 0)\n                {\n                    encodeSession(session, responseChannel, bufferClaim.buffer(), bufferClaim.offset());\n                    bufferClaim.commit();\n                    break;\n                }\n\n                checkResultAndIdle(result);\n            }\n        }\n        else\n        {\n            final int offset = 0;\n            encodeSession(session, responseChannel, offerBuffer, offset);\n            offer(offerBuffer, offset, length);\n        }\n    }\n\n    public void snapshotTimer(final long correlationId, final long deadline)\n    {\n        idleStrategy.reset();\n        while (true)\n        {\n            final long result = publication.tryClaim(ENCODED_TIMER_LENGTH, bufferClaim);\n            if (result > 0)\n            {\n                timerEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .correlationId(correlationId)\n                    .deadline(deadline);\n                bufferClaim.commit();\n                break;\n            }\n\n            checkResultAndIdle(result);\n        }\n    }\n\n    void snapshot(final PendingServiceMessageTracker tracker, final ErrorHandler errorHandler)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + PendingMessageTrackerEncoder.BLOCK_LENGTH;\n        final long nextServiceSessionId = correctNextServiceSessionId(tracker, errorHandler);\n\n        idleStrategy.reset();\n        while (true)\n        {\n            final long result = publication.tryClaim(length, bufferClaim);\n            if (result > 0)\n            {\n                pendingMessageTrackerEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .nextServiceSessionId(nextServiceSessionId)\n                    .logServiceSessionId(tracker.logServiceSessionId())\n                    .pendingMessageCapacity(tracker.pendingMessages().size())\n                    .serviceId(tracker.serviceId());\n                bufferClaim.commit();\n                break;\n            }\n\n            checkResultAndIdle(result);\n        }\n\n        tracker.pendingMessages().forEach(this, Integer.MAX_VALUE);\n    }\n\n    private void encodeSession(\n        final ClusterSession session, final String responseChannel, final MutableDirectBuffer buffer, final int offset)\n    {\n        clusterSessionEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .clusterSessionId(session.id())\n            .correlationId(session.correlationId())\n            .openedLogPosition(session.openedLogPosition())\n            .timeOfLastActivity(Aeron.NULL_VALUE)\n            .closeReason(session.closeReason())\n            .responseStreamId(session.responseStreamId())\n            .responseChannel(responseChannel);\n    }\n\n    private static long correctNextServiceSessionId(\n        final PendingServiceMessageTracker tracker,\n        final ErrorHandler errorHandler)\n    {\n        final long nextServiceSessionId = tracker.pendingMessages().isEmpty() ?\n            tracker.logServiceSessionId() + 1 : tracker.nextServiceSessionId();\n        final long missedServiceMessageCount = nextServiceSessionId - tracker.nextServiceSessionId();\n\n        if (0 < missedServiceMessageCount)\n        {\n            final String message = \"Follower has missed \" + missedServiceMessageCount +\n                \" service message(s).  Please check service (id=\" + tracker.serviceId() +\n                \") determinism around the use of Cluster::offer\";\n            errorHandler.onError(new AeronEvent(message, AeronException.Category.ERROR));\n        }\n\n        return nextServiceSessionId;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleStateExport.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.codecs.CloseReason;\nimport org.agrona.ExpandableRingBuffer;\n\nimport java.util.List;\n\nimport static java.util.Collections.unmodifiableList;\n\nclass ConsensusModuleStateExport\n{\n    final long logRecordingId;\n    final long leadershipTermId;\n    final long expectedAckPosition;\n    final long nextSessionId;\n    final long serviceAckId;\n    final List<TimerStateExport> timers;\n    final List<ClusterSessionStateExport> sessions;\n    final List<PendingServiceMessageTrackerStateExport> pendingMessageTrackers;\n\n    ConsensusModuleStateExport(\n        final long logRecordingId,\n        final long leadershipTermId,\n        final long expectedAckPosition,\n        final long nextSessionId,\n        final long serviceAckId,\n        final List<TimerStateExport> timers,\n        final List<ClusterSessionStateExport> sessions,\n        final List<PendingServiceMessageTrackerStateExport> pendingMessageTrackers)\n    {\n        this.logRecordingId = logRecordingId;\n        this.leadershipTermId = leadershipTermId;\n        this.expectedAckPosition = expectedAckPosition;\n        this.nextSessionId = nextSessionId;\n        this.serviceAckId = serviceAckId;\n        this.timers = unmodifiableList(timers);\n        this.sessions = unmodifiableList(sessions);\n        this.pendingMessageTrackers = unmodifiableList(pendingMessageTrackers);\n    }\n\n    public String toString()\n    {\n        return \"ConsensusModuleStateExport{\" +\n            \"logRecordingId=\" + logRecordingId +\n            \", leadershipTermId=\" + leadershipTermId +\n            \", expectedAckPosition=\" + expectedAckPosition +\n            \", nextSessionId=\" + nextSessionId +\n            \", serviceAckId=\" + serviceAckId +\n            \", timers=\" + timers +\n            \", sessions=\" + sessions +\n            \", pendingMessageTrackers=\" + pendingMessageTrackers +\n            '}';\n    }\n\n    static class TimerStateExport\n    {\n        final long correlationId;\n        final long deadline;\n\n        TimerStateExport(final long correlationId, final long deadline)\n        {\n            this.correlationId = correlationId;\n            this.deadline = deadline;\n        }\n    }\n\n    static class ClusterSessionStateExport\n    {\n        final long id;\n        final long correlationId;\n        final long openedLogPosition;\n        final long timeOfLastActivityNs;\n        final int responseStreamId;\n        final String responseChannel;\n        final CloseReason closeReason;\n\n        ClusterSessionStateExport(\n            final long id,\n            final long correlationId,\n            final long openedLogPosition,\n            final long timeOfLastActivityNs,\n            final int responseStreamId,\n            final String responseChannel,\n            final CloseReason closeReason)\n        {\n            this.id = id;\n            this.correlationId = correlationId;\n            this.openedLogPosition = openedLogPosition;\n            this.timeOfLastActivityNs = timeOfLastActivityNs;\n            this.responseStreamId = responseStreamId;\n            this.responseChannel = responseChannel;\n            this.closeReason = closeReason;\n        }\n    }\n\n    static class PendingServiceMessageTrackerStateExport\n    {\n        final long nextServiceSessionId;\n        final long logServiceSessionId;\n        final int capacity;\n        final int serviceId;\n        final ExpandableRingBuffer pendingMessages;\n\n        PendingServiceMessageTrackerStateExport(\n            final long nextServiceSessionId,\n            final long logServiceSessionId,\n            final int capacity,\n            final int serviceId,\n            final ExpandableRingBuffer pendingMessages)\n        {\n            this.nextServiceSessionId = nextServiceSessionId;\n            this.logServiceSessionId = logServiceSessionId;\n            this.capacity = capacity;\n            this.serviceId = serviceId;\n            this.pendingMessages = pendingMessages;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ConsensusPublisher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Publication;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.ExpandableArrayBuffer;\n\nimport java.util.List;\n\nfinal class ConsensusPublisher\n{\n    private static final int SEND_ATTEMPTS = 3;\n\n    private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n    private final BufferClaim bufferClaim = new BufferClaim();\n    private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n    private final CanvassPositionEncoder canvassPositionEncoder = new CanvassPositionEncoder();\n    private final RequestVoteEncoder requestVoteEncoder = new RequestVoteEncoder();\n    private final VoteEncoder voteEncoder = new VoteEncoder();\n    private final NewLeadershipTermEncoder newLeadershipTermEncoder = new NewLeadershipTermEncoder();\n    private final AppendPositionEncoder appendPositionEncoder = new AppendPositionEncoder();\n    private final CommitPositionEncoder commitPositionEncoder = new CommitPositionEncoder();\n    private final CatchupPositionEncoder catchupPositionEncoder = new CatchupPositionEncoder();\n    private final StopCatchupEncoder stopCatchupEncoder = new StopCatchupEncoder();\n    private final TerminationPositionEncoder terminationPositionEncoder = new TerminationPositionEncoder();\n    private final TerminationAckEncoder terminationAckEncoder = new TerminationAckEncoder();\n    private final BackupQueryEncoder backupQueryEncoder = new BackupQueryEncoder();\n    private final BackupResponseEncoder backupResponseEncoder = new BackupResponseEncoder();\n    private final HeartbeatRequestEncoder heartbeatRequestEncoder = new HeartbeatRequestEncoder();\n    private final HeartbeatResponseEncoder heartbeatResponseEncoder = new HeartbeatResponseEncoder();\n    private final ChallengeResponseEncoder challengeResponseEncoder = new ChallengeResponseEncoder();\n    private final StandbySnapshotEncoder standbySnapshotEncoder = new StandbySnapshotEncoder();\n\n    void canvassPosition(\n        final ExclusivePublication publication,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long leadershipTermId,\n        final int followerMemberId)\n    {\n        if (null == publication)\n        {\n            return;\n        }\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + CanvassPositionEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                canvassPositionEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .logLeadershipTermId(logLeadershipTermId)\n                    .logPosition(logPosition)\n                    .leadershipTermId(leadershipTermId)\n                    .followerMemberId(followerMemberId)\n                    .protocolVersion(ConsensusModule.Configuration.PROTOCOL_SEMANTIC_VERSION);\n\n                bufferClaim.commit();\n\n                return;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n    }\n\n    boolean requestVote(\n        final ExclusivePublication publication,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateMemberId)\n    {\n        if (null == publication)\n        {\n            return false;\n        }\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + RequestVoteEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                requestVoteEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .logLeadershipTermId(logLeadershipTermId)\n                    .logPosition(logPosition)\n                    .candidateTermId(candidateTermId)\n                    .candidateMemberId(candidateMemberId)\n                    .protocolVersion(ConsensusModule.Configuration.PROTOCOL_SEMANTIC_VERSION);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    void placeVote(\n        final ExclusivePublication publication,\n        final long candidateTermId,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final int candidateMemberId,\n        final int followerMemberId,\n        final boolean vote)\n    {\n        if (null == publication)\n        {\n            return;\n        }\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + VoteEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                voteEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .candidateTermId(candidateTermId)\n                    .logLeadershipTermId(logLeadershipTermId)\n                    .logPosition(logPosition)\n                    .candidateMemberId(candidateMemberId)\n                    .followerMemberId(followerMemberId)\n                    .vote(vote ? BooleanType.TRUE : BooleanType.FALSE);\n\n                bufferClaim.commit();\n\n                return;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n    }\n\n    void newLeadershipTerm(\n        final ExclusivePublication publication,\n        final long logLeadershipTermId,\n        final long nextLeadershipTermId,\n        final long nextTermBaseLogPosition,\n        final long nextLogPosition,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long commitPosition,\n        final long leaderRecordingId,\n        final long timestamp,\n        final int leaderMemberId,\n        final int logSessionId,\n        final int appVersion,\n        final boolean isStartup)\n    {\n        if (null == publication)\n        {\n            return;\n        }\n\n        if (CommonContext.NULL_SESSION_ID == logSessionId)\n        {\n            throw new ClusterException(\"logSessionId was null, should always have a value\");\n        }\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + NewLeadershipTermEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                newLeadershipTermEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .logLeadershipTermId(logLeadershipTermId)\n                    .nextLeadershipTermId(nextLeadershipTermId)\n                    .nextTermBaseLogPosition(nextTermBaseLogPosition)\n                    .nextLogPosition(nextLogPosition)\n                    .leadershipTermId(leadershipTermId)\n                    .termBaseLogPosition(termBaseLogPosition)\n                    .logPosition(logPosition)\n                    .leaderRecordingId(leaderRecordingId)\n                    .timestamp(timestamp)\n                    .leaderMemberId(leaderMemberId)\n                    .logSessionId(logSessionId)\n                    .appVersion(appVersion)\n                    .isStartup(isStartup ? BooleanType.TRUE : BooleanType.FALSE)\n                    .commitPosition(commitPosition);\n\n                bufferClaim.commit();\n\n                return;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n    }\n\n    boolean appendPosition(\n        final ExclusivePublication publication,\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final short flags)\n    {\n        if (null == publication)\n        {\n            return false;\n        }\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + AppendPositionEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                appendPositionEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .logPosition(logPosition)\n                    .followerMemberId(followerMemberId)\n                    .flags(flags);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    void commitPosition(\n        final ExclusivePublication publication,\n        final long leadershipTermId,\n        final long logPosition,\n        final int leaderMemberId)\n    {\n        if (null == publication)\n        {\n            return;\n        }\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + CommitPositionEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                commitPositionEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .logPosition(logPosition)\n                    .leaderMemberId(leaderMemberId);\n\n                bufferClaim.commit();\n\n                return;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n    }\n\n    boolean catchupPosition(\n        final ExclusivePublication publication,\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final String catchupEndpoint)\n    {\n        if (null == publication)\n        {\n            return false;\n        }\n\n        final int length =\n            MessageHeaderEncoder.ENCODED_LENGTH +\n            CatchupPositionEncoder.BLOCK_LENGTH +\n            CatchupPositionEncoder.catchupEndpointHeaderLength() +\n            catchupEndpoint.length();\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                catchupPositionEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .logPosition(logPosition)\n                    .followerMemberId(followerMemberId)\n                    .catchupEndpoint(catchupEndpoint);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean stopCatchup(final ExclusivePublication publication, final long leadershipTermId, final int followerMemberId)\n    {\n        if (null == publication)\n        {\n            return false;\n        }\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + StopCatchupEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                stopCatchupEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .followerMemberId(followerMemberId);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean terminationPosition(\n        final ExclusivePublication publication, final long leadershipTermId, final long logPosition)\n    {\n        if (null == publication)\n        {\n            return false;\n        }\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + TerminationPositionEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                terminationPositionEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .logPosition(logPosition);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n\n            checkResult(position, publication);\n            Thread.yield();\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean terminationAck(\n        final ExclusivePublication publication, final long leadershipTermId, final long logPosition, final int memberId)\n    {\n        if (null == publication)\n        {\n            return false;\n        }\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + TerminationAckEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                terminationAckEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .logPosition(logPosition)\n                    .memberId(memberId);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n\n            checkResult(position, publication);\n            Thread.yield();\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean backupQuery(\n        final ExclusivePublication publication,\n        final long correlationId,\n        final int responseStreamId,\n        final int version,\n        final long logPosition,\n        final String responseChannel,\n        final byte[] encodedCredentials)\n    {\n        if (null == publication)\n        {\n            return false;\n        }\n\n        final long logPositionValue = Aeron.NULL_VALUE != logPosition ? logPosition :\n            BackupQueryEncoder.logPositionNullValue();\n\n        backupQueryEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .correlationId(correlationId)\n            .responseStreamId(responseStreamId)\n            .version(version)\n            .logPosition(logPositionValue)\n            .responseChannel(responseChannel)\n            .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + backupQueryEncoder.encodedLength();\n        return sendPublication(publication, buffer, length);\n    }\n\n    boolean backupResponse(\n        final ClusterSession session,\n        final int commitPositionCounterId,\n        final int leaderMemberId,\n        final int memberId,\n        final RecordingLog.Entry lastEntry,\n        final String clusterMembers,\n        final List<RecordingLog.Snapshot> snapshots)\n    {\n        backupResponseEncoder.wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .correlationId(session.correlationId())\n            .logRecordingId(lastEntry.recordingId)\n            .logLeadershipTermId(lastEntry.leadershipTermId)\n            .logTermBaseLogPosition(lastEntry.termBaseLogPosition)\n            .lastLeadershipTermId(lastEntry.leadershipTermId)\n            .lastTermBaseLogPosition(lastEntry.termBaseLogPosition)\n            .commitPositionCounterId(commitPositionCounterId)\n            .leaderMemberId(leaderMemberId)\n            .memberId(memberId);\n\n        final List<RecordingLog.Snapshot> snapshotList = null == snapshots ? List.of() : snapshots;\n\n        final BackupResponseEncoder.SnapshotsEncoder snapshotsEncoder =\n            backupResponseEncoder.snapshotsCount(snapshotList.size());\n        for (int i = 0, length = snapshotList.size(); i < length; i++)\n        {\n            final RecordingLog.Snapshot snapshot = snapshotList.get(i);\n\n            snapshotsEncoder.next()\n                .recordingId(snapshot.recordingId())\n                .leadershipTermId(snapshot.leadershipTermId())\n                .termBaseLogPosition(snapshot.termBaseLogPosition())\n                .logPosition(snapshot.logPosition())\n                .timestamp(snapshot.timestamp())\n                .serviceId(snapshot.serviceId());\n        }\n\n        backupResponseEncoder.clusterMembers(clusterMembers);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + backupResponseEncoder.encodedLength();\n        return sendSession(session, buffer, length);\n    }\n\n    boolean heartbeatRequest(\n        final ExclusivePublication publication,\n        final long correlationId,\n        final int responseStreamId,\n        final String responseChannel,\n        final byte[] encodedCredentials)\n    {\n        if (null == publication)\n        {\n            return false;\n        }\n\n        heartbeatRequestEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .correlationId(correlationId)\n            .responseStreamId(responseStreamId)\n            .responseChannel(responseChannel)\n            .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + heartbeatRequestEncoder.encodedLength();\n        return sendPublication(publication, buffer, length);\n    }\n\n    boolean heartbeatResponse(final ClusterSession session)\n    {\n        heartbeatResponseEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .correlationId(session.correlationId());\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + heartbeatResponseEncoder.encodedLength();\n        return sendSession(session, buffer, length);\n    }\n\n    boolean challengeResponse(\n        final ExclusivePublication publication,\n        final long nextCorrelationId,\n        final long clusterSessionId,\n        final byte[] encodedChallengeResponse)\n    {\n        challengeResponseEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .correlationId(nextCorrelationId)\n            .clusterSessionId(clusterSessionId)\n            .putEncodedCredentials(encodedChallengeResponse, 0, encodedChallengeResponse.length);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + challengeResponseEncoder.encodedLength();\n\n        return sendPublication(publication, buffer, length);\n    }\n\n    boolean standbySnapshotTaken(\n        final ExclusivePublication publication,\n        final long correlationId,\n        final int version,\n        final int responseStreamId,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final List<RecordingLog.Entry> snapshots,\n        final String archiveEndpoint)\n    {\n        final int snapshotsLength = snapshots.size();\n        standbySnapshotEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder);\n\n        standbySnapshotEncoder\n            .correlationId(correlationId)\n            .version(version)\n            .responseStreamId(responseStreamId);\n\n        final StandbySnapshotEncoder.SnapshotsEncoder snapshotsEncoder = standbySnapshotEncoder\n            .snapshotsCount(snapshotsLength);\n\n        for (int i = 0; i < snapshotsLength; i++)\n        {\n            final RecordingLog.Entry entry = snapshots.get(i);\n            snapshotsEncoder\n                .next()\n                .recordingId(entry.recordingId)\n                .leadershipTermId(entry.leadershipTermId)\n                .termBaseLogPosition(entry.termBaseLogPosition)\n                .logPosition(entry.logPosition)\n                .timestamp(entry.timestamp)\n                .serviceId(entry.serviceId)\n                .archiveEndpoint(archiveEndpoint);\n        }\n\n        standbySnapshotEncoder\n            .responseChannel(responseChannel)\n            .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length);\n\n        final int encodedLength = MessageHeaderEncoder.ENCODED_LENGTH + standbySnapshotEncoder.encodedLength();\n\n        return sendPublication(publication, buffer, encodedLength);\n    }\n\n    private static void checkResult(final long position, final Publication publication)\n    {\n        if (Publication.CLOSED == position)\n        {\n            throw new ClusterException(\"publication is closed\");\n        }\n\n        if (Publication.MAX_POSITION_EXCEEDED == position)\n        {\n            throw new ClusterException(\"publication at max position: term-length=\" + publication.termBufferLength());\n        }\n    }\n\n    private static boolean sendPublication(\n        final ExclusivePublication publication,\n        final ExpandableArrayBuffer buffer,\n        final int length)\n    {\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.offer(buffer, 0, length);\n            if (position > 0)\n            {\n                return true;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    private static boolean sendSession(\n        final ClusterSession session,\n        final ExpandableArrayBuffer buffer,\n        final int length)\n    {\n        int attempts = SEND_ATTEMPTS;\n        final Publication publication = session.responsePublication();\n        do\n        {\n            final long position = publication.offer(buffer, 0, length);\n            if (position > 0)\n            {\n                return true;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/EgressPublisher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.ArrayUtil;\n\nimport static io.aeron.cluster.ClusterSession.MAX_ENCODED_MEMBERSHIP_QUERY_LENGTH;\n\nclass EgressPublisher\n{\n    private static final int SEND_ATTEMPTS = 3;\n\n    private final BufferClaim bufferClaim = new BufferClaim();\n    private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(MAX_ENCODED_MEMBERSHIP_QUERY_LENGTH);\n    private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n    private final SessionEventEncoder sessionEventEncoder = new SessionEventEncoder();\n    private final ChallengeEncoder challengeEncoder = new ChallengeEncoder();\n    private final NewLeaderEventEncoder newLeaderEventEncoder = new NewLeaderEventEncoder();\n    private final AdminResponseEncoder adminResponseEncoder = new AdminResponseEncoder();\n    private final long leaderHeartbeatTimeoutNs;\n\n    EgressPublisher(final long leaderHeartbeatTimeoutNs)\n    {\n        this.leaderHeartbeatTimeoutNs = leaderHeartbeatTimeoutNs;\n    }\n\n    boolean sendEvent(\n        final ClusterSession session,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final EventCode code,\n        final String detail)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH +\n            SessionEventEncoder.BLOCK_LENGTH +\n            SessionEventEncoder.detailHeaderLength() +\n            detail.length();\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = session.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                sessionEventEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .clusterSessionId(session.id())\n                    .correlationId(session.correlationId())\n                    .leadershipTermId(leadershipTermId)\n                    .leaderMemberId(leaderMemberId)\n                    .code(code)\n                    .version(AeronCluster.Configuration.PROTOCOL_SEMANTIC_VERSION)\n                    .leaderHeartbeatTimeoutNs(leaderHeartbeatTimeoutNs)\n                    .detail(detail);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean sendChallenge(final ClusterSession session, final byte[] encodedChallenge)\n    {\n        challengeEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .clusterSessionId(session.id())\n            .correlationId(session.correlationId())\n            .putEncodedChallenge(encodedChallenge, 0, encodedChallenge.length);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + challengeEncoder.encodedLength();\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = session.offer(buffer, 0, length);\n            if (position > 0)\n            {\n                return true;\n            }\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean newLeader(\n        final ClusterSession session,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final String ingressEndpoints)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH +\n            NewLeaderEventEncoder.BLOCK_LENGTH +\n            NewLeaderEventEncoder.ingressEndpointsHeaderLength() +\n            ingressEndpoints.length();\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = session.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                newLeaderEventEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .clusterSessionId(session.id())\n                    .leadershipTermId(leadershipTermId)\n                    .leaderMemberId(leaderMemberId)\n                    .ingressEndpoints(ingressEndpoints);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean sendAdminResponse(\n        final ClusterSession session,\n        final long correlationId,\n        final AdminRequestType adminRequestType,\n        final AdminResponseCode responseCode,\n        final String message)\n    {\n        adminResponseEncoder\n            .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n            .clusterSessionId(session.id())\n            .correlationId(correlationId)\n            .requestType(adminRequestType)\n            .responseCode(responseCode)\n            .message(message)\n            .putPayload(ArrayUtil.EMPTY_BYTE_ARRAY, 0, 0);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + adminResponseEncoder.encodedLength();\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = session.offer(buffer, 0, length);\n            if (position > 0)\n            {\n                return true;\n            }\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    public String toString()\n    {\n        return \"EgressPublisher{}\";\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/Election.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.CloseHelper;\nimport org.agrona.LangUtil;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.concurrent.AgentTerminationException;\n\nimport java.util.Arrays;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_MANUAL;\nimport static io.aeron.CommonContext.NULL_SESSION_ID;\nimport static io.aeron.CommonContext.UDP_MEDIA;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.ClusterMember.compareLog;\nimport static io.aeron.cluster.ConsensusModuleAgent.APPEND_POSITION_FLAG_NONE;\nimport static io.aeron.cluster.ElectionState.CANDIDATE_BALLOT;\nimport static io.aeron.cluster.ElectionState.CANVASS;\nimport static io.aeron.cluster.ElectionState.CLOSED;\nimport static io.aeron.cluster.ElectionState.FOLLOWER_BALLOT;\nimport static io.aeron.cluster.ElectionState.FOLLOWER_CATCHUP;\nimport static io.aeron.cluster.ElectionState.FOLLOWER_CATCHUP_AWAIT;\nimport static io.aeron.cluster.ElectionState.FOLLOWER_CATCHUP_INIT;\nimport static io.aeron.cluster.ElectionState.FOLLOWER_LOG_AWAIT;\nimport static io.aeron.cluster.ElectionState.FOLLOWER_LOG_INIT;\nimport static io.aeron.cluster.ElectionState.FOLLOWER_LOG_REPLICATION;\nimport static io.aeron.cluster.ElectionState.FOLLOWER_READY;\nimport static io.aeron.cluster.ElectionState.FOLLOWER_REPLAY;\nimport static io.aeron.cluster.ElectionState.INIT;\nimport static io.aeron.cluster.ElectionState.LEADER_INIT;\nimport static io.aeron.cluster.ElectionState.LEADER_LOG_REPLICATION;\nimport static io.aeron.cluster.ElectionState.LEADER_READY;\nimport static io.aeron.cluster.ElectionState.LEADER_REPLAY;\nimport static io.aeron.cluster.ElectionState.NOMINATE;\nimport static java.lang.Math.max;\nimport static java.lang.Math.min;\n\n/**\n * Election process to determine a new cluster leader and catch up followers.\n */\nclass Election\n{\n    private final ClusterMember[] clusterMembers;\n    private final ClusterMember thisMember;\n    private final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap;\n    private final ConsensusPublisher consensusPublisher;\n    private final ConsensusModule.Context ctx;\n    private final ConsensusModuleAgent consensusModuleAgent;\n    private final long initialLogLeadershipTermId;\n    private final long initialTimeOfLastUpdateNs;\n    private final long initialTermBaseLogPosition;\n    private final boolean isNodeStartup;\n    private long timeOfLastStateChangeNs;\n    private long timeOfLastUpdateNs;\n    private long timeOfLastCommitPositionUpdateNs;\n    private long nominationDeadlineNs;\n    private long logPosition;\n    private long appendPosition;\n    private long notifiedCommitPosition;\n    private long catchupJoinPosition = NULL_POSITION;\n    private long replicationLeadershipTermId = NULL_VALUE;\n    private long replicationStopPosition = NULL_POSITION;\n    private long replicationDeadlineNs;\n    private long replicationTermBaseLogPosition;\n    private long leaderRecordingId = NULL_VALUE;\n    private long leadershipTermId;\n    private long logLeadershipTermId;\n    private long candidateTermId;\n    private long lastPublishedCommitPosition;\n    private long lastPublishedAppendPosition;\n    private int logSessionId = NULL_SESSION_ID;\n    private int gracefulClosedLeaderId;\n    private boolean isFirstInit = true;\n    private boolean isLeaderStartup;\n    private boolean isExtendedCanvass;\n    private ClusterMember leaderMember = null;\n    private ElectionState state = INIT;\n    private Subscription logSubscription = null;\n    private LogReplay logReplay = null;\n    private RecordingReplication logReplication = null;\n\n    Election(\n        final boolean isNodeStartup,\n        final int gracefulClosedLeaderId,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long appendPosition,\n        final ClusterMember[] clusterMembers,\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap,\n        final ClusterMember thisMember,\n        final ConsensusPublisher consensusPublisher,\n        final ConsensusModule.Context ctx,\n        final ConsensusModuleAgent consensusModuleAgent)\n    {\n        this.isNodeStartup = isNodeStartup;\n        this.isExtendedCanvass = isNodeStartup;\n        this.gracefulClosedLeaderId = gracefulClosedLeaderId;\n        this.logPosition = logPosition;\n        this.appendPosition = appendPosition;\n        this.logLeadershipTermId = leadershipTermId;\n        this.initialLogLeadershipTermId = leadershipTermId;\n        this.initialTermBaseLogPosition = termBaseLogPosition;\n        this.leadershipTermId = leadershipTermId;\n        this.candidateTermId = leadershipTermId;\n        this.clusterMembers = clusterMembers;\n        this.clusterMemberByIdMap = clusterMemberByIdMap;\n        this.thisMember = thisMember;\n        this.consensusPublisher = consensusPublisher;\n        this.ctx = ctx;\n        this.consensusModuleAgent = consensusModuleAgent;\n\n        final long nowNs = nowNs(ctx);\n        this.initialTimeOfLastUpdateNs = nowNs - TimeUnit.DAYS.toNanos(1);\n        this.timeOfLastUpdateNs = initialTimeOfLastUpdateNs;\n        this.timeOfLastCommitPositionUpdateNs = initialTimeOfLastUpdateNs;\n\n        Objects.requireNonNull(thisMember);\n        ctx.electionStateCounter().setRelease(INIT.code());\n        ctx.electionCounter().incrementRelease();\n    }\n\n    ClusterMember leader()\n    {\n        return leaderMember;\n    }\n\n    int logSessionId()\n    {\n        return logSessionId;\n    }\n\n    long leadershipTermId()\n    {\n        return leadershipTermId;\n    }\n\n    long logPosition()\n    {\n        return logPosition;\n    }\n\n    boolean isLeaderStartup()\n    {\n        return isLeaderStartup;\n    }\n\n    int thisMemberId()\n    {\n        return thisMember.id();\n    }\n\n    int doWork(final long nowNs)\n    {\n        int workCount = 0;\n\n        switch (state)\n        {\n            case INIT:\n                workCount += init(nowNs);\n                break;\n\n            case CANVASS:\n                workCount += canvass(nowNs);\n                break;\n\n            case NOMINATE:\n                workCount += nominate(nowNs);\n                break;\n\n            case CANDIDATE_BALLOT:\n                workCount += candidateBallot(nowNs);\n                break;\n\n            case FOLLOWER_BALLOT:\n                workCount += followerBallot(nowNs);\n                break;\n\n            case LEADER_LOG_REPLICATION:\n                workCount += leaderLogReplication(nowNs);\n                break;\n\n            case LEADER_REPLAY:\n                workCount += leaderReplay(nowNs);\n                break;\n\n            case LEADER_INIT:\n                workCount += leaderInit(nowNs);\n                break;\n\n            case LEADER_READY:\n                workCount += leaderReady(nowNs);\n                break;\n\n            case FOLLOWER_LOG_REPLICATION:\n                workCount += followerLogReplication(nowNs);\n                break;\n\n            case FOLLOWER_REPLAY:\n                workCount += followerReplay(nowNs);\n                break;\n\n            case FOLLOWER_CATCHUP_INIT:\n                workCount += followerCatchupInit(nowNs);\n                break;\n\n            case FOLLOWER_CATCHUP_AWAIT:\n                workCount += followerCatchupAwait(nowNs);\n                break;\n\n            case FOLLOWER_CATCHUP:\n                workCount += followerCatchup(nowNs);\n                break;\n\n            case FOLLOWER_LOG_INIT:\n                workCount += followerLogInit(nowNs);\n                break;\n\n            case FOLLOWER_LOG_AWAIT:\n                workCount += followerLogAwait(nowNs);\n                break;\n\n            case FOLLOWER_READY:\n                workCount += followerReady(nowNs);\n                break;\n\n            case CLOSED:\n                break;\n        }\n\n        return workCount;\n    }\n\n    void handleError(final long nowNs, final Throwable ex)\n    {\n        ctx.countedErrorHandler().onError(ex);\n        logPosition = ctx.commitPositionCounter().getPlain();\n        state(INIT, nowNs, ex.getMessage());\n\n        if (ex instanceof AgentTerminationException || ex instanceof InterruptedException)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    void onRecordingSignal(\n        final long correlationId, final long recordingId, final long position, final RecordingSignal signal)\n    {\n        if (INIT == state)\n        {\n            return;\n        }\n\n        if (null != logReplication)\n        {\n            logReplication.onSignal(correlationId, recordingId, position, signal);\n            consensusModuleAgent.logRecordingId(logReplication.recordingId());\n        }\n    }\n\n    void onCanvassPosition(\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long leadershipTermId,\n        final int followerMemberId,\n        final int protocolVersion)\n    {\n        if (INIT == state)\n        {\n            return;\n        }\n\n        if (followerMemberId == gracefulClosedLeaderId)\n        {\n            gracefulClosedLeaderId = NULL_VALUE;\n        }\n\n        final ClusterMember follower = clusterMemberByIdMap.get(followerMemberId);\n        if (null != follower && thisMember.id() != followerMemberId)\n        {\n            consensusModuleAgent.updateMemberLogPosition(follower, logLeadershipTermId, logPosition);\n\n            if (logLeadershipTermId < this.leadershipTermId)\n            {\n                if (Cluster.Role.LEADER == consensusModuleAgent.role())\n                {\n                    final long nowNs = nowNs(ctx);\n                    publishNewLeadershipTerm(\n                        follower,\n                        logLeadershipTermId,\n                        consensusModuleAgent.quorumPositionBoundedByLeaderLog(\n                            appendPosition, nowNs),\n                        nanosToTimestamp(ctx, nowNs));\n                }\n            }\n            else if (logLeadershipTermId > this.leadershipTermId)\n            {\n                switch (state)\n                {\n                    case LEADER_LOG_REPLICATION:\n                    case LEADER_READY:\n                        throw new ClusterEvent(\"potential new election in progress\");\n\n                    default:\n                        break;\n                }\n            }\n        }\n    }\n\n    void onRequestVote(\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateId,\n        final int protocolVersion)\n    {\n        if (INIT == state)\n        {\n            return;\n        }\n\n        if (candidateId == thisMember.id())\n        {\n            return;\n        }\n\n        if (candidateTermId <= this.candidateTermId)\n        {\n            placeVote(candidateTermId, candidateId, false);\n        }\n        else if (compareLog(this.logLeadershipTermId, appendPosition, logLeadershipTermId, logPosition) > 0)\n        {\n            this.candidateTermId = ctx.nodeStateFile().proposeMaxCandidateTermId(\n                candidateTermId, logPosition, ctx.epochClock().time());\n\n            placeVote(candidateTermId, candidateId, false);\n\n            final ClusterMember candidateMember = clusterMemberByIdMap.get(candidateId);\n            if (null != candidateMember && Cluster.Role.LEADER == consensusModuleAgent.role())\n            {\n                final long nowNs = nowNs(ctx);\n                publishNewLeadershipTerm(\n                    candidateMember,\n                    logLeadershipTermId,\n                    consensusModuleAgent.quorumPositionBoundedByLeaderLog(appendPosition, nowNs),\n                    nanosToTimestamp(ctx, nowNs));\n            }\n        }\n        else if (CANVASS == state || NOMINATE == state || CANDIDATE_BALLOT == state || FOLLOWER_BALLOT == state)\n        {\n            final long nowNs = nowNs(ctx);\n            this.candidateTermId = ctx.nodeStateFile().proposeMaxCandidateTermId(\n                candidateTermId, logPosition, TimeUnit.MILLISECONDS.convert(nowNs, TimeUnit.NANOSECONDS));\n            placeVote(candidateTermId, candidateId, true);\n            state(FOLLOWER_BALLOT, nowNs, \"\");\n        }\n    }\n\n    void onVote(\n        final long logLeadershipTermId,\n        final long logPosition,\n        final long candidateTermId,\n        final int candidateId,\n        final int voterId,\n        final boolean vote)\n    {\n        if (INIT == state)\n        {\n            return;\n        }\n\n        if (CANDIDATE_BALLOT == state &&\n            candidateTermId == this.candidateTermId &&\n            candidateId == thisMember.id())\n        {\n            final ClusterMember follower = clusterMemberByIdMap.get(voterId);\n            if (null != follower)\n            {\n                follower\n                    .candidateTermId(candidateTermId)\n                    .vote(vote ? Boolean.TRUE : Boolean.FALSE);\n                consensusModuleAgent.updateMemberLogPosition(follower, logLeadershipTermId, logPosition);\n            }\n        }\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    void onNewLeadershipTerm(\n        final long logLeadershipTermId,\n        final long nextLeadershipTermId,\n        final long nextTermBaseLogPosition,\n        final long nextLogPosition,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long commitPosition,\n        final long leaderRecordingId,\n        final long timestamp,\n        final int leaderMemberId,\n        final int logSessionId,\n        final boolean isStartup)\n    {\n        if (INIT == state)\n        {\n            return;\n        }\n\n        final ClusterMember leader = clusterMemberByIdMap.get(leaderMemberId);\n        if (null == leader || (leaderMemberId == thisMember.id() && leadershipTermId == this.leadershipTermId))\n        {\n            return;\n        }\n\n        if (leaderMemberId == gracefulClosedLeaderId)\n        {\n            gracefulClosedLeaderId = NULL_VALUE;\n        }\n\n        if (((FOLLOWER_BALLOT == state || CANDIDATE_BALLOT == state) && leadershipTermId == candidateTermId) ||\n            CANVASS == state)\n        {\n            if (logLeadershipTermId == this.logLeadershipTermId)\n            {\n                if (NULL_POSITION != nextTermBaseLogPosition && nextTermBaseLogPosition < appendPosition)\n                {\n                    onTruncateLogEntry(\n                        thisMember.id(),\n                        state,\n                        logLeadershipTermId,\n                        this.leadershipTermId,\n                        candidateTermId,\n                        ctx.commitPositionCounter().getPlain(),\n                        this.logPosition,\n                        appendPosition,\n                        appendPosition,\n                        nextTermBaseLogPosition);\n                }\n\n                this.leaderMember = leader;\n                this.isLeaderStartup = isStartup;\n                this.leadershipTermId = leadershipTermId;\n                this.candidateTermId = max(leadershipTermId, candidateTermId);\n                this.logSessionId = logSessionId;\n                this.leaderRecordingId = leaderRecordingId;\n                this.catchupJoinPosition = appendPosition < logPosition ? logPosition : NULL_POSITION;\n                notifiedCommitPosition = max(notifiedCommitPosition, commitPosition);\n\n                if (this.appendPosition < termBaseLogPosition)\n                {\n                    if (NULL_VALUE != nextLeadershipTermId)\n                    {\n                        if (appendPosition < nextTermBaseLogPosition)\n                        {\n                            replicationLeadershipTermId = logLeadershipTermId;\n                            replicationStopPosition = nextTermBaseLogPosition;\n                            // Here we should have an open, but uncommitted term so the base position\n                            // is already known. We could look it up from the recording log only to write\n                            // it back again...\n                            replicationTermBaseLogPosition = NULL_VALUE;\n                            state(FOLLOWER_LOG_REPLICATION, nowNs(ctx), \"\");\n                        }\n                        else if (appendPosition == nextTermBaseLogPosition)\n                        {\n                            if (NULL_POSITION != nextLogPosition)\n                            {\n                                replicationLeadershipTermId = nextLeadershipTermId;\n                                replicationStopPosition = nextLogPosition;\n                                replicationTermBaseLogPosition = nextTermBaseLogPosition;\n                                state(FOLLOWER_LOG_REPLICATION, nowNs(ctx), \"\");\n                            }\n                        }\n                    }\n                    else\n                    {\n                        throw new ClusterException(\n                            \"invalid newLeadershipTerm - this.appendPosition=\" + appendPosition +\n                            \" < termBaseLogPosition=\" + termBaseLogPosition +\n                            \" and nextLeadershipTermId=\" + nextLeadershipTermId +\n                            \", logLeadershipTermId=\" + logLeadershipTermId +\n                            \", nextTermBaseLogPosition=\" + nextTermBaseLogPosition +\n                            \", nextLogPosition=\" + nextLogPosition +\n                            \", leadershipTermId=\" + leadershipTermId +\n                            \", termBaseLogPosition=\" + termBaseLogPosition +\n                            \", logPosition=\" + logPosition +\n                            \", commitPosition=\" + commitPosition +\n                            \", leaderRecordingId=\" + leaderRecordingId +\n                            \", leaderMemberId=\" + leaderMemberId +\n                            \", logSessionId=\" + logSessionId +\n                            \", isStartup=\" + isStartup);\n                    }\n                }\n                else\n                {\n                    state(FOLLOWER_REPLAY, nowNs(ctx), \"\");\n                }\n            }\n            else\n            {\n                state(CANVASS, nowNs(ctx), \"\");\n            }\n        }\n\n        if (FOLLOWER_LOG_REPLICATION == state &&\n            logLeadershipTermId == this.logLeadershipTermId &&\n            leaderMember.id() == leaderMemberId)\n        {\n            replicationDeadlineNs = nowNs(ctx) + ctx.leaderHeartbeatTimeoutNs();\n        }\n    }\n\n    void onAppendPosition(\n        final long leadershipTermId,\n        final long logPosition,\n        final int followerMemberId,\n        final short flags)\n    {\n        if (INIT == state)\n        {\n            return;\n        }\n\n        if (leadershipTermId <= this.leadershipTermId)\n        {\n            final ClusterMember follower = clusterMemberByIdMap.get(followerMemberId);\n            if (null != follower)\n            {\n                consensusModuleAgent.updateMemberLogPosition(follower, leadershipTermId, logPosition);\n                consensusModuleAgent.trackCatchupCompletion(follower, leadershipTermId, flags);\n            }\n        }\n    }\n\n    void onCommitPosition(final long leadershipTermId, final long logPosition, final int leaderMemberId)\n    {\n        final ElectionState state = this.state;\n        if (INIT == state)\n        {\n            return;\n        }\n\n        // we do not check `leadershipTermId == this.leadershipTermId` here, because prior to fixes the leader was\n        // sending wrong `leadershipTermId` value in the `CommitPosition` message (i.e. not matching the one sent\n        // by the `NewLeadershipTerm` message).\n        if (null != leaderMember && leaderMember.id() == leaderMemberId)\n        {\n            notifiedCommitPosition = max(notifiedCommitPosition, logPosition);\n            if (FOLLOWER_LOG_REPLICATION == state)\n            {\n                replicationDeadlineNs = nowNs(ctx) + ctx.leaderHeartbeatTimeoutNs();\n            }\n        }\n        else if (leadershipTermId > this.leadershipTermId && LEADER_READY == state)\n        {\n            throw new ClusterEvent(\"new leader detected due to commit position - \" +\n                \" memberId=\" + thisMemberId() +\n                \" this.leadershipTermId=\" + this.leadershipTermId +\n                \" this.leaderMemberId=\" + (null != leaderMember ? leaderMember.id() : NULL_VALUE) +\n                \" this.logPosition=\" + this.logPosition +\n                \" newLeadershipTermId=\" + leadershipTermId +\n                \" newLeaderMemberId=\" + leaderMemberId +\n                \" newCommitPosition=\" + logPosition +\n                \")\");\n        }\n    }\n\n    void onReplayNewLeadershipTermEvent(\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final long termBaseLogPosition)\n    {\n        if (INIT == state)\n        {\n            return;\n        }\n\n        if (FOLLOWER_CATCHUP == state || FOLLOWER_REPLAY == state)\n        {\n            final long nowNs = timestampToNanos(ctx, timestamp);\n            ensureRecordingLogCoherent(leadershipTermId, termBaseLogPosition, NULL_VALUE, nowNs);\n            this.logPosition = logPosition;\n            this.logLeadershipTermId = leadershipTermId;\n        }\n    }\n\n    void onTruncateLogEntry(\n        final int memberId,\n        final ElectionState state,\n        final long logLeadershipTermId,\n        final long leadershipTermId,\n        final long candidateTermId,\n        final long commitPosition,\n        final long logPosition,\n        final long appendPosition,\n        final long oldPosition,\n        final long newPosition)\n    {\n        consensusModuleAgent.truncateLogEntry(logLeadershipTermId, newPosition);\n        this.appendPosition = newPosition;\n        throw new ClusterEvent(\"Truncating Cluster Log - memberId=\" + memberId +\n            \" state=\" + state +\n            \" this.logLeadershipTermId=\" + logLeadershipTermId +\n            \" this.leadershipTermId=\" + leadershipTermId +\n            \" this.candidateTermId=\" + candidateTermId +\n            \" this.commitPosition=\" + commitPosition +\n            \" this.logPosition=\" + logPosition +\n            \" this.appendPosition=\" + appendPosition +\n            \" oldPosition=\" + oldPosition +\n            \" newPosition=\" + newPosition);\n    }\n\n    long notifiedCommitPosition()\n    {\n        return notifiedCommitPosition;\n    }\n\n    private int init(final long nowNs)\n    {\n        if (isFirstInit)\n        {\n            isFirstInit = false;\n            if (!isNodeStartup)\n            {\n                prepareForNewLeadership(nowNs);\n            }\n        }\n        else\n        {\n            stopLogReplication();\n            stopCatchup();\n\n            prepareForNewLeadership(nowNs);\n            logSessionId = NULL_SESSION_ID;\n            stopReplay();\n\n            if (null != logSubscription)\n            {\n                CloseHelper.close(logSubscription);\n                consensusModuleAgent.awaitLocalSocketsClosed(logSubscription.registrationId());\n                logSubscription = null;\n            }\n        }\n\n        notifiedCommitPosition = 0;\n        candidateTermId = max(ctx.nodeStateFile().candidateTerm().candidateTermId(), leadershipTermId);\n\n        if (clusterMembers.length == 1 && thisMember.id() == clusterMembers[0].id())\n        {\n            // short-circuit nominating self to a candidate and winning the ballot\n            candidateTermId += 1;\n            ctx.nodeStateFile().updateCandidateTermId(candidateTermId, logPosition, ctx.epochClock().time());\n            leaderMember = thisMember;\n            leadershipTermId = candidateTermId;\n            state(LEADER_LOG_REPLICATION, nowNs, \"single-node cluster leader\");\n        }\n        else\n        {\n            state(CANVASS, nowNs, \"\");\n        }\n\n        return 1;\n    }\n\n    private int canvass(final long nowNs)\n    {\n        int workCount = 0;\n        final long deadlineNs = isExtendedCanvass ?\n            timeOfLastStateChangeNs + ctx.startupCanvassTimeoutNs() :\n            consensusModuleAgent.timeOfLastLeaderUpdateNs() + ctx.leaderHeartbeatTimeoutNs();\n\n        if (hasUpdateIntervalExpired(nowNs, ctx.electionStatusIntervalNs()))\n        {\n            timeOfLastUpdateNs = nowNs;\n            publishCanvassPosition();\n\n            workCount++;\n        }\n\n        if (ctx.appointedLeaderId() != NULL_VALUE && ctx.appointedLeaderId() != thisMember.id())\n        {\n            return workCount;\n        }\n\n        if (ClusterMember.isUnanimousCandidate(clusterMembers, thisMember, gracefulClosedLeaderId) ||\n            (nowNs >= deadlineNs && ClusterMember.isQuorumCandidate(clusterMembers, thisMember)))\n        {\n            final long delayNs = (long)(ctx.random().nextDouble() * (ctx.electionTimeoutNs() >> 1));\n            nominationDeadlineNs = nowNs + delayNs;\n            state(NOMINATE, nowNs, \"\");\n            workCount++;\n        }\n\n        return workCount;\n    }\n\n    private int nominate(final long nowNs)\n    {\n        if (nowNs >= nominationDeadlineNs)\n        {\n            candidateTermId = ctx.nodeStateFile().proposeMaxCandidateTermId(\n                candidateTermId + 1, logPosition, ctx.epochClock().time());\n            ClusterMember.becomeCandidate(clusterMembers, candidateTermId, thisMember.id());\n            state(CANDIDATE_BALLOT, nowNs, \"\");\n\n            return 1;\n        }\n        else if (hasUpdateIntervalExpired(nowNs, ctx.electionStatusIntervalNs()))\n        {\n            timeOfLastUpdateNs = nowNs;\n            publishCanvassPosition();\n\n            return 1;\n        }\n\n        return 0;\n    }\n\n    private int candidateBallot(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (ClusterMember.isUnanimousLeader(clusterMembers, candidateTermId, gracefulClosedLeaderId))\n        {\n            leaderMember = thisMember;\n            leadershipTermId = candidateTermId;\n            state(LEADER_LOG_REPLICATION, nowNs, \"unanimous leader\");\n            workCount++;\n        }\n        else if (nowNs >= (timeOfLastStateChangeNs + ctx.electionTimeoutNs()))\n        {\n            if (ClusterMember.isQuorumLeader(clusterMembers, candidateTermId))\n            {\n                leaderMember = thisMember;\n                leadershipTermId = candidateTermId;\n                state(LEADER_LOG_REPLICATION, nowNs, \"quorum leader\");\n            }\n            else\n            {\n                state(CANVASS, nowNs, \"\");\n            }\n\n            workCount++;\n        }\n        else\n        {\n            for (final ClusterMember member : clusterMembers)\n            {\n                if (!member.isBallotSent())\n                {\n                    workCount++;\n                    member.isBallotSent(consensusPublisher.requestVote(\n                        member.publication(), logLeadershipTermId, appendPosition, candidateTermId, thisMember.id()));\n                }\n            }\n        }\n\n        return workCount;\n    }\n\n    private int followerBallot(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (nowNs >= (timeOfLastStateChangeNs + ctx.electionTimeoutNs()))\n        {\n            state(CANVASS, nowNs, \"\");\n            workCount++;\n        }\n\n        return workCount;\n    }\n\n    private int leaderLogReplication(final long nowNs)\n    {\n        int workCount = 0;\n\n        thisMember.logPosition(appendPosition).timeOfLastAppendPositionNs(nowNs);\n\n        final long quorumPosition = consensusModuleAgent.quorumPositionBoundedByLeaderLog(appendPosition, nowNs);\n        workCount += publishNewLeadershipTermOnInterval(quorumPosition, nowNs);\n        workCount += publishCommitPositionOnInterval(quorumPosition, nowNs);\n\n        if (quorumPosition >= appendPosition)\n        {\n            workCount++;\n            state(LEADER_REPLAY, nowNs, \"\");\n        }\n\n        return workCount;\n    }\n\n    private int leaderReplay(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (null == logReplay)\n        {\n            if (logPosition < appendPosition)\n            {\n                logReplay = consensusModuleAgent.newLogReplay(logPosition, appendPosition);\n            }\n            else\n            {\n                state(LEADER_INIT, nowNs, \"\");\n            }\n\n            workCount++;\n            isLeaderStartup = isNodeStartup;\n            thisMember\n                .leadershipTermId(leadershipTermId)\n                .logPosition(appendPosition)\n                .timeOfLastAppendPositionNs(nowNs);\n        }\n        else\n        {\n            workCount += logReplay.doWork();\n            if (logReplay.isDone())\n            {\n                stopReplay();\n                logPosition = appendPosition;\n                state(LEADER_INIT, nowNs, \"\");\n            }\n        }\n\n        final long quorumPosition = consensusModuleAgent.quorumPositionBoundedByLeaderLog(appendPosition, nowNs);\n        workCount += publishNewLeadershipTermOnInterval(quorumPosition, nowNs);\n        workCount += publishCommitPositionOnInterval(quorumPosition, nowNs);\n\n        return workCount;\n    }\n\n    private int leaderInit(final long nowNs)\n    {\n        consensusModuleAgent.joinLogAsLeader(leadershipTermId, logPosition, logSessionId, isLeaderStartup);\n        updateRecordingLog(nowNs);\n        state(LEADER_READY, nowNs, \"\");\n\n        return 1;\n    }\n\n    private int leaderReady(final long nowNs)\n    {\n        final long quorumPosition = consensusModuleAgent.quorumPositionBoundedByLeaderLog(appendPosition, nowNs);\n        int workCount = consensusModuleAgent.updateLeaderPosition(nowNs, appendPosition, quorumPosition);\n        workCount += publishNewLeadershipTermOnInterval(quorumPosition, nowNs);\n\n        if (ClusterMember.hasQuorumAtPosition(\n                clusterMembers, leadershipTermId, logPosition, nowNs, ctx.leaderHeartbeatTimeoutNs()))\n        {\n            if (consensusModuleAgent.appendNewLeadershipTermEvent(nowNs))\n            {\n                consensusModuleAgent.electionComplete(nowNs);\n                state(CLOSED, nowNs, \"\");\n                workCount++;\n            }\n        }\n\n        return workCount;\n    }\n\n    private int followerLogReplication(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (null == logReplication)\n        {\n            if (appendPosition < replicationStopPosition)\n            {\n                logReplication = consensusModuleAgent.newLogReplication(\n                    leaderMember.archiveEndpoint(),\n                    leaderMember.archiveResponseEndpoint(),\n                    leaderRecordingId,\n                    replicationStopPosition,\n                    nowNs);\n                replicationDeadlineNs = nowNs + ctx.leaderHeartbeatTimeoutNs();\n                workCount++;\n            }\n            else\n            {\n                updateRecordingLogForReplication(\n                    replicationLeadershipTermId, replicationTermBaseLogPosition, replicationStopPosition, nowNs);\n                state(CANVASS, nowNs, \"\");\n            }\n        }\n        else\n        {\n            workCount += consensusModuleAgent.pollArchiveEvents();\n            logReplication.poll(nowNs);\n            final boolean replicationDone = logReplication.hasReplicationEnded() && logReplication.hasStopped();\n            // Log replication runs concurrently, calling this after the check for completion ensures that the\n            // last position at the end of the leadership is published as an appendPosition event.\n            workCount += publishFollowerReplicationPosition(nowNs);\n\n            if (replicationDone)\n            {\n                if (notifiedCommitPosition >= appendPosition)\n                {\n                    ConsensusModuleAgent.logReplicationEnded(\n                        thisMember.id(),\n                        \"ELECTION\",\n                        logReplication.srcArchiveChannel(),\n                        logReplication.recordingId(),\n                        leaderRecordingId,\n                        logReplication.position(),\n                        logReplication.hasSynced());\n\n                    appendPosition = logReplication.position();\n                    stopLogReplication();\n                    updateRecordingLogForReplication(\n                        replicationLeadershipTermId, replicationTermBaseLogPosition, replicationStopPosition, nowNs);\n                    state(CANVASS, nowNs, \"\");\n                    workCount++;\n                }\n                else if (nowNs >= replicationDeadlineNs)\n                {\n                    throw new TimeoutException(\"timeout awaiting commit position\", AeronException.Category.WARN);\n                }\n            }\n        }\n\n        return workCount;\n    }\n\n    private int followerReplay(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (null == logReplay)\n        {\n            if (logPosition < appendPosition)\n            {\n                if (0 == notifiedCommitPosition)\n                {\n                    return publishFollowerAppendPosition(nowNs);\n                }\n                else if (logPosition >= notifiedCommitPosition)\n                {\n                    state(CANVASS, nowNs, \"log replay rejected: logPosition=\" + logPosition + \" is \" +\n                        (logPosition > notifiedCommitPosition ? \"greater than\" : \"equal to\") +\n                        \" quorumPosition=\" + notifiedCommitPosition);\n                }\n                else\n                {\n                    logReplay =\n                        consensusModuleAgent.newLogReplay(logPosition, min(appendPosition, notifiedCommitPosition));\n                    workCount++;\n                }\n            }\n            else\n            {\n                state(\n                    NULL_POSITION != catchupJoinPosition ? FOLLOWER_CATCHUP_INIT : FOLLOWER_LOG_INIT,\n                    nowNs,\n                    \"nothing to replay\");\n                workCount++;\n            }\n        }\n        else\n        {\n            workCount += logReplay.doWork();\n            if (logReplay.isDone())\n            {\n                logPosition = logReplay.position();\n                stopReplay();\n\n                if (logPosition == appendPosition)\n                {\n                    state(\n                        NULL_POSITION != catchupJoinPosition ? FOLLOWER_CATCHUP_INIT : FOLLOWER_LOG_INIT,\n                        nowNs,\n                        \"log replay done\");\n                }\n                else\n                {\n                    state(CANVASS, nowNs, \"incomplete log replay: logPosition=\" + logPosition +\n                        \" appendPosition=\" + appendPosition);\n                }\n            }\n        }\n\n        return workCount;\n    }\n\n    private int followerCatchupInit(final long nowNs)\n    {\n        if (null == logSubscription)\n        {\n            logSubscription = addFollowerSubscription();\n            addCatchupLogDestination();\n        }\n\n        String catchupEndpoint = null;\n        final String endpoint = thisMember.catchupEndpoint();\n        if (endpoint.endsWith(\":0\"))\n        {\n            final String resolvedEndpoint = logSubscription.resolvedEndpoint();\n            if (null != resolvedEndpoint)\n            {\n                final int i = resolvedEndpoint.lastIndexOf(':');\n                catchupEndpoint = endpoint.substring(0, endpoint.length() - 2) + resolvedEndpoint.substring(i);\n            }\n        }\n        else\n        {\n            catchupEndpoint = endpoint;\n        }\n\n        if (null != catchupEndpoint && sendCatchupPosition(catchupEndpoint))\n        {\n            timeOfLastUpdateNs = nowNs;\n            consensusModuleAgent.catchupInitiated(nowNs);\n            state(FOLLOWER_CATCHUP_AWAIT, nowNs, \"\");\n        }\n        else if (nowNs >= (timeOfLastStateChangeNs + ctx.leaderHeartbeatTimeoutNs()))\n        {\n            throw new TimeoutException(\"failed to send catchup position\", AeronException.Category.WARN);\n        }\n\n        return 1;\n    }\n\n    private int followerCatchupAwait(final long nowNs)\n    {\n        int workCount = 0;\n\n        final Image image = logSubscription.imageBySessionId(logSessionId);\n        if (null != image)\n        {\n            verifyLogJoinPosition(\"followerCatchupAwait\", image.joinPosition());\n            if (consensusModuleAgent.tryJoinLogAsFollower(image, isLeaderStartup, nowNs))\n            {\n                state(FOLLOWER_CATCHUP, nowNs, \"\");\n                workCount++;\n            }\n            else if (ChannelEndpointStatus.ERRORED == logSubscription.channelStatus())\n            {\n                final String message = \"failed to add catchup log as follower - \" + logSubscription.channel();\n                throw new ClusterException(message, AeronException.Category.WARN);\n            }\n            else if (nowNs >= (timeOfLastStateChangeNs + ctx.leaderHeartbeatTimeoutNs()))\n            {\n                throw new TimeoutException(\"failed to join catchup log as follower\", AeronException.Category.WARN);\n            }\n        }\n        else if (nowNs >= (timeOfLastStateChangeNs + ctx.leaderHeartbeatTimeoutNs()))\n        {\n            throw new TimeoutException(\"failed to join catchup log\", AeronException.Category.WARN);\n        }\n\n        return workCount;\n    }\n\n    private int followerCatchup(final long nowNs)\n    {\n        int workCount = consensusModuleAgent.catchupPoll(notifiedCommitPosition, nowNs);\n\n        if (null == consensusModuleAgent.liveLogDestination() &&\n            consensusModuleAgent.isCatchupNearLive(max(catchupJoinPosition, notifiedCommitPosition)))\n        {\n            addLiveLogDestination();\n            workCount++;\n        }\n\n        final long position = ctx.commitPositionCounter().getPlain();\n        if (position >= catchupJoinPosition &&\n            position >= notifiedCommitPosition &&\n            null == consensusModuleAgent.catchupLogDestination() &&\n            ConsensusModule.State.SNAPSHOT != consensusModuleAgent.state())\n        {\n            appendPosition = position;\n            logPosition = position;\n            state(FOLLOWER_LOG_INIT, nowNs, \"\");\n            workCount++;\n        }\n\n        return workCount;\n    }\n\n    private int followerLogInit(final long nowNs)\n    {\n        if (null == logSubscription)\n        {\n            if (NULL_SESSION_ID != logSessionId)\n            {\n                logSubscription = addFollowerSubscription();\n                addLiveLogDestination();\n                state(FOLLOWER_LOG_AWAIT, nowNs, \"\");\n            }\n        }\n        else\n        {\n            state(FOLLOWER_READY, nowNs, \"\");\n        }\n\n        return 1;\n    }\n\n    private int followerLogAwait(final long nowNs)\n    {\n        int workCount = 0;\n\n        final Image image = logSubscription.imageBySessionId(logSessionId);\n        if (null != image)\n        {\n            verifyLogJoinPosition(\"followerLogAwait\", image.joinPosition());\n            if (consensusModuleAgent.tryJoinLogAsFollower(image, isLeaderStartup, nowNs))\n            {\n                updateRecordingLog(nowNs);\n                state(FOLLOWER_READY, nowNs, \"\");\n                workCount++;\n            }\n            else if (nowNs >= (timeOfLastStateChangeNs + ctx.leaderHeartbeatTimeoutNs()))\n            {\n                throw new TimeoutException(\"failed to join live log as follower\", AeronException.Category.WARN);\n            }\n        }\n        else if (ChannelEndpointStatus.ERRORED == logSubscription.channelStatus())\n        {\n            final String message = \"failed to add live log as follower - \" + logSubscription.channel();\n            throw new ClusterException(message, AeronException.Category.WARN);\n        }\n        else if (nowNs >= (timeOfLastStateChangeNs + ctx.leaderHeartbeatTimeoutNs()))\n        {\n            throw new TimeoutException(\"failed to join live log\", AeronException.Category.WARN);\n        }\n\n        return workCount;\n    }\n\n    private int followerReady(final long nowNs)\n    {\n        if (consensusPublisher.appendPosition(\n            leaderMember.publication(), leadershipTermId, logPosition, thisMember.id(), APPEND_POSITION_FLAG_NONE))\n        {\n            consensusModuleAgent.electionComplete(nowNs);\n            state(CLOSED, nowNs, \"\");\n        }\n        else if (nowNs >= (timeOfLastStateChangeNs + ctx.leaderHeartbeatTimeoutNs()))\n        {\n            throw new TimeoutException(\"ready follower failed to notify leader\", AeronException.Category.WARN);\n        }\n\n        return 1;\n    }\n\n    private void placeVote(final long candidateTermId, final int candidateId, final boolean vote)\n    {\n        final ClusterMember candidate = clusterMemberByIdMap.get(candidateId);\n        if (null != candidate)\n        {\n            consensusPublisher.placeVote(\n                candidate.publication(),\n                candidateTermId,\n                logLeadershipTermId,\n                appendPosition,\n                candidateId,\n                thisMember.id(),\n                vote);\n        }\n    }\n\n    private int publishNewLeadershipTermOnInterval(final long quorumPosition, final long nowNs)\n    {\n        int workCount = 0;\n\n        if (hasUpdateIntervalExpired(nowNs, ctx.leaderHeartbeatIntervalNs()))\n        {\n            timeOfLastUpdateNs = nowNs;\n            publishNewLeadershipTerm(quorumPosition, nanosToTimestamp(ctx, nowNs));\n            workCount++;\n        }\n\n        return workCount;\n    }\n\n    private int publishCommitPositionOnInterval(final long quorumPosition, final long nowNs)\n    {\n        int workCount = 0;\n\n        // We would stop sending `commitPosition` if `quorumPosition` regresses (goes backwards) during election. This\n        // is fine, because the NewLeadershipTerm continues to be sent on a regular interval.\n        if (lastPublishedCommitPosition < quorumPosition ||\n            (lastPublishedCommitPosition == quorumPosition &&\n            hasIntervalExpired(nowNs, timeOfLastCommitPositionUpdateNs, ctx.leaderHeartbeatIntervalNs())))\n        {\n            timeOfLastCommitPositionUpdateNs = nowNs;\n            lastPublishedCommitPosition = quorumPosition;\n            // use `leadershipTermId` of the election, i.e. match the value used for the `NewLeadershipTerm` event\n            consensusModuleAgent.publishCommitPosition(quorumPosition, leadershipTermId);\n            workCount++;\n        }\n\n        return workCount;\n    }\n\n    private void publishCanvassPosition()\n    {\n        for (final ClusterMember member : clusterMembers)\n        {\n            if (member.id() != thisMember.id())\n            {\n                if (null == member.publication())\n                {\n                    ClusterMember.tryAddPublication(\n                        member,\n                        ctx.consensusStreamId(),\n                        ctx.aeron(),\n                        ctx.countedErrorHandler());\n                }\n\n                consensusPublisher.canvassPosition(\n                    member.publication(), logLeadershipTermId, appendPosition, leadershipTermId, thisMember.id());\n            }\n        }\n    }\n\n    private void publishNewLeadershipTerm(final long quorumPosition, final long timestamp)\n    {\n        for (final ClusterMember member : clusterMembers)\n        {\n            publishNewLeadershipTerm(member, logLeadershipTermId, quorumPosition, timestamp);\n        }\n    }\n\n    private void publishNewLeadershipTerm(\n        final ClusterMember member,\n        final long logLeadershipTermId,\n        final long quorumPosition,\n        final long timestamp)\n    {\n        if (member.id() != thisMember.id() && NULL_SESSION_ID != logSessionId)\n        {\n            final RecordingLog.Entry logNextTermEntry =\n                ctx.recordingLog().findTermEntry(logLeadershipTermId + 1);\n\n            final long nextLeadershipTermId = null != logNextTermEntry ?\n                logNextTermEntry.leadershipTermId : leadershipTermId;\n            final long nextTermBaseLogPosition = null != logNextTermEntry ?\n                logNextTermEntry.termBaseLogPosition : appendPosition;\n\n            final long nextLogPosition = null != logNextTermEntry ?\n                (NULL_POSITION != logNextTermEntry.logPosition ? logNextTermEntry.logPosition : appendPosition) :\n                NULL_POSITION;\n\n            consensusPublisher.newLeadershipTerm(\n                member.publication(),\n                logLeadershipTermId,\n                nextLeadershipTermId,\n                nextTermBaseLogPosition,\n                nextLogPosition,\n                leadershipTermId,\n                appendPosition,\n                appendPosition,\n                quorumPosition,\n                consensusModuleAgent.logRecordingId(),\n                timestamp,\n                thisMember.id(),\n                logSessionId,\n                ctx.appVersion(),\n                isLeaderStartup);\n        }\n    }\n\n    private int publishFollowerReplicationPosition(final long nowNs)\n    {\n        final long position = logReplication.position();\n        if (position > appendPosition ||\n            (position == appendPosition && hasUpdateIntervalExpired(nowNs, ctx.leaderHeartbeatIntervalNs())))\n        {\n            if (consensusPublisher.appendPosition(\n                leaderMember.publication(), leadershipTermId, position, thisMember.id(), APPEND_POSITION_FLAG_NONE))\n            {\n                appendPosition = position;\n                timeOfLastUpdateNs = nowNs;\n                return 1;\n            }\n        }\n\n        return 0;\n    }\n\n    private int publishFollowerAppendPosition(final long nowNs)\n    {\n        if (lastPublishedAppendPosition != appendPosition ||\n            hasUpdateIntervalExpired(nowNs, ctx.leaderHeartbeatIntervalNs()))\n        {\n            if (consensusPublisher.appendPosition(\n                leaderMember.publication(),\n                leadershipTermId,\n                appendPosition,\n                thisMember.id(),\n                APPEND_POSITION_FLAG_NONE))\n            {\n                lastPublishedAppendPosition = appendPosition;\n                timeOfLastUpdateNs = nowNs;\n                return 1;\n            }\n        }\n        return 0;\n    }\n\n    private boolean sendCatchupPosition(final String catchupEndpoint)\n    {\n        return consensusPublisher.catchupPosition(\n            leaderMember.publication(), leadershipTermId, logPosition, thisMember.id(), catchupEndpoint);\n    }\n\n    private void addCatchupLogDestination()\n    {\n        final String destination = ChannelUri.createDestinationUri(ctx.logChannel(), thisMember.catchupEndpoint());\n        logSubscription.addDestination(destination);\n        consensusModuleAgent.catchupLogDestination(destination);\n    }\n\n    private void addLiveLogDestination()\n    {\n        final String destination;\n        if (ctx.isLogMdc())\n        {\n            destination = ChannelUri.createDestinationUri(ctx.logChannel(), thisMember.logEndpoint());\n        }\n        else\n        {\n            destination = ctx.logChannel();\n        }\n        logSubscription.addDestination(destination);\n        consensusModuleAgent.liveLogDestination(destination);\n    }\n\n    private Subscription addFollowerSubscription()\n    {\n        final Aeron aeron = ctx.aeron();\n        final ChannelUri logChannelUri = ChannelUri.parse(ctx.logChannel());\n        final String channel = new ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .tags(aeron.nextCorrelationId() + \",\" + aeron.nextCorrelationId())\n            .controlMode(MDC_CONTROL_MODE_MANUAL)\n            .sessionId(logSessionId)\n            .group(Boolean.TRUE)\n            .rejoin(Boolean.FALSE)\n            .socketRcvbufLength(logChannelUri)\n            .receiverWindowLength(logChannelUri)\n            .alias(\"log-cm\")\n            .build();\n\n        return aeron.addSubscription(channel, ctx.logStreamId());\n    }\n\n    private void state(final ElectionState newState, final long nowNs, final String reason)\n    {\n        if (newState != state)\n        {\n            if (CANVASS == state)\n            {\n                isExtendedCanvass = false;\n            }\n\n            switch (newState)\n            {\n                case CANVASS:\n                    resetMembers();\n                    consensusModuleAgent.role(Cluster.Role.FOLLOWER);\n                    break;\n\n                case CANDIDATE_BALLOT:\n                    consensusModuleAgent.role(Cluster.Role.CANDIDATE);\n                    break;\n\n                case LEADER_LOG_REPLICATION:\n                    consensusModuleAgent.role(Cluster.Role.LEADER);\n                    logSessionId = consensusModuleAgent.addLogPublication(appendPosition);\n                    break;\n\n                case FOLLOWER_LOG_REPLICATION:\n                case FOLLOWER_REPLAY:\n                    consensusModuleAgent.role(Cluster.Role.FOLLOWER);\n                    break;\n\n                default:\n                    break;\n            }\n\n            logStateChange(\n                thisMember.id(),\n                state,\n                newState,\n                null != leaderMember ? leaderMember.id() : NULL_VALUE,\n                candidateTermId,\n                leadershipTermId,\n                logPosition,\n                logLeadershipTermId,\n                appendPosition,\n                catchupJoinPosition,\n                reason);\n\n            state = newState;\n            ctx.electionStateCounter().setRelease(newState.code());\n            timeOfLastStateChangeNs = nowNs;\n            timeOfLastUpdateNs = initialTimeOfLastUpdateNs;\n            timeOfLastCommitPositionUpdateNs = initialTimeOfLastUpdateNs;\n        }\n    }\n\n    private void stopCatchup()\n    {\n        consensusModuleAgent.stopAllCatchups();\n        catchupJoinPosition = NULL_POSITION;\n    }\n\n    private void resetMembers()\n    {\n        ClusterMember.reset(clusterMembers);\n        thisMember.leadershipTermId(leadershipTermId).logPosition(appendPosition);\n        leaderMember = null;\n    }\n\n    private void stopReplay()\n    {\n        if (null != logReplay)\n        {\n            logReplay.close();\n            logReplay = null;\n        }\n        lastPublishedAppendPosition = 0;\n    }\n\n    private void stopLogReplication()\n    {\n        if (null != logReplication)\n        {\n            logReplication.close();\n            logReplication = null;\n        }\n        replicationDeadlineNs = 0;\n        lastPublishedCommitPosition = 0;\n    }\n\n    private void ensureRecordingLogCoherent(\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long nowNs)\n    {\n        ensureRecordingLogCoherent(\n            ctx,\n            consensusModuleAgent.logRecordingId(),\n            initialLogLeadershipTermId,\n            initialTermBaseLogPosition,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            nowNs);\n    }\n\n    static void ensureRecordingLogCoherent(\n        final ConsensusModule.Context ctx,\n        final long recordingId,\n        final long initialLogLeadershipTermId,\n        final long initialTermBaseLogPosition,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long nowNs)\n    {\n        if (NULL_VALUE == recordingId)\n        {\n            // This can happen during a log replication if the initial appendPosition != 0 and\n            // nextTermLogPosition == appendPosition.\n            return;\n        }\n\n        final long timestamp = nanosToTimestamp(ctx, nowNs);\n        final RecordingLog recordingLog = ctx.recordingLog();\n\n        recordingLog.ensureCoherent(\n            recordingId,\n            initialLogLeadershipTermId,\n            initialTermBaseLogPosition,\n            leadershipTermId,\n            NULL_VALUE != termBaseLogPosition ? termBaseLogPosition : initialTermBaseLogPosition,\n            logPosition,\n            nowNs,\n            timestamp,\n            ctx.fileSyncLevel());\n    }\n\n    private void updateRecordingLog(final long nowNs)\n    {\n        ensureRecordingLogCoherent(leadershipTermId, logPosition, NULL_VALUE, nowNs);\n        logLeadershipTermId = leadershipTermId;\n    }\n\n    private void updateRecordingLogForReplication(\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long nowNs)\n    {\n        ensureRecordingLogCoherent(leadershipTermId, termBaseLogPosition, logPosition, nowNs);\n        logLeadershipTermId = leadershipTermId;\n    }\n\n    private void verifyLogJoinPosition(final String state, final long joinPosition)\n    {\n        if (joinPosition != logPosition)\n        {\n            final String inequality = joinPosition < logPosition ? \" less \" : \" greater \";\n            throw new ClusterEvent(\n                state + \" - joinPosition=\" + joinPosition + inequality + \"than logPosition=\" + logPosition);\n        }\n    }\n\n    private boolean hasUpdateIntervalExpired(final long nowNs, final long intervalNs)\n    {\n        return hasIntervalExpired(nowNs, timeOfLastUpdateNs, intervalNs);\n    }\n\n    private boolean hasIntervalExpired(\n        final long nowNs, final long previousTimestampForIntervalNs, final long intervalNs)\n    {\n        return (nowNs - previousTimestampForIntervalNs) >= intervalNs;\n    }\n\n    private void logStateChange(\n        final int memberId,\n        final ElectionState oldState,\n        final ElectionState newState,\n        final int leaderId,\n        final long candidateTermId,\n        final long leadershipTermId,\n        final long logPosition,\n        final long logLeadershipTermId,\n        final long appendPosition,\n        final long catchupPosition,\n        final String reason)\n    {\n        /*\n        System.out.println(\"Election: memberId=\" + memberId + \" \" + oldState + \" -> \" + newState +\n            \" leaderId=\" + leaderId +\n            \" candidateTermId=\" + candidateTermId +\n            \" leadershipTermId=\" + leadershipTermId +\n            \" logPosition=\" + logPosition +\n            \" logLeadershipTermId=\" + logLeadershipTermId +\n            \" appendPosition=\" + appendPosition +\n            \" catchupPosition=\" + catchupPosition +\n            \" notifiedCommitPosition=\" + notifiedCommitPosition +\n            \" reason=\" + reason);\n         */\n    }\n\n    private void prepareForNewLeadership(final long nowNs)\n    {\n        final long lastAppendPosition = consensusModuleAgent.prepareForNewLeadership(logPosition, nowNs);\n        if (NULL_POSITION != lastAppendPosition)\n        {\n            appendPosition = lastAppendPosition;\n        }\n    }\n\n    private static long nowNs(final ConsensusModule.Context ctx)\n    {\n        return ctx.clusterClock().timeNanos();\n    }\n\n    private static long nanosToTimestamp(final ConsensusModule.Context ctx, final long timeNs)\n    {\n        return ctx.clusterClock().timeUnit().convert(timeNs, TimeUnit.NANOSECONDS);\n    }\n\n    private static long timestampToNanos(final ConsensusModule.Context ctx, final long timestamp)\n    {\n        return ctx.clusterClock().timeUnit().toNanos(timestamp);\n    }\n\n    public String toString()\n    {\n        return \"Election{\" +\n            \"clusterMembers=\" + Arrays.toString(clusterMembers) +\n            \", thisMember=\" + thisMember +\n            \", leaderMember=\" + leaderMember +\n            \", initialLogLeadershipTermId=\" + initialLogLeadershipTermId +\n            \", initialTimeOfLastUpdateNs=\" + initialTimeOfLastUpdateNs +\n            \", initialTermBaseLogPosition=\" + initialTermBaseLogPosition +\n            \", isNodeStartup=\" + isNodeStartup +\n            \", timeOfLastStateChangeNs=\" + timeOfLastStateChangeNs +\n            \", timeOfLastUpdateNs=\" + timeOfLastUpdateNs +\n            \", timeOfLastCommitPositionUpdateNs=\" + timeOfLastCommitPositionUpdateNs +\n            \", nominationDeadlineNs=\" + nominationDeadlineNs +\n            \", logPosition=\" + logPosition +\n            \", appendPosition=\" + appendPosition +\n            \", notifiedCommitPosition=\" + notifiedCommitPosition +\n            \", catchupJoinPosition=\" + catchupJoinPosition +\n            \", replicationLeadershipTermId=\" + replicationLeadershipTermId +\n            \", replicationStopPosition=\" + replicationStopPosition +\n            \", replicationDeadlineNs=\" + replicationDeadlineNs +\n            \", replicationTermBaseLogPosition=\" + replicationTermBaseLogPosition +\n            \", leaderRecordingId=\" + leaderRecordingId +\n            \", leadershipTermId=\" + leadershipTermId +\n            \", logLeadershipTermId=\" + logLeadershipTermId +\n            \", candidateTermId=\" + candidateTermId +\n            \", lastPublishedCommitPosition=\" + lastPublishedCommitPosition +\n            \", logSessionId=\" + logSessionId +\n            \", gracefulClosedLeaderId=\" + gracefulClosedLeaderId +\n            \", isFirstInit=\" + isFirstInit +\n            \", isLeaderStartup=\" + isLeaderStartup +\n            \", isExtendedCanvass=\" + isExtendedCanvass +\n            \", state=\" + state +\n            \", logSubscription=\" + logSubscription +\n            \", logReplay=\" + logReplay +\n            \", logReplication=\" + logReplication +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ElectionState.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.client.ClusterException;\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Election states for a {@link ConsensusModule} which get represented by a {@link #code()} stored in a\n * {@link io.aeron.Counter} of the type {@link ConsensusModule.Configuration#ELECTION_STATE_TYPE_ID}.\n */\npublic enum ElectionState\n{\n    /**\n     * Consolidate local state and prepare for new leadership.\n     */\n    INIT(0),\n\n    /**\n     * Canvass members for current state and to assess if a successful leadership attempt can be mounted.\n     */\n    CANVASS(1),\n\n    /**\n     * Nominate member for new leadership by requesting votes.\n     */\n    NOMINATE(2),\n\n    /**\n     * Await ballot outcome from members on candidacy for leadership.\n     */\n    CANDIDATE_BALLOT(3),\n\n    /**\n     * Await ballot outcome after voting for a candidate.\n     */\n    FOLLOWER_BALLOT(4),\n\n    /**\n     * Wait for followers to replicate any missing log entries to track commit position.\n     */\n    LEADER_LOG_REPLICATION(5),\n\n    /**\n     * Replay local log in preparation for new leadership term.\n     */\n    LEADER_REPLAY(6),\n\n    /**\n     * Initialise state for new leadership term.\n     */\n    LEADER_INIT(7),\n\n    /**\n     * Publish new leadership term and commit position, while awaiting followers ready.\n     */\n    LEADER_READY(8),\n\n    /**\n     * Replicate missing log entries from the leader.\n     */\n    FOLLOWER_LOG_REPLICATION(9),\n\n    /**\n     * Replay local log in preparation for following new leader.\n     */\n    FOLLOWER_REPLAY(10),\n\n    /**\n     * Initialise catch-up in preparation of receiving a replay from the leader to catch up in current term.\n     */\n    FOLLOWER_CATCHUP_INIT(11),\n\n    /**\n     * Await joining a replay from leader to catch-up.\n     */\n    FOLLOWER_CATCHUP_AWAIT(12),\n\n    /**\n     * Catch-up to leader until live log can be added and merged.\n     */\n    FOLLOWER_CATCHUP(13),\n\n    /**\n     * Initialise follower in preparation for joining the live log.\n     */\n    FOLLOWER_LOG_INIT(14),\n\n    /**\n     * Await joining the live log from the leader.\n     */\n    FOLLOWER_LOG_AWAIT(15),\n\n    /**\n     * Publish append position to leader to signify ready for new term.\n     */\n    FOLLOWER_READY(16),\n\n    /**\n     * Election is closed after new leader is established.\n     */\n    CLOSED(17);\n\n    static final ElectionState[] STATES = values();\n\n    private final int code;\n\n    ElectionState(final int code)\n    {\n        if (code != ordinal())\n        {\n            throw new IllegalArgumentException(name() + \" - code must equal ordinal value: code=\" + code);\n        }\n\n        this.code = code;\n    }\n\n    /**\n     * Code stored in a {@link io.aeron.Counter} to represent the election state.\n     *\n     * @return code stored in a {@link io.aeron.Counter} to represent the election state.\n     */\n    public int code()\n    {\n        return code;\n    }\n\n    /**\n     * Get the enum value for a given code stored in a counter.\n     *\n     * @param code representing election state.\n     * @return the enum value for a given code stored in a counter.\n     */\n    public static ElectionState get(final long code)\n    {\n        if (code < 0 || code > (STATES.length - 1))\n        {\n            throw new ClusterException(\"invalid election state counter code: \" + code);\n        }\n\n        return STATES[(int)code];\n    }\n\n    /**\n     * Get the {@link ElectionState} value based on the value stored in an {@link AtomicCounter}.\n     *\n     * @param counter to read the value for matching against {@link #code()}.\n     * @return the {@link ElectionState} value based on the value stored in an {@link AtomicCounter}.\n     */\n    public static ElectionState get(final AtomicCounter counter)\n    {\n        if (counter.isClosed())\n        {\n            return CLOSED;\n        }\n\n        return get(counter.get());\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/IngressAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.ArrayUtil;\n\nclass IngressAdapter implements AutoCloseable\n{\n    private final int fragmentPollLimit;\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SessionConnectRequestDecoder connectRequestDecoder = new SessionConnectRequestDecoder();\n    private final SessionCloseRequestDecoder closeRequestDecoder = new SessionCloseRequestDecoder();\n    private final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n    private final SessionKeepAliveDecoder sessionKeepAliveDecoder = new SessionKeepAliveDecoder();\n    private final ChallengeResponseDecoder challengeResponseDecoder = new ChallengeResponseDecoder();\n    private final AdminRequestDecoder adminRequestDecoder = new AdminRequestDecoder();\n    private final ControlledFragmentAssembler udpFragmentAssembler = new ControlledFragmentAssembler(this::onMessage);\n    private final ControlledFragmentAssembler ipcFragmentAssembler = new ControlledFragmentAssembler(this::onMessage);\n    private final ConsensusModuleAgent consensusModuleAgent;\n    private Subscription subscription;\n    private Subscription ipcSubscription;\n\n    IngressAdapter(final int fragmentPollLimit, final ConsensusModuleAgent consensusModuleAgent)\n    {\n        this.fragmentPollLimit = fragmentPollLimit;\n        this.consensusModuleAgent = consensusModuleAgent;\n    }\n\n    public void close()\n    {\n        final Subscription subscription = this.subscription;\n        final Subscription ipcSubscription = this.ipcSubscription;\n\n        this.subscription = null;\n        this.ipcSubscription = null;\n\n        if (null != subscription)\n        {\n            subscription.close();\n        }\n\n        if (null != ipcSubscription)\n        {\n            ipcSubscription.close();\n        }\n\n        udpFragmentAssembler.clear();\n        ipcFragmentAssembler.clear();\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    public ControlledFragmentHandler.Action onMessage(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        final int templateId = messageHeaderDecoder.templateId();\n        final int actingVersion = messageHeaderDecoder.version();\n        final int actingBlockLength = messageHeaderDecoder.blockLength();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            return consensusModuleAgent.onExtensionMessage(\n                actingBlockLength, templateId, schemaId, actingVersion, buffer, offset, length, header);\n        }\n\n        if (templateId == SessionMessageHeaderDecoder.TEMPLATE_ID)\n        {\n            sessionMessageHeaderDecoder.wrap(\n                buffer,\n                offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                messageHeaderDecoder.blockLength(),\n                actingVersion);\n\n            return consensusModuleAgent.onIngressMessage(\n                sessionMessageHeaderDecoder.leadershipTermId(),\n                sessionMessageHeaderDecoder.clusterSessionId(),\n                buffer,\n                offset + AeronCluster.SESSION_HEADER_LENGTH,\n                length - AeronCluster.SESSION_HEADER_LENGTH);\n        }\n\n        switch (templateId)\n        {\n            case SessionConnectRequestDecoder.TEMPLATE_ID:\n            {\n                connectRequestDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                final String responseChannel = connectRequestDecoder.responseChannel();\n                final int credentialsLength = connectRequestDecoder.encodedCredentialsLength();\n                byte[] credentials = ArrayUtil.EMPTY_BYTE_ARRAY;\n                if (credentialsLength > 0)\n                {\n                    credentials = new byte[credentialsLength];\n                    connectRequestDecoder.getEncodedCredentials(credentials, 0, credentialsLength);\n                }\n                else\n                {\n                    connectRequestDecoder.skipEncodedCredentials();\n                }\n                final String clientInfo = connectRequestDecoder.clientInfo();\n\n                consensusModuleAgent.onSessionConnect(\n                    connectRequestDecoder.correlationId(),\n                    connectRequestDecoder.responseStreamId(),\n                    connectRequestDecoder.version(),\n                    responseChannel,\n                    credentials,\n                    clientInfo,\n                    header);\n                break;\n            }\n\n            case SessionCloseRequestDecoder.TEMPLATE_ID:\n            {\n                closeRequestDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                consensusModuleAgent.onSessionClose(\n                    closeRequestDecoder.leadershipTermId(),\n                    closeRequestDecoder.clusterSessionId());\n                break;\n            }\n\n            case SessionKeepAliveDecoder.TEMPLATE_ID:\n            {\n                sessionKeepAliveDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                consensusModuleAgent.onSessionKeepAlive(\n                    sessionKeepAliveDecoder.leadershipTermId(),\n                    sessionKeepAliveDecoder.clusterSessionId(),\n                    header);\n                break;\n            }\n\n            case ChallengeResponseDecoder.TEMPLATE_ID:\n            {\n                challengeResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                final byte[] credentials = new byte[challengeResponseDecoder.encodedCredentialsLength()];\n                challengeResponseDecoder.getEncodedCredentials(credentials, 0, credentials.length);\n\n                consensusModuleAgent.onIngressChallengeResponse(\n                    challengeResponseDecoder.correlationId(),\n                    challengeResponseDecoder.clusterSessionId(),\n                    credentials);\n                break;\n            }\n\n            case AdminRequestDecoder.TEMPLATE_ID:\n            {\n                adminRequestDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                final int payloadOffset = adminRequestDecoder.offset() +\n                    adminRequestDecoder.encodedLength() +\n                    AdminRequestDecoder.payloadHeaderLength();\n                consensusModuleAgent.onAdminRequest(\n                    adminRequestDecoder.leadershipTermId(),\n                    adminRequestDecoder.clusterSessionId(),\n                    adminRequestDecoder.correlationId(),\n                    adminRequestDecoder.requestType(),\n                    buffer,\n                    payloadOffset,\n                    adminRequestDecoder.payloadLength());\n                break;\n            }\n        }\n\n        return ControlledFragmentHandler.Action.CONTINUE;\n    }\n\n    void connect(final Subscription subscription, final Subscription ipcSubscription)\n    {\n        this.subscription = subscription;\n        this.ipcSubscription = ipcSubscription;\n    }\n\n    int poll()\n    {\n        int fragmentsRead = 0;\n\n        if (null != subscription)\n        {\n            fragmentsRead += subscription.controlledPoll(udpFragmentAssembler, fragmentPollLimit);\n        }\n\n        if (null != ipcSubscription)\n        {\n            fragmentsRead += ipcSubscription.controlledPoll(ipcFragmentAssembler, fragmentPollLimit);\n        }\n\n        return fragmentsRead;\n    }\n\n    void freeSessionBuffer(final int imageSessionId, final boolean isIpc)\n    {\n        if (isIpc)\n        {\n            ipcFragmentAssembler.freeSessionBuffer(imageSessionId);\n        }\n        else\n        {\n            udpFragmentAssembler.freeSessionBuffer(imageSessionId);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/LogAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.BufferBuilder;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\n\nfinal class LogAdapter implements ControlledFragmentHandler\n{\n    private final int fragmentLimit;\n    private long logPosition;\n    private Image image;\n    private final ConsensusModuleAgent consensusModuleAgent;\n    private final BufferBuilder builder = new BufferBuilder();\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SessionOpenEventDecoder sessionOpenEventDecoder = new SessionOpenEventDecoder();\n    private final SessionCloseEventDecoder sessionCloseEventDecoder = new SessionCloseEventDecoder();\n    private final SessionMessageHeaderDecoder sessionHeaderDecoder = new SessionMessageHeaderDecoder();\n    private final TimerEventDecoder timerEventDecoder = new TimerEventDecoder();\n    private final ClusterActionRequestDecoder clusterActionRequestDecoder = new ClusterActionRequestDecoder();\n    private final NewLeadershipTermEventDecoder newLeadershipTermEventDecoder = new NewLeadershipTermEventDecoder();\n\n    LogAdapter(final ConsensusModuleAgent consensusModuleAgent, final int fragmentLimit)\n    {\n        this.consensusModuleAgent = consensusModuleAgent;\n        this.fragmentLimit = fragmentLimit;\n    }\n\n    long disconnect(final ErrorHandler errorHandler)\n    {\n        long registrationId = Aeron.NULL_VALUE;\n\n        if (null != image)\n        {\n            logPosition = image.position();\n            CloseHelper.close(errorHandler, image.subscription());\n            registrationId = image.subscription().registrationId();\n            image = null;\n        }\n\n        return registrationId;\n    }\n\n    void disconnect(final ErrorHandler errorHandler, final long maxLogPosition)\n    {\n        consensusModuleAgent.awaitLocalSocketsClosed(disconnect(errorHandler));\n        logPosition = Math.min(logPosition, maxLogPosition);\n    }\n\n    Subscription subscription()\n    {\n        return null == image ? null : image.subscription();\n    }\n\n    ConsensusModuleAgent consensusModuleAgent()\n    {\n        return consensusModuleAgent;\n    }\n\n    long position()\n    {\n        if (null == image)\n        {\n            return logPosition;\n        }\n\n        return image.position();\n    }\n\n    int poll(final long boundPosition)\n    {\n        if (null == image)\n        {\n            return 0;\n        }\n\n        return image.boundedControlledPoll(this, boundPosition, fragmentLimit);\n    }\n\n    boolean isImageClosed()\n    {\n        return null == image || image.isClosed();\n    }\n\n    boolean isLogEndOfStream()\n    {\n        return null != image && image.isEndOfStream();\n    }\n\n    boolean isLogEndOfStreamAt(final long position)\n    {\n        return null != image && position == image.endOfStreamPosition();\n    }\n\n    Image image()\n    {\n        return image;\n    }\n\n    void image(final Image image)\n    {\n        if (null != this.image)\n        {\n            logPosition = this.image.position();\n        }\n\n        this.image = image;\n    }\n\n    void asyncRemoveDestination(final String destination)\n    {\n        if (null != image && !image.subscription().isClosed())\n        {\n            image.subscription().asyncRemoveDestination(destination);\n        }\n    }\n\n    public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        Action action = Action.CONTINUE;\n        final byte flags = header.flags();\n\n        if ((flags & UNFRAGMENTED) == UNFRAGMENTED)\n        {\n            action = onMessage(buffer, offset, length, header);\n        }\n        else if ((flags & BEGIN_FRAG_FLAG) == BEGIN_FRAG_FLAG)\n        {\n            builder.reset()\n                .captureHeader(header)\n                .append(buffer, offset, length)\n                .nextTermOffset(BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT));\n        }\n        else if (offset == builder.nextTermOffset())\n        {\n            final int limit = builder.limit();\n\n            builder.append(buffer, offset, length);\n\n            if ((flags & END_FRAG_FLAG) == END_FRAG_FLAG)\n            {\n                action = onMessage(builder.buffer(), 0, builder.limit(), builder.completeHeader(header));\n\n                if (Action.ABORT == action)\n                {\n                    builder.limit(limit);\n                }\n                else\n                {\n                    builder.reset();\n                }\n            }\n            else\n            {\n                builder.nextTermOffset(BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT));\n            }\n        }\n        else\n        {\n            builder.reset();\n        }\n\n        return action;\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private Action onMessage(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        final int templateId = messageHeaderDecoder.templateId();\n        final int actingVersion = messageHeaderDecoder.version();\n        final int actingBlockLength = messageHeaderDecoder.blockLength();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            return consensusModuleAgent.onReplayExtensionMessage(\n                actingBlockLength, templateId, schemaId, actingVersion, buffer, offset, length, header);\n        }\n\n        switch (templateId)\n        {\n            case SessionMessageHeaderDecoder.TEMPLATE_ID:\n                sessionHeaderDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                consensusModuleAgent.onReplaySessionMessage(\n                    sessionHeaderDecoder.clusterSessionId(),\n                    sessionHeaderDecoder.timestamp());\n\n                return Action.CONTINUE;\n\n            case TimerEventDecoder.TEMPLATE_ID:\n                timerEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                consensusModuleAgent.onReplayTimerEvent(\n                    timerEventDecoder.correlationId());\n                break;\n\n            case SessionOpenEventDecoder.TEMPLATE_ID:\n                sessionOpenEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                consensusModuleAgent.onReplaySessionOpen(\n                    header.position(),\n                    sessionOpenEventDecoder.correlationId(),\n                    sessionOpenEventDecoder.clusterSessionId(),\n                    sessionOpenEventDecoder.timestamp(),\n                    sessionOpenEventDecoder.responseStreamId(),\n                    sessionOpenEventDecoder.responseChannel());\n                break;\n\n            case SessionCloseEventDecoder.TEMPLATE_ID:\n                sessionCloseEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                consensusModuleAgent.onReplaySessionClose(\n                    sessionCloseEventDecoder.clusterSessionId(),\n                    sessionCloseEventDecoder.closeReason());\n                break;\n\n            case ClusterActionRequestDecoder.TEMPLATE_ID:\n                clusterActionRequestDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                final int flags = ClusterActionRequestDecoder.flagsNullValue() != clusterActionRequestDecoder.flags() ?\n                    clusterActionRequestDecoder.flags() : ConsensusModule.CLUSTER_ACTION_FLAGS_DEFAULT;\n\n                consensusModuleAgent.onReplayClusterAction(\n                    clusterActionRequestDecoder.leadershipTermId(),\n                    clusterActionRequestDecoder.logPosition(),\n                    clusterActionRequestDecoder.timestamp(),\n                    clusterActionRequestDecoder.action(),\n                    flags);\n                return Action.BREAK;\n\n            case NewLeadershipTermEventDecoder.TEMPLATE_ID:\n                newLeadershipTermEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    actingVersion);\n\n                consensusModuleAgent.onReplayNewLeadershipTermEvent(\n                    newLeadershipTermEventDecoder.leadershipTermId(),\n                    newLeadershipTermEventDecoder.logPosition(),\n                    newLeadershipTermEventDecoder.timestamp(),\n                    newLeadershipTermEventDecoder.termBaseLogPosition(),\n                    ClusterClock.map(newLeadershipTermEventDecoder.timeUnit()),\n                    newLeadershipTermEventDecoder.appVersion());\n                break;\n        }\n\n        return Action.CONTINUE;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/LogPublisher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Publication;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.ClusterAction;\nimport io.aeron.cluster.codecs.ClusterActionRequestEncoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.NewLeadershipTermEventEncoder;\nimport io.aeron.cluster.codecs.SessionCloseEventEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderEncoder;\nimport io.aeron.cluster.codecs.SessionOpenEventEncoder;\nimport io.aeron.cluster.codecs.TimerEventEncoder;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.cluster.client.AeronCluster.SESSION_HEADER_LENGTH;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static org.agrona.BitUtil.align;\n\nfinal class LogPublisher\n{\n    private static final int SEND_ATTEMPTS = 3;\n\n    private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n    private final SessionMessageHeaderEncoder sessionHeaderEncoder = new SessionMessageHeaderEncoder();\n    private final SessionOpenEventEncoder sessionOpenEventEncoder = new SessionOpenEventEncoder();\n    private final SessionCloseEventEncoder sessionCloseEventEncoder = new SessionCloseEventEncoder();\n    private final TimerEventEncoder timerEventEncoder = new TimerEventEncoder();\n    private final ClusterActionRequestEncoder clusterActionRequestEncoder = new ClusterActionRequestEncoder();\n    private final NewLeadershipTermEventEncoder newLeadershipTermEventEncoder = new NewLeadershipTermEventEncoder();\n    private final UnsafeBuffer sessionHeaderBuffer = new UnsafeBuffer(new byte[SESSION_HEADER_LENGTH]);\n    private final ExpandableArrayBuffer expandableArrayBuffer = new ExpandableArrayBuffer();\n    private final BufferClaim bufferClaim = new BufferClaim();\n\n    private final String destinationChannel;\n    private ExclusivePublication publication;\n\n    LogPublisher(final String destinationChannel)\n    {\n        this.destinationChannel = destinationChannel;\n        sessionHeaderEncoder.wrapAndApplyHeader(sessionHeaderBuffer, 0, new MessageHeaderEncoder());\n    }\n\n    void publication(final ExclusivePublication publication)\n    {\n        if (null != this.publication)\n        {\n            this.publication.close();\n        }\n        this.publication = publication;\n    }\n\n    ExclusivePublication publication()\n    {\n        return publication;\n    }\n\n    void disconnect(final ErrorHandler errorHandler)\n    {\n        if (null != publication)\n        {\n            CloseHelper.close(errorHandler, publication);\n            this.publication = null;\n        }\n    }\n\n    long position()\n    {\n        if (null == publication)\n        {\n            return 0;\n        }\n\n        return publication.position();\n    }\n\n    int sessionId()\n    {\n        return publication.sessionId();\n    }\n\n    void addDestination(final String followerLogEndpoint)\n    {\n        if (null != publication)\n        {\n            publication.asyncAddDestination(ChannelUri.createDestinationUri(destinationChannel, followerLogEndpoint));\n        }\n    }\n\n    long appendMessage(\n        final long leadershipTermId,\n        final long clusterSessionId,\n        final long timestamp,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        sessionHeaderEncoder\n            .leadershipTermId(leadershipTermId)\n            .clusterSessionId(clusterSessionId)\n            .timestamp(timestamp);\n\n        int attempts = SEND_ATTEMPTS;\n        long position;\n        do\n        {\n            position = publication.offer(sessionHeaderBuffer, 0, SESSION_HEADER_LENGTH, buffer, offset, length, null);\n\n            if (position > 0)\n            {\n                break;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return position;\n    }\n\n    long appendSessionOpen(final ClusterSession session, final long leadershipTermId, final long timestamp)\n    {\n        long position;\n        final byte[] encodedPrincipal = session.encodedPrincipal();\n        final String channel = session.responseChannel();\n\n        sessionOpenEventEncoder\n            .wrapAndApplyHeader(expandableArrayBuffer, 0, messageHeaderEncoder)\n            .leadershipTermId(leadershipTermId)\n            .clusterSessionId(session.id())\n            .correlationId(session.correlationId())\n            .timestamp(timestamp)\n            .responseStreamId(session.responseStreamId())\n            .responseChannel(channel)\n            .putEncodedPrincipal(encodedPrincipal, 0, encodedPrincipal.length);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + sessionOpenEventEncoder.encodedLength();\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            position = publication.offer(expandableArrayBuffer, 0, length, null);\n            if (position > 0)\n            {\n                break;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return position;\n    }\n\n    boolean appendSessionClose(\n        final int memberId,\n        final ClusterSession session,\n        final long leadershipTermId,\n        final long timestamp,\n        final TimeUnit timeUnit)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + SessionCloseEventEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                sessionCloseEventEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .clusterSessionId(session.id())\n                    .timestamp(timestamp)\n                    .closeReason(session.closeReason());\n\n                bufferClaim.commit();\n                return true;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    long appendTimer(final long correlationId, final long leadershipTermId, final long timestamp)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + TimerEventEncoder.BLOCK_LENGTH;\n\n        int attempts = SEND_ATTEMPTS;\n        long position;\n        do\n        {\n            position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                timerEventEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .correlationId(correlationId)\n                    .timestamp(timestamp);\n\n                bufferClaim.commit();\n                break;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return position;\n    }\n\n    boolean appendClusterAction(\n        final long leadershipTermId,\n        final long timestamp,\n        final ClusterAction action,\n        final int flags)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ClusterActionRequestEncoder.BLOCK_LENGTH;\n        final int fragmentLength = DataHeaderFlyweight.HEADER_LENGTH + length;\n        final int alignedFragmentLength = align(fragmentLength, FRAME_ALIGNMENT);\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long logPosition = publication.position() + alignedFragmentLength;\n            final long position = publication.tryClaim(length, bufferClaim);\n\n            if (position > 0)\n            {\n                clusterActionRequestEncoder.wrapAndApplyHeader(\n                    bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .logPosition(logPosition)\n                    .timestamp(timestamp)\n                    .action(action)\n                    .flags(flags);\n\n                bufferClaim.commit();\n                return true;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    boolean appendNewLeadershipTermEvent(\n        final long leadershipTermId,\n        final long timestamp,\n        final long termBaseLogPosition,\n        final int leaderMemberId,\n        final int logSessionId,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + NewLeadershipTermEventEncoder.BLOCK_LENGTH;\n        final int fragmentLength = DataHeaderFlyweight.HEADER_LENGTH + length;\n        final int alignedFragmentLength = align(fragmentLength, FRAME_ALIGNMENT);\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long logPosition = publication.position() + alignedFragmentLength;\n            final long position = publication.tryClaim(length, bufferClaim);\n\n            if (position > 0)\n            {\n                newLeadershipTermEventEncoder.wrapAndApplyHeader(\n                    bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .logPosition(logPosition)\n                    .timestamp(timestamp)\n                    .termBaseLogPosition(termBaseLogPosition)\n                    .leaderMemberId(leaderMemberId)\n                    .logSessionId(logSessionId)\n                    .timeUnit(ClusterClock.map(timeUnit))\n                    .appVersion(appVersion);\n\n                bufferClaim.commit();\n                return true;\n            }\n\n            checkResult(position, publication);\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    private static void checkResult(final long position, final Publication publication)\n    {\n        if (Publication.CLOSED == position)\n        {\n            throw new ClusterException(\"log publication is closed\");\n        }\n\n        if (Publication.MAX_POSITION_EXCEEDED == position)\n        {\n            throw new ClusterException(\n                \"log publication at max position: term-length=\" + publication.termBufferLength());\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"LogPublisher{\" +\n            \"destinationChannel='\" + destinationChannel + '\\'' +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/LogReplay.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.service.Cluster;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.CountedErrorHandler;\n\nfinal class LogReplay\n{\n    private final AeronArchive archive;\n    private final long startPosition;\n    private final long stopPosition;\n    private final long replaySessionId;\n    private final int logSessionId;\n    private final ConsensusModuleAgent consensusModuleAgent;\n    private final ConsensusModule.Context ctx;\n    private final LogAdapter logAdapter;\n    private final Subscription logSubscription;\n\n    LogReplay(\n        final AeronArchive archive,\n        final long recordingId,\n        final long startPosition,\n        final long stopPosition,\n        final LogAdapter logAdapter,\n        final ConsensusModule.Context ctx)\n    {\n        this.archive = archive;\n        this.startPosition = startPosition;\n        this.stopPosition = stopPosition;\n        this.logAdapter = logAdapter;\n        this.consensusModuleAgent = logAdapter.consensusModuleAgent();\n        this.ctx = ctx;\n\n        final String channel = ctx.replayChannel();\n        final int streamId = ctx.replayStreamId();\n        final long length = stopPosition - startPosition;\n        replaySessionId = archive.startReplay(recordingId, startPosition, length, channel, streamId);\n        logSessionId = (int)replaySessionId;\n        logSubscription = ctx.aeron().addSubscription(ChannelUri.addSessionId(channel, logSessionId), streamId);\n    }\n\n    void close()\n    {\n        final CountedErrorHandler errorHandler = ctx.countedErrorHandler();\n        try\n        {\n            archive.stopReplay(replaySessionId);\n        }\n        catch (final RuntimeException ex)\n        {\n            errorHandler.onError(ex);\n        }\n        logAdapter.disconnect(errorHandler);\n        CloseHelper.close(errorHandler, logSubscription);\n    }\n\n    int doWork()\n    {\n        int workCount = 0;\n\n        if (null == logAdapter.image())\n        {\n            final Image image = logSubscription.imageBySessionId(logSessionId);\n            if (null != image)\n            {\n                if (image.joinPosition() != startPosition)\n                {\n                    throw new ClusterException(\n                        \"joinPosition=\" + image.joinPosition() + \" expected startPosition=\" + startPosition,\n                        ClusterException.Category.WARN);\n                }\n\n                logAdapter.image(image);\n                consensusModuleAgent.awaitServicesReady(\n                    logSubscription.channel(),\n                    logSubscription.streamId(),\n                    logSessionId,\n                    startPosition,\n                    stopPosition,\n                    true,\n                    Cluster.Role.FOLLOWER);\n\n                workCount += 1;\n            }\n        }\n        else\n        {\n            workCount += consensusModuleAgent.replayLogPoll(logAdapter, stopPosition);\n        }\n\n        return workCount;\n    }\n\n    boolean isDone()\n    {\n        return logAdapter.position() >= stopPosition &&\n            consensusModuleAgent.state() != ConsensusModule.State.SNAPSHOT;\n    }\n\n    long position()\n    {\n        return logAdapter.position();\n    }\n\n    public String toString()\n    {\n        return \"LogReplay{\" +\n            \"startPosition=\" + startPosition +\n            \", stopPosition=\" + stopPosition +\n            \", replaySessionId=\" + replaySessionId +\n            \", logSessionId=\" + logSessionId +\n            \", logSubscription=\" + logSubscription +\n            \", position=\" + position() +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/LogSourceValidator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\nclass LogSourceValidator\n{\n    private final ClusterBackup.SourceType sourceType;\n\n    LogSourceValidator(final ClusterBackup.SourceType sourceType)\n    {\n        this.sourceType = sourceType;\n    }\n\n    boolean isAcceptable(final long leaderMemberId, final long memberId)\n    {\n        switch (sourceType)\n        {\n            case LEADER:\n                return NULL_VALUE != leaderMemberId && leaderMemberId == memberId;\n            case FOLLOWER:\n                return NULL_VALUE == leaderMemberId || leaderMemberId != memberId;\n            case ANY:\n                return true;\n        }\n\n        throw new IllegalStateException(\"Unknown sourceType=\" + sourceType);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/MillisecondClusterClock.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.service.ClusterClock;\n\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\n\n/**\n * A {@link ClusterClock} implemented by calling {@link System#currentTimeMillis()}.\n */\npublic class MillisecondClusterClock implements ClusterClock\n{\n    /**\n     * Default constructor.\n     */\n    public MillisecondClusterClock()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long time()\n    {\n        return System.currentTimeMillis();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long timeMillis()\n    {\n        return System.currentTimeMillis();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long timeMicros()\n    {\n        return MILLISECONDS.toMicros(System.currentTimeMillis());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long timeNanos()\n    {\n        return MILLISECONDS.toNanos(System.currentTimeMillis());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long convertToNanos(final long time)\n    {\n        return MILLISECONDS.toNanos(time);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/MultipleRecordingReplication.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ReplicationParams;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.Long2LongHashMap;\n\nimport java.util.ArrayList;\n\nfinal class MultipleRecordingReplication implements AutoCloseable\n{\n    private final AeronArchive archive;\n    private final int srcControlStreamId;\n    private final String srcControlChannel;\n    private final String replicationChannel;\n    private final String srcResponseChannel;\n    private final ArrayList<RecordingInfo> recordingsPending = new ArrayList<>();\n    private final Long2LongHashMap recordingsCompleted = new Long2LongHashMap(Aeron.NULL_VALUE);\n    private final long progressTimeoutNs;\n    private final long progressIntervalNs;\n    private int recordingCursor = 0;\n    private RecordingReplication recordingReplication = null;\n    private EventListener eventListener = null;\n\n    private MultipleRecordingReplication(\n        final AeronArchive archive,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String replicationChannel,\n        final String srcResponseChannel,\n        final long replicationProgressTimeoutNs,\n        final long replicationProgressIntervalNs)\n    {\n        this.archive = archive;\n        this.srcControlStreamId = srcControlStreamId;\n        this.srcControlChannel = srcControlChannel;\n        this.replicationChannel = replicationChannel;\n        this.srcResponseChannel = srcResponseChannel;\n        this.progressTimeoutNs = replicationProgressTimeoutNs;\n        this.progressIntervalNs = replicationProgressIntervalNs;\n    }\n\n    static MultipleRecordingReplication newInstance(\n        final AeronArchive archive,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String replicationChannel,\n        final long replicationProgressTimeoutNs,\n        final long replicationProgressIntervalNs)\n    {\n        return new MultipleRecordingReplication(\n            archive,\n            srcControlStreamId,\n            srcControlChannel,\n            replicationChannel,\n            null,\n            replicationProgressTimeoutNs,\n            replicationProgressIntervalNs);\n    }\n\n    static MultipleRecordingReplication newInstance(\n        final AeronArchive archive,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String replicationChannel,\n        final String srcResponseChannel,\n        final long replicationProgressTimeoutNs,\n        final long replicationProgressIntervalNs)\n    {\n        return new MultipleRecordingReplication(\n            archive,\n            srcControlStreamId,\n            srcControlChannel,\n            replicationChannel,\n            srcResponseChannel,\n            replicationProgressTimeoutNs,\n            replicationProgressIntervalNs);\n    }\n\n    void addRecording(final long srcRecordingId, final long dstRecordingId, final long stopPosition)\n    {\n        recordingsPending.add(new RecordingInfo(srcRecordingId, dstRecordingId, stopPosition));\n    }\n\n    int poll(final long nowNs)\n    {\n        if (isComplete())\n        {\n            return 0;\n        }\n\n        int workDone = 0;\n\n        if (null == recordingReplication)\n        {\n            replicateCurrentSnapshot(nowNs);\n            workDone++;\n        }\n        else\n        {\n            recordingReplication.poll(nowNs);\n            if (recordingReplication.hasReplicationEnded())\n            {\n                final RecordingInfo pending = recordingsPending.get(recordingCursor);\n\n                onReplicationEnded(\n                    srcControlChannel,\n                    pending.srcRecordingId,\n                    recordingReplication.recordingId(),\n                    recordingReplication.position(),\n                    recordingReplication.hasSynced());\n\n                if (recordingReplication.hasSynced())\n                {\n                    recordingsCompleted.put(pending.srcRecordingId, recordingReplication.recordingId());\n                    recordingCursor++;\n\n                    final RecordingReplication replication = recordingReplication;\n                    recordingReplication = null;\n                    CloseHelper.close(replication);\n                }\n                else\n                {\n                    final RecordingReplication replication = recordingReplication;\n                    recordingReplication = null;\n                    CloseHelper.close(replication);\n\n                    replicateCurrentSnapshot(nowNs);\n                }\n\n                workDone++;\n            }\n        }\n\n        return workDone;\n    }\n\n    long completedDstRecordingId(final long srcRecordingId)\n    {\n        return recordingsCompleted.get(srcRecordingId);\n    }\n\n    void onSignal(final long correlationId, final long recordingId, final long position, final RecordingSignal signal)\n    {\n        if (null != recordingReplication)\n        {\n            recordingReplication.onSignal(correlationId, recordingId, position, signal);\n        }\n    }\n\n    long currentReplicationId()\n    {\n        return null != recordingReplication ? recordingReplication.replicationId() : Aeron.NULL_VALUE;\n    }\n\n    long currentSrcRecordingId()\n    {\n        if (0 <= recordingCursor && recordingCursor < recordingsPending.size())\n        {\n            final RecordingInfo recordingInfo = recordingsPending.get(recordingCursor);\n            return recordingInfo.srcRecordingId;\n        }\n\n        return Aeron.NULL_VALUE;\n    }\n\n    boolean isComplete()\n    {\n        return recordingCursor >= recordingsPending.size();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(recordingReplication);\n        recordingReplication = null;\n    }\n\n    private void replicateCurrentSnapshot(final long nowNs)\n    {\n        final RecordingInfo recordingInfo = recordingsPending.get(recordingCursor);\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .dstRecordingId(recordingInfo.dstRecordingId)\n            .stopPosition(recordingInfo.stopPosition)\n            .replicationChannel(replicationChannel)\n            .srcResponseChannel(srcResponseChannel)\n            .replicationSessionId((int)archive.context().aeron().nextCorrelationId());\n\n        recordingReplication = new RecordingReplication(\n            archive,\n            recordingInfo.srcRecordingId,\n            srcControlChannel,\n            srcControlStreamId,\n            replicationParams,\n            progressTimeoutNs,\n            progressIntervalNs,\n            nowNs);\n    }\n\n    static final class RecordingInfo\n    {\n        private final long srcRecordingId;\n        private final long dstRecordingId;\n        private final long stopPosition;\n\n        private RecordingInfo(final long srcRecordingId, final long dstRecordingId, final long stopPosition)\n        {\n            this.srcRecordingId = srcRecordingId;\n            this.dstRecordingId = dstRecordingId;\n            this.stopPosition = stopPosition;\n        }\n    }\n\n    private void onReplicationEnded(\n        final String srcArchiveControlChannel,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long position,\n        final boolean hasSynced)\n    {\n        if (null != eventListener)\n        {\n            eventListener.onReplicationEnded(\n                srcArchiveControlChannel, srcRecordingId, dstRecordingId, position, hasSynced);\n        }\n    }\n\n    void setEventListener(final EventListener eventListener)\n    {\n        this.eventListener = eventListener;\n    }\n\n    interface EventListener\n    {\n        void onReplicationEnded(\n            String controlUri,\n            long srcRecordingId,\n            long dstRecordingId,\n            long position,\n            boolean hasSynced);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/NanosecondClusterClock.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.service.ClusterClock;\nimport org.agrona.concurrent.HighResolutionClock;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A {@link ClusterClock} implemented by calling {@link HighResolutionClock#epochNanos()}.\n */\npublic class NanosecondClusterClock implements ClusterClock\n{\n    /**\n     * Default constructor.\n     */\n    public NanosecondClusterClock()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public TimeUnit timeUnit()\n    {\n        return TimeUnit.NANOSECONDS;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long time()\n    {\n        return HighResolutionClock.epochNanos();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long timeMillis()\n    {\n        return System.currentTimeMillis();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long timeMicros()\n    {\n        return HighResolutionClock.epochMicros();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long timeNanos()\n    {\n        return HighResolutionClock.epochNanos();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long convertToNanos(final long time)\n    {\n        return time;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/NodeControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.CommonContext;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.service.ClusterCounters;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\n\n/**\n * Toggle control {@link ToggleState}s for a cluster node such as {@link ToggleState#REPLICATE_STANDBY_SNAPSHOT}.\n * This can only be applied to individual nodes and does not apply across the cluster.\n */\npublic final class NodeControl\n{\n    /**\n     * Toggle states for controlling the cluster node once it has entered the active state after initialising.\n     * The toggle can only we switched into a new state from {@link #NEUTRAL} and will be reset by the\n     * {@link ConsensusModule} once the triggered action is complete.\n     */\n    public enum ToggleState\n    {\n        /**\n         * Inactive state, not accepting new actions.\n         */\n        INACTIVE(0),\n\n        /**\n         * Neutral state ready to accept a new action.\n         */\n        NEUTRAL(1),\n\n        /**\n         * Trigger a replication of the most resent standby snapshots.\n         */\n        REPLICATE_STANDBY_SNAPSHOT(2);\n\n        private final int code;\n\n        private static final ToggleState[] STATES = values();\n\n        ToggleState(final int code)\n        {\n            if (code != ordinal())\n            {\n                throw new IllegalArgumentException(name() + \" - code must equal ordinal value: code=\" + code);\n            }\n\n            this.code = code;\n        }\n\n        /**\n         * Code to be used as the indicator in the control toggle counter.\n         *\n         * @return code to be used as the indicator in the control toggle counter.\n         */\n        public final int code()\n        {\n            return code;\n        }\n\n        /**\n         * Toggle the control counter to trigger the requested {@link ToggleState}.\n         *\n         * @param controlToggle to change to the trigger state.\n         * @return true if the counter toggles or false if it is in a state other than {@link ToggleState#NEUTRAL}.\n         */\n        public final boolean toggle(final AtomicCounter controlToggle)\n        {\n            return controlToggle.compareAndSet(NEUTRAL.code(), code());\n        }\n\n        /**\n         * Reset the toggle to the {@link #NEUTRAL} state.\n         *\n         * @param controlToggle to be reset.\n         */\n        public static void reset(final AtomicCounter controlToggle)\n        {\n            controlToggle.set(NEUTRAL.code());\n        }\n\n        /**\n         * Activate the toggle by setting it to the {@link #NEUTRAL} state.\n         *\n         * @param controlToggle to be activated.\n         */\n        public static void activate(final AtomicCounter controlToggle)\n        {\n            controlToggle.set(NEUTRAL.code());\n        }\n\n        /**\n         * Activate the toggle by setting it to the {@link #INACTIVE} state.\n         *\n         * @param controlToggle to be deactivated.\n         */\n        public static void deactivate(final AtomicCounter controlToggle)\n        {\n            controlToggle.set(INACTIVE.code());\n        }\n\n        /**\n         * Get the {@link ToggleState} for a given control toggle.\n         *\n         * @param controlToggle to get the current state for.\n         * @return the state for the current control toggle.\n         * @throws ClusterException if the counter is not one of the valid values.\n         */\n        public static ToggleState get(final AtomicCounter controlToggle)\n        {\n            if (controlToggle.isClosed())\n            {\n                throw new ClusterException(\"counter is closed\");\n            }\n\n            final long toggleValue = controlToggle.get();\n            if (toggleValue < 0 || toggleValue > (STATES.length - 1))\n            {\n                throw new ClusterException(\"invalid toggle value: \" + toggleValue);\n            }\n\n            return STATES[(int)toggleValue];\n        }\n    }\n\n    /**\n     * Counter type id for the control toggle.\n     */\n    public static final int CONTROL_TOGGLE_TYPE_ID = AeronCounters.NODE_CONTROL_TOGGLE_TYPE_ID;\n\n    private NodeControl()\n    {\n    }\n\n    /**\n     * Find the control toggle counter or return null if not found.\n     *\n     * @param counters  to search within.\n     * @param clusterId to which the allocated counter belongs.\n     * @return the control toggle counter or return null if not found.\n     */\n    public static AtomicCounter findControlToggle(final CountersReader counters, final int clusterId)\n    {\n        final int counterId = ClusterCounters.find(counters, CONTROL_TOGGLE_TYPE_ID, clusterId);\n        if (Aeron.NULL_VALUE != counterId)\n        {\n            return new AtomicCounter(counters.valuesBuffer(), counterId, null);\n        }\n\n        return null;\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        checkUsage(args);\n\n        final ToggleState toggleState = ToggleState.valueOf(args[0].toUpperCase());\n\n        final File cncFile = CommonContext.newDefaultCncFile();\n        System.out.println(\"Command `n Control file \" + cncFile);\n\n        final CountersReader countersReader = ClusterControl.mapCounters(cncFile);\n        final int clusterId = ClusteredServiceContainer.Configuration.clusterId();\n        final AtomicCounter controlToggle = findControlToggle(countersReader, clusterId);\n\n        if (null == controlToggle)\n        {\n            System.out.println(\"Failed to find control toggle\");\n            System.exit(0);\n        }\n\n        if (toggleState.toggle(controlToggle))\n        {\n            System.out.println(toggleState + \" toggled successfully\");\n        }\n        else\n        {\n            System.out.println(toggleState + \" did NOT toggle: current state=\" + ToggleState.get(controlToggle));\n        }\n    }\n\n    private static void checkUsage(final String[] args)\n    {\n        if (1 != args.length)\n        {\n            System.out.format(\"Usage: [-Daeron.dir=<directory containing CnC file> -Daeron.cluster.id=<id>] \" +\n                NodeControl.class.getName() + \" <action>%n\");\n\n            System.exit(0);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/NodeStateFile.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.node.*;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport org.agrona.*;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.MappedByteBuffer;\n\nimport static org.agrona.BitUtil.align;\nimport static org.agrona.concurrent.UnsafeBuffer.ALIGNMENT;\n\n/**\n * An extensible list of information relating to a specific cluster node. Used to track persistent state that is node\n * specific and shouldn't be present in the snapshot. E.g. candidateTermId.\n * <p>\n *     The structure consists of a node header at the beginning of the file followed by n entries that use the\n *     standard open framing header, followed by the message header, and finally the body.\n * </p>\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                     Node State Header                         |\n *  +---------------------------------------------------------------+\n *  </pre>\n *  <p>\n *      Entry. All records must be laid out so that the body has an 8-byte alignment. Fields that require volatile\n *      accesses must be aligned to an 8-byte boundary. The message header is 8-bytes long to aid with this.\n *      Records are padded to a 8-byte boundary before the next record.\n *  </p>\n *  <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +---------------------------------------------------------------+\n *  |                       Message Header                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                       Message Body (Variable)               ...\n *  ...                                                             |\n *  +---------------------------------------------------------------+\n * </pre>\n * <p>\n *     The current structure contains:\n * <pre>\n *     &lt;Node State Header&gt;\n *     &lt;Candidate Term&gt;\n *     &lt;Cluster Members&gt;\n *     &lt;Node State Footer&gt;\n * </pre>\n */\npublic class NodeStateFile implements AutoCloseable\n{\n    /**\n     * File name.\n     */\n    public static final String FILENAME = \"node-state.dat\";\n    private static final int MINIMUM_FILE_LENGTH = 1 << 20;\n    private final CandidateTerm candidateTerm = new CandidateTerm();\n    private final MappedByteBuffer mappedFile;\n    private final int fileSyncLevel;\n    private final NodeStateHeaderDecoder nodeStateHeaderDecoder = new NodeStateHeaderDecoder();\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final CandidateTermDecoder candidateTermDecoder = new CandidateTermDecoder();\n    private final UnsafeBuffer buffer;\n    private int candidateTermIdOffset;\n\n    /**\n     * Construct the NodeStateFile.\n     *\n     * @param clusterDir    directory containing the NodeStateFile.\n     * @param createNew     whether a new file should be created if one does not already exist.\n     * @param fileSyncLevel whether the mapped byte buffer should be synchronised with the underlying filesystem on\n     *                      change.\n     * @throws IOException if there is an error creating the file or <code>createNew == false</code> and the file does\n     * not already exist.\n     */\n    public NodeStateFile(final File clusterDir, final boolean createNew, final int fileSyncLevel) throws IOException\n    {\n        this.fileSyncLevel = fileSyncLevel;\n        final UnsafeBuffer buffer;\n        MappedByteBuffer mappedFile = null;\n\n        try\n        {\n            final File nodeStateFile = new File(clusterDir, NodeStateFile.FILENAME);\n            if (!nodeStateFile.exists())\n            {\n                if (!createNew)\n                {\n                    throw new IOException(\"NodeStateFile does not exist and createNew=false\");\n                }\n\n                mappedFile = IoUtil.mapNewFile(nodeStateFile, MINIMUM_FILE_LENGTH);\n                buffer = new UnsafeBuffer(mappedFile, 0, mappedFile.capacity());\n                buffer.verifyAlignment();\n\n                initialiseDecodersOnCreation(\n                    buffer,\n                    nodeStateHeaderDecoder,\n                    messageHeaderDecoder,\n                    candidateTermDecoder);\n\n                candidateTermIdOffset = calculateAndVerifyCandidateTermIdOffset();\n                buffer.putLongVolatile(candidateTermIdOffset, Aeron.NULL_VALUE);\n            }\n            else\n            {\n                mappedFile = IoUtil.mapExistingFile(nodeStateFile, \"NodeState\");\n                buffer = new UnsafeBuffer(mappedFile, 0, mappedFile.capacity());\n\n                loadDecodersAndOffsets(buffer);\n            }\n\n            syncFile(mappedFile);\n        }\n        catch (final IOException | RuntimeException ex)\n        {\n            if (null != mappedFile)\n            {\n                IoUtil.unmap(mappedFile);\n            }\n            throw ex;\n        }\n\n        this.mappedFile = mappedFile;\n        this.buffer = buffer;\n    }\n\n    private int calculateAndVerifyCandidateTermIdOffset()\n    {\n        final int candidateTermIdOffset;\n        candidateTermIdOffset = candidateTermDecoder.offset() + CandidateTermDecoder.candidateTermIdEncodingOffset();\n        verifyAlignment(candidateTermIdOffset);\n        return candidateTermIdOffset;\n    }\n\n    private static void verifyAlignment(final int offset)\n    {\n        if (0 != (offset & (ALIGNMENT - 1)))\n        {\n            throw new IllegalStateException(\n                \"offset=\" + offset + \" is not correctly aligned, it is not divisible by \" + ALIGNMENT);\n        }\n    }\n\n    private static void loadInitialState(\n        final MutableDirectBuffer buffer,\n        final NodeStateHeaderDecoder nodeStateHeaderDecoder,\n        final CandidateTermDecoder candidateTermDecoder,\n        final MessageHeaderDecoder messageHeaderDecoder)\n    {\n        nodeStateHeaderDecoder.wrap(\n            buffer, 0, NodeStateHeaderDecoder.BLOCK_LENGTH, NodeStateHeaderDecoder.SCHEMA_VERSION);\n\n        final int version = nodeStateHeaderDecoder.version();\n        if (ClusterMarkFile.MAJOR_VERSION != SemanticVersion.major(version))\n        {\n            throw new ClusterException(\n                \"mark file major version \" + SemanticVersion.major(version) +\n                \" does not match software: \" + ClusterMarkFile.MAJOR_VERSION);\n        }\n\n        final int footerOffset = scanForMessageTypeOffset(\n            nodeStateHeaderDecoder.sbeBlockLength(),\n            NodeStateFooterDecoder.TEMPLATE_ID,\n            buffer,\n            messageHeaderDecoder);\n\n        if (Aeron.NULL_VALUE == footerOffset)\n        {\n            throw new IllegalStateException(\"failed to find NodeStateFooter entry, file corrupt?\");\n        }\n\n        final int candidateTermOffset = scanForMessageTypeOffset(\n            nodeStateHeaderDecoder.sbeBlockLength(),\n            CandidateTermDecoder.TEMPLATE_ID,\n            buffer,\n            messageHeaderDecoder);\n\n        if (Aeron.NULL_VALUE == candidateTermOffset)\n        {\n            throw new IllegalStateException(\"failed to find CandidateTerm entry\");\n        }\n\n        candidateTermDecoder.wrapAndApplyHeader(buffer, candidateTermOffset, messageHeaderDecoder);\n    }\n\n    private static void initialiseDecodersOnCreation(\n        final MutableDirectBuffer buffer,\n        final NodeStateHeaderDecoder nodeStateHeaderDecoder,\n        final MessageHeaderDecoder messageHeaderDecoder,\n        final CandidateTermDecoder candidateTermDecoder)\n    {\n        final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n\n        nodeStateHeaderDecoder.wrap(\n            buffer, 0, NodeStateHeaderDecoder.BLOCK_LENGTH, NodeStateHeaderDecoder.SCHEMA_VERSION);\n        new NodeStateHeaderEncoder()\n            .wrap(buffer, 0)\n            .version(ClusterMarkFile.SEMANTIC_VERSION);\n\n        final int candidateTermFrameOffset = NodeStateHeaderDecoder.BLOCK_LENGTH;\n        verifyAlignment(candidateTermFrameOffset);\n\n        // Set candidateTermId\n        final CandidateTermEncoder candidateTermEncoder = new CandidateTermEncoder();\n        candidateTermEncoder.wrapAndApplyHeader(buffer, candidateTermFrameOffset, messageHeaderEncoder);\n        candidateTermDecoder.wrapAndApplyHeader(buffer, candidateTermFrameOffset, messageHeaderDecoder);\n        candidateTermEncoder\n            .logPosition(Aeron.NULL_VALUE)\n            .timestamp(Aeron.NULL_VALUE)\n            .candidateTermId(Aeron.NULL_VALUE);\n        messageHeaderEncoder.frameLength(MessageHeaderEncoder.ENCODED_LENGTH + candidateTermEncoder.encodedLength());\n\n        final int footerOffset = candidateTermFrameOffset + align(messageHeaderDecoder.frameLength(), ALIGNMENT);\n        final NodeStateFooterEncoder nodeStateFooterEncoder = new NodeStateFooterEncoder();\n        nodeStateFooterEncoder.wrapAndApplyHeader(buffer, footerOffset, messageHeaderEncoder);\n        messageHeaderEncoder\n            .frameLength(MessageHeaderEncoder.ENCODED_LENGTH + nodeStateFooterEncoder.encodedLength());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        IoUtil.unmap(mappedFile);\n    }\n\n    /**\n     * Set the current candidate term id with associated information.\n     *\n     * @param candidateTermId current candidate term id.\n     * @param logPosition    log position where the term id change occurred.\n     * @param timestampMs    timestamp of the candidate term id change.\n     */\n    public void updateCandidateTermId(final long candidateTermId, final long logPosition, final long timestampMs)\n    {\n        buffer.putLong(candidateTermDecoder.offset() + CandidateTermDecoder.logPositionEncodingOffset(), logPosition);\n        buffer.putLong(candidateTermDecoder.offset() + CandidateTermDecoder.timestampEncodingOffset(), timestampMs);\n        buffer.putLongVolatile(candidateTermIdOffset, candidateTermId);\n        syncFile(mappedFile);\n    }\n\n    /**\n     * Set the current candidate term id with associated information.\n     *\n     * @param candidateTermId current candidate term id.\n     * @param logPosition     log position where the term id change occurred.\n     * @param timestampMs     timestamp of the candidate term id change.\n     * @return the new candidate term id.\n     */\n    public long proposeMaxCandidateTermId(final long candidateTermId, final long logPosition, final long timestampMs)\n    {\n        final long existingCandidateTermId = candidateTerm.candidateTermId();\n\n        if (candidateTermId > existingCandidateTermId)\n        {\n            updateCandidateTermId(candidateTermId, logPosition, timestampMs);\n            return candidateTermId;\n        }\n\n        return existingCandidateTermId;\n    }\n\n    /**\n     * Get the reference to CandidateTerm wrapper that can be used to fetch the values associated with the current\n     * candidate term.\n     *\n     * @return the CandidateTerm wrapper.\n     */\n    public CandidateTerm candidateTerm()\n    {\n        return candidateTerm;\n    }\n\n    private void loadDecodersAndOffsets(final UnsafeBuffer buffer)\n    {\n        loadInitialState(\n            buffer,\n            nodeStateHeaderDecoder,\n            candidateTermDecoder,\n            messageHeaderDecoder);\n\n        candidateTermIdOffset = calculateAndVerifyCandidateTermIdOffset();\n    }\n\n    private static int scanForMessageTypeOffset(\n        final int startPosition,\n        final int templateId,\n        final DirectBuffer buffer,\n        final MessageHeaderDecoder messageHeaderDecoder)\n    {\n        verifyAlignment(startPosition);\n        int position = startPosition;\n\n        while (position < buffer.capacity())\n        {\n            messageHeaderDecoder.wrap(buffer, position);\n\n            final int messageLength = messageHeaderDecoder.frameLength();\n\n            if (templateId == messageHeaderDecoder.templateId())\n            {\n                return position;\n            }\n            else if (NodeStateFooterEncoder.TEMPLATE_ID == messageHeaderDecoder.templateId())\n            {\n                return Aeron.NULL_VALUE;\n            }\n\n            if (messageLength < 0)\n            {\n                throw new IllegalStateException(\"Message length < 0, file corrupt?\");\n            }\n            else if (0 == messageLength)\n            {\n                return Aeron.NULL_VALUE;\n            }\n\n            position += align(messageLength, ALIGNMENT);\n        }\n\n        return Aeron.NULL_VALUE;\n    }\n\n    /**\n     * Wrapper class for the candidate term.\n     */\n    public final class CandidateTerm\n    {\n        private CandidateTerm()\n        {\n        }\n\n        /**\n         * Gets the current candidateTermId.\n         *\n         * @return the candidateTermId.\n         */\n        public long candidateTermId()\n        {\n            return buffer.getLongVolatile(candidateTermIdOffset);\n        }\n\n        /**\n         * Get the timestamp of the latest candidateTermId update.\n         *\n         * @return epoch timestamp in ms.\n         */\n        public long timestamp()\n        {\n            return candidateTermDecoder.timestamp();\n        }\n\n        /**\n         * Get the log position of the latest candidateTermId update.\n         *\n         * @return log position.\n         */\n        public long logPosition()\n        {\n            return candidateTermDecoder.logPosition();\n        }\n    }\n\n    private void syncFile(final MappedByteBuffer mappedFile)\n    {\n        if (0 < fileSyncLevel)\n        {\n            mappedFile.force();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/PendingServiceMessageTracker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Counter;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderDecoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderEncoder;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableRingBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.MutableInteger;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.cluster.client.AeronCluster.SESSION_HEADER_LENGTH;\n\nfinal class PendingServiceMessageTracker\n{\n    private static final int SERVICE_MESSAGE_LIMIT = 20;\n\n    private final int serviceId;\n    private int pendingMessageHeadOffset = 0;\n    private int uncommittedMessages = 0;\n    private long nextServiceSessionId;\n    private long logServiceSessionId;\n    private long leadershipTermId = NULL_VALUE;\n\n    private final Counter commitPosition;\n    private final LogPublisher logPublisher;\n    private final ClusterClock clusterClock;\n    private final ExpandableRingBuffer pendingMessages = new ExpandableRingBuffer();\n    private final ExpandableRingBuffer.MessageConsumer messageAppender = this::messageAppender;\n    private final ExpandableRingBuffer.MessageConsumer leaderMessageSweeper = this::leaderMessageSweeper;\n    private final ExpandableRingBuffer.MessageConsumer followerMessageSweeper = this::followerMessageSweeper;\n\n    PendingServiceMessageTracker(\n        final int serviceId,\n        final Counter commitPosition,\n        final LogPublisher logPublisher,\n        final ClusterClock clusterClock)\n    {\n        this.serviceId = serviceId;\n        this.commitPosition = commitPosition;\n        this.logPublisher = logPublisher;\n        this.clusterClock = clusterClock;\n\n        logServiceSessionId = serviceSessionId(serviceId, Long.MIN_VALUE);\n        nextServiceSessionId = logServiceSessionId + 1;\n    }\n\n    void leadershipTermId(final long leadershipTermId)\n    {\n        this.leadershipTermId = leadershipTermId;\n    }\n\n    int serviceId()\n    {\n        return serviceId;\n    }\n\n    long nextServiceSessionId()\n    {\n        return nextServiceSessionId;\n    }\n\n    long logServiceSessionId()\n    {\n        return logServiceSessionId;\n    }\n\n    void enqueueMessage(final MutableDirectBuffer buffer, final int offset, final int length)\n    {\n        final long clusterSessionId = nextServiceSessionId++;\n        if (clusterSessionId > logServiceSessionId)\n        {\n            final int headerOffset = offset - SessionMessageHeaderDecoder.BLOCK_LENGTH;\n            final int clusterSessionIdOffset =\n                headerOffset + SessionMessageHeaderDecoder.clusterSessionIdEncodingOffset();\n            final int timestampOffset = headerOffset + SessionMessageHeaderDecoder.timestampEncodingOffset();\n\n            buffer.putLong(clusterSessionIdOffset, clusterSessionId, SessionMessageHeaderDecoder.BYTE_ORDER);\n            buffer.putLong(timestampOffset, Long.MAX_VALUE, SessionMessageHeaderDecoder.BYTE_ORDER);\n            if (!pendingMessages.append(buffer, offset - SESSION_HEADER_LENGTH, length + SESSION_HEADER_LENGTH))\n            {\n                throw new ClusterException(\"pending service message buffer at capacity=\" + pendingMessages.size() +\n                    \" for serviceId=\" + serviceId);\n            }\n        }\n    }\n\n    void sweepFollowerMessages(final long clusterSessionId)\n    {\n        logServiceSessionId = clusterSessionId;\n        pendingMessages.consume(followerMessageSweeper, Integer.MAX_VALUE);\n    }\n\n    void sweepLeaderMessages()\n    {\n        if (uncommittedMessages > 0)\n        {\n            pendingMessageHeadOffset -= pendingMessages.consume(leaderMessageSweeper, Integer.MAX_VALUE);\n            pendingMessageHeadOffset = Math.max(pendingMessageHeadOffset, 0);\n        }\n    }\n\n    void restoreUncommittedMessages()\n    {\n        if (uncommittedMessages > 0)\n        {\n            pendingMessages.consume(leaderMessageSweeper, Integer.MAX_VALUE);\n            pendingMessages.forEach(PendingServiceMessageTracker::messageReset, Integer.MAX_VALUE);\n            uncommittedMessages = 0;\n            pendingMessageHeadOffset = 0;\n        }\n    }\n\n    void appendMessage(final DirectBuffer buffer, final int offset, final int length)\n    {\n        pendingMessages.append(buffer, offset, length);\n    }\n\n    void loadState(final long nextServiceSessionId, final long logServiceSessionId, final int pendingMessageCapacity)\n    {\n        this.nextServiceSessionId = nextServiceSessionId;\n        this.logServiceSessionId = logServiceSessionId;\n        pendingMessages.reset(pendingMessageCapacity);\n    }\n\n    int poll()\n    {\n        return pendingMessages.forEach(pendingMessageHeadOffset, messageAppender, SERVICE_MESSAGE_LIMIT);\n    }\n\n    int size()\n    {\n        return pendingMessages.size();\n    }\n\n    void verify()\n    {\n        final MutableInteger messageCount = new MutableInteger();\n        final ExpandableRingBuffer.MessageConsumer messageConsumer =\n            (buffer, offset, length, headOffset) ->\n            {\n                messageCount.increment();\n\n                final int headerOffset = offset + MessageHeaderDecoder.ENCODED_LENGTH;\n                final int clusterSessionIdOffset =\n                    headerOffset + SessionMessageHeaderDecoder.clusterSessionIdEncodingOffset();\n\n                final long clusterSessionId = buffer.getLong(\n                    clusterSessionIdOffset, SessionMessageHeaderDecoder.BYTE_ORDER);\n\n                if (clusterSessionId != (logServiceSessionId + messageCount.get()))\n                {\n                    throw new ClusterException(\"snapshot has incorrect pending message:\" +\n                        \" serviceId=\" + serviceId +\n                        \" nextServiceSessionId=\" + nextServiceSessionId +\n                        \" logServiceSessionId=\" + logServiceSessionId +\n                        \" clusterSessionId=\" + clusterSessionId +\n                        \" pendingMessageIndex=\" + messageCount.get(),\n                        AeronException.Category.FATAL);\n                }\n\n                return true;\n            };\n\n        pendingMessages.forEach(messageConsumer, Integer.MAX_VALUE);\n\n        if (nextServiceSessionId != (logServiceSessionId + messageCount.get() + 1))\n        {\n            throw new ClusterException(\"snapshot has incorrect pending message state:\" +\n                \" serviceId=\" + serviceId +\n                \" nextServiceSessionId=\" + nextServiceSessionId +\n                \" logServiceSessionId=\" + logServiceSessionId +\n                \" pendingMessageCount=\" + messageCount.get(),\n                AeronException.Category.FATAL);\n        }\n    }\n\n    void reset()\n    {\n        pendingMessages.forEach(PendingServiceMessageTracker::messageReset, Integer.MAX_VALUE);\n    }\n\n    ExpandableRingBuffer pendingMessages()\n    {\n        return pendingMessages;\n    }\n\n    private boolean messageAppender(\n        final MutableDirectBuffer buffer, final int offset, final int length, final int headOffset)\n    {\n        final int headerOffset = offset + MessageHeaderDecoder.ENCODED_LENGTH;\n        final int clusterSessionIdOffset = headerOffset + SessionMessageHeaderDecoder.clusterSessionIdEncodingOffset();\n        final int timestampOffset = headerOffset + SessionMessageHeaderDecoder.timestampEncodingOffset();\n        final long clusterSessionId = buffer.getLong(clusterSessionIdOffset, SessionMessageHeaderDecoder.BYTE_ORDER);\n\n        final long appendPosition = logPublisher.appendMessage(\n            leadershipTermId,\n            clusterSessionId,\n            clusterClock.time(),\n            buffer,\n            offset + SESSION_HEADER_LENGTH,\n            length - SESSION_HEADER_LENGTH);\n\n        if (appendPosition > 0)\n        {\n            ++uncommittedMessages;\n            pendingMessageHeadOffset = headOffset;\n            buffer.putLong(timestampOffset, appendPosition, SessionMessageHeaderEncoder.BYTE_ORDER);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    private static boolean messageReset(\n        final MutableDirectBuffer buffer, final int offset, final int length, final int headOffset)\n    {\n        final int timestampOffset = offset +\n            MessageHeaderDecoder.ENCODED_LENGTH + SessionMessageHeaderDecoder.timestampEncodingOffset();\n        final long appendPosition = buffer.getLong(timestampOffset, SessionMessageHeaderDecoder.BYTE_ORDER);\n\n        if (appendPosition < Long.MAX_VALUE)\n        {\n            buffer.putLong(timestampOffset, Long.MAX_VALUE, SessionMessageHeaderEncoder.BYTE_ORDER);\n            return true;\n        }\n\n        return false;\n    }\n\n    private boolean leaderMessageSweeper(\n        final MutableDirectBuffer buffer, final int offset, final int length, final int headOffset)\n    {\n        final int headerOffset = offset + MessageHeaderDecoder.ENCODED_LENGTH;\n        final int clusterSessionIdOffset = headerOffset + SessionMessageHeaderDecoder.clusterSessionIdEncodingOffset();\n        final int timestampOffset = headerOffset + SessionMessageHeaderDecoder.timestampEncodingOffset();\n        final long appendPosition = buffer.getLong(timestampOffset, SessionMessageHeaderDecoder.BYTE_ORDER);\n\n        if (commitPosition.getPlain() >= appendPosition)\n        {\n            logServiceSessionId = buffer.getLong(clusterSessionIdOffset, SessionMessageHeaderDecoder.BYTE_ORDER);\n            --uncommittedMessages;\n\n            return true;\n        }\n\n        return false;\n    }\n\n    private boolean followerMessageSweeper(\n        final MutableDirectBuffer buffer, final int offset, final int length, final int headOffset)\n    {\n        final int clusterSessionIdOffset = offset +\n            MessageHeaderDecoder.ENCODED_LENGTH + SessionMessageHeaderDecoder.clusterSessionIdEncodingOffset();\n\n        return buffer.getLong(clusterSessionIdOffset, SessionMessageHeaderDecoder.BYTE_ORDER) <= logServiceSessionId;\n    }\n\n    static int serviceIdFromLogMessage(final long clusterSessionId)\n    {\n        return ((int)(clusterSessionId >>> 56)) & 0x7F;\n    }\n\n    /**\n     * Services use different approach for communicating the serviceId, this method extracts the serviceId from a\n     * cluster session id sent via an inter-service message.\n     *\n     * @param clusterSessionId passed in on an inter-service message.\n     * @return the associated serviceId.\n     */\n    static int serviceIdFromServiceMessage(final long clusterSessionId)\n    {\n        return (int)clusterSessionId;\n    }\n\n    static long serviceSessionId(final int serviceId, final long sessionId)\n    {\n        return ((long)serviceId << 56) | sessionId;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/PriorityHeapTimerService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.collections.Long2ObjectHashMap;\n\nimport java.util.Arrays;\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * Implementation of the {@link TimerService} that uses a priority heap to order the timestamps.\n *\n * <p>\n * <b>Caveats</b>\n * <p>\n * Timers with the same deadline are not be ordered with one another. In contrast, the timers with different deadlines\n * are guaranteed to expire in order even after Cluster restart, i.e. when the deadlines are in the past.\n * <p>\n * <b>Note:</b> Not thread safe.\n */\nfinal class PriorityHeapTimerService implements TimerService\n{\n    private static final Timer[] EMPTY_TIMERS = new Timer[0];\n    private static final int MIN_CAPACITY = 8;\n\n    private final TimerHandler timerHandler;\n    private final Long2ObjectHashMap<Timer> timerByCorrelationId = new Long2ObjectHashMap<>();\n    private Timer[] timers = EMPTY_TIMERS;\n    private Timer[] freeTimers = EMPTY_TIMERS;\n    private int size;\n    private int freeTimerCount;\n\n    /**\n     * Construct a Priority Heap Timer Service using the supplied handler to\n     * callback for expired timers.\n     *\n     * @param timerHandler to callback when a timer expires.\n     */\n    PriorityHeapTimerService(final TimerHandler timerHandler)\n    {\n        this.timerHandler = Objects.requireNonNull(timerHandler, \"TimerHandler\");\n    }\n\n    /**\n     * Poll for expired timers, firing the callback supplied in the constructor.\n     *\n     * @param now current time.\n     * @return the number of expired timers\n     */\n    public int poll(final long now)\n    {\n        int expiredTimers = 0;\n        final Timer[] timers = this.timers;\n        final TimerHandler timerHandler = this.timerHandler;\n\n        while (size > 0 && expiredTimers < POLL_LIMIT)\n        {\n            final Timer timer = timers[0];\n            if (timer.deadline > now)\n            {\n                break;\n            }\n\n            if (!timerHandler.onTimerEvent(timer.correlationId))\n            {\n                break;\n            }\n\n            expiredTimers++;\n            final int lastIndex = --size;\n            final Timer lastTimer = timers[lastIndex];\n            timers[lastIndex] = null;\n\n            if (0 != lastIndex)\n            {\n                shiftDown(timers, lastIndex, 0, lastTimer);\n            }\n\n            timerByCorrelationId.remove(timer.correlationId);\n            addToFreeList(timer);\n        }\n\n        return expiredTimers;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void scheduleTimerForCorrelationId(final long correlationId, final long deadline)\n    {\n        final Timer existingTimer = timerByCorrelationId.get(correlationId);\n        if (null != existingTimer)\n        {\n            if (deadline < existingTimer.deadline)\n            {\n                existingTimer.deadline = deadline;\n                shiftUp(timers, existingTimer.index, existingTimer);\n            }\n            else if (deadline > existingTimer.deadline)\n            {\n                existingTimer.deadline = deadline;\n                shiftDown(timers, size, existingTimer.index, existingTimer);\n            }\n        }\n        else\n        {\n            ensureCapacity(size + 1);\n\n            final int index = size++;\n            final Timer timer;\n            if (freeTimerCount > 0)\n            {\n                final int freeIndex = --freeTimerCount;\n                timer = freeTimers[freeIndex];\n                freeTimers[freeIndex] = null;\n                timer.reset(correlationId, deadline, index);\n            }\n            else\n            {\n                timer = new Timer(correlationId, deadline, index);\n            }\n\n            timerByCorrelationId.put(correlationId, timer);\n            shiftUp(timers, index, timer);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean cancelTimerByCorrelationId(final long correlationId)\n    {\n        final Timer removedTimer = timerByCorrelationId.remove(correlationId);\n        if (null == removedTimer)\n        {\n            return false;\n        }\n\n        final int lastIndex = --size;\n        final Timer lastTimer = timers[lastIndex];\n        timers[lastIndex] = null;\n\n        if (lastIndex != removedTimer.index)\n        {\n            shiftDown(timers, lastIndex, removedTimer.index, lastTimer);\n            if (timers[removedTimer.index] == lastTimer)\n            {\n                shiftUp(timers, removedTimer.index, lastTimer);\n            }\n        }\n\n        addToFreeList(removedTimer);\n\n        return true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void snapshot(final TimerSnapshotTaker snapshotTaker)\n    {\n        final Timer[] timers = this.timers;\n        for (int i = 0, size = this.size; i < size; i++)\n        {\n            final Timer timer = timers[i];\n            snapshotTaker.snapshotTimer(timer.correlationId, timer.deadline);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void currentTime(final long now)\n    {\n    }\n\n    void forEach(final Consumer<PriorityHeapTimerService.Timer> consumer)\n    {\n        final Timer[] timers = this.timers;\n        for (int i = 0, size = this.size; i < size; i++)\n        {\n            consumer.accept(timers[i]);\n        }\n    }\n\n    private static void shiftUp(final Timer[] timers, final int startIndex, final Timer timer)\n    {\n        int index = startIndex;\n        while (index > 0)\n        {\n            final int prevIndex = (index - 1) >>> 1;\n            final Timer prevTimer = timers[prevIndex];\n\n            if (timer.deadline >= prevTimer.deadline)\n            {\n                break;\n            }\n\n            timers[index] = prevTimer;\n            prevTimer.index = index;\n            index = prevIndex;\n        }\n\n        timers[index] = timer;\n        timer.index = index;\n    }\n\n    private static void shiftDown(final Timer[] timers, final int size, final int startIndex, final Timer timer)\n    {\n        final int half = size >>> 1;\n        int index = startIndex;\n        while (index < half)\n        {\n            int nextIndex = (index << 1) + 1;\n            final int right = nextIndex + 1;\n            Timer nextTimer = timers[nextIndex];\n\n            if (right < size && nextTimer.deadline > timers[right].deadline)\n            {\n                nextIndex = right;\n                nextTimer = timers[nextIndex];\n            }\n\n            if (timer.deadline < nextTimer.deadline)\n            {\n                break;\n            }\n\n            timers[index] = nextTimer;\n            nextTimer.index = index;\n            index = nextIndex;\n        }\n\n        timers[index] = timer;\n        timer.index = index;\n    }\n\n    private void ensureCapacity(final int requiredCapacity)\n    {\n        final int currentCapacity = timers.length;\n\n        if (requiredCapacity > currentCapacity)\n        {\n            if (requiredCapacity > ArrayUtil.MAX_CAPACITY)\n            {\n                throw new IllegalStateException(\"max capacity reached: \" + ArrayUtil.MAX_CAPACITY);\n            }\n\n            if (EMPTY_TIMERS == timers)\n            {\n                timers = new Timer[MIN_CAPACITY];\n                freeTimers = new Timer[MIN_CAPACITY];\n            }\n            else\n            {\n                int newCapacity = currentCapacity + (currentCapacity >> 1);\n                if (newCapacity < 0 || newCapacity > ArrayUtil.MAX_CAPACITY)\n                {\n                    newCapacity = ArrayUtil.MAX_CAPACITY;\n                }\n\n                timers = Arrays.copyOf(timers, newCapacity);\n                freeTimers = Arrays.copyOf(freeTimers, newCapacity);\n            }\n        }\n    }\n\n    private void addToFreeList(final Timer timer)\n    {\n        timer.reset(Aeron.NULL_VALUE, Aeron.NULL_VALUE, Aeron.NULL_VALUE);\n        freeTimers[freeTimerCount++] = timer;\n    }\n\n    static final class Timer\n    {\n        long correlationId;\n        long deadline;\n        int index;\n\n        Timer(final long correlationId, final long deadline, final int index)\n        {\n            reset(correlationId, deadline, index);\n        }\n\n        void reset(final long correlationId, final long deadline, final int index)\n        {\n            this.correlationId = correlationId;\n            this.deadline = deadline;\n            this.index = index;\n        }\n\n        public String toString()\n        {\n            return \"PriorityHeapTimerService.Timer{\" +\n                \"correlationId=\" + correlationId +\n                \", deadline=\" + deadline +\n                \", index=\" + index +\n                '}';\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/PriorityHeapTimerServiceSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Supplies instances of the {@link PriorityHeapTimerService}.\n */\npublic class PriorityHeapTimerServiceSupplier implements TimerServiceSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public PriorityHeapTimerServiceSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public TimerService newInstance(final TimeUnit timeUnit, final TimerService.TimerHandler timerHandler)\n    {\n        return new PriorityHeapTimerService(timerHandler);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/PublicationGroup.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.Publication;\nimport org.agrona.CloseHelper;\n\nimport java.util.Arrays;\nimport java.util.Random;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\n\nclass PublicationGroup<P extends Publication> implements AutoCloseable\n{\n    private final String[] endpoints;\n    private final String channelTemplate;\n    private final int streamId;\n    private final PublicationFactory<P> publicationFactory;\n    int cursor = 0;\n    private int excludedPublicationCursorValue = -1;\n    private P current;\n\n    PublicationGroup(\n        final String[] endpoints,\n        final String channelTemplate,\n        final int streamId,\n        final PublicationFactory<P> publicationFactory)\n    {\n\n        this.endpoints = endpoints;\n        this.channelTemplate = channelTemplate;\n        this.streamId = streamId;\n        this.publicationFactory = publicationFactory;\n    }\n\n\n    P next(final Aeron aeron)\n    {\n        final int cursor = nextCursor();\n        final String endpoint = endpoints[cursor];\n\n        final ChannelUri channelUri = ChannelUri.parse(channelTemplate);\n        channelUri.put(ENDPOINT_PARAM_NAME, endpoint);\n        final String channel = channelUri.toString();\n\n        CloseHelper.quietClose(current);\n        current = publicationFactory.addPublication(aeron, channel, streamId);\n        return current;\n    }\n\n    private int nextCursor()\n    {\n        do\n        {\n            ++cursor;\n            if (endpoints.length <= cursor)\n            {\n                cursor = 0;\n            }\n        }\n        while (cursor == excludedPublicationCursorValue);\n\n        return cursor;\n    }\n\n    P current()\n    {\n        return current;\n    }\n\n    void clearExclusion()\n    {\n        excludedPublicationCursorValue = -1;\n    }\n\n    void closeAndExcludeCurrent()\n    {\n        excludedPublicationCursorValue = cursor;\n        CloseHelper.quietClose(current);\n    }\n\n    void shuffle()\n    {\n        close();\n        clearExclusion();\n        final Random random = ThreadLocalRandom.current();\n        for (int i = endpoints.length; --i > -1;)\n        {\n            final int j = random.nextInt(i + 1);\n            final String tmp = endpoints[i];\n            endpoints[i] = endpoints[j];\n            endpoints[j] = tmp;\n        }\n    }\n\n    public void close()\n    {\n        CloseHelper.quietClose(current);\n    }\n\n    public boolean isConnected()\n    {\n        return null != current && current.isConnected();\n    }\n\n    interface PublicationFactory<P>\n    {\n        P addPublication(Aeron aeron, String channel, int streamId);\n    }\n\n    public String toString()\n    {\n        return \"PublicationGroup{\" +\n            \"endpoints=\" + Arrays.toString(endpoints) +\n            \", channelTemplate='\" + channelTemplate + '\\'' +\n            \", streamId=\" + streamId +\n            \", publicationFactory=\" + publicationFactory +\n            \", cursor=\" + cursor +\n            \", excludedPublicationCursorValue=\" + excludedPublicationCursorValue +\n            \", current=\" + current +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/RecordingExtent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.client.RecordingDescriptorConsumer;\n\n/**\n * The extent covered by a recording in the archive in terms of position and time.\n *\n * @see io.aeron.archive.client.AeronArchive#listRecording(long, RecordingDescriptorConsumer)\n */\nclass RecordingExtent implements RecordingDescriptorConsumer\n{\n    /**\n     * Id of the recording.\n     */\n    public long recordingId;\n    /**\n     * Start position.\n     */\n    public long startPosition;\n    /**\n     * Stop position.\n     */\n    public long stopPosition;\n    /**\n     * Initial term id.\n     */\n    public int initialTermId;\n    /**\n     * Term buffer length.\n     */\n    public int termBufferLength;\n    /**\n     * MTU length.\n     */\n    public int mtuLength;\n    /**\n     * Session id.\n     */\n    public int sessionId;\n\n    public void onRecordingDescriptor(\n        final long controlSessionId,\n        final long correlationId,\n        final long recordingId,\n        final long startTimestamp,\n        final long stopTimestamp,\n        final long startPosition,\n        final long stopPosition,\n        final int initialTermId,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int mtuLength,\n        final int sessionId,\n        final int streamId,\n        final String strippedChannel,\n        final String originalChannel,\n        final String sourceIdentity)\n    {\n        this.recordingId = recordingId;\n        this.startPosition = startPosition;\n        this.stopPosition = stopPosition;\n        this.initialTermId = initialTermId;\n        this.termBufferLength = termBufferLength;\n        this.mtuLength = mtuLength;\n        this.sessionId = sessionId;\n    }\n\n    public String toString()\n    {\n        return \"RecordingExtent{\" +\n            \"recordingId=\" + recordingId +\n            \", startPosition=\" + startPosition +\n            \", stopPosition=\" + stopPosition +\n            \", initialTermId=\" + initialTermId +\n            \", termBufferLength=\" + termBufferLength +\n            \", mtuLength=\" + mtuLength +\n            \", sessionId=\" + sessionId +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/RecordingLog.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.cluster.client.ClusterException;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.Strings;\nimport org.agrona.collections.IntArrayList;\nimport org.agrona.collections.Long2LongHashMap;\nimport org.agrona.collections.MutableReference;\nimport org.agrona.collections.Object2ObjectHashMap;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.StandardOpenOption;\nimport java.util.*;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SERVICE_ID;\nimport static java.lang.Math.max;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.nio.file.StandardOpenOption.*;\nimport static org.agrona.BitUtil.*;\n\n/**\n * A log of recordings which make up the history of a Raft log across leadership terms. Entries are in order.\n * <p>\n * The log is made up of entries of leadership terms or snapshots to roll up state as of a log position within a\n * leadership term.\n * <p>\n * The latest state is made up of the latest snapshot followed by any leadership log which follows. It is possible\n * that a snapshot is taken midterm and therefore the latest state is the snapshot plus the log of messages which\n * got appended to the log after the snapshot was taken.\n * <p>\n * Record layout as follows:\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                        Recording ID                           |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                     Leadership Term ID                        |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |              Log Position at beginning of term                |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |        Log Position when entry was created or committed       |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |               Timestamp when entry was created                |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                  Service ID when a Snapshot                   |\n *  +---------------------------------------------------------------+\n *  |R|               Entry Type (Log or Snapshot)                  |\n *  +---------------------------------------------------------------+\n *  |                 Archive Endpoint (length)                     |\n *  +---------------------------------------------------------------+\n *  |                 Archive Endpoint (length)                     |\n *  |                    Archive Endpoint                         ...\n *  ...                     (variable)                              |\n *  +---------------------------------------------------------------+\n *  |                                                               |\n *  |                                                              ...\n * ...                Repeats to the end of the log                 |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n * </pre>\n * <p>The reserved bit on the entry type indicates whether the entry was marked invalid.</p>\n */\npublic final class RecordingLog implements AutoCloseable\n{\n    /**\n     * Representation of the entry in the {@link RecordingLog}.\n     */\n    public static final class Entry\n    {\n        /**\n         * Identity of the recording in the archive.\n         */\n        public final long recordingId;\n\n        /**\n         * Identity of the leadership term.\n         */\n        public final long leadershipTermId;\n\n        /**\n         * The log position at the base of the leadership term.\n         */\n        public final long termBaseLogPosition;\n\n        /**\n         * Position the log has reached for the entry.\n         */\n        public final long logPosition;\n\n        /**\n         * Timestamp for the cluster clock in the time units configured for the cluster.\n         */\n        public final long timestamp;\n\n        /**\n         * Identity of the service associated with the entry.\n         */\n        public final int serviceId;\n\n        /**\n         * Type, or classification, of the entry, e.g. {@link #ENTRY_TYPE_TERM} or {@link #ENTRY_TYPE_SNAPSHOT}.\n         */\n        public final int type;\n\n        /**\n         * Index of the entry in the recording log.\n         */\n        public final int entryIndex;\n\n        /**\n         * Flag to indicate if the entry is invalid and thus should be ignored.\n         */\n        public final boolean isValid;\n\n        /**\n         * Endpoint for an archive where a remote snapshot is located.\n         */\n        public final String archiveEndpoint;\n\n        private long position;\n\n        /**\n         * A new entry in the recording log.\n         *\n         * @param recordingId         of the entry in an archive.\n         * @param leadershipTermId    of this entry.\n         * @param termBaseLogPosition position of the log over leadership terms at the beginning of this term.\n         * @param logPosition         position reached when the entry was created\n         * @param timestamp           of this entry.\n         * @param serviceId           service id for snapshot.\n         * @param type                of the entry as a log of a term or a snapshot.\n         * @param archiveEndpoint     archive where the snapshot is located, if\n         *                            <code>entryType == ENTRY_TYPE_STANDBY_SNAPSHOT</code>.\n         * @param isValid             indicates if the entry is valid, {@link RecordingLog#invalidateEntry(int)}\n         *                            marks it invalid.\n         * @param position            of the entry on disk.\n         * @param entryIndex          of the entry on disk.\n         * @throws ClusterException if <code>entryType == ENTRY_TYPE_STANDBY_SNAPSHOT</code> and <code>endpoint</code>\n         *                          is null or empty.\n         */\n        public Entry(\n            final long recordingId,\n            final long leadershipTermId,\n            final long termBaseLogPosition,\n            final long logPosition,\n            final long timestamp,\n            final int serviceId,\n            final int type,\n            final String archiveEndpoint,\n            final boolean isValid,\n            final long position,\n            final int entryIndex)\n        {\n            this.recordingId = recordingId;\n            this.leadershipTermId = leadershipTermId;\n            this.termBaseLogPosition = termBaseLogPosition;\n            this.logPosition = logPosition;\n            this.timestamp = timestamp;\n            this.serviceId = serviceId;\n            this.type = type;\n            this.archiveEndpoint = archiveEndpoint;\n            this.position = position;\n            this.entryIndex = entryIndex;\n            this.isValid = isValid;\n\n            if (ENTRY_TYPE_STANDBY_SNAPSHOT == type && Strings.isEmpty(archiveEndpoint))\n            {\n                throw new ClusterException(\"Remote snapshots must has a valid endpoint\");\n            }\n        }\n\n        Entry(\n            final long recordingId,\n            final long leadershipTermId,\n            final long termBaseLogPosition,\n            final long logPosition,\n            final long timestamp,\n            final int serviceId,\n            final int type,\n            final String archiveEndpoint,\n            final boolean isValid,\n            final int entryIndex)\n        {\n            this(\n                recordingId,\n                leadershipTermId,\n                termBaseLogPosition,\n                logPosition,\n                timestamp,\n                serviceId,\n                type,\n                archiveEndpoint,\n                isValid,\n                NULL_VALUE,\n                entryIndex);\n        }\n\n        /**\n         * Binary length of the serialised entry including alignment.\n         *\n         * @return length of this entry.\n         */\n        public int length()\n        {\n            final int unalignedLength = ENTRY_TYPE_STANDBY_SNAPSHOT == type ?\n                (ENDPOINT_OFFSET + SIZE_OF_INT + archiveEndpoint.length()) : ENDPOINT_OFFSET;\n\n            return align(unalignedLength, RECORD_ALIGNMENT);\n        }\n\n        private void position(final long position)\n        {\n            this.position = position;\n        }\n\n        Entry invalidate()\n        {\n            return new Entry(\n                recordingId,\n                leadershipTermId,\n                termBaseLogPosition,\n                logPosition,\n                timestamp,\n                serviceId,\n                type,\n                archiveEndpoint,\n                false,\n                position,\n                entryIndex);\n        }\n\n        Entry logPosition(final long logPosition)\n        {\n            return new Entry(\n                recordingId,\n                leadershipTermId,\n                termBaseLogPosition,\n                logPosition,\n                timestamp,\n                serviceId,\n                type,\n                archiveEndpoint,\n                isValid,\n                position,\n                entryIndex);\n        }\n\n        long serviceId()\n        {\n            return serviceId;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public boolean equals(final Object o)\n        {\n            if (this == o)\n            {\n                return true;\n            }\n\n            if (o == null || getClass() != o.getClass())\n            {\n                return false;\n            }\n\n            final Entry entry = (Entry)o;\n\n            return recordingId == entry.recordingId &&\n                leadershipTermId == entry.leadershipTermId &&\n                termBaseLogPosition == entry.termBaseLogPosition &&\n                logPosition == entry.logPosition &&\n                timestamp == entry.timestamp &&\n                serviceId == entry.serviceId &&\n                type == entry.type &&\n                entryIndex == entry.entryIndex &&\n                isValid == entry.isValid;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public int hashCode()\n        {\n            int result = Long.hashCode(recordingId);\n            result = 31 * result + Long.hashCode(leadershipTermId);\n            result = 31 * result + Long.hashCode(termBaseLogPosition);\n            result = 31 * result + Long.hashCode(logPosition);\n            result = 31 * result + Long.hashCode(timestamp);\n            result = 31 * result + serviceId;\n            result = 31 * result + type;\n            result = 31 * result + entryIndex;\n            result = 31 * result + (isValid ? 1 : 0);\n\n            return result;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            final String archiveEndpointEntry = ENTRY_TYPE_STANDBY_SNAPSHOT == type ?\n                \", archiveEndpoint=\" + archiveEndpoint : \"\";\n\n            return \"Entry{\" +\n                \"recordingId=\" + recordingId +\n                \", leadershipTermId=\" + leadershipTermId +\n                \", termBaseLogPosition=\" + termBaseLogPosition +\n                \", logPosition=\" + logPosition +\n                \", timestamp=\" + timestamp +\n                \", serviceId=\" + serviceId +\n                \", type=\" + typeAsString(type) +\n                \", isValid=\" + isValid +\n                \", entryIndex=\" + entryIndex +\n                archiveEndpointEntry +\n                '}';\n        }\n    }\n\n    /**\n     * Representation of a snapshot entry in the {@link RecordingLog}.\n     *\n     * @param recordingId           identity of the recording in the archive for the snapshot.\n     * @param leadershipTermId      identity of the leadership term.\n     * @param termBaseLogPosition   the log position as the base of the leadership term.\n     * @param logPosition           that the log has reached for the snapshot.\n     * @param timestamp             that the cluster clock was as at the point of the snapshot (in configured units).\n     * @param serviceId             identity of the service taking the snapshot\n     *                              ({@link ConsensusModule.Configuration#SERVICE_ID} is used for the consensus module's\n     *                              entry.\n     */\n    public record Snapshot(\n        long recordingId,\n        long leadershipTermId,\n        long termBaseLogPosition,\n        long logPosition,\n        long timestamp,\n        int serviceId)\n    {\n    }\n\n    /**\n     * Representation of a log entry in the {@link RecordingLog}.\n     *\n     * @param recordingId           identity of the recording in the archive for the snapshot.\n     * @param leadershipTermId      identity of the leadership term.\n     * @param termBaseLogPosition   the log position as the base of the leadership term.\n     * @param logPosition           that the log has reached for the snapshot.\n     * @param startPosition         of the recording captured in the archive.\n     * @param stopPosition          of the recording captured in the archive.\n     * @param initialTermId         of the stream captured for the recording.\n     * @param termBufferLength      of the stream captured for the recording.\n     * @param mtuLength             of the stream captured for the recording.\n     * @param sessionId             of the stream captured for the recording.\n     */\n    public record Log(\n        long recordingId,\n        long leadershipTermId,\n        long termBaseLogPosition,\n        long logPosition,\n        long startPosition,\n        long stopPosition,\n        int initialTermId,\n        int termBufferLength,\n        int mtuLength,\n        int sessionId)\n    {\n    }\n\n    /**\n     * The snapshots and steps to recover the state of a cluster.\n     *\n     * @param lastLeadershipTermId      the last, i.e. most recent, leadership term identity for the log.\n     * @param lastTermBaseLogPosition   the last, i.e. most recent, leadership term base log position.\n     * @param appendedLogPosition       the position reached for local appended log.\n     * @param committedLogPosition      the position reached for the local appended log for which the commit position\n     *                                  is known.\n     * @param snapshots                 the most recent snapshots for the consensus module and services to accelerate\n     *                                  recovery.\n     * @param log                       the appended local log details.\n     */\n    public record RecoveryPlan(\n        long lastLeadershipTermId,\n        long lastTermBaseLogPosition,\n        long appendedLogPosition,\n        long committedLogPosition,\n        ArrayList<Snapshot> snapshots,\n        Log log)\n    {\n    }\n\n    /**\n     * Filename for the history of leadership log terms and snapshot recordings.\n     */\n    public static final String RECORDING_LOG_FILE_NAME = \"recording.log\";\n\n    /**\n     * The log entry is for a recording of messages within a leadership term to the log.\n     */\n    public static final int ENTRY_TYPE_TERM = 0;\n\n    /**\n     * The log entry is for a recording of a snapshot of state taken as of a position in the log.\n     */\n    public static final int ENTRY_TYPE_SNAPSHOT = 1;\n\n    /**\n     * The log entry is for a recording of a snapshot of state taken as of a position in the log on another machine.\n     * Entries of this time should have an endpoint for an archive associated with them.\n     */\n    public static final int ENTRY_TYPE_STANDBY_SNAPSHOT = 2;\n\n    /**\n     * The flag used to determine if the entry has been marked with invalid.\n     */\n    public static final int ENTRY_TYPE_INVALID_FLAG = 1 << 31;\n\n    /**\n     * The offset at which the recording id for the entry is stored.\n     */\n    public static final int RECORDING_ID_OFFSET = 0;\n\n    /**\n     * The offset at which the leadership term id for the entry is stored.\n     */\n    public static final int LEADERSHIP_TERM_ID_OFFSET = RECORDING_ID_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * The offset at which the log position as of the beginning of the term for the entry is stored.\n     */\n    public static final int TERM_BASE_LOG_POSITION_OFFSET = LEADERSHIP_TERM_ID_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * The offset at which the log position is stored.\n     */\n    public static final int LOG_POSITION_OFFSET = TERM_BASE_LOG_POSITION_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * The offset at which the timestamp for the entry is stored.\n     */\n    public static final int TIMESTAMP_OFFSET = LOG_POSITION_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * The offset at which the service id is recorded.\n     */\n    public static final int SERVICE_ID_OFFSET = TIMESTAMP_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * The offset at which the type of the entry is stored.\n     */\n    public static final int ENTRY_TYPE_OFFSET = SERVICE_ID_OFFSET + SIZE_OF_INT;\n\n    /**\n     * The offset at which the endpoint of the remote snapshot is held.\n     */\n    public static final int ENDPOINT_OFFSET = ENTRY_TYPE_OFFSET + SIZE_OF_INT;\n\n    /**\n     * Maximum possible entry length. Include the entry plus a variable length endpoint string.\n     */\n    public static final int MAX_ENTRY_LENGTH = 4096;\n\n    static final int MAX_ENDPOINT_LENGTH = MAX_ENTRY_LENGTH - ENDPOINT_OFFSET - SIZE_OF_INT;\n\n    private static final int RECORD_ALIGNMENT = 64;\n\n    private static final Comparator<Entry> ENTRY_COMPARATOR =\n        (Entry e1, Entry e2) ->\n        {\n            int result = Long.compare(e1.leadershipTermId, e2.leadershipTermId);\n            if (0 != result)\n            {\n                return result;\n            }\n\n            result = Long.compare(e1.termBaseLogPosition, e2.termBaseLogPosition);\n            if (0 != result)\n            {\n                return result;\n            }\n\n            result = Integer.compare(e1.type, e2.type);\n            if (0 != result)\n            {\n                if (ENTRY_TYPE_SNAPSHOT == e1.type)\n                {\n                    return 1;\n                }\n                else if (ENTRY_TYPE_SNAPSHOT == e2.type)\n                {\n                    return -1;\n                }\n                return result;\n            }\n\n            if (ENTRY_TYPE_TERM == e1.type)\n            {\n                return Integer.compare(e1.entryIndex, e2.entryIndex);\n            }\n            else\n            {\n                result = Long.compare(e1.logPosition, e2.logPosition);\n                if (0 != result)\n                {\n                    return result;\n                }\n\n                return Integer.compare(e2.serviceId, e1.serviceId); // reverse serviceId order\n            }\n        };\n\n    private long termRecordingId = NULL_VALUE;\n    private int nextEntryIndex;\n    private final FileChannel fileChannel;\n    private final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(MAX_ENTRY_LENGTH).order(LITTLE_ENDIAN);\n    private final UnsafeBuffer buffer = new UnsafeBuffer(byteBuffer);\n    private final ArrayList<Entry> entriesCache = new ArrayList<>();\n    private final Long2LongHashMap cacheIndexByLeadershipTermIdMap = new Long2LongHashMap(NULL_VALUE);\n    private final IntArrayList invalidSnapshots = new IntArrayList();\n\n    /**\n     * Create a log that appends to an existing log or creates a new one.\n     *\n     * @param parentDir in which the log will be created.\n     * @param createNew create a new recording log if one does not exist.\n     */\n    public RecordingLog(final File parentDir, final boolean createNew)\n    {\n        final File logFile = new File(parentDir, RECORDING_LOG_FILE_NAME);\n        final boolean isNewFile = !logFile.exists();\n        final EnumSet<StandardOpenOption> openOptions = EnumSet.of(READ, WRITE);\n\n        if (createNew)\n        {\n            openOptions.add(CREATE);\n        }\n\n        try\n        {\n            fileChannel = FileChannel.open(logFile.toPath(), openOptions);\n\n            if (isNewFile)\n            {\n                syncDirectory(parentDir);\n            }\n            else\n            {\n                reload();\n            }\n        }\n        catch (final IOException ex)\n        {\n            throw new ClusterException(ex);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(fileChannel);\n    }\n\n    /**\n     * Force the file to durable storage. Same as calling {@link FileChannel#force(boolean)} with true.\n     *\n     * @param fileSyncLevel as defined by {@link ConsensusModule.Configuration#FILE_SYNC_LEVEL_PROP_NAME}.\n     */\n    public void force(final int fileSyncLevel)\n    {\n        if (fileSyncLevel > 0)\n        {\n            try\n            {\n                fileChannel.force(fileSyncLevel > 1);\n            }\n            catch (final IOException ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n        }\n    }\n\n    /**\n     * List of currently loaded entries.\n     *\n     * @return the list of currently loaded entries.\n     */\n    public List<Entry> entries()\n    {\n        return entriesCache;\n    }\n\n    /**\n     * Get the next index to be used when appending an entry to the log.\n     *\n     * @return the next index to be used when appending an entry to the log.\n     */\n    public int nextEntryIndex()\n    {\n        return nextEntryIndex;\n    }\n\n    /**\n     * Reload the recording log from disk.\n     */\n    public void reload()\n    {\n        entriesCache.clear();\n        cacheIndexByLeadershipTermIdMap.clear();\n        invalidSnapshots.clear();\n        cacheIndexByLeadershipTermIdMap.compact();\n\n        nextEntryIndex = 0;\n        byteBuffer.clear();\n\n        try\n        {\n            long filePosition = 0;\n            long consumePosition = 0;\n\n            while (true)\n            {\n                final int bytesRead = fileChannel.read(byteBuffer, filePosition);\n                filePosition += bytesRead;\n\n                if (0 < bytesRead)\n                {\n                    byteBuffer.flip();\n                    consumePosition += captureEntriesFromBuffer(consumePosition, byteBuffer, buffer, entriesCache);\n                    byteBuffer.compact();\n                }\n                else\n                {\n                    break;\n                }\n            }\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        entriesCache.sort(ENTRY_COMPARATOR);\n        for (int i = 0, size = entriesCache.size(); i < size; i++)\n        {\n            final Entry entry = entriesCache.get(i);\n\n            if (isValidTerm(entry))\n            {\n                cacheIndexByLeadershipTermIdMap.put(entry.leadershipTermId, i);\n            }\n\n            if (!entry.isValid && (ENTRY_TYPE_SNAPSHOT == entry.type || ENTRY_TYPE_STANDBY_SNAPSHOT == entry.type))\n            {\n                invalidSnapshots.add(i);\n            }\n        }\n    }\n\n    /**\n     * Find the last recording id used for a leadership term. If not found then {@link RecordingPos#NULL_RECORDING_ID}.\n     *\n     * @return the last leadership term recording id or {@link RecordingPos#NULL_RECORDING_ID} if not found.\n     */\n    public long findLastTermRecordingId()\n    {\n        final Entry lastTerm = findLastTerm();\n        return null != lastTerm ? lastTerm.recordingId : RecordingPos.NULL_RECORDING_ID;\n    }\n\n    /**\n     * Find the last leadership term in the recording log.\n     *\n     * @return the last leadership term in the recording log.\n     */\n    public Entry findLastTerm()\n    {\n        for (int i = entriesCache.size() - 1; i >= 0; i--)\n        {\n            final Entry entry = entriesCache.get(i);\n            if (isValidTerm(entry))\n            {\n                return entry;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Get the term {@link Entry} for a given leadership term id.\n     *\n     * @param leadershipTermId to get {@link Entry} for.\n     * @return the {@link Entry} if found or throw {@link IllegalArgumentException} if no entry exists for term.\n     */\n    public Entry getTermEntry(final long leadershipTermId)\n    {\n        final int index = (int)cacheIndexByLeadershipTermIdMap.get(leadershipTermId);\n        if (NULL_VALUE != index)\n        {\n            return entriesCache.get(index);\n        }\n\n        throw new ClusterException(\"unknown leadershipTermId=\" + leadershipTermId);\n    }\n\n    /**\n     * Find the term {@link Entry} for a given leadership term id.\n     *\n     * @param leadershipTermId to get {@link Entry} for.\n     * @return the {@link Entry} if found or null if not found.\n     */\n    public Entry findTermEntry(final long leadershipTermId)\n    {\n        final int index = (int)cacheIndexByLeadershipTermIdMap.get(leadershipTermId);\n        if (NULL_VALUE != index)\n        {\n            return entriesCache.get(index);\n        }\n\n        return null;\n    }\n\n    /**\n     * Get the latest snapshot {@link Entry} in the log.\n     *\n     * @param serviceId for the snapshot.\n     * @return the latest snapshot {@link Entry} in the log or null if no snapshot exists.\n     */\n    public Entry getLatestSnapshot(final int serviceId)\n    {\n        for (int i = entriesCache.size() - 1; i >= 0; i--)\n        {\n            final Entry entry = entriesCache.get(i);\n            if (isValidSnapshot(entry) && SERVICE_ID == entry.serviceId)\n            {\n                if (SERVICE_ID == serviceId)\n                {\n                    return entry;\n                }\n\n                final int serviceSnapshotIndex = i - (serviceId + 1);\n                if (serviceSnapshotIndex >= 0)\n                {\n                    final Entry snapshot = entriesCache.get(serviceSnapshotIndex);\n                    if (isValidSnapshot(snapshot) && serviceId == snapshot.serviceId)\n                    {\n                        return snapshot;\n                    }\n                }\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Invalidate the last snapshot taken by the cluster so that on restart it can revert to the previous one.\n     *\n     * @return true if the latest snapshot was found and marked as invalid, so it will not be reloaded.\n     */\n    public boolean invalidateLatestSnapshot()\n    {\n        final IntArrayList indices = new IntArrayList();\n        long highLogPosition = NULL_POSITION;\n\n        for (int idx = entriesCache.size() - 1; idx >= 0; idx--)\n        {\n            final Entry entry = entriesCache.get(idx);\n            if (isValidAnySnapshot(entry) && SERVICE_ID == entry.serviceId)\n            {\n                if (entry.logPosition >= highLogPosition)\n                {\n                    if (!cacheIndexByLeadershipTermIdMap.containsKey(entry.leadershipTermId))\n                    {\n                        throw new ClusterException(\n                            \"no matching term for snapshot: leadershipTermId=\" + entry.leadershipTermId);\n                    }\n\n                    if (entry.logPosition > highLogPosition)\n                    {\n                        indices.clear();\n                        highLogPosition = entry.logPosition;\n                    }\n\n                    indices.pushInt(idx);\n                }\n                else\n                {\n                    // found an earlier snapshot, so stop searching through the cache\n                    break;\n                }\n            }\n        }\n\n        final boolean found = !indices.isEmpty();\n\n        while (!indices.isEmpty())\n        {\n            final int startingIndex = indices.popInt();\n\n            int serviceId = SERVICE_ID;\n\n            for (int idx = startingIndex; idx >= 0; idx--)\n            {\n                final Entry entry = entriesCache.get(idx);\n                if (isValidAnySnapshot(entry) && entry.serviceId == serviceId)\n                {\n                    invalidateEntry(idx);\n                    serviceId++;\n                }\n                else\n                {\n                    break;\n                }\n            }\n        }\n\n        return found;\n    }\n\n    /**\n     * Get the {@link Entry#timestamp} for a term.\n     *\n     * @param leadershipTermId to get {@link Entry#timestamp} for.\n     * @return the timestamp or {@link io.aeron.Aeron#NULL_VALUE} if not found.\n     */\n    public long getTermTimestamp(final long leadershipTermId)\n    {\n        final int index = (int)cacheIndexByLeadershipTermIdMap.get(leadershipTermId);\n        if (NULL_VALUE != index)\n        {\n            return entriesCache.get(index).timestamp;\n        }\n\n        return NULL_VALUE;\n    }\n\n    /**\n     * Create a recovery plan for the cluster so that when the steps are replayed the plan will bring the cluster\n     * back to the latest stable state.\n     *\n     * @param archive               to lookup recording descriptors.\n     * @param serviceCount          of services that may have snapshots.\n     * @param replicatedRecordingId leader's recordingId used for replicating\n     * @return a new {@link RecoveryPlan} for the cluster.\n     */\n    public RecoveryPlan createRecoveryPlan(\n        final AeronArchive archive, final int serviceCount, final long replicatedRecordingId)\n    {\n        final ArrayList<Snapshot> snapshots = new ArrayList<>();\n        final MutableReference<Log> logRef = new MutableReference<>();\n        planRecovery(snapshots, logRef, entriesCache, archive, serviceCount, replicatedRecordingId);\n\n        long lastLeadershipTermId = NULL_VALUE;\n        long lastTermBaseLogPosition = 0;\n        long committedLogPosition = 0;\n        long appendedLogPosition = 0;\n\n        final int snapshotStepsSize = snapshots.size();\n        if (snapshotStepsSize > 0)\n        {\n            final Snapshot snapshot = snapshots.get(0);\n\n            lastLeadershipTermId = snapshot.leadershipTermId;\n            lastTermBaseLogPosition = snapshot.termBaseLogPosition;\n            appendedLogPosition = snapshot.logPosition;\n            committedLogPosition = snapshot.logPosition;\n        }\n\n        final Log log = logRef.get();\n        if (null != log)\n        {\n            lastLeadershipTermId = log.leadershipTermId;\n            lastTermBaseLogPosition = log.termBaseLogPosition;\n            appendedLogPosition = log.stopPosition;\n            committedLogPosition = NULL_POSITION != log.logPosition ? log.logPosition : committedLogPosition;\n        }\n\n        return new RecoveryPlan(\n            lastLeadershipTermId,\n            lastTermBaseLogPosition,\n            appendedLogPosition,\n            committedLogPosition,\n            snapshots,\n            log);\n    }\n\n    /**\n     * Is the given leadershipTermId unknown for the log?\n     *\n     * @param leadershipTermId to check.\n     * @return true if term has not yet been appended otherwise false.\n     */\n    public boolean isUnknown(final long leadershipTermId)\n    {\n        return NULL_VALUE == cacheIndexByLeadershipTermIdMap.get(leadershipTermId);\n    }\n\n    /**\n     * Append a log entry for a leadership term. Terms must be appended in ascending order.\n     *\n     * @param recordingId         of the log in the archive.\n     * @param leadershipTermId    for the appended term.\n     * @param termBaseLogPosition for the beginning of the term.\n     * @param timestamp           at the beginning of the appended term.\n     */\n    public void appendTerm(\n        final long recordingId, final long leadershipTermId, final long termBaseLogPosition, final long timestamp)\n    {\n        validateTermRecordingId(recordingId);\n\n        long logPosition = NULL_POSITION;\n\n        if (!entriesCache.isEmpty())\n        {\n            if (cacheIndexByLeadershipTermIdMap.containsKey(leadershipTermId))\n            {\n                throw new ClusterException(\"duplicate TERM entry for leadershipTermId=\" + leadershipTermId);\n            }\n\n            final long previousLeadershipTermId = leadershipTermId - 1;\n            if (cacheIndexByLeadershipTermIdMap.containsKey(previousLeadershipTermId))\n            {\n                commitLogPosition(previousLeadershipTermId, termBaseLogPosition);\n            }\n\n            final Entry nextTermEntry = findTermEntry(leadershipTermId + 1);\n            if (null != nextTermEntry)\n            {\n                logPosition = nextTermEntry.termBaseLogPosition;\n            }\n        }\n\n        final int index = append(\n            ENTRY_TYPE_TERM,\n            recordingId,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            timestamp,\n            NULL_VALUE,\n            null);\n\n        cacheIndexByLeadershipTermIdMap.put(leadershipTermId, index);\n    }\n\n    /**\n     * Append a log entry for a snapshot. Snapshots must be for the current term.\n     *\n     * @param recordingId         in the archive for the snapshot.\n     * @param leadershipTermId    for the current term\n     * @param termBaseLogPosition at the beginning of the leadership term.\n     * @param logPosition         within the current term or accumulated length for the log.\n     * @param timestamp           at which the snapshot was taken.\n     * @param serviceId           for which the snapshot is recorded.\n     */\n    public void appendSnapshot(\n        final long recordingId,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long timestamp,\n        final int serviceId)\n    {\n        validateRecordingId(recordingId);\n\n        if (!restoreInvalidSnapshot(\n            ENTRY_TYPE_SNAPSHOT, recordingId, leadershipTermId, termBaseLogPosition, logPosition, timestamp, serviceId))\n        {\n            append(\n                ENTRY_TYPE_SNAPSHOT,\n                recordingId,\n                leadershipTermId,\n                termBaseLogPosition,\n                logPosition,\n                timestamp,\n                serviceId,\n                null);\n        }\n    }\n\n    /**\n     * Append a log entry for a snapshot. Snapshots must be for the current term.\n     *\n     * @param recordingId         in the archive for the snapshot.\n     * @param leadershipTermId    for the current term\n     * @param termBaseLogPosition at the beginning of the leadership term.\n     * @param logPosition         within the current term or accumulated length for the log.\n     * @param timestamp           at which the snapshot was taken.\n     * @param serviceId           for which the snapshot is recorded.\n     * @param archiveEndpoint     endpoint for the archive where\n     */\n    public void appendStandbySnapshot(\n        final long recordingId,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long timestamp,\n        final int serviceId,\n        final String archiveEndpoint)\n    {\n        validateRecordingId(recordingId);\n\n        if (Strings.isEmpty(archiveEndpoint))\n        {\n            throw new ClusterException(\"Remote snapshots must have a valid endpoint\");\n        }\n\n        if (archiveEndpoint.length() > MAX_ENDPOINT_LENGTH)\n        {\n            throw new ClusterException(\n                \"Endpoint is too long: \" + archiveEndpoint.length() + \" vs \" + MAX_ENDPOINT_LENGTH);\n        }\n\n        if (!restoreInvalidSnapshot(\n            ENTRY_TYPE_STANDBY_SNAPSHOT,\n            recordingId,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            timestamp,\n            serviceId))\n        {\n            append(\n                ENTRY_TYPE_STANDBY_SNAPSHOT,\n                recordingId,\n                leadershipTermId,\n                termBaseLogPosition,\n                logPosition,\n                timestamp,\n                serviceId,\n                archiveEndpoint);\n        }\n    }\n\n    /**\n     * Commit the log position reached in a leadership term.\n     *\n     * @param leadershipTermId for committing the term position reached.\n     * @param logPosition      reached in the leadership term.\n     */\n    public void commitLogPosition(final long leadershipTermId, final long logPosition)\n    {\n        final int index = (int)cacheIndexByLeadershipTermIdMap.get(leadershipTermId);\n        if (NULL_VALUE == index)\n        {\n            throw new ClusterException(\"unknown leadershipTermId=\" + leadershipTermId);\n        }\n\n        final Entry entry = entriesCache.get(index);\n        if (entry.logPosition != logPosition)\n        {\n            buffer.putLong(0, logPosition, LITTLE_ENDIAN);\n            persistToStorage(entry.position, LOG_POSITION_OFFSET, SIZE_OF_LONG);\n            entriesCache.set(index, entry.logPosition(logPosition));\n        }\n    }\n\n    void invalidateEntry(final int index)\n    {\n        final Entry invalidEntry = entriesCache.get(index).invalidate();\n        entriesCache.set(index, invalidEntry);\n\n        if (ENTRY_TYPE_TERM == invalidEntry.type)\n        {\n            cacheIndexByLeadershipTermIdMap.remove(invalidEntry.leadershipTermId);\n        }\n        else if (ENTRY_TYPE_SNAPSHOT == invalidEntry.type || ENTRY_TYPE_STANDBY_SNAPSHOT == invalidEntry.type)\n        {\n            invalidSnapshots.add(index);\n        }\n\n        final int invalidEntryType = ENTRY_TYPE_INVALID_FLAG | invalidEntry.type;\n        buffer.putInt(0, invalidEntryType, LITTLE_ENDIAN);\n        persistToStorage(invalidEntry.position, ENTRY_TYPE_OFFSET, SIZE_OF_INT);\n    }\n\n    /**\n     * Remove an entry in the log and reload the caches.\n     *\n     * @param leadershipTermId to match for validation.\n     * @param entryIndex       reached in the leadership term.\n     */\n    void removeEntry(final long leadershipTermId, final int entryIndex)\n    {\n        Entry entryToRemove = null;\n\n        for (int i = entriesCache.size() - 1; i >= 0; i--)\n        {\n            final Entry entry = entriesCache.get(i);\n            if (entry.leadershipTermId == leadershipTermId && entry.entryIndex == entryIndex)\n            {\n                entryToRemove = entry;\n                break;\n            }\n        }\n\n        if (null == entryToRemove)\n        {\n            throw new ClusterException(\"unknown entry index: \" + entryIndex);\n        }\n\n        buffer.putInt(0, NULL_VALUE, LITTLE_ENDIAN);\n        persistToStorage(entryToRemove.position, ENTRY_TYPE_OFFSET, SIZE_OF_INT);\n\n        reload();\n    }\n\n    /**\n     * Return a collection of the most recent remote snapshots grouped by the archiveEndpoint where the snapshot is\n     * stored.\n     *\n     * @param serviceCount to ensure that we have a complete set of snapshots.\n     * @return collection of snapshots.\n     */\n    public Map<String, List<Entry>> latestStandbySnapshots(final int serviceCount)\n    {\n        final Map<String, List<Entry>> latestStandbySnapshots = new Object2ObjectHashMap<>();\n        final Map<String, NavigableMap<Long, List<Entry>>> standbySnapshots = new Object2ObjectHashMap<>();\n\n        for (int i = entriesCache.size() - 1; i >= 0; i--)\n        {\n            final Entry entry = entriesCache.get(i);\n            if (ENTRY_TYPE_STANDBY_SNAPSHOT == entry.type && entry.isValid)\n            {\n                standbySnapshots.computeIfAbsent(entry.archiveEndpoint, (s) -> new TreeMap<>())\n                    .computeIfAbsent(entry.logPosition, (l) -> new ArrayList<>())\n                    .add(entry);\n            }\n        }\n\n        standbySnapshots.forEach(\n            (k, v) ->\n            {\n                while (!v.isEmpty())\n                {\n                    final Map.Entry<Long, List<Entry>> lastEntry = v.lastEntry();\n                    final int snapshotCount = serviceCount + 1;\n                    if (lastEntry.getValue().size() == snapshotCount)\n                    {\n                        latestStandbySnapshots.put(k, lastEntry.getValue());\n                        break;\n                    }\n                    else\n                    {\n                        v.remove(lastEntry.getKey());\n                    }\n                }\n            });\n\n        return latestStandbySnapshots;\n    }\n\n    List<Snapshot> findSnapshotAtOrBeforeOrLowest(final long logPosition, final int serviceCount)\n    {\n        entriesCache.sort(ENTRY_COMPARATOR);\n\n        for (int i = entriesCache.size() - 1; i >= 0; i--)\n        {\n            final Entry entry = entriesCache.get(i);\n            if (isValidSnapshot(entry) && SERVICE_ID == entry.serviceId)\n            {\n                if (entry.logPosition > logPosition)\n                {\n                    continue;\n                }\n\n                if (validateSnapshotEntries(entriesCache, serviceCount, i, entry))\n                {\n                    return convertEntryListToSnapshotList(entriesCache.subList(i - serviceCount, i + 1));\n                }\n            }\n        }\n\n        for (int i = 0; i < entriesCache.size(); i++)\n        {\n            final Entry entry = entriesCache.get(i);\n            if (isValidSnapshot(entry) && SERVICE_ID == entry.serviceId)\n            {\n                if (validateSnapshotEntries(entriesCache, serviceCount, i, entry))\n                {\n                    return convertEntryListToSnapshotList(entriesCache.subList(i - serviceCount, i + 1));\n                }\n            }\n        }\n\n        return null;\n    }\n\n    static List<Snapshot> convertEntryListToSnapshotList(final List<Entry> entries)\n    {\n        final List<Snapshot> snapshots = new ArrayList<>(entries.size());\n\n        for (int i = 0; i < entries.size(); i++)\n        {\n            final Entry entry = entries.get(i);\n            snapshots.add(new Snapshot(\n                entry.recordingId,\n                entry.leadershipTermId,\n                entry.termBaseLogPosition,\n                entry.logPosition,\n                entry.timestamp,\n                entry.serviceId));\n        }\n\n        return snapshots;\n    }\n\n    static boolean validateSnapshotEntries(\n        final ArrayList<Entry> entries,\n        final int serviceCount,\n        final int snapshotIndex,\n        final Entry consensusModuleEntry)\n    {\n        for (int i = 1; i <= serviceCount; i++)\n        {\n            final Entry entry = entries.get(snapshotIndex - i);\n\n            if (ENTRY_TYPE_SNAPSHOT != entry.type ||\n                entry.leadershipTermId != consensusModuleEntry.leadershipTermId ||\n                entry.logPosition != consensusModuleEntry.logPosition)\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"RecordingLog{\" +\n            \"entries=\" + entriesCache +\n            \", cacheIndex=\" + cacheIndexByLeadershipTermIdMap +\n            '}';\n    }\n\n    static String typeAsString(final int type)\n    {\n        switch (type)\n        {\n            case ENTRY_TYPE_TERM:\n                return \"TERM\";\n            case ENTRY_TYPE_SNAPSHOT:\n                return \"SNAPSHOT\";\n            case ENTRY_TYPE_STANDBY_SNAPSHOT:\n                return \"STANDBY_SNAPSHOT\";\n            default:\n                return \"UNKNOWN\";\n        }\n    }\n\n    static void addSnapshots(\n        final ArrayList<Snapshot> snapshots,\n        final ArrayList<Entry> entries,\n        final int serviceCount,\n        final int snapshotIndex)\n    {\n        final Entry snapshot = entries.get(snapshotIndex);\n        snapshots.add(new Snapshot(\n            snapshot.recordingId,\n            snapshot.leadershipTermId,\n            snapshot.termBaseLogPosition,\n            snapshot.logPosition,\n            snapshot.timestamp,\n            snapshot.serviceId));\n\n        for (int i = 1; i <= serviceCount; i++)\n        {\n            if ((snapshotIndex - i) < 0)\n            {\n                throw new ClusterException(\"snapshot missing for service at index \" + i + \" in \" + entries);\n            }\n\n            final Entry entry = entries.get(snapshotIndex - i);\n\n            if (ENTRY_TYPE_SNAPSHOT == entry.type &&\n                entry.leadershipTermId == snapshot.leadershipTermId &&\n                entry.logPosition == snapshot.logPosition)\n            {\n                snapshots.add(entry.serviceId + 1, new Snapshot(\n                    entry.recordingId,\n                    entry.leadershipTermId,\n                    entry.termBaseLogPosition,\n                    entry.logPosition,\n                    entry.timestamp,\n                    entry.serviceId));\n            }\n        }\n    }\n\n    static void writeEntryToBuffer(final Entry entry, final UnsafeBuffer buffer)\n    {\n        buffer.putLong(RECORDING_ID_OFFSET, entry.recordingId, LITTLE_ENDIAN);\n        buffer.putLong(LEADERSHIP_TERM_ID_OFFSET, entry.leadershipTermId, LITTLE_ENDIAN);\n        buffer.putLong(TERM_BASE_LOG_POSITION_OFFSET, entry.termBaseLogPosition, LITTLE_ENDIAN);\n        buffer.putLong(LOG_POSITION_OFFSET, entry.logPosition, LITTLE_ENDIAN);\n        buffer.putLong(TIMESTAMP_OFFSET, entry.timestamp, LITTLE_ENDIAN);\n        buffer.putInt(SERVICE_ID_OFFSET, entry.serviceId, LITTLE_ENDIAN);\n        buffer.putInt(ENTRY_TYPE_OFFSET, entry.type, LITTLE_ENDIAN);\n        if (!Strings.isEmpty(entry.archiveEndpoint))\n        {\n            buffer.putStringAscii(ENDPOINT_OFFSET, entry.archiveEndpoint);\n        }\n    }\n\n    static boolean isValidSnapshot(final Entry entry)\n    {\n        return entry.isValid && ENTRY_TYPE_SNAPSHOT == entry.type;\n    }\n\n    static boolean isInvalidSnapshot(final Entry entry)\n    {\n        return !entry.isValid && ENTRY_TYPE_SNAPSHOT == entry.type;\n    }\n\n    static boolean isValidAnySnapshot(final Entry entry)\n    {\n        return entry.isValid && (ENTRY_TYPE_SNAPSHOT == entry.type || ENTRY_TYPE_STANDBY_SNAPSHOT == entry.type);\n    }\n\n    void ensureCoherent(\n        final long recordingId,\n        final long initialLogLeadershipTermId,\n        final long initialTermBaseLogPosition,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long nowNs,\n        final long timestamp,\n        final int fileSyncLevel)\n    {\n        Entry lastTerm = findLastTerm();\n\n        if (null == lastTerm)\n        {\n            for (long termId = max(0, initialLogLeadershipTermId); termId < leadershipTermId; termId++)\n            {\n                appendTerm(recordingId, termId, initialTermBaseLogPosition, timestamp);\n            }\n\n            appendTerm(recordingId, leadershipTermId, termBaseLogPosition, timestamp);\n            if (NULL_VALUE != logPosition)\n            {\n                commitLogPosition(leadershipTermId, logPosition);\n            }\n        }\n        else if (lastTerm.leadershipTermId < leadershipTermId)\n        {\n            if (NULL_VALUE == lastTerm.logPosition)\n            {\n                if (NULL_VALUE == termBaseLogPosition)\n                {\n                    throw new ClusterException(\n                        \"Prior term was not committed: \" + lastTerm +\n                        \" and logTermBasePosition was not specified: leadershipTermId = \" + leadershipTermId +\n                        \", logTermBasePosition = \" + termBaseLogPosition + \", logPosition = \" + logPosition +\n                        \", nowNs = \" + nowNs);\n                }\n                else\n                {\n                    commitLogPosition(lastTerm.leadershipTermId, termBaseLogPosition);\n                    lastTerm = Objects.requireNonNull(findLastTerm());\n                }\n            }\n\n            for (long termId = lastTerm.leadershipTermId + 1; termId < leadershipTermId; termId++)\n            {\n                appendTerm(recordingId, termId, lastTerm.logPosition, timestamp);\n            }\n\n            appendTerm(recordingId, leadershipTermId, lastTerm.logPosition, timestamp);\n            if (NULL_VALUE != logPosition)\n            {\n                commitLogPosition(leadershipTermId, logPosition);\n            }\n        }\n        else\n        {\n            if (NULL_VALUE != logPosition)\n            {\n                commitLogPosition(leadershipTermId, logPosition);\n            }\n        }\n\n        force(fileSyncLevel);\n    }\n\n    private static void validateRecordingId(final long recordingId)\n    {\n        if (NULL_VALUE == recordingId)\n        {\n            throw new ClusterException(\"invalid recordingId=\" + recordingId);\n        }\n    }\n\n    private void validateTermRecordingId(final long recordingId)\n    {\n        validateRecordingId(recordingId);\n\n        if (recordingId != termRecordingId)\n        {\n            if (NULL_VALUE == termRecordingId)\n            {\n                termRecordingId = recordingId;\n            }\n            else\n            {\n                throw new ClusterException(\n                    \"invalid TERM recordingId=\" + recordingId + \", expected recordingId=\" + termRecordingId);\n            }\n        }\n    }\n\n    private boolean restoreInvalidSnapshot(\n        final int snapshotEntryType,\n        final long recordingId,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long timestamp,\n        final int serviceId)\n    {\n        for (int i = invalidSnapshots.size() - 1; i >= 0; i--)\n        {\n            final int entryCacheIndex = invalidSnapshots.getInt(i);\n            final Entry entry = entriesCache.get(entryCacheIndex);\n\n            if (snapshotEntryType == entry.type &&\n                matchesEntry(entry, leadershipTermId, termBaseLogPosition, logPosition, serviceId))\n            {\n                final Entry validatedEntry = new Entry(\n                    recordingId,\n                    leadershipTermId,\n                    termBaseLogPosition,\n                    logPosition,\n                    timestamp,\n                    serviceId,\n                    snapshotEntryType,\n                    null,\n                    true,\n                    entry.position,\n                    entry.entryIndex);\n\n                writeEntryToBuffer(validatedEntry, buffer);\n                persistToStorage(entry, buffer);\n\n                entriesCache.set(entryCacheIndex, validatedEntry);\n                invalidSnapshots.fastUnorderedRemove(i);\n\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    int append(\n        final int entryType,\n        final long recordingId,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long timestamp,\n        final int serviceId,\n        final String endpoint)\n    {\n        final Entry entry = new Entry(\n            recordingId,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            timestamp,\n            serviceId,\n            entryType,\n            endpoint,\n            true,\n            NULL_VALUE,\n            nextEntryIndex);\n\n        writeEntryToBuffer(entry, buffer);\n        persistToStorage(entry, buffer);\n\n        nextEntryIndex++;\n\n        final ArrayList<Entry> entries = this.entriesCache;\n        final int size = entries.size();\n        int index = size;\n\n        for (int i = size - 1; i >= 0; i--)\n        {\n            final Entry e = entries.get(i);\n            if (ENTRY_COMPARATOR.compare(entry, e) < 0)\n            {\n                index--;\n            }\n            else\n            {\n                break;\n            }\n        }\n\n        if (index < size)\n        {\n            entries.add(null);\n            for (int i = size - 1; i >= index; i--)\n            {\n                entries.set(i + 1, entries.get(i));\n            }\n            entries.set(index, entry);\n\n            final Long2LongHashMap.EntryIterator entryIterator = cacheIndexByLeadershipTermIdMap.entrySet().iterator();\n            while (entryIterator.hasNext())\n            {\n                entryIterator.next();\n                if (entryIterator.getLongKey() > leadershipTermId)\n                {\n                    entryIterator.setValue(entryIterator.getLongValue() + 1);\n                }\n            }\n\n            for (int i = invalidSnapshots.size() - 1; i >= 0; i--)\n            {\n                final int snapshotIndex = invalidSnapshots.getInt(i);\n                if (snapshotIndex >= index)\n                {\n                    invalidSnapshots.set(i, snapshotIndex + 1);\n                }\n                else\n                {\n                    break;\n                }\n            }\n        }\n        else\n        {\n            entries.add(entry);\n        }\n\n        return index;\n    }\n\n    private void persistToStorage(final long entryPosition, final int offset, final int length)\n    {\n        byteBuffer.limit(length).position(0);\n        final long position = entryPosition + offset;\n\n        try\n        {\n            if (length != fileChannel.write(byteBuffer, position))\n            {\n                throw new ClusterException(\"failed to write field atomically\");\n            }\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    private void persistToStorage(final Entry entry, final DirectBuffer directBuffer)\n    {\n        final ByteBuffer byteBuffer = directBuffer.byteBuffer();\n        byteBuffer.limit(entry.length()).position(0);\n\n        try\n        {\n            if (NULL_VALUE == entry.position)\n            {\n                final long nextPosition = align(fileChannel.size(), RECORD_ALIGNMENT);\n                entry.position(nextPosition);\n            }\n\n            if (entry.length() != fileChannel.write(byteBuffer, entry.position))\n            {\n                throw new ClusterException(\"failed to write field atomically\");\n            }\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    private int captureEntriesFromBuffer(\n        final long filePosition, final ByteBuffer byteBuffer, final UnsafeBuffer buffer, final ArrayList<Entry> entries)\n    {\n        int consumed = 0; // This is assuming that we have filled the byte buffer from the beginning.\n\n        for (int length = byteBuffer.limit(); consumed + ENDPOINT_OFFSET < length; )\n        {\n            final long position = filePosition + consumed;\n            final int entryType = buffer.getInt(consumed + ENTRY_TYPE_OFFSET, LITTLE_ENDIAN);\n            final int type = entryType & ~ENTRY_TYPE_INVALID_FLAG;\n            final boolean isValid = (entryType & ENTRY_TYPE_INVALID_FLAG) == 0;\n            final int endPointOffset = consumed + ENDPOINT_OFFSET;\n\n            if (ENTRY_TYPE_STANDBY_SNAPSHOT == type &&\n                (endPointOffset + SIZE_OF_INT > length ||\n                endPointOffset + SIZE_OF_INT + buffer.getInt(endPointOffset, LITTLE_ENDIAN) > length))\n            {\n                break;\n            }\n\n            final Entry entry = new Entry(\n                buffer.getLong(consumed + RECORDING_ID_OFFSET, LITTLE_ENDIAN),\n                buffer.getLong(consumed + LEADERSHIP_TERM_ID_OFFSET, LITTLE_ENDIAN),\n                buffer.getLong(consumed + TERM_BASE_LOG_POSITION_OFFSET, LITTLE_ENDIAN),\n                buffer.getLong(consumed + LOG_POSITION_OFFSET, LITTLE_ENDIAN),\n                buffer.getLong(consumed + TIMESTAMP_OFFSET, LITTLE_ENDIAN),\n                buffer.getInt(consumed + SERVICE_ID_OFFSET, LITTLE_ENDIAN),\n                type,\n                ENTRY_TYPE_STANDBY_SNAPSHOT == type ? buffer.getStringAscii(endPointOffset) : null,\n                isValid,\n                position,\n                nextEntryIndex);\n\n            if (NULL_VALUE != entryType)\n            {\n                if (ENTRY_TYPE_TERM == type)\n                {\n                    validateTermRecordingId(entry.recordingId);\n                }\n\n                entries.add(entry);\n            }\n\n            consumed += entry.length();\n            ++nextEntryIndex;\n        }\n\n        byteBuffer.position(consumed);\n        return consumed;\n    }\n\n    private static void syncDirectory(final File dir)\n    {\n        try (FileChannel fileChannel = FileChannel.open(dir.toPath()))\n        {\n            fileChannel.force(true);\n        }\n        catch (final IOException ignore)\n        {\n        }\n    }\n\n    private static void planRecovery(\n        final ArrayList<Snapshot> snapshots,\n        final MutableReference<Log> logRef,\n        final ArrayList<Entry> entries,\n        final AeronArchive archive,\n        final int serviceCount,\n        final long replicatedRecordingId)\n    {\n        if (entries.isEmpty())\n        {\n            if (Aeron.NULL_VALUE != replicatedRecordingId)\n            {\n                final RecordingExtent recordingExtent = new RecordingExtent();\n                if (archive.listRecording(replicatedRecordingId, recordingExtent) == 0)\n                {\n                    throw new ClusterException(\"unknown recording id: \" + replicatedRecordingId);\n                }\n\n                logRef.set(new Log(\n                    replicatedRecordingId,\n                    NULL_VALUE,\n                    0,\n                    0,\n                    recordingExtent.startPosition,\n                    recordingExtent.stopPosition,\n                    recordingExtent.initialTermId,\n                    recordingExtent.termBufferLength,\n                    recordingExtent.mtuLength,\n                    recordingExtent.sessionId));\n            }\n\n            return;\n        }\n\n        int logIndex = -1;\n        int snapshotIndex = -1;\n\n        for (int i = entries.size() - 1; i >= 0; i--)\n        {\n            final Entry entry = entries.get(i);\n            if (-1 == snapshotIndex && isValidSnapshot(entry) &&\n                entry.serviceId == SERVICE_ID)\n            {\n                snapshotIndex = i;\n            }\n            else if (-1 == logIndex && isValidTerm(entry) && NULL_VALUE != entry.recordingId)\n            {\n                logIndex = i;\n            }\n            else if (-1 != snapshotIndex && -1 != logIndex)\n            {\n                break;\n            }\n        }\n\n        if (-1 != snapshotIndex)\n        {\n            addSnapshots(snapshots, entries, serviceCount, snapshotIndex);\n        }\n\n        if (-1 != logIndex)\n        {\n            final Entry entry = entries.get(logIndex);\n            final RecordingExtent recordingExtent = new RecordingExtent();\n            if (archive.listRecording(entry.recordingId, recordingExtent) == 0)\n            {\n                throw new ClusterException(\"unknown recording id: \" + entry.recordingId);\n            }\n\n            final long startPosition = -1 == snapshotIndex ?\n                recordingExtent.startPosition : snapshots.get(0).logPosition;\n\n            logRef.set(new Log(\n                entry.recordingId,\n                entry.leadershipTermId,\n                entry.termBaseLogPosition,\n                entry.logPosition,\n                startPosition,\n                recordingExtent.stopPosition,\n                recordingExtent.initialTermId,\n                recordingExtent.termBufferLength,\n                recordingExtent.mtuLength,\n                recordingExtent.sessionId));\n        }\n    }\n\n    private static boolean isValidTerm(final Entry entry)\n    {\n        return ENTRY_TYPE_TERM == entry.type && entry.isValid;\n    }\n\n    private static boolean matchesEntry(\n        final Entry entry,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final int serviceId)\n    {\n        return entry.leadershipTermId == leadershipTermId &&\n            entry.termBaseLogPosition == termBaseLogPosition &&\n            entry.logPosition == logPosition &&\n            entry.serviceId == serviceId;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/RecordingReplication.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ReplicationParams;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\n\nfinal class RecordingReplication implements AutoCloseable\n{\n    private final long replicationId;\n    private final long stopPosition;\n    private final long progressCheckTimeoutNs;\n    private final long progressCheckIntervalNs;\n    private final String srcArchiveChannel;\n\n    private int recordingPositionCounterId = NULL_COUNTER_ID;\n    private long recordingId = NULL_VALUE;\n    private long position = NULL_POSITION;\n\n    private long progressDeadlineNs;\n    private long progressCheckDeadlineNs;\n    private final AeronArchive archive;\n    private RecordingSignal lastRecordingSignal = RecordingSignal.NULL_VAL;\n\n    private boolean hasReplicationEnded = false;\n    private boolean hasSynced = false;\n    private boolean hasStopped = false;\n\n    RecordingReplication(\n        final AeronArchive archive,\n        final long srcRecordingId,\n        final String srcArchiveChannel,\n        final int srcControlStreamId,\n        final ReplicationParams replicationParams,\n        final long progressCheckTimeoutNs,\n        final long progressCheckIntervalNs,\n        final long nowNs)\n    {\n        this.archive = archive;\n        this.stopPosition = replicationParams.stopPosition();\n        this.progressCheckTimeoutNs = progressCheckTimeoutNs;\n        this.progressCheckIntervalNs = progressCheckIntervalNs;\n        this.progressDeadlineNs = nowNs + progressCheckTimeoutNs;\n        this.progressCheckDeadlineNs = nowNs + progressCheckIntervalNs;\n        this.srcArchiveChannel = srcArchiveChannel;\n\n        replicationId = archive.replicate(\n            srcRecordingId,\n            srcControlStreamId,\n            srcArchiveChannel,\n            replicationParams);\n    }\n\n    int poll(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (hasReplicationEnded)\n        {\n            return workCount;\n        }\n\n        try\n        {\n            if (nowNs >= progressCheckDeadlineNs)\n            {\n                progressCheckDeadlineNs = nowNs + progressCheckIntervalNs;\n                if (pollDstRecordingPosition())\n                {\n                    progressDeadlineNs = nowNs + progressCheckTimeoutNs;\n                }\n                workCount++;\n            }\n\n            if (nowNs >= progressDeadlineNs)\n            {\n                if (NULL_POSITION == stopPosition || position < stopPosition)\n                {\n                    throw new ClusterException(\"log replication has not progressed\", AeronException.Category.WARN);\n                }\n                else\n                {\n                    throw new ClusterException(\"log replication failed to stop\");\n                }\n            }\n\n            return workCount;\n        }\n        catch (final ClusterException ex)\n        {\n            try\n            {\n                close();\n            }\n            catch (final ClusterException ex1)\n            {\n                ex.addSuppressed(ex1);\n            }\n\n            throw ex;\n        }\n    }\n\n    long replicationId()\n    {\n        return replicationId;\n    }\n\n    long position()\n    {\n        return position;\n    }\n\n    long recordingId()\n    {\n        return recordingId;\n    }\n\n    boolean hasReplicationEnded()\n    {\n        return hasReplicationEnded;\n    }\n\n    boolean hasSynced()\n    {\n        return hasSynced;\n    }\n\n    boolean hasStopped()\n    {\n        return hasStopped;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (!hasReplicationEnded)\n        {\n            try\n            {\n                hasReplicationEnded = true;\n                archive.tryStopReplication(replicationId);\n            }\n            catch (final Exception ex)\n            {\n                throw new ClusterException(\"failed to stop log replication\", ex, AeronException.Category.WARN);\n            }\n        }\n    }\n\n    void onSignal(final long correlationId, final long recordingId, final long position, final RecordingSignal signal)\n    {\n        if (correlationId == replicationId)\n        {\n            if (RecordingSignal.EXTEND == signal)\n            {\n                final CountersReader counters = archive.context().aeron().countersReader();\n                recordingPositionCounterId =\n                    RecordingPos.findCounterIdByRecording(counters, recordingId, archive.archiveId());\n            }\n            else if (RecordingSignal.SYNC == signal)\n            {\n                hasSynced = true;\n            }\n            else if (RecordingSignal.REPLICATE_END == signal)\n            {\n                hasReplicationEnded = true;\n            }\n            else if (RecordingSignal.STOP == signal)\n            {\n                if (NULL_POSITION != position)\n                {\n                    this.position = position;\n                }\n                hasStopped = true;\n            }\n            else if (RecordingSignal.DELETE == signal)\n            {\n                throw new ClusterException(\"recording was deleted during replication: \" + this);\n            }\n\n            this.lastRecordingSignal = signal;\n\n            if (NULL_VALUE != recordingId)\n            {\n                this.recordingId = recordingId;\n            }\n\n            if (NULL_POSITION != position)\n            {\n                this.position = position;\n            }\n        }\n    }\n\n    private boolean pollDstRecordingPosition()\n    {\n        if (NULL_COUNTER_ID != recordingPositionCounterId)\n        {\n            final CountersReader counters = archive.context().aeron().countersReader();\n            final long recordingPosition = counters.getCounterValue(recordingPositionCounterId);\n\n            if (RecordingPos.isActive(counters, recordingPositionCounterId, recordingId) &&\n                recordingPosition > position)\n            {\n                position = recordingPosition;\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    String srcArchiveChannel()\n    {\n        return srcArchiveChannel;\n    }\n\n    public String toString()\n    {\n        return \"RecordingReplication{\" +\n            \"replicationId=\" + replicationId +\n            \", stopPosition=\" + stopPosition +\n            \", progressCheckTimeoutNs=\" + progressCheckTimeoutNs +\n            \", progressCheckIntervalNs=\" + progressCheckIntervalNs +\n            \", recordingPositionCounterId=\" + recordingPositionCounterId +\n            \", recordingId=\" + recordingId +\n            \", position=\" + position +\n            \", progressDeadlineNs=\" + progressDeadlineNs +\n            \", progressCheckDeadlineNs=\" + progressCheckDeadlineNs +\n            \", lastRecordingSignal=\" + lastRecordingSignal +\n            \", hasReplicationEnded=\" + hasReplicationEnded +\n            \", hasSynced=\" + hasSynced +\n            \", hasStopped=\" + hasStopped +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ServiceAck.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.cluster.client.ClusterException;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\n\nimport java.util.ArrayDeque;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * State holder for ACKs from each of the {@link io.aeron.cluster.service.ClusteredService}s.\n */\nfinal class ServiceAck\n{\n    static final ServiceAck[] EMPTY_SERVICE_ACKS = new ServiceAck[0];\n\n    private final long ackId;\n    private final long logPosition;\n    private final long relevantId;\n\n    ServiceAck(final long ackId, final long logPosition, final long relevantId)\n    {\n        this.logPosition = logPosition;\n        this.ackId = ackId;\n        this.relevantId = relevantId;\n    }\n\n    long ackId()\n    {\n        return ackId;\n    }\n\n    long logPosition()\n    {\n        return logPosition;\n    }\n\n    long relevantId()\n    {\n        return relevantId;\n    }\n\n    static boolean hasReached(final long logPosition, final long ackId, final ArrayDeque<ServiceAck>[] queues)\n    {\n        for (int serviceId = 0, serviceCount = queues.length; serviceId < serviceCount; serviceId++)\n        {\n            final ServiceAck serviceAck = queues[serviceId].peek();\n            if (null == serviceAck)\n            {\n                return false;\n            }\n\n            if (serviceAck.ackId != ackId || serviceAck.logPosition != logPosition)\n            {\n                throw new ClusterException(\n                    \"ack out of sequence: expected [ackId=\" + ackId + \", logPosition=\" + logPosition + \"] vs \" +\n                    \"received [ackId=\" + serviceAck.ackId + \", logPosition=\" + serviceAck.logPosition +\n                    \", relevantId=\" + serviceAck.relevantId + \", serviceId=\" + serviceId + \"]\");\n            }\n        }\n\n        return true;\n    }\n\n    static void removeHead(final ArrayDeque<ServiceAck>[] queues)\n    {\n        for (final ArrayDeque<ServiceAck> queue : queues)\n        {\n            queue.pollFirst();\n        }\n    }\n\n    static ServiceAck[] pollServiceAcks(final ArrayDeque<ServiceAck>[] serviceAckQueues)\n    {\n        final ServiceAck[] serviceAcks = new ServiceAck[serviceAckQueues.length];\n        for (int id = 0, length = serviceAckQueues.length; id < length; id++)\n        {\n            final ServiceAck serviceAck = serviceAckQueues[id].pollFirst();\n            if (null == serviceAck)\n            {\n                throw new ClusterException(\"invalid ack for serviceId=\" + id);\n            }\n\n            serviceAcks[id] = serviceAck;\n        }\n\n        return serviceAcks;\n    }\n\n    static ServiceAck[] pollServiceAcks(\n        final long logPosition,\n        final int serviceId,\n        final ArrayDeque<ServiceAck>[] serviceAckQueues)\n    {\n        final ServiceAck[] serviceAcks = new ServiceAck[serviceAckQueues.length];\n        for (int id = 0, length = serviceAckQueues.length; id < length; id++)\n        {\n            final ServiceAck serviceAck = serviceAckQueues[id].pollFirst();\n            if (null == serviceAck || serviceAck.logPosition() != logPosition)\n            {\n                throw new ClusterException(\n                    \"invalid ack for serviceId=\" + serviceId + \" logPosition=\" + logPosition + \" \" + serviceAck);\n            }\n\n            serviceAcks[id] = serviceAck;\n        }\n\n        return serviceAcks;\n    }\n\n    static boolean areAllRelevantIdsNonNull(\n        final String message,\n        final ServiceAck[] serviceAcks,\n        final DistinctErrorLog distinctErrorLog)\n    {\n        boolean isSetComplete = true;\n\n        for (int serviceId = serviceAcks.length - 1; serviceId >= 0; serviceId--)\n        {\n            if (NULL_VALUE == serviceAcks[serviceId].relevantId())\n            {\n                distinctErrorLog.record(new ClusterEvent(\"service=\" + serviceId + \" \" + message));\n                isSetComplete = false;\n            }\n        }\n\n        return isSetComplete;\n    }\n\n    @SuppressWarnings({ \"unchecked\", \"rawtypes\" })\n    static ArrayDeque<ServiceAck>[] newArrayOfQueues(final int serviceCount)\n    {\n        final ArrayDeque<ServiceAck>[] queues = new ArrayDeque[serviceCount];\n\n        for (int serviceId = 0; serviceId < serviceCount; serviceId++)\n        {\n            queues[serviceId] = new ArrayDeque<>();\n        }\n\n        return queues;\n    }\n\n    public String toString()\n    {\n        return \"ServiceAck{\" +\n            \"ackId=\" + ackId +\n            \", logPosition=\" + logPosition +\n            \", relevantId=\" + relevantId +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ServiceProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Publication;\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.*;\n\nfinal class ServiceProxy implements AutoCloseable\n{\n    private static final int SEND_ATTEMPTS = 5;\n\n    private final BufferClaim bufferClaim = new BufferClaim();\n    private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n    private final JoinLogEncoder joinLogEncoder = new JoinLogEncoder();\n    private final ClusterMembersResponseEncoder clusterMembersResponseEncoder = new ClusterMembersResponseEncoder();\n    private final ServiceTerminationPositionEncoder serviceTerminationPositionEncoder =\n        new ServiceTerminationPositionEncoder();\n    private final ClusterMembersExtendedResponseEncoder clusterMembersExtendedResponseEncoder =\n        new ClusterMembersExtendedResponseEncoder();\n    private final RequestServiceAckEncoder requestServiceAckEncoder = new RequestServiceAckEncoder();\n    private final ExpandableArrayBuffer expandableArrayBuffer = new ExpandableArrayBuffer();\n    private final Publication publication;\n\n    ServiceProxy(final Publication publication)\n    {\n        this.publication = publication;\n    }\n\n    public void close()\n    {\n        CloseHelper.close(publication);\n    }\n\n    void joinLog(\n        final long logPosition,\n        final long maxLogPosition,\n        final int memberId,\n        final int logSessionId,\n        final int logStreamId,\n        final boolean isStartup,\n        final Cluster.Role role,\n        final String channel,\n        final boolean isStandby)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + JoinLogEncoder.BLOCK_LENGTH +\n            JoinLogEncoder.logChannelHeaderLength() + channel.length();\n        long position;\n\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            position = publication.tryClaim(length, bufferClaim);\n            if (position > 0)\n            {\n                joinLogEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .logPosition(logPosition)\n                    .maxLogPosition(maxLogPosition)\n                    .memberId(memberId)\n                    .logSessionId(logSessionId)\n                    .logStreamId(logStreamId)\n                    .isStartup(isStartup ? BooleanType.TRUE : BooleanType.FALSE)\n                    .role(role.code())\n                    .isStandby(isStandby ? BooleanType.TRUE : BooleanType.FALSE)\n                    .logChannel(channel);\n\n                bufferClaim.commit();\n\n                return;\n            }\n\n            checkResult(position, publication);\n            if (Publication.BACK_PRESSURED == position)\n            {\n                Thread.yield();\n            }\n        }\n        while (--attempts > 0);\n\n        throw new ClusterException(\"failed to send join log request: \" + Publication.errorString(position));\n    }\n\n    void clusterMembersResponse(\n        final long correlationId, final int leaderMemberId, final String activeMembers)\n    {\n        final String passiveFollowers = \"\";\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ClusterMembersResponseEncoder.BLOCK_LENGTH +\n            ClusterMembersResponseEncoder.activeMembersHeaderLength() + activeMembers.length() +\n            ClusterMembersResponseEncoder.passiveFollowersHeaderLength() + passiveFollowers.length();\n\n        long result;\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            result = publication.tryClaim(length, bufferClaim);\n            if (result > 0)\n            {\n                clusterMembersResponseEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .correlationId(correlationId)\n                    .leaderMemberId(leaderMemberId)\n                    .activeMembers(activeMembers)\n                    .passiveFollowers(passiveFollowers);\n\n                bufferClaim.commit();\n\n                return;\n            }\n\n            if (Publication.BACK_PRESSURED == result)\n            {\n                Thread.yield();\n            }\n        }\n        while (--attempts > 0);\n\n        throw new ClusterException(\"failed to send cluster members response: result=\" + result);\n    }\n\n    void clusterMembersExtendedResponse(\n        final long correlationId,\n        final long currentTimeNs,\n        final int leaderMemberId,\n        final int memberId,\n        final ClusterMember[] activeMembers)\n    {\n        clusterMembersExtendedResponseEncoder\n            .wrapAndApplyHeader(expandableArrayBuffer, 0, messageHeaderEncoder)\n            .correlationId(correlationId)\n            .currentTimeNs(currentTimeNs)\n            .leaderMemberId(leaderMemberId)\n            .memberId(memberId);\n\n        final ClusterMembersExtendedResponseEncoder.ActiveMembersEncoder activeMembersEncoder =\n            clusterMembersExtendedResponseEncoder.activeMembersCount(activeMembers.length);\n        for (final ClusterMember member : activeMembers)\n        {\n            activeMembersEncoder.next()\n                .leadershipTermId(member.leadershipTermId())\n                .logPosition(member.logPosition())\n                .timeOfLastAppendNs(member.timeOfLastAppendPositionNs())\n                .memberId(member.id())\n                .ingressEndpoint(member.ingressEndpoint())\n                .consensusEndpoint(member.consensusEndpoint())\n                .logEndpoint(member.logEndpoint())\n                .catchupEndpoint(member.catchupEndpoint())\n                .archiveEndpoint(member.archiveEndpoint());\n        }\n\n        clusterMembersExtendedResponseEncoder.passiveMembersCount(0);\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + clusterMembersExtendedResponseEncoder.encodedLength();\n\n        long result;\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            result = publication.offer(expandableArrayBuffer, 0, length, null);\n            if (result > 0)\n            {\n                return;\n            }\n\n            if (Publication.BACK_PRESSURED == result)\n            {\n                Thread.yield();\n            }\n        }\n        while (--attempts > 0);\n\n        throw new ClusterException(\"failed to send cluster members extended response: result=\" + result);\n    }\n\n    void terminationPosition(final long logPosition, final ErrorHandler errorHandler)\n    {\n        if (!publication.isClosed())\n        {\n            final int length = MessageHeaderDecoder.ENCODED_LENGTH + ServiceTerminationPositionEncoder.BLOCK_LENGTH;\n            long result;\n\n            int attempts = SEND_ATTEMPTS;\n            do\n            {\n                result = publication.tryClaim(length, bufferClaim);\n                if (result > 0)\n                {\n                    serviceTerminationPositionEncoder\n                        .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                        .logPosition(logPosition);\n\n                    bufferClaim.commit();\n\n                    return;\n                }\n\n                if (Publication.BACK_PRESSURED == result)\n                {\n                    Thread.yield();\n                }\n            }\n            while (--attempts > 0);\n\n            errorHandler.onError(new ClusterEvent(\n                \"failed to send service termination position: result=\" + Publication.errorString(result)));\n        }\n    }\n\n    void requestServiceAck(final long logPosition)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + RequestServiceAckEncoder.BLOCK_LENGTH;\n\n        long result;\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            result = publication.tryClaim(length, bufferClaim);\n            if (result > 0)\n            {\n                requestServiceAckEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .logPosition(logPosition);\n\n                bufferClaim.commit();\n\n                return;\n            }\n\n            if (Publication.BACK_PRESSURED == result)\n            {\n                Thread.yield();\n            }\n        }\n        while (--attempts > 0);\n\n        throw new ClusterException(\"failed to send request for service ack: result=\" + Publication.errorString(result));\n    }\n\n    private static void checkResult(final long position, final Publication publication)\n    {\n        if (Publication.NOT_CONNECTED == position)\n        {\n            throw new ClusterException(\"publication is not connected\");\n        }\n\n        if (Publication.CLOSED == position)\n        {\n            throw new ClusterException(\"publication is closed\");\n        }\n\n        if (Publication.MAX_POSITION_EXCEEDED == position)\n        {\n            throw new ClusterException(\"publication at max position: term-length=\" + publication.termBufferLength());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/SessionManager.java",
    "content": "/*\n * Copyright 2014-2024 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.Counter;\nimport io.aeron.Image;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.codecs.BackupQueryDecoder;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.cluster.codecs.HeartbeatRequestDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.StandbySnapshotDecoder;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.AuthorisationService;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.SemanticVersion;\nimport org.agrona.Strings;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.ClusterSession.State.AUTHENTICATED;\nimport static io.aeron.cluster.ClusterSession.State.CHALLENGED;\nimport static io.aeron.cluster.ClusterSession.State.CLOSING;\nimport static io.aeron.cluster.ClusterSession.State.CONNECTED;\nimport static io.aeron.cluster.ClusterSession.State.CONNECTING;\nimport static io.aeron.cluster.ClusterSession.State.INIT;\nimport static io.aeron.cluster.ClusterSession.State.INVALID;\nimport static io.aeron.cluster.ClusterSession.State.REJECTED;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SESSION_INVALID_VERSION_MSG;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SESSION_LIMIT_MSG;\nimport static io.aeron.cluster.ConsensusModuleAgent.logAppendSessionClose;\nimport static io.aeron.cluster.ConsensusModuleAgent.logAppendSessionOpen;\nimport static io.aeron.cluster.client.AeronCluster.Configuration.PROTOCOL_SEMANTIC_VERSION;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\n\nclass SessionManager\n{\n    private final Long2ObjectHashMap<ClusterSession> sessionByIdMap = new Long2ObjectHashMap<>();\n    private final ArrayList<ClusterSession> sessions = new ArrayList<>();\n\n    private final ArrayList<ClusterSession> pendingUserSessions = new ArrayList<>();\n    private final ArrayList<ClusterSession> rejectedUserSessions = new ArrayList<>();\n    private final ArrayList<ClusterSession> redirectUserSessions = new ArrayList<>();\n\n    private final ArrayList<ClusterSession> pendingBackupSessions = new ArrayList<>();\n    private final ArrayList<ClusterSession> rejectedBackupSessions = new ArrayList<>();\n\n    private final ArrayDeque<ClusterSession> uncommittedClosedSessions = new ArrayDeque<>();\n    private final ArrayDeque<List<StandbySnapshotEntry>> pendingStandbySnapshotNotifications = new ArrayDeque<>();\n    private final ArrayDeque<DelayedStandbySnapshotNotification> delayedStandbySnapshotNotifications =\n        new ArrayDeque<>();\n\n    private final int memberId;\n    private final ClusterClock clusterClock;\n    private final ClusterMember[] activeMembers;\n    private final ClusterSessionProxy sessionProxy;\n    private final EgressPublisher egressPublisher;\n    private final TimeUnit clusterTimeUnit;\n    private final Aeron aeron;\n    private final long sessionTimeoutNs;\n    private final CountedErrorHandler errorHandler;\n    private final Counter timedOutCounter;\n    private final DistinctErrorLog errorLog;\n    private final boolean acceptStandbySnapshots;\n    private final Counter standbySnapshotCounter;\n    private final Authenticator authenticator;\n    private final AuthorisationService authorisationService;\n    private final RecordingLog recordingLog;\n    private final LogPublisher logPublisher;\n    private final ConsensusPublisher consensusPublisher;\n    private final ConsensusModuleExtension consensusModuleExtension;\n    private final int commitPositionCounterId;\n    private final int clusterId;\n    private final int maxConcurrentSessions;\n    private final int serviceCount;\n    private final long standbySnapshotNotificationProcessingDelayNs;\n    private final MutableDirectBuffer tempBuffer = new ExpandableArrayBuffer();\n\n    private long nextSessionId = 1;\n    private long nextCommittedSessionId = nextSessionId;\n\n    SessionManager(\n        final ClusterMember[] activeMembers,\n        final int memberId,\n        final ClusterClock clusterClock,\n        final EgressPublisher egressPublisher,\n        final Aeron aeron,\n        final long sessionTimeoutNs,\n        final CountedErrorHandler errorHandler,\n        final Counter timedOutCounter,\n        final DistinctErrorLog errorLog,\n        final boolean acceptStandbySnapshots,\n        final Counter standbySnapshotCounter,\n        final Authenticator authenticator,\n        final AuthorisationService authorisationService,\n        final RecordingLog recordingLog,\n        final LogPublisher logPublisher,\n        final ConsensusPublisher consensusPublisher,\n        final ConsensusModuleExtension consensusModuleExtension,\n        final int commitPositionCounterId,\n        final int clusterId,\n        final int maxConcurrentSessions,\n        final int serviceCount,\n        final long standbySnapshotNotificationProcessingDelayNs)\n    {\n        this.activeMembers = activeMembers;\n        this.memberId = memberId;\n        this.clusterClock = clusterClock;\n        this.sessionProxy = new ClusterSessionProxy(egressPublisher);\n        this.egressPublisher = egressPublisher;\n        this.clusterTimeUnit = clusterClock.timeUnit();\n        this.aeron = aeron;\n        this.sessionTimeoutNs = sessionTimeoutNs;\n        this.errorHandler = errorHandler;\n        this.timedOutCounter = timedOutCounter;\n        this.errorLog = errorLog;\n        this.acceptStandbySnapshots = acceptStandbySnapshots;\n        this.standbySnapshotCounter = standbySnapshotCounter;\n        this.authenticator = authenticator;\n        this.authorisationService = authorisationService;\n        this.recordingLog = recordingLog;\n        this.logPublisher = logPublisher;\n        this.consensusPublisher = consensusPublisher;\n        this.consensusModuleExtension = consensusModuleExtension;\n        this.commitPositionCounterId = commitPositionCounterId;\n        this.clusterId = clusterId;\n        this.maxConcurrentSessions = maxConcurrentSessions;\n        this.serviceCount = serviceCount;\n        this.standbySnapshotNotificationProcessingDelayNs = standbySnapshotNotificationProcessingDelayNs;\n    }\n\n    SessionManager(\n        final ConsensusModule.Context ctx,\n        final ClusterMember[] activeMembers,\n        final ConsensusPublisher consensusPublisher)\n    {\n        this(\n            activeMembers,\n            ctx.clusterMemberId(),\n            ctx.clusterClock(),\n            ctx.egressPublisher(),\n            ctx.aeron(),\n            ctx.sessionTimeoutNs(),\n            ctx.countedErrorHandler(),\n            ctx.timedOutClientCounter(),\n            ctx.errorLog(),\n            ctx.acceptStandbySnapshots(),\n            ctx.standbySnapshotCounter(),\n            ctx.authenticatorSupplier().get(),\n            ctx.authorisationServiceSupplier().get(),\n            ctx.recordingLog(),\n            ctx.logPublisher(),\n            consensusPublisher,\n            ctx.consensusModuleExtension(),\n            ctx.commitPositionCounter().id(),\n            ctx.clusterId(),\n            ctx.maxConcurrentSessions(),\n            ctx.serviceCount(),\n            ctx.standbySnapshotNotificationProcessingDelayNs());\n    }\n\n    ClusterSession findBySessionId(final long clusterSessionId)\n    {\n        return sessionByIdMap.get(clusterSessionId);\n    }\n\n    /**\n     * Get all the sessions, used by cluster standby for export prior to transition.\n     *\n     * @return all the cluster sessions.\n     */\n    Iterable<ClusterSession> findAll()\n    {\n        return sessions;\n    }\n\n    AuthorisationService authorisationService()\n    {\n        return authorisationService;\n    }\n\n    long nextCommittedSessionId()\n    {\n        return nextCommittedSessionId;\n    }\n\n    void closeSession(final ClusterSession session)\n    {\n        final long sessionId = session.id();\n\n        sessionByIdMap.remove(sessionId);\n        for (int i = sessions.size() - 1; i >= 0; i--)\n        {\n            if (sessions.get(i).id() == sessionId)\n            {\n                sessions.remove(i);\n                break;\n            }\n        }\n\n        session.close(aeron, errorHandler, \"closed\");\n\n        if (null != consensusModuleExtension && null != session.closeReason())\n        {\n            consensusModuleExtension.onSessionClosed(sessionId, session.closeReason());\n        }\n    }\n\n    void onSessionConnect(\n        final long correlationId,\n        final int responseStreamId,\n        final int version,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final String clientInfo,\n        final Header header,\n        final Cluster.Role role,\n        final String ingressEndpoints)\n    {\n        final long clusterSessionId = Cluster.Role.LEADER == role ? nextSessionId++ : NULL_VALUE;\n        final ClusterSession session = new ClusterSession(\n            memberId,\n            clusterSessionId,\n            responseStreamId,\n            responseChannel,\n            sessionInfo(clientInfo, header));\n\n        session.asyncConnect(aeron, tempBuffer, clusterId);\n        final long nowNs = clusterClock.timeNanos();\n        session.lastActivityNs(nowNs, correlationId);\n\n        if (Cluster.Role.LEADER != role)\n        {\n            session.redirect(ingressEndpoints);\n            redirectUserSessions.add(session);\n        }\n        else\n        {\n            if (AeronCluster.Configuration.PROTOCOL_MAJOR_VERSION != SemanticVersion.major(version))\n            {\n                final String detail = SESSION_INVALID_VERSION_MSG + \" \" + SemanticVersion.toString(version) +\n                    \", cluster is \" + SemanticVersion.toString(PROTOCOL_SEMANTIC_VERSION);\n                session.reject(EventCode.ERROR, detail, errorLog);\n                rejectedUserSessions.add(session);\n            }\n            else if (pendingUserSessions.size() + sessions.size() >= maxConcurrentSessions)\n            {\n                session.reject(EventCode.ERROR, SESSION_LIMIT_MSG, errorLog);\n                rejectedUserSessions.add(session);\n            }\n            else\n            {\n                session.linkIngressImage(header);\n                authenticator.onConnectRequest(session.id(), encodedCredentials, NANOSECONDS.toMillis(nowNs));\n                pendingUserSessions.add(session);\n            }\n        }\n    }\n\n    void onSessionClose(final long leadershipTermId, final long clusterSessionId)\n    {\n        final ClusterSession session = sessionByIdMap.get(clusterSessionId);\n        if (null != session && session.isOpen())\n        {\n            session.closing(CloseReason.CLIENT_ACTION);\n            session.disconnect(aeron, errorHandler);\n\n            final long timestamp = clusterClock.time();\n            if (logPublisher.appendSessionClose(memberId, session, leadershipTermId, timestamp, clusterTimeUnit))\n            {\n                logAppendSessionClose(\n                    memberId, session.id(), session.closeReason(), leadershipTermId, timestamp, clusterTimeUnit);\n                session.closedLogPosition(logPublisher.position());\n                uncommittedClosedSessions.addLast(session);\n                closeSession(session);\n            }\n        }\n    }\n\n    void onStandbySnapshot(\n        final long correlationId,\n        final int version,\n        final List<StandbySnapshotEntry> standbySnapshotEntries,\n        final int responseStreamId,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final Header header)\n    {\n        onBackupAction(\n            ClusterSession.Action.STANDBY_SNAPSHOT,\n            standbySnapshotEntries,\n            correlationId,\n            responseStreamId,\n            version,\n            responseChannel,\n            encodedCredentials,\n            header);\n    }\n\n    void onBackupQuery(\n        final long correlationId,\n        final int responseStreamId,\n        final int version,\n        final long logPosition,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final Header header)\n    {\n        onBackupAction(\n            ClusterSession.Action.BACKUP,\n            NULL_VALUE == logPosition ? null : logPosition,\n            correlationId,\n            responseStreamId,\n            version,\n            responseChannel,\n            encodedCredentials,\n            header\n        );\n    }\n\n    void onHeartbeatRequest(\n        final long correlationId,\n        final int responseStreamId,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final Header header)\n    {\n        onBackupAction(\n            ClusterSession.Action.HEARTBEAT,\n            null,\n            correlationId,\n            responseStreamId,\n            PROTOCOL_SEMANTIC_VERSION,\n            responseChannel,\n            encodedCredentials,\n            header);\n    }\n\n    private void onBackupAction(\n        final ClusterSession.Action action,\n        final Object requestInput,\n        final long correlationId,\n        final int responseStreamId,\n        final int version,\n        final String responseChannel,\n        final byte[] encodedCredentials,\n        final Header header)\n    {\n        final ClusterSession session = new ClusterSession(\n            memberId,\n            NULL_VALUE,\n            responseStreamId,\n            responseChannel,\n            sessionInfo(action.name(), header));\n\n        final long nowNs = clusterClock.timeNanos();\n\n        session.action(action);\n        session.asyncConnect(aeron, tempBuffer, clusterId);\n        session.lastActivityNs(nowNs, correlationId);\n        session.requestInput(requestInput);\n\n        if (AeronCluster.Configuration.PROTOCOL_MAJOR_VERSION == SemanticVersion.major(version))\n        {\n            authenticator.onConnectRequest(session.id(), encodedCredentials, NANOSECONDS.toMillis(nowNs));\n            pendingBackupSessions.add(session);\n        }\n        else\n        {\n            final String detail = SESSION_INVALID_VERSION_MSG + \" \" + SemanticVersion.toString(version) +\n                \", cluster=\" + SemanticVersion.toString(PROTOCOL_SEMANTIC_VERSION);\n            session.reject(EventCode.ERROR, detail, errorLog);\n            rejectedBackupSessions.add(session);\n        }\n    }\n\n    void onChallengeResponseForBackupSession(\n        final long correlationId,\n        final long clusterSessionId,\n        final byte[] encodedCredentials)\n    {\n        onChallengeResponseForSession(pendingBackupSessions, correlationId, clusterSessionId, encodedCredentials);\n    }\n\n    void onChallengeResponseForUserSession(\n        final long correlationId,\n        final long clusterSessionId,\n        final byte[] encodedCredentials)\n    {\n        onChallengeResponseForSession(pendingUserSessions, correlationId, clusterSessionId, encodedCredentials);\n    }\n\n    private void onChallengeResponseForSession(\n        final ArrayList<ClusterSession> pendingSessions,\n        final long correlationId,\n        final long clusterSessionId,\n        final byte[] encodedCredentials)\n    {\n        for (int lastIndex = pendingSessions.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final ClusterSession session = pendingSessions.get(i);\n\n            if (session.id() == clusterSessionId && session.state() == CHALLENGED)\n            {\n                final long nowNs = clusterClock.timeNanos();\n                session.lastActivityNs(nowNs, correlationId);\n                authenticator.onChallengeResponse(clusterSessionId, encodedCredentials, NANOSECONDS.toMillis(nowNs));\n                break;\n            }\n        }\n    }\n\n    void disconnectSessions()\n    {\n        for (int i = 0, size = sessions.size(); i < size; i++)\n        {\n            final ClusterSession session = sessions.get(i);\n            session.unlinkIngressImage();\n            session.disconnect(aeron, errorHandler);\n        }\n    }\n\n    void onServiceCloseSession(final long clusterSessionId, final boolean isActiveLeader, final long leadershipTermId)\n    {\n        final ClusterSession session = sessionByIdMap.get(clusterSessionId);\n        if (null != session)\n        {\n            session.closing(CloseReason.SERVICE_ACTION);\n\n            if (isActiveLeader)\n            {\n                final long timestamp = clusterClock.time();\n                if (logPublisher.appendSessionClose(memberId, session, leadershipTermId, timestamp, clusterTimeUnit))\n                {\n                    logAppendSessionClose(\n                        memberId, session.id(), session.closeReason(), leadershipTermId, timestamp, clusterTimeUnit);\n                    final String msg = CloseReason.SERVICE_ACTION.name();\n                    egressPublisher.sendEvent(session, leadershipTermId, memberId, EventCode.CLOSED, msg);\n                    session.closedLogPosition(logPublisher.position());\n                    uncommittedClosedSessions.addLast(session);\n                    closeSession(session);\n                }\n            }\n        }\n    }\n\n    void onReplaySessionOpen(\n        final long logPosition,\n        final long correlationId,\n        final long clusterSessionId,\n        final long timestamp,\n        final int responseStreamId,\n        final String responseChannel)\n    {\n        final ClusterSession session = new ClusterSession(\n            memberId, clusterSessionId, responseStreamId, responseChannel, responseChannel);\n        session.open(logPosition);\n        session.lastActivityNs(clusterClock.convertToNanos(timestamp), correlationId);\n\n        addSession(session);\n        if (clusterSessionId >= nextSessionId)\n        {\n            nextSessionId = clusterSessionId + 1;\n            nextCommittedSessionId = nextSessionId;\n        }\n\n        if (null != consensusModuleExtension)\n        {\n            consensusModuleExtension.onSessionOpened(clusterSessionId);\n        }\n    }\n\n    void onReplaySessionClose(final long clusterSessionId, final CloseReason closeReason)\n    {\n        final ClusterSession session = sessionByIdMap.get(clusterSessionId);\n        if (null != session)\n        {\n            session.closing(closeReason);\n            closeSession(session);\n        }\n    }\n\n    int processAllPendingSessions(\n        final long nowNs,\n        final int leaderMemberId,\n        final long leadershipTermId)\n    {\n        int workCount = 0;\n        workCount += processPendingSessions(\n            pendingUserSessions,\n            rejectedUserSessions,\n            nowNs,\n            leaderMemberId,\n            leadershipTermId);\n\n        workCount += processPendingSessions(\n            pendingBackupSessions,\n            rejectedBackupSessions,\n            nowNs,\n            leaderMemberId,\n            leadershipTermId);\n\n        return workCount;\n    }\n\n    void onLoadClusterSession(\n        final long clusterSessionId,\n        final long correlationId,\n        final long openedPosition,\n        final long timeOfLastActivity,\n        final CloseReason closeReason,\n        final int responseStreamId,\n        final String responseChannel)\n    {\n        final ClusterSession session = new ClusterSession(\n            memberId, clusterSessionId, responseStreamId, responseChannel, responseChannel);\n\n        session.loadSnapshotState(correlationId, openedPosition, timeOfLastActivity, closeReason);\n\n        addSession(session);\n\n        if (clusterSessionId >= nextSessionId)\n        {\n            nextSessionId = clusterSessionId + 1;\n            nextCommittedSessionId = nextSessionId;\n        }\n    }\n\n    void loadNextSessionId(final long nextSessionId)\n    {\n        this.nextSessionId = nextSessionId;\n        this.nextCommittedSessionId = nextSessionId;\n    }\n\n    int processPendingBackupSessions(\n        final long nowNs,\n        final int leaderMemberId,\n        final long leadershipTermId)\n    {\n        return processPendingSessions(\n            pendingBackupSessions,\n            rejectedBackupSessions,\n            nowNs,\n            leaderMemberId,\n            leadershipTermId);\n    }\n\n    @SuppressWarnings(\"checkstyle:methodlength\")\n    int processPendingSessions(\n        final ArrayList<ClusterSession> pendingSessions,\n        final ArrayList<ClusterSession> rejectedSessions,\n        final long nowNs,\n        final int leaderMemberId,\n        final long leadershipTermId)\n    {\n        int workCount = 0;\n\n        for (int lastIndex = pendingSessions.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final ClusterSession session = pendingSessions.get(i);\n\n            if (session.state() == INVALID)\n            {\n                ArrayListUtil.fastUnorderedRemove(pendingSessions, i, lastIndex--);\n                session.close(aeron, errorHandler, \"invalid session\");\n                continue;\n            }\n\n            if (nowNs > (session.timeOfLastActivityNs() + sessionTimeoutNs) && session.state() != INIT)\n            {\n                ArrayListUtil.fastUnorderedRemove(pendingSessions, i, lastIndex--);\n                session.close(aeron, errorHandler, \"session timed out\");\n                timedOutCounter.incrementRelease();\n                continue;\n            }\n\n            if (session.state() == INIT || session.state() == CONNECTING || session.state() == CONNECTED)\n            {\n                if (session.isResponsePublicationConnected(aeron, nowNs))\n                {\n                    session.state(CONNECTED, \"connected\");\n                    authenticator.onConnectedSession(sessionProxy.session(session), clusterClock.timeMillis());\n                }\n            }\n\n            if (session.state() == CHALLENGED)\n            {\n                if (session.isResponsePublicationConnected(aeron, nowNs))\n                {\n                    authenticator.onChallengedSession(sessionProxy.session(session), clusterClock.timeMillis());\n                }\n            }\n\n            if (session.state() == AUTHENTICATED)\n            {\n                switch (session.action())\n                {\n                    case CLIENT:\n                    {\n                        if (session.appendSessionToLogAndSendOpen(\n                            logPublisher, egressPublisher, leadershipTermId, memberId, nowNs, clusterClock.time()))\n                        {\n                            logAppendSessionOpen(\n                                memberId,\n                                session.id(),\n                                leadershipTermId,\n                                session.openedLogPosition(),\n                                nowNs,\n                                clusterTimeUnit);\n                            if (session.id() >= nextCommittedSessionId)\n                            {\n                                nextCommittedSessionId = session.id() + 1;\n                            }\n                            ArrayListUtil.fastUnorderedRemove(pendingSessions, i, lastIndex--);\n                            addSession(session);\n                            workCount += 1;\n                            if (null != consensusModuleExtension)\n                            {\n                                consensusModuleExtension.onSessionOpened(session.id());\n                            }\n                        }\n                        break;\n                    }\n\n                    case BACKUP:\n                    {\n                        if (!authorisationService.isAuthorised(\n                            MessageHeaderDecoder.SCHEMA_ID,\n                            BackupQueryDecoder.TEMPLATE_ID,\n                            null,\n                            session.encodedPrincipal()))\n                        {\n                            session.reject(\n                                EventCode.AUTHENTICATION_REJECTED,\n                                \"Not authorised for BackupQuery\",\n                                errorLog);\n                            break;\n                        }\n\n                        final Long logPositionOrNull = (Long)session.requestInput();\n                        final long logPosition = null == logPositionOrNull ? Long.MAX_VALUE : logPositionOrNull;\n                        final RecordingLog.Entry entry = recordingLog.findLastTerm();\n                        if (null != entry && consensusPublisher.backupResponse(\n                            session,\n                            commitPositionCounterId,\n                            leaderMemberId,\n                            memberId,\n                            entry,\n                            ClusterMember.encodeAsString(activeMembers),\n                            recordingLog.findSnapshotAtOrBeforeOrLowest(logPosition, serviceCount)))\n                        {\n                            ArrayListUtil.fastUnorderedRemove(pendingSessions, i, lastIndex--);\n                            session.close(aeron, errorHandler, \"done\");\n                            workCount += 1;\n                        }\n                        break;\n                    }\n\n                    case HEARTBEAT:\n                    {\n                        if (!authorisationService.isAuthorised(\n                            MessageHeaderDecoder.SCHEMA_ID,\n                            HeartbeatRequestDecoder.TEMPLATE_ID,\n                            null,\n                            session.encodedPrincipal()))\n                        {\n                            session.reject(\n                                EventCode.AUTHENTICATION_REJECTED,\n                                \"Not authorised for Heartbeat\",\n                                errorLog);\n                            break;\n                        }\n\n                        if (consensusPublisher.heartbeatResponse(session))\n                        {\n                            ArrayListUtil.fastUnorderedRemove(pendingSessions, i, lastIndex--);\n                            session.close(aeron, errorHandler, \"done\");\n                            workCount += 1;\n                        }\n                        break;\n                    }\n\n                    case STANDBY_SNAPSHOT:\n                    {\n                        if (!acceptStandbySnapshots)\n                        {\n                            session.reject(\n                                EventCode.ERROR,\n                                \"Not configured to accept StandbySnapshot\",\n                                errorLog);\n                            break;\n                        }\n\n                        if (!authorisationService.isAuthorised(\n                            MessageHeaderDecoder.SCHEMA_ID,\n                            StandbySnapshotDecoder.TEMPLATE_ID,\n                            null,\n                            session.encodedPrincipal()))\n                        {\n                            session.reject(\n                                EventCode.AUTHENTICATION_REJECTED,\n                                \"Not authorised for StandbySnapshot\",\n                                errorLog);\n                            break;\n                        }\n\n                        @SuppressWarnings(\"unchecked\")\n                        final List<StandbySnapshotEntry> standbySnapshotEntries =\n                            (List<StandbySnapshotEntry>)session.requestInput();\n\n                        pendingStandbySnapshotNotifications.add(standbySnapshotEntries);\n\n                        ArrayListUtil.fastUnorderedRemove(pendingSessions, i, lastIndex--);\n                        session.close(aeron, errorHandler, \"done\");\n                        workCount += 1;\n                    }\n                }\n            }\n            else if (session.state() == REJECTED)\n            {\n                ArrayListUtil.fastUnorderedRemove(pendingSessions, i, lastIndex--);\n                rejectedSessions.add(session);\n            }\n        }\n\n        return workCount;\n    }\n\n    int processPendingStandbySnapshotNotifications(final long commitPosition, final long nowNs)\n    {\n        int workCount = 0;\n\n        if (!pendingStandbySnapshotNotifications.isEmpty())\n        {\n            final Iterator<List<StandbySnapshotEntry>> iterator = pendingStandbySnapshotNotifications.iterator();\n            while (iterator.hasNext())\n            {\n                final List<StandbySnapshotEntry> standbySnapshotEntries = iterator.next();\n\n                if (standbySnapshotEntries.isEmpty())\n                {\n                    iterator.remove();\n                    continue;\n                }\n\n                final long snapshotLogPosition = standbySnapshotEntries.get(0).logPosition();\n\n                if (snapshotLogPosition > commitPosition)\n                {\n                    continue;\n                }\n\n                if (standbySnapshotNotificationProcessingDelayNs > 0)\n                {\n                    delayedStandbySnapshotNotifications.add(new DelayedStandbySnapshotNotification(\n                        nowNs + standbySnapshotNotificationProcessingDelayNs, standbySnapshotEntries));\n                }\n                else\n                {\n                    appendStandbySnapshot(standbySnapshotEntries);\n                }\n\n                iterator.remove();\n                workCount++;\n            }\n        }\n\n        while (!delayedStandbySnapshotNotifications.isEmpty() &&\n            0 <= nowNs - delayedStandbySnapshotNotifications.peek().deadlineNs())\n        {\n            appendStandbySnapshot(delayedStandbySnapshotNotifications.remove().snapshotEntries());\n\n            workCount++;\n        }\n\n        return workCount;\n    }\n\n    private void appendStandbySnapshot(final List<StandbySnapshotEntry> entries)\n    {\n        for (final StandbySnapshotEntry standbySnapshotEntry : entries)\n        {\n            ConsensusModuleAgent.logStandbySnapshotNotification(\n                memberId,\n                standbySnapshotEntry.recordingId(),\n                standbySnapshotEntry.leadershipTermId(),\n                standbySnapshotEntry.termBaseLogPosition(),\n                standbySnapshotEntry.logPosition(),\n                standbySnapshotEntry.timestamp(),\n                clusterTimeUnit,\n                standbySnapshotEntry.serviceId(),\n                standbySnapshotEntry.archiveEndpoint());\n\n            recordingLog.appendStandbySnapshot(\n                standbySnapshotEntry.recordingId(),\n                standbySnapshotEntry.leadershipTermId(),\n                standbySnapshotEntry.termBaseLogPosition(),\n                standbySnapshotEntry.logPosition(),\n                standbySnapshotEntry.timestamp(),\n                standbySnapshotEntry.serviceId(),\n                standbySnapshotEntry.archiveEndpoint());\n        }\n\n        standbySnapshotCounter.increment();\n    }\n\n    int checkSessions(\n        final long nowNs,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final String ingressEndpoints)\n    {\n        final ArrayList<ClusterSession> sessions = this.sessions;\n        int workCount = 0;\n\n        for (int i = sessions.size() - 1; i >= 0; i--)\n        {\n            final ClusterSession session = sessions.get(i);\n\n            if (nowNs > (session.timeOfLastActivityNs() + sessionTimeoutNs))\n            {\n                switch (session.state())\n                {\n                    case OPEN:\n                    {\n                        session.closing(CloseReason.TIMEOUT);\n\n                        final long timestamp = clusterClock.time();\n                        if (logPublisher.appendSessionClose(\n                            memberId, session, leadershipTermId, timestamp, clusterTimeUnit))\n                        {\n                            logAppendSessionClose(\n                                memberId,\n                                session.id(),\n                                session.closeReason(),\n                                leadershipTermId,\n                                timestamp,\n                                clusterTimeUnit);\n                            final String msg = session.closeReason().name();\n                            egressPublisher.sendEvent(session, leadershipTermId, memberId, EventCode.CLOSED, msg);\n                            session.closedLogPosition(logPublisher.position());\n                            uncommittedClosedSessions.addLast(session);\n                            timedOutCounter.incrementRelease();\n                            closeSession(session);\n                        }\n                        workCount++;\n                        break;\n                    }\n\n                    case CLOSING:\n                    {\n                        final long timestamp = clusterClock.time();\n                        if (logPublisher.appendSessionClose(\n                            memberId, session, leadershipTermId, timestamp, clusterTimeUnit))\n                        {\n                            logAppendSessionClose(\n                                memberId,\n                                session.id(),\n                                session.closeReason(),\n                                leadershipTermId,\n                                timestamp,\n                                clusterTimeUnit);\n                            final String msg = session.closeReason().name();\n                            egressPublisher.sendEvent(session, leadershipTermId, memberId, EventCode.CLOSED, msg);\n                            session.closedLogPosition(logPublisher.position());\n                            uncommittedClosedSessions.addLast(session);\n                            if (session.closeReason() == CloseReason.TIMEOUT)\n                            {\n                                timedOutCounter.incrementRelease();\n                            }\n                            closeSession(session);\n                            workCount++;\n                        }\n                        break;\n                    }\n\n                    default:\n                    {\n                        closeSession(session);\n                        workCount++;\n                        break;\n                    }\n                }\n            }\n            else if (session.hasOpenEventPending())\n            {\n                workCount += session.sendSessionOpenEvent(egressPublisher, leadershipTermId, memberId);\n            }\n            else if (session.hasNewLeaderEventPending())\n            {\n                workCount += sendNewLeaderEvent(session, leadershipTermId, leaderMemberId, ingressEndpoints);\n            }\n        }\n\n        return workCount;\n    }\n\n    void prepareSessionsForNewTerm(final boolean isStartup)\n    {\n        if (isStartup)\n        {\n            for (int i = 0, size = sessions.size(); i < size; i++)\n            {\n                final ClusterSession session = sessions.get(i);\n                if (session.state() == ClusterSession.State.OPEN)\n                {\n                    session.closing(CloseReason.TIMEOUT);\n                }\n            }\n        }\n        else\n        {\n            for (int i = 0, size = sessions.size(); i < size; i++)\n            {\n                final ClusterSession session = sessions.get(i);\n                if (session.state() == ClusterSession.State.OPEN)\n                {\n                    session.connect(errorHandler, aeron, tempBuffer, clusterId);\n                }\n            }\n\n            final long nowNs = clusterClock.timeNanos();\n            for (int i = 0, size = sessions.size(); i < size; i++)\n            {\n                final ClusterSession session = sessions.get(i);\n                if (session.state() == ClusterSession.State.OPEN)\n                {\n                    session.timeOfLastActivityNs(nowNs);\n                    session.hasNewLeaderEventPending(true);\n                }\n            }\n        }\n    }\n\n    int sendRedirects(\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final long nowNs)\n    {\n        return sendForAll(redirectUserSessions, leadershipTermId, leaderMemberId, nowNs);\n    }\n\n    int sendRejections(\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final long nowNs)\n    {\n        int workCount = 0;\n\n        workCount += sendForAll(rejectedUserSessions, leadershipTermId, leaderMemberId, nowNs);\n        workCount += sendForAll(rejectedBackupSessions, leadershipTermId, leaderMemberId, nowNs);\n\n        return workCount;\n    }\n\n    private int sendForAll(\n        final ArrayList<ClusterSession> sessions,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final long nowNs)\n    {\n        int workCount = 0;\n\n        for (int lastIndex = sessions.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final ClusterSession session = sessions.get(i);\n            final EventCode eventCode = session.eventCode();\n            final String detail = session.responseDetail();\n\n            if (session.isResponsePublicationConnected(aeron, nowNs) &&\n                egressPublisher.sendEvent(session, leadershipTermId, leaderMemberId, eventCode, detail))\n            {\n                ArrayListUtil.fastUnorderedRemove(sessions, i, lastIndex--);\n                session.close(aeron, errorHandler, eventCode.name());\n                workCount++;\n            }\n            else if (session.hasTimedOut(nowNs, sessionTimeoutNs))\n            {\n                ArrayListUtil.fastUnorderedRemove(sessions, i, lastIndex--);\n                session.close(aeron, errorHandler, \"session timed out\");\n                workCount++;\n            }\n            else if (session.state() == INVALID)\n            {\n                ArrayListUtil.fastUnorderedRemove(sessions, i, lastIndex--);\n                session.close(aeron, errorHandler, \"invalid\");\n                workCount++;\n            }\n        }\n\n        return workCount;\n    }\n\n    void clearSessionsAfter(final long logPosition, final long leadershipTermId)\n    {\n        for (int i = sessions.size() - 1; i >= 0; i--)\n        {\n            final ClusterSession session = sessions.get(i);\n            if (session.openedLogPosition() > logPosition)\n            {\n                egressPublisher.sendEvent(session, leadershipTermId, memberId, EventCode.CLOSED, \"election\");\n                closeSession(session);\n            }\n        }\n\n        for (final ClusterSession session : pendingUserSessions)\n        {\n            egressPublisher.sendEvent(session, leadershipTermId, memberId, EventCode.CLOSED, \"election\");\n            session.close(aeron, errorHandler, \"election\");\n        }\n\n        pendingUserSessions.clear();\n    }\n\n    void closeSessions(final CountedErrorHandler errorHandler, final ConsensusModuleAgent consensusModuleAgent)\n    {\n        for (final ClusterSession session : sessionByIdMap.values())\n        {\n            session.close(aeron, errorHandler, \"Cluster node terminated\");\n        }\n    }\n\n    void sweepUncommittedSessions(final long commitPosition)\n    {\n        while (true)\n        {\n            final ClusterSession clusterSession = uncommittedClosedSessions.peekFirst();\n            if (null == clusterSession || clusterSession.closedLogPosition() > commitPosition)\n            {\n                break;\n            }\n\n            uncommittedClosedSessions.pollFirst();\n        }\n    }\n\n    void restoreUncommittedSessions(final long commitPosition)\n    {\n        ClusterSession session;\n        while (null != (session = uncommittedClosedSessions.pollFirst()))\n        {\n            if (session.closedLogPosition() > commitPosition)\n            {\n                session.closedLogPosition(NULL_POSITION);\n                session.state(CLOSING, \"uncommitted session\");\n                addSession(session);\n            }\n        }\n    }\n\n    void updateTimeOfLastActivity()\n    {\n        final long nowNs = clusterClock.timeNanos();\n        for (int i = 0, size = sessions.size(); i < size; i++)\n        {\n            sessions.get(i).timeOfLastActivityNs(nowNs);\n        }\n    }\n\n    void snapshotSessions(final ConsensusModuleSnapshotTaker snapshotTaker)\n    {\n        for (int i = 0, size = sessions.size(); i < size; i++)\n        {\n            final ClusterSession session = sessions.get(i);\n            final ClusterSession.State sessionState = session.state();\n\n            if (sessionState == ClusterSession.State.OPEN || sessionState == CLOSING)\n            {\n                snapshotTaker.snapshotSession(session);\n            }\n        }\n    }\n\n    void timeoutOnUnavailableImage(final long imageCorrelationId, final ConsensusModuleAgent consensusModuleAgent)\n    {\n        for (int i = 0, size = sessions.size(); i < size; i++)\n        {\n            final ClusterSession session = sessions.get(i);\n\n            if (session.ingressImageCorrelationId() == imageCorrelationId && session.isOpen())\n            {\n                session.closing(CloseReason.TIMEOUT);\n            }\n        }\n    }\n\n    private void addSession(final ClusterSession session)\n    {\n        sessionByIdMap.put(session.id(), session);\n\n        final int size = sessions.size();\n        int addIndex = size;\n        for (int i = size - 1; i >= 0; i--)\n        {\n            if (sessions.get(i).id() < session.id())\n            {\n                addIndex = i + 1;\n                break;\n            }\n        }\n\n        if (size == addIndex)\n        {\n            sessions.add(session);\n        }\n        else\n        {\n            sessions.add(addIndex, session);\n        }\n    }\n\n    private int sendNewLeaderEvent(\n        final ClusterSession session,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final String ingressEndpoints)\n    {\n        if (egressPublisher.newLeader(session, leadershipTermId, leaderMemberId, ingressEndpoints))\n        {\n            session.hasNewLeaderEventPending(false);\n            return 1;\n        }\n\n        return 0;\n    }\n\n    private static String sessionInfo(final String clientInfo, final Header header)\n    {\n        final Image image = (Image)header.context();\n        final String imageInfo = \"sourceIdentity=\" + image.sourceIdentity() + \" sessionId=\" + image.sessionId();\n        return Strings.isEmpty(clientInfo) ? imageInfo : clientInfo + \" \" + imageInfo;\n    }\n\n    private record DelayedStandbySnapshotNotification(long deadlineNs, List<StandbySnapshotEntry> snapshotEntries)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/SnapshotReplication.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport org.agrona.CloseHelper;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nclass SnapshotReplication implements AutoCloseable\n{\n    private final ArrayList<RecordingLog.Snapshot> snapshotsPending = new ArrayList<>();\n    private final MultipleRecordingReplication multipleRecordingReplication;\n\n    SnapshotReplication(\n        final AeronArchive archive,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String replicationChannel)\n    {\n        this(\n            archive,\n            srcControlStreamId,\n            srcControlChannel,\n            replicationChannel,\n            TimeUnit.SECONDS.toNanos(10),\n            TimeUnit.SECONDS.toNanos(1));\n    }\n\n    SnapshotReplication(\n        final AeronArchive archive,\n        final int srcControlStreamId,\n        final String srcControlChannel,\n        final String replicationChannel,\n        final long replicationProgressTimeoutNs,\n        final long replicationProgressIntervalNs)\n    {\n        multipleRecordingReplication = MultipleRecordingReplication.newInstance(\n            archive,\n            srcControlStreamId,\n            srcControlChannel,\n            replicationChannel,\n            replicationProgressTimeoutNs,\n            replicationProgressIntervalNs);\n    }\n\n    void addSnapshot(final RecordingLog.Snapshot snapshot)\n    {\n        snapshotsPending.add(snapshot);\n        multipleRecordingReplication.addRecording(snapshot.recordingId(), Aeron.NULL_VALUE, Aeron.NULL_VALUE);\n    }\n\n    int poll(final long nowNs)\n    {\n        return multipleRecordingReplication.poll(nowNs);\n    }\n\n    void onSignal(final long correlationId, final long recordingId, final long position, final RecordingSignal signal)\n    {\n        multipleRecordingReplication.onSignal(correlationId, recordingId, position, signal);\n    }\n\n    long currentReplicationId()\n    {\n        return multipleRecordingReplication.currentReplicationId();\n    }\n\n    boolean isComplete()\n    {\n        return multipleRecordingReplication.isComplete();\n    }\n\n    List<RecordingLog.Snapshot> snapshotsRetrieved()\n    {\n        final ArrayList<RecordingLog.Snapshot> snapshots = new ArrayList<>();\n        for (int i = 0, n = snapshotsPending.size(); i < n; i++)\n        {\n            final RecordingLog.Snapshot pendingSnapshot = snapshotsPending.get(i);\n            final long dstRecordingId = multipleRecordingReplication.completedDstRecordingId(\n                pendingSnapshot.recordingId());\n            snapshots.add(retrievedSnapshot(pendingSnapshot, dstRecordingId));\n        }\n\n        return snapshots;\n    }\n\n    RecordingLog.Snapshot currentSnapshot()\n    {\n        final long currentSrcRecordingId = multipleRecordingReplication.currentSrcRecordingId();\n        if (Aeron.NULL_VALUE == currentSrcRecordingId)\n        {\n            return null;\n        }\n\n        for (int i = 0, n = snapshotsPending.size(); i < n; i++)\n        {\n            final RecordingLog.Snapshot snapshot = snapshotsPending.get(i);\n            if (snapshot.recordingId() == currentSrcRecordingId)\n            {\n                return snapshot;\n            }\n        }\n\n        return null;\n    }\n\n    static RecordingLog.Snapshot retrievedSnapshot(final RecordingLog.Snapshot pending, final long recordingId)\n    {\n        return new RecordingLog.Snapshot(\n            recordingId,\n            pending.leadershipTermId(),\n            pending.termBaseLogPosition(),\n            pending.logPosition(),\n            pending.timestamp(),\n            pending.serviceId());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(multipleRecordingReplication);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/StandbySnapshotEntry.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nclass StandbySnapshotEntry\n{\n    private final long recordingId;\n    private final long leadershipTermId;\n    private final long termBaseLogPosition;\n    private final long logPosition;\n    private final long timestamp;\n    private final int serviceId;\n    private final String archiveEndpoint;\n\n    StandbySnapshotEntry(\n        final long recordingId,\n        final long leadershipTermId,\n        final long termBaseLogPosition,\n        final long logPosition,\n        final long timestamp,\n        final int serviceId,\n        final String archiveEndpoint)\n    {\n        this.recordingId = recordingId;\n        this.leadershipTermId = leadershipTermId;\n        this.termBaseLogPosition = termBaseLogPosition;\n        this.logPosition = logPosition;\n        this.timestamp = timestamp;\n        this.serviceId = serviceId;\n        this.archiveEndpoint = archiveEndpoint;\n    }\n\n    public long recordingId()\n    {\n        return recordingId;\n    }\n\n    public long leadershipTermId()\n    {\n        return leadershipTermId;\n    }\n\n    public long termBaseLogPosition()\n    {\n        return termBaseLogPosition;\n    }\n\n    public long logPosition()\n    {\n        return logPosition;\n    }\n\n    public long timestamp()\n    {\n        return timestamp;\n    }\n\n    public int serviceId()\n    {\n        return serviceId;\n    }\n\n    public String archiveEndpoint()\n    {\n        return archiveEndpoint;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/StandbySnapshotReplicator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.Counter;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.status.RecordingPos.NULL_RECORDING_ID;\n\nclass StandbySnapshotReplicator implements AutoCloseable\n{\n    private final int memberId;\n    private final AeronArchive archive;\n    private final RecordingLog recordingLog;\n    private final int serviceCount;\n    private final String archiveControlChannel;\n    private final int archiveControlStreamId;\n    private final String replicationChannel;\n    private final int fileSyncLevel;\n    private final Counter snapshotCounter;\n    private final Object2ObjectHashMap<String, String> errorsByEndpoint = new Object2ObjectHashMap<>();\n    private MultipleRecordingReplication recordingReplication;\n    private ArrayList<SnapshotReplicationEntry> snapshotsToReplicate;\n    private SnapshotReplicationEntry currentSnapshotToReplicate;\n    private boolean isComplete = false;\n\n    StandbySnapshotReplicator(\n        final int memberId,\n        final AeronArchive archive,\n        final RecordingLog recordingLog,\n        final int serviceCount,\n        final String archiveControlChannel,\n        final int archiveControlStreamId,\n        final String replicationChannel,\n        final int fileSyncLevel,\n        final Counter snapshotCounter)\n    {\n        this.memberId = memberId;\n        this.archive = archive;\n        this.recordingLog = recordingLog;\n        this.serviceCount = serviceCount;\n        this.archiveControlChannel = archiveControlChannel;\n        this.archiveControlStreamId = archiveControlStreamId;\n        this.replicationChannel = replicationChannel;\n        this.fileSyncLevel = fileSyncLevel;\n        this.snapshotCounter = snapshotCounter;\n    }\n\n    static StandbySnapshotReplicator newInstance(\n        final int memberId,\n        final AeronArchive.Context archiveCtx,\n        final RecordingLog recordingLog,\n        final int serviceCount,\n        final String archiveControlChannel,\n        final int archiveControlStreamId,\n        final String replicationChannel,\n        final int fileSyncLevel, final Counter snapshotCounter)\n    {\n        final AeronArchive archive = AeronArchive.connect(archiveCtx.clone().errorHandler(null));\n        final StandbySnapshotReplicator standbySnapshotReplicator = new StandbySnapshotReplicator(\n            memberId,\n            archive,\n            recordingLog,\n            serviceCount,\n            archiveControlChannel,\n            archiveControlStreamId,\n            replicationChannel,\n            fileSyncLevel,\n            snapshotCounter);\n        archive.context().recordingSignalConsumer(standbySnapshotReplicator::onSignal);\n        return standbySnapshotReplicator;\n    }\n\n    int poll(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (null == recordingReplication)\n        {\n            workCount++;\n\n            if (null == snapshotsToReplicate)\n            {\n                snapshotsToReplicate = computeSnapshotsToReplicate();\n\n                if (null == snapshotsToReplicate)\n                {\n                    isComplete = true;\n                    return workCount;\n                }\n            }\n\n            if (snapshotsToReplicate.isEmpty())\n            {\n                throw new ClusterException(\n                    \"failed to replicate any standby snapshots, errors: \" + errorsByEndpoint,\n                    AeronException.Category.WARN);\n            }\n\n            currentSnapshotToReplicate = snapshotsToReplicate.remove(0);\n            final String srcChannel = ChannelUri.createDestinationUri(\n                archiveControlChannel, currentSnapshotToReplicate.endpoint);\n\n            final long progressTimeoutNs = archive.context().messageTimeoutNs() * 2;\n            final long progressIntervalNs = progressTimeoutNs / 10;\n\n            recordingReplication = MultipleRecordingReplication.newInstance(\n                archive,\n                archiveControlStreamId,\n                srcChannel,\n                replicationChannel,\n                progressTimeoutNs,\n                progressIntervalNs);\n            recordingReplication.setEventListener(this::logReplicationEnded);\n\n            for (int i = 0, n = currentSnapshotToReplicate.recordingLogEntries.size(); i < n; i++)\n            {\n                final RecordingLog.Entry entry = currentSnapshotToReplicate.recordingLogEntries.get(i);\n                recordingReplication.addRecording(entry.recordingId, NULL_RECORDING_ID, NULL_POSITION);\n            }\n\n            workCount++;\n        }\n\n        try\n        {\n            workCount += recordingReplication.poll(nowNs);\n            archive.pollForRecordingSignals();\n        }\n        catch (final ArchiveException | ClusterException ex)\n        {\n            errorsByEndpoint.put(currentSnapshotToReplicate.endpoint, ex.getMessage());\n            CloseHelper.quietClose(recordingReplication);\n            recordingReplication = null;\n        }\n\n        if (null != recordingReplication && recordingReplication.isComplete())\n        {\n            for (int i = 0, n = currentSnapshotToReplicate.recordingLogEntries.size(); i < n; i++)\n            {\n                final RecordingLog.Entry entry = currentSnapshotToReplicate.recordingLogEntries.get(i);\n                final long dstRecordingId = recordingReplication.completedDstRecordingId(entry.recordingId);\n                recordingLog.appendSnapshot(\n                    dstRecordingId,\n                    entry.leadershipTermId,\n                    entry.termBaseLogPosition,\n                    entry.logPosition,\n                    entry.timestamp,\n                    entry.serviceId);\n            }\n            recordingLog.force(fileSyncLevel);\n\n            snapshotCounter.incrementRelease();\n\n            CloseHelper.quietClose(recordingReplication);\n            recordingReplication = null;\n            isComplete = true;\n        }\n\n        return workCount;\n    }\n\n    private ArrayList<SnapshotReplicationEntry> computeSnapshotsToReplicate()\n    {\n        final Map<String, List<RecordingLog.Entry>> snapshotsByEndpoint = filterByExistingRecordingLogEntries(\n            recordingLog.latestStandbySnapshots(serviceCount));\n\n        final ArrayList<SnapshotReplicationEntry> orderedSnapshotToReplicate;\n        if (snapshotsByEndpoint.isEmpty())\n        {\n            orderedSnapshotToReplicate = null;\n        }\n        else\n        {\n            orderedSnapshotToReplicate = new ArrayList<>();\n\n            snapshotsByEndpoint.forEach(\n                (k, v) ->\n                {\n                    final long logPosition = v.get(0).logPosition;\n                    orderedSnapshotToReplicate.add(new SnapshotReplicationEntry(k, logPosition, v));\n                });\n\n            orderedSnapshotToReplicate.sort(StandbySnapshotReplicator::compareTo);\n        }\n\n        return orderedSnapshotToReplicate;\n    }\n\n    private static int compareTo(final SnapshotReplicationEntry a, final SnapshotReplicationEntry b)\n    {\n        final int descendingOrderCompare = -Long.compare(a.logPosition, b.logPosition);\n        if (0 != descendingOrderCompare)\n        {\n            return descendingOrderCompare;\n        }\n\n        return a.endpoint.compareTo(b.endpoint);\n    }\n\n    boolean isComplete()\n    {\n        return isComplete;\n    }\n\n    void onSignal(\n        final long controlSessionId,\n        final long correlationId,\n        final long recordingId,\n        final long subscriptionId,\n        final long position,\n        final RecordingSignal signal)\n    {\n        if (null != recordingReplication)\n        {\n            recordingReplication.onSignal(correlationId, recordingId, position, signal);\n        }\n    }\n\n    public void close()\n    {\n        CloseHelper.quietClose(archive);\n    }\n\n    private static final class SnapshotReplicationEntry\n    {\n        private final String endpoint;\n        private final long logPosition;\n        private final List<RecordingLog.Entry> recordingLogEntries = new ArrayList<>();\n\n        private SnapshotReplicationEntry(\n            final String endpoint,\n            final long logPosition,\n            final List<RecordingLog.Entry> entries)\n        {\n            this.endpoint = endpoint;\n            this.logPosition = logPosition;\n            this.recordingLogEntries.addAll(entries);\n        }\n    }\n\n    private void logReplicationEnded(\n        final String controlUri,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final long position,\n        final boolean hasSynced)\n    {\n        ConsensusModuleAgent.logReplicationEnded(\n            memberId, \"STANDBY_SNAPSHOT\", controlUri, srcRecordingId, dstRecordingId, position, hasSynced);\n    }\n\n    private Map<String, List<RecordingLog.Entry>> filterByExistingRecordingLogEntries(\n        final Map<String, List<RecordingLog.Entry>> standbySnapshotsByEndpoint)\n    {\n        final Map<String, List<RecordingLog.Entry>> filteredSnapshotsByEndpoint = new Object2ObjectHashMap<>();\n\n        for (final Map.Entry<String, List<RecordingLog.Entry>> entry : standbySnapshotsByEndpoint.entrySet())\n        {\n            for (int i = entry.getValue().size(); --i > -1;)\n            {\n                final RecordingLog.Entry standbySnapshotEntry = entry.getValue().get(i);\n                final RecordingLog.Entry snapshotEntry = recordingLog.getLatestSnapshot(standbySnapshotEntry.serviceId);\n                if (null != snapshotEntry && standbySnapshotEntry.logPosition <= snapshotEntry.logPosition)\n                {\n                    entry.getValue().remove(i);\n                }\n            }\n\n            if (!entry.getValue().isEmpty())\n            {\n                filteredSnapshotsByEndpoint.put(entry.getKey(), entry.getValue());\n            }\n        }\n\n        return filteredSnapshotsByEndpoint;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/TimerService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\n/**\n * Timer service providing API to schedule timers in the Cluster.\n */\npublic interface TimerService\n{\n    /**\n     * Called from the {@link #poll(long)}.\n     */\n    @FunctionalInterface\n    interface TimerHandler\n    {\n        /**\n         * Invoked for each expired timer.\n         *\n         * @param correlationId of the timer.\n         * @return {@code true} if the timer event was processed or {@code false} otherwise, i.e. the timer should be\n         * kept as non-expired.\n         */\n        boolean onTimerEvent(long correlationId);\n    }\n\n    /**\n     * Called from the {@link #snapshot(TimerSnapshotTaker)}.\n     */\n    @FunctionalInterface\n    interface TimerSnapshotTaker\n    {\n        /**\n         * Take a snapshot of the timer.\n         *\n         * @param correlationId of the timer.\n         * @param deadline      when the timer expires.\n         */\n        void snapshotTimer(long correlationId, long deadline);\n    }\n\n    /**\n     * Max number of the timers to expire per single {@link #poll(long)}.\n     */\n    int POLL_LIMIT = 20;\n\n    /**\n     * Poll for expired timers. For each expired timer it invokes {@link TimerHandler#onTimerEvent(long)} on the\n     * {@link TimerHandler} that was provided upon creation of this {@link TimerService} instance.\n     * Poll can be terminated early if {@link #POLL_LIMIT} is reached or if {@link TimerHandler#onTimerEvent(long)}\n     * returns {@code false}.\n     *\n     * @param now current time.\n     * @return number of timers expired, never exceeds {@link #POLL_LIMIT}.\n     */\n    int poll(long now);\n\n    /**\n     * Schedule timer with the given {@code correlationId} and {@code deadline}.\n     *\n     * @param correlationId to associate with the timer.\n     * @param deadline      when the timer expires.\n     */\n    void scheduleTimerForCorrelationId(long correlationId, long deadline);\n\n    /**\n     * Cancels timer by its {@code correlationId}.\n     *\n     * @param correlationId of the timer to cancel.\n     * @return {@code true} if the timer was cancelled.\n     */\n    boolean cancelTimerByCorrelationId(long correlationId);\n\n    /**\n     * Takes a snapshot of the timer service state, i.e. serializes all non-expired timers.\n     *\n     * @param snapshotTaker to take a snapshot.\n     */\n    void snapshot(TimerSnapshotTaker snapshotTaker);\n\n    /**\n     * Set the current time from the Cluster in case the underlying implementation depends on it.\n     *\n     * @param now the current time.\n     */\n    void currentTime(long now);\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/TimerServiceSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Supplier of {@link TimerService} implementations to be used by the {@link ConsensusModule}.\n */\n@FunctionalInterface\npublic interface TimerServiceSupplier\n{\n    /**\n     * New instance of the {@link TimerService}.\n     *\n     * @param timeUnit      units to be used by the underlying timer service.\n     * @param timerHandler  that must be invoked for each expired timer.\n     * @return              timer service instance ready for immediate usage.\n     */\n    TimerService newInstance(TimeUnit timeUnit, TimerService.TimerHandler timerHandler);\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/ToggleApplication.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\n\ninterface ToggleApplication<T extends Enum<T>>\n{\n    T get(AtomicCounter counter);\n\n    boolean apply(AtomicCounter counter, T targetState);\n\n    AtomicCounter find(CountersReader countersReader, int clusterId);\n\n    @SuppressWarnings(\"BooleanMethodIsAlwaysInverted\")\n    boolean isNeutral(T toggleState);\n\n    /**\n     * Cluster control toggle.\n     */\n    ToggleApplication<ClusterControl.ToggleState> CLUSTER_CONTROL = new ToggleApplication<ClusterControl.ToggleState>()\n    {\n        public ClusterControl.ToggleState get(final AtomicCounter counter)\n        {\n            return ClusterControl.ToggleState.get(counter);\n        }\n\n        public boolean apply(final AtomicCounter counter, final ClusterControl.ToggleState targetState)\n        {\n            return targetState.toggle(counter);\n        }\n\n        public AtomicCounter find(final CountersReader countersReader, final int clusterId)\n        {\n            return ClusterControl.findControlToggle(countersReader, clusterId);\n        }\n\n        public boolean isNeutral(final ClusterControl.ToggleState toggleState)\n        {\n            return ClusterControl.ToggleState.NEUTRAL == toggleState;\n        }\n    };\n\n    /**\n     * Node state file control toggle.\n     */\n    ToggleApplication<NodeControl.ToggleState> NODE_CONTROL = new ToggleApplication<NodeControl.ToggleState>()\n    {\n        public NodeControl.ToggleState get(final AtomicCounter counter)\n        {\n            return NodeControl.ToggleState.get(counter);\n        }\n\n        public boolean apply(final AtomicCounter counter, final NodeControl.ToggleState targetState)\n        {\n            return targetState.toggle(counter);\n        }\n\n        public AtomicCounter find(final CountersReader countersReader, final int clusterId)\n        {\n            return NodeControl.findControlToggle(countersReader, clusterId);\n        }\n\n        public boolean isNeutral(final NodeControl.ToggleState toggleState)\n        {\n            return NodeControl.ToggleState.NEUTRAL == toggleState;\n        }\n    };\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/WheelTimerService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport org.agrona.DeadlineTimerWheel;\nimport org.agrona.collections.Long2LongHashMap;\n\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Implementation of the {@link TimerService} that is based on {@link DeadlineTimerWheel}.\n *\n * <p>\n * <b>Caveats</b>\n * <p>\n * Timers that expire in the same tick are not be ordered with one another. As ticks are\n * fairly coarse resolution normally, this means that some timers may expire out of order.\n * <p>\n * Upon Cluster restart the expiration order of the already expired timers is <em>not guaranteed</em>, i.e. timers with\n * the later deadlines might expire before timers with the earlier deadlines. To avoid this behavior use the\n * {@link PriorityHeapTimerService} instead.\n * <p>\n * <b>Note:</b> Not thread safe.\n */\nfinal class WheelTimerService extends DeadlineTimerWheel implements DeadlineTimerWheel.TimerHandler, TimerService\n{\n    private final TimerService.TimerHandler timerHandler;\n    private final Long2LongHashMap timerIdByCorrelationIdMap = new Long2LongHashMap(Long.MAX_VALUE);\n    private final Long2LongHashMap correlationIdByTimerIdMap = new Long2LongHashMap(Long.MAX_VALUE);\n    private boolean isAbort;\n\n    WheelTimerService(\n        final TimerService.TimerHandler timerHandler,\n        final TimeUnit timeUnit,\n        final long startTime,\n        final long tickResolution,\n        final int ticksPerWheel)\n    {\n        super(timeUnit, startTime, tickResolution, ticksPerWheel);\n        this.timerHandler = Objects.requireNonNull(timerHandler);\n    }\n\n    public int poll(final long now)\n    {\n        int expired = 0;\n        isAbort = false;\n\n        do\n        {\n            expired += poll(now, this, POLL_LIMIT);\n\n            if (isAbort)\n            {\n                break;\n            }\n        }\n        while (expired < POLL_LIMIT && currentTickTime() < now);\n\n        return expired;\n    }\n\n    public boolean onTimerExpiry(final TimeUnit timeUnit, final long now, final long timerId)\n    {\n        final long correlationId = correlationIdByTimerIdMap.get(timerId);\n\n        if (timerHandler.onTimerEvent(correlationId))\n        {\n            correlationIdByTimerIdMap.remove(timerId);\n            timerIdByCorrelationIdMap.remove(correlationId);\n\n            return true;\n        }\n        else\n        {\n            isAbort = true;\n\n            return false;\n        }\n    }\n\n    public void scheduleTimerForCorrelationId(final long correlationId, final long deadline)\n    {\n        cancelTimerByCorrelationId(correlationId);\n\n        final long timerId = scheduleTimer(deadline);\n        timerIdByCorrelationIdMap.put(correlationId, timerId);\n        correlationIdByTimerIdMap.put(timerId, correlationId);\n    }\n\n    public boolean cancelTimerByCorrelationId(final long correlationId)\n    {\n        final long timerId = timerIdByCorrelationIdMap.remove(correlationId);\n        if (Long.MAX_VALUE != timerId)\n        {\n            cancelTimer(timerId);\n            correlationIdByTimerIdMap.remove(timerId);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    public void snapshot(final TimerSnapshotTaker snapshotTaker)\n    {\n        final Long2LongHashMap.EntryIterator iter = timerIdByCorrelationIdMap.entrySet().iterator();\n\n        while (iter.hasNext())\n        {\n            iter.next();\n\n            final long correlationId = iter.getLongKey();\n            final long deadline = deadline(iter.getLongValue());\n\n            snapshotTaker.snapshotTimer(correlationId, deadline);\n        }\n    }\n\n    public void currentTime(final long now)\n    {\n        currentTickTime(now);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/WheelTimerServiceSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.agrona.BitUtil.findNextPositivePowerOfTwo;\n\n/**\n * Supplies an instance of a {@link WheelTimerService} based on the configuration given to the constructor.\n */\npublic class WheelTimerServiceSupplier implements TimerServiceSupplier\n{\n    private final TimeUnit timeUnit;\n    private final long startTime;\n    private final long tickResolution;\n    private final int ticksPerWheel;\n\n    /**\n     * Construct the supplier with the necessary parameters to configure the timer wheel.\n     *\n     * @param timeUnit       for the values used to express the time.  This time unit is used to denote the supplied\n     *                       time values.  When the instance is constructed it will use this to convert the supplied\n     *                       <code>startTime</code> and <code>tickResolution</code> into the time unit that is being\n     *                       used by the cluster clock.\n     * @param startTime      for the wheel (in given {@link TimeUnit}).\n     * @param tickResolution for the wheel, i.e. how many {@link TimeUnit}s per tick.\n     * @param ticksPerWheel  or spokes, for the wheel (must be power of 2).\n     */\n    public WheelTimerServiceSupplier(\n        final TimeUnit timeUnit,\n        final long startTime,\n        final long tickResolution,\n        final int ticksPerWheel)\n    {\n        this.timeUnit = timeUnit;\n        this.startTime = startTime;\n        this.tickResolution = tickResolution;\n        this.ticksPerWheel = ticksPerWheel;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public TimerService newInstance(final TimeUnit clusterTimeUnit, final TimerService.TimerHandler timerHandler)\n    {\n        final long startTimeInClusterTimeUnits = clusterTimeUnit.convert(startTime, timeUnit);\n        final long resolutionInClusterTimeUnits = clusterTimeUnit.convert(tickResolution, timeUnit);\n\n        return new WheelTimerService(\n            timerHandler,\n            clusterTimeUnit,\n            startTimeInClusterTimeUnits,\n            findNextPositivePowerOfTwo(resolutionInClusterTimeUnits),\n            ticksPerWheel);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/AeronCluster.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.DirectBufferVector;\nimport io.aeron.ErrorCode;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.codecs.AdminRequestEncoder;\nimport io.aeron.cluster.codecs.AdminRequestType;\nimport io.aeron.cluster.codecs.AdminResponseCode;\nimport io.aeron.cluster.codecs.AdminResponseDecoder;\nimport io.aeron.cluster.codecs.ChallengeResponseEncoder;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.NewLeaderEventDecoder;\nimport io.aeron.cluster.codecs.SessionCloseRequestEncoder;\nimport io.aeron.cluster.codecs.SessionConnectRequestEncoder;\nimport io.aeron.cluster.codecs.SessionEventDecoder;\nimport io.aeron.cluster.codecs.SessionKeepAliveEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderDecoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderEncoder;\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.security.AuthenticationException;\nimport io.aeron.security.CredentialsSupplier;\nimport io.aeron.security.NullCredentialsSupplier;\nimport io.aeron.version.Versioned;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.SemanticVersion;\nimport org.agrona.Strings;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.BackoffIdleStrategy;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.REJOIN_PARAM_NAME;\nimport static org.agrona.SystemUtil.getDurationInNanos;\nimport static org.agrona.SystemUtil.getProperty;\n\n/**\n * Client for interacting with an Aeron Cluster.\n * <p>\n * A client will attempt to open a session and then offer ingress messages which are replicated to clustered services\n * for reliability. If the clustered service responds then response messages and events are sent via the egress stream.\n * <p>\n * <b>Note:</b> Instances of this class are not threadsafe.\n */\n@Versioned\npublic final class AeronCluster implements AutoCloseable\n{\n    /**\n     * Length of a session message header for cluster ingress or egress.\n     */\n    public static final int SESSION_HEADER_LENGTH =\n        MessageHeaderEncoder.ENCODED_LENGTH + SessionMessageHeaderEncoder.BLOCK_LENGTH;\n\n    private static final int SEND_ATTEMPTS = 3;\n    private static final int FRAGMENT_LIMIT = 10;\n\n    private final long clusterSessionId;\n    private long leadershipTermId;\n    private int leaderMemberId;\n    private final Context ctx;\n    private final Subscription subscription;\n    private State state;\n    private long stateDeadline;\n    private Image egressImage;\n    private Publication publication;\n    private final NanoClock nanoClock;\n    private final IdleStrategy idleStrategy;\n    private final BufferClaim bufferClaim = new BufferClaim();\n    private final UnsafeBuffer headerBuffer = new UnsafeBuffer(new byte[SESSION_HEADER_LENGTH]);\n    private final DirectBufferVector headerVector = new DirectBufferVector(headerBuffer, 0, SESSION_HEADER_LENGTH);\n    private final MessageHeaderEncoder messageHeaderEncoder;\n    private final SessionMessageHeaderEncoder sessionMessageHeaderEncoder = new SessionMessageHeaderEncoder();\n    private final SessionKeepAliveEncoder sessionKeepAliveEncoder = new SessionKeepAliveEncoder();\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n    private final NewLeaderEventDecoder newLeaderEventDecoder = new NewLeaderEventDecoder();\n    private final SessionEventDecoder sessionEventDecoder = new SessionEventDecoder();\n    private final AdminRequestEncoder adminRequestEncoder = new AdminRequestEncoder();\n    private final AdminResponseDecoder adminResponseDecoder = new AdminResponseDecoder();\n    private final FragmentAssembler fragmentAssembler;\n    private final EgressListener egressListener;\n    private final ControlledFragmentAssembler controlledFragmentAssembler;\n    private final ControlledEgressListener controlledEgressListener;\n    private EgressListenerExtension egressListenerExtension;\n    private ControlledEgressListenerExtension controlledEgressListenerExtension;\n    private Int2ObjectHashMap<MemberIngress> endpointByIdMap;\n\n    /**\n     * Connect to the cluster using default configuration.\n     *\n     * @return allocated cluster client if the connection is successful.\n     */\n    public static AeronCluster connect()\n    {\n        return connect(new Context());\n    }\n\n    /**\n     * Connect to the cluster providing {@link Context} for configuration.\n     *\n     * @param ctx for configuration.\n     * @return allocated cluster client if the connection is successful.\n     */\n    public static AeronCluster connect(final AeronCluster.Context ctx)\n    {\n        AsyncConnect asyncConnect = null;\n        try\n        {\n            ctx.conclude();\n            final IdleStrategy idleStrategy = ctx.idleStrategy();\n\n            asyncConnect = new AsyncConnect(ctx);\n\n            AeronCluster aeronCluster;\n            AsyncConnect.State state = asyncConnect.state();\n            while (null == (aeronCluster = asyncConnect.poll()))\n            {\n                if (state != asyncConnect.state())\n                {\n                    state = asyncConnect.state();\n                    idleStrategy.reset();\n                }\n                else\n                {\n                    idleStrategy.idle();\n                }\n            }\n\n            return aeronCluster;\n        }\n        catch (final ConcurrentConcludeException ex)\n        {\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            if (!ctx.ownsAeronClient())\n            {\n                CloseHelper.quietCloseAll(asyncConnect);\n            }\n\n            CloseHelper.quietClose(ctx::close);\n\n            throw ex;\n        }\n    }\n\n    /**\n     * Begin an attempt at creating a connection which can be completed by calling {@link AsyncConnect#poll()} until\n     * it returns the client, before complete it will return null.\n     *\n     * @return the {@link AsyncConnect} that can be polled for completion.\n     */\n    public static AsyncConnect asyncConnect()\n    {\n        return asyncConnect(new Context());\n    }\n\n    /**\n     * Begin an attempt at creating a connection which can be completed by calling {@link AsyncConnect#poll()} until\n     * it returns the client, before complete it will return null.\n     *\n     * @param ctx for the cluster.\n     * @return the {@link AsyncConnect} that can be polled for completion.\n     */\n    public static AsyncConnect asyncConnect(final Context ctx)\n    {\n        try\n        {\n            ctx.conclude();\n\n            return new AsyncConnect(ctx);\n        }\n        catch (final Exception ex)\n        {\n            ctx.close();\n\n            throw ex;\n        }\n    }\n\n    AeronCluster(\n        final Context ctx,\n        final MessageHeaderEncoder messageHeaderEncoder,\n        final Publication publication,\n        final Subscription subscription,\n        final Image egressImage,\n        final Int2ObjectHashMap<MemberIngress> endpointByIdMap,\n        final long clusterSessionId,\n        final long leadershipTermId,\n        final int leaderMemberId)\n    {\n        this.ctx = ctx;\n        this.messageHeaderEncoder = messageHeaderEncoder;\n        this.subscription = subscription;\n        this.egressImage = egressImage;\n        this.endpointByIdMap = endpointByIdMap;\n        this.clusterSessionId = clusterSessionId;\n        this.leadershipTermId = leadershipTermId;\n        this.leaderMemberId = leaderMemberId;\n        this.publication = publication;\n\n        this.nanoClock = ctx.aeron().context().nanoClock();\n        this.idleStrategy = ctx.idleStrategy();\n        this.egressListener = ctx.egressListener();\n        this.fragmentAssembler = new FragmentAssembler(this::onFragment, 0, ctx.isDirectAssemblers());\n        this.controlledEgressListener = ctx.controlledEgressListener();\n        this.controlledFragmentAssembler = new ControlledFragmentAssembler(\n            this::onControlledFragment, 0, ctx.isDirectAssemblers());\n\n        sessionMessageHeaderEncoder\n            .wrapAndApplyHeader(headerBuffer, 0, messageHeaderEncoder)\n            .clusterSessionId(clusterSessionId)\n            .leadershipTermId(leadershipTermId);\n\n        state(State.CONNECTED, 0);\n    }\n\n    /**\n     * An EgressListener for extension schemas.\n     *\n     * @param listenerExtension listener extension.\n     */\n    public void egressListenerExtension(final EgressListenerExtension listenerExtension)\n    {\n        this.egressListenerExtension = listenerExtension;\n    }\n\n    /**\n     * A ControlledEgressListener for extension schemas.\n     *\n     * @param listenerExtension listener extension.\n     */\n    public void controlledEgressListenerExtension(final ControlledEgressListenerExtension listenerExtension)\n    {\n        this.controlledEgressListenerExtension = listenerExtension;\n    }\n\n    /**\n     * Close session and release associated resources.\n     */\n    public void close()\n    {\n        if (State.CLOSED == state)\n        {\n            return;\n        }\n\n        if (State.CONNECTED == state && null != publication && publication.isConnected())\n        {\n            closeSession();\n        }\n\n        if (!ctx.ownsAeronClient())\n        {\n            final ErrorHandler errorHandler = ctx.errorHandler();\n            CloseHelper.close(errorHandler, subscription);\n            CloseHelper.close(errorHandler, publication);\n        }\n\n        state(State.CLOSED, 0);\n        ctx.close();\n    }\n\n    /**\n     * Is the client closed? The client can be closed by calling {@link #close()}, the cluster sending an event,\n     * or the client permanently losing a cluster connection.\n     *\n     * @return true if closed otherwise false.\n     */\n    public boolean isClosed()\n    {\n        return State.CLOSED == state;\n    }\n\n    /**\n     * Get the context used to launch this cluster client.\n     *\n     * @return the context used to launch this cluster client.\n     */\n    public Context context()\n    {\n        return ctx;\n    }\n\n    /**\n     * Cluster session id for the session that was opened as the result of a successful connect.\n     *\n     * @return session id for the session that was opened as the result of a successful connect.\n     */\n    public long clusterSessionId()\n    {\n        return clusterSessionId;\n    }\n\n    /**\n     * Leadership term identity for the cluster. Advances with changing leadership.\n     *\n     * @return leadership term identity for the cluster.\n     */\n    public long leadershipTermId()\n    {\n        return leadershipTermId;\n    }\n\n    /**\n     * Get the current leader member id for the cluster.\n     *\n     * @return the current leader member id for the cluster.\n     */\n    public int leaderMemberId()\n    {\n        return leaderMemberId;\n    }\n\n    /**\n     * Get the raw {@link Publication} for sending to the cluster.\n     * <p>\n     * This can be wrapped with a {@link IngressSessionDecorator} for pre-pending the cluster session header to\n     * messages.\n     * {@link io.aeron.cluster.codecs.SessionMessageHeaderEncoder} should be used for raw access.\n     * <p>\n     * Results of offering to this publication should be passed to {@link #trackIngressPublicationResult(long)}.\n     *\n     * @return the raw {@link Publication} for connecting to the cluster.\n     */\n    public Publication ingressPublication()\n    {\n        return publication;\n    }\n\n    /**\n     * Get the raw {@link Subscription} for receiving from the cluster.\n     * <p>\n     * This can be wrapped with a {@link EgressAdapter} for dispatching events from the cluster.\n     * {@link io.aeron.cluster.codecs.SessionMessageHeaderDecoder} should be used for raw access.\n     *\n     * @return the raw {@link Subscription} for receiving from the cluster.\n     */\n    public Subscription egressSubscription()\n    {\n        return subscription;\n    }\n\n    /**\n     * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n     * Once the message has been written then {@link BufferClaim#commit()} should be called thus making it available.\n     * <p>\n     * On successful claim, the Cluster ingress header will be written to the start of the claimed buffer section.\n     * Clients <b>MUST</b> write into the claimed buffer region at offset + {@link AeronCluster#SESSION_HEADER_LENGTH}.\n     * <pre>{@code\n     *     final DirectBuffer srcBuffer = acquireMessage();\n     *\n     *     if (aeronCluster.tryClaim(length, bufferClaim) > 0L)\n     *     {\n     *         try\n     *         {\n     *              final MutableDirectBuffer buffer = bufferClaim.buffer();\n     *              final int offset = bufferClaim.offset();\n     *              // ensure that data is written at the correct offset\n     *              buffer.putBytes(offset + AeronCluster.SESSION_HEADER_LENGTH, srcBuffer, 0, length);\n     *         }\n     *         finally\n     *         {\n     *             bufferClaim.commit();\n     *         }\n     *     }\n     * }</pre>\n     *\n     * @param length      of the range to claim in bytes. The additional bytes for the session header will be added.\n     * @param bufferClaim to be populated if the claim succeeds.\n     * @return The new stream position, otherwise a negative error value as specified in\n     * {@link io.aeron.Publication#tryClaim(int, BufferClaim)}.\n     * @throws IllegalArgumentException if the length is greater than {@link io.aeron.Publication#maxPayloadLength()}.\n     * @see Publication#tryClaim(int, BufferClaim)\n     * @see BufferClaim#commit()\n     * @see BufferClaim#abort()\n     */\n    public long tryClaim(final int length, final BufferClaim bufferClaim)\n    {\n        final long offset = publication.tryClaim(length + SESSION_HEADER_LENGTH, bufferClaim);\n\n        trackIngressPublicationResult(offset);\n\n        if (offset > 0)\n        {\n            bufferClaim.putBytes(headerBuffer, 0, SESSION_HEADER_LENGTH);\n        }\n\n        return offset;\n    }\n\n    /**\n     * Non-blocking publish of a partial buffer containing a message plus session header to a cluster.\n     * <p>\n     * This version of the method will set the timestamp value in the header to zero.\n     *\n     * @param buffer containing message.\n     * @param offset offset in the buffer at which the encoded message begins.\n     * @param length in bytes of the encoded message.\n     * @return the same as {@link Publication#offer(DirectBuffer, int, int)}.\n     */\n    public long offer(final DirectBuffer buffer, final int offset, final int length)\n    {\n        final long result = publication.offer(headerBuffer, 0, SESSION_HEADER_LENGTH, buffer, offset, length, null);\n\n        trackIngressPublicationResult(result);\n\n        return result;\n    }\n\n    /**\n     * Non-blocking publish by gathering buffer vectors into a message. The first vector will be replaced by the cluster\n     * session message header so must be left unused.\n     *\n     * @param vectors which make up the message.\n     * @return the same as {@link Publication#offer(DirectBufferVector[])}.\n     * @see Publication#offer(DirectBufferVector[])\n     */\n    public long offer(final DirectBufferVector[] vectors)\n    {\n        vectors[0] = headerVector;\n\n        final long result = publication.offer(vectors, null);\n\n        trackIngressPublicationResult(result);\n\n        return result;\n    }\n\n    /**\n     * Send a keep alive message to the cluster to keep this session open.\n     * <p>\n     * <b>Note:</b> Sending keep-alive can fail during a leadership transition. The application should continue to call\n     * {@link #pollEgress()} to ensure a connection to the new leader is established.\n     *\n     * @return true if successfully sent otherwise false if back pressured.\n     */\n    public boolean sendKeepAlive()\n    {\n        idleStrategy.reset();\n        int attempts = SEND_ATTEMPTS;\n\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + SessionKeepAliveEncoder.BLOCK_LENGTH;\n\n        while (true)\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n\n            trackIngressPublicationResult(position);\n\n            if (position > 0)\n            {\n                sessionKeepAliveEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .clusterSessionId(clusterSessionId);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n\n            if (position == Publication.MAX_POSITION_EXCEEDED)\n            {\n                throw new ClusterException(\"max position exceeded: term-length=\" + publication.termBufferLength());\n            }\n\n            if (--attempts <= 0)\n            {\n                break;\n            }\n\n            idleStrategy.idle();\n        }\n\n        return false;\n    }\n\n    /**\n     * Sends an admin request to initiate a snapshot action in the cluster. This request requires elevated privileges.\n     *\n     * @param correlationId for the request.\n     * @return {@code true} if the request was sent or {@code false} otherwise.\n     * @see EgressListener#onAdminResponse(long, long, AdminRequestType, AdminResponseCode, String, DirectBuffer, int, int)\n     * @see ControlledEgressListener#onAdminResponse(long, long, AdminRequestType, AdminResponseCode, String, DirectBuffer, int, int)\n     */\n    public boolean sendAdminRequestToTakeASnapshot(final long correlationId)\n    {\n        idleStrategy.reset();\n        int attempts = SEND_ATTEMPTS;\n\n        final int length =\n            MessageHeaderEncoder.ENCODED_LENGTH +\n            AdminRequestEncoder.BLOCK_LENGTH +\n            AdminRequestEncoder.payloadHeaderLength();\n\n        while (true)\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n\n            trackIngressPublicationResult(position);\n\n            if (position > 0)\n            {\n                adminRequestEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .clusterSessionId(clusterSessionId)\n                    .correlationId(correlationId)\n                    .requestType(AdminRequestType.SNAPSHOT)\n                    .putPayload(ArrayUtil.EMPTY_BYTE_ARRAY, 0, 0);\n\n                bufferClaim.commit();\n\n                return true;\n            }\n\n            if (position == Publication.CLOSED)\n            {\n                throw new ClusterException(\"ingress publication is closed\");\n            }\n\n            if (position == Publication.MAX_POSITION_EXCEEDED)\n            {\n                throw new ClusterException(\"max position exceeded: term-length=\" + publication.termBufferLength());\n            }\n\n            if (--attempts <= 0)\n            {\n                break;\n            }\n\n            idleStrategy.idle();\n        }\n\n        return false;\n    }\n\n    /**\n     * Poll the {@link #egressSubscription()} for session messages which are dispatched to\n     * {@link Context#egressListener()}. Invoking this method, or {@link #controlledPollEgress()}, frequently is\n     * important for detecting leadership changes in a cluster.\n     * <p>\n     * <b>Note:</b> if {@link Context#egressListener()} is not set then a {@link ConfigurationException} could result.\n     *\n     * @return 0 if no work was available, or a positive number if work has been done,\n     * typically the number of fragments processed.\n     * @see #controlledPollEgress()\n     */\n    public int pollEgress()\n    {\n        int workCount = subscription.poll(fragmentAssembler, FRAGMENT_LIMIT);\n\n        if (egressImage.isClosed() && (State.CONNECTED == state || State.AWAIT_NEW_LEADER_CONNECTION == state))\n        {\n            onDisconnected();\n            workCount++;\n        }\n\n        workCount += pollStateChanges();\n\n        return workCount;\n    }\n\n    /**\n     * Poll the {@link #egressSubscription()} for session messages which are dispatched to\n     * {@link Context#controlledEgressListener()}. Invoking this method, or {@link #pollEgress()}, frequently is\n     * important for detecting leadership changes in a cluster.\n     * <p>\n     * <b>Note:</b> if {@link Context#controlledEgressListener()} is not set then a {@link ConfigurationException}\n     * could result.\n     *\n     * @return 0 if no work was available, a positive number if work has been done,\n     * typically the number of fragments processed.\n     * @see #pollEgress()\n     */\n    public int controlledPollEgress()\n    {\n        int workCount = subscription.controlledPoll(controlledFragmentAssembler, FRAGMENT_LIMIT);\n\n        if (egressImage.isClosed() && (State.CONNECTED == state || State.AWAIT_NEW_LEADER_CONNECTION == state))\n        {\n            onDisconnected();\n            workCount++;\n        }\n\n        workCount += pollStateChanges();\n\n        return workCount;\n    }\n\n    /**\n     * Polls for client state changes. Needs to be called explicitly only by applications which use\n     * {@link #egressSubscription()} directly instead of calling {@link #pollEgress()} or\n     * {@link #controlledPollEgress()}.\n     *\n     * @return 0 if state has not changed, a positive number otherwise.\n     */\n    public int pollStateChanges()\n    {\n        if (State.PENDING_CLOSE == state ||\n            ((State.AWAIT_NEW_LEADER == state || State.AWAIT_NEW_LEADER_CONNECTION == state) &&\n            0 <= nanoClock.nanoTime() - stateDeadline))\n        {\n            close();\n\n            return 1;\n        }\n\n        return 0;\n    }\n\n    /**\n     * To be called when a new leader event is delivered. This method needs to be called when using the\n     * {@link EgressAdapter} or {@link EgressPoller} rather than {@link #pollEgress()} method.\n     *\n     * @param clusterSessionId which must match {@link #clusterSessionId()}.\n     * @param leadershipTermId that identifies the term for which the new leader has been elected.\n     * @param leaderMemberId   which has become the new leader.\n     * @param ingressEndpoints comma separated list of cluster ingress endpoints to connect to with the leader first.\n     */\n    public void onNewLeader(\n        final long clusterSessionId,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final String ingressEndpoints)\n    {\n        if (clusterSessionId != this.clusterSessionId)\n        {\n            throw new ClusterException(\n                \"invalid clusterSessionId=\" + clusterSessionId + \" expected=\" + this.clusterSessionId);\n        }\n\n        state(State.AWAIT_NEW_LEADER_CONNECTION, nanoClock.nanoTime() + ctx.messageTimeoutNs());\n\n        this.leadershipTermId = leadershipTermId;\n        this.leaderMemberId = leaderMemberId;\n        sessionMessageHeaderEncoder.leadershipTermId(leadershipTermId);\n\n        CloseHelper.close(publication);\n        if (null == ctx.ingressEndpoints())\n        {\n            publication = addNewLeaderIngressPublication(ctx, ctx.ingressChannel(), ctx.ingressStreamId());\n        }\n        else\n        {\n            ctx.ingressEndpoints(ingressEndpoints);\n            updateMemberEndpoints(ingressEndpoints, leaderMemberId);\n        }\n\n        fragmentAssembler.clear();\n        controlledFragmentAssembler.clear();\n        egressListener.onNewLeader(clusterSessionId, leadershipTermId, leaderMemberId, ingressEndpoints);\n        controlledEgressListener.onNewLeader(clusterSessionId, leadershipTermId, leaderMemberId, ingressEndpoints);\n    }\n\n    /**\n     * Updates the state of this client based on ingress publication result. Should be called with every {@code offer}\n     * and {@code tryClaim} result when {@link #ingressPublication()} is used directly. Methods of this class which send\n     * ingress messages call it automatically.\n     *\n     * @param result the result returned by the ingress publication.\n     */\n    public void trackIngressPublicationResult(final long result)\n    {\n        if (State.CONNECTED == state)\n        {\n            if (Publication.NOT_CONNECTED == result || Publication.CLOSED == result)\n            {\n                onDisconnected();\n            }\n            else if (Publication.MAX_POSITION_EXCEEDED == result)\n            {\n                publication.close();\n                state(State.PENDING_CLOSE, 0);\n            }\n        }\n        else if (State.AWAIT_NEW_LEADER_CONNECTION == state)\n        {\n            if (0 < result)\n            {\n                state(State.CONNECTED, 0);\n            }\n        }\n    }\n\n    static Int2ObjectHashMap<MemberIngress> parseIngressEndpoints(final Context ctx, final String endpoints)\n    {\n        final Int2ObjectHashMap<MemberIngress> endpointByIdMap = new Int2ObjectHashMap<>();\n\n        if (null != endpoints)\n        {\n            for (final String endpoint : endpoints.split(\",\"))\n            {\n                final int i = endpoint.indexOf('=');\n                if (-1 == i)\n                {\n                    throw new ConfigurationException(\"endpoint missing '=' separator: \" + endpoints);\n                }\n\n                final int memberId = AsciiEncoding.parseIntAscii(endpoint, 0, i);\n                endpointByIdMap.put(memberId, new MemberIngress(ctx, memberId, endpoint.substring(i + 1)));\n            }\n        }\n\n        return endpointByIdMap;\n    }\n\n    private Publication addNewLeaderIngressPublication(final Context ctx, final String channel, final int streamId)\n    {\n        final long registrationId = asyncAddIngressPublication(ctx, channel, streamId);\n        final long deadlineNs = nanoClock.nanoTime() + ctx.messageTimeoutNs();\n        do\n        {\n            final Publication publication = getIngressPublication(ctx, registrationId);\n            if (null != publication)\n            {\n                return publication;\n            }\n            idleStrategy.idle(ctx.runAgentInvokers());\n        }\n        while (nanoClock.nanoTime() < deadlineNs);\n\n        throw new TimeoutException(\"failed to add new leader ingress publication (leaderMemberId=\" + leaderMemberId +\n            \", leadershipTermId=\" + leadershipTermId + \", channel=\" + channel + \", streamId=\" + streamId +\n            \") within \" + SystemUtil.formatDuration(ctx.messageTimeoutNs()));\n    }\n\n    static long asyncAddIngressPublication(final Context ctx, final String channel, final int streamId)\n    {\n        if (ctx.isIngressExclusive())\n        {\n            return ctx.aeron().asyncAddExclusivePublication(channel, streamId);\n        }\n        else\n        {\n            return ctx.aeron().asyncAddPublication(channel, streamId);\n        }\n    }\n\n    static Publication getIngressPublication(final Context ctx, final long registrationId)\n    {\n        if (ctx.isIngressExclusive())\n        {\n            return ctx.aeron().getExclusivePublication(registrationId);\n        }\n        else\n        {\n            return ctx.aeron().getPublication(registrationId);\n        }\n    }\n\n    private void updateMemberEndpoints(final String ingressEndpoints, final int leaderMemberId)\n    {\n        CloseHelper.closeAll(endpointByIdMap.values());\n\n        final Int2ObjectHashMap<MemberIngress> map = parseIngressEndpoints(ctx, ingressEndpoints);\n        endpointByIdMap = map;\n\n        final MemberIngress newLeader = map.get(leaderMemberId);\n        final ChannelUri channelUri = ChannelUri.parse(ctx.ingressChannel());\n        if (channelUri.isUdp())\n        {\n            channelUri.put(CommonContext.ENDPOINT_PARAM_NAME, newLeader.endpoint);\n        }\n        publication = newLeader.publication =\n            addNewLeaderIngressPublication(ctx, channelUri.toString(), ctx.ingressStreamId());\n    }\n\n    private void onDisconnected()\n    {\n        publication.close();\n        state(State.AWAIT_NEW_LEADER, nanoClock.nanoTime() + ctx.newLeaderTimeoutNs());\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        final int templateId = messageHeaderDecoder.templateId();\n\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            if (egressListenerExtension != null)\n            {\n                egressListenerExtension.onExtensionMessage(\n                    messageHeaderDecoder.blockLength(),\n                    templateId,\n                    schemaId,\n                    messageHeaderDecoder.version(),\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    length - MessageHeaderDecoder.ENCODED_LENGTH);\n            }\n            else\n            {\n                throw new ClusterException(\n                    \"expected cluster egress schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \" actual=\" + schemaId);\n            }\n        }\n\n        switch (templateId)\n        {\n            case SessionMessageHeaderDecoder.TEMPLATE_ID:\n            {\n                sessionMessageHeaderDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = sessionMessageHeaderDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    egressListener.onMessage(\n                        sessionId,\n                        sessionMessageHeaderDecoder.timestamp(),\n                        buffer,\n                        offset + SESSION_HEADER_LENGTH,\n                        length - SESSION_HEADER_LENGTH,\n                        header);\n                }\n                break;\n            }\n\n            case SessionEventDecoder.TEMPLATE_ID:\n            {\n                sessionEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = sessionEventDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    final EventCode code = sessionEventDecoder.code();\n                    if (EventCode.CLOSED == code)\n                    {\n                        state(State.PENDING_CLOSE, 0);\n                    }\n\n                    egressListener.onSessionEvent(\n                        sessionEventDecoder.correlationId(),\n                        sessionId,\n                        sessionEventDecoder.leadershipTermId(),\n                        sessionEventDecoder.leaderMemberId(),\n                        code,\n                        sessionEventDecoder.detail());\n                }\n                break;\n            }\n\n            case NewLeaderEventDecoder.TEMPLATE_ID:\n            {\n                newLeaderEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = newLeaderEventDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    egressImage = (Image)header.context();\n                    onNewLeader(\n                        sessionId,\n                        newLeaderEventDecoder.leadershipTermId(),\n                        newLeaderEventDecoder.leaderMemberId(),\n                        newLeaderEventDecoder.ingressEndpoints());\n                }\n                break;\n            }\n\n            case AdminResponseDecoder.TEMPLATE_ID:\n            {\n                adminResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = adminResponseDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    final long correlationId = adminResponseDecoder.correlationId();\n                    final AdminRequestType requestType = adminResponseDecoder.requestType();\n                    final AdminResponseCode responseCode = adminResponseDecoder.responseCode();\n                    final String message = adminResponseDecoder.message();\n                    final int payloadOffset = adminResponseDecoder.offset() +\n                        AdminResponseDecoder.BLOCK_LENGTH +\n                        AdminResponseDecoder.messageHeaderLength() +\n                        message.length() +\n                        AdminResponseDecoder.payloadHeaderLength();\n                    final int payloadLength = adminResponseDecoder.payloadLength();\n\n                    egressListener.onAdminResponse(\n                        sessionId,\n                        correlationId,\n                        requestType,\n                        responseCode,\n                        message,\n                        buffer,\n                        payloadOffset,\n                        payloadLength);\n                }\n                break;\n            }\n\n            default:\n                break;\n        }\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private ControlledFragmentHandler.Action onControlledFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        final int templateId = messageHeaderDecoder.templateId();\n\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            if (null != controlledEgressListenerExtension)\n            {\n                return controlledEgressListenerExtension.onExtensionMessage(\n                    messageHeaderDecoder.blockLength(),\n                    templateId,\n                    schemaId,\n                    messageHeaderDecoder.version(),\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    length - MessageHeaderDecoder.ENCODED_LENGTH);\n            }\n            else\n            {\n                throw new ClusterException(\n                    \"expected cluster egress schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \" actual=\" + schemaId);\n            }\n        }\n\n        switch (templateId)\n        {\n            case SessionMessageHeaderDecoder.TEMPLATE_ID:\n            {\n                sessionMessageHeaderDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = sessionMessageHeaderDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    return controlledEgressListener.onMessage(\n                        sessionId,\n                        sessionMessageHeaderDecoder.timestamp(),\n                        buffer,\n                        offset + SESSION_HEADER_LENGTH,\n                        length - SESSION_HEADER_LENGTH,\n                        header);\n                }\n                break;\n            }\n\n            case SessionEventDecoder.TEMPLATE_ID:\n            {\n                sessionEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = sessionEventDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    final EventCode code = sessionEventDecoder.code();\n                    if (EventCode.CLOSED == code)\n                    {\n                        state(State.PENDING_CLOSE, 0);\n                    }\n\n                    controlledEgressListener.onSessionEvent(\n                        sessionEventDecoder.correlationId(),\n                        sessionId,\n                        sessionEventDecoder.leadershipTermId(),\n                        sessionEventDecoder.leaderMemberId(),\n                        code,\n                        sessionEventDecoder.detail());\n                }\n                break;\n            }\n\n            case NewLeaderEventDecoder.TEMPLATE_ID:\n            {\n                newLeaderEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = newLeaderEventDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    egressImage = (Image)header.context();\n                    onNewLeader(\n                        sessionId,\n                        newLeaderEventDecoder.leadershipTermId(),\n                        newLeaderEventDecoder.leaderMemberId(),\n                        newLeaderEventDecoder.ingressEndpoints());\n\n                    return ControlledFragmentHandler.Action.COMMIT;\n                }\n                break;\n            }\n\n            case AdminResponseDecoder.TEMPLATE_ID:\n            {\n                adminResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = adminResponseDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    final long correlationId = adminResponseDecoder.correlationId();\n                    final AdminRequestType requestType = adminResponseDecoder.requestType();\n                    final AdminResponseCode responseCode = adminResponseDecoder.responseCode();\n                    final String message = adminResponseDecoder.message();\n                    final int payloadOffset = adminResponseDecoder.offset() +\n                        AdminResponseDecoder.BLOCK_LENGTH +\n                        AdminResponseDecoder.messageHeaderLength() +\n                        message.length() +\n                        AdminResponseDecoder.payloadHeaderLength();\n                    final int payloadLength = adminResponseDecoder.payloadLength();\n\n                    controlledEgressListener.onAdminResponse(\n                        sessionId,\n                        correlationId,\n                        requestType,\n                        responseCode,\n                        message,\n                        buffer,\n                        payloadOffset,\n                        payloadLength);\n                }\n                break;\n            }\n\n            default:\n                break;\n        }\n\n        return ControlledFragmentHandler.Action.CONTINUE;\n    }\n\n    private void closeSession()\n    {\n        idleStrategy.reset();\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + SessionCloseRequestEncoder.BLOCK_LENGTH;\n        final SessionCloseRequestEncoder sessionCloseRequestEncoder = new SessionCloseRequestEncoder();\n        int attempts = SEND_ATTEMPTS;\n\n        while (true)\n        {\n            final long position = publication.tryClaim(length, bufferClaim);\n\n            trackIngressPublicationResult(position);\n\n            if (position > 0)\n            {\n                sessionCloseRequestEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .leadershipTermId(leadershipTermId)\n                    .clusterSessionId(clusterSessionId);\n\n                bufferClaim.commit();\n                break;\n            }\n\n            if (--attempts <= 0)\n            {\n                break;\n            }\n\n            idleStrategy.idle();\n        }\n    }\n\n    private void state(final State newState, final long newStateDeadline)\n    {\n        //System.out.println(\n        //    Instant.now() + \" AeronCluster \" + state + \" -> \" + newState + \" (\" + newStateDeadline + \")\");\n        state = newState;\n        stateDeadline = newStateDeadline;\n    }\n\n    private enum State\n    {\n        /**\n         * The session is connected to a leader.\n         */\n        CONNECTED,\n        /**\n         * The session got disconnected from a leader, waiting for a new one.\n         */\n        AWAIT_NEW_LEADER,\n        /**\n         * The session got notified of a new leader, i.e. egress connected, waiting for ingress to connect.\n         */\n        AWAIT_NEW_LEADER_CONNECTION,\n        /**\n         * The session got notified it's closed or the client decided it can't continue.\n         * The client is about to close, possibly during the next poll.\n         */\n        PENDING_CLOSE,\n        /**\n         * The session and client are closed. Terminal state.\n         */\n        CLOSED,\n    }\n\n    /**\n     * Configuration options for cluster client.\n     */\n    @Config(existsInC = false)\n    public static final class Configuration\n    {\n        private Configuration()\n        {\n        }\n\n        /**\n         * Major version of the network protocol from client to consensus module. If these don't match then client\n         * and consensus module are not compatible.\n         */\n        public static final int PROTOCOL_MAJOR_VERSION = 0;\n\n        /**\n         * Minor version of the network protocol from client to consensus module. If these don't match then some\n         * features may not be available.\n         */\n        public static final int PROTOCOL_MINOR_VERSION = 3;\n\n        /**\n         * Patch version of the network protocol from client to consensus module. If these don't match then bug fixes\n         * may not have been applied.\n         */\n        public static final int PROTOCOL_PATCH_VERSION = 0;\n\n        /**\n         * Combined semantic version for the client to consensus module protocol.\n         *\n         * @see SemanticVersion\n         */\n        public static final int PROTOCOL_SEMANTIC_VERSION = SemanticVersion.compose(\n            PROTOCOL_MAJOR_VERSION, PROTOCOL_MINOR_VERSION, PROTOCOL_PATCH_VERSION);\n\n        /**\n         * Timeout when waiting on a message to be sent or received.\n         */\n        @Config\n        public static final String MESSAGE_TIMEOUT_PROP_NAME = \"aeron.cluster.message.timeout\";\n\n        /**\n         * Default timeout when waiting on a message to be sent or received.\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 5L * 1000 * 1000 * 1000)\n        public static final long MESSAGE_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5);\n\n        /**\n         * Property name for the comma separated map of cluster memberId to ingress endpoint for use with unicast. This\n         * is the endpoint values which get substituted into the {@link #INGRESS_CHANNEL_PROP_NAME} when using UDP\n         * unicast.\n         * <p>\n         * {@code \"0=endpoint,1=endpoint,2=endpoint\"}\n         * <p>\n         * Each member of the list will be substituted for the endpoint in the {@link #INGRESS_CHANNEL_PROP_NAME} value.\n         */\n        @Config\n        public static final String INGRESS_ENDPOINTS_PROP_NAME = \"aeron.cluster.ingress.endpoints\";\n\n        /**\n         * Default comma separated list of cluster ingress endpoints.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String INGRESS_ENDPOINTS_DEFAULT = null;\n\n        /**\n         * Channel for sending messages to a cluster. Ideally this will be a multicast address otherwise unicast will\n         * be required and the {@link #INGRESS_ENDPOINTS_PROP_NAME} is used to substitute the endpoints from the\n         * {@link #INGRESS_ENDPOINTS_PROP_NAME} list.\n         */\n        @Config\n        public static final String INGRESS_CHANNEL_PROP_NAME = \"aeron.cluster.ingress.channel\";\n\n        /**\n         * Channel for sending messages to a cluster.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String INGRESS_CHANNEL_DEFAULT = null;\n\n        /**\n         * Stream id within a channel for sending messages to a cluster.\n         */\n        @Config\n        public static final String INGRESS_STREAM_ID_PROP_NAME = \"aeron.cluster.ingress.stream.id\";\n\n        /**\n         * Default stream id within a channel for sending messages to a cluster.\n         */\n        @Config\n        public static final int INGRESS_STREAM_ID_DEFAULT = 101;\n\n        /**\n         * Channel for receiving response messages from a cluster.\n         * <p>\n         * Channel's <em>endpoint</em> can be specified explicitly (i.e. by providing address and port pair) or\n         * by using zero as a port number. Here is an example of valid response channels:\n         * <ul>\n         *     <li>{@code aeron:udp?endpoint=localhost:9020} - listen on port {@code 9020} on localhost.</li>\n         *     <li>{@code aeron:udp?endpoint=192.168.10.10:9020} - listen on port {@code 9020} on\n         *     {@code 192.168.10.10}.</li>\n         *     <li>{@code aeron:udp?endpoint=localhost:0} - in this case the port is unspecified and the OS\n         *     will assign a free port from the\n         *     <a href=\"https://en.wikipedia.org/wiki/Ephemeral_port\">ephemeral port range</a>.</li>\n         * </ul>\n         */\n        @Config\n        public static final String EGRESS_CHANNEL_PROP_NAME = \"aeron.cluster.egress.channel\";\n\n        /**\n         * Channel for receiving response messages from a cluster.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String EGRESS_CHANNEL_DEFAULT = null;\n\n        /**\n         * Stream id within a channel for receiving messages from a cluster.\n         */\n        @Config\n        public static final String EGRESS_STREAM_ID_PROP_NAME = \"aeron.cluster.egress.stream.id\";\n\n        /**\n         * Default stream id within a channel for receiving messages from a cluster.\n         */\n        @Config\n        public static final int EGRESS_STREAM_ID_DEFAULT = 102;\n\n        /**\n         * System property to name Cluster client. Defaults to empty string.\n         *\n         * @since 1.49.0\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\", skipCDefaultValidation = true)\n        public static final String CLIENT_NAME_PROP_NAME = \"aeron.cluster.client.name\";\n\n        /**\n         * The timeout in nanoseconds to wait for a message.\n         *\n         * @return timeout in nanoseconds to wait for a message.\n         * @see #MESSAGE_TIMEOUT_PROP_NAME\n         */\n        public static long messageTimeoutNs()\n        {\n            return getDurationInNanos(MESSAGE_TIMEOUT_PROP_NAME, MESSAGE_TIMEOUT_DEFAULT_NS);\n        }\n\n        /**\n         * The value {@link #INGRESS_ENDPOINTS_DEFAULT} or system property {@link #INGRESS_ENDPOINTS_PROP_NAME} if set.\n         *\n         * @return {@link #INGRESS_ENDPOINTS_DEFAULT} or system property {@link #INGRESS_ENDPOINTS_PROP_NAME} if set.\n         */\n        public static String ingressEndpoints()\n        {\n            return System.getProperty(INGRESS_ENDPOINTS_PROP_NAME, INGRESS_ENDPOINTS_DEFAULT);\n        }\n\n        /**\n         * The value {@link #INGRESS_CHANNEL_DEFAULT} or system property {@link #INGRESS_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #INGRESS_CHANNEL_DEFAULT} or system property {@link #INGRESS_CHANNEL_PROP_NAME} if set.\n         */\n        public static String ingressChannel()\n        {\n            return System.getProperty(INGRESS_CHANNEL_PROP_NAME, INGRESS_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #INGRESS_STREAM_ID_DEFAULT} or system property {@link #INGRESS_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #INGRESS_STREAM_ID_DEFAULT} or system property {@link #INGRESS_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int ingressStreamId()\n        {\n            return Integer.getInteger(INGRESS_STREAM_ID_PROP_NAME, INGRESS_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #EGRESS_CHANNEL_DEFAULT} or system property {@link #EGRESS_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #EGRESS_CHANNEL_DEFAULT} or system property {@link #EGRESS_CHANNEL_PROP_NAME} if set.\n         */\n        public static String egressChannel()\n        {\n            return System.getProperty(EGRESS_CHANNEL_PROP_NAME, EGRESS_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #EGRESS_STREAM_ID_DEFAULT} or system property {@link #EGRESS_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #EGRESS_STREAM_ID_DEFAULT} or system property {@link #EGRESS_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int egressStreamId()\n        {\n            return Integer.getInteger(EGRESS_STREAM_ID_PROP_NAME, EGRESS_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * Get the configured client name.\n         *\n         * @return specified client name or empty string if not set.\n         * @see #CLIENT_NAME_PROP_NAME\n         * @since 1.49.0\n         */\n        public static String clientName()\n        {\n            return getProperty(CLIENT_NAME_PROP_NAME, \"\");\n        }\n    }\n\n    /**\n     * Context for cluster session and connection.\n     */\n    public static final class Context implements Cloneable\n    {\n        private static final VarHandle IS_CONCLUDED_VH;\n\n        static\n        {\n            try\n            {\n                IS_CONCLUDED_VH = MethodHandles.lookup().findVarHandle(Context.class, \"isConcluded\", boolean.class);\n            }\n            catch (final ReflectiveOperationException ex)\n            {\n                throw new ExceptionInInitializerError(ex);\n            }\n        }\n\n        private volatile boolean isConcluded;\n        private long messageTimeoutNs = Configuration.messageTimeoutNs();\n        private long newLeaderTimeoutNs = NULL_VALUE;\n        private String ingressEndpoints = Configuration.ingressEndpoints();\n        private String ingressChannel = Configuration.ingressChannel();\n        private int ingressStreamId = Configuration.ingressStreamId();\n        private String egressChannel = Configuration.egressChannel();\n        private int egressStreamId = Configuration.egressStreamId();\n        private IdleStrategy idleStrategy;\n        private String aeronDirectoryName = CommonContext.getAeronDirectoryName();\n        private Aeron aeron;\n        private CredentialsSupplier credentialsSupplier;\n        private boolean ownsAeronClient = false;\n        private boolean isIngressExclusive = true;\n        private ErrorHandler errorHandler = Aeron.Configuration.DEFAULT_ERROR_HANDLER;\n        private boolean isDirectAssemblers = false;\n        private EgressListener egressListener;\n        private ControlledEgressListener controlledEgressListener;\n        private AgentInvoker agentInvoker;\n        private String clientName = Configuration.clientName();\n\n        /**\n         * Construct a Context using default values and loading from system properties.\n         */\n        public Context()\n        {\n        }\n\n        /**\n         * Perform a shallow copy of the object.\n         *\n         * @return a shallow copy of the object.\n         */\n        public Context clone()\n        {\n            try\n            {\n                return (Context)super.clone();\n            }\n            catch (final CloneNotSupportedException ex)\n            {\n                throw new RuntimeException(ex);\n            }\n        }\n\n        /**\n         * Conclude configuration by setting up defaults when specifics are not provided.\n         */\n        public void conclude()\n        {\n            if ((boolean)IS_CONCLUDED_VH.getAndSet(this, true))\n            {\n                throw new ConcurrentConcludeException();\n            }\n\n            if (Strings.isEmpty(ingressChannel))\n            {\n                throw new ConfigurationException(\"ingressChannel must be specified\");\n            }\n\n            if (ingressChannel.startsWith(CommonContext.IPC_CHANNEL))\n            {\n                if (null != ingressEndpoints)\n                {\n                    throw new ConfigurationException(\n                        \"AeronCluster.Context ingressEndpoints must be null when using IPC ingress\");\n                }\n            }\n\n            if (Strings.isEmpty(egressChannel))\n            {\n                throw new ConfigurationException(\"egressChannel must be specified\");\n            }\n\n            final ChannelUri egressChannelUri = ChannelUri.parse(egressChannel);\n            if (egressChannelUri.isUdp())\n            {\n                egressChannelUri.put(REJOIN_PARAM_NAME, \"false\");\n                egressChannel = egressChannelUri.toString();\n            }\n\n            if (clientName.length() > Aeron.Configuration.MAX_CLIENT_NAME_LENGTH)\n            {\n                throw new ConfigurationException(\n                    \"AeronCluster.Context.clientName length must be <= \" + Aeron.Configuration.MAX_CLIENT_NAME_LENGTH);\n            }\n\n            if (null == aeron)\n            {\n                aeron = Aeron.connect(\n                    new Aeron.Context()\n                        .aeronDirectoryName(aeronDirectoryName)\n                        .errorHandler(errorHandler)\n                        .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE)\n                        .clientName(clientName.isEmpty() ? \"cluster-client\" : clientName));\n\n                ownsAeronClient = true;\n            }\n\n            if (null == idleStrategy)\n            {\n                idleStrategy = new BackoffIdleStrategy(1, 10, 1000, 1000);\n            }\n\n            if (null == credentialsSupplier)\n            {\n                credentialsSupplier = new NullCredentialsSupplier();\n            }\n\n            if (null == egressListener)\n            {\n                egressListener =\n                    (clusterSessionId, timestamp, buffer, offset, length, header) ->\n                    {\n                        throw new ConfigurationException(\n                            \"egressListener must be specified on AeronCluster.Context\");\n                    };\n            }\n\n            if (null == controlledEgressListener)\n            {\n                controlledEgressListener =\n                    (clusterSessionId, timestamp, buffer, offset, length, header) ->\n                    {\n                        throw new ConfigurationException(\n                            \"controlledEgressListener must be specified on AeronCluster.Context\");\n                    };\n            }\n        }\n\n        /**\n         * Has the context had the {@link #conclude()} method called.\n         *\n         * @return true if the {@link #conclude()} method has been called.\n         */\n        public boolean isConcluded()\n        {\n            return isConcluded;\n        }\n\n        /**\n         * Set the message timeout in nanoseconds to wait for sending or receiving a message.\n         *\n         * @param messageTimeoutNs to wait for sending or receiving a message.\n         * @return this for a fluent API.\n         * @see Configuration#MESSAGE_TIMEOUT_PROP_NAME\n         */\n        public Context messageTimeoutNs(final long messageTimeoutNs)\n        {\n            this.messageTimeoutNs = messageTimeoutNs;\n            return this;\n        }\n\n        /**\n         * The message timeout in nanoseconds to wait for sending or receiving a message.\n         *\n         * @return the message timeout in nanoseconds to wait for sending or receiving a message.\n         * @see Configuration#MESSAGE_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long messageTimeoutNs()\n        {\n            return CommonContext.checkDebugTimeout(messageTimeoutNs, TimeUnit.NANOSECONDS);\n        }\n\n        /**\n         * The timeout to wait for a new leader after noticing a disconnection from the previous one. Upon timeout the\n         * cluster will be considered lost and the client will close. If set to {@link Aeron#NULL_VALUE}, a reasonable\n         * default will be used.\n         *\n         * @param newLeaderTimeoutNs the new leader timeout in nanoseconds or {@link Aeron#NULL_VALUE}.\n         * @return this for a fluent API.\n         */\n        public Context newLeaderTimeoutNs(final long newLeaderTimeoutNs)\n        {\n            if (!(0 < newLeaderTimeoutNs || NULL_VALUE == newLeaderTimeoutNs))\n            {\n                throw new IllegalArgumentException(\n                    \"newLeaderTimeoutNs must be positive or -1, but was \" + newLeaderTimeoutNs);\n            }\n            this.newLeaderTimeoutNs = newLeaderTimeoutNs;\n            return this;\n        }\n\n        /**\n         * The timeout to wait for a new leader after noticing a disconnection from the previous one. Upon timeout the\n         * cluster will be considered lost and the client will close.\n         *\n         * @return the new leader timeout in nanoseconds.\n         */\n        public long newLeaderTimeoutNs()\n        {\n            return CommonContext.checkDebugTimeout(newLeaderTimeoutNs, TimeUnit.NANOSECONDS);\n        }\n\n        /**\n         * The endpoints representing members for use with unicast to be substituted into the {@link #ingressChannel()}\n         * for endpoints. A null value can be used when multicast where the {@link #ingressChannel()} contains the\n         * multicast endpoint.\n         *\n         * @param clusterMembers which are all candidates to be leader.\n         * @return this for a fluent API.\n         * @see Configuration#INGRESS_ENDPOINTS_PROP_NAME\n         */\n        public Context ingressEndpoints(final String clusterMembers)\n        {\n            this.ingressEndpoints = clusterMembers;\n            return this;\n        }\n\n        /**\n         * The endpoints representing members for use with unicast to be substituted into the {@link #ingressChannel()}\n         * for endpoints. A null value can be used when multicast where the {@link #ingressChannel()} contains the\n         * multicast endpoint.\n         *\n         * @return member endpoints of the cluster which are all candidates to be leader.\n         * @see Configuration#INGRESS_ENDPOINTS_PROP_NAME\n         */\n        @Config\n        public String ingressEndpoints()\n        {\n            return ingressEndpoints;\n        }\n\n        /**\n         * Set the channel parameter for the ingress channel.\n         * <p>\n         * The endpoints representing members for use with unicast are substituted from {@link #ingressEndpoints()}\n         * for endpoints. If this channel contains a multicast endpoint, then {@link #ingressEndpoints()} should\n         * be set to null.\n         *\n         * @param channel parameter for the ingress channel.\n         * @return this for a fluent API.\n         * @see Configuration#INGRESS_CHANNEL_PROP_NAME\n         */\n        public Context ingressChannel(final String channel)\n        {\n            ingressChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the ingress channel.\n         * <p>\n         * The endpoints representing members for use with unicast are substituted from {@link #ingressEndpoints()}\n         * for endpoints. A null value can be used when multicast where this contains the multicast endpoint.\n         *\n         * @return the channel parameter for the ingress channel.\n         * @see Configuration#INGRESS_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String ingressChannel()\n        {\n            return ingressChannel;\n        }\n\n        /**\n         * Set the stream id for the ingress channel.\n         *\n         * @param streamId for the ingress channel.\n         * @return this for a fluent API\n         * @see Configuration#INGRESS_STREAM_ID_PROP_NAME\n         */\n        public Context ingressStreamId(final int streamId)\n        {\n            ingressStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the ingress channel.\n         *\n         * @return the stream id for the ingress channel.\n         * @see Configuration#INGRESS_STREAM_ID_PROP_NAME\n         */\n        @Config\n        public int ingressStreamId()\n        {\n            return ingressStreamId;\n        }\n\n        /**\n         * Set the channel parameter for the egress channel.\n         *\n         * @param channel parameter for the egress channel.\n         * @return this for a fluent API.\n         * @see Configuration#EGRESS_CHANNEL_PROP_NAME\n         */\n        public Context egressChannel(final String channel)\n        {\n            egressChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the egress channel.\n         *\n         * @return the channel parameter for the egress channel.\n         * @see Configuration#EGRESS_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String egressChannel()\n        {\n            return egressChannel;\n        }\n\n        /**\n         * Set the stream id for the egress channel.\n         *\n         * @param streamId for the egress channel.\n         * @return this for a fluent API\n         * @see Configuration#EGRESS_STREAM_ID_PROP_NAME\n         */\n        public Context egressStreamId(final int streamId)\n        {\n            egressStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the egress channel.\n         *\n         * @return the stream id for the egress channel.\n         * @see Configuration#EGRESS_STREAM_ID_PROP_NAME\n         */\n        @Config\n        public int egressStreamId()\n        {\n            return egressStreamId;\n        }\n\n        /**\n         * Set the {@link IdleStrategy} used when waiting for responses.\n         *\n         * @param idleStrategy used when waiting for responses.\n         * @return this for a fluent API.\n         */\n        public Context idleStrategy(final IdleStrategy idleStrategy)\n        {\n            this.idleStrategy = idleStrategy;\n            return this;\n        }\n\n        /**\n         * Get the {@link IdleStrategy} used when waiting for responses.\n         *\n         * @return the {@link IdleStrategy} used when waiting for responses.\n         */\n        public IdleStrategy idleStrategy()\n        {\n            return idleStrategy;\n        }\n\n        /**\n         * Sets the name used to identify this client among other clients connected to the Cluster.\n         *\n         * @param clientName to use.\n         * @return this for a fluent API.\n         * @see AeronCluster.Configuration#CLIENT_NAME_PROP_NAME\n         * @since 1.49.0\n         */\n        public Context clientName(final String clientName)\n        {\n            this.clientName = Strings.isEmpty(clientName) ? \"\" : clientName;\n            return this;\n        }\n\n        /**\n         * Returns the name of this Cluster client.\n         *\n         * @return name of this client or empty String if not configured.\n         * @see AeronCluster.Configuration#CLIENT_NAME_PROP_NAME\n         * @since 1.49.0\n         */\n        public String clientName()\n        {\n            return clientName;\n        }\n\n        /**\n         * Set the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @param aeronDirectoryName the top level Aeron directory.\n         * @return this for a fluent API.\n         */\n        public Context aeronDirectoryName(final String aeronDirectoryName)\n        {\n            this.aeronDirectoryName = aeronDirectoryName;\n            return this;\n        }\n\n        /**\n         * Get the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @return The top level Aeron directory.\n         */\n        public String aeronDirectoryName()\n        {\n            return aeronDirectoryName;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * This client will be closed when the {@link AeronCluster#close()} or {@link #close()} methods are called if\n         * {@link #ownsAeronClient()} is true.\n         *\n         * @param aeron client for communicating with the local Media Driver.\n         * @return this for a fluent API.\n         * @see Aeron#connect()\n         */\n        public Context aeron(final Aeron aeron)\n        {\n            this.aeron = aeron;\n            return this;\n        }\n\n        /**\n         * {@link Aeron} client for communicating with the local Media Driver.\n         * <p>\n         * If not provided then a default will be established during {@link #conclude()} by calling\n         * {@link Aeron#connect()}.\n         *\n         * @return client for communicating with the local Media Driver.\n         */\n        public Aeron aeron()\n        {\n            return aeron;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @param ownsAeronClient does this context own the {@link #aeron()} client.\n         * @return this for a fluent API.\n         */\n        public Context ownsAeronClient(final boolean ownsAeronClient)\n        {\n            this.ownsAeronClient = ownsAeronClient;\n            return this;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @return does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         */\n        public boolean ownsAeronClient()\n        {\n            return ownsAeronClient;\n        }\n\n        /**\n         * Is ingress to the cluster exclusively from a single thread to this client? The client should not be used\n         * from another thread, e.g. a separate thread calling {@link AeronCluster#sendKeepAlive()} - which is awful\n         * design by the way!\n         *\n         * @param isIngressExclusive true if ingress to the cluster is exclusively from a single thread for this client?\n         * @return this for a fluent API.\n         */\n        public Context isIngressExclusive(final boolean isIngressExclusive)\n        {\n            this.isIngressExclusive = isIngressExclusive;\n            return this;\n        }\n\n        /**\n         * Is ingress the {@link Publication} to the cluster used exclusively from a single thread to this client?\n         *\n         * @return true if the ingress {@link Publication} is to be used exclusively from a single thread?\n         */\n        public boolean isIngressExclusive()\n        {\n            return isIngressExclusive;\n        }\n\n        /**\n         * Set the {@link CredentialsSupplier} to be used for authentication with the cluster.\n         *\n         * @param credentialsSupplier to be used for authentication with the cluster.\n         * @return this for fluent API.\n         */\n        public Context credentialsSupplier(final CredentialsSupplier credentialsSupplier)\n        {\n            this.credentialsSupplier = credentialsSupplier;\n            return this;\n        }\n\n        /**\n         * Get the {@link CredentialsSupplier} to be used for authentication with the cluster.\n         *\n         * @return the {@link CredentialsSupplier} to be used for authentication with the cluster.\n         */\n        public CredentialsSupplier credentialsSupplier()\n        {\n            return credentialsSupplier;\n        }\n\n        /**\n         * Set the {@link ErrorHandler} to be used for handling any exceptions.\n         *\n         * @param errorHandler Method to handle objects of type Throwable.\n         * @return this for fluent API.\n         */\n        public Context errorHandler(final ErrorHandler errorHandler)\n        {\n            this.errorHandler = errorHandler;\n            return this;\n        }\n\n        /**\n         * Get the {@link ErrorHandler} to be used for handling any exceptions.\n         *\n         * @return The {@link ErrorHandler} to be used for handling any exceptions.\n         */\n        public ErrorHandler errorHandler()\n        {\n            return errorHandler;\n        }\n\n        /**\n         * Are direct buffers used for fragment assembly on egress?\n         *\n         * @param isDirectAssemblers true if direct buffers used for fragment assembly on egress.\n         * @return this for a fluent API.\n         */\n        public Context isDirectAssemblers(final boolean isDirectAssemblers)\n        {\n            this.isDirectAssemblers = isDirectAssemblers;\n            return this;\n        }\n\n        /**\n         * Are direct buffers used for fragment assembly on egress?\n         *\n         * @return true if direct buffers used for fragment assembly on egress.\n         */\n        public boolean isDirectAssemblers()\n        {\n            return isDirectAssemblers;\n        }\n\n        /**\n         * Set the {@link EgressListener} function that will be called when polling for egress via\n         * {@link AeronCluster#pollEgress()}.\n         * <p>\n         * Only {@link EgressListener#onMessage(long, long, DirectBuffer, int, int, Header)} will be dispatched\n         * when using {@link AeronCluster#pollEgress()}.\n         *\n         * @param listener function that will be called when polling for egress via {@link AeronCluster#pollEgress()}.\n         * @return this for a fluent API.\n         */\n        public Context egressListener(final EgressListener listener)\n        {\n            this.egressListener = listener;\n            return this;\n        }\n\n        /**\n         * Get the {@link EgressListener} function that will be called when polling for egress via\n         * {@link AeronCluster#pollEgress()}.\n         *\n         * @return the {@link EgressListener} function that will be called when polling for egress via\n         * {@link AeronCluster#pollEgress()}.\n         */\n        public EgressListener egressListener()\n        {\n            return egressListener;\n        }\n\n        /**\n         * Set the {@link ControlledEgressListener} function that will be called when polling for egress via\n         * {@link AeronCluster#controlledPollEgress()}.\n         * <p>\n         * Only {@link ControlledEgressListener#onMessage(long, long, DirectBuffer, int, int, Header)} will be\n         * dispatched when using {@link AeronCluster#controlledPollEgress()}.\n         *\n         * @param listener function that will be called when polling for egress via\n         *                 {@link AeronCluster#controlledPollEgress()}.\n         * @return this for a fluent API.\n         */\n        public Context controlledEgressListener(final ControlledEgressListener listener)\n        {\n            this.controlledEgressListener = listener;\n            return this;\n        }\n\n        /**\n         * Get the {@link ControlledEgressListener} function that will be called when polling for egress via\n         * {@link AeronCluster#controlledPollEgress()}.\n         *\n         * @return the {@link ControlledEgressListener} function that will be called when polling for egress via\n         * {@link AeronCluster#controlledPollEgress()}.\n         */\n        public ControlledEgressListener controlledEgressListener()\n        {\n            return controlledEgressListener;\n        }\n\n        /**\n         * Set the {@link AgentInvoker} to be invoked in addition to any invoker used by the {@link #aeron()} instance.\n         * <p>\n         * Useful for when running on a low thread count scenario.\n         * <p>\n         * <em><strong>Note:</strong> it is the responsibility of the user to call {@code agentInvoker} and the\n         * {@code aeron.conductorAgentInvoker()} periodically.</em>\n         *\n         * @param agentInvoker to be invoked while awaiting a response in the client or when awaiting completion.\n         * @return this for a fluent API.\n         */\n        public Context agentInvoker(final AgentInvoker agentInvoker)\n        {\n            this.agentInvoker = agentInvoker;\n            return this;\n        }\n\n        /**\n         * Get the {@link AgentInvoker} to be invoked in addition to any invoker used by the {@link #aeron()} instance.\n         *\n         * @return the {@link AgentInvoker} that is used.\n         */\n        public AgentInvoker agentInvoker()\n        {\n            return agentInvoker;\n        }\n\n        /**\n         * Close the context and free applicable resources.\n         * <p>\n         * If {@link #ownsAeronClient()} is true then the {@link #aeron()} client will be closed.\n         */\n        public void close()\n        {\n            if (ownsAeronClient)\n            {\n                CloseHelper.close(aeron);\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"AeronCluster.Context\" +\n                \"\\n{\" +\n                \"\\n    isConcluded=\" + isConcluded() +\n                \"\\n    ownsAeronClient=\" + ownsAeronClient +\n                \"\\n    aeronDirectoryName='\" + aeronDirectoryName + '\\'' +\n                \"\\n    aeron=\" + aeron +\n                \"\\n    messageTimeoutNs=\" + messageTimeoutNs +\n                \"\\n    newLeaderTimeoutNs=\" + newLeaderTimeoutNs +\n                \"\\n    ingressEndpoints='\" + ingressEndpoints + '\\'' +\n                \"\\n    ingressChannel='\" + ingressChannel + '\\'' +\n                \"\\n    ingressStreamId=\" + ingressStreamId +\n                \"\\n    egressChannel='\" + egressChannel + '\\'' +\n                \"\\n    egressStreamId=\" + egressStreamId +\n                \"\\n    idleStrategy=\" + idleStrategy +\n                \"\\n    credentialsSupplier=\" + credentialsSupplier +\n                \"\\n    isIngressExclusive=\" + isIngressExclusive +\n                \"\\n    errorHandler=\" + errorHandler +\n                \"\\n    isDirectAssemblers=\" + isDirectAssemblers +\n                \"\\n    egressListener=\" + egressListener +\n                \"\\n    controlledEgressListener=\" + controlledEgressListener +\n                \"\\n}\";\n        }\n\n        int runAgentInvokers()\n        {\n            int workDone = 0;\n\n            final AgentInvoker conductorAgentInvoker = aeron.conductorAgentInvoker();\n            if (null != conductorAgentInvoker)\n            {\n                workDone += conductorAgentInvoker.invoke();\n            }\n\n            final AgentInvoker agentInvoker = this.agentInvoker;\n            if (null != agentInvoker)\n            {\n                workDone += agentInvoker.invoke();\n            }\n\n            return workDone;\n        }\n    }\n\n    /**\n     * Allows for the async establishment of a cluster session. {@link #poll()} should be called repeatedly until\n     * it returns a non-null value with the new {@link AeronCluster} client. On error {@link #close()} should be called\n     * to clean up allocated resources.\n     */\n    public static final class AsyncConnect implements AutoCloseable\n    {\n        /**\n         * Represents connection state.\n         */\n        public enum State\n        {\n            /**\n             * Create egress subscription.\n             */\n            CREATE_EGRESS_SUBSCRIPTION(-1),\n            /**\n             * Create ingress publication.\n             */\n            CREATE_INGRESS_PUBLICATIONS(0),\n            /**\n             * Await ingress publication connected.\n             */\n            AWAIT_PUBLICATION_CONNECTED(1),\n            /**\n             * Send message to Cluster.\n             */\n            SEND_MESSAGE(2),\n            /**\n             * Poll for Cluster response.\n             */\n            POLL_RESPONSE(3),\n            /**\n             * Initialize internal state.\n             */\n            CONCLUDE_CONNECT(4),\n            /**\n             * Connection established.\n             */\n            DONE(5);\n\n            private static final State[] STATES = values();\n\n            final int step;\n\n            State(final int step)\n            {\n                this.step = step;\n            }\n\n            static State fromStep(final int step)\n            {\n                if (step < CREATE_EGRESS_SUBSCRIPTION.step || step > DONE.step)\n                {\n                    return null;\n                }\n                return STATES[step + 1];\n            }\n        }\n\n        private Image egressImage;\n        private final long deadlineNs;\n        private long leaderHeartbeatTimeoutNs;\n        private long correlationId = NULL_VALUE;\n        private long clusterSessionId;\n        private long leadershipTermId;\n        private int leaderMemberId;\n        private State state = State.CREATE_EGRESS_SUBSCRIPTION;\n        private int messageLength = 0;\n\n        private final Context ctx;\n        private final NanoClock nanoClock;\n        private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n        private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n\n        private Subscription egressSubscription;\n        private EgressPoller egressPoller;\n        private long egressRegistrationId = NULL_VALUE;\n        private Int2ObjectHashMap<MemberIngress> memberByIdMap;\n        private long ingressRegistrationId = NULL_VALUE;\n        private Publication ingressPublication;\n\n        private AsyncConnect(final Context ctx)\n        {\n            this(ctx, ctx.aeron().context().nanoClock().nanoTime() + ctx.messageTimeoutNs());\n        }\n\n        AsyncConnect(final Context ctx, final long deadlineNs)\n        {\n            this.ctx = ctx;\n\n            memberByIdMap = parseIngressEndpoints(ctx, ctx.ingressEndpoints());\n            nanoClock = ctx.aeron().context().nanoClock();\n            this.deadlineNs = deadlineNs;\n        }\n\n        /**\n         * Close allocated resources. Must be called on error. On success this is a no op.\n         */\n        public void close()\n        {\n            if (State.DONE != state)\n            {\n                final ErrorHandler errorHandler = ctx.errorHandler();\n                if (null != ingressPublication)\n                {\n                    CloseHelper.close(errorHandler, ingressPublication);\n                }\n                else if (NULL_VALUE != ingressRegistrationId)\n                {\n                    ctx.aeron().asyncRemovePublication(ingressRegistrationId);\n                }\n\n                if (null != egressSubscription)\n                {\n                    CloseHelper.close(errorHandler, egressSubscription);\n                }\n                else if (NULL_VALUE != egressRegistrationId)\n                {\n                    ctx.aeron().asyncRemoveSubscription(egressRegistrationId);\n                }\n\n                CloseHelper.closeAll(errorHandler, memberByIdMap.values());\n                ctx.close();\n            }\n        }\n\n        /**\n         * Indicates which step in the connect process has been reached.\n         *\n         * @return which step in the connect process has reached.\n         */\n        public int step()\n        {\n            return state.step;\n        }\n\n\n        /**\n         * Get the current connection state.\n         *\n         * @return current state.\n         */\n        public State state()\n        {\n            return state;\n        }\n\n        private void state(final State newState)\n        {\n//            System.out.println(\"AeronCluster.AsyncConnect \" + state + \" -> \" + stepName(newState));\n            state = newState;\n        }\n\n        /**\n         * Get the String representation of a step in the connect process.\n         *\n         * @param step to get the string representation for.\n         * @return the String representation of a step in the connect process.\n         * @see #step()\n         */\n        public static String stepName(final int step)\n        {\n            final State state = State.fromStep(step);\n            return null != state ? state.name() : \"<unknown>\";\n        }\n\n        /**\n         * Poll to advance steps in the connection until complete or error.\n         *\n         * @return null if not yet complete then {@link AeronCluster} when complete.\n         */\n        public AeronCluster poll()\n        {\n            checkDeadline();\n            ctx.runAgentInvokers();\n\n            switch (state)\n            {\n                case CREATE_EGRESS_SUBSCRIPTION:\n                    createEgressSubscription();\n                    break;\n\n                case CREATE_INGRESS_PUBLICATIONS:\n                    createIngressPublications();\n                    break;\n\n                case AWAIT_PUBLICATION_CONNECTED:\n                    awaitPublicationConnected();\n                    break;\n\n                case SEND_MESSAGE:\n                    sendMessage();\n                    break;\n\n                case POLL_RESPONSE:\n                    pollResponse();\n                    break;\n\n                case CONCLUDE_CONNECT:\n                    return concludeConnect();\n\n                default:\n                    break;\n            }\n\n            return null;\n        }\n\n        private void checkDeadline()\n        {\n            if (deadlineNs - nanoClock.nanoTime() < 0)\n            {\n                final boolean isConnected = null != egressSubscription && egressSubscription.isConnected();\n                final String egressChannel = null != egressSubscription ?\n                    egressSubscription.tryResolveChannelEndpointPort() : \"<unknown>\";\n                final TimeoutException ex = new TimeoutException(\n                    \"cluster connect timeout: state=\" + state +\n                    \" messageTimeout=\" + SystemUtil.formatDuration(ctx.messageTimeoutNs()) +\n                    \" ingressChannel=\" + ctx.ingressChannel() +\n                    \" ingressEndpoints=\" + ctx.ingressEndpoints() +\n                    \" ingressPublication=\" + ingressPublication +\n                    \" egress.isConnected=\" + isConnected +\n                    \" responseChannel=\" + egressChannel);\n\n                for (final MemberIngress member : memberByIdMap.values())\n                {\n                    if (null != member.publicationException)\n                    {\n                        ex.addSuppressed(member.publicationException);\n                    }\n                }\n\n                throw ex;\n            }\n\n            if (Thread.currentThread().isInterrupted())\n            {\n                throw new AeronException(\"unexpected interrupt\");\n            }\n        }\n\n        private void createEgressSubscription()\n        {\n            if (NULL_VALUE == egressRegistrationId)\n            {\n                egressRegistrationId = ctx.aeron().asyncAddSubscription(ctx.egressChannel(), ctx.egressStreamId());\n            }\n\n            try\n            {\n                egressSubscription = ctx.aeron().getSubscription(egressRegistrationId);\n            }\n            catch (final RegistrationException ex)\n            {\n                egressRegistrationId = NULL_VALUE;\n\n                if (ErrorCode.RESOURCE_TEMPORARILY_UNAVAILABLE != ex.errorCode())\n                {\n                    throw ex;\n                }\n            }\n\n            if (null != egressSubscription)\n            {\n                egressPoller = new EgressPoller(egressSubscription, FRAGMENT_LIMIT);\n                egressRegistrationId = NULL_VALUE;\n                state(State.CREATE_INGRESS_PUBLICATIONS);\n            }\n        }\n\n        private void createIngressPublications()\n        {\n            if (null == ctx.ingressEndpoints())\n            {\n                if (null == ingressPublication)\n                {\n                    if (NULL_VALUE == ingressRegistrationId)\n                    {\n                        ingressRegistrationId =\n                            asyncAddIngressPublication(ctx, ctx.ingressChannel(), ctx.ingressStreamId());\n                    }\n\n                    try\n                    {\n                        ingressPublication = getIngressPublication(ctx, ingressRegistrationId);\n                    }\n                    catch (final RegistrationException ex)\n                    {\n                        ingressRegistrationId = NULL_VALUE;\n                        throw ex;\n                    }\n                }\n                else\n                {\n                    ingressRegistrationId = NULL_VALUE;\n                    state(State.AWAIT_PUBLICATION_CONNECTED);\n                }\n            }\n            else\n            {\n                int count = 0;\n                for (final MemberIngress member : memberByIdMap.values())\n                {\n                    if (null != member.publication || null != member.publicationException)\n                    {\n                        count++;\n                    }\n                    else\n                    {\n                        if (NULL_VALUE == member.registrationId)\n                        {\n                            member.asyncAddPublication();\n                        }\n                        member.asyncGetPublication();\n                    }\n                }\n\n                if (memberByIdMap.size() == count)\n                {\n                    state(State.AWAIT_PUBLICATION_CONNECTED);\n                }\n            }\n        }\n\n        private void awaitPublicationConnected()\n        {\n            final String responseChannel = egressSubscription.tryResolveChannelEndpointPort();\n            if (null != responseChannel)\n            {\n                if (null == ingressPublication)\n                {\n                    for (final MemberIngress member : memberByIdMap.values())\n                    {\n                        if (null == member.publication && NULL_VALUE != member.registrationId)\n                        {\n                            member.asyncGetPublication();\n                        }\n\n                        if (null != member.publication && member.publication.isConnected())\n                        {\n                            ingressPublication = member.publication;\n                            prepareConnectRequest(responseChannel);\n                            break;\n                        }\n                    }\n                }\n                else if (ingressPublication.isConnected())\n                {\n                    prepareConnectRequest(responseChannel);\n                }\n            }\n        }\n\n        private void prepareConnectRequest(final String responseChannel)\n        {\n            correlationId = ctx.aeron().nextCorrelationId();\n            final byte[] encodedCredentials = ctx.credentialsSupplier().encodedCredentials();\n\n            final String clientInfo = \"name=\" + ctx.clientName() + \" \" +\n                AeronCounters.formatVersionInfo(AeronClusterVersion.VERSION, AeronClusterVersion.GIT_SHA);\n\n            final SessionConnectRequestEncoder encoder = new SessionConnectRequestEncoder()\n                .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n                .correlationId(correlationId)\n                .responseStreamId(ctx.egressStreamId())\n                .version(Configuration.PROTOCOL_SEMANTIC_VERSION)\n                .responseChannel(responseChannel)\n                .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length)\n                .clientInfo(clientInfo);\n\n            messageLength = MessageHeaderEncoder.ENCODED_LENGTH + encoder.encodedLength();\n            state(State.SEND_MESSAGE);\n        }\n\n        private void sendMessage()\n        {\n            final long position = ingressPublication.offer(buffer, 0, messageLength);\n            if (position > 0)\n            {\n                state(State.POLL_RESPONSE);\n            }\n            else if (Publication.CLOSED == position || Publication.NOT_CONNECTED == position)\n            {\n                throw new ClusterException(\"unexpected loss of connection to cluster\");\n            }\n        }\n\n        private void pollResponse()\n        {\n            if (egressPoller.poll() > 0 &&\n                egressPoller.isPollComplete() &&\n                egressPoller.correlationId() == correlationId)\n            {\n                if (egressPoller.isChallenged())\n                {\n                    correlationId = NULL_VALUE;\n                    clusterSessionId = egressPoller.clusterSessionId();\n                    prepareChallengeResponse(ctx.credentialsSupplier().onChallenge(egressPoller.encodedChallenge()));\n                    return;\n                }\n\n                switch (egressPoller.eventCode())\n                {\n                    case OK:\n                        leadershipTermId = egressPoller.leadershipTermId();\n                        leaderMemberId = egressPoller.leaderMemberId();\n                        clusterSessionId = egressPoller.clusterSessionId();\n                        leaderHeartbeatTimeoutNs = egressPoller.leaderHeartbeatTimeoutNs();\n                        egressImage = egressPoller.egressImage();\n                        state(State.CONCLUDE_CONNECT);\n                        break;\n\n                    case ERROR:\n                        throw new ClusterException(egressPoller.detail());\n\n                    case REDIRECT:\n                        updateMembers();\n                        break;\n\n                    case AUTHENTICATION_REJECTED:\n                        throw new AuthenticationException(egressPoller.detail());\n\n                    case CLOSED:\n                    case NULL_VAL:\n                        break;\n                }\n            }\n        }\n\n        private void prepareChallengeResponse(final byte[] encodedCredentials)\n        {\n            correlationId = ctx.aeron().nextCorrelationId();\n\n            final ChallengeResponseEncoder encoder = new ChallengeResponseEncoder()\n                .wrapAndApplyHeader(buffer, 0, messageHeaderEncoder)\n                .correlationId(correlationId)\n                .clusterSessionId(clusterSessionId)\n                .putEncodedCredentials(encodedCredentials, 0, encodedCredentials.length);\n\n            messageLength = MessageHeaderEncoder.ENCODED_LENGTH + encoder.encodedLength();\n\n            state(State.SEND_MESSAGE);\n        }\n\n        private void updateMembers()\n        {\n            leaderMemberId = egressPoller.leaderMemberId();\n            final MemberIngress oldLeader = memberByIdMap.remove(leaderMemberId);\n\n            CloseHelper.close(ingressPublication);\n            ingressPublication = null;\n            CloseHelper.closeAll(memberByIdMap.values());\n\n            memberByIdMap = parseIngressEndpoints(ctx, egressPoller.detail());\n\n            final MemberIngress newLeader = memberByIdMap.get(leaderMemberId);\n            if (null != oldLeader &&\n                null == oldLeader.publicationException &&\n                newLeader.endpoint.equals(oldLeader.endpoint))\n            {\n                ingressPublication = newLeader.publication = oldLeader.publication;\n                newLeader.registrationId = oldLeader.registrationId;\n            }\n            else\n            {\n                CloseHelper.close(oldLeader);\n                newLeader.asyncAddPublication();\n            }\n\n            state(State.AWAIT_PUBLICATION_CONNECTED);\n        }\n\n        private AeronCluster concludeConnect()\n        {\n            if (ctx.newLeaderTimeoutNs() == NULL_VALUE)\n            {\n                ctx.newLeaderTimeoutNs(2 * (leaderHeartbeatTimeoutNs != NULL_VALUE ?\n                    leaderHeartbeatTimeoutNs : ConsensusModule.Configuration.LEADER_HEARTBEAT_TIMEOUT_DEFAULT_NS));\n            }\n\n            final AeronCluster aeronCluster = new AeronCluster(\n                ctx,\n                messageHeaderEncoder,\n                ingressPublication,\n                egressSubscription,\n                egressImage,\n                memberByIdMap,\n                clusterSessionId,\n                leadershipTermId,\n                leaderMemberId);\n\n            ingressPublication = null;\n            memberByIdMap.remove(leaderMemberId);\n            CloseHelper.closeAll(memberByIdMap.values());\n\n            state(State.DONE);\n\n            return aeronCluster;\n        }\n    }\n\n    static final class MemberIngress implements AutoCloseable\n    {\n        private final Context ctx;\n        final int memberId;\n        final String endpoint;\n        long registrationId = NULL_VALUE;\n        Publication publication;\n        RegistrationException publicationException;\n\n        MemberIngress(final Context ctx, final int memberId, final String endpoint)\n        {\n            this.ctx = ctx;\n            this.memberId = memberId;\n            this.endpoint = endpoint;\n        }\n\n        void asyncAddPublication()\n        {\n            final ChannelUri channelUri = ChannelUri.parse(ctx.ingressChannel());\n            if (channelUri.isUdp())\n            {\n                channelUri.put(CommonContext.ENDPOINT_PARAM_NAME, endpoint);\n            }\n\n            registrationId = asyncAddIngressPublication(ctx, channelUri.toString(), ctx.ingressStreamId());\n            publication = null;\n        }\n\n        void asyncGetPublication()\n        {\n            try\n            {\n                publication = getIngressPublication(ctx, registrationId);\n                if (null != publication)\n                {\n                    registrationId = NULL_VALUE;\n                }\n            }\n            catch (final RegistrationException ex)\n            {\n                publicationException = ex;\n                registrationId = NULL_VALUE;\n            }\n        }\n\n        public void close()\n        {\n            if (null != publication)\n            {\n                CloseHelper.close(publication);\n            }\n            else if (NULL_VALUE != registrationId)\n            {\n                ctx.aeron().asyncRemovePublication(registrationId);\n            }\n            registrationId = NULL_VALUE;\n            publication = null;\n        }\n\n        public String toString()\n        {\n            return \"MemberIngress{\" +\n                \"memberId=\" + memberId +\n                \", endpoint='\" + endpoint + '\\'' +\n                \", publication=\" + publication +\n                '}';\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/ClusterEvent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.exceptions.AeronEvent;\nimport io.aeron.exceptions.AeronException;\n\n/**\n * A means to capture a Cluster event of significance that does not require a stack trace, so it can be lighter-weight\n * and take up less space in a {@link org.agrona.concurrent.errors.DistinctErrorLog}.\n */\npublic class ClusterEvent extends AeronEvent\n{\n    private static final long serialVersionUID = -3787117048989508866L;\n\n    /**\n     * Cluster event with provided message and {@link io.aeron.exceptions.AeronException.Category#WARN}.\n     *\n     * @param message to detail the event.\n     */\n    public ClusterEvent(final String message)\n    {\n        super(message, AeronException.Category.WARN);\n    }\n\n    /**\n     * Cluster event with provided message and {@link io.aeron.exceptions.AeronException.Category}.\n     *\n     * @param message  to detail the event.\n     * @param category of the event.\n     */\n    public ClusterEvent(final String message, final AeronException.Category category)\n    {\n        super(message, category);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/ClusterException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.exceptions.AeronException;\n\n/**\n * Exceptions specific to Cluster operation.\n */\npublic class ClusterException extends AeronException\n{\n    private static final long serialVersionUID = -2688245045186545277L;\n\n    /**\n     * Default Cluster exception as {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     */\n    public ClusterException()\n    {\n    }\n\n    /**\n     * Cluster exception with provided message and {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     *\n     * @param message to detail the exception.\n     */\n    public ClusterException(final String message)\n    {\n        super(message);\n    }\n\n    /**\n     * Cluster exception with a detailed message and provided {@link io.aeron.exceptions.AeronException.Category}.\n     *\n     * @param message  providing detail on the error.\n     * @param category of the exception.\n     */\n    public ClusterException(final String message, final Category category)\n    {\n        super(message, category);\n    }\n\n    /**\n     * Cluster exception with a detailed message, cause, and {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     *\n     * @param message providing detail on the error.\n     * @param cause   of the error.\n     */\n    public ClusterException(final String message, final Throwable cause)\n    {\n        super(message, cause);\n    }\n\n    /**\n     * Cluster exception with provided cause and {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     *\n     * @param cause of the error.\n     */\n    public ClusterException(final Throwable cause)\n    {\n        super(cause);\n    }\n\n    /**\n     * Cluster exception with cause and provided {@link io.aeron.exceptions.AeronException.Category}.\n     *\n     * @param cause    of the error.\n     * @param category of the exception.\n     */\n    public ClusterException(final Throwable cause, final Category category)\n    {\n        super(cause, category);\n    }\n\n    /**\n     * Cluster exception with a detailed message, cause, and {@link io.aeron.exceptions.AeronException.Category}.\n     *\n     * @param message  providing detail on the error.\n     * @param cause    of the error.\n     * @param category of the exception.\n     */\n    public ClusterException(final String message, final Throwable cause, final Category category)\n    {\n        super(message, cause, category);\n    }\n\n    /**\n     * Constructs a new Cluster exception with a detail message, cause, suppression enabled or disabled,\n     * and writable stack trace enabled or disabled, in the category\n     * {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     *\n     * @param message            providing detail on the error.\n     * @param cause              of the error.\n     * @param enableSuppression  is suppression enabled or not.\n     * @param writableStackTrace is the stack trace writable or not.\n     */\n    public ClusterException(\n        final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace)\n    {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/ControlledEgressAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\npackage io.aeron.cluster.client;\n\nimport static io.aeron.cluster.client.AeronCluster.SESSION_HEADER_LENGTH;\n\nimport org.agrona.DirectBuffer;\n\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\n\n/**\n * Adapter for dispatching egress messages from a cluster to a {@link ControlledEgressListener}.\n */\npublic final class ControlledEgressAdapter implements ControlledFragmentHandler\n{\n    private final long clusterSessionId;\n    private final int fragmentLimit;\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SessionEventDecoder sessionEventDecoder = new SessionEventDecoder();\n    private final NewLeaderEventDecoder newLeaderEventDecoder = new NewLeaderEventDecoder();\n    private final AdminResponseDecoder adminResponseDecoder = new AdminResponseDecoder();\n    private final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n    private final ControlledFragmentAssembler fragmentAssembler = new ControlledFragmentAssembler(this);\n    private final ControlledEgressListener listener;\n    private final ControlledEgressListenerExtension listenerExtension;\n    private final Subscription subscription;\n\n    /**\n     * Construct an adapter for cluster egress which consumes from the subscription and dispatches to the\n     * {@link ControlledEgressListener}.\n     *\n     * @param listener         to dispatch events to.\n     * @param clusterSessionId for the egress.\n     * @param subscription     over the egress stream.\n     * @param fragmentLimit    to poll on each {@link #poll()} operation.\n     */\n    public ControlledEgressAdapter(\n        final ControlledEgressListener listener,\n        final long clusterSessionId,\n        final Subscription subscription,\n        final int fragmentLimit)\n    {\n        this(listener, null, clusterSessionId, subscription, fragmentLimit);\n    }\n\n    /**\n     * Construct an adapter for cluster egress which consumes from the subscription and dispatches to the\n     * {@link ControlledEgressListener} or extension messages to {@link ControlledEgressListenerExtension}.\n     *\n     * @param listener          to dispatch events to.\n     * @param listenerExtension to dispatch extension messages to\n     * @param clusterSessionId  for the egress.\n     * @param subscription      over the egress stream.\n     * @param fragmentLimit     to poll on each {@link #poll()} operation.\n     */\n    public ControlledEgressAdapter(\n        final ControlledEgressListener listener,\n        final ControlledEgressListenerExtension listenerExtension,\n        final long clusterSessionId,\n        final Subscription subscription,\n        final int fragmentLimit)\n    {\n        this.clusterSessionId = clusterSessionId;\n        this.fragmentLimit = fragmentLimit;\n        this.listener = listener;\n        this.listenerExtension = listenerExtension;\n        this.subscription = subscription;\n    }\n\n    /**\n     * Poll the egress subscription and dispatch assembled events to the {@link ControlledEgressListener}.\n     *\n     * @return the number of fragments consumed.\n     */\n    public int poll()\n    {\n        return subscription.controlledPoll(fragmentAssembler, fragmentLimit);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @SuppressWarnings(\"MethodLength\")\n    public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int templateId = messageHeaderDecoder.templateId();\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            if (listenerExtension != null)\n            {\n                return listenerExtension.onExtensionMessage(\n                    messageHeaderDecoder.blockLength(),\n                    templateId,\n                    schemaId,\n                    messageHeaderDecoder.version(),\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    length - MessageHeaderDecoder.ENCODED_LENGTH);\n            }\n            throw new ClusterException(\"expected schemaId=\" +\n                MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (templateId)\n        {\n            case SessionMessageHeaderDecoder.TEMPLATE_ID:\n            {\n                sessionMessageHeaderDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = sessionMessageHeaderDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    return listener.onMessage(\n                        sessionId,\n                        sessionMessageHeaderDecoder.timestamp(),\n                        buffer,\n                        offset + SESSION_HEADER_LENGTH,\n                        length - SESSION_HEADER_LENGTH,\n                        header);\n                }\n                break;\n            }\n\n            case SessionEventDecoder.TEMPLATE_ID:\n            {\n                sessionEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = sessionEventDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    listener.onSessionEvent(\n                        sessionEventDecoder.correlationId(),\n                        sessionId,\n                        sessionEventDecoder.leadershipTermId(),\n                        sessionEventDecoder.leaderMemberId(),\n                        sessionEventDecoder.code(),\n                        sessionEventDecoder.detail());\n                }\n                break;\n            }\n\n            case NewLeaderEventDecoder.TEMPLATE_ID:\n            {\n                newLeaderEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = newLeaderEventDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    listener.onNewLeader(\n                        sessionId,\n                        newLeaderEventDecoder.leadershipTermId(),\n                        newLeaderEventDecoder.leaderMemberId(),\n                        newLeaderEventDecoder.ingressEndpoints());\n                }\n                break;\n            }\n\n            case AdminResponseDecoder.TEMPLATE_ID:\n            {\n                adminResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = adminResponseDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    final long correlationId = adminResponseDecoder.correlationId();\n                    final AdminRequestType requestType = adminResponseDecoder.requestType();\n                    final AdminResponseCode responseCode = adminResponseDecoder.responseCode();\n                    final String message = adminResponseDecoder.message();\n                    final int payloadOffset = adminResponseDecoder.offset() +\n                        AdminResponseDecoder.BLOCK_LENGTH +\n                        AdminResponseDecoder.messageHeaderLength() +\n                        message.length() +\n                        AdminResponseDecoder.payloadHeaderLength();\n                    final int payloadLength = adminResponseDecoder.payloadLength();\n                    listener.onAdminResponse(\n                        sessionId,\n                        correlationId,\n                        requestType,\n                        responseCode,\n                        message,\n                        buffer,\n                        payloadOffset,\n                        payloadLength);\n                }\n                break;\n            }\n\n            default:\n                break;\n        }\n        return Action.CONTINUE;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/ControlledEgressListener.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.cluster.codecs.AdminRequestType;\nimport io.aeron.cluster.codecs.AdminResponseCode;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\n/**\n * Interface for consuming messages coming from the cluster that also include administrative events in a controlled\n * fashion like {@link io.aeron.logbuffer.ControlledFragmentHandler}. Only session messages may be\n * controlled in consumption, other are consumed via {@link io.aeron.logbuffer.ControlledFragmentHandler.Action#COMMIT}.\n */\n@FunctionalInterface\npublic interface ControlledEgressListener\n{\n    /**\n     * Message event returned from the clustered service.\n     *\n     * @param clusterSessionId to which the message belongs.\n     * @param timestamp        at which the correlated ingress was sequenced in the cluster.\n     * @param buffer           containing the message.\n     * @param offset           at which the message begins.\n     * @param length           of the message in bytes.\n     * @param header           Aeron header associated with the message fragment.\n     * @return what action should be taken regarding advancement of the stream.\n     */\n    ControlledFragmentHandler.Action onMessage(\n        long clusterSessionId,\n        long timestamp,\n        DirectBuffer buffer,\n        int offset,\n        int length,\n        Header header);\n\n    /**\n     * Session event emitted from the cluster which after connect can indicate an error or session close.\n     *\n     * @param correlationId    associated with the cluster ingress.\n     * @param clusterSessionId to which the event belongs.\n     * @param leadershipTermId for identifying the active term of leadership\n     * @param leaderMemberId   identity of the active leader.\n     * @param code             to indicate the type of event.\n     * @param detail           Textual detail to explain the event.\n     */\n    default void onSessionEvent(\n        final long correlationId,\n        final long clusterSessionId,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final EventCode code,\n        final String detail)\n    {\n    }\n\n    /**\n     * Event indicating a new leader has been elected.\n     *\n     * @param clusterSessionId to which the event belongs.\n     * @param leadershipTermId for identifying the active term of leadership\n     * @param leaderMemberId   identity of the active leader.\n     * @param ingressEndpoints for connecting to the cluster.\n     */\n    default void onNewLeader(\n        final long clusterSessionId,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final String ingressEndpoints)\n    {\n    }\n\n    /**\n     * Message returned in response to an admin request.\n     *\n     * @param clusterSessionId to which the response belongs.\n     * @param correlationId    of the admin request.\n     * @param requestType      of the admin request.\n     * @param responseCode     describing the response.\n     * @param message          describing the response (e.g. error message).\n     * @param payload          delivered with the response, can be empty.\n     * @param payloadOffset    into the payload buffer.\n     * @param payloadLength    of the payload.\n     */\n    default void onAdminResponse(\n        final long clusterSessionId,\n        final long correlationId,\n        final AdminRequestType requestType,\n        final AdminResponseCode responseCode,\n        final String message,\n        final DirectBuffer payload,\n        final int payloadOffset,\n        final int payloadLength)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/ControlledEgressListenerExtension.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport org.agrona.DirectBuffer;\n\nimport io.aeron.logbuffer.ControlledFragmentHandler;\n\n/**\n * Interface for consuming extension messages coming from the cluster that also\n * include administrative events in a controlled\n * fashion like {@link ControlledFragmentHandler}.\n */\n@FunctionalInterface\npublic interface ControlledEgressListenerExtension\n{\n    /**\n     * Message of unknown schema to egress that can be handled by specific listener implementation.\n     *\n     * @param actingBlockLength acting block length from header\n     * @param templateId        template id\n     * @param schemaId          schema id\n     * @param actingVersion     acting version\n     * @param buffer        message buffer\n     * @param offset        message offset\n     * @param length        message length\n     * @return action to be taken after processing the message.\n     */\n    ControlledFragmentHandler.Action onExtensionMessage(\n        int actingBlockLength,\n        int templateId,\n        int schemaId,\n        int actingVersion,\n        DirectBuffer buffer,\n        int offset,\n        int length);\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/EgressAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\nimport static io.aeron.cluster.client.AeronCluster.SESSION_HEADER_LENGTH;\n\n/**\n * Adapter for dispatching egress messages from a cluster to a {@link EgressListener}.\n */\npublic final class EgressAdapter implements FragmentHandler\n{\n    private final long clusterSessionId;\n    private final int fragmentLimit;\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SessionEventDecoder sessionEventDecoder = new SessionEventDecoder();\n    private final NewLeaderEventDecoder newLeaderEventDecoder = new NewLeaderEventDecoder();\n    private final AdminResponseDecoder adminResponseDecoder = new AdminResponseDecoder();\n    private final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n    private final FragmentAssembler fragmentAssembler = new FragmentAssembler(this);\n    private final EgressListener listener;\n    private final EgressListenerExtension listenerExtension;\n    private final Subscription subscription;\n\n    /**\n     * Construct an adapter for cluster egress which consumes from the subscription and dispatches to the\n     * {@link EgressListener}.\n     *\n     * @param listener         to dispatch events to.\n     * @param clusterSessionId for the egress.\n     * @param subscription     over the egress stream.\n     * @param fragmentLimit    to poll on each {@link #poll()} operation.\n     */\n    public EgressAdapter(\n        final EgressListener listener,\n        final long clusterSessionId,\n        final Subscription subscription,\n        final int fragmentLimit)\n    {\n        this(listener, null, clusterSessionId, subscription, fragmentLimit);\n    }\n\n    /**\n     * Construct an adapter for cluster egress which consumes from the subscription and dispatches to the\n     * {@link EgressListener} or extension messages to {@link EgressListenerExtension}.\n     *\n     * @param listener          to dispatch events to.\n     * @param listenerExtension to dispatch extension messages to\n     * @param clusterSessionId  for the egress.\n     * @param subscription      over the egress stream.\n     * @param fragmentLimit     to poll on each {@link #poll()} operation.\n     */\n    public EgressAdapter(\n        final EgressListener listener,\n        final EgressListenerExtension listenerExtension,\n        final long clusterSessionId,\n        final Subscription subscription,\n        final int fragmentLimit)\n    {\n        this.clusterSessionId = clusterSessionId;\n        this.fragmentLimit = fragmentLimit;\n        this.listener = listener;\n        this.listenerExtension = listenerExtension;\n        this.subscription = subscription;\n    }\n\n    /**\n     * Poll the egress subscription and dispatch assembled events to the {@link EgressListener}.\n     *\n     * @return the number of fragments consumed.\n     */\n    public int poll()\n    {\n        return subscription.poll(fragmentAssembler, fragmentLimit);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @SuppressWarnings(\"MethodLength\")\n    public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int templateId = messageHeaderDecoder.templateId();\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            if (listenerExtension != null)\n            {\n                listenerExtension.onExtensionMessage(\n                    messageHeaderDecoder.blockLength(),\n                    templateId,\n                    schemaId,\n                    messageHeaderDecoder.version(),\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    length - MessageHeaderDecoder.ENCODED_LENGTH);\n                return;\n            }\n            throw new ClusterException(\"expected schemaId=\" +\n                MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (templateId)\n        {\n            case SessionMessageHeaderDecoder.TEMPLATE_ID:\n            {\n                sessionMessageHeaderDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = sessionMessageHeaderDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    listener.onMessage(\n                        sessionId,\n                        sessionMessageHeaderDecoder.timestamp(),\n                        buffer,\n                        offset + SESSION_HEADER_LENGTH,\n                        length - SESSION_HEADER_LENGTH,\n                        header);\n                }\n                break;\n            }\n\n            case SessionEventDecoder.TEMPLATE_ID:\n            {\n                sessionEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = sessionEventDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    listener.onSessionEvent(\n                        sessionEventDecoder.correlationId(),\n                        sessionId,\n                        sessionEventDecoder.leadershipTermId(),\n                        sessionEventDecoder.leaderMemberId(),\n                        sessionEventDecoder.code(),\n                        sessionEventDecoder.detail());\n                }\n                break;\n            }\n\n            case NewLeaderEventDecoder.TEMPLATE_ID:\n            {\n                newLeaderEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = newLeaderEventDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    listener.onNewLeader(\n                        sessionId,\n                        newLeaderEventDecoder.leadershipTermId(),\n                        newLeaderEventDecoder.leaderMemberId(),\n                        newLeaderEventDecoder.ingressEndpoints());\n                }\n                break;\n            }\n\n            case AdminResponseDecoder.TEMPLATE_ID:\n            {\n                adminResponseDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long sessionId = adminResponseDecoder.clusterSessionId();\n                if (sessionId == clusterSessionId)\n                {\n                    final long correlationId = adminResponseDecoder.correlationId();\n                    final AdminRequestType requestType = adminResponseDecoder.requestType();\n                    final AdminResponseCode responseCode = adminResponseDecoder.responseCode();\n                    final String message = adminResponseDecoder.message();\n                    final int payloadOffset = adminResponseDecoder.offset() +\n                        AdminResponseDecoder.BLOCK_LENGTH +\n                        AdminResponseDecoder.messageHeaderLength() +\n                        message.length() +\n                        AdminResponseDecoder.payloadHeaderLength();\n                    final int payloadLength = adminResponseDecoder.payloadLength();\n                    listener.onAdminResponse(\n                        sessionId,\n                        correlationId,\n                        requestType,\n                        responseCode,\n                        message,\n                        buffer,\n                        payloadOffset,\n                        payloadLength);\n                }\n                break;\n            }\n\n            default:\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/EgressListener.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.cluster.codecs.AdminRequestType;\nimport io.aeron.cluster.codecs.AdminResponseCode;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\n/**\n * Interface for consuming messages coming from the cluster that also include administrative events.\n */\n@FunctionalInterface\npublic interface EgressListener\n{\n    /**\n     * Message returned from the clustered service.\n     *\n     * @param clusterSessionId to which the message belongs.\n     * @param timestamp        at which the correlated ingress was sequenced in the cluster.\n     * @param buffer           containing the message.\n     * @param offset           at which the message begins.\n     * @param length           of the message in bytes.\n     * @param header           Aeron header associated with the message fragment.\n     */\n    void onMessage(\n        long clusterSessionId,\n        long timestamp,\n        DirectBuffer buffer,\n        int offset,\n        int length,\n        Header header);\n\n    /**\n     * Session event emitted from the cluster which after connect can indicate an error or session close.\n     *\n     * @param correlationId    associated with the cluster ingress.\n     * @param clusterSessionId to which the event belongs.\n     * @param leadershipTermId for identifying the active term of leadership\n     * @param leaderMemberId   identity of the active leader.\n     * @param code             to indicate the type of event.\n     * @param detail           Textual detail to explain the event.\n     */\n    default void onSessionEvent(\n        final long correlationId,\n        final long clusterSessionId,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final EventCode code,\n        final String detail)\n    {\n    }\n\n    /**\n     * Event indicating a new leader has been elected.\n     *\n     * @param clusterSessionId to which the event belongs.\n     * @param leadershipTermId for identifying the active term of leadership\n     * @param leaderMemberId   identity of the active leader.\n     * @param ingressEndpoints for connecting to the cluster.\n     */\n    default void onNewLeader(\n        final long clusterSessionId,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final String ingressEndpoints)\n    {\n    }\n\n    /**\n     * Message returned in response to an admin request.\n     *\n     * @param clusterSessionId to which the response belongs.\n     * @param correlationId    of the admin request.\n     * @param requestType      of the admin request.\n     * @param responseCode     describing the response.\n     * @param message          describing the response (e.g. error message).\n     * @param payload          delivered with the response, can be empty.\n     * @param payloadOffset    into the payload buffer.\n     * @param payloadLength    of the payload.\n     */\n    default void onAdminResponse(\n        final long clusterSessionId,\n        final long correlationId,\n        final AdminRequestType requestType,\n        final AdminResponseCode responseCode,\n        final String message,\n        final DirectBuffer payload,\n        final int payloadOffset,\n        final int payloadLength)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/EgressListenerExtension.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport org.agrona.DirectBuffer;\n\n/**\n * Interface for consuming messages coming from the cluster for an unknown schema.\n */\n@FunctionalInterface\npublic interface EgressListenerExtension\n{\n    /**\n     * Message of unknown schema to egress that can be handled by specific listener implementation.\n     *\n     * @param actingBlockLength acting block length from header.\n     * @param templateId        template id.\n     * @param schemaId          schema id.\n     * @param actingVersion     acting version.\n     * @param buffer            message buffer.\n     * @param offset            message offset.\n     * @param length            message length.\n     */\n    void onExtensionMessage(\n        int actingBlockLength,\n        int templateId,\n        int schemaId,\n        int actingVersion,\n        DirectBuffer buffer,\n        int offset,\n        int length);\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/EgressPoller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.ArrayUtil;\n\n/**\n * Poller for the egress from a cluster to capture administration message details.\n */\npublic final class EgressPoller implements ControlledFragmentHandler\n{\n    private final int fragmentLimit;\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SessionEventDecoder sessionEventDecoder = new SessionEventDecoder();\n    private final ChallengeDecoder challengeDecoder = new ChallengeDecoder();\n    private final NewLeaderEventDecoder newLeaderEventDecoder = new NewLeaderEventDecoder();\n    private final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n    private final ControlledFragmentAssembler fragmentAssembler = new ControlledFragmentAssembler(this);\n    private final Subscription subscription;\n\n    private Image egressImage;\n    private long clusterSessionId = Aeron.NULL_VALUE;\n    private long correlationId = Aeron.NULL_VALUE;\n    private long leadershipTermId = Aeron.NULL_VALUE;\n    private int leaderMemberId = Aeron.NULL_VALUE;\n    private int templateId = Aeron.NULL_VALUE;\n    private int version = 0;\n    private long leaderHeartbeatTimeoutNs = Aeron.NULL_VALUE;\n    private boolean isPollComplete = false;\n    private EventCode eventCode;\n    private String detail = \"\";\n    private byte[] encodedChallenge;\n\n    /**\n     * Construct a poller on the egress subscription.\n     *\n     * @param subscription  for egress from the cluster.\n     * @param fragmentLimit for each poll operation.\n     */\n    public EgressPoller(final Subscription subscription, final int fragmentLimit)\n    {\n        this.subscription = subscription;\n        this.fragmentLimit = fragmentLimit;\n    }\n\n    /**\n     * Get the {@link Subscription} used for polling events.\n     *\n     * @return the {@link Subscription} used for polling events.\n     */\n    public Subscription subscription()\n    {\n        return subscription;\n    }\n\n    /**\n     * {@link Image} for the egress response from the cluster which can be used for connection tracking.\n     *\n     * @return {@link Image} for the egress response from the cluster which can be used for connection tracking.\n     */\n    public Image egressImage()\n    {\n        return egressImage;\n    }\n\n    /**\n     * Get the template id of the last received event.\n     *\n     * @return the template id of the last received event.\n     */\n    public int templateId()\n    {\n        return templateId;\n    }\n\n    /**\n     * Cluster session id of the last polled event or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return cluster session id of the last polled event or {@link Aeron#NULL_VALUE} if not returned.\n     */\n    public long clusterSessionId()\n    {\n        return clusterSessionId;\n    }\n\n    /**\n     * Correlation id of the last polled event or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return correlation id of the last polled event or {@link Aeron#NULL_VALUE} if not returned.\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * Leadership term id of the last polled event or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return leadership term id of the last polled event or {@link Aeron#NULL_VALUE} if not returned.\n     */\n    public long leadershipTermId()\n    {\n        return leadershipTermId;\n    }\n\n    /**\n     * Leader cluster member id of the last polled event or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     *\n     * @return leader cluster member id of the last polled event or {@link Aeron#NULL_VALUE} if poll returned nothing.\n     */\n    public int leaderMemberId()\n    {\n        return leaderMemberId;\n    }\n\n    /**\n     * Get the event code returned from the last session event.\n     *\n     * @return the event code returned from the last session event.\n     */\n    public EventCode eventCode()\n    {\n        return eventCode;\n    }\n\n    /**\n     * Version response from the server in semantic version form.\n     *\n     * @return response from the server in semantic version form.\n     */\n    public int version()\n    {\n        return version;\n    }\n\n    /**\n     * Leader heartbeat timeout of the last polled event or {@link Aeron#NULL_VALUE} if not available.\n     *\n     * @return leader heartbeat timeout of the last polled event or {@link Aeron#NULL_VALUE} if not available.\n     */\n    public long leaderHeartbeatTimeoutNs()\n    {\n        return leaderHeartbeatTimeoutNs;\n    }\n\n    /**\n     * Get the detail returned from the last session event.\n     *\n     * @return the detail returned from the last session event.\n     */\n    public String detail()\n    {\n        return detail;\n    }\n\n    /**\n     * Get the encoded challenge in the last challenge.\n     *\n     * @return the encoded challenge in the last challenge or null if last message was not a challenge.\n     */\n    public byte[] encodedChallenge()\n    {\n        return encodedChallenge;\n    }\n\n    /**\n     * Has the last polling action received a complete event?\n     *\n     * @return true if the last polling action received a complete event.\n     */\n    public boolean isPollComplete()\n    {\n        return isPollComplete;\n    }\n\n    /**\n     * Was last message a challenge or not?\n     *\n     * @return true if last message was a challenge or false if not.\n     */\n    public boolean isChallenged()\n    {\n        return ChallengeDecoder.TEMPLATE_ID == templateId;\n    }\n\n    /**\n     * Reset last captured value and poll the egress subscription for output.\n     *\n     * @return number of fragments consumed.\n     */\n    public int poll()\n    {\n        if (isPollComplete)\n        {\n            isPollComplete = false;\n            clusterSessionId = Aeron.NULL_VALUE;\n            correlationId = Aeron.NULL_VALUE;\n            leadershipTermId = Aeron.NULL_VALUE;\n            leaderMemberId = Aeron.NULL_VALUE;\n            templateId = Aeron.NULL_VALUE;\n            version = 0;\n            leaderHeartbeatTimeoutNs = Aeron.NULL_VALUE;\n            eventCode = null;\n            detail = \"\";\n            encodedChallenge = null;\n        }\n\n        return subscription.controlledPoll(fragmentAssembler, fragmentLimit);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public ControlledFragmentAssembler.Action onFragment(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        if (isPollComplete)\n        {\n            return Action.ABORT;\n        }\n\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            return Action.CONTINUE; // skip unknown schemas\n        }\n\n        templateId = messageHeaderDecoder.templateId();\n        switch (templateId)\n        {\n            case SessionMessageHeaderDecoder.TEMPLATE_ID:\n                sessionMessageHeaderDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                leadershipTermId = sessionMessageHeaderDecoder.leadershipTermId();\n                clusterSessionId = sessionMessageHeaderDecoder.clusterSessionId();\n                isPollComplete = true;\n                return Action.BREAK;\n\n            case SessionEventDecoder.TEMPLATE_ID:\n                sessionEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                clusterSessionId = sessionEventDecoder.clusterSessionId();\n                correlationId = sessionEventDecoder.correlationId();\n                leadershipTermId = sessionEventDecoder.leadershipTermId();\n                leaderMemberId = sessionEventDecoder.leaderMemberId();\n                eventCode = sessionEventDecoder.code();\n                version = sessionEventDecoder.version();\n                leaderHeartbeatTimeoutNs = leaderHeartbeatTimeoutNs(sessionEventDecoder);\n                detail = sessionEventDecoder.detail();\n                isPollComplete = true;\n                egressImage = (Image)header.context();\n                return Action.BREAK;\n\n            case NewLeaderEventDecoder.TEMPLATE_ID:\n                newLeaderEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                clusterSessionId = newLeaderEventDecoder.clusterSessionId();\n                leadershipTermId = newLeaderEventDecoder.leadershipTermId();\n                leaderMemberId = newLeaderEventDecoder.leaderMemberId();\n                detail = newLeaderEventDecoder.ingressEndpoints();\n                isPollComplete = true;\n                egressImage = (Image)header.context();\n                return Action.BREAK;\n\n            case ChallengeDecoder.TEMPLATE_ID:\n                challengeDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final int encodedChallengeLength = challengeDecoder.encodedChallengeLength();\n                encodedChallenge =\n                    0 == encodedChallengeLength ? ArrayUtil.EMPTY_BYTE_ARRAY : new byte[encodedChallengeLength];\n                challengeDecoder.getEncodedChallenge(encodedChallenge, 0, encodedChallengeLength);\n\n                clusterSessionId = challengeDecoder.clusterSessionId();\n                correlationId = challengeDecoder.correlationId();\n                isPollComplete = true;\n                return Action.BREAK;\n        }\n\n        return Action.CONTINUE;\n    }\n\n    private static long leaderHeartbeatTimeoutNs(final SessionEventDecoder sessionEventDecoder)\n    {\n        final long leaderHeartbeatTimeoutNs = sessionEventDecoder.leaderHeartbeatTimeoutNs();\n\n        if (leaderHeartbeatTimeoutNs == SessionEventDecoder.leaderHeartbeatTimeoutNsNullValue())\n        {\n            return Aeron.NULL_VALUE;\n        }\n\n        return leaderHeartbeatTimeoutNs;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/IngressSessionDecorator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.Publication;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderEncoder;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\n/**\n * Encapsulate applying a client message header for ingress to the cluster.\n * <p>\n * The client message header is applied to the {@link Publication} before the offered buffer.\n * <p>\n * <b>Note:</b> This class is NOT threadsafe for updating {@link #clusterSessionId(long)} or\n * {@link #leadershipTermId(long)}. Each publisher thread requires its own instance.\n */\npublic final class IngressSessionDecorator\n{\n    /**\n     * Length of the session header that will be prepended to the message.\n     */\n    public static final int HEADER_LENGTH =\n        MessageHeaderEncoder.ENCODED_LENGTH + SessionMessageHeaderEncoder.BLOCK_LENGTH;\n\n    private final SessionMessageHeaderEncoder sessionMessageHeaderEncoder = new SessionMessageHeaderEncoder();\n    private final UnsafeBuffer headerBuffer = new UnsafeBuffer(new byte[HEADER_LENGTH]);\n\n    /**\n     * Construct a new ingress session header wrapper that defaults all fields to {@link Aeron#NULL_VALUE}.\n     */\n    public IngressSessionDecorator()\n    {\n        this(Aeron.NULL_VALUE, Aeron.NULL_VALUE);\n    }\n\n    /**\n     * Construct a new session header wrapper.\n     *\n     * @param clusterSessionId that has been allocated by the cluster.\n     * @param leadershipTermId of the current leader.\n     */\n    public IngressSessionDecorator(final long clusterSessionId, final long leadershipTermId)\n    {\n        sessionMessageHeaderEncoder\n            .wrapAndApplyHeader(headerBuffer, 0, new MessageHeaderEncoder())\n            .leadershipTermId(leadershipTermId)\n            .clusterSessionId(clusterSessionId)\n            .timestamp(Aeron.NULL_VALUE);\n    }\n\n    /**\n     * Reset the cluster session id in the header.\n     *\n     * @param clusterSessionId to be set in the header.\n     * @return this for a fluent API.\n     */\n    public IngressSessionDecorator clusterSessionId(final long clusterSessionId)\n    {\n        sessionMessageHeaderEncoder.clusterSessionId(clusterSessionId);\n        return this;\n    }\n\n    /**\n     * Reset the leadership term id in the header.\n     *\n     * @param leadershipTermId to be set in the header.\n     * @return this for a fluent API.\n     */\n    public IngressSessionDecorator leadershipTermId(final long leadershipTermId)\n    {\n        sessionMessageHeaderEncoder.leadershipTermId(leadershipTermId);\n        return this;\n    }\n\n    /**\n     * Non-blocking publish of a partial buffer containing a message plus session header to a cluster.\n     * <p>\n     * This version of the method will set the timestamp value in the header to {@link Aeron#NULL_VALUE}.\n     *\n     * @param publication to be offered to.\n     * @param buffer      containing message.\n     * @param offset      offset in the buffer at which the encoded message begins.\n     * @param length      in bytes of the encoded message.\n     * @return the same as {@link Publication#offer(DirectBuffer, int, int)}.\n     */\n    public long offer(final Publication publication, final DirectBuffer buffer, final int offset, final int length)\n    {\n        return publication.offer(headerBuffer, 0, HEADER_LENGTH, buffer, offset, length, null);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/client/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * {@link io.aeron.cluster.client.AeronCluster} clients interact with fault-tolerant services running in the cluster.\n */\npackage io.aeron.cluster.client;"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Aeron Cluster provides support for fault-tolerant services as replicated state machines based on the\n * <a href=\"https://raft.github.io/raft.pdf\" target=\"_blank\">Raft</a> consensus algorithm.\n * <p>\n * The purpose of Aeron Cluster is to aggregate and sequence streams from cluster clients into a single log. A number of\n * nodes will replicate and archive the log to achieve fault tolerance.\n * {@link io.aeron.cluster.service.ClusteredService}s deterministically process the log and respond to cluster clients.\n * <p>\n * Aeron Cluster works on the concept of a strong leader using an adaptation of the\n * <a href=\"https://raft.github.io/raft.pdf\" target=\"_blank\">Raft</a> algorithm. The leader sequences the log and is\n * responsible for replicating it to other cluster members known as followers.\n * <p>\n * A number of components make up Aeron Cluster. Central is the {@link io.aeron.cluster.ConsensusModule} which sequences\n * the log and coordinates consensus for the recording of the sequenced log to persistent storage, and the services\n * consuming the log across cluster members. Aeron {@link io.aeron.archive.Archive} records the log to durable storage.\n * Services consume the log once a majority of the cluster members have safely recorded the log to durable storage.\n * <p>\n * To enable fast recovery, the services and consensus module can take a snapshot of their state as of a given log\n * position thus enabling recovery by loading the most recent snapshot and replaying logs from that point forward.\n * Snapshots are recorded as streams in the {@link io.aeron.archive.Archive} for local and remote replay so that a\n * distributed file system is not required.\n * <h2>Usage</h2>\n * The cluster can run in various configurations:\n * <ul>\n *     <li>\n *         <b>Single Node:</b> For development, debugging, or when a sequenced and archived log on a single node is\n *         sufficient.\n *     </li>\n *     <li>\n *         <b>Appointed Leader:</b> A leader of the cluster can be appointed via configuration without requiring an\n *         election. In the event of a leader failure then a follower will have to be manually appointed the new leader.\n *         This is not the recommended way to use Cluster. Automatic elections are more reliable.\n *     </li>\n *     <li>\n *         <b>Automatic Elections:</b> Automatic elections (default) can be enabled to have a leader elected at random\n *         from the members with the most up to date log.\n *     </li>\n * </ul>\n * <p>\n * The majority of cluster members determine consensus. Clusters should typically be 3 or 5 in population size.\n * However, 2 node clusters are supported whereby both members must agree the log and in the event of failure the\n * remaining member must be manually reconfigured as a single node cluster to progress.\n * <h2>Protocol</h2>\n * Messages are specified using <a href=\"https://github.com/aeron-io/simple-binary-encoding\" target=\"_blank\">SBE</a>\n * in this schema\n * <a href=\"https://github.com/aeron-io/aeron/blob/master/aeron-cluster/src/main/resources/cluster/aeron-cluster-codecs.xml\"\n *    target=\"_blank\">aeron-cluster-codecs.xml</a>\n */\npackage io.aeron.cluster;"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ActiveLogEvent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\n/**\n * Event to signal a change of active log to follow.\n */\nclass ActiveLogEvent\n{\n    final long logPosition;\n    final long maxLogPosition;\n    final int memberId;\n    final int sessionId;\n    final int streamId;\n    final boolean isStartup;\n    final Cluster.Role role;\n    final String channel;\n    final boolean isStandby;\n\n    ActiveLogEvent(\n        final long logPosition,\n        final long maxLogPosition,\n        final int memberId,\n        final int sessionId,\n        final int streamId,\n        final boolean isStartup,\n        final Cluster.Role role,\n        final boolean isStandby,\n        final String channel)\n    {\n        this.logPosition = logPosition;\n        this.maxLogPosition = maxLogPosition;\n        this.memberId = memberId;\n        this.sessionId = sessionId;\n        this.streamId = streamId;\n        this.isStartup = isStartup;\n        this.role = role;\n        this.channel = channel;\n        this.isStandby = isStandby;\n    }\n\n    public String toString()\n    {\n        return \"ActiveLogEvent{\" +\n            \", logPosition=\" + logPosition +\n            \", maxLogPosition=\" + maxLogPosition +\n            \", memberId=\" + memberId +\n            \", sessionId=\" + sessionId +\n            \", streamId=\" + streamId +\n            \", isStartup=\" + isStartup +\n            \", role=\" + role +\n            \", isStandby=\" + isStandby +\n            \", channel='\" + channel + '\\'' +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/BoundedLogAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.BufferBuilder;\nimport io.aeron.Image;\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.client.*;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.*;\nimport org.agrona.*;\n\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\n\n/**\n * Adapter for reading a log with an upper-bound applied beyond which the consumer cannot progress.\n */\nfinal class BoundedLogAdapter implements ControlledFragmentHandler, AutoCloseable\n{\n    private final int fragmentLimit;\n    private long maxLogPosition;\n    private Image image;\n    private final ClusteredServiceAgent agent;\n    private final BufferBuilder builder = new BufferBuilder();\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SessionMessageHeaderDecoder sessionHeaderDecoder = new SessionMessageHeaderDecoder();\n    private final TimerEventDecoder timerEventDecoder = new TimerEventDecoder();\n    private final SessionOpenEventDecoder openEventDecoder = new SessionOpenEventDecoder();\n    private final SessionCloseEventDecoder closeEventDecoder = new SessionCloseEventDecoder();\n    private final ClusterActionRequestDecoder actionRequestDecoder = new ClusterActionRequestDecoder();\n    private final NewLeadershipTermEventDecoder newLeadershipTermEventDecoder = new NewLeadershipTermEventDecoder();\n\n    BoundedLogAdapter(final ClusteredServiceAgent agent, final int fragmentLimit)\n    {\n        this.agent = agent;\n        this.fragmentLimit = fragmentLimit;\n    }\n\n    public void close()\n    {\n        if (null != image)\n        {\n            CloseHelper.close(image.subscription());\n            image = null;\n        }\n    }\n\n    public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        Action action = Action.CONTINUE;\n        final byte flags = header.flags();\n\n        if ((flags & UNFRAGMENTED) == UNFRAGMENTED)\n        {\n            action = onMessage(buffer, offset, length, header);\n        }\n        else if ((flags & BEGIN_FRAG_FLAG) == BEGIN_FRAG_FLAG)\n        {\n            builder.reset()\n                .captureHeader(header)\n                .append(buffer, offset, length)\n                .nextTermOffset(BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT));\n        }\n        else if (offset == builder.nextTermOffset())\n        {\n            final int limit = builder.limit();\n\n            builder.append(buffer, offset, length);\n\n            if ((flags & END_FRAG_FLAG) == END_FRAG_FLAG)\n            {\n                action = onMessage(builder.buffer(), 0, builder.limit(), builder.completeHeader(header));\n\n                if (Action.ABORT == action)\n                {\n                    builder.limit(limit);\n                }\n                else\n                {\n                    builder.reset();\n                }\n            }\n            else\n            {\n                builder.nextTermOffset(BitUtil.align(offset + length + HEADER_LENGTH, FRAME_ALIGNMENT));\n            }\n        }\n        else\n        {\n            builder.reset();\n        }\n\n        return action;\n    }\n\n    void maxLogPosition(final long position)\n    {\n        maxLogPosition = position;\n    }\n\n    boolean isDone()\n    {\n        return image.position() >= maxLogPosition || image.isEndOfStream() || image.isClosed();\n    }\n\n    void image(final Image image)\n    {\n        this.image = image;\n    }\n\n    Image image()\n    {\n        return image;\n    }\n\n    int poll(final long limit)\n    {\n        return image.boundedControlledPoll(this, limit, fragmentLimit);\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private Action onMessage(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ClusterException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        final int templateId = messageHeaderDecoder.templateId();\n        if (templateId == SessionMessageHeaderDecoder.TEMPLATE_ID)\n        {\n            sessionHeaderDecoder.wrap(\n                buffer,\n                offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                messageHeaderDecoder.blockLength(),\n                messageHeaderDecoder.version());\n\n            agent.onSessionMessage(\n                header.position(),\n                sessionHeaderDecoder.clusterSessionId(),\n                sessionHeaderDecoder.timestamp(),\n                buffer,\n                offset + AeronCluster.SESSION_HEADER_LENGTH,\n                length - AeronCluster.SESSION_HEADER_LENGTH,\n                header);\n\n            return Action.CONTINUE;\n        }\n\n        switch (templateId)\n        {\n            case TimerEventDecoder.TEMPLATE_ID:\n                timerEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                agent.onTimerEvent(\n                    header.position(),\n                    timerEventDecoder.correlationId(),\n                    timerEventDecoder.timestamp());\n                break;\n\n            case SessionOpenEventDecoder.TEMPLATE_ID:\n                openEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final String responseChannel = openEventDecoder.responseChannel();\n                final byte[] encodedPrincipal = new byte[openEventDecoder.encodedPrincipalLength()];\n                openEventDecoder.getEncodedPrincipal(encodedPrincipal, 0, encodedPrincipal.length);\n\n                agent.onSessionOpen(\n                    openEventDecoder.leadershipTermId(),\n                    header.position(),\n                    openEventDecoder.clusterSessionId(),\n                    openEventDecoder.timestamp(),\n                    openEventDecoder.responseStreamId(),\n                    responseChannel,\n                    encodedPrincipal);\n                break;\n\n            case SessionCloseEventDecoder.TEMPLATE_ID:\n                closeEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                agent.onSessionClose(\n                    closeEventDecoder.leadershipTermId(),\n                    header.position(),\n                    closeEventDecoder.clusterSessionId(),\n                    closeEventDecoder.timestamp(),\n                    closeEventDecoder.closeReason());\n                break;\n\n            case ClusterActionRequestDecoder.TEMPLATE_ID:\n                actionRequestDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final int flags = ClusterActionRequestDecoder.flagsNullValue() != actionRequestDecoder.flags() ?\n                    actionRequestDecoder.flags() : ConsensusModule.CLUSTER_ACTION_FLAGS_DEFAULT;\n\n                agent.onServiceAction(\n                    actionRequestDecoder.leadershipTermId(),\n                    actionRequestDecoder.logPosition(),\n                    actionRequestDecoder.timestamp(),\n                    actionRequestDecoder.action(),\n                    flags);\n                break;\n\n            case NewLeadershipTermEventDecoder.TEMPLATE_ID:\n                newLeadershipTermEventDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                agent.onNewLeadershipTermEvent(\n                    newLeadershipTermEventDecoder.leadershipTermId(),\n                    newLeadershipTermEventDecoder.logPosition(),\n                    newLeadershipTermEventDecoder.timestamp(),\n                    newLeadershipTermEventDecoder.termBaseLogPosition(),\n                    newLeadershipTermEventDecoder.leaderMemberId(),\n                    newLeadershipTermEventDecoder.logSessionId(),\n                    ClusterClock.map(newLeadershipTermEventDecoder.timeUnit()),\n                    newLeadershipTermEventDecoder.appVersion());\n                break;\n\n            case MembershipChangeEventDecoder.TEMPLATE_ID:\n                // Removed Dynamic Join.\n                break;\n        }\n\n        return Action.CONTINUE;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ClientSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.DirectBufferVector;\nimport io.aeron.Publication;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.DirectBuffer;\n\n/**\n * Session representing a connected external client to the cluster.\n */\npublic interface ClientSession\n{\n    /**\n     * Return value to indicate egress to a session is mocked out by the cluster when in follower mode.\n     */\n    long MOCKED_OFFER = 1;\n\n    /**\n     * Cluster session identity uniquely allocated when the session was opened.\n     *\n     * @return the cluster session identity uniquely allocated when the session was opened.\n     */\n    long id();\n\n    /**\n     * The response channel stream id for responding to the client.\n     *\n     * @return response channel stream id for responding to the client.\n     */\n    int responseStreamId();\n\n    /**\n     * The response channel for responding to the client.\n     *\n     * @return response channel for responding to the client.\n     */\n    String responseChannel();\n\n    /**\n     * Cluster session encoded principal from when the session was authenticated.\n     *\n     * @return The encoded Principal passed. May be 0 length to indicate none present.\n     */\n    byte[] encodedPrincipal();\n\n    /**\n     * Close of this {@link ContainerClientSession} by sending the request to the consensus module.\n     * <p>\n     * This method is idempotent.\n     */\n    void close();\n\n    /**\n     * Indicates that a request to close this session has been made.\n     *\n     * @return whether a request to close this session has been made.\n     */\n    boolean isClosing();\n\n    /**\n     * Non-blocking publish of a partial buffer containing a message to a cluster.\n     *\n     * @param buffer containing message.\n     * @param offset offset in the buffer at which the encoded message begins.\n     * @param length in bytes of the encoded message.\n     * @return the same as {@link Publication#offer(DirectBuffer, int, int)} when in {@link Cluster.Role#LEADER},\n     * otherwise {@link #MOCKED_OFFER} when a follower.\n     */\n    long offer(DirectBuffer buffer, int offset, int length);\n\n    /**\n     * Non-blocking publish by gathering buffer vectors into a message. The first vector will be replaced by the cluster\n     * egress header so must be left unused. Will return the result of {@link Publication#offer(DirectBufferVector[])}\n     * when in {@link Cluster.Role#LEADER}, otherwise {@link #MOCKED_OFFER} when a follower.\n     *\n     * @param vectors which make up the message.\n     * @return the same as {@link Publication#offer(DirectBufferVector[])}.\n     * @see Publication#offer(DirectBufferVector[])\n     */\n    long offer(DirectBufferVector[] vectors);\n\n    /**\n     * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n     * Once the message has been written then {@link BufferClaim#commit()} should be called thus making it available.\n     * <p>\n     * On successful claim, the Cluster egress header will be written to the start of the claimed buffer section.\n     * Clients <b>MUST</b> write into the claimed buffer region at offset + {@link AeronCluster#SESSION_HEADER_LENGTH}.\n     * <pre>{@code\n     *     final DirectBuffer srcBuffer = acquireMessage();\n     *\n     *     if (session.tryClaim(length, bufferClaim) > 0L)\n     *     {\n     *         try\n     *         {\n     *              final MutableDirectBuffer buffer = bufferClaim.buffer();\n     *              final int offset = bufferClaim.offset();\n     *              // ensure that data is written at the correct offset\n     *              buffer.putBytes(offset + AeronCluster.SESSION_HEADER_LENGTH, srcBuffer, 0, length);\n     *         }\n     *         finally\n     *         {\n     *             bufferClaim.commit();\n     *         }\n     *     }\n     * }</pre>\n     *\n     * @param length      of the range to claim in bytes. The additional bytes for the session header will be added.\n     * @param bufferClaim to be populated if the claim succeeds.\n     * @return The new stream position, otherwise a negative error value as specified in\n     *         {@link io.aeron.Publication#tryClaim(int, BufferClaim)} when in {@link Cluster.Role#LEADER},\n     *         otherwise {@link #MOCKED_OFFER} when a follower.\n     * @throws IllegalArgumentException if the length is greater than {@link io.aeron.Publication#maxPayloadLength()}.\n     * @see Publication#tryClaim(int, BufferClaim)\n     * @see BufferClaim#commit()\n     * @see BufferClaim#abort()\n     */\n    long tryClaim(int length, BufferClaim bufferClaim);\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/Cluster.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.Aeron;\nimport io.aeron.DirectBufferVector;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.util.Collection;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\n\n/**\n * Interface for a {@link ClusteredService} to interact with cluster hosting it.\n * <p>\n * This object should only be used to send messages to the cluster or schedule timers in response to other messages\n * and timers. Sending messages and timers should not happen from cluster lifecycle methods like\n * {@link ClusteredService#onStart(Cluster, Image)}, {@link ClusteredService#onRoleChange(Cluster.Role)} or\n * {@link ClusteredService#onTakeSnapshot(ExclusivePublication)}, or {@link ClusteredService#onTerminate(Cluster)},\n * except the session lifecycle methods {@link ClusteredService#onSessionOpen(ClientSession, long)},\n * {@link ClusteredService#onSessionClose(ClientSession, long, CloseReason)},\n * and {@link ClusteredService#onNewLeadershipTermEvent(long, long, long, long, int, int, TimeUnit, int)}.\n */\npublic interface Cluster\n{\n    /**\n     * Role of the node in the cluster.\n     */\n    enum Role\n    {\n        /**\n         * The cluster node is a follower in the current leadership term.\n         */\n        FOLLOWER(0),\n\n        /**\n         * The cluster node is a candidate to become a leader in an election.\n         */\n        CANDIDATE(1),\n\n        /**\n         * The cluster node is the leader for the current leadership term.\n         */\n        LEADER(2);\n\n        static final Role[] ROLES = values();\n\n        private final int code;\n\n        Role(final int code)\n        {\n            if (code != ordinal())\n            {\n                throw new IllegalArgumentException(name() + \" - code must equal ordinal value: code=\" + code);\n            }\n\n            this.code = code;\n        }\n\n        /**\n         * The code which matches the role in the cluster.\n         *\n         * @return the code which matches the role in the cluster.\n         */\n        public final int code()\n        {\n            return code;\n        }\n\n        /**\n         * Get the role from a code read from a counter.\n         *\n         * @param code for the {@link Role}.\n         * @return the {@link Role} of the cluster node.\n         */\n        public static Role get(final long code)\n        {\n            if (code < 0 || code > (ROLES.length - 1))\n            {\n                throw new IllegalStateException(\"Invalid role counter code: \" + code);\n            }\n\n            return ROLES[(int)code];\n        }\n\n        /**\n         * Get the role by reading the code from a counter.\n         *\n         * @param counter containing the value of the role.\n         * @return the role for the cluster member.\n         */\n        public static Role get(final AtomicCounter counter)\n        {\n            if (counter.isClosed())\n            {\n                return FOLLOWER;\n            }\n\n            return get(counter.get());\n        }\n    }\n\n    /**\n     * The unique id for the hosting member of the cluster. Useful only for debugging purposes.\n     *\n     * @return unique id for the hosting member of the cluster.\n     */\n    int memberId();\n\n    /**\n     * The role the cluster node is playing.\n     *\n     * @return the role the cluster node is playing.\n     */\n    Role role();\n\n    /**\n     * Position the log has reached in bytes as of the current message.\n     *\n     * @return position the log has reached in bytes as of the current message.\n     */\n    long logPosition();\n\n    /**\n     * Get the {@link Aeron} client used by the cluster.\n     *\n     * @return the {@link Aeron} client used by the cluster.\n     */\n    Aeron aeron();\n\n    /**\n     * Get the {@link ClusteredServiceContainer.Context} under which the container is running.\n     *\n     * @return the {@link ClusteredServiceContainer.Context} under which the container is running.\n     */\n    ClusteredServiceContainer.Context context();\n\n    /**\n     * Get the {@link ClientSession} for a given cluster session id.\n     *\n     * @param clusterSessionId to be looked up.\n     * @return the {@link ClientSession} that matches the clusterSessionId.\n     */\n    ClientSession getClientSession(long clusterSessionId);\n\n    /**\n     * Get the current collection of cluster client sessions.\n     * <p>\n     * The {@link java.util.Iterator} on this class does not support nested iteration. It reuses the iterator to\n     * avoid allocation.\n     *\n     * @return the current collection of cluster client sessions.\n     */\n    Collection<ClientSession> clientSessions();\n\n    /**\n     * For each iterator over {@link ClientSession}s using the most efficient method possible.\n     *\n     * @param action to be taken for each {@link ClientSession} in turn.\n     */\n    void forEachClientSession(Consumer<? super ClientSession> action);\n\n    /**\n     * Request the close of a {@link ClientSession} by sending the request to the consensus module.\n     *\n     * @param clusterSessionId to be closed.\n     * @return true if the event to close a session was sent or false if back pressure was applied.\n     * @throws ClusterException if the clusterSessionId is not recognised.\n     */\n    boolean closeClientSession(long clusterSessionId);\n\n    /**\n     * Cluster time as {@link #timeUnit()}s since 1 Jan 1970 UTC.\n     *\n     * @return time as {@link #timeUnit()}s since 1 Jan 1970 UTC.\n     */\n    long time();\n\n    /**\n     * The unit of time applied when timestamping and {@link #time()} operations.\n     *\n     * @return the unit of time applied when timestamping and {@link #time()} operations.\n     */\n    TimeUnit timeUnit();\n\n    /**\n     * Schedule a timer for a given deadline and provide a correlation id to identify the timer when it expires or\n     * for cancellation. This action is asynchronous, when rescheduling it will race with the timer expiring.\n     * <p>\n     * If the correlationId is for an existing scheduled timer then it will be rescheduled to the new deadline. However,\n     * it is best to generate correlationIds in a monotonic fashion and be aware of potential clashes with other\n     * services in the same cluster. Service isolation can be achieved by using the upper bits for service id.\n     * <p>\n     * Timers should only be scheduled or cancelled in the context of processing a\n     * {@link ClusteredService#onSessionMessage(ClientSession, long, DirectBuffer, int, int, Header)},\n     * {@link ClusteredService#onTimerEvent(long, long)},\n     * {@link ClusteredService#onSessionOpen(ClientSession, long)}, or\n     * {@link ClusteredService#onSessionClose(ClientSession, long, CloseReason)}.\n     * If applied to other events then they are not guaranteed to be reliable.\n     * <p>\n     * Callers of this method must loop until the method succeeds.\n     *\n     * <pre>{@code\n     * private Cluster cluster;\n     * // Lines omitted...\n     *\n     * cluster.idleStrategy().reset();\n     * while (!cluster.scheduleTimer(correlationId, deadline))\n     * {\n     *     cluster.idleStrategy().idle();\n     * }\n     * }</pre>\n     * <p>\n     * The cluster's idle strategy must be used in the body of the loop to allow for the clustered service to be\n     * shutdown if required.\n     *\n     * @param correlationId to identify the timer when it expires. {@link Long#MAX_VALUE} not supported.\n     * @param deadline      time after which the timer will fire. {@link Long#MAX_VALUE} not supported.\n     * @return {@code true} if the request to schedule a timer has been sent or {@code false} in case of\n     * {@link io.aeron.Publication#ADMIN_ACTION} or {@link io.aeron.Publication#BACK_PRESSURED}.\n     * @throws ClusterException if request fails with an error.\n     * @see #cancelTimer(long)\n     */\n    boolean scheduleTimer(long correlationId, long deadline);\n\n    /**\n     * Cancel a previously scheduled timer. This action is asynchronous and will race with the timer expiring.\n     * <p>\n     * Timers should only be scheduled or cancelled in the context of processing a\n     * {@link ClusteredService#onSessionMessage(ClientSession, long, DirectBuffer, int, int, Header)},\n     * {@link ClusteredService#onTimerEvent(long, long)},\n     * {@link ClusteredService#onSessionOpen(ClientSession, long)}, or\n     * {@link ClusteredService#onSessionClose(ClientSession, long, CloseReason)}.\n     * If applied to other events then they are not guaranteed to be reliable.\n     * <p>\n     * Callers of this method must loop until the method succeeds.\n     *\n     * <pre>{@code\n     * private Cluster cluster;\n     * // Lines omitted...\n     *\n     * cluster.idleStrategy().reset();\n     * while (!cluster.cancelTimer(correlationId))\n     * {\n     *     cluster.idleStrategy().idle();\n     * }\n     * }</pre>\n     *\n     * @param correlationId for the timer provided when it was scheduled. {@link Long#MAX_VALUE} not supported.\n     * @return {@code true} if the request to cancel a timer has been sent or {@code false} in case of\n     * {@link io.aeron.Publication#ADMIN_ACTION} or {@link io.aeron.Publication#BACK_PRESSURED}.\n     * @throws ClusterException if request fails with an error.\n     * @see #scheduleTimer(long, long)\n     */\n    boolean cancelTimer(long correlationId);\n\n    /**\n     * Offer a message as ingress to the cluster for sequencing. This will happen efficiently over IPC to the\n     * consensus module and have the cluster session of as the\n     * {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_ID_PROP_NAME}.\n     * <p>\n     * Callers of this method must loop until the method succeeds.\n     *\n     * <pre>{@code\n     * private Cluster cluster;\n     * // Lines omitted...\n     *\n     * cluster.idleStrategy().reset();\n     * while(true)\n     * {\n     *     final long position = cluster.offer(buffer, offset, length);\n     *     if (position > 0)\n     *     {\n     *         break;\n     *     }\n     *     else if (Publication.ADMIN_ACTION != position && Publication.BACK_PRESSURED != position)\n     *     {\n     *         throw new ClusterException(\"Internal offer failed: \" + position);\n     *     }\n     *\n     *     cluster.idleStrategy.idle();\n     * }\n     * }</pre>\n     * <p>\n     * The cluster's idle strategy must be used in the body of the loop to allow for the clustered service to be\n     * shutdown if required.\n     *\n     * @param buffer containing the message to be offered.\n     * @param offset in the buffer at which the encoded message begins.\n     * @param length in the buffer of the encoded message.\n     * @return positive value if successful.\n     * @see io.aeron.Publication#offer(DirectBuffer, int, int)\n     */\n    long offer(DirectBuffer buffer, int offset, int length);\n\n    /**\n     * Offer a message as ingress to the cluster for sequencing. This will happen efficiently over IPC to the\n     * consensus module and have the cluster session of as the\n     * {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_ID_PROP_NAME}.\n     * <p>\n     * The first vector must be left free to be filled in for the session message header.\n     * <p>\n     * Callers of this method should loop until the method succeeds, see\n     * {@link io.aeron.cluster.service.Cluster#offer(DirectBuffer, int, int)} for an example.\n     *\n     * @param vectors containing the message parts with the first left to be filled.\n     * @return positive value if successful.\n     * @see io.aeron.Publication#offer(DirectBufferVector[])\n     */\n    long offer(DirectBufferVector[] vectors);\n\n    /**\n     * Try to claim a range in the publication log into which a message can be written with zero copy semantics.\n     * Once the message has been written then {@link BufferClaim#commit()} should be called thus making it available.\n     * <p>\n     * On successful claim, the Cluster session header will be written to the start of the claimed buffer section.\n     * Clients <b>MUST</b> write into the claimed buffer region at offset + {@link AeronCluster#SESSION_HEADER_LENGTH}.\n     * <p>\n     * Callers of this method must loop until the method succeeds.\n     *\n     * <pre>{@code\n     * private final BufferClaim bufferClaim = new BufferClaim();\n     * private Cluster cluster;\n     * // Lines omitted...\n     *\n     * final DirectBuffer srcBuffer = acquireMessage();\n     * cluster.idleStrategy().reset();\n     * while(true)\n     * {\n     *     final long position = cluster.tryClaim(length, bufferClaim);\n     *     if (position > 0)\n     *     {\n     *         final MutableDirectBuffer buffer = bufferClaim.buffer();\n     *         final int offset = bufferClaim.offset();\n     *         // ensure that data is written after the session header\n     *         buffer.putBytes(offset + AeronCluster.SESSION_HEADER_LENGTH, srcBuffer, 0, length);\n     *         bufferClaim.commit();\n     *         break;\n     *     }\n     *     else if (Publication.ADMIN_ACTION != position && Publication.BACK_PRESSURED != position)\n     *     {\n     *         throw new ClusterException(\"Internal tryClaim failed: \" + position);\n     *     }\n     *\n     *     cluster.idleStrategy.idle();\n     * }\n     * }</pre>\n     *\n     * @param length      of the range to claim, in bytes.\n     * @param bufferClaim to be populated if the claim succeeds.\n     * @return positive value if successful.\n     * @throws IllegalArgumentException if the length is greater than {@link io.aeron.Publication#maxPayloadLength()}.\n     * @see io.aeron.Publication#tryClaim(int, BufferClaim)\n     * @see BufferClaim#commit()\n     * @see BufferClaim#abort()\n     */\n    long tryClaim(int length, BufferClaim bufferClaim);\n\n    /**\n     * {@link IdleStrategy} which should be used by the service when it experiences back-pressure on egress,\n     * closing sessions, making timer requests, or any long-running actions.\n     *\n     * @return the {@link IdleStrategy} which should be used by the service when it experiences back-pressure.\n     */\n    IdleStrategy idleStrategy();\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ClusterClock.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.cluster.codecs.ClusterTimeUnit;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A clock representing a number of {@link TimeUnit}s since 1 Jan 1970 UTC. Defaults to {@link TimeUnit#MILLISECONDS}.\n * <p>\n * This is the clock used to timestamp sequenced messages for the cluster log and timers. Implementations should\n * be efficient otherwise throughput of the cluster will be impacted due to frequency of use.\n */\n@FunctionalInterface\npublic interface ClusterClock\n{\n    /**\n     * The unit of time returned from the {@link #time()} method.\n     *\n     * @return the unit of time returned from the {@link #time()} method.\n     */\n    default TimeUnit timeUnit()\n    {\n        return TimeUnit.MILLISECONDS;\n    }\n\n    /**\n     * The count of {@link #timeUnit()}s since 1 Jan 1970 UTC.\n     *\n     * @return the count of {@link #timeUnit()}s since 1 Jan 1970 UTC.\n     */\n    long time();\n\n    /**\n     * Get the current time in {@link TimeUnit#MILLISECONDS}.\n     *\n     * @return the current time in {@link TimeUnit#MILLISECONDS}.\n     */\n    default long timeMillis()\n    {\n        return timeUnit().toMillis(time());\n    }\n\n    /**\n     * Get the current time in {@link TimeUnit#MICROSECONDS}.\n     *\n     * @return the current time in {@link TimeUnit#MICROSECONDS}.\n     */\n    default long timeMicros()\n    {\n        return timeUnit().toMicros(time());\n    }\n\n    /**\n     * Get the current time in {@link TimeUnit#NANOSECONDS}.\n     *\n     * @return the current time in {@link TimeUnit#NANOSECONDS}.\n     */\n    default long timeNanos()\n    {\n        return timeUnit().toNanos(time());\n    }\n\n    /**\n     * Convert given Cluster time to {@link TimeUnit#NANOSECONDS}.\n     *\n     * @param time to convert to nanoseconds.\n     * @return time in {@link TimeUnit#NANOSECONDS}.\n     */\n    default long convertToNanos(final long time)\n    {\n        return timeUnit().toNanos(time);\n    }\n\n    /**\n     * Map {@link TimeUnit} to a corresponding {@link ClusterTimeUnit}.\n     *\n     * @param timeUnit to map to a corresponding {@link ClusterTimeUnit}.\n     * @return a corresponding {@link ClusterTimeUnit}.\n     */\n    static ClusterTimeUnit map(final TimeUnit timeUnit)\n    {\n        switch (timeUnit)\n        {\n            case MILLISECONDS:\n                return ClusterTimeUnit.MILLIS;\n\n            case MICROSECONDS:\n                return ClusterTimeUnit.MICROS;\n\n            case NANOSECONDS:\n                return ClusterTimeUnit.NANOS;\n\n            default:\n                throw new IllegalArgumentException(\"unsupported time unit: \" + timeUnit);\n        }\n    }\n\n    /**\n     * Map {@link ClusterTimeUnit} to a corresponding {@link TimeUnit}.\n     *\n     * @param clusterTimeUnit to map to a corresponding {@link TimeUnit}.\n     * @return a corresponding {@link TimeUnit}, if {@link ClusterTimeUnit#NULL_VAL} is passed then defaults\n     * to {@link TimeUnit#MILLISECONDS}.\n     */\n    static TimeUnit map(final ClusterTimeUnit clusterTimeUnit)\n    {\n        switch (clusterTimeUnit)\n        {\n            case NULL_VAL:\n            case MILLIS:\n                return TimeUnit.MILLISECONDS;\n\n            case MICROS:\n                return TimeUnit.MICROSECONDS;\n\n            case NANOS:\n                return TimeUnit.NANOSECONDS;\n        }\n\n        throw new IllegalArgumentException(\"unsupported time unit: \" + clusterTimeUnit);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ClusterCounters.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.Counter;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.concurrent.status.CountersReader.*;\n\n/**\n * For allocating and finding cluster associated counters identified by\n * {@link ClusteredServiceContainer.Context#clusterId()}.\n */\npublic final class ClusterCounters\n{\n    /**\n     * Suffix for `clusterId` in the counter label.\n     */\n    public static final String CLUSTER_ID_LABEL_SUFFIX = \" - clusterId=\";\n    static final String SERVICE_ID_SUFFIX = \" serviceId=\";\n\n    private ClusterCounters()\n    {\n    }\n\n    /**\n     * Allocate a counter to represent component state within a cluster.\n     *\n     * @param aeron      to allocate the counter.\n     * @param tempBuffer temporary storage to create label and metadata.\n     * @param name       of the counter for the label.\n     * @param typeId     for the counter.\n     * @param clusterId  to which the allocated counter belongs.\n     * @return the new {@link Counter}.\n     */\n    public static Counter allocate(\n        final Aeron aeron,\n        final MutableDirectBuffer tempBuffer,\n        final String name,\n        final int typeId,\n        final int clusterId)\n    {\n        int index = 0;\n        tempBuffer.putInt(index, clusterId);\n        index += SIZE_OF_INT;\n        final int keyLength = index;\n\n        index += tempBuffer.putStringWithoutLengthAscii(index, name);\n        index += tempBuffer.putStringWithoutLengthAscii(index, CLUSTER_ID_LABEL_SUFFIX);\n        index += tempBuffer.putIntAscii(index, clusterId);\n\n        return aeron.addCounter(typeId, tempBuffer, 0, keyLength, tempBuffer, keyLength, index - keyLength);\n    }\n\n    /**\n     * Allocate a counter to represent component state within a cluster and append a version info to its label.\n     *\n     * @param aeron          to allocate the counter.\n     * @param tempBuffer     temporary storage to create label and metadata.\n     * @param name           of the counter for the label.\n     * @param typeId         for the counter.\n     * @param clusterId      to which the allocated counter belongs.\n     * @param version        of the component.\n     * @param commitHashCode Git commit SHA of the component.\n     * @return the new {@link Counter}.\n     */\n    public static Counter allocateVersioned(\n        final Aeron aeron,\n        final MutableDirectBuffer tempBuffer,\n        final String name,\n        final int typeId,\n        final int clusterId,\n        final String version,\n        final String commitHashCode)\n    {\n        int index = 0;\n        tempBuffer.putInt(index, clusterId);\n        index += SIZE_OF_INT;\n        final int keyLength = index;\n\n        index += tempBuffer.putStringWithoutLengthAscii(index, name);\n        index += tempBuffer.putStringWithoutLengthAscii(index, CLUSTER_ID_LABEL_SUFFIX);\n        index += tempBuffer.putIntAscii(index, clusterId);\n        index += AeronCounters.appendVersionInfo(tempBuffer, index, version, commitHashCode);\n\n        return aeron.addCounter(typeId, tempBuffer, 0, keyLength, tempBuffer, keyLength, index - keyLength);\n    }\n\n    /**\n     * Find the counter id for a type of counter in a cluster.\n     *\n     * @param counters  to search within.\n     * @param typeId    of the counter.\n     * @param clusterId to which the allocated counter belongs.\n     * @return the matching counter id or {@link Aeron#NULL_VALUE} if not found.\n     */\n    public static int find(final CountersReader counters, final int typeId, final int clusterId)\n    {\n        final AtomicBuffer buffer = counters.metaDataBuffer();\n\n        for (int i = 0, size = counters.maxCounterId(); i < size; i++)\n        {\n            final int counterState = counters.getCounterState(i);\n\n            if (counterState == RECORD_ALLOCATED)\n            {\n                if (counters.getCounterTypeId(i) == typeId &&\n                    buffer.getInt(CountersReader.metaDataOffset(i) + KEY_OFFSET) == clusterId)\n                {\n                    return i;\n                }\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return Aeron.NULL_VALUE;\n    }\n\n    /**\n     * Allocate a counter to represent component state within a cluster.\n     *\n     * @param aeron      to allocate the counter.\n     * @param tempBuffer temporary storage to create label and metadata.\n     * @param name       of the counter for the label.\n     * @param typeId     for the counter.\n     * @param clusterId  to which the allocated counter belongs.\n     * @param serviceId  to which the allocated counter belongs.\n     * @return the {@link Counter} for the commit position.\n     */\n    public static Counter allocateServiceCounter(\n        final Aeron aeron,\n        final MutableDirectBuffer tempBuffer,\n        final String name,\n        final int typeId,\n        final int clusterId,\n        final int serviceId)\n    {\n        int index = 0;\n        tempBuffer.putInt(index, clusterId);\n        index += SIZE_OF_INT;\n        tempBuffer.putInt(index, serviceId);\n        index += SIZE_OF_INT;\n        final int keyLength = index;\n\n        index += tempBuffer.putStringWithoutLengthAscii(index, name);\n        index += tempBuffer.putStringWithoutLengthAscii(index, CLUSTER_ID_LABEL_SUFFIX);\n        index += tempBuffer.putIntAscii(index, clusterId);\n        index += tempBuffer.putStringWithoutLengthAscii(index, SERVICE_ID_SUFFIX);\n        index += tempBuffer.putIntAscii(index, serviceId);\n\n        return aeron.addCounter(typeId, tempBuffer, 0, keyLength, tempBuffer, keyLength, index - keyLength);\n    }\n\n    static Counter allocateServiceErrorCounter(\n        final Aeron aeron,\n        final MutableDirectBuffer tempBuffer,\n        final int clusterId,\n        final int serviceId)\n    {\n        int index = 0;\n        tempBuffer.putInt(index, clusterId);\n        index += SIZE_OF_INT;\n        tempBuffer.putInt(index, serviceId);\n        index += SIZE_OF_INT;\n        final int keyLength = index;\n\n        index += tempBuffer.putStringWithoutLengthAscii(index, \"Cluster Container Errors\");\n        index += tempBuffer.putStringWithoutLengthAscii(index, CLUSTER_ID_LABEL_SUFFIX);\n        index += tempBuffer.putIntAscii(index, clusterId);\n        index += tempBuffer.putStringWithoutLengthAscii(index, SERVICE_ID_SUFFIX);\n        index += tempBuffer.putIntAscii(index, serviceId);\n        index += AeronCounters.appendVersionInfo(\n            tempBuffer, index, ClusteredServiceContainerVersion.VERSION, ClusteredServiceContainerVersion.GIT_SHA);\n\n        return aeron.addCounter(\n            AeronCounters.CLUSTER_CLUSTERED_SERVICE_ERROR_COUNT_TYPE_ID,\n            tempBuffer,\n            0,\n            keyLength,\n            tempBuffer,\n            keyLength,\n            index - keyLength);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ClusterMarkFile.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.mark.ClusterComponentType;\nimport io.aeron.cluster.codecs.mark.MarkFileHeaderDecoder;\nimport io.aeron.cluster.codecs.mark.MarkFileHeaderEncoder;\nimport io.aeron.cluster.codecs.mark.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.mark.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.mark.VarAsciiEncodingEncoder;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.MarkFile;\nimport org.agrona.SemanticVersion;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.nio.MappedByteBuffer;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.function.Consumer;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PAGE_MIN_SIZE;\n\n/**\n * Used to indicate if a cluster component is running and what configuration it is using. Errors encountered by\n * the service are recorded within this file by a {@link org.agrona.concurrent.errors.DistinctErrorLog}.\n */\npublic final class ClusterMarkFile implements AutoCloseable\n{\n    /**\n     * Major version.\n     */\n    public static final int MAJOR_VERSION = 0;\n    /**\n     * Minor version.\n     */\n    public static final int MINOR_VERSION = 3;\n    /**\n     * Patch version.\n     */\n    public static final int PATCH_VERSION = 0;\n    /**\n     * Full semantic version.\n     */\n    public static final int SEMANTIC_VERSION = SemanticVersion.compose(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION);\n    /**\n     * Length of the {@code header} section.\n     */\n    public static final int HEADER_LENGTH = 8 * 1024;\n    /**\n     * Special version to indicate that component failed to start.\n     */\n    public static final int VERSION_FAILED = -1;\n    /**\n     * Min length for the error log buffer.\n     */\n    public static final int ERROR_BUFFER_MIN_LENGTH = 1024 * 1024;\n    /**\n     * Max allowed length for the error log buffer.\n     */\n    public static final int ERROR_BUFFER_MAX_LENGTH = Integer.MAX_VALUE - HEADER_LENGTH;\n    /**\n     * File extension used by the mark file.\n     */\n    public static final String FILE_EXTENSION = \".dat\";\n    /**\n     * File extension used by the link file.\n     */\n    public static final String LINK_FILE_EXTENSION = \".lnk\";\n    /**\n     * Mark file name.\n     */\n    public static final String FILENAME = \"cluster-mark\" + FILE_EXTENSION;\n    /**\n     * Link file name.\n     */\n    public static final String LINK_FILENAME = \"cluster-mark\" + LINK_FILE_EXTENSION;\n    /**\n     * Service mark file name.\n     */\n    public static final String SERVICE_FILENAME_PREFIX = \"cluster-mark-service-\";\n\n    private static final UnsafeBuffer EMPTY_BUFFER = new UnsafeBuffer(0, 0);\n\n    private static final int HEADER_OFFSET = MessageHeaderDecoder.ENCODED_LENGTH;\n\n    private final MarkFileHeaderDecoder headerDecoder = new MarkFileHeaderDecoder();\n    private final MarkFileHeaderEncoder headerEncoder = new MarkFileHeaderEncoder();\n    private final MarkFile markFile;\n    private final UnsafeBuffer buffer;\n    private final UnsafeBuffer errorBuffer;\n    private final int headerOffset;\n\n    /**\n     * Create new {@link MarkFile} for a cluster component but check if an existing component is active.\n     *\n     * @param file              full qualified file to the {@link MarkFile}.\n     * @param type              of cluster component the {@link MarkFile} represents.\n     * @param errorBufferLength for storing the error log.\n     * @param epochClock        for checking liveness against.\n     * @param timeoutMs         for the activity check on an existing {@link MarkFile}.\n     * @deprecated Use {@link #ClusterMarkFile(File, ClusterComponentType, int, EpochClock, long, int)} instead.\n     */\n    @Deprecated(forRemoval = true)\n    public ClusterMarkFile(\n        final File file,\n        final ClusterComponentType type,\n        final int errorBufferLength,\n        final EpochClock epochClock,\n        final long timeoutMs)\n    {\n        this(file, type, errorBufferLength, epochClock, timeoutMs, PAGE_MIN_SIZE);\n    }\n\n    /**\n     * Create new {@link MarkFile} for a cluster component but check if an existing component is active.\n     *\n     * @param file              full qualified file to the {@link MarkFile}.\n     * @param type              of cluster component the {@link MarkFile} represents.\n     * @param errorBufferLength for storing the error log.\n     * @param epochClock        for checking liveness against.\n     * @param timeoutMs         for the activity check on an existing {@link MarkFile}.\n     * @param filePageSize      for aligning file length to.\n     * @since 1.48.0\n     */\n    public ClusterMarkFile(\n        final File file,\n        final ClusterComponentType type,\n        final int errorBufferLength,\n        final EpochClock epochClock,\n        final long timeoutMs,\n        final int filePageSize)\n    {\n        if (errorBufferLength < ERROR_BUFFER_MIN_LENGTH || errorBufferLength > ERROR_BUFFER_MAX_LENGTH)\n        {\n            throw new IllegalArgumentException(\"Invalid errorBufferLength: \" + errorBufferLength);\n        }\n\n        LogBufferDescriptor.checkPageSize(filePageSize);\n\n        final boolean markFileExists = file.exists();\n        final int totalFileLength =\n            BitUtil.align(HEADER_LENGTH + errorBufferLength, filePageSize);\n\n        final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n\n        final long candidateTermId;\n        if (markFileExists)\n        {\n            final int currentHeaderOffset = headerOffset(file);\n            final MarkFile existingMarkFile = new MarkFile(\n                file,\n                true,\n                currentHeaderOffset + MarkFileHeaderDecoder.versionEncodingOffset(),\n                currentHeaderOffset + MarkFileHeaderDecoder.activityTimestampEncodingOffset(),\n                totalFileLength,\n                timeoutMs,\n                epochClock,\n                (version) ->\n                {\n                    if (VERSION_FAILED == version)\n                    {\n                        System.err.println(\"mark file version -1 indicates error on previous startup.\");\n                    }\n                    else if (SemanticVersion.major(version) != MAJOR_VERSION)\n                    {\n                        throw new ClusterException(\"mark file major version \" + SemanticVersion.major(version) +\n                            \" does not match software: \" + MAJOR_VERSION);\n                    }\n                },\n                null);\n            final UnsafeBuffer existingBuffer = existingMarkFile.buffer();\n\n            if (0 != currentHeaderOffset)\n            {\n                headerDecoder.wrapAndApplyHeader(existingBuffer, 0, messageHeaderDecoder);\n            }\n            else\n            {\n                headerDecoder.wrap(\n                    existingBuffer, 0, MarkFileHeaderDecoder.BLOCK_LENGTH, MarkFileHeaderDecoder.SCHEMA_VERSION);\n            }\n\n            final ClusterComponentType existingType = headerDecoder.componentType();\n            if (existingType != ClusterComponentType.UNKNOWN && existingType != type)\n            {\n                if (existingType != ClusterComponentType.BACKUP || ClusterComponentType.CONSENSUS_MODULE != type)\n                {\n                    throw new ClusterException(\n                        \"existing Mark file type \" + existingType + \" not same as required type \" + type);\n                }\n            }\n\n            final int existingErrorBufferLength = headerDecoder.errorBufferLength();\n            final int headerLength = headerDecoder.headerLength();\n            final UnsafeBuffer existingErrorBuffer =\n                new UnsafeBuffer(existingBuffer, headerLength, existingErrorBufferLength);\n\n            saveExistingErrors(file, existingErrorBuffer, type, CommonContext.fallbackLogger());\n            existingErrorBuffer.setMemory(0, existingErrorBufferLength, (byte)0);\n\n            candidateTermId = headerDecoder.candidateTermId();\n\n            if (0 != currentHeaderOffset)\n            {\n                markFile = existingMarkFile;\n                buffer = existingBuffer;\n            }\n            else\n            {\n                headerDecoder.wrap(EMPTY_BUFFER, 0, 0, 0);\n                CloseHelper.close(existingMarkFile);\n\n                markFile = new MarkFile(\n                    file,\n                    true,\n                    HEADER_OFFSET + MarkFileHeaderDecoder.versionEncodingOffset(),\n                    HEADER_OFFSET + MarkFileHeaderDecoder.activityTimestampEncodingOffset(),\n                    totalFileLength,\n                    timeoutMs,\n                    epochClock,\n                    (version) -> {},\n                    null);\n                buffer = markFile.buffer();\n                buffer.setMemory(0, headerLength, (byte)0);\n            }\n        }\n        else\n        {\n            markFile = new MarkFile(\n                file,\n                false,\n                HEADER_OFFSET + MarkFileHeaderDecoder.versionEncodingOffset(),\n                HEADER_OFFSET + MarkFileHeaderDecoder.activityTimestampEncodingOffset(),\n                totalFileLength,\n                timeoutMs,\n                epochClock,\n                (version) -> {},\n                null);\n            buffer = markFile.buffer();\n            candidateTermId = NULL_VALUE;\n        }\n\n        headerOffset = HEADER_OFFSET;\n\n        errorBuffer = new UnsafeBuffer(buffer, HEADER_LENGTH, errorBufferLength);\n\n        headerEncoder\n            .wrapAndApplyHeader(buffer, 0, new MessageHeaderEncoder())\n            .componentType(type)\n            .startTimestamp(epochClock.time())\n            .pid(SystemUtil.getPid())\n            .candidateTermId(candidateTermId)\n            .headerLength(HEADER_LENGTH)\n            .errorBufferLength(errorBufferLength);\n\n        headerDecoder.wrapAndApplyHeader(buffer, 0, messageHeaderDecoder);\n    }\n\n    /**\n     * Construct to read the status of an existing {@link MarkFile} for a cluster component.\n     *\n     * @param directory  in which the mark file exists.\n     * @param filename   for the {@link MarkFile}.\n     * @param epochClock to be used for checking liveness.\n     * @param timeoutMs  to wait for file to exist.\n     * @param logger     to which debug information will be written if an issue occurs.\n     */\n    public ClusterMarkFile(\n        final File directory,\n        final String filename,\n        final EpochClock epochClock,\n        final long timeoutMs,\n        final Consumer<String> logger)\n    {\n        this(openExistingMarkFile(directory, filename, epochClock, timeoutMs, logger));\n    }\n\n    ClusterMarkFile(final MarkFile markFile)\n    {\n        this.markFile = markFile;\n        buffer = markFile.buffer();\n\n        headerOffset = headerOffset(buffer);\n        if (0 != headerOffset)\n        {\n            headerEncoder.wrap(buffer, headerOffset);\n            headerDecoder.wrapAndApplyHeader(buffer, 0, new MessageHeaderDecoder());\n        }\n        else\n        {\n            headerEncoder.wrap(buffer, 0);\n            headerDecoder.wrap(buffer, 0, MarkFileHeaderDecoder.BLOCK_LENGTH, MarkFileHeaderDecoder.SCHEMA_VERSION);\n        }\n\n        errorBuffer = new UnsafeBuffer(buffer, headerDecoder.headerLength(), headerDecoder.errorBufferLength());\n    }\n\n    /**\n     * Get the parent directory containing the mark file.\n     *\n     * @return parent directory of the mark file.\n     * @see MarkFile#parentDirectory()\n     */\n    public File parentDirectory()\n    {\n        return markFile.parentDirectory();\n    }\n\n    /**\n     * Determines if this path name matches the service mark file name pattern.\n     *\n     * @param path       to examine.\n     * @param attributes ignored, only needed for BiPredicate signature matching.\n     * @return true if the name matches.\n     */\n    public static boolean isServiceMarkFile(final Path path, final BasicFileAttributes attributes)\n    {\n        final String fileName = path.getFileName().toString();\n        return fileName.startsWith(SERVICE_FILENAME_PREFIX) && fileName.endsWith(FILE_EXTENSION);\n    }\n\n    /**\n     * Determines if this path name matches the consensus module file name pattern.\n     *\n     * @param path       to examine.\n     * @param attributes ignored, only needed for BiPredicate signature matching.\n     * @return true if the name matches.\n     */\n    public static boolean isConsensusModuleMarkFile(final Path path, final BasicFileAttributes attributes)\n    {\n        return path.getFileName().toString().equals(FILENAME);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (!markFile.isClosed())\n        {\n            headerEncoder.wrap(EMPTY_BUFFER, 0);\n            headerDecoder.wrap(EMPTY_BUFFER, 0, 0, 0);\n            errorBuffer.wrap(0, 0);\n            CloseHelper.close(markFile);\n        }\n    }\n\n    /**\n     * Check if the {@link MarkFile} is closed.\n     *\n     * @return true if the {@link MarkFile} is closed.\n     */\n    public boolean isClosed()\n    {\n        return markFile.isClosed();\n    }\n\n    /**\n     * Get the current value of a candidate term id if a vote is placed in an election with volatile semantics.\n     *\n     * @return the current candidate term id within an election after voting or {@link Aeron#NULL_VALUE} if\n     * no voting phase of an election is currently active.\n     */\n    public long candidateTermId()\n    {\n        return markFile.isClosed() ? NULL_VALUE :\n            buffer.getLongVolatile(headerOffset + MarkFileHeaderDecoder.candidateTermIdEncodingOffset());\n    }\n\n    /**\n     * Cluster member id.\n     *\n     * @return cluster member id.\n     */\n    public int memberId()\n    {\n        return markFile.isClosed() ? NULL_VALUE : headerDecoder.memberId();\n    }\n\n    /**\n     * Member id assigned as part of active join to the log in a clustered service.\n     *\n     * @param memberId assigned as part of active join to the log in a clustered service.\n     */\n    public void memberId(final int memberId)\n    {\n        if (!markFile.isClosed())\n        {\n            headerEncoder.memberId(memberId);\n        }\n    }\n\n    /**\n     * Identity of the cluster instance so multiple clusters can run on the same driver.\n     *\n     * @return id of the cluster instance so multiple clusters can run on the same driver.\n     */\n    public int clusterId()\n    {\n        return markFile.isClosed() ? NULL_VALUE : headerDecoder.clusterId();\n    }\n\n    /**\n     * Identity of the cluster instance so multiple clusters can run on the same driver.\n     *\n     * @param clusterId of the cluster instance so multiple clusters can run on the same driver.\n     */\n    public void clusterId(final int clusterId)\n    {\n        if (!markFile.isClosed())\n        {\n            headerEncoder.clusterId(clusterId);\n        }\n    }\n\n    /**\n     * Signal the cluster component has concluded successfully and ready to start.\n     *\n     * @deprecated Use {@link #signalReady(long)} instead.\n     */\n    @Deprecated(forRemoval = true)\n    public void signalReady()\n    {\n        if (!markFile.isClosed())\n        {\n            markFile.signalReady(SEMANTIC_VERSION);\n        }\n    }\n\n    /**\n     * Signal the cluster component has concluded successfully and ready to start.\n     *\n     * @param activityTimestamp to be used.\n     */\n    public void signalReady(final long activityTimestamp)\n    {\n        signalReady(SEMANTIC_VERSION, activityTimestamp);\n    }\n\n    /**\n     * Signal the cluster component has failed to conclude and cannot start.\n     */\n    public void signalFailedStart()\n    {\n        signalReady(VERSION_FAILED, NULL_VALUE);\n    }\n\n    /**\n     * Signal the cluster component has been terminated.\n     */\n    public void signalTerminated()\n    {\n        if (!markFile.isClosed())\n        {\n            markFile.timestampRelease(NULL_VALUE);\n            force();\n        }\n    }\n\n    /**\n     * Update the activity timestamp as a proof of life.\n     *\n     * @param nowMs activity timestamp as a proof of life.\n     */\n    public void updateActivityTimestamp(final long nowMs)\n    {\n        if (!markFile.isClosed())\n        {\n            markFile.timestampRelease(nowMs);\n        }\n    }\n\n    /**\n     * Read the activity timestamp of the cluster component with volatile semantics.\n     *\n     * @return the activity timestamp of the cluster component with volatile semantics.\n     */\n    public long activityTimestampVolatile()\n    {\n        return markFile.isClosed() ? NULL_VALUE : markFile.timestampVolatile();\n    }\n\n    /**\n     * The encoder for writing the {@link MarkFile} header.\n     *\n     * @return the encoder for writing the {@link MarkFile} header.\n     */\n    public MarkFileHeaderEncoder encoder()\n    {\n        return headerEncoder;\n    }\n\n    /**\n     * The decoder for reading the {@link MarkFile} header.\n     *\n     * @return the decoder for reading the {@link MarkFile} header.\n     */\n    public MarkFileHeaderDecoder decoder()\n    {\n        return headerDecoder;\n    }\n\n    /**\n     * The direct buffer which wraps the region of the {@link MarkFile} which contains the error log.\n     *\n     * @return the direct buffer which wraps the region of the {@link MarkFile} which contains the error log.\n     */\n    public AtomicBuffer errorBuffer()\n    {\n        return errorBuffer;\n    }\n\n    /**\n     * Save the existing errors from a {@link MarkFile} to a {@link PrintStream} for logging.\n     *\n     * @param markFile    which contains the error buffer.\n     * @param errorBuffer which wraps the error log.\n     * @param type        type of the mark file being checked.\n     * @param logger      to which the existing errors will be printed.\n     */\n    public static void saveExistingErrors(\n        final File markFile,\n        final AtomicBuffer errorBuffer,\n        final ClusterComponentType type,\n        final PrintStream logger)\n    {\n        CommonContext.saveExistingErrors(markFile, errorBuffer, logger, type.name());\n    }\n\n    /**\n     * Check if the header length is sufficient for encoding the provided properties.\n     *\n     * @param aeronDirectory for the media driver.\n     * @param controlChannel for the consensus module.\n     * @param ingressChannel from the cluster clients.\n     * @param serviceName    for the application service.\n     * @param authenticator  for the application service.\n     */\n    public static void checkHeaderLength(\n        final String aeronDirectory,\n        final String controlChannel,\n        final String ingressChannel,\n        final String serviceName,\n        final String authenticator)\n    {\n        final int length =\n            HEADER_OFFSET +\n                MarkFileHeaderEncoder.BLOCK_LENGTH +\n                (5 * VarAsciiEncodingEncoder.lengthEncodingLength()) +\n                (null == aeronDirectory ? 0 : aeronDirectory.length()) +\n                (null == controlChannel ? 0 : controlChannel.length()) +\n                (null == ingressChannel ? 0 : ingressChannel.length()) +\n                (null == serviceName ? 0 : serviceName.length()) +\n                (null == authenticator ? 0 : authenticator.length());\n\n        if (length > HEADER_LENGTH)\n        {\n            throw new ClusterException(\n                \"ClusterMarkFile headerLength=\" + length + \" > headerLengthCapacity=\" + HEADER_LENGTH);\n        }\n    }\n\n    /**\n     * The filename to be used for the mark file given a service id.\n     *\n     * @param serviceId of the service the {@link ClusterMarkFile} represents.\n     * @return the filename to be used for the mark file given a service id.\n     */\n    public static String markFilenameForService(final int serviceId)\n    {\n        return SERVICE_FILENAME_PREFIX + serviceId + FILE_EXTENSION;\n    }\n\n    /**\n     * The filename to be used for the link file given a service id.\n     *\n     * @param serviceId of the service the {@link ClusterMarkFile} represents.\n     * @return the filename to be used for the link file given a service id.\n     */\n    public static String linkFilenameForService(final int serviceId)\n    {\n        return SERVICE_FILENAME_PREFIX + serviceId + LINK_FILE_EXTENSION;\n    }\n\n    /**\n     * The control properties for communicating between the consensus module and the services.\n     *\n     * @return the control properties for communicating between the consensus module and the services or {@code null}\n     * if mark file was already closed.\n     */\n    public ClusterNodeControlProperties loadControlProperties()\n    {\n        if (!markFile.isClosed())\n        {\n            headerDecoder.sbeRewind();\n            return new ClusterNodeControlProperties(\n                headerDecoder.memberId(),\n                headerDecoder.serviceStreamId(),\n                headerDecoder.consensusModuleStreamId(),\n                headerDecoder.aeronDirectory(),\n                headerDecoder.controlChannel());\n        }\n        return null;\n    }\n\n    /**\n     * Forces any changes made to the mark file's content to be written to the storage device containing the mapped\n     * file.\n     *\n     * @since 1.44.0\n     */\n    public void force()\n    {\n        if (!markFile.isClosed())\n        {\n            final MappedByteBuffer mappedByteBuffer = markFile.mappedByteBuffer();\n            mappedByteBuffer.force();\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ClusterMarkFile{\" +\n            \"semanticVersion=\" + SemanticVersion.toString(SEMANTIC_VERSION) +\n            \", markFile=\" + markFile.markFile() +\n            '}';\n    }\n\n    private void signalReady(final int version, final long activityTimestamp)\n    {\n        if (!markFile.isClosed())\n        {\n            markFile.timestampRelease(activityTimestamp);\n            markFile.signalReady(version);\n            force();\n        }\n    }\n\n    private static int headerOffset(final File file)\n    {\n        final MappedByteBuffer mappedByteBuffer = IoUtil.mapExistingFile(file, FILENAME);\n        try\n        {\n            final UnsafeBuffer unsafeBuffer =\n                new UnsafeBuffer(mappedByteBuffer, 0, HEADER_OFFSET);\n            return headerOffset(unsafeBuffer);\n        }\n        finally\n        {\n            IoUtil.unmap(mappedByteBuffer);\n        }\n    }\n\n    private static int headerOffset(final UnsafeBuffer headerBuffer)\n    {\n        final MessageHeaderDecoder decoder = new MessageHeaderDecoder();\n        decoder.wrap(headerBuffer, 0);\n        return MarkFileHeaderDecoder.TEMPLATE_ID == decoder.templateId() &&\n            MarkFileHeaderDecoder.SCHEMA_ID == decoder.schemaId() ? HEADER_OFFSET : 0;\n    }\n\n    private static MarkFile openExistingMarkFile(\n        final File directory,\n        final String filename,\n        final EpochClock epochClock,\n        final long timeoutMs,\n        final Consumer<String> logger)\n    {\n        final int headerOffset = headerOffset(new File(directory, filename));\n        return new MarkFile(\n            directory,\n            filename,\n            headerOffset + MarkFileHeaderDecoder.versionEncodingOffset(),\n            headerOffset + MarkFileHeaderDecoder.activityTimestampEncodingOffset(),\n            timeoutMs,\n            epochClock,\n            (version) ->\n            {\n                if (SemanticVersion.major(version) != MAJOR_VERSION)\n                {\n                    throw new ClusterException(\n                        \"mark file major version \" + SemanticVersion.major(version) +\n                            \" does not match software: \" + MAJOR_VERSION);\n                }\n            },\n            logger);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ClusterNodeControlProperties.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\n/**\n * Data class for holding the properties used when interacting with a cluster for local admin control.\n *\n * @see io.aeron.cluster.ClusterTool\n * @see ClusterMarkFile\n */\npublic final class ClusterNodeControlProperties\n{\n    /**\n     * Member id of the cluster to which the properties belong.\n     */\n    public final int memberId;\n\n    /**\n     * Stream id in the control channel on which the services listen.\n     */\n    public final int serviceStreamId;\n\n    /**\n     * Stream id in the control channel on which the consensus module listens.\n     */\n    public final int consensusModuleStreamId;\n\n    /**\n     * Directory where the Aeron Media Driver is running.\n     */\n    public final String aeronDirectoryName;\n\n    /**\n     * URI for the control channel.\n     */\n    public final String controlChannel;\n\n    /**\n     * Construct the set of properties for interacting with a cluster.\n     *\n     * @param memberId                of the cluster to which the properties belong.\n     * @param serviceStreamId         in the control channel on which the services listen.\n     * @param consensusModuleStreamId in the control channel on which the consensus module listens.\n     * @param aeronDirectoryName      where the Aeron Media Driver is running.\n     * @param controlChannel          for the services and consensus module.\n     */\n    ClusterNodeControlProperties(\n        final int memberId,\n        final int serviceStreamId,\n        final int consensusModuleStreamId,\n        final String aeronDirectoryName,\n        final String controlChannel)\n    {\n        this.memberId = memberId;\n        this.serviceStreamId = serviceStreamId;\n        this.consensusModuleStreamId = consensusModuleStreamId;\n        this.aeronDirectoryName = aeronDirectoryName;\n        this.controlChannel = controlChannel;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ClusterTerminationException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport org.agrona.concurrent.AgentTerminationException;\n\n/**\n * Used to terminate the {@link org.agrona.concurrent.Agent} within a cluster in an expected/unexpected fashion.\n */\npublic class ClusterTerminationException extends AgentTerminationException\n{\n    private static final long serialVersionUID = -2705156056823180407L;\n\n    /**\n     * Is expected termination?\n     */\n    private final boolean isExpected;\n\n    /**\n     * Construct an exception used to terminate the cluster with {@link #isExpected()} set to true.\n     */\n    public ClusterTerminationException()\n    {\n        this(true);\n    }\n\n    /**\n     * Construct an exception used to terminate the cluster.\n     *\n     * @param isExpected true if the termination is expected, i.e. it was requested.\n     */\n    public ClusterTerminationException(final boolean isExpected)\n    {\n        super(isExpected ? \"expected termination\" : \"unexpected termination\");\n        this.isExpected = isExpected;\n    }\n\n    /**\n     * Whether the termination is expected.\n     *\n     * @return true if expected otherwise false.\n     */\n    public boolean isExpected()\n    {\n        return isExpected;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ClusteredService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Interface which a service must implement to be contained in the cluster.\n * <p>\n * The {@code cluster} object should only be used to send messages to the cluster or schedule timers in\n * response to other messages and timers. Sending messages and timers should not happen from cluster lifecycle\n * methods like {@link #onStart(Cluster, Image)}, {@link #onRoleChange(Cluster.Role)} or\n * {@link #onTakeSnapshot(ExclusivePublication)}, or {@link #onTerminate(Cluster)}, except the session lifecycle\n * methods.\n */\npublic interface ClusteredService\n{\n    /**\n     * Start event where the service can perform any initialisation required and load snapshot state.\n     * The snapshot image can be null if no previous snapshot exists.\n     * <p>\n     * <b>Note:</b> As this is a potentially long-running operation the implementation should use\n     * {@link Cluster#idleStrategy()} and then occasionally call {@link org.agrona.concurrent.IdleStrategy#idle()} or\n     * {@link org.agrona.concurrent.IdleStrategy#idle(int)}, especially when polling the {@link Image} returns 0.\n     * <p>\n     * If a {@link RuntimeException} is thrown from this method, e.g. {@link IllegalStateException}, then this will\n     * cause the ClusteredServiceAgent and the ConsensusModuleAgent to shut down with an\n     * {@link org.agrona.concurrent.AgentTerminationException}.\n     *\n     * @param cluster       with which the service can interact.\n     * @param snapshotImage from which the service can load its archived state which can be null when no snapshot.\n     */\n    void onStart(Cluster cluster, Image snapshotImage);\n\n    /**\n     * A session has been opened for a client to the cluster.\n     *\n     * @param session   for the client which have been opened.\n     * @param timestamp at which the session was opened.\n     */\n    void onSessionOpen(ClientSession session, long timestamp);\n\n    /**\n     * A session has been closed for a client to the cluster.\n     *\n     * @param session     that has been closed.\n     * @param timestamp   at which the session was closed.\n     * @param closeReason the session was closed.\n     */\n    void onSessionClose(ClientSession session, long timestamp, CloseReason closeReason);\n\n    /**\n     * A message has been received to be processed by a clustered service.\n     *\n     * @param session   for the client which sent the message. This can be null if the client was a service.\n     * @param timestamp for when the message was received.\n     * @param buffer    containing the message.\n     * @param offset    in the buffer at which the message is encoded.\n     * @param length    of the encoded message.\n     * @param header    aeron header for the incoming message.\n     */\n    void onSessionMessage(\n        ClientSession session,\n        long timestamp,\n        DirectBuffer buffer,\n        int offset,\n        int length,\n        Header header);\n\n    /**\n     * A scheduled timer has expired.\n     *\n     * @param correlationId for the expired timer.\n     * @param timestamp     at which the timer expired.\n     */\n    void onTimerEvent(long correlationId, long timestamp);\n\n    /**\n     * The service should take a snapshot and store its state to the provided archive {@link ExclusivePublication}.\n     * <p>\n     * <b>Note:</b> As this is a potentially long-running operation the implementation should use\n     * {@link Cluster#idleStrategy()} and then occasionally call {@link org.agrona.concurrent.IdleStrategy#idle()} or\n     * {@link org.agrona.concurrent.IdleStrategy#idle(int)},\n     * especially when the snapshot {@link ExclusivePublication} returns {@link Publication#BACK_PRESSURED}.\n     *\n     * @param snapshotPublication to which the state should be recorded.\n     */\n    void onTakeSnapshot(ExclusivePublication snapshotPublication);\n\n    /**\n     * Notify that the cluster node has changed role.\n     *\n     * @param newRole that the node has assumed.\n     */\n    void onRoleChange(Cluster.Role newRole);\n\n    /**\n     * Called when the container is going to terminate but only after a successful start.\n     *\n     * @param cluster with which the service can interact.\n     */\n    void onTerminate(Cluster cluster);\n\n    /**\n     * An election has been successful and a leader has entered a new term.\n     *\n     * @param leadershipTermId    identity for the new leadership term.\n     * @param logPosition         position the log has reached as the result of this message.\n     * @param timestamp           for the new leadership term.\n     * @param termBaseLogPosition position at the beginning of the leadership term.\n     * @param leaderMemberId      who won the election.\n     * @param logSessionId        session id for the publication of the log.\n     * @param timeUnit            for the timestamps in the coming leadership term.\n     * @param appVersion          for the application configured in the consensus module.\n     */\n    default void onNewLeadershipTermEvent(\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final long termBaseLogPosition,\n        final int leaderMemberId,\n        final int logSessionId,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n    }\n\n    /**\n     * Implement this method to perform background tasks that are not related to the deterministic state machine\n     * model, such as keeping external connections alive to the cluster. This method must <b>not</b> be used to\n     * directly, or indirectly, update the service state. This method cannot be used for making calls on\n     * {@link Cluster} which could update the log such as {@link Cluster#scheduleTimer(long, long)} or\n     * {@link Cluster#offer(DirectBuffer, int, int)}.\n     * <p>\n     * This method is not for long-running operations. Time taken can impact latency and should only be used for\n     * short constant time operations.\n     *\n     * @param nowNs which can be used for measuring elapsed time and be used in the same way as\n     *              {@link System#nanoTime()}. This is <b>not</b> {@link Cluster#time()}.\n     * @return 0 if no work is done otherwise a positive number.\n     * @since 1.40.0\n     */\n    default int doBackgroundWork(final long nowNs)\n    {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ClusteredServiceAgent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.DirectBufferVector;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.codecs.ClusterAction;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderEncoder;\nimport io.aeron.driver.Configuration;\nimport io.aeron.driver.DutyCycleTracker;\nimport io.aeron.exceptions.AeronEvent;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.status.ReadableCounter;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.SemanticVersion;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.codecs.SourceLocation.LOCAL;\nimport static io.aeron.cluster.ConsensusModule.CLUSTER_ACTION_FLAGS_DEFAULT;\nimport static io.aeron.cluster.ConsensusModule.CLUSTER_ACTION_FLAGS_STANDBY_SNAPSHOT;\nimport static io.aeron.cluster.client.AeronCluster.SESSION_HEADER_LENGTH;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.COMMIT_POSITION_TYPE_ID;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.MARK_FILE_UPDATE_INTERVAL_NS;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.SNAPSHOT_TYPE_ID;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\n\nabstract class ClusteredServiceAgentLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nabstract class ClusteredServiceAgentHotFields extends ClusteredServiceAgentLhsPadding\n{\n    static final int LIFECYCLE_CALLBACK_NONE = 0;\n    static final int LIFECYCLE_CALLBACK_ON_START = 1;\n    static final int LIFECYCLE_CALLBACK_ON_TERMINATE = 2;\n    static final int LIFECYCLE_CALLBACK_ON_ROLE_CHANGE = 3;\n    static final int LIFECYCLE_CALLBACK_DO_BACKGROUND_WORK = 4;\n\n    static String lifecycleName(final int activeLifecycleCallback)\n    {\n        switch (activeLifecycleCallback)\n        {\n            case LIFECYCLE_CALLBACK_NONE:\n                return \"none\";\n            case LIFECYCLE_CALLBACK_ON_START:\n                return \"onStart\";\n            case LIFECYCLE_CALLBACK_ON_TERMINATE:\n                return \"onTerminate\";\n            case LIFECYCLE_CALLBACK_ON_ROLE_CHANGE:\n                return \"onRoleChange\";\n            case LIFECYCLE_CALLBACK_DO_BACKGROUND_WORK:\n                return \"doBackgroundWork\";\n            default:\n                return \"unknown\";\n        }\n    }\n\n    int activeLifecycleCallback;\n}\n\nabstract class ClusteredServiceAgentRhsPadding extends ClusteredServiceAgentHotFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\nfinal class ClusteredServiceAgent extends ClusteredServiceAgentRhsPadding implements Agent, Cluster, IdleStrategy\n{\n    private static final long ONE_MILLISECOND_NS = TimeUnit.MILLISECONDS.toNanos(1);\n    private static final long MARK_FILE_UPDATE_INTERVAL_MS =\n        TimeUnit.NANOSECONDS.toMillis(MARK_FILE_UPDATE_INTERVAL_NS);\n\n    private volatile boolean isAbort;\n    private boolean isServiceActive;\n    private final int serviceId;\n    private int memberId = NULL_VALUE;\n    private long closeHandlerRegistrationId;\n    private long ackId = 0;\n    private long terminationPosition = NULL_POSITION;\n    private long markFileUpdateDeadlineMs;\n    private long lastSlowTickNs;\n    private long clusterTime;\n    private long logPosition = NULL_POSITION;\n\n    private final IdleStrategy idleStrategy;\n    private final ClusterMarkFile markFile;\n    private final ClusteredServiceContainer.Context ctx;\n    private final Aeron aeron;\n    private final AgentInvoker aeronAgentInvoker;\n    private final ClusteredService service;\n    private final ConsensusModuleProxy consensusModuleProxy;\n    private final ServiceAdapter serviceAdapter;\n    private final EpochClock epochClock;\n    private final NanoClock nanoClock;\n    private final UnsafeBuffer messageBuffer = new UnsafeBuffer(new byte[Configuration.MAX_UDP_PAYLOAD_LENGTH]);\n    private final UnsafeBuffer headerBuffer = new UnsafeBuffer(\n        messageBuffer,\n        DataHeaderFlyweight.HEADER_LENGTH,\n        Configuration.MAX_UDP_PAYLOAD_LENGTH - DataHeaderFlyweight.HEADER_LENGTH);\n    private final DirectBufferVector headerVector = new DirectBufferVector(headerBuffer, 0, SESSION_HEADER_LENGTH);\n    private final SessionMessageHeaderEncoder sessionMessageHeaderEncoder = new SessionMessageHeaderEncoder();\n    private final ArrayList<ContainerClientSession> sessions = new ArrayList<>();\n    private final Long2ObjectHashMap<ContainerClientSession> sessionByIdMap = new Long2ObjectHashMap<>();\n    private final Collection<ClientSession> unmodifiableClientSessions = Collections.unmodifiableCollection(sessions);\n    private final BoundedLogAdapter logAdapter;\n    private final DutyCycleTracker dutyCycleTracker;\n    private final SnapshotDurationTracker snapshotDurationTracker;\n    private final String subscriptionAlias;\n    private int standbySnapshotFlags = CLUSTER_ACTION_FLAGS_DEFAULT;\n\n    private ReadableCounter commitPosition;\n    private ActiveLogEvent activeLogEvent;\n    private Role role = Role.FOLLOWER;\n    private TimeUnit timeUnit = null;\n    private long requestedAckPosition = NULL_POSITION;\n\n    ClusteredServiceAgent(final ClusteredServiceContainer.Context ctx)\n    {\n        logAdapter = new BoundedLogAdapter(this, ctx.logFragmentLimit());\n        this.ctx = ctx;\n\n        markFile = ctx.clusterMarkFile();\n        aeron = ctx.aeron();\n        aeronAgentInvoker = ctx.aeron().conductorAgentInvoker();\n        service = ctx.clusteredService();\n        idleStrategy = ctx.idleStrategy();\n        serviceId = ctx.serviceId();\n        epochClock = ctx.epochClock();\n        nanoClock = ctx.nanoClock();\n        dutyCycleTracker = ctx.dutyCycleTracker();\n        snapshotDurationTracker = ctx.snapshotDurationTracker();\n        subscriptionAlias = \"log-sc-\" + ctx.serviceId();\n\n        final String channel = ctx.controlChannel();\n        consensusModuleProxy = new ConsensusModuleProxy(aeron.addPublication(channel, ctx.consensusModuleStreamId()));\n        serviceAdapter = new ServiceAdapter(aeron.addSubscription(channel, ctx.serviceStreamId()), this);\n        sessionMessageHeaderEncoder.wrapAndApplyHeader(headerBuffer, 0, new MessageHeaderEncoder());\n    }\n\n    public void onStart()\n    {\n        closeHandlerRegistrationId = aeron.addCloseHandler(this::abort);\n        aeron.addUnavailableCounterHandler(this::counterUnavailable);\n        final CountersReader counters = aeron.countersReader();\n        commitPosition = awaitCommitPositionCounter(counters, ctx.clusterId());\n\n        recoverState(counters);\n        dutyCycleTracker.update(nanoClock.nanoTime());\n        isServiceActive = true;\n    }\n\n    public void onClose()\n    {\n        aeron.removeCloseHandler(closeHandlerRegistrationId);\n\n        if (isAbort)\n        {\n            ctx.abortLatch().countDown();\n        }\n        else\n        {\n            final CountedErrorHandler errorHandler = ctx.countedErrorHandler();\n            if (isServiceActive)\n            {\n                isServiceActive = false;\n                try\n                {\n                    service.onTerminate(this);\n                }\n                catch (final Exception ex)\n                {\n                    errorHandler.onError(ex);\n                }\n            }\n\n            CloseHelper.close(errorHandler, logAdapter);\n\n            if (!ctx.ownsAeronClient() && !aeron.isClosed())\n            {\n                CloseHelper.close(errorHandler, serviceAdapter);\n                CloseHelper.close(errorHandler, consensusModuleProxy);\n                disconnectEgress(errorHandler);\n            }\n        }\n\n        markFile.signalTerminated();\n        ctx.close();\n    }\n\n    public int doWork()\n    {\n        int workCount = 0;\n\n        final long nowNs = nanoClock.nanoTime();\n        dutyCycleTracker.measureAndUpdate(nowNs);\n\n        try\n        {\n            if (checkForClockTick(nowNs))\n            {\n                workCount += pollServiceAdapter();\n            }\n\n            if (null != logAdapter.image())\n            {\n                final int polled = logAdapter.poll(commitPosition.get());\n                workCount += polled;\n\n                if (0 == polled && logAdapter.isDone())\n                {\n                    closeLog();\n                }\n            }\n\n            workCount += invokeBackgroundWork(nowNs);\n        }\n        catch (final AgentTerminationException ex)\n        {\n            runTerminationHook();\n            throw ex;\n        }\n\n        return workCount;\n    }\n\n    public String roleName()\n    {\n        return ctx.serviceName();\n    }\n\n    public Cluster.Role role()\n    {\n        return role;\n    }\n\n    public int memberId()\n    {\n        return memberId;\n    }\n\n    public Aeron aeron()\n    {\n        return aeron;\n    }\n\n    public ClusteredServiceContainer.Context context()\n    {\n        return ctx;\n    }\n\n    public ClientSession getClientSession(final long clusterSessionId)\n    {\n        return sessionByIdMap.get(clusterSessionId);\n    }\n\n    public Collection<ClientSession> clientSessions()\n    {\n        return unmodifiableClientSessions;\n    }\n\n    public void forEachClientSession(final Consumer<? super ClientSession> action)\n    {\n        sessions.forEach(action);\n    }\n\n    public boolean closeClientSession(final long clusterSessionId)\n    {\n        checkForValidInvocation();\n\n        final ContainerClientSession clientSession = sessionByIdMap.get(clusterSessionId);\n        if (clientSession == null)\n        {\n            throw new ClusterException(\"unknown clusterSessionId: \" + clusterSessionId);\n        }\n\n        if (clientSession.isClosing())\n        {\n            return true;\n        }\n\n        int attempts = 3;\n        do\n        {\n            if (consensusModuleProxy.closeSession(clusterSessionId))\n            {\n                clientSession.markClosing();\n                return true;\n            }\n            idle();\n        }\n        while (--attempts > 0);\n\n        return false;\n    }\n\n    public TimeUnit timeUnit()\n    {\n        return timeUnit;\n    }\n\n    public long time()\n    {\n        return clusterTime;\n    }\n\n    public long logPosition()\n    {\n        return logPosition;\n    }\n\n    public boolean scheduleTimer(final long correlationId, final long deadline)\n    {\n        checkForValidInvocation();\n\n        return consensusModuleProxy.scheduleTimer(correlationId, deadline);\n    }\n\n    public boolean cancelTimer(final long correlationId)\n    {\n        checkForValidInvocation();\n\n        return consensusModuleProxy.cancelTimer(correlationId);\n    }\n\n    public long offer(final DirectBuffer buffer, final int offset, final int length)\n    {\n        checkForValidInvocation();\n        sessionMessageHeaderEncoder.clusterSessionId(context().serviceId());\n\n        return consensusModuleProxy.offer(headerBuffer, 0, SESSION_HEADER_LENGTH, buffer, offset, length);\n    }\n\n    public long offer(final DirectBufferVector[] vectors)\n    {\n        checkForValidInvocation();\n        sessionMessageHeaderEncoder.clusterSessionId(context().serviceId());\n        vectors[0] = headerVector;\n\n        return consensusModuleProxy.offer(vectors);\n    }\n\n    public long tryClaim(final int length, final BufferClaim bufferClaim)\n    {\n        checkForValidInvocation();\n        sessionMessageHeaderEncoder.clusterSessionId(context().serviceId());\n\n        return consensusModuleProxy.tryClaim(length + SESSION_HEADER_LENGTH, bufferClaim, headerBuffer);\n    }\n\n    public IdleStrategy idleStrategy()\n    {\n        return this;\n    }\n\n    public void reset()\n    {\n        idleStrategy.reset();\n    }\n\n    public void idle()\n    {\n        idleStrategy.idle();\n        doIdleWork();\n    }\n\n    public void idle(final int workCount)\n    {\n        idleStrategy.idle(workCount);\n        if (workCount <= 0)\n        {\n            doIdleWork();\n        }\n    }\n\n    private void doIdleWork()\n    {\n        if (Thread.currentThread().isInterrupted())\n        {\n            throw new AgentTerminationException(\"interrupted\");\n        }\n\n        final long nowNs = nanoClock.nanoTime();\n\n        checkForClockTick(nowNs);\n\n        if (isServiceActive)\n        {\n            invokeBackgroundWork(nowNs);\n        }\n    }\n\n    void onJoinLog(\n        final long logPosition,\n        final long maxLogPosition,\n        final int memberId,\n        final int logSessionId,\n        final int logStreamId,\n        final boolean isStartup,\n        final Cluster.Role role,\n        final boolean isStandby,\n        final String logChannel)\n    {\n        logAdapter.maxLogPosition(logPosition);\n        activeLogEvent = new ActiveLogEvent(\n            logPosition,\n            maxLogPosition,\n            memberId,\n            logSessionId,\n            logStreamId,\n            isStartup,\n            role,\n            isStandby,\n            logChannel);\n    }\n\n    void onServiceTerminationPosition(final long logPosition)\n    {\n        terminationPosition = logPosition;\n    }\n\n    void onRequestServiceAck(final long logPosition)\n    {\n        requestedAckPosition = logPosition;\n    }\n\n    void onSessionMessage(\n        final long logPosition,\n        final long clusterSessionId,\n        final long timestamp,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        this.logPosition = logPosition;\n        clusterTime = timestamp;\n        final ClientSession clientSession = sessionByIdMap.get(clusterSessionId);\n\n        service.onSessionMessage(clientSession, timestamp, buffer, offset, length, header);\n    }\n\n    void onTimerEvent(final long logPosition, final long correlationId, final long timestamp)\n    {\n        this.logPosition = logPosition;\n        clusterTime = timestamp;\n        service.onTimerEvent(correlationId, timestamp);\n    }\n\n    void onSessionOpen(\n        final long leadershipTermId,\n        final long logPosition,\n        final long clusterSessionId,\n        final long timestamp,\n        final int responseStreamId,\n        final String responseChannel,\n        final byte[] encodedPrincipal)\n    {\n        this.logPosition = logPosition;\n        clusterTime = timestamp;\n\n        if (sessionByIdMap.containsKey(clusterSessionId))\n        {\n            throw new ClusterException(\"clashing open clusterSessionId=\" + clusterSessionId +\n                \" leadershipTermId=\" + leadershipTermId + \" logPosition=\" + logPosition);\n        }\n\n        final ContainerClientSession session = new ContainerClientSession(\n            clusterSessionId, responseStreamId, responseChannel, encodedPrincipal, this);\n\n        if (Role.LEADER == role && ctx.isRespondingService())\n        {\n            session.connect(aeron);\n        }\n\n        addSession(session);\n        service.onSessionOpen(session, timestamp);\n    }\n\n    void onSessionClose(\n        final long leadershipTermId,\n        final long logPosition,\n        final long clusterSessionId,\n        final long timestamp,\n        final CloseReason closeReason)\n    {\n        this.logPosition = logPosition;\n        clusterTime = timestamp;\n\n        final ContainerClientSession session = sessionByIdMap.remove(clusterSessionId);\n        if (null == session)\n        {\n            ctx.countedErrorHandler().onError(new ClusterEvent(\n                \"unknown session close: clusterSessionId=\" + clusterSessionId + \" closeReason=\" + closeReason +\n                \" leadershipTermId=\" + leadershipTermId + \" logPosition=\" + logPosition));\n        }\n        else\n        {\n            for (int i = 0, size = sessions.size(); i < size; i++)\n            {\n                if (sessions.get(i).id() == clusterSessionId)\n                {\n                    sessions.remove(i);\n                    break;\n                }\n            }\n\n            session.disconnect(ctx.countedErrorHandler());\n            service.onSessionClose(session, timestamp, closeReason);\n        }\n    }\n\n    void onServiceAction(\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final ClusterAction action,\n        final int flags)\n    {\n        this.logPosition = logPosition;\n        clusterTime = timestamp;\n        executeAction(action, logPosition, leadershipTermId, flags);\n    }\n\n    void onNewLeadershipTermEvent(\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final long termBaseLogPosition,\n        final int leaderMemberId,\n        final int logSessionId,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n        if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))\n        {\n            ctx.countedErrorHandler().onError(new ClusterException(\n                \"incompatible version: \" + SemanticVersion.toString(ctx.appVersion()) +\n                \" log=\" + SemanticVersion.toString(appVersion)));\n            throw new AgentTerminationException();\n        }\n\n        sessionMessageHeaderEncoder.leadershipTermId(leadershipTermId);\n        this.logPosition = logPosition;\n        clusterTime = timestamp;\n        this.timeUnit = timeUnit;\n\n        service.onNewLeadershipTermEvent(\n            leadershipTermId,\n            logPosition,\n            timestamp,\n            termBaseLogPosition,\n            leaderMemberId,\n            logSessionId,\n            timeUnit,\n            appVersion);\n    }\n\n    void addSession(\n        final long clusterSessionId,\n        final int responseStreamId,\n        final String responseChannel,\n        final byte[] encodedPrincipal)\n    {\n        final ContainerClientSession session = new ContainerClientSession(\n            clusterSessionId, responseStreamId, responseChannel, encodedPrincipal, this);\n\n        addSession(session);\n    }\n\n    private void addSession(final ContainerClientSession session)\n    {\n        final long clusterSessionId = session.id();\n        sessionByIdMap.put(clusterSessionId, session);\n\n        final int size = sessions.size();\n        int addIndex = size;\n        for (int i = size - 1; i >= 0; i--)\n        {\n            if (sessions.get(i).id() < clusterSessionId)\n            {\n                addIndex = i + 1;\n                break;\n            }\n        }\n\n        if (size == addIndex)\n        {\n            sessions.add(session);\n        }\n        else\n        {\n            sessions.add(addIndex, session);\n        }\n    }\n\n    void handleError(final Throwable ex)\n    {\n        ctx.countedErrorHandler().onError(ex);\n    }\n\n    long offer(\n        final long clusterSessionId,\n        final Publication publication,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        checkForValidInvocation();\n\n        if (Cluster.Role.LEADER != role)\n        {\n            return ClientSession.MOCKED_OFFER;\n        }\n\n        if (null == publication)\n        {\n            return Publication.NOT_CONNECTED;\n        }\n\n        sessionMessageHeaderEncoder\n            .clusterSessionId(clusterSessionId)\n            .timestamp(clusterTime);\n\n        return publication.offer(headerBuffer, 0, SESSION_HEADER_LENGTH, buffer, offset, length, null);\n    }\n\n    long offer(final long clusterSessionId, final Publication publication, final DirectBufferVector[] vectors)\n    {\n        checkForValidInvocation();\n\n        if (Cluster.Role.LEADER != role)\n        {\n            return ClientSession.MOCKED_OFFER;\n        }\n\n        if (null == publication)\n        {\n            return Publication.NOT_CONNECTED;\n        }\n\n        sessionMessageHeaderEncoder\n            .clusterSessionId(clusterSessionId)\n            .timestamp(clusterTime);\n\n        vectors[0] = headerVector;\n\n        return publication.offer(vectors, null);\n    }\n\n    long tryClaim(\n        final long clusterSessionId,\n        final Publication publication,\n        final int length,\n        final BufferClaim bufferClaim)\n    {\n        checkForValidInvocation();\n\n        if (Cluster.Role.LEADER != role)\n        {\n            final int maxPayloadLength = headerBuffer.capacity() - SESSION_HEADER_LENGTH;\n            if (length > maxPayloadLength)\n            {\n                throw new IllegalArgumentException(\n                    \"claim exceeds maxPayloadLength=\" + maxPayloadLength + \", length=\" + length);\n            }\n\n            bufferClaim.wrap(\n                messageBuffer, 0, DataHeaderFlyweight.HEADER_LENGTH + SESSION_HEADER_LENGTH + length);\n            return ClientSession.MOCKED_OFFER;\n        }\n\n        if (null == publication)\n        {\n            return Publication.NOT_CONNECTED;\n        }\n\n        final long offset = publication.tryClaim(SESSION_HEADER_LENGTH + length, bufferClaim);\n        if (offset > 0)\n        {\n            sessionMessageHeaderEncoder\n                .clusterSessionId(clusterSessionId)\n                .timestamp(clusterTime);\n\n            bufferClaim.putBytes(headerBuffer, 0, SESSION_HEADER_LENGTH);\n        }\n\n        return offset;\n    }\n\n    private void role(final Role newRole)\n    {\n        if (newRole != role)\n        {\n            role = newRole;\n            activeLifecycleCallback = LIFECYCLE_CALLBACK_ON_ROLE_CHANGE;\n            try\n            {\n                service.onRoleChange(newRole);\n            }\n            finally\n            {\n                activeLifecycleCallback = LIFECYCLE_CALLBACK_NONE;\n            }\n        }\n    }\n\n    private void recoverState(final CountersReader counters)\n    {\n        final int recoveryCounterId = awaitRecoveryCounter(counters);\n        logPosition = RecoveryState.getLogPosition(counters, recoveryCounterId);\n        clusterTime = RecoveryState.getTimestamp(counters, recoveryCounterId);\n        final long leadershipTermId = RecoveryState.getLeadershipTermId(counters, recoveryCounterId);\n        sessionMessageHeaderEncoder.leadershipTermId(leadershipTermId);\n\n        Exception exception = null;\n        long snapshotRecordingId = NULL_VALUE;\n\n        activeLifecycleCallback = LIFECYCLE_CALLBACK_ON_START;\n        try\n        {\n            if (NULL_VALUE != leadershipTermId)\n            {\n                snapshotRecordingId = RecoveryState.getSnapshotRecordingId(counters, recoveryCounterId, serviceId);\n                loadSnapshot(snapshotRecordingId);\n            }\n            else\n            {\n                service.onStart(this, null);\n            }\n        }\n        catch (final Exception ex)\n        {\n            exception = ex;\n        }\n        finally\n        {\n            activeLifecycleCallback = LIFECYCLE_CALLBACK_NONE;\n        }\n\n        final long id = ackId++;\n        final long relevantId = (null == exception) ? aeron.clientId() : NULL_VALUE;\n        while (!consensusModuleProxy.ack(logPosition, clusterTime, id, relevantId, serviceId))\n        {\n            idle();\n        }\n\n        if (null != exception)\n        {\n            final String message = \"failed to start service=\" + ctx.serviceId() +\n                \" leadershipTermId=\" + leadershipTermId +\n                \" logPosition=\" + logPosition +\n                \" clusterTime=\" + clusterTime +\n                \" snapshotRecordingId=\" + snapshotRecordingId;\n            throw new AgentTerminationException(message, exception);\n        }\n    }\n\n    private int awaitRecoveryCounter(final CountersReader counters)\n    {\n        idleStrategy.reset();\n        int counterId = RecoveryState.findCounterId(counters, ctx.clusterId());\n        while (NULL_COUNTER_ID == counterId)\n        {\n            idle();\n            counterId = RecoveryState.findCounterId(counters, ctx.clusterId());\n        }\n\n        return counterId;\n    }\n\n    private void closeLog()\n    {\n        logPosition = Math.max(logAdapter.image().position(), logPosition);\n        CloseHelper.close(ctx.countedErrorHandler(), logAdapter);\n        disconnectEgress(ctx.countedErrorHandler());\n        role(Role.FOLLOWER);\n    }\n\n    private void disconnectEgress(final CountedErrorHandler errorHandler)\n    {\n        for (int i = 0, size = sessions.size(); i < size; i++)\n        {\n            sessions.get(i).disconnect(errorHandler);\n        }\n    }\n\n    private void joinActiveLog(final ActiveLogEvent activeLog)\n    {\n        if (Role.LEADER != activeLog.role)\n        {\n            disconnectEgress(ctx.countedErrorHandler());\n        }\n\n        this.standbySnapshotFlags = activeLog.isStandby ? CLUSTER_ACTION_FLAGS_STANDBY_SNAPSHOT :\n            CLUSTER_ACTION_FLAGS_DEFAULT;\n\n        final String channel = new ChannelUriStringBuilder(activeLog.channel)\n            .alias(subscriptionAlias)\n            .build();\n\n        Subscription logSubscription = aeron.addSubscription(channel, activeLog.streamId);\n        try\n        {\n            final Image image = awaitImage(activeLog.sessionId, logSubscription);\n            if (image.joinPosition() != logPosition)\n            {\n                throw new ClusterException(\"Cluster log must be contiguous for joining image: \" +\n                    \"expectedPosition=\" + logPosition + \" joinPosition=\" + image.joinPosition());\n            }\n\n            if (activeLog.logPosition != logPosition)\n            {\n                throw new ClusterException(\"Cluster log must be contiguous for active log event: \" +\n                    \"expectedPosition=\" + logPosition + \" eventPosition=\" + activeLog.logPosition);\n            }\n\n            logAdapter.image(image);\n            logAdapter.maxLogPosition(activeLog.maxLogPosition);\n            logSubscription = null;\n\n            final long id = ackId++;\n            while (!consensusModuleProxy.ack(activeLog.logPosition, clusterTime, id, NULL_VALUE, serviceId))\n            {\n                idle();\n            }\n        }\n        finally\n        {\n            CloseHelper.quietClose(logSubscription);\n        }\n\n        memberId = activeLog.memberId;\n        markFile.memberId(memberId);\n\n        if (Role.LEADER == activeLog.role)\n        {\n            for (int i = 0, size = sessions.size(); i < size; i++)\n            {\n                final ContainerClientSession session = sessions.get(i);\n\n                if (ctx.isRespondingService() && !activeLog.isStartup)\n                {\n                    session.connect(aeron);\n                }\n\n                session.resetClosing();\n            }\n        }\n\n        role(activeLog.role);\n    }\n\n    private Image awaitImage(final int sessionId, final Subscription subscription)\n    {\n        idleStrategy.reset();\n        Image image;\n        while ((image = subscription.imageBySessionId(sessionId)) == null)\n        {\n            idle();\n        }\n\n        return image;\n    }\n\n    private ReadableCounter awaitCommitPositionCounter(final CountersReader counters, final int clusterId)\n    {\n        idleStrategy.reset();\n        int counterId = ClusterCounters.find(counters, COMMIT_POSITION_TYPE_ID, clusterId);\n        while (NULL_COUNTER_ID == counterId)\n        {\n            idle();\n            counterId = ClusterCounters.find(counters, COMMIT_POSITION_TYPE_ID, clusterId);\n        }\n\n        return new ReadableCounter(counters, counters.getCounterRegistrationId(counterId), counterId);\n    }\n\n    private void loadSnapshot(final long recordingId)\n    {\n        try (AeronArchive archive = AeronArchive.connect(ctx.archiveContext().clone()))\n        {\n            final String channel = ctx.replayChannel();\n            final int streamId = ctx.replayStreamId();\n            final int sessionId = (int)archive.startReplay(recordingId, 0, NULL_VALUE, channel, streamId);\n\n            final String replaySessionChannel = ChannelUri.addSessionId(channel, sessionId);\n            try (Subscription subscription = aeron.addSubscription(replaySessionChannel, streamId))\n            {\n                final Image image = awaitImage(sessionId, subscription);\n                loadState(image, archive);\n                service.onStart(this, image);\n            }\n        }\n    }\n\n    private void loadState(final Image image, final AeronArchive archive)\n    {\n        final ServiceSnapshotLoader snapshotLoader = new ServiceSnapshotLoader(image, this);\n        while (true)\n        {\n            final int fragments = snapshotLoader.poll();\n            if (snapshotLoader.isDone())\n            {\n                break;\n            }\n\n            if (0 == fragments)\n            {\n                archive.checkForErrorResponse();\n                if (image.isClosed())\n                {\n                    throw new ClusterException(\"snapshot ended unexpectedly: \" + image);\n                }\n            }\n\n            idle(fragments);\n        }\n\n        final int appVersion = snapshotLoader.appVersion();\n        if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))\n        {\n            throw new ClusterException(\n                \"incompatible app version: \" + SemanticVersion.toString(ctx.appVersion()) +\n                \" snapshot=\" + SemanticVersion.toString(appVersion));\n        }\n\n        timeUnit = snapshotLoader.timeUnit();\n    }\n\n    private long onTakeSnapshot(final long logPosition, final long leadershipTermId)\n    {\n        try (AeronArchive archive = AeronArchive.connect(ctx.archiveContext().clone());\n            ExclusivePublication publication = aeron.addExclusivePublication(\n                ctx.snapshotChannel(), ctx.snapshotStreamId()))\n        {\n            final String channel = ChannelUri.addSessionId(ctx.snapshotChannel(), publication.sessionId());\n            archive.startRecording(channel, ctx.snapshotStreamId(), LOCAL, true);\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = awaitRecordingCounter(publication.sessionId(), counters, archive);\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            snapshotState(publication, logPosition, leadershipTermId);\n            checkForClockTick(nanoClock.nanoTime());\n            archive.checkForErrorResponse();\n\n            service.onTakeSnapshot(publication);\n\n            awaitRecordingComplete(recordingId, publication.position(), counters, counterId, archive);\n\n            return recordingId;\n        }\n        catch (final ArchiveException ex)\n        {\n            if (ex.errorCode() == ArchiveException.STORAGE_SPACE)\n            {\n                throw new AgentTerminationException(ex);\n            }\n\n            throw ex;\n        }\n    }\n\n    private void awaitRecordingComplete(\n        final long recordingId,\n        final long position,\n        final CountersReader counters,\n        final int counterId,\n        final AeronArchive archive)\n    {\n        idleStrategy.reset();\n        while (counters.getCounterValue(counterId) < position)\n        {\n            idle();\n            archive.checkForErrorResponse();\n\n            if (!RecordingPos.isActive(counters, counterId, recordingId))\n            {\n                throw new ClusterException(\"recording stopped unexpectedly: \" + recordingId);\n            }\n        }\n    }\n\n    private void snapshotState(\n        final ExclusivePublication publication, final long logPosition, final long leadershipTermId)\n    {\n        final ServiceSnapshotTaker snapshotTaker = new ServiceSnapshotTaker(\n            publication, idleStrategy, aeronAgentInvoker);\n\n        snapshotTaker.markBegin(SNAPSHOT_TYPE_ID, logPosition, leadershipTermId, 0, timeUnit, ctx.appVersion());\n\n        for (int i = 0, size = sessions.size(); i < size; i++)\n        {\n            snapshotTaker.snapshotSession(sessions.get(i));\n        }\n\n        snapshotTaker.markEnd(SNAPSHOT_TYPE_ID, logPosition, leadershipTermId, 0, timeUnit, ctx.appVersion());\n    }\n\n    private void executeAction(\n        final ClusterAction action,\n        final long logPosition,\n        final long leadershipTermId,\n        final int flags)\n    {\n        if (ClusterAction.SNAPSHOT == action && shouldSnapshot(flags))\n        {\n            long recordingId = NULL_VALUE;\n            Exception exception = null;\n            snapshotDurationTracker.onSnapshotBegin(nanoClock.nanoTime());\n            try\n            {\n                recordingId = onTakeSnapshot(logPosition, leadershipTermId);\n            }\n            catch (final Exception ex)\n            {\n                exception = ex;\n            }\n            finally\n            {\n                snapshotDurationTracker.onSnapshotEnd(nanoClock.nanoTime());\n            }\n\n            final long id = ackId++;\n            while (!consensusModuleProxy.ack(logPosition, clusterTime, id, recordingId, serviceId))\n            {\n                idle();\n            }\n\n            if (null != exception)\n            {\n                LangUtil.rethrowUnchecked(exception);\n            }\n        }\n    }\n\n    private boolean shouldSnapshot(final int flags)\n    {\n        return CLUSTER_ACTION_FLAGS_DEFAULT == flags || 0 != (flags & standbySnapshotFlags);\n    }\n\n    private int awaitRecordingCounter(final int sessionId, final CountersReader counters, final AeronArchive archive)\n    {\n        idleStrategy.reset();\n        final long archiveId = archive.archiveId();\n        int counterId = RecordingPos.findCounterIdBySession(counters, sessionId, archiveId);\n        while (NULL_COUNTER_ID == counterId)\n        {\n            idle();\n            archive.checkForErrorResponse();\n            counterId = RecordingPos.findCounterIdBySession(counters, sessionId, archiveId);\n        }\n\n        return counterId;\n    }\n\n    private boolean checkForClockTick(final long nowNs)\n    {\n        if (isAbort || aeron.isClosed())\n        {\n            isAbort = true;\n            throw new AgentTerminationException(\"unexpected Aeron close\");\n        }\n\n        if (nowNs - lastSlowTickNs > ONE_MILLISECOND_NS)\n        {\n            lastSlowTickNs = nowNs;\n\n            if (null != aeronAgentInvoker)\n            {\n                aeronAgentInvoker.invoke();\n                if (isAbort || aeron.isClosed())\n                {\n                    isAbort = true;\n                    throw new AgentTerminationException(\"unexpected Aeron close\");\n                }\n            }\n\n            if (null != commitPosition && commitPosition.isClosed())\n            {\n                ctx.errorLog().record(new AeronEvent(\n                    \"commit-pos counter unexpectedly closed, terminating\", AeronException.Category.WARN));\n\n                throw new ClusterTerminationException(true);\n            }\n\n            final long nowMs = epochClock.time();\n            if (nowMs >= markFileUpdateDeadlineMs)\n            {\n                markFileUpdateDeadlineMs = nowMs + MARK_FILE_UPDATE_INTERVAL_MS;\n                markFile.updateActivityTimestamp(nowMs);\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n\n    private int pollServiceAdapter()\n    {\n        int workCount = 0;\n\n        workCount += serviceAdapter.poll();\n\n        if (null != activeLogEvent && null == logAdapter.image())\n        {\n            final ActiveLogEvent event = activeLogEvent;\n            activeLogEvent = null;\n            joinActiveLog(event);\n        }\n\n        if (NULL_POSITION != terminationPosition && logPosition >= terminationPosition)\n        {\n            if (logPosition > terminationPosition)\n            {\n                ctx.countedErrorHandler().onError(new ClusterEvent(\n                    \"service terminate: logPosition=\" + logPosition + \" > terminationPosition=\" + terminationPosition));\n            }\n\n            terminate(logPosition == terminationPosition);\n        }\n\n        if (NULL_POSITION != requestedAckPosition && logPosition >= requestedAckPosition)\n        {\n            if (logPosition > requestedAckPosition)\n            {\n                ctx.countedErrorHandler().onError(new ClusterEvent(\n                    \"invalid ack request: logPosition=\" + logPosition +\n                    \" > requestedAckPosition=\" + requestedAckPosition));\n            }\n\n            final long id = ackId++;\n            while (!consensusModuleProxy.ack(logPosition, clusterTime, id, NULL_VALUE, serviceId))\n            {\n                idle();\n            }\n            requestedAckPosition = NULL_POSITION;\n        }\n\n        return workCount;\n    }\n\n    private void terminate(final boolean isTerminationExpected)\n    {\n        isServiceActive = false;\n        activeLifecycleCallback = LIFECYCLE_CALLBACK_ON_TERMINATE;\n        try\n        {\n            service.onTerminate(this);\n        }\n        catch (final Exception ex)\n        {\n            ctx.countedErrorHandler().onError(ex);\n        }\n        finally\n        {\n            activeLifecycleCallback = LIFECYCLE_CALLBACK_NONE;\n        }\n\n        try\n        {\n            int attempts = 5;\n            final long id = ackId++;\n            while (!consensusModuleProxy.ack(logPosition, clusterTime, id, NULL_VALUE, serviceId))\n            {\n                if (0 == --attempts)\n                {\n                    break;\n                }\n                idle();\n            }\n        }\n        catch (final Exception ex)\n        {\n            ctx.countedErrorHandler().onError(ex);\n        }\n\n        terminationPosition = NULL_VALUE;\n        throw new ClusterTerminationException(isTerminationExpected);\n    }\n\n    private void checkForValidInvocation()\n    {\n        if (LIFECYCLE_CALLBACK_NONE != activeLifecycleCallback)\n        {\n            throw new ClusterException(\n                \"sending messages or scheduling timers is not allowed from \" + lifecycleName(activeLifecycleCallback));\n        }\n    }\n\n    private void abort()\n    {\n        isAbort = true;\n\n        try\n        {\n            if (!ctx.abortLatch().await(AgentRunner.RETRY_CLOSE_TIMEOUT_MS * 3L, TimeUnit.MILLISECONDS))\n            {\n                ctx.countedErrorHandler().onError(\n                    new TimeoutException(\"awaiting abort latch\", AeronException.Category.WARN));\n            }\n        }\n        catch (final InterruptedException ignore)\n        {\n            Thread.currentThread().interrupt();\n        }\n    }\n\n    private void counterUnavailable(final CountersReader countersReader, final long registrationId, final int counterId)\n    {\n        final ReadableCounter commitPosition = this.commitPosition;\n        if (null != commitPosition &&\n            commitPosition.counterId() == counterId &&\n            commitPosition.registrationId() == registrationId)\n        {\n            commitPosition.close();\n        }\n    }\n\n    private int invokeBackgroundWork(final long nowNs)\n    {\n        activeLifecycleCallback = LIFECYCLE_CALLBACK_DO_BACKGROUND_WORK;\n        try\n        {\n            return service.doBackgroundWork(nowNs);\n        }\n        finally\n        {\n            activeLifecycleCallback = LIFECYCLE_CALLBACK_NONE;\n        }\n    }\n\n    private void runTerminationHook()\n    {\n        try\n        {\n            ctx.terminationHook().run();\n        }\n        catch (final Exception ex)\n        {\n            ctx.countedErrorHandler().onError(ex);\n        }\n    }\n\n    private void logAck(\n        final int memberId,\n        final long logPosition,\n        final long clusterTime,\n        final long id,\n        final long recordingId,\n        final int serviceId)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ClusteredServiceContainer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.CommonContext;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.AppVersionValidator;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.mark.ClusterComponentType;\nimport io.aeron.cluster.codecs.mark.MarkFileHeaderEncoder;\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\nimport io.aeron.driver.DutyCycleTracker;\nimport io.aeron.driver.status.DutyCycleStallTracker;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.version.Versioned;\nimport org.agrona.CloseHelper;\nimport org.agrona.DelegatingErrorHandler;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.MarkFile;\nimport org.agrona.SemanticVersion;\nimport org.agrona.Strings;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.StatusIndicator;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static io.aeron.ChannelUri.addAliasIfAbsent;\nimport static io.aeron.CommonContext.driverFilePageSize;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.LIVENESS_TIMEOUT_MS;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.MAX_SERVICE_COUNT;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.SERVICE_NAME_PROP_NAME;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.agrona.SystemUtil.getDurationInNanos;\nimport static org.agrona.SystemUtil.getSizeAsInt;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Container for a service in the cluster managed by the Consensus Module. This is where business logic resides and\n * loaded via {@link ClusteredServiceContainer.Configuration#SERVICE_CLASS_NAME_PROP_NAME} or\n * {@link ClusteredServiceContainer.Context#clusteredService(ClusteredService)}.\n */\n@Versioned\npublic final class ClusteredServiceContainer implements AutoCloseable\n{\n    /**\n     * Launch the clustered service container and await a shutdown signal.\n     *\n     * @param args command line argument which is a list for properties files as URLs or filenames.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            ClusteredServiceContainer ignore = launch(new Context().terminationHook(barrier::signalAll)))\n        {\n            barrier.await();\n\n            System.out.println(\"Shutdown ClusteredServiceContainer...\");\n        }\n    }\n\n    private final Context ctx;\n    private final AgentRunner serviceAgentRunner;\n\n    private ClusteredServiceContainer(final Context ctx)\n    {\n        this.ctx = ctx;\n\n        try\n        {\n            ctx.conclude();\n        }\n        catch (final Exception ex)\n        {\n            final ClusterMarkFile markFile = ctx.markFile;\n            if (null != markFile)\n            {\n                markFile.signalFailedStart();\n            }\n\n            ctx.close();\n            throw ex;\n        }\n\n        final ClusteredServiceAgent agent = new ClusteredServiceAgent(ctx);\n        serviceAgentRunner = new AgentRunner(ctx.idleStrategy(), ctx.errorHandler(), ctx.errorCounter(), agent);\n    }\n\n    /**\n     * Launch an ClusteredServiceContainer using a default configuration.\n     *\n     * @return a new instance of a ClusteredServiceContainer.\n     */\n    public static ClusteredServiceContainer launch()\n    {\n        return launch(new Context());\n    }\n\n    /**\n     * Launch a ClusteredServiceContainer by providing a configuration context.\n     *\n     * @param ctx for the configuration parameters.\n     * @return a new instance of a ClusteredServiceContainer.\n     */\n    public static ClusteredServiceContainer launch(final Context ctx)\n    {\n        final ClusteredServiceContainer clusteredServiceContainer = new ClusteredServiceContainer(ctx);\n        AgentRunner.startOnThread(clusteredServiceContainer.serviceAgentRunner, ctx.threadFactory());\n\n        return clusteredServiceContainer;\n    }\n\n    /**\n     * Get the {@link Context} that is used by this {@link ClusteredServiceContainer}.\n     *\n     * @return the {@link Context} that is used by this {@link ClusteredServiceContainer}.\n     */\n    public Context context()\n    {\n        return ctx;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(serviceAgentRunner);\n    }\n\n    /**\n     * Configuration options for the consensus module and service container within a cluster.\n     */\n    @Config(existsInC = false)\n    public static final class Configuration\n    {\n        private Configuration()\n        {\n        }\n\n        /**\n         * Type of snapshot for this service.\n         */\n        public static final long SNAPSHOT_TYPE_ID = 2;\n\n        /**\n         * Update interval for cluster mark file in nanoseconds.\n         */\n        public static final long MARK_FILE_UPDATE_INTERVAL_NS = TimeUnit.SECONDS.toNanos(1);\n\n        /**\n         * Timeout in milliseconds to detect liveness.\n         */\n        public static final long LIVENESS_TIMEOUT_MS = 10 * TimeUnit.NANOSECONDS.toMillis(MARK_FILE_UPDATE_INTERVAL_NS);\n\n        /**\n         * Property name for the identity of the cluster instance.\n         */\n        @Config\n        public static final String CLUSTER_ID_PROP_NAME = \"aeron.cluster.id\";\n\n        /**\n         * Default identity for a clustered instance.\n         */\n        @Config\n        public static final int CLUSTER_ID_DEFAULT = 0;\n\n        /**\n         * Identity for a clustered service. Services should be numbered from 0 and be contiguous.\n         */\n        @Config\n        public static final String SERVICE_ID_PROP_NAME = \"aeron.cluster.service.id\";\n\n        /**\n         * Default identity for a clustered service.\n         */\n        @Config\n        public static final int SERVICE_ID_DEFAULT = 0;\n\n        /**\n         * The max number of services supported by the cluster instance.\n         */\n        public static final int MAX_SERVICE_COUNT = 10;\n\n        /**\n         * Name for a clustered service to be the role of the {@link Agent}.\n         */\n        @Config\n        public static final String SERVICE_NAME_PROP_NAME = \"aeron.cluster.service.name\";\n\n        /**\n         * Name for a clustered service to be the role of the {@link Agent}.\n         */\n        @Config\n        public static final String SERVICE_NAME_DEFAULT = \"clustered-service\";\n\n        /**\n         * Class name for dynamically loading a {@link ClusteredService}. This is used if\n         * {@link Context#clusteredService()} is not set.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String SERVICE_CLASS_NAME_PROP_NAME = \"aeron.cluster.service.class.name\";\n\n        /**\n         * Channel to be used for log or snapshot replay on startup.\n         */\n        @Config\n        public static final String REPLAY_CHANNEL_PROP_NAME = \"aeron.cluster.replay.channel\";\n\n        /**\n         * Default channel to be used for log or snapshot replay on startup.\n         */\n        @Config\n        public static final String REPLAY_CHANNEL_DEFAULT = CommonContext.IPC_CHANNEL;\n\n        /**\n         * Stream id within a channel for the clustered log or snapshot replay.\n         */\n        @Config\n        public static final String REPLAY_STREAM_ID_PROP_NAME = \"aeron.cluster.replay.stream.id\";\n\n        /**\n         * Default stream id for the log or snapshot replay within a channel.\n         */\n        @Config\n        public static final int REPLAY_STREAM_ID_DEFAULT = 103;\n\n        /**\n         * Channel for control communications between the local consensus module and services.\n         */\n        @Config\n        public static final String CONTROL_CHANNEL_PROP_NAME = \"aeron.cluster.control.channel\";\n\n        /**\n         * Default channel for communications between the local consensus module and services. This should be IPC.\n         */\n        @Config\n        public static final String CONTROL_CHANNEL_DEFAULT = \"aeron:ipc?term-length=128k\";\n\n        /**\n         * Stream id within the control channel for communications from the consensus module to the services.\n         */\n        @Config\n        public static final String SERVICE_STREAM_ID_PROP_NAME = \"aeron.cluster.service.stream.id\";\n\n        /**\n         * Default stream id within the control channel for communications from the consensus module.\n         */\n        @Config\n        public static final int SERVICE_STREAM_ID_DEFAULT = 104;\n\n        /**\n         * Stream id within the control channel for communications from the services to the consensus module.\n         */\n        @Config\n        public static final String CONSENSUS_MODULE_STREAM_ID_PROP_NAME = \"aeron.cluster.consensus.module.stream.id\";\n\n        /**\n         * Default stream id within a channel for communications from the services to the consensus module.\n         */\n        @Config\n        public static final int CONSENSUS_MODULE_STREAM_ID_DEFAULT = 105;\n\n        /**\n         * Channel to be used for archiving snapshots.\n         */\n        @Config\n        public static final String SNAPSHOT_CHANNEL_PROP_NAME = \"aeron.cluster.snapshot.channel\";\n\n        /**\n         * Default channel to be used for archiving snapshots.\n         */\n        @Config\n        public static final String SNAPSHOT_CHANNEL_DEFAULT = \"aeron:ipc?alias=snapshot\";\n\n        /**\n         * Stream id within a channel for archiving snapshots.\n         */\n        @Config\n        public static final String SNAPSHOT_STREAM_ID_PROP_NAME = \"aeron.cluster.snapshot.stream.id\";\n\n        /**\n         * Default stream id for the archived snapshots within a channel.\n         */\n        @Config\n        public static final int SNAPSHOT_STREAM_ID_DEFAULT = 106;\n\n        /**\n         * Directory to use for the aeron cluster.\n         */\n        @Config\n        public static final String CLUSTER_DIR_PROP_NAME = \"aeron.cluster.dir\";\n\n        /**\n         * Default directory to use for the aeron cluster.\n         */\n        @Config\n        public static final String CLUSTER_DIR_DEFAULT = \"aeron-cluster\";\n\n        /**\n         * Directory to use for the aeron cluster services, will default to\n         * {@link io.aeron.cluster.ConsensusModule.Context#clusterDir()} if not specified.\n         */\n        @Config(defaultType = DefaultType.STRING)\n        public static final String CLUSTER_SERVICES_DIR_PROP_NAME = \"aeron.cluster.services.dir\";\n\n        /**\n         * Directory to use for the Cluster component's mark file.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String MARK_FILE_DIR_PROP_NAME = \"aeron.cluster.mark.file.dir\";\n\n        /**\n         * Length in bytes of the error buffer for the cluster container.\n         */\n        @Config(id = \"SERVICE_ERROR_BUFFER_LENGTH\")\n        public static final String ERROR_BUFFER_LENGTH_PROP_NAME = \"aeron.cluster.service.error.buffer.length\";\n\n        /**\n         * Default length in bytes of the error buffer for the cluster container.\n         */\n        @Config(id = \"SERVICE_ERROR_BUFFER_LENGTH\")\n        public static final int ERROR_BUFFER_LENGTH_DEFAULT = 1024 * 1024;\n\n        /**\n         * Is this a responding service to client requests property.\n         */\n        @Config\n        public static final String RESPONDER_SERVICE_PROP_NAME = \"aeron.cluster.service.responder\";\n\n        /**\n         * Default to true that this a responding service to client requests.\n         */\n        @Config\n        public static final boolean RESPONDER_SERVICE_DEFAULT = true;\n\n        /**\n         * Fragment limit to use when polling the log.\n         */\n        @Config\n        public static final String LOG_FRAGMENT_LIMIT_PROP_NAME = \"aeron.cluster.log.fragment.limit\";\n\n        /**\n         * Default fragment limit for polling log.\n         */\n        @Config\n        public static final int LOG_FRAGMENT_LIMIT_DEFAULT = 50;\n\n        /**\n         * Delegating {@link ErrorHandler} which will be first in the chain before delegating to the\n         * {@link Context#errorHandler()}.\n         */\n        @Config(defaultType = DefaultType.STRING, defaultString = \"\")\n        public static final String DELEGATING_ERROR_HANDLER_PROP_NAME =\n            \"aeron.cluster.service.delegating.error.handler\";\n\n        /**\n         * Property name for threshold value for the container work cycle threshold to track\n         * for being exceeded.\n         */\n        @Config(id = \"SERVICE_CYCLE_THRESHOLD\")\n        public static final String CYCLE_THRESHOLD_PROP_NAME = \"aeron.cluster.service.cycle.threshold\";\n\n        /**\n         * Default threshold value for the container work cycle threshold to track for being exceeded.\n         */\n        @Config(\n            id = \"SERVICE_CYCLE_THRESHOLD\",\n            defaultType = DefaultType.LONG,\n            defaultLong = 100_000_000L)\n        public static final long CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n        /**\n         * Property name for threshold value, which is used for tracking snapshot duration breaches.\n         *\n         * @since 1.44.0\n         */\n        @Config\n        public static final String SNAPSHOT_DURATION_THRESHOLD_PROP_NAME = \"aeron.cluster.service.snapshot.threshold\";\n\n        /**\n         * Default threshold value, which is used for tracking snapshot duration breaches.\n         *\n         * @since 1.44.0\n         */\n        @Config(defaultType = DefaultType.LONG, defaultLong = 1000L * 1000 * 1000)\n        public static final long SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(1000);\n\n        /**\n         * Counter type id for the cluster node role.\n         */\n        public static final int CLUSTER_NODE_ROLE_TYPE_ID = AeronCounters.CLUSTER_NODE_ROLE_TYPE_ID;\n\n        /**\n         * Counter type id of the commit position.\n         */\n        public static final int COMMIT_POSITION_TYPE_ID = AeronCounters.CLUSTER_COMMIT_POSITION_TYPE_ID;\n\n        /**\n         * Counter type id for the clustered service error count.\n         */\n        public static final int CLUSTERED_SERVICE_ERROR_COUNT_TYPE_ID =\n            AeronCounters.CLUSTER_CLUSTERED_SERVICE_ERROR_COUNT_TYPE_ID;\n\n        /**\n         * The value {@link #CLUSTER_ID_DEFAULT} or system property {@link #CLUSTER_ID_PROP_NAME} if set.\n         *\n         * @return {@link #CLUSTER_ID_DEFAULT} or system property {@link #CLUSTER_ID_PROP_NAME} if set.\n         */\n        public static int clusterId()\n        {\n            return Integer.getInteger(CLUSTER_ID_PROP_NAME, CLUSTER_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #SERVICE_ID_DEFAULT} or system property {@link #SERVICE_ID_PROP_NAME} if set.\n         *\n         * @return {@link #SERVICE_ID_DEFAULT} or system property {@link #SERVICE_ID_PROP_NAME} if set.\n         */\n        public static int serviceId()\n        {\n            return Integer.getInteger(SERVICE_ID_PROP_NAME, SERVICE_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #SERVICE_NAME_DEFAULT} or system property {@link #SERVICE_NAME_PROP_NAME} if set.\n         *\n         * @return {@link #SERVICE_NAME_DEFAULT} or system property {@link #SERVICE_NAME_PROP_NAME} if set.\n         */\n        public static String serviceName()\n        {\n            return System.getProperty(SERVICE_NAME_PROP_NAME, SERVICE_NAME_DEFAULT);\n        }\n\n        /**\n         * The value {@link #REPLAY_CHANNEL_DEFAULT} or system property {@link #REPLAY_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #REPLAY_CHANNEL_DEFAULT} or system property {@link #REPLAY_CHANNEL_PROP_NAME} if set.\n         */\n        public static String replayChannel()\n        {\n            return System.getProperty(REPLAY_CHANNEL_PROP_NAME, REPLAY_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #REPLAY_STREAM_ID_DEFAULT} or system property {@link #REPLAY_STREAM_ID_PROP_NAME}\n         * if set.\n         *\n         * @return {@link #REPLAY_STREAM_ID_DEFAULT} or system property {@link #REPLAY_STREAM_ID_PROP_NAME}\n         * if set.\n         */\n        public static int replayStreamId()\n        {\n            return Integer.getInteger(REPLAY_STREAM_ID_PROP_NAME, REPLAY_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #CONTROL_CHANNEL_DEFAULT} or system property\n         * {@link #CONTROL_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #CONTROL_CHANNEL_DEFAULT} or system property\n         * {@link #CONTROL_CHANNEL_PROP_NAME} if set.\n         */\n        public static String controlChannel()\n        {\n            return System.getProperty(CONTROL_CHANNEL_PROP_NAME, CONTROL_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #CONSENSUS_MODULE_STREAM_ID_DEFAULT} or system property\n         * {@link #CONSENSUS_MODULE_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #CONSENSUS_MODULE_STREAM_ID_DEFAULT} or system property\n         * {@link #CONSENSUS_MODULE_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int consensusModuleStreamId()\n        {\n            return Integer.getInteger(CONSENSUS_MODULE_STREAM_ID_PROP_NAME, CONSENSUS_MODULE_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #SERVICE_STREAM_ID_DEFAULT} or system property\n         * {@link #SERVICE_STREAM_ID_PROP_NAME} if set.\n         *\n         * @return {@link #SERVICE_STREAM_ID_DEFAULT} or system property\n         * {@link #SERVICE_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int serviceStreamId()\n        {\n            return Integer.getInteger(SERVICE_STREAM_ID_PROP_NAME, SERVICE_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * The value {@link #SNAPSHOT_CHANNEL_DEFAULT} or system property {@link #SNAPSHOT_CHANNEL_PROP_NAME} if set.\n         *\n         * @return {@link #SNAPSHOT_CHANNEL_DEFAULT} or system property {@link #SNAPSHOT_CHANNEL_PROP_NAME} if set.\n         */\n        public static String snapshotChannel()\n        {\n            return System.getProperty(SNAPSHOT_CHANNEL_PROP_NAME, SNAPSHOT_CHANNEL_DEFAULT);\n        }\n\n        /**\n         * The value {@link #SNAPSHOT_STREAM_ID_DEFAULT} or system property {@link #SNAPSHOT_STREAM_ID_PROP_NAME}\n         * if set.\n         *\n         * @return {@link #SNAPSHOT_STREAM_ID_DEFAULT} or system property {@link #SNAPSHOT_STREAM_ID_PROP_NAME} if set.\n         */\n        public static int snapshotStreamId()\n        {\n            return Integer.getInteger(SNAPSHOT_STREAM_ID_PROP_NAME, SNAPSHOT_STREAM_ID_DEFAULT);\n        }\n\n        /**\n         * Default {@link IdleStrategy} to be employed for cluster agents.\n         */\n        @Config(id = \"CLUSTER_IDLE_STRATEGY\")\n        public static final String DEFAULT_IDLE_STRATEGY = \"org.agrona.concurrent.BackoffIdleStrategy\";\n\n        /**\n         * {@link IdleStrategy} to be employed for cluster agents.\n         */\n        @Config\n        public static final String CLUSTER_IDLE_STRATEGY_PROP_NAME = \"aeron.cluster.idle.strategy\";\n\n        /**\n         * Create a supplier of {@link IdleStrategy}s that will use the system property.\n         *\n         * @param controllableStatus if a {@link org.agrona.concurrent.ControllableIdleStrategy} is required.\n         * @return the new idle strategy\n         */\n        public static Supplier<IdleStrategy> idleStrategySupplier(final StatusIndicator controllableStatus)\n        {\n            return () ->\n            {\n                final String name = System.getProperty(CLUSTER_IDLE_STRATEGY_PROP_NAME, DEFAULT_IDLE_STRATEGY);\n                return io.aeron.driver.Configuration.agentIdleStrategy(name, controllableStatus);\n            };\n        }\n\n        /**\n         * The value {@link #CLUSTER_DIR_DEFAULT} or system property {@link #CLUSTER_DIR_PROP_NAME} if set.\n         *\n         * @return {@link #CLUSTER_DIR_DEFAULT} or system property {@link #CLUSTER_DIR_PROP_NAME} if set.\n         */\n        public static String clusterDirName()\n        {\n            return System.getProperty(CLUSTER_DIR_PROP_NAME, CLUSTER_DIR_DEFAULT);\n        }\n\n        /**\n         * The value of system property {@link #CLUSTER_DIR_PROP_NAME} if set or null.\n         *\n         * @return {@link #CLUSTER_DIR_PROP_NAME} if set or null.\n         */\n        public static String clusterServicesDirName()\n        {\n            return System.getProperty(CLUSTER_SERVICES_DIR_PROP_NAME);\n        }\n\n        /**\n         * Size in bytes of the error buffer in the mark file.\n         *\n         * @return length of error buffer in bytes.\n         * @see #ERROR_BUFFER_LENGTH_PROP_NAME\n         */\n        public static int errorBufferLength()\n        {\n            return getSizeAsInt(ERROR_BUFFER_LENGTH_PROP_NAME, ERROR_BUFFER_LENGTH_DEFAULT);\n        }\n\n        /**\n         * The value {@link #RESPONDER_SERVICE_DEFAULT} or system property {@link #RESPONDER_SERVICE_PROP_NAME} if set.\n         *\n         * @return {@link #RESPONDER_SERVICE_DEFAULT} or system property {@link #RESPONDER_SERVICE_PROP_NAME} if set.\n         */\n        public static boolean isRespondingService()\n        {\n            final String property = System.getProperty(RESPONDER_SERVICE_PROP_NAME);\n            if (null == property)\n            {\n                return RESPONDER_SERVICE_DEFAULT;\n            }\n\n            return \"true\".equals(property);\n        }\n\n        /**\n         * The value {@link #LOG_FRAGMENT_LIMIT_DEFAULT} or system property\n         * {@link #LOG_FRAGMENT_LIMIT_PROP_NAME} if set.\n         *\n         * @return {@link #LOG_FRAGMENT_LIMIT_DEFAULT} or system property\n         * {@link #LOG_FRAGMENT_LIMIT_PROP_NAME} if set.\n         */\n        public static int logFragmentLimit()\n        {\n            return Integer.getInteger(LOG_FRAGMENT_LIMIT_PROP_NAME, LOG_FRAGMENT_LIMIT_DEFAULT);\n        }\n\n        /**\n         * Get threshold value for the container work cycle threshold to track for being exceeded.\n         *\n         * @return threshold value in nanoseconds.\n         */\n        public static long cycleThresholdNs()\n        {\n            return getDurationInNanos(CYCLE_THRESHOLD_PROP_NAME, CYCLE_THRESHOLD_DEFAULT_NS);\n        }\n\n        /**\n         * Get threshold value, which is used for monitoring snapshot duration breaches of its predefined\n         * threshold.\n         *\n         * @return threshold value in nanoseconds.\n         */\n        public static long snapshotDurationThresholdNs()\n        {\n            return getDurationInNanos(SNAPSHOT_DURATION_THRESHOLD_PROP_NAME, SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS);\n        }\n\n        /**\n         * Create a new {@link ClusteredService} based on the configured {@link #SERVICE_CLASS_NAME_PROP_NAME}.\n         *\n         * @return a new {@link ClusteredService} based on the configured {@link #SERVICE_CLASS_NAME_PROP_NAME}.\n         */\n        public static ClusteredService newClusteredService()\n        {\n            final String className = System.getProperty(Configuration.SERVICE_CLASS_NAME_PROP_NAME);\n            if (null == className)\n            {\n                throw new ClusterException(\"either a instance or class name for the service must be provided\");\n            }\n\n            try\n            {\n                return (ClusteredService)Class.forName(className).getConstructor().newInstance();\n            }\n            catch (final Exception ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n                return null;\n            }\n        }\n\n        /**\n         * Create a new {@link DelegatingErrorHandler} defined by {@link #DELEGATING_ERROR_HANDLER_PROP_NAME}.\n         *\n         * @return a new {@link DelegatingErrorHandler} defined by {@link #DELEGATING_ERROR_HANDLER_PROP_NAME} or\n         * null if property not set.\n         */\n        public static DelegatingErrorHandler newDelegatingErrorHandler()\n        {\n            final String className = System.getProperty(Configuration.DELEGATING_ERROR_HANDLER_PROP_NAME);\n            if (null != className)\n            {\n                try\n                {\n                    return (DelegatingErrorHandler)Class.forName(className).getConstructor().newInstance();\n                }\n                catch (final Exception ex)\n                {\n                    LangUtil.rethrowUnchecked(ex);\n                }\n            }\n\n            return null;\n        }\n\n        /**\n         * Get the alternative directory to be used for storing the Cluster component's mark file.\n         *\n         * @return the directory to be used for storing the archive mark file.\n         */\n        public static String markFileDir()\n        {\n            return System.getProperty(MARK_FILE_DIR_PROP_NAME);\n        }\n    }\n\n    /**\n     * The context will be owned by {@link ClusteredServiceAgent} after a successful\n     * {@link ClusteredServiceContainer#launch(Context)} and closed via {@link ClusteredServiceContainer#close()}.\n     */\n    public static final class Context implements Cloneable\n    {\n        private static final VarHandle IS_CONCLUDED_VH;\n\n        static\n        {\n            try\n            {\n                IS_CONCLUDED_VH = MethodHandles.lookup().findVarHandle(Context.class, \"isConcluded\", boolean.class);\n            }\n            catch (final ReflectiveOperationException ex)\n            {\n                throw new ExceptionInInitializerError(ex);\n            }\n        }\n\n        private volatile boolean isConcluded;\n        private int appVersion = SemanticVersion.compose(0, 0, 1);\n        private int clusterId = Configuration.clusterId();\n        private int serviceId = Configuration.serviceId();\n        private String serviceName = System.getProperty(SERVICE_NAME_PROP_NAME);\n        private String replayChannel = Configuration.replayChannel();\n        private int replayStreamId = Configuration.replayStreamId();\n        private String controlChannel = Configuration.controlChannel();\n        private int consensusModuleStreamId = Configuration.consensusModuleStreamId();\n        private int serviceStreamId = Configuration.serviceStreamId();\n        private String snapshotChannel = Configuration.snapshotChannel();\n        private int snapshotStreamId = Configuration.snapshotStreamId();\n        private int errorBufferLength = Configuration.errorBufferLength();\n        private boolean isRespondingService = Configuration.isRespondingService();\n        private int logFragmentLimit = Configuration.logFragmentLimit();\n        private long cycleThresholdNs = Configuration.cycleThresholdNs();\n        private long snapshotDurationThresholdNs = Configuration.snapshotDurationThresholdNs();\n\n        private CountDownLatch abortLatch;\n        private ThreadFactory threadFactory;\n        private Supplier<IdleStrategy> idleStrategySupplier;\n        private EpochClock epochClock;\n        private NanoClock nanoClock;\n        private DistinctErrorLog errorLog;\n        private ErrorHandler errorHandler;\n        private DelegatingErrorHandler delegatingErrorHandler;\n        private AtomicCounter errorCounter;\n        private CountedErrorHandler countedErrorHandler;\n        private AeronArchive.Context archiveContext;\n        private String clusterDirectoryName = Configuration.clusterDirName();\n        private File clusterDir;\n        private File markFileDir;\n        private String aeronDirectoryName = CommonContext.getAeronDirectoryName();\n        private Aeron aeron;\n        private DutyCycleTracker dutyCycleTracker;\n        private SnapshotDurationTracker snapshotDurationTracker;\n        private AppVersionValidator appVersionValidator;\n        private boolean ownsAeronClient;\n\n        private ClusteredService clusteredService;\n        private Runnable terminationHook;\n        private ClusterMarkFile markFile;\n\n        /**\n         * Construct a Context using default values and loading from system properties.\n         */\n        public Context()\n        {\n        }\n\n        /**\n         * Perform a shallow copy of the object.\n         *\n         * @return a shallow copy of the object.\n         */\n        public Context clone()\n        {\n            try\n            {\n                return (Context)super.clone();\n            }\n            catch (final CloneNotSupportedException ex)\n            {\n                throw new RuntimeException(ex);\n            }\n        }\n\n        /**\n         * Conclude configuration by setting up defaults when specifics are not provided.\n         */\n        @SuppressWarnings(\"MethodLength\")\n        public void conclude()\n        {\n            if ((boolean)IS_CONCLUDED_VH.getAndSet(this, true))\n            {\n                throw new ConcurrentConcludeException();\n            }\n\n            final int maxId = MAX_SERVICE_COUNT - 1;\n            if (serviceId < 0 || serviceId > maxId)\n            {\n                throw new ConfigurationException(\"service id outside allowed range [0,\" + maxId + \"]: \" + serviceId);\n            }\n\n            if (null == threadFactory)\n            {\n                threadFactory = Thread::new;\n            }\n\n            if (null == idleStrategySupplier)\n            {\n                idleStrategySupplier = Configuration.idleStrategySupplier(null);\n            }\n\n            if (null == appVersionValidator)\n            {\n                appVersionValidator = AppVersionValidator.SEMANTIC_VERSIONING_VALIDATOR;\n            }\n\n            if (null == epochClock)\n            {\n                epochClock = SystemEpochClock.INSTANCE;\n            }\n\n            if (null == nanoClock)\n            {\n                nanoClock = SystemNanoClock.INSTANCE;\n            }\n\n            if (null == clusterDir)\n            {\n                clusterDir = new File(clusterDirectoryName);\n            }\n\n            if (null == markFileDir)\n            {\n                final String dir = Configuration.markFileDir();\n                markFileDir = Strings.isEmpty(dir) ? clusterDir : new File(dir);\n            }\n\n            try\n            {\n                clusterDir = clusterDir.getCanonicalFile();\n                clusterDirectoryName = clusterDir.getAbsolutePath();\n                markFileDir = markFileDir.getCanonicalFile();\n            }\n            catch (final IOException e)\n            {\n                throw new UncheckedIOException(e);\n            }\n\n            IoUtil.ensureDirectoryExists(clusterDir, \"cluster\");\n            IoUtil.ensureDirectoryExists(markFileDir, \"mark file\");\n\n            if (null == markFile)\n            {\n                final int filePageSize = null != aeron ? aeron.context().filePageSize() :\n                    driverFilePageSize(new File(aeronDirectoryName), epochClock, new CommonContext().driverTimeoutMs());\n                markFile = new ClusterMarkFile(\n                    new File(markFileDir, ClusterMarkFile.markFilenameForService(serviceId)),\n                    ClusterComponentType.CONTAINER,\n                    errorBufferLength,\n                    epochClock,\n                    LIVENESS_TIMEOUT_MS,\n                    filePageSize);\n            }\n\n            MarkFile.ensureMarkFileLink(\n                clusterDir,\n                new File(markFile.parentDirectory(), ClusterMarkFile.markFilenameForService(serviceId)),\n                ClusterMarkFile.linkFilenameForService(serviceId));\n\n            if (null == errorLog)\n            {\n                errorLog = new DistinctErrorLog(markFile.errorBuffer(), epochClock, US_ASCII);\n            }\n\n            errorHandler = CommonContext.setupErrorHandler(this.errorHandler, errorLog);\n\n            if (null == delegatingErrorHandler)\n            {\n                delegatingErrorHandler = Configuration.newDelegatingErrorHandler();\n                if (null != delegatingErrorHandler)\n                {\n                    delegatingErrorHandler.next(errorHandler);\n                    errorHandler = delegatingErrorHandler;\n                }\n            }\n            else\n            {\n                delegatingErrorHandler.next(errorHandler);\n                errorHandler = delegatingErrorHandler;\n            }\n\n            if (Strings.isEmpty(serviceName))\n            {\n                serviceName = \"clustered-service-\" + clusterId + \"-\" + serviceId;\n            }\n\n            if (null == aeron)\n            {\n                aeron = Aeron.connect(\n                    new Aeron.Context()\n                        .aeronDirectoryName(aeronDirectoryName)\n                        .errorHandler(errorHandler)\n                        .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE)\n                        .awaitingIdleStrategy(YieldingIdleStrategy.INSTANCE)\n                        .epochClock(epochClock)\n                        .clientName(serviceName));\n\n                ownsAeronClient = true;\n            }\n\n            if (!(aeron.context().subscriberErrorHandler() instanceof RethrowingErrorHandler))\n            {\n                throw new ClusterException(\"Aeron client must use a RethrowingErrorHandler\");\n            }\n\n            final ExpandableArrayBuffer tempBuffer = new ExpandableArrayBuffer();\n            if (null == errorCounter)\n            {\n                errorCounter = ClusterCounters.allocateServiceErrorCounter(aeron, tempBuffer, clusterId, serviceId);\n            }\n\n            if (null == countedErrorHandler)\n            {\n                countedErrorHandler = new CountedErrorHandler(errorHandler, errorCounter);\n                if (ownsAeronClient)\n                {\n                    aeron.context().errorHandler(countedErrorHandler);\n                }\n            }\n\n            if (null == dutyCycleTracker)\n            {\n                dutyCycleTracker = new DutyCycleStallTracker(\n                    ClusterCounters.allocateServiceCounter(\n                        aeron,\n                        tempBuffer,\n                        \"Cluster container max cycle time in ns\",\n                        AeronCounters.CLUSTER_CLUSTERED_SERVICE_MAX_CYCLE_TIME_TYPE_ID,\n                        clusterId,\n                        serviceId),\n                    ClusterCounters.allocateServiceCounter(\n                        aeron,\n                        tempBuffer,\n                        \"Cluster container work cycle time exceeded count: threshold=\" +\n                            SystemUtil.formatDuration(cycleThresholdNs),\n                        AeronCounters.CLUSTER_CLUSTERED_SERVICE_CYCLE_TIME_THRESHOLD_EXCEEDED_TYPE_ID,\n                        clusterId,\n                        serviceId),\n                    cycleThresholdNs);\n            }\n\n            if (null == snapshotDurationTracker)\n            {\n                snapshotDurationTracker = new SnapshotDurationTracker(\n                    ClusterCounters.allocateServiceCounter(\n                        aeron,\n                        tempBuffer,\n                        \"Clustered service max snapshot duration in ns\",\n                        AeronCounters.CLUSTERED_SERVICE_MAX_SNAPSHOT_DURATION_TYPE_ID,\n                        clusterId,\n                        serviceId\n                    ),\n                    ClusterCounters.allocateServiceCounter(\n                        aeron,\n                        tempBuffer,\n                        \"Clustered service max snapshot duration exceeded count: threshold=\" +\n                            SystemUtil.formatDuration(snapshotDurationThresholdNs),\n                        AeronCounters.CLUSTERED_SERVICE_SNAPSHOT_DURATION_THRESHOLD_EXCEEDED_TYPE_ID,\n                        clusterId,\n                        serviceId\n                    ),\n                    snapshotDurationThresholdNs);\n            }\n\n            if (null == archiveContext)\n            {\n                archiveContext = new AeronArchive.Context()\n                    .controlRequestChannel(AeronArchive.Configuration.localControlChannel())\n                    .controlResponseChannel(AeronArchive.Configuration.localControlChannel())\n                    .controlRequestStreamId(AeronArchive.Configuration.localControlStreamId())\n                    .controlResponseStreamId(\n                        clusterId * 100 + 100 + AeronArchive.Configuration.controlResponseStreamId() + (serviceId + 1));\n            }\n\n            if (!archiveContext.controlRequestChannel().startsWith(CommonContext.IPC_CHANNEL))\n            {\n                throw new ClusterException(\"local archive control must be IPC\");\n            }\n\n            if (!archiveContext.controlResponseChannel().startsWith(CommonContext.IPC_CHANNEL))\n            {\n                throw new ClusterException(\"local archive control must be IPC\");\n            }\n\n            archiveContext\n                .aeron(aeron)\n                .ownsAeronClient(false)\n                .lock(NoOpLock.INSTANCE)\n                .errorHandler(countedErrorHandler)\n                .controlRequestChannel(addAliasIfAbsent(\n                archiveContext.controlRequestChannel(),\n                \"sc-\" + serviceId + \"-archive-ctrl-req-cluster-\" + clusterId))\n                .controlResponseChannel(addAliasIfAbsent(\n                archiveContext.controlResponseChannel(),\n                \"sc-\" + serviceId + \"-archive-ctrl-resp-cluster-\" + clusterId))\n                .clientName(serviceName);\n\n            if (null == terminationHook)\n            {\n                terminationHook = () -> {};\n            }\n\n            if (null == clusteredService)\n            {\n                clusteredService = Configuration.newClusteredService();\n            }\n\n            abortLatch = new CountDownLatch(!aeron.context().useConductorAgentInvoker() ? 1 : 0);\n            concludeMarkFile();\n\n            if (CommonContext.shouldPrintConfigurationOnStart())\n            {\n                System.out.println(this);\n            }\n        }\n\n        /**\n         * Has the context had the {@link #conclude()} method called.\n         *\n         * @return true of the {@link #conclude()} method has been called.\n         */\n        public boolean isConcluded()\n        {\n            return isConcluded;\n        }\n\n        /**\n         * User assigned application version which appended to the log as the appVersion in new leadership events.\n         * <p>\n         * This can be validated using {@link org.agrona.SemanticVersion} to ensure only application nodes of the same\n         * major version communicate with each other.\n         *\n         * @param appVersion for user application.\n         * @return this for a fluent API.\n         */\n        public Context appVersion(final int appVersion)\n        {\n            this.appVersion = appVersion;\n            return this;\n        }\n\n        /**\n         * User assigned application version which appended to the log as the appVersion in new leadership events.\n         * <p>\n         * This can be validated using {@link org.agrona.SemanticVersion} to ensure only application nodes of the same\n         * major version communicate with each other.\n         *\n         * @return appVersion for user application.\n         */\n        public int appVersion()\n        {\n            return appVersion;\n        }\n\n        /**\n         * User assigned application version validator implementation used to check version compatibility.\n         * <p>\n         * The default validator uses {@link org.agrona.SemanticVersion} semantics.\n         *\n         * @param appVersionValidator for user application.\n         * @return this for fluent API.\n         */\n        public Context appVersionValidator(final AppVersionValidator appVersionValidator)\n        {\n            this.appVersionValidator = appVersionValidator;\n            return this;\n        }\n\n        /**\n         * User assigned application version validator implementation used to check version compatibility.\n         * <p>\n         * The default is to use {@link org.agrona.SemanticVersion} major version for checking compatibility.\n         *\n         * @return AppVersionValidator in use.\n         */\n        public AppVersionValidator appVersionValidator()\n        {\n            return appVersionValidator;\n        }\n\n        /**\n         * Set the id for this cluster instance. This must match with the Consensus Module.\n         *\n         * @param clusterId for this clustered instance.\n         * @return this for a fluent API\n         * @see Configuration#CLUSTER_ID_PROP_NAME\n         */\n        public Context clusterId(final int clusterId)\n        {\n            this.clusterId = clusterId;\n            return this;\n        }\n\n        /**\n         * Get the id for this cluster instance. This must match with the Consensus Module.\n         *\n         * @return the id for this cluster instance.\n         * @see Configuration#CLUSTER_ID_PROP_NAME\n         */\n        @Config\n        public int clusterId()\n        {\n            return clusterId;\n        }\n\n        /**\n         * Set the id for this clustered service. Services should be numbered from 0 and be contiguous.\n         *\n         * @param serviceId for this clustered service.\n         * @return this for a fluent API\n         * @see Configuration#SERVICE_ID_PROP_NAME\n         */\n        public Context serviceId(final int serviceId)\n        {\n            this.serviceId = serviceId;\n            return this;\n        }\n\n        /**\n         * Get the id for this clustered service. Services should be numbered from 0 and be contiguous.\n         *\n         * @return the id for this clustered service.\n         * @see Configuration#SERVICE_ID_PROP_NAME\n         */\n        @Config\n        public int serviceId()\n        {\n            return serviceId;\n        }\n\n        /**\n         * Set the name for a clustered service to be the {@link Agent#roleName()} for the {@link Agent}.\n         *\n         * @param serviceName for a clustered service to be the role for the {@link Agent}.\n         * @return this for a fluent API.\n         * @see Configuration#SERVICE_NAME_PROP_NAME\n         */\n        public Context serviceName(final String serviceName)\n        {\n            this.serviceName = serviceName;\n            return this;\n        }\n\n        /**\n         * Get the name for a clustered service to be the {@link Agent#roleName()} for the {@link Agent}.\n         *\n         * @return the name for a clustered service to be the role of the {@link Agent}.\n         * @see Configuration#SERVICE_NAME_PROP_NAME\n         */\n        @Config\n        public String serviceName()\n        {\n            return serviceName;\n        }\n\n        /**\n         * Set the channel parameter for the cluster log and snapshot replay channel.\n         *\n         * @param channel parameter for the cluster log replay channel.\n         * @return this for a fluent API.\n         * @see Configuration#REPLAY_CHANNEL_PROP_NAME\n         */\n        public Context replayChannel(final String channel)\n        {\n            replayChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for the cluster log and snapshot replay channel.\n         *\n         * @return the channel parameter for the cluster replay channel.\n         * @see Configuration#REPLAY_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String replayChannel()\n        {\n            return replayChannel;\n        }\n\n        /**\n         * Set the stream id for the cluster log and snapshot replay channel.\n         *\n         * @param streamId for the cluster log replay channel.\n         * @return this for a fluent API\n         * @see Configuration#REPLAY_STREAM_ID_PROP_NAME\n         */\n        public Context replayStreamId(final int streamId)\n        {\n            replayStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for the cluster log and snapshot replay channel.\n         *\n         * @return the stream id for the cluster log replay channel.\n         * @see Configuration#REPLAY_STREAM_ID_PROP_NAME\n         */\n        @Config\n        public int replayStreamId()\n        {\n            return replayStreamId;\n        }\n\n        /**\n         * Set the channel parameter for bidirectional communications between the consensus module and services.\n         *\n         * @param channel parameter for sending messages to the Consensus Module.\n         * @return this for a fluent API.\n         * @see Configuration#CONTROL_CHANNEL_PROP_NAME\n         */\n        public Context controlChannel(final String channel)\n        {\n            controlChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for bidirectional communications between the consensus module and services.\n         *\n         * @return the channel parameter for sending messages to the Consensus Module.\n         * @see Configuration#CONTROL_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String controlChannel()\n        {\n            return controlChannel;\n        }\n\n        /**\n         * Set the stream id for communications from the consensus module and to the services.\n         *\n         * @param streamId for communications from the consensus module and to the services.\n         * @return this for a fluent API\n         * @see Configuration#SERVICE_STREAM_ID_PROP_NAME\n         */\n        public Context serviceStreamId(final int streamId)\n        {\n            serviceStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for communications from the consensus module and to the services.\n         *\n         * @return the stream id for communications from the consensus module and to the services.\n         * @see Configuration#SERVICE_STREAM_ID_PROP_NAME\n         */\n        @Config\n        public int serviceStreamId()\n        {\n            return serviceStreamId;\n        }\n\n        /**\n         * Set the stream id for communications from the services to the consensus module.\n         *\n         * @param streamId for communications from the services to the consensus module.\n         * @return this for a fluent API\n         * @see Configuration#CONSENSUS_MODULE_STREAM_ID_PROP_NAME\n         */\n        public Context consensusModuleStreamId(final int streamId)\n        {\n            consensusModuleStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for communications from the services to the consensus module.\n         *\n         * @return the stream id for communications from the services to the consensus module.\n         * @see Configuration#CONSENSUS_MODULE_STREAM_ID_PROP_NAME\n         */\n        @Config\n        public int consensusModuleStreamId()\n        {\n            return consensusModuleStreamId;\n        }\n\n        /**\n         * Set the channel parameter for snapshot recordings.\n         *\n         * @param channel parameter for snapshot recordings\n         * @return this for a fluent API.\n         * @see Configuration#SNAPSHOT_CHANNEL_PROP_NAME\n         */\n        public Context snapshotChannel(final String channel)\n        {\n            snapshotChannel = channel;\n            return this;\n        }\n\n        /**\n         * Get the channel parameter for snapshot recordings.\n         *\n         * @return the channel parameter for snapshot recordings.\n         * @see Configuration#SNAPSHOT_CHANNEL_PROP_NAME\n         */\n        @Config\n        public String snapshotChannel()\n        {\n            return snapshotChannel;\n        }\n\n        /**\n         * Set the stream id for snapshot recordings.\n         *\n         * @param streamId for snapshot recordings.\n         * @return this for a fluent API\n         * @see Configuration#SNAPSHOT_STREAM_ID_PROP_NAME\n         */\n        public Context snapshotStreamId(final int streamId)\n        {\n            snapshotStreamId = streamId;\n            return this;\n        }\n\n        /**\n         * Get the stream id for snapshot recordings.\n         *\n         * @return the stream id for snapshot recordings.\n         * @see Configuration#SNAPSHOT_STREAM_ID_PROP_NAME\n         */\n        @Config\n        public int snapshotStreamId()\n        {\n            return snapshotStreamId;\n        }\n\n        /**\n         * Set if this a service that responds to client requests.\n         *\n         * @param isRespondingService true if this service responds to client requests, otherwise false.\n         * @return this for a fluent API.\n         * @see Configuration#RESPONDER_SERVICE_PROP_NAME\n         */\n        public Context isRespondingService(final boolean isRespondingService)\n        {\n            this.isRespondingService = isRespondingService;\n            return this;\n        }\n\n        /**\n         * Set the fragment limit to be used when polling the log {@link Subscription}.\n         *\n         * @param logFragmentLimit for this clustered service.\n         * @return this for a fluent API\n         * @see Configuration#LOG_FRAGMENT_LIMIT_DEFAULT\n         */\n        public Context logFragmentLimit(final int logFragmentLimit)\n        {\n            this.logFragmentLimit = logFragmentLimit;\n            return this;\n        }\n\n        /**\n         * Get the fragment limit to be used when polling the log {@link Subscription}.\n         *\n         * @return the fragment limit to be used when polling the log {@link Subscription}.\n         * @see Configuration#LOG_FRAGMENT_LIMIT_PROP_NAME\n         */\n        @Config\n        public int logFragmentLimit()\n        {\n            return logFragmentLimit;\n        }\n\n        /**\n         * Is this a service that responds to client requests?\n         *\n         * @return true if this service responds to client requests, otherwise false.\n         * @see Configuration#RESPONDER_SERVICE_PROP_NAME\n         */\n        @Config(id = \"RESPONDER_SERVICE\")\n        public boolean isRespondingService()\n        {\n            return isRespondingService;\n        }\n\n        /**\n         * Get the thread factory used for creating threads.\n         *\n         * @return thread factory used for creating threads.\n         */\n        public ThreadFactory threadFactory()\n        {\n            return threadFactory;\n        }\n\n        /**\n         * Set the thread factory used for creating threads.\n         *\n         * @param threadFactory used for creating threads\n         * @return this for a fluent API.\n         */\n        public Context threadFactory(final ThreadFactory threadFactory)\n        {\n            this.threadFactory = threadFactory;\n            return this;\n        }\n\n        /**\n         * Provides an {@link IdleStrategy} supplier for the idle strategy for the agent duty cycle.\n         *\n         * @param idleStrategySupplier supplier for the idle strategy for the agent duty cycle.\n         * @return this for a fluent API.\n         */\n        public Context idleStrategySupplier(final Supplier<IdleStrategy> idleStrategySupplier)\n        {\n            this.idleStrategySupplier = idleStrategySupplier;\n            return this;\n        }\n\n        /**\n         * Get a new {@link IdleStrategy} based on configured supplier.\n         *\n         * @return a new {@link IdleStrategy} based on configured supplier.\n         */\n        @Config(id = \"CLUSTER_IDLE_STRATEGY\")\n        public IdleStrategy idleStrategy()\n        {\n            return idleStrategySupplier.get();\n        }\n\n        /**\n         * Set the {@link EpochClock} to be used for tracking wall clock time when interacting with the container.\n         *\n         * @param clock {@link EpochClock} to be used for tracking wall clock time when interacting with the container.\n         * @return this for a fluent API.\n         */\n        public Context epochClock(final EpochClock clock)\n        {\n            this.epochClock = clock;\n            return this;\n        }\n\n        /**\n         * Get the {@link EpochClock} to used for tracking wall clock time within the container.\n         *\n         * @return the {@link EpochClock} to used for tracking wall clock time within the container.\n         */\n        public EpochClock epochClock()\n        {\n            return epochClock;\n        }\n\n        /**\n         * Get the {@link ErrorHandler} to be used by the {@link ClusteredServiceContainer}.\n         *\n         * @return the {@link ErrorHandler} to be used by the {@link ClusteredServiceContainer}.\n         */\n        public ErrorHandler errorHandler()\n        {\n            return errorHandler;\n        }\n\n        /**\n         * Set the {@link ErrorHandler} to be used by the {@link ClusteredServiceContainer}.\n         *\n         * @param errorHandler the error handler to be used by the {@link ClusteredServiceContainer}.\n         * @return this for a fluent API\n         */\n        public Context errorHandler(final ErrorHandler errorHandler)\n        {\n            this.errorHandler = errorHandler;\n            return this;\n        }\n\n        /**\n         * Get the {@link DelegatingErrorHandler} to be used by the {@link ClusteredServiceContainer} which will\n         * delegate to {@link #errorHandler()} as next in the chain.\n         *\n         * @return the {@link DelegatingErrorHandler} to be used by the {@link ClusteredServiceContainer}.\n         * @see Configuration#DELEGATING_ERROR_HANDLER_PROP_NAME\n         */\n        @Config\n        public DelegatingErrorHandler delegatingErrorHandler()\n        {\n            return delegatingErrorHandler;\n        }\n\n        /**\n         * Set the {@link DelegatingErrorHandler} to be used by the {@link ClusteredServiceContainer} which will\n         * delegate to {@link #errorHandler()} as next in the chain.\n         *\n         * @param delegatingErrorHandler the error handler to be used by the {@link ClusteredServiceContainer}.\n         * @return this for a fluent API\n         * @see Configuration#DELEGATING_ERROR_HANDLER_PROP_NAME\n         */\n        public Context delegatingErrorHandler(final DelegatingErrorHandler delegatingErrorHandler)\n        {\n            this.delegatingErrorHandler = delegatingErrorHandler;\n            return this;\n        }\n\n        /**\n         * Get the error counter that will record the number of errors the container has observed.\n         *\n         * @return the error counter that will record the number of errors the container has observed.\n         */\n        public AtomicCounter errorCounter()\n        {\n            return errorCounter;\n        }\n\n        /**\n         * Set the error counter that will record the number of errors the cluster node has observed.\n         *\n         * @param errorCounter the error counter that will record the number of errors the cluster node has observed.\n         * @return this for a fluent API.\n         */\n        public Context errorCounter(final AtomicCounter errorCounter)\n        {\n            this.errorCounter = errorCounter;\n            return this;\n        }\n\n        /**\n         * Non-default for context.\n         *\n         * @param countedErrorHandler to override the default.\n         * @return this for a fluent API.\n         */\n        public Context countedErrorHandler(final CountedErrorHandler countedErrorHandler)\n        {\n            this.countedErrorHandler = countedErrorHandler;\n            return this;\n        }\n\n        /**\n         * The {@link #errorHandler()} that will increment {@link #errorCounter()} by default.\n         *\n         * @return {@link #errorHandler()} that will increment {@link #errorCounter()} by default.\n         */\n        public CountedErrorHandler countedErrorHandler()\n        {\n            return countedErrorHandler;\n        }\n\n        /**\n         * Set the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @param aeronDirectoryName the top level Aeron directory.\n         * @return this for a fluent API.\n         */\n        public Context aeronDirectoryName(final String aeronDirectoryName)\n        {\n            this.aeronDirectoryName = aeronDirectoryName;\n            return this;\n        }\n\n        /**\n         * Get the top level Aeron directory used for communication between the Aeron client and Media Driver.\n         *\n         * @return The top level Aeron directory.\n         */\n        public String aeronDirectoryName()\n        {\n            return aeronDirectoryName;\n        }\n\n        /**\n         * An {@link Aeron} client for the container.\n         *\n         * @return {@link Aeron} client for the container\n         */\n        public Aeron aeron()\n        {\n            return aeron;\n        }\n\n        /**\n         * Provide an {@link Aeron} client for the container\n         * <p>\n         * If not provided then one will be created.\n         *\n         * @param aeron client for the container\n         * @return this for a fluent API.\n         */\n        public Context aeron(final Aeron aeron)\n        {\n            this.aeron = aeron;\n            return this;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @param ownsAeronClient does this context own the {@link #aeron()} client.\n         * @return this for a fluent API.\n         */\n        public Context ownsAeronClient(final boolean ownsAeronClient)\n        {\n            this.ownsAeronClient = ownsAeronClient;\n            return this;\n        }\n\n        /**\n         * Does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         *\n         * @return does this context own the {@link #aeron()} client and this takes responsibility for closing it?\n         */\n        public boolean ownsAeronClient()\n        {\n            return ownsAeronClient;\n        }\n\n        /**\n         * The service this container holds.\n         *\n         * @return service this container holds.\n         */\n        @Config(id = \"SERVICE_CLASS_NAME\")\n        public ClusteredService clusteredService()\n        {\n            return clusteredService;\n        }\n\n        /**\n         * Set the service this container is to hold.\n         *\n         * @param clusteredService this container is to hold.\n         * @return this for fluent API.\n         */\n        public Context clusteredService(final ClusteredService clusteredService)\n        {\n            this.clusteredService = clusteredService;\n            return this;\n        }\n\n        /**\n         * Set the context that should be used for communicating with the local Archive.\n         *\n         * @param archiveContext that should be used for communicating with the local Archive.\n         * @return this for a fluent API.\n         */\n        public Context archiveContext(final AeronArchive.Context archiveContext)\n        {\n            this.archiveContext = archiveContext;\n            return this;\n        }\n\n        /**\n         * Get the context that should be used for communicating with the local Archive.\n         *\n         * @return the context that should be used for communicating with the local Archive.\n         */\n        public AeronArchive.Context archiveContext()\n        {\n            return archiveContext;\n        }\n\n        /**\n         * Set the directory name to use for the consensus module directory.\n         *\n         * @param clusterDirectoryName to use.\n         * @return this for a fluent API.\n         * @see Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public Context clusterDirectoryName(final String clusterDirectoryName)\n        {\n            this.clusterDirectoryName = clusterDirectoryName;\n            return this;\n        }\n\n        /**\n         * The directory name to use for the cluster directory.\n         *\n         * @return directory name for the cluster directory.\n         * @see Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        @Config(id = \"CLUSTER_DIR\")\n        public String clusterDirectoryName()\n        {\n            return clusterDirectoryName;\n        }\n\n        /**\n         * Set the directory to use for the cluster directory.\n         *\n         * @param clusterDir to use.\n         * @return this for a fluent API.\n         * @see ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public Context clusterDir(final File clusterDir)\n        {\n            this.clusterDir = clusterDir;\n            return this;\n        }\n\n        /**\n         * The directory used for the cluster directory.\n         *\n         * @return directory for the cluster directory.\n         * @see ClusteredServiceContainer.Configuration#CLUSTER_DIR_PROP_NAME\n         */\n        public File clusterDir()\n        {\n            return clusterDir;\n        }\n\n        /**\n         * Get the directory in which the ClusteredServiceContainer will store mark file (i.e. {@code\n         * cluster-mark-service-0.dat}). It defaults to {@link #clusterDir()} if it is not set explicitly via the {@link\n         * ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME}.\n         *\n         * @return the directory in which the ClusteredServiceContainer will store mark file (i.e.\n         * {@code cluster-mark-service-0.dat}).\n         * @see ClusteredServiceContainer.Configuration#MARK_FILE_DIR_PROP_NAME\n         * @see #clusterDir()\n         */\n        @Config\n        public File markFileDir()\n        {\n            return markFileDir;\n        }\n\n        /**\n         * Set the directory in which the ClusteredServiceContainer will store mark file (i.e. {@code\n         * cluster-mark-service-0.dat}).\n         *\n         * @param markFileDir the directory in which the ClusteredServiceContainer will store mark file (i.e. {@code\n         *                    cluster-mark-service-0.dat}).\n         * @return this for a fluent API.\n         */\n        public ClusteredServiceContainer.Context markFileDir(final File markFileDir)\n        {\n            this.markFileDir = markFileDir;\n            return this;\n        }\n\n        /**\n         * Set the {@link Runnable} that is called when container is instructed to terminate.\n         *\n         * @param terminationHook that can be used to terminate a service container.\n         * @return this for a fluent API.\n         */\n        public Context terminationHook(final Runnable terminationHook)\n        {\n            this.terminationHook = terminationHook;\n            return this;\n        }\n\n        /**\n         * Get the {@link Runnable} that is called when container is instructed to terminate.\n         *\n         * @return the {@link Runnable} that can be used to terminate a service container.\n         */\n        public Runnable terminationHook()\n        {\n            return terminationHook;\n        }\n\n        /**\n         * Set the {@link ClusterMarkFile} in use.\n         *\n         * @param markFile to use.\n         * @return this for a fluent API.\n         */\n        public Context clusterMarkFile(final ClusterMarkFile markFile)\n        {\n            this.markFile = markFile;\n            return this;\n        }\n\n        /**\n         * The {@link ClusterMarkFile} in use.\n         *\n         * @return {@link ClusterMarkFile} in use.\n         */\n        public ClusterMarkFile clusterMarkFile()\n        {\n            return markFile;\n        }\n\n        /**\n         * Set the error buffer length in bytes to use.\n         *\n         * @param errorBufferLength in bytes to use.\n         * @return this for a fluent API.\n         */\n        public Context errorBufferLength(final int errorBufferLength)\n        {\n            this.errorBufferLength = errorBufferLength;\n            return this;\n        }\n\n        /**\n         * The error buffer length in bytes.\n         *\n         * @return error buffer length in bytes.\n         */\n        @Config(id = \"SERVICE_ERROR_BUFFER_LENGTH\")\n        public int errorBufferLength()\n        {\n            return errorBufferLength;\n        }\n\n        /**\n         * Set the {@link DistinctErrorLog} in use.\n         *\n         * @param errorLog to use.\n         * @return this for a fluent API.\n         */\n        public Context errorLog(final DistinctErrorLog errorLog)\n        {\n            this.errorLog = errorLog;\n            return this;\n        }\n\n        /**\n         * The {@link DistinctErrorLog} in use.\n         *\n         * @return {@link DistinctErrorLog} in use.\n         */\n        public DistinctErrorLog errorLog()\n        {\n            return errorLog;\n        }\n\n        /**\n         * The {@link NanoClock} as a source of time in nanoseconds for measuring duration.\n         *\n         * @return the {@link NanoClock} as a source of time in nanoseconds for measuring duration.\n         */\n        public NanoClock nanoClock()\n        {\n            return nanoClock;\n        }\n\n        /**\n         * The {@link NanoClock} as a source of time in nanoseconds for measuring duration.\n         *\n         * @param clock to be used.\n         * @return this for a fluent API.\n         */\n        public Context nanoClock(final NanoClock clock)\n        {\n            nanoClock = clock;\n            return this;\n        }\n\n        /**\n         * Set a threshold for the container work cycle time which when exceed it will increment the\n         * counter.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see Configuration#CYCLE_THRESHOLD_PROP_NAME\n         * @see Configuration#CYCLE_THRESHOLD_DEFAULT_NS\n         */\n        public Context cycleThresholdNs(final long thresholdNs)\n        {\n            this.cycleThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for the container work cycle time which when exceed it will increment the\n         * counter.\n         *\n         * @return threshold to track for the container work cycle time.\n         */\n        @Config(id = \"SERVICE_CYCLE_THRESHOLD\")\n        public long cycleThresholdNs()\n        {\n            return cycleThresholdNs;\n        }\n\n        /**\n         * Set a duty cycle tracker to be used for tracking the duty cycle time of the container.\n         *\n         * @param dutyCycleTracker to use for tracking.\n         * @return this for fluent API.\n         */\n        public Context dutyCycleTracker(final DutyCycleTracker dutyCycleTracker)\n        {\n            this.dutyCycleTracker = dutyCycleTracker;\n            return this;\n        }\n\n        /**\n         * The duty cycle tracker used to track the container duty cycle.\n         *\n         * @return the duty cycle tracker.\n         */\n        public DutyCycleTracker dutyCycleTracker()\n        {\n            return dutyCycleTracker;\n        }\n\n        /**\n         * Set a threshold for snapshot duration which when exceeded will result in a counter increment.\n         *\n         * @param thresholdNs value in nanoseconds.\n         * @return this for fluent API.\n         * @see Configuration#SNAPSHOT_DURATION_THRESHOLD_PROP_NAME\n         * @see Configuration#SNAPSHOT_DURATION_THRESHOLD_DEFAULT_NS\n         * @since 1.44.0\n         */\n        public Context snapshotDurationThresholdNs(final long thresholdNs)\n        {\n            this.snapshotDurationThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for snapshot duration which when exceeded will result in a counter increment.\n         *\n         * @return threshold value in nanoseconds.\n         * @since 1.44.0\n         */\n        @Config\n        public long snapshotDurationThresholdNs()\n        {\n            return snapshotDurationThresholdNs;\n        }\n\n        /**\n         * Set snapshot duration tracker used for monitoring snapshot duration.\n         *\n         * @param snapshotDurationTracker snapshot duration tracker.\n         * @return this for fluent API.\n         * @since 1.44.0\n         */\n        public Context snapshotDurationTracker(final SnapshotDurationTracker snapshotDurationTracker)\n        {\n            this.snapshotDurationTracker = snapshotDurationTracker;\n            return this;\n        }\n\n        /**\n         * Get snapshot duration tracker used for monitoring snapshot duration.\n         *\n         * @return snapshot duration tracker.\n         * @since 1.44.0\n         */\n        public SnapshotDurationTracker snapshotDurationTracker()\n        {\n            return snapshotDurationTracker;\n        }\n\n        /**\n         * Delete the cluster container directory.\n         */\n        public void deleteDirectory()\n        {\n            if (null != clusterDir)\n            {\n                IoUtil.delete(clusterDir, false);\n            }\n        }\n\n        /**\n         * Indicates if this node should take standby snapshots.\n         *\n         * @return <code>true</code> if this should take standby snapshots, <code>false</code> otherwise.\n         * @deprecated This value is now ignored. The cluster will auto-determine if standby snapshots are required.\n         */\n        @Deprecated\n        public boolean standbySnapshotEnabled()\n        {\n            return false;\n        }\n\n        /**\n         * Indicates if this node should take standby snapshots.\n         *\n         * @param standbySnapshotEnabled if this node should take standby snapshots.\n         * @return this for a fluent API.\n         * @deprecated This value is now ignored. The cluster will auto-determine if standby snapshots are required.\n         */\n        @Deprecated\n        public ClusteredServiceContainer.Context standbySnapshotEnabled(final boolean standbySnapshotEnabled)\n        {\n            return this;\n        }\n\n        /**\n         * Close the context and free applicable resources.\n         * <p>\n         * If {@link #ownsAeronClient()} is true then the {@link #aeron()} client will be closed.\n         */\n        public void close()\n        {\n            final ErrorHandler errorHandler = countedErrorHandler();\n            if (ownsAeronClient)\n            {\n                CloseHelper.close(errorHandler, aeron);\n            }\n\n            CloseHelper.close(markFile);\n        }\n\n        CountDownLatch abortLatch()\n        {\n            return abortLatch;\n        }\n\n        private void concludeMarkFile()\n        {\n            ClusterMarkFile.checkHeaderLength(\n                aeron.context().aeronDirectoryName(), controlChannel(), null, serviceName, null);\n\n            final MarkFileHeaderEncoder encoder = markFile.encoder();\n\n            encoder\n                .archiveStreamId(archiveContext.controlRequestStreamId())\n                .serviceStreamId(serviceStreamId)\n                .consensusModuleStreamId(consensusModuleStreamId)\n                .ingressStreamId(Aeron.NULL_VALUE)\n                .memberId(Aeron.NULL_VALUE)\n                .serviceId(serviceId)\n                .clusterId(clusterId)\n                .aeronDirectory(aeron.context().aeronDirectoryName())\n                .controlChannel(controlChannel)\n                .ingressChannel(null)\n                .serviceName(serviceName)\n                .authenticator(null);\n\n            markFile.signalReady(epochClock.time());\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"ClusteredServiceContainer.Context\" +\n                \"\\n{\" +\n                \"\\n    isConcluded=\" + isConcluded() +\n                \"\\n    ownsAeronClient=\" + ownsAeronClient +\n                \"\\n    aeronDirectoryName='\" + aeronDirectoryName + '\\'' +\n                \"\\n    aeron=\" + aeron +\n                \"\\n    archiveContext=\" + archiveContext +\n                \"\\n    clusterDirectoryName='\" + clusterDirectoryName + '\\'' +\n                \"\\n    clusterDir=\" + clusterDir +\n                \"\\n    appVersion=\" + appVersion +\n                \"\\n    clusterId=\" + clusterId +\n                \"\\n    serviceId=\" + serviceId +\n                \"\\n    serviceName='\" + serviceName + '\\'' +\n                \"\\n    replayChannel='\" + replayChannel + '\\'' +\n                \"\\n    replayStreamId=\" + replayStreamId +\n                \"\\n    controlChannel='\" + controlChannel + '\\'' +\n                \"\\n    consensusModuleStreamId=\" + consensusModuleStreamId +\n                \"\\n    serviceStreamId=\" + serviceStreamId +\n                \"\\n    snapshotChannel='\" + snapshotChannel + '\\'' +\n                \"\\n    snapshotStreamId=\" + snapshotStreamId +\n                \"\\n    errorBufferLength=\" + errorBufferLength +\n                \"\\n    isRespondingService=\" + isRespondingService +\n                \"\\n    logFragmentLimit=\" + logFragmentLimit +\n                \"\\n    abortLatch=\" + abortLatch +\n                \"\\n    threadFactory=\" + threadFactory +\n                \"\\n    idleStrategySupplier=\" + idleStrategySupplier +\n                \"\\n    epochClock=\" + epochClock +\n                \"\\n    errorLog=\" + errorLog +\n                \"\\n    errorHandler=\" + errorHandler +\n                \"\\n    delegatingErrorHandler=\" + delegatingErrorHandler +\n                \"\\n    errorCounter=\" + errorCounter +\n                \"\\n    countedErrorHandler=\" + countedErrorHandler +\n                \"\\n    clusteredService=\" + clusteredService +\n                \"\\n    terminationHook=\" + terminationHook +\n                \"\\n    cycleThresholdNs=\" + cycleThresholdNs +\n                \"\\n    dutyCyleTracker=\" + dutyCycleTracker +\n                \"\\n    snapshotDurationThresholdNs=\" + snapshotDurationThresholdNs +\n                \"\\n    snapshotDurationTracker=\" + snapshotDurationTracker +\n                \"\\n    markFile=\" + markFile +\n                \"\\n}\";\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ConsensusModuleProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.DirectBufferVector;\nimport io.aeron.Publication;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\n\n/**\n * Proxy for communicating with the Consensus Module over IPC.\n * <p>\n * <b>Note: </b>This class is not for public use.\n */\npublic final class ConsensusModuleProxy implements AutoCloseable\n{\n    private final BufferClaim bufferClaim = new BufferClaim();\n    private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n    private final ScheduleTimerEncoder scheduleTimerEncoder = new ScheduleTimerEncoder();\n    private final CancelTimerEncoder cancelTimerEncoder = new CancelTimerEncoder();\n    private final ServiceAckEncoder serviceAckEncoder = new ServiceAckEncoder();\n    private final CloseSessionEncoder closeSessionEncoder = new CloseSessionEncoder();\n    private final ClusterMembersQueryEncoder clusterMembersQueryEncoder = new ClusterMembersQueryEncoder();\n    private final Publication publication;\n\n    /**\n     * Construct a proxy to the consensus module that will send messages over a provided {@link Publication}.\n     *\n     * @param publication for sending messages to the consensus module.\n     */\n    public ConsensusModuleProxy(final Publication publication)\n    {\n        this.publication = publication;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(publication);\n    }\n\n    boolean scheduleTimer(final long correlationId, final long deadline)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ScheduleTimerEncoder.BLOCK_LENGTH;\n        final long position = publication.tryClaim(length, bufferClaim);\n        if (position > 0)\n        {\n            scheduleTimerEncoder\n                .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                .correlationId(correlationId)\n                .deadline(deadline);\n\n            bufferClaim.commit();\n\n            return true;\n        }\n\n        checkResult(position, publication);\n\n        return false;\n    }\n\n    boolean cancelTimer(final long correlationId)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + CancelTimerEncoder.BLOCK_LENGTH;\n        final long position = publication.tryClaim(length, bufferClaim);\n        if (position > 0)\n        {\n            cancelTimerEncoder\n                .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                .correlationId(correlationId);\n\n            bufferClaim.commit();\n\n            return true;\n        }\n\n        checkResult(position, publication);\n\n        return false;\n    }\n\n    long offer(\n        final DirectBuffer headerBuffer,\n        final int headerOffset,\n        final int headerLength,\n        final DirectBuffer messageBuffer,\n        final int messageOffset,\n        final int messageLength)\n    {\n        final long position = publication.offer(\n            headerBuffer, headerOffset, headerLength, messageBuffer, messageOffset, messageLength);\n        if (position < 0)\n        {\n            checkResult(position, publication);\n        }\n\n        return position;\n    }\n\n    long offer(final DirectBufferVector[] vectors)\n    {\n        final long position = publication.offer(vectors, null);\n        if (position < 0)\n        {\n            checkResult(position, publication);\n        }\n\n        return position;\n    }\n\n    long tryClaim(final int length, final BufferClaim bufferClaim, final DirectBuffer sessionHeader)\n    {\n        final long position = publication.tryClaim(length, bufferClaim);\n        if (position > 0)\n        {\n            bufferClaim.putBytes(sessionHeader, 0, AeronCluster.SESSION_HEADER_LENGTH);\n        }\n        else\n        {\n            checkResult(position, publication);\n        }\n\n        return position;\n    }\n\n    boolean ack(\n        final long logPosition, final long timestamp, final long ackId, final long relevantId, final int serviceId)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ServiceAckEncoder.BLOCK_LENGTH;\n        final long position = publication.tryClaim(length, bufferClaim);\n        if (position > 0)\n        {\n            serviceAckEncoder\n                .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                .logPosition(logPosition)\n                .timestamp(timestamp)\n                .ackId(ackId)\n                .relevantId(relevantId)\n                .serviceId(serviceId);\n\n            bufferClaim.commit();\n\n            return true;\n        }\n\n        checkResult(position, publication);\n\n        return false;\n    }\n\n    boolean closeSession(final long clusterSessionId)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + CloseSessionEncoder.BLOCK_LENGTH;\n        final long position = publication.tryClaim(length, bufferClaim);\n        if (position > 0)\n        {\n            closeSessionEncoder\n                .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                .clusterSessionId(clusterSessionId);\n\n            bufferClaim.commit();\n\n            return true;\n        }\n\n        checkResult(position, publication);\n\n        return false;\n    }\n\n    /**\n     * Query for the current cluster members.\n     *\n     * @param correlationId for the request.\n     * @return true of the request was successfully sent, otherwise false.\n     */\n    public boolean clusterMembersQuery(final long correlationId)\n    {\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ClusterMembersQueryEncoder.BLOCK_LENGTH;\n        final long position = publication.tryClaim(length, bufferClaim);\n        if (position > 0)\n        {\n            clusterMembersQueryEncoder\n                .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                .correlationId(correlationId)\n                .extended(BooleanType.TRUE);\n\n            bufferClaim.commit();\n\n            return true;\n        }\n\n        checkResult(position, publication);\n\n        return false;\n    }\n\n    private static void checkResult(final long position, final Publication publication)\n    {\n        if (Publication.NOT_CONNECTED == position)\n        {\n            throw new ClusterException(\"publication is not connected\");\n        }\n\n        if (Publication.CLOSED == position)\n        {\n            throw new ClusterException(\"publication is closed\");\n        }\n\n        if (Publication.MAX_POSITION_EXCEEDED == position)\n        {\n            throw new ClusterException(\"publication at max position: term-length=\" + publication.termBufferLength());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ContainerClientSession.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.*;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.*;\n\nimport java.util.Arrays;\n\nfinal class ContainerClientSession implements ClientSession\n{\n    private final long id;\n    private final int responseStreamId;\n    private final String responseChannel;\n    private final byte[] encodedPrincipal;\n\n    private final ClusteredServiceAgent clusteredServiceAgent;\n    private Publication responsePublication;\n    private boolean isClosing;\n\n    ContainerClientSession(\n        final long sessionId,\n        final int responseStreamId,\n        final String responseChannel,\n        final byte[] encodedPrincipal,\n        final ClusteredServiceAgent clusteredServiceAgent)\n    {\n        this.id = sessionId;\n        this.responseStreamId = responseStreamId;\n        this.responseChannel = responseChannel;\n        this.encodedPrincipal = encodedPrincipal;\n        this.clusteredServiceAgent = clusteredServiceAgent;\n    }\n\n\n    public long id()\n    {\n        return id;\n    }\n\n    public int responseStreamId()\n    {\n        return responseStreamId;\n    }\n\n    public String responseChannel()\n    {\n        return responseChannel;\n    }\n\n    public byte[] encodedPrincipal()\n    {\n        return encodedPrincipal;\n    }\n\n    public void close()\n    {\n        if (null != clusteredServiceAgent.getClientSession(id))\n        {\n            clusteredServiceAgent.closeClientSession(id);\n        }\n    }\n\n    public boolean isClosing()\n    {\n        return isClosing;\n    }\n\n    public long offer(final DirectBuffer buffer, final int offset, final int length)\n    {\n        return clusteredServiceAgent.offer(id, responsePublication, buffer, offset, length);\n    }\n\n    public long offer(final DirectBufferVector[] vectors)\n    {\n        return clusteredServiceAgent.offer(id, responsePublication, vectors);\n    }\n\n    public long tryClaim(final int length, final BufferClaim bufferClaim)\n    {\n        return clusteredServiceAgent.tryClaim(id, responsePublication, length, bufferClaim);\n    }\n\n    void connect(final Aeron aeron)\n    {\n        try\n        {\n            if (null == responsePublication)\n            {\n                responsePublication = aeron.addPublication(responseChannel, responseStreamId);\n            }\n        }\n        catch (final RegistrationException ex)\n        {\n            clusteredServiceAgent.handleError(new ClusterException(\n                \"failed to connect session response publication: \" + ex.getMessage(), AeronException.Category.WARN));\n        }\n    }\n\n    void markClosing()\n    {\n        this.isClosing = true;\n    }\n\n    void resetClosing()\n    {\n        isClosing = false;\n    }\n\n    void disconnect(final ErrorHandler errorHandler)\n    {\n        CloseHelper.close(errorHandler, responsePublication);\n        responsePublication = null;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ClientSession{\" +\n            \"id=\" + id +\n            \", responseStreamId=\" + responseStreamId +\n            \", responseChannel='\" + responseChannel + '\\'' +\n            \", encodedPrincipal=\" + Arrays.toString(encodedPrincipal) +\n            \", responsePublication=\" + responsePublication +\n            \", isClosing=\" + isClosing +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/RecoveryState.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.Counter;\nimport io.aeron.cluster.client.ClusterException;\nimport org.agrona.*;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.concurrent.status.CountersReader.*;\n\n/**\n * Counter representing the Recovery State for the cluster.\n * <p>\n * Key layout as follows:\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                     Leadership Term ID                        |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                  Log position for Snapshot                    |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |              Timestamp at beginning of Recovery               |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                         Cluster ID                            |\n *  +---------------------------------------------------------------+\n *  |                     Count of Services                         |\n *  +---------------------------------------------------------------+\n *  |             Snapshot Recording ID (Service ID 0)              |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |             Snapshot Recording ID (Service ID n)              |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic final class RecoveryState\n{\n    /**\n     * Type id of a recovery state counter.\n     */\n    public static final int RECOVERY_STATE_TYPE_ID = AeronCounters.CLUSTER_RECOVERY_STATE_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"Cluster recovery: leadershipTermId=\";\n\n    /**\n     * Offset of the {@code term-id} field.\n     */\n    public static final int LEADERSHIP_TERM_ID_OFFSET = 0;\n    /**\n     * Offset of the {@code log-position} field.\n     */\n    public static final int LOG_POSITION_OFFSET = LEADERSHIP_TERM_ID_OFFSET + SIZE_OF_LONG;\n    /**\n     * Offset of the {@code timestamp} field.\n     */\n    public static final int TIMESTAMP_OFFSET = LOG_POSITION_OFFSET + SIZE_OF_LONG;\n    /**\n     * Offset of the {@code cluster-id} field.\n     */\n    public static final int CLUSTER_ID_OFFSET = TIMESTAMP_OFFSET + SIZE_OF_LONG;\n    /**\n     * Offset of the {@code service-count} field.\n     */\n    public static final int SERVICE_COUNT_OFFSET = CLUSTER_ID_OFFSET + SIZE_OF_INT;\n    /**\n     * Offset of the {@code snapshot-recording-ids} field.\n     */\n    public static final int SNAPSHOT_RECORDING_IDS_OFFSET = SERVICE_COUNT_OFFSET + SIZE_OF_INT;\n\n    private RecoveryState()\n    {\n    }\n\n    /**\n     * Allocate a counter to represent the snapshot services should load on start.\n     *\n     * @param aeron                to allocate the counter.\n     * @param leadershipTermId     at which the snapshot was taken.\n     * @param logPosition          at which the snapshot was taken.\n     * @param timestamp            the snapshot was taken.\n     * @param clusterId            which identifies the cluster instance.\n     * @param snapshotRecordingIds for the services to use during recovery indexed by service id.\n     * @return the {@link Counter} for the recovery state.\n     */\n    public static Counter allocate(\n        final Aeron aeron,\n        final long leadershipTermId,\n        final long logPosition,\n        final long timestamp,\n        final int clusterId,\n        final long... snapshotRecordingIds)\n    {\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(256);\n\n        buffer.putLong(LEADERSHIP_TERM_ID_OFFSET, leadershipTermId);\n        buffer.putLong(LOG_POSITION_OFFSET, logPosition);\n        buffer.putLong(TIMESTAMP_OFFSET, timestamp);\n        buffer.putInt(CLUSTER_ID_OFFSET, clusterId);\n\n        final int serviceCount = snapshotRecordingIds.length;\n        buffer.putInt(SERVICE_COUNT_OFFSET, serviceCount);\n\n        final int keyLength = SNAPSHOT_RECORDING_IDS_OFFSET + (serviceCount * SIZE_OF_LONG);\n        if (keyLength > MAX_KEY_LENGTH)\n        {\n            throw new ClusterException(keyLength + \" exceeds max key length \" + MAX_KEY_LENGTH);\n        }\n\n        for (int i = 0; i < serviceCount; i++)\n        {\n            buffer.putLong(SNAPSHOT_RECORDING_IDS_OFFSET + (i * SIZE_OF_LONG), snapshotRecordingIds[i]);\n        }\n\n        final int labelOffset = BitUtil.align(keyLength, SIZE_OF_INT);\n        int labelLength = 0;\n        labelLength += buffer.putStringWithoutLengthAscii(labelOffset + labelLength, NAME);\n        labelLength += buffer.putLongAscii(keyLength + labelLength, leadershipTermId);\n        labelLength += buffer.putStringWithoutLengthAscii(labelOffset + labelLength, \" logPosition=\");\n        labelLength += buffer.putLongAscii(labelOffset + labelLength, logPosition);\n        labelLength += buffer.putStringWithoutLengthAscii(labelOffset + labelLength, \" clusterId=\");\n        labelLength += buffer.putIntAscii(labelOffset + labelLength, clusterId);\n\n        return aeron.addCounter(RECOVERY_STATE_TYPE_ID, buffer, 0, keyLength, buffer, labelOffset, labelLength);\n    }\n\n    /**\n     * Find the active counter id for recovery state.\n     *\n     * @param counters  to search within.\n     * @param clusterId to constrain the search.\n     * @return the counter id if found otherwise {@link CountersReader#NULL_COUNTER_ID}.\n     */\n    public static int findCounterId(final CountersReader counters, final int clusterId)\n    {\n        final DirectBuffer buffer = counters.metaDataBuffer();\n\n        for (int i = 0, size = counters.maxCounterId(); i < size; i++)\n        {\n            final int counterState = counters.getCounterState(i);\n            if (counterState == RECORD_ALLOCATED && counters.getCounterTypeId(i) == RECOVERY_STATE_TYPE_ID)\n            {\n                if (buffer.getInt(CountersReader.metaDataOffset(i) + KEY_OFFSET + CLUSTER_ID_OFFSET) == clusterId)\n                {\n                    return i;\n                }\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        return NULL_COUNTER_ID;\n    }\n\n    /**\n     * Get the leadership term id for the snapshot state. {@link Aeron#NULL_VALUE} if no snapshot for recovery.\n     *\n     * @param counters  to search within.\n     * @param counterId for the active recovery counter.\n     * @return the leadership term id if found otherwise {@link Aeron#NULL_VALUE}.\n     */\n    public static long getLeadershipTermId(final CountersReader counters, final int counterId)\n    {\n        final DirectBuffer buffer = counters.metaDataBuffer();\n\n        if (counters.getCounterState(counterId) == RECORD_ALLOCATED &&\n            counters.getCounterTypeId(counterId) == RECOVERY_STATE_TYPE_ID)\n        {\n            return buffer.getLong(CountersReader.metaDataOffset(counterId) + KEY_OFFSET + LEADERSHIP_TERM_ID_OFFSET);\n        }\n\n        return NULL_VALUE;\n    }\n\n    /**\n     * Get the position at which the snapshot was taken. {@link Aeron#NULL_VALUE} if no snapshot for recovery.\n     *\n     * @param counters  to search within.\n     * @param counterId for the active recovery counter.\n     * @return the log position if found otherwise {@link Aeron#NULL_VALUE}.\n     */\n    public static long getLogPosition(final CountersReader counters, final int counterId)\n    {\n        final DirectBuffer buffer = counters.metaDataBuffer();\n\n        if (counters.getCounterState(counterId) == RECORD_ALLOCATED &&\n            counters.getCounterTypeId(counterId) == RECOVERY_STATE_TYPE_ID)\n        {\n            return buffer.getLong(CountersReader.metaDataOffset(counterId) + KEY_OFFSET + LOG_POSITION_OFFSET);\n        }\n\n        return NULL_VALUE;\n    }\n\n    /**\n     * Get the timestamp at the beginning of recovery. {@link Aeron#NULL_VALUE} if no snapshot for recovery.\n     *\n     * @param counters  to search within.\n     * @param counterId for the active recovery counter.\n     * @return the timestamp if found otherwise {@link Aeron#NULL_VALUE}.\n     */\n    public static long getTimestamp(final CountersReader counters, final int counterId)\n    {\n        final DirectBuffer buffer = counters.metaDataBuffer();\n\n        if (counters.getCounterState(counterId) == RECORD_ALLOCATED &&\n            counters.getCounterTypeId(counterId) == RECOVERY_STATE_TYPE_ID)\n        {\n            return buffer.getLong(CountersReader.metaDataOffset(counterId) + KEY_OFFSET + TIMESTAMP_OFFSET);\n        }\n\n        return NULL_VALUE;\n    }\n\n    /**\n     * Get the recording id of the snapshot for a service.\n     *\n     * @param counters  to search within.\n     * @param counterId for the active recovery counter.\n     * @param serviceId for the snapshot required.\n     * @return the count of replay terms if found otherwise {@link Aeron#NULL_VALUE}.\n     */\n    public static long getSnapshotRecordingId(final CountersReader counters, final int counterId, final int serviceId)\n    {\n        final DirectBuffer buffer = counters.metaDataBuffer();\n\n        if (counters.getCounterState(counterId) == RECORD_ALLOCATED &&\n            counters.getCounterTypeId(counterId) == RECOVERY_STATE_TYPE_ID)\n        {\n            final int recordOffset = CountersReader.metaDataOffset(counterId);\n\n            final int serviceCount = buffer.getInt(recordOffset + KEY_OFFSET + SERVICE_COUNT_OFFSET);\n            if (serviceId < 0 || serviceId >= serviceCount)\n            {\n                throw new ClusterException(\"invalid serviceId \" + serviceId + \" for count of \" + serviceCount);\n            }\n\n            return buffer.getLong(\n                recordOffset + KEY_OFFSET + SNAPSHOT_RECORDING_IDS_OFFSET + (serviceId * SIZE_OF_LONG));\n        }\n\n        throw new ClusterException(\"active counter not found \" + counterId);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ServiceAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\n\nfinal class ServiceAdapter implements AutoCloseable\n{\n    private static final int FRAGMENT_LIMIT = 1;\n\n    private final Subscription subscription;\n    private final ClusteredServiceAgent clusteredServiceAgent;\n    private final FragmentAssembler fragmentAssembler = new FragmentAssembler(this::onFragment);\n\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final JoinLogDecoder joinLogDecoder = new JoinLogDecoder();\n    private final RequestServiceAckDecoder requestServiceAckDecoder = new RequestServiceAckDecoder();\n    private final ServiceTerminationPositionDecoder serviceTerminationPositionDecoder =\n        new ServiceTerminationPositionDecoder();\n\n    ServiceAdapter(final Subscription subscription, final ClusteredServiceAgent clusteredServiceAgent)\n    {\n        this.subscription = subscription;\n        this.clusteredServiceAgent = clusteredServiceAgent;\n    }\n\n    public void close()\n    {\n        CloseHelper.close(subscription);\n    }\n\n    int poll()\n    {\n        return subscription.poll(fragmentAssembler, FRAGMENT_LIMIT);\n    }\n\n    private void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)\n        {\n            throw new ClusterException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (messageHeaderDecoder.templateId())\n        {\n            case JoinLogDecoder.TEMPLATE_ID:\n                joinLogDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                clusteredServiceAgent.onJoinLog(\n                    joinLogDecoder.logPosition(),\n                    joinLogDecoder.maxLogPosition(),\n                    joinLogDecoder.memberId(),\n                    joinLogDecoder.logSessionId(),\n                    joinLogDecoder.logStreamId(),\n                    joinLogDecoder.isStartup() == BooleanType.TRUE,\n                    Cluster.Role.get(joinLogDecoder.role()),\n                    joinLogDecoder.isStandby() == BooleanType.TRUE,\n                    joinLogDecoder.logChannel());\n                break;\n\n            case ServiceTerminationPositionDecoder.TEMPLATE_ID:\n                serviceTerminationPositionDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                clusteredServiceAgent.onServiceTerminationPosition(serviceTerminationPositionDecoder.logPosition());\n                break;\n\n            case RequestServiceAckDecoder.TEMPLATE_ID:\n                requestServiceAckDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                clusteredServiceAgent.onRequestServiceAck(requestServiceAckDecoder.logPosition());\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ServiceSnapshotLoader.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.Image;\nimport io.aeron.ImageControlledFragmentAssembler;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.SNAPSHOT_TYPE_ID;\n\nfinal class ServiceSnapshotLoader implements ControlledFragmentHandler\n{\n    private static final int FRAGMENT_LIMIT = 10;\n\n    private boolean inSnapshot = false;\n    private boolean isDone = false;\n    private int appVersion;\n    private TimeUnit timeUnit;\n\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final SnapshotMarkerDecoder snapshotMarkerDecoder = new SnapshotMarkerDecoder();\n    private final ClientSessionDecoder clientSessionDecoder = new ClientSessionDecoder();\n    private final ImageControlledFragmentAssembler fragmentAssembler = new ImageControlledFragmentAssembler(this);\n    private final Image image;\n    private final ClusteredServiceAgent agent;\n\n    ServiceSnapshotLoader(final Image image, final ClusteredServiceAgent agent)\n    {\n        this.image = image;\n        this.agent = agent;\n    }\n\n    boolean isDone()\n    {\n        return isDone;\n    }\n\n    int appVersion()\n    {\n        return appVersion;\n    }\n\n    TimeUnit timeUnit()\n    {\n        return timeUnit;\n    }\n\n    int poll()\n    {\n        return image.controlledPoll(fragmentAssembler, FRAGMENT_LIMIT);\n    }\n\n    public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        messageHeaderDecoder.wrap(buffer, offset);\n\n        final int schemaId = messageHeaderDecoder.schemaId();\n        if (MessageHeaderDecoder.SCHEMA_ID != schemaId)\n        {\n            throw new ClusterException(\"expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=\" + schemaId);\n        }\n\n        switch (messageHeaderDecoder.templateId())\n        {\n            case SnapshotMarkerDecoder.TEMPLATE_ID:\n                snapshotMarkerDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long typeId = snapshotMarkerDecoder.typeId();\n                if (SNAPSHOT_TYPE_ID != typeId)\n                {\n                    throw new ClusterException(\"unexpected snapshot type: \" + typeId);\n                }\n\n                switch (snapshotMarkerDecoder.mark())\n                {\n                    case BEGIN:\n                        if (inSnapshot)\n                        {\n                            throw new ClusterException(\"already in snapshot\");\n                        }\n                        inSnapshot = true;\n                        appVersion = snapshotMarkerDecoder.appVersion();\n                        timeUnit = ClusterClock.map(snapshotMarkerDecoder.timeUnit());\n                        return Action.CONTINUE;\n\n                    case END:\n                        if (!inSnapshot)\n                        {\n                            throw new ClusterException(\"missing begin snapshot\");\n                        }\n                        isDone = true;\n                        return Action.BREAK;\n\n                    case SECTION:\n                    case NULL_VAL:\n                        break;\n                }\n                break;\n\n            case ClientSessionDecoder.TEMPLATE_ID:\n                clientSessionDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final String responseChannel = clientSessionDecoder.responseChannel();\n                final byte[] encodedPrincipal = new byte[clientSessionDecoder.encodedPrincipalLength()];\n                clientSessionDecoder.getEncodedPrincipal(encodedPrincipal, 0, encodedPrincipal.length);\n\n                agent.addSession(\n                    clientSessionDecoder.clusterSessionId(),\n                    clientSessionDecoder.responseStreamId(),\n                    responseChannel,\n                    encodedPrincipal);\n                break;\n        }\n\n        return Action.CONTINUE;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/ServiceSnapshotTaker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.cluster.codecs.ClientSessionEncoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.IdleStrategy;\n\nfinal class ServiceSnapshotTaker extends SnapshotTaker\n{\n    private final ExpandableArrayBuffer offerBuffer = new ExpandableArrayBuffer(1024);\n    private final ClientSessionEncoder clientSessionEncoder = new ClientSessionEncoder();\n\n    ServiceSnapshotTaker(\n        final ExclusivePublication publication, final IdleStrategy idleStrategy, final AgentInvoker aeronClientInvoker)\n    {\n        super(publication, idleStrategy, aeronClientInvoker);\n    }\n\n    void snapshotSession(final ClientSession session)\n    {\n        final String responseChannel = session.responseChannel();\n        final byte[] encodedPrincipal = session.encodedPrincipal();\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ClientSessionEncoder.BLOCK_LENGTH +\n            ClientSessionEncoder.responseChannelHeaderLength() + responseChannel.length() +\n            ClientSessionEncoder.encodedPrincipalHeaderLength() + encodedPrincipal.length;\n        if (length <= publication.maxPayloadLength())\n        {\n            idleStrategy.reset();\n            while (true)\n            {\n                final long result = publication.tryClaim(length, bufferClaim);\n                if (result > 0)\n                {\n                    final MutableDirectBuffer buffer = bufferClaim.buffer();\n                    final int offset = bufferClaim.offset();\n\n                    encodeSession(session, responseChannel, encodedPrincipal, buffer, offset);\n                    bufferClaim.commit();\n                    break;\n                }\n\n                checkResultAndIdle(result);\n            }\n        }\n        else\n        {\n            final int offset = 0;\n            encodeSession(session, responseChannel, encodedPrincipal, offerBuffer, offset);\n            offer(offerBuffer, offset, length);\n        }\n    }\n\n    private void encodeSession(\n        final ClientSession session,\n        final String responseChannel,\n        final byte[] encodedPrincipal,\n        final MutableDirectBuffer buffer,\n        final int offset)\n    {\n        clientSessionEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .clusterSessionId(session.id())\n            .responseStreamId(session.responseStreamId())\n            .responseChannel(responseChannel)\n            .putEncodedPrincipal(encodedPrincipal, 0, encodedPrincipal.length);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/SnapshotDurationTracker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport org.agrona.concurrent.status.AtomicCounter;\n\n\n/**\n * Snapshot duration tracker that tracks maximum snapshot duration and also keeps count of how many times a predefined\n * duration threshold is breached.\n */\npublic class SnapshotDurationTracker\n{\n    private final AtomicCounter maxSnapshotDuration;\n    private final AtomicCounter snapshotDurationThresholdExceededCount;\n    private final long durationThresholdNs;\n    private long snapshotStartTimeNs = Long.MIN_VALUE;\n\n    /**\n     * Create a tracker to track max snapshot duration and breaches of a threshold.\n     *\n     * @param maxSnapshotDuration                    counter for tracking.\n     * @param snapshotDurationThresholdExceededCount counter for tracking.\n     * @param durationThresholdNs                    to use for tracking breaches.\n     */\n    public SnapshotDurationTracker(\n        final AtomicCounter maxSnapshotDuration,\n        final AtomicCounter snapshotDurationThresholdExceededCount,\n        final long durationThresholdNs)\n    {\n        this.maxSnapshotDuration = maxSnapshotDuration;\n        this.snapshotDurationThresholdExceededCount = snapshotDurationThresholdExceededCount;\n        this.durationThresholdNs = durationThresholdNs;\n    }\n\n    /**\n     * Get max snapshot duration counter.\n     *\n     * @return max snapshot duration counter.\n     */\n    public AtomicCounter maxSnapshotDuration()\n    {\n        return maxSnapshotDuration;\n    }\n\n    /**\n     * Get counter tracking number of times {@link SnapshotDurationTracker#durationThresholdNs} was exceeded.\n     *\n     * @return duration threshold exceeded counter.\n     */\n    public AtomicCounter snapshotDurationThresholdExceededCount()\n    {\n        return snapshotDurationThresholdExceededCount;\n    }\n\n    /**\n     * Called when snapshotting has started.\n     *\n     * @param timeNanos snapshot start time in nanoseconds.\n     */\n    public void onSnapshotBegin(final long timeNanos)\n    {\n        snapshotStartTimeNs = timeNanos;\n    }\n\n    /**\n     * Called when snapshot has been taken.\n     *\n     * @param timeNanos snapshot end time in nanoseconds.\n     */\n    public void onSnapshotEnd(final long timeNanos)\n    {\n        if (snapshotStartTimeNs != Long.MIN_VALUE)\n        {\n            final long snapshotDurationNs = timeNanos - snapshotStartTimeNs;\n\n            if (snapshotDurationNs > durationThresholdNs)\n            {\n                snapshotDurationThresholdExceededCount.increment();\n            }\n\n            maxSnapshotDuration.proposeMax(snapshotDurationNs);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"SnapshotDurationTracker{\" +\n            \"maxSnapshotDuration=\" + maxSnapshotDuration +\n            \", snapshotDurationThresholdExceededCount=\" + snapshotDurationThresholdExceededCount +\n            \", durationThresholdNs=\" + durationThresholdNs +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/SnapshotTaker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Publication;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.SnapshotMark;\nimport io.aeron.cluster.codecs.SnapshotMarkerEncoder;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.IdleStrategy;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Base class of common functions required to take a snapshot of cluster state.\n */\npublic class SnapshotTaker\n{\n    /**\n     * Reusable {@link BufferClaim} to avoid allocation.\n     */\n    protected final BufferClaim bufferClaim = new BufferClaim();\n\n    /**\n     * Reusable {@link MessageHeaderEncoder} to avoid allocation.\n     */\n    protected final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n\n    /**\n     * {@link Publication} to which the snapshot will be written.\n     */\n    protected final ExclusivePublication publication;\n\n    /**\n     * {@link IdleStrategy} to be called when back pressure is propagated from the {@link #publication}.\n     */\n    protected final IdleStrategy idleStrategy;\n\n    private static final int ENCODED_MARKER_LENGTH =\n        MessageHeaderEncoder.ENCODED_LENGTH + SnapshotMarkerEncoder.BLOCK_LENGTH;\n    private final AgentInvoker aeronAgentInvoker;\n    private final SnapshotMarkerEncoder snapshotMarkerEncoder = new SnapshotMarkerEncoder();\n\n    /**\n     * Construct a {@link SnapshotTaker} which will encode the snapshot to a publication.\n     *\n     * @param publication       into which the snapshot will be encoded.\n     * @param idleStrategy      to call when the publication is back pressured.\n     * @param aeronAgentInvoker to call when idling so it stays active.\n     */\n    public SnapshotTaker(\n        final ExclusivePublication publication, final IdleStrategy idleStrategy, final AgentInvoker aeronAgentInvoker)\n    {\n        this.publication = publication;\n        this.idleStrategy = idleStrategy;\n        this.aeronAgentInvoker = aeronAgentInvoker;\n    }\n\n    /**\n     * Mark the beginning of the encoded snapshot.\n     *\n     * @param snapshotTypeId   type to identify snapshot within a cluster.\n     * @param logPosition      at which the snapshot was taken.\n     * @param leadershipTermId at which the snapshot was taken.\n     * @param snapshotIndex    so the snapshot can be sectioned.\n     * @param timeUnit         of the cluster timestamps stored in the snapshot.\n     * @param appVersion       associated with the snapshot from {@link ClusteredServiceContainer.Context#appVersion()}.\n     */\n    public void markBegin(\n        final long snapshotTypeId,\n        final long logPosition,\n        final long leadershipTermId,\n        final int snapshotIndex,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n        markSnapshot(\n            snapshotTypeId, logPosition, leadershipTermId, snapshotIndex, SnapshotMark.BEGIN, timeUnit, appVersion);\n    }\n\n    /**\n     * Mark the end of the encoded snapshot.\n     *\n     * @param snapshotTypeId   type to identify snapshot within a cluster.\n     * @param logPosition      at which the snapshot was taken.\n     * @param leadershipTermId at which the snapshot was taken.\n     * @param snapshotIndex    so the snapshot can be sectioned.\n     * @param timeUnit         of the cluster timestamps stored in the snapshot.\n     * @param appVersion       associated with the snapshot from {@link ClusteredServiceContainer.Context#appVersion()}.\n     */\n    public void markEnd(\n        final long snapshotTypeId,\n        final long logPosition,\n        final long leadershipTermId,\n        final int snapshotIndex,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n        markSnapshot(\n            snapshotTypeId, logPosition, leadershipTermId, snapshotIndex, SnapshotMark.END, timeUnit, appVersion);\n    }\n\n    /**\n     * Generically {@link SnapshotMark} a snapshot.\n     *\n     * @param snapshotTypeId   type to identify snapshot within a cluster.\n     * @param logPosition      at which the snapshot was taken.\n     * @param leadershipTermId at which the snapshot was taken.\n     * @param snapshotIndex    so the snapshot can be sectioned.\n     * @param snapshotMark     which specifies the type of snapshot mark.\n     * @param timeUnit         of the cluster timestamps stored in the snapshot.\n     * @param appVersion       associated with the snapshot from {@link ClusteredServiceContainer.Context#appVersion()}.\n     */\n    public void markSnapshot(\n        final long snapshotTypeId,\n        final long logPosition,\n        final long leadershipTermId,\n        final int snapshotIndex,\n        final SnapshotMark snapshotMark,\n        final TimeUnit timeUnit,\n        final int appVersion)\n    {\n        idleStrategy.reset();\n        while (true)\n        {\n            final long result = publication.tryClaim(ENCODED_MARKER_LENGTH, bufferClaim);\n            if (result > 0)\n            {\n                snapshotMarkerEncoder\n                    .wrapAndApplyHeader(bufferClaim.buffer(), bufferClaim.offset(), messageHeaderEncoder)\n                    .typeId(snapshotTypeId)\n                    .logPosition(logPosition)\n                    .leadershipTermId(leadershipTermId)\n                    .index(snapshotIndex)\n                    .mark(snapshotMark)\n                    .timeUnit(ClusterClock.map(timeUnit))\n                    .appVersion(appVersion);\n\n                bufferClaim.commit();\n                break;\n            }\n\n            checkResultAndIdle(result);\n        }\n    }\n\n    /**\n     * Check for thread interrupt and throw an {@link AgentTerminationException} if interrupted.\n     */\n    protected static void checkInterruptStatus()\n    {\n        if (Thread.currentThread().isInterrupted())\n        {\n            throw new AgentTerminationException(\"interrupted\");\n        }\n    }\n\n    /**\n     * Check the result of offering to a publication when writing a snapshot.\n     *\n     * @param position    of an offer or try claim to a publication.\n     * @param publication on which the offer or try claim was attempted.\n     */\n    protected static void checkResult(final long position, final Publication publication)\n    {\n        if (Publication.NOT_CONNECTED == position)\n        {\n            throw new ClusterException(\"publication is not connected\");\n        }\n\n        if (Publication.CLOSED == position)\n        {\n            throw new ClusterException(\"publication is closed\");\n        }\n\n        if (Publication.MAX_POSITION_EXCEEDED == position)\n        {\n            throw new ClusterException(\"publication at max position: term-length=\" + publication.termBufferLength());\n        }\n    }\n\n    /**\n     * Check the result of offering to a publication when writing a snapshot and then idle after invoking the client\n     * agent if necessary.\n     *\n     * @param position of an offer or try claim to a publication.\n     */\n    protected void checkResultAndIdle(final long position)\n    {\n        checkResult(position, publication);\n        checkInterruptStatus();\n        invokeAgentClient();\n        idleStrategy.idle();\n    }\n\n    /**\n     * Invoke the Aeron client agent if necessary.\n     */\n    protected void invokeAgentClient()\n    {\n        if (null != aeronAgentInvoker)\n        {\n            aeronAgentInvoker.invoke();\n        }\n    }\n\n    /**\n     * Helper method to offer a message into the snapshot publication.\n     *\n     * @param buffer containing the message.\n     * @param offset at which the message begins.\n     * @param length of the message.\n     */\n    protected final void offer(final DirectBuffer buffer, final int offset, final int length)\n    {\n        idleStrategy.reset();\n        while (true)\n        {\n            final long result = publication.offer(buffer, offset, length);\n            if (result > 0)\n            {\n                break;\n            }\n\n            checkResultAndIdle(result);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/main/java/io/aeron/cluster/service/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Services implement {@link io.aeron.cluster.service.ClusteredService} and have their lifecycle managed by the\n * {@link io.aeron.cluster.service.Cluster}. This is where application logic is contained.\n */\npackage io.aeron.cluster.service;"
  },
  {
    "path": "aeron-cluster/src/main/resources/cluster/aeron-cluster-codecs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<sbe:messageSchema xmlns:sbe=\"http://fixprotocol.io/2016/sbe\"\n                   package=\"io.aeron.cluster.codecs\"\n                   id=\"111\"\n                   version=\"16\"\n                   semanticVersion=\"5.4\"\n                   description=\"Message Codecs for communicating with, and within, an Aeron Cluster.\"\n                   byteOrder=\"littleEndian\">\n    <types>\n        <composite name=\"messageHeader\" description=\"Message identifiers and length of message root.\">\n            <type name=\"blockLength\"    primitiveType=\"uint16\"/>\n            <type name=\"templateId\"     primitiveType=\"uint16\"/>\n            <type name=\"schemaId\"       primitiveType=\"uint16\"/>\n            <type name=\"version\"        primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"groupSizeEncoding\" description=\"Repeating group dimensions.\">\n            <type name=\"blockLength\"    primitiveType=\"uint16\"/>\n            <type name=\"numInGroup\"     primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"varAsciiEncoding\" description=\"Variable length ASCII string header.\">\n            <type name=\"length\"         primitiveType=\"uint32\" maxValue=\"1073741824\"/>\n            <type name=\"varData\"        primitiveType=\"uint8\" length=\"0\" characterEncoding=\"US-ASCII\"/>\n        </composite>\n        <composite name=\"varDataEncoding\" description=\"Variable length data blob header.\">\n            <type name=\"length\"         primitiveType=\"uint32\" maxValue=\"1073741824\"/>\n            <type name=\"varData\"        primitiveType=\"uint8\" length=\"0\"/>\n        </composite>\n        <enum name=\"BooleanType\" encodingType=\"int32\" description=\"Language independent boolean type.\">\n            <validValue name=\"FALSE\" description=\"Language independent boolean false.\">0</validValue>\n            <validValue name=\"TRUE\" description=\"Language independent boolean true.\">1</validValue>\n        </enum>\n        <enum name=\"EventCode\" encodingType=\"int32\" description=\"Type of event for a response.\">\n            <validValue name=\"OK\" description=\"Operation was successful\">0</validValue>\n            <validValue name=\"ERROR\" description=\"Error occurred during operation.\">1</validValue>\n            <validValue name=\"REDIRECT\" description=\"Redirect to cluster leader.\">2</validValue>\n            <validValue name=\"AUTHENTICATION_REJECTED\" description=\"Authentication credentials rejected.\">3</validValue>\n            <validValue name=\"CLOSED\" description=\"Session has been closed.\">4</validValue>\n        </enum>\n        <enum name=\"CloseReason\" encodingType=\"int32\" description=\"Reason why a session was closed.\">\n            <validValue name=\"CLIENT_ACTION\" description=\"Client closed the session.\">0</validValue>\n            <validValue name=\"SERVICE_ACTION\" description=\"Service closed the session.\">1</validValue>\n            <validValue name=\"TIMEOUT\" description=\"Session timed out due to inactivity.\">2</validValue>\n        </enum>\n        <enum name=\"ClusterAction\" encodingType=\"int32\" description=\"Action to be taken by cluster nodes.\">\n            <validValue name=\"SUSPEND\" description=\"Suspend ingress to the cluster.\">0</validValue>\n            <validValue name=\"RESUME\" description=\"Resume ingress to the cluster.\">1</validValue>\n            <validValue name=\"SNAPSHOT\" description=\"Snapshot state in the cluster.\">2</validValue>\n        </enum>\n        <enum name=\"SnapshotMark\" encodingType=\"int32\" description=\"Mark within a snapshot.\">\n            <validValue name=\"BEGIN\" description=\"Begin marker for a snapshot.\">0</validValue>\n            <validValue name=\"SECTION\" description=\"Section marker for a snapshot.\">1</validValue>\n            <validValue name=\"END\" description=\"End marker for a snapshot.\">2</validValue>\n        </enum>\n        <enum name=\"ChangeType\" encodingType=\"int32\" description=\"Type of Cluster Change Event.\">\n            <validValue name=\"JOIN\" description=\"Join cluster as dynamic member.\">0</validValue>\n            <validValue name=\"QUIT\" description=\"Quit cluster as dynamic member.\">1</validValue>\n        </enum>\n        <enum name=\"ClusterTimeUnit\" encodingType=\"int32\" description=\"Type the time unit used for timestamps.\">\n            <validValue name=\"MILLIS\" description=\"Time unit of milliseconds for timestamps.\">0</validValue>\n            <validValue name=\"MICROS\" description=\"Time unit of microseconds for timestamps.\">1</validValue>\n            <validValue name=\"NANOS\" description=\"Time unit of nanoseconds for timestamps.\">2</validValue>\n        </enum>\n        <enum name=\"AdminRequestType\" encodingType=\"int32\" description=\"Admin command to execute in the cluster.\">\n            <validValue name=\"SNAPSHOT\" description=\"Command to snapshot state in the cluster.\">0</validValue>\n        </enum>\n        <enum name=\"AdminResponseCode\" encodingType=\"int32\" description=\"Response code for an admin command request.\">\n            <validValue name=\"OK\" description=\"Command was submitted or executed successfully.\">0</validValue>\n            <validValue name=\"ERROR\" description=\"An error occurred during admin operation.\">1</validValue>\n            <validValue name=\"UNAUTHORISED_ACCESS\" description=\"Admin request was not authorised.\">2</validValue>\n        </enum>\n        <type name=\"time_t\"    primitiveType=\"int64\" description=\"Epoch time since 1 Jan 1970 UTC.\"/>\n        <type name=\"version_t\" primitiveType=\"int32\" description=\"Protocol or application suite version.\"\n              presence=\"optional\" nullValue=\"0\" minValue=\"1\" maxValue=\"16777215\"/>\n        <type name=\"capacity_t\" primitiveType=\"int32\" description=\"Capacity of a container.\"\n              presence=\"optional\" nullValue=\"0\"/>\n    </types>\n\n<!--\n    Cluster Session Protocol\n    ========================\n\n    Session Protocol:\n        -> session-connect, *[session-message | session-keep-alive | admin-request], session-close\n                          \\\n        <-                 +session-event, *[session-event | session-message | new-leader-event | admin-response]\n\n    1. Session Connect\n        - An attempt is made to establish a connection with the cluster via multicast or multi-destination-cast.\n        - If successful, an OK session-event will be sent in the return path with the list of endpoint destinations\n          for the cluster members. The leader will come first.\n        - Followers can respond with a REDIRECT code and a list of member endpoint destinations in the\n          detail that will have the leader first.\n        - If a change of leader occurs mid-session then a new-leader-event will be sent from the new leader.\n          Ideally, a client should send a session-keep-alive message as the first message to the new leader.\n\n    2. Ingress/Egress Messages - Session Messages which make up application protocol.\n        - Messages are sent to a clustered service with a SessionMessageHeader followed by an application payload.\n        - The service may emit responses and events with a SessionMessageHeader followed by an application payload.\n        - The application protocol is the user defined messages for interacting with the service being clustered.\n        - Keep alive messages should be sent when insufficient ingress messages are sent to keep the session open.\n\n    3. Session Close\n        - A cluster session can be closed with a CloseSessionRequest.\n        - If the session is not explicitly closed then it will close following a timeout when the connection goes away.\n\n    Cluster Authentication Protocol\n    ===============================\n\n    Connect protocol can occur in multiple valid sequences\n\n    1. Authenticated via Session Connect\n        -> session-connect, ...\n                          \\\n        <-                 *session-event (OK), ...\n\n    2. Authenticated via Challenge Response\n        -> session-connect           challenge-response, ...\n                          \\         /                  \\\n        <-                 challenge                    session-event (OK), ...\n\n    3. Rejected via Session Connect\n        -> session-connect\n                          \\\n        <-                 *session-event (AUTHENTICATION_REJECTED)\n\n    4. Rejected via Challenge Response\n        -> session-connect           challenge-response\n                          \\         /                  \\\n        <-                 challenge                     session-event (AUTHENTICATION_REJECTED)\n-->\n\n    <sbe:message name=\"SessionMessageHeader\"\n                 id=\"1\"\n                 description=\"Header for application session messages between clients and cluster services.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"clusterSessionId\"         id=\"2\" type=\"int64\"/>\n        <field name=\"timestamp\"                id=\"3\" type=\"time_t\"/>\n    </sbe:message>\n\n    <sbe:message name=\"SessionEvent\"\n                 id=\"2\"\n                 description=\"Response to a connect request or async event, details will be empty if code is OK.\">\n        <field name=\"clusterSessionId\"         id=\"1\" type=\"int64\"\n               description=\"Session id for a multiplexed session over a shared connection, i.e. same Image.\"/>\n        <field name=\"correlationId\"            id=\"2\" type=\"int64\"\n               description=\"Request correlation id with which this event is associated.\"/>\n        <field name=\"leadershipTermId\"         id=\"3\" type=\"int64\"\n               description=\"Current leadership term identifier.\"/>\n        <field name=\"leaderMemberId\"           id=\"4\" type=\"int32\"\n               description=\"Current leader of the cluster.\"/>\n        <field name=\"code\"                     id=\"5\" type=\"EventCode\"\n               description=\"Code type of the response.\"/>\n        <field name=\"version\"                  id=\"6\" type=\"version_t\" presence=\"optional\" sinceVersion=\"6\"\n               description=\"Protocol version for the server using semantic version form.\"/>\n        <field name=\"leaderHeartbeatTimeoutNs\" id=\"8\" type=\"int64\" presence=\"optional\" sinceVersion=\"13\"\n               description=\"Leader heartbeat timeout in nanoseconds as configured on the leader.\"/>\n        <data  name=\"detail\"                   id=\"7\" type=\"varAsciiEncoding\"\n               description=\"Further detail such as an error message or list of cluster ingress endpoints.\"/>\n    </sbe:message>\n\n    <sbe:message name=\"SessionConnectRequest\"\n                 id=\"3\"\n                 description=\"Connect to the cluster and if successful then open a session.\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"responseStreamId\"         id=\"2\" type=\"int32\"/>\n        <field name=\"version\"                  id=\"3\" type=\"version_t\" presence=\"optional\" sinceVersion=\"2\"/>\n        <data  name=\"responseChannel\"          id=\"4\" type=\"varAsciiEncoding\"/>\n        <data  name=\"encodedCredentials\"       id=\"5\" type=\"varDataEncoding\"/>\n        <data  name=\"clientInfo\"               id=\"6\" type=\"varAsciiEncoding\" sinceVersion=\"14\"/>\n    </sbe:message>\n\n    <sbe:message name=\"SessionCloseRequest\"\n                 id=\"4\"\n                 description=\"Close an open cluster session.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"clusterSessionId\"         id=\"2\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"SessionKeepAlive\"\n                 id=\"5\"\n                 description=\"Keep a cluster session open by indicating the client is alive.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"clusterSessionId\"         id=\"2\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"NewLeaderEvent\"\n                 id=\"6\"\n                 description=\"Event to indicate a new leader has been elected for the cluster.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"clusterSessionId\"         id=\"2\" type=\"int64\"/>\n        <field name=\"leaderMemberId\"           id=\"3\" type=\"int32\"/>\n        <data  name=\"ingressEndpoints\"         id=\"4\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"Challenge\"\n                 id=\"7\"\n                 description=\"Challenge the client to provide credentials.\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"clusterSessionId\"         id=\"2\" type=\"int64\"/>\n        <data  name=\"encodedChallenge\"         id=\"3\" type=\"varDataEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ChallengeResponse\"\n                 id=\"8\"\n                 description=\"Response to a cluster challenge with credentials.\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"clusterSessionId\"         id=\"2\" type=\"int64\"/>\n        <data  name=\"encodedCredentials\"       id=\"3\" type=\"varDataEncoding\"/>\n    </sbe:message>\n\n<!-- Codecs for messages that get encoded into the log by the Consensus Module -->\n\n    <sbe:message name=\"TimerEvent\"\n                 id=\"20\"\n                 description=\"Timer triggered event as the result of a deadline passing on a registered timer.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"            id=\"2\" type=\"int64\"/>\n        <field name=\"timestamp\"                id=\"3\" type=\"time_t\"/>\n    </sbe:message>\n\n    <sbe:message name=\"SessionOpenEvent\"\n                 id=\"21\"\n                 description=\"Event for the state machine to notify a session has been opened.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"            id=\"2\" type=\"int64\"/>\n        <field name=\"clusterSessionId\"         id=\"3\" type=\"int64\"/>\n        <field name=\"timestamp\"                id=\"4\" type=\"time_t\"/>\n        <field name=\"responseStreamId\"         id=\"6\" type=\"int32\"/>\n        <data  name=\"responseChannel\"          id=\"7\" type=\"varAsciiEncoding\"/>\n        <data  name=\"encodedPrincipal\"         id=\"8\" type=\"varDataEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"SessionCloseEvent\"\n                 id=\"22\"\n                 description=\"Event for the state machine to notify a session has been closed.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"clusterSessionId\"         id=\"2\" type=\"int64\"/>\n        <field name=\"timestamp\"                id=\"3\" type=\"time_t\"/>\n        <field name=\"closeReason\"              id=\"4\" type=\"CloseReason\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ClusterActionRequest\"\n                 id=\"23\"\n                 description=\"Request an action be taken by the service.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"timestamp\"                id=\"3\" type=\"time_t\"/>\n        <field name=\"action\"                   id=\"4\" type=\"ClusterAction\"/>\n        <field name=\"flags\"                    id=\"5\" type=\"int32\" sinceVersion=\"11\" presence=\"optional\"/>\n    </sbe:message>\n\n    <sbe:message name=\"NewLeadershipTermEvent\"\n                 id=\"24\"\n                 description=\"Event for the start of a new leadership term.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"timestamp\"                id=\"3\" type=\"time_t\"/>\n        <field name=\"termBaseLogPosition\"      id=\"4\" type=\"int64\"/>\n        <field name=\"leaderMemberId\"           id=\"5\" type=\"int32\"/>\n        <field name=\"logSessionId\"             id=\"6\" type=\"int32\"/>\n        <field name=\"timeUnit\"                 id=\"7\" type=\"ClusterTimeUnit\" sinceVersion=\"4\" presence=\"optional\"/>\n        <field name=\"appVersion\"               id=\"8\" type=\"version_t\" sinceVersion=\"4\" presence=\"optional\"/>\n    </sbe:message>\n\n    <sbe:message name=\"MembershipChangeEvent\"\n                 id=\"25\"\n                 description=\"Event for the change of the cluster membership that affects the cluster size.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"timestamp\"                id=\"3\" type=\"time_t\"/>\n        <field name=\"leaderMemberId\"           id=\"4\" type=\"int32\"/>\n        <field name=\"clusterSize\"              id=\"5\" type=\"int32\"/>\n        <field name=\"changeType\"               id=\"6\" type=\"ChangeType\"/>\n        <field name=\"memberId\"                 id=\"7\" type=\"int32\"/>\n        <data  name=\"clusterMembers\"           id=\"8\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"AdminRequest\"\n                 id=\"26\"\n                 description=\"Request to execute an admin command in the cluster.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"\n               description=\"Current leadership term identifier.\"/>\n        <field name=\"clusterSessionId\"         id=\"2\" type=\"int64\"\n               description=\"Session id of the cluster client which made the request.\"/>\n        <field name=\"correlationId\"            id=\"3\" type=\"int64\"\n               description=\"Correlation id with which the response could be associated with.\"/>\n        <field name=\"requestType\"              id=\"4\" type=\"AdminRequestType\"\n               description=\"Type of the request to be executed.\"/>\n        <data name=\"payload\"                   id=\"5\" type=\"varDataEncoding\"\n              description=\"An optional request payload, can be empty.\"/>\n    </sbe:message>\n\n    <sbe:message name=\"AdminResponse\"\n                 id=\"27\"\n                 description=\"Response to the admin request.\">\n        <field name=\"clusterSessionId\"         id=\"1\" type=\"int64\"\n               description=\"Session id of the cluster client which made the request.\"/>\n        <field name=\"correlationId\"            id=\"2\" type=\"int64\"\n               description=\"AdminRequest correlation id with which this response is associated.\"/>\n        <field name=\"requestType\"              id=\"3\" type=\"AdminRequestType\"\n               description=\"A type of the admin request that was executed.\"/>\n        <field name=\"responseCode\"             id=\"4\" type=\"AdminResponseCode\"\n               description=\"A response code describing the result.\"/>\n        <data name=\"message\"                   id=\"5\" type=\"varAsciiEncoding\"\n              description=\"A response message (e.g. an error message).\"/>\n        <data name=\"payload\"                   id=\"6\" type=\"varDataEncoding\"\n              description=\"An optional response payload, can be empty.\"/>\n    </sbe:message>\n\n<!-- Messages for the control protocol between clustered services, or cluster tool, and the Consensus Module -->\n\n    <sbe:message name=\"CloseSession\"\n                 id=\"30\"\n                 description=\"Service instructing that a session be closed.\">\n        <field name=\"clusterSessionId\"         id=\"1\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ScheduleTimer\"\n                 id=\"31\"\n                 description=\"Schedule a timer event to or reschedule for an unexpired correlation id.\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"deadline\"                 id=\"2\" type=\"time_t\"/>\n    </sbe:message>\n\n    <sbe:message name=\"CancelTimer\"\n                 id=\"32\"\n                 description=\"Cancel a scheduled timer event.\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ServiceAck\"\n                 id=\"33\"\n                 description=\"Service acknowledging that it has reached a position or taken an action.\">\n        <field name=\"logPosition\"              id=\"1\" type=\"int64\"/>\n        <field name=\"timestamp\"                id=\"2\" type=\"time_t\"/>\n        <field name=\"ackId\"                    id=\"3\" type=\"int64\"/>\n        <field name=\"relevantId\"               id=\"4\" type=\"int64\"/>\n        <field name=\"serviceId\"                id=\"5\" type=\"int32\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ClusterMembersQuery\"\n                 id=\"34\"\n                 description=\"Request list of cluster members and passive followers to be sent.\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"extended\"                 id=\"2\" type=\"BooleanType\" sinceVersion=\"5\" presence=\"optional\"/>\n    </sbe:message>\n\n    <sbe:message name=\"RemoveMember\"\n                 id=\"35\"\n                 description=\"Remove a cluster member either as normal member or passive member.\"\n                 deprecated=\"12\">\n        <field name=\"memberId\"                 id=\"1\" type=\"int32\"/>\n        <field name=\"isPassive\"                id=\"2\" type=\"BooleanType\"/>\n    </sbe:message>\n\n    <sbe:message name=\"JoinLog\"\n                 id=\"40\"\n                 description=\"Consensus Module instructing a service to join a log.\">\n        <field name=\"logPosition\"              id=\"1\" type=\"int64\"/>\n        <field name=\"maxLogPosition\"           id=\"2\" type=\"int64\"/>\n        <field name=\"memberId\"                 id=\"3\" type=\"int32\"/>\n        <field name=\"logSessionId\"             id=\"4\" type=\"int32\"/>\n        <field name=\"logStreamId\"              id=\"5\" type=\"int32\"/>\n        <field name=\"isStartup\"                id=\"6\" type=\"BooleanType\"/>\n        <field name=\"role\"                     id=\"7\" type=\"int32\"/>\n        <field name=\"isStandby\"                id=\"9\" type=\"BooleanType\" sinceVersion=\"16\" presence=\"optional\"/>\n        <data  name=\"logChannel\"               id=\"8\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ClusterMembersResponse\"\n                 id=\"41\"\n                 description=\"Cluster Members for normal and passive followers.\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"leaderMemberId\"           id=\"2\" type=\"int32\"/>\n        <data name=\"activeMembers\"             id=\"3\" type=\"varAsciiEncoding\"/>\n        <data name=\"passiveFollowers\"          id=\"4\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ServiceTerminationPosition\"\n                 id=\"42\"\n                 description=\"Consensus Module instructing a service to terminate at a given position.\">\n        <field name=\"logPosition\"              id=\"1\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ClusterMembersExtendedResponse\"\n                 id=\"43\"\n                 description=\"Cluster Members status for active and passive members.\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"currentTimeNs\"            id=\"2\" type=\"time_t\"/>\n        <field name=\"leaderMemberId\"           id=\"3\" type=\"int32\"/>\n        <field name=\"memberId\"                 id=\"4\" type=\"int32\"/>\n        <group name=\"activeMembers\"            id=\"5\" dimensionType=\"groupSizeEncoding\"\n               description=\"Members of the cluster which have voting rights.\">\n            <field name=\"leadershipTermId\"     id=\"6\" type=\"int64\"/>\n            <field name=\"logPosition\"          id=\"7\" type=\"int64\"/>\n            <field name=\"timeOfLastAppendNs\"   id=\"8\" type=\"time_t\"/>\n            <field name=\"memberId\"             id=\"9\" type=\"int32\"/>\n            <data name=\"ingressEndpoint\"       id=\"10\" type=\"varAsciiEncoding\"/>\n            <data name=\"consensusEndpoint\"     id=\"11\" type=\"varAsciiEncoding\"/>\n            <data name=\"logEndpoint\"           id=\"12\" type=\"varAsciiEncoding\"/>\n            <data name=\"catchupEndpoint\"       id=\"13\" type=\"varAsciiEncoding\"/>\n            <data name=\"archiveEndpoint\"       id=\"14\" type=\"varAsciiEncoding\"/>\n        </group>\n        <group name=\"passiveMembers\"           id=\"15\" dimensionType=\"groupSizeEncoding\"\n               description=\"Members of the cluster which do not have voting rights but could become active members.\">\n            <field name=\"leadershipTermId\"     id=\"16\" type=\"int64\"/>\n            <field name=\"logPosition\"          id=\"17\" type=\"int64\"/>\n            <field name=\"timeOfLastAppendNs\"   id=\"18\" type=\"time_t\"/>\n            <field name=\"memberId\"             id=\"19\" type=\"int32\"/>\n            <data name=\"ingressEndpoint\"       id=\"20\" type=\"varAsciiEncoding\"/>\n            <data name=\"consensusEndpoint\"     id=\"21\" type=\"varAsciiEncoding\"/>\n            <data name=\"logEndpoint\"           id=\"22\" type=\"varAsciiEncoding\"/>\n            <data name=\"catchupEndpoint\"       id=\"23\" type=\"varAsciiEncoding\"/>\n            <data name=\"archiveEndpoint\"       id=\"24\" type=\"varAsciiEncoding\"/>\n        </group>\n    </sbe:message>\n\n    <sbe:message name=\"requestServiceAck\"\n                 id=\"108\"\n                 description=\"request an ack from all service container on the same node\">\n        <field name=\"logPosition\" id=\"1\" type=\"int64\"/>\n    </sbe:message>\n\n<!--\n    Cluster Consensus Protocol\n    ==========================\n\n    The leader replicates a log stream to all followers for its term as leader. Leadership term ids are monotonic.\n\n    The followers persist the replicated log locally and send updates to the leader with the highest position they\n    have persisted. The log position is an accumulation of the term positions over the leadership terms.\n    Each leadership term starts with base log position for the accumulated term positions.\n\n    The leader gathers the positions reached by the followers, plus its own locally persisted log, and publishes\n    the highest position for a quorum of the member nodes including itself which can be committed to the state\n    machines. If the quorum position does not progress within a timeout then the latest position will be\n    re-transmitted as a heartbeat from the leader.\n\n    Messages are only valid within the context of a leadership term and all updates are applied as monotonically\n    increasing state.\n-->\n\n    <sbe:message name=\"CanvassPosition\"\n                 id=\"50\"\n                 description=\"Position a follower has appended to their local log when canvassing for leadership.\">\n        <field name=\"logLeadershipTermId\"      id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"leadershipTermId\"         id=\"3\" type=\"int64\"/>\n        <field name=\"followerMemberId\"         id=\"4\" type=\"int32\"/>\n        <field name=\"protocolVersion\"          id=\"5\" type=\"version_t\" sinceVersion=\"9\"/>\n    </sbe:message>\n\n    <sbe:message name=\"RequestVote\"\n                 id=\"51\"\n                 description=\"Request the vote from another member to become the cluster leader.\">\n        <field name=\"logLeadershipTermId\"      id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"candidateTermId\"          id=\"3\" type=\"int64\"/>\n        <field name=\"candidateMemberId\"        id=\"4\" type=\"int32\"/>\n        <field name=\"protocolVersion\"          id=\"5\" type=\"version_t\" sinceVersion=\"9\"/>\n    </sbe:message>\n\n    <sbe:message name=\"Vote\"\n                 id=\"52\"\n                 description=\"Response to a vote request from a follower to the candidate.\">\n        <field name=\"candidateTermId\"          id=\"1\" type=\"int64\"/>\n        <field name=\"logLeadershipTermId\"      id=\"2\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"3\" type=\"int64\"/>\n        <field name=\"candidateMemberId\"        id=\"4\" type=\"int32\"/>\n        <field name=\"followerMemberId\"         id=\"5\" type=\"int32\"/>\n        <field name=\"vote\"                     id=\"6\" type=\"BooleanType\"/>\n    </sbe:message>\n\n    <sbe:message name=\"NewLeadershipTerm\"\n                 id=\"53\"\n                 description=\"A leader has been successfully elected and has begun a new term.\">\n        <field name=\"logLeadershipTermId\"       id=\"1\"  type=\"int64\"/>\n        <field name=\"nextLeadershipTermId\"      id=\"2\"  type=\"int64\"/>\n        <field name=\"nextTermBaseLogPosition\"   id=\"3\"  type=\"int64\"/>\n        <field name=\"nextLogPosition\"           id=\"4\"  type=\"int64\"/>\n        <field name=\"leadershipTermId\"          id=\"5\"  type=\"int64\"/>\n        <field name=\"termBaseLogPosition\"       id=\"6\"  type=\"int64\"/>\n        <field name=\"logPosition\"               id=\"7\"  type=\"int64\"/>\n        <field name=\"leaderRecordingId\"         id=\"8\"  type=\"int64\"/>\n        <field name=\"timestamp\"                 id=\"9\"  type=\"time_t\"/>\n        <field name=\"leaderMemberId\"            id=\"10\" type=\"int32\"/>\n        <field name=\"logSessionId\"              id=\"11\" type=\"int32\"/>\n        <field name=\"appVersion\"                id=\"12\" type=\"version_t\"/>\n        <field name=\"isStartup\"                 id=\"13\" type=\"BooleanType\"/>\n        <field name=\"commitPosition\"            id=\"14\" type=\"int64\" sinceVersion=\"15\"/>\n    </sbe:message>\n\n    <!-- keep this under 32 - 8 = 24 bytes for efficiency -->\n    <sbe:message name=\"AppendPosition\"\n                 id=\"54\"\n                 description=\"The highest position a follower has appended to their local log.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"followerMemberId\"         id=\"3\" type=\"int32\"/>\n        <field name=\"flags\"                    id=\"4\" type=\"uint8\" sinceVersion=\"8\"/>\n    </sbe:message>\n\n    <!-- keep this under 32 - 8 = 24 bytes for efficiency -->\n    <sbe:message name=\"CommitPosition\"\n                 id=\"55\"\n                 description=\"The highest position reached by quorum of the cluster as determined by the leader.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"leaderMemberId\"           id=\"3\" type=\"int32\"/>\n    </sbe:message>\n\n    <sbe:message name=\"CatchupPosition\"\n                 id=\"56\"\n                 description=\"The follower requests catchup from the leader starting at the given position.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"followerMemberId\"         id=\"3\" type=\"int32\"/>\n        <data  name=\"catchupEndpoint\"          id=\"4\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StopCatchup\"\n                 id=\"57\"\n                 description=\"The leader informs the follower it can stop the catchup process.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"followerMemberId\"         id=\"2\" type=\"int32\"/>\n    </sbe:message>\n\n    <sbe:message name=\"AddPassiveMember\"\n                 id=\"70\"\n                 description=\"Add a member to the passive member list.\"\n                 deprecated=\"12\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <data  name=\"memberEndpoints\"          id=\"2\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ClusterMembersChange\"\n                 id=\"71\"\n                 description=\"Cluster Member list change, add or remove, response.\"\n                 deprecated=\"12\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"leaderMemberId\"           id=\"2\" type=\"int32\"/>\n        <data  name=\"activeMembers\"            id=\"3\" type=\"varAsciiEncoding\"/>\n        <data  name=\"passiveMembers\"           id=\"4\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"SnapshotRecordingQuery\"\n                 id=\"72\"\n                 description=\"Query a leader for the info on latest snapshot recordings.\"\n                 deprecated=\"12\">\n        <field name=\"correlationId\"            id=\"1\"  type=\"int64\"/>\n        <field name=\"requestMemberId\"          id=\"2\"  type=\"int32\"/>\n    </sbe:message>\n\n    <sbe:message name=\"SnapshotRecordings\"\n                 id=\"73\"\n                 description=\"Response to Snapshot recording query.\"\n                 deprecated=\"12\">\n        <field name=\"correlationId\"            id=\"1\"  type=\"int64\"/>\n        <group name=\"snapshots\"                id=\"3\"  dimensionType=\"groupSizeEncoding\"\n               description=\"Snapshots of state for the consensus module and services.\">\n            <field name=\"recordingId\"          id=\"4\"  type=\"int64\"/>\n            <field name=\"leadershipTermId\"     id=\"5\"  type=\"int64\"/>\n            <field name=\"termBaseLogPosition\"  id=\"6\"  type=\"int64\"/>\n            <field name=\"logPosition\"          id=\"7\"  type=\"int64\"/>\n            <field name=\"timestamp\"            id=\"8\"  type=\"time_t\"/>\n            <field name=\"serviceId\"            id=\"9\"  type=\"int32\"/>\n        </group>\n        <data  name=\"memberEndpoints\"          id=\"10\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"JoinCluster\"\n                 id=\"74\"\n                 description=\"Join the cluster officially, increasing the member size, transitioning from passive.\"\n                 deprecated=\"12\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"memberId\"                 id=\"2\" type=\"int32\"/>\n    </sbe:message>\n\n    <sbe:message name=\"TerminationPosition\"\n                 id=\"75\"\n                 description=\"Leader instructs members to terminate at given position.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"TerminationAck\"\n                 id=\"76\"\n                 description=\"Follower acknowledges that it has reached termination position.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"memberId\"                 id=\"3\" type=\"int32\"/>\n    </sbe:message>\n\n    <sbe:message name=\"BackupQuery\"\n                 id=\"77\"\n                 description=\"Query a leader for the latest backup information.\">\n        <field name=\"correlationId\"            id=\"1\"  type=\"int64\"/>\n        <field name=\"responseStreamId\"         id=\"2\"  type=\"int32\"/>\n        <field name=\"version\"                  id=\"3\"  type=\"version_t\" presence=\"optional\" sinceVersion=\"2\"/>\n        <field name=\"logPosition\"              id=\"6\"  type=\"int64\" presence=\"optional\" sinceVersion=\"16\"/>\n        <data  name=\"responseChannel\"          id=\"4\"  type=\"varAsciiEncoding\"/>\n        <data  name=\"encodedCredentials\"       id=\"5\"  type=\"varDataEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"BackupResponse\"\n                 id=\"78\"\n                 description=\"Response to backup query that includes all relevant information for backup operation.\">\n        <field name=\"correlationId\"            id=\"1\"  type=\"int64\"/>\n        <field name=\"logRecordingId\"           id=\"2\"  type=\"int64\"/>\n        <field name=\"logLeadershipTermId\"      id=\"3\"  type=\"int64\"/>\n        <field name=\"logTermBaseLogPosition\"   id=\"4\"  type=\"int64\"/>\n        <field name=\"lastLeadershipTermId\"     id=\"5\"  type=\"int64\"/>\n        <field name=\"lastTermBaseLogPosition\"  id=\"6\"  type=\"int64\"/>\n        <field name=\"commitPositionCounterId\"  id=\"7\"  type=\"int32\"/>\n        <field name=\"leaderMemberId\"           id=\"8\"  type=\"int32\"/>\n        <field name=\"memberId\"                 id=\"17\" type=\"int32\" sinceVersion=\"10\"\n               description=\"id of member providing the response.\"/>\n        <group name=\"snapshots\"                id=\"9\"  dimensionType=\"groupSizeEncoding\"\n               description=\"Snapshots of state for the consensus module and services.\">\n            <field name=\"recordingId\"          id=\"10\" type=\"int64\"/>\n            <field name=\"leadershipTermId\"     id=\"11\" type=\"int64\"/>\n            <field name=\"termBaseLogPosition\"  id=\"12\" type=\"int64\"/>\n            <field name=\"logPosition\"          id=\"13\" type=\"int64\"/>\n            <field name=\"timestamp\"            id=\"14\" type=\"time_t\"/>\n            <field name=\"serviceId\"            id=\"15\" type=\"int32\"/>\n        </group>\n        <data  name=\"clusterMembers\"           id=\"16\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"HeartbeatRequest\"\n                 id=\"79\"\n                 description=\"Request a heartbeat response from a cluster member.\">\n        <field name=\"correlationId\"            id=\"1\"  type=\"int64\"/>\n        <field name=\"responseStreamId\"         id=\"2\"  type=\"int32\"/>\n        <data  name=\"responseChannel\"          id=\"4\"  type=\"varAsciiEncoding\"/>\n        <data  name=\"encodedCredentials\"       id=\"5\"  type=\"varDataEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"HeartbeatResponse\"\n                 id=\"80\"\n                 description=\"Response to a heartbeat request.\">\n        <field name=\"correlationId\"            id=\"1\"  type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"StandbySnapshot\"\n                 id=\"81\"\n                 description=\"Notify node(s) of the creation of a snapshot taken on a remote node\"\n                 sinceVersion=\"11\">\n        <field name=\"correlationId\"            id=\"1\"  type=\"int64\"/>\n        <field name=\"version\"                  id=\"2\"  type=\"int32\"/>\n        <field name=\"responseStreamId\"         id=\"3\"  type=\"int32\"/>\n        <group name=\"snapshots\" id=\"4\" dimensionType=\"groupSizeEncoding\">\n            <field name=\"recordingId\"           id=\"5\" type=\"int64\"/>\n            <field name=\"leadershipTermId\"      id=\"6\" type=\"int64\"/>\n            <field name=\"termBaseLogPosition\"   id=\"7\" type=\"int64\"/>\n            <field name=\"logPosition\"           id=\"8\" type=\"int64\"/>\n            <field name=\"timestamp\"             id=\"9\" type=\"int64\"/>\n            <field name=\"serviceId\"             id=\"10\" type=\"int32\"/>\n            <data  name=\"archiveEndpoint\"        id=\"11\" type=\"varAsciiEncoding\"/>\n        </group>\n        <data name=\"responseChannel\"          id=\"12\"  type=\"varAsciiEncoding\"/>\n        <data name=\"encodedCredentials\"       id=\"13\"  type=\"varDataEncoding\"/>\n    </sbe:message>\n\n    <!-- Serialisation of major entities in addition to others above that can be in a snapshot -->\n\n    <sbe:message name=\"SnapshotMarker\"\n                 id=\"100\"\n                 description=\"Mark the beginning, end, or section index of a snapshot.\">\n        <field name=\"typeId\"                   id=\"1\" type=\"int64\"/>\n        <field name=\"logPosition\"              id=\"2\" type=\"int64\"/>\n        <field name=\"leadershipTermId\"         id=\"3\" type=\"int64\"/>\n        <field name=\"index\"                    id=\"4\" type=\"int32\"/>\n        <field name=\"mark\"                     id=\"5\" type=\"SnapshotMark\"/>\n        <field name=\"timeUnit\"                 id=\"6\" type=\"ClusterTimeUnit\" sinceVersion=\"4\" presence=\"optional\"/>\n        <field name=\"appVersion\"               id=\"7\" type=\"version_t\" sinceVersion=\"4\" presence=\"optional\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ClientSession\"\n                 id=\"102\"\n                 description=\"A serialised client session in the context of a service.\">\n        <field name=\"clusterSessionId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"responseStreamId\"         id=\"2\" type=\"int32\"/>\n        <data  name=\"responseChannel\"          id=\"3\" type=\"varAsciiEncoding\"/>\n        <data  name=\"encodedPrincipal\"         id=\"4\" type=\"varDataEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ClusterSession\"\n                 id=\"103\"\n                 description=\"A serialised client session in the context of a Consensus Module.\">\n        <field name=\"clusterSessionId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"correlationId\"            id=\"2\" type=\"int64\"/>\n        <field name=\"openedLogPosition\"        id=\"3\" type=\"int64\"/>\n        <field name=\"timeOfLastActivity\"       id=\"4\" type=\"time_t\"/>\n        <field name=\"closeReason\"              id=\"5\" type=\"CloseReason\"/>\n        <field name=\"responseStreamId\"         id=\"6\" type=\"int32\"/>\n        <data  name=\"responseChannel\"          id=\"7\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"Timer\"\n                 id=\"104\"\n                 description=\"A serialised scheduled timer.\">\n        <field name=\"correlationId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"deadline\"                 id=\"2\" type=\"time_t\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ConsensusModule\"\n                 id=\"105\"\n                 description=\"Serialised state of the Consensus Module.\">\n        <field name=\"nextSessionId\"            id=\"1\" type=\"int64\"/>\n        <field name=\"nextServiceSessionId\"     id=\"2\" type=\"int64\" sinceVersion=\"3\" presence=\"optional\"/>\n        <field name=\"logServiceSessionId\"      id=\"3\" type=\"int64\" sinceVersion=\"3\" presence=\"optional\"/>\n        <field name=\"pendingMessageCapacity\"   id=\"4\" type=\"capacity_t\" sinceVersion=\"3\" presence=\"optional\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ClusterMembers\"\n                 id=\"106\"\n                 description=\"Serialised state of Cluster Members.\">\n        <field name=\"memberId\"                 id=\"1\" type=\"int32\"/>\n        <field name=\"highMemberId\"             id=\"2\" type=\"int32\"/>\n        <data  name=\"clusterMembers\"           id=\"3\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"PendingMessageTracker\"\n                 id=\"107\"\n                 description=\"Serialised state of the service pending message tracker.\">\n        <field name=\"nextServiceSessionId\"     id=\"1\" type=\"int64\"/>\n        <field name=\"logServiceSessionId\"      id=\"2\" type=\"int64\"/>\n        <field name=\"pendingMessageCapacity\"   id=\"3\" type=\"capacity_t\"/>\n        <field name=\"serviceId\"                id=\"4\" type=\"int32\"/>\n    </sbe:message>\n\n</sbe:messageSchema>\n"
  },
  {
    "path": "aeron-cluster/src/main/resources/cluster/aeron-cluster-mark-codecs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<sbe:messageSchema xmlns:sbe=\"http://fixprotocol.io/2016/sbe\"\n                   package=\"io.aeron.cluster.codecs.mark\"\n                   id=\"110\"\n                   version=\"2\"\n                   semanticVersion=\"5.4\"\n                   description=\"Codecs for Mark file of an Aeron Cluster.\"\n                   byteOrder=\"littleEndian\">\n    <types>\n        <composite name=\"messageHeader\" description=\"Message identifiers and length of message root.\">\n            <type name=\"blockLength\" primitiveType=\"uint16\"/>\n            <type name=\"templateId\"  primitiveType=\"uint16\"/>\n            <type name=\"schemaId\"    primitiveType=\"uint16\"/>\n            <type name=\"version\"     primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"groupSizeEncoding\" description=\"Repeating group dimensions.\">\n            <type name=\"blockLength\" primitiveType=\"uint16\"/>\n            <type name=\"numInGroup\"  primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"varAsciiEncoding\" description=\"Variable length ASCII string header.\">\n            <type name=\"length\"      primitiveType=\"uint32\" maxValue=\"1073741824\"/>\n            <type name=\"varData\"     primitiveType=\"uint8\" length=\"0\" characterEncoding=\"US-ASCII\"/>\n        </composite>\n        <type name=\"time_t\" primitiveType=\"int64\" description=\"Epoch time in milliseconds since 1 Jan 1970 UTC.\"/>\n        <enum name=\"ClusterComponentType\" encodingType=\"int32\" description=\"Type of Cluster Component\">\n            <validValue name=\"UNKNOWN\">0</validValue>\n            <validValue name=\"CONSENSUS_MODULE\">1</validValue>\n            <validValue name=\"CONTAINER\">2</validValue>\n            <validValue name=\"BACKUP\">3</validValue>\n            <validValue name=\"STANDBY\" sinceVersion=\"1\">4</validValue>\n        </enum>\n    </types>\n\n    <sbe:message name=\"MarkFileHeader\"\n                 id=\"200\"\n                 blockLength=\"128\"\n                 description=\"Header of Consensus Module and Container Mark file.\">\n        <field name=\"version\"                 id=\"1\"  type=\"int32\"/>\n        <field name=\"componentType\"           id=\"2\"  type=\"ClusterComponentType\"/>\n        <field name=\"activityTimestamp\"       id=\"3\"  type=\"time_t\"/>\n        <field name=\"startTimestamp\"          id=\"4\"  type=\"time_t\"/>\n        <field name=\"pid\"                     id=\"5\"  type=\"int64\"/>\n        <field name=\"candidateTermId\"         id=\"6\"  type=\"int64\"/>\n        <field name=\"archiveStreamId\"         id=\"7\"  type=\"int32\"/>\n        <field name=\"serviceStreamId\"         id=\"8\"  type=\"int32\"/>\n        <field name=\"consensusModuleStreamId\" id=\"9\"  type=\"int32\"/>\n        <field name=\"ingressStreamId\"         id=\"10\" type=\"int32\"/>\n        <field name=\"memberId\"                id=\"11\" type=\"int32\"/>\n        <field name=\"serviceId\"               id=\"12\" type=\"int32\"/>\n        <field name=\"headerLength\"            id=\"13\" type=\"int32\"/>\n        <field name=\"errorBufferLength\"       id=\"14\" type=\"int32\"/>\n        <field name=\"clusterId\"               id=\"15\" type=\"int32\"/>\n        <data  name=\"aeronDirectory\"          id=\"16\" type=\"varAsciiEncoding\"/>\n        <data  name=\"controlChannel\"          id=\"17\" type=\"varAsciiEncoding\"/>\n        <data  name=\"ingressChannel\"          id=\"18\" type=\"varAsciiEncoding\"/>\n        <data  name=\"serviceName\"             id=\"19\" type=\"varAsciiEncoding\"/>\n        <data  name=\"authenticator\"           id=\"20\" type=\"varAsciiEncoding\"/>\n        <data  name=\"servicesClusterDir\"      id=\"21\" type=\"varAsciiEncoding\" sinceVersion=\"1\"/>\n    </sbe:message>\n\n</sbe:messageSchema>\n"
  },
  {
    "path": "aeron-cluster/src/main/resources/cluster/aeron-cluster-node-state-codecs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\n<!--\nCodec for the persistent local state of a cluster node.\n-->\n<sbe:messageSchema xmlns:sbe=\"http://fixprotocol.io/2016/sbe\"\n                   package=\"io.aeron.cluster.codecs.node\"\n                   id=\"112\"\n                   version=\"10\"\n                   semanticVersion=\"5.3\"\n                   description=\"Message Codecs for the persistent state of a node in Aeron Cluster.\">\n    <types>\n        <composite name=\"messageHeader\" description=\"Message identifiers and length of message root.\">\n            <type name=\"blockLength\"    primitiveType=\"uint16\"/>\n            <type name=\"templateId\"     primitiveType=\"uint16\"/>\n            <type name=\"schemaId\"       primitiveType=\"uint16\"/>\n            <type name=\"version\"        primitiveType=\"uint16\"/>\n            <type name=\"frameLength\"    primitiveType=\"int32\"/>\n            <type name=\"padding\"        primitiveType=\"int32\"/>\n        </composite>\n        <composite name=\"groupSizeEncoding\" description=\"Repeating group dimensions.\">\n            <type name=\"blockLength\"    primitiveType=\"uint16\"/>\n            <type name=\"numInGroup\"     primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"varAsciiEncoding\" description=\"Variable length ASCII string header.\">\n            <type name=\"length\"         primitiveType=\"uint32\" maxValue=\"1073741824\"/>\n            <type name=\"varData\"        primitiveType=\"uint8\" length=\"0\" characterEncoding=\"US-ASCII\"/>\n        </composite>\n        <type name=\"time_t\" primitiveType=\"int64\" description=\"Epoch time in milliseconds since 1 Jan 1970 UTC.\"/>\n    </types>\n\n    <sbe:message name=\"nodeStateHeader\" id=\"300\">\n        <field name=\"version\" id=\"1\" type=\"int32\"/>\n        <field name=\"padding\" id=\"2\" type=\"int32\"/>\n    </sbe:message>\n\n    <sbe:message name=\"candidateTerm\" id=\"301\">\n        <field name=\"candidateTermId\"   id=\"1\" type=\"int64\"/>\n        <field name=\"timestamp\"         id=\"2\" type=\"time_t\"/>\n        <field name=\"logPosition\"       id=\"3\" type=\"int64\"/>\n    </sbe:message>\n\n    <sbe:message name=\"ClusterMembers\"\n                 id=\"303\"\n                 description=\"Serialised state of Cluster Members.\">\n        <field name=\"leadershipTermId\"         id=\"1\" type=\"int64\"/>\n        <field name=\"memberId\"                 id=\"2\" type=\"int32\"/>\n        <field name=\"highMemberId\"             id=\"3\" type=\"int32\"/>\n        <data  name=\"clusterMembers\"           id=\"4\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n    <sbe:message name=\"nodeStateFooter\" id=\"304\"/>\n\n</sbe:messageSchema>"
  },
  {
    "path": "aeron-cluster/src/main/resources/cluster/fpl/sbe.xsd",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xs:schema xmlns:sbe=\"http://fixprotocol.io/2016/sbe\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" targetNamespace=\"http://fixprotocol.io/2016/sbe\" elementFormDefault=\"unqualified\" version=\"1.0 Draft Standard\">\n    <xs:annotation>\n        <xs:documentation>\n            Message schema for FIX Simple Binary Encoding\n            Version: 1.0 Draft Standard\n            © Copyright 2014-2016 FIX Protocol Limited\n            License: Creative Commons Attribution-NoDerivatives 4.0 International Public License\n        </xs:documentation>\n    </xs:annotation>\n    <xs:element name=\"messageSchema\">\n        <xs:annotation>\n            <xs:documentation>\n                Root of XML document, holds all message templates\n                and their elements\n            </xs:documentation>\n        </xs:annotation>\n        <xs:complexType>\n            <xs:sequence>\n                <xs:element name=\"types\" maxOccurs=\"unbounded\">\n                    <xs:annotation>\n                        <xs:documentation>\n                            More than one set of types may be provided.\n                            Names must be unique across all encoding\n                            types.\n                            Encoding types may appear in any order.\n                        </xs:documentation>\n                    </xs:annotation>\n                    <xs:complexType>\n                        <xs:choice maxOccurs=\"unbounded\">\n                            <xs:element name=\"type\" type=\"sbe:encodedDataType\"/>\n                            <xs:element name=\"composite\" type=\"sbe:compositeDataType\"/>\n                            <xs:element name=\"enum\" type=\"sbe:enumType\"/>\n                            <xs:element name=\"set\" type=\"sbe:setType\"/>\n                        </xs:choice>\n                    </xs:complexType>\n                </xs:element>\n                <xs:element ref=\"sbe:message\" maxOccurs=\"unbounded\"/>\n            </xs:sequence>\n            <xs:attribute name=\"package\" type=\"xs:string\"/>\n            <xs:attribute name=\"id\" type=\"xs:unsignedShort\">\n                <xs:annotation>\n                    <xs:documentation>Unique ID of a message schema\n                    </xs:documentation>\n                </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"version\" type=\"xs:nonNegativeInteger\" use=\"required\">\n                <xs:annotation>\n                    <xs:documentation>The version of a message schema. Initial version\n                        is 0.\n                    </xs:documentation>\n                </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"semanticVersion\" type=\"xs:string\" use=\"optional\">\n                <xs:annotation>\n                    <xs:documentation>Application layer specification version, such as\n                        FIX version 'FIX.5.0SP2'\n                    </xs:documentation>\n                </xs:annotation>\n            </xs:attribute>\n            <xs:attribute name=\"description\" type=\"xs:string\" use=\"optional\"/>\n            <xs:attribute name=\"byteOrder\" default=\"littleEndian\">\n                <xs:simpleType>\n                    <xs:restriction base=\"xs:token\">\n                        <xs:enumeration value=\"bigEndian\"/>\n                        <xs:enumeration value=\"littleEndian\"/>\n                    </xs:restriction>\n                </xs:simpleType>\n            </xs:attribute>\n            <xs:attribute name=\"headerType\" type=\"sbe:symbolicName_t\" default=\"messageHeader\">\n                <xs:annotation>\n                    <xs:documentation>\n                        Name of the encoding type of the message header,\n                        which is the same for all messages in a schema. The name has a\n                        default, but an encoding of that name must be present under a\n                        'types' element.\n                    </xs:documentation>\n                </xs:annotation>\n            </xs:attribute>\n        </xs:complexType>\n    </xs:element>\n    <xs:element name=\"message\" type=\"sbe:blockType\">\n        <xs:annotation>\n            <xs:documentation>\n                A message type, also known as a message template\n            </xs:documentation>\n        </xs:annotation>\n    </xs:element>\n    <xs:complexType name=\"blockType\">\n        <xs:annotation>\n            <xs:documentation>Base type of message and repeating group entry\n            </xs:documentation>\n        </xs:annotation>\n        <xs:sequence>\n            <xs:element name=\"field\" type=\"sbe:fieldType\" minOccurs=\"0\" maxOccurs=\"unbounded\">\n                <xs:annotation>\n                    <xs:documentation>Fixed-length fields</xs:documentation>\n                </xs:annotation>\n            </xs:element>\n            <xs:element name=\"group\" type=\"sbe:groupType\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n            <xs:element name=\"data\" type=\"sbe:fieldType\" minOccurs=\"0\" maxOccurs=\"unbounded\">\n                <xs:annotation>\n                    <xs:documentation>Variable-length fields</xs:documentation>\n                </xs:annotation>\n            </xs:element>\n        </xs:sequence>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"id\" type=\"xs:unsignedShort\" use=\"required\">\n            <xs:annotation>\n                <xs:documentation>Unique ID of a message template\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attribute name=\"blockLength\" type=\"xs:nonNegativeInteger\" use=\"optional\">\n            <xs:annotation>\n                <xs:documentation>Space reserved for root level of message, not\n                    include groups or variable-length\n                    data elements.\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"groupType\">\n        <xs:annotation>\n            <xs:documentation>\n                A repeating group contains an array of entries\n            </xs:documentation>\n        </xs:annotation>\n        <xs:complexContent>\n            <xs:extension base=\"sbe:blockType\">\n                <xs:attribute name=\"dimensionType\" type=\"sbe:symbolicName_t\" default=\"groupSizeEncoding\"/>\n            </xs:extension>\n        </xs:complexContent>\n    </xs:complexType>\n    <xs:complexType name=\"encodedDataType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                Simple wire encoding consisting of a primitive type\n                or array of primitives\n            </xs:documentation>\n        </xs:annotation>\n        <xs:simpleContent>\n            <xs:extension base=\"xs:token\">\n                <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n                <xs:attribute name=\"nullValue\" type=\"xs:string\" use=\"optional\">\n                    <xs:annotation>\n                        <xs:documentation>Override of default null indicator for the data\n                            type in SBE specification,\n                            as a string.\n                        </xs:documentation>\n                    </xs:annotation>\n                </xs:attribute>\n                <xs:attribute name=\"minValue\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attribute name=\"maxValue\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attribute name=\"length\" type=\"xs:nonNegativeInteger\" default=\"1\"/>\n                <xs:attribute name=\"primitiveType\" use=\"required\">\n                    <xs:simpleType>\n                        <xs:restriction base=\"xs:token\">\n                            <xs:enumeration value=\"char\"/>\n                            <xs:enumeration value=\"int8\"/>\n                            <xs:enumeration value=\"int16\"/>\n                            <xs:enumeration value=\"int32\"/>\n                            <xs:enumeration value=\"int64\"/>\n                            <xs:enumeration value=\"uint8\"/>\n                            <xs:enumeration value=\"uint16\"/>\n                            <xs:enumeration value=\"uint32\"/>\n                            <xs:enumeration value=\"uint64\"/>\n                            <xs:enumeration value=\"float\"/>\n                            <xs:enumeration value=\"double\"/>\n                        </xs:restriction>\n                    </xs:simpleType>\n                </xs:attribute>\n                <xs:attribute name=\"characterEncoding\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n                <xs:attributeGroup ref=\"sbe:presenceAttributes\"/>\n                <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n                <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n            </xs:extension>\n        </xs:simpleContent>\n    </xs:complexType>\n    <xs:complexType name=\"compositeDataType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                A wire encoding composed of multiple parts\n            </xs:documentation>\n        </xs:annotation>\n        <xs:choice maxOccurs=\"unbounded\">\n            <xs:element name=\"type\" type=\"sbe:encodedDataType\"/>\n            <xs:element name=\"enum\" type=\"sbe:enumType\"/>\n            <xs:element name=\"set\" type=\"sbe:setType\"/>\n            <xs:element name=\"composite\" type=\"sbe:compositeDataType\"/>\n            <xs:element name=\"ref\" type=\"sbe:refType\"/>\n        </xs:choice>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"enumType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                An enumeration of valid values\n            </xs:documentation>\n        </xs:annotation>\n        <xs:sequence>\n            <xs:element name=\"validValue\" type=\"sbe:validValue\" maxOccurs=\"unbounded\"/>\n        </xs:sequence>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"encodingType\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"validValue\">\n        <xs:annotation>\n            <xs:documentation>\n                Valid value as a string\n            </xs:documentation>\n        </xs:annotation>\n        <xs:simpleContent>\n            <xs:extension base=\"xs:token\">\n                <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n                <xs:attribute name=\"description\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n            </xs:extension>\n        </xs:simpleContent>\n    </xs:complexType>\n    <xs:complexType name=\"refType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                A reference to any existing encoding type (simple type, enum or set)\n                to reuse as a member of a composite type\n            </xs:documentation>\n        </xs:annotation>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"type\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"setType\" mixed=\"true\">\n        <xs:annotation>\n            <xs:documentation>\n                A multi value choice (encoded as a bitset)\n            </xs:documentation>\n        </xs:annotation>\n        <xs:sequence>\n            <xs:element name=\"choice\" type=\"sbe:choice\" maxOccurs=\"64\"/>\n        </xs:sequence>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"encodingType\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n    </xs:complexType>\n    <xs:complexType name=\"choice\">\n        <xs:annotation>\n            <xs:documentation>\n                A choice within a multi value set. Value is the\n                position within a bitset (zero-based index).\n            </xs:documentation>\n        </xs:annotation>\n        <xs:simpleContent>\n            <xs:extension base=\"xs:nonNegativeInteger\">\n                <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n                <xs:attribute name=\"description\" type=\"xs:string\" use=\"optional\"/>\n                <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n            </xs:extension>\n        </xs:simpleContent>\n    </xs:complexType>\n    <xs:complexType name=\"fieldType\">\n        <xs:annotation>\n            <xs:documentation>\n                A field of a message of a specified dataType\n            </xs:documentation>\n        </xs:annotation>\n        <xs:attribute name=\"name\" type=\"sbe:symbolicName_t\" use=\"required\"/>\n        <xs:attribute name=\"id\" type=\"xs:unsignedShort\" use=\"required\"/>\n        <xs:attribute name=\"type\" type=\"sbe:symbolicName_t\" use=\"required\">\n            <xs:annotation>\n                <xs:documentation>Must match the name of an encoding contained by\n                    'types' element\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attribute name=\"epoch\" type=\"xs:string\" default=\"unix\"/>\n        <xs:attribute name=\"timeUnit\" type=\"xs:string\" default=\"nanosecond\">\n            <xs:annotation>\n                <xs:documentation>Deprecated - only for back compatibility with RC2\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attributeGroup ref=\"sbe:alignmentAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:presenceAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:semanticAttributes\"/>\n        <xs:attributeGroup ref=\"sbe:versionAttributes\"/>\n        <!-- start of time period - default is UNIX epoch -->\n    </xs:complexType>\n    <xs:attributeGroup name=\"semanticAttributes\">\n        <xs:annotation>\n            <xs:documentation>\n                Application layer class. Maps a field or encoding\n                to a FIX data type.\n            </xs:documentation>\n        </xs:annotation>\n        <xs:attribute name=\"semanticType\" type=\"xs:token\" use=\"optional\"/>\n        <xs:attribute name=\"description\" type=\"xs:string\" use=\"optional\"/>\n    </xs:attributeGroup>\n    <xs:attributeGroup name=\"versionAttributes\">\n        <xs:annotation>\n            <xs:documentation>\n                Schema versioning supports message extension\n            </xs:documentation>\n        </xs:annotation>\n        <xs:attribute name=\"sinceVersion\" type=\"xs:nonNegativeInteger\" default=\"0\">\n            <xs:annotation>\n                <xs:documentation>\n                    The schema version in which an element was added\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n        <xs:attribute name=\"deprecated\" type=\"xs:nonNegativeInteger\" use=\"optional\">\n            <xs:annotation>\n                <xs:documentation>\n                    The version of the schema in which an element was\n                    deprecated. It is retained for back compatibility but should no\n                    longer be used by updated applications.\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n    </xs:attributeGroup>\n    <xs:attributeGroup name=\"alignmentAttributes\">\n        <xs:attribute name=\"offset\" type=\"xs:unsignedInt\" use=\"optional\">\n            <xs:annotation>\n                <xs:documentation>Offset from start of a composite type or block\n                    as a zero-based index.\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n    </xs:attributeGroup>\n    <xs:attributeGroup name=\"presenceAttributes\">\n        <xs:attribute name=\"presence\" default=\"required\">\n            <xs:simpleType>\n                <xs:restriction base=\"xs:token\">\n                    <xs:enumeration value=\"required\">\n                        <xs:annotation>\n                            <xs:documentation>The value must always be populated\n                            </xs:documentation>\n                        </xs:annotation>\n                    </xs:enumeration>\n                    <xs:enumeration value=\"optional\">\n                        <xs:annotation>\n                            <xs:documentation>Value may be set to nullValue for its data type\n                            </xs:documentation>\n                        </xs:annotation>\n                    </xs:enumeration>\n                    <xs:enumeration value=\"constant\">\n                        <xs:annotation>\n                            <xs:documentation>Value does not vary so it need not be\n                                serialized on the wire\n                            </xs:documentation>\n                        </xs:annotation>\n                    </xs:enumeration>\n                </xs:restriction>\n            </xs:simpleType>\n        </xs:attribute>\n        <xs:attribute name=\"valueRef\" type=\"sbe:qualifiedName_t\" use=\"optional\">\n            <xs:annotation>\n                <xs:documentation>A constant value as valid value of an enum\n                    in the form enum-name.valid-value-name\n                </xs:documentation>\n            </xs:annotation>\n        </xs:attribute>\n    </xs:attributeGroup>\n    <xs:simpleType name=\"symbolicName_t\">\n        <xs:restriction base=\"xs:string\">\n            <xs:minLength value=\"1\"/>\n            <xs:maxLength value=\"64\"/>\n            <xs:pattern value=\"([A-Z]|[a-z]|_)([0-9]|[A-Z]|[a-z]|_)*\"/>\n        </xs:restriction>\n    </xs:simpleType>\n    <xs:simpleType name=\"qualifiedName_t\">\n        <xs:restriction base=\"xs:string\">\n            <xs:pattern value=\"([A-Z]|[a-z]|_)([0-9]|[A-Z]|[a-z]|_)*\\.([A-Z]|[a-z]|_)([0-9]|[A-Z]|[a-z]|_)*\"/>\n        </xs:restriction>\n    </xs:simpleType>\n</xs:schema>\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/AuthenticationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.security.*;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.ClusterTests;\nimport io.aeron.test.cluster.StubClusteredService;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.collections.MutableReference;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static io.aeron.cluster.ClusterTestConstants.CLUSTER_MEMBERS;\nimport static io.aeron.cluster.ClusterTestConstants.INGRESS_ENDPOINTS;\nimport static io.aeron.security.NullCredentialsSupplier.NULL_CREDENTIAL;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.spy;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass AuthenticationTest\n{\n    private static final String CREDENTIALS_STRING = \"username=\\\"admin\\\"|password=\\\"secret\\\"\";\n    private static final String CHALLENGE_STRING = \"I challenge you!\";\n    private static final String PRINCIPAL_STRING = \"I am THE Principal!\";\n\n    private ClusteredMediaDriver clusteredMediaDriver;\n    private ClusteredServiceContainer container;\n\n    private final ExpandableArrayBuffer msgBuffer = new ExpandableArrayBuffer();\n    private AeronCluster aeronCluster;\n\n    private final byte[] encodedCredentials = CREDENTIALS_STRING.getBytes();\n    private final byte[] encodedChallenge = CHALLENGE_STRING.getBytes();\n\n    @AfterEach\n    void after()\n    {\n        final ConsensusModule consensusModule = null == clusteredMediaDriver ?\n            null : clusteredMediaDriver.consensusModule();\n\n        CloseHelper.closeAll(aeronCluster, consensusModule, container, clusteredMediaDriver);\n\n        if (null != clusteredMediaDriver)\n        {\n            clusteredMediaDriver.consensusModule().context().deleteDirectory();\n            clusteredMediaDriver.archive().context().deleteDirectory();\n            clusteredMediaDriver.mediaDriver().context().deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAuthenticateOnConnectRequestWithEmptyCredentials()\n    {\n        final AtomicLong serviceMsgCounter = new AtomicLong(0L);\n        final MutableLong serviceSessionId = new MutableLong(-1L);\n        final MutableLong authenticatorSessionId = new MutableLong(-1L);\n        final MutableReference<byte[]> encodedPrincipal = new MutableReference<>();\n\n        final CredentialsSupplier credentialsSupplier = spy(new CredentialsSupplier()\n        {\n            public byte[] encodedCredentials()\n            {\n                return NULL_CREDENTIAL;\n            }\n\n            public byte[] onChallenge(final byte[] encodedChallenge)\n            {\n                fail();\n                return null;\n            }\n        });\n\n        final Authenticator authenticator = spy(new Authenticator()\n        {\n            public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                authenticatorSessionId.value = sessionId;\n                assertEquals(0, encodedCredentials.length);\n            }\n\n            public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                fail();\n            }\n\n            public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                sessionProxy.authenticate(null);\n            }\n\n            public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                fail();\n            }\n        });\n\n        launchClusteredMediaDriver(() -> authenticator);\n        launchService(serviceSessionId, encodedPrincipal, serviceMsgCounter);\n\n        connectClient(credentialsSupplier);\n        sendCountedMessageIntoCluster(0);\n        Tests.awaitValue(serviceMsgCounter, 1);\n\n        assertEquals(aeronCluster.clusterSessionId(), authenticatorSessionId.value);\n        assertEquals(aeronCluster.clusterSessionId(), serviceSessionId.value);\n        assertEquals(0, encodedPrincipal.get().length);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAuthenticateOnConnectRequestWithCredentials()\n    {\n        final AtomicLong serviceMsgCounter = new AtomicLong(0L);\n        final MutableLong serviceSessionId = new MutableLong(-1L);\n        final MutableLong authenticatorSessionId = new MutableLong(-1L);\n        final MutableReference<byte[]> encodedPrincipal = new MutableReference<>();\n\n        final CredentialsSupplier credentialsSupplier = spy(new CredentialsSupplier()\n        {\n            public byte[] encodedCredentials()\n            {\n                return encodedCredentials;\n            }\n\n            public byte[] onChallenge(final byte[] encodedChallenge)\n            {\n                fail();\n                return null;\n            }\n        });\n\n        final Authenticator authenticator = spy(new Authenticator()\n        {\n            public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                authenticatorSessionId.value = sessionId;\n                assertEquals(CREDENTIALS_STRING, new String(encodedCredentials));\n            }\n\n            public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                fail();\n            }\n\n            public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                sessionProxy.authenticate(PRINCIPAL_STRING.getBytes());\n            }\n\n            public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                fail();\n            }\n        });\n\n        launchClusteredMediaDriver(() -> authenticator);\n        launchService(serviceSessionId, encodedPrincipal, serviceMsgCounter);\n\n        connectClient(credentialsSupplier);\n        sendCountedMessageIntoCluster(0);\n        Tests.awaitValue(serviceMsgCounter, 1);\n\n        assertEquals(aeronCluster.clusterSessionId(), authenticatorSessionId.value);\n        assertEquals(aeronCluster.clusterSessionId(), serviceSessionId.value);\n        assertEquals(PRINCIPAL_STRING, new String(encodedPrincipal.get()));\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAuthenticateOnChallengeResponse()\n    {\n        final AtomicLong serviceMsgCounter = new AtomicLong(0L);\n        final MutableLong serviceSessionId = new MutableLong(-1L);\n        final MutableLong authenticatorSessionId = new MutableLong(-1L);\n        final MutableReference<byte[]> encodedPrincipal = new MutableReference<>();\n\n        final CredentialsSupplier credentialsSupplier = spy(new CredentialsSupplier()\n        {\n            public byte[] encodedCredentials()\n            {\n                return NULL_CREDENTIAL;\n            }\n\n            public byte[] onChallenge(final byte[] encodedChallenge)\n            {\n                assertEquals(CHALLENGE_STRING, new String(encodedChallenge));\n                return encodedCredentials;\n            }\n        });\n\n        final Authenticator authenticator = spy(new Authenticator()\n        {\n            boolean challengeSuccessful = false;\n\n            public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                authenticatorSessionId.value = sessionId;\n                assertEquals(0, encodedCredentials.length);\n            }\n\n            public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                assertEquals(sessionId, authenticatorSessionId.value);\n                assertEquals(CREDENTIALS_STRING, new String(encodedCredentials));\n                challengeSuccessful = true;\n            }\n\n            public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                sessionProxy.challenge(encodedChallenge);\n            }\n\n            public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                if (challengeSuccessful)\n                {\n                    assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                    sessionProxy.authenticate(PRINCIPAL_STRING.getBytes());\n                }\n            }\n        });\n\n        launchClusteredMediaDriver(() -> authenticator);\n        launchService(serviceSessionId, encodedPrincipal, serviceMsgCounter);\n\n        connectClient(credentialsSupplier);\n        sendCountedMessageIntoCluster(0);\n        Tests.awaitValue(serviceMsgCounter, 1);\n\n        assertEquals(aeronCluster.clusterSessionId(), authenticatorSessionId.value);\n        assertEquals(aeronCluster.clusterSessionId(), serviceSessionId.value);\n        assertEquals(PRINCIPAL_STRING, new String(encodedPrincipal.get()));\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRejectOnConnectRequest()\n    {\n        final AtomicLong serviceMsgCounter = new AtomicLong(0L);\n        final MutableLong serviceSessionId = new MutableLong(-1L);\n        final MutableLong authenticatorSessionId = new MutableLong(-1L);\n        final MutableReference<byte[]> encodedPrincipal = new MutableReference<>();\n\n        final CredentialsSupplier credentialsSupplier = spy(new CredentialsSupplier()\n        {\n            public byte[] encodedCredentials()\n            {\n                return NULL_CREDENTIAL;\n            }\n\n            public byte[] onChallenge(final byte[] encodedChallenge)\n            {\n                assertEquals(CHALLENGE_STRING, new String(encodedChallenge));\n                return encodedCredentials;\n            }\n        });\n\n        final Authenticator authenticator = spy(new Authenticator()\n        {\n            public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                authenticatorSessionId.value = sessionId;\n                assertEquals(0, encodedCredentials.length);\n            }\n\n            public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                fail();\n            }\n\n            public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                sessionProxy.reject();\n            }\n\n            public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                fail();\n            }\n        });\n\n        launchClusteredMediaDriver(() -> authenticator);\n        launchService(serviceSessionId, encodedPrincipal, serviceMsgCounter);\n\n        try\n        {\n            connectClient(credentialsSupplier);\n        }\n        catch (final AuthenticationException ex)\n        {\n            assertEquals(-1L, serviceSessionId.value);\n\n            ClusterTests.failOnClusterError();\n            return;\n        }\n\n        fail(\"should have seen exception\");\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRejectOnChallengeResponse()\n    {\n        final AtomicLong serviceMsgCounter = new AtomicLong(0L);\n        final MutableLong serviceSessionId = new MutableLong(-1L);\n        final MutableLong authenticatorSessionId = new MutableLong(-1L);\n        final MutableReference<byte[]> encodedPrincipal = new MutableReference<>();\n\n        final CredentialsSupplier credentialsSupplier = spy(new CredentialsSupplier()\n        {\n            public byte[] encodedCredentials()\n            {\n                return NULL_CREDENTIAL;\n            }\n\n            public byte[] onChallenge(final byte[] encodedChallenge)\n            {\n                assertEquals(CHALLENGE_STRING, new String(encodedChallenge));\n                return encodedCredentials;\n            }\n        });\n\n        final Authenticator authenticator = spy(new Authenticator()\n        {\n            boolean challengeRespondedTo = false;\n\n            public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                authenticatorSessionId.value = sessionId;\n                assertEquals(0, encodedCredentials.length);\n            }\n\n            public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                assertEquals(sessionId, authenticatorSessionId.value);\n                assertEquals(CREDENTIALS_STRING, new String(encodedCredentials));\n                challengeRespondedTo = true;\n            }\n\n            public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                sessionProxy.challenge(encodedChallenge);\n            }\n\n            public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                if (challengeRespondedTo)\n                {\n                    assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                    sessionProxy.reject();\n                }\n            }\n        });\n\n        launchClusteredMediaDriver(() -> authenticator);\n        launchService(serviceSessionId, encodedPrincipal, serviceMsgCounter);\n\n        try\n        {\n            connectClient(credentialsSupplier);\n        }\n        catch (final AuthenticationException ex)\n        {\n            assertEquals(-1L, serviceSessionId.value);\n\n            ClusterTests.failOnClusterError();\n            return;\n        }\n\n        fail(\"should have seen exception\");\n    }\n\n    private void sendCountedMessageIntoCluster(final int value)\n    {\n        msgBuffer.putInt(0, value);\n\n        while (aeronCluster.offer(msgBuffer, 0, SIZE_OF_INT) < 0)\n        {\n            Tests.yield();\n        }\n    }\n\n    private void launchService(\n        final MutableLong sessionId, final MutableReference<byte[]> encodedPrincipal, final AtomicLong msgCounter)\n    {\n        final ClusteredService service = new StubClusteredService()\n        {\n            private int counterValue = 0;\n\n            public void onSessionOpen(final ClientSession session, final long timestamp)\n            {\n                sessionId.value = session.id();\n                encodedPrincipal.set(session.encodedPrincipal());\n            }\n\n            public void onSessionMessage(\n                final ClientSession session,\n                final long timestamp,\n                final DirectBuffer buffer,\n                final int offset,\n                final int length,\n                final Header header)\n            {\n                assertEquals(counterValue, buffer.getInt(offset));\n                msgCounter.getAndIncrement();\n                counterValue++;\n            }\n        };\n\n        container = ClusteredServiceContainer.launch(\n            new ClusteredServiceContainer.Context()\n                .clusteredService(service)\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .errorHandler(ClusterTests.errorHandler(0)));\n    }\n\n    private AeronCluster connectToCluster(final CredentialsSupplier credentialsSupplier)\n    {\n        return AeronCluster.connect(\n            new AeronCluster.Context()\n                .ingressChannel(\"aeron:udp\")\n                .ingressEndpoints(INGRESS_ENDPOINTS)\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\")\n                .credentialsSupplier(credentialsSupplier));\n    }\n\n    private void connectClient(final CredentialsSupplier credentialsSupplier)\n    {\n        aeronCluster = connectToCluster(credentialsSupplier);\n    }\n\n    private void launchClusteredMediaDriver(final AuthenticatorSupplier authenticatorSupplier)\n    {\n        clusteredMediaDriver = ClusteredMediaDriver.launch(\n            new MediaDriver.Context()\n                .warnIfDirectoryExists(false)\n                .threadingMode(ThreadingMode.SHARED)\n                .errorHandler(ClusterTests.errorHandler(0))\n                .dirDeleteOnStart(true)\n                .dirDeleteOnShutdown(false),\n            TestContexts.localhostArchive()\n                .catalogCapacity(ClusterTestConstants.CATALOG_CAPACITY)\n                .threadingMode(ArchiveThreadingMode.SHARED)\n                .recordingEventsEnabled(false)\n                .deleteArchiveOnStart(true),\n            new ConsensusModule.Context()\n                .errorHandler(ClusterTests.errorHandler(0))\n                .authenticatorSupplier(authenticatorSupplier)\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .ingressChannel(\"aeron:udp\")\n                .clusterMembers(CLUSTER_MEMBERS)\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .deleteDirOnStart(true));\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ClusterBackupAgentTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.client.AeronArchive;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.ClusterBackup.Configuration.ReplayStart;\nimport static io.aeron.cluster.ClusterBackupAgent.replayStartPosition;\nimport static java.util.Collections.emptyList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ClusterBackupAgentTest\n{\n    final AeronArchive mockAeronArchive = mock(AeronArchive.class);\n\n    @Test\n    void shouldReturnReplayStartPositionIfAlreadyExisting()\n    {\n        final long expectedStartPosition = 892374;\n        final long recordingId = 234;\n        final RecordingLog.Entry lastTerm = new RecordingLog.Entry(\n            recordingId, 0, 0, expectedStartPosition, 0, 0, 0, null, true, 0);\n\n        when(mockAeronArchive.getStopPosition(anyLong())).thenReturn(expectedStartPosition);\n\n        final long replayStartPosition = replayStartPosition(\n            lastTerm, emptyList(), ReplayStart.BEGINNING, mockAeronArchive);\n        assertEquals(expectedStartPosition, replayStartPosition);\n    }\n\n    @Test\n    void shouldReturnNullPositionIfLastTermIsNullAndSnapshotsIsEmpty()\n    {\n        assertEquals(NULL_POSITION, replayStartPosition(null, emptyList(), ReplayStart.BEGINNING, mockAeronArchive));\n    }\n\n    @Test\n    void shouldLargestPositionLessThanOrEqualToInitialReplayPosition()\n    {\n        final List<RecordingLog.Snapshot> snapshots = Arrays.asList(\n            new RecordingLog.Snapshot(1, 0, 0, 1000, 0, ConsensusModule.Configuration.SERVICE_ID),\n            new RecordingLog.Snapshot(1, 0, 0, 2000, 0, ConsensusModule.Configuration.SERVICE_ID),\n            new RecordingLog.Snapshot(1, 0, 0, 3000, 0, ConsensusModule.Configuration.SERVICE_ID),\n            new RecordingLog.Snapshot(1, 0, 0, 4000, 0, ConsensusModule.Configuration.SERVICE_ID),\n            new RecordingLog.Snapshot(1, 0, 0, 5000, 0, ConsensusModule.Configuration.SERVICE_ID),\n            new RecordingLog.Snapshot(1, 0, 0, 6000, 0, ConsensusModule.Configuration.SERVICE_ID));\n\n        assertEquals(NULL_POSITION, replayStartPosition(null, snapshots, ReplayStart.BEGINNING, mockAeronArchive));\n        assertEquals(\n            6000, replayStartPosition(null, snapshots, ReplayStart.LATEST_SNAPSHOT, mockAeronArchive));\n    }\n}"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ClusterBackupContextTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\nimport static io.aeron.cluster.codecs.mark.ClusterComponentType.BACKUP;\nimport static io.aeron.cluster.service.ClusterMarkFile.ERROR_BUFFER_MIN_LENGTH;\nimport static io.aeron.cluster.service.ClusterMarkFile.HEADER_LENGTH;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.MARK_FILE_DIR_PROP_NAME;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PAGE_MIN_SIZE;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.startsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ClusterBackupContextTest\n{\n    @TempDir\n    private File clusterDir;\n    private ClusterBackup.Context context;\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void setUp()\n    {\n        final RethrowingErrorHandler errorHandler = mock(RethrowingErrorHandler.class);\n        final Aeron.Context aeronContext = mock(Aeron.Context.class);\n        when(aeronContext.aeronDirectoryName()).thenReturn(\"funny\");\n        when(aeronContext.subscriberErrorHandler()).thenReturn(errorHandler);\n        when(aeronContext.useConductorAgentInvoker()).thenReturn(true);\n        when(aeronContext.filePageSize()).thenReturn(PAGE_MIN_SIZE);\n        final Aeron aeron = mock(Aeron.class);\n        when(aeron.context()).thenReturn(aeronContext);\n        final AtomicCounter errorCounter = mock(AtomicCounter.class);\n        context = new ClusterBackup.Context()\n            .aeron(aeron)\n            .errorCounter(errorCounter)\n            .errorHandler(errorHandler)\n            .clusterDir(clusterDir)\n            .catchupEndpoint(\"something\");\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        context.close();\n    }\n\n    @Test\n    void throwsIllegalStateExceptionIfThereIsAnActiveMarkFile()\n    {\n        final ClusterBackup.Context other = context.clone();\n\n        context.conclude();\n\n        final IllegalStateException exception =\n            assertThrowsExactly(IllegalStateException.class, other::conclude);\n        assertThat(exception.getMessage(), startsWith(\"active mark file detected: \"));\n    }\n\n    @Test\n    void clusterDirectoryNameShouldMatchClusterDirWhenClusterDirSet() throws IOException\n    {\n        context.clusterDir(clusterDir);\n        context.conclude();\n\n        assertEquals(\n            new File(context.clusterDirectoryName()).getCanonicalPath(), context.clusterDir().getCanonicalPath());\n    }\n\n    @Test\n    void clusterDirectoryNameShouldMatchClusterDirWhenClusterDirectoryNameSet() throws IOException\n    {\n        context.clusterDir(null);\n        context.clusterDirectoryName(clusterDir.getAbsolutePath());\n        context.conclude();\n\n        assertEquals(\n            new File(context.clusterDirectoryName()).getCanonicalPath(), context.clusterDir().getCanonicalPath());\n    }\n\n    @Test\n    @Disabled\n    void concludeShouldCreateMarkFileDirSetViaSystemProperty(final @TempDir File tempDir)\n    {\n        final File rootDir = new File(tempDir, \"root\");\n        final File markFileDir = new File(rootDir, \"mark-file-dir\");\n        assertFalse(markFileDir.exists());\n\n        System.setProperty(MARK_FILE_DIR_PROP_NAME, markFileDir.getAbsolutePath());\n        try\n        {\n            assertSame(null, context.markFileDir());\n\n            context.conclude();\n\n            assertEquals(markFileDir, context.markFileDir());\n            assertTrue(markFileDir.exists());\n            assertTrue(new File(context.clusterDir(), ClusterMarkFile.LINK_FILENAME).exists());\n        }\n        finally\n        {\n            System.clearProperty(MARK_FILE_DIR_PROP_NAME);\n        }\n    }\n\n    @Test\n    void concludeShouldCreateMarkFileDirSetDirectly(final @TempDir File tempDir)\n    {\n        final File rootDir = new File(tempDir, \"root\");\n        final File markFileDir = new File(rootDir, \"mark-file-dir\");\n        assertFalse(markFileDir.exists());\n        context.markFileDir(markFileDir);\n\n        context.conclude();\n\n        assertEquals(markFileDir, context.markFileDir());\n        assertTrue(markFileDir.exists());\n        assertTrue(new File(context.clusterDir(), ClusterMarkFile.LINK_FILENAME).exists());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldRemoveLinkIfMarkFileIsInClusterDir(final boolean isSet) throws IOException\n    {\n        final File markFileDir = isSet ? context.clusterDir() : null;\n\n        context.markFileDir(markFileDir);\n        final File oldLinkFile = new File(context.clusterDir(), ClusterMarkFile.LINK_FILENAME);\n        assertTrue(oldLinkFile.createNewFile());\n        assertTrue(oldLinkFile.exists());\n\n        context.conclude();\n\n        assertFalse(oldLinkFile.exists());\n    }\n\n    @Test\n    void concludeShouldCreateLinkFilePointingToTheParentDirectoryOfTheMarkFile(\n        final @TempDir File clusterDir,\n        final @TempDir File markFileDir,\n        final @TempDir File otherDir) throws IOException\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            new File(otherDir, \"abc.xyz\"),\n            BACKUP, ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            4,\n            PAGE_MIN_SIZE);\n        context\n            .clusterDir(clusterDir)\n            .markFileDir(markFileDir)\n            .clusterMarkFile(clusterMarkFile);\n\n        context.conclude();\n\n        assertEquals(clusterDir, context.clusterDir());\n        assertEquals(markFileDir, context.markFileDir());\n        assertEquals(otherDir, context.clusterMarkFile().parentDirectory());\n        final File linkFile = new File(context.clusterDir(), ClusterMarkFile.LINK_FILENAME);\n        assertTrue(linkFile.exists());\n        assertEquals(otherDir.getCanonicalPath(), new String(Files.readAllBytes(linkFile.toPath()), US_ASCII));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 4096, 16 * 1024 })\n    void shouldAlignMarkFileBasedOnTheAeronClientFilePageSize(final int filePageSize)\n    {\n        final Aeron.Context aeronContext = context.aeron().context();\n        when(aeronContext.filePageSize()).thenReturn(filePageSize);\n        context.errorBufferLength(3131311);\n\n        try\n        {\n            context.conclude();\n\n            final File file = new File(context.markFileDir(), ClusterMarkFile.FILENAME);\n            assertTrue(file.exists());\n            assertEquals(BitUtil.align(context.errorBufferLength() + HEADER_LENGTH, filePageSize), file.length());\n\n            verify(aeronContext).filePageSize();\n        }\n        finally\n        {\n            context.close();\n        }\n    }\n\n    @Test\n    void shouldAlignMarkFileBasedOnTheMediaDriverFilePageSize() throws IOException\n    {\n        final Path aeronDir = Paths.get(CommonContext.generateRandomDirName());\n        Files.createDirectories(aeronDir);\n\n        final int filePageSize = 1024 * 1024;\n        try (TestMediaDriver driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.toString())\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .filePageSize(filePageSize), systemTestWatcher))\n        {\n            context\n                .aeron(null)\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .errorBufferLength(1919191);\n\n            context.conclude();\n\n            final File file = new File(context.markFileDir(), ClusterMarkFile.FILENAME);\n            assertTrue(file.exists());\n            assertEquals(BitUtil.align(context.errorBufferLength() + HEADER_LENGTH, filePageSize), file.length());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ClusterMemberTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.ClusterMember.compareLog;\nimport static io.aeron.cluster.ClusterMember.hasQuorumAtPosition;\nimport static io.aeron.cluster.ClusterMember.isQuorumCandidate;\nimport static io.aeron.cluster.ClusterMember.isQuorumLeader;\nimport static io.aeron.cluster.ClusterMember.isUnanimousCandidate;\nimport static io.aeron.cluster.ClusterMember.isUnanimousLeader;\nimport static io.aeron.cluster.ClusterMember.quorumPosition;\nimport static io.aeron.cluster.ClusterMember.quorumThreshold;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass ClusterMemberTest\n{\n    private final ClusterMember[] members = ClusterMember.parse(\n        \"0,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint|\" +\n        \"1,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint|\" +\n        \"2,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint|\");\n\n    private final ClusterMember[] membersWithArchiveResponse = ClusterMember.parse(\n        \"0,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint,archiveResponseEndpoint|\" +\n        \"1,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint,archiveResponseEndpoint|\" +\n        \"2,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint,archiveResponseEndpoint|\");\n\n    private final ClusterMember[] membersWithEgressResponse = ClusterMember.parse(\n        \"0,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint,archiveResponseEndpoint,\" +\n        \"egressResponseEndpoint|\" +\n        \"1,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint,archiveResponseEndpoint,\" +\n        \"egressResponseEndpoint|\" +\n        \"2,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint,archiveResponseEndpoint,\" +\n        \"egressResponseEndpoint|\");\n\n    private final long[] rankedPositions = new long[quorumThreshold(members.length)];\n\n    @Test\n    void shouldParseCorrectly()\n    {\n        for (final ClusterMember member : members)\n        {\n            assertEquals(\"ingressEndpoint\", member.ingressEndpoint());\n            assertEquals(\"consensusEndpoint\", member.consensusEndpoint());\n            assertEquals(\"logEndpoint\", member.logEndpoint());\n            assertEquals(\"catchupEndpoint\", member.catchupEndpoint());\n            assertEquals(\"archiveEndpoint\", member.archiveEndpoint());\n            assertNull(member.archiveResponseEndpoint());\n            assertNull(member.egressResponseEndpoint());\n            assertEquals(\n                \"ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint\", member.endpoints());\n        }\n    }\n\n    @Test\n    void shouldParseCorrectlyOptionalArchiveResponse()\n    {\n        for (final ClusterMember member : membersWithArchiveResponse)\n        {\n            assertEquals(\"ingressEndpoint\", member.ingressEndpoint());\n            assertEquals(\"consensusEndpoint\", member.consensusEndpoint());\n            assertEquals(\"logEndpoint\", member.logEndpoint());\n            assertEquals(\"catchupEndpoint\", member.catchupEndpoint());\n            assertEquals(\"archiveEndpoint\", member.archiveEndpoint());\n            assertEquals(\"archiveResponseEndpoint\", member.archiveResponseEndpoint());\n            assertNull(member.egressResponseEndpoint());\n            assertEquals(\n                \"ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint,archiveResponseEndpoint\",\n                member.endpoints());\n        }\n    }\n\n    @Test\n    void shouldParseCorrectlyOptionalEgressResponseEntry()\n    {\n        for (final ClusterMember member : membersWithEgressResponse)\n        {\n            assertEquals(\"ingressEndpoint\", member.ingressEndpoint());\n            assertEquals(\"consensusEndpoint\", member.consensusEndpoint());\n            assertEquals(\"logEndpoint\", member.logEndpoint());\n            assertEquals(\"catchupEndpoint\", member.catchupEndpoint());\n            assertEquals(\"archiveEndpoint\", member.archiveEndpoint());\n            assertEquals(\"archiveResponseEndpoint\", member.archiveResponseEndpoint());\n            assertEquals(\"egressResponseEndpoint\", member.egressResponseEndpoint());\n            assertEquals(\n                \"ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint,\" +\n                \"archiveResponseEndpoint,egressResponseEndpoint\",\n                member.endpoints());\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"1,1\",\n        \"2,2\",\n        \"3,2\",\n        \"4,3\",\n        \"5,3\",\n        \"6,4\",\n        \"7,4\" })\n    void shouldDetermineQuorumSize(final int clusterSize, final int expectedQuorumSize)\n    {\n        assertEquals(expectedQuorumSize, ClusterMember.quorumThreshold(clusterSize));\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"0,0,0,false\",\n        \"0,0,1,true\",\n        \"0,1,1,false\",\n        \"0,2,1,false\",\n        \"1,2,1,false\",\n        \"1,2,5,true\",\n        \"1,5,5,true\",\n        \"1,8,5,false\",\n        \"10,8,1,true\",\n        \"10,10,1,true\",\n        \"10,12,1,false\"\n    })\n    void shouldCheckMemberIsActive(\n        final long timeOfLastAppendPositionNs,\n        final long nowNs,\n        final long timeoutNs,\n        final boolean expected)\n    {\n        final ClusterMember member = newMember(0).timeOfLastAppendPositionNs(timeOfLastAppendPositionNs);\n        assertEquals(expected, member.isActive(nowNs, timeoutNs));\n    }\n\n    @Test\n    void shouldRankClusterStart()\n    {\n        assertThat(quorumPosition(members, rankedPositions, 0, 10), is(0L));\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"0,0,0,0\",\n        \"123,0,0,0\",\n        \"123,123,0,123\",\n        \"123,123,123,123\",\n        \"0,123,123,123\",\n        \"0,0,123,0\",\n        \"0,123,200,123\",\n        \"5,3,1,3\",\n        \"5,1,3,3\",\n        \"1,3,5,3\",\n        \"1,5,3,3\",\n        \"3,1,5,3\",\n        \"3,5,1,3\"\n    })\n    void shouldDetermineQuorumPosition(\n        final long member0LogPosition,\n        final long member1LogPosition,\n        final long member2LogPosition,\n        final long expectedQuorumPosition)\n    {\n        members[0].logPosition(member0LogPosition);\n        members[1].logPosition(member1LogPosition);\n        members[2].logPosition(member2LogPosition);\n\n        final long quorumPosition = quorumPosition(members, rankedPositions, 0, 10);\n        assertEquals(expectedQuorumPosition, quorumPosition);\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"0,0,0,0,0,0,0,0,0,0,0,10,0\",\n        \"1,0,2,0,3,0,4,0,5,0,5,10,3\",\n        \"1,0,2,0,3,0,2,0,5,0,5,10,2\",\n        \"1,1,2,0,3,1,2,0,5,1,5,5,1\",\n        \"5,1,2,0,3,1,2,0,1,1,5,5,1\",\n        \"5,0,2,1,3,0,2,1,1,0,5,5,0\",\n        \"5,0,2,1,3,0,2,1,1,1,5,5,1\",\n        \"5,0,2,1,3,1,2,1,1,0,5,5,2\",\n        \"5,0,2,2,3,3,2,1,1,5,8,7,1\",\n        \"5,0,2,0,3,0,2,0,1,0,8,7,0\",\n        \"5,5,2,0,3,0,2,0,1,0,8,7,0\",\n        \"5,5,2,5,3,0,2,0,1,0,8,7,0\",\n        \"5,5,2,5,3,5,2,0,1,0,8,7,2\",\n        \"4,5,2,5,4,5,2,0,1,0,8,7,2\",\n        \"4,5,2,5,4,5,2,0,3,3,8,7,3\",\n    })\n    void shouldOnlyConsiderActiveNodesWhenDeterminingQuorumPosition(\n        final long member0LogPosition,\n        final long member0Timestamp,\n        final long member1LogPosition,\n        final long member1Timestamp,\n        final long member2LogPosition,\n        final long member2Timestamp,\n        final long member3LogPosition,\n        final long member3Timestamp,\n        final long member4LogPosition,\n        final long member4Timestamp,\n        final long nowNs,\n        final long timeoutNs,\n        final long expectedQuorumPosition)\n    {\n        final ClusterMember[] clusterMembers = new ClusterMember[]\n        {\n            newMember(0, 0, member0LogPosition, member0Timestamp),\n            newMember(1, 0, member1LogPosition, member1Timestamp),\n            newMember(2, 0, member2LogPosition, member2Timestamp),\n            newMember(3, 0, member3LogPosition, member3Timestamp),\n            newMember(4, 0, member4LogPosition, member4Timestamp)\n        };\n        final long[] positions = new long[quorumThreshold(clusterMembers.length)];\n\n        final long quorumPosition = quorumPosition(clusterMembers, positions, nowNs, timeoutNs);\n        assertEquals(expectedQuorumPosition, quorumPosition);\n    }\n\n    @Test\n    void isUnanimousCandidateReturnFalseIfThereIsAMemberWithoutLogPosition()\n    {\n        final int gracefulClosedLeaderId = NULL_VALUE;\n        final ClusterMember candidate = newMember(4, 100, 1000, 0);\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1, 2, 100, 0),\n            newMember(2, 8, NULL_POSITION, 0),\n            newMember(3, 1, 1, 0)\n        };\n\n        assertFalse(isUnanimousCandidate(members, candidate, gracefulClosedLeaderId));\n    }\n\n    @Test\n    void isUnanimousCandidateReturnFalseIfThereIsAMemberWithMoreUpToDateLog()\n    {\n        final int gracefulClosedLeaderId = NULL_VALUE;\n        final ClusterMember candidate = newMember(4, 10, 800, 0);\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1, 2, 100, 0),\n            newMember(2, 8, 6, 0),\n            newMember(3, 11, 1000, 0)\n        };\n\n        assertFalse(isUnanimousCandidate(members, candidate, gracefulClosedLeaderId));\n    }\n\n    @Test\n    void isUnanimousCandidateReturnFalseIfLeaderClosesGracefully()\n    {\n        final int gracefulClosedLeaderId = 1;\n        final ClusterMember candidate = newMember(2, 2, 100, 0);\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1, 2, 100, 0),\n            newMember(2, 2, 100, 0),\n        };\n\n        assertFalse(isUnanimousCandidate(members, candidate, gracefulClosedLeaderId));\n    }\n\n    @Test\n    void isUnanimousCandidateReturnTrueIfTheCandidateHasTheMostUpToDateLog()\n    {\n        final int gracefulClosedLeaderId = NULL_VALUE;\n        final ClusterMember candidate = newMember(2, 10, 800, 0);\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(10, 2, 100, 0),\n            newMember(20, 8, 6, 0),\n            newMember(30, 10, 800, 0)\n        };\n\n        assertTrue(isUnanimousCandidate(members, candidate, gracefulClosedLeaderId));\n    }\n\n    @Test\n    void isQuorumCandidateReturnFalseWhenQuorumIsNotReached()\n    {\n        final ClusterMember candidate = newMember(2, 10, 800, 0);\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(10, 2, 100, 0),\n            newMember(20, 18, 600, 0),\n            newMember(30, 10, 800, 0),\n            newMember(40, 19, 800, 0),\n            newMember(50, 10, 1000, 0),\n        };\n\n        assertFalse(isQuorumCandidate(members, candidate));\n    }\n\n    @Test\n    void isQuorumCandidateReturnTrueWhenQuorumIsReached()\n    {\n        final ClusterMember candidate = newMember(2, 10, 800, 0);\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(10, 2, 100, 0),\n            newMember(20, 18, 600, 0),\n            newMember(30, 10, 800, 0),\n            newMember(40, 9, 800, 0),\n            newMember(50, 10, 700, 0)\n        };\n\n        assertTrue(isQuorumCandidate(members, candidate));\n    }\n\n    @Test\n    void isQuorumLeaderReturnsTrueWhenQuorumIsReached()\n    {\n        final int candidateTermId = -5;\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(2).candidateTermId(candidateTermId * 2).vote(Boolean.FALSE),\n            newMember(3).candidateTermId(candidateTermId).vote(null),\n            newMember(4).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(5).candidateTermId(candidateTermId).vote(Boolean.TRUE)\n        };\n\n        assertTrue(isQuorumLeader(members, candidateTermId));\n    }\n\n    @Test\n    void isQuorumLeaderReturnsFalseIfAtLeastOneNegativeVoteIsDetected()\n    {\n        final int candidateTermId = 8;\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(2).candidateTermId(candidateTermId).vote(Boolean.FALSE),\n            newMember(3).candidateTermId(candidateTermId).vote(Boolean.TRUE)\n        };\n\n        assertFalse(isQuorumLeader(members, candidateTermId));\n    }\n\n    @Test\n    void isQuorumLeaderReturnsFalseWhenQuorumIsNotReached()\n    {\n        final int candidateTermId = 2;\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(2).candidateTermId(candidateTermId).vote(null),\n            newMember(3).candidateTermId(candidateTermId + 5).vote(Boolean.TRUE)\n        };\n\n        assertFalse(isQuorumLeader(members, candidateTermId));\n    }\n\n    @Test\n    void isUnanimousLeaderReturnsFalseIfThereIsAtLeastOneNegativeVoteForAGivenCandidateTerm()\n    {\n        final int candidateTermId = 42;\n        final int gracefulClosedLeaderId = NULL_VALUE;\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(2).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(3).candidateTermId(candidateTermId).vote(Boolean.FALSE)\n        };\n\n        assertFalse(isUnanimousLeader(members, candidateTermId, gracefulClosedLeaderId));\n    }\n\n    @Test\n    void isUnanimousLeaderReturnsFalseIfLeaderClosesGracefully()\n    {\n        final int candidateTermId = 7;\n        final int gracefulClosedLeaderId = 1;\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(2).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n        };\n\n        assertFalse(isUnanimousLeader(members, candidateTermId, gracefulClosedLeaderId));\n    }\n\n    @Test\n    void isUnanimousLeaderReturnsFalseIfNotAllNodesVotedPositively()\n    {\n        final int candidateTermId = 2;\n        final int gracefulClosedLeaderId = NULL_VALUE;\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(2).candidateTermId(candidateTermId).vote(null),\n            newMember(3).candidateTermId(candidateTermId).vote(Boolean.TRUE)\n        };\n\n        assertFalse(isUnanimousLeader(members, candidateTermId, gracefulClosedLeaderId));\n    }\n\n    @Test\n    void isUnanimousLeaderReturnsFalseIfNotAllNodesHadTheExpectedCandidateTermId()\n    {\n        final int candidateTermId = 2;\n        final int gracefulClosedLeaderId = NULL_VALUE;\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(2).candidateTermId(candidateTermId + 1).vote(Boolean.TRUE),\n            newMember(3).candidateTermId(candidateTermId).vote(Boolean.TRUE)\n        };\n\n        assertFalse(isUnanimousLeader(members, candidateTermId, gracefulClosedLeaderId));\n    }\n\n    @Test\n    void isUnanimousLeaderReturnsTrueIfAllNodesVotedWithTrue()\n    {\n        final int candidateTermId = 42;\n        final int gracefulClosedLeaderId = NULL_VALUE;\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(1).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(2).candidateTermId(candidateTermId).vote(Boolean.TRUE),\n            newMember(3).candidateTermId(candidateTermId).vote(Boolean.TRUE)\n        };\n\n        assertTrue(isUnanimousLeader(members, candidateTermId, gracefulClosedLeaderId));\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"5,1000,3,999999,1\",\n        \"-100,99999,4,0,-1\",\n        \"42,371239192371239,42,1001,1\",\n        \"3,-777,3,273291846723894,-1\",\n        \"1,1024,1,1024,0\",\n    })\n    void compareLogReturnsResult(\n        final long lhsLogLeadershipTermId,\n        final long lhsLogPosition,\n        final long rhsLogLeadershipTermId,\n        final long rhsLogPosition,\n        final int expectedResult)\n    {\n        assertEquals(\n            expectedResult,\n            compareLog(lhsLogLeadershipTermId, lhsLogPosition, rhsLogLeadershipTermId, rhsLogPosition));\n        assertEquals(expectedResult, compareLog(\n            newMember(5, lhsLogLeadershipTermId, lhsLogPosition, 0),\n            newMember(100, rhsLogLeadershipTermId, rhsLogPosition, 0)));\n    }\n\n    @Test\n    void shouldNotVoteIfHasNoPosition()\n    {\n        final ClusterMember member = newMember(1, 0, NULL_POSITION, 0);\n        final ClusterMember candidate = newMember(2, 0, 100, 0);\n        assertFalse(member.willVoteFor(candidate));\n    }\n\n    @Test\n    void shouldNotVoteIfHasMoreLog()\n    {\n        final ClusterMember member = newMember(1, 0, 500, 0);\n        final ClusterMember candidate = newMember(2, 0, 100, 0);\n        assertFalse(member.willVoteFor(candidate));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 0, 900, 1024 })\n    void shouldVoteIfHasLessOrTheSameAmountOfLog(final long logPosition)\n    {\n        final ClusterMember member = newMember(1, 0, logPosition, 0);\n        final ClusterMember candidate = newMember(2, 0, 1024, 0);\n        assertTrue(member.willVoteFor(candidate));\n    }\n\n    @Test\n    void shouldReturnFalseIfNotActiveWhenDoingPositionChecks()\n    {\n        final long leadershipTermId = 42;\n        final long logPosition = 500;\n        final ClusterMember member = newMember(1, leadershipTermId, logPosition, 0);\n        assertFalse(member.hasReachedPosition(leadershipTermId, logPosition, 5, 3));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 0, 10 })\n    void shouldReturnFalseIfLeadershipTermIdDoesNotMatch(final long leadershipTermId)\n    {\n        final long logPosition = 500;\n        final ClusterMember member = newMember(1, 6, logPosition, 0);\n        assertFalse(member.hasReachedPosition(leadershipTermId, logPosition, 5, 10));\n    }\n\n    @Test\n    void shouldReturnFalseIfLogPositionIsLessThan()\n    {\n        final long leadershipTermId = 6;\n        final long logPosition = 500;\n        final ClusterMember member = newMember(1, leadershipTermId, 100, 0);\n        assertFalse(member.hasReachedPosition(leadershipTermId, logPosition, 5, 10));\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 100, Long.MAX_VALUE })\n    void shouldReturnTrueIfLogPositionIsEqualOrGreaterThan(final long logPosition)\n    {\n        final long leadershipTermId = 42;\n        final ClusterMember member = newMember(1, leadershipTermId, logPosition, 10);\n        assertTrue(member.hasReachedPosition(leadershipTermId, 100, 12, 5));\n    }\n\n    @Test\n    void hasQuorumAtPositionReturnFalseIfNotAQuorum()\n    {\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(10, 2, 100, 0),\n            newMember(20, 18, 600, 0),\n            newMember(30, 10, 800, 0),\n            newMember(40, 19, 800, 0),\n            newMember(50, 10, 1000, 0),\n        };\n\n        assertFalse(hasQuorumAtPosition(members, 10, 800, 0, 10));\n    }\n\n    @Test\n    void hasQuorumAtPositionReturnTrueIfQuorumIsAtPosition()\n    {\n        final ClusterMember[] members = new ClusterMember[]\n        {\n            newMember(10, 2, 100, 0),\n            newMember(20, 10, 600, 0),\n            newMember(30, 10, 800, 0),\n            newMember(40, 19, 800, 0),\n            newMember(50, 10, 1000, 0),\n        };\n\n        assertTrue(hasQuorumAtPosition(members, 10, 600, 5, 10));\n    }\n\n    private static ClusterMember newMember(\n        final int id, final long leadershipTermId, final long logPosition, final long timeOfLastAppendPositionNs)\n    {\n        return newMember(id)\n            .leadershipTermId(leadershipTermId)\n            .logPosition(logPosition)\n            .timeOfLastAppendPositionNs(timeOfLastAppendPositionNs);\n    }\n\n    private static ClusterMember newMember(final int id)\n    {\n        return new ClusterMember(id, null, null, null, null, null, null);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ClusterNodeRestartTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.*;\nimport io.aeron.test.cluster.ClusterTests;\nimport io.aeron.test.cluster.StubClusteredService;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static io.aeron.cluster.ClusterTestConstants.CLUSTER_MEMBERS;\nimport static io.aeron.cluster.ClusterTestConstants.INGRESS_ENDPOINTS;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\n@ExtendWith({InterruptingTestCallback.class, HideStdErrExtension.class})\nclass ClusterNodeRestartTest\n{\n    private static final int MESSAGE_LENGTH = SIZE_OF_INT;\n    private static final int TIMER_MESSAGE_LENGTH = SIZE_OF_INT + SIZE_OF_LONG + SIZE_OF_LONG;\n    private static final int MESSAGE_VALUE_OFFSET = 0;\n    private static final int TIMER_MESSAGE_ID_OFFSET = MESSAGE_VALUE_OFFSET + SIZE_OF_INT;\n    private static final int TIMER_MESSAGE_DELAY_OFFSET = TIMER_MESSAGE_ID_OFFSET + SIZE_OF_LONG;\n    public static final long TIMER_CORRELATION_ID = 777;\n\n    private ClusteredMediaDriver clusteredMediaDriver;\n    private ClusteredServiceContainer container;\n    private AeronCluster aeronCluster;\n\n    private final ExpandableArrayBuffer msgBuffer = new ExpandableArrayBuffer();\n    private final AtomicReference<String> serviceState = new AtomicReference<>();\n    private final CountDownLatch terminationLatch = new CountDownLatch(1);\n\n    @BeforeEach\n    void before()\n    {\n        launchClusteredMediaDriver(true);\n    }\n\n    @AfterEach\n    void after()\n    {\n        if (null == clusteredMediaDriver)\n        {\n            CloseHelper.closeAll(aeronCluster, container);\n        }\n        else\n        {\n            CloseHelper.closeAll(clusteredMediaDriver.consensusModule(), aeronCluster, container, clusteredMediaDriver);\n            clusteredMediaDriver.consensusModule().context().deleteDirectory();\n            clusteredMediaDriver.archive().context().deleteDirectory();\n            clusteredMediaDriver.mediaDriver().context().deleteDirectory();\n            container.context().deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRestartServiceWithReplay()\n    {\n        final AtomicLong serviceMsgCount = new AtomicLong(0);\n        final AtomicLong restartServiceMsgCount = new AtomicLong(0);\n\n        launchService(serviceMsgCount);\n        connectClient();\n\n        sendNumberedMessageIntoCluster(0);\n        Tests.awaitValue(serviceMsgCount, 1);\n\n        forceCloseForRestart();\n\n        launchClusteredMediaDriver(false);\n        launchService(restartServiceMsgCount);\n        connectClient();\n\n        Tests.awaitValue(restartServiceMsgCount, 1);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRestartServiceWithReplayAndContinue()\n    {\n        final AtomicLong serviceMsgCount = new AtomicLong(0);\n\n        launchService(serviceMsgCount);\n        connectClient();\n\n        sendNumberedMessageIntoCluster(0);\n        Tests.awaitValue(serviceMsgCount, 1);\n\n        forceCloseForRestart();\n\n        final AtomicLong restartServiceMsgCounter = new AtomicLong(0);\n\n        launchClusteredMediaDriver(false);\n        launchService(restartServiceMsgCounter);\n        connectClient();\n\n        sendNumberedMessageIntoCluster(1);\n        Tests.awaitValue(restartServiceMsgCounter, 1);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRestartServiceFromEmptySnapshot()\n    {\n        final AtomicLong serviceMsgCount = new AtomicLong(0);\n\n        launchService(serviceMsgCount);\n        connectClient();\n\n        final AtomicCounter controlToggle = getControlToggle();\n        assertTrue(ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle));\n\n        Tests.awaitValue(clusteredMediaDriver.consensusModule().context().snapshotCounter(), 1);\n\n        forceCloseForRestart();\n\n        serviceState.set(null);\n        launchClusteredMediaDriver(false);\n        launchService(serviceMsgCount);\n        connectClient();\n\n        Tests.await(() -> null != serviceState.get());\n        assertEquals(\"0\", serviceState.get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRestartServiceFromSnapshot()\n    {\n        final AtomicLong serviceMsgCount = new AtomicLong(0);\n\n        launchService(serviceMsgCount);\n        connectClient();\n\n        sendNumberedMessageIntoCluster(0);\n        sendNumberedMessageIntoCluster(1);\n        sendNumberedMessageIntoCluster(2);\n\n        Tests.awaitValue(serviceMsgCount, 3);\n\n        final AtomicCounter controlToggle = getControlToggle();\n        assertTrue(ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle));\n\n        Tests.awaitValue(clusteredMediaDriver.consensusModule().context().snapshotCounter(), 1);\n\n        forceCloseForRestart();\n\n        serviceState.set(null);\n        launchClusteredMediaDriver(false);\n        launchService(serviceMsgCount);\n        connectClient();\n\n        Tests.await(() -> null != serviceState.get());\n        assertEquals(\"3\", serviceState.get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRestartServiceFromSnapshotWithFurtherLog()\n    {\n        final AtomicLong serviceMsgCount = new AtomicLong(0);\n\n        launchService(serviceMsgCount);\n        connectClient();\n\n        sendNumberedMessageIntoCluster(0);\n        sendNumberedMessageIntoCluster(1);\n        sendNumberedMessageIntoCluster(2);\n\n        Tests.awaitValue(serviceMsgCount, 3);\n\n        final AtomicCounter controlToggle = getControlToggle();\n        assertTrue(ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle));\n\n        Tests.awaitValue(clusteredMediaDriver.consensusModule().context().snapshotCounter(), 1);\n\n        sendNumberedMessageIntoCluster(3);\n        Tests.awaitValue(serviceMsgCount, 4);\n\n        forceCloseForRestart();\n\n        serviceMsgCount.set(0);\n        launchClusteredMediaDriver(false);\n        launchService(serviceMsgCount);\n        connectClient();\n\n        Tests.awaitValue(serviceMsgCount, 1);\n        assertEquals(\"4\", serviceState.get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldTakeMultipleSnapshots()\n    {\n        final AtomicLong serviceMsgCount = new AtomicLong(0);\n        launchService(serviceMsgCount);\n        connectClient();\n\n        final AtomicCounter controlToggle = getControlToggle();\n\n        for (int i = 0; i < 3; i++)\n        {\n            assertTrue(ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle));\n\n            while (controlToggle.get() != ClusterControl.ToggleState.NEUTRAL.code())\n            {\n                Tests.sleep(1, \"snapshot %d\", i);\n            }\n        }\n\n        assertEquals(3L, clusteredMediaDriver.consensusModule().context().snapshotCounter().get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRestartServiceWithTimerFromSnapshotWithFurtherLog()\n    {\n        final AtomicLong serviceMsgCount = new AtomicLong(0);\n\n        launchService(serviceMsgCount);\n        connectClient();\n\n        sendNumberedMessageIntoCluster(0);\n        sendNumberedMessageIntoCluster(1);\n        sendNumberedMessageIntoCluster(2);\n        sendTimerMessageIntoCluster(3, TimeUnit.HOURS.toMillis(10));\n\n        Tests.awaitValue(serviceMsgCount, 4);\n\n        final AtomicCounter controlToggle = getControlToggle();\n        assertTrue(ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle));\n\n        Tests.awaitValue(clusteredMediaDriver.consensusModule().context().snapshotCounter(), 1);\n\n        sendNumberedMessageIntoCluster(4);\n        Tests.awaitValue(serviceMsgCount, 5);\n\n        forceCloseForRestart();\n\n        serviceMsgCount.set(0);\n        launchClusteredMediaDriver(false);\n        launchService(serviceMsgCount);\n        connectClient();\n\n        Tests.awaitValue(serviceMsgCount, 1);\n        assertEquals(\"5\", serviceState.get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldTriggerRescheduledTimerAfterReplay()\n    {\n        final AtomicLong triggeredTimersCount = new AtomicLong();\n\n        launchReschedulingService(triggeredTimersCount);\n        connectClient();\n\n        sendNumberedMessageIntoCluster(0);\n        Tests.awaitValue(triggeredTimersCount, 2);\n\n        forceCloseForRestart();\n\n        final long triggeredSinceStart = triggeredTimersCount.getAndSet(0);\n\n        launchClusteredMediaDriver(false);\n        launchReschedulingService(triggeredTimersCount);\n\n        Tests.awaitValue(triggeredTimersCount, triggeredSinceStart + 1);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldRestartServiceTwiceWithInvalidSnapshotAndFurtherLog()\n    {\n        final AtomicLong serviceMsgCount = new AtomicLong(0);\n\n        launchService(serviceMsgCount);\n        connectClient();\n\n        sendNumberedMessageIntoCluster(0);\n        sendNumberedMessageIntoCluster(1);\n        sendNumberedMessageIntoCluster(2);\n\n        Tests.awaitValue(serviceMsgCount, 3);\n\n        final AtomicCounter controlToggle = getControlToggle();\n        assertTrue(ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle));\n\n        Tests.awaitValue(clusteredMediaDriver.consensusModule().context().snapshotCounter(), 1);\n\n        sendNumberedMessageIntoCluster(3);\n        Tests.awaitValue(serviceMsgCount, 4);\n\n        forceCloseForRestart();\n\n        final PrintStream mockOut = mock(PrintStream.class);\n        final File clusterDir = clusteredMediaDriver.consensusModule().context().clusterDir();\n        assertTrue(ClusterTool.invalidateLatestSnapshot(mockOut, clusterDir));\n\n        verify(mockOut).println(\" invalidate latest snapshot: true\");\n\n        serviceMsgCount.set(0);\n        launchClusteredMediaDriver(false);\n        launchService(serviceMsgCount);\n\n        Tests.awaitValue(serviceMsgCount, 4);\n        assertEquals(\"4\", serviceState.get());\n\n        connectClient();\n        sendNumberedMessageIntoCluster(4);\n        Tests.awaitValue(serviceMsgCount, 5);\n\n        forceCloseForRestart();\n\n        serviceMsgCount.set(0);\n        launchClusteredMediaDriver(false);\n        launchService(serviceMsgCount);\n\n        connectClient();\n        assertEquals(\"5\", serviceState.get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(20)\n    @IgnoreStdErr\n    void shouldRestartServiceAfterShutdownWithInvalidatedSnapshot() throws InterruptedException\n    {\n        final AtomicLong serviceMsgCount = new AtomicLong(0);\n\n        launchService(serviceMsgCount);\n        connectClient();\n\n        sendNumberedMessageIntoCluster(0);\n        sendNumberedMessageIntoCluster(1);\n        sendNumberedMessageIntoCluster(2);\n\n        Tests.awaitValue(serviceMsgCount, 3);\n\n        final AtomicCounter controlToggle = getControlToggle();\n        assertTrue(ClusterControl.ToggleState.SHUTDOWN.toggle(controlToggle));\n\n        terminationLatch.await();\n        forceCloseForRestart();\n\n        final PrintStream mockOut = mock(PrintStream.class);\n        final File clusterDir = clusteredMediaDriver.consensusModule().context().clusterDir();\n        assertTrue(ClusterTool.invalidateLatestSnapshot(mockOut, clusterDir));\n\n        verify(mockOut).println(\" invalidate latest snapshot: true\");\n\n        serviceMsgCount.set(0);\n        launchClusteredMediaDriver(false);\n        launchService(serviceMsgCount);\n\n        Tests.awaitValue(serviceMsgCount, 3);\n        assertEquals(\"3\", serviceState.get());\n\n        connectClient();\n        sendNumberedMessageIntoCluster(3);\n        Tests.awaitValue(serviceMsgCount, 4);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    private AtomicCounter getControlToggle()\n    {\n        final int clusterId = container.context().clusterId();\n        final CountersReader counters = container.context().aeron().countersReader();\n        final AtomicCounter controlToggle = ClusterControl.findControlToggle(counters, clusterId);\n        assertNotNull(controlToggle);\n\n        return controlToggle;\n    }\n\n    private void sendNumberedMessageIntoCluster(final int value)\n    {\n        msgBuffer.putInt(MESSAGE_VALUE_OFFSET, value);\n\n        sendMessageIntoCluster(aeronCluster, msgBuffer, MESSAGE_LENGTH);\n    }\n\n    private void sendTimerMessageIntoCluster(final int value, final long delay)\n    {\n        msgBuffer.putInt(MESSAGE_VALUE_OFFSET, value);\n        msgBuffer.putLong(TIMER_MESSAGE_ID_OFFSET, TIMER_CORRELATION_ID);\n        msgBuffer.putLong(TIMER_MESSAGE_DELAY_OFFSET, delay);\n\n        sendMessageIntoCluster(aeronCluster, msgBuffer, TIMER_MESSAGE_LENGTH);\n    }\n\n    private static void sendMessageIntoCluster(final AeronCluster cluster, final DirectBuffer buffer, final int length)\n    {\n        while (true)\n        {\n            final long result = cluster.offer(buffer, 0, length);\n            if (result > 0)\n            {\n                break;\n            }\n\n            checkResult(result);\n            if (cluster.isClosed())\n            {\n                fail(\"unexpected cluster client close\");\n            }\n\n            Tests.yield();\n        }\n    }\n\n    private void launchService(final AtomicLong msgCounter)\n    {\n        final ClusteredService service = new StubClusteredService()\n        {\n            private int nextCorrelationId = 0;\n            private int counterValue = 0;\n\n            public void onStart(final Cluster cluster, final Image snapshotImage)\n            {\n                super.onStart(cluster, snapshotImage);\n\n                if (null != snapshotImage)\n                {\n                    final FragmentHandler fragmentHandler =\n                        (buffer, offset, length, header) ->\n                        {\n                            nextCorrelationId = buffer.getInt(offset);\n                            offset += SIZE_OF_INT;\n\n                            counterValue = buffer.getInt(offset);\n                            offset += SIZE_OF_INT;\n\n                            serviceState.set(buffer.getStringAscii(offset));\n                        };\n\n                    while (true)\n                    {\n                        final int fragments = snapshotImage.poll(fragmentHandler, 1);\n                        if (fragments == 1 || snapshotImage.isEndOfStream())\n                        {\n                            break;\n                        }\n\n                        idleStrategy.idle();\n                    }\n                }\n            }\n\n            public void onSessionMessage(\n                final ClientSession session,\n                final long timestamp,\n                final DirectBuffer buffer,\n                final int offset,\n                final int length,\n                final Header header)\n            {\n                final int sentValue = buffer.getInt(offset + MESSAGE_VALUE_OFFSET);\n                assertEquals(counterValue, sentValue);\n\n                counterValue++;\n                serviceState.set(Integer.toString(counterValue));\n                msgCounter.getAndIncrement();\n\n                if (TIMER_MESSAGE_LENGTH == length)\n                {\n                    final long correlationId = serviceCorrelationId(nextCorrelationId++);\n                    final long deadlineMs = timestamp + buffer.getLong(offset + TIMER_MESSAGE_DELAY_OFFSET);\n\n                    idleStrategy.reset();\n                    while (!cluster.scheduleTimer(correlationId, deadlineMs))\n                    {\n                        idleStrategy.idle();\n                    }\n                }\n            }\n\n            public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n            {\n                final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n\n                int length = 0;\n                buffer.putInt(length, nextCorrelationId);\n                length += SIZE_OF_INT;\n\n                buffer.putInt(length, counterValue);\n                length += SIZE_OF_INT;\n\n                length += buffer.putStringAscii(length, Integer.toString(counterValue));\n\n                snapshotPublication.offer(buffer, 0, length);\n            }\n        };\n\n        container = ClusteredServiceContainer.launch(\n            new ClusteredServiceContainer.Context()\n                .clusteredService(service)\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .errorHandler(ClusterTests.errorHandler(0)));\n    }\n\n    private void launchReschedulingService(final AtomicLong triggeredTimersCounter)\n    {\n        final ClusteredService service = new StubClusteredService()\n        {\n            public void onSessionMessage(\n                final ClientSession session,\n                final long timestamp,\n                final DirectBuffer buffer,\n                final int offset,\n                final int length,\n                final Header header)\n            {\n                scheduleNext(serviceCorrelationId(7), timestamp + 200);\n            }\n\n            public void onTimerEvent(final long correlationId, final long timestamp)\n            {\n                triggeredTimersCounter.getAndIncrement();\n                scheduleNext(correlationId, timestamp + 200);\n            }\n\n            public void onStart(final Cluster cluster, final Image snapshotImage)\n            {\n                super.onStart(cluster, snapshotImage);\n\n                if (null != snapshotImage)\n                {\n                    final FragmentHandler fragmentHandler =\n                        (buffer, offset, length, header) -> triggeredTimersCounter.set(buffer.getLong(offset));\n\n                    while (true)\n                    {\n                        final int fragments = snapshotImage.poll(fragmentHandler, 1);\n                        if (fragments == 1 || snapshotImage.isEndOfStream())\n                        {\n                            break;\n                        }\n\n                        idleStrategy.idle();\n                    }\n                }\n            }\n\n            public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n            {\n                final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n                buffer.putLong(0, triggeredTimersCounter.get());\n\n                idleStrategy.reset();\n                while (snapshotPublication.offer(buffer, 0, SIZE_OF_INT) < 0)\n                {\n                    idleStrategy.idle();\n                }\n            }\n\n            private void scheduleNext(final long correlationId, final long deadline)\n            {\n                idleStrategy.reset();\n                while (!cluster.scheduleTimer(correlationId, deadline))\n                {\n                    idleStrategy.idle();\n                }\n            }\n        };\n\n        container = ClusteredServiceContainer.launch(\n            new ClusteredServiceContainer.Context()\n                .clusteredService(service)\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .errorHandler(ClusterTests.errorHandler(0)));\n    }\n\n    private void forceCloseForRestart()\n    {\n        CloseHelper.closeAll(clusteredMediaDriver.consensusModule(), aeronCluster, container, clusteredMediaDriver);\n    }\n\n    private void connectClient()\n    {\n        CloseHelper.close(aeronCluster);\n        aeronCluster = AeronCluster.connect(new AeronCluster.Context()\n                .ingressChannel(\"aeron:udp\")\n                .ingressEndpoints(INGRESS_ENDPOINTS)\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\"));\n    }\n\n    private void launchClusteredMediaDriver(final boolean initialLaunch)\n    {\n        clusteredMediaDriver = ClusteredMediaDriver.launch(\n            new MediaDriver.Context()\n                .warnIfDirectoryExists(initialLaunch)\n                .threadingMode(ThreadingMode.SHARED)\n                .termBufferSparseFile(true)\n                .errorHandler(ClusterTests.errorHandler(0))\n                .dirDeleteOnStart(true),\n            TestContexts.localhostArchive()\n                .catalogCapacity(ClusterTestConstants.CATALOG_CAPACITY)\n                .recordingEventsEnabled(false)\n                .threadingMode(ArchiveThreadingMode.SHARED)\n                .deleteArchiveOnStart(initialLaunch),\n            new ConsensusModule.Context()\n                .errorHandler(ClusterTests.errorHandler(0))\n                .terminationHook(terminationLatch::countDown)\n                .deleteDirOnStart(initialLaunch)\n                .ingressChannel(\"aeron:udp\")\n                .clusterMembers(CLUSTER_MEMBERS)\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\"));\n    }\n\n    private static void checkResult(final long position)\n    {\n        if (position == Publication.NOT_CONNECTED ||\n            position == Publication.CLOSED ||\n            position == Publication.MAX_POSITION_EXCEEDED)\n        {\n            throw new IllegalStateException(\"unexpected publication state: \" + Publication.errorString(position));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ClusterNodeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.EgressListener;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.ClusterTests;\nimport io.aeron.test.cluster.StubClusteredService;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport static io.aeron.cluster.ClusterTestConstants.CLUSTER_MEMBERS;\nimport static io.aeron.cluster.ClusterTestConstants.INGRESS_ENDPOINTS;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ClusterNodeTest\n{\n    private ClusteredMediaDriver clusteredMediaDriver;\n    private ClusteredServiceContainer container;\n    private AeronCluster aeronCluster;\n\n    @BeforeEach\n    void before()\n    {\n        clusteredMediaDriver = ClusteredMediaDriver.launch(\n            new MediaDriver.Context()\n                .threadingMode(ThreadingMode.SHARED)\n                .termBufferSparseFile(true)\n                .errorHandler(ClusterTests.errorHandler(0))\n                .dirDeleteOnStart(true),\n            TestContexts.localhostArchive()\n                .catalogCapacity(ClusterTestConstants.CATALOG_CAPACITY)\n                .threadingMode(ArchiveThreadingMode.SHARED)\n                .recordingEventsEnabled(false)\n                .deleteArchiveOnStart(true),\n            new ConsensusModule.Context()\n                .errorHandler(ClusterTests.errorHandler(0))\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .clusterMembers(CLUSTER_MEMBERS)\n                .ingressChannel(\"aeron:udp\")\n                .logChannel(\"aeron:ipc\")\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .deleteDirOnStart(true));\n    }\n\n    @AfterEach\n    void after()\n    {\n        final ConsensusModule consensusModule = null == clusteredMediaDriver ?\n            null : clusteredMediaDriver.consensusModule();\n\n        CloseHelper.closeAll(aeronCluster, consensusModule, container, clusteredMediaDriver);\n\n        if (null != clusteredMediaDriver)\n        {\n            clusteredMediaDriver.consensusModule().context().deleteDirectory();\n            clusteredMediaDriver.archive().context().deleteDirectory();\n            clusteredMediaDriver.mediaDriver().context().deleteDirectory();\n            container.context().deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldConnectAndSendKeepAlive()\n    {\n        container = launchEchoService();\n        aeronCluster = connectToCluster(null);\n\n        assertTrue(aeronCluster.sendKeepAlive());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldEchoMessageViaServiceUsingDirectOffer()\n    {\n        final ExpandableArrayBuffer msgBuffer = new ExpandableArrayBuffer();\n        final String msg = \"Hello World!\";\n        msgBuffer.putStringWithoutLengthAscii(0, msg);\n\n        final MutableInteger messageCount = new MutableInteger();\n\n        final EgressListener listener =\n            (clusterSessionId, timestamp, buffer, offset, length, header) ->\n            {\n                assertEquals(msg, buffer.getStringWithoutLengthAscii(offset, length));\n                messageCount.value += 1;\n            };\n\n        container = launchEchoService();\n        aeronCluster = connectToCluster(listener);\n\n        offerMessage(msgBuffer, msg);\n        awaitResponse(messageCount);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldEchoMessageViaServiceUsingTryClaim()\n    {\n        final ExpandableArrayBuffer msgBuffer = new ExpandableArrayBuffer();\n        final String msg = \"Hello World!\";\n        msgBuffer.putStringWithoutLengthAscii(0, msg);\n\n        final MutableInteger messageCount = new MutableInteger();\n\n        final EgressListener listener =\n            (clusterSessionId, timestamp, buffer, offset, length, header) ->\n            {\n                assertEquals(msg, buffer.getStringWithoutLengthAscii(offset, length));\n                messageCount.value += 1;\n            };\n\n        container = launchEchoService();\n        aeronCluster = connectToCluster(listener);\n\n        final BufferClaim bufferClaim = new BufferClaim();\n        long publicationResult;\n        do\n        {\n            publicationResult = aeronCluster.tryClaim(msg.length(), bufferClaim);\n            if (publicationResult > 0)\n            {\n                final int offset = bufferClaim.offset() + AeronCluster.SESSION_HEADER_LENGTH;\n                bufferClaim.buffer().putBytes(offset, msgBuffer, 0, msg.length());\n                bufferClaim.commit();\n            }\n            else\n            {\n                Tests.yield();\n            }\n        }\n        while (publicationResult < 0);\n\n        offerMessage(msgBuffer, msg);\n        awaitResponse(messageCount);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldScheduleEventInService()\n    {\n        final ExpandableArrayBuffer msgBuffer = new ExpandableArrayBuffer();\n        final String msg = \"Hello World!\";\n        msgBuffer.putStringWithoutLengthAscii(0, msg);\n\n        final MutableInteger messageCount = new MutableInteger();\n\n        final EgressListener listener =\n            (clusterSessionId, timestamp, buffer, offset, length, header) ->\n            {\n                final String expected = msg + \"-scheduled\";\n                assertEquals(expected, buffer.getStringWithoutLengthAscii(offset, length));\n                messageCount.value += 1;\n            };\n\n        container = launchTimedService();\n        aeronCluster = connectToCluster(listener);\n\n        offerMessage(msgBuffer, msg);\n        awaitResponse(messageCount);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSendResponseAfterServiceMessage()\n    {\n        final ExpandableArrayBuffer msgBuffer = new ExpandableArrayBuffer();\n        final String msg = \"Hello World!\";\n        msgBuffer.putStringWithoutLengthAscii(0, msg);\n\n        final MutableInteger messageCount = new MutableInteger();\n\n        final EgressListener listener =\n            (clusterSessionId, timestamp, buffer, offset, length, header) ->\n            {\n                assertEquals(msg, buffer.getStringWithoutLengthAscii(offset, length));\n                messageCount.value += 1;\n            };\n\n        container = launchServiceMessageIngressService();\n        aeronCluster = connectToCluster(listener);\n\n        offerMessage(msgBuffer, msg);\n        awaitResponse(messageCount);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    private void offerMessage(final ExpandableArrayBuffer msgBuffer, final String msg)\n    {\n        while (aeronCluster.offer(msgBuffer, 0, msg.length()) < 0)\n        {\n            Tests.yield();\n        }\n    }\n\n    private void awaitResponse(final MutableInteger messageCount)\n    {\n        while (messageCount.get() == 0)\n        {\n            if (aeronCluster.pollEgress() <= 0)\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    private ClusteredServiceContainer launchEchoService()\n    {\n        final ClusteredService clusteredService = new StubClusteredService()\n        {\n            public void onSessionMessage(\n                final ClientSession session,\n                final long timestamp,\n                final DirectBuffer buffer,\n                final int offset,\n                final int length,\n                final Header header)\n            {\n                echoMessage(session, buffer, offset, length);\n            }\n        };\n\n        return ClusteredServiceContainer.launch(\n            new ClusteredServiceContainer.Context()\n                .clusteredService(clusteredService)\n                .errorHandler(Tests::onError));\n    }\n\n    private ClusteredServiceContainer launchTimedService()\n    {\n        final ClusteredService clusteredService = new StubClusteredService()\n        {\n            private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n            private long clusterSessionId;\n            private int nextCorrelationId;\n            private String msg;\n\n            public void onSessionMessage(\n                final ClientSession session,\n                final long timestamp,\n                final DirectBuffer buffer,\n                final int offset,\n                final int length,\n                final Header header)\n            {\n                clusterSessionId = session.id();\n                msg = buffer.getStringWithoutLengthAscii(offset, length);\n                final long correlationId = serviceCorrelationId(nextCorrelationId++);\n\n                idleStrategy.reset();\n                while (!cluster.scheduleTimer(correlationId, timestamp + 100))\n                {\n                    idleStrategy.idle();\n                }\n            }\n\n            public void onTimerEvent(final long correlationId, final long timestamp)\n            {\n                final String responseMsg = msg + \"-scheduled\";\n                buffer.putStringWithoutLengthAscii(0, responseMsg);\n                final ClientSession clientSession = cluster.getClientSession(clusterSessionId);\n\n                echoMessage(clientSession, buffer, 0, responseMsg.length());\n            }\n        };\n\n        return ClusteredServiceContainer.launch(\n            new ClusteredServiceContainer.Context()\n                .clusteredService(clusteredService)\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .errorHandler(ClusterTests.errorHandler(0)));\n    }\n\n    private ClusteredServiceContainer launchServiceMessageIngressService()\n    {\n        final ClusteredService clusteredService = new StubClusteredService()\n        {\n            public void onSessionMessage(\n                final ClientSession session,\n                final long timestamp,\n                final DirectBuffer buffer,\n                final int offset,\n                final int length,\n                @SuppressWarnings(\"unused\") final Header header)\n            {\n                if (null != session)\n                {\n                    idleStrategy.reset();\n                    while (cluster.offer(buffer, offset, length) < 0)\n                    {\n                        idleStrategy.idle();\n                    }\n                }\n                else\n                {\n                    cluster.forEachClientSession((clientSession) -> echoMessage(clientSession, buffer, offset, length));\n                }\n            }\n        };\n\n        return ClusteredServiceContainer.launch(\n            new ClusteredServiceContainer.Context()\n                .clusteredService(clusteredService)\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .errorHandler(ClusterTests.errorHandler(0)));\n    }\n\n    private AeronCluster connectToCluster(final EgressListener egressListener)\n    {\n        return AeronCluster.connect(\n            new AeronCluster.Context()\n                .egressListener(egressListener)\n                .ingressChannel(\"aeron:udp\")\n                .ingressEndpoints(INGRESS_ENDPOINTS)\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\"));\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ClusterTestConstants.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nclass ClusterTestConstants\n{\n    static final String INGRESS_ENDPOINT = \"localhost:20000\";\n    static final String INGRESS_ENDPOINTS = \"0=\" + INGRESS_ENDPOINT;\n    static final String CLUSTER_MEMBERS =\n        \"0,\" + INGRESS_ENDPOINT + \",localhost:20001,localhost:20002,localhost:0,localhost:8010\";\n    static final long CATALOG_CAPACITY = 1024 * 1024;\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ClusterTimerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.ClusterTests;\nimport io.aeron.test.cluster.StubClusteredService;\nimport org.agrona.CloseHelper;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static io.aeron.cluster.ClusterTestConstants.CLUSTER_MEMBERS;\nimport static io.aeron.cluster.ClusterTestConstants.INGRESS_ENDPOINTS;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nabstract class ClusterTimerTest\n{\n    private static final int INTERVAL_MS = 20;\n\n    private ClusteredMediaDriver clusteredMediaDriver;\n    private ClusteredServiceContainer container;\n    private AeronCluster aeronCluster;\n\n    @BeforeEach\n    void before()\n    {\n        launchClusteredMediaDriver(true);\n    }\n\n    @AfterEach\n    void after()\n    {\n        final ConsensusModule consensusModule = null == clusteredMediaDriver ?\n            null : clusteredMediaDriver.consensusModule();\n\n        CloseHelper.closeAll(aeronCluster, consensusModule, container, clusteredMediaDriver);\n\n        if (null != clusteredMediaDriver)\n        {\n            clusteredMediaDriver.consensusModule().context().deleteDirectory();\n            clusteredMediaDriver.archive().context().deleteDirectory();\n            clusteredMediaDriver.mediaDriver().context().deleteDirectory();\n            container.context().deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRestartServiceWithTimerFromSnapshotWithFurtherLog()\n    {\n        final AtomicLong triggeredTimersCounter = new AtomicLong();\n\n        launchReschedulingService(triggeredTimersCounter);\n        connectClient();\n\n        Tests.awaitValue(triggeredTimersCounter, 2);\n\n        final CountersReader counters = aeronCluster.context().aeron().countersReader();\n        final int clusterId = clusteredMediaDriver.consensusModule().context().clusterId();\n        final AtomicCounter controlToggle = ClusterControl.findControlToggle(counters, clusterId);\n        assertNotNull(controlToggle);\n        assertTrue(ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle));\n\n        Tests.awaitValue(clusteredMediaDriver.consensusModule().context().snapshotCounter(), 1);\n        Tests.awaitValue(triggeredTimersCounter, 4);\n\n        forceCloseForRestart();\n        triggeredTimersCounter.set(0);\n\n        launchClusteredMediaDriver(false);\n        launchReschedulingService(triggeredTimersCounter);\n        connectClient();\n\n        Tests.awaitValue(triggeredTimersCounter, 2 + 4);\n\n        forceCloseForRestart();\n        final long triggeredSinceStart = triggeredTimersCounter.getAndSet(0);\n\n        launchClusteredMediaDriver(false);\n        launchReschedulingService(triggeredTimersCounter);\n        connectClient();\n\n        Tests.awaitValue(triggeredTimersCounter, triggeredSinceStart + 4);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldTriggerRescheduledTimerAfterReplay()\n    {\n        final AtomicLong triggeredTimersCounter = new AtomicLong();\n\n        launchReschedulingService(triggeredTimersCounter);\n        connectClient();\n\n        Tests.awaitValue(triggeredTimersCounter, 2);\n\n        forceCloseForRestart();\n\n        long triggeredSinceStart = triggeredTimersCounter.getAndSet(0);\n\n        launchClusteredMediaDriver(false);\n        launchReschedulingService(triggeredTimersCounter);\n\n        Tests.awaitValue(triggeredTimersCounter, triggeredSinceStart + 2);\n\n        forceCloseForRestart();\n\n        triggeredSinceStart = triggeredTimersCounter.getAndSet(0);\n\n        launchClusteredMediaDriver(false);\n        launchReschedulingService(triggeredTimersCounter);\n\n        Tests.awaitValue(triggeredTimersCounter, triggeredSinceStart + 4);\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRescheduleTimerWhenSchedulingWithExistingCorrelationId()\n    {\n        final AtomicLong timerCounter1 = new AtomicLong();\n        final AtomicLong timerCounter2 = new AtomicLong();\n\n        final ClusteredService service = new StubClusteredService()\n        {\n            public void onSessionOpen(final ClientSession session, final long timestamp)\n            {\n                schedule(1, timestamp + 1_000_000); // Too far in the future\n                schedule(1, timestamp + 20);\n                schedule(2, timestamp + 30);\n            }\n\n            public void onTimerEvent(final long correlationId, final long timestamp)\n            {\n                if (correlationId == 1)\n                {\n                    timerCounter1.incrementAndGet();\n                }\n                else\n                {\n                    timerCounter2.incrementAndGet();\n                }\n            }\n\n            private void schedule(final long correlationId, final long deadlineMs)\n            {\n                idleStrategy.reset();\n                while (!cluster.scheduleTimer(correlationId, deadlineMs))\n                {\n                    idleStrategy.idle();\n                }\n            }\n        };\n\n        container = ClusteredServiceContainer.launch(\n            new ClusteredServiceContainer.Context()\n                .clusteredService(service)\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .errorHandler(ClusterTests.errorHandler(0)));\n\n        connectClient();\n\n        Tests.awaitValue(timerCounter2, 1);\n        assertEquals(1, timerCounter1.get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    abstract TimerServiceSupplier timerServiceSupplier();\n\n    private void launchReschedulingService(final AtomicLong triggeredTimersCounter)\n    {\n        final ClusteredService service = new StubClusteredService()\n        {\n            private int timerCorrelationId = 1;\n\n            public void onTimerEvent(final long correlationId, final long timestamp)\n            {\n                triggeredTimersCounter.getAndIncrement();\n                scheduleNext(serviceCorrelationId(timerCorrelationId++), timestamp + INTERVAL_MS);\n            }\n\n            public void onStart(final Cluster cluster, final Image snapshotImage)\n            {\n                super.onStart(cluster, snapshotImage);\n                this.cluster = cluster;\n\n                if (null != snapshotImage)\n                {\n                    final FragmentHandler fragmentHandler =\n                        (buffer, offset, length, header) -> timerCorrelationId = buffer.getInt(offset);\n\n                    while (true)\n                    {\n                        final int fragments = snapshotImage.poll(fragmentHandler, 1);\n                        if (fragments == 1 || snapshotImage.isEndOfStream())\n                        {\n                            break;\n                        }\n\n                        idleStrategy.idle();\n                    }\n                }\n            }\n\n            public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n            {\n                final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(SIZE_OF_INT);\n                buffer.putInt(0, timerCorrelationId);\n\n                idleStrategy.reset();\n                while (snapshotPublication.offer(buffer, 0, SIZE_OF_INT) < 0)\n                {\n                    idleStrategy.idle();\n                }\n            }\n\n            public void onNewLeadershipTermEvent(\n                final long leadershipTermId,\n                final long logPosition,\n                final long timestamp,\n                final long termBaseLogPosition,\n                final int leaderMemberId,\n                final int logSessionId,\n                final TimeUnit timeUnit,\n                final int appVersion)\n            {\n                scheduleNext(serviceCorrelationId(timerCorrelationId++), timestamp + INTERVAL_MS);\n            }\n\n            private void scheduleNext(final long correlationId, final long deadlineMs)\n            {\n                idleStrategy.reset();\n                while (!cluster.scheduleTimer(correlationId, deadlineMs))\n                {\n                    idleStrategy.idle();\n                }\n            }\n        };\n\n        container = ClusteredServiceContainer.launch(\n            new ClusteredServiceContainer.Context()\n                .clusteredService(service)\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .errorHandler(ClusterTests.errorHandler(0)));\n    }\n\n    private void forceCloseForRestart()\n    {\n        CloseHelper.closeAll(aeronCluster, container, clusteredMediaDriver);\n    }\n\n    private void connectClient()\n    {\n        aeronCluster = AeronCluster.connect(new AeronCluster.Context()\n                .ingressChannel(\"aeron:udp\")\n                .ingressEndpoints(INGRESS_ENDPOINTS)\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\"));\n    }\n\n    private void launchClusteredMediaDriver(final boolean initialLaunch)\n    {\n        clusteredMediaDriver = ClusteredMediaDriver.launch(\n            new MediaDriver.Context()\n                .warnIfDirectoryExists(initialLaunch)\n                .threadingMode(ThreadingMode.SHARED)\n                .termBufferSparseFile(true)\n                .errorHandler(ClusterTests.errorHandler(0))\n                .dirDeleteOnStart(true),\n            TestContexts.localhostArchive()\n                .catalogCapacity(ClusterTestConstants.CATALOG_CAPACITY)\n                .errorHandler(ClusterTests.errorHandler(0))\n                .threadingMode(ArchiveThreadingMode.SHARED)\n                .recordingEventsEnabled(false)\n                .deleteArchiveOnStart(initialLaunch),\n            new ConsensusModule.Context()\n                .errorHandler(ClusterTests.errorHandler(0))\n                .logChannel(\"aeron:ipc\")\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .ingressChannel(\"aeron:udp\")\n                .clusterMembers(CLUSTER_MEMBERS)\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .timerServiceSupplier(timerServiceSupplier())\n                .deleteDirOnStart(initialLaunch));\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ClusterWithNoServicesTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.cluster.ClusterTests;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InOrder;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.atLeastOnce;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.isNull;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ClusterWithNoServicesTest\n{\n    enum LatchTrigger\n    {\n        SESSION_OPENED, SNAPSHOT_TAKEN, CLOSED\n    }\n\n    private ClusteredMediaDriver clusteredMediaDriver;\n    private AeronCluster aeronCluster;\n\n    @AfterEach\n    void after()\n    {\n        final ConsensusModule consensusModule = null == clusteredMediaDriver ?\n            null : clusteredMediaDriver.consensusModule();\n\n        CloseHelper.closeAll(aeronCluster, consensusModule, clusteredMediaDriver);\n\n        if (null != clusteredMediaDriver)\n        {\n            clusteredMediaDriver.consensusModule().context().deleteDirectory();\n            clusteredMediaDriver.archive().context().deleteDirectory();\n            clusteredMediaDriver.mediaDriver().context().deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldConnectAndSendKeepAliveWithExtensionLoaded() throws InterruptedException\n    {\n        final CountDownLatch latch = new CountDownLatch(1);\n        final ConsensusModuleExtension consensusModuleExtension = spy(\n            new TestConsensusModuleExtension(latch, LatchTrigger.SESSION_OPENED));\n\n        clusteredMediaDriver = launchCluster(consensusModuleExtension);\n        aeronCluster = connectClient();\n\n        assertTrue(aeronCluster.sendKeepAlive());\n        latch.await();\n\n        final InOrder inOrder = inOrder(consensusModuleExtension);\n        inOrder.verify(consensusModuleExtension).onStart(any(ConsensusModuleControl.class), isNull());\n        inOrder.verify(consensusModuleExtension).onElectionComplete(any(ConsensusControlState.class));\n        inOrder.verify(consensusModuleExtension, atLeastOnce()).doWork(anyLong());\n\n        verify(consensusModuleExtension).onSessionOpened(anyLong());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSnapshotExtensionState() throws InterruptedException\n    {\n        final CountDownLatch latch = new CountDownLatch(1);\n\n        final TestConsensusModuleExtension moduleExtension =\n            new TestConsensusModuleExtension(latch, LatchTrigger.SNAPSHOT_TAKEN);\n        clusteredMediaDriver = launchCluster(moduleExtension);\n        aeronCluster = connectClient();\n\n        final AtomicCounter controlToggle = getClusterControlToggle();\n        assertTrue(ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle));\n\n        latch.await();\n        assertEquals(1, moduleExtension.snapshotCount.get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldShutdownWithExtension() throws InterruptedException\n    {\n        final CountDownLatch latch = new CountDownLatch(1);\n\n        final TestConsensusModuleExtension moduleExtension =\n            new TestConsensusModuleExtension(latch, LatchTrigger.CLOSED);\n        clusteredMediaDriver = launchCluster(moduleExtension);\n        aeronCluster = connectClient();\n\n        final AtomicCounter controlToggle = getClusterControlToggle();\n        assertTrue(ClusterControl.ToggleState.SHUTDOWN.toggle(controlToggle));\n\n        latch.await();\n        assertEquals(1, moduleExtension.snapshotCount.get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAbortWithExtension() throws InterruptedException\n    {\n        final CountDownLatch latch = new CountDownLatch(1);\n\n        final TestConsensusModuleExtension moduleExtension =\n            new TestConsensusModuleExtension(latch, LatchTrigger.CLOSED);\n        clusteredMediaDriver = launchCluster(moduleExtension);\n        aeronCluster = connectClient();\n\n        final AtomicCounter controlToggle = getClusterControlToggle();\n        assertTrue(ClusterControl.ToggleState.ABORT.toggle(controlToggle));\n\n        latch.await();\n        assertEquals(0, moduleExtension.snapshotCount.get());\n\n        ClusterTests.failOnClusterError();\n    }\n\n    private ClusteredMediaDriver launchCluster(final ConsensusModuleExtension consensusModuleExtension)\n    {\n        return ClusteredMediaDriver.launch(\n            new MediaDriver.Context()\n                .threadingMode(ThreadingMode.SHARED)\n                .termBufferSparseFile(true)\n                .errorHandler(ClusterTests.errorHandler(0))\n                .dirDeleteOnStart(true),\n            TestContexts.localhostArchive()\n                .catalogCapacity(ClusterTestConstants.CATALOG_CAPACITY)\n                .threadingMode(ArchiveThreadingMode.SHARED)\n                .recordingEventsEnabled(false)\n                .deleteArchiveOnStart(true),\n            new ConsensusModule.Context()\n                .errorHandler(ClusterTests.errorHandler(0))\n                .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n                .clusterMembers(ClusterTestConstants.CLUSTER_MEMBERS)\n                .ingressChannel(\"aeron:udp\")\n                .serviceCount(0)\n                .consensusModuleExtension(consensusModuleExtension)\n                .logChannel(\"aeron:ipc\")\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .deleteDirOnStart(true));\n    }\n\n    private static AeronCluster connectClient()\n    {\n        return AeronCluster.connect(\n            new AeronCluster.Context()\n                .ingressChannel(\"aeron:udp\")\n                .ingressEndpoints(ClusterTestConstants.INGRESS_ENDPOINTS)\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\"));\n    }\n\n    public AtomicCounter getClusterControlToggle()\n    {\n        final CountersReader counters = clusteredMediaDriver.mediaDriver().context().countersManager();\n        final int clusterId = clusteredMediaDriver.consensusModule().context().clusterId();\n        final AtomicCounter controlToggle = ClusterControl.findControlToggle(counters, clusterId);\n        assertNotNull(controlToggle);\n\n        return controlToggle;\n    }\n\n    static final class TestConsensusModuleExtension implements ConsensusModuleExtension\n    {\n        private final CountDownLatch latch;\n        private final LatchTrigger latchTrigger;\n        final AtomicInteger snapshotCount = new AtomicInteger();\n\n        TestConsensusModuleExtension(final CountDownLatch latch, final LatchTrigger latchTrigger)\n        {\n            this.latch = latch;\n            this.latchTrigger = latchTrigger;\n        }\n\n        public int supportedSchemaId()\n        {\n            return 0;\n        }\n\n        public void onStart(final ConsensusModuleControl consensusModuleControl, final Image snapshotImage)\n        {\n        }\n\n        public int doWork(final long nowNs)\n        {\n            return 0;\n        }\n\n        public int slowTickWork(final long nowNs)\n        {\n            return 0;\n        }\n\n        public int consensusWork(final long nowNs)\n        {\n            return 0;\n        }\n\n        public void onElectionComplete(final ConsensusControlState consensusControlState)\n        {\n        }\n\n        public void onNewLeadershipTerm(final ConsensusControlState consensusControlState)\n        {\n        }\n\n        public ControlledFragmentHandler.Action onIngressExtensionMessage(\n            final int actingBlockLength,\n            final int templateId,\n            final int schemaId,\n            final int actingVersion,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            return ControlledFragmentHandler.Action.CONTINUE;\n        }\n\n        public ControlledFragmentHandler.Action onLogExtensionMessage(\n            final int actingBlockLength,\n            final int templateId,\n            final int schemaId,\n            final int actingVersion,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            return ControlledFragmentHandler.Action.CONTINUE;\n        }\n\n        public void close()\n        {\n            if (LatchTrigger.CLOSED == latchTrigger)\n            {\n                latch.countDown();\n            }\n        }\n\n        public void onSessionOpened(final long clusterSessionId)\n        {\n            if (LatchTrigger.SESSION_OPENED == latchTrigger)\n            {\n                latch.countDown();\n            }\n        }\n\n        public void onSessionClosed(final long clusterSessionId, final CloseReason closeReason)\n        {\n        }\n\n        public void onPrepareForNewLeadership()\n        {\n        }\n\n        public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n        {\n            snapshotCount.getAndIncrement();\n            if (LatchTrigger.SNAPSHOT_TAKEN == latchTrigger)\n            {\n                latch.countDown();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ConsensusModuleAgentTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.ConcurrentPublication;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.UnavailableImageHandler;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.client.ClusterEvent;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.codecs.ClusterAction;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.cluster.service.ClusterTerminationException;\nimport io.aeron.driver.DutyCycleTracker;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.security.DefaultAuthenticatorSupplier;\nimport io.aeron.status.ReadableCounter;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestClusterClock;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.NoOpIdleStrategy;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.InOrder;\nimport org.mockito.Mockito;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.LongConsumer;\n\nimport static io.aeron.AeronCounters.CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID;\nimport static io.aeron.AeronCounters.CLUSTER_CONTROL_TOGGLE_TYPE_ID;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.ClusterControl.ToggleState.NEUTRAL;\nimport static io.aeron.cluster.ClusterControl.ToggleState.RESUME;\nimport static io.aeron.cluster.ClusterControl.ToggleState.STANDBY_SNAPSHOT;\nimport static io.aeron.cluster.ClusterControl.ToggleState.SUSPEND;\nimport static io.aeron.cluster.ConsensusModule.CLUSTER_ACTION_FLAGS_STANDBY_SNAPSHOT;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SESSION_LIMIT_MSG;\nimport static io.aeron.cluster.ConsensusModuleAgent.SLOW_TICK_INTERVAL_NS;\nimport static io.aeron.cluster.client.AeronCluster.Configuration.PROTOCOL_SEMANTIC_VERSION;\nimport static java.lang.Boolean.TRUE;\nimport static org.agrona.concurrent.status.CountersReader.COUNTER_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ConsensusModuleAgentTest\n{\n    private static final long SLOW_TICK_INTERVAL_MS = TimeUnit.NANOSECONDS.toMillis(SLOW_TICK_INTERVAL_NS);\n    private static final String RESPONSE_CHANNEL_ONE = \"aeron:udp?endpoint=localhost:11111\";\n    private static final String RESPONSE_CHANNEL_TWO = \"aeron:udp?endpoint=localhost:22222\";\n    private static final int SCHEMA_ID = 17;\n    private static final int UPDATE_INTERVAL_MS = 19;\n\n    private final EgressPublisher mockEgressPublisher = mock(EgressPublisher.class);\n    private final LogPublisher mockLogPublisher = mock(LogPublisher.class);\n    private final Aeron mockAeron = mock(Aeron.class);\n    private final ConcurrentPublication mockResponsePublication = mock(ConcurrentPublication.class);\n    private final ExclusivePublication mockExclusivePublication = mock(ExclusivePublication.class);\n    private final Counter mockTimedOutClientCounter = mock(Counter.class);\n    private final LongConsumer mockTimeConsumer = mock(LongConsumer.class);\n    private final Image mockImage = mock(Image.class);\n    private final Header header = new Header(0, 0, mockImage);\n    private final CountersManager countersManager = Tests.newCountersManager(2 * COUNTER_LENGTH);\n    private long registrationId = 20;\n\n    private final ConsensusModule.Context ctx = TestContexts.localhostConsensusModule()\n        .errorHandler(Tests::onError)\n        .errorCounter(mock(AtomicCounter.class))\n        .countedErrorHandler(mock(CountedErrorHandler.class))\n        .moduleStateCounter(mock(Counter.class))\n        .commitPositionCounter(mock(Counter.class))\n        .controlToggleCounter(mock(Counter.class))\n        .nodeControlToggleCounter(mock(Counter.class))\n        .clusterNodeRoleCounter(mock(Counter.class))\n        .electionCounter(mock(Counter.class))\n        .leadershipTermIdCounter(mock(Counter.class))\n        .timedOutClientCounter(mockTimedOutClientCounter)\n        .clusterTimeConsumerSupplier((ctx) -> mockTimeConsumer)\n        .idleStrategySupplier(NoOpIdleStrategy::new)\n        .timerServiceSupplier((clusterClock, timerHandler) -> mock(TimerService.class))\n        .aeron(mockAeron)\n        .clusterMemberId(0)\n        .authenticatorSupplier(new DefaultAuthenticatorSupplier())\n        .authorisationServiceSupplier(() -> AuthorisationService.DENY_ALL)\n        .clusterMarkFile(mock(ClusterMarkFile.class))\n        .archiveContext(new AeronArchive.Context())\n        .logPublisher(mockLogPublisher)\n        .egressPublisher(mockEgressPublisher)\n        .dutyCycleTracker(new DutyCycleTracker());\n\n    private Counter newCounter(final String name, final int typeId)\n    {\n        final AtomicCounter atomicCounter = countersManager.newCounter(name, typeId);\n        return new Counter(countersManager, ++registrationId, atomicCounter.id());\n    }\n\n    @BeforeEach\n    void before()\n    {\n        when(mockAeron.conductorAgentInvoker()).thenReturn(mock(AgentInvoker.class));\n        when(mockEgressPublisher.sendEvent(any(), anyLong(), anyInt(), any(), any())).thenReturn(TRUE);\n        when(mockLogPublisher.appendSessionClose(anyInt(), any(), anyLong(), anyLong(), any())).thenReturn(TRUE);\n        when(mockLogPublisher.appendSessionOpen(any(), anyLong(), anyLong())).thenReturn(128L);\n        when(mockLogPublisher.appendClusterAction(anyLong(), anyLong(), any(ClusterAction.class), anyInt()))\n            .thenReturn(TRUE);\n        when(mockAeron.addPublication(anyString(), anyInt())).thenReturn(mockResponsePublication);\n        when(mockAeron.getPublication(anyLong())).thenReturn(mockResponsePublication);\n        when(mockAeron.addExclusivePublication(anyString(), anyInt())).thenReturn(mockExclusivePublication);\n        when(mockAeron.addSubscription(anyString(), anyInt())).thenReturn(mock(Subscription.class));\n        when(mockAeron.addSubscription(anyString(), anyInt(), eq(null), any(UnavailableImageHandler.class)))\n            .thenReturn(mock(Subscription.class));\n        when(mockResponsePublication.isConnected()).thenReturn(TRUE);\n        when(mockResponsePublication.availableWindow()).thenReturn(Long.MAX_VALUE);\n    }\n\n    @Test\n    void shouldUseAssignedRoleName()\n    {\n        final String expectedRoleName = \"test-role-name\";\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        ctx.agentRoleName(expectedRoleName)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        assertEquals(expectedRoleName, agent.roleName());\n    }\n\n    @Test\n    void shouldLimitActiveSessions()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        ctx.maxConcurrentSessions(1)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n\n        final long correlationIdOne = 1L;\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.LEADER);\n        Tests.setField(agent, \"appendPosition\", mock(ReadableCounter.class));\n        agent.onSessionConnect(\n            correlationIdOne, 2, PROTOCOL_SEMANTIC_VERSION, RESPONSE_CHANNEL_ONE, new byte[0], \"\", header);\n\n        clock.update(UPDATE_INTERVAL_MS, TimeUnit.MILLISECONDS);\n        agent.doWork();\n        verify(mockTimeConsumer).accept(clock.time());\n\n        verify(mockLogPublisher).appendSessionOpen(any(ClusterSession.class), anyLong(), anyLong());\n\n        final long correlationIdTwo = 2L;\n        agent.onSessionConnect(\n            correlationIdTwo, 3, PROTOCOL_SEMANTIC_VERSION, RESPONSE_CHANNEL_TWO, new byte[0], \"\", header);\n        clock.update(clock.time() + 10L, TimeUnit.MILLISECONDS);\n        agent.doWork();\n        verify(mockTimeConsumer).accept(clock.time());\n\n        verify(mockEgressPublisher).sendEvent(\n            any(ClusterSession.class), anyLong(), anyInt(), eq(EventCode.ERROR), eq(SESSION_LIMIT_MSG));\n    }\n\n    @Test\n    void shouldCloseInactiveSession()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final long startMs = SLOW_TICK_INTERVAL_MS;\n        clock.update(startMs, TimeUnit.MILLISECONDS);\n\n        ctx.epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n\n        final long correlationId = 1L;\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.LEADER);\n        Tests.setField(agent, \"appendPosition\", mock(ReadableCounter.class));\n        agent.onSessionConnect(\n            correlationId, 2, PROTOCOL_SEMANTIC_VERSION, RESPONSE_CHANNEL_ONE, new byte[0], \"\", header);\n\n        agent.doWork();\n\n        verify(mockLogPublisher).appendSessionOpen(any(ClusterSession.class), anyLong(), eq(startMs));\n        verify(mockTimeConsumer).accept(clock.time());\n\n        final long timeMs = startMs + TimeUnit.NANOSECONDS.toMillis(ConsensusModule.Configuration.sessionTimeoutNs());\n        clock.update(timeMs, TimeUnit.MILLISECONDS);\n        agent.clusterMember().timeOfLastAppendPositionNs(clock.nanoTime());\n\n        agent.doWork();\n\n        final long timeoutMs = timeMs + SLOW_TICK_INTERVAL_MS;\n        clock.update(timeoutMs, TimeUnit.MILLISECONDS);\n        agent.doWork();\n\n        verify(mockTimeConsumer).accept(clock.time());\n        verify(mockTimedOutClientCounter).incrementRelease();\n        verify(mockLogPublisher).appendSessionClose(\n            anyInt(), any(ClusterSession.class), anyLong(), eq(timeoutMs), eq(clock.timeUnit()));\n        verify(mockEgressPublisher).sendEvent(\n            any(ClusterSession.class), anyLong(), anyInt(), eq(EventCode.CLOSED), eq(CloseReason.TIMEOUT.name()));\n    }\n\n    @Test\n    void shouldCloseTerminatedSession()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final long startMs = SLOW_TICK_INTERVAL_MS;\n        clock.update(startMs, TimeUnit.MILLISECONDS);\n\n        ctx.epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n\n        final long correlationId = 1L;\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.LEADER);\n        Tests.setField(agent, \"appendPosition\", mock(ReadableCounter.class));\n        agent.onSessionConnect(\n            correlationId, 2, PROTOCOL_SEMANTIC_VERSION, RESPONSE_CHANNEL_ONE, new byte[0], \"\", header);\n\n        agent.doWork();\n\n        final ArgumentCaptor<ClusterSession> sessionCaptor = ArgumentCaptor.forClass(ClusterSession.class);\n\n        verify(mockLogPublisher).appendSessionOpen(sessionCaptor.capture(), anyLong(), eq(startMs));\n\n        final long timeMs = startMs + SLOW_TICK_INTERVAL_MS;\n        clock.update(timeMs, TimeUnit.MILLISECONDS);\n        agent.doWork();\n\n        agent.onServiceCloseSession(sessionCaptor.getValue().id());\n\n        verify(mockLogPublisher).appendSessionClose(\n            anyInt(), any(ClusterSession.class), anyLong(), eq(timeMs), eq(clock.timeUnit()));\n        verify(mockEgressPublisher).sendEvent(\n            any(ClusterSession.class),\n            anyLong(),\n            anyInt(),\n            eq(EventCode.CLOSED),\n            eq(CloseReason.SERVICE_ACTION.name()));\n    }\n\n    @Test\n    void shouldSuspendThenResume()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final Counter stateCounter = newCounter(\"state counter\", CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID);\n        final Counter controlToggle = newCounter(\"control toggle\", CLUSTER_CONTROL_TOGGLE_TYPE_ID);\n\n        controlToggle.set(NEUTRAL.code());\n\n        ctx.moduleStateCounter(stateCounter)\n            .controlToggleCounter(controlToggle)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        Tests.setField(agent, \"appendPosition\", mock(ReadableCounter.class));\n\n        assertEquals(ConsensusModule.State.INIT.code(), stateCounter.get());\n\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.LEADER);\n        assertEquals(ConsensusModule.State.ACTIVE.code(), stateCounter.get());\n\n        SUSPEND.toggle(controlToggle);\n        clock.update(SLOW_TICK_INTERVAL_MS, TimeUnit.MILLISECONDS);\n        agent.doWork();\n\n        assertEquals(ConsensusModule.State.SUSPENDED.code(), stateCounter.get());\n        assertEquals(SUSPEND.code(), controlToggle.get());\n\n        RESUME.toggle(controlToggle);\n        clock.update(SLOW_TICK_INTERVAL_MS * 2, TimeUnit.MILLISECONDS);\n        agent.doWork();\n\n        assertEquals(ConsensusModule.State.ACTIVE.code(), stateCounter.get());\n        assertEquals(NEUTRAL.code(), controlToggle.get());\n\n        final InOrder inOrder = Mockito.inOrder(mockLogPublisher);\n        inOrder.verify(mockLogPublisher).appendClusterAction(anyLong(), anyLong(), eq(ClusterAction.SUSPEND), anyInt());\n        inOrder.verify(mockLogPublisher).appendClusterAction(anyLong(), anyLong(), eq(ClusterAction.RESUME), anyInt());\n    }\n\n    @Test\n    void shouldThrowClusterTerminationExceptionUponShutdown()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final CountedErrorHandler countedErrorHandler = mock(CountedErrorHandler.class);\n        final MutableLong stateValue = new MutableLong();\n        final Counter mockState = mock(Counter.class);\n\n        when(mockState.get()).thenAnswer((invocation) -> stateValue.value);\n        doAnswer(\n            (invocation) ->\n            {\n                stateValue.value = invocation.getArgument(0);\n                return null;\n            })\n            .when(mockState).set(anyLong());\n\n        ctx.countedErrorHandler(countedErrorHandler)\n            .moduleStateCounter(mockState)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        agent.state(ConsensusModule.State.QUITTING, \"\");\n\n        assertThrows(ClusterTerminationException.class,\n            () -> agent.onServiceAck(1024, 100, 0, 55, 0));\n    }\n\n    @ParameterizedTest\n    @CsvSource(value = {\n        \"false, null, aeron:udp?endpoint=acme:2040, aeron:udp?endpoint=acme:2040\",\n        \"true, aeron:udp?endpoint=host:port, aeron:ipc, aeron:ipc\",\n        \"false, null, aeron:ipc, aeron:ipc\",\n        \"false, aeron:udp?endpoint=host:port, aeron:ipc, aeron:udp?endpoint=host:port\",\n        \"false, aeron:udp?endpoint=host1:5050|interface=eth0|mtu=1440, \" +\n            \"aeron:udp?endpoint=localhost:8080|mtu=8k|alias=test, \" +\n            \"aeron:udp?endpoint=host1:5050|interface=eth0|mtu=1440|alias=test\",\n        \"false, aeron:udp?endpoint=node0:21300|eos=false, aeron:udp?mtu=8000|interface=if1|eos=true|ttl=100, \" +\n            \"aeron:udp?endpoint=node0:21300|eos=false|mtu=8000|interface=if1|ttl=100\"\n    }, nullValues = \"null\")\n    void responseChannelIsBuiltBasedOnTheEgressChannel(\n        final boolean isIpcIngressAllowed,\n        final String egressChannel,\n        final String responseChannel,\n        final String expectedResponseChannel)\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        ctx.epochClock(clock.asEpochClock())\n            .clusterClock(clock)\n            .egressChannel(egressChannel)\n            .isIpcIngressAllowed(isIpcIngressAllowed);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.LEADER);\n\n        final long correlationId = 1L;\n        final int responseStreamId = 42;\n        agent.onSessionConnect(\n            correlationId, responseStreamId, PROTOCOL_SEMANTIC_VERSION, responseChannel, new byte[0], \"\", header);\n\n        final ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);\n        verify(mockAeron).asyncAddPublication(channelCaptor.capture(), eq(responseStreamId));\n\n        assertEquals(ChannelUri.parse(expectedResponseChannel), ChannelUri.parse(channelCaptor.getValue()));\n    }\n\n    @Test\n    void shouldPublishLogMessageButNotSnapshotOnStandbySnapshot()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final Counter stateCounter = newCounter(\"state counter\", CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID);\n        final Counter controlToggle = newCounter(\"control toggle\", CLUSTER_CONTROL_TOGGLE_TYPE_ID);\n\n        controlToggle.set(NEUTRAL.code());\n\n        ctx.moduleStateCounter(stateCounter)\n            .controlToggleCounter(controlToggle)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        Tests.setField(agent, \"appendPosition\", mock(ReadableCounter.class));\n\n        assertEquals(ConsensusModule.State.INIT.code(), stateCounter.get());\n\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.LEADER);\n        assertEquals(ConsensusModule.State.ACTIVE.code(), stateCounter.get());\n\n        assertTrue(STANDBY_SNAPSHOT.toggle(controlToggle));\n        clock.update(SLOW_TICK_INTERVAL_MS, TimeUnit.MILLISECONDS);\n        agent.doWork();\n\n        assertEquals(ConsensusModule.State.ACTIVE.code(), stateCounter.get());\n        assertEquals(NEUTRAL.code(), stateCounter.get());\n\n        final InOrder inOrder = Mockito.inOrder(mockLogPublisher);\n        inOrder.verify(mockLogPublisher).appendClusterAction(\n            anyLong(), anyLong(), eq(ClusterAction.SNAPSHOT), eq(CLUSTER_ACTION_FLAGS_STANDBY_SNAPSHOT));\n\n        agent.onReplayClusterAction(-1, 2048, 0, ClusterAction.SNAPSHOT, CLUSTER_ACTION_FLAGS_STANDBY_SNAPSHOT);\n        assertEquals(ConsensusModule.State.ACTIVE.code(), stateCounter.get());\n    }\n\n    @Test\n    void onNewLeadershipTermShouldUpdateTimeOfLastLeaderMessageReceived()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.NANOSECONDS);\n        ctx.clusterClock(clock)\n            .epochClock(clock.asEpochClock())\n            .appVersionValidator(AppVersionValidator.SEMANTIC_VERSIONING_VALIDATOR);\n\n        final int leadershipTermId = 2;\n        final ConsensusModuleAgent consensusModuleAgent = new ConsensusModuleAgent(ctx);\n        consensusModuleAgent.leadershipTermId(leadershipTermId);\n        assertEquals(0, consensusModuleAgent.timeOfLastLeaderUpdateNs());\n\n        clock.increment(12345);\n\n        consensusModuleAgent.onNewLeadershipTerm(\n            3,\n            4,\n            1024,\n            2048,\n            leadershipTermId,\n            100,\n            600,\n            4096,\n            8,\n            200,\n            0,\n            42,\n            777,\n            false);\n\n        assertEquals(12345, consensusModuleAgent.timeOfLastLeaderUpdateNs());\n    }\n\n    @Test\n    void onCommitPositionShouldUpdateTimeOfLastLeaderMessageReceived()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.NANOSECONDS);\n        ctx.clusterClock(clock)\n            .epochClock(clock.asEpochClock());\n\n        final int leadershipTermId = 42;\n        final ConsensusModuleAgent consensusModuleAgent = new ConsensusModuleAgent(ctx);\n        consensusModuleAgent.leadershipTermId(leadershipTermId);\n        assertEquals(0, consensusModuleAgent.timeOfLastLeaderUpdateNs());\n\n        clock.increment(444);\n\n        consensusModuleAgent.onCommitPosition(leadershipTermId, 555, 0);\n\n        assertEquals(444, consensusModuleAgent.timeOfLastLeaderUpdateNs());\n    }\n\n    @Test\n    void shouldDelegateHandlingToRegisteredExtension()\n    {\n        final ConsensusModuleExtension consensusModuleExtension = mock(ConsensusModuleExtension.class, \"used adapter\");\n        when(consensusModuleExtension.supportedSchemaId()).thenReturn(SCHEMA_ID);\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        ctx.epochClock(clock.asEpochClock())\n            .clusterClock(clock)\n            .consensusModuleExtension(consensusModuleExtension);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        agent.onExtensionMessage(0, 1, SCHEMA_ID, 0, null, 0, 0, null);\n\n        verify(consensusModuleExtension)\n            .onIngressExtensionMessage(0, 1, SCHEMA_ID, 0, null, 0, 0, null);\n    }\n\n    @Test\n    void shouldThrowExceptionOnUnknownSchemaAndNoAdapter()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final CountedErrorHandler mockErrorHandler = mock(CountedErrorHandler.class);\n        ctx.countedErrorHandler(mockErrorHandler)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n\n        agent.onExtensionMessage(0, 0, SCHEMA_ID, 0, null, 0, 0, null);\n        verify(mockErrorHandler).onError(any(ClusterEvent.class));\n    }\n\n    @Test\n    void shouldHandlePaddingMessageAtEndOfTerm()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final Counter stateCounter = newCounter(\"state counter\", CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID);\n        final Counter controlToggle = newCounter(\"control toggle\", CLUSTER_CONTROL_TOGGLE_TYPE_ID);\n\n        controlToggle.set(NEUTRAL.code());\n\n        ctx.moduleStateCounter(stateCounter)\n            .controlToggleCounter(controlToggle)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        Tests.setField(agent, \"appendPosition\", mock(ReadableCounter.class));\n\n        assertEquals(ConsensusModule.State.INIT.code(), stateCounter.get());\n\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.LEADER);\n        assertEquals(ConsensusModule.State.ACTIVE.code(), stateCounter.get());\n\n        final LogAdapter mockLogAdapter = mock(LogAdapter.class);\n\n        agent.replayLogPoll(mockLogAdapter, 65536);\n    }\n\n    @Test\n    void notifiedCommitPositionShouldNotGoBackwardsUponReceivingCommitPosition()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final Counter stateCounter = newCounter(\"state counter\", CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID);\n        final Counter controlToggle = newCounter(\"control toggle\", CLUSTER_CONTROL_TOGGLE_TYPE_ID);\n\n        controlToggle.set(NEUTRAL.code());\n\n        ctx.moduleStateCounter(stateCounter)\n            .controlToggleCounter(controlToggle)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        assertEquals(ConsensusModule.State.INIT.code(), stateCounter.get());\n\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.FOLLOWER);\n        final long leadershipTermId = 42;\n        agent.leadershipTermId(leadershipTermId);\n        final ClusterMember leader = new ClusterMember(19, \"\", \"\", \"\", \"\", \"\", \"\");\n        Tests.setField(agent, \"leaderMember\", leader);\n        assertSame(leader, Tests.getField(agent, \"leaderMember\"));\n\n        assertEquals(0, agent.notifiedCommitPosition());\n\n        clock.increment(1);\n        agent.onCommitPosition(leadershipTermId, 100, leader.id());\n        assertEquals(100, agent.notifiedCommitPosition());\n        assertEquals(clock.timeNanos(), agent.timeOfLastLogUpdateNs());\n\n        clock.increment(1);\n        agent.onCommitPosition(leadershipTermId, 200, leader.id());\n        assertEquals(200, agent.notifiedCommitPosition());\n        assertEquals(clock.timeNanos(), agent.timeOfLastLogUpdateNs());\n\n        clock.increment(1);\n        agent.onCommitPosition(leadershipTermId, 50, leader.id());\n        assertEquals(200, agent.notifiedCommitPosition());\n        assertEquals(clock.timeNanos(), agent.timeOfLastLogUpdateNs());\n\n        clock.increment(1);\n        agent.onCommitPosition(leadershipTermId, -1, leader.id());\n        assertEquals(200, agent.notifiedCommitPosition());\n        assertEquals(clock.timeNanos(), agent.timeOfLastLogUpdateNs());\n\n        final long lastUpdateNs = clock.timeNanos();\n        clock.increment(1);\n        agent.onCommitPosition(leadershipTermId - 1, 5000, leader.id());\n        assertEquals(200, agent.notifiedCommitPosition());\n        assertEquals(lastUpdateNs, agent.timeOfLastLogUpdateNs());\n\n        clock.increment(5);\n        agent.onCommitPosition(leadershipTermId, 700, -100);\n        assertEquals(200, agent.notifiedCommitPosition());\n        assertEquals(lastUpdateNs, agent.timeOfLastLogUpdateNs());\n\n        clock.increment(3);\n        agent.role(Cluster.Role.CANDIDATE);\n        agent.onCommitPosition(leadershipTermId, 555, leader.id());\n        assertEquals(200, agent.notifiedCommitPosition());\n        assertEquals(lastUpdateNs, agent.timeOfLastLogUpdateNs());\n\n        clock.increment(2);\n        agent.role(Cluster.Role.LEADER);\n        agent.onCommitPosition(leadershipTermId, 999, leader.id());\n        assertEquals(200, agent.notifiedCommitPosition());\n        assertEquals(lastUpdateNs, agent.timeOfLastLogUpdateNs());\n    }\n\n    @Test\n    void notifiedCommitPositionShouldNotGoBackwardsUponElectionCompletion()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final Counter stateCounter = newCounter(\"state counter\", CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID);\n        final Counter controlToggle = newCounter(\"control toggle\", CLUSTER_CONTROL_TOGGLE_TYPE_ID);\n\n        controlToggle.set(NEUTRAL.code());\n\n        ctx.moduleStateCounter(stateCounter)\n            .controlToggleCounter(controlToggle)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock)\n            .recordingLog(mock(RecordingLog.class))\n            .ingressChannel(\"aeron:udp\");\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        assertEquals(ConsensusModule.State.INIT.code(), stateCounter.get());\n\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.FOLLOWER);\n        final Election election = mock(Election.class);\n        final long logPosition = 200L;\n        when(election.logPosition()).thenReturn(logPosition);\n        when(election.leader()).thenReturn(mock(ClusterMember.class));\n        Tests.setField(agent, \"election\", election);\n        assertSame(election, Tests.getField(agent, \"election\"));\n\n        assertEquals(0, agent.notifiedCommitPosition());\n\n        agent.electionComplete(555);\n        assertEquals(logPosition, agent.notifiedCommitPosition());\n        verify(election).logPosition();\n\n        reset(election);\n        when(election.logPosition()).thenReturn(50L);\n        when(election.leader()).thenReturn(mock(ClusterMember.class));\n        Tests.setField(agent, \"election\", election);\n\n        agent.electionComplete(777);\n        assertEquals(logPosition, agent.notifiedCommitPosition());\n        verify(election).logPosition();\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    void notifiedCommitPositionShouldNotGoBackwardsUponReceivingNewLeadershipTerm()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        final Counter stateCounter = newCounter(\"state counter\", CLUSTER_CONSENSUS_MODULE_STATE_TYPE_ID);\n        final Counter controlToggle = newCounter(\"control toggle\", CLUSTER_CONTROL_TOGGLE_TYPE_ID);\n\n        controlToggle.set(NEUTRAL.code());\n\n        final AppVersionValidator appVersionValidator = mock(AppVersionValidator.class);\n        when(appVersionValidator.isVersionCompatible(anyInt(), anyInt())).thenReturn(true);\n        ctx.moduleStateCounter(stateCounter)\n            .controlToggleCounter(controlToggle)\n            .epochClock(clock.asEpochClock())\n            .clusterClock(clock)\n            .appVersionValidator(appVersionValidator);\n\n        final ConsensusModuleAgent agent = new ConsensusModuleAgent(ctx);\n        assertEquals(ConsensusModule.State.INIT.code(), stateCounter.get());\n\n        agent.state(ConsensusModule.State.ACTIVE, \"\");\n        agent.role(Cluster.Role.FOLLOWER);\n        final long leadershipTermId = 42;\n        agent.leadershipTermId(leadershipTermId);\n        final ClusterMember leader = new ClusterMember(19, \"\", \"\", \"\", \"\", \"\", \"\");\n        Tests.setField(agent, \"leaderMember\", leader);\n        assertSame(leader, Tests.getField(agent, \"leaderMember\"));\n\n        assertEquals(0, agent.notifiedCommitPosition());\n\n        clock.increment(1);\n        agent.onNewLeadershipTerm(\n            0,\n            1,\n            NULL_POSITION,\n            NULL_POSITION,\n            leadershipTermId,\n            0,\n            1024 * 1024,\n            100,\n            8,\n            clock.nanoTime(),\n            leader.id(),\n            16,\n            1,\n            false);\n        assertEquals(100, agent.notifiedCommitPosition());\n        assertEquals(clock.timeNanos(), agent.timeOfLastLogUpdateNs());\n\n        clock.increment(1);\n        agent.onNewLeadershipTerm(\n            0,\n            1,\n            NULL_POSITION,\n            NULL_POSITION,\n            leadershipTermId,\n            0,\n            1024 * 1024,\n            3000,\n            8,\n            clock.nanoTime(),\n            leader.id(),\n            16,\n            1,\n            false);\n        assertEquals(3000, agent.notifiedCommitPosition());\n        assertEquals(clock.timeNanos(), agent.timeOfLastLogUpdateNs());\n\n        clock.increment(1);\n        agent.onNewLeadershipTerm(\n            0,\n            1,\n            NULL_POSITION,\n            NULL_POSITION,\n            leadershipTermId,\n            0,\n            1024 * 1024,\n            50,\n            8,\n            clock.nanoTime(),\n            leader.id(),\n            16,\n            1,\n            false);\n        assertEquals(3000, agent.notifiedCommitPosition());\n        assertEquals(clock.timeNanos(), agent.timeOfLastLogUpdateNs());\n\n        clock.increment(1);\n        agent.onNewLeadershipTerm(\n            0,\n            1,\n            NULL_POSITION,\n            NULL_POSITION,\n            leadershipTermId,\n            0,\n            1024 * 1024,\n            NULL_POSITION,\n            8,\n            clock.nanoTime(),\n            leader.id(),\n            16,\n            1,\n            false);\n        assertEquals(3000, agent.notifiedCommitPosition());\n        assertEquals(clock.timeNanos(), agent.timeOfLastLogUpdateNs());\n\n        final long timeOfLastUpdateNs = clock.timeNanos();\n\n        // wrong leadershipTermId\n        clock.increment(1);\n        agent.onNewLeadershipTerm(\n            0,\n            1,\n            NULL_POSITION,\n            NULL_POSITION,\n            leadershipTermId - 1,\n            0,\n            1024 * 1024,\n            5000,\n            8,\n            clock.nanoTime(),\n            leader.id(),\n            16,\n            1,\n            false);\n        assertEquals(3000, agent.notifiedCommitPosition());\n        assertEquals(timeOfLastUpdateNs, agent.timeOfLastLogUpdateNs());\n\n        // wrong leaderId\n        clock.increment(1);\n        agent.onNewLeadershipTerm(\n            0,\n            1,\n            NULL_POSITION,\n            NULL_POSITION,\n            leadershipTermId,\n            0,\n            1024 * 1024,\n            7000,\n            8,\n            clock.nanoTime(),\n            999999,\n            16,\n            1,\n            false);\n        assertEquals(3000, agent.notifiedCommitPosition());\n        assertEquals(timeOfLastUpdateNs, agent.timeOfLastLogUpdateNs());\n\n        // wrong role\n        for (final Cluster.Role role : Cluster.Role.values())\n        {\n            if (Cluster.Role.FOLLOWER != role)\n            {\n                agent.role(role);\n                clock.increment(1);\n                agent.onNewLeadershipTerm(\n                    0,\n                    1,\n                    NULL_POSITION,\n                    NULL_POSITION,\n                    leadershipTermId,\n                    0,\n                    1024 * 1024,\n                    10000,\n                    8,\n                    clock.nanoTime(),\n                    leader.id(),\n                    16,\n                    1,\n                    false);\n                assertEquals(3000, agent.notifiedCommitPosition());\n                assertEquals(timeOfLastUpdateNs, agent.timeOfLastLogUpdateNs());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ConsensusModuleConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2024 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.codecs.BackupQueryDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.SessionConnectRequestDecoder;\nimport io.aeron.security.AuthorisationService;\nimport org.junit.jupiter.api.Test;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class ConsensusModuleConfigurationTest\n{\n    @Test\n    void shouldUseDenyAllAuthorisationSupplierWhenPropertySet()\n    {\n        final String denyAll = \"DENY_ALL\";\n        System.setProperty(ConsensusModule.Configuration.AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME, denyAll);\n        try\n        {\n            final AuthorisationService authorisationService =\n                requireNonNull(ConsensusModule.Configuration.authorisationServiceSupplier()).get();\n\n            assertFalse(authorisationService.isAuthorised(\n                MessageHeaderDecoder.SCHEMA_ID, BackupQueryDecoder.TEMPLATE_ID, null, new byte[0]));\n            assertFalse(authorisationService.isAuthorised(\n                MessageHeaderDecoder.SCHEMA_ID, SessionConnectRequestDecoder.TEMPLATE_ID, null, new byte[0]));\n        }\n        finally\n        {\n            System.clearProperty(ConsensusModule.Configuration.AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        }\n\n        final AuthorisationService authorisationService =\n            requireNonNull(ConsensusModule.Configuration.authorisationServiceSupplier()).get();\n\n        assertTrue(authorisationService.isAuthorised(\n            MessageHeaderDecoder.SCHEMA_ID, BackupQueryDecoder.TEMPLATE_ID, null, new byte[0]));\n        assertFalse(authorisationService.isAuthorised(\n            MessageHeaderDecoder.SCHEMA_ID, SessionConnectRequestDecoder.TEMPLATE_ID, null, new byte[0]));\n    }\n\n    @Test\n    void shouldUseAllowAllAuthorisationSupplierWhenPropertySet()\n    {\n        final String denyAll = \"ALLOW_ALL\";\n        System.setProperty(ConsensusModule.Configuration.AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME, denyAll);\n        try\n        {\n            final AuthorisationService authorisationService =\n                requireNonNull(ConsensusModule.Configuration.authorisationServiceSupplier()).get();\n\n            assertTrue(authorisationService.isAuthorised(\n                MessageHeaderDecoder.SCHEMA_ID, BackupQueryDecoder.TEMPLATE_ID, null, new byte[0]));\n            assertTrue(authorisationService.isAuthorised(\n                MessageHeaderDecoder.SCHEMA_ID, SessionConnectRequestDecoder.TEMPLATE_ID, null, new byte[0]));\n        }\n        finally\n        {\n            System.clearProperty(ConsensusModule.Configuration.AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        }\n\n        final AuthorisationService authorisationService =\n            requireNonNull(ConsensusModule.Configuration.authorisationServiceSupplier()).get();\n\n        assertTrue(authorisationService.isAuthorised(\n            MessageHeaderDecoder.SCHEMA_ID, BackupQueryDecoder.TEMPLATE_ID, null, new byte[0]));\n        assertFalse(authorisationService.isAuthorised(\n            MessageHeaderDecoder.SCHEMA_ID, SessionConnectRequestDecoder.TEMPLATE_ID, null, new byte[0]));\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ConsensusModuleContextCloseTests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.Counter;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\n\nimport javax.naming.InvalidNameException;\nimport java.io.IOException;\n\nimport static io.aeron.test.Tests.throwOnClose;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass ConsensusModuleContextCloseTests\n{\n    private final CountedErrorHandler countedErrorHandler = mock(CountedErrorHandler.class);\n    private final ErrorHandler errorHandler = mock(\n        ErrorHandler.class, withSettings().extraInterfaces(AutoCloseable.class));\n    private final RecordingLog recordingLog = mock(RecordingLog.class);\n    private final ReflectiveOperationException recodingLogException = new ReflectiveOperationException();\n    private final ClusterMarkFile markFile = mock(ClusterMarkFile.class);\n    private final IOException markFileException = new IOException();\n    private final Aeron aeron = mock(Aeron.class);\n    private final AeronException aeronException = new AeronException();\n    private final Counter moduleState = mock(Counter.class);\n    private final IllegalStateException moduleStateException = new IllegalStateException();\n    private final Counter commitPosition = mock(Counter.class);\n    private final InvalidNameException commitPositionException = new InvalidNameException();\n    private final Counter clusterNodeRole = mock(Counter.class);\n    private final UnsupportedOperationException clusterNodeRoleException = new UnsupportedOperationException();\n    private final Counter controlToggle = mock(Counter.class);\n    private final IllegalArgumentException controlToggleException = new IllegalArgumentException();\n    private final Counter snapshotCounter = mock(Counter.class);\n    private final IllegalMonitorStateException snapshotCounterException = new IllegalMonitorStateException();\n    private final Counter timedOutClientCounter = mock(Counter.class);\n    private final IndexOutOfBoundsException timedOutClientCounterException = new IndexOutOfBoundsException();\n    private ConsensusModule.Context context;\n\n    @BeforeEach\n    void before() throws Exception\n    {\n        throwOnClose(recordingLog, recodingLogException);\n        throwOnClose(markFile, markFileException);\n        throwOnClose(aeron, aeronException);\n        throwOnClose(timedOutClientCounter, timedOutClientCounterException);\n        throwOnClose(snapshotCounter, snapshotCounterException);\n        throwOnClose(controlToggle, controlToggleException);\n        throwOnClose(moduleState, moduleStateException);\n        throwOnClose(clusterNodeRole, clusterNodeRoleException);\n        throwOnClose(commitPosition, commitPositionException);\n\n        context = new ConsensusModule.Context()\n            .countedErrorHandler(countedErrorHandler)\n            .errorHandler(errorHandler)\n            .recordingLog(recordingLog)\n            .clusterMarkFile(markFile)\n            .aeron(aeron)\n            .moduleStateCounter(moduleState)\n            .clusterNodeRoleCounter(clusterNodeRole)\n            .commitPositionCounter(commitPosition)\n            .controlToggleCounter(controlToggle)\n            .snapshotCounter(snapshotCounter)\n            .timedOutClientCounter(timedOutClientCounter);\n    }\n\n    @Test\n    void ownsAeronClient() throws Exception\n    {\n        context.ownsAeronClient(true);\n\n        final AeronException ex = assertThrows(AeronException.class, context::close);\n\n        assertSame(aeronException, ex);\n\n        final InOrder inOrder = inOrder(countedErrorHandler, errorHandler, aeron);\n        inOrder.verify(countedErrorHandler).onError(recodingLogException);\n        inOrder.verify(countedErrorHandler).onError(markFileException);\n        inOrder.verify((AutoCloseable)errorHandler).close();\n        inOrder.verify(aeron).close();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void doesNotOwnAeronClientAndClientIsNotClosed() throws Exception\n    {\n        context.ownsAeronClient(false);\n        when(aeron.isClosed()).thenReturn(false);\n\n        final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, context::close);\n\n        assertSame(timedOutClientCounterException, ex);\n\n        final Throwable[] expected =\n        {\n            controlToggleException,\n            snapshotCounterException,\n            moduleStateException,\n            clusterNodeRoleException,\n            commitPositionException,\n        };\n\n        assertArrayEquals(expected, ex.getSuppressed());\n\n        final InOrder inOrder = inOrder(countedErrorHandler, errorHandler, aeron);\n        inOrder.verify(countedErrorHandler).onError(recodingLogException);\n        inOrder.verify(countedErrorHandler).onError(markFileException);\n        inOrder.verify((AutoCloseable)errorHandler).close();\n        inOrder.verify(aeron).isClosed();\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void doesNotOwnAeronClientAndClientIsClosed() throws Exception\n    {\n        context.ownsAeronClient(false);\n        when(aeron.isClosed()).thenReturn(true);\n\n        context.close();\n\n        final InOrder inOrder = inOrder(countedErrorHandler, errorHandler, aeron);\n        inOrder.verify(countedErrorHandler).onError(recodingLogException);\n        inOrder.verify(countedErrorHandler).onError(markFileException);\n        inOrder.verify((AutoCloseable)errorHandler).close();\n        inOrder.verify(aeron).isClosed();\n        inOrder.verifyNoMoreInteractions();\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ConsensusModuleContextTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.Counter;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.mark.MarkFileHeaderDecoder;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.AuthenticatorSupplier;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.security.AuthorisationServiceSupplier;\nimport io.aeron.security.DefaultAuthenticatorSupplier;\nimport io.aeron.security.SessionProxy;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestClusterClock;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.AeronCounters.CLUSTER_ELECTION_COUNT_TYPE_ID;\nimport static io.aeron.AeronCounters.CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID;\nimport static io.aeron.AeronCounters.NODE_CONTROL_TOGGLE_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.AUTHENTICATOR_SUPPLIER_PROP_NAME;\nimport static io.aeron.cluster.ConsensusModule.Configuration.AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CLUSTER_CLOCK_PROP_NAME;\nimport static io.aeron.cluster.ConsensusModule.Configuration.COMMIT_POSITION_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CONSENSUS_MODULE_STATE_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.CONTROL_TOGGLE_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.DEFAULT_AUTHORISATION_SERVICE_SUPPLIER;\nimport static io.aeron.cluster.ConsensusModule.Configuration.ELECTION_STATE_TYPE_ID;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.MAX_SERVICE_COUNT;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SERVICE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SNAPSHOT_COUNTER_TYPE_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.TIMER_SERVICE_SUPPLIER_PRIORITY_HEAP;\nimport static io.aeron.cluster.ConsensusModule.Configuration.TIMER_SERVICE_SUPPLIER_PROP_NAME;\nimport static io.aeron.cluster.ConsensusModule.Configuration.TIMER_SERVICE_SUPPLIER_WHEEL;\nimport static io.aeron.cluster.codecs.mark.ClusterComponentType.CONSENSUS_MODULE;\nimport static io.aeron.cluster.service.ClusterMarkFile.ERROR_BUFFER_MIN_LENGTH;\nimport static io.aeron.cluster.service.ClusterMarkFile.HEADER_LENGTH;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.MARK_FILE_DIR_PROP_NAME;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PAGE_MIN_SIZE;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.startsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ConsensusModuleContextTest\n{\n    @TempDir\n    File clusterDir;\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private ConsensusModule.Context context;\n    private final CountersManager countersManager = Tests.newCountersManager(16 * 1024);\n    private long registrationId = 0;\n\n    @BeforeEach\n    void beforeEach()\n    {\n        final Aeron.Context aeronContext = mock(Aeron.Context.class);\n        when(aeronContext.subscriberErrorHandler()).thenReturn(new RethrowingErrorHandler());\n        when(aeronContext.aeronDirectoryName()).thenReturn(\"some aeron dir\");\n        when(aeronContext.useConductorAgentInvoker()).thenReturn(true);\n        when(aeronContext.filePageSize()).thenReturn(PAGE_MIN_SIZE);\n        final AgentInvoker conductorInvoker = mock(AgentInvoker.class);\n        final Aeron aeron = mock(Aeron.class);\n        when(aeron.addCounter(\n            anyInt(), any(DirectBuffer.class), anyInt(), anyInt(), any(DirectBuffer.class), anyInt(), anyInt()))\n            .thenAnswer(Tests.addCounterAnswer(countersManager, () -> registrationId++));\n        when(aeron.context()).thenReturn(aeronContext);\n        when(aeron.conductorAgentInvoker()).thenReturn(conductorInvoker);\n        when(aeron.countersReader()).thenReturn(countersManager);\n\n        context = TestContexts.localhostConsensusModule()\n            .clusterDir(clusterDir)\n            .aeron(aeron)\n            .errorCounter(mock(AtomicCounter.class))\n            .ingressChannel(\"must be specified\")\n            .replicationChannel(\"must be specified\")\n            .moduleStateCounter(newCounter(\"moduleState\", CONSENSUS_MODULE_STATE_TYPE_ID))\n            .electionStateCounter(newCounter(\"electionState\", ELECTION_STATE_TYPE_ID))\n            .electionCounter(newCounter(\"electionCount\", CLUSTER_ELECTION_COUNT_TYPE_ID))\n            .leadershipTermIdCounter(newCounter(\"leadershipTermId\", CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID))\n            .clusterNodeRoleCounter(newCounter(\"clusterNodeRole\", AeronCounters.CLUSTER_NODE_ROLE_TYPE_ID))\n            .commitPositionCounter(newCounter(\"commitPosition\", COMMIT_POSITION_TYPE_ID))\n            .controlToggleCounter(newCounter(\"controlToggle\", CONTROL_TOGGLE_TYPE_ID))\n            .nodeControlToggleCounter(newCounter(\"nodeControlToggle\", NODE_CONTROL_TOGGLE_TYPE_ID))\n            .snapshotCounter(newCounter(\"snapshot\", SNAPSHOT_COUNTER_TYPE_ID))\n            .timedOutClientCounter(newCounter(\"timedOut\", AeronCounters.CLUSTER_CLIENT_TIMEOUT_COUNT_TYPE_ID));\n    }\n\n    private Counter newCounter(final String name, final int typeId)\n    {\n        final AtomicCounter atomicCounter = countersManager.newCounter(name, typeId);\n        return new Counter(countersManager, ++registrationId, atomicCounter.id());\n    }\n\n    @AfterEach\n    void afterEach()\n    {\n        context.close();\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { TIMER_SERVICE_SUPPLIER_WHEEL, TIMER_SERVICE_SUPPLIER_PRIORITY_HEAP })\n    void validTimerServiceSupplier(final String supplierName)\n    {\n        System.setProperty(TIMER_SERVICE_SUPPLIER_PROP_NAME, supplierName);\n        try\n        {\n            context.conclude();\n\n            final TimerServiceSupplier supplier = context.timerServiceSupplier();\n            assertNotNull(supplier);\n\n            final TimerService.TimerHandler timerHandler = mock(TimerService.TimerHandler.class);\n            final TimerService timerService = supplier.newInstance(context.clusterClock().timeUnit(), timerHandler);\n\n            assertNotNull(timerService);\n            assertEquals(supplierName, supplier.getClass().getName());\n        }\n        finally\n        {\n            System.clearProperty(TIMER_SERVICE_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void unknownTimerServiceSupplier()\n    {\n        final String supplierName = \"unknown timer service supplier\";\n        System.setProperty(TIMER_SERVICE_SUPPLIER_PROP_NAME, supplierName);\n        try\n        {\n            final ClusterException exception = assertThrows(ClusterException.class, context::conclude);\n            assertEquals(\"ERROR - invalid TimerServiceSupplier: \" + supplierName, exception.getMessage());\n        }\n        finally\n        {\n            System.clearProperty(TIMER_SERVICE_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void defaultTimerServiceSupplier()\n    {\n        context.conclude();\n\n        final TimerServiceSupplier supplier = context.timerServiceSupplier();\n        assertNotNull(supplier);\n\n        final TimerService.TimerHandler timerHandler = mock(TimerService.TimerHandler.class);\n        final TimerService timerService = supplier.newInstance(context.clusterClock().timeUnit(), timerHandler);\n\n        assertNotNull(timerService);\n        assertEquals(WheelTimerService.class, timerService.getClass());\n    }\n\n    @Test\n    void explicitTimerServiceSupplier()\n    {\n        final TimerServiceSupplier supplier = (clusterClock, timerHandler) -> null;\n\n        context.timerServiceSupplier(supplier);\n        assertSame(supplier, context.timerServiceSupplier());\n\n        context.conclude();\n\n        assertSame(supplier, context.timerServiceSupplier());\n    }\n\n    @Test\n    void rejectInvalidLogChannelParameters()\n    {\n        final String channelTermId = context.logChannel() + \"|\" + CommonContext.TERM_ID_PARAM_NAME + \"=0\";\n        final String channelInitialTermId =\n            context.logChannel() + \"|\" + CommonContext.INITIAL_TERM_ID_PARAM_NAME + \"=0\";\n        final String channelTermOffset = context.logChannel() + \"|\" + CommonContext.TERM_OFFSET_PARAM_NAME + \"=0\";\n\n        assertThrows(ConfigurationException.class, () -> context.clone().logChannel(channelTermId).conclude());\n        assertThrows(ConfigurationException.class, () -> context.clone().logChannel(channelInitialTermId).conclude());\n        assertThrows(ConfigurationException.class, () -> context.clone().logChannel(channelTermOffset).conclude());\n    }\n\n    @Test\n    void defaultAuthorisationServiceSupplierAllowsBackupAndStandby()\n    {\n        assertSame(AllowBackupAndStandbyAuthorisationService.INSTANCE, DEFAULT_AUTHORISATION_SERVICE_SUPPLIER.get());\n    }\n\n    @Test\n    void shouldUseDefaultAuthorisationServiceSupplierIfTheSystemPropertyIsNotSet()\n    {\n        assertNull(context.authorisationServiceSupplier());\n\n        context.conclude();\n\n        System.clearProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        assertSame(DEFAULT_AUTHORISATION_SERVICE_SUPPLIER, context.authorisationServiceSupplier());\n    }\n\n    @Test\n    void shouldUseDefaultAuthorisationServiceSupplierIfTheSystemPropertyIsSetToEmptyValue()\n    {\n        System.setProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME, \"\");\n        try\n        {\n            assertNull(context.authorisationServiceSupplier());\n\n            context.conclude();\n\n            assertSame(DEFAULT_AUTHORISATION_SERVICE_SUPPLIER, context.authorisationServiceSupplier());\n        }\n        finally\n        {\n            System.clearProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldInstantiateAuthorisationServiceSupplierBasedOnTheSystemProperty()\n    {\n        System.setProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME, TestAuthorisationSupplier.class.getName());\n        try\n        {\n            context.conclude();\n            final AuthorisationServiceSupplier supplier = context.authorisationServiceSupplier();\n            assertNotSame(DEFAULT_AUTHORISATION_SERVICE_SUPPLIER, supplier);\n            assertInstanceOf(TestAuthorisationSupplier.class, supplier);\n        }\n        finally\n        {\n            System.clearProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldUseProvidedAuthorisationServiceSupplierInstance()\n    {\n        final AuthorisationServiceSupplier providedSupplier = mock(AuthorisationServiceSupplier.class);\n        context.authorisationServiceSupplier(providedSupplier);\n        assertSame(providedSupplier, context.authorisationServiceSupplier());\n\n        System.setProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME, TestAuthorisationSupplier.class.getName());\n        try\n        {\n            context.conclude();\n            assertSame(providedSupplier, context.authorisationServiceSupplier());\n        }\n        finally\n        {\n            System.clearProperty(AUTHORISATION_SERVICE_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldUseDefaultAuthenticatorSupplierIfTheSystemPropertyIsSetToEmptyValue()\n    {\n        System.setProperty(AUTHENTICATOR_SUPPLIER_PROP_NAME, \"\");\n        try\n        {\n            assertNull(context.authenticatorSupplier());\n\n            context.conclude();\n\n            final AuthenticatorSupplier authenticatorSupplier = context.authenticatorSupplier();\n            assertSame(DefaultAuthenticatorSupplier.INSTANCE, authenticatorSupplier);\n        }\n        finally\n        {\n            System.clearProperty(AUTHENTICATOR_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldInstantiateAuthenticatorSupplierBasedOnTheSystemProperty()\n    {\n        System.setProperty(AUTHENTICATOR_SUPPLIER_PROP_NAME, TestAuthenticatorSupplier.class.getName());\n        try\n        {\n            context.conclude();\n            final AuthenticatorSupplier supplier = context.authenticatorSupplier();\n            assertInstanceOf(TestAuthenticatorSupplier.class, supplier);\n        }\n        finally\n        {\n            System.clearProperty(AUTHENTICATOR_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldUseProvidedAAuthenticatorSupplierInstance()\n    {\n        final AuthenticatorSupplier providedSupplier = mock(AuthenticatorSupplier.class);\n        context.authenticatorSupplier(providedSupplier);\n        assertSame(providedSupplier, context.authenticatorSupplier());\n\n        System.setProperty(AUTHENTICATOR_SUPPLIER_PROP_NAME, TestAuthenticatorSupplier.class.getName());\n        try\n        {\n            context.conclude();\n            assertSame(providedSupplier, context.authenticatorSupplier());\n        }\n        finally\n        {\n            System.clearProperty(AUTHENTICATOR_SUPPLIER_PROP_NAME);\n        }\n    }\n\n    @Test\n    void writeAuthenticatorSupplierClassNameIntoTheMarkFile()\n    {\n        final TestAuthenticatorSupplier authenticatorSupplier = new TestAuthenticatorSupplier();\n        final String authenticatorSupplierClassName = authenticatorSupplier.getClass().getName();\n        context.authenticatorSupplier(authenticatorSupplier);\n\n        context.conclude();\n\n        final ClusterMarkFile markFile = context.clusterMarkFile();\n        assertNotNull(markFile);\n        final MarkFileHeaderDecoder decoder = markFile.decoder();\n        decoder.sbeRewind();\n        assertEquals(ClusterMarkFile.SEMANTIC_VERSION, decoder.version());\n        assertEquals(CONSENSUS_MODULE, decoder.componentType());\n        assertEquals(SystemUtil.getPid(), decoder.pid());\n        assertEquals(SERVICE_ID, decoder.serviceId());\n        assertEquals(context.aeron().context().aeronDirectoryName(), decoder.aeronDirectory());\n        assertEquals(context.controlChannel(), decoder.controlChannel());\n        assertEquals(context.ingressChannel(), decoder.ingressChannel());\n        assertNotNull(decoder.serviceName());\n        assertEquals(authenticatorSupplierClassName, decoder.authenticator());\n    }\n\n    @Test\n    void shouldValidateModuleStateCounter()\n    {\n        context.moduleStateCounter(newCounter(\"moduleState\", CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID));\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldValidateElectionStateCounter()\n    {\n        context.electionStateCounter(newCounter(\"electionState\", CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID));\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldValidateClusterNodeRoleCounter()\n    {\n        context.clusterNodeRoleCounter(newCounter(\"clusterNodeRole\", CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID));\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldValidateCommitPositionCounter()\n    {\n        context.commitPositionCounter(newCounter(\"commitPosition\", CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID));\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldValidateControlToggleCounter()\n    {\n        context.controlToggleCounter(newCounter(\"controlToggle\", CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID));\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldValidateSnapshotCounter()\n    {\n        context.snapshotCounter(newCounter(\"snapshot\", CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID));\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldValidateTimedOutClientCounter()\n    {\n        context.timedOutClientCounter(newCounter(\"timedOut\", CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID));\n        assertThrows(ConfigurationException.class, context::conclude);\n    }\n\n    @Test\n    void shouldThrowIllegalStateExceptionIfAnActiveMarkFileExists()\n    {\n        final ConsensusModule.Context another = context.clone();\n        context.conclude();\n\n        final IllegalStateException exception =\n            assertThrowsExactly(IllegalStateException.class, another::conclude);\n        assertThat(exception.getMessage(), startsWith(\"active mark file detected: \"));\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"0, 1000\", \"5000,5000\", \"2000000000, 1000000001\" })\n    void startupCanvassTimeoutMustBeMultiplesOfTheLeaderHeartbeatTimeout(\n        final long startupCanvassTimeoutNs, final long leaderHeartbeatTimeoutNs)\n    {\n        context.startupCanvassTimeoutNs(startupCanvassTimeoutNs)\n            .leaderHeartbeatTimeoutNs(leaderHeartbeatTimeoutNs);\n\n        final ClusterException exception = assertThrows(ClusterException.class, context::conclude);\n        assertEquals(\"ERROR - startupCanvassTimeoutNs=\" + startupCanvassTimeoutNs +\n            \" must be a multiple of leaderHeartbeatTimeoutNs=\" + leaderHeartbeatTimeoutNs,\n            exception.getMessage());\n    }\n\n    @Test\n    void startupCanvassTimeoutMustCanBeSetToBeMultiplesOfTheLeaderHeartbeatTimeout()\n    {\n        context.startupCanvassTimeoutNs(TimeUnit.SECONDS.toNanos(30))\n            .leaderHeartbeatTimeoutNs(TimeUnit.SECONDS.toNanos(5));\n\n        context.conclude();\n    }\n\n    @Test\n    void shouldThrowIfConductorInvokerModeIsNotUsed()\n    {\n        when(context.aeron().context().useConductorAgentInvoker()).thenReturn(false);\n        assertThrows(ClusterException.class, () -> context.conclude());\n    }\n\n    @Test\n    void shouldUseCandidateTermIdFromClusterMarkFileIfNodeStateFileIsNew()\n    {\n        final TestClusterClock clock = new TestClusterClock(MILLISECONDS);\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            new File(clusterDir, ClusterMarkFile.FILENAME),\n            CONSENSUS_MODULE,\n            ERROR_BUFFER_MIN_LENGTH,\n            clock.asEpochClock(),\n            1_000,\n            PAGE_MIN_SIZE);\n        final long existingCandidateTermId = 23;\n\n        assertEquals(Aeron.NULL_VALUE, clusterMarkFile.candidateTermId());\n        clusterMarkFile.encoder().candidateTermId(existingCandidateTermId);\n        context.clusterMarkFile(clusterMarkFile);\n\n        context.conclude();\n\n        assertEquals(existingCandidateTermId, context.nodeStateFile().candidateTerm().candidateTermId());\n    }\n\n    @Test\n    void clusterDirectoryNameShouldMatchClusterDirWhenClusterDirSet() throws IOException\n    {\n        context.clusterDir(clusterDir);\n        context.conclude();\n\n        assertEquals(\n            new File(context.clusterDirectoryName()).getCanonicalPath(), context.clusterDir().getCanonicalPath());\n    }\n\n    @Test\n    void clusterDirectoryNameShouldMatchClusterDirWhenClusterDirectoryNameSet() throws IOException\n    {\n        context.clusterDir(null);\n        context.clusterDirectoryName(clusterDir.getAbsolutePath());\n        context.conclude();\n\n        assertEquals(\n            new File(context.clusterDirectoryName()).getCanonicalPath(), context.clusterDir().getCanonicalPath());\n    }\n\n    @Test\n    void clusterServiceDirectoryNameShouldBeSetFromClusterDirectoryName(@TempDir final Path dir) throws IOException\n    {\n        final File clusterDir = dir.resolve(\"b/./42/../c\").toFile();\n        context.clusterServicesDirectoryName(\"\");\n        context.clusterDirectoryName(\"rubbish\");\n        context.clusterDir(clusterDir);\n\n        context.conclude();\n\n        final String resolvedPath = clusterDir.getCanonicalFile().getAbsolutePath();\n        assertEquals(resolvedPath, context.clusterDirectoryName());\n        assertEquals(resolvedPath, context.clusterServicesDirectoryName());\n    }\n\n    @Test\n    void clusterServiceDirectoryNameShouldBeResolved(@TempDir final Path dir) throws IOException\n    {\n        final Path serviceDirectory = dir.resolve(\"m/n/././././o\");\n        context.clusterServicesDirectoryName(serviceDirectory.toString());\n        context.clusterDirectoryName(\"something else\");\n        context.clusterDir(dir.resolve(\"b/./42/../c\").toFile());\n\n        context.conclude();\n\n        assertEquals(context.clusterDir().getAbsolutePath(), context.clusterDirectoryName());\n        assertEquals(serviceDirectory.toFile().getCanonicalPath(), context.clusterServicesDirectoryName());\n    }\n\n    @Test\n    void concludeShouldCreateMarkFileDirSetViaSystemProperty(final @TempDir File tempDir) throws IOException\n    {\n        final File rootDir = new File(tempDir, \"root\");\n        final File markFileDir = new File(rootDir, \"mark/file/./.././dir\");\n        assertFalse(markFileDir.exists());\n\n        System.setProperty(MARK_FILE_DIR_PROP_NAME, markFileDir.getPath());\n        try\n        {\n            assertSame(null, context.markFileDir());\n\n            context.conclude();\n\n            assertEquals(markFileDir.getCanonicalFile(), context.markFileDir());\n            assertTrue(markFileDir.getCanonicalFile().exists());\n            assertTrue(new File(context.clusterDir(), ClusterMarkFile.LINK_FILENAME).exists());\n        }\n        finally\n        {\n            System.clearProperty(MARK_FILE_DIR_PROP_NAME);\n        }\n    }\n\n    @Test\n    void concludeShouldCreateMarkFileDirSetDirectly(final @TempDir File tempDir) throws IOException\n    {\n        final File rootDir = new File(tempDir, \"root\");\n        final File markFileDir = new File(rootDir, \"mark-file-dir\");\n        assertFalse(markFileDir.exists());\n        context.markFileDir(markFileDir);\n\n        context.conclude();\n\n        assertEquals(markFileDir.getCanonicalFile(), context.markFileDir());\n        assertTrue(markFileDir.getCanonicalFile().exists());\n        assertTrue(new File(context.clusterDir(), ClusterMarkFile.LINK_FILENAME).exists());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldRemoveLinkIfMarkFileIsInClusterDir(final boolean isSet) throws IOException\n    {\n        final File markFileDir = isSet ? context.clusterDir() : null;\n\n        context.markFileDir(markFileDir);\n        final File oldLinkFile = new File(context.clusterDir(), ClusterMarkFile.LINK_FILENAME);\n        assertTrue(oldLinkFile.createNewFile());\n        assertTrue(oldLinkFile.exists());\n\n        context.conclude();\n\n        assertFalse(oldLinkFile.exists());\n    }\n\n    @Test\n    void concludeShouldCreateLinkPointingToTheParentDirectoryOfTheMarkFile(\n        final @TempDir File clusterDir,\n        final @TempDir File markFileDir,\n        final @TempDir File otherDir) throws IOException\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            new File(otherDir, \"test.me\"),\n            CONSENSUS_MODULE,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            10,\n            PAGE_MIN_SIZE);\n        context\n            .clusterDir(clusterDir)\n            .markFileDir(markFileDir)\n            .clusterMarkFile(clusterMarkFile);\n\n        context.conclude();\n\n        assertEquals(clusterDir.getCanonicalFile(), context.clusterDir());\n        assertEquals(markFileDir.getCanonicalFile(), context.markFileDir());\n        assertEquals(otherDir, context.clusterMarkFile().parentDirectory());\n        final File linkFile = new File(context.clusterDir(), ClusterMarkFile.LINK_FILENAME);\n        assertTrue(linkFile.exists());\n        assertEquals(otherDir.getCanonicalPath(), new String(Files.readAllBytes(linkFile.toPath()), US_ASCII));\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"io.aeron.cluster.MillisecondClusterClock\",\n        \"io.aeron.cluster.NanosecondClusterClock\",\n        \"io.aeron.test.cluster.TestClusterClock\" })\n    void shouldSetClusterClockViaSystemProperty(final String clockClassName)\n    {\n        System.setProperty(CLUSTER_CLOCK_PROP_NAME, clockClassName);\n        try\n        {\n            context.clusterClock(null);\n\n            context.conclude();\n\n            final ClusterClock clusterClock = context.clusterClock();\n            assertNotNull(clusterClock);\n            assertEquals(clockClassName, clusterClock.getClass().getName());\n        }\n        finally\n        {\n            System.clearProperty(CLUSTER_CLOCK_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldThrowClusterExceptionIfClockCannotBeCreated()\n    {\n        final String clockClassName = String.class.getName();\n        System.setProperty(CLUSTER_CLOCK_PROP_NAME, clockClassName);\n        try\n        {\n            context.clusterClock(null);\n\n            final ClusterException clusterException =\n                assertThrowsExactly(ClusterException.class, context::conclude);\n            assertEquals(\"ERROR - failed to instantiate ClusterClock \" + clockClassName, clusterException.getMessage());\n            final Throwable cause = clusterException.getCause();\n            assertInstanceOf(ClassCastException.class, cause);\n        }\n        finally\n        {\n            System.clearProperty(CLUSTER_CLOCK_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldUseExplicitlyAssignedClockInstance()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.NANOSECONDS);\n        System.setProperty(CLUSTER_CLOCK_PROP_NAME, String.class.getName());\n        try\n        {\n            context.clusterClock(clock);\n\n            context.conclude();\n\n            assertSame(clock, context.clusterClock());\n        }\n        finally\n        {\n            System.clearProperty(CLUSTER_CLOCK_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldAllowElectionCounterToBeExplicitlySet()\n    {\n        final Counter electionCounter = newCounter(\"x\", CLUSTER_ELECTION_COUNT_TYPE_ID);\n        context.electionCounter(electionCounter);\n        assertSame(electionCounter, context.electionCounter());\n\n        context.conclude();\n\n        assertSame(electionCounter, context.electionCounter());\n    }\n\n    @Test\n    void shouldThrowConfigurationExceptionIfElectionCounterHasWrongType()\n    {\n        final Counter electionCounter = newCounter(\"wrong type id\", 1);\n        context.electionCounter(electionCounter);\n        assertSame(electionCounter, context.electionCounter());\n\n        final ConfigurationException exception = assertThrows(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - The type for counterId=\" + electionCounter.id() +\n            \", typeId=1 does not match the expected=\" + CLUSTER_ELECTION_COUNT_TYPE_ID,\n            exception.getMessage());\n    }\n\n    @Test\n    void shouldCreateElectionCounter()\n    {\n        context.electionCounter(null);\n\n        context.conclude();\n\n        final Counter electionCounter = context.electionCounter();\n        assertNotNull(electionCounter);\n        assertEquals(CLUSTER_ELECTION_COUNT_TYPE_ID, countersManager.getCounterTypeId(electionCounter.id()));\n    }\n\n    @Test\n    void shouldAllowLeadershipTermIdCounterToBeExplicitlySet()\n    {\n        final Counter counter = newCounter(\"x\", CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID);\n        context.leadershipTermIdCounter(counter);\n        assertSame(counter, context.leadershipTermIdCounter());\n\n        context.conclude();\n\n        assertSame(counter, context.leadershipTermIdCounter());\n    }\n\n    @Test\n    void shouldThrowConfigurationExceptionIfLeadershipTermIdCounterHasWrongType()\n    {\n        final Counter counter = newCounter(\"wrong type id\", 5);\n        context.leadershipTermIdCounter(counter);\n        assertSame(counter, context.leadershipTermIdCounter());\n\n        final ConfigurationException exception = assertThrows(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - The type for counterId=\" + counter.id() +\n            \", typeId=5 does not match the expected=\" + CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID,\n            exception.getMessage());\n    }\n\n    @Test\n    void shouldCreateLeadershipTermIdCounter()\n    {\n        context.leadershipTermIdCounter(null);\n\n        context.conclude();\n\n        final Counter counter = context.leadershipTermIdCounter();\n        assertNotNull(counter);\n        assertEquals(CLUSTER_LEADERSHIP_TERM_ID_TYPE_ID, countersManager.getCounterTypeId(counter.id()));\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void shouldGenerateAgentRoleNameIfNotSet(final String emptyAgentRoleName)\n    {\n        context.clusterId(19).clusterMemberId(7).agentRoleName(emptyAgentRoleName);\n\n        context.conclude();\n\n        assertEquals(\"consensus-module-19-7\", context.agentRoleName());\n    }\n\n    @Test\n    void shouldUseSpecifiedAgentRoleName()\n    {\n        context.clusterId(42).clusterMemberId(3).agentRoleName(\"test name\");\n\n        context.conclude();\n\n        assertEquals(\"test name\", context.agentRoleName());\n    }\n\n    @Test\n    void shouldNotSetClientNameOnTheExplicitlyAssignedAeronClient()\n    {\n        context.agentRoleName(\"test\");\n\n        context.conclude();\n\n        verify(context.aeron().context(), never()).clientName(anyString());\n    }\n\n    @Test\n    void shouldUseExplicitlyAssignArchiveContext()\n    {\n        final AeronArchive.Context archiveContext = new AeronArchive.Context()\n            .controlRequestChannel(\"aeron:ipc\")\n            .controlResponseChannel(\"aeron:ipc\");\n        context.archiveContext(archiveContext);\n        assertSame(archiveContext, context.archiveContext());\n\n        try\n        {\n            context.conclude();\n\n            assertSame(archiveContext, context.archiveContext());\n            assertSame(context.aeron(), archiveContext.aeron());\n            assertFalse(archiveContext.ownsAeronClient());\n            assertSame(context.countedErrorHandler(), archiveContext.errorHandler());\n            assertSame(NoOpLock.INSTANCE, archiveContext.lock());\n        }\n        finally\n        {\n            CloseHelper.quietClose(context::close);\n        }\n    }\n\n    @Test\n    void shouldCreateArchiveContextUsingLocalChannelConfiguration()\n    {\n        final String controlChannel = \"aeron:ipc?alias=test\";\n        final int localControlStreamId = 8;\n        System.setProperty(AeronArchive.Configuration.LOCAL_CONTROL_CHANNEL_PROP_NAME, controlChannel);\n        System.setProperty(\n            AeronArchive.Configuration.LOCAL_CONTROL_STREAM_ID_PROP_NAME, Integer.toString(localControlStreamId));\n        context.archiveContext(null);\n        assertNull(context.archiveContext());\n\n        try\n        {\n            context.conclude();\n\n            final AeronArchive.Context archiveContext = context.archiveContext();\n            assertNotNull(archiveContext);\n            assertSame(context.aeron(), archiveContext.aeron());\n            assertFalse(archiveContext.ownsAeronClient());\n            assertSame(context.countedErrorHandler(), archiveContext.errorHandler());\n            assertSame(NoOpLock.INSTANCE, archiveContext.lock());\n            assertEquals(controlChannel, archiveContext.controlRequestChannel());\n            assertEquals(controlChannel, archiveContext.controlResponseChannel());\n            assertEquals(localControlStreamId, archiveContext.controlRequestStreamId());\n            assertNotEquals(localControlStreamId, archiveContext.controlResponseStreamId());\n        }\n        finally\n        {\n            CloseHelper.quietClose(context::close);\n            System.clearProperty(AeronArchive.Configuration.LOCAL_CONTROL_CHANNEL_PROP_NAME);\n            System.clearProperty(AeronArchive.Configuration.LOCAL_CONTROL_STREAM_ID_PROP_NAME);\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"19,20\", \"0,222\" })\n    void shouldCreateAliasForControlStreams(final int clusterId, final int controlResponseStreamId)\n    {\n        final String controlChannel = \"aeron:ipc?term-length=64k\";\n        final int localControlStreamId = 10;\n        System.setProperty(AeronArchive.Configuration.LOCAL_CONTROL_CHANNEL_PROP_NAME, controlChannel);\n        System.setProperty(\n            AeronArchive.Configuration.LOCAL_CONTROL_STREAM_ID_PROP_NAME, Integer.toString(localControlStreamId));\n        System.setProperty(\n            AeronArchive.Configuration.CONTROL_RESPONSE_STREAM_ID_PROP_NAME, Integer.toString(controlResponseStreamId));\n        context.archiveContext(null).clusterId(clusterId);\n        assertNull(context.archiveContext());\n\n        try\n        {\n            context.conclude();\n\n            final AeronArchive.Context archiveContext = context.archiveContext();\n            assertNotNull(archiveContext);\n            assertThat(\n                archiveContext.controlRequestChannel(),\n                Matchers.containsString(\"alias=cm-archive-ctrl-req-cluster-\" + clusterId));\n            assertThat(\n                archiveContext.controlResponseChannel(),\n                Matchers.containsString(\"alias=cm-archive-ctrl-resp-cluster-\" + clusterId));\n            assertEquals(localControlStreamId, archiveContext.controlRequestStreamId());\n            assertEquals(clusterId * 100 + 100 + controlResponseStreamId, archiveContext.controlResponseStreamId());\n        }\n        finally\n        {\n            CloseHelper.quietClose(context::close);\n            System.clearProperty(AeronArchive.Configuration.LOCAL_CONTROL_CHANNEL_PROP_NAME);\n            System.clearProperty(AeronArchive.Configuration.LOCAL_CONTROL_STREAM_ID_PROP_NAME);\n            System.clearProperty(AeronArchive.Configuration.CONTROL_RESPONSE_STREAM_ID_PROP_NAME);\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"aeron:ipc,aeron:ipc?term-length=64k|mtu=8k,\" +\n            \"aeron:ipc?alias=cm-archive-ctrl-req-cluster--65-member-0,\" +\n            \"aeron:ipc?term-length=64k|mtu=8k|alias=cm-archive-ctrl-resp-cluster--65-member-0\",\n        \"aeron:ipc?alias=x,aeron:ipc?alias=y,aeron:ipc?alias=x,aeron:ipc?alias=y\"\n    })\n    void shouldCreateAliasForControlStreamsEvenWhenArchiveContextAssignedExplicitly(\n        final String controlRequestChannel,\n        final String controlResponseChannel,\n        final String expectedControlRequestChannel,\n        final String expectedControlResponseChannel)\n    {\n        final AeronArchive.Context archiveContext = new AeronArchive.Context()\n            .controlRequestChannel(controlRequestChannel)\n            .controlResponseChannel(controlResponseChannel)\n            .controlRequestStreamId(42)\n            .controlResponseStreamId(18);\n        context.archiveContext(archiveContext).clusterId(-65);\n\n        try\n        {\n            context.conclude();\n\n            assertEquals(\n                ChannelUri.parse(archiveContext.controlRequestChannel()),\n                ChannelUri.parse(expectedControlRequestChannel));\n            assertEquals(\n                ChannelUri.parse(archiveContext.controlResponseChannel()),\n                ChannelUri.parse(expectedControlResponseChannel));\n            assertEquals(42, archiveContext.controlRequestStreamId());\n            assertEquals(18, archiveContext.controlResponseStreamId());\n        }\n        finally\n        {\n            CloseHelper.quietClose(context::close);\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 8192, 32 * 1024 })\n    void shouldAlignMarkFileToTheAeronClientFilePageSize(final int filePageSize)\n    {\n        final Aeron.Context aeronContext = context.aeron().context();\n        when(aeronContext.filePageSize()).thenReturn(filePageSize);\n\n        try\n        {\n            context.conclude();\n\n            final File file = new File(context.markFileDir(), ClusterMarkFile.FILENAME);\n            assertTrue(file.exists());\n            assertEquals(BitUtil.align(context.errorBufferLength() + HEADER_LENGTH, filePageSize), file.length());\n\n            verify(aeronContext).filePageSize();\n        }\n        finally\n        {\n            context.close();\n        }\n    }\n\n    @Test\n    void shouldAlignMarkFileBasedOnTheMediaDriverFilePageSize() throws IOException\n    {\n        final Path aeronDir = Paths.get(CommonContext.generateRandomDirName());\n        Files.createDirectories(aeronDir);\n\n        final int filePageSize = 1024 * 1024;\n        try (TestMediaDriver driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.toString())\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .filePageSize(filePageSize), systemTestWatcher))\n        {\n            final ConsensusModule.Context ctx = TestContexts.localhostConsensusModule()\n                .clusterDir(clusterDir)\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .ingressChannel(\"aeron:ipc\")\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\")\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .errorBufferLength(1919191);\n            try\n            {\n                ctx.conclude();\n\n                final File file = new File(ctx.markFileDir(), ClusterMarkFile.FILENAME);\n                assertTrue(file.exists());\n                assertEquals(BitUtil.align(context.errorBufferLength() + HEADER_LENGTH, filePageSize), file.length());\n            }\n            finally\n            {\n                ctx.close();\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = {Integer.MIN_VALUE, -1, Integer.MAX_VALUE, MAX_SERVICE_COUNT + 1})\n    void shouldValidateServiceCount(final int serviceCount)\n    {\n        context.serviceCount(serviceCount);\n\n        final ClusterException clusterException = assertThrowsExactly(ClusterException.class, context::conclude);\n        assertEquals(\"ERROR - service count of range [0, \" + MAX_SERVICE_COUNT + \"]: \" + serviceCount,\n            clusterException.getMessage());\n    }\n\n    @Test\n    void shouldRejectServiceCountZeroWithoutConsensusModuleExtension()\n    {\n        context\n            .serviceCount(0)\n            .consensusModuleExtension(null);\n\n        final ClusterException clusterException = assertThrowsExactly(ClusterException.class, context::conclude);\n        assertEquals(\n            \"ERROR - zero services are only supported when ConsensusModuleExtension is enabled\",\n            clusterException.getMessage());\n    }\n\n    public static class TestAuthorisationSupplier implements AuthorisationServiceSupplier\n    {\n        public AuthorisationService get()\n        {\n            return new TestAuthorisationService();\n        }\n    }\n\n    static class TestAuthorisationService implements AuthorisationService\n    {\n        public boolean isAuthorised(\n            final int protocolId, final int actionId, final Object type, final byte[] encodedPrincipal)\n        {\n            return false;\n        }\n    }\n\n    public static class TestAuthenticatorSupplier implements AuthenticatorSupplier\n    {\n        public Authenticator get()\n        {\n            return new TestAuthenticator();\n        }\n    }\n\n    static class TestAuthenticator implements Authenticator\n    {\n        public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n        {\n        }\n\n        public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n        {\n        }\n\n        public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n        {\n        }\n\n        public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n        {\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ConsensusModuleSnapshotTakerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.logbuffer.BufferClaim;\n\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\nimport org.mockito.stubbing.Answer;\n\nimport static io.aeron.Publication.ADMIN_ACTION;\nimport static io.aeron.Publication.BACK_PRESSURED;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.agrona.BitUtil.align;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nclass ConsensusModuleSnapshotTakerTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[4096]);\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final ConsensusModuleDecoder consensusModuleDecoder = new ConsensusModuleDecoder();\n    private final PendingMessageTrackerDecoder pendingMessageTrackerDecoder = new PendingMessageTrackerDecoder();\n    private final TimerDecoder timerDecoder = new TimerDecoder();\n    private final ClusterSessionDecoder clusterSessionDecoder = new ClusterSessionDecoder();\n    private final ExclusivePublication publication = mock(ExclusivePublication.class);\n    private final IdleStrategy idleStrategy = mock(IdleStrategy.class);\n\n    private final ConsensusModuleSnapshotTaker snapshotTaker = new ConsensusModuleSnapshotTaker(\n        publication, idleStrategy, null);\n\n    @Test\n    void snapshotConsensusModuleState()\n    {\n        final int offset = 32;\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ConsensusModuleEncoder.BLOCK_LENGTH;\n        final long nextSessionId = 42;\n        final long nextServiceSessionId = -4_000_000_001L;\n        final long logServiceSessionId = -4_000_000_000L;\n        final int pendingMessageCapacity = 4096;\n        when(publication.tryClaim(eq(length), any()))\n            .thenReturn(BACK_PRESSURED, ADMIN_ACTION)\n            .thenAnswer(mockTryClaim(offset));\n\n        snapshotTaker.snapshotConsensusModuleState(\n            nextSessionId, nextServiceSessionId, logServiceSessionId, pendingMessageCapacity);\n\n        final InOrder inOrder = inOrder(idleStrategy, publication);\n        inOrder.verify(idleStrategy).reset();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verifyNoMoreInteractions();\n\n        consensusModuleDecoder.wrapAndApplyHeader(buffer, offset + HEADER_LENGTH, messageHeaderDecoder);\n        assertEquals(nextSessionId, consensusModuleDecoder.nextSessionId());\n        assertEquals(nextServiceSessionId, consensusModuleDecoder.nextServiceSessionId());\n        assertEquals(logServiceSessionId, consensusModuleDecoder.logServiceSessionId());\n        assertEquals(pendingMessageCapacity, consensusModuleDecoder.pendingMessageCapacity());\n    }\n\n    @Test\n    void snapshotPendingServiceMessageTracker()\n    {\n        final int offset = 108;\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + PendingMessageTrackerEncoder.BLOCK_LENGTH;\n        final int serviceId = 6;\n        final PendingServiceMessageTracker pendingServiceMessageTracker = new PendingServiceMessageTracker(\n            serviceId, mock(Counter.class), mock(LogPublisher.class), mock(ClusterClock.class));\n        pendingServiceMessageTracker.enqueueMessage(buffer, 32, 0);\n        final int capacity = pendingServiceMessageTracker.size();\n        when(publication.tryClaim(eq(length), any()))\n            .thenReturn(ADMIN_ACTION)\n            .thenAnswer(mockTryClaim(offset));\n        when(publication.offer(any(), anyInt(), anyInt()))\n            .thenReturn(BACK_PRESSURED, 9L);\n\n        snapshotTaker.snapshot(pendingServiceMessageTracker, mock(ErrorHandler.class));\n\n        final InOrder inOrder = inOrder(idleStrategy, publication);\n        inOrder.verify(idleStrategy).reset();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verify(idleStrategy).reset();\n        inOrder.verify(publication).offer(any(), anyInt(), anyInt());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).offer(any(), anyInt(), anyInt());\n        inOrder.verifyNoMoreInteractions();\n\n        pendingMessageTrackerDecoder.wrapAndApplyHeader(buffer, offset + HEADER_LENGTH, messageHeaderDecoder);\n        assertEquals(-8791026472627208190L, pendingMessageTrackerDecoder.nextServiceSessionId());\n        assertEquals(-8791026472627208192L, pendingMessageTrackerDecoder.logServiceSessionId());\n        assertEquals(capacity, pendingMessageTrackerDecoder.pendingMessageCapacity());\n        assertEquals(serviceId, pendingMessageTrackerDecoder.serviceId());\n    }\n\n    @Test\n    void snapshotPendingServiceMessageTrackerWithServiceMessagesMissedByFollower()\n    {\n        final int serviceId = 6;\n        final PendingServiceMessageTracker pendingServiceMessageTracker = new PendingServiceMessageTracker(\n            serviceId, mock(Counter.class), mock(LogPublisher.class), mock(ClusterClock.class));\n        final AtomicBuffer headerMessageBuffer = new UnsafeBuffer(new byte[1024]);\n\n        final long expectedLogServiceSessionId = pendingServiceMessageTracker.logServiceSessionId() + 1;\n        final long expectedNextServiceSessionId = expectedLogServiceSessionId + 1;\n        when(publication.tryClaim(anyInt(), any())).thenAnswer(\n            (invocation) ->\n            {\n                final int length = invocation.getArgument(0, Integer.class);\n                final BufferClaim bufferClaim = invocation.getArgument(1, BufferClaim.class);\n\n                bufferClaim.wrap(headerMessageBuffer, 0, length + 32);\n                return (long)length;\n            });\n\n        pendingServiceMessageTracker.sweepFollowerMessages(expectedLogServiceSessionId);\n\n        snapshotTaker.snapshot(pendingServiceMessageTracker, mock(ErrorHandler.class));\n\n        pendingMessageTrackerDecoder.wrapAndApplyHeader(headerMessageBuffer, HEADER_LENGTH, messageHeaderDecoder);\n        assertEquals(expectedNextServiceSessionId, pendingMessageTrackerDecoder.nextServiceSessionId());\n        assertEquals(expectedLogServiceSessionId, pendingMessageTrackerDecoder.logServiceSessionId());\n    }\n\n    @Test\n    void snapshotTimer()\n    {\n        final int offset = 18;\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + TimerEncoder.BLOCK_LENGTH;\n        final long correlationId = -901;\n        final long deadline = 12345678901L;\n        when(publication.tryClaim(eq(length), any()))\n            .thenReturn(BACK_PRESSURED, ADMIN_ACTION)\n            .thenAnswer(mockTryClaim(offset));\n\n        snapshotTaker.snapshotTimer(correlationId, deadline);\n\n        final InOrder inOrder = inOrder(idleStrategy, publication);\n        inOrder.verify(idleStrategy).reset();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verifyNoMoreInteractions();\n\n        timerDecoder.wrapAndApplyHeader(buffer, offset + HEADER_LENGTH, messageHeaderDecoder);\n        assertEquals(correlationId, timerDecoder.correlationId());\n        assertEquals(deadline, timerDecoder.deadline());\n    }\n\n    @Test\n    void snapshotSessionShouldUseTryClaimIfDataFitsIntoMaxPayloadSize()\n    {\n        final int offset = 10;\n        final String responseChannel = \"aeron:ipc\";\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ClusterSessionEncoder.BLOCK_LENGTH +\n            ClusterSessionEncoder.responseChannelHeaderLength() + responseChannel.length();\n        final ClusterSession clusterSession = new ClusterSession(2, 556, 42, responseChannel, \"\");\n        clusterSession.loadSnapshotState(\n            13, 1024, 800, CloseReason.CLIENT_ACTION);\n        when(publication.maxPayloadLength()).thenReturn(length);\n        when(publication.tryClaim(eq(length), any()))\n            .thenReturn(BACK_PRESSURED, ADMIN_ACTION)\n            .thenAnswer(mockTryClaim(offset));\n\n        snapshotTaker.snapshotSession(clusterSession);\n\n        final InOrder inOrder = inOrder(idleStrategy, publication);\n        inOrder.verify(publication).maxPayloadLength();\n        inOrder.verify(idleStrategy).reset();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verifyNoMoreInteractions();\n\n        clusterSessionDecoder.wrapAndApplyHeader(buffer, offset + HEADER_LENGTH, messageHeaderDecoder);\n        assertEquals(clusterSession.id(), clusterSessionDecoder.clusterSessionId());\n        assertEquals(clusterSession.correlationId(), clusterSessionDecoder.correlationId());\n        assertEquals(clusterSession.openedLogPosition(), clusterSessionDecoder.openedLogPosition());\n        assertEquals(Aeron.NULL_VALUE, clusterSessionDecoder.timeOfLastActivity());\n        assertEquals(clusterSession.closeReason(), clusterSessionDecoder.closeReason());\n        assertEquals(clusterSession.responseStreamId(), clusterSessionDecoder.responseStreamId());\n        assertEquals(responseChannel, clusterSessionDecoder.responseChannel());\n    }\n\n    @Test\n    void snapshotSessionShouldUseOfferIfDataDoesNotFitIntoMaxPayloadSize()\n    {\n        final String responseChannel = \"aeron:ipc?alias=very very very long string|mtu=4444\";\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ClusterSessionEncoder.BLOCK_LENGTH +\n            ClusterSessionEncoder.responseChannelHeaderLength() + responseChannel.length();\n        final ClusterSession clusterSession = new ClusterSession(0, 42, 4, responseChannel, \"\");\n        clusterSession.loadSnapshotState(\n            -1, 76, 98, CloseReason.TIMEOUT);\n\n        when(publication.maxPayloadLength()).thenReturn(length - 1);\n        when(publication.offer(any(), eq(0), eq(length)))\n            .thenReturn(BACK_PRESSURED, ADMIN_ACTION)\n            .thenAnswer(mockOffer());\n\n        snapshotTaker.snapshotSession(clusterSession);\n\n        final InOrder inOrder = inOrder(idleStrategy, publication);\n        inOrder.verify(publication).maxPayloadLength();\n        inOrder.verify(idleStrategy).reset();\n        inOrder.verify(publication).offer(any(), anyInt(), anyInt());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).offer(any(), anyInt(), anyInt());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).offer(any(), anyInt(), anyInt());\n        inOrder.verifyNoMoreInteractions();\n\n        clusterSessionDecoder.wrapAndApplyHeader(buffer, 0, messageHeaderDecoder);\n        assertEquals(clusterSession.id(), clusterSessionDecoder.clusterSessionId());\n        assertEquals(clusterSession.correlationId(), clusterSessionDecoder.correlationId());\n        assertEquals(clusterSession.openedLogPosition(), clusterSessionDecoder.openedLogPosition());\n        assertEquals(Aeron.NULL_VALUE, clusterSessionDecoder.timeOfLastActivity());\n        assertEquals(clusterSession.closeReason(), clusterSessionDecoder.closeReason());\n        assertEquals(clusterSession.responseStreamId(), clusterSessionDecoder.responseStreamId());\n        assertEquals(responseChannel, clusterSessionDecoder.responseChannel());\n    }\n\n    private Answer<Long> mockTryClaim(final int offset)\n    {\n        return (invocation) ->\n        {\n            final int length = invocation.getArgument(0);\n            final BufferClaim bufferClaim = invocation.getArgument(1);\n            final int frameLength = length + HEADER_LENGTH;\n            final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n            bufferClaim.wrap(buffer, offset, alignedFrameLength);\n            return 1024L;\n        };\n    }\n\n    private Answer<Long> mockOffer()\n    {\n        return (invocation) ->\n        {\n            final DirectBuffer srcBuffer = invocation.getArgument(0);\n            final int srcOffset = invocation.getArgument(1);\n            final int length = invocation.getArgument(2);\n            buffer.putBytes(0, srcBuffer, srcOffset, length);\n            return 128L;\n        };\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/ElectionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestClusterClock;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.mockito.InOrder;\nimport org.mockito.Mockito;\n\nimport java.util.Random;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.ConsensusModuleAgent.APPEND_POSITION_FLAG_NONE;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyBoolean;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.anyShort;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.atLeastOnce;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.reset;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\nclass ElectionTest\n{\n    private static final long RECORDING_ID = 600L;\n    private static final int LOG_SESSION_ID = 777;\n    private static final int VERSION = ConsensusModule.Configuration.PROTOCOL_SEMANTIC_VERSION;\n    private final Aeron aeron = mock(Aeron.class);\n    private final Counter electionStateCounter = mock(Counter.class);\n    private final Counter electionCounter = mock(Counter.class);\n    private final Counter commitPositionCounter = mock(Counter.class);\n    private final Subscription subscription = mock(Subscription.class);\n    private final Image logImage = mock(Image.class);\n    private final RecordingLog recordingLog = mock(RecordingLog.class);\n    private final ClusterMarkFile clusterMarkFile = mock(ClusterMarkFile.class);\n    private final NodeStateFile nodeStateFile = mock(NodeStateFile.class);\n    private final NodeStateFile.CandidateTerm candidateTerm = mock(NodeStateFile.CandidateTerm.class);\n    private final ConsensusPublisher consensusPublisher = mock(ConsensusPublisher.class);\n    private final ConsensusModuleAgent consensusModuleAgent = mock(ConsensusModuleAgent.class);\n    private final CountedErrorHandler countedErrorHandler = mock(CountedErrorHandler.class);\n    private final TestClusterClock clock = new TestClusterClock(NANOSECONDS);\n    private final MutableLong markFileCandidateTermId = new MutableLong(-1);\n\n    private final ConsensusModule.Context ctx = new ConsensusModule.Context()\n        .aeron(aeron)\n        .recordingLog(recordingLog)\n        .clusterClock(clock)\n        .epochClock(clock.asEpochClock())\n        .random(new Random())\n        .electionStateCounter(electionStateCounter)\n        .electionCounter(electionCounter)\n        .commitPositionCounter(commitPositionCounter)\n        .clusterMarkFile(clusterMarkFile)\n        .nodeStateFile(nodeStateFile)\n        .countedErrorHandler(countedErrorHandler);\n\n    @BeforeEach\n    void before()\n    {\n        when(aeron.addCounter(anyInt(), anyString())).thenReturn(electionStateCounter);\n        when(aeron.addSubscription(anyString(), anyInt())).thenReturn(subscription);\n        when(consensusModuleAgent.logRecordingId()).thenReturn(RECORDING_ID);\n        when(consensusModuleAgent.addLogPublication(anyLong())).thenReturn(LOG_SESSION_ID);\n        when(subscription.imageBySessionId(anyInt())).thenReturn(logImage);\n        when(nodeStateFile.candidateTerm()).thenReturn(candidateTerm);\n\n        when(candidateTerm.candidateTermId()).thenAnswer((invocation) -> markFileCandidateTermId.get());\n        when(nodeStateFile.proposeMaxCandidateTermId(anyLong(), anyLong(), anyLong())).thenAnswer(\n            (invocation) ->\n            {\n                final long candidateTermId = invocation.getArgument(0);\n                final long existingCandidateTermId = markFileCandidateTermId.get();\n\n                if (candidateTermId > existingCandidateTermId)\n                {\n                    markFileCandidateTermId.set(candidateTermId);\n                    return candidateTermId;\n                }\n\n                return existingCandidateTermId;\n            });\n        doAnswer(invocation ->\n        {\n            final ClusterMember member = invocation.getArgument(0);\n            member\n                .leadershipTermId(invocation.getArgument(1))\n                .logPosition(invocation.getArgument(2))\n                .timeOfLastAppendPositionNs(clock.timeNanos());\n            return null;\n        }).when(consensusModuleAgent).updateMemberLogPosition(any(ClusterMember.class), anyLong(), anyLong());\n    }\n\n    @Test\n    void shouldElectSingleNodeClusterLeader()\n    {\n        final long leadershipTermId = NULL_VALUE;\n        final long logPosition = 0;\n        final ClusterMember[] clusterMembers = ClusterMember.parse(\n            \"0,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint\");\n\n        final ClusterMember thisMember = clusterMembers[0];\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, thisMember);\n\n        final long newLeadershipTermId = leadershipTermId + 1;\n        when(recordingLog.isUnknown(newLeadershipTermId)).thenReturn(Boolean.TRUE);\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        election.doWork(clock.nanoTime());\n        election.doWork(clock.nanoTime());\n        election.doWork(clock.nanoTime());\n\n        verify(consensusModuleAgent).joinLogAsLeader(eq(newLeadershipTermId), eq(logPosition), anyInt(), eq(true));\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_READY.code());\n        verify(electionCounter).incrementRelease();\n        verify(recordingLog).ensureCoherent(\n            RECORDING_ID,\n            NULL_VALUE,\n            logPosition,\n            newLeadershipTermId,\n            logPosition,\n            NULL_VALUE,\n            clock.nanoTime(),\n            clock.nanoTime(),\n            ctx.fileSyncLevel());\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    void shouldElectAppointedLeader()\n    {\n        final long leadershipTermId = NULL_VALUE;\n        final long logPosition = 0;\n        final long commitPosition = 42;\n        final int appVersion = -98;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember candidateMember = clusterMembers[0];\n        when(consensusModuleAgent.logRecordingId()).thenReturn(RECORDING_ID);\n        when(consensusModuleAgent.quorumPositionBoundedByLeaderLog(anyLong(), anyLong())).thenReturn(commitPosition);\n\n        ctx.appointedLeaderId(candidateMember.id()).appVersion(appVersion);\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, candidateMember);\n\n        final long candidateTermId = leadershipTermId + 1;\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 1, VERSION);\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 2, VERSION);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n\n        clock.increment(ctx.electionTimeoutNs() >> 1);\n        election.doWork(clock.nanoTime());\n        election.doWork(clock.nanoTime());\n        verify(consensusPublisher).requestVote(\n            clusterMembers[1].publication(),\n            leadershipTermId,\n            logPosition,\n            candidateTermId,\n            candidateMember.id());\n        verify(consensusPublisher).requestVote(\n            clusterMembers[2].publication(),\n            leadershipTermId,\n            logPosition,\n            candidateTermId,\n            candidateMember.id());\n        verify(electionStateCounter).setRelease(ElectionState.CANDIDATE_BALLOT.code());\n        verify(consensusModuleAgent).role(Cluster.Role.CANDIDATE);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.CANDIDATE);\n        election.onVote(\n            leadershipTermId, logPosition, candidateTermId, candidateMember.id(), clusterMembers[1].id(), true);\n        election.onVote(\n            leadershipTermId, logPosition, candidateTermId, candidateMember.id(), clusterMembers[2].id(), true);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_LOG_REPLICATION.code());\n\n        election.doWork(clock.nanoTime());\n\n        verify(consensusPublisher).newLeadershipTerm(\n            clusterMembers[1].publication(),\n            leadershipTermId,\n            0,\n            0,\n            NULL_POSITION,\n            candidateTermId,\n            logPosition,\n            logPosition,\n            commitPosition, RECORDING_ID,\n            clock.nanoTime(),\n            candidateMember.id(),\n            LOG_SESSION_ID,\n            appVersion,\n            election.isLeaderStartup()\n        );\n\n        verify(consensusPublisher).newLeadershipTerm(\n            clusterMembers[2].publication(),\n            leadershipTermId,\n            0,\n            0,\n            NULL_POSITION,\n            candidateTermId,\n            logPosition,\n            logPosition,\n            commitPosition, RECORDING_ID,\n            clock.nanoTime(),\n            candidateMember.id(),\n            LOG_SESSION_ID,\n            appVersion,\n            election.isLeaderStartup()\n        );\n\n        when(recordingLog.isUnknown(candidateTermId)).thenReturn(Boolean.TRUE);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_REPLAY.code());\n\n        election.doWork(clock.nanoTime());\n\n        verify(consensusModuleAgent).joinLogAsLeader(eq(candidateTermId), eq(logPosition), anyInt(), eq(true));\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_READY.code());\n        verify(recordingLog).ensureCoherent(\n            RECORDING_ID,\n            NULL_VALUE,\n            logPosition,\n            candidateTermId,\n            logPosition,\n            NULL_VALUE,\n            clock.nanoTime(),\n            clock.nanoTime(),\n            ctx.fileSyncLevel());\n\n        assertEquals(0, clusterMembers[1].logPosition());\n        assertEquals(0, clusterMembers[2].logPosition());\n        assertEquals(candidateTermId, election.leadershipTermId());\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_READY.code());\n\n        when(consensusModuleAgent.appendNewLeadershipTermEvent(anyLong())).thenReturn(true);\n\n        clock.increment(1);\n        election.onAppendPosition(candidateTermId, logPosition, clusterMembers[1].id(), APPEND_POSITION_FLAG_NONE);\n        election.onAppendPosition(candidateTermId, logPosition, clusterMembers[2].id(), APPEND_POSITION_FLAG_NONE);\n        election.doWork(clock.nanoTime());\n        final InOrder inOrder = inOrder(consensusModuleAgent, electionStateCounter);\n        inOrder.verify(consensusModuleAgent).electionComplete(anyLong());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CLOSED.code());\n    }\n\n    @Test\n    void shouldVoteForAppointedLeader()\n    {\n        final long leadershipTermId = NULL_VALUE;\n        final long logPosition = 0;\n        final int candidateId = 0;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember followerMember = clusterMembers[1];\n        final long leaderRecordingId = 983724;\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, followerMember);\n\n        long nowNs = 0;\n        election.doWork(++nowNs);\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        final long candidateTermId = leadershipTermId + 1;\n        election.onRequestVote(leadershipTermId, logPosition, candidateTermId, candidateId, VERSION);\n        verify(consensusPublisher).placeVote(\n            clusterMembers[candidateId].publication(),\n            candidateTermId,\n            leadershipTermId,\n            logPosition,\n            candidateId,\n            followerMember.id(),\n            true);\n        election.doWork(++nowNs);\n        clock.increment(1);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_BALLOT.code());\n\n        final int logSessionId = -7;\n        final long commitPosition = 100;\n        election.onNewLeadershipTerm(\n            leadershipTermId,\n            NULL_VALUE,\n            NULL_POSITION,\n            NULL_POSITION,\n            candidateTermId,\n            logPosition,\n            logPosition,\n            commitPosition,\n            leaderRecordingId,\n            clock.nanoTime(),\n            candidateId,\n            logSessionId,\n            false);\n\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_REPLAY.code());\n\n        election.doWork(++nowNs);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_LOG_INIT.code());\n\n        election.doWork(++nowNs);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_LOG_AWAIT.code());\n\n        when(consensusModuleAgent.tryJoinLogAsFollower(any(), anyBoolean(), anyLong())).thenReturn(true);\n        election.doWork(++nowNs);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_READY.code());\n\n        when(consensusPublisher.appendPosition(\n            any(), anyLong(), anyLong(), anyInt(), anyShort())).thenReturn(Boolean.TRUE);\n        when(consensusModuleAgent.appendNewLeadershipTermEvent(anyLong())).thenReturn(true);\n\n        election.doWork(++nowNs);\n        final InOrder inOrder = inOrder(consensusPublisher, consensusModuleAgent, electionStateCounter);\n        inOrder.verify(consensusPublisher).appendPosition(\n            clusterMembers[candidateId].publication(),\n            candidateTermId,\n            logPosition,\n            followerMember.id(),\n            APPEND_POSITION_FLAG_NONE);\n        inOrder.verify(consensusModuleAgent).electionComplete(anyLong());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CLOSED.code());\n    }\n\n    @Test\n    void shouldCanvassMembersInSuccessfulLeadershipBid()\n    {\n        final long logPosition = 0;\n        final long leadershipTermId = NULL_VALUE;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember followerMember = clusterMembers[1];\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, followerMember);\n\n        election.doWork(0);\n\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n        verify(consensusPublisher).canvassPosition(\n            clusterMembers[0].publication(), leadershipTermId, logPosition, leadershipTermId, followerMember.id());\n        verify(consensusPublisher).canvassPosition(\n            clusterMembers[2].publication(), leadershipTermId, logPosition, leadershipTermId, followerMember.id());\n\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 0, VERSION);\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 2, VERSION);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n    }\n\n    @Test\n    void shouldVoteForCandidateDuringNomination()\n    {\n        final long logPosition = 0;\n        final long leadershipTermId = NULL_VALUE;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember followerMember = clusterMembers[1];\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, followerMember);\n\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 0, VERSION);\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 2, VERSION);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n\n        clock.increment(1);\n        final long candidateTermId = leadershipTermId + 1;\n        election.onRequestVote(leadershipTermId, logPosition, candidateTermId, 0, VERSION);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_BALLOT.code());\n    }\n\n    @Test\n    void shouldTimeoutCanvassWithMajority()\n    {\n        final long leadershipTermId = NULL_VALUE;\n        final long logPosition = 0;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember followerMember = clusterMembers[1];\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, followerMember);\n\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onAppendPosition(leadershipTermId, logPosition, 0, APPEND_POSITION_FLAG_NONE);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n\n        clock.increment(ctx.startupCanvassTimeoutNs());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n    }\n\n    @Test\n    void shouldWinCandidateBallotWithMajority()\n    {\n        final long leadershipTermId = NULL_VALUE;\n        final long logPosition = 0;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember candidateMember = clusterMembers[1];\n\n        final Election election = newElection(false, leadershipTermId, logPosition, clusterMembers, candidateMember);\n\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 0, VERSION);\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 2, VERSION);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n\n        clock.increment(ctx.electionTimeoutNs() >> 1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANDIDATE_BALLOT.code());\n\n        clock.increment(ctx.electionTimeoutNs());\n        final long candidateTermId = leadershipTermId + 1;\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.CANDIDATE);\n        election.onVote(\n            leadershipTermId, logPosition, candidateTermId, candidateMember.id(), clusterMembers[2].id(), true);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_LOG_REPLICATION.code());\n    }\n\n    @ParameterizedTest\n    @CsvSource(value = { \"true,true\", \"true,false\", \"false,false\", \"false,true\" })\n    void shouldBaseStartupValueOnLeader(final boolean isLeaderStart, final boolean isNodeStart)\n    {\n        final long leadershipTermId = 0;\n        final long logPosition = 0;\n        final long leaderRecordingId = 367234;\n        final long commitPosition = 1024;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember followerMember = clusterMembers[1];\n        when(consensusModuleAgent.tryJoinLogAsFollower(any(), anyBoolean(), anyLong())).thenReturn(true);\n\n        final Election election = newElection(\n            isNodeStart, leadershipTermId, logPosition, clusterMembers, followerMember);\n\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        clock.increment(1);\n        final int leaderMemberId = clusterMembers[0].id();\n        election.onNewLeadershipTerm(\n            leadershipTermId,\n            NULL_VALUE,\n            NULL_POSITION,\n            NULL_POSITION,\n            leadershipTermId,\n            logPosition,\n            logPosition,\n            commitPosition,\n            leaderRecordingId,\n            clock.nanoTime(),\n            leaderMemberId,\n            0,\n            isLeaderStart);\n        election.doWork(clock.nanoTime());\n\n        election.doWork(clock.increment(1));\n\n        final long timeAtFollowerLogAwaitNs = clock.increment(1);\n        election.doWork(timeAtFollowerLogAwaitNs);\n\n        election.doWork(clock.increment(1));\n\n        verify(consensusModuleAgent).tryJoinLogAsFollower(logImage, isLeaderStart, timeAtFollowerLogAwaitNs);\n    }\n\n    @Test\n    void shouldElectCandidateWithFullVote()\n    {\n        final long leadershipTermId = NULL_VALUE;\n        final long logPosition = 0;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember candidateMember = clusterMembers[1];\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, candidateMember);\n\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 0, VERSION);\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 2, VERSION);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n\n        clock.increment(ctx.electionTimeoutNs() >> 1);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANDIDATE_BALLOT.code());\n\n        clock.increment(1);\n        final long candidateTermId = leadershipTermId + 1;\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.CANDIDATE);\n        election.onVote(\n            leadershipTermId, logPosition, candidateTermId, candidateMember.id(), clusterMembers[0].id(), true);\n        election.onVote(\n            leadershipTermId, logPosition, candidateTermId, candidateMember.id(), clusterMembers[2].id(), true);\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_LOG_REPLICATION.code());\n    }\n\n    @Test\n    void shouldTimeoutCandidateBallotWithoutMajority()\n    {\n        final long leadershipTermId = NULL_VALUE;\n        final long logPosition = 0;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember candidateMember = clusterMembers[1];\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, candidateMember);\n\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        final InOrder inOrder = Mockito.inOrder(electionStateCounter);\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 0, VERSION);\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 2, VERSION);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n\n        clock.increment(ctx.electionTimeoutNs() >> 1);\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANDIDATE_BALLOT.code());\n\n        clock.increment(ctx.electionTimeoutNs());\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n        assertEquals(leadershipTermId, election.leadershipTermId());\n    }\n\n    @Test\n    void shouldTimeoutFailedCandidateBallotOnSplitVoteThenSucceedOnRetry()\n    {\n        final long leadershipTermId = NULL_VALUE;\n        final long logPosition = 0;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember candidateMember = clusterMembers[1];\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, candidateMember);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        final InOrder inOrder = Mockito.inOrder(electionStateCounter);\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 0, VERSION);\n\n        clock.increment(ctx.startupCanvassTimeoutNs());\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n\n        clock.increment(ctx.electionTimeoutNs() >> 1);\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANDIDATE_BALLOT.code());\n\n        clock.increment(1);\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.CANDIDATE);\n        election.onVote(\n            leadershipTermId, logPosition, leadershipTermId + 1, candidateMember.id(), clusterMembers[2].id(), false);\n        election.doWork(clock.nanoTime());\n\n        clock.increment(ctx.electionTimeoutNs());\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onCanvassPosition(leadershipTermId, logPosition, leadershipTermId, 0, VERSION);\n\n        clock.increment(ctx.leaderHeartbeatTimeoutNs());\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n\n        clock.increment(ctx.electionTimeoutNs());\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANDIDATE_BALLOT.code());\n\n        final long candidateTermId = leadershipTermId + 2;\n        election.onVote(\n            leadershipTermId + 1, logPosition, candidateTermId, candidateMember.id(), clusterMembers[2].id(), true);\n\n        clock.increment(ctx.electionTimeoutNs());\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.LEADER_LOG_REPLICATION.code());\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.LEADER_REPLAY.code());\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        election.doWork(clock.nanoTime());\n        assertEquals(candidateTermId, election.leadershipTermId());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.LEADER_READY.code());\n    }\n\n    @Test\n    void shouldTimeoutFollowerBallotWithoutLeaderEmerging()\n    {\n        final long leadershipTermId = NULL_VALUE;\n        final long logPosition = 0L;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember followerMember = clusterMembers[1];\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, followerMember);\n\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        final InOrder inOrder = Mockito.inOrder(electionStateCounter);\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        final long candidateTermId = leadershipTermId + 1;\n        election.onRequestVote(leadershipTermId, logPosition, candidateTermId, 0, VERSION);\n\n        clock.increment(1);\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_BALLOT.code());\n\n        clock.increment(ctx.electionTimeoutNs());\n        election.doWork(clock.nanoTime());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n        assertEquals(leadershipTermId, election.leadershipTermId());\n    }\n\n    @Test\n    void shouldBecomeFollowerIfEnteringNewElection()\n    {\n        final long leadershipTermId = 1;\n        final long logPosition = 120;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[0];\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.LEADER);\n        final Election election = newElection(false, leadershipTermId, logPosition, clusterMembers, thisMember);\n\n        clock.update(1, clock.timeUnit());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n        verify(consensusModuleAgent).prepareForNewLeadership(logPosition, clock.nanoTime());\n        verify(consensusModuleAgent, atLeastOnce()).role(Cluster.Role.FOLLOWER);\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    void followerShouldReplicateLogBeforeReplayDuringElection()\n    {\n        final long term0Id = 0;\n        final long term1Id = 1;\n        final long term1BaseLogPosition = 60;\n\n        final long term2Id = 2;\n        final long term2BaseLogPosition = 120;\n\n        final long localRecordingId = 2390485;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[1];\n        final ClusterMember liveLeader = clusterMembers[0];\n        final int leaderId = liveLeader.id();\n        final RecordingReplication logReplication = mock(RecordingReplication.class);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.FOLLOWER);\n        when(consensusModuleAgent.prepareForNewLeadership(anyLong(), anyLong())).thenReturn(term1BaseLogPosition);\n\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        final Election followerElection = new Election(\n            true,\n            NULL_VALUE,\n            term0Id,\n            term1BaseLogPosition,\n            term1BaseLogPosition,\n            term1BaseLogPosition,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n\n        long t1 = System.nanoTime();\n        followerElection.doWork(++t1);\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n        reset(consensusPublisher);\n\n        followerElection.onRequestVote(term1Id, term2BaseLogPosition, term2Id, leaderId, VERSION);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_BALLOT.code());\n\n        followerElection.onNewLeadershipTerm(\n            term1Id,\n            term2Id,\n            term2BaseLogPosition,\n            term2BaseLogPosition,\n            term2Id,\n            term2BaseLogPosition,\n            term2BaseLogPosition,\n            term1BaseLogPosition,\n            RECORDING_ID,\n            t1,\n            leaderId,\n            0,\n            true);\n\n        verify(electionStateCounter, times(2)).setRelease(ElectionState.CANVASS.code());\n\n        followerElection.doWork(++t1);\n        verify(consensusPublisher).canvassPosition(\n            liveLeader.publication(),\n            term0Id,\n            term1BaseLogPosition,\n            term0Id,\n            thisMember.id());\n\n        followerElection.onNewLeadershipTerm(\n            term0Id,\n            term1Id,\n            term1BaseLogPosition,\n            term2BaseLogPosition,\n            term2Id,\n            term2BaseLogPosition,\n            term2BaseLogPosition,\n            term1BaseLogPosition,\n            RECORDING_ID,\n            t1,\n            leaderId,\n            0,\n            true);\n\n        when(consensusModuleAgent.newLogReplication(any(), any(), anyLong(), anyLong(), anyLong()))\n            .thenReturn(logReplication);\n        followerElection.doWork(++t1);\n\n        verify(consensusModuleAgent, times(1)).newLogReplication(\n            liveLeader.archiveEndpoint(), liveLeader.archiveResponseEndpoint(), RECORDING_ID, term2BaseLogPosition, t1);\n\n        when(logReplication.hasReplicationEnded()).thenReturn(false);\n        followerElection.doWork(++t1);\n        followerElection.doWork(++t1);\n        followerElection.doWork(++t1);\n        followerElection.doWork(++t1);\n\n        verify(consensusModuleAgent, times(4)).pollArchiveEvents();\n\n        when(logReplication.hasReplicationEnded()).thenReturn(true);\n        when(logReplication.hasStopped()).thenReturn(true);\n        when(logReplication.position()).thenReturn(term2BaseLogPosition);\n        when(logReplication.recordingId()).thenReturn(localRecordingId);\n        when(consensusPublisher.appendPosition(\n            liveLeader.publication(), term2Id, term2BaseLogPosition, thisMember.id(), APPEND_POSITION_FLAG_NONE))\n            .thenReturn(true);\n        t1 += ctx.leaderHeartbeatIntervalNs();\n\n        verify(electionStateCounter, times(2)).setRelease(ElectionState.CANVASS.code());\n        followerElection.doWork(clock.nanoTime());\n\n        verify(consensusPublisher).appendPosition(\n            liveLeader.publication(), term2Id, term2BaseLogPosition, thisMember.id(), APPEND_POSITION_FLAG_NONE);\n        verify(electionStateCounter, times(2)).setRelease(ElectionState.CANVASS.code());\n\n        followerElection.onCommitPosition(term2Id, term2BaseLogPosition, leaderId);\n        followerElection.doWork(++t1);\n        verify(electionStateCounter, times(3)).setRelease(ElectionState.CANVASS.code());\n    }\n\n    @Test\n    void followerShouldTimeoutLeaderIfReplicateLogPositionIsNotCommittedByLeader()\n    {\n        final long term1Id = 1;\n        final long term2Id = 2;\n        final long term1BaseLogPosition = 60;\n        final long term2BaseLogPosition = 120;\n        final long localRecordingId = 2390485;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[1];\n        final ClusterMember liveLeader = clusterMembers[0];\n        final int leaderId = liveLeader.id();\n        final RecordingReplication logReplication = mock(RecordingReplication.class);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.FOLLOWER);\n        when(consensusModuleAgent.prepareForNewLeadership(anyLong(), anyLong())).thenReturn(term1BaseLogPosition);\n\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        final Election election = new Election(\n            true,\n            NULL_VALUE,\n            term1Id,\n            term1BaseLogPosition,\n            term1BaseLogPosition,\n            term1BaseLogPosition,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n\n        election.doWork(clock.increment(1));\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onRequestVote(term1Id, term2BaseLogPosition, term2Id, leaderId, VERSION);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_BALLOT.code());\n\n        election.onNewLeadershipTerm(\n            term1Id,\n            term2Id,\n            term2BaseLogPosition,\n            term2BaseLogPosition,\n            term2Id,\n            term2BaseLogPosition,\n            term2BaseLogPosition,\n            NULL_POSITION,\n            RECORDING_ID,\n            clock.nanoTime(),\n            leaderId,\n            0,\n            true);\n\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_LOG_REPLICATION.code());\n\n        when(consensusModuleAgent.newLogReplication(any(), any(), anyLong(), anyLong(), anyLong()))\n            .thenReturn(logReplication);\n        election.doWork(clock.increment(1));\n\n        verify(consensusModuleAgent, times(1)).newLogReplication(\n            liveLeader.archiveEndpoint(),\n            liveLeader.archiveResponseEndpoint(),\n            RECORDING_ID,\n            term2BaseLogPosition,\n            clock.nanoTime());\n\n        when(logReplication.hasReplicationEnded()).thenReturn(true);\n        when(logReplication.hasStopped()).thenReturn(true);\n        when(logReplication.position()).thenReturn(term2BaseLogPosition);\n        when(logReplication.recordingId()).thenReturn(localRecordingId);\n        when(consensusPublisher.appendPosition(\n            liveLeader.publication(), term1Id, term2BaseLogPosition, thisMember.id(), APPEND_POSITION_FLAG_NONE))\n            .thenReturn(true);\n        clock.increment(ctx.leaderHeartbeatIntervalNs());\n        election.doWork(clock.nanoTime());\n\n        verify(consensusModuleAgent, atLeastOnce()).pollArchiveEvents();\n        verify(consensusPublisher).appendPosition(\n            liveLeader.publication(), term2Id, term2BaseLogPosition, thisMember.id(), APPEND_POSITION_FLAG_NONE);\n        verify(electionStateCounter, never()).setRelease(ElectionState.FOLLOWER_REPLAY.code());\n        reset(countedErrorHandler);\n\n        clock.increment(ctx.leaderHeartbeatTimeoutNs());\n        assertThrows(TimeoutException.class, () -> election.doWork(clock.nanoTime()));\n    }\n\n    @Test\n    void followerShouldProgressThroughFailedElectionsTermsImmediatelyPriorToCurrent()\n    {\n        final long term1Id = 1;\n        final long term10Id = 10;\n        final long term1BaseLogPosition = 60;\n        final long term10BaseLogPosition = 120;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[1];\n        final ClusterMember liveLeader = clusterMembers[0];\n        final int leaderId = liveLeader.id();\n        final RecordingReplication logReplication = mock(RecordingReplication.class);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.FOLLOWER);\n        when(consensusModuleAgent.prepareForNewLeadership(anyLong(), anyLong())).thenReturn(term1BaseLogPosition);\n\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        final Election election = new Election(\n            true,\n            NULL_VALUE,\n            term1Id,\n            term1BaseLogPosition,\n            term1BaseLogPosition,\n            term1BaseLogPosition,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n\n        long t1 = 0;\n        election.doWork(++t1);\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onRequestVote(term1Id, term10BaseLogPosition, term10Id, leaderId, VERSION);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_BALLOT.code());\n\n        election.onNewLeadershipTerm(\n            term1Id,\n            term10Id,\n            term10BaseLogPosition,\n            term10BaseLogPosition,\n            term10Id,\n            term10BaseLogPosition,\n            term10BaseLogPosition,\n            term10BaseLogPosition,\n            RECORDING_ID,\n            t1,\n            leaderId,\n            0,\n            true);\n\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_LOG_REPLICATION.code());\n\n        when(consensusModuleAgent.newLogReplication(any(), any(), anyLong(), anyLong(), anyLong()))\n            .thenReturn(logReplication);\n        election.doWork(++t1);\n\n        verify(consensusModuleAgent, times(1)).newLogReplication(\n            liveLeader.archiveEndpoint(),\n            liveLeader.archiveResponseEndpoint(),\n            RECORDING_ID,\n            term10BaseLogPosition,\n            t1);\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    void followerShouldProgressThroughInterimElectionsTerms()\n    {\n        final long term9Id = 9;\n        final long term10Id = 10;\n        final long term9BaseLogPosition = 60;\n        final long term10BaseLogPosition = 120;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[1];\n        final ClusterMember liveLeader = clusterMembers[0];\n        final int leaderId = liveLeader.id();\n        final RecordingReplication logReplication = mock(RecordingReplication.class);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.FOLLOWER);\n        when(consensusModuleAgent.prepareForNewLeadership(anyLong(), anyLong())).thenReturn(0L);\n\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        final Election election = new Election(\n            true,\n            NULL_VALUE,\n            0,\n            0,\n            0,\n            0,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n\n        long t1 = System.nanoTime();\n        election.doWork(++t1);\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onRequestVote(term9Id, term10BaseLogPosition, term10Id, leaderId, VERSION);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_BALLOT.code());\n        reset(consensusPublisher);\n        reset(electionStateCounter);\n\n        election.onNewLeadershipTerm(\n            term9Id,\n            term10Id,\n            term10BaseLogPosition,\n            term10BaseLogPosition,\n            term10Id,\n            term10BaseLogPosition,\n            term10BaseLogPosition,\n            term10BaseLogPosition,\n            RECORDING_ID,\n            t1,\n            leaderId,\n            0,\n            true);\n\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.doWork(++t1);\n        verify(consensusPublisher).canvassPosition(liveLeader.publication(), 0L, 0L, 0L, thisMember.id());\n\n        for (int i = 0; i < term9Id - 1; i++)\n        {\n            reset(consensusPublisher);\n            reset(electionStateCounter);\n\n            election.onNewLeadershipTerm(\n                i,\n                i + 1,\n                0,\n                0,\n                term10Id,\n                term10BaseLogPosition,\n                term10BaseLogPosition,\n                term10BaseLogPosition,\n                RECORDING_ID,\n                t1,\n                leaderId,\n                0,\n                true);\n\n            election.doWork(++t1);\n            verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_LOG_REPLICATION.code());\n\n            election.doWork(++t1);\n            verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n            election.doWork(++t1);\n            verify(consensusPublisher).canvassPosition(liveLeader.publication(), i + 1, 0L, term10Id, thisMember.id());\n        }\n        reset(consensusPublisher);\n        reset(electionStateCounter);\n\n        election.onNewLeadershipTerm(\n            term9Id - 1,\n            term9Id,\n            term9BaseLogPosition,\n            term10BaseLogPosition,\n            term10Id,\n            term10BaseLogPosition,\n            term10BaseLogPosition,\n            term10BaseLogPosition,\n            RECORDING_ID,\n            t1,\n            leaderId,\n            0,\n            true);\n\n        election.doWork(++t1);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_LOG_REPLICATION.code());\n\n        when(consensusModuleAgent.newLogReplication(any(), any(), anyLong(), anyLong(), anyLong()))\n            .thenReturn(logReplication);\n        election.doWork(++t1);\n\n        verify(consensusModuleAgent).newLogReplication(\n            liveLeader.archiveEndpoint(), liveLeader.archiveResponseEndpoint(), RECORDING_ID, term9BaseLogPosition, t1);\n    }\n\n    @Test\n    void followerShouldReplayAndCatchupWhenLateJoiningClusterInSameTerm()\n    {\n        final long leadershipTermId = 1;\n        final long leaderLogPosition = 120;\n        final long followerLogPosition = 60;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[1];\n        final ClusterMember liveLeader = clusterMembers[0];\n        final int leaderId = liveLeader.id();\n        final LogReplay logReplay = mock(LogReplay.class);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.FOLLOWER);\n        when(consensusModuleAgent.prepareForNewLeadership(anyLong(), anyLong())).thenReturn(followerLogPosition);\n\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        final Election election = new Election(\n            true,\n            NULL_VALUE,\n            1,\n            followerLogPosition,\n            followerLogPosition,\n            followerLogPosition,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n\n        long t1 = 0;\n        election.doWork(++t1);\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onNewLeadershipTerm(\n            leadershipTermId,\n            NULL_VALUE,\n            NULL_POSITION,\n            NULL_POSITION,\n            leadershipTermId,\n            followerLogPosition,\n            leaderLogPosition,\n            leaderLogPosition,\n            RECORDING_ID,\n            t1,\n            leaderId,\n            LOG_SESSION_ID,\n            true);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_REPLAY.code());\n\n        when(consensusModuleAgent.newLogReplay(anyLong(), anyLong())).thenReturn(logReplay);\n        when(logReplay.isDone()).thenReturn(true);\n        election.doWork(++t1);\n        election.doWork(++t1);\n\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_CATCHUP_INIT.code());\n    }\n\n    @Test\n    void followerShouldReplicateReplayAndCatchupWhenLateJoiningClusterInLaterTerm()\n    {\n        final long leadershipTermId = 1;\n        final long leaderLogPosition = 120;\n        final long termBaseLogPosition = 60;\n        final long localRecordingId = 2390485;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[1];\n        final ClusterMember liveLeader = clusterMembers[0];\n        final int leaderId = liveLeader.id();\n        final RecordingReplication logReplication = mock(RecordingReplication.class);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.FOLLOWER);\n\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        final Election election = new Election(\n            true,\n            NULL_VALUE,\n            0,\n            0,\n            0,\n            0,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n\n        long t1 = 0;\n        election.doWork(++t1);\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onNewLeadershipTerm(\n            0,\n            1,\n            termBaseLogPosition,\n            leaderLogPosition,\n            leadershipTermId,\n            termBaseLogPosition,\n            leaderLogPosition,\n            leaderLogPosition,\n            RECORDING_ID,\n            t1,\n            leaderId,\n            0,\n            false);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_LOG_REPLICATION.code());\n\n        when(consensusModuleAgent.newLogReplication(any(), any(), anyLong(), anyLong(), anyLong()))\n            .thenReturn(logReplication);\n        election.doWork(++t1);\n\n        verify(consensusModuleAgent, times(1)).newLogReplication(\n            liveLeader.archiveEndpoint(), liveLeader.archiveResponseEndpoint(), RECORDING_ID, termBaseLogPosition, t1);\n\n        when(logReplication.hasReplicationEnded()).thenReturn(true);\n        when(logReplication.hasStopped()).thenReturn(true);\n        when(logReplication.position()).thenReturn(termBaseLogPosition);\n        when(logReplication.recordingId()).thenReturn(localRecordingId);\n        when(consensusPublisher.appendPosition(any(), anyLong(), anyLong(), anyInt(), anyShort())).thenReturn(true);\n        t1 += ctx.leaderHeartbeatIntervalNs();\n        election.doWork(++t1);\n\n        verify(consensusPublisher).appendPosition(\n            liveLeader.publication(),\n            leadershipTermId,\n            termBaseLogPosition,\n            thisMember.id(),\n            APPEND_POSITION_FLAG_NONE);\n\n        election.onCommitPosition(leadershipTermId, leaderLogPosition, leaderId);\n        election.doWork(++t1);\n        verify(electionStateCounter, times(2)).setRelease(ElectionState.CANVASS.code());\n    }\n\n    @Test\n    void followerShouldUseInitialLeadershipTermIdAndInitialTermBaseLogPositionWhenRecordingLogIsEmpty()\n    {\n        final long initialLeadershipTermId = 2;\n        final long initialTermBaseLogPosition = 120;\n        final long snapshotLogPosition = 150;\n        final long leaderLeadershipTermId = 3;\n        final long leaderTermBaseLogPosition = 500;\n        final long leaderLogPosition = 600;\n        final long localRecordingId = 2390485;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[1];\n        final ClusterMember liveLeader = clusterMembers[0];\n        final int leaderId = liveLeader.id();\n        final RecordingReplication logReplication = mock(RecordingReplication.class);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.FOLLOWER);\n\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        final Election election = new Election(\n            false,\n            NULL_VALUE,\n            initialLeadershipTermId,\n            initialTermBaseLogPosition,\n            snapshotLogPosition,\n            snapshotLogPosition,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n\n        long t1 = 0;\n        election.doWork(++t1);\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        when(logReplication.hasReplicationEnded()).thenReturn(true);\n        when(logReplication.hasStopped()).thenReturn(true);\n        when(logReplication.recordingId()).thenReturn(localRecordingId);\n        when(consensusPublisher.appendPosition(any(), anyLong(), anyLong(), anyInt(), anyShort())).thenReturn(true);\n\n        election.onNewLeadershipTerm(\n            initialLeadershipTermId,\n            leaderLeadershipTermId,\n            leaderTermBaseLogPosition,\n            leaderLogPosition,\n            leaderLeadershipTermId,\n            leaderTermBaseLogPosition,\n            leaderLogPosition,\n            leaderLogPosition,\n            RECORDING_ID,\n            t1,\n            leaderId,\n            0,\n            false);\n\n        doReturn(logReplication).when(consensusModuleAgent)\n            .newLogReplication(any(), any(), anyLong(), anyLong(), anyLong());\n        doReturn(leaderTermBaseLogPosition).when(logReplication).position();\n\n        election.doWork(++t1);\n        election.onCommitPosition(leaderLeadershipTermId, leaderTermBaseLogPosition, leaderId);\n        election.doWork(++t1);\n\n        verify(recordingLog).ensureCoherent(\n            RECORDING_ID,\n            initialLeadershipTermId,\n            initialTermBaseLogPosition,\n            initialLeadershipTermId,\n            initialTermBaseLogPosition,\n            leaderTermBaseLogPosition,\n            t1,\n            t1,\n            ctx.fileSyncLevel());\n    }\n\n    @Test\n    void followerShouldReplicateAndSendAppendPositionWhenLogReplicationDone()\n    {\n        final long leadershipTermId = 1;\n        final long leaderLogPosition = 120;\n        final long termBaseLogPosition = 60;\n        final long nextTermBaseLogPosition = 60;\n        final long localRecordingId = 2390485;\n        final MutableLong replicationPosition = new MutableLong(0);\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[1];\n        final ClusterMember liveLeader = clusterMembers[0];\n        final int leaderId = liveLeader.id();\n        final RecordingReplication logReplication = mock(RecordingReplication.class);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.FOLLOWER);\n\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        final Election election = new Election(\n            true,\n            NULL_VALUE,\n            0,\n            0,\n            0,\n            0,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n\n        long t1 = 0;\n        election.doWork(++t1);\n        verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onNewLeadershipTerm(\n            0,\n            1,\n            nextTermBaseLogPosition,\n            NULL_POSITION,\n            leadershipTermId,\n            termBaseLogPosition,\n            leaderLogPosition,\n            leaderLogPosition,\n            RECORDING_ID,\n            t1,\n            leaderId,\n            0,\n            false);\n        verify(electionStateCounter).setRelease(ElectionState.FOLLOWER_LOG_REPLICATION.code());\n\n        when(consensusModuleAgent.newLogReplication(any(), any(), anyLong(), anyLong(), anyLong()))\n            .thenReturn(logReplication);\n        election.doWork(++t1);\n\n        verify(consensusModuleAgent, times(1)).newLogReplication(\n            liveLeader.archiveEndpoint(), liveLeader.archiveResponseEndpoint(), RECORDING_ID, termBaseLogPosition, t1);\n\n        when(logReplication.recordingId()).thenReturn(localRecordingId);\n\n        when(logReplication.position()).then((invocation) -> replicationPosition.get());\n        // When replication is done we move the replication position.\n        when(logReplication.poll(anyLong())).thenAnswer(\n            (invocation) ->\n            {\n                replicationPosition.set(nextTermBaseLogPosition);\n                return 1;\n            });\n        when(logReplication.hasReplicationEnded()).thenReturn(true);\n        when(logReplication.hasStopped()).thenReturn(true);\n\n        when(consensusPublisher.appendPosition(any(), anyLong(), anyLong(), anyInt(), anyShort()))\n            .thenReturn(true);\n\n        t1 += ctx.leaderHeartbeatIntervalNs();\n        election.doWork(++t1);\n\n        verify(consensusPublisher).appendPosition(\n            liveLeader.publication(),\n            leadershipTermId,\n            nextTermBaseLogPosition, // Expect to be the position after the replication has completed.\n            thisMember.id(),\n            APPEND_POSITION_FLAG_NONE);\n\n        election.onCommitPosition(leadershipTermId, leaderLogPosition, leaderId);\n        election.doWork(++t1);\n        verify(electionStateCounter, times(2)).setRelease(ElectionState.CANVASS.code());\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    @Test\n    void leaderShouldMoveToLogReplicationThenWaitForCommitPosition()\n    {\n        final long leadershipTermId = 1;\n        final long candidateTermId = leadershipTermId + 1;\n        final long leaderLogPosition = 120;\n        final long followerLogPosition = 60;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember thisMember = clusterMembers[0];\n        final ClusterMember liveFollower = clusterMembers[1];\n        final int leaderId = thisMember.id();\n        final int followerId = liveFollower.id();\n        final int appVersion = 888;\n        ctx.appVersion(appVersion);\n\n        when(consensusModuleAgent.role()).thenReturn(Cluster.Role.LEADER);\n        when(consensusModuleAgent.logRecordingId()).thenReturn(RECORDING_ID);\n\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        final Election election = new Election(\n            true,\n            NULL_VALUE,\n            leadershipTermId,\n            leaderLogPosition,\n            leaderLogPosition,\n            leaderLogPosition,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n\n        election.doWork(0);\n\n        final InOrder inOrder = inOrder(electionStateCounter);\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.INIT.code());\n        inOrder.verify(electionStateCounter).setRelease(ElectionState.CANVASS.code());\n\n        election.onCanvassPosition(leadershipTermId, leaderLogPosition, leadershipTermId, leaderId, VERSION);\n        election.onCanvassPosition(leadershipTermId, followerLogPosition, leadershipTermId, followerId, VERSION);\n\n        clock.increment(2 * ctx.startupCanvassTimeoutNs());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.NOMINATE.code());\n\n        clock.increment(2 * ctx.electionTimeoutNs());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.CANDIDATE_BALLOT.code());\n\n        election.onVote(leadershipTermId, 120, candidateTermId, leaderId, leaderId, true);\n        election.onVote(leadershipTermId, 120, candidateTermId, leaderId, followerId, true);\n\n        clock.increment(2 * ctx.electionTimeoutNs());\n        election.doWork(clock.nanoTime());\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_LOG_REPLICATION.code());\n\n        // Until the quorum position moves to the leader's append position\n        // we stay in the same state and emit new leadership terms.\n        when(consensusModuleAgent.quorumPositionBoundedByLeaderLog(anyLong(), anyLong()))\n            .thenReturn(followerLogPosition);\n        election.doWork(clock.increment(1));\n        verifyNoMoreInteractions(electionStateCounter);\n        verify(consensusPublisher).newLeadershipTerm(\n            clusterMembers[1].publication(),\n            leadershipTermId,\n            candidateTermId,\n            leaderLogPosition,\n            NULL_POSITION,\n            candidateTermId,\n            leaderLogPosition,\n            leaderLogPosition,\n            followerLogPosition, RECORDING_ID,\n            clock.nanoTime(),\n            leaderId,\n            LOG_SESSION_ID,\n            appVersion,\n            election.isLeaderStartup()\n        );\n\n        verify(consensusPublisher).newLeadershipTerm(\n            clusterMembers[2].publication(),\n            leadershipTermId,\n            candidateTermId,\n            leaderLogPosition,\n            NULL_POSITION,\n            candidateTermId,\n            leaderLogPosition,\n            leaderLogPosition,\n            followerLogPosition, RECORDING_ID,\n            clock.nanoTime(),\n            leaderId,\n            LOG_SESSION_ID,\n            appVersion,\n            election.isLeaderStartup()\n        );\n\n        // Begin replay once a quorum of followers has caught up.\n        when(consensusModuleAgent.quorumPositionBoundedByLeaderLog(anyLong(), anyLong())).thenReturn(leaderLogPosition);\n        election.doWork(clock.increment(1));\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_REPLAY.code());\n    }\n\n    @Test\n    void shouldThrowNonZeroLogPositionAndNullRecordingIdSpecified()\n    {\n        Election.ensureRecordingLogCoherent(ctx, NULL_POSITION, 0, 0, 0, 0, 0, 1);\n        Election.ensureRecordingLogCoherent(ctx, NULL_POSITION, 0, 0, 0, 0, 1000, 1);\n\n        verifyNoInteractions(recordingLog);\n    }\n\n    @Test\n    void shouldSendCommitPositionAndNewLeadershipTermEventsWithTheSameLeadershipTerm()\n    {\n        final long logPosition = 100;\n        final long leadershipTermId = 42;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember leaderMember = clusterMembers[1];\n        leaderMember.isLeader(true);\n        final long quorumPosition1 = 1024 * 1024;\n        final long quorumPosition2 = 2 * quorumPosition1;\n        final int logSessionId = 5;\n        final long logRecordingId = 842384023;\n        when(consensusModuleAgent.quorumPositionBoundedByLeaderLog(anyLong(), anyLong()))\n            .thenReturn(quorumPosition1, quorumPosition2, Long.MIN_VALUE);\n        when(consensusModuleAgent.addLogPublication(logPosition)).thenReturn(logSessionId);\n        when(consensusModuleAgent.logRecordingId()).thenReturn(logRecordingId);\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, leaderMember);\n\n        clock.update(1, clock.timeUnit());\n        Tests.setField(election, \"state\", ElectionState.LEADER_LOG_REPLICATION);\n        Tests.setField(election, \"logSessionId\", logSessionId);\n\n        clock.increment(1);\n        final long replicationTimeNs = clock.nanoTime();\n        election.doWork(replicationTimeNs);\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_REPLAY.code());\n        verify(consensusPublisher, times(2)).newLeadershipTerm(\n            any(),\n            eq(leadershipTermId),\n            eq(leadershipTermId),\n            eq(logPosition),\n            eq(NULL_POSITION),\n            eq(leadershipTermId),\n            eq(logPosition),\n            eq(logPosition),\n            eq(quorumPosition1),\n            eq(logRecordingId),\n            eq(replicationTimeNs),\n            eq(leaderMember.id()),\n            eq(logSessionId),\n            eq(ctx.appVersion()),\n            eq(false));\n        verify(consensusModuleAgent).publishCommitPosition(quorumPosition1, leadershipTermId);\n\n        clock.increment(1);\n        final long replayTimeNs = clock.nanoTime();\n        election.doWork(replayTimeNs);\n        verify(electionStateCounter).setRelease(ElectionState.LEADER_INIT.code());\n        verify(consensusPublisher, times(2)).newLeadershipTerm(\n            any(),\n            eq(leadershipTermId),\n            eq(leadershipTermId),\n            eq(logPosition),\n            eq(NULL_POSITION),\n            eq(leadershipTermId),\n            eq(logPosition),\n            eq(logPosition),\n            eq(quorumPosition2),\n            eq(logRecordingId),\n            eq(replayTimeNs),\n            eq(leaderMember.id()),\n            eq(logSessionId),\n            eq(ctx.appVersion()),\n            eq(true));\n        verify(consensusModuleAgent).publishCommitPosition(quorumPosition2, leadershipTermId);\n    }\n\n    @Test\n    void notifiedCommitPositionCannotGoBackwardsUponReceivingCommitPosition()\n    {\n        final long logPosition = 100;\n        final long leadershipTermId = 7;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember follower = clusterMembers[0];\n        final ClusterMember leader = clusterMembers[1];\n        leader.isLeader(true);\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, follower);\n        Tests.setField(election, \"state\", ElectionState.FOLLOWER_READY);\n        Tests.setField(election, \"leaderMember\", leader);\n        assertSame(leader, Tests.getField(election, \"leaderMember\"));\n\n        assertEquals(0, election.notifiedCommitPosition());\n\n        election.onCommitPosition(leadershipTermId, 2048, leader.id());\n        assertEquals(2048, election.notifiedCommitPosition());\n\n        election.onCommitPosition(leadershipTermId, 5000, leader.id());\n        assertEquals(5000, election.notifiedCommitPosition());\n\n        election.onCommitPosition(leadershipTermId, 100, leader.id());\n        assertEquals(5000, election.notifiedCommitPosition());\n\n        election.onCommitPosition(leadershipTermId, 4999, leader.id());\n        assertEquals(5000, election.notifiedCommitPosition());\n\n        election.onCommitPosition(leadershipTermId, Long.MIN_VALUE, leader.id());\n        assertEquals(5000, election.notifiedCommitPosition());\n\n        // wrong state\n        Tests.setField(election, \"state\", ElectionState.INIT);\n        election.onCommitPosition(leadershipTermId, Long.MAX_VALUE, leader.id());\n        assertEquals(5000, election.notifiedCommitPosition());\n\n        // wrong `leadershipTermId` should be allowed due to legacy code compatibility\n        Tests.setField(election, \"state\", ElectionState.CANVASS);\n        election.onCommitPosition(leadershipTermId - 5, 10000, leader.id());\n        assertEquals(10000, election.notifiedCommitPosition());\n        election.onCommitPosition(leadershipTermId + 10, 20000, leader.id());\n        assertEquals(20000, election.notifiedCommitPosition());\n\n        // wrong leader id\n        election.onCommitPosition(leadershipTermId, 13131, leader.id() + 5);\n        assertEquals(20000, election.notifiedCommitPosition());\n\n        // Leader member not set\n        Tests.setField(election, \"leaderMember\", null);\n        assertNull(Tests.getField(election, \"leaderMember\"));\n        election.onCommitPosition(leadershipTermId, 65535, leader.id());\n        assertEquals(20000, election.notifiedCommitPosition());\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    void notifiedCommitPositionCannotGoBackwardsUponReceivingNewLeadershipTerm()\n    {\n        final long logPosition = 100;\n        final long leadershipTermId = 7;\n        final long logLeadershipTermId = leadershipTermId;\n        final ClusterMember[] clusterMembers = prepareClusterMembers();\n        final ClusterMember follower = clusterMembers[0];\n        final ClusterMember leader = clusterMembers[1];\n        leader.isLeader(true);\n\n        final Election election = newElection(leadershipTermId, logPosition, clusterMembers, follower);\n        Tests.setField(election, \"state\", ElectionState.CANVASS);\n\n        assertEquals(0, election.notifiedCommitPosition());\n\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            111,\n            logPosition,\n            1073741824,\n            333,\n            9,\n            100,\n            leader.id(),\n            42,\n            false);\n        assertEquals(333, election.notifiedCommitPosition());\n        assertEquals(111, election.leadershipTermId());\n\n        // FOLLOWER_BALLOT checks candidateTermId\n        Tests.setField(election, \"state\", ElectionState.FOLLOWER_BALLOT);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            111,\n            logPosition,\n            1073741824,\n            555,\n            9,\n            100,\n            leader.id(),\n            42,\n            false);\n        assertEquals(555, election.notifiedCommitPosition());\n        assertEquals(111, election.leadershipTermId());\n\n        // whereas CANVASS does not\n        Tests.setField(election, \"state\", ElectionState.CANVASS);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            2,\n            logPosition,\n            1073741824,\n            222,\n            9,\n            100,\n            leader.id(),\n            42,\n            false);\n        assertEquals(555, election.notifiedCommitPosition());\n        assertEquals(2, election.leadershipTermId());\n\n        // INIT state is a no op\n        Tests.setField(election, \"state\", ElectionState.INIT);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            222,\n            logPosition,\n            1073741824,\n            777,\n            9,\n            100,\n            leader.id(),\n            42,\n            false);\n        assertEquals(555, election.notifiedCommitPosition());\n        assertEquals(2, election.leadershipTermId());\n\n        // Unknown leaderMemberId\n        Tests.setField(election, \"state\", ElectionState.CANVASS);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            222,\n            logPosition,\n            1073741824,\n            777,\n            9,\n            100,\n            4343434,\n            42,\n            false);\n        assertEquals(555, election.notifiedCommitPosition());\n        assertEquals(2, election.leadershipTermId());\n\n        // Current memberId is ignored if leadershipTermId matches\n        Tests.setField(election, \"state\", ElectionState.CANVASS);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            election.leadershipTermId(),\n            logPosition,\n            1073741824,\n            777,\n            9,\n            100,\n            follower.id(),\n            42,\n            false);\n        assertEquals(555, election.notifiedCommitPosition());\n        assertEquals(2, election.leadershipTermId());\n\n        // Current memberId is accepted if leadershipTermId is new\n        Tests.setField(election, \"state\", ElectionState.CANVASS);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            5,\n            logPosition,\n            1073741824,\n            777,\n            9,\n            100,\n            follower.id(),\n            42,\n            false);\n        assertEquals(777, election.notifiedCommitPosition());\n        assertEquals(5, election.leadershipTermId());\n\n        // CANDIDATE_BALLOT also checks candidateTermId\n        Tests.setField(election, \"state\", ElectionState.CANDIDATE_BALLOT);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            111,\n            logPosition,\n            1073741824,\n            789,\n            9,\n            100,\n            leader.id(),\n            42,\n            false);\n        assertEquals(789, election.notifiedCommitPosition());\n        assertEquals(111, election.leadershipTermId());\n\n        // Value cannot go backwards\n        Tests.setField(election, \"state\", ElectionState.CANVASS);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            333,\n            logPosition,\n            1073741824,\n            500,\n            9,\n            100,\n            leader.id(),\n            42,\n            false);\n        assertEquals(789, election.notifiedCommitPosition());\n        assertEquals(333, election.leadershipTermId());\n\n        // Value cannot go backwards\n        Tests.setField(election, \"state\", ElectionState.CANVASS);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId,\n            logLeadershipTermId + 1,\n            NULL_POSITION,\n            0,\n            4,\n            logPosition,\n            1073741824,\n            NULL_POSITION,\n            9,\n            100,\n            leader.id(),\n            42,\n            false);\n        assertEquals(789, election.notifiedCommitPosition());\n        assertEquals(4, election.leadershipTermId());\n\n        // logLeadershipTermId does not match\n        Tests.setField(election, \"state\", ElectionState.CANVASS);\n        election.onNewLeadershipTerm(\n            logLeadershipTermId - 1,\n            logLeadershipTermId,\n            NULL_POSITION,\n            0,\n            444,\n            logPosition,\n            1073741824,\n            1000,\n            9,\n            100,\n            leader.id(),\n            42,\n            false);\n        assertEquals(789, election.notifiedCommitPosition());\n        assertEquals(4, election.leadershipTermId());\n\n        // unsupported states\n        for (final ElectionState state : ElectionState.STATES)\n        {\n            if (ElectionState.INIT == state ||\n                ElectionState.CANVASS == state ||\n                ElectionState.CANDIDATE_BALLOT == state ||\n                ElectionState.FOLLOWER_BALLOT == state)\n            {\n                continue;\n            }\n\n            Tests.setField(election, \"state\", state);\n            election.onNewLeadershipTerm(\n                logLeadershipTermId - 1,\n                logLeadershipTermId,\n                NULL_POSITION,\n                0,\n                555,\n                logPosition,\n                1073741824,\n                2000,\n                9,\n                100,\n                leader.id(),\n                42,\n                false);\n            assertEquals(789, election.notifiedCommitPosition());\n            assertEquals(4, election.leadershipTermId());\n        }\n    }\n\n    private Election newElection(\n        final boolean isStartup,\n        final long logLeadershipTermId,\n        final long logPosition,\n        final ClusterMember[] clusterMembers,\n        final ClusterMember thisMember)\n    {\n        final Int2ObjectHashMap<ClusterMember> idToClusterMemberMap = new Int2ObjectHashMap<>();\n\n        ClusterMember.addClusterMemberIds(clusterMembers, idToClusterMemberMap);\n\n        return new Election(\n            isStartup,\n            NULL_VALUE,\n            logLeadershipTermId,\n            logPosition,\n            logPosition,\n            logPosition,\n            clusterMembers,\n            idToClusterMemberMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n    }\n\n    private Election newElection(\n        final long logLeadershipTermId,\n        final long logPosition,\n        final ClusterMember[] clusterMembers,\n        final ClusterMember thisMember)\n    {\n        final Int2ObjectHashMap<ClusterMember> clusterMemberByIdMap = new Int2ObjectHashMap<>();\n\n        ClusterMember.addClusterMemberIds(clusterMembers, clusterMemberByIdMap);\n\n        return new Election(\n            true,\n            NULL_VALUE,\n            logLeadershipTermId,\n            logPosition,\n            logPosition,\n            logPosition,\n            clusterMembers,\n            clusterMemberByIdMap,\n            thisMember,\n            consensusPublisher,\n            ctx,\n            consensusModuleAgent);\n    }\n\n    private static ClusterMember[] prepareClusterMembers()\n    {\n        final ClusterMember[] clusterMembers = ClusterMember.parse(\n            \"0,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint|\" +\n            \"1,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint|\" +\n            \"2,ingressEndpoint,consensusEndpoint,logEndpoint,catchupEndpoint,archiveEndpoint|\");\n\n        clusterMembers[0].publication(mock(ExclusivePublication.class));\n        clusterMembers[1].publication(mock(ExclusivePublication.class));\n        clusterMembers[2].publication(mock(ExclusivePublication.class));\n\n        return clusterMembers;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/IngressAdapterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport static org.mockito.Mockito.*;\n\nimport java.util.function.Consumer;\nimport java.util.stream.Stream;\n\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport io.aeron.cluster.codecs.AdminRequestDecoder;\nimport io.aeron.cluster.codecs.AdminRequestEncoder;\nimport io.aeron.cluster.codecs.AdminRequestType;\nimport io.aeron.cluster.codecs.ChallengeResponseDecoder;\nimport io.aeron.cluster.codecs.ChallengeResponseEncoder;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.SessionCloseRequestDecoder;\nimport io.aeron.cluster.codecs.SessionCloseRequestEncoder;\nimport io.aeron.cluster.codecs.SessionConnectRequestDecoder;\nimport io.aeron.cluster.codecs.SessionConnectRequestEncoder;\nimport io.aeron.cluster.codecs.SessionKeepAliveDecoder;\nimport io.aeron.cluster.codecs.SessionKeepAliveEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderDecoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderEncoder;\n\nclass IngressAdapterTest\n{\n    private final ConsensusModuleAgent consensusModuleAgent = mock(ConsensusModuleAgent.class);\n    private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n    private final Header header = new Header(0, 0);\n    private final IngressAdapter adapter = new IngressAdapter(0, consensusModuleAgent);\n\n    @SuppressWarnings(\"unused\") // name used for test display name\n    @ParameterizedTest(name = \"{index} {0}\")\n    @MethodSource\n    void shouldDelegateToConsensusModuleAgent(\n        final String name,\n        final Consumer<MutableDirectBuffer> encoder,\n        final int blockLength,\n        final ConsensusModuleAgentExpectation expectation)\n    {\n        encoder.accept(buffer);\n\n        final int length = MessageHeaderDecoder.ENCODED_LENGTH + blockLength;\n\n        adapter.onMessage(buffer, 0, length, header);\n\n        expectation.expect(verify(consensusModuleAgent), buffer, header);\n    }\n\n    static Stream<Arguments> shouldDelegateToConsensusModuleAgent()\n    {\n        return Stream.of(\n        Arguments.of(\n            \"UnknownSchema\",\n            (Consumer<MutableDirectBuffer>)(buffer) ->\n            {\n                new MessageHeaderEncoder().wrap(buffer, 0)\n                    .blockLength(0)\n                    .schemaId(17)\n                    .templateId(19)\n                    .version(0);\n            }, 0,  // no wrapping\n            (ConsensusModuleAgentExpectation)(a, buffer, header) ->\n                a.onExtensionMessage(\n                    0, 19, 17, 0, buffer, 0,\n                    MessageHeaderDecoder.ENCODED_LENGTH, header)),\n        Arguments.of(\n            \"SessionMessageHeaderDecoder\",\n            (Consumer<MutableDirectBuffer>)(buffer) ->\n            {\n                new SessionMessageHeaderEncoder().wrapAndApplyHeader(buffer, 0, new MessageHeaderEncoder())\n                    .leadershipTermId(17)\n                    .clusterSessionId(19)\n                    .timestamp(23);\n            }, SessionMessageHeaderDecoder.BLOCK_LENGTH,\n            (ConsensusModuleAgentExpectation)(a, buffer, header) ->\n                a.onIngressMessage(17, 19,\n                    buffer, MessageHeaderDecoder.ENCODED_LENGTH + SessionMessageHeaderDecoder.BLOCK_LENGTH, 0)),\n        Arguments.of(\n            \"SessionConnectRequestDecoder\",\n            (Consumer<MutableDirectBuffer>)(buffer) ->\n            {\n                new SessionConnectRequestEncoder().wrapAndApplyHeader(buffer, 0, new MessageHeaderEncoder())\n                    .correlationId(17)\n                    .responseStreamId(19)\n                    .version(23)\n                    .responseChannel(\"x\")\n                    .putEncodedCredentials(new byte[10], 5, 0)\n                    .clientInfo(\"test\");\n            }, SessionConnectRequestDecoder.BLOCK_LENGTH,\n            (ConsensusModuleAgentExpectation)(a, buffer, header) ->\n                a.onSessionConnect(17, 19, 23, \"x\", new byte[0], \"test\", header)),\n        Arguments.of(\n            \"SessionCloseRequestDecoder\",\n            (Consumer<MutableDirectBuffer>)(buffer) ->\n            {\n                new SessionCloseRequestEncoder().wrapAndApplyHeader(buffer, 0, new MessageHeaderEncoder())\n                    .leadershipTermId(17)\n                    .clusterSessionId(19);\n            }, SessionCloseRequestDecoder.BLOCK_LENGTH,\n            (ConsensusModuleAgentExpectation)(a, buffer, header) -> a.onSessionClose(17, 19)),\n        Arguments.of(\n            \"SessionKeepAliveDecoder\",\n            (Consumer<MutableDirectBuffer>)(buffer) ->\n            {\n                new SessionKeepAliveEncoder().wrapAndApplyHeader(buffer, 0, new MessageHeaderEncoder())\n                    .leadershipTermId(17)\n                    .clusterSessionId(19);\n            }, SessionKeepAliveDecoder.BLOCK_LENGTH,\n            (ConsensusModuleAgentExpectation)(a, buffer, header) -> a.onSessionKeepAlive(17, 19, header)),\n        Arguments.of(\n            \"ChallengeResponseDecoder\",\n            (Consumer<MutableDirectBuffer>)(buffer) ->\n            {\n                final byte[] challenge = \"foo\".getBytes();\n                new ChallengeResponseEncoder().wrapAndApplyHeader(buffer, 0, new MessageHeaderEncoder())\n                    .correlationId(17)\n                    .clusterSessionId(19)\n                    .putEncodedCredentials(challenge, 0, challenge.length);\n            }, ChallengeResponseDecoder.BLOCK_LENGTH,\n            (ConsensusModuleAgentExpectation)(a, buffer, header) ->\n                a.onIngressChallengeResponse(17, 19, \"foo\".getBytes())),\n        Arguments.of(\n            \"AdminRequestDecoder\",\n            (Consumer<MutableDirectBuffer>)(buffer) ->\n            {\n                final byte[] payload = \"foo\".getBytes();\n                new AdminRequestEncoder().wrapAndApplyHeader(buffer, 0, new MessageHeaderEncoder())\n                    .leadershipTermId(17)\n                    .clusterSessionId(19)\n                    .correlationId(23)\n                    .putPayload(payload, 0, payload.length);\n            }, AdminRequestDecoder.BLOCK_LENGTH,\n            (ConsensusModuleAgentExpectation)(a, buffer, header) ->\n                a.onAdminRequest(17, 19, 23,\n                    AdminRequestType.SNAPSHOT, buffer,\n                    AdminRequestDecoder.BLOCK_LENGTH +\n                            MessageHeaderDecoder.ENCODED_LENGTH +\n                            AdminRequestDecoder.payloadHeaderLength(),\n                    \"foo\".getBytes().length)));\n    }\n\n    private interface ConsensusModuleAgentExpectation\n    {\n        void expect(ConsensusModuleAgent agent, DirectBuffer buffer, Header header);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/LogSourceValidatorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport org.junit.jupiter.api.Test;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass LogSourceValidatorTest\n{\n    @Test\n    void leaderLogSourceTypeShouldOnlyAcceptLeader()\n    {\n        final LogSourceValidator logSourceValidator = new LogSourceValidator(ClusterBackup.SourceType.LEADER);\n\n        final long leaderMemberId = 123;\n        final long followerMemberId = 456;\n\n        assertTrue(logSourceValidator.isAcceptable(leaderMemberId, leaderMemberId));\n        assertFalse(logSourceValidator.isAcceptable(leaderMemberId, followerMemberId));\n        assertFalse(logSourceValidator.isAcceptable(NULL_VALUE, NULL_VALUE));\n        assertFalse(logSourceValidator.isAcceptable(leaderMemberId, NULL_VALUE));\n        assertFalse(logSourceValidator.isAcceptable(NULL_VALUE, followerMemberId));\n    }\n\n    @Test\n    void followerLogSourceTypeShouldOnlyAcceptFollowerAndUnknown()\n    {\n        final LogSourceValidator logSourceValidator = new LogSourceValidator(ClusterBackup.SourceType.FOLLOWER);\n\n        final long leaderMemberId = 123;\n        final long followerMemberId = 456;\n\n        assertFalse(logSourceValidator.isAcceptable(leaderMemberId, leaderMemberId));\n        assertTrue(logSourceValidator.isAcceptable(leaderMemberId, followerMemberId));\n        assertTrue(logSourceValidator.isAcceptable(NULL_VALUE, NULL_VALUE));\n        assertTrue(logSourceValidator.isAcceptable(leaderMemberId, NULL_VALUE));\n        assertTrue(logSourceValidator.isAcceptable(NULL_VALUE, followerMemberId));\n    }\n\n\n    @Test\n    void anyLogSourceTypeShouldAny()\n    {\n        final LogSourceValidator logSourceValidator = new LogSourceValidator(ClusterBackup.SourceType.ANY);\n\n        final long leaderMemberId = 123;\n        final long followerMemberId = 456;\n\n        assertTrue(logSourceValidator.isAcceptable(leaderMemberId, leaderMemberId));\n        assertTrue(logSourceValidator.isAcceptable(leaderMemberId, followerMemberId));\n        assertTrue(logSourceValidator.isAcceptable(NULL_VALUE, NULL_VALUE));\n        assertTrue(logSourceValidator.isAcceptable(leaderMemberId, NULL_VALUE));\n        assertTrue(logSourceValidator.isAcceptable(NULL_VALUE, followerMemberId));\n    }\n}"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/NameResolutionClusterNodeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.CommonContext;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.exceptions.InvalidChannelException;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.ClusterTests;\nimport io.aeron.test.cluster.StubClusteredService;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport static io.aeron.cluster.ClusterTestConstants.CLUSTER_MEMBERS;\nimport static io.aeron.cluster.ClusterTestConstants.INGRESS_ENDPOINTS;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass NameResolutionClusterNodeTest\n{\n    private MediaDriver mediaDriver;\n    private Archive archive;\n    private ConsensusModule consensusModule;\n    private ClusteredServiceContainer container;\n    private AeronCluster aeronCluster;\n\n    private final CopyOnWriteArrayList<Throwable> errors = new CopyOnWriteArrayList<>();\n\n    @BeforeEach\n    void before()\n    {\n        mediaDriver = MediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .threadingMode(ThreadingMode.SHARED)\n            .termBufferSparseFile(true)\n            .errorHandler(errors::add)\n            .dirDeleteOnStart(true));\n\n        archive = Archive.launch(TestContexts.localhostArchive()\n            .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n            .catalogCapacity(ClusterTestConstants.CATALOG_CAPACITY)\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .recordingEventsEnabled(false)\n            .deleteArchiveOnStart(true));\n\n        consensusModule = ConsensusModule.launch(new ConsensusModule.Context()\n            .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n            .errorHandler(ClusterTests.errorHandler(0))\n            .terminationHook(ClusterTests.NOOP_TERMINATION_HOOK)\n            .logChannel(\"aeron:ipc\")\n            .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n            .ingressChannel(\"aeron:udp\")\n            .clusterMembers(CLUSTER_MEMBERS)\n            .deleteDirOnStart(true));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeronCluster, consensusModule, container, archive, mediaDriver);\n\n        if (null != container)\n        {\n            container.context().deleteDirectory();\n        }\n\n        if (null != consensusModule)\n        {\n            consensusModule.context().deleteDirectory();\n        }\n\n        if (null != archive)\n        {\n            archive.context().deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldConnectAndSendKeepAliveWithBadName()\n    {\n        container = launchEchoService();\n        aeronCluster = connectToCluster();\n\n        assertTrue(aeronCluster.sendKeepAlive());\n\n        assertEquals(1, errors.size(), errors::toString);\n        final Throwable exception = errors.get(0);\n\n        assertInstanceOf(InvalidChannelException.class, exception);\n        assertThat(exception.getMessage(), containsString(\"badname\"));\n\n        ClusterTests.failOnClusterError();\n    }\n\n    private ClusteredServiceContainer launchEchoService()\n    {\n        final ClusteredService clusteredService = new StubClusteredService()\n        {\n            public void onSessionMessage(\n                final ClientSession session,\n                final long timestamp,\n                final DirectBuffer buffer,\n                final int offset,\n                final int length,\n                final Header header)\n            {\n                echoMessage(session, buffer, offset, length);\n            }\n        };\n\n        return ClusteredServiceContainer.launch(\n            new ClusteredServiceContainer.Context()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .clusteredService(clusteredService)\n                .errorHandler(Tests::onError));\n    }\n\n    private AeronCluster connectToCluster()\n    {\n        final ErrorHandler errorHandler =\n            (t) ->\n            {\n                System.err.println(\"** MY HANDLER **\");\n                t.printStackTrace();\n            };\n\n        return AeronCluster.connect(\n            new AeronCluster.Context()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .errorHandler(errorHandler)\n                .ingressChannel(\"aeron:udp\")\n                .ingressEndpoints(INGRESS_ENDPOINTS + \",1=badname:9011\")\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\"));\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/NodeStateFileTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.node.NodeStateHeaderEncoder;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport org.agrona.IoUtil;\nimport org.agrona.SemanticVersion;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.nio.MappedByteBuffer;\nimport java.nio.file.Files;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nclass NodeStateFileTest\n{\n    private static final long V_1_42_X_CANDIDATE_TERM_ID = 982374;\n    private static final long V_1_42_X_LOG_POSITION = 89723648762342L;\n    private static final long V_1_42_X_TIMESTAMP_MS = 9878967687234L;\n\n    private final int syncLevel = 1;\n\n    @Test\n    void shouldFailIfCreateNewFalseAndFileDoesNotExist(@TempDir final File clusterDir)\n    {\n        assertThrows(IOException.class, () -> new NodeStateFile(clusterDir, false, syncLevel));\n    }\n\n    @Test\n    void shouldCreateIfCreateNewTrueAndFileDoesNotExist(@TempDir final File clusterDir) throws IOException\n    {\n        assertEquals(0, requireNonNull(clusterDir.list()).length);\n        try (NodeStateFile ignore = new NodeStateFile(clusterDir, true, syncLevel))\n        {\n            requireNonNull(ignore);\n            assertTrue(new File(clusterDir, NodeStateFile.FILENAME).exists());\n        }\n    }\n\n    @Test\n    void shouldHaveNullCandidateTermIdOnInitialCreation(@TempDir final File clusterDir) throws IOException\n    {\n        try (NodeStateFile nodeStateFile = new NodeStateFile(clusterDir, true, syncLevel))\n        {\n            assertEquals(Aeron.NULL_VALUE, nodeStateFile.candidateTerm().candidateTermId());\n        }\n    }\n\n    @Test\n    void shouldPersistCandidateTermId(@TempDir final File clusterDir) throws Exception\n    {\n        final long candidateTermId = 832234;\n        final long timestampMs = 324234;\n        final long logPosition = 8923423;\n        try (NodeStateFile nodeStateFile = new NodeStateFile(clusterDir, true, syncLevel))\n        {\n            nodeStateFile.updateCandidateTermId(candidateTermId, logPosition, timestampMs);\n        }\n\n        try (NodeStateFile nodeStateFile = new NodeStateFile(clusterDir, false, syncLevel))\n        {\n            assertEquals(candidateTermId, nodeStateFile.candidateTerm().candidateTermId());\n            assertEquals(timestampMs, nodeStateFile.candidateTerm().timestamp());\n            assertEquals(logPosition, nodeStateFile.candidateTerm().logPosition());\n        }\n    }\n\n    @Test\n    void shouldThrowIfVersionMismatch(@TempDir final File clusterDir) throws IOException\n    {\n        try (NodeStateFile ignore = new NodeStateFile(clusterDir, true, syncLevel))\n        {\n            requireNonNull(ignore);\n        }\n\n        final int invalidVersion = SemanticVersion.compose(\n            ClusterMarkFile.MAJOR_VERSION + 1, ClusterMarkFile.MINOR_VERSION, ClusterMarkFile.PATCH_VERSION);\n        forceVersion(clusterDir, invalidVersion);\n\n        try (NodeStateFile ignore = new NodeStateFile(clusterDir, false, syncLevel))\n        {\n            requireNonNull(ignore);\n            fail(\"ClusterException should have been thrown\");\n        }\n        catch (final ClusterException ignore)\n        {\n        }\n    }\n\n    @Test\n    void shouldProposeNewMaxTermId(@TempDir final File clusterDir) throws IOException\n    {\n        final long nextCandidateTermId;\n        try (NodeStateFile nodeStateFile = new NodeStateFile(clusterDir, true, syncLevel))\n        {\n            nodeStateFile.updateCandidateTermId(5, 10, 10);\n\n            nextCandidateTermId = nodeStateFile.candidateTerm().candidateTermId() + 1;\n            assertEquals(nextCandidateTermId, nodeStateFile.proposeMaxCandidateTermId(nextCandidateTermId, 20, 20));\n            final long tooLowCandidateTermId = nodeStateFile.candidateTerm().candidateTermId() - 1;\n            assertEquals(nextCandidateTermId, nodeStateFile.proposeMaxCandidateTermId(tooLowCandidateTermId, 30, 30));\n        }\n\n        try (NodeStateFile nodeStateFile = new NodeStateFile(clusterDir, false, syncLevel))\n        {\n            assertEquals(nextCandidateTermId, nodeStateFile.candidateTerm().candidateTermId());\n        }\n    }\n\n    /**\n     * Old node state file created using 1.42.x format with the following code.\n     * <code>\n     * try (NodeStateFile nodeStateFile = new NodeStateFile(clusterDir, true, syncLevel))\n     * {\n     *     nodeStateFile.updateCandidateTermId(\n     *         V_1_42_X_CANDIDATE_TERM_ID, V_1_42_X_LOG_POSITION, V_1_42_X_TIMESTAMP_MS);\n     *     nodeStateFile.updateClusterMembers(\n     *         10001, 10002, 10003,\n     *         \"10001,localhost:20110,localhost:20220,localhost:20330,localhost:20440,localhost:8010\" +\n     *         \"10002,localhost:20111,localhost:20221,localhost:20331,localhost:20441,localhost:8011\" +\n     *         \"10003,localhost:20112,localhost:20222,localhost:20332,localhost:20442,localhost:8012\");\n     * }\n     * </code>\n     *\n     * @param clusterDir temporary cluster directory\n     * @throws IOException if an I/O exception occurs\n     */\n    @Test\n    void shouldHandleNodeStateFileCreatedWithEarlierVersion(@TempDir final File clusterDir) throws IOException\n    {\n        final URL resource = this.getClass().getResource(\"/v1_42_x/node-state.dat\");\n        Files.copy(requireNonNull(resource).openStream(), new File(clusterDir, NodeStateFile.FILENAME).toPath());\n        final long candidateTermId = 10L;\n        final long logPosition = 20L;\n        final long timestampMs = 30L;\n        final long newCandidateTermId = candidateTermId + 100;\n\n        try (NodeStateFile nodeStateFile = new NodeStateFile(clusterDir, false, syncLevel))\n        {\n            final NodeStateFile.CandidateTerm candidateTerm = nodeStateFile.candidateTerm();\n            assertEquals(V_1_42_X_CANDIDATE_TERM_ID, candidateTerm.candidateTermId());\n            assertEquals(V_1_42_X_LOG_POSITION, candidateTerm.logPosition());\n            assertEquals(V_1_42_X_TIMESTAMP_MS, candidateTerm.timestamp());\n\n            nodeStateFile.updateCandidateTermId(candidateTermId, logPosition, timestampMs);\n            assertEquals(candidateTermId, candidateTerm.candidateTermId());\n            assertEquals(logPosition, candidateTerm.logPosition());\n            assertEquals(timestampMs, candidateTerm.timestamp());\n\n            final long candidateTermIdPostPropose = nodeStateFile.proposeMaxCandidateTermId(\n                newCandidateTermId, logPosition, timestampMs);\n            assertEquals(newCandidateTermId, candidateTermIdPostPropose);\n            assertEquals(newCandidateTermId, candidateTerm.candidateTermId());\n        }\n    }\n\n    private void forceVersion(final File clusterDir, final int semanticVersion)\n    {\n        MappedByteBuffer buffer = null;\n        try\n        {\n            buffer = IoUtil.mapExistingFile(\n                new File(clusterDir, NodeStateFile.FILENAME), \"test node state file\");\n            final UnsafeBuffer unsafeBuffer = new UnsafeBuffer(buffer);\n            unsafeBuffer.putInt(NodeStateHeaderEncoder.versionEncodingOffset(), semanticVersion);\n        }\n        finally\n        {\n            if (null != buffer)\n            {\n                IoUtil.unmap(buffer);\n            }\n        }\n    }\n}"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/PendingServiceMessageTrackerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Counter;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderDecoder;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestClusterClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass PendingServiceMessageTrackerTest\n{\n    private final CountersManager countersManager = Tests.newCountersManager(16 * 1024);\n    private final int counterId = countersManager.allocate(\"test\");\n    private final Counter counter = new Counter(countersManager, 0, counterId);\n    private final LogPublisher logPublisher = mock(LogPublisher.class);\n    private final int clusterSessionIdOffset =\n        MessageHeaderEncoder.ENCODED_LENGTH + SessionMessageHeaderDecoder.clusterSessionIdEncodingOffset();\n    private final TestClusterClock clusterClock = new TestClusterClock(TimeUnit.MILLISECONDS);\n\n    @BeforeEach\n    void setUp()\n    {\n        when(logPublisher.appendMessage(anyLong(), anyLong(), anyLong(), any(), anyInt(), anyInt()))\n            .thenReturn(64L);\n    }\n\n    @Test\n    void snapshotEmpty()\n    {\n        final PendingServiceMessageTracker tracker = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        final Snapshot snapshot = takeSnapshot(tracker);\n\n        final PendingServiceMessageTracker trackerLoaded = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        loadSnapshot(trackerLoaded, snapshot);\n        trackerLoaded.verify();\n    }\n\n    @Test\n    void snapshotAfterEnqueueBeforePollAndSweep()\n    {\n        final PendingServiceMessageTracker tracker = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        final UnsafeBuffer message = new UnsafeBuffer(new byte[64]);\n\n        tracker.enqueueMessage(\n            message,\n            AeronCluster.SESSION_HEADER_LENGTH,\n            message.capacity() - AeronCluster.SESSION_HEADER_LENGTH);\n\n        final Snapshot snapshot = takeSnapshot(tracker);\n\n        final PendingServiceMessageTracker trackerLoaded = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        loadSnapshot(trackerLoaded, snapshot);\n        trackerLoaded.verify();\n    }\n\n    @Test\n    void snapshotAfterEnqueueAndPollBeforeSweep()\n    {\n        final PendingServiceMessageTracker tracker = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        final UnsafeBuffer message = new UnsafeBuffer(new byte[64]);\n\n        tracker.enqueueMessage(\n            message,\n            AeronCluster.SESSION_HEADER_LENGTH,\n            message.capacity() - AeronCluster.SESSION_HEADER_LENGTH);\n\n        tracker.poll();\n\n        final Snapshot snapshot = takeSnapshot(tracker);\n\n        final PendingServiceMessageTracker trackerLoaded = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        loadSnapshot(trackerLoaded, snapshot);\n        trackerLoaded.verify();\n    }\n\n    @Test\n    void snapshotAfterEnqueuePollAndSweepForLeader()\n    {\n        final PendingServiceMessageTracker tracker = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        final UnsafeBuffer message = new UnsafeBuffer(new byte[64]);\n\n        tracker.enqueueMessage(\n            message,\n            AeronCluster.SESSION_HEADER_LENGTH,\n            message.capacity() - AeronCluster.SESSION_HEADER_LENGTH);\n\n        tracker.poll();\n        tracker.sweepLeaderMessages();\n\n        final Snapshot snapshot = takeSnapshot(tracker);\n\n        final PendingServiceMessageTracker trackerLoaded = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        loadSnapshot(trackerLoaded, snapshot);\n        trackerLoaded.verify();\n    }\n\n\n    @Test\n    void snapshotAfterEnqueuePollAndSweepForFollower()\n    {\n        final PendingServiceMessageTracker tracker = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        final UnsafeBuffer message = new UnsafeBuffer(new byte[64]);\n\n        tracker.enqueueMessage(\n            message,\n            AeronCluster.SESSION_HEADER_LENGTH,\n            message.capacity() - AeronCluster.SESSION_HEADER_LENGTH);\n        final long clusterSessionId = message.getLong(clusterSessionIdOffset);\n\n        tracker.poll();\n        tracker.sweepFollowerMessages(clusterSessionId);\n\n        final Snapshot snapshot = takeSnapshot(tracker);\n\n        final PendingServiceMessageTracker trackerLoaded = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        loadSnapshot(trackerLoaded, snapshot);\n        trackerLoaded.verify();\n    }\n\n    @Test\n    void loadInvalid()\n    {\n        final CountersManager countersManager = Tests.newCountersManager(16 * 1024);\n        final int counterId = countersManager.allocate(\"test\");\n        final Counter counter = new Counter(countersManager, 0, counterId);\n        final LogPublisher logPublisher = mock(LogPublisher.class);\n\n        final TestClusterClock clusterClock = new TestClusterClock(TimeUnit.MILLISECONDS);\n\n        final PendingServiceMessageTracker tracker = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        tracker.loadState(-9223372036854774166L, -9223372036854774166L, 0);\n        assertThrows(ClusterException.class, tracker::verify);\n    }\n\n    @Test\n    void loadValid()\n    {\n        final CountersManager countersManager = Tests.newCountersManager(16 * 1024);\n        final int counterId = countersManager.allocate(\"test\");\n        final Counter counter = new Counter(countersManager, 0, counterId);\n        final LogPublisher logPublisher = mock(LogPublisher.class);\n\n        final TestClusterClock clusterClock = new TestClusterClock(TimeUnit.MILLISECONDS);\n\n        final PendingServiceMessageTracker tracker = new PendingServiceMessageTracker(\n            0, counter, logPublisher, clusterClock);\n\n        tracker.loadState(-9223372036854774165L, -9223372036854774166L, 0);\n        tracker.verify();\n    }\n\n    private static Snapshot takeSnapshot(final PendingServiceMessageTracker tracker)\n    {\n        final long nextServiceSessionId = tracker.nextServiceSessionId();\n        final long logServiceSessionId = tracker.logServiceSessionId();\n        final int size = tracker.size();\n        final ArrayList<UnsafeBuffer> messages = new ArrayList<>();\n        tracker.pendingMessages().forEach(\n            (buffer, offset, length, headOffset) ->\n            {\n                final UnsafeBuffer msg = new UnsafeBuffer(new byte[length]);\n                msg.putBytes(0, buffer, offset, length);\n                messages.add(msg);\n\n                return true;\n            },\n            10);\n\n        return new Snapshot(nextServiceSessionId, logServiceSessionId, size, messages);\n    }\n\n    private static void loadSnapshot(final PendingServiceMessageTracker tracker, final Snapshot snapshot)\n    {\n        tracker.loadState(snapshot.nextServiceSessionId, snapshot.logServiceSessionId, snapshot.size);\n        snapshot.messages.forEach((msg) -> tracker.appendMessage(msg, 0, msg.capacity()));\n    }\n\n    private static final class Snapshot\n    {\n        private final long nextServiceSessionId;\n        private final long logServiceSessionId;\n        private final int size;\n        private final ArrayList<UnsafeBuffer> messages;\n\n        Snapshot(\n            final long nextServiceSessionId,\n            final long logServiceSessionId,\n            final int size,\n            final ArrayList<UnsafeBuffer> messages)\n        {\n\n            this.nextServiceSessionId = nextServiceSessionId;\n            this.logServiceSessionId = logServiceSessionId;\n            this.size = size;\n            this.messages = messages;\n        }\n    }\n}"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/PriorityHeapTimerServiceClusterTimeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nclass PriorityHeapTimerServiceClusterTimeTest extends ClusterTimerTest\n{\n    TimerServiceSupplier timerServiceSupplier()\n    {\n        return new PriorityHeapTimerServiceSupplier();\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/PriorityHeapTimerServiceTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.TimerService.TimerHandler;\nimport io.aeron.cluster.TimerService.TimerSnapshotTaker;\nimport org.agrona.collections.Long2LongHashMap;\nimport org.agrona.collections.MutableInteger;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\n\nimport java.util.*;\n\nimport static io.aeron.cluster.TimerService.POLL_LIMIT;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass PriorityHeapTimerServiceTest\n{\n    @Test\n    void throwsNullPointerExceptionIfTimerHandlerIsNull()\n    {\n        assertThrows(NullPointerException.class, () -> new PriorityHeapTimerService(null));\n    }\n\n    @Test\n    void pollIsANoOpWhenNoTimersWhereScheduled()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n\n        assertEquals(0, timerService.poll(Long.MIN_VALUE));\n\n        verifyNoInteractions(timerHandler);\n    }\n\n    @Test\n    void pollIsANoOpWhenNoScheduledTimersAreExpired()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 100);\n\n        assertEquals(0, timerService.poll(7));\n\n        verifyNoInteractions(timerHandler);\n    }\n\n    @Test\n    void pollShouldNotExpireTimerIfHandlerReturnsFalse()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 100);\n\n        assertEquals(0, timerService.poll(Long.MAX_VALUE));\n\n        verify(timerHandler).onTimerEvent(1);\n        verifyNoMoreInteractions(timerHandler);\n    }\n\n    @Test\n    void pollShouldExpireSingleTimer()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 100);\n\n        assertEquals(1, timerService.poll(200));\n        assertEquals(0, timerService.poll(200));\n\n        verify(timerHandler).onTimerEvent(1);\n        verifyNoMoreInteractions(timerHandler);\n    }\n\n    @Test\n    void pollShouldRemovedExpiredTimers()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 100);\n        timerService.scheduleTimerForCorrelationId(2, 200);\n        timerService.scheduleTimerForCorrelationId(3, 300);\n        timerService.scheduleTimerForCorrelationId(4, 400);\n        timerService.scheduleTimerForCorrelationId(5, 500);\n\n        assertEquals(2, timerService.poll(200));\n        assertEquals(3, timerService.poll(500));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(1);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(timerHandler).onTimerEvent(3);\n        inOrder.verify(timerHandler).onTimerEvent(4);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void pollShouldExpireTimersInOrderOfDeadlineButWithinTheDeadlineOrderIsUndefined()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 10);\n        timerService.scheduleTimerForCorrelationId(4, 3);\n        timerService.scheduleTimerForCorrelationId(2, 0);\n        timerService.scheduleTimerForCorrelationId(3, 3);\n        timerService.scheduleTimerForCorrelationId(5, 0);\n        timerService.scheduleTimerForCorrelationId(6, 11);\n\n        assertEquals(5, timerService.poll(10));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verify(timerHandler).onTimerEvent(3);\n        inOrder.verify(timerHandler).onTimerEvent(4);\n        inOrder.verify(timerHandler).onTimerEvent(1);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void cancelTimerByCorrelationIdIsANoOpIfNoTimersRegistered()\n    {\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(mock(TimerHandler.class));\n\n        assertFalse(timerService.cancelTimerByCorrelationId(100));\n    }\n\n    @Test\n    void cancelTimerByCorrelationIdReturnsFalseForUnknownCorrelationId()\n    {\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(mock(TimerHandler.class));\n        timerService.scheduleTimerForCorrelationId(1, 100);\n        timerService.scheduleTimerForCorrelationId(7, 50);\n\n        assertFalse(timerService.cancelTimerByCorrelationId(3));\n    }\n\n    @Test\n    void cancelTimerByCorrelationIdReturnsTrueAfterCancellingTheTimer()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 100);\n        timerService.scheduleTimerForCorrelationId(7, 50);\n        timerService.scheduleTimerForCorrelationId(2, 90);\n\n        assertTrue(timerService.cancelTimerByCorrelationId(7));\n\n        assertEquals(2, timerService.poll(111));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(timerHandler).onTimerEvent(1);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void cancelTimerByCorrelationIdReturnsTrueAfterCancellingTheLastTimer()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 100);\n        timerService.scheduleTimerForCorrelationId(7, 50);\n        timerService.scheduleTimerForCorrelationId(2, 90);\n\n        assertTrue(timerService.cancelTimerByCorrelationId(1));\n\n        assertEquals(2, timerService.poll(111));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(7);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void cancelTimerByCorrelationIdAfterPoll()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 30);\n        timerService.scheduleTimerForCorrelationId(5, 15);\n        timerService.scheduleTimerForCorrelationId(2, 20);\n        timerService.scheduleTimerForCorrelationId(4, 40);\n\n        assertEquals(1, timerService.poll(19));\n        assertTrue(timerService.cancelTimerByCorrelationId(1));\n\n        assertEquals(2, timerService.poll(40));\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(timerHandler).onTimerEvent(4);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void scheduleTimerForAnExistingCorrelationIdIsANoOpIfDeadlineDoesNotChange()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(3, 30);\n        timerService.scheduleTimerForCorrelationId(5, 50);\n        timerService.scheduleTimerForCorrelationId(7, 70);\n\n        timerService.scheduleTimerForCorrelationId(5, 50);\n\n        assertEquals(3, timerService.poll(70));\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(3);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verify(timerHandler).onTimerEvent(7);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void scheduleTimerForAnExistingCorrelationIdShouldShiftEntryUpWhenDeadlineIsDecreasing()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 10);\n        timerService.scheduleTimerForCorrelationId(2, 10);\n        timerService.scheduleTimerForCorrelationId(3, 30);\n        timerService.scheduleTimerForCorrelationId(4, 30);\n        timerService.scheduleTimerForCorrelationId(5, 50);\n\n        timerService.scheduleTimerForCorrelationId(5, 10);\n\n        assertEquals(3, timerService.poll(10));\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(1);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void scheduleTimerForAnExistingCorrelationIdShouldShiftEntryDownWhenDeadlineIsIncreasing()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 10);\n        timerService.scheduleTimerForCorrelationId(2, 10);\n        timerService.scheduleTimerForCorrelationId(3, 30);\n        timerService.scheduleTimerForCorrelationId(4, 30);\n        timerService.scheduleTimerForCorrelationId(5, 50);\n\n        timerService.scheduleTimerForCorrelationId(1, 30);\n\n        assertEquals(4, timerService.poll(30));\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(timerHandler).onTimerEvent(4);\n        inOrder.verify(timerHandler).onTimerEvent(1);\n        inOrder.verify(timerHandler).onTimerEvent(3);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void pollShouldStopAfterPollLimitIsReached()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        for (int i = 0; i < POLL_LIMIT * 2; i++)\n        {\n            timerService.scheduleTimerForCorrelationId(i, i);\n        }\n\n        assertEquals(POLL_LIMIT, timerService.poll(Long.MAX_VALUE));\n\n        verify(timerHandler, times(POLL_LIMIT)).onTimerEvent(anyLong());\n        verifyNoMoreInteractions(timerHandler);\n    }\n\n    @Test\n    void snapshotProcessesAllScheduledTimers()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final TimerSnapshotTaker snapshotTaker = mock(TimerSnapshotTaker.class);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 10);\n        timerService.scheduleTimerForCorrelationId(2, 14);\n        timerService.scheduleTimerForCorrelationId(3, 30);\n        timerService.scheduleTimerForCorrelationId(4, 29);\n        timerService.scheduleTimerForCorrelationId(5, 15);\n\n        assertEquals(2, timerService.poll(14));\n\n        timerService.snapshot(snapshotTaker);\n\n        final InOrder inOrder = inOrder(timerHandler, snapshotTaker);\n        inOrder.verify(timerHandler).onTimerEvent(1);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(snapshotTaker).snapshotTimer(5, 15);\n        inOrder.verify(snapshotTaker).snapshotTimer(4, 29);\n        inOrder.verify(snapshotTaker).snapshotTimer(3, 30);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void expireThanCancelTimer()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 100);\n        timerService.scheduleTimerForCorrelationId(2, 2);\n        timerService.scheduleTimerForCorrelationId(3, 30);\n        timerService.scheduleTimerForCorrelationId(4, 4);\n        timerService.scheduleTimerForCorrelationId(5, 50);\n\n        assertEquals(2, timerService.poll(5));\n\n        assertTrue(timerService.cancelTimerByCorrelationId(1));\n\n        assertEquals(2, timerService.poll(1000));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(timerHandler).onTimerEvent(4);\n        inOrder.verify(timerHandler).onTimerEvent(3);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void moveUpAnExistingTimerAndCancelAnotherOne()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 1);\n        timerService.scheduleTimerForCorrelationId(2, 1);\n        timerService.scheduleTimerForCorrelationId(3, 3);\n        timerService.scheduleTimerForCorrelationId(4, 4);\n        timerService.scheduleTimerForCorrelationId(5, 5);\n        timerService.scheduleTimerForCorrelationId(5, 1);\n\n        assertTrue(timerService.cancelTimerByCorrelationId(3));\n\n        assertEquals(4, timerService.poll(5));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(1);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verify(timerHandler).onTimerEvent(4);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void moveDownAnExistingTimerAndCancelAnotherOne()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 1);\n        timerService.scheduleTimerForCorrelationId(2, 1);\n        timerService.scheduleTimerForCorrelationId(3, 3);\n        timerService.scheduleTimerForCorrelationId(4, 4);\n        timerService.scheduleTimerForCorrelationId(5, 5);\n        timerService.scheduleTimerForCorrelationId(1, 5);\n\n        assertTrue(timerService.cancelTimerByCorrelationId(3));\n\n        assertEquals(4, timerService.poll(5));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(2);\n        inOrder.verify(timerHandler).onTimerEvent(4);\n        inOrder.verify(timerHandler).onTimerEvent(1);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void cancelExpiredTimerIsANoOp()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 1);\n        timerService.scheduleTimerForCorrelationId(2, 1);\n        timerService.scheduleTimerForCorrelationId(3, 3);\n\n        assertEquals(2, timerService.poll(1));\n\n        assertFalse(timerService.cancelTimerByCorrelationId(1));\n        assertFalse(timerService.cancelTimerByCorrelationId(2));\n    }\n\n    @Test\n    void scheduleMustRetainOrderBetweenDeadlines()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(3, 3);\n        timerService.scheduleTimerForCorrelationId(4, 4);\n        timerService.scheduleTimerForCorrelationId(5, 5);\n        timerService.scheduleTimerForCorrelationId(6, 0);\n\n        assertEquals(4, timerService.poll(5));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(6);\n        inOrder.verify(timerHandler).onTimerEvent(3);\n        inOrder.verify(timerHandler).onTimerEvent(4);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldReuseExpiredEntriesFromAFreeList()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 1);\n        timerService.scheduleTimerForCorrelationId(2, 2);\n        timerService.scheduleTimerForCorrelationId(3, 3);\n        final Set<PriorityHeapTimerService.Timer> entries = Collections.newSetFromMap(new IdentityHashMap<>());\n\n        timerService.forEach(entries::add);\n        assertEquals(3, entries.size());\n\n        assertEquals(2, timerService.poll(2));\n\n        timerService.scheduleTimerForCorrelationId(4, 4);\n        timerService.scheduleTimerForCorrelationId(5, 5);\n        timerService.scheduleTimerForCorrelationId(6, 0);\n\n        timerService.forEach(entries::add);\n        assertEquals(4, entries.size());\n\n        assertEquals(4, timerService.poll(5));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(6);\n        inOrder.verify(timerHandler).onTimerEvent(3);\n        inOrder.verify(timerHandler).onTimerEvent(4);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldReuseCanceledTimerEntriesFromAFreeList()\n    {\n        final TimerHandler timerHandler = mock(TimerHandler.class);\n        when(timerHandler.onTimerEvent(anyLong())).thenReturn(true);\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(timerHandler);\n        timerService.scheduleTimerForCorrelationId(1, 1);\n        timerService.scheduleTimerForCorrelationId(2, 2);\n        timerService.scheduleTimerForCorrelationId(3, 3);\n        timerService.scheduleTimerForCorrelationId(4, 4);\n        final Set<PriorityHeapTimerService.Timer> entries = Collections.newSetFromMap(new IdentityHashMap<>());\n\n        timerService.forEach(entries::add);\n        assertEquals(4, entries.size());\n\n        assertTrue(timerService.cancelTimerByCorrelationId(2));\n        assertTrue(timerService.cancelTimerByCorrelationId(4));\n\n        timerService.scheduleTimerForCorrelationId(5, 5);\n        timerService.scheduleTimerForCorrelationId(6, 0);\n\n        timerService.forEach(entries::add);\n        assertEquals(4, entries.size());\n\n        assertEquals(4, timerService.poll(5));\n\n        final InOrder inOrder = inOrder(timerHandler);\n        inOrder.verify(timerHandler).onTimerEvent(6);\n        inOrder.verify(timerHandler).onTimerEvent(1);\n        inOrder.verify(timerHandler).onTimerEvent(3);\n        inOrder.verify(timerHandler).onTimerEvent(5);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void manyRandomOperations()\n    {\n        final TestHarness testHarness = new TestHarness();\n        for (int i = 0; i < 10000; i++)\n        {\n            testHarness.doRandomOperation();\n        }\n    }\n\n    private static final class TestHarness\n    {\n        final PriorityHeapTimerService timerService = new PriorityHeapTimerService(this::onTimerEvent);\n        final Long2LongHashMap timerDeadlines = new Long2LongHashMap(-1);\n        final Random random = new Random(1);\n        long nextCorrelationId = 1;\n        long time = 1000;\n\n        void doRandomOperation()\n        {\n            if (timerDeadlines.size() < 5)\n            {\n                scheduleTimer();\n            }\n            else if (timerDeadlines.size() > 20)\n            {\n                incrementTimeAndPoll();\n            }\n            else\n            {\n                switch (random.nextInt(5))\n                {\n                    case 0:\n                        incrementTimeAndPoll();\n                        break;\n                    case 1:\n                        cancelRandomTimer();\n                        break;\n                    case 2:\n                        rescheduleRandomTimer();\n                        break;\n                    default:\n                        scheduleTimer();\n                }\n            }\n            validateOrdering();\n        }\n\n        private void scheduleTimer()\n        {\n            final long correlationId = nextCorrelationId++;\n            final long deadline = (time + random.nextInt(20));\n            timerService.scheduleTimerForCorrelationId(correlationId, deadline);\n            timerDeadlines.put(correlationId, deadline);\n        }\n\n        private void cancelRandomTimer()\n        {\n            final long correlationId = randomScheduledCorrelationId();\n            assertNotEquals(-1, timerDeadlines.remove(correlationId));\n            assertTrue(timerService.cancelTimerByCorrelationId(correlationId));\n        }\n\n        private void rescheduleRandomTimer()\n        {\n            final long correlationId = randomScheduledCorrelationId();\n            final long newDeadline = (time + random.nextInt(20));\n            timerService.scheduleTimerForCorrelationId(correlationId, newDeadline);\n            assertNotEquals(-1, timerDeadlines.put(correlationId, newDeadline));\n        }\n\n        private long randomScheduledCorrelationId()\n        {\n            final Long2LongHashMap.KeyIterator itr = timerDeadlines.keySet().iterator();\n            for (int i = random.nextInt(timerDeadlines.size()); i > 0; i--)\n            {\n                itr.nextValue();\n            }\n            return itr.nextValue();\n        }\n\n        private void incrementTimeAndPoll()\n        {\n            time += random.nextInt(2);\n            timerService.poll(time);\n        }\n\n        private boolean onTimerEvent(final long correlationId)\n        {\n            final long deadline = timerDeadlines.remove(correlationId);\n            assertTrue(deadline >= 1000 && deadline <= time,\n                () -> \"correlationId=\" + correlationId + \" deadline=\" + deadline);\n            return true;\n        }\n\n        private void validateOrdering()\n        {\n            final MutableInteger nextIndex = new MutableInteger();\n            final ArrayList<PriorityHeapTimerService.Timer> entries = new ArrayList<>(timerDeadlines.size());\n            timerService.forEach(timer ->\n            {\n                assertEquals(nextIndex.value++, timer.index);\n                entries.add(timer);\n                if (timer.index > 0)\n                {\n                    final PriorityHeapTimerService.Timer parent = entries.get((timer.index - 1) / 2);\n                    assertTrue(parent.deadline <= timer.deadline, () -> \"parent=\" + parent + \"\\n child=\" + timer);\n                }\n            });\n            assertEquals(timerDeadlines.size(), entries.size());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/PublicationGroupTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ExclusivePublication;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass PublicationGroupTest\n{\n    private final String channelTemplate = \"aeron:udp?term-length=64k\";\n    private final int streamId = 10000;\n    private final String[] endpoints = { \"localhost:1001\", \"localhost:1002\", \"localhost:1003\" };\n    private final PublicationGroup<ExclusivePublication> publicationGroup = new PublicationGroup<>(\n        endpoints, channelTemplate, streamId, Aeron::addExclusivePublication);\n    private final Aeron mockAeron = mock(Aeron.class);\n    private final List<ExclusivePublication> publications = Arrays.asList(\n        mock(ExclusivePublication.class, \"pub_localhost:1001\"),\n        mock(ExclusivePublication.class, \"pub_localhost:1002\"),\n        mock(ExclusivePublication.class, \"pub_localhost:1003\"));\n\n    @BeforeEach\n    void setUp()\n    {\n        when(mockAeron.addExclusivePublication(matches(\".*localhost:1001.*\"), anyInt()))\n            .thenReturn(publications.get(0));\n        when(mockAeron.addExclusivePublication(matches(\".*localhost:1002.*\"), anyInt()))\n            .thenReturn(publications.get(1));\n        when(mockAeron.addExclusivePublication(matches(\".*localhost:1003.*\"), anyInt()))\n            .thenReturn(publications.get(2));\n    }\n\n    @Test\n    void shouldUseAllPublicationsInListWhenGettingNextPublication()\n    {\n        publicationGroup.next(mockAeron);\n        publicationGroup.next(mockAeron);\n        publicationGroup.next(mockAeron);\n\n        verify(mockAeron).addExclusivePublication(\"aeron:udp?endpoint=localhost:1001|term-length=64k\", streamId);\n        verify(mockAeron).addExclusivePublication(\"aeron:udp?endpoint=localhost:1002|term-length=64k\", streamId);\n        verify(mockAeron).addExclusivePublication(\"aeron:udp?endpoint=localhost:1003|term-length=64k\", streamId);\n    }\n\n    @Test\n    void shouldNextPublicationAndReturnSameWithCurrent()\n    {\n        final ExclusivePublication pubNext = publicationGroup.next(mockAeron);\n        final ExclusivePublication pubCurrent1 = publicationGroup.current();\n        final ExclusivePublication pubCurrent2 = publicationGroup.current();\n\n        assertSame(pubNext, pubCurrent1);\n        assertSame(pubNext, pubCurrent2);\n\n        verify(mockAeron, times(1)).addExclusivePublication(anyString(), anyInt());\n    }\n\n    @Test\n    void shouldResultNullWhenCurrentCalledWithoutNext()\n    {\n        assertNull(publicationGroup.current());\n    }\n\n    @Test\n    void shouldCloseWhenCurrentIsNotNullWhenNextIsCalled()\n    {\n        assertNull(publicationGroup.current());\n        final ExclusivePublication current = publicationGroup.next(mockAeron);\n\n        assertNotNull(publicationGroup.next(mockAeron));\n        verify(current).close();\n    }\n\n    @Test\n    void shouldExcludeCurrentPublication()\n    {\n        final ExclusivePublication toExclude = publicationGroup.next(mockAeron);\n        publicationGroup.closeAndExcludeCurrent();\n\n        for (int i = 0; i < 100; i++)\n        {\n            assertNotSame(toExclude, publicationGroup.next(mockAeron));\n        }\n    }\n\n    @Test\n    void shouldClearExclusion()\n    {\n        final ExclusivePublication toExclude = publicationGroup.next(mockAeron);\n        publicationGroup.closeAndExcludeCurrent();\n        assertNotSame(toExclude, publicationGroup.next(mockAeron));\n\n        for (int i = 0; i < publications.size(); i++)\n        {\n            assertNotSame(toExclude, publicationGroup.next(mockAeron));\n        }\n\n        publicationGroup.clearExclusion();\n\n        boolean found = false;\n        for (int i = 0; i < publications.size(); i++)\n        {\n            if (toExclude == publicationGroup.next(mockAeron))\n            {\n                found = true;\n            }\n        }\n\n        assertTrue(found);\n    }\n\n    @Test\n    void shouldClosePublicationOnClose()\n    {\n        final ExclusivePublication pub = publicationGroup.next(mockAeron);\n        publicationGroup.close();\n        verify(pub).close();\n    }\n\n    @Test\n    void shouldEventuallyGetADifferentOrderAfterShuffle()\n    {\n        final String[] originalOrder = Arrays.copyOf(endpoints, endpoints.length);\n        int differenceCount = 0;\n        for (int i = 0; i < 100; i++)\n        {\n            publicationGroup.shuffle();\n            differenceCount += !Arrays.equals(originalOrder, endpoints) ? 1 : 0;\n        }\n\n        assertNotEquals(0, differenceCount);\n    }\n}"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/RecordingLogTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.RecordingDescriptorConsumer;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.test.Tests;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Predicate;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SERVICE_ID;\nimport static io.aeron.cluster.RecordingLog.*;\nimport static java.util.Arrays.asList;\nimport static java.util.Objects.requireNonNull;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass RecordingLogTest\n{\n    private static final long RECORDING_ID = 9234236;\n\n    @TempDir\n    private File tempDir;\n\n    @Test\n    void shouldCreateNewIndex()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(0, recordingLog.entries().size());\n        }\n    }\n\n    @Test\n    void shouldAppendAndThenReloadLatestSnapshot()\n    {\n        final RecordingLog.Entry entry = new RecordingLog.Entry(\n            1, 3, 2, 777, 4, NULL_VALUE, ENTRY_TYPE_SNAPSHOT, null, true, 0);\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendSnapshot(\n                entry.recordingId,\n                entry.leadershipTermId,\n                entry.termBaseLogPosition,\n                777,\n                entry.timestamp,\n                SERVICE_ID);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(1, recordingLog.entries().size());\n\n            final RecordingLog.Entry snapshot = recordingLog.getLatestSnapshot(SERVICE_ID);\n            assertNotNull(snapshot);\n            assertEquals(entry, snapshot);\n        }\n    }\n\n    @Test\n    void shouldIgnoreIncompleteSnapshotInRecoveryPlan()\n    {\n        final int serviceCount = 1;\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendSnapshot(1L, 1L, 0, 777L, 0, 0);\n            recordingLog.appendSnapshot(2L, 1L, 0, 777L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(3L, 1L, 0, 888L, 0, 0);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(3, recordingLog.entries().size());\n\n            final AeronArchive mockArchive = mock(AeronArchive.class);\n            final RecordingLog.RecoveryPlan recoveryPlan = recordingLog.createRecoveryPlan(\n                mockArchive, serviceCount, Aeron.NULL_VALUE);\n            assertEquals(2, recoveryPlan.snapshots().size());\n            assertEquals(SERVICE_ID, recoveryPlan.snapshots().get(0).serviceId());\n            assertEquals(2L, recoveryPlan.snapshots().get(0).recordingId());\n            assertEquals(0, recoveryPlan.snapshots().get(1).serviceId());\n            assertEquals(1L, recoveryPlan.snapshots().get(1).recordingId());\n        }\n    }\n\n    @Test\n    void shouldIgnoreInvalidMidSnapshotInRecoveryPlan()\n    {\n        final int serviceCount = 1;\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendSnapshot(1, 1L, 0, 777L, 0, 0);\n            recordingLog.appendSnapshot(2, 1L, 0, 777L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(3, 1L, 0, 888L, 0, 0);\n            recordingLog.appendSnapshot(4, 1L, 0, 888L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(5, 1L, 0, 999L, 0, 0);\n            recordingLog.appendSnapshot(6, 1L, 0, 999L, 0, SERVICE_ID);\n\n            recordingLog.invalidateEntry(2);\n            recordingLog.invalidateEntry(3);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            final AeronArchive mockArchive = mock(AeronArchive.class);\n            final RecordingLog.RecoveryPlan recoveryPlan = recordingLog.createRecoveryPlan(\n                mockArchive, serviceCount, Aeron.NULL_VALUE);\n            assertEquals(2, recoveryPlan.snapshots().size());\n            assertEquals(SERVICE_ID, recoveryPlan.snapshots().get(0).serviceId());\n            assertEquals(6L, recoveryPlan.snapshots().get(0).recordingId());\n            assertEquals(0, recoveryPlan.snapshots().get(1).serviceId());\n            assertEquals(5L, recoveryPlan.snapshots().get(1).recordingId());\n        }\n    }\n\n    @Test\n    void shouldIgnoreInvalidTermInRecoveryPlan()\n    {\n        final int serviceCount = 1;\n        final long removedLeadershipTerm = 11L;\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendTerm(0L, 9L, 444, 0);\n            recordingLog.appendTerm(0L, 10L, 666, 0);\n            recordingLog.appendSnapshot(1L, 10L, 666, 777L, 0, 0);\n            recordingLog.appendSnapshot(2L, 10L, 666, 777L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(3L, 10L, 666, 888L, 0, 0);\n            recordingLog.appendSnapshot(4L, 10L, 666, 888L, 0, SERVICE_ID);\n            recordingLog.appendTerm(0L, removedLeadershipTerm, 999, 0);\n\n            final RecordingLog.Entry lastTerm = recordingLog.findLastTerm();\n\n            assertNotNull(lastTerm);\n            assertEquals(999L, lastTerm.termBaseLogPosition);\n\n            recordingLog.invalidateEntry(6);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            final AeronArchive mockArchive = mock(AeronArchive.class);\n            when(mockArchive.listRecording(anyLong(), any())).thenReturn(1);\n\n            final RecordingLog.RecoveryPlan recoveryPlan = recordingLog.createRecoveryPlan(\n                mockArchive, serviceCount, Aeron.NULL_VALUE);\n            assertEquals(0L, recoveryPlan.log().recordingId());\n            assertEquals(10L, recoveryPlan.log().leadershipTermId());\n            assertEquals(666, recoveryPlan.log().termBaseLogPosition());\n\n            final RecordingLog.Entry lastTerm = recordingLog.findLastTerm();\n            assertNotNull(lastTerm);\n            assertEquals(0L, lastTerm.recordingId);\n            assertEquals(0L, recordingLog.findLastTermRecordingId());\n            assertTrue(recordingLog.isUnknown(removedLeadershipTerm));\n            assertEquals(NULL_VALUE, recordingLog.getTermTimestamp(removedLeadershipTerm));\n\n            assertThrows(ClusterException.class, () -> recordingLog.getTermEntry(removedLeadershipTerm));\n            assertThrows(ClusterException.class, () -> recordingLog.commitLogPosition(removedLeadershipTerm, 99L));\n        }\n    }\n\n    @Test\n    void shouldAppendAndThenCommitTermPosition()\n    {\n        final long newPosition = 9999L;\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            final long recordingId = 1L;\n            final long leadershipTermId = 1111L;\n            final long logPosition = 2222L;\n            final long timestamp = 3333L;\n\n            recordingLog.appendTerm(recordingId, leadershipTermId, logPosition, timestamp);\n            recordingLog.commitLogPosition(leadershipTermId, newPosition);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(1, recordingLog.entries().size());\n\n            final RecordingLog.Entry actualEntry = recordingLog.entries().get(0);\n            assertEquals(newPosition, actualEntry.logPosition);\n        }\n    }\n\n    @Test\n    void shouldRemoveEntry()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            final RecordingLog.Entry entryOne = new RecordingLog.Entry(\n                1L, 3, 2, NULL_POSITION, 4, 0, ENTRY_TYPE_TERM, null, true, 0);\n            recordingLog.appendTerm(\n                entryOne.recordingId, entryOne.leadershipTermId, entryOne.termBaseLogPosition, entryOne.timestamp);\n\n            final RecordingLog.Entry entryTwo = new RecordingLog.Entry(\n                1L, 4, 3, NULL_POSITION, 5, 0, ENTRY_TYPE_TERM, null, true, 0);\n            recordingLog.appendTerm(\n                entryTwo.recordingId, entryTwo.leadershipTermId, entryTwo.termBaseLogPosition, entryTwo.timestamp);\n\n            recordingLog.removeEntry(entryTwo.leadershipTermId, recordingLog.nextEntryIndex() - 1);\n            assertEquals(1, recordingLog.entries().size());\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(1, recordingLog.entries().size());\n            assertEquals(2, recordingLog.nextEntryIndex());\n        }\n    }\n\n    @Test\n    void shouldCorrectlyOrderSnapshots()\n    {\n        final ArrayList<RecordingLog.Snapshot> snapshots = new ArrayList<>();\n        final ArrayList<RecordingLog.Entry> entries = new ArrayList<>();\n\n        addRecordingLogEntry(entries, ConsensusModule.Configuration.SERVICE_ID, 0, ENTRY_TYPE_TERM);\n        addRecordingLogEntry(entries, 2, 4, ENTRY_TYPE_SNAPSHOT);\n        addRecordingLogEntry(entries, 1, 5, ENTRY_TYPE_SNAPSHOT);\n        addRecordingLogEntry(entries, 0, 6, ENTRY_TYPE_SNAPSHOT);\n        addRecordingLogEntry(entries, ConsensusModule.Configuration.SERVICE_ID, 7, ENTRY_TYPE_SNAPSHOT);\n\n        RecordingLog.addSnapshots(snapshots, entries, 3, entries.size() - 1);\n\n        assertEquals(4, snapshots.size());\n        assertEquals(ConsensusModule.Configuration.SERVICE_ID, snapshots.get(0).serviceId());\n        assertEquals(0, snapshots.get(1).serviceId());\n        assertEquals(1, snapshots.get(2).serviceId());\n        assertEquals(2, snapshots.get(3).serviceId());\n    }\n\n    @Test\n    void shouldInvalidateLatestSnapshot()\n    {\n        final long termBaseLogPosition = 0L;\n        final long logIncrement = 640L;\n        long leadershipTermId = 7L;\n        long logPosition = 0L;\n        long timestamp = 1000L;\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendTerm(1, leadershipTermId, termBaseLogPosition, timestamp);\n\n            timestamp += 1;\n            logPosition += logIncrement;\n\n            recordingLog.appendSnapshot(\n                2, leadershipTermId, termBaseLogPosition, logPosition, timestamp, 0);\n            recordingLog.appendSnapshot(\n                3, leadershipTermId, termBaseLogPosition, logPosition, timestamp, SERVICE_ID);\n\n            timestamp += 1;\n            logPosition += logIncrement;\n\n            recordingLog.appendSnapshot(\n                4, leadershipTermId, termBaseLogPosition, logPosition, timestamp, 0);\n            recordingLog.appendSnapshot(\n                5, leadershipTermId, termBaseLogPosition, logPosition, timestamp, SERVICE_ID);\n\n            leadershipTermId++;\n            recordingLog.appendTerm(1, leadershipTermId, logPosition, timestamp);\n\n            assertTrue(recordingLog.invalidateLatestSnapshot());\n            assertEquals(6, recordingLog.entries().size());\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(6, recordingLog.entries().size());\n\n            assertTrue(recordingLog.entries().get(0).isValid);\n            assertTrue(recordingLog.entries().get(1).isValid);\n            assertTrue(recordingLog.entries().get(2).isValid);\n            assertFalse(recordingLog.entries().get(3).isValid);\n            assertFalse(recordingLog.entries().get(4).isValid);\n            assertTrue(recordingLog.entries().get(5).isValid);\n\n            final RecordingLog.Entry latestServiceSnapshot = recordingLog.getLatestSnapshot(0);\n            assertSame(recordingLog.entries().get(1), latestServiceSnapshot);\n\n            final RecordingLog.Entry latestCmSnapshot = recordingLog.getLatestSnapshot(SERVICE_ID);\n            assertSame(recordingLog.entries().get(2), latestCmSnapshot);\n\n            assertTrue(recordingLog.invalidateLatestSnapshot());\n            assertEquals(6, recordingLog.entries().size());\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(6, recordingLog.entries().size());\n\n            assertTrue(recordingLog.entries().get(0).isValid);\n            assertFalse(recordingLog.entries().get(1).isValid);\n            assertFalse(recordingLog.entries().get(2).isValid);\n            assertFalse(recordingLog.entries().get(3).isValid);\n            assertFalse(recordingLog.entries().get(4).isValid);\n            assertTrue(recordingLog.entries().get(5).isValid);\n\n            assertFalse(recordingLog.invalidateLatestSnapshot());\n            assertSame(recordingLog.entries().get(5), recordingLog.getTermEntry(leadershipTermId));\n        }\n    }\n\n    @Test\n    void shouldRecoverSnapshotsMidLogMarkedInvalid()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendSnapshot(1L, 1L, 10, 555L, 0, 0);\n            recordingLog.appendSnapshot(2L, 1L, 10, 555L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(3L, 1L, 10, 777L, 0, 0);\n            recordingLog.appendSnapshot(4L, 1L, 10, 777L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(5L, 1L, 10, 888L, 0, 0);\n            recordingLog.appendSnapshot(6L, 1L, 10, 888L, 0, SERVICE_ID);\n\n            recordingLog.invalidateEntry(2);\n            recordingLog.invalidateEntry(3);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendSnapshot(7L, 1L, 10, 777L, 555, 0);\n            recordingLog.appendSnapshot(8L, 1L, 10, 777L, -999, SERVICE_ID);\n\n            assertEquals(6, recordingLog.entries().size());\n            assertTrue(recordingLog.entries().get(2).isValid);\n            assertEquals(7L, recordingLog.entries().get(2).recordingId);\n            assertTrue(recordingLog.entries().get(3).isValid);\n            assertEquals(8L, recordingLog.entries().get(3).recordingId);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(6, recordingLog.entries().size());\n            assertTrue(recordingLog.entries().get(2).isValid);\n            assertEquals(7L, recordingLog.entries().get(2).recordingId);\n            assertTrue(recordingLog.entries().get(3).isValid);\n            assertEquals(8L, recordingLog.entries().get(3).recordingId);\n        }\n    }\n\n    @Test\n    void shouldRecoverSnapshotsLastInLogMarkedWithInvalid()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendSnapshot(-10, 1L, 0, 777L, 0, 0);\n            recordingLog.appendSnapshot(-11, 1L, 0, 777L, 0, SERVICE_ID);\n\n            recordingLog.appendTerm(1, 2L, 10, 0);\n            recordingLog.appendSnapshot(-12, 2L, 10, 888L, 0, 0);\n            recordingLog.appendSnapshot(-13, 2L, 10, 888L, 0, SERVICE_ID);\n\n            recordingLog.appendTerm(1, 3L, 20, 0);\n            recordingLog.appendSnapshot(-14, 3L, 20, 999L, 0, 0);\n            recordingLog.appendSnapshot(-15, 3L, 20, 999L, 0, SERVICE_ID);\n\n            recordingLog.invalidateLatestSnapshot();\n            recordingLog.invalidateLatestSnapshot();\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendSnapshot(1, 2L, 10, 888L, 0, 0);\n            recordingLog.appendSnapshot(2, 2L, 10, 888L, 0, SERVICE_ID);\n\n            recordingLog.appendSnapshot(3, 3L, 20, 999L, 0, 0);\n            recordingLog.appendSnapshot(4, 3L, 20, 999L, 0, SERVICE_ID);\n\n            assertEquals(8, recordingLog.entries().size());\n            assertTrue(recordingLog.entries().get(2).isValid);\n            assertEquals(1L, recordingLog.entries().get(3).recordingId);\n            assertTrue(recordingLog.entries().get(3).isValid);\n            assertEquals(2L, recordingLog.entries().get(4).recordingId);\n            assertTrue(recordingLog.entries().get(4).isValid);\n            assertEquals(3L, recordingLog.entries().get(6).recordingId);\n            assertTrue(recordingLog.entries().get(5).isValid);\n            assertEquals(4L, recordingLog.entries().get(7).recordingId);\n        }\n    }\n\n    @Test\n    void shouldNotAllowInvalidateOfSnapshotWithoutParentTerm()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendSnapshot(-10, 1L, 0, 777L, 0, 0);\n            recordingLog.appendSnapshot(-11, 1L, 0, 777L, 0, SERVICE_ID);\n\n            final ClusterException ex = assertThrows(ClusterException.class, recordingLog::invalidateLatestSnapshot);\n            assertEquals(\"ERROR - no matching term for snapshot: leadershipTermId=1\", ex.getMessage());\n        }\n    }\n\n    @Test\n    void shouldFailToRecoverSnapshotsMarkedInvalidIfFieldsDoNotMatchCorrectly()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendTerm(10L, 0L, 0, 0);\n            recordingLog.appendTerm(10L, 1L, 500, 0);\n            recordingLog.appendSnapshot(0, 1L, 500, 777L, 0, 0);\n            recordingLog.appendSnapshot(1, 1L, 500, 777L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(2, 1L, 500, 888L, 0, 0);\n            recordingLog.appendSnapshot(3, 1L, 500, 888L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(4, 1L, 500, 999L, 0, 0);\n            recordingLog.appendSnapshot(5, 1L, 500, 999L, 0, SERVICE_ID);\n            recordingLog.appendTerm(10L, 2L, 1000, 5);\n\n            assertTrue(recordingLog.invalidateLatestSnapshot());\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendSnapshot(6, 2L, 500, 999L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(7, 1L, 501, 999L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(8, 1L, 500, 998L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(9, 1L, 500, 999L, 0, 42);\n\n            final List<RecordingLog.Entry> entries = recordingLog.entries();\n            assertEquals(\n                new RecordingLog.Entry(8, 1, 500, 998, 0, SERVICE_ID, ENTRY_TYPE_SNAPSHOT, null, true, 11),\n                entries.get(6));\n            assertEquals(\n                new RecordingLog.Entry(9, 1, 500, 999, 0, 42, ENTRY_TYPE_SNAPSHOT, null, true, 12),\n                entries.get(7));\n            assertEquals(\n                new RecordingLog.Entry(4, 1, 500, 999, 0, 0, ENTRY_TYPE_SNAPSHOT, null, false, 6),\n                entries.get(8));\n            assertEquals(\n                new RecordingLog.Entry(5, 1, 500, 999, 0, SERVICE_ID, ENTRY_TYPE_SNAPSHOT, null, false, 7),\n                entries.get(9));\n            assertEquals(\n                new RecordingLog.Entry(7, 1, 501, 999, 0, SERVICE_ID, ENTRY_TYPE_SNAPSHOT, null, true, 10),\n                entries.get(10));\n            assertEquals(\n                new RecordingLog.Entry(6, 2, 500, 999, 0, SERVICE_ID, ENTRY_TYPE_SNAPSHOT, null, true, 9),\n                entries.get(11));\n            assertEquals(\n                new RecordingLog.Entry(10, 2, 1000, NULL_POSITION, 5, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 8),\n                entries.get(12));\n            final RecordingLog.Entry latestSnapshot = recordingLog.getLatestSnapshot(SERVICE_ID);\n            assertNotNull(latestSnapshot);\n            assertEquals(6L, latestSnapshot.recordingId);\n        }\n    }\n\n    @Test\n    void shouldFindSnapshotAtOrBeforeOrLowest()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            long recordingId = 0;\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 777L, 0, 0);\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 777L, 0, 1);\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 777L, 0, SERVICE_ID);\n            recordingLog.appendStandbySnapshot(recordingId++, 1L, 500, 888L, 0, 0, \"localhost:8080\");\n            recordingLog.appendStandbySnapshot(recordingId++, 1L, 500, 888L, 0, 1, \"localhost:8080\");\n            recordingLog.appendStandbySnapshot(recordingId++, 1L, 500, 888L, 0, SERVICE_ID, \"localhost:8080\");\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 888L, 0, 0);\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 888L, 0, 1);\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 888L, 0, SERVICE_ID);\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 890L, 0, 0);\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 890L, 0, SERVICE_ID); // Missing one service snapshot\n            recordingLog.appendStandbySnapshot(recordingId++, 1L, 500, 990L, 0, 0, \"localhost:8080\");\n            recordingLog.appendStandbySnapshot(recordingId++, 1L, 500, 990L, 0, 1, \"localhost:8080\");\n            recordingLog.appendStandbySnapshot(recordingId++, 1L, 500, 990L, 0, SERVICE_ID, \"localhost:8080\");\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 999L, 0, 0);\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 999L, 0, 1);\n            recordingLog.appendSnapshot(recordingId++, 1L, 500, 999L, 0, SERVICE_ID);\n\n            final int serviceCount = 2;\n\n            assertSnapshot(recordingLog.findSnapshotAtOrBeforeOrLowest(777L, serviceCount), serviceCount, 777L);\n            assertSnapshot(recordingLog.findSnapshotAtOrBeforeOrLowest(888L, serviceCount), serviceCount, 888L);\n            assertSnapshot(recordingLog.findSnapshotAtOrBeforeOrLowest(999L, serviceCount), serviceCount, 999L);\n            assertSnapshot(recordingLog.findSnapshotAtOrBeforeOrLowest(890L, serviceCount), serviceCount, 888L);\n            assertSnapshot(recordingLog.findSnapshotAtOrBeforeOrLowest(990L, serviceCount), serviceCount, 888L);\n            assertSnapshot(recordingLog.findSnapshotAtOrBeforeOrLowest(750L, serviceCount), serviceCount, 777L);\n            assertSnapshot(recordingLog.findSnapshotAtOrBeforeOrLowest(1000L, serviceCount), serviceCount, 999L);\n        }\n    }\n\n    private static void assertSnapshot(\n        final List<Snapshot> snapshot,\n        final int serviceCount,\n        final long logPosition)\n    {\n        assertNotNull(snapshot);\n        assertEquals(serviceCount + 1, snapshot.size());\n        snapshot.forEach((s) -> assertEquals(logPosition, s.logPosition()));\n    }\n\n    @Test\n    void shouldAppendTermWithLeadershipTermIdOutOfOrder()\n    {\n        final List<RecordingLog.Entry> sortedEntries = asList(\n            new RecordingLog.Entry(0, 0, 0, 700, 0, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 0),\n            new RecordingLog.Entry(0, 1, 700, 2048, 0, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 3),\n            new RecordingLog.Entry(0, 2, 2048, 5000, 0, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 1),\n            new RecordingLog.Entry(0, 3, 5000, NULL_POSITION, 100, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 2));\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendTerm(0, 0, 0, 0);\n            recordingLog.appendTerm(0, 2, 2048, 0);\n            recordingLog.appendTerm(0, 3, 5000, 100);\n            recordingLog.appendTerm(0, 1, 700, 0);\n\n            assertEquals(4, recordingLog.nextEntryIndex());\n            final List<RecordingLog.Entry> entries = recordingLog.entries();\n            assertEquals(sortedEntries, entries);\n\n            assertSame(entries.get(0), recordingLog.getTermEntry(0));\n            assertSame(entries.get(1), recordingLog.getTermEntry(1));\n            assertSame(entries.get(2), recordingLog.getTermEntry(2));\n            assertSame(entries.get(3), recordingLog.getTermEntry(3));\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(sortedEntries, recordingLog.entries());\n        }\n    }\n\n    @Test\n    void shouldAppendSnapshotWithLeadershipTermIdOutOfOrder()\n    {\n        final List<RecordingLog.Entry> sortedEntries = asList(\n            new RecordingLog.Entry(3, 1, 0, 200, 0, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 0),\n            new RecordingLog.Entry(10, 1, 0, 56, 42, SERVICE_ID, ENTRY_TYPE_SNAPSHOT, null, true, 1),\n            new RecordingLog.Entry(3, 2, 200, 2048, 555, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 2),\n            new RecordingLog.Entry(11, 2, 200, 250, 100, 1, ENTRY_TYPE_SNAPSHOT, null, true, 4),\n            new RecordingLog.Entry(100, 2, 200, 250, 100, 0, ENTRY_TYPE_SNAPSHOT, null, true, 5),\n            new RecordingLog.Entry(3, 3, 2048, NULL_POSITION, 0, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 3));\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendTerm(3, 1, 0, 0);\n            recordingLog.appendSnapshot(10, 1, 0, 56, 42, SERVICE_ID);\n            recordingLog.invalidateEntry(1);\n\n            recordingLog.commitLogPosition(1, 200);\n            recordingLog.appendTerm(3, 2, 200, 555);\n\n            recordingLog.commitLogPosition(2, 2048);\n            recordingLog.appendTerm(3, 3, 2048, 0);\n            recordingLog.appendSnapshot(11, 2, 200, 250, 100, 1);\n            recordingLog.appendSnapshot(10, 1, 0, 56, 42, SERVICE_ID);\n            recordingLog.appendSnapshot(100, 2, 200, 250, 100, 0);\n\n            final List<RecordingLog.Entry> entries = recordingLog.entries();\n            assertEquals(sortedEntries, entries);\n            assertEquals(6, recordingLog.nextEntryIndex());\n\n            assertSame(entries.get(0), recordingLog.getTermEntry(1));\n            assertNull(recordingLog.findTermEntry(0));\n            assertSame(entries.get(2), recordingLog.getTermEntry(2));\n            assertSame(entries.get(5), recordingLog.getTermEntry(3));\n            final RecordingLog.Entry latestSnapshot = recordingLog.getLatestSnapshot(SERVICE_ID);\n            assertSame(entries.get(1), latestSnapshot);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            assertEquals(sortedEntries, recordingLog.entries());\n        }\n    }\n\n    @Test\n    void appendTermShouldRejectNullValueAsRecordingId()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            final ClusterException exception = assertThrows(ClusterException.class,\n                () -> recordingLog.appendTerm(NULL_VALUE, 0, 0, 0));\n            assertEquals(\"ERROR - invalid recordingId=-1\", exception.getMessage());\n            assertEquals(0, recordingLog.entries().size());\n        }\n    }\n\n    @Test\n    void appendSnapshotShouldRejectNullValueAsRecordingId()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            final ClusterException exception = assertThrows(ClusterException.class,\n                () -> recordingLog.appendSnapshot(NULL_VALUE, 0, 0, 0, 0, 0));\n            assertEquals(\"ERROR - invalid recordingId=-1\", exception.getMessage());\n            assertEquals(0, recordingLog.entries().size());\n        }\n    }\n\n    @Test\n    void appendTermShouldNotAcceptDifferentRecordingIds()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendTerm(42, 0, 0, 0);\n\n            final ClusterException exception = assertThrows(ClusterException.class,\n                () -> recordingLog.appendTerm(21, 1, 0, 0));\n            assertEquals(\"ERROR - invalid TERM recordingId=21, expected recordingId=42\", exception.getMessage());\n            assertEquals(1, recordingLog.entries().size());\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            final ClusterException exception = assertThrows(ClusterException.class,\n                () -> recordingLog.appendTerm(-5, -5, -5, -5));\n            assertEquals(\"ERROR - invalid TERM recordingId=-5, expected recordingId=42\", exception.getMessage());\n            assertEquals(1, recordingLog.entries().size());\n        }\n    }\n\n    @Test\n    void appendTermShouldOnlyAllowASingleValidTermForTheSameLeadershipTermId()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendTerm(8, 0, 0, 0);\n            recordingLog.appendTerm(8, 1, 1, 1);\n\n            recordingLog.invalidateEntry(0);\n            recordingLog.appendTerm(8, 0, 100, 100);\n\n            final ClusterException exception = assertThrows(ClusterException.class,\n                () -> recordingLog.appendTerm(8, 1, 5, 5));\n            assertEquals(\"ERROR - duplicate TERM entry for leadershipTermId=1\", exception.getMessage());\n            assertEquals(3, recordingLog.entries().size());\n        }\n    }\n\n    @Test\n    void entriesInTheRecordingLogShouldBeSorted()\n    {\n        final String archiveEndpoint = \"aeron:udp?endpoint=localhost:8080\";\n        final List<RecordingLog.Entry> sortedList = new ArrayList<>();\n        sortedList.add(new RecordingLog.Entry(0, 0, 0, 90, 0, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 0));\n        sortedList.add(new RecordingLog.Entry(0, 1, 0, 777, 42, 2, ENTRY_TYPE_SNAPSHOT, null, true, 11));\n        sortedList.add(new RecordingLog.Entry(0, 1, 90, 400, 9, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 8));\n        sortedList.add(new RecordingLog.Entry(0, 1, 100, 1000000, 10, NULL_VALUE, ENTRY_TYPE_TERM, null, false, 1));\n        sortedList.add(new RecordingLog.Entry(0, 1, 111, 222, 12, 1, ENTRY_TYPE_SNAPSHOT, null, false, 2));\n        sortedList.add(new RecordingLog.Entry(0, 1, 111, 222, 12, 0, ENTRY_TYPE_SNAPSHOT, null, false, 4));\n        sortedList.add(new RecordingLog.Entry(0, 1, 111, 222, 12, SERVICE_ID, ENTRY_TYPE_SNAPSHOT, null, false, 3));\n        sortedList.add(new RecordingLog.Entry(0, 2, 400, 500, 20, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 7));\n        sortedList.add(new RecordingLog.Entry(\n            0, 2, 400, 1400, 200, 1, ENTRY_TYPE_STANDBY_SNAPSHOT, archiveEndpoint, true, 14));\n        sortedList.add(new RecordingLog.Entry(\n            0, 2, 400, 1400, 200, 0, ENTRY_TYPE_STANDBY_SNAPSHOT, archiveEndpoint, true, 15));\n        sortedList.add(new RecordingLog.Entry(\n            0, 2, 400, 1400, 200, SERVICE_ID, ENTRY_TYPE_STANDBY_SNAPSHOT, archiveEndpoint, true, 13));\n        sortedList.add(new RecordingLog.Entry(0, 2, 400, 1400, 200, 1, ENTRY_TYPE_SNAPSHOT, null, false, 10));\n        sortedList.add(new RecordingLog.Entry(0, 2, 400, 1400, 200, 0, ENTRY_TYPE_SNAPSHOT, null, true, 12));\n        sortedList.add(new RecordingLog.Entry(0, 2, 400, 1400, 200, SERVICE_ID, ENTRY_TYPE_SNAPSHOT, null, true, 9));\n        sortedList.add(\n            new RecordingLog.Entry(0, 2, 1_000_000, 500, 1_000_000, NULL_VALUE, ENTRY_TYPE_TERM, null, false, 6));\n        sortedList.add(new RecordingLog.Entry(0, 3, 500, NULL_VALUE, 30, NULL_VALUE, ENTRY_TYPE_TERM, null, true, 5));\n\n        try (RecordingLog recordingLog = new RecordingLog(tempDir, true))\n        {\n            recordingLog.appendTerm(0, 0, 0, 0);\n            recordingLog.appendTerm(0, 1, 100, 10);\n            recordingLog.appendSnapshot(0, 1, 111, 222, 12, 1);\n            recordingLog.appendSnapshot(0, 1, 111, 222, 12, SERVICE_ID);\n            recordingLog.appendSnapshot(0, 1, 111, 222, 12, 0);\n            recordingLog.appendTerm(0, 3, 500, 30);\n            recordingLog.appendTerm(0, 2, 1_000_000, 1_000_000);\n\n            recordingLog.invalidateEntry(1);\n            recordingLog.invalidateEntry(5);\n\n            recordingLog.appendTerm(0, 2, 400, 20);\n            recordingLog.appendTerm(0, 1, 90, 9);\n\n            assertTrue(recordingLog.invalidateLatestSnapshot());\n\n            recordingLog.appendSnapshot(0, 2, 400, 1400, 200, SERVICE_ID);\n            recordingLog.appendSnapshot(0, 2, 400, 1400, 200, 1);\n            recordingLog.appendSnapshot(0, 1, 0, 777, 42, 2);\n            recordingLog.appendSnapshot(0, 2, 400, 1400, 200, 0);\n\n            recordingLog.invalidateEntry(8);\n\n            recordingLog.appendStandbySnapshot(0, 2, 400, 1400, 200, SERVICE_ID, archiveEndpoint);\n            recordingLog.appendStandbySnapshot(0, 2, 400, 1400, 200, 1, archiveEndpoint);\n            recordingLog.appendStandbySnapshot(0, 2, 400, 1400, 200, 0, archiveEndpoint);\n\n            assertEquals(sortedList, recordingLog.entries()); // in memory view\n\n            recordingLog.reload();\n\n            assertEquals(sortedList, recordingLog.entries()); // reload from disc and re-sort\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"0, 0, 4\",\n        \"2, 500, 4\",\n        \"100, 1000000, 200\"\n    })\n    void shouldCreateInitialEmptyTermsWithAnEmptyRecordingLog(\n        final long initialLogLeadershipTermId,\n        final long initialTermBaseLogPosition,\n        final long leadershipTermId,\n        @TempDir final File tempDir)\n    {\n        final long logPosition = initialTermBaseLogPosition + 500;\n        final long nowNs = 10_000_000_000L;\n        final long timestampMs = nowNs / 1_000_000;\n\n        final RecordingLog log = new RecordingLog(tempDir, true);\n\n        log.ensureCoherent(\n            RECORDING_ID,\n            initialLogLeadershipTermId,\n            initialTermBaseLogPosition,\n            leadershipTermId,\n            initialTermBaseLogPosition,\n            logPosition,\n            nowNs,\n            timestampMs,\n            1);\n\n        for (long termId = initialLogLeadershipTermId + 1; termId < leadershipTermId; termId++)\n        {\n            final RecordingLog.Entry termEntry = log.findTermEntry(termId);\n            assertNotNull(termEntry);\n            assertEquals(initialTermBaseLogPosition, termEntry.termBaseLogPosition);\n            assertEquals(initialTermBaseLogPosition, termEntry.logPosition);\n            assertEquals(timestampMs, termEntry.timestamp);\n        }\n\n        final RecordingLog.Entry termEntry = log.findTermEntry(leadershipTermId);\n        assertNotNull(termEntry);\n        assertEquals(initialTermBaseLogPosition, termEntry.termBaseLogPosition);\n        assertEquals(logPosition, termEntry.logPosition);\n        assertEquals(timestampMs, termEntry.timestamp);\n    }\n\n    @Test\n    void shouldNotCreateInitialTermWithMinusOneTermId(@TempDir final File tempDir)\n    {\n        final long initialLogLeadershipTermId = -1;\n        final long initialTermBaseLogPosition = 0;\n        final long leadershipTermId = 4;\n        final long logPosition = initialTermBaseLogPosition + 500;\n        final long nowNs = 1_000_000;\n\n        final RecordingLog recordingLog = new RecordingLog(tempDir, true);\n\n        recordingLog.ensureCoherent(\n            RECORDING_ID,\n            initialLogLeadershipTermId,\n            initialTermBaseLogPosition,\n            leadershipTermId,\n            initialTermBaseLogPosition,\n            logPosition,\n            nowNs,\n            nowNs,\n            1);\n\n        for (final RecordingLog.Entry entry : recordingLog.entries())\n        {\n            assertNotEquals(-1, entry.leadershipTermId);\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"0, 0, 4\",\n        \"2, 500, 4\",\n        \"100, 1000000, 200\"\n    })\n    void shouldBackFillEmptyLeadershipTermsInANonemptyRecordingLog(\n        final long initialLogLeadershipTermId,\n        final long initialTermBaseLogPosition,\n        final long leadershipTermId,\n        @TempDir final Path tempDir)\n    {\n        final long termBaseLogPosition = initialTermBaseLogPosition + 100;\n        final long logPosition = initialTermBaseLogPosition + 500;\n        final long nowNs = 1_000_000;\n\n        final RecordingLog log = new RecordingLog(tempDir.toFile(), true);\n\n        log.appendTerm(RECORDING_ID, initialLogLeadershipTermId, initialTermBaseLogPosition, nowNs);\n\n        log.ensureCoherent(\n            RECORDING_ID,\n            initialLogLeadershipTermId,\n            initialTermBaseLogPosition,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            nowNs,\n            nowNs,\n            1);\n\n        for (long termId = initialLogLeadershipTermId + 1; termId < leadershipTermId; termId++)\n        {\n            final RecordingLog.Entry termEntry = log.findTermEntry(termId);\n            assertNotNull(termEntry);\n            assertEquals(termBaseLogPosition, termEntry.termBaseLogPosition);\n            assertEquals(termBaseLogPosition, termEntry.logPosition);\n        }\n\n        final RecordingLog.Entry termEntry = log.findTermEntry(leadershipTermId);\n        assertNotNull(termEntry);\n        assertEquals(termBaseLogPosition, termEntry.termBaseLogPosition);\n        assertEquals(logPosition, termEntry.logPosition);\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"0, 0, 4\",\n        \"2, 500, 4\",\n        \"100, 1000000, 200\"\n    })\n    void shouldCompleteExistingTerm(\n        final long initialLogLeadershipTermId,\n        final long initialTermBaseLogPosition,\n        final long leadershipTermId,\n        @TempDir final Path tempDir)\n    {\n        final long termBaseLogPosition = initialTermBaseLogPosition + 100;\n        final long logPosition = initialTermBaseLogPosition + 500;\n        final long nowNs = 1_000_000;\n\n        final RecordingLog log = new RecordingLog(tempDir.toFile(), true);\n\n        log.appendTerm(RECORDING_ID, leadershipTermId, termBaseLogPosition, nowNs);\n\n        log.ensureCoherent(\n            RECORDING_ID,\n            initialLogLeadershipTermId,\n            initialTermBaseLogPosition,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            nowNs,\n            nowNs,\n            1);\n\n        final RecordingLog.Entry termEntry = requireNonNull(log.findTermEntry(leadershipTermId));\n        assertEquals(termBaseLogPosition, termEntry.termBaseLogPosition);\n        assertEquals(logPosition, termEntry.logPosition);\n\n        assertEquals(1, log.entries().size());\n    }\n\n    @Test\n    void shouldBackFillPriorTerm(@TempDir final Path tempDir)\n    {\n        final long initialLogLeadershipTermId = 0;\n        final long initialTermBaseLogPosition = 0;\n        final long leadershipTermId = 1;\n        final long termBaseLogPosition = 10_000;\n        final long logPosition = -1;\n        final long nowNs = 1_000_000;\n\n        final RecordingLog log = new RecordingLog(tempDir.toFile(), true);\n\n        log.ensureCoherent(\n            RECORDING_ID,\n            initialLogLeadershipTermId,\n            initialTermBaseLogPosition,\n            leadershipTermId,\n            termBaseLogPosition,\n            logPosition,\n            nowNs,\n            nowNs,\n            1);\n\n        final RecordingLog.Entry termEntry0 = requireNonNull(log.findTermEntry(0));\n        assertEquals(initialLogLeadershipTermId, termEntry0.termBaseLogPosition);\n        assertEquals(initialTermBaseLogPosition, termEntry0.termBaseLogPosition);\n        assertEquals(termBaseLogPosition, termEntry0.logPosition);\n\n        final RecordingLog.Entry termEntry1 = requireNonNull(log.findTermEntry(1));\n        assertEquals(leadershipTermId, termEntry1.leadershipTermId);\n        assertEquals(termBaseLogPosition, termEntry1.termBaseLogPosition);\n        assertEquals(-1, termEntry1.logPosition);\n\n        assertEquals(2, log.entries().size());\n    }\n\n    @Test\n    void shouldThrowIfLastTermIsUnfinishedAndTermBaseLogPositionIsNotSpecified(@TempDir final Path tempDir)\n    {\n        final long leadershipTermId = 4;\n        final long logPosition = 500;\n        final long nowNs = 1_000_000;\n\n        final RecordingLog log = new RecordingLog(tempDir.toFile(), true);\n\n        log.appendTerm(RECORDING_ID, leadershipTermId - 1, 0, nowNs);\n\n        assertThrows(ClusterException.class, () -> log.ensureCoherent(\n            RECORDING_ID, 0, 0, leadershipTermId, NULL_POSITION, logPosition, nowNs, nowNs, 1));\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"0,TERM\", \"1,SNAPSHOT\", \"-5,UNKNOWN\", \"36542364,UNKNOWN\" })\n    void typeAsString(final int type, final String expectedString)\n    {\n        assertEquals(expectedString, RecordingLog.typeAsString(type));\n    }\n\n    @Test\n    void entryToString()\n    {\n        final RecordingLog.Entry entry = new RecordingLog.Entry(\n            42, 5, 1024, 701, 1_000_000_000_000L, 16, ENTRY_TYPE_SNAPSHOT, null, true, 2);\n        assertEquals(\n            \"Entry{recordingId=42, leadershipTermId=5, termBaseLogPosition=1024, logPosition=701, \" +\n            \"timestamp=1000000000000, serviceId=16, type=SNAPSHOT, isValid=true, entryIndex=2}\",\n            entry.toString());\n    }\n\n    @Test\n    void shouldDetermineIfSnapshotIsInvalid()\n    {\n        final RecordingLog.Entry validSnapshot = new RecordingLog.Entry(\n            42, 5, 1024, 701, 1_000_000_000_000L, 16, ENTRY_TYPE_SNAPSHOT, null, true, 2);\n        final RecordingLog.Entry invalidSnapshot = new RecordingLog.Entry(\n            42, 5, 1024, 701, 1_000_000_000_000L, 16, ENTRY_TYPE_SNAPSHOT, null, false, 2);\n        final RecordingLog.Entry term = new RecordingLog.Entry(\n            42, 5, 1024, 701, 1_000_000_000_000L, 16, ENTRY_TYPE_TERM, null, true, 2);\n\n        assertFalse(RecordingLog.isInvalidSnapshot(validSnapshot));\n        assertTrue(RecordingLog.isInvalidSnapshot(invalidSnapshot));\n        assertFalse(RecordingLog.isInvalidSnapshot(term));\n    }\n\n    @Test\n    void shouldInsertStandbySnapshotInRecordingLog(@TempDir final File tempDir)\n    {\n        try (RecordingLog log = new RecordingLog(tempDir, true))\n        {\n            log.appendSnapshot(1, 1, 0, 1000, 1_000_000_000L, SERVICE_ID);\n            log.appendSnapshot(2, 1, 0, 1000, 1_000_000_000L, 0);\n\n            log.appendStandbySnapshot(3, 2, 1000, 2000, 1_000_000_000L, SERVICE_ID, \"remotehost.aeron.io:20002\");\n            log.appendStandbySnapshot(4, 2, 1000, 2000, 1_000_000_000L, 0, \"remotehost.aeron.io:20002\");\n\n            log.appendSnapshot(5, 3, 2000, 3000, 1_000_000_000L, SERVICE_ID);\n            log.appendSnapshot(6, 3, 2000, 3000, 1_000_000_000L, 0);\n        }\n\n        try (RecordingLog log = new RecordingLog(tempDir, false))\n        {\n            assertLogEntry(log, 1, ENTRY_TYPE_SNAPSHOT, null);\n            assertLogEntry(log, 2, ENTRY_TYPE_SNAPSHOT, null);\n            assertLogEntry(log, 3, ENTRY_TYPE_STANDBY_SNAPSHOT, \"remotehost.aeron.io:20002\");\n            assertLogEntry(log, 4, ENTRY_TYPE_STANDBY_SNAPSHOT, \"remotehost.aeron.io:20002\");\n            assertLogEntry(log, 5, ENTRY_TYPE_SNAPSHOT, null);\n            assertLogEntry(log, 6, ENTRY_TYPE_SNAPSHOT, null);\n        }\n    }\n\n    int nextSnapshotIndex(final RecordingLog recordingLog)\n    {\n        for (int i = recordingLog.entries().size() - 1; i >= 0; i--)\n        {\n            final Entry entry = recordingLog.entries().get(i);\n            if (RecordingLog.isValidAnySnapshot(entry))\n            {\n                return i;\n            }\n        }\n\n        return -1;\n    }\n\n    @Test\n    void shouldInvalidateLatestAnySnapshots(@TempDir final File tempDir)\n    {\n        try (RecordingLog log = new RecordingLog(tempDir, true))\n        {\n            log.appendTerm(1, 1, 0, 1_000_000_000L);\n            log.appendTerm(1, 2, 0, 1_000_000_000L);\n            log.appendTerm(1, 3, 0, 1_000_000_000L);\n            log.appendTerm(1, 4, 0, 1_000_000_000L);\n\n            // 1\n            log.appendSnapshot(1, 1, 0, 1000, 1_000_000_000L, 0);\n            log.appendSnapshot(2, 1, 0, 1000, 1_000_000_000L, SERVICE_ID);\n\n            // 2\n            log.appendStandbySnapshot(3, 2, 1000, 2000, 1_000_000_000L, 0, \"remotehost.aeron.io:20002\");\n            log.appendStandbySnapshot(4, 2, 1000, 2000, 1_000_000_000L, SERVICE_ID, \"remotehost.aeron.io:20002\");\n\n            // 3\n            log.appendSnapshot(5, 3, 2000, 3000, 1_000_000_000L, 0);\n            log.appendSnapshot(6, 3, 2000, 3000, 1_000_000_000L, SERVICE_ID);\n\n            // 4\n            log.appendStandbySnapshot(7, 4, 1000, 4000, 1_000_000_000L, 0, \"remotehost.aeron.io:20002\");\n            log.appendStandbySnapshot(8, 4, 1000, 4000, 1_000_000_000L, SERVICE_ID, \"remotehost.aeron.io:20002\");\n            log.appendSnapshot(9, 4, 2000, 4000, 1_000_000_000L, 0);\n            log.appendSnapshot(10, 4, 2000, 4000, 1_000_000_000L, SERVICE_ID);\n\n            assertEquals(13, nextSnapshotIndex(log));\n        }\n\n        try (RecordingLog log = new RecordingLog(tempDir, false))\n        {\n            log.invalidateLatestSnapshot();\n            assertEquals(8, nextSnapshotIndex(log));\n\n            log.invalidateLatestSnapshot();\n            assertEquals(5, nextSnapshotIndex(log));\n\n            log.invalidateLatestSnapshot();\n            assertEquals(2, nextSnapshotIndex(log));\n        }\n    }\n\n    @Test\n    void shouldNotIncludeStandbySnapshotInRecoveryPlan(@TempDir final File tempDir)\n    {\n        try (RecordingLog log = new RecordingLog(tempDir, true))\n        {\n            log.appendSnapshot(1, 1, 0, 1000, 1_000_000_000L, SERVICE_ID);\n            log.appendSnapshot(2, 1, 0, 1000, 1_000_000_000L, 0);\n\n            log.appendStandbySnapshot(3, 2, 1000, 2000, 1_000_000_000L, SERVICE_ID, \"remotehost.aeron.io:20002\");\n            log.appendStandbySnapshot(4, 2, 1000, 2000, 1_000_000_000L, 0, \"remotehost.aeron.io:20002\");\n        }\n\n        final AeronArchive mockArchive = mock(AeronArchive.class);\n        mockExtent(mockArchive, 1);\n        mockExtent(mockArchive, 2);\n        mockExtent(mockArchive, 3);\n        mockExtent(mockArchive, 4);\n\n        try (RecordingLog log = new RecordingLog(tempDir, false))\n        {\n            final RecordingLog.RecoveryPlan recoveryPlan = log.createRecoveryPlan(mockArchive, 1, NULL_VALUE);\n\n            assertEquals(2, recoveryPlan.snapshots().size());\n            assertEquals(2, recoveryPlan.snapshots().size());\n            assertTrue(recoveryPlan.snapshots().stream().anyMatch((s) -> s.recordingId() == 1));\n            assertTrue(recoveryPlan.snapshots().stream().anyMatch((s) -> s.recordingId() == 2));\n        }\n    }\n\n    @Test\n    void shouldGetLatestStandbySnapshotsGroupedByEndpoint(@TempDir final File tempDir)\n    {\n        try (RecordingLog log = new RecordingLog(tempDir, true))\n        {\n            log.appendSnapshot(1, 1, 0, 1000, 1_000_000_000L, SERVICE_ID);\n            log.appendSnapshot(2, 1, 0, 1000, 1_000_000_000L, 0);\n\n            log.appendStandbySnapshot(5, 2, 500, 800, 1_000_000_000L, SERVICE_ID, \"remotehost0.aeron.io:20002\");\n            log.appendStandbySnapshot(6, 2, 500, 800, 1_000_000_000L, 0, \"remotehost0.aeron.io:20002\");\n\n            log.appendStandbySnapshot(3, 2, 1000, 2000, 1_000_000_000L, SERVICE_ID, \"remotehost0.aeron.io:20002\");\n            log.appendStandbySnapshot(4, 2, 1000, 2000, 1_000_000_000L, 0, \"remotehost0.aeron.io:20002\");\n\n            log.appendStandbySnapshot(3, 2, 1000, 2000, 1_000_000_000L, SERVICE_ID, \"remotehost1.aeron.io:20002\");\n            log.appendStandbySnapshot(4, 2, 1000, 2000, 1_000_000_000L, 0, \"remotehost1.aeron.io:20002\");\n\n            log.appendStandbySnapshot(10, 2, 3000, 4000, 1_000_000_000L, 0, \"remotehost0.aeron.io:20002\");\n            log.appendStandbySnapshot(11, 2, 3000, 4000, 1_000_000_000L, 0, \"remotehost1.aeron.io:20002\");\n        }\n\n        try (RecordingLog log = new RecordingLog(tempDir, false))\n        {\n            assertLogEntry(log, 1, ENTRY_TYPE_SNAPSHOT, null);\n            assertLogEntry(log, 2, ENTRY_TYPE_SNAPSHOT, null);\n            assertLogEntry(log, 3, ENTRY_TYPE_STANDBY_SNAPSHOT, \"remotehost0.aeron.io:20002\");\n            assertLogEntry(log, 4, ENTRY_TYPE_STANDBY_SNAPSHOT, \"remotehost0.aeron.io:20002\");\n            assertLogEntry(log, 3, ENTRY_TYPE_STANDBY_SNAPSHOT, \"remotehost1.aeron.io:20002\");\n            assertLogEntry(log, 4, ENTRY_TYPE_STANDBY_SNAPSHOT, \"remotehost1.aeron.io:20002\");\n\n            final int serviceCount = 1;\n            final Map<String, List<RecordingLog.Entry>> standbySnapshots = log.latestStandbySnapshots(serviceCount);\n            assertNotNull(standbySnapshots);\n\n            assertEquals(2, standbySnapshots.get(\"remotehost0.aeron.io:20002\").size());\n            assertEquals(2, standbySnapshots.get(\"remotehost1.aeron.io:20002\").size());\n        }\n    }\n\n    @Test\n    void shouldInvalidateLatestSnapshotIgnoringStandbySnapshots(@TempDir final File tempDir)\n    {\n        try (RecordingLog log = new RecordingLog(tempDir, true))\n        {\n            log.appendTerm(1, 0, 0, 1_000_000_000L);\n\n            log.appendSnapshot(2, 0, 0, 1000, 1_000_000_000L, SERVICE_ID);\n            log.appendSnapshot(3, 0, 0, 1000, 1_000_000_000L, 0);\n\n            log.appendSnapshot(4, 0, 2, 2000, 1_000_000_000L, SERVICE_ID);\n            log.appendSnapshot(5, 0, 2, 2000, 1_000_000_000L, 0);\n\n            log.appendStandbySnapshot(15, 0, 500, 800, 1_000_000_000L, SERVICE_ID, \"remotehost0.aeron.io:20002\");\n            log.appendStandbySnapshot(16, 0, 500, 800, 1_000_000_000L, 0, \"remotehost0.aeron.io:20002\");\n        }\n\n        try (RecordingLog log = new RecordingLog(tempDir, false))\n        {\n            assertEquals(4, requireNonNull(log.getLatestSnapshot(SERVICE_ID)).recordingId);\n            final Map<String, List<RecordingLog.Entry>> preInvalidate = log.latestStandbySnapshots(2);\n\n            log.invalidateLatestSnapshot();\n\n            assertEquals(2, requireNonNull(log.getLatestSnapshot(SERVICE_ID)).recordingId);\n            assertEquals(preInvalidate, log.latestStandbySnapshots(2));\n        }\n    }\n\n    @Test\n    void shouldHandleEntriesStraddlingPageBoundary(@TempDir final File tempDir)\n    {\n        final String endpoint = Tests.generateStringWithSuffix(\"a\", \"x\", 3079);\n        try (RecordingLog log = new RecordingLog(tempDir, true))\n        {\n            log.appendStandbySnapshot(1, 2, 1000, 2000, 1_000_000_000L, SERVICE_ID, endpoint);\n            log.appendStandbySnapshot(2, 2, 1000, 2000, 1_000_000_000L, 0, endpoint);\n            log.appendStandbySnapshot(3, 2, 1000, 2000, 1_000_000_000L, 1, endpoint);\n            log.append(ENTRY_TYPE_TERM, 4, 3, 10000, 11111, 2_000_000_000L, SERVICE_ID, RECORDING_LOG_FILE_NAME);\n            log.appendSnapshot(5, 4, 20000, 22222, 3_000_000_000L, SERVICE_ID);\n            log.appendSnapshot(6, 4, 20000, 22222, 3_000_000_000L, 0);\n        }\n\n        try (RecordingLog log = new RecordingLog(tempDir, false))\n        {\n            assertLogEntry(log, 1, ENTRY_TYPE_STANDBY_SNAPSHOT, endpoint);\n            assertLogEntry(log, 2, ENTRY_TYPE_STANDBY_SNAPSHOT, endpoint);\n            assertLogEntry(log, 3, ENTRY_TYPE_STANDBY_SNAPSHOT, endpoint);\n            assertLogEntry(log, 4, ENTRY_TYPE_TERM, null);\n            assertLogEntry(log, 5, ENTRY_TYPE_SNAPSHOT, null);\n            assertLogEntry(log, 6, ENTRY_TYPE_SNAPSHOT, null);\n        }\n    }\n\n    @Test\n    void shouldRejectSnapshotEntryIfEndpointIsTooLong(@TempDir final File tempDir)\n    {\n        final String endpoint = Tests.generateStringWithSuffix(\"a\", \"x\", 5000);\n        try (RecordingLog log = new RecordingLog(tempDir, true))\n        {\n            final ClusterException exception = assertThrowsExactly(ClusterException.class,\n                () -> log.appendStandbySnapshot(1, 2, 1000, 2000, 1_000_000_000L, SERVICE_ID, endpoint));\n            assertEquals(\"ERROR - Endpoint is too long: \" + endpoint.length() + \" vs \" + MAX_ENDPOINT_LENGTH,\n                exception.getMessage());\n        }\n    }\n\n    private void mockExtent(final AeronArchive mockArchive, final long recordingId)\n    {\n        when(mockArchive.listRecording(eq(recordingId), any())).thenAnswer(\n            invocation ->\n            {\n                final RecordingDescriptorConsumer consumer = invocation.getArgument(\n                    1, RecordingDescriptorConsumer.class);\n\n                consumer.onRecordingDescriptor(\n                    recordingId + 1_000_000,\n                    recordingId + 2_000_000,\n                    recordingId,\n                    0,\n                    0,\n                    0,\n                    50_000,\n                    0,\n                    128 * 1024 * 1024,\n                    256 * 1024,\n                    1408,\n                    0,\n                    0,\n                    \"\",\n                    \"\",\n                    \"\");\n\n                return 1;\n            });\n    }\n\n    private static void assertLogEntry(\n        final RecordingLog log,\n        final long recordingId,\n        final int type,\n        final String endpoint)\n    {\n        final Predicate<RecordingLog.Entry> entryPredicate = (entry) ->\n            entry.recordingId == recordingId &&\n            entry.type == type &&\n            Objects.equals(entry.archiveEndpoint, endpoint);\n\n        assertTrue(log.entries().stream().anyMatch(entryPredicate));\n    }\n\n    private static void addRecordingLogEntry(\n        final ArrayList<RecordingLog.Entry> entries, final int serviceId, final int recordingId, final int entryType)\n    {\n        entries.add(new RecordingLog.Entry(\n            recordingId, 1, 1440, 2880, 0L, serviceId, entryType, null, true, entries.size()));\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/RecordingReplicationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ControlResponsePoller;\nimport io.aeron.archive.client.ReplicationParams;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.cluster.client.ClusterException;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass RecordingReplicationTest\n{\n    private static final long SRC_RECORDING_ID = 1;\n    private static final long DST_RECORDING_ID = 2;\n    private static final long REPLICATION_ID = 4;\n\n    private static final String ENDPOINT = \"localhost:20123\";\n    private static final int SRC_STREAM_ID = 982734;\n    private static final String REPLICATION_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n    private static final long PROGRESS_CHECK_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(5);\n    private static final long PROGRESS_CHECK_INTERVAL_NS = TimeUnit.SECONDS.toNanos(1);\n\n    private final CountersReader countersReader = mock(CountersReader.class);\n    private final AeronArchive aeronArchive = mock(AeronArchive.class);\n    private final ControlResponsePoller controlResponsePoller = mock(ControlResponsePoller.class);\n    private final Subscription subscription = mock(Subscription.class);\n\n    @BeforeEach\n    void setUp()\n    {\n        final AeronArchive.Context ctx = mock(AeronArchive.Context.class);\n        final Aeron aeron = mock(Aeron.class);\n        when(aeronArchive.context()).thenReturn(ctx);\n        when(ctx.aeron()).thenReturn(aeron);\n        when(aeron.countersReader()).thenReturn(countersReader);\n\n        when(aeronArchive.controlResponsePoller()).thenReturn(controlResponsePoller);\n        when(controlResponsePoller.subscription()).thenReturn(subscription);\n        when(aeronArchive.replicate(anyLong(), anyInt(), any(), any())).thenReturn(REPLICATION_ID);\n    }\n\n    @Test\n    void shouldIndicateAppropriateStatesAsSignalsAreReceived()\n    {\n        final long stopPosition = 982734;\n        final long nowNs = 0;\n\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .dstRecordingId(DST_RECORDING_ID)\n            .stopPosition(stopPosition)\n            .replicationChannel(REPLICATION_CHANNEL)\n            .replicationSessionId(Aeron.NULL_VALUE);\n\n        final RecordingReplication logReplication = new RecordingReplication(\n            aeronArchive,\n            SRC_RECORDING_ID,\n            ENDPOINT,\n            SRC_STREAM_ID,\n            replicationParams,\n            PROGRESS_CHECK_TIMEOUT_NS,\n            PROGRESS_CHECK_INTERVAL_NS,\n            nowNs);\n\n        logReplication.onSignal(REPLICATION_ID, DST_RECORDING_ID, NULL_POSITION, RecordingSignal.REPLICATE_END);\n        assertTrue(logReplication.hasReplicationEnded());\n        assertFalse(logReplication.hasSynced());\n        assertFalse(logReplication.hasStopped());\n\n        logReplication.onSignal(REPLICATION_ID, DST_RECORDING_ID, NULL_POSITION, RecordingSignal.STOP);\n        assertTrue(logReplication.hasReplicationEnded());\n        assertFalse(logReplication.hasSynced());\n        assertTrue(logReplication.hasStopped());\n\n        logReplication.onSignal(REPLICATION_ID, DST_RECORDING_ID, NULL_POSITION, RecordingSignal.SYNC);\n        assertTrue(logReplication.hasReplicationEnded());\n        assertTrue(logReplication.hasSynced());\n        assertTrue(logReplication.hasStopped());\n    }\n\n    @ParameterizedTest\n    @EnumSource(value = RecordingSignal.class, names = { \"START\", \"SYNC\", \"EXTEND\", \"REPLICATE\", \"MERGE\" })\n    void shouldStopReplicationIfNotAlreadyStopped(final RecordingSignal recordingSignal)\n    {\n        final long stopPosition = 982734;\n        final long nowNs = 0;\n\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .dstRecordingId(DST_RECORDING_ID)\n            .stopPosition(stopPosition)\n            .replicationChannel(REPLICATION_CHANNEL)\n            .replicationSessionId(Aeron.NULL_VALUE);\n\n        final RecordingReplication logReplication = new RecordingReplication(\n            aeronArchive,\n            SRC_RECORDING_ID,\n            ENDPOINT,\n            SRC_STREAM_ID,\n            replicationParams,\n            PROGRESS_CHECK_TIMEOUT_NS,\n            PROGRESS_CHECK_INTERVAL_NS,\n            nowNs);\n\n        logReplication.onSignal(REPLICATION_ID, DST_RECORDING_ID, stopPosition, recordingSignal);\n        logReplication.poll(nowNs);\n        assertFalse(logReplication.hasReplicationEnded());\n\n        logReplication.close();\n        verify(aeronArchive).tryStopReplication(REPLICATION_ID);\n    }\n\n    @Test\n    void shouldNotStopReplicationIfStopSignalled()\n    {\n        final long stopPosition = 982734;\n        final long nowNs = 0;\n\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .dstRecordingId(DST_RECORDING_ID)\n            .stopPosition(stopPosition)\n            .replicationChannel(REPLICATION_CHANNEL)\n            .replicationSessionId(Aeron.NULL_VALUE);\n\n        final RecordingReplication logReplication = new RecordingReplication(\n            aeronArchive,\n            SRC_RECORDING_ID,\n            ENDPOINT,\n            SRC_STREAM_ID,\n            replicationParams,\n            PROGRESS_CHECK_TIMEOUT_NS,\n            PROGRESS_CHECK_INTERVAL_NS,\n            nowNs);\n\n        logReplication.onSignal(REPLICATION_ID, DST_RECORDING_ID, stopPosition, RecordingSignal.STOP);\n\n        logReplication.close();\n        verify(aeronArchive, never()).stopReplication(anyLong());\n    }\n\n    @Test\n    void shouldFailIfRecordingLogIsDeletedDuringReplication()\n    {\n        final RecordingSignal recordingSignal = RecordingSignal.DELETE;\n        final long stopPosition = 982734;\n        final long nowNs = 0;\n\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .dstRecordingId(DST_RECORDING_ID)\n            .stopPosition(stopPosition)\n            .replicationChannel(REPLICATION_CHANNEL)\n            .replicationSessionId(Aeron.NULL_VALUE);\n\n        final RecordingReplication logReplication = new RecordingReplication(\n            aeronArchive,\n            SRC_RECORDING_ID,\n            ENDPOINT,\n            SRC_STREAM_ID,\n            replicationParams,\n            PROGRESS_CHECK_TIMEOUT_NS,\n            PROGRESS_CHECK_INTERVAL_NS,\n            nowNs);\n\n        assertThrows(\n            ClusterException.class,\n            () -> logReplication.onSignal(REPLICATION_ID, DST_RECORDING_ID, stopPosition - 1, recordingSignal));\n    }\n\n    @Test\n    void shouldPollForProgressAndFailIfNotProgressing()\n    {\n        final long stopPosition = 982734;\n        final long t0 = 20L;\n\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .dstRecordingId(DST_RECORDING_ID)\n            .stopPosition(stopPosition)\n            .replicationChannel(REPLICATION_CHANNEL)\n            .replicationSessionId(Aeron.NULL_VALUE);\n\n        final RecordingReplication logReplication = new RecordingReplication(\n            aeronArchive,\n            SRC_RECORDING_ID,\n            ENDPOINT,\n            SRC_STREAM_ID,\n            replicationParams,\n            PROGRESS_CHECK_TIMEOUT_NS,\n            PROGRESS_CHECK_INTERVAL_NS,\n            t0);\n\n        logReplication.onSignal(REPLICATION_ID, DST_RECORDING_ID, 0, RecordingSignal.EXTEND);\n        logReplication.poll(t0);\n        logReplication.poll(t0 + (PROGRESS_CHECK_INTERVAL_NS - 1));\n        logReplication.poll(t0 + PROGRESS_CHECK_INTERVAL_NS);\n\n        assertThrows(\n            ClusterException.class,\n            () -> logReplication.poll(t0 + PROGRESS_CHECK_INTERVAL_NS + PROGRESS_CHECK_TIMEOUT_NS));\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/SessionEventCodecCompatibilityTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport org.agrona.SemanticVersion;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass SessionEventCodecCompatibilityTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[1024]);\n\n    @Test\n    void readingVersion12UsingVersion5Codec()\n    {\n        final long clusterSessionId = -4623823;\n        final long correlationId = 3583456348756843L;\n        final long leadershipTermId = 10_000_000_000L;\n        final int leaderMemberId = 2;\n        final io.aeron.cluster.codecs.v12.EventCode code = io.aeron.cluster.codecs.v12.EventCode.REDIRECT;\n        final int version = SemanticVersion.compose(3, 47, 123);\n        final String detail = \"some very detailed message\";\n        final io.aeron.cluster.codecs.v12.SessionEventEncoder encoderV12 =\n            new io.aeron.cluster.codecs.v12.SessionEventEncoder();\n        encoderV12\n            .wrapAndApplyHeader(buffer, 0, new io.aeron.cluster.codecs.v12.MessageHeaderEncoder())\n            .clusterSessionId(clusterSessionId)\n            .correlationId(correlationId)\n            .leadershipTermId(leadershipTermId)\n            .leaderMemberId(leaderMemberId)\n            .code(code)\n            .version(version)\n            .detail(detail);\n\n        final io.aeron.cluster.codecs.v5.SessionEventDecoder decoderV5 =\n            new io.aeron.cluster.codecs.v5.SessionEventDecoder();\n        final io.aeron.cluster.codecs.v5.MessageHeaderDecoder headerDecoderV5 =\n            new io.aeron.cluster.codecs.v5.MessageHeaderDecoder();\n        decoderV5.wrapAndApplyHeader(buffer, 0, headerDecoderV5);\n\n        assertEquals(\n            io.aeron.cluster.codecs.v5.SessionEventDecoder.SCHEMA_ID,\n            io.aeron.cluster.codecs.v12.SessionEventEncoder.SCHEMA_ID);\n        assertThat(\n            io.aeron.cluster.codecs.v12.SessionEventEncoder.SCHEMA_VERSION,\n            greaterThan(io.aeron.cluster.codecs.v5.SessionEventDecoder.SCHEMA_VERSION));\n        assertThat(\n            io.aeron.cluster.codecs.v12.SessionEventEncoder.BLOCK_LENGTH,\n            greaterThan(io.aeron.cluster.codecs.v5.SessionEventDecoder.BLOCK_LENGTH));\n        assertEquals(io.aeron.cluster.codecs.v5.SessionEventDecoder.SCHEMA_ID, headerDecoderV5.sbeSchemaId());\n        assertEquals(io.aeron.cluster.codecs.v5.SessionEventDecoder.SCHEMA_VERSION, headerDecoderV5.sbeSchemaVersion());\n        assertEquals(io.aeron.cluster.codecs.v12.SessionEventEncoder.SCHEMA_VERSION, headerDecoderV5.version());\n        assertEquals(io.aeron.cluster.codecs.v12.SessionEventEncoder.BLOCK_LENGTH, headerDecoderV5.blockLength());\n\n        assertEquals(clusterSessionId, decoderV5.clusterSessionId());\n        assertEquals(correlationId, decoderV5.correlationId());\n        assertEquals(leadershipTermId, decoderV5.leadershipTermId());\n        assertEquals(leaderMemberId, decoderV5.leaderMemberId());\n        assertEquals(code.value(), decoderV5.code().value());\n        assertEquals(detail, decoderV5.detail());\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/SessionManagerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ConcurrentPublication;\nimport io.aeron.Counter;\nimport io.aeron.Image;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.security.DefaultAuthenticatorSupplier;\nimport io.aeron.test.cluster.TestClusterClock;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\nclass SessionManagerTest\n{\n    final RecordingLog mockRecordingLog = mock(RecordingLog.class);\n    final Header mockHeader = mock(Header.class);\n    final Image mockImage = mock(Image.class);\n    final Aeron mockAeron = mock(Aeron.class);\n    final ConcurrentPublication mockPublication = mock(ConcurrentPublication.class);\n\n    final TestClusterClock clock = new TestClusterClock(TimeUnit.NANOSECONDS);\n    SessionManager sessionManager;\n\n    @BeforeEach\n    void setup()\n    {\n        when(mockHeader.context()).thenReturn(mockImage);\n        when(mockImage.sourceIdentity()).thenReturn(\"localhost:1234\");\n        when(mockAeron.getPublication(anyLong())).thenReturn(mockPublication);\n        when(mockPublication.isConnected()).thenReturn(true);\n    }\n\n    void setupSessionManager(final long standbySnapshotNotificationProcessingDelayNs)\n    {\n        sessionManager = new SessionManager(\n            new ClusterMember[0],\n            0,\n            clock,\n            mock(EgressPublisher.class),\n            mockAeron,\n            0,\n            mock(CountedErrorHandler.class),\n            mock(Counter.class),\n            mock(DistinctErrorLog.class),\n            true,\n            mock(Counter.class),\n            DefaultAuthenticatorSupplier.DEFAULT_AUTHENTICATOR,\n            AuthorisationService.ALLOW_ALL,\n            mockRecordingLog,\n            mock(LogPublisher.class),\n            mock(ConsensusPublisher.class),\n            mock(ConsensusModuleExtension.class),\n            0,\n            0,\n            0,\n            0,\n            standbySnapshotNotificationProcessingDelayNs);\n    }\n\n\n    @Test\n    void shouldProcessPendingStandbySnapshotNotificationsAfterReachingCommitPosition()\n    {\n        setupSessionManager(0);\n\n        final List<StandbySnapshotEntry> standby1SnapshotEntries = new ArrayList<>();\n        standby1SnapshotEntries.add(new StandbySnapshotEntry(0, 0, 0, 100, 0, -1, \"localhost:1234\"));\n        standby1SnapshotEntries.add(new StandbySnapshotEntry(1, 0, 0, 100, 0, 0, \"localhost:1234\"));\n\n        sessionManager.onStandbySnapshot(\n            0,\n            0,\n            standby1SnapshotEntries,\n            0,\n            null,\n            new byte[0],\n            mockHeader);\n\n        final List<StandbySnapshotEntry> standby2SnapshotEntries = new ArrayList<>();\n        standby2SnapshotEntries.add(new StandbySnapshotEntry(2, 0, 0, 50, 0, -1, \"localhost:1234\"));\n        standby2SnapshotEntries.add(new StandbySnapshotEntry(3, 0, 0, 50, 0, 0, \"localhost:1234\"));\n\n        sessionManager.onStandbySnapshot(\n            0,\n            0,\n            standby2SnapshotEntries,\n            0,\n            null,\n            new byte[0],\n            mockHeader);\n\n        sessionManager.processPendingBackupSessions(clock.time(), 0, 0);\n\n        sessionManager.processPendingStandbySnapshotNotifications(49, clock.time());\n\n        verify(mockRecordingLog, never())\n            .appendStandbySnapshot(anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyInt(), anyString());\n\n        sessionManager.processPendingStandbySnapshotNotifications(50, clock.time());\n\n        for (final StandbySnapshotEntry entry : standby2SnapshotEntries)\n        {\n            verify(mockRecordingLog).appendStandbySnapshot(\n                eq(entry.recordingId()),\n                eq(entry.leadershipTermId()),\n                eq(entry.termBaseLogPosition()),\n                eq(entry.logPosition()),\n                eq(entry.timestamp()),\n                eq(entry.serviceId()),\n                eq(entry.archiveEndpoint()));\n        }\n\n        verifyNoMoreInteractions(mockRecordingLog);\n\n        sessionManager.processPendingStandbySnapshotNotifications(100, clock.time());\n\n        for (final StandbySnapshotEntry entry : standby1SnapshotEntries)\n        {\n            verify(mockRecordingLog).appendStandbySnapshot(\n                eq(entry.recordingId()),\n                eq(entry.leadershipTermId()),\n                eq(entry.termBaseLogPosition()),\n                eq(entry.logPosition()),\n                eq(entry.timestamp()),\n                eq(entry.serviceId()),\n                eq(entry.archiveEndpoint()));\n        }\n\n        sessionManager.processPendingStandbySnapshotNotifications(100, clock.time());\n\n        verifyNoMoreInteractions(mockRecordingLog);\n    }\n\n    @Test\n    void shouldProcessPendingStandbySnapshotNotificationsAfterProcessingDelay()\n    {\n        clock.update(0, TimeUnit.NANOSECONDS);\n\n        final long processingDelay = 100;\n\n        setupSessionManager(processingDelay);\n\n        final List<StandbySnapshotEntry> standbySnapshotEntries = new ArrayList<>();\n        standbySnapshotEntries.add(new StandbySnapshotEntry(0, 0, 0, 100, 0, -1, \"localhost:1234\"));\n        standbySnapshotEntries.add(new StandbySnapshotEntry(1, 0, 0, 100, 0, 0, \"localhost:1234\"));\n\n        sessionManager.onStandbySnapshot(\n            0,\n            0,\n            standbySnapshotEntries,\n            0,\n            null,\n            new byte[0],\n            mockHeader);\n\n        sessionManager.processPendingBackupSessions(clock.time(), 0, 0);\n\n        sessionManager.processPendingStandbySnapshotNotifications(100, clock.time());\n\n        verify(mockRecordingLog, never())\n            .appendStandbySnapshot(anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyInt(), anyString());\n\n        clock.increment(processingDelay);\n\n        sessionManager.processPendingStandbySnapshotNotifications(100, clock.time());\n\n        for (final StandbySnapshotEntry entry : standbySnapshotEntries)\n        {\n            verify(mockRecordingLog).appendStandbySnapshot(\n                eq(entry.recordingId()),\n                eq(entry.leadershipTermId()),\n                eq(entry.termBaseLogPosition()),\n                eq(entry.logPosition()),\n                eq(entry.timestamp()),\n                eq(entry.serviceId()),\n                eq(entry.archiveEndpoint()));\n        }\n    }\n}"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/SnapshotReplicationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ReplicationParams;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static io.aeron.archive.codecs.RecordingSignal.REPLICATE_END;\nimport static io.aeron.archive.codecs.RecordingSignal.SYNC;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass SnapshotReplicationTest\n{\n    private final AeronArchive archive = mock(AeronArchive.class);\n    private final String srcChannel = \"aeron:udp?endpoint=coming_from:8888\";\n    private final String replicationChannel = \"aeron:udp?endpoint=going_to:8888\";\n    private final int srcStreamId = 892374;\n    private final long nowNs = 1_000_000_000;\n    private final long correlationId = 987237452342L;\n\n    @BeforeEach\n    void setUp()\n    {\n        final AeronArchive.Context mockContext = mock(AeronArchive.Context.class);\n        final Aeron mockAeron = mock(Aeron.class);\n        when(archive.context()).thenReturn(mockContext);\n        when(mockContext.aeron()).thenReturn(mockAeron);\n        when(mockAeron.nextCorrelationId()).thenReturn(correlationId);\n    }\n\n    @Test\n    void shouldReplicateTwoSnapshots()\n    {\n        final long replicationId0 = 89374;\n        final long newRecordingId0 = 9823754293L;\n        final long replicationId1 = 89375;\n        final long newRecordingId1 = 7635445643L;\n\n        final List<RecordingLog.Snapshot> snapshots = Arrays.asList(\n            new RecordingLog.Snapshot(2, 3, 5, 7, 11, 0),\n            new RecordingLog.Snapshot(17, 3, 5, 7, 31, -1));\n\n        when(archive.replicate(eq(snapshots.get(0).recordingId()), anyInt(), any(), any()))\n            .thenReturn(replicationId0);\n        when(archive.replicate(eq(snapshots.get(1).recordingId()), anyInt(), any(), any()))\n            .thenReturn(replicationId1);\n\n        final SnapshotReplication snapshotReplication = new SnapshotReplication(\n            archive, srcStreamId, srcChannel, replicationChannel);\n\n        snapshotReplication.addSnapshot(snapshots.get(0));\n        snapshotReplication.addSnapshot(snapshots.get(1));\n\n        assertEquals(1, snapshotReplication.poll(nowNs));\n\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .replicationChannel(replicationChannel)\n            .replicationSessionId((int)correlationId);\n\n        verify(archive).replicate(\n            snapshots.get(0).recordingId(),\n            srcStreamId,\n            srcChannel,\n            replicationParams);\n        assertEquals(replicationId0, snapshotReplication.currentReplicationId());\n        assertEquals(snapshots.get(0).recordingId(), snapshotReplication.currentSnapshot().recordingId());\n\n        ignoreArchiveContextLookup();\n\n        snapshotReplication.poll(nowNs);\n        verifyNoMoreInteractions(archive);\n\n        snapshotReplication.onSignal(replicationId0, newRecordingId0, 23423, SYNC);\n        snapshotReplication.poll(nowNs);\n        verifyNoMoreInteractions(archive);\n\n        snapshotReplication.onSignal(replicationId0, newRecordingId0, 23423, REPLICATE_END);\n        snapshotReplication.poll(nowNs);\n        verifyNoMoreInteractions(archive);\n\n        snapshotReplication.poll(nowNs);\n\n        verify(archive).replicate(\n            snapshots.get(1).recordingId(),\n            srcStreamId,\n            srcChannel,\n            replicationParams);\n        assertEquals(replicationId1, snapshotReplication.currentReplicationId());\n        assertEquals(snapshots.get(1).recordingId(), snapshotReplication.currentSnapshot().recordingId());\n\n        ignoreArchiveContextLookup();\n\n        snapshotReplication.poll(nowNs);\n        verifyNoMoreInteractions(archive);\n\n        snapshotReplication.onSignal(replicationId1, newRecordingId1, 23423, SYNC);\n        snapshotReplication.poll(nowNs);\n        verifyNoMoreInteractions(archive);\n\n        snapshotReplication.onSignal(replicationId1, newRecordingId1, 23423, REPLICATE_END);\n        snapshotReplication.poll(nowNs);\n        verifyNoMoreInteractions(archive);\n\n        assertTrue(snapshotReplication.isComplete());\n        assertEquals(0, snapshotReplication.snapshotsRetrieved().get(0).serviceId());\n        assertEquals(newRecordingId0, snapshotReplication.snapshotsRetrieved().get(0).recordingId());\n        assertEquals(-1, snapshotReplication.snapshotsRetrieved().get(1).serviceId());\n        assertEquals(newRecordingId1, snapshotReplication.snapshotsRetrieved().get(1).recordingId());\n\n        snapshotReplication.poll(nowNs);\n        verifyNoMoreInteractions(archive);\n    }\n\n    @Test\n    void shouldNotBeCompleteIfNotSynced()\n    {\n        final long replicationId0 = 89374;\n        final long replicationId1 = 89375;\n        final List<RecordingLog.Snapshot> snapshots = Arrays.asList(\n            new RecordingLog.Snapshot(2, 3, 5, 7, 11, 13),\n            new RecordingLog.Snapshot(17, 19, 23, 29, 31, 37));\n\n        when(archive.replicate(eq(snapshots.get(0).recordingId()), anyLong(), anyLong(), anyInt(), any(), any(), any()))\n            .thenReturn(replicationId0);\n        when(archive.replicate(eq(snapshots.get(1).recordingId()), anyLong(), anyLong(), anyInt(), any(), any(), any()))\n            .thenReturn(replicationId1);\n\n        final SnapshotReplication snapshotReplication = new SnapshotReplication(\n            archive, srcStreamId, srcChannel, replicationChannel);\n\n        snapshotReplication.addSnapshot(snapshots.get(0));\n        snapshotReplication.addSnapshot(snapshots.get(1));\n\n        snapshotReplication.poll(nowNs);\n        verify(archive).replicate(eq(snapshots.get(0).recordingId()), anyInt(), any(), any());\n\n        snapshotReplication.poll(nowNs);\n        snapshotReplication.onSignal(replicationId0, snapshots.get(0).recordingId(), 23423, REPLICATE_END);\n        assertFalse(snapshotReplication.isComplete());\n    }\n\n    @Test\n    void closeWillCloseUnderlyingSnapshotReplication()\n    {\n        final List<RecordingLog.Snapshot> snapshots = Arrays.asList(\n            new RecordingLog.Snapshot(2, 3, 5, 7, 11, 0),\n            new RecordingLog.Snapshot(17, 3, 5, 7, 31, -1));\n\n        when(archive.replicate(eq(snapshots.get(0).recordingId()), anyLong(), anyLong(), anyInt(), any(), any(), any()))\n            .thenReturn(1L);\n        when(archive.replicate(eq(snapshots.get(1).recordingId()), anyLong(), anyLong(), anyInt(), any(), any(), any()))\n            .thenReturn(2L);\n\n        final SnapshotReplication snapshotReplication = new SnapshotReplication(\n            archive, srcStreamId, srcChannel, replicationChannel);\n        snapshots.forEach(snapshotReplication::addSnapshot);\n\n        snapshotReplication.poll(nowNs);\n\n        verify(archive).replicate(anyLong(), anyInt(), any(), any());\n        ignoreArchiveContextLookup();\n\n        snapshotReplication.close();\n        verify(archive).tryStopReplication(anyLong());\n\n        snapshotReplication.close();\n        verifyNoMoreInteractions(archive);\n    }\n\n    private void ignoreArchiveContextLookup()\n    {\n        verify(archive, atLeast(0)).context();\n    }\n}"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/StandbySnapshotReplicatorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.Counter;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.cluster.client.ClusterException;\nimport org.agrona.collections.Long2LongHashMap;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.MockedStatic;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.status.RecordingPos.NULL_RECORDING_ID;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SERVICE_ID;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.contains;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass StandbySnapshotReplicatorTest\n{\n    private final String endpoint0 = \"host0:10001\";\n    private final String endpoint1 = \"host1:10101\";\n    private final String archiveControlChannel = \"aeron:udp?endpoint=invalid:6666\";\n    private final int archiveControlStreamId = 98734;\n    private final String replicationChannel = \"aeron:udp?endpoint=host0:0\";\n    private final AeronArchive.Context ctx = new AeronArchive.Context();\n    private final int memberId = 12;\n    private final int fileSyncLevel = 1;\n\n    private final AeronArchive mockArchive = mock(AeronArchive.class);\n    private final MultipleRecordingReplication mockMultipleRecordingReplication0 = mock(\n        MultipleRecordingReplication.class, \"host0\");\n    private final MultipleRecordingReplication mockMultipleRecordingReplication1 = mock(\n        MultipleRecordingReplication.class, \"host1\");\n    private final Counter snapshotCounter = mock(Counter.class, \"snapshotCounter\");\n\n    @BeforeEach\n    void setUp()\n    {\n        when(mockArchive.context()).thenReturn(ctx);\n    }\n\n    @TempDir\n    private File clusterDir;\n\n    @Test\n    void shouldReplicateStandbySnapshots()\n    {\n        final long logPositionOldest = 1000L;\n        final long logPositionNewest = 3000L;\n        final long progressTimeoutNs = TimeUnit.SECONDS.toNanos(20);\n        final long intervalTimeoutNs = TimeUnit.SECONDS.toNanos(2);\n        final int serviceCount = 1;\n        final Long2LongHashMap dstRecordingIds = new Long2LongHashMap(Aeron.NULL_VALUE);\n        dstRecordingIds.put(1, 11);\n        dstRecordingIds.put(2, 12);\n\n        when(mockMultipleRecordingReplication0.completedDstRecordingId(anyLong())).thenAnswer(\n            (invocation) -> dstRecordingIds.get(invocation.<Long>getArgument(0)));\n\n        try (RecordingLog recordingLog = spy(new RecordingLog(clusterDir, true)))\n        {\n            recordingLog.appendSnapshot(1, 0, 0, logPositionOldest, 1_000_000_000L, SERVICE_ID);\n            recordingLog.appendSnapshot(2, 0, 0, logPositionOldest, 1_000_000_000L, 0);\n\n            recordingLog.appendStandbySnapshot(1, 0, 0, logPositionNewest, 1_000_000_000L, SERVICE_ID, endpoint0);\n            recordingLog.appendStandbySnapshot(2, 0, 0, logPositionNewest, 1_000_000_000L, 0, endpoint0);\n\n            recordingLog.appendStandbySnapshot(1, 0, 0, 2000L, 1_000_000_000L, SERVICE_ID, endpoint1);\n            recordingLog.appendStandbySnapshot(2, 0, 0, 2000L, 1_000_000_000L, 0, endpoint1);\n\n            final long nowNs = 2_000_000_000L;\n\n            try (MockedStatic<MultipleRecordingReplication> staticMockReplication = mockStatic(\n                MultipleRecordingReplication.class);\n                MockedStatic<AeronArchive> staticMockArchive = mockStatic(AeronArchive.class))\n            {\n                staticMockReplication\n                    .when(() -> MultipleRecordingReplication.newInstance(\n                        any(), anyInt(), any(), any(), anyLong(), anyLong()))\n                    .thenReturn(mockMultipleRecordingReplication0);\n                staticMockArchive.when(() -> AeronArchive.connect(any())).thenReturn(mockArchive);\n\n                final StandbySnapshotReplicator standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n                    memberId,\n                    ctx,\n                    recordingLog,\n                    serviceCount,\n                    archiveControlChannel,\n                    archiveControlStreamId,\n                    replicationChannel,\n                    fileSyncLevel,\n                    snapshotCounter);\n\n                when(mockMultipleRecordingReplication0.isComplete()).thenReturn(true);\n\n                assertNotEquals(0, standbySnapshotReplicator.poll(nowNs));\n                assertTrue(standbySnapshotReplicator.isComplete());\n\n                verify(mockArchive).pollForRecordingSignals();\n\n                staticMockReplication.verify(() -> MultipleRecordingReplication.newInstance(\n                    eq(mockArchive),\n                    eq(archiveControlStreamId),\n                    contains(endpoint0),\n                    eq(replicationChannel),\n                    eq(progressTimeoutNs),\n                    eq(intervalTimeoutNs)));\n\n                verify(mockMultipleRecordingReplication0).addRecording(1L, NULL_RECORDING_ID, NULL_POSITION);\n                verify(mockMultipleRecordingReplication0).addRecording(2L, NULL_RECORDING_ID, NULL_POSITION);\n                verify(mockMultipleRecordingReplication0).poll(nowNs);\n                verify(recordingLog).force(fileSyncLevel);\n                verify(snapshotCounter).incrementRelease();\n            }\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            for (int serviceId = -1; serviceId < serviceCount; serviceId++)\n            {\n                final RecordingLog.Entry latestSnapshot = recordingLog.getLatestSnapshot(serviceId);\n\n                assertNotNull(latestSnapshot);\n                assertEquals(RecordingLog.ENTRY_TYPE_SNAPSHOT, latestSnapshot.type);\n                assertEquals(logPositionNewest, latestSnapshot.logPosition);\n                assertEquals(12 + serviceId, latestSnapshot.recordingId);\n            }\n        }\n    }\n\n    @Test\n    void shouldPassSignalsToRecordingReplication()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true);\n            MockedStatic<MultipleRecordingReplication> staticMockReplication = mockStatic(\n                MultipleRecordingReplication.class);\n            MockedStatic<AeronArchive> staticMockArchive = mockStatic(AeronArchive.class))\n        {\n            recordingLog.appendStandbySnapshot(1, 0, 0, 1000, 1_000_000_000L, SERVICE_ID, endpoint0);\n            recordingLog.appendStandbySnapshot(2, 0, 0, 1000, 1_000_000_000L, 0, endpoint0);\n\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), any(), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication0);\n            staticMockArchive.when(() -> AeronArchive.connect(any())).thenReturn(mockArchive);\n\n            final StandbySnapshotReplicator standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n                memberId,\n                ctx,\n                recordingLog,\n                1,\n                archiveControlChannel,\n                archiveControlStreamId,\n                replicationChannel,\n                fileSyncLevel,\n                snapshotCounter);\n\n            standbySnapshotReplicator.poll(0);\n            verify(mockArchive).pollForRecordingSignals();\n            standbySnapshotReplicator.onSignal(2, 11, 23, 29, 37, RecordingSignal.START);\n\n            verify(mockMultipleRecordingReplication0).onSignal(11, 23, 37, RecordingSignal.START);\n            verifyNoInteractions(snapshotCounter);\n        }\n    }\n\n    @Test\n    void shouldHandleNoStandbySnapshots()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true);\n            MockedStatic<MultipleRecordingReplication> staticMockReplication = mockStatic(\n                MultipleRecordingReplication.class);\n            MockedStatic<AeronArchive> staticMockArchive = mockStatic(AeronArchive.class))\n        {\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), any(), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication0);\n            staticMockArchive.when(() -> AeronArchive.connect(any())).thenReturn(mockArchive);\n\n            final StandbySnapshotReplicator standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n                memberId,\n                ctx,\n                recordingLog,\n                1,\n                archiveControlChannel,\n                archiveControlStreamId,\n                replicationChannel,\n                fileSyncLevel, snapshotCounter);\n\n            standbySnapshotReplicator.poll(0);\n            assertTrue(standbySnapshotReplicator.isComplete());\n            verifyNoInteractions(snapshotCounter);\n        }\n    }\n\n    @Test\n    void shouldSwitchEndpointsOnMultipleReplicationException()\n    {\n        final long standbySnapshotLogPosition = 10_000L;\n        final long localSnapshotLogPosition = 2_000L;\n        final long nowNs = 1_000_000_000L;\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            recordingLog.appendSnapshot(1, 0, 0, localSnapshotLogPosition, 1_000_000_000L, SERVICE_ID);\n            recordingLog.appendSnapshot(2, 0, 0, localSnapshotLogPosition, 1_000_000_000L, 0);\n\n            recordingLog.appendStandbySnapshot(\n                1, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, SERVICE_ID, endpoint0);\n            recordingLog.appendStandbySnapshot(2, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, 0, endpoint0);\n\n            recordingLog.appendStandbySnapshot(\n                1, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, SERVICE_ID, endpoint1);\n            recordingLog.appendStandbySnapshot(2, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, 0, endpoint1);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true);\n            MockedStatic<MultipleRecordingReplication> staticMockReplication = mockStatic(\n                MultipleRecordingReplication.class);\n            MockedStatic<AeronArchive> staticMockArchive = mockStatic(AeronArchive.class))\n        {\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), contains(\"host0\"), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication0);\n\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), contains(\"host1\"), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication1);\n\n            staticMockArchive.when(() -> AeronArchive.connect(any())).thenReturn(mockArchive);\n\n            final StandbySnapshotReplicator standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n                memberId,\n                ctx,\n                recordingLog,\n                1,\n                archiveControlChannel,\n                archiveControlStreamId,\n                replicationChannel,\n                fileSyncLevel,\n                snapshotCounter);\n\n            when(mockMultipleRecordingReplication0.poll(anyLong())).thenThrow(new ClusterException(\"fail\"));\n            when(mockMultipleRecordingReplication1.isComplete()).thenReturn(true);\n\n            standbySnapshotReplicator.poll(nowNs);\n            standbySnapshotReplicator.poll(nowNs);\n\n            assertTrue(standbySnapshotReplicator.isComplete());\n\n            verify(mockMultipleRecordingReplication0).poll(anyLong());\n            verify(mockMultipleRecordingReplication1).poll(anyLong());\n            verify(snapshotCounter).incrementRelease();\n        }\n    }\n\n    @Test\n    void shouldSwitchEndpointsOnArchivePollForSignalsException()\n    {\n        final long standbySnapshotLogPosition = 10_000L;\n        final long localSnapshotLogPosition = 2_000L;\n        final long nowNs = 1_000_000_000L;\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            recordingLog.appendSnapshot(1, 0, 0, localSnapshotLogPosition, 1_000_000_000L, SERVICE_ID);\n            recordingLog.appendSnapshot(2, 0, 0, localSnapshotLogPosition, 1_000_000_000L, 0);\n\n            recordingLog.appendStandbySnapshot(\n                1, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, SERVICE_ID, endpoint0);\n            recordingLog.appendStandbySnapshot(2, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, 0, endpoint0);\n\n            recordingLog.appendStandbySnapshot(\n                1, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, SERVICE_ID, endpoint1);\n            recordingLog.appendStandbySnapshot(2, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, 0, endpoint1);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true);\n            MockedStatic<MultipleRecordingReplication> staticMockReplication = mockStatic(\n                MultipleRecordingReplication.class);\n            MockedStatic<AeronArchive> staticMockArchive = mockStatic(AeronArchive.class))\n        {\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), contains(\"host0\"), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication0);\n\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), contains(\"host1\"), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication1);\n\n            staticMockArchive.when(() -> AeronArchive.connect(any())).thenReturn(mockArchive);\n            when(mockArchive.pollForRecordingSignals()).thenThrow(new ArchiveException(\"fail\")).thenReturn(1);\n\n            final StandbySnapshotReplicator standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n                memberId,\n                ctx,\n                recordingLog,\n                1,\n                archiveControlChannel,\n                archiveControlStreamId,\n                replicationChannel,\n                fileSyncLevel,\n                snapshotCounter);\n\n            when(mockMultipleRecordingReplication1.isComplete()).thenReturn(true);\n\n            standbySnapshotReplicator.poll(nowNs);\n            standbySnapshotReplicator.poll(nowNs);\n\n            assertTrue(standbySnapshotReplicator.isComplete());\n\n            verify(mockMultipleRecordingReplication0).poll(anyLong());\n            verify(mockMultipleRecordingReplication1).poll(anyLong());\n            verify(snapshotCounter).incrementRelease();\n        }\n    }\n\n    @Test\n    void shouldThrowExceptionIfUnableToReplicateAnySnapshotsDueToClusterExceptions()\n    {\n        final long standbySnapshotLogPosition = 10_000L;\n        final long localSnapshotLogPosition = 2_000L;\n        final long nowNs = 1_000_000_000L;\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            recordingLog.appendSnapshot(1, 0, 0, localSnapshotLogPosition, 1_000_000_000L, SERVICE_ID);\n            recordingLog.appendSnapshot(2, 0, 0, localSnapshotLogPosition, 1_000_000_000L, 0);\n\n            recordingLog.appendStandbySnapshot(\n                1, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, SERVICE_ID, endpoint0);\n            recordingLog.appendStandbySnapshot(2, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, 0, endpoint0);\n\n            recordingLog.appendStandbySnapshot(\n                1, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, SERVICE_ID, endpoint1);\n            recordingLog.appendStandbySnapshot(2, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, 0, endpoint1);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true);\n            MockedStatic<MultipleRecordingReplication> staticMockReplication = mockStatic(\n                MultipleRecordingReplication.class);\n            MockedStatic<AeronArchive> staticMockArchive = mockStatic(AeronArchive.class))\n        {\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), contains(\"host0\"), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication0);\n\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), contains(\"host1\"), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication1);\n\n            staticMockArchive.when(() -> AeronArchive.connect(any())).thenReturn(mockArchive);\n\n            when(mockMultipleRecordingReplication0.poll(anyLong())).thenThrow(new ClusterException(\"fail\"));\n            when(mockMultipleRecordingReplication1.poll(anyLong())).thenThrow(new ClusterException(\"fail\"));\n\n            final StandbySnapshotReplicator standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n                memberId,\n                ctx,\n                recordingLog,\n                1,\n                archiveControlChannel,\n                archiveControlStreamId,\n                replicationChannel,\n                fileSyncLevel,\n                snapshotCounter);\n\n            standbySnapshotReplicator.poll(nowNs);\n            standbySnapshotReplicator.poll(nowNs);\n            assertThrows(ClusterException.class, () -> standbySnapshotReplicator.poll(nowNs));\n\n            verify(mockMultipleRecordingReplication0).poll(anyLong());\n            verify(mockMultipleRecordingReplication1).poll(anyLong());\n            verifyNoInteractions(snapshotCounter);\n        }\n    }\n\n    @Test\n    void shouldThrowExceptionIfUnableToReplicateAnySnapshotsDueToArchiveExceptions()\n    {\n        final long standbySnapshotLogPosition = 10_000L;\n        final long localSnapshotLogPosition = 2_000L;\n        final long nowNs = 1_000_000_000L;\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            recordingLog.appendSnapshot(1, 0, 0, localSnapshotLogPosition, 1_000_000_000L, SERVICE_ID);\n            recordingLog.appendSnapshot(2, 0, 0, localSnapshotLogPosition, 1_000_000_000L, 0);\n\n            recordingLog.appendStandbySnapshot(\n                1, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, SERVICE_ID, endpoint0);\n            recordingLog.appendStandbySnapshot(2, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, 0, endpoint0);\n\n            recordingLog.appendStandbySnapshot(\n                1, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, SERVICE_ID, endpoint1);\n            recordingLog.appendStandbySnapshot(2, 0, 0, standbySnapshotLogPosition, 1_000_000_000L, 0, endpoint1);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true);\n            MockedStatic<MultipleRecordingReplication> staticMockReplication = mockStatic(\n                MultipleRecordingReplication.class);\n            MockedStatic<AeronArchive> staticMockArchive = mockStatic(AeronArchive.class))\n        {\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), contains(\"host0\"), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication0);\n\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), contains(\"host1\"), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication1);\n\n            staticMockArchive.when(() -> AeronArchive.connect(any())).thenReturn(mockArchive);\n            when(mockArchive.pollForRecordingSignals()).thenThrow(new ArchiveException(\"fail\"));\n\n            final StandbySnapshotReplicator standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n                memberId,\n                ctx,\n                recordingLog,\n                1,\n                archiveControlChannel,\n                archiveControlStreamId,\n                replicationChannel,\n                fileSyncLevel,\n                snapshotCounter);\n\n            standbySnapshotReplicator.poll(nowNs);\n            standbySnapshotReplicator.poll(nowNs);\n            assertThrows(ClusterException.class, () -> standbySnapshotReplicator.poll(nowNs));\n\n            verify(mockMultipleRecordingReplication0).poll(anyLong());\n            verify(mockMultipleRecordingReplication1).poll(anyLong());\n            verifyNoInteractions(snapshotCounter);\n        }\n    }\n\n    @Test\n    void shouldNotRetrieveSnapshotsIfRecordingLogAlreadyHasUpToDateCopies()\n    {\n        final long logPosition = 1001234;\n        final long nowNs = 1_000_000_000L;\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            recordingLog.appendSnapshot(1, 0, 0, logPosition, 1_000_000_000L, SERVICE_ID);\n            recordingLog.appendSnapshot(2, 0, 0, logPosition, 1_000_000_000L, 0);\n\n            recordingLog.appendStandbySnapshot(1, 0, 0, logPosition, 1_000_000_000L, SERVICE_ID, endpoint0);\n            recordingLog.appendStandbySnapshot(2, 0, 0, logPosition, 1_000_000_000L, 0, endpoint0);\n        }\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true);\n            MockedStatic<MultipleRecordingReplication> staticMockReplication = mockStatic(\n                MultipleRecordingReplication.class);\n            MockedStatic<AeronArchive> staticMockArchive = mockStatic(AeronArchive.class))\n        {\n            staticMockReplication\n                .when(() -> MultipleRecordingReplication.newInstance(\n                    any(), anyInt(), any(), any(), anyLong(), anyLong()))\n                .thenReturn(mockMultipleRecordingReplication0);\n\n            staticMockArchive.when(() -> AeronArchive.connect(any())).thenReturn(mockArchive);\n\n            final StandbySnapshotReplicator standbySnapshotReplicator = StandbySnapshotReplicator.newInstance(\n                memberId,\n                ctx,\n                recordingLog,\n                1,\n                archiveControlChannel,\n                archiveControlStreamId,\n                replicationChannel,\n                fileSyncLevel,\n                snapshotCounter);\n\n            standbySnapshotReplicator.poll(nowNs);\n            standbySnapshotReplicator.poll(nowNs);\n\n            verify(mockMultipleRecordingReplication0, never()).poll(anyLong());\n            verifyNoInteractions(snapshotCounter);\n        }\n    }\n}"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/WheelTimerServiceClusterTimeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport java.util.concurrent.TimeUnit;\n\nclass WheelTimerServiceClusterTimeTest extends ClusterTimerTest\n{\n    TimerServiceSupplier timerServiceSupplier()\n    {\n        return new WheelTimerServiceSupplier(TimeUnit.MILLISECONDS, 0, 8, 128);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/client/AeronClusterAsyncConnectTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.ConcurrentPublication;\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.SessionEventEncoder;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.security.NullCredentialsSupplier;\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.NoOpIdleStrategy;\nimport org.agrona.concurrent.SystemNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\nimport org.mockito.stubbing.Answer;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.cluster.client.AeronCluster.AsyncConnect.State.AWAIT_PUBLICATION_CONNECTED;\nimport static io.aeron.cluster.client.AeronCluster.AsyncConnect.State.CONCLUDE_CONNECT;\nimport static io.aeron.cluster.client.AeronCluster.AsyncConnect.State.CREATE_EGRESS_SUBSCRIPTION;\nimport static io.aeron.cluster.client.AeronCluster.AsyncConnect.State.CREATE_INGRESS_PUBLICATIONS;\nimport static io.aeron.cluster.client.AeronCluster.AsyncConnect.State.POLL_RESPONSE;\nimport static io.aeron.cluster.client.AeronCluster.AsyncConnect.State.SEND_MESSAGE;\nimport static io.aeron.protocol.DataHeaderFlyweight.BEGIN_AND_END_FLAGS;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.atMostOnce;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.only;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass AeronClusterAsyncConnectTest\n{\n    private final Aeron aeron = mock(Aeron.class);\n    private final Aeron.Context aeronContext = new Aeron.Context().nanoClock(SystemNanoClock.INSTANCE);\n    private final AeronCluster.Context context = spy(new AeronCluster.Context()\n        .aeron(aeron)\n        .ownsAeronClient(false)\n        .egressChannel(\"aeron:udp?endpoint=localhost:0\")\n        .egressStreamId(42)\n        .ingressChannel(\"aeron:udp?endpoint=replace-me:5555\")\n        .ingressStreamId(-19)\n        .credentialsSupplier(new NullCredentialsSupplier()))\n        .idleStrategy(NoOpIdleStrategy.INSTANCE);\n\n    @BeforeEach\n    void before()\n    {\n        when(aeron.context()).thenReturn(aeronContext);\n    }\n\n    @Test\n    public void initialState()\n    {\n        final AeronCluster.AsyncConnect asyncConnect = new AeronCluster.AsyncConnect(context, 1);\n        assertEquals(CREATE_EGRESS_SUBSCRIPTION, asyncConnect.state());\n        assertEquals(CREATE_EGRESS_SUBSCRIPTION.step, asyncConnect.step());\n    }\n\n    @Test\n    public void shouldCloseAsyncSubscription()\n    {\n        final long subscriptionId = 999;\n        when(aeron.asyncAddSubscription(context.egressChannel(), context.egressStreamId())).thenReturn(subscriptionId);\n\n        final AeronCluster.AsyncConnect asyncConnect = new AeronCluster.AsyncConnect(\n            context, aeronContext.nanoClock().nanoTime() + TimeUnit.HOURS.toNanos(1));\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CREATE_EGRESS_SUBSCRIPTION, asyncConnect.state());\n\n        asyncConnect.close();\n        final InOrder inOrder = inOrder(aeron, context);\n        inOrder.verify(aeron).context();\n        inOrder.verify(aeron).asyncAddSubscription(context.egressChannel(), context.egressStreamId());\n        inOrder.verify(aeron).getSubscription(subscriptionId);\n        inOrder.verify(aeron).asyncRemoveSubscription(subscriptionId);\n        inOrder.verify(context).close();\n    }\n\n    @Test\n    public void shouldCloseEgressSubscription()\n    {\n        final long subscriptionId = -4343;\n        when(aeron.asyncAddSubscription(context.egressChannel(), context.egressStreamId())).thenReturn(subscriptionId);\n        final Subscription subscription = mock(Subscription.class);\n        when(aeron.getSubscription(subscriptionId)).thenReturn(subscription);\n\n        final AeronCluster.AsyncConnect asyncConnect = new AeronCluster.AsyncConnect(\n            context, aeronContext.nanoClock().nanoTime() + TimeUnit.HOURS.toNanos(1));\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CREATE_INGRESS_PUBLICATIONS, asyncConnect.state());\n\n        asyncConnect.close();\n        verify(subscription, only()).close();\n        verify(context).close();\n        verify(aeron, never()).asyncRemoveSubscription(subscriptionId);\n    }\n\n    @Test\n    public void shouldCloseAsyncPublication()\n    {\n        final long subscriptionId = 87;\n        when(aeron.asyncAddSubscription(context.egressChannel(), context.egressStreamId())).thenReturn(subscriptionId);\n        final Subscription subscription = mock(Subscription.class);\n        when(aeron.getSubscription(subscriptionId)).thenReturn(subscription);\n\n        context.isIngressExclusive(true);\n        final long publicationId = Long.MAX_VALUE;\n        when(aeron.asyncAddExclusivePublication(context.ingressChannel(), context.ingressStreamId()))\n            .thenReturn(publicationId);\n\n        final AeronCluster.AsyncConnect asyncConnect = new AeronCluster.AsyncConnect(\n            context, aeronContext.nanoClock().nanoTime() + TimeUnit.HOURS.toNanos(1));\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CREATE_INGRESS_PUBLICATIONS, asyncConnect.state());\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CREATE_INGRESS_PUBLICATIONS, asyncConnect.state());\n\n        asyncConnect.close();\n        final InOrder inOrder = inOrder(aeron, subscription, context);\n        inOrder.verify(aeron).context();\n        inOrder.verify(aeron).asyncAddSubscription(context.egressChannel(), context.egressStreamId());\n        inOrder.verify(aeron).getSubscription(subscriptionId);\n        inOrder.verify(aeron).asyncAddExclusivePublication(context.ingressChannel(), context.ingressStreamId());\n        inOrder.verify(aeron).getExclusivePublication(publicationId);\n        inOrder.verify(aeron).asyncRemovePublication(publicationId);\n        inOrder.verify(subscription).close();\n        inOrder.verify(context).close();\n    }\n\n    @Test\n    public void shouldCloseIngressPublication()\n    {\n        final long subscriptionId = 42;\n        when(aeron.asyncAddSubscription(context.egressChannel(), context.egressStreamId())).thenReturn(subscriptionId);\n        final Subscription subscription = mock(Subscription.class);\n        when(aeron.getSubscription(subscriptionId)).thenReturn(subscription);\n\n        context.isIngressExclusive(false);\n        final long publicationId = -6342756432L;\n        when(aeron.asyncAddPublication(context.ingressChannel(), context.ingressStreamId())).thenReturn(publicationId);\n        final ConcurrentPublication publication = mock(ConcurrentPublication.class);\n        when(aeron.getPublication(publicationId)).thenReturn(publication);\n\n        final AeronCluster.AsyncConnect asyncConnect = new AeronCluster.AsyncConnect(\n            context, aeronContext.nanoClock().nanoTime() + TimeUnit.HOURS.toNanos(1));\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CREATE_INGRESS_PUBLICATIONS, asyncConnect.state());\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CREATE_INGRESS_PUBLICATIONS, asyncConnect.state());\n\n        assertNull(asyncConnect.poll());\n        assertEquals(AWAIT_PUBLICATION_CONNECTED, asyncConnect.state());\n\n        asyncConnect.close();\n        verify(publication, only()).close();\n        verify(subscription, only()).close();\n        verify(context).close();\n        verify(aeron, never()).asyncRemovePublication(publicationId);\n        verify(aeron, never()).asyncRemoveSubscription(subscriptionId);\n    }\n\n    @Test\n    public void shouldCloseIngressPublicationsOnMembers()\n    {\n        final long subscriptionId = 42;\n        when(aeron.asyncAddSubscription(context.egressChannel(), context.egressStreamId())).thenReturn(subscriptionId);\n        final Subscription subscription = mock(Subscription.class);\n        when(aeron.getSubscription(subscriptionId)).thenReturn(subscription);\n\n        final int insgressStreamId = 878;\n        context\n            .isIngressExclusive(true)\n            .ingressEndpoints(\"0=localhost:20000,1=localhost:20001,2=localhost:20002\")\n            .ingressStreamId(insgressStreamId);\n        final long publicationId1 = -6342756432L;\n        final ExclusivePublication publication1 = mock(ExclusivePublication.class);\n        when(aeron.asyncAddExclusivePublication(\"aeron:udp?endpoint=localhost:20000\", insgressStreamId))\n            .thenReturn(publicationId1);\n        when(aeron.getExclusivePublication(publicationId1)).thenReturn(null, publication1);\n        final long publicationId2 = Aeron.NULL_VALUE;\n        when(aeron.asyncAddExclusivePublication(\"aeron:udp?endpoint=localhost:20001\", insgressStreamId))\n            .thenReturn(publicationId2);\n        final long publicationId3 = 573495;\n        when(aeron.asyncAddExclusivePublication(\"aeron:udp?endpoint=localhost:20002\", insgressStreamId))\n            .thenReturn(publicationId3);\n\n        final AeronCluster.AsyncConnect asyncConnect = new AeronCluster.AsyncConnect(\n            context, aeronContext.nanoClock().nanoTime() + TimeUnit.HOURS.toNanos(1));\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CREATE_INGRESS_PUBLICATIONS, asyncConnect.state());\n\n        final int iterations = 10;\n        for (int i = 0; i < iterations; i++)\n        {\n            assertNull(asyncConnect.poll());\n            assertEquals(CREATE_INGRESS_PUBLICATIONS, asyncConnect.state());\n        }\n\n        verify(aeron, atMostOnce())\n            .asyncAddExclusivePublication(\"aeron:udp?endpoint=localhost:20000\", insgressStreamId);\n        verify(aeron, times(iterations))\n            .asyncAddExclusivePublication(\"aeron:udp?endpoint=localhost:20001\", insgressStreamId);\n        verify(aeron, atMostOnce())\n            .asyncAddExclusivePublication(\"aeron:udp?endpoint=localhost:20002\", insgressStreamId);\n        verify(aeron, times(2)).getExclusivePublication(publicationId1);\n        verify(aeron, times(iterations)).getExclusivePublication(publicationId2);\n        verify(aeron, times(iterations)).getExclusivePublication(publicationId3);\n\n        asyncConnect.close();\n\n        verify(subscription, only()).close();\n        verify(publication1, only()).close();\n        verify(context).close();\n        verify(aeron, atMostOnce()).asyncRemovePublication(publicationId3);\n        verify(aeron, never()).asyncRemoveSubscription(subscriptionId);\n        verify(aeron, never()).asyncRemovePublication(publicationId1);\n        verify(aeron, never()).asyncRemovePublication(publicationId2);\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    public void shouldConnectViaIngressChannel()\n    {\n        final long subscriptionId = 42;\n        final MutableLong correlationId = new MutableLong();\n        when(aeron.nextCorrelationId()).thenAnswer((invocation) -> correlationId.incrementAndGet());\n        when(aeron.asyncAddSubscription(context.egressChannel(), context.egressStreamId())).thenReturn(subscriptionId);\n        final Subscription subscription = mock(Subscription.class);\n        when(aeron.getSubscription(subscriptionId)).thenReturn(subscription);\n\n        context.isIngressExclusive(false);\n        final long publicationId = -19L;\n        when(aeron.asyncAddPublication(context.ingressChannel(), context.ingressStreamId())).thenReturn(publicationId);\n        final ConcurrentPublication publication = mock(ConcurrentPublication.class);\n        when(aeron.getPublication(publicationId)).thenReturn(publication);\n\n        final AeronCluster.AsyncConnect asyncConnect = new AeronCluster.AsyncConnect(\n            context, aeronContext.nanoClock().nanoTime() + TimeUnit.HOURS.toNanos(1));\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CREATE_INGRESS_PUBLICATIONS, asyncConnect.state());\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CREATE_INGRESS_PUBLICATIONS, asyncConnect.state());\n\n        assertNull(asyncConnect.poll());\n        assertEquals(AWAIT_PUBLICATION_CONNECTED, asyncConnect.state());\n\n        final String responseChannel = \"aeron:udp?endpoint=localhost:8888\";\n        when(subscription.tryResolveChannelEndpointPort()).thenReturn(responseChannel);\n        when(publication.isConnected()).thenReturn(true);\n\n        assertNull(asyncConnect.poll());\n        assertEquals(SEND_MESSAGE, asyncConnect.state());\n        final long sendMessageCorrelationId = correlationId.get();\n\n        when(publication.offer(any(DirectBuffer.class), eq(0), anyInt())).thenReturn(8L);\n\n        assertNull(asyncConnect.poll());\n        assertEquals(POLL_RESPONSE, asyncConnect.state());\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(new byte[256]);\n        final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n        final SessionEventEncoder sessionEventEncoder = new SessionEventEncoder();\n        final long clusterSessionId = 888;\n        final long leadershipTermId = 5;\n        final int leaderMemberId = 2;\n        sessionEventEncoder\n            .wrapAndApplyHeader(buffer, HEADER_LENGTH, headerEncoder)\n            .clusterSessionId(clusterSessionId)\n            .correlationId(sendMessageCorrelationId)\n            .leadershipTermId(leadershipTermId)\n            .leaderMemberId(leaderMemberId)\n            .code(EventCode.OK)\n            .version(ConsensusModule.Configuration.PROTOCOL_SEMANTIC_VERSION)\n            .leaderHeartbeatTimeoutNs(SessionEventEncoder.leaderHeartbeatTimeoutNsNullValue())\n            .detail(\"you are now connected\");\n        final Image egressImage = mock(Image.class);\n        final Header header = new Header(1, 16, egressImage);\n        header.buffer(buffer).offset(0);\n        final DataHeaderFlyweight headerFlyweight = new DataHeaderFlyweight();\n        headerFlyweight.wrap(buffer, 0, HEADER_LENGTH);\n        headerFlyweight.flags(BEGIN_AND_END_FLAGS);\n        when(subscription.controlledPoll(any(ControlledFragmentHandler.class), anyInt()))\n            .thenAnswer((Answer<Integer>)invocation ->\n            {\n                final ControlledFragmentAssembler assembler = invocation.getArgument(0);\n                assembler.onFragment(buffer, HEADER_LENGTH, sessionEventEncoder.encodedLength(), header);\n                return 1;\n            });\n\n        assertNull(asyncConnect.poll());\n        assertEquals(CONCLUDE_CONNECT, asyncConnect.state());\n\n        final AeronCluster aeronCluster = asyncConnect.poll();\n        assertNotNull(aeronCluster);\n        assertSame(publication, aeronCluster.ingressPublication());\n        assertSame(subscription, aeronCluster.egressSubscription());\n        assertEquals(leadershipTermId, aeronCluster.leadershipTermId());\n        assertEquals(leaderMemberId, aeronCluster.leaderMemberId());\n        assertEquals(clusterSessionId, aeronCluster.clusterSessionId());\n        assertEquals(\n            2 * ConsensusModule.Configuration.LEADER_HEARTBEAT_TIMEOUT_DEFAULT_NS,\n            context.newLeaderTimeoutNs());\n\n        asyncConnect.close();\n        verify(publication, never()).close();\n        verify(subscription, never()).close();\n\n        when(publication.tryClaim(anyInt(), any(BufferClaim.class))).thenAnswer((invocation) ->\n        {\n            final int length = invocation.getArgument(0);\n            final BufferClaim bufferClaim = invocation.getArgument(1);\n            bufferClaim.wrap(buffer, 0, BitUtil.align(HEADER_LENGTH + length, HEADER_LENGTH));\n            return 42L;\n        });\n        aeronCluster.close();\n        assertTrue(aeronCluster.isClosed());\n        final InOrder inOrder = inOrder(context, subscription, publication);\n        inOrder.verify(publication).tryClaim(anyInt(), any(BufferClaim.class));\n        inOrder.verify(subscription).close();\n        inOrder.verify(publication).close();\n        inOrder.verify(context).close();\n        inOrder.verifyNoMoreInteractions();\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/client/AeronClusterContextTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.test.Tests;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass AeronClusterContextTest\n{\n    private final Aeron aeron = mock(Aeron.class);\n    private final Aeron.Context aeronContext = new Aeron.Context();\n    private final AeronCluster.Context context = new AeronCluster.Context();\n\n    @BeforeEach\n    void before()\n    {\n        when(aeron.context()).thenReturn(aeronContext);\n        aeronContext.subscriberErrorHandler(RethrowingErrorHandler.INSTANCE);\n\n        context\n            .aeron(aeron)\n            .ingressChannel(\"aeron:udp\")\n            .egressChannel(\"aeron:udp?endpoint=localhost:0\");\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void concludeThrowsConfigurationExceptionIfIngressChannelIsNotSet(final String ingressChannel)\n    {\n        context.ingressChannel(ingressChannel);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\"ERROR - ingressChannel must be specified\", exception.getMessage());\n    }\n\n    @Test\n    void concludeThrowsConfigurationExceptionIfIngressChannelIsSetToIpcAndIngressEndpointsSpecified()\n    {\n        context\n            .ingressChannel(\"aeron:ipc\")\n            .ingressEndpoints(\"0,localhost:1234\");\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - AeronCluster.Context ingressEndpoints must be null when using IPC ingress\",\n            exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void concludeThrowsConfigurationExceptionIfEgressChannelIsNotSet(final String egressChannel)\n    {\n        context.egressChannel(egressChannel);\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\"ERROR - egressChannel must be specified\", exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void clientNameShouldHandleEmptyValue(final String clientName)\n    {\n        context.clientName(clientName);\n        assertEquals(\"\", context.clientName());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"test\", \"Some other name\" })\n    void clientNameShouldReturnAssignedValue(final String clientName)\n    {\n        context.clientName(clientName);\n        assertEquals(clientName, context.clientName());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"some\", \"42\" })\n    void clientNameCanBeSetViaSystemProperty(final String clientName)\n    {\n        System.setProperty(AeronCluster.Configuration.CLIENT_NAME_PROP_NAME, clientName);\n        try\n        {\n            assertEquals(clientName, new AeronCluster.Context().clientName());\n        }\n        finally\n        {\n            System.clearProperty(AeronCluster.Configuration.CLIENT_NAME_PROP_NAME);\n        }\n    }\n\n    @Test\n    void clientNameMustNotExceedMaxLength()\n    {\n        context.clientName(Tests.generateStringWithSuffix(\"test\", \"x\", Aeron.Configuration.MAX_CLIENT_NAME_LENGTH));\n\n        final ConfigurationException exception =\n            assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - AeronCluster.Context.clientName length must be <= \" + Aeron.Configuration.MAX_CLIENT_NAME_LENGTH,\n            exception.getMessage());\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/client/AeronClusterTest.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.Aeron;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.Subscription;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.NewLeaderEventEncoder;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.concurrent.NoOpIdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Stream;\n\nimport static org.agrona.BitUtil.align;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.isNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass AeronClusterTest\n{\n    private static final String INGRESS_ENDPOINTS = \"foo:1000,bar:1000,baz:1000\";\n    private static final int CLUSTER_SESSION_ID = 123;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[1024]);\n    private final UnsafeBuffer appMessage = new UnsafeBuffer(new byte[8]);\n    private final EgressListener egressListener = mock(EgressListener.class);\n    private final Aeron aeron = mock(Aeron.class);\n    private final Aeron.Context aeronContext = new Aeron.Context()\n        .nanoClock(this::nanoTime)\n        .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE);\n    private final AeronCluster.Context context = spy(new AeronCluster.Context()\n        .aeron(aeron)\n        .ownsAeronClient(false)\n        .egressChannel(\"aeron:udp?endpoint=localhost:0\")\n        .ingressChannel(\"aeron:udp\")\n        .idleStrategy(NoOpIdleStrategy.INSTANCE)\n        .egressListener(egressListener))\n        .newLeaderTimeoutNs(TimeUnit.SECONDS.toNanos(1));\n    private final ExclusivePublication ingressPublication = mock(ExclusivePublication.class);\n    private final Subscription egressSubscription = mock(Subscription.class);\n    private final Image egressImage = mock(Image.class);\n    private AeronCluster aeronCluster;\n    private long nanoTime;\n    private int leadershipTermId = 2;\n    private int leaderMemberId = 1;\n    private boolean newLeaderEventPending;\n\n    @BeforeEach\n    void setUp()\n    {\n        when(aeron.context()).thenReturn(aeronContext);\n        final long ingressPublicationRegistrationId = 42L;\n        when(ingressPublication.registrationId()).thenReturn(ingressPublicationRegistrationId);\n        when(aeron.asyncAddExclusivePublication(context.ingressChannel(), context.ingressStreamId()))\n            .thenReturn(ingressPublicationRegistrationId);\n        when(aeron.getExclusivePublication(ingressPublicationRegistrationId)).thenReturn(ingressPublication);\n\n        context.conclude();\n\n        when(egressSubscription.poll(any(FragmentHandler.class), anyInt())).thenAnswer(invocation ->\n        {\n            if (newLeaderEventPending)\n            {\n                newLeaderEventPending = false;\n\n                final int offset = DataHeaderFlyweight.HEADER_LENGTH;\n                FrameDescriptor.frameFlags(buffer, 0, FrameDescriptor.UNFRAGMENTED);\n\n                final NewLeaderEventEncoder newLeaderEventEncoder = new NewLeaderEventEncoder();\n                newLeaderEventEncoder.wrapAndApplyHeader(buffer, offset, new MessageHeaderEncoder());\n                newLeaderEventEncoder.clusterSessionId(CLUSTER_SESSION_ID);\n                newLeaderEventEncoder.leadershipTermId(++leadershipTermId);\n                newLeaderEventEncoder.leaderMemberId(++leaderMemberId);\n                newLeaderEventEncoder.ingressEndpoints(INGRESS_ENDPOINTS);\n\n                final int length = MessageHeaderEncoder.ENCODED_LENGTH + newLeaderEventEncoder.encodedLength();\n\n                final Header header = new Header(0, 0, egressImage);\n                header.buffer(buffer);\n\n                final FragmentHandler handler = invocation.getArgument(0, FragmentHandler.class);\n                handler.onFragment(buffer, offset, length, header);\n\n                return 1;\n            }\n\n            return 0;\n        });\n\n        aeronCluster = new AeronCluster(\n            context,\n            new MessageHeaderEncoder(),\n            ingressPublication,\n            egressSubscription,\n            egressImage,\n            new Int2ObjectHashMap<>(),\n            CLUSTER_SESSION_ID,\n            leadershipTermId,\n            leaderMemberId);\n    }\n\n    static Stream<Arguments> shouldStayConnectedAfterSuccessfulFailover()\n    {\n        return Stream.of(\n            arguments(false, false),\n            arguments(false, true),\n            arguments(true, false),\n            arguments(true, true));\n    }\n\n    @ParameterizedTest\n    @MethodSource\n    void shouldStayConnectedAfterSuccessfulFailover(final boolean withIngressDisconnect, final boolean withAppMessages)\n    {\n        final long initialResult = withIngressDisconnect ? Publication.NOT_CONNECTED : 128;\n        makeIngressPublicationReturn(initialResult);\n        if (withAppMessages)\n        {\n            assertEquals(initialResult, aeronCluster.offer(appMessage, 0, 8));\n        }\n        else\n        {\n            assertEquals(!withIngressDisconnect, aeronCluster.sendKeepAlive());\n        }\n\n        nanoTime += context.newLeaderTimeoutNs() - 1;\n\n        makeEgressSubscriptionDeliverNewLeaderEvent();\n        assertEquals(1, aeronCluster.pollEgress());\n        verify(egressListener).onNewLeader(CLUSTER_SESSION_ID, leadershipTermId, leaderMemberId, INGRESS_ENDPOINTS);\n        assertEquals(0, aeronCluster.pollEgress());\n\n        nanoTime += context.messageTimeoutNs() - 1;\n\n        makeIngressPublicationReturn(256);\n        if (withAppMessages)\n        {\n            assertEquals(256, aeronCluster.offer(appMessage, 0, 8));\n        }\n        else\n        {\n            assertTrue(aeronCluster.sendKeepAlive());\n        }\n\n        nanoTime += 1;\n\n        assertEquals(0, aeronCluster.pollEgress());\n        assertFalse(aeronCluster.isClosed());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { false, true })\n    void shouldCloseItselfWhenDisconnectedForLongerThanNewLeaderTimeout(final boolean withAppMessages)\n    {\n        makeIngressPublicationReturn(Publication.NOT_CONNECTED);\n        if (withAppMessages)\n        {\n            assertEquals(Publication.NOT_CONNECTED, aeronCluster.offer(appMessage, 0, 8));\n        }\n        else\n        {\n            assertFalse(aeronCluster.sendKeepAlive());\n        }\n\n        nanoTime += context.newLeaderTimeoutNs() - 1;\n\n        assertEquals(0, aeronCluster.pollEgress());\n        assertFalse(aeronCluster.isClosed());\n\n        nanoTime += 1;\n\n        assertEquals(1, aeronCluster.pollEgress());\n        assertTrue(aeronCluster.isClosed());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { false, true })\n    void shouldCloseItselfWhenUnableToSendMessageForLongerThanNewLeaderConnectionTimeout(final boolean withAppMessages)\n    {\n        makeIngressPublicationReturn(Publication.NOT_CONNECTED);\n        if (withAppMessages)\n        {\n            assertEquals(Publication.NOT_CONNECTED, aeronCluster.offer(appMessage, 0, 8));\n        }\n        else\n        {\n            assertFalse(aeronCluster.sendKeepAlive());\n        }\n\n        nanoTime += context.newLeaderTimeoutNs() / 2;\n\n        makeEgressSubscriptionDeliverNewLeaderEvent();\n\n        assertEquals(1, aeronCluster.pollEgress());\n        assertFalse(aeronCluster.isClosed());\n\n        nanoTime += context.messageTimeoutNs() - 1;\n        if (withAppMessages)\n        {\n            assertEquals(Publication.NOT_CONNECTED, aeronCluster.offer(appMessage, 0, 8));\n        }\n        else\n        {\n            assertFalse(aeronCluster.sendKeepAlive());\n        }\n\n        nanoTime += 1;\n\n        assertEquals(1, aeronCluster.pollEgress());\n        assertTrue(aeronCluster.isClosed());\n    }\n\n    @Test\n    void shouldCloseIngressPublicationWhenEgressImageCloses()\n    {\n        // in CONNECTED state\n        when(egressImage.isClosed()).thenReturn(true);\n        assertEquals(1, aeronCluster.pollEgress());\n        verify(ingressPublication).close();\n\n        when(egressImage.isClosed()).thenReturn(false);\n        makeEgressSubscriptionDeliverNewLeaderEvent();\n        assertEquals(1, aeronCluster.pollEgress());\n        verify(ingressPublication, times(2)).close();\n\n        // and in AWAIT_NEW_LEADER_CONNECTION state too\n        when(egressImage.isClosed()).thenReturn(true);\n        assertEquals(1, aeronCluster.pollEgress());\n        verify(ingressPublication, times(3)).close();\n    }\n\n    @Test\n    void shouldCloseItselfAfterReachingMaxPositionOnTheIngressPublication()\n    {\n        makeIngressPublicationReturn(Publication.MAX_POSITION_EXCEEDED);\n        assertEquals(Publication.MAX_POSITION_EXCEEDED, aeronCluster.offer(appMessage, 0, 8));\n        verify(ingressPublication).close();\n        assertEquals(1, aeronCluster.pollStateChanges());\n        assertTrue(aeronCluster.isClosed());\n    }\n\n    private void makeIngressPublicationReturn(final long result)\n    {\n        if (result > 0)\n        {\n            when(ingressPublication.tryClaim(anyInt(), any(BufferClaim.class))).thenAnswer(invocation ->\n            {\n                int length = invocation.getArgument(0, Integer.class);\n                length = align(DataHeaderFlyweight.HEADER_LENGTH + length, FrameDescriptor.FRAME_ALIGNMENT);\n                final BufferClaim bufferClaim = invocation.getArgument(1, BufferClaim.class);\n                bufferClaim.wrap(buffer, 0, length);\n                return result;\n            });\n        }\n        else\n        {\n            when(ingressPublication.tryClaim(anyInt(), any(BufferClaim.class))).thenReturn(result);\n        }\n\n        when(ingressPublication.offer(\n            any(DirectBuffer.class), anyInt(), anyInt(),\n            any(DirectBuffer.class), anyInt(), anyInt(),\n            isNull())).thenReturn(result);\n    }\n\n    private void makeEgressSubscriptionDeliverNewLeaderEvent()\n    {\n        newLeaderEventPending = true;\n    }\n\n    private long nanoTime()\n    {\n        return nanoTime;\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/client/EgressAdapterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.Subscription;\nimport io.aeron.cluster.codecs.*;\nimport io.aeron.logbuffer.Header;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\n\nimport static io.aeron.cluster.client.AeronCluster.SESSION_HEADER_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.*;\n\nclass EgressAdapterTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[512]);\n    private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n    private final SessionMessageHeaderEncoder sessionMessageHeaderEncoder = new SessionMessageHeaderEncoder();\n    private final SessionEventEncoder sessionEventEncoder = new SessionEventEncoder();\n    private final NewLeaderEventEncoder newLeaderEventEncoder = new NewLeaderEventEncoder();\n    private final AdminResponseEncoder adminResponseEncoder = new AdminResponseEncoder();\n\n    @Test\n    void onFragmentShouldDelegateToEgressListenerOnUnknownSchemaId()\n    {\n        final int schemaId = 17;\n        final int templateId = 19;\n        messageHeaderEncoder\n            .wrap(buffer, 0)\n            .schemaId(schemaId)\n            .templateId(templateId);\n\n        final EgressListenerExtension listenerExtension = mock(EgressListenerExtension.class);\n        final Header header = new Header(0, 0);\n        final EgressAdapter adapter = new EgressAdapter(\n            mock(EgressListener.class), listenerExtension, 0, mock(Subscription.class), 3);\n\n        adapter.onFragment(buffer, 0, MessageHeaderDecoder.ENCODED_LENGTH * 2, header);\n\n        verify(listenerExtension).onExtensionMessage(\n            anyInt(),\n            eq(templateId),\n            eq(schemaId),\n            eq(0),\n            eq(buffer),\n            eq(MessageHeaderDecoder.ENCODED_LENGTH),\n            eq(MessageHeaderDecoder.ENCODED_LENGTH));\n        verifyNoMoreInteractions(listenerExtension);\n    }\n\n    @Test\n    void defaultEgressListenerBehaviourShouldThrowClusterExceptionOnUnknownSchemaId()\n    {\n        final EgressListener listener = (clusterSessionId, timestamp, buffer, offset, length, header) ->\n        {\n        };\n        final EgressAdapter adapter =\n            new EgressAdapter(listener, 42, mock(Subscription.class), 5);\n        final ClusterException exception = assertThrows(ClusterException.class,\n            () -> adapter.onFragment(buffer, 0, 64, new Header(0, 0)));\n        assertEquals(\"ERROR - expected schemaId=\" + MessageHeaderDecoder.SCHEMA_ID + \", actual=0\",\n            exception.getMessage());\n\n    }\n\n    @Test\n    void onFragmentShouldInvokeOnMessageCallbackIfSessionIdMatches()\n    {\n        final int offset = 4;\n        final long sessionId = 2973438724L;\n        final long timestamp = -46328746238764832L;\n        sessionMessageHeaderEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .clusterSessionId(sessionId)\n            .timestamp(timestamp);\n\n        final EgressListener egressListener = mock(EgressListener.class);\n        final Header header = new Header(0, 0);\n        final EgressAdapter adapter = new EgressAdapter(egressListener, sessionId, mock(Subscription.class), 3);\n\n        adapter.onFragment(buffer, offset, sessionMessageHeaderEncoder.encodedLength(), header);\n\n        verify(egressListener).onMessage(\n            sessionId,\n            timestamp,\n            buffer,\n            offset + SESSION_HEADER_LENGTH,\n            sessionMessageHeaderEncoder.encodedLength() - SESSION_HEADER_LENGTH, header);\n        verifyNoMoreInteractions(egressListener);\n    }\n\n    @Test\n    void onFragmentIsANoOpIfSessionIdDoesNotMatchOnSessionMessage()\n    {\n        final int offset = 18;\n        final long sessionId = 21;\n        final long timestamp = 1000;\n        sessionMessageHeaderEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .clusterSessionId(sessionId)\n            .timestamp(timestamp);\n\n        final EgressListener egressListener = mock(EgressListener.class);\n        final Header header = new Header(0, 0);\n        final EgressAdapter adapter = new EgressAdapter(egressListener, -19, mock(Subscription.class), 3);\n\n        adapter.onFragment(buffer, offset, sessionMessageHeaderEncoder.encodedLength(), header);\n\n        verifyNoInteractions(egressListener);\n    }\n\n    @Test\n    void onFragmentShouldInvokeOnSessionEventCallbackIfSessionIdMatches()\n    {\n        final int offset = 8;\n        final long clusterSessionId = 42;\n        final long correlationId = 777;\n        final long leadershipTermId = 6;\n        final int leaderMemberId = 3;\n        final EventCode eventCode = EventCode.REDIRECT;\n        final int version = 18;\n        final String eventDetail = \"Event details\";\n        sessionEventEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .clusterSessionId(clusterSessionId)\n            .correlationId(correlationId)\n            .leadershipTermId(leadershipTermId)\n            .leaderMemberId(leaderMemberId)\n            .code(eventCode)\n            .version(version)\n            .detail(eventDetail);\n\n        final EgressListener egressListener = mock(EgressListener.class);\n        final Header header = new Header(1, 3);\n        final EgressAdapter adapter = new EgressAdapter(egressListener, clusterSessionId, mock(Subscription.class), 10);\n\n        adapter.onFragment(buffer, offset, sessionEventEncoder.encodedLength(), header);\n\n        verify(egressListener).onSessionEvent(\n            correlationId, clusterSessionId, leadershipTermId, leaderMemberId, eventCode, eventDetail);\n        verifyNoMoreInteractions(egressListener);\n    }\n\n    @Test\n    void onFragmentIsANoOpIfSessionIdDoesNotMatchOnSessionEvent()\n    {\n        final int offset = 8;\n        final long clusterSessionId = 42;\n        final long correlationId = 777;\n        final long leadershipTermId = 6;\n        final int leaderMemberId = 3;\n        final EventCode eventCode = EventCode.REDIRECT;\n        final int version = 18;\n        final String eventDetail = \"Event details\";\n        sessionEventEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .clusterSessionId(clusterSessionId)\n            .correlationId(correlationId)\n            .leadershipTermId(leadershipTermId)\n            .leaderMemberId(leaderMemberId)\n            .code(eventCode)\n            .version(version)\n            .detail(eventDetail);\n\n        final EgressListener egressListener = mock(EgressListener.class);\n        final Header header = new Header(0, 0);\n        final EgressAdapter adapter = new EgressAdapter(\n            egressListener, clusterSessionId + 1, mock(Subscription.class), 3);\n\n        adapter.onFragment(buffer, offset, sessionEventEncoder.encodedLength(), header);\n\n        verifyNoInteractions(egressListener);\n    }\n\n    @Test\n    void onFragmentShouldInvokeOnNewLeaderCallbackIfSessionIdMatches()\n    {\n        final int offset = 0;\n        final long clusterSessionId = 0;\n        final long leadershipTermId = 6;\n        final int leaderMemberId = 9999;\n        final String ingressEndpoints = \"ingress endpoints ...\";\n        newLeaderEventEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .leadershipTermId(leadershipTermId)\n            .clusterSessionId(clusterSessionId)\n            .leaderMemberId(leaderMemberId)\n            .ingressEndpoints(ingressEndpoints);\n\n        final EgressListener egressListener = mock(EgressListener.class);\n        final Header header = new Header(1, 3);\n        final EgressAdapter adapter = new EgressAdapter(egressListener, clusterSessionId, mock(Subscription.class), 10);\n\n        adapter.onFragment(buffer, offset, newLeaderEventEncoder.encodedLength(), header);\n\n        verify(egressListener).onNewLeader(clusterSessionId, leadershipTermId, leaderMemberId, ingressEndpoints);\n        verifyNoMoreInteractions(egressListener);\n    }\n\n    @Test\n    void onFragmentIsANoOpIfSessionIdDoesNotMatchOnNewLeader()\n    {\n        final int offset = 0;\n        final long clusterSessionId = -100;\n        final long leadershipTermId = 6;\n        final int leaderMemberId = 9999;\n        final String ingressEndpoints = \"ingress endpoints ...\";\n        newLeaderEventEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .leadershipTermId(leadershipTermId)\n            .clusterSessionId(clusterSessionId)\n            .leaderMemberId(leaderMemberId)\n            .ingressEndpoints(ingressEndpoints);\n\n        final EgressListener egressListener = mock(EgressListener.class);\n        final Header header = new Header(1, 3);\n        final EgressAdapter adapter = new EgressAdapter(egressListener, 0, mock(Subscription.class), 10);\n\n        adapter.onFragment(buffer, offset, newLeaderEventEncoder.encodedLength(), header);\n\n        verifyNoInteractions(egressListener);\n    }\n\n    @Test\n    void onFragmentShouldInvokeOnAdminResponseCallbackIfSessionIdMatches()\n    {\n        final int offset = 24;\n        final long clusterSessionId = 18;\n        final long correlationId = 3274239749237498239L;\n        final AdminRequestType type = AdminRequestType.SNAPSHOT;\n        final AdminResponseCode responseCode = AdminResponseCode.UNAUTHORISED_ACCESS;\n        final String message = \"Unauthorised access detected!\";\n        final byte[] payload = new byte[]{ 0x1, 0x2, 0x3 };\n        adminResponseEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .clusterSessionId(clusterSessionId)\n            .correlationId(correlationId)\n            .requestType(type)\n            .responseCode(responseCode)\n            .message(message);\n        adminResponseEncoder.putPayload(payload, 0, payload.length);\n\n        final EgressListener egressListener = mock(EgressListener.class);\n        final Header header = new Header(1, 3);\n        final EgressAdapter adapter = new EgressAdapter(egressListener, clusterSessionId, mock(Subscription.class), 10);\n\n        adapter.onFragment(buffer, offset, adminResponseEncoder.encodedLength(), header);\n\n        verify(egressListener).onAdminResponse(\n            clusterSessionId,\n            correlationId,\n            type,\n            responseCode,\n            message,\n            buffer,\n            offset + MessageHeaderEncoder.ENCODED_LENGTH + adminResponseEncoder.encodedLength() - payload.length,\n            payload.length);\n        verifyNoMoreInteractions(egressListener);\n    }\n\n    @Test\n    void onFragmentIsANoOpIfSessionIdDoesNotMatchOnAdminResponse()\n    {\n        final int offset = 24;\n        final long clusterSessionId = 18;\n        final long correlationId = 3274239749237498239L;\n        final AdminRequestType type = AdminRequestType.SNAPSHOT;\n        final AdminResponseCode responseCode = AdminResponseCode.OK;\n        final String message = \"Unauthorised access detected!\";\n        final byte[] payload = new byte[]{ 0x1, 0x2, 0x3 };\n        adminResponseEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .clusterSessionId(clusterSessionId)\n            .correlationId(correlationId)\n            .requestType(type)\n            .responseCode(responseCode)\n            .message(message);\n        adminResponseEncoder.putPayload(payload, 0, payload.length);\n\n        final EgressListener egressListener = mock(EgressListener.class);\n        final Header header = new Header(1, 3);\n        final EgressAdapter adapter = new EgressAdapter(\n            egressListener, -clusterSessionId, mock(Subscription.class), 10);\n\n        adapter.onFragment(buffer, offset, adminResponseEncoder.encodedLength(), header);\n\n        verifyNoInteractions(egressListener);\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/client/EgressPollerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.client;\n\nimport io.aeron.Subscription;\nimport io.aeron.archive.codecs.ControlResponseCode;\nimport io.aeron.archive.codecs.ControlResponseEncoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderEncoder;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\n\nimport static io.aeron.logbuffer.ControlledFragmentHandler.Action.ABORT;\nimport static io.aeron.logbuffer.ControlledFragmentHandler.Action.BREAK;\nimport static io.aeron.logbuffer.ControlledFragmentHandler.Action.CONTINUE;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\n\nclass EgressPollerTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[1024]);\n    private final Header header = new Header(42, 16);\n    private final Subscription subscription = mock(Subscription.class);\n    private final EgressPoller egressPoller = new EgressPoller(subscription, 10);\n\n    @Test\n    void shouldIgnoreUnknownMessageSchema()\n    {\n        final int offset = 64;\n        final ControlResponseEncoder controlResponseEncoder = new ControlResponseEncoder();\n        final io.aeron.archive.codecs.MessageHeaderEncoder messageHeaderEncoder =\n            new io.aeron.archive.codecs.MessageHeaderEncoder();\n        controlResponseEncoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .correlationId(42)\n            .code(ControlResponseCode.ERROR)\n            .errorMessage(\"test\");\n\n        assertEquals(\n            CONTINUE,\n            egressPoller.onFragment(\n                buffer, offset, messageHeaderEncoder.encodedLength() + controlResponseEncoder.encodedLength(), header));\n        assertFalse(egressPoller.isPollComplete());\n    }\n\n    @Test\n    void shouldHandleSessionMessage()\n    {\n        final int offset = 16;\n        final SessionMessageHeaderEncoder encoder = new SessionMessageHeaderEncoder();\n        final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n        final long clusterSessionId = 7777;\n        final long leadershipTermId = 5;\n        encoder\n            .wrapAndApplyHeader(buffer, offset, messageHeaderEncoder)\n            .clusterSessionId(clusterSessionId)\n            .leadershipTermId(leadershipTermId);\n\n        assertEquals(\n            BREAK,\n            egressPoller.onFragment(\n                buffer, offset, messageHeaderEncoder.encodedLength() + encoder.encodedLength(), header));\n        assertTrue(egressPoller.isPollComplete());\n        assertEquals(clusterSessionId, egressPoller.clusterSessionId());\n        assertEquals(leadershipTermId, egressPoller.leadershipTermId());\n\n        assertEquals(\n            ABORT,\n            egressPoller.onFragment(\n                buffer, offset, messageHeaderEncoder.encodedLength() + encoder.encodedLength(), header));\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/service/ClusterMarkFileTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.Aeron;\nimport io.aeron.cluster.codecs.mark.ClusterComponentType;\nimport io.aeron.cluster.codecs.mark.MarkFileHeaderDecoder;\nimport io.aeron.cluster.codecs.mark.MarkFileHeaderEncoder;\nimport org.agrona.BitUtil;\nimport org.agrona.IoUtil;\nimport org.agrona.MarkFile;\nimport org.agrona.SemanticVersion;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.DisabledForJreRange;\nimport org.junit.jupiter.api.condition.JRE;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.InOrder;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.MappedByteBuffer;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\n\nimport static io.aeron.cluster.service.ClusterMarkFile.ERROR_BUFFER_MAX_LENGTH;\nimport static io.aeron.cluster.service.ClusterMarkFile.ERROR_BUFFER_MIN_LENGTH;\nimport static io.aeron.cluster.service.ClusterMarkFile.HEADER_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PAGE_MIN_SIZE;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ClusterMarkFileTest\n{\n    @TempDir\n    private Path tempDir;\n\n    @ParameterizedTest\n    @ValueSource(ints = { Integer.MIN_VALUE, -100, ERROR_BUFFER_MIN_LENGTH - 1, ERROR_BUFFER_MAX_LENGTH + 1 })\n    void throwsExceptionIfErrorBufferLengthIsInvalid(final int errorBufferLength)\n    {\n        final IllegalArgumentException exception = assertThrowsExactly(\n            IllegalArgumentException.class,\n            () -> new ClusterMarkFile(\n                tempDir.resolve(\"test.cfg\").toFile(),\n                ClusterComponentType.CONSENSUS_MODULE,\n                errorBufferLength,\n                SystemEpochClock.INSTANCE,\n                10,\n                PAGE_MIN_SIZE));\n        assertEquals(\"Invalid errorBufferLength: \" + errorBufferLength, exception.getMessage());\n    }\n\n    @Test\n    @DisabledForJreRange(min = JRE.JAVA_21)\n    void shouldCallForceIfMarkFileIsNotClosed()\n    {\n        final MarkFile markFile = mock(MarkFile.class);\n        final MappedByteBuffer mappedByteBuffer = mock(MappedByteBuffer.class);\n        when(markFile.mappedByteBuffer()).thenReturn(mappedByteBuffer);\n        when(markFile.buffer()).thenReturn(new UnsafeBuffer(new byte[128]));\n        try (ClusterMarkFile clusterMarkFile = new ClusterMarkFile(markFile))\n        {\n            clusterMarkFile.force();\n\n            final InOrder inOrder = inOrder(markFile, mappedByteBuffer);\n            inOrder.verify(markFile).isClosed();\n            inOrder.verify(markFile).mappedByteBuffer();\n            inOrder.verify(mappedByteBuffer).force();\n            inOrder.verifyNoMoreInteractions();\n        }\n    }\n\n    @Test\n    @DisabledForJreRange(min = JRE.JAVA_21)\n    void shouldNotCallForceIfMarkFileIsClosed()\n    {\n        final MarkFile markFile = mock(MarkFile.class);\n        final MappedByteBuffer mappedByteBuffer = mock(MappedByteBuffer.class);\n        when(markFile.mappedByteBuffer()).thenReturn(mappedByteBuffer);\n        when(markFile.buffer()).thenReturn(new UnsafeBuffer(new byte[128]));\n        when(markFile.isClosed()).thenReturn(true);\n        try (ClusterMarkFile clusterMarkFile = new ClusterMarkFile(markFile))\n        {\n            clusterMarkFile.force();\n\n            final InOrder inOrder = inOrder(markFile, mappedByteBuffer);\n            inOrder.verify(markFile).isClosed();\n            inOrder.verifyNoMoreInteractions();\n        }\n    }\n\n    @ParameterizedTest\n    @EnumSource(ClusterComponentType.class)\n    void shouldCreateNewMarkFile(final ClusterComponentType componentType)\n    {\n        final File file = tempDir.resolve(ClusterMarkFile.FILENAME).toFile();\n        assertFalse(file.exists());\n\n        final CachedEpochClock epochClock = new CachedEpochClock();\n        epochClock.advance(35984758934759843L);\n\n        try (ClusterMarkFile clusterMarkFile =\n            new ClusterMarkFile(file, componentType, ERROR_BUFFER_MIN_LENGTH, epochClock, 1000, PAGE_MIN_SIZE))\n        {\n            assertTrue(file.exists());\n            assertEquals(HEADER_LENGTH + ERROR_BUFFER_MIN_LENGTH, file.length());\n\n            final long startTimestamp = epochClock.time();\n            clusterMarkFile.signalReady(startTimestamp);\n\n            verifyMarkFileContents(\n                clusterMarkFile,\n                ClusterMarkFile.SEMANTIC_VERSION,\n                componentType,\n                startTimestamp,\n                startTimestamp,\n                SystemUtil.getPid(),\n                Aeron.NULL_VALUE,\n                0,\n                0,\n                0,\n                0,\n                0,\n                0,\n                HEADER_LENGTH,\n                ERROR_BUFFER_MIN_LENGTH,\n                0,\n                \"\",\n                \"\",\n                \"\",\n                \"\",\n                \"\",\n                \"\");\n\n            assertInstanceOf(MarkFileHeaderEncoder.class, clusterMarkFile.encoder());\n            assertInstanceOf(MarkFileHeaderDecoder.class, clusterMarkFile.decoder());\n        }\n    }\n\n    @Test\n    void shouldUpdateExistingMarkFile()\n    {\n        final long activityTimestamp = 112211443311L;\n        final long candidateTermId = 753475487L;\n        final int archiveStreamId = 4;\n        final int serviceStreamId = 5;\n        final int consensusModuleStreamId = 108;\n        final int insgresStreamId = 101;\n        final int memberId = 8;\n        final int serviceId = 1;\n        final int clusterId = -9;\n        final String aeronDir = tempDir.resolve(\"aeron\").toString();\n        final String controlChannel = \"aeron:ipc\";\n        final String ingressChannel = \"aeron:udp?alias=ingress\";\n        final String serviceName = \"io.aeron.cluster.TestService\";\n        final String authenticator = \"authenticator\";\n        final String clusterDir = \"cluster dir\";\n\n        final File file = tempDir.resolve(ClusterMarkFile.FILENAME).toFile();\n        assertFalse(file.exists());\n\n        final CachedEpochClock epochClock = new CachedEpochClock();\n        epochClock.update(123456L);\n\n        try (ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            file, ClusterComponentType.BACKUP, ERROR_BUFFER_MIN_LENGTH, epochClock, 1000, PAGE_MIN_SIZE))\n        {\n            clusterMarkFile.signalReady(10);\n\n            clusterMarkFile.encoder()\n                .activityTimestamp(activityTimestamp)\n                .startTimestamp(-900000)\n                .pid(Long.MIN_VALUE)\n                .candidateTermId(candidateTermId)\n                .archiveStreamId(archiveStreamId)\n                .serviceStreamId(serviceStreamId)\n                .consensusModuleStreamId(consensusModuleStreamId)\n                .ingressStreamId(insgresStreamId)\n                .memberId(memberId)\n                .serviceId(serviceId)\n                .headerLength(444444)\n                .errorBufferLength(555555)\n                .clusterId(clusterId)\n                .aeronDirectory(aeronDir)\n                .controlChannel(controlChannel)\n                .ingressChannel(ingressChannel)\n                .serviceName(serviceName)\n                .authenticator(authenticator)\n                .servicesClusterDir(clusterDir);\n        }\n\n        epochClock.update(753498573948593L);\n        try (ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            file, ClusterComponentType.CONSENSUS_MODULE, ERROR_BUFFER_MIN_LENGTH * 2, epochClock, 2222, PAGE_MIN_SIZE))\n        {\n            verifyMarkFileContents(\n                clusterMarkFile,\n                ClusterMarkFile.SEMANTIC_VERSION,\n                ClusterComponentType.CONSENSUS_MODULE,\n                MarkFile.ACTIVATION_IN_PROGRESS_TIMESTAMP,\n                epochClock.time(),\n                SystemUtil.getPid(),\n                candidateTermId,\n                archiveStreamId,\n                serviceStreamId,\n                consensusModuleStreamId,\n                insgresStreamId,\n                memberId,\n                serviceId,\n                HEADER_LENGTH,\n                ERROR_BUFFER_MIN_LENGTH * 2,\n                clusterId,\n                aeronDir,\n                controlChannel,\n                ingressChannel,\n                serviceName,\n                authenticator,\n                clusterDir);\n        }\n    }\n\n    @ParameterizedTest\n    @EnumSource(io.aeron.cluster.codecs.mark.v0.ClusterComponentType.class)\n    @SuppressWarnings(\"MethodLength\")\n    void shouldHandleExistingMarkFileV0(final io.aeron.cluster.codecs.mark.v0.ClusterComponentType componentType)\n        throws IOException\n    {\n        final int version = SemanticVersion.compose(ClusterMarkFile.MAJOR_VERSION, 98, 157);\n        final ClusterComponentType currentComponentType = ClusterComponentType.get(componentType.value());\n        final int activityTimestamp = 89898989;\n        final int startTimestamp = -94237423;\n        final long pid = 42;\n        final long candidateTermId = -78;\n        final int archiveStreamId = 33;\n        final int serviceStreamId = 777;\n        final int consensusModuleStreamId = -87;\n        final int ingressStreamId = 5;\n        final int memberId = 16;\n        final int serviceId = 6;\n        final int headerLength = 2048;\n        final int errorBufferLength = 1500;\n        final int clusterId = 3;\n        final String aeronDir = tempDir.resolve(\"path/to/dev/shm\").toString();\n        final String controlChannel = \"control\";\n        final String ingressChannel = \"aeron:udp?endpoint=9999\";\n        final String serviceName = \"service name\";\n        final String authenticator = \"auth\";\n        final String clusterDir = tempDir.resolve(\"cluster\").toString();\n\n        final Path file =\n            Files.write(tempDir.resolve(\"test.txt\"), new byte[4096], StandardOpenOption.CREATE_NEW);\n\n        final MarkFile markFile = new MarkFile(\n            IoUtil.mapExistingFile(file.toFile(), ClusterMarkFile.FILENAME),\n            io.aeron.cluster.codecs.mark.v0.MarkFileHeaderDecoder.versionEncodingOffset(),\n            io.aeron.cluster.codecs.mark.v0.MarkFileHeaderDecoder.activityTimestampEncodingOffset());\n\n        final io.aeron.cluster.codecs.mark.v0.MarkFileHeaderEncoder encoder =\n            new io.aeron.cluster.codecs.mark.v0.MarkFileHeaderEncoder();\n        encoder.wrap(markFile.buffer(), 0);\n\n        encoder\n            .version(version)\n            .componentType(componentType)\n            .activityTimestamp(activityTimestamp)\n            .startTimestamp(startTimestamp)\n            .pid(pid)\n            .candidateTermId(candidateTermId)\n            .archiveStreamId(archiveStreamId)\n            .serviceStreamId(serviceStreamId)\n            .consensusModuleStreamId(consensusModuleStreamId)\n            .ingressStreamId(ingressStreamId)\n            .memberId(memberId)\n            .serviceId(serviceId)\n            .headerLength(headerLength)\n            .errorBufferLength(errorBufferLength)\n            .clusterId(clusterId)\n            .aeronDirectory(aeronDir)\n            .controlChannel(controlChannel)\n            .ingressChannel(ingressChannel)\n            .serviceName(serviceName)\n            .authenticator(authenticator);\n\n        markFile.buffer().putStringAscii(encoder.encodedLength(), clusterDir);\n\n        try (ClusterMarkFile clusterMarkFile = new ClusterMarkFile(markFile))\n        {\n            verifyMarkFileContents(\n                clusterMarkFile,\n                version,\n                currentComponentType,\n                activityTimestamp,\n                startTimestamp,\n                pid,\n                candidateTermId,\n                archiveStreamId,\n                serviceStreamId,\n                consensusModuleStreamId,\n                ingressStreamId,\n                memberId,\n                serviceId,\n                headerLength,\n                errorBufferLength,\n                clusterId,\n                aeronDir,\n                controlChannel,\n                ingressChannel,\n                serviceName,\n                authenticator,\n                clusterDir);\n\n            clusterMarkFile.signalFailedStart();\n            assertEquals(ClusterMarkFile.VERSION_FAILED, clusterMarkFile.decoder().version());\n\n            clusterMarkFile.signalReady(activityTimestamp * 2);\n            assertEquals(ClusterMarkFile.SEMANTIC_VERSION, clusterMarkFile.decoder().version());\n\n            clusterMarkFile.memberId(42);\n            assertEquals(42, clusterMarkFile.memberId());\n\n            clusterMarkFile.clusterId(8888888);\n            assertEquals(8888888, clusterMarkFile.clusterId());\n\n            assertEquals(candidateTermId, clusterMarkFile.candidateTermId());\n            assertEquals(componentType.value(), clusterMarkFile.decoder().componentType().value());\n\n            clusterMarkFile.decoder().sbeRewind();\n            assertEquals(aeronDir, clusterMarkFile.decoder().aeronDirectory());\n        }\n\n        final CachedEpochClock epochClock = new CachedEpochClock();\n        epochClock.advance(5436547234L);\n\n        try (ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            file.getParent().toFile(),\n            file.getFileName().toString(),\n            epochClock,\n            50_000,\n            null))\n        {\n            verifyMarkFileContents(\n                clusterMarkFile,\n                ClusterMarkFile.SEMANTIC_VERSION,\n                currentComponentType,\n                activityTimestamp * 2,\n                startTimestamp,\n                pid,\n                candidateTermId,\n                archiveStreamId,\n                serviceStreamId,\n                consensusModuleStreamId,\n                ingressStreamId,\n                42,\n                serviceId,\n                headerLength,\n                errorBufferLength,\n                8888888,\n                aeronDir,\n                controlChannel,\n                ingressChannel,\n                serviceName,\n                authenticator,\n                clusterDir);\n\n            clusterMarkFile.signalReady(activityTimestamp * 3);\n            assertEquals(ClusterMarkFile.SEMANTIC_VERSION, clusterMarkFile.decoder().version());\n            assertEquals(activityTimestamp * 3, clusterMarkFile.decoder().activityTimestamp());\n        }\n\n        // should overwrite existing data when message header offset is being added\n        try (ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            file.toFile(),\n            currentComponentType,\n            ERROR_BUFFER_MIN_LENGTH * 2,\n            epochClock,\n            1000,\n            PAGE_MIN_SIZE))\n        {\n            clusterMarkFile.memberId(8);\n            clusterMarkFile.clusterId(3);\n\n            verifyMarkFileContents(\n                clusterMarkFile,\n                0,\n                currentComponentType,\n                0,\n                epochClock.time(),\n                SystemUtil.getPid(),\n                candidateTermId,\n                0,\n                0,\n                0,\n                0,\n                8,\n                0,\n                HEADER_LENGTH,\n                ERROR_BUFFER_MIN_LENGTH * 2,\n                3,\n                \"\",\n                \"\",\n                \"\",\n                \"\",\n                \"\",\n                \"\");\n        }\n    }\n\n    @Test\n    void shouldUnmapBufferUponClose()\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            tempDir.resolve(\"test-mark.file\").toFile(),\n            ClusterComponentType.STANDBY,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100,\n            PAGE_MIN_SIZE);\n\n        final MarkFileHeaderEncoder encoder = clusterMarkFile.encoder();\n        final MarkFileHeaderDecoder decoder = clusterMarkFile.decoder();\n        final AtomicBuffer errorBuffer = clusterMarkFile.errorBuffer();\n\n        assertNotNull(errorBuffer.byteBuffer());\n        assertSame(errorBuffer.byteBuffer(), encoder.buffer().byteBuffer());\n        assertSame(errorBuffer.byteBuffer(), decoder.buffer().byteBuffer());\n\n        clusterMarkFile.close();\n\n        assertTrue(clusterMarkFile.isClosed());\n        assertNull(errorBuffer.byteBuffer());\n        assertEquals(0, errorBuffer.capacity());\n        assertNull(encoder.buffer().byteBuffer());\n        assertEquals(0, encoder.buffer().capacity());\n        assertNull(decoder.buffer().byteBuffer());\n        assertEquals(0, decoder.buffer().capacity());\n\n        clusterMarkFile.close();\n\n        assertTrue(clusterMarkFile.isClosed());\n    }\n\n    @Test\n    void clusterIdAccessors()\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            tempDir.resolve(\"test-mark.file\").toFile(),\n            ClusterComponentType.STANDBY,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100,\n            PAGE_MIN_SIZE);\n\n        assertEquals(0, clusterMarkFile.clusterId());\n\n        clusterMarkFile.clusterId(42);\n        assertEquals(42, clusterMarkFile.clusterId());\n\n        clusterMarkFile.encoder().clusterId(123);\n        assertEquals(123, clusterMarkFile.clusterId());\n        assertEquals(123, clusterMarkFile.decoder().clusterId());\n\n        clusterMarkFile.close();\n\n        clusterMarkFile.clusterId(2);\n        assertEquals(Aeron.NULL_VALUE, clusterMarkFile.clusterId());\n    }\n\n    @Test\n    void memberIdAccessors()\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            tempDir.resolve(\"test-mark.file\").toFile(),\n            ClusterComponentType.STANDBY,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100,\n            PAGE_MIN_SIZE);\n\n        assertEquals(0, clusterMarkFile.memberId());\n\n        clusterMarkFile.memberId(7);\n        assertEquals(7, clusterMarkFile.memberId());\n\n        clusterMarkFile.encoder().memberId(-5);\n        assertEquals(-5, clusterMarkFile.memberId());\n        assertEquals(-5, clusterMarkFile.decoder().memberId());\n\n        clusterMarkFile.close();\n\n        clusterMarkFile.memberId(111);\n        assertEquals(Aeron.NULL_VALUE, clusterMarkFile.memberId());\n    }\n\n    @Test\n    void candidateTermIdAccessors()\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            tempDir.resolve(\"test-mark.file\").toFile(),\n            ClusterComponentType.STANDBY,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100,\n            PAGE_MIN_SIZE);\n\n        assertEquals(Aeron.NULL_VALUE, clusterMarkFile.candidateTermId());\n\n        clusterMarkFile.encoder().candidateTermId(123);\n        assertEquals(123, clusterMarkFile.candidateTermId());\n        assertEquals(123, clusterMarkFile.decoder().candidateTermId());\n\n        clusterMarkFile.close();\n\n        assertEquals(Aeron.NULL_VALUE, clusterMarkFile.candidateTermId());\n    }\n\n    @Test\n    void activityTimestampAccessors()\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            tempDir.resolve(\"test-mark.file\").toFile(),\n            ClusterComponentType.STANDBY,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100,\n            PAGE_MIN_SIZE);\n\n        assertEquals(0, clusterMarkFile.activityTimestampVolatile());\n\n        clusterMarkFile.updateActivityTimestamp(4444555555L);\n        assertEquals(4444555555L, clusterMarkFile.activityTimestampVolatile());\n\n        clusterMarkFile.encoder().activityTimestamp(439856438756348L);\n        assertEquals(439856438756348L, clusterMarkFile.activityTimestampVolatile());\n        assertEquals(439856438756348L, clusterMarkFile.decoder().activityTimestamp());\n\n        clusterMarkFile.close();\n\n        clusterMarkFile.updateActivityTimestamp(222);\n        assertEquals(Aeron.NULL_VALUE, clusterMarkFile.activityTimestampVolatile());\n    }\n\n    @Test\n    void loadControlPropertiesAccessors()\n    {\n        final String controlChannel = \"aeron:udp?endpoint=192.168.0.10:5555\";\n        final int memberId = 42;\n        final int serviceStreamId = 61;\n        final int consensusModuleStreamId = 4;\n        final String aeronDir = \"/dev/shm/aeron\";\n        final String ingressChannel = \"aeron:udp?endpoint=localhost:8080\";\n        final String clusterDir = \"/data/cluster/dir\";\n\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            tempDir.resolve(\"test-mark.file\").toFile(),\n            ClusterComponentType.STANDBY,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100,\n            PAGE_MIN_SIZE);\n\n        clusterMarkFile.encoder()\n            .clusterId(123)\n            .memberId(memberId)\n            .serviceId(7)\n            .candidateTermId(-5)\n            .serviceStreamId(serviceStreamId)\n            .consensusModuleStreamId(consensusModuleStreamId)\n            .aeronDirectory(aeronDir)\n            .controlChannel(controlChannel)\n            .ingressChannel(ingressChannel)\n            .serviceName(\"test\")\n            .authenticator(\"authenticator\")\n            .servicesClusterDir(clusterDir);\n\n        clusterMarkFile.decoder().skipAeronDirectory();\n        clusterMarkFile.decoder().skipControlChannel();\n        assertEquals(ingressChannel, clusterMarkFile.decoder().ingressChannel());\n        clusterMarkFile.decoder().skipServiceName();\n        clusterMarkFile.decoder().skipAuthenticator();\n        assertEquals(clusterDir, clusterMarkFile.decoder().servicesClusterDir());\n\n        final ClusterNodeControlProperties controlProperties = clusterMarkFile.loadControlProperties();\n        assertNotNull(controlProperties);\n        assertEquals(memberId, controlProperties.memberId);\n        assertEquals(serviceStreamId, controlProperties.serviceStreamId);\n        assertEquals(consensusModuleStreamId, controlProperties.consensusModuleStreamId);\n        assertEquals(aeronDir, controlProperties.aeronDirectoryName);\n        assertEquals(controlChannel, controlProperties.controlChannel);\n\n        clusterMarkFile.close();\n\n        assertNull(clusterMarkFile.loadControlProperties());\n    }\n\n    @Test\n    void signalReady()\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            tempDir.resolve(\"test-mark.file\").toFile(),\n            ClusterComponentType.STANDBY,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100,\n            PAGE_MIN_SIZE);\n\n        assertEquals(0, clusterMarkFile.decoder().version());\n\n        clusterMarkFile.signalReady(1000);\n        assertEquals(ClusterMarkFile.SEMANTIC_VERSION, clusterMarkFile.decoder().version());\n        assertEquals(1000, clusterMarkFile.decoder().activityTimestamp());\n\n        clusterMarkFile.close();\n\n        clusterMarkFile.signalReady(2000);\n    }\n\n    @Test\n    void signalFailedStart()\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            tempDir.resolve(\"test-mark.file\").toFile(),\n            ClusterComponentType.STANDBY,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            100,\n            PAGE_MIN_SIZE);\n\n        assertEquals(0, clusterMarkFile.decoder().version());\n\n        clusterMarkFile.signalFailedStart();\n        assertEquals(ClusterMarkFile.VERSION_FAILED, clusterMarkFile.decoder().version());\n        assertEquals(Aeron.NULL_VALUE, clusterMarkFile.decoder().activityTimestamp());\n\n        clusterMarkFile.close();\n\n        clusterMarkFile.signalFailedStart();\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 4096, 32 * 1024 })\n    @SuppressWarnings(\"try\")\n    void shouldAlignMarkFileLengthBasedOnTheFilePageSizeFromAeronClient(final int filePageSize)\n    {\n        final int errorBufferLength = 1345679;\n\n        final File file = tempDir.resolve(\"test-mark.file\").toFile();\n        try (ClusterMarkFile ignored = new ClusterMarkFile(\n            file,\n            ClusterComponentType.STANDBY,\n            errorBufferLength,\n            SystemEpochClock.INSTANCE,\n            100,\n            filePageSize))\n        {\n            assertEquals(BitUtil.align(HEADER_LENGTH + errorBufferLength, filePageSize), file.length());\n        }\n    }\n\n    @Test\n    void shouldRejectFilePageSizeIfNotPowerOf2()\n    {\n        final IllegalStateException exception = assertThrowsExactly(\n            IllegalStateException.class, () -> new ClusterMarkFile(\n                tempDir.resolve(\"test-mark.file\").toFile(),\n                ClusterComponentType.STANDBY,\n                ERROR_BUFFER_MIN_LENGTH,\n                SystemEpochClock.INSTANCE,\n                100,\n                4199));\n\n        assertEquals(\"Page size not a power of 2: page size=4199\", exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, -5, 4095 })\n    void shouldRejectFilePageSizeIfTooSmall(final int pageSize)\n    {\n        final IllegalStateException exception = assertThrowsExactly(\n            IllegalStateException.class, () -> new ClusterMarkFile(\n                tempDir.resolve(\"test-mark.file\").toFile(),\n                ClusterComponentType.STANDBY,\n                ERROR_BUFFER_MIN_LENGTH,\n                SystemEpochClock.INSTANCE,\n                100,\n                pageSize));\n\n        assertEquals(\"Page size less than min size of 4096: page size=\" + pageSize, exception.getMessage());\n    }\n\n    @Test\n    void shouldRejectFilePageSizeIfTooBig()\n    {\n        final IllegalStateException exception = assertThrowsExactly(\n            IllegalStateException.class, () -> new ClusterMarkFile(\n                tempDir.resolve(\"test-mark.file\").toFile(),\n                ClusterComponentType.STANDBY,\n                ERROR_BUFFER_MIN_LENGTH,\n                SystemEpochClock.INSTANCE,\n                100,\n                Integer.MAX_VALUE));\n\n        assertEquals(\"Page size more than max size of 1073741824: page size=2147483647\", exception.getMessage());\n    }\n\n    private static void verifyMarkFileContents(\n        final ClusterMarkFile clusterMarkFile,\n        final int version,\n        final ClusterComponentType clusterComponentType,\n        final long activityTimestamp,\n        final long startTimestamp,\n        final long pid,\n        final long candidateTermId,\n        final int archiveStreamId,\n        final int serviceStreamId,\n        final int consensusModuleStreamId,\n        final int ingressStreamId,\n        final int memberId,\n        final int serviceId,\n        final int headerLength,\n        final int errorBufferLength,\n        final int clusterId,\n        final String aeronDir,\n        final String controlChannel,\n        final String ingressChannel,\n        final String serviceName,\n        final String authenticator,\n        final String clusterDir)\n    {\n        assertEquals(version, clusterMarkFile.decoder().version());\n        assertEquals(clusterComponentType, clusterMarkFile.decoder().componentType());\n        assertEquals(activityTimestamp, clusterMarkFile.decoder().activityTimestamp());\n        assertEquals(startTimestamp, clusterMarkFile.decoder().startTimestamp());\n        assertEquals(pid, clusterMarkFile.decoder().pid());\n        assertEquals(candidateTermId, clusterMarkFile.decoder().candidateTermId());\n        assertEquals(archiveStreamId, clusterMarkFile.decoder().archiveStreamId());\n        assertEquals(serviceStreamId, clusterMarkFile.decoder().serviceStreamId());\n        assertEquals(consensusModuleStreamId, clusterMarkFile.decoder().consensusModuleStreamId());\n        assertEquals(ingressStreamId, clusterMarkFile.decoder().ingressStreamId());\n        assertEquals(memberId, clusterMarkFile.decoder().memberId());\n        assertEquals(serviceId, clusterMarkFile.decoder().serviceId());\n        assertEquals(headerLength, clusterMarkFile.decoder().headerLength());\n        assertEquals(errorBufferLength, clusterMarkFile.decoder().errorBufferLength());\n        assertEquals(clusterId, clusterMarkFile.decoder().clusterId());\n        assertEquals(aeronDir, clusterMarkFile.decoder().aeronDirectory());\n        assertEquals(controlChannel, clusterMarkFile.decoder().controlChannel());\n        assertEquals(ingressChannel, clusterMarkFile.decoder().ingressChannel());\n        assertEquals(serviceName, clusterMarkFile.decoder().serviceName());\n        assertEquals(authenticator, clusterMarkFile.decoder().authenticator());\n        assertEquals(clusterDir, clusterMarkFile.decoder().servicesClusterDir());\n\n        assertEquals(memberId, clusterMarkFile.memberId());\n        assertEquals(clusterId, clusterMarkFile.clusterId());\n        assertEquals(candidateTermId, clusterMarkFile.candidateTermId());\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/service/ClusteredServiceAgentTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.*;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.driver.DutyCycleTracker;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.test.CountersAnswer;\nimport io.aeron.test.Tests;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.*;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\nimport org.agrona.concurrent.errors.ErrorLogReader;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.AeronCounters.CLUSTER_COMMIT_POSITION_TYPE_ID;\nimport static io.aeron.AeronCounters.CLUSTER_RECOVERY_STATE_TYPE_ID;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.*;\n\nclass ClusteredServiceAgentTest\n{\n    @Test\n    void shouldClaimAndWriteToBufferWhenFollower()\n    {\n        final Aeron aeron = mock(Aeron.class);\n        final Publication publication = mock(Publication.class);\n\n        final ClusteredServiceContainer.Context ctx = new ClusteredServiceContainer.Context()\n            .aeron(aeron)\n            .idleStrategySupplier(() -> YieldingIdleStrategy.INSTANCE);\n        final ClusteredServiceAgent clusteredServiceAgent = new ClusteredServiceAgent(ctx);\n\n        final BufferClaim bufferClaim = new BufferClaim();\n        final int length = 64;\n        final DirectBuffer msg = new UnsafeBuffer(new byte[length]);\n\n        final long position = clusteredServiceAgent.tryClaim(0, publication, length, bufferClaim);\n        assertEquals(ClientSession.MOCKED_OFFER, position);\n\n        final MutableDirectBuffer buffer = bufferClaim.buffer();\n        buffer.putBytes(bufferClaim.offset() + AeronCluster.SESSION_HEADER_LENGTH, msg, 0, length);\n        bufferClaim.commit();\n    }\n\n    @Test\n    void shouldAbortClusteredServiceIfCommitPositionCounterIsClosed()\n    {\n        final UnsafeBuffer claimBuffer = new UnsafeBuffer(new byte[64 * 1024]);\n        final Aeron aeron = mock(Aeron.class);\n        final ConcurrentPublication publication = mock(ConcurrentPublication.class);\n        final Subscription subscription = mock(Subscription.class);\n        when(aeron.addPublication(any(), anyInt())).thenReturn(publication);\n        when(aeron.addSubscription(any(), anyInt())).thenReturn(subscription);\n        when(publication.tryClaim(anyInt(), any())).thenAnswer(\n            (invocation) ->\n            {\n                final BufferClaim claim = invocation.getArgument(1, BufferClaim.class);\n                claim.wrap(claimBuffer, 0, claimBuffer.capacity());\n                return invocation.getArgument(0, Integer.class).longValue();\n            });\n        final ArgumentCaptor<UnavailableCounterHandler> captor =\n            ArgumentCaptor.forClass(UnavailableCounterHandler.class);\n        final ClusterMarkFile markFile = mock(ClusterMarkFile.class);\n        final CountersManager countersManager = Tests.newCountersManager(64 * 1024);\n\n        when(aeron.addCounter(anyInt(), any(), anyInt(), anyInt(), any(), anyInt(), anyInt()))\n            .then(CountersAnswer.mapTo(countersManager));\n\n        RecoveryState.allocate(aeron, NULL_VALUE, 0, 0, 0, 0);\n\n        final int commitPositionCounterId = countersManager.allocate(\"commit-pos\", CLUSTER_COMMIT_POSITION_TYPE_ID);\n        final int recoveryStateCounterId = countersManager.allocate(\"recovery-state\", CLUSTER_RECOVERY_STATE_TYPE_ID);\n        countersManager.setCounterValue(recoveryStateCounterId, NULL_VALUE);\n\n        when(aeron.countersReader()).thenReturn(countersManager);\n        when(aeron.isClosed()).thenReturn(false);\n\n        final CachedNanoClock nanoClock = new CachedNanoClock();\n        final ClusteredServiceContainer.Context ctx = new ClusteredServiceContainer.Context()\n            .aeron(aeron)\n            .nanoClock(nanoClock)\n            .epochClock(new CachedEpochClock())\n            .clusterMarkFile(markFile)\n            .clusteredService(mock(ClusteredService.class))\n            .dutyCycleTracker(new DutyCycleTracker())\n            .idleStrategySupplier(() -> YieldingIdleStrategy.INSTANCE)\n            .errorLog(mock(DistinctErrorLog.class))\n            .terminationHook(() -> {});\n        final ClusteredServiceAgent clusteredServiceAgent = new ClusteredServiceAgent(ctx);\n\n        clusteredServiceAgent.onStart();\n\n        verify(aeron).addUnavailableCounterHandler(captor.capture());\n\n        clusteredServiceAgent.doWork();\n\n        captor.getValue().onUnavailableCounter(countersManager, 0, commitPositionCounterId);\n\n        nanoClock.advance(TimeUnit.MILLISECONDS.toNanos(2));\n        assertThrowsExactly(ClusterTerminationException.class, clusteredServiceAgent::doWork);\n    }\n\n    @Test\n    void shouldLogErrorInsteadOfThrowingIfSessionIsNotFoundOnClose()\n    {\n        final Aeron aeron = mock(Aeron.class);\n        final DistinctErrorLog distinctErrorLog = new DistinctErrorLog(\n            new UnsafeBuffer(ByteBuffer.allocateDirect(16384)), new SystemEpochClock());\n        final CountersManager countersManager = Tests.newCountersManager(16 * 1024);\n        final AtomicCounter errorCounter = countersManager.newCounter(\"test\");\n        final long originalErrorCount = errorCounter.get();\n\n        final ErrorHandler errorHandler = CommonContext.setupErrorHandler(null, distinctErrorLog);\n        final CountedErrorHandler countedErrorHandler = new CountedErrorHandler(errorHandler, errorCounter);\n        final ClusteredServiceContainer.Context ctx = new ClusteredServiceContainer.Context()\n            .aeron(aeron)\n            .idleStrategySupplier(() -> YieldingIdleStrategy.INSTANCE)\n            .countedErrorHandler(countedErrorHandler);\n        final ClusteredServiceAgent clusteredServiceAgent = new ClusteredServiceAgent(ctx);\n\n        clusteredServiceAgent.onSessionClose(99, 999, 9999, 99999, CloseReason.CLIENT_ACTION);\n\n        assertEquals(originalErrorCount + 1, errorCounter.get());\n        assertTrue(ErrorLogReader.hasErrors(distinctErrorLog.buffer()));\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/service/ClusteredServiceContainerContextTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.Counter;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.codecs.mark.ClusterComponentType;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\nimport static io.aeron.cluster.service.ClusterMarkFile.ERROR_BUFFER_MIN_LENGTH;\nimport static io.aeron.cluster.service.ClusterMarkFile.HEADER_LENGTH;\nimport static io.aeron.cluster.service.ClusteredServiceContainer.Configuration.MARK_FILE_DIR_PROP_NAME;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PAGE_MIN_SIZE;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.startsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass ClusteredServiceContainerContextTest\n{\n    @TempDir\n    private File clusterDir;\n    private ClusteredServiceContainer.Context context;\n    private final int serviceId = 1;\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void setUp()\n    {\n        final RethrowingErrorHandler errorHandler = mock(RethrowingErrorHandler.class);\n        final Aeron.Context aeronContext = mock(Aeron.Context.class);\n        when(aeronContext.aeronDirectoryName()).thenReturn(\"funny\");\n        when(aeronContext.subscriberErrorHandler()).thenReturn(errorHandler);\n        when(aeronContext.filePageSize()).thenReturn(PAGE_MIN_SIZE);\n        final Aeron aeron = mock(Aeron.class);\n        when(aeron.addCounter(\n            anyInt(), any(DirectBuffer.class), anyInt(), anyInt(), any(DirectBuffer.class), anyInt(), anyInt()))\n            .thenAnswer(invocation -> mock(Counter.class));\n        when(aeron.context()).thenReturn(aeronContext);\n        final AtomicCounter errorCounter = mock(AtomicCounter.class);\n        final ClusteredService clusteredService = mock(ClusteredService.class);\n        context = new ClusteredServiceContainer.Context()\n            .aeron(aeron)\n            .errorCounter(errorCounter)\n            .errorHandler(errorHandler)\n            .clusteredService(clusteredService)\n            .clusterDir(clusterDir)\n            .serviceId(serviceId);\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        context.close();\n    }\n\n    @Test\n    void throwsIllegalStateExceptionIfAnActiveMarkFileExists()\n    {\n        final ClusteredServiceContainer.Context anotherInstance = context.clone();\n\n        context.conclude();\n\n        final IllegalStateException exception =\n            assertThrowsExactly(IllegalStateException.class, anotherInstance::conclude);\n        assertThat(exception.getMessage(), startsWith(\"active mark file detected: \"));\n    }\n\n    @Test\n    void concludeShouldCreateMarkFileDirSetViaSystemProperty(final @TempDir File tempDir) throws IOException\n    {\n        final File rootDir = new File(tempDir, \"root\");\n        final File markFileDir = new File(rootDir, \"mark-file/../dir\");\n        assertFalse(markFileDir.getCanonicalFile().exists());\n        context.serviceId(serviceId);\n\n        System.setProperty(MARK_FILE_DIR_PROP_NAME, markFileDir.getAbsolutePath());\n        try\n        {\n            assertNull(context.markFileDir());\n\n            context.conclude();\n\n            assertEquals(markFileDir.getCanonicalFile(), context.markFileDir());\n            assertTrue(markFileDir.getCanonicalFile().exists());\n            assertTrue(\n                new File(context.clusterDir(), ClusterMarkFile.linkFilenameForService(context.serviceId())).exists());\n        }\n        finally\n        {\n            System.clearProperty(MARK_FILE_DIR_PROP_NAME);\n        }\n    }\n\n    @Test\n    void concludeShouldCreateMarkFileDirSetDirectly(final @TempDir File tempDir) throws IOException\n    {\n        final File rootDir = new File(tempDir, \"root\");\n        final File markFileDir = new File(rootDir, \"mark/.././file-dir\");\n        assertFalse(markFileDir.exists());\n        context.serviceId(serviceId).markFileDir(markFileDir);\n\n        context.conclude();\n\n        assertEquals(markFileDir.getCanonicalFile(), context.markFileDir());\n        assertTrue(markFileDir.getCanonicalFile().exists());\n        assertTrue(\n            new File(context.clusterDir(), ClusterMarkFile.linkFilenameForService(context.serviceId())).exists());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldRemoveLinkIfMarkFileIsInClusterDir(final boolean isSet) throws IOException\n    {\n        final File markFileDir = isSet ? context.clusterDir() : null;\n\n        context.serviceId(serviceId).markFileDir(markFileDir);\n        final File oldLinkFile = new File(context.clusterDir(), ClusterMarkFile.linkFilenameForService(serviceId));\n        assertTrue(oldLinkFile.createNewFile());\n        assertTrue(oldLinkFile.exists());\n\n        context.conclude();\n\n        assertFalse(oldLinkFile.exists());\n    }\n\n    @Test\n    void concludeShouldCreateMarkFileLinkInTheParentDirectoryOfTheClusterMarkFile(\n        final @TempDir File clusterDir,\n        final @TempDir File markFileDir,\n        final @TempDir File otherDir) throws IOException\n    {\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            new File(otherDir, \"test.not\"),\n            ClusterComponentType.CONSENSUS_MODULE,\n            ERROR_BUFFER_MIN_LENGTH,\n            SystemEpochClock.INSTANCE,\n            10,\n            PAGE_MIN_SIZE);\n        context\n            .serviceId(serviceId)\n            .clusterDir(clusterDir)\n            .markFileDir(markFileDir)\n            .clusterMarkFile(clusterMarkFile);\n\n        context.conclude();\n\n        assertEquals(clusterDir.getCanonicalFile(), context.clusterDir());\n        assertEquals(markFileDir.getCanonicalFile(), context.markFileDir());\n        assertEquals(otherDir, context.clusterMarkFile().parentDirectory());\n        assertTrue(clusterDir.getCanonicalFile().exists());\n        assertTrue(markFileDir.getCanonicalFile().exists());\n        final File linkFile = new File(context.clusterDir(), ClusterMarkFile.linkFilenameForService(serviceId));\n        assertTrue(linkFile.exists());\n        assertEquals(otherDir.getCanonicalPath(), new String(Files.readAllBytes(linkFile.toPath()), US_ASCII));\n    }\n\n    @Test\n    void shouldInitializeClusterDirectoryNameFromTheAssignedClusterDir(@TempDir final Path dir) throws IOException\n    {\n        final Path clusterDir = dir.resolve(\"explicit/cluster/./dir\");\n        context.clusterDir(clusterDir.toFile()).clusterDirectoryName(\"replace-me\");\n\n        context.conclude();\n\n        assertEquals(clusterDir.toFile().getCanonicalFile(), context.clusterDir());\n        assertEquals(context.clusterDir().getAbsolutePath(), context.clusterDirectoryName());\n    }\n\n    @Test\n    void shouldInitializeClusterDirectoryFromTheGivenDirectoryName(@TempDir final Path temp) throws IOException\n    {\n        final Path dir = Paths.get(temp.toString(), \"/some/../path\");\n        context.clusterDir(null).clusterDirectoryName(dir.toString());\n\n        context.conclude();\n\n        assertEquals(dir.toFile().getCanonicalFile(), context.clusterDir());\n        assertEquals(context.clusterDir().getAbsolutePath(), context.clusterDirectoryName());\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void shouldSetServiceNameIfNotSpecified(final String serviceName)\n    {\n        context.clusterId(7).serviceId(5).serviceName(serviceName);\n\n        context.conclude();\n\n        assertEquals(\"clustered-service-7-5\", context.serviceName());\n    }\n\n    @Test\n    void shouldSetServiceNameExplicitly()\n    {\n        context.clusterId(7).serviceId(5).serviceName(\"test 13\");\n\n        context.conclude();\n\n        assertEquals(\"test 13\", context.serviceName());\n    }\n\n    @Test\n    void shouldNotSetClientName()\n    {\n        context.clusterId(42).serviceId(0).serviceName(\"test 13\");\n\n        context.conclude();\n\n        verify(context.aeron().context(), never()).clientName(anyString());\n    }\n\n    @Test\n    void shouldUseExplicitlyAssignArchiveContext()\n    {\n        final AeronArchive.Context archiveContext = new AeronArchive.Context()\n            .controlRequestChannel(\"aeron:ipc\")\n            .controlResponseChannel(\"aeron:ipc\");\n        context.archiveContext(archiveContext);\n        assertSame(archiveContext, context.archiveContext());\n\n        context.conclude();\n\n        assertSame(archiveContext, context.archiveContext());\n        assertSame(context.aeron(), archiveContext.aeron());\n        assertFalse(archiveContext.ownsAeronClient());\n        assertSame(context.countedErrorHandler(), archiveContext.errorHandler());\n        assertSame(NoOpLock.INSTANCE, archiveContext.lock());\n    }\n\n    @Test\n    void shouldCreateArchiveContextUsingLocalChannelConfiguration()\n    {\n        final String controlChannel = \"aeron:ipc?alias=test\";\n        final int localControlStreamId = 8;\n        System.setProperty(AeronArchive.Configuration.LOCAL_CONTROL_CHANNEL_PROP_NAME, controlChannel);\n        System.setProperty(\n            AeronArchive.Configuration.LOCAL_CONTROL_STREAM_ID_PROP_NAME, Integer.toString(localControlStreamId));\n        context.archiveContext(null);\n        assertNull(context.archiveContext());\n\n        try\n        {\n            context.conclude();\n\n            final AeronArchive.Context archiveContext = context.archiveContext();\n            assertNotNull(archiveContext);\n            assertSame(context.aeron(), archiveContext.aeron());\n            assertFalse(archiveContext.ownsAeronClient());\n            assertSame(context.countedErrorHandler(), archiveContext.errorHandler());\n            assertSame(NoOpLock.INSTANCE, archiveContext.lock());\n            assertEquals(controlChannel, archiveContext.controlRequestChannel());\n            assertEquals(controlChannel, archiveContext.controlResponseChannel());\n            assertEquals(localControlStreamId, archiveContext.controlRequestStreamId());\n            assertNotEquals(localControlStreamId, archiveContext.controlResponseStreamId());\n        }\n        finally\n        {\n            System.clearProperty(AeronArchive.Configuration.LOCAL_CONTROL_CHANNEL_PROP_NAME);\n            System.clearProperty(AeronArchive.Configuration.LOCAL_CONTROL_STREAM_ID_PROP_NAME);\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"42,88\", \"0,19\" })\n    void shouldCreateAliasForControlStreams(final int clusterId, final int controlResponseStreamId)\n    {\n        final String controlChannel = \"aeron:ipc?term-length=64k\";\n        final int localControlStreamId = 0;\n        System.setProperty(AeronArchive.Configuration.LOCAL_CONTROL_CHANNEL_PROP_NAME, controlChannel);\n        System.setProperty(\n            AeronArchive.Configuration.LOCAL_CONTROL_STREAM_ID_PROP_NAME, Integer.toString(localControlStreamId));\n        System.setProperty(\n            AeronArchive.Configuration.CONTROL_RESPONSE_STREAM_ID_PROP_NAME, Integer.toString(controlResponseStreamId));\n        context.archiveContext(null).serviceId(5).clusterId(clusterId);\n        assertNull(context.archiveContext());\n\n        try\n        {\n            context.conclude();\n\n            final AeronArchive.Context archiveContext = context.archiveContext();\n            assertNotNull(archiveContext);\n            assertThat(\n                archiveContext.controlRequestChannel(),\n                Matchers.containsString(\"alias=sc-5-archive-ctrl-req-cluster-\" + clusterId));\n            assertThat(\n                archiveContext.controlResponseChannel(),\n                Matchers.containsString(\"alias=sc-5-archive-ctrl-resp-cluster-\" + clusterId));\n            assertEquals(localControlStreamId, archiveContext.controlRequestStreamId());\n            assertEquals(\n                clusterId * 100 + 100 + controlResponseStreamId + (context.serviceId() + 1),\n                archiveContext.controlResponseStreamId());\n        }\n        finally\n        {\n            System.clearProperty(AeronArchive.Configuration.LOCAL_CONTROL_CHANNEL_PROP_NAME);\n            System.clearProperty(AeronArchive.Configuration.LOCAL_CONTROL_STREAM_ID_PROP_NAME);\n            System.clearProperty(AeronArchive.Configuration.CONTROL_RESPONSE_STREAM_ID_PROP_NAME);\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"aeron:ipc,aeron:ipc?term-length=64k|mtu=8k,\" +\n            \"aeron:ipc?alias=sc-8-archive-ctrl-req-cluster-99,\" +\n            \"aeron:ipc?term-length=64k|mtu=8k|alias=sc-8-archive-ctrl-resp-cluster-99\",\n        \"aeron:ipc?alias=x,aeron:ipc?alias=y,aeron:ipc?alias=x,aeron:ipc?alias=y\"\n    })\n    void shouldCreateAliasForControlStreamsEvenWhenArchiveContextAssignedExplicitly(\n        final String controlRequestChannel,\n        final String controlResponseChannel,\n        final String expectedControlRequestChannel,\n        final String expectedControlResponseChannel)\n    {\n        final AeronArchive.Context archiveContext = new AeronArchive.Context()\n            .controlRequestChannel(controlRequestChannel)\n            .controlResponseChannel(controlResponseChannel)\n            .controlRequestStreamId(42)\n            .controlResponseStreamId(18);\n        context.archiveContext(archiveContext).clusterId(99).serviceId(8);\n\n        context.conclude();\n\n        assertEquals(\n            ChannelUri.parse(archiveContext.controlRequestChannel()),\n            ChannelUri.parse(expectedControlRequestChannel));\n        assertEquals(\n            ChannelUri.parse(archiveContext.controlResponseChannel()),\n            ChannelUri.parse(expectedControlResponseChannel));\n        assertEquals(42, archiveContext.controlRequestStreamId());\n        assertEquals(18, archiveContext.controlResponseStreamId());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 8192, 32 * 1024 })\n    void shouldAlignMarkFileToTheAeronClientFilePageSize(final int filePageSize)\n    {\n        final Aeron.Context aeronContext = context.aeron().context();\n        when(aeronContext.filePageSize()).thenReturn(filePageSize);\n\n        context.conclude();\n\n        final File file = new File(context.markFileDir(), ClusterMarkFile.markFilenameForService(serviceId));\n        assertTrue(file.exists());\n        assertEquals(BitUtil.align(context.errorBufferLength() + HEADER_LENGTH, filePageSize), file.length());\n\n        verify(aeronContext).filePageSize();\n    }\n\n    @Test\n    void shouldAlignMarkFileBasedOnTheMediaDriverFilePageSize() throws IOException\n    {\n        final Path aeronDir = Paths.get(CommonContext.generateRandomDirName());\n        Files.createDirectories(aeronDir);\n\n        final int filePageSize = 1024 * 1024;\n        try (TestMediaDriver driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.toString())\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .filePageSize(filePageSize), systemTestWatcher))\n        {\n            context\n                .aeron(null)\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .errorBufferLength(1919191);\n\n            context.conclude();\n\n            final File file = new File(context.markFileDir(), ClusterMarkFile.markFilenameForService(serviceId));\n            assertTrue(file.exists());\n            assertEquals(BitUtil.align(context.errorBufferLength() + HEADER_LENGTH, filePageSize), file.length());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/java/io/aeron/cluster/service/ServiceSnapshotTakerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster.service;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.cluster.codecs.ClientSessionDecoder;\nimport io.aeron.cluster.codecs.ClientSessionEncoder;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\nimport org.mockito.stubbing.Answer;\n\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.Publication.ADMIN_ACTION;\nimport static io.aeron.Publication.BACK_PRESSURED;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.agrona.BitUtil.align;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nclass ServiceSnapshotTakerTest\n{\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[4096]);\n    private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n    private final ClientSessionDecoder clientSessionDecoder = new ClientSessionDecoder();\n    private final ExclusivePublication publication = mock(ExclusivePublication.class);\n    private final IdleStrategy idleStrategy = mock(IdleStrategy.class);\n    private final ServiceSnapshotTaker serviceSnapshotTaker = new ServiceSnapshotTaker(publication, idleStrategy, null);\n\n    @Test\n    void snapshotSessionUsesTryClaimIfDataFitIntoMaxPayloadSize()\n    {\n        final int offset = 40;\n        final String responseChannel = \"aeron:udp?endpoint=localhost:8080\";\n        final byte[] encodedPrincipal = new byte[100];\n        ThreadLocalRandom.current().nextBytes(encodedPrincipal);\n        final ContainerClientSession session =\n            new ContainerClientSession(42, 8, responseChannel, encodedPrincipal, null);\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ClientSessionEncoder.BLOCK_LENGTH +\n            ClientSessionEncoder.responseChannelHeaderLength() + responseChannel.length() +\n            ClientSessionEncoder.encodedPrincipalHeaderLength() + encodedPrincipal.length;\n        when(publication.maxPayloadLength()).thenReturn(length);\n        when(publication.tryClaim(eq(length), any()))\n            .thenReturn(BACK_PRESSURED)\n            .thenAnswer(mockTryClaim(offset));\n\n        serviceSnapshotTaker.snapshotSession(session);\n\n        final InOrder inOrder = inOrder(idleStrategy, publication);\n        inOrder.verify(publication).maxPayloadLength();\n        inOrder.verify(idleStrategy).reset();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).tryClaim(anyInt(), any());\n        inOrder.verifyNoMoreInteractions();\n\n        clientSessionDecoder.wrapAndApplyHeader(buffer, offset + HEADER_LENGTH, messageHeaderDecoder);\n        assertEquals(session.id(), clientSessionDecoder.clusterSessionId());\n        assertEquals(session.responseStreamId(), clientSessionDecoder.responseStreamId());\n        assertEquals(responseChannel, clientSessionDecoder.responseChannel());\n        assertEquals(encodedPrincipal.length, clientSessionDecoder.encodedPrincipalLength());\n        final byte[] snapshotPrincipal = new byte[encodedPrincipal.length];\n        clientSessionDecoder.getEncodedPrincipal(snapshotPrincipal, 0, snapshotPrincipal.length);\n        assertArrayEquals(encodedPrincipal, snapshotPrincipal);\n    }\n\n    @Test\n    void snapshotSessionUsesOfferIfDataDoesNotIntoMaxPayloadSize()\n    {\n        final String responseChannel = \"aeron:udp?endpoint=localhost:8080|alias=long time ago\";\n        final byte[] encodedPrincipal = new byte[1000];\n        ThreadLocalRandom.current().nextBytes(encodedPrincipal);\n        final ContainerClientSession session =\n            new ContainerClientSession(8, -3, responseChannel, encodedPrincipal, null);\n        final int length = MessageHeaderEncoder.ENCODED_LENGTH + ClientSessionEncoder.BLOCK_LENGTH +\n            ClientSessionEncoder.responseChannelHeaderLength() + responseChannel.length() +\n            ClientSessionEncoder.encodedPrincipalHeaderLength() + encodedPrincipal.length;\n        when(publication.maxPayloadLength()).thenReturn(20);\n        when(publication.offer(any(), eq(0), eq(length)))\n            .thenReturn(BACK_PRESSURED, ADMIN_ACTION)\n            .thenAnswer(mockOffer());\n\n        serviceSnapshotTaker.snapshotSession(session);\n\n        final InOrder inOrder = inOrder(idleStrategy, publication);\n        inOrder.verify(publication).maxPayloadLength();\n        inOrder.verify(idleStrategy).reset();\n        inOrder.verify(publication).offer(any(), anyInt(), anyInt());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).offer(any(), anyInt(), anyInt());\n        inOrder.verify(idleStrategy).idle();\n        inOrder.verify(publication).offer(any(), anyInt(), anyInt());\n        inOrder.verifyNoMoreInteractions();\n\n        clientSessionDecoder.wrapAndApplyHeader(buffer, 0, messageHeaderDecoder);\n        assertEquals(session.id(), clientSessionDecoder.clusterSessionId());\n        assertEquals(session.responseStreamId(), clientSessionDecoder.responseStreamId());\n        assertEquals(responseChannel, clientSessionDecoder.responseChannel());\n        assertEquals(encodedPrincipal.length, clientSessionDecoder.encodedPrincipalLength());\n        final byte[] snapshotPrincipal = new byte[encodedPrincipal.length];\n        clientSessionDecoder.getEncodedPrincipal(snapshotPrincipal, 0, snapshotPrincipal.length);\n        assertArrayEquals(encodedPrincipal, snapshotPrincipal);\n    }\n\n    private Answer<Long> mockTryClaim(final int offset)\n    {\n        return (invocation) ->\n        {\n            final int length = invocation.getArgument(0);\n            final BufferClaim bufferClaim = invocation.getArgument(1);\n            final int frameLength = length + HEADER_LENGTH;\n            final int alignedFrameLength = align(frameLength, FRAME_ALIGNMENT);\n            bufferClaim.wrap(buffer, offset, alignedFrameLength);\n            return 1024L;\n        };\n    }\n\n    private Answer<Long> mockOffer()\n    {\n        return (invocation) ->\n        {\n            final DirectBuffer srcBuffer = invocation.getArgument(0);\n            final int srcOffset = invocation.getArgument(1);\n            final int length = invocation.getArgument(2);\n            buffer.putBytes(0, srcBuffer, srcOffset, length);\n            return 128L;\n        };\n    }\n}\n"
  },
  {
    "path": "aeron-cluster/src/test/resources/aeron-cluster-mark-codecs-v0.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<sbe:messageSchema xmlns:sbe=\"http://fixprotocol.io/2016/sbe\"\n                   package=\"io.aeron.cluster.codecs.mark\"\n                   id=\"110\"\n                   version=\"0\"\n                   semanticVersion=\"5.3\"\n                   description=\"Codecs for Mark file of an Aeron Cluster.\"\n                   byteOrder=\"littleEndian\">\n    <types>\n        <composite name=\"messageHeader\" description=\"Message identifiers and length of message root.\">\n            <type name=\"blockLength\" primitiveType=\"uint16\"/>\n            <type name=\"templateId\"  primitiveType=\"uint16\"/>\n            <type name=\"schemaId\"    primitiveType=\"uint16\"/>\n            <type name=\"version\"     primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"groupSizeEncoding\" description=\"Repeating group dimensions.\">\n            <type name=\"blockLength\" primitiveType=\"uint16\"/>\n            <type name=\"numInGroup\"  primitiveType=\"uint16\"/>\n        </composite>\n        <composite name=\"varAsciiEncoding\" description=\"Variable length ASCII string header.\">\n            <type name=\"length\"      primitiveType=\"uint32\" maxValue=\"1073741824\"/>\n            <type name=\"varData\"     primitiveType=\"uint8\" length=\"0\" characterEncoding=\"US-ASCII\"/>\n        </composite>\n        <type name=\"time_t\" primitiveType=\"int64\" description=\"Epoch time in milliseconds since 1 Jan 1970 UTC.\"/>\n        <enum name=\"ClusterComponentType\" encodingType=\"int32\" description=\"Type of Cluster Component\">\n            <validValue name=\"NULL\">0</validValue>\n            <validValue name=\"CONSENSUS_MODULE\">1</validValue>\n            <validValue name=\"CONTAINER\">2</validValue>\n            <validValue name=\"BACKUP\">3</validValue>\n        </enum>\n    </types>\n\n    <sbe:message name=\"MarkFileHeader\"\n                 id=\"200\"\n                 blockLength=\"128\"\n                 description=\"Header of Consensus Module and Container Mark file.\">\n        <field name=\"version\"                 id=\"1\"  type=\"int32\"/>\n        <field name=\"componentType\"           id=\"2\"  type=\"ClusterComponentType\"/>\n        <field name=\"activityTimestamp\"       id=\"3\"  type=\"time_t\"/>\n        <field name=\"startTimestamp\"          id=\"4\"  type=\"time_t\"/>\n        <field name=\"pid\"                     id=\"5\"  type=\"int64\"/>\n        <field name=\"candidateTermId\"         id=\"6\"  type=\"int64\"/>\n        <field name=\"archiveStreamId\"         id=\"7\"  type=\"int32\"/>\n        <field name=\"serviceStreamId\"         id=\"8\"  type=\"int32\"/>\n        <field name=\"consensusModuleStreamId\" id=\"9\"  type=\"int32\"/>\n        <field name=\"ingressStreamId\"         id=\"10\" type=\"int32\"/>\n        <field name=\"memberId\"                id=\"11\" type=\"int32\"/>\n        <field name=\"serviceId\"               id=\"12\" type=\"int32\"/>\n        <field name=\"headerLength\"            id=\"13\" type=\"int32\"/>\n        <field name=\"errorBufferLength\"       id=\"14\" type=\"int32\"/>\n        <field name=\"clusterId\"               id=\"15\" type=\"int32\"/>\n        <data  name=\"aeronDirectory\"          id=\"16\" type=\"varAsciiEncoding\"/>\n        <data  name=\"controlChannel\"          id=\"17\" type=\"varAsciiEncoding\"/>\n        <data  name=\"ingressChannel\"          id=\"18\" type=\"varAsciiEncoding\"/>\n        <data  name=\"serviceName\"             id=\"19\" type=\"varAsciiEncoding\"/>\n        <data  name=\"authenticator\"           id=\"20\" type=\"varAsciiEncoding\"/>\n    </sbe:message>\n\n</sbe:messageSchema>\n"
  },
  {
    "path": "aeron-config.cmake.in",
    "content": "@PACKAGE_INIT@\n\ninclude(CMakeFindDependencyMacro)\nfind_package(Threads)\nfind_package(Java)\n\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/aeron-targets.cmake\")\n\ncheck_required_components(aeron)"
  },
  {
    "path": "aeron-driver/README.md",
    "content": "Aeron Media Driver\n===\n\n[![Javadocs](http://www.javadoc.io/badge/io.aeron/aeron-all.svg)](http://www.javadoc.io/doc/io.aeron/aeron-all)\n\nThe media driver can run in, or out of, process with aeron clients. It is responsible for replicating publications\nto appear as images over a network, aka the media.\n"
  },
  {
    "path": "aeron-driver/src/main/c/CMakeLists.txt",
    "content": "# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ninclude(CheckSymbolExists)\ninclude(CheckIncludeFile)\ninclude(CheckTypeSize)\n\nif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n    set(CMAKE_REQUIRED_DEFINITIONS \"-D_GNU_SOURCE\")\n    add_definitions(-D_DEFAULT_SOURCE)\nendif ()\n\nif (MSVC AND \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\")\n    set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)\n    set(BUILD_SHARED_LIBS ON)\nendif ()\n\nif (NOT MSVC)\n    message(STATUS \"Looking up: /dev/urandom\")\n    execute_process(\n        COMMAND ls /dev/urandom\n        RESULT_VARIABLE DEV_URANDOM_EXISTS\n        OUTPUT_QUIET\n    )\nendif ()\n\ncheck_include_file(\"bsd/stdlib.h\" BSDSTDLIB_H_EXISTS)\ncheck_include_file(\"uuid/uuid.h\" UUID_H_EXISTS)\nfind_library(LIBBSD_EXISTS NAMES bsd libbsd)\nfind_library(LIBUUID_EXISTS NAMES uuid libuuid libuuid.dll)\n\nif (LIBBSD_EXISTS)\n    set(CMAKE_REQUIRED_LIBRARIES \"${CMAKE_REQUIRED_LIBRARIES} -lbsd\")\nendif ()\n\nif (LIBUUID_EXISTS)\n    set(CMAKE_REQUIRED_LIBRARIES \"${CMAKE_REQUIRED_LIBRARIES} -luuid\")\nendif ()\n\nif (NOT BSDSTDLIB_H_EXISTS)\n    check_symbol_exists(arc4random \"stdlib.h\" ARC4RANDOM_PROTOTYPE_EXISTS)\nelse ()\n    add_definitions(-DHAVE_BSDSTDLIB_H)\n    check_symbol_exists(arc4random \"bsd/stdlib.h\" ARC4RANDOM_PROTOTYPE_EXISTS)\nendif ()\n\nif (UUID_H_EXISTS)\n    add_definitions(-DHAVE_UUID_H)\nendif ()\n\nif (MSVC AND \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\")\n    set(AERON_LIB_WINSOCK_LIBS wsock32 ws2_32 Iphlpapi)\n    set(WSAPOLL_PROTOTYPE_EXISTS True)\nendif ()\n\ncheck_symbol_exists(uuid_generate \"uuid/uuid.h\" UUID_GENERATE_PROTOTYPE_EXISTS)\n\ncheck_symbol_exists(poll \"poll.h\" POLL_PROTOTYPE_EXISTS)\ncheck_symbol_exists(epoll_create \"sys/epoll.h\" EPOLL_PROTOTYPE_EXISTS)\n\nset(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h)\ncheck_type_size(\"struct mmsghdr\" STRUCT_MMSGHDR_TYPE_EXISTS)\nset(CMAKE_EXTRA_INCLUDE_FILES)\n\ncheck_symbol_exists(recvmmsg \"sys/socket.h\" RECVMMSG_PROTOTYPE_EXISTS)\ncheck_symbol_exists(sendmmsg \"sys/socket.h\" SENDMMSG_PROTOTYPE_EXISTS)\ncheck_symbol_exists(fallocate \"fcntl.h\" FALLOCATE_PROTOTYPE_EXISTS)\ncheck_symbol_exists(posix_fallocate \"fcntl.h\" POSIX_FALLOCATE_PROTOTYPE_EXISTS)\ncheck_symbol_exists(F_PREALLOCATE \"fcntl.h\" F_PREALLOCATE_PROTOTYPE_EXISTS)\n\nif (FALLOCATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_FALLOCATE)\nendif ()\n\nif (POSIX_FALLOCATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_POSIX_FALLOCATE)\nendif ()\n\nif (F_PREALLOCATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_F_PREALLOCATE)\nendif ()\n\nif (ARC4RANDOM_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_ARC4RANDOM)\nelse ()\n    message(WARNING \"Could not find arc4random. If on Linux, is libbsd installed?\")\nendif ()\n\nif (DEV_URANDOM_EXISTS EQUAL 0)\n    message(STATUS \"Found: /dev/urandom\")\n    add_definitions(-DHAVE_DEV_URANDOM)\nendif ()\n\nif (UUID_GENERATE_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_UUID_GENERATE)\nelse ()\n    message(WARNING \"Could not find uuid_generate. If on Linux, is libuuid installed?\")\nendif ()\n\nif (POLL_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_POLL)\nendif ()\n\nif (EPOLL_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_EPOLL)\nendif ()\n\nif (WSAPOLL_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_WSAPOLL)\nendif ()\n\nif (NOT POLL_PROTOTYPE_EXISTS AND NOT EPOLL_PROTOTYPE_EXISTS AND NOT WSAPOLL_PROTOTYPE_EXISTS)\n    message(FATAL_ERROR \"Unsupported configuration: neither POLL nor EPOLL nor WSAPoll found\")\nendif ()\n\nif (STRUCT_MMSGHDR_TYPE_EXISTS)\n    add_definitions(-DHAVE_STRUCT_MMSGHDR)\nendif ()\n\nif (RECVMMSG_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_RECVMMSG)\nendif ()\n\nif (SENDMMSG_PROTOTYPE_EXISTS)\n    add_definitions(-DHAVE_SENDMMSG)\nendif ()\n\nSET(C_CLIENT_SOURCE\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_array_to_ptr_hash_map.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_bit_set.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_hashing.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_int64_counter_map.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_int64_to_ptr_hash_map.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_int64_to_tagged_ptr_hash_map.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_linked_queue.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_map.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_str_to_ptr_hash_map.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_atomic.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_blocking_linked_queue.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_broadcast_receiver.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_broadcast_transmitter.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_counters_manager.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_distinct_error_log.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_executor.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_logbuffer_descriptor.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_mpsc_concurrent_array_queue.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_mpsc_rb.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_spsc_concurrent_array_queue.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_spsc_rb.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_gap_filler.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_gap_scanner.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_rebuilder.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_scanner.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_unblocker.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_thread.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/status/aeron_local_sockaddr.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/protocol/aeron_udp_protocol.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/reports/aeron_loss_reporter.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_arrayutil.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_bitutil.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_clock.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_deque.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_dlopen.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_env.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_error.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_fileutil.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_http_util.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_math.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_netutil.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_parse_util.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_properties_util.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_strutil.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_symbol_table.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/uri/aeron_uri.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_agent.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_alloc.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_client.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_client_conductor.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_cnc.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_cnc_file_descriptor.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_context.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_counter.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_exclusive_publication.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_fragment_assembler.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_image.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_log_buffer.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_publication.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_socket.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_subscription.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_windows.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeronc.c\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_version.c)\n\nset(C_CLIENT_HEADERS\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_array_to_ptr_hash_map.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_bit_set.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_hashing.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_int64_counter_map.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_int64_to_ptr_hash_map.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_int64_to_tagged_ptr_hash_map.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_linked_queue.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_map.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/collections/aeron_str_to_ptr_hash_map.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/command/aeron_control_protocol.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_atomic.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_blocking_linked_queue.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_atomic64_gcc_x86_64.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_atomic64_msvc.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_broadcast_descriptor.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_broadcast_receiver.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_broadcast_transmitter.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_concurrent_array_queue.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_counters_manager.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_distinct_error_log.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_executor.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_logbuffer_descriptor.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_mpsc_concurrent_array_queue.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_mpsc_rb.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_rb.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_spsc_concurrent_array_queue.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_spsc_rb.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_gap_filler.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_gap_scanner.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_rebuilder.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_scanner.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_term_unblocker.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/concurrent/aeron_thread.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/protocol/aeron_udp_protocol.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/reports/aeron_loss_reporter.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_arrayutil.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_bitutil.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_clock.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_deque.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_dlopen.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_env.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_error.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_fileutil.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_http_util.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_math.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_netutil.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_parse_util.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_platform.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_properties_util.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_strutil.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/util/aeron_symbol_table.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_agent.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_alloc.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_client.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_client_conductor.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_cnc_file_descriptor.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_common.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_context.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_counter.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_exclusive_publication.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_fragment_assembler.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_image.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_log_buffer.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_publication.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_socket.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_subscription.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeron_windows.h\n    ${AERON_C_CLIENT_SOURCE_PATH}/aeronc.h)\n\nSET(SOURCE\n    ${C_CLIENT_SOURCE}\n    agent/aeron_driver_agent.c\n    concurrent/aeron_logbuffer_unblocker.c\n    media/aeron_receive_channel_endpoint.c\n    media/aeron_receive_destination.c\n    media/aeron_send_channel_endpoint.c\n    media/aeron_timestamps.c\n    media/aeron_udp_channel.c\n    media/aeron_udp_channel_transport.c\n    media/aeron_udp_channel_transport_bindings.c\n    media/aeron_udp_channel_transport_fixed_loss.c\n    media/aeron_udp_channel_transport_multi_gap_loss.c\n    media/aeron_udp_channel_transport_loss.c\n    media/aeron_udp_destination_tracker.c\n    media/aeron_udp_transport_poller.c\n    uri/aeron_driver_uri.c\n    aeron_congestion_control.c\n    aeron_csv_table_name_resolver.c\n    aeron_data_packet_dispatcher.c\n    aeron_driver_version.c\n    aeron_driver.c\n    aeron_driver_conductor.c\n    aeron_driver_conductor_proxy.c\n    aeron_driver_context.c\n    aeron_driver_name_resolver.c\n    aeron_driver_receiver.c\n    aeron_driver_receiver_proxy.c\n    aeron_driver_sender.c\n    aeron_driver_sender_proxy.c\n    aeron_flow_control.c\n    aeron_ipc_publication.c\n    aeron_loss_detector.c\n    aeron_min_flow_control.c\n    aeron_name_resolver.c\n    aeron_name_resolver_cache.c\n    aeron_network_publication.c\n    aeron_port_manager.c\n    aeron_position.c\n    aeron_publication_image.c\n    aeron_retransmit_handler.c\n    aeron_system_counters.c\n    aeron_termination_validator.c)\n\nSET(HEADERS\n    ${C_CLIENT_HEADERS}\n    agent/aeron_driver_agent.h\n    concurrent/aeron_logbuffer_unblocker.h\n    media/aeron_receive_channel_endpoint.h\n    media/aeron_receive_destination.h\n    media/aeron_send_channel_endpoint.h\n    media/aeron_timestamps.h\n    media/aeron_udp_channel.h\n    media/aeron_udp_channel_transport.h\n    media/aeron_udp_channel_transport_bindings.h\n    media/aeron_udp_channel_transport_fixed_loss.h\n    media/aeron_udp_channel_transport_multi_gap_loss.h\n    media/aeron_udp_channel_transport_loss.h\n    media/aeron_udp_destination_tracker.h\n    media/aeron_udp_transport_poller.h\n    uri/aeron_driver_uri.h\n    aeron_congestion_control.h\n    aeron_csv_table_name_resolver.h\n    aeron_data_packet_dispatcher.h\n    aeron_driver.h\n    aeron_driver_common.h\n    aeron_driver_conductor.h\n    aeron_driver_conductor_proxy.h\n    aeron_driver_context.h\n    aeron_driver_name_resolver.h\n    aeron_driver_receiver.h\n    aeron_driver_receiver_proxy.h\n    aeron_driver_sender.h\n    aeron_driver_sender_proxy.h\n    aeron_duty_cycle_tracker.h\n    aeron_flow_control.h\n    aeron_ipc_publication.h\n    aeron_loss_detector.h\n    aeron_name_resolver.h\n    aeron_name_resolver_cache.h\n    aeron_network_publication.h\n    aeron_port_manager.h\n    aeron_position.h\n    aeron_publication_image.h\n    aeron_retransmit_handler.h\n    aeron_system_counters.h\n    aeron_termination_validator.h\n    aeron_driver_version.h\n    aeronmd.h)\n\nadd_library(aeron_driver SHARED ${SOURCE} ${HEADERS})\nadd_library(aeron::aeron_driver ALIAS aeron_driver)\ntarget_include_directories(aeron_driver PUBLIC\n    \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR};${AERON_C_CLIENT_SOURCE_PATH}>\"\n    \"$<INSTALL_INTERFACE:include/aeronmd;include/aeron>\")\n\nadd_library(aeron_driver_static STATIC ${SOURCE} ${HEADERS})\nadd_library(aeron::aeron_driver_static ALIAS aeron_driver_static)\ntarget_include_directories(aeron_driver_static PUBLIC\n    \"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR};${AERON_C_CLIENT_SOURCE_PATH}>\"\n    \"$<INSTALL_INTERFACE:include/aeronmd;include/aeron>\")\n\nadd_executable(aeronmd aeronmd.c)\ntarget_include_directories(aeronmd\n    PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${AERON_C_CLIENT_SOURCE_PATH})\n\nadd_executable(aeronmd_s aeronmd.c)\ntarget_include_directories(aeronmd_s\n    PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${AERON_C_CLIENT_SOURCE_PATH})\n\nset(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -DDISABLE_BOUNDS_CHECKS\")\n\nif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n    if (LIBBSD_EXISTS)\n        set(AERON_LIB_BSD_LIBS bsd)\n    endif ()\n\n    if (LIBUUID_EXISTS)\n        set(AERON_LIB_UUID_LIBS uuid)\n    endif ()\n\n    set(AERON_LIB_M_LIBS m)\n\n    if (\"${CMAKE_SYSTEM_PROCESSOR}\" MATCHES \"aarch64\")\n        set(AERON_LIB_ATOMIC_LIBS atomic)\n    endif ()\nendif ()\n\nif (CYGWIN)\n    if (LIBUUID_EXISTS)\n        set(AERON_LIB_UUID_LIBS uuid)\n    endif ()\nendif ()\n\ntarget_link_libraries(\n    aeron_driver\n    ${CMAKE_DL_LIBS}\n    ${AERON_LIB_BSD_LIBS}\n    ${AERON_LIB_UUID_LIBS}\n    ${AERON_LIB_M_LIBS}\n    ${AERON_LIB_ATOMIC_LIBS}\n    ${CMAKE_THREAD_LIBS_INIT}\n    ${AERON_LIB_WINSOCK_LIBS})\n\ntarget_link_libraries(\n    aeron_driver_static\n    ${CMAKE_DL_LIBS}\n    ${AERON_LIB_BSD_LIBS}\n    ${AERON_LIB_UUID_LIBS}\n    ${AERON_LIB_M_LIBS}\n    ${AERON_LIB_ATOMIC_LIBS}\n    ${CMAKE_THREAD_LIBS_INIT}\n    ${AERON_LIB_WINSOCK_LIBS})\n\ntarget_link_libraries(\n    aeronmd\n    aeron_driver\n    ${CMAKE_DL_LIBS}\n    ${AERON_LIB_BSD_LIBS}\n    ${AERON_LIB_UUID_LIBS}\n    ${AERON_LIB_M_LIBS}\n    ${AERON_LIB_ATOMIC_LIBS}\n    ${CMAKE_THREAD_LIBS_INIT}\n    ${AERON_LIB_WINSOCK_LIBS})\n\ntarget_link_libraries(\n    aeronmd_s\n    aeron_driver_static\n    ${CMAKE_DL_LIBS}\n    ${AERON_LIB_BSD_LIBS}\n    ${AERON_LIB_UUID_LIBS}\n    ${AERON_LIB_M_LIBS}\n    ${AERON_LIB_ATOMIC_LIBS}\n    ${CMAKE_THREAD_LIBS_INIT}\n    ${AERON_LIB_WINSOCK_LIBS})\n\ntarget_compile_definitions(aeron_driver PRIVATE -DAERON_DRIVER)\ntarget_compile_definitions(aeron_driver_static PRIVATE -DAERON_DRIVER)\n\nif (AERON_INSTALL_TARGETS)\n    if (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n        set_target_properties(aeronmd PROPERTIES INSTALL_RPATH \"$ORIGIN/../lib\")\n    endif ()\n    install(\n        TARGETS aeron_driver aeron_driver_static\n        EXPORT aeron-targets\n        RUNTIME DESTINATION lib\n        LIBRARY DESTINATION lib\n        ARCHIVE DESTINATION lib)\n    install(TARGETS aeronmd aeronmd_s DESTINATION bin)\n    install(DIRECTORY ./ DESTINATION include/aeronmd FILES_MATCHING PATTERN \"*.h\")\nendif ()\n"
  },
  {
    "path": "aeron-driver/src/main/c/README.md",
    "content": "# C Media Driver\n\nHere you will find the source for the C Media Driver for Aeron. The build process builds\na binary for the driver and places it in the following location\n\n    ${CMAKE_CURRENT_BINARY_DIR}/binaries/aeronmd\n\n## Dependencies\n\nThe driver binary requires the following dependencies.\n\n- Aeron C Driver Library, source of which is included here and built and placed in `${CMAKE_CURRENT_BINARY_DIR}/lib`\n- Linux Dependencies:\n    - C Library (for the system built on)\n    - `-lpthread` - pthread Library\n    - `-ldl` - DL Library\n    - `-lbsd` - BSD Library (optional - will use /dev/urandom directly instead of arc4random if not available)\n    - `-luuid` - UUID Library (optional - will use pure random Receiver ID if not available)\n    - `-lm` - Math Library\n- Windows Dependencies:\n\t- Windows Version >= Vista \n\t- MSVC >= v141 (Visual Studio 2017)\n\t\n## Configuration\n\nAll configuration for the C media driver is done, currently, via environment variables. The variables are directly related to the Java properties\nfor the Java media driver. The environment variables simply have `_` in the place of `.`. For example, setting the environment variable `AERON_TERM_BUFFER_LENGTH` is equivalent\nto setting `aeron.term.buffer.length` in the Java media driver.\n\n## Operation\n\nThe driver can be started simply by executing `aeronmd`.\n\n    $ aeronmd\n\nThe driver can be stopped gracefully via `Control-C` or `SIGINT` just like the Java media driver.\n\n## Embedding the Driver\n\nThe C media driver may be embedded quite easily. An example is the driver main itself, \n[aeronmd.c](https://github.com/aeron-io/aeron/blob/master/aeron-driver/src/main/c/aeronmd.c), and the API is documented in \n[aeronmd.h](https://github.com/aeron-io/aeron/blob/master/aeron-driver/src/main/c/aeronmd.h).\n\n## Driver Logging\n\nThe C media driver uses DL interception for logging. To use this logging, add the\nAeron C Driver Agent Library to `LD_PRELOAD` or `DYLD_INSERT_LIBRARIES` (for Mac.\nAlso requires flat namespace) and set the environment variable, `AERON_EVENT_LOG`\nto a numeric mask for the events of interest. The following\n[script](https://github.com/aeron-io/aeron/blob/master/aeron-samples/scripts/logging-c-media-driver)\nmay be used for convenience.\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_congestion_control.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <math.h>\n#include \"util/aeron_parse_util.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_symbol_table.h\"\n#include \"aeron_congestion_control.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_position.h\"\n#include \"media/aeron_udp_channel.h\"\n\n#define AERON_CUBICCONGESTIONCONTROL_INITIALRTT_DEFAULT (100 * 1000LL)\n#define AERON_CUBICCONGESTIONCONTROL_SECOND_IN_NS (1 * 1000 * 1000 * 1000LL)\n\n#define AERON_CUBICCONGESTIONCONTROL_INITCWND (10)\n#define AERON_CUBICCONGESTIONCONTROL_C (0.4)\n#define AERON_CUBICCONGESTIONCONTROL_B (0.2)\n#define AERON_CUBICCONGESTIONCONTROL_RTT_TIMEOUT_MULTIPLE (4)\n\nstatic const aeron_symbol_table_func_t aeron_congestion_control_table[] =\n    {\n        {\n            \"default\",\n            \"aeron_congestion_control_default_strategy_supplier\",\n            (aeron_fptr_t)aeron_congestion_control_default_strategy_supplier\n        },\n        {\n            \"static\",\n            \"aeron_static_window_congestion_control_strategy_supplier\",\n            (aeron_fptr_t)aeron_static_window_congestion_control_strategy_supplier\n        },\n        {\n            \"cubic\",\n            \"aeron_cubic_congestion_control_strategy_supplier\",\n            (aeron_fptr_t)aeron_cubic_congestion_control_strategy_supplier\n        },\n    };\n\nstatic const size_t aeron_congestion_control_table_length =\n    sizeof(aeron_congestion_control_table) / sizeof(aeron_symbol_table_func_t);\n\naeron_congestion_control_strategy_supplier_func_t aeron_congestion_control_strategy_supplier_load(\n    const char *strategy_name)\n{\n    return (aeron_congestion_control_strategy_supplier_func_t)aeron_symbol_table_func_load(\n        aeron_congestion_control_table, aeron_congestion_control_table_length, strategy_name, \"congestion control\");\n}\n\nstruct aeron_static_window_congestion_control_strategy_state_stct\n{\n    int32_t window_length;\n};\n\ntypedef struct aeron_static_window_congestion_control_strategy_state_stct\n    aeron_static_window_congestion_control_strategy_state_t;\n\nbool aeron_static_window_congestion_control_strategy_should_measure_rtt(void *state, int64_t now_ns)\n{\n    return false;\n}\n\nvoid aeron_static_window_congestion_control_strategy_on_rttm_sent(void *state, int64_t now_ns)\n{\n}\n\nvoid aeron_static_window_congestion_control_strategy_on_rttm(\n    void *state, int64_t now_ns, int64_t rtt_ns, struct sockaddr_storage *source_address)\n{\n}\n\nint32_t aeron_static_window_congestion_control_strategy_on_track_rebuild(\n    void *state,\n    bool *should_force_sm,\n    int64_t now_ns,\n    int64_t new_consumption_position,\n    int64_t last_sm_position,\n    int64_t hwm_position,\n    int64_t starting_rebuild_position,\n    int64_t ending_rebuild_position,\n    bool loss_occurred)\n{\n    *should_force_sm = false;\n    return ((aeron_static_window_congestion_control_strategy_state_t *)state)->window_length;\n}\n\nint32_t aeron_static_window_congestion_control_strategy_initial_window_length(void *state)\n{\n    return ((aeron_static_window_congestion_control_strategy_state_t *)state)->window_length;\n}\n\nint32_t aeron_static_window_congestion_control_strategy_max_window_length(void *state)\n{\n    return ((aeron_static_window_congestion_control_strategy_state_t *)state)->window_length;\n}\n\nint aeron_congestion_control_strategy_fini(aeron_congestion_control_strategy_t *strategy)\n{\n    aeron_free(strategy->state);\n    aeron_free(strategy);\n\n    return 0;\n}\n\nint aeron_static_window_congestion_control_strategy_supplier(\n    aeron_congestion_control_strategy_t **strategy,\n    aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t term_length,\n    int32_t sender_mtu_length,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *src_address,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager)\n{\n    aeron_congestion_control_strategy_t *_strategy;\n\n    if (aeron_alloc((void **)&_strategy, sizeof(aeron_congestion_control_strategy_t)) < 0 ||\n        aeron_alloc(&_strategy->state, sizeof(aeron_static_window_congestion_control_strategy_state_t)) < 0)\n    {\n        return -1;\n    }\n\n    _strategy->should_measure_rtt = aeron_static_window_congestion_control_strategy_should_measure_rtt;\n    _strategy->on_rttm_sent = aeron_static_window_congestion_control_strategy_on_rttm_sent;\n    _strategy->on_rttm = aeron_static_window_congestion_control_strategy_on_rttm;\n    _strategy->on_track_rebuild = aeron_static_window_congestion_control_strategy_on_track_rebuild;\n    _strategy->initial_window_length = aeron_static_window_congestion_control_strategy_initial_window_length;\n    _strategy->max_window_length = aeron_static_window_congestion_control_strategy_max_window_length;\n    _strategy->fini = aeron_congestion_control_strategy_fini;\n\n    aeron_static_window_congestion_control_strategy_state_t *state = _strategy->state;\n    const int32_t initial_window_length = (int32_t)aeron_udp_channel_receiver_window(\n        channel, context->initial_window_length);\n\n    state->window_length = (int32_t)aeron_receiver_window_length(initial_window_length, term_length);\n\n    *strategy = _strategy;\n\n    return 0;\n}\n\nint aeron_congestion_control_default_strategy_supplier(\n    aeron_congestion_control_strategy_t **strategy,\n    aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t term_length,\n    int32_t sender_mtu_length,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *src_address,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager)\n{\n    const char *cc_str = aeron_uri_find_param_value(&channel->uri.params.udp.additional_params, AERON_URI_CC_KEY);\n    size_t scc_length = sizeof(AERON_STATICWINDOWCONGESTIONCONTROL_CC_PARAM_VALUE) + 1;\n    size_t ccc_length = sizeof(AERON_CUBICCONGESTIONCONTROL_CC_PARAM_VALUE) + 1;\n    int result = -1;\n\n    if (NULL == cc_str || 0 == strncmp(cc_str, AERON_STATICWINDOWCONGESTIONCONTROL_CC_PARAM_VALUE, scc_length))\n    {\n        result = aeron_static_window_congestion_control_strategy_supplier(\n            strategy,\n            channel,\n            stream_id,\n            session_id,\n            registration_id,\n            term_length,\n            sender_mtu_length,\n            control_address,\n            src_address,\n            context,\n            counters_manager);\n    }\n    else if (0 == strncmp(cc_str, AERON_CUBICCONGESTIONCONTROL_CC_PARAM_VALUE, ccc_length))\n    {\n        result = aeron_cubic_congestion_control_strategy_supplier(\n            strategy,\n            channel,\n            stream_id,\n            session_id,\n            registration_id,\n            term_length,\n            sender_mtu_length,\n            control_address,\n            src_address,\n            context,\n            counters_manager);\n    }\n\n    return result;\n}\n\nstruct aeron_cubic_congestion_control_strategy_state_stct\n{\n    bool tcp_mode;\n    bool measure_rtt;\n\n    int32_t initial_window_length;\n    int32_t max_window_length;\n    int32_t mtu;\n    int32_t max_cwnd;\n    int32_t cwnd;\n    int32_t w_max;\n    double k;\n\n    uint64_t initial_rtt_ns;\n    int64_t rtt_ns;\n    int64_t rtt_timeout_ns;\n    int64_t window_update_timeout_ns;\n    int64_t last_loss_timestamp_ns;\n    int64_t last_update_timestamp_ns;\n    int64_t last_rtt_timestamp_ns;\n\n    aeron_position_t rtt_indicator;\n    aeron_position_t window_indicator;\n\n    aeron_counters_manager_t *counters_manager;\n};\n\ntypedef struct aeron_cubic_congestion_control_strategy_state_stct aeron_cubic_congestion_control_strategy_state_t;\n\nbool aeron_cubic_congestion_control_strategy_should_measure_rtt(void *state, int64_t now_ns)\n{\n    aeron_cubic_congestion_control_strategy_state_t *cubic_state =\n        (aeron_cubic_congestion_control_strategy_state_t *)state;\n\n    return cubic_state->measure_rtt &&\n        ((cubic_state->last_rtt_timestamp_ns + cubic_state->rtt_timeout_ns) - now_ns < 0);\n}\n\nvoid aeron_cubic_congestion_control_strategy_on_rttm_sent(void *state, int64_t now_ns)\n{\n    aeron_cubic_congestion_control_strategy_state_t *cubic_state =\n        (aeron_cubic_congestion_control_strategy_state_t *)state;\n\n    cubic_state->last_rtt_timestamp_ns = now_ns;\n}\n\nvoid aeron_cubic_congestion_control_strategy_on_rttm(\n    void *state, int64_t now_ns, int64_t rtt_ns, struct sockaddr_storage *source_address)\n{\n    aeron_cubic_congestion_control_strategy_state_t *cubic_state =\n        (aeron_cubic_congestion_control_strategy_state_t *)state;\n\n    cubic_state->last_rtt_timestamp_ns = now_ns;\n    cubic_state->rtt_ns = rtt_ns;\n    aeron_counter_set_release(cubic_state->rtt_indicator.value_addr, rtt_ns);\n    cubic_state->rtt_timeout_ns =\n        (rtt_ns > (int64_t)cubic_state->initial_rtt_ns ? rtt_ns : (int64_t)cubic_state->initial_rtt_ns) *\n        AERON_CUBICCONGESTIONCONTROL_RTT_TIMEOUT_MULTIPLE;\n}\n\nint32_t aeron_cubic_congestion_control_strategy_on_track_rebuild(\n    void *state,\n    bool *should_force_sm,\n    int64_t now_ns,\n    int64_t new_consumption_position,\n    int64_t last_sm_position,\n    int64_t hwm_position,\n    int64_t starting_rebuild_position,\n    int64_t ending_rebuild_position,\n    bool loss_occurred)\n{\n    aeron_cubic_congestion_control_strategy_state_t *cubic_state =\n        (aeron_cubic_congestion_control_strategy_state_t *)state;\n    *should_force_sm = false;\n\n    if (loss_occurred)\n    {\n        *should_force_sm = true;\n        cubic_state->w_max = cubic_state->cwnd;\n        cubic_state->k = cbrt(\n            (double)cubic_state->w_max * AERON_CUBICCONGESTIONCONTROL_B / AERON_CUBICCONGESTIONCONTROL_C);\n\n        const int32_t cwnd = (int32_t)(cubic_state->cwnd * (1.0 - AERON_CUBICCONGESTIONCONTROL_B));\n        cubic_state->cwnd = cwnd > 1 ? cwnd : 1;\n        cubic_state->last_loss_timestamp_ns = now_ns;\n    }\n    else if (cubic_state->cwnd < cubic_state->max_cwnd &&\n        ((cubic_state->last_update_timestamp_ns + cubic_state->window_update_timeout_ns) - now_ns < 0))\n    {\n        // W_cubic = C(T - K)^3 + w_max\n        const double duration_since_decr =\n            (double)(now_ns - cubic_state->last_loss_timestamp_ns) / (double)AERON_CUBICCONGESTIONCONTROL_SECOND_IN_NS;\n        const double diff_to_k = duration_since_decr - cubic_state->k;\n        const double incr = AERON_CUBICCONGESTIONCONTROL_C * diff_to_k * diff_to_k * diff_to_k;\n\n        const int32_t cwnd = cubic_state->w_max + (int32_t)incr;\n        cubic_state->cwnd = cwnd < cubic_state->max_cwnd ? cwnd : cubic_state->max_cwnd;\n\n        // if using TCP mode, then check to see if we are in the TCP region\n        if (cubic_state->tcp_mode && cubic_state->cwnd < cubic_state->w_max)\n        {\n            // W_tcp(t) = w_max * (1 - B) + 3 * B / (2 - B) * t / RTT\n            const double rtt_in_seconds =\n                (double)cubic_state->rtt_ns / (double)AERON_CUBICCONGESTIONCONTROL_SECOND_IN_NS;\n            const double w_tcp = (double)cubic_state->w_max * (1.0 - AERON_CUBICCONGESTIONCONTROL_B) +\n                ((3.0 * AERON_CUBICCONGESTIONCONTROL_B / (2.0 - AERON_CUBICCONGESTIONCONTROL_B)) *\n                    (duration_since_decr / rtt_in_seconds));\n\n            const int32_t new_cwnd = (int32_t)w_tcp;\n            cubic_state->cwnd = new_cwnd > cubic_state->cwnd ? new_cwnd : cubic_state->cwnd;\n        }\n\n        cubic_state->last_update_timestamp_ns = now_ns;\n    }\n    else if (1 == cubic_state->cwnd && new_consumption_position > last_sm_position)\n    {\n        *should_force_sm = true;\n    }\n\n    const int32_t window = cubic_state->cwnd * cubic_state->mtu;\n    aeron_counter_set_release(cubic_state->window_indicator.value_addr, window);\n\n    return window;\n}\n\nint32_t aeron_cubic_congestion_control_strategy_initial_window_length(void *state)\n{\n    return ((aeron_cubic_congestion_control_strategy_state_t *)state)->initial_window_length;\n}\n\nint32_t aeron_cubic_congestion_control_strategy_max_window_length(void *state)\n{\n    return ((aeron_cubic_congestion_control_strategy_state_t *)state)->max_window_length;\n}\n\nint aeron_cubic_congestion_control_strategy_fini(aeron_congestion_control_strategy_t *strategy)\n{\n    aeron_cubic_congestion_control_strategy_state_t *state = strategy->state;\n    aeron_counters_manager_free(state->counters_manager, state->rtt_indicator.counter_id);\n    aeron_counters_manager_free(state->counters_manager, state->window_indicator.counter_id);\n\n    aeron_free(strategy->state);\n    aeron_free(strategy);\n\n    return 0;\n}\n\nint aeron_cubic_congestion_control_strategy_supplier(\n    aeron_congestion_control_strategy_t **strategy,\n    aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t term_length,\n    int32_t sender_mtu_length,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *src_address,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager)\n{\n    aeron_congestion_control_strategy_t *_strategy;\n\n    if (aeron_alloc((void **)&_strategy, sizeof(aeron_congestion_control_strategy_t)) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_alloc(&_strategy->state, sizeof(aeron_cubic_congestion_control_strategy_state_t)) < 0)\n    {\n        aeron_free(strategy);\n        return -1;\n    }\n\n    _strategy->should_measure_rtt = aeron_cubic_congestion_control_strategy_should_measure_rtt;\n    _strategy->on_rttm_sent = aeron_cubic_congestion_control_strategy_on_rttm_sent;\n    _strategy->on_rttm = aeron_cubic_congestion_control_strategy_on_rttm;\n    _strategy->on_track_rebuild = aeron_cubic_congestion_control_strategy_on_track_rebuild;\n    _strategy->initial_window_length = aeron_cubic_congestion_control_strategy_initial_window_length;\n    _strategy->max_window_length = aeron_cubic_congestion_control_strategy_max_window_length;\n    _strategy->fini = aeron_cubic_congestion_control_strategy_fini;\n\n    aeron_cubic_congestion_control_strategy_state_t *state = _strategy->state;\n\n    // Config values\n    state->tcp_mode = aeron_parse_bool(getenv(AERON_CUBICCONGESTIONCONTROL_TCPMODE_ENV_VAR), false);\n    state->measure_rtt = aeron_parse_bool(getenv(AERON_CUBICCONGESTIONCONTROL_MEASURERTT_ENV_VAR), false);\n    state->initial_rtt_ns = AERON_CUBICCONGESTIONCONTROL_INITIALRTT_DEFAULT;\n    char *const rtt_ns = getenv(AERON_CUBICCONGESTIONCONTROL_INITIALRTT_ENV_VAR);\n    if (NULL != rtt_ns)\n    {\n        if (-1 == aeron_parse_duration_ns(rtt_ns, &state->initial_rtt_ns))\n        {\n            goto error_cleanup;\n        }\n    }\n\n    state->mtu = sender_mtu_length;\n    const int32_t initial_window_length = (int32_t)aeron_udp_channel_receiver_window(\n        channel, context->initial_window_length);\n    state->max_window_length = (int32_t)aeron_receiver_window_length(initial_window_length, term_length);\n\n    state->max_cwnd = state->max_window_length / sender_mtu_length;\n    state->cwnd = state->max_cwnd > AERON_CUBICCONGESTIONCONTROL_INITCWND ?\n        AERON_CUBICCONGESTIONCONTROL_INITCWND : state->max_cwnd;\n    state->initial_window_length = state->cwnd * sender_mtu_length;\n\n    // initially set w_max to max window and act in the TCP and concave region initially\n    state->w_max = state->max_cwnd;\n    state->k = cbrt((double)state->w_max * AERON_CUBICCONGESTIONCONTROL_B / AERON_CUBICCONGESTIONCONTROL_C);\n\n    // determine interval for adjustment based on heuristic of MTU, max window, and/or RTT estimate\n    state->rtt_ns = (int64_t)state->initial_rtt_ns;\n    state->window_update_timeout_ns = state->rtt_ns;\n    state->rtt_timeout_ns = state->rtt_ns * AERON_CUBICCONGESTIONCONTROL_RTT_TIMEOUT_MULTIPLE;\n\n    const int32_t rtt_indicator_counter_id = aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_CUBICCONGESTIONCONTROL_RTT_INDICATOR_COUNTER_NAME,\n        AERON_COUNTER_PER_IMAGE_TYPE_ID,\n        AERON_NULL_VALUE,\n        registration_id,\n        session_id,\n        stream_id,\n        channel->uri_length,\n        channel->original_uri,\n        \"\");\n    if (rtt_indicator_counter_id < 0)\n    {\n        goto error_cleanup;\n    }\n\n    const int32_t window_indicator_counter_id = aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_CUBICCONGESTIONCONTROL_WINDOW_INDICATOR_COUNTER_NAME,\n        AERON_COUNTER_PER_IMAGE_TYPE_ID,\n        AERON_NULL_VALUE,\n        registration_id,\n        session_id,\n        stream_id,\n        channel->uri_length,\n        channel->original_uri,\n        \"\");\n    if (window_indicator_counter_id < 0)\n    {\n        aeron_counters_manager_free(counters_manager, rtt_indicator_counter_id);\n        goto error_cleanup;\n    }\n\n    state->counters_manager = counters_manager;\n\n    state->rtt_indicator.counter_id = rtt_indicator_counter_id;\n    state->rtt_indicator.value_addr = aeron_counters_manager_addr(counters_manager, rtt_indicator_counter_id);\n    aeron_counter_set_release(state->rtt_indicator.value_addr, 0);\n\n    state->window_indicator.counter_id = window_indicator_counter_id;\n    state->window_indicator.value_addr = aeron_counters_manager_addr(counters_manager, window_indicator_counter_id);\n    aeron_counter_set_release(state->window_indicator.value_addr, state->initial_window_length);\n\n    state->last_rtt_timestamp_ns = 0;\n\n    state->last_loss_timestamp_ns = aeron_clock_cached_nano_time(context->receiver_cached_clock);\n    state->last_update_timestamp_ns = state->last_loss_timestamp_ns;\n\n    *strategy = _strategy;\n    return 0;\n\nerror_cleanup:\n    aeron_congestion_control_strategy_fini(_strategy);\n    return -1;\n}\n\nint32_t aeron_cubic_congestion_control_strategy_get_max_cwnd(void *state)\n{\n    return ((aeron_cubic_congestion_control_strategy_state_t *)state)->max_cwnd;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_congestion_control.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_CONGESTION_CONTROL_H\n#define AERON_CONGESTION_CONTROL_H\n\n#include \"aeron_socket.h\"\n#include \"aeron_driver_common.h\"\n#include \"aeronmd.h\"\n\n#define AERON_STATICWINDOWCONGESTIONCONTROL_CC_PARAM_VALUE (\"static\")\n#define AERON_CUBICCONGESTIONCONTROL_CC_PARAM_VALUE (\"cubic\")\n\n#define AERON_CUBICCONGESTIONCONTROL_RTT_INDICATOR_COUNTER_NAME (\"rcv-cc-cubic-rtt\")\n#define AERON_CUBICCONGESTIONCONTROL_WINDOW_INDICATOR_COUNTER_NAME (\"rcv-cc-cubic-wnd\")\n\ntypedef struct aeron_congestion_control_strategy_stct aeron_congestion_control_strategy_t;\ntypedef struct aeron_driver_context_stct aeron_driver_context_t;\ntypedef struct aeron_counters_manager_stct aeron_counters_manager_t;\n\ntypedef bool (*aeron_congestion_control_strategy_should_measure_rtt_func_t)(void *state, int64_t now_ns);\n\ntypedef void (*aeron_congestion_control_strategy_on_rttm_sent_func_t)(void *state, int64_t now_ns);\n\ntypedef void (*aeron_congestion_control_strategy_on_rttm_func_t)(\n    void *state, int64_t now_ns, int64_t rtt_ns, struct sockaddr_storage *source_address);\n\ntypedef int32_t (*aeron_congestion_control_strategy_on_track_rebuild_func_t)(\n    void *state,\n    bool *should_force_sm,\n    int64_t now_ns,\n    int64_t new_consumption_position,\n    int64_t last_sm_position,\n    int64_t hwm_position,\n    int64_t starting_rebuild_position,\n    int64_t ending_rebuild_position,\n    bool loss_occurred);\n\ntypedef int32_t (*aeron_congestion_control_strategy_initial_window_length_func_t)(void *state);\n\ntypedef int32_t (*aeron_congestion_control_strategy_max_window_length_func_t)(void *state);\n\ntypedef int (*aeron_congestion_control_strategy_fini_func_t)(aeron_congestion_control_strategy_t *strategy);\n\nstruct aeron_congestion_control_strategy_stct\n{\n    aeron_congestion_control_strategy_should_measure_rtt_func_t should_measure_rtt;\n    aeron_congestion_control_strategy_on_rttm_sent_func_t on_rttm_sent;\n    aeron_congestion_control_strategy_on_rttm_func_t on_rttm;\n    aeron_congestion_control_strategy_on_track_rebuild_func_t on_track_rebuild;\n    aeron_congestion_control_strategy_initial_window_length_func_t initial_window_length;\n    aeron_congestion_control_strategy_max_window_length_func_t max_window_length;\n    aeron_congestion_control_strategy_fini_func_t fini;\n    void *state;\n};\n\ntypedef struct aeron_congestion_control_strategy_stct aeron_congestion_control_strategy_t;\n\naeron_congestion_control_strategy_supplier_func_t aeron_congestion_control_strategy_supplier_load(\n    const char *strategy_name);\n\nint aeron_congestion_control_default_strategy_supplier(\n    aeron_congestion_control_strategy_t **strategy,\n    aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t term_length,\n    int32_t sender_mtu_length,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *src_address,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager);\n\nint aeron_static_window_congestion_control_strategy_supplier(\n    aeron_congestion_control_strategy_t **strategy,\n    aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t term_length,\n    int32_t sender_mtu_length,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *src_address,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager);\n\nint aeron_cubic_congestion_control_strategy_supplier(\n    aeron_congestion_control_strategy_t **strategy,\n    aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t term_length,\n    int32_t sender_mtu_length,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *src_address,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager);\n\n#endif //AERON_CONGESTION_CONTROL_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_csv_table_name_resolver.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <string.h>\n#include <inttypes.h>\n\n#include \"util/aeron_error.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"command/aeron_control_protocol.h\"\n#include \"aeron_csv_table_name_resolver.h\"\n\n#define AERON_NAME_RESOLVER_CSV_TABLE_MAX_SIZE (1024)\n#define AERON_NAME_RESOLVER_CSV_TABLE_COLUMNS (3)\n\ntypedef struct aeron_csv_table_name_resolver_row_stct\n{\n    const char *name;\n    const char *initial_resolution_host;\n    const char *re_resolution_host;\n    aeron_atomic_counter_t operation_toggle;\n}\naeron_csv_table_name_resolver_row_t;\n\ntypedef struct aeron_csv_table_name_resolver_stct\n{\n    aeron_csv_table_name_resolver_row_t *array;\n    size_t length;\n    size_t capacity;\n    char *saved_config_csv;\n}\naeron_csv_table_name_resolver_t;\n\nint aeron_csv_table_name_resolver_resolve(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_resolution,\n    struct sockaddr_storage *address)\n{\n    const char *hostname = name;\n    if (NULL != resolver->state)\n    {\n        aeron_csv_table_name_resolver_t *table = (aeron_csv_table_name_resolver_t *)resolver->state;\n\n        for (size_t i = 0; i < table->length; i++)\n        {\n            if (strncmp(name, table->array[i].name, strlen(table->array[i].name) + 1) == 0)\n            {\n                int64_t operation;\n                AERON_GET_ACQUIRE(operation, *table->array[i].operation_toggle.value_addr);\n\n                if (AERON_NAME_RESOLVER_CSV_DISABLE_RESOLUTION_OP == operation)\n                {\n                    AERON_SET_ERR(-AERON_ERROR_CODE_UNKNOWN_HOST, \"Unable to resolve host=(%s): (forced)\", hostname);\n                    return -1;\n                }\n                else if (AERON_NAME_RESOLVER_CSV_USE_INITIAL_RESOLUTION_HOST_OP == operation)\n                {\n                    hostname = table->array[i].initial_resolution_host;\n                }\n                else if (AERON_NAME_RESOLVER_CSV_USE_RE_RESOLUTION_HOST_OP == operation)\n                {\n                    hostname = table->array[i].re_resolution_host;\n                }\n            }\n        }\n    }\n\n    int result = aeron_default_name_resolver_resolve(resolver, hostname, uri_param_name, is_re_resolution, address);\n\n    return result;\n}\n\nint aeron_csv_table_name_resolver_close(aeron_name_resolver_t *resolver)\n{\n    aeron_csv_table_name_resolver_t *resolver_state = (aeron_csv_table_name_resolver_t *)resolver->state;\n\n    if (NULL != resolver_state)\n    {\n        aeron_free(resolver_state->saved_config_csv);\n        aeron_free(resolver_state->array);\n        aeron_free(resolver_state);\n    }\n    return 0;\n}\n\nint aeron_csv_table_name_resolver_supplier(\n    aeron_name_resolver_t *resolver,\n    const char *args,\n    aeron_driver_context_t *context)\n{\n    resolver->lookup_func = aeron_default_name_resolver_lookup;\n    resolver->close_func = aeron_csv_table_name_resolver_close;\n    resolver->resolve_func = aeron_csv_table_name_resolver_resolve;\n    resolver->do_work_func = aeron_default_name_resolver_do_work;\n    resolver->name = \"csv\";\n\n    char *rows[AERON_NAME_RESOLVER_CSV_TABLE_MAX_SIZE];\n    char *columns[AERON_NAME_RESOLVER_CSV_TABLE_COLUMNS];\n    aeron_csv_table_name_resolver_t *lookup_table = NULL;\n\n    if (NULL == args)\n    {\n        AERON_SET_ERR(EINVAL, \"No CSV configuration, please specify: %s\", AERON_NAME_RESOLVER_INIT_ARGS_ENV_VAR);\n        goto error;\n    }\n\n    if (aeron_alloc((void **)&lookup_table, sizeof(aeron_csv_table_name_resolver_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Allocating lookup table\");\n        goto error;\n    }\n    resolver->state = lookup_table;\n\n    lookup_table->saved_config_csv = strdup(args);\n    if (NULL == lookup_table->saved_config_csv)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Duplicating config string\");\n        goto error;\n    }\n\n    int num_rows = aeron_tokenise(lookup_table->saved_config_csv, '|', AERON_NAME_RESOLVER_CSV_TABLE_MAX_SIZE, rows);\n    if (num_rows < 0)\n    {\n        AERON_SET_ERR(num_rows, \"%s\", \"Failed to parse rows for lookup table\");\n        goto error;\n    }\n\n    for (int i = num_rows; -1 < --i;)\n    {\n        int ensure_capacity_result = 0;\n        AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, (*lookup_table), aeron_csv_table_name_resolver_row_t)\n        if (ensure_capacity_result < 0)\n        {\n            AERON_APPEND_ERR(\n                \"Failed to allocate rows for lookup table (%\" PRIu64 \",%\" PRIu64 \")\",\n                (uint64_t)lookup_table->length,\n                (uint64_t)lookup_table->capacity);\n            goto error;\n        }\n\n        int num_columns = aeron_tokenise(rows[i], ',', AERON_NAME_RESOLVER_CSV_TABLE_COLUMNS, columns);\n        if (AERON_NAME_RESOLVER_CSV_TABLE_COLUMNS == num_columns)\n        {\n            // Fields are in reverse order.\n            aeron_csv_table_name_resolver_row_t *row = &lookup_table->array[lookup_table->length];\n            row->re_resolution_host = columns[0];\n            row->initial_resolution_host = columns[1];\n            row->name = columns[2];\n\n            uint8_t key_buffer[512] = { 0 };\n            uint32_t name_str_length = (uint32_t)strlen(row->name);\n            uint32_t name_length = name_str_length < sizeof(key_buffer) - sizeof(name_str_length) ?\n                name_str_length : sizeof(key_buffer) - sizeof(name_str_length);\n\n            memcpy(key_buffer, &name_length, sizeof(name_length));\n            memcpy(&key_buffer[sizeof(name_length)], row->name, name_length);\n\n            char value_buffer[512] = { 0 };\n            size_t value_buffer_maxlen = sizeof(value_buffer) - 1;\n\n            int value_buffer_result = snprintf(\n                value_buffer,\n                value_buffer_maxlen,\n                \"NameEntry{name='%s', initialResolutionHost='%s', reResolutionHost='%s'}\",\n                row->name,\n                row->initial_resolution_host,\n                row->re_resolution_host);\n            \n            if (value_buffer_result < 0)\n            {\n                AERON_SET_ERR(EINVAL, \"%s\", \"Failed to create csv resolver counter label\");\n                goto error;\n            }\n\n            size_t key_length = sizeof(name_length) + name_length;\n            size_t value_length = (size_t)value_buffer_result < value_buffer_maxlen ?\n                (size_t)value_buffer_result : value_buffer_maxlen;\n\n            row->operation_toggle.counter_id = aeron_counters_manager_allocate(\n                context->counters_manager, \n                AERON_NAME_RESOLVER_CSV_ENTRY_COUNTER_TYPE_ID,\n                key_buffer,\n                key_length,\n                value_buffer,\n                value_length);\n\n            if (row->operation_toggle.counter_id < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"Failed to allocate csv resolver counter\");\n                goto error;\n            }\n\n            row->operation_toggle.value_addr = aeron_counters_manager_addr(\n                context->counters_manager, row->operation_toggle.counter_id);\n\n            lookup_table->length++;\n        }\n    }\n\n    resolver->state = lookup_table;\n\n    return 0;\n\nerror:\n    aeron_csv_table_name_resolver_close(resolver);\n    return -1;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_csv_table_name_resolver.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_CSV_TABLE_NAME_RESOLVER_H\n#define AERON_CSV_TABLE_NAME_RESOLVER_H\n\n#include \"aeron_name_resolver.h\"\n\n#define AERON_NAME_RESOLVER_CSV_ENTRY_COUNTER_TYPE_ID (2001)\n#define AERON_NAME_RESOLVER_CSV_DISABLE_RESOLUTION_OP INT64_C(-1)\n#define AERON_NAME_RESOLVER_CSV_USE_INITIAL_RESOLUTION_HOST_OP INT64_C(0)\n#define AERON_NAME_RESOLVER_CSV_USE_RE_RESOLUTION_HOST_OP INT64_C(1)\n\nint aeron_csv_table_name_resolver_supplier(\n    aeron_name_resolver_t *resolver,\n    const char *args,\n    aeron_driver_context_t *context);\n\n#endif //AERON_CSV_TABLE_NAME_RESOLVER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_data_packet_dispatcher.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <inttypes.h>\n#include \"util/aeron_error.h\"\n#include \"aeron_publication_image.h\"\n#include \"aeron_driver_receiver.h\"\n\nint aeron_data_packet_dispatcher_init(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    aeron_driver_receiver_t *receiver)\n{\n    if (aeron_int64_to_ptr_hash_map_init(\n        &dispatcher->ignored_sessions_map, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init ignored_sessions_map\");\n        return -1;\n    }\n\n    if (aeron_int64_to_ptr_hash_map_init(\n        &dispatcher->session_by_stream_id_map, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init session_by_stream_id_map\");\n        return -1;\n    }\n\n    dispatcher->conductor_proxy = conductor_proxy;\n    dispatcher->receiver = receiver;\n    dispatcher->stream_session_limit = receiver->context->stream_session_limit;\n\n    return 0;\n}\n\nstatic int aeron_data_packet_dispatcher_stream_interest_init(\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest,\n    bool is_all_sessions)\n{\n    stream_interest->is_all_sessions = is_all_sessions;\n    if (aeron_int64_to_ptr_hash_map_init(\n        &stream_interest->image_by_session_id_map, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init image_by_session_id_map\");\n        return -1;\n    }\n\n    if (aeron_int64_to_ptr_hash_map_init(\n        &stream_interest->subscribed_sessions, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init subscribed_sessions\");\n        return -1;\n    }\n\n    if (aeron_int64_counter_map_init(\n        &stream_interest->state_by_session_id_map, -1, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to init image_by_session_id_map\");\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_data_packet_dispatcher_stream_interest_close(\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest)\n{\n    aeron_int64_to_ptr_hash_map_delete(&stream_interest->image_by_session_id_map);\n    aeron_int64_counter_map_delete(&stream_interest->state_by_session_id_map);\n    aeron_int64_to_ptr_hash_map_delete(&stream_interest->subscribed_sessions);\n    return 0;\n}\n\nstatic void aeron_data_packet_dispatcher_stream_interest_delete(\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest)\n{\n    aeron_data_packet_dispatcher_stream_interest_close(stream_interest);\n    aeron_free(stream_interest);\n}\n\nstatic void aeron_data_packet_dispatcher_delete_stream_interest(void *clientd, int64_t key, void *value)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest = value;\n    aeron_data_packet_dispatcher_stream_interest_delete(stream_interest);\n}\n\nint aeron_data_packet_dispatcher_close(aeron_data_packet_dispatcher_t *dispatcher)\n{\n    aeron_int64_to_ptr_hash_map_for_each(\n        &dispatcher->session_by_stream_id_map, aeron_data_packet_dispatcher_delete_stream_interest, dispatcher);\n    aeron_int64_to_ptr_hash_map_delete(&dispatcher->ignored_sessions_map);\n    aeron_int64_to_ptr_hash_map_delete(&dispatcher->session_by_stream_id_map);\n\n    return 0;\n}\n\nbool aeron_data_packet_dispatcher_stream_interest_for_session(\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest,\n    int32_t session_id)\n{\n    return stream_interest->is_all_sessions ||\n        NULL != aeron_int64_to_ptr_hash_map_get(&stream_interest->subscribed_sessions, session_id);\n}\n\nint aeron_data_packet_dispatcher_mark_image_pending_setup(\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest,\n    int32_t session_id,\n    bool *should_elicit_setup)\n{\n    *should_elicit_setup = false;\n\n    int64_t state = aeron_int64_counter_map_get(&stream_interest->state_by_session_id_map, session_id);\n\n    if (AERON_DATA_PACKET_DISPATCHER_IMAGE_ACTIVE == state || stream_interest->state_by_session_id_map.initial_value == state)\n    {\n        if (aeron_int64_counter_map_put(\n            &stream_interest->state_by_session_id_map,\n            session_id,\n            AERON_DATA_PACKET_DISPATCHER_IMAGE_PENDING_SETUP_FRAME,\n            NULL) < 0)\n        {\n            AERON_APPEND_ERR(\n                \"Unable to set IMAGE_PENDING_SETUP_FRAME for session_id (%\" PRId32 \") in image_by_session_id_map\",\n                session_id);\n            return -1;\n        }\n\n        *should_elicit_setup = true;\n    }\n\n    return 0;\n}\n\nbool aeron_data_packet_dispatcher_match_tombstone(void *clientd, int64_t key, int64_t value)\n{\n    return (int64_t)AERON_DATA_PACKET_DISPATCHER_IMAGE_NO_INTEREST == value;\n}\n\nbool aeron_data_packet_dispatcher_match_image_with_no_subscription(void *clientd, int64_t key, void *value)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest = clientd;\n\n    return NULL == aeron_int64_to_ptr_hash_map_get(&stream_interest->subscribed_sessions, key);\n}\n\nbool aeron_data_packet_dispatcher_match_state_with_no_subscription(void *clientd, int64_t key, int64_t value)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest = clientd;\n\n    return NULL == aeron_int64_to_ptr_hash_map_get(&stream_interest->subscribed_sessions, key);\n}\n\nbool aeron_data_packet_dispatcher_match_no_subscription(void *clientd, int64_t key, uint32_t tag, void *value)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest = clientd;\n\n    return NULL == aeron_int64_to_ptr_hash_map_get(&stream_interest->subscribed_sessions, key);\n}\n\nint aeron_data_packet_dispatcher_add_subscription(aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest = aeron_int64_to_ptr_hash_map_get(\n        &dispatcher->session_by_stream_id_map, stream_id);\n\n    if (NULL == stream_interest)\n    {\n        if (aeron_alloc((void **)&stream_interest, sizeof(aeron_data_packet_dispatcher_stream_interest_t)) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to allocate stream_interest\");\n            return -1;\n        }\n\n        if (aeron_data_packet_dispatcher_stream_interest_init(stream_interest, true) < 0 ||\n            aeron_int64_to_ptr_hash_map_put(&dispatcher->session_by_stream_id_map, stream_id, stream_interest) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to add stream_interest to session_by_stream_id_map\");\n            aeron_free(stream_interest);\n            return -1;\n        }\n    }\n    else if (!stream_interest->is_all_sessions)\n    {\n        stream_interest->is_all_sessions = true;\n\n        aeron_int64_counter_map_remove_if(\n            &stream_interest->state_by_session_id_map, aeron_data_packet_dispatcher_match_tombstone, NULL);\n    }\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_add_subscription_by_session(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id, int32_t session_id)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest = aeron_int64_to_ptr_hash_map_get(\n        &dispatcher->session_by_stream_id_map, stream_id);\n\n    if (NULL == stream_interest)\n    {\n        if (aeron_alloc((void **)&stream_interest, sizeof(aeron_data_packet_dispatcher_stream_interest_t)) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to allocate stream_interest\");\n            return -1;\n        }\n\n        if (aeron_data_packet_dispatcher_stream_interest_init(stream_interest, false) < 0 ||\n            aeron_int64_to_ptr_hash_map_put(&dispatcher->session_by_stream_id_map, stream_id, stream_interest) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to add stream_interest to session_by_stream_id_map\");\n            aeron_free(stream_interest);\n            return -1;\n        }\n    }\n\n    if (aeron_int64_to_ptr_hash_map_put(\n        &stream_interest->subscribed_sessions, session_id, &dispatcher->tokens.subscribed) < 0)\n    {\n        AERON_APPEND_ERR(\"Failed to add session_id (%\" PRId32 \") to subscribed sessions\", session_id);\n        return -1;\n    }\n\n    if ((int64_t)AERON_DATA_PACKET_DISPATCHER_IMAGE_NO_INTEREST == aeron_int64_counter_map_get(\n        &stream_interest->state_by_session_id_map, session_id))\n    {\n        aeron_int64_counter_map_remove(&stream_interest->state_by_session_id_map, session_id);\n    }\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_remove_subscription(aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest;\n\n    if ((stream_interest = aeron_int64_to_ptr_hash_map_get(&dispatcher->session_by_stream_id_map, stream_id)) == NULL)\n    {\n        AERON_SET_ERR(EINVAL, \"No subscription for stream: %\" PRIi32, stream_id);\n        return -1;\n    }\n\n    aeron_int64_to_ptr_hash_map_remove_if(\n        &stream_interest->image_by_session_id_map,\n        aeron_data_packet_dispatcher_match_image_with_no_subscription,\n        stream_interest);\n\n    aeron_int64_counter_map_remove_if(\n        &stream_interest->state_by_session_id_map,\n        aeron_data_packet_dispatcher_match_state_with_no_subscription,\n        stream_interest);\n\n    stream_interest->is_all_sessions = false;\n\n    if (0 == stream_interest->image_by_session_id_map.size && 0 == stream_interest->subscribed_sessions.size)\n    {\n        aeron_int64_to_ptr_hash_map_remove(&dispatcher->session_by_stream_id_map, stream_id);\n        aeron_data_packet_dispatcher_stream_interest_delete(stream_interest);\n    }\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_remove_subscription_by_session(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id, int32_t session_id)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest;\n\n    if ((stream_interest = aeron_int64_to_ptr_hash_map_get(&dispatcher->session_by_stream_id_map, stream_id)) == NULL)\n    {\n        AERON_SET_ERR(EINVAL, \"No subscription for stream: %\" PRIi32, stream_id);\n        return -1;\n    }\n\n    if (!stream_interest->is_all_sessions)\n    {\n        aeron_int64_to_ptr_hash_map_remove(&stream_interest->image_by_session_id_map, session_id);\n        aeron_int64_counter_map_remove(&stream_interest->state_by_session_id_map, session_id);\n    }\n\n    aeron_int64_to_ptr_hash_map_remove(&stream_interest->subscribed_sessions, session_id);\n\n    if (!stream_interest->is_all_sessions && 0 == stream_interest->subscribed_sessions.size)\n    {\n        aeron_int64_to_ptr_hash_map_remove(&dispatcher->session_by_stream_id_map, stream_id);\n        aeron_data_packet_dispatcher_stream_interest_delete(stream_interest);\n    }\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_add_publication_image(\n    aeron_data_packet_dispatcher_t *dispatcher, aeron_publication_image_t *image)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest =\n        aeron_int64_to_ptr_hash_map_get(&dispatcher->session_by_stream_id_map, image->stream_id);\n\n    if (NULL != stream_interest)\n    {\n        aeron_int64_counter_map_remove(&stream_interest->state_by_session_id_map, image->session_id);\n        if (aeron_int64_to_ptr_hash_map_put(&stream_interest->image_by_session_id_map, image->session_id, image) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to add image to image_by_session_id_map\");\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_remove_publication_image(\n    aeron_data_packet_dispatcher_t *dispatcher, aeron_publication_image_t *image)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest =\n        aeron_int64_to_ptr_hash_map_get(&dispatcher->session_by_stream_id_map, image->stream_id);\n\n    if (NULL != stream_interest)\n    {\n        aeron_publication_image_t *mapped_image = aeron_int64_to_ptr_hash_map_get(\n            &stream_interest->image_by_session_id_map, image->session_id);\n\n        if (NULL != mapped_image &&\n            image->conductor_fields.managed_resource.registration_id == mapped_image->conductor_fields.managed_resource.registration_id)\n        {\n            aeron_int64_to_ptr_hash_map_remove(&stream_interest->image_by_session_id_map, image->session_id);\n\n            // TODO: Java driver checks end of stream at this point.\n            if (aeron_int64_counter_map_put(\n                &stream_interest->state_by_session_id_map, image->session_id, AERON_DATA_PACKET_DISPATCHER_IMAGE_COOL_DOWN, NULL) < 0)\n            {\n                AERON_APPEND_ERR(\n                    \"Unable to set IMAGE_COOL_DOWN for session_id (%\" PRId32 \") in image_by_session_id_map\",\n                    image->session_id);\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nbool aeron_data_packet_dispatcher_has_interest_in(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id, int32_t session_id)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest =\n        aeron_int64_to_ptr_hash_map_get(&dispatcher->session_by_stream_id_map, stream_id);\n\n    if (NULL == stream_interest)\n    {\n        return false;\n    }\n\n    aeron_publication_image_t *image = aeron_int64_to_ptr_hash_map_get(\n        &stream_interest->image_by_session_id_map, session_id);\n\n    return NULL != image ? true : aeron_data_packet_dispatcher_stream_interest_for_session(stream_interest, session_id);\n}\n\nstatic void aeron_data_packet_dispatcher_mark_as_no_interest_to_prevent_repeated_hash_lookups(\n    aeron_int64_counter_map_t *mage, int32_t session_id)\n{\n    // This is here as an optimisation so that streams that we don't care about don't trigger the slow\n    // path and require checking for interest. As it is an optimisation, we are ignoring the possible\n    // put failure from the hash map (occurs if a rehash fails to allocation memory).\n    aeron_int64_counter_map_put(mage, session_id, AERON_DATA_PACKET_DISPATCHER_IMAGE_NO_INTEREST, NULL);\n}\n\nint aeron_data_packet_dispatcher_on_data(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    aeron_data_header_t *header,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest =\n        aeron_int64_to_ptr_hash_map_get(&dispatcher->session_by_stream_id_map, header->stream_id);\n\n    if (NULL != stream_interest)\n    {\n        const int32_t session_id = header->session_id;\n        aeron_publication_image_t *image = aeron_int64_to_ptr_hash_map_get(\n            &stream_interest->image_by_session_id_map, session_id);\n\n        if (NULL != image)\n        {\n            return aeron_publication_image_insert_packet(\n                image, destination, header->term_id, header->term_offset, buffer, length, addr);\n        }\n        else if (0 == (header->frame_header.flags & AERON_DATA_HEADER_EOS_FLAG) &&\n            stream_interest->state_by_session_id_map.initial_value == aeron_int64_counter_map_get(&stream_interest->state_by_session_id_map, session_id))\n        {\n            if (aeron_data_packet_dispatcher_stream_interest_for_session(stream_interest, session_id))\n            {\n                if (aeron_data_packet_dispatcher_elicit_setup_from_source(\n                    dispatcher, stream_interest, endpoint, destination, addr, header->stream_id, session_id) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"\");\n                    return -1;\n                }\n\n                return 0;\n            }\n            else\n            {\n                aeron_data_packet_dispatcher_mark_as_no_interest_to_prevent_repeated_hash_lookups(\n                    &stream_interest->state_by_session_id_map, session_id);\n            }\n        }\n    }\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_create_publication(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    aeron_setup_header_t *header,\n    struct sockaddr_storage *addr,\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest)\n{\n    if (aeron_int64_counter_map_put(\n        &stream_interest->state_by_session_id_map,\n        header->session_id,\n        AERON_DATA_PACKET_DISPATCHER_IMAGE_INIT_IN_PROGRESS,\n        NULL) < 0)\n    {\n        AERON_APPEND_ERR(\n            \"Unable to set INIT_IN_PROGRESS for session_id (%\" PRId32 \") in image_by_session_id_map\",\n            header->session_id);\n        return -1;\n    }\n\n    struct sockaddr_storage *control_addr = endpoint->conductor_fields.udp_channel->is_multicast ?\n        &endpoint->conductor_fields.udp_channel->remote_control : addr;\n\n    aeron_driver_conductor_proxy_on_create_publication_image_cmd(\n        dispatcher->conductor_proxy,\n        header->session_id,\n        header->stream_id,\n        header->initial_term_id,\n        header->active_term_id,\n        header->term_offset,\n        header->term_length,\n        header->mtu,\n        header->frame_header.flags,\n        control_addr,\n        addr,\n        endpoint,\n        destination);\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_on_setup(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    aeron_setup_header_t *header,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest =\n        aeron_int64_to_ptr_hash_map_get(&dispatcher->session_by_stream_id_map, header->stream_id);\n\n    if (NULL != stream_interest)\n    {\n        if ((size_t)dispatcher->stream_session_limit <= stream_interest->image_by_session_id_map.size)\n        {\n            AERON_SET_ERR(EINVAL, \"exceeded session limit, stream-id=%\" PRId32, header->stream_id);\n            return -1;\n        }\n\n        const int32_t session_id = header->session_id;\n        aeron_publication_image_t *image = aeron_int64_to_ptr_hash_map_get(\n            &stream_interest->image_by_session_id_map, session_id);\n\n        if (NULL != image)\n        {\n            aeron_publication_image_add_connection_if_unknown(image, destination, addr);\n        }\n        else\n        {\n            int64_t state = aeron_int64_counter_map_get(&stream_interest->state_by_session_id_map, session_id);\n            if (AERON_DATA_PACKET_DISPATCHER_IMAGE_PENDING_SETUP_FRAME == state)\n            {\n                if (destination->conductor_fields.udp_channel->is_multicast &&\n                    destination->conductor_fields.udp_channel->multicast_ttl < header->ttl)\n                {\n                    aeron_counter_increment_release(endpoint->possible_ttl_asymmetry_counter);\n                }\n\n                if (aeron_data_packet_dispatcher_create_publication(\n                    dispatcher, endpoint, destination, header, addr, stream_interest) < 0)\n                {\n                    return -1;\n                }\n            }\n            else if(stream_interest->state_by_session_id_map.initial_value == state)\n            {\n                if (aeron_data_packet_dispatcher_stream_interest_for_session(stream_interest, session_id))\n                {\n                    if (aeron_data_packet_dispatcher_create_publication(\n                        dispatcher, endpoint, destination, header, addr, stream_interest) < 0)\n                    {\n                        return -1;\n                    }\n                }\n                else\n                {\n                    aeron_data_packet_dispatcher_mark_as_no_interest_to_prevent_repeated_hash_lookups(\n                        &stream_interest->state_by_session_id_map, session_id);\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_on_rttm(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    aeron_rttm_header_t *header,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest =\n        aeron_int64_to_ptr_hash_map_get(&dispatcher->session_by_stream_id_map, header->stream_id);\n\n    if (NULL != stream_interest)\n    {\n        aeron_publication_image_t *image = aeron_int64_to_ptr_hash_map_get(\n            &stream_interest->image_by_session_id_map, header->session_id);\n\n        if (NULL != image)\n        {\n            if (header->frame_header.flags & AERON_RTTM_HEADER_REPLY_FLAG)\n            {\n                struct sockaddr_storage *control_addr = endpoint->conductor_fields.udp_channel->is_multicast ?\n                    &endpoint->conductor_fields.udp_channel->remote_control : addr;\n\n                return aeron_receive_channel_endpoint_send_rttm(\n                    endpoint,\n                    destination,\n                    control_addr,\n                    header->stream_id,\n                    header->session_id,\n                    header->echo_timestamp,\n                    0,\n                    false);\n            }\n            else\n            {\n                return aeron_publication_image_on_rttm(image, header, addr);\n            }\n        }\n    }\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_try_connect_stream(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    int32_t stream_id,\n    int32_t session_id,\n    struct sockaddr_storage *addr)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest =\n        aeron_int64_to_ptr_hash_map_get(&dispatcher->session_by_stream_id_map, stream_id);\n\n    if (NULL == stream_interest)\n    {\n        AERON_SET_ERR(EINVAL, \"no stream interest found for streamId=%\" PRId32, stream_id);\n        return -1;\n    }\n\n    if (aeron_data_packet_dispatcher_stream_interest_for_session(stream_interest, session_id))\n    {\n        if (aeron_data_packet_dispatcher_elicit_setup_from_source(\n            dispatcher, stream_interest, endpoint, destination, addr, stream_id, session_id) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nint aeron_data_packet_dispatcher_elicit_setup_from_source(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *addr,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    bool should_elicit_setup = false;\n    if (aeron_data_packet_dispatcher_mark_image_pending_setup(stream_interest, session_id, &should_elicit_setup) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (!should_elicit_setup)\n    {\n        return 0;\n    }\n\n    struct sockaddr_storage *control_addr = destination->conductor_fields.udp_channel->is_multicast ?\n        &destination->conductor_fields.udp_channel->remote_control : addr;\n\n    if (aeron_receive_channel_endpoint_send_sm(\n        endpoint, destination, control_addr, stream_id, session_id, 0, 0, 0,\n        AERON_STATUS_MESSAGE_HEADER_SEND_SETUP_FLAG) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_driver_receiver_add_pending_setup(\n        dispatcher->receiver, endpoint, destination, session_id, stream_id, NULL))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nextern void aeron_data_packet_dispatcher_remove_matching_state(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t session_id, int32_t stream_id, uint32_t image_state);\n\nvoid aeron_data_packet_dispatcher_remove_pending_setup(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t session_id, int32_t stream_id)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest =\n        (aeron_data_packet_dispatcher_stream_interest_t *)aeron_int64_to_ptr_hash_map_get(\n            &dispatcher->session_by_stream_id_map, stream_id);\n\n    if (NULL != stream_interest)\n    {\n        int64_t state = aeron_int64_counter_map_get(&stream_interest->state_by_session_id_map, session_id);\n\n        if (AERON_DATA_PACKET_DISPATCHER_IMAGE_PENDING_SETUP_FRAME == state)\n        {\n            aeron_int64_counter_map_remove(&stream_interest->state_by_session_id_map, session_id);\n        }\n    }\n}\n\nextern void aeron_data_packet_dispatcher_remove_cool_down(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t session_id, int32_t stream_id);\n\nextern bool aeron_data_packet_dispatcher_should_elicit_setup_message(aeron_data_packet_dispatcher_t *dispatcher);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_data_packet_dispatcher.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DATA_PACKET_DISPATCHER_H\n#define AERON_DATA_PACKET_DISPATCHER_H\n\n#include \"aeron_socket.h\"\n#include \"collections/aeron_int64_to_ptr_hash_map.h\"\n#include \"collections/aeron_int64_to_tagged_ptr_hash_map.h\"\n#include \"aeron_driver_conductor_proxy.h\"\n#include \"media/aeron_receive_destination.h\"\n\n#define AERON_DATA_PACKET_DISPATCHER_IMAGE_ACTIVE UINT32_C(1)\n#define AERON_DATA_PACKET_DISPATCHER_IMAGE_PENDING_SETUP_FRAME UINT32_C(2)\n#define AERON_DATA_PACKET_DISPATCHER_IMAGE_INIT_IN_PROGRESS UINT32_C(3)\n#define AERON_DATA_PACKET_DISPATCHER_IMAGE_COOL_DOWN UINT32_C(4)\n#define AERON_DATA_PACKET_DISPATCHER_IMAGE_NO_INTEREST UINT32_C(5)\n\ntypedef struct aeron_publication_image_stct aeron_publication_image_t;\ntypedef struct aeron_receive_channel_endpoint_stct aeron_receive_channel_endpoint_t;\ntypedef struct aeron_receive_destination_stct aeron_receive_destination_t;\ntypedef struct aeron_driver_receiver_stct aeron_driver_receiver_t;\n\ntypedef struct aeron_data_packet_dispatcher_stct\n{\n    aeron_int64_to_ptr_hash_map_t ignored_sessions_map;\n    aeron_int64_to_ptr_hash_map_t session_by_stream_id_map;\n\n    /* tombstones for PENDING_SETUP_FRAME, INIT_IN_PROGRESS, and ON_COOL_DOWN */\n    struct aeron_data_packet_dispatcher_tokens_stct\n    {\n        int subscribed;\n    }\n    tokens;\n\n    aeron_driver_conductor_proxy_t *conductor_proxy;\n    aeron_driver_receiver_t *receiver;\n    int32_t stream_session_limit;\n}\naeron_data_packet_dispatcher_t;\n\nint aeron_data_packet_dispatcher_init(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    aeron_driver_receiver_t *receiver);\nint aeron_data_packet_dispatcher_close(aeron_data_packet_dispatcher_t *dispatcher);\n\ntypedef struct aeron_data_packet_dispatcher_stream_interest_stct\n{\n    bool is_all_sessions;\n    aeron_int64_to_ptr_hash_map_t subscribed_sessions;\n    aeron_int64_to_ptr_hash_map_t image_by_session_id_map;\n    aeron_int64_counter_map_t state_by_session_id_map;\n}\naeron_data_packet_dispatcher_stream_interest_t;\n\nint aeron_data_packet_dispatcher_add_subscription(aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id);\nint aeron_data_packet_dispatcher_add_subscription_by_session(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id, int32_t session_id);\nint aeron_data_packet_dispatcher_remove_subscription(aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id);\nint aeron_data_packet_dispatcher_remove_subscription_by_session(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id, int32_t session_id);\n\nint aeron_data_packet_dispatcher_add_publication_image(\n    aeron_data_packet_dispatcher_t *dispatcher, aeron_publication_image_t *image);\nint aeron_data_packet_dispatcher_remove_publication_image(\n    aeron_data_packet_dispatcher_t *dispatcher, aeron_publication_image_t *image);\n\n// Used by ATS\nbool aeron_data_packet_dispatcher_has_interest_in(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t stream_id, int32_t session_id);\n\nint aeron_data_packet_dispatcher_on_data(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    aeron_data_header_t *header,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nint aeron_data_packet_dispatcher_on_setup(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    aeron_setup_header_t *header,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nint aeron_data_packet_dispatcher_on_rttm(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    aeron_rttm_header_t *header,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\n// Used by ATS\nint aeron_data_packet_dispatcher_try_connect_stream(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    int32_t stream_id,\n    int32_t session_id,\n    struct sockaddr_storage *addr);\n\nint aeron_data_packet_dispatcher_elicit_setup_from_source(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *addr,\n    int32_t stream_id,\n    int32_t session_id);\n\ninline void aeron_data_packet_dispatcher_remove_matching_state(\n    aeron_data_packet_dispatcher_t *dispatcher,\n    int32_t session_id,\n    int32_t stream_id,\n    uint32_t image_state)\n{\n    aeron_data_packet_dispatcher_stream_interest_t *stream_interest =\n        (aeron_data_packet_dispatcher_stream_interest_t *)aeron_int64_to_ptr_hash_map_get(\n            &dispatcher->session_by_stream_id_map, stream_id);\n\n    if (NULL != stream_interest)\n    {\n        if (AERON_DATA_PACKET_DISPATCHER_IMAGE_ACTIVE == image_state)\n        {\n            aeron_int64_to_ptr_hash_map_remove(&stream_interest->image_by_session_id_map, session_id);\n        }\n        else\n        {\n            int64_t state = aeron_int64_counter_map_get(&stream_interest->state_by_session_id_map, session_id);\n\n            // If not found state will be -1 so won't match.\n            if ((int64_t)image_state == state)\n            {\n                aeron_int64_counter_map_remove(&stream_interest->state_by_session_id_map, session_id);\n            }\n        }\n    }\n}\n\nvoid aeron_data_packet_dispatcher_remove_pending_setup(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t session_id, int32_t stream_id);\n\ninline void aeron_data_packet_dispatcher_remove_cool_down(\n    aeron_data_packet_dispatcher_t *dispatcher, int32_t session_id, int32_t stream_id)\n{\n    aeron_data_packet_dispatcher_remove_matching_state(\n        dispatcher, session_id, stream_id, AERON_DATA_PACKET_DISPATCHER_IMAGE_COOL_DOWN);\n}\n\ninline bool aeron_data_packet_dispatcher_should_elicit_setup_message(aeron_data_packet_dispatcher_t *dispatcher)\n{\n    return 0 != dispatcher->session_by_stream_id_map.size;\n}\n\n#endif //AERON_DATA_PACKET_DISPATCHER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#ifdef HAVE_BSDSTDLIB_H\n#include <bsd/stdlib.h>\n#endif\n#endif\n\n#include \"util/aeron_platform.h\"\n#if defined(AERON_COMPILER_MSVC)\n#define _CRT_RAND_S\n\n#define S_IRWXU 0\n#define S_IRWXG 0\n#define S_IRWXO 0\n#endif\n#include <stdlib.h>\n#include <sys/stat.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <inttypes.h>\n\n#include \"util/aeron_error.h\"\n#include \"aeronmd.h\"\n#include \"aeron_alloc.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_fileutil.h\"\n#include \"aeron_driver.h\"\n#include \"aeron_socket.h\"\n#include \"util/aeron_dlopen.h\"\n\nvoid aeron_log_func_stderr(const char *str)\n{\n    fprintf(stderr, \"%s\\n\", str);\n}\n\nvoid aeron_log_func_none(const char *str)\n{\n}\n\nstatic void error_log_reader_save_to_file(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    FILE *saved_errors_file = (FILE *)clientd;\n    char first_datestamp[AERON_FORMAT_DATE_MAX_LENGTH];\n    char last_datestamp[AERON_FORMAT_DATE_MAX_LENGTH];\n\n    aeron_format_date(first_datestamp, sizeof(first_datestamp) - 1, first_observation_timestamp);\n    aeron_format_date(last_datestamp, sizeof(last_datestamp) - 1, last_observation_timestamp);\n    fprintf(\n        saved_errors_file,\n        \"***\\n%d observations from %s to %s for:\\n %.*s\\n\",\n        observation_count,\n        first_datestamp,\n        last_datestamp,\n        (int)error_length,\n        error);\n}\n\nint aeron_report_existing_errors(aeron_mapped_file_t *cnc_map, const char *aeron_dir)\n{\n    char buffer[AERON_MAX_PATH];\n    int result = 0;\n\n    aeron_cnc_metadata_t *metadata = (aeron_cnc_metadata_t *)cnc_map->addr;\n\n    if (aeron_semantic_version_major(AERON_CNC_VERSION) == aeron_semantic_version_major(metadata->cnc_version) &&\n        aeron_error_log_exists(aeron_cnc_error_log_buffer(cnc_map->addr), (size_t)metadata->error_log_buffer_length))\n    {\n        char datestamp[AERON_FORMAT_DATE_MAX_LENGTH];\n        FILE *saved_errors_file = NULL;\n\n        aeron_format_date(datestamp, sizeof(datestamp) - 1, aeron_epoch_clock());\n        while (true)\n        {\n            char *invalid_win_symbol = strstr(datestamp, \":\");\n            if (invalid_win_symbol == NULL)\n            {\n                break;\n            }\n\n            *invalid_win_symbol = '-';\n        }\n\n        snprintf(buffer, sizeof(buffer), \"%s-%s-error.log\", aeron_dir, datestamp);\n\n        if ((saved_errors_file = fopen(buffer, \"w\")) != NULL)\n        {\n            uint64_t observations = aeron_error_log_read(\n                aeron_cnc_error_log_buffer(metadata),\n                (size_t)metadata->error_log_buffer_length,\n                error_log_reader_save_to_file,\n                saved_errors_file,\n                0);\n\n            fprintf(saved_errors_file, \"\\n%\" PRIu64 \" distinct errors observed.\\n\", observations);\n            fprintf(stderr, \"WARNING: Existing errors saved to: %s\\n\", buffer);\n\n            fclose(saved_errors_file);\n        }\n        else\n        {\n            AERON_SET_ERR(errno, \"Failed to open saved_error_file: %s\", buffer);\n            result = -1;\n        }\n    }\n\n    return result;\n}\n\nint aeron_driver_ensure_dir_is_recreated(aeron_driver_context_t *context)\n{\n    char filename[AERON_MAX_PATH];\n    char buffer[2 * AERON_MAX_PATH];\n    const char *dirname = context->aeron_dir;\n    aeron_log_func_t log_func = aeron_log_func_none;\n\n    if (aeron_is_directory(dirname))\n    {\n        if (context->warn_if_dirs_exist)\n        {\n            log_func = aeron_log_func_stderr;\n            snprintf(buffer, sizeof(buffer), \"WARNING: %s exists\", dirname);\n            log_func(buffer);\n        }\n\n        if (context->dirs_delete_on_start)\n        {\n            if (0 != aeron_delete_directory(dirname))\n            {\n                snprintf(buffer, sizeof(buffer), \"INFO: failed to delete: %s\", dirname);\n                log_func(buffer);\n                return -1;\n            }\n        }\n        else\n        {\n            aeron_mapped_file_t cnc_mmap = { .addr = NULL, .length = 0 };\n\n            if (aeron_cnc_resolve_filename(dirname, filename, sizeof(filename)) < 0)\n            {\n                snprintf(buffer, sizeof(buffer), \"INFO: failed to resole CnC file: path=%s\", dirname);\n                log_func(buffer);\n                return -1;\n            }\n\n            if (aeron_map_existing_file(&cnc_mmap, filename) < 0)\n            {\n                if (ENOENT == aeron_errcode())\n                {\n                    aeron_err_clear();\n                }\n                else\n                {\n                    snprintf(buffer, sizeof(buffer), \"INFO: failed to mmap CnC file: %s\", filename);\n                    log_func(buffer);\n                    return -1;\n                }\n            }\n            else\n            {\n                snprintf(buffer, sizeof(buffer), \"INFO: Aeron CnC file %s exists\", filename);\n                log_func(buffer);\n\n                if (aeron_is_driver_active_with_cnc(\n                    &cnc_mmap, (int64_t)context->driver_timeout_ms, aeron_epoch_clock(), log_func))\n                {\n                    aeron_unmap(&cnc_mmap);\n                    return -1;\n                }\n\n                if (aeron_report_existing_errors(&cnc_mmap, dirname) < 0)\n                {\n                    aeron_unmap(&cnc_mmap);\n                    return -1;\n                }\n\n                aeron_unmap(&cnc_mmap);\n            }\n\n            if (aeron_delete_directory(context->aeron_dir) != 0)\n            {\n                snprintf(buffer, sizeof(buffer) - 1, \"INFO: failed to delete %s\", context->aeron_dir);\n                log_func(buffer);\n            }\n        }\n    }\n\n    if (aeron_mkdir_recursive(dirname, S_IRWXU | S_IRWXG | S_IRWXO) != 0)\n    {\n        AERON_APPEND_ERR(\"Failed to mkdir aeron directory: %s\", dirname);\n        return -1;\n    }\n\n    if (aeron_file_resolve(dirname, AERON_PUBLICATIONS_DIR, filename, sizeof(filename)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to get publications directory filename\");\n        return -1;\n    }\n\n    if (aeron_mkdir_recursive(filename, S_IRWXU | S_IRWXG | S_IRWXO) != 0)\n    {\n        AERON_APPEND_ERR(\"Failed to mkdir publications directory: %s\", filename);\n        return -1;\n    }\n\n    if (aeron_file_resolve(dirname, AERON_IMAGES_DIR, filename, sizeof(filename)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to get images directory filename\");\n        return -1;\n    }\n\n    if (aeron_mkdir_recursive(filename, S_IRWXU | S_IRWXG | S_IRWXO) != 0)\n    {\n        AERON_SET_ERR(errno, \"Failed to mkdir images directory: %s\", filename);\n        return -1;\n    }\n\n    return 0;\n}\n\nvoid aeron_driver_fill_cnc_metadata(aeron_driver_context_t *context)\n{\n    aeron_cnc_metadata_t *metadata = (aeron_cnc_metadata_t *)context->cnc_map.addr;\n    metadata->to_driver_buffer_length = (int32_t)context->to_driver_buffer_length;\n    metadata->to_clients_buffer_length = (int32_t)context->to_clients_buffer_length;\n    metadata->counter_metadata_buffer_length =\n        (int32_t)(AERON_COUNTERS_METADATA_BUFFER_LENGTH(context->counters_values_buffer_length));\n    metadata->counter_values_buffer_length = (int32_t)context->counters_values_buffer_length;\n    metadata->error_log_buffer_length = (int32_t)context->error_buffer_length;\n    metadata->client_liveness_timeout = (int64_t)context->client_liveness_timeout_ns;\n    metadata->start_timestamp = context->epoch_clock();\n    metadata->pid = getpid();\n    metadata->file_page_size = (int32_t)context->file_page_size;\n\n    context->to_driver_buffer = aeron_cnc_to_driver_buffer(metadata);\n    context->to_clients_buffer = aeron_cnc_to_clients_buffer(metadata);\n    context->counters_values_buffer = aeron_cnc_counters_values_buffer(metadata);\n    context->counters_metadata_buffer = aeron_cnc_counters_metadata_buffer(metadata);\n    context->error_buffer = aeron_cnc_error_log_buffer(metadata);\n}\n\nint aeron_driver_validate_value_range(uint64_t value, uint64_t min_value, uint64_t max_value, const char *name)\n{\n    if (value < min_value)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"%s less than min size of %\" PRIu64 \": value=%\" PRIu64,\n            name, min_value, value);\n        return -1;\n    }\n\n    if (value > max_value)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"%s greater than max size of %\" PRIu64 \": value=%\" PRIu64,\n            name, max_value, value);\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_driver_create_cnc_file(aeron_driver_t *driver)\n{\n    char buffer[AERON_MAX_PATH];\n    size_t cnc_file_length = aeron_cnc_length(driver->context);\n    if (aeron_driver_validate_value_range(cnc_file_length, 0, INT32_MAX, \"CnC file length\") < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    driver->context->cnc_map.addr = NULL;\n    driver->context->cnc_map.length = cnc_file_length;\n\n    if(aeron_file_resolve(driver->context->aeron_dir, AERON_CNC_FILE, buffer, sizeof(buffer)) < 0)\n    {\n        AERON_APPEND_ERR(\"Failed to resolve CnC file path: dir=%s, filename=%s\", driver->context->aeron_dir, AERON_CNC_FILE);\n        return -1;\n    }\n\n    if (aeron_map_new_file(&driver->context->cnc_map, buffer, true) < 0)\n    {\n        AERON_APPEND_ERR(\"CnC file: %s\", buffer);\n        return -1;\n    }\n\n    aeron_driver_fill_cnc_metadata(driver->context);\n\n    return 0;\n}\n\nint aeron_driver_create_loss_report_file(aeron_driver_t *driver)\n{\n    char buffer[AERON_MAX_PATH];\n\n    driver->context->loss_report.addr = NULL;\n    driver->context->loss_report.length =\n        AERON_ALIGN(driver->context->loss_report_length, driver->context->file_page_size);\n\n    if (aeron_loss_reporter_resolve_filename(driver->context->aeron_dir, buffer, sizeof(buffer)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to get loss report filename\");\n        return -1;\n    }\n\n    if (aeron_map_new_file(&driver->context->loss_report, buffer, true) < 0)\n    {\n        AERON_APPEND_ERR(\"could not map loss report file: %s\", buffer);\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_driver_validate_sufficient_socket_buffer_lengths(aeron_driver_t *driver)\n{\n    int result = -1;\n    aeron_socket_t probe_fd;\n\n    if ((probe_fd = aeron_socket(AF_INET, SOCK_DGRAM, 0)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to probe socket for buffer lengths\");\n        goto cleanup;\n    }\n\n    size_t default_sndbuf = 0;\n    socklen_t len = sizeof(default_sndbuf);\n    if (aeron_getsockopt(probe_fd, SOL_SOCKET, SO_SNDBUF, &default_sndbuf, &len) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to get SOL_SOCKET/SO_SNDBUF option\");\n        goto cleanup;\n    }\n\n    size_t default_rcvbuf = 0;\n    len = sizeof(default_rcvbuf);\n    if (aeron_getsockopt(probe_fd, SOL_SOCKET, SO_RCVBUF, &default_rcvbuf, &len) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to get SOL_SOCKET/SO_RCVBUF option\");\n        goto cleanup;\n    }\n\n    size_t max_rcvbuf = default_rcvbuf;\n    size_t max_sndbuf = default_sndbuf;\n\n    if (driver->context->socket_sndbuf > 0)\n    {\n        size_t socket_sndbuf = driver->context->socket_sndbuf;\n\n        if (aeron_setsockopt(probe_fd, SOL_SOCKET, SO_SNDBUF, &socket_sndbuf, sizeof(socket_sndbuf)) < 0)\n        {\n            AERON_APPEND_ERR(\"failed to set SOL_SOCKET/SO_SNDBUF option to: %\" PRIu64, (uint64_t)socket_sndbuf);\n            goto cleanup;\n        }\n\n        len = sizeof(socket_sndbuf);\n        if (aeron_getsockopt(probe_fd, SOL_SOCKET, SO_SNDBUF, &socket_sndbuf, &len) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to get SOL_SOCKET/SO_SNDBUF option\");\n            goto cleanup;\n        }\n\n        max_sndbuf = socket_sndbuf;\n\n        if (driver->context->socket_sndbuf > socket_sndbuf)\n        {\n            fprintf(\n                stderr,\n                \"WARNING: Could not get desired SO_SNDBUF, adjust OS buffer to match %s: attempted=%\" PRIu64 \", actual=%\" PRIu64 \"\\n\",\n                AERON_SOCKET_SO_SNDBUF_ENV_VAR,\n                (uint64_t)driver->context->socket_sndbuf,\n                (uint64_t)socket_sndbuf);\n        }\n    }\n\n    if (driver->context->socket_rcvbuf > 0)\n    {\n        size_t socket_rcvbuf = driver->context->socket_rcvbuf;\n\n        if (aeron_setsockopt(probe_fd, SOL_SOCKET, SO_RCVBUF, &socket_rcvbuf, sizeof(socket_rcvbuf)) < 0)\n        {\n            AERON_APPEND_ERR(\"failed to set SOL_SOCKET/SO_RCVBUF option to: %\" PRIu64, (uint64_t)socket_rcvbuf);\n            goto cleanup;\n        }\n\n        len = sizeof(socket_rcvbuf);\n        if (aeron_getsockopt(probe_fd, SOL_SOCKET, SO_RCVBUF, &socket_rcvbuf, &len) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to get SOL_SOCKET/SO_RCVBUF option\");\n            goto cleanup;\n        }\n\n        max_rcvbuf = socket_rcvbuf;\n\n        if (driver->context->socket_rcvbuf > socket_rcvbuf)\n        {\n            fprintf(\n                stderr,\n                \"WARNING: Could not get desired SO_RCVBUF, adjust OS buffer to match %s: attempted=%\" PRIu64 \", actual=%\" PRIu64 \"\\n\",\n                AERON_SOCKET_SO_RCVBUF_ENV_VAR,\n                (uint64_t)driver->context->socket_rcvbuf,\n                (uint64_t)socket_rcvbuf);\n        }\n    }\n\n    if (driver->context->mtu_length > max_sndbuf)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"MTU greater than socket SO_SNDBUF, adjust %s to match MTU: mtuLength=%\" PRIu64 \", SO_SNDBUF=%\" PRIu64 \"\\n\",\n            AERON_SOCKET_SO_SNDBUF_ENV_VAR,\n            (uint64_t)driver->context->mtu_length,\n            max_sndbuf);\n        goto cleanup;\n    }\n\n    if (driver->context->initial_window_length > max_rcvbuf)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Window length greater than socket SO_RCVBUF, increase %s to match window: windowLength=%\" PRIu64 \", SO_RCVBUF=%\" PRIu64 \"\\n\",\n            AERON_RCV_INITIAL_WINDOW_LENGTH_ENV_VAR,\n            (uint64_t)driver->context->initial_window_length,\n            max_rcvbuf);\n        goto cleanup;\n    }\n\n    result = 0;\n\ncleanup:\n    aeron_close_socket(probe_fd);\n\n    return result;\n}\n\nint aeron_driver_validate_page_size(aeron_driver_t *driver)\n{\n    if (aeron_driver_validate_value_range(\n        driver->context->file_page_size, AERON_PAGE_MIN_SIZE, AERON_PAGE_MAX_SIZE, \"file_page_size\") < 0)\n    {\n        return -1;\n    }\n\n    if (!AERON_IS_POWER_OF_TWO(driver->context->file_page_size))\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Page size not a power of 2: page size=%\" PRIu64,\n            driver->context->file_page_size);\n        return -1;\n    }\n\n    return 0;\n}\n\nconst char *aeron_driver_threading_mode_to_string(aeron_threading_mode_t mode)\n{\n    switch (mode)\n    {\n        case AERON_THREADING_MODE_DEDICATED:\n            return \"DEDICATED\";\n        case AERON_THREADING_MODE_SHARED:\n            return \"SHARED\";\n        case AERON_THREADING_MODE_SHARED_NETWORK:\n            return \"SHARED_NETWORK\";\n        case AERON_THREADING_MODE_INVOKER:\n            return \"INVOKER\";\n        default:\n            return \"unknown\";\n    }\n}\n\nvoid aeron_driver_context_print_configuration(aeron_driver_context_t *context)\n{\n    FILE *fpout = stdout;\n    char buffer[1024];\n\n    fprintf(fpout, \"aeron_driver_context_t {\");\n    fprintf(fpout, \"\\n    cnc_version=%d.%d.%d\",\n        (int)aeron_semantic_version_major(AERON_CNC_VERSION),\n        (int)aeron_semantic_version_minor(AERON_CNC_VERSION),\n        (int)aeron_semantic_version_patch(AERON_CNC_VERSION));\n    fprintf(fpout, \"\\n    aeron_dir=%s\", context->aeron_dir);\n    fprintf(fpout, \"\\n    driver_timeout_ms=%\" PRIu64, context->driver_timeout_ms);\n    fprintf(fpout, \"\\n    print_configuration_on_start=%d\", context->print_configuration_on_start);\n    fprintf(fpout, \"\\n    dirs_delete_on_start=%d\", context->dirs_delete_on_start);\n    fprintf(fpout, \"\\n    dirs_delete_on_shutdown=%d\", context->dirs_delete_on_shutdown);\n    fprintf(fpout, \"\\n    warn_if_dirs_exists=%d\", context->warn_if_dirs_exist);\n    fprintf(fpout, \"\\n    term_buffer_sparse_file=%d\", context->term_buffer_sparse_file);\n    fprintf(fpout, \"\\n    perform_storage_checks=%d\", context->perform_storage_checks);\n    fprintf(fpout, \"\\n    spies_simulate_connection=%d\", context->spies_simulate_connection);\n    fprintf(fpout, \"\\n    reliable_stream=%d\", context->reliable_stream);\n    fprintf(fpout, \"\\n    tether_subscriptions=%d\", context->tether_subscriptions);\n    fprintf(fpout, \"\\n    rejoin_stream=%d\", context->rejoin_stream);\n    fprintf(fpout, \"\\n    receiver_group_consideration=%d\", context->receiver_group_consideration);\n    fprintf(fpout, \"\\n    to_driver_buffer_length=%\" PRIu64, (uint64_t)context->to_driver_buffer_length);\n    fprintf(fpout, \"\\n    to_clients_buffer_length=%\" PRIu64, (uint64_t)context->to_clients_buffer_length);\n    fprintf(fpout, \"\\n    counters_values_buffer_length=%\" PRIu64, (uint64_t)context->counters_values_buffer_length);\n    fprintf(fpout, \"\\n    error_buffer_length=%\" PRIu64, (uint64_t)context->error_buffer_length);\n    fprintf(fpout, \"\\n    timer_interval_ns=%\" PRIu64, context->timer_interval_ns);\n    fprintf(fpout, \"\\n    client_liveness_timeout_ns=%\" PRIu64, context->client_liveness_timeout_ns);\n    fprintf(fpout, \"\\n    image_liveness_timeout_ns=%\" PRIu64, context->image_liveness_timeout_ns);\n    fprintf(fpout, \"\\n    publication_unblock_timeout_ns=%\" PRIu64, context->publication_unblock_timeout_ns);\n    fprintf(fpout, \"\\n    publication_connection_timeout_ns=%\" PRIu64, context->publication_connection_timeout_ns);\n    fprintf(fpout, \"\\n    publication_linger_timeout_ns=%\" PRIu64, context->publication_linger_timeout_ns);\n    fprintf(fpout, \"\\n    untethered_window_limit_timeout_ns=%\" PRIu64, context->untethered_window_limit_timeout_ns);\n    fprintf(fpout, \"\\n    untethered_resting_timeout_ns=%\" PRIu64, context->untethered_resting_timeout_ns);\n    fprintf(fpout, \"\\n    max_resend=%\" PRIu32, context->max_resend);\n    fprintf(fpout, \"\\n    retransmit_unicast_delay_ns=%\" PRIu64, context->retransmit_unicast_delay_ns);\n    fprintf(fpout, \"\\n    retransmit_unicast_linger_ns=%\" PRIu64, context->retransmit_unicast_linger_ns);\n    fprintf(fpout, \"\\n    nak_unicast_delay_ns=%\" PRIu64, context->nak_unicast_delay_ns);\n    fprintf(fpout, \"\\n    nak_unicast_retry_delay_ratio=%\" PRIu64, context->nak_unicast_retry_delay_ratio);\n    fprintf(fpout, \"\\n    nak_multicast_max_backoff_ns=%\" PRIu64, context->nak_multicast_max_backoff_ns);\n    fprintf(fpout, \"\\n    unicast_flow_control_rrwm=%\" PRIu64, (uint64_t)context->unicast_flow_control_rrwm);\n    fprintf(fpout, \"\\n    multicast_flow_control_rrwm=%\" PRIu64, (uint64_t)context->multicast_flow_control_rrwm);\n    fprintf(fpout, \"\\n    nak_multicast_group_size=%\" PRIu64, (uint64_t)context->nak_multicast_group_size);\n    fprintf(fpout, \"\\n    status_message_timeout_ns=%\" PRIu64, context->status_message_timeout_ns);\n    fprintf(fpout, \"\\n    counter_free_to_reuse_ns=%\" PRIu64, context->counter_free_to_reuse_ns);\n    fprintf(fpout, \"\\n    conductor_cycle_threshold_ns=%\" PRIu64,\n        context->conductor_duty_cycle_stall_tracker.cycle_threshold_ns);\n    fprintf(fpout, \"\\n    sender_cycle_threshold_ns=%\" PRIu64,\n        context->sender_duty_cycle_stall_tracker.cycle_threshold_ns);\n    fprintf(fpout, \"\\n    receiver_cycle_threshold_ns=%\" PRIu64,\n        context->receiver_duty_cycle_stall_tracker.cycle_threshold_ns);\n    fprintf(fpout, \"\\n    name_resolver_threshold_ns=%\" PRIu64,\n        context->name_resolver_time_stall_tracker.cycle_threshold_ns);\n    fprintf(fpout, \"\\n    term_buffer_length=%\" PRIu64, (uint64_t)context->term_buffer_length);\n    fprintf(fpout, \"\\n    ipc_term_buffer_length=%\" PRIu64, (uint64_t)context->ipc_term_buffer_length);\n    fprintf(fpout, \"\\n    publication_window_length=%\" PRIu64, (uint64_t)context->publication_window_length);\n    fprintf(fpout, \"\\n    ipc_publication_window_length=%\" PRIu64, (uint64_t)context->ipc_publication_window_length);\n    fprintf(fpout, \"\\n    initial_window_length=%\" PRIu64, (uint64_t)context->initial_window_length);\n    fprintf(fpout, \"\\n    socket_sndbuf_length=%\" PRIu64, (uint64_t)context->socket_sndbuf);\n    fprintf(fpout, \"\\n    socket_rcvbuf_length=%\" PRIu64, (uint64_t)context->socket_rcvbuf);\n    fprintf(fpout, \"\\n    multicast_ttl=%\" PRIu8, context->multicast_ttl);\n    fprintf(fpout, \"\\n    mtu_length=%\" PRIu64, (uint64_t)context->mtu_length);\n    fprintf(fpout, \"\\n    ipc_mtu_length=%\" PRIu64, (uint64_t)context->ipc_mtu_length);\n    fprintf(fpout, \"\\n    file_page_size=%\" PRIu64, (uint64_t)context->file_page_size);\n    fprintf(fpout, \"\\n    low_file_store_warning_threshold=%\" PRIu64, (uint64_t)context->low_file_store_warning_threshold);\n    fprintf(fpout, \"\\n    publication_reserved_session_id_low=%\" PRId32, context->publication_reserved_session_id_low);\n    fprintf(fpout, \"\\n    publication_reserved_session_id_high=%\" PRId32, context->publication_reserved_session_id_high);\n    fprintf(fpout, \"\\n    loss_report_length=%\" PRIu64, (uint64_t)context->loss_report_length);\n    fprintf(fpout, \"\\n    send_to_sm_poll_ratio=%\" PRIu64, (uint64_t)context->send_to_sm_poll_ratio);\n    fprintf(fpout, \"\\n    receiver_io_vector_capacity=%\" PRIu64, (uint64_t)context->receiver_io_vector_capacity);\n    fprintf(fpout, \"\\n    sender_io_vector_capacity=%\" PRIu64, (uint64_t)context->sender_io_vector_capacity);\n    fprintf(\n        fpout, \"\\n    network_publication_max_messages_per_send=%\" PRIu64,\n        (uint64_t)context->network_publication_max_messages_per_send);\n    fprintf(fpout, \"\\n    resource_free_limit=%\" PRIu32, context->resource_free_limit);\n    fprintf(fpout, \"\\n    async_executor_threads=%\" PRIu32, context->async_executor_threads);\n    fprintf(fpout, \"\\n    conductor_cpu_affinity_no=%\" PRId32, context->conductor_cpu_affinity_no);\n    fprintf(fpout, \"\\n    receiver_cpu_affinity_no=%\" PRId32, context->receiver_cpu_affinity_no);\n    fprintf(fpout, \"\\n    sender_cpu_affinity_no=%\" PRId32, context->sender_cpu_affinity_no);\n\n    fprintf(fpout, \"\\n    epoch_clock=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->epoch_clock, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    nano_clock=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->nano_clock, buffer, sizeof(buffer)));\n    /* cachedEpochClock */\n    /* cachedNanoClock */\n    fprintf(fpout, \"\\n    threading_mode=%s\", aeron_driver_threading_mode_to_string(context->threading_mode));\n    fprintf(fpout, \"\\n    agent_on_start_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->agent_on_start_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    agent_on_start_state=%p\", context->agent_on_start_state);\n    fprintf(fpout, \"\\n    conductor_idle_strategy_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->conductor_idle_strategy_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    conductor_idle_strategy_init_args=%p%s\",\n        (void *)context->conductor_idle_strategy_init_args,\n        context->conductor_idle_strategy_init_args ? context->conductor_idle_strategy_init_args : \"\");\n    fprintf(fpout, \"\\n    sender_idle_strategy_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->sender_idle_strategy_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    sender_idle_strategy_init_args=%p%s\",\n        (void *)context->sender_idle_strategy_init_args,\n        context->sender_idle_strategy_init_args ? context->sender_idle_strategy_init_args : \"\");\n    fprintf(fpout, \"\\n    receiver_idle_strategy_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->receiver_idle_strategy_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    receiver_idle_strategy_init_args=%p%s\",\n        (void *)context->receiver_idle_strategy_init_args,\n        context->receiver_idle_strategy_init_args ? context->receiver_idle_strategy_init_args : \"\");\n    fprintf(fpout, \"\\n    shared_network_idle_strategy_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->shared_network_idle_strategy_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    shared_network_idle_strategy_init_args=%p%s\",\n        (void *)context->shared_network_idle_strategy_init_args,\n        context->shared_network_idle_strategy_init_args ? context->shared_network_idle_strategy_init_args : \"\");\n    fprintf(fpout, \"\\n    shared_idle_strategy_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->shared_idle_strategy_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    shared_idle_strategy_init_args=%p%s\",\n        (void *)context->shared_idle_strategy_init_args,\n        context->shared_idle_strategy_init_args ? context->shared_idle_strategy_init_args : \"\");\n    fprintf(fpout, \"\\n    unicast_flow_control_supplier_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->unicast_flow_control_supplier_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    multicast_flow_control_supplier_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->multicast_flow_control_supplier_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    receiver_group_tag.is_present=%d\",\n        context->receiver_group_tag.is_present);\n    fprintf(fpout, \"\\n    receiver_group_tag.value=%\" PRId64, context->receiver_group_tag.value);\n    fprintf(fpout, \"\\n    flow_control.group_tag=%\" PRId64, context->flow_control.group_tag);\n    fprintf(fpout, \"\\n    flow_control.group_min_size=%\" PRId32, context->flow_control.group_min_size);\n    fprintf(fpout, \"\\n    flow_control_receiver_timeout_ns=%\" PRIu64, context->flow_control.receiver_timeout_ns);\n    fprintf(fpout, \"\\n    congestion_control_supplier_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->congestion_control_supplier_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    usable_fs_space_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->usable_fs_space_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    termination_validator_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->termination_validator_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    termination_validator_state=%p\", context->termination_validator_state);\n    fprintf(fpout, \"\\n    termination_hook_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->termination_hook_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    termination_hook_state=%p\", context->termination_hook_state);\n    fprintf(fpout, \"\\n    name_resolver_supplier_func=%s\",\n        aeron_dlinfo_func((aeron_fptr_t)context->name_resolver_supplier_func, buffer, sizeof(buffer)));\n    fprintf(fpout, \"\\n    name_resolver_init_args=%s\",\n        (void *)context->name_resolver_init_args ? context->name_resolver_init_args : \"\");\n    fprintf(fpout, \"\\n    resolver_name=%s\",\n        (void *)context->resolver_name ? context->resolver_name : \"\");\n    fprintf(fpout, \"\\n    resolver_interface=%s\",\n        (void *)context->resolver_interface ? context->resolver_interface : \"\");\n    fprintf(fpout, \"\\n    resolver_bootstrap_neighbor=%s\",\n        (void *)context->resolver_bootstrap_neighbor ? context->resolver_bootstrap_neighbor : \"\");\n    fprintf(fpout, \"\\n    re_resolution_check_interval_ns=%\" PRIu64, context->re_resolution_check_interval_ns);\n    fprintf(fpout, \"\\n    sender_wildcard_port_range=\\\"%\" PRIu16 \" %\" PRIu16 \"\\\"\", \n        context->sender_wildcard_port_manager.low_port, context->sender_wildcard_port_manager.high_port);\n    fprintf(fpout, \"\\n    receiver_wildcard_port_range=\\\"%\" PRIu16 \" %\" PRIu16 \"\\\"\",\n        context->receiver_wildcard_port_manager.low_port, context->receiver_wildcard_port_manager.high_port);\n    fprintf(fpout, \"\\n    enable_experimental_features=%s\", context->enable_experimental_features ? \"true\" : \"false\");\n    fprintf(fpout, \"\\n    stream_session_limit=%\" PRId32, context->stream_session_limit);\n\n    const aeron_udp_channel_transport_bindings_t *bindings = context->udp_channel_transport_bindings;\n    if (NULL != bindings)\n    {\n        fprintf(\n            fpout, \"\\n    udp_channel_transport_bindings.%s=%s,%p%s\",\n            bindings->meta_info.type,\n            bindings->meta_info.name,\n            bindings->meta_info.source_symbol,\n            aeron_dlinfo(bindings->meta_info.source_symbol, buffer, sizeof(buffer)));\n    }\n\n    const aeron_udp_channel_transport_bindings_t *conductor_bindings = context->conductor_udp_channel_transport_bindings;\n    if (NULL != conductor_bindings)\n    {\n        fprintf(\n            fpout, \"\\n    conductor_udp_channel_transport_bindings.%s=%s,%p%s\",\n            conductor_bindings->meta_info.type,\n            conductor_bindings->meta_info.name,\n            conductor_bindings->meta_info.source_symbol,\n            aeron_dlinfo(conductor_bindings->meta_info.source_symbol, buffer, sizeof(buffer)));\n    }\n\n    const aeron_udp_channel_interceptor_bindings_t *interceptor_bindings;\n\n    interceptor_bindings = context->udp_channel_outgoing_interceptor_bindings;\n    while (NULL != interceptor_bindings)\n    {\n        fprintf(\n            fpout, \"\\n    udp_channel_outgoing_interceptor_bindings.%s=%s,%s\",\n            interceptor_bindings->meta_info.type,\n            interceptor_bindings->meta_info.name,\n            aeron_dlinfo_func(interceptor_bindings->meta_info.source_symbol, buffer, sizeof(buffer)));\n\n        interceptor_bindings = interceptor_bindings->meta_info.next_interceptor_bindings;\n    }\n\n    interceptor_bindings = context->udp_channel_incoming_interceptor_bindings;\n    while (NULL != interceptor_bindings)\n    {\n        fprintf(\n            fpout, \"\\n    udp_channel_incoming_interceptor_bindings.%s=%s,%s\",\n            interceptor_bindings->meta_info.type,\n            interceptor_bindings->meta_info.name,\n            aeron_dlinfo_func(interceptor_bindings->meta_info.source_symbol, buffer, sizeof(buffer)));\n\n        interceptor_bindings = interceptor_bindings->meta_info.next_interceptor_bindings;\n    }\n\n    fprintf(fpout, \"\\n}\\n\");\n    fflush(fpout);\n}\n\nint aeron_driver_shared_do_work(void *clientd)\n{\n    aeron_driver_t *driver = (aeron_driver_t *)clientd;\n    int sum = 0;\n\n    sum += aeron_driver_conductor_do_work(&driver->conductor);\n    sum += aeron_driver_sender_do_work(&driver->sender);\n    sum += aeron_driver_receiver_do_work(&driver->receiver);\n\n    return sum;\n}\n\nvoid aeron_driver_shared_on_close(void *clientd)\n{\n    aeron_driver_t *driver = (aeron_driver_t *)clientd;\n\n    aeron_driver_conductor_on_close(&driver->conductor);\n    aeron_driver_sender_on_close(&driver->sender);\n    aeron_driver_receiver_on_close(&driver->receiver);\n}\n\nint aeron_driver_shared_network_do_work(void *clientd)\n{\n    aeron_driver_t *driver = (aeron_driver_t *)clientd;\n    int sum = 0;\n\n    sum += aeron_driver_sender_do_work(&driver->sender);\n    sum += aeron_driver_receiver_do_work(&driver->receiver);\n\n    return sum;\n}\n\nvoid aeron_driver_shared_network_on_close(void *clientd)\n{\n    aeron_driver_t *driver = (aeron_driver_t *)clientd;\n\n    aeron_driver_sender_on_close(&driver->sender);\n    aeron_driver_receiver_on_close(&driver->receiver);\n}\n\nint aeron_driver_init(aeron_driver_t **driver, aeron_driver_context_t *context)\n{\n    aeron_driver_t *_driver = NULL;\n\n    if (NULL == driver || NULL == context)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"driver or context are null\");\n        goto error;\n    }\n\n    if (aeron_alloc((void **)&_driver, sizeof(aeron_driver_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Unable to allocate driver\");\n        goto error;\n    }\n\n    if (aeron_driver_context_bindings_clientd_create_entries(context) < 0)\n    {\n        goto error;\n    }\n\n    _driver->context = context;\n\n    for (int i = 0; i < AERON_AGENT_RUNNER_MAX; i++)\n    {\n        _driver->runners[i].state = AERON_AGENT_STATE_UNUSED;\n        _driver->runners[i].role_name = NULL;\n        _driver->runners[i].on_close = NULL;\n    }\n\n    if (aeron_logbuffer_check_term_length(_driver->context->term_buffer_length) < 0 ||\n        aeron_logbuffer_check_term_length(_driver->context->ipc_term_buffer_length) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_driver_validate_page_size(_driver) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_driver_context_validate_mtu_length(_driver->context->mtu_length) < 0 ||\n        aeron_driver_context_validate_mtu_length(_driver->context->ipc_mtu_length) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_driver_validate_value_range(\n        _driver->context->to_driver_buffer_length,\n        AERON_TO_CONDUCTOR_BUFFER_LENGTH_DEFAULT,\n        INT32_MAX,\n        \"to_driver_buffer_length\") < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_driver_validate_value_range(\n        _driver->context->to_clients_buffer_length,\n        AERON_TO_CLIENTS_BUFFER_LENGTH_DEFAULT,\n        INT32_MAX,\n        \"to_clients_buffer_length\") < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_driver_validate_value_range(\n        _driver->context->counters_values_buffer_length,\n        AERON_COUNTERS_VALUES_BUFFER_LENGTH_MIN,\n        AERON_COUNTERS_VALUES_BUFFER_LENGTH_MAX,\n        \"counters_values_buffer_length\") < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_driver_validate_value_range(\n        _driver->context->error_buffer_length,\n        AERON_ERROR_BUFFER_LENGTH_DEFAULT,\n        INT32_MAX,\n        \"error_buffer_length\") < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_driver_validate_value_range(\n        _driver->context->publication_window_length,\n        0,\n        AERON_LOGBUFFER_TERM_MAX_LENGTH,\n        \"publication_window_length\") < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_driver_validate_value_range(\n        _driver->context->ipc_publication_window_length,\n        0,\n        AERON_LOGBUFFER_TERM_MAX_LENGTH,\n        \"ipc_publication_window_length\") < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_driver_validate_sufficient_socket_buffer_lengths(_driver) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_driver_ensure_dir_is_recreated(_driver->context) < 0)\n    {\n        AERON_APPEND_ERR(\"could not recreate aeron dir: %s\", _driver->context->aeron_dir);\n        goto error;\n    }\n\n    if (aeron_driver_create_cnc_file(_driver) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_driver_create_loss_report_file(_driver) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_driver_validate_unblock_timeout(_driver->context) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_driver_validate_untethered_timeouts(_driver->context) < 0)\n    {\n        goto error;\n    }\n\n    context->counters_manager = &_driver->conductor.counters_manager;\n    context->system_counters = &_driver->conductor.system_counters;\n    context->error_log = &_driver->conductor.error_log;\n    _driver->context->conductor_proxy = &_driver->conductor.conductor_proxy;\n\n    if (aeron_driver_conductor_init(&_driver->conductor, context) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_driver_sender_init(\n        &_driver->sender, context, &_driver->conductor.system_counters, &_driver->conductor.error_log) < 0)\n    {\n        goto error;\n    }\n\n    _driver->context->sender_proxy = &_driver->sender.sender_proxy;\n\n    if (aeron_driver_receiver_init(\n        &_driver->receiver, context, &_driver->conductor.system_counters, &_driver->conductor.error_log) < 0)\n    {\n        goto error;\n    }\n\n    _driver->context->receiver_proxy = &_driver->receiver.receiver_proxy;\n\n    aeron_counter_set_release(\n        aeron_system_counter_addr(context->system_counters, AERON_SYSTEM_COUNTER_AERON_VERSION),\n        aeron_semantic_version_compose(aeron_version_major(), aeron_version_minor(), aeron_version_patch()));\n\n    aeron_counter_set_release(\n        aeron_system_counter_addr(context->system_counters, AERON_SYSTEM_COUNTER_CONTROL_PROTOCOL_VERSION),\n        aeron_semantic_version_compose(\n        AERON_CONTROL_PROTOCOL_MAJOR_VERSION,\n        AERON_CONTROL_PROTOCOL_MINOR_VERSION,\n        AERON_CONTROL_PROTOCOL_PATCH_VERSION));\n\n    aeron_counter_set_release(\n        aeron_system_counter_addr(context->system_counters, AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED),\n        (int64_t)(_driver->context->cnc_map.length + _driver->context->loss_report_length));\n\n    if (aeron_feedback_delay_state_init(\n        &_driver->context->unicast_delay_feedback_generator,\n        aeron_loss_detector_nak_unicast_delay_generator,\n        (int64_t)_driver->context->nak_unicast_delay_ns,\n        (int64_t)_driver->context->nak_unicast_delay_ns * (int64_t)_driver->context->nak_unicast_retry_delay_ratio,\n        1) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_feedback_delay_state_init(\n        &_driver->context->multicast_delay_feedback_generator,\n        aeron_loss_detector_nak_multicast_delay_generator,\n        (int64_t)_driver->context->nak_multicast_max_backoff_ns,\n        (int64_t)_driver->context->nak_multicast_max_backoff_ns,\n        _driver->context->nak_multicast_group_size) < 0)\n    {\n        goto error;\n    }\n\n    aeron_mpsc_rb_next_correlation_id(&_driver->conductor.to_driver_commands);\n    aeron_mpsc_rb_consumer_heartbeat_time(&_driver->conductor.to_driver_commands, aeron_epoch_clock());\n    aeron_cnc_version_signal_cnc_ready((aeron_cnc_metadata_t *)context->cnc_map.addr, AERON_CNC_VERSION);\n    aeron_msync(context->cnc_map.addr, context->cnc_map.length);\n\n    if (_driver->context->print_configuration_on_start)\n    {\n        aeron_driver_context_print_configuration(_driver->context);\n    }\n\n    switch (_driver->context->threading_mode)\n    {\n        case AERON_THREADING_MODE_INVOKER:\n        case AERON_THREADING_MODE_SHARED:\n            if (aeron_agent_init(\n                &_driver->runners[AERON_AGENT_RUNNER_SHARED],\n                \"[conductor, sender, receiver]\",\n                _driver,\n                _driver->context->agent_on_start_func,\n                _driver->context->agent_on_start_state,\n                aeron_driver_shared_do_work,\n                aeron_driver_shared_on_close,\n                _driver->context->shared_idle_strategy_func,\n                _driver->context->shared_idle_strategy_state) < 0)\n            {\n                goto error;\n            }\n            break;\n\n        case AERON_THREADING_MODE_SHARED_NETWORK:\n            if (aeron_agent_init(\n                &_driver->runners[AERON_AGENT_RUNNER_CONDUCTOR],\n                \"conductor\",\n                &_driver->conductor,\n                _driver->context->agent_on_start_func,\n                _driver->context->agent_on_start_state,\n                aeron_driver_conductor_do_work,\n                aeron_driver_conductor_on_close,\n                _driver->context->conductor_idle_strategy_func,\n                _driver->context->conductor_idle_strategy_state) < 0)\n            {\n                goto error;\n            }\n\n            if (aeron_agent_init(\n                &_driver->runners[AERON_AGENT_RUNNER_SHARED_NETWORK],\n                \"[sender, receiver]\",\n                _driver,\n                _driver->context->agent_on_start_func,\n                _driver->context->agent_on_start_state,\n                aeron_driver_shared_network_do_work,\n                aeron_driver_shared_network_on_close,\n                _driver->context->shared_network_idle_strategy_func,\n                _driver->context->shared_network_idle_strategy_state) < 0)\n            {\n                goto error;\n            }\n            break;\n\n        case AERON_THREADING_MODE_DEDICATED:\n        default:\n            if (aeron_agent_init(\n                &_driver->runners[AERON_AGENT_RUNNER_CONDUCTOR],\n                \"conductor\",\n                &_driver->conductor,\n                _driver->context->agent_on_start_func,\n                _driver->context->agent_on_start_state,\n                aeron_driver_conductor_do_work,\n                aeron_driver_conductor_on_close,\n                _driver->context->conductor_idle_strategy_func,\n                _driver->context->conductor_idle_strategy_state) < 0)\n            {\n                goto error;\n            }\n\n            if (aeron_agent_init(\n                &_driver->runners[AERON_AGENT_RUNNER_SENDER],\n                \"sender\",\n                &_driver->sender,\n                _driver->context->agent_on_start_func,\n                _driver->context->agent_on_start_state,\n                aeron_driver_sender_do_work,\n                aeron_driver_sender_on_close,\n                _driver->context->sender_idle_strategy_func,\n                _driver->context->sender_idle_strategy_state) < 0)\n            {\n                goto error;\n            }\n\n            if (aeron_agent_init(\n                &_driver->runners[AERON_AGENT_RUNNER_RECEIVER],\n                \"receiver\",\n                &_driver->receiver,\n                _driver->context->agent_on_start_func,\n                _driver->context->agent_on_start_state,\n                aeron_driver_receiver_do_work,\n                aeron_driver_receiver_on_close,\n                _driver->context->receiver_idle_strategy_func,\n                _driver->context->receiver_idle_strategy_state) < 0)\n            {\n                goto error;\n            }\n            break;\n    }\n\n    *driver = _driver;\n    return 0;\n\nerror:\n    if (NULL != _driver)\n    {\n        aeron_free(_driver);\n    }\n\n    return -1;\n}\n\nint aeron_driver_start(aeron_driver_t *driver, bool manual_main_loop)\n{\n    if (NULL == driver)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"driver is null\");\n        return -1;\n    }\n\n    if (!manual_main_loop)\n    {\n        if (AERON_THREADING_MODE_INVOKER == driver->context->threading_mode)\n        {\n            AERON_SET_ERR(EINVAL, \"%s\", \"INVOKER threading mode requires manual_main_loop\");\n            return -1;\n        }\n\n        if (aeron_agent_start(&driver->runners[0]) < 0)\n        {\n            return -1;\n        }\n    }\n    else\n    {\n        aeron_agent_runner_t first_runner = driver->runners[0];\n        if (NULL != first_runner.on_start)\n        {\n            first_runner.on_start(first_runner.on_start_state, first_runner.role_name);\n        }\n\n        first_runner.state = AERON_AGENT_STATE_MANUAL;\n    }\n\n    for (int i = 1; i < AERON_AGENT_RUNNER_MAX; i++)\n    {\n        if (AERON_AGENT_STATE_INITED == driver->runners[i].state)\n        {\n            if (aeron_agent_start(&driver->runners[i]) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nint aeron_driver_main_do_work(aeron_driver_t *driver)\n{\n    if (NULL == driver)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"driver is null\");\n        return -1;\n    }\n\n    return aeron_agent_do_work(&driver->runners[AERON_AGENT_RUNNER_CONDUCTOR]);\n}\n\nvoid aeron_driver_main_idle_strategy(aeron_driver_t *driver, int work_count)\n{\n    if (NULL == driver)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"driver is null\");\n        return;\n    }\n\n    aeron_agent_idle(&driver->runners[AERON_AGENT_RUNNER_CONDUCTOR], work_count);\n}\n\nint aeron_driver_close(aeron_driver_t *driver)\n{\n    if (NULL == driver)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"driver is null\");\n        return -1;\n    }\n\n    for (int i = 0; i < AERON_AGENT_RUNNER_MAX; i++)\n    {\n        if (aeron_agent_stop(&driver->runners[i]) < 0)\n        {\n            return -1;\n        }\n    }\n\n    for (int i = 0; i < AERON_AGENT_RUNNER_MAX; i++)\n    {\n        if (aeron_agent_close(&driver->runners[i]) < 0)\n        {\n            return -1;\n        }\n    }\n\n    aeron_free(driver);\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_H\n#define AERON_DRIVER_H\n\n#include \"aeron_driver_context.h\"\n#include \"aeron_driver_conductor.h\"\n#include \"aeron_driver_sender.h\"\n#include \"aeron_driver_receiver.h\"\n\n#define AERON_AGENT_RUNNER_CONDUCTOR 0\n#define AERON_AGENT_RUNNER_SENDER 1\n#define AERON_AGENT_RUNNER_RECEIVER 2\n#define AERON_AGENT_RUNNER_SHARED_NETWORK 1\n#define AERON_AGENT_RUNNER_SHARED 0\n#define AERON_AGENT_RUNNER_MAX 3\n\ntypedef struct aeron_driver_stct\n{\n    aeron_driver_context_t *context;\n    aeron_driver_conductor_t conductor;\n    aeron_driver_sender_t sender;\n    aeron_driver_receiver_t receiver;\n    aeron_agent_runner_t runners[AERON_AGENT_RUNNER_MAX];\n}\naeron_driver_t;\n\nbool aeron_is_driver_active_with_cnc(\n    aeron_mapped_file_t *cnc_map, int64_t timeout_ms, int64_t now_ms, aeron_log_func_t log_func);\n\n#endif //AERON_DRIVER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_common.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_COMMON_H\n#define AERON_DRIVER_COMMON_H\n\n#include <stdint.h>\n#include <stddef.h>\n#include <stdbool.h>\n\n#define AERON_MAX_HOSTNAME_LEN (256)\n#define AERON_CHANNEL_STATUS_INDICATOR_NOT_ALLOCATED (-1)\n#define AERON_URI_INVALID_TAG (-1)\n\ntypedef void (*aeron_idle_strategy_func_t)(void *state, int work_count);\ntypedef int (*aeron_idle_strategy_init_func_t)(void **state, const char *env_var, const char *init_args);\n\ntypedef enum aeron_driver_managed_resource_event_enum\n{\n    AERON_DRIVER_MANAGED_RESOURCE_EVENT_INCREF,\n    AERON_DRIVER_MANAGED_RESOURCE_EVENT_DECREF,\n    AERON_DRIVER_MANAGED_RESOURCE_EVENT_REVOKE\n}\naeron_driver_managed_resource_event_t;\n\ntypedef struct aeron_driver_managed_resource_stct\n{\n    int64_t registration_id;\n    int64_t time_of_last_state_change_ns;\n    void *clientd;\n    void (*handle_event)(aeron_driver_managed_resource_event_t, void *);\n}\naeron_driver_managed_resource_t;\n\n#define AERON_DRIVER_MANAGED_RESOURCE_HANDLE_EVENT(_resource, _event) \\\ndo                                                                    \\\n{                                                                     \\\n    if (NULL != (_resource) && NULL != (_resource)->handle_event)     \\\n    {                                                                 \\\n        (_resource)->handle_event((_event), (_resource)->clientd);    \\\n    }                                                                 \\\n}                                                                     \\\nwhile (0)\n\n#define AERON_DRIVER_MANAGED_RESOURCE_INCREF(_resource) AERON_DRIVER_MANAGED_RESOURCE_HANDLE_EVENT(_resource, AERON_DRIVER_MANAGED_RESOURCE_EVENT_INCREF)\n#define AERON_DRIVER_MANAGED_RESOURCE_DECREF(_resource) AERON_DRIVER_MANAGED_RESOURCE_HANDLE_EVENT(_resource, AERON_DRIVER_MANAGED_RESOURCE_EVENT_DECREF)\n#define AERON_DRIVER_MANAGED_RESOURCE_REVOKE(_resource) AERON_DRIVER_MANAGED_RESOURCE_HANDLE_EVENT(_resource, AERON_DRIVER_MANAGED_RESOURCE_EVENT_REVOKE)\n\ntypedef struct aeron_position_stct\n{\n    int32_t counter_id;\n    volatile int64_t *value_addr;\n}\naeron_position_t;\n\ntypedef struct aeron_position_stct aeron_atomic_counter_t;\n\ntypedef enum aeron_subscription_tether_state_enum\n{\n    AERON_SUBSCRIPTION_TETHER_ACTIVE,\n    AERON_SUBSCRIPTION_TETHER_LINGER,\n    AERON_SUBSCRIPTION_TETHER_RESTING,\n    AERON_SUBSCRIPTION_TETHER_CLOSED\n}\naeron_subscription_tether_state_t;\n\ntypedef struct aeron_tetherable_position_stct\n{\n    volatile int64_t *value_addr;\n    int64_t subscription_registration_id;\n    int64_t time_of_last_update_ns;\n    aeron_subscription_tether_state_t state;\n    int32_t counter_id;\n    bool is_tether;\n    bool is_rejoin;\n}\naeron_tetherable_position_t;\n\ntypedef void (*aeron_untethered_subscription_state_change_func_t)(\n    aeron_tetherable_position_t *tetherable_position,\n    int64_t now_ns,\n    aeron_subscription_tether_state_t old_state,\n    aeron_subscription_tether_state_t new_state,\n    int32_t stream_id,\n    int32_t session_id);\n\ntypedef struct aeron_subscribable_stct\n{\n    int64_t correlation_id;\n    size_t length;\n    size_t capacity;\n    aeron_tetherable_position_t *array;\n    size_t inactive_count;\n    void (*add_position_hook_func)(void *clientd, volatile int64_t *value_addr);\n    void (*remove_position_hook_func)(void *clientd, volatile int64_t *value_addr);\n    void *clientd;\n}\naeron_subscribable_t;\n\nvoid aeron_driver_subscribable_state(\n    aeron_subscribable_t *subscribable,\n    aeron_tetherable_position_t *tetherable_position,\n    aeron_subscription_tether_state_t state,\n    int64_t now_ns,\n    int32_t stream_id,\n    int32_t session_id,\n    aeron_untethered_subscription_state_change_func_t log_func);\n\nsize_t aeron_driver_subscribable_working_position_count(aeron_subscribable_t *subscribable);\n\nbool aeron_driver_subscribable_has_working_positions(aeron_subscribable_t *subscribable);\n\nbool aeron_driver_subscribable_is_active_state(aeron_subscription_tether_state_t state);\n\ntypedef struct aeron_command_base_stct\n{\n    void (*func)(void *clientd, void *command);\n    void *item;\n}\naeron_command_base_t;\n\ntypedef struct aeron_feedback_delay_generator_state_stct aeron_feedback_delay_generator_state_t;\n\ntypedef int64_t (*aeron_feedback_delay_generator_func_t)(aeron_feedback_delay_generator_state_t *state, bool retry);\n\nstruct aeron_feedback_delay_generator_state_stct\n{\n    struct static_delay_stct\n    {\n        int64_t delay_ns;\n        int64_t retry_ns;\n    }\n    static_delay;\n\n    struct optimal_delay_stct\n    {\n        double rand_max;\n        double base_x;\n        double constant_t;\n        double factor_t;\n    }\n    optimal_delay;\n\n    aeron_feedback_delay_generator_func_t delay_generator;\n};\n\nvoid aeron_driver_subscribable_remove_position(aeron_subscribable_t *subscribable, int32_t counter_id);\n\ninline void aeron_driver_subscribable_null_hook(void *clientd, volatile int64_t *value_addr)\n{\n}\n\ntypedef void (*aeron_on_remove_publication_cleanup_func_t)(\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\ntypedef void (*aeron_on_remove_subscription_cleanup_func_t)(\n    int64_t id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\ntypedef void (*aeron_on_remove_image_cleanup_func_t)(\n    int64_t id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\ntypedef void (*aeron_on_endpoint_change_func_t)(const void *channel);\n\n#endif //AERON_DRIVER_COMMON_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_conductor.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"util/aeron_platform.h\"\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdio.h>\n#include <inttypes.h>\n#include <errno.h>\n#if !defined(AERON_COMPILER_MSVC)\n#include <unistd.h>\n#endif\n\n\n#include \"util/aeron_math.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"aeron_driver_conductor.h\"\n#include \"aeron_position.h\"\n#include \"aeron_driver_sender.h\"\n#include \"aeron_driver_receiver.h\"\n#include \"collections/aeron_bit_set.h\"\n#include \"uri/aeron_uri.h\"\n#include \"util/aeron_parse_util.h\"\n\n\n#define STATIC_BIT_SET_U64_LEN (512u)\n\n#define COPY_ENDPOINT_NAME(_d, _s)                    \\\ndo                                                    \\\n{                                                     \\\n    size_t _len = strnlen(_s, AERON_MAX_HOST_LENGTH); \\\n    memcpy(_d, _s, _len);                             \\\n    _d[_len] = '\\0';                                  \\\n} while (0)                                           \\\n\ntypedef struct aeron_time_tracking_name_resolver_stct\n{\n    aeron_name_resolver_t delegate_resolver;\n    aeron_driver_context_t *context;\n}\naeron_time_tracking_name_resolver_t;\n\nconst char * const AERON_DRIVER_CONDUCTOR_INVALID_DESTINATION_KEYS[] =\n{\n    AERON_URI_MTU_LENGTH_KEY,\n    AERON_URI_RECEIVER_WINDOW_KEY,\n    AERON_URI_SOCKET_RCVBUF_KEY,\n    AERON_URI_SOCKET_SNDBUF_KEY,\n    AERON_URI_RESPONSE_CORRELATION_ID_KEY,\n    NULL\n};\n\nstatic bool aeron_driver_conductor_network_subscription_link_matches(\n    const aeron_subscription_link_t *link,\n    const aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    bool has_session_id,\n    int32_t session_id)\n{\n    return link->endpoint == endpoint &&\n        link->stream_id == stream_id &&\n        link->has_session_id == has_session_id &&\n        (!has_session_id || link->session_id == session_id);\n}\n\nstatic bool aeron_driver_conductor_is_wildcard_or_session_id_match(\n    const aeron_subscription_link_t *link, int32_t session_id)\n{\n    return (!link->has_session_id && !link->is_response) || (link->session_id == session_id);\n}\n\nstatic bool aeron_driver_conductor_network_subscription_link_matches_allowing_wildcard(\n    const aeron_subscription_link_t *link,\n    const aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    return link->endpoint == endpoint &&\n        link->stream_id == stream_id &&\n        aeron_driver_conductor_is_wildcard_or_session_id_match(link, session_id);\n}\n\nstatic bool aeron_driver_conductor_spy_subscription_link_matches(\n    const aeron_subscription_link_t *link,\n    const aeron_network_publication_t *publication)\n{\n    aeron_udp_channel_t *const spy_channel = link->spy_channel;\n    const aeron_udp_channel_t *publication_channel = publication->endpoint->conductor_fields.udp_channel;\n    bool is_same_channel_tag =\n        AERON_URI_INVALID_TAG != spy_channel->tag_id && spy_channel->tag_id == publication_channel->tag_id;\n    return link->stream_id == publication->stream_id &&\n        (is_same_channel_tag ||\n            (aeron_driver_conductor_is_wildcard_or_session_id_match(link, publication->session_id) &&\n                publication_channel->canonical_length == spy_channel->canonical_length &&\n                0 == strncmp(\n                    publication_channel->canonical_form,\n                    spy_channel->canonical_form,\n                    publication_channel->canonical_length)));\n}\n\nstatic bool aeron_driver_conductor_subscription_link_matches_ipc_publication(\n    const aeron_subscription_link_t *link, const aeron_ipc_publication_t *publication)\n{\n    return link->stream_id == publication->stream_id &&\n        aeron_driver_conductor_is_wildcard_or_session_id_match(link, publication->session_id);\n}\n\nstatic bool aeron_driver_conductor_has_network_subscription_interest(\n    aeron_driver_conductor_t *conductor,\n    const aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    for (size_t i = 0, length = conductor->network_subscriptions.length; i < length; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n\n        if (aeron_driver_conductor_network_subscription_link_matches_allowing_wildcard(\n            link,\n            endpoint,\n            stream_id,\n            session_id))\n        {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nstatic bool aeron_driver_conductor_is_oldest_subscription_sparse(\n    aeron_driver_conductor_t *conductor,\n    const aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t highest_id)\n{\n    int64_t registration_id = highest_id;\n    bool is_sparse = conductor->context->term_buffer_sparse_file;\n\n    for (size_t i = 0, length = conductor->network_subscriptions.length; i < length; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n\n        if (aeron_driver_conductor_network_subscription_link_matches_allowing_wildcard(\n            link,\n            endpoint,\n            stream_id,\n            session_id) &&\n            link->registration_id < registration_id)\n        {\n            registration_id = link->registration_id;\n            is_sparse = link->is_sparse;\n        }\n    }\n\n    return is_sparse;\n}\n\nstatic bool aeron_driver_conductor_receive_endpoint_has_clashing_timestamp_offsets(\n    aeron_driver_conductor_t *conductor,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel)\n{\n    if (endpoint->conductor_fields.udp_channel->media_rcv_timestamp_offset != channel->media_rcv_timestamp_offset)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"option conflicts with existing subscription: %s=%\" PRId32 \" %s existingChannel=%.*s channel=%.*s\",\n            AERON_URI_MEDIA_RCV_TIMESTAMP_OFFSET_KEY,\n            channel->media_rcv_timestamp_offset,\n            aeron_driver_uri_get_offset_info(channel->media_rcv_timestamp_offset),\n            (int)endpoint->conductor_fields.udp_channel->uri_length,\n            endpoint->conductor_fields.udp_channel->original_uri,\n            (int)channel->uri_length,\n            channel->original_uri);\n        return true;\n    }\n\n    if (endpoint->conductor_fields.udp_channel->channel_rcv_timestamp_offset != channel->channel_rcv_timestamp_offset)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"option conflicts with existing subscription: %s=%\" PRId32 \" %s existingChannel=%.*s channel=%.*s\",\n            AERON_URI_CHANNEL_RCV_TIMESTAMP_OFFSET_KEY,\n            channel->channel_rcv_timestamp_offset,\n            aeron_driver_uri_get_offset_info(channel->channel_rcv_timestamp_offset),\n            (int)endpoint->conductor_fields.udp_channel->uri_length,\n            endpoint->conductor_fields.udp_channel->original_uri,\n            (int)channel->uri_length,\n            channel->original_uri);\n        return true;\n    }\n\n    return false;\n}\n\nstatic int aeron_driver_conductor_find_existing_receive_channel_endpoint(\n    aeron_driver_conductor_t *conductor,\n    aeron_udp_channel_t *channel,\n    aeron_receive_channel_endpoint_t **result_endpoint)\n{\n    aeron_receive_channel_endpoint_t *endpoint;\n    if (AERON_URI_INVALID_TAG != channel->tag_id)\n    {\n        for (size_t i = 0, size = conductor->receive_channel_endpoints.length; i < size; i++)\n        {\n            endpoint = conductor->receive_channel_endpoints.array[i].endpoint;\n\n            bool has_match = false;\n            if (aeron_receive_channel_endpoint_matches_tag(endpoint, channel, &has_match) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return -1;\n            }\n\n            if (has_match)\n            {\n                *result_endpoint = endpoint;\n                return 0;\n            }\n        }\n    }\n\n    endpoint = aeron_str_to_ptr_hash_map_get(\n        &conductor->receive_channel_endpoint_by_channel_map, channel->canonical_form, channel->canonical_length);\n    if (NULL != endpoint &&\n        AERON_URI_INVALID_TAG != endpoint->conductor_fields.udp_channel->tag_id &&\n        AERON_URI_INVALID_TAG != channel->tag_id &&\n        channel->tag_id != endpoint->conductor_fields.udp_channel->tag_id)\n    {\n        endpoint = NULL;\n    }\n\n    *result_endpoint = endpoint;\n    return 0;\n}\nstatic int aeron_driver_conductor_find_existing_send_channel_endpoint(\n    aeron_driver_conductor_t *conductor,\n    aeron_udp_channel_t *channel,\n    aeron_send_channel_endpoint_t **result_endpoint)\n{\n    if (AERON_URI_INVALID_TAG != channel->tag_id)\n    {\n        for (size_t i = 0, size = conductor->send_channel_endpoints.length; i < size; i++)\n        {\n            aeron_send_channel_endpoint_t *endpoint = conductor->send_channel_endpoints.array[i].endpoint;\n\n            bool has_match = false;\n            if (aeron_send_channel_endpoint_matches_tag(endpoint, channel, &has_match) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return -1;\n            }\n\n            if (has_match)\n            {\n                *result_endpoint = endpoint;\n                return 0;\n            }\n        }\n\n        if (!channel->has_explicit_control &&\n            AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL != channel->control_mode &&\n            NULL == channel->uri.params.udp.endpoint)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"URI must have explicit control, endpoint, or be manual control-mode when original: channel=%.*s\",\n                (int)channel->uri_length,\n                channel->original_uri);\n            return -1;\n        }\n    }\n\n    aeron_send_channel_endpoint_t *endpoint = aeron_str_to_ptr_hash_map_get(\n        &conductor->send_channel_endpoint_by_channel_map, channel->canonical_form, channel->canonical_length);\n    if (NULL != endpoint &&\n        AERON_URI_INVALID_TAG != endpoint->conductor_fields.udp_channel->tag_id &&\n        AERON_URI_INVALID_TAG != channel->tag_id &&\n        channel->tag_id != endpoint->conductor_fields.udp_channel->tag_id)\n    {\n        endpoint = NULL;\n    }\n\n    *result_endpoint = endpoint;\n    return 0;\n}\n\nstatic bool aeron_driver_conductor_has_clashing_subscription(\n    aeron_driver_conductor_t *conductor,\n    aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    aeron_driver_uri_subscription_params_t *params)\n{\n    aeron_receive_channel_endpoint_t *endpoint;\n    if (aeron_driver_conductor_find_existing_receive_channel_endpoint(conductor, channel, &endpoint) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return true;\n    }\n\n    if (NULL != endpoint)\n    {\n        if (aeron_driver_conductor_receive_endpoint_has_clashing_timestamp_offsets(conductor, endpoint, channel))\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return true;\n        }\n\n        for (size_t i = 0, length = conductor->network_subscriptions.length; i < length; i++)\n        {\n            aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n            bool matches_tag = AERON_URI_INVALID_TAG != channel->tag_id ||\n                link->endpoint->conductor_fields.udp_channel->tag_id == channel->tag_id;\n\n            if (matches_tag && aeron_driver_conductor_network_subscription_link_matches(\n                link,\n                endpoint,\n                stream_id,\n                params->has_session_id,\n                params->session_id))\n            {\n                if (params->is_reliable != link->is_reliable)\n                {\n                    const char *value = params->is_reliable ? \"true\" : \"false\";\n                    AERON_SET_ERR(\n                        EINVAL,\n                        \"option conflicts with existing subscription: reliable=%s existingChannel=%.*s channel=%.*s\",\n                        value,\n                        link->channel_length,\n                        link->channel,\n                        (int)channel->uri_length,\n                        channel->original_uri);\n                    return true;\n                }\n\n                if (params->is_rejoin != link->is_rejoin)\n                {\n                    const char *value = params->is_rejoin ? \"true\" : \"false\";\n                    AERON_SET_ERR(\n                        EINVAL,\n                        \"option conflicts with existing subscription: rejoin=%s existingChannel=%.*s channel=%.*s\",\n                        value,\n                        link->channel_length,\n                        link->channel,\n                        (int)channel->uri_length,\n                        channel->original_uri);\n                    return true;\n                }\n\n                if (params->is_response != link->is_response)\n                {\n                    const char *value = params->is_response ? \"true\" : \"false\";\n                    AERON_SET_ERR(\n                        EINVAL,\n                        \"option conflicts with existing subscription: isResponse=%s existingChannel=%.*s channel=%.*s\",\n                        value,\n                        link->channel_length,\n                        link->channel,\n                        (int)channel->uri_length,\n                        channel->original_uri);\n                    return true;\n                }\n            }\n        }\n    }\n\n    return false;\n}\n\nstatic bool aeron_driver_conductor_send_endpoint_has_clashing_timestamp_offsets(\n    aeron_driver_conductor_t *conductor,\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel)\n{\n    if (endpoint->conductor_fields.udp_channel->channel_snd_timestamp_offset != channel->channel_snd_timestamp_offset)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"option conflicts with existing subscription: %s=%\" PRId32 \" %s existingChannel=%.*s channel=%.*s\",\n            AERON_URI_CHANNEL_SND_TIMESTAMP_OFFSET_KEY,\n            channel->channel_snd_timestamp_offset,\n            aeron_driver_uri_get_offset_info(channel->channel_snd_timestamp_offset),\n            (int)endpoint->conductor_fields.udp_channel->uri_length,\n            endpoint->conductor_fields.udp_channel->original_uri,\n            (int)channel->uri_length,\n            channel->original_uri);\n        return true;\n    }\n\n    return false;\n}\n\n\nstatic int aeron_driver_conductor_validate_destination_uri_prefix(\n    const char *channel_uri, int32_t channel_length, const char *transport_direction)\n{\n    if ((int32_t)AERON_SPY_PREFIX_LEN <= channel_length &&\n        0 == strncmp(channel_uri, AERON_SPY_PREFIX, AERON_SPY_PREFIX_LEN))\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_INVALID_CHANNEL,\n            \"Aeron spies are invalid as %s destinations: %.*s\",\n            transport_direction,\n            (int)channel_length,\n            channel_uri);\n\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_conductor_validate_send_destination_uri(aeron_uri_t *uri, size_t uri_length)\n{\n    if (AERON_URI_UDP == uri->type && NULL != uri->params.udp.endpoint)\n    {\n        aeron_parsed_address_t parsed_address = { .host = { 0 }, .port = { 0 }, .ip_version_hint= 0 };\n\n        if (0 <= aeron_address_split(uri->params.udp.endpoint, &parsed_address))\n        {\n            if (0 == strcmp(\"0\", parsed_address.port))\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"%s has port=0 for send destination: channel=%.*s\",\n                    AERON_UDP_CHANNEL_ENDPOINT_KEY,\n                    (int)uri_length,\n                    uri->mutable_uri);\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_conductor_validate_destination_uri_params(aeron_uri_t *uri, size_t uri_length)\n{\n    aeron_uri_params_t *params = NULL;\n    switch (uri->type)\n    {\n        case AERON_URI_UDP:\n            params = &uri->params.udp.additional_params;\n            break;\n\n        case AERON_URI_IPC:\n            params = &uri->params.ipc.additional_params;\n            break;\n\n        case AERON_URI_UNKNOWN:\n            AERON_SET_ERR(EINVAL, \"unknown uri type: channel=%.*s\", (int)uri_length, uri->mutable_uri);\n            break;\n    }\n\n    if (NULL == params)\n    {\n        return -1;\n    }\n\n    for (int i = 0; NULL != AERON_DRIVER_CONDUCTOR_INVALID_DESTINATION_KEYS[i]; i++)\n    {\n        const char *param = AERON_DRIVER_CONDUCTOR_INVALID_DESTINATION_KEYS[i];\n        if (NULL != aeron_uri_find_param_value(params, param))\n        {\n            AERON_SET_ERR(\n                -AERON_ERROR_CODE_INVALID_CHANNEL,\n                \"destinations must not contain the key: %s channel=%.*s\",\n                param,\n                (int)uri_length,\n                uri->mutable_uri);\n            return -1;\n        }\n    }\n\n    if (AERON_URI_UDP == uri->type &&\n        NULL != uri->params.udp.control_mode &&\n        0 == strcmp(uri->params.udp.control_mode, AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE))\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_INVALID_CHANNEL,\n            \"destinations may not specify %s=%s\",\n            AERON_UDP_CHANNEL_CONTROL_MODE_KEY,\n            AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_conductor_validate_initial_window_for_rcvbuf(\n    aeron_driver_uri_subscription_params_t *params,\n    size_t endpoint_socket_rcvbuf,\n    size_t os_default_socket_rcvbuf,\n    aeron_udp_channel_t *channel,\n    aeron_udp_channel_t *existing_channel)\n{\n    if (0 != endpoint_socket_rcvbuf && endpoint_socket_rcvbuf < params->initial_window_length)\n    {\n        if (NULL == existing_channel)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"Initial window greater than SO_SNDBUF for channel: rcv-wnd=%\" PRIu64 \" so-rcvbuf=%\" PRIu64 \" channel=%.*s\",\n                params->initial_window_length,\n                endpoint_socket_rcvbuf,\n                (int)channel->uri_length,\n                channel->original_uri);\n        }\n        else\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"Initial window greater than SO_SNDBUF for channel: rcv-wnd=%\" PRIu64 \" so-rcvbuf=%\" PRIu64 \" existingChannel=%.*s channel=%.*s\",\n                params->initial_window_length,\n                endpoint_socket_rcvbuf,\n                (int)existing_channel->uri_length,\n                existing_channel->original_uri,\n                (int)channel->uri_length,\n                channel->original_uri);\n        }\n        return -1;\n    }\n\n    if (0 == endpoint_socket_rcvbuf && os_default_socket_rcvbuf < params->initial_window_length)\n    {\n        if (NULL == existing_channel)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"Initial window greater than SO_SNDBUF for channel: rcv-wnd=%\" PRIu64 \" so-rcvbuf=%\" PRIu64 \" (OS default) channel=%.*s\",\n                params->initial_window_length,\n                endpoint_socket_rcvbuf,\n                (int)channel->uri_length,\n                channel->original_uri);\n        }\n        else\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"Initial window greater than SO_SNDBUF for channel: rcv-wnd=%\" PRIu64 \" so-rcvbuf=%\" PRIu64 \" (OS default) existingChannel=%.*s channel=%.*s\",\n                params->initial_window_length,\n                endpoint_socket_rcvbuf,\n                (int)existing_channel->uri_length,\n                existing_channel->original_uri,\n                (int)channel->uri_length,\n                channel->original_uri);\n        }\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_conductor_validate_channel_buffer_length(\n    const char *param_name,\n    size_t new_length,\n    size_t existing_length,\n    aeron_udp_channel_t *channel,\n    aeron_udp_channel_t *existing_channel)\n{\n    if (0 != new_length && new_length != existing_length)\n    {\n        if (0 == existing_length)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"%s=%\" PRIu64 \" does not match existing value of OS default: existingChannel=%.*s channel=%.*s\",\n                param_name,\n                (uint64_t)new_length,\n                (int)existing_channel->uri_length,\n                existing_channel->original_uri,\n                (int)channel->uri_length,\n                channel->original_uri);\n        }\n        else\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"%s=%\" PRIu64 \" does not match existing value of %\" PRIu64 \": existingChannel=%.*s channel=%.*s\",\n                param_name,\n                (uint64_t)new_length,\n                (uint64_t)existing_length,\n                (int)existing_channel->uri_length,\n                existing_channel->original_uri,\n                (int)channel->uri_length,\n                channel->original_uri);\n        }\n\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_conductor_validate_endpoint_for_publication(aeron_udp_channel_t *udp_channel)\n{\n    if (!aeron_udp_channel_is_multi_destination(udp_channel) &&\n        udp_channel->has_explicit_endpoint &&\n        aeron_is_wildcard_port(&udp_channel->remote_data))\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"%s has port=0 for publication: channel=%.*s\",\n            AERON_UDP_CHANNEL_ENDPOINT_KEY,\n            (int)udp_channel->uri_length,\n            udp_channel->original_uri);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_conductor_validate_control_for_publication(aeron_udp_channel_t *udp_channel)\n{\n    if (AERON_UDP_CHANNEL_CONTROL_MODE_DYNAMIC == udp_channel->control_mode && !udp_channel->has_explicit_control)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"'control-mode=dynamic' requires that 'control' parameter is set, channel=%.*s\",\n            (int)udp_channel->uri_length,\n            udp_channel->original_uri);\n        return -1;\n    }\n\n    if (udp_channel->has_explicit_control &&\n        !udp_channel->has_explicit_endpoint &&\n        NULL == udp_channel->uri.params.udp.control_mode)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"'control' parameter requires that either 'endpoint' or 'control-mode' is specified, channel=%.*s\",\n            (int)udp_channel->uri_length,\n            udp_channel->original_uri);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_conductor_validate_control_for_subscription(aeron_udp_channel_t *udp_channel)\n{\n    if (udp_channel->has_explicit_control &&\n        aeron_is_wildcard_port(&udp_channel->local_control))\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"%s has port=0 for subscription: channel=%.*s\",\n            AERON_UDP_CHANNEL_CONTROL_KEY,\n            (int)udp_channel->uri_length,\n            udp_channel->original_uri);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_conductor_validate_response_subscription(\n    aeron_driver_conductor_t *conductor,\n    aeron_udp_channel_t *udp_channel,\n    aeron_driver_uri_publication_params_t *param)\n{\n    if (AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE != udp_channel->control_mode &&\n        AERON_NULL_VALUE != param->response_correlation_id)\n    {\n        for (int last_index = (int)conductor->network_subscriptions.length - 1, i = last_index; i >= 0; i--)\n        {\n            aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n            if (param->response_correlation_id == link->registration_id)\n            {\n                return 0;\n            }\n        }\n\n        AERON_SET_ERR(\n            EINVAL,\n            \"unable to find response subscription for response-correlation-id=%\" PRId64,\n            param->response_correlation_id);\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_conductor_validate_experimental_features(\n    bool enable_experimental_features,\n    aeron_udp_channel_t *udp_channel)\n{\n    if (enable_experimental_features)\n    {\n        return 0;\n    }\n\n    /*\n     * Put experimental feature validation here.\n     */\n\n    return 0;\n}\n\nstatic int aeron_time_tracking_name_resolver_resolve(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_resolution,\n    struct sockaddr_storage *address)\n{\n    aeron_time_tracking_name_resolver_t *time_tracking_resolver = (aeron_time_tracking_name_resolver_t *)resolver->state;\n    aeron_driver_context_t *context = time_tracking_resolver->context;\n    int64_t begin_ns = context->nano_clock();\n    aeron_duty_cycle_tracker_t *tracker = context->name_resolver_time_tracker;\n    tracker->update(tracker->state, begin_ns);\n\n    int result = time_tracking_resolver->delegate_resolver.resolve_func(\n        &time_tracking_resolver->delegate_resolver,\n        name,\n        uri_param_name,\n        is_re_resolution,\n        address);\n\n    int64_t end_ns = context->nano_clock();\n    tracker->measure_and_update(tracker->state, end_ns);\n\n    if (NULL != context->log.on_name_resolve)\n    {\n        struct sockaddr_storage *resolved_address = 0 <= result ? address : NULL;\n        context->log.on_name_resolve(\n            &time_tracking_resolver->delegate_resolver, end_ns - begin_ns, name, is_re_resolution, resolved_address);\n    }\n\n    return result;\n}\n\nstatic int aeron_time_tracking_name_resolver_lookup(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_lookup,\n    const char **resolved_name)\n{\n    aeron_time_tracking_name_resolver_t *time_tracking_resolver = (aeron_time_tracking_name_resolver_t *)resolver->state;\n    aeron_driver_context_t *context = time_tracking_resolver->context;\n    int64_t begin_ns = context->nano_clock();\n    aeron_duty_cycle_tracker_t *tracker = context->name_resolver_time_tracker;\n    tracker->update(tracker->state, begin_ns);\n\n    int result = time_tracking_resolver->delegate_resolver.lookup_func(\n        &time_tracking_resolver->delegate_resolver,\n        name,\n        uri_param_name,\n        is_re_lookup,\n        resolved_name);\n\n    int64_t end_ns = context->nano_clock();\n    tracker->measure_and_update(tracker->state, end_ns);\n\n    if (NULL != context->log.on_name_lookup)\n    {\n        const char *result_name = 0 <= result ? *resolved_name : NULL;\n        context->log.on_name_lookup(\n            &time_tracking_resolver->delegate_resolver, end_ns - begin_ns, name, is_re_lookup, result_name);\n    }\n\n    return result;\n}\n\nstatic int aeron_time_tracking_name_resolver_do_work(aeron_name_resolver_t *resolver, int64_t now_ms)\n{\n    aeron_time_tracking_name_resolver_t *time_tracking_resolver = (aeron_time_tracking_name_resolver_t *)resolver->state;\n    return time_tracking_resolver->delegate_resolver.do_work_func(&time_tracking_resolver->delegate_resolver, now_ms);\n}\n\nstatic int aeron_time_tracking_name_resolver_close(aeron_name_resolver_t *resolver)\n{\n    aeron_time_tracking_name_resolver_t *time_tracking_resolver = (aeron_time_tracking_name_resolver_t *)resolver->state;\n    time_tracking_resolver->delegate_resolver.close_func(&time_tracking_resolver->delegate_resolver);\n    aeron_free(time_tracking_resolver);\n    return 0;\n}\n\nstatic bool aeron_driver_conductor_treat_image_as_multicast(\n    aeron_udp_channel_t *channel, uint8_t flags, aeron_inferable_boolean_t is_group)\n{\n    bool is_group_from_flag = ((flags & AERON_SETUP_HEADER_GROUP_FLAG) == AERON_SETUP_HEADER_GROUP_FLAG);\n    return AERON_INFER == is_group ?\n        channel->is_multicast || is_group_from_flag : AERON_FORCE_TRUE == is_group;\n}\n\nint aeron_driver_conductor_init(aeron_driver_conductor_t *conductor, aeron_driver_context_t *context)\n{\n    if (aeron_mpsc_rb_init(\n        &conductor->to_driver_commands, context->to_driver_buffer, context->to_driver_buffer_length) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_broadcast_transmitter_init(\n        &conductor->to_clients, context->to_clients_buffer, context->to_clients_buffer_length) < 0)\n    {\n        return -1;\n    }\n\n    int64_t free_to_reuse_timeout_ms = 0;\n    if (context->counter_free_to_reuse_ns > 0)\n    {\n        free_to_reuse_timeout_ms = (int64_t)context->counter_free_to_reuse_ns / (1000 * 1000LL);\n        free_to_reuse_timeout_ms = free_to_reuse_timeout_ms <= 0 ? 1 : free_to_reuse_timeout_ms;\n    }\n\n    if (aeron_counters_manager_init(\n        &conductor->counters_manager,\n        context->counters_metadata_buffer,\n        AERON_COUNTERS_METADATA_BUFFER_LENGTH(context->counters_values_buffer_length),\n        context->counters_values_buffer,\n        context->counters_values_buffer_length,\n        context->cached_clock,\n        free_to_reuse_timeout_ms) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_system_counters_init(&conductor->system_counters, &conductor->counters_manager) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_distinct_error_log_init(\n        &conductor->error_log, context->error_buffer, context->error_buffer_length, context->epoch_clock) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_str_to_ptr_hash_map_init(\n        &conductor->send_channel_endpoint_by_channel_map, 64, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_str_to_ptr_hash_map_init(\n        &conductor->receive_channel_endpoint_by_channel_map, 64, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        goto error;\n    }\n\n    if (aeron_loss_reporter_init(&conductor->loss_reporter, context->loss_report.addr, context->loss_report.length) < 0)\n    {\n        goto error;\n    }\n\n    conductor->conductor_proxy.command_queue = &context->conductor_command_queue;\n    conductor->conductor_proxy.fail_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_CONDUCTOR_PROXY_FAILS);\n    conductor->conductor_proxy.threading_mode = context->threading_mode;\n    conductor->conductor_proxy.conductor = conductor;\n\n    if (aeron_executor_init(&conductor->executor, context->async_executor_threads >= 1, NULL, conductor) < 0)\n    {\n        goto error;\n    }\n    conductor->async_client_command_in_flight = false;\n\n    conductor->clients.array = NULL;\n    conductor->clients.capacity = 0;\n    conductor->clients.length = 0;\n    conductor->clients.on_time_event = aeron_client_on_time_event;\n    conductor->clients.has_reached_end_of_life = aeron_client_has_reached_end_of_life;\n    conductor->clients.delete_func = aeron_client_delete;\n    conductor->clients.free_func = NULL;\n\n    conductor->ipc_publications.array = NULL;\n    conductor->ipc_publications.length = 0;\n    conductor->ipc_publications.capacity = 0;\n    conductor->ipc_publications.on_time_event = aeron_ipc_publication_entry_on_time_event;\n    conductor->ipc_publications.has_reached_end_of_life = aeron_ipc_publication_entry_has_reached_end_of_life;\n    conductor->ipc_publications.delete_func = aeron_ipc_publication_entry_delete;\n    conductor->ipc_publications.free_func = aeron_ipc_publication_entry_free;\n\n    conductor->network_publications.array = NULL;\n    conductor->network_publications.length = 0;\n    conductor->network_publications.capacity = 0;\n    conductor->network_publications.on_time_event = aeron_network_publication_entry_on_time_event;\n    conductor->network_publications.has_reached_end_of_life = aeron_network_publication_entry_has_reached_end_of_life;\n    conductor->network_publications.delete_func = aeron_network_publication_entry_delete;\n    conductor->network_publications.free_func = aeron_network_publication_entry_free;\n\n    conductor->send_channel_endpoints.array = NULL;\n    conductor->send_channel_endpoints.length = 0;\n    conductor->send_channel_endpoints.capacity = 0;\n    conductor->send_channel_endpoints.on_time_event = aeron_send_channel_endpoint_entry_on_time_event;\n    conductor->send_channel_endpoints.has_reached_end_of_life =\n        aeron_send_channel_endpoint_entry_has_reached_end_of_life;\n    conductor->send_channel_endpoints.delete_func = aeron_send_channel_endpoint_entry_delete;\n    conductor->send_channel_endpoints.free_func = NULL;\n\n    conductor->receive_channel_endpoints.array = NULL;\n    conductor->receive_channel_endpoints.length = 0;\n    conductor->receive_channel_endpoints.capacity = 0;\n    conductor->receive_channel_endpoints.on_time_event = aeron_receive_channel_endpoint_entry_on_time_event;\n    conductor->receive_channel_endpoints.has_reached_end_of_life =\n        aeron_receive_channel_endpoint_entry_has_reached_end_of_life;\n    conductor->receive_channel_endpoints.delete_func = aeron_receive_channel_endpoint_entry_delete;\n    conductor->receive_channel_endpoints.free_func = NULL;\n\n    conductor->publication_images.array = NULL;\n    conductor->publication_images.length = 0;\n    conductor->publication_images.capacity = 0;\n    conductor->publication_images.on_time_event = aeron_publication_image_entry_on_time_event;\n    conductor->publication_images.has_reached_end_of_life = aeron_publication_image_entry_has_reached_end_of_life;\n    conductor->publication_images.delete_func = aeron_publication_image_entry_delete;\n    conductor->publication_images.free_func = aeron_publication_image_entry_free;\n\n    conductor->lingering_resources.array = NULL;\n    conductor->lingering_resources.length = 0;\n    conductor->lingering_resources.capacity = 0;\n    conductor->lingering_resources.on_time_event = aeron_linger_resource_entry_on_time_event;\n    conductor->lingering_resources.has_reached_end_of_life = aeron_linger_resource_entry_has_reached_end_of_life;\n    conductor->lingering_resources.delete_func = aeron_linger_resource_entry_delete;\n    conductor->lingering_resources.free_func = NULL;\n\n    conductor->ipc_subscriptions.array = NULL;\n    conductor->ipc_subscriptions.length = 0;\n    conductor->ipc_subscriptions.capacity = 0;\n\n    conductor->network_subscriptions.array = NULL;\n    conductor->network_subscriptions.length = 0;\n    conductor->network_subscriptions.capacity = 0;\n\n    conductor->spy_subscriptions.array = NULL;\n    conductor->spy_subscriptions.length = 0;\n    conductor->spy_subscriptions.capacity = 0;\n\n    if (aeron_deque_init(&conductor->end_of_life_queue, 1024, sizeof(aeron_end_of_life_resource_t)))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    conductor->errors_counter = aeron_counters_manager_addr(&conductor->counters_manager, AERON_SYSTEM_COUNTER_ERRORS);\n    conductor->images_rejected_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_IMAGES_REJECTED);\n    conductor->unblocked_commands_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_UNBLOCKED_COMMANDS);\n    conductor->client_timeouts_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_CLIENT_TIMEOUTS);\n\n    int64_t now_ns = context->nano_clock();\n    aeron_clock_update_cached_time(context->cached_clock, context->epoch_clock(), now_ns);\n\n    conductor->clock_update_deadline_ns = now_ns + AERON_DRIVER_CONDUCTOR_CLOCK_UPDATE_INTERNAL_NS;\n    conductor->timeout_check_deadline_ns = now_ns;\n    conductor->time_of_last_to_driver_position_change_ns = now_ns;\n    conductor->next_session_id = aeron_randomised_int32();\n    conductor->publication_reserved_session_id_low = context->publication_reserved_session_id_low;\n    conductor->publication_reserved_session_id_high = context->publication_reserved_session_id_high;\n    conductor->last_command_consumer_position = aeron_mpsc_rb_consumer_position(&conductor->to_driver_commands);\n\n    context->conductor_duty_cycle_stall_tracker.max_cycle_time_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_CONDUCTOR_MAX_CYCLE_TIME);\n    context->conductor_duty_cycle_stall_tracker.cycle_time_threshold_exceeded_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED);\n    context->sender_duty_cycle_stall_tracker.max_cycle_time_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_SENDER_MAX_CYCLE_TIME);\n    context->sender_duty_cycle_stall_tracker.cycle_time_threshold_exceeded_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED);\n    context->receiver_duty_cycle_stall_tracker.max_cycle_time_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_RECEIVER_MAX_CYCLE_TIME);\n    context->receiver_duty_cycle_stall_tracker.cycle_time_threshold_exceeded_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED);\n    context->name_resolver_time_stall_tracker.max_cycle_time_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_NAME_RESOLVER_MAX_TIME);\n    context->name_resolver_time_stall_tracker.cycle_time_threshold_exceeded_counter = aeron_counters_manager_addr(\n        &conductor->counters_manager, AERON_SYSTEM_COUNTER_NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED);\n\n    aeron_time_tracking_name_resolver_t *time_tracking_name_resolver = NULL;\n    if (aeron_alloc((void **)&time_tracking_name_resolver, sizeof(aeron_time_tracking_name_resolver_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate aeron_time_tracking_name_resolver_t\");\n        goto error;\n    }\n    time_tracking_name_resolver->context = context;\n\n    if (aeron_name_resolver_init(\n        &time_tracking_name_resolver->delegate_resolver,\n        context->name_resolver_init_args,\n        context) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to init name resolver\");\n        aeron_free(time_tracking_name_resolver);\n        goto error;\n    }\n\n    conductor->name_resolver.name = \"time_tracking_name_resolver\";\n    conductor->name_resolver.resolve_func = aeron_time_tracking_name_resolver_resolve;\n    conductor->name_resolver.lookup_func = aeron_time_tracking_name_resolver_lookup;\n    conductor->name_resolver.do_work_func = aeron_time_tracking_name_resolver_do_work;\n    conductor->name_resolver.close_func = aeron_time_tracking_name_resolver_close;\n    conductor->name_resolver.state = time_tracking_name_resolver;\n\n    char label[AERON_COUNTER_MAX_LABEL_LENGTH];\n    const char *driver_name = NULL == context->resolver_name ? \"\" : context->resolver_name;\n\n    int label_length = snprintf(label, sizeof(label), \": driverName=%s\", driver_name);\n    if (label_length > 0)\n    {\n        aeron_counters_manager_append_to_label(\n            &conductor->counters_manager, AERON_SYSTEM_COUNTER_RESOLUTION_CHANGES, (size_t)label_length, label);\n    }\n\n    const char* threading_mode  = aeron_driver_threading_mode_to_string(context->threading_mode);\n    label_length = snprintf(label, sizeof(label), \": %s\", threading_mode);\n    if (label_length > 0)\n    {\n        aeron_counters_manager_append_to_label(\n            &conductor->counters_manager,\n            AERON_SYSTEM_COUNTER_CONDUCTOR_MAX_CYCLE_TIME,\n            (size_t)label_length,\n            label);\n    }\n\n    label_length = snprintf(label, sizeof(label), \": %s\", threading_mode);\n    if (label_length > 0)\n    {\n        aeron_counters_manager_append_to_label(\n            &conductor->counters_manager,\n            AERON_SYSTEM_COUNTER_SENDER_MAX_CYCLE_TIME,\n            (size_t)label_length,\n            label);\n    }\n\n    label_length = snprintf(label, sizeof(label), \": %s\", threading_mode);\n    if (label_length > 0)\n    {\n        aeron_counters_manager_append_to_label(\n            &conductor->counters_manager,\n            AERON_SYSTEM_COUNTER_RECEIVER_MAX_CYCLE_TIME,\n            (size_t)label_length,\n            label);\n    }\n\n    char threshold[32];\n    int threshold_length = aeron_format_duration_ns(\n        context->conductor_duty_cycle_stall_tracker.cycle_threshold_ns, threshold, sizeof(threshold));\n    if (threshold_length > 0)\n    {\n        label_length = snprintf(label, sizeof(label), \": threshold=%.*s %s\",\n            threshold_length, threshold, threading_mode);\n        if (label_length > 0)\n        {\n            aeron_counters_manager_append_to_label(\n                &conductor->counters_manager,\n                AERON_SYSTEM_COUNTER_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED,\n                (size_t)label_length,\n                label);\n        }\n    }\n\n    threshold_length = aeron_format_duration_ns(\n        context->sender_duty_cycle_stall_tracker.cycle_threshold_ns, threshold, sizeof(threshold));\n    if (threshold_length > 0)\n    {\n        label_length = snprintf(label, sizeof(label), \": threshold=%.*s %s\",\n            threshold_length, threshold, threading_mode);\n        if (label_length > 0)\n        {\n            aeron_counters_manager_append_to_label(\n                &conductor->counters_manager,\n                AERON_SYSTEM_COUNTER_SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED,\n                (size_t)label_length,\n                label);\n        }\n    }\n\n    threshold_length = aeron_format_duration_ns(\n        context->receiver_duty_cycle_stall_tracker.cycle_threshold_ns, threshold, sizeof(threshold));\n    if (threshold_length > 0)\n    {\n        label_length = snprintf(label, sizeof(label), \": threshold=%.*s %s\",\n            threshold_length, threshold, threading_mode);\n        if (label_length > 0)\n        {\n            aeron_counters_manager_append_to_label(\n                &conductor->counters_manager,\n                AERON_SYSTEM_COUNTER_RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED,\n                (size_t)label_length,\n                label);\n        }\n    }\n\n    threshold_length = aeron_format_duration_ns(\n        context->name_resolver_time_stall_tracker.cycle_threshold_ns, threshold, sizeof(threshold));\n    if (threshold_length > 0)\n    {\n        label_length = snprintf(label, sizeof(label), \": threshold=%.*s\", threshold_length, threshold);\n        if (label_length > 0)\n        {\n            aeron_counters_manager_append_to_label(\n                &conductor->counters_manager,\n                AERON_SYSTEM_COUNTER_NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED,\n                (size_t)label_length,\n                label);\n        }\n    }\n\n    context->conductor_duty_cycle_tracker->update(context->conductor_duty_cycle_tracker->state, now_ns);\n\n    conductor->context = context;\n\n    return 0;\n\nerror:\n    aeron_deque_close(&conductor->end_of_life_queue);\n    aeron_executor_close(&conductor->executor);\n    aeron_str_to_ptr_hash_map_delete(&conductor->receive_channel_endpoint_by_channel_map);\n    aeron_str_to_ptr_hash_map_delete(&conductor->send_channel_endpoint_by_channel_map);\n    aeron_distinct_error_log_close(&conductor->error_log);\n    aeron_system_counters_close(&conductor->system_counters);\n    aeron_counters_manager_close(&conductor->counters_manager);\n    return -1;\n}\n\nint aeron_driver_conductor_find_client(aeron_driver_conductor_t *conductor, int64_t client_id)\n{\n    int index = -1;\n\n    for (int i = (int)conductor->clients.length - 1; i >= 0; i--)\n    {\n        if (client_id == conductor->clients.array[i].client_id)\n        {\n            index = i;\n            break;\n        }\n    }\n\n    return index;\n}\n\naeron_client_t *aeron_driver_conductor_get_or_add_client(aeron_driver_conductor_t *conductor, int64_t client_id)\n{\n    aeron_client_t *client = NULL;\n    int index = aeron_driver_conductor_find_client(conductor, client_id);\n\n    if (-1 == index)\n    {\n        int ensure_capacity_result = 0;\n        AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, conductor->clients, aeron_client_t)\n\n        if (ensure_capacity_result >= 0)\n        {\n            aeron_atomic_counter_t client_heartbeat;\n\n            client_heartbeat.counter_id = aeron_counter_client_heartbeat_timestamp_allocate(\n                &conductor->counters_manager, client_id);\n\n            client_heartbeat.value_addr = aeron_counters_manager_addr(\n                &conductor->counters_manager, client_heartbeat.counter_id);\n\n            if (client_heartbeat.counter_id >= 0)\n            {\n                aeron_counters_manager_counter_registration_id(\n                    &conductor->counters_manager, client_heartbeat.counter_id, client_id);\n\n                aeron_counters_manager_counter_owner_id(\n                    &conductor->counters_manager, client_heartbeat.counter_id, client_id);\n\n                index = (int)conductor->clients.length;\n                client = &conductor->clients.array[index];\n\n                client->client_id = client_id;\n                client->reached_end_of_life = false;\n                client->closed_by_command = false;\n\n                client->heartbeat_timestamp.counter_id = client_heartbeat.counter_id;\n                client->heartbeat_timestamp.value_addr = client_heartbeat.value_addr;\n                const int64_t now_ms = aeron_clock_cached_epoch_time(conductor->context->cached_clock);\n                aeron_counter_set_release(client->heartbeat_timestamp.value_addr, now_ms);\n\n                client->client_liveness_timeout_ms = conductor->context->client_liveness_timeout_ns < 1000000 ?\n                    1 : (int64_t)(conductor->context->client_liveness_timeout_ns / 1000000);\n                client->publication_links.array = NULL;\n                client->publication_links.length = 0;\n                client->publication_links.capacity = 0;\n                client->counter_links.array = NULL;\n                client->counter_links.length = 0;\n                client->counter_links.capacity = 0;\n                conductor->clients.length++;\n\n                aeron_driver_conductor_on_counter_ready(conductor, client_id, client_heartbeat.counter_id);\n            }\n        }\n    }\n    else\n    {\n        client = &conductor->clients.array[index];\n    }\n\n    return client;\n}\n\nvoid aeron_client_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_client_t *client, int64_t now_ns, int64_t now_ms)\n{\n    int64_t timestamp_ms = aeron_counter_get_acquire(client->heartbeat_timestamp.value_addr);\n    if (now_ms > (timestamp_ms + client->client_liveness_timeout_ms))\n    {\n        client->reached_end_of_life = true;\n\n        if (!client->closed_by_command)\n        {\n            aeron_counter_increment_release(conductor->client_timeouts_counter);\n            aeron_driver_conductor_on_client_timeout(conductor, client->client_id);\n        }\n\n        aeron_driver_conductor_on_unavailable_counter(\n            conductor, client->client_id, client->heartbeat_timestamp.counter_id);\n    }\n}\n\nbool aeron_client_has_reached_end_of_life(aeron_driver_conductor_t *conductor, aeron_client_t *client)\n{\n    return client->reached_end_of_life;\n}\n\nint32_t aeron_driver_conductor_next_session_id(aeron_driver_conductor_t *conductor)\n{\n    const int32_t low = conductor->publication_reserved_session_id_low;\n    const int32_t high = conductor->publication_reserved_session_id_high;\n\n    return low <= conductor->next_session_id && conductor->next_session_id <= high ?\n        aeron_add_wrap_i32(high, 1) : conductor->next_session_id;\n}\n\nint32_t aeron_driver_conductor_update_next_session_id(aeron_driver_conductor_t *conductor, int32_t session_id)\n{\n    conductor->next_session_id = aeron_add_wrap_i32(session_id, 1);\n    return session_id;\n}\n\nstatic void aeron_driver_conductor_track_session_id_offsets(\n    aeron_driver_conductor_t *conductor, aeron_bit_set_t *session_id_offsets, int32_t publication_session_id)\n{\n    const int32_t next_session_id = aeron_driver_conductor_next_session_id(conductor);\n    const int32_t session_id_offset = aeron_sub_wrap_i32(publication_session_id, next_session_id);\n\n    if (0 <= session_id_offset && (size_t)session_id_offset < session_id_offsets->bit_set_length)\n    {\n        aeron_bit_set_set(session_id_offsets, (size_t)session_id_offset, true);\n    }\n}\n\nstatic int aeron_driver_conductor_speculate_next_session_id(\n    aeron_driver_conductor_t *conductor, aeron_bit_set_t *session_id_offsets, int32_t *session_id)\n{\n    size_t index = 0;\n\n    if (aeron_bit_set_find_first(session_id_offsets, false, &index) < 0)\n    {\n        return -1;\n    }\n\n    *session_id = aeron_add_wrap_i32(aeron_driver_conductor_next_session_id(conductor), (int32_t)index);\n\n    return 0;\n}\n\nint aeron_confirm_publication_match(\n    const aeron_driver_uri_publication_params_t *params,\n    const int32_t existing_session_id,\n    const aeron_logbuffer_metadata_t *logbuffer_metadata,\n    const int32_t existing_initial_term_id,\n    const int32_t existing_term_id,\n    const size_t existing_term_offset)\n{\n    if (params->has_session_id && params->session_id != existing_session_id)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"existing publication has different '%s': existing=%\" PRId32 \" requested=%\" PRId32,\n            AERON_URI_SESSION_ID_KEY, existing_session_id, params->session_id);\n\n        return -1;\n    }\n\n    if (params->has_mtu_length && params->mtu_length != (size_t)logbuffer_metadata->mtu_length)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"existing publication has different '%s': existing=%\" PRId32 \" requested=%\" PRIu64,\n            AERON_URI_MTU_LENGTH_KEY, logbuffer_metadata->mtu_length, (uint64_t)params->mtu_length);\n\n        return -1;\n    }\n\n    if (params->has_term_length && params->term_length != (size_t)logbuffer_metadata->term_length)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"existing publication has different '%s': existing=%\" PRId32 \" requested=%\" PRIu64,\n            AERON_URI_TERM_LENGTH_KEY, logbuffer_metadata->term_length, (uint64_t)params->term_length);\n\n        return -1;\n    }\n\n    if (params->has_position)\n    {\n        if (params->initial_term_id != existing_initial_term_id)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"existing publication has different '%s': existing=%\" PRId32 \" requested=%\" PRId32,\n                AERON_URI_INITIAL_TERM_ID_KEY, existing_initial_term_id, params->initial_term_id);\n\n            return -1;\n        }\n\n        if (params->term_id != existing_term_id)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"existing publication has different '%s': existing=%\" PRId32 \" requested=%\" PRId32,\n                AERON_URI_TERM_ID_KEY, existing_term_id, params->term_id);\n\n            return -1;\n        }\n\n        if (params->term_offset != existing_term_offset)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"existing publication has different '%s': existing=%\" PRId64 \" requested=%\" PRIu64,\n                AERON_URI_TERM_OFFSET_KEY, (uint64_t)existing_term_offset, (uint64_t)params->term_offset);\n\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nvoid aeron_driver_conductor_unlink_from_endpoint(aeron_driver_conductor_t *conductor, aeron_subscription_link_t *link)\n{\n    conductor->context->log.remove_subscription_cleanup(\n        link->registration_id, link->stream_id, link->channel_length, link->channel);\n\n    aeron_receive_channel_endpoint_t *endpoint = link->endpoint;\n\n    link->endpoint = NULL;\n    if (link->has_session_id)\n    {\n        aeron_receive_channel_endpoint_decref_to_stream_and_session(endpoint, link->stream_id, link->session_id);\n    }\n    else if (link->is_response)\n    {\n        aeron_receive_channel_endpoint_decref_to_response_stream(endpoint, link->stream_id);\n    }\n    else\n    {\n        aeron_receive_channel_endpoint_decref_to_stream(endpoint, link->stream_id);\n    }\n\n    aeron_driver_conductor_unlink_all_subscribable(conductor, link);\n}\n\nvoid aeron_driver_conductor_log_explicit_error(\n    aeron_driver_conductor_t *conductor,\n    int error_code,\n    const char *description)\n{\n    aeron_distinct_error_log_record(&conductor->error_log, error_code, description);\n    aeron_counter_increment(conductor->errors_counter);\n    aeron_err_clear();\n}\n\nvoid aeron_driver_conductor_log_error(aeron_driver_conductor_t *conductor)\n{\n    aeron_driver_conductor_log_explicit_error(conductor, aeron_errcode(), aeron_errmsg());\n}\n\nvoid aeron_client_delete(aeron_driver_conductor_t *conductor, aeron_client_t *client)\n{\n    for (size_t i = 0; i < client->publication_links.length; i++)\n    {\n        aeron_driver_managed_resource_t *resource = client->publication_links.array[i].resource;\n        AERON_DRIVER_MANAGED_RESOURCE_DECREF(resource);\n    }\n\n    for (size_t i = 0; i < client->counter_links.length; i++)\n    {\n        aeron_counter_link_t *link = &client->counter_links.array[i];\n        aeron_driver_conductor_on_unavailable_counter(conductor, link->registration_id, link->counter_id);\n        aeron_counters_manager_free(&conductor->counters_manager, link->counter_id);\n    }\n\n    for (int last_index = (int)conductor->ipc_subscriptions.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[i];\n\n        if (client->client_id == link->client_id)\n        {\n            aeron_driver_conductor_unlink_all_subscribable(conductor, link);\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)conductor->ipc_subscriptions.array, sizeof(aeron_subscription_link_t), i, last_index);\n            conductor->ipc_subscriptions.length--;\n            last_index--;\n        }\n    }\n\n    for (int last_index = (int)conductor->network_subscriptions.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n\n        if (client->client_id == link->client_id)\n        {\n            aeron_driver_conductor_unlink_from_endpoint(conductor, link);\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)conductor->network_subscriptions.array, sizeof(aeron_subscription_link_t), i, last_index);\n            conductor->network_subscriptions.length--;\n            last_index--;\n        }\n    }\n\n    for (int last_index = (int)conductor->spy_subscriptions.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_subscription_link_t *link = &conductor->spy_subscriptions.array[i];\n\n        if (client->client_id == link->client_id)\n        {\n            aeron_driver_conductor_unlink_all_subscribable(conductor, link);\n            aeron_udp_channel_delete(link->spy_channel);\n            link->spy_channel = NULL;\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)conductor->spy_subscriptions.array, sizeof(aeron_subscription_link_t), i, last_index);\n            conductor->spy_subscriptions.length--;\n            last_index--;\n        }\n    }\n\n    aeron_counters_manager_free(&conductor->counters_manager, client->heartbeat_timestamp.counter_id);\n\n    aeron_free(client->publication_links.array);\n    client->publication_links.array = NULL;\n    client->publication_links.length = 0;\n    client->publication_links.capacity = 0;\n\n    aeron_free(client->counter_links.array);\n    client->counter_links.array = NULL;\n    client->counter_links.length = 0;\n    client->counter_links.capacity = 0;\n\n    client->client_id = -1;\n    client->heartbeat_timestamp.counter_id = -1;\n    client->heartbeat_timestamp.value_addr = NULL;\n}\n\nbool aeron_client_free(void *resource)\n{\n    return true;\n}\n\nvoid on_available_image(\n    aeron_driver_conductor_t *conductor,\n    const int64_t correlation_id,\n    const int32_t stream_id,\n    const int32_t session_id,\n    const char *log_file_name,\n    const size_t log_file_name_length,\n    const int32_t subscriber_position_id,\n    const int64_t subscriber_registration_id,\n    const char *source_identity,\n    const size_t source_identity_length,\n    const size_t response_length,\n    char *response_buffer)\n{\n    aeron_image_buffers_ready_t *response = (aeron_image_buffers_ready_t *)response_buffer;\n\n    response->correlation_id = correlation_id;\n    response->stream_id = stream_id;\n    response->session_id = session_id;\n    response->subscriber_position_id = subscriber_position_id;\n    response->subscriber_registration_id = subscriber_registration_id;\n    response_buffer += sizeof(aeron_image_buffers_ready_t);\n\n    int32_t length_field;\n\n    length_field = (int32_t)log_file_name_length;\n    memcpy(response_buffer, &length_field, sizeof(length_field));\n    response_buffer += sizeof(int32_t);\n    memcpy(response_buffer, log_file_name, log_file_name_length);\n    response_buffer += AERON_ALIGN(log_file_name_length, sizeof(int32_t));\n\n    length_field = (int32_t)source_identity_length;\n    memcpy(response_buffer, &length_field, sizeof(length_field));\n    response_buffer += sizeof(int32_t);\n    memcpy(response_buffer, source_identity, source_identity_length);\n\n    aeron_driver_conductor_client_transmit(conductor, AERON_RESPONSE_ON_AVAILABLE_IMAGE, response, response_length);\n}\n\nvoid aeron_driver_conductor_on_available_image(\n    aeron_driver_conductor_t *conductor,\n    int64_t correlation_id,\n    int32_t stream_id,\n    int32_t session_id,\n    const char *log_file_name,\n    size_t log_file_name_length,\n    int32_t subscriber_position_id,\n    int64_t subscriber_registration_id,\n    const char *source_identity,\n    size_t source_identity_length)\n{\n    const size_t response_length =\n        sizeof(aeron_image_buffers_ready_t) +\n        AERON_ALIGN(log_file_name_length, sizeof(int32_t)) +\n        source_identity_length +\n        (2 * sizeof(int32_t));\n\n    if (response_length > sizeof(aeron_image_buffers_ready_t) + (2 * AERON_MAX_PATH))\n    {\n        char *buffer = NULL;\n        if (aeron_alloc((void **)&buffer, response_length) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to allocate response buffer\");\n            aeron_driver_conductor_log_error(conductor);\n            return;\n        }\n\n        on_available_image(\n            conductor,\n            correlation_id,\n            stream_id,\n            session_id,\n            log_file_name,\n            log_file_name_length,\n            subscriber_position_id,\n            subscriber_registration_id,\n            source_identity,\n            source_identity_length,\n            response_length,\n            buffer);\n        aeron_free(buffer);\n    }\n    else\n    {\n        char buffer[sizeof(aeron_image_buffers_ready_t) + (2 * AERON_MAX_PATH)];\n        on_available_image(\n            conductor,\n            correlation_id,\n            stream_id,\n            session_id,\n            log_file_name,\n            log_file_name_length,\n            subscriber_position_id,\n            subscriber_registration_id,\n            source_identity,\n            source_identity_length,\n            response_length,\n            buffer);\n    }\n}\n\nvoid aeron_ipc_publication_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_entry_t *entry, int64_t now_ns, int64_t now_ms)\n{\n    aeron_ipc_publication_on_time_event(conductor, entry->publication, now_ns, now_ms);\n}\n\nbool aeron_ipc_publication_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_entry_t *entry)\n{\n    return aeron_ipc_publication_has_reached_end_of_life(entry->publication);\n}\n\nvoid aeron_ipc_publication_entry_delete(aeron_driver_conductor_t *conductor, aeron_ipc_publication_entry_t *entry)\n{\n    aeron_ipc_publication_t *publication = entry->publication;\n    conductor->context->log.remove_publication_cleanup(\n        publication->session_id, publication->stream_id, publication->channel_length, publication->channel);\n\n    for (size_t i = 0, size = conductor->ipc_subscriptions.length; i < size; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[i];\n        aeron_driver_conductor_unlink_subscribable(link, &publication->conductor_fields.subscribable);\n    }\n\n    aeron_ipc_publication_close(&conductor->counters_manager, publication);\n    entry->publication = NULL;\n}\n\nstatic bool aeron_ipc_publication_free_voidp(void *publication)\n{\n    return aeron_ipc_publication_free((aeron_ipc_publication_t *)publication);\n}\n\nvoid aeron_ipc_publication_entry_free(aeron_driver_conductor_t *conductor, aeron_ipc_publication_entry_t *entry)\n{\n    aeron_driver_conductor_add_end_of_life_resource(conductor, entry->publication, aeron_ipc_publication_free_voidp);\n}\n\nvoid aeron_network_publication_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_entry_t *entry, int64_t now_ns, int64_t now_ms)\n{\n    aeron_network_publication_on_time_event(conductor, entry->publication, now_ns, now_ms);\n}\n\nbool aeron_network_publication_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_entry_t *entry)\n{\n    return aeron_network_publication_has_sender_released(entry->publication);\n}\n\nvoid aeron_network_publication_entry_delete(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_entry_t *entry)\n{\n    aeron_send_channel_endpoint_t *endpoint = entry->publication->endpoint;\n\n    for (size_t i = 0, size = conductor->spy_subscriptions.length; i < size; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->spy_subscriptions.array[i];\n        aeron_driver_conductor_unlink_subscribable(link, &entry->publication->conductor_fields.subscribable);\n    }\n\n    aeron_network_publication_close(&conductor->counters_manager, entry->publication);\n    entry->publication = NULL;\n\n    AERON_DRIVER_MANAGED_RESOURCE_DECREF(&(endpoint->conductor_fields.managed_resource));\n}\n\nstatic bool aeron_network_publication_free_voidp(void *publication)\n{\n    return aeron_network_publication_free((aeron_network_publication_t *)publication);\n}\n\nvoid aeron_network_publication_entry_free(aeron_driver_conductor_t *conductor, aeron_network_publication_entry_t *entry)\n{\n    aeron_driver_conductor_add_end_of_life_resource(\n        conductor, entry->publication, aeron_network_publication_free_voidp);\n}\n\nvoid aeron_driver_conductor_cleanup_spies(aeron_driver_conductor_t *conductor, aeron_network_publication_t *publication)\n{\n    for (size_t i = 0, size = conductor->spy_subscriptions.length; i < size; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->spy_subscriptions.array[i];\n\n        if (aeron_driver_conductor_is_subscribable_linked(link, &publication->conductor_fields.subscribable))\n        {\n            aeron_driver_conductor_on_unavailable_image(\n                conductor,\n                publication->conductor_fields.managed_resource.registration_id,\n                link->registration_id,\n                link->stream_id,\n                link->channel,\n                link->channel_length);\n        }\n\n        aeron_driver_conductor_unlink_subscribable(link, &publication->conductor_fields.subscribable);\n    }\n}\n\nvoid aeron_driver_conductor_cleanup_network_publication(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_t *publication)\n{\n    const aeron_udp_channel_t *udp_channel = publication->endpoint->conductor_fields.udp_channel;\n    conductor->context->log.remove_publication_cleanup(\n        publication->session_id, publication->stream_id, udp_channel->uri_length, udp_channel->original_uri);\n\n    aeron_driver_sender_proxy_on_remove_publication(conductor->context->sender_proxy, publication);\n}\n\nvoid aeron_send_channel_endpoint_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_send_channel_endpoint_entry_t *entry, int64_t now_ns, int64_t now_ms)\n{\n    /* Nothing done here. Could linger if needed. */\n}\n\nbool aeron_send_channel_endpoint_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_send_channel_endpoint_entry_t *entry)\n{\n    return aeron_send_channel_endpoint_has_sender_released(entry->endpoint);\n}\n\nvoid aeron_send_channel_endpoint_entry_delete(\n    aeron_driver_conductor_t *conductor, aeron_send_channel_endpoint_entry_t *entry)\n{\n    aeron_str_to_ptr_hash_map_remove(\n        &conductor->send_channel_endpoint_by_channel_map,\n        entry->endpoint->conductor_fields.udp_channel->canonical_form,\n        entry->endpoint->conductor_fields.udp_channel->canonical_length);\n\n    aeron_send_channel_endpoint_delete(&conductor->counters_manager, entry->endpoint);\n}\n\nvoid aeron_receive_channel_endpoint_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_receive_channel_endpoint_entry_t *entry, int64_t now_ns, int64_t now_ms)\n{\n    /* Nothing done here. could linger if needed. */\n}\n\nbool aeron_receive_channel_endpoint_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_receive_channel_endpoint_entry_t *entry)\n{\n    return aeron_receive_channel_endpoint_has_receiver_released(entry->endpoint);\n}\n\n// Called from conductor.\nvoid aeron_receive_channel_endpoint_entry_delete(\n    aeron_driver_conductor_t *conductor, aeron_receive_channel_endpoint_entry_t *entry)\n{\n    for (size_t i = 0, size = conductor->publication_images.length; i < size; i++)\n    {\n        aeron_publication_image_t *image = conductor->publication_images.array[i].image;\n\n        if (entry->endpoint == image->conductor_fields.endpoint)\n        {\n            const aeron_udp_channel_t *udp_channel = entry->endpoint->conductor_fields.udp_channel;\n            conductor->context->log.remove_image_cleanup(\n                image->conductor_fields.managed_resource.registration_id,\n                image->session_id,\n                image->stream_id,\n                udp_channel->uri_length,\n                udp_channel->original_uri);\n            aeron_publication_image_conductor_disconnect_endpoint(image);\n        }\n    }\n\n    aeron_receive_channel_endpoint_delete(&conductor->counters_manager, entry->endpoint);\n}\n\nvoid aeron_publication_image_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_entry_t *entry, int64_t now_ns, int64_t now_ms)\n{\n    aeron_publication_image_on_time_event(conductor, entry->image, now_ns, now_ms);\n}\n\nbool aeron_publication_image_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_entry_t *entry)\n{\n    bool has_receiver_released = false;\n    AERON_GET_ACQUIRE(has_receiver_released, entry->image->has_receiver_released);\n\n    return AERON_PUBLICATION_IMAGE_STATE_DONE == entry->image->conductor_fields.state && has_receiver_released;\n}\n\nvoid aeron_publication_image_entry_delete(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_entry_t *entry)\n{\n    aeron_publication_image_t *image = entry->image;\n\n    for (size_t i = 0, size = conductor->network_subscriptions.length; i < size; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n        aeron_driver_conductor_unlink_subscribable(link, &image->conductor_fields.subscribable);\n    }\n\n    aeron_publication_image_close(&conductor->counters_manager, image);\n    entry->image = NULL;\n}\n\nstatic bool aeron_publication_image_free_voidp(void *image)\n{\n    return aeron_publication_image_free((aeron_publication_image_t *)image);\n}\n\nvoid aeron_publication_image_entry_free(aeron_driver_conductor_t *conductor, aeron_publication_image_entry_t *entry)\n{\n    aeron_driver_conductor_add_end_of_life_resource(conductor, entry->image, aeron_publication_image_free_voidp);\n}\n\nvoid aeron_linger_resource_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_linger_resource_entry_t *entry, int64_t now_ns, int64_t now_ms)\n{\n    if (now_ns > entry->timeout_ns)\n    {\n        entry->has_reached_end_of_life = true;\n    }\n}\n\nbool aeron_linger_resource_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_linger_resource_entry_t *entry)\n{\n    return entry->has_reached_end_of_life;\n}\n\nvoid aeron_linger_resource_entry_delete(aeron_driver_conductor_t *conductor, aeron_linger_resource_entry_t *entry)\n{\n    aeron_free(entry->buffer);\n}\n\n// Called from conductor\nvoid aeron_driver_conductor_image_transition_to_linger(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_t *image)\n{\n    if (NULL != image->conductor_fields.endpoint)\n    {\n        bool rejoin = true;\n\n        for (size_t i = 0, size = conductor->network_subscriptions.length; i < size; i++)\n        {\n            aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n\n            if (aeron_driver_conductor_is_subscribable_linked(link, &image->conductor_fields.subscribable))\n            {\n                rejoin = link->is_rejoin;\n\n                aeron_driver_conductor_on_unavailable_image(\n                    conductor,\n                    image->conductor_fields.managed_resource.registration_id,\n                    link->registration_id,\n                    image->stream_id,\n                    link->channel,\n                    link->channel_length);\n            }\n        }\n\n        if (rejoin)\n        {\n            aeron_driver_receiver_proxy_on_remove_cool_down(\n                conductor->context->receiver_proxy,\n                image->conductor_fields.endpoint,\n                image->session_id,\n                image->stream_id);\n        }\n    }\n}\n\nvoid aeron_driver_conductor_add_end_of_life_resource(\n    aeron_driver_conductor_t *conductor,\n    void *resource,\n    aeron_end_of_life_resource_free_t free_func)\n{\n    aeron_end_of_life_resource_t end_of_life_resource = { 0 };\n    int64_t *counter = aeron_system_counter_addr(\n        &conductor->system_counters, AERON_SYSTEM_COUNTER_FREE_FAILS);\n\n    end_of_life_resource.resource = resource;\n    end_of_life_resource.free_func = free_func;\n    if (aeron_deque_add_last(&conductor->end_of_life_queue, (void *)&end_of_life_resource) < 0)\n    {\n        aeron_counter_increment_release(counter);\n    }\n}\n\nint aeron_driver_conductor_free_end_of_life_resources(aeron_driver_conductor_t *conductor)\n{\n    const int limit = (int)conductor->context->resource_free_limit;\n    aeron_end_of_life_resource_t end_of_life_resource = { 0 };\n    int count = 0;\n\n    for (; count < limit; count++)\n    {\n        if (0 == aeron_deque_remove_first(&conductor->end_of_life_queue, (void *)&end_of_life_resource))\n        {\n            break;\n        }\n\n        if (!end_of_life_resource.free_func(end_of_life_resource.resource))\n        {\n            int64_t *counter = aeron_system_counter_addr(&conductor->system_counters, AERON_SYSTEM_COUNTER_FREE_FAILS);\n            aeron_counter_increment_release(counter);\n            aeron_deque_add_last(&conductor->end_of_life_queue, (void *)&end_of_life_resource);\n        }\n    }\n\n    return count;\n}\n\n#define AERON_DRIVER_CONDUCTOR_CHECK_MANAGED_RESOURCE(c, l, t, now_ns, now_ms)             \\\nfor (int last_index = (int)l.length - 1, i = last_index; i >= 0; i--)                      \\\n{                                                                                          \\\n    t *elem = &l.array[i];                                                                 \\\n    l.on_time_event(c, elem, now_ns, now_ms);                                              \\\n    if (l.has_reached_end_of_life(c, elem))                                                \\\n    {                                                                                      \\\n        if (NULL != l.free_func)                                                           \\\n        {                                                                                  \\\n            l.free_func(c, elem);                                                          \\\n        }                                                                                  \\\n        l.delete_func(c, elem);                                                            \\\n        aeron_array_fast_unordered_remove((uint8_t *)l.array, sizeof(t), i, last_index);   \\\n        last_index--;                                                                      \\\n        l.length--;                                                                        \\\n    }                                                                                      \\\n}\n\nvoid aeron_driver_conductor_on_check_managed_resources(\n    aeron_driver_conductor_t *conductor, int64_t now_ns, int64_t now_ms)\n{\n    AERON_DRIVER_CONDUCTOR_CHECK_MANAGED_RESOURCE(\n        conductor, conductor->clients, aeron_client_t, now_ns, now_ms)\n    AERON_DRIVER_CONDUCTOR_CHECK_MANAGED_RESOURCE(\n        conductor, conductor->ipc_publications, aeron_ipc_publication_entry_t, now_ns, now_ms)\n    AERON_DRIVER_CONDUCTOR_CHECK_MANAGED_RESOURCE(\n        conductor, conductor->network_publications, aeron_network_publication_entry_t, now_ns, now_ms)\n    AERON_DRIVER_CONDUCTOR_CHECK_MANAGED_RESOURCE(\n        conductor, conductor->send_channel_endpoints, aeron_send_channel_endpoint_entry_t, now_ns, now_ms)\n    AERON_DRIVER_CONDUCTOR_CHECK_MANAGED_RESOURCE(\n        conductor, conductor->receive_channel_endpoints, aeron_receive_channel_endpoint_entry_t, now_ns, now_ms)\n    AERON_DRIVER_CONDUCTOR_CHECK_MANAGED_RESOURCE(\n        conductor, conductor->publication_images, aeron_publication_image_entry_t, now_ns, now_ms)\n    AERON_DRIVER_CONDUCTOR_CHECK_MANAGED_RESOURCE(\n        conductor, conductor->lingering_resources, aeron_linger_resource_entry_t, now_ns, now_ms)\n}\n\nstatic void aeron_driver_conductor_find_and_update_ipc_response_subscription(\n    const aeron_driver_conductor_t *conductor,\n    int64_t response_correlation_id,\n    int32_t response_pub_session_id)\n{\n    if (AERON_NULL_VALUE != response_correlation_id)\n    {\n        for (size_t i = 0, pub_n = conductor->ipc_publications.length; i < pub_n; i++)\n        {\n            aeron_ipc_publication_t *potential_request_pub = conductor->ipc_publications.array[i].publication;\n            int64_t ipc_registration_id = potential_request_pub->conductor_fields.managed_resource.registration_id;\n\n            if (ipc_registration_id == response_correlation_id)\n            {\n                int64_t request_pub_response_correlation_id = potential_request_pub->conductor_fields.response_correlation_id;\n\n                for (size_t j = 0, sub_n = conductor->ipc_subscriptions.length; j < sub_n; j++)\n                {\n                    aeron_subscription_link_t *potential_response_sub = &conductor->ipc_subscriptions.array[j];\n                    if (request_pub_response_correlation_id == potential_response_sub->registration_id)\n                    {\n                        potential_response_sub->has_session_id = true;\n                        potential_response_sub->session_id = response_pub_session_id;\n\n                        break;\n                    }\n                }\n\n                break;\n            }\n        }\n    }\n}\n\naeron_ipc_publication_t *aeron_driver_conductor_get_or_add_ipc_publication(\n    aeron_driver_conductor_t *conductor,\n    aeron_client_t *client,\n    aeron_driver_uri_publication_params_t *params,\n    int64_t registration_id,\n    int32_t stream_id,\n    size_t uri_length,\n    const char *uri,\n    bool is_exclusive)\n{\n    aeron_ipc_publication_t *publication = NULL;\n\n    uint64_t bits[STATIC_BIT_SET_U64_LEN];\n    aeron_bit_set_t session_id_offsets;\n    aeron_bit_set_stack_init(\n        conductor->ipc_publications.length + 1, bits, STATIC_BIT_SET_U64_LEN, false, &session_id_offsets);\n    assert(conductor->ipc_publications.length < session_id_offsets.bit_set_length);\n\n    bool is_session_id_in_use = false;\n\n    for (size_t i = 0; i < conductor->ipc_publications.length; i++)\n    {\n        aeron_ipc_publication_t *pub_entry = conductor->ipc_publications.array[i].publication;\n\n        if (stream_id == pub_entry->stream_id)\n        {\n            if (AERON_IPC_PUBLICATION_STATE_ACTIVE == pub_entry->conductor_fields.state &&\n                NULL == publication && !is_exclusive && !pub_entry->is_exclusive &&\n                pub_entry->conductor_fields.response_correlation_id == params->response_correlation_id)\n            {\n                publication = pub_entry;\n            }\n\n            if (AERON_IPC_PUBLICATION_STATE_ACTIVE == pub_entry->conductor_fields.state ||\n                AERON_IPC_PUBLICATION_STATE_DRAINING == pub_entry->conductor_fields.state)\n            {\n                if (params->has_session_id && pub_entry->session_id == params->session_id)\n                {\n                    is_session_id_in_use = true;\n                }\n\n                aeron_driver_conductor_track_session_id_offsets(conductor, &session_id_offsets, pub_entry->session_id);\n            }\n        }\n    }\n\n    int32_t speculated_session_id = 0;\n    int session_id_found = aeron_driver_conductor_speculate_next_session_id(\n        conductor, &session_id_offsets, &speculated_session_id);\n    aeron_bit_set_stack_free(&session_id_offsets);\n\n    if (session_id_found < 0)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"(BUG) Unable to allocate session-id\");\n        return NULL;\n    }\n\n    if (is_session_id_in_use && (is_exclusive || NULL == publication))\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Specified session-id is already in exclusive use for channel=%.*s stream-id=%\" PRId32,\n            (int)uri_length, uri, stream_id);\n\n        return NULL;\n    }\n\n    if (!is_exclusive && NULL != publication)\n    {\n        if (aeron_confirm_publication_match(\n            params,\n            publication->session_id,\n            publication->log_meta_data,\n            publication->initial_term_id,\n            publication->starting_term_id,\n            publication->starting_term_offset) < 0)\n        {\n            return NULL;\n        }\n    }\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, client->publication_links, aeron_publication_link_t)\n\n    if (ensure_capacity_result >= 0)\n    {\n        if (NULL == publication)\n        {\n            AERON_ARRAY_ENSURE_CAPACITY(\n                ensure_capacity_result, conductor->ipc_publications, aeron_ipc_publication_entry_t)\n\n            if (ensure_capacity_result >= 0)\n            {\n                if (!params->has_session_id)\n                {\n                    aeron_driver_conductor_update_next_session_id(conductor, speculated_session_id);\n                }\n\n                int32_t session_id = params->has_session_id ? params->session_id : speculated_session_id;\n                int32_t initial_term_id = params->has_position ? params->initial_term_id : aeron_randomised_int32();\n\n                aeron_position_t pub_pos_position;\n                aeron_position_t pub_lmt_position;\n\n                pub_pos_position.counter_id = aeron_counter_publisher_position_allocate(\n                    &conductor->counters_manager,\n                    client->client_id,\n                    registration_id,\n                    session_id,\n                    stream_id,\n                    uri_length,\n                    uri,\n                    is_exclusive);\n                pub_pos_position.value_addr = aeron_counters_manager_addr(\n                    &conductor->counters_manager, pub_pos_position.counter_id);\n                pub_lmt_position.counter_id = aeron_counter_publisher_limit_allocate(\n                    &conductor->counters_manager, client->client_id, registration_id, session_id, stream_id, uri_length, uri);\n                pub_lmt_position.value_addr = aeron_counters_manager_addr(\n                    &conductor->counters_manager, pub_lmt_position.counter_id);\n\n                if (pub_pos_position.counter_id < 0 || pub_lmt_position.counter_id < 0)\n                {\n                    return NULL;\n                }\n\n                if (params->has_position)\n                {\n                    int64_t position = aeron_logbuffer_compute_position(\n                        params->term_id,\n                        (int32_t)params->term_offset,\n                        (size_t)aeron_number_of_trailing_zeroes((int32_t)params->term_length),\n                        initial_term_id);\n\n                    aeron_counter_set_release(pub_pos_position.value_addr, position);\n                    aeron_counter_set_release(pub_lmt_position.value_addr, position);\n                }\n\n                if (aeron_ipc_publication_create(\n                    &publication,\n                    conductor->context,\n                    session_id,\n                    stream_id,\n                    registration_id,\n                    &pub_pos_position,\n                    &pub_lmt_position,\n                    initial_term_id,\n                    params,\n                    is_exclusive,\n                    &conductor->system_counters,\n                    uri_length,\n                    uri) >= 0)\n                {\n                    aeron_publication_link_t *link = &client->publication_links.array[client->publication_links.length];\n\n                    AERON_PUBLICATION_LINK_INIT(link, &publication->conductor_fields.managed_resource, registration_id);\n                    client->publication_links.length++;\n\n                    conductor->ipc_publications.array[conductor->ipc_publications.length++].publication = publication;\n                    publication->conductor_fields.managed_resource.time_of_last_state_change_ns =\n                        aeron_clock_cached_nano_time(conductor->context->cached_clock);\n\n                    aeron_driver_conductor_find_and_update_ipc_response_subscription(\n                        conductor, publication->conductor_fields.response_correlation_id, publication->session_id);\n                }\n            }\n        }\n        else\n        {\n            aeron_publication_link_t *link = &client->publication_links.array[client->publication_links.length];\n\n            AERON_PUBLICATION_LINK_INIT(link, &publication->conductor_fields.managed_resource, registration_id);\n            client->publication_links.length++;\n\n            AERON_DRIVER_MANAGED_RESOURCE_INCREF(&(publication->conductor_fields.managed_resource));\n        }\n    }\n\n    return ensure_capacity_result >= 0 ? publication : NULL;\n}\n\nstatic int aeron_driver_conductor_find_response_publication_image(\n    aeron_driver_conductor_t *conductor,\n    const aeron_udp_channel_t *udp_channel,\n    aeron_driver_uri_publication_params_t *params,\n    aeron_publication_image_t **image)\n{\n    *image = NULL;\n    if (AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE != udp_channel->control_mode)\n    {\n        return 0;\n    }\n\n    if (AERON_NULL_VALUE == params->response_correlation_id)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"control-mode=response was specified, but no response-correlation-id set\");\n        return -1;\n    }\n\n    if (AERON_URI_PROTOTYPE_VALUE_CORRELATION_ID == params->response_correlation_id)\n    {\n        return 0;\n    }\n\n    for (size_t i = 0; i < conductor->publication_images.length; i++)\n    {\n        aeron_publication_image_t *image_entry = conductor->publication_images.array[i].image;\n        if (aeron_publication_image_registration_id(image_entry) == params->response_correlation_id)\n        {\n            if (aeron_publication_image_has_send_response_setup(image_entry))\n            {\n                *image = image_entry;\n                return 0;\n            }\n            else\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"image.correlationId=%\" PRId64 \" did not request a response channel\",\n                    params->response_correlation_id);\n                return -1;\n            }\n        }\n    }\n\n    AERON_SET_ERR(EINVAL, \"image.correlationId=%\" PRId64 \" not found\", params->response_correlation_id);\n    return -1;\n}\n\nstatic aeron_publication_image_t *aeron_driver_conductor_find_publication_image(\n    aeron_driver_conductor_t *conductor,\n    int64_t correlation_id)\n{\n    for (size_t i = 0; i < conductor->publication_images.length; i++)\n    {\n        aeron_publication_image_t *image_entry = conductor->publication_images.array[i].image;\n        if (aeron_publication_image_registration_id(image_entry) == correlation_id)\n        {\n            return image_entry;\n        }\n    }\n\n    return NULL;\n}\n\naeron_network_publication_t *aeron_driver_conductor_get_or_add_network_publication(\n    aeron_driver_conductor_t *conductor,\n    aeron_client_t *client,\n    aeron_send_channel_endpoint_t *endpoint,\n    size_t uri_length,\n    const char *uri,\n    aeron_driver_uri_publication_params_t *params,\n    aeron_publication_image_t *response_publication_image,\n    int64_t registration_id,\n    int32_t stream_id,\n    bool is_exclusive)\n{\n    aeron_network_publication_t *publication = NULL;\n    const aeron_udp_channel_t *udp_channel = endpoint->conductor_fields.udp_channel;\n\n    uint64_t bits[STATIC_BIT_SET_U64_LEN];\n    aeron_bit_set_t session_id_offsets;\n    aeron_bit_set_stack_init(\n        conductor->network_publications.length + 1, bits, STATIC_BIT_SET_U64_LEN, false, &session_id_offsets);\n\n    bool is_session_id_in_use = false;\n\n    // TODO: Extract\n    for (size_t i = 0; i < conductor->network_publications.length; i++)\n    {\n        aeron_network_publication_t *pub_entry = conductor->network_publications.array[i].publication;\n\n        if (endpoint == pub_entry->endpoint && stream_id == pub_entry->stream_id)\n        {\n            if (AERON_NETWORK_PUBLICATION_STATE_ACTIVE == pub_entry->conductor_fields.state &&\n                !is_exclusive &&\n                !pub_entry->is_exclusive &&\n                pub_entry->response_correlation_id == params->response_correlation_id)\n            {\n                publication = pub_entry;\n            }\n\n            if (params->has_session_id && pub_entry->session_id == params->session_id)\n            {\n                is_session_id_in_use = true;\n            }\n\n            aeron_driver_conductor_track_session_id_offsets(conductor, &session_id_offsets, pub_entry->session_id);\n        }\n    }\n\n    int32_t speculated_session_id = 0;\n    int session_id_found = aeron_driver_conductor_speculate_next_session_id(\n        conductor, &session_id_offsets, &speculated_session_id);\n    aeron_bit_set_stack_free(&session_id_offsets);\n\n    if (session_id_found < 0)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"(BUG) Unable to allocate session-id\");\n        return NULL;\n    }\n\n    if (is_session_id_in_use && (is_exclusive || NULL == publication))\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Specified session-id is already in exclusive use for channel=%.*s stream-id=%\" PRId32,\n            (int)uri_length, uri, stream_id);\n\n        return NULL;\n    }\n\n    if (!is_exclusive && NULL != publication)\n    {\n        if (publication->spies_simulate_connection != params->spies_simulate_connection)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"existing publication has different spies simulate connection: requested=%s\",\n                params->spies_simulate_connection ? \"true\" : \"false\");\n\n            return NULL;\n        }\n\n        if (0 != aeron_confirm_publication_match(\n            params,\n            publication->session_id,\n            publication->log_meta_data,\n            publication->initial_term_id,\n            publication->starting_term_id,\n            publication->starting_term_offset))\n        {\n            return NULL;\n        }\n    }\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, client->publication_links, aeron_publication_link_t)\n\n    if (ensure_capacity_result >= 0)\n    {\n        if (NULL == publication)\n        {\n            AERON_ARRAY_ENSURE_CAPACITY(\n                ensure_capacity_result, conductor->network_publications, aeron_network_publication_entry_t)\n\n            if (ensure_capacity_result >= 0)\n            {\n                if (params->is_response &&\n                    AERON_URI_PROTOTYPE_VALUE_CORRELATION_ID == params->response_correlation_id)\n                {\n                    params->term_length = AERON_LOGBUFFER_TERM_MIN_LENGTH;\n                }\n\n                if (!params->has_session_id)\n                {\n                    aeron_driver_conductor_update_next_session_id(conductor, speculated_session_id);\n                }\n\n                int32_t session_id = params->has_session_id ? params->session_id : speculated_session_id;\n                int32_t initial_term_id = params->has_position ? params->initial_term_id : aeron_randomised_int32();\n\n                aeron_flow_control_strategy_t *flow_control_strategy;\n                if (aeron_default_multicast_flow_control_strategy_supplier(\n                    &flow_control_strategy,\n                    conductor->context,\n                    &conductor->counters_manager,\n                    udp_channel,\n                    stream_id,\n                    session_id,\n                    registration_id,\n                    initial_term_id,\n                    params->term_length) < 0)\n                {\n                    return NULL;\n                }\n\n                aeron_position_t pub_pos_position;\n                aeron_position_t pub_lmt_position;\n                aeron_position_t snd_pos_position;\n                aeron_position_t snd_lmt_position;\n                aeron_atomic_counter_t snd_bpe_counter;\n                aeron_atomic_counter_t snd_naks_received_counter;\n\n                pub_pos_position.counter_id = aeron_counter_publisher_position_allocate(\n                    &conductor->counters_manager,\n                    client->client_id,\n                    registration_id,\n                    session_id,\n                    stream_id,\n                    uri_length,\n                    uri,\n                    is_exclusive);\n                pub_lmt_position.counter_id = aeron_counter_publisher_limit_allocate(\n                    &conductor->counters_manager, client->client_id, registration_id, session_id, stream_id, uri_length, uri);\n                snd_pos_position.counter_id = aeron_counter_sender_position_allocate(\n                    &conductor->counters_manager, client->client_id, registration_id, session_id, stream_id, uri_length, uri);\n                snd_lmt_position.counter_id = aeron_counter_sender_limit_allocate(\n                    &conductor->counters_manager, client->client_id, registration_id, session_id, stream_id, uri_length, uri);\n                snd_bpe_counter.counter_id = aeron_counter_sender_bpe_allocate(\n                    &conductor->counters_manager, client->client_id, registration_id, session_id, stream_id, uri_length, uri);\n                snd_naks_received_counter.counter_id = aeron_counter_sender_naks_received_allocate(\n                    &conductor->counters_manager, client->client_id, registration_id, session_id, stream_id, uri_length, uri);\n\n                if (pub_pos_position.counter_id < 0 || pub_lmt_position.counter_id < 0 ||\n                    snd_pos_position.counter_id < 0 || snd_lmt_position.counter_id < 0 ||\n                    snd_bpe_counter.counter_id < 0 || snd_naks_received_counter.counter_id < 0)\n                {\n                    return NULL;\n                }\n\n                pub_pos_position.value_addr = aeron_counters_manager_addr(\n                    &conductor->counters_manager, pub_pos_position.counter_id);\n                pub_lmt_position.value_addr = aeron_counters_manager_addr(\n                    &conductor->counters_manager, pub_lmt_position.counter_id);\n                snd_pos_position.value_addr = aeron_counters_manager_addr(\n                    &conductor->counters_manager, snd_pos_position.counter_id);\n                snd_lmt_position.value_addr = aeron_counters_manager_addr(\n                    &conductor->counters_manager, snd_lmt_position.counter_id);\n                snd_bpe_counter.value_addr = aeron_counters_manager_addr(\n                    &conductor->counters_manager, snd_bpe_counter.counter_id);\n                snd_naks_received_counter.value_addr = aeron_counters_manager_addr(\n                    &conductor->counters_manager, snd_naks_received_counter.counter_id);\n\n                if (params->has_position)\n                {\n                    int64_t position = aeron_logbuffer_compute_position(\n                        params->term_id,\n                        (int32_t)params->term_offset,\n                        (size_t)aeron_number_of_trailing_zeroes((int32_t)params->term_length),\n                        initial_term_id);\n\n                    aeron_counter_set_release(pub_pos_position.value_addr, position);\n                    aeron_counter_set_release(pub_lmt_position.value_addr, position);\n                    aeron_counter_set_release(snd_pos_position.value_addr, position);\n                    aeron_counter_set_release(snd_lmt_position.value_addr, position);\n                }\n\n                if (pub_lmt_position.counter_id >= 0 &&\n                    aeron_network_publication_create(\n                        &publication,\n                        endpoint,\n                        conductor->context,\n                        registration_id,\n                        session_id,\n                        stream_id,\n                        initial_term_id,\n                        &pub_pos_position,\n                        &pub_lmt_position,\n                        &snd_pos_position,\n                        &snd_lmt_position,\n                        &snd_bpe_counter,\n                        &snd_naks_received_counter,\n                        flow_control_strategy,\n                        params,\n                        is_exclusive,\n                        &conductor->system_counters) >= 0)\n                {\n                    AERON_DRIVER_MANAGED_RESOURCE_INCREF(&(endpoint->conductor_fields.managed_resource));\n                    aeron_driver_sender_proxy_on_add_publication(conductor->context->sender_proxy, publication);\n\n                    aeron_publication_link_t *link = &client->publication_links.array[client->publication_links.length];\n\n                    AERON_PUBLICATION_LINK_INIT(link, &publication->conductor_fields.managed_resource, registration_id);\n                    client->publication_links.length++;\n\n                    conductor->network_publications.array[conductor->network_publications.length++].publication =\n                        publication;\n                    publication->conductor_fields.managed_resource.time_of_last_state_change_ns =\n                        aeron_clock_cached_nano_time(conductor->context->cached_clock);\n                }\n            }\n        }\n        else\n        {\n            aeron_publication_link_t *link = &client->publication_links.array[client->publication_links.length];\n\n            AERON_PUBLICATION_LINK_INIT(link, &publication->conductor_fields.managed_resource, registration_id);\n            client->publication_links.length++;\n\n            AERON_DRIVER_MANAGED_RESOURCE_INCREF(&(publication->conductor_fields.managed_resource));\n        }\n\n        if (NULL != response_publication_image)\n        {\n            aeron_publication_image_set_response_session_id(\n                response_publication_image, (int64_t)publication->session_id);\n        }\n    }\n\n    return ensure_capacity_result >= 0 ? publication : NULL;\n}\n\n/* This should be re-usable if/when we decide to reuse transports with MDS */\nint aeron_driver_conductor_update_and_check_ats_status(\n    aeron_driver_context_t *context, aeron_udp_channel_t *channel, const aeron_udp_channel_t *existing_channel)\n{\n    if (!context->ats_enabled && AERON_URI_ATS_STATUS_ENABLED == channel->ats_status)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"ATS is not enabled and thus ats=true not allowed: channel=%.*s\",\n            (int)channel->uri_length,\n            channel->original_uri);\n        return -1;\n    }\n\n    aeron_uri_ats_status_t context_status = context->ats_enabled ?\n        AERON_URI_ATS_STATUS_ENABLED : AERON_URI_ATS_STATUS_DISABLED;\n    channel->ats_status = AERON_URI_ATS_STATUS_DEFAULT == channel->ats_status ? context_status : channel->ats_status;\n\n    if (NULL != existing_channel)\n    {\n        if (existing_channel->ats_status != channel->ats_status)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"ATS mismatch: existingChannel=%.*s channel=%.*s\",\n                (int)existing_channel->uri_length,\n                existing_channel->original_uri,\n                (int)channel->uri_length,\n                channel->original_uri);\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\naeron_send_channel_endpoint_t *aeron_driver_conductor_get_or_add_send_channel_endpoint(\n    aeron_driver_conductor_t *conductor,\n    aeron_udp_channel_t *channel,\n    aeron_driver_uri_publication_params_t *params,\n    int64_t registration_id)\n{\n    aeron_send_channel_endpoint_t *endpoint;\n    if (aeron_driver_conductor_find_existing_send_channel_endpoint(conductor, channel, &endpoint) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    if (aeron_driver_conductor_update_and_check_ats_status(\n        conductor->context, channel, NULL == endpoint ? NULL : endpoint->conductor_fields.udp_channel) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    if (NULL == endpoint)\n    {\n        if (aeron_publication_params_validate_mtu_for_sndbuf(\n            params,\n            0,\n            channel->socket_sndbuf_length,\n            conductor->context->socket_sndbuf,\n            conductor->context->os_buffer_lengths.default_so_sndbuf) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n\n        int ensure_capacity_result = 0;\n\n        AERON_ARRAY_ENSURE_CAPACITY(\n            ensure_capacity_result, conductor->send_channel_endpoints, aeron_send_channel_endpoint_entry_t)\n\n        if (ensure_capacity_result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n\n        if (aeron_send_channel_endpoint_create(\n            &endpoint, channel, params, conductor->context, &conductor->counters_manager, registration_id) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            // the `channel` is now owned by the endpoint\n            return NULL;\n        }\n\n        aeron_counter_set_release(endpoint->channel_status.value_addr, AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_ACTIVE);\n\n        aeron_driver_sender_proxy_on_add_endpoint(conductor->context->sender_proxy, endpoint);\n\n        if (aeron_str_to_ptr_hash_map_put(\n            &conductor->send_channel_endpoint_by_channel_map,\n            channel->canonical_form,\n            channel->canonical_length,\n            endpoint) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            aeron_send_channel_endpoint_delete(&conductor->counters_manager, endpoint);\n            return NULL;\n        }\n\n        conductor->send_channel_endpoints.array[conductor->send_channel_endpoints.length++].endpoint = endpoint;\n    }\n    else\n    {\n        if (aeron_driver_conductor_send_endpoint_has_clashing_timestamp_offsets(conductor, endpoint, channel))\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n\n        if (aeron_publication_params_validate_mtu_for_sndbuf(\n            params,\n            endpoint->conductor_fields.socket_sndbuf,\n            channel->socket_sndbuf_length,\n            conductor->context->socket_sndbuf,\n            conductor->context->os_buffer_lengths.default_so_sndbuf) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n\n        if (aeron_driver_conductor_validate_channel_buffer_length(\n            AERON_URI_SOCKET_RCVBUF_KEY,\n            channel->socket_rcvbuf_length,\n            endpoint->conductor_fields.socket_rcvbuf,\n            channel,\n            endpoint->conductor_fields.udp_channel) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n\n        if (aeron_driver_conductor_validate_channel_buffer_length(\n            AERON_URI_SOCKET_SNDBUF_KEY,\n            channel->socket_sndbuf_length,\n            endpoint->conductor_fields.socket_sndbuf,\n            channel,\n            endpoint->conductor_fields.udp_channel) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n    }\n\n    return endpoint;\n\nerror_cleanup:\n    aeron_udp_channel_delete(channel);\n    return NULL;\n}\n\naeron_receive_channel_endpoint_t *aeron_driver_conductor_get_or_add_receive_channel_endpoint(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_uri_subscription_params_t *params,\n    aeron_udp_channel_t *channel,\n    int64_t correlation_id)\n{\n    aeron_receive_channel_endpoint_t *endpoint;\n    if (aeron_driver_conductor_find_existing_receive_channel_endpoint(conductor, channel, &endpoint) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    if (aeron_driver_conductor_update_and_check_ats_status(\n        conductor->context, channel, NULL == endpoint ? NULL : endpoint->conductor_fields.udp_channel) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    const size_t socket_rcvbuf = aeron_udp_channel_socket_so_rcvbuf(channel, conductor->context->socket_rcvbuf);\n    const size_t socket_sndbuf = aeron_udp_channel_socket_so_sndbuf(channel, conductor->context->socket_sndbuf);\n\n    if (NULL == endpoint)\n    {\n        aeron_atomic_counter_t status_indicator;\n        int ensure_capacity_result = 0;\n        char bind_addr_and_port[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n        int bind_addr_and_port_length;\n\n        AERON_ARRAY_ENSURE_CAPACITY(\n            ensure_capacity_result, conductor->receive_channel_endpoints, aeron_receive_channel_endpoint_entry_t)\n\n        if (ensure_capacity_result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n\n        status_indicator.counter_id = aeron_counter_receive_channel_status_allocate(\n            &conductor->counters_manager, correlation_id, channel->uri_length, channel->original_uri);\n\n        if (status_indicator.counter_id < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n\n        status_indicator.value_addr = aeron_counters_manager_addr(\n            &conductor->counters_manager, status_indicator.counter_id);\n\n        aeron_receive_destination_t *destination = NULL;\n\n        if (AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL != channel->control_mode)\n        {\n            if (aeron_receive_destination_create(\n                &destination,\n                channel,\n                channel,\n                conductor->context,\n                &conductor->counters_manager,\n                correlation_id,\n                status_indicator.counter_id) < 0)\n            {\n                AERON_APPEND_ERR(\"correlation_id=%\" PRId64, correlation_id);\n                aeron_counters_manager_free(&conductor->counters_manager, status_indicator.counter_id);\n                goto error_cleanup;\n            }\n        }\n\n        if (aeron_driver_conductor_validate_initial_window_for_rcvbuf(\n            params,\n            socket_rcvbuf,\n            conductor->context->os_buffer_lengths.default_so_rcvbuf,\n            channel,\n            NULL) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            if (NULL != destination)\n            {\n                aeron_receive_destination_delete(destination, &conductor->counters_manager);\n            }\n            aeron_counters_manager_free(&conductor->counters_manager, status_indicator.counter_id);\n            return NULL;\n        }\n\n        if (aeron_receive_channel_endpoint_create(\n            &endpoint,\n            channel,\n            destination,\n            &status_indicator,\n            &conductor->system_counters,\n            conductor->context) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            if (NULL != destination)\n            {\n                aeron_receive_destination_delete(destination, &conductor->counters_manager);\n            }\n            aeron_counters_manager_free(&conductor->counters_manager, status_indicator.counter_id);\n            return NULL;\n        }\n\n        if ((bind_addr_and_port_length = aeron_receive_channel_endpoint_bind_addr_and_port(\n            endpoint, bind_addr_and_port, sizeof(bind_addr_and_port))) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            aeron_receive_channel_endpoint_delete(&conductor->counters_manager, endpoint);\n            return NULL;\n        }\n\n        aeron_channel_endpoint_status_update_label(\n            &conductor->counters_manager,\n            status_indicator.counter_id,\n            AERON_COUNTER_RECEIVE_CHANNEL_STATUS_NAME,\n            channel->uri_length,\n            channel->original_uri,\n            bind_addr_and_port_length,\n            bind_addr_and_port);\n        *status_indicator.value_addr = AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_ACTIVE;\n\n        aeron_driver_receiver_proxy_on_add_endpoint(endpoint->receiver_proxy, endpoint);\n\n        if (aeron_str_to_ptr_hash_map_put(\n            &conductor->receive_channel_endpoint_by_channel_map,\n            channel->canonical_form,\n            channel->canonical_length,\n            endpoint) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            aeron_receive_channel_endpoint_delete(&conductor->counters_manager, endpoint);\n            return NULL;\n        }\n\n        conductor->receive_channel_endpoints.array[conductor->receive_channel_endpoints.length++].endpoint = endpoint;\n    }\n    else\n    {\n        if (AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL != channel->control_mode && 1 == endpoint->destinations.length)\n        {\n            const size_t socket_sndbuf_existing = aeron_udp_channel_socket_so_sndbuf(\n                endpoint->conductor_fields.udp_channel, conductor->context->socket_sndbuf);\n            const size_t socket_rcvbuf_existing = aeron_udp_channel_socket_so_rcvbuf(\n                endpoint->conductor_fields.udp_channel, conductor->context->socket_rcvbuf);\n\n            if (aeron_driver_conductor_validate_initial_window_for_rcvbuf(\n                params,\n                socket_rcvbuf,\n                conductor->context->os_buffer_lengths.default_so_rcvbuf,\n                channel,\n                endpoint->conductor_fields.udp_channel) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                goto error_cleanup;\n            }\n\n            if (aeron_driver_conductor_validate_channel_buffer_length(\n                AERON_URI_SOCKET_SNDBUF_KEY,\n                socket_sndbuf,\n                socket_sndbuf_existing,\n                channel,\n                endpoint->conductor_fields.udp_channel) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                goto error_cleanup;\n            }\n\n            if (aeron_driver_conductor_validate_channel_buffer_length(\n                AERON_URI_SOCKET_RCVBUF_KEY,\n                socket_rcvbuf,\n                socket_rcvbuf_existing,\n                channel,\n                endpoint->conductor_fields.udp_channel) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                goto error_cleanup;\n            }\n        }\n    }\n\n    return endpoint;\n\nerror_cleanup:\n    aeron_udp_channel_delete(channel);\n    return NULL;\n}\n\nvoid aeron_driver_conductor_client_transmit(\n    aeron_driver_conductor_t *conductor, int32_t msg_type_id, const void *msg, size_t length)\n{\n    conductor->context->log.to_client_interceptor(conductor, msg_type_id, msg, length);\n    if (aeron_broadcast_transmitter_transmit(&conductor->to_clients, msg_type_id, msg, length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to transmit message\");\n        aeron_driver_conductor_log_error(conductor);\n    }\n}\n\nvoid on_error(\n    aeron_driver_conductor_t *conductor,\n    const int32_t error_code,\n    const char *message,\n    const size_t length,\n    const int64_t correlation_id,\n    const size_t response_length,\n    char *response_buffer)\n{\n    aeron_error_response_t *response = (aeron_error_response_t *)response_buffer;\n\n    response->offending_command_correlation_id = correlation_id;\n    response->error_code = error_code;\n    response->error_message_length = (int32_t)length;\n    memcpy(response_buffer + sizeof(aeron_error_response_t), message, length);\n\n    aeron_driver_conductor_client_transmit(conductor, AERON_RESPONSE_ON_ERROR, response, response_length);\n}\n\nvoid aeron_driver_conductor_on_publication_error(void *clientd, void *item)\n{\n    uint8_t buffer[sizeof(aeron_publication_error_t) + AERON_ERROR_MAX_TEXT_LENGTH];\n    aeron_driver_conductor_t *conductor = clientd;\n    aeron_command_publication_error_t *error = item;\n\n    char log_str[AERON_ERROR_MAX_TEXT_LENGTH * 2];\n    snprintf(log_str, sizeof(log_str), \"onPublicationError: registrationId=%\" PRIi64\n        \", destinationRegistrationId=%\" PRIi64\n        \", sessionId=%\" PRIi32\n        \", streamId=%\" PRIi32\n        \", receiverId=%\" PRIi64\n        \", groupId=%\" PRIi64\n        \", errorCode=%\" PRIi32\n        \", errorMessage=%.*s\",\n        error->registration_id,\n        error->destination_registration_id,\n        error->session_id,\n        error->stream_id,\n        error->receiver_id,\n        error->group_tag,\n        error->error_code,\n        error->error_length,\n        error->error_text);\n    aeron_driver_conductor_log_explicit_error(conductor, error->error_code, (const char *)log_str);\n\n    aeron_publication_error_t *response = (aeron_publication_error_t *)buffer;\n    response->error_code = error->error_code;\n    response->registration_id = error->registration_id;\n    response->destination_registration_id = error->destination_registration_id;\n    response->session_id = error->session_id;\n    response->stream_id = error->stream_id;\n    response->receiver_id = error->receiver_id;\n    response->group_tag = error->group_tag;\n\n    memset(&response->source_address[0], 0, sizeof(response->source_address));\n    if (AF_INET == error->src_address.ss_family)\n    {\n        struct sockaddr_in *src_addr_in = (struct sockaddr_in *)&error->src_address;\n        response->address_type = AERON_RESPONSE_ADDRESS_TYPE_IPV4;\n        response->source_port = ntohs(src_addr_in->sin_port);\n        memcpy(&response->source_address[0], &src_addr_in->sin_addr, sizeof(src_addr_in->sin_addr));\n    }\n    else if (AF_INET6 == error->src_address.ss_family)\n    {\n        struct sockaddr_in6 *src_addr_in6 = (struct sockaddr_in6 *)&error->src_address;\n        response->address_type = AERON_RESPONSE_ADDRESS_TYPE_IPV6;\n        response->source_port = ntohs(src_addr_in6->sin6_port);\n        memcpy(&response->source_address[0], &src_addr_in6->sin6_addr, sizeof(src_addr_in6->sin6_addr));\n    }\n    else\n    {\n        response->address_type = 0;\n        response->source_port = 0;\n    }\n\n    response->error_message_length = error->error_length;\n    memcpy(response->error_message, error->error_text, error->error_length);\n    size_t response_length = offsetof(aeron_publication_error_values_t, error_message) + response->error_message_length;\n\n    aeron_driver_conductor_client_transmit(conductor, AERON_RESPONSE_ON_PUBLICATION_ERROR, response, response_length);\n}\n\nvoid aeron_driver_conductor_on_error(\n    aeron_driver_conductor_t *conductor,\n    int32_t errcode,\n    const char *errmsg,\n    int64_t correlation_id\n)\n{\n    int os_errno = errcode;\n    int code = AERON_ERROR_CODE_GENERIC_ERROR;\n    if (os_errno < 0)\n    {\n        code = -os_errno;\n    }\n    else if (AERON_FILEUTIL_ERROR_ENOSPC == os_errno)\n    {\n        code = AERON_ERROR_CODE_STORAGE_SPACE;\n    }\n\n    const size_t length = strlen(errmsg);\n    const size_t response_length = sizeof(aeron_error_response_t) + length;\n\n    if (response_length > sizeof(aeron_error_response_t) + AERON_ERROR_MAX_TOTAL_LENGTH)\n    {\n        char *buffer = NULL;\n        if (aeron_alloc((void **)&buffer, response_length) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to allocate response buffer\");\n            aeron_driver_conductor_log_error(conductor);\n\n            goto log_error;\n        }\n        on_error(conductor, code, errmsg, length, correlation_id, response_length, buffer);\n        aeron_free(buffer);\n    }\n    else\n    {\n        char buffer[sizeof(aeron_error_response_t) + AERON_ERROR_MAX_TOTAL_LENGTH];\n        on_error(conductor, code, errmsg, length, correlation_id, response_length, buffer);\n    }\n\nlog_error:\n    aeron_driver_conductor_log_explicit_error(conductor, code, errmsg);\n}\n\nvoid on_publication_ready(\n    aeron_driver_conductor_t *conductor,\n    const int64_t registration_id,\n    const int64_t original_registration_id,\n    const int32_t stream_id,\n    const int32_t session_id,\n    const int32_t position_limit_counter_id,\n    const int32_t channel_status_indicator_id,\n    const bool is_exclusive,\n    const char *log_file_name,\n    const size_t log_file_name_length,\n    const size_t response_length,\n    char *response_buffer)\n{\n    aeron_publication_buffers_ready_t *response = (aeron_publication_buffers_ready_t *)response_buffer;\n\n    response->correlation_id = registration_id;\n    response->registration_id = original_registration_id;\n    response->stream_id = stream_id;\n    response->session_id = session_id;\n    response->position_limit_counter_id = position_limit_counter_id;\n    response->channel_status_indicator_id = channel_status_indicator_id;\n    response->log_file_length = (int32_t)log_file_name_length;\n    memcpy(response_buffer + sizeof(aeron_publication_buffers_ready_t), log_file_name, log_file_name_length);\n\n    aeron_driver_conductor_client_transmit(\n        conductor,\n        is_exclusive ? AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY : AERON_RESPONSE_ON_PUBLICATION_READY,\n        response,\n        response_length);\n}\n\nvoid aeron_driver_conductor_on_publication_ready(\n    aeron_driver_conductor_t *conductor,\n    int64_t registration_id,\n    int64_t original_registration_id,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t position_limit_counter_id,\n    int32_t channel_status_indicator_id,\n    bool is_exclusive,\n    const char *log_file_name,\n    size_t log_file_name_length)\n{\n    const size_t response_length = sizeof(aeron_publication_buffers_ready_t) + log_file_name_length;\n\n    if (response_length > sizeof(aeron_publication_buffers_ready_t) + AERON_MAX_PATH)\n    {\n        char *buffer = NULL;\n        if (aeron_alloc((void **)&buffer, response_length) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to allocate response buffer\");\n            aeron_driver_conductor_log_error(conductor);\n            return;\n        }\n\n        on_publication_ready(\n            conductor,\n            registration_id,\n            original_registration_id,\n            stream_id,\n            session_id,\n            position_limit_counter_id,\n            channel_status_indicator_id,\n            is_exclusive,\n            log_file_name,\n            log_file_name_length,\n            response_length,\n            buffer);\n        aeron_free(buffer);\n    }\n    else\n    {\n        char buffer[sizeof(aeron_publication_buffers_ready_t) + AERON_MAX_PATH];\n        on_publication_ready(\n            conductor,\n            registration_id,\n            original_registration_id,\n            stream_id,\n            session_id,\n            position_limit_counter_id,\n            channel_status_indicator_id,\n            is_exclusive,\n            log_file_name,\n            log_file_name_length,\n            response_length,\n            buffer);\n    }\n}\n\nvoid aeron_driver_conductor_on_subscription_ready(\n    aeron_driver_conductor_t *conductor, int64_t registration_id, int32_t channel_status_indicator_id)\n{\n    char response_buffer[sizeof(aeron_correlated_command_t)];\n    aeron_subscription_ready_t *response = (aeron_subscription_ready_t *)response_buffer;\n\n    response->correlation_id = registration_id;\n    response->channel_status_indicator_id = channel_status_indicator_id;\n\n    aeron_driver_conductor_client_transmit(\n        conductor, AERON_RESPONSE_ON_SUBSCRIPTION_READY, response, sizeof(aeron_subscription_ready_t));\n}\n\nvoid aeron_driver_conductor_on_counter_ready(\n    aeron_driver_conductor_t *conductor, int64_t registration_id, int32_t counter_id)\n{\n    char response_buffer[sizeof(aeron_counter_update_t)];\n    aeron_counter_update_t *response = (aeron_counter_update_t *)response_buffer;\n\n    response->correlation_id = registration_id;\n    response->counter_id = counter_id;\n\n    aeron_driver_conductor_client_transmit(\n        conductor, AERON_RESPONSE_ON_COUNTER_READY, response, sizeof(aeron_counter_update_t));\n}\n\nvoid aeron_driver_conductor_on_unavailable_counter(\n    aeron_driver_conductor_t *conductor, int64_t registration_id, int32_t counter_id)\n{\n    char response_buffer[sizeof(aeron_counter_update_t)];\n    aeron_counter_update_t *response = (aeron_counter_update_t *)response_buffer;\n\n    response->correlation_id = registration_id;\n    response->counter_id = counter_id;\n\n    aeron_driver_conductor_client_transmit(\n        conductor, AERON_RESPONSE_ON_UNAVAILABLE_COUNTER, response, sizeof(aeron_counter_update_t));\n}\n\nvoid aeron_driver_conductor_on_static_counter(\n    aeron_driver_conductor_t *conductor, int64_t correlation_id, int32_t counter_id)\n{\n    char response_buffer[sizeof(aeron_static_counter_response_t)];\n    aeron_static_counter_response_t *response = (aeron_static_counter_response_t *)response_buffer;\n\n    response->correlation_id = correlation_id;\n    response->counter_id = counter_id;\n\n    aeron_driver_conductor_client_transmit(\n        conductor, AERON_RESPONSE_ON_STATIC_COUNTER, response, sizeof(aeron_static_counter_response_t));\n}\n\nvoid aeron_driver_conductor_on_operation_succeeded(aeron_driver_conductor_t *conductor, int64_t correlation_id)\n{\n    char response_buffer[sizeof(aeron_correlated_command_t)];\n    aeron_operation_succeeded_t *response = (aeron_operation_succeeded_t *)response_buffer;\n\n    response->correlation_id = correlation_id;\n\n    aeron_driver_conductor_client_transmit(\n        conductor, AERON_RESPONSE_ON_OPERATION_SUCCESS, response, sizeof(aeron_operation_succeeded_t));\n}\n\nvoid aeron_driver_conductor_response_next_available_session_id(\n    aeron_driver_conductor_t *conductor, int64_t correlation_id, int32_t next_session_id)\n{\n    char response_buffer[sizeof(aeron_next_available_session_id_response_t)];\n    aeron_next_available_session_id_response_t *response = (aeron_next_available_session_id_response_t *)response_buffer;\n\n    response->correlation_id = correlation_id;\n    response->next_session_id = next_session_id;\n\n    aeron_driver_conductor_client_transmit(\n        conductor, AERON_RESPONSE_ON_NEXT_AVAILABLE_SESSION_ID, response, sizeof(aeron_next_available_session_id_response_t));\n}\n\nvoid aeron_driver_conductor_on_client_timeout(aeron_driver_conductor_t *conductor, int64_t client_id)\n{\n    char response_buffer[sizeof(aeron_client_timeout_t)];\n    aeron_client_timeout_t *response = (aeron_client_timeout_t *)response_buffer;\n\n    response->client_id = client_id;\n\n    aeron_driver_conductor_client_transmit(\n        conductor, AERON_RESPONSE_ON_CLIENT_TIMEOUT, response, sizeof(aeron_client_timeout_t));\n}\n\nvoid on_unavailable_image(\n    aeron_driver_conductor_t *conductor,\n    const int64_t correlation_id,\n    const int64_t subscription_registration_id,\n    const int32_t stream_id,\n    const char *channel,\n    const size_t channel_length,\n    const size_t response_length,\n    char *response_buffer)\n{\n    aeron_image_message_t *response = (aeron_image_message_t *)response_buffer;\n\n    response->correlation_id = correlation_id;\n    response->subscription_registration_id = subscription_registration_id;\n    response->stream_id = stream_id;\n    response->channel_length = (int32_t)channel_length;\n    memcpy(response_buffer + sizeof(aeron_image_message_t), channel, channel_length);\n\n    aeron_driver_conductor_client_transmit(conductor, AERON_RESPONSE_ON_UNAVAILABLE_IMAGE, response, response_length);\n}\n\nvoid aeron_driver_conductor_on_unavailable_image(\n    aeron_driver_conductor_t *conductor,\n    int64_t correlation_id,\n    int64_t subscription_registration_id,\n    int32_t stream_id,\n    const char *channel,\n    size_t channel_length)\n{\n    const size_t response_length = sizeof(aeron_image_message_t) + channel_length;\n\n    if (response_length > sizeof(aeron_image_message_t) + AERON_MAX_PATH)\n    {\n        char *buffer = NULL;\n        if (aeron_alloc((void **)&buffer, response_length) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to allocate response buffer\");\n            aeron_driver_conductor_log_error(conductor);\n            return;\n        }\n\n        on_unavailable_image(\n            conductor,\n            correlation_id,\n            subscription_registration_id,\n            stream_id,\n            channel,\n            channel_length,\n            response_length,\n            buffer);\n        aeron_free(buffer);\n    }\n    else\n    {\n        char buffer[sizeof(aeron_image_message_t) + AERON_MAX_PATH];\n        on_unavailable_image(\n            conductor,\n            correlation_id,\n            subscription_registration_id,\n            stream_id,\n            channel,\n            channel_length,\n            response_length,\n            buffer);\n    }\n}\n\nstatic bool aeron_driver_conductor_not_accepting_client_commands(aeron_driver_conductor_t *conductor)\n{\n    if (conductor->async_client_command_in_flight)\n    {\n        return true;\n    }\n\n    aeron_mpsc_rb_t *sender_rb = conductor->context->sender_proxy->command_queue;\n    aeron_mpsc_rb_t *receiver_rb = conductor->context->receiver_proxy->command_queue;\n    return\n        ((sender_rb->capacity - aeron_mpsc_rb_size(sender_rb)) <= AERON_COMMAND_RB_RESERVE) ||\n        ((receiver_rb->capacity - aeron_mpsc_rb_size(receiver_rb)) <= AERON_COMMAND_RB_RESERVE);\n}\n\ntypedef struct aeron_driver_async_command_stct\n{\n    aeron_udp_channel_async_parse_t async_parse;\n    aeron_name_resolver_async_resolve_t async_resolve;\n    aeron_send_channel_endpoint_t *endpoint;\n    aeron_uri_t *uri;\n    void *original_command;\n    bool is_exclusive;\n}\naeron_driver_async_command_t;\n\ntypedef int (*aeron_driver_async_client_command_on_execute_func_t)(aeron_driver_conductor_t *conductor, void *clientd);\n\ntypedef int (*aeron_driver_async_client_command_on_complete_func_t)(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_command_t *async_command,\n    void *clientd);\n\ntypedef int (*aeron_driver_async_client_command_on_error_func_t)(\n    aeron_driver_conductor_t *conductor,\n    int result,\n    aeron_driver_async_command_t *async_command,\n    void *clientd);\n\ntypedef struct aeron_driver_async_client_command_stct\n{\n    aeron_correlated_command_t *correlated;\n    aeron_driver_async_client_command_on_execute_func_t on_execute;\n    void *on_execute_clientd; // this is passed to the on_execute callback\n    aeron_driver_async_client_command_on_complete_func_t on_complete;\n    aeron_driver_async_client_command_on_error_func_t on_error;\n    aeron_driver_async_command_t async_command;\n}\naeron_driver_async_client_command_t;\n\n/* This is an aeron_executor 'execute' callback - it's called from an executor thread */\nint aeron_driver_async_client_command_execute(void *task_clientd, void *executor_clientd)\n{\n    aeron_driver_async_client_command_t *async_client_command = task_clientd;\n    aeron_driver_conductor_t *conductor = executor_clientd;\n\n    if (async_client_command->on_execute(conductor, async_client_command->on_execute_clientd) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\n/* This is an aeron_executor 'complete' callback - it's called when 'aeron_executor_process_completions' is called */\nvoid aeron_driver_async_client_command_complete(\n    int result,\n    int errcode,\n    const char *errmsg,\n    void *task_clientd,\n    void *executor_clientd)\n{\n    aeron_driver_async_client_command_t *async_client_command = task_clientd;\n    aeron_driver_conductor_t *conductor = executor_clientd;\n    int64_t correlation_id = async_client_command->correlated->correlation_id;\n\n    conductor->async_client_command_in_flight = false;\n\n    if (result < 0)\n    {\n        if (NULL == async_client_command->on_error)\n        {\n            aeron_driver_conductor_on_error(conductor, errcode, errmsg, correlation_id);\n        }\n        else\n        {\n            // TODO set current errcode/errmsg to what was handed to this function???\n            if (async_client_command->on_error(\n                conductor,\n                result,\n                &async_client_command->async_command,\n                async_client_command->on_execute_clientd) < 0)\n            {\n                aeron_driver_conductor_on_error(conductor, aeron_errcode(), aeron_errmsg(), correlation_id);\n            }\n        }\n    }\n    else if (async_client_command->on_complete(\n        conductor, &async_client_command->async_command, async_client_command->on_execute_clientd) < 0)\n    {\n        aeron_driver_conductor_on_error(conductor, aeron_errcode(), aeron_errmsg(), correlation_id);\n    }\n\n    aeron_free(async_client_command);\n}\n\nint aeron_driver_async_client_command_allocate(\n    aeron_driver_async_client_command_t **async_client_commandp,\n    void *original_command,\n    size_t original_command_length)\n{\n    aeron_driver_async_client_command_t *async_client_command;\n\n    if (aeron_alloc(\n        (void **)&async_client_command,\n        AERON_PADDED_SIZEOF(aeron_driver_async_client_command_t) + original_command_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    async_client_command->on_error = NULL;\n    async_client_command->async_command.original_command =\n        (void *)((const char *)async_client_command + AERON_PADDED_SIZEOF(aeron_driver_async_client_command_t));\n\n    memcpy(async_client_command->async_command.original_command, original_command, original_command_length);\n\n    *async_client_commandp = async_client_command;\n\n    return 0;\n}\n\nint aeron_driver_async_client_command_free(\n    aeron_driver_async_client_command_t *async_client_command)\n{\n    aeron_udp_channel_delete(async_client_command->async_command.async_parse.channel);\n    aeron_uri_close(async_client_command->async_command.uri);\n    aeron_free(async_client_command->async_command.uri);\n    aeron_free(async_client_command);\n    return 0;\n}\n\nvoid aeron_driver_async_client_command_cancel(void *task_clientd, void *executor_clientd)\n{\n    aeron_driver_async_client_command_t *async_client_command = task_clientd;\n    aeron_driver_async_client_command_free(async_client_command);\n}\n\nint aeron_driver_async_client_command_submit(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_client_command_t *async_client_command)\n{\n    conductor->async_client_command_in_flight = true;\n\n    if (aeron_executor_submit(\n        &conductor->executor,\n        aeron_driver_async_client_command_execute,\n        aeron_driver_async_client_command_complete,\n        aeron_driver_async_client_command_cancel,\n        async_client_command) < 0)\n    {\n        conductor->async_client_command_in_flight = false;\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_driver_async_parse_udp_channel_execute(aeron_driver_conductor_t *conductor, void *clientd)\n{\n    aeron_udp_channel_async_parse_t *async_parse = clientd;\n\n    if (aeron_udp_channel_finish_parse(\n        &conductor->name_resolver,\n        async_parse) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_driver_async_resolve_execute(aeron_driver_conductor_t *conductor, void *clientd)\n{\n    aeron_name_resolver_async_resolve_t *async_resolve = clientd;\n\n    if (aeron_name_resolver_resolve_host_and_port(\n        &conductor->name_resolver,\n        async_resolve->endpoint_name,\n        async_resolve->uri_param_name,\n        async_resolve->is_re_resolution,\n        &async_resolve->sockaddr) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\ntypedef struct aeron_async_re_resolve_stct\n{\n    aeron_name_resolver_async_resolve_t async_resolve;\n    struct sockaddr_storage existing_addr;\n    void *endpoint;\n    void *destination;\n}\naeron_async_re_resolve_t;\n\nint aeron_driver_conductor_async_resolve_host_and_port_execute(void *task_clientd, void *executor_clientd)\n{\n    aeron_async_re_resolve_t *async_cmd = task_clientd;\n    aeron_driver_conductor_t *conductor = executor_clientd;\n\n    if (aeron_driver_async_resolve_execute(conductor, &async_cmd->async_resolve) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\naeron_rb_read_action_t aeron_driver_conductor_on_command(\n    int32_t msg_type_id, const void *message, size_t length, void *clientd)\n{\n    aeron_driver_conductor_t *conductor = (aeron_driver_conductor_t *)clientd;\n    int64_t correlation_id = 0;\n    int result = 0;\n\n    if (aeron_driver_conductor_not_accepting_client_commands(conductor))\n    {\n        return AERON_RB_ABORT;\n    }\n\n    conductor->context->log.to_driver_interceptor(msg_type_id, message, length, clientd);\n\n    switch (msg_type_id)\n    {\n        case AERON_COMMAND_ADD_PUBLICATION:\n        {\n            aeron_publication_command_t *command = (aeron_publication_command_t *)message;\n\n            if (length < sizeof(aeron_publication_command_t) ||\n                length < (sizeof(aeron_publication_command_t) + command->channel_length))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n            const char *channel = (const char *)message + sizeof(aeron_publication_command_t);\n\n            if (strncmp(channel, AERON_IPC_CHANNEL, AERON_IPC_CHANNEL_LEN) == 0)\n            {\n                result = aeron_driver_conductor_on_add_ipc_publication(conductor, command, false);\n            }\n            else\n            {\n                result = aeron_driver_conductor_on_add_network_publication(conductor, command, false);\n            }\n            break;\n        }\n\n        case AERON_COMMAND_ADD_EXCLUSIVE_PUBLICATION:\n        {\n            aeron_publication_command_t *command = (aeron_publication_command_t *)message;\n\n            if (length < sizeof(aeron_publication_command_t) ||\n                length < (sizeof(aeron_publication_command_t) + command->channel_length))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n            const char *channel = (const char *)message + sizeof(aeron_publication_command_t);\n\n            if (strncmp(channel, AERON_IPC_CHANNEL, AERON_IPC_CHANNEL_LEN) == 0)\n            {\n                result = aeron_driver_conductor_on_add_ipc_publication(conductor, command, true);\n            }\n            else\n            {\n                result = aeron_driver_conductor_on_add_network_publication(conductor, command, true);\n            }\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_PUBLICATION:\n        {\n            aeron_remove_publication_command_t *command = (aeron_remove_publication_command_t *)message;\n\n            aeron_remove_publication_command_t complete_command;\n            aeron_remove_publication_command_t *complete_command_p;\n\n            if (length < sizeof(aeron_remove_publication_command_t))\n            {\n                if (length == offsetof(aeron_remove_publication_command_t, flags)) // old aeron_remove_command_t has no flags field\n                {\n                    complete_command.correlated.client_id = command->correlated.client_id;\n                    complete_command.correlated.correlation_id = command->correlated.correlation_id;\n                    complete_command.registration_id = command->registration_id;\n                    complete_command.flags = 0;\n\n                    complete_command_p = &complete_command;\n                }\n                else\n                {\n                    goto malformed_command;\n                }\n            }\n            else\n            {\n                complete_command_p = command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n\n            result = aeron_driver_conductor_on_remove_publication(conductor, complete_command_p);\n            break;\n        }\n\n        case AERON_COMMAND_ADD_SUBSCRIPTION:\n        {\n            aeron_subscription_command_t *command = (aeron_subscription_command_t *)message;\n\n            if (length < sizeof(aeron_subscription_command_t) ||\n                length < (sizeof(aeron_subscription_command_t) + command->channel_length))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n            const char *channel = (const char *)message + sizeof(aeron_subscription_command_t);\n\n            if (strncmp(channel, AERON_IPC_CHANNEL, AERON_IPC_CHANNEL_LEN) == 0)\n            {\n                result = aeron_driver_conductor_on_add_ipc_subscription(conductor, command);\n            }\n            else if (strncmp(channel, AERON_SPY_PREFIX, AERON_SPY_PREFIX_LEN) == 0)\n            {\n                result = aeron_driver_conductor_on_add_spy_subscription(conductor, command);\n            }\n            else\n            {\n                result = aeron_driver_conductor_on_add_network_subscription(conductor, command);\n            }\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_SUBSCRIPTION:\n        {\n            aeron_remove_subscription_command_t *command = (aeron_remove_subscription_command_t *)message;\n\n            if (length < sizeof(aeron_remove_subscription_command_t))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n\n            result = aeron_driver_conductor_on_remove_subscription(conductor, command);\n            break;\n        }\n\n        case AERON_COMMAND_CLIENT_KEEPALIVE:\n        {\n            aeron_correlated_command_t *command = (aeron_correlated_command_t *)message;\n\n            if (length < sizeof(aeron_correlated_command_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_driver_conductor_on_client_keepalive(conductor, command->client_id);\n            break;\n        }\n\n        case AERON_COMMAND_ADD_DESTINATION:\n        {\n            aeron_destination_command_t *command = (aeron_destination_command_t *)message;\n\n            if (length < sizeof(aeron_destination_command_t) ||\n                length < (sizeof(aeron_destination_command_t) + command->channel_length))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n\n            result = aeron_driver_conductor_on_add_send_destination(conductor, command);\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_DESTINATION:\n        {\n            aeron_destination_command_t *command = (aeron_destination_command_t *)message;\n\n            if (length < sizeof(aeron_destination_command_t) ||\n                length < (sizeof(aeron_destination_command_t) + command->channel_length))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n\n            result = aeron_driver_conductor_on_remove_send_destination(conductor, command);\n            break;\n        }\n\n        case AERON_COMMAND_ADD_RCV_DESTINATION:\n        {\n            aeron_destination_command_t *command = (aeron_destination_command_t *)message;\n\n            if (length < sizeof(aeron_destination_command_t) ||\n                length < (sizeof(aeron_destination_command_t) + command->channel_length))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n            const char *channel = (const char *)message + sizeof(aeron_destination_command_t);\n\n            if (strncmp(channel, AERON_IPC_CHANNEL, AERON_IPC_CHANNEL_LEN) == 0)\n            {\n                result = aeron_driver_conductor_on_add_receive_ipc_destination(conductor, command);\n            }\n            else if (strncmp(channel, AERON_SPY_PREFIX, AERON_SPY_PREFIX_LEN) == 0)\n            {\n                result = aeron_driver_conductor_on_add_receive_spy_destination(conductor, command);\n            }\n            else\n            {\n                result = aeron_driver_conductor_on_add_receive_network_destination(conductor, command);\n            }\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_RCV_DESTINATION:\n        {\n            aeron_destination_command_t *command = (aeron_destination_command_t *)message;\n\n            if (length < sizeof(aeron_destination_command_t) ||\n                length < (sizeof(aeron_destination_command_t) + command->channel_length))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n            const char *channel = (const char *)message + sizeof(aeron_destination_command_t);\n\n            if (strncmp(channel, AERON_IPC_CHANNEL, AERON_IPC_CHANNEL_LEN) == 0)\n            {\n                result = aeron_driver_conductor_on_remove_receive_ipc_destination(conductor, command);\n            }\n            else if (strncmp(channel, AERON_SPY_PREFIX, AERON_SPY_PREFIX_LEN) == 0)\n            {\n                result = aeron_driver_conductor_on_remove_receive_spy_destination(conductor, command);\n            }\n            else\n            {\n                result = aeron_driver_conductor_on_remove_receive_network_destination(conductor, command);\n            }\n            break;\n        }\n\n        case AERON_COMMAND_ADD_COUNTER:\n        {\n            aeron_counter_command_t *command = (aeron_counter_command_t *)message;\n\n            if (length < sizeof(aeron_counter_command_t))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n\n            result = aeron_driver_conductor_on_add_counter(conductor, command);\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_COUNTER:\n        {\n            aeron_remove_counter_command_t *command = (aeron_remove_counter_command_t *)message;\n\n            if (length < sizeof(aeron_remove_counter_command_t))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n\n            result = aeron_driver_conductor_on_remove_counter(conductor, command);\n            break;\n        }\n\n        case AERON_COMMAND_CLIENT_CLOSE:\n        {\n            aeron_correlated_command_t *command = (aeron_correlated_command_t *)message;\n\n            if (length < sizeof(aeron_correlated_command_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_driver_conductor_on_client_close(conductor, command);\n            break;\n        }\n\n        case AERON_COMMAND_TERMINATE_DRIVER:\n        {\n            aeron_terminate_driver_command_t *command = (aeron_terminate_driver_command_t *)message;\n\n            if (length < sizeof(aeron_terminate_driver_command_t))\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_driver_conductor_on_terminate_driver(conductor, command);\n\n            break;\n        }\n\n        case AERON_COMMAND_ADD_STATIC_COUNTER:\n        {\n            aeron_static_counter_command_t *command = (aeron_static_counter_command_t *)message;\n\n            if (length < sizeof(aeron_static_counter_command_t))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n\n            result = aeron_driver_conductor_on_add_static_counter(conductor, command);\n            break;\n        }\n\n        case AERON_COMMAND_REJECT_IMAGE:\n        {\n            aeron_reject_image_command_t *command = (aeron_reject_image_command_t *)message;\n            correlation_id = command->correlated.correlation_id;\n\n            if (length < sizeof(aeron_reject_image_command_t))\n            {\n                goto malformed_command;\n            }\n\n            if (length < offsetof(aeron_reject_image_command_t, reason_text) + command->reason_length)\n            {\n                goto malformed_command;\n            }\n\n            result = aeron_driver_conductor_on_invalidate_image(conductor, command);\n\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_DESTINATION_BY_ID:\n        {\n            aeron_destination_by_id_command_t *command = (aeron_destination_by_id_command_t *)message;\n\n            if (length < sizeof(aeron_destination_by_id_command_t))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n\n            aeron_driver_conductor_on_remove_receive_send_destination_by_id(conductor, command);\n            break;\n        }\n\n        case AERON_COMMAND_GET_NEXT_AVAILABLE_SESSION_ID:\n        {\n            aeron_get_next_available_session_id_command_t *command = (aeron_get_next_available_session_id_command_t *)message;\n\n            if (length < sizeof(aeron_get_next_available_session_id_command_t))\n            {\n                goto malformed_command;\n            }\n\n            correlation_id = command->correlated.correlation_id;\n\n            aeron_driver_conductor_on_get_next_available_session_id(conductor, command);\n            break;\n        }\n\n        default:\n            AERON_SET_ERR(-AERON_ERROR_CODE_UNKNOWN_COMMAND_TYPE_ID, \"command=%d unknown\", msg_type_id);\n            aeron_driver_conductor_log_error(conductor);\n            break;\n    }\n\n    if (result < 0)\n    {\n        aeron_driver_conductor_on_error(conductor, aeron_errcode(), aeron_errmsg(), correlation_id);\n    }\n\n    if (conductor->async_client_command_in_flight)\n    {\n        return AERON_RB_BREAK;\n    }\n\n    return AERON_RB_CONTINUE;\n\nmalformed_command:\n    AERON_SET_ERR(\n        -AERON_ERROR_CODE_MALFORMED_COMMAND, \"command=%d too short: length=%\" PRIu64, msg_type_id, (uint64_t)length);\n    aeron_driver_conductor_log_error(conductor);\n\n    return AERON_RB_CONTINUE;\n}\n\nvoid aeron_driver_conductor_on_command_queue(void *clientd, void *item)\n{\n    aeron_command_base_t *cmd = (aeron_command_base_t *)item;\n    cmd->func(clientd, cmd);\n}\n\nvoid aeron_driver_conductor_on_check_for_blocked_driver_commands(aeron_driver_conductor_t *conductor, int64_t now_ns)\n{\n    int64_t consumer_position = aeron_mpsc_rb_consumer_position(&conductor->to_driver_commands);\n\n    if (consumer_position == conductor->last_command_consumer_position &&\n        aeron_mpsc_rb_producer_position(&conductor->to_driver_commands) > consumer_position)\n    {\n        int64_t position_change_deadline_ns = conductor->time_of_last_to_driver_position_change_ns +\n            (int64_t)conductor->context->client_liveness_timeout_ns;\n        if (now_ns > position_change_deadline_ns)\n        {\n            if (aeron_mpsc_rb_unblock(&conductor->to_driver_commands))\n            {\n                aeron_counter_increment_release(conductor->unblocked_commands_counter);\n            }\n        }\n    }\n    else\n    {\n        conductor->time_of_last_to_driver_position_change_ns = now_ns;\n        conductor->last_command_consumer_position = consumer_position;\n    }\n}\n\nvoid aeron_driver_conductor_track_time(aeron_driver_conductor_t *conductor, int64_t now_ns)\n{\n    aeron_clock_update_cached_nano_time(conductor->context->cached_clock, now_ns);\n    aeron_duty_cycle_tracker_t *tracker = conductor->context->conductor_duty_cycle_tracker;\n\n    tracker->measure_and_update(tracker->state, now_ns);\n\n    if (now_ns >= conductor->clock_update_deadline_ns)\n    {\n        conductor->clock_update_deadline_ns = now_ns + AERON_DRIVER_CONDUCTOR_CLOCK_UPDATE_INTERNAL_NS;\n        aeron_clock_update_cached_epoch_time(conductor->context->cached_clock, conductor->context->epoch_clock());\n    }\n}\n\nstatic void aeron_driver_conductor_on_rb_command_queue(\n    int32_t msg_type_id,\n    const void *message,\n    size_t size,\n    void *clientd)\n{\n    aeron_command_base_t *cmd = (aeron_command_base_t *)message;\n    cmd->func(clientd, cmd);\n}\n\nint aeron_driver_conductor_do_work(void *clientd)\n{\n    aeron_driver_conductor_t *conductor = (aeron_driver_conductor_t *)clientd;\n    const int64_t now_ns = conductor->context->nano_clock();\n    aeron_driver_conductor_track_time(conductor, now_ns);\n    const int64_t now_ms = aeron_clock_cached_epoch_time(conductor->context->cached_clock);\n    int work_count = 0;\n\n    if (now_ns > conductor->timeout_check_deadline_ns)\n    {\n        aeron_mpsc_rb_consumer_heartbeat_time(&conductor->to_driver_commands, now_ms);\n        aeron_driver_conductor_on_check_managed_resources(conductor, now_ns, now_ms);\n        aeron_driver_conductor_on_check_for_blocked_driver_commands(conductor, now_ns);\n        conductor->timeout_check_deadline_ns = now_ns + (int64_t)conductor->context->timer_interval_ns;\n        work_count++;\n    }\n    if (!conductor->async_client_command_in_flight)\n    {\n        work_count += (int)aeron_mpsc_rb_controlled_read(\n            &conductor->to_driver_commands, aeron_driver_conductor_on_command, conductor, AERON_COMMAND_DRAIN_LIMIT);\n    }\n    work_count += (int)aeron_mpsc_rb_read(\n        conductor->conductor_proxy.command_queue,\n        aeron_driver_conductor_on_rb_command_queue,\n        conductor,\n        AERON_COMMAND_DRAIN_LIMIT);\n\n    for (size_t i = 0, length = conductor->publication_images.length; i < length; i++)\n    {\n        aeron_publication_image_track_rebuild(conductor->publication_images.array[i].image, now_ns);\n    }\n\n    for (size_t i = 0, length = conductor->network_publications.length; i < length; i++)\n    {\n        work_count +=\n            aeron_network_publication_update_pub_pos_and_lmt(conductor->network_publications.array[i].publication);\n    }\n\n    for (size_t i = 0, length = conductor->ipc_publications.length; i < length; i++)\n    {\n        work_count += aeron_ipc_publication_update_pub_pos_and_lmt(conductor->ipc_publications.array[i].publication);\n    }\n\n    work_count += conductor->name_resolver.do_work_func(&conductor->name_resolver, now_ms);\n    work_count += aeron_driver_conductor_free_end_of_life_resources(conductor);\n    work_count += aeron_executor_process_completions(&conductor->executor, 1);\n\n    return work_count;\n}\n\nvoid aeron_driver_conductor_on_close(void *clientd)\n{\n    aeron_driver_conductor_t *conductor = (aeron_driver_conductor_t *)clientd;\n\n    aeron_executor_close(&conductor->executor);\n\n    conductor->name_resolver.close_func(&conductor->name_resolver);\n\n    for (size_t i = 0, length = conductor->clients.length; i < length; i++)\n    {\n        aeron_free(conductor->clients.array[i].publication_links.array);\n        aeron_free(conductor->clients.array[i].counter_links.array);\n    }\n    aeron_free(conductor->clients.array);\n\n    for (size_t i = 0, length = conductor->ipc_publications.length; i < length; i++)\n    {\n        aeron_ipc_publication_close(&conductor->counters_manager, conductor->ipc_publications.array[i].publication);\n        aeron_ipc_publication_free(conductor->ipc_publications.array[i].publication);\n    }\n    aeron_free(conductor->ipc_publications.array);\n\n    for (size_t i = 0, length = conductor->network_publications.length; i < length; i++)\n    {\n        aeron_network_publication_close(\n            &conductor->counters_manager, conductor->network_publications.array[i].publication);\n        aeron_network_publication_free(conductor->network_publications.array[i].publication);\n    }\n    aeron_free(conductor->network_publications.array);\n\n    for (size_t i = 0, length = conductor->ipc_subscriptions.length; i < length; i++)\n    {\n        aeron_free(conductor->ipc_subscriptions.array[i].subscribable_list.array);\n    }\n    aeron_free(conductor->ipc_subscriptions.array);\n\n    for (size_t i = 0, length = conductor->network_subscriptions.length; i < length; i++)\n    {\n        aeron_free(conductor->network_subscriptions.array[i].subscribable_list.array);\n    }\n    aeron_free(conductor->network_subscriptions.array);\n\n    for (size_t i = 0, length = conductor->spy_subscriptions.length; i < length; i++)\n    {\n        aeron_udp_channel_delete(conductor->spy_subscriptions.array[i].spy_channel);\n        aeron_free(conductor->spy_subscriptions.array[i].subscribable_list.array);\n    }\n    aeron_free(conductor->spy_subscriptions.array);\n\n    for (size_t i = 0, length = conductor->send_channel_endpoints.length; i < length; i++)\n    {\n        aeron_send_channel_endpoint_delete(\n            &conductor->counters_manager, conductor->send_channel_endpoints.array[i].endpoint);\n    }\n    aeron_free(conductor->send_channel_endpoints.array);\n\n    for (size_t i = 0, length = conductor->receive_channel_endpoints.length; i < length; i++)\n    {\n        aeron_receive_channel_endpoint_delete(\n            &conductor->counters_manager, conductor->receive_channel_endpoints.array[i].endpoint);\n    }\n    aeron_free(conductor->receive_channel_endpoints.array);\n\n    for (size_t i = 0, length = conductor->publication_images.length; i < length; i++)\n    {\n        aeron_publication_image_close(&conductor->counters_manager, conductor->publication_images.array[i].image);\n        aeron_publication_image_free(conductor->publication_images.array[i].image);\n    }\n    aeron_free(conductor->publication_images.array);\n\n    aeron_deque_close(&conductor->end_of_life_queue);\n\n    aeron_system_counters_close(&conductor->system_counters);\n    aeron_counters_manager_close(&conductor->counters_manager);\n    aeron_distinct_error_log_close(&conductor->error_log);\n\n    aeron_str_to_ptr_hash_map_delete(&conductor->send_channel_endpoint_by_channel_map);\n    aeron_str_to_ptr_hash_map_delete(&conductor->receive_channel_endpoint_by_channel_map);\n    aeron_mpsc_rb_consumer_heartbeat_time(&conductor->to_driver_commands, AERON_NULL_VALUE);\n    aeron_msync(conductor->context->cnc_map.addr, conductor->context->cnc_map.length);\n}\n\nint aeron_driver_subscribable_add_position(\n    aeron_subscribable_t *subscribable,\n    aeron_subscription_link_t *link,\n    int32_t counter_id,\n    int64_t *value_addr,\n    int64_t now_ns)\n{\n    int ensure_capacity_result = 0, result = -1;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, (*subscribable), aeron_tetherable_position_t)\n\n    if (ensure_capacity_result >= 0)\n    {\n        aeron_tetherable_position_t *entry = &subscribable->array[subscribable->length];\n        entry->is_tether = link->is_tether;\n        entry->is_rejoin = link->is_rejoin;\n        entry->state = AERON_SUBSCRIPTION_TETHER_ACTIVE;\n        entry->counter_id = counter_id;\n        entry->value_addr = value_addr;\n        entry->subscription_registration_id = link->registration_id;\n        entry->time_of_last_update_ns = now_ns;\n        subscribable->add_position_hook_func(subscribable->clientd, value_addr);\n        subscribable->length++;\n        result = 0;\n    }\n\n    return result;\n}\n\nvoid aeron_driver_subscribable_remove_position(aeron_subscribable_t *subscribable, int32_t counter_id)\n{\n    for (size_t i = 0, size = subscribable->length, last_index = size - 1; i < size; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &subscribable->array[i];\n        if (counter_id == tetherable_position->counter_id)\n        {\n            if (!aeron_driver_subscribable_is_active_state(tetherable_position->state))\n            {\n                subscribable->inactive_count--;\n            }\n\n            subscribable->remove_position_hook_func(subscribable->clientd, tetherable_position->value_addr);\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)subscribable->array, sizeof(aeron_tetherable_position_t), i, last_index);\n            subscribable->length--;\n\n            break;\n        }\n    }\n}\n\nint aeron_driver_conductor_link_subscribable(\n    aeron_driver_conductor_t *conductor,\n    aeron_subscription_link_t *link,\n    aeron_subscribable_t *subscribable,\n    int64_t original_registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    int64_t join_position,\n    int64_t now_ns,\n    size_t source_identity_length,\n    const char *source_identity,\n    size_t log_file_name_length,\n    const char *log_file_name)\n{\n    int ensure_capacity_result = 0, result = -1;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, link->subscribable_list, aeron_subscribable_list_entry_t)\n\n    if (ensure_capacity_result >= 0)\n    {\n        int64_t joining_position = join_position;\n        int32_t counter_id = aeron_counter_subscription_position_allocate(\n            &conductor->counters_manager,\n            link->client_id,\n            link->registration_id,\n            session_id,\n            stream_id,\n            link->channel_length,\n            link->channel,\n            joining_position);\n\n        if (counter_id >= 0)\n        {\n            aeron_counters_manager_counter_owner_id(\n                &conductor->counters_manager, counter_id, link->client_id);\n            aeron_counters_manager_counter_reference_id(\n                &conductor->counters_manager, counter_id, original_registration_id);\n            int64_t *position_addr = aeron_counters_manager_addr(&conductor->counters_manager, counter_id);\n\n            if (aeron_driver_subscribable_add_position(subscribable, link, counter_id, position_addr, now_ns) >= 0)\n            {\n                aeron_subscribable_list_entry_t *entry =\n                    &link->subscribable_list.array[link->subscribable_list.length++];\n\n                aeron_counter_set_release(position_addr, joining_position);\n\n                entry->subscribable = subscribable;\n                entry->counter_id = counter_id;\n\n                aeron_driver_conductor_on_available_image(\n                    conductor,\n                    original_registration_id,\n                    stream_id,\n                    session_id,\n                    log_file_name,\n                    log_file_name_length,\n                    counter_id,\n                    link->registration_id,\n                    source_identity,\n                    source_identity_length);\n\n                result = 0;\n            }\n            else\n            {\n                aeron_counters_manager_free(&conductor->counters_manager, counter_id);\n            }\n        }\n    }\n\n    return result;\n}\n\nvoid aeron_driver_subscribable_state(\n    aeron_subscribable_t *subscribable,\n    aeron_tetherable_position_t *tetherable_position,\n    const aeron_subscription_tether_state_t state,\n    int64_t now_ns,\n    int32_t stream_id,\n    int32_t session_id,\n    aeron_untethered_subscription_state_change_func_t log_func)\n{\n    aeron_subscription_tether_state_t old_state = tetherable_position->state;\n    if (state != old_state)\n    {\n        if (!aeron_driver_subscribable_is_active_state(state))\n        {\n            subscribable->inactive_count++;\n        }\n        else if (!aeron_driver_subscribable_is_active_state(old_state))\n        {\n            subscribable->inactive_count--;\n        }\n    }\n\n    tetherable_position->state = state;\n    tetherable_position->time_of_last_update_ns = now_ns;\n\n    log_func(tetherable_position, now_ns, old_state, state, stream_id, session_id);\n}\n\nsize_t aeron_driver_subscribable_working_position_count(aeron_subscribable_t *subscribable)\n{\n    return subscribable->length - subscribable->inactive_count;\n}\n\nbool aeron_driver_subscribable_has_working_positions(aeron_subscribable_t *subscribable)\n{\n    return subscribable->length > subscribable->inactive_count;\n}\n\nbool aeron_driver_subscribable_is_active_state(aeron_subscription_tether_state_t state)\n{\n    return state != AERON_SUBSCRIPTION_TETHER_RESTING && state != AERON_SUBSCRIPTION_TETHER_CLOSED;\n}\n\nvoid aeron_driver_conductor_unlink_subscribable(aeron_subscription_link_t *link, aeron_subscribable_t *subscribable)\n{\n    for (int last_index = (int32_t)link->subscribable_list.length - 1, i = last_index; i >= 0; i--)\n    {\n        if (subscribable == link->subscribable_list.array[i].subscribable)\n        {\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)link->subscribable_list.array, sizeof(aeron_subscribable_list_entry_t), i, last_index);\n            link->subscribable_list.length--;\n            last_index--;\n        }\n    }\n}\n\nvoid aeron_driver_conductor_unlink_all_subscribable(\n    aeron_driver_conductor_t *conductor, aeron_subscription_link_t *link)\n{\n    for (size_t i = 0; i < link->subscribable_list.length; i++)\n    {\n        aeron_subscribable_list_entry_t *entry = &link->subscribable_list.array[i];\n\n        aeron_driver_subscribable_remove_position(entry->subscribable, entry->counter_id);\n        aeron_counters_manager_free(&conductor->counters_manager, entry->counter_id);\n    }\n\n    aeron_free(link->subscribable_list.array);\n    link->subscribable_list.array = NULL;\n    link->subscribable_list.length = 0;\n    link->subscribable_list.capacity = 0;\n}\n\nint aeron_driver_conductor_link_ipc_subscriptions(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_t *publication)\n{\n    aeron_subscribable_t *subscribable = &publication->conductor_fields.subscribable;\n    int64_t now_ns = aeron_clock_cached_nano_time(conductor->context->cached_clock);\n\n    for (size_t i = 0; i < conductor->ipc_subscriptions.length; i++)\n    {\n        aeron_subscription_link_t *subscription_link = &conductor->ipc_subscriptions.array[i];\n\n        if (aeron_driver_conductor_subscription_link_matches_ipc_publication(subscription_link, publication) &&\n            !aeron_driver_conductor_is_subscribable_linked(subscription_link, subscribable) &&\n            aeron_ipc_publication_is_accepting_subscriptions(publication))\n        {\n            if (aeron_driver_conductor_link_subscribable(\n                conductor,\n                subscription_link,\n                subscribable,\n                publication->conductor_fields.managed_resource.registration_id,\n                publication->session_id,\n                publication->stream_id,\n                aeron_ipc_publication_join_position(publication),\n                now_ns,\n                AERON_IPC_CHANNEL_LEN,\n                AERON_IPC_CHANNEL,\n                publication->log_file_name_length,\n                publication->log_file_name) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_add_ipc_publication(\n    aeron_driver_conductor_t *conductor, aeron_publication_command_t *command, bool is_exclusive)\n{\n    int64_t correlation_id = command->correlated.correlation_id;\n    const char *uri = (const char *)command + sizeof(aeron_publication_command_t);\n    size_t uri_length = (size_t)command->channel_length;\n    aeron_uri_t aeron_uri_params;\n    aeron_driver_uri_publication_params_t params;\n\n    if (aeron_uri_parse(uri_length, uri, &aeron_uri_params) < 0 ||\n        aeron_diver_uri_publication_params(\n            &aeron_uri_params,\n            &params,\n            conductor,\n            is_exclusive) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to parse IPC publication URI\");\n        goto error_cleanup;\n    }\n\n    aeron_client_t *client = aeron_driver_conductor_get_or_add_client(conductor, command->correlated.client_id);\n    if (NULL == client)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to add client\");\n        goto error_cleanup;\n    }\n\n    aeron_ipc_publication_t *publication = aeron_driver_conductor_get_or_add_ipc_publication(\n        conductor, client, &params, correlation_id, command->stream_id, uri_length, uri, is_exclusive);\n    if (NULL == publication)\n    {\n        goto error_cleanup;\n    }\n\n    aeron_driver_conductor_on_publication_ready(\n        conductor,\n        command->correlated.correlation_id,\n        publication->conductor_fields.managed_resource.registration_id,\n        publication->stream_id,\n        publication->session_id,\n        publication->pub_lmt_position.counter_id,\n        AERON_CHANNEL_STATUS_INDICATOR_NOT_ALLOCATED,\n        is_exclusive,\n        publication->log_file_name,\n        publication->log_file_name_length);\n\n    if (aeron_driver_conductor_link_ipc_subscriptions(conductor, publication) < 0)\n    {\n        goto error_cleanup;\n    }\n\n    aeron_uri_close(&aeron_uri_params);\n    return 0;\n\nerror_cleanup:\n    aeron_uri_close(&aeron_uri_params);\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_network_publication_complete(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_command_t *async_command,\n    void *on_execute_clientd)\n{\n    aeron_udp_channel_async_parse_t *async_parse = on_execute_clientd;\n    aeron_udp_channel_t *udp_channel = async_parse->channel;\n    aeron_publication_command_t *command = async_command->original_command;\n    int64_t correlation_id = command->correlated.correlation_id;\n\n    bool is_exclusive = async_command->is_exclusive;\n    const char *uri = (const char *)command + sizeof(aeron_publication_command_t);\n    size_t uri_length = (size_t)command->channel_length;\n\n    aeron_driver_uri_publication_params_t params;\n\n    if (aeron_diver_uri_publication_params(&udp_channel->uri, &params, conductor, is_exclusive) < 0 ||\n        aeron_driver_conductor_validate_experimental_features(\n            conductor->context->enable_experimental_features, udp_channel) < 0 ||\n        aeron_driver_conductor_validate_endpoint_for_publication(udp_channel) < 0 ||\n        aeron_driver_conductor_validate_control_for_publication(udp_channel) < 0 ||\n        aeron_driver_conductor_validate_response_subscription(conductor, udp_channel, &params) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    aeron_client_t *client = aeron_driver_conductor_get_or_add_client(conductor, command->correlated.client_id);\n    if (NULL == client)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to add client\");\n        goto error_cleanup;\n    }\n\n    aeron_publication_image_t *response_publication_image = NULL;\n    if (aeron_driver_conductor_find_response_publication_image(\n        conductor, udp_channel, &params, &response_publication_image) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    aeron_send_channel_endpoint_t *endpoint = aeron_driver_conductor_get_or_add_send_channel_endpoint(\n        conductor, udp_channel, &params, correlation_id);\n    if (NULL == endpoint)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        udp_channel = NULL; // deleted by the previous method\n        goto error_cleanup;\n    }\n\n    if (endpoint->conductor_fields.udp_channel != udp_channel)\n    {\n        aeron_udp_channel_delete(udp_channel);\n    }\n    udp_channel = NULL;\n\n    if (AERON_SEND_CHANNEL_ENDPOINT_STATUS_ACTIVE != endpoint->conductor_fields.status)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_RESOURCE_TEMPORARILY_UNAVAILABLE,\n            \"%s\",\n            \"send_channel_endpoint found in CLOSING state, please retry\");\n\n        goto error_cleanup;\n    }\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_get_or_add_network_publication(\n        conductor,\n        client,\n        endpoint,\n        uri_length,\n        uri,\n        &params,\n        response_publication_image,\n        correlation_id,\n        command->stream_id,\n        is_exclusive);\n\n    if (NULL == publication)\n    {\n        AERON_APPEND_ERR(\"uri=%.*s\", uri_length, uri);\n        goto error_cleanup;\n    }\n\n    aeron_driver_conductor_on_publication_ready(\n        conductor,\n        correlation_id,\n        publication->conductor_fields.managed_resource.registration_id,\n        publication->stream_id,\n        publication->session_id,\n        publication->pub_lmt_position.counter_id,\n        endpoint->channel_status.counter_id,\n        is_exclusive,\n        publication->log_file_name,\n        publication->log_file_name_length);\n\n    int64_t now_ns = aeron_clock_cached_nano_time(conductor->context->cached_clock);\n    aeron_subscribable_t *subscribable = &publication->conductor_fields.subscribable;\n\n    for (size_t i = 0; i < conductor->spy_subscriptions.length; i++)\n    {\n        aeron_subscription_link_t *subscription_link = &conductor->spy_subscriptions.array[i];\n\n        if (aeron_driver_conductor_spy_subscription_link_matches(subscription_link, publication) &&\n            !aeron_driver_conductor_is_subscribable_linked(subscription_link, subscribable))\n        {\n            if (aeron_driver_conductor_link_subscribable(\n                conductor,\n                subscription_link,\n                &publication->conductor_fields.subscribable,\n                publication->conductor_fields.managed_resource.registration_id,\n                publication->session_id,\n                publication->stream_id,\n                aeron_network_publication_join_position(publication),\n                now_ns,\n                AERON_IPC_CHANNEL_LEN,\n                AERON_IPC_CHANNEL,\n                publication->log_file_name_length,\n                publication->log_file_name) < 0)\n            {\n                goto error_cleanup;\n            }\n        }\n    }\n\n    return 0;\n\nerror_cleanup:\n    aeron_udp_channel_delete(udp_channel);\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_network_publication(\n    aeron_driver_conductor_t *conductor, aeron_publication_command_t *command, bool is_exclusive)\n{\n    aeron_driver_async_client_command_t *async_client_command;\n\n    if (aeron_driver_async_client_command_allocate(\n        &async_client_command,\n        command,\n        sizeof(aeron_publication_command_t) + command->channel_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_udp_channel_do_initial_parse(\n        (size_t)command->channel_length,\n        (const char *)command + sizeof(aeron_publication_command_t),\n        &async_client_command->async_command.async_parse) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    async_client_command->async_command.async_parse.is_destination = false;\n\n    async_client_command->async_command.is_exclusive = is_exclusive;\n\n    async_client_command->correlated = &((aeron_publication_command_t *)async_client_command->async_command.original_command)->correlated;\n    async_client_command->on_execute = aeron_driver_async_parse_udp_channel_execute;\n    async_client_command->on_execute_clientd = &async_client_command->async_command.async_parse;\n    async_client_command->on_complete = aeron_driver_conductor_on_add_network_publication_complete;\n\n    if (aeron_driver_async_client_command_submit(conductor, async_client_command) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    return 0;\n\nerror_cleanup:\n    aeron_driver_async_client_command_free(async_client_command);\n    return -1;\n}\n\nint aeron_driver_conductor_on_remove_publication(aeron_driver_conductor_t *conductor, aeron_remove_publication_command_t *command)\n{\n    int index = aeron_driver_conductor_find_client(conductor, command->correlated.client_id);\n    if (index >= 0)\n    {\n        aeron_client_t *client = &conductor->clients.array[index];\n\n        for (size_t i = 0, size = client->publication_links.length, last_index = size - 1; i < size; i++)\n        {\n            aeron_driver_managed_resource_t *resource = client->publication_links.array[i].resource;\n\n            if (command->registration_id == client->publication_links.array[i].registration_id)\n            {\n                if (command->flags & AERON_COMMAND_REMOVE_PUBLICATION_FLAG_REVOKE)\n                {\n                    AERON_DRIVER_MANAGED_RESOURCE_REVOKE(resource);\n                }\n                AERON_DRIVER_MANAGED_RESOURCE_DECREF(resource);\n\n                aeron_array_fast_unordered_remove(\n                    (uint8_t *)client->publication_links.array, sizeof(aeron_publication_link_t), i, last_index);\n                client->publication_links.length--;\n\n                aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n                return 0;\n            }\n        }\n    }\n\n    AERON_SET_ERR(\n        -AERON_ERROR_CODE_UNKNOWN_PUBLICATION,\n        \"unknown publication client_id=%\" PRId64 \" registration_id=%\" PRId64,\n        command->correlated.client_id,\n        command->registration_id);\n\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_ipc_subscription(\n    aeron_driver_conductor_t *conductor, aeron_subscription_command_t *command)\n{\n    const char *uri = (const char *)command + sizeof(aeron_subscription_command_t);\n    aeron_uri_t aeron_uri_params;\n    aeron_driver_uri_subscription_params_t params;\n    size_t uri_length = (size_t)command->channel_length;\n\n    if (aeron_uri_parse(uri_length, uri, &aeron_uri_params) < 0 ||\n        aeron_driver_uri_subscription_params(&aeron_uri_params, &params, conductor) < 0)\n    {\n        goto error_cleanup;\n    }\n\n    if (NULL == aeron_driver_conductor_get_or_add_client(conductor, command->correlated.client_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to add client\");\n        goto error_cleanup;\n    }\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, conductor->ipc_subscriptions, aeron_subscription_link_t)\n    if (ensure_capacity_result < 0)\n    {\n        goto error_cleanup;\n    }\n\n    aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[conductor->ipc_subscriptions.length++];\n    aeron_driver_init_subscription_channel(command->channel_length, uri, link);\n    link->endpoint = NULL;\n    link->spy_channel = NULL;\n    link->stream_id = command->stream_id;\n    link->has_session_id = params.has_session_id;\n    link->session_id = params.session_id;\n    link->client_id = command->correlated.client_id;\n    link->registration_id = command->correlated.correlation_id;\n    link->is_reliable = true;\n    link->is_rejoin = params.is_rejoin;\n    link->is_response = params.is_response;\n    link->group = AERON_INFER;\n    link->is_sparse = params.is_sparse;\n    link->is_tether = params.is_tether;\n    link->subscribable_list.length = 0;\n    link->subscribable_list.capacity = 0;\n    link->subscribable_list.array = NULL;\n\n    aeron_driver_conductor_on_subscription_ready(\n        conductor, command->correlated.correlation_id, AERON_CHANNEL_STATUS_INDICATOR_NOT_ALLOCATED);\n\n    int64_t now_ns = aeron_clock_cached_nano_time(conductor->context->cached_clock);\n\n    for (size_t i = 0; i < conductor->ipc_publications.length; i++)\n    {\n        aeron_ipc_publication_entry_t *publication_entry = &conductor->ipc_publications.array[i];\n        aeron_ipc_publication_t *publication = publication_entry->publication;\n\n        if (aeron_driver_conductor_subscription_link_matches_ipc_publication(link, publication) &&\n            aeron_ipc_publication_is_accepting_subscriptions(publication))\n        {\n            if (aeron_driver_conductor_link_subscribable(\n                conductor,\n                link,\n                &publication->conductor_fields.subscribable,\n                publication->conductor_fields.managed_resource.registration_id,\n                publication->session_id,\n                publication->stream_id,\n                aeron_ipc_publication_join_position(publication),\n                now_ns,\n                AERON_IPC_CHANNEL_LEN,\n                AERON_IPC_CHANNEL,\n                publication->log_file_name_length,\n                publication->log_file_name) < 0)\n            {\n                goto error_cleanup;\n            }\n        }\n    }\n\n    aeron_uri_close(&aeron_uri_params);\n    return 0;\n\nerror_cleanup:\n    aeron_uri_close(&aeron_uri_params);\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_spy_subscription_complete(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_command_t *async_command,\n    void *on_execute_clientd)\n{\n    aeron_subscription_command_t *command = async_command->original_command;\n    aeron_udp_channel_async_parse_t *async_parse = on_execute_clientd;\n    aeron_udp_channel_t *udp_channel = async_parse->channel;\n    const char *uri = (const char *)command + sizeof(aeron_subscription_command_t);\n    aeron_driver_uri_subscription_params_t params;\n\n    if (aeron_driver_uri_subscription_params(&udp_channel->uri, &params, conductor) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (NULL == aeron_driver_conductor_get_or_add_client(conductor, command->correlated.client_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to add client\");\n        return -1;\n    }\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, conductor->spy_subscriptions, aeron_subscription_link_t)\n    if (ensure_capacity_result < 0)\n    {\n        return -1;\n    }\n\n    aeron_subscription_link_t *link = &conductor->spy_subscriptions.array[conductor->spy_subscriptions.length++];\n    aeron_driver_init_subscription_channel(command->channel_length, uri, link);\n    link->endpoint = NULL;\n    link->spy_channel = udp_channel;\n    link->stream_id = command->stream_id;\n    link->has_session_id = params.has_session_id;\n    link->session_id = params.session_id;\n    link->client_id = command->correlated.client_id;\n    link->registration_id = command->correlated.correlation_id;\n    link->is_reliable = params.is_reliable;\n    link->is_sparse = params.is_sparse;\n    link->is_tether = params.is_tether;\n    link->is_rejoin = params.is_rejoin;\n    link->is_response = false;\n    link->group = AERON_INFER;\n    link->subscribable_list.length = 0;\n    link->subscribable_list.capacity = 0;\n    link->subscribable_list.array = NULL;\n\n    aeron_driver_conductor_on_subscription_ready(\n        conductor, command->correlated.correlation_id, AERON_CHANNEL_STATUS_INDICATOR_NOT_ALLOCATED);\n\n    int64_t now_ns = aeron_clock_cached_nano_time(conductor->context->cached_clock);\n\n    for (size_t i = 0, length = conductor->network_publications.length; i < length; i++)\n    {\n        aeron_network_publication_t *publication = conductor->network_publications.array[i].publication;\n\n        if (aeron_driver_conductor_spy_subscription_link_matches(link, publication) &&\n            aeron_network_publication_is_accepting_subscriptions(publication))\n        {\n            if (aeron_driver_conductor_link_subscribable(\n                conductor,\n                link,\n                &publication->conductor_fields.subscribable,\n                publication->conductor_fields.managed_resource.registration_id,\n                publication->session_id,\n                publication->stream_id,\n                aeron_network_publication_join_position(publication),\n                now_ns,\n                AERON_IPC_CHANNEL_LEN,\n                AERON_IPC_CHANNEL,\n                publication->log_file_name_length,\n                publication->log_file_name) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_add_spy_subscription(\n    aeron_driver_conductor_t *conductor, aeron_subscription_command_t *command)\n{\n    aeron_driver_async_client_command_t *async_client_command;\n\n    if (aeron_driver_async_client_command_allocate(\n        &async_client_command,\n        command,\n        sizeof(aeron_subscription_command_t) + command->channel_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_udp_channel_do_initial_parse(\n        (size_t)command->channel_length - strlen(AERON_SPY_PREFIX),\n        (const char *)command + sizeof(aeron_subscription_command_t) + strlen(AERON_SPY_PREFIX),\n        &async_client_command->async_command.async_parse) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    async_client_command->async_command.async_parse.is_destination = false;\n\n    async_client_command->correlated = &((aeron_subscription_command_t *)async_client_command->async_command.original_command)->correlated;\n    async_client_command->on_execute = aeron_driver_async_parse_udp_channel_execute;\n    async_client_command->on_execute_clientd = &async_client_command->async_command.async_parse;\n    async_client_command->on_complete = aeron_driver_conductor_on_add_spy_subscription_complete;\n\n    if (aeron_driver_async_client_command_submit(conductor, async_client_command) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    return 0;\n\nerror_cleanup:\n    aeron_driver_async_client_command_free(async_client_command);\n    return -1;\n}\n\nint aeron_driver_conductor_add_network_subscription_to_receiver(\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    bool has_session_id,\n    int32_t session_id)\n{\n    if (has_session_id)\n    {\n        if (aeron_receive_channel_endpoint_incref_to_stream_and_session(endpoint, stream_id, session_id) < 0)\n        {\n            return -1;\n        }\n    }\n    else\n    {\n        if (aeron_receive_channel_endpoint_incref_to_stream(endpoint, stream_id) < 0)\n        {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_add_network_subscription_complete(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_command_t *async_command,\n    void *on_execute_clientd)\n{\n    aeron_subscription_command_t *command = async_command->original_command;\n    aeron_udp_channel_async_parse_t *async_parse = on_execute_clientd;\n    aeron_udp_channel_t *udp_channel = async_parse->channel;\n    int64_t correlation_id = command->correlated.correlation_id;\n\n    const char *uri = (const char *)command + sizeof(aeron_subscription_command_t);\n\n    aeron_driver_uri_subscription_params_t params;\n    if (aeron_driver_uri_subscription_params(&udp_channel->uri, &params, conductor) < 0 ||\n        aeron_driver_conductor_validate_experimental_features(\n            conductor->context->enable_experimental_features,\n            udp_channel) < 0 ||\n        aeron_driver_conductor_validate_control_for_subscription(udp_channel) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n    const aeron_udp_channel_control_mode control_mode = udp_channel->control_mode;\n\n    if (aeron_driver_conductor_has_clashing_subscription(conductor, udp_channel, command->stream_id, &params))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_get_or_add_receive_channel_endpoint(\n        conductor, &params, udp_channel, correlation_id);\n    if (NULL == endpoint)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        udp_channel = NULL; // deleted by the previous method\n        goto error_cleanup;\n    }\n\n    if (endpoint->conductor_fields.udp_channel != udp_channel)\n    {\n        aeron_udp_channel_delete(udp_channel);\n    }\n    // Ownership is transferred to the channel.\n    udp_channel = NULL;\n\n    if (AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_ACTIVE != endpoint->conductor_fields.status)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_RESOURCE_TEMPORARILY_UNAVAILABLE,\n            \"%s\",\n            \"receive_channel_endpoint found in CLOSING state, please retry\");\n        goto error_cleanup;\n    }\n\n    if (NULL == aeron_driver_conductor_get_or_add_client(conductor, command->correlated.client_id))\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to add client\");\n        goto error_cleanup;\n    }\n\n    if (AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE == control_mode)\n    {\n        if (aeron_receive_channel_endpoint_incref_to_response_stream(endpoint, command->stream_id) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n    }\n    else\n    {\n        if (aeron_driver_conductor_add_network_subscription_to_receiver(\n            endpoint, command->stream_id, params.has_session_id, params.session_id) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n    }\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, conductor->network_subscriptions, aeron_subscription_link_t)\n\n    if (ensure_capacity_result >= 0)\n    {\n        aeron_subscription_link_t *link =\n            &conductor->network_subscriptions.array[conductor->network_subscriptions.length++];\n\n        aeron_driver_init_subscription_channel(command->channel_length, uri, link);\n        link->endpoint = endpoint;\n        link->spy_channel = NULL;\n        link->stream_id = command->stream_id;\n        link->has_session_id = params.has_session_id;\n        link->session_id = params.session_id;\n        link->client_id = command->correlated.client_id;\n        link->registration_id = correlation_id;\n        link->is_reliable = params.is_reliable;\n        link->is_sparse = params.is_sparse;\n        link->is_tether = params.is_tether;\n        link->is_rejoin = params.is_rejoin;\n        link->is_response = params.is_response;\n        link->group = params.group;\n        link->subscribable_list.length = 0;\n        link->subscribable_list.capacity = 0;\n        link->subscribable_list.array = NULL;\n\n        aeron_driver_conductor_on_subscription_ready(\n            conductor, correlation_id, endpoint->channel_status.counter_id);\n\n        int64_t now_ns = aeron_clock_cached_nano_time(conductor->context->cached_clock);\n\n        for (size_t i = 0, length = conductor->publication_images.length; i < length; i++)\n        {\n            aeron_publication_image_t *image = conductor->publication_images.array[i].image;\n\n            if (aeron_driver_conductor_network_subscription_link_matches_allowing_wildcard(\n                link, image->conductor_fields.endpoint, image->stream_id, image->session_id) &&\n                aeron_publication_image_is_accepting_subscriptions(image))\n            {\n                if (aeron_driver_conductor_link_subscribable(\n                    conductor,\n                    link,\n                    &image->conductor_fields.subscribable,\n                    image->conductor_fields.managed_resource.registration_id,\n                    image->session_id,\n                    image->stream_id,\n                    aeron_publication_image_join_position(image),\n                    now_ns,\n                    image->source_identity_length,\n                    image->source_identity,\n                    image->log_file_name_length,\n                    image->log_file_name) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"\");\n                    goto error_cleanup;\n                }\n            }\n        }\n\n        return 0;\n    }\n\nerror_cleanup:\n    aeron_udp_channel_delete(udp_channel);\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_network_subscription(\n    aeron_driver_conductor_t *conductor, aeron_subscription_command_t *command)\n{\n    aeron_driver_async_client_command_t *async_client_command;\n\n    if (aeron_driver_async_client_command_allocate(\n        &async_client_command,\n        command,\n        sizeof(aeron_subscription_command_t) + command->channel_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_udp_channel_do_initial_parse(\n        (size_t)command->channel_length,\n        (const char *)command + sizeof(aeron_subscription_command_t),\n        &async_client_command->async_command.async_parse) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    async_client_command->async_command.async_parse.is_destination = false;\n\n    async_client_command->correlated = &((aeron_subscription_command_t *)async_client_command->async_command.original_command)->correlated;\n    async_client_command->on_execute = aeron_driver_async_parse_udp_channel_execute;\n    async_client_command->on_execute_clientd = &async_client_command->async_command.async_parse;\n    async_client_command->on_complete = aeron_driver_conductor_on_add_network_subscription_complete;\n\n    if (aeron_driver_async_client_command_submit(conductor, async_client_command) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    return 0;\n\nerror_cleanup:\n    aeron_driver_async_client_command_free(async_client_command);\n    return -1;\n}\n\nint aeron_driver_conductor_on_remove_subscription(\n    aeron_driver_conductor_t *conductor, aeron_remove_subscription_command_t *command)\n{\n    bool is_any_subscription_found = false;\n\n    for (int last_index = (int32_t)conductor->ipc_subscriptions.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[i];\n\n        if (command->registration_id == link->registration_id)\n        {\n            aeron_driver_conductor_unlink_all_subscribable(conductor, link);\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)conductor->ipc_subscriptions.array, sizeof(aeron_subscription_link_t), i, last_index);\n            conductor->ipc_subscriptions.length--;\n            last_index--;\n            is_any_subscription_found = true;\n        }\n    }\n\n    for (int last_index = (int32_t)conductor->network_subscriptions.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n\n        if (command->registration_id == link->registration_id)\n        {\n            aeron_driver_conductor_unlink_from_endpoint(conductor, link);\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)conductor->network_subscriptions.array, sizeof(aeron_subscription_link_t), i, last_index);\n            conductor->network_subscriptions.length--;\n            last_index--;\n            is_any_subscription_found = true;\n        }\n    }\n\n    for (int last_index = (int32_t)conductor->spy_subscriptions.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_subscription_link_t *link = &conductor->spy_subscriptions.array[i];\n\n        if (command->registration_id == link->registration_id)\n        {\n            aeron_driver_conductor_unlink_all_subscribable(conductor, link);\n\n            aeron_udp_channel_delete(link->spy_channel);\n            link->spy_channel = NULL;\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)conductor->spy_subscriptions.array, sizeof(aeron_subscription_link_t), i, last_index);\n            conductor->spy_subscriptions.length--;\n            last_index--;\n            is_any_subscription_found = true;\n        }\n    }\n\n    if (!is_any_subscription_found)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_UNKNOWN_SUBSCRIPTION,\n            \"unknown subscription client_id=%\" PRId64 \" registration_id=%\" PRId64,\n            command->correlated.client_id,\n            command->registration_id);\n\n        return -1;\n    }\n\n    aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n    return 0;\n}\n\nint aeron_driver_conductor_on_client_keepalive(aeron_driver_conductor_t *conductor, int64_t client_id)\n{\n    int index = aeron_driver_conductor_find_client(conductor, client_id);\n    if (index >= 0)\n    {\n        aeron_client_t *client = &conductor->clients.array[index];\n        int64_t now_ms = aeron_clock_cached_epoch_time(conductor->context->cached_clock);\n        aeron_counter_set_release(client->heartbeat_timestamp.value_addr, now_ms);\n    }\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_add_send_destination_complete(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_command_t *async_command,\n    void *on_execute_clientd)\n{\n    aeron_destination_command_t *command = async_command->original_command;\n    aeron_name_resolver_async_resolve_t *async_resolve = on_execute_clientd;\n\n    aeron_driver_sender_proxy_on_add_destination(\n        conductor->context->sender_proxy,\n        async_command->endpoint,\n        async_command->uri,\n        &async_resolve->sockaddr,\n        command->correlated.correlation_id);\n\n    aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_add_send_destination_error(\n    aeron_driver_conductor_t *conductor,\n    int result,\n    aeron_driver_async_command_t *async_command,\n    void *on_execute_clientd)\n{\n    aeron_name_resolver_async_resolve_t *async_resolve = (aeron_name_resolver_async_resolve_t *)on_execute_clientd;\n\n    memset(&async_resolve->sockaddr, 0, sizeof(async_resolve->sockaddr));\n    async_resolve->sockaddr.ss_family = AF_UNSPEC;\n\n    return aeron_driver_conductor_on_add_send_destination_complete(conductor, async_command, on_execute_clientd);\n}\n\nint aeron_driver_conductor_on_add_send_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command)\n{\n    aeron_send_channel_endpoint_t *endpoint = NULL;\n\n    for (size_t i = 0, length = conductor->network_publications.length; i < length; i++)\n    {\n        aeron_network_publication_t *publication = conductor->network_publications.array[i].publication;\n\n        if (command->registration_id == publication->conductor_fields.managed_resource.registration_id)\n        {\n            endpoint = publication->endpoint;\n            break;\n        }\n    }\n\n    if (NULL == endpoint)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_UNKNOWN_PUBLICATION,\n            \"unknown add destination client_id=%\" PRId64 \" registration_id=%\" PRId64,\n            command->correlated.client_id,\n            command->registration_id);\n\n        return -1;\n    }\n\n    aeron_uri_t *uri = NULL;\n    const char *command_uri = (const char *)command + sizeof(aeron_destination_command_t);\n\n    if (aeron_driver_conductor_validate_destination_uri_prefix(command_uri, command->channel_length, \"send\") < 0)\n    {\n        goto error_cleanup;\n    }\n\n    if (aeron_alloc((void **)&uri, sizeof(aeron_uri_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate uri\");\n        goto error_cleanup;\n    }\n\n    size_t uri_length = (size_t)command->channel_length;\n    if (aeron_uri_parse(uri_length, command_uri, uri) < 0 ||\n        aeron_driver_conductor_validate_send_destination_uri(uri, uri_length) < 0)\n    {\n        goto error_cleanup;\n    }\n\n    if (aeron_driver_conductor_validate_destination_uri_params(uri, uri_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    if (NULL == endpoint->destination_tracker || !endpoint->destination_tracker->is_manual_control_mode)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"channel does not allow manual control of destinations: %.*s\",\n            command->channel_length, command_uri);\n        goto error_cleanup;\n    }\n\n    if (uri->type != AERON_URI_UDP || NULL == uri->params.udp.endpoint)\n    {\n        AERON_SET_ERR(EINVAL, \"incorrect URI format for destination: %.*s\", command->channel_length, command_uri);\n        goto error_cleanup;\n    }\n\n    aeron_driver_async_client_command_t *async_client_command;\n\n    if (aeron_driver_async_client_command_allocate(\n        &async_client_command,\n        command,\n        sizeof(aeron_destination_command_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    async_client_command->async_command.async_resolve.uri_param_name = AERON_UDP_CHANNEL_ENDPOINT_KEY;\n    async_client_command->async_command.async_resolve.is_re_resolution = false;\n    COPY_ENDPOINT_NAME(async_client_command->async_command.async_resolve.endpoint_name, uri->params.udp.endpoint);\n\n    // TODO instead of storing the pointer, should we be looking this up again in the on_complete callback?\n    async_client_command->async_command.endpoint = endpoint;\n    async_client_command->async_command.uri = uri;\n\n    async_client_command->correlated = &((aeron_destination_command_t *)async_client_command->async_command.original_command)->correlated;\n    async_client_command->on_execute = aeron_driver_async_resolve_execute;\n    async_client_command->on_execute_clientd = &async_client_command->async_command.async_resolve;\n    async_client_command->on_complete = aeron_driver_conductor_on_add_send_destination_complete;\n    async_client_command->on_error = aeron_driver_conductor_on_add_send_destination_error;\n\n    if (aeron_driver_async_client_command_submit(conductor, async_client_command) < 0)\n    {\n        aeron_driver_async_client_command_free(async_client_command);\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    return 0;\n\nerror_cleanup:\n    aeron_uri_close(uri);\n    aeron_free(uri);\n    return -1;\n}\n\nint aeron_driver_conductor_on_remove_send_destination_complete(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_command_t *async_command,\n    void *on_execute_clientd)\n{\n    aeron_destination_command_t *command = async_command->original_command;\n    aeron_name_resolver_async_resolve_t *async_resolve = on_execute_clientd;\n\n    aeron_driver_sender_proxy_on_remove_destination(\n        conductor->context->sender_proxy, async_command->endpoint, &async_resolve->sockaddr);\n    aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_remove_send_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command)\n{\n    aeron_send_channel_endpoint_t *endpoint = NULL;\n\n    for (size_t i = 0, length = conductor->network_publications.length; i < length; i++)\n    {\n        aeron_network_publication_t *publication = conductor->network_publications.array[i].publication;\n\n        if (command->registration_id == publication->conductor_fields.managed_resource.registration_id)\n        {\n            endpoint = publication->endpoint;\n            break;\n        }\n    }\n\n    if (NULL == endpoint)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_UNKNOWN_PUBLICATION,\n            \"unknown remove destination, client_id=%\" PRId64 \" registration_id=%\" PRId64,\n            command->correlated.client_id,\n            command->registration_id);\n\n        return -1;\n    }\n\n    int rc = -1;\n    aeron_uri_t uri_params;\n    const char *command_uri = (const char *)command + sizeof(aeron_destination_command_t);\n    size_t uri_length = (size_t)command->channel_length;\n    if (aeron_uri_parse(uri_length, command_uri, &uri_params) < 0)\n    {\n        goto cleanup;\n    }\n\n    if (NULL == endpoint->destination_tracker || !endpoint->destination_tracker->is_manual_control_mode)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"channel does not allow manual control of destinations: %.*s\",\n            command->channel_length, command_uri);\n        goto cleanup;\n    }\n\n    if (uri_params.type != AERON_URI_UDP || NULL == uri_params.params.udp.endpoint)\n    {\n        AERON_SET_ERR(EINVAL, \"incorrect URI format for destination: %.*s\", command->channel_length, command_uri);\n        goto cleanup;\n    }\n\n    aeron_driver_async_client_command_t *async_client_command;\n\n    if (aeron_driver_async_client_command_allocate(\n        &async_client_command,\n        command,\n        sizeof(aeron_destination_command_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto cleanup;\n    }\n\n    async_client_command->async_command.async_resolve.uri_param_name = AERON_UDP_CHANNEL_ENDPOINT_KEY;\n    async_client_command->async_command.async_resolve.is_re_resolution = true;\n    COPY_ENDPOINT_NAME(async_client_command->async_command.async_resolve.endpoint_name, uri_params.params.udp.endpoint);\n\n    async_client_command->async_command.endpoint = endpoint;\n    async_client_command->async_command.uri = NULL;\n\n    async_client_command->correlated = &((aeron_destination_command_t *)async_client_command->async_command.original_command)->correlated;\n    async_client_command->on_execute = aeron_driver_async_resolve_execute;\n    async_client_command->on_execute_clientd = &async_client_command->async_command.async_resolve;\n    async_client_command->on_complete = aeron_driver_conductor_on_remove_send_destination_complete;\n\n    if (aeron_driver_async_client_command_submit(conductor, async_client_command) < 0)\n    {\n        aeron_driver_async_client_command_free(async_client_command);\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto cleanup;\n    }\n\n    rc = 0;\n\ncleanup:\n    aeron_uri_close(&uri_params);\n    return rc;\n}\n\nint aeron_driver_conductor_on_remove_receive_send_destination_by_id(\n    aeron_driver_conductor_t *conductor, aeron_destination_by_id_command_t *command)\n{\n    int rc = -1;\n    aeron_send_channel_endpoint_t *endpoint = NULL;\n\n    for (size_t i = 0, length = conductor->network_publications.length; i < length; i++)\n    {\n        aeron_network_publication_t *publication = conductor->network_publications.array[i].publication;\n\n        if (command->resource_registration_id == publication->conductor_fields.managed_resource.registration_id)\n        {\n            endpoint = publication->endpoint;\n            break;\n        }\n    }\n\n    if (NULL == endpoint)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_UNKNOWN_PUBLICATION,\n            \"unknown remove destination, client_id=%\" PRId64 \" registration_id=%\" PRId64,\n            command->correlated.client_id,\n            command->resource_registration_id);\n\n        goto cleanup;\n    }\n\n    if (NULL == endpoint->destination_tracker || !endpoint->destination_tracker->is_manual_control_mode)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"channel does not allow manual control of destinations: %\" PRId64,\n            command->resource_registration_id);\n        goto cleanup;\n    }\n\n    aeron_driver_sender_proxy_on_remove_destination_by_id(\n        conductor->context->sender_proxy, endpoint, command->destination_registration_id);\n    aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n\n    rc = 0;\n\ncleanup:\n    return rc;\n}\n\naeron_subscription_link_t *aeron_driver_conductor_find_mds_subscription(\n    aeron_driver_conductor_t *conductor, int64_t client_id, int64_t registration_id)\n{\n    aeron_subscription_link_t *mds_subscription_link = NULL;\n\n    for (size_t i = 0, size = conductor->network_subscriptions.length; i < size; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n\n        if (registration_id == link->registration_id)\n        {\n            mds_subscription_link = link;\n            break;\n        }\n    }\n\n    if (NULL == mds_subscription_link)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_UNKNOWN_SUBSCRIPTION,\n            \"unknown subscription client_id=%\" PRId64 \" registration_id=%\" PRId64,\n            client_id,\n            registration_id);\n\n        return NULL;\n    }\n\n    if (AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL != mds_subscription_link->endpoint->conductor_fields.udp_channel->control_mode)\n    {\n        AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL, \"%s\", \"channel does not allow manual control\");\n        return NULL;\n    }\n\n    return mds_subscription_link;\n}\n\nint aeron_driver_conductor_on_add_receive_ipc_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command)\n{\n    aeron_subscription_link_t *mds_subscription_link = NULL;\n    const char *command_uri = (const char *)command + sizeof(aeron_destination_command_t);\n    aeron_uri_t aeron_uri_params;\n    aeron_driver_uri_subscription_params_t params;\n    size_t uri_length = (size_t)command->channel_length;\n\n    if (aeron_uri_parse(uri_length, command_uri, &aeron_uri_params) < 0 ||\n        aeron_driver_uri_subscription_params(&aeron_uri_params, &params, conductor) < 0)\n    {\n        goto error_cleanup;\n    }\n\n    mds_subscription_link = aeron_driver_conductor_find_mds_subscription(\n        conductor, command->correlated.client_id, command->registration_id);\n    if (NULL == mds_subscription_link)\n    {\n        goto error_cleanup;\n    }\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, conductor->ipc_subscriptions, aeron_subscription_link_t)\n    if (ensure_capacity_result < 0)\n    {\n        goto error_cleanup;\n    }\n\n    aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[conductor->ipc_subscriptions.length++];\n    aeron_driver_init_subscription_channel(command->channel_length, command_uri, link);\n    link->endpoint = NULL;\n    link->spy_channel = NULL;\n    link->stream_id = mds_subscription_link->stream_id;\n    link->has_session_id = params.has_session_id;\n    link->session_id = params.session_id;\n    link->client_id = command->correlated.client_id;\n    link->registration_id = mds_subscription_link->registration_id;\n    link->is_reliable = true;\n    link->is_rejoin = params.is_rejoin;\n    link->is_response = false;\n    link->group = AERON_INFER;\n    link->is_sparse = params.is_sparse;\n    link->is_tether = params.is_tether;\n    link->subscribable_list.length = 0;\n    link->subscribable_list.capacity = 0;\n    link->subscribable_list.array = NULL;\n\n    aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n\n    int64_t now_ns = aeron_clock_cached_nano_time(conductor->context->cached_clock);\n\n    for (size_t i = 0; i < conductor->ipc_publications.length; i++)\n    {\n        aeron_ipc_publication_entry_t *publication_entry = &conductor->ipc_publications.array[i];\n        aeron_ipc_publication_t *publication = publication_entry->publication;\n\n        if (aeron_driver_conductor_subscription_link_matches_ipc_publication(link, publication) &&\n            aeron_ipc_publication_is_accepting_subscriptions(publication))\n        {\n            if (aeron_driver_conductor_link_subscribable(\n                conductor,\n                link,\n                &publication->conductor_fields.subscribable,\n                publication->conductor_fields.managed_resource.registration_id,\n                publication->session_id,\n                publication->stream_id,\n                aeron_ipc_publication_join_position(publication),\n                now_ns,\n                AERON_IPC_CHANNEL_LEN,\n                AERON_IPC_CHANNEL,\n                publication->log_file_name_length,\n                publication->log_file_name) < 0)\n            {\n                goto error_cleanup;\n            }\n        }\n    }\n\n    aeron_uri_close(&aeron_uri_params);\n    return 0;\n\nerror_cleanup:\n    aeron_uri_close(&aeron_uri_params);\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_receive_spy_destination_complete(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_command_t *async_command,\n    void *on_execute_clientd)\n{\n    aeron_destination_command_t *command = async_command->original_command;\n    aeron_udp_channel_async_parse_t *async_parse = on_execute_clientd;\n    aeron_udp_channel_t *udp_channel = async_parse->channel;\n\n    aeron_subscription_link_t *mds_subscription_link = NULL;\n    const char *command_uri = (const char *)command + sizeof(aeron_destination_command_t);\n    aeron_driver_uri_subscription_params_t params;\n\n    if (aeron_driver_uri_subscription_params(&udp_channel->uri, &params, conductor) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    mds_subscription_link = aeron_driver_conductor_find_mds_subscription(\n        conductor, command->correlated.client_id, command->registration_id);\n    if (NULL == mds_subscription_link)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, conductor->spy_subscriptions, aeron_subscription_link_t)\n    if (ensure_capacity_result < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    aeron_subscription_link_t *link = &conductor->spy_subscriptions.array[conductor->spy_subscriptions.length++];\n    aeron_driver_init_subscription_channel(command->channel_length, command_uri, link);\n    link->endpoint = NULL;\n    link->spy_channel = udp_channel;\n    link->stream_id = mds_subscription_link->stream_id;\n    link->has_session_id = params.has_session_id;\n    link->session_id = params.session_id;\n    link->client_id = command->correlated.client_id;\n    link->registration_id = mds_subscription_link->registration_id;\n    link->is_reliable = params.is_reliable;\n    link->is_sparse = params.is_sparse;\n    link->is_tether = params.is_tether;\n    link->is_rejoin = params.is_rejoin;\n    link->is_response = false;\n    link->group = AERON_INFER;\n    link->subscribable_list.length = 0;\n    link->subscribable_list.capacity = 0;\n    link->subscribable_list.array = NULL;\n\n    aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n\n    int64_t now_ns = aeron_clock_cached_nano_time(conductor->context->cached_clock);\n\n    for (size_t i = 0, length = conductor->network_publications.length; i < length; i++)\n    {\n        aeron_network_publication_t *publication = conductor->network_publications.array[i].publication;\n\n        if (aeron_driver_conductor_spy_subscription_link_matches(link, publication) &&\n            aeron_network_publication_is_accepting_subscriptions(publication))\n        {\n            if (aeron_driver_conductor_link_subscribable(\n                conductor,\n                link,\n                &publication->conductor_fields.subscribable,\n                publication->conductor_fields.managed_resource.registration_id,\n                publication->session_id,\n                publication->stream_id,\n                aeron_network_publication_join_position(publication),\n                now_ns,\n                AERON_IPC_CHANNEL_LEN,\n                AERON_IPC_CHANNEL,\n                publication->log_file_name_length,\n                publication->log_file_name) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                goto error_cleanup_skip_channel_delete;\n            }\n        }\n    }\n\n    return 0;\n\nerror_cleanup:\n    aeron_udp_channel_delete(udp_channel);\n\nerror_cleanup_skip_channel_delete:\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_receive_spy_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command)\n{\n    aeron_driver_async_client_command_t *async_client_command;\n\n    if (aeron_driver_async_client_command_allocate(\n        &async_client_command,\n        command,\n        sizeof(aeron_destination_command_t) + command->channel_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_udp_channel_do_initial_parse(\n        (size_t)command->channel_length - strlen(AERON_SPY_PREFIX),\n        (const char *)command + sizeof(aeron_destination_command_t) + strlen(AERON_SPY_PREFIX),\n        &async_client_command->async_command.async_parse) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    async_client_command->async_command.async_parse.is_destination = true;\n\n    async_client_command->correlated = &((aeron_destination_command_t *)async_client_command->async_command.original_command)->correlated;\n    async_client_command->on_execute = aeron_driver_async_parse_udp_channel_execute;\n    async_client_command->on_execute_clientd = &async_client_command->async_command.async_parse;\n    async_client_command->on_complete = aeron_driver_conductor_on_add_receive_spy_destination_complete;\n\n    if (aeron_driver_async_client_command_submit(conductor, async_client_command) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    return 0;\n\nerror_cleanup:\n    aeron_driver_async_client_command_free(async_client_command);\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_receive_network_destination_complete(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_command_t *async_command,\n    void *on_execute_clientd)\n{\n    aeron_destination_command_t *command = async_command->original_command;\n    aeron_udp_channel_async_parse_t *async_parse = on_execute_clientd;\n    aeron_udp_channel_t *udp_channel = async_parse->channel;\n\n    aeron_subscription_link_t *mds_subscription_link = NULL;\n    aeron_receive_channel_endpoint_t *endpoint = NULL;\n\n    mds_subscription_link = aeron_driver_conductor_find_mds_subscription(\n        conductor, command->correlated.client_id, command->registration_id);\n    if (NULL == mds_subscription_link)\n    {\n        goto error_cleanup;\n    }\n\n    endpoint = mds_subscription_link->endpoint;\n\n    if (aeron_driver_conductor_validate_destination_uri_params(&udp_channel->uri, udp_channel->uri_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    if (aeron_driver_conductor_update_and_check_ats_status(conductor->context, udp_channel, NULL) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    aeron_receive_destination_t *destination = NULL;\n\n    if (aeron_receive_destination_create(\n        &destination,\n        udp_channel,\n        endpoint->conductor_fields.udp_channel,\n        conductor->context,\n        &conductor->counters_manager,\n        command->registration_id,\n        endpoint->channel_status.counter_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    udp_channel = NULL;\n\n    aeron_driver_receiver_proxy_on_add_destination(conductor->context->receiver_proxy, endpoint, destination);\n    aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n\n    return 0;\n\nerror_cleanup:\n    aeron_udp_channel_delete(udp_channel);\n\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_receive_network_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command)\n{\n    aeron_driver_async_client_command_t *async_client_command;\n\n    if (aeron_driver_async_client_command_allocate(\n        &async_client_command,\n        command,\n        sizeof(aeron_destination_command_t) + command->channel_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_udp_channel_do_initial_parse(\n        (size_t)command->channel_length,\n        (const char *)command + sizeof(aeron_destination_command_t),\n        &async_client_command->async_command.async_parse) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    async_client_command->async_command.async_parse.is_destination = true;\n\n    async_client_command->correlated = &((aeron_destination_command_t *)async_client_command->async_command.original_command)->correlated;\n    async_client_command->on_execute = aeron_driver_async_parse_udp_channel_execute;\n    async_client_command->on_execute_clientd = &async_client_command->async_command.async_parse;\n    async_client_command->on_complete = aeron_driver_conductor_on_add_receive_network_destination_complete;\n\n    if (aeron_driver_async_client_command_submit(conductor, async_client_command) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    return 0;\n\nerror_cleanup:\n    aeron_driver_async_client_command_free(async_client_command);\n    return -1;\n}\n\nvoid aeron_driver_conductor_subscription_link_notify_unavailable_images(\n    aeron_driver_conductor_t *conductor, aeron_subscription_link_t *link)\n{\n    for (size_t i = 0; i < link->subscribable_list.length; i++)\n    {\n        aeron_subscribable_list_entry_t *entry = &link->subscribable_list.array[i];\n\n        aeron_driver_conductor_on_unavailable_image(\n            conductor,\n            entry->subscribable->correlation_id,\n            link->registration_id,\n            link->stream_id,\n            link->channel,\n            link->channel_length);\n    }\n}\n\nint aeron_driver_conductor_on_remove_receive_ipc_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command)\n{\n    const char *command_uri = (const char *)command + sizeof(aeron_destination_command_t);\n    aeron_subscription_link_t *subscription_link = NULL;\n\n    for (size_t i = 0, size = conductor->ipc_subscriptions.length, last_index = size - 1; i < size; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[i];\n\n        if (command->registration_id == link->registration_id &&\n            link->channel_length == command->channel_length &&\n            strncmp(link->channel, command_uri, link->channel_length) == 0)\n        {\n            aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n            aeron_driver_conductor_subscription_link_notify_unavailable_images(conductor, link);\n            aeron_driver_conductor_unlink_all_subscribable(conductor, link);\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)conductor->ipc_subscriptions.array, sizeof(aeron_subscription_link_t), i, last_index);\n            conductor->ipc_subscriptions.length--;\n            subscription_link = link;\n            break;\n        }\n    }\n\n    if (NULL == subscription_link)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_UNKNOWN_SUBSCRIPTION,\n            \"unknown subscription client_id=%\" PRId64 \" registration_id=%\" PRId64,\n            command->correlated.client_id,\n            command->registration_id);\n\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_remove_receive_spy_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command)\n{\n    const char *command_uri = (const char *)command + sizeof(aeron_destination_command_t);\n    aeron_subscription_link_t *subscription_link = NULL;\n\n    for (size_t i = 0, size = conductor->spy_subscriptions.length, last_index = size - 1; i < size; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->spy_subscriptions.array[i];\n\n        if (command->registration_id == link->registration_id &&\n            link->channel_length == command->channel_length &&\n            strncmp(link->channel, command_uri, link->channel_length) == 0)\n        {\n            aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n            aeron_driver_conductor_subscription_link_notify_unavailable_images(conductor, link);\n\n            aeron_driver_conductor_unlink_all_subscribable(conductor, link);\n            aeron_udp_channel_delete(link->spy_channel);\n            link->spy_channel = NULL;\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)conductor->spy_subscriptions.array, sizeof(aeron_subscription_link_t), i, last_index);\n            conductor->spy_subscriptions.length--;\n            subscription_link = link;\n            break;\n        }\n    }\n\n    if (NULL == subscription_link)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_UNKNOWN_SUBSCRIPTION,\n            \"unknown subscription client_id=%\" PRId64 \" registration_id=%\" PRId64,\n            command->correlated.client_id,\n            command->registration_id);\n\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_remove_receive_network_destination_complete(\n    aeron_driver_conductor_t *conductor,\n    aeron_driver_async_command_t *async_command,\n    void *on_execute_clientd)\n{\n    aeron_destination_command_t *command = async_command->original_command;\n    aeron_udp_channel_async_parse_t *async_parse = on_execute_clientd;\n    aeron_udp_channel_t *udp_channel = async_parse->channel;\n    aeron_subscription_link_t *mds_subscription_link = NULL;\n    aeron_receive_channel_endpoint_t *endpoint = NULL;\n\n    mds_subscription_link = aeron_driver_conductor_find_mds_subscription(\n        conductor, command->correlated.client_id, command->registration_id);\n    if (NULL == mds_subscription_link)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    endpoint = mds_subscription_link->endpoint;\n\n    aeron_driver_receiver_proxy_on_remove_destination(conductor->context->receiver_proxy, endpoint, udp_channel);\n    aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n\n    return 0;\n\nerror_cleanup:\n    aeron_udp_channel_delete(udp_channel);\n    return -1;\n}\n\nint aeron_driver_conductor_on_remove_receive_network_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command)\n{\n    aeron_driver_async_client_command_t *async_client_command;\n\n    if (aeron_driver_async_client_command_allocate(\n        &async_client_command,\n        command,\n        sizeof(aeron_destination_command_t) + command->channel_length) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_udp_channel_do_initial_parse(\n        (size_t)command->channel_length,\n        (const char *)command + sizeof(aeron_destination_command_t),\n        &async_client_command->async_command.async_parse) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    async_client_command->async_command.async_parse.is_destination = true;\n\n    async_client_command->correlated = &((aeron_destination_command_t *)async_client_command->async_command.original_command)->correlated;\n    async_client_command->on_execute = aeron_driver_async_parse_udp_channel_execute;\n    async_client_command->on_execute_clientd = &async_client_command->async_command.async_parse;\n    async_client_command->on_complete = aeron_driver_conductor_on_remove_receive_network_destination_complete;\n\n    if (aeron_driver_async_client_command_submit(conductor, async_client_command) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    return 0;\n\nerror_cleanup:\n    aeron_driver_async_client_command_free(async_client_command);\n    return -1;\n}\n\nvoid aeron_driver_conductor_on_delete_receive_destination(void *clientd, void *item)\n{\n    aeron_driver_conductor_t *conductor = (aeron_driver_conductor_t *)clientd;\n    aeron_command_delete_destination_t *command = (aeron_command_delete_destination_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)command->endpoint;\n    aeron_receive_destination_t *destination = (aeron_receive_destination_t *)command->destination;\n\n    endpoint->transport_bindings->close_func(&destination->transport);\n\n    aeron_udp_channel_delete((aeron_udp_channel_t *)command->channel);\n    aeron_receive_destination_delete(destination, &conductor->counters_manager);\n}\n\nvoid aeron_driver_conductor_on_delete_send_destination(void *clientd, void *cmd)\n{\n    aeron_command_base_t *command = (aeron_command_base_t *)cmd;\n    aeron_uri_t *uri = (aeron_uri_t *)command->item;\n\n    aeron_uri_close(uri);\n    aeron_free(uri);\n}\n\nint aeron_driver_conductor_on_add_counter(aeron_driver_conductor_t *conductor, aeron_counter_command_t *command)\n{\n    aeron_client_t *client = aeron_driver_conductor_get_or_add_client(conductor, command->correlated.client_id);\n    if (NULL == client)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to add client\");\n        return -1;\n    }\n\n    const uint8_t *cursor = (const uint8_t *)command + sizeof(aeron_counter_command_t);\n    int32_t key_length;\n\n    memcpy(&key_length, cursor, sizeof(key_length));\n    const uint8_t *key = cursor + sizeof(int32_t);\n\n    cursor = key + AERON_ALIGN(key_length, sizeof(int32_t));\n    int32_t label_length;\n\n    memcpy(&label_length, cursor, sizeof(label_length));\n    const char *label = (const char *)cursor + sizeof(int32_t);\n\n    const int32_t counter_id = aeron_counters_manager_allocate(\n        &conductor->counters_manager, command->type_id, key, (size_t)key_length, label, (size_t)label_length);\n\n    if (counter_id >= 0)\n    {\n        aeron_counters_manager_counter_registration_id(\n            &conductor->counters_manager, counter_id, command->correlated.correlation_id);\n\n        aeron_counters_manager_counter_owner_id(\n            &conductor->counters_manager, counter_id, command->correlated.client_id);\n\n        int ensure_capacity_result = 0;\n\n        AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, client->counter_links, aeron_counter_link_t)\n        if (ensure_capacity_result >= 0)\n        {\n            aeron_counter_link_t *link = &client->counter_links.array[client->counter_links.length++];\n\n            link->registration_id = command->correlated.correlation_id;\n            link->counter_id = counter_id;\n\n            aeron_driver_conductor_on_counter_ready(conductor, command->correlated.correlation_id, counter_id);\n            return 0;\n        }\n    }\n\n    return -1;\n}\n\nint aeron_driver_conductor_on_remove_counter(aeron_driver_conductor_t *conductor, aeron_remove_counter_command_t *command)\n{\n    int index = aeron_driver_conductor_find_client(conductor, command->correlated.client_id);\n    if (index >= 0)\n    {\n        aeron_client_t *client = &conductor->clients.array[index];\n\n        for (size_t i = 0, size = client->counter_links.length, last_index = size - 1; i < size; i++)\n        {\n            aeron_counter_link_t *link = &client->counter_links.array[i];\n\n            if (command->registration_id == link->registration_id)\n            {\n                aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n                aeron_driver_conductor_on_unavailable_counter(conductor, link->registration_id, link->counter_id);\n\n                aeron_counters_manager_free(&conductor->counters_manager, link->counter_id);\n\n                aeron_array_fast_unordered_remove(\n                    (uint8_t *)client->counter_links.array, sizeof(aeron_counter_link_t), i, last_index);\n                client->counter_links.length--;\n\n                return 0;\n            }\n        }\n    }\n\n    AERON_SET_ERR(\n        -AERON_ERROR_CODE_UNKNOWN_COUNTER,\n        \"unknown counter client_id=%\" PRId64 \" registration_id=%\" PRId64,\n        command->correlated.client_id,\n        command->registration_id);\n\n    return -1;\n}\n\nint aeron_driver_conductor_on_add_static_counter(\n    aeron_driver_conductor_t *conductor,\n    aeron_static_counter_command_t *command)\n{\n    aeron_client_t *client = aeron_driver_conductor_get_or_add_client(conductor, command->correlated.client_id);\n    if (NULL == client)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to add client\");\n        return -1;\n    }\n\n    aeron_counters_reader_t *reader = (aeron_counters_reader_t *)&conductor->counters_manager;\n\n    int32_t counter_id = aeron_counters_reader_find_by_type_id_and_registration_id(\n        reader, command->type_id, command->registration_id);\n    if (AERON_NULL_COUNTER_ID != counter_id)\n    {\n        int64_t owner_id;\n        aeron_counters_reader_counter_owner_id(reader, counter_id, &owner_id);\n        if (AERON_NULL_VALUE != owner_id)\n        {\n            AERON_SET_ERR(-AERON_ERROR_CODE_GENERIC_ERROR,\n                \"cannot add static counter, because a non-static counter exists (counterId=%\" PRIi32 \") for typeId=%\"\n                PRIi32 \" and registrationId=%\" PRIi64 \"\", counter_id, command->type_id, command->registration_id);\n            return -1;\n        }\n    }\n    else\n    {\n        const uint8_t *cursor = (const uint8_t *)command + sizeof(aeron_static_counter_command_t);\n        int32_t key_length;\n\n        memcpy(&key_length, cursor, sizeof(key_length));\n        const uint8_t *key = cursor + sizeof(int32_t);\n\n        cursor = key + AERON_ALIGN(key_length, sizeof(int32_t));\n        int32_t label_length;\n\n        memcpy(&label_length, cursor, sizeof(label_length));\n        const char *label = (const char *)cursor + sizeof(int32_t);\n\n        counter_id = aeron_counters_manager_allocate(\n            &conductor->counters_manager, command->type_id, key, (size_t)key_length, label, (size_t)label_length);\n\n        if (counter_id < 0)\n        {\n            return -1;\n        }\n\n        aeron_counters_manager_counter_registration_id(\n            &conductor->counters_manager, counter_id, command->registration_id);\n\n        aeron_counters_manager_counter_owner_id(\n            &conductor->counters_manager, counter_id, AERON_NULL_VALUE);\n\n        int64_t saved_registration_id;\n        aeron_counters_reader_counter_registration_id(reader, counter_id, &saved_registration_id);\n\n        int64_t saved_owner_id;\n        aeron_counters_reader_counter_owner_id(reader, counter_id, &saved_owner_id);\n    }\n\n    aeron_driver_conductor_on_static_counter(conductor, command->correlated.correlation_id, counter_id);\n    return 0;\n}\n\nint aeron_driver_conductor_on_client_close(aeron_driver_conductor_t *conductor, aeron_correlated_command_t *command)\n{\n    int index = aeron_driver_conductor_find_client(conductor, command->client_id);\n    if (index >= 0)\n    {\n        aeron_client_t *client = &conductor->clients.array[index];\n\n        client->closed_by_command = true;\n        aeron_counter_set_release(client->heartbeat_timestamp.value_addr, 0);\n    }\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_terminate_driver(\n    aeron_driver_conductor_t *conductor, aeron_terminate_driver_command_t *command)\n{\n    aeron_driver_context_t *ctx = conductor->context;\n    bool is_validated = false;\n\n    if (NULL != ctx->termination_validator_func)\n    {\n        uint8_t *token_buffer = (uint8_t *)command + sizeof(aeron_terminate_driver_command_t);\n\n        is_validated = ctx->termination_validator_func(\n            ctx->termination_validator_state, token_buffer, command->token_length);\n    }\n\n    if (NULL != ctx->termination_hook_func && is_validated)\n    {\n        ctx->termination_hook_func(ctx->termination_hook_state);\n    }\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_invalidate_image(\n    aeron_driver_conductor_t *conductor, aeron_reject_image_command_t *command)\n{\n    const int64_t image_correlation_id = command->image_correlation_id;\n\n    if (AERON_ERROR_MAX_TEXT_LENGTH < command->reason_length)\n    {\n        AERON_SET_ERR(AERON_ERROR_CODE_GENERIC_ERROR, \"%s\", \"Invalidation reason_text must be 1023 bytes or less\");\n        return -1;\n    }\n\n    aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image(\n        conductor, image_correlation_id);\n\n    if (NULL == image)\n    {\n        aeron_ipc_publication_t *found_publication = NULL;\n\n        for (size_t i = 0; i < conductor->ipc_publications.length; i++)\n        {\n            aeron_ipc_publication_t *pub_entry = conductor->ipc_publications.array[i].publication;\n\n            if (image_correlation_id == pub_entry->conductor_fields.managed_resource.registration_id)\n            {\n                found_publication = pub_entry;\n                break;\n            }\n        }\n\n        if (NULL == found_publication)\n        {\n            AERON_SET_ERR(\n                AERON_ERROR_CODE_GENERIC_ERROR,\n                \"Unable to resolve image for correlationId=%\" PRId64, image_correlation_id);\n            return -1;\n        }\n\n        const char *reason_text = (const char *)command->reason_text;\n        aeron_ipc_publication_reject(\n            found_publication,\n            command->position,\n            command->reason_length,\n            reason_text,\n            conductor,\n            aeron_clock_cached_nano_time(conductor->context->cached_clock));\n    }\n    else\n    {\n        const char *reason_text = (const char *)command->reason_text;\n        aeron_driver_receiver_proxy_on_invalidate_image(\n            conductor->context->receiver_proxy,\n            image_correlation_id,\n            command->position,\n            command->reason_length,\n            reason_text);\n    }\n\n    aeron_counter_increment_release(conductor->images_rejected_counter);\n\n    aeron_driver_conductor_on_operation_succeeded(conductor, command->correlated.correlation_id);\n\n    return 0;\n}\n\nint aeron_driver_conductor_on_get_next_available_session_id(\n    aeron_driver_conductor_t *conductor, aeron_get_next_available_session_id_command_t *command)\n{\n    const int32_t stream_id = command->stream_id;\n    outer: while (true)\n    {\n        const int32_t next_session_id = aeron_driver_conductor_next_session_id(conductor);\n        aeron_driver_conductor_update_next_session_id(conductor, next_session_id);\n\n        for (size_t i = 0; i < conductor->ipc_publications.length; i++)\n        {\n            aeron_ipc_publication_t *pub_entry = conductor->ipc_publications.array[i].publication;\n\n            if (stream_id == pub_entry->stream_id && next_session_id == pub_entry->session_id)\n            {\n                goto outer;\n            }\n        }\n\n        for (size_t i = 0; i < conductor->network_publications.length; i++)\n        {\n            aeron_network_publication_t *pub_entry = conductor->network_publications.array[i].publication;\n            if (stream_id == pub_entry->stream_id && next_session_id == pub_entry->session_id)\n            {\n                goto outer;\n            }\n        }\n\n        aeron_driver_conductor_response_next_available_session_id(\n            conductor, command->correlated.correlation_id, next_session_id);\n        return 0;\n    }\n}\n\nvoid aeron_driver_conductor_unlink_ipc_subscriptions(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_t *publication)\n{\n    for (size_t i = 0, size = conductor->ipc_subscriptions.length; i < size; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[i];\n\n        if (aeron_driver_conductor_is_subscribable_linked(link, &publication->conductor_fields.subscribable))\n        {\n            aeron_driver_conductor_on_unavailable_image(\n                conductor,\n                publication->conductor_fields.managed_resource.registration_id,\n                link->registration_id,\n                link->stream_id,\n                link->channel,\n                link->channel_length);\n\n            aeron_driver_conductor_unlink_subscribable(link, &publication->conductor_fields.subscribable);\n        }\n    }\n}\n\nvoid aeron_driver_conductor_on_create_publication_image(void *clientd, void *item)\n{\n    aeron_driver_conductor_t *conductor = (aeron_driver_conductor_t *)clientd;\n    aeron_command_create_publication_image_t *command = (aeron_command_create_publication_image_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = command->endpoint;\n    aeron_receive_destination_t *destination = command->destination;\n\n    size_t initial_window_length = aeron_udp_channel_receiver_window(\n        endpoint->conductor_fields.udp_channel, conductor->context->initial_window_length);\n\n    if (aeron_receiver_channel_endpoint_validate_sender_mtu_length(\n        endpoint, (size_t)command->mtu_length, initial_window_length, conductor->context) < 0)\n    {\n        AERON_APPEND_ERR(\"stream_id=%d session_id=%d\", command->stream_id, command->session_id);\n        goto error_cleanup;\n    }\n\n    if (!aeron_driver_conductor_has_network_subscription_interest(\n        conductor, endpoint, command->stream_id, command->session_id))\n    {\n        aeron_driver_receiver_proxy_on_remove_init_in_progress(\n            conductor->context->receiver_proxy, endpoint, command->session_id, command->stream_id);\n        return;\n    }\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, conductor->publication_images, aeron_publication_image_entry_t)\n    if (ensure_capacity_result < 0)\n    {\n        AERON_APPEND_ERR(\"stream_id=%d session_id=%d\", command->stream_id, command->session_id);\n        goto error_cleanup;\n    }\n\n    const int64_t registration_id = aeron_mpsc_rb_next_correlation_id(&conductor->to_driver_commands);\n    const int64_t join_position = aeron_logbuffer_compute_position(\n        command->active_term_id,\n        command->term_offset,\n        (size_t)aeron_number_of_trailing_zeroes(command->term_length),\n        command->initial_term_id);\n\n    aeron_congestion_control_strategy_t *congestion_control = NULL;\n    if (conductor->context->congestion_control_supplier_func(\n        &congestion_control,\n        endpoint->conductor_fields.udp_channel,\n        command->stream_id,\n        command->session_id,\n        registration_id,\n        command->term_length,\n        command->mtu_length,\n        &command->control_address,\n        &command->src_address,\n        conductor->context,\n        &conductor->counters_manager) < 0)\n    {\n        AERON_APPEND_ERR(\"stream_id=%d session_id=%d\", command->stream_id, command->session_id);\n        goto error_cleanup;\n    }\n\n    aeron_subscription_link_t subscription_link = conductor->network_subscriptions.array[0];\n    for (size_t i = 0; i < conductor->network_subscriptions.length; i++)\n    {\n        if (aeron_driver_conductor_network_subscription_link_matches_allowing_wildcard(\n            &conductor->network_subscriptions.array[i], endpoint, command->stream_id, command->session_id))\n        {\n            subscription_link = conductor->network_subscriptions.array[i];\n            break;\n        }\n    }\n\n    const char *uri = subscription_link.channel;\n    size_t uri_length = subscription_link.channel_length;\n\n    aeron_position_t rcv_hwm_position;\n    rcv_hwm_position.counter_id = aeron_counter_receiver_hwm_allocate(\n        &conductor->counters_manager,\n        subscription_link.client_id,\n        registration_id,\n        command->session_id,\n        command->stream_id,\n        uri_length,\n        uri);\n    if (rcv_hwm_position.counter_id < 0)\n    {\n        AERON_APPEND_ERR(\"stream_id=%d session_id=%d\", command->stream_id, command->session_id);\n        goto error_cleanup;\n    }\n\n    aeron_position_t rcv_pos_position;\n    rcv_pos_position.counter_id = aeron_counter_receiver_position_allocate(\n        &conductor->counters_manager,\n        subscription_link.client_id,\n        registration_id,\n        command->session_id,\n        command->stream_id,\n        uri_length,\n        uri);\n    if (rcv_pos_position.counter_id < 0)\n    {\n        aeron_counters_manager_free(&conductor->counters_manager, rcv_hwm_position.counter_id);\n        AERON_APPEND_ERR(\"stream_id=%d session_id=%d\", command->stream_id, command->session_id);\n        goto error_cleanup;\n    }\n\n    aeron_atomic_counter_t rcv_naks_sent;\n    rcv_naks_sent.counter_id = aeron_counter_receiver_naks_sent_allocate(\n        &conductor->counters_manager,\n        subscription_link.client_id,\n        registration_id,\n        command->session_id,\n        command->stream_id,\n        uri_length,\n        uri);\n    if (rcv_naks_sent.counter_id < 0)\n    {\n        aeron_counters_manager_free(&conductor->counters_manager, rcv_hwm_position.counter_id);\n        aeron_counters_manager_free(&conductor->counters_manager, rcv_pos_position.counter_id);\n        AERON_APPEND_ERR(\"stream_id=%d session_id=%d\", command->stream_id, command->session_id);\n        goto error_cleanup;\n    }\n\n    rcv_hwm_position.value_addr = aeron_counters_manager_addr(\n        &conductor->counters_manager, rcv_hwm_position.counter_id);\n    rcv_pos_position.value_addr = aeron_counters_manager_addr(\n        &conductor->counters_manager, rcv_pos_position.counter_id);\n    rcv_naks_sent.value_addr = aeron_counters_manager_addr(\n        &conductor->counters_manager, rcv_naks_sent.counter_id);\n\n    bool is_reliable = subscription_link.is_reliable;\n    bool treat_as_multicast = aeron_driver_conductor_treat_image_as_multicast(\n        endpoint->conductor_fields.udp_channel, command->flags, subscription_link.group);\n    bool is_oldest_subscription_sparse = aeron_driver_conductor_is_oldest_subscription_sparse(\n        conductor, endpoint, command->stream_id, command->session_id, registration_id);\n\n    aeron_publication_image_t *image = NULL;\n    if (aeron_publication_image_create(\n        &image,\n        endpoint,\n        destination,\n        conductor,\n        registration_id,\n        command->session_id,\n        command->stream_id,\n        command->initial_term_id,\n        command->active_term_id,\n        command->term_offset,\n        &rcv_hwm_position,\n        &rcv_pos_position,\n        &rcv_naks_sent,\n        congestion_control,\n        &command->control_address,\n        &command->src_address,\n        command->term_length,\n        command->mtu_length,\n        command->flags,\n        &conductor->loss_reporter,\n        is_reliable,\n        is_oldest_subscription_sparse,\n        treat_as_multicast,\n        &conductor->system_counters) < 0)\n    {\n        aeron_counters_manager_free(&conductor->counters_manager, rcv_hwm_position.counter_id);\n        aeron_counters_manager_free(&conductor->counters_manager, rcv_pos_position.counter_id);\n        aeron_counters_manager_free(&conductor->counters_manager, rcv_naks_sent.counter_id);\n        AERON_APPEND_ERR(\"stream_id=%d session_id=%d\", command->stream_id, command->session_id);\n        goto error_cleanup;\n    }\n\n    aeron_receive_channel_endpoint_inc_image_ref_count(endpoint);\n    conductor->publication_images.array[conductor->publication_images.length++].image = image;\n    int64_t now_ns = aeron_clock_cached_nano_time(conductor->context->cached_clock);\n\n    for (size_t i = 0, length = conductor->network_subscriptions.length; i < length; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n\n        if (!aeron_driver_conductor_network_subscription_link_matches_allowing_wildcard(\n            link,\n            endpoint,\n            command->stream_id,\n            command->session_id))\n        {\n            continue;\n        }\n\n        if (aeron_driver_conductor_link_subscribable(\n            conductor,\n            link,\n            &image->conductor_fields.subscribable,\n            registration_id,\n            command->session_id,\n            command->stream_id,\n            join_position,\n            now_ns,\n            image->source_identity_length,\n            image->source_identity,\n            image->log_file_name_length,\n            image->log_file_name) < 0)\n        {\n            AERON_APPEND_ERR(\"stream_id=%d session_id=%d\", command->stream_id, command->session_id);\n            goto error_cleanup;\n        }\n    }\n\n    aeron_driver_receiver_proxy_on_add_publication_image(conductor->context->receiver_proxy, endpoint, image);\n    return;\n\nerror_cleanup:\n    aeron_driver_conductor_log_error(conductor);\n    aeron_driver_receiver_proxy_on_remove_init_in_progress(\n        conductor->context->receiver_proxy, endpoint, command->session_id, command->stream_id);\n}\n\nvoid aeron_driver_conductor_on_re_resolve_endpoint_complete(\n    int result,\n    int errcode,\n    const char *errmsg,\n    void *task_clientd,\n    void *executor_clientd)\n{\n    aeron_async_re_resolve_t *async_cmd = task_clientd;\n    aeron_driver_conductor_t *conductor = executor_clientd;\n\n    if (result < 0)\n    {\n        aeron_driver_conductor_log_explicit_error(conductor, errcode, errmsg);\n    }\n    else if (0 != memcmp(\n        &async_cmd->async_resolve.sockaddr,\n        &async_cmd->existing_addr,\n        sizeof(struct sockaddr_storage)))\n    {\n        aeron_driver_sender_proxy_on_resolution_change(\n            conductor->context->sender_proxy,\n            async_cmd->async_resolve.endpoint_name,\n            async_cmd->endpoint,\n            &async_cmd->async_resolve.sockaddr);\n    }\n\n    aeron_free(async_cmd);\n}\n\nvoid aeron_driver_conductor_on_re_resolve_cancel(void *task_clientd, void *executor_clientd)\n{\n    aeron_async_re_resolve_t *async_cmd = task_clientd;\n    aeron_free(async_cmd);\n}\n\nvoid aeron_driver_conductor_on_re_resolve_endpoint(void *clientd, void *item)\n{\n    aeron_driver_conductor_t *conductor = clientd;\n    aeron_command_re_resolve_t *cmd = item;\n    struct sockaddr_storage resolved_addr;\n    memset(&resolved_addr, 0, sizeof(resolved_addr));\n    aeron_send_channel_endpoint_t *endpoint = cmd->endpoint;\n\n    if (AERON_SEND_CHANNEL_ENDPOINT_STATUS_ACTIVE != endpoint->conductor_fields.status)\n    {\n        return;\n    }\n\n    aeron_async_re_resolve_t *async_cmd;\n\n    if (aeron_alloc((void **)&async_cmd, sizeof(aeron_async_re_resolve_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return;\n    }\n\n    async_cmd->async_resolve.uri_param_name = AERON_UDP_CHANNEL_ENDPOINT_KEY;\n    async_cmd->async_resolve.is_re_resolution = true;\n    COPY_ENDPOINT_NAME(async_cmd->async_resolve.endpoint_name, cmd->endpoint_name);\n\n    memcpy(&async_cmd->existing_addr, &cmd->existing_addr, sizeof(cmd->existing_addr));\n    async_cmd->endpoint = endpoint;\n    async_cmd->destination = NULL;\n\n    if (aeron_executor_submit(\n        &conductor->executor,\n        aeron_driver_conductor_async_resolve_host_and_port_execute,\n        aeron_driver_conductor_on_re_resolve_endpoint_complete,\n        aeron_driver_conductor_on_re_resolve_cancel,\n        async_cmd) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_driver_conductor_log_error(conductor);\n        aeron_free(async_cmd);\n    }\n}\n\nvoid aeron_driver_conductor_on_re_resolve_control_complete(\n    int result,\n    int errcode,\n    const char *errmsg,\n    void *task_clientd,\n    void *executor_clientd)\n{\n    aeron_async_re_resolve_t *async_cmd = task_clientd;\n    aeron_driver_conductor_t *conductor = executor_clientd;\n\n    if (result < 0)\n    {\n        aeron_driver_conductor_log_explicit_error(conductor, errcode, errmsg);\n    }\n    else if (0 != memcmp(\n        &async_cmd->async_resolve.sockaddr,\n        &async_cmd->existing_addr,\n        sizeof(struct sockaddr_storage)))\n    {\n        aeron_driver_receiver_proxy_on_resolution_change(\n            conductor->context->receiver_proxy,\n            async_cmd->async_resolve.endpoint_name,\n            async_cmd->endpoint,\n            async_cmd->destination,\n            &async_cmd->async_resolve.sockaddr);\n    }\n\n    aeron_free(async_cmd);\n}\n\nvoid aeron_driver_conductor_on_re_resolve_control(void *clientd, void *item)\n{\n    aeron_driver_conductor_t *conductor = clientd;\n    aeron_command_re_resolve_t *cmd = item;\n    struct sockaddr_storage resolved_addr;\n    memset(&resolved_addr, 0, sizeof(resolved_addr));\n\n    aeron_async_re_resolve_t *async_cmd;\n\n    if (aeron_alloc((void **)&async_cmd, sizeof(aeron_async_re_resolve_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return;\n    }\n\n    async_cmd->async_resolve.uri_param_name = AERON_UDP_CHANNEL_CONTROL_KEY;\n    async_cmd->async_resolve.is_re_resolution = true;\n    COPY_ENDPOINT_NAME(async_cmd->async_resolve.endpoint_name, cmd->endpoint_name);\n\n    memcpy(&async_cmd->existing_addr, &cmd->existing_addr, sizeof(cmd->existing_addr));\n    async_cmd->endpoint = cmd->endpoint;\n    async_cmd->destination = cmd->destination;\n\n    if (aeron_executor_submit(\n        &conductor->executor,\n        aeron_driver_conductor_async_resolve_host_and_port_execute,\n        aeron_driver_conductor_on_re_resolve_control_complete,\n        aeron_driver_conductor_on_re_resolve_cancel,\n        async_cmd) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_driver_conductor_log_error(conductor);\n        aeron_free(async_cmd);\n    }\n}\n\nvoid aeron_driver_conductor_on_receive_endpoint_removed(void *clientd, void *item)\n{\n    aeron_driver_conductor_t *conductor = clientd;\n    aeron_command_base_t *cmd = item;\n    aeron_receive_channel_endpoint_t *endpoint = cmd->item;\n\n    if (endpoint->conductor_fields.status == AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_CLOSING)\n    {\n        aeron_udp_channel_t *udp_channel = endpoint->conductor_fields.udp_channel;\n\n        aeron_str_to_ptr_hash_map_remove(\n            &conductor->receive_channel_endpoint_by_channel_map,\n            udp_channel->canonical_form,\n            udp_channel->canonical_length);\n\n        aeron_receive_channel_endpoint_close(endpoint);\n        aeron_receive_channel_endpoint_receiver_release(endpoint);\n    }\n}\n\nvoid aeron_driver_conductor_on_response_setup(void *clientd, void *item)\n{\n    aeron_driver_conductor_t *conductor = clientd;\n    aeron_command_response_setup_t *cmd = item;\n    const int64_t response_correlation_id = cmd->response_correlation_id;\n    const int32_t response_session_id = cmd->response_session_id;\n\n    for (size_t i = 0, length = conductor->network_subscriptions.length; i < length; i++)\n    {\n        aeron_subscription_link_t *subscription_link = &conductor->network_subscriptions.array[i];\n        if (subscription_link->registration_id == response_correlation_id)\n        {\n            if (subscription_link->has_session_id)\n            {\n                aeron_driver_receiver_proxy_on_request_setup(\n                    subscription_link->endpoint->receiver_proxy,\n                    subscription_link->endpoint,\n                    subscription_link->stream_id,\n                    subscription_link->session_id);\n            }\n            else\n            {\n                subscription_link->has_session_id = true;\n                subscription_link->session_id = response_session_id;\n                subscription_link->is_response = false;\n\n                aeron_driver_conductor_add_network_subscription_to_receiver(\n                    subscription_link->endpoint,\n                    subscription_link->stream_id,\n                    subscription_link->has_session_id,\n                    subscription_link->session_id);\n                aeron_receive_channel_endpoint_decref_to_response_stream(\n                    subscription_link->endpoint, subscription_link->stream_id);\n            }\n        }\n    }\n}\n\nvoid aeron_driver_conductor_on_response_connected(void *clientd, void *item)\n{\n    aeron_driver_conductor_t *conductor = clientd;\n    aeron_command_response_connected_t *cmd = item;\n    const int64_t response_correlation_id = cmd->response_correlation_id;\n\n    for (size_t i = 0, length = conductor->publication_images.length; i < length; i++)\n    {\n        aeron_publication_image_t *publication_image = conductor->publication_images.array[i].image;\n        if (response_correlation_id == aeron_publication_image_registration_id(publication_image))\n        {\n            aeron_publication_image_remove_response_session_id(publication_image);\n        }\n    }\n}\n\nvoid aeron_driver_conductor_on_release_resource(void *clientd, void *item)\n{\n    aeron_driver_conductor_t *conductor = clientd;\n    aeron_command_release_resource_t *cmd = item;\n    void *managed_resource = cmd->base.item;\n    aeron_driver_conductor_resource_type_t resource_type = cmd->resource_type;\n\n    switch (resource_type)\n    {\n        case AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_CLIENT:\n        case AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_IPC_PUBLICATION:\n            break;\n\n        case AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_NETWORK_PUBLICATION:\n            aeron_network_publication_sender_release(managed_resource);\n            break;\n\n        case AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_SEND_CHANNEL_ENDPOINT:\n        {\n            aeron_send_channel_endpoint_t *endpoint = (aeron_send_channel_endpoint_t *)managed_resource;\n\n            if (endpoint->conductor_fields.status == AERON_SEND_CHANNEL_ENDPOINT_STATUS_CLOSING)\n            {\n                aeron_str_to_ptr_hash_map_remove(\n                    &conductor->send_channel_endpoint_by_channel_map,\n                    endpoint->conductor_fields.udp_channel->canonical_form,\n                    endpoint->conductor_fields.udp_channel->canonical_length);\n\n                aeron_send_channel_endpoint_close(endpoint);\n                aeron_send_channel_endpoint_sender_release(managed_resource);\n            }\n\n            break;\n        }\n\n        case AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_PUBLICATION_IMAGE:\n            aeron_publication_image_receiver_release(managed_resource);\n            break;\n\n        case AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_LINGER_RESOURCE:\n            break;\n    }\n\n}\n\n\nextern void aeron_driver_subscribable_null_hook(void *clientd, volatile int64_t *value_addr);\n\nextern bool aeron_driver_conductor_is_subscribable_linked(\n    aeron_subscription_link_t *link, aeron_subscribable_t *subscribable);\n\nextern size_t aeron_driver_conductor_num_clients(aeron_driver_conductor_t *conductor);\n\nextern size_t aeron_driver_conductor_num_ipc_publications(aeron_driver_conductor_t *conductor);\n\nextern size_t aeron_driver_conductor_num_ipc_subscriptions(aeron_driver_conductor_t *conductor);\n\nextern size_t aeron_driver_conductor_num_network_publications(aeron_driver_conductor_t *conductor);\n\nextern size_t aeron_driver_conductor_num_network_subscriptions(aeron_driver_conductor_t *conductor);\n\nextern size_t aeron_driver_conductor_num_spy_subscriptions(aeron_driver_conductor_t *conductor);\n\nextern size_t aeron_driver_conductor_num_send_channel_endpoints(aeron_driver_conductor_t *conductor);\n\nextern size_t aeron_driver_conductor_num_receive_channel_endpoints(aeron_driver_conductor_t *conductor);\n\nextern size_t aeron_driver_conductor_num_active_ipc_subscriptions(\n    aeron_driver_conductor_t *conductor, int32_t stream_id);\n\nextern size_t aeron_driver_conductor_num_active_network_subscriptions(\n    aeron_driver_conductor_t *conductor, const char *original_uri, int32_t stream_id);\n\nextern size_t aeron_driver_conductor_num_active_spy_subscriptions(\n    aeron_driver_conductor_t *conductor, const char *original_uri, int32_t stream_id);\n\nextern size_t aeron_driver_conductor_num_images(aeron_driver_conductor_t *conductor);\n\nextern aeron_ipc_publication_t *aeron_driver_conductor_find_ipc_publication(\n    aeron_driver_conductor_t *conductor, int64_t id);\n\nextern aeron_network_publication_t *aeron_driver_conductor_find_network_publication(\n    aeron_driver_conductor_t *conductor, int64_t id);\n\nextern aeron_network_publication_t *aeron_driver_conductor_find_network_publication_by_tag(\n    aeron_driver_conductor_t *conductor, int64_t tag_id);\n\nextern void aeron_driver_init_subscription_channel(size_t uri_length, const char *uri, aeron_subscription_link_t *link);\n\nextern void aeron_duty_cycle_stall_tracker_update(void *state, int64_t now_ns);\n\nextern void aeron_duty_cycle_stall_tracker_measure_and_update(void *state, int64_t now_ns);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_conductor.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_DRIVER_CONDUCTOR_H\n#define AERON_DRIVER_CONDUCTOR_H\n\n#include \"aeron_driver_common.h\"\n#include \"aeron_driver_context.h\"\n#include \"uri/aeron_uri.h\"\n#include \"concurrent/aeron_mpsc_rb.h\"\n#include \"concurrent/aeron_broadcast_transmitter.h\"\n#include \"concurrent/aeron_distinct_error_log.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"concurrent/aeron_executor.h\"\n#include \"command/aeron_control_protocol.h\"\n#include \"aeron_system_counters.h\"\n#include \"aeron_ipc_publication.h\"\n#include \"collections/aeron_str_to_ptr_hash_map.h\"\n#include \"media/aeron_send_channel_endpoint.h\"\n#include \"media/aeron_receive_channel_endpoint.h\"\n#include \"aeron_driver_conductor_proxy.h\"\n#include \"aeron_publication_image.h\"\n#include \"reports/aeron_loss_reporter.h\"\n#include \"util/aeron_deque.h\"\n\n#define AERON_DRIVER_CONDUCTOR_LINGER_RESOURCE_TIMEOUT_NS (5 * 1000 * 1000 * 1000LL)\n#define AERON_DRIVER_CONDUCTOR_CLOCK_UPDATE_INTERNAL_NS (1000 * 1000LL)\n\ntypedef struct aeron_publication_link_stct\n{\n    aeron_driver_managed_resource_t *resource;\n    int64_t registration_id;\n}\naeron_publication_link_t;\n\n#define AERON_PUBLICATION_LINK_INIT(_link, _resource, _registration_id) \\\ndo {                                                                    \\\n    link->resource = _resource;                                         \\\n    link->registration_id = registration_id;                            \\\n} while (0)\n\ntypedef struct aeron_counter_link_stct\n{\n    int32_t counter_id;\n    int64_t registration_id;\n}\naeron_counter_link_t;\n\ntypedef struct aeron_client_stct\n{\n    bool reached_end_of_life;\n    bool closed_by_command;\n    int64_t client_id;\n    int64_t client_liveness_timeout_ms;\n\n    aeron_atomic_counter_t heartbeat_timestamp;\n\n    struct publication_link_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_publication_link_t *array;\n    }\n    publication_links;\n\n    struct counter_link_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_counter_link_t *array;\n    }\n    counter_links;\n}\naeron_client_t;\n\ntypedef struct aeron_subscribable_list_entry_stct\n{\n    int32_t counter_id;\n    aeron_subscribable_t *subscribable;\n}\naeron_subscribable_list_entry_t;\n\ntypedef struct aeron_subscription_link_stct\n{\n    char channel[AERON_URI_MAX_LENGTH];\n    bool is_tether;\n    bool is_sparse;\n    bool is_reliable;\n    bool is_rejoin;\n    bool has_session_id;\n    bool is_response;\n    aeron_inferable_boolean_t group;\n    int32_t stream_id;\n    int32_t session_id;\n    int32_t channel_length;\n    int64_t registration_id;\n    int64_t client_id;\n\n    aeron_receive_channel_endpoint_t *endpoint;\n    aeron_udp_channel_t *spy_channel;\n\n    struct subscribable_list_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_subscribable_list_entry_t *array;\n    }\n    subscribable_list;\n}\naeron_subscription_link_t;\n\ntypedef struct aeron_ipc_publication_entry_stct\n{\n    aeron_ipc_publication_t *publication;\n}\n aeron_ipc_publication_entry_t;\n\ntypedef struct aeron_network_publication_entry_stct\n{\n    aeron_network_publication_t *publication;\n}\naeron_network_publication_entry_t;\n\ntypedef struct aeron_send_channel_endpoint_entry_stct\n{\n    aeron_send_channel_endpoint_t *endpoint;\n}\naeron_send_channel_endpoint_entry_t;\n\ntypedef struct aeron_receive_channel_endpoint_entry_stct\n{\n    aeron_receive_channel_endpoint_t *endpoint;\n}\naeron_receive_channel_endpoint_entry_t;\n\ntypedef struct aeron_publication_image_entry_stct\n{\n    aeron_publication_image_t *image;\n}\naeron_publication_image_entry_t;\n\ntypedef struct aeron_linger_resource_entry_stct\n{\n    bool has_reached_end_of_life;\n    uint8_t *buffer;\n    int64_t timeout_ns;\n}\naeron_linger_resource_entry_t;\n\ntypedef bool (*aeron_end_of_life_resource_free_t)(void *resource);\n\nstruct aeron_end_of_life_resource_stct\n{\n    void *resource;\n    aeron_end_of_life_resource_free_t free_func;\n};\ntypedef struct aeron_end_of_life_resource_stct aeron_end_of_life_resource_t;\n\ntypedef struct aeron_driver_conductor_stct aeron_driver_conductor_t;\n\ntypedef struct aeron_driver_conductor_stct\n{\n    aeron_driver_context_t *context;\n    aeron_mpsc_rb_t to_driver_commands;\n    aeron_broadcast_transmitter_t to_clients;\n    aeron_distinct_error_log_t error_log;\n    aeron_counters_manager_t counters_manager;\n    aeron_system_counters_t system_counters;\n    aeron_driver_conductor_proxy_t conductor_proxy;\n    aeron_loss_reporter_t loss_reporter;\n    aeron_name_resolver_t name_resolver;\n    aeron_executor_t executor;\n\n    aeron_str_to_ptr_hash_map_t send_channel_endpoint_by_channel_map;\n    aeron_str_to_ptr_hash_map_t receive_channel_endpoint_by_channel_map;\n\n    struct client_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_client_t *array;\n        void (*on_time_event)(aeron_driver_conductor_t *, aeron_client_t *, int64_t, int64_t);\n        bool (*has_reached_end_of_life)(aeron_driver_conductor_t *, aeron_client_t *);\n        void (*delete_func)(aeron_driver_conductor_t *, aeron_client_t *);\n        void (*free_func)(aeron_driver_conductor_t *, aeron_client_t *);\n    }\n    clients;\n\n    struct ipc_subscriptions_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_subscription_link_t *array;\n    }\n    ipc_subscriptions;\n\n    struct ipc_publication_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_ipc_publication_entry_t *array;\n        void (*on_time_event)(aeron_driver_conductor_t *, aeron_ipc_publication_entry_t *, int64_t, int64_t);\n        bool (*has_reached_end_of_life)(aeron_driver_conductor_t *, aeron_ipc_publication_entry_t *);\n        void (*delete_func)(aeron_driver_conductor_t *, aeron_ipc_publication_entry_t *);\n        void (*free_func)(aeron_driver_conductor_t *, aeron_ipc_publication_entry_t *);\n    }\n    ipc_publications;\n\n    struct network_subscriptions_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_subscription_link_t *array;\n    }\n    network_subscriptions;\n\n    struct spy_subscriptions_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_subscription_link_t *array;\n    }\n    spy_subscriptions;\n\n    struct network_publication_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_network_publication_entry_t *array;\n        void (*on_time_event)(aeron_driver_conductor_t *, aeron_network_publication_entry_t *, int64_t, int64_t);\n        bool (*has_reached_end_of_life)(aeron_driver_conductor_t *, aeron_network_publication_entry_t *);\n        void (*delete_func)(aeron_driver_conductor_t *, aeron_network_publication_entry_t *);\n        void (*free_func)(aeron_driver_conductor_t *, aeron_network_publication_entry_t *);\n    }\n    network_publications;\n\n    struct send_channel_endpoint_stct\n    {\n        aeron_send_channel_endpoint_entry_t *array;\n        size_t length;\n        size_t capacity;\n        void (*on_time_event)(aeron_driver_conductor_t *, aeron_send_channel_endpoint_entry_t *, int64_t, int64_t);\n        bool (*has_reached_end_of_life)(aeron_driver_conductor_t *, aeron_send_channel_endpoint_entry_t *);\n        void (*delete_func)(aeron_driver_conductor_t *, aeron_send_channel_endpoint_entry_t *);\n        void (*free_func)(aeron_driver_conductor_t *, aeron_send_channel_endpoint_entry_t *);\n    }\n    send_channel_endpoints;\n\n    struct receive_channel_endpoint_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_receive_channel_endpoint_entry_t *array;\n        void (*on_time_event)(aeron_driver_conductor_t *, aeron_receive_channel_endpoint_entry_t *, int64_t, int64_t);\n        bool (*has_reached_end_of_life)(aeron_driver_conductor_t *, aeron_receive_channel_endpoint_entry_t *);\n        void (*delete_func)(aeron_driver_conductor_t *, aeron_receive_channel_endpoint_entry_t *);\n        void (*free_func)(aeron_driver_conductor_t *, aeron_receive_channel_endpoint_entry_t *);\n    }\n    receive_channel_endpoints;\n\n    struct publication_image_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_publication_image_entry_t *array;\n        void (*on_time_event)(aeron_driver_conductor_t *, aeron_publication_image_entry_t *, int64_t, int64_t);\n        bool (*has_reached_end_of_life)(aeron_driver_conductor_t *, aeron_publication_image_entry_t *);\n        void (*delete_func)(aeron_driver_conductor_t *, aeron_publication_image_entry_t *);\n        void (*free_func)(aeron_driver_conductor_t *, aeron_publication_image_entry_t *);\n    }\n    publication_images;\n\n    struct aeron_driver_conductor_lingering_resources_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_linger_resource_entry_t *array;\n        void (*on_time_event)(aeron_driver_conductor_t *, aeron_linger_resource_entry_t *, int64_t, int64_t);\n        bool (*has_reached_end_of_life)(aeron_driver_conductor_t *, aeron_linger_resource_entry_t *);\n        void (*delete_func)(aeron_driver_conductor_t *, aeron_linger_resource_entry_t *);\n        void (*free_func)(aeron_driver_conductor_t *, aeron_linger_resource_entry_t *);\n    }\n    lingering_resources;\n\n    aeron_deque_t end_of_life_queue;\n\n    int64_t *errors_counter;\n    int64_t *images_rejected_counter;\n    int64_t *unblocked_commands_counter;\n    int64_t *client_timeouts_counter;\n\n    int64_t clock_update_deadline_ns;\n\n    int32_t next_session_id;\n    int32_t publication_reserved_session_id_low;\n    int32_t publication_reserved_session_id_high;\n    int64_t timeout_check_deadline_ns;\n    int64_t time_of_last_to_driver_position_change_ns;\n    int64_t last_command_consumer_position;\n\n    bool async_client_command_in_flight;\n\n    uint8_t padding[AERON_CACHE_LINE_LENGTH];\n}\naeron_driver_conductor_t;\n\nvoid aeron_client_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_client_t *client, int64_t now_ns, int64_t now_ms);\n\nbool aeron_client_has_reached_end_of_life(aeron_driver_conductor_t *conductor, aeron_client_t *client);\n\nvoid aeron_driver_conductor_add_end_of_life_resource(\n    aeron_driver_conductor_t *conductor,\n    void *resource,\n    aeron_end_of_life_resource_free_t free_func);\n\nvoid aeron_client_delete(aeron_driver_conductor_t *conductor, aeron_client_t *);\n\nbool aeron_client_free(void *);\n\nvoid aeron_ipc_publication_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_entry_t *entry, int64_t now_ns, int64_t now_ms);\n\nbool aeron_ipc_publication_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_entry_t *entry);\n\nvoid aeron_ipc_publication_entry_delete(aeron_driver_conductor_t *conductor, aeron_ipc_publication_entry_t *);\n\nvoid aeron_ipc_publication_entry_free(aeron_driver_conductor_t *conductor, aeron_ipc_publication_entry_t *entry);\n\nvoid aeron_network_publication_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_entry_t *entry, int64_t now_ns, int64_t now_ms);\n\nbool aeron_network_publication_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_entry_t *entry);\n\nvoid aeron_network_publication_entry_delete(aeron_driver_conductor_t *conductor, aeron_network_publication_entry_t *);\n\nvoid aeron_network_publication_entry_free(aeron_driver_conductor_t *conductor, aeron_network_publication_entry_t *entry);\n\nvoid aeron_send_channel_endpoint_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_send_channel_endpoint_entry_t *entry, int64_t now_ns, int64_t now_ms);\n\nbool aeron_send_channel_endpoint_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_send_channel_endpoint_entry_t *entry);\n\nvoid aeron_send_channel_endpoint_entry_delete(\n    aeron_driver_conductor_t *conductor, aeron_send_channel_endpoint_entry_t *);\n\nvoid aeron_receive_channel_endpoint_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_receive_channel_endpoint_entry_t *entry, int64_t now_ns, int64_t now_ms);\n\nbool aeron_receive_channel_endpoint_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_receive_channel_endpoint_entry_t *entry);\n\nvoid aeron_receive_channel_endpoint_entry_delete(\n    aeron_driver_conductor_t *conductor, aeron_receive_channel_endpoint_entry_t *);\n\nvoid aeron_publication_image_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_entry_t *entry, int64_t now_ns, int64_t now_ms);\n\nbool aeron_publication_image_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_entry_t *entry);\n\nvoid aeron_publication_image_entry_delete(aeron_driver_conductor_t *conductor, aeron_publication_image_entry_t *);\n\nvoid aeron_publication_image_entry_free(aeron_driver_conductor_t *conductor, aeron_publication_image_entry_t *entry);\n\nvoid aeron_linger_resource_entry_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_linger_resource_entry_t *entry, int64_t now_ns, int64_t now_ms);\n\nbool aeron_linger_resource_entry_has_reached_end_of_life(\n    aeron_driver_conductor_t *conductor, aeron_linger_resource_entry_t *entry);\n\nvoid aeron_linger_resource_entry_delete(aeron_driver_conductor_t *conductor, aeron_linger_resource_entry_t *);\n\nvoid aeron_driver_conductor_image_transition_to_linger(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_t *image);\n\nint aeron_driver_conductor_init(aeron_driver_conductor_t *conductor, aeron_driver_context_t *context);\n\nvoid aeron_driver_conductor_client_transmit(\n    aeron_driver_conductor_t *conductor,\n    int32_t msg_type_id,\n    const void *message,\n    size_t length);\n\nvoid aeron_driver_conductor_on_available_image(\n    aeron_driver_conductor_t *conductor,\n    int64_t correlation_id,\n    int32_t stream_id,\n    int32_t session_id,\n    const char *log_file_name,\n    size_t log_file_name_length,\n    int32_t subscriber_position_id,\n    int64_t subscriber_registration_id,\n    const char *source_identity,\n    size_t source_identity_length);\n\nvoid aeron_driver_conductor_on_unavailable_image(\n    aeron_driver_conductor_t *conductor,\n    int64_t correlation_id,\n    int64_t subscription_registration_id,\n    int32_t stream_id,\n    const char *channel,\n    size_t channel_length);\n\nvoid aeron_driver_conductor_on_counter_ready(\n    aeron_driver_conductor_t *conductor, int64_t registration_id, int32_t counter_id);\n\nvoid aeron_driver_conductor_on_unavailable_counter(\n    aeron_driver_conductor_t *conductor, int64_t registration_id, int32_t counter_id);\n\nvoid aeron_driver_conductor_on_client_timeout(aeron_driver_conductor_t *conductor, int64_t correlation_id);\n\nvoid aeron_driver_conductor_on_static_counter(\n    aeron_driver_conductor_t *conductor, int64_t correlation_id, int32_t counter_id);\n\nvoid aeron_driver_conductor_cleanup_spies(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_t *publication);\n\nvoid aeron_driver_conductor_cleanup_network_publication(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_t *publication);\n\naeron_rb_read_action_t aeron_driver_conductor_on_command(\n    int32_t msg_type_id, const void *message, size_t length, void *clientd);\n\nint aeron_driver_conductor_do_work(void *clientd);\n\nvoid aeron_driver_conductor_on_close(void *clientd);\n\nint aeron_driver_conductor_link_subscribable(\n    aeron_driver_conductor_t *conductor,\n    aeron_subscription_link_t *link,\n    aeron_subscribable_t *subscribable,\n    int64_t original_registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    int64_t join_position,\n    int64_t now_ns,\n    size_t source_identity_length,\n    const char *source_identity,\n    size_t log_file_name_length,\n    const char *log_file_name);\n\nvoid aeron_driver_conductor_unlink_subscribable(aeron_subscription_link_t *link, aeron_subscribable_t *subscribable);\n\nvoid aeron_driver_conductor_unlink_all_subscribable(\n    aeron_driver_conductor_t *conductor, aeron_subscription_link_t *link);\n\nint aeron_driver_conductor_link_ipc_subscriptions(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_t *publication);\n\nint aeron_driver_conductor_on_add_ipc_publication(\n    aeron_driver_conductor_t *conductor, aeron_publication_command_t *command, bool is_exclusive);\n\nint aeron_driver_conductor_on_add_network_publication(\n    aeron_driver_conductor_t *conductor, aeron_publication_command_t *command, bool is_exclusive);\n\nint aeron_driver_conductor_on_remove_publication(aeron_driver_conductor_t *conductor, aeron_remove_publication_command_t *command);\n\nint aeron_driver_conductor_on_add_ipc_subscription(\n    aeron_driver_conductor_t *conductor, aeron_subscription_command_t *command);\n\nint aeron_driver_conductor_on_add_spy_subscription(\n    aeron_driver_conductor_t *conductor, aeron_subscription_command_t *command);\n\nint aeron_driver_conductor_on_add_network_subscription(\n    aeron_driver_conductor_t *conductor, aeron_subscription_command_t *command);\n\nint aeron_driver_conductor_on_remove_subscription(aeron_driver_conductor_t *conductor, aeron_remove_subscription_command_t *command);\n\nint aeron_driver_conductor_on_client_keepalive(aeron_driver_conductor_t *conductor, int64_t client_id);\n\nint aeron_driver_conductor_on_add_send_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command);\n\nint aeron_driver_conductor_on_remove_send_destination(\n    aeron_driver_conductor_t *conductor, aeron_destination_command_t *command);\n\nint aeron_driver_conductor_on_remove_receive_send_destination_by_id(\n    aeron_driver_conductor_t *conductor, aeron_destination_by_id_command_t *command);\n\nint aeron_driver_conductor_on_add_receive_ipc_destination(\n    aeron_driver_conductor_t *conductor,\n    aeron_destination_command_t *command);\n\nint aeron_driver_conductor_on_add_receive_spy_destination(\n    aeron_driver_conductor_t *conductor,\n    aeron_destination_command_t *command);\n\nint aeron_driver_conductor_on_add_receive_network_destination(\n    aeron_driver_conductor_t *conductor,\n    aeron_destination_command_t *command);\n\nint aeron_driver_conductor_on_remove_receive_ipc_destination(\n    aeron_driver_conductor_t *conductor,\n    aeron_destination_command_t *command);\n\nint aeron_driver_conductor_on_remove_receive_spy_destination(\n    aeron_driver_conductor_t *conductor,\n    aeron_destination_command_t *command);\n\nint aeron_driver_conductor_on_remove_receive_network_destination(\n    aeron_driver_conductor_t *conductor,\n    aeron_destination_command_t *command);\n\nvoid aeron_driver_conductor_on_delete_receive_destination(void *clientd, void *cmd);\n\nvoid aeron_driver_conductor_on_delete_send_destination(void *clientd, void *cmd);\n\nint aeron_driver_conductor_on_add_counter(aeron_driver_conductor_t *conductor, aeron_counter_command_t *command);\n\nint aeron_driver_conductor_on_remove_counter(aeron_driver_conductor_t *conductor, aeron_remove_counter_command_t *command);\n\nint aeron_driver_conductor_on_add_static_counter(aeron_driver_conductor_t *conductor, aeron_static_counter_command_t *command);\n\nint aeron_driver_conductor_on_client_close(aeron_driver_conductor_t *conductor, aeron_correlated_command_t *command);\n\nint aeron_driver_conductor_on_terminate_driver(\n    aeron_driver_conductor_t *conductor, aeron_terminate_driver_command_t *command);\n\nint aeron_driver_conductor_on_invalidate_image(\n    aeron_driver_conductor_t *conductor, aeron_reject_image_command_t *command);\n\nint aeron_driver_conductor_on_get_next_available_session_id(\n    aeron_driver_conductor_t *conductor, aeron_get_next_available_session_id_command_t *command);\n\nvoid aeron_driver_conductor_unlink_ipc_subscriptions(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_t *publication);\n\nvoid aeron_driver_conductor_on_create_publication_image(void *clientd, void *item);\n\nvoid aeron_driver_conductor_on_re_resolve_endpoint(void *clientd, void *item);\n\nvoid aeron_driver_conductor_on_re_resolve_control(void *clientd, void *item);\n\nvoid aeron_driver_conductor_on_receive_endpoint_removed(void *clientd, void *item);\n\nvoid aeron_driver_conductor_on_response_setup(void *clientd, void *item);\n\nvoid aeron_driver_conductor_on_response_connected(void *clientd, void *item);\n\nvoid aeron_driver_conductor_on_publication_error(void *clientd, void *item);\n\nvoid aeron_driver_conductor_on_release_resource(void *clientd, void *item);\n\ninline bool aeron_driver_conductor_is_subscribable_linked(\n    aeron_subscription_link_t *link, aeron_subscribable_t *subscribable)\n{\n    bool result = false;\n\n    for (size_t i = 0; i < link->subscribable_list.length; i++)\n    {\n        aeron_subscribable_list_entry_t *entry = &link->subscribable_list.array[i];\n\n        if (subscribable == entry->subscribable)\n        {\n            result = true;\n            break;\n        }\n    }\n\n    return result;\n}\n\ninline size_t aeron_driver_conductor_num_clients(aeron_driver_conductor_t *conductor)\n{\n    return conductor->clients.length;\n}\n\ninline size_t aeron_driver_conductor_num_ipc_publications(aeron_driver_conductor_t *conductor)\n{\n    return conductor->ipc_publications.length;\n}\n\ninline size_t aeron_driver_conductor_num_ipc_subscriptions(aeron_driver_conductor_t *conductor)\n{\n    return conductor->ipc_subscriptions.length;\n}\n\ninline size_t aeron_driver_conductor_num_network_publications(aeron_driver_conductor_t *conductor)\n{\n    return conductor->network_publications.length;\n}\n\ninline size_t aeron_driver_conductor_num_network_subscriptions(aeron_driver_conductor_t *conductor)\n{\n    return conductor->network_subscriptions.length;\n}\n\ninline size_t aeron_driver_conductor_num_spy_subscriptions(aeron_driver_conductor_t *conductor)\n{\n    return conductor->spy_subscriptions.length;\n}\n\ninline size_t aeron_driver_conductor_num_send_channel_endpoints(aeron_driver_conductor_t *conductor)\n{\n    return conductor->send_channel_endpoints.length;\n}\n\ninline size_t aeron_driver_conductor_num_receive_channel_endpoints(aeron_driver_conductor_t *conductor)\n{\n    return conductor->receive_channel_endpoints.length;\n}\n\ninline size_t aeron_driver_conductor_num_images(aeron_driver_conductor_t *conductor)\n{\n    return conductor->publication_images.length;\n}\n\ninline size_t aeron_driver_conductor_num_active_ipc_subscriptions(\n    aeron_driver_conductor_t *conductor, int32_t stream_id)\n{\n    size_t num = 0;\n\n    for (size_t i = 0, length = conductor->ipc_subscriptions.length; i < length; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[i];\n\n        if (stream_id == link->stream_id)\n        {\n            num += link->subscribable_list.length;\n        }\n    }\n\n    return num;\n}\n\ninline size_t aeron_driver_conductor_num_active_network_subscriptions(\n    aeron_driver_conductor_t *conductor, const char *original_uri, int32_t stream_id)\n{\n    size_t num = 0;\n\n    for (size_t i = 0, length = conductor->network_subscriptions.length; i < length; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->network_subscriptions.array[i];\n        aeron_udp_channel_t *udp_channel = link->endpoint->conductor_fields.udp_channel;\n\n        if (stream_id == link->stream_id &&\n            strncmp(original_uri, udp_channel->original_uri, udp_channel->uri_length) == 0)\n        {\n            num += link->subscribable_list.length;\n        }\n    }\n\n    return num;\n}\n\ninline size_t aeron_driver_conductor_num_active_spy_subscriptions(\n    aeron_driver_conductor_t *conductor, const char *original_uri, int32_t stream_id)\n{\n    size_t num = 0;\n\n    for (size_t i = 0, length = conductor->spy_subscriptions.length; i < length; i++)\n    {\n        aeron_subscription_link_t *link = &conductor->spy_subscriptions.array[i];\n        aeron_udp_channel_t *udp_channel = link->spy_channel;\n\n        if (stream_id == link->stream_id &&\n            strncmp(original_uri, udp_channel->original_uri, udp_channel->uri_length) == 0)\n        {\n            num += link->subscribable_list.length;\n        }\n    }\n\n    return num;\n}\n\ninline aeron_send_channel_endpoint_t * aeron_driver_conductor_find_send_channel_endpoint(\n    aeron_driver_conductor_t *conductor, const char *original_uri)\n{\n    for (size_t i = 0, length = conductor->send_channel_endpoints.length; i < length; i++)\n    {\n        aeron_send_channel_endpoint_t *endpoint = conductor->send_channel_endpoints.array[i].endpoint;\n        const aeron_udp_channel_t *udp_channel = endpoint->conductor_fields.udp_channel;\n\n        if (strncmp(original_uri, udp_channel->original_uri, udp_channel->uri_length) == 0)\n        {\n            return endpoint;\n        }\n    }\n\n    return NULL;\n}\n\ninline aeron_receive_channel_endpoint_t * aeron_driver_conductor_find_receive_channel_endpoint(\n    aeron_driver_conductor_t *conductor, const char *original_uri)\n{\n    for (size_t i = 0, length = conductor->receive_channel_endpoints.length; i < length; i++)\n    {\n        aeron_receive_channel_endpoint_t *endpoint = conductor->receive_channel_endpoints.array[i].endpoint;\n        aeron_udp_channel_t *udp_channel = endpoint->conductor_fields.udp_channel;\n\n        if (strncmp(original_uri, udp_channel->original_uri, udp_channel->uri_length) == 0)\n        {\n            return endpoint;\n        }\n    }\n\n    return NULL;\n}\n\ninline aeron_ipc_publication_t * aeron_driver_conductor_find_ipc_publication(\n    aeron_driver_conductor_t *conductor, int64_t id)\n{\n    for (size_t i = 0, length = conductor->ipc_publications.length; i < length; i++)\n    {\n        aeron_ipc_publication_t *publication = conductor->ipc_publications.array[i].publication;\n\n        if (id == publication->conductor_fields.managed_resource.registration_id)\n        {\n            return publication;\n        }\n    }\n\n    return NULL;\n}\n\ninline aeron_network_publication_t * aeron_driver_conductor_find_network_publication(\n    aeron_driver_conductor_t *conductor, int64_t id)\n{\n    for (size_t i = 0, length = conductor->network_publications.length; i < length; i++)\n    {\n        aeron_network_publication_t *publication = conductor->network_publications.array[i].publication;\n\n        if (id == publication->conductor_fields.managed_resource.registration_id)\n        {\n            return publication;\n        }\n    }\n\n    return NULL;\n}\n\ninline aeron_network_publication_t *aeron_driver_conductor_find_network_publication_by_tag(\n    aeron_driver_conductor_t *conductor, int64_t tag_id)\n{\n    for (size_t i = 0, length = conductor->network_publications.length; i < length; i++)\n    {\n        aeron_network_publication_t *publication = conductor->network_publications.array[i].publication;\n\n        if (tag_id == publication->tag && AERON_URI_INVALID_TAG != publication->tag)\n        {\n            return publication;\n        }\n    }\n\n    return NULL;\n}\n\ninline void aeron_driver_init_subscription_channel(size_t uri_length, const char *uri, aeron_subscription_link_t *link)\n{\n    size_t copy_length = sizeof(link->channel) - 1;\n    copy_length = uri_length < copy_length ? uri_length : copy_length;\n\n    strncpy(link->channel, uri, copy_length);\n    link->channel[copy_length] = '\\0';\n    link->channel_length = (int32_t)copy_length;\n}\n\n#endif //AERON_DRIVER_CONDUCTOR_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_conductor_proxy.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"aeron_driver_conductor_proxy.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_driver_conductor.h\"\n\nvoid aeron_driver_conductor_proxy_offer(aeron_driver_conductor_proxy_t *conductor_proxy, void *cmd, size_t length)\n{\n    aeron_rb_write_result_t result;\n    while (AERON_RB_FULL == (result = aeron_mpsc_rb_write(conductor_proxy->command_queue, 1, cmd, length)))\n    {\n        aeron_counter_increment_release(conductor_proxy->fail_counter);\n        sched_yield();\n    }\n\n    if (AERON_RB_ERROR == result)\n    {\n        aeron_distinct_error_log_record(\n            &conductor_proxy->conductor->error_log, EINVAL, \"Error writing to receiver proxy ring buffer\");\n    }\n}\n\nvoid aeron_driver_conductor_proxy_on_create_publication_image_cmd(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t initial_term_id,\n    int32_t active_term_id,\n    int32_t term_offset,\n    int32_t term_length,\n    int32_t mtu_length,\n    uint8_t flags,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *src_address,\n    void *endpoint,\n    void *destination)\n{\n    aeron_command_create_publication_image_t cmd =\n        {\n            .base = { .func = aeron_driver_conductor_on_create_publication_image, .item = NULL },\n            .session_id = session_id,\n            .stream_id = stream_id,\n            .initial_term_id = initial_term_id,\n            .active_term_id = active_term_id,\n            .term_offset = term_offset,\n            .term_length = term_length,\n            .mtu_length = mtu_length,\n            .flags = flags,\n            .endpoint = endpoint,\n            .destination = destination\n        };\n    memcpy(&cmd.control_address, control_address, sizeof(struct sockaddr_storage));\n    memcpy(&cmd.src_address, src_address, sizeof(struct sockaddr_storage));\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode))\n    {\n        aeron_driver_conductor_on_create_publication_image(conductor_proxy->conductor, &cmd);\n    }\n    else\n    {\n        aeron_driver_conductor_proxy_offer(conductor_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_conductor_proxy_on_re_resolve(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    void (*resolve_func)(void *, void *),\n    const char *endpoint_name,\n    void *endpoint,\n    void *destination,\n    struct sockaddr_storage *existing_addr)\n{\n    aeron_command_re_resolve_t cmd =\n        {\n            .base = { .func = resolve_func, .item = NULL },\n            .endpoint_name = endpoint_name,\n            .endpoint = endpoint,\n            .destination = destination,\n        };\n    memcpy(&cmd.existing_addr, existing_addr, sizeof(cmd.existing_addr));\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode))\n    {\n        resolve_func(conductor_proxy->conductor, &cmd);\n    }\n    else\n    {\n        aeron_driver_conductor_proxy_offer(conductor_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_conductor_proxy_on_re_resolve_endpoint(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    const char *endpoint_name,\n    void *endpoint,\n    struct sockaddr_storage *existing_addr)\n{\n    aeron_driver_conductor_proxy_on_re_resolve(\n        conductor_proxy, aeron_driver_conductor_on_re_resolve_endpoint, endpoint_name, endpoint, NULL, existing_addr);\n}\n\nvoid aeron_driver_conductor_proxy_on_re_resolve_control(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    const char *endpoint_name,\n    void *endpoint,\n    void *destination,\n    struct sockaddr_storage *existing_addr)\n{\n    aeron_driver_conductor_proxy_on_re_resolve(\n        conductor_proxy,\n        aeron_driver_conductor_on_re_resolve_control,\n        endpoint_name,\n        endpoint,\n        destination,\n        existing_addr);\n}\n\nvoid aeron_driver_conductor_proxy_on_delete_receive_destination(\n    aeron_driver_conductor_proxy_t *conductor_proxy, void *endpoint, void *destination, void *channel)\n{\n    aeron_command_delete_destination_t cmd =\n        {\n            .base = { .func = aeron_driver_conductor_on_delete_receive_destination, .item = NULL },\n            .endpoint = endpoint,\n            .destination = destination,\n            .channel = channel\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode))\n    {\n        aeron_driver_conductor_on_delete_receive_destination(conductor_proxy->conductor, &cmd);\n    }\n    else\n    {\n        aeron_driver_conductor_proxy_offer(conductor_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_conductor_proxy_on_delete_send_destination(\n    aeron_driver_conductor_proxy_t *conductor_proxy, void *removed_uri)\n{\n    aeron_command_base_t cmd =\n        {\n            .func = aeron_driver_conductor_on_delete_send_destination,\n            .item = removed_uri,\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode))\n    {\n        aeron_driver_conductor_on_delete_send_destination(conductor_proxy->conductor, &cmd);\n    }\n    else\n    {\n        aeron_driver_conductor_proxy_offer(conductor_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_conductor_proxy_on_receive_endpoint_removed(\n    aeron_driver_conductor_proxy_t *conductor_proxy, void *endpoint)\n{\n    aeron_command_base_t cmd =\n        {\n            .func = aeron_driver_conductor_on_receive_endpoint_removed,\n            .item = endpoint\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode))\n    {\n        aeron_driver_conductor_on_receive_endpoint_removed(conductor_proxy->conductor, &cmd);\n    }\n    else\n    {\n        aeron_driver_conductor_proxy_offer(conductor_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_conductor_proxy_on_response_setup(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    int64_t response_correlation_id,\n    int32_t response_session_id)\n{\n    aeron_command_response_setup_t cmd = {\n        .base = {\n            .func = aeron_driver_conductor_on_response_setup,\n            .item = NULL\n        },\n        .response_correlation_id = response_correlation_id,\n        .response_session_id = response_session_id\n    };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode))\n    {\n        aeron_driver_conductor_on_response_setup(conductor_proxy->conductor, &cmd);\n    }\n    else\n    {\n        aeron_driver_conductor_proxy_offer(conductor_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_conductor_proxy_on_response_connected(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    int64_t response_correlation_id)\n{\n    aeron_command_response_connected_t cmd = {\n        .base = {\n            .func = aeron_driver_conductor_on_response_connected,\n            .item = NULL\n        },\n        .response_correlation_id = response_correlation_id\n    };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode))\n    {\n        aeron_driver_conductor_on_response_connected(conductor_proxy->conductor, &cmd);\n    }\n    else\n    {\n        aeron_driver_conductor_proxy_offer(conductor_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_conductor_proxy_on_release_resource(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    void *managed_resource,\n    aeron_driver_conductor_resource_type_t resource_type)\n{\n   aeron_command_release_resource_t cmd =\n        {\n            .base =\n                {\n                    .func = aeron_driver_conductor_on_release_resource,\n                    .item = managed_resource\n                },\n            .resource_type = resource_type\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode))\n    {\n        aeron_driver_conductor_on_release_resource(conductor_proxy->conductor, &cmd);\n    }\n    else\n    {\n        aeron_driver_conductor_proxy_offer(conductor_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_conductor_proxy_on_publication_error(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    const int64_t registration_id,\n    const int64_t destination_registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    int64_t receiver_id,\n    int64_t group_tag,\n    struct sockaddr_storage *src_address,\n    int32_t error_code,\n    int32_t error_length,\n    const uint8_t *error_text)\n{\n    uint8_t buffer[AERON_COMMAND_PUBLICATION_ERROR_MAX_LENGTH];\n    aeron_command_publication_error_t *error = (aeron_command_publication_error_t *)buffer;\n    error_length = error_length <= AERON_ERROR_MAX_TEXT_LENGTH ? error_length : AERON_ERROR_MAX_TEXT_LENGTH;\n\n    error->base.func = aeron_driver_conductor_on_publication_error;\n    error->base.item = NULL;\n    error->registration_id = registration_id;\n    error->destination_registration_id = destination_registration_id;\n    error->session_id = session_id;\n    error->stream_id = stream_id;\n    error->receiver_id = receiver_id;\n    error->group_tag = group_tag;\n    memset(&error->src_address, 0, sizeof(struct sockaddr_storage));\n    memcpy(&error->src_address, src_address, AERON_ADDR_LEN(src_address));\n    error->error_code = error_code;\n    error->error_length = error_length;\n    memcpy(error->error_text, error_text, (size_t)error_length);\n    aeron_str_null_terminate(error->error_text, error_length);\n\n    size_t cmd_length = sizeof(aeron_command_publication_error_t) + error_length + 1;\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(conductor_proxy->threading_mode))\n    {\n        aeron_driver_conductor_on_publication_error(conductor_proxy->conductor, error);\n    }\n    else\n    {\n        aeron_driver_conductor_proxy_offer(conductor_proxy, (void *)error, cmd_length);\n    }\n}\n\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_conductor_proxy.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_CONDUCTOR_PROXY_H\n#define AERON_DRIVER_CONDUCTOR_PROXY_H\n\n#include \"aeron_driver_context.h\"\n\ntypedef enum aeron_driver_conductor_resource_type_en\n{\n    AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_CLIENT,\n    AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_IPC_PUBLICATION,\n    AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_NETWORK_PUBLICATION,\n    AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_SEND_CHANNEL_ENDPOINT,\n    AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_PUBLICATION_IMAGE,\n    AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_LINGER_RESOURCE\n}\naeron_driver_conductor_resource_type_t;\n\ntypedef struct aeron_driver_conductor_stct aeron_driver_conductor_t;\n\ntypedef struct aeron_driver_conductor_proxy_stct\n{\n    aeron_driver_conductor_t *conductor;\n    aeron_threading_mode_t threading_mode;\n    aeron_mpsc_rb_t *command_queue;\n    int64_t *fail_counter;\n}\naeron_driver_conductor_proxy_t;\n\ntypedef struct aeron_command_create_publication_image_stct\n{\n    aeron_command_base_t base;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t initial_term_id;\n    int32_t active_term_id;\n    int32_t term_offset;\n    int32_t term_length;\n    int32_t mtu_length;\n    uint8_t flags;\n    struct sockaddr_storage control_address;\n    struct sockaddr_storage src_address;\n    void *endpoint;\n    void *destination;\n}\naeron_command_create_publication_image_t;\n\ntypedef struct aeron_command_re_resolve_stct\n{\n    aeron_command_base_t base;\n    const char *endpoint_name;\n    void *endpoint;\n    void *destination;\n    struct sockaddr_storage existing_addr;\n}\naeron_command_re_resolve_t;\n\ntypedef struct aeron_command_delete_destination_stct\n{\n    aeron_command_base_t base;\n    void *endpoint;\n    void *destination;\n    void *channel;\n}\naeron_command_delete_destination_t;\n\nstruct aeron_command_response_connected_stct\n{\n    aeron_command_base_t base;\n    int64_t response_correlation_id;\n};\ntypedef struct aeron_command_response_connected_stct aeron_command_response_connected_t;\n\nstruct aeron_command_response_setup_stct\n{\n    aeron_command_base_t base;\n    int64_t response_correlation_id;\n    int32_t response_session_id;\n};\ntypedef struct aeron_command_response_setup_stct aeron_command_response_setup_t;\n\nstruct aeron_command_release_resource_stct\n{\n    aeron_command_base_t base;\n    aeron_driver_conductor_resource_type_t resource_type;\n};\ntypedef struct aeron_command_release_resource_stct aeron_command_release_resource_t;\n\nstruct aeron_command_publication_error_stct\n{\n    aeron_command_base_t base;\n    int64_t registration_id;\n    int64_t destination_registration_id;\n    int32_t session_id;\n    int32_t stream_id;\n    int64_t receiver_id;\n    int64_t group_tag;\n    struct sockaddr_storage src_address;\n    int32_t error_code;\n    int32_t error_length;\n    uint8_t error_text[1];\n};\ntypedef struct aeron_command_publication_error_stct aeron_command_publication_error_t;\n\nvoid aeron_driver_conductor_proxy_on_create_publication_image_cmd(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t initial_term_id,\n    int32_t active_term_id,\n    int32_t term_offset,\n    int32_t term_length,\n    int32_t mtu_length,\n    uint8_t flags,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *src_address,\n    void *endpoint,\n    void *destination);\n\nvoid aeron_driver_conductor_proxy_on_re_resolve_endpoint(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    const char *endpoint_name,\n    void *endpoint,\n    struct sockaddr_storage *existing_addr);\n\nvoid aeron_driver_conductor_proxy_on_re_resolve_control(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    const char *endpoint_name,\n    void *endpoint,\n    void *destination,\n    struct sockaddr_storage *existing_addr);\n\nvoid aeron_driver_conductor_proxy_on_delete_receive_destination(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    void *endpoint,\n    void *destination,\n    void *channel);\n\nvoid aeron_driver_conductor_proxy_on_delete_send_destination(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    void *removed_uri);\n\nvoid aeron_driver_conductor_proxy_on_receive_endpoint_removed(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    void *endpoint);\n\nvoid aeron_driver_conductor_proxy_on_response_setup(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    int64_t response_correlation_id,\n    int32_t response_session_id);\n\nvoid aeron_driver_conductor_proxy_on_response_connected(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    int64_t response_correlation_id);\n\nvoid aeron_driver_conductor_proxy_on_release_resource(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    void *managed_resource,\n    aeron_driver_conductor_resource_type_t resource_type);\n\n#define AERON_COMMAND_PUBLICATION_ERROR_MAX_LENGTH (sizeof(aeron_command_publication_error_t) + AERON_ERROR_MAX_TEXT_LENGTH)\n\nvoid aeron_driver_conductor_proxy_on_publication_error(\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    const int64_t registration_id,\n    const int64_t destination_registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    int64_t receiver_id,\n    int64_t group_tag,\n    struct sockaddr_storage *src_address,\n    int32_t error_code,\n    int32_t error_length,\n    const uint8_t *error_text);\n\n#endif //AERON_DRIVER_CONDUCTOR_PROXY_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_context.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stddef.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <inttypes.h>\n#include <limits.h>\n#include <errno.h>\n\n#ifdef HAVE_UUID_H\n#include <uuid/uuid.h>\n#endif\n\n#include \"aeron_windows.h\"\n#include \"util/aeron_error.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"aeron_driver.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_termination_validator.h\"\n#include \"agent/aeron_driver_agent.h\"\n#include \"util/aeron_dlopen.h\"\n\naeron_threading_mode_t aeron_config_parse_threading_mode(const char *threading_mode, aeron_threading_mode_t def)\n{\n    aeron_threading_mode_t result = def;\n\n    if (NULL != threading_mode)\n    {\n        if (strncmp(threading_mode, \"SHARED\", sizeof(\"SHARED\")) == 0)\n        {\n            result = AERON_THREADING_MODE_SHARED;\n        }\n        else if (strncmp(threading_mode, \"SHARED_NETWORK\", sizeof(\"SHARED_NETWORK\")) == 0)\n        {\n            result = AERON_THREADING_MODE_SHARED_NETWORK;\n        }\n        else if (strncmp(threading_mode, \"DEDICATED\", sizeof(\"DEDICATED\")) == 0)\n        {\n            result = AERON_THREADING_MODE_DEDICATED;\n        }\n        else if (strncmp(threading_mode, \"INVOKER\", sizeof(\"INVOKER\")) == 0)\n        {\n            result = AERON_THREADING_MODE_INVOKER;\n        }\n        else\n        {\n            aeron_config_prop_warning(AERON_THREADING_MODE_ENV_VAR, threading_mode);\n        }\n    }\n\n    return result;\n}\n\naeron_inferable_boolean_t aeron_config_parse_inferable_boolean(\n    const char *inferable_boolean, aeron_inferable_boolean_t def)\n{\n    aeron_inferable_boolean_t result = def;\n\n    if (NULL != inferable_boolean)\n    {\n        if (strncmp(inferable_boolean, \"true\", sizeof(\"true\")) == 0)\n        {\n            result = AERON_FORCE_TRUE;\n        }\n        else if (strncmp(inferable_boolean, \"infer\", sizeof(\"infer\")) == 0)\n        {\n            result = AERON_INFER;\n        }\n        else\n        {\n            result = AERON_FORCE_FALSE;\n        }\n    }\n\n    return result;\n}\n\n#define AERON_CONFIG_GETENV_OR_DEFAULT(e, d) ((NULL == getenv(e)) ? (d) : getenv(e))\n#define AERON_CONFIG_STRNDUP_GETENV_OR_NULL(e) ((NULL == getenv(e)) ? (NULL) : aeron_strndup(getenv(e), AERON_MAX_PATH))\n\nstatic void aeron_driver_conductor_to_driver_interceptor_null(\n    int32_t msg_type_id, const void *message, size_t length, void *clientd)\n{\n}\n\nstatic void aeron_driver_conductor_to_client_interceptor_null(\n    aeron_driver_conductor_t *conductor, int32_t msg_type_id, const void *message, size_t length)\n{\n}\n\nstatic void aeron_driver_conductor_name_resolver_on_neighbor_change_null(const struct sockaddr_storage *addr)\n{\n}\n\nstatic void aeron_driver_conductor_remove_publication_cleanup_null(\n    const int32_t session_id, const int32_t stream_id, const size_t channel_length, const char *channel)\n{\n}\n\nstatic void aeron_driver_conductor_remove_subscription_cleanup_null(\n    const int64_t id, const int32_t stream_id, const size_t channel_length, const char *channel)\n{\n}\n\nstatic void aeron_driver_conductor_remove_image_cleanup_null(\n    const int64_t id,\n    const int32_t session_id,\n    const int32_t stream_id,\n    const size_t channel_length,\n    const char *channel)\n{\n}\n\nstatic void aeron_driver_conductor_on_endpoint_change_null(const void *channel)\n{\n}\n\nstatic void aeron_driver_untethered_subscription_state_change_null(\n    aeron_tetherable_position_t *tetherable_position,\n    int64_t now_ns,\n    aeron_subscription_tether_state_t old_state,\n    aeron_subscription_tether_state_t new_state,\n    int32_t stream_id,\n    int32_t session_id)\n{\n}\n\n#define AERON_DIR_WARN_IF_EXISTS_DEFAULT false\n#define AERON_THREADING_MODE_DEFAULT AERON_THREADING_MODE_DEDICATED\n#define AERON_DIR_DELETE_ON_START_DEFAULT false\n#define AERON_DIR_DELETE_ON_SHUTDOWN_DEFAULT false\n#define AERON_CLIENT_LIVENESS_TIMEOUT_NS_DEFAULT (10 * 1000 * 1000 * INT64_C(1000))\n#define AERON_TERM_BUFFER_LENGTH_DEFAULT (16 * 1024 * 1024)\n#define AERON_IPC_TERM_BUFFER_LENGTH_DEFAULT (64 * 1024 * 1024)\n#define AERON_TERM_BUFFER_SPARSE_FILE_DEFAULT (true)\n#define AERON_PERFORM_STORAGE_CHECKS_DEFAULT (true)\n#define AERON_LOW_FILE_STORE_WARNING_THRESHOLD_DEFAULT (AERON_TERM_BUFFER_LENGTH_DEFAULT * INT64_C(10))\n#define AERON_SPIES_SIMULATE_CONNECTION_DEFAULT (false)\n#define AERON_FILE_PAGE_SIZE_DEFAULT (4 * 1024)\n#define AERON_MTU_LENGTH_DEFAULT (1408)\n#define AERON_IPC_MTU_LENGTH_DEFAULT (1408)\n#define AERON_IPC_PUBLICATION_TERM_WINDOW_LENGTH_DEFAULT (0)\n#define AERON_PUBLICATION_TERM_WINDOW_LENGTH_DEFAULT (0)\n#define AERON_PUBLICATION_LINGER_TIMEOUT_NS_DEFAULT (5 * 1000 * 1000 * INT64_C(1000))\n#define AERON_SOCKET_SO_RCVBUF_DEFAULT (128 * 1024)\n#define AERON_SOCKET_SO_SNDBUF_DEFAULT (0)\n#define AERON_SOCKET_MULTICAST_TTL_DEFAULT (0)\n#define AERON_RECEIVER_GROUP_TAG_IS_PRESENT_DEFAULT false\n#define AERON_RECEIVER_GROUP_TAG_VALUE_DEFAULT (-1)\n#define AERON_FLOW_CONTROL_GROUP_TAG_DEFAULT (-1)\n#define AERON_FLOW_CONTROL_GROUP_MIN_SIZE_DEFAULT (0)\n#define AERON_FLOW_CONTROL_RECEIVER_TIMEOUT_NS_DEFAULT (5 * 1000 * 1000 * INT64_C(1000))\n#define AERON_SEND_TO_STATUS_POLL_RATIO_DEFAULT (6)\n#define AERON_RCV_STATUS_MESSAGE_TIMEOUT_NS_DEFAULT (200 * 1000 * INT64_C(1000))\n#define AERON_MULTICAST_FLOWCONTROL_SUPPLIER_DEFAULT (\"aeron_max_multicast_flow_control_strategy_supplier\")\n#define AERON_UNICAST_FLOWCONTROL_SUPPLIER_DEFAULT (\"aeron_unicast_flow_control_strategy_supplier\")\n#define AERON_CONGESTIONCONTROL_SUPPLIER_DEFAULT (\"aeron_congestion_control_default_strategy_supplier\")\n#define AERON_IMAGE_LIVENESS_TIMEOUT_NS_DEFAULT (10 * 1000 * 1000 * INT64_C(1000))\n#define AERON_RCV_INITIAL_WINDOW_LENGTH_DEFAULT (128 * 1024)\n#define AERON_LOSS_REPORT_BUFFER_LENGTH_DEFAULT (1024 * 1024)\n#define AERON_PUBLICATION_UNBLOCK_TIMEOUT_NS_DEFAULT (15 * 1000 * 1000 * INT64_C(1000))\n#define AERON_PUBLICATION_CONNECTION_TIMEOUT_NS_DEFAULT (5 * 1000 * 1000 * INT64_C(1000))\n#define AERON_TIMER_INTERVAL_NS_DEFAULT (1 * 1000 * 1000 * INT64_C(1000))\n#define AERON_IDLE_STRATEGY_BACKOFF_DEFAULT \"aeron_idle_strategy_backoff\"\n#define AERON_COUNTERS_FREE_TO_REUSE_TIMEOUT_NS_DEFAULT (1 * 1000 * 1000 * INT64_C(1000))\n#define AERON_PRINT_CONFIGURATION_DEFAULT (false)\n#define AERON_RELIABLE_STREAM_DEFAULT (true)\n#define AERON_TETHER_SUBSCRIPTIONS_DEFAULT (true)\n#define AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_DEFAULT (5 * 1000 * 1000 * INT64_C(1000))\n#define AERON_UNTETHERED_RESTING_TIMEOUT_NS_DEFAULT (10 * 1000 * 1000 * INT64_C(1000))\n#define AERON_DRIVER_TIMEOUT_MS_DEFAULT (10 * 1000)\n#define AERON_RETRANSMIT_UNICAST_DELAY_NS_DEFAULT (0)\n#define AERON_RETRANSMIT_UNICAST_LINGER_NS_DEFAULT (10 * 1000 * INT64_C(1000))\n#define AERON_NAK_MULTICAST_GROUP_SIZE_DEFAULT (10)\n#define AERON_NAK_MULTICAST_MAX_BACKOFF_NS_DEFAULT (10 * 1000 * INT64_C(1000))\n#define AERON_NAK_UNICAST_DELAY_NS_MIN UINT64_C(1000)\n#define AERON_NAK_UNICAST_DELAY_NS_DEFAULT (AERON_NAK_UNICAST_DELAY_NS_MIN)\n#define AERON_NAK_UNICAST_RETRY_DELAY_RATIO_DEFAULT UINT64_C(100)\n#define AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA_DEFAULT (\"default\")\n#define AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_INTERCEPTORS_DEFAULT (\"\")\n#define AERON_RECEIVER_GROUP_CONSIDERATION_DEFAULT (AERON_INFER)\n#define AERON_REJOIN_STREAM_DEFAULT (true)\n#define AERON_PUBLICATION_RESERVED_SESSION_ID_LOW_DEFAULT (-1)\n#define AERON_PUBLICATION_RESERVED_SESSION_ID_HIGH_DEFAULT (1000)\n#define AERON_DRIVER_RERESOLUTION_CHECK_INTERVAL_NS_DEFAULT (1 * 1000 * 1000 * INT64_C(1000))\n#define AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD_NS_DEFAULT (100 * 1000 * INT64_C(1000))\n#define AERON_DRIVER_SENDER_CYCLE_THRESHOLD_NS_DEFAULT (100 * 1000 * INT64_C(1000))\n#define AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD_NS_DEFAULT (100 * 1000 * INT64_C(1000))\n#define AERON_DRIVER_NAME_RESOLVER_THRESHOLD_NS_DEFAULT (5 * 1000 * 1000 * INT64_C(1000))\n#define AERON_RECEIVER_IO_VECTOR_CAPACITY_DEFAULT UINT32_C(4)\n#define AERON_SENDER_IO_VECTOR_CAPACITY_DEFAULT UINT32_C(4)\n#define AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_DEFAULT UINT32_C(4)\n#define AERON_DRIVER_RESOURCE_FREE_LIMIT_DEFAULT UINT32_C(10)\n#define AERON_DRIVER_ASYNC_EXECUTOR_THREADS_DEFAULT UINT32_C(1)\n#define AERON_CPU_AFFINITY_DEFAULT (-1)\n#define AERON_DRIVER_CONNECT_DEFAULT true\n#define AERON_ENABLE_EXPERIMENTAL_FEATURES_DEFAULT false\n#define AERON_DRIVER_STREAM_SESSION_LIMIT_DEFAULT (INT32_MAX)\n\n\nint aeron_driver_context_init(aeron_driver_context_t **context)\n{\n    aeron_driver_context_t *_context = NULL;\n\n    if (NULL == context)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"aeron_driver_context_init(NULL)\");\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&_context, sizeof(aeron_driver_context_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate aeron_driver_context\");\n        return -1;\n    }\n\n    _context->cnc_map.addr = NULL;\n    _context->loss_report.addr = NULL;\n    _context->conductor_proxy = NULL;\n    _context->sender_proxy = NULL;\n    _context->receiver_proxy = NULL;\n    _context->counters_manager = NULL;\n    _context->error_log = NULL;\n\n    _context->conductor_duty_cycle_tracker = &_context->conductor_duty_cycle_stall_tracker.tracker;\n    _context->conductor_duty_cycle_stall_tracker.tracker.update =\n        aeron_duty_cycle_stall_tracker_update;\n    _context->conductor_duty_cycle_stall_tracker.tracker.measure_and_update =\n        aeron_duty_cycle_stall_tracker_measure_and_update;\n    _context->conductor_duty_cycle_stall_tracker.tracker.state = &_context->conductor_duty_cycle_stall_tracker;\n\n    _context->sender_duty_cycle_tracker = &_context->sender_duty_cycle_stall_tracker.tracker;\n    _context->sender_duty_cycle_stall_tracker.tracker.update =\n        aeron_duty_cycle_stall_tracker_update;\n    _context->sender_duty_cycle_stall_tracker.tracker.measure_and_update =\n        aeron_duty_cycle_stall_tracker_measure_and_update;\n    _context->sender_duty_cycle_stall_tracker.tracker.state = &_context->sender_duty_cycle_stall_tracker;\n\n    _context->receiver_duty_cycle_tracker = &_context->receiver_duty_cycle_stall_tracker.tracker;\n    _context->receiver_duty_cycle_stall_tracker.tracker.update =\n        aeron_duty_cycle_stall_tracker_update;\n    _context->receiver_duty_cycle_stall_tracker.tracker.measure_and_update =\n        aeron_duty_cycle_stall_tracker_measure_and_update;\n    _context->receiver_duty_cycle_stall_tracker.tracker.state = &_context->receiver_duty_cycle_stall_tracker;\n\n    _context->name_resolver_time_tracker = &_context->name_resolver_time_stall_tracker.tracker;\n    _context->name_resolver_time_stall_tracker.tracker.update =\n        aeron_duty_cycle_stall_tracker_update;\n    _context->name_resolver_time_stall_tracker.tracker.measure_and_update =\n        aeron_duty_cycle_stall_tracker_measure_and_update;\n    _context->name_resolver_time_stall_tracker.tracker.state = &_context->name_resolver_time_stall_tracker;\n\n    _context->sender_port_manager = &_context->sender_wildcard_port_manager.port_manager;\n    _context->receiver_port_manager = &_context->receiver_wildcard_port_manager.port_manager;\n\n    _context->udp_channel_outgoing_interceptor_bindings = NULL;\n    _context->udp_channel_incoming_interceptor_bindings = NULL;\n    _context->dynamic_libs = NULL;\n    _context->bindings_clientd_entries = NULL;\n    _context->num_bindings_clientd_entries = 0;\n\n    const size_t command_rb_capacity = (AERON_COMMAND_RB_CAPACITY * 1024) + AERON_RB_TRAILER_LENGTH;\n\n    void *sender_buffer;\n    if (aeron_alloc(&sender_buffer, command_rb_capacity))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_mpsc_rb_init(&_context->sender_command_queue, sender_buffer, command_rb_capacity))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_free(sender_buffer);\n        goto error;\n    }\n\n    void *receiver_buffer;\n    if (aeron_alloc(&receiver_buffer, command_rb_capacity))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_mpsc_rb_init(&_context->receiver_command_queue, receiver_buffer, command_rb_capacity))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_free(receiver_buffer);\n        goto error;\n    }\n\n    void *conductor_buffer;\n    if (aeron_alloc(&conductor_buffer, command_rb_capacity))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_mpsc_rb_init(&_context->conductor_command_queue, conductor_buffer, command_rb_capacity))\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_free(conductor_buffer);\n        goto error;\n    }\n\n    _context->agent_on_start_func = NULL;\n    _context->agent_on_start_state = NULL;\n    _context->agent_on_start_func_delegate = NULL;\n    _context->agent_on_start_state_delegate = NULL;\n\n    if ((_context->unicast_flow_control_supplier_func = aeron_flow_control_strategy_supplier_load(\n        AERON_UNICAST_FLOWCONTROL_SUPPLIER_DEFAULT)) == NULL)\n    {\n        goto error;\n    }\n\n    if ((_context->multicast_flow_control_supplier_func = aeron_flow_control_strategy_supplier_load(\n        AERON_MULTICAST_FLOWCONTROL_SUPPLIER_DEFAULT)) == NULL)\n    {\n        goto error;\n    }\n\n    if ((_context->congestion_control_supplier_func = aeron_congestion_control_strategy_supplier_load(\n        AERON_CONGESTIONCONTROL_SUPPLIER_DEFAULT)) == NULL)\n    {\n        goto error;\n    }\n\n    if ((_context->name_resolver_supplier_func = aeron_name_resolver_supplier_load(\n        AERON_NAME_RESOLVER_SUPPLIER_DEFAULT)) == NULL)\n    {\n        goto error;\n    }\n\n    if (aeron_wildcard_port_manager_init(&_context->sender_wildcard_port_manager, true) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"unable to initialize sender wildcard port manager\");\n        goto error;\n    }\n\n    if (aeron_wildcard_port_manager_init(&_context->receiver_wildcard_port_manager, false) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"unable to initialize receiver wildcard port manager\");\n        goto error;\n    }\n\n    if (aeron_default_path(_context->aeron_dir, sizeof(_context->aeron_dir)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to resolve default aeron directory\");\n        goto error;\n    }\n\n    _context->threading_mode = aeron_config_parse_threading_mode(\n        getenv(AERON_THREADING_MODE_ENV_VAR), AERON_THREADING_MODE_DEFAULT);\n    _context->receiver_group_consideration = aeron_config_parse_inferable_boolean(\n        getenv(AERON_RECEIVER_GROUP_CONSIDERATION_ENV_VAR), AERON_RECEIVER_GROUP_CONSIDERATION_DEFAULT);\n    _context->dirs_delete_on_start = AERON_DIR_DELETE_ON_START_DEFAULT;\n    _context->dirs_delete_on_shutdown = AERON_DIR_DELETE_ON_SHUTDOWN_DEFAULT;\n    _context->warn_if_dirs_exist = AERON_DIR_WARN_IF_EXISTS_DEFAULT;\n    _context->term_buffer_sparse_file = AERON_TERM_BUFFER_SPARSE_FILE_DEFAULT;\n    _context->perform_storage_checks = AERON_PERFORM_STORAGE_CHECKS_DEFAULT;\n    _context->spies_simulate_connection = AERON_SPIES_SIMULATE_CONNECTION_DEFAULT;\n    _context->print_configuration_on_start = AERON_PRINT_CONFIGURATION_DEFAULT;\n    _context->reliable_stream = AERON_RELIABLE_STREAM_DEFAULT;\n    _context->tether_subscriptions = AERON_TETHER_SUBSCRIPTIONS_DEFAULT;\n    _context->rejoin_stream = AERON_REJOIN_STREAM_DEFAULT;\n    _context->ats_enabled = false;\n    _context->driver_timeout_ms = AERON_DRIVER_TIMEOUT_MS_DEFAULT;\n    _context->to_driver_buffer_length = AERON_TO_CONDUCTOR_BUFFER_LENGTH_DEFAULT;\n    _context->to_clients_buffer_length = AERON_TO_CLIENTS_BUFFER_LENGTH_DEFAULT;\n    _context->counters_values_buffer_length = AERON_COUNTERS_VALUES_BUFFER_LENGTH_DEFAULT;\n    _context->error_buffer_length = AERON_ERROR_BUFFER_LENGTH_DEFAULT;\n    _context->client_liveness_timeout_ns = AERON_CLIENT_LIVENESS_TIMEOUT_NS_DEFAULT;\n    _context->timer_interval_ns = AERON_TIMER_INTERVAL_NS_DEFAULT;\n    _context->term_buffer_length = AERON_TERM_BUFFER_LENGTH_DEFAULT;\n    _context->ipc_term_buffer_length = AERON_IPC_TERM_BUFFER_LENGTH_DEFAULT;\n    _context->mtu_length = AERON_MTU_LENGTH_DEFAULT;\n    _context->ipc_mtu_length = AERON_IPC_MTU_LENGTH_DEFAULT;\n    _context->ipc_publication_window_length = AERON_IPC_PUBLICATION_TERM_WINDOW_LENGTH_DEFAULT;\n    _context->publication_window_length = AERON_PUBLICATION_TERM_WINDOW_LENGTH_DEFAULT;\n    _context->publication_linger_timeout_ns = AERON_PUBLICATION_LINGER_TIMEOUT_NS_DEFAULT;\n    _context->socket_rcvbuf = AERON_SOCKET_SO_RCVBUF_DEFAULT;\n    _context->socket_sndbuf = AERON_SOCKET_SO_SNDBUF_DEFAULT;\n    _context->multicast_ttl = AERON_SOCKET_MULTICAST_TTL_DEFAULT;\n    _context->receiver_group_tag.is_present = AERON_RECEIVER_GROUP_TAG_IS_PRESENT_DEFAULT;\n    _context->receiver_group_tag.value = AERON_RECEIVER_GROUP_TAG_VALUE_DEFAULT;\n    _context->flow_control.group_tag = AERON_FLOW_CONTROL_GROUP_TAG_DEFAULT;\n    _context->flow_control.group_min_size = AERON_FLOW_CONTROL_GROUP_MIN_SIZE_DEFAULT;\n    _context->flow_control.receiver_timeout_ns = AERON_FLOW_CONTROL_RECEIVER_TIMEOUT_NS_DEFAULT;\n    _context->send_to_sm_poll_ratio = AERON_SEND_TO_STATUS_POLL_RATIO_DEFAULT;\n    _context->status_message_timeout_ns = AERON_RCV_STATUS_MESSAGE_TIMEOUT_NS_DEFAULT;\n    _context->image_liveness_timeout_ns = AERON_IMAGE_LIVENESS_TIMEOUT_NS_DEFAULT;\n    _context->initial_window_length = AERON_RCV_INITIAL_WINDOW_LENGTH_DEFAULT;\n    _context->loss_report_length = AERON_LOSS_REPORT_BUFFER_LENGTH_DEFAULT;\n    _context->file_page_size = AERON_FILE_PAGE_SIZE_DEFAULT;\n    _context->low_file_store_warning_threshold = AERON_LOW_FILE_STORE_WARNING_THRESHOLD_DEFAULT;\n    _context->publication_unblock_timeout_ns = AERON_PUBLICATION_UNBLOCK_TIMEOUT_NS_DEFAULT;\n    _context->publication_connection_timeout_ns = AERON_PUBLICATION_CONNECTION_TIMEOUT_NS_DEFAULT;\n    _context->counter_free_to_reuse_ns = AERON_COUNTERS_FREE_TO_REUSE_TIMEOUT_NS_DEFAULT;\n    _context->untethered_window_limit_timeout_ns = AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_DEFAULT;\n    _context->untethered_linger_timeout_ns = AERON_NULL_VALUE;\n    _context->untethered_resting_timeout_ns = AERON_UNTETHERED_RESTING_TIMEOUT_NS_DEFAULT;\n    _context->max_resend = AERON_RETRANSMIT_HANDLER_MAX_RESEND;\n    _context->retransmit_unicast_delay_ns = AERON_RETRANSMIT_UNICAST_DELAY_NS_DEFAULT;\n    _context->retransmit_unicast_linger_ns = AERON_RETRANSMIT_UNICAST_LINGER_NS_DEFAULT;\n    _context->nak_multicast_group_size = AERON_NAK_MULTICAST_GROUP_SIZE_DEFAULT;\n    _context->nak_multicast_max_backoff_ns = AERON_NAK_MULTICAST_MAX_BACKOFF_NS_DEFAULT;\n    _context->multicast_flow_control_rrwm = AERON_MULTICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE;\n    _context->unicast_flow_control_rrwm = AERON_UNICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE;\n    _context->nak_unicast_delay_ns = AERON_NAK_UNICAST_DELAY_NS_DEFAULT;\n    _context->nak_unicast_retry_delay_ratio = AERON_NAK_UNICAST_RETRY_DELAY_RATIO_DEFAULT;\n    _context->publication_reserved_session_id_low = AERON_PUBLICATION_RESERVED_SESSION_ID_LOW_DEFAULT;\n    _context->publication_reserved_session_id_high = AERON_PUBLICATION_RESERVED_SESSION_ID_HIGH_DEFAULT;\n    _context->resolver_name = NULL;\n    _context->resolver_interface = NULL;\n    _context->resolver_bootstrap_neighbor = NULL;\n    _context->name_resolver_init_args = NULL;\n    _context->re_resolution_check_interval_ns = AERON_DRIVER_RERESOLUTION_CHECK_INTERVAL_NS_DEFAULT;\n    _context->conductor_duty_cycle_stall_tracker.cycle_threshold_ns = AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD_NS_DEFAULT;\n    _context->sender_duty_cycle_stall_tracker.cycle_threshold_ns = AERON_DRIVER_SENDER_CYCLE_THRESHOLD_NS_DEFAULT;\n    _context->receiver_duty_cycle_stall_tracker.cycle_threshold_ns = AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD_NS_DEFAULT;\n    _context->name_resolver_time_stall_tracker.cycle_threshold_ns = AERON_DRIVER_NAME_RESOLVER_THRESHOLD_NS_DEFAULT;\n    _context->receiver_io_vector_capacity = AERON_RECEIVER_IO_VECTOR_CAPACITY_DEFAULT;\n    _context->sender_io_vector_capacity = AERON_SENDER_IO_VECTOR_CAPACITY_DEFAULT;\n    _context->network_publication_max_messages_per_send = AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_DEFAULT;\n    _context->resource_free_limit = AERON_DRIVER_RESOURCE_FREE_LIMIT_DEFAULT;\n    _context->async_executor_threads = AERON_DRIVER_ASYNC_EXECUTOR_THREADS_DEFAULT;\n    _context->connect_enabled = AERON_DRIVER_CONNECT_DEFAULT;\n    _context->conductor_cpu_affinity_no = AERON_CPU_AFFINITY_DEFAULT;\n    _context->sender_cpu_affinity_no = AERON_CPU_AFFINITY_DEFAULT;\n    _context->receiver_cpu_affinity_no = AERON_CPU_AFFINITY_DEFAULT;\n    _context->enable_experimental_features = AERON_ENABLE_EXPERIMENTAL_FEATURES_DEFAULT;\n    _context->stream_session_limit = AERON_DRIVER_STREAM_SESSION_LIMIT_DEFAULT;\n\n    char *value = NULL;\n\n    if ((value = getenv(AERON_DRIVER_DYNAMIC_LIBRARIES_ENV_VAR)))\n    {\n        if (aeron_dl_load_libs(&_context->dynamic_libs, value) < 0)\n        {\n            goto error;\n        }\n    }\n\n    if ((value = getenv(AERON_DIR_ENV_VAR)))\n    {\n        if (aeron_driver_context_set_dir(_context, value) < 0)\n        {\n            goto error;\n        }\n    }\n\n    if ((value = getenv(AERON_AGENT_ON_START_FUNCTION_ENV_VAR)))\n    {\n        if ((_context->agent_on_start_func = aeron_agent_on_start_load(value)) == NULL)\n        {\n            goto error;\n        }\n    }\n\n    if ((value = getenv(AERON_UNICAST_FLOWCONTROL_SUPPLIER_ENV_VAR)))\n    {\n        if ((_context->unicast_flow_control_supplier_func = aeron_flow_control_strategy_supplier_load(value)) == NULL)\n        {\n            goto error;\n        }\n    }\n\n    if ((value = getenv(AERON_MULTICAST_FLOWCONTROL_SUPPLIER_ENV_VAR)))\n    {\n        if ((_context->multicast_flow_control_supplier_func = aeron_flow_control_strategy_supplier_load(value)) == NULL)\n        {\n            goto error;\n        }\n    }\n\n    if ((value = getenv(AERON_CONGESTIONCONTROL_SUPPLIER_ENV_VAR)))\n    {\n        if ((_context->congestion_control_supplier_func =\n            aeron_congestion_control_strategy_supplier_load(value)) == NULL)\n        {\n            goto error;\n        }\n    }\n\n    if ((value = getenv(AERON_NAME_RESOLVER_SUPPLIER_ENV_VAR)))\n    {\n        if ((_context->name_resolver_supplier_func = aeron_name_resolver_supplier_load(value)) == NULL)\n        {\n            goto error;\n        }\n    }\n\n    _context->resolver_name = getenv(AERON_DRIVER_RESOLVER_NAME_ENV_VAR);\n    _context->resolver_interface = getenv(AERON_DRIVER_RESOLVER_INTERFACE_ENV_VAR);\n    _context->resolver_bootstrap_neighbor = getenv(AERON_DRIVER_RESOLVER_BOOTSTRAP_NEIGHBOR_ENV_VAR);\n    _context->name_resolver_init_args = getenv(AERON_NAME_RESOLVER_INIT_ARGS_ENV_VAR);\n\n    if (NULL != _context->resolver_interface)\n    {\n        if (NULL == _context->resolver_name || 0 == strlen(_context->resolver_name))\n        {\n            AERON_SET_ERR(EINVAL, \"%s\", \"`resolverName` is required when `resolverInterface` is set\");\n            goto error;\n        }\n    }\n\n    _context->dirs_delete_on_start = aeron_parse_bool(\n        getenv(AERON_DIR_DELETE_ON_START_ENV_VAR), _context->dirs_delete_on_start);\n\n    _context->dirs_delete_on_shutdown = aeron_parse_bool(\n        getenv(AERON_DIR_DELETE_ON_SHUTDOWN_ENV_VAR), _context->dirs_delete_on_shutdown);\n\n    _context->warn_if_dirs_exist = aeron_parse_bool(\n        getenv(AERON_DIR_WARN_IF_EXISTS_ENV_VAR), _context->warn_if_dirs_exist);\n\n    _context->term_buffer_sparse_file = aeron_parse_bool(\n        getenv(AERON_TERM_BUFFER_SPARSE_FILE_ENV_VAR), _context->term_buffer_sparse_file);\n\n    _context->perform_storage_checks = aeron_parse_bool(\n        getenv(AERON_PERFORM_STORAGE_CHECKS_ENV_VAR), _context->perform_storage_checks);\n\n    _context->spies_simulate_connection = aeron_parse_bool(\n        getenv(AERON_SPIES_SIMULATE_CONNECTION_ENV_VAR), _context->spies_simulate_connection);\n\n    _context->print_configuration_on_start = aeron_parse_bool(\n        getenv(AERON_PRINT_CONFIGURATION_ON_START_ENV_VAR), _context->print_configuration_on_start);\n\n    _context->reliable_stream = aeron_parse_bool(\n        getenv(AERON_RELIABLE_STREAM_ENV_VAR), _context->reliable_stream);\n\n    _context->tether_subscriptions = aeron_parse_bool(\n        getenv(AERON_TETHER_SUBSCRIPTIONS_ENV_VAR), _context->tether_subscriptions);\n\n    _context->rejoin_stream = aeron_parse_bool(\n        getenv(AERON_REJOIN_STREAM_ENV_VAR), _context->rejoin_stream);\n\n    _context->connect_enabled = aeron_parse_bool(getenv(AERON_DRIVER_CONNECT_ENV_VAR), _context->connect_enabled);\n\n    _context->to_driver_buffer_length = aeron_config_parse_size64(\n        AERON_TO_CONDUCTOR_BUFFER_LENGTH_ENV_VAR,\n        getenv(AERON_TO_CONDUCTOR_BUFFER_LENGTH_ENV_VAR),\n        _context->to_driver_buffer_length,\n        AERON_TO_CONDUCTOR_BUFFER_LENGTH_DEFAULT,\n        INT32_MAX);\n\n    _context->to_clients_buffer_length = aeron_config_parse_size64(\n        AERON_TO_CLIENTS_BUFFER_LENGTH_ENV_VAR,\n        getenv(AERON_TO_CLIENTS_BUFFER_LENGTH_ENV_VAR),\n        _context->to_clients_buffer_length,\n        AERON_TO_CLIENTS_BUFFER_LENGTH_DEFAULT,\n        INT32_MAX);\n\n    _context->counters_values_buffer_length = aeron_config_parse_size64(\n        AERON_COUNTERS_VALUES_BUFFER_LENGTH_ENV_VAR,\n        getenv(AERON_COUNTERS_VALUES_BUFFER_LENGTH_ENV_VAR),\n        _context->counters_values_buffer_length,\n        AERON_COUNTERS_VALUES_BUFFER_LENGTH_MIN,\n        AERON_COUNTERS_VALUES_BUFFER_LENGTH_MAX);\n\n    _context->error_buffer_length = aeron_config_parse_size64(\n        AERON_ERROR_BUFFER_LENGTH_ENV_VAR,\n        getenv(AERON_ERROR_BUFFER_LENGTH_ENV_VAR),\n        _context->error_buffer_length,\n        AERON_ERROR_BUFFER_LENGTH_DEFAULT,\n        INT32_MAX);\n\n    _context->client_liveness_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_CLIENT_LIVENESS_TIMEOUT_ENV_VAR,\n        getenv(AERON_CLIENT_LIVENESS_TIMEOUT_ENV_VAR),\n        _context->client_liveness_timeout_ns,\n        1000,\n        INT64_MAX);\n\n    _context->publication_linger_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_PUBLICATION_LINGER_TIMEOUT_ENV_VAR,\n        getenv(AERON_PUBLICATION_LINGER_TIMEOUT_ENV_VAR),\n        _context->publication_linger_timeout_ns,\n        1000,\n        INT64_MAX);\n\n    _context->term_buffer_length = aeron_config_parse_size64(\n        AERON_TERM_BUFFER_LENGTH_ENV_VAR,\n        getenv(AERON_TERM_BUFFER_LENGTH_ENV_VAR),\n        _context->term_buffer_length,\n        1024,\n        INT32_MAX);\n\n    _context->ipc_term_buffer_length = aeron_config_parse_size64(\n        AERON_IPC_TERM_BUFFER_LENGTH_ENV_VAR,\n        getenv(AERON_IPC_TERM_BUFFER_LENGTH_ENV_VAR),\n        _context->ipc_term_buffer_length,\n        1024,\n        INT32_MAX);\n\n    _context->mtu_length = aeron_config_parse_size64(\n        AERON_MTU_LENGTH_ENV_VAR,\n        getenv(AERON_MTU_LENGTH_ENV_VAR),\n        _context->mtu_length,\n        AERON_DATA_HEADER_LENGTH,\n        AERON_MAX_UDP_PAYLOAD_LENGTH);\n\n    _context->ipc_mtu_length = aeron_config_parse_size64(\n        AERON_IPC_MTU_LENGTH_ENV_VAR,\n        getenv(AERON_IPC_MTU_LENGTH_ENV_VAR),\n        _context->ipc_mtu_length,\n        AERON_DATA_HEADER_LENGTH,\n        AERON_MAX_UDP_PAYLOAD_LENGTH);\n\n    _context->ipc_publication_window_length = aeron_config_parse_size64(\n        AERON_IPC_PUBLICATION_TERM_WINDOW_LENGTH_ENV_VAR,\n        getenv(AERON_IPC_PUBLICATION_TERM_WINDOW_LENGTH_ENV_VAR),\n        _context->ipc_publication_window_length,\n        0,\n        AERON_LOGBUFFER_TERM_MAX_LENGTH);\n\n    _context->publication_window_length = aeron_config_parse_size64(\n        AERON_PUBLICATION_TERM_WINDOW_LENGTH_ENV_VAR,\n        getenv(AERON_PUBLICATION_TERM_WINDOW_LENGTH_ENV_VAR),\n        _context->publication_window_length,\n        0,\n        AERON_LOGBUFFER_TERM_MAX_LENGTH);\n\n    _context->socket_rcvbuf = aeron_config_parse_size64(\n        AERON_SOCKET_SO_RCVBUF_ENV_VAR,\n        getenv(AERON_SOCKET_SO_RCVBUF_ENV_VAR),\n        _context->socket_rcvbuf,\n        0,\n        INT32_MAX);\n\n    _context->socket_sndbuf = aeron_config_parse_size64(\n        AERON_SOCKET_SO_SNDBUF_ENV_VAR,\n        getenv(AERON_SOCKET_SO_SNDBUF_ENV_VAR),\n        _context->socket_sndbuf,\n        0,\n        INT32_MAX);\n\n    _context->multicast_ttl = (uint8_t)aeron_config_parse_uint64(\n        AERON_SOCKET_MULTICAST_TTL_ENV_VAR,\n        getenv(AERON_SOCKET_MULTICAST_TTL_ENV_VAR),\n        _context->multicast_ttl,\n        0,\n        255);\n\n    _context->conductor_cpu_affinity_no = aeron_config_parse_int32(\n        AERON_CONDUCTOR_CPU_AFFINITY_ENV_VAR,\n        getenv(AERON_CONDUCTOR_CPU_AFFINITY_ENV_VAR),\n        _context->conductor_cpu_affinity_no,\n        -1,\n        255);\n    _context->receiver_cpu_affinity_no = aeron_config_parse_int32(\n        AERON_RECEIVER_CPU_AFFINITY_ENV_VAR,\n        getenv(AERON_RECEIVER_CPU_AFFINITY_ENV_VAR),\n        _context->receiver_cpu_affinity_no,\n        -1,\n        255);\n    _context->sender_cpu_affinity_no = aeron_config_parse_int32(\n        AERON_SENDER_CPU_AFFINITY_ENV_VAR,\n        getenv(AERON_SENDER_CPU_AFFINITY_ENV_VAR),\n        _context->sender_cpu_affinity_no,\n        -1,\n        255);\n\n    _context->send_to_sm_poll_ratio = (uint8_t)aeron_config_parse_uint64(\n        AERON_SEND_TO_STATUS_POLL_RATIO_ENV_VAR,\n        getenv(AERON_SEND_TO_STATUS_POLL_RATIO_ENV_VAR),\n        _context->send_to_sm_poll_ratio,\n        1,\n        INT32_MAX);\n\n    _context->driver_timeout_ms = aeron_config_parse_uint64(\n        AERON_DRIVER_TIMEOUT_ENV_VAR,\n        getenv(AERON_DRIVER_TIMEOUT_ENV_VAR),\n        _context->driver_timeout_ms,\n        0,\n        INT64_MAX);\n\n    _context->status_message_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_RCV_STATUS_MESSAGE_TIMEOUT_ENV_VAR,\n        getenv(AERON_RCV_STATUS_MESSAGE_TIMEOUT_ENV_VAR),\n        _context->status_message_timeout_ns,\n        1000,\n        INT64_MAX);\n\n    _context->image_liveness_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_IMAGE_LIVENESS_TIMEOUT_ENV_VAR,\n        getenv(AERON_IMAGE_LIVENESS_TIMEOUT_ENV_VAR),\n        _context->image_liveness_timeout_ns,\n        1000,\n        INT64_MAX);\n\n    _context->initial_window_length = aeron_config_parse_size64(\n        AERON_RCV_INITIAL_WINDOW_LENGTH_ENV_VAR,\n        getenv(AERON_RCV_INITIAL_WINDOW_LENGTH_ENV_VAR),\n        _context->initial_window_length,\n        256,\n        INT32_MAX);\n\n    _context->loss_report_length = aeron_config_parse_size64(\n        AERON_LOSS_REPORT_BUFFER_LENGTH_ENV_VAR,\n        getenv(AERON_LOSS_REPORT_BUFFER_LENGTH_ENV_VAR),\n        _context->loss_report_length,\n        1024,\n        INT32_MAX);\n\n    _context->file_page_size = aeron_config_parse_size64(\n        AERON_FILE_PAGE_SIZE_ENV_VAR,\n        getenv(AERON_FILE_PAGE_SIZE_ENV_VAR),\n        _context->file_page_size,\n        4 * 1024,\n        INT32_MAX);\n\n    _context->low_file_store_warning_threshold = aeron_config_parse_size64(\n        AERON_LOW_FILE_STORE_WARNING_THRESHOLD_ENV_VAR,\n        getenv(AERON_LOW_FILE_STORE_WARNING_THRESHOLD_ENV_VAR),\n        _context->low_file_store_warning_threshold,\n        0,\n        INT64_MAX);\n\n    _context->publication_unblock_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_PUBLICATION_UNBLOCK_TIMEOUT_ENV_VAR,\n        getenv(AERON_PUBLICATION_UNBLOCK_TIMEOUT_ENV_VAR),\n        _context->publication_unblock_timeout_ns,\n        1000,\n        INT64_MAX);\n\n    _context->publication_connection_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_PUBLICATION_CONNECTION_TIMEOUT_ENV_VAR,\n        getenv(AERON_PUBLICATION_CONNECTION_TIMEOUT_ENV_VAR),\n        _context->publication_connection_timeout_ns,\n        1000,\n        INT64_MAX);\n\n    _context->timer_interval_ns = aeron_config_parse_duration_ns(\n        AERON_TIMER_INTERVAL_ENV_VAR,\n        getenv(AERON_TIMER_INTERVAL_ENV_VAR),\n        _context->timer_interval_ns,\n        1000,\n        INT64_MAX);\n\n    _context->counter_free_to_reuse_ns = aeron_config_parse_duration_ns(\n        AERON_COUNTERS_FREE_TO_REUSE_TIMEOUT_ENV_VAR,\n        getenv(AERON_COUNTERS_FREE_TO_REUSE_TIMEOUT_ENV_VAR),\n        _context->counter_free_to_reuse_ns,\n        0,\n        INT64_MAX);\n\n    _context->untethered_window_limit_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT_ENV_VAR,\n        getenv(AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT_ENV_VAR),\n        _context->untethered_window_limit_timeout_ns,\n        0,\n        INT64_MAX);\n\n    const uint64_t parsed_untethered_linger_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_UNTETHERED_LINGER_TIMEOUT_ENV_VAR,\n        getenv(AERON_UNTETHERED_LINGER_TIMEOUT_ENV_VAR),\n        UINT64_MAX,\n        0,\n        INT64_MAX);\n\n    if (UINT64_MAX != parsed_untethered_linger_timeout_ns)\n    {\n        _context->untethered_linger_timeout_ns = (int64_t)parsed_untethered_linger_timeout_ns;\n    }\n\n    _context->untethered_resting_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_UNTETHERED_RESTING_TIMEOUT_ENV_VAR,\n        getenv(AERON_UNTETHERED_RESTING_TIMEOUT_ENV_VAR),\n        _context->untethered_resting_timeout_ns,\n        0,\n        INT64_MAX);\n\n    _context->max_resend = aeron_config_parse_uint32(\n        AERON_MAX_RESEND_ENV_VAR,\n        getenv(AERON_MAX_RESEND_ENV_VAR),\n        _context->max_resend,\n        1,\n        AERON_RETRANSMIT_HANDLER_MAX_RESEND_MAX);\n\n    _context->retransmit_unicast_delay_ns = aeron_config_parse_duration_ns(\n        AERON_RETRANSMIT_UNICAST_DELAY_ENV_VAR,\n        getenv(AERON_RETRANSMIT_UNICAST_DELAY_ENV_VAR),\n        _context->retransmit_unicast_delay_ns,\n        0,\n        INT64_MAX);\n\n    _context->retransmit_unicast_linger_ns = aeron_config_parse_duration_ns(\n        AERON_RETRANSMIT_UNICAST_LINGER_ENV_VAR,\n        getenv(AERON_RETRANSMIT_UNICAST_LINGER_ENV_VAR),\n        _context->retransmit_unicast_linger_ns,\n        1000,\n        INT64_MAX);\n\n    _context->nak_multicast_group_size = (size_t)aeron_config_parse_uint64(\n        AERON_NAK_MULTICAST_GROUP_SIZE_ENV_VAR,\n        getenv(AERON_NAK_MULTICAST_GROUP_SIZE_ENV_VAR),\n        _context->nak_multicast_group_size,\n        1,\n        INT32_MAX);\n\n    _context->nak_multicast_max_backoff_ns = aeron_config_parse_duration_ns(\n        AERON_NAK_MULTICAST_MAX_BACKOFF_ENV_VAR,\n        getenv(AERON_NAK_MULTICAST_MAX_BACKOFF_ENV_VAR),\n        _context->nak_multicast_max_backoff_ns,\n        1000,\n        INT64_MAX);\n\n    _context->nak_unicast_delay_ns = aeron_config_parse_duration_ns(\n        AERON_NAK_UNICAST_DELAY_ENV_VAR,\n        getenv(AERON_NAK_UNICAST_DELAY_ENV_VAR),\n        _context->nak_unicast_delay_ns,\n        AERON_NAK_UNICAST_DELAY_NS_MIN,\n        INT64_MAX);\n\n    _context->nak_unicast_retry_delay_ratio = aeron_config_parse_int64(\n        AERON_NAK_UNICAST_RETRY_DELAY_RATIO_ENV_VAR,\n        getenv(AERON_NAK_UNICAST_RETRY_DELAY_RATIO_ENV_VAR),\n        (int64_t)_context->nak_unicast_retry_delay_ratio,\n        1,\n        INT64_MAX);\n\n    if ((_context->nak_unicast_delay_ns * _context->nak_unicast_retry_delay_ratio) > (uint64_t)INT64_MAX)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"nak_unicast_delay_ns (%\" PRIu64 \") * nak_unicast_retry_delay_ratio (%\" PRIu64 \") exceeds %\" PRIi64 \"\",\n            _context->nak_unicast_delay_ns,\n            _context->nak_unicast_retry_delay_ratio,\n            INT64_MAX);\n        goto error;\n    }\n\n    _context->publication_reserved_session_id_low = aeron_config_parse_int32(\n        AERON_PUBLICATION_RESERVED_SESSION_ID_LOW_ENV_VAR,\n        getenv(AERON_PUBLICATION_RESERVED_SESSION_ID_LOW_ENV_VAR),\n        _context->publication_reserved_session_id_low,\n        INT32_MIN,\n        INT32_MAX);\n\n    _context->publication_reserved_session_id_high = aeron_config_parse_int32(\n        AERON_PUBLICATION_RESERVED_SESSION_ID_HIGH_ENV_VAR,\n        getenv(AERON_PUBLICATION_RESERVED_SESSION_ID_HIGH_ENV_VAR),\n        _context->publication_reserved_session_id_high,\n        INT32_MIN,\n        INT32_MAX);\n\n    const char *group_tag_str = getenv(AERON_RECEIVER_GROUP_TAG_ENV_VAR);\n    if (NULL != group_tag_str)\n    {\n        _context->receiver_group_tag.is_present = true;\n        _context->receiver_group_tag.value = aeron_config_parse_int64(\n            AERON_RECEIVER_GROUP_TAG_ENV_VAR,\n            getenv(AERON_RECEIVER_GROUP_TAG_ENV_VAR),\n            _context->receiver_group_tag.value,\n            INT64_MIN,\n            INT64_MAX);\n    }\n\n    _context->flow_control.group_tag = aeron_config_parse_int64(\n        AERON_FLOW_CONTROL_GROUP_TAG_ENV_VAR,\n        getenv(AERON_FLOW_CONTROL_GROUP_TAG_ENV_VAR),\n        _context->flow_control.group_tag,\n        INT64_MIN,\n        INT64_MAX);\n\n    _context->flow_control.group_min_size = aeron_config_parse_int32(\n        AERON_FLOW_CONTROL_GROUP_MIN_SIZE_ENV_VAR,\n        getenv(AERON_FLOW_CONTROL_GROUP_MIN_SIZE_ENV_VAR),\n        _context->flow_control.group_min_size,\n        0,\n        INT32_MAX);\n\n    _context->flow_control.receiver_timeout_ns = aeron_config_parse_duration_ns(\n        AERON_FLOW_CONTROL_RECEIVER_TIMEOUT_ENV_VAR,\n        getenv(AERON_FLOW_CONTROL_RECEIVER_TIMEOUT_ENV_VAR),\n        _context->flow_control.receiver_timeout_ns,\n        0,\n        INT64_MAX);\n\n    _context->re_resolution_check_interval_ns = aeron_config_parse_duration_ns(\n        AERON_DRIVER_RERESOLUTION_CHECK_INTERVAL_ENV_VAR,\n        getenv(AERON_DRIVER_RERESOLUTION_CHECK_INTERVAL_ENV_VAR),\n        _context->re_resolution_check_interval_ns,\n        0,\n        INT64_MAX);\n\n    _context->conductor_duty_cycle_stall_tracker.cycle_threshold_ns = aeron_config_parse_duration_ns(\n        AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD_ENV_VAR,\n        getenv(AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD_ENV_VAR),\n        _context->conductor_duty_cycle_stall_tracker.cycle_threshold_ns,\n        0,\n        LLONG_MAX);\n\n    _context->sender_duty_cycle_stall_tracker.cycle_threshold_ns = aeron_config_parse_duration_ns(\n        AERON_DRIVER_SENDER_CYCLE_THRESHOLD_ENV_VAR,\n        getenv(AERON_DRIVER_SENDER_CYCLE_THRESHOLD_ENV_VAR),\n        _context->sender_duty_cycle_stall_tracker.cycle_threshold_ns,\n        0,\n        LLONG_MAX);\n\n    _context->receiver_duty_cycle_stall_tracker.cycle_threshold_ns = aeron_config_parse_duration_ns(\n        AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD_ENV_VAR,\n        getenv(AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD_ENV_VAR),\n        _context->receiver_duty_cycle_stall_tracker.cycle_threshold_ns,\n        0,\n        LLONG_MAX);\n\n    _context->name_resolver_time_stall_tracker.cycle_threshold_ns = aeron_config_parse_duration_ns(\n        AERON_DRIVER_NAME_RESOLVER_THRESHOLD_ENV_VAR,\n        getenv(AERON_DRIVER_NAME_RESOLVER_THRESHOLD_ENV_VAR),\n        _context->name_resolver_time_stall_tracker.cycle_threshold_ns,\n        0,\n        LLONG_MAX);\n\n    if ((value = getenv(AERON_DRIVER_SENDER_WILDCARD_PORT_RANGE_ENV_VAR)))\n    {\n        uint16_t low_port = 0, high_port = 0;\n\n        if (aeron_parse_port_range(value, &low_port, &high_port) < 0)\n        {\n            AERON_APPEND_ERR(\"sender wildcard port range \\\"%s\\\" is invalid\", value);\n            goto error;\n        }\n\n        aeron_wildcard_port_manager_set_range(&_context->sender_wildcard_port_manager, low_port, high_port);\n    }\n\n    if ((value = getenv(AERON_DRIVER_RECEIVER_WILDCARD_PORT_RANGE_ENV_VAR)))\n    {\n        uint16_t low_port = 0, high_port = 0;\n\n        if (aeron_parse_port_range(value, &low_port, &high_port) < 0)\n        {\n            AERON_APPEND_ERR(\"receiver wildcard port range \\\"%s\\\" is invalid\", value);\n            goto error;\n        }\n\n        aeron_wildcard_port_manager_set_range(&_context->receiver_wildcard_port_manager, low_port, high_port);\n    }\n\n    _context->receiver_io_vector_capacity = aeron_config_parse_uint32(\n        AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR,\n        getenv(AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR),\n        (int32_t)_context->receiver_io_vector_capacity,\n        1,\n        AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX);\n\n    _context->sender_io_vector_capacity = aeron_config_parse_uint32(\n        AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR,\n        getenv(AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR),\n        _context->sender_io_vector_capacity,\n        1,\n        AERON_DRIVER_SENDER_IO_VECTOR_LENGTH_MAX);\n\n    _context->network_publication_max_messages_per_send = aeron_config_parse_uint32(\n        AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_ENV_VAR,\n        getenv(AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_ENV_VAR),\n        _context->network_publication_max_messages_per_send,\n        1,\n        AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND);\n\n    _context->resource_free_limit = aeron_config_parse_uint32(\n        AERON_DRIVER_RESOURCE_FREE_LIMIT_ENV_VAR,\n        getenv(AERON_DRIVER_RESOURCE_FREE_LIMIT_ENV_VAR),\n        _context->resource_free_limit,\n        1,\n        INT32_MAX);\n\n    _context->async_executor_threads = aeron_config_parse_uint32(\n        AERON_DRIVER_ASYNC_EXECUTOR_THREADS_ENV_VAR,\n        getenv(AERON_DRIVER_ASYNC_EXECUTOR_THREADS_ENV_VAR),\n        _context->async_executor_threads,\n        0,\n        1);\n\n    _context->enable_experimental_features = aeron_parse_bool(\n        getenv(AERON_ENABLE_EXPERIMENTAL_FEATURES_ENV_VAR), _context->enable_experimental_features);\n\n    _context->stream_session_limit = aeron_config_parse_int32(\n        AERON_DRIVER_STREAM_SESSION_LIMIT_ENV_VAR,\n        getenv(AERON_DRIVER_STREAM_SESSION_LIMIT_ENV_VAR),\n        _context->stream_session_limit,\n        1,\n        INT32_MAX);\n\n    _context->to_driver_buffer = NULL;\n    _context->to_clients_buffer = NULL;\n    _context->counters_values_buffer = NULL;\n    _context->counters_metadata_buffer = NULL;\n    _context->error_buffer = NULL;\n\n    _context->nano_clock = aeron_nano_clock;\n    _context->epoch_clock = aeron_epoch_clock;\n    if (aeron_clock_cache_alloc(&_context->cached_clock) < 0)\n    {\n        goto error;\n    }\n    if (aeron_clock_cache_alloc(&_context->sender_cached_clock) < 0)\n    {\n        goto error;\n    }\n    if (aeron_clock_cache_alloc(&_context->receiver_cached_clock) < 0)\n    {\n        goto error;\n    }\n\n    _context->conductor_idle_strategy_name = aeron_strndup(\"backoff\", 10);\n    _context->shared_idle_strategy_name = aeron_strndup(\"backoff\", 10);\n    _context->shared_network_idle_strategy_name = aeron_strndup(\"backoff\", 10);\n    _context->sender_idle_strategy_name = aeron_strndup(\"backoff\", 10);\n    _context->receiver_idle_strategy_name = aeron_strndup(\"backoff\", 10);\n\n    _context->conductor_idle_strategy_init_args =\n        AERON_CONFIG_STRNDUP_GETENV_OR_NULL(AERON_CONDUCTOR_IDLE_STRATEGY_INIT_ARGS_ENV_VAR);\n    if ((_context->conductor_idle_strategy_func = aeron_idle_strategy_load(\n        AERON_CONFIG_GETENV_OR_DEFAULT(AERON_CONDUCTOR_IDLE_STRATEGY_ENV_VAR, \"backoff\"),\n        &_context->conductor_idle_strategy_state,\n        AERON_CONDUCTOR_IDLE_STRATEGY_ENV_VAR,\n        _context->conductor_idle_strategy_init_args)) == NULL)\n    {\n        goto error;\n    }\n\n    _context->shared_idle_strategy_init_args =\n        AERON_CONFIG_STRNDUP_GETENV_OR_NULL(AERON_SHARED_IDLE_STRATEGY_ENV_INIT_ARGS_VAR);\n    if ((_context->shared_idle_strategy_func = aeron_idle_strategy_load(\n        AERON_CONFIG_GETENV_OR_DEFAULT(AERON_SHARED_IDLE_STRATEGY_ENV_VAR, \"backoff\"),\n        &_context->shared_idle_strategy_state,\n        AERON_SHARED_IDLE_STRATEGY_ENV_VAR,\n        _context->shared_idle_strategy_init_args)) == NULL)\n    {\n        goto error;\n    }\n\n    _context->shared_network_idle_strategy_init_args =\n        AERON_CONFIG_STRNDUP_GETENV_OR_NULL(AERON_SHAREDNETWORK_IDLE_STRATEGY_INIT_ARGS_ENV_VAR);\n    if ((_context->shared_network_idle_strategy_func = aeron_idle_strategy_load(\n        AERON_CONFIG_GETENV_OR_DEFAULT(AERON_SHAREDNETWORK_IDLE_STRATEGY_ENV_VAR, \"backoff\"),\n        &_context->shared_network_idle_strategy_state,\n        AERON_SHAREDNETWORK_IDLE_STRATEGY_ENV_VAR,\n        _context->shared_network_idle_strategy_init_args)) == NULL)\n    {\n        goto error;\n    }\n\n    _context->sender_idle_strategy_init_args =\n        AERON_CONFIG_STRNDUP_GETENV_OR_NULL(AERON_SENDER_IDLE_STRATEGY_INIT_ARGS_ENV_VAR);\n    if ((_context->sender_idle_strategy_func = aeron_idle_strategy_load(\n        AERON_CONFIG_GETENV_OR_DEFAULT(AERON_SENDER_IDLE_STRATEGY_ENV_VAR, \"backoff\"),\n        &_context->sender_idle_strategy_state,\n        AERON_SENDER_IDLE_STRATEGY_ENV_VAR,\n        _context->sender_idle_strategy_init_args)) == NULL)\n    {\n        goto error;\n    }\n\n    _context->receiver_idle_strategy_init_args =\n        AERON_CONFIG_STRNDUP_GETENV_OR_NULL(AERON_RECEIVER_IDLE_STRATEGY_INIT_ARGS_ENV_VAR);\n    if ((_context->receiver_idle_strategy_func = aeron_idle_strategy_load(\n        AERON_CONFIG_GETENV_OR_DEFAULT(AERON_RECEIVER_IDLE_STRATEGY_ENV_VAR, \"backoff\"),\n        &_context->receiver_idle_strategy_state,\n        AERON_RECEIVER_IDLE_STRATEGY_ENV_VAR,\n        _context->receiver_idle_strategy_init_args)) == NULL)\n    {\n        goto error;\n    }\n\n    _context->usable_fs_space_func = _context->perform_storage_checks ?\n        aeron_usable_fs_space : aeron_usable_fs_space_disabled;\n    _context->raw_log_map_func = aeron_raw_log_map;\n    _context->raw_log_close_func = aeron_raw_log_close;\n    _context->raw_log_free_func = aeron_raw_log_free;\n\n    _context->log.to_driver_interceptor = aeron_driver_conductor_to_driver_interceptor_null;\n    _context->log.to_client_interceptor = aeron_driver_conductor_to_client_interceptor_null;\n\n    _context->log.remove_publication_cleanup = aeron_driver_conductor_remove_publication_cleanup_null;\n    _context->log.remove_subscription_cleanup = aeron_driver_conductor_remove_subscription_cleanup_null;\n    _context->log.remove_image_cleanup = aeron_driver_conductor_remove_image_cleanup_null;\n\n    _context->log.sender_proxy_on_add_endpoint = aeron_driver_conductor_on_endpoint_change_null;\n    _context->log.sender_proxy_on_remove_endpoint = aeron_driver_conductor_on_endpoint_change_null;\n    _context->log.receiver_proxy_on_add_endpoint = aeron_driver_conductor_on_endpoint_change_null;\n    _context->log.receiver_proxy_on_remove_endpoint = aeron_driver_conductor_on_endpoint_change_null;\n\n    _context->log.untethered_subscription_on_state_change = aeron_driver_untethered_subscription_state_change_null;\n\n    _context->log.name_resolution_on_neighbor_added = aeron_driver_conductor_name_resolver_on_neighbor_change_null;\n    _context->log.name_resolution_on_neighbor_removed = aeron_driver_conductor_name_resolver_on_neighbor_change_null;\n\n    _context->log.flow_control_on_receiver_added = NULL;\n    _context->log.flow_control_on_receiver_removed = NULL;\n    _context->log.on_name_resolve = NULL;\n\n    _context->log.send_nak_message = NULL;\n    _context->log.on_nak_message = NULL;\n    _context->log.resend = NULL;\n\n    if ((_context->termination_validator_func = aeron_driver_termination_validator_load(\n        AERON_CONFIG_GETENV_OR_DEFAULT(AERON_DRIVER_TERMINATION_VALIDATOR_ENV_VAR, \"deny\"))) == NULL)\n    {\n        goto error;\n    }\n\n    _context->termination_validator_state = NULL;\n\n    _context->termination_hook_func = NULL;\n    _context->termination_hook_state = NULL;\n\n    if ((_context->udp_channel_transport_bindings = aeron_udp_channel_transport_bindings_load_media(\n        AERON_CONFIG_GETENV_OR_DEFAULT(\n            AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA_ENV_VAR,\n            AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA_DEFAULT))) == NULL)\n    {\n        goto error;\n    }\n\n    if ((_context->conductor_udp_channel_transport_bindings = aeron_udp_channel_transport_bindings_load_media(\n        AERON_CONFIG_GETENV_OR_DEFAULT(\n            AERON_CONDUCTOR_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA_ENV_VAR,\n            AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA_DEFAULT))) == NULL)\n    {\n        goto error;\n    }\n\n    if ((value = getenv(AERON_UDP_CHANNEL_OUTGOING_INTERCEPTORS_ENV_VAR)))\n    {\n        if ((_context->udp_channel_outgoing_interceptor_bindings = aeron_udp_channel_interceptor_bindings_load(\n            NULL, value)) == NULL)\n        {\n            goto error;\n        }\n    }\n\n    if ((value = getenv(AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR)))\n    {\n        if ((_context->udp_channel_incoming_interceptor_bindings = aeron_udp_channel_interceptor_bindings_load(\n            NULL, value)) == NULL)\n        {\n            goto error;\n        }\n    }\n\n#ifdef HAVE_UUID_GENERATE\n    uuid_t id;\n    uuid_generate(id);\n\n    struct uuid_as_uint64\n    {\n        uint64_t high;\n        uint64_t low;\n    }\n\n    *id_as_uint64 = (struct uuid_as_uint64 *)&id;\n    _context->next_receiver_id = id_as_uint64->high ^ id_as_uint64->low;\n#else\n    /* pure random id */\n    int64_t receiver_id = 0;\n    do\n    {\n        receiver_id = (int64_t)aeron_randomised_int32() * (int64_t)aeron_randomised_int32();\n    }\n    while (0 == receiver_id);\n    _context->next_receiver_id = receiver_id;\n#endif\n\n    if (aeron_netutil_get_so_buf_lengths(\n        &_context->os_buffer_lengths.default_so_rcvbuf, &_context->os_buffer_lengths.default_so_sndbuf) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to initial context with buffer lengths\");\n        return -1;\n    }\n\n    if (aeron_driver_context_bindings_clientd_create_entries(_context) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate bindings_clientd entries\");\n        goto error;\n    }\n\n    // Should be last in the initialization chain\n    if (getenv(AERON_EVENT_LOG_ENV_VAR))\n    {\n        if (aeron_driver_agent_context_init(_context) < 0)\n        {\n            goto error;\n        }\n    }\n\n    *context = _context;\n    return 0;\n\nerror:\n    aeron_driver_context_close(_context);\n    return -1;\n}\n\nstatic void aeron_driver_context_free_bindings(const aeron_udp_channel_interceptor_bindings_t *bindings)\n{\n    if (NULL != bindings)\n    {\n        aeron_driver_context_free_bindings(bindings->meta_info.next_interceptor_bindings);\n        aeron_free((void *)bindings);\n    }\n}\n\nint aeron_driver_context_run_storage_checks(aeron_driver_context_t *context, uint64_t log_length)\n{\n    if (context->perform_storage_checks)\n    {\n        const uint64_t usable_space = context->usable_fs_space_func(context->aeron_dir);\n        if (usable_space < log_length)\n        {\n            AERON_SET_ERR(\n                -AERON_ERROR_CODE_STORAGE_SPACE,\n                \"insufficient usable storage for new log of length=%\" PRId64 \" usable=%\" PRId64 \" in %s\",\n            log_length, usable_space, context->aeron_dir);\n            return -1;\n        }\n\n        if (usable_space <= context->low_file_store_warning_threshold)\n        {\n            AERON_SET_ERR(\n                -AERON_ERROR_CODE_STORAGE_SPACE,\n                \"WARNING: space is running low: threshold=%\" PRId64 \" usable=%\" PRId64 \" in %s\",\n            context->low_file_store_warning_threshold, usable_space, context->aeron_dir);\n            aeron_distinct_error_log_record(context->error_log, aeron_errcode(), aeron_errmsg());\n            aeron_err_clear();\n        }\n    }\n    return 0;\n}\n\nint aeron_driver_context_bindings_clientd_create_entries(aeron_driver_context_t *context)\n{\n    const aeron_udp_channel_interceptor_bindings_t *interceptor_bindings;\n    aeron_driver_context_bindings_clientd_entry_t *_entries;\n    size_t num_entries = 1;\n\n    interceptor_bindings = context->udp_channel_outgoing_interceptor_bindings;\n    while (NULL != interceptor_bindings)\n    {\n        num_entries++;\n        interceptor_bindings = interceptor_bindings->meta_info.next_interceptor_bindings;\n    }\n\n    interceptor_bindings = context->udp_channel_incoming_interceptor_bindings;\n    while (NULL != interceptor_bindings)\n    {\n        num_entries++;\n        interceptor_bindings = interceptor_bindings->meta_info.next_interceptor_bindings;\n    }\n\n    if (0 == context->num_bindings_clientd_entries)\n    {\n        if (aeron_alloc((void **)&_entries, sizeof(aeron_driver_context_bindings_clientd_entry_t) * num_entries) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"could not allocate context_bindings_clientd_entries\");\n            return -1;\n        }\n\n        for (size_t i = 0; i < num_entries; i++)\n        {\n            _entries[i].name = NULL;\n            _entries[i].clientd = NULL;\n        }\n\n        context->bindings_clientd_entries = _entries;\n    }\n    else if (num_entries > context->num_bindings_clientd_entries)\n    {\n        if (aeron_reallocf(\n            (void **)&context->bindings_clientd_entries,\n            sizeof(aeron_driver_context_bindings_clientd_entry_t) * num_entries) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"could not reallocate context_bindings_clientd_entries\");\n            return -1;\n        }\n\n        for (size_t i = context->num_bindings_clientd_entries; i < num_entries; i++)\n        {\n            context->bindings_clientd_entries[i].name = NULL;\n            context->bindings_clientd_entries[i].clientd = NULL;\n        }\n    }\n\n    context->num_bindings_clientd_entries = num_entries;\n\n    return 0;\n}\n\nvoid aeron_driver_context_drain_all_free(void *cliend, void *item)\n{\n    aeron_free(item);\n}\n\nint aeron_driver_context_close(aeron_driver_context_t *context)\n{\n    if (NULL == context)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"aeron_driver_context_close(NULL)\");\n        return -1;\n    }\n\n    aeron_wildcard_port_manager_delete(&context->sender_wildcard_port_manager);\n    aeron_wildcard_port_manager_delete(&context->receiver_wildcard_port_manager);\n\n    aeron_free(context->conductor_command_queue.buffer);\n    aeron_free(context->sender_command_queue.buffer);\n    aeron_free(context->receiver_command_queue.buffer);\n\n    aeron_driver_context_free_bindings(context->udp_channel_outgoing_interceptor_bindings);\n    aeron_driver_context_free_bindings(context->udp_channel_incoming_interceptor_bindings);\n\n    aeron_unmap(&context->loss_report);\n    aeron_unmap(&context->cnc_map);\n\n    int result = 0;\n    if (context->dirs_delete_on_shutdown)\n    {\n        int delete_result = aeron_delete_directory(context->aeron_dir);\n        if (0 != delete_result)\n        {\n            if (-1 == delete_result)\n            {\n                delete_result = EINVAL;\n            }\n\n            AERON_SET_ERR(delete_result, \"aeron_driver_context_close failed to delete dir: %s\", context->aeron_dir);\n\n            result = -1;\n        }\n    }\n\n    aeron_free(context->conductor_idle_strategy_state);\n    aeron_free(context->receiver_idle_strategy_state);\n    aeron_free(context->sender_idle_strategy_state);\n    aeron_free(context->shared_idle_strategy_state);\n    aeron_free(context->shared_network_idle_strategy_state);\n    aeron_free((void *)context->conductor_idle_strategy_name);\n    aeron_free((void *)context->shared_network_idle_strategy_name);\n    aeron_free((void *)context->shared_idle_strategy_name);\n    aeron_free((void *)context->sender_idle_strategy_name);\n    aeron_free((void *)context->receiver_idle_strategy_name);\n    aeron_free(context->conductor_idle_strategy_init_args);\n    aeron_free(context->sender_idle_strategy_init_args);\n    aeron_free(context->receiver_idle_strategy_init_args);\n    aeron_free(context->shared_idle_strategy_init_args);\n    aeron_free(context->shared_network_idle_strategy_init_args);\n    aeron_free(context->bindings_clientd_entries);\n    aeron_free(context->cached_clock);\n    aeron_free(context->sender_cached_clock);\n    aeron_free(context->receiver_cached_clock);\n    aeron_dl_load_libs_delete(context->dynamic_libs);\n\n    aeron_free(context);\n\n    return result;\n}\n\nint aeron_driver_validate_unblock_timeout(aeron_driver_context_t *context)\n{\n    if (context->publication_unblock_timeout_ns <= context->client_liveness_timeout_ns)\n    {\n        errno = EINVAL;\n        AERON_SET_ERR(\n            EINVAL,\n            \"publication_unblock_timeout_ns=%\" PRIu64 \" <= client_liveness_timeout_ns=%\" PRIu64,\n            context->publication_unblock_timeout_ns, context->client_liveness_timeout_ns);\n        return -1;\n    }\n\n    if (context->client_liveness_timeout_ns <= context->timer_interval_ns)\n    {\n        errno = EINVAL;\n        AERON_SET_ERR(\n            EINVAL,\n            \"client_liveness_timeout_ns=%\" PRIu64 \" <= timer_interval_ns=%\" PRIu64,\n            context->client_liveness_timeout_ns, context->timer_interval_ns);\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_driver_validate_untethered_timeouts(aeron_driver_context_t *context)\n{\n    if (context->untethered_window_limit_timeout_ns <= context->timer_interval_ns)\n    {\n        errno = EINVAL;\n        AERON_SET_ERR(\n            EINVAL,\n            \"untethered_window_limit_timeout_ns=%\" PRIu64 \" <= timer_interval_ns=%\" PRIu64,\n            context->untethered_window_limit_timeout_ns, context->timer_interval_ns);\n        return -1;\n    }\n\n    if (context->untethered_resting_timeout_ns <= context->timer_interval_ns)\n    {\n        errno = EINVAL;\n        AERON_SET_ERR(\n            EINVAL,\n            \"untethered_resting_timeout_ns=%\" PRIu64 \" <= timer_interval_ns=%\" PRIu64,\n            context->untethered_resting_timeout_ns, context->timer_interval_ns);\n        return -1;\n    }\n\n    if (AERON_NULL_VALUE != context->untethered_linger_timeout_ns &&\n        (uint64_t)context->untethered_linger_timeout_ns <= context->timer_interval_ns)\n    {\n        errno = EINVAL;\n        AERON_SET_ERR(\n            EINVAL,\n            \"untethered_linger_timeout_ns=%\" PRIi64 \" <= timer_interval_ns=%\" PRIu64,\n            context->untethered_linger_timeout_ns, context->timer_interval_ns);\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_driver_context_validate_mtu_length(uint64_t mtu_length)\n{\n    if (mtu_length <= AERON_DATA_HEADER_LENGTH || mtu_length > AERON_MAX_UDP_PAYLOAD_LENGTH)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"mtuLength must be a > HEADER_LENGTH and <= MAX_UDP_PAYLOAD_LENGTH: mtuLength=%\" PRIu64,\n            mtu_length);\n        return -1;\n    }\n\n    if ((mtu_length & (AERON_LOGBUFFER_FRAME_ALIGNMENT - 1)) != 0)\n    {\n        AERON_SET_ERR(EINVAL, \"mtuLength must be a multiple of FRAME_ALIGNMENT: mtuLength=%\" PRIu64, mtu_length);\n        return -1;\n    }\n\n    return 0;\n}\n\nbool aeron_is_driver_active_with_cnc(\n    aeron_mapped_file_t *cnc_mmap, int64_t timeout_ms, int64_t now_ms, aeron_log_func_t log_func)\n{\n    char buffer[AERON_MAX_PATH];\n    aeron_cnc_metadata_t *metadata = (aeron_cnc_metadata_t *)cnc_mmap->addr;\n    int32_t cnc_version;\n\n    while (0 == (cnc_version = aeron_cnc_version_volatile(metadata)))\n    {\n        if (aeron_epoch_clock() > (now_ms + timeout_ms))\n        {\n            snprintf(buffer, sizeof(buffer) - 1, \"ERROR: aeron cnc file version was 0 for timeout\");\n            return false;\n        }\n\n        aeron_micro_sleep(1000);\n    }\n\n    if (aeron_semantic_version_major(AERON_CNC_VERSION) != aeron_semantic_version_major(cnc_version))\n    {\n        snprintf(\n            buffer, sizeof(buffer) - 1,\n            \"ERROR: aeron cnc version not compatible: app version=%d.%d.%d file=%d.%d.%d\",\n            (int)aeron_semantic_version_major(AERON_CNC_VERSION),\n            (int)aeron_semantic_version_minor(AERON_CNC_VERSION),\n            (int)aeron_semantic_version_patch(AERON_CNC_VERSION),\n            (int)aeron_semantic_version_major(cnc_version),\n            (int)aeron_semantic_version_minor(cnc_version),\n            (int)aeron_semantic_version_patch(cnc_version));\n\n        log_func(buffer);\n    }\n    else\n    {\n        aeron_mpsc_rb_t rb;\n\n        if (aeron_mpsc_rb_init(\n            &rb, aeron_cnc_to_driver_buffer(metadata), (size_t)metadata->to_driver_buffer_length) != 0)\n        {\n            snprintf(buffer, sizeof(buffer) - 1, \"ERROR: aeron cnc file could not init to-driver buffer\");\n            log_func(buffer);\n        }\n        else\n        {\n            int64_t timestamp_ms = aeron_mpsc_rb_consumer_heartbeat_time_value(&rb);\n            int64_t age = now_ms - timestamp_ms;\n\n            snprintf(buffer, sizeof(buffer) - 1, \"INFO: Aeron driver heartbeat is %\" PRId64 \" ms old\", age);\n            log_func(buffer);\n\n            if (age <= timeout_ms)\n            {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\nbool aeron_is_driver_active(const char *dirname, int64_t timeout_ms, aeron_log_func_t log_func)\n{\n    char filename[AERON_MAX_PATH];\n    char buffer[2 * AERON_MAX_PATH];\n    bool result = false;\n\n    if (aeron_is_directory(dirname))\n    {\n        aeron_mapped_file_t cnc_map = { .addr = NULL, .length = 0 };\n\n        snprintf(buffer, sizeof(buffer) - 1, \"INFO: Aeron directory %s exists\", dirname);\n        log_func(buffer);\n\n        if (aeron_cnc_resolve_filename(dirname, filename, sizeof(filename)) < 0)\n        {\n            snprintf(buffer, sizeof(buffer) - 1, \"INFO: Unable to resolve cnc filename: %s\", aeron_errmsg());\n            log_func(buffer);\n            return false;\n        }\n\n        if (aeron_map_existing_file(&cnc_map, filename) < 0)\n        {\n            snprintf(buffer, sizeof(buffer) - 1, \"INFO: failed to mmap CnC file\");\n            log_func(buffer);\n            return false;\n        }\n\n        snprintf(buffer, sizeof(buffer) - 1, \"INFO: Aeron CnC file %s exists\", filename);\n        log_func(buffer);\n\n        result = aeron_is_driver_active_with_cnc(&cnc_map, timeout_ms, aeron_epoch_clock(), log_func);\n\n        aeron_unmap(&cnc_map);\n    }\n\n    return result;\n}\n\nsize_t aeron_cnc_length(aeron_driver_context_t *context)\n{\n    return aeron_cnc_computed_length(\n        context->to_driver_buffer_length +\n        context->to_clients_buffer_length +\n        AERON_COUNTERS_METADATA_BUFFER_LENGTH(context->counters_values_buffer_length) +\n        context->counters_values_buffer_length +\n        context->error_buffer_length,\n        context->file_page_size);\n}\n\nextern void aeron_cnc_version_signal_cnc_ready(aeron_cnc_metadata_t *metadata, int32_t cnc_version);\n\nextern size_t aeron_producer_window_length(size_t producer_window_length, size_t term_length);\n\nextern size_t aeron_receiver_window_length(size_t initial_receiver_window_length, size_t term_length);\n\n#define AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(r, a) \\\ndo \\\n{ \\\n    if (NULL == (a)) \\\n    { \\\n        AERON_SET_ERR(EINVAL, \"%s is null\", #a); \\\n        return (r); \\\n    } \\\n} \\\nwhile (false) \\\n\nint aeron_driver_context_set_dir(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    snprintf(context->aeron_dir, sizeof(context->aeron_dir), \"%s\", value);\n    return 0;\n}\n\nconst char *aeron_driver_context_get_dir(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->aeron_dir : NULL;\n}\n\nint aeron_driver_context_set_dir_warn_if_exists(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->warn_if_dirs_exist = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_dir_warn_if_exists(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->warn_if_dirs_exist : AERON_DIR_WARN_IF_EXISTS_DEFAULT;\n}\n\nint aeron_driver_context_set_threading_mode(aeron_driver_context_t *context, aeron_threading_mode_t mode)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->threading_mode = mode;\n    return 0;\n}\n\naeron_threading_mode_t aeron_driver_context_get_threading_mode(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->threading_mode : AERON_THREADING_MODE_DEFAULT;\n}\n\nint aeron_driver_context_set_dir_delete_on_start(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->dirs_delete_on_start = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_dir_delete_on_start(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->dirs_delete_on_start : AERON_DIR_DELETE_ON_START_DEFAULT;\n}\n\nint aeron_driver_context_set_dir_delete_on_shutdown(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->dirs_delete_on_shutdown = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_dir_delete_on_shutdown(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->dirs_delete_on_shutdown : AERON_DIR_DELETE_ON_SHUTDOWN_DEFAULT;\n}\n\nint aeron_driver_context_set_to_conductor_buffer_length(aeron_driver_context_t *context, size_t length)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->to_driver_buffer_length = length;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_to_conductor_buffer_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->to_driver_buffer_length : AERON_TO_CONDUCTOR_BUFFER_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_to_clients_buffer_length(aeron_driver_context_t *context, size_t length)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->to_clients_buffer_length = length;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_to_clients_buffer_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->to_clients_buffer_length : AERON_TO_CLIENTS_BUFFER_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_counters_buffer_length(aeron_driver_context_t *context, size_t length)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->counters_values_buffer_length = length;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_counters_buffer_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->counters_values_buffer_length : AERON_COUNTERS_VALUES_BUFFER_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_error_buffer_length(aeron_driver_context_t *context, size_t length)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->error_buffer_length = length;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_error_buffer_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->error_buffer_length : AERON_ERROR_BUFFER_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_client_liveness_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->client_liveness_timeout_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_client_liveness_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->client_liveness_timeout_ns : AERON_CLIENT_LIVENESS_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_term_buffer_length(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->term_buffer_length = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_term_buffer_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->term_buffer_length : AERON_TERM_BUFFER_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_ipc_term_buffer_length(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->ipc_term_buffer_length = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_ipc_term_buffer_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->ipc_term_buffer_length : AERON_IPC_TERM_BUFFER_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_term_buffer_sparse_file(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->term_buffer_sparse_file = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_term_buffer_sparse_file(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->term_buffer_sparse_file : AERON_TERM_BUFFER_SPARSE_FILE_DEFAULT;\n}\n\nint aeron_driver_context_set_perform_storage_checks(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->perform_storage_checks = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_perform_storage_checks(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->perform_storage_checks : AERON_PERFORM_STORAGE_CHECKS_DEFAULT;\n}\n\nint aeron_driver_context_set_low_file_store_warning_threshold(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->low_file_store_warning_threshold = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_low_file_store_warning_threshold(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->low_file_store_warning_threshold : AERON_LOW_FILE_STORE_WARNING_THRESHOLD_DEFAULT;\n}\n\nint aeron_driver_context_set_spies_simulate_connection(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->spies_simulate_connection = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_spies_simulate_connection(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->spies_simulate_connection : AERON_SPIES_SIMULATE_CONNECTION_DEFAULT;\n}\n\nint aeron_driver_context_set_file_page_size(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->file_page_size = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_file_page_size(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->file_page_size : AERON_FILE_PAGE_SIZE_DEFAULT;\n}\n\nint aeron_driver_context_set_mtu_length(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->mtu_length = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_mtu_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->mtu_length : AERON_MTU_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_ipc_mtu_length(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->ipc_mtu_length = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_ipc_mtu_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->ipc_mtu_length : AERON_IPC_MTU_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_ipc_publication_term_window_length(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->ipc_publication_window_length = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_ipc_publication_term_window_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->ipc_publication_window_length : AERON_IPC_PUBLICATION_TERM_WINDOW_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_publication_term_window_length(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->publication_window_length = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_publication_term_window_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->publication_window_length : AERON_PUBLICATION_TERM_WINDOW_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_publication_linger_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->publication_linger_timeout_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_publication_linger_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->publication_linger_timeout_ns : AERON_PUBLICATION_LINGER_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_socket_so_rcvbuf(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->socket_rcvbuf = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_socket_so_rcvbuf(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->socket_rcvbuf : AERON_SOCKET_SO_RCVBUF_DEFAULT;\n}\n\nint aeron_driver_context_set_socket_so_sndbuf(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->socket_sndbuf = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_socket_so_sndbuf(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->socket_sndbuf : AERON_SOCKET_SO_SNDBUF_DEFAULT;\n}\n\nint aeron_driver_context_set_socket_multicast_ttl(aeron_driver_context_t *context, uint8_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->multicast_ttl = value;\n    return 0;\n}\n\nuint8_t aeron_driver_context_get_socket_multicast_ttl(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->multicast_ttl : AERON_SOCKET_MULTICAST_TTL_DEFAULT;\n}\n\nint aeron_driver_context_set_send_to_status_poll_ratio(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->send_to_sm_poll_ratio = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_send_to_status_poll_ratio(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->send_to_sm_poll_ratio : AERON_SEND_TO_STATUS_POLL_RATIO_DEFAULT;\n}\n\nint aeron_driver_context_set_rcv_status_message_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->status_message_timeout_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_rcv_status_message_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->status_message_timeout_ns : AERON_RCV_STATUS_MESSAGE_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_multicast_flowcontrol_supplier(\n    aeron_driver_context_t *context, aeron_flow_control_strategy_supplier_func_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    context->multicast_flow_control_supplier_func = value;\n    return 0;\n}\n\naeron_flow_control_strategy_supplier_func_t aeron_driver_context_get_multicast_flowcontrol_supplier(\n    aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->multicast_flow_control_supplier_func :\n        aeron_flow_control_strategy_supplier_load(AERON_MULTICAST_FLOWCONTROL_SUPPLIER_DEFAULT);\n}\n\nint aeron_driver_context_set_unicast_flowcontrol_supplier(\n    aeron_driver_context_t *context, aeron_flow_control_strategy_supplier_func_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    context->unicast_flow_control_supplier_func = value;\n    return 0;\n}\n\naeron_flow_control_strategy_supplier_func_t aeron_driver_context_get_unicast_flowcontrol_supplier(\n    aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->unicast_flow_control_supplier_func :\n        aeron_flow_control_strategy_supplier_load(AERON_UNICAST_FLOWCONTROL_SUPPLIER_DEFAULT);\n}\n\nint aeron_driver_context_set_image_liveness_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->image_liveness_timeout_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_image_liveness_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->image_liveness_timeout_ns : AERON_IMAGE_LIVENESS_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_rcv_initial_window_length(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->initial_window_length = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_rcv_initial_window_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->initial_window_length : AERON_RCV_INITIAL_WINDOW_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_congestioncontrol_supplier(\n    aeron_driver_context_t *context, aeron_congestion_control_strategy_supplier_func_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    context->congestion_control_supplier_func = value;\n    return 0;\n}\n\naeron_congestion_control_strategy_supplier_func_t aeron_driver_context_get_congestioncontrol_supplier(\n    aeron_driver_context_t *context)\n{\n    return NULL != context ? context->congestion_control_supplier_func :\n        aeron_congestion_control_strategy_supplier_load(AERON_CONGESTIONCONTROL_SUPPLIER_DEFAULT);\n}\n\nint aeron_driver_context_set_loss_report_buffer_length(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->loss_report_length = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_loss_report_buffer_length(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->loss_report_length : AERON_LOSS_REPORT_BUFFER_LENGTH_DEFAULT;\n}\n\nint aeron_driver_context_set_publication_unblock_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->publication_unblock_timeout_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_publication_unblock_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->publication_unblock_timeout_ns : AERON_PUBLICATION_UNBLOCK_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_publication_connection_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->publication_connection_timeout_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_publication_connection_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->publication_connection_timeout_ns : AERON_PUBLICATION_CONNECTION_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_timer_interval_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->timer_interval_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_timer_interval_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->timer_interval_ns : AERON_TIMER_INTERVAL_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_sender_idle_strategy(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    aeron_free(context->sender_idle_strategy_state);\n    aeron_free((void *)context->sender_idle_strategy_name);\n    context->sender_idle_strategy_state = NULL;\n    context->sender_idle_strategy_name = NULL;\n\n    if ((context->sender_idle_strategy_func = aeron_idle_strategy_load(\n        value,\n        &context->sender_idle_strategy_state,\n        AERON_SENDER_IDLE_STRATEGY_ENV_VAR,\n        context->sender_idle_strategy_init_args)) == NULL)\n    {\n        return -1;\n    }\n\n    context->sender_idle_strategy_name = aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_sender_idle_strategy(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->sender_idle_strategy_name : AERON_IDLE_STRATEGY_BACKOFF_DEFAULT;\n}\n\nint aeron_driver_context_set_conductor_idle_strategy(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    aeron_free(context->conductor_idle_strategy_state);\n    aeron_free((void *)context->conductor_idle_strategy_name);\n    context->conductor_idle_strategy_state = NULL;\n    context->conductor_idle_strategy_name = NULL;\n\n    if ((context->conductor_idle_strategy_func = aeron_idle_strategy_load(\n        value,\n        &context->conductor_idle_strategy_state,\n        AERON_CONDUCTOR_IDLE_STRATEGY_ENV_VAR,\n        context->conductor_idle_strategy_init_args)) == NULL)\n    {\n        return -1;\n    }\n\n    context->conductor_idle_strategy_name = aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_conductor_idle_strategy(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->conductor_idle_strategy_name : AERON_IDLE_STRATEGY_BACKOFF_DEFAULT;\n}\n\nint aeron_driver_context_set_receiver_idle_strategy(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    aeron_free(context->receiver_idle_strategy_state);\n    aeron_free((void *)context->receiver_idle_strategy_name);\n    context->receiver_idle_strategy_state = NULL;\n    context->receiver_idle_strategy_name = NULL;\n\n    if ((context->receiver_idle_strategy_func = aeron_idle_strategy_load(\n        value,\n        &context->receiver_idle_strategy_state,\n        AERON_RECEIVER_IDLE_STRATEGY_ENV_VAR,\n        context->receiver_idle_strategy_init_args)) == NULL)\n    {\n        return -1;\n    }\n\n    context->receiver_idle_strategy_name = aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_receiver_idle_strategy(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->receiver_idle_strategy_name : AERON_IDLE_STRATEGY_BACKOFF_DEFAULT;\n}\n\nint aeron_driver_context_set_sharednetwork_idle_strategy(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    aeron_free(context->shared_network_idle_strategy_state);\n    aeron_free((void *)context->shared_network_idle_strategy_name);\n\n    if ((context->shared_network_idle_strategy_func = aeron_idle_strategy_load(\n        value,\n        &context->shared_network_idle_strategy_state,\n        AERON_SHAREDNETWORK_IDLE_STRATEGY_ENV_VAR,\n        context->shared_network_idle_strategy_init_args)) == NULL)\n    {\n        return -1;\n    }\n\n    context->shared_network_idle_strategy_name = aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_sharednetwork_idle_strategy(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->shared_network_idle_strategy_name : AERON_IDLE_STRATEGY_BACKOFF_DEFAULT;\n}\n\nint aeron_driver_context_set_shared_idle_strategy(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, value);\n\n    aeron_free(context->shared_idle_strategy_state);\n    aeron_free((void *)context->shared_idle_strategy_name);\n\n    if ((context->shared_idle_strategy_func = aeron_idle_strategy_load(\n        value,\n        &context->shared_idle_strategy_state,\n        AERON_SHARED_IDLE_STRATEGY_ENV_VAR,\n        context->shared_idle_strategy_init_args)) == NULL)\n    {\n        return -1;\n    }\n\n    context->shared_idle_strategy_name = aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_shared_idle_strategy(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->shared_idle_strategy_name : AERON_IDLE_STRATEGY_BACKOFF_DEFAULT;\n}\n\nint aeron_driver_context_set_sender_idle_strategy_init_args(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    aeron_free(context->sender_idle_strategy_init_args);\n    context->sender_idle_strategy_init_args = NULL == value ? NULL : aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_sender_idle_strategy_init_args(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->sender_idle_strategy_init_args : NULL;\n}\n\nint aeron_driver_context_set_conductor_idle_strategy_init_args(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    aeron_free(context->conductor_idle_strategy_init_args);\n    context->conductor_idle_strategy_init_args = NULL == value ? NULL : aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_conductor_idle_strategy_init_args(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->conductor_idle_strategy_init_args : NULL;\n}\n\nint aeron_driver_context_set_receiver_idle_strategy_init_args(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    aeron_free(context->receiver_idle_strategy_init_args);\n    context->receiver_idle_strategy_init_args = NULL == value ? NULL : aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_receiver_idle_strategy_init_args(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->receiver_idle_strategy_init_args : NULL;\n}\n\nint aeron_driver_context_set_sharednetwork_idle_strategy_init_args(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    aeron_free(context->shared_network_idle_strategy_init_args);\n    context->shared_network_idle_strategy_init_args = NULL == value ? NULL : aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_sharednetwork_idle_strategy_init_args(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->shared_network_idle_strategy_init_args : NULL;\n}\n\nint aeron_driver_context_set_shared_idle_strategy_init_args(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    aeron_free(context->shared_idle_strategy_init_args);\n    context->shared_idle_strategy_init_args = NULL == value ? NULL : aeron_strndup(value, AERON_MAX_PATH);\n\n    return 0;\n}\n\nconst char *aeron_driver_context_get_shared_idle_strategy_init_args(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->shared_idle_strategy_init_args : NULL;\n}\n\nint aeron_driver_context_set_agent_on_start_function(\n    aeron_driver_context_t *context, aeron_agent_on_start_func_t value, void *state)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->agent_on_start_func = value;\n    context->agent_on_start_state = state;\n\n    return 0;\n}\n\naeron_agent_on_start_func_t aeron_driver_context_get_agent_on_start_function(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->agent_on_start_func : NULL;\n}\n\nvoid *aeron_driver_context_get_agent_on_start_state(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->agent_on_start_state : NULL;\n}\n\nint aeron_driver_context_set_counters_free_to_reuse_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->counter_free_to_reuse_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_counters_free_to_reuse_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->counter_free_to_reuse_ns : AERON_COUNTERS_FREE_TO_REUSE_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_flow_control_receiver_timeout_ns(aeron_driver_context_t *context,  uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->flow_control.receiver_timeout_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_flow_control_receiver_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->flow_control.receiver_timeout_ns : AERON_FLOW_CONTROL_RECEIVER_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_flow_control_group_tag(aeron_driver_context_t *context, int64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->flow_control.group_tag = value;\n    return 0;\n}\n\nint64_t aeron_driver_context_get_flow_control_group_tag(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->flow_control.group_tag : AERON_FLOW_CONTROL_GROUP_TAG_DEFAULT;\n}\n\nint aeron_driver_context_set_flow_control_group_min_size(aeron_driver_context_t *context, int32_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->flow_control.group_min_size = value;\n    return 0;\n}\n\nint32_t aeron_driver_context_get_flow_control_group_min_size(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->flow_control.group_min_size :\n        AERON_FLOW_CONTROL_GROUP_MIN_SIZE_DEFAULT;\n}\n\nint aeron_driver_context_set_receiver_group_tag(aeron_driver_context_t *context, bool is_present, int64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->receiver_group_tag.is_present = is_present;\n    context->receiver_group_tag.value = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_receiver_group_tag_is_present(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->receiver_group_tag.is_present : AERON_RECEIVER_GROUP_TAG_IS_PRESENT_DEFAULT;\n}\n\nint64_t aeron_driver_context_get_receiver_group_tag_value(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->receiver_group_tag.value : AERON_RECEIVER_GROUP_TAG_VALUE_DEFAULT;\n}\n\nint aeron_driver_context_set_driver_termination_validator(\n    aeron_driver_context_t *context, aeron_driver_termination_validator_func_t value, void *state)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->termination_validator_func = value;\n    context->termination_validator_state = state;\n    return 0;\n}\n\naeron_driver_termination_validator_func_t aeron_driver_context_get_driver_termination_validator(\n    aeron_driver_context_t *context)\n{\n    return NULL != context ? context->termination_validator_func : aeron_driver_termination_validator_default_deny;\n}\n\nvoid *aeron_driver_context_get_driver_termination_validator_state(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->termination_validator_state : NULL;\n}\n\nint aeron_driver_context_set_driver_termination_hook(\n    aeron_driver_context_t *context, aeron_driver_termination_hook_func_t value, void *state)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->termination_hook_func = value;\n    context->termination_hook_state = state;\n    return 0;\n}\n\naeron_driver_termination_hook_func_t aeron_driver_context_get_driver_termination_hook(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->termination_hook_func : NULL;\n}\n\nvoid *aeron_driver_context_get_driver_termination_hook_state(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->termination_hook_state : NULL;\n}\n\nint aeron_driver_context_set_print_configuration(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->print_configuration_on_start = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_print_configuration(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->print_configuration_on_start : AERON_PRINT_CONFIGURATION_DEFAULT;\n}\n\nint aeron_driver_context_set_reliable_stream(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->reliable_stream = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_reliable_stream(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->reliable_stream : AERON_RELIABLE_STREAM_DEFAULT;\n}\n\nint aeron_driver_context_set_tether_subscriptions(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->tether_subscriptions = value;\n    return 0;\n}\n\nbool aeron_driver_context_get_tether_subscriptions(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->tether_subscriptions : AERON_TETHER_SUBSCRIPTIONS_DEFAULT;\n}\n\nint aeron_driver_context_set_untethered_window_limit_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->untethered_window_limit_timeout_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_untethered_window_limit_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->untethered_window_limit_timeout_ns : AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_untethered_linger_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->untethered_linger_timeout_ns = value;\n    return 0;\n}\n\nint64_t aeron_driver_context_get_untethered_linger_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->untethered_linger_timeout_ns : AERON_NULL_VALUE;\n}\n\nint aeron_driver_context_set_untethered_resting_timeout_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->untethered_resting_timeout_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_untethered_resting_timeout_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->untethered_resting_timeout_ns : AERON_UNTETHERED_RESTING_TIMEOUT_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_driver_timeout_ms(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->driver_timeout_ms = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_driver_timeout_ms(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->driver_timeout_ms : AERON_DRIVER_TIMEOUT_MS_DEFAULT;\n}\n\nint aeron_driver_context_set_max_resend(aeron_driver_context_t *context, uint32_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->max_resend = value;\n    return 0;\n}\n\nuint32_t aeron_driver_context_get_max_resend(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->max_resend : AERON_RETRANSMIT_HANDLER_MAX_RESEND;\n}\n\nint aeron_driver_context_set_retransmit_unicast_delay_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->retransmit_unicast_delay_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_retransmit_unicast_delay_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->retransmit_unicast_delay_ns : AERON_RETRANSMIT_UNICAST_DELAY_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_retransmit_unicast_linger_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->retransmit_unicast_linger_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_retransmit_unicast_linger_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->retransmit_unicast_linger_ns : AERON_RETRANSMIT_UNICAST_LINGER_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_nak_multicast_group_size(aeron_driver_context_t *context, size_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->nak_multicast_group_size = value;\n    return 0;\n}\n\nsize_t aeron_driver_context_get_nak_multicast_group_size(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->nak_multicast_group_size : AERON_NAK_MULTICAST_GROUP_SIZE_DEFAULT;\n}\n\nint aeron_driver_context_set_nak_multicast_max_backoff_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->nak_multicast_max_backoff_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_nak_multicast_max_backoff_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->nak_multicast_max_backoff_ns : AERON_NAK_MULTICAST_MAX_BACKOFF_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_nak_unicast_delay_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->nak_unicast_delay_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_nak_unicast_delay_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->nak_unicast_delay_ns : AERON_NAK_UNICAST_DELAY_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_nak_unicast_retry_delay_ratio(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->nak_unicast_retry_delay_ratio = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_nak_unicast_retry_delay_ratio(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->nak_unicast_retry_delay_ratio : AERON_NAK_UNICAST_RETRY_DELAY_RATIO_DEFAULT;\n}\n\nint aeron_driver_context_set_udp_channel_transport_bindings(\n    aeron_driver_context_t *context, aeron_udp_channel_transport_bindings_t *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->udp_channel_transport_bindings = value;\n    return 0;\n}\n\naeron_udp_channel_transport_bindings_t *aeron_driver_context_get_udp_channel_transport_bindings(\n    aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->udp_channel_transport_bindings :\n        aeron_udp_channel_transport_bindings_load_media(AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA_DEFAULT);\n}\n\nint aeron_driver_context_set_udp_channel_outgoing_interceptors(\n    aeron_driver_context_t *context, aeron_udp_channel_interceptor_bindings_t *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->udp_channel_outgoing_interceptor_bindings = value;\n    return 0;\n}\n\naeron_udp_channel_interceptor_bindings_t *aeron_driver_context_get_udp_channel_outgoing_interceptors(\n    aeron_driver_context_t *context)\n{\n    return NULL != context ? context->udp_channel_outgoing_interceptor_bindings : NULL;\n}\n\nint aeron_driver_context_set_udp_channel_incoming_interceptors(\n    aeron_driver_context_t *context, aeron_udp_channel_interceptor_bindings_t *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->udp_channel_incoming_interceptor_bindings = value;\n    return 0;\n}\n\naeron_udp_channel_interceptor_bindings_t *aeron_driver_context_get_udp_channel_incoming_interceptors(\n    aeron_driver_context_t *context)\n{\n    return NULL != context ? context->udp_channel_incoming_interceptor_bindings : NULL;\n}\n\nint aeron_driver_context_set_receiver_group_consideration(\n    aeron_driver_context_t *context, aeron_inferable_boolean_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->receiver_group_consideration = value;\n    return 0;\n}\n\naeron_inferable_boolean_t aeron_driver_context_get_receiver_group_consideration(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->receiver_group_consideration : AERON_RECEIVER_GROUP_CONSIDERATION_DEFAULT;\n}\n\nint aeron_driver_context_set_rejoin_stream(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->rejoin_stream = value;\n    return 0;\n}\n\nint aeron_driver_context_set_publication_reserved_session_id_low(aeron_driver_context_t *context, int32_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->publication_reserved_session_id_low = value;\n    return 0;\n}\n\nint32_t aeron_driver_context_get_publication_reserved_session_id_low(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->publication_reserved_session_id_low : AERON_PUBLICATION_RESERVED_SESSION_ID_LOW_DEFAULT;\n}\n\nint aeron_driver_context_set_publication_reserved_session_id_high(aeron_driver_context_t *context, int32_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->publication_reserved_session_id_high = value;\n    return 0;\n}\n\nint32_t aeron_driver_context_get_publication_reserved_session_id_high(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->publication_reserved_session_id_high : AERON_PUBLICATION_RESERVED_SESSION_ID_HIGH_DEFAULT;\n}\n\nbool aeron_driver_context_get_rejoin_stream(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->rejoin_stream : AERON_REJOIN_STREAM_DEFAULT;\n}\n\nint aeron_driver_context_set_connect_enabled(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->connect_enabled = value;\n    return 0;\n}\n\nint aeron_driver_context_get_connect_enabled(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->connect_enabled : AERON_DRIVER_CONNECT_DEFAULT;\n}\n\nint aeron_driver_context_set_resolver_name(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->resolver_name = value;\n    return 0;\n}\n\nconst char *aeron_driver_context_get_resolver_name(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->resolver_name : NULL;\n}\n\nint aeron_driver_context_set_resolver_interface(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->resolver_interface = value;\n    return 0;\n}\n\nconst char *aeron_driver_context_get_resolver_interface(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->resolver_interface : NULL;\n}\n\nint aeron_driver_context_set_resolver_bootstrap_neighbor(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->resolver_bootstrap_neighbor = value;\n    return 0;\n}\n\nconst char *aeron_driver_context_get_resolver_bootstrap_neighbor(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->resolver_bootstrap_neighbor : NULL;\n}\n\nint aeron_driver_context_set_name_resolver_supplier(\n    aeron_driver_context_t *context, aeron_name_resolver_supplier_func_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->name_resolver_supplier_func = value;\n    return 0;\n}\n\naeron_name_resolver_supplier_func_t aeron_driver_context_get_name_resolver_supplier(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->name_resolver_supplier_func : aeron_name_resolver_supplier_load(AERON_NAME_RESOLVER_SUPPLIER_DEFAULT);\n}\n\nint aeron_driver_context_set_name_resolver_init_args(aeron_driver_context_t *context, const char *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->name_resolver_init_args = value;\n    return 0;\n}\n\nconst char *aeron_driver_context_get_name_resolver_init_args(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->name_resolver_init_args : NULL;\n}\n\nint aeron_driver_context_set_re_resolution_check_interval_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->re_resolution_check_interval_ns = value;\n    return 0;\n}\n\nuint64_t aeron_driver_context_get_re_resolution_check_interval_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->re_resolution_check_interval_ns : AERON_DRIVER_RERESOLUTION_CHECK_INTERVAL_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_conductor_duty_cycle_tracker(\n    aeron_driver_context_t *context, aeron_duty_cycle_tracker_t *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->conductor_duty_cycle_tracker = value;\n    return 0;\n}\n\naeron_duty_cycle_tracker_t *aeron_driver_context_get_conductor_duty_cycle_tracker(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->conductor_duty_cycle_tracker : NULL;\n}\n\nint aeron_driver_context_set_sender_duty_cycle_tracker(\n    aeron_driver_context_t *context, aeron_duty_cycle_tracker_t *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->sender_duty_cycle_tracker = value;\n    return 0;\n}\n\naeron_duty_cycle_tracker_t *aeron_driver_context_get_sender_duty_cycle_tracker(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->sender_duty_cycle_tracker : NULL;\n}\n\nint aeron_driver_context_set_receiver_duty_cycle_tracker(\n    aeron_driver_context_t *context, aeron_duty_cycle_tracker_t *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->receiver_duty_cycle_tracker = value;\n    return 0;\n}\n\naeron_duty_cycle_tracker_t *aeron_driver_context_get_receiver_duty_cycle_tracker(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->receiver_duty_cycle_tracker : NULL;\n}\n\nint aeron_driver_context_set_name_resolver_time_tracker(\n    aeron_driver_context_t *context, aeron_duty_cycle_tracker_t *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->name_resolver_time_tracker = value;\n    return 0;\n}\n\naeron_duty_cycle_tracker_t *aeron_driver_context_get_name_resolver_time_tracker(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->name_resolver_time_tracker : NULL;\n}\n\nint64_t aeron_driver_context_set_conductor_cycle_threshold_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->conductor_duty_cycle_stall_tracker.cycle_threshold_ns = value;\n    return 0;\n}\n\nint64_t aeron_driver_context_get_conductor_cycle_threshold_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->conductor_duty_cycle_stall_tracker.cycle_threshold_ns :\n        AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD_NS_DEFAULT;\n}\n\nint64_t aeron_driver_context_set_sender_cycle_threshold_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->sender_duty_cycle_stall_tracker.cycle_threshold_ns = value;\n    return 0;\n}\n\nint64_t aeron_driver_context_get_sender_cycle_threshold_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->sender_duty_cycle_stall_tracker.cycle_threshold_ns :\n        AERON_DRIVER_SENDER_CYCLE_THRESHOLD_NS_DEFAULT;\n}\n\nint64_t aeron_driver_context_set_receiver_cycle_threshold_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->receiver_duty_cycle_stall_tracker.cycle_threshold_ns = value;\n    return 0;\n}\n\nint64_t aeron_driver_context_get_receiver_cycle_threshold_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->receiver_duty_cycle_stall_tracker.cycle_threshold_ns :\n        AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD_NS_DEFAULT;\n}\n\nint64_t aeron_driver_context_set_name_resolver_threshold_ns(aeron_driver_context_t *context, uint64_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->name_resolver_time_stall_tracker.cycle_threshold_ns = value;\n    return 0;\n}\n\nint64_t aeron_driver_context_get_name_resolver_threshold_ns(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n        context->name_resolver_time_stall_tracker.cycle_threshold_ns :\n        AERON_DRIVER_NAME_RESOLVER_THRESHOLD_NS_DEFAULT;\n}\n\nint aeron_driver_context_set_sender_wildcard_port_range(\n    aeron_driver_context_t *context, uint16_t low_port, uint16_t high_port)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    aeron_wildcard_port_manager_set_range(&context->sender_wildcard_port_manager, low_port, high_port);\n    return 0;\n}\n\nint aeron_driver_context_get_sender_wildcard_port_range(\n    aeron_driver_context_t *context, uint16_t *low_port, uint16_t *high_port)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    *low_port = context->sender_wildcard_port_manager.low_port;\n    *high_port = context->sender_wildcard_port_manager.high_port;\n    return 0;\n}\n\nint aeron_driver_context_set_receiver_wildcard_port_range(\n    aeron_driver_context_t *context, uint16_t low_port, uint16_t high_port)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    aeron_wildcard_port_manager_set_range(&context->receiver_wildcard_port_manager, low_port, high_port);\n    return 0;\n}\n\nint aeron_driver_context_get_receiver_wildcard_port_range(\n    aeron_driver_context_t *context, uint16_t *low_port, uint16_t *high_port)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    *low_port = context->receiver_wildcard_port_manager.low_port;\n    *high_port = context->receiver_wildcard_port_manager.high_port;\n    return 0;\n}\n\nint aeron_driver_context_set_sender_port_manager(\n    aeron_driver_context_t *context, aeron_port_manager_t *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->sender_port_manager = value;\n    return 0;\n}\n\naeron_port_manager_t *aeron_driver_context_get_sender_port_manager(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->sender_port_manager : NULL;\n}\n\nint aeron_driver_context_set_receiver_port_manager(\n    aeron_driver_context_t *context, aeron_port_manager_t *value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->receiver_port_manager = value;\n    return 0;\n}\n\naeron_port_manager_t *aeron_driver_context_get_receiver_port_manager(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->receiver_port_manager : NULL;\n}\n\nint aeron_driver_context_set_enable_experimental_features(aeron_driver_context_t *context, bool value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->enable_experimental_features = value;\n    return 0;\n}\n\nint aeron_driver_context_get_enable_experimental_features(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->enable_experimental_features : false;\n}\n\nint aeron_driver_context_set_stream_session_limit(aeron_driver_context_t *context, int32_t value)\n{\n    AERON_DRIVER_CONTEXT_SET_CHECK_ARG_AND_RETURN(-1, context);\n\n    context->stream_session_limit = value;\n    return 0;\n}\n\nint32_t aeron_driver_context_get_stream_session_limit(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->stream_session_limit : AERON_DRIVER_STREAM_SESSION_LIMIT_DEFAULT;\n}\n\n\nint aeron_driver_context_bindings_clientd_find_first_free_index(aeron_driver_context_t *context)\n{\n    for (size_t i = 0; i < context->num_bindings_clientd_entries; i++)\n    {\n        if (NULL == context->bindings_clientd_entries[i].clientd)\n        {\n            return (int)i;\n        }\n    }\n\n    return -1;\n}\n\nint aeron_driver_context_bindings_clientd_find(aeron_driver_context_t *context, const char *name)\n{\n    for (size_t i = 0; i < context->num_bindings_clientd_entries; i++)\n    {\n        aeron_driver_context_bindings_clientd_entry_t *entry = &context->bindings_clientd_entries[i];\n\n        if (NULL != entry->name && 0 == strncmp(entry->name, name, strlen(name)))\n        {\n            return (int)i;\n        }\n    }\n\n    return -1;\n}\n\naeron_driver_context_bindings_clientd_entry_t *aeron_driver_context_bindings_clientd_get_or_find_first_free_entry(\n    aeron_driver_context_t *context, const char *name)\n{\n    int index = aeron_driver_context_bindings_clientd_find(context, name);\n    if (-1 == index)\n    {\n        index = aeron_driver_context_bindings_clientd_find_first_free_index(context);\n        if (-1 == index)\n        {\n            return NULL;\n        }\n\n        context->bindings_clientd_entries[index].name = name;\n    }\n\n    return &context->bindings_clientd_entries[index];\n}\n\n\nstatic uint32_t aeron_driver_context_clamp_value(uint32_t value, uint32_t min, uint32_t max)\n{\n    uint32_t clamped_value;\n    clamped_value = value > max ? max : value;\n    clamped_value = clamped_value < min ? min : clamped_value;\n\n    return clamped_value;\n}\n\nint aeron_driver_context_set_receiver_io_vector_capacity(aeron_driver_context_t *context, uint32_t value)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    context->receiver_io_vector_capacity = aeron_driver_context_clamp_value(\n        value, 1, AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX);\n\n    return 0;\n}\n\nuint32_t aeron_driver_context_get_receiver_io_vector_capacity(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->receiver_io_vector_capacity : AERON_RECEIVER_IO_VECTOR_CAPACITY_DEFAULT;\n}\n\nint aeron_driver_context_set_sender_io_vector_capacity(aeron_driver_context_t *context, uint32_t value)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    context->sender_io_vector_capacity = aeron_driver_context_clamp_value(\n        value, 1, AERON_DRIVER_SENDER_IO_VECTOR_LENGTH_MAX);\n\n    return 0;\n}\n\nuint32_t aeron_driver_context_get_sender_io_vector_capacity(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->sender_io_vector_capacity : AERON_SENDER_IO_VECTOR_CAPACITY_DEFAULT;\n}\n\nint aeron_driver_context_set_network_publication_max_messages_per_send(aeron_driver_context_t *context, uint32_t value)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    context->network_publication_max_messages_per_send = aeron_driver_context_clamp_value(\n        value, 1, AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND);\n    return 0;\n}\n\nuint32_t aeron_driver_context_get_network_publication_max_messages_per_send(aeron_driver_context_t *context)\n{\n    return NULL != context ?\n           context->network_publication_max_messages_per_send : AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_DEFAULT;\n}\n\nint aeron_driver_context_set_resource_free_limit(aeron_driver_context_t *context, uint32_t value)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    context->resource_free_limit = value;\n    return 0;\n}\n\nuint32_t aeron_driver_context_get_resource_free_limit(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->resource_free_limit : AERON_DRIVER_RESOURCE_FREE_LIMIT_DEFAULT;\n}\n\nint aeron_driver_context_set_async_executor_threads(aeron_driver_context_t *context, uint32_t value)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    context->async_executor_threads = value;\n    return 0;\n}\n\nuint32_t aeron_driver_context_get_async_executor_threads(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->async_executor_threads : AERON_DRIVER_ASYNC_EXECUTOR_THREADS_DEFAULT;\n}\n\nvoid aeron_set_thread_affinity_on_start(void *state, const char *role_name)\n{\n    aeron_driver_context_t *context = (aeron_driver_context_t *)state;\n    int result = 0;\n    if (0 <= context->conductor_cpu_affinity_no &&\n       (0 == strcmp(\"conductor\", role_name) ||\n        0 == strcmp(\"[conductor, sender, receiver]\", role_name)))\n    {\n        result = aeron_thread_set_affinity(role_name, (uint8_t)context->conductor_cpu_affinity_no);\n    }\n    else if (0 <= context->sender_cpu_affinity_no &&\n            (0 == strcmp(\"sender\", role_name) ||\n             0 == strcmp(\"[sender, receiver]\", role_name)))\n    {\n        result = aeron_thread_set_affinity(role_name, (uint8_t)context->sender_cpu_affinity_no);\n    }\n    else if (0 <= context->receiver_cpu_affinity_no && 0 == strcmp(\"receiver\", role_name))\n    {\n        result = aeron_thread_set_affinity(role_name, (uint8_t)context->receiver_cpu_affinity_no);\n    }\n\n    if (result < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"WARNING: unable to apply affinity\");\n        // Just in case the error log is not initialised, but it should be by this point.\n        if (NULL != context->error_log)\n        {\n            aeron_distinct_error_log_record(context->error_log, aeron_errcode(), aeron_errmsg());\n        }\n        else\n        {\n            fprintf(stderr, \"%s\", aeron_errmsg());\n        }\n        aeron_err_clear();\n    }\n\n    // if start function was overridden call it here with the explicitly set state\n    if (NULL != context->agent_on_start_func_delegate)\n    {\n        context->agent_on_start_func_delegate(\n            NULL != context->agent_on_start_state_delegate ? context->agent_on_start_state_delegate : state,\n            role_name);\n    }\n}\n\nint aeron_driver_context_set_conductor_cpu_affinity(aeron_driver_context_t *context, int32_t value)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    context->conductor_cpu_affinity_no = value;\n    return 0;\n}\n\nint32_t aeron_driver_context_get_conductor_cpu_affinity(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->conductor_cpu_affinity_no : AERON_CPU_AFFINITY_DEFAULT;\n}\n\nint aeron_driver_context_set_sender_cpu_affinity(aeron_driver_context_t *context, int32_t value)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    context->sender_cpu_affinity_no = value;\n    return 0;\n}\n\nint32_t aeron_driver_context_get_sender_cpu_affinity(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->sender_cpu_affinity_no : AERON_CPU_AFFINITY_DEFAULT;\n}\n\nint aeron_driver_context_set_receiver_cpu_affinity(aeron_driver_context_t *context, int32_t value)\n{\n    if (NULL == context)\n    {\n        return -1;\n    }\n\n    context->receiver_cpu_affinity_no = value;\n    return 0;\n}\n\nint32_t aeron_driver_context_get_receiver_cpu_affinity(aeron_driver_context_t *context)\n{\n    return NULL != context ? context->receiver_cpu_affinity_no :AERON_CPU_AFFINITY_DEFAULT;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_context.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_CONTEXT_H\n#define AERON_DRIVER_CONTEXT_H\n\n#include \"concurrent/aeron_distinct_error_log.h\"\n#include \"media/aeron_udp_channel_transport_bindings.h\"\n#include \"aeron_driver_common.h\"\n#include \"aeronmd.h\"\n#include \"util/aeron_fileutil.h\"\n#include \"concurrent/aeron_spsc_concurrent_array_queue.h\"\n#include \"concurrent/aeron_mpsc_concurrent_array_queue.h\"\n#include \"concurrent/aeron_mpsc_rb.h\"\n#include \"concurrent/aeron_broadcast_descriptor.h\"\n#include \"aeron_flow_control.h\"\n#include \"aeron_congestion_control.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_system_counters.h\"\n#include \"aeron_cnc_file_descriptor.h\"\n#include \"aeron_duty_cycle_tracker.h\"\n#include \"aeron_port_manager.h\"\n\n#define AERON_COMMAND_RB_CAPACITY (128 * 1024)\n#define AERON_COMMAND_RB_RESERVE (1024)\n#define AERON_COMMAND_DRAIN_LIMIT (1)\n\n#define AERON_DRIVER_SENDER_IO_VECTOR_LENGTH_MAX (16)\n\n#define AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND (16)\n\n#define AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX (16)\n#define AERON_DRIVER_RECEIVER_MAX_UDP_PACKET_LENGTH (64 * 1024)\n\n#define AERON_TO_CONDUCTOR_BUFFER_LENGTH_DEFAULT (1024 * 1024 + AERON_RB_TRAILER_LENGTH)\n#define AERON_TO_CLIENTS_BUFFER_LENGTH_DEFAULT (1024 * 1024 + AERON_BROADCAST_BUFFER_TRAILER_LENGTH)\n#define AERON_COUNTERS_VALUES_BUFFER_LENGTH_DEFAULT UINT32_C(8 * 1024 * 1024)\n#define AERON_COUNTERS_VALUES_BUFFER_LENGTH_MIN UINT32_C(1024 * 1024)\n#define AERON_COUNTERS_VALUES_BUFFER_LENGTH_MAX UINT32_C(500 * 1024 * 1024)\n#define AERON_ERROR_BUFFER_LENGTH_DEFAULT (4 * 1024 * 1024)\n\ntypedef struct aeron_driver_conductor_stct aeron_driver_conductor_t;\n\ntypedef struct aeron_driver_conductor_proxy_stct aeron_driver_conductor_proxy_t;\ntypedef struct aeron_driver_sender_proxy_stct aeron_driver_sender_proxy_t;\ntypedef struct aeron_driver_receiver_proxy_stct aeron_driver_receiver_proxy_t;\ntypedef struct aeron_dl_loaded_libs_state_stct aeron_dl_loaded_libs_state_t;\n\ntypedef aeron_rb_handler_t aeron_driver_conductor_to_driver_interceptor_func_t;\ntypedef void (*aeron_driver_conductor_to_client_interceptor_func_t)(\n    aeron_driver_conductor_t *conductor, int32_t msg_type_id, const void *message, size_t length);\n\n#define AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(m) (AERON_THREADING_MODE_SHARED == (m) || AERON_THREADING_MODE_INVOKER == (m))\n\ntypedef struct aeron_driver_context_bindings_clientd_entry_stct\n{\n    const char *name;\n    void *clientd;\n}\naeron_driver_context_bindings_clientd_entry_t;\n\ntypedef void (*aeron_driver_name_resolver_on_neighbor_change_func_t)(const struct sockaddr_storage *addr);\n\ntypedef void (*aeron_driver_flow_control_strategy_on_receiver_change_func_t)(\n    int64_t receiver_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    size_t receiver_count);\n\ntypedef void (*aeron_driver_nak_message_func_t)(\n    const struct sockaddr_storage *address,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t nak_length,\n    size_t channel_length,\n    const char *channel);\n\ntypedef void (*aeron_driver_resend_func_t)(\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t resend_length,\n    size_t channel_length,\n    const char *channel);\n\ntypedef void (*aeron_driver_publication_revoke_func_t)(\n    int64_t revoked_pos,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\ntypedef void (*aeron_driver_publication_image_revoke_func_t)(\n    int64_t revoked_pos,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\ntypedef void (*aeron_driver_name_resolver_on_resolve_t)(\n    aeron_name_resolver_t *name_resolver,\n    int64_t duration_ns,\n    const char *hostname,\n    bool is_re_resolution,\n    struct sockaddr_storage *address);\n\ntypedef void (*aeron_driver_name_resolver_on_lookup_t)(\n    aeron_name_resolver_t *name_resolver,\n    int64_t duration_ns,\n    const char *name,\n    bool is_re_lookup,\n    const char *resolved_name);\n\ntypedef void (*aeron_driver_name_resolver_on_host_name_t)(\n    int64_t duration_ns,\n    const char *host_name);\n\ntypedef struct aeron_driver_context_stct\n{\n    char aeron_dir[AERON_MAX_PATH];                         /* aeron.dir */\n    aeron_threading_mode_t threading_mode;                  /* aeron.threading.mode = DEDICATED */\n    aeron_inferable_boolean_t receiver_group_consideration; /* aeron.receiver.group.consideration = INFER */\n    bool dirs_delete_on_start;                              /* aeron.dir.delete.on.start = false */\n    bool dirs_delete_on_shutdown;                           /* aeron.dir.delete.on.shutdown = false */\n    bool warn_if_dirs_exist;                                /* aeron.dir.warn.if.exists = false */\n    bool term_buffer_sparse_file;                           /* aeron.term.buffer.sparse.file = true */\n    bool perform_storage_checks;                            /* aeron.perform.storage.checks = true */\n    bool spies_simulate_connection;                         /* aeron.spies.simulate.connection = false */\n    bool print_configuration_on_start;                      /* aeron.print.configuration = false */\n    bool reliable_stream;                                   /* aeron.reliable.stream = true */\n    bool tether_subscriptions;                              /* aeron.tether.subscriptions = true */\n    bool rejoin_stream;                                     /* aeron.rejoin.stream = true */\n    bool ats_enabled;\n    bool connect_enabled;                                   /* aeron.driver.connect = true */\n    uint64_t driver_timeout_ms;                             /* aeron.driver.timeout = 10s */\n    uint64_t client_liveness_timeout_ns;                    /* aeron.client.liveness.timeout = 10s */\n    uint64_t publication_linger_timeout_ns;                 /* aeron.publication.linger.timeout = 5s */\n    uint64_t status_message_timeout_ns;                     /* aeron.rcv.status.message.timeout = 200ms */\n    uint64_t image_liveness_timeout_ns;                     /* aeron.image.liveness.timeout = 10s */\n    uint64_t publication_unblock_timeout_ns;                /* aeron.publication.unblock.timeout = 15s */\n    uint64_t publication_connection_timeout_ns;             /* aeron.publication.connection.timeout = 5s */\n    uint64_t timer_interval_ns;                             /* aeron.timer.interval = 1s */\n    uint64_t counter_free_to_reuse_ns;                      /* aeron.counters.free.to.reuse.timeout = 1s */\n    uint64_t untethered_window_limit_timeout_ns;            /* aeron.untethered.window.limit.timeout = 5s */\n    int64_t untethered_linger_timeout_ns;                   /* aeron.untethered.linger.timeout = -1 */\n    uint64_t untethered_resting_timeout_ns;                 /* aeron.untethered.resting.timeout = 10s */\n    uint64_t retransmit_unicast_delay_ns;                   /* aeron.retransmit.unicast.delay = 0 */\n    uint64_t retransmit_unicast_linger_ns;                  /* aeron.retransmit.unicast.linger = 10ms */\n    uint64_t nak_unicast_delay_ns;                          /* aeron.nak.unicast.delay = 100us */\n    uint64_t nak_unicast_retry_delay_ratio;                 /* aeron.nak.unicast.retry.delay.ratio = 100 */\n    uint64_t nak_multicast_max_backoff_ns;                  /* aeron.nak.multicast.max.backoff = 10ms */\n    size_t unicast_flow_control_rrwm;                       /* aeron.unicast.flow.control.rrwm = 16 */\n    size_t multicast_flow_control_rrwm;                     /* aeron.multicast.flow.control.rrwm = 4 */\n    uint64_t re_resolution_check_interval_ns;               /* aeron.driver.reresolution.check.interval = 1s */\n    uint64_t low_file_store_warning_threshold;              /* aeron.low.file.store.warning.threshold = 160MB */\n    size_t to_driver_buffer_length;                         /* aeron.conductor.buffer.length = 1MB + trailer*/\n    size_t to_clients_buffer_length;                        /* aeron.clients.buffer.length = 1MB + trailer */\n    size_t counters_values_buffer_length;                   /* aeron.counters.buffer.length = 1MB */\n    size_t error_buffer_length;                             /* aeron.error.buffer.length = 1MB */\n    size_t term_buffer_length;                              /* aeron.term.buffer.length = 16MB */\n    size_t ipc_term_buffer_length;                          /* aeron.ipc.term.buffer.length = 64MB */\n    size_t mtu_length;                                      /* aeron.mtu.length = 1408 */\n    size_t ipc_mtu_length;                                  /* aeron.ipc.mtu.length = 1408 */\n    size_t ipc_publication_window_length;                   /* aeron.ipc.publication.term.window.length = 0 */\n    size_t publication_window_length;                       /* aeron.publication.term.window.length = 0 */\n    size_t socket_rcvbuf;                                   /* aeron.socket.so_rcvbuf = 128 * 1024 */\n    size_t socket_sndbuf;                                   /* aeron.socket.so_sndbuf = 0 */\n    size_t send_to_sm_poll_ratio;                           /* aeron.send.to.status.poll.ratio = 6 */\n    size_t initial_window_length;                           /* aeron.rcv.initial.window.length = 128KB */\n    size_t loss_report_length;                              /* aeron.loss.report.buffer.length = 1MB */\n    size_t file_page_size;                                  /* aeron.file.page.size = 4KB */\n    size_t nak_multicast_group_size;                        /* aeron.nak.multicast.group.size = 10 */\n    int32_t publication_reserved_session_id_low;            /* aeron.publication.reserved.session.id.low = -1 */\n    int32_t publication_reserved_session_id_high;           /* aeron.publication.reserved.session.id.high = 1000 */\n    uint8_t multicast_ttl;                                  /* aeron.socket.multicast.ttl = 0 */\n    uint32_t receiver_io_vector_capacity;                   /* aeron.receiver.io.vector.capacity = 4 */\n    uint32_t sender_io_vector_capacity;                     /* aeron.sender.io.vector.capacity = 4 */\n    uint32_t network_publication_max_messages_per_send;     /* aeron.network.publication.max.messages.per.send = 4 */\n    uint32_t resource_free_limit;                           /* aeron.driver.resource.free.limit = 10 */\n    uint32_t async_executor_threads;                        /* aeron.driver.async.executor.threads = 1 */\n    uint32_t max_resend;                                    /* aeron.max.resend = 16 */\n\n    int32_t conductor_cpu_affinity_no;                      /* aeron.conductor.cpu.affinity = -1 */\n    int32_t receiver_cpu_affinity_no;                       /* aeron.receiver.cpu.affinity = -1 */\n    int32_t sender_cpu_affinity_no;                         /* aeron.sender.cpu.affinity = -1 */\n    int32_t stream_session_limit;                           /* aeron.driver.stream.session.limit = INT32_MAX */\n    bool enable_experimental_features;                      /* aeron.enable.experimental.features = false */\n\n    struct                                                  /* aeron.receiver.receiver.tag = <unset> */\n    {\n        bool is_present;\n        int64_t value;\n    }\n    receiver_group_tag;\n    struct\n    {\n        int32_t group_min_size;                             /* aeron.flow.control.receiver.group.min.size = 0 */\n        uint64_t receiver_timeout_ns;                       /* aeron.flow.control.receiver.timeout = 5s */\n        int64_t group_tag;                                  /* aeron.flow.control.gtag = -1 */\n    }\n    flow_control;\n\n    aeron_mapped_file_t cnc_map;\n    aeron_mapped_file_t loss_report;\n\n    uint8_t *to_driver_buffer;\n    uint8_t *to_clients_buffer;\n    uint8_t *counters_values_buffer;\n    uint8_t *counters_metadata_buffer;\n    uint8_t *error_buffer;\n\n    aeron_clock_func_t nano_clock;\n    aeron_clock_func_t epoch_clock;\n    aeron_clock_cache_t *cached_clock;\n    aeron_clock_cache_t *sender_cached_clock;\n    aeron_clock_cache_t *receiver_cached_clock;\n\n    aeron_mpsc_rb_t sender_command_queue;\n    aeron_mpsc_rb_t receiver_command_queue;\n    aeron_mpsc_rb_t conductor_command_queue;\n\n    aeron_agent_on_start_func_t agent_on_start_func;\n    void *agent_on_start_state;\n    aeron_agent_on_start_func_t agent_on_start_func_delegate;\n    void *agent_on_start_state_delegate;\n\n    aeron_idle_strategy_func_t conductor_idle_strategy_func;\n    void *conductor_idle_strategy_state;\n    char *conductor_idle_strategy_init_args;\n    const char *conductor_idle_strategy_name;\n\n    aeron_idle_strategy_func_t shared_idle_strategy_func;\n    void *shared_idle_strategy_state;\n    char *shared_idle_strategy_init_args;\n    const char *shared_idle_strategy_name;\n\n    aeron_idle_strategy_func_t shared_network_idle_strategy_func;\n    void *shared_network_idle_strategy_state;\n    char *shared_network_idle_strategy_init_args;\n    const char *shared_network_idle_strategy_name;\n\n    aeron_idle_strategy_func_t sender_idle_strategy_func;\n    void *sender_idle_strategy_state;\n    char *sender_idle_strategy_init_args;\n    const char *sender_idle_strategy_name;\n\n    aeron_idle_strategy_func_t receiver_idle_strategy_func;\n    void *receiver_idle_strategy_state;\n    char *receiver_idle_strategy_init_args;\n    const char *receiver_idle_strategy_name;\n\n    aeron_usable_fs_space_func_t usable_fs_space_func;\n    aeron_raw_log_map_func_t raw_log_map_func;\n    aeron_raw_log_close_func_t raw_log_close_func;\n    aeron_raw_log_free_func_t raw_log_free_func;\n\n    aeron_flow_control_strategy_supplier_func_t unicast_flow_control_supplier_func;\n    aeron_flow_control_strategy_supplier_func_t multicast_flow_control_supplier_func;\n\n    aeron_congestion_control_strategy_supplier_func_t congestion_control_supplier_func;\n\n    aeron_driver_conductor_proxy_t *conductor_proxy;\n    aeron_driver_sender_proxy_t *sender_proxy;\n    aeron_driver_receiver_proxy_t *receiver_proxy;\n\n    aeron_counters_manager_t *counters_manager;\n    aeron_system_counters_t *system_counters;\n    aeron_distinct_error_log_t *error_log;\n\n    struct\n    {\n        aeron_driver_conductor_to_driver_interceptor_func_t to_driver_interceptor;\n        aeron_driver_conductor_to_client_interceptor_func_t to_client_interceptor;\n        aeron_on_remove_publication_cleanup_func_t remove_publication_cleanup;\n        aeron_on_remove_subscription_cleanup_func_t remove_subscription_cleanup;\n        aeron_on_remove_image_cleanup_func_t remove_image_cleanup;\n        aeron_on_endpoint_change_func_t sender_proxy_on_add_endpoint;\n        aeron_on_endpoint_change_func_t sender_proxy_on_remove_endpoint;\n        aeron_on_endpoint_change_func_t receiver_proxy_on_add_endpoint;\n        aeron_on_endpoint_change_func_t receiver_proxy_on_remove_endpoint;\n        aeron_untethered_subscription_state_change_func_t untethered_subscription_on_state_change;\n        aeron_driver_name_resolver_on_neighbor_change_func_t name_resolution_on_neighbor_added;\n        aeron_driver_name_resolver_on_neighbor_change_func_t name_resolution_on_neighbor_removed;\n        aeron_driver_flow_control_strategy_on_receiver_change_func_t flow_control_on_receiver_added;\n        aeron_driver_flow_control_strategy_on_receiver_change_func_t flow_control_on_receiver_removed;\n        aeron_driver_name_resolver_on_resolve_t on_name_resolve;\n        aeron_driver_name_resolver_on_lookup_t on_name_lookup;\n        aeron_driver_name_resolver_on_host_name_t on_host_name;\n        aeron_driver_nak_message_func_t send_nak_message;\n        aeron_driver_nak_message_func_t on_nak_message;\n        aeron_driver_resend_func_t resend;\n        aeron_driver_publication_revoke_func_t publication_revoke;\n        aeron_driver_publication_image_revoke_func_t publication_image_revoke;\n    } log;\n\n    aeron_driver_termination_validator_func_t termination_validator_func;\n    void *termination_validator_state;\n\n    aeron_driver_termination_hook_func_t termination_hook_func;\n    void *termination_hook_state;\n\n    aeron_udp_channel_transport_bindings_t *udp_channel_transport_bindings;\n    aeron_udp_channel_transport_bindings_t *conductor_udp_channel_transport_bindings;\n    aeron_udp_channel_interceptor_bindings_t *udp_channel_outgoing_interceptor_bindings;\n    aeron_udp_channel_interceptor_bindings_t *udp_channel_incoming_interceptor_bindings;\n\n    int64_t next_receiver_id;\n\n    aeron_feedback_delay_generator_state_t unicast_delay_feedback_generator;\n    aeron_feedback_delay_generator_state_t multicast_delay_feedback_generator;\n\n    const char *resolver_name;\n    const char *resolver_interface;\n    const char *resolver_bootstrap_neighbor;\n    const char *name_resolver_init_args;\n    aeron_name_resolver_supplier_func_t name_resolver_supplier_func;\n    aeron_name_resolver_supplier_func_t driver_name_resolver_bootstrap_resolver_supplier_func;\n\n    aeron_duty_cycle_tracker_t *conductor_duty_cycle_tracker;\n    aeron_duty_cycle_tracker_t *sender_duty_cycle_tracker;\n    aeron_duty_cycle_tracker_t *receiver_duty_cycle_tracker;\n    aeron_duty_cycle_tracker_t *name_resolver_time_tracker;\n    aeron_duty_cycle_stall_tracker_t conductor_duty_cycle_stall_tracker;\n    aeron_duty_cycle_stall_tracker_t sender_duty_cycle_stall_tracker;\n    aeron_duty_cycle_stall_tracker_t receiver_duty_cycle_stall_tracker;\n    aeron_duty_cycle_stall_tracker_t name_resolver_time_stall_tracker;\n\n    aeron_port_manager_t *sender_port_manager;\n    aeron_port_manager_t *receiver_port_manager;\n    aeron_wildcard_port_manager_t sender_wildcard_port_manager;\n    aeron_wildcard_port_manager_t receiver_wildcard_port_manager;\n\n    aeron_dl_loaded_libs_state_t *dynamic_libs;\n    aeron_driver_context_bindings_clientd_entry_t *bindings_clientd_entries;\n    size_t num_bindings_clientd_entries;\n    struct\n    {\n        size_t default_so_sndbuf;\n        size_t max_so_sndbuf;\n        size_t default_so_rcvbuf;\n        size_t max_so_rcvbuf;\n    }\n    os_buffer_lengths;\n}\naeron_driver_context_t;\n\naeron_inferable_boolean_t aeron_config_parse_inferable_boolean(\n    const char *inferable_boolean, aeron_inferable_boolean_t def);\n\nconst char *aeron_driver_threading_mode_to_string(aeron_threading_mode_t mode);\n\nvoid aeron_driver_context_print_configuration(aeron_driver_context_t *context);\n\nvoid aeron_driver_fill_cnc_metadata(aeron_driver_context_t *context);\n\nint aeron_driver_validate_unblock_timeout(aeron_driver_context_t *context);\n\nint aeron_driver_validate_untethered_timeouts(aeron_driver_context_t *context);\n\nint aeron_driver_context_validate_mtu_length(uint64_t mtu_length);\n\nsize_t aeron_cnc_length(aeron_driver_context_t *context);\n\nint aeron_driver_context_run_storage_checks(aeron_driver_context_t *context, uint64_t log_length);\n\nint aeron_driver_context_bindings_clientd_create_entries(aeron_driver_context_t *context);\nint aeron_driver_context_bindings_clientd_delete_entries(aeron_driver_context_t *context);\nint aeron_driver_context_bindings_clientd_find_first_free_index(aeron_driver_context_t *context);\nint aeron_driver_context_bindings_clientd_find(aeron_driver_context_t *context, const char *name);\n\naeron_driver_context_bindings_clientd_entry_t *aeron_driver_context_bindings_clientd_get_or_find_first_free_entry(\n    aeron_driver_context_t *context, const char *name);\n\ninline void aeron_cnc_version_signal_cnc_ready(aeron_cnc_metadata_t *metadata, int32_t cnc_version)\n{\n    AERON_SET_RELEASE(metadata->cnc_version, cnc_version);\n}\n\ninline size_t aeron_producer_window_length(size_t producer_window_length, size_t term_length)\n{\n    size_t window_length = term_length / 2;\n\n    if (0 != producer_window_length && producer_window_length < window_length)\n    {\n        window_length = producer_window_length;\n    }\n\n    return window_length;\n}\n\ninline size_t aeron_receiver_window_length(size_t initial_receiver_window_length, size_t term_length)\n{\n    size_t term_window_length = term_length / 2;\n\n    return term_window_length < initial_receiver_window_length ? term_window_length : initial_receiver_window_length;\n}\n\n#endif //AERON_DRIVER_CONTEXT_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_name_resolver.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"util/aeron_platform.h\"\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <string.h>\n#include <inttypes.h>\n\n#if defined(AERON_COMPILER_MSVC)\n\n#include <winsock2.h>\n#include <inaddr.h>\n\n#define in_addr_t ULONG\n#else\n#include <unistd.h>\n#endif\n\n#include \"util/aeron_error.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"aeron_name_resolver.h\"\n#include \"media/aeron_udp_channel_transport.h\"\n#include \"media/aeron_udp_transport_poller.h\"\n#include \"aeron_name_resolver_cache.h\"\n#include \"aeron_driver_name_resolver.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\n#define AERON_NAME_RESOLVER_DRIVER_DUTY_CYCLE_MS (10)\n#define AERON_NAME_RESOLVER_DRIVER_NUM_RECV_BUFFERS (1)\n\n#define AERON_NAME_RESOLVER_DRIVER_MAX_BOOTSTRAP_NEIGHBORS (20)\n\ntypedef struct aeron_driver_name_resolver_neighbor_stct\n{\n    aeron_name_resolver_cache_addr_t cache_addr;\n    int64_t time_of_last_activity_ms;\n    struct sockaddr_storage socket_addr;\n}\naeron_driver_name_resolver_neighbor_t;\n\ntypedef struct aeron_driver_name_resolver_stct\n{\n    const char *name;\n    size_t name_length;\n    struct sockaddr_storage local_socket_addr;\n    aeron_name_resolver_cache_addr_t local_cache_addr;\n    const char *bootstrap_neighbor;\n    char *saved_bootstrap_neighbor;\n    size_t bootstrap_neighbors_length;\n    char **bootstrap_neighbors;\n    struct sockaddr_storage bootstrap_neighbor_addr;\n    aeron_udp_channel_transport_bindings_t *transport_bindings;\n    aeron_name_resolver_t bootstrap_resolver;\n    aeron_udp_channel_data_paths_t data_paths;\n    aeron_udp_channel_transport_t transport;\n    aeron_udp_transport_poller_t poller;\n    aeron_name_resolver_cache_t cache;\n    struct neighbours_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_driver_name_resolver_neighbor_t *array;\n    }\n    neighbors;\n\n    int64_t self_resolution_interval_ms;\n    int64_t neighbor_resolution_interval_ms;\n    int64_t neighbor_timeout_ms;\n\n    int64_t work_deadline_ms;\n    int64_t bootstrap_neighbor_resolve_deadline_ms;\n    int64_t self_resolutions_deadline_ms;\n    int64_t neighbor_resolutions_deadline_ms;\n\n    int64_t *invalid_packets_counter;\n    int64_t *short_sends_counter;\n    int64_t *error_counter;\n\n    aeron_clock_cache_t *cached_clock;\n    aeron_counters_manager_t *counters_manager;\n    aeron_position_t neighbor_counter;\n    aeron_position_t cache_size_counter;\n    aeron_distinct_error_log_t *error_log;\n\n    struct\n    {\n        aeron_driver_name_resolver_on_neighbor_change_func_t neighbor_added;\n        aeron_driver_name_resolver_on_neighbor_change_func_t neighbor_removed;\n    } log;\n\n    struct sockaddr_storage received_address;\n    uint8_t *aligned_buffer;\n    uint8_t buffer[AERON_MAX_UDP_PAYLOAD_LENGTH + AERON_CACHE_LINE_LENGTH];\n}\naeron_driver_name_resolver_t;\n\nvoid aeron_driver_name_resolver_receive(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_receive_timestamp);\n\nstatic int aeron_driver_name_resolver_from_sockaddr(\n    struct sockaddr_storage *addr, aeron_name_resolver_cache_addr_t *cache_addr);\n\nstatic int aeron_driver_name_resolver_resolve_bootstrap_neighbor(aeron_driver_name_resolver_t *driver_resolver)\n{\n    for (size_t i = 0; i < driver_resolver->bootstrap_neighbors_length; i++)\n    {\n        if (0 == aeron_name_resolver_resolve_host_and_port(\n            &driver_resolver->bootstrap_resolver,\n            driver_resolver->bootstrap_neighbors[i],\n            \"bootstrap_neighbor\",\n            false,\n            &driver_resolver->bootstrap_neighbor_addr))\n        {\n            aeron_err_clear();\n            return 0;\n        }\n    }\n\n    return -1;\n}\n\nstatic const char *aeron_driver_name_resolver_build_neighbor_counter_label(\n    aeron_driver_name_resolver_t *driver_resolver)\n{\n    static char buffer[512] = \"\";\n    int offset = snprintf(buffer, sizeof(buffer) - 1, \"Resolver neighbors: bound \");\n    offset += aeron_format_source_identity(\n        (char *)(buffer + offset), AERON_NETUTIL_FORMATTED_MAX_LENGTH, &driver_resolver->local_socket_addr);\n\n    if (NULL != driver_resolver->bootstrap_neighbor)\n    {\n        offset += snprintf(buffer + offset, 12, \" bootstrap \");\n        aeron_format_source_identity(\n            (char *)(buffer + offset), AERON_NETUTIL_FORMATTED_MAX_LENGTH, &driver_resolver->bootstrap_neighbor_addr);\n    }\n\n    return buffer;\n}\n\nint aeron_driver_name_resolver_free(aeron_driver_name_resolver_t *driver_resolver)\n{\n    if (NULL != driver_resolver->transport_bindings)\n    {\n        driver_resolver->transport_bindings->poller_close_func(&driver_resolver->poller);\n        driver_resolver->transport_bindings->close_func(&driver_resolver->transport);\n    }\n    aeron_udp_channel_data_paths_delete(&driver_resolver->data_paths);\n    aeron_name_resolver_cache_close(&driver_resolver->cache);\n    aeron_free(driver_resolver->neighbors.array);\n    aeron_free(driver_resolver->saved_bootstrap_neighbor);\n    aeron_free(driver_resolver->bootstrap_neighbors);\n    aeron_free(driver_resolver);\n\n    return 0;\n}\n\nint aeron_driver_name_resolver_init(\n    aeron_driver_name_resolver_t **driver_resolver,\n    aeron_driver_context_t *context,\n    const char *name,\n    const char *interface_name,\n    const char *bootstrap_neighbor)\n{\n    aeron_driver_name_resolver_t *_driver_resolver = NULL;\n\n    if (aeron_alloc((void **)&_driver_resolver, sizeof(aeron_driver_name_resolver_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"Failed to allocate driver resolver for: %s\", name);\n        goto error_cleanup;\n    }\n    _driver_resolver->saved_bootstrap_neighbor = NULL;\n    _driver_resolver->bootstrap_neighbors = NULL;\n    _driver_resolver->aligned_buffer = aeron_cache_line_align_buffer(_driver_resolver->buffer);\n    _driver_resolver->name = name;\n    _driver_resolver->name_length = strlen(name);\n\n    size_t prefixlen = 0;\n    if (aeron_interface_parse_and_resolve(interface_name, &_driver_resolver->local_socket_addr, &prefixlen) < 0)\n    {\n        AERON_APPEND_ERR(\"failed to parse interface: %s\", interface_name);\n        goto error_cleanup;\n    }\n\n    if (aeron_driver_name_resolver_from_sockaddr(\n        &_driver_resolver->local_socket_addr, &_driver_resolver->local_cache_addr) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    const aeron_name_resolver_supplier_func_t bootstrap_resolver_supplier_func =\n        NULL != context->driver_name_resolver_bootstrap_resolver_supplier_func ?\n        context->driver_name_resolver_bootstrap_resolver_supplier_func :\n        aeron_default_name_resolver_supplier;\n    if (bootstrap_resolver_supplier_func(&_driver_resolver->bootstrap_resolver, NULL, context) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    _driver_resolver->log.neighbor_added = context->log.name_resolution_on_neighbor_added;\n    _driver_resolver->log.neighbor_removed = context->log.name_resolution_on_neighbor_removed;\n\n    _driver_resolver->bootstrap_neighbor = bootstrap_neighbor;\n    _driver_resolver->bootstrap_neighbors_length = 0;\n    if (NULL != bootstrap_neighbor)\n    {\n        _driver_resolver->saved_bootstrap_neighbor = strdup(bootstrap_neighbor);\n        if (NULL == _driver_resolver->saved_bootstrap_neighbor)\n        {\n            AERON_SET_ERR(EINVAL, \"failed to duplicate bootstrap neighbors string: %s\", bootstrap_neighbor);\n            goto error_cleanup;\n        }\n        char *bootstrap_neighbors[AERON_NAME_RESOLVER_DRIVER_MAX_BOOTSTRAP_NEIGHBORS];\n\n        const int num_neighbors = aeron_tokenise(\n            _driver_resolver->saved_bootstrap_neighbor,\n            ',',\n            AERON_NAME_RESOLVER_DRIVER_MAX_BOOTSTRAP_NEIGHBORS,\n            bootstrap_neighbors);\n\n        if (num_neighbors < 0)\n        {\n            AERON_SET_ERR(EINVAL, \"failed to parse bootstrap neighbors list: %s\", bootstrap_neighbor);\n            goto error_cleanup;\n        }\n\n        if (aeron_alloc(\n            (void **)&_driver_resolver->bootstrap_neighbors,\n            (strlen(bootstrap_neighbor) + num_neighbors) * sizeof(char)) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"failed to allocate bootstrap neighbors array\");\n            goto error_cleanup;\n        }\n\n        _driver_resolver->bootstrap_neighbors_length = (size_t)num_neighbors;\n        for (int i = 0; i < num_neighbors; i++)\n        {\n            _driver_resolver->bootstrap_neighbors[i] = bootstrap_neighbors[num_neighbors - i - 1];\n        }\n\n        if (aeron_driver_name_resolver_resolve_bootstrap_neighbor(_driver_resolver) < 0)\n        {\n            AERON_APPEND_ERR(\"failed to bootstrap neighbor from: %s\", bootstrap_neighbor);\n            goto error_cleanup;\n        }\n    }\n\n    _driver_resolver->transport_bindings = context->conductor_udp_channel_transport_bindings;\n    if (aeron_udp_channel_data_paths_init(\n        &_driver_resolver->data_paths,\n        context->udp_channel_outgoing_interceptor_bindings,\n        context->udp_channel_incoming_interceptor_bindings,\n        _driver_resolver->transport_bindings,\n        aeron_driver_name_resolver_receive,\n        context,\n        AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_CONDUCTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    aeron_udp_channel_transport_params_t transport_params = {\n        context->socket_rcvbuf,\n        context->socket_sndbuf,\n        context->mtu_length,\n        0,\n        0,\n        false,\n    };\n\n    if (_driver_resolver->transport_bindings->init_func(\n        &_driver_resolver->transport,\n        &_driver_resolver->local_socket_addr,\n        NULL, // Unicast only.\n        NULL, // No connected\n        &transport_params,\n        context,\n        AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_CONDUCTOR) < 0)\n    {\n        AERON_APPEND_ERR(\n            \"resolver, name=%s interface_name=%s bootstrap_neighbor=%s\",\n            _driver_resolver->name,\n            interface_name,\n            bootstrap_neighbor);\n        goto error_cleanup;\n    }\n\n    _driver_resolver->transport.data_paths = &_driver_resolver->data_paths;\n\n    if (_driver_resolver->transport_bindings->poller_init_func(\n        &_driver_resolver->poller, context, AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_CONDUCTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    if (_driver_resolver->transport_bindings->poller_add_func(\n        &_driver_resolver->poller, &_driver_resolver->transport) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    aeron_name_resolver_cache_init(&_driver_resolver->cache, AERON_NAME_RESOLVER_DRIVER_TIMEOUT_MS);\n\n    int64_t now_ms = context->epoch_clock();\n    _driver_resolver->cached_clock = context->cached_clock;\n    _driver_resolver->neighbor_timeout_ms = AERON_NAME_RESOLVER_DRIVER_TIMEOUT_MS;\n    _driver_resolver->self_resolution_interval_ms = AERON_NAME_RESOLVER_DRIVER_SELF_RESOLUTION_INTERVAL_MS;\n    _driver_resolver->self_resolutions_deadline_ms = 0;\n    _driver_resolver->neighbor_resolution_interval_ms = AERON_NAME_RESOLVER_DRIVER_NEIGHBOUR_RESOLUTION_INTERVAL_MS;\n    _driver_resolver->neighbor_resolutions_deadline_ms = now_ms + _driver_resolver->neighbor_resolution_interval_ms;\n    _driver_resolver->bootstrap_neighbor_resolve_deadline_ms = now_ms;\n    _driver_resolver->work_deadline_ms = 0;\n\n    const char *neighbor_counter_label = aeron_driver_name_resolver_build_neighbor_counter_label(_driver_resolver);\n    _driver_resolver->neighbor_counter.counter_id = aeron_counters_manager_allocate(\n        context->counters_manager,\n        AERON_COUNTER_NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID,\n        NULL, 0,\n        neighbor_counter_label, strlen(neighbor_counter_label));\n    if (_driver_resolver->neighbor_counter.counter_id < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    _driver_resolver->neighbor_counter.value_addr = aeron_counters_manager_addr(\n        context->counters_manager, _driver_resolver->neighbor_counter.counter_id);\n    char cache_entries_label[512];\n    snprintf(\n        cache_entries_label, sizeof(cache_entries_label), \"Resolver cache entries: name=%s\", _driver_resolver->name);\n    _driver_resolver->cache_size_counter.counter_id = aeron_counters_manager_allocate(\n        context->counters_manager,\n        AERON_COUNTER_NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID,\n        NULL, 0,\n        cache_entries_label, strlen(cache_entries_label));\n    if (_driver_resolver->cache_size_counter.counter_id < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n    _driver_resolver->counters_manager = context->counters_manager;\n\n    _driver_resolver->cache_size_counter.value_addr = aeron_counters_manager_addr(\n        context->counters_manager, _driver_resolver->cache_size_counter.counter_id);\n\n    _driver_resolver->short_sends_counter = aeron_system_counter_addr(\n        context->system_counters, AERON_SYSTEM_COUNTER_SHORT_SENDS);\n    _driver_resolver->invalid_packets_counter = aeron_system_counter_addr(\n        context->system_counters, AERON_SYSTEM_COUNTER_INVALID_PACKETS);\n    _driver_resolver->error_counter = aeron_system_counter_addr(\n        context->system_counters, AERON_SYSTEM_COUNTER_ERRORS);\n\n    _driver_resolver->error_log = context->error_log;\n\n    *driver_resolver = _driver_resolver;\n    return 0;\n\nerror_cleanup:\n    aeron_driver_name_resolver_free(_driver_resolver);\n    return -1;\n}\n\nint aeron_driver_name_resolver_close(aeron_name_resolver_t *resolver)\n{\n    aeron_driver_name_resolver_t *driver_resolver = (aeron_driver_name_resolver_t *)resolver->state;\n    return aeron_driver_name_resolver_free(driver_resolver);\n}\n\nstatic int aeron_driver_name_resolver_to_sockaddr(\n    aeron_name_resolver_cache_addr_t *cache_addr, struct sockaddr_storage *addr)\n{\n    int result = -1;\n    if (cache_addr->res_type == AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD)\n    {\n        struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;\n        addr_in6->sin6_family = AF_INET6;\n        addr_in6->sin6_port = htons(cache_addr->port);\n        memcpy(&addr_in6->sin6_addr, cache_addr->address, sizeof(addr_in6->sin6_addr));\n        result = 0;\n    }\n    else if (cache_addr->res_type == AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD)\n    {\n        struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;\n        addr_in->sin_family = AF_INET;\n        addr_in->sin_port = htons(cache_addr->port);\n        memcpy(&addr_in->sin_addr, cache_addr->address, sizeof(addr_in->sin_addr));\n        result = 0;\n    }\n    else\n    {\n        AERON_SET_ERR(EINVAL, \"Invalid res_type: %d\", cache_addr->res_type);\n    }\n\n    return result;\n}\n\nstatic int aeron_driver_name_resolver_from_sockaddr(\n    struct sockaddr_storage *addr, aeron_name_resolver_cache_addr_t *cache_addr)\n{\n    int result = -1;\n    if (AF_INET6 == addr->ss_family)\n    {\n        struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;\n        cache_addr->res_type = AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD;\n        cache_addr->port = ntohs(addr_in6->sin6_port);\n        memcpy(cache_addr->address, &addr_in6->sin6_addr, sizeof(addr_in6->sin6_addr));\n        result = 0;\n    }\n    else if (AF_INET == addr->ss_family)\n    {\n        struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;\n        cache_addr->res_type = AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD;\n        cache_addr->port = ntohs(addr_in->sin_port);\n        memcpy(cache_addr->address, &addr_in->sin_addr, sizeof(addr_in->sin_addr));\n        result = 0;\n    }\n    else\n    {\n        AERON_SET_ERR(EINVAL, \"Invalid address family: %d\", addr->ss_family);\n    }\n\n    return result;\n}\n\nstatic uint16_t aeron_driver_name_resolver_get_port(aeron_driver_name_resolver_t *resolver)\n{\n    uint16_t port = AF_INET6 == resolver->local_socket_addr.ss_family ?\n        ((struct sockaddr_in6 *)&resolver->local_socket_addr)->sin6_port :\n        ((struct sockaddr_in *)&resolver->local_socket_addr)->sin_port;\n\n    return ntohs(port);\n}\n\nstatic int aeron_driver_name_resolver_find_neighbor_by_addr(\n    aeron_driver_name_resolver_t *resolver, aeron_name_resolver_cache_addr_t *cache_addr)\n{\n    for (size_t i = 0; i < resolver->neighbors.length; i++)\n    {\n        aeron_driver_name_resolver_neighbor_t *neighbor = &resolver->neighbors.array[i];\n\n        if (cache_addr->res_type == neighbor->cache_addr.res_type &&\n            cache_addr->port == neighbor->cache_addr.port &&\n            0 == memcmp(\n                cache_addr->address,\n                neighbor->cache_addr.address,\n                aeron_res_header_address_length(cache_addr->res_type)))\n        {\n            return (int)i;\n        }\n    }\n\n    return -1;\n}\n\nstatic int aeron_driver_name_resolver_add_neighbor(\n    aeron_driver_name_resolver_t *resolver,\n    aeron_name_resolver_cache_addr_t *cache_addr,\n    bool is_self,\n    int64_t time_of_last_activity_ms)\n{\n    const int neighbor_index = aeron_driver_name_resolver_find_neighbor_by_addr(resolver, cache_addr);\n    if (neighbor_index < 0)\n    {\n        int ensure_capacity_result = 0;\n        AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, resolver->neighbors, aeron_driver_name_resolver_neighbor_t)\n        if (ensure_capacity_result < 0)\n        {\n            AERON_APPEND_ERR(\n                \"Failed to allocate rows for neighbors table (%\" PRIu64 \", %\" PRIu64 \")\",\n                (uint64_t)resolver->neighbors.length,\n                (uint64_t)resolver->neighbors.capacity);\n\n            return ensure_capacity_result;\n        }\n\n        aeron_driver_name_resolver_neighbor_t *new_neighbor = &resolver->neighbors.array[resolver->neighbors.length];\n        if (aeron_driver_name_resolver_to_sockaddr(cache_addr, &new_neighbor->socket_addr) < 0)\n        {\n            return -1;\n        }\n\n        resolver->log.neighbor_added(&new_neighbor->socket_addr);\n\n        memcpy(&new_neighbor->cache_addr, cache_addr, sizeof(new_neighbor->cache_addr));\n        new_neighbor->time_of_last_activity_ms = time_of_last_activity_ms;\n        resolver->neighbors.length++;\n        aeron_counter_set_release(resolver->neighbor_counter.value_addr, (int64_t) resolver->neighbors.length);\n\n        return 1;\n    }\n    else if (is_self)\n    {\n        resolver->neighbors.array[neighbor_index].time_of_last_activity_ms = time_of_last_activity_ms;\n\n        return 2;\n    }\n\n    return 0;\n}\n\nstatic int aeron_driver_name_resolver_on_resolution_entry(\n    aeron_driver_name_resolver_t *resolver,\n    const aeron_resolution_header_t *resolution_header,\n    const char *name,\n    size_t name_length,\n    aeron_name_resolver_cache_addr_t *cache_addr,\n    bool is_self,\n    int64_t now_ms)\n{\n    // ignore self record\n    if (cache_addr->port == aeron_driver_name_resolver_get_port(resolver) &&\n        name_length == resolver->name_length &&\n        0 == strncmp(resolver->name, name, name_length))\n    {\n        return 0;\n    }\n\n    int64_t time_of_last_activity_ms = now_ms - resolution_header->age_in_ms;\n\n    if (aeron_name_resolver_cache_add_or_update(\n        &resolver->cache,\n        name,\n        name_length,\n        cache_addr,\n        time_of_last_activity_ms,\n        resolver->cache_size_counter.value_addr) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_driver_name_resolver_add_neighbor(resolver, cache_addr, is_self, time_of_last_activity_ms) < 0)\n    {\n        return -1;\n    }\n\n    return 1;\n}\n\nstatic bool aeron_driver_name_resolver_is_wildcard(int8_t res_type, uint8_t *address)\n{\n    in_addr_t ipv4_wildcard = INADDR_ANY;\n    return\n        (res_type == AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD && 0 == memcmp(address, &in6addr_any, sizeof(in6addr_any))) ||\n        0 == memcmp(address, &ipv4_wildcard, sizeof(ipv4_wildcard));\n}\n\nstatic void aeron_name_resolver_log_and_clear_error(aeron_driver_name_resolver_t *resolver)\n{\n    aeron_distinct_error_log_record(resolver->error_log, aeron_errcode(), aeron_errmsg());\n    aeron_counter_increment(resolver->error_counter);\n    aeron_err_clear();\n}\n\nvoid aeron_driver_name_resolver_receive(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_receive_timestamp)\n{\n    aeron_driver_name_resolver_t *resolver = receiver_clientd;\n    aeron_frame_header_t *frame_header = (aeron_frame_header_t *)buffer;\n    size_t remaining = length;\n\n    if ((remaining < sizeof(aeron_frame_header_t)) || (frame_header->version != AERON_FRAME_HEADER_VERSION))\n    {\n        aeron_counter_increment(resolver->invalid_packets_counter);\n        return;\n    }\n\n    remaining -= sizeof(aeron_frame_header_t);\n\n    while (0 < remaining)\n    {\n        const size_t offset = length - remaining;\n        if (AERON_HDR_TYPE_RES != frame_header->type || remaining < sizeof(aeron_resolution_header_t))\n        {\n            aeron_counter_increment(resolver->invalid_packets_counter);\n            return;\n        }\n\n        aeron_resolution_header_t *resolution_header = (aeron_resolution_header_t *)&buffer[offset];\n        aeron_name_resolver_cache_addr_t cache_addr;\n        const char *name = NULL;\n        size_t name_length;\n        size_t entry_length;\n\n        cache_addr.res_type = resolution_header->res_type;\n        cache_addr.port = resolution_header->udp_port;\n\n        if (AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD == resolution_header->res_type)\n        {\n            aeron_resolution_header_ipv4_t *ip4_hdr = (aeron_resolution_header_ipv4_t *)resolution_header;\n            if (length < sizeof(aeron_resolution_header_ipv4_t) ||\n                length < (entry_length = aeron_res_header_entry_length_ipv4(ip4_hdr)))\n            {\n                aeron_counter_increment(resolver->invalid_packets_counter);\n                return;\n            }\n\n            memcpy(cache_addr.address, ip4_hdr->addr, sizeof(ip4_hdr->addr));\n            name_length = (size_t)ip4_hdr->name_length;\n            name = (const char *)(ip4_hdr + 1);\n        }\n        else if (AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD == resolution_header->res_type)\n        {\n            aeron_resolution_header_ipv6_t *ip6_hdr = (aeron_resolution_header_ipv6_t *)resolution_header;\n            if (length < sizeof(aeron_resolution_header_ipv6_t) ||\n                length < (entry_length = aeron_res_header_entry_length_ipv6(ip6_hdr)))\n            {\n                aeron_counter_increment(resolver->invalid_packets_counter);\n                return;\n            }\n\n            memcpy(cache_addr.address, ip6_hdr->addr, sizeof(ip6_hdr->addr));\n            name_length = (size_t)ip6_hdr->name_length;\n            name = (const char *)(ip6_hdr + 1);\n        }\n        else\n        {\n            AERON_SET_ERR(EINVAL, \"Invalid res type on entry: %d\", resolution_header->res_type);\n            aeron_name_resolver_log_and_clear_error(resolver);\n            return;\n        }\n\n        const bool is_self = AERON_RES_HEADER_SELF_FLAG == (resolution_header->res_flags & AERON_RES_HEADER_SELF_FLAG);\n\n        if (is_self && aeron_driver_name_resolver_is_wildcard(cache_addr.res_type, cache_addr.address))\n        {\n            if (aeron_driver_name_resolver_from_sockaddr(addr, &cache_addr) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"Failed to replace wildcard with source addr\");\n                aeron_name_resolver_log_and_clear_error(resolver);\n\n                return;\n            }\n        }\n\n        const int64_t now_ms = aeron_clock_cached_epoch_time(resolver->cached_clock);\n        if (aeron_driver_name_resolver_on_resolution_entry(\n            resolver, resolution_header, name, name_length, &cache_addr, is_self, now_ms) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to handle resolution entry\");\n            aeron_name_resolver_log_and_clear_error(resolver);\n        }\n\n        remaining -= entry_length;\n    }\n}\n\nstatic int aeron_driver_name_resolver_poll(aeron_driver_name_resolver_t *resolver)\n{\n    struct mmsghdr mmsghdr[AERON_NAME_RESOLVER_DRIVER_NUM_RECV_BUFFERS];\n    struct iovec iov[AERON_NAME_RESOLVER_DRIVER_NUM_RECV_BUFFERS];\n    iov[0].iov_base = resolver->aligned_buffer;\n    iov[0].iov_len = AERON_MAX_UDP_PAYLOAD_LENGTH;\n\n    for (size_t i = 0; i < AERON_NAME_RESOLVER_DRIVER_NUM_RECV_BUFFERS; i++)\n    {\n        mmsghdr[i].msg_hdr.msg_name = &resolver->received_address;\n        mmsghdr[i].msg_hdr.msg_namelen = AERON_ADDR_LEN(&resolver->received_address);\n        mmsghdr[i].msg_hdr.msg_iov = &iov[i];\n        mmsghdr[i].msg_hdr.msg_iovlen = 1;\n        mmsghdr[i].msg_hdr.msg_flags = 0;\n        mmsghdr[i].msg_hdr.msg_control = NULL;\n        mmsghdr[i].msg_hdr.msg_controllen = 0;\n        mmsghdr[i].msg_len = 0;\n    }\n\n    int64_t bytes_received = 0;\n    int poll_result = resolver->transport_bindings->poller_poll_func(\n        &resolver->poller,\n        mmsghdr,\n        AERON_NAME_RESOLVER_DRIVER_NUM_RECV_BUFFERS,\n        &bytes_received,\n        resolver->data_paths.recv_func,\n        resolver->transport_bindings->recvmmsg_func,\n        resolver);\n\n    if (poll_result < 0)\n    {\n        AERON_APPEND_ERR(\"Failed to poll in driver name resolver: %s\", resolver->name);\n        aeron_name_resolver_log_and_clear_error(resolver);\n    }\n\n    return bytes_received > 0 ? (int)bytes_received : 0;\n}\n\nstatic bool aeron_driver_name_resolver_sockaddr_equals(struct sockaddr_storage *a, struct sockaddr_storage *b)\n{\n    if (a->ss_family != b->ss_family)\n    {\n        return false;\n    }\n\n    if (AF_INET == a->ss_family)\n    {\n        struct sockaddr_in *a_in = (struct sockaddr_in *)a;\n        struct sockaddr_in *b_in = (struct sockaddr_in *)b;\n\n        return a_in->sin_addr.s_addr == b_in->sin_addr.s_addr && a_in->sin_port == b_in->sin_port;\n    }\n    else if (AF_INET6 == a->ss_family)\n    {\n        struct sockaddr_in6 *a_in = (struct sockaddr_in6 *)a;\n        struct sockaddr_in6 *b_in = (struct sockaddr_in6 *)b;\n\n        return 0 == memcmp(&a_in->sin6_addr, &b_in->sin6_addr, sizeof(a_in->sin6_addr)) &&\n            a_in->sin6_port == b_in->sin6_port;\n    }\n\n    return false;\n}\n\nstatic int aeron_driver_name_resolver_do_send(\n    aeron_driver_name_resolver_t *resolver,\n    aeron_frame_header_t *frame_header,\n    ssize_t length,\n    struct sockaddr_storage *neighbor_address)\n{\n    struct iovec iov;\n    int64_t bytes_sent = 0;\n\n    iov.iov_base = frame_header;\n    iov.iov_len = (uint32_t)length;\n\n    int send_result = resolver->data_paths.send_func(\n        &resolver->data_paths, &resolver->transport, neighbor_address, &iov, 1, &bytes_sent);\n    if (0 <= send_result)\n    {\n        if (bytes_sent < (int64_t)iov.iov_len)\n        {\n            aeron_counter_increment(resolver->short_sends_counter);\n        }\n    }\n    else\n    {\n        char buffer[AERON_NETUTIL_FORMATTED_MAX_LENGTH] = { 0 };\n        aeron_format_source_identity(buffer, AERON_NETUTIL_FORMATTED_MAX_LENGTH, neighbor_address);\n        AERON_APPEND_ERR(\"Failed to send resolution frames to neighbor: %s (protocol_family=%i)\",\n            buffer, neighbor_address->ss_family);\n    }\n\n    return send_result;\n}\n\nstatic int aeron_driver_name_resolver_send_self_resolutions(\n    aeron_driver_name_resolver_t *driver_resolver, aeron_name_resolver_t *resolver, int64_t now_ms)\n{\n    if (0 == driver_resolver->bootstrap_neighbors_length && 0 == driver_resolver->neighbors.length)\n    {\n        return 0;\n    }\n\n    const size_t entry_offset = sizeof(aeron_frame_header_t);\n    uint8_t *aligned_buffer = driver_resolver->aligned_buffer;\n    aeron_frame_header_t *frame_header = (aeron_frame_header_t *)&aligned_buffer[0];\n    aeron_resolution_header_t *resolution_header = (aeron_resolution_header_t *)&aligned_buffer[entry_offset];\n\n    const size_t name_length = driver_resolver->name_length; // TODO: cache name length\n\n    int entry_length = aeron_driver_name_resolver_set_resolution_header(\n        resolution_header,\n        AERON_MAX_UDP_PAYLOAD_LENGTH - entry_offset,\n        AERON_RES_HEADER_SELF_FLAG,\n        &driver_resolver->local_cache_addr,\n        driver_resolver->name,\n        name_length);\n\n    assert(0 <= entry_length && \"local_cache_addr should have been correctly constructed during init\");\n\n    size_t frame_length = sizeof(aeron_frame_header_t) + (size_t)entry_length;\n\n    frame_header->type = AERON_HDR_TYPE_RES;\n    frame_header->flags = UINT8_C(0);\n    frame_header->version = AERON_FRAME_HEADER_VERSION;\n    frame_header->frame_length = (int32_t)frame_length;\n    resolution_header->age_in_ms = 0;\n\n    struct sockaddr_storage neighbor_sock_addr;\n\n    bool send_to_bootstrap = driver_resolver->bootstrap_neighbors_length > 0;\n    int work_count = 0;\n\n    for (size_t k = 0; k < driver_resolver->neighbors.length; k++)\n    {\n        aeron_driver_name_resolver_neighbor_t *neighbor = &driver_resolver->neighbors.array[k];\n\n        aeron_driver_name_resolver_to_sockaddr(&neighbor->cache_addr, &neighbor_sock_addr);\n\n        if (aeron_driver_name_resolver_do_send(driver_resolver, frame_header, frame_length, &neighbor_sock_addr) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to send self resolutions\");\n            aeron_name_resolver_log_and_clear_error(driver_resolver);\n        }\n        else\n        {\n            work_count++;\n        }\n\n        if (send_to_bootstrap &&\n            aeron_driver_name_resolver_sockaddr_equals(&driver_resolver->bootstrap_neighbor_addr, &neighbor_sock_addr))\n        {\n            send_to_bootstrap = false;\n        }\n    }\n\n    if (send_to_bootstrap)\n    {\n        if (now_ms > driver_resolver->bootstrap_neighbor_resolve_deadline_ms)\n        {\n            struct sockaddr_storage old_address = driver_resolver->bootstrap_neighbor_addr;\n            if (aeron_driver_name_resolver_resolve_bootstrap_neighbor(driver_resolver) < 0)\n            {\n                AERON_APPEND_ERR(\"failed to resolve bootstrap neighbor (%s)\", driver_resolver->bootstrap_neighbor);\n                return work_count;\n            }\n\n            driver_resolver->bootstrap_neighbor_resolve_deadline_ms = now_ms + AERON_NAME_RESOLVER_DRIVER_TIMEOUT_MS;\n\n            if (!aeron_driver_name_resolver_sockaddr_equals(&driver_resolver->bootstrap_neighbor_addr, &old_address))\n            {\n                const char *neighbor_counter_label = aeron_driver_name_resolver_build_neighbor_counter_label(driver_resolver);\n                aeron_counters_manager_update_label(\n                    driver_resolver->counters_manager,\n                    driver_resolver->neighbor_counter.counter_id,\n                    strlen(neighbor_counter_label),\n                    neighbor_counter_label);\n\n                // avoid sending resolution frame if new bootstrap is in the neighbors list\n                for (size_t k = 0; k < driver_resolver->neighbors.length; k++)\n                {\n                    aeron_driver_name_resolver_neighbor_t *neighbor = &driver_resolver->neighbors.array[k];\n\n                    aeron_driver_name_resolver_to_sockaddr(&neighbor->cache_addr, &neighbor_sock_addr);\n\n                    if (aeron_driver_name_resolver_sockaddr_equals(\n                        &driver_resolver->bootstrap_neighbor_addr, &neighbor_sock_addr))\n                    {\n                        send_to_bootstrap = false;\n                        break;\n                    }\n                }\n            }\n        }\n\n        if (send_to_bootstrap)\n        {\n            if (aeron_driver_name_resolver_do_send(\n                driver_resolver, frame_header, frame_length, &driver_resolver->bootstrap_neighbor_addr) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"Failed to send bootstrap resolutions\");\n                aeron_name_resolver_log_and_clear_error(driver_resolver);\n            }\n            else\n            {\n                work_count++;\n            }\n        }\n    }\n\n    return work_count;\n}\n\nstatic int aeron_driver_name_resolver_send_neighbor_resolutions(aeron_driver_name_resolver_t *resolver, int64_t now_ms)\n{\n    uint8_t *aligned_buffer = resolver->aligned_buffer;\n    aeron_frame_header_t *frame_header = (aeron_frame_header_t *)aligned_buffer;\n    frame_header->type = AERON_HDR_TYPE_RES;\n    frame_header->flags = UINT8_C(0);\n    frame_header->version = AERON_FRAME_HEADER_VERSION;\n\n    size_t i;\n    size_t j;\n    int work_count = 0;\n    for (i = 0; i < resolver->cache.entries.length;)\n    {\n        size_t entry_offset = sizeof(aeron_frame_header_t);\n\n        for (j = i; j < resolver->cache.entries.length;)\n        {\n            aeron_resolution_header_t *resolution_header = (aeron_resolution_header_t *)&aligned_buffer[entry_offset];\n            aeron_name_resolver_cache_entry_t *cache_entry = &resolver->cache.entries.array[j];\n\n            int entry_length = aeron_driver_name_resolver_set_resolution_header(\n                resolution_header,\n                AERON_MAX_UDP_PAYLOAD_LENGTH - entry_offset,\n                0,\n                &cache_entry->cache_addr,\n                cache_entry->name,\n                cache_entry->name_length);\n\n            assert(-1 != entry_length && \"Invalid res_type crept in from somewhere\");\n\n            resolution_header->age_in_ms = (int32_t)(now_ms - cache_entry->time_of_last_activity_ms);\n\n            if (0 == entry_length)\n            {\n                break;\n            }\n\n            entry_offset += entry_length;\n            j++;\n        }\n\n        frame_header->frame_length = (int32_t)entry_offset;\n\n        for (size_t k = 0; k < resolver->neighbors.length; k++)\n        {\n            aeron_driver_name_resolver_neighbor_t *neighbor = &resolver->neighbors.array[k];\n\n            if (aeron_driver_name_resolver_do_send(resolver, frame_header, entry_offset, &neighbor->socket_addr) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"Failed to send neighbor resolutions\");\n                aeron_distinct_error_log_record(resolver->error_log, aeron_errcode(), aeron_errmsg());\n            }\n            else\n            {\n                work_count++;\n            }\n        }\n\n        i = j;\n    }\n\n    return work_count;\n}\n\nstatic int aeron_driver_name_resolver_timeout_neighbors(aeron_driver_name_resolver_t *resolver, int64_t now_ms)\n{\n    int num_removed = 0;\n    for (int last_index = (int)resolver->neighbors.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_driver_name_resolver_neighbor_t *entry = &resolver->neighbors.array[i];\n\n        if ((entry->time_of_last_activity_ms + resolver->neighbor_timeout_ms) <= now_ms)\n        {\n            resolver->log.neighbor_removed(&entry->socket_addr);\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)resolver->neighbors.array, sizeof(aeron_driver_name_resolver_neighbor_t), i, last_index);\n            resolver->neighbors.length--;\n            last_index--;\n            num_removed++;\n        }\n    }\n\n    if (0 != num_removed)\n    {\n        aeron_counter_set_release(resolver->neighbor_counter.value_addr, (int64_t) resolver->neighbors.length);\n    }\n\n    return num_removed;\n}\n\nint aeron_driver_name_resolver_set_resolution_header_from_sockaddr(\n    aeron_resolution_header_t *resolution_header,\n    size_t capacity,\n    uint8_t flags,\n    struct sockaddr_storage *addr,\n    const char *name,\n    size_t name_length)\n{\n    aeron_name_resolver_cache_addr_t cache_addr;\n\n    if (aeron_driver_name_resolver_from_sockaddr(addr, &cache_addr) < 0)\n    {\n        return -1;\n    }\n\n    return aeron_driver_name_resolver_set_resolution_header(\n        resolution_header, capacity, flags, &cache_addr, name, name_length);\n}\n\nint aeron_driver_name_resolver_set_resolution_header(\n    aeron_resolution_header_t *resolution_header,\n    size_t capacity,\n    uint8_t flags,\n    aeron_name_resolver_cache_addr_t *cache_addr,\n    const char *name,\n    size_t name_length)\n{\n    size_t name_offset;\n    size_t entry_length;\n\n    switch (cache_addr->res_type)\n    {\n        case AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD:\n            entry_length = AERON_ALIGN(sizeof(aeron_resolution_header_ipv4_t) + name_length, sizeof(int64_t));\n            if (capacity < entry_length)\n            {\n                return 0;\n            }\n\n            aeron_resolution_header_ipv4_t *hdr_ipv4 = (aeron_resolution_header_ipv4_t *)resolution_header;\n            memcpy(hdr_ipv4->addr, cache_addr->address, sizeof(hdr_ipv4->addr));\n            hdr_ipv4->name_length = (int16_t)name_length;\n            name_offset = sizeof(aeron_resolution_header_ipv4_t);\n\n            break;\n\n        case AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD:\n            entry_length = AERON_ALIGN(sizeof(aeron_resolution_header_ipv6_t) + name_length, sizeof(int64_t));\n            if (capacity < entry_length)\n            {\n                return 0;\n            }\n\n            aeron_resolution_header_ipv6_t *hdr_ipv6 = (aeron_resolution_header_ipv6_t *)resolution_header;\n\n            memcpy(hdr_ipv6->addr, cache_addr->address, sizeof(hdr_ipv6->addr));\n            hdr_ipv6->name_length = (int16_t)name_length;\n            name_offset = sizeof(aeron_resolution_header_ipv6_t);\n\n            break;\n\n        default:\n            return -1;\n    }\n\n    resolution_header->res_type = cache_addr->res_type;\n    resolution_header->udp_port = cache_addr->port;\n    resolution_header->res_flags = flags;\n\n    uint8_t *buffer = (uint8_t *)resolution_header;\n    memcpy(buffer + name_offset, name, name_length);\n\n    return (int)entry_length;\n}\n\nint aeron_driver_name_resolver_resolve(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_resolution,\n    struct sockaddr_storage *sock_addr)\n{\n    aeron_driver_name_resolver_t *driver_resolver = resolver->state;\n\n    const int8_t res_type = AF_INET6 == sock_addr->ss_family ?\n        AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD : AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD;\n\n    aeron_name_resolver_cache_entry_t *cache_entry;\n    int result = -1;\n\n    if (aeron_name_resolver_cache_lookup_by_name(\n        &driver_resolver->cache, name, strlen(name), res_type, &cache_entry) < 0)\n    {\n        if (0 == strncmp(name, driver_resolver->name, driver_resolver->name_length + 1))\n        {\n            memcpy(sock_addr, &driver_resolver->local_socket_addr, sizeof(struct sockaddr_storage));\n            result = 0;\n        }\n        else\n        {\n            result = driver_resolver->bootstrap_resolver.resolve_func(\n                &driver_resolver->bootstrap_resolver, name, uri_param_name, is_re_resolution, sock_addr);\n        }\n    }\n    else\n    {\n        result = aeron_driver_name_resolver_to_sockaddr(&cache_entry->cache_addr, sock_addr);\n    }\n\n    return result;\n}\n\nint aeron_driver_name_resolver_do_work(aeron_name_resolver_t *resolver, int64_t now_ms)\n{\n    aeron_driver_name_resolver_t *driver_resolver = resolver->state;\n    int work_count = 0;\n\n    if (now_ms > driver_resolver->work_deadline_ms)\n    {\n        work_count += aeron_driver_name_resolver_poll(driver_resolver);\n        work_count += aeron_name_resolver_cache_timeout_old_entries(\n            &driver_resolver->cache, now_ms, driver_resolver->cache_size_counter.value_addr);\n        work_count += aeron_driver_name_resolver_timeout_neighbors(driver_resolver, now_ms);\n\n        if (now_ms > driver_resolver->self_resolutions_deadline_ms)\n        {\n            work_count += aeron_driver_name_resolver_send_self_resolutions(driver_resolver, resolver, now_ms);\n\n            driver_resolver->self_resolutions_deadline_ms = now_ms + driver_resolver->self_resolution_interval_ms;\n        }\n\n        if (now_ms > driver_resolver->neighbor_resolutions_deadline_ms)\n        {\n            work_count += aeron_driver_name_resolver_send_neighbor_resolutions(driver_resolver, now_ms);\n\n            driver_resolver->neighbor_resolutions_deadline_ms =\n                now_ms + driver_resolver->neighbor_resolution_interval_ms;\n        }\n\n        driver_resolver->work_deadline_ms = now_ms + AERON_NAME_RESOLVER_DRIVER_DUTY_CYCLE_MS;\n    }\n\n    return work_count;\n}\n\nint aeron_driver_name_resolver_supplier(\n    aeron_name_resolver_t *resolver, const char *args, aeron_driver_context_t *context)\n{\n    aeron_driver_name_resolver_t *name_resolver = NULL;\n\n    resolver->state = NULL;\n    if (aeron_driver_name_resolver_init(\n        &name_resolver, context,\n        context->resolver_name,\n        context->resolver_interface,\n        context->resolver_bootstrap_neighbor) < 0)\n    {\n        return -1;\n    }\n\n    resolver->lookup_func = aeron_default_name_resolver_lookup;\n    resolver->resolve_func = aeron_driver_name_resolver_resolve;\n    resolver->do_work_func = aeron_driver_name_resolver_do_work;\n    resolver->close_func = aeron_driver_name_resolver_close;\n    resolver->state = name_resolver;\n    resolver->name = \"driver\";\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_name_resolver.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_NAME_RESOLVER_DRIVER_H\n#define AERON_NAME_RESOLVER_DRIVER_H\n\n#define AERON_NAME_RESOLVER_DRIVER_SELF_RESOLUTION_INTERVAL_MS (1 * INT64_C(1000))\n#define AERON_NAME_RESOLVER_DRIVER_NEIGHBOUR_RESOLUTION_INTERVAL_MS (2 * INT64_C(1000))\n#define AERON_NAME_RESOLVER_DRIVER_TIMEOUT_MS (10 * INT64_C(1000))\n#define AERON_COUNTER_NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID (15)\n#define AERON_COUNTER_NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID (16)\n\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"aeronmd.h\"\n#include \"aeron_name_resolver_cache.h\"\n\nint aeron_driver_name_resolver_set_resolution_header(\n    aeron_resolution_header_t *resolution_header,\n    size_t capacity,\n    uint8_t flags,\n    aeron_name_resolver_cache_addr_t *cache_addr,\n    const char *name,\n    size_t name_length);\n\nint aeron_driver_name_resolver_set_resolution_header_from_sockaddr(\n    aeron_resolution_header_t *resolution_header,\n    size_t capacity,\n    uint8_t flags,\n    struct sockaddr_storage *addr,\n    const char *name,\n    size_t name_length);\n\nint aeron_driver_name_resolver_supplier(\n    aeron_name_resolver_t *resolver, const char *args, aeron_driver_context_t *context);\n\n#endif //AERON_NAME_RESOLVER_DRIVER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_receiver.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdio.h>\n#include \"util/aeron_arrayutil.h\"\n#include \"media/aeron_receive_channel_endpoint.h\"\n#include \"aeron_driver_receiver.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\nint aeron_driver_receiver_init(\n    aeron_driver_receiver_t *receiver,\n    aeron_driver_context_t *context,\n    aeron_system_counters_t *system_counters,\n    aeron_distinct_error_log_t *error_log)\n{\n    if (context->udp_channel_transport_bindings->poller_init_func(\n        &receiver->poller, context, AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_RECEIVER) < 0)\n    {\n        return -1;\n    }\n\n    receiver->recv_buffers.vector_capacity = context->receiver_io_vector_capacity;\n    for (size_t i = 0; i < receiver->recv_buffers.vector_capacity; i++)\n    {\n        size_t offset;\n        if (aeron_alloc_aligned(\n            (void **)&receiver->recv_buffers.buffers[i],\n            &offset,\n            AERON_DRIVER_RECEIVER_MAX_UDP_PACKET_LENGTH,\n            AERON_CACHE_LINE_LENGTH) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to allocate receiver->recv_buffers\");\n            return -1;\n        }\n\n        receiver->recv_buffers.iov[i].iov_base = receiver->recv_buffers.buffers[i] + offset;\n        receiver->recv_buffers.iov[i].iov_len = AERON_DRIVER_RECEIVER_MAX_UDP_PACKET_LENGTH;\n    }\n\n    if (aeron_udp_channel_data_paths_init(\n        &receiver->data_paths,\n        context->udp_channel_outgoing_interceptor_bindings,\n        context->udp_channel_incoming_interceptor_bindings,\n        context->udp_channel_transport_bindings,\n        aeron_receive_channel_endpoint_dispatch,\n        context,\n        AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_RECEIVER) < 0)\n    {\n        return -1;\n    }\n\n    receiver->images.array = NULL;\n    receiver->images.length = 0;\n    receiver->images.capacity = 0;\n\n    receiver->pending_setups.array = NULL;\n    receiver->pending_setups.length = 0;\n    receiver->pending_setups.capacity = 0;\n\n    receiver->context = context;\n    receiver->poller_poll_func = context->udp_channel_transport_bindings->poller_poll_func;\n    receiver->recvmmsg_func = context->udp_channel_transport_bindings->recvmmsg_func;\n    receiver->error_log = error_log;\n\n    receiver->receiver_proxy.command_queue = &context->receiver_command_queue;\n    receiver->receiver_proxy.fail_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_RECEIVER_PROXY_FAILS);\n    receiver->receiver_proxy.threading_mode = context->threading_mode;\n    receiver->receiver_proxy.receiver = receiver;\n    receiver->receiver_proxy.log.on_add_endpoint = context->log.receiver_proxy_on_add_endpoint;\n    receiver->receiver_proxy.log.on_remove_endpoint = context->log.receiver_proxy_on_remove_endpoint;\n\n    receiver->errors_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_ERRORS);\n    receiver->invalid_frames_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_INVALID_PACKETS);\n    receiver->total_bytes_received_counter =  aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_BYTES_RECEIVED);\n    receiver->resolution_changes_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_RESOLUTION_CHANGES);\n\n    int64_t now_ns = context->nano_clock();\n    receiver->re_resolution_deadline_ns = now_ns + (int64_t)context->re_resolution_check_interval_ns;\n    aeron_duty_cycle_tracker_t *tracker = receiver->context->receiver_duty_cycle_tracker;\n    tracker->update(tracker->state, now_ns);\n\n    return 0;\n}\n\nstatic void aeron_driver_receiver_on_rb_command_queue(\n    int32_t msg_type_id,\n    const void *message,\n    size_t size,\n    void *clientd)\n{\n    aeron_command_base_t *cmd = (aeron_command_base_t *)message;\n    cmd->func(clientd, cmd);\n}\n\nint aeron_driver_receiver_do_work(void *clientd)\n{\n    struct mmsghdr mmsghdr[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX];\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n\n    const size_t vlen = receiver->recv_buffers.vector_capacity;\n    int64_t now_ns = receiver->context->nano_clock();\n    aeron_clock_update_cached_nano_time(receiver->context->receiver_cached_clock, now_ns);\n\n    aeron_duty_cycle_tracker_t *tracker = receiver->context->receiver_duty_cycle_tracker;\n    tracker->measure_and_update(tracker->state, now_ns);\n\n    int work_count = (int)aeron_mpsc_rb_read(\n        receiver->receiver_proxy.command_queue,\n        aeron_driver_receiver_on_rb_command_queue,\n        receiver,\n        AERON_COMMAND_DRAIN_LIMIT);\n\n    for (size_t i = 0; i < vlen; i++)\n    {\n        mmsghdr[i].msg_hdr.msg_name = &receiver->recv_buffers.addrs[i];\n        mmsghdr[i].msg_hdr.msg_namelen = sizeof(receiver->recv_buffers.addrs[i]);\n        mmsghdr[i].msg_hdr.msg_iov = &receiver->recv_buffers.iov[i];\n        mmsghdr[i].msg_hdr.msg_iovlen = 1;\n        mmsghdr[i].msg_hdr.msg_flags = 0;\n        mmsghdr[i].msg_hdr.msg_control = NULL;\n        mmsghdr[i].msg_hdr.msg_controllen = 0;\n        mmsghdr[i].msg_len = 0;\n    }\n\n    int64_t bytes_received = 0;\n    int poll_result = receiver->poller_poll_func(\n        &receiver->poller,\n        mmsghdr,\n        vlen,\n        &bytes_received,\n        receiver->data_paths.recv_func,\n        receiver->recvmmsg_func,\n        receiver);\n\n    if (poll_result < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"receiver poller_poll\");\n        aeron_driver_receiver_log_error(receiver);\n    }\n\n    work_count += (int)bytes_received;\n\n    aeron_counter_get_and_add_release(receiver->total_bytes_received_counter, bytes_received);\n\n    for (size_t i = 0, length = receiver->images.length; i < length; i++)\n    {\n        aeron_publication_image_t *image = receiver->images.array[i].image;\n\n        if (NULL != image->endpoint)\n        {\n            int send_result = aeron_publication_image_send_pending_status_message(image, now_ns);\n            if (send_result < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"receiver send SM\");\n                aeron_driver_receiver_log_error(receiver);\n            }\n            else\n            {\n                work_count += send_result;\n            }\n\n            send_result = aeron_publication_image_send_pending_loss(image);\n            if (send_result < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"receiver send NAK\");\n                aeron_driver_receiver_log_error(receiver);\n            }\n            else\n            {\n                work_count += send_result;\n            }\n\n            send_result = aeron_publication_image_initiate_rttm(image, now_ns);\n            if (send_result < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"receiver send RTTM\");\n                aeron_driver_receiver_log_error(receiver);\n            }\n            else\n            {\n                work_count += send_result;\n            }\n        }\n    }\n\n    for (int last_index = (int)receiver->pending_setups.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_driver_receiver_pending_setup_entry_t *entry = &receiver->pending_setups.array[i];\n\n        if (now_ns > (entry->time_of_status_message_ns + AERON_DRIVER_RECEIVER_PENDING_SETUP_TIMEOUT_NS))\n        {\n            if (!entry->is_periodic)\n            {\n                aeron_receive_channel_endpoint_on_remove_pending_setup(\n                    entry->endpoint, entry->session_id, entry->stream_id);\n\n                aeron_array_fast_unordered_remove(\n                    (uint8_t *)receiver->pending_setups.array,\n                    sizeof(aeron_driver_receiver_pending_setup_entry_t),\n                    (size_t)i,\n                    (size_t)last_index);\n                last_index--;\n                receiver->pending_setups.length--;\n            }\n            else if (aeron_receive_channel_endpoint_should_elicit_setup_message(entry->endpoint))\n            {\n                if (aeron_receive_channel_endpoint_send_sm(\n                    entry->endpoint,\n                    entry->destination,\n                    &entry->control_addr,\n                    entry->stream_id,\n                    entry->session_id,\n                    0,\n                    0,\n                    0,\n                    AERON_STATUS_MESSAGE_HEADER_SEND_SETUP_FLAG) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"receiver send periodic SM\");\n                    aeron_driver_receiver_log_error(receiver);\n                }\n\n                entry->time_of_status_message_ns = now_ns;\n            }\n        }\n    }\n\n    if (receiver->context->re_resolution_check_interval_ns > 0 && (receiver->re_resolution_deadline_ns - now_ns) < 0)\n    {\n        receiver->re_resolution_deadline_ns = now_ns + (int64_t)receiver->context->re_resolution_check_interval_ns;\n        aeron_udp_transport_poller_check_receive_endpoint_re_resolutions(\n            &receiver->poller, now_ns, receiver->context->conductor_proxy);\n    }\n\n    return work_count;\n}\n\nvoid aeron_driver_receiver_on_close(void *clientd)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n\n    for (size_t i = 0; i < receiver->recv_buffers.vector_capacity; i++)\n    {\n        aeron_free(receiver->recv_buffers.buffers[i]);\n    }\n\n    aeron_free(receiver->images.array);\n    aeron_free(receiver->pending_setups.array);\n    aeron_udp_channel_data_paths_delete(&receiver->data_paths);\n\n    receiver->context->udp_channel_transport_bindings->poller_close_func(&receiver->poller);\n}\n\nvoid aeron_driver_receiver_on_add_endpoint(void *clientd, void *command)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_base_t *cmd = (aeron_command_base_t *)command;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)cmd->item;\n\n    if (aeron_receive_channel_endpoint_add_poll_transports(endpoint, &receiver->poller) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"receiver on_add_endpoint\");\n        aeron_driver_receiver_log_error(receiver);\n        return;\n    }\n\n    aeron_receive_channel_endpoint_add_pending_setup(endpoint, receiver, 0, 0);\n}\n\nvoid aeron_driver_receiver_on_remove_endpoint(void *clientd, void *command)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_base_t *cmd = (aeron_command_base_t *)command;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)cmd->item;\n\n    if (aeron_receive_channel_endpoint_remove_poll_transports(endpoint, &receiver->poller) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"receiver on_remove_endpoint\");\n        aeron_driver_receiver_log_error(receiver);\n    }\n\n    for (int last_index = (int)receiver->pending_setups.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_driver_receiver_pending_setup_entry_t *entry = &receiver->pending_setups.array[i];\n\n        if (entry->endpoint == endpoint)\n        {\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)receiver->pending_setups.array,\n                sizeof(aeron_driver_receiver_pending_setup_entry_t),\n                (size_t)i,\n                (size_t)last_index);\n            last_index--;\n            receiver->pending_setups.length--;\n        }\n    }\n\n    for (size_t i = 0, len = receiver->images.length; i < len; i++)\n    {\n        aeron_publication_image_t *image = receiver->images.array[i].image;\n        if (endpoint == image->endpoint)\n        {\n            aeron_publication_image_disconnect_endpoint(image);\n        }\n    }\n\n    aeron_driver_conductor_proxy_on_receive_endpoint_removed(receiver->context->conductor_proxy, endpoint);\n}\n\nvoid aeron_driver_receiver_on_add_subscription(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_subscription_t *cmd = (aeron_command_subscription_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)cmd->endpoint;\n\n    if (aeron_receive_channel_endpoint_on_add_subscription(endpoint, cmd->stream_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"receiver on_add_subscription\");\n        aeron_driver_receiver_log_error(receiver);\n    }\n}\n\nvoid aeron_driver_receiver_on_remove_subscription(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_subscription_t *cmd = (aeron_command_subscription_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)cmd->endpoint;\n\n    if (aeron_receive_channel_endpoint_on_remove_subscription(endpoint, cmd->stream_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"receiver on_remove_subscription\");\n        aeron_driver_receiver_log_error(receiver);\n    }\n}\n\nvoid aeron_driver_receiver_on_add_subscription_by_session(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_subscription_t *cmd = (aeron_command_subscription_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)cmd->endpoint;\n\n    if (aeron_receive_channel_endpoint_on_add_subscription_by_session(endpoint, cmd->stream_id, cmd->session_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_driver_receiver_log_error(receiver);\n        return;\n    }\n\n    if (endpoint->conductor_fields.udp_channel->has_explicit_control)\n    {\n        if (aeron_receive_channel_endpoint_elicit_setup(endpoint, cmd->stream_id, cmd->session_id) < 0)\n        {\n            AERON_APPEND_ERR(\"streamId=%\" PRId32 \" sessionId=%\" PRId32, cmd->stream_id, cmd->session_id);\n            aeron_driver_receiver_log_error(receiver);\n            return;\n        }\n    }\n}\n\nvoid aeron_driver_receiver_on_request_setup(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_subscription_t *cmd = (aeron_command_subscription_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)cmd->endpoint;\n\n    if (endpoint->conductor_fields.udp_channel->has_explicit_control)\n    {\n        if (aeron_receive_channel_endpoint_elicit_setup(endpoint, cmd->stream_id, cmd->session_id) < 0)\n        {\n            AERON_APPEND_ERR(\"streamId=%\" PRId32 \" sessionId=%\" PRId32, cmd->stream_id, cmd->session_id);\n            aeron_driver_receiver_log_error(receiver);\n            return;\n        }\n    }\n}\n\nvoid aeron_driver_receiver_on_remove_subscription_by_session(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_subscription_t *cmd = (aeron_command_subscription_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)cmd->endpoint;\n\n    if (aeron_receive_channel_endpoint_on_remove_subscription_by_session(endpoint, cmd->stream_id, cmd->session_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"receiver on_remove_subscription\");\n        aeron_driver_receiver_log_error(receiver);\n    }\n}\n\nvoid aeron_driver_receiver_on_add_destination(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_add_rcv_destination_t *command = (aeron_command_add_rcv_destination_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)command->endpoint;\n    aeron_receive_destination_t *destination = (aeron_receive_destination_t *)command->destination;\n\n    if (aeron_receive_channel_endpoint_add_destination(endpoint, destination) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"on_add_destination, add to endpoint\");\n        aeron_driver_receiver_log_error(receiver);\n        return;\n    }\n\n    if (aeron_udp_channel_interceptors_transport_notifications(\n        destination->data_paths,\n        &destination->transport,\n        destination->conductor_fields.udp_channel,\n        &endpoint->dispatcher,\n        AERON_UDP_CHANNEL_INTERCEPTOR_ADD_NOTIFICATION) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"on_add_destination, interceptors transport notifications\");\n        aeron_driver_receiver_log_error(receiver);\n    }\n\n    if (endpoint->transport_bindings->poller_add_func(&receiver->poller, &destination->transport) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"on_add_destination, add to poller\");\n        aeron_driver_receiver_log_error(receiver);\n        aeron_receive_channel_endpoint_remove_destination(endpoint, destination->conductor_fields.udp_channel, NULL);\n        return;\n    }\n\n    if (destination->conductor_fields.udp_channel->has_explicit_control)\n    {\n        if (aeron_receive_channel_endpoint_add_pending_setup_destination(endpoint, receiver, destination, 0, 0) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"on_add_destination, pending_setup\");\n            aeron_driver_receiver_log_error(receiver);\n\n            aeron_receive_channel_endpoint_remove_destination(endpoint, destination->conductor_fields.udp_channel, NULL);\n            endpoint->transport_bindings->poller_remove_func(&receiver->poller, &destination->transport);\n            endpoint->transport_bindings->close_func(&destination->transport);\n            return;\n        }\n    }\n\n    for (size_t i = 0, len = receiver->images.length; i < len; i++)\n    {\n        aeron_publication_image_t *image = receiver->images.array[i].image;\n        if (endpoint == image->endpoint)\n        {\n            aeron_publication_image_add_destination(image, destination);\n        }\n    }\n}\n\nvoid aeron_driver_receiver_on_remove_destination(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_remove_rcv_destination_t *command = (aeron_command_remove_rcv_destination_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)command->endpoint;\n    aeron_udp_channel_t *channel = (aeron_udp_channel_t *)command->channel;\n    aeron_receive_destination_t *destination = NULL;\n\n    if (0 < aeron_receive_channel_endpoint_remove_destination(endpoint, channel, &destination) && NULL != destination)\n    {\n        if (aeron_udp_channel_interceptors_transport_notifications(\n            destination->data_paths,\n            &destination->transport,\n            destination->conductor_fields.udp_channel,\n            &endpoint->dispatcher,\n            AERON_UDP_CHANNEL_INTERCEPTOR_REMOVE_NOTIFICATION) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"on_add_destination, interceptors transport notifications\");\n            aeron_driver_receiver_log_error(receiver);\n        }\n\n        endpoint->transport_bindings->poller_remove_func(&receiver->poller, &destination->transport);\n\n        for (size_t i = 0, len = receiver->images.length; i < len; i++)\n        {\n            aeron_publication_image_t *image = receiver->images.array[i].image;\n            if (endpoint == image->endpoint)\n            {\n                aeron_publication_image_remove_destination(image, channel);\n            }\n        }\n\n        aeron_driver_conductor_proxy_on_delete_receive_destination(\n            receiver->context->conductor_proxy, endpoint, destination, channel);\n    }\n}\n\nvoid aeron_driver_receiver_disconnect_inactive_image(\n    aeron_driver_receiver_t *receiver,\n    const aeron_receive_channel_endpoint_t *endpoint,\n    const int32_t stream_id,\n    const int32_t session_id)\n{\n    for (size_t i = 0, length = receiver->images.length; i < length; i++)\n    {\n        aeron_publication_image_t *image = receiver->images.array[i].image;\n        if (endpoint == image->endpoint && stream_id == image->stream_id && session_id == image->session_id)\n        {\n            aeron_publication_image_stop_status_messages_if_not_active(image);\n        }\n    }\n}\n\nvoid aeron_driver_receiver_on_add_publication_image(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_publication_image_t *cmd = (aeron_command_publication_image_t *)item;\n    aeron_publication_image_t *image = cmd->image;\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, receiver->images, aeron_driver_receiver_image_entry_t)\n\n    if (ensure_capacity_result < 0 ||\n        aeron_receive_channel_endpoint_on_add_publication_image(image->endpoint, image) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"receiver on_add_publication_image\");\n        aeron_driver_receiver_log_error(receiver);\n    }\n    else\n    {\n        aeron_driver_receiver_disconnect_inactive_image(receiver, image->endpoint, image->stream_id, image->session_id);\n        receiver->images.array[receiver->images.length++].image = cmd->image;\n    }\n}\n\nvoid aeron_driver_receiver_on_remove_publication_image(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)clientd;\n    aeron_command_publication_image_t *cmd = (aeron_command_publication_image_t *)item;\n    aeron_publication_image_t *image = (aeron_publication_image_t *)cmd->image;\n\n    if (NULL != image->endpoint &&\n        aeron_receive_channel_endpoint_on_remove_publication_image(image->endpoint, image) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"receiver on_remove_publication_image\");\n        aeron_driver_receiver_log_error(receiver);\n    }\n\n    for (size_t i = 0, size = receiver->images.length, last_index = size - 1; i < size; i++)\n    {\n        if (image == receiver->images.array[i].image)\n        {\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)receiver->images.array, sizeof(aeron_driver_receiver_image_entry_t), i, last_index);\n            receiver->images.length--;\n            break;\n        }\n    }\n\n    aeron_driver_conductor_proxy_on_release_resource(\n        receiver->context->conductor_proxy, image, AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_PUBLICATION_IMAGE);\n}\n\nvoid aeron_driver_receiver_on_remove_matching_state(void *clientd, void *item)\n{\n    aeron_command_on_remove_matching_state_t *cmd = (aeron_command_on_remove_matching_state_t *)item;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)cmd->endpoint;\n\n    aeron_receive_channel_endpoint_on_remove_matching_state(endpoint, cmd->session_id, cmd->stream_id, cmd->state);\n}\n\nvoid aeron_driver_receiver_on_resolution_change(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = clientd;\n    aeron_command_receiver_resolution_change_t *cmd = item;\n    aeron_receive_channel_endpoint_t *endpoint = cmd->endpoint;\n    aeron_receive_destination_t *destination = cmd->destination;\n\n    for (size_t i = 0; i < receiver->pending_setups.length; i++)\n    {\n        aeron_driver_receiver_pending_setup_entry_t *pending_setup = &receiver->pending_setups.array[i];\n\n        if (pending_setup->endpoint == endpoint &&\n            pending_setup->destination == destination &&\n            pending_setup->is_periodic)\n        {\n            memcpy(&pending_setup->control_addr, &cmd->new_addr, sizeof(pending_setup->control_addr));\n            aeron_counter_get_and_add_release(receiver->resolution_changes_counter, 1);\n        }\n    }\n\n    aeron_receive_channel_endpoint_update_control_address(endpoint, destination, &cmd->new_addr);\n}\n\nvoid aeron_driver_receiver_on_invalidate_image(void *clientd, void *item)\n{\n    aeron_driver_receiver_t *receiver = clientd;\n    aeron_command_receiver_invalidate_image_t *cmd = item;\n    const int64_t correlation_id = cmd->image_correlation_id;\n    const int32_t reason_length = cmd->reason_length;\n    const char *reason = (const char *)cmd->reason_text;\n\n    for (size_t i = 0, size = receiver->images.length; i < size; i++)\n    {\n        aeron_publication_image_t *image = receiver->images.array[i].image;\n        // TODO: Should we pass the pointer to the image here instead of the correlation_id.\n        if (correlation_id == aeron_publication_image_registration_id(image))\n        {\n            aeron_publication_image_invalidate(image, reason_length, reason);\n            break;\n        }\n    }\n}\n\nint aeron_driver_receiver_add_pending_setup(\n    aeron_driver_receiver_t *receiver,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    int32_t session_id,\n    int32_t stream_id,\n    struct sockaddr_storage *control_addr)\n{\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(\n        ensure_capacity_result, receiver->pending_setups, aeron_driver_receiver_pending_setup_entry_t)\n\n    if (ensure_capacity_result < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"receiver add_pending_setup\");\n        return ensure_capacity_result;\n    }\n\n    aeron_driver_receiver_pending_setup_entry_t *entry =\n        &receiver->pending_setups.array[receiver->pending_setups.length++];\n\n    entry->endpoint = endpoint;\n    entry->destination = destination;\n    entry->session_id = session_id;\n    entry->stream_id = stream_id;\n    entry->time_of_status_message_ns = aeron_clock_cached_nano_time(receiver->context->receiver_cached_clock);\n    entry->is_periodic = false;\n    if (NULL != control_addr)\n    {\n        memcpy(&entry->control_addr, control_addr, sizeof(entry->control_addr));\n        entry->is_periodic = true;\n    }\n\n    return ensure_capacity_result;\n}\n\nextern void aeron_driver_receiver_log_error(aeron_driver_receiver_t *receiver);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_receiver.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_RECEIVER_H\n#define AERON_DRIVER_RECEIVER_H\n\n#include \"media/aeron_udp_transport_poller.h\"\n#include \"concurrent/aeron_distinct_error_log.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_driver_receiver_proxy.h\"\n#include \"aeron_system_counters.h\"\n#include \"media/aeron_udp_channel.h\"\n\n#define AERON_DRIVER_RECEIVER_PENDING_SETUP_TIMEOUT_NS (1000 * 1000 * 1000LL)\n\ntypedef struct aeron_driver_receiver_image_entry_stct\n{\n    aeron_publication_image_t *image;\n}\naeron_driver_receiver_image_entry_t;\n\ntypedef struct aeron_driver_receiver_pending_setup_entry_stct\n{\n    bool is_periodic;\n    int32_t session_id;\n    int32_t stream_id;\n    aeron_receive_channel_endpoint_t *endpoint;\n    aeron_receive_destination_t *destination;\n    int64_t time_of_status_message_ns;\n    struct sockaddr_storage control_addr;\n}\naeron_driver_receiver_pending_setup_entry_t;\n\ntypedef struct aeron_driver_receiver_stct\n{\n    aeron_driver_receiver_proxy_t receiver_proxy;\n    aeron_udp_transport_poller_t poller;\n    struct aeron_driver_receiver_buffers_stct\n    {\n        size_t vector_capacity;\n        uint8_t *buffers[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX];\n        struct iovec iov[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX];\n        struct sockaddr_storage addrs[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX];\n    }\n    recv_buffers;\n\n    struct aeron_driver_receiver_images_stct\n    {\n        aeron_driver_receiver_image_entry_t *array;\n        size_t length;\n        size_t capacity;\n    }\n    images;\n\n    struct aeron_driver_receiver_pending_setups_stct\n    {\n        aeron_driver_receiver_pending_setup_entry_t *array;\n        size_t length;\n        size_t capacity;\n    }\n    pending_setups;\n\n    aeron_udp_channel_data_paths_t data_paths;\n\n    aeron_driver_context_t *context;\n    aeron_udp_transport_poller_poll_func_t poller_poll_func;\n    aeron_udp_channel_transport_recvmmsg_func_t recvmmsg_func;\n    aeron_distinct_error_log_t *error_log;\n    int64_t re_resolution_deadline_ns;\n\n    int64_t *errors_counter;\n    int64_t *invalid_frames_counter;\n    int64_t *total_bytes_received_counter;\n    int64_t *resolution_changes_counter;\n}\naeron_driver_receiver_t;\n\ninline void aeron_driver_receiver_log_error(aeron_driver_receiver_t *receiver)\n{\n    aeron_distinct_error_log_record(receiver->error_log, aeron_errcode(), aeron_errmsg());\n    aeron_counter_increment(receiver->errors_counter);\n    aeron_err_clear();\n}\n\nint aeron_driver_receiver_init(\n    aeron_driver_receiver_t *receiver,\n    aeron_driver_context_t *context,\n    aeron_system_counters_t *system_counters,\n    aeron_distinct_error_log_t *error_log);\n\nint aeron_driver_receiver_do_work(void *clientd);\nvoid aeron_driver_receiver_on_close(void *clientd);\n\nvoid aeron_driver_receiver_on_add_endpoint(void *clientd, void *item);\nvoid aeron_driver_receiver_on_remove_endpoint(void *clientd, void *item);\n\nvoid aeron_driver_receiver_on_add_subscription(void *clientd, void *item);\nvoid aeron_driver_receiver_on_remove_subscription(void *clientd, void *item);\nvoid aeron_driver_receiver_on_add_subscription_by_session(void *clientd, void *item);\nvoid aeron_driver_receiver_on_request_setup(void *clientd, void *item);\nvoid aeron_driver_receiver_on_remove_subscription_by_session(void *clientd, void *item);\n\nvoid aeron_driver_receiver_on_add_destination(void *clientd, void *item);\nvoid aeron_driver_receiver_on_remove_destination(void *clientd, void *item);\n\nvoid aeron_driver_receiver_on_add_publication_image(void *clientd, void *item);\nvoid aeron_driver_receiver_on_remove_publication_image(void *clientd, void *item);\n\nvoid aeron_driver_receiver_on_remove_matching_state(void *clientd, void *item);\n\nvoid aeron_driver_receiver_on_resolution_change(void *clientd, void *item);\n\nvoid aeron_driver_receiver_on_invalidate_image(void *clientd, void *item);\n\nint aeron_driver_receiver_add_pending_setup(\n    aeron_driver_receiver_t *receiver,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    int32_t session_id,\n    int32_t stream_id,\n    struct sockaddr_storage *control_addr);\n\n#endif //AERON_DRIVER_RECEIVER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_receiver_proxy.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"aeron_driver_receiver_proxy.h\"\n#include \"aeron_driver_receiver.h\"\n#include \"aeron_alloc.h\"\n\nvoid aeron_driver_receiver_proxy_offer(aeron_driver_receiver_proxy_t *receiver_proxy, void *cmd, size_t length)\n{\n    aeron_rb_write_result_t result;\n    while (AERON_RB_FULL == (result = aeron_mpsc_rb_write(receiver_proxy->command_queue, 1, cmd, length)))\n    {\n        aeron_counter_increment_release(receiver_proxy->fail_counter);\n        sched_yield();\n    }\n\n    if (AERON_RB_ERROR == result)\n    {\n        aeron_distinct_error_log_record(\n            receiver_proxy->receiver->error_log, EINVAL, \"Error writing to receiver proxy ring buffer\");\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_add_endpoint(\n    aeron_driver_receiver_proxy_t *receiver_proxy, aeron_receive_channel_endpoint_t *endpoint)\n{\n    receiver_proxy->log.on_add_endpoint(endpoint->conductor_fields.udp_channel);\n\n    aeron_command_base_t cmd =\n        {\n            .func = aeron_driver_receiver_on_add_endpoint,\n            .item = endpoint\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_add_endpoint(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_remove_endpoint(\n    aeron_driver_receiver_proxy_t *receiver_proxy, aeron_receive_channel_endpoint_t *endpoint)\n{\n    receiver_proxy->log.on_remove_endpoint(endpoint->conductor_fields.udp_channel);\n\n    aeron_command_base_t cmd =\n        {\n            .func = aeron_driver_receiver_on_remove_endpoint,\n            .item = endpoint\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_remove_endpoint(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_add_subscription(\n    aeron_driver_receiver_proxy_t *receiver_proxy, aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id)\n{\n    aeron_command_subscription_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_add_subscription, .item = NULL },\n            .endpoint = endpoint,\n            .stream_id = stream_id,\n            .session_id = 0 // ignored\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_add_subscription(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_remove_subscription(\n    aeron_driver_receiver_proxy_t *receiver_proxy, aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id)\n{\n    aeron_command_subscription_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_remove_subscription, .item = NULL },\n            .endpoint = endpoint,\n            .stream_id = stream_id,\n            .session_id = 0 // ignored.\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_remove_subscription(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_add_subscription_by_session(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    aeron_command_subscription_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_add_subscription_by_session, .item = NULL },\n            .endpoint = endpoint,\n            .stream_id = stream_id,\n            .session_id = session_id\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_add_subscription_by_session(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_request_setup(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    aeron_command_subscription_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_request_setup, .item = NULL },\n            .endpoint = endpoint,\n            .stream_id = stream_id,\n            .session_id = session_id\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_request_setup(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_remove_subscription_by_session(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    aeron_command_subscription_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_remove_subscription_by_session, .item = NULL },\n            .endpoint = endpoint,\n            .stream_id = stream_id,\n            .session_id = session_id\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_remove_subscription_by_session(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_add_destination(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination)\n{\n    aeron_command_add_rcv_destination_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_add_destination, .item = NULL },\n            .endpoint = endpoint,\n            .destination = destination\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_add_destination(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_remove_destination(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel)\n{\n    aeron_command_remove_rcv_destination_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_remove_destination, .item = NULL },\n            .endpoint = endpoint,\n            .channel = channel\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_remove_destination(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_add_publication_image(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_publication_image_t *image)\n{\n    aeron_command_publication_image_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_add_publication_image, .item = NULL },\n            .image = image\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_add_publication_image(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_remove_publication_image(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_publication_image_t *image)\n{\n    aeron_command_publication_image_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_remove_publication_image, .item = NULL },\n            .image = image\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_remove_publication_image(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_remove_cool_down(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    aeron_command_on_remove_matching_state_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_remove_matching_state, .item = NULL },\n            .endpoint = endpoint,\n            .session_id = session_id,\n            .stream_id = stream_id,\n            .state = AERON_DATA_PACKET_DISPATCHER_IMAGE_COOL_DOWN\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_remove_matching_state(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_remove_init_in_progress(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    aeron_command_on_remove_matching_state_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_remove_matching_state, .item = NULL },\n            .endpoint = endpoint,\n            .session_id = session_id,\n            .stream_id = stream_id,\n            .state = AERON_DATA_PACKET_DISPATCHER_IMAGE_INIT_IN_PROGRESS\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_remove_matching_state(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_resolution_change(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    const char *endpoint_name,\n    void *endpoint,\n    void *destination,\n    struct sockaddr_storage *new_addr)\n{\n    aeron_command_receiver_resolution_change_t cmd =\n        {\n            .base = { .func = aeron_driver_receiver_on_resolution_change, .item = NULL },\n            .endpoint_name = endpoint_name,\n            .endpoint = endpoint,\n            .destination = destination\n        };\n    memcpy(&cmd.new_addr, new_addr, sizeof(cmd.new_addr));\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_resolution_change(receiver_proxy->receiver, &cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_receiver_proxy_on_invalidate_image(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    int64_t image_correlation_id,\n    int64_t position,\n    int32_t reason_length,\n    const char *reason)\n{\n    reason_length = reason_length <= AERON_ERROR_MAX_TEXT_LENGTH ? reason_length : AERON_ERROR_MAX_TEXT_LENGTH;\n    uint8_t message_buffer[sizeof(aeron_command_base_t) + AERON_ERROR_MAX_TEXT_LENGTH + 1];\n    aeron_command_receiver_invalidate_image_t *cmd = (aeron_command_receiver_invalidate_image_t *)message_buffer;\n\n    cmd->base.func = aeron_driver_receiver_on_invalidate_image;\n    cmd->base.item = NULL;\n    cmd->image_correlation_id = image_correlation_id;\n    cmd->position = position;\n    cmd->reason_length = reason_length;\n    memcpy(cmd->reason_text, reason, reason_length);\n    aeron_str_null_terminate(cmd->reason_text, reason_length);\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(receiver_proxy->threading_mode))\n    {\n        aeron_driver_receiver_on_invalidate_image(receiver_proxy->receiver, cmd);\n    }\n    else\n    {\n        aeron_driver_receiver_proxy_offer(receiver_proxy, cmd, sizeof(*cmd) + reason_length);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_receiver_proxy.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_RECEIVER_PROXY_H\n#define AERON_DRIVER_RECEIVER_PROXY_H\n\n#include \"aeron_driver_context.h\"\n\ntypedef struct aeron_driver_receiver_stct aeron_driver_receiver_t;\ntypedef struct aeron_receive_channel_endpoint_stct aeron_receive_channel_endpoint_t;\ntypedef struct aeron_receive_destination_stct aeron_receive_destination_t;\ntypedef struct aeron_publication_image_stct aeron_publication_image_t;\n\ntypedef struct aeron_driver_receiver_proxy_stct\n{\n    aeron_driver_receiver_t *receiver;\n    aeron_threading_mode_t threading_mode;\n    struct\n    {\n        aeron_on_endpoint_change_func_t on_add_endpoint;\n        aeron_on_endpoint_change_func_t on_remove_endpoint;\n    } log;\n    aeron_mpsc_rb_t *command_queue;\n    int64_t *fail_counter;\n}\naeron_driver_receiver_proxy_t;\n\nvoid aeron_driver_receiver_proxy_on_add_endpoint(\n    aeron_driver_receiver_proxy_t *receiver_proxy, aeron_receive_channel_endpoint_t *endpoint);\nvoid aeron_driver_receiver_proxy_on_remove_endpoint(\n    aeron_driver_receiver_proxy_t *receiver_proxy, aeron_receive_channel_endpoint_t *endpoint);\n\ntypedef struct aeron_command_subscription_stct\n{\n    aeron_command_base_t base;\n    void *endpoint;\n    int32_t stream_id;\n    int32_t session_id;\n}\naeron_command_subscription_t;\n\nvoid aeron_driver_receiver_proxy_on_add_subscription(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id);\n\nvoid aeron_driver_receiver_proxy_on_remove_subscription(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id);\n\nvoid aeron_driver_receiver_proxy_on_add_subscription_by_session(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id);\n\nvoid aeron_driver_receiver_proxy_on_request_setup(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id);\n\nvoid aeron_driver_receiver_proxy_on_remove_subscription_by_session(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id);\n\ntypedef struct aeron_command_add_rcv_destination_stct\n{\n    aeron_command_base_t base;\n    void *endpoint;\n    void *destination;\n}\naeron_command_add_rcv_destination_t;\n\nvoid aeron_driver_receiver_proxy_on_add_destination(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination);\n\ntypedef struct aeron_command_remove_rcv_destination_stct\n{\n    aeron_command_base_t base;\n    void *endpoint;\n    void *channel;\n}\naeron_command_remove_rcv_destination_t;\n\nvoid aeron_driver_receiver_proxy_on_remove_destination(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel);\n\ntypedef struct aeron_command_publication_image_stct\n{\n    aeron_command_base_t base;\n    void *image;\n}\naeron_command_publication_image_t;\n\ntypedef struct aeron_command_on_remove_matching_state_stct\n{\n    aeron_command_base_t base;\n    void *endpoint;\n    int32_t session_id;\n    int32_t stream_id;\n    uint32_t state;\n}\naeron_command_on_remove_matching_state_t;\n\ntypedef struct aeron_command_receiver_resolution_change_stct\n{\n    aeron_command_base_t base;\n    const char *endpoint_name;\n    void *endpoint;\n    void *destination;\n    struct sockaddr_storage new_addr;\n}\naeron_command_receiver_resolution_change_t;\n\ntypedef struct aeron_command_receiver_invalidate_image_stct\n{\n    aeron_command_base_t base;\n    int64_t image_correlation_id;\n    int64_t position;\n    int32_t reason_length;\n    uint8_t reason_text[1];\n}\naeron_command_receiver_invalidate_image_t;\n\n\nvoid aeron_driver_receiver_proxy_on_add_publication_image(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_publication_image_t *image);\nvoid aeron_driver_receiver_proxy_on_remove_publication_image(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_publication_image_t *image);\nvoid aeron_driver_receiver_proxy_on_remove_cool_down(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t session_id,\n    int32_t stream_id);\nvoid aeron_driver_receiver_proxy_on_remove_init_in_progress(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t session_id,\n    int32_t stream_id);\nvoid aeron_driver_receiver_proxy_on_invalidate_image(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    int64_t image_correlation_id,\n    int64_t position,\n    int32_t reason_length,\n    const char *reason);\nvoid aeron_driver_receiver_proxy_on_resolution_change(\n    aeron_driver_receiver_proxy_t *receiver_proxy,\n    const char *endpoint_name,\n    void *endpoint,\n    void *destination,\n    struct sockaddr_storage *new_addr);\n\n\n#endif //AERON_DRIVER_RECEIVER_PROXY_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_sender.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <aeron_socket.h>\n#include <stdio.h>\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\n#include \"util/aeron_arrayutil.h\"\n#include \"media/aeron_send_channel_endpoint.h\"\n#include \"aeron_driver_sender.h\"\n\nint aeron_driver_sender_init(\n    aeron_driver_sender_t *sender,\n    aeron_driver_context_t *context,\n    aeron_system_counters_t *system_counters,\n    aeron_distinct_error_log_t *error_log)\n{\n    if (context->udp_channel_transport_bindings->poller_init_func(\n        &sender->poller, context, AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_SENDER) < 0)\n    {\n        return -1;\n    }\n\n    sender->recv_buffers.vector_capacity = context->sender_io_vector_capacity;\n    for (size_t i = 0; i < sender->recv_buffers.vector_capacity; i++)\n    {\n        size_t offset;\n        if (aeron_alloc_aligned(\n            (void **)&sender->recv_buffers.buffers[i],\n            &offset,\n            context->mtu_length,\n            AERON_CACHE_LINE_LENGTH) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to allocate sender->recv_buffers\");\n            return -1;\n        }\n\n        sender->recv_buffers.iov[i].iov_base = sender->recv_buffers.buffers[i] + offset;\n        sender->recv_buffers.iov[i].iov_len = (uint32_t)context->mtu_length;\n    }\n\n    if (aeron_udp_channel_data_paths_init(\n        &sender->data_paths,\n        context->udp_channel_outgoing_interceptor_bindings,\n        context->udp_channel_incoming_interceptor_bindings,\n        context->udp_channel_transport_bindings,\n        aeron_send_channel_endpoint_dispatch,\n        context,\n        AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_SENDER) < 0)\n    {\n        return -1;\n    }\n\n    sender->context = context;\n    sender->poller_poll_func = context->udp_channel_transport_bindings->poller_poll_func;\n    sender->recvmmsg_func = context->udp_channel_transport_bindings->recvmmsg_func;\n    sender->error_log = error_log;\n    sender->sender_proxy.sender = sender;\n    sender->sender_proxy.command_queue = &context->sender_command_queue;\n    sender->sender_proxy.fail_counter =\n        aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_SENDER_PROXY_FAILS);\n    sender->sender_proxy.threading_mode = context->threading_mode;\n    sender->sender_proxy.log.on_add_endpoint = context->log.sender_proxy_on_add_endpoint;\n    sender->sender_proxy.log.on_remove_endpoint = context->log.sender_proxy_on_remove_endpoint;\n\n    sender->network_publications.array = NULL;\n    sender->network_publications.length = 0;\n    sender->network_publications.capacity = 0;\n\n    sender->round_robin_index = 0;\n    sender->duty_cycle_counter = 0;\n    sender->duty_cycle_ratio = context->send_to_sm_poll_ratio;\n    sender->status_message_read_timeout_ns = (int64_t)(context->status_message_timeout_ns / 2);\n    sender->control_poll_timeout_ns = 0;\n    sender->total_bytes_sent_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_BYTES_SENT);\n    sender->errors_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_ERRORS);\n    sender->invalid_frames_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_INVALID_PACKETS);\n    sender->status_messages_received_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_STATUS_MESSAGES_RECEIVED);\n    sender->nak_messages_received_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_NAK_MESSAGES_RECEIVED);\n    sender->error_messages_received_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED);\n    sender->resolution_changes_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_RESOLUTION_CHANGES);\n    sender->short_sends_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_SHORT_SENDS);\n\n    int64_t now_ns = context->nano_clock();\n    sender->re_resolution_deadline_ns = now_ns + (int64_t)context->re_resolution_check_interval_ns;\n    aeron_duty_cycle_tracker_t *dutyCycleTracker = sender->context->sender_duty_cycle_tracker;\n    dutyCycleTracker->update(dutyCycleTracker->state, now_ns);\n\n    return 0;\n}\n\nstatic void aeron_driver_sender_on_rb_command_queue(\n    int32_t msg_type_id,\n    const void *message,\n    size_t size,\n    void *clientd)\n{\n    aeron_command_base_t *cmd = (aeron_command_base_t *)message;\n    cmd->func(clientd, cmd);\n}\n\nint aeron_driver_sender_do_work(void *clientd)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)clientd;\n\n    int64_t now_ns = sender->context->nano_clock();\n    aeron_clock_update_cached_nano_time(sender->context->sender_cached_clock, now_ns);\n\n    aeron_duty_cycle_tracker_t *tracker = sender->context->sender_duty_cycle_tracker;\n    tracker->measure_and_update(tracker->state, now_ns);\n\n    int work_count = (int)aeron_mpsc_rb_read(\n        sender->sender_proxy.command_queue, aeron_driver_sender_on_rb_command_queue, sender, AERON_COMMAND_DRAIN_LIMIT);\n\n    int64_t bytes_received = 0;\n    int64_t short_sends_before = aeron_counter_get_plain(sender->short_sends_counter);\n    int bytes_sent = aeron_driver_sender_do_send(sender, now_ns);\n\n    if (0 == bytes_sent ||\n        ++sender->duty_cycle_counter >= sender->duty_cycle_ratio ||\n        now_ns > sender->control_poll_timeout_ns ||\n        short_sends_before < aeron_counter_get_plain(sender->short_sends_counter))\n    {\n        struct mmsghdr mmsghdr[1];\n        mmsghdr[0].msg_hdr.msg_name = &sender->recv_buffers.addrs[0];\n        mmsghdr[0].msg_hdr.msg_namelen = sizeof(sender->recv_buffers.addrs[0]);\n        mmsghdr[0].msg_hdr.msg_iov = &sender->recv_buffers.iov[0];\n        mmsghdr[0].msg_hdr.msg_iovlen = 1;\n        mmsghdr[0].msg_hdr.msg_flags = 0;\n        mmsghdr[0].msg_hdr.msg_control = NULL;\n        mmsghdr[0].msg_hdr.msg_controllen = 0;\n        mmsghdr[0].msg_len = 0;\n\n        int poll_result = sender->poller_poll_func(\n            &sender->poller,\n            mmsghdr,\n            1,\n            &bytes_received,\n            sender->data_paths.recv_func,\n            sender->recvmmsg_func,\n            sender);\n\n        if (poll_result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"sender poller_poll\");\n            aeron_driver_sender_log_error(sender);\n        }\n        work_count += (poll_result < 0 ? 0 : poll_result);\n\n        sender->duty_cycle_counter = 0;\n        sender->control_poll_timeout_ns = now_ns + sender->status_message_read_timeout_ns;\n    }\n\n    if (sender->context->re_resolution_check_interval_ns > 0 && (sender->re_resolution_deadline_ns - now_ns) < 0)\n    {\n        sender->re_resolution_deadline_ns = (int64_t)(now_ns + sender->context->re_resolution_check_interval_ns);\n        aeron_udp_transport_poller_check_send_endpoint_re_resolutions(\n                &sender->poller, now_ns, sender->context->conductor_proxy);\n    }\n\n    return work_count + bytes_sent + (int)bytes_received;\n}\n\nvoid aeron_driver_sender_on_close(void *clientd)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)clientd;\n\n    for (size_t i = 0; i < sender->recv_buffers.vector_capacity; i++)\n    {\n        aeron_free(sender->recv_buffers.buffers[i]);\n    }\n\n    aeron_udp_channel_data_paths_delete(&sender->data_paths);\n\n    sender->context->udp_channel_transport_bindings->poller_close_func(&sender->poller);\n    aeron_free(sender->network_publications.array);\n}\n\nvoid aeron_driver_sender_on_add_endpoint(void *clientd, void *command)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)clientd;\n    aeron_command_base_t *cmd = (aeron_command_base_t *)command;\n    aeron_send_channel_endpoint_t *endpoint = (aeron_send_channel_endpoint_t *)cmd->item;\n\n    if (aeron_udp_channel_interceptors_transport_notifications(\n        endpoint->transport.data_paths,\n        &endpoint->transport,\n        endpoint->conductor_fields.udp_channel,\n        NULL,\n        AERON_UDP_CHANNEL_INTERCEPTOR_ADD_NOTIFICATION) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_add_endpoint interceptors transport notifications\");\n        aeron_driver_sender_log_error(sender);\n    }\n\n    if (sender->context->udp_channel_transport_bindings->poller_add_func(&sender->poller, &endpoint->transport) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_add_endpoint\");\n        aeron_driver_sender_log_error(sender);\n    }\n}\n\nvoid aeron_driver_sender_on_remove_endpoint(void *clientd, void *command)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)clientd;\n    aeron_command_base_t *cmd = (aeron_command_base_t *)command;\n    aeron_send_channel_endpoint_t *endpoint = (aeron_send_channel_endpoint_t *)cmd->item;\n\n    if (aeron_udp_channel_interceptors_transport_notifications(\n        endpoint->transport.data_paths,\n        &endpoint->transport,\n        endpoint->conductor_fields.udp_channel,\n        NULL,\n        AERON_UDP_CHANNEL_INTERCEPTOR_REMOVE_NOTIFICATION) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_remove_endpoint interceptors transport notifications\");\n        aeron_driver_sender_log_error(sender);\n    }\n\n    if (sender->context->udp_channel_transport_bindings->poller_remove_func(&sender->poller, &endpoint->transport) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_remove_endpoint\");\n        aeron_driver_sender_log_error(sender);\n    }\n\n    aeron_driver_conductor_proxy_on_release_resource(\n        sender->context->conductor_proxy, endpoint, AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_SEND_CHANNEL_ENDPOINT);\n}\n\nvoid aeron_driver_sender_on_add_publication(void *clientd, void *command)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)clientd;\n    aeron_command_base_t *cmd = (aeron_command_base_t *)command;\n    aeron_network_publication_t *publication = (aeron_network_publication_t *)cmd->item;\n    aeron_udp_channel_transport_t *transport = &publication->endpoint->transport;\n\n    int ensure_capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(\n        ensure_capacity_result, sender->network_publications, aeron_driver_sender_network_publication_entry_t)\n\n    if (ensure_capacity_result < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_add_publication\");\n        aeron_driver_sender_log_error(sender);\n        return;\n    }\n\n    sender->network_publications.array[sender->network_publications.length++].publication = publication;\n    if (aeron_send_channel_endpoint_add_publication(publication->endpoint, publication) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_add_publication add_publication\");\n        aeron_driver_sender_log_error(sender);\n    }\n\n    if (aeron_udp_channel_interceptors_publication_notifications(\n        transport->data_paths, transport, publication, AERON_UDP_CHANNEL_INTERCEPTOR_ADD_NOTIFICATION) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_add_publication interceptors publication notifications\");\n        aeron_driver_sender_log_error(sender);\n    }\n}\n\nvoid aeron_driver_sender_on_remove_publication(void *clientd, void *command)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)clientd;\n    aeron_command_base_t *cmd = (aeron_command_base_t *)command;\n    aeron_network_publication_t *publication = (aeron_network_publication_t *)cmd->item;\n    aeron_udp_channel_transport_t *transport = &publication->endpoint->transport;\n\n    for (size_t i = 0, size = sender->network_publications.length, last_index = size - 1; i < size; i++)\n    {\n        if (publication == sender->network_publications.array[i].publication)\n        {\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)sender->network_publications.array,\n                sizeof(aeron_driver_sender_network_publication_entry_t),\n                i,\n                last_index);\n            sender->network_publications.length--;\n            break;\n        }\n    }\n\n    if (aeron_send_channel_endpoint_remove_publication(publication->endpoint, publication) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_remove_publication\");\n        aeron_driver_sender_log_error(sender);\n    }\n\n    if (aeron_udp_channel_interceptors_publication_notifications(\n        transport->data_paths, transport, publication, AERON_UDP_CHANNEL_INTERCEPTOR_REMOVE_NOTIFICATION) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_remove_publication interceptors publication notifications\");\n        aeron_driver_sender_log_error(sender);\n    }\n\n    aeron_driver_conductor_proxy_on_release_resource(\n        sender->context->conductor_proxy, publication, AERON_DRIVER_CONDUCTOR_RESOURCE_TYPE_NETWORK_PUBLICATION);\n}\n\nvoid aeron_driver_sender_on_add_destination(void *clientd, void *command)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)clientd;\n    aeron_command_destination_t *cmd = (aeron_command_destination_t *)command;\n\n    if (aeron_send_channel_endpoint_add_destination(\n        cmd->endpoint, cmd->uri, &cmd->control_address, cmd->destination_registration_id) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_add_destination\");\n        aeron_driver_sender_log_error(sender);\n    }\n}\n\nvoid aeron_driver_sender_on_remove_destination(void *clientd, void *command)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)clientd;\n    aeron_command_destination_t *cmd = (aeron_command_destination_t *)command;\n    aeron_uri_t *old_uri = NULL;\n\n    if (aeron_send_channel_endpoint_remove_destination(cmd->endpoint, &cmd->control_address, &old_uri) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_remove_destination\");\n        aeron_driver_sender_log_error(sender);\n    }\n\n    if (NULL != old_uri)\n    {\n        aeron_driver_conductor_proxy_on_delete_send_destination(sender->context->conductor_proxy, old_uri);\n    }\n}\n\nvoid aeron_driver_sender_on_remove_destination_by_id(void *clientd, void *command)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)clientd;\n    aeron_command_destination_by_id_t *cmd = (aeron_command_destination_by_id_t *)command;\n    aeron_uri_t *old_uri = NULL;\n\n    if (aeron_send_channel_endpoint_remove_destination_by_id(\n        cmd->endpoint, cmd->destination_registration_id, &old_uri) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"sender on_remove_destination\");\n        aeron_driver_sender_log_error(sender);\n    }\n\n    if (NULL != old_uri)\n    {\n        aeron_driver_conductor_proxy_on_delete_send_destination(sender->context->conductor_proxy, old_uri);\n    }\n}\n\nvoid aeron_driver_sender_on_resolution_change(void *clientd, void *command)\n{\n    aeron_driver_sender_t *sender = clientd;\n    aeron_command_sender_resolution_change_t *resolution_change = (aeron_command_sender_resolution_change_t *)command;\n    aeron_send_channel_endpoint_t *endpoint = resolution_change->endpoint;\n\n    if (aeron_send_channel_endpoint_resolution_change(\n        sender->context, endpoint, resolution_change->endpoint_name, &resolution_change->new_addr) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_driver_sender_log_error(sender);\n    }\n\n    aeron_counter_get_and_add_release(sender->resolution_changes_counter, 1);\n}\n\nint aeron_driver_sender_do_send(aeron_driver_sender_t *sender, int64_t now_ns)\n{\n    int bytes_sent = 0;\n    aeron_driver_sender_network_publication_entry_t *publications = sender->network_publications.array;\n    size_t length = sender->network_publications.length;\n    size_t starting_index = sender->round_robin_index++;\n\n    if (starting_index >= length)\n    {\n        sender->round_robin_index = starting_index = 0;\n    }\n\n    for (size_t i = starting_index; i < length; i++)\n    {\n        int result = aeron_network_publication_send(publications[i].publication, now_ns);\n        if (result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"sender do_send\");\n            aeron_driver_sender_log_error(sender);\n        }\n        else\n        {\n            bytes_sent += result;\n        }\n    }\n\n    for (size_t i = 0; i < starting_index; i++)\n    {\n        int result = aeron_network_publication_send(publications[i].publication, now_ns);\n        if (result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"sender do_send\");\n            aeron_driver_sender_log_error(sender);\n        }\n        else\n        {\n            bytes_sent += result;\n        }\n    }\n\n    aeron_counter_get_and_add_release(sender->total_bytes_sent_counter, bytes_sent);\n\n    return bytes_sent;\n}\n\nextern void aeron_driver_sender_log_error(aeron_driver_sender_t *sender);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_sender.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_SENDER_H\n#define AERON_DRIVER_SENDER_H\n\n#include \"aeron_driver_context.h\"\n#include \"aeron_driver_sender_proxy.h\"\n#include \"aeron_system_counters.h\"\n#include \"media/aeron_udp_transport_poller.h\"\n#include \"aeron_network_publication.h\"\n#include \"concurrent/aeron_distinct_error_log.h\"\n\ntypedef struct aeron_driver_sender_network_publication_entry_stct\n{\n    aeron_network_publication_t *publication;\n}\naeron_driver_sender_network_publication_entry_t;\n\ntypedef struct aeron_driver_sender_stct\n{\n    aeron_driver_sender_proxy_t sender_proxy;\n    aeron_udp_transport_poller_t poller;\n\n    struct aeron_driver_sender_network_publications_stct\n    {\n        aeron_driver_sender_network_publication_entry_t *array;\n        size_t length;\n        size_t capacity;\n    }\n    network_publications;\n\n    struct aeron_driver_sender_buffers_stct\n    {\n        size_t vector_capacity;\n        uint8_t *buffers[AERON_DRIVER_SENDER_IO_VECTOR_LENGTH_MAX];\n        struct iovec iov[AERON_DRIVER_SENDER_IO_VECTOR_LENGTH_MAX];\n        struct sockaddr_storage addrs[AERON_DRIVER_SENDER_IO_VECTOR_LENGTH_MAX];\n    }\n    recv_buffers;\n\n    aeron_udp_channel_data_paths_t data_paths;\n\n    volatile int64_t *total_bytes_sent_counter;\n    volatile int64_t *errors_counter;\n    volatile int64_t *invalid_frames_counter;\n    volatile int64_t *status_messages_received_counter;\n    volatile int64_t *nak_messages_received_counter;\n    volatile int64_t *error_messages_received_counter;\n    volatile int64_t *resolution_changes_counter;\n    volatile int64_t *short_sends_counter;\n\n    aeron_driver_context_t *context;\n    aeron_udp_transport_poller_poll_func_t poller_poll_func;\n    aeron_udp_channel_transport_recvmmsg_func_t recvmmsg_func;\n    aeron_distinct_error_log_t *error_log;\n    int64_t status_message_read_timeout_ns;\n    int64_t control_poll_timeout_ns;\n    int64_t re_resolution_deadline_ns;\n    size_t round_robin_index;\n    size_t duty_cycle_counter;\n    size_t duty_cycle_ratio;\n\n    uint8_t padding[AERON_CACHE_LINE_LENGTH];\n}\naeron_driver_sender_t;\n\ninline void aeron_driver_sender_log_error(aeron_driver_sender_t *sender)\n{\n    aeron_distinct_error_log_record(sender->error_log, aeron_errcode(), aeron_errmsg());\n    aeron_counter_increment(sender->errors_counter);\n    aeron_err_clear();\n}\n\nint aeron_driver_sender_init(\n    aeron_driver_sender_t *sender,\n    aeron_driver_context_t *context,\n    aeron_system_counters_t *system_counters,\n    aeron_distinct_error_log_t *error_log);\n\nint aeron_driver_sender_do_work(void *clientd);\nvoid aeron_driver_sender_on_close(void *clientd);\n\nvoid aeron_driver_sender_on_add_endpoint(void *clientd, void *command);\nvoid aeron_driver_sender_on_remove_endpoint(void *clientd, void *command);\nvoid aeron_driver_sender_on_add_publication(void *clientd, void *command);\nvoid aeron_driver_sender_on_remove_publication(void *clientd, void *command);\nvoid aeron_driver_sender_on_add_destination(void *clientd, void *command);\nvoid aeron_driver_sender_on_remove_destination(void *clientd, void *command);\nvoid aeron_driver_sender_on_remove_destination_by_id(void *clientd, void *command);\nvoid aeron_driver_sender_on_resolution_change(void *clientd, void *command);\n\nint aeron_driver_sender_do_send(aeron_driver_sender_t *sender, int64_t now_ns);\n\n#endif //AERON_DRIVER_SENDER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_sender_proxy.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_driver_sender.h\"\n#include \"aeron_alloc.h\"\n\nvoid aeron_driver_sender_proxy_offer(aeron_driver_sender_proxy_t *sender_proxy, void *cmd, size_t length)\n{\n    aeron_rb_write_result_t result;\n    while (AERON_RB_FULL == (result = aeron_mpsc_rb_write(sender_proxy->command_queue, 1, cmd, length)))\n    {\n        aeron_counter_increment_release(sender_proxy->fail_counter);\n        sched_yield();\n    }\n\n    if (AERON_RB_ERROR == result)\n    {\n        aeron_distinct_error_log_record(\n            sender_proxy->sender->error_log, EINVAL, \"Error writing to receiver proxy ring buffer\");\n    }\n}\n\nvoid aeron_driver_sender_proxy_on_add_endpoint(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_send_channel_endpoint_t *endpoint)\n{\n    sender_proxy->log.on_add_endpoint(endpoint->conductor_fields.udp_channel);\n    aeron_command_base_t cmd =\n        {\n            .func = aeron_driver_sender_on_add_endpoint,\n            .item = endpoint\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(sender_proxy->threading_mode))\n    {\n        aeron_driver_sender_on_add_endpoint(sender_proxy->sender, &cmd);\n    }\n    else\n    {\n        aeron_driver_sender_proxy_offer(sender_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_sender_proxy_on_remove_endpoint(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_send_channel_endpoint_t *endpoint)\n{\n    sender_proxy->log.on_remove_endpoint(endpoint->conductor_fields.udp_channel);\n    aeron_command_base_t cmd =\n        {\n            .func = aeron_driver_sender_on_remove_endpoint,\n            .item = endpoint\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(sender_proxy->threading_mode))\n    {\n        aeron_driver_sender_on_remove_endpoint(sender_proxy->sender, &cmd);\n    }\n    else\n    {\n        aeron_driver_sender_proxy_offer(sender_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_sender_proxy_on_add_publication(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_network_publication_t *publication)\n{\n    aeron_command_base_t cmd =\n        {\n            .func = aeron_driver_sender_on_add_publication,\n            .item = publication\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(sender_proxy->threading_mode))\n    {\n        aeron_driver_sender_on_add_publication(sender_proxy->sender, &cmd);\n    }\n    else\n    {\n        aeron_driver_sender_proxy_offer(sender_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_sender_proxy_on_remove_publication(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_network_publication_t *publication)\n{\n    aeron_command_base_t cmd =\n        {\n            .func = aeron_driver_sender_on_remove_publication,\n            .item = publication\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(sender_proxy->threading_mode))\n    {\n        aeron_driver_sender_on_remove_publication(sender_proxy->sender, &cmd);\n    }\n    else\n    {\n        aeron_driver_sender_proxy_offer(sender_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_sender_proxy_on_add_destination(\n    aeron_driver_sender_proxy_t *sender_proxy,\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_uri_t *uri,\n    struct sockaddr_storage *addr,\n    int64_t destination_registration_id)\n{\n    aeron_command_destination_t cmd =\n        {\n            .base = { .func = aeron_driver_sender_on_add_destination, .item = NULL },\n            .destination_registration_id = destination_registration_id,\n            .endpoint = endpoint,\n            .uri = uri\n        };\n    memcpy(&cmd.control_address, addr, sizeof(cmd.control_address));\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(sender_proxy->threading_mode))\n    {\n        aeron_driver_sender_on_add_destination(sender_proxy->sender, &cmd);\n    }\n    else\n    {\n        aeron_driver_sender_proxy_offer(sender_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_sender_proxy_on_remove_destination(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_send_channel_endpoint_t *endpoint, struct sockaddr_storage *addr)\n{\n    aeron_command_destination_t cmd =\n        {\n            .base = { .func = aeron_driver_sender_on_remove_destination, .item = NULL },\n            .endpoint = endpoint\n        };\n    memcpy(&cmd.control_address, addr, sizeof(cmd.control_address));\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(sender_proxy->threading_mode))\n    {\n        aeron_driver_sender_on_remove_destination(sender_proxy->sender, &cmd);\n    }\n    else\n    {\n        aeron_driver_sender_proxy_offer(sender_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_sender_proxy_on_remove_destination_by_id(\n    aeron_driver_sender_proxy_t *sender_proxy,\n    aeron_send_channel_endpoint_t *endpoint,\n    int64_t destination_registration_id)\n{\n    aeron_command_destination_by_id_t cmd =\n        {\n            .base = { .func = aeron_driver_sender_on_remove_destination_by_id, .item = NULL },\n            .endpoint = endpoint,\n            .destination_registration_id = destination_registration_id\n        };\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(sender_proxy->threading_mode))\n    {\n        aeron_driver_sender_on_remove_destination_by_id(sender_proxy->sender, &cmd);\n    }\n    else\n    {\n        aeron_driver_sender_proxy_offer(sender_proxy, &cmd, sizeof(cmd));\n    }\n}\n\nvoid aeron_driver_sender_proxy_on_resolution_change(\n    aeron_driver_sender_proxy_t *sender_proxy,\n    const char *endpoint_name,\n    aeron_send_channel_endpoint_t *endpoint,\n    struct sockaddr_storage *new_addr)\n{\n    aeron_command_sender_resolution_change_t cmd =\n        {\n            .base = { .func = aeron_driver_sender_on_resolution_change, .item = NULL },\n            .endpoint = endpoint,\n            .endpoint_name = endpoint_name,\n        };\n    memcpy(&cmd.new_addr, new_addr, sizeof(cmd.new_addr));\n\n    if (AERON_THREADING_MODE_IS_SHARED_OR_INVOKER(sender_proxy->threading_mode))\n    {\n        aeron_driver_sender_on_resolution_change(sender_proxy->sender, &cmd);\n    }\n    else\n    {\n        aeron_driver_sender_proxy_offer(sender_proxy, &cmd, sizeof(cmd));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_sender_proxy.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_SENDER_PROXY_H\n#define AERON_DRIVER_SENDER_PROXY_H\n\n#include \"aeron_driver_context.h\"\n\ntypedef struct aeron_driver_sender_stct aeron_driver_sender_t;\ntypedef struct aeron_send_channel_endpoint_stct aeron_send_channel_endpoint_t;\ntypedef struct aeron_network_publication_stct aeron_network_publication_t;\n\ntypedef struct aeron_driver_sender_proxy_stct\n{\n    aeron_driver_sender_t *sender;\n    aeron_threading_mode_t threading_mode;\n    struct\n    {\n        aeron_on_endpoint_change_func_t on_add_endpoint;\n        aeron_on_endpoint_change_func_t on_remove_endpoint;\n    } log;\n    aeron_mpsc_rb_t *command_queue;\n    int64_t *fail_counter;\n}\naeron_driver_sender_proxy_t;\n\nvoid aeron_driver_sender_proxy_on_add_endpoint(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_send_channel_endpoint_t *endpoint);\n\nvoid aeron_driver_sender_proxy_on_remove_endpoint(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_send_channel_endpoint_t *endpoint);\n\nvoid aeron_driver_sender_proxy_on_add_publication(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_network_publication_t *publication);\n\nvoid aeron_driver_sender_proxy_on_remove_publication(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_network_publication_t *publication);\n\nvoid aeron_driver_sender_proxy_on_resolution_change(\n    aeron_driver_sender_proxy_t *sender_proxy,\n    const char *endpoint_name,\n    aeron_send_channel_endpoint_t *endpoint,\n    struct sockaddr_storage *new_addr);\n\ntypedef struct aeron_command_destination_stct\n{\n    aeron_command_base_t base;\n    int64_t destination_registration_id;\n    struct sockaddr_storage control_address;\n    void *endpoint;\n    void *uri;\n}\naeron_command_destination_t;\n\ntypedef struct aeron_command_destination_by_id_stct\n{\n    aeron_command_base_t base;\n    struct sockaddr_storage control_address;\n    void *endpoint;\n    int64_t destination_registration_id;\n}\naeron_command_destination_by_id_t;\n\ntypedef struct aeron_command_sender_resolution_change_stct\n{\n    aeron_command_base_t base;\n    const char *endpoint_name;\n    void *endpoint;\n    struct sockaddr_storage new_addr;\n}\naeron_command_sender_resolution_change_t;\n\nvoid aeron_driver_sender_proxy_on_add_destination(\n    aeron_driver_sender_proxy_t *sender_proxy,\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_uri_t *uri,\n    struct sockaddr_storage *addr,\n    int64_t destination_registration_id);\n\nvoid aeron_driver_sender_proxy_on_remove_destination(\n    aeron_driver_sender_proxy_t *sender_proxy, aeron_send_channel_endpoint_t *endpoint, struct sockaddr_storage *addr);\n\nvoid aeron_driver_sender_proxy_on_remove_destination_by_id(\n    aeron_driver_sender_proxy_t *sender_proxy,\n    aeron_send_channel_endpoint_t *endpoint,\n    int64_t destination_registration_id);\n\n#endif //AERON_DRIVER_SENDER_PROXY_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_version.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include \"aeron_driver_version.h\"\n\nconst char *aeron_driver_version_text(void)\n{\n    return AERON_VERSION_TXT;\n}\n\nconst char *aeron_driver_version_git_sha(void)\n{\n    return AERON_VERSION_GITSHA;\n}\n\nint aeron_driver_version_major(void)\n{\n    return AERON_VERSION_MAJOR;\n}\n\nint aeron_driver_version_minor(void)\n{\n    return AERON_VERSION_MINOR;\n}\n\nint aeron_driver_version_patch(void)\n{\n    return AERON_VERSION_PATCH;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_driver_version.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_VERSION_H\n#define AERON_DRIVER_VERSION_H\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\nconst char *aeron_driver_version_text(void);\nconst char *aeron_driver_version_git_sha(void);\nint aeron_driver_version_major(void);\nint aeron_driver_version_minor(void);\nint aeron_driver_version_patch(void);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif //AERON_DRIVER_VERSION_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_duty_cycle_tracker.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DUTY_CYCLE_TRACKER_H\n#define AERON_DUTY_CYCLE_TRACKER_H\n\n#include \"aeron_driver_common.h\"\n\ntypedef void (*aeron_duty_cycle_tracker_update_func_t)(void *state, int64_t now_ns);\ntypedef void (*aeron_duty_cycle_tracker_measure_and_update_func_t)(void *state, int64_t now_ns);\n\nstruct aeron_duty_cycle_tracker_stct\n{\n    aeron_duty_cycle_tracker_update_func_t update;\n    aeron_duty_cycle_tracker_measure_and_update_func_t measure_and_update;\n    void *state;\n};\n\ntypedef struct aeron_duty_cycle_stall_tracker_stct {\n    struct aeron_duty_cycle_tracker_stct tracker;\n    char lhs_padding[AERON_CACHE_LINE_LENGTH - sizeof(int64_t)];\n    int64_t last_time_of_update_ns;\n    char rhs_padding[AERON_CACHE_LINE_LENGTH - sizeof(int64_t)];\n    uint64_t cycle_threshold_ns;\n    int64_t *max_cycle_time_counter;\n    int64_t *cycle_time_threshold_exceeded_counter;\n}\naeron_duty_cycle_stall_tracker_t;\n\ninline void aeron_duty_cycle_stall_tracker_update(void *state, int64_t now_ns)\n{\n    aeron_duty_cycle_stall_tracker_t *tracker = (aeron_duty_cycle_stall_tracker_t *)state;\n\n    tracker->last_time_of_update_ns = now_ns;\n}\n\ninline void aeron_duty_cycle_stall_tracker_measure_and_update(void *state, int64_t now_ns)\n{\n    aeron_duty_cycle_stall_tracker_t *tracker = (aeron_duty_cycle_stall_tracker_t *)state;\n    int64_t cycle_time_ns = now_ns - tracker->last_time_of_update_ns;\n\n    aeron_counter_propose_max_release(tracker->max_cycle_time_counter, cycle_time_ns);\n    if (cycle_time_ns > (int64_t)(tracker->cycle_threshold_ns))\n    {\n        aeron_counter_increment_release(tracker->cycle_time_threshold_exceeded_counter);\n    }\n\n    tracker->last_time_of_update_ns = now_ns;\n}\n\n#endif // AERON_DUTY_CYCLE_TRACKER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_flow_control.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <errno.h>\n#include \"media/aeron_udp_channel.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"util/aeron_symbol_table.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_flow_control.h\"\n\ntypedef struct aeron_max_flow_control_strategy_state_stct\n{\n    size_t retransmit_receiver_window_multiple;\n}\naeron_max_flow_control_strategy_state_t;\n\ntypedef struct aeron_unicast_flow_control_strategy_state_stct\n{\n    size_t retransmit_receiver_window_multiple;\n}\naeron_unicast_flow_control_strategy_state_t;\n\naeron_symbol_table_func_t aeron_flow_control_strategy_table[] =\n    {\n        {\n            AERON_UNICAST_MAX_FLOW_CONTROL_STRATEGY_NAME,\n            \"aeron_unicast_flow_control_strategy_supplier\",\n            (aeron_fptr_t)aeron_unicast_flow_control_strategy_supplier\n        },\n        {\n            AERON_MULTICAST_MAX_FLOW_CONTROL_STRATEGY_NAME,\n            \"aeron_max_multicast_flow_control_strategy_supplier\",\n            (aeron_fptr_t)aeron_max_multicast_flow_control_strategy_supplier\n        },\n        {\n            AERON_MULTICAST_MIN_FLOW_CONTROL_STRATEGY_NAME,\n            \"aeron_min_flow_control_strategy_supplier\",\n            (aeron_fptr_t)aeron_min_flow_control_strategy_supplier\n        },\n        {\n            AERON_MULTICAST_TAGGED_FLOW_CONTROL_STRATEGY_NAME,\n            \"aeron_tagged_flow_control_strategy_supplier\",\n            (aeron_fptr_t)aeron_tagged_flow_control_strategy_supplier\n        }\n    };\n\nstatic const size_t aeron_flow_control_strategy_table_length =\n    sizeof(aeron_flow_control_strategy_table) / sizeof(aeron_symbol_table_func_t);\n\naeron_flow_control_strategy_supplier_func_t aeron_flow_control_strategy_supplier_load(const char *strategy_name)\n{\n    return (aeron_flow_control_strategy_supplier_func_t)aeron_symbol_table_func_load(\n        aeron_flow_control_strategy_table, aeron_flow_control_strategy_table_length, strategy_name, \"flow control\");\n}\n\nbool aeron_flow_control_strategy_has_required_receivers_default(aeron_flow_control_strategy_t *strategy)\n{\n    return true;\n}\n\nint64_t aeron_max_flow_control_strategy_on_idle(\n    void *state,\n    int64_t now_ns,\n    int64_t snd_lmt,\n    int64_t snd_pos,\n    bool is_end_of_stream)\n{\n    return snd_lmt;\n}\n\nint64_t aeron_max_flow_control_strategy_on_setup(\n    void *state,\n    const uint8_t *setup,\n    size_t length,\n    int64_t now_ns,\n    int64_t snd_lmt,\n    size_t position_bits_to_shift,\n    int64_t snd_pos)\n{\n    return snd_lmt;\n}\n\nint64_t aeron_max_flow_control_strategy_on_sm(\n    void *state,\n    const uint8_t *sm,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t snd_lmt,\n    int32_t initial_term_id,\n    size_t position_bits_to_shift,\n    int64_t now_ns)\n{\n    aeron_status_message_header_t *status_message_header = (aeron_status_message_header_t *)sm;\n\n    int64_t position = aeron_logbuffer_compute_position(\n        status_message_header->consumption_term_id,\n        status_message_header->consumption_term_offset,\n        position_bits_to_shift,\n        initial_term_id);\n    int64_t window_edge = position + status_message_header->receiver_window;\n\n    return snd_lmt > window_edge ? snd_lmt : window_edge;\n}\n\nvoid aeron_max_flow_control_strategy_on_error(\n    void *state,\n    const uint8_t *error,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t now_ns)\n{\n}\n\nsize_t aeron_flow_control_calculate_retransmission_length(\n    size_t resend_length,\n    size_t term_buffer_length,\n    size_t term_offset,\n    size_t retransmit_receiver_window_multiple)\n{\n    size_t length_to_end_of_term = term_buffer_length - term_offset;\n    size_t initial_window_length = aeron_driver_context_get_rcv_initial_window_length(NULL);\n    size_t receiver_window_length = aeron_receiver_window_length(initial_window_length, term_buffer_length);\n    size_t estimated_retransmit_length = receiver_window_length * retransmit_receiver_window_multiple;\n\n    return (length_to_end_of_term < estimated_retransmit_length) ?\n        AERON_MIN(length_to_end_of_term, resend_length) :\n        AERON_MIN(estimated_retransmit_length, resend_length);\n}\n\nsize_t aeron_max_flow_control_strategy_max_retransmission_length(\n    void *state,\n    size_t term_offset,\n    size_t resend_length,\n    size_t term_buffer_length,\n    size_t mtu_length)\n{\n    aeron_max_flow_control_strategy_state_t *strategy_state = (aeron_max_flow_control_strategy_state_t *)state;\n\n    return aeron_flow_control_calculate_retransmission_length(\n        resend_length,\n        term_buffer_length,\n        term_offset,\n        strategy_state->retransmit_receiver_window_multiple);\n}\n\nsize_t aeron_unicast_flow_control_strategy_max_retransmission_length(\n    void *state,\n    size_t term_offset,\n    size_t resend_length,\n    size_t term_buffer_length,\n    size_t mtu_length)\n{\n    aeron_unicast_flow_control_strategy_state_t *strategy_state = (aeron_unicast_flow_control_strategy_state_t *)state;\n\n    return aeron_flow_control_calculate_retransmission_length(\n        resend_length,\n        term_buffer_length,\n        term_offset,\n        strategy_state->retransmit_receiver_window_multiple);\n}\n\nvoid aeron_max_flow_control_strategy_on_trigger_send_setup(\n    void *state,\n    const uint8_t *sm,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t now_ns)\n{\n}\n\nint aeron_max_flow_control_strategy_fini(aeron_flow_control_strategy_t *strategy)\n{\n    aeron_free(strategy->state);\n    aeron_free(strategy);\n    return 0;\n}\n\nint aeron_flow_control_parse_max_options(\n    size_t options_length, const char *options, aeron_flow_control_max_options_t *flow_control_options)\n{\n    if (0 == options_length || NULL == options)\n    {\n        return 0;\n    }\n\n    const char *current_option = options;\n    size_t remaining = options_length;\n\n    const char *next_option;\n    do\n    {\n        next_option = (const char *)memchr(current_option, ',', remaining);\n\n        ptrdiff_t current_option_length;\n\n        if (NULL == next_option)\n        {\n            current_option_length = remaining;\n        }\n        else\n        {\n            current_option_length = next_option - current_option;\n\n            // Skip the comma.\n            next_option++;\n            remaining -= (current_option_length + 1);\n        }\n\n        if (strncmp(current_option, \"max\", 3) == 0)\n        {\n            // skip over the strategy declaration\n        }\n        else if (strncmp(current_option, \"rrwm:\", 5) == 0)\n        {\n            char *endptr;\n            errno = 0;\n            int64_t signedRrwm = strtol(current_option + 5, &endptr, 10);\n            if (0 == errno && signedRrwm > 0)\n            {\n                flow_control_options->multicast_flow_control_rrwm = (size_t)signedRrwm;\n            }\n            else\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"Flow control options - invalid flow control retransmit receiver window multiple, field: %.*s, options: %.*s\",\n                    (int)current_option_length,\n                    current_option,\n                    (int)options_length,\n                    options);\n                return -1;\n            }\n        }\n        else\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"Flow control options - unrecognised option, field: %.*s, options: %.*s\",\n                (int)current_option_length,\n                current_option,\n                (int)options_length,\n                options);\n\n            return -1;\n        }\n\n        current_option = next_option;\n    }\n    while (NULL != current_option && 0 < remaining);\n\n    return 1;\n}\n\nint aeron_max_multicast_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_length)\n{\n    aeron_flow_control_strategy_t *_strategy;\n\n    aeron_flow_control_max_options_t options;\n\n    options.multicast_flow_control_rrwm = context->multicast_flow_control_rrwm;\n    const char *fc_options = aeron_uri_find_param_value(&channel->uri.params.udp.additional_params, AERON_URI_FC_KEY);\n    if (aeron_flow_control_parse_max_options(NULL != fc_options ? strlen(fc_options) : 0, fc_options, &options) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_alloc((void**)&_strategy, sizeof(aeron_flow_control_strategy_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_alloc(&_strategy->state, sizeof(aeron_max_flow_control_strategy_state_t)) < 0)\n    {\n        aeron_free(_strategy);\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    _strategy->on_idle = aeron_max_flow_control_strategy_on_idle;\n    _strategy->on_status_message = aeron_max_flow_control_strategy_on_sm;\n    _strategy->on_setup = aeron_max_flow_control_strategy_on_setup;\n    _strategy->on_error = aeron_max_flow_control_strategy_on_error;\n    _strategy->fini = aeron_max_flow_control_strategy_fini;\n    _strategy->has_required_receivers = aeron_flow_control_strategy_has_required_receivers_default;\n    _strategy->on_trigger_send_setup = aeron_max_flow_control_strategy_on_trigger_send_setup;\n    _strategy->max_retransmission_length = aeron_max_flow_control_strategy_max_retransmission_length;\n    aeron_max_flow_control_strategy_state_t *state = _strategy->state;\n    state->retransmit_receiver_window_multiple = options.multicast_flow_control_rrwm;\n\n    *strategy = _strategy;\n\n    return 0;\n}\n\nint aeron_unicast_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_length)\n{\n    aeron_flow_control_strategy_t *_strategy;\n\n    if (aeron_alloc((void **)&_strategy, sizeof(aeron_flow_control_strategy_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_alloc(&_strategy->state, sizeof(aeron_unicast_flow_control_strategy_state_t)) < 0)\n    {\n        aeron_free(_strategy);\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    _strategy->on_idle = aeron_max_flow_control_strategy_on_idle;\n    _strategy->on_status_message = aeron_max_flow_control_strategy_on_sm;\n    _strategy->on_setup = aeron_max_flow_control_strategy_on_setup;\n    _strategy->on_error = aeron_max_flow_control_strategy_on_error;\n    _strategy->fini = aeron_max_flow_control_strategy_fini;\n    _strategy->has_required_receivers = aeron_flow_control_strategy_has_required_receivers_default;\n    _strategy->on_trigger_send_setup = aeron_max_flow_control_strategy_on_trigger_send_setup;\n    _strategy->max_retransmission_length = aeron_unicast_flow_control_strategy_max_retransmission_length;\n    aeron_unicast_flow_control_strategy_state_t *state = _strategy->state;\n    state->retransmit_receiver_window_multiple = context->unicast_flow_control_rrwm;\n    *strategy = _strategy;\n\n    return 0;\n}\n\naeron_flow_control_strategy_supplier_func_table_entry_t aeron_flow_control_strategy_supplier_table[] =\n{\n    { AERON_UNICAST_MAX_FLOW_CONTROL_STRATEGY_NAME, aeron_unicast_flow_control_strategy_supplier },\n    { AERON_MULTICAST_MAX_FLOW_CONTROL_STRATEGY_NAME, aeron_max_multicast_flow_control_strategy_supplier },\n    { AERON_MULTICAST_MIN_FLOW_CONTROL_STRATEGY_NAME, aeron_min_flow_control_strategy_supplier },\n    { AERON_MULTICAST_TAGGED_FLOW_CONTROL_STRATEGY_NAME, aeron_tagged_flow_control_strategy_supplier }\n};\n\naeron_flow_control_strategy_supplier_func_t aeron_flow_control_strategy_supplier_by_name(const char *name)\n{\n    size_t entries = sizeof(aeron_flow_control_strategy_supplier_table) /\n        sizeof(aeron_flow_control_strategy_supplier_func_table_entry_t);\n\n    for (size_t i = 0; i < entries; i++)\n    {\n        aeron_flow_control_strategy_supplier_func_table_entry_t *entry = &aeron_flow_control_strategy_supplier_table[i];\n\n        if (strncmp(entry->name, name, strlen(entry->name)) == 0)\n        {\n            return entry->supplier_func;\n        }\n    }\n\n    return NULL;\n}\n\nvoid aeron_flow_control_extract_strategy_name_length(\n    const size_t options_length, const char *options, size_t *strategy_length)\n{\n    const char *next_option = (const char *)memchr(options, ',', options_length);\n    *strategy_length = NULL == next_option ? options_length : (size_t)labs((long)(next_option - options));\n}\n\nint aeron_default_multicast_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_length)\n{\n    aeron_flow_control_strategy_supplier_func_t flow_control_strategy_supplier_func;\n\n    if (aeron_udp_channel_is_multi_destination(channel) ||\n        channel->is_multicast)\n    {\n        const char *flow_control_options = aeron_uri_find_param_value(\n            &channel->uri.params.udp.additional_params, AERON_URI_FC_KEY);\n        if (NULL != flow_control_options)\n        {\n            const char *strategy_name = flow_control_options;\n            size_t strategy_name_length = 0;\n            aeron_flow_control_extract_strategy_name_length(\n                strlen(flow_control_options), flow_control_options, &strategy_name_length);\n\n            if (0 == strategy_name_length)\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"No flow control strategy name specified, URI: %.*s\",\n                    (int)channel->uri_length,\n                    channel->original_uri);\n                return -1;\n            }\n\n            if (strlen(AERON_MAX_FLOW_CONTROL_STRATEGY_NAME) == strategy_name_length &&\n                0 == strncmp(AERON_MAX_FLOW_CONTROL_STRATEGY_NAME, strategy_name, strategy_name_length))\n            {\n                flow_control_strategy_supplier_func = aeron_max_multicast_flow_control_strategy_supplier;\n            }\n            else if (strlen(AERON_MIN_FLOW_CONTROL_STRATEGY_NAME) == strategy_name_length &&\n                0 == strncmp(AERON_MIN_FLOW_CONTROL_STRATEGY_NAME, strategy_name, strategy_name_length))\n            {\n                flow_control_strategy_supplier_func = aeron_min_flow_control_strategy_supplier;\n            }\n            else if (strlen(AERON_TAGGED_FLOW_CONTROL_STRATEGY_NAME) == strategy_name_length &&\n                0 == strncmp(AERON_TAGGED_FLOW_CONTROL_STRATEGY_NAME, strategy_name, strategy_name_length))\n            {\n                flow_control_strategy_supplier_func = aeron_tagged_flow_control_strategy_supplier;\n            }\n            else\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"Invalid flow control strategy name: %.*s from URI: %.*s\",\n                    (int)strategy_name_length,\n                    strategy_name,\n                    (int)channel->uri_length,\n                    channel->original_uri);\n\n                return -1;\n            }\n        }\n        else\n        {\n            flow_control_strategy_supplier_func = context->multicast_flow_control_supplier_func;\n        }\n    }\n    else\n    {\n        flow_control_strategy_supplier_func = context->unicast_flow_control_supplier_func;\n    }\n\n    int rc = flow_control_strategy_supplier_func(\n        strategy, context, counters_manager, channel, stream_id, session_id,\n        registration_id, initial_term_id, term_length);\n\n    if (0 <= rc && NULL != *strategy && NULL == (*strategy)->has_required_receivers)\n    {\n        (*strategy)->has_required_receivers = aeron_flow_control_strategy_has_required_receivers_default;\n    }\n\n    return rc;\n}\n\n#define AERON_FLOW_CONTROL_NUMBER_BUFFER_LEN (64)\n\nint aeron_flow_control_parse_tagged_options(\n    size_t options_length, const char *options, aeron_flow_control_tagged_options_t *flow_control_options)\n{\n    flow_control_options->strategy_name = NULL;\n    flow_control_options->strategy_name_length = 0;\n    flow_control_options->timeout_ns.is_present = false;\n    flow_control_options->timeout_ns.value = 0;\n    flow_control_options->group_tag.is_present = false;\n    flow_control_options->group_tag.value = -1;\n    flow_control_options->group_min_size.is_present = false;\n    flow_control_options->group_min_size.value = 0;\n\n    char number_buffer[AERON_FLOW_CONTROL_NUMBER_BUFFER_LEN];\n\n    if (0 == options_length || NULL == options)\n    {\n        return 0;\n    }\n\n    const char *current_option = options;\n    size_t remaining = options_length;\n\n    const char *next_option;\n    do\n    {\n        next_option = (const char *)memchr(current_option, ',', remaining);\n\n        ptrdiff_t current_option_length;\n\n        if (NULL == next_option)\n        {\n            current_option_length = remaining;\n        }\n        else\n        {\n            current_option_length = next_option - current_option;\n\n            // Skip the comma.\n            next_option++;\n            remaining -= (current_option_length + 1);\n        }\n\n        if (NULL == flow_control_options->strategy_name)\n        {\n            flow_control_options->strategy_name = current_option;\n            flow_control_options->strategy_name_length = current_option_length;\n        }\n        else if (current_option_length > 2 &&\n            ('g' == current_option[0] || 't' == current_option[0]) &&\n            ':' == current_option[1])\n        {\n            const size_t value_length = current_option_length - 2;\n            const char *value = current_option + 2;\n\n            if (AERON_FLOW_CONTROL_NUMBER_BUFFER_LEN <= value_length)\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"Flow control options - number field too long (found %d, max %d), field: %.*s, options: %.*s\",\n                    (int)value_length,\n                    (AERON_FLOW_CONTROL_NUMBER_BUFFER_LEN - 1),\n                    (int)value_length,\n                    value,\n                    (int)options_length,\n                    options);\n\n                return -1;\n            }\n            strncpy(number_buffer, value, value_length);\n            number_buffer[value_length] = '\\0';\n\n            if ('g' == current_option[0])\n            {\n                char *end_ptr = \"\";\n                errno = 0;\n\n                const long long group_tag = strtoll(number_buffer, &end_ptr, 10);\n                const bool has_group_min_size = '/' == *end_ptr;\n\n                if (0 == errno && number_buffer != end_ptr && ('\\0' == *end_ptr || has_group_min_size))\n                {\n                    flow_control_options->group_tag.is_present = true;\n                    flow_control_options->group_tag.value = (int64_t)group_tag;\n                }\n                else if (number_buffer != end_ptr && !has_group_min_size) // Allow empty values if we have a group count\n                {\n                    AERON_SET_ERR(\n                        EINVAL,\n                        \"Flow control options - invalid group, field: %.*s, options: %.*s\",\n                        (int)current_option_length,\n                        current_option,\n                        (int)options_length,\n                        options);\n\n                    return -1;\n                }\n\n                if (has_group_min_size)\n                {\n                    const char *group_min_size_ptr = end_ptr + 1;\n                    end_ptr = \"\";\n                    errno = 0;\n\n                    const long group_min_size = strtol(group_min_size_ptr, &end_ptr, 10);\n\n                    if (0 == errno &&\n                        '\\0' == *end_ptr &&\n                        group_min_size_ptr != end_ptr &&\n                        0 <= group_min_size && group_min_size <= INT32_MAX)\n                    {\n                        flow_control_options->group_min_size.is_present = true;\n                        flow_control_options->group_min_size.value = (int32_t)group_min_size;\n                    }\n                    else\n                    {\n                        AERON_SET_ERR(\n                            EINVAL,\n                            \"Group count invalid, field: %.*s, options: %.*s\",\n                            (int)current_option_length,\n                            current_option,\n                            (int)options_length,\n                            options);\n\n                        return -1;\n                    }\n                }\n            }\n            else if ('t' == current_option[0])\n            {\n                uint64_t timeout_ns;\n                if (0 <= aeron_parse_duration_ns(number_buffer, &timeout_ns))\n                {\n                    flow_control_options->timeout_ns.is_present = true;\n                    flow_control_options->timeout_ns.value = timeout_ns;\n                }\n                else\n                {\n                    AERON_SET_ERR(\n                        EINVAL,\n                        \"Flow control options - invalid timeout, field: %.*s, options: %.*s\",\n                        (int)current_option_length,\n                        current_option,\n                        (int)options_length,\n                        options);\n\n                    return -1;\n                }\n            }\n        }\n        else if (strncmp(current_option, \"rrwm:\", 5) == 0)\n        {\n            char *endptr;\n            errno = 0;\n            int64_t signedRrwm = strtol(current_option + 5, &endptr, 10);\n            if (0 == errno && signedRrwm > 0)\n            {\n                flow_control_options->multicast_flow_control_rrwm = (size_t)signedRrwm;\n            }\n            else\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"Flow control options - invalid flow control retransmit receiver window multiple, field: %.*s, options: %.*s\",\n                    (int)current_option_length,\n                    current_option,\n                    (int)options_length,\n                    options);\n                return -1;\n            }\n        }\n        else\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"Flow control options - unrecognised option, field: %.*s, options: %.*s\",\n                (int)current_option_length,\n                current_option,\n                (int)options_length,\n                options);\n\n            return -1;\n        }\n\n        current_option = next_option;\n    }\n    while (NULL != current_option && 0 < remaining);\n\n    return 1;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_flow_control.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_FLOW_CONTROL_H\n#define AERON_FLOW_CONTROL_H\n\n#include \"aeron_socket.h\"\n#include \"aeron_driver_common.h\"\n#include \"aeronmd.h\"\n\n#define AERON_MAX_FLOW_CONTROL_STRATEGY_NAME \"max\"\n#define AERON_MIN_FLOW_CONTROL_STRATEGY_NAME \"min\"\n#define AERON_TAGGED_FLOW_CONTROL_STRATEGY_NAME \"tagged\"\n\n#define AERON_MIN_FLOW_CONTROL_RECEIVERS_COUNTER_NAME (\"fc-receivers\")\n\n#define AERON_MULTICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE UINT32_C(4)\n#define AERON_UNICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE UINT32_C(16)\n\ntypedef int64_t (*aeron_flow_control_strategy_on_idle_func_t)(\n    void *state,\n    int64_t now_ns,\n    int64_t snd_lmt,\n    int64_t snd_pos,\n    bool is_end_of_stream);\n\ntypedef int64_t (*aeron_flow_control_strategy_on_sm_func_t)(\n    void *state,\n    const uint8_t *sm,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t snd_lmt,\n    int32_t initial_term_id,\n    size_t position_bits_to_shift,\n    int64_t now_ns);\n\ntypedef int64_t (*aeron_flow_control_strategy_on_setup_func_t)(\n    void *state,\n    const uint8_t *setup,\n    size_t length,\n    int64_t now_ns,\n    int64_t snd_lmt,\n    size_t position_bits_to_shift,\n    int64_t snd_pos);\n\ntypedef void (*aeron_flow_control_strategy_on_error_func_t)(\n    void *state,\n    const uint8_t *error,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t now_ns);\n\ntypedef void (*aeron_flow_control_strategy_on_trigger_send_setup_func_t)(\n    void *state,\n    const uint8_t *sm,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t now_ns);\n\ntypedef size_t (*aeron_flow_control_strategy_max_retransmission_length_func_t)(\n    void *state,\n    size_t term_offset,\n    size_t resend_length,\n    size_t term_buffer_length,\n    size_t mtu_length);\n\ntypedef int (*aeron_flow_control_strategy_fini_func_t)(aeron_flow_control_strategy_t *strategy);\n\ntypedef bool (*aeron_flow_control_strategy_has_required_receivers_func_t)(aeron_flow_control_strategy_t *strategy);\n\ntypedef struct aeron_flow_control_strategy_stct\n{\n    aeron_flow_control_strategy_on_sm_func_t on_status_message;\n    aeron_flow_control_strategy_on_idle_func_t on_idle;\n    aeron_flow_control_strategy_on_setup_func_t on_setup;\n    aeron_flow_control_strategy_on_error_func_t on_error;\n    aeron_flow_control_strategy_fini_func_t fini;\n    aeron_flow_control_strategy_has_required_receivers_func_t has_required_receivers;\n    aeron_flow_control_strategy_on_trigger_send_setup_func_t on_trigger_send_setup;\n    aeron_flow_control_strategy_max_retransmission_length_func_t max_retransmission_length;\n    void *state;\n}\naeron_flow_control_strategy_t;\n\nbool aeron_flow_control_strategy_has_required_receivers_default(aeron_flow_control_strategy_t *strategy);\n\ntypedef struct aeron_flow_control_tagged_options_stct\n{\n    size_t strategy_name_length;\n    const char *strategy_name;\n    struct\n    {\n        bool is_present;\n        int64_t value;\n    }\n    group_tag;\n    struct\n    {\n        bool is_present;\n        uint64_t value;\n    }\n    timeout_ns;\n    struct\n    {\n        bool is_present;\n        int32_t value;\n    }\n    group_min_size;\n    size_t multicast_flow_control_rrwm;\n}\naeron_flow_control_tagged_options_t;\n\ntypedef struct aeron_flow_control_max_options_stct\n{\n    size_t multicast_flow_control_rrwm;\n}\naeron_flow_control_max_options_t;\n\naeron_flow_control_strategy_supplier_func_t aeron_flow_control_strategy_supplier_load(const char *strategy_name);\n\nint aeron_max_multicast_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_length);\n\nint aeron_unicast_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_length);\n\nint aeron_min_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_buffer_capacity);\n\nint aeron_tagged_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_buffer_capacity);\n\nint aeron_tagged_flow_control_strategy_to_string(\n    aeron_flow_control_strategy_t *strategy, char *buffer, size_t buffer_len);\n\nint aeron_default_multicast_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_length);\n\ntypedef struct aeron_flow_control_strategy_supplier_func_table_entry_stct\n{\n    const char *name;\n    aeron_flow_control_strategy_supplier_func_t supplier_func;\n}\naeron_flow_control_strategy_supplier_func_table_entry_t;\n\nint aeron_flow_control_parse_tagged_options(\n    size_t options_length, const char *options, aeron_flow_control_tagged_options_t *flow_control_options);\n\nsize_t aeron_flow_control_calculate_retransmission_length(\n    size_t resend_length,\n    size_t term_buffer_length,\n    size_t term_offset,\n    size_t retransmit_receiver_window_multiple);\n\n#endif //AERON_FLOW_CONTROL_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_ipc_publication.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string.h>\n#include <errno.h>\n#include <inttypes.h>\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"concurrent/aeron_logbuffer_unblocker.h\"\n#include \"aeron_ipc_publication.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_driver_conductor.h\"\n\nstatic void aeron_ipc_publication_handle_managed_resource_event(aeron_driver_managed_resource_event_t event, void *clientd);\n\nint aeron_ipc_publication_create(\n    aeron_ipc_publication_t **publication,\n    aeron_driver_context_t *context,\n    int32_t session_id,\n    int32_t stream_id,\n    int64_t registration_id,\n    aeron_position_t *pub_pos_position,\n    aeron_position_t *pub_lmt_position,\n    int32_t initial_term_id,\n    aeron_driver_uri_publication_params_t *params,\n    bool is_exclusive,\n    aeron_system_counters_t *system_counters,\n    size_t channel_length,\n    const char *channel)\n{\n    char path[AERON_MAX_PATH];\n    int path_length = aeron_ipc_publication_location(path, sizeof(path), context->aeron_dir, registration_id);\n    if (path_length < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not resolve IPC publication file path\");\n        return -1;\n    }\n    aeron_ipc_publication_t *_pub = NULL;\n    const uint64_t log_length = aeron_logbuffer_compute_log_length(params->term_length, context->file_page_size);\n\n    *publication = NULL;\n\n    if (aeron_driver_context_run_storage_checks(context, log_length) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&_pub, sizeof(aeron_ipc_publication_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not allocate IPC publication\");\n        return -1;\n    }\n\n    _pub->log_file_name = NULL;\n    if (aeron_alloc((void **)(&_pub->log_file_name), (size_t)path_length + 1) < 0)\n    {\n        aeron_free(_pub);\n        AERON_APPEND_ERR(\"%s\", \"Could not allocate IPC publication log_file_name\");\n        return -1;\n    }\n\n    _pub->channel = NULL;\n    if (aeron_alloc((void **)(&_pub->channel), (size_t)channel_length + 1) < 0)\n    {\n        aeron_free(_pub->log_file_name);\n        aeron_free(_pub);\n        AERON_APPEND_ERR(\"%s\", \"Could not allocate IPC publication channel\");\n        return -1;\n    }\n\n    if (context->raw_log_map_func(\n        &_pub->mapped_raw_log, path, params->is_sparse, params->term_length, context->file_page_size) < 0)\n    {\n        aeron_free(_pub->log_file_name);\n        aeron_free(_pub->channel);\n        aeron_free(_pub);\n        AERON_APPEND_ERR(\"error mapping IPC raw log: %s\", path);\n        return -1;\n    }\n\n    _pub->mapped_bytes_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED);\n    aeron_counter_get_and_add_release(_pub->mapped_bytes_counter, (int64_t) log_length);\n\n    _pub->raw_log_close_func = context->raw_log_close_func;\n    _pub->raw_log_free_func = context->raw_log_free_func;\n    _pub->log.untethered_subscription_state_change = context->log.untethered_subscription_on_state_change;\n    _pub->log.publication_revoke = context->log.publication_revoke;\n\n    strncpy(_pub->log_file_name, path, (size_t)path_length);\n    _pub->log_file_name[path_length] = '\\0';\n    _pub->log_file_name_length = (size_t)path_length;\n    _pub->log_meta_data = (aeron_logbuffer_metadata_t *)(_pub->mapped_raw_log.log_meta_data.addr);\n\n    strncpy(_pub->channel, channel, channel_length);\n    _pub->channel[channel_length] = '\\0';\n    _pub->channel_length = (int32_t)channel_length;\n\n    if (params->has_position)\n    {\n        int32_t term_id = params->term_id;\n        int32_t term_count = aeron_logbuffer_compute_term_count(params->term_id, initial_term_id);\n        size_t active_index = aeron_logbuffer_index_by_term_count(term_count);\n\n        _pub->log_meta_data->term_tail_counters[active_index] =\n            (term_id * (INT64_C(1) << 32)) | (int64_t)params->term_offset;\n\n        for (int i = 1; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n        {\n            int32_t expected_term_id = (term_id + i) - AERON_LOGBUFFER_PARTITION_COUNT;\n            active_index = (active_index + 1) % AERON_LOGBUFFER_PARTITION_COUNT;\n            _pub->log_meta_data->term_tail_counters[active_index] = expected_term_id * (INT64_C(1) << 32);\n        }\n\n        _pub->log_meta_data->active_term_count = term_count;\n    }\n    else\n    {\n        _pub->log_meta_data->term_tail_counters[0] = initial_term_id * (INT64_C(1) << 32);\n        for (int i = 1; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n        {\n            int32_t expected_term_id = (initial_term_id + i) - AERON_LOGBUFFER_PARTITION_COUNT;\n            _pub->log_meta_data->term_tail_counters[i] = expected_term_id * (INT64_C(1) << 32);\n        }\n\n        _pub->log_meta_data->active_term_count = 0;\n    }\n\n    int64_t now_ns = aeron_clock_cached_nano_time(context->cached_clock);\n\n    aeron_logbuffer_metadata_init(\n        _pub->mapped_raw_log.log_meta_data.addr,\n        INT64_MAX,\n        0,\n        0,\n        registration_id,\n        initial_term_id,\n        (int32_t)params->mtu_length,\n        (int32_t)params->term_length,\n        (int32_t)context->file_page_size,\n        (int32_t)params->publication_window_length,\n        0,\n        0,\n        (int32_t)context->os_buffer_lengths.default_so_sndbuf,\n        (int32_t)context->os_buffer_lengths.max_so_sndbuf,\n        0,\n        (int32_t)context->os_buffer_lengths.default_so_rcvbuf,\n        (int32_t)context->os_buffer_lengths.max_so_rcvbuf,\n        (int32_t)params->max_resend,\n        session_id,\n        stream_id,\n        (int64_t)params->entity_tag,\n        (int64_t)params->response_correlation_id,\n        (int64_t)params->linger_timeout_ns,\n        (int64_t)params->untethered_window_limit_timeout_ns,\n        (int64_t)params->untethered_linger_timeout_ns,\n        (int64_t)params->untethered_resting_timeout_ns,\n        (uint8_t)false,\n        (uint8_t)params->is_response,\n        (uint8_t)false,\n        (uint8_t)false,\n        (uint8_t)params->is_sparse,\n        (uint8_t)params->signal_eos,\n        (uint8_t)params->spies_simulate_connection,\n        (uint8_t)false,\n        is_exclusive ? AERON_LOGBUFFER_TYPE_EXCLUSIVE_PUBLICATION : AERON_LOGBUFFER_TYPE_CONCURRENT_PUBLICATION);\n\n    _pub->conductor_fields.subscribable.correlation_id = registration_id;\n    _pub->conductor_fields.subscribable.array = NULL;\n    _pub->conductor_fields.subscribable.length = 0;\n    _pub->conductor_fields.subscribable.capacity = 0;\n    _pub->conductor_fields.subscribable.inactive_count = 0;\n    _pub->conductor_fields.subscribable.add_position_hook_func = aeron_ipc_publication_add_subscriber_hook;\n    _pub->conductor_fields.subscribable.remove_position_hook_func = aeron_ipc_publication_remove_subscriber_hook;\n    _pub->conductor_fields.subscribable.clientd = _pub;\n    _pub->conductor_fields.managed_resource.registration_id = registration_id;\n    _pub->conductor_fields.managed_resource.clientd = _pub;\n    _pub->conductor_fields.managed_resource.handle_event = aeron_ipc_publication_handle_managed_resource_event;\n    _pub->conductor_fields.has_reached_end_of_life = false;\n    _pub->conductor_fields.response_correlation_id = params->response_correlation_id;\n    _pub->conductor_fields.trip_limit = 0;\n    _pub->conductor_fields.time_of_last_consumer_position_change_ns = now_ns;\n    _pub->conductor_fields.state = AERON_IPC_PUBLICATION_STATE_ACTIVE;\n    _pub->conductor_fields.refcnt = 1;\n    _pub->session_id = session_id;\n    _pub->stream_id = stream_id;\n    _pub->pub_lmt_position.counter_id = pub_lmt_position->counter_id;\n    _pub->pub_lmt_position.value_addr = pub_lmt_position->value_addr;\n    _pub->pub_pos_position.counter_id = pub_pos_position->counter_id;\n    _pub->pub_pos_position.value_addr = pub_pos_position->value_addr;\n    _pub->initial_term_id = initial_term_id;\n    _pub->starting_term_id = params->has_position ? params->term_id : initial_term_id;\n    _pub->starting_term_offset = params->has_position ? params->term_offset : 0;\n    _pub->position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes((int32_t)params->term_length);\n    _pub->term_window_length = params->publication_window_length;\n    _pub->trip_gain = _pub->term_window_length / 8;\n    _pub->unblock_timeout_ns = (int64_t)context->publication_unblock_timeout_ns;\n    _pub->untethered_window_limit_timeout_ns = (int64_t)params->untethered_window_limit_timeout_ns;\n    _pub->untethered_linger_timeout_ns = params->untethered_linger_timeout_ns;\n    _pub->untethered_resting_timeout_ns = (int64_t)params->untethered_resting_timeout_ns;\n    _pub->liveness_timeout_ns = (int64_t)context->image_liveness_timeout_ns;\n    _pub->is_exclusive = is_exclusive;\n    _pub->in_cool_down = false;\n    _pub->cool_down_expire_time_ns = 0;\n\n    _pub->conductor_fields.consumer_position = aeron_ipc_publication_producer_position(_pub);\n    _pub->conductor_fields.last_consumer_position = _pub->conductor_fields.consumer_position;\n    _pub->conductor_fields.clean_position = _pub->conductor_fields.consumer_position;\n\n    _pub->unblocked_publications_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_UNBLOCKED_PUBLICATIONS);\n    _pub->publications_revoked_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_PUBLICATIONS_REVOKED);\n\n    *publication = _pub;\n\n    return 0;\n}\n\nvoid aeron_ipc_publication_close(aeron_counters_manager_t *counters_manager, aeron_ipc_publication_t *publication)\n{\n    if (NULL != publication)\n    {\n        aeron_subscribable_t *subscribable = &publication->conductor_fields.subscribable;\n\n        aeron_counters_manager_free(counters_manager, publication->pub_lmt_position.counter_id);\n        aeron_counters_manager_free(counters_manager, publication->pub_pos_position.counter_id);\n\n        for (size_t i = 0, length = subscribable->length; i < length; i++)\n        {\n            aeron_counters_manager_free(counters_manager, subscribable->array[i].counter_id);\n        }\n        aeron_free(subscribable->array);\n\n        aeron_free(publication->channel);\n    }\n}\n\nbool aeron_ipc_publication_free(aeron_ipc_publication_t *publication)\n{\n    if (NULL == publication)\n    {\n        return true;\n    }\n\n    if (!publication->raw_log_free_func(&publication->mapped_raw_log, publication->log_file_name))\n    {\n        return false;\n    }\n\n    aeron_counter_get_and_add_release(\n        publication->mapped_bytes_counter, -((int64_t) publication->mapped_raw_log.mapped_file.length));\n\n    aeron_free(publication->log_file_name);\n    aeron_free(publication);\n\n    return true;\n}\n\nvoid aeron_ipc_publication_reject(\n    aeron_ipc_publication_t *publication,\n    int64_t position,\n    int32_t reason_length,\n    const char *reason,\n    aeron_driver_conductor_t *conductor,\n    int64_t now_ns)\n{\n    {\n        uint8_t buffer[AERON_COMMAND_PUBLICATION_ERROR_MAX_LENGTH];\n        aeron_command_publication_error_t *error = (aeron_command_publication_error_t *)buffer;\n        int32_t error_length = reason_length <= AERON_ERROR_MAX_TEXT_LENGTH ? reason_length : AERON_ERROR_MAX_TEXT_LENGTH;\n\n        struct sockaddr_storage src_addr_storage;\n        memset(&src_addr_storage, 0, sizeof(struct sockaddr_storage));\n\n        struct sockaddr_in *src_addr_in = (struct sockaddr_in *)&src_addr_storage;\n        src_addr_in->sin_family = AF_INET;\n        src_addr_in->sin_port = 0;\n        src_addr_in->sin_addr.s_addr = INADDR_LOOPBACK;\n\n        error->registration_id = publication->conductor_fields.managed_resource.registration_id;\n        error->destination_registration_id = AERON_NULL_VALUE;\n        error->session_id = publication->session_id;\n        error->stream_id = publication->stream_id;\n        error->receiver_id = AERON_NULL_VALUE;\n        error->group_tag =  AERON_NULL_VALUE;\n        memcpy(&error->src_address, &src_addr_storage, sizeof(struct sockaddr_storage));\n        error->error_code = AERON_ERROR_CODE_IMAGE_REJECTED;\n        error->error_length = error_length;\n        memcpy(error->error_text, reason, error_length);\n        aeron_str_null_terminate(error->error_text, error_length);\n\n        aeron_driver_conductor_on_publication_error(conductor, error);\n    }\n\n    if (!publication->in_cool_down)\n    {\n        AERON_SET_RELEASE(publication->log_meta_data->is_connected, 0);\n\n        aeron_driver_conductor_unlink_ipc_subscriptions(conductor, publication);\n\n        {\n            aeron_subscribable_t *subscribable = &publication->conductor_fields.subscribable;\n\n            for (size_t i = 0, length = subscribable->length; i < length; i++)\n            {\n                aeron_counters_manager_free(&conductor->counters_manager, subscribable->array[i].counter_id);\n            }\n\n            aeron_free(subscribable->array);\n            subscribable->array = NULL;\n            subscribable->length = 0;\n            subscribable->capacity = 0;\n        }\n\n        publication->in_cool_down = true;\n    }\n\n    publication->cool_down_expire_time_ns = now_ns + publication->liveness_timeout_ns;\n}\n\nint aeron_ipc_publication_update_pub_pos_and_lmt(aeron_ipc_publication_t *publication)\n{\n    int work_count = 0;\n\n    if (AERON_IPC_PUBLICATION_STATE_ACTIVE == publication->conductor_fields.state)\n    {\n        const int64_t producer_position = aeron_ipc_publication_producer_position(publication);\n        int64_t consumer_position = publication->conductor_fields.consumer_position;\n\n        aeron_counter_set_release(publication->pub_pos_position.value_addr, producer_position);\n\n        if (aeron_driver_subscribable_has_working_positions(&publication->conductor_fields.subscribable))\n        {\n            int64_t min_sub_pos = INT64_MAX;\n            int64_t max_sub_pos = consumer_position;\n\n            for (size_t i = 0, length = publication->conductor_fields.subscribable.length; i < length; i++)\n            {\n                aeron_tetherable_position_t *tetherable_position = &publication->conductor_fields.subscribable.array[i];\n\n                if (aeron_driver_subscribable_is_active_state(tetherable_position->state))\n                {\n                    int64_t position = aeron_counter_get_acquire(tetherable_position->value_addr);\n\n                    min_sub_pos = position < min_sub_pos ? position : min_sub_pos;\n                    max_sub_pos = position > max_sub_pos ? position : max_sub_pos;\n                }\n            }\n\n            int64_t new_limit_position = min_sub_pos + publication->term_window_length;\n            if (new_limit_position > publication->conductor_fields.trip_limit)\n            {\n                aeron_ipc_publication_clean_buffer(publication, min_sub_pos);\n                aeron_counter_set_release(publication->pub_lmt_position.value_addr, new_limit_position);\n                publication->conductor_fields.trip_limit = new_limit_position + publication->trip_gain;\n                work_count++;\n            }\n\n            publication->conductor_fields.consumer_position = max_sub_pos;\n        }\n        else if (*publication->pub_lmt_position.value_addr > consumer_position)\n        {\n            aeron_counter_set_release(publication->pub_lmt_position.value_addr, consumer_position);\n            publication->conductor_fields.trip_limit = consumer_position;\n            aeron_ipc_publication_clean_buffer(publication, consumer_position);\n            work_count++;\n        }\n    }\n\n    return work_count;\n}\n\nvoid aeron_ipc_publication_clean_buffer(aeron_ipc_publication_t *publication, int64_t position)\n{\n    int64_t clean_position = publication->conductor_fields.clean_position;\n    if (position > clean_position)\n    {\n        size_t dirty_index = aeron_logbuffer_index_by_position(clean_position, publication->position_bits_to_shift);\n        size_t bytes_to_clean = (size_t)(position - clean_position);\n        size_t term_length = publication->mapped_raw_log.term_length;\n        size_t term_offset = (size_t)(clean_position & (term_length - 1));\n        size_t bytes_left_in_term = term_length - term_offset;\n        size_t length = bytes_to_clean < bytes_left_in_term ? bytes_to_clean : bytes_left_in_term;\n\n        memset(\n            publication->mapped_raw_log.term_buffers[dirty_index].addr + term_offset + sizeof(int64_t),\n            0,\n            length - sizeof(int64_t));\n\n        volatile uint64_t *ptr = (volatile uint64_t *)(publication->mapped_raw_log.term_buffers[dirty_index].addr + term_offset);\n        AERON_SET_RELEASE(*ptr, (uint64_t)0);\n\n        publication->conductor_fields.clean_position = (int64_t)(clean_position + length);\n    }\n}\n\nvoid aeron_ipc_publication_check_untethered_subscriptions(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_t *publication, int64_t now_ns)\n{\n    int64_t consumer_position = publication->conductor_fields.consumer_position;\n    int64_t term_window_length = publication->term_window_length;\n    int64_t untethered_window_limit = (consumer_position - term_window_length) + (term_window_length / 4);\n\n    aeron_subscribable_t *subscribable = &publication->conductor_fields.subscribable;\n    for (size_t i = 0, length = subscribable->length; i < length; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &subscribable->array[i];\n\n        if (tetherable_position->is_tether)\n        {\n            tetherable_position->time_of_last_update_ns = now_ns;\n        }\n        else\n        {\n            switch (tetherable_position->state)\n            {\n                case AERON_SUBSCRIPTION_TETHER_ACTIVE:\n                    if (aeron_counter_get_acquire(tetherable_position->value_addr) > untethered_window_limit)\n                    {\n                        tetherable_position->time_of_last_update_ns = now_ns;\n                    }\n                    else if (now_ns > (tetherable_position->time_of_last_update_ns + publication->untethered_window_limit_timeout_ns))\n                    {\n                        aeron_driver_conductor_on_unavailable_image(\n                            conductor,\n                            publication->conductor_fields.managed_resource.registration_id,\n                            tetherable_position->subscription_registration_id,\n                            publication->stream_id,\n                            AERON_IPC_CHANNEL,\n                            AERON_IPC_CHANNEL_LEN);\n\n                        aeron_driver_subscribable_state(\n                            subscribable,\n                            tetherable_position,\n                            AERON_SUBSCRIPTION_TETHER_LINGER,\n                            now_ns,\n                            publication->stream_id,\n                            publication->session_id,\n                            publication->log.untethered_subscription_state_change);\n                    }\n                    break;\n\n                case AERON_SUBSCRIPTION_TETHER_LINGER:\n                    if (now_ns > (tetherable_position->time_of_last_update_ns + publication->untethered_linger_timeout_ns))\n                    {\n                        if (tetherable_position->is_rejoin)\n                        {\n                            aeron_driver_subscribable_state(\n                                subscribable,\n                                tetherable_position,\n                                AERON_SUBSCRIPTION_TETHER_RESTING,\n                                now_ns,\n                                publication->stream_id,\n                                publication->session_id,\n                                publication->log.untethered_subscription_state_change);\n                        }\n                        else\n                        {\n                            aeron_driver_subscribable_state(\n                                subscribable,\n                                tetherable_position,\n                                AERON_SUBSCRIPTION_TETHER_CLOSED,\n                                now_ns,\n                                publication->stream_id,\n                                publication->session_id,\n                                publication->log.untethered_subscription_state_change);\n\n                            if (0 == aeron_counters_manager_free(&conductor->counters_manager, tetherable_position->counter_id))\n                            {\n                                tetherable_position->counter_id = AERON_NULL_COUNTER_ID; // prevent double free\n                            }\n                        }\n                    }\n                    break;\n\n                case AERON_SUBSCRIPTION_TETHER_RESTING:\n                    if (now_ns > (tetherable_position->time_of_last_update_ns + publication->untethered_resting_timeout_ns))\n                    {\n                        int64_t join_position = aeron_ipc_publication_join_position(publication);\n                        aeron_counter_set_release(tetherable_position->value_addr, join_position);\n                        aeron_driver_conductor_on_available_image(\n                            conductor,\n                            publication->conductor_fields.managed_resource.registration_id,\n                            publication->stream_id,\n                            publication->session_id,\n                            publication->log_file_name,\n                            publication->log_file_name_length,\n                            tetherable_position->counter_id,\n                            tetherable_position->subscription_registration_id,\n                            AERON_IPC_CHANNEL,\n                            AERON_IPC_CHANNEL_LEN);\n\n                        aeron_driver_subscribable_state(\n                            subscribable,\n                            tetherable_position,\n                            AERON_SUBSCRIPTION_TETHER_ACTIVE,\n                            now_ns,\n                            publication->stream_id,\n                            publication->session_id,\n                            publication->log.untethered_subscription_state_change);\n                    }\n                    break;\n\n                case AERON_SUBSCRIPTION_TETHER_CLOSED:\n                    break;\n            }\n        }\n    }\n}\n\nvoid aeron_ipc_publication_check_cooldown_status(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_t *publication, int64_t now_ns)\n{\n    if (publication->in_cool_down && publication->cool_down_expire_time_ns < now_ns)\n    {\n        publication->in_cool_down = false;\n\n        aeron_driver_conductor_link_ipc_subscriptions(conductor, publication);\n\n        publication->cool_down_expire_time_ns = 0;\n    }\n}\n\nvoid aeron_ipc_publication_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_t *publication, int64_t now_ns, int64_t now_ms)\n{\n    switch (publication->conductor_fields.state)\n    {\n        case AERON_IPC_PUBLICATION_STATE_ACTIVE:\n        {\n            uint8_t is_publication_revoked;\n\n            AERON_GET_ACQUIRE(is_publication_revoked, publication->log_meta_data->is_publication_revoked);\n\n            if (is_publication_revoked)\n            {\n                int64_t revoked_position = aeron_ipc_publication_producer_position(publication);\n                aeron_counter_set_release(publication->pub_lmt_position.value_addr, revoked_position);\n                AERON_SET_RELEASE(publication->log_meta_data->end_of_stream_position, revoked_position);\n                AERON_SET_RELEASE(publication->log_meta_data->is_connected, 0);\n\n                for (size_t i = 0, size = conductor->ipc_subscriptions.length; i < size; i++)\n                {\n                    aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[i];\n\n                    if (aeron_driver_conductor_is_subscribable_linked(\n                        link,\n                        &publication->conductor_fields.subscribable))\n                    {\n                        aeron_driver_conductor_on_unavailable_image(\n                            conductor,\n                            publication->conductor_fields.managed_resource.registration_id,\n                            link->registration_id,\n                            publication->stream_id,\n                            AERON_IPC_CHANNEL,\n                            AERON_IPC_CHANNEL_LEN);\n                    }\n                }\n\n                publication->conductor_fields.state = AERON_IPC_PUBLICATION_STATE_LINGER;\n\n                aeron_driver_publication_revoke_func_t publication_revoke = publication->log.publication_revoke;\n                if (NULL != publication_revoke)\n                {\n                    publication_revoke(\n                        revoked_position,\n                        publication->session_id,\n                        publication->stream_id,\n                        publication->channel_length,\n                        publication->channel);\n                }\n\n                aeron_counter_increment_release(publication->publications_revoked_counter);\n            }\n            else\n            {\n                aeron_ipc_publication_check_untethered_subscriptions(conductor, publication, now_ns);\n                AERON_SET_RELEASE(\n                    publication->log_meta_data->is_connected,\n                    aeron_driver_subscribable_working_position_count(&publication->conductor_fields.subscribable) > 0);\n                const int64_t producer_position = aeron_ipc_publication_producer_position(publication);\n                aeron_counter_set_release(publication->pub_pos_position.value_addr, producer_position);\n\n                if (!publication->is_exclusive)\n                {\n                    aeron_ipc_publication_check_for_blocked_publisher(publication, producer_position, now_ns);\n                }\n\n                aeron_ipc_publication_check_cooldown_status(conductor, publication, now_ns);\n            }\n            break;\n        }\n\n        case AERON_IPC_PUBLICATION_STATE_DRAINING:\n        {\n            const int64_t producer_position = aeron_ipc_publication_producer_position(publication);\n            aeron_counter_set_release(publication->pub_pos_position.value_addr, producer_position);\n\n            if (aeron_ipc_publication_is_drained(publication))\n            {\n                publication->conductor_fields.state = AERON_IPC_PUBLICATION_STATE_LINGER;\n                publication->conductor_fields.managed_resource.time_of_last_state_change_ns = now_ns;\n\n                for (size_t i = 0, size = conductor->ipc_subscriptions.length; i < size; i++)\n                {\n                    aeron_subscription_link_t *link = &conductor->ipc_subscriptions.array[i];\n\n                    if (aeron_driver_conductor_is_subscribable_linked(\n                        link,\n                        &publication->conductor_fields.subscribable))\n                    {\n                        aeron_driver_conductor_on_unavailable_image(\n                            conductor,\n                            publication->conductor_fields.managed_resource.registration_id,\n                            link->registration_id,\n                            publication->stream_id,\n                            AERON_IPC_CHANNEL,\n                            AERON_IPC_CHANNEL_LEN);\n                    }\n                }\n            }\n            else if (aeron_logbuffer_unblocker_unblock(\n                publication->mapped_raw_log.term_buffers,\n                publication->log_meta_data,\n                publication->conductor_fields.consumer_position))\n            {\n                aeron_counter_increment_release(publication->unblocked_publications_counter);\n            }\n            break;\n        }\n\n        case AERON_IPC_PUBLICATION_STATE_LINGER:\n        {\n            if (publication->conductor_fields.refcnt <= 0)\n            {\n                publication->conductor_fields.has_reached_end_of_life = true;\n            }\n            break;\n        }\n\n        case AERON_IPC_PUBLICATION_STATE_DONE:\n            break;\n    }\n}\n\nvoid aeron_ipc_publication_handle_managed_resource_event(aeron_driver_managed_resource_event_t event, void *clientd)\n{\n    aeron_ipc_publication_t *publication = (aeron_ipc_publication_t *)clientd;\n\n    switch(event)\n    {\n        case AERON_DRIVER_MANAGED_RESOURCE_EVENT_INCREF:\n        {\n            publication->conductor_fields.refcnt++;\n            break;\n        }\n\n        case AERON_DRIVER_MANAGED_RESOURCE_EVENT_DECREF:\n        {\n            int32_t ref_count = --publication->conductor_fields.refcnt;\n\n            if (0 == ref_count)\n            {\n                int64_t producer_position = aeron_ipc_publication_producer_position(publication);\n\n                if (aeron_counter_get_plain(publication->pub_lmt_position.value_addr) > producer_position)\n                {\n                    aeron_counter_set_release(publication->pub_lmt_position.value_addr, producer_position);\n                }\n\n                AERON_SET_RELEASE(publication->log_meta_data->end_of_stream_position, producer_position);\n\n                uint8_t is_publication_revoked;\n\n                AERON_GET_ACQUIRE(is_publication_revoked, publication->log_meta_data->is_publication_revoked);\n\n                if (!is_publication_revoked)\n                {\n                    publication->conductor_fields.state = AERON_IPC_PUBLICATION_STATE_DRAINING;\n                }\n            }\n            break;\n        }\n\n        case AERON_DRIVER_MANAGED_RESOURCE_EVENT_REVOKE:\n        {\n            AERON_SET_RELEASE(publication->log_meta_data->is_publication_revoked, true);\n            break;\n        }\n    }\n}\n\nvoid aeron_ipc_publication_check_for_blocked_publisher(\n    aeron_ipc_publication_t *publication, int64_t producer_position, int64_t now_ns)\n{\n    int64_t consumer_position = publication->conductor_fields.consumer_position;\n\n    if (consumer_position == publication->conductor_fields.last_consumer_position &&\n        aeron_ipc_publication_is_possibly_blocked(publication, producer_position, consumer_position))\n    {\n        if (now_ns >\n            (publication->conductor_fields.time_of_last_consumer_position_change_ns + publication->unblock_timeout_ns))\n        {\n            if (aeron_logbuffer_unblocker_unblock(\n                publication->mapped_raw_log.term_buffers,\n                publication->log_meta_data,\n                publication->conductor_fields.consumer_position))\n            {\n                aeron_counter_increment_release(publication->unblocked_publications_counter);\n            }\n        }\n    }\n    else\n    {\n        publication->conductor_fields.time_of_last_consumer_position_change_ns = now_ns;\n        publication->conductor_fields.last_consumer_position = publication->conductor_fields.consumer_position;\n    }\n}\n\nextern void aeron_ipc_publication_add_subscriber_hook(void *clientd, volatile int64_t *value_addr);\n\nextern void aeron_ipc_publication_remove_subscriber_hook(void *clientd, volatile int64_t *value_addr);\n\nextern bool aeron_ipc_publication_is_possibly_blocked(\n    aeron_ipc_publication_t *publication, int64_t producer_position, int64_t consumer_position);\n\nextern int64_t aeron_ipc_publication_producer_position(aeron_ipc_publication_t *publication);\n\nextern int64_t aeron_ipc_publication_join_position(aeron_ipc_publication_t *publication);\n\nextern bool aeron_ipc_publication_has_reached_end_of_life(aeron_ipc_publication_t *publication);\n\nextern bool aeron_ipc_publication_is_drained(aeron_ipc_publication_t *publication);\n\nextern bool aeron_ipc_publication_is_accepting_subscriptions(aeron_ipc_publication_t *publication);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_ipc_publication.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_IPC_PUBLICATION_H\n#define AERON_IPC_PUBLICATION_H\n\n#include \"util/aeron_bitutil.h\"\n#include \"uri/aeron_driver_uri.h\"\n#include \"util/aeron_fileutil.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_system_counters.h\"\n\ntypedef enum aeron_ipc_publication_state_enum\n{\n    AERON_IPC_PUBLICATION_STATE_ACTIVE,\n    AERON_IPC_PUBLICATION_STATE_DRAINING,\n    AERON_IPC_PUBLICATION_STATE_LINGER,\n    AERON_IPC_PUBLICATION_STATE_DONE\n}\naeron_ipc_publication_state_t;\n\ntypedef struct aeron_ipc_publication_stct\n{\n    aeron_mapped_raw_log_t mapped_raw_log;\n    aeron_logbuffer_metadata_t *log_meta_data;\n    aeron_position_t pub_lmt_position;\n    aeron_position_t pub_pos_position;\n\n    struct aeron_ipc_publication_conductor_fields_stct\n    {\n        bool has_reached_end_of_life;\n        aeron_ipc_publication_state_t state;\n        int32_t refcnt;\n        aeron_driver_managed_resource_t managed_resource;\n        aeron_subscribable_t subscribable;\n        int64_t trip_limit;\n        int64_t clean_position;\n        int64_t consumer_position;\n        int64_t last_consumer_position;\n        int64_t time_of_last_consumer_position_change_ns;\n        int64_t response_correlation_id;\n    }\n    conductor_fields;\n\n    size_t position_bits_to_shift;\n    int64_t term_window_length;\n    int64_t trip_gain;\n    int64_t unblock_timeout_ns;\n    int64_t untethered_window_limit_timeout_ns;\n    int64_t untethered_linger_timeout_ns;\n    int64_t untethered_resting_timeout_ns;\n    int64_t liveness_timeout_ns;\n    int32_t initial_term_id;\n    bool is_exclusive;\n    bool in_cool_down;\n    int64_t cool_down_expire_time_ns;\n    int64_t tag;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t starting_term_id;\n    size_t starting_term_offset;\n    int32_t channel_length;\n    char *channel;\n    size_t log_file_name_length;\n    char *log_file_name;\n\n    aeron_raw_log_close_func_t raw_log_close_func;\n    aeron_raw_log_free_func_t raw_log_free_func;\n    struct\n    {\n        aeron_untethered_subscription_state_change_func_t untethered_subscription_state_change;\n        aeron_driver_publication_revoke_func_t publication_revoke;\n    } log;\n\n    volatile int64_t *unblocked_publications_counter;\n    volatile int64_t *publications_revoked_counter;\n    volatile int64_t *mapped_bytes_counter;\n}\naeron_ipc_publication_t;\n\nint aeron_ipc_publication_create(\n    aeron_ipc_publication_t **publication,\n    aeron_driver_context_t *context,\n    int32_t session_id,\n    int32_t stream_id,\n    int64_t registration_id,\n    aeron_position_t *pub_pos_position,\n    aeron_position_t *pub_lmt_position,\n    int32_t initial_term_id,\n    aeron_driver_uri_publication_params_t *params,\n    bool is_exclusive,\n    aeron_system_counters_t *system_counters,\n    size_t channel_length,\n    const char *channel);\n\nvoid aeron_ipc_publication_close(aeron_counters_manager_t *counters_manager, aeron_ipc_publication_t *publication);\n\nbool aeron_ipc_publication_free(aeron_ipc_publication_t *publication);\n\nint aeron_ipc_publication_update_pub_pos_and_lmt(aeron_ipc_publication_t *publication);\n\nvoid aeron_ipc_publication_clean_buffer(aeron_ipc_publication_t *publication, int64_t position);\n\nvoid aeron_ipc_publication_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_ipc_publication_t *publication, int64_t now_ns, int64_t now_ms);\n\nvoid aeron_ipc_publication_reject(\n    aeron_ipc_publication_t *publication,\n    int64_t position,\n    int32_t reason_length,\n    const char *reason,\n    aeron_driver_conductor_t *conductor,\n    int64_t now_ns);\n\nvoid aeron_ipc_publication_check_for_blocked_publisher(\n    aeron_ipc_publication_t *publication, int64_t producer_position, int64_t now_ns);\n\ninline void aeron_ipc_publication_add_subscriber_hook(void *clientd, volatile int64_t *value_addr)\n{\n    aeron_ipc_publication_t *publication = (aeron_ipc_publication_t *)clientd;\n    AERON_SET_RELEASE(publication->log_meta_data->is_connected, 1);\n}\n\ninline void aeron_ipc_publication_remove_subscriber_hook(void *clientd, volatile int64_t *value_addr)\n{\n    aeron_ipc_publication_t *publication = (aeron_ipc_publication_t *)clientd;\n\n    if (1 == aeron_driver_subscribable_working_position_count(&publication->conductor_fields.subscribable))\n    {\n        AERON_SET_RELEASE(publication->log_meta_data->is_connected, 0);\n    }\n}\n\ninline bool aeron_ipc_publication_is_possibly_blocked(\n    aeron_ipc_publication_t *publication, int64_t producer_position, int64_t consumer_position)\n{\n    int32_t producer_term_count;\n\n    AERON_GET_ACQUIRE(producer_term_count, publication->log_meta_data->active_term_count);\n    const int32_t expected_term_count = (int32_t)(consumer_position >> publication->position_bits_to_shift);\n\n    if (producer_term_count != expected_term_count)\n    {\n        return true;\n    }\n\n    return producer_position > consumer_position;\n}\n\ninline int64_t aeron_ipc_publication_producer_position(aeron_ipc_publication_t *publication)\n{\n    int64_t raw_tail;\n\n    AERON_LOGBUFFER_RAWTAIL_VOLATILE(raw_tail, publication->log_meta_data);\n\n    return aeron_logbuffer_compute_position(\n        aeron_logbuffer_term_id(raw_tail),\n        aeron_logbuffer_term_offset(raw_tail, (int32_t)publication->mapped_raw_log.term_length),\n        publication->position_bits_to_shift,\n        publication->initial_term_id);\n}\n\ninline int64_t aeron_ipc_publication_join_position(aeron_ipc_publication_t *publication)\n{\n    int64_t position = publication->conductor_fields.consumer_position;\n\n    for (size_t i = 0, length = publication->conductor_fields.subscribable.length; i < length; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &publication->conductor_fields.subscribable.array[i];\n\n        if (aeron_driver_subscribable_is_active_state(tetherable_position->state))\n        {\n            const int64_t sub_pos = aeron_counter_get_acquire(tetherable_position->value_addr);\n\n            if (sub_pos < position)\n            {\n                position = sub_pos;\n            }\n        }\n    }\n\n    return position;\n}\n\ninline bool aeron_ipc_publication_has_reached_end_of_life(aeron_ipc_publication_t *publication)\n{\n    return publication->conductor_fields.has_reached_end_of_life;\n}\n\ninline bool aeron_ipc_publication_is_drained(aeron_ipc_publication_t *publication)\n{\n    int64_t producer_position = aeron_ipc_publication_producer_position(publication);\n\n    for (size_t i = 0, length = publication->conductor_fields.subscribable.length; i < length; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &publication->conductor_fields.subscribable.array[i];\n\n        if (aeron_driver_subscribable_is_active_state(tetherable_position->state))\n        {\n            const int64_t sub_pos = aeron_counter_get_acquire(tetherable_position->value_addr);\n\n            if (sub_pos < producer_position)\n            {\n                return false;\n            }\n        }\n    }\n\n    return true;\n}\n\ninline bool aeron_ipc_publication_is_accepting_subscriptions(aeron_ipc_publication_t *publication)\n{\n    return !publication->in_cool_down &&\n        (AERON_IPC_PUBLICATION_STATE_ACTIVE == publication->conductor_fields.state ||\n        (AERON_IPC_PUBLICATION_STATE_DRAINING == publication->conductor_fields.state &&\n            !aeron_ipc_publication_is_drained(publication)));\n}\n\n#endif //AERON_IPC_PUBLICATION_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_loss_detector.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <math.h>\n#include <stdlib.h>\n#include \"aeronmd.h\"\n#include \"aeron_windows.h\"\n#include \"aeron_loss_detector.h\"\n\nint aeron_loss_detector_init(\n    aeron_loss_detector_t *detector,\n    aeron_feedback_delay_generator_state_t *feedback_delay_state,\n    aeron_term_gap_scanner_on_gap_detected_func_t on_gap_detected,\n    void *on_gap_detected_clientd)\n{\n    detector->on_gap_detected = on_gap_detected;\n    detector->on_gap_detected_clientd = on_gap_detected_clientd;\n    detector->expiry_ns = AERON_LOSS_DETECTOR_TIMER_INACTIVE;\n    detector->active_gap.term_offset = -1;\n    detector->scanned_gap.term_offset = -1;\n    detector->feedback_delay_state = feedback_delay_state;\n\n    return 0;\n}\n\nint32_t aeron_loss_detector_scan(\n    aeron_loss_detector_t *detector,\n    bool *loss_found,\n    const uint8_t *buffer,\n    int64_t rebuild_position,\n    int64_t hwm_position,\n    int64_t now_ns,\n    size_t term_length_mask,\n    size_t position_bits_to_shift,\n    int32_t initial_term_id)\n{\n    *loss_found = false;\n    int32_t rebuild_offset = (int32_t)(rebuild_position & term_length_mask);\n\n    if (rebuild_position < hwm_position)\n    {\n        const int32_t rebuild_term_count = (int32_t)(rebuild_position >> position_bits_to_shift);\n        const int32_t hwm_term_count = (int32_t)(hwm_position >> position_bits_to_shift);\n\n        const int32_t rebuild_term_id = aeron_add_wrap_i32(initial_term_id, rebuild_term_count);\n        const int32_t hwm_term_offset = (int32_t)(hwm_position & term_length_mask);\n        const int32_t limit_offset = rebuild_term_count == hwm_term_count ?\n            hwm_term_offset : (int32_t)(term_length_mask + 1);\n\n        rebuild_offset = aeron_term_gap_scanner_scan_for_gap(\n            buffer,\n            rebuild_term_id,\n            rebuild_offset,\n            limit_offset,\n            aeron_loss_detector_on_gap,\n            detector);\n\n        if (rebuild_offset < limit_offset)\n        {\n            if (!aeron_loss_detector_gaps_match(detector))\n            {\n                aeron_loss_detector_activate_gap(detector, now_ns);\n                *loss_found = true;\n            }\n\n            aeron_loss_detector_check_timer_expiry(detector, now_ns);\n        }\n    }\n\n    return rebuild_offset;\n}\n\nint aeron_feedback_delay_state_init(\n    aeron_feedback_delay_generator_state_t *state,\n    aeron_feedback_delay_generator_func_t delay_generator,\n    int64_t delay_ns,\n    int64_t retry_ns,\n    size_t multicast_group_size)\n{\n    static bool is_seeded = false;\n    double lambda = log((double)multicast_group_size) + 1;\n    double max_backoff_T = (double)delay_ns;\n\n    state->static_delay.delay_ns = delay_ns;\n    state->static_delay.retry_ns = retry_ns;\n\n    state->optimal_delay.rand_max = lambda / max_backoff_T;\n    state->optimal_delay.base_x = lambda / (max_backoff_T * (exp(lambda) - 1));\n    state->optimal_delay.constant_t = max_backoff_T / lambda;\n    state->optimal_delay.factor_t = (exp(lambda) - 1) * (max_backoff_T / lambda);\n\n    if (!is_seeded)\n    {\n        aeron_srand48((uint64_t)aeron_nano_clock());\n        is_seeded = true;\n    }\n\n    state->delay_generator = delay_generator;\n    return 0;\n}\n\nint64_t aeron_loss_detector_nak_multicast_delay_generator(aeron_feedback_delay_generator_state_t *state, bool retry)\n{\n    const double x = (aeron_drand48() * state->optimal_delay.rand_max) + state->optimal_delay.base_x;\n\n    return (int64_t)(state->optimal_delay.constant_t * log(x * state->optimal_delay.factor_t));\n}\n\nextern int64_t aeron_loss_detector_nak_unicast_delay_generator(aeron_feedback_delay_generator_state_t *state, bool retry);\nextern void aeron_loss_detector_on_gap(void *clientd, int32_t term_id, int32_t term_offset, size_t length);\nextern bool aeron_loss_detector_gaps_match(aeron_loss_detector_t *detector);\nextern void aeron_loss_detector_activate_gap(aeron_loss_detector_t *detector, int64_t now_ns);\nextern void aeron_loss_detector_check_timer_expiry(aeron_loss_detector_t *detector, int64_t now_ns);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_loss_detector.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_LOSS_DETECTOR_H\n#define AERON_LOSS_DETECTOR_H\n\n#include \"aeron_driver_common.h\"\n#include \"concurrent/aeron_term_gap_scanner.h\"\n\ntypedef struct aeron_loss_detector_gap_stct\n{\n    int32_t term_id;\n    int32_t term_offset;\n    size_t length;\n}\naeron_loss_detector_gap_t;\n\n#define AERON_LOSS_DETECTOR_TIMER_INACTIVE (-1)\n\ntypedef struct aeron_loss_detector_stct\n{\n    aeron_term_gap_scanner_on_gap_detected_func_t on_gap_detected;\n    aeron_feedback_delay_generator_state_t *feedback_delay_state;\n    void *on_gap_detected_clientd;\n    aeron_loss_detector_gap_t scanned_gap;\n    aeron_loss_detector_gap_t active_gap;\n    int64_t expiry_ns;\n}\naeron_loss_detector_t;\n\nint aeron_loss_detector_init(\n    aeron_loss_detector_t *detector,\n    aeron_feedback_delay_generator_state_t *feedback_delay_state,\n    aeron_term_gap_scanner_on_gap_detected_func_t on_gap_detected,\n    void *on_gap_detected_clientd);\n\nint32_t aeron_loss_detector_scan(\n    aeron_loss_detector_t *detector,\n    bool *loss_found,\n    const uint8_t *buffer,\n    int64_t rebuild_position,\n    int64_t hwm_position,\n    int64_t now_ns,\n    size_t term_length_mask,\n    size_t position_bits_to_shift,\n    int32_t initial_term_id);\n\n#define AERON_LOSS_DETECTOR_NAK_UNICAST_DELAY_NS (60 * 1000 * 1000LL)\n\ninline int64_t aeron_loss_detector_nak_unicast_delay_generator(aeron_feedback_delay_generator_state_t *state, bool retry)\n{\n    if (retry)\n    {\n        return state->static_delay.retry_ns;\n    }\n    else\n    {\n        return state->static_delay.delay_ns;\n    }\n}\n\n#define AERON_LOSS_DETECTOR_NAK_MULTICAST_GROUP_SIZE (10.0)\n#define AERON_LOSS_DETECTOR_NAK_MULTICAST_MAX_BACKOFF_NS (60.0 * 1000.0 * 1000.0)\n\nint aeron_feedback_delay_state_init(\n    aeron_feedback_delay_generator_state_t *state,\n    aeron_feedback_delay_generator_func_t delay_generator,\n    int64_t delay_ns,\n    int64_t retry_ns,\n    size_t multicast_group_size);\n\nint64_t aeron_loss_detector_nak_multicast_delay_generator(aeron_feedback_delay_generator_state_t *state, bool retry);\n\ninline void aeron_loss_detector_on_gap(void *clientd, int32_t term_id, int32_t term_offset, size_t length)\n{\n    aeron_loss_detector_t *detector = (aeron_loss_detector_t *)clientd;\n\n    detector->scanned_gap.term_id = term_id;\n    detector->scanned_gap.term_offset = term_offset;\n    detector->scanned_gap.length = length;\n}\n\ninline bool aeron_loss_detector_gaps_match(aeron_loss_detector_t *detector)\n{\n    return detector->active_gap.term_id == detector->scanned_gap.term_id &&\n        detector->active_gap.term_offset == detector->scanned_gap.term_offset &&\n        detector->active_gap.length == detector->scanned_gap.length;\n}\n\ninline void aeron_loss_detector_activate_gap(aeron_loss_detector_t *detector, int64_t now_ns)\n{\n    detector->active_gap.term_id = detector->scanned_gap.term_id;\n    detector->active_gap.term_offset = detector->scanned_gap.term_offset;\n    detector->active_gap.length = detector->scanned_gap.length;\n\n    detector->expiry_ns = now_ns + detector->feedback_delay_state->delay_generator(detector->feedback_delay_state, false);\n}\n\ninline void aeron_loss_detector_check_timer_expiry(aeron_loss_detector_t *detector, int64_t now_ns)\n{\n    if (now_ns >= detector->expiry_ns)\n    {\n        detector->on_gap_detected(\n            detector->on_gap_detected_clientd,\n            detector->active_gap.term_id,\n            detector->active_gap.term_offset,\n            detector->active_gap.length);\n        detector->expiry_ns = now_ns + detector->feedback_delay_state->delay_generator(detector->feedback_delay_state, true);\n    }\n}\n\n#endif //AERON_LOSS_DETECTOR_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_min_flow_control.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <inttypes.h>\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"concurrent/aeron_logbuffer_descriptor.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"aeron_flow_control.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_driver_context.h\"\n#include \"media/aeron_udp_channel.h\"\n#include \"aeron_counters.h\"\n#include \"aeron_position.h\"\n\ntypedef struct aeron_min_flow_control_strategy_receiver_stct\n{\n    uint8_t padding_before[AERON_CACHE_LINE_LENGTH];\n    int64_t last_position;\n    int64_t last_position_plus_window;\n    int64_t time_of_last_status_message_ns;\n    int64_t receiver_id;\n    int32_t session_id;\n    int32_t stream_id;\n    bool eos_flagged;\n    uint8_t padding_after[AERON_CACHE_LINE_LENGTH];\n}\naeron_min_flow_control_strategy_receiver_t;\n\ntypedef struct aeron_min_flow_control_strategy_state_stct\n{\n    struct receiver_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_min_flow_control_strategy_receiver_t *array;\n    }\n    receivers;\n\n    volatile bool has_required_receivers;\n    int64_t receiver_timeout_ns;\n    int64_t time_of_last_setup_ns;\n    int64_t last_setup_snd_lmt;\n    int64_t group_tag;\n    int32_t group_min_size;\n    bool has_tagged_status_message_triggered_setup;\n    const aeron_udp_channel_t *channel;\n    aeron_counters_manager_t *counters_manager;\n\n    aeron_distinct_error_log_t *error_log;\n    struct\n    {\n        aeron_driver_flow_control_strategy_on_receiver_change_func_t receiver_added;\n        aeron_driver_flow_control_strategy_on_receiver_change_func_t receiver_removed;\n    } log;\n    aeron_position_t receivers_counter;\n    size_t retransmit_receiver_window_multiple;\n}\naeron_min_flow_control_strategy_state_t;\n\nint64_t aeron_min_flow_control_strategy_last_setup_snd_lmt(\n    aeron_min_flow_control_strategy_state_t *strategy_state,\n    int64_t now_ns)\n{\n    if (-1 != strategy_state->last_setup_snd_lmt)\n    {\n        if ((strategy_state->time_of_last_setup_ns + strategy_state->receiver_timeout_ns) - now_ns < 0)\n        {\n            strategy_state->last_setup_snd_lmt = -1;\n        }\n        else\n        {\n            return strategy_state->last_setup_snd_lmt;\n        }\n    }\n\n    return INT64_MAX;\n}\n\nint64_t aeron_min_flow_control_strategy_on_idle(\n    void *state, int64_t now_ns, int64_t snd_lmt, int64_t snd_pos, bool is_end_of_stream)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state;\n    int64_t min_limit_position = aeron_min_flow_control_strategy_last_setup_snd_lmt(strategy_state, now_ns);\n    size_t receiver_count = strategy_state->receivers.length;\n\n    for (int last_index = (int)receiver_count - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_min_flow_control_strategy_receiver_t *receiver = &strategy_state->receivers.array[i];\n\n        if ((receiver->time_of_last_status_message_ns + strategy_state->receiver_timeout_ns) - now_ns < 0 ||\n            receiver->eos_flagged)\n        {\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)strategy_state->receivers.array,\n                sizeof(aeron_min_flow_control_strategy_receiver_t),\n                (size_t)i,\n                (size_t)last_index);\n            last_index--;\n            receiver_count--;\n\n            aeron_driver_flow_control_strategy_on_receiver_change_func_t receiver_removed =\n                strategy_state->log.receiver_removed;\n            if (NULL != receiver_removed)\n            {\n                receiver_removed(\n                    receiver->receiver_id,\n                    receiver->session_id,\n                    receiver->stream_id,\n                    strategy_state->channel->uri_length,\n                    strategy_state->channel->original_uri,\n                    receiver_count);\n            }\n        }\n        else\n        {\n            min_limit_position = receiver->last_position_plus_window < min_limit_position ?\n                receiver->last_position_plus_window : min_limit_position;\n        }\n    }\n\n    if (receiver_count != strategy_state->receivers.length)\n    {\n        strategy_state->receivers.length = receiver_count;\n        bool has_required_receivers = receiver_count >= (size_t)strategy_state->group_min_size;\n        AERON_SET_RELEASE(strategy_state->has_required_receivers, has_required_receivers);\n        aeron_counter_set_release(\n            strategy_state->receivers_counter.value_addr, (int64_t) strategy_state->receivers.length);\n    }\n\n    return strategy_state->receivers.length < (size_t)strategy_state->group_min_size ||\n        strategy_state->receivers.length == 0 ? snd_lmt : min_limit_position;\n}\n\nint64_t aeron_min_flow_control_strategy_process_sm(\n    aeron_min_flow_control_strategy_state_t *strategy_state,\n    aeron_status_message_header_t *status_message_header,\n    int64_t snd_lmt,\n    int32_t initial_term_id,\n    size_t position_bits_to_shift,\n    int64_t now_ns,\n    bool matches_tag)\n{\n    const int64_t position = aeron_logbuffer_compute_position(\n        status_message_header->consumption_term_id,\n        status_message_header->consumption_term_offset,\n        position_bits_to_shift,\n        initial_term_id);\n    const int64_t window_length = status_message_header->receiver_window;\n    const int64_t receiver_id = status_message_header->receiver_id;\n    const bool eos_flagged = status_message_header->frame_header.flags & AERON_STATUS_MESSAGE_HEADER_EOS_FLAG;\n    int64_t position_plus_window = position + window_length;\n\n    bool is_existing = false;\n    int64_t min_position = aeron_min_flow_control_strategy_last_setup_snd_lmt(strategy_state, now_ns);\n\n    for (size_t i = 0; i < strategy_state->receivers.length; i++)\n    {\n        aeron_min_flow_control_strategy_receiver_t *receiver = &strategy_state->receivers.array[i];\n\n        if (matches_tag && receiver_id == receiver->receiver_id)\n        {\n            receiver->eos_flagged = eos_flagged;\n            receiver->last_position = position > receiver->last_position ? position : receiver->last_position;\n            receiver->last_position_plus_window = position + window_length;\n            receiver->time_of_last_status_message_ns = now_ns;\n            is_existing = true;\n        }\n\n        min_position = receiver->last_position_plus_window < min_position ?\n            receiver->last_position_plus_window : min_position;\n    }\n\n    if (!is_existing &&\n        !eos_flagged &&\n        matches_tag &&\n        (0 == strategy_state->receivers.length || position_plus_window >= min_position - window_length))\n    {\n        int ensure_capacity_result = 0;\n        AERON_ARRAY_ENSURE_CAPACITY(\n            ensure_capacity_result, strategy_state->receivers, aeron_min_flow_control_strategy_receiver_t)\n\n        if (ensure_capacity_result >= 0)\n        {\n            const size_t receivers_length = strategy_state->receivers.length;\n            aeron_min_flow_control_strategy_receiver_t *receiver = &strategy_state->receivers.array[receivers_length];\n            strategy_state->receivers.length = receivers_length + 1;\n\n            receiver->last_position = position;\n            receiver->last_position_plus_window = position + window_length;\n            receiver->time_of_last_status_message_ns = now_ns;\n            receiver->receiver_id = receiver_id;\n            receiver->session_id = status_message_header->session_id;\n            receiver->stream_id = status_message_header->stream_id;\n            receiver->eos_flagged = false;\n\n            min_position = position_plus_window < min_position ? position_plus_window : min_position;\n\n            bool has_required_receivers = strategy_state->receivers.length >= (size_t)strategy_state->group_min_size;\n            AERON_SET_RELEASE(strategy_state->has_required_receivers, has_required_receivers);\n\n            strategy_state->last_setup_snd_lmt = -1;\n\n            aeron_driver_flow_control_strategy_on_receiver_change_func_t receiver_added =\n                strategy_state->log.receiver_added;\n            if (NULL != receiver_added)\n            {\n                receiver_added(\n                    receiver->receiver_id,\n                    receiver->session_id,\n                    receiver->stream_id,\n                    strategy_state->channel->uri_length,\n                    strategy_state->channel->original_uri,\n                    strategy_state->receivers.length);\n            }\n\n            aeron_counter_set_release(\n                strategy_state->receivers_counter.value_addr, (int64_t) strategy_state->receivers.length);\n        }\n    }\n\n    if (strategy_state->receivers.length < (size_t)strategy_state->group_min_size)\n    {\n        return snd_lmt;\n    }\n    else if (0 == strategy_state->receivers.length)\n    {\n        return snd_lmt > position_plus_window ? snd_lmt : position_plus_window;\n    }\n    else\n    {\n        return snd_lmt > min_position ? snd_lmt : min_position;\n    }\n}\n\nint64_t aeron_min_flow_control_strategy_on_sm(\n    void *state,\n    const uint8_t *sm,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t snd_lmt,\n    int32_t initial_term_id,\n    size_t position_bits_to_shift,\n    int64_t now_ns)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state;\n    aeron_status_message_header_t *status_message_header = (aeron_status_message_header_t *)sm;\n\n    return aeron_min_flow_control_strategy_process_sm(\n        strategy_state,\n        status_message_header,\n        snd_lmt,\n        initial_term_id,\n        position_bits_to_shift,\n        now_ns,\n        true);\n}\n\nint64_t aeron_min_flow_control_strategy_on_setup(\n    void *state,\n    const uint8_t *setup,\n    size_t length,\n    int64_t now_ns,\n    int64_t snd_lmt,\n    size_t position_bits_to_shift,\n    int64_t snd_pos)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state;\n\n    if (strategy_state->has_tagged_status_message_triggered_setup && strategy_state->receivers.length > 0)\n    {\n        strategy_state->time_of_last_setup_ns = now_ns;\n        strategy_state->last_setup_snd_lmt = snd_lmt;\n    }\n\n    strategy_state->has_tagged_status_message_triggered_setup = false;\n\n    return snd_lmt;\n}\n\nvoid aeron_min_flow_control_strategy_on_error(\n    void *state,\n    const uint8_t *error,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t now_ns)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state;\n    aeron_error_t *error_header = (aeron_error_t *)error;\n\n    for (size_t i = 0; i < strategy_state->receivers.length; i++)\n    {\n        aeron_min_flow_control_strategy_receiver_t *receiver = &strategy_state->receivers.array[i];\n\n        if (error_header->receiver_id == receiver->receiver_id)\n        {\n            receiver->eos_flagged = true;\n        }\n    }\n}\n\nint64_t aeron_tagged_flow_control_strategy_on_sm(\n    void *state,\n    const uint8_t *sm,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t snd_lmt,\n    int32_t initial_term_id,\n    size_t position_bits_to_shift,\n    int64_t now_ns)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state;\n    aeron_status_message_header_t *status_message_header = (aeron_status_message_header_t *)sm;\n\n    int64_t receiver_group_tag;\n    const int bytes_read = aeron_udp_protocol_group_tag(status_message_header, &receiver_group_tag);\n    const bool was_present = bytes_read == sizeof(receiver_group_tag);\n\n    if (0 != bytes_read && !was_present)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"%s\",\n            \"Received a status message for tagged flow control that did not have 0 or 8 bytes for the group_tag\");\n        aeron_distinct_error_log_record(strategy_state->error_log, aeron_errcode(), aeron_errmsg());\n        aeron_err_clear();\n    }\n\n    const bool matches_tag = was_present && receiver_group_tag == strategy_state->group_tag;\n\n    return aeron_min_flow_control_strategy_process_sm(\n        strategy_state, status_message_header, snd_lmt, initial_term_id, position_bits_to_shift, now_ns, matches_tag);\n}\n\nint64_t aeron_tagged_flow_control_strategy_on_setup(\n    void *state,\n    const uint8_t *setup,\n    size_t length,\n    int64_t now_ns,\n    int64_t snd_lmt,\n    size_t position_bits_to_shift,\n    int64_t snd_pos)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state;\n\n    if (strategy_state->has_tagged_status_message_triggered_setup && strategy_state->receivers.length > 0)\n    {\n        strategy_state->time_of_last_setup_ns = now_ns;\n        strategy_state->last_setup_snd_lmt = snd_lmt;\n    }\n\n    strategy_state->has_tagged_status_message_triggered_setup = false;\n\n    return snd_lmt;\n}\n\nvoid aeron_min_flow_control_strategy_process_on_trigger_send_setup(\n    aeron_min_flow_control_strategy_state_t *strategy_state,\n    aeron_status_message_header_t *status_message_header,\n    size_t length,\n    int64_t now_ns,\n    bool has_matching_tag)\n{\n    if (!strategy_state->has_tagged_status_message_triggered_setup)\n    {\n        strategy_state->has_tagged_status_message_triggered_setup = has_matching_tag;\n    }\n}\n\nvoid aeron_tagged_flow_control_strategy_on_trigger_send_setup(\n    void *state,\n    const uint8_t *sm,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t now_ns)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state;\n    aeron_status_message_header_t *status_message_header = (aeron_status_message_header_t *)sm;\n\n    int64_t receiver_group_tag;\n    const int bytes_read = aeron_udp_protocol_group_tag(status_message_header, &receiver_group_tag);\n    const bool is_tag_present = bytes_read == sizeof(receiver_group_tag);\n\n    if (0 != bytes_read && !is_tag_present)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"%s\",\n            \"Received a status message for tagged flow control that did not have 0 or 8 bytes for the group_tag\");\n        aeron_distinct_error_log_record(strategy_state->error_log, aeron_errcode(), aeron_errmsg());\n        aeron_err_clear();\n    }\n\n    const bool has_matching_tag = is_tag_present && receiver_group_tag == strategy_state->group_tag;\n\n    aeron_min_flow_control_strategy_process_on_trigger_send_setup(\n        strategy_state, status_message_header, length, now_ns, has_matching_tag);\n}\n\nsize_t aeron_min_flow_control_strategy_max_retransmission_length(\n    void *state,\n    size_t term_offset,\n    size_t resend_length,\n    size_t term_buffer_length,\n    size_t mtu_length)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state;\n\n    return aeron_flow_control_calculate_retransmission_length(\n        resend_length,\n        term_buffer_length,\n        term_offset,\n        strategy_state->retransmit_receiver_window_multiple);\n}\n\nvoid aeron_min_flow_control_strategy_on_trigger_send_setup(\n    void *state,\n    const uint8_t *sm,\n    size_t length,\n    struct sockaddr_storage *recv_addr,\n    int64_t now_ns)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state = (aeron_min_flow_control_strategy_state_t *)state;\n    aeron_status_message_header_t *status_message_header = (aeron_status_message_header_t *)sm;\n\n    aeron_min_flow_control_strategy_process_on_trigger_send_setup(\n        strategy_state, status_message_header, length, now_ns, true);\n}\n\nint aeron_min_flow_control_strategy_fini(aeron_flow_control_strategy_t *strategy)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state =\n        (aeron_min_flow_control_strategy_state_t *)strategy->state;\n\n    if (NULL != strategy_state->counters_manager)\n    {\n        aeron_counters_manager_free(\n            strategy_state->counters_manager, strategy_state->receivers_counter.counter_id);\n    }\n\n    aeron_free(strategy_state->receivers.array);\n    aeron_free(strategy->state);\n    aeron_free(strategy);\n\n    return 0;\n}\n\nbool aeron_min_flow_control_strategy_has_required_receivers(aeron_flow_control_strategy_t *strategy)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state =\n        (aeron_min_flow_control_strategy_state_t *)strategy->state;\n\n    bool has_required_receivers;\n    AERON_GET_ACQUIRE(has_required_receivers, strategy_state->has_required_receivers);\n\n    return has_required_receivers;\n}\n\nint aeron_tagged_flow_control_strategy_allocate_receiver_counter(\n    aeron_min_flow_control_strategy_state_t *strategy_state,\n    aeron_counters_manager_t *counters_manager,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    const aeron_udp_channel_t *channel)\n{\n    const int32_t counter_id = aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_MIN_FLOW_CONTROL_RECEIVERS_COUNTER_NAME,\n        AERON_COUNTER_FC_NUM_RECEIVERS_TYPE_ID,\n        AERON_NULL_VALUE,\n        registration_id,\n        session_id,\n        stream_id,\n        channel->uri_length,\n        channel->original_uri,\n        \"\");\n\n    if (counter_id < 0)\n    {\n        return -1;\n    }\n\n    strategy_state->receivers_counter.counter_id = counter_id;\n    strategy_state->receivers_counter.value_addr = aeron_counters_manager_addr(counters_manager, counter_id);\n    aeron_counter_set_release(strategy_state->receivers_counter.value_addr, 0);\n\n    return 0;\n}\n\nint aeron_tagged_flow_control_strategy_supplier_init(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_buffer_capacity,\n    bool is_group_tag_aware)\n{\n    aeron_flow_control_strategy_t *_strategy;\n    aeron_flow_control_tagged_options_t options;\n\n    options.multicast_flow_control_rrwm = context->multicast_flow_control_rrwm;\n    const char *fc_options = aeron_uri_find_param_value(&channel->uri.params.udp.additional_params, AERON_URI_FC_KEY);\n    if (aeron_flow_control_parse_tagged_options(NULL != fc_options ? strlen(fc_options) : 0, fc_options, &options) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_alloc((void**)&_strategy, sizeof(aeron_flow_control_strategy_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_alloc(&_strategy->state, sizeof(aeron_min_flow_control_strategy_state_t)) < 0)\n    {\n        aeron_free(_strategy);\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    _strategy->on_idle = aeron_min_flow_control_strategy_on_idle;\n    _strategy->on_status_message = is_group_tag_aware ?\n        aeron_tagged_flow_control_strategy_on_sm : aeron_min_flow_control_strategy_on_sm;\n    _strategy->on_setup = is_group_tag_aware ?\n        aeron_tagged_flow_control_strategy_on_setup : aeron_min_flow_control_strategy_on_setup;\n    _strategy->on_error = aeron_min_flow_control_strategy_on_error;\n    _strategy->fini = aeron_min_flow_control_strategy_fini;\n    _strategy->has_required_receivers = aeron_min_flow_control_strategy_has_required_receivers;\n    _strategy->on_trigger_send_setup = is_group_tag_aware ?\n        aeron_tagged_flow_control_strategy_on_trigger_send_setup :\n        aeron_min_flow_control_strategy_on_trigger_send_setup;\n    _strategy->max_retransmission_length = aeron_min_flow_control_strategy_max_retransmission_length;\n\n    aeron_min_flow_control_strategy_state_t *state = (aeron_min_flow_control_strategy_state_t *)_strategy->state;\n\n    state->receivers.array = NULL;\n    state->receivers.capacity = 0;\n    state->receivers.length = 0;\n\n    state->channel = channel;\n\n    state->receiver_timeout_ns = (int64_t)(options.timeout_ns.is_present ?\n        options.timeout_ns.value : context->flow_control.receiver_timeout_ns);\n    state->group_min_size = options.group_min_size.is_present ?\n        options.group_min_size.value : context->flow_control.group_min_size;\n    state->group_tag = options.group_tag.is_present ? options.group_tag.value : context->flow_control.group_tag;\n    state->has_tagged_status_message_triggered_setup = false;\n\n    state->error_log = context->error_log;\n    state->time_of_last_setup_ns = 0;\n    state->last_setup_snd_lmt = -1;\n\n    state->log.receiver_added = context->log.flow_control_on_receiver_added;\n    state->log.receiver_removed = context->log.flow_control_on_receiver_removed;\n    state->counters_manager = counters_manager;\n    state->receivers_counter.value_addr = NULL;\n    state->receivers_counter.counter_id = -1;\n\n    if (NULL != counters_manager &&\n        aeron_tagged_flow_control_strategy_allocate_receiver_counter(\n            state, counters_manager, registration_id, session_id, stream_id, channel) < 0)\n    {\n        return -1;\n    }\n\n    bool has_required_receivers = state->receivers.length >= (size_t)state->group_min_size;\n    AERON_SET_RELEASE(state->has_required_receivers, has_required_receivers);\n    state->retransmit_receiver_window_multiple = options.multicast_flow_control_rrwm;\n    *strategy = _strategy;\n\n    return 0;\n}\n\nint aeron_min_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_buffer_capacity)\n{\n    return aeron_tagged_flow_control_strategy_supplier_init(\n        strategy, context, counters_manager, channel, stream_id, session_id,\n        registration_id, initial_term_id, term_buffer_capacity, false);\n}\n\nint aeron_tagged_flow_control_strategy_supplier(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_buffer_capacity)\n{\n    return aeron_tagged_flow_control_strategy_supplier_init(\n        strategy, context, counters_manager, channel, stream_id, session_id,\n        registration_id, initial_term_id, term_buffer_capacity, true);\n}\n\nint aeron_tagged_flow_control_strategy_to_string(\n    aeron_flow_control_strategy_t *strategy, char *buffer, size_t buffer_len)\n{\n    aeron_min_flow_control_strategy_state_t *strategy_state =\n        (aeron_min_flow_control_strategy_state_t *)strategy->state;\n\n    buffer[buffer_len - 1] = '\\0';\n\n    return snprintf(\n        buffer,\n        buffer_len - 1,\n        \"group_tag: %\" PRId64 \", group_min_size: %\" PRId32 \", receiver_count: %\" PRIu64,\n        strategy_state->group_tag,\n        strategy_state->group_min_size,\n        (uint64_t)strategy_state->receivers.length);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_name_resolver.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <string.h>\n#include \"aeron_socket.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"util/aeron_netutil.h\"\n#include \"util/aeron_dlopen.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_symbol_table.h\"\n#include \"aeron_name_resolver.h\"\n#include \"aeron_driver_name_resolver.h\"\n#include \"aeron_csv_table_name_resolver.h\"\n\nstatic const aeron_symbol_table_func_t aeron_name_resolver_table[] =\n    {\n        {\n            \"default\",\n            \"aeron_default_name_resolver_supplier\",\n            (aeron_fptr_t)aeron_default_name_resolver_supplier\n        },\n        {\n            \"driver\",\n            \"aeron_driver_name_resolver_supplier\",\n            (aeron_fptr_t)aeron_driver_name_resolver_supplier\n        },\n        {\n            \"csv_table\",\n            \"aeron_csv_table_name_resolver_supplier\",\n            (aeron_fptr_t)aeron_csv_table_name_resolver_supplier\n        }\n    };\n\nstatic const size_t aeron_name_resolver_table_length =\n    sizeof(aeron_name_resolver_table) / sizeof(aeron_symbol_table_func_t);\n\nstatic void aeron_name_resolver_load_function_info(\n    aeron_name_resolver_t *resolver,\n    char *lookup_name_buffer,\n    size_t lookup_name_buffer_len,\n    char *resolve_name_buffer,\n    size_t resolve_name_buffer_len);\n\nint aeron_name_resolver_init(aeron_name_resolver_t *resolver, const char *args, aeron_driver_context_t *context)\n{\n    if (context->name_resolver_supplier_func(resolver, args, context) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_default_name_resolver_supplier(\n    aeron_name_resolver_t *resolver, const char *args, aeron_driver_context_t *context)\n{\n    resolver->lookup_func = aeron_default_name_resolver_lookup;\n    resolver->resolve_func = aeron_default_name_resolver_resolve;\n    resolver->do_work_func = aeron_default_name_resolver_do_work;\n    resolver->close_func = aeron_default_name_resolver_close;\n    resolver->state = NULL;\n    resolver->name = \"default\";\n\n    return 0;\n}\n\nint aeron_default_name_resolver_resolve(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_resolution,\n    struct sockaddr_storage *address)\n{\n    return aeron_ip_addr_resolver(name, address, AF_INET, IPPROTO_UDP);\n}\n\nint aeron_default_name_resolver_lookup(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_lookup,\n    const char **resolved_name)\n{\n    *resolved_name = name;\n    return 0;\n}\n\nint aeron_default_name_resolver_do_work(aeron_name_resolver_t *resolver, int64_t now_ms)\n{\n    return 0;\n}\n\nint aeron_default_name_resolver_close(aeron_name_resolver_t *resolver)\n{\n    return 0;\n}\n\nint aeron_name_resolver_resolve_host_and_port(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_resolution,\n    struct sockaddr_storage *sockaddr)\n{\n    aeron_parsed_address_t parsed_address;\n    const char *address_str = NULL;\n    int result = -1;\n\n    if (resolver->lookup_func(resolver, name, uri_param_name, is_re_resolution, &address_str) < 0)\n    {\n        goto exit;\n    }\n\n    if (aeron_address_split(address_str, &parsed_address) < 0)\n    {\n        goto exit;\n    }\n\n    const int family_hint = 6 == parsed_address.ip_version_hint ? AF_INET6 : AF_INET;\n\n    int port = aeron_udp_port_resolver(parsed_address.port, false);\n\n    if (0 <= port)\n    {\n        if (AF_INET == family_hint)\n        {\n            if (aeron_try_parse_ipv4(parsed_address.host, sockaddr))\n            {\n                result = 0;\n            }\n            else\n            {\n                result = resolver->resolve_func(\n                    resolver, parsed_address.host, uri_param_name, is_re_resolution, sockaddr);\n            }\n\n            ((struct sockaddr_in *)sockaddr)->sin_port = htons((uint16_t)port);\n        }\n        else if (AF_INET6 == family_hint)\n        {\n            if (aeron_try_parse_ipv6(parsed_address.host, sockaddr))\n            {\n                result = 0;\n            }\n            else\n            {\n                result = resolver->resolve_func(\n                    resolver, parsed_address.host, uri_param_name, is_re_resolution, sockaddr);\n            }\n\n            ((struct sockaddr_in6 *)sockaddr)->sin6_port = htons((uint16_t)port);\n        }\n    }\n\nexit:\n    if (result < 0)\n    {\n        char lookup_info[128];\n        char resolve_info[128];\n\n        const char *address_or_null = NULL != address_str ? address_str : \"null\";\n        aeron_name_resolver_load_function_info(\n            resolver, lookup_info, sizeof(resolve_info), resolve_info, sizeof(resolve_info));\n\n        AERON_APPEND_ERR(\n            \"Unresolved - %s=%s, name-and-port=%s\",\n            uri_param_name,\n            name,\n            address_or_null);\n    }\n\n    return result;\n}\n\naeron_name_resolver_supplier_func_t aeron_name_resolver_supplier_load(const char *name)\n{\n    return (aeron_name_resolver_supplier_func_t)aeron_symbol_table_func_load(\n        aeron_name_resolver_table, aeron_name_resolver_table_length, name, \"name resolver\");\n}\n\nstatic void aeron_name_resolver_load_function_info(\n    aeron_name_resolver_t *resolver,\n    char *lookup_name_buffer,\n    size_t lookup_name_buffer_len,\n    char *resolve_name_buffer,\n    size_t resolve_name_buffer_len)\n{\n    aeron_dlinfo_func((aeron_fptr_t)resolver->lookup_func, lookup_name_buffer, lookup_name_buffer_len);\n    aeron_dlinfo_func((aeron_fptr_t)resolver->resolve_func, resolve_name_buffer, resolve_name_buffer_len);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_name_resolver.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_NAME_RESOLVER_H\n#define AERON_NAME_RESOLVER_H\n\n#include <stdbool.h>\n#include <stdio.h>\n#include \"aeron_driver_common.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_system_counters.h\"\n#include \"util/aeron_parse_util.h\"\n\n#define AERON_NAME_RESOLVER_CSV_TABLE \"csv_table\"\n#define AERON_NAME_RESOLVER_DRIVER \"driver\"\n#define AERON_NAME_RESOLVER_CSV_TABLE_ARGS_ENV_VAR \"AERON_NAME_RESOLVER_CSV_LOOKUP_TABLE_ARGS\"\n\ntypedef int (*aeron_name_resolver_resolve_func_t)(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_resolution,\n    struct sockaddr_storage *address);\n\n/**\n * Resolves a name to a host:port string.\n *\n * @return 0 if not found, 1 if found, -1 on error.\n */\ntypedef int (*aeron_name_resolver_lookup_func_t)(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_lookup,\n    const char **resolved_name);\n\ntypedef int (*aeron_name_resolver_do_work_func_t)(aeron_name_resolver_t *resolver, int64_t now_ms);\n\ntypedef int (*aeron_name_resolver_close_func_t)(aeron_name_resolver_t *resolver);\n\ntypedef struct aeron_name_resolver_stct\n{\n    const char *name;\n    aeron_name_resolver_lookup_func_t lookup_func;\n    aeron_name_resolver_resolve_func_t resolve_func;\n    aeron_name_resolver_do_work_func_t do_work_func;\n    aeron_name_resolver_close_func_t close_func;\n    void *state;\n}\naeron_name_resolver_t;\n\naeron_name_resolver_supplier_func_t aeron_name_resolver_supplier_load(const char *name);\n\nint aeron_name_resolver_init(aeron_name_resolver_t *resolver, const char *args, aeron_driver_context_t *context);\n\nint aeron_default_name_resolver_supplier(\n    aeron_name_resolver_t *resolver, const char *args, aeron_driver_context_t *context);\n\nint aeron_default_name_resolver_resolve(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_resolution,\n    struct sockaddr_storage *address);\n\nint aeron_default_name_resolver_lookup(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_lookup,\n    const char **resolved_name);\n\nint aeron_default_name_resolver_do_work(aeron_name_resolver_t *resolver, int64_t now_ms);\n\nint aeron_default_name_resolver_close(aeron_name_resolver_t *resolver);\n\ntypedef struct aeron_name_resolver_async_resolve_stct\n{\n    const char *uri_param_name;\n    bool is_re_resolution;\n    struct sockaddr_storage sockaddr;\n    char endpoint_name[AERON_MAX_HOST_LENGTH + 1];\n}\naeron_name_resolver_async_resolve_t;\n\nint aeron_name_resolver_resolve_host_and_port(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_resolution,\n    struct sockaddr_storage *sockaddr);\n\n#endif //AERON_NAME_RESOLVER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_name_resolver_cache.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string.h>\n#include <inttypes.h>\n\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"aeron_name_resolver_cache.h\"\n\nint aeron_name_resolver_cache_init(aeron_name_resolver_cache_t *cache, int64_t timeout_ms)\n{\n    memset(cache, 0, sizeof(aeron_name_resolver_cache_t));\n    cache->timeout_ms = timeout_ms;\n    return 0;\n}\n\nint aeron_name_resolver_cache_close(aeron_name_resolver_cache_t *cache)\n{\n    if (NULL != cache)\n    {\n        for (size_t i = 0; i < cache->entries.length; i++)\n        {\n            aeron_free((void *)cache->entries.array[i].name);\n        }\n\n        aeron_free((void *)cache->entries.array);\n    }\n\n    return 0;\n}\n\nint aeron_name_resolver_cache_find_index_by_name_and_type(\n    aeron_name_resolver_cache_t *cache, const char *name, size_t name_length, int8_t res_type)\n{\n    for (size_t i = 0; i < cache->entries.length; i++)\n    {\n        aeron_name_resolver_cache_entry_t *entry = &cache->entries.array[i];\n\n        if (res_type == entry->cache_addr.res_type &&\n            name_length == entry->name_length &&\n            0 == strncmp(name, entry->name, name_length))\n        {\n            return (int)i;\n        }\n    }\n    \n    return -1;\n}\n\nint aeron_name_resolver_cache_add_or_update(\n    aeron_name_resolver_cache_t *cache,\n    const char *name,\n    size_t name_length,\n    aeron_name_resolver_cache_addr_t *cache_addr,\n    int64_t time_of_last_activity_ms,\n    volatile int64_t *cache_entries_counter)\n{\n    int index = aeron_name_resolver_cache_find_index_by_name_and_type(cache, name, name_length, cache_addr->res_type);\n    aeron_name_resolver_cache_entry_t *entry;\n    int num_updated;\n\n    if (index < 0)\n    {\n        int ensure_capacity_result = 0;\n        AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, cache->entries, aeron_name_resolver_cache_entry_t)\n\n        if (ensure_capacity_result < 0)\n        {\n            AERON_APPEND_ERR(\n                \"Failed to allocate rows for lookup table (%\" PRIu64 \",%\" PRIu64 \")\",\n                (uint64_t)cache->entries.length,\n                (uint64_t)cache->entries.capacity);\n            return -1;\n        }\n\n        entry = &cache->entries.array[cache->entries.length];\n\n        if (aeron_alloc((void **)&entry->name, name_length + 1) < 0) // NULL terminate, just to be safe.\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to allocate name for resolver cache\");\n            return -1;\n        }\n\n        strncpy((char *)entry->name, name, name_length);\n        entry->name_length = name_length;\n        num_updated = 1;\n\n        cache->entries.length++;\n\n        aeron_counter_set_release(cache_entries_counter, (int64_t) cache->entries.length);\n    }\n    else\n    {\n        entry = &cache->entries.array[index];\n        num_updated = 0;\n    }\n\n    memcpy(&entry->cache_addr, cache_addr, sizeof(entry->cache_addr));\n    entry->time_of_last_activity_ms = time_of_last_activity_ms;\n    entry->deadline_ms = time_of_last_activity_ms + cache->timeout_ms;\n\n    return num_updated;\n}\n\nint aeron_name_resolver_cache_lookup_by_name(\n    aeron_name_resolver_cache_t *cache,\n    const char *name,\n    size_t name_length,\n    int8_t res_type,\n    aeron_name_resolver_cache_entry_t **entry)\n{\n    int index = aeron_name_resolver_cache_find_index_by_name_and_type(cache, name, name_length, res_type);\n\n    if (0 <= index && NULL != entry)\n    {\n        *entry = &cache->entries.array[index];\n    }\n\n    return index;\n}\n\nint aeron_name_resolver_cache_timeout_old_entries(\n    aeron_name_resolver_cache_t *cache, int64_t now_ms, volatile int64_t *cache_entries_counter)\n{\n    int num_removed = 0;\n    for (int last_index = (int)cache->entries.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_name_resolver_cache_entry_t *entry = &cache->entries.array[i];\n\n        if (entry->deadline_ms <= now_ms)\n        {\n            aeron_free((void *)entry->name);\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)cache->entries.array, sizeof(aeron_name_resolver_cache_entry_t), i, last_index);\n            cache->entries.length--;\n            last_index--;\n            num_removed++;\n        }\n    }\n\n    if (0 != num_removed)\n    {\n        aeron_counter_set_release(cache_entries_counter, (int64_t) cache->entries.length);\n    }\n\n    return num_removed;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_name_resolver_cache.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_NAME_RESOLVER_CACHE_H\n#define AERON_AERON_NAME_RESOLVER_CACHE_H\n\n#include \"protocol/aeron_udp_protocol.h\"\n\ntypedef struct aeron_name_resolver_cache_addr_stct\n{\n    uint8_t address[AERON_RES_HEADER_ADDRESS_LENGTH_IP6];\n    uint16_t port;\n    int8_t res_type;\n}\naeron_name_resolver_cache_addr_t;\n\ntypedef struct aeron_name_resolver_cache_entry_stct\n{\n    aeron_name_resolver_cache_addr_t cache_addr;\n    int64_t deadline_ms;\n    int64_t time_of_last_activity_ms;\n    size_t name_length;\n    const char *name;\n}\naeron_name_resolver_cache_entry_t;\n\ntypedef struct aeron_name_resolver_cache_stct\n{\n    int64_t timeout_ms;\n    struct entry_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_name_resolver_cache_entry_t *array;\n    }\n    entries;\n}\naeron_name_resolver_cache_t;\n\nint aeron_name_resolver_cache_init(aeron_name_resolver_cache_t *cache, int64_t timeout_ms);\n\nint aeron_name_resolver_cache_add_or_update(\n    aeron_name_resolver_cache_t *cache,\n    const char *name,\n    size_t name_length,\n    aeron_name_resolver_cache_addr_t *cache_addr,\n    int64_t time_of_last_activity_ms,\n    volatile int64_t *cache_entries_counter);\n\nint aeron_name_resolver_cache_lookup_by_name(\n    aeron_name_resolver_cache_t *cache,\n    const char *name,\n    size_t name_length,\n    int8_t res_type,\n    aeron_name_resolver_cache_entry_t **entry);\n\nint aeron_name_resolver_cache_close(aeron_name_resolver_cache_t *cache);\n\nint aeron_name_resolver_cache_timeout_old_entries(\n    aeron_name_resolver_cache_t *cache, int64_t now_ms, volatile int64_t *cache_entries_counter);\n\n#endif //AERON_AERON_NAME_RESOLVER_CACHE_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_network_publication.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <string.h>\n#include <inttypes.h>\n#include \"aeron_socket.h\"\n#include \"concurrent/aeron_term_scanner.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_network_publication.h\"\n#include \"aeron_alloc.h\"\n#include \"media/aeron_send_channel_endpoint.h\"\n#include \"aeron_driver_conductor.h\"\n#include \"concurrent/aeron_logbuffer_unblocker.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\nstatic inline bool aeron_network_publication_liveness_on_remote_close(\n    aeron_network_publication_t *publication,\n    int64_t receiver_id)\n{\n    int64_t missing_value = publication->receiver_liveness_tracker.initial_value;\n    return missing_value != aeron_int64_counter_map_remove(&publication->receiver_liveness_tracker, receiver_id);\n}\n\nstatic inline int aeron_network_publication_liveness_on_status_message(\n    aeron_network_publication_t *publication,\n    int64_t receiver_id,\n    int64_t time_ns)\n{\n    if (aeron_int64_counter_map_put(&publication->receiver_liveness_tracker, receiver_id, time_ns, NULL) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic inline bool aeron_network_publication_liveness_is_expired(\n    void *clientd,\n    int64_t receiver_id,\n    int64_t last_sm_time_ns)\n{\n    int64_t *expiry_time_ns = (int64_t *)clientd;\n    return last_sm_time_ns <= *expiry_time_ns;\n}\n\nstatic inline void aeron_network_publication_liveness_on_idle(\n    aeron_network_publication_t *publication,\n    int64_t time_ns,\n    int64_t timeout_ns)\n{\n    const int64_t expiry_time_ns = time_ns - timeout_ns;\n    aeron_int64_counter_map_remove_if(\n        &publication->receiver_liveness_tracker,\n        aeron_network_publication_liveness_is_expired,\n        (void *)&expiry_time_ns);\n}\n\nstatic void aeron_network_publication_update_has_receivers(\n    aeron_network_publication_t *publication,\n    const int64_t now_ns)\n{\n    aeron_network_publication_liveness_on_idle(publication, now_ns, publication->connection_timeout_ns);\n    const bool is_live = 0 != publication->receiver_liveness_tracker.size;\n\n    bool has_receivers;\n    AERON_GET_ACQUIRE(has_receivers, publication->has_receivers);\n    if (is_live != has_receivers)\n    {\n        AERON_SET_RELEASE(publication->has_receivers, is_live);\n    }\n}\n\nstatic void aeron_network_publication_handle_managed_resource_event(aeron_driver_managed_resource_event_t event, void *clientd);\n\nint aeron_network_publication_create(\n    aeron_network_publication_t **publication,\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_driver_context_t *context,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t initial_term_id,\n    aeron_position_t *pub_pos_position,\n    aeron_position_t *pub_lmt_position,\n    aeron_position_t *snd_pos_position,\n    aeron_position_t *snd_lmt_position,\n    aeron_atomic_counter_t *snd_bpe_counter,\n    aeron_atomic_counter_t *snd_naks_received_counter,\n    aeron_flow_control_strategy_t *flow_control_strategy,\n    aeron_driver_uri_publication_params_t *params,\n    bool is_exclusive,\n    aeron_system_counters_t *system_counters)\n{\n    aeron_network_publication_t *_pub = NULL;\n    const uint64_t log_length = aeron_logbuffer_compute_log_length(params->term_length, context->file_page_size);\n\n    *publication = NULL;\n\n    if (aeron_driver_context_run_storage_checks(context, log_length) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&_pub, sizeof(aeron_network_publication_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not allocate network publication\");\n        return -1;\n    }\n\n    char path[AERON_MAX_PATH];\n    int path_length = aeron_network_publication_location(path, sizeof(path), context->aeron_dir, registration_id);\n    if (path_length < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not resolve network publication file path\");\n        aeron_free(_pub);\n        return -1;\n    }\n\n    _pub->log_file_name = NULL;\n    if (aeron_alloc((void **)(&_pub->log_file_name), (size_t)path_length + 1) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not allocate network publication log_file_name\");\n        aeron_free(_pub);\n        return -1;\n    }\n\n    int64_t *retransmit_overflow_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW);\n\n    bool has_group_semantics = aeron_udp_channel_has_group_semantics(endpoint->conductor_fields.udp_channel);\n\n    if (aeron_retransmit_handler_init(\n        &_pub->retransmit_handler,\n        aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_INVALID_PACKETS),\n        context->retransmit_unicast_delay_ns,\n        context->retransmit_unicast_linger_ns,\n        has_group_semantics,\n        params->has_max_resend ? params->max_resend : context->max_resend,\n        retransmit_overflow_counter) < 0)\n    {\n        aeron_free(_pub->log_file_name);\n        aeron_free(_pub);\n        AERON_APPEND_ERR(\n            \"Could not init network publication retransmit handler, delay: %\" PRIu64 \", linger: %\" PRIu64,\n            context->retransmit_unicast_delay_ns,\n            context->retransmit_unicast_linger_ns);\n        return -1;\n    }\n\n    if (context->raw_log_map_func(\n        &_pub->mapped_raw_log, path, params->is_sparse, params->term_length, context->file_page_size) < 0)\n    {\n        aeron_free(_pub->log_file_name);\n        aeron_free(_pub);\n        AERON_APPEND_ERR(\"error mapping network raw log: %s\", path);\n        return -1;\n    }\n\n    _pub->mapped_bytes_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED);\n    aeron_counter_get_and_add_release(_pub->mapped_bytes_counter, (int64_t) log_length);\n\n    _pub->raw_log_close_func = context->raw_log_close_func;\n    _pub->raw_log_free_func = context->raw_log_free_func;\n    _pub->log.untethered_subscription_state_change = context->log.untethered_subscription_on_state_change;\n    _pub->log.resend = context->log.resend;\n    _pub->log.publication_revoke = context->log.publication_revoke;\n\n    strncpy(_pub->log_file_name, path, (size_t)path_length);\n    _pub->log_file_name[path_length] = '\\0';\n    _pub->log_file_name_length = (size_t)path_length;\n    _pub->log_meta_data = (aeron_logbuffer_metadata_t *)(_pub->mapped_raw_log.log_meta_data.addr);\n\n    if (params->has_position)\n    {\n        int32_t term_id = params->term_id;\n        int32_t term_count = aeron_logbuffer_compute_term_count(term_id, initial_term_id);\n        size_t active_index = aeron_logbuffer_index_by_term_count(term_count);\n\n        _pub->log_meta_data->term_tail_counters[active_index] =\n            (term_id * (INT64_C(1) << 32)) | (int64_t)params->term_offset;\n\n        for (int i = 1; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n        {\n            int32_t expected_term_id = (term_id + i) - AERON_LOGBUFFER_PARTITION_COUNT;\n            active_index = (active_index + 1) % AERON_LOGBUFFER_PARTITION_COUNT;\n            _pub->log_meta_data->term_tail_counters[active_index] = expected_term_id * (INT64_C(1) << 32);\n        }\n\n        _pub->log_meta_data->active_term_count = term_count;\n    }\n    else\n    {\n        _pub->log_meta_data->term_tail_counters[0] = initial_term_id * (INT64_C(1) << 32);\n        for (int i = 1; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n        {\n            int32_t expected_term_id = (initial_term_id + i) - AERON_LOGBUFFER_PARTITION_COUNT;\n            _pub->log_meta_data->term_tail_counters[i] = expected_term_id * (INT64_C(1) << 32);\n        }\n\n        _pub->log_meta_data->active_term_count = 0;\n    }\n\n    // Called from conductor thread...\n    int64_t now_ns = aeron_clock_cached_nano_time(context->cached_clock);\n\n    aeron_logbuffer_metadata_init(\n        _pub->mapped_raw_log.log_meta_data.addr,\n        INT64_MAX,\n        0,\n        0,\n        registration_id,\n        initial_term_id,\n        (int32_t)params->mtu_length,\n        (int32_t)params->term_length,\n        (int32_t)context->file_page_size,\n        (int32_t)params->publication_window_length,\n        0,\n        (int32_t)endpoint->conductor_fields.udp_channel->socket_sndbuf_length,\n        (int32_t)context->os_buffer_lengths.default_so_sndbuf,\n        (int32_t)context->os_buffer_lengths.max_so_sndbuf,\n        (int32_t)endpoint->conductor_fields.udp_channel->socket_rcvbuf_length,\n        (int32_t)context->os_buffer_lengths.default_so_rcvbuf,\n        (int32_t)context->os_buffer_lengths.max_so_rcvbuf,\n        (int32_t)params->max_resend,\n        session_id,\n        stream_id,\n        (int64_t)params->entity_tag,\n        (int64_t)params->response_correlation_id,\n        (int64_t)params->linger_timeout_ns,\n        (int64_t)params->untethered_window_limit_timeout_ns,\n        (int64_t)params->untethered_linger_timeout_ns,\n        (int64_t)params->untethered_resting_timeout_ns,\n        (uint8_t)has_group_semantics,\n        (uint8_t)params->is_response,\n        (uint8_t)false,\n        (uint8_t)false,\n        (uint8_t)params->is_sparse,\n        (uint8_t)params->signal_eos,\n        (uint8_t)params->spies_simulate_connection,\n        (uint8_t)false,\n        is_exclusive ? AERON_LOGBUFFER_TYPE_EXCLUSIVE_PUBLICATION : AERON_LOGBUFFER_TYPE_CONCURRENT_PUBLICATION);\n\n    _pub->endpoint = endpoint;\n    _pub->flow_control = flow_control_strategy;\n    // Will be called from sender thread.\n    _pub->cached_clock = context->sender_cached_clock;\n    _pub->conductor_fields.subscribable.array = NULL;\n    _pub->conductor_fields.subscribable.length = 0;\n    _pub->conductor_fields.subscribable.capacity = 0;\n    _pub->conductor_fields.subscribable.inactive_count = 0;\n    _pub->conductor_fields.subscribable.add_position_hook_func = aeron_network_publication_add_subscriber_hook;\n    _pub->conductor_fields.subscribable.remove_position_hook_func = aeron_network_publication_remove_subscriber_hook;\n    _pub->conductor_fields.subscribable.clientd = _pub;\n    _pub->conductor_fields.managed_resource.registration_id = registration_id;\n    _pub->conductor_fields.managed_resource.clientd = _pub;\n    _pub->conductor_fields.managed_resource.handle_event = aeron_network_publication_handle_managed_resource_event;\n    _pub->conductor_fields.has_reached_end_of_life = false;\n    _pub->conductor_fields.clean_position = 0;\n    _pub->conductor_fields.state = AERON_NETWORK_PUBLICATION_STATE_ACTIVE;\n    _pub->conductor_fields.refcnt = 1;\n    _pub->conductor_fields.time_of_last_activity_ns = now_ns;\n    _pub->conductor_fields.last_snd_pos = 0;\n    _pub->session_id = session_id;\n    _pub->stream_id = stream_id;\n    _pub->pub_lmt_position.counter_id = pub_lmt_position->counter_id;\n    _pub->pub_lmt_position.value_addr = pub_lmt_position->value_addr;\n    _pub->pub_pos_position.counter_id = pub_pos_position->counter_id;\n    _pub->pub_pos_position.value_addr = pub_pos_position->value_addr;\n    _pub->snd_pos_position.counter_id = snd_pos_position->counter_id;\n    _pub->snd_pos_position.value_addr = snd_pos_position->value_addr;\n    _pub->snd_lmt_position.counter_id = snd_lmt_position->counter_id;\n    _pub->snd_lmt_position.value_addr = snd_lmt_position->value_addr;\n    _pub->snd_bpe_counter.counter_id = snd_bpe_counter->counter_id;\n    _pub->snd_bpe_counter.value_addr = snd_bpe_counter->value_addr;\n    _pub->snd_naks_received_counter.counter_id = snd_naks_received_counter->counter_id;\n    _pub->snd_naks_received_counter.value_addr = snd_naks_received_counter->value_addr;\n    _pub->tag = params->entity_tag;\n    _pub->initial_term_id = initial_term_id;\n    _pub->starting_term_id = params->has_position ? params->term_id : initial_term_id;\n    _pub->starting_term_offset = params->has_position ? params->term_offset : 0;\n    _pub->term_buffer_length = _pub->log_meta_data->term_length;\n    _pub->term_length_mask = (int32_t)params->term_length - 1;\n    _pub->position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes((int32_t)params->term_length);\n    _pub->mtu_length = params->mtu_length;\n    _pub->max_messages_per_send = context->network_publication_max_messages_per_send;\n    _pub->term_window_length = params->publication_window_length;\n    _pub->linger_timeout_ns = (int64_t)params->linger_timeout_ns;\n    _pub->untethered_window_limit_timeout_ns = (int64_t)params->untethered_window_limit_timeout_ns;\n    _pub->untethered_linger_timeout_ns = params->untethered_linger_timeout_ns;\n    _pub->untethered_resting_timeout_ns = (int64_t)params->untethered_resting_timeout_ns;\n    _pub->unblock_timeout_ns = (int64_t)context->publication_unblock_timeout_ns;\n    _pub->connection_timeout_ns = (int64_t)context->publication_connection_timeout_ns;\n    _pub->time_of_last_data_or_heartbeat_ns = now_ns - AERON_NETWORK_PUBLICATION_HEARTBEAT_TIMEOUT_NS - 1;\n    _pub->time_of_last_setup_ns = now_ns - AERON_NETWORK_PUBLICATION_SETUP_TIMEOUT_NS - 1;\n    _pub->status_message_deadline_ns = params->spies_simulate_connection ?\n        now_ns : now_ns + (int64_t)context->publication_connection_timeout_ns;\n    _pub->is_exclusive = is_exclusive;\n    _pub->spies_simulate_connection = params->spies_simulate_connection;\n    _pub->signal_eos = params->signal_eos;\n    _pub->is_setup_elicited = false;\n    _pub->has_receivers = false;\n    _pub->has_spies = false;\n    _pub->is_connected = false;\n    _pub->is_end_of_stream = false;\n    _pub->track_sender_limits = false;\n    _pub->has_sender_released = false;\n    _pub->has_received_unicast_eos = false;\n\n    _pub->short_sends_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_SHORT_SENDS);\n    _pub->heartbeats_sent_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_HEARTBEATS_SENT);\n    _pub->sender_flow_control_limits_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_SENDER_FLOW_CONTROL_LIMITS);\n    _pub->retransmits_sent_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_RETRANSMITS_SENT);\n    _pub->retransmitted_bytes_counter =\n        aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES);\n    _pub->unblocked_publications_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_UNBLOCKED_PUBLICATIONS);\n    _pub->publications_revoked_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_PUBLICATIONS_REVOKED);\n\n    _pub->conductor_fields.last_snd_pos = aeron_counter_get_plain(_pub->snd_pos_position.value_addr);\n    _pub->conductor_fields.clean_position = _pub->conductor_fields.last_snd_pos;\n\n    _pub->endpoint_address.ss_family = AF_UNSPEC;\n    _pub->is_response = AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE == endpoint->conductor_fields.udp_channel->control_mode;\n    _pub->response_correlation_id = params->response_correlation_id;\n\n    aeron_int64_counter_map_init(&_pub->receiver_liveness_tracker, AERON_NULL_VALUE, 16, 0.6f);\n\n    *publication = _pub;\n\n    return 0;\n}\n\nvoid aeron_network_publication_close(\n    aeron_counters_manager_t *counters_manager, aeron_network_publication_t *publication)\n{\n    if (NULL != publication)\n    {\n        aeron_subscribable_t *subscribable = &publication->conductor_fields.subscribable;\n\n        aeron_counters_manager_free(counters_manager, publication->pub_pos_position.counter_id);\n        aeron_counters_manager_free(counters_manager, publication->pub_lmt_position.counter_id);\n        aeron_counters_manager_free(counters_manager, publication->snd_pos_position.counter_id);\n        aeron_counters_manager_free(counters_manager, publication->snd_lmt_position.counter_id);\n        aeron_counters_manager_free(counters_manager, publication->snd_bpe_counter.counter_id);\n        aeron_counters_manager_free(counters_manager, publication->snd_naks_received_counter.counter_id);\n\n        for (size_t i = 0, length = subscribable->length; i < length; i++)\n        {\n            aeron_counters_manager_free(counters_manager, subscribable->array[i].counter_id);\n        }\n\n        aeron_free(subscribable->array);\n        publication->conductor_fields.managed_resource.clientd = NULL;\n        aeron_int64_counter_map_delete(&publication->receiver_liveness_tracker);\n\n        aeron_retransmit_handler_close(&publication->retransmit_handler);\n        publication->flow_control->fini(publication->flow_control);\n    }\n}\n\nbool aeron_network_publication_free(aeron_network_publication_t *publication)\n{\n    if (NULL == publication)\n    {\n        return true;\n    }\n\n    if (!publication->raw_log_free_func(&publication->mapped_raw_log, publication->log_file_name))\n    {\n         return false;\n    }\n\n    aeron_counter_get_and_add_release(\n        publication->mapped_bytes_counter, -((int64_t) publication->mapped_raw_log.mapped_file.length));\n\n    aeron_free(publication->log_file_name);\n    aeron_free(publication);\n\n    return true;\n}\n\nstatic int aeron_network_publication_do_send(\n    aeron_network_publication_t *publication,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n    if (publication->is_response)\n    {\n        if (AF_UNSPEC != publication->endpoint_address.ss_family)\n        {\n            return aeron_send_channel_send_endpoint_address(\n                publication->endpoint, &publication->endpoint_address, iov, iov_length, bytes_sent);\n        }\n        else\n        {\n            return 0;\n        }\n    }\n    else\n    {\n        return aeron_send_channel_send(publication->endpoint, iov, iov_length, bytes_sent);\n    }\n}\n\nint aeron_network_publication_setup_message_check(\n    aeron_network_publication_t *publication, int64_t now_ns, int32_t active_term_id, int32_t term_offset)\n{\n    int result = 0;\n    int64_t bytes_sent = 0;\n\n    if (now_ns > (publication->time_of_last_setup_ns + AERON_NETWORK_PUBLICATION_SETUP_TIMEOUT_NS))\n    {\n        uint8_t setup_buffer[sizeof(aeron_setup_header_t)];\n        aeron_setup_header_t *setup_header = (aeron_setup_header_t *)setup_buffer;\n        struct iovec iov;\n\n        uint8_t send_response_flag = (!publication->is_response && AERON_NULL_VALUE != publication->response_correlation_id) ?\n            AERON_SETUP_HEADER_SEND_RESPONSE_FLAG : 0;\n        uint8_t group_flag = publication->retransmit_handler.has_group_semantics ? AERON_SETUP_HEADER_GROUP_FLAG : 0;\n\n        setup_header->frame_header.frame_length = sizeof(aeron_setup_header_t);\n        setup_header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n        setup_header->frame_header.flags = 0;\n        setup_header->frame_header.type = AERON_HDR_TYPE_SETUP;\n        setup_header->frame_header.flags = send_response_flag | group_flag;\n        setup_header->term_offset = term_offset;\n        setup_header->session_id = publication->session_id;\n        setup_header->stream_id = publication->stream_id;\n        setup_header->initial_term_id = publication->initial_term_id;\n        setup_header->active_term_id = active_term_id;\n        setup_header->term_length = publication->term_length_mask + 1;\n        setup_header->mtu = (int32_t)publication->mtu_length;\n        setup_header->ttl = publication->endpoint->conductor_fields.udp_channel->multicast_ttl;\n\n        iov.iov_base = setup_buffer;\n        iov.iov_len = sizeof(aeron_setup_header_t);\n\n        if (publication->is_setup_elicited)\n        {\n            publication->flow_control->on_setup(\n                publication->flow_control->state,\n                setup_buffer,\n                sizeof(aeron_setup_header_t),\n                now_ns,\n                *publication->snd_lmt_position.value_addr,\n                publication->position_bits_to_shift,\n                *publication->snd_pos_position.value_addr);\n        }\n\n        if (0 <= (result = aeron_network_publication_do_send(publication, &iov, 1, &bytes_sent)))\n        {\n            if (bytes_sent < (int64_t)iov.iov_len)\n            {\n                aeron_counter_increment(publication->short_sends_counter);\n            }\n        }\n\n        publication->time_of_last_setup_ns = now_ns;\n\n        if (publication->has_receivers)\n        {\n            publication->is_setup_elicited = false;\n        }\n    }\n\n    return result;\n}\n\nint aeron_network_publication_heartbeat_message_check(\n    aeron_network_publication_t *publication,\n    int64_t now_ns,\n    int32_t active_term_id,\n    int32_t term_offset,\n    bool is_end_of_stream)\n{\n    int result = 0;\n    int64_t bytes_sent = 0;\n\n    if (publication->has_initial_connection &&\n        now_ns > (publication->time_of_last_data_or_heartbeat_ns + AERON_NETWORK_PUBLICATION_HEARTBEAT_TIMEOUT_NS))\n    {\n        uint8_t is_publication_revoked;\n        AERON_GET_ACQUIRE(is_publication_revoked, publication->log_meta_data->is_publication_revoked);\n\n        uint8_t flags = AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG;\n        if (is_publication_revoked)\n        {\n            flags |= AERON_DATA_HEADER_EOS_FLAG;\n            flags |= AERON_DATA_HEADER_REVOKED_FLAG;\n        }\n        else if (publication->signal_eos & is_end_of_stream)\n        {\n            flags |= AERON_DATA_HEADER_EOS_FLAG;\n        }\n\n        uint8_t heartbeat_buffer[sizeof(aeron_data_header_t)];\n        aeron_data_header_t *data_header = (aeron_data_header_t *)heartbeat_buffer;\n        struct iovec iov;\n\n        data_header->frame_header.frame_length = 0;\n        data_header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n        data_header->frame_header.flags = flags;\n        data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n        data_header->term_offset = term_offset;\n        data_header->session_id = publication->session_id;\n        data_header->stream_id = publication->stream_id;\n        data_header->term_id = active_term_id;\n        data_header->reserved_value = 0l;\n\n        iov.iov_base = heartbeat_buffer;\n        iov.iov_len = sizeof(aeron_data_header_t);\n\n        if (0 <= (result = aeron_network_publication_do_send(publication, &iov, 1, &bytes_sent)))\n        {\n            result = (int)bytes_sent;\n            if (bytes_sent < (int64_t)iov.iov_len)\n            {\n                aeron_counter_increment(publication->short_sends_counter);\n            }\n        }\n\n        aeron_counter_increment_release(publication->heartbeats_sent_counter);\n        publication->time_of_last_data_or_heartbeat_ns = now_ns;\n    }\n\n    return result;\n}\n\nint aeron_network_publication_send_data(\n    aeron_network_publication_t *publication, int64_t now_ns, int64_t snd_pos, int32_t term_offset)\n{\n    const int32_t term_length = publication->term_length_mask + 1;\n    const size_t max_vlen = publication->max_messages_per_send;\n    int result = 0, vlen = 0;\n    int64_t bytes_sent = 0;\n    int32_t available_window = (int32_t)(aeron_counter_get_plain(publication->snd_lmt_position.value_addr) - snd_pos);\n    int64_t highest_pos = snd_pos;\n    struct iovec iov[AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND];\n\n    for (size_t i = 0; i < max_vlen && available_window > 0; i++)\n    {\n        int32_t scan_limit = available_window < (int32_t)publication->mtu_length ?\n           available_window : (int32_t)publication->mtu_length;\n        size_t active_index = aeron_logbuffer_index_by_position(snd_pos, publication->position_bits_to_shift);\n        int32_t padding = 0;\n\n        uint8_t *ptr = publication->mapped_raw_log.term_buffers[active_index].addr + term_offset;\n        const int32_t term_length_left = term_length - term_offset;\n        const int32_t available = aeron_term_scanner_scan_for_availability(ptr, term_length_left, scan_limit, &padding);\n\n        if (available > 0)\n        {\n            iov[i].iov_base = ptr;\n            iov[i].iov_len = (uint32_t)available;\n            vlen++;\n\n            int32_t total_available = (int32_t)(available + padding);\n            available_window -= total_available;\n            term_offset += total_available;\n            highest_pos += total_available;\n        }\n        else if (available < 0)\n        {\n            if (publication->track_sender_limits)\n            {\n                aeron_counter_increment_release(publication->snd_bpe_counter.value_addr);\n                aeron_counter_increment_release(publication->sender_flow_control_limits_counter);\n                publication->track_sender_limits = false;\n            }\n            break;\n        }\n\n        if (available == 0 || term_length == term_offset)\n        {\n            break;\n        }\n    }\n\n    if (vlen > 0)\n    {\n        result = aeron_network_publication_do_send(publication, iov, vlen, &bytes_sent);\n        if (result == vlen) /* assume that a partial send from a broken stack will also move the snd-pos */\n        {\n            publication->time_of_last_data_or_heartbeat_ns = now_ns;\n            publication->track_sender_limits = true;\n            aeron_counter_set_release(publication->snd_pos_position.value_addr, highest_pos);\n        }\n        else if (result >= 0)\n        {\n            aeron_counter_increment(publication->short_sends_counter);\n        }\n    }\n    else if (publication->track_sender_limits && available_window <= 0)\n    {\n        aeron_counter_increment_release(publication->snd_bpe_counter.value_addr);\n        aeron_counter_increment_release(publication->sender_flow_control_limits_counter);\n        publication->track_sender_limits = false;\n    }\n\n    return result < 0 ? result : (int)bytes_sent;\n}\n\nint aeron_network_publication_send(aeron_network_publication_t *publication, int64_t now_ns)\n{\n    int64_t snd_pos = aeron_counter_get_plain(publication->snd_pos_position.value_addr);\n    int32_t active_term_id = aeron_logbuffer_compute_term_id_from_position(\n        snd_pos, publication->position_bits_to_shift, publication->initial_term_id);\n    int32_t term_offset = (int32_t)(snd_pos & publication->term_length_mask);\n\n    if (!publication->has_initial_connection || publication->is_setup_elicited)\n    {\n        if (aeron_network_publication_setup_message_check(publication, now_ns, active_term_id, term_offset) < 0)\n        {\n            return -1;\n        }\n    }\n\n    int bytes_sent = aeron_network_publication_send_data(publication, now_ns, snd_pos, term_offset);\n    if (bytes_sent < 0)\n    {\n        return -1;\n    }\n\n    if (0 == bytes_sent)\n    {\n        bool is_end_of_stream;\n        AERON_GET_ACQUIRE(is_end_of_stream, publication->is_end_of_stream);\n\n        bytes_sent = aeron_network_publication_heartbeat_message_check(\n            publication, now_ns, active_term_id, term_offset, is_end_of_stream);\n        if (bytes_sent < 0)\n        {\n            return -1;\n        }\n\n        bool has_spies;\n        AERON_GET_ACQUIRE(has_spies, publication->has_spies);\n\n        if (publication->spies_simulate_connection && has_spies && !publication->has_receivers)\n        {\n            const int64_t new_snd_pos = aeron_network_publication_max_spy_position(publication, snd_pos);\n            aeron_counter_set_release(publication->snd_pos_position.value_addr, new_snd_pos);\n\n            int64_t flow_control_position = publication->flow_control->on_idle(\n                publication->flow_control->state, now_ns, new_snd_pos, new_snd_pos, is_end_of_stream);\n            aeron_counter_set_release(publication->snd_lmt_position.value_addr, flow_control_position);\n        }\n        else\n        {\n            int64_t snd_lmt = aeron_counter_get_plain(publication->snd_lmt_position.value_addr);\n            int64_t flow_control_position = publication->flow_control->on_idle(\n                publication->flow_control->state, now_ns, snd_lmt, snd_pos, is_end_of_stream);\n            aeron_counter_set_release(publication->snd_lmt_position.value_addr, flow_control_position);\n        }\n\n        aeron_network_publication_update_has_receivers(publication, now_ns);\n    }\n\n    aeron_retransmit_handler_process_timeouts(\n        &publication->retransmit_handler, now_ns, aeron_network_publication_resend, publication);\n\n    return bytes_sent;\n}\n\nint aeron_network_publication_resend(void *clientd, int32_t term_id, int32_t term_offset, size_t length)\n{\n    aeron_network_publication_t *publication = (aeron_network_publication_t *)clientd;\n    int64_t sender_position = aeron_counter_get_plain(publication->snd_pos_position.value_addr);\n    int64_t resend_position = aeron_logbuffer_compute_position(\n        term_id, term_offset, publication->position_bits_to_shift, publication->initial_term_id);\n    int32_t term_length = publication->term_length_mask + 1;\n    int64_t bottom_resend_window =\n        sender_position - (int64_t)(term_length >> 1) - (int64_t)aeron_compute_max_message_length(term_length);\n    int result = 0;\n\n    if (bottom_resend_window <= resend_position && resend_position < sender_position)\n    {\n        size_t index = aeron_logbuffer_index_by_position(resend_position, publication->position_bits_to_shift);\n        size_t remaining_bytes = length;\n        int32_t bytes_sent = 0;\n        int32_t total_bytes_sent = 0;\n        int32_t offset = term_offset;\n\n        do\n        {\n            offset += bytes_sent;\n\n            uint8_t *ptr = publication->mapped_raw_log.term_buffers[index].addr + offset;\n            int32_t term_length_left = term_length - offset;\n            int32_t padding = 0;\n            int32_t max_length = remaining_bytes < publication->mtu_length ?\n                (int32_t)remaining_bytes : (int32_t)publication->mtu_length;\n\n            int32_t available = aeron_term_scanner_scan_for_availability(ptr, term_length_left, max_length, &padding);\n            if (available <= 0)\n            {\n                break;\n            }\n\n            struct iovec iov;\n            iov.iov_base = ptr;\n            iov.iov_len = (uint32_t)available;\n            int64_t msg_bytes_sent = 0;\n\n            int sendmsg_result = aeron_network_publication_do_send(publication, &iov, 1, &msg_bytes_sent);\n            if (0 <= sendmsg_result)\n            {\n                if (msg_bytes_sent < (int64_t)iov.iov_len)\n                {\n                    aeron_counter_increment(publication->short_sends_counter);\n                    break;\n                }\n            }\n            else\n            {\n                result = -1;\n                break;\n            }\n\n            bytes_sent = available + padding;\n            total_bytes_sent += bytes_sent;\n            remaining_bytes -= bytes_sent;\n        }\n        while (remaining_bytes > 0);\n\n        if (total_bytes_sent > 0)\n        {\n            aeron_counter_increment_release(publication->retransmits_sent_counter);\n            aeron_counter_get_and_add_release(publication->retransmitted_bytes_counter, total_bytes_sent);\n        }\n    }\n\n    if (NULL != publication->log.resend)\n    {\n        publication->log.resend(\n            publication->session_id,\n            publication->stream_id,\n            term_id,\n            term_offset,\n            (int32_t)length,\n            publication->endpoint->conductor_fields.udp_channel->uri_length,\n            publication->endpoint->conductor_fields.udp_channel->original_uri);\n    }\n\n    return result;\n}\n\nint aeron_network_publication_on_nak(\n    aeron_network_publication_t *publication, int32_t term_id, int32_t term_offset, int32_t length)\n{\n    aeron_counter_increment_release(publication->snd_naks_received_counter.value_addr);\n\n    int result = aeron_retransmit_handler_on_nak(\n        &publication->retransmit_handler,\n        term_id,\n        term_offset,\n        length,\n        (size_t)publication->term_length_mask + 1,\n        publication->mtu_length,\n        publication->flow_control,\n        aeron_clock_cached_nano_time(publication->cached_clock),\n        aeron_network_publication_resend,\n        publication);\n\n    if (0 != result)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n    }\n\n    return result;\n}\n\ninline static bool aeron_network_publication_has_subscribers(aeron_network_publication_t *publication)\n{\n    bool has_receivers;\n    AERON_GET_ACQUIRE(has_receivers, publication->has_receivers);\n\n    return (has_receivers && publication->flow_control->has_required_receivers(publication->flow_control)) ||\n            (publication->spies_simulate_connection &&\n             aeron_driver_subscribable_has_working_positions(&publication->conductor_fields.subscribable));\n}\n\ninline static void aeron_network_publication_update_connected_status(\n    aeron_network_publication_t *publication,\n    bool expected_status)\n{\n    bool is_connected;\n    AERON_GET_ACQUIRE(is_connected, publication->is_connected);\n\n    if (is_connected != expected_status)\n    {\n        AERON_SET_RELEASE(publication->log_meta_data->is_connected, expected_status);\n        AERON_SET_RELEASE(publication->is_connected, expected_status);\n    }\n}\n\nvoid aeron_network_publication_on_status_message(\n    aeron_network_publication_t *publication,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    const uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    const int64_t time_ns = aeron_clock_cached_nano_time(publication->cached_clock);\n    const aeron_status_message_header_t *sm = (aeron_status_message_header_t *)buffer;\n    const bool is_eos = sm->frame_header.flags & AERON_STATUS_MESSAGE_HEADER_EOS_FLAG;\n    publication->status_message_deadline_ns = time_ns + publication->connection_timeout_ns;\n\n    if (is_eos)\n    {\n        aeron_network_publication_liveness_on_remote_close(publication, sm->receiver_id);\n\n        if (aeron_send_channel_is_unicast(publication->endpoint))\n        {\n            AERON_SET_RELEASE(publication->has_received_unicast_eos, true);\n        }\n    }\n    else\n    {\n        aeron_network_publication_liveness_on_status_message(publication, sm->receiver_id, time_ns);\n    }\n\n    const bool is_live = 0 != publication->receiver_liveness_tracker.size;\n    bool existing_has_receivers;\n    AERON_GET_ACQUIRE(existing_has_receivers, publication->has_receivers);\n\n    if (!existing_has_receivers && is_live)\n    {\n        aeron_driver_conductor_proxy_on_response_connected(conductor_proxy, publication->response_correlation_id);\n    }\n\n    if (existing_has_receivers != is_live)\n    {\n        AERON_SET_RELEASE(publication->has_receivers, is_live);\n    }\n\n    if (!publication->has_initial_connection)\n    {\n        publication->has_initial_connection = true;\n    }\n\n    aeron_counter_set_release(\n        publication->snd_lmt_position.value_addr,\n        publication->flow_control->on_status_message(\n            publication->flow_control->state,\n            buffer,\n            length,\n            addr,\n            *publication->snd_lmt_position.value_addr,\n            publication->initial_term_id,\n            publication->position_bits_to_shift,\n            time_ns));\n\n    aeron_network_publication_update_connected_status(\n            publication,\n            aeron_network_publication_has_subscribers(publication));\n}\n\nvoid aeron_network_publication_on_error(\n    aeron_network_publication_t *publication,\n    int64_t destination_registration_id,\n    const uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *src_address,\n    aeron_driver_conductor_proxy_t *conductor_proxy)\n{\n    aeron_error_t *error = (aeron_error_t *)buffer;\n    const uint8_t *error_text = (const uint8_t *)(error + 1);\n    const int64_t time_ns = aeron_clock_cached_nano_time(publication->cached_clock);\n    publication->flow_control->on_error(publication->flow_control->state, buffer, length, src_address, time_ns);\n    if (aeron_network_publication_liveness_on_remote_close(publication, error->receiver_id))\n    {\n        const int64_t registration_id = aeron_network_publication_registration_id(publication);\n        aeron_driver_conductor_proxy_on_publication_error(\n            conductor_proxy,\n            registration_id,\n            destination_registration_id,\n            error->session_id,\n            error->stream_id,\n            error->receiver_id,\n            AERON_ERROR_HAS_GROUP_TAG_FLAG & error->frame_header.flags ? error->group_tag : AERON_NULL_VALUE,\n            src_address,\n            error->error_code,\n            error->error_length,\n            error_text);\n    }\n}\n\nvoid aeron_network_publication_on_rttm(\n    aeron_network_publication_t *publication, const uint8_t *buffer, size_t length, struct sockaddr_storage *addr)\n{\n    aeron_rttm_header_t *rttm_in_header = (aeron_rttm_header_t *)buffer;\n\n    if (rttm_in_header->frame_header.flags & AERON_RTTM_HEADER_REPLY_FLAG)\n    {\n        uint8_t rttm_reply_buffer[sizeof(aeron_rttm_header_t)];\n        aeron_rttm_header_t *rttm_out_header = (aeron_rttm_header_t *)rttm_reply_buffer;\n        struct iovec iov;\n        int64_t bytes_sent;\n\n        rttm_out_header->frame_header.frame_length = sizeof(aeron_rttm_header_t);\n        rttm_out_header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n        rttm_out_header->frame_header.flags = 0;\n        rttm_out_header->frame_header.type = AERON_HDR_TYPE_RTTM;\n        rttm_out_header->session_id = publication->session_id;\n        rttm_out_header->stream_id = publication->stream_id;\n        rttm_out_header->echo_timestamp = rttm_in_header->echo_timestamp;\n        rttm_out_header->reception_delta = 0;\n        rttm_out_header->receiver_id = rttm_in_header->receiver_id;\n\n        iov.iov_base = rttm_reply_buffer;\n        iov.iov_len = sizeof(aeron_rttm_header_t);\n\n        if (0 <= aeron_network_publication_do_send(publication, &iov, 1, &bytes_sent))\n        {\n            if (bytes_sent < (int64_t)iov.iov_len)\n            {\n                aeron_counter_increment(publication->short_sends_counter);\n            }\n        }\n    }\n}\n\nvoid aeron_network_publication_clean_buffer(aeron_network_publication_t *publication, int64_t position)\n{\n    int64_t clean_position = publication->conductor_fields.clean_position;\n    if (position > clean_position)\n    {\n        size_t dirty_index = aeron_logbuffer_index_by_position(clean_position, publication->position_bits_to_shift);\n        size_t bytes_to_clean = (size_t)(position - clean_position);\n        size_t term_length = publication->mapped_raw_log.term_length;\n        size_t term_offset = (size_t)(clean_position & publication->term_length_mask);\n        size_t bytes_left_in_term = term_length - term_offset;\n        size_t length = bytes_to_clean < bytes_left_in_term ? bytes_to_clean : bytes_left_in_term;\n\n        memset(\n            publication->mapped_raw_log.term_buffers[dirty_index].addr + term_offset + sizeof(int64_t),\n            0,\n            length - sizeof(int64_t));\n\n        volatile uint64_t *ptr = (volatile uint64_t *)(publication->mapped_raw_log.term_buffers[dirty_index].addr + term_offset);\n        AERON_SET_RELEASE(*ptr, (uint64_t)0);\n\n        publication->conductor_fields.clean_position = clean_position + (int64_t)length;\n    }\n}\n\nint aeron_network_publication_update_pub_pos_and_lmt(aeron_network_publication_t *publication)\n{\n    int work_count = 0;\n\n    if (AERON_NETWORK_PUBLICATION_STATE_ACTIVE == publication->conductor_fields.state)\n    {\n        const int64_t producer_position = aeron_network_publication_producer_position(publication);\n        int64_t snd_pos = aeron_counter_get_acquire(publication->snd_pos_position.value_addr);\n\n        aeron_counter_set_release(publication->pub_pos_position.value_addr, producer_position);\n\n        if (aeron_network_publication_has_subscribers(publication))\n        {\n            int64_t min_consumer_position = snd_pos;\n            if (publication->conductor_fields.subscribable.length > 0)\n            {\n                for (size_t i = 0, length = publication->conductor_fields.subscribable.length; i < length; i++)\n                {\n                    aeron_tetherable_position_t *tetherable_position =\n                        &publication->conductor_fields.subscribable.array[i];\n\n                    if (aeron_driver_subscribable_is_active_state(tetherable_position->state))\n                    {\n                        int64_t position = aeron_counter_get_acquire(tetherable_position->value_addr);\n                        min_consumer_position = position < min_consumer_position ? position : min_consumer_position;\n                    }\n                }\n            }\n\n            int64_t new_limit_position = min_consumer_position + publication->term_window_length;\n            if (new_limit_position > aeron_counter_get_plain(publication->pub_lmt_position.value_addr))\n            {\n                aeron_network_publication_clean_buffer(publication, min_consumer_position - publication->term_buffer_length);\n                const int64_t clean_position = publication->conductor_fields.clean_position;\n                const int32_t dirty_term_id = aeron_logbuffer_compute_term_id_from_position(\n                    clean_position, publication->position_bits_to_shift, publication->initial_term_id);\n                const int32_t active_term_id = aeron_logbuffer_compute_term_id_from_position(\n                    new_limit_position, publication->position_bits_to_shift, publication->initial_term_id);\n                const int32_t term_gap = aeron_logbuffer_compute_term_count(active_term_id, dirty_term_id);\n                if (term_gap < 2 || (2 == term_gap && 0 != (clean_position & publication->term_length_mask)))\n                {\n                    aeron_counter_set_release(publication->pub_lmt_position.value_addr, new_limit_position);\n                }\n                work_count = 1;\n            }\n        }\n        else if (*publication->pub_lmt_position.value_addr > snd_pos)\n        {\n            aeron_network_publication_update_connected_status(publication, false);\n            aeron_counter_set_release(publication->pub_lmt_position.value_addr, snd_pos);\n            aeron_network_publication_clean_buffer(publication, snd_pos - publication->term_buffer_length);\n            work_count = 1;\n        }\n    }\n\n    return work_count;\n}\n\nvoid aeron_network_publication_check_for_blocked_publisher(\n    aeron_network_publication_t *publication, int64_t now_ns, int64_t producer_position, int64_t snd_pos)\n{\n    if (snd_pos == publication->conductor_fields.last_snd_pos &&\n        aeron_network_publication_is_possibly_blocked(publication, producer_position, snd_pos))\n    {\n        if (now_ns > (publication->conductor_fields.time_of_last_activity_ns + publication->unblock_timeout_ns))\n        {\n            if (aeron_logbuffer_unblocker_unblock(\n                publication->mapped_raw_log.term_buffers, publication->log_meta_data, snd_pos))\n            {\n                aeron_counter_increment_release(publication->unblocked_publications_counter);\n            }\n        }\n    }\n    else\n    {\n        publication->conductor_fields.time_of_last_activity_ns = now_ns;\n        publication->conductor_fields.last_snd_pos = snd_pos;\n    }\n}\n\nvoid aeron_network_publication_handle_managed_resource_event(aeron_driver_managed_resource_event_t event, void *clientd)\n{\n    aeron_network_publication_t *publication = (aeron_network_publication_t *)clientd;\n\n    switch(event)\n    {\n        case AERON_DRIVER_MANAGED_RESOURCE_EVENT_INCREF:\n        {\n            publication->conductor_fields.refcnt++;\n            break;\n        }\n\n        case AERON_DRIVER_MANAGED_RESOURCE_EVENT_DECREF:\n        {\n            int32_t ref_count = --publication->conductor_fields.refcnt;\n\n            if (0 == ref_count)\n            {\n                publication->conductor_fields.time_of_last_activity_ns = aeron_clock_cached_nano_time(\n                    publication->cached_clock);\n\n                const int64_t producer_position = aeron_network_publication_producer_position(publication);\n                aeron_counter_set_release(publication->pub_lmt_position.value_addr, producer_position);\n                AERON_SET_RELEASE(publication->log_meta_data->end_of_stream_position, producer_position);\n\n                uint8_t is_publication_revoked;\n\n                AERON_GET_ACQUIRE(is_publication_revoked, publication->log_meta_data->is_publication_revoked);\n\n                if (!is_publication_revoked)\n                {\n                    if (aeron_counter_get_acquire(publication->snd_pos_position.value_addr) >= producer_position)\n                    {\n                        AERON_SET_RELEASE(publication->is_end_of_stream, true);\n                    }\n\n                    publication->conductor_fields.state = AERON_NETWORK_PUBLICATION_STATE_DRAINING;\n                }\n            }\n            break;\n        }\n\n        case AERON_DRIVER_MANAGED_RESOURCE_EVENT_REVOKE:\n        {\n            AERON_SET_RELEASE(publication->log_meta_data->is_publication_revoked, true);\n            break;\n        }\n    }\n}\n\nbool aeron_network_publication_spies_finished_consuming(\n    aeron_network_publication_t *publication, aeron_driver_conductor_t *conductor, int64_t eos_pos)\n{\n    if (aeron_driver_subscribable_has_working_positions(&publication->conductor_fields.subscribable))\n    {\n        for (size_t i = 0, length = publication->conductor_fields.subscribable.length; i < length; i++)\n        {\n            aeron_tetherable_position_t *tetherable_position = &publication->conductor_fields.subscribable.array[i];\n\n            if (aeron_driver_subscribable_is_active_state(tetherable_position->state))\n            {\n                if (aeron_counter_get_acquire(tetherable_position->value_addr) < eos_pos)\n                {\n                    return false;\n                }\n            }\n        }\n\n        AERON_SET_RELEASE(publication->has_spies, false);\n        aeron_driver_conductor_cleanup_spies(conductor, publication);\n\n        for (size_t i = 0, length = publication->conductor_fields.subscribable.length; i < length; i++)\n        {\n            aeron_counters_manager_free(\n                &conductor->counters_manager, publication->conductor_fields.subscribable.array[i].counter_id);\n        }\n\n        aeron_free(publication->conductor_fields.subscribable.array);\n        publication->conductor_fields.subscribable.array = NULL;\n        publication->conductor_fields.subscribable.length = 0;\n        publication->conductor_fields.subscribable.capacity = 0;\n        publication->conductor_fields.subscribable.inactive_count = 0;\n    }\n\n    return true;\n}\n\nvoid aeron_network_publication_check_untethered_subscriptions(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_t *publication, int64_t now_ns)\n{\n    const int64_t sender_position = aeron_counter_get_acquire(publication->snd_pos_position.value_addr);\n    int64_t term_window_length = publication->term_window_length;\n    int64_t untethered_window_limit = (sender_position - term_window_length) + (term_window_length / 4);\n\n    aeron_subscribable_t *subscribable = &publication->conductor_fields.subscribable;\n    for (size_t i = 0, length = subscribable->length; i < length; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &subscribable->array[i];\n\n        if (tetherable_position->is_tether)\n        {\n            tetherable_position->time_of_last_update_ns = now_ns;\n        }\n        else\n        {\n            int64_t window_limit_timeout_ns = publication->untethered_window_limit_timeout_ns;\n            int64_t resting_timeout_ns = publication->untethered_resting_timeout_ns;\n            int64_t linger_timeout_ns = publication->untethered_linger_timeout_ns;\n\n            switch (tetherable_position->state)\n            {\n                case AERON_SUBSCRIPTION_TETHER_ACTIVE:\n                    if (aeron_counter_get_acquire(tetherable_position->value_addr) > untethered_window_limit)\n                    {\n                        tetherable_position->time_of_last_update_ns = now_ns;\n                    }\n                    else if (now_ns > (tetherable_position->time_of_last_update_ns + window_limit_timeout_ns))\n                    {\n                        aeron_driver_conductor_on_unavailable_image(\n                            conductor,\n                            publication->conductor_fields.managed_resource.registration_id,\n                            tetherable_position->subscription_registration_id,\n                            publication->stream_id,\n                            AERON_IPC_CHANNEL,\n                            AERON_IPC_CHANNEL_LEN);\n\n                        aeron_driver_subscribable_state(\n                            subscribable,\n                            tetherable_position,\n                            AERON_SUBSCRIPTION_TETHER_LINGER,\n                            now_ns,\n                            publication->stream_id,\n                            publication->session_id,\n                            publication->log.untethered_subscription_state_change);\n                    }\n                    break;\n\n                case AERON_SUBSCRIPTION_TETHER_LINGER:\n                    if (now_ns > (tetherable_position->time_of_last_update_ns + linger_timeout_ns))\n                    {\n                        if (tetherable_position->is_rejoin)\n                        {\n                            aeron_driver_subscribable_state(\n                                subscribable,\n                                tetherable_position,\n                                AERON_SUBSCRIPTION_TETHER_RESTING,\n                                now_ns,\n                                publication->stream_id,\n                                publication->session_id,\n                                publication->log.untethered_subscription_state_change);\n                        }\n                        else\n                        {\n                            aeron_driver_subscribable_state(\n                                subscribable,\n                                tetherable_position,\n                                AERON_SUBSCRIPTION_TETHER_CLOSED,\n                                now_ns,\n                                publication->stream_id,\n                                publication->session_id,\n                                publication->log.untethered_subscription_state_change);\n\n                            if (0 == aeron_counters_manager_free(&conductor->counters_manager, tetherable_position->counter_id))\n                            {\n                                tetherable_position->counter_id = AERON_NULL_COUNTER_ID; // prevent double free\n                            }\n                        }\n                    }\n                    break;\n\n                case AERON_SUBSCRIPTION_TETHER_RESTING:\n                    if (now_ns > (tetherable_position->time_of_last_update_ns + resting_timeout_ns))\n                    {\n                        aeron_counter_set_release(tetherable_position->value_addr, sender_position);\n\n                        aeron_driver_conductor_on_available_image(\n                            conductor,\n                            publication->conductor_fields.managed_resource.registration_id,\n                            publication->stream_id,\n                            publication->session_id,\n                            publication->log_file_name,\n                            publication->log_file_name_length,\n                            tetherable_position->counter_id,\n                            tetherable_position->subscription_registration_id,\n                            AERON_IPC_CHANNEL,\n                            AERON_IPC_CHANNEL_LEN);\n\n                        aeron_driver_subscribable_state(\n                            subscribable,\n                            tetherable_position,\n                            AERON_SUBSCRIPTION_TETHER_ACTIVE,\n                            now_ns,\n                            publication->stream_id,\n                            publication->session_id,\n                            publication->log.untethered_subscription_state_change);\n                    }\n                    break;\n\n                case AERON_SUBSCRIPTION_TETHER_CLOSED:\n                    break;\n            }\n        }\n    }\n}\n\nvoid aeron_network_publication_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_t *publication, int64_t now_ns, int64_t now_ms)\n{\n    switch (publication->conductor_fields.state)\n    {\n        case AERON_NETWORK_PUBLICATION_STATE_ACTIVE:\n        {\n            uint8_t is_publication_revoked;\n\n            AERON_GET_ACQUIRE(is_publication_revoked, publication->log_meta_data->is_publication_revoked);\n\n            if (is_publication_revoked)\n            {\n                int64_t revoked_position = aeron_network_publication_producer_position(publication);\n                aeron_counter_set_release(publication->pub_lmt_position.value_addr, revoked_position);\n                AERON_SET_RELEASE(publication->log_meta_data->end_of_stream_position, revoked_position);\n                aeron_network_publication_update_connected_status(publication, false);\n\n                AERON_SET_RELEASE(publication->is_end_of_stream, true);\n\n                aeron_driver_conductor_cleanup_spies(conductor, publication);\n\n                publication->conductor_fields.state = AERON_NETWORK_PUBLICATION_STATE_LINGER;\n\n                aeron_driver_publication_revoke_func_t publication_revoke = publication->log.publication_revoke;\n                if (NULL != publication_revoke)\n                {\n                    publication_revoke(\n                        revoked_position,\n                        publication->session_id,\n                        publication->stream_id,\n                        publication->endpoint->conductor_fields.udp_channel->uri_length,\n                        publication->endpoint->conductor_fields.udp_channel->original_uri);\n                }\n\n                aeron_counter_increment_release(publication->publications_revoked_counter);\n            }\n            else\n            {\n                aeron_network_publication_check_untethered_subscriptions(conductor, publication, now_ns);\n\n                aeron_network_publication_update_connected_status(\n                        publication, aeron_network_publication_has_subscribers(publication));\n\n                const int64_t producer_position = aeron_network_publication_producer_position(publication);\n                aeron_counter_set_release(publication->pub_pos_position.value_addr, producer_position);\n\n                if (!publication->is_exclusive)\n                {\n                    aeron_network_publication_check_for_blocked_publisher(\n                        publication,\n                        now_ns,\n                        producer_position,\n                        aeron_counter_get_acquire(publication->snd_pos_position.value_addr));\n                }\n            }\n            break;\n        }\n\n        case AERON_NETWORK_PUBLICATION_STATE_DRAINING:\n        {\n            const int64_t producer_position = aeron_network_publication_producer_position(publication);\n            aeron_counter_set_release(publication->pub_pos_position.value_addr, producer_position);\n\n            const int64_t sender_position = aeron_counter_get_acquire(publication->snd_pos_position.value_addr);\n\n            if (producer_position > sender_position)\n            {\n                if (aeron_logbuffer_unblocker_unblock(\n                    publication->mapped_raw_log.term_buffers, publication->log_meta_data, sender_position))\n                {\n                    aeron_counter_increment_release(publication->unblocked_publications_counter);\n                    break;\n                }\n\n                bool has_receivers;\n                AERON_GET_ACQUIRE(has_receivers, publication->has_receivers);\n                if (has_receivers)\n                {\n                    break;\n                }\n            }\n            else\n            {\n                AERON_SET_RELEASE(publication->is_end_of_stream, true);\n            }\n\n            if (aeron_network_publication_spies_finished_consuming(publication, conductor, producer_position))\n            {\n                publication->conductor_fields.time_of_last_activity_ns = now_ns;\n                publication->conductor_fields.state = AERON_NETWORK_PUBLICATION_STATE_LINGER;\n            }\n            break;\n        }\n\n        case AERON_NETWORK_PUBLICATION_STATE_LINGER:\n        {\n            bool has_received_unicast_eos = false;\n            AERON_GET_ACQUIRE(has_received_unicast_eos, publication->has_received_unicast_eos);\n\n            if (publication->conductor_fields.refcnt <= 0 &&\n                (has_received_unicast_eos ||\n                now_ns > (publication->conductor_fields.time_of_last_activity_ns + publication->linger_timeout_ns)))\n            {\n                aeron_driver_conductor_cleanup_network_publication(conductor, publication);\n                publication->conductor_fields.state = AERON_NETWORK_PUBLICATION_STATE_DONE;\n            }\n            break;\n        }\n\n        case AERON_NETWORK_PUBLICATION_STATE_DONE:\n            break;\n    }\n}\n\nvoid aeron_network_publication_add_subscriber_hook(void *clientd, volatile int64_t *value_addr)\n{\n    aeron_network_publication_t *publication = (aeron_network_publication_t *)clientd;\n\n    AERON_SET_RELEASE(publication->has_spies, true);\n    if (publication->spies_simulate_connection)\n    {\n        aeron_network_publication_update_connected_status(publication, true);\n    }\n}\n\nvoid aeron_network_publication_remove_subscriber_hook(void *clientd, volatile int64_t *value_addr)\n{\n    aeron_network_publication_t *publication = (aeron_network_publication_t *)clientd;\n\n    if (1 == aeron_driver_subscribable_working_position_count(&publication->conductor_fields.subscribable))\n    {\n        AERON_SET_RELEASE(publication->has_spies, false);\n    }\n\n    if (publication->spies_simulate_connection)\n    {\n        aeron_network_publication_update_connected_status(\n                publication, aeron_network_publication_has_subscribers(publication));\n    }\n}\n\nextern bool aeron_network_publication_is_possibly_blocked(\n    aeron_network_publication_t *publication, int64_t producer_position, int64_t consumer_position);\n\nextern int64_t aeron_network_publication_producer_position(aeron_network_publication_t *publication);\n\nextern int64_t aeron_network_publication_join_position(aeron_network_publication_t *publication);\n\nextern void aeron_network_publication_trigger_send_setup_frame(\n    aeron_network_publication_t *publication, uint8_t *buffer, size_t length, struct sockaddr_storage *addr);\n\nextern void aeron_network_publication_sender_release(aeron_network_publication_t *publication);\n\nextern bool aeron_network_publication_has_sender_released(aeron_network_publication_t *publication);\n\nextern int64_t aeron_network_publication_max_spy_position(aeron_network_publication_t *publication, int64_t snd_pos);\n\nextern bool aeron_network_publication_is_accepting_subscriptions(aeron_network_publication_t *publication);\n\nextern inline int64_t aeron_network_publication_registration_id(aeron_network_publication_t *publication);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_network_publication.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_NETWORK_PUBLICATION_H\n#define AERON_NETWORK_PUBLICATION_H\n\n#include \"util/aeron_bitutil.h\"\n#include \"util/aeron_fileutil.h\"\n#include \"uri/aeron_driver_uri.h\"\n#include \"aeron_driver_common.h\"\n#include \"aeron_driver_context.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"aeron_system_counters.h\"\n#include \"aeron_retransmit_handler.h\"\n\ntypedef enum aeron_network_publication_state_enum\n{\n    AERON_NETWORK_PUBLICATION_STATE_ACTIVE,\n    AERON_NETWORK_PUBLICATION_STATE_DRAINING,\n    AERON_NETWORK_PUBLICATION_STATE_LINGER,\n    AERON_NETWORK_PUBLICATION_STATE_DONE\n}\naeron_network_publication_state_t;\n\n#define AERON_NETWORK_PUBLICATION_HEARTBEAT_TIMEOUT_NS (100 * 1000 * 1000LL)\n#define AERON_NETWORK_PUBLICATION_SETUP_TIMEOUT_NS (100 * 1000 * 1000LL)\n\ntypedef struct aeron_send_channel_endpoint_stct aeron_send_channel_endpoint_t;\ntypedef struct aeron_driver_conductor_stct aeron_driver_conductor_t;\n\ntypedef struct aeron_network_publication_stct\n{\n    struct aeron_network_publication_conductor_fields_stct\n    {\n        bool has_reached_end_of_life;\n        aeron_network_publication_state_t state;\n        int32_t refcnt;\n        aeron_driver_managed_resource_t managed_resource;\n        aeron_subscribable_t subscribable;\n        int64_t clean_position;\n        int64_t time_of_last_activity_ns;\n        int64_t last_snd_pos;\n    }\n    conductor_fields;\n\n    uint8_t conductor_fields_pad[\n        (4 * AERON_CACHE_LINE_LENGTH) - sizeof(struct aeron_network_publication_conductor_fields_stct)];\n\n    aeron_mapped_raw_log_t mapped_raw_log;\n    aeron_position_t pub_pos_position;\n    aeron_position_t pub_lmt_position;\n    aeron_position_t snd_pos_position;\n    aeron_position_t snd_lmt_position;\n    aeron_atomic_counter_t snd_bpe_counter;\n    aeron_atomic_counter_t snd_naks_received_counter;\n    aeron_retransmit_handler_t retransmit_handler;\n    aeron_logbuffer_metadata_t *log_meta_data;\n    aeron_send_channel_endpoint_t *endpoint;\n    aeron_flow_control_strategy_t *flow_control;\n    aeron_clock_cache_t *cached_clock;\n\n    uint8_t sender_fields_pad_lhs[AERON_CACHE_LINE_LENGTH];\n    bool has_initial_connection;\n    bool track_sender_limits;\n    int64_t time_of_last_data_or_heartbeat_ns;\n    int64_t status_message_deadline_ns;\n    int64_t time_of_last_setup_ns;\n    uint8_t sender_fields_pad_rhs[AERON_CACHE_LINE_LENGTH];\n\n    struct sockaddr_storage endpoint_address;\n\n    char *log_file_name;\n    int64_t term_buffer_length;\n    int64_t term_window_length;\n    int64_t trip_gain;\n    int64_t linger_timeout_ns;\n    int64_t unblock_timeout_ns;\n    int64_t connection_timeout_ns;\n    int64_t untethered_window_limit_timeout_ns;\n    int64_t untethered_linger_timeout_ns;\n    int64_t untethered_resting_timeout_ns;\n\n    int64_t tag;\n    int64_t response_correlation_id;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t initial_term_id;\n    int32_t starting_term_id;\n    int32_t term_length_mask;\n    size_t starting_term_offset;\n    size_t log_file_name_length;\n    size_t position_bits_to_shift;\n    size_t mtu_length;\n    size_t max_messages_per_send;\n    bool spies_simulate_connection;\n    bool signal_eos;\n    bool is_setup_elicited;\n    bool is_exclusive;\n    bool is_response;\n    volatile bool has_receivers;\n    volatile bool has_spies;\n    volatile bool is_connected;\n    volatile bool is_end_of_stream;\n    volatile bool has_sender_released;\n    volatile bool has_received_unicast_eos;\n    aeron_raw_log_close_func_t raw_log_close_func;\n    aeron_raw_log_free_func_t raw_log_free_func;\n    struct\n    {\n        aeron_untethered_subscription_state_change_func_t untethered_subscription_state_change;\n        aeron_driver_resend_func_t resend;\n        aeron_driver_publication_revoke_func_t publication_revoke;\n    } log;\n\n    volatile int64_t *short_sends_counter;\n    volatile int64_t *heartbeats_sent_counter;\n    volatile int64_t *sender_flow_control_limits_counter;\n    volatile int64_t *retransmits_sent_counter;\n    volatile int64_t *retransmitted_bytes_counter;\n    volatile int64_t *unblocked_publications_counter;\n    volatile int64_t *publications_revoked_counter;\n    volatile int64_t *mapped_bytes_counter;\n\n    aeron_int64_counter_map_t receiver_liveness_tracker;\n}\naeron_network_publication_t;\n\nint aeron_network_publication_create(\n    aeron_network_publication_t **publication,\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_driver_context_t *context,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t initial_term_id,\n    aeron_position_t *pub_pos_position,\n    aeron_position_t *pub_lmt_position,\n    aeron_position_t *snd_pos_position,\n    aeron_position_t *snd_lmt_position,\n    aeron_atomic_counter_t *snd_bpe_counter,\n    aeron_atomic_counter_t *snd_naks_received_counter,\n    aeron_flow_control_strategy_t *flow_control_strategy,\n    aeron_driver_uri_publication_params_t *params,\n    bool is_exclusive,\n    aeron_system_counters_t *system_counters);\n\nvoid aeron_network_publication_close(\n    aeron_counters_manager_t *counters_manager, aeron_network_publication_t *publication);\n\nbool aeron_network_publication_free(aeron_network_publication_t *publication);\n\nvoid aeron_network_publication_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_network_publication_t *publication, int64_t now_ns, int64_t now_ms);\n\nint aeron_network_publication_send(aeron_network_publication_t *publication, int64_t now_ns);\nint aeron_network_publication_resend(void *clientd, int32_t term_id, int32_t term_offset, size_t length);\n\nint aeron_network_publication_send_data(\n    aeron_network_publication_t *publication, int64_t now_ns, int64_t snd_pos, int32_t term_offset);\n\nint aeron_network_publication_on_nak(\n    aeron_network_publication_t *publication, int32_t term_id, int32_t term_offset, int32_t length);\n\nvoid aeron_network_publication_on_status_message(\n    aeron_network_publication_t *publication,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    const uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nvoid aeron_network_publication_on_error(\n    aeron_network_publication_t *publication,\n    int64_t destination_registration_id,\n    const uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *src_address,\n    aeron_driver_conductor_proxy_t *pStct);\n\nvoid aeron_network_publication_on_rttm(\n    aeron_network_publication_t *publication, const uint8_t *buffer, size_t length, struct sockaddr_storage *addr);\n\nvoid aeron_network_publication_clean_buffer(aeron_network_publication_t *publication, int64_t position);\n\nint aeron_network_publication_update_pub_pos_and_lmt(aeron_network_publication_t *publication);\n\nvoid aeron_network_publication_check_for_blocked_publisher(\n    aeron_network_publication_t *publication, int64_t now_ns, int64_t producer_position, int64_t snd_pos);\n\nvoid aeron_network_publication_add_subscriber_hook(void *clientd, volatile int64_t *value_addr);\n\nvoid aeron_network_publication_remove_subscriber_hook(void *clientd, volatile int64_t *value_addr);\n\ninline bool aeron_network_publication_is_possibly_blocked(\n    aeron_network_publication_t *publication, int64_t producer_position, int64_t consumer_position)\n{\n    int32_t producer_term_count;\n\n    AERON_GET_ACQUIRE(producer_term_count, publication->log_meta_data->active_term_count);\n    const int32_t expected_term_count = (int32_t)(consumer_position >> publication->position_bits_to_shift);\n\n    if (producer_term_count != expected_term_count)\n    {\n        return true;\n    }\n\n    return producer_position > consumer_position;\n}\n\ninline int64_t aeron_network_publication_producer_position(aeron_network_publication_t *publication)\n{\n    int64_t raw_tail;\n\n    AERON_LOGBUFFER_RAWTAIL_VOLATILE(raw_tail, publication->log_meta_data);\n\n    return aeron_logbuffer_compute_position(\n        aeron_logbuffer_term_id(raw_tail),\n        aeron_logbuffer_term_offset(raw_tail, (int32_t)publication->mapped_raw_log.term_length),\n        publication->position_bits_to_shift,\n        publication->initial_term_id);\n}\n\ninline int64_t aeron_network_publication_join_position(aeron_network_publication_t *publication)\n{\n    return aeron_counter_get_acquire(publication->snd_pos_position.value_addr);\n}\n\ninline void aeron_network_publication_trigger_send_setup_frame(\n    aeron_network_publication_t *publication,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    const int64_t time_ns = aeron_clock_cached_nano_time(publication->cached_clock);\n    bool is_end_of_stream;\n    AERON_GET_ACQUIRE(is_end_of_stream, publication->is_end_of_stream);\n\n    if (!is_end_of_stream)\n    {\n        publication->is_setup_elicited = true;\n        publication->flow_control->on_trigger_send_setup(\n            publication->flow_control->state,\n            buffer,\n            length,\n            addr,\n            time_ns);\n\n        if (publication->is_response)\n        {\n            if (AF_INET == addr->ss_family)\n            {\n                memcpy(&publication->endpoint_address, addr, sizeof(struct sockaddr_in));\n            }\n            else if (AF_INET6 == addr->ss_family)\n            {\n                memcpy(&publication->endpoint_address, addr, sizeof(struct sockaddr_in6));\n            }\n        }\n    }\n}\n\ninline void aeron_network_publication_sender_release(aeron_network_publication_t *publication)\n{\n    AERON_SET_RELEASE(publication->has_sender_released, true);\n}\n\ninline bool aeron_network_publication_has_sender_released(aeron_network_publication_t *publication)\n{\n    bool has_sender_released;\n    AERON_GET_ACQUIRE(has_sender_released, publication->has_sender_released);\n\n    return has_sender_released;\n}\n\ninline int64_t aeron_network_publication_max_spy_position(aeron_network_publication_t *publication, int64_t snd_pos)\n{\n    int64_t position = snd_pos;\n\n    for (size_t i = 0, length = publication->conductor_fields.subscribable.length; i < length; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &publication->conductor_fields.subscribable.array[i];\n        int64_t spy_position = aeron_counter_get_acquire(tetherable_position->value_addr);\n\n        if (aeron_driver_subscribable_is_active_state(tetherable_position->state))\n        {\n            position = spy_position > position ? spy_position : position;\n        }\n    }\n\n    return position;\n}\n\ninline bool aeron_network_publication_is_accepting_subscriptions(aeron_network_publication_t *publication)\n{\n    return AERON_NETWORK_PUBLICATION_STATE_ACTIVE == publication->conductor_fields.state ||\n        (AERON_NETWORK_PUBLICATION_STATE_DRAINING == publication->conductor_fields.state &&\n            aeron_driver_subscribable_has_working_positions(&publication->conductor_fields.subscribable) &&\n         aeron_network_publication_producer_position(publication) >\n             aeron_counter_get_acquire(publication->snd_pos_position.value_addr));\n}\n\ninline int64_t aeron_network_publication_registration_id(aeron_network_publication_t *publication)\n{\n    return publication->log_meta_data->correlation_id;\n}\n\n#endif //AERON_NETWORK_PUBLICATION_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_port_manager.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_port_manager.h\"\n#include \"media/aeron_udp_channel.h\"\n#include \"util/aeron_error.h\"\n#include \"inttypes.h\"\n\nuint16_t aeron_wildcard_port_manager_get_port(struct sockaddr_storage *addr)\n{\n    if (addr->ss_family == AF_INET)\n    {\n        return ntohs(((struct sockaddr_in *)addr)->sin_port);\n    }\n    else if (addr->ss_family == AF_INET6)\n    {\n        return ntohs(((struct sockaddr_in6 *)addr)->sin6_port);\n    }\n\n    return 0;\n}\n\nint aeron_wildcard_port_manager_init(aeron_wildcard_port_manager_t *port_manager, bool is_sender)\n{\n    port_manager->port_manager.get_managed_port = aeron_wildcard_port_manager_get_managed_port;\n    port_manager->port_manager.free_managed_port = aeron_wildcard_port_manager_free_managed_port;\n    port_manager->port_manager.state = port_manager;\n\n    if (aeron_int64_counter_map_init(\n        &port_manager->port_table, 0, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"could not init wildcard port manager map\");\n        return -1;\n    }\n\n    port_manager->low_port = 0;\n    port_manager->high_port = 0;\n    port_manager->next_port = 0;\n    port_manager->is_os_wildcard = true;\n    port_manager->is_sender = is_sender;\n\n    return 0;\n}\n\nvoid aeron_wildcard_port_manager_set_range(\n    aeron_wildcard_port_manager_t *port_manager, uint16_t low_port, uint16_t high_port)\n{\n    port_manager->low_port = low_port;\n    port_manager->high_port = high_port;\n    port_manager->next_port = low_port;\n    port_manager->is_os_wildcard = low_port == high_port && 0 == low_port;\n}\n\nvoid aeron_wildcard_port_manager_delete(aeron_wildcard_port_manager_t *port_manager)\n{\n    aeron_int64_counter_map_delete(&port_manager->port_table);\n}\n\nuint16_t aeron_wildcard_port_manager_find_open_port(aeron_wildcard_port_manager_t *port_manager)\n{\n    for (uint16_t i = port_manager->next_port; i <= port_manager->high_port; i++)\n    {\n        if (aeron_int64_counter_map_get(&port_manager->port_table, i) == 0)\n        {\n            return i;\n        }\n    }\n\n    for (uint16_t i = port_manager->low_port; i <= port_manager->next_port; i++)\n    {\n        if (aeron_int64_counter_map_get(&port_manager->port_table, i) == 0)\n        {\n            return i;\n        }\n    }\n\n    return 0;\n}\n\nint aeron_wildcard_port_manager_allocate_open_port(aeron_wildcard_port_manager_t *port_manager)\n{\n    uint16_t port = aeron_wildcard_port_manager_find_open_port(port_manager);\n\n    if (0 == port)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"no available ports in range %\" PRIu16 \" %\" PRIu16,\n            port_manager->low_port, port_manager->high_port);\n        return -1;\n    }\n\n    port_manager->next_port = port + 1;\n    if (port_manager->next_port > port_manager->high_port)\n    {\n        port_manager->next_port = port_manager->low_port;\n    }\n\n    if (aeron_int64_counter_map_add_and_get(&port_manager->port_table, port, 1, NULL) == -1)\n    {\n        AERON_APPEND_ERR(\"%s\", \"could not add to wildcard port manager map\");\n        return -1;\n    }\n\n    return port;\n}\n\nint aeron_wildcard_port_manager_get_managed_port(\n    void *state,\n    struct sockaddr_storage *bind_addr_out,\n    aeron_udp_channel_t *udp_channel,\n    struct sockaddr_storage *bind_addr)\n{\n    aeron_wildcard_port_manager_t *port_manager = (aeron_wildcard_port_manager_t *)state;\n    uint16_t bind_port_in = aeron_wildcard_port_manager_get_port(bind_addr);\n\n    memcpy(bind_addr_out, bind_addr, sizeof(struct sockaddr_storage));\n\n    if (0 != bind_port_in)\n    {\n        if (aeron_int64_counter_map_add_and_get(&port_manager->port_table, bind_port_in, 1, NULL) == -1)\n        {\n            AERON_APPEND_ERR(\"%s\", \"could not add to wildcard port manager map\");\n            return -1;\n        }\n    }\n    else if (!port_manager->is_os_wildcard)\n    {\n        if (!port_manager->is_sender || udp_channel->has_explicit_control)\n        {\n            int bind_port_out = aeron_wildcard_port_manager_allocate_open_port(port_manager);\n\n            if (bind_port_out < 0)\n            {\n                return -1;\n            }\n\n            if (bind_addr_out->ss_family == AF_INET)\n            {\n                ((struct sockaddr_in *)bind_addr_out)->sin_port = htons((uint16_t)bind_port_out);\n            }\n            else if (bind_addr_out->ss_family == AF_INET6)\n            {\n                ((struct sockaddr_in6 *)bind_addr_out)->sin6_port = htons((uint16_t)bind_port_out);\n            }\n        }\n    }\n\n    return 0;\n}\n\nvoid aeron_wildcard_port_manager_free_managed_port(void *state, struct sockaddr_storage *bind_addr)\n{\n    aeron_wildcard_port_manager_t *port_manager = (aeron_wildcard_port_manager_t *)state;\n    uint16_t bind_port_in = aeron_wildcard_port_manager_get_port(bind_addr);\n\n    if (0 != bind_port_in)\n    {\n        aeron_int64_counter_map_remove(&port_manager->port_table, bind_port_in);\n    }\n}\n\nint aeron_parse_port_range(const char *range_str, uint16_t *low_port, uint16_t *high_port)\n{\n    errno = 0;\n    char *end = \"\";\n    int64_t v = strtoll(range_str, &end, 10);\n    if ((0 == v && 0 != errno) || v < 0 || v > 65535 || end == range_str)\n    {\n        const int err = 0 != errno ? errno : EINVAL;\n        AERON_SET_ERR(err, \"%s\", \"failed to parse first part of port range\");\n        return -1;\n    }\n\n    const uint16_t low = (uint16_t)v;\n    range_str = end;\n    end = \"\";\n\n    v = strtoll(range_str, &end, 10);\n    if ((0 == v && 0 != errno) || v < 0 || v > 65535 || end == range_str)\n    {\n        const int err = 0 != errno ? errno : EINVAL;\n        AERON_SET_ERR(err, \"%s\", \"failed to parse second part of port range\");\n        return -1;\n    }\n\n    const uint16_t high = (uint16_t)v;\n\n    if (low > high)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"low port should be less than or equal to high port\");\n        return -1;\n    }\n\n    *low_port = low;\n    *high_port = high;\n    return 0;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_port_manager.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_PORT_MANAGER_H\n#define AERON_PORT_MANAGER_H\n\n#include \"aeron_driver_common.h\"\n#include \"collections/aeron_int64_counter_map.h\"\n\nstruct sockaddr_storage;\ntypedef struct aeron_udp_channel_stct aeron_udp_channel_t;\n\ntypedef int (*aeron_port_manager_get_managed_port_func_t)(\n    void *state,\n    struct sockaddr_storage *bind_addr_out,\n    aeron_udp_channel_t *udp_channel,\n    struct sockaddr_storage *bind_addr);\ntypedef void (*aeron_port_manager_free_managed_port_func_t)(void *state, struct sockaddr_storage *bind_addr);\n\ntypedef struct aeron_port_manager_stct\n{\n    aeron_port_manager_get_managed_port_func_t get_managed_port;\n    aeron_port_manager_free_managed_port_func_t free_managed_port;\n    void *state;\n}\naeron_port_manager_t;\n\ntypedef struct aeron_wildcard_port_manager_stct {\n    aeron_port_manager_t port_manager;\n    aeron_int64_counter_map_t port_table;\n    uint16_t low_port;\n    uint16_t high_port;\n    uint16_t next_port;\n    bool is_sender;\n    bool is_os_wildcard;\n}\naeron_wildcard_port_manager_t;\n\nint aeron_wildcard_port_manager_get_managed_port(\n    void *state,\n    struct sockaddr_storage *bind_addr_out,\n    aeron_udp_channel_t *udp_channel,\n    struct sockaddr_storage *bind_addr);\nvoid aeron_wildcard_port_manager_free_managed_port(void *state, struct sockaddr_storage *bind_addr);\n\nint aeron_wildcard_port_manager_init(\n    aeron_wildcard_port_manager_t *port_manager, bool is_sender);\n\nvoid aeron_wildcard_port_manager_set_range(\n    aeron_wildcard_port_manager_t *port_manager, uint16_t low_port, uint16_t high_port);\n\nvoid aeron_wildcard_port_manager_delete(aeron_wildcard_port_manager_t *port_manager);\nint aeron_parse_port_range(const char *range_str, uint16_t *low_port, uint16_t *high_port);\n\n#endif // AERON_PORT_MANAGER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_position.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <inttypes.h>\n#include <string.h>\n#include \"aeron_position.h\"\n#include \"aeron_driver_context.h\"\n\nint32_t aeron_stream_counter_allocate(\n    aeron_counters_manager_t *counters_manager,\n    const char *name,\n    int32_t type_id,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    const char *suffix)\n{\n    char label[AERON_COUNTER_MAX_LABEL_LENGTH];\n    int total_length = snprintf(\n        label, sizeof(label), \"%s: %\" PRId64 \" %\" PRId32 \" %\" PRId32 \" %.*s%s\",\n        name, registration_id, session_id, stream_id, (int)channel_length, channel, suffix);\n    size_t label_length = AERON_MIN(AERON_COUNTER_MAX_LABEL_LENGTH, (size_t)total_length);\n\n    aeron_stream_position_counter_key_layout_t layout =\n        {\n            .registration_id = registration_id,\n            .session_id = session_id,\n            .stream_id = stream_id,\n        };\n\n    size_t key_channel_length = channel_length > sizeof(layout.channel) ? sizeof(layout.channel) : channel_length;\n    layout.channel_length = (int32_t)key_channel_length;\n    memcpy(layout.channel, channel, key_channel_length);\n\n    int32_t counter_id = aeron_counters_manager_allocate(\n        counters_manager, type_id, (const uint8_t *)&layout, sizeof(layout), label, label_length);\n\n    if (counter_id >= 0)\n    {\n        aeron_counters_manager_counter_registration_id(counters_manager, counter_id, registration_id);\n        aeron_counters_manager_counter_owner_id(counters_manager, counter_id, client_id);\n    }\n\n    return counter_id;\n}\n\nint32_t aeron_counter_publisher_limit_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_COUNTER_PUBLISHER_LIMIT_NAME,\n        AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        \"\");\n}\n\nint32_t aeron_counter_subscription_position_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    int64_t joining_position)\n{\n    char buffer[64];\n\n    snprintf(buffer, sizeof(buffer) - 1, \" @%\" PRId64, joining_position);\n\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_COUNTER_SUBSCRIPTION_POSITION_NAME,\n        AERON_COUNTER_SUBSCRIPTION_POSITION_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        buffer);\n}\n\nint32_t aeron_counter_sender_position_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_COUNTER_SENDER_POSITION_NAME,\n        AERON_COUNTER_SENDER_POSITION_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        \"\");\n}\n\nint32_t aeron_counter_sender_limit_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_COUNTER_SENDER_LIMIT_NAME,\n        AERON_COUNTER_SENDER_LIMIT_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        \"\");\n}\n\nint32_t aeron_counter_receiver_hwm_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_COUNTER_RECEIVER_HWM_NAME,\n        AERON_COUNTER_RECEIVER_HWM_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        \"\");\n}\n\nint32_t aeron_counter_receiver_position_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_COUNTER_RECEIVER_POSITION_NAME,\n        AERON_COUNTER_RECEIVER_POSITION_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        \"\");\n}\n\nint32_t aeron_channel_endpoint_status_allocate(\n    aeron_counters_manager_t *counters_manager,\n    const char *name,\n    int32_t type_id,\n    int64_t registration_id,\n    size_t channel_length,\n    const char *channel)\n{\n    char label[AERON_COUNTER_MAX_LABEL_LENGTH];\n    int total_length = snprintf(label, sizeof(label), \"%s: %.*s\", name, (int)channel_length, channel);\n    size_t label_length = AERON_MIN(AERON_COUNTER_MAX_LABEL_LENGTH, (size_t)total_length);\n\n    aeron_channel_endpoint_status_key_layout_t layout;\n    size_t key_channel_length = channel_length > sizeof(layout.channel) ? sizeof(layout.channel) : channel_length;\n    layout.channel_length = (int32_t)key_channel_length;\n    memcpy(layout.channel, channel, key_channel_length);\n\n    int32_t counter_id = aeron_counters_manager_allocate(\n        counters_manager, type_id, (const uint8_t *)&layout, sizeof(layout), label, label_length);\n\n    if (counter_id >= 0)\n    {\n        aeron_counters_manager_counter_registration_id(counters_manager, counter_id, registration_id);\n    }\n    \n    return counter_id;\n}\n\nvoid aeron_channel_endpoint_status_update_label(\n    aeron_counters_manager_t *counters_manager,\n    int32_t counter_id,\n    const char *name,\n    size_t channel_length,\n    const char *channel,\n    size_t additional_length,\n    const char *additional)\n{\n    char label[AERON_COUNTER_MAX_LABEL_LENGTH];\n    int total_length = snprintf(\n        label, sizeof(label), \"%s: %.*s %.*s\", name, (int)channel_length, channel, (int)additional_length, additional);\n    size_t label_length = AERON_MIN(AERON_COUNTER_MAX_LABEL_LENGTH, (size_t)total_length);\n\n    aeron_counters_manager_update_label(counters_manager, counter_id, label_length, label);\n}\n\nint32_t aeron_counter_send_channel_status_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t registration_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_channel_endpoint_status_allocate(\n        counters_manager,\n        AERON_COUNTER_SEND_CHANNEL_STATUS_NAME,\n        AERON_COUNTER_SEND_CHANNEL_STATUS_TYPE_ID,\n        registration_id,\n        channel_length,\n        channel);\n}\n\nint32_t aeron_counter_receive_channel_status_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t registration_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_channel_endpoint_status_allocate(\n        counters_manager,\n        AERON_COUNTER_RECEIVE_CHANNEL_STATUS_NAME,\n        AERON_COUNTER_RECEIVE_CHANNEL_STATUS_TYPE_ID,\n        registration_id,\n        channel_length,\n        channel);\n}\n\nint32_t aeron_counter_local_sockaddr_indicator_allocate(\n    aeron_counters_manager_t *counters_manager,\n    const char *name,\n    int64_t registration_id,\n    int32_t channel_status_counter_id,\n    const char *local_sockaddr)\n{\n    aeron_local_sockaddr_key_layout_t sockaddr_layout;\n    size_t local_sockaddr_srclen = strlen(local_sockaddr);\n    size_t local_sockaddr_dstlen = sizeof(sockaddr_layout.local_sockaddr) - 1;\n\n    sockaddr_layout.channel_status_id = channel_status_counter_id,\n    sockaddr_layout.local_sockaddr_len =\n        (int32_t)(local_sockaddr_srclen < local_sockaddr_dstlen ? local_sockaddr_srclen : local_sockaddr_dstlen);\n    memcpy(sockaddr_layout.local_sockaddr, local_sockaddr, sockaddr_layout.local_sockaddr_len);\n    sockaddr_layout.local_sockaddr[sockaddr_layout.local_sockaddr_len] = '\\0';\n\n    char label[AERON_COUNTER_MAX_LABEL_LENGTH];\n    int total_length = snprintf(\n        label, sizeof(label), \"%s: %\" PRId32 \" %s\", name, channel_status_counter_id, local_sockaddr);\n    size_t label_length = AERON_MIN(AERON_COUNTER_MAX_LABEL_LENGTH, (size_t)total_length);\n\n    int32_t counter_id = aeron_counters_manager_allocate(\n        counters_manager,\n        AERON_COUNTER_LOCAL_SOCKADDR_TYPE_ID,\n        (const uint8_t *)&sockaddr_layout,\n        sizeof(sockaddr_layout),\n        label,\n        label_length);\n\n    if (counter_id >= 0)\n    {\n        aeron_counters_manager_counter_registration_id(counters_manager, counter_id, registration_id);\n    }\n\n    return counter_id;\n}\n\nint32_t aeron_heartbeat_timestamp_allocate(\n    aeron_counters_manager_t *counters_manager,\n    const char *name,\n    int32_t type_id,\n    int64_t registration_id)\n{\n    char label[AERON_COUNTER_MAX_LABEL_LENGTH];\n    int label_length = snprintf(label, sizeof(label), \"%s: id=%\" PRId64, name, registration_id);\n    aeron_heartbeat_timestamp_key_layout_t layout =\n        {\n            .registration_id = registration_id\n        };\n\n    return aeron_counters_manager_allocate(\n        counters_manager, type_id, (const uint8_t *)&layout, sizeof(layout), label, (size_t)label_length);\n}\n\nint32_t aeron_counter_client_heartbeat_timestamp_allocate(aeron_counters_manager_t *counters_manager, int64_t client_id)\n{\n    return aeron_heartbeat_timestamp_allocate(\n        counters_manager,\n        AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_NAME,\n        AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID,\n        client_id);\n}\n\nint32_t aeron_counter_publisher_position_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    bool is_exclusive)\n{\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        is_exclusive ? AERON_COUNTER_PUBLISHER_POSITION_NAME_EXCLUSIVE : AERON_COUNTER_PUBLISHER_POSITION_NAME_CONCURRENT,\n        AERON_COUNTER_PUBLISHER_POSITION_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        \"\");\n}\n\nint32_t aeron_counter_sender_bpe_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_COUNTER_SENDER_BPE_NAME,\n        AERON_COUNTER_SENDER_BPE_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        \"\");\n}\n\nint32_t aeron_counter_sender_naks_received_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_COUNTER_SENDER_NAKS_RECEIVED_NAME,\n        AERON_COUNTER_SENDER_NAKS_RECEIVED_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        \"\");\n}\n\nint32_t aeron_counter_receiver_naks_sent_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    return aeron_stream_counter_allocate(\n        counters_manager,\n        AERON_COUNTER_RECEIVER_NAKS_SENT_NAME,\n        AERON_COUNTER_RECEIVER_NAKS_SENT_TYPE_ID,\n        client_id,\n        registration_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        \"\");\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_position.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_DRIVER_POSITION_H\n#define AERON_DRIVER_POSITION_H\n\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"aeron_counters.h\"\n\nint32_t aeron_stream_counter_allocate(\n    aeron_counters_manager_t *counters_manager,\n    const char *name,\n    int32_t type_id,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    const char *suffix);\n\nint32_t aeron_channel_endpoint_status_allocate(\n    aeron_counters_manager_t *counters_manager,\n    const char *name,\n    int32_t type_id,\n    int64_t registration_id,\n    size_t channel_length,\n    const char *channel);\n\nvoid aeron_channel_endpoint_status_update_label(\n    aeron_counters_manager_t *counters_manager,\n    int32_t counter_id,\n    const char *name,\n    size_t channel_length,\n    const char *channel,\n    size_t additional_length,\n    const char *additional);\n\nint32_t aeron_heartbeat_timestamp_allocate(\n    aeron_counters_manager_t *counters_manager,\n    const char *name,\n    int32_t type_id,\n    int64_t registration_id);\n\nint32_t aeron_counter_publisher_limit_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\nint32_t aeron_counter_sender_position_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\nint32_t aeron_counter_sender_limit_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\nint32_t aeron_counter_subscription_position_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    int64_t joining_position);\n\nint32_t aeron_counter_receiver_hwm_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\nint32_t aeron_counter_receiver_position_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\nint32_t aeron_counter_send_channel_status_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t registration_id,\n    size_t channel_length,\n    const char *channel);\n\nint32_t aeron_counter_receive_channel_status_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t registration_id,\n    size_t channel_length,\n    const char *channel);\n\nint32_t aeron_counter_local_sockaddr_indicator_allocate(\n    aeron_counters_manager_t *counters_manager,\n    const char *name,\n    int64_t registration_id,\n    int32_t channel_status_counter_id,\n    const char *local_sockaddr);\n\nint32_t aeron_counter_client_heartbeat_timestamp_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id);\n\nint32_t aeron_counter_publisher_position_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    bool is_exclusive);\n\nint32_t aeron_counter_sender_bpe_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\nint32_t aeron_counter_sender_naks_received_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\nint32_t aeron_counter_receiver_naks_sent_allocate(\n    aeron_counters_manager_t *counters_manager,\n    int64_t client_id,\n    int64_t registration_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\n#endif\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_publication_image.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <inttypes.h>\n#include \"util/aeron_netutil.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"concurrent/aeron_term_rebuilder.h\"\n#include \"aeron_publication_image.h\"\n#include \"aeron_driver_receiver_proxy.h\"\n#include \"aeron_driver_conductor.h\"\n#include \"concurrent/aeron_term_gap_filler.h\"\n#include \"util/aeron_parse_util.h\"\n\n#define AERON_PUBLICATION_RESPONSE_NULL_RESPONSE_SESSION_ID INT64_C(0xF000000000000000)\n\nstatic void aeron_publication_image_connection_set_control_address(\n    aeron_publication_image_connection_t *connection, const struct sockaddr_storage *control_address)\n{\n    memcpy(\n        &connection->resolved_control_address_for_implicit_unicast_channels,\n        control_address,\n        sizeof(connection->resolved_control_address_for_implicit_unicast_channels));\n    connection->control_addr = &connection->resolved_control_address_for_implicit_unicast_channels;\n}\n\nstatic void aeron_update_active_transport_count(aeron_publication_image_t *image, int64_t now_ns)\n{\n    int active_transport_count = 0;\n\n    for (size_t i = 0, len = image->connections.length; i < len; i++)\n    {\n        aeron_publication_image_connection_t *connection = &image->connections.array[i];\n        if (now_ns < connection->time_of_last_frame_ns + image->conductor_fields.liveness_timeout_ns)\n        {\n            active_transport_count++;\n        }\n    }\n\n    if (active_transport_count != image->log_meta_data->active_transport_count)\n    {\n        AERON_SET_RELEASE(image->log_meta_data->active_transport_count, active_transport_count);\n    }\n}\n\nstatic bool aeron_publication_image_check_and_get_response_session_id(\n    aeron_publication_image_t *image, int32_t *response_session_id)\n{\n    int64_t _response_session_id;\n    AERON_GET_ACQUIRE(_response_session_id, image->response_session_id);\n    *response_session_id = (int32_t)_response_session_id;\n    return ((int64_t)INT32_MIN) <= _response_session_id && _response_session_id <= ((int64_t)INT32_MAX);\n}\n\nstatic aeron_feedback_delay_generator_state_t *aeron_publication_image_create_static_delay_generator_state(\n    aeron_publication_image_t *image, int64_t nak_delay_ns, int64_t retry_delay_ns)\n{\n    if (aeron_feedback_delay_state_init(\n     &image->feedback_delay_state,\n    aeron_loss_detector_nak_unicast_delay_generator,\n    nak_delay_ns,\n    retry_delay_ns,\n    1) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not init publication image feedback_delay_state\");\n        return NULL;\n    }\n\n    return &image->feedback_delay_state;\n}\n\nstatic aeron_feedback_delay_generator_state_t *aeron_publication_image_acquire_delay_generator_state(\n    bool is_reliable,\n    bool treat_as_multicast,\n    aeron_driver_context_t *context,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_publication_image_t *image)\n{\n    if (!is_reliable)\n    {\n        return aeron_publication_image_create_static_delay_generator_state(image, 0, 0);\n    }\n\n    if (treat_as_multicast)\n    {\n        return &context->multicast_delay_feedback_generator;\n    }\n\n    const char *nak_delay_string = aeron_uri_find_param_value(\n        &endpoint->conductor_fields.udp_channel->uri.params.udp.additional_params,\n        AERON_URI_NAK_DELAY_KEY);\n\n    if (NULL == nak_delay_string)\n    {\n        return &context->unicast_delay_feedback_generator;\n    }\n\n    uint64_t nak_delay_ns;\n\n    if (aeron_parse_duration_ns(nak_delay_string, &nak_delay_ns) < 0)\n    {\n        AERON_SET_ERR(EINVAL, \"%s is not parseable: %s\", AERON_URI_NAK_DELAY_KEY, nak_delay_string);\n        return NULL;\n    }\n\n    return aeron_publication_image_create_static_delay_generator_state(\n        image, (int64_t)nak_delay_ns, (int64_t)nak_delay_ns * (int64_t)context->nak_unicast_retry_delay_ratio);\n}\n\nstatic void aeron_publication_image_report_loss(\n    aeron_publication_image_t *image, int32_t term_id, int32_t term_offset, size_t length, size_t bytes_lost)\n{\n    if (image->conductor_fields.loss_report_entry_offset >= 0)\n    {\n        aeron_loss_reporter_record_observation(\n            image->conductor_fields.loss_report, image->conductor_fields.loss_report_entry_offset, (int64_t)bytes_lost, image->epoch_clock());\n    }\n    else if (NULL != image->conductor_fields.loss_report)\n    {\n        if (NULL != image->conductor_fields.endpoint)\n        {\n            image->conductor_fields.loss_report_entry_offset = aeron_loss_reporter_create_entry(\n                image->conductor_fields.loss_report,\n                (int64_t)bytes_lost,\n                image->epoch_clock(),\n                image->session_id,\n                image->stream_id,\n                image->conductor_fields.endpoint->conductor_fields.udp_channel->original_uri,\n                image->conductor_fields.endpoint->conductor_fields.udp_channel->uri_length,\n                image->source_identity,\n                image->source_identity_length);\n        }\n\n        if (-1 == image->conductor_fields.loss_report_entry_offset)\n        {\n            image->conductor_fields.loss_report = NULL;\n        }\n    }\n    image->conductor_fields.loss_report_term_id = term_id;\n    image->conductor_fields.loss_report_term_offset = term_offset;\n    image->conductor_fields.loss_report_length = length;\n}\n\nint aeron_publication_image_create(\n    aeron_publication_image_t **image,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    aeron_driver_conductor_t *conductor,\n    int64_t correlation_id,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t initial_term_id,\n    int32_t active_term_id,\n    int32_t initial_term_offset,\n    aeron_position_t *rcv_hwm_position,\n    aeron_position_t *rcv_pos_position,\n    aeron_atomic_counter_t *rcv_naks_sent,\n    aeron_congestion_control_strategy_t *congestion_control,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *source_address,\n    int32_t term_buffer_length,\n    int32_t sender_mtu_length,\n    uint8_t flags,\n    aeron_loss_reporter_t *loss_reporter,\n    bool is_reliable,\n    bool is_sparse,\n    bool treat_as_multicast,\n    aeron_system_counters_t *system_counters)\n{\n    aeron_driver_context_t *context = conductor->context;\n\n    aeron_publication_image_t *_image = NULL;\n    const uint64_t log_length = aeron_logbuffer_compute_log_length(\n        (uint64_t)term_buffer_length, context->file_page_size);\n\n    *image = NULL;\n\n    if (aeron_driver_context_run_storage_checks(context, log_length) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_alloc((void **)&_image, sizeof(aeron_publication_image_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not allocate publication image\");\n        return -1;\n    }\n\n    char path[AERON_MAX_PATH];\n    int path_length = aeron_publication_image_location(path, sizeof(path), context->aeron_dir, correlation_id);\n    if (path_length < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not resolve publication image file path\");\n        aeron_free(_image);\n        return -1;\n    }\n\n    _image->log_file_name = NULL;\n    if (aeron_alloc((void **)(&_image->log_file_name), (size_t)path_length + 1) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not allocate publication image log_file_name\");\n        aeron_free(_image);\n        return -1;\n    }\n\n    aeron_feedback_delay_generator_state_t *feedback_delay_state;\n    feedback_delay_state = aeron_publication_image_acquire_delay_generator_state(is_reliable, treat_as_multicast, context, endpoint, _image);\n    if (NULL == feedback_delay_state)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    if (aeron_loss_detector_init(\n        &_image->loss_detector,\n        feedback_delay_state,\n        aeron_publication_image_on_gap_detected,\n        _image) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not init publication image loss detector\");\n        goto error;\n    }\n\n    if (context->raw_log_map_func(\n        &_image->mapped_raw_log, path, is_sparse, (uint64_t)term_buffer_length, context->file_page_size) < 0)\n    {\n        AERON_APPEND_ERR(\"error mapping network raw log: %s\", path);\n        goto error;\n    }\n\n    _image->mapped_bytes_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED);\n    aeron_counter_get_and_add_release(_image->mapped_bytes_counter, (int64_t) log_length);\n\n    _image->raw_log_close_func = context->raw_log_close_func;\n    _image->raw_log_free_func = context->raw_log_free_func;\n    _image->log.untethered_subscription_state_change = context->log.untethered_subscription_on_state_change;\n    _image->log.publication_image_revoke = context->log.publication_image_revoke;\n\n    _image->nano_clock = context->nano_clock;\n    _image->epoch_clock = context->epoch_clock;\n    _image->cached_clock = context->receiver_cached_clock;\n\n    if (aeron_publication_image_add_destination(_image, destination) < 0)\n    {\n        goto error;\n    }\n    if (!destination->has_control_addr)\n    {\n        aeron_publication_image_connection_set_control_address(&_image->connections.array[0], control_address);\n    }\n\n    strncpy(_image->log_file_name, path, (size_t)path_length);\n    _image->log_file_name[path_length] = '\\0';\n    _image->log_file_name_length = (size_t)path_length;\n    _image->log_meta_data = (aeron_logbuffer_metadata_t *)(_image->mapped_raw_log.log_meta_data.addr);\n\n    aeron_driver_uri_subscription_params_t params;\n    if (aeron_driver_uri_subscription_params(&endpoint->conductor_fields.udp_channel->uri, &params, conductor) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n\n    aeron_logbuffer_metadata_init(\n        _image->mapped_raw_log.log_meta_data.addr,\n        INT64_MAX,\n        0,\n        0,\n        correlation_id,\n        initial_term_id,\n        sender_mtu_length,\n        term_buffer_length,\n        (int32_t)context->file_page_size,\n        0,\n        (int32_t)params.initial_window_length,\n        (int32_t)endpoint->conductor_fields.udp_channel->socket_sndbuf_length,\n        (int32_t)context->os_buffer_lengths.default_so_sndbuf,\n        (int32_t)context->os_buffer_lengths.max_so_sndbuf,\n        (int32_t)endpoint->conductor_fields.udp_channel->socket_rcvbuf_length,\n        (int32_t)context->os_buffer_lengths.default_so_rcvbuf,\n        (int32_t)context->os_buffer_lengths.max_so_rcvbuf,\n        0,\n        session_id,\n        stream_id,\n        0,\n        0,\n        0,\n        (int64_t)params.untethered_window_limit_timeout_ns,\n        params.untethered_linger_timeout_ns,\n        (int64_t)params.untethered_resting_timeout_ns,\n        (uint8_t)treat_as_multicast,\n        (uint8_t)params.is_response,\n        (uint8_t)params.is_rejoin,\n        (uint8_t)is_reliable,\n        (uint8_t)is_sparse,\n        (uint8_t)false,\n        (uint8_t)false,\n        (uint8_t)params.is_tether,\n        AERON_LOGBUFFER_TYPE_PUBLICATION_IMAGE);\n\n    _image->endpoint = endpoint;\n    _image->conductor_fields.endpoint = endpoint;\n\n    _image->congestion_control = congestion_control;\n    _image->conductor_fields.loss_report = loss_reporter;\n    _image->conductor_fields.loss_report_entry_offset = -1;\n    _image->conductor_fields.loss_report_term_id = 0;\n    _image->conductor_fields.loss_report_term_offset = 0;\n    _image->conductor_fields.loss_report_length = 0;\n    _image->conductor_fields.subscribable.correlation_id = correlation_id;\n    _image->conductor_fields.subscribable.array = NULL;\n    _image->conductor_fields.subscribable.length = 0;\n    _image->conductor_fields.subscribable.capacity = 0;\n    _image->conductor_fields.subscribable.inactive_count = 0;\n    _image->conductor_fields.subscribable.add_position_hook_func = aeron_driver_subscribable_null_hook;\n    _image->conductor_fields.subscribable.remove_position_hook_func = aeron_driver_subscribable_null_hook;\n    _image->conductor_fields.subscribable.clientd = NULL;\n    _image->conductor_fields.managed_resource.registration_id = correlation_id;\n    _image->conductor_fields.managed_resource.clientd = _image;\n    _image->conductor_fields.managed_resource.handle_event = NULL;\n    _image->conductor_fields.is_reliable = is_reliable;\n    _image->conductor_fields.state = AERON_PUBLICATION_IMAGE_STATE_ACTIVE;\n    _image->conductor_fields.liveness_timeout_ns = (int64_t)context->image_liveness_timeout_ns;\n    _image->conductor_fields.flags = flags;\n\n    _image->conductor_fields.untethered_window_limit_timeout_ns = (int64_t)params.untethered_window_limit_timeout_ns;\n    _image->conductor_fields.untethered_linger_timeout_ns = (int64_t)params.untethered_linger_timeout_ns;\n    _image->conductor_fields.untethered_resting_timeout_ns = (int64_t)params.untethered_resting_timeout_ns;\n\n    _image->session_id = session_id;\n    _image->stream_id = stream_id;\n    _image->rcv_hwm_position.counter_id = rcv_hwm_position->counter_id;\n    _image->rcv_hwm_position.value_addr = rcv_hwm_position->value_addr;\n    _image->rcv_pos_position.counter_id = rcv_pos_position->counter_id;\n    _image->rcv_pos_position.value_addr = rcv_pos_position->value_addr;\n    _image->rcv_naks_sent.counter_id = rcv_naks_sent->counter_id;\n    _image->rcv_naks_sent.value_addr = rcv_naks_sent->value_addr;\n    _image->term_length = term_buffer_length;\n    _image->initial_term_id = initial_term_id;\n    _image->term_length_mask = term_buffer_length - 1;\n    _image->position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes((int32_t)term_buffer_length);\n    _image->mtu_length = sender_mtu_length;\n    _image->is_end_of_stream = false;\n    _image->is_sending_eos_sm = false;\n    _image->has_receiver_released = false;\n    _image->sm_timeout_ns = (int64_t)context->status_message_timeout_ns;\n    _image->invalidation_reason = NULL;\n\n    memcpy(&_image->source_address, source_address, sizeof(_image->source_address));\n    const int source_identity_length = aeron_format_source_identity(\n        _image->source_identity, sizeof(_image->source_identity), source_address);\n    if (source_identity_length <= 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to format source identity\");\n        goto error;\n    }\n    _image->source_identity_length = (size_t)source_identity_length;\n\n    _image->heartbeats_received_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_HEARTBEATS_RECEIVED);\n    _image->flow_control_under_runs_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_FLOW_CONTROL_UNDER_RUNS);\n    _image->flow_control_over_runs_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_FLOW_CONTROL_OVER_RUNS);\n    _image->status_messages_sent_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_STATUS_MESSAGES_SENT);\n    _image->nak_messages_sent_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_NAK_MESSAGES_SENT);\n    _image->loss_gap_fills_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_LOSS_GAP_FILLS);\n    _image->publication_images_revoked_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_PUBLICATION_IMAGES_REVOKED);\n\n    const int64_t initial_position = aeron_logbuffer_compute_position(\n        active_term_id, initial_term_offset, _image->position_bits_to_shift, initial_term_id);\n    const int64_t now_ns = aeron_clock_cached_nano_time(context->cached_clock);\n\n    _image->begin_loss_change = 0;\n    _image->end_loss_change = 0;\n    _image->last_loss_change_number = 0;\n    _image->loss_term_id = active_term_id;\n    _image->loss_term_offset = initial_term_offset;\n    _image->loss_length = 0;\n\n    _image->begin_sm_change = 0;\n    _image->end_sm_change = 0;\n    _image->last_sm_change_number = 0;\n    _image->next_sm_position = initial_position;\n    _image->next_sm_receiver_window_length = _image->congestion_control->initial_window_length(\n        _image->congestion_control->state);\n    _image->max_receiver_window_length = _image->congestion_control->max_window_length(\n        _image->congestion_control->state);\n    _image->last_sm_position = initial_position;\n    _image->last_overrun_threshold = initial_position + (term_buffer_length / 2);\n    _image->time_of_last_packet_ns = now_ns;\n    _image->next_sm_deadline_ns = 0;\n    _image->is_sm_enabled = true;\n    _image->conductor_fields.clean_position = initial_position;\n    _image->conductor_fields.time_of_last_state_change_ns = now_ns;\n\n    aeron_publication_image_remove_response_session_id(_image);\n    aeron_counter_set_release(_image->rcv_hwm_position.value_addr, initial_position);\n    aeron_counter_set_release(_image->rcv_pos_position.value_addr, initial_position);\n\n    *image = _image;\n\n    return 0;\n\nerror:\n    aeron_free(_image->connections.array);\n    aeron_free(_image->log_file_name);\n    aeron_free(_image);\n    return -1;\n}\n\nint aeron_publication_image_close(aeron_counters_manager_t *counters_manager, aeron_publication_image_t *image)\n{\n    if (NULL != image)\n    {\n        aeron_subscribable_t *subscribable = &image->conductor_fields.subscribable;\n\n        aeron_counters_manager_free(counters_manager, image->rcv_hwm_position.counter_id);\n        aeron_counters_manager_free(counters_manager, image->rcv_pos_position.counter_id);\n        aeron_counters_manager_free(counters_manager, image->rcv_naks_sent.counter_id);\n\n        for (size_t i = 0, length = subscribable->length; i < length; i++)\n        {\n            aeron_counters_manager_free(counters_manager, subscribable->array[i].counter_id);\n        }\n\n        aeron_free(subscribable->array);\n        aeron_free(image->connections.array);\n\n        image->congestion_control->fini(image->congestion_control);\n    }\n\n    return 0;\n}\n\nbool aeron_publication_image_free(aeron_publication_image_t *image)\n{\n    if (NULL == image)\n    {\n        return true;\n    }\n\n    if (!image->raw_log_free_func(&image->mapped_raw_log, image->log_file_name))\n    {\n        return false;\n    }\n\n    aeron_counter_get_and_add_release(\n        image->mapped_bytes_counter,\n        -((int64_t) image->mapped_raw_log.mapped_file.length));\n\n    aeron_free(image->log_file_name);\n    aeron_free((void *)image->invalidation_reason);\n    aeron_free(image);\n\n    return true;\n}\n\nvoid aeron_publication_image_clean_buffer_to(aeron_publication_image_t *image, int64_t position)\n{\n    int64_t clean_position = image->conductor_fields.clean_position;\n    if (position > clean_position)\n    {\n        size_t dirty_index = aeron_logbuffer_index_by_position(clean_position, image->position_bits_to_shift);\n        size_t bytes_to_clean = (size_t)(position - clean_position);\n        size_t term_length = (size_t)image->term_length;\n        size_t term_offset = (size_t)(clean_position & image->term_length_mask);\n        size_t bytes_left_in_term = term_length - term_offset;\n        size_t length = bytes_to_clean < bytes_left_in_term ? bytes_to_clean : bytes_left_in_term;\n\n        memset(\n            image->mapped_raw_log.term_buffers[dirty_index].addr + term_offset + sizeof(int64_t),\n            0,\n            length - sizeof(int64_t));\n\n        volatile uint64_t *ptr = (volatile uint64_t *)(image->mapped_raw_log.term_buffers[dirty_index].addr + term_offset);\n        AERON_SET_RELEASE(*ptr, (uint64_t)0);\n\n        image->conductor_fields.clean_position = clean_position + (int64_t)length;\n    }\n}\n\n// Called from conductor via loss detector.\nvoid aeron_publication_image_on_gap_detected(void *clientd, int32_t term_id, int32_t term_offset, size_t length)\n{\n    aeron_publication_image_t *image = (aeron_publication_image_t *)clientd;\n    const int64_t change_number = image->begin_loss_change + 1;\n\n    AERON_SET_RELEASE(image->begin_loss_change, change_number);\n    aeron_release();\n\n    image->loss_term_id = term_id;\n    image->loss_term_offset = term_offset;\n    image->loss_length = length;\n\n    AERON_SET_RELEASE(image->end_loss_change, change_number);\n\n    size_t loss_report_end_offset;\n    if (term_id != image->conductor_fields.loss_report_term_id ||\n        (size_t)term_offset >= (loss_report_end_offset = (size_t)image->conductor_fields.loss_report_term_offset + image->conductor_fields.loss_report_length))\n    {\n        aeron_publication_image_report_loss(image, term_id, term_offset, length, length);\n    }\n    else if ((size_t)term_offset + length > loss_report_end_offset)\n    {\n        aeron_publication_image_report_loss(image, term_id, term_offset, length, length - (loss_report_end_offset - (size_t)term_offset));\n    }\n}\n\nvoid aeron_publication_image_track_rebuild(aeron_publication_image_t *image, int64_t now_ns)\n{\n    uint8_t is_publication_revoked;\n\n    AERON_GET_ACQUIRE(is_publication_revoked, image->log_meta_data->is_publication_revoked);\n\n    if (aeron_driver_subscribable_has_working_positions(&image->conductor_fields.subscribable) &&\n        !is_publication_revoked)\n    {\n        const int64_t hwm_position = aeron_counter_get_acquire(image->rcv_hwm_position.value_addr);\n        int64_t min_sub_pos = INT64_MAX;\n        int64_t max_sub_pos = 0;\n\n        for (size_t i = 0; i < image->conductor_fields.subscribable.length; i++)\n        {\n            aeron_tetherable_position_t *tetherable_position = &image->conductor_fields.subscribable.array[i];\n\n            if (aeron_driver_subscribable_is_active_state(tetherable_position->state))\n            {\n                const int64_t position = aeron_counter_get_acquire(tetherable_position->value_addr);\n\n                min_sub_pos = position < min_sub_pos ? position : min_sub_pos;\n                max_sub_pos = position > max_sub_pos ? position : max_sub_pos;\n            }\n        }\n\n        if (INT64_MAX == min_sub_pos)\n        {\n            return;\n        }\n\n        const int64_t rebuild_position = *image->rcv_pos_position.value_addr > max_sub_pos ?\n            *image->rcv_pos_position.value_addr : max_sub_pos;\n\n        bool loss_found = false;\n        const size_t index = aeron_logbuffer_index_by_position(rebuild_position, image->position_bits_to_shift);\n        const int32_t rebuild_offset = aeron_loss_detector_scan(\n            &image->loss_detector,\n            &loss_found,\n            image->mapped_raw_log.term_buffers[index].addr,\n            rebuild_position,\n            hwm_position,\n            now_ns,\n            (size_t)image->term_length_mask,\n            image->position_bits_to_shift,\n            image->initial_term_id);\n\n        const int32_t rebuild_term_offset = (int32_t)(rebuild_position & image->term_length_mask);\n        const int64_t new_rebuild_position = (rebuild_position - rebuild_term_offset) + rebuild_offset;\n\n        aeron_counter_propose_max_release(image->rcv_pos_position.value_addr, new_rebuild_position);\n\n        bool should_force_send_sm = false;\n        const int32_t window_length = image->congestion_control->on_track_rebuild(\n            image->congestion_control->state,\n            &should_force_send_sm,\n            now_ns,\n            min_sub_pos,\n            image->next_sm_position,\n            hwm_position,\n            rebuild_position,\n            new_rebuild_position,\n            loss_found);\n\n        const int32_t threshold = window_length / 4;\n\n        if (should_force_send_sm ||\n            (min_sub_pos > (image->next_sm_position + threshold)) ||\n            window_length != image->next_sm_receiver_window_length)\n        {\n            aeron_publication_image_clean_buffer_to(image, min_sub_pos - image->term_length);\n            aeron_publication_image_schedule_status_message(image, min_sub_pos, window_length);\n        }\n    }\n}\n\nstatic inline void aeron_publication_image_track_connection(\n    aeron_publication_image_t *image,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *source_addr,\n    int64_t now_ns)\n{\n    aeron_publication_image_connection_t *connection = NULL;\n    for (size_t i = 0, len = image->connections.length; i < len; i++)\n    {\n        if (image->connections.array[i].destination == destination)\n        {\n            connection = &image->connections.array[i];\n            break;\n        }\n    }\n\n    if (NULL == connection)\n    {\n        // TODO: Might be useful to prevent inlining\n        if (aeron_publication_image_add_destination(image, destination) < 0)\n        {\n            return;\n        }\n\n        connection = &image->connections.array[image->connections.length - 1];\n    }\n\n    if (NULL == connection->control_addr)\n    {\n        // TODO: Might be useful to prevent inlining\n        aeron_publication_image_connection_set_control_address(connection, source_addr);\n    }\n\n    connection->time_of_last_activity_ns = now_ns;\n    connection->time_of_last_frame_ns = now_ns;\n}\n\nstatic inline bool aeron_publication_image_all_eos(\n    aeron_publication_image_t *image, aeron_receive_destination_t *destination, int64_t packet_position)\n{\n    bool all_eos = true;\n\n    for (size_t i = 0, len = image->connections.length; i < len; i++)\n    {\n        aeron_publication_image_connection_t *connection = &image->connections.array[i];\n        if (connection->destination == destination)\n        {\n            connection->is_eos = true;\n            connection->eos_position = packet_position;\n        }\n        else\n        {\n            all_eos &= connection->is_eos;\n        }\n    }\n\n    return all_eos;\n}\n\nstatic inline int64_t aeron_publication_find_eos_position(aeron_publication_image_t *image)\n{\n    int64_t eos_position = 0;\n\n    for (size_t i = 0, len = image->connections.length; i < len; i++)\n    {\n        aeron_publication_image_connection_t *connection = &image->connections.array[i];\n        if (connection->eos_position > eos_position)\n        {\n            eos_position = connection->eos_position;\n        }\n    }\n\n    return eos_position;\n}\n\nstatic inline bool aeron_publication_image_connection_is_alive(\n    const aeron_publication_image_connection_t *connection, const int64_t now_ns)\n{\n    return NULL != connection->control_addr &&\n        now_ns < connection->time_of_last_activity_ns + AERON_RECEIVE_DESTINATION_TIMEOUT_NS;\n}\n\nvoid aeron_publication_image_add_connection_if_unknown(\n    aeron_publication_image_t *image, aeron_receive_destination_t *destination, struct sockaddr_storage *src_addr)\n{\n    aeron_publication_image_track_connection(\n        image, destination, src_addr, aeron_clock_cached_nano_time(image->cached_clock));\n}\n\nint aeron_publication_image_insert_packet(\n    aeron_publication_image_t *image,\n    aeron_receive_destination_t *destination,\n    int32_t term_id,\n    int32_t term_offset,\n    const uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    if (aeron_sub_wrap_i32(term_id, image->initial_term_id) < 0)\n    {\n        return 0;\n    }\n\n    if (NULL != image->invalidation_reason)\n    {\n        return 0;\n    }\n\n    const bool is_heartbeat = aeron_publication_image_is_heartbeat(buffer, length);\n    const int64_t packet_position = aeron_logbuffer_compute_position(\n        term_id, term_offset, image->position_bits_to_shift, image->initial_term_id);\n    const int64_t proposed_position = is_heartbeat ? packet_position : packet_position + (int64_t)length;\n\n    if (!aeron_publication_image_is_flow_control_over_run(image, proposed_position))\n    {\n        const int64_t now_ns = aeron_clock_cached_nano_time(image->cached_clock);\n\n        if (is_heartbeat)\n        {\n            const int64_t potential_window_bottom = image->last_sm_position - (image->term_length_mask + 1);\n            const int64_t publication_window_bottom = potential_window_bottom < 0 ? 0 : potential_window_bottom;\n\n            if (packet_position >= publication_window_bottom)\n            {\n                aeron_publication_image_track_connection(image, destination, addr, now_ns);\n                AERON_SET_RELEASE(image->time_of_last_packet_ns, now_ns);\n\n                const bool is_eos = aeron_publication_image_is_end_of_stream(buffer, length);\n                if (is_eos && !image->is_end_of_stream)\n                {\n                    if (aeron_publication_image_all_eos(image, destination, packet_position))\n                    {\n                        const int64_t eos_position = aeron_publication_find_eos_position(image);\n\n                        const bool is_revoked = aeron_publication_image_is_revoked(buffer, length);\n                        if (is_revoked)\n                        {\n                            AERON_SET_RELEASE(image->log_meta_data->is_publication_revoked, (uint8_t)true);\n\n                            aeron_driver_publication_image_revoke_func_t publication_image_revoke = image->log.publication_image_revoke;\n                            if (NULL != publication_image_revoke)\n                            {\n                                publication_image_revoke(\n                                    eos_position,\n                                    image->session_id,\n                                    image->stream_id,\n                                    image->endpoint->conductor_fields.udp_channel->uri_length,\n                                    image->endpoint->conductor_fields.udp_channel->original_uri);\n                            }\n\n                            aeron_counter_increment_release(image->publication_images_revoked_counter);\n                        }\n\n                        AERON_SET_RELEASE(image->log_meta_data->end_of_stream_position, eos_position);\n                        AERON_SET_RELEASE(image->is_end_of_stream, true);\n                    }\n                }\n\n                aeron_counter_propose_max_release(image->rcv_hwm_position.value_addr, proposed_position);\n                aeron_counter_increment_release(image->heartbeats_received_counter);\n            }\n            else\n            {\n                aeron_counter_increment_release(image->flow_control_under_runs_counter);\n            }\n        }\n        else if (!aeron_publication_image_is_flow_control_under_run(image, packet_position))\n        {\n            aeron_publication_image_track_connection(image, destination, addr, now_ns);\n            AERON_SET_RELEASE(image->time_of_last_packet_ns, now_ns);\n\n            const size_t index = aeron_logbuffer_index_by_position(packet_position, image->position_bits_to_shift);\n            uint8_t *term_buffer = image->mapped_raw_log.term_buffers[index].addr;\n\n            aeron_term_rebuilder_insert(term_buffer + term_offset, buffer, length);\n\n            aeron_counter_propose_max_release(image->rcv_hwm_position.value_addr, proposed_position);\n        }\n        else if (proposed_position >= (image->last_sm_position - image->max_receiver_window_length))\n        {\n            aeron_publication_image_track_connection(image, destination, addr, now_ns);\n        }\n    }\n\n    return (int)length;\n}\n\nint aeron_publication_image_on_rttm(\n    aeron_publication_image_t *image, aeron_rttm_header_t *header, struct sockaddr_storage *addr)\n{\n    const int64_t now_ns = image->nano_clock();\n    const int64_t rtt_in_ns = now_ns - header->echo_timestamp - header->reception_delta;\n\n    image->congestion_control->on_rttm(image->congestion_control->state, now_ns, rtt_in_ns, addr);\n\n    return 1;\n}\n\n// Called from receiver.\nint aeron_publication_image_send_pending_status_message(aeron_publication_image_t *image, int64_t now_ns)\n{\n    int work_count = 0;\n    int64_t change_number;\n    AERON_GET_ACQUIRE(change_number, image->end_sm_change);\n    const bool has_sm_timed_out = image->is_sm_enabled && image->next_sm_deadline_ns < now_ns;\n\n    if (NULL != image->invalidation_reason)\n    {\n        if (has_sm_timed_out)\n        {\n            for (size_t i = 0, len = image->connections.length; i < len; i++)\n            {\n                aeron_publication_image_connection_t *connection = &image->connections.array[i];\n                aeron_receiver_channel_endpoint_send_error_frame(\n                    image->endpoint,\n                    connection->destination,\n                    connection->control_addr,\n                    image->session_id,\n                    image->stream_id,\n                    AERON_ERROR_CODE_IMAGE_REJECTED,\n                    image->invalidation_reason);\n            }\n\n            image->next_sm_deadline_ns = now_ns + image->sm_timeout_ns;\n        }\n\n        return 0;\n    }\n\n    int32_t response_session_id = 0;\n    if (has_sm_timed_out && aeron_publication_image_check_and_get_response_session_id(image, &response_session_id))\n    {\n        for (size_t i = 0, len = image->connections.length; i < len; i++)\n        {\n            aeron_publication_image_connection_t *connection = &image->connections.array[i];\n\n            if (aeron_publication_image_connection_is_alive(connection, now_ns))\n            {\n                int send_response_setup_result = aeron_receive_channel_endpoint_send_response_setup(\n                    image->endpoint,\n                    connection->destination,\n                    connection->control_addr,\n                    image->stream_id,\n                    image->session_id,\n                    response_session_id);\n\n                if (send_response_setup_result < 0)\n                {\n                    work_count = send_response_setup_result;\n                    break;\n                }\n\n                work_count++;\n            }\n        }\n    }\n\n    if (work_count < 0)\n    {\n        return work_count;\n    }\n\n    if (change_number != image->last_sm_change_number || has_sm_timed_out)\n    {\n        const int64_t sm_position = image->next_sm_position;\n        const int32_t receiver_window_length = image->next_sm_receiver_window_length;\n\n        aeron_acquire();\n\n        int64_t begin_change_number;\n        AERON_GET_ACQUIRE(begin_change_number, image->begin_sm_change);\n\n        if (change_number == begin_change_number)\n        {\n            const int32_t term_id = aeron_logbuffer_compute_term_id_from_position(\n                sm_position, image->position_bits_to_shift, image->initial_term_id);\n            const int32_t term_offset = (int32_t)(sm_position & image->term_length_mask);\n            bool is_sending_eos_sm;\n            AERON_GET_ACQUIRE(is_sending_eos_sm, image->is_sending_eos_sm);\n            const uint8_t flags = is_sending_eos_sm ? AERON_STATUS_MESSAGE_HEADER_EOS_FLAG : 0;\n\n            for (size_t i = 0, len = image->connections.length; i < len; i++)\n            {\n                aeron_publication_image_connection_t *connection = &image->connections.array[i];\n\n                if (aeron_publication_image_connection_is_alive(connection, now_ns))\n                {\n                    int send_sm_result = aeron_receive_channel_endpoint_send_sm(\n                        image->endpoint,\n                        connection->destination,\n                        connection->control_addr,\n                        image->stream_id,\n                        image->session_id,\n                        term_id,\n                        term_offset,\n                        receiver_window_length,\n                        flags);\n\n                    if (send_sm_result < 0)\n                    {\n                        AERON_APPEND_ERR(\"%s\", \"\");\n                        work_count = send_sm_result;\n                        break;\n                    }\n\n                    work_count++;\n                    aeron_counter_increment_release(image->status_messages_sent_counter);\n                }\n            }\n\n            image->last_sm_position = sm_position;\n            image->last_overrun_threshold = sm_position + (image->term_length / 2);\n            image->last_sm_change_number = change_number;\n            image->next_sm_deadline_ns = now_ns + image->sm_timeout_ns;\n\n            aeron_update_active_transport_count(image, now_ns);\n        }\n    }\n\n    return work_count;\n}\n\n// Called from receiver.\nint aeron_publication_image_send_pending_loss(aeron_publication_image_t *image)\n{\n    uint8_t is_publication_revoked;\n\n    AERON_GET_ACQUIRE(is_publication_revoked, image->log_meta_data->is_publication_revoked);\n\n    if (is_publication_revoked)\n    {\n        return 0;\n    }\n\n    int work_count = 0;\n\n    int64_t change_number;\n    AERON_GET_ACQUIRE(change_number, image->end_loss_change);\n\n    if (change_number != image->last_loss_change_number)\n    {\n        const int32_t term_id = image->loss_term_id;\n        const int32_t term_offset = image->loss_term_offset;\n        const size_t length = image->loss_length;\n\n        aeron_acquire();\n\n        int64_t begin_change_number;\n        AERON_GET_ACQUIRE(begin_change_number, image->begin_loss_change);\n\n        if (change_number == begin_change_number)\n        {\n            if (image->conductor_fields.is_reliable)\n            {\n                const int64_t now_ns = aeron_clock_cached_nano_time(image->cached_clock);\n\n                for (size_t i = 0, len = image->connections.length; i < len; i++)\n                {\n                    aeron_publication_image_connection_t *connection = &image->connections.array[i];\n\n                    if (aeron_publication_image_connection_is_alive(connection, now_ns))\n                    {\n                        int send_nak_result = aeron_receive_channel_endpoint_send_nak(\n                            image->endpoint,\n                            connection->destination,\n                            connection->control_addr,\n                            image->stream_id,\n                            image->session_id,\n                            term_id,\n                            term_offset,\n                            (int32_t)length);\n\n                        if (send_nak_result < 0)\n                        {\n                            work_count = send_nak_result;\n                            break;\n                        }\n\n                        work_count++;\n                        aeron_counter_increment_release(image->nak_messages_sent_counter);\n                        aeron_counter_increment_release(image->rcv_naks_sent.value_addr);\n                    }\n                }\n            }\n            else\n            {\n                const size_t index = aeron_logbuffer_index_by_term(image->initial_term_id, term_id);\n                uint8_t *buffer = image->mapped_raw_log.term_buffers[index].addr;\n\n                if (aeron_term_gap_filler_try_fill_gap(image->log_meta_data, buffer, term_id, term_offset, length))\n                {\n                    aeron_counter_increment_release(image->loss_gap_fills_counter);\n                }\n\n                work_count = 1;\n            }\n\n            image->last_loss_change_number = change_number;\n        }\n    }\n\n    return work_count;\n}\n\n// Called from receiver\nint aeron_publication_image_initiate_rttm(aeron_publication_image_t *image, int64_t now_ns)\n{\n    int work_count = 0;\n\n    if (image->congestion_control->should_measure_rtt(image->congestion_control->state, now_ns))\n    {\n        for (size_t i = 0, len = image->connections.length; i < len; i++)\n        {\n            aeron_publication_image_connection_t *connection = &image->connections.array[i];\n\n            if (aeron_publication_image_connection_is_alive(connection, now_ns))\n            {\n                int send_rttm_result = aeron_receive_channel_endpoint_send_rttm(\n                    image->endpoint,\n                    connection->destination,\n                    connection->control_addr,\n                    image->stream_id,\n                    image->session_id,\n                    now_ns,\n                    0,\n                    true);\n\n                if (send_rttm_result < 0)\n                {\n                    work_count = send_rttm_result;\n                    break;\n                }\n                else\n                {\n                    image->congestion_control->on_rttm_sent(image->congestion_control->state, now_ns);\n                }\n\n                work_count++;\n            }\n        }\n    }\n\n    return work_count;\n}\n\nint aeron_publication_image_add_destination(aeron_publication_image_t *image, aeron_receive_destination_t *destination)\n{\n    int capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(capacity_result, image->connections, aeron_publication_image_connection_t)\n\n    if (capacity_result < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to ensure space for image->connections\");\n        return -1;\n    }\n\n    aeron_publication_image_connection_t *new_connection = &image->connections.array[image->connections.length];\n\n    new_connection->is_eos = false;\n    new_connection->destination = destination;\n    new_connection->control_addr = destination->has_control_addr ? &destination->current_control_addr : NULL;\n    new_connection->time_of_last_activity_ns = aeron_clock_cached_nano_time(image->cached_clock);\n    new_connection->time_of_last_frame_ns = 0;\n    new_connection->eos_position = 0;\n\n    image->connections.length++;\n\n    return (int)image->connections.length;\n}\n\nint aeron_publication_image_remove_destination(aeron_publication_image_t *image, aeron_udp_channel_t *channel)\n{\n    int deleted = 0;\n\n    for (int last_index = (int)image->connections.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_receive_destination_t *destination = image->connections.array[i].destination;\n        if (aeron_udp_channel_equals(channel, destination->conductor_fields.udp_channel))\n        {\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)image->connections.array, sizeof(aeron_publication_image_connection_t), i, last_index);\n\n            --image->connections.length;\n            ++deleted;\n            break;\n        }\n    }\n\n    aeron_update_active_transport_count(image, aeron_clock_cached_nano_time(image->cached_clock));\n\n    return deleted;\n}\n\nvoid aeron_publication_image_check_untethered_subscriptions(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_t *image, int64_t now_ns)\n{\n    int64_t max_sub_pos = 0;\n\n    aeron_subscribable_t *subscribable = &image->conductor_fields.subscribable;\n    for (size_t i = 0, length = subscribable->length; i < length; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &subscribable->array[i];\n        if (aeron_driver_subscribable_is_active_state(tetherable_position->state)) {\n            int64_t position = aeron_counter_get_acquire(tetherable_position->value_addr);\n            max_sub_pos = position > max_sub_pos ? position : max_sub_pos;\n        }\n    }\n\n    int64_t window_length = image->next_sm_receiver_window_length;\n    int64_t untethered_window_limit = (max_sub_pos - window_length) + (window_length / 4);\n\n    for (size_t i = 0, length = subscribable->length; i < length; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &subscribable->array[i];\n\n        if (tetherable_position->is_tether)\n        {\n            tetherable_position->time_of_last_update_ns = now_ns;\n        }\n        else\n        {\n            int64_t window_limit_timeout_ns = image->conductor_fields.untethered_window_limit_timeout_ns;\n            int64_t linger_timeout_ns = image->conductor_fields.untethered_linger_timeout_ns;\n            int64_t resting_timeout_ns = image->conductor_fields.untethered_resting_timeout_ns;\n\n            switch (tetherable_position->state)\n            {\n                case AERON_SUBSCRIPTION_TETHER_ACTIVE:\n                    if (aeron_counter_get_acquire(tetherable_position->value_addr) > untethered_window_limit)\n                    {\n                        tetherable_position->time_of_last_update_ns = now_ns;\n                    }\n                    else if (now_ns > (tetherable_position->time_of_last_update_ns + window_limit_timeout_ns))\n                    {\n                        aeron_driver_conductor_on_unavailable_image(\n                            conductor,\n                            image->conductor_fields.managed_resource.registration_id,\n                            tetherable_position->subscription_registration_id,\n                            image->stream_id,\n                            AERON_IPC_CHANNEL,\n                            AERON_IPC_CHANNEL_LEN);\n\n                        aeron_driver_subscribable_state(\n                            subscribable,\n                            tetherable_position,\n                            AERON_SUBSCRIPTION_TETHER_LINGER,\n                            now_ns,\n                            image->stream_id,\n                            image->session_id,\n                            image->log.untethered_subscription_state_change);\n                    }\n                    break;\n\n                case AERON_SUBSCRIPTION_TETHER_LINGER:\n                    if (now_ns > (tetherable_position->time_of_last_update_ns + linger_timeout_ns))\n                    {\n                        if (tetherable_position->is_rejoin)\n                        {\n                            aeron_driver_subscribable_state(\n                                subscribable,\n                                tetherable_position,\n                                AERON_SUBSCRIPTION_TETHER_RESTING,\n                                now_ns,\n                                image->stream_id,\n                                image->session_id,\n                                image->log.untethered_subscription_state_change);\n                        }\n                        else\n                        {\n                            aeron_driver_subscribable_state(\n                                subscribable,\n                                tetherable_position,\n                                AERON_SUBSCRIPTION_TETHER_CLOSED,\n                                now_ns,\n                                image->stream_id,\n                                image->session_id,\n                                image->log.untethered_subscription_state_change);\n\n                            if (0 == aeron_counters_manager_free(&conductor->counters_manager, tetherable_position->counter_id))\n                            {\n                                tetherable_position->counter_id = AERON_NULL_COUNTER_ID; // prevent double free\n                            }\n                        }\n                    }\n                    break;\n\n                case AERON_SUBSCRIPTION_TETHER_RESTING:\n                    if (now_ns > (tetherable_position->time_of_last_update_ns + resting_timeout_ns))\n                    {\n                        int64_t join_position = aeron_publication_image_join_position(image);\n                        aeron_counter_set_release(tetherable_position->value_addr, join_position);\n                        aeron_driver_conductor_on_available_image(\n                            conductor,\n                            image->conductor_fields.managed_resource.registration_id,\n                            image->stream_id,\n                            image->session_id,\n                            image->log_file_name,\n                            image->log_file_name_length,\n                            tetherable_position->counter_id,\n                            tetherable_position->subscription_registration_id,\n                            image->source_identity,\n                            image->source_identity_length);\n\n                        aeron_driver_subscribable_state(\n                            subscribable,\n                            tetherable_position,\n                            AERON_SUBSCRIPTION_TETHER_ACTIVE,\n                            now_ns,\n                            image->stream_id,\n                            image->session_id,\n                            image->log.untethered_subscription_state_change);\n                    }\n                    break;\n\n                case AERON_SUBSCRIPTION_TETHER_CLOSED:\n                    break;\n            }\n        }\n    }\n}\n\n// Called from conductor.\nvoid aeron_publication_image_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_t *image, int64_t now_ns, int64_t now_ms)\n{\n    switch (image->conductor_fields.state)\n    {\n        case AERON_PUBLICATION_IMAGE_STATE_ACTIVE:\n        {\n            uint8_t is_publication_revoked;\n\n            AERON_GET_ACQUIRE(is_publication_revoked, image->log_meta_data->is_publication_revoked);\n\n            if (is_publication_revoked)\n            {\n                image->conductor_fields.state = AERON_PUBLICATION_IMAGE_STATE_DRAINING;\n            }\n            else\n            {\n                aeron_publication_image_check_untethered_subscriptions(conductor, image, now_ns);\n\n                int64_t last_packet_timestamp_ns;\n                AERON_GET_ACQUIRE(last_packet_timestamp_ns, image->time_of_last_packet_ns);\n                bool is_end_of_stream;\n                AERON_GET_ACQUIRE(is_end_of_stream, image->is_end_of_stream);\n\n                if (!aeron_driver_subscribable_has_working_positions(&image->conductor_fields.subscribable) ||\n                    now_ns > (last_packet_timestamp_ns + image->conductor_fields.liveness_timeout_ns) ||\n                    (is_end_of_stream &&\n                        aeron_counter_get_plain(image->rcv_pos_position.value_addr) >=\n                            aeron_counter_get_acquire(image->rcv_hwm_position.value_addr)))\n                {\n                    image->conductor_fields.state = AERON_PUBLICATION_IMAGE_STATE_DRAINING;\n                    image->conductor_fields.time_of_last_state_change_ns = now_ns;\n                    AERON_SET_RELEASE(image->is_sending_eos_sm, true);\n                }\n            }\n            break;\n        }\n\n        case AERON_PUBLICATION_IMAGE_STATE_DRAINING:\n        {\n            uint8_t is_publication_revoked;\n\n            AERON_GET_ACQUIRE(is_publication_revoked, image->log_meta_data->is_publication_revoked);\n\n            if ((aeron_publication_image_is_drained(image) &&\n                ((image->conductor_fields.time_of_last_state_change_ns +\n                (AERON_IMAGE_SM_EOS_MULTIPLE * image->sm_timeout_ns)) - now_ns < 0)) ||\n                is_publication_revoked)\n            {\n                if (is_publication_revoked)\n                {\n                    AERON_SET_RELEASE(image->is_sending_eos_sm, true);\n\n                    image->next_sm_deadline_ns = now_ns - 1;\n                }\n\n                image->conductor_fields.state = AERON_PUBLICATION_IMAGE_STATE_LINGER;\n                image->conductor_fields.time_of_last_state_change_ns = now_ns;\n\n                aeron_receive_channel_endpoint_dec_image_ref_count(image->endpoint);\n                aeron_driver_receiver_proxy_on_remove_publication_image(conductor->context->receiver_proxy, image);\n                aeron_driver_conductor_image_transition_to_linger(conductor, image);\n\n                aeron_receive_channel_endpoint_try_remove_endpoint(image->endpoint);\n            }\n            break;\n        }\n\n        case AERON_PUBLICATION_IMAGE_STATE_LINGER:\n        {\n            if (aeron_publication_image_has_no_subscribers(image) ||\n                (now_ns >\n                (image->conductor_fields.time_of_last_state_change_ns + image->conductor_fields.liveness_timeout_ns)))\n            {\n                image->conductor_fields.state = AERON_PUBLICATION_IMAGE_STATE_DONE;\n            }\n            break;\n        }\n\n        case AERON_PUBLICATION_IMAGE_STATE_DONE:\n            break;\n    }\n}\n\nvoid aeron_publication_image_receiver_release(aeron_publication_image_t *image)\n{\n    AERON_SET_RELEASE(image->has_receiver_released, true);\n}\n\nvoid aeron_publication_image_invalidate(aeron_publication_image_t *image, int32_t reason_length, const char *reason)\n{\n    aeron_alloc((void **)&image->invalidation_reason, reason_length + 1);\n    memcpy((void *)image->invalidation_reason, reason, reason_length);\n}\n\nvoid aeron_publication_image_remove_response_session_id(aeron_publication_image_t *image)\n{\n    aeron_publication_image_set_response_session_id(image, AERON_PUBLICATION_RESPONSE_NULL_RESPONSE_SESSION_ID);\n}\n\nvoid aeron_publication_image_stop_status_messages_if_not_active(aeron_publication_image_t *image)\n{\n    if (AERON_PUBLICATION_IMAGE_STATE_ACTIVE != image->conductor_fields.state)\n    {\n        image->is_sm_enabled = false;\n    }\n}\n\nextern bool aeron_publication_image_is_heartbeat(const uint8_t *buffer, size_t length);\n\nextern bool aeron_publication_image_is_end_of_stream(const uint8_t *buffer, size_t length);\n\nextern bool aeron_publication_image_is_revoked(const uint8_t *buffer, size_t length);\n\nextern bool aeron_publication_image_is_flow_control_under_run(\n    aeron_publication_image_t *image, int64_t packet_position);\n\nextern bool aeron_publication_image_is_flow_control_over_run(\n    aeron_publication_image_t *image, int64_t proposed_position);\n\nextern void aeron_publication_image_schedule_status_message(\n    aeron_publication_image_t *image, int64_t sm_position, int32_t window_length);\n\nextern bool aeron_publication_image_is_drained(aeron_publication_image_t *image);\n\nextern bool aeron_publication_image_has_no_subscribers(aeron_publication_image_t *image);\n\nextern bool aeron_publication_image_is_accepting_subscriptions(aeron_publication_image_t *image);\n\nextern void aeron_publication_image_disconnect_endpoint(aeron_publication_image_t *image);\n\nextern void aeron_publication_image_conductor_disconnect_endpoint(aeron_publication_image_t *image);\n\nextern const char *aeron_publication_image_log_file_name(aeron_publication_image_t *image);\n\nextern int64_t aeron_publication_image_registration_id(aeron_publication_image_t *image);\n\nextern bool aeron_publication_image_has_send_response_setup(aeron_publication_image_t *image);\n\nvoid aeron_publication_image_set_response_session_id(\n    aeron_publication_image_t *image, int64_t response_session_id);\n\nextern int64_t aeron_publication_image_join_position(aeron_publication_image_t *image);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_publication_image.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_PUBLICATION_IMAGE_H\n#define AERON_PUBLICATION_IMAGE_H\n\n#include \"aeron_driver_common.h\"\n#include \"media/aeron_receive_channel_endpoint.h\"\n#include \"aeron_congestion_control.h\"\n#include \"aeron_loss_detector.h\"\n#include \"reports/aeron_loss_reporter.h\"\n\ntypedef enum aeron_publication_image_state_enum\n{\n    AERON_PUBLICATION_IMAGE_STATE_ACTIVE,\n    AERON_PUBLICATION_IMAGE_STATE_DRAINING,\n    AERON_PUBLICATION_IMAGE_STATE_LINGER,\n    AERON_PUBLICATION_IMAGE_STATE_DONE\n}\naeron_publication_image_state_t;\n\n#define AERON_IMAGE_SM_EOS_MULTIPLE (5)\n\ntypedef struct aeron_publication_image_connection_stct\n{\n    uint8_t padding_before[AERON_CACHE_LINE_LENGTH];\n    struct sockaddr_storage resolved_control_address_for_implicit_unicast_channels;\n    aeron_receive_destination_t *destination;  // Not owned.\n    struct sockaddr_storage *control_addr;     // Not owned.\n    bool is_eos;\n    int64_t time_of_last_activity_ns;\n    int64_t time_of_last_frame_ns;\n    int64_t eos_position;\n    uint8_t padding_after[AERON_CACHE_LINE_LENGTH];\n}\naeron_publication_image_connection_t;\n\ntypedef struct aeron_publication_image_stct\n{\n    uint8_t padding_before[AERON_CACHE_LINE_LENGTH];\n\n    struct aeron_publication_image_conductor_fields_stct\n    {\n        bool is_reliable;\n        aeron_publication_image_state_t state;\n        aeron_driver_managed_resource_t managed_resource;\n        aeron_subscribable_t subscribable;\n        int64_t time_of_last_state_change_ns;\n        int64_t liveness_timeout_ns;\n        int64_t untethered_window_limit_timeout_ns;\n        int64_t untethered_linger_timeout_ns;\n        int64_t untethered_resting_timeout_ns;\n        int64_t clean_position;\n        int32_t loss_report_term_id;\n        int32_t loss_report_term_offset;\n        size_t loss_report_length;\n        aeron_loss_reporter_t *loss_report;\n        aeron_loss_reporter_entry_offset_t loss_report_entry_offset;\n        aeron_receive_channel_endpoint_t *endpoint;\n        uint8_t flags;\n    }\n    conductor_fields;\n\n    uint8_t padding_after[AERON_CACHE_LINE_LENGTH];\n\n    struct image_connection_entries\n    {\n        size_t length;\n        size_t capacity;\n        aeron_publication_image_connection_t *array;\n    }\n    connections;\n\n    struct sockaddr_storage source_address;\n    size_t source_identity_length;\n    char source_identity[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n    aeron_loss_detector_t loss_detector;\n    aeron_feedback_delay_generator_state_t feedback_delay_state;\n\n    aeron_mapped_raw_log_t mapped_raw_log;\n    aeron_position_t rcv_hwm_position;\n    aeron_position_t rcv_pos_position;\n    aeron_atomic_counter_t rcv_naks_sent;\n    aeron_logbuffer_metadata_t *log_meta_data;\n\n    aeron_receive_channel_endpoint_t *endpoint;\n    aeron_congestion_control_strategy_t *congestion_control;\n    aeron_clock_func_t nano_clock;\n    aeron_clock_func_t epoch_clock;\n    aeron_clock_cache_t *cached_clock;\n\n    char *log_file_name;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t initial_term_id;\n    int32_t active_term_id;\n    int32_t term_length;\n    int32_t mtu_length;\n    int32_t term_length_mask;\n    size_t log_file_name_length;\n    size_t position_bits_to_shift;\n\n    aeron_raw_log_close_func_t raw_log_close_func;\n    aeron_raw_log_free_func_t raw_log_free_func;\n    struct\n    {\n        aeron_untethered_subscription_state_change_func_t untethered_subscription_state_change;\n        aeron_driver_publication_image_revoke_func_t publication_image_revoke;\n    } log;\n\n    volatile int64_t begin_loss_change;\n    volatile int64_t end_loss_change;\n    int64_t last_loss_change_number;\n    int32_t loss_term_id;\n    int32_t loss_term_offset;\n    size_t loss_length;\n\n    volatile int64_t begin_sm_change;\n    volatile int64_t end_sm_change;\n    int64_t last_overrun_threshold;\n    int64_t next_sm_position;\n    int32_t next_sm_receiver_window_length;\n    int32_t max_receiver_window_length;\n\n    int64_t last_sm_change_number;\n    int64_t last_sm_position;\n    int64_t next_sm_deadline_ns;\n    int64_t sm_timeout_ns;\n\n    volatile int64_t time_of_last_packet_ns;\n    const char *invalidation_reason;\n\n    bool is_sm_enabled;\n\n    volatile int64_t response_session_id;\n\n    volatile bool is_end_of_stream;\n    volatile bool is_sending_eos_sm;\n    volatile bool has_receiver_released;\n\n    volatile int64_t *heartbeats_received_counter;\n    volatile int64_t *flow_control_under_runs_counter;\n    volatile int64_t *flow_control_over_runs_counter;\n    volatile int64_t *status_messages_sent_counter;\n    volatile int64_t *nak_messages_sent_counter;\n    volatile int64_t *loss_gap_fills_counter;\n    volatile int64_t *mapped_bytes_counter;\n    volatile int64_t *publication_images_revoked_counter;\n}\naeron_publication_image_t;\n\nint aeron_publication_image_create(\n    aeron_publication_image_t **image,\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    aeron_driver_conductor_t *conductor,\n    int64_t correlation_id,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t initial_term_id,\n    int32_t active_term_id,\n    int32_t initial_term_offset,\n    aeron_position_t *rcv_hwm_position,\n    aeron_position_t *rcv_pos_position,\n    aeron_atomic_counter_t *rcv_naks_sent,\n    aeron_congestion_control_strategy_t *congestion_control,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *source_address,\n    int32_t term_buffer_length,\n    int32_t sender_mtu_length,\n    uint8_t flags,\n    aeron_loss_reporter_t *loss_reporter,\n    bool is_reliable,\n    bool is_sparse,\n    bool treat_as_multicast,\n    aeron_system_counters_t *system_counters);\n\nint aeron_publication_image_close(aeron_counters_manager_t *counters_manager, aeron_publication_image_t *image);\n\nbool aeron_publication_image_free(aeron_publication_image_t *image);\n\nvoid aeron_publication_image_clean_buffer_to(aeron_publication_image_t *image, int64_t position);\n\nvoid aeron_publication_image_on_gap_detected(void *clientd, int32_t term_id, int32_t term_offset, size_t length);\n\nvoid aeron_publication_image_track_rebuild(aeron_publication_image_t *image, int64_t now_ns);\n\nint aeron_publication_image_insert_packet(\n    aeron_publication_image_t *image,\n    aeron_receive_destination_t *destination,\n    int32_t term_id,\n    int32_t term_offset,\n    const uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nint aeron_publication_image_on_rttm(\n    aeron_publication_image_t *image, aeron_rttm_header_t *header, struct sockaddr_storage *addr);\n\nint aeron_publication_image_send_pending_status_message(aeron_publication_image_t *image, int64_t now_ns);\n\nint aeron_publication_image_send_pending_loss(aeron_publication_image_t *image);\n\nint aeron_publication_image_initiate_rttm(aeron_publication_image_t *image, int64_t now_ns);\n\nint aeron_publication_image_add_destination(aeron_publication_image_t *image, aeron_receive_destination_t *destination);\n\nint aeron_publication_image_remove_destination(aeron_publication_image_t *image, aeron_udp_channel_t *channel);\n\nvoid aeron_publication_image_add_connection_if_unknown(\n    aeron_publication_image_t *image, aeron_receive_destination_t *destination, struct sockaddr_storage *src_addr);\n\nvoid aeron_publication_image_on_time_event(\n    aeron_driver_conductor_t *conductor, aeron_publication_image_t *image, int64_t now_ns, int64_t now_ms);\n\nvoid aeron_publication_image_receiver_release(aeron_publication_image_t *image);\n\nvoid aeron_publication_image_invalidate(aeron_publication_image_t *image, int32_t reason_length, const char *reason);\n\ninline bool aeron_publication_image_is_heartbeat(const uint8_t *buffer, size_t length)\n{\n    return length == AERON_DATA_HEADER_LENGTH && 0 == ((aeron_frame_header_t *)buffer)->frame_length;\n}\n\ninline bool aeron_publication_image_is_end_of_stream(const uint8_t *buffer, size_t length)\n{\n    return (((aeron_frame_header_t *)buffer)->flags & AERON_DATA_HEADER_EOS_FLAG) != 0;\n}\n\ninline bool aeron_publication_image_is_revoked(const uint8_t *buffer, size_t length)\n{\n    return (((aeron_frame_header_t *)buffer)->flags & AERON_DATA_HEADER_REVOKED_FLAG) != 0;\n}\n\ninline bool aeron_publication_image_is_flow_control_under_run(aeron_publication_image_t *image, int64_t packet_position)\n{\n    const bool is_flow_control_under_run = packet_position < image->last_sm_position;\n\n    if (is_flow_control_under_run)\n    {\n        aeron_counter_increment_release(image->flow_control_under_runs_counter);\n    }\n\n    return is_flow_control_under_run;\n}\n\ninline bool aeron_publication_image_is_flow_control_over_run(\n    aeron_publication_image_t *image, int64_t proposed_position)\n{\n    const bool is_flow_control_over_run = proposed_position > image->last_overrun_threshold;\n\n    if (is_flow_control_over_run)\n    {\n        aeron_counter_increment_release(image->flow_control_over_runs_counter);\n    }\n\n    return is_flow_control_over_run;\n}\n\ninline void aeron_publication_image_schedule_status_message(\n    aeron_publication_image_t *image, int64_t sm_position, int32_t window_length)\n{\n    const int64_t change_number = image->begin_sm_change + 1;\n\n    AERON_SET_RELEASE(image->begin_sm_change, change_number);\n    aeron_release();\n\n    image->next_sm_position = sm_position;\n    image->next_sm_receiver_window_length = window_length;\n\n    AERON_SET_RELEASE(image->end_sm_change, change_number);\n}\n\ninline bool aeron_publication_image_is_drained(aeron_publication_image_t *image)\n{\n    int64_t rebuild_position = aeron_counter_get_plain(image->rcv_pos_position.value_addr);\n\n    for (size_t i = 0, length = image->conductor_fields.subscribable.length; i < length; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &image->conductor_fields.subscribable.array[i];\n\n        if (aeron_driver_subscribable_is_active_state(tetherable_position->state))\n        {\n            const int64_t sub_pos = aeron_counter_get_acquire(tetherable_position->value_addr);\n\n            if (sub_pos < rebuild_position)\n            {\n                return false;\n            }\n        }\n    }\n\n    return true;\n}\n\ninline bool aeron_publication_image_has_no_subscribers(aeron_publication_image_t *image)\n{\n    return !aeron_driver_subscribable_has_working_positions(&image->conductor_fields.subscribable);\n}\n\ninline bool aeron_publication_image_is_accepting_subscriptions(aeron_publication_image_t *image)\n{\n    return aeron_driver_subscribable_has_working_positions(&image->conductor_fields.subscribable) &&\n        (image->conductor_fields.state == AERON_PUBLICATION_IMAGE_STATE_ACTIVE ||\n            (image->conductor_fields.state == AERON_PUBLICATION_IMAGE_STATE_DRAINING &&\n                !aeron_publication_image_is_drained(image)));\n}\n\ninline void aeron_publication_image_disconnect_endpoint(aeron_publication_image_t *image)\n{\n    image->endpoint = NULL;\n}\n\ninline void aeron_publication_image_conductor_disconnect_endpoint(aeron_publication_image_t *image)\n{\n    image->conductor_fields.endpoint = NULL;\n}\n\ninline const char *aeron_publication_image_log_file_name(aeron_publication_image_t *image)\n{\n    return image->log_file_name;\n}\n\ninline int64_t aeron_publication_image_registration_id(aeron_publication_image_t *image)\n{\n    return image->conductor_fields.managed_resource.registration_id;\n}\n\ninline bool aeron_publication_image_has_send_response_setup(aeron_publication_image_t *image)\n{\n    return AERON_SETUP_HEADER_SEND_RESPONSE_FLAG & image->conductor_fields.flags;\n}\n\n// Called from Conductor\ninline void aeron_publication_image_set_response_session_id(\n    aeron_publication_image_t *image, int64_t response_session_id)\n{\n    AERON_SET_RELEASE(image->response_session_id, response_session_id);\n}\n\nvoid aeron_publication_image_remove_response_session_id(aeron_publication_image_t *image);\n\ninline int64_t aeron_publication_image_join_position(aeron_publication_image_t *image)\n{\n    int64_t position = *image->rcv_pos_position.value_addr;\n\n    for (size_t i = 0, length = image->conductor_fields.subscribable.length; i < length; i++)\n    {\n        aeron_tetherable_position_t *tetherable_position = &image->conductor_fields.subscribable.array[i];\n\n        if (aeron_driver_subscribable_is_active_state(tetherable_position->state))\n        {\n            const int64_t sub_pos = aeron_counter_get_acquire(tetherable_position->value_addr);\n\n            if (sub_pos < position)\n            {\n                position = sub_pos;\n            }\n        }\n    }\n\n    return position;\n}\n\nvoid aeron_publication_image_stop_status_messages_if_not_active(aeron_publication_image_t *image);\n\n#endif //AERON_PUBLICATION_IMAGE_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_retransmit_handler.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_retransmit_handler.h\"\n#include \"aeron_flow_control.h\"\n#include <assert.h>\n\nint aeron_retransmit_handler_scan_for_available_retransmit(\n    aeron_retransmit_handler_t *handler,\n    int32_t term_id,\n    int32_t term_offset,\n    aeron_retransmit_action_t **actionp);\n\nint aeron_retransmit_handler_init(\n    aeron_retransmit_handler_t *handler,\n    int64_t *invalid_packets_counter,\n    uint64_t delay_timeout_ns,\n    uint64_t linger_timeout_ns,\n    bool has_group_semantics,\n    uint32_t max_retransmits,\n    int64_t *retransmit_overflow_counter)\n{\n    handler->invalid_packets_counter = invalid_packets_counter;\n    handler->delay_timeout_ns = delay_timeout_ns;\n    handler->linger_timeout_ns = linger_timeout_ns;\n    handler->has_group_semantics = has_group_semantics;\n    handler->max_retransmits = has_group_semantics ? max_retransmits : 1;\n    handler->retransmit_overflow_counter = retransmit_overflow_counter;\n\n    assert(NULL != retransmit_overflow_counter);\n\n    if (aeron_alloc((void **)&handler->retransmit_action_pool, sizeof(aeron_retransmit_action_t) * handler->max_retransmits) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Could not allocate retransmit_action_pool\");\n        return -1;\n    }\n\n    for (size_t i = 0; i < handler->max_retransmits; i++)\n    {\n        handler->retransmit_action_pool[i].state = AERON_RETRANSMIT_ACTION_STATE_INACTIVE;\n    }\n\n    handler->active_retransmit_count = 0;\n\n    return 0;\n}\n\nvoid aeron_retransmit_handler_close(aeron_retransmit_handler_t *handler)\n{\n    aeron_free(handler->retransmit_action_pool);\n}\n\naeron_retransmit_action_t *aeron_retransmit_handler_add_retransmit(aeron_retransmit_handler_t *handler, aeron_retransmit_action_t *action)\n{\n    ++handler->active_retransmit_count;\n    return action;\n}\n\nvoid aeron_retransmit_handler_remove_retransmit(aeron_retransmit_handler_t *handler, aeron_retransmit_action_t *action)\n{\n    --handler->active_retransmit_count;\n    action->state = AERON_RETRANSMIT_ACTION_STATE_INACTIVE;\n}\n\nbool aeron_retransmit_handler_is_invalid(aeron_retransmit_handler_t *handler, int32_t term_offset, size_t term_length, int32_t length)\n{\n    const bool is_invalid = (term_offset > ((int32_t)(term_length - AERON_DATA_HEADER_LENGTH))) || (term_offset < 0) ||\n            (length < 0);\n\n    if (is_invalid)\n    {\n        aeron_counter_increment(handler->invalid_packets_counter);\n    }\n\n    return is_invalid;\n}\n\nint aeron_retransmit_handler_on_nak(\n    aeron_retransmit_handler_t *handler,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t length,\n    size_t term_length,\n    size_t mtu_length,\n    aeron_flow_control_strategy_t *flow_control,\n    int64_t now_ns,\n    aeron_retransmit_handler_resend_func_t resend,\n    void *resend_clientd)\n{\n    int result = 0;\n\n    if (!aeron_retransmit_handler_is_invalid(handler, term_offset, term_length, length) && 0 != length)\n    {\n        const size_t retransmit_length = flow_control->max_retransmission_length(flow_control->state, term_offset, (size_t)length, term_length, mtu_length);\n        aeron_retransmit_action_t *action = NULL;\n        if (aeron_retransmit_handler_scan_for_available_retransmit(handler, term_id, term_offset, &action) != 0)\n        {\n            AERON_APPEND_ERR(\"dropping nak termId=%d termOffset=%d retransmit_length=%d\", term_id, term_offset, retransmit_length);\n            return -1;\n        }\n\n        if (action != NULL)\n        {\n            action->term_id = term_id;\n            action->term_offset = term_offset;\n            action->length = retransmit_length;\n\n            if (0 == handler->delay_timeout_ns)\n            {\n                result = resend(resend_clientd, term_id, term_offset, action->length);\n                action->state = AERON_RETRANSMIT_ACTION_STATE_LINGERING;\n                action->expiry_ns = now_ns + (int64_t)handler->linger_timeout_ns;\n            }\n            else\n            {\n                action->state = AERON_RETRANSMIT_ACTION_STATE_DELAYED;\n                action->expiry_ns = now_ns + (int64_t)handler->delay_timeout_ns;\n            }\n        }\n    }\n\n    return result;\n}\n\nint aeron_retransmit_handler_process_timeouts(\n    aeron_retransmit_handler_t *handler,\n    int64_t now_ns,\n    aeron_retransmit_handler_resend_func_t resend,\n    void *resend_clientd)\n{\n    int result = 0;\n\n    if (handler->active_retransmit_count > 0)\n    {\n        for (size_t i = 0; i < handler->max_retransmits; i++)\n        {\n            aeron_retransmit_action_t *action = &handler->retransmit_action_pool[i];\n\n            if (AERON_RETRANSMIT_ACTION_STATE_DELAYED == action->state)\n            {\n                if (now_ns > action->expiry_ns)\n                {\n                    result = resend(resend_clientd, action->term_id, action->term_offset, action->length);\n                    action->state = AERON_RETRANSMIT_ACTION_STATE_LINGERING;\n                    action->expiry_ns = now_ns + (int64_t)handler->linger_timeout_ns;\n                    result++;\n                }\n            }\n            else if (AERON_RETRANSMIT_ACTION_STATE_LINGERING == action->state)\n            {\n                if (now_ns > action->expiry_ns)\n                {\n                    aeron_retransmit_handler_remove_retransmit(handler, action);\n                    result++;\n                }\n            }\n        }\n    }\n\n    return result;\n}\n\nint aeron_retransmit_handler_scan_for_available_retransmit(\n    aeron_retransmit_handler_t *handler,\n    int32_t term_id,\n    int32_t term_offset,\n    aeron_retransmit_action_t **actionp)\n{\n   if (0 == handler->active_retransmit_count)\n   {\n       *actionp = aeron_retransmit_handler_add_retransmit(handler, &handler->retransmit_action_pool[0]);\n       return 0;\n   }\n\n    aeron_retransmit_action_t *available_action = NULL;\n    for (size_t i = 0; i < handler->max_retransmits; i++)\n    {\n        aeron_retransmit_action_t *action = &handler->retransmit_action_pool[i];\n\n        switch (action->state)\n        {\n            case AERON_RETRANSMIT_ACTION_STATE_INACTIVE:\n                if (NULL == available_action)\n                {\n                    available_action = action;\n                }\n                break;\n\n            case AERON_RETRANSMIT_ACTION_STATE_DELAYED:\n            case AERON_RETRANSMIT_ACTION_STATE_LINGERING:\n                if (action->term_id == term_id &&\n                    action->term_offset <= term_offset &&\n                    term_offset < action->term_offset + (int32_t)action->length)\n                {\n                    *actionp = NULL;\n                    return 0;\n                }\n\n                if (!handler->has_group_semantics)\n                {\n                    // this is unicast, and the NAK does NOT overlap the previous one, so just reuse it\n                    available_action = action;\n                }\n                break;\n        }\n    }\n\n    if (handler->has_group_semantics)\n    {\n        if (NULL != available_action)\n        {\n            *actionp = aeron_retransmit_handler_add_retransmit(handler, available_action);\n            return 0;\n        }\n\n        aeron_counter_get_and_add_release(handler->retransmit_overflow_counter, 1);\n        *actionp = NULL;\n    }\n    else\n    {\n        *actionp = available_action;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_retransmit_handler.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_RETRANSMIT_HANDLER_H\n#define AERON_RETRANSMIT_HANDLER_H\n\n#include \"collections/aeron_int64_to_ptr_hash_map.h\"\n#include \"aeron_driver_common.h\"\n#include \"aeronmd.h\"\n\ntypedef enum aeron_retransmit_action_state_enum\n{\n    AERON_RETRANSMIT_ACTION_STATE_DELAYED,\n    AERON_RETRANSMIT_ACTION_STATE_LINGERING,\n    AERON_RETRANSMIT_ACTION_STATE_INACTIVE,\n}\naeron_retransmit_action_state_t;\n\ntypedef struct aeron_retransmit_action_stct\n{\n    int64_t expiry_ns;\n    int32_t term_id;\n    int32_t term_offset;\n    size_t length;\n    aeron_retransmit_action_state_t state;\n}\naeron_retransmit_action_t;\n\n#define AERON_RETRANSMIT_HANDLER_MAX_RESEND (16)\n#define AERON_RETRANSMIT_HANDLER_MAX_RESEND_MAX (256)\n\ntypedef int (*aeron_retransmit_handler_resend_func_t)(\n    void *clientd, int32_t term_id, int32_t term_offset, size_t length);\n\ntypedef struct aeron_retransmit_handler_stct\n{\n    aeron_retransmit_action_t *retransmit_action_pool;\n    uint64_t delay_timeout_ns;\n    uint64_t linger_timeout_ns;\n\n    int64_t *invalid_packets_counter;\n\n    int active_retransmit_count;\n\n    bool has_group_semantics;\n    size_t max_retransmits;\n    int64_t *retransmit_overflow_counter;\n}\naeron_retransmit_handler_t;\n\nint aeron_retransmit_handler_init(\n    aeron_retransmit_handler_t *handler,\n    int64_t *invalid_packets_counter,\n    uint64_t delay_timeout_ns,\n    uint64_t linger_timeout_ns,\n    bool has_group_semantics,\n    uint32_t max_retransmits,\n    int64_t *retransmit_overflow_counter);\n\nvoid aeron_retransmit_handler_close(aeron_retransmit_handler_t *handler);\n\nint aeron_retransmit_handler_on_nak(\n    aeron_retransmit_handler_t *handler,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t length,\n    size_t term_length,\n    size_t mtu_length,\n    aeron_flow_control_strategy_t *flow_control,\n    int64_t now_ns,\n    aeron_retransmit_handler_resend_func_t resend,\n    void *resend_clientd);\n\nint aeron_retransmit_handler_process_timeouts(\n    aeron_retransmit_handler_t *handler,\n    int64_t now_ns,\n    aeron_retransmit_handler_resend_func_t resend,\n    void *resend_clientd);\n\n#endif //AERON_RETRANSMIT_HANDLER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_system_counters.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <string.h>\n#include <errno.h>\n#include <inttypes.h>\n#include \"util/aeron_error.h\"\n#include \"aeron_system_counters.h\"\n#include \"aeron_alloc.h\"\n\nstatic aeron_system_counter_t system_counters[] =\n    {\n        { \"Bytes sent\", AERON_SYSTEM_COUNTER_BYTES_SENT },\n        { \"Bytes received\", AERON_SYSTEM_COUNTER_BYTES_RECEIVED },\n        { \"Failed offers to ReceiverProxy\", AERON_SYSTEM_COUNTER_RECEIVER_PROXY_FAILS },\n        { \"Failed offers to SenderProxy\", AERON_SYSTEM_COUNTER_SENDER_PROXY_FAILS },\n        { \"Failed offers to DriverConductorProxy\", AERON_SYSTEM_COUNTER_CONDUCTOR_PROXY_FAILS },\n        { \"NAKs sent\", AERON_SYSTEM_COUNTER_NAK_MESSAGES_SENT },\n        { \"NAKs received\", AERON_SYSTEM_COUNTER_NAK_MESSAGES_RECEIVED },\n        { \"Status Messages sent\", AERON_SYSTEM_COUNTER_STATUS_MESSAGES_SENT },\n        { \"Status Messages received\", AERON_SYSTEM_COUNTER_STATUS_MESSAGES_RECEIVED },\n        { \"Heartbeats sent\", AERON_SYSTEM_COUNTER_HEARTBEATS_SENT },\n        { \"Heartbeats received\", AERON_SYSTEM_COUNTER_HEARTBEATS_RECEIVED },\n        { \"Retransmits sent\", AERON_SYSTEM_COUNTER_RETRANSMITS_SENT },\n        { \"Flow control under runs\", AERON_SYSTEM_COUNTER_FLOW_CONTROL_UNDER_RUNS },\n        { \"Flow control over runs\", AERON_SYSTEM_COUNTER_FLOW_CONTROL_OVER_RUNS },\n        { \"Invalid packets\", AERON_SYSTEM_COUNTER_INVALID_PACKETS },\n        { \"Errors: version=\" AERON_VERSION_TXT \" commit=\" AERON_VERSION_GITSHA, AERON_SYSTEM_COUNTER_ERRORS },\n        { \"Short sends\", AERON_SYSTEM_COUNTER_SHORT_SENDS },\n        { \"Failed attempts to free log buffers\", AERON_SYSTEM_COUNTER_FREE_FAILS },\n        { \"Sender flow control limits, i.e. back-pressure events\", AERON_SYSTEM_COUNTER_SENDER_FLOW_CONTROL_LIMITS },\n        { \"Unblocked Publications\", AERON_SYSTEM_COUNTER_UNBLOCKED_PUBLICATIONS },\n        { \"Unblocked Control Commands\", AERON_SYSTEM_COUNTER_UNBLOCKED_COMMANDS },\n        { \"Possible TTL Asymmetry\", AERON_SYSTEM_COUNTER_POSSIBLE_TTL_ASYMMETRY },\n        { \"ControllableIdleStrategy status\", AERON_SYSTEM_COUNTER_CONTROLLABLE_IDLE_STRATEGY },\n        { \"Loss gap fills\", AERON_SYSTEM_COUNTER_LOSS_GAP_FILLS },\n        { \"Client liveness timeouts\", AERON_SYSTEM_COUNTER_CLIENT_TIMEOUTS },\n        { \"Resolution changes\", AERON_SYSTEM_COUNTER_RESOLUTION_CHANGES },\n        { \"Conductor max cycle time doing its work in ns\", AERON_SYSTEM_COUNTER_CONDUCTOR_MAX_CYCLE_TIME },\n        { \"Conductor work cycle exceeded threshold count\", AERON_SYSTEM_COUNTER_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED },\n        { \"Sender max cycle time doing its work in ns\", AERON_SYSTEM_COUNTER_SENDER_MAX_CYCLE_TIME },\n        { \"Sender work cycle exceeded threshold count\", AERON_SYSTEM_COUNTER_SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED },\n        { \"Receiver max cycle time doing its work in ns\", AERON_SYSTEM_COUNTER_RECEIVER_MAX_CYCLE_TIME },\n        { \"Receiver work cycle exceeded threshold count\", AERON_SYSTEM_COUNTER_RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED },\n        { \"NameResolver max time in ns\", AERON_SYSTEM_COUNTER_NAME_RESOLVER_MAX_TIME },\n        { \"NameResolver exceeded threshold count\", AERON_SYSTEM_COUNTER_NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED },\n        { \"Aeron software: version=\" AERON_VERSION_TXT \" commit=\" AERON_VERSION_GITSHA, AERON_SYSTEM_COUNTER_AERON_VERSION },\n        { \"Bytes currently mapped\", AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED },\n        { \"Retransmitted bytes\", AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES },\n        { \"Retransmit Pool Overflow count\", AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW },\n        { \"Error Frames received\", AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED },\n        { \"Error Frames sent\", AERON_SYSTEM_COUNTER_ERROR_FRAMES_SENT },\n        { \"Publications Revoked\", AERON_SYSTEM_COUNTER_PUBLICATIONS_REVOKED },\n        { \"Publication Images Revoked\", AERON_SYSTEM_COUNTER_PUBLICATION_IMAGES_REVOKED },\n        { \"Images rejected\", AERON_SYSTEM_COUNTER_IMAGES_REJECTED },\n        { \"Control protocol version\", AERON_SYSTEM_COUNTER_CONTROL_PROTOCOL_VERSION }\n    };\n\nstatic size_t num_system_counters = sizeof(system_counters) / sizeof(aeron_system_counter_t);\n\n#ifdef AERON_COMPILER_GCC\n_Static_assert(\n    AERON_SYSTEM_COUNTER_DUMMY_LAST == sizeof(system_counters) / sizeof(aeron_system_counter_t),\n    \"Please add counters in aeron_system_counter_t enum to the system_counters table\");\n#endif\n\nint aeron_system_counters_init(aeron_system_counters_t *counters, aeron_counters_manager_t *manager)\n{\n    if (NULL == counters || NULL == manager)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"counters=%s, manager=%s\", NULL == counters ? \"NULL\" : \"OK\", NULL == manager ? \"NULL\" : \"OK\");\n        return -1;\n    }\n\n    counters->manager = manager;\n\n    for (size_t i = 0; i < num_system_counters; i++)\n    {\n        const int32_t counter_id = aeron_counters_manager_allocate(\n            manager,\n            AERON_COUNTER_SYSTEM_COUNTER_TYPE_ID,\n            (const uint8_t *) &(system_counters[i].id),\n            sizeof(system_counters[i].id),\n            system_counters[i].label,\n            strlen(system_counters[i].label));\n\n        if (counter_id < 0 || counter_id != system_counters[i].id)\n        {\n            AERON_APPEND_ERR(\n                \"Failed to allocate system counter: id=%\" PRIi32 \", label=%s\",\n                system_counters[i].id,\n                system_counters[i].label);\n            return -1;\n        }\n\n        aeron_counters_manager_counter_registration_id(manager, counter_id, counter_id);\n        aeron_counters_manager_counter_owner_id(manager, counter_id, AERON_NULL_VALUE);\n    }\n\n    return 0;\n}\n\nvoid aeron_system_counters_close(aeron_system_counters_t *counters)\n{\n    for (int32_t i = 0; i < (int32_t)num_system_counters; i++)\n    {\n        aeron_counters_manager_free(counters->manager, system_counters[i].id);\n    }\n}\n\nextern int64_t *aeron_system_counter_addr(aeron_system_counters_t *counters, aeron_system_counter_enum_t type);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_system_counters.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_SYSTEM_COUNTERS_H\n#define AERON_SYSTEM_COUNTERS_H\n\n#include \"aeron_counters.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n\ntypedef enum aeron_system_counter_enum_stct\n{\n    AERON_SYSTEM_COUNTER_BYTES_SENT = AERON_SYSTEM_COUNTER_ID_BYTES_SENT,\n    AERON_SYSTEM_COUNTER_BYTES_RECEIVED = AERON_SYSTEM_COUNTER_ID_BYTES_RECEIVED,\n    AERON_SYSTEM_COUNTER_RECEIVER_PROXY_FAILS = AERON_SYSTEM_COUNTER_ID_RECEIVER_PROXY_FAILS,\n    AERON_SYSTEM_COUNTER_SENDER_PROXY_FAILS = AERON_SYSTEM_COUNTER_ID_SENDER_PROXY_FAILS,\n    AERON_SYSTEM_COUNTER_CONDUCTOR_PROXY_FAILS = AERON_SYSTEM_COUNTER_ID_CONDUCTOR_PROXY_FAILS,\n    AERON_SYSTEM_COUNTER_NAK_MESSAGES_SENT = AERON_SYSTEM_COUNTER_ID_NAK_MESSAGES_SENT,\n    AERON_SYSTEM_COUNTER_NAK_MESSAGES_RECEIVED = AERON_SYSTEM_COUNTER_ID_NAK_MESSAGES_RECEIVED,\n    AERON_SYSTEM_COUNTER_STATUS_MESSAGES_SENT = AERON_SYSTEM_COUNTER_ID_STATUS_MESSAGES_SENT,\n    AERON_SYSTEM_COUNTER_STATUS_MESSAGES_RECEIVED = AERON_SYSTEM_COUNTER_ID_STATUS_MESSAGES_RECEIVED,\n    AERON_SYSTEM_COUNTER_HEARTBEATS_SENT = AERON_SYSTEM_COUNTER_ID_HEARTBEATS_SENT,\n    AERON_SYSTEM_COUNTER_HEARTBEATS_RECEIVED = AERON_SYSTEM_COUNTER_ID_HEARTBEATS_RECEIVED,\n    AERON_SYSTEM_COUNTER_RETRANSMITS_SENT = AERON_SYSTEM_COUNTER_ID_RETRANSMITS_SENT,\n    AERON_SYSTEM_COUNTER_FLOW_CONTROL_UNDER_RUNS = AERON_SYSTEM_COUNTER_ID_FLOW_CONTROL_UNDER_RUNS,\n    AERON_SYSTEM_COUNTER_FLOW_CONTROL_OVER_RUNS = AERON_SYSTEM_COUNTER_ID_FLOW_CONTROL_OVER_RUNS,\n    AERON_SYSTEM_COUNTER_INVALID_PACKETS = AERON_SYSTEM_COUNTER_ID_INVALID_PACKETS,\n    AERON_SYSTEM_COUNTER_ERRORS = AERON_SYSTEM_COUNTER_ID_ERRORS,\n    AERON_SYSTEM_COUNTER_SHORT_SENDS = AERON_SYSTEM_COUNTER_ID_SHORT_SENDS,\n    AERON_SYSTEM_COUNTER_FREE_FAILS = AERON_SYSTEM_COUNTER_ID_FREE_FAILS,\n    AERON_SYSTEM_COUNTER_SENDER_FLOW_CONTROL_LIMITS = AERON_SYSTEM_COUNTER_ID_SENDER_FLOW_CONTROL_LIMITS,\n    AERON_SYSTEM_COUNTER_UNBLOCKED_PUBLICATIONS = AERON_SYSTEM_COUNTER_ID_UNBLOCKED_PUBLICATIONS,\n    AERON_SYSTEM_COUNTER_UNBLOCKED_COMMANDS = AERON_SYSTEM_COUNTER_ID_UNBLOCKED_COMMANDS,\n    AERON_SYSTEM_COUNTER_POSSIBLE_TTL_ASYMMETRY = AERON_SYSTEM_COUNTER_ID_POSSIBLE_TTL_ASYMMETRY,\n    AERON_SYSTEM_COUNTER_CONTROLLABLE_IDLE_STRATEGY = AERON_SYSTEM_COUNTER_ID_CONTROLLABLE_IDLE_STRATEGY,\n    AERON_SYSTEM_COUNTER_LOSS_GAP_FILLS = AERON_SYSTEM_COUNTER_ID_LOSS_GAP_FILLS,\n    AERON_SYSTEM_COUNTER_CLIENT_TIMEOUTS = AERON_SYSTEM_COUNTER_ID_CLIENT_TIMEOUTS,\n    AERON_SYSTEM_COUNTER_RESOLUTION_CHANGES = AERON_SYSTEM_COUNTER_ID_RESOLUTION_CHANGES,\n    AERON_SYSTEM_COUNTER_CONDUCTOR_MAX_CYCLE_TIME = AERON_SYSTEM_COUNTER_ID_CONDUCTOR_MAX_CYCLE_TIME,\n    AERON_SYSTEM_COUNTER_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED = AERON_SYSTEM_COUNTER_ID_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED,\n    AERON_SYSTEM_COUNTER_SENDER_MAX_CYCLE_TIME = AERON_SYSTEM_COUNTER_ID_SENDER_MAX_CYCLE_TIME,\n    AERON_SYSTEM_COUNTER_SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED = AERON_SYSTEM_COUNTER_ID_SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED,\n    AERON_SYSTEM_COUNTER_RECEIVER_MAX_CYCLE_TIME = AERON_SYSTEM_COUNTER_ID_RECEIVER_MAX_CYCLE_TIME,\n    AERON_SYSTEM_COUNTER_RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED = AERON_SYSTEM_COUNTER_ID_RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED,\n    AERON_SYSTEM_COUNTER_NAME_RESOLVER_MAX_TIME = AERON_SYSTEM_COUNTER_ID_NAME_RESOLVER_MAX_TIME,\n    AERON_SYSTEM_COUNTER_NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED = AERON_SYSTEM_COUNTER_ID_NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED,\n    AERON_SYSTEM_COUNTER_AERON_VERSION = AERON_SYSTEM_COUNTER_ID_AERON_VERSION,\n    AERON_SYSTEM_COUNTER_BYTES_CURRENTLY_MAPPED = AERON_SYSTEM_COUNTER_ID_BYTES_CURRENTLY_MAPPED,\n    AERON_SYSTEM_COUNTER_RETRANSMITTED_BYTES = AERON_SYSTEM_COUNTER_ID_RETRANSMITTED_BYTES,\n    AERON_SYSTEM_COUNTER_RETRANSMIT_OVERFLOW = AERON_SYSTEM_COUNTER_ID_RETRANSMIT_OVERFLOW,\n    AERON_SYSTEM_COUNTER_ERROR_FRAMES_RECEIVED = AERON_SYSTEM_COUNTER_ID_ERROR_FRAMES_RECEIVED,\n    AERON_SYSTEM_COUNTER_ERROR_FRAMES_SENT = AERON_SYSTEM_COUNTER_ID_ERROR_FRAMES_SENT,\n    AERON_SYSTEM_COUNTER_PUBLICATIONS_REVOKED = AERON_SYSTEM_COUNTER_ID_PUBLICATIONS_REVOKED,\n    AERON_SYSTEM_COUNTER_PUBLICATION_IMAGES_REVOKED = AERON_SYSTEM_COUNTER_ID_PUBLICATION_IMAGES_REVOKED,\n    AERON_SYSTEM_COUNTER_IMAGES_REJECTED = AERON_SYSTEM_COUNTER_ID_IMAGES_REJECTED,\n    AERON_SYSTEM_COUNTER_CONTROL_PROTOCOL_VERSION = AERON_SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION,\n\n    // Add all new counters before this one (used for a static assertion).\n    AERON_SYSTEM_COUNTER_DUMMY_LAST,\n}\naeron_system_counter_enum_t;\n\ntypedef struct aeron_system_counter_stct\n{\n    const char *label;\n    int32_t id;\n}\naeron_system_counter_t;\n\ntypedef struct aeron_system_counters_stct\n{\n    aeron_counters_manager_t *manager;\n}\naeron_system_counters_t;\n\nint aeron_system_counters_init(aeron_system_counters_t *counters, aeron_counters_manager_t *manager);\n\nvoid aeron_system_counters_close(aeron_system_counters_t *counters);\n\ninline int64_t *aeron_system_counter_addr(aeron_system_counters_t *counters, aeron_system_counter_enum_t type)\n{\n    return aeron_counters_manager_addr(counters->manager, (int32_t)type);\n}\n\n#endif //AERON_SYSTEM_COUNTERS_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_termination_validator.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <string.h>\n#include \"util/aeron_error.h\"\n#include \"util/aeron_symbol_table.h\"\n#include \"aeron_termination_validator.h\"\n\nstatic const aeron_symbol_table_func_t aeron_termination_validator_table[] =\n    {\n        {\n            \"allow\",\n            \"aeron_driver_termination_validator_default_allow\",\n            (aeron_fptr_t)aeron_driver_termination_validator_default_allow\n        },\n        {\n            \"deny\",\n            \"aeron_driver_termination_validator_default_deny\",\n            (aeron_fptr_t)aeron_driver_termination_validator_default_deny\n        }\n    };\n\nstatic const size_t aeron_termination_validator_table_length =\n    sizeof(aeron_termination_validator_table) / sizeof(aeron_symbol_table_func_t);\n\nbool aeron_driver_termination_validator_default_allow(void *state, uint8_t *token_buffer, int32_t token_length)\n{\n    return true;\n}\n\nbool aeron_driver_termination_validator_default_deny(void *state, uint8_t *token_buffer, int32_t token_length)\n{\n    return false;\n}\n\naeron_driver_termination_validator_func_t aeron_driver_termination_validator_load(const char *validator_name)\n{\n    return (aeron_driver_termination_validator_func_t)aeron_symbol_table_func_load(\n        aeron_termination_validator_table,\n        aeron_termination_validator_table_length,\n        validator_name,\n        \"terminate validator\");\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeron_termination_validator.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef AERON_TERMINATION_VALIDATOR_H\n#define AERON_TERMINATION_VALIDATOR_H\n\n#include \"aeron_driver_common.h\"\n#include \"aeronmd.h\"\n\nbool aeron_driver_termination_validator_default_allow(void *state, uint8_t *token_buffer, int32_t token_length);\nbool aeron_driver_termination_validator_default_deny(void *state, uint8_t *token_buffer, int32_t token_length);\n\naeron_driver_termination_validator_func_t aeron_driver_termination_validator_load(const char *validator_name);\n\n#endif //AERON_TERMINATION_VALIDATOR_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeronmd.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdio.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#include \"aeronmd.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"aeron_driver_context.h\"\n#include \"util/aeron_properties_util.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_error.h\"\n\nvolatile int exit_status = AERON_NULL_VALUE;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(exit_status, signal);\n}\n\nvoid termination_hook(void *state)\n{\n    AERON_SET_RELEASE(exit_status, EXIT_SUCCESS);\n}\n\ninline bool is_running(void)\n{\n    int result;\n    AERON_GET_ACQUIRE(result, exit_status);\n    return (AERON_NULL_VALUE == result);\n}\n\nint set_property(void *clientd, const char *name, const char *value)\n{\n    return aeron_properties_setenv(name, value);\n}\n\nint main(int argc, char **argv)\n{\n    aeron_driver_context_t *context = NULL;\n    aeron_driver_t *driver = NULL;\n\n    int opt;\n\n    while ((opt = getopt(argc, argv, \"D:v\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'D':\n            {\n                aeron_properties_parser_state_t state;\n                aeron_properties_parse_init(&state);\n                if (aeron_properties_parse_line(&state, optarg, strlen(optarg), set_property, NULL) < 0)\n                {\n                    fprintf(stderr, \"malformed define: %s\\n\", optarg);\n                    exit(EXIT_FAILURE);\n                }\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            default:\n                fprintf(stderr, \"Usage: %s [-v][-Dname=value]\\n\", argv[0]);\n                exit(EXIT_FAILURE);\n        }\n    }\n\n    for (int i = optind; i < argc; i++)\n    {\n        if (aeron_properties_load(argv[i]) < 0)\n        {\n            fprintf(stderr, \"ERROR: loading properties from %s (%d) %s\\n\", argv[i], aeron_errcode(), aeron_errmsg());\n            exit(EXIT_FAILURE);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n    signal(SIGTERM, sigint_handler);\n\n    if (aeron_driver_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"ERROR: context init (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        AERON_SET_RELEASE(exit_status, EXIT_FAILURE);\n        goto cleanup;\n    }\n\n    if (aeron_driver_context_set_driver_termination_hook(context, termination_hook, NULL) < 0)\n    {\n        fprintf(stderr, \"ERROR: context set termination hook (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        AERON_SET_RELEASE(exit_status, EXIT_FAILURE);\n        goto cleanup;\n    }\n\n    // preserve overridden start function and state\n    context->agent_on_start_func_delegate = context->agent_on_start_func;\n    context->agent_on_start_state_delegate = context->agent_on_start_state;\n    if (aeron_driver_context_set_agent_on_start_function(context, aeron_set_thread_affinity_on_start, context))\n    {\n        fprintf(stderr, \"ERROR: unable to set on_start function(%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        AERON_SET_RELEASE(exit_status, EXIT_FAILURE);\n        goto cleanup;\n    }\n\n    if (aeron_driver_init(&driver, context) < 0)\n    {\n        fprintf(stderr, \"ERROR: driver init (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        AERON_SET_RELEASE(exit_status, EXIT_FAILURE);\n        goto cleanup;\n    }\n\n    if (aeron_driver_start(driver, true) < 0)\n    {\n        fprintf(stderr, \"ERROR: driver start (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        AERON_SET_RELEASE(exit_status, EXIT_FAILURE);\n        goto cleanup;\n    }\n\n    while (is_running())\n    {\n        aeron_driver_main_idle_strategy(driver, aeron_driver_main_do_work(driver));\n    }\n\n    printf(\"Shutting down driver...\\n\");\n\ncleanup:\n    if (NULL != driver && 0 != aeron_driver_close(driver))\n    {\n        fprintf(stderr, \"ERROR: driver close (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        AERON_SET_RELEASE(exit_status, EXIT_FAILURE);\n    }\n\n    if (NULL != context && 0 != aeron_driver_context_close(context))\n    {\n        fprintf(stderr, \"ERROR: driver context close (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        AERON_SET_RELEASE(exit_status, EXIT_FAILURE);\n    }\n\n    return exit_status;\n}\n\nextern bool is_running(void);\n"
  },
  {
    "path": "aeron-driver/src/main/c/aeronmd.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERONMD_H\n#define AERON_AERONMD_H\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stddef.h>\n\ntypedef struct aeron_driver_context_stct aeron_driver_context_t;\ntypedef struct aeron_driver_stct aeron_driver_t;\n\n/**\n * Environment variables used for setting values of an aeron_driver_context_t.\n */\n\n/**\n * The top level Aeron directory used for communication between a Media Driver and client.\n */\n#define AERON_DIR_ENV_VAR \"AERON_DIR\"\n\nint aeron_driver_context_set_dir(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_dir(aeron_driver_context_t *context);\n\n/**\n * Warn if the top level Aeron directory exists when starting the driver.\n */\n#define AERON_DIR_WARN_IF_EXISTS_ENV_VAR \"AERON_DIR_WARN_IF_EXISTS\"\n\nint aeron_driver_context_set_dir_warn_if_exists(aeron_driver_context_t *context, bool value);\nbool aeron_driver_context_get_dir_warn_if_exists(aeron_driver_context_t *context);\n\n/**\n * Threading Mode to be used by the driver.\n */\n#define AERON_THREADING_MODE_ENV_VAR \"AERON_THREADING_MODE\"\n\ntypedef enum aeron_threading_mode_enum\n{\n    AERON_THREADING_MODE_DEDICATED,\n    AERON_THREADING_MODE_SHARED_NETWORK,\n    AERON_THREADING_MODE_SHARED,\n    AERON_THREADING_MODE_INVOKER\n}\naeron_threading_mode_t;\n\nint aeron_driver_context_set_threading_mode(aeron_driver_context_t *context, aeron_threading_mode_t mode);\naeron_threading_mode_t aeron_driver_context_get_threading_mode(aeron_driver_context_t *context);\n\n/**\n * Attempt to delete directories on start if they exist.\n */\n#define AERON_DIR_DELETE_ON_START_ENV_VAR \"AERON_DIR_DELETE_ON_START\"\n\nint aeron_driver_context_set_dir_delete_on_start(aeron_driver_context_t * context, bool value);\nbool aeron_driver_context_get_dir_delete_on_start(aeron_driver_context_t *context);\n\n/**\n * Attempt to delete directories on shutdown.\n */\n#define AERON_DIR_DELETE_ON_SHUTDOWN_ENV_VAR \"AERON_DIR_DELETE_ON_SHUTDOWN\"\n\nint aeron_driver_context_set_dir_delete_on_shutdown(aeron_driver_context_t * context, bool value);\nbool aeron_driver_context_get_dir_delete_on_shutdown(aeron_driver_context_t *context);\n\n/**\n * Length (in bytes) of the conductor buffer for control commands from the clients to the media driver conductor.\n */\n#define AERON_TO_CONDUCTOR_BUFFER_LENGTH_ENV_VAR \"AERON_CONDUCTOR_BUFFER_LENGTH\"\n\nint aeron_driver_context_set_to_conductor_buffer_length(aeron_driver_context_t *context, size_t length);\nsize_t aeron_driver_context_get_to_conductor_buffer_length(aeron_driver_context_t *context);\n\n/**\n * Length (in bytes) of the broadcast buffers from the media driver to the clients.\n */\n#define AERON_TO_CLIENTS_BUFFER_LENGTH_ENV_VAR \"AERON_CLIENTS_BUFFER_LENGTH\"\n\nint aeron_driver_context_set_to_clients_buffer_length(aeron_driver_context_t *context, size_t length);\nsize_t aeron_driver_context_get_to_clients_buffer_length(aeron_driver_context_t *context);\n\n/**\n * Length (in bytes) of the value buffer for the system counters.\n */\n#define AERON_COUNTERS_VALUES_BUFFER_LENGTH_ENV_VAR \"AERON_COUNTERS_BUFFER_LENGTH\"\n\nint aeron_driver_context_set_counters_buffer_length(aeron_driver_context_t *context, size_t length);\nsize_t aeron_driver_context_get_counters_buffer_length(aeron_driver_context_t *context);\n\n/**\n * Length (in bytes) of the buffer for the distinct error log.\n */\n#define AERON_ERROR_BUFFER_LENGTH_ENV_VAR \"AERON_ERROR_BUFFER_LENGTH\"\n\nint aeron_driver_context_set_error_buffer_length(aeron_driver_context_t *context, size_t length);\nsize_t aeron_driver_context_get_error_buffer_length(aeron_driver_context_t *context);\n\n/**\n * Client liveness timeout in nanoseconds\n */\n#define AERON_CLIENT_LIVENESS_TIMEOUT_ENV_VAR \"AERON_CLIENT_LIVENESS_TIMEOUT\"\n\nint aeron_driver_context_set_client_liveness_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_client_liveness_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * Length (in bytes) of the log buffers for publication terms.\n */\n#define AERON_TERM_BUFFER_LENGTH_ENV_VAR \"AERON_TERM_BUFFER_LENGTH\"\n\nint aeron_driver_context_set_term_buffer_length(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_term_buffer_length(aeron_driver_context_t *context);\n\n/**\n * Length (in bytes) of the log buffers for IPC publication terms.\n */\n#define AERON_IPC_TERM_BUFFER_LENGTH_ENV_VAR \"AERON_IPC_TERM_BUFFER_LENGTH\"\n\nint aeron_driver_context_set_ipc_term_buffer_length(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_ipc_term_buffer_length(aeron_driver_context_t *context);\n\n/**\n * Should term buffers be created sparse.\n */\n#define AERON_TERM_BUFFER_SPARSE_FILE_ENV_VAR \"AERON_TERM_BUFFER_SPARSE_FILE\"\n\nint aeron_driver_context_set_term_buffer_sparse_file(aeron_driver_context_t *context, bool value);\nbool aeron_driver_context_get_term_buffer_sparse_file(aeron_driver_context_t *context);\n\n/**\n * Should storage checks should be performed when allocating files.\n */\n#define AERON_PERFORM_STORAGE_CHECKS_ENV_VAR \"AERON_PERFORM_STORAGE_CHECKS\"\n\nint aeron_driver_context_set_perform_storage_checks(aeron_driver_context_t *context, bool value);\nbool aeron_driver_context_get_perform_storage_checks(aeron_driver_context_t *context);\n\n/**\n * Specify the interval which checks for re-resolutions of names occurs.\n */\n#define AERON_LOW_FILE_STORE_WARNING_THRESHOLD_ENV_VAR \"AERON_LOW_FILE_STORE_WARNING_THRESHOLD\"\n\nint aeron_driver_context_set_low_file_store_warning_threshold(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_low_file_store_warning_threshold(aeron_driver_context_t *context);\n\n/**\n * Should a spy subscription simulate a connection to a network publication.\n */\n#define AERON_SPIES_SIMULATE_CONNECTION_ENV_VAR \"AERON_SPIES_SIMULATE_CONNECTION\"\n\nint aeron_driver_context_set_spies_simulate_connection(aeron_driver_context_t *context, bool value);\nbool aeron_driver_context_get_spies_simulate_connection(aeron_driver_context_t *context);\n\n/**\n * Page size for alignment of all files.\n */\n#define AERON_FILE_PAGE_SIZE_ENV_VAR \"AERON_FILE_PAGE_SIZE\"\n\nint aeron_driver_context_set_file_page_size(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_file_page_size(aeron_driver_context_t *context);\n\n/**\n * Length (in bytes) of the maximum transmission unit of the publication.\n */\n#define AERON_MTU_LENGTH_ENV_VAR \"AERON_MTU_LENGTH\"\n\nint aeron_driver_context_set_mtu_length(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_mtu_length(aeron_driver_context_t *context);\n\n/**\n * Length (in bytes) of the maximum transmission unit of the IPC publication.\n */\n#define AERON_IPC_MTU_LENGTH_ENV_VAR \"AERON_IPC_MTU_LENGTH\"\n\nint aeron_driver_context_set_ipc_mtu_length(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_ipc_mtu_length(aeron_driver_context_t *context);\n\n/**\n * Window limit on IPC Publication side.\n */\n#define AERON_IPC_PUBLICATION_TERM_WINDOW_LENGTH_ENV_VAR \"AERON_IPC_PUBLICATION_TERM_WINDOW_LENGTH\"\n\nint aeron_driver_context_set_ipc_publication_term_window_length(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_ipc_publication_term_window_length(aeron_driver_context_t *context);\n\n/**\n * Window limit on Publication side.\n */\n#define AERON_PUBLICATION_TERM_WINDOW_LENGTH_ENV_VAR \"AERON_PUBLICATION_TERM_WINDOW_LENGTH\"\n\nint aeron_driver_context_set_publication_term_window_length(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_publication_term_window_length(aeron_driver_context_t *context);\n\n/**\n * Linger timeout in nanoseconds on publications.\n */\n#define AERON_PUBLICATION_LINGER_TIMEOUT_ENV_VAR \"AERON_PUBLICATION_LINGER_TIMEOUT\"\n\nint aeron_driver_context_set_publication_linger_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_publication_linger_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * SO_RCVBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP).\n */\n#define AERON_SOCKET_SO_RCVBUF_ENV_VAR \"AERON_SOCKET_SO_RCVBUF\"\n\nint aeron_driver_context_set_socket_so_rcvbuf(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_socket_so_rcvbuf(aeron_driver_context_t *context);\n\n/**\n * SO_SNDBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP).\n */\n#define AERON_SOCKET_SO_SNDBUF_ENV_VAR \"AERON_SOCKET_SO_SNDBUF\"\n\nint aeron_driver_context_set_socket_so_sndbuf(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_socket_so_sndbuf(aeron_driver_context_t *context);\n\n/**\n * IP_MULTICAST_TTL setting on outgoing UDP sockets.\n */\n#define AERON_SOCKET_MULTICAST_TTL_ENV_VAR \"AERON_SOCKET_MULTICAST_TTL\"\n\nint aeron_driver_context_set_socket_multicast_ttl(aeron_driver_context_t *context, uint8_t value);\nuint8_t aeron_driver_context_get_socket_multicast_ttl(aeron_driver_context_t *context);\n\n/**\n * Ratio of sending data to polling status messages in the Sender.\n */\n#define AERON_SEND_TO_STATUS_POLL_RATIO_ENV_VAR \"AERON_SEND_TO_STATUS_POLL_RATIO\"\n\nint aeron_driver_context_set_send_to_status_poll_ratio(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_send_to_status_poll_ratio(aeron_driver_context_t *context);\n\n/**\n * Status Message timeout in nanoseconds.\n */\n#define AERON_RCV_STATUS_MESSAGE_TIMEOUT_ENV_VAR \"AERON_RCV_STATUS_MESSAGE_TIMEOUT\"\n\nint aeron_driver_context_set_rcv_status_message_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_rcv_status_message_timeout_ns(aeron_driver_context_t *context);\n\ntypedef struct aeron_flow_control_strategy_stct aeron_flow_control_strategy_t;\n\ntypedef struct aeron_counters_manager_stct aeron_counters_manager_t;\ntypedef struct aeron_udp_channel_stct aeron_udp_channel_t;\n\ntypedef int (*aeron_flow_control_strategy_supplier_func_t)(\n    aeron_flow_control_strategy_t **strategy,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    const aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t initial_term_id,\n    size_t term_length);\n\n#define AERON_MULTICAST_MIN_FLOW_CONTROL_STRATEGY_NAME \"multicast_min\"\n#define AERON_MULTICAST_MAX_FLOW_CONTROL_STRATEGY_NAME \"multicast_max\"\n#define AERON_MULTICAST_TAGGED_FLOW_CONTROL_STRATEGY_NAME \"multicast_tagged\"\n#define AERON_UNICAST_MAX_FLOW_CONTROL_STRATEGY_NAME \"unicast_max\"\n\n/**\n * Return a flow control strategy supplier function pointer associated with the given name. This only will find\n * strategies built into the driver and will not try to dynamically load nor find any in the current executable.\n *\n * @param name of the strategy\n * @return function pointer to supplier associated with the name\n */\naeron_flow_control_strategy_supplier_func_t aeron_flow_control_strategy_supplier_by_name(const char *name);\n\n/**\n * Supplier for flow control structure to be employed for multicast channels.\n */\n#define AERON_MULTICAST_FLOWCONTROL_SUPPLIER_ENV_VAR \"AERON_MULTICAST_FLOWCONTROL_SUPPLIER\"\n\nint aeron_driver_context_set_multicast_flowcontrol_supplier(\n    aeron_driver_context_t *context, aeron_flow_control_strategy_supplier_func_t value);\naeron_flow_control_strategy_supplier_func_t aeron_driver_context_get_multicast_flowcontrol_supplier(\n    aeron_driver_context_t *context);\n\n/**\n * Supplier for flow control structure to be employed for unicast channels.\n */\n#define AERON_UNICAST_FLOWCONTROL_SUPPLIER_ENV_VAR \"AERON_UNICAST_FLOWCONTROL_SUPPLIER\"\n\nint aeron_driver_context_set_unicast_flowcontrol_supplier(\n    aeron_driver_context_t *context, aeron_flow_control_strategy_supplier_func_t value);\naeron_flow_control_strategy_supplier_func_t aeron_driver_context_get_unicast_flowcontrol_supplier(\n    aeron_driver_context_t *context);\n\n/**\n * Image liveness timeout in nanoseconds\n */\n#define AERON_IMAGE_LIVENESS_TIMEOUT_ENV_VAR \"AERON_IMAGE_LIVENESS_TIMEOUT\"\n\nint aeron_driver_context_set_image_liveness_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_image_liveness_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * Length of the initial window which must be sufficient for Bandwidth Delay Product (BDP).\n */\n#define AERON_RCV_INITIAL_WINDOW_LENGTH_ENV_VAR \"AERON_RCV_INITIAL_WINDOW_LENGTH\"\n\nint aeron_driver_context_set_rcv_initial_window_length(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_rcv_initial_window_length(aeron_driver_context_t *context);\n\n/**\n * Supplier for congestion control structure to be employed for Images.\n */\n#define AERON_CONGESTIONCONTROL_SUPPLIER_ENV_VAR \"AERON_CONGESTIONCONTROL_SUPPLIER\"\n\ntypedef struct aeron_congestion_control_strategy_stct aeron_congestion_control_strategy_t;\n\n/**\n * Should Cubic congestion control measure RTT.\n */\n#define AERON_CUBICCONGESTIONCONTROL_MEASURERTT_ENV_VAR \"AERON_CUBICCONGESTIONCONTROL_MEASURERTT\"\n\n/**\n * Initial RTT measurement in nanoseconds for Cubic congestion control.\n */\n#define AERON_CUBICCONGESTIONCONTROL_INITIALRTT_ENV_VAR \"AERON_CUBICCONGESTIONCONTROL_INITIALRTT\"\n\n/**\n * Should Cubic congestion control account for TCP behavior in low RTT values after a loss.\n * <p>\n * <b>WARNING:</b> Be aware that throughput utilization becomes important. Turning this on may drastically be off\n * the necessary throughput if utilization is low.\n */\n#define AERON_CUBICCONGESTIONCONTROL_TCPMODE_ENV_VAR \"AERON_CUBICCONGESTIONCONTROL_TCPMODE\"\n\ntypedef struct aeron_counters_manager_stct aeron_counters_manager_t;\nstruct sockaddr_storage;\n\ntypedef int (*aeron_congestion_control_strategy_supplier_func_t)(\n    aeron_congestion_control_strategy_t **strategy,\n    aeron_udp_channel_t *channel,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t registration_id,\n    int32_t term_length,\n    int32_t sender_mtu_length,\n    struct sockaddr_storage *control_address,\n    struct sockaddr_storage *src_address,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager);\n\nint aeron_driver_context_set_congestioncontrol_supplier(\n    aeron_driver_context_t *context, aeron_congestion_control_strategy_supplier_func_t value);\naeron_congestion_control_strategy_supplier_func_t aeron_driver_context_get_congestioncontrol_supplier(\n    aeron_driver_context_t *context);\n\n/**\n * Length (in bytes) of the buffer for the loss report log.\n */\n#define AERON_LOSS_REPORT_BUFFER_LENGTH_ENV_VAR \"AERON_LOSS_REPORT_BUFFER_LENGTH\"\n\nint aeron_driver_context_set_loss_report_buffer_length(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_loss_report_buffer_length(aeron_driver_context_t *context);\n\n/**\n * Timeout for publication unblock in nanoseconds.\n */\n#define AERON_PUBLICATION_UNBLOCK_TIMEOUT_ENV_VAR \"AERON_PUBLICATION_UNBLOCK_TIMEOUT\"\n\nint aeron_driver_context_set_publication_unblock_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_publication_unblock_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * Timeout for publication connection in nanoseconds.\n */\n#define AERON_PUBLICATION_CONNECTION_TIMEOUT_ENV_VAR \"AERON_PUBLICATION_CONNECTION_TIMEOUT\"\n\nint aeron_driver_context_set_publication_connection_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_publication_connection_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * Interval (in nanoseconds) between checks for timers and timeouts.\n */\n#define AERON_TIMER_INTERVAL_ENV_VAR \"AERON_TIMER_INTERVAL\"\n\nint aeron_driver_context_set_timer_interval_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_timer_interval_ns(aeron_driver_context_t *context);\n\n/**\n * Idle strategy to be employed by Sender for DEDICATED Threading Mode.\n */\n#define AERON_SENDER_IDLE_STRATEGY_ENV_VAR \"AERON_SENDER_IDLE_STRATEGY\"\n\nint aeron_driver_context_set_sender_idle_strategy(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_sender_idle_strategy(aeron_driver_context_t *context);\n\n/**\n * Idle strategy to be employed by Conductor for DEDICATED or SHARED_NETWORK Threading Mode.\n */\n#define AERON_CONDUCTOR_IDLE_STRATEGY_ENV_VAR \"AERON_CONDUCTOR_IDLE_STRATEGY\"\n\nint aeron_driver_context_set_conductor_idle_strategy(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_conductor_idle_strategy(aeron_driver_context_t *context);\n\n/**\n * Idle strategy to be employed by Receiver for DEDICATED Threading Mode.\n */\n#define AERON_RECEIVER_IDLE_STRATEGY_ENV_VAR \"AERON_RECEIVER_IDLE_STRATEGY\"\n\nint aeron_driver_context_set_receiver_idle_strategy(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_receiver_idle_strategy(aeron_driver_context_t *context);\n\n/**\n * Idle strategy to be employed by Sender and Receiver for SHARED_NETWORK Threading Mode.\n */\n#define AERON_SHAREDNETWORK_IDLE_STRATEGY_ENV_VAR \"AERON_SHAREDNETWORK_IDLE_STRATEGY\"\n\nint aeron_driver_context_set_sharednetwork_idle_strategy(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_sharednetwork_idle_strategy(aeron_driver_context_t *context);\n\n/**\n * Idle strategy to be employed by Conductor, Sender, and Receiver for SHARED Threading Mode.\n */\n#define AERON_SHARED_IDLE_STRATEGY_ENV_VAR \"AERON_SHARED_IDLE_STRATEGY\"\n\nint aeron_driver_context_set_shared_idle_strategy(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_shared_idle_strategy(aeron_driver_context_t *context);\n\n/**\n * Idle strategy init args to be employed by Sender for DEDICATED Threading Mode.\n */\n#define AERON_SENDER_IDLE_STRATEGY_INIT_ARGS_ENV_VAR \"AERON_SENDER_IDLE_STRATEGY_INIT_ARGS\"\n\nint aeron_driver_context_set_sender_idle_strategy_init_args(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_sender_idle_strategy_init_args(aeron_driver_context_t *context);\n\n/**\n * Idle strategy init args to be employed by Conductor for DEDICATED or SHARED_NETWORK Threading Mode.\n */\n#define AERON_CONDUCTOR_IDLE_STRATEGY_INIT_ARGS_ENV_VAR \"AERON_CONDUCTOR_IDLE_STRATEGY_INIT_ARGS\"\n\nint aeron_driver_context_set_conductor_idle_strategy_init_args(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_conductor_idle_strategy_init_args(aeron_driver_context_t *context);\n\n/**\n * Idle strategy init args to be employed by Receiver for DEDICATED Threading Mode.\n */\n#define AERON_RECEIVER_IDLE_STRATEGY_INIT_ARGS_ENV_VAR \"AERON_RECEIVER_IDLE_STRATEGY_INIT_ARGS\"\n\nint aeron_driver_context_set_receiver_idle_strategy_init_args(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_receiver_idle_strategy_init_args(aeron_driver_context_t *context);\n\n/**\n * Idle strategy init args to be employed by Sender and Receiver for SHARED_NETWORK Threading Mode.\n */\n#define AERON_SHAREDNETWORK_IDLE_STRATEGY_INIT_ARGS_ENV_VAR \"AERON_SHAREDNETWORK_IDLE_STRATEGY_INIT_ARGS\"\n\nint aeron_driver_context_set_sharednetwork_idle_strategy_init_args(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_sharednetwork_idle_strategy_init_args(aeron_driver_context_t *context);\n\n/**\n * Idle strategy init args to be employed by Conductor, Sender, and Receiver for SHARED Threading Mode.\n */\n#define AERON_SHARED_IDLE_STRATEGY_ENV_INIT_ARGS_VAR \"AERON_SHARED_IDLE_STRATEGY_INIT_ARGS\"\n\nint aeron_driver_context_set_shared_idle_strategy_init_args(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_shared_idle_strategy_init_args(aeron_driver_context_t *context);\n\n/**\n * Function name to call on start of each agent.\n */\n#define AERON_AGENT_ON_START_FUNCTION_ENV_VAR \"AERON_AGENT_ON_START_FUNCTION\"\n\ntypedef void (*aeron_agent_on_start_func_t)(void *state, const char *role_name);\n\nint aeron_driver_context_set_agent_on_start_function(\n    aeron_driver_context_t *context, aeron_agent_on_start_func_t value, void *state);\naeron_agent_on_start_func_t aeron_driver_context_get_agent_on_start_function(aeron_driver_context_t *context);\nvoid *aeron_driver_context_get_agent_on_start_state(aeron_driver_context_t *context);\n\n/**\n * Timeout for freed counters before they can be reused.\n */\n#define AERON_COUNTERS_FREE_TO_REUSE_TIMEOUT_ENV_VAR \"AERON_COUNTERS_FREE_TO_REUSE_TIMEOUT\"\n\nint aeron_driver_context_set_counters_free_to_reuse_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_counters_free_to_reuse_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * Timeout for a receiver to be tracked.\n */\n#define AERON_FLOW_CONTROL_RECEIVER_TIMEOUT_ENV_VAR \"AERON_FLOW_CONTROL_RECEIVER_TIMEOUT\"\n\nint aeron_driver_context_set_flow_control_receiver_timeout_ns(aeron_driver_context_t *context, uint64_t value);\n\nuint64_t aeron_driver_context_get_flow_control_receiver_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * Default receiver tag for publishers to group endpoints by using tagged flow control.\n */\n#define AERON_FLOW_CONTROL_GROUP_TAG_ENV_VAR \"AERON_FLOW_CONTROL_GROUP_TAG\"\n\nint aeron_driver_context_set_flow_control_group_tag(aeron_driver_context_t *context, int64_t value);\nint64_t aeron_driver_context_get_flow_control_group_tag(aeron_driver_context_t *context);\n\n/**\n * Default required group size to use in tagged multicast flow control.\n */\n#define AERON_FLOW_CONTROL_GROUP_MIN_SIZE_ENV_VAR \"AERON_FLOW_CONTROL_GROUP_MIN_SIZE\"\n\nint aeron_driver_context_set_flow_control_group_min_size(aeron_driver_context_t *context, int32_t value);\nint32_t aeron_driver_context_get_flow_control_group_min_size(aeron_driver_context_t *context);\n\n/**\n * Default receiver tag to be sent on status messages from channel to handle tagged flow control.\n */\n#define AERON_RECEIVER_GROUP_TAG_ENV_VAR \"AERON_RECEIVER_GROUP_TAG\"\n\nint aeron_driver_context_set_receiver_group_tag(aeron_driver_context_t *context, bool is_present, int64_t value);\nbool aeron_driver_context_get_receiver_group_tag_is_present(aeron_driver_context_t *context);\nint64_t aeron_driver_context_get_receiver_group_tag_value(aeron_driver_context_t *context);\n\n/**\n * Function name to call for termination validation.\n */\n#define AERON_DRIVER_TERMINATION_VALIDATOR_ENV_VAR \"AERON_DRIVER_TERMINATION_VALIDATOR\"\n\ntypedef bool (*aeron_driver_termination_validator_func_t)(void *state, uint8_t *buffer, int32_t length);\n\nint aeron_driver_context_set_driver_termination_validator(\n    aeron_driver_context_t *context, aeron_driver_termination_validator_func_t value, void *state);\naeron_driver_termination_validator_func_t aeron_driver_context_get_driver_termination_validator(\n    aeron_driver_context_t *context);\nvoid *aeron_driver_context_get_driver_termination_validator_state(aeron_driver_context_t *context);\n\ntypedef void (*aeron_driver_termination_hook_func_t)(void *clientd);\n\nint aeron_driver_context_set_driver_termination_hook(\n    aeron_driver_context_t *context, aeron_driver_termination_hook_func_t value, void *state);\naeron_driver_termination_hook_func_t aeron_driver_context_get_driver_termination_hook(aeron_driver_context_t *context);\nvoid *aeron_driver_context_get_driver_termination_hook_state(aeron_driver_context_t *context);\n\n/**\n * Should the driver print its configuration on start to stdout.\n */\n#define AERON_PRINT_CONFIGURATION_ON_START_ENV_VAR \"AERON_PRINT_CONFIGURATION\"\n\nint aeron_driver_context_set_print_configuration(aeron_driver_context_t *context, bool value);\nbool aeron_driver_context_get_print_configuration(aeron_driver_context_t *context);\n\n/**\n * Property name for default boolean value for if a stream is reliable. True to NAK, false to gap fill.\n */\n#define AERON_RELIABLE_STREAM_ENV_VAR \"AERON_RELIABLE_STREAM\"\n\nint aeron_driver_context_set_reliable_stream(aeron_driver_context_t *context, bool value);\nbool aeron_driver_context_get_reliable_stream(aeron_driver_context_t *context);\n\n/**\n * Property name for default boolean value for if subscriptions should have a tether for flow control.\n */\n#define AERON_TETHER_SUBSCRIPTIONS_ENV_VAR \"AERON_TETHER_SUBSCRIPTIONS\"\n\nint aeron_driver_context_set_tether_subscriptions(aeron_driver_context_t *context, bool value);\nbool aeron_driver_context_get_tether_subscriptions(aeron_driver_context_t *context);\n\n/**\n * Untethered subscriptions window limit timeout after which they are removed from flow control.\n */\n#define AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT_ENV_VAR \"AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT\"\n\n\nint aeron_driver_context_set_untethered_window_limit_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_untethered_window_limit_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * Timeout for an untethered subscriptions to stay in the linger state.\n */\n#define AERON_UNTETHERED_LINGER_TIMEOUT_ENV_VAR \"AERON_UNTETHERED_LINGER_TIMEOUT\"\n\n\nint aeron_driver_context_set_untethered_linger_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nint64_t aeron_driver_context_get_untethered_linger_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * Untethered subscriptions resting timeout before they are allowed to re join an active stream.\n */\n#define AERON_UNTETHERED_RESTING_TIMEOUT_ENV_VAR \"AERON_UNTETHERED_RESTING_TIMEOUT\"\n\nint aeron_driver_context_set_untethered_resting_timeout_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_untethered_resting_timeout_ns(aeron_driver_context_t *context);\n\n/**\n * Timeout in which the driver is expected to respond or heartbeat.\n */\n#define AERON_DRIVER_TIMEOUT_ENV_VAR \"AERON_DRIVER_TIMEOUT\"\n\nint aeron_driver_context_set_driver_timeout_ms(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_driver_timeout_ms(aeron_driver_context_t *context);\n\n/**\n * Expected size of multicast receiver groups property name.\n */\n#define AERON_NAK_MULTICAST_GROUP_SIZE_ENV_VAR \"AERON_NAK_MULTICAST_GROUP_SIZE\"\n\nint aeron_driver_context_set_nak_multicast_group_size(aeron_driver_context_t *context, size_t value);\nsize_t aeron_driver_context_get_nak_multicast_group_size(aeron_driver_context_t *context);\n\n/**\n * Max backoff time for multicast NAK delay randomisation in nanoseconds.\n */\n#define AERON_NAK_MULTICAST_MAX_BACKOFF_ENV_VAR \"AERON_NAK_MULTICAST_MAX_BACKOFF\"\n\nint aeron_driver_context_set_nak_multicast_max_backoff_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_nak_multicast_max_backoff_ns(aeron_driver_context_t *context);\n\n/**\n * How long to delay before sending an initial NAK.\n */\n#define AERON_NAK_UNICAST_DELAY_ENV_VAR \"AERON_NAK_UNICAST_DELAY\"\n\nint aeron_driver_context_set_nak_unicast_delay_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_nak_unicast_delay_ns(aeron_driver_context_t *context);\n\n/**\n * A ratio to apply to the nak unicast delay to calculate the resend delay. Used as a multipler.\n */\n#define AERON_NAK_UNICAST_RETRY_DELAY_RATIO_ENV_VAR \"AERON_NAK_UNICAST_RETRY_DELAY_RATIO\"\n\nint aeron_driver_context_set_nak_unicast_retry_delay_ratio(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_nak_unicast_retry_delay_ratio(aeron_driver_context_t *context);\n\n/**\n * Max number of active retransmissions tracked for udp streams with group semantics.\n */\n#define AERON_MAX_RESEND_ENV_VAR \"AERON_MAX_RESEND\"\n\nint aeron_driver_context_set_max_resend(aeron_driver_context_t *context, uint32_t value);\nuint32_t aeron_driver_context_get_max_resend(aeron_driver_context_t *context);\n\n/**\n * How long to delay before sending a retransmit following a NAK.\n */\n#define AERON_RETRANSMIT_UNICAST_DELAY_ENV_VAR \"AERON_RETRANSMIT_UNICAST_DELAY\"\n\nint aeron_driver_context_set_retransmit_unicast_delay_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_retransmit_unicast_delay_ns(aeron_driver_context_t *context);\n\n/**\n * How long to linger after delay on a NAK.\n */\n#define AERON_RETRANSMIT_UNICAST_LINGER_ENV_VAR \"AERON_RETRANSMIT_UNICAST_LINGER\"\n\nint aeron_driver_context_set_retransmit_unicast_linger_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_retransmit_unicast_linger_ns(aeron_driver_context_t *context);\n\n/**\n * Group semantics for network subscriptions.\n */\n#define AERON_RECEIVER_GROUP_CONSIDERATION_ENV_VAR \"AERON_RECEIVER_GROUP_CONSIDERATION\"\n\ntypedef enum aeron_inferable_boolean_enum\n{\n    AERON_FORCE_FALSE,\n    AERON_FORCE_TRUE,\n    AERON_INFER\n}\naeron_inferable_boolean_t;\n\nint aeron_driver_context_set_receiver_group_consideration(\n    aeron_driver_context_t *context, aeron_inferable_boolean_t value);\naeron_inferable_boolean_t aeron_driver_context_get_receiver_group_consideration(aeron_driver_context_t *context);\n\n/**\n * Property name for default boolean value for if a stream is rejoinable. True to allow rejoin, false to not.\n * */\n#define AERON_REJOIN_STREAM_ENV_VAR \"AERON_REJOIN_STREAM\"\n\nint aeron_driver_context_set_rejoin_stream(aeron_driver_context_t *context, bool value);\nbool aeron_driver_context_get_rejoin_stream(aeron_driver_context_t *context);\n\n#define AERON_DRIVER_CONNECT_ENV_VAR \"AERON_DRIVER_CONNECT\"\n\nint aeron_driver_context_set_connect_enabled(aeron_driver_context_t *context, bool value);\nint aeron_driver_context_get_connect_enabled(aeron_driver_context_t *context);\n\n/**\n * Bindings for UDP Channel Transports.\n */\n#define AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA_ENV_VAR \"AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA\"\n\n/**\n * Bindings for Conductor UDP Channel Transports.\n */\n#define AERON_CONDUCTOR_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA_ENV_VAR \"AERON_CONDUCTOR_UDP_CHANNEL_TRANSPORT_BINDINGS_MEDIA\"\n\ntypedef struct aeron_udp_channel_transport_bindings_stct aeron_udp_channel_transport_bindings_t;\n\nint aeron_driver_context_set_udp_channel_transport_bindings(\n    aeron_driver_context_t *context, aeron_udp_channel_transport_bindings_t *value);\naeron_udp_channel_transport_bindings_t *aeron_driver_context_get_udp_channel_transport_bindings(\n    aeron_driver_context_t *context);\n\n#define AERON_UDP_CHANNEL_OUTGOING_INTERCEPTORS_ENV_VAR \"AERON_UDP_CHANNEL_OUTGOING_INTERCEPTORS\"\n#define AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR \"AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS\"\n\ntypedef struct aeron_udp_channel_interceptor_bindings_stct aeron_udp_channel_interceptor_bindings_t;\n\nint aeron_driver_context_set_udp_channel_outgoing_interceptors(\n    aeron_driver_context_t *context, aeron_udp_channel_interceptor_bindings_t *value);\naeron_udp_channel_interceptor_bindings_t *aeron_driver_context_get_udp_channel_outgoing_interceptors(\n    aeron_driver_context_t *context);\n\nint aeron_driver_context_set_udp_channel_incoming_interceptors(\n    aeron_driver_context_t *context, aeron_udp_channel_interceptor_bindings_t *value);\naeron_udp_channel_interceptor_bindings_t *aeron_driver_context_get_udp_channel_incoming_interceptors(\n    aeron_driver_context_t *context);\n\n#define AERON_PUBLICATION_RESERVED_SESSION_ID_LOW_ENV_VAR \"AERON_PUBLICATION_RESERVED_SESSION_ID_LOW\"\n\nint aeron_driver_context_set_publication_reserved_session_id_low(aeron_driver_context_t *context, int32_t value);\nint32_t aeron_driver_context_get_publication_reserved_session_id_low(aeron_driver_context_t *context);\n\n#define AERON_PUBLICATION_RESERVED_SESSION_ID_HIGH_ENV_VAR \"AERON_PUBLICATION_RESERVED_SESSION_ID_HIGH\"\n\nint aeron_driver_context_set_publication_reserved_session_id_high(aeron_driver_context_t *context, int32_t value);\nint32_t aeron_driver_context_get_publication_reserved_session_id_high(aeron_driver_context_t *context);\n\nstruct aeron_name_resolver_stct;\ntypedef struct aeron_name_resolver_stct aeron_name_resolver_t;\ntypedef int (*aeron_name_resolver_supplier_func_t)(\n    aeron_name_resolver_t *resolver,\n    const char *args,\n    aeron_driver_context_t *context);\n\n/**\n * Set the name of the MediaDriver for name resolver purposes.\n */\n#define AERON_DRIVER_RESOLVER_NAME_ENV_VAR \"AERON_DRIVER_RESOLVER_NAME\"\n\nint aeron_driver_context_set_resolver_name(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_resolver_name(aeron_driver_context_t *context);\n\n/**\n* The interface of the MediaDriver for name resolver purposes.\n*\n* The format is hostname:port.\n*/\n#define AERON_DRIVER_RESOLVER_INTERFACE_ENV_VAR \"AERON_DRIVER_RESOLVER_INTERFACE\"\n\nint aeron_driver_context_set_resolver_interface(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_resolver_interface(aeron_driver_context_t *context);\n\n/**\n * Get the bootstrap neighbor of the {@link MediaDriver} for name resolver purposes.\n *\n * The format is comma separated list of hostname:port pairs and follows the URI format for the endpoint parameter.\n */\n#define AERON_DRIVER_RESOLVER_BOOTSTRAP_NEIGHBOR_ENV_VAR \"AERON_DRIVER_RESOLVER_BOOTSTRAP_NEIGHBOR\"\n\nint aeron_driver_context_set_resolver_bootstrap_neighbor(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_resolver_bootstrap_neighbor(aeron_driver_context_t *context);\n\n/**\n* Specify the name of the name resolver (supplier) to be used by this media driver\n*/\n#define AERON_NAME_RESOLVER_SUPPLIER_ENV_VAR \"AERON_NAME_RESOLVER_SUPPLIER\"\n#define AERON_NAME_RESOLVER_SUPPLIER_DEFAULT \"default\"\n\nint aeron_driver_context_set_name_resolver_supplier(\n    aeron_driver_context_t *context, aeron_name_resolver_supplier_func_t value);\naeron_name_resolver_supplier_func_t aeron_driver_context_get_name_resolver_supplier(aeron_driver_context_t *context);\n\n/**\n * Specify the name of the name resolver (supplier) to be used by this media driver\n */\n#define AERON_NAME_RESOLVER_INIT_ARGS_ENV_VAR \"AERON_NAME_RESOLVER_INIT_ARGS\"\n\nint aeron_driver_context_set_name_resolver_init_args(aeron_driver_context_t *context, const char *value);\nconst char *aeron_driver_context_get_name_resolver_init_args(aeron_driver_context_t *context);\n\n/**\n * Specify the interval which checks for re-resolutions of names occurs.\n */\n#define AERON_DRIVER_RERESOLUTION_CHECK_INTERVAL_ENV_VAR \"AERON_DRIVER_RERESOLUTION_CHECK_INTERVAL\"\n\nint aeron_driver_context_set_re_resolution_check_interval_ns(aeron_driver_context_t *context, uint64_t value);\nuint64_t aeron_driver_context_get_re_resolution_check_interval_ns(aeron_driver_context_t *context);\n\ntypedef struct aeron_duty_cycle_tracker_stct aeron_duty_cycle_tracker_t;\n\nint aeron_driver_context_set_conductor_duty_cycle_tracker(\n    aeron_driver_context_t *context, aeron_duty_cycle_tracker_t *value);\naeron_duty_cycle_tracker_t *aeron_driver_context_get_conductor_duty_cycle_tracker(aeron_driver_context_t *context);\n\nint aeron_driver_context_set_sender_duty_cycle_tracker(\n    aeron_driver_context_t *context, aeron_duty_cycle_tracker_t *value);\naeron_duty_cycle_tracker_t *aeron_driver_context_get_sender_duty_cycle_tracker(aeron_driver_context_t *context);\n\nint aeron_driver_context_set_receiver_duty_cycle_tracker(\n    aeron_driver_context_t *context, aeron_duty_cycle_tracker_t *value);\naeron_duty_cycle_tracker_t *aeron_driver_context_get_receiver_duty_cycle_tracker(aeron_driver_context_t *context);\n\nint aeron_driver_context_set_name_resolver_time_tracker(\n    aeron_driver_context_t *context, aeron_duty_cycle_tracker_t *value);\naeron_duty_cycle_tracker_t *aeron_driver_context_get_name_resolver_time_tracker(aeron_driver_context_t *context);\n\n/**\n * Specify the sender wildcard port range.\n */\n#define AERON_DRIVER_SENDER_WILDCARD_PORT_RANGE_ENV_VAR \"AERON_SENDER_WILDCARD_PORT_RANGE\"\n\nint aeron_driver_context_set_sender_wildcard_port_range(\n    aeron_driver_context_t *context, uint16_t low_port, uint16_t high_port);\nint aeron_driver_context_get_sender_wildcard_port_range(\n    aeron_driver_context_t *context, uint16_t *low_port, uint16_t *high_port);\n\n/**\n * Specify the receiver wildcard port range.\n */\n#define AERON_DRIVER_RECEIVER_WILDCARD_PORT_RANGE_ENV_VAR \"AERON_RECEIVER_WILDCARD_PORT_RANGE\"\n\nint aeron_driver_context_set_receiver_wildcard_port_range(\n    aeron_driver_context_t *context, uint16_t low_port, uint16_t high_port);\nint aeron_driver_context_get_receiver_wildcard_port_range(\n    aeron_driver_context_t *context, uint16_t *low_port, uint16_t *high_port);\n\ntypedef struct aeron_port_manager_stct aeron_port_manager_t;\n\nint aeron_driver_context_set_sender_port_manager(\n    aeron_driver_context_t *context, aeron_port_manager_t *value);\naeron_port_manager_t *aeron_driver_context_get_sender_port_manager(aeron_driver_context_t *context);\n\nint aeron_driver_context_set_receiver_port_manager(\n    aeron_driver_context_t *context, aeron_port_manager_t *value);\naeron_port_manager_t *aeron_driver_context_get_receiver_port_manager(aeron_driver_context_t *context);\n\n/**\n * Specify the duty cycle time threshold for the conductor.\n */\n#define AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD_ENV_VAR \"AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD\"\n\nint64_t aeron_driver_context_set_conductor_cycle_threshold_ns(aeron_driver_context_t *context, uint64_t value);\nint64_t aeron_driver_context_get_conductor_cycle_threshold_ns(aeron_driver_context_t *context);\n\n/**\n * Specify the duty cycle time threshold for the sender.\n */\n#define AERON_DRIVER_SENDER_CYCLE_THRESHOLD_ENV_VAR \"AERON_DRIVER_SENDER_CYCLE_THRESHOLD\"\n\nint64_t aeron_driver_context_set_sender_cycle_threshold_ns(aeron_driver_context_t *context, uint64_t value);\nint64_t aeron_driver_context_get_sender_cycle_threshold_ns(aeron_driver_context_t *context);\n\n/**\n * Specify the duty cycle time threshold for the receiver.\n */\n#define AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD_ENV_VAR \"AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD\"\n\nint64_t aeron_driver_context_set_receiver_cycle_threshold_ns(aeron_driver_context_t *context, uint64_t value);\nint64_t aeron_driver_context_get_receiver_cycle_threshold_ns(aeron_driver_context_t *context);\n\n/**\n * Specify the duty cycle time threshold for the name_resolver.\n */\n#define AERON_DRIVER_NAME_RESOLVER_THRESHOLD_ENV_VAR \"AERON_DRIVER_NAME_RESOLVER_THRESHOLD\"\n\nint64_t aeron_driver_context_set_name_resolver_threshold_ns(aeron_driver_context_t *context, uint64_t value);\nint64_t aeron_driver_context_get_name_resolver_threshold_ns(aeron_driver_context_t *context);\n\n#define AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR \"AERON_RECEIVER_IO_VECTOR_CAPACITY\"\nint aeron_driver_context_set_receiver_io_vector_capacity(aeron_driver_context_t *context, uint32_t value);\nuint32_t aeron_driver_context_get_receiver_io_vector_capacity(aeron_driver_context_t *context);\n\n#define AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR \"AERON_SENDER_IO_VECTOR_CAPACITY\"\nint aeron_driver_context_set_sender_io_vector_capacity(aeron_driver_context_t *context, uint32_t value);\nuint32_t aeron_driver_context_get_sender_io_vector_capacity(aeron_driver_context_t *context);\n\n#define AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_ENV_VAR \"AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND\"\nint aeron_driver_context_set_network_publication_max_messages_per_send(aeron_driver_context_t *context, uint32_t value);\nuint32_t aeron_driver_context_get_network_publication_max_messages_per_send(aeron_driver_context_t *context);\n\n#define AERON_DRIVER_RESOURCE_FREE_LIMIT_ENV_VAR \"AERON_DRIVER_RESOURCE_FREE_LIMIT\"\nint aeron_driver_context_set_resource_free_limit(aeron_driver_context_t *context, uint32_t value);\nuint32_t aeron_driver_context_get_resource_free_limit(aeron_driver_context_t *context);\n\n#define AERON_DRIVER_ASYNC_EXECUTOR_THREADS_ENV_VAR \"AERON_DRIVER_ASYNC_EXECUTOR_THREADS\"\nint aeron_driver_context_set_async_executor_threads(aeron_driver_context_t *context, uint32_t value);\nuint32_t aeron_driver_context_get_async_executor_threads(aeron_driver_context_t *context);\n\n#define AERON_CONDUCTOR_CPU_AFFINITY_ENV_VAR \"AERON_CONDUCTOR_CPU_AFFINITY\"\nint aeron_driver_context_set_conductor_cpu_affinity(aeron_driver_context_t *context, int32_t value);\nint32_t aeron_driver_context_get_conductor_cpu_affinity(aeron_driver_context_t *context);\n\n#define AERON_RECEIVER_CPU_AFFINITY_ENV_VAR \"AERON_RECEIVER_CPU_AFFINITY\"\nint aeron_driver_context_set_receiver_cpu_affinity(aeron_driver_context_t *context, int32_t value);\nint32_t aeron_driver_context_get_receiver_cpu_affinity(aeron_driver_context_t *context);\n\n#define AERON_SENDER_CPU_AFFINITY_ENV_VAR \"AERON_SENDER_CPU_AFFINITY\"\nint aeron_driver_context_set_sender_cpu_affinity(aeron_driver_context_t *context, int32_t value);\nint32_t aeron_driver_context_get_sender_cpu_affinity(aeron_driver_context_t *context);\n\n/**\n * Set the list of filenames to dynamic libraries to load upon context init.\n */\n#define AERON_DRIVER_DYNAMIC_LIBRARIES_ENV_VAR \"AERON_DRIVER_DYNAMIC_LIBRARIES\"\n\n#define AERON_ENABLE_EXPERIMENTAL_FEATURES_ENV_VAR \"AERON_ENABLE_EXPERIMENTAL_FEATURES\"\nint aeron_driver_context_set_enable_experimental_features(aeron_driver_context_t *context, bool value);\nint aeron_driver_context_get_enable_experimental_features(aeron_driver_context_t *context);\n\n/**\n * Limit the number of sessions for a given stream that the driver will support\n */\n#define AERON_DRIVER_STREAM_SESSION_LIMIT_ENV_VAR \"AERON_DRIVER_STREAM_SESSION_LIMIT\"\n\nint aeron_driver_context_set_stream_session_limit(aeron_driver_context_t *context, int32_t value);\nint32_t aeron_driver_context_get_stream_session_limit(aeron_driver_context_t *context);\n\n\n/**\n * Return full version and build string.\n *\n * @return full version and build string.\n */\nconst char *aeron_version_full(void);\n\n/**\n * Return major version number.\n *\n * @return major version number.\n */\nint aeron_version_major(void);\n\n/**\n * Return minor version number.\n *\n * @return minor version number.\n */\nint aeron_version_minor(void);\n\n/**\n * Return patch version number.\n *\n * @return patch version number.\n */\nint aeron_version_patch(void);\n\n/**\n * Create a aeron_driver_context_t struct and initialize with default values.\n *\n * @param context to create and initialize\n * @return 0 for success and -1 for error.\n */\nint aeron_driver_context_init(aeron_driver_context_t **context);\n\n/**\n * Close and delete aeron_driver_context_t struct.\n *\n * @param context to close and delete\n * @return 0 for success and -1 for error.\n */\nint aeron_driver_context_close(aeron_driver_context_t *context);\n\n/**\n * Create a aeron_driver_t struct and initialize from the aeron_driver_context_t struct.\n *\n * The given aeron_driver_context_t struct will be used exclusively by the driver. Do not reuse between drivers.\n *\n * @param driver  to create and initialize.\n * @param context to use for initialization.\n * @return 0 for success and -1 for error.\n */\nint aeron_driver_init(aeron_driver_t **driver, aeron_driver_context_t *context);\n\n/**\n * Start an aeron_driver_t given the threading mode. This may spawn threads for the Sender, Receiver, and Conductor\n * depending on threading mode used.\n *\n * @param driver to start.\n * @param manual_main_loop to be called by the caller for the Conductor do_work cycle.\n * @return 0 for success and -1 for error.\n */\nint aeron_driver_start(aeron_driver_t *driver, bool manual_main_loop);\n\n/**\n * Call the Conductor (or Shared) main do_work duty cycle once.\n *\n * Driver must have been created with manual_main_loop set to true.\n *\n * @param driver to call do_work duty cycle on.\n * @return 0 for success and -1 for error.\n */\nint aeron_driver_main_do_work(aeron_driver_t *driver);\n\n/**\n * Call the Conductor (or Shared) Idle Strategy.\n *\n * @param driver to idle.\n * @param work_count to pass to idle strategy.\n */\nvoid aeron_driver_main_idle_strategy(aeron_driver_t *driver, int work_count);\n\n/**\n * Close and delete aeron_driver_t struct.\n *\n * @param driver to close and delete\n * @return 0 for success and -1 for error.\n */\nint aeron_driver_close(aeron_driver_t *driver);\n\n/**\n * Delete the given aeron directory.\n *\n * @param dirname to delete.\n * @return 0 for success and -1 for error.\n */\nint aeron_delete_directory(const char *dirname);\n\n/**\n * Clock function used by aeron.\n */\ntypedef int64_t (*aeron_clock_func_t)(void);\n\n/**\n * Return time in nanoseconds for machine. Is not wall clock time.\n *\n * @return nanoseconds since epoch for machine.\n */\nint64_t aeron_nano_clock(void);\n\n/**\n * Return time in milliseconds since epoch. Is wall clock time.\n *\n * @return milliseconds since epoch.\n */\nint64_t aeron_epoch_clock(void);\n\n/**\n * Function to return logging information.\n */\ntypedef void (*aeron_log_func_t)(const char *);\n\n/**\n * Determine if an aeron driver is using a given aeron directory.\n *\n * @param dirname  for aeron directory\n * @param timeout_ms  to use to determine activity for aeron directory\n * @param log_func to call during activity check to log diagnostic information.\n * @return true for active driver or false for no active driver.\n */\nbool aeron_is_driver_active(const char *dirname, int64_t timeout_ms, aeron_log_func_t log_func);\n\n/**\n * Load properties from a string containing name=value pairs and set appropriate environment variables for the\n * process so that subsequent calls to aeron_driver_context_init will use those values.\n *\n * @param buffer containing properties and values.\n * @return 0 for success and -1 for error.\n */\nint aeron_properties_buffer_load(const char *buffer);\n\n/**\n * Load properties file and set appropriate environment variables for the process so that subsequent\n * calls to aeron_driver_context_init will use those values.\n *\n * @param filename to load.\n * @return 0 for success and -1 for error.\n */\nint aeron_properties_file_load(const char *filename);\n\n/**\n * Load properties from HTTP URL and set environment variables for the process so that subsequent\n * calls to aeron_driver_context_init will use those values.\n *\n * @param url to attempt to retrieve and load.\n * @return 0 for success and -1 for error.\n */\nint aeron_properties_http_load(const char *url);\n\n/**\n * Load properties based on URL or filename. If string contains file or http URL, it will attempt\n * to load properties from a file or http as indicated. If not a URL, then it will try to load the string\n * as a filename.\n *\n * @param url_or_filename to load properties from.\n * @return 0 for success and -1 for error.\n */\nint aeron_properties_load(const char *url_or_filename);\n\n/**\n * Return current aeron error code (errno) for calling thread.\n *\n * @return aeron error code for calling thread.\n */\nint aeron_errcode(void);\n\n/**\n * Return the current aeron error message for calling thread.\n *\n * @return aeron error message for calling thread.\n */\nconst char *aeron_errmsg(void);\n\n/**\n * Get the default path used by the Aeron media driver.\n *\n * @param path buffer to store the path.\n * @param path_length space available in the buffer\n * @return -1 if there is an issue or the number of bytes written to path excluding the terminator `\\0`. If this\n * is equal to or greater than the path_length then the path has been truncated.\n */\nint aeron_default_path(char *path, size_t path_length);\n\n/**\n * Affinity setting function that complies with the aeron_agent_on_start_func_t structure that can\n * be used as an agent start function.  The state should be the aeron_driver_context_t* and the function\n * will match the values \"conductor\", \"sender\", \"receiver\" and use the respective configuration options from\n * the aeron_driver_context_t.\n *\n * @param state client information passed to function, should be the aeron_driver_context_t*.\n * @param role_name name of the role specified on the agent.\n */\nvoid aeron_set_thread_affinity_on_start(void *state, const char *role_name);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif //AERON_AERONMD_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/agent/aeron_driver_agent.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#if !defined(_MSC_VER)\n#include <pthread.h>\n#include <arpa/inet.h>\n#include <sys/socket.h>\n#endif\n\n#include <stdio.h>\n#include <time.h>\n#include <inttypes.h>\n\n#include \"agent/aeron_driver_agent.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_driver_version.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_windows.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\n#define AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN (0)\n#define AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN (1)\n#define AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT (2)\n#define AERON_DRIVER_AGENT_EVENT_TYPE_OTHER (3)\n\n#define AERON_DRIVER_AGENT_MAX_EVENT_NAME_LENGTH (64)\n\ntypedef struct aeron_driver_agent_dynamic_dissector_entry_stct\n{\n    aeron_driver_agent_generic_dissector_func_t dissector_func;\n}\naeron_driver_agent_dynamic_dissector_entry_t;\n\ntypedef struct aeron_driver_agent_log_event_stct\n{\n    char name[AERON_DRIVER_AGENT_MAX_EVENT_NAME_LENGTH];\n    uint8_t type;\n    bool enabled;\n}\naeron_driver_agent_log_event_t;\n\nstatic AERON_INIT_ONCE agent_is_initialized = AERON_INIT_ONCE_VALUE;\nstatic aeron_mpsc_rb_t logging_mpsc_rb;\nstatic uint8_t *rb_buffer = NULL;\nstatic FILE *logfp = NULL;\nstatic aeron_thread_t log_reader_thread;\nstatic aeron_driver_agent_dynamic_dissector_entry_t *dynamic_dissector_entries = NULL;\nstatic size_t num_dynamic_dissector_entries = 0;\nstatic int64_t dynamic_dissector_index = 0;\nstatic aeron_driver_agent_log_event_t log_events[] =\n    {\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { \"FRAME_IN\",                             AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"FRAME_OUT\",                            AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"CMD_IN_ADD_PUBLICATION\",               AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_IN_REMOVE_PUBLICATION\",            AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_IN_ADD_SUBSCRIPTION\",              AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_IN_REMOVE_SUBSCRIPTION\",           AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_OUT_PUBLICATION_READY\",            AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { \"CMD_OUT_AVAILABLE_IMAGE\",              AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { \"CMD_OUT_ON_OPERATION_SUCCESS\",         AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { \"CMD_IN_KEEPALIVE_CLIENT\",              AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"REMOVE_PUBLICATION_CLEANUP\",           AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"REMOVE_SUBSCRIPTION_CLEANUP\",          AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"REMOVE_IMAGE_CLEANUP\",                 AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"CMD_OUT_ON_UNAVAILABLE_IMAGE\",         AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { \"SEND_CHANNEL_CREATION\",                AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"RECEIVE_CHANNEL_CREATION\",             AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"SEND_CHANNEL_CLOSE\",                   AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"RECEIVE_CHANNEL_CLOSE\",                AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,  AERON_DRIVER_AGENT_EVENT_TYPE_UNKNOWN, false },\n        { \"CMD_IN_ADD_DESTINATION\",               AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_IN_REMOVE_DESTINATION\",            AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_IN_ADD_EXCLUSIVE_PUBLICATION\",     AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_OUT_EXCLUSIVE_PUBLICATION_READY\",  AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { \"CMD_OUT_ERROR\",                        AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { \"CMD_IN_ADD_COUNTER\",                   AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_IN_REMOVE_COUNTER\",                AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_OUT_SUBSCRIPTION_READY\",           AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { \"CMD_OUT_COUNTER_READY\",                AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { \"CMD_OUT_ON_UNAVAILABLE_COUNTER\",       AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { \"CMD_IN_CLIENT_CLOSE\",                  AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_IN_ADD_RCV_DESTINATION\",           AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_IN_REMOVE_RCV_DESTINATION\",        AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_OUT_ON_CLIENT_TIMEOUT\",            AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, false },\n        { \"CMD_IN_TERMINATE_DRIVER\",              AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"UNTETHERED_SUBSCRIPTION_STATE_CHANGE\", AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"NAME_RESOLUTION_NEIGHBOR_ADDED\",       AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"NAME_RESOLUTION_NEIGHBOR_REMOVED\",     AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"FLOW_CONTROL_RECEIVER_ADDED\",          AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"FLOW_CONTROL_RECEIVER_REMOVED\",        AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"NAME_RESOLUTION_RESOLVE\",              AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"GENERIC_MESSAGE\",                      AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"NAME_RESOLUTION_LOOKUP\",               AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"NAME_RESOLUTION_HOST_NAME\",            AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"NAK_SENT\",                             AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"RESEND\",                               AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"CMD_IN_REMOVE_DESTINATION_BY_ID\",      AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"CMD_IN_REJECT_IMAGE\",                  AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,  false },\n        { \"NAK_RECEIVED\",                         AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"PUBLICATION_REVOKE\",                   AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN,   false },\n        { \"PUBLICATION_IMAGE_REVOKE\",             AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"ADD_DYNAMIC_DISSECTOR\",                AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n        { \"DYNAMIC_DISSECTOR_EVENT\",              AERON_DRIVER_AGENT_EVENT_TYPE_OTHER,   false },\n    };\n\n#define AERON_DRIVER_EVENT_NUM_ELEMENTS (sizeof(log_events) / sizeof(aeron_driver_agent_log_event_t))\n\nsize_t aeron_driver_agent_max_event_count(void)\n{\n    return AERON_DRIVER_EVENT_NUM_ELEMENTS;\n}\n\naeron_mpsc_rb_t *aeron_driver_agent_mpsc_rb(void)\n{\n    return &logging_mpsc_rb;\n}\n\nstatic void *aeron_driver_agent_log_reader(void *arg)\n{\n    while (true)\n    {\n        size_t messages_read = aeron_mpsc_rb_read(&logging_mpsc_rb, aeron_driver_agent_log_dissector, NULL, 10);\n        if (0 == messages_read)\n        {\n            aeron_nano_sleep(1000 * 1000);\n        }\n    }\n\n    return NULL;\n}\n\nvoid aeron_driver_agent_logging_ring_buffer_init(void)\n{\n    size_t rb_length = AERON_EVENT_RB_LENGTH + AERON_RB_TRAILER_LENGTH;\n\n    if ((rb_buffer = (uint8_t *)malloc(rb_length)) == NULL)\n    {\n        fprintf(stderr, \"could not allocate ring buffer buffer. exiting.\\n\");\n        exit(EXIT_FAILURE);\n    }\n\n    memset(rb_buffer, 0, rb_length);\n\n    if (aeron_mpsc_rb_init(&logging_mpsc_rb, rb_buffer, rb_length) < 0)\n    {\n        fprintf(stderr, \"could not init logging mpsc_rb. exiting.\\n\");\n        exit(EXIT_FAILURE);\n    }\n}\n\nvoid aeron_driver_agent_logging_ring_buffer_free(void)\n{\n    if (NULL != rb_buffer)\n    {\n        aeron_free(rb_buffer);\n        rb_buffer = NULL;\n    }\n}\n\nstatic bool aeron_driver_agent_is_unknown_event(const char *event_name)\n{\n    return 0 == strncmp(\n        AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME, event_name, strlen(AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME) + 1);\n}\n\nstatic aeron_driver_agent_event_t aeron_driver_agent_event_name_to_id(const char *event_name)\n{\n    if (aeron_driver_agent_is_unknown_event(event_name))\n    {\n        return AERON_DRIVER_EVENT_UNKNOWN_EVENT;\n    }\n\n    for (size_t i = 0; i < AERON_DRIVER_EVENT_NUM_ELEMENTS; i++)\n    {\n        const char *name = log_events[i].name;\n        if (0 == strncmp(name, event_name, strlen(name) + 1))\n        {\n            return (aeron_driver_agent_event_t)i;\n        }\n    }\n\n    return AERON_DRIVER_EVENT_UNKNOWN_EVENT;\n}\n\nstatic inline bool is_valid_event_id(const int id)\n{\n    return id >= 0 && id < (int)AERON_DRIVER_EVENT_NUM_ELEMENTS;\n}\n\nconst char *aeron_driver_agent_event_name(const aeron_driver_agent_event_t id)\n{\n    if (is_valid_event_id(id))\n    {\n        return log_events[id].name;\n    }\n\n    return AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME;\n}\n\nbool aeron_driver_agent_is_event_enabled(const aeron_driver_agent_event_t id)\n{\n    return is_valid_event_id(id) && log_events[id].enabled;\n}\n\nstatic void aeron_driver_agent_set_enabled_all_events(const bool is_enabled)\n{\n    for (size_t i = 0; i < AERON_DRIVER_EVENT_NUM_ELEMENTS; i++)\n    {\n        const char *event_name = log_events[i].name;\n        if (!aeron_driver_agent_is_unknown_event(event_name))\n        {\n            log_events[i].enabled = is_enabled;\n        }\n    }\n}\n\nstatic void aeron_driver_agent_set_enabled_admin_events(const bool is_enabled)\n{\n    for (size_t i = 0; i < AERON_DRIVER_EVENT_NUM_ELEMENTS; i++)\n    {\n        if (AERON_DRIVER_EVENT_FRAME_IN != i &&\n            AERON_DRIVER_EVENT_FRAME_OUT != i &&\n            AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR != i &&\n            AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT != i)\n        {\n            const char *event_name = log_events[i].name;\n            if (!aeron_driver_agent_is_unknown_event(event_name))\n            {\n                log_events[i].enabled = is_enabled;\n            }\n        }\n    }\n}\n\nstatic void aeron_driver_agent_set_enabled_specific_events(const uint8_t type, const bool is_enabled)\n{\n    for (size_t i = 0; i < AERON_DRIVER_EVENT_NUM_ELEMENTS; i++)\n    {\n        if (type == log_events[i].type)\n        {\n            log_events[i].enabled = is_enabled;\n        }\n    }\n}\n\nstatic bool any_event_enabled(const uint8_t type)\n{\n    for (size_t i = 0; i < AERON_DRIVER_EVENT_NUM_ELEMENTS; i++)\n    {\n        if (type == log_events[i].type && log_events[i].enabled)\n        {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nstatic aeron_driver_agent_event_t parse_event_name(const char *event_name)\n{\n    if (0 == strncmp(\"SEND_NAK_MESSAGE\", event_name, strlen(\"SEND_NAK_MESSAGE\") + 1))\n    {\n        return AERON_DRIVER_EVENT_NAK_SENT;\n    }\n\n    aeron_driver_agent_event_t event_id = aeron_driver_agent_event_name_to_id(event_name);\n    if (AERON_DRIVER_EVENT_UNKNOWN_EVENT != event_id)\n    {\n        return event_id;\n    }\n\n    const long id = strtol(event_name, NULL, 0);\n    if (is_valid_event_id((int)id) &&\n        !aeron_driver_agent_is_unknown_event(aeron_driver_agent_event_name((aeron_driver_agent_event_t)id)))\n    {\n        return (aeron_driver_agent_event_t)id;\n    }\n\n    return AERON_DRIVER_EVENT_UNKNOWN_EVENT;\n}\n\nstatic bool aeron_driver_agent_events_set_enabled(char const **events, const int num_events, const bool is_enabled)\n{\n    bool result = false;\n\n    for (int i = num_events - 1; i >= 0; i--)\n    {\n        const char *event_name = events[i];\n        if (0 == strncmp(AERON_DRIVER_AGENT_ALL_EVENTS, event_name, strlen(AERON_DRIVER_AGENT_ALL_EVENTS) + 1))\n        {\n            aeron_driver_agent_set_enabled_all_events(is_enabled);\n            result = true;\n            break;\n        }\n        else if (0 == strncmp(AERON_DRIVER_AGENT_ADMIN_EVENTS, event_name, strlen(AERON_DRIVER_AGENT_ADMIN_EVENTS) + 1))\n        {\n            aeron_driver_agent_set_enabled_admin_events(is_enabled);\n            result = true;\n        }\n        else if (0 == strncmp(\"0x\", event_name, 2))  // Legacy mask-based events\n        {\n            const uint64_t mask = strtoull(event_name, NULL, 0);\n\n            if (0 != mask)\n            {\n                if (mask >= 0xFFFFu)\n                {\n                    aeron_driver_agent_set_enabled_all_events(is_enabled);\n                    result = true;\n                }\n                else\n                {\n                    if (mask & 0x1u)\n                    {\n                        aeron_driver_agent_set_enabled_specific_events(\n                            AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN, is_enabled);\n                        result = true;\n                    }\n\n                    if (mask & 0x2u)\n                    {\n                        aeron_driver_agent_set_enabled_specific_events(\n                            AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT, is_enabled);\n                        result = true;\n                    }\n\n                    if (mask & 0x4u)\n                    {\n                        log_events[AERON_DRIVER_EVENT_FRAME_IN].enabled = is_enabled;\n                        result = true;\n                    }\n\n                    if (mask & 0x8u || mask & 0x10u)\n                    {\n                        log_events[AERON_DRIVER_EVENT_FRAME_OUT].enabled = is_enabled;\n                        result = true;\n                    }\n\n                    if (mask & 0x80u)\n                    {\n                        log_events[AERON_DRIVER_EVENT_UNTETHERED_SUBSCRIPTION_STATE_CHANGE].enabled = is_enabled;\n                        result = true;\n                    }\n\n                    if (mask & 0x100u)\n                    {\n                        log_events[AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT].enabled = is_enabled;\n                        result = true;\n                    }\n                }\n            }\n            break;\n        }\n        else\n        {\n            const aeron_driver_agent_event_t event_id = parse_event_name(event_name);\n\n            if (AERON_DRIVER_EVENT_UNKNOWN_EVENT != event_id)\n            {\n                log_events[event_id].enabled = is_enabled;\n                result = true;\n            }\n            else\n            {\n                fprintf(stderr, \"unknown event code: '%s'\\n\", event_name);\n            }\n        }\n    }\n    return result;\n}\n\nbool aeron_driver_agent_logging_events_init(const char *event_log, const char *event_log_disable)\n{\n    aeron_driver_agent_logging_events_free();\n\n    if (NULL == event_log)\n    {\n        return false;\n    }\n\n    char *event_log_dup = strdup(event_log);\n    if (NULL == event_log_dup)\n    {\n        fprintf(stderr, \"failed to copy logging events string\\n\");\n        return false;\n    }\n\n    char *events[AERON_DRIVER_EVENT_NUM_ELEMENTS];\n    const int num_events = aeron_tokenise(event_log_dup, ',', AERON_DRIVER_EVENT_NUM_ELEMENTS, events);\n\n    if (num_events < 0)\n    {\n        fprintf(stderr, \"failed to parse logging events: '%s'\\n\", event_log);\n        aeron_free(event_log_dup);\n        return false;\n    }\n\n    char *events_disable[AERON_DRIVER_EVENT_NUM_ELEMENTS];\n    int num_events_disable = 0;\n    char *event_log_disable_dup = NULL;\n\n    if (NULL != event_log_disable)\n    {\n        event_log_disable_dup = strdup(event_log_disable);\n        if (NULL == event_log_disable_dup)\n        {\n            fprintf(stderr, \"failed to copy logging disable events string\\n\");\n            aeron_free(event_log_dup);\n            return false;\n        }\n\n        num_events_disable = aeron_tokenise(\n            event_log_disable_dup, ',', AERON_DRIVER_EVENT_NUM_ELEMENTS, events_disable);\n\n        if (num_events_disable < 0)\n        {\n            fprintf(stderr, \"failed to parse logging events: '%s'\\n\", event_log_disable);\n            aeron_free(event_log_dup);\n            aeron_free(event_log_disable_dup);\n            return false;\n        }\n    }\n\n    bool result = aeron_driver_agent_events_set_enabled((const char **)events, num_events, true);\n    if (0 < num_events_disable)\n    {\n        result &= aeron_driver_agent_events_set_enabled((const char **)events_disable, num_events_disable, false);\n    }\n\n    aeron_free(event_log_dup);\n    aeron_free(event_log_disable_dup);\n\n    return result;\n}\n\nvoid aeron_driver_agent_logging_events_free(void)\n{\n    for (size_t i = 0; i < AERON_DRIVER_EVENT_NUM_ELEMENTS; i++)\n    {\n        log_events[i].enabled = false;\n    }\n\n    dynamic_dissector_entries = NULL;\n    dynamic_dissector_index = 0;\n    num_dynamic_dissector_entries = 0;\n}\n\nstatic void initialize_agent_logging(void)\n{\n    const char *event_log_str = getenv(AERON_EVENT_LOG_ENV_VAR);\n    const char *event_log_disable_str = getenv(AERON_EVENT_LOG_DISABLE_ENV_VAR);\n\n    if (aeron_driver_agent_logging_events_init(event_log_str, event_log_disable_str))\n    {\n        logfp = stdout;\n        const char *log_filename = getenv(AERON_EVENT_LOG_FILENAME_ENV_VAR);\n\n        if (log_filename)\n        {\n            if ((logfp = fopen(log_filename, \"a\")) == NULL)\n            {\n                int errcode = errno;\n\n                fprintf(stderr, \"could not fopen log file %s (%d, %s). exiting.\\n\",\n                    log_filename, errcode, strerror(errcode));\n                exit(EXIT_FAILURE);\n            }\n        }\n\n        aeron_driver_agent_logging_ring_buffer_init();\n\n        if (aeron_thread_create(&log_reader_thread, NULL, aeron_driver_agent_log_reader, NULL) != 0)\n        {\n            fprintf(stderr, \"could not start log reader thread. exiting.\\n\");\n            exit(EXIT_FAILURE);\n        }\n\n        fprintf(logfp, \"%s\\n\", aeron_driver_agent_dissect_log_start(aeron_nano_clock(), aeron_epoch_clock()));\n    }\n}\n\nstatic aeron_driver_agent_event_t command_id_to_driver_event_id(const int32_t msg_type_id)\n{\n    switch (msg_type_id)\n    {\n        case AERON_COMMAND_ADD_PUBLICATION:\n            return AERON_DRIVER_EVENT_CMD_IN_ADD_PUBLICATION;\n\n        case AERON_COMMAND_REMOVE_PUBLICATION:\n            return AERON_DRIVER_EVENT_CMD_IN_REMOVE_PUBLICATION;\n\n        case AERON_COMMAND_ADD_EXCLUSIVE_PUBLICATION:\n            return AERON_DRIVER_EVENT_CMD_IN_ADD_EXCLUSIVE_PUBLICATION;\n\n        case AERON_COMMAND_ADD_SUBSCRIPTION:\n            return AERON_DRIVER_EVENT_CMD_IN_ADD_SUBSCRIPTION;\n\n        case AERON_COMMAND_REMOVE_SUBSCRIPTION:\n            return AERON_DRIVER_EVENT_CMD_IN_REMOVE_SUBSCRIPTION;\n\n        case AERON_COMMAND_CLIENT_KEEPALIVE:\n            return AERON_DRIVER_EVENT_CMD_IN_KEEPALIVE_CLIENT;\n\n        case AERON_COMMAND_ADD_DESTINATION:\n            return AERON_DRIVER_EVENT_CMD_IN_ADD_DESTINATION;\n\n        case AERON_COMMAND_REMOVE_DESTINATION:\n            return AERON_DRIVER_EVENT_CMD_IN_REMOVE_DESTINATION;\n\n        case AERON_COMMAND_ADD_COUNTER:\n            return AERON_DRIVER_EVENT_CMD_IN_ADD_COUNTER;\n\n        case AERON_COMMAND_REMOVE_COUNTER:\n            return AERON_DRIVER_EVENT_CMD_IN_REMOVE_COUNTER;\n\n        case AERON_COMMAND_CLIENT_CLOSE:\n            return AERON_DRIVER_EVENT_CMD_IN_CLIENT_CLOSE;\n\n        case AERON_COMMAND_ADD_RCV_DESTINATION:\n            return AERON_DRIVER_EVENT_CMD_IN_ADD_RCV_DESTINATION;\n\n        case AERON_COMMAND_REMOVE_RCV_DESTINATION:\n            return AERON_DRIVER_EVENT_CMD_IN_REMOVE_RCV_DESTINATION;\n\n        case AERON_COMMAND_TERMINATE_DRIVER:\n            return AERON_DRIVER_EVENT_CMD_IN_TERMINATE_DRIVER;\n\n        case AERON_RESPONSE_ON_ERROR:\n            return AERON_DRIVER_EVENT_CMD_OUT_ERROR;\n\n        case AERON_RESPONSE_ON_AVAILABLE_IMAGE:\n            return AERON_DRIVER_EVENT_CMD_OUT_AVAILABLE_IMAGE;\n\n        case AERON_RESPONSE_ON_PUBLICATION_READY:\n            return AERON_DRIVER_EVENT_CMD_OUT_PUBLICATION_READY;\n\n        case AERON_RESPONSE_ON_OPERATION_SUCCESS:\n            return AERON_DRIVER_EVENT_CMD_OUT_ON_OPERATION_SUCCESS;\n\n        case AERON_RESPONSE_ON_UNAVAILABLE_IMAGE:\n            return AERON_DRIVER_EVENT_CMD_OUT_ON_UNAVAILABLE_IMAGE;\n\n        case AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY:\n            return AERON_DRIVER_EVENT_CMD_OUT_EXCLUSIVE_PUBLICATION_READY;\n\n        case AERON_RESPONSE_ON_SUBSCRIPTION_READY:\n            return AERON_DRIVER_EVENT_CMD_OUT_SUBSCRIPTION_READY;\n\n        case AERON_RESPONSE_ON_COUNTER_READY:\n            return AERON_DRIVER_EVENT_CMD_OUT_COUNTER_READY;\n\n        case AERON_RESPONSE_ON_UNAVAILABLE_COUNTER:\n            return AERON_DRIVER_EVENT_CMD_OUT_ON_UNAVAILABLE_COUNTER;\n\n        case AERON_RESPONSE_ON_CLIENT_TIMEOUT:\n            return AERON_DRIVER_EVENT_CMD_OUT_ON_CLIENT_TIMEOUT;\n\n        case AERON_COMMAND_REMOVE_DESTINATION_BY_ID:\n            return AERON_DRIVER_EVENT_CMD_IN_REMOVE_DESTINATION_BY_ID;\n\n        case AERON_COMMAND_REJECT_IMAGE:\n            return AERON_DRIVER_EVENT_CMD_IN_REJECT_IMAGE;\n\n        default:\n            return AERON_DRIVER_EVENT_UNKNOWN_EVENT;\n    }\n}\n\nstatic void aeron_driver_agent_log_nak_message(\n    const aeron_driver_agent_event_t log_event,\n    const struct sockaddr_storage *address,\n    const int32_t session_id,\n    const int32_t stream_id,\n    const int32_t term_id,\n    const int32_t term_offset,\n    const int32_t nak_length,\n    const size_t channel_length,\n    const char *channel)\n{\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        log_event,\n        sizeof(aeron_driver_agent_nak_message_header_t) +\n            channel_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_nak_message_header_t *hdr =\n            (aeron_driver_agent_nak_message_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        memcpy(&hdr->address, address, sizeof(hdr->address));\n        hdr->session_id = session_id;\n        hdr->stream_id = stream_id;\n        hdr->term_id = term_id;\n        hdr->term_offset = term_offset;\n        hdr->nak_length = nak_length;\n        hdr->channel_length = (int32_t)channel_length;\n\n        ptr += sizeof(aeron_driver_agent_nak_message_header_t);\n        memcpy(ptr, channel, channel_length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_conductor_to_driver_interceptor(\n    int32_t msg_type_id, const void *message, size_t length, void *clientd)\n{\n    const aeron_driver_agent_event_t event_id = command_id_to_driver_event_id(msg_type_id);\n    if (!aeron_driver_agent_is_event_enabled(event_id))\n    {\n        return;\n    }\n\n    const size_t command_length = sizeof(aeron_driver_agent_cmd_log_header_t) + length;\n\n    int32_t offset = aeron_mpsc_rb_try_claim(&logging_mpsc_rb, event_id, command_length);\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_cmd_log_header_t *hdr = (aeron_driver_agent_cmd_log_header_t *)ptr;\n        hdr->time_ns = aeron_nano_clock();\n        hdr->cmd_id = msg_type_id;\n        memcpy(ptr + sizeof(aeron_driver_agent_cmd_log_header_t), message, length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_conductor_to_client_interceptor(\n    aeron_driver_conductor_t *conductor, int32_t msg_type_id, const void *message, size_t length)\n{\n    const aeron_driver_agent_event_t event_id = command_id_to_driver_event_id(msg_type_id);\n    if (!aeron_driver_agent_is_event_enabled(event_id))\n    {\n        return;\n    }\n\n    const size_t command_length = sizeof(aeron_driver_agent_cmd_log_header_t) + length;\n    int32_t offset = aeron_mpsc_rb_try_claim(&logging_mpsc_rb, event_id, command_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_cmd_log_header_t *hdr = (aeron_driver_agent_cmd_log_header_t *)ptr;\n        hdr->time_ns = aeron_nano_clock();\n        hdr->cmd_id = msg_type_id;\n        memcpy(ptr + sizeof(aeron_driver_agent_cmd_log_header_t), message, length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_log_frame(int32_t msg_type_id, const struct msghdr *msghdr, int32_t message_len)\n{\n    const int32_t copy_length = message_len < AERON_MAX_FRAME_LENGTH ? message_len : AERON_MAX_FRAME_LENGTH;\n    const size_t command_length =\n        sizeof(aeron_driver_agent_frame_log_header_t) + msghdr->msg_namelen + copy_length;\n    int32_t offset = aeron_mpsc_rb_try_claim(&logging_mpsc_rb, msg_type_id, command_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_frame_log_header_t *hdr = (aeron_driver_agent_frame_log_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->sockaddr_len = msghdr->msg_namelen;\n        hdr->message_len = message_len;\n\n        if (msghdr->msg_iovlen > 1)\n        {\n            fprintf(stderr, \"only aware of 1 iov. %d iovs detected.\\n\", (int)msghdr->msg_iovlen);\n        }\n\n        ptr += sizeof(aeron_driver_agent_frame_log_header_t);\n        memcpy(ptr, msghdr->msg_name, msghdr->msg_namelen);\n\n        ptr += msghdr->msg_namelen;\n        memcpy(ptr, msghdr->msg_iov[0].iov_base, (size_t)copy_length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_log_frame_iov(\n    int32_t msg_type_id, const struct sockaddr_storage *address, struct iovec *iov, int32_t message_len)\n{\n    const int32_t copy_length = message_len < AERON_MAX_FRAME_LENGTH ? message_len : AERON_MAX_FRAME_LENGTH;\n    size_t address_length = AERON_ADDR_LEN(address);\n    const size_t command_length =\n        sizeof(aeron_driver_agent_frame_log_header_t) + address_length + copy_length;\n    int32_t offset = aeron_mpsc_rb_try_claim(&logging_mpsc_rb, msg_type_id, command_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_frame_log_header_t *hdr = (aeron_driver_agent_frame_log_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->sockaddr_len = (int32_t)address_length;\n        hdr->message_len = message_len;\n\n        ptr += sizeof(aeron_driver_agent_frame_log_header_t);\n        memcpy(ptr, address, address_length);\n\n        ptr += address_length;\n        memcpy(ptr, iov->iov_base, (size_t)copy_length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nint aeron_driver_agent_outgoing_send(\n    void *interceptor_state,\n    aeron_udp_channel_outgoing_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n    for (size_t i = 0; i < iov_length; i++)\n    {\n        aeron_driver_agent_log_frame_iov(\n            AERON_DRIVER_EVENT_FRAME_OUT,\n            address,\n            &iov[i],\n            (int32_t)iov[i].iov_len);\n    }\n\n    int result = 0;\n    if (NULL != delegate)\n    {\n        result = delegate->outgoing_send_func(\n            delegate->interceptor_state, delegate->next_interceptor, transport, address, iov, iov_length, bytes_sent);\n    }\n\n    return result;\n}\n\nvoid aeron_driver_agent_incoming_msg(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp)\n{\n    struct msghdr message;\n    struct iovec iov;\n\n    iov.iov_base = buffer;\n    iov.iov_len = (uint32_t)length;\n    message.msg_iovlen = 1;\n    message.msg_iov = &iov;\n    message.msg_name = addr;\n    message.msg_control = NULL;\n    message.msg_controllen = 0;\n    message.msg_namelen = AERON_ADDR_LEN(addr);\n\n    aeron_driver_agent_log_frame(AERON_DRIVER_EVENT_FRAME_IN, &message, (int32_t)length);\n\n    if (NULL != delegate)\n    {\n        delegate->incoming_func(\n            delegate->interceptor_state,\n            delegate->next_interceptor,\n            transport,\n            receiver_clientd,\n            endpoint_clientd,\n            destination_clientd,\n            buffer,\n            length,\n            addr,\n            media_timestamp);\n    }\n}\n\nvoid aeron_driver_agent_untethered_subscription_state_change(\n    aeron_tetherable_position_t *tetherable_position,\n    int64_t now_ns,\n    aeron_subscription_tether_state_t old_state,\n    aeron_subscription_tether_state_t new_state,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        AERON_DRIVER_EVENT_UNTETHERED_SUBSCRIPTION_STATE_CHANGE,\n        sizeof(aeron_driver_agent_untethered_subscription_state_change_log_header_t));\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_untethered_subscription_state_change_log_header_t *hdr =\n            (aeron_driver_agent_untethered_subscription_state_change_log_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->subscription_id = tetherable_position->subscription_registration_id;\n        hdr->stream_id = stream_id;\n        hdr->session_id = session_id;\n        hdr->old_state = old_state;\n        hdr->new_state = new_state;\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid log_name_resolution_neighbor_change(const aeron_driver_agent_event_t id, const struct sockaddr_storage *addr)\n{\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb, id, sizeof(aeron_driver_agent_log_header_t) + AERON_ADDR_LEN(addr));\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_log_header_t *hdr = (aeron_driver_agent_log_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n\n        ptr += sizeof(aeron_driver_agent_log_header_t);\n        memcpy(ptr, addr, AERON_ADDR_LEN(addr));\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_name_resolution_on_neighbor_added(const struct sockaddr_storage *addr)\n{\n    log_name_resolution_neighbor_change(AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_ADDED, addr);\n}\n\nvoid aeron_driver_agent_name_resolution_on_neighbor_removed(const struct sockaddr_storage *addr)\n{\n    log_name_resolution_neighbor_change(AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_REMOVED, addr);\n}\n\nvoid log_flow_control_on_receiver_change(\n    const aeron_driver_agent_event_t event_id,\n    const int64_t receiver_id,\n    const int32_t session_id,\n    const int32_t stream_id,\n    const size_t channel_length,\n    const char *channel,\n    const size_t receiver_count)\n{\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        event_id,\n        sizeof(aeron_driver_agent_flow_control_receiver_change_log_header_t) + channel_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_flow_control_receiver_change_log_header_t *hdr =\n            (aeron_driver_agent_flow_control_receiver_change_log_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->receiver_id = receiver_id;\n        hdr->session_id = session_id;\n        hdr->stream_id = stream_id;\n        hdr->channel_length = (int32_t)channel_length;\n        hdr->receiver_count = (int32_t)receiver_count;\n\n        memcpy(ptr + sizeof(aeron_driver_agent_flow_control_receiver_change_log_header_t), channel, channel_length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_flow_control_on_receiver_added(\n    int64_t receiver_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    size_t receiver_count)\n{\n    log_flow_control_on_receiver_change(\n        AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_ADDED,\n        receiver_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        receiver_count);\n}\n\nvoid aeron_driver_agent_flow_control_on_receiver_removed(\n    int64_t receiver_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    size_t receiver_count)\n{\n    log_flow_control_on_receiver_change(\n        AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_REMOVED,\n        receiver_id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        receiver_count);\n}\n\nint32_t aeron_driver_agent_socket_address_length(const struct sockaddr_storage *address)\n{\n    if (NULL == address)\n    {\n        return 0;\n    }\n    else if (AF_INET == address->ss_family)\n    {\n        return sizeof(((struct sockaddr_in*)(address))->sin_addr);\n    }\n    else if (AF_INET6 == address->ss_family)\n    {\n        return sizeof(((struct sockaddr_in6*)(address))->sin6_addr);\n    }\n\n    return 0;\n}\n\nvoid aeron_driver_agent_send_nak_message(\n    const struct sockaddr_storage *address,\n    const int32_t session_id,\n    const int32_t stream_id,\n    const int32_t term_id,\n    const int32_t term_offset,\n    const int32_t nak_length,\n    const size_t channel_length,\n    const char *channel)\n{\n    aeron_driver_agent_log_nak_message(\n        AERON_DRIVER_EVENT_NAK_SENT,\n        address,\n        session_id,\n        stream_id,\n        term_id,\n        term_offset,\n        nak_length,\n        channel_length,\n        channel);\n}\n\nvoid aeron_driver_agent_on_nak_message(\n    const struct sockaddr_storage *address,\n    const int32_t session_id,\n    const int32_t stream_id,\n    const int32_t term_id,\n    const int32_t term_offset,\n    const int32_t nak_length,\n    const size_t channel_length,\n    const char *channel)\n{\n    aeron_driver_agent_log_nak_message(\n        AERON_DRIVER_EVENT_NAK_RECEIVED,\n        address,\n        session_id,\n        stream_id,\n        term_id,\n        term_offset,\n        nak_length,\n        channel_length,\n        channel);\n}\n\nvoid aeron_driver_agent_resend(\n    const int32_t session_id,\n    const int32_t stream_id,\n    const int32_t term_id,\n    const int32_t term_offset,\n    const int32_t resend_length,\n    const size_t channel_length,\n    const char *channel)\n{\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        AERON_DRIVER_EVENT_RESEND,\n        sizeof(aeron_driver_agent_resend_header_t) +\n            channel_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_resend_header_t *hdr =\n            (aeron_driver_agent_resend_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->session_id = session_id;\n        hdr->stream_id = stream_id;\n        hdr->term_id = term_id;\n        hdr->term_offset = term_offset;\n        hdr->resend_length = resend_length;\n        hdr->channel_length = (int32_t)channel_length;\n\n        ptr += sizeof(aeron_driver_agent_resend_header_t);\n        memcpy(ptr, channel, channel_length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_publication_revoke(\n    int64_t revoked_pos,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        AERON_DRIVER_EVENT_PUBLICATION_REVOKE,\n        sizeof(aeron_driver_agent_publication_revoke_header_t) +\n            channel_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_publication_revoke_header_t *hdr =\n            (aeron_driver_agent_publication_revoke_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->revoked_pos = revoked_pos;\n        hdr->session_id = session_id;\n        hdr->stream_id = stream_id;\n        hdr->channel_length = (int32_t)channel_length;\n\n        ptr += sizeof(aeron_driver_agent_publication_revoke_header_t);\n        memcpy(ptr, channel, channel_length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_publication_image_revoke(\n    int64_t revoked_pos,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        AERON_DRIVER_EVENT_PUBLICATION_IMAGE_REVOKE,\n        sizeof(aeron_driver_agent_publication_image_revoke_header_t) +\n            channel_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_publication_image_revoke_header_t *hdr =\n            (aeron_driver_agent_publication_image_revoke_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->revoked_pos = revoked_pos;\n        hdr->session_id = session_id;\n        hdr->stream_id = stream_id;\n        hdr->channel_length = (int32_t)channel_length;\n\n        ptr += sizeof(aeron_driver_agent_publication_image_revoke_header_t);\n        memcpy(ptr, channel, channel_length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_socket_address_copy(uint8_t *ptr, const struct sockaddr_storage *address, const size_t address_length)\n{\n    if (AF_INET == address->ss_family)\n    {\n        struct sockaddr_in *address_in = (struct sockaddr_in *)address;\n        memcpy(ptr, &address_in->sin_addr, address_length);\n    }\n    else if (AF_INET6 == address->ss_family)\n    {\n        struct sockaddr_in6 *address_in6 = (struct sockaddr_in6 *)address;\n        memcpy(ptr, &address_in6->sin6_addr, address_length);\n    }\n}\n\n\nvoid aeron_driver_agent_name_resolver_on_resolve(\n    aeron_name_resolver_t *name_resolver,\n    int64_t duration_ns,\n    const char *hostname,\n    bool is_re_resolution,\n    struct sockaddr_storage *address)\n{\n    const size_t resolverNameFullLength = strlen(name_resolver->name);\n    const size_t resolverNameLength = resolverNameFullLength < AERON_MAX_HOSTNAME_LEN ?\n        resolverNameFullLength : AERON_MAX_HOSTNAME_LEN;\n\n    const size_t hostnameFullLength = strlen(hostname);\n    const size_t hostnameLength = hostnameFullLength < AERON_MAX_HOSTNAME_LEN ?\n        hostnameFullLength : AERON_MAX_HOSTNAME_LEN;\n\n    const size_t addressLength = aeron_driver_agent_socket_address_length(address);\n\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        AERON_DRIVER_EVENT_NAME_RESOLUTION_RESOLVE,\n        sizeof(aeron_driver_agent_name_resolver_resolve_log_header_t) +\n            resolverNameLength +\n            hostnameLength +\n            addressLength);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_name_resolver_resolve_log_header_t *hdr =\n            (aeron_driver_agent_name_resolver_resolve_log_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->duration_ns = duration_ns;\n        hdr->resolver_name_length = (int32_t)resolverNameLength;\n        hdr->hostname_length = (int32_t)hostnameLength;\n        hdr->address_length = (int32_t)addressLength;\n        hdr->is_re_resolution = is_re_resolution;\n\n        uint8_t *bodyPtr = ptr + sizeof(aeron_driver_agent_name_resolver_resolve_log_header_t);\n        memcpy(bodyPtr, name_resolver->name, (size_t)resolverNameLength);\n        memcpy(bodyPtr + resolverNameLength, hostname, (size_t)hostnameLength);\n\n        if (NULL != address)\n        {\n            aeron_driver_agent_socket_address_copy(\n                bodyPtr + resolverNameLength + hostnameLength,\n                address,\n                addressLength);\n        }\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_name_resolver_on_lookup(\n    aeron_name_resolver_t *name_resolver,\n    int64_t duration_ns,\n    const char *name,\n    bool is_re_lookup,\n    const char *resolved_name)\n{\n    const size_t resolverNameFullLength = strlen(name_resolver->name);\n    const size_t resolverNameLength = resolverNameFullLength < AERON_MAX_HOSTNAME_LEN ?\n        resolverNameFullLength : AERON_MAX_HOSTNAME_LEN;\n\n    const size_t nameFullLength = strlen(name);\n    const size_t nameLength = nameFullLength < AERON_MAX_HOSTNAME_LEN ? nameFullLength : AERON_MAX_HOSTNAME_LEN;\n\n    const size_t resolvedNameFullLength = NULL != resolved_name ? strlen(resolved_name) : 0;\n    const size_t resolvedNameLength = resolvedNameFullLength < AERON_MAX_HOSTNAME_LEN ?\n        resolvedNameFullLength : AERON_MAX_HOSTNAME_LEN;\n\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        AERON_DRIVER_EVENT_NAME_RESOLUTION_LOOKUP,\n        sizeof(aeron_driver_agent_name_resolver_lookup_log_header_t) +\n            resolverNameLength +\n            nameLength +\n            resolvedNameLength);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_name_resolver_lookup_log_header_t *hdr =\n            (aeron_driver_agent_name_resolver_lookup_log_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->duration_ns = duration_ns;\n        hdr->resolver_name_length = (int32_t)resolverNameLength;\n        hdr->name_length = (int32_t)nameLength;\n        hdr->resolved_name_length = (int32_t)resolvedNameLength;\n        hdr->is_re_lookup = is_re_lookup;\n\n        uint8_t *bodyPtr = ptr + sizeof(aeron_driver_agent_name_resolver_lookup_log_header_t);\n        memcpy(bodyPtr, name_resolver->name, (size_t)resolverNameLength);\n        memcpy(bodyPtr + resolverNameLength, name, (size_t)nameLength);\n\n        if (NULL != resolved_name)\n        {\n            memcpy(bodyPtr + resolverNameLength + nameLength, resolved_name, (size_t)resolvedNameLength);\n        }\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_name_resolver_on_host_name(int64_t duration_ns, const char *host_name)\n{\n    const size_t hostNameFullLength = strlen(host_name);\n    const size_t hostNameLength =\n        hostNameFullLength < AERON_MAX_HOSTNAME_LEN ? hostNameFullLength : AERON_MAX_HOSTNAME_LEN;\n\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        AERON_DRIVER_EVENT_NAME_RESOLUTION_HOST_NAME,\n        sizeof(aeron_driver_agent_name_resolver_host_name_log_header_t) + hostNameLength);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_name_resolver_host_name_log_header_t *hdr =\n            (aeron_driver_agent_name_resolver_host_name_log_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->duration_ns = duration_ns;\n        hdr->host_name_length = (int32_t)hostNameLength;\n\n        uint8_t *bodyPtr = ptr + sizeof(aeron_driver_agent_name_resolver_host_name_log_header_t);\n        memcpy(bodyPtr, host_name, (size_t)hostNameLength);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nint aeron_driver_agent_interceptor_init(\n    void **interceptor_state, aeron_driver_context_t *context, aeron_udp_channel_transport_affinity_t affinity)\n{\n    return 0;\n}\n\naeron_udp_channel_interceptor_bindings_t *aeron_driver_agent_new_interceptor(void)\n{\n    aeron_udp_channel_interceptor_bindings_t *interceptor = NULL;\n\n    if (aeron_alloc((void **)&interceptor, sizeof(aeron_udp_channel_interceptor_bindings_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to allocate interceptor bindings for logging agent\");\n        return NULL;\n    }\n\n    interceptor->incoming_init_func = aeron_driver_agent_interceptor_init;\n    interceptor->incoming_close_func = NULL;\n    interceptor->incoming_func = NULL;\n    interceptor->incoming_transport_notification_func = NULL;\n    interceptor->incoming_publication_notification_func = NULL;\n    interceptor->incoming_image_notification_func = NULL;\n    interceptor->outgoing_init_func = aeron_driver_agent_interceptor_init;\n    interceptor->outgoing_close_func = NULL;\n    interceptor->outgoing_send_func = NULL;\n    interceptor->outgoing_transport_notification_func = NULL;\n    interceptor->outgoing_publication_notification_func = NULL;\n    interceptor->outgoing_image_notification_func = NULL;\n\n    interceptor->meta_info.name = \"logging\";\n    interceptor->meta_info.type = \"interceptor\";\n    interceptor->meta_info.source_symbol = (aeron_fptr_t)aeron_driver_agent_context_init;\n    interceptor->meta_info.next_interceptor_bindings = NULL;\n\n    return interceptor;\n}\n\nint aeron_driver_agent_init_logging_events_interceptors(aeron_driver_context_t *context)\n{\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_IN))\n    {\n        aeron_udp_channel_interceptor_bindings_t *interceptors = context->udp_channel_incoming_interceptor_bindings;\n        aeron_udp_channel_interceptor_bindings_t *first_interceptor = aeron_driver_agent_new_interceptor();\n        if (NULL == first_interceptor)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n        first_interceptor->incoming_func = aeron_driver_agent_incoming_msg;\n        first_interceptor->meta_info.name = \"incoming pre logging\";\n        first_interceptor->meta_info.next_interceptor_bindings = interceptors;\n        context->udp_channel_incoming_interceptor_bindings = first_interceptor;\n\n        if (NULL != interceptors)\n        {\n            aeron_udp_channel_interceptor_bindings_t *last_interceptor = aeron_driver_agent_new_interceptor();\n            if (NULL == last_interceptor)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return -1;\n            }\n\n            last_interceptor->incoming_func = aeron_driver_agent_incoming_msg;\n            last_interceptor->meta_info.name = \"incoming post logging\";\n\n            while (NULL != interceptors->meta_info.next_interceptor_bindings)\n            {\n                interceptors = (aeron_udp_channel_interceptor_bindings_t *)interceptors->meta_info.next_interceptor_bindings;\n            }\n            interceptors->meta_info.next_interceptor_bindings = last_interceptor;\n        }\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_OUT))\n    {\n        aeron_udp_channel_interceptor_bindings_t *interceptors = context->udp_channel_outgoing_interceptor_bindings;\n        aeron_udp_channel_interceptor_bindings_t *first_interceptor = aeron_driver_agent_new_interceptor();\n        if (NULL == first_interceptor)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        first_interceptor->outgoing_send_func = aeron_driver_agent_outgoing_send;\n        first_interceptor->meta_info.name = \"outgoing pre logging\";\n        first_interceptor->meta_info.next_interceptor_bindings = interceptors;\n        context->udp_channel_outgoing_interceptor_bindings = first_interceptor;\n\n        if (NULL != interceptors)\n        {\n            aeron_udp_channel_interceptor_bindings_t *last_interceptor = aeron_driver_agent_new_interceptor();\n            if (NULL == last_interceptor)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                return -1;\n            }\n\n            last_interceptor->outgoing_send_func = aeron_driver_agent_outgoing_send;\n            last_interceptor->meta_info.name = \"outgoing post logging\";\n\n            while (NULL != interceptors->meta_info.next_interceptor_bindings)\n            {\n                interceptors = (aeron_udp_channel_interceptor_bindings_t *)interceptors->meta_info.next_interceptor_bindings;\n            }\n            interceptors->meta_info.next_interceptor_bindings = last_interceptor;\n        }\n    }\n\n    if (any_event_enabled(AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN))\n    {\n        context->log.to_driver_interceptor = aeron_driver_agent_conductor_to_driver_interceptor;\n    }\n\n    if (any_event_enabled(AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT))\n    {\n        context->log.to_client_interceptor = aeron_driver_agent_conductor_to_client_interceptor;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_REMOVE_PUBLICATION_CLEANUP))\n    {\n        context->log.remove_publication_cleanup = aeron_driver_agent_remove_publication_cleanup;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_REMOVE_SUBSCRIPTION_CLEANUP))\n    {\n        context->log.remove_subscription_cleanup = aeron_driver_agent_remove_subscription_cleanup;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_REMOVE_IMAGE_CLEANUP))\n    {\n        context->log.remove_image_cleanup = aeron_driver_agent_remove_image_cleanup;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_SEND_CHANNEL_CREATION))\n    {\n        context->log.sender_proxy_on_add_endpoint = aeron_driver_agent_sender_proxy_on_add_endpoint;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_SEND_CHANNEL_CLOSE))\n    {\n        context->log.sender_proxy_on_remove_endpoint = aeron_driver_agent_sender_proxy_on_remove_endpoint;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CREATION))\n    {\n        context->log.receiver_proxy_on_add_endpoint = aeron_driver_agent_receiver_proxy_on_add_endpoint;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CLOSE))\n    {\n        context->log.receiver_proxy_on_remove_endpoint = aeron_driver_agent_receiver_proxy_on_remove_endpoint;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_UNTETHERED_SUBSCRIPTION_STATE_CHANGE))\n    {\n        context->log.untethered_subscription_on_state_change =\n            aeron_driver_agent_untethered_subscription_state_change;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_ADDED))\n    {\n        context->log.name_resolution_on_neighbor_added = aeron_driver_agent_name_resolution_on_neighbor_added;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_REMOVED))\n    {\n        context->log.name_resolution_on_neighbor_removed = aeron_driver_agent_name_resolution_on_neighbor_removed;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_ADDED))\n    {\n        context->log.flow_control_on_receiver_added = aeron_driver_agent_flow_control_on_receiver_added;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_REMOVED))\n    {\n        context->log.flow_control_on_receiver_removed = aeron_driver_agent_flow_control_on_receiver_removed;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAME_RESOLUTION_RESOLVE))\n    {\n        context->log.on_name_resolve = aeron_driver_agent_name_resolver_on_resolve;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAME_RESOLUTION_LOOKUP))\n    {\n        context->log.on_name_lookup = aeron_driver_agent_name_resolver_on_lookup;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAME_RESOLUTION_HOST_NAME))\n    {\n        context->log.on_host_name = aeron_driver_agent_name_resolver_on_host_name;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAK_SENT))\n    {\n        context->log.send_nak_message = aeron_driver_agent_send_nak_message;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAK_RECEIVED))\n    {\n        context->log.on_nak_message = aeron_driver_agent_on_nak_message;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_RESEND))\n    {\n        context->log.resend = aeron_driver_agent_resend;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_PUBLICATION_REVOKE))\n    {\n        context->log.publication_revoke = aeron_driver_agent_publication_revoke;\n    }\n\n    if (aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_PUBLICATION_IMAGE_REVOKE))\n    {\n        context->log.publication_image_revoke = aeron_driver_agent_publication_image_revoke;\n    }\n\n    return 0;\n}\n\nint aeron_driver_agent_context_init(aeron_driver_context_t *context)\n{\n    (void)aeron_thread_once(&agent_is_initialized, initialize_agent_logging);\n\n    return aeron_driver_agent_init_logging_events_interceptors(context);\n}\n\n#define NANOS_PER_SECOND (1000000000)\n\nconst char *aeron_driver_agent_dissect_log_header(\n    int64_t time_ns,\n    aeron_driver_agent_event_t event_id,\n    size_t capture_length,\n    size_t message_length)\n{\n    static char buffer[256];\n\n    const int64_t seconds = time_ns / NANOS_PER_SECOND;\n    const int64_t nanos = time_ns - seconds * NANOS_PER_SECOND;\n    const char *event_name = aeron_driver_agent_event_name(event_id);\n    snprintf(\n        buffer,\n        sizeof(buffer) - 1,\n        \"[%\" PRIu64\".%09\" PRIu64 \"] %s: %.*s [%\" PRIu64 \"/%\" PRIu64 \"]\",\n        seconds,\n        nanos,\n        AERON_DRIVER_AGENT_LOG_CONTEXT,\n        AERON_DRIVER_AGENT_MAX_EVENT_NAME_LENGTH,\n        event_name,\n        (uint64_t)capture_length,\n        (uint64_t)message_length);\n\n    return buffer;\n}\n\nconst char *aeron_driver_agent_dissect_log_start(int64_t time_ns, int64_t time_ms)\n{\n    static char buffer[512];\n    static char datestamp[256];\n\n    const int64_t seconds = time_ns / NANOS_PER_SECOND;\n    const int64_t nanos = time_ns - seconds * NANOS_PER_SECOND;\n    aeron_format_date(datestamp, sizeof(datestamp) - 1, time_ms);\n    snprintf(buffer, sizeof(buffer) - 1, \"[%\" PRIu64\".%09\" PRIu64 \"] log started %s, enabled loggers: {DRIVER: version=%s commit=%s}\",\n        seconds,\n        nanos,\n        datestamp,\n        aeron_driver_version_text(),\n        aeron_driver_version_git_sha());\n\n    return buffer;\n}\n\nstatic const char *dissect_cmd_in(int64_t cmd_id, const void *message, size_t length)\n{\n    static char buffer[4096];\n\n    buffer[0] = '\\0';\n    switch (cmd_id)\n    {\n        case AERON_COMMAND_ADD_PUBLICATION:\n        case AERON_COMMAND_ADD_EXCLUSIVE_PUBLICATION:\n        {\n            aeron_publication_command_t *command = (aeron_publication_command_t *)message;\n\n            const char *channel = (const char *)message + sizeof(aeron_publication_command_t);\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"streamId=%d clientId=%\" PRId64 \" correlationId=%\" PRId64 \" channel=%.*s\",\n                command->stream_id,\n                command->correlated.client_id,\n                command->correlated.correlation_id,\n                command->channel_length,\n                channel);\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_PUBLICATION:\n        {\n            aeron_remove_publication_command_t *command = (aeron_remove_publication_command_t *)message;\n\n            if (length >= sizeof(aeron_remove_publication_command_t))\n            {\n                snprintf(\n                    buffer,\n                    sizeof(buffer) - 1,\n                    \"registrationId=%\" PRId64 \" clientId=%\" PRId64 \" correlationId=%\" PRId64 \" revoke=%s\",\n                    command->registration_id,\n                    command->correlated.client_id,\n                    command->correlated.correlation_id,\n                    command->flags & AERON_COMMAND_REMOVE_PUBLICATION_FLAG_REVOKE ? \"true\" : \"false\");\n            }\n            else\n            {\n                snprintf(\n                    buffer,\n                    sizeof(buffer) - 1,\n                    \"registrationId=%\" PRId64 \" clientId=%\" PRId64 \" correlationId=%\" PRId64,\n                    command->registration_id,\n                    command->correlated.client_id,\n                    command->correlated.correlation_id);\n            }\n\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_SUBSCRIPTION:\n        {\n            aeron_remove_subscription_command_t *command = (aeron_remove_subscription_command_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"registrationId=%\" PRId64 \" clientId=%\" PRId64 \" correlationId=%\" PRId64,\n                command->registration_id,\n                command->correlated.client_id,\n                command->correlated.correlation_id);\n\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_COUNTER:\n        {\n            aeron_remove_counter_command_t *command = (aeron_remove_counter_command_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"registrationId=%\" PRId64 \" clientId=%\" PRId64 \" correlationId=%\" PRId64,\n                command->registration_id,\n                command->correlated.client_id,\n                command->correlated.correlation_id);\n\n            break;\n        }\n\n        case AERON_COMMAND_ADD_SUBSCRIPTION:\n        {\n            aeron_subscription_command_t *command = (aeron_subscription_command_t *)message;\n\n            const char *channel = (const char *)message + sizeof(aeron_subscription_command_t);\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"streamId=%d registrationCorrelationId=%\" PRId64 \" clientId=%\" PRId64 \" correlationId=%\" PRId64 \" channel=%.*s\",\n                command->stream_id,\n                command->registration_correlation_id,\n                command->correlated.client_id,\n                command->correlated.correlation_id,\n                command->channel_length,\n                channel);\n            break;\n        }\n\n        case AERON_COMMAND_CLIENT_KEEPALIVE:\n        case AERON_COMMAND_CLIENT_CLOSE:\n        {\n            aeron_correlated_command_t *command = (aeron_correlated_command_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"clientId=%\" PRId64 \" correlationId=%\" PRId64,\n                command->client_id,\n                command->correlation_id);\n            break;\n        }\n\n        case AERON_COMMAND_ADD_DESTINATION:\n        case AERON_COMMAND_REMOVE_DESTINATION:\n        case AERON_COMMAND_ADD_RCV_DESTINATION:\n        case AERON_COMMAND_REMOVE_RCV_DESTINATION:\n        {\n            aeron_destination_command_t *command = (aeron_destination_command_t *)message;\n\n            const char *channel = (const char *)message + sizeof(aeron_destination_command_t);\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1, \"registrationCorrelationId=%\" PRId64 \" clientId=%\" PRId64 \" correlationId=%\" PRId64 \" channel=%.*s\",\n                command->registration_id,\n                command->correlated.client_id,\n                command->correlated.correlation_id,\n                command->channel_length,\n                channel);\n            break;\n        }\n\n        case AERON_COMMAND_ADD_COUNTER:\n        {\n            aeron_counter_command_t *command = (aeron_counter_command_t *)message;\n\n            const uint8_t *cursor = (const uint8_t *)message + sizeof(aeron_counter_command_t);\n\n            int32_t key_length;\n            memcpy(&key_length, cursor, sizeof(key_length));\n\n            const uint8_t *key = cursor + sizeof(int32_t);\n            cursor = key + AERON_ALIGN(key_length, sizeof(int32_t));\n\n            int32_t label_length;\n            memcpy(&label_length, cursor, sizeof(label_length));\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"ADD_COUNTER typeId=%d keyBufferOffset=%d keyBufferLength=%d labelBufferOffset=%d labelBufferLength=%d clientId=%\" PRId64 \" correlationId=%\" PRId64,\n                command->type_id,\n                (int)(sizeof(aeron_counter_command_t) + sizeof(int32_t)),\n                key_length,\n                (int)(sizeof(aeron_counter_command_t) + (2 * sizeof(int32_t)) + AERON_ALIGN(key_length, sizeof(int32_t))),\n                label_length,\n                command->correlated.client_id,\n                command->correlated.correlation_id);\n            break;\n        }\n\n        case AERON_COMMAND_TERMINATE_DRIVER:\n        {\n            aeron_terminate_driver_command_t *command = (aeron_terminate_driver_command_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"clientId=%\" PRId64 \" tokenBufferLength=%d\",\n                command->correlated.client_id,\n                command->token_length);\n            break;\n        }\n\n        case AERON_COMMAND_REMOVE_DESTINATION_BY_ID:\n        {\n            aeron_destination_by_id_command_t *command = (aeron_destination_by_id_command_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"resourceRegistrationId=%\" PRId64 \"  destinationRegistrationId=%\" PRId64,\n                command->resource_registration_id,\n                command->destination_registration_id);\n            break;\n        }\n\n        case AERON_COMMAND_REJECT_IMAGE:\n        {\n            aeron_reject_image_command_t *command = (aeron_reject_image_command_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"clientId=%\" PRId64 \" correlationId=%\" PRId64 \" imageCorrelationId=%\" PRId64 \"position=%\" PRId64 \" reason=%.*s\",\n                command->correlated.client_id,\n                command->correlated.correlation_id,\n                command->image_correlation_id,\n                command->position,\n                command->reason_length,\n                command->reason_text);\n            break;\n        }\n\n        default:\n            break;\n    }\n\n    return buffer;\n}\n\nstatic const char *dissect_cmd_out(int64_t cmd_id, const void *message, size_t length)\n{\n    static char buffer[4096];\n\n    buffer[0] = '\\0';\n    switch (cmd_id)\n    {\n        case AERON_RESPONSE_ON_OPERATION_SUCCESS:\n        {\n            aeron_operation_succeeded_t *command = (aeron_operation_succeeded_t *)message;\n\n            snprintf(buffer, sizeof(buffer) - 1, \"correlationId=%\" PRId64, command->correlation_id);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_PUBLICATION_READY:\n        case AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY:\n        {\n            aeron_publication_buffers_ready_t *command = (aeron_publication_buffers_ready_t *)message;\n\n            const char *log_file_name = (const char *)message + sizeof(aeron_publication_buffers_ready_t);\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"sessionId=%d streamId=%d publicationLimitCounterId=%d channelStatusCounterId=%d correlationId=%\" PRId64 \" registrationId=%\" PRId64 \" logFileName=%.*s\",\n                command->session_id,\n                command->stream_id,\n                command->position_limit_counter_id,\n                command->channel_status_indicator_id,\n                command->correlation_id,\n                command->registration_id,\n                command->log_file_length,\n                log_file_name);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_SUBSCRIPTION_READY:\n        {\n            aeron_subscription_ready_t *command = (aeron_subscription_ready_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"correlationId=%\" PRId64 \" channelStatusCounterId=%d\",\n                command->correlation_id,\n                command->channel_status_indicator_id);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_ERROR:\n        {\n            aeron_error_response_t *command = (aeron_error_response_t *)message;\n\n            const char *error_message = (const char *)message + sizeof(aeron_error_response_t);\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"offendingCommandCorrelationId=%\" PRId64 \" errorCode=%s message=%.*s\",\n                command->offending_command_correlation_id,\n                aeron_error_code_str(command->error_code),\n                command->error_message_length,\n                error_message);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_UNAVAILABLE_IMAGE:\n        {\n            aeron_image_message_t *command = (aeron_image_message_t *)message;\n\n            const char *channel = (const char *)message + sizeof(aeron_image_message_t);\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1, \"streamId=%d correlationId=%\" PRId64 \" subscriptionRegistrationId=%\" PRId64 \" channel=%.*s\",\n                command->stream_id,\n                command->correlation_id,\n                command->subscription_registration_id,\n                command->channel_length,\n                channel);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_AVAILABLE_IMAGE:\n        {\n            aeron_image_buffers_ready_t *command = (aeron_image_buffers_ready_t *)message;\n            char *log_file_name_ptr = (char *)message + sizeof(aeron_image_buffers_ready_t);\n            int32_t log_file_name_length;\n            memcpy(&log_file_name_length, log_file_name_ptr, sizeof(int32_t));\n            const char *log_file_name = log_file_name_ptr + sizeof(int32_t);\n\n            char *source_identity_ptr =\n                log_file_name_ptr + AERON_ALIGN(log_file_name_length, sizeof(int32_t)) + sizeof(int32_t);\n            int32_t source_identity_length;\n            memcpy(&source_identity_length, source_identity_ptr, sizeof(int32_t));\n            const char *source_identity = source_identity_ptr + sizeof(int32_t);\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"sessionId=%d streamId=%d subscriberPositionId=%\" PRId32 \" subscriptionRegistrationId=%\" PRId64 \" correlationId=%\" PRId64 \" sourceIdentity=%.*s logFileName=%.*s\",\n                command->session_id,\n                command->stream_id,\n                command->subscriber_position_id,\n                command->subscriber_registration_id,\n                command->correlation_id,\n                source_identity_length,\n                source_identity,\n                log_file_name_length,\n                log_file_name);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_COUNTER_READY:\n        {\n            aeron_counter_update_t *command = (aeron_counter_update_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"correlationId=%\" PRId64 \" counterId=%d\",\n                command->correlation_id,\n                command->counter_id);\n            break;\n        }\n\n        case AERON_RESPONSE_ON_CLIENT_TIMEOUT:\n        {\n            aeron_client_timeout_t *command = (aeron_client_timeout_t *)message;\n\n            snprintf(buffer, sizeof(buffer) - 1, \"clientId=%\" PRId64, command->client_id);\n            break;\n        }\n\n        default:\n            break;\n    }\n\n    return buffer;\n}\n\nstatic char *dissect_flags(uint8_t flags, char *dissected_flags)\n{\n    const size_t len = 8;\n    uint8_t flag_mask = (uint8_t)(1 << (len - 1));\n\n    for (size_t i = 0; i < len; i++)\n    {\n        dissected_flags[i] = (flags & flag_mask) == flag_mask ? '1' : '0';\n        flag_mask >>= 1;\n    }\n\n    return dissected_flags;\n}\n\nstatic const char *dissect_res_address(int8_t res_type, const uint8_t *address)\n{\n    static char addr_buffer[INET6_ADDRSTRLEN];\n    int af = AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD == res_type ? AF_INET6 : AF_INET;\n    inet_ntop(af, address, addr_buffer, sizeof(addr_buffer));\n\n    return addr_buffer;\n}\n\nstatic const char *dissect_frame_type(int16_t type)\n{\n    switch (type)\n    {\n        case AERON_HDR_TYPE_PAD:\n            return \"PAD\";\n\n        case AERON_HDR_TYPE_DATA:\n            return \"DATA\";\n\n        case AERON_HDR_TYPE_NAK:\n            return \"NAK\";\n\n        case AERON_HDR_TYPE_SM:\n            return \"SM\";\n\n        case AERON_HDR_TYPE_ERR:\n            return \"ERR\";\n\n        case AERON_HDR_TYPE_SETUP:\n            return \"SETUP\";\n\n        case AERON_HDR_TYPE_RTTM:\n            return \"RTT\";\n\n        case AERON_HDR_TYPE_RES:\n            return \"RES\";\n\n        case AERON_HDR_TYPE_ATS_DATA:\n            return \"ATS_DATA\";\n\n        case AERON_HDR_TYPE_ATS_SETUP:\n            return \"ATS_SETUP\";\n\n        case AERON_HDR_TYPE_ATS_SM:\n            return \"ATS_SM\";\n\n        case AERON_HDR_TYPE_RSP_SETUP:\n            return \"RSP_SETUP\";\n\n        default:\n            return \"unknown frame type\";\n    }\n}\n\nconst char *aeron_driver_agent_dissect_frame(const void *message, size_t length)\n{\n    static char buffer[256];\n    static char dissected_flags[8] = { '0', '0', '0', '0', '0', '0', '0', '0' };\n    aeron_frame_header_t *hdr = (aeron_frame_header_t *)message;\n\n    buffer[0] = '\\0';\n    switch (hdr->type)\n    {\n        case AERON_HDR_TYPE_PAD:\n        case AERON_HDR_TYPE_DATA:\n        case AERON_HDR_TYPE_ATS_DATA:\n        {\n            aeron_data_header_t *data = (aeron_data_header_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"type=%s flags=%.*s frameLength=%d sessionId=%d streamId=%d termId=%d termOffset=%d\",\n                dissect_frame_type(hdr->type),\n                (int)sizeof(dissected_flags),\n                dissect_flags(hdr->flags, dissected_flags),\n                hdr->frame_length,\n                data->session_id,\n                data->stream_id,\n                data->term_id,\n                data->term_offset);\n            break;\n        }\n\n        case AERON_HDR_TYPE_NAK:\n        {\n            aeron_nak_header_t *nak = (aeron_nak_header_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"type=%s flags=%.*s frameLength=%d sessionId=%d streamId=%d termId=%d termOffset=%d length=%d\",\n                dissect_frame_type(hdr->type),\n                (int)sizeof(dissected_flags),\n                dissect_flags(hdr->flags, dissected_flags),\n                hdr->frame_length,\n                nak->session_id,\n                nak->stream_id,\n                nak->term_id,\n                nak->term_offset,\n                nak->length);\n            break;\n        }\n\n        case AERON_HDR_TYPE_SM:\n        case AERON_HDR_TYPE_ATS_SM:\n        {\n            aeron_status_message_header_t *sm = (aeron_status_message_header_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"type=%s flags=%.*s frameLength=%d sessionId=%d streamId=%d termId=%d termOffset=%d receiverWindowLength=%d receiverId=%\" PRId64,\n                dissect_frame_type(hdr->type),\n                (int)sizeof(dissected_flags),\n                dissect_flags(hdr->flags, dissected_flags),\n                hdr->frame_length,\n                sm->session_id,\n                sm->stream_id,\n                sm->consumption_term_id,\n                sm->consumption_term_offset,\n                sm->receiver_window,\n                sm->receiver_id);\n            break;\n        }\n\n        case AERON_HDR_TYPE_ERR:\n        {\n            aeron_error_t *err = (aeron_error_t *)message;\n            const char *error_msg = (const char *)message + sizeof(aeron_error_t);\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"type=%s flags=%.*s frameLength=%d sessionId=%d streamId=%d receiverId=%\" PRIi64 \" groupTag=%\" PRIi64 \" errorCode=%d errorMessage=\\\"%.*s\\\"\",\n                dissect_frame_type(hdr->type),\n                (int)sizeof(dissected_flags),\n                dissect_flags(hdr->flags, dissected_flags),\n                hdr->frame_length,\n                err->session_id,\n                err->stream_id,\n                err->receiver_id,\n                err->group_tag,\n                err->error_code,\n                err->error_length,\n                error_msg);\n            break;\n        }\n\n        case AERON_HDR_TYPE_SETUP:\n        case AERON_HDR_TYPE_ATS_SETUP:\n        {\n            aeron_setup_header_t *setup = (aeron_setup_header_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"type=%s flags=%.*s frameLength=%d sessionId=%d streamId=%d activeTermId=%d initialTermId=%d termOffset=%d termLength=%d mtu=%d ttl=%d\",\n                dissect_frame_type(hdr->type),\n                (int)sizeof(dissected_flags),\n                dissect_flags(hdr->flags, dissected_flags),\n                hdr->frame_length,\n                setup->session_id,\n                setup->stream_id,\n                setup->active_term_id,\n                setup->initial_term_id,\n                setup->term_offset,\n                setup->term_length,\n                setup->mtu,\n                setup->ttl);\n            break;\n        }\n\n        case AERON_HDR_TYPE_RSP_SETUP:\n        {\n            aeron_response_setup_header_t *rsp_setup = (aeron_response_setup_header_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"type=%s flags=%.*s frameLength=%d sessionId=%d streamId=%d responseSessionId=%d\",\n                dissect_frame_type(hdr->type),\n                (int)sizeof(dissected_flags),\n                dissect_flags(hdr->flags, dissected_flags),\n                hdr->frame_length,\n                rsp_setup->session_id,\n                rsp_setup->stream_id,\n                rsp_setup->response_session_id);\n            break;\n        }\n\n        case AERON_HDR_TYPE_RTTM:\n        {\n            aeron_rttm_header_t *rttm = (aeron_rttm_header_t *)message;\n\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"type=%s flags=%.*s frameLength=%d sessionId=%d streamId=%d echoTimestampNs=%\" PRId64 \" receptionDelta=%\" PRId64 \" receiverId=%\" PRId64,\n                dissect_frame_type(hdr->type),\n                (int)sizeof(dissected_flags),\n                dissect_flags(hdr->flags, dissected_flags),\n                hdr->frame_length,\n                rttm->session_id,\n                rttm->stream_id,\n                rttm->echo_timestamp,\n                rttm->reception_delta,\n                rttm->receiver_id);\n            break;\n        }\n\n        case AERON_HDR_TYPE_RES:\n        {\n            uint8_t null_buffer[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };\n            const uint8_t *message_bytes = (uint8_t *)message;\n            const int buffer_available = sizeof(buffer) - 1;\n\n            int buffer_used = snprintf(\n                buffer,\n                buffer_available,\n                \"type=%s flags=%.*s frameLength=%d\",\n                dissect_frame_type(hdr->type),\n                (int)sizeof(dissected_flags),\n                dissect_flags(hdr->flags, dissected_flags),\n                hdr->frame_length);\n            size_t message_offset = sizeof(aeron_frame_header_t);\n\n            while (message_offset < length && buffer_used < buffer_available)\n            {\n                aeron_resolution_header_t *res = (aeron_resolution_header_t *)&message_bytes[message_offset];\n                const int entry_length = aeron_res_header_entry_length(res, length - message_offset);\n                if (entry_length < 0)\n                {\n                    break;\n                }\n\n                const uint8_t *address = null_buffer;\n                const uint8_t *name = null_buffer;\n                int16_t name_length = 0;\n\n                switch (res->res_type)\n                {\n                    case AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD:\n                    {\n                        aeron_resolution_header_ipv6_t *res_ipv6 = (aeron_resolution_header_ipv6_t *)res;\n                        address = res_ipv6->addr;\n                        name_length = res_ipv6->name_length;\n                        name = &message_bytes[message_offset + sizeof(aeron_resolution_header_ipv6_t)];\n                        break;\n                    }\n\n                    case AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD:\n                    {\n                        aeron_resolution_header_ipv4_t *res_ipv4 = (aeron_resolution_header_ipv4_t *)res;\n                        address = res_ipv4->addr;\n                        name_length = res_ipv4->name_length;\n                        name = &message_bytes[message_offset + sizeof(aeron_resolution_header_ipv4_t)];\n                        break;\n                    }\n                }\n\n                buffer_used += snprintf(\n                    &buffer[buffer_used], buffer_available - buffer_used,\n                    \" [resType=%\" PRId8 \" flags=%.*s port=%\" PRIu16 \" ageInMs=%\" PRId32 \" address=%s name=%.*s]\",\n                    res->res_type,\n                    (int)sizeof(dissected_flags),\n                    dissect_flags(res->res_flags, dissected_flags),\n                    res->udp_port,\n                    res->age_in_ms,\n                    dissect_res_address(res->res_type, address),\n                    (int)name_length,\n                    (char *)name);\n\n                message_offset += entry_length;\n            }\n\n            if (buffer_available < buffer_used)\n            {\n                snprintf(&buffer[buffer_available - 3], 4, \"...\");\n            }\n\n            break;\n        }\n\n        default:\n            snprintf(\n                buffer,\n                sizeof(buffer) - 1,\n                \"type=%s (0x%x) version=0x%x flags=%.*s frameLength=%d\",\n                dissect_frame_type(hdr->type),\n                hdr->type,\n                hdr->version,\n                (int)sizeof(dissected_flags),\n                dissect_flags(hdr->flags, dissected_flags),\n                hdr->frame_length);\n            break;\n    }\n\n    return buffer;\n}\n\nstatic const char *dissect_tether_state(aeron_subscription_tether_state_t state)\n{\n    switch (state)\n    {\n        case AERON_SUBSCRIPTION_TETHER_ACTIVE:\n            return \"ACTIVE\";\n\n        case AERON_SUBSCRIPTION_TETHER_LINGER:\n            return \"LINGER\";\n\n        case AERON_SUBSCRIPTION_TETHER_RESTING:\n            return \"RESTING\";\n\n        case AERON_SUBSCRIPTION_TETHER_CLOSED:\n            return \"CLOSED\";\n\n        default:\n            return \"unknown tether state\";\n    }\n}\n\nvoid aeron_driver_agent_log_dissector(int32_t msg_type_id, const void *message, size_t length, void *clientd)\n{\n    switch (msg_type_id)\n    {\n        case AERON_DRIVER_EVENT_FRAME_IN:\n        case AERON_DRIVER_EVENT_FRAME_OUT:\n        {\n            aeron_driver_agent_frame_log_header_t *hdr = (aeron_driver_agent_frame_log_header_t *)message;\n            struct sockaddr_storage *addr =\n                (struct sockaddr_storage *)((const char *)message + sizeof(aeron_driver_agent_frame_log_header_t));\n            const char *frame =\n                (const char *)message + sizeof(aeron_driver_agent_frame_log_header_t) + hdr->sockaddr_len;\n            char addr_buf[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n            const int addr_length = aeron_format_source_identity(addr_buf, sizeof(addr_buf), addr);\n\n            fprintf(\n                logfp,\n                \"%s: address=%.*s %s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, (size_t)hdr->message_len),\n                addr_length,\n                addr_buf,\n                aeron_driver_agent_dissect_frame(frame, (size_t)hdr->message_len));\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_REMOVE_PUBLICATION_CLEANUP:\n        {\n            aeron_driver_agent_remove_resource_cleanup_t *hdr = (aeron_driver_agent_remove_resource_cleanup_t *)message;\n            const char *channel = (const char *)message + sizeof(aeron_driver_agent_remove_resource_cleanup_t);\n            fprintf(\n                logfp,\n                \"%s: sessionId=%d streamId=%d channel=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                hdr->session_id,\n                hdr->stream_id,\n                hdr->channel_length,\n                channel);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_REMOVE_SUBSCRIPTION_CLEANUP:\n        {\n            aeron_driver_agent_remove_resource_cleanup_t *hdr = (aeron_driver_agent_remove_resource_cleanup_t *)message;\n            const char *channel = (const char *)message + sizeof(aeron_driver_agent_remove_resource_cleanup_t);\n            fprintf(\n                logfp,\n                \"%s: streamId=%d id=%\" PRId64 \" channel=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                hdr->stream_id,\n                hdr->id,\n                hdr->channel_length,\n                channel);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_REMOVE_IMAGE_CLEANUP:\n        {\n            aeron_driver_agent_remove_resource_cleanup_t *hdr = (aeron_driver_agent_remove_resource_cleanup_t *)message;\n            const char *channel = (const char *)message + sizeof(aeron_driver_agent_remove_resource_cleanup_t);\n            fprintf(\n                logfp,\n                \"%s: sessionId=%d streamId=%d id=%\" PRId64 \" channel=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                hdr->session_id,\n                hdr->stream_id,\n                hdr->id,\n                hdr->channel_length,\n                channel);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_SEND_CHANNEL_CREATION:\n        case AERON_DRIVER_EVENT_SEND_CHANNEL_CLOSE:\n        case AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CREATION:\n        case AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CLOSE:\n        {\n            aeron_driver_agent_on_endpoint_change_t *hdr = (aeron_driver_agent_on_endpoint_change_t *)message;\n            char local_addr_buf[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n            const int local_addr_length = aeron_format_source_identity(\n                local_addr_buf, sizeof(local_addr_buf), &hdr->local_data);\n            char remote_addr_buf[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n            const int remote_addr_length = aeron_format_source_identity(\n                remote_addr_buf, sizeof(remote_addr_buf), &hdr->remote_data);\n\n            fprintf(\n                logfp,\n                \"%s: localData: %.*s, remoteData: %.*s, ttl: %d\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                local_addr_length,\n                local_addr_buf,\n                remote_addr_length,\n                remote_addr_buf,\n                hdr->multicast_ttl);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_UNTETHERED_SUBSCRIPTION_STATE_CHANGE:\n        {\n            aeron_driver_agent_untethered_subscription_state_change_log_header_t *hdr =\n                (aeron_driver_agent_untethered_subscription_state_change_log_header_t *)message;\n\n            fprintf(\n                logfp,\n                \"%s: subscriptionId=%\" PRId64 \" streamId=%d sessionId=%d %s -> %s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                hdr->subscription_id,\n                hdr->stream_id,\n                hdr->session_id,\n                dissect_tether_state(hdr->old_state),\n                dissect_tether_state(hdr->new_state));\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_ADDED:\n        case AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_REMOVED:\n        {\n            aeron_driver_agent_log_header_t *hdr = (aeron_driver_agent_log_header_t *)message;\n            struct sockaddr_storage *addr =\n                (struct sockaddr_storage *)((const char *)message + sizeof(aeron_driver_agent_log_header_t));\n            char addr_buf[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n            const int addr_length = aeron_format_source_identity(addr_buf, sizeof(addr_buf), addr);\n\n            fprintf(\n                logfp,\n                \"%s: %.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                addr_length,\n                addr_buf);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_ADDED:\n        case AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_REMOVED:\n        {\n            aeron_driver_agent_flow_control_receiver_change_log_header_t *hdr = (aeron_driver_agent_flow_control_receiver_change_log_header_t *)message;\n            const char *channel = (const char *)message + sizeof(aeron_driver_agent_flow_control_receiver_change_log_header_t);\n            fprintf(\n                logfp,\n                \"%s: receiverCount=%d receiverId=%\" PRId64 \" sessionId=%d streamId=%d channel=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                hdr->receiver_count,\n                hdr->receiver_id,\n                hdr->session_id,\n                hdr->stream_id,\n                hdr->channel_length,\n                channel);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_NAK_SENT:\n        case AERON_DRIVER_EVENT_NAK_RECEIVED:\n        {\n            aeron_driver_agent_nak_message_header_t *hdr = (aeron_driver_agent_nak_message_header_t *)message;\n            char address_buf[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n            const int address_length = aeron_format_source_identity(address_buf, sizeof(address_buf), &hdr->address);\n            const char *channel = (const char *)message + sizeof(aeron_driver_agent_nak_message_header_t);\n\n            fprintf(\n                logfp,\n                \"%s: address=%.*s sessionId=%d streamId=%d termId=%d termOffset=%d length=%d channel=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                address_length,\n                address_buf,\n                hdr->session_id,\n                hdr->stream_id,\n                hdr->term_id,\n                hdr->term_offset,\n                hdr->nak_length,\n                hdr->channel_length,\n                channel);\n\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_RESEND:\n        {\n            aeron_driver_agent_resend_header_t *hdr = (aeron_driver_agent_resend_header_t *)message;\n            const char *channel = (const char *)message + sizeof(aeron_driver_agent_resend_header_t);\n\n            fprintf(\n                logfp,\n                \"%s: sessionId=%d streamId=%d termId=%d termOffset=%d length=%d channel=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                hdr->session_id,\n                hdr->stream_id,\n                hdr->term_id,\n                hdr->term_offset,\n                hdr->resend_length,\n                hdr->channel_length,\n                channel);\n\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_PUBLICATION_REVOKE:\n        {\n            aeron_driver_agent_publication_revoke_header_t *hdr = (aeron_driver_agent_publication_revoke_header_t *)message;\n            const char *channel = (const char *)message + sizeof(aeron_driver_agent_publication_revoke_header_t);\n\n            fprintf(\n                logfp,\n                \"%s: revokedPos=%\" PRIu64 \" sessionId=%d streamId=%d channel=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                hdr->revoked_pos,\n                hdr->session_id,\n                hdr->stream_id,\n                hdr->channel_length,\n                channel);\n\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_PUBLICATION_IMAGE_REVOKE:\n        {\n            aeron_driver_agent_publication_image_revoke_header_t *hdr = (aeron_driver_agent_publication_image_revoke_header_t *)message;\n            const char *channel = (const char *)message + sizeof(aeron_driver_agent_publication_image_revoke_header_t);\n\n            fprintf(\n                logfp,\n                \"%s: revokedPos=%\" PRIu64 \" sessionId=%d streamId=%d channel=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                hdr->revoked_pos,\n                hdr->session_id,\n                hdr->stream_id,\n                hdr->channel_length,\n                channel);\n\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_NAME_RESOLUTION_RESOLVE:\n        {\n            aeron_driver_agent_name_resolver_resolve_log_header_t *hdr =\n                (aeron_driver_agent_name_resolver_resolve_log_header_t *)message;\n            char *address_str = \"unknown-address\";\n            char address_buf[INET6_ADDRSTRLEN] = { 0 };\n            uint8_t *resolver_name_ptr =\n                (uint8_t *)message + sizeof(aeron_driver_agent_name_resolver_resolve_log_header_t);\n            uint8_t *hostname_ptr = resolver_name_ptr + hdr->resolver_name_length;\n            uint8_t *address_ptr = hostname_ptr + hdr->hostname_length;\n\n            const char *addr_prefix = \"\";\n            const char *addr_suffix = \"\";\n            if (sizeof(((struct sockaddr_in *)0)->sin_addr) == hdr->address_length)\n            {\n                inet_ntop(AF_INET, address_ptr, address_buf, sizeof(address_buf));\n                address_str = address_buf;\n            }\n            else if (sizeof(((struct sockaddr_in6 *)0)->sin6_addr) == hdr->address_length)\n            {\n                addr_prefix = \"[\";\n                addr_suffix = \"]\";\n\n                inet_ntop(AF_INET6, address_ptr, address_buf, sizeof(address_buf));\n                address_str = address_buf;\n            }\n\n            fprintf(\n                logfp,\n                \"%s: resolver=%.*s durationNs=%\" PRIu64 \" name=%.*s isReResolution=%s address=%s%s%s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                (int)hdr->resolver_name_length,\n                resolver_name_ptr,\n                (uint64_t)hdr->duration_ns,\n                (int)hdr->hostname_length,\n                hostname_ptr,\n                hdr->is_re_resolution ? \"true\" : \"false\",\n                addr_prefix,\n                address_str,\n                addr_suffix);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_NAME_RESOLUTION_LOOKUP:\n        {\n            aeron_driver_agent_name_resolver_lookup_log_header_t *hdr =\n                (aeron_driver_agent_name_resolver_lookup_log_header_t *)message;\n            uint8_t *resolver_name_ptr =\n                (uint8_t *)message + sizeof(aeron_driver_agent_name_resolver_lookup_log_header_t);\n            uint8_t *name_ptr = resolver_name_ptr + hdr->resolver_name_length;\n            uint8_t *resolved_name_ptr = name_ptr + hdr->name_length;\n\n            fprintf(\n                logfp,\n                \"%s: resolver=%.*s durationNs=%\" PRIu64 \" name=%.*s isReLookup=%s resolvedName=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                (int)hdr->resolver_name_length,\n                resolver_name_ptr,\n                (uint64_t)hdr->duration_ns,\n                (int)hdr->name_length,\n                name_ptr,\n                hdr->is_re_lookup ? \"true\" : \"false\",\n                (int)hdr->resolved_name_length,\n                resolved_name_ptr);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_NAME_RESOLUTION_HOST_NAME:\n        {\n            aeron_driver_agent_name_resolver_host_name_log_header_t *hdr =\n                (aeron_driver_agent_name_resolver_host_name_log_header_t *)message;\n            uint8_t *host_name_name_ptr =\n                (uint8_t *)message + sizeof(aeron_driver_agent_name_resolver_host_name_log_header_t);\n\n            fprintf(\n                logfp,\n                \"%s: durationNs=%\" PRIu64 \" hostName=%.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                (uint64_t)hdr->duration_ns,\n                (int)hdr->host_name_length,\n                host_name_name_ptr);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_NAME_TEXT_DATA:\n        {\n            aeron_driver_agent_text_data_log_header_t *hdr =\n                (aeron_driver_agent_text_data_log_header_t *)message;\n            uint8_t *message_ptr =\n                (uint8_t *)message + sizeof(aeron_driver_agent_text_data_log_header_t);\n\n            fprintf(\n                logfp,\n                \"%s: %.*s\\n\",\n                aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                hdr->message_length,\n                message_ptr);\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR:\n        {\n            aeron_driver_agent_add_dissector_header_t *hdr = (aeron_driver_agent_add_dissector_header_t *)message;\n\n            if (aeron_array_ensure_capacity(\n                (uint8_t **)&dynamic_dissector_entries,\n                sizeof(aeron_driver_agent_dynamic_dissector_entry_t),\n                num_dynamic_dissector_entries,\n                hdr->index + 1) >= 0)\n            {\n                dynamic_dissector_entries[hdr->index].dissector_func = hdr->dissector_func;\n                num_dynamic_dissector_entries = hdr->index + 1;\n            }\n            break;\n        }\n\n        case AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT:\n        {\n            aeron_driver_agent_dynamic_event_header_t *hdr = (aeron_driver_agent_dynamic_event_header_t *)message;\n\n            if (hdr->index >= 0 && hdr->index < (int64_t)num_dynamic_dissector_entries &&\n                NULL != dynamic_dissector_entries[hdr->index].dissector_func)\n            {\n                dynamic_dissector_entries[hdr->index].dissector_func(\n                    logfp,\n                    aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                    message,\n                    length);\n            }\n            break;\n        }\n\n        default:\n            if (is_valid_event_id(msg_type_id))\n            {\n                const uint8_t event_type = log_events[msg_type_id].type;\n                if (AERON_DRIVER_AGENT_EVENT_TYPE_CMD_IN == event_type)\n                {\n                    aeron_driver_agent_cmd_log_header_t *hdr = (aeron_driver_agent_cmd_log_header_t *)message;\n\n                    fprintf(\n                        logfp,\n                        \"%s: %s\\n\",\n                        aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                        dissect_cmd_in(\n                            hdr->cmd_id,\n                            (const char *)message + sizeof(aeron_driver_agent_cmd_log_header_t),\n                            length - sizeof(aeron_driver_agent_cmd_log_header_t)));\n                }\n                else if (AERON_DRIVER_AGENT_EVENT_TYPE_CMD_OUT == event_type)\n                {\n                    aeron_driver_agent_cmd_log_header_t *hdr = (aeron_driver_agent_cmd_log_header_t *)message;\n\n                    fprintf(\n                        logfp,\n                        \"%s: %s\\n\",\n                        aeron_driver_agent_dissect_log_header(hdr->time_ns, msg_type_id, length, length),\n                        dissect_cmd_out(\n                            hdr->cmd_id,\n                            (const char *)message + sizeof(aeron_driver_agent_cmd_log_header_t),\n                            length - sizeof(aeron_driver_agent_cmd_log_header_t)));\n                }\n            }\n            break;\n    }\n}\n\nstatic void log_remove_resource_cleanup(\n    const int64_t id,\n    const int32_t session_id,\n    const int32_t stream_id,\n    const size_t channel_length,\n    const char *channel,\n    const aeron_driver_agent_event_t event_id)\n{\n    const size_t command_length = sizeof(aeron_driver_agent_remove_resource_cleanup_t) + channel_length;\n    int32_t offset = aeron_mpsc_rb_try_claim(&logging_mpsc_rb, event_id, command_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_remove_resource_cleanup_t *hdr = (aeron_driver_agent_remove_resource_cleanup_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->id = id;\n        hdr->stream_id = stream_id;\n        hdr->session_id = session_id;\n        hdr->channel_length = (int32_t)channel_length;\n\n        memcpy(ptr + sizeof(aeron_driver_agent_remove_resource_cleanup_t), channel, channel_length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_remove_publication_cleanup(\n    int32_t session_id, int32_t stream_id, size_t channel_length, const char *channel)\n{\n    log_remove_resource_cleanup(\n        AERON_NULL_VALUE,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        AERON_DRIVER_EVENT_REMOVE_PUBLICATION_CLEANUP);\n}\n\nvoid aeron_driver_agent_remove_subscription_cleanup(\n    int64_t id, int32_t stream_id, size_t channel_length, const char *channel)\n{\n    log_remove_resource_cleanup(\n        id,\n        AERON_NULL_VALUE,\n        stream_id,\n        channel_length,\n        channel,\n        AERON_DRIVER_EVENT_REMOVE_SUBSCRIPTION_CLEANUP);\n}\n\nvoid aeron_driver_agent_remove_image_cleanup(\n    int64_t id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel)\n{\n    log_remove_resource_cleanup(\n        id,\n        session_id,\n        stream_id,\n        channel_length,\n        channel,\n        AERON_DRIVER_EVENT_REMOVE_IMAGE_CLEANUP);\n}\n\nstatic void log_endpoint_change_event(const aeron_driver_agent_event_t event_id, const void *channel)\n{\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb, event_id, sizeof(aeron_driver_agent_on_endpoint_change_t));\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_on_endpoint_change_t *hdr = (aeron_driver_agent_on_endpoint_change_t *)ptr;\n\n        const aeron_udp_channel_t *udp_channel = channel;\n\n        hdr->time_ns = aeron_nano_clock();\n        memcpy(&hdr->local_data, &udp_channel->local_data, AERON_ADDR_LEN(&udp_channel->local_data));\n        memcpy(&hdr->remote_data, &udp_channel->remote_data, AERON_ADDR_LEN(&udp_channel->remote_data));\n        hdr->multicast_ttl = udp_channel->multicast_ttl;\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n\nvoid aeron_driver_agent_sender_proxy_on_add_endpoint(const void *channel)\n{\n    log_endpoint_change_event(AERON_DRIVER_EVENT_SEND_CHANNEL_CREATION, channel);\n}\n\nvoid aeron_driver_agent_sender_proxy_on_remove_endpoint(const void *channel)\n{\n    log_endpoint_change_event(AERON_DRIVER_EVENT_SEND_CHANNEL_CLOSE, channel);\n}\n\nvoid aeron_driver_agent_receiver_proxy_on_add_endpoint(const void *channel)\n{\n    log_endpoint_change_event(AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CREATION, channel);\n}\n\nvoid aeron_driver_agent_receiver_proxy_on_remove_endpoint(const void *channel)\n{\n    log_endpoint_change_event(AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CLOSE, channel);\n}\n\nint64_t aeron_driver_agent_add_dynamic_dissector(aeron_driver_agent_generic_dissector_func_t func)\n{\n    if (!aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT))\n    {\n        return -1;\n    }\n\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR,\n        sizeof(aeron_driver_agent_add_dissector_header_t));\n\n    if (offset > 0)\n    {\n\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n\n        aeron_driver_agent_add_dissector_header_t *hdr =\n            (aeron_driver_agent_add_dissector_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        AERON_GET_AND_ADD_INT64(hdr->index, dynamic_dissector_index, INT64_C(1));\n        hdr->dissector_func = func;\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n        return hdr->index;\n    }\n\n    return -1;\n}\n\nvoid aeron_driver_agent_log_dynamic_event(int64_t index, const void *message, size_t length)\n{\n    if (!aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT))\n    {\n        return;\n    }\n\n    const size_t copy_length = length < AERON_MAX_FRAME_LENGTH ? length : AERON_MAX_FRAME_LENGTH;\n    int32_t offset = aeron_mpsc_rb_try_claim(\n        &logging_mpsc_rb,\n        AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT,\n        sizeof(aeron_driver_agent_dynamic_event_header_t) + copy_length);\n\n    if (offset > 0)\n    {\n        uint8_t *ptr = (logging_mpsc_rb.buffer + offset);\n        aeron_driver_agent_dynamic_event_header_t *hdr = (aeron_driver_agent_dynamic_event_header_t *)ptr;\n\n        hdr->time_ns = aeron_nano_clock();\n        hdr->index = index;\n        memcpy(ptr + sizeof(aeron_driver_agent_dynamic_event_header_t), message, copy_length);\n\n        aeron_mpsc_rb_commit(&logging_mpsc_rb, offset);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/agent/aeron_driver_agent.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_AGENT_H\n#define AERON_DRIVER_AGENT_H\n\n#include \"aeron_driver_conductor.h\"\n#include \"command/aeron_control_protocol.h\"\n\n#define AERON_EVENT_LOG_ENV_VAR \"AERON_EVENT_LOG\"\n#define AERON_EVENT_LOG_DISABLE_ENV_VAR \"AERON_EVENT_LOG_DISABLE\"\n#define AERON_EVENT_LOG_FILENAME_ENV_VAR \"AERON_EVENT_LOG_FILENAME\"\n#define AERON_EVENT_RB_LENGTH (8 * 1024 * 1024)\n#define AERON_MAX_FRAME_LENGTH (AERON_MAX_UDP_PAYLOAD_LENGTH)\n\n#define AERON_DRIVER_AGENT_LOG_CONTEXT \"DRIVER\"\n#define AERON_DRIVER_AGENT_ALL_EVENTS \"all\"\n#define AERON_DRIVER_AGENT_ADMIN_EVENTS \"admin\"\n#define AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME \"unknown\"\n\ntypedef enum aeron_driver_agent_event_enum\n{\n    AERON_DRIVER_EVENT_UNKNOWN_EVENT = -1,\n    AERON_DRIVER_EVENT_FRAME_IN = 1,\n    AERON_DRIVER_EVENT_FRAME_OUT = 2,\n    AERON_DRIVER_EVENT_CMD_IN_ADD_PUBLICATION = 3,\n    AERON_DRIVER_EVENT_CMD_IN_REMOVE_PUBLICATION = 4,\n    AERON_DRIVER_EVENT_CMD_IN_ADD_SUBSCRIPTION = 5,\n    AERON_DRIVER_EVENT_CMD_IN_REMOVE_SUBSCRIPTION = 6,\n    AERON_DRIVER_EVENT_CMD_OUT_PUBLICATION_READY = 7,\n    AERON_DRIVER_EVENT_CMD_OUT_AVAILABLE_IMAGE = 8,\n    AERON_DRIVER_EVENT_CMD_OUT_ON_OPERATION_SUCCESS = 12,\n    AERON_DRIVER_EVENT_CMD_IN_KEEPALIVE_CLIENT = 13,\n    AERON_DRIVER_EVENT_REMOVE_PUBLICATION_CLEANUP = 14,\n    AERON_DRIVER_EVENT_REMOVE_SUBSCRIPTION_CLEANUP = 15,\n    AERON_DRIVER_EVENT_REMOVE_IMAGE_CLEANUP = 16,\n    AERON_DRIVER_EVENT_CMD_OUT_ON_UNAVAILABLE_IMAGE = 17,\n    AERON_DRIVER_EVENT_SEND_CHANNEL_CREATION = 23,\n    AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CREATION = 24,\n    AERON_DRIVER_EVENT_SEND_CHANNEL_CLOSE = 25,\n    AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CLOSE = 26,\n    AERON_DRIVER_EVENT_CMD_IN_ADD_DESTINATION = 30,\n    AERON_DRIVER_EVENT_CMD_IN_REMOVE_DESTINATION = 31,\n    AERON_DRIVER_EVENT_CMD_IN_ADD_EXCLUSIVE_PUBLICATION = 32,\n    AERON_DRIVER_EVENT_CMD_OUT_EXCLUSIVE_PUBLICATION_READY = 33,\n    AERON_DRIVER_EVENT_CMD_OUT_ERROR = 34,\n    AERON_DRIVER_EVENT_CMD_IN_ADD_COUNTER = 35,\n    AERON_DRIVER_EVENT_CMD_IN_REMOVE_COUNTER = 36,\n    AERON_DRIVER_EVENT_CMD_OUT_SUBSCRIPTION_READY = 37,\n    AERON_DRIVER_EVENT_CMD_OUT_COUNTER_READY = 38,\n    AERON_DRIVER_EVENT_CMD_OUT_ON_UNAVAILABLE_COUNTER = 39,\n    AERON_DRIVER_EVENT_CMD_IN_CLIENT_CLOSE = 40,\n    AERON_DRIVER_EVENT_CMD_IN_ADD_RCV_DESTINATION = 41,\n    AERON_DRIVER_EVENT_CMD_IN_REMOVE_RCV_DESTINATION = 42,\n    AERON_DRIVER_EVENT_CMD_OUT_ON_CLIENT_TIMEOUT = 43,\n    AERON_DRIVER_EVENT_CMD_IN_TERMINATE_DRIVER = 44,\n    AERON_DRIVER_EVENT_UNTETHERED_SUBSCRIPTION_STATE_CHANGE = 45,\n    AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_ADDED = 46,\n    AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_REMOVED = 47,\n    AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_ADDED = 48,\n    AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_REMOVED = 49,\n    AERON_DRIVER_EVENT_NAME_RESOLUTION_RESOLVE = 50,\n    AERON_DRIVER_EVENT_NAME_TEXT_DATA = 51,\n    AERON_DRIVER_EVENT_NAME_RESOLUTION_LOOKUP = 52,\n    AERON_DRIVER_EVENT_NAME_RESOLUTION_HOST_NAME = 53,\n    AERON_DRIVER_EVENT_NAK_SENT = 54,\n    AERON_DRIVER_EVENT_RESEND = 55,\n    AERON_DRIVER_EVENT_CMD_IN_REMOVE_DESTINATION_BY_ID = 56,\n    AERON_DRIVER_EVENT_CMD_IN_REJECT_IMAGE = 57,\n    AERON_DRIVER_EVENT_NAK_RECEIVED = 58,\n    AERON_DRIVER_EVENT_PUBLICATION_REVOKE = 59,\n    AERON_DRIVER_EVENT_PUBLICATION_IMAGE_REVOKE = 60,\n\n    // C-specific events. Note: event IDs are dynamic to avoid gaps in the sparse arrays.\n    AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR = 61,\n    AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT = 62,\n}\naeron_driver_agent_event_t;\n\ntypedef struct aeron_driver_agent_log_header_stct\n{\n    int64_t time_ns;\n}\naeron_driver_agent_log_header_t;\n\ntypedef struct aeron_driver_agent_cmd_log_header_stct\n{\n    int64_t time_ns;\n    int64_t cmd_id;\n}\naeron_driver_agent_cmd_log_header_t;\n\ntypedef struct aeron_driver_agent_frame_log_header_stct\n{\n    int64_t time_ns;\n    int32_t sockaddr_len;\n    int32_t message_len;\n}\naeron_driver_agent_frame_log_header_t;\n\ntypedef struct aeron_driver_agent_untethered_subscription_state_change_log_header_stct\n{\n    int64_t time_ns;\n    int64_t subscription_id;\n    int32_t stream_id;\n    int32_t session_id;\n    aeron_subscription_tether_state_t old_state;\n    aeron_subscription_tether_state_t new_state;\n}\naeron_driver_agent_untethered_subscription_state_change_log_header_t;\n\ntypedef struct aeron_driver_agent_remove_resource_cleanup_stct\n{\n    int64_t time_ns;\n    int64_t id;\n    int32_t stream_id;\n    int32_t session_id;\n    int32_t channel_length;\n}\naeron_driver_agent_remove_resource_cleanup_t;\n\ntypedef struct aeron_driver_agent_on_endpoint_change_stct\n{\n    int64_t time_ns;\n    struct sockaddr_storage local_data;\n    struct sockaddr_storage remote_data;\n    uint8_t multicast_ttl;\n}\naeron_driver_agent_on_endpoint_change_t;\n\ntypedef void (*aeron_driver_agent_generic_dissector_func_t)(\n    FILE *fpout, const char *log_header_str, const void *message, size_t len);\n\ntypedef struct aeron_driver_agent_add_dissector_header_stct\n{\n    int64_t time_ns;\n    int64_t index;\n    aeron_driver_agent_generic_dissector_func_t dissector_func;\n}\naeron_driver_agent_add_dissector_header_t;\n\ntypedef struct aeron_driver_agent_dynamic_event_header_stct\n{\n    int64_t time_ns;\n    int64_t index;\n}\naeron_driver_agent_dynamic_event_header_t;\n\ntypedef struct aeron_driver_agent_flow_control_receiver_change_log_header_stct\n{\n    int64_t time_ns;\n    int64_t receiver_id;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t channel_length;\n    int32_t receiver_count;\n}\naeron_driver_agent_flow_control_receiver_change_log_header_t;\n\ntypedef struct aeron_driver_agent_nak_message_header_stct\n{\n    int64_t time_ns;\n    struct sockaddr_storage address;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t term_id;\n    int32_t term_offset;\n    int32_t nak_length;\n    int32_t channel_length;\n}\naeron_driver_agent_nak_message_header_t;\n\ntypedef struct aeron_driver_agent_resend_header_stct\n{\n    int64_t time_ns;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t term_id;\n    int32_t term_offset;\n    int32_t resend_length;\n    int32_t channel_length;\n}\naeron_driver_agent_resend_header_t;\n\ntypedef struct aeron_driver_agent_publication_revoke_header_stct\n{\n    int64_t time_ns;\n    int64_t revoked_pos;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t channel_length;\n}\naeron_driver_agent_publication_revoke_header_t;\n\ntypedef struct aeron_driver_agent_publication_image_revoke_header_stct\n{\n    int64_t time_ns;\n    int64_t revoked_pos;\n    int32_t session_id;\n    int32_t stream_id;\n    int32_t channel_length;\n}\naeron_driver_agent_publication_image_revoke_header_t;\n\ntypedef struct aeron_driver_agent_name_resolver_resolve_log_header_stct\n{\n    int64_t time_ns;\n    int64_t duration_ns;\n    int32_t resolver_name_length;\n    int32_t hostname_length;\n    int32_t address_length;\n    bool is_re_resolution;\n}\naeron_driver_agent_name_resolver_resolve_log_header_t;\n\ntypedef struct aeron_driver_agent_name_resolver_lookup_log_header_stct\n{\n    int64_t time_ns;\n    int64_t duration_ns;\n    int32_t resolver_name_length;\n    int32_t name_length;\n    int32_t resolved_name_length;\n    bool is_re_lookup;\n}\naeron_driver_agent_name_resolver_lookup_log_header_t;\n\ntypedef struct aeron_driver_agent_name_resolver_host_name_log_header_stct\n{\n    int64_t time_ns;\n    int64_t duration_ns;\n    int32_t host_name_length;\n}\naeron_driver_agent_name_resolver_host_name_log_header_t;\n\ntypedef struct aeron_driver_agent_text_data_log_header_stct\n{\n    int64_t time_ns;\n    int32_t message_length;\n}\naeron_driver_agent_text_data_log_header_t;\n\naeron_mpsc_rb_t *aeron_driver_agent_mpsc_rb(void);\n\ntypedef int (*aeron_driver_context_init_t)(aeron_driver_context_t **);\n\nint aeron_driver_agent_context_init(aeron_driver_context_t *context);\n\nsize_t aeron_driver_agent_max_event_count(void);\n\nconst char *aeron_driver_agent_dissect_log_header(\n    int64_t time_ns,\n    aeron_driver_agent_event_t event_id,\n    size_t capture_length,\n    size_t message_length);\n\nconst char *aeron_driver_agent_dissect_log_start(int64_t time_ns, int64_t time_ms);\n\nconst char *aeron_driver_agent_dissect_frame(const void *message, size_t length);\n\nvoid aeron_driver_agent_log_dissector(int32_t msg_type_id, const void *message, size_t length, void *clientd);\n\nint aeron_driver_agent_init_logging_events_interceptors(aeron_driver_context_t *context);\n\nvoid aeron_driver_agent_logging_ring_buffer_init(void);\n\nvoid aeron_driver_agent_logging_ring_buffer_free(void);\n\nbool aeron_driver_agent_logging_events_init(const char *event_log, const char *event_log_disable);\n\nvoid aeron_driver_agent_logging_events_free(void);\n\nbool aeron_driver_agent_is_event_enabled(aeron_driver_agent_event_t id);\n\nconst char *aeron_driver_agent_event_name(aeron_driver_agent_event_t id);\n\nvoid aeron_driver_agent_untethered_subscription_state_change(\n    aeron_tetherable_position_t *tetherable_position,\n    int64_t now_ns,\n    aeron_subscription_tether_state_t old_state,\n    aeron_subscription_tether_state_t new_state,\n    int32_t stream_id,\n    int32_t session_id);\n\nvoid aeron_driver_agent_name_resolution_on_neighbor_added(const struct sockaddr_storage *addr);\n\nvoid aeron_driver_agent_name_resolution_on_neighbor_removed(const struct sockaddr_storage *addr);\n\nvoid aeron_driver_agent_remove_publication_cleanup(\n    int32_t session_id, int32_t stream_id, size_t channel_length, const char *channel);\n\nvoid aeron_driver_agent_remove_subscription_cleanup(\n    int64_t id, int32_t stream_id, size_t channel_length, const char *channel);\n\nvoid aeron_driver_agent_remove_image_cleanup(\n    int64_t id, int32_t session_id, int32_t stream_id, size_t channel_length, const char *channel);\n\nvoid aeron_driver_agent_conductor_to_driver_interceptor(\n    int32_t msg_type_id, const void *message, size_t length, void *clientd);\n\nvoid aeron_driver_agent_conductor_to_client_interceptor(\n    aeron_driver_conductor_t *conductor, int32_t msg_type_id, const void *message, size_t length);\n\nvoid aeron_driver_agent_log_frame(int32_t msg_type_id, const struct msghdr *msghdr, int32_t message_len);\n\nvoid aeron_driver_agent_sender_proxy_on_add_endpoint(const void *channel);\n\nvoid aeron_driver_agent_sender_proxy_on_remove_endpoint(const void *channel);\n\nvoid aeron_driver_agent_receiver_proxy_on_add_endpoint(const void *channel);\n\nvoid aeron_driver_agent_receiver_proxy_on_remove_endpoint(const void *channel);\n\nint64_t aeron_driver_agent_add_dynamic_dissector(aeron_driver_agent_generic_dissector_func_t func);\n\nvoid aeron_driver_agent_log_dynamic_event(int64_t index, const void *message, size_t length);\n\nvoid aeron_driver_agent_flow_control_on_receiver_added(\n    int64_t receiver_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    size_t receiver_count);\n\nvoid aeron_driver_agent_flow_control_on_receiver_removed(\n    int64_t receiver_id,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel,\n    size_t receiver_count);\n\nvoid aeron_driver_agent_send_nak_message(\n    const struct sockaddr_storage *address,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t nak_length,\n    size_t channel_length,\n    const char *channel);\n\nvoid aeron_driver_agent_on_nak_message(\n    const struct sockaddr_storage *address,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t nak_length,\n    size_t channel_length,\n    const char *channel);\n\nvoid aeron_driver_agent_resend(\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t resend_length,\n    size_t channel_length,\n    const char *channel);\n\nvoid aeron_driver_agent_publication_revoke(\n    int64_t revoked_pos,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\nvoid aeron_driver_agent_publication_image_revoke(\n    int64_t revoked_pos,\n    int32_t session_id,\n    int32_t stream_id,\n    size_t channel_length,\n    const char *channel);\n\n#endif //AERON_DRIVER_AGENT_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/concurrent/aeron_logbuffer_unblocker.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"concurrent/aeron_logbuffer_unblocker.h\"\n\nbool aeron_logbuffer_unblocker_unblock(\n    aeron_mapped_buffer_t *term_buffers, aeron_logbuffer_metadata_t *log_meta_data, int64_t blocked_position)\n{\n    const size_t term_length = term_buffers[0].length;\n    const size_t position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes((int32_t)term_length);\n    const int32_t blocked_term_count = (int32_t)(blocked_position >> position_bits_to_shift);\n    const size_t blocked_index = aeron_logbuffer_index_by_term_count(blocked_term_count);\n\n    int32_t active_term_count;\n    AERON_GET_ACQUIRE(active_term_count, log_meta_data->active_term_count);\n\n    int64_t raw_tail;\n    AERON_GET_ACQUIRE(raw_tail, log_meta_data->term_tail_counters[blocked_index]);\n\n    const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n    const int32_t tail_offset = aeron_logbuffer_term_offset(raw_tail, (int32_t)term_length);\n    const int32_t blocked_offset = aeron_logbuffer_compute_term_offset_from_position(\n        blocked_position, position_bits_to_shift);\n\n    if (active_term_count == (blocked_term_count - 1) && 0 == blocked_offset)\n    {\n        int64_t current_raw_tail;\n        AERON_GET_ACQUIRE(\n            current_raw_tail,\n            log_meta_data->term_tail_counters[aeron_logbuffer_index_by_term_count(active_term_count)]);\n\n        const int32_t current_term_id = aeron_logbuffer_term_id(current_raw_tail);\n        aeron_logbuffer_rotate_log(log_meta_data, active_term_count, current_term_id);\n\n        return true;\n    }\n\n    switch (aeron_term_unblocker_unblock(\n        log_meta_data, term_buffers[blocked_index].addr, term_length, blocked_offset, tail_offset, term_id))\n    {\n        case AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED_TO_END:\n            aeron_logbuffer_rotate_log(log_meta_data, blocked_term_count, term_id);\n            return true;\n\n        case AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED:\n            return true;\n\n        case AERON_TERM_UNBLOCKER_STATUS_NO_ACTION:\n            break;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/concurrent/aeron_logbuffer_unblocker.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_LOG_BUFFER_UNBLOCK_H\n#define AERON_LOG_BUFFER_UNBLOCK_H\n\n#include \"util/aeron_fileutil.h\"\n#include \"concurrent/aeron_term_unblocker.h\"\n\nbool aeron_logbuffer_unblocker_unblock(\n    aeron_mapped_buffer_t *term_buffers, aeron_logbuffer_metadata_t *log_meta_data, int64_t blocked_position);\n\n#endif //AERON_LOG_BUFFER_UNBLOCK_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <inttypes.h>\n\n#include \"aeron_driver_context.h\"\n#include \"aeron_socket.h\"\n#include \"aeron_system_counters.h\"\n#include \"util/aeron_netutil.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"aeron_alloc.h\"\n#include \"collections/aeron_int64_to_ptr_hash_map.h\"\n#include \"media/aeron_receive_channel_endpoint.h\"\n#include \"aeron_driver_receiver.h\"\n#include \"aeron_timestamps.h\"\n\nint aeron_receive_channel_endpoint_set_group_tag(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel,\n    aeron_driver_context_t *context)\n{\n    int64_t group_tag = 0;\n    int rc = aeron_uri_get_int64(&channel->uri.params.udp.additional_params, AERON_URI_GTAG_KEY, 0, &group_tag);\n    if (rc < 0)\n    {\n        return -1;\n    }\n\n    if (0 == rc)\n    {\n        endpoint->group_tag.is_present = context->receiver_group_tag.is_present;\n        endpoint->group_tag.value = context->receiver_group_tag.value;\n    }\n    else\n    {\n        endpoint->group_tag.is_present = true;\n        endpoint->group_tag.value = group_tag;\n    }\n\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_create(\n    aeron_receive_channel_endpoint_t **endpoint,\n    aeron_udp_channel_t *channel,\n    aeron_receive_destination_t *straight_through_destination,\n    aeron_atomic_counter_t *status_indicator,\n    aeron_system_counters_t *system_counters,\n    aeron_driver_context_t *context)\n{\n    aeron_receive_channel_endpoint_t *_endpoint = NULL;\n\n    if (aeron_alloc((void **)&_endpoint, sizeof(aeron_receive_channel_endpoint_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"could not allocate receive_channel_endpoint\");\n        return -1;\n    }\n\n    if (aeron_data_packet_dispatcher_init(\n        &_endpoint->dispatcher, context->conductor_proxy, context->receiver_proxy->receiver) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to initialise data packet dispatcher\");\n        return -1;\n    }\n\n    if (aeron_int64_counter_map_init(\n        &_endpoint->stream_id_to_refcnt_map, 0, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"could not init stream_id_to_refcnt_map\");\n        return -1;\n    }\n\n    if (aeron_int64_counter_map_init(\n        &_endpoint->stream_and_session_id_to_refcnt_map, 0, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"could not init stream_and_session_id_to_refcnt_map\");\n        return -1;\n    }\n\n    if (aeron_int64_counter_map_init(\n        &_endpoint->response_stream_id_to_refcnt_map, 0, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"could not init response_stream_id_to_refcnt_map\");\n        return -1;\n    }\n\n    _endpoint->conductor_fields.managed_resource.clientd = _endpoint;\n    _endpoint->conductor_fields.managed_resource.registration_id = -1;\n    _endpoint->conductor_fields.status = AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_ACTIVE;\n    _endpoint->conductor_fields.image_ref_count = 0;\n    _endpoint->channel_status.counter_id = -1;\n    _endpoint->transport_bindings = context->udp_channel_transport_bindings;\n\n    _endpoint->has_receiver_released = false;\n\n    _endpoint->channel_status.counter_id = status_indicator->counter_id;\n    _endpoint->channel_status.value_addr = status_indicator->value_addr;\n\n    _endpoint->receiver_id = context->next_receiver_id++;\n    _endpoint->receiver_proxy = context->receiver_proxy;\n\n    if (aeron_receive_channel_endpoint_set_group_tag(_endpoint, channel, context) < 0)\n    {\n        aeron_receive_channel_endpoint_delete(NULL, _endpoint);\n        return -1;\n    }\n\n    _endpoint->short_sends_counter = aeron_system_counter_addr(system_counters, AERON_SYSTEM_COUNTER_SHORT_SENDS);\n    _endpoint->possible_ttl_asymmetry_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_POSSIBLE_TTL_ASYMMETRY);\n    _endpoint->errors_frames_sent_counter = aeron_system_counter_addr(\n        system_counters, AERON_SYSTEM_COUNTER_ERROR_FRAMES_SENT);\n\n    _endpoint->cached_clock = context->receiver_cached_clock;\n\n    _endpoint->send_nak_message = context->log.send_nak_message;\n\n    if (NULL != straight_through_destination)\n    {\n        if (aeron_receive_channel_endpoint_add_destination(_endpoint, straight_through_destination) < 0)\n        {\n            return -1;\n        }\n    }\n\n    // Only take ownership on successful construction.\n    _endpoint->conductor_fields.udp_channel = channel;\n    *endpoint = _endpoint;\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_delete(\n    aeron_counters_manager_t *counters_manager, aeron_receive_channel_endpoint_t *endpoint)\n{\n    if (NULL != counters_manager && -1 != endpoint->channel_status.counter_id)\n    {\n        aeron_counters_manager_free(counters_manager, endpoint->channel_status.counter_id);\n    }\n\n    aeron_int64_counter_map_delete(&endpoint->stream_id_to_refcnt_map);\n    aeron_int64_counter_map_delete(&endpoint->stream_and_session_id_to_refcnt_map);\n    aeron_int64_counter_map_delete(&endpoint->response_stream_id_to_refcnt_map);\n    aeron_data_packet_dispatcher_close(&endpoint->dispatcher);\n    bool delete_this_channel = false;\n\n    for (size_t i = 0, len = endpoint->destinations.length; i < len; i++)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n\n        if (endpoint->conductor_fields.status != AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_CLOSED)\n        {\n            endpoint->transport_bindings->close_func(&destination->transport);\n        }\n\n        // The endpoint will be deleted by the destination, for simple endpoints, i.e. non-mds the channel is shared.\n        delete_this_channel |= destination->conductor_fields.udp_channel == endpoint->conductor_fields.udp_channel;\n        aeron_receive_destination_delete(destination, counters_manager);\n    }\n\n    if (!delete_this_channel)\n    {\n        aeron_udp_channel_delete(endpoint->conductor_fields.udp_channel);\n    }\n\n    aeron_free(endpoint->destinations.array);\n    aeron_free(endpoint);\n\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_close(aeron_receive_channel_endpoint_t *endpoint)\n{\n    for (size_t i = 0, len = endpoint->destinations.length; i < len; i++)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n        endpoint->transport_bindings->close_func(&destination->transport);\n    }\n\n    endpoint->conductor_fields.status = AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_CLOSED;\n\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_send(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *address,\n    struct iovec *iov)\n{\n    int64_t min_bytes_sent = (int64_t)iov->iov_len;\n    int64_t bytes_sent = 0;\n\n    const int sendmsg_result = destination->data_paths->send_func(\n        destination->data_paths, &destination->transport, address, iov, 1, &bytes_sent);\n\n    if (0 <= sendmsg_result)\n    {\n        min_bytes_sent = bytes_sent < min_bytes_sent ? bytes_sent : min_bytes_sent;\n    }\n    else\n    {\n        min_bytes_sent = sendmsg_result;\n    }\n\n    return (int)min_bytes_sent;\n}\n\nint aeron_receive_channel_endpoint_elicit_setup(\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    int work_count = 0;\n    for (size_t i = 0, len = endpoint->destinations.length; i < len; i++)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n        int result = aeron_receive_channel_endpoint_send_sm(\n            endpoint,\n            destination,\n            &destination->current_control_addr,\n            stream_id,\n            session_id,\n            0,\n            0,\n            0,\n            AERON_STATUS_MESSAGE_HEADER_SEND_SETUP_FLAG);\n\n        if (result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        result += work_count;\n    }\n\n    return work_count;\n}\n\nint aeron_receive_channel_endpoint_send_sm(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *control_addr,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t receiver_window,\n    uint8_t flags)\n{\n    uint8_t buffer[sizeof(aeron_status_message_header_t) + sizeof(aeron_status_message_optional_header_t)];\n    aeron_status_message_header_t *sm_header = (aeron_status_message_header_t *)buffer;\n    aeron_status_message_optional_header_t *sm_optional_header =\n        (aeron_status_message_optional_header_t *)(buffer + sizeof(aeron_status_message_header_t));\n\n    struct iovec iov;\n\n    const int32_t frame_length = endpoint->group_tag.is_present ?\n        sizeof(aeron_status_message_header_t) + sizeof(aeron_status_message_optional_header_t) :\n        sizeof(aeron_status_message_header_t);\n\n    sm_header->frame_header.frame_length = frame_length;\n    sm_header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    sm_header->frame_header.flags = flags;\n    sm_header->frame_header.type = AERON_HDR_TYPE_SM;\n    sm_header->session_id = session_id;\n    sm_header->stream_id = stream_id;\n    sm_header->consumption_term_id = term_id;\n    sm_header->consumption_term_offset = term_offset;\n    sm_header->receiver_window = receiver_window;\n    sm_header->receiver_id = endpoint->receiver_id;\n    sm_optional_header->group_tag = endpoint->group_tag.value;\n\n    iov.iov_base = buffer;\n    iov.iov_len = (size_t)frame_length;\n\n    int bytes_sent = aeron_receive_channel_endpoint_send(endpoint, destination, control_addr, &iov);\n    if (bytes_sent != (int)iov.iov_len)\n    {\n        if (bytes_sent >= 0)\n        {\n            aeron_counter_increment(endpoint->short_sends_counter);\n        }\n    }\n\n    return bytes_sent;\n}\n\nint aeron_receive_channel_endpoint_send_nak(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *addr,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t length)\n{\n    uint8_t buffer[sizeof(aeron_nak_header_t)];\n    aeron_nak_header_t *nak_header = (aeron_nak_header_t *)buffer;\n    struct iovec iov;\n\n    nak_header->frame_header.frame_length = sizeof(aeron_nak_header_t);\n    nak_header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    nak_header->frame_header.flags = 0;\n    nak_header->frame_header.type = AERON_HDR_TYPE_NAK;\n    nak_header->session_id = session_id;\n    nak_header->stream_id = stream_id;\n    nak_header->term_id = term_id;\n    nak_header->term_offset = term_offset;\n    nak_header->length = length;\n\n    iov.iov_base = buffer;\n    iov.iov_len = sizeof(aeron_nak_header_t);\n\n    int bytes_sent = aeron_receive_channel_endpoint_send(endpoint, destination, addr, &iov);\n    if (bytes_sent != (int)iov.iov_len)\n    {\n        if (bytes_sent >= 0)\n        {\n            aeron_counter_increment(endpoint->short_sends_counter);\n        }\n    }\n\n    aeron_driver_nak_message_func_t send_nak_message = endpoint->send_nak_message;\n    if (NULL != send_nak_message)\n    {\n        send_nak_message(\n            addr,\n            session_id,\n            stream_id,\n            term_id,\n            term_offset,\n            length,\n            endpoint->conductor_fields.udp_channel->uri_length,\n            endpoint->conductor_fields.udp_channel->original_uri);\n    }\n\n    return bytes_sent;\n}\n\nint aeron_receive_channel_endpoint_send_rttm(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *addr,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t echo_timestamp,\n    int64_t reception_delta,\n    bool is_reply)\n{\n    uint8_t buffer[sizeof(aeron_rttm_header_t)];\n    aeron_rttm_header_t *rttm_header = (aeron_rttm_header_t *)buffer;\n    struct iovec iov;\n\n    rttm_header->frame_header.frame_length = sizeof(aeron_rttm_header_t);\n    rttm_header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    rttm_header->frame_header.flags = is_reply ? AERON_RTTM_HEADER_REPLY_FLAG : (uint8_t)0;\n    rttm_header->frame_header.type = AERON_HDR_TYPE_RTTM;\n    rttm_header->session_id = session_id;\n    rttm_header->stream_id = stream_id;\n    rttm_header->echo_timestamp = echo_timestamp;\n    rttm_header->reception_delta = reception_delta;\n    rttm_header->receiver_id = endpoint->receiver_id;\n\n    iov.iov_base = buffer;\n    iov.iov_len = sizeof(aeron_rttm_header_t);\n\n    int bytes_sent = aeron_receive_channel_endpoint_send(endpoint, destination, addr, &iov);\n    if (bytes_sent != (int)iov.iov_len)\n    {\n        if (bytes_sent >= 0)\n        {\n            aeron_counter_increment(endpoint->short_sends_counter);\n        }\n    }\n\n    return bytes_sent;\n}\n\nint aeron_receive_channel_endpoint_send_response_setup(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *addr,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t response_session_id)\n{\n    uint8_t buffer[sizeof(aeron_response_setup_header_t)];\n    aeron_response_setup_header_t *res_setup_header = (aeron_response_setup_header_t *)buffer;\n    struct iovec iov;\n\n    res_setup_header->frame_header.frame_length = sizeof(aeron_response_setup_header_t);\n    res_setup_header->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    res_setup_header->frame_header.flags = UINT8_C(0);\n    res_setup_header->frame_header.type = AERON_HDR_TYPE_RSP_SETUP;\n    res_setup_header->session_id = session_id;\n    res_setup_header->stream_id = stream_id;\n    res_setup_header->response_session_id = response_session_id;\n\n    iov.iov_base = buffer;\n    iov.iov_len = sizeof(aeron_response_setup_header_t);\n    int bytes_sent = aeron_receive_channel_endpoint_send(endpoint, destination, addr, &iov);\n    if (bytes_sent != (int)iov.iov_len)\n    {\n        if (bytes_sent >= 0)\n        {\n            aeron_counter_increment(endpoint->short_sends_counter);\n        }\n    }\n\n    return bytes_sent;\n}\n\nint aeron_receiver_channel_endpoint_send_error_frame(\n    aeron_receive_channel_endpoint_t *channel_endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *control_addr,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t error_code,\n    const char *invalidation_reason)\n{\n    uint8_t buffer[AERON_ERROR_MAX_FRAME_LENGTH];\n    aeron_error_t *error = (aeron_error_t *)buffer;\n    struct iovec iov;\n\n    const size_t error_message_length = strnlen(invalidation_reason, AERON_ERROR_MAX_TEXT_LENGTH);\n    const size_t frame_length = sizeof(aeron_error_t) + error_message_length;\n    error->frame_header.frame_length = (int32_t)frame_length;\n    error->frame_header.version = AERON_FRAME_HEADER_VERSION;\n    error->frame_header.flags = channel_endpoint->group_tag.is_present ? AERON_ERROR_HAS_GROUP_TAG_FLAG : UINT8_C(0);\n    error->frame_header.type = AERON_HDR_TYPE_ERR;\n    error->session_id = session_id;\n    error->stream_id = stream_id;\n    error->receiver_id = channel_endpoint->receiver_id;\n    error->group_tag = channel_endpoint->group_tag.value;\n    error->error_code = error_code;\n    error->error_length = (int32_t)error_message_length;\n    memcpy(&buffer[sizeof(aeron_error_t)], invalidation_reason, error_message_length);\n\n    iov.iov_base = buffer;\n    iov.iov_len = (unsigned long)frame_length;\n    int bytes_sent = aeron_receive_channel_endpoint_send(channel_endpoint, destination, control_addr, &iov);\n    if (bytes_sent != (int)iov.iov_len)\n    {\n        if (bytes_sent >= 0)\n        {\n            aeron_counter_increment(channel_endpoint->short_sends_counter);\n        }\n    }\n    else\n    {\n        aeron_counter_increment(channel_endpoint->errors_frames_sent_counter);\n    }\n\n    return bytes_sent;\n}\n\nvoid aeron_receive_channel_endpoint_dispatch(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_receive_timestamp)\n{\n    aeron_driver_receiver_t *receiver = (aeron_driver_receiver_t *)receiver_clientd;\n    aeron_frame_header_t *frame_header = (aeron_frame_header_t *)buffer;\n    aeron_receive_channel_endpoint_t *endpoint = (aeron_receive_channel_endpoint_t *)endpoint_clientd;\n    aeron_receive_destination_t *destination = (aeron_receive_destination_t *)destination_clientd;\n\n    if ((length < sizeof(aeron_frame_header_t)) || (frame_header->version != AERON_FRAME_HEADER_VERSION))\n    {\n        aeron_counter_increment(receiver->invalid_frames_counter);\n        return;\n    }\n\n    switch (frame_header->type)\n    {\n        case AERON_HDR_TYPE_PAD:\n        case AERON_HDR_TYPE_DATA:\n            if (length >= sizeof(aeron_data_header_t))\n            {\n                if (aeron_receive_channel_endpoint_on_data(\n                    endpoint, destination, buffer, length, addr, media_receive_timestamp) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"receiver on_data\");\n                    aeron_driver_receiver_log_error(receiver);\n                }\n            }\n            else\n            {\n                aeron_counter_increment(receiver->invalid_frames_counter);\n            }\n            break;\n\n        case AERON_HDR_TYPE_SETUP:\n            if (length >= sizeof(aeron_setup_header_t))\n            {\n                if (aeron_receive_channel_endpoint_on_setup(endpoint, destination, buffer, length, addr) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"receiver on_setup\");\n                    aeron_driver_receiver_log_error(receiver);\n                }\n            }\n            else\n            {\n                aeron_counter_increment(receiver->invalid_frames_counter);\n            }\n            break;\n\n        case AERON_HDR_TYPE_RTTM:\n            if (length >= sizeof(aeron_rttm_header_t))\n            {\n                if (aeron_receive_channel_endpoint_on_rttm(endpoint, destination, buffer, length, addr) < 0)\n                {\n                    AERON_APPEND_ERR(\"%s\", \"receiver on_rttm\");\n                    aeron_driver_receiver_log_error(receiver);\n                }\n            }\n            else\n            {\n                aeron_counter_increment(receiver->invalid_frames_counter);\n            }\n            break;\n\n        default:\n            break;\n    }\n}\n\nstatic void aeron_receive_channel_endpoint_apply_timestamps(\n    aeron_udp_channel_t *endpoint_channel,\n    uint32_t timestamp_flags,\n    struct timespec *media_receive_timestamp,\n    uint8_t *buffer,\n    size_t length)\n{\n    if (!aeron_publication_image_is_heartbeat(buffer, length))\n    {\n        if (NULL != media_receive_timestamp)\n        {\n            int32_t offset = endpoint_channel->media_rcv_timestamp_offset;\n            aeron_timestamps_set_timestamp(media_receive_timestamp, offset, buffer, length);\n        }\n\n        if (AERON_UDP_CHANNEL_TRANSPORT_CHANNEL_RCV_TIMESTAMP & timestamp_flags)\n        {\n            struct timespec receive_timestamp;\n            if (0 == aeron_clock_gettime_realtime(&receive_timestamp))\n            {\n                int32_t offset = endpoint_channel->channel_rcv_timestamp_offset;\n                aeron_timestamps_set_timestamp(&receive_timestamp, offset, buffer, length);\n            }\n        }\n    }\n}\n\nint aeron_receive_channel_endpoint_on_data(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_receive_timestamp)\n{\n    aeron_data_header_t *data_header = (aeron_data_header_t *)buffer;\n\n    aeron_receive_channel_endpoint_apply_timestamps(\n        endpoint->conductor_fields.udp_channel,\n        destination->transport.timestamp_flags,\n        media_receive_timestamp,\n        buffer,\n        length);\n\n    aeron_receive_destination_update_last_activity_ns(\n        destination, aeron_clock_cached_nano_time(endpoint->cached_clock));\n\n    return aeron_data_packet_dispatcher_on_data(\n        &endpoint->dispatcher, endpoint, destination, data_header, buffer, length, addr);\n}\n\nint aeron_receive_channel_endpoint_on_setup(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    aeron_setup_header_t *setup_header = (aeron_setup_header_t *)buffer;\n\n    aeron_receive_destination_update_last_activity_ns(\n        destination, aeron_clock_cached_nano_time(endpoint->cached_clock));\n\n    return aeron_data_packet_dispatcher_on_setup(\n        &endpoint->dispatcher, endpoint, destination, setup_header, buffer, length, addr);\n}\n\nint aeron_receive_channel_endpoint_on_rttm(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    aeron_rttm_header_t *rttm_header = (aeron_rttm_header_t *)buffer;\n    int result = 0;\n\n    if (endpoint->receiver_id == rttm_header->receiver_id || 0 == rttm_header->receiver_id)\n    {\n        int64_t now_ns = aeron_clock_cached_nano_time(endpoint->cached_clock);\n        aeron_receive_destination_update_last_activity_ns(destination, now_ns);\n\n        result = aeron_data_packet_dispatcher_on_rttm(\n            &endpoint->dispatcher, endpoint, destination, rttm_header, buffer, length, addr);\n    }\n\n    return result;\n}\n\nint aeron_receive_channel_endpoint_matches_tag(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel,\n    bool *has_match)\n{\n    struct sockaddr_storage* current_control_addr = NULL;\n    if (1 == endpoint->destinations.length &&\n        endpoint->destinations.array[0].destination->conductor_fields.udp_channel == endpoint->conductor_fields.udp_channel &&\n        endpoint->destinations.array[0].destination->conductor_fields.udp_channel->has_explicit_control)\n    {\n        current_control_addr = &endpoint->destinations.array[0].destination->current_control_addr;\n    }\n\n    if (aeron_udp_channel_matches_tag(\n        channel, endpoint->conductor_fields.udp_channel, current_control_addr, NULL, has_match) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nvoid aeron_receive_channel_endpoint_try_remove_endpoint(aeron_receive_channel_endpoint_t *endpoint)\n{\n    if (0 == endpoint->stream_id_to_refcnt_map.size &&\n        0 == endpoint->stream_and_session_id_to_refcnt_map.size &&\n        0 == endpoint->response_stream_id_to_refcnt_map.size &&\n        0 >= endpoint->conductor_fields.image_ref_count)\n    {\n        /* mark as CLOSING to be aware not to use again (to be receiver_released and deleted) */\n        endpoint->conductor_fields.status = AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_CLOSING;\n        aeron_driver_receiver_proxy_on_remove_endpoint(endpoint->receiver_proxy, endpoint);\n    }\n}\n\nint aeron_receive_channel_endpoint_incref_to_stream(aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id)\n{\n    int64_t count;\n    if (aeron_int64_counter_map_inc_and_get(&endpoint->stream_id_to_refcnt_map, stream_id, &count) < 0)\n    {\n        return -1;\n    }\n\n    if (1 == count)\n    {\n        aeron_driver_receiver_proxy_on_add_subscription(endpoint->receiver_proxy, endpoint, stream_id);\n    }\n\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_decref_to_stream(aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id)\n{\n    const int64_t count = aeron_int64_counter_map_get(&endpoint->stream_id_to_refcnt_map, stream_id);\n\n    if (0 == count)\n    {\n        return 0;\n    }\n\n    int64_t count_after_dec = 0;\n    int result = aeron_int64_counter_map_dec_and_get(&endpoint->stream_id_to_refcnt_map, stream_id, &count_after_dec);\n    if (result < 0)\n    {\n        return -1;\n    }\n\n    if (0 == count_after_dec)\n    {\n        aeron_driver_receiver_proxy_on_remove_subscription(endpoint->receiver_proxy, endpoint, stream_id);\n\n        aeron_receive_channel_endpoint_try_remove_endpoint(endpoint);\n    }\n\n    return result;\n}\n\nint aeron_receive_channel_endpoint_incref_to_response_stream(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id)\n{\n    int64_t count;\n    if (aeron_int64_counter_map_inc_and_get(&endpoint->response_stream_id_to_refcnt_map, stream_id, &count) < 0)\n    {\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_decref_to_response_stream(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id)\n{\n    const int64_t count = aeron_int64_counter_map_get(&endpoint->response_stream_id_to_refcnt_map, stream_id);\n\n    if (0 == count)\n    {\n        return 0;\n    }\n\n    int64_t count_after_dec = 0;\n    int result = aeron_int64_counter_map_dec_and_get(\n        &endpoint->response_stream_id_to_refcnt_map, stream_id, &count_after_dec);\n    if (result < 0)\n    {\n        return -1;\n    }\n\n    if (0 == count_after_dec)\n    {\n        aeron_receive_channel_endpoint_try_remove_endpoint(endpoint);\n    }\n\n    return result;\n}\n\nint aeron_receive_channel_endpoint_incref_to_stream_and_session(\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id)\n{\n    int64_t count;\n    if (aeron_int64_counter_map_inc_and_get(\n        &endpoint->stream_and_session_id_to_refcnt_map,\n        aeron_map_compound_key(stream_id, session_id),\n        &count) < 0)\n    {\n        return -1;\n    }\n\n    if (1 == count)\n    {\n        aeron_driver_receiver_proxy_on_add_subscription_by_session(\n            endpoint->receiver_proxy, endpoint, stream_id, session_id);\n    }\n\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_decref_to_stream_and_session(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id, int32_t session_id)\n{\n    const int64_t stream_and_session_key = aeron_map_compound_key(stream_id, session_id);\n    const int64_t count = aeron_int64_counter_map_get(\n        &endpoint->stream_and_session_id_to_refcnt_map, stream_and_session_key);\n\n    if (0 == count)\n    {\n        return 0;\n    }\n\n    int64_t count_after_dec = 0;\n    int result = aeron_int64_counter_map_dec_and_get(\n        &endpoint->stream_and_session_id_to_refcnt_map, stream_and_session_key, &count_after_dec);\n\n    if (result < 0)\n    {\n        return -1;\n    }\n\n    if (0 == count_after_dec)\n    {\n        aeron_driver_receiver_proxy_on_remove_subscription_by_session(\n            endpoint->receiver_proxy, endpoint, stream_id, session_id);\n\n        aeron_receive_channel_endpoint_try_remove_endpoint(endpoint);\n    }\n\n    return result;\n}\n\nint aeron_receive_channel_endpoint_on_add_subscription(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id)\n{\n    return aeron_data_packet_dispatcher_add_subscription(&endpoint->dispatcher, stream_id);\n}\n\nint aeron_receive_channel_endpoint_on_remove_subscription(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id)\n{\n    return aeron_data_packet_dispatcher_remove_subscription(&endpoint->dispatcher, stream_id);\n}\n\nint aeron_receive_channel_endpoint_on_add_subscription_by_session(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id, int32_t session_id)\n{\n    return aeron_data_packet_dispatcher_add_subscription_by_session(&endpoint->dispatcher, stream_id, session_id);\n}\n\nint aeron_receive_channel_endpoint_add_destination(\n    aeron_receive_channel_endpoint_t *endpoint, aeron_receive_destination_t *destination)\n{\n    int capacity_result = 0;\n    AERON_ARRAY_ENSURE_CAPACITY(capacity_result, endpoint->destinations, aeron_receive_channel_endpoint_t)\n\n    if (capacity_result < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to allocate space for additional destinations\");\n        return -1;\n    }\n\n    endpoint->destinations.array[endpoint->destinations.length].destination = destination;\n    destination->transport.dispatch_clientd = endpoint;\n\n    endpoint->destinations.length++;\n\n    return (int)endpoint->destinations.length;\n}\n\nint aeron_receive_channel_endpoint_remove_destination(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel,\n    aeron_receive_destination_t **destination_out)\n{\n    int deleted = 0;\n\n    for (int last_index = (int)endpoint->destinations.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n        if (aeron_udp_channel_equals(channel, destination->conductor_fields.udp_channel))\n        {\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)endpoint->destinations.array, sizeof(aeron_receive_destination_entry_t), i, last_index);\n\n            --endpoint->destinations.length;\n            ++deleted;\n\n            if (NULL != destination)\n            {\n                *destination_out = destination;\n            }\n\n            break;\n        }\n    }\n\n    return deleted;\n}\n\nint aeron_receive_channel_endpoint_on_remove_subscription_by_session(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id, int32_t session_id)\n{\n    return aeron_data_packet_dispatcher_remove_subscription_by_session(&endpoint->dispatcher, stream_id, session_id);\n}\n\nint aeron_receive_channel_endpoint_on_add_publication_image(\n    aeron_receive_channel_endpoint_t *endpoint, aeron_publication_image_t *image)\n{\n    for (size_t i = 0, len = endpoint->destinations.length; i < len; i++)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n\n        if (aeron_udp_channel_interceptors_image_notifications(\n            destination->data_paths,\n            &destination->transport,\n            image,\n            AERON_UDP_CHANNEL_INTERCEPTOR_ADD_NOTIFICATION) < 0)\n        {\n            return -1;\n        }\n    }\n\n    return aeron_data_packet_dispatcher_add_publication_image(&endpoint->dispatcher, image);\n}\n\nint aeron_receive_channel_endpoint_on_remove_publication_image(\n    aeron_receive_channel_endpoint_t *endpoint, aeron_publication_image_t *image)\n{\n    for (size_t i = 0, len = endpoint->destinations.length; i < len; i++)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n\n        if (aeron_udp_channel_interceptors_image_notifications(\n            destination->data_paths,\n            &destination->transport,\n            image,\n            AERON_UDP_CHANNEL_INTERCEPTOR_REMOVE_NOTIFICATION) < 0)\n        {\n            return -1;\n        }\n    }\n\n    return aeron_data_packet_dispatcher_remove_publication_image(&endpoint->dispatcher, image);\n}\n\nstatic inline bool aeron_receive_channel_endpoint_validate_so_rcvbuf(\n    size_t so_rcvbuf,\n    size_t value,\n    const char *msg,\n    aeron_driver_context_t *ctx)\n{\n    if (0 != so_rcvbuf && so_rcvbuf < value)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"%s greater than socket SO_RCVBUF, increase '\"\n            AERON_RCV_INITIAL_WINDOW_LENGTH_ENV_VAR \"' to match window: value=%\" PRIu64 \", SO_RCVBUF=%\" PRIu64,\n            msg, value, so_rcvbuf);\n\n        return false;\n    }\n\n    if (0 == so_rcvbuf && ctx->os_buffer_lengths.default_so_rcvbuf < value)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"%s greater than socket SO_RCVBUF, increase '\"\n            AERON_RCV_INITIAL_WINDOW_LENGTH_ENV_VAR \"' to match window: value=%\" PRIu64 \", SO_RCVBUF=%\" PRIu64 \" (OS Default)\",\n            msg, value, ctx->os_buffer_lengths.default_so_rcvbuf);\n\n        return false;\n    }\n\n    return true;\n}\n\nint aeron_receiver_channel_endpoint_validate_sender_mtu_length(\n    aeron_receive_channel_endpoint_t *endpoint,\n    size_t sender_mtu_length,\n    size_t window_max_length,\n    aeron_driver_context_t *ctx)\n{\n    if (sender_mtu_length < AERON_DATA_HEADER_LENGTH)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"mtuLength=%\" PRIu64 \" < DATA_HEADER_LENGTH=%d\",\n            (uint64_t)sender_mtu_length,\n            AERON_DATA_HEADER_LENGTH);\n        return -1;\n    }\n\n    if (sender_mtu_length > AERON_MAX_UDP_PAYLOAD_LENGTH)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"mtuLength=%\" PRIu64 \" > MAX_UDP_PAYLOAD_LENGTH=%d\",\n            (uint64_t)sender_mtu_length,\n            AERON_MAX_UDP_PAYLOAD_LENGTH);\n        return -1;\n    }\n\n    if ((sender_mtu_length & (AERON_LOGBUFFER_FRAME_ALIGNMENT - 1)) != 0)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"mtuLength=%\" PRIu64 \" must be a multiple of FRAME_ALIGNMENT=%d\",\n            (uint64_t)sender_mtu_length,\n            AERON_LOGBUFFER_FRAME_ALIGNMENT);\n        return -1;\n    }\n\n    if (sender_mtu_length > window_max_length)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"mtuLength=%\" PRIu64 \" > initialWindowLength=%\" PRIu64,\n            (uint64_t)sender_mtu_length,\n            (uint64_t)window_max_length);\n        return -1;\n    }\n\n    const size_t socket_rcvbuf = aeron_udp_channel_socket_so_rcvbuf(\n        endpoint->conductor_fields.udp_channel, ctx->socket_rcvbuf);\n\n    if (!aeron_receive_channel_endpoint_validate_so_rcvbuf(socket_rcvbuf, window_max_length, \"Max Window length\", ctx))\n    {\n        return -1;\n    }\n\n    if (!aeron_receive_channel_endpoint_validate_so_rcvbuf(socket_rcvbuf, sender_mtu_length, \"Sender MTU\", ctx))\n    {\n        return -1;\n    }\n\n    return 0;\n}\n\nvoid aeron_receive_channel_endpoint_check_for_re_resolution(\n    aeron_receive_channel_endpoint_t *endpoint, int64_t now_ns, aeron_driver_conductor_proxy_t *conductor_proxy)\n{\n    for (size_t i = 0, len = endpoint->destinations.length; i < len; i++)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n\n        if (aeron_receive_destination_re_resolution_required(destination, now_ns))\n        {\n            const char *endpoint_name = destination->conductor_fields.udp_channel->uri.params.udp.control;\n            struct sockaddr_storage *addr = &destination->current_control_addr;\n            aeron_driver_conductor_proxy_on_re_resolve_control(\n                conductor_proxy, endpoint_name, endpoint, destination, addr);\n            aeron_receive_destination_update_last_activity_ns(destination, now_ns);\n        }\n    }\n}\n\nvoid aeron_receive_channel_endpoint_update_control_address(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *address)\n{\n    if (destination->conductor_fields.udp_channel->has_explicit_control)\n    {\n        memcpy(&destination->current_control_addr, address, sizeof(destination->current_control_addr));\n    }\n}\n\nint aeron_receive_channel_endpoint_add_poll_transports(\n    aeron_receive_channel_endpoint_t *endpoint, aeron_udp_transport_poller_t *poller)\n{\n    for (size_t i = 0, len = endpoint->destinations.length; i < len; i++)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n\n        if (aeron_udp_channel_interceptors_transport_notifications(\n            destination->data_paths,\n            &destination->transport,\n            destination->conductor_fields.udp_channel,\n            &endpoint->dispatcher,\n            AERON_UDP_CHANNEL_INTERCEPTOR_ADD_NOTIFICATION) < 0)\n        {\n            return -1;\n        }\n\n        if (endpoint->transport_bindings->poller_add_func(poller, &destination->transport))\n        {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_remove_poll_transports(\n    aeron_receive_channel_endpoint_t *endpoint, aeron_udp_transport_poller_t *poller)\n{\n    for (size_t i = 0, len = endpoint->destinations.length; i < len; i++)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n\n        if (aeron_udp_channel_interceptors_transport_notifications(\n            destination->data_paths,\n            &destination->transport,\n            destination->conductor_fields.udp_channel,\n            &endpoint->dispatcher,\n            AERON_UDP_CHANNEL_INTERCEPTOR_REMOVE_NOTIFICATION) < 0)\n        {\n            return -1;\n        }\n\n        if (endpoint->transport_bindings->poller_remove_func(poller, &destination->transport))\n        {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_add_pending_setup_destination(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_driver_receiver_t *receiver,\n    aeron_receive_destination_t *destination,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    aeron_udp_channel_t *udp_channel = destination->conductor_fields.udp_channel;\n\n    if (destination->conductor_fields.udp_channel->has_explicit_control)\n    {\n        if (aeron_driver_receiver_add_pending_setup(\n            receiver, endpoint, destination, session_id, stream_id, &udp_channel->local_control) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to add pending setup for receiver\");\n            return -1;\n        }\n\n        if (aeron_receive_channel_endpoint_send_sm(\n            endpoint, destination, &destination->current_control_addr, stream_id, session_id, 0, 0, 0,\n            AERON_STATUS_MESSAGE_HEADER_SEND_SETUP_FLAG) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Failed to send sm for receiver\");\n            return -1;\n        }\n\n        return 1;\n    }\n\n    return 0;\n}\n\nint aeron_receive_channel_endpoint_add_pending_setup(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_driver_receiver_t *receiver,\n    int32_t session_id,\n    int32_t stream_id)\n{\n    for (size_t i = 0, len = endpoint->destinations.length; i < len; i++)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[i].destination;\n        if (aeron_receive_channel_endpoint_add_pending_setup_destination(\n            endpoint, receiver, destination, session_id, stream_id) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            aeron_driver_receiver_log_error(receiver);\n        }\n    }\n\n    return 0;\n}\n\nextern void aeron_receive_channel_endpoint_on_remove_pending_setup(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t session_id, int32_t stream_id);\n\nextern void aeron_receive_channel_endpoint_receiver_release(aeron_receive_channel_endpoint_t *endpoint);\n\nextern bool aeron_receive_channel_endpoint_has_receiver_released(aeron_receive_channel_endpoint_t *endpoint);\n\nextern bool aeron_receive_channel_endpoint_should_elicit_setup_message(aeron_receive_channel_endpoint_t *endpoint);\n\nextern int aeron_receive_channel_endpoint_bind_addr_and_port(\n    aeron_receive_channel_endpoint_t *endpoint, char *buffer, size_t length);\n\nextern void aeron_receive_channel_endpoint_inc_image_ref_count(aeron_receive_channel_endpoint_t *endpoint);\nextern void aeron_receive_channel_endpoint_dec_image_ref_count(aeron_receive_channel_endpoint_t *endpoint);\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_receive_channel_endpoint.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_RECEIVE_CHANNEL_ENDPOINT_H\n#define AERON_RECEIVE_CHANNEL_ENDPOINT_H\n\n#include \"collections/aeron_int64_counter_map.h\"\n#include \"aeron_data_packet_dispatcher.h\"\n#include \"aeron_udp_channel.h\"\n#include \"aeron_udp_channel_transport.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_system_counters.h\"\n#include \"media/aeron_receive_destination.h\"\n\ntypedef enum aeron_receive_channel_endpoint_status_enum\n{\n    AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_ACTIVE,\n    AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_CLOSING,\n    AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_CLOSED\n}\naeron_receive_channel_endpoint_status_t;\n\ntypedef struct aeron_receive_destination_entry_stct\n{\n    aeron_receive_destination_t *destination;\n}\naeron_receive_destination_entry_t;\n\ntypedef struct aeron_receive_channel_endpoint_stct\n{\n    struct aeron_receive_channel_endpoint_conductor_fields_stct\n    {\n        aeron_driver_managed_resource_t managed_resource;\n        aeron_udp_channel_t *udp_channel;\n        aeron_receive_channel_endpoint_status_t status;\n        int64_t image_ref_count;\n    }\n    conductor_fields;\n\n    struct destination_stct\n    {\n        size_t length;\n        size_t capacity;\n        aeron_receive_destination_entry_t *array;\n    }\n    destinations;\n\n    aeron_data_packet_dispatcher_t dispatcher;\n    aeron_int64_counter_map_t stream_id_to_refcnt_map;\n    aeron_int64_counter_map_t stream_and_session_id_to_refcnt_map;\n    aeron_int64_counter_map_t response_stream_id_to_refcnt_map;\n    aeron_atomic_counter_t channel_status;\n    aeron_driver_receiver_proxy_t *receiver_proxy;\n    aeron_udp_channel_transport_bindings_t *transport_bindings;\n    aeron_clock_cache_t *cached_clock;\n\n    aeron_driver_nak_message_func_t send_nak_message;\n\n    int64_t receiver_id;\n    volatile bool has_receiver_released;\n    struct\n    {\n        bool is_present;\n        int64_t value;\n    }\n    group_tag;\n\n    int64_t *short_sends_counter;\n    int64_t *possible_ttl_asymmetry_counter;\n    int64_t *errors_frames_sent_counter;\n}\naeron_receive_channel_endpoint_t;\n\nint aeron_receive_channel_endpoint_create(\n    aeron_receive_channel_endpoint_t **endpoint,\n    aeron_udp_channel_t *channel,\n    aeron_receive_destination_t *straight_through_destination,\n    aeron_atomic_counter_t *status_indicator,\n    aeron_system_counters_t *system_counters,\n    aeron_driver_context_t *context);\n\nint aeron_receive_channel_endpoint_delete(\n    aeron_counters_manager_t *counters_manager, aeron_receive_channel_endpoint_t *endpoint);\n\nint aeron_receive_channel_endpoint_close(aeron_receive_channel_endpoint_t *endpoint);\n\nint aeron_receive_channel_endpoint_send(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *address,\n    struct iovec *iov);\n\nint aeron_receive_channel_endpoint_elicit_setup(\n    aeron_receive_channel_endpoint_t *endpoint,\n    int32_t stream_id,\n    int32_t session_id);\n\nint aeron_receive_channel_endpoint_send_sm(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *control_addr,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t receiver_window,\n    uint8_t flags);\n\nint aeron_receive_channel_endpoint_send_nak(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *addr,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t term_id,\n    int32_t term_offset,\n    int32_t length);\n\nint aeron_receive_channel_endpoint_send_rttm(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *addr,\n    int32_t stream_id,\n    int32_t session_id,\n    int64_t echo_timestamp,\n    int64_t reception_delta,\n    bool is_reply);\n\nint aeron_receive_channel_endpoint_send_response_setup(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *addr,\n    int32_t stream_id,\n    int32_t session_id,\n    int32_t response_session_id);\n\nint aeron_receiver_channel_endpoint_send_error_frame(\n    aeron_receive_channel_endpoint_t *channel_endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *control_addr,\n    int32_t session_id,\n    int32_t stream_id,\n    int32_t error_code,\n    const char *invalidation_reason);\n\nvoid aeron_receive_channel_endpoint_dispatch(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_receive_timestamp);\n\nint aeron_receive_channel_endpoint_on_data(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_receive_timestamp);\n\nint aeron_receive_channel_endpoint_on_setup(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nint aeron_receive_channel_endpoint_on_rttm(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nint aeron_receive_channel_endpoint_on_unconnected_stream(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nint aeron_receive_channel_endpoint_matches_tag(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel,\n    bool *has_match);\n\nvoid aeron_receive_channel_endpoint_try_remove_endpoint(aeron_receive_channel_endpoint_t *endpoint);\n\nint aeron_receive_channel_endpoint_incref_to_stream(aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id);\n\nint aeron_receive_channel_endpoint_decref_to_stream(aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id);\n\nint aeron_receive_channel_endpoint_incref_to_stream_and_session(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id, int32_t session_id);\n\nint aeron_receive_channel_endpoint_decref_to_stream_and_session(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id, int32_t session_id);\n\nint aeron_receive_channel_endpoint_incref_to_response_stream(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id);\n\nint aeron_receive_channel_endpoint_decref_to_response_stream(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id);\n\nint aeron_receive_channel_endpoint_on_add_subscription(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id);\nint aeron_receive_channel_endpoint_on_remove_subscription(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id);\nint aeron_receive_channel_endpoint_on_add_subscription_by_session(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id, int32_t session_id);\nint aeron_receive_channel_endpoint_on_remove_subscription_by_session(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id, int32_t session_id);\n\nint aeron_receive_channel_endpoint_add_destination(\n    aeron_receive_channel_endpoint_t *endpoint, aeron_receive_destination_t *destination);\nint aeron_receive_channel_endpoint_remove_destination(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel,\n    aeron_receive_destination_t **destination_out);\n\nint aeron_receive_channel_endpoint_on_add_publication_image(\n    aeron_receive_channel_endpoint_t *endpoint, aeron_publication_image_t *image);\nint aeron_receive_channel_endpoint_on_remove_publication_image(\n    aeron_receive_channel_endpoint_t *endpoint, aeron_publication_image_t *image);\n\nint aeron_receiver_channel_endpoint_validate_sender_mtu_length(\n    aeron_receive_channel_endpoint_t *endpoint,\n    size_t sender_mtu_length,\n    size_t window_max_length,\n    aeron_driver_context_t *ctx);\n\nvoid aeron_receive_channel_endpoint_check_for_re_resolution(\n    aeron_receive_channel_endpoint_t *endpoint,\n    int64_t now_ns,\n    aeron_driver_conductor_proxy_t *conductor_proxy);\n\nvoid aeron_receive_channel_endpoint_update_control_address(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_receive_destination_t *destination,\n    struct sockaddr_storage *address);\n\nint aeron_receive_channel_endpoint_add_poll_transports(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_transport_poller_t *poller);\n\nint aeron_receive_channel_endpoint_remove_poll_transports(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_udp_transport_poller_t *poller);\n\nint aeron_receive_channel_endpoint_add_pending_setup(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_driver_receiver_t *receiver,\n    int32_t session_id,\n    int32_t stream_id);\n\nint aeron_receive_channel_endpoint_add_pending_setup_destination(\n    aeron_receive_channel_endpoint_t *endpoint,\n    aeron_driver_receiver_t *receiver,\n    aeron_receive_destination_t *destination,\n    int32_t session_id,\n    int32_t stream_id);\n\ninline void aeron_receive_channel_endpoint_on_remove_pending_setup(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t session_id, int32_t stream_id)\n{\n    aeron_data_packet_dispatcher_remove_pending_setup(&endpoint->dispatcher, session_id, stream_id);\n}\n\nstatic inline void aeron_receive_channel_endpoint_on_remove_matching_state(\n    aeron_receive_channel_endpoint_t *endpoint, int32_t session_id, int32_t stream_id, uint32_t image_state)\n{\n    aeron_data_packet_dispatcher_remove_matching_state(&endpoint->dispatcher, session_id, stream_id, image_state);\n}\n\ninline void aeron_receive_channel_endpoint_receiver_release(aeron_receive_channel_endpoint_t *endpoint)\n{\n    AERON_SET_RELEASE(endpoint->has_receiver_released, true);\n}\n\ninline bool aeron_receive_channel_endpoint_has_receiver_released(aeron_receive_channel_endpoint_t *endpoint)\n{\n    bool has_receiver_released;\n    AERON_GET_ACQUIRE(has_receiver_released, endpoint->has_receiver_released);\n\n    return has_receiver_released;\n}\n\ninline bool aeron_receive_channel_endpoint_should_elicit_setup_message(aeron_receive_channel_endpoint_t *endpoint)\n{\n    return aeron_data_packet_dispatcher_should_elicit_setup_message(&endpoint->dispatcher);\n}\n\ninline int aeron_receive_channel_endpoint_bind_addr_and_port(\n    aeron_receive_channel_endpoint_t *endpoint, char *buffer, size_t length)\n{\n    if (0 < endpoint->destinations.length)\n    {\n        aeron_receive_destination_t *destination = endpoint->destinations.array[0].destination;\n        return endpoint->transport_bindings->bind_addr_and_port_func(&destination->transport, buffer, length);\n    }\n    else\n    {\n        buffer[0] = '\\0';\n    }\n\n    return 0;\n}\n\ninline void aeron_receive_channel_endpoint_inc_image_ref_count(aeron_receive_channel_endpoint_t *endpoint)\n{\n    endpoint->conductor_fields.image_ref_count++;\n}\n\ninline void aeron_receive_channel_endpoint_dec_image_ref_count(aeron_receive_channel_endpoint_t *endpoint)\n{\n    endpoint->conductor_fields.image_ref_count--;\n}\n\n#endif //AERON_RECEIVE_CHANNEL_ENDPOINT_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_receive_destination.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"util/aeron_error.h\"\n#include \"aeron_driver_receiver.h\"\n#include \"aeron_position.h\"\n#include \"media/aeron_receive_destination.h\"\n\nint aeron_receive_destination_create(\n    aeron_receive_destination_t **destination,\n    aeron_udp_channel_t *destination_channel,\n    aeron_udp_channel_t *endpoint_channel,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    int64_t registration_id,\n    int32_t channel_status_counter_id)\n{\n    aeron_receive_destination_t *_destination = NULL;\n    const size_t socket_rcvbuf = aeron_udp_channel_socket_so_rcvbuf(endpoint_channel, context->socket_rcvbuf);\n    const size_t socket_sndbuf = aeron_udp_channel_socket_so_sndbuf(endpoint_channel, context->socket_sndbuf);\n    bool is_media_timestamping = aeron_udp_channel_is_media_rcv_timestamps_enabled(endpoint_channel);\n\n    if (aeron_alloc((void **)&_destination, sizeof(aeron_receive_destination_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"could not allocate receive_channel_endpoint\");\n        return -1;\n    }\n\n    _destination->transport.fd = -1;\n    _destination->data_paths = &context->receiver_proxy->receiver->data_paths;\n    _destination->transport.data_paths = _destination->data_paths;\n    _destination->local_sockaddr_indicator.counter_id = AERON_NULL_COUNTER_ID;\n\n    if (context->receiver_port_manager->get_managed_port(\n        context->receiver_port_manager->state,\n        &_destination->bind_addr,\n        destination_channel,\n        &destination_channel->remote_data) < 0)\n    {\n        AERON_APPEND_ERR(\"uri = %s\", destination_channel->original_uri);\n        aeron_receive_destination_delete(_destination, counters_manager);\n        return -1;\n    }\n\n    _destination->port_manager = context->receiver_port_manager;\n\n    aeron_udp_channel_transport_params_t transport_params = {\n        socket_rcvbuf,\n        socket_sndbuf,\n        context->mtu_length,\n        destination_channel->interface_index,\n        0 != destination_channel->multicast_ttl ? destination_channel->multicast_ttl : context->multicast_ttl,\n        is_media_timestamping,\n    };\n\n    if (context->udp_channel_transport_bindings->init_func(\n        &_destination->transport,\n        &_destination->bind_addr,\n        &destination_channel->local_data,\n        NULL,\n        &transport_params,\n        context,\n        AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_RECEIVER) < 0)\n    {\n        AERON_APPEND_ERR(\"uri = %s\", destination_channel->original_uri);\n        aeron_receive_destination_delete(_destination, counters_manager);\n        return -1;\n    }\n\n    if (aeron_udp_channel_is_channel_rcv_timestamps_enabled(endpoint_channel))\n    {\n        _destination->transport.timestamp_flags |= AERON_UDP_CHANNEL_TRANSPORT_CHANNEL_RCV_TIMESTAMP;\n    }\n\n    char local_sockaddr[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n    if (context->udp_channel_transport_bindings->bind_addr_and_port_func(\n        &_destination->transport, local_sockaddr, sizeof(local_sockaddr)) < 0)\n    {\n        aeron_receive_destination_delete(_destination, counters_manager);\n        return -1;\n    }\n\n    _destination->local_sockaddr_indicator.counter_id = aeron_counter_local_sockaddr_indicator_allocate(\n        counters_manager,\n        AERON_COUNTER_RCV_LOCAL_SOCKADDR_NAME,\n        registration_id,\n        channel_status_counter_id,\n        local_sockaddr);\n    _destination->local_sockaddr_indicator.value_addr = aeron_counters_manager_addr(\n        counters_manager, _destination->local_sockaddr_indicator.counter_id);\n\n    if (_destination->local_sockaddr_indicator.counter_id < 0)\n    {\n        aeron_receive_destination_delete(_destination, counters_manager);\n        return -1;\n    }\n\n    if (context->udp_channel_transport_bindings->get_so_rcvbuf_func(&_destination->transport, &_destination->so_rcvbuf) < 0)\n    {\n        aeron_receive_destination_delete(_destination, counters_manager);\n        return -1;\n    }\n\n    _destination->transport.destination_clientd = _destination;\n    _destination->time_of_last_activity_ns = aeron_clock_cached_nano_time(context->receiver_cached_clock);\n\n    if (destination_channel->is_multicast)\n    {\n        memcpy(&_destination->current_control_addr, &destination_channel->remote_control, sizeof(_destination->current_control_addr));\n    }\n    else if (destination_channel->has_explicit_control)\n    {\n        memcpy(&_destination->current_control_addr, &destination_channel->local_control, sizeof(_destination->current_control_addr));\n    }\n\n    _destination->has_control_addr = destination_channel->is_multicast || destination_channel->has_explicit_control;\n\n    aeron_counter_set_release(\n        _destination->local_sockaddr_indicator.value_addr, AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_ACTIVE);\n\n    // Only take ownership of the destination_channel if the receive destination is successfully created.\n    _destination->conductor_fields.udp_channel = destination_channel;\n    *destination = _destination;\n\n    return 0;\n}\n\nvoid aeron_receive_destination_delete(\n    aeron_receive_destination_t *destination, aeron_counters_manager_t *counters_manager)\n{\n    if (NULL != counters_manager && AERON_NULL_COUNTER_ID != destination->local_sockaddr_indicator.counter_id)\n    {\n        aeron_counter_set_release(\n            destination->local_sockaddr_indicator.value_addr, AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_CLOSING);\n        aeron_counters_manager_free(counters_manager, destination->local_sockaddr_indicator.counter_id);\n        destination->local_sockaddr_indicator.counter_id = AERON_NULL_COUNTER_ID;\n    }\n\n    if (NULL != destination->port_manager)\n    {\n        destination->port_manager->free_managed_port(\n            destination->port_manager->state,\n            &destination->bind_addr);\n    }\n\n    aeron_udp_channel_delete(destination->conductor_fields.udp_channel);\n    aeron_free(destination);\n}\n\nextern void aeron_receive_destination_update_last_activity_ns(aeron_receive_destination_t *destination, int64_t now_ns);\n\nextern bool aeron_receive_destination_re_resolution_required(aeron_receive_destination_t *destination, int64_t now_ns);\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_receive_destination.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_RECEIVE_DESTINATION_H\n#define AERON_AERON_RECEIVE_DESTINATION_H\n\n#include \"media/aeron_udp_channel_transport.h\"\n#include \"media/aeron_udp_channel.h\"\n#include \"media/aeron_receive_channel_endpoint.h\"\n\n#define AERON_RECEIVE_DESTINATION_TIMEOUT_NS (5 * 1000 * 1000 * 1000LL)\n\ntypedef struct aeron_receive_destination_stct\n{\n    struct aeron_receive_destination_conductor_fields_stct\n    {\n        aeron_udp_channel_t *udp_channel;\n    }\n    conductor_fields;\n\n    aeron_udp_channel_transport_t transport;\n    aeron_udp_channel_data_paths_t *data_paths;\n    aeron_port_manager_t *port_manager;\n    aeron_atomic_counter_t local_sockaddr_indicator;\n    struct sockaddr_storage current_control_addr;\n    struct sockaddr_storage bind_addr;\n    size_t so_rcvbuf;\n    bool has_control_addr;\n    int64_t time_of_last_activity_ns;\n    uint8_t padding[AERON_CACHE_LINE_LENGTH];\n}\naeron_receive_destination_t;\n\nint aeron_receive_destination_create(\n    aeron_receive_destination_t **destination,\n    aeron_udp_channel_t *destination_channel,\n    aeron_udp_channel_t *endpoint_channel,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    int64_t registration_id,\n    int32_t channel_status_counter_id);\n\nvoid aeron_receive_destination_delete(\n    aeron_receive_destination_t *destination, aeron_counters_manager_t *counters_manager);\n\ninline void aeron_receive_destination_update_last_activity_ns(aeron_receive_destination_t *destination, int64_t now_ns)\n{\n    destination->time_of_last_activity_ns = now_ns;\n}\n\ninline bool aeron_receive_destination_re_resolution_required(aeron_receive_destination_t *destination, int64_t now_ns)\n{\n    return destination->conductor_fields.udp_channel->has_explicit_control &&\n        now_ns > destination->time_of_last_activity_ns + AERON_RECEIVE_DESTINATION_TIMEOUT_NS;\n}\n\n#endif //AERON_AERON_RECEIVE_DESTINATION_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_send_channel_endpoint.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <string.h>\n#include \"aeron_socket.h\"\n#include \"uri/aeron_uri.h\"\n#include \"aeron_driver_sender.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_position.h\"\n#include \"aeron_timestamps.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\nstatic void aeron_send_channel_endpoint_handle_managed_resource_event(aeron_driver_managed_resource_event_t event, void *clientd);\n\nint aeron_send_channel_endpoint_create(\n    aeron_send_channel_endpoint_t **endpoint,\n    aeron_udp_channel_t *channel,\n    aeron_driver_uri_publication_params_t *params,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    int64_t registration_id)\n{\n    aeron_send_channel_endpoint_t *_endpoint = NULL;\n    char bind_addr_and_port[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n    int bind_addr_and_port_length;\n\n    if (aeron_alloc((void **)&_endpoint, sizeof(aeron_send_channel_endpoint_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_udp_channel_delete(channel);\n        return -1;\n    }\n\n    _endpoint->destination_tracker = NULL;\n    _endpoint->data_paths = &context->sender_proxy->sender->data_paths;\n\n    struct sockaddr_storage *connect_addr = NULL;\n    if (aeron_udp_channel_is_multi_destination(channel))\n    {\n        if (aeron_alloc((void **)&_endpoint->destination_tracker, sizeof(aeron_udp_destination_tracker_t)) < 0 ||\n            aeron_udp_destination_tracker_init(\n                _endpoint->destination_tracker,\n                _endpoint->data_paths,\n                context->sender_cached_clock,\n                AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL == channel->control_mode,\n                AERON_UDP_DESTINATION_TRACKER_DESTINATION_TIMEOUT_NS) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            aeron_udp_channel_delete(channel);\n            aeron_free(_endpoint);\n            return -1;\n        }\n    }\n    else if (channel->has_explicit_endpoint && context->connect_enabled)\n    {\n        connect_addr = &channel->remote_data;\n    }\n\n    _endpoint->conductor_fields.refcnt = 0;\n    _endpoint->conductor_fields.udp_channel = channel;\n    _endpoint->conductor_fields.managed_resource.handle_event = aeron_send_channel_endpoint_handle_managed_resource_event;\n    _endpoint->conductor_fields.managed_resource.clientd = _endpoint;\n    _endpoint->conductor_fields.managed_resource.registration_id = -1;\n    _endpoint->conductor_fields.status = AERON_SEND_CHANNEL_ENDPOINT_STATUS_ACTIVE;\n    _endpoint->conductor_fields.socket_sndbuf = 0 != channel->socket_sndbuf_length ?\n        channel->socket_sndbuf_length : context->socket_sndbuf;\n    _endpoint->conductor_fields.socket_rcvbuf = 0 != channel->socket_rcvbuf_length ?\n        channel->socket_rcvbuf_length : context->socket_rcvbuf;\n    _endpoint->transport.fd = -1;\n    _endpoint->channel_status.counter_id = -1;\n    _endpoint->local_sockaddr_indicator.counter_id = -1;\n    _endpoint->tracker_num_destinations.counter_id = -1;\n    _endpoint->transport_bindings = context->udp_channel_transport_bindings;\n    _endpoint->data_paths = &context->sender_proxy->sender->data_paths;\n    _endpoint->transport.data_paths = _endpoint->data_paths;\n\n    if (context->sender_port_manager->get_managed_port(\n        context->sender_port_manager->state,\n        &_endpoint->bind_addr,\n        channel,\n        channel->is_multicast ? &channel->remote_control : &channel->local_control) < 0)\n    {\n        AERON_APPEND_ERR(\"uri=%s\", channel->original_uri);\n        aeron_send_channel_endpoint_delete(counters_manager, _endpoint);\n        return -1;\n    }\n\n    _endpoint->port_manager = context->sender_port_manager;\n\n    aeron_udp_channel_transport_params_t transport_params = {\n        _endpoint->conductor_fields.socket_rcvbuf,\n        _endpoint->conductor_fields.socket_sndbuf,\n        params->mtu_length,\n        channel->interface_index,\n        0 != channel->multicast_ttl ? channel->multicast_ttl : context->multicast_ttl,\n        false,\n    };\n\n    if (context->udp_channel_transport_bindings->init_func(\n        &_endpoint->transport,\n        &_endpoint->bind_addr,\n        channel->is_multicast ? &channel->local_control : &channel->remote_control,\n        connect_addr,\n        &transport_params,\n        context,\n        AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_SENDER) < 0)\n    {\n        AERON_APPEND_ERR(\"uri=%s\", channel->original_uri);\n        aeron_send_channel_endpoint_delete(counters_manager, _endpoint);\n        return -1;\n    }\n\n    if (aeron_udp_channel_is_channel_snd_timestamps_enabled(channel))\n    {\n        _endpoint->transport.timestamp_flags |= AERON_UDP_CHANNEL_TRANSPORT_CHANNEL_SND_TIMESTAMP;\n    }\n\n    if (aeron_int64_to_ptr_hash_map_init(\n        &_endpoint->publication_dispatch_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        aeron_send_channel_endpoint_delete(counters_manager, _endpoint);\n        return -1;\n    }\n\n    if ((bind_addr_and_port_length = aeron_send_channel_endpoint_bind_addr_and_port(\n        _endpoint, bind_addr_and_port, sizeof(bind_addr_and_port))) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_send_channel_endpoint_delete(counters_manager, _endpoint);\n        return -1;\n    }\n\n    _endpoint->transport.dispatch_clientd = _endpoint;\n    _endpoint->has_sender_released = false;\n\n    _endpoint->channel_status.counter_id = aeron_counter_send_channel_status_allocate(\n        counters_manager, registration_id, channel->uri_length, channel->original_uri);\n    _endpoint->channel_status.value_addr = aeron_counters_manager_addr(\n        counters_manager, _endpoint->channel_status.counter_id);\n\n    if (_endpoint->channel_status.counter_id < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_send_channel_endpoint_delete(counters_manager, _endpoint);\n        return -1;\n    }\n\n    _endpoint->on_nak_message = context->log.on_nak_message;\n\n    if (NULL != _endpoint->destination_tracker)\n    {\n        _endpoint->tracker_num_destinations.counter_id = aeron_channel_endpoint_status_allocate(\n            counters_manager,\n            AERON_COUNTER_CHANNEL_MDC_NUM_DESTINATIONS_NAME,\n            AERON_COUNTER_CHANNEL_NUM_DESTINATIONS_TYPE_ID,\n            registration_id,\n            channel->uri_length,\n            channel->original_uri);\n        _endpoint->tracker_num_destinations.value_addr = aeron_counters_manager_addr(\n            counters_manager, _endpoint->tracker_num_destinations.counter_id);\n\n        if (_endpoint->tracker_num_destinations.counter_id < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            aeron_send_channel_endpoint_delete(counters_manager, _endpoint);\n            return -1;\n        }\n\n        aeron_udp_destination_tracker_set_counter(\n            _endpoint->destination_tracker, &_endpoint->tracker_num_destinations);\n    }\n\n    aeron_channel_endpoint_status_update_label(\n        counters_manager,\n        _endpoint->channel_status.counter_id,\n        AERON_COUNTER_SEND_CHANNEL_STATUS_NAME,\n        channel->uri_length,\n        channel->original_uri,\n        bind_addr_and_port_length,\n        bind_addr_and_port);\n\n    _endpoint->local_sockaddr_indicator.counter_id = aeron_counter_local_sockaddr_indicator_allocate(\n        counters_manager,\n        AERON_COUNTER_SND_LOCAL_SOCKADDR_NAME,\n        registration_id,\n        _endpoint->channel_status.counter_id,\n        bind_addr_and_port);\n\n    _endpoint->local_sockaddr_indicator.value_addr = aeron_counters_manager_addr(\n        counters_manager, _endpoint->local_sockaddr_indicator.counter_id);\n\n    if (_endpoint->local_sockaddr_indicator.counter_id < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_send_channel_endpoint_delete(counters_manager, _endpoint);\n        return -1;\n    }\n\n    aeron_counter_set_release(\n        _endpoint->local_sockaddr_indicator.value_addr, AERON_COUNTER_CHANNEL_ENDPOINT_STATUS_ACTIVE);\n\n    _endpoint->sender_proxy = context->sender_proxy;\n    _endpoint->cached_clock = context->sender_cached_clock;\n    _endpoint->time_of_last_sm_ns = aeron_clock_cached_nano_time(_endpoint->cached_clock);\n    memcpy(&_endpoint->current_data_addr, &channel->remote_data, sizeof(_endpoint->current_data_addr));\n\n    *endpoint = _endpoint;\n    return 0;\n}\n\nint aeron_send_channel_endpoint_delete(\n    aeron_counters_manager_t *counters_manager, aeron_send_channel_endpoint_t *endpoint)\n{\n    if (NULL != counters_manager)\n    {\n        if (-1 != endpoint->channel_status.counter_id)\n        {\n            aeron_counters_manager_free(counters_manager, endpoint->channel_status.counter_id);\n        }\n\n        if (-1 != endpoint->local_sockaddr_indicator.counter_id)\n        {\n            aeron_counters_manager_free(counters_manager, endpoint->local_sockaddr_indicator.counter_id);\n        }\n\n        if (-1 != endpoint->tracker_num_destinations.counter_id)\n        {\n            aeron_counters_manager_free(counters_manager, endpoint->tracker_num_destinations.counter_id);\n        }\n    }\n\n    aeron_int64_to_ptr_hash_map_delete(&endpoint->publication_dispatch_map);\n    aeron_udp_channel_delete(endpoint->conductor_fields.udp_channel);\n    if (endpoint->conductor_fields.status != AERON_SEND_CHANNEL_ENDPOINT_STATUS_CLOSED)\n    {\n        endpoint->transport_bindings->close_func(&endpoint->transport);\n    }\n\n    if (NULL != endpoint->port_manager)\n    {\n        endpoint->port_manager->free_managed_port(endpoint->port_manager->state, &endpoint->bind_addr);\n    }\n\n    if (NULL != endpoint->destination_tracker)\n    {\n        aeron_udp_destination_tracker_close(endpoint->destination_tracker);\n        aeron_free(endpoint->destination_tracker);\n    }\n\n    aeron_free(endpoint);\n\n    return 0;\n}\n\nint aeron_send_channel_endpoint_close(aeron_send_channel_endpoint_t *endpoint)\n{\n    endpoint->transport_bindings->close_func(&endpoint->transport);\n    endpoint->conductor_fields.status = AERON_SEND_CHANNEL_ENDPOINT_STATUS_CLOSED;\n\n    return 0;\n}\n\nvoid aeron_send_channel_endpoint_handle_managed_resource_event(aeron_driver_managed_resource_event_t event, void *clientd)\n{\n    aeron_send_channel_endpoint_t *endpoint = (aeron_send_channel_endpoint_t *)clientd;\n\n    switch(event)\n    {\n        case AERON_DRIVER_MANAGED_RESOURCE_EVENT_INCREF:\n        {\n            endpoint->conductor_fields.refcnt++;\n            break;\n        }\n\n        case AERON_DRIVER_MANAGED_RESOURCE_EVENT_DECREF:\n        {\n            if (0 == --endpoint->conductor_fields.refcnt)\n            {\n                /* mark as CLOSING to be aware not to use again (to be receiver_released and deleted) */\n                endpoint->conductor_fields.status = AERON_SEND_CHANNEL_ENDPOINT_STATUS_CLOSING;\n                aeron_driver_sender_proxy_on_remove_endpoint(endpoint->sender_proxy, endpoint);\n            }\n            break;\n        }\n\n        case AERON_DRIVER_MANAGED_RESOURCE_EVENT_REVOKE:\n        {\n            break;\n        }\n    }\n}\n\nstatic void aeron_send_channel_apply_timestamps(\n    aeron_send_channel_endpoint_t *endpoint,\n    struct iovec *iov,\n    size_t iov_length)\n{\n    if (AERON_UDP_CHANNEL_TRANSPORT_CHANNEL_SND_TIMESTAMP & endpoint->transport.timestamp_flags)\n    {\n        struct timespec send_timestamp;\n        if (0 == aeron_clock_gettime_realtime(&send_timestamp))\n        {\n            int32_t offset = endpoint->conductor_fields.udp_channel->channel_snd_timestamp_offset;\n\n            for (size_t i = 0; i < iov_length; i++)\n            {\n                aeron_timestamps_set_timestamp(\n                    &send_timestamp,\n                    offset,\n                    (uint8_t *)iov[i].iov_base,\n                    iov[i].iov_len);\n            }\n        }\n    }\n}\n\nint aeron_send_channel_send(\n    aeron_send_channel_endpoint_t *endpoint,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n    int result;\n\n    aeron_send_channel_apply_timestamps(endpoint, iov, iov_length);\n\n    if (NULL == endpoint->destination_tracker)\n    {\n        result = endpoint->data_paths->send_func(\n            endpoint->data_paths, &endpoint->transport, &endpoint->current_data_addr, iov, iov_length, bytes_sent);\n    }\n    else\n    {\n        result = aeron_udp_destination_tracker_send(\n            endpoint->destination_tracker, &endpoint->transport, iov, iov_length, bytes_sent);\n    }\n\n    return result;\n}\n\nint aeron_send_channel_send_endpoint_address(\n    aeron_send_channel_endpoint_t *endpoint,\n    struct sockaddr_storage* endpoint_address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n    aeron_send_channel_apply_timestamps(endpoint, iov, iov_length);\n\n    return endpoint->data_paths->send_func(\n            endpoint->data_paths, &endpoint->transport, endpoint_address, iov, iov_length, bytes_sent);\n}\n\nint aeron_send_channel_endpoint_add_publication(\n    aeron_send_channel_endpoint_t *endpoint, aeron_network_publication_t *publication)\n{\n    int64_t key_value = aeron_map_compound_key(publication->stream_id, publication->session_id);\n\n    int result = aeron_int64_to_ptr_hash_map_put(&endpoint->publication_dispatch_map, key_value, publication);\n    if (result < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"Failed to add publication to publication_dispatch_map\");\n    }\n\n    return result;\n}\n\nint aeron_send_channel_endpoint_remove_publication(\n    aeron_send_channel_endpoint_t *endpoint, aeron_network_publication_t *publication)\n{\n    int64_t key_value = aeron_map_compound_key(publication->stream_id, publication->session_id);\n\n    aeron_int64_to_ptr_hash_map_remove(&endpoint->publication_dispatch_map, key_value);\n    return 0;\n}\n\nvoid aeron_send_channel_endpoint_dispatch(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    void *sender_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp)\n{\n    aeron_driver_sender_t *sender = (aeron_driver_sender_t *)sender_clientd;\n    aeron_frame_header_t *frame_header = (aeron_frame_header_t *)buffer;\n    aeron_send_channel_endpoint_t *endpoint = (aeron_send_channel_endpoint_t *)endpoint_clientd;\n    aeron_driver_conductor_proxy_t *conductor_proxy = sender->context->conductor_proxy;\n\n    int result = 0;\n\n    if (!aeron_is_frame_valid(frame_header, length))\n    {\n        aeron_counter_increment(sender->invalid_frames_counter);\n        return;\n    }\n\n    switch (frame_header->type)\n    {\n        case AERON_HDR_TYPE_NAK:\n            if (length >= sizeof(aeron_nak_header_t) && length >= (size_t)frame_header->frame_length)\n            {\n                result = aeron_send_channel_endpoint_on_nak(endpoint, buffer, length, addr);\n                aeron_counter_increment_release(sender->nak_messages_received_counter);\n            }\n            else\n            {\n                aeron_counter_increment(sender->invalid_frames_counter);\n            }\n            break;\n\n        case AERON_HDR_TYPE_SM:\n            if (length >= sizeof(aeron_status_message_header_t) && length >= (size_t)frame_header->frame_length)\n            {\n                result = aeron_send_channel_endpoint_on_status_message(endpoint, conductor_proxy, buffer, length, addr);\n                aeron_counter_increment_release(sender->status_messages_received_counter);\n            }\n            else\n            {\n                aeron_counter_increment(sender->invalid_frames_counter);\n            }\n            break;\n\n        case AERON_HDR_TYPE_ERR:\n            if (length >= sizeof(aeron_error_t) && length >= (size_t)frame_header->frame_length)\n            {\n                result = aeron_send_channel_endpoint_on_error(endpoint, conductor_proxy, buffer, length, addr);\n                aeron_counter_increment_release(sender->error_messages_received_counter);\n            }\n            else\n            {\n                aeron_counter_increment(sender->invalid_frames_counter);\n            }\n            break;\n\n        case AERON_HDR_TYPE_RTTM:\n            if (length >= sizeof(aeron_rttm_header_t) && length >= (size_t)frame_header->frame_length)\n            {\n                aeron_send_channel_endpoint_on_rttm(endpoint, buffer, length, addr);\n            }\n            else\n            {\n                aeron_counter_increment(sender->invalid_frames_counter);\n            }\n            break;\n\n        case AERON_HDR_TYPE_RSP_SETUP:\n            if (length >= sizeof(aeron_response_setup_header_t) && length >= (size_t)frame_header->frame_length)\n            {\n                aeron_send_channel_endpoint_on_response_setup(endpoint, conductor_proxy, buffer, length, addr);\n            }\n            else\n            {\n                aeron_counter_increment(sender->invalid_frames_counter);\n            }\n            break;\n\n        default:\n            break;\n    }\n\n    if (0 != result)\n    {\n        aeron_driver_sender_log_error(sender);\n    }\n}\n\nint aeron_send_channel_endpoint_on_nak(\n    aeron_send_channel_endpoint_t *endpoint, uint8_t *buffer, size_t length, struct sockaddr_storage *addr)\n{\n    aeron_nak_header_t *nak_header = (aeron_nak_header_t *)buffer;\n    int64_t key_value = aeron_map_compound_key(nak_header->stream_id, nak_header->session_id);\n    aeron_network_publication_t *publication = aeron_int64_to_ptr_hash_map_get(\n        &endpoint->publication_dispatch_map, key_value);\n\n    if (NULL != publication)\n    {\n        int result = aeron_network_publication_on_nak(publication, nak_header->term_id, nak_header->term_offset, nak_header->length);\n\n        if (0 != result)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n        }\n\n        return result;\n    }\n\n    aeron_driver_nak_message_func_t on_nak_message = endpoint->on_nak_message;\n    if (NULL != on_nak_message)\n    {\n        on_nak_message(\n            addr,\n            nak_header->session_id,\n            nak_header->stream_id,\n            nak_header->term_id,\n            nak_header->term_offset,\n            nak_header->length,\n            endpoint->conductor_fields.udp_channel->uri_length,\n            endpoint->conductor_fields.udp_channel->original_uri);\n    }\n\n    // we got a NAK for a publication that doesn't exist...\n    return 0;\n}\n\nint aeron_send_channel_endpoint_on_status_message(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    aeron_status_message_header_t *sm_header = (aeron_status_message_header_t *)buffer;\n    int64_t key_value = aeron_map_compound_key(sm_header->stream_id, sm_header->session_id);\n    aeron_network_publication_t *publication = aeron_int64_to_ptr_hash_map_get(\n        &endpoint->publication_dispatch_map, key_value);\n\n    int result = 0;\n\n    if (NULL != endpoint->destination_tracker)\n    {\n        result = aeron_udp_destination_tracker_on_status_message(endpoint->destination_tracker, buffer, length, addr);\n\n        if (0 != result)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return result;\n        }\n    }\n\n    if (NULL != publication)\n    {\n        if (sm_header->frame_header.flags & AERON_STATUS_MESSAGE_HEADER_SEND_SETUP_FLAG)\n        {\n            aeron_network_publication_trigger_send_setup_frame(publication, buffer, length, addr);\n        }\n        else\n        {\n            aeron_network_publication_on_status_message(publication, conductor_proxy, buffer, length, addr);\n        }\n\n        endpoint->time_of_last_sm_ns = aeron_clock_cached_nano_time(endpoint->cached_clock);\n    }\n\n    return result;\n}\n\nint aeron_send_channel_endpoint_on_error(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    aeron_error_t *error = (aeron_error_t *)buffer;\n\n    int64_t destination_registration_id = AERON_NULL_VALUE;\n    if (NULL != endpoint->destination_tracker)\n    {\n        destination_registration_id = aeron_udp_destination_tracker_find_registration_id(\n            endpoint->destination_tracker, buffer, length, addr);\n    }\n\n    int64_t key_value = aeron_map_compound_key(error->stream_id, error->session_id);\n    aeron_network_publication_t *publication = aeron_int64_to_ptr_hash_map_get(\n        &endpoint->publication_dispatch_map, key_value);\n    int result = 0;\n\n    if (NULL != publication)\n    {\n        aeron_network_publication_on_error(\n            publication, destination_registration_id, buffer, length, addr, conductor_proxy);\n    }\n\n    return result;\n}\n\nvoid aeron_send_channel_endpoint_on_rttm(\n    aeron_send_channel_endpoint_t *endpoint, uint8_t *buffer, size_t length, struct sockaddr_storage *addr)\n{\n    aeron_rttm_header_t *rttm_header = (aeron_rttm_header_t *)buffer;\n    int64_t key_value = aeron_map_compound_key(rttm_header->stream_id, rttm_header->session_id);\n    aeron_network_publication_t *publication = aeron_int64_to_ptr_hash_map_get(\n        &endpoint->publication_dispatch_map, key_value);\n\n    if (NULL != publication)\n    {\n        aeron_network_publication_on_rttm(publication, buffer, length, addr);\n    }\n}\n\nvoid aeron_send_channel_endpoint_on_response_setup(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr)\n{\n    aeron_response_setup_header_t *rsp_setup_header = (aeron_response_setup_header_t *)buffer;\n    int64_t key_value = aeron_map_compound_key(rsp_setup_header->stream_id, rsp_setup_header->session_id);\n    aeron_network_publication_t *publication = aeron_int64_to_ptr_hash_map_get(\n        &endpoint->publication_dispatch_map, key_value);\n\n    if (NULL != publication)\n    {\n        const int64_t response_correlation_id = publication->response_correlation_id;\n        if (AERON_NULL_VALUE != response_correlation_id)\n        {\n            aeron_driver_conductor_proxy_on_response_setup(\n                conductor_proxy, response_correlation_id, rsp_setup_header->response_session_id);\n        }\n    }\n}\n\n\nint aeron_send_channel_endpoint_check_for_re_resolution(\n    aeron_send_channel_endpoint_t *endpoint, int64_t now_ns, aeron_driver_conductor_proxy_t *conductor_proxy)\n{\n    if (AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL == endpoint->conductor_fields.udp_channel->control_mode)\n    {\n        aeron_udp_destination_tracker_check_for_re_resolution(\n            endpoint->destination_tracker, endpoint, now_ns, conductor_proxy);\n    }\n    else if (!endpoint->conductor_fields.udp_channel->is_multicast &&\n        endpoint->conductor_fields.udp_channel->has_explicit_endpoint &&\n        now_ns > (endpoint->time_of_last_sm_ns + AERON_SEND_CHANNEL_ENDPOINT_DESTINATION_TIMEOUT_NS))\n    {\n        const char *endpoint_name = endpoint->conductor_fields.udp_channel->uri.params.udp.endpoint;\n\n        aeron_driver_conductor_proxy_on_re_resolve_endpoint(\n            conductor_proxy, endpoint_name, endpoint, &endpoint->current_data_addr);\n    }\n\n    return 0;\n}\n\nint aeron_send_channel_endpoint_resolution_change(\n    aeron_driver_context_t *context,\n    aeron_send_channel_endpoint_t *endpoint,\n    const char *endpoint_name,\n    struct sockaddr_storage *new_addr)\n{\n    if (NULL != endpoint->destination_tracker)\n    {\n        aeron_udp_destination_tracker_resolution_change(endpoint->destination_tracker, endpoint_name, new_addr);\n    }\n    else\n    {\n        memcpy(&endpoint->current_data_addr, new_addr, sizeof(endpoint->current_data_addr));\n        if (context->udp_channel_transport_bindings->reconnect_func(&endpoint->transport, &endpoint->current_data_addr) < 0)\n        {\n            char addr_str[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n            aeron_format_source_identity(addr_str, sizeof(addr_str), &endpoint->current_data_addr);\n            AERON_APPEND_ERR(\"failed to reconnect transport with re-resolved address: %s\", addr_str);\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\nint aeron_send_channel_endpoint_matches_tag(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel,\n    bool *has_match)\n{\n    if (aeron_udp_channel_matches_tag(\n        channel, endpoint->conductor_fields.udp_channel, NULL, &endpoint->current_data_addr, has_match) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nextern void aeron_send_channel_endpoint_sender_release(aeron_send_channel_endpoint_t *endpoint);\n\nextern bool aeron_send_channel_endpoint_has_sender_released(aeron_send_channel_endpoint_t *endpoint);\n\nextern int aeron_send_channel_endpoint_add_destination(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_uri_t *uri,\n    struct sockaddr_storage *addr,\n    int64_t destination_registration_id);\n\nextern int aeron_send_channel_endpoint_remove_destination(\n    aeron_send_channel_endpoint_t *endpoint,\n    struct sockaddr_storage *addr,\n    aeron_uri_t **removed_uri);\n\nextern int aeron_send_channel_endpoint_remove_destination_by_id(\n    aeron_send_channel_endpoint_t *endpoint, int64_t registration_destination_id, aeron_uri_t **removed_uri);\n\nextern bool aeron_send_channel_endpoint_tags_match(\n    aeron_send_channel_endpoint_t *endpoint, aeron_udp_channel_t *channel);\n\nextern int aeron_send_channel_endpoint_bind_addr_and_port(\n    aeron_send_channel_endpoint_t *endpoint, char *buffer, size_t length);\n\nextern bool aeron_send_channel_is_unicast(aeron_send_channel_endpoint_t *endpoint);\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_send_channel_endpoint.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_SEND_CHANNEL_ENDPOINT_H\n#define AERON_SEND_CHANNEL_ENDPOINT_H\n\n#include \"collections/aeron_int64_to_ptr_hash_map.h\"\n#include \"util/aeron_netutil.h\"\n#include \"aeron_network_publication.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_udp_channel.h\"\n#include \"aeron_udp_channel_transport.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"aeron_udp_destination_tracker.h\"\n#include \"aeron_driver_sender_proxy.h\"\n\n#define AERON_SEND_CHANNEL_ENDPOINT_DESTINATION_TIMEOUT_NS (5 * 1000 * 1000 * 1000LL)\n\ntypedef enum aeron_send_channel_endpoint_status_enum\n{\n    AERON_SEND_CHANNEL_ENDPOINT_STATUS_ACTIVE,\n    AERON_SEND_CHANNEL_ENDPOINT_STATUS_CLOSING,\n    AERON_SEND_CHANNEL_ENDPOINT_STATUS_CLOSED\n}\naeron_send_channel_endpoint_status_t;\n\ntypedef struct aeron_send_channel_endpoint_stct\n{\n    struct aeron_send_channel_endpoint_conductor_fields_stct\n    {\n        aeron_driver_managed_resource_t managed_resource;\n        int32_t refcnt;\n        bool has_reached_end_of_life;\n        aeron_udp_channel_t *udp_channel;\n        aeron_send_channel_endpoint_status_t status;\n        size_t socket_sndbuf;\n        size_t socket_rcvbuf;\n    }\n    conductor_fields;\n\n    volatile bool has_sender_released;\n    aeron_udp_channel_transport_t transport;\n    aeron_atomic_counter_t channel_status;\n    aeron_atomic_counter_t local_sockaddr_indicator;\n    aeron_atomic_counter_t tracker_num_destinations;\n    aeron_udp_destination_tracker_t *destination_tracker;\n    aeron_driver_sender_proxy_t *sender_proxy;\n    aeron_int64_to_ptr_hash_map_t publication_dispatch_map;\n    aeron_udp_channel_transport_bindings_t *transport_bindings;\n    aeron_udp_channel_data_paths_t *data_paths;\n    struct sockaddr_storage current_data_addr;\n    struct sockaddr_storage bind_addr;\n    aeron_port_manager_t *port_manager;\n    aeron_clock_cache_t *cached_clock;\n    int64_t time_of_last_sm_ns;\n\n    aeron_driver_nak_message_func_t on_nak_message;\n\n    uint8_t padding[AERON_CACHE_LINE_LENGTH];\n}\naeron_send_channel_endpoint_t;\n\nint aeron_send_channel_endpoint_create(\n    aeron_send_channel_endpoint_t **endpoint,\n    aeron_udp_channel_t *channel,\n    aeron_driver_uri_publication_params_t *params,\n    aeron_driver_context_t *context,\n    aeron_counters_manager_t *counters_manager,\n    int64_t registration_id);\n\nint aeron_send_channel_endpoint_delete(\n    aeron_counters_manager_t *counters_manager, aeron_send_channel_endpoint_t *endpoint);\n\nint aeron_send_channel_endpoint_close(aeron_send_channel_endpoint_t *endpoint);\n\nint aeron_send_channel_send(\n    aeron_send_channel_endpoint_t *endpoint,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent);\n\nint aeron_send_channel_send_endpoint_address(\n    aeron_send_channel_endpoint_t *endpoint,\n    struct sockaddr_storage* endpoint_address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent);\n\nint aeron_send_channel_endpoint_add_publication(\n    aeron_send_channel_endpoint_t *endpoint, aeron_network_publication_t *publication);\n\nint aeron_send_channel_endpoint_remove_publication(\n    aeron_send_channel_endpoint_t *endpoint, aeron_network_publication_t *publication);\n\nvoid aeron_send_channel_endpoint_dispatch(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    void *sender_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp);\n\nint aeron_send_channel_endpoint_on_nak(\n    aeron_send_channel_endpoint_t *endpoint, uint8_t *buffer, size_t length, struct sockaddr_storage *addr);\n\nint aeron_send_channel_endpoint_on_status_message(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nint aeron_send_channel_endpoint_on_error(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nvoid aeron_send_channel_endpoint_on_rttm(\n    aeron_send_channel_endpoint_t *endpoint, uint8_t *buffer, size_t length, struct sockaddr_storage *addr);\n\nvoid aeron_send_channel_endpoint_on_response_setup(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_driver_conductor_proxy_t *conductor_proxy,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr);\n\nint aeron_send_channel_endpoint_check_for_re_resolution(\n    aeron_send_channel_endpoint_t *endpoint, int64_t now_ns, aeron_driver_conductor_proxy_t *conductor_proxy);\n\nint aeron_send_channel_endpoint_resolution_change(\n    aeron_driver_context_t *context,\n    aeron_send_channel_endpoint_t *endpoint,\n    const char *endpoint_name,\n    struct sockaddr_storage *new_addr);\n\nint aeron_send_channel_endpoint_matches_tag(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_udp_channel_t *channel,\n    bool *has_match);\n\ninline void aeron_send_channel_endpoint_sender_release(aeron_send_channel_endpoint_t *endpoint)\n{\n    AERON_SET_RELEASE(endpoint->has_sender_released, true);\n}\n\ninline bool aeron_send_channel_endpoint_has_sender_released(aeron_send_channel_endpoint_t *endpoint)\n{\n    bool has_sender_released;\n    AERON_GET_ACQUIRE(has_sender_released, endpoint->has_sender_released);\n\n    return has_sender_released;\n}\n\ninline int aeron_send_channel_endpoint_add_destination(\n    aeron_send_channel_endpoint_t *endpoint,\n    aeron_uri_t *uri,\n    struct sockaddr_storage *addr,\n    int64_t destination_registration_id)\n{\n    const int64_t now_ns = aeron_clock_cached_nano_time(endpoint->destination_tracker->cached_clock);\n    return aeron_udp_destination_tracker_manual_add_destination(\n        endpoint->destination_tracker, now_ns, uri, addr, destination_registration_id);\n}\n\ninline int aeron_send_channel_endpoint_remove_destination(\n    aeron_send_channel_endpoint_t *endpoint, struct sockaddr_storage *addr, aeron_uri_t **removed_uri)\n{\n    return aeron_udp_destination_tracker_remove_destination(endpoint->destination_tracker, addr, removed_uri);\n}\n\ninline int aeron_send_channel_endpoint_remove_destination_by_id(\n    aeron_send_channel_endpoint_t *endpoint, int64_t registration_destination_id, aeron_uri_t **removed_uri)\n{\n    return aeron_udp_destination_tracker_remove_destination_by_id(\n        endpoint->destination_tracker, registration_destination_id, removed_uri);\n}\n\ninline int aeron_send_channel_endpoint_bind_addr_and_port(\n    aeron_send_channel_endpoint_t *endpoint, char *buffer, size_t length)\n{\n    return endpoint->transport_bindings->bind_addr_and_port_func(&endpoint->transport, buffer, length);\n}\n\ninline bool aeron_send_channel_is_unicast(aeron_send_channel_endpoint_t *endpoint)\n{\n    return NULL == endpoint->destination_tracker && !endpoint->conductor_fields.udp_channel->is_multicast;\n}\n\n#endif //AERON_SEND_CHANNEL_ENDPOINT_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_timestamps.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"aeron_timestamps.h\"\n#include \"aeron_udp_channel.h\"\n\nvoid aeron_timestamps_set_timestamp(\n    struct timespec *timestamp,\n    int32_t offset,\n    uint8_t *frame,\n    size_t frame_length)\n{\n    aeron_data_header_t *data_header = (aeron_data_header_t *)frame;\n\n    if (frame_length >= sizeof(aeron_data_header_t) &&\n        AERON_HDR_TYPE_DATA == data_header->frame_header.type &&\n        AERON_DATA_HEADER_BEGIN_FLAG & data_header->frame_header.flags &&\n        0 != data_header->frame_header.frame_length)\n    {\n        size_t body_length = frame_length - sizeof(aeron_data_header_t);\n        uint8_t *body_buffer = frame + sizeof(aeron_data_header_t);\n        int64_t timestamp_ns = (INT64_C(1000) * 1000 * 1000 * timestamp->tv_sec) + timestamp->tv_nsec;\n\n        if (AERON_UDP_CHANNEL_RESERVED_VALUE_OFFSET == offset)\n        {\n            data_header->reserved_value = timestamp_ns;\n        }\n        else if (0 <= offset &&\n            offset <= (int32_t)(body_length - sizeof(timestamp_ns)) &&\n            offset <= (int32_t)((data_header->frame_header.frame_length - sizeof(*data_header)) - sizeof(timestamp_ns)))\n        {\n            memcpy(body_buffer + (size_t)offset, &timestamp_ns, sizeof(timestamp_ns));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_timestamps.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_TIMESTAMPS_H\n#define AERON_TIMESTAMPS_H\n\n#include <stdint.h>\n#include <time.h>\n\nvoid aeron_timestamps_set_timestamp(\n    struct timespec *timestamp,\n    int32_t offset,\n    uint8_t *frame,\n    size_t frame_length);\n\n#endif // AERON_TIMESTAMPS_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <string.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <stdio.h>\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n#include \"media/aeron_udp_channel.h\"\n#include \"command/aeron_control_protocol.h\"\n\nstatic int aeron_udp_channel_endpoints_match_with_override(\n    aeron_udp_channel_t *channel,\n    aeron_udp_channel_t *endpoint_channel,\n    struct sockaddr_storage *local_address,\n    struct sockaddr_storage *remote_address,\n    bool *result)\n{\n    bool cmp = false;\n    int rc = 0;\n\n    if (aeron_udp_channel_is_wildcard(channel))\n    {\n        *result = true;\n        return rc;\n    }\n\n    struct sockaddr_storage *endpoint_remote_data = NULL != remote_address ? remote_address :\n        &endpoint_channel->remote_data;\n    struct sockaddr_storage *endpoint_local_data = NULL != local_address ? local_address :\n        &endpoint_channel->local_data;\n\n    rc = aeron_sockaddr_storage_cmp(&channel->remote_data, endpoint_remote_data, &cmp);\n    if (rc < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"remote_data\");\n        return rc;\n    }\n\n    if (!cmp)\n    {\n        *result = cmp;\n        return 0;\n    }\n\n    rc = aeron_sockaddr_storage_cmp(&channel->local_data, endpoint_local_data, &cmp);\n    if (rc < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"local_data\");\n        return rc;\n    }\n\n    *result = cmp;\n    return 0;\n}\n\nint aeron_ipv4_multicast_control_address(struct sockaddr_in *data_addr, struct sockaddr_in *control_addr)\n{\n    uint8_t bytes[sizeof(struct in_addr)];\n    size_t addr_len = sizeof(struct in_addr);\n    size_t last_byte_index = addr_len - 1;\n\n    memcpy(bytes, &(data_addr->sin_addr), addr_len);\n\n    if ((bytes[last_byte_index] & 0x1u) == 0)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"Multicast data address must be odd\");\n        return -1;\n    }\n\n    bytes[last_byte_index]++;\n    control_addr->sin_family = data_addr->sin_family;\n    memcpy(&(control_addr->sin_addr), bytes, addr_len);\n    control_addr->sin_port = data_addr->sin_port;\n\n    return 0;\n}\n\nint aeron_ipv6_multicast_control_address(struct sockaddr_in6 *data_addr, struct sockaddr_in6 *control_addr)\n{\n    uint8_t bytes[sizeof(struct in6_addr)];\n    size_t addr_len = sizeof(struct in6_addr);\n    size_t last_byte_index = addr_len - 1;\n\n    memcpy(bytes, &(data_addr->sin6_addr), addr_len);\n\n    if ((bytes[last_byte_index] & 0x1u) == 0)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"Multicast data address must be odd\");\n        return -1;\n    }\n\n    bytes[last_byte_index]++;\n    control_addr->sin6_family = data_addr->sin6_family;\n    memcpy(&(control_addr->sin6_addr), bytes, addr_len);\n    control_addr->sin6_port = data_addr->sin6_port;\n\n    return 0;\n}\n\nint aeron_multicast_control_address(struct sockaddr_storage *data_addr, struct sockaddr_storage *control_addr)\n{\n    if (AF_INET6 == data_addr->ss_family)\n    {\n        return aeron_ipv6_multicast_control_address(\n            (struct sockaddr_in6 *)data_addr, (struct sockaddr_in6 *)control_addr);\n    }\n    else if (AF_INET == data_addr->ss_family)\n    {\n        return aeron_ipv4_multicast_control_address(\n            (struct sockaddr_in *)data_addr, (struct sockaddr_in *)control_addr);\n    }\n\n    AERON_SET_ERR(EINVAL, \"unknown address family: %d\", data_addr->ss_family);\n    return -1;\n}\n\nint aeron_find_multicast_interface(\n    int family, const char *interface_str, struct sockaddr_storage *interface_addr, unsigned int *interface_index)\n{\n    char *wildcard_str = AF_INET6 == family ? \"[0::]/0\" : \"0.0.0.0/0\";\n\n    return aeron_find_interface(\n        family, NULL == interface_str ? wildcard_str : interface_str, interface_addr, interface_index);\n}\n\nstatic int32_t unique_canonical_form_value = 0;\n\nint aeron_uri_udp_canonicalise(\n    char *canonical_form,\n    size_t length,\n    const char *local_param_value,\n    struct sockaddr_storage *local_data,\n    const char *remote_param_value,\n    struct sockaddr_storage *remote_data,\n    bool make_unique,\n    int64_t tag)\n{\n    char unique_suffix[32] = \"\";\n    char local_data_buffer[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n    char remote_data_buffer[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n\n    const char *local_data_str;\n    const char *remote_data_str;\n\n    if (NULL == local_param_value)\n    {\n        if (aeron_format_source_identity(local_data_buffer, sizeof(local_data_buffer), local_data) < 0)\n        {\n            return -1;\n        }\n\n        local_data_str = local_data_buffer;\n    }\n    else\n    {\n        local_data_str = local_param_value;\n    }\n\n    if (NULL == remote_param_value)\n    {\n        if (aeron_format_source_identity(remote_data_buffer, sizeof(remote_data_buffer), remote_data) < 0)\n        {\n            return -1;\n        }\n\n        remote_data_str = remote_data_buffer;\n    }\n    else\n    {\n        remote_data_str = remote_param_value;\n    }\n\n    if (make_unique)\n    {\n        if (AERON_URI_INVALID_TAG != tag)\n        {\n            snprintf(unique_suffix, sizeof(unique_suffix) - 1, \"#%\" PRId64, tag);\n        }\n        else\n        {\n            int32_t result;\n            AERON_GET_AND_ADD_INT32(result, unique_canonical_form_value, 1);\n            snprintf(unique_suffix, sizeof(unique_suffix) - 1, \"-%\" PRId32, result);\n        }\n    }\n\n    return snprintf(canonical_form, length, \"UDP-%s-%s%s\", local_data_str, remote_data_str, unique_suffix);\n}\n\nstatic int aeron_udp_channel_verify_timestamp_offsets_do_not_overlap(aeron_udp_channel_t *channel)\n{\n    if (AERON_NULL_VALUE != channel->media_rcv_timestamp_offset)\n    {\n        if (AERON_NULL_VALUE != channel->channel_rcv_timestamp_offset &&\n            abs(channel->media_rcv_timestamp_offset - channel->channel_rcv_timestamp_offset) < (int32_t)sizeof(int64_t))\n        {\n            AERON_SET_ERR(\n                EINVAL, \"%s and %s overlap\", AERON_URI_MEDIA_RCV_TIMESTAMP_OFFSET_KEY, AERON_URI_CHANNEL_RCV_TIMESTAMP_OFFSET_KEY);\n            return -1;\n        }\n\n        if (AERON_NULL_VALUE != channel->channel_snd_timestamp_offset &&\n            abs(channel->media_rcv_timestamp_offset - channel->channel_snd_timestamp_offset) < (int32_t)sizeof(int64_t))\n        {\n            AERON_SET_ERR(\n                EINVAL, \"%s and %s overlap\", AERON_URI_MEDIA_RCV_TIMESTAMP_OFFSET_KEY, AERON_URI_CHANNEL_SND_TIMESTAMP_OFFSET_KEY);\n            return -1;\n        }\n    }\n\n    if (AERON_NULL_VALUE != channel->channel_rcv_timestamp_offset &&\n        AERON_NULL_VALUE != channel->channel_snd_timestamp_offset &&\n        abs(channel->channel_rcv_timestamp_offset - channel->channel_snd_timestamp_offset) < (int32_t)sizeof(int64_t))\n    {\n        AERON_SET_ERR(\n            EINVAL, \"%s and %s overlap\", AERON_URI_CHANNEL_RCV_TIMESTAMP_OFFSET_KEY, AERON_URI_CHANNEL_SND_TIMESTAMP_OFFSET_KEY);\n        return -1;\n    }\n\n    return 0;\n}\n\n/* Do the initial allocations required to create an aeron_udp_channel_t */\nint aeron_udp_channel_do_initial_parse(\n    size_t uri_length,\n    const char *uri,\n    aeron_udp_channel_async_parse_t *async_parse)\n{\n    aeron_udp_channel_t *_channel = NULL;\n\n    if (aeron_alloc((void **)&_channel, sizeof(aeron_udp_channel_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"UDP channel, uri=%.*s\", (int)uri_length, uri);\n        return -1;\n    }\n\n    if (aeron_uri_parse(uri_length, uri, &_channel->uri) < 0)\n    {\n        async_parse->channel = NULL;\n        aeron_udp_channel_delete(_channel);\n        return -1;\n    }\n\n    size_t copy_length = sizeof(_channel->original_uri) - 1;\n    copy_length = uri_length < copy_length ? uri_length : copy_length;\n\n    memcpy(_channel->original_uri, uri, copy_length);\n    _channel->original_uri[copy_length] = '\\0';\n    _channel->uri_length = copy_length;\n\n    async_parse->channel = _channel;\n\n    return 0;\n}\n\n/* Finish filling out the channel */\n/* This function is designed to be run off an executor thread */\nint aeron_udp_channel_finish_parse(\n    aeron_name_resolver_t *resolver,\n    aeron_udp_channel_async_parse_t *async_parse)\n{\n    aeron_udp_channel_t *_channel = async_parse->channel;\n    struct sockaddr_storage endpoint_addr, explicit_control_addr, interface_addr;\n    unsigned int interface_index = 0;\n\n    memset(&endpoint_addr, 0, sizeof(endpoint_addr));\n    memset(&explicit_control_addr, 0, sizeof(explicit_control_addr));\n    memset(&interface_addr, 0, sizeof(interface_addr));\n\n    _channel->has_explicit_endpoint = NULL != _channel->uri.params.udp.endpoint;\n    _channel->has_explicit_control = false;\n    _channel->control_mode = AERON_UDP_CHANNEL_CONTROL_MODE_NONE;\n    _channel->is_multicast = false;\n    _channel->tag_id = AERON_URI_INVALID_TAG;\n    _channel->ats_status = AERON_URI_ATS_STATUS_DEFAULT;\n    _channel->socket_rcvbuf_length = 0;\n    _channel->socket_sndbuf_length = 0;\n    _channel->receiver_window_length = 0;\n    _channel->media_rcv_timestamp_offset = AERON_NULL_VALUE;\n    _channel->channel_rcv_timestamp_offset = AERON_NULL_VALUE;\n    _channel->channel_snd_timestamp_offset = AERON_NULL_VALUE;\n\n    if (_channel->uri.type != AERON_URI_UDP)\n    {\n        AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL, \"%s\", \"UDP channels must use UDP URIs\");\n        goto error_cleanup;\n    }\n\n    if (NULL != _channel->uri.params.udp.control_mode)\n    {\n        if (strcmp(_channel->uri.params.udp.control_mode, AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL_VALUE) == 0)\n        {\n            _channel->control_mode = AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL;\n        }\n        else if (strcmp(_channel->uri.params.udp.control_mode, AERON_UDP_CHANNEL_CONTROL_MODE_DYNAMIC_VALUE) == 0)\n        {\n            _channel->control_mode = AERON_UDP_CHANNEL_CONTROL_MODE_DYNAMIC;\n        }\n        else if (strcmp(_channel->uri.params.udp.control_mode, AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE) == 0)\n        {\n            _channel->control_mode = AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE;\n        }\n    }\n\n    if (AERON_UDP_CHANNEL_CONTROL_MODE_DYNAMIC == _channel->control_mode && NULL == _channel->uri.params.udp.control)\n    {\n        AERON_SET_ERR(-AERON_ERROR_CODE_INVALID_CHANNEL, \"%s\", \"explicit control expected with dynamic control mode\");\n        goto error_cleanup;\n    }\n\n    bool has_no_distinguishing_characteristic =\n        NULL == _channel->uri.params.udp.endpoint &&\n        NULL == _channel->uri.params.udp.control &&\n        NULL == _channel->uri.params.udp.channel_tag;\n\n    if (has_no_distinguishing_characteristic && AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL != _channel->control_mode &&\n        AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE != _channel->control_mode)\n    {\n        AERON_SET_ERR(\n            -AERON_ERROR_CODE_INVALID_CHANNEL,\n            \"%s\",\n            \"URIs for UDP must specify endpoint, control, tags, or control-mode=manual/response\");\n        goto error_cleanup;\n    }\n\n    if (NULL != _channel->uri.params.udp.control)\n    {\n        if (aeron_name_resolver_resolve_host_and_port(\n            resolver, _channel->uri.params.udp.control, AERON_UDP_CHANNEL_CONTROL_KEY, false, &explicit_control_addr) < 0)\n        {\n            goto error_cleanup;\n        }\n    }\n\n    if (NULL != _channel->uri.params.udp.endpoint)\n    {\n        if (aeron_name_resolver_resolve_host_and_port(\n            resolver, _channel->uri.params.udp.endpoint, AERON_UDP_CHANNEL_ENDPOINT_KEY, false, &endpoint_addr) < 0)\n        {\n            AERON_APPEND_ERR(\"URI: %.*s\", (int)_channel->uri_length, _channel->original_uri);\n            goto error_cleanup;\n        }\n    }\n    else\n    {\n        if (NULL != _channel->uri.params.udp.control && AF_INET6 == explicit_control_addr.ss_family)\n        {\n            aeron_set_ipv6_wildcard_host_and_port(&endpoint_addr);\n        }\n        else\n        {\n            aeron_set_ipv4_wildcard_host_and_port(&endpoint_addr);\n        }\n    }\n\n    bool requires_additional_suffix =\n        (NULL == _channel->uri.params.udp.endpoint && NULL == _channel->uri.params.udp.control) ||\n        (NULL != _channel->uri.params.udp.endpoint && aeron_is_wildcard_port(&endpoint_addr)) ||\n        (NULL != _channel->uri.params.udp.control && aeron_is_wildcard_port(&explicit_control_addr));\n\n    requires_additional_suffix = requires_additional_suffix && !async_parse->is_destination;\n\n    if (NULL != _channel->uri.params.udp.channel_tag)\n    {\n        if ((_channel->tag_id = aeron_uri_parse_tag(_channel->uri.params.udp.channel_tag)) == AERON_URI_INVALID_TAG)\n        {\n            AERON_SET_ERR(\n                -AERON_ERROR_CODE_INVALID_CHANNEL,\n                \"could not parse channel tag string: %s\",\n                _channel->uri.params.udp.channel_tag);\n            goto error_cleanup;\n        }\n    }\n\n    if (aeron_uri_get_ats(&_channel->uri.params.udp.additional_params, &_channel->ats_status) < 0)\n    {\n        goto error_cleanup;\n    }\n\n    if (aeron_uri_get_socket_buf_lengths(\n        &_channel->uri.params.udp.additional_params,\n        &_channel->socket_sndbuf_length,\n        &_channel->socket_rcvbuf_length) < 0)\n    {\n        goto error_cleanup;\n    }\n\n    if (aeron_uri_get_receiver_window_length(\n        &_channel->uri.params.udp.additional_params, &_channel->receiver_window_length) < 0)\n    {\n        goto error_cleanup;\n    }\n\n    if (aeron_is_addr_multicast(&endpoint_addr))\n    {\n        memcpy(&_channel->remote_data, &endpoint_addr, AERON_ADDR_LEN(&endpoint_addr));\n        if (aeron_multicast_control_address(&endpoint_addr, &_channel->remote_control) < 0)\n        {\n            goto error_cleanup;\n        }\n\n        if (aeron_find_multicast_interface(\n            endpoint_addr.ss_family, _channel->uri.params.udp.bind_interface, &interface_addr, &interface_index) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error_cleanup;\n        }\n\n        _channel->interface_index = interface_index;\n        _channel->multicast_ttl = aeron_uri_multicast_ttl(&_channel->uri);\n        memcpy(&_channel->local_data, &interface_addr, AERON_ADDR_LEN(&interface_addr));\n        memcpy(&_channel->local_control, &interface_addr, AERON_ADDR_LEN(&interface_addr));\n        aeron_uri_udp_canonicalise(\n            _channel->canonical_form, sizeof(_channel->canonical_form),\n            NULL, &interface_addr,\n            NULL, &endpoint_addr,\n            false,\n            AERON_URI_INVALID_TAG);\n        _channel->canonical_length = strlen(_channel->canonical_form);\n        _channel->is_multicast = true;\n    }\n    else if (NULL != _channel->uri.params.udp.control)\n    {\n        if (aeron_find_unicast_interface(\n            explicit_control_addr.ss_family, _channel->uri.params.udp.bind_interface, &interface_addr, &interface_index) < 0)\n        {\n            goto error_cleanup;\n        }\n\n        _channel->interface_index = interface_index;\n        _channel->multicast_ttl = 0;\n        memcpy(&_channel->remote_data, &endpoint_addr, AERON_ADDR_LEN(&endpoint_addr));\n        memcpy(&_channel->remote_control, &endpoint_addr, AERON_ADDR_LEN(&endpoint_addr));\n        memcpy(&_channel->local_data, &explicit_control_addr, AERON_ADDR_LEN(&explicit_control_addr));\n        memcpy(&_channel->local_control, &explicit_control_addr, AERON_ADDR_LEN(&explicit_control_addr));\n        aeron_uri_udp_canonicalise(\n            _channel->canonical_form, sizeof(_channel->canonical_form),\n            _channel->uri.params.udp.control, &explicit_control_addr,\n            _channel->uri.params.udp.endpoint, &endpoint_addr,\n            requires_additional_suffix,\n            _channel->tag_id);\n        _channel->canonical_length = strlen(_channel->canonical_form);\n        _channel->has_explicit_control = true;\n    }\n    else\n    {\n        if (aeron_find_unicast_interface(\n            endpoint_addr.ss_family, _channel->uri.params.udp.bind_interface, &interface_addr, &interface_index) < 0)\n        {\n            goto error_cleanup;\n        }\n\n        _channel->interface_index = interface_index;\n        _channel->multicast_ttl = 0;\n        memcpy(&_channel->remote_data, &endpoint_addr, AERON_ADDR_LEN(&endpoint_addr));\n        memcpy(&_channel->remote_control, &endpoint_addr, AERON_ADDR_LEN(&endpoint_addr));\n        memcpy(&_channel->local_data, &interface_addr, AERON_ADDR_LEN(&interface_addr));\n        memcpy(&_channel->local_control, &interface_addr, AERON_ADDR_LEN(&interface_addr));\n        aeron_uri_udp_canonicalise(\n            _channel->canonical_form,\n            sizeof(_channel->canonical_form),\n            NULL, &interface_addr,\n            _channel->uri.params.udp.endpoint, &endpoint_addr,\n            requires_additional_suffix,\n            _channel->tag_id);\n        _channel->canonical_length = strlen(_channel->canonical_form);\n    }\n\n    if (aeron_driver_uri_get_timestamp_offset(\n        &_channel->uri, AERON_URI_MEDIA_RCV_TIMESTAMP_OFFSET_KEY, &_channel->media_rcv_timestamp_offset) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    if (aeron_driver_uri_get_timestamp_offset(\n        &_channel->uri, AERON_URI_CHANNEL_RCV_TIMESTAMP_OFFSET_KEY, &_channel->channel_rcv_timestamp_offset) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    if (aeron_driver_uri_get_timestamp_offset(\n        &_channel->uri, AERON_URI_CHANNEL_SND_TIMESTAMP_OFFSET_KEY, &_channel->channel_snd_timestamp_offset) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    if (aeron_udp_channel_verify_timestamp_offsets_do_not_overlap(_channel) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error_cleanup;\n    }\n\n    return 0;\n\nerror_cleanup:\n    async_parse->channel = NULL;\n    aeron_udp_channel_delete(_channel);\n\n    return -1;\n}\n\n// This is the old synchronous method of channel parsing.\n// It's deprecated in favor of using the aeron_udp_channel_async_parse_t functions\nint aeron_udp_channel_parse(\n    size_t uri_length,\n    const char *uri,\n    aeron_name_resolver_t *resolver,\n    aeron_udp_channel_t **channel,\n    bool is_destination)\n{\n    aeron_udp_channel_async_parse_t async_parse;\n    async_parse.is_destination = is_destination;\n\n    if (aeron_udp_channel_do_initial_parse(uri_length, uri, &async_parse) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        *channel = NULL;\n        return -1;\n    }\n\n    if (aeron_udp_channel_finish_parse(resolver, &async_parse) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        *channel = NULL;\n        return -1;\n    }\n\n    *channel = async_parse.channel;\n    return 0;\n}\n\nvoid aeron_udp_channel_delete(aeron_udp_channel_t *channel)\n{\n    if (NULL != channel)\n    {\n        aeron_uri_close(&channel->uri);\n        aeron_free((void *)channel);\n    }\n}\n\nint aeron_udp_channel_matches_tag(\n    aeron_udp_channel_t *channel,\n    aeron_udp_channel_t *endpoint_channel,\n    struct sockaddr_storage *local_address,\n    struct sockaddr_storage *remote_address,\n    bool *has_match)\n{\n    if (AERON_URI_INVALID_TAG == channel->tag_id ||\n        AERON_URI_INVALID_TAG == endpoint_channel->tag_id ||\n        channel->tag_id != endpoint_channel->tag_id)\n    {\n        *has_match = false;\n        return 0;\n    }\n\n    if (!aeron_udp_channel_control_modes_match(channel, endpoint_channel))\n    {\n        *has_match = false;\n\n        AERON_SET_ERR(\n            EINVAL,\n            \"matching tag %\" PRId64 \" has mismatched control-mode: %.*s <> %.*s\",\n            channel->tag_id,\n            (int)channel->uri_length,\n            channel->original_uri,\n            (int)endpoint_channel->uri_length,\n            endpoint_channel->original_uri);\n\n        return -1;\n    }\n\n    bool addresses_match = false;\n    if (aeron_udp_channel_endpoints_match_with_override(\n        channel, endpoint_channel, local_address, remote_address, &addresses_match) < 0)\n    {\n        *has_match = false;\n\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (!addresses_match)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"matching tag %\" PRId64 \" has mismatched endpoint or control: %.*s <> %.*s\",\n            channel->tag_id,\n            (int)channel->uri_length,\n            channel->original_uri,\n            (int)endpoint_channel->uri_length,\n            endpoint_channel->original_uri);\n        return -1;\n    }\n\n    *has_match = true;\n    return 0;\n}\n\n\nextern bool aeron_udp_channel_is_wildcard(aeron_udp_channel_t *channel);\n\nextern bool aeron_udp_channel_control_modes_match(aeron_udp_channel_t *channel, aeron_udp_channel_t *other);\n\nextern bool aeron_udp_channel_equals(aeron_udp_channel_t *a, aeron_udp_channel_t *b);\n\nextern size_t aeron_udp_channel_socket_so_sndbuf(aeron_udp_channel_t *channel, size_t default_so_sndbuf);\n\nextern size_t aeron_udp_channel_socket_so_rcvbuf(aeron_udp_channel_t *channel, size_t default_so_rcvbuf);\n\nextern size_t aeron_udp_channel_receiver_window(aeron_udp_channel_t *channel, size_t default_receiver_window);\n\nextern bool aeron_udp_channel_is_media_rcv_timestamps_enabled(aeron_udp_channel_t *channel);\n\nextern bool aeron_udp_channel_is_channel_rcv_timestamps_enabled(aeron_udp_channel_t *channel);\n\nextern bool aeron_udp_channel_is_channel_snd_timestamps_enabled(aeron_udp_channel_t *channel);\n\nextern bool aeron_udp_channel_is_multi_destination(const aeron_udp_channel_t *channel);\n\nextern bool aeron_udp_channel_has_group_semantics(const aeron_udp_channel_t *channel);\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_UDP_CHANNEL_H\n#define AERON_UDP_CHANNEL_H\n\n#include \"aeron_socket.h\"\n#include \"uri/aeron_uri.h\"\n#include \"util/aeron_netutil.h\"\n#include \"aeron_name_resolver.h\"\n#include \"util/aeron_error.h\"\n\n#define AERON_UDP_CHANNEL_RESERVED_VALUE_OFFSET (-8)\n\nenum aeron_udp_channel_control_mode_en\n{\n    AERON_UDP_CHANNEL_CONTROL_MODE_NONE,\n    AERON_UDP_CHANNEL_CONTROL_MODE_DYNAMIC,\n    AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL,\n    AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE\n};\n\ntypedef enum aeron_udp_channel_control_mode_en aeron_udp_channel_control_mode;\n\ntypedef struct aeron_udp_channel_stct\n{\n    char original_uri[AERON_URI_MAX_LENGTH];\n    char canonical_form[AERON_URI_MAX_LENGTH];\n    aeron_uri_t uri;\n    struct sockaddr_storage remote_data;\n    struct sockaddr_storage local_data;\n    struct sockaddr_storage remote_control;\n    struct sockaddr_storage local_control;\n    int64_t tag_id;\n    unsigned int interface_index;\n    size_t uri_length;\n    size_t canonical_length;\n    uint8_t multicast_ttl;\n    bool has_explicit_endpoint;\n    bool has_explicit_control;\n    aeron_udp_channel_control_mode control_mode;\n    bool is_multicast;\n    aeron_uri_ats_status_t ats_status;\n    size_t socket_sndbuf_length;\n    size_t socket_rcvbuf_length;\n    size_t receiver_window_length;\n    int32_t media_rcv_timestamp_offset;\n    int32_t channel_rcv_timestamp_offset;\n    int32_t channel_snd_timestamp_offset;\n}\naeron_udp_channel_t;\n\ntypedef struct aeron_udp_channel_async_parse_stct\n{\n    aeron_udp_channel_t *channel;\n    bool is_destination;\n}\naeron_udp_channel_async_parse_t;\n\nint aeron_udp_channel_do_initial_parse(\n    size_t uri_length,\n    const char *uri,\n    aeron_udp_channel_async_parse_t *async_parse);\n\nint aeron_udp_channel_finish_parse(\n    aeron_name_resolver_t *resolver,\n    aeron_udp_channel_async_parse_t *async_parse);\n\nint aeron_udp_channel_parse(\n    size_t uri_length,\n    const char *uri,\n    aeron_name_resolver_t *resolver,\n    aeron_udp_channel_t **channel,\n    bool is_destination);\n\nint aeron_udp_channel_matches_tag(\n    aeron_udp_channel_t *channel,\n    aeron_udp_channel_t *endpoint_channel,\n    struct sockaddr_storage *local_address,\n    struct sockaddr_storage *remote_address,\n    bool *has_match);\n\nvoid aeron_udp_channel_delete(aeron_udp_channel_t *channel);\n\ninline bool aeron_udp_channel_is_wildcard(aeron_udp_channel_t *channel)\n{\n    return aeron_is_wildcard_addr(&channel->remote_data) && aeron_is_wildcard_port(&channel->remote_data) &&\n        aeron_is_wildcard_addr(&channel->local_data) && aeron_is_wildcard_port(&channel->local_data);\n}\n\ninline bool aeron_udp_channel_control_modes_match(aeron_udp_channel_t *channel, aeron_udp_channel_t *other)\n{\n    return AERON_UDP_CHANNEL_CONTROL_MODE_NONE == channel->control_mode || channel->control_mode == other->control_mode;\n}\n\ninline bool aeron_udp_channel_equals(aeron_udp_channel_t *a, aeron_udp_channel_t *b)\n{\n    return a == b || (a != NULL && 0 == strncmp(a->canonical_form, b->canonical_form, AERON_URI_MAX_LENGTH));\n}\n\ninline size_t aeron_udp_channel_receiver_window(aeron_udp_channel_t *channel, size_t default_receiver_window)\n{\n    return 0 != channel->receiver_window_length ? channel->receiver_window_length : default_receiver_window;\n}\n\ninline size_t aeron_udp_channel_socket_so_sndbuf(aeron_udp_channel_t *channel, size_t default_so_sndbuf)\n{\n    return 0 != channel->socket_sndbuf_length ? channel->socket_sndbuf_length : default_so_sndbuf;\n}\n\ninline size_t aeron_udp_channel_socket_so_rcvbuf(aeron_udp_channel_t *channel, size_t default_so_rcvbuf)\n{\n    return 0 != channel->socket_rcvbuf_length ? channel->socket_rcvbuf_length : default_so_rcvbuf;\n}\n\ninline bool aeron_udp_channel_is_media_rcv_timestamps_enabled(aeron_udp_channel_t *channel)\n{\n    return AERON_UDP_CHANNEL_RESERVED_VALUE_OFFSET == channel->media_rcv_timestamp_offset ||\n        0 <= channel->media_rcv_timestamp_offset;\n}\n\ninline bool aeron_udp_channel_is_channel_rcv_timestamps_enabled(aeron_udp_channel_t *channel)\n{\n    return AERON_UDP_CHANNEL_RESERVED_VALUE_OFFSET == channel->channel_rcv_timestamp_offset ||\n        0 <= channel->channel_rcv_timestamp_offset;\n}\n\ninline bool aeron_udp_channel_is_channel_snd_timestamps_enabled(aeron_udp_channel_t *channel)\n{\n    return AERON_UDP_CHANNEL_RESERVED_VALUE_OFFSET == channel->channel_snd_timestamp_offset ||\n        0 <= channel->channel_snd_timestamp_offset;\n}\n\ninline bool aeron_udp_channel_is_multi_destination(const aeron_udp_channel_t *channel)\n{\n    return AERON_UDP_CHANNEL_CONTROL_MODE_DYNAMIC == channel->control_mode ||\n        AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL == channel->control_mode;\n}\n\ninline bool aeron_udp_channel_has_group_semantics(const aeron_udp_channel_t *channel)\n{\n    return channel->is_multicast || aeron_udp_channel_is_multi_destination(channel);\n}\n\n#endif //AERON_UDP_CHANNEL_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include \"util/aeron_platform.h\"\n\n#if defined(AERON_COMPILER_MSVC)\n#include <io.h>\n#else\n#include <unistd.h>\n#endif\n\n#include \"aeron_socket.h\"\n\n#include <string.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <time.h>\n#include <inttypes.h>\n#include \"util/aeron_error.h\"\n#include \"util/aeron_netutil.h\"\n#include \"aeron_udp_channel_transport.h\"\n#include \"aeron_driver_context.h\"\n\n#if defined(__linux__)\n#define HAS_MEDIA_RCV_TIMESTAMPS\n#include <linux/net_tstamp.h>\n#include <sys/ioctl.h>\n#include <linux/sockios.h>\n#endif\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\nstatic int aeron_udp_channel_transport_setup_media_rcv_timestamps(aeron_udp_channel_transport_t *transport)\n{\n#if defined(HAS_MEDIA_RCV_TIMESTAMPS)\n    uint32_t enable_timestamp = 1;\n    if (aeron_setsockopt(transport->fd, SOL_SOCKET, SO_TIMESTAMPNS, &enable_timestamp, sizeof(enable_timestamp)) < 0)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"setsockopt(SO_TIMESTAMPNS)\");\n        return -1;\n    }\n\n    uint32_t timestamp_flags = SOF_TIMESTAMPING_RX_HARDWARE;\n    if (aeron_setsockopt(transport->fd, SOL_SOCKET, SO_TIMESTAMPING, &timestamp_flags, sizeof(timestamp_flags)) < 0)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"setsockopt(SO_TIMESTAMPING)\");\n        return -1;\n    }\n\n    // The kernel does both falling back when required.  Essentially we just need a non-zero value for normal UDP.\n    transport->timestamp_flags = AERON_UDP_CHANNEL_TRANSPORT_MEDIA_RCV_TIMESTAMP;\n    return 0;\n#endif\n\n    AERON_SET_ERR(EINVAL, \"%s\", \"Timestamps are not supported on this platform\");\n    return -1;\n}\n\nint aeron_udp_channel_transport_init(\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *bind_addr,\n    struct sockaddr_storage *multicast_if_addr,\n    struct sockaddr_storage *connect_addr,\n    aeron_udp_channel_transport_params_t *params,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity)\n{\n    bool is_ipv6, is_multicast;\n    struct sockaddr_in *in4 = (struct sockaddr_in *)bind_addr;\n    struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)bind_addr;\n\n    transport->fd = -1;\n    transport->bindings_clientd = NULL;\n    transport->timestamp_flags = AERON_UDP_CHANNEL_TRANSPORT_MEDIA_RCV_TIMESTAMP_NONE;\n    transport->error_log = context->error_log;\n    transport->errors_counter = aeron_system_counter_addr(context->system_counters, AERON_SYSTEM_COUNTER_ERRORS);\n    for (size_t i = 0; i < AERON_UDP_CHANNEL_TRANSPORT_MAX_INTERCEPTORS; i++)\n    {\n        transport->interceptor_clientds[i] = NULL;\n    }\n\n    if (NULL == params)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"channel transport params is NULL\");\n        goto error;\n    }\n\n    if (0 == params->mtu_length)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"mtu_length must be greater than 0\");\n        goto error;\n    }\n\n    if ((transport->fd = aeron_socket(bind_addr->ss_family, SOCK_DGRAM, 0)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        goto error;\n    }\n    transport->recv_fd = transport->fd;\n\n    is_ipv6 = AF_INET6 == bind_addr->ss_family;\n    is_multicast = aeron_is_addr_multicast(bind_addr);\n    socklen_t bind_addr_len = is_ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);\n\n    if (!is_multicast)\n    {\n        if (aeron_bind(transport->recv_fd, (struct sockaddr *)bind_addr, bind_addr_len) < 0)\n        {\n            AERON_APPEND_ERR(\"unicast bind, affinity=%d\", affinity);\n            goto error;\n        }\n    }\n    else\n    {\n        if (NULL != connect_addr)\n        {\n            if ((transport->recv_fd = aeron_socket(bind_addr->ss_family, SOCK_DGRAM, 0)) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"\");\n                goto error;\n            }\n        }\n\n        int reuse = 1;\n\n#if defined(SO_REUSEADDR)\n        if (aeron_setsockopt(transport->recv_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)\n        {\n            AERON_APPEND_ERR(\"failed to set SOL_SOCKET/SO_REUSEADDR option to: %d\", reuse);\n            goto error;\n        }\n#endif\n\n#if defined(SO_REUSEPORT)\n        if (aeron_setsockopt(transport->recv_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) < 0)\n        {\n            AERON_SET_ERR(errno, \"%s\", \"setsockopt(SO_REUSEPORT)\");\n            goto error;\n        }\n#endif\n\n        if (is_ipv6)\n        {\n            struct sockaddr_in6 addr;\n            memcpy(&addr, bind_addr, sizeof(addr));\n            addr.sin6_addr = in6addr_any;\n\n            if (aeron_bind(transport->recv_fd, (struct sockaddr *)&addr, bind_addr_len) < 0)\n            {\n                AERON_APPEND_ERR(\"multicast IPv6 bind, affinity=%d\", affinity);\n                goto error;\n            }\n\n            struct ipv6_mreq mreq;\n\n            memcpy(&mreq.ipv6mr_multiaddr, &in6->sin6_addr, sizeof(in6->sin6_addr));\n            mreq.ipv6mr_interface = params->multicast_if_index;\n\n            if (aeron_setsockopt(transport->recv_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0)\n            {\n                char addr_buf[AERON_NETUTIL_FORMATTED_MAX_LENGTH] = { 0 };\n                inet_ntop(AF_INET6, &mreq.ipv6mr_multiaddr, addr_buf, sizeof(addr_buf));\n                AERON_APPEND_ERR(\n                    \"failed to set IPPROTO_IPV6/IPV6_JOIN_GROUP option to: struct ipv6_mreq{.ipv6mr_multiaddr=%s, .ipv6mr_interface=%u}\",\n                    addr_buf,\n                    mreq.ipv6mr_interface);\n                goto error;\n            }\n\n            if (aeron_setsockopt(\n                transport->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &params->multicast_if_index, sizeof(params->multicast_if_index)) < 0)\n            {\n                AERON_APPEND_ERR(\"failed to set IPPROTO_IPV6/IPV6_MULTICAST_IF option to: %u\", params->multicast_if_index);\n                goto error;\n            }\n\n            if (params->ttl > 0)\n            {\n                if (aeron_setsockopt(transport->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &params->ttl, sizeof(params->ttl)) < 0)\n                {\n                    AERON_APPEND_ERR(\n                        \"failed to set IPPROTO_IPV6/IPV6_MULTICAST_HOPS option to: %\" PRIu8, params->ttl);\n                    goto error;\n                }\n            }\n        }\n        else\n        {\n            struct sockaddr_in addr;\n            memcpy(&addr, bind_addr, sizeof(addr));\n            addr.sin_addr.s_addr = INADDR_ANY;\n\n            if (aeron_bind(transport->recv_fd, (struct sockaddr *)&addr, bind_addr_len) < 0)\n            {\n                AERON_APPEND_ERR(\"multicast IPv4 bind, affinity=%d\", affinity);\n                goto error;\n            }\n\n            struct ip_mreq mreq;\n            struct sockaddr_in *interface_addr = (struct sockaddr_in *)multicast_if_addr;\n\n            mreq.imr_multiaddr.s_addr = in4->sin_addr.s_addr;\n            mreq.imr_interface.s_addr = interface_addr->sin_addr.s_addr;\n\n            if (aeron_setsockopt(transport->recv_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)\n            {\n                char addr_buf[AERON_NETUTIL_FORMATTED_MAX_LENGTH] = { 0 };\n                char intr_buf[AERON_NETUTIL_FORMATTED_MAX_LENGTH] = { 0 };\n                inet_ntop(AF_INET, &mreq.imr_multiaddr, addr_buf, sizeof(addr_buf));\n                inet_ntop(AF_INET, &mreq.imr_interface, intr_buf, sizeof(intr_buf));\n                AERON_APPEND_ERR(\n                    \"failed to set IPPROTO_IP/IP_ADD_MEMBERSHIP option to: struct ip_mreq{.imr_multiaddr=%s, .imr_interface=%s}\",\n                    addr_buf,\n                    intr_buf);\n\n                goto error;\n            }\n\n            if (aeron_setsockopt(\n                transport->fd, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr->sin_addr, sizeof(struct in_addr)) < 0)\n            {\n                char intr_buf[AERON_NETUTIL_FORMATTED_MAX_LENGTH] = { 0 };\n                inet_ntop(AF_INET, &interface_addr->sin_addr, intr_buf, sizeof(intr_buf));\n                AERON_APPEND_ERR(\"failed to set IPPROTO_IP/IP_MULTICAST_IF option to: %s\", intr_buf);\n                goto error;\n            }\n\n            if (params->ttl > 0)\n            {\n                if (aeron_setsockopt(transport->fd, IPPROTO_IP, IP_MULTICAST_TTL, &params->ttl, sizeof(params->ttl)) < 0)\n                {\n                    AERON_APPEND_ERR(\n                        \"failed to set IPPROTO_IP/IP_MULTICAST_TTL option to: %\" PRIu8, params->ttl);\n                    goto error;\n                }\n            }\n        }\n    }\n\n    if (NULL != connect_addr)\n    {\n        if (aeron_connect(transport->fd, (struct sockaddr *)connect_addr, AERON_ADDR_LEN(connect_addr)) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            goto error;\n        }\n        transport->connected_address = connect_addr;\n    }\n\n    if (params->socket_rcvbuf > 0)\n    {\n        if (aeron_setsockopt(transport->recv_fd, SOL_SOCKET, SO_RCVBUF, &params->socket_rcvbuf, sizeof(params->socket_rcvbuf)) < 0)\n        {\n            AERON_APPEND_ERR(\n                \"failed to set SOL_SOCKET/SO_RCVBUF option to: %\" PRIu64, (uint64_t)params->socket_rcvbuf);\n            goto error;\n        }\n    }\n\n    if (params->socket_sndbuf > 0)\n    {\n        if (aeron_setsockopt(transport->fd, SOL_SOCKET, SO_SNDBUF, &params->socket_sndbuf, sizeof(params->socket_sndbuf)) < 0)\n        {\n            AERON_APPEND_ERR(\n                \"failed to set SOL_SOCKET/SO_SNDBUF option to: %\" PRIu64, (uint64_t)params->socket_sndbuf);\n            goto error;\n        }\n    }\n\n    if (params->is_media_timestamping)\n    {\n        if (aeron_udp_channel_transport_setup_media_rcv_timestamps(transport) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"WARNING: unable to setup media timestamping\");\n            aeron_distinct_error_log_record(context->error_log, aeron_errcode(), aeron_errmsg());\n            aeron_err_clear();\n        }\n    }\n\n    if (aeron_set_socket_non_blocking(transport->fd) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to set transport->fd to be non-blocking\");\n        goto error;\n    }\n\n    if (aeron_set_socket_non_blocking(transport->recv_fd) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to set transport->recv_fd to be non-blocking\");\n        goto error;\n    }\n\n\n    return 0;\n\nerror:\n    if (-1 != transport->fd)\n    {\n        aeron_close_socket(transport->fd);\n    }\n\n    transport->fd = -1;\n    return -1;\n}\n\nint aeron_udp_channel_transport_reconnect(\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *connect_addr)\n{\n    if (NULL != connect_addr && NULL != transport->connected_address)\n    {\n        if (aeron_connect(transport->fd, (struct sockaddr *)connect_addr, AERON_ADDR_LEN(connect_addr)) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        transport->connected_address = connect_addr;\n    }\n\n    return 0;\n}\n\nint aeron_udp_channel_transport_close(aeron_udp_channel_transport_t *transport)\n{\n    if (transport->fd != -1)\n    {\n        aeron_close_socket(transport->fd);\n    }\n    if (transport->recv_fd != transport->fd)\n    {\n        aeron_close_socket(transport->recv_fd);\n    }\n\n    return 0;\n}\n\nstatic inline int aeron_udp_channel_transport_recvmsg(\n    aeron_udp_channel_transport_t *transport,\n    struct mmsghdr *msgvec,\n    size_t vlen,\n    int64_t *bytes_rcved,\n    aeron_udp_transport_recv_func_t recv_func,\n    void *clientd)\n{\n    struct timespec *media_rcv_timestamp = NULL;\n#if defined(HAS_MEDIA_RCV_TIMESTAMPS)\n    AERON_DECL_ALIGNED(\n        char buf[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX][CMSG_SPACE(sizeof(struct timespec))],\n        sizeof(struct cmsghdr));\n\n    if (transport->timestamp_flags)\n    {\n        for (int i = 0; i < (int)vlen; i++)\n        {\n            msgvec[i].msg_hdr.msg_control = (void *)buf[i];\n            msgvec[i].msg_hdr.msg_controllen = CMSG_LEN(sizeof(buf[i]));\n        }\n    }\n#endif\n\n    int work_count = 0;\n\n    for (size_t i = 0, length = vlen; i < length; i++)\n    {\n        ssize_t result = aeron_recvmsg(transport->recv_fd, &msgvec[i].msg_hdr, 0);\n\n        if (result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        if (0 == result)\n        {\n            break;\n        }\n\n#if defined(HAS_MEDIA_RCV_TIMESTAMPS)\n        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgvec[i].msg_hdr);\n        if (NULL != cmsg &&\n            SOL_SOCKET == cmsg->cmsg_level &&\n            SCM_TIMESTAMPNS == cmsg->cmsg_type &&\n            CMSG_LEN(sizeof(struct timespec)) == cmsg->cmsg_len)\n        {\n            media_rcv_timestamp = (struct timespec *)CMSG_DATA(cmsg);\n        }\n#endif\n\n        msgvec[i].msg_len = (unsigned int)result;\n        recv_func(\n            transport->data_paths,\n            transport,\n            clientd,\n            transport->dispatch_clientd,\n            transport->destination_clientd,\n            msgvec[i].msg_hdr.msg_iov[0].iov_base,\n            msgvec[i].msg_len,\n            msgvec[i].msg_hdr.msg_name,\n            media_rcv_timestamp);\n        *bytes_rcved += msgvec[i].msg_len;\n        work_count++;\n    }\n\n    return work_count;\n}\n\nint aeron_udp_channel_transport_recvmmsg(\n    aeron_udp_channel_transport_t *transport,\n    struct mmsghdr *msgvec,\n    size_t vlen,\n    int64_t *bytes_rcved,\n    aeron_udp_transport_recv_func_t recv_func,\n    void *clientd)\n{\n#if defined(HAVE_RECVMMSG)\n    if (vlen > 1)\n    {\n        struct timespec tv = { .tv_nsec = 0, .tv_sec = 0 };\n        struct timespec *media_rcv_timestamp = NULL;\n#if defined(HAS_MEDIA_RCV_TIMESTAMPS)\n        AERON_DECL_ALIGNED(\n            char buf[AERON_DRIVER_RECEIVER_IO_VECTOR_LENGTH_MAX][CMSG_SPACE(sizeof(struct timespec))],\n            sizeof(struct cmsghdr));\n\n        if (transport->timestamp_flags)\n        {\n            for (int i = 0; i < (int)vlen; i++)\n            {\n                msgvec[i].msg_hdr.msg_control = (void *)buf[i];\n                msgvec[i].msg_hdr.msg_controllen = CMSG_LEN(sizeof(buf[i]));\n            }\n        }\n#endif\n\n        int result = recvmmsg(transport->recv_fd, msgvec, vlen, 0, &tv);\n        if (result < 0)\n        {\n            int err = errno;\n\n            // ECONNREFUSED can sometimes occur with connected UDP sockets if ICMP traffic is able to indicate that the\n            // remote end had closed on a previous send.\n            if (EINTR == err || EAGAIN == err || ECONNREFUSED == err)\n            {\n                return 0;\n            }\n\n            AERON_SET_ERR(\n                err,\n                \"Failed to recvmmsg, fd=%d, recv_fd=%d, connected=%s\",\n                transport->fd,\n                transport->recv_fd,\n                NULL != transport->connected_address ? \"true\" : \"false\");\n\n            return -1;\n        }\n        else if (0 == result)\n        {\n            return 0;\n        }\n        else\n        {\n            for (size_t i = 0, length = (size_t)result; i < length; i++)\n            {\n#if defined(HAS_MEDIA_RCV_TIMESTAMPS)\n                struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgvec[i].msg_hdr);\n                if (NULL != cmsg &&\n                    SOL_SOCKET == cmsg->cmsg_level &&\n                    SCM_TIMESTAMPNS == cmsg->cmsg_type &&\n                    CMSG_LEN(sizeof(struct timespec)) == cmsg->cmsg_len)\n                {\n                    media_rcv_timestamp = (struct timespec *)CMSG_DATA(cmsg);\n                }\n#endif\n\n                recv_func(\n                    transport->data_paths,\n                    transport,\n                    clientd,\n                    transport->dispatch_clientd,\n                    transport->destination_clientd,\n                    msgvec[i].msg_hdr.msg_iov[0].iov_base,\n                    msgvec[i].msg_len,\n                    msgvec[i].msg_hdr.msg_name,\n                    media_rcv_timestamp);\n                *bytes_rcved += msgvec[i].msg_len;\n            }\n\n            return result;\n        }\n    }\n    else\n#endif\n    {\n        return aeron_udp_channel_transport_recvmsg(transport, msgvec, vlen, bytes_rcved, recv_func, clientd);\n    }\n}\n\nstatic int aeron_udp_channel_transport_send_connected(\n    aeron_udp_channel_transport_t *transport,\n    struct iovec *iov,\n    int64_t *bytes_sent)\n{\n    ssize_t send_result = aeron_send(transport->fd, iov->iov_base, iov->iov_len, 0);\n    if (send_result < 0)\n    {\n        *bytes_sent = 0;\n        char addr[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n        aeron_format_source_identity(addr, sizeof(addr), transport->connected_address);\n        AERON_APPEND_ERR(\"address=%s (protocol_family=%i)\", addr, transport->connected_address->ss_family);\n        return -1;\n    }\n    else\n    {\n        *bytes_sent += send_result;\n        return 0 == send_result ? 0 : 1;\n    }\n}\n\n#if !defined(HAVE_SENDMMSG)\nstatic int aeron_udp_channel_transport_send_unconnected(\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    int64_t *bytes_sent)\n{\n    struct msghdr msg;\n    msg.msg_control = NULL;\n    msg.msg_controllen = 0;\n    msg.msg_name = address;\n    msg.msg_namelen = AERON_ADDR_LEN(address);\n    msg.msg_iovlen = 1;\n    msg.msg_flags = 0;\n    msg.msg_iov = iov;\n\n    ssize_t send_result = aeron_sendmsg(transport->fd, &msg, 0);\n    if (send_result < 0)\n    {\n        char addr[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n        aeron_format_source_identity(addr, sizeof(addr), address);\n        AERON_APPEND_ERR(\"address=%s\", addr);\n        return -1;\n    }\n    else\n    {\n        *bytes_sent += send_result;\n        return (int)send_result;\n    }\n}\n#endif\n\n#if defined(HAVE_SENDMMSG)\n\nstatic int aeron_udp_channel_transport_sendv(\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n    struct mmsghdr msg[AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND];\n    size_t msg_i;\n\n    size_t msg_namelen = address == NULL ? 0 : AERON_ADDR_LEN(address);\n\n    for (msg_i = 0; msg_i < iov_length && msg_i < AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND; msg_i++)\n    {\n        msg[msg_i].msg_hdr.msg_control = NULL;\n        msg[msg_i].msg_hdr.msg_controllen = 0;\n        msg[msg_i].msg_hdr.msg_name = address;\n        msg[msg_i].msg_hdr.msg_namelen = msg_namelen;\n        msg[msg_i].msg_hdr.msg_flags = 0;\n        msg[msg_i].msg_hdr.msg_iov = &iov[msg_i];\n        msg[msg_i].msg_hdr.msg_iovlen = 1;\n        msg[msg_i].msg_len = 0;\n    }\n\n    int num_sent = sendmmsg(transport->fd, msg, msg_i, 0);\n    if (num_sent < 0)\n    {\n        if (EAGAIN == errno || EWOULDBLOCK == errno || ECONNREFUSED == errno || EINTR == errno)\n        {\n            return 0;\n        }\n        else\n        {\n            char addr[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n            aeron_format_source_identity(addr, sizeof(addr), address);\n            AERON_SET_ERR(errno, \"%s: address=%s (protocol_family=%i)\", \"failed to sendmmsg\", addr, address->ss_family);\n            return -1;\n        }\n    }\n    else\n    {\n        for (int i = 0; i < num_sent; i++)\n        {\n            *bytes_sent += msg[i].msg_len;\n        }\n\n        return num_sent;\n    }\n}\n\n#endif\n\n\nint aeron_udp_channel_transport_send(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n#if defined(HAVE_SENDMMSG)\n    if (NULL != transport->connected_address)\n    {\n        if (1 == iov_length)\n        {\n            return aeron_udp_channel_transport_send_connected(transport, iov, bytes_sent);\n        }\n        else\n        {\n            return aeron_udp_channel_transport_sendv(transport, NULL, iov, iov_length, bytes_sent);\n        }\n    }\n    else\n    {\n        return aeron_udp_channel_transport_sendv(transport, address, iov, iov_length, bytes_sent);\n    }\n#else\n    int result = 0;\n    if (NULL != transport->connected_address)\n    {\n        for (size_t i = 0; i < iov_length; i++)\n        {\n            int send_result = aeron_udp_channel_transport_send_connected(transport, &iov[i], bytes_sent);\n            if (send_result < 0)\n            {\n                result = -1;\n                break;\n            }\n            else if (0 == send_result)\n            {\n                break;\n            }\n            else\n            {\n                result++;\n            }\n        }\n    }\n    else\n    {\n        for (size_t i = 0; i < iov_length; i++)\n        {\n            int send_result = aeron_udp_channel_transport_send_unconnected(transport, address, &iov[i], bytes_sent);\n            if (send_result < 0)\n            {\n                result = -1;\n                break;\n            }\n            else if (0 == send_result)\n            {\n                break;\n            }\n            else\n            {\n                result++;\n            }\n        }\n    }\n    return result;\n#endif\n}\n\nint aeron_udp_channel_transport_get_so_rcvbuf(aeron_udp_channel_transport_t *transport, size_t *so_rcvbuf)\n{\n    socklen_t len = sizeof(size_t);\n\n    if (aeron_getsockopt(transport->recv_fd, SOL_SOCKET, SO_RCVBUF, so_rcvbuf, &len) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"failed to get SOL_SOCKET/SO_RCVBUF option\");\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_udp_channel_transport_bind_addr_and_port(\n    aeron_udp_channel_transport_t *transport, char *buffer, size_t length)\n{\n    struct sockaddr_storage addr;\n    socklen_t addr_len = sizeof(addr);\n\n    if (getsockname(transport->recv_fd, (struct sockaddr *)&addr, &addr_len) < 0)\n    {\n        AERON_SET_ERR(errno, \"Failed to get socket name for fd: %d\", transport->fd);\n        return -1;\n    }\n\n    return aeron_format_source_identity(buffer, length, &addr);\n}\n\nextern void *aeron_udp_channel_transport_get_interceptor_clientd(\n    aeron_udp_channel_transport_t *transport, int interceptor_index);\n\nextern void aeron_udp_channel_transport_set_interceptor_clientd(\n    aeron_udp_channel_transport_t *transport, int interceptor_index, void *clientd);\n\nextern void aeron_udp_channel_transport_log_error(aeron_udp_channel_transport_t *transport);\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_UDP_CHANNEL_TRANSPORT_H\n#define AERON_UDP_CHANNEL_TRANSPORT_H\n\n#include \"aeron_socket.h\"\n#include \"aeron_driver_common.h\"\n#include \"aeron_udp_channel_transport_bindings.h\"\n#include \"concurrent/aeron_distinct_error_log.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"util/aeron_error.h\"\n\n#define AERON_UDP_CHANNEL_TRANSPORT_MEDIA_RCV_TIMESTAMP_NONE (0x0)\n#define AERON_UDP_CHANNEL_TRANSPORT_MEDIA_RCV_TIMESTAMP_HW (0x1)\n#define AERON_UDP_CHANNEL_TRANSPORT_MEDIA_RCV_TIMESTAMP_SW (0x2)\n#define AERON_UDP_CHANNEL_TRANSPORT_MEDIA_RCV_TIMESTAMP (0x3)\n#define AERON_UDP_CHANNEL_TRANSPORT_CHANNEL_RCV_TIMESTAMP (0x4)\n#define AERON_UDP_CHANNEL_TRANSPORT_CHANNEL_SND_TIMESTAMP (0x8)\n\nstruct aeron_udp_channel_transport_params_stct\n{\n    size_t socket_rcvbuf;\n    size_t socket_sndbuf;\n    size_t mtu_length;\n    unsigned int multicast_if_index;\n    uint8_t ttl;\n    bool is_media_timestamping;\n};\ntypedef struct aeron_udp_channel_transport_params_stct aeron_udp_channel_transport_params_t;\n\ntypedef struct aeron_udp_channel_transport_stct\n{\n    aeron_socket_t fd;\n    aeron_socket_t recv_fd;\n    aeron_udp_channel_data_paths_t *data_paths;\n    struct sockaddr_storage *connected_address;\n    void *dispatch_clientd;\n    void *bindings_clientd;\n    void *destination_clientd;\n    void *interceptor_clientds[AERON_UDP_CHANNEL_TRANSPORT_MAX_INTERCEPTORS];\n    aeron_distinct_error_log_t *error_log;\n    int64_t *errors_counter;\n    uint32_t timestamp_flags;\n}\naeron_udp_channel_transport_t;\n\nstruct mmsghdr;\n\nint aeron_udp_channel_transport_init(\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *bind_addr,\n    struct sockaddr_storage *multicast_if_addr,\n    struct sockaddr_storage *connect_addr,\n    aeron_udp_channel_transport_params_t *params,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity);\n\nint aeron_udp_channel_transport_reconnect(\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *connect_addr);\n\nint aeron_udp_channel_transport_close(aeron_udp_channel_transport_t *transport);\n\nint aeron_udp_channel_transport_recvmmsg(\n    aeron_udp_channel_transport_t *transport,\n    struct mmsghdr *msgvec,\n    size_t vlen,\n    int64_t *bytes_rcved,\n    aeron_udp_transport_recv_func_t recv_func,\n    void *clientd);\n\nint aeron_udp_channel_transport_send(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent);\n\nint aeron_udp_channel_transport_get_so_rcvbuf(aeron_udp_channel_transport_t *transport, size_t *so_rcvbuf);\nint aeron_udp_channel_transport_bind_addr_and_port(\n    aeron_udp_channel_transport_t *transport, char *buffer, size_t length);\n\ninline void *aeron_udp_channel_transport_get_interceptor_clientd(\n    aeron_udp_channel_transport_t *transport, int interceptor_index)\n{\n    return transport->interceptor_clientds[interceptor_index];\n}\n\ninline void aeron_udp_channel_transport_set_interceptor_clientd(\n    aeron_udp_channel_transport_t *transport, int interceptor_index, void *clientd)\n{\n    transport->interceptor_clientds[interceptor_index] = clientd;\n}\n\ninline void aeron_udp_channel_transport_log_error(aeron_udp_channel_transport_t *transport)\n{\n    aeron_distinct_error_log_record(transport->error_log, aeron_errcode(), aeron_errmsg());\n    aeron_counter_increment(transport->errors_counter);\n    aeron_err_clear();\n}\n\n#endif //AERON_UDP_CHANNEL_TRANSPORT_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport_bindings.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <string.h>\n#include <inttypes.h>\n\n#include \"aeron_alloc.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_dlopen.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_symbol_table.h\"\n#include \"aeron_udp_channel_transport_loss.h\"\n#include \"aeron_udp_channel_transport_fixed_loss.h\"\n#include \"aeron_udp_channel_transport_multi_gap_loss.h\"\n#include \"aeron_udp_channel_transport_bindings.h\"\n#include \"aeron_udp_channel_transport.h\"\n#include \"aeron_udp_transport_poller.h\"\n\naeron_udp_channel_transport_bindings_t aeron_udp_channel_transport_bindings_default =\n    {\n        aeron_udp_channel_transport_init,\n        aeron_udp_channel_transport_reconnect,\n        aeron_udp_channel_transport_close,\n        aeron_udp_channel_transport_recvmmsg,\n        aeron_udp_channel_transport_send,\n        aeron_udp_channel_transport_get_so_rcvbuf,\n        aeron_udp_channel_transport_bind_addr_and_port,\n        aeron_udp_transport_poller_init,\n        aeron_udp_transport_poller_close,\n        aeron_udp_transport_poller_add,\n        aeron_udp_transport_poller_remove,\n        aeron_udp_transport_poller_poll,\n        {\n            \"default\",\n            \"media\",\n            NULL,\n        }\n    };\n\nstatic const aeron_symbol_table_obj_t aeron_udp_channel_transport_bindings_table[] =\n    {\n        {\n            \"default\",\n            \"aeron_udp_channel_transport_bindings_default\",\n            (void *)&aeron_udp_channel_transport_bindings_default\n        },\n    };\n\nstatic const size_t aeron_udp_channel_transport_bindings_table_length =\n    sizeof(aeron_udp_channel_transport_bindings_table) / sizeof (aeron_symbol_table_obj_t);\n\nstatic const aeron_symbol_table_func_t aeron_udp_channel_interceptor_table[] =\n    {\n        {\n            \"loss\",\n            \"aeron_udp_channel_interceptor_loss_load\",\n            (aeron_fptr_t)aeron_udp_channel_interceptor_loss_load\n        },\n        {\n            \"fixed-loss\",\n            \"aeron_udp_channel_interceptor_fixed_loss_load\",\n            (aeron_fptr_t)aeron_udp_channel_interceptor_fixed_loss_load\n        },\n        {\n            \"multi-gap-loss\",\n            \"aeron_udp_channel_interceptor_multi_gap_loss_load\",\n            (aeron_fptr_t)aeron_udp_channel_interceptor_multi_gap_loss_load\n        }\n    };\n\nstatic const size_t aeron_udp_channel_interceptor_table_length =\n    sizeof(aeron_udp_channel_interceptor_table) / sizeof(aeron_symbol_table_func_t);\n\naeron_udp_channel_transport_bindings_t *aeron_udp_channel_transport_bindings_load_media(const char *bindings_name)\n{\n    aeron_udp_channel_transport_bindings_t *bindings = aeron_symbol_table_obj_load(\n        aeron_udp_channel_transport_bindings_table,\n        aeron_udp_channel_transport_bindings_table_length,\n        bindings_name,\n        \"udp channel bindings\");\n\n    if (NULL == bindings_name)\n    {\n        AERON_SET_ERR(EINVAL, \"%s\", \"invalid UDP channel transport bindings name\");\n        return NULL;\n    }\n\n    bindings->meta_info.source_symbol = bindings;\n\n    return bindings;\n}\n\nstatic aeron_udp_channel_interceptor_bindings_load_func_t *aeron_udp_channel_interceptor_bindings_load_interceptor(\n    const char *interceptor_name)\n{\n    return (aeron_udp_channel_interceptor_bindings_load_func_t *)aeron_symbol_table_func_load(\n        aeron_udp_channel_interceptor_table,\n        aeron_udp_channel_interceptor_table_length,\n        interceptor_name,\n        \"interceptor bindings\");\n}\n\n#define AERON_MAX_INTERCEPTORS_LEN (4094)\n#define AERON_MAX_INTERCEPTOR_NAMES (10)\n\naeron_udp_channel_interceptor_bindings_t *aeron_udp_channel_interceptor_bindings_load(\n    aeron_udp_channel_interceptor_bindings_t *existing_interceptor_bindings,\n    const char *interceptors)\n{\n    char interceptors_dup[AERON_MAX_INTERCEPTORS_LEN] = { 0 };\n    char *interceptor_names[AERON_MAX_INTERCEPTOR_NAMES] = { 0 };\n    aeron_udp_channel_interceptor_bindings_t *current_bindings = NULL;\n    const size_t interceptors_length = strlen(interceptors);\n\n    if (interceptors_length >= (size_t)AERON_MAX_INTERCEPTORS_LEN)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"Interceptors list too long, must have: %\" PRIu64 \" < %d\",\n            (uint64_t)interceptors_length,\n            AERON_MAX_INTERCEPTORS_LEN);\n        return NULL;\n    }\n\n    strncpy(interceptors_dup, interceptors, AERON_MAX_INTERCEPTORS_LEN - 1);\n\n    const int num_interceptors = aeron_tokenise(\n        interceptors_dup, ',', AERON_MAX_INTERCEPTOR_NAMES, interceptor_names);\n\n    if (-ERANGE == num_interceptors)\n    {\n        AERON_SET_ERR(\n            EINVAL, \"Too many interceptors defined, limit %d: %s\", AERON_MAX_INTERCEPTOR_NAMES, interceptors);\n        return NULL;\n    }\n    else if (num_interceptors < 0)\n    {\n        AERON_SET_ERR(EINVAL, \"Failed to parse interceptors: %s\", interceptors != NULL ? interceptors : \"(null)\");\n        return NULL;\n    }\n\n    current_bindings = existing_interceptor_bindings;\n\n    for (int i = 0; i < num_interceptors; i++)\n    {\n        const char *interceptor_name = interceptor_names[i];\n\n        aeron_udp_channel_interceptor_bindings_load_func_t *interceptor_load_func =\n            aeron_udp_channel_interceptor_bindings_load_interceptor(interceptor_name);\n\n        if (NULL == interceptor_load_func)\n        {\n            return NULL;\n        }\n\n        current_bindings = interceptor_load_func(current_bindings);\n        if (NULL == current_bindings)\n        {\n            AERON_APPEND_ERR(\"Failed to load interceptor bindings: %s\", interceptor_name);\n            return NULL;\n        }\n\n        current_bindings->meta_info.source_symbol = (aeron_fptr_t)interceptor_load_func;\n    }\n\n    return current_bindings;\n}\n\nint aeron_udp_channel_data_paths_init(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_interceptor_bindings_t *outgoing_interceptor_bindings,\n    aeron_udp_channel_interceptor_bindings_t *incoming_interceptor_bindings,\n    aeron_udp_channel_transport_bindings_t *media_bindings,\n    aeron_udp_transport_recv_func_t recv_func,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity)\n{\n    data_paths->outgoing_interceptors = NULL;\n    data_paths->incoming_interceptors = NULL;\n\n    /* if no interceptors, then use sendmmsg_func from transport bindings. */\n    data_paths->send_func = media_bindings->send_func;\n    /* if no interceptors, then use passed in recv_func */\n    data_paths->recv_func = recv_func;\n\n    if (NULL != outgoing_interceptor_bindings)\n    {\n        aeron_udp_channel_outgoing_interceptor_t *last_outgoing_interceptor = NULL;\n        aeron_udp_channel_outgoing_interceptor_t *outgoing_transport_interceptor = NULL;\n\n        for (\n            const aeron_udp_channel_interceptor_bindings_t *binding = outgoing_interceptor_bindings;\n            NULL != binding;\n            binding = binding->meta_info.next_interceptor_bindings)\n        {\n            aeron_udp_channel_outgoing_interceptor_t *interceptor;\n\n            if (aeron_alloc((void **)&interceptor, sizeof(aeron_udp_channel_outgoing_interceptor_t)) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"Outgoing interceptor for UDP transport bindings\");\n                return -1;\n            }\n\n            interceptor->interceptor_state = NULL;\n            interceptor->outgoing_send_func = binding->outgoing_send_func;\n            interceptor->close_func = binding->outgoing_close_func;\n            interceptor->outgoing_transport_notification_func = binding->outgoing_transport_notification_func;\n            interceptor->outgoing_publication_notification_func = binding->outgoing_publication_notification_func;\n            interceptor->outgoing_image_notification_func = binding->outgoing_image_notification_func;\n            interceptor->next_interceptor = NULL;\n\n            if (binding->outgoing_init_func(&interceptor->interceptor_state, context, affinity) < 0)\n            {\n                return -1;\n            }\n\n            if (NULL == last_outgoing_interceptor)\n            {\n                data_paths->outgoing_interceptors = interceptor;\n            }\n            else\n            {\n                last_outgoing_interceptor->next_interceptor = interceptor;\n            }\n\n            last_outgoing_interceptor = interceptor;\n        }\n\n        if (aeron_alloc((void **)&outgoing_transport_interceptor, sizeof(aeron_udp_channel_outgoing_interceptor_t)) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Last outgoing interceptor for UDP transport bindings\");\n            return -1;\n        }\n\n        outgoing_transport_interceptor->interceptor_state = media_bindings;\n\n        /* last interceptor calls sendmmsg_func/sendmsg_func from transport bindings */\n        outgoing_transport_interceptor->outgoing_send_func = aeron_udp_channel_outgoing_interceptor_send_to_transport;\n        outgoing_transport_interceptor->close_func = NULL;\n        outgoing_transport_interceptor->next_interceptor = NULL;\n        last_outgoing_interceptor->next_interceptor = outgoing_transport_interceptor;\n        /* set up to pass into interceptors */\n        data_paths->send_func = aeron_udp_channel_outgoing_interceptor_send;\n    }\n\n    if (NULL != incoming_interceptor_bindings)\n    {\n        aeron_udp_channel_incoming_interceptor_t *last_incoming_interceptor = NULL;\n        aeron_udp_channel_incoming_interceptor_t *incoming_transport_interceptor = NULL;\n\n        for (\n            const aeron_udp_channel_interceptor_bindings_t *binding = incoming_interceptor_bindings;\n            NULL != binding;\n            binding = binding->meta_info.next_interceptor_bindings)\n        {\n            aeron_udp_channel_incoming_interceptor_t *interceptor;\n\n            if (aeron_alloc((void **)&interceptor, sizeof(aeron_udp_channel_incoming_interceptor_t)) < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"Incoming interceptor for UDP transport bindings\");\n                return -1;\n            }\n\n            interceptor->interceptor_state = NULL;\n            interceptor->incoming_func = binding->incoming_func;\n            interceptor->close_func = binding->incoming_close_func;\n            interceptor->incoming_transport_notification_func = binding->incoming_transport_notification_func;\n            interceptor->incoming_publication_notification_func = binding->incoming_publication_notification_func;\n            interceptor->incoming_image_notification_func = binding->incoming_image_notification_func;\n            interceptor->next_interceptor = NULL;\n\n            if (binding->incoming_init_func(&interceptor->interceptor_state, context, affinity) < 0)\n            {\n                return -1;\n            }\n\n            if (NULL == last_incoming_interceptor)\n            {\n                data_paths->incoming_interceptors = interceptor;\n            }\n            else\n            {\n                last_incoming_interceptor->next_interceptor = interceptor;\n            }\n\n            last_incoming_interceptor = interceptor;\n        }\n\n        if (aeron_alloc(\n            (void **)&incoming_transport_interceptor, sizeof(aeron_udp_channel_incoming_interceptor_t)) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Last incoming interceptor for UDP transport bindings\");\n            return -1;\n        }\n\n        aeron_udp_channel_transport_recv_func_holder_t *recv_function_holder = NULL;\n        if (aeron_alloc(\n            (void **)&recv_function_holder, sizeof(aeron_udp_channel_transport_recv_func_holder_t)) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"Function holder for last incoming interceptor for UDP transport bindings\");\n            return -1;\n        }\n        recv_function_holder->func = recv_func;\n\n        incoming_transport_interceptor->interceptor_state = recv_function_holder;\n        incoming_transport_interceptor->incoming_func = aeron_udp_channel_incoming_interceptor_to_endpoint;\n        incoming_transport_interceptor->close_func = aeron_udp_channel_transport_recv_func_holder_close;\n        incoming_transport_interceptor->next_interceptor = NULL;\n        last_incoming_interceptor->next_interceptor = incoming_transport_interceptor;\n        data_paths->recv_func = aeron_udp_channel_incoming_interceptor_recv_func;\n    }\n\n    return 0;\n}\n\nint aeron_udp_channel_data_paths_delete(aeron_udp_channel_data_paths_t *data_paths)\n{\n    if (NULL != data_paths->outgoing_interceptors)\n    {\n        aeron_udp_channel_outgoing_interceptor_t *interceptor;\n\n        while ((interceptor = data_paths->outgoing_interceptors) != NULL)\n        {\n            if (NULL != interceptor->close_func)\n            {\n                interceptor->close_func(interceptor->interceptor_state);\n            }\n\n            data_paths->outgoing_interceptors = interceptor->next_interceptor;\n            aeron_free(interceptor);\n        }\n    }\n\n    if (NULL != data_paths->incoming_interceptors)\n    {\n        aeron_udp_channel_incoming_interceptor_t *interceptor;\n\n        while ((interceptor = data_paths->incoming_interceptors) != NULL)\n        {\n            if (NULL != interceptor->close_func)\n            {\n                interceptor->close_func(interceptor->interceptor_state);\n            }\n\n            data_paths->incoming_interceptors = interceptor->next_interceptor;\n            aeron_free(interceptor);\n        }\n    }\n\n    return 0;\n}\n\nint aeron_udp_channel_transport_recv_func_holder_close(void *holder)\n{\n    aeron_free(holder);\n    return 0;\n}\n\nvoid aeron_udp_channel_incoming_interceptor_recv_func(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp)\n{\n    aeron_udp_channel_incoming_interceptor_t *interceptor = data_paths->incoming_interceptors;\n\n    interceptor->incoming_func(\n        interceptor->interceptor_state,\n        interceptor->next_interceptor,\n        transport,\n        receiver_clientd,\n        endpoint_clientd,\n        destination_clientd,\n        buffer,\n        length,\n        addr,\n        media_timestamp);\n}\n\nextern int aeron_udp_channel_outgoing_interceptor_send(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent);\n\nextern int aeron_udp_channel_outgoing_interceptor_mmsg_to_transport(\n    void *interceptor_state,\n    aeron_udp_channel_outgoing_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    struct mmsghdr *msgvec,\n    size_t vlen);\n\nextern int aeron_udp_channel_outgoing_interceptor_msg_to_transport(\n    void *interceptor_state,\n    aeron_udp_channel_outgoing_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    struct msghdr *message);\n\nextern int aeron_udp_channel_outgoing_interceptor_send_to_transport(\n    void *interceptor_state,\n    aeron_udp_channel_outgoing_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent);\n\nextern void aeron_udp_channel_incoming_interceptor_to_endpoint(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp);\n\nextern int aeron_udp_channel_interceptors_transport_notifications(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    const aeron_udp_channel_t *udp_channel,\n    aeron_data_packet_dispatcher_t *data_packet_dispatcher,\n    aeron_udp_channel_interceptor_notification_type_t type);\n\nextern int aeron_udp_channel_interceptors_publication_notifications(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    aeron_network_publication_t *publication,\n    aeron_udp_channel_interceptor_notification_type_t type);\n\nextern int aeron_udp_channel_interceptors_image_notifications(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    aeron_publication_image_t *image,\n    aeron_udp_channel_interceptor_notification_type_t type);\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport_bindings.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_H\n#define AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_H\n\n#include \"uri/aeron_driver_uri.h\"\n#include \"aeron_socket.h\"\n#include \"aeron_driver_common.h\"\n\n#define AERON_UDP_CHANNEL_TRANSPORT_MAX_INTERCEPTORS (2)\n\ntypedef enum aeron_udp_channel_transport_affinity_en\n{\n    AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_SENDER,\n    AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_RECEIVER,\n    AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_CONDUCTOR\n}\naeron_udp_channel_transport_affinity_t;\n\nstruct mmsghdr;\ntypedef struct aeron_udp_channel_transport_params_stct aeron_udp_channel_transport_params_t;\ntypedef struct aeron_udp_channel_transport_stct aeron_udp_channel_transport_t;\ntypedef struct aeron_network_publication_stct aeron_network_publication_t;\ntypedef struct aeron_publication_image_stct aeron_publication_image_t;\ntypedef struct aeron_udp_transport_poller_stct aeron_udp_transport_poller_t;\ntypedef struct aeron_udp_channel_data_paths_stct aeron_udp_channel_data_paths_t;\ntypedef struct aeron_data_packet_dispatcher_stct aeron_data_packet_dispatcher_t;\n\ntypedef int (*aeron_udp_channel_transport_init_func_t)(\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *bind_addr,\n    struct sockaddr_storage *multicast_if_addr,\n    struct sockaddr_storage *connect_addr,\n    aeron_udp_channel_transport_params_t *params,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity);\n\ntypedef int (*aeron_udp_channel_transport_reconnect_func_t)(\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *connect_addr);\n\ntypedef int (*aeron_udp_channel_transport_close_func_t)(aeron_udp_channel_transport_t *transport);\n\ntypedef void (*aeron_udp_transport_recv_func_t)(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp);\n\ntypedef int (*aeron_udp_channel_transport_recvmmsg_func_t)(\n    aeron_udp_channel_transport_t *transport,\n    struct mmsghdr *msgvec,\n    size_t vlen,\n    int64_t *bytes_rcved,\n    aeron_udp_transport_recv_func_t recv_func,\n    void *clientd);\n\ntypedef int (*aeron_udp_channel_transport_send_func_t)(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent);\n\ntypedef int (*aeron_udp_channel_transport_get_so_rcvbuf_func_t)(\n    aeron_udp_channel_transport_t *transport, size_t *so_rcvbuf);\n\ntypedef int (*aeron_udp_channel_transport_bind_addr_and_port_func_t)(\n    aeron_udp_channel_transport_t *transport, char *buffer, size_t length);\n\ntypedef int (*aeron_udp_transport_poller_init_func_t)(\n    aeron_udp_transport_poller_t *poller,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity);\ntypedef int (*aeron_udp_transport_poller_close_func_t)(aeron_udp_transport_poller_t *poller);\n\ntypedef int (*aeron_udp_transport_poller_add_func_t)(\n    aeron_udp_transport_poller_t *poller, aeron_udp_channel_transport_t *transport);\ntypedef int (*aeron_udp_transport_poller_remove_func_t)(\n    aeron_udp_transport_poller_t *poller, aeron_udp_channel_transport_t *transport);\n\ntypedef int (*aeron_udp_transport_poller_poll_func_t)(\n    aeron_udp_transport_poller_t *poller,\n    struct mmsghdr *msgvec,\n    size_t vlen,\n    int64_t *bytes_rcved,\n    aeron_udp_transport_recv_func_t recv_func,\n    aeron_udp_channel_transport_recvmmsg_func_t recvmmsg_func,\n    void *clientd);\n\ntypedef struct aeron_udp_channel_transport_bindings_stct aeron_udp_channel_transport_bindings_t;\n\nstruct aeron_udp_channel_transport_bindings_stct\n{\n    aeron_udp_channel_transport_init_func_t init_func;\n    aeron_udp_channel_transport_reconnect_func_t reconnect_func;\n    aeron_udp_channel_transport_close_func_t close_func;\n    aeron_udp_channel_transport_recvmmsg_func_t recvmmsg_func;\n    aeron_udp_channel_transport_send_func_t send_func;\n    aeron_udp_channel_transport_get_so_rcvbuf_func_t get_so_rcvbuf_func;\n    aeron_udp_channel_transport_bind_addr_and_port_func_t bind_addr_and_port_func;\n    aeron_udp_transport_poller_init_func_t poller_init_func;\n    aeron_udp_transport_poller_close_func_t poller_close_func;\n    aeron_udp_transport_poller_add_func_t poller_add_func;\n    aeron_udp_transport_poller_remove_func_t poller_remove_func;\n    aeron_udp_transport_poller_poll_func_t poller_poll_func;\n    struct meta_info_fields\n    {\n        const char *name;\n        const char *type;\n        const void *source_symbol;\n    }\n    meta_info;\n};\n\naeron_udp_channel_transport_bindings_t *aeron_udp_channel_transport_bindings_load_media(const char *bindings_name);\n\ntypedef struct aeron_udp_channel_interceptor_bindings_stct aeron_udp_channel_interceptor_bindings_t;\n\ntypedef aeron_udp_channel_interceptor_bindings_t *(aeron_udp_channel_interceptor_bindings_load_func_t)(\n    aeron_udp_channel_interceptor_bindings_t *delegate_bindings);\n\naeron_udp_channel_interceptor_bindings_t *aeron_udp_channel_interceptor_bindings_load(\n    aeron_udp_channel_interceptor_bindings_t *existing_interceptor_bindings,\n    const char *interceptors);\n\ntypedef struct aeron_udp_channel_outgoing_interceptor_stct aeron_udp_channel_outgoing_interceptor_t;\ntypedef struct aeron_udp_channel_incoming_interceptor_stct aeron_udp_channel_incoming_interceptor_t;\n\ntypedef enum aeron_udp_channel_interceptor_notification_type_en\n{\n    AERON_UDP_CHANNEL_INTERCEPTOR_ADD_NOTIFICATION,\n    AERON_UDP_CHANNEL_INTERCEPTOR_REMOVE_NOTIFICATION\n}\naeron_udp_channel_interceptor_notification_type_t;\n\ntypedef int (*aeron_udp_channel_interceptor_outgoing_send_func_t)(\n    void *interceptor_state,\n    aeron_udp_channel_outgoing_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent);\n\ntypedef void (*aeron_udp_channel_interceptor_incoming_func_t)(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec* media_timestamp);\n\ntypedef int (*aeron_udp_channel_interceptor_init_func_t)(\n    void **interceptor_state,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity);\n\ntypedef int (*aeron_udp_channel_interceptor_close_func_t)(\n    void *interceptor_state);\n\ntypedef int (*aeron_udp_channel_interceptor_transport_notification_func_t)(\n    void *interceptor_state,\n    aeron_udp_channel_transport_t *transport,\n    const aeron_udp_channel_t *udp_channel,\n    aeron_data_packet_dispatcher_t *data_packet_dispatcher,\n    aeron_udp_channel_interceptor_notification_type_t type);\n\ntypedef int (*aeron_udp_channel_interceptor_publication_notification_func_t)(\n    void *interceptor_state,\n    aeron_udp_channel_transport_t *transport,\n    aeron_network_publication_t *publication,\n    aeron_udp_channel_interceptor_notification_type_t type);\n\ntypedef int (*aeron_udp_channel_interceptor_image_notification_func_t)(\n    void *interceptor_state,\n    aeron_udp_channel_transport_t *transport,\n    aeron_publication_image_t *image,\n    aeron_udp_channel_interceptor_notification_type_t type);\n\nstruct aeron_udp_channel_interceptor_bindings_stct\n{\n    aeron_udp_channel_interceptor_init_func_t outgoing_init_func;\n    aeron_udp_channel_interceptor_init_func_t incoming_init_func;\n    aeron_udp_channel_interceptor_outgoing_send_func_t outgoing_send_func;\n    aeron_udp_channel_interceptor_incoming_func_t incoming_func;\n    aeron_udp_channel_interceptor_close_func_t outgoing_close_func;\n    aeron_udp_channel_interceptor_close_func_t incoming_close_func;\n    aeron_udp_channel_interceptor_transport_notification_func_t outgoing_transport_notification_func;\n    aeron_udp_channel_interceptor_transport_notification_func_t incoming_transport_notification_func;\n    aeron_udp_channel_interceptor_publication_notification_func_t outgoing_publication_notification_func;\n    aeron_udp_channel_interceptor_publication_notification_func_t incoming_publication_notification_func;\n    aeron_udp_channel_interceptor_image_notification_func_t outgoing_image_notification_func;\n    aeron_udp_channel_interceptor_image_notification_func_t incoming_image_notification_func;\n    struct interceptor_meta_info_fields\n    {\n        const char *name;\n        const char *type;\n        const aeron_udp_channel_interceptor_bindings_t *next_interceptor_bindings;\n        void (*source_symbol)(void);\n    }\n    meta_info;\n};\n\nstruct aeron_udp_channel_outgoing_interceptor_stct\n{\n    void *interceptor_state;\n    aeron_udp_channel_interceptor_outgoing_send_func_t outgoing_send_func;\n    aeron_udp_channel_interceptor_close_func_t close_func;\n    aeron_udp_channel_interceptor_transport_notification_func_t outgoing_transport_notification_func;\n    aeron_udp_channel_interceptor_publication_notification_func_t outgoing_publication_notification_func;\n    aeron_udp_channel_interceptor_image_notification_func_t outgoing_image_notification_func;\n    aeron_udp_channel_outgoing_interceptor_t *next_interceptor;\n};\n\nstruct aeron_udp_channel_incoming_interceptor_stct\n{\n    void *interceptor_state;\n    aeron_udp_channel_interceptor_incoming_func_t incoming_func;\n    aeron_udp_channel_interceptor_close_func_t close_func;\n    aeron_udp_channel_interceptor_transport_notification_func_t incoming_transport_notification_func;\n    aeron_udp_channel_interceptor_publication_notification_func_t incoming_publication_notification_func;\n    aeron_udp_channel_interceptor_image_notification_func_t incoming_image_notification_func;\n    aeron_udp_channel_incoming_interceptor_t *next_interceptor;\n};\n\nstruct aeron_udp_channel_data_paths_stct\n{\n    aeron_udp_channel_outgoing_interceptor_t *outgoing_interceptors;\n    aeron_udp_channel_incoming_interceptor_t *incoming_interceptors;\n    aeron_udp_channel_transport_send_func_t send_func;\n    aeron_udp_transport_recv_func_t recv_func;\n};\n\nstruct aeron_udp_channel_transport_recv_func_holder_stct\n{\n    aeron_udp_transport_recv_func_t func;\n};\ntypedef struct aeron_udp_channel_transport_recv_func_holder_stct aeron_udp_channel_transport_recv_func_holder_t;\n\nint aeron_udp_channel_transport_recv_func_holder_close(void *holder);\n\ninline int aeron_udp_channel_outgoing_interceptor_send(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n    aeron_udp_channel_outgoing_interceptor_t *interceptor = data_paths->outgoing_interceptors;\n\n    /* use first interceptor and pass in delegate */\n    return interceptor->outgoing_send_func(\n        interceptor->interceptor_state,\n        interceptor->next_interceptor,\n        transport,\n        address,\n        iov,\n        iov_length,\n        bytes_sent);\n}\n\ninline int aeron_udp_channel_outgoing_interceptor_send_to_transport(\n    void *interceptor_state,\n    aeron_udp_channel_outgoing_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n    aeron_udp_channel_transport_send_func_t func =\n        ((aeron_udp_channel_transport_bindings_t *)interceptor_state)->send_func;\n\n    return func(NULL, transport, address, iov, iov_length, bytes_sent);\n}\n\n\nvoid aeron_udp_channel_incoming_interceptor_recv_func(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp);\n\ninline void aeron_udp_channel_incoming_interceptor_to_endpoint(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp)\n{\n    aeron_udp_channel_transport_recv_func_holder_t *recv_function_holder =\n        (aeron_udp_channel_transport_recv_func_holder_t *)interceptor_state;\n\n    recv_function_holder->func(\n        NULL,\n        transport,\n        receiver_clientd,\n        endpoint_clientd,\n        destination_clientd,\n        buffer,\n        length,\n        addr,\n        media_timestamp);\n}\n\nint aeron_udp_channel_data_paths_init(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_interceptor_bindings_t *outgoing_interceptor_bindings,\n    aeron_udp_channel_interceptor_bindings_t *incoming_interceptor_bindings,\n    aeron_udp_channel_transport_bindings_t *media_bindings,\n    aeron_udp_transport_recv_func_t recv_func,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity);\n\nint aeron_udp_channel_data_paths_delete(aeron_udp_channel_data_paths_t *data_paths);\n\ninline int aeron_udp_channel_interceptors_transport_notifications(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    const aeron_udp_channel_t *udp_channel,\n    aeron_data_packet_dispatcher_t *data_packet_dispatcher,\n    aeron_udp_channel_interceptor_notification_type_t type)\n{\n    for (\n        aeron_udp_channel_incoming_interceptor_t *interceptor = data_paths->incoming_interceptors;\n        NULL != interceptor;\n        interceptor = interceptor->next_interceptor)\n    {\n        if (NULL != interceptor->incoming_transport_notification_func)\n        {\n            if (interceptor->incoming_transport_notification_func(\n                interceptor->interceptor_state, transport, udp_channel, data_packet_dispatcher, type) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    for (\n        aeron_udp_channel_outgoing_interceptor_t *interceptor = data_paths->outgoing_interceptors;\n        NULL != interceptor;\n        interceptor = interceptor->next_interceptor)\n    {\n        if (NULL != interceptor->outgoing_transport_notification_func)\n        {\n            if (interceptor->outgoing_transport_notification_func(\n                interceptor->interceptor_state, transport, udp_channel, data_packet_dispatcher, type) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\ninline int aeron_udp_channel_interceptors_publication_notifications(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    aeron_network_publication_t *publication,\n    aeron_udp_channel_interceptor_notification_type_t type)\n{\n    for (\n        aeron_udp_channel_incoming_interceptor_t *interceptor = data_paths->incoming_interceptors;\n        NULL != interceptor;\n        interceptor = interceptor->next_interceptor)\n    {\n        if (NULL != interceptor->incoming_publication_notification_func)\n        {\n            if (interceptor->incoming_publication_notification_func(\n                interceptor->interceptor_state, transport, publication, type) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    for (\n        aeron_udp_channel_outgoing_interceptor_t *interceptor = data_paths->outgoing_interceptors;\n        NULL != interceptor;\n        interceptor = interceptor->next_interceptor)\n    {\n        if (NULL != interceptor->outgoing_publication_notification_func)\n        {\n            if (interceptor->outgoing_publication_notification_func(\n                interceptor->interceptor_state, transport, publication, type) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\ninline int aeron_udp_channel_interceptors_image_notifications(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    aeron_publication_image_t *image,\n    aeron_udp_channel_interceptor_notification_type_t type)\n{\n    for (\n        aeron_udp_channel_incoming_interceptor_t *interceptor = data_paths->incoming_interceptors;\n        NULL != interceptor;\n        interceptor = interceptor->next_interceptor)\n    {\n        if (NULL != interceptor->incoming_image_notification_func)\n        {\n            if (interceptor->incoming_image_notification_func(\n                interceptor->interceptor_state, transport, image, type) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    for (\n        aeron_udp_channel_outgoing_interceptor_t *interceptor = data_paths->outgoing_interceptors;\n        NULL != interceptor;\n        interceptor = interceptor->next_interceptor)\n    {\n        if (NULL != interceptor->outgoing_image_notification_func)\n        {\n            if (interceptor->outgoing_image_notification_func(\n                interceptor->interceptor_state, transport, image, type) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\n#endif //AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport_fixed_loss.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"concurrent/aeron_thread.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_windows.h\"\n#include \"aeron_udp_channel_transport_fixed_loss.h\"\n#include \"collections/aeron_int64_counter_map.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\n#define AERON_CONFIG_GETENV_OR_DEFAULT(e, d) ((NULL == getenv(e)) ? (d) : getenv(e))\n#define AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_FIXED_LOSS_ARGS_ENV_VAR \"AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_FIXED_LOSS_ARGS\"\n\nstatic AERON_INIT_ONCE env_is_initialized = AERON_INIT_ONCE_VALUE;\n\nstatic aeron_udp_channel_interceptor_fixed_loss_params_t aeron_udp_channel_interceptor_fixed_loss_params;\n\n#define STREAM_AND_SESSION_ID_NULL_OFFSET (-1)\n\nint aeron_udp_channel_interceptor_fixed_loss_init_incoming(\n    void **interceptor_state, aeron_driver_context_t *context, aeron_udp_channel_transport_affinity_t affinity);\nint aeron_udp_channel_interceptor_fixed_loss_close_incoming(void *interceptor_state);\n\naeron_udp_channel_interceptor_bindings_t *aeron_udp_channel_interceptor_fixed_loss_load(\n    aeron_udp_channel_interceptor_bindings_t *delegate_bindings)\n{\n    aeron_udp_channel_interceptor_bindings_t *interceptor_bindings;\n    if (aeron_alloc((void **)&interceptor_bindings, sizeof(aeron_udp_channel_interceptor_bindings_t)) < 0)\n    {\n        return NULL;\n    }\n\n    interceptor_bindings->incoming_init_func = aeron_udp_channel_interceptor_fixed_loss_init_incoming;\n    interceptor_bindings->outgoing_init_func = NULL;\n    interceptor_bindings->outgoing_send_func = NULL;\n    interceptor_bindings->incoming_func = aeron_udp_channel_interceptor_fixed_loss_incoming;\n    interceptor_bindings->outgoing_close_func = NULL;\n    interceptor_bindings->incoming_close_func = aeron_udp_channel_interceptor_fixed_loss_close_incoming;\n    interceptor_bindings->outgoing_transport_notification_func = NULL;\n    interceptor_bindings->outgoing_publication_notification_func = NULL;\n    interceptor_bindings->outgoing_image_notification_func = NULL;\n    interceptor_bindings->incoming_transport_notification_func = NULL;\n    interceptor_bindings->incoming_publication_notification_func = NULL;\n    interceptor_bindings->incoming_image_notification_func = NULL;\n\n    interceptor_bindings->meta_info.name = \"fixed-loss\";\n    interceptor_bindings->meta_info.type = \"interceptor\";\n    interceptor_bindings->meta_info.next_interceptor_bindings = delegate_bindings;\n\n    return interceptor_bindings;\n}\n\nint aeron_udp_channel_interceptor_fixed_loss_configure(const aeron_udp_channel_interceptor_fixed_loss_params_t *fixed_loss_params)\n{\n    memcpy(&aeron_udp_channel_interceptor_fixed_loss_params, fixed_loss_params, sizeof(aeron_udp_channel_interceptor_fixed_loss_params_t));\n\n    return 0;\n}\n\nvoid aeron_udp_channel_transport_fixed_loss_load_env(void)\n{\n    aeron_udp_channel_interceptor_fixed_loss_params.term_offset = -1;\n\n    const char *args = AERON_CONFIG_GETENV_OR_DEFAULT(AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_FIXED_LOSS_ARGS_ENV_VAR, \"\");\n    char *args_dup = strdup(args);\n    if (NULL == args_dup)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Duplicating args string\");\n        return;\n    }\n\n    aeron_udp_channel_interceptor_fixed_loss_params_t *params;\n    if (aeron_alloc((void **)&params, sizeof(aeron_udp_channel_interceptor_fixed_loss_params_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_free(args_dup);\n        return;\n    }\n\n    if (aeron_udp_channel_interceptor_fixed_loss_parse_params(args_dup, params) >= 0)\n    {\n        aeron_udp_channel_interceptor_fixed_loss_configure(params);\n    }\n\n    aeron_free(params);\n    aeron_free(args_dup);\n}\n\nint aeron_udp_channel_interceptor_fixed_loss_init_incoming(\n    void **interceptor_state, aeron_driver_context_t *context, aeron_udp_channel_transport_affinity_t affinity)\n{\n    (void)aeron_thread_once(&env_is_initialized, aeron_udp_channel_transport_fixed_loss_load_env);\n\n    if (aeron_udp_channel_interceptor_fixed_loss_params.term_offset == -1)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"fixed loss params not set\");\n        return -1;\n    }\n\n    aeron_int64_counter_map_t *stream_and_session_id_to_offset_map;\n\n    if (aeron_alloc((void **)&stream_and_session_id_to_offset_map, sizeof(aeron_int64_counter_map_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_int64_counter_map_init(stream_and_session_id_to_offset_map, STREAM_AND_SESSION_ID_NULL_OFFSET, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Unable to init stream_and_session_id_to_offset_map\");\n        aeron_free(stream_and_session_id_to_offset_map);\n        return -1;\n    }\n\n    *interceptor_state = stream_and_session_id_to_offset_map;\n\n    return 0;\n}\n\nint aeron_udp_channel_interceptor_fixed_loss_close_incoming(void *interceptor_state)\n{\n    aeron_int64_counter_map_delete((aeron_int64_counter_map_t *)interceptor_state);\n\n    aeron_free(interceptor_state);\n\n    return 0;\n}\n\nstatic bool aeron_udp_channel_interceptor_fixed_loss_should_drop_frame(\n    aeron_int64_counter_map_t *stream_and_session_id_to_offset_map,\n    const uint8_t *buffer, size_t buffer_length, const int32_t term_id, const int32_t term_offset, const size_t length)\n{\n    const aeron_frame_header_t *frame_header = (aeron_frame_header_t *)buffer;\n    const bool is_data_msg = (unsigned int)frame_header->type == AERON_HDR_TYPE_DATA;\n\n    if (is_data_msg)\n    {\n        const aeron_data_header_t *data_header = (aeron_data_header_t *)buffer;\n\n        if (term_id == data_header->term_id)\n        {\n            const int64_t key = aeron_map_compound_key(data_header->stream_id, data_header->session_id);\n\n            int64_t tracking_offset = aeron_int64_counter_map_get(stream_and_session_id_to_offset_map, key);\n\n            if (tracking_offset == STREAM_AND_SESSION_ID_NULL_OFFSET)\n            {\n                tracking_offset = data_header->term_offset;\n                if (aeron_int64_counter_map_put(stream_and_session_id_to_offset_map, key, tracking_offset, NULL) != 0)\n                {\n                    return false;\n                }\n            }\n\n            if (tracking_offset < term_offset + (int64_t)length\n                && data_header->term_offset <= tracking_offset\n                && tracking_offset < data_header->term_offset + (int64_t)buffer_length)\n            {\n                if (aeron_int64_counter_map_put(stream_and_session_id_to_offset_map, key, data_header->term_offset + (int64_t)buffer_length, NULL) != 0)\n                {\n                    return false;\n                }\n\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\nvoid aeron_udp_channel_interceptor_fixed_loss_incoming(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp)\n{\n    if (!aeron_udp_channel_interceptor_fixed_loss_should_drop_frame(\n        (aeron_int64_counter_map_t *)interceptor_state,\n        buffer,\n        length,\n        aeron_udp_channel_interceptor_fixed_loss_params.term_id,\n        aeron_udp_channel_interceptor_fixed_loss_params.term_offset,\n        aeron_udp_channel_interceptor_fixed_loss_params.length))\n    {\n        delegate->incoming_func(\n            delegate->interceptor_state,\n            delegate->next_interceptor,\n            transport,\n            receiver_clientd,\n            endpoint_clientd,\n            destination_clientd,\n            buffer,\n            length,\n            addr,\n            media_timestamp);\n    }\n}\n\nint aeron_udp_channel_interceptor_fixed_loss_parse_params(char *uri, aeron_udp_channel_interceptor_fixed_loss_params_t *fixed_loss_params)\n{\n    return aeron_uri_parse_params(uri, aeron_udp_channel_interceptor_fixed_loss_parse_callback, (void *)fixed_loss_params);\n}\n\nint aeron_udp_channel_interceptor_fixed_loss_parse_callback(void *clientd, const char *key, const char *value)\n{\n    aeron_udp_channel_interceptor_fixed_loss_params_t *fixed_loss_params = clientd;\n    int result = 0;\n\n    if (strncmp(key, \"term-id\", sizeof(\"term-id\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        fixed_loss_params->term_id = strtol(value, &endptr, 10);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse fixed-loss %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n    else if (strncmp(key, \"term-offset\", sizeof(\"term-offset\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        fixed_loss_params->term_offset = strtol(value, &endptr, 10);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse fixed-loss %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n    else if (strncmp(key, \"length\", sizeof(\"length\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        fixed_loss_params->length = strtoul(value, &endptr, 10);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse fixed-loss %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport_fixed_loss.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_UDP_CHANNEL_TRANSPORT_FIXED_LOSS_H\n#define AERON_AERON_UDP_CHANNEL_TRANSPORT_FIXED_LOSS_H\n\n#include \"aeron_udp_channel_transport_bindings.h\"\n\ntypedef struct aeron_udp_channel_interceptor_fixed_loss_params_stct\n{\n    int32_t term_id;\n    int32_t term_offset;\n    size_t length;\n}\naeron_udp_channel_interceptor_fixed_loss_params_t;\n\naeron_udp_channel_interceptor_bindings_t *aeron_udp_channel_interceptor_fixed_loss_load(\n    aeron_udp_channel_interceptor_bindings_t *delegate_bindings);\n\nvoid aeron_udp_channel_interceptor_fixed_loss_incoming(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp);\n\nint aeron_udp_channel_interceptor_fixed_loss_configure(const aeron_udp_channel_interceptor_fixed_loss_params_t *fixed_loss_params);\n\nint aeron_udp_channel_interceptor_fixed_loss_parse_params(char *uri, aeron_udp_channel_interceptor_fixed_loss_params_t *fixed_loss_params);\n\nint aeron_udp_channel_interceptor_fixed_loss_parse_callback(void *clientd, const char *key, const char *value);\n\n#endif //AERON_AERON_UDP_CHANNEL_TRANSPORT_FIXED_LOSS_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport_loss.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"concurrent/aeron_thread.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_windows.h\"\n#include \"aeron_udp_channel_transport_loss.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\n#define AERON_CONFIG_GETENV_OR_DEFAULT(e, d) ((NULL == getenv(e)) ? (d) : getenv(e))\n#define AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_LOSS_ARGS_ENV_VAR \"AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_LOSS_ARGS\"\n\nstatic AERON_INIT_ONCE env_is_initialized = AERON_INIT_ONCE_VALUE;\n\nstatic aeron_udp_channel_interceptor_loss_params_t aeron_udp_channel_interceptor_loss_params;\nstatic unsigned short data_loss_xsubi[3];\n\nint aeron_udp_channel_interceptor_loss_init_incoming(\n    void **interceptor_state, aeron_driver_context_t *context, aeron_udp_channel_transport_affinity_t affinity);\n\naeron_udp_channel_interceptor_bindings_t *aeron_udp_channel_interceptor_loss_load(\n    aeron_udp_channel_interceptor_bindings_t *delegate_bindings)\n{\n    aeron_udp_channel_interceptor_bindings_t *interceptor_bindings;\n    if (aeron_alloc((void **)&interceptor_bindings, sizeof(aeron_udp_channel_interceptor_bindings_t)) < 0)\n    {\n        return NULL;\n    }\n\n    interceptor_bindings->incoming_init_func = aeron_udp_channel_interceptor_loss_init_incoming;\n    interceptor_bindings->outgoing_init_func = NULL;\n    interceptor_bindings->outgoing_send_func = NULL;\n    interceptor_bindings->incoming_func = aeron_udp_channel_interceptor_loss_incoming;\n    interceptor_bindings->outgoing_close_func = NULL;\n    interceptor_bindings->incoming_close_func = NULL;\n    interceptor_bindings->outgoing_transport_notification_func = NULL;\n    interceptor_bindings->outgoing_publication_notification_func = NULL;\n    interceptor_bindings->outgoing_image_notification_func = NULL;\n    interceptor_bindings->incoming_transport_notification_func = NULL;\n    interceptor_bindings->incoming_publication_notification_func = NULL;\n    interceptor_bindings->incoming_image_notification_func = NULL;\n\n    interceptor_bindings->meta_info.name = \"loss\";\n    interceptor_bindings->meta_info.type = \"interceptor\";\n    interceptor_bindings->meta_info.next_interceptor_bindings = delegate_bindings;\n\n    return interceptor_bindings;\n}\n\nint aeron_udp_channel_interceptor_loss_configure(const aeron_udp_channel_interceptor_loss_params_t *loss_params)\n{\n    memcpy(&aeron_udp_channel_interceptor_loss_params, loss_params, sizeof(aeron_udp_channel_interceptor_loss_params_t));\n\n    data_loss_xsubi[2] = (unsigned short)(aeron_udp_channel_interceptor_loss_params.seed & 0xFFFF);\n    data_loss_xsubi[1] = (unsigned short)((aeron_udp_channel_interceptor_loss_params.seed >> 16) & 0xFFFF);\n    data_loss_xsubi[0] = (unsigned short)((aeron_udp_channel_interceptor_loss_params.seed >> 32) & 0xFFFF);\n\n    return 0;\n}\n\nvoid aeron_udp_channel_transport_loss_load_env(void)\n{\n    const char *args = AERON_CONFIG_GETENV_OR_DEFAULT(AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_LOSS_ARGS_ENV_VAR, \"\");\n    char *args_dup = strdup(args);\n    if (NULL == args_dup)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Duplicating args string\");\n        return;\n    }\n\n    aeron_udp_channel_interceptor_loss_params_t *params;\n    if (aeron_alloc((void **)&params, sizeof(aeron_udp_channel_interceptor_loss_params_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_free(args_dup);\n        return;\n    }\n\n    if (aeron_udp_channel_interceptor_loss_parse_params(args_dup, params) >= 0)\n    {\n        aeron_udp_channel_interceptor_loss_configure(params);\n    }\n\n    aeron_free(params);\n    aeron_free(args_dup);\n}\n\nint aeron_udp_channel_interceptor_loss_init_incoming(\n    void **interceptor_state, aeron_driver_context_t *context, aeron_udp_channel_transport_affinity_t affinity)\n{\n    (void)aeron_thread_once(&env_is_initialized, aeron_udp_channel_transport_loss_load_env);\n\n    *interceptor_state = NULL;\n\n    return 0;\n}\n\nstatic bool aeron_udp_channel_interceptor_loss_should_drop_frame(\n    const uint8_t *buffer, const double rate, const unsigned long msg_type_mask)\n{\n    const aeron_frame_header_t *frame_header = (aeron_frame_header_t *)buffer;\n    const unsigned int msg_type_bit = 1U << (unsigned int)frame_header->type;\n    const bool msg_type_matches_mask = (msg_type_bit & msg_type_mask) != 0;\n\n    return 0.0 < rate && msg_type_matches_mask && (aeron_erand48(data_loss_xsubi) <= rate);\n}\n\nvoid aeron_udp_channel_interceptor_loss_incoming(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp)\n{\n    if (!aeron_udp_channel_interceptor_loss_should_drop_frame(\n        buffer, aeron_udp_channel_interceptor_loss_params.rate,\n        aeron_udp_channel_interceptor_loss_params.recv_msg_type_mask))\n    {\n        delegate->incoming_func(\n            delegate->interceptor_state,\n            delegate->next_interceptor,\n            transport,\n            receiver_clientd,\n            endpoint_clientd,\n            destination_clientd,\n            buffer,\n            length,\n            addr,\n            media_timestamp);\n    }\n}\n\nint aeron_udp_channel_interceptor_loss_parse_params(char *uri, aeron_udp_channel_interceptor_loss_params_t *params)\n{\n    return aeron_uri_parse_params(uri, aeron_udp_channel_interceptor_loss_parse_callback, (void *)params);\n}\n\nint aeron_udp_channel_interceptor_loss_parse_callback(void *clientd, const char *key, const char *value)\n{\n    aeron_udp_channel_interceptor_loss_params_t *loss_params = clientd;\n    int result = 0;\n\n    if (strncmp(key, \"rate\", sizeof(\"rate\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        loss_params->rate = strtod(value, &endptr);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse loss %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n    else if (strncmp(key, \"seed\", sizeof(\"seed\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        loss_params->seed = strtoull(value, &endptr, 10);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse seed %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n    else if (strncmp(key, \"recv-msg-mask\", sizeof(\"recv-msg-mask\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        loss_params->recv_msg_type_mask = strtoul(value, &endptr, 16);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse recv-msg-mask %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport_loss.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_UDP_CHANNEL_TRANSPORT_LOSS_H\n#define AERON_AERON_UDP_CHANNEL_TRANSPORT_LOSS_H\n\n#include \"aeron_udp_channel_transport_bindings.h\"\n\ntypedef struct aeron_udp_channel_interceptor_loss_params_stct\n{\n    double rate;\n    unsigned long recv_msg_type_mask;\n    unsigned long long seed;\n}\naeron_udp_channel_interceptor_loss_params_t;\n\naeron_udp_channel_interceptor_bindings_t *aeron_udp_channel_interceptor_loss_load(\n    aeron_udp_channel_interceptor_bindings_t *delegate_bindings);\n\nvoid aeron_udp_channel_interceptor_loss_incoming(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp);\n\nint aeron_udp_channel_interceptor_loss_configure(const aeron_udp_channel_interceptor_loss_params_t *loss_params);\n\nint aeron_udp_channel_interceptor_loss_parse_params(char *uri, aeron_udp_channel_interceptor_loss_params_t *params);\n\nint aeron_udp_channel_interceptor_loss_parse_callback(void *clientd, const char *key, const char *value);\n\n#endif //AERON_AERON_UDP_CHANNEL_TRANSPORT_LOSS_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport_multi_gap_loss.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <errno.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"concurrent/aeron_thread.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_alloc.h\"\n#include \"aeron_windows.h\"\n#include \"aeron_udp_channel_transport_multi_gap_loss.h\"\n#include \"collections/aeron_int64_counter_map.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\n#define AERON_CONFIG_GETENV_OR_DEFAULT(e, d) ((NULL == getenv(e)) ? (d) : getenv(e))\n#define AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MULTI_GAP_LOSS_ARGS_ENV_VAR \"AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MULTI_GAP_LOSS_ARGS\"\n\nstatic AERON_INIT_ONCE env_is_initialized = AERON_INIT_ONCE_VALUE;\n\nstatic aeron_udp_channel_interceptor_multi_gap_loss_params_t aeron_udp_channel_interceptor_multi_gap_loss_params;\n\n#define STREAM_AND_SESSION_ID_NULL_OFFSET (-1)\n\naeron_udp_channel_interceptor_bindings_t *aeron_udp_channel_interceptor_multi_gap_loss_load(\n    aeron_udp_channel_interceptor_bindings_t *delegate_bindings)\n{\n    aeron_udp_channel_interceptor_bindings_t *interceptor_bindings;\n    if (aeron_alloc((void **)&interceptor_bindings, sizeof(aeron_udp_channel_interceptor_bindings_t)) < 0)\n    {\n        return NULL;\n    }\n\n    interceptor_bindings->incoming_init_func = aeron_udp_channel_interceptor_multi_gap_loss_init_incoming;\n    interceptor_bindings->outgoing_init_func = NULL;\n    interceptor_bindings->outgoing_send_func = NULL;\n    interceptor_bindings->incoming_func = aeron_udp_channel_interceptor_multi_gap_loss_incoming;\n    interceptor_bindings->outgoing_close_func = NULL;\n    interceptor_bindings->incoming_close_func = aeron_udp_channel_interceptor_multi_gap_loss_close_incoming;\n    interceptor_bindings->outgoing_transport_notification_func = NULL;\n    interceptor_bindings->outgoing_publication_notification_func = NULL;\n    interceptor_bindings->outgoing_image_notification_func = NULL;\n    interceptor_bindings->incoming_transport_notification_func = NULL;\n    interceptor_bindings->incoming_publication_notification_func = NULL;\n    interceptor_bindings->incoming_image_notification_func = NULL;\n\n    interceptor_bindings->meta_info.name = \"multi-gap-loss\";\n    interceptor_bindings->meta_info.type = \"interceptor\";\n    interceptor_bindings->meta_info.next_interceptor_bindings = delegate_bindings;\n\n    return interceptor_bindings;\n}\n\nint aeron_udp_channel_interceptor_multi_gap_loss_configure(const aeron_udp_channel_interceptor_multi_gap_loss_params_t *multi_gap_loss_params)\n{\n    memcpy(&aeron_udp_channel_interceptor_multi_gap_loss_params, multi_gap_loss_params, sizeof(aeron_udp_channel_interceptor_multi_gap_loss_params_t));\n\n    return 0;\n}\n\nvoid aeron_udp_channel_transport_multi_gap_loss_load_env(void)\n{\n    const char *args = AERON_CONFIG_GETENV_OR_DEFAULT(AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MULTI_GAP_LOSS_ARGS_ENV_VAR, \"\");\n    char *args_dup = strdup(args);\n    if (NULL == args_dup)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Duplicating args string\");\n        return;\n    }\n\n    aeron_udp_channel_interceptor_multi_gap_loss_params_t *params;\n    if (aeron_alloc((void **)&params, sizeof(aeron_udp_channel_interceptor_multi_gap_loss_params_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        aeron_free(args_dup);\n        return;\n    }\n\n    if (aeron_udp_channel_interceptor_multi_gap_loss_parse_params(args_dup, params) >= 0)\n    {\n        aeron_udp_channel_interceptor_multi_gap_loss_configure(params);\n    }\n\n    aeron_free(params);\n    aeron_free(args_dup);\n}\n\nint aeron_udp_channel_interceptor_multi_gap_loss_init_incoming(\n    void **interceptor_state, aeron_driver_context_t *context, aeron_udp_channel_transport_affinity_t affinity)\n{\n    (void)aeron_thread_once(&env_is_initialized, aeron_udp_channel_transport_multi_gap_loss_load_env);\n\n    aeron_int64_counter_map_t *stream_and_session_id_to_offset_map;\n\n    if (aeron_alloc((void **)&stream_and_session_id_to_offset_map, sizeof(aeron_int64_counter_map_t)) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (aeron_int64_counter_map_init(stream_and_session_id_to_offset_map, STREAM_AND_SESSION_ID_NULL_OFFSET, 16, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"Unable to init stream_and_session_id_to_offset_map\");\n        aeron_free(stream_and_session_id_to_offset_map);\n        return -1;\n    }\n\n    *interceptor_state = stream_and_session_id_to_offset_map;\n\n    return 0;\n}\n\nint aeron_udp_channel_interceptor_multi_gap_loss_close_incoming(void *interceptor_state)\n{\n    aeron_int64_counter_map_delete((aeron_int64_counter_map_t *)interceptor_state);\n\n    aeron_free(interceptor_state);\n\n    return 0;\n}\n\nstatic bool aeron_udp_channel_interceptor_multi_gap_loss_should_drop_frame(\n    aeron_int64_counter_map_t *stream_and_session_id_to_offset_map,\n    const uint8_t *buffer, size_t buffer_length, const int32_t term_id, const int gap_radix_bits,\n    const uint32_t gap_radix_mask, const size_t gap_length, const int32_t last_gap_limit)\n{\n    const aeron_frame_header_t *frame_header = (aeron_frame_header_t *)buffer;\n    const bool is_data_msg = (unsigned int)frame_header->type == AERON_HDR_TYPE_DATA;\n\n    if (!is_data_msg)\n    {\n        return false;\n    }\n\n    const aeron_data_header_t *data_header = (aeron_data_header_t *)buffer;\n\n    if (term_id != data_header->term_id)\n    {\n        return false;\n    }\n\n    if (data_header->term_offset > last_gap_limit)\n    {\n        return false;\n    }\n\n    const int64_t key = aeron_map_compound_key(data_header->stream_id, data_header->session_id);\n\n    int64_t tracking_offset = aeron_int64_counter_map_get(stream_and_session_id_to_offset_map, key);\n\n    if (tracking_offset == STREAM_AND_SESSION_ID_NULL_OFFSET)\n    {\n        tracking_offset = data_header->term_offset;\n        if (aeron_int64_counter_map_put(stream_and_session_id_to_offset_map, key, tracking_offset, NULL) != 0)\n        {\n            return false;\n        }\n    }\n\n    if (tracking_offset > data_header->term_offset)\n    {\n        return false;\n    }\n\n    const int64_t frame_limit = data_header->term_offset + (int64_t)buffer_length;\n\n    if (data_header->term_offset != 0 && aeron_number_of_trailing_zeroes(data_header->term_offset) >= gap_radix_bits)\n    {\n        goto drop_frame;\n    }\n\n    const int64_t previous_gap_offset = data_header->term_offset & gap_radix_mask;\n    const int64_t previous_gap_limit = previous_gap_offset + (int64_t)gap_length;\n\n    if (previous_gap_offset > 0 && data_header->term_offset < previous_gap_limit)\n    {\n        goto drop_frame;\n    }\n\n    const int64_t next_gap_offset = ((data_header->term_offset >> gap_radix_bits) + 1) << gap_radix_bits;\n    const int64_t next_gap_limit = next_gap_offset + (int64_t)gap_length;\n\n    if (frame_limit > next_gap_offset && data_header->term_offset < next_gap_limit)\n    {\n        goto drop_frame;\n    }\n\n    return false;\n\ndrop_frame:\n    if (aeron_int64_counter_map_put(stream_and_session_id_to_offset_map, key, frame_limit, NULL) != 0)\n    {\n        return false;\n    }\n\n    return true;\n}\n\nvoid aeron_udp_channel_interceptor_multi_gap_loss_incoming(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp)\n{\n    if (!aeron_udp_channel_interceptor_multi_gap_loss_should_drop_frame(\n        (aeron_int64_counter_map_t *)interceptor_state,\n        buffer,\n        length,\n        aeron_udp_channel_interceptor_multi_gap_loss_params.term_id,\n        aeron_udp_channel_interceptor_multi_gap_loss_params.gap_radix_bits,\n        aeron_udp_channel_interceptor_multi_gap_loss_params.gap_radix_mask,\n        aeron_udp_channel_interceptor_multi_gap_loss_params.gap_length,\n        aeron_udp_channel_interceptor_multi_gap_loss_params.last_gap_limit))\n    {\n        delegate->incoming_func(\n            delegate->interceptor_state,\n            delegate->next_interceptor,\n            transport,\n            receiver_clientd,\n            endpoint_clientd,\n            destination_clientd,\n            buffer,\n            length,\n            addr,\n            media_timestamp);\n    }\n}\n\nint aeron_udp_channel_interceptor_multi_gap_loss_parse_params(char *uri, aeron_udp_channel_interceptor_multi_gap_loss_params_t *multi_gap_loss_params)\n{\n    int rc = aeron_uri_parse_params(uri, aeron_udp_channel_interceptor_multi_gap_loss_parse_callback, (void *)multi_gap_loss_params);\n\n    if (rc < 0)\n    {\n        return rc;\n    }\n\n    const int32_t gap_radix = aeron_find_next_power_of_two(multi_gap_loss_params->gap_radix);\n    multi_gap_loss_params->gap_radix_bits = aeron_number_of_trailing_zeroes(gap_radix);\n    multi_gap_loss_params->gap_radix_mask = ~(gap_radix - 1);\n    multi_gap_loss_params->last_gap_limit = (multi_gap_loss_params->total_gaps * multi_gap_loss_params->gap_radix) + (int32_t)multi_gap_loss_params->gap_length;\n\n    return rc;\n}\n\nint aeron_udp_channel_interceptor_multi_gap_loss_parse_callback(void *clientd, const char *key, const char *value)\n{\n    aeron_udp_channel_interceptor_multi_gap_loss_params_t *multi_gap_loss_params = clientd;\n    int result = 0;\n\n    if (strncmp(key, \"term-id\", sizeof(\"term-id\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        multi_gap_loss_params->term_id = strtol(value, &endptr, 10);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse multi-gap-loss %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n    else if (strncmp(key, \"gap-radix\", sizeof(\"gap-radix\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        multi_gap_loss_params->gap_radix = strtol(value, &endptr, 10);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse multi-gap-loss %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n    else if (strncmp(key, \"gap-length\", sizeof(\"gap-length\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        multi_gap_loss_params->gap_length = strtoul(value, &endptr, 10);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse multi-gap-loss %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n    else if (strncmp(key, \"total-gaps\", sizeof(\"total-gaps\")) == 0)\n    {\n        errno = 0;\n        char *endptr;\n        multi_gap_loss_params->total_gaps = strtol(value, &endptr, 10);\n\n        if (errno != 0 || value == endptr)\n        {\n            AERON_SET_ERR(EINVAL, \"Could not parse multi-gap-loss %s from: %s:\", key, value);\n            result = -1;\n        }\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_channel_transport_multi_gap_loss.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_UDP_CHANNEL_TRANSPORT_MULTI_GAP_LOSS_H\n#define AERON_AERON_UDP_CHANNEL_TRANSPORT_MULTI_GAP_LOSS_H\n\n#include \"aeron_udp_channel_transport_bindings.h\"\n\ntypedef struct aeron_udp_channel_interceptor_multi_gap_loss_params_stct\n{\n    int32_t term_id;\n    int32_t gap_radix;\n    size_t gap_length;\n    int32_t total_gaps;\n    int gap_radix_bits;\n    uint32_t gap_radix_mask;\n    int32_t last_gap_limit;\n}\naeron_udp_channel_interceptor_multi_gap_loss_params_t;\n\naeron_udp_channel_interceptor_bindings_t *aeron_udp_channel_interceptor_multi_gap_loss_load(\n    aeron_udp_channel_interceptor_bindings_t *delegate_bindings);\n\nint aeron_udp_channel_interceptor_multi_gap_loss_init_incoming(\n    void **interceptor_state, aeron_driver_context_t *context, aeron_udp_channel_transport_affinity_t affinity);\nint aeron_udp_channel_interceptor_multi_gap_loss_close_incoming(void *interceptor_state);\n\nvoid aeron_udp_channel_interceptor_multi_gap_loss_incoming(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp);\n\nint aeron_udp_channel_interceptor_multi_gap_loss_configure(const aeron_udp_channel_interceptor_multi_gap_loss_params_t *multi_gap_loss_params);\n\nint aeron_udp_channel_interceptor_multi_gap_loss_parse_params(char *uri, aeron_udp_channel_interceptor_multi_gap_loss_params_t *multi_gap_loss_params);\n\nint aeron_udp_channel_interceptor_multi_gap_loss_parse_callback(void *clientd, const char *key, const char *value);\n\n#endif //AERON_AERON_UDP_CHANNEL_TRANSPORT_MULTI_GAP_LOSS_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_destination_tracker.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include \"uri/aeron_uri.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"util/aeron_netutil.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"media/aeron_send_channel_endpoint.h\"\n#include \"aeron_driver_conductor.h\"\n#include \"media/aeron_udp_destination_tracker.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n\nint aeron_udp_destination_tracker_init(\n    aeron_udp_destination_tracker_t *tracker,\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_clock_cache_t *cached_clock,\n    bool is_manual_control_model,\n    int64_t timeout_ns)\n{\n    tracker->cached_clock = cached_clock;\n    tracker->data_paths = data_paths;\n    tracker->destination_timeout_ns = timeout_ns;\n    tracker->destinations.array = NULL;\n    tracker->destinations.length = 0;\n    tracker->destinations.capacity = 0;\n    tracker->is_manual_control_mode = is_manual_control_model;\n    tracker->round_robin_index = 0;\n\n    return 0;\n}\n\nint aeron_udp_destination_tracker_close(aeron_udp_destination_tracker_t *tracker)\n{\n    if (NULL != tracker)\n    {\n        for (size_t i = 0; i < tracker->destinations.length; i++)\n        {\n            aeron_uri_close(tracker->destinations.array[i].uri);\n            aeron_free(tracker->destinations.array[i].uri);\n        }\n\n        aeron_free(tracker->destinations.array);\n    }\n\n    return 0;\n}\n\nstatic void aeron_udp_destination_tracker_remove_inactive_destinations(\n    aeron_udp_destination_tracker_t *tracker,\n    int64_t now_ns)\n{\n    if (!tracker->is_manual_control_mode)\n    {\n        for (int last_index = (int)tracker->destinations.length - 1, i = last_index; i >= 0; i--)\n        {\n            aeron_udp_destination_entry_t *entry = &tracker->destinations.array[i];\n\n            if (now_ns > (entry->time_of_last_activity_ns + tracker->destination_timeout_ns))\n            {\n                aeron_array_fast_unordered_remove(\n                    (uint8_t *)tracker->destinations.array,\n                    sizeof(aeron_udp_destination_entry_t),\n                    (size_t)i,\n                    (size_t)last_index);\n                last_index--;\n                tracker->destinations.length--;\n\n                aeron_counter_set_release(tracker->num_destinations_addr, (int64_t) tracker->destinations.length);\n            }\n        }\n    }\n}\n\nint aeron_udp_destination_tracker_send(\n    aeron_udp_destination_tracker_t *tracker,\n    aeron_udp_channel_transport_t *transport,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n    const int64_t now_ns = aeron_clock_cached_nano_time(tracker->cached_clock);\n    const bool is_dynamic_control_mode = !tracker->is_manual_control_mode;\n    const int length = (int)tracker->destinations.length;\n    int result = (int)iov_length;\n    int to_be_removed = 0;\n\n    *bytes_sent = iov->iov_len;\n\n    int starting_index = tracker->round_robin_index++;\n    if (starting_index >= length)\n    {\n        tracker->round_robin_index = starting_index = 0;\n    }\n\n    for (int i = starting_index; i < length; i++)\n    {\n        aeron_udp_destination_entry_t *entry = &tracker->destinations.array[i];\n\n        if (is_dynamic_control_mode && now_ns > (entry->time_of_last_activity_ns + tracker->destination_timeout_ns))\n        {\n            to_be_removed++;\n        }\n        else if (entry->addr.ss_family != AF_UNSPEC)\n        {\n            const int sendmsg_result = tracker->data_paths->send_func(\n                tracker->data_paths, transport, &entry->addr, iov, iov_length, bytes_sent);\n            if (sendmsg_result < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"aeron_udp_destination_tracker_send\");\n                aeron_udp_channel_transport_log_error(transport);\n                result = 0;\n            }\n        }\n    }\n\n    for (int i = 0; i < starting_index; i++)\n    {\n        aeron_udp_destination_entry_t *entry = &tracker->destinations.array[i];\n\n        if (is_dynamic_control_mode && now_ns > (entry->time_of_last_activity_ns + tracker->destination_timeout_ns))\n        {\n            to_be_removed++;\n        }\n        else if (entry->addr.ss_family != AF_UNSPEC)\n        {\n            const int sendmsg_result = tracker->data_paths->send_func(\n                tracker->data_paths, transport, &entry->addr, iov, iov_length, bytes_sent);\n            if (sendmsg_result < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"aeron_udp_destination_tracker_send\");\n                aeron_udp_channel_transport_log_error(transport);\n                result = 0;\n            }\n        }\n    }\n\n    if (0 < to_be_removed)\n    {\n        aeron_udp_destination_tracker_remove_inactive_destinations(tracker, now_ns);\n    }\n\n    return result;\n}\n\nstatic bool aeron_udp_destination_tracker_same_port(struct sockaddr_storage *lhs, struct sockaddr_storage *rhs)\n{\n    bool result = false;\n\n    if (AF_INET6 == lhs->ss_family)\n    {\n        struct sockaddr_in6 *lhs_in6_addr = (struct sockaddr_in6 *)lhs;\n        struct sockaddr_in6 *rhs_in6_addr = (struct sockaddr_in6 *)rhs;\n\n        return ntohs(lhs_in6_addr->sin6_port) == ntohs(rhs_in6_addr->sin6_port);\n    }\n    else if (AF_INET == lhs->ss_family)\n    {\n        struct sockaddr_in *lhs_in_addr = (struct sockaddr_in *)lhs;\n        struct sockaddr_in *rhs_in_addr = (struct sockaddr_in *)rhs;\n\n        return ntohs(lhs_in_addr->sin_port) == ntohs(rhs_in_addr->sin_port);\n    }\n\n    return result;\n}\n\nstatic bool aeron_udp_destination_tracker_same_addr(struct sockaddr_storage *lhs, struct sockaddr_storage *rhs)\n{\n    bool result = false;\n\n    if (AF_INET6 == lhs->ss_family)\n    {\n        struct sockaddr_in6 *lhs_in6_addr = (struct sockaddr_in6 *)lhs;\n        struct sockaddr_in6 *rhs_in6_addr = (struct sockaddr_in6 *)rhs;\n\n        return 0 == memcmp(&lhs_in6_addr->sin6_addr, &rhs_in6_addr->sin6_addr, sizeof(struct in6_addr));\n    }\n    else if (AF_INET == lhs->ss_family)\n    {\n        struct sockaddr_in *lhs_in_addr = (struct sockaddr_in *)lhs;\n        struct sockaddr_in *rhs_in_addr = (struct sockaddr_in *)rhs;\n\n        return 0 == memcmp(&lhs_in_addr->sin_addr, &rhs_in_addr->sin_addr, sizeof(struct in_addr));\n    }\n\n    return result;\n}\n\nstatic bool aeron_udp_destination_tracker_is_match(\n    aeron_udp_destination_entry_t *entry,\n    int64_t receiver_id,\n    struct sockaddr_storage *addr)\n{\n    return\n        (entry->is_receiver_id_valid && receiver_id == entry->receiver_id &&\n            aeron_udp_destination_tracker_same_port(&entry->addr, addr)) ||\n        (!entry->is_receiver_id_valid && aeron_udp_destination_tracker_same_addr(&entry->addr, addr) &&\n            aeron_udp_destination_tracker_same_port(&entry->addr, addr));\n}\n\nint aeron_udp_destination_tracker_add_destination(\n    aeron_udp_destination_tracker_t *tracker,\n    int64_t receiver_id,\n    bool is_receiver_id_valid,\n    int64_t now_ns,\n    aeron_uri_t *uri,\n    struct sockaddr_storage *addr,\n    int64_t destination_registration_id)\n{\n    int result = 0;\n\n    AERON_ARRAY_ENSURE_CAPACITY(result, tracker->destinations, aeron_udp_destination_entry_t)\n    if (result >= 0)\n    {\n        aeron_udp_destination_entry_t *entry = &tracker->destinations.array[tracker->destinations.length++];\n\n        entry->receiver_id = receiver_id;\n        entry->registration_id = destination_registration_id;\n        entry->is_receiver_id_valid = is_receiver_id_valid;\n        entry->time_of_last_activity_ns = now_ns;\n        entry->destination_timeout_ns = AERON_UDP_DESTINATION_TRACKER_DESTINATION_TIMEOUT_NS;\n        entry->uri = uri;\n        memcpy(&entry->addr, addr, sizeof(struct sockaddr_storage));\n    }\n\n    aeron_counter_set_release(tracker->num_destinations_addr, (int64_t) tracker->destinations.length);\n\n    return result;\n}\n\nint aeron_udp_destination_tracker_on_status_message(\n    aeron_udp_destination_tracker_t *tracker, const uint8_t *buffer, size_t len, struct sockaddr_storage *addr)\n{\n    const aeron_status_message_header_t *status_message_header = (aeron_status_message_header_t *)buffer;\n    const int64_t now_ns = aeron_clock_cached_nano_time(tracker->cached_clock);\n    const int64_t receiver_id = status_message_header->receiver_id;\n    const bool is_dynamic_control_mode = !tracker->is_manual_control_mode;\n\n    int result = 0;\n    bool is_existing = false;\n\n    for (size_t i = 0, size = tracker->destinations.length; i < size; i++)\n    {\n        aeron_udp_destination_entry_t *entry = &tracker->destinations.array[i];\n\n        is_existing = aeron_udp_destination_tracker_is_match(entry, receiver_id, addr);\n        if (is_existing)\n        {\n            if (!entry->is_receiver_id_valid)\n            {\n                entry->receiver_id = receiver_id;\n                entry->is_receiver_id_valid = true;\n            }\n            entry->time_of_last_activity_ns = now_ns;\n\n            break;\n        }\n    }\n\n    if (is_dynamic_control_mode && !is_existing)\n    {\n        result = aeron_udp_destination_tracker_add_destination(\n            tracker, receiver_id, true, now_ns, NULL, addr, AERON_NULL_VALUE);\n    }\n\n    return result;\n}\n\nint aeron_udp_destination_tracker_manual_add_destination(\n    aeron_udp_destination_tracker_t *tracker,\n    int64_t now_ns,\n    aeron_uri_t *uri,\n    struct sockaddr_storage *addr,\n    int64_t destination_registration_id)\n{\n    if (!tracker->is_manual_control_mode)\n    {\n        return 0;\n    }\n\n    return aeron_udp_destination_tracker_add_destination(\n        tracker, now_ns, 0, false, uri, addr, destination_registration_id);\n}\n\nint aeron_udp_destination_tracker_address_compare(struct sockaddr_storage *lhs, struct sockaddr_storage *rhs)\n{\n    if (lhs->ss_family == rhs->ss_family)\n    {\n        size_t len = AF_INET == lhs->ss_family ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);\n\n        return memcmp(lhs, rhs, len);\n    }\n\n    return 1;\n}\n\nint aeron_udp_destination_tracker_remove_destination(\n    aeron_udp_destination_tracker_t *tracker,\n    struct sockaddr_storage *addr,\n    aeron_uri_t **removed_uri)\n{\n    for (int last_index = (int)tracker->destinations.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_udp_destination_entry_t *entry = &tracker->destinations.array[i];\n\n        if (aeron_udp_destination_tracker_address_compare(&entry->addr, addr) == 0)\n        {\n            *removed_uri = entry->uri;\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)tracker->destinations.array,\n                sizeof(aeron_udp_destination_entry_t),\n                (size_t)i,\n                (size_t)last_index);\n\n            tracker->destinations.length--;\n            break;\n        }\n    }\n\n    aeron_counter_set_release(tracker->num_destinations_addr, (int64_t) tracker->destinations.length);\n\n    return 0;\n}\n\nint aeron_udp_destination_tracker_remove_destination_by_id(\n    aeron_udp_destination_tracker_t *tracker,\n    int64_t destination_registration_id,\n    aeron_uri_t **removed_uri)\n{\n    for (int last_index = (int)tracker->destinations.length - 1, i = last_index; i >= 0; i--)\n    {\n        aeron_udp_destination_entry_t *entry = &tracker->destinations.array[i];\n\n        if (entry->registration_id == destination_registration_id)\n        {\n            *removed_uri = entry->uri;\n\n            aeron_array_fast_unordered_remove(\n                (uint8_t *)tracker->destinations.array,\n                sizeof(aeron_udp_destination_entry_t),\n                (size_t)i,\n                (size_t)last_index);\n\n            tracker->destinations.length--;\n            break;\n        }\n    }\n\n    aeron_counter_set_release(tracker->num_destinations_addr, (int64_t) tracker->destinations.length);\n\n    return 0;\n}\n\nint64_t aeron_udp_destination_tracker_find_registration_id(\n    aeron_udp_destination_tracker_t *tracker,\n    const uint8_t *buffer,\n    size_t len,\n    struct sockaddr_storage *addr)\n{\n    aeron_error_t *error = (aeron_error_t *)buffer;\n    const int64_t receiver_id = error->receiver_id;\n\n    for (size_t i = 0, size = tracker->destinations.length; i < size; i++)\n    {\n        aeron_udp_destination_entry_t *entry = &tracker->destinations.array[i];\n        if (aeron_udp_destination_tracker_is_match(entry, receiver_id, addr))\n        {\n            return entry->registration_id;\n        }\n    }\n\n    return AERON_NULL_VALUE;\n}\n\nvoid aeron_udp_destination_tracker_check_for_re_resolution(\n    aeron_udp_destination_tracker_t *tracker,\n    aeron_send_channel_endpoint_t *endpoint,\n    int64_t now_ns,\n    aeron_driver_conductor_proxy_t *conductor_proxy)\n{\n    if (tracker->is_manual_control_mode)\n    {\n        for (size_t i = 0; i < tracker->destinations.length; i++)\n        {\n            aeron_udp_destination_entry_t *destination = &tracker->destinations.array[i];\n\n            if (now_ns > (destination->time_of_last_activity_ns + destination->destination_timeout_ns))\n            {\n                assert(NULL != destination->uri);\n\n                aeron_driver_conductor_proxy_on_re_resolve_endpoint(\n                    conductor_proxy, destination->uri->params.udp.endpoint, endpoint, &destination->addr);\n                destination->time_of_last_activity_ns = now_ns;\n            }\n        }\n    }\n}\n\nvoid aeron_udp_destination_tracker_resolution_change(\n    aeron_udp_destination_tracker_t *tracker, const char *endpoint_name, struct sockaddr_storage *addr)\n{\n    if (tracker->is_manual_control_mode)\n    {\n        for (size_t i = 0; i < tracker->destinations.length; i++)\n        {\n            aeron_udp_destination_entry_t *destination = &tracker->destinations.array[i];\n            const size_t endpoint_name_len = strlen(endpoint_name);\n\n            if (0 == strncmp(endpoint_name, destination->uri->params.udp.endpoint, endpoint_name_len + 1))\n            {\n                memcpy(&destination->addr, addr, sizeof(destination->addr));\n            }\n        }\n    }\n}\n\nextern void aeron_udp_destination_tracker_set_counter(\n    aeron_udp_destination_tracker_t *tracker, aeron_atomic_counter_t *counter);\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_destination_tracker.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_UDP_DESTINATION_TRACKER_H\n#define AERON_UDP_DESTINATION_TRACKER_H\n\n#include \"aeron_socket.h\"\n#include \"util/aeron_clock.h\"\n#include \"aeron_udp_channel_transport.h\"\n\n#define AERON_UDP_DESTINATION_TRACKER_DESTINATION_TIMEOUT_NS (5 * 1000 * 1000 * 1000LL)\n\ntypedef struct aeron_udp_destination_entry_stct\n{\n    uint8_t padding_before[AERON_CACHE_LINE_LENGTH];\n    int64_t time_of_last_activity_ns;\n    int64_t destination_timeout_ns;\n    int64_t receiver_id;\n    int64_t registration_id;\n    bool is_receiver_id_valid;\n    aeron_uri_t *uri;\n    struct sockaddr_storage addr;\n    uint8_t padding_after[AERON_CACHE_LINE_LENGTH];\n}\naeron_udp_destination_entry_t;\n\ntypedef struct aeron_udp_destination_tracker_stct\n{\n    uint8_t padding_before[AERON_CACHE_LINE_LENGTH];\n    struct aeron_udp_destination_tracker_destinations_stct\n    {\n        aeron_udp_destination_entry_t *array;\n        size_t length;\n        size_t capacity;\n    }\n    destinations;\n\n    bool is_manual_control_mode;\n    aeron_clock_cache_t *cached_clock;\n    int64_t destination_timeout_ns;\n    aeron_udp_channel_data_paths_t *data_paths;\n    volatile int64_t *num_destinations_addr;\n\n    int round_robin_index;\n    uint8_t padding_after[AERON_CACHE_LINE_LENGTH];\n}\naeron_udp_destination_tracker_t;\n\nint aeron_udp_destination_tracker_init(\n    aeron_udp_destination_tracker_t *tracker,\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_clock_cache_t *cached_clock,\n    bool is_manual_control_model,\n    int64_t timeout_ns);\nint aeron_udp_destination_tracker_close(aeron_udp_destination_tracker_t *tracker);\n\nint aeron_udp_destination_tracker_send(\n    aeron_udp_destination_tracker_t *tracker,\n    aeron_udp_channel_transport_t *transport,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent);\n\nint aeron_udp_destination_tracker_on_status_message(\n    aeron_udp_destination_tracker_t *tracker, const uint8_t *buffer, size_t len, struct sockaddr_storage *addr);\n\nint aeron_udp_destination_tracker_manual_add_destination(\n    aeron_udp_destination_tracker_t *tracker,\n    int64_t now_ns,\n    aeron_uri_t *uri,\n    struct sockaddr_storage *addr,\n    int64_t destination_registration_id);\n\nint aeron_udp_destination_tracker_remove_destination(\n    aeron_udp_destination_tracker_t *tracker,\n    struct sockaddr_storage *addr,\n    aeron_uri_t **removed_uri);\n\nint aeron_udp_destination_tracker_remove_destination_by_id(\n    aeron_udp_destination_tracker_t *tracker, int64_t destination_registration_id, aeron_uri_t **removed_uri);\n\nint64_t aeron_udp_destination_tracker_find_registration_id(\n    aeron_udp_destination_tracker_t *tracker, const uint8_t *buffer, size_t len, struct sockaddr_storage *addr);\n\nvoid aeron_udp_destination_tracker_check_for_re_resolution(\n    aeron_udp_destination_tracker_t *tracker,\n    aeron_send_channel_endpoint_t *endpoint,\n    int64_t now_ns,\n    aeron_driver_conductor_proxy_t *conductor_proxy);\n\nvoid aeron_udp_destination_tracker_resolution_change(\n    aeron_udp_destination_tracker_t *tracker, const char *endpoint_name, struct sockaddr_storage *addr);\n\ninline void aeron_udp_destination_tracker_set_counter(\n    aeron_udp_destination_tracker_t *tracker, aeron_atomic_counter_t *counter)\n{\n    tracker->num_destinations_addr = counter->value_addr;\n}\n\n#endif //AERON_UDP_DESTINATION_TRACKER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_transport_poller.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"util/aeron_platform.h\"\n\n#if !defined(AERON_COMPILER_MSVC)\n#include <unistd.h>\n#endif\n\n#if defined(HAVE_EPOLL)\n#include <sys/epoll.h>\n#elif defined(HAVE_POLL)\n#include <poll.h>\n#elif defined(HAVE_WSAPOLL)\n#include \"aeron_windows.h\"\n#endif\n\n#include \"util/aeron_arrayutil.h\"\n#include \"aeron_alloc.h\"\n#include \"media/aeron_udp_transport_poller.h\"\n\nint aeron_udp_transport_poller_init(\n    aeron_udp_transport_poller_t *poller,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity)\n{\n    poller->transports.array = NULL;\n    poller->transports.length = 0;\n    poller->transports.capacity = 0;\n\n#if defined(HAVE_EPOLL)\n    if ((poller->fd = epoll_create1(0)) < 0)\n    {\n        AERON_SET_ERR(errno, \"%s\", \"epoll_create1\");\n        return -1;\n    }\n#endif\n\n    poller->bindings_clientd = NULL;\n    return 0;\n}\n\nint aeron_udp_transport_poller_close(aeron_udp_transport_poller_t *poller)\n{\n    aeron_free(poller->transports.array);\n#if defined(HAVE_EPOLL)\n    close(poller->fd);\n#endif\n    aeron_free(poller->bindings_clientd);\n    return 0;\n}\n\nint aeron_udp_transport_poller_add(aeron_udp_transport_poller_t *poller, aeron_udp_channel_transport_t *transport)\n{\n    int ensure_capacity_result = 0;\n    size_t old_capacity = poller->transports.capacity, index = poller->transports.length;\n\n    AERON_ARRAY_ENSURE_CAPACITY(ensure_capacity_result, poller->transports, aeron_udp_channel_transport_entry_t)\n    if (ensure_capacity_result < 0)\n    {\n        return -1;\n    }\n\n    poller->transports.array[index].transport = transport;\n\n#if defined(HAVE_EPOLL)\n    size_t new_capacity = poller->transports.capacity;\n\n    if (new_capacity > old_capacity)\n    {\n        if (aeron_array_ensure_capacity(\n            (uint8_t **)&poller->bindings_clientd, sizeof(struct epoll_event), old_capacity, new_capacity) < 0)\n        {\n            return -1;\n        }\n    }\n\n    struct epoll_event event;\n\n    event.data.fd = transport->recv_fd;\n    event.data.ptr = transport;\n    event.events = EPOLLIN;\n    int result = epoll_ctl(poller->fd, EPOLL_CTL_ADD, transport->recv_fd, &event);\n    if (result < 0)\n    {\n        AERON_SET_ERR(errno, \"Failed to epoll_ctl(EPOLL_CTL_ADD), fd: %d\", transport->recv_fd);\n        return -1;\n    }\n\n#elif defined(HAVE_POLL) || defined(HAVE_WSAPOLL)\n    size_t new_capacity = poller->transports.capacity;\n\n    if (new_capacity > old_capacity)\n    {\n        if (aeron_array_ensure_capacity(\n            (uint8_t **)&poller->bindings_clientd, sizeof(struct pollfd), old_capacity, new_capacity) < 0)\n        {\n            return -1;\n        }\n    }\n\n    struct pollfd *pollfds = (struct pollfd *)poller->bindings_clientd;\n    pollfds[index].fd = transport->recv_fd;\n    pollfds[index].events = POLLIN;\n    pollfds[index].revents = 0;\n#endif\n\n    poller->transports.length++;\n\n    return 0;\n}\n\nint aeron_udp_transport_poller_remove(aeron_udp_transport_poller_t *poller, aeron_udp_channel_transport_t *transport)\n{\n    int index = -1, last_index = (int)poller->transports.length - 1;\n\n    for (int i = last_index; i >= 0; i--)\n    {\n        if (poller->transports.array[i].transport == transport)\n        {\n            index = i;\n            break;\n        }\n    }\n\n    if (index >= 0)\n    {\n        aeron_array_fast_unordered_remove(\n            (uint8_t *)poller->transports.array,\n            sizeof(aeron_udp_channel_transport_entry_t),\n            (size_t)index,\n            (size_t)last_index);\n        poller->transports.length--;\n\n#if defined(HAVE_EPOLL)\n        aeron_array_fast_unordered_remove(\n            (uint8_t *)poller->bindings_clientd,\n            sizeof(struct epoll_event),\n            (size_t)index,\n            (size_t)last_index);\n\n        struct epoll_event event;\n\n        event.data.fd = transport->recv_fd;\n        event.data.ptr = transport;\n        event.events = EPOLLIN;\n        int result = epoll_ctl(poller->fd, EPOLL_CTL_DEL, transport->recv_fd, &event);\n        if (result < 0)\n        {\n            AERON_SET_ERR(errno, \"%s\", \"epoll_ctl(EPOLL_CTL_DEL)\");\n            return -1;\n        }\n\n#elif defined(HAVE_POLL) || defined(HAVE_WSAPOLL)\n        aeron_array_fast_unordered_remove(\n            (uint8_t *)poller->bindings_clientd,\n            sizeof(struct pollfd),\n            (size_t)index,\n            (size_t)last_index);\n#endif\n    }\n\n    return 0;\n}\n\nint aeron_udp_transport_poller_poll(\n    aeron_udp_transport_poller_t *poller,\n    struct mmsghdr *msgvec,\n    size_t vlen,\n    int64_t *bytes_rcved,\n    aeron_udp_transport_recv_func_t recv_func,\n    aeron_udp_channel_transport_recvmmsg_func_t recvmmsg_func,\n    void *clientd)\n{\n    int work_count = 0;\n\n    if (poller->transports.length <= AERON_UDP_TRANSPORT_POLLER_ITERATION_THRESHOLD)\n    {\n        for (size_t i = 0, length = poller->transports.length; i < length; i++)\n        {\n            aeron_udp_channel_transport_t *transport = poller->transports.array[i].transport;\n            int recv_result = recvmmsg_func(transport, msgvec, vlen, bytes_rcved, recv_func, clientd);\n            if (recv_result < 0)\n            {\n                AERON_APPEND_ERR(\"%s\", \"aeron_udp_transport_poller_poll\");\n                aeron_udp_channel_transport_log_error(transport);\n            }\n            else\n            {\n                work_count += recv_result;\n            }\n        }\n    }\n    else\n    {\n#if defined(HAVE_EPOLL)\n        struct epoll_event *epoll_events = (struct epoll_event *)poller->bindings_clientd;\n        int result = epoll_wait(poller->fd, epoll_events, (int) poller->transports.length, 0);\n\n        if (result < 0)\n        {\n            int err = errno;\n\n            if (EINTR == err || EAGAIN == err)\n            {\n                return 0;\n            }\n\n            AERON_SET_ERR(err, \"%s\", \"epoll_wait\");\n            return -1;\n        }\n        else if (0 == result)\n        {\n            return 0;\n        }\n        else\n        {\n            for (size_t i = 0, length = (size_t)result; i < length; i++)\n            {\n                if (epoll_events[i].events & EPOLLIN)\n                {\n                    aeron_udp_channel_transport_t *transport = epoll_events[i].data.ptr;\n                    int recv_result = recvmmsg_func( transport, msgvec, vlen, bytes_rcved, recv_func, clientd);\n\n                    if (recv_result < 0)\n                    {\n                        AERON_APPEND_ERR(\"%s\", \"aeron_udp_transport_poller_poll\");\n                        aeron_udp_channel_transport_log_error(transport);\n                    }\n                    else\n                    {\n                        work_count += recv_result;\n                    }\n                }\n\n                epoll_events[i].events = 0;\n            }\n        }\n\n#elif defined(HAVE_POLL) || defined(HAVE_WSAPOLL)\n        struct pollfd *pollfds = (struct pollfd *)poller->bindings_clientd;\n        const int result = aeron_poll(pollfds, (unsigned long)poller->transports.length, 0);\n\n        if (result < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n        else if (0 == result)\n        {\n            return 0;\n        }\n        else\n        {\n            for (size_t i = 0, length = poller->transports.length; i < length; i++)\n            {\n                if (pollfds[i].revents & POLLIN)\n                {\n                    aeron_udp_channel_transport_t *transport = poller->transports.array[i].transport;\n                    int recv_result = recvmmsg_func(\n                        transport, msgvec, vlen, bytes_rcved, recv_func, clientd);\n                    if (recv_result < 0)\n                    {\n                        AERON_APPEND_ERR(\"%s\", \"aeron_udp_transport_poller_poll\");\n                        aeron_udp_channel_transport_log_error(transport);\n                    }\n                    else\n                    {\n                        work_count += recv_result;\n                    }\n                }\n\n                pollfds[i].revents = 0;\n            }\n        }\n#endif\n    }\n\n    return work_count;\n}\n\nint aeron_udp_transport_poller_check_send_endpoint_re_resolutions(\n    aeron_udp_transport_poller_t *poller,\n    int64_t now_ns,\n    struct aeron_driver_conductor_proxy_stct *conductor_proxy)\n{\n    for (size_t i = 0; i < poller->transports.length; i++)\n    {\n        aeron_udp_channel_transport_t *transport = poller->transports.array[i].transport;\n        aeron_send_channel_endpoint_t *endpoint = transport->dispatch_clientd;\n\n        aeron_send_channel_endpoint_check_for_re_resolution(endpoint, now_ns, conductor_proxy);\n    }\n\n    return 0;\n}\n\nint aeron_udp_transport_poller_check_receive_endpoint_re_resolutions(\n    aeron_udp_transport_poller_t *poller,\n    int64_t now_ns,\n    aeron_driver_conductor_proxy_t *conductor_proxy)\n{\n    for (size_t i = 0; i < poller->transports.length; i++)\n    {\n        aeron_udp_channel_transport_t *transport = poller->transports.array[i].transport;\n        aeron_receive_channel_endpoint_t *endpoint = transport->dispatch_clientd;\n\n        aeron_receive_channel_endpoint_check_for_re_resolution(endpoint, now_ns, conductor_proxy);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/media/aeron_udp_transport_poller.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_UDP_TRANSPORT_POLLER_H\n#define AERON_UDP_TRANSPORT_POLLER_H\n\n#include \"aeron_driver_conductor.h\"\n\n#define AERON_UDP_TRANSPORT_POLLER_ITERATION_THRESHOLD (5)\n\ntypedef struct aeron_udp_channel_transport_entry_stct\n{\n    aeron_udp_channel_transport_t *transport;\n}\naeron_udp_channel_transport_entry_t;\n\ntypedef struct aeron_udp_transport_poller_stct\n{\n    struct aeron_udp_channel_transports_stct\n    {\n        aeron_udp_channel_transport_entry_t *array;\n        size_t length;\n        size_t capacity;\n    }\n    transports;\n\n    int fd;\n    void *bindings_clientd;\n}\naeron_udp_transport_poller_t;\n\nint aeron_udp_transport_poller_init(\n    aeron_udp_transport_poller_t *poller,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity);\nint aeron_udp_transport_poller_close(aeron_udp_transport_poller_t *poller);\n\nint aeron_udp_transport_poller_add(aeron_udp_transport_poller_t *poller, aeron_udp_channel_transport_t *transport);\nint aeron_udp_transport_poller_remove(aeron_udp_transport_poller_t *poller, aeron_udp_channel_transport_t *transport);\n\nint aeron_udp_transport_poller_poll(\n    aeron_udp_transport_poller_t *poller,\n    struct mmsghdr *msgvec,\n    size_t vlen,\n    int64_t *bytes_rcved,\n    aeron_udp_transport_recv_func_t recv_func,\n    aeron_udp_channel_transport_recvmmsg_func_t recvmmsg_func,\n    void *clientd);\n\nint aeron_udp_transport_poller_check_send_endpoint_re_resolutions(\n    aeron_udp_transport_poller_t *poller,\n    int64_t now_ns,\n    aeron_driver_conductor_proxy_t *conductor_proxy);\n\nint aeron_udp_transport_poller_check_receive_endpoint_re_resolutions(\n    aeron_udp_transport_poller_t *poller,\n    int64_t now_ns,\n    aeron_driver_conductor_proxy_t *conductor_proxy);\n\n#endif //AERON_UDP_TRANSPORT_POLLER_H\n"
  },
  {
    "path": "aeron-driver/src/main/c/uri/aeron_driver_uri.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <inttypes.h>\n#include \"uri/aeron_driver_uri.h\"\n#include \"util/aeron_arrayutil.h\"\n#include \"util/aeron_math.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_driver_conductor.h\"\n\nstatic int aeron_driver_uri_get_response_correlation_id(aeron_uri_params_t *uri_params, int64_t *response_correlation_id);\n\nint aeron_uri_get_term_length_param(aeron_uri_params_t *uri_params, aeron_driver_uri_publication_params_t *params)\n{\n    const char *value_str;\n\n    if ((value_str = aeron_uri_find_param_value(uri_params, AERON_URI_TERM_LENGTH_KEY)) != NULL)\n    {\n        uint64_t value;\n\n        if (-1 == aeron_parse_size64(value_str, &value))\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse %s=%s in URI\", AERON_URI_TERM_LENGTH_KEY, value_str);\n            return -1;\n        }\n\n        if (aeron_logbuffer_check_term_length(value) < 0)\n        {\n            return -1;\n        }\n\n        params->term_length = value;\n        params->has_term_length = true;\n    }\n\n    return 0;\n}\n\nint aeron_uri_get_max_resend_param(aeron_uri_params_t *uri_params, aeron_driver_uri_publication_params_t *params)\n{\n    const char *value_str;\n\n    if ((value_str = aeron_uri_find_param_value(uri_params, AERON_URI_MAX_RESEND_KEY)) != NULL)\n    {\n        uint64_t value;\n\n        if (-1 == aeron_parse_size64(value_str, &value))\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse %s=%s in URI\", AERON_URI_MAX_RESEND_KEY, value_str);\n            return -1;\n        }\n\n        if (value < 1 || value > AERON_RETRANSMIT_HANDLER_MAX_RESEND_MAX)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"invalid %s=%\" PRIu64 \", must be > 0 and <= %i\",\n                AERON_URI_MAX_RESEND_KEY,\n                value,\n                AERON_RETRANSMIT_HANDLER_MAX_RESEND_MAX);\n            return -1;\n        }\n\n        params->max_resend = (uint32_t)value;\n        params->has_max_resend = true;\n    }\n\n    return 0;\n}\n\nint aeron_uri_get_mtu_length_param(aeron_uri_params_t *uri_params, aeron_driver_uri_publication_params_t *params)\n{\n    const char *value_str;\n\n    if ((value_str = aeron_uri_find_param_value(uri_params, AERON_URI_MTU_LENGTH_KEY)) != NULL)\n    {\n        uint64_t value;\n\n        if (-1 == aeron_parse_size64(value_str, &value))\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse %s=%s in URI\", AERON_URI_MTU_LENGTH_KEY, value_str);\n            return -1;\n        }\n\n        if (aeron_driver_context_validate_mtu_length(value) < 0)\n        {\n            AERON_APPEND_ERR(\"%s\", \"\");\n            return -1;\n        }\n\n        params->mtu_length = value;\n        params->has_mtu_length = true;\n    }\n\n    return 0;\n}\n\nint aeron_uri_get_publication_window_length_param(aeron_uri_params_t *uri_params, aeron_driver_uri_publication_params_t *params)\n{\n    const char *value_str;\n\n    if ((value_str = aeron_uri_find_param_value(uri_params, AERON_URI_PUBLICATION_WINDOW_KEY)) != NULL)\n    {\n        uint64_t value;\n\n        if (-1 == aeron_parse_size64(value_str, &value))\n        {\n            AERON_SET_ERR(EINVAL, \"could not parse %s=%s in URI\", AERON_URI_PUBLICATION_WINDOW_KEY, value_str);\n            return -1;\n        }\n\n        if (value < params->mtu_length)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"%s=%\" PRIu64 \" cannot be less than the %s=%\" PRIu64,\n                AERON_URI_PUBLICATION_WINDOW_KEY,\n                value,\n                AERON_URI_MTU_LENGTH_KEY,\n                params->mtu_length);\n            return -1;\n        }\n\n        if (value > (params->term_length >> 1))\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"%s=%\" PRIu64 \" must not exceed half the %s=%\" PRIu64,\n                AERON_URI_PUBLICATION_WINDOW_KEY,\n                value,\n                AERON_URI_TERM_LENGTH_KEY,\n                params->term_length);\n            return -1;\n        }\n\n        params->publication_window_length = (int32_t)value;\n        params->has_publication_window_length = true;\n    }\n\n    return 0;\n}\n\nint aeron_uri_linger_timeout_param(aeron_uri_params_t *uri_params, aeron_driver_uri_publication_params_t *params)\n{\n    return aeron_uri_get_timeout(uri_params, AERON_URI_LINGER_TIMEOUT_KEY, &params->linger_timeout_ns);\n}\n\nint aeron_uri_publication_session_id_param(\n    aeron_uri_params_t *uri_params, aeron_driver_conductor_t *conductor, aeron_driver_uri_publication_params_t *params)\n{\n    const char *session_id_str = aeron_uri_find_param_value(uri_params, AERON_URI_SESSION_ID_KEY);\n    if (NULL != session_id_str)\n    {\n        if (0 == strncmp(\"tag:\", session_id_str, strlen(\"tag:\")))\n        {\n            char *end_ptr;\n            errno = 0;\n\n            long long tag = strtoll(&session_id_str[4], &end_ptr, 0);\n            if (0 != errno || '\\0' != *end_ptr)\n            {\n                AERON_SET_ERR(\n                    EINVAL,\n                    \"could not parse %s=%s as int64_t in URI due to: %s\",\n                    AERON_URI_SESSION_ID_KEY, session_id_str, strerror(errno));\n                return -1;\n            }\n\n            aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication_by_tag(\n                conductor, (int64_t)tag);\n\n            if (NULL == publication)\n            {\n                AERON_SET_ERR(\n                    EINVAL, \"%s=%s must reference a network publication\", AERON_URI_SESSION_ID_KEY, session_id_str);\n                return -1;\n            }\n\n            params->has_session_id = true;\n            params->session_id = publication->session_id;\n            params->mtu_length = publication->mtu_length;\n            params->term_length = publication->term_buffer_length;\n        }\n        else\n        {\n            int result = aeron_uri_get_int32(uri_params, AERON_URI_SESSION_ID_KEY, &params->session_id);\n            params->has_session_id = 1 == result;\n\n            return result < 0 ? -1 : 0;\n        }\n    }\n\n    return 0;\n}\n\nint aeron_uri_subscription_session_id_param(aeron_uri_params_t *uri_params, aeron_driver_uri_subscription_params_t *params)\n{\n    int result = aeron_uri_get_int32(uri_params, AERON_URI_SESSION_ID_KEY, &params->session_id);\n    params->has_session_id = 1 == result;\n\n    return result < 0 ? -1 : 0;\n}\n\nint aeron_diver_uri_publication_params(\n    aeron_uri_t *uri,\n    aeron_driver_uri_publication_params_t *params,\n    aeron_driver_conductor_t *conductor,\n    bool is_exclusive)\n{\n    aeron_driver_context_t *context = conductor->context;\n\n    params->linger_timeout_ns = context->publication_linger_timeout_ns;\n    params->untethered_window_limit_timeout_ns = context->untethered_window_limit_timeout_ns;\n    params->untethered_linger_timeout_ns = context->untethered_linger_timeout_ns;\n    params->untethered_resting_timeout_ns = context->untethered_resting_timeout_ns;\n    params->term_length = AERON_URI_IPC == uri->type ? context->ipc_term_buffer_length : context->term_buffer_length;\n    params->has_term_length = false;\n    params->mtu_length = AERON_URI_IPC == uri->type ? context->ipc_mtu_length : context->mtu_length;\n    params->has_mtu_length = false;\n    params->initial_term_id = 0;\n    params->term_offset = 0;\n    params->term_id = 0;\n    params->has_position = false;\n    params->is_sparse = context->term_buffer_sparse_file;\n    params->signal_eos = true;\n    params->spies_simulate_connection = context->spies_simulate_connection;\n    params->has_session_id = false;\n    params->session_id = 0;\n    params->entity_tag = AERON_URI_INVALID_TAG;\n    params->response_correlation_id = AERON_NULL_VALUE;\n    params->has_max_resend = false;\n    params->max_resend = 0;\n    params->is_response =\n        (AERON_URI_UDP == uri->type &&\n        NULL != uri->params.udp.control_mode &&\n        strcmp(uri->params.udp.control_mode, AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE) == 0);\n\n    aeron_uri_params_t *uri_params = AERON_URI_IPC == uri->type ?\n        &uri->params.ipc.additional_params : &uri->params.udp.additional_params;\n\n    if (aeron_uri_publication_session_id_param(uri_params, conductor, params) < 0)\n    {\n        return -1;\n    }\n\n    const char *entity_tag_str = AERON_URI_IPC == uri->type ? uri->params.ipc.entity_tag : uri->params.udp.entity_tag;\n    if (NULL != entity_tag_str)\n    {\n        errno = 0;\n        char *end_ptr;\n        long long entity_tag = strtoll(entity_tag_str, &end_ptr, 10);\n        if (0 != errno || *end_ptr != '\\0')\n        {\n            AERON_SET_ERR(EINVAL, \"Entity tag invalid: %s\", entity_tag_str);\n            return -1;\n        }\n\n        params->entity_tag = (int64_t)entity_tag;\n    }\n\n    if (aeron_uri_linger_timeout_param(uri_params, params) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_term_length_param(uri_params, params) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_max_resend_param(uri_params, params) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_mtu_length_param(uri_params, params) < 0)\n    {\n        return -1;\n    }\n\n    params->publication_window_length = (int32_t)aeron_producer_window_length(\n        AERON_URI_IPC == uri->type ? context->ipc_publication_window_length : context->publication_window_length,\n        params->term_length);\n    params->has_publication_window_length = false;\n\n    if (aeron_uri_get_publication_window_length_param(uri_params, params) < 0)\n    {\n        return -1;\n    }\n\n    int count = 0;\n\n    int32_t initial_term_id;\n    int32_t term_id;\n    int parse_result;\n\n    parse_result = aeron_uri_get_int32(uri_params, AERON_URI_INITIAL_TERM_ID_KEY, &initial_term_id);\n    if (parse_result < 0)\n    {\n        return -1;\n    }\n    count += parse_result > 0 ? 1 : 0;\n\n    parse_result = aeron_uri_get_int32(uri_params, AERON_URI_TERM_ID_KEY, &term_id);\n    if (parse_result < 0)\n    {\n        return -1;\n    }\n    count += parse_result > 0 ? 1 : 0;\n\n    const char *term_offset_str = aeron_uri_find_param_value(uri_params, AERON_URI_TERM_OFFSET_KEY);\n    count += term_offset_str ? 1 : 0;\n\n    if (count > 0)\n    {\n        char *end_ptr = NULL;\n\n        if (count < 3)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"params: %s %s %s must be used as a complete set\",\n                AERON_URI_INITIAL_TERM_ID_KEY,\n                AERON_URI_TERM_ID_KEY,\n                AERON_URI_TERM_OFFSET_KEY);\n            return -1;\n        }\n\n        errno = 0;\n        end_ptr = NULL;\n        uint64_t term_offset = strtoull(term_offset_str, &end_ptr, 0);\n        if ((term_offset == 0 && 0 != errno) || end_ptr == term_offset_str)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"could not parse %s=%s in URI: %s\",\n                AERON_URI_TERM_OFFSET_KEY,\n                term_offset_str,\n                strerror(errno));\n            return -1;\n        }\n\n        if (aeron_sub_wrap_i32(term_id, initial_term_id) < 0)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"Param difference greater than 2^31 - 1: %s=%\" PRId32 \" %s=%\" PRId32,\n                AERON_URI_INITIAL_TERM_ID_KEY,\n                initial_term_id,\n                AERON_URI_TERM_OFFSET_KEY,\n                term_id);\n            return -1;\n        }\n\n        if (term_offset > params->term_length)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"Param %s=%\" PRIu64 \" > %s=%\" PRIu64,\n                AERON_URI_TERM_OFFSET_KEY,\n                term_offset,\n                AERON_URI_TERM_LENGTH_KEY,\n                params->term_length);\n            return -1;\n        }\n\n        if ((term_offset & (AERON_LOGBUFFER_FRAME_ALIGNMENT - 1u)) != 0)\n        {\n            AERON_SET_ERR(\n                EINVAL,\n                \"Param %s=%\" PRIu64 \" must be multiple of FRAME_ALIGNMENT\",\n                AERON_URI_TERM_OFFSET_KEY,\n                params->term_offset);\n            return -1;\n        }\n\n        params->term_offset = term_offset;\n        params->initial_term_id = initial_term_id;\n        params->term_id = term_id;\n        params->has_position = true;\n    }\n\n    if (aeron_uri_get_bool(uri_params, AERON_URI_SPARSE_TERM_KEY, &params->is_sparse) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_bool(uri_params, AERON_URI_EOS_KEY, &params->signal_eos) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_bool(uri_params, AERON_URI_SPIES_SIMULATE_CONNECTION_KEY, &params->spies_simulate_connection) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_driver_uri_get_response_correlation_id(uri_params, &params->response_correlation_id) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_timeout(\n        uri_params,\n        AERON_URI_UNTETHERED_WINDOW_LIMIT_TIMEOUT_KEY,\n        &params->untethered_window_limit_timeout_ns) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    uint64_t cur_val = 0;\n    if (aeron_uri_get_timeout(\n            uri_params,\n            AERON_URI_UNTETHERED_LINGER_TIMEOUT_KEY,\n            &cur_val) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (cur_val > 0)\n    {\n        params->untethered_linger_timeout_ns = (int64_t)cur_val;\n    }\n    else if (params->untethered_linger_timeout_ns == AERON_NULL_VALUE)\n    {\n        params->untethered_linger_timeout_ns = (int64_t)params->untethered_window_limit_timeout_ns;\n    }\n\n    if (aeron_uri_get_timeout(\n        uri_params,\n        AERON_URI_UNTETHERED_RESTING_TIMEOUT_KEY,\n        &params->untethered_resting_timeout_ns) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nint aeron_driver_uri_subscription_params(\n    aeron_uri_t *uri, aeron_driver_uri_subscription_params_t *params, aeron_driver_conductor_t *conductor)\n{\n    aeron_driver_context_t *context = conductor->context;\n\n    params->is_reliable = context->reliable_stream;\n    params->is_sparse = context->term_buffer_sparse_file;\n    params->is_tether = context->tether_subscriptions;\n    params->is_rejoin = context->rejoin_stream;\n    params->initial_window_length = context->initial_window_length;\n    params->untethered_window_limit_timeout_ns = context->untethered_window_limit_timeout_ns;\n    params->untethered_linger_timeout_ns = context->untethered_linger_timeout_ns;\n    params->untethered_resting_timeout_ns = context->untethered_resting_timeout_ns;\n\n    aeron_uri_params_t *uri_params = AERON_URI_IPC == uri->type ?\n        &uri->params.ipc.additional_params : &uri->params.udp.additional_params;\n\n    if (aeron_uri_get_bool(uri_params, AERON_UDP_CHANNEL_RELIABLE_KEY, &params->is_reliable) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_bool(uri_params, AERON_URI_SPARSE_TERM_KEY, &params->is_sparse) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_bool(uri_params, AERON_URI_TETHER_KEY, &params->is_tether) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_bool(uri_params, AERON_URI_REJOIN_KEY, &params->is_rejoin) < 0)\n    {\n        return -1;\n    }\n\n    params->group = aeron_config_parse_inferable_boolean(\n        aeron_uri_find_param_value(uri_params, AERON_URI_GROUP_KEY), context->receiver_group_consideration);\n\n    if (aeron_uri_subscription_session_id_param(uri_params, params) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_uri_get_receiver_window_length(uri_params, &params->initial_window_length) < 0)\n    {\n        return -1;\n    }\n\n    params->is_response =\n        (AERON_URI_UDP == uri->type &&\n        NULL != uri->params.udp.control_mode &&\n        0 == strcmp(AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE, uri->params.udp.control_mode)) ||\n        (AERON_URI_IPC == uri->type &&\n        NULL != uri->params.ipc.control_mode &&\n        0 == strcmp(AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE, uri->params.ipc.control_mode));\n\n    if (aeron_uri_get_timeout(\n        uri_params,\n        AERON_URI_UNTETHERED_WINDOW_LIMIT_TIMEOUT_KEY,\n        &params->untethered_window_limit_timeout_ns) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    uint64_t cur_val = 0;\n    if (aeron_uri_get_timeout(\n        uri_params,\n        AERON_URI_UNTETHERED_LINGER_TIMEOUT_KEY,\n        &cur_val) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    if (cur_val > 0)\n    {\n        params->untethered_linger_timeout_ns = (int64_t)cur_val;\n    }\n    else if (params->untethered_linger_timeout_ns == AERON_NULL_VALUE)\n    {\n        params->untethered_linger_timeout_ns = (int64_t)params->untethered_window_limit_timeout_ns;\n    }\n\n    if (aeron_uri_get_timeout(\n        uri_params,\n        AERON_URI_UNTETHERED_RESTING_TIMEOUT_KEY,\n        &params->untethered_resting_timeout_ns) < 0)\n    {\n        AERON_APPEND_ERR(\"%s\", \"\");\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int aeron_publication_params_validate_mtu(size_t socket_sndbuf, size_t mtu_length, const char *name)\n{\n    if (socket_sndbuf < mtu_length)\n    {\n        AERON_SET_ERR(\n            EINVAL,\n            \"MTU greater than SO_SNDBUF for %s: mtu=%\" PRIu64 \" so-sndbuf=%\" PRIu64,\n            name, mtu_length, socket_sndbuf);\n        return -1;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nint aeron_publication_params_validate_mtu_for_sndbuf(\n    aeron_driver_uri_publication_params_t *params,\n    size_t endpoint_socket_sndbuf,\n    size_t channel_socket_sndbuf,\n    size_t context_socket_sndbuf,\n    size_t os_default_socket_sndbuf)\n{\n    if (0 != endpoint_socket_sndbuf)\n    {\n        return aeron_publication_params_validate_mtu(endpoint_socket_sndbuf, params->mtu_length, \"endpoint\");\n    }\n\n    if (0 != channel_socket_sndbuf)\n    {\n        return aeron_publication_params_validate_mtu(channel_socket_sndbuf, params->mtu_length, \"channel\");\n    }\n\n    if (0 != context_socket_sndbuf)\n    {\n        return aeron_publication_params_validate_mtu(context_socket_sndbuf, params->mtu_length, \"context\");\n    }\n\n    if (0 != os_default_socket_sndbuf)\n    {\n        return aeron_publication_params_validate_mtu(os_default_socket_sndbuf, params->mtu_length, \"os default\");\n    }\n\n    return 0;\n}\n\nint aeron_driver_uri_get_timestamp_offset(aeron_uri_t *uri, const char *key, int32_t *offset)\n{\n    *offset = AERON_NULL_VALUE;\n\n    if (AERON_URI_UDP != uri->type)\n    {\n        return 0;\n    }\n\n    const char *offset_str = aeron_uri_find_param_value(&uri->params.udp.additional_params, key);\n\n    if (NULL == offset_str)\n    {\n        return 0;\n    }\n\n    if (0 == strcmp(AERON_URI_TIMESTAMP_OFFSET_RESERVED, offset_str))\n    {\n        *offset = AERON_UDP_CHANNEL_RESERVED_VALUE_OFFSET;\n        return 0;\n    }\n\n    char *end_ptr = NULL;\n    errno = 0;\n    long parse_offset = strtol(offset_str, &end_ptr, 0);\n    errno = 0 == errno && '\\0' != *end_ptr ? EINVAL : 0;\n    if (0 != errno)\n    {\n        AERON_SET_ERR(errno, \"Invalid %s: %s\", AERON_URI_MEDIA_RCV_TIMESTAMP_OFFSET_KEY, offset_str);\n        return -1;\n    }\n\n    *offset = (int32_t)parse_offset;\n\n    return 0;\n}\n\nconst char *aeron_driver_uri_get_offset_info(int32_t offset)\n{\n    if (AERON_NULL_VALUE == offset)\n    {\n        return \"(not specified)\";\n    }\n    else if (AERON_UDP_CHANNEL_RESERVED_VALUE_OFFSET == offset)\n    {\n        return \"(reserved)\";\n    }\n\n    return \"\";\n}\n\nint aeron_driver_uri_get_response_correlation_id(aeron_uri_params_t *uri_params, int64_t *response_correlation_id)\n{\n    const char *response_correlation_id_str = aeron_uri_find_param_value(uri_params, AERON_URI_RESPONSE_CORRELATION_ID_KEY);\n\n    if (NULL == response_correlation_id_str)\n    {\n        return 0;\n    }\n\n    if (0 == strcmp(AERON_URI_RESPONSE_CORRELATION_ID_PROTOTYPE, response_correlation_id_str))\n    {\n        *response_correlation_id = AERON_URI_PROTOTYPE_VALUE_CORRELATION_ID;\n        return 0;\n    }\n\n    if (aeron_uri_get_int64(\n        uri_params, AERON_URI_RESPONSE_CORRELATION_ID_KEY, AERON_NULL_VALUE, response_correlation_id) < 0)\n    {\n        goto invalid_correlation_id;\n    }\n\n    if (*response_correlation_id < -1)\n    {\n        goto invalid_correlation_id;\n    }\n\n    return 0;\n\ninvalid_correlation_id:\n    AERON_SET_ERR(EINVAL, \"%s\", \"invalid response-correlation-id, must be a number greater than or equal to -1, or 'prototype'\");\n    return -1;\n}\n"
  },
  {
    "path": "aeron-driver/src/main/c/uri/aeron_driver_uri.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_DRIVER_URI_H\n#define AERON_AERON_DRIVER_URI_H\n\n#include \"uri/aeron_uri.h\"\n#include \"aeron_driver_common.h\"\n#include \"aeronmd.h\"\n\n#define AERON_URI_PROTOTYPE_VALUE_CORRELATION_ID (-2)\n\ntypedef struct aeron_driver_uri_publication_params_stct\n{\n    bool has_position;\n    bool is_sparse;\n    bool signal_eos;\n    bool spies_simulate_connection;\n    bool has_mtu_length;\n    size_t mtu_length;\n    bool has_term_length;\n    size_t term_length;\n    size_t term_offset;\n    int32_t initial_term_id;\n    int32_t term_id;\n    uint64_t linger_timeout_ns;\n    uint64_t untethered_window_limit_timeout_ns;\n    int64_t untethered_linger_timeout_ns;\n    uint64_t untethered_resting_timeout_ns;\n    bool has_session_id;\n    int32_t session_id;\n    int64_t entity_tag;\n    int64_t response_correlation_id;\n    bool has_max_resend;\n    uint32_t max_resend;\n    bool has_publication_window_length;\n    int32_t publication_window_length;\n    bool is_response;\n}\naeron_driver_uri_publication_params_t;\n\ntypedef struct aeron_driver_uri_subscription_params_stct\n{\n    bool is_reliable;\n    bool is_sparse;\n    bool is_tether;\n    bool is_rejoin;\n    bool has_session_id;\n    bool is_response;\n    aeron_inferable_boolean_t group;\n    int32_t session_id;\n    size_t initial_window_length;\n    uint64_t untethered_window_limit_timeout_ns;\n    int64_t untethered_linger_timeout_ns;\n    uint64_t untethered_resting_timeout_ns;\n}\naeron_driver_uri_subscription_params_t;\n\ntypedef struct aeron_driver_context_stct aeron_driver_context_t;\ntypedef struct aeron_driver_conductor_stct aeron_driver_conductor_t;\n\nint aeron_diver_uri_publication_params(\n    aeron_uri_t *uri,\n    aeron_driver_uri_publication_params_t *params,\n    aeron_driver_conductor_t *conductor,\n    bool is_exclusive);\n\nint aeron_driver_uri_subscription_params(\n    aeron_uri_t *uri,\n    aeron_driver_uri_subscription_params_t *params,\n    aeron_driver_conductor_t *conductor);\n\nint aeron_publication_params_validate_mtu_for_sndbuf(\n    aeron_driver_uri_publication_params_t *params,\n    size_t endpoint_socket_sndbuf,\n    size_t channel_socket_sndbuf,\n    size_t context_socket_sndbuf,\n    size_t os_default_socket_sndbuf);\n\nint aeron_driver_uri_get_timestamp_offset(aeron_uri_t *uri, const char *key, int32_t *offset);\nconst char *aeron_driver_uri_get_offset_info(int32_t offset);\n\n\n#endif //AERON_AERON_DRIVER_URI_H\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/AbstractMinMulticastFlowControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CommonContext;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.status.FlowControlReceivers;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.net.InetSocketAddress;\nimport java.util.Arrays;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\nimport static io.aeron.protocol.StatusMessageFlyweight.END_OF_STREAM_FLAG;\nimport static org.agrona.AsciiEncoding.parseIntAscii;\nimport static org.agrona.AsciiEncoding.parseLongAscii;\nimport static org.agrona.SystemUtil.parseDuration;\nimport static org.agrona.collections.ArrayUtil.add;\n\nabstract class AbstractMinMulticastFlowControlLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nabstract class AbstractMinMulticastFlowControlFields extends AbstractMinMulticastFlowControlLhsPadding\n{\n    long lastSetupSenderLimit;\n    long timeOfLastSetupNs;\n    boolean hasTaggedStatusMessageTriggeredSetup;\n}\n\nabstract class AbstractMinMulticastFlowControlRhsPadding extends AbstractMinMulticastFlowControlFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\n/**\n * Abstract minimum multicast sender flow control strategy. It supports the concept of only tracking the minimum of a\n * group of receivers, not all possible receivers. However, it is agnostic of how that group is determined.\n * <p>\n * Tracking of receivers is done as long as they continue to send Status Messages. Once SMs stop, the receiver tracking\n * for that receiver will time out after a given number of nanoseconds.\n */\npublic abstract class AbstractMinMulticastFlowControl\n    extends AbstractMinMulticastFlowControlRhsPadding\n    implements FlowControl\n{\n    private static final Receiver[] EMPTY_RECEIVERS = new Receiver[0];\n\n    private final boolean isGroupTagAware;\n    private volatile boolean hasRequiredReceivers;\n    private int groupMinSize;\n    private long groupTag;\n    private long receiverTimeoutNs;\n    private Receiver[] receivers = EMPTY_RECEIVERS;\n    private String channel;\n    private AtomicCounter receiverCount;\n    private ErrorHandler errorHandler;\n    private int retransmitReceiverWindowMultiple;\n\n    /**\n     * Base constructor for use by specialised implementations.\n     *\n     * @param isGroupTagAware true if the group tag is used.\n     */\n    protected AbstractMinMulticastFlowControl(final boolean isGroupTagAware)\n    {\n        this.isGroupTagAware = isGroupTagAware;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void initialize(\n        final MediaDriver.Context context,\n        final CountersManager countersManager,\n        final UdpChannel udpChannel,\n        final int streamId,\n        final int sessionId,\n        final long registrationId,\n        final int initialTermId,\n        final int termBufferLength)\n    {\n        receiverTimeoutNs = context.flowControlReceiverTimeoutNs();\n        groupTag = isGroupTagAware ? context.flowControlGroupTag() : 0;\n        groupMinSize = context.flowControlGroupMinSize();\n        channel = udpChannel.originalUriString();\n\n        parseUriParam(udpChannel.channelUri().get(CommonContext.FLOW_CONTROL_PARAM_NAME));\n        hasRequiredReceivers = receivers.length >= groupMinSize;\n        errorHandler = context.errorHandler();\n        receiverCount = FlowControlReceivers.allocate(\n            context.tempBuffer(), countersManager, registrationId, sessionId, streamId, channel);\n        timeOfLastSetupNs = 0;\n        lastSetupSenderLimit = -1;\n        hasTaggedStatusMessageTriggeredSetup = false;\n        retransmitReceiverWindowMultiple = FlowControl.retransmitReceiverWindowMultiple(\n            udpChannel,\n            context.multicastFlowControlRetransmitReceiverWindowMultiple()\n        );\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(errorHandler, receiverCount);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onSetup(\n        final SetupFlyweight flyweight,\n        final long senderLimit,\n        final long senderPosition,\n        final int positionBitsToShift,\n        final long timeNs)\n    {\n        if (hasTaggedStatusMessageTriggeredSetup && receivers.length > 0)\n        {\n            timeOfLastSetupNs = timeNs;\n            lastSetupSenderLimit = senderLimit;\n        }\n\n        hasTaggedStatusMessageTriggeredSetup = false;\n\n        return senderLimit;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onIdle(final long timeNs, final long senderLimit, final long senderPosition, final boolean isEos)\n    {\n        long minLimitPosition = lastSetupSenderLimit(timeNs);\n        int removed = 0;\n        Receiver[] receivers = this.receivers;\n\n        for (int lastIndex = receivers.length - 1, i = lastIndex; i >= 0; i--)\n        {\n            final Receiver receiver = receivers[i];\n            if ((receiver.timeOfLastStatusMessageNs + receiverTimeoutNs) - timeNs < 0 || receiver.eosFlagged)\n            {\n                if (i != lastIndex)\n                {\n                    receivers[i] = receivers[lastIndex--];\n                }\n                removed++;\n                receiverRemoved(\n                    receiver.receiverId, receiver.sessionId, receiver.streamId, channel, receivers.length - removed);\n            }\n            else\n            {\n                minLimitPosition = Math.min(minLimitPosition, receiver.lastPositionPlusWindow);\n            }\n        }\n\n        if (removed > 0)\n        {\n            receivers = truncateReceivers(receivers, removed);\n            hasRequiredReceivers = receivers.length >= groupMinSize;\n            this.receivers = receivers;\n            receiverCount.setRelease(receivers.length);\n        }\n\n        return receivers.length < groupMinSize || receivers.length == 0 ? senderLimit : minLimitPosition;\n    }\n\n    /**\n     * Has the observed receiver count reached the {@link #groupMinSize()} threshold?\n     *\n     * @return true if the observed receiver count reached the {@link #groupMinSize()} threshold?\n     */\n    public boolean hasRequiredReceivers()\n    {\n        return hasRequiredReceivers;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int maxRetransmissionLength(\n        final int termOffset,\n        final int resendLength,\n        final int termBufferLength,\n        final int mtuLength)\n    {\n        return FlowControl.calculateRetransmissionLength(\n            resendLength, termBufferLength, termOffset, retransmitReceiverWindowMultiple);\n    }\n\n    /**\n     * Process a received status message.\n     *\n     * @param flyweight           mapped over the status message.\n     * @param senderLimit         the sender is currently limited to for sending.\n     * @param initialTermId       for the publication.\n     * @param positionBitsToShift when calculating term length with requiring a divide.\n     * @param timeNs              current time.\n     * @param matchesTag          if the status messages comes from a receiver with a tag matching the group.\n     * @return the new position limit to be employed by the sender.\n     */\n    protected final long processStatusMessage(\n        final StatusMessageFlyweight flyweight,\n        final long senderLimit,\n        final int initialTermId,\n        final int positionBitsToShift,\n        final long timeNs,\n        final boolean matchesTag)\n    {\n        final long position = computePosition(\n            flyweight.consumptionTermId(),\n            flyweight.consumptionTermOffset(),\n            positionBitsToShift,\n            initialTermId);\n\n        final long windowLength = flyweight.receiverWindowLength();\n        final long receiverId = flyweight.receiverId();\n        final long lastPositionPlusWindow = position + windowLength;\n        final boolean eosFlagged = END_OF_STREAM_FLAG == (flyweight.flags() & END_OF_STREAM_FLAG);\n        boolean isExisting = false;\n        long minPosition = lastSetupSenderLimit(timeNs);\n\n        Receiver[] receivers = this.receivers;\n\n        for (final Receiver receiver : receivers)\n        {\n            if (matchesTag && receiverId == receiver.receiverId)\n            {\n                receiver.eosFlagged = eosFlagged;\n                receiver.lastPosition = Math.max(position, receiver.lastPosition);\n                receiver.lastPositionPlusWindow = lastPositionPlusWindow;\n                receiver.timeOfLastStatusMessageNs = timeNs;\n                isExisting = true;\n            }\n\n            minPosition = Math.min(minPosition, receiver.lastPositionPlusWindow);\n        }\n\n        if (!isExisting &&\n            !eosFlagged &&\n            matchesTag &&\n            (0 == receivers.length || lastPositionPlusWindow >= minPosition - windowLength))\n        {\n            final Receiver receiver = new Receiver(\n                receiverId, flyweight.sessionId(), flyweight.streamId(), position, lastPositionPlusWindow, timeNs);\n            receivers = add(receivers, receiver);\n            hasRequiredReceivers = receivers.length >= groupMinSize;\n            this.receivers = receivers;\n            minPosition = Math.min(minPosition, lastPositionPlusWindow);\n            receiverAdded(receiver.receiverId, receiver.sessionId, receiver.streamId, channel, receivers.length);\n            receiverCount.setRelease(receivers.length);\n            lastSetupSenderLimit = -1;\n        }\n\n        if (receivers.length < groupMinSize)\n        {\n            return senderLimit;\n        }\n        else if (0 == receivers.length)\n        {\n            return Math.max(senderLimit, lastPositionPlusWindow);\n        }\n        else\n        {\n            return Math.max(senderLimit, minPosition);\n        }\n    }\n\n    /**\n     *  Process a status message triggering a setup to be sent.\n     *\n     * @param flyweight       over the status message receiver.\n     * @param receiverAddress of the receiver.\n     * @param timeNs          current time (in nanoseconds).\n     * @param hasMatchingTag  if the status messages comes from a receiver with a tag matching the group.\n     */\n    protected void processSendSetupTrigger(\n        final StatusMessageFlyweight flyweight,\n        final InetSocketAddress receiverAddress,\n        final long timeNs,\n        final boolean hasMatchingTag)\n    {\n        if (!hasTaggedStatusMessageTriggeredSetup)\n        {\n            hasTaggedStatusMessageTriggeredSetup = hasMatchingTag;\n        }\n    }\n\n    /**\n     * Process an error frame from a downstream receiver.\n     *\n     * @param error             flyweight over the error frame.\n     * @param receiverAddress   of the receiver.\n     * @param timeNs            current time in nanoseconds.\n     * @param hasMatchingTag    if the error message comes from a receiver with a tag matching the group.\n     */\n    protected void processError(\n        final ErrorFlyweight error,\n        final InetSocketAddress receiverAddress,\n        final long timeNs,\n        final boolean hasMatchingTag)\n    {\n        if (hasMatchingTag)\n        {\n            final long receiverId = error.receiverId();\n            for (final Receiver receiver : receivers)\n            {\n                if (receiverId == receiver.receiverId)\n                {\n                    receiver.eosFlagged = true;\n                    break;\n                }\n            }\n        }\n    }\n\n    /**\n     * Timeout after which an inactive receiver will be dropped.\n     *\n     * @return timeout after which an inactive receiver will be dropped.\n     */\n    protected final long receiverTimeoutNs()\n    {\n        return receiverTimeoutNs;\n    }\n\n    /**\n     * Indicates if the flow control strategy has a group tag it is aware of for tracking membership.\n     *\n     * @return true if the flow control strategy has a group tag it is aware of for tracking membership.\n     */\n    protected final boolean hasGroupTag()\n    {\n        return isGroupTagAware;\n    }\n\n    /**\n     * The tag used to identify members of the group.\n     *\n     * @return tag used to identify members of the group.\n     */\n    protected final long groupTag()\n    {\n        return groupTag;\n    }\n\n    /**\n     * The minimum group size required for progress.\n     *\n     * @return minimum group size required for progress.\n     */\n    protected final int groupMinSize()\n    {\n        return groupMinSize;\n    }\n\n    static Receiver[] truncateReceivers(final Receiver[] receivers, final int removed)\n    {\n        final int length = receivers.length;\n        final int newLength = length - removed;\n\n        if (0 == newLength)\n        {\n            return EMPTY_RECEIVERS;\n        }\n        else\n        {\n            return Arrays.copyOf(receivers, newLength);\n        }\n    }\n\n    private void parseUriParam(final String fcValue)\n    {\n        if (null != fcValue)\n        {\n            for (final String arg : fcValue.split(\",\"))\n            {\n                if (arg.startsWith(\"t:\"))\n                {\n                    receiverTimeoutNs = parseDuration(\"fc receiver timeout\", arg.substring(2));\n                }\n                else if (arg.startsWith(\"g:\"))\n                {\n                    final int groupMinSizeIndex = arg.indexOf('/');\n\n                    if (2 != groupMinSizeIndex && isGroupTagAware)\n                    {\n                        final int lengthToParse = -1 == groupMinSizeIndex ? arg.length() - 2 : groupMinSizeIndex - 2;\n                        groupTag = parseLongAscii(arg, 2, lengthToParse);\n                    }\n\n                    if (-1 != groupMinSizeIndex)\n                    {\n                        groupMinSize = parseIntAscii(\n                            arg, groupMinSizeIndex + 1, arg.length() - (groupMinSizeIndex + 1));\n                    }\n                }\n            }\n        }\n    }\n\n    private void receiverAdded(\n        final long receiverId, final int sessionId, final int streamId, final String channel, final int receiverCount)\n    {\n//        System.out.println(\"Receiver added: receiverCount=\" + receiverCount +\n//            \", receiverId=\" + receiverId + \", sessionId=\" + sessionId + \", streamId=\" + streamId +\n//            \", channel=\" + channel);\n    }\n\n    private void receiverRemoved(\n        final long receiverId, final int sessionId, final int streamId, final String channel, final int receiverCount)\n    {\n//        System.out.println(\"Receiver removed: receiverCount=\" + receiverCount +\n//            \", receiverId=\" + receiverId + \", sessionId=\" + sessionId + \", streamId=\" + streamId +\n//            \", channel=\" + channel);\n    }\n\n    private long lastSetupSenderLimit(final long nowNs)\n    {\n        if (-1 != lastSetupSenderLimit)\n        {\n            if ((timeOfLastSetupNs + receiverTimeoutNs) - nowNs < 0)\n            {\n                lastSetupSenderLimit = -1;\n            }\n            else\n            {\n                return lastSetupSenderLimit;\n            }\n        }\n\n        return Long.MAX_VALUE;\n    }\n\n    static final class Receiver\n    {\n        final int sessionId;\n        final int streamId;\n        final long receiverId;\n        long lastPosition;\n        long lastPositionPlusWindow;\n        long timeOfLastStatusMessageNs;\n        boolean eosFlagged;\n\n        Receiver(\n            final long receiverId,\n            final int sessionId,\n            final int streamId,\n            final long lastPosition,\n            final long lastPositionPlusWindow,\n            final long timeNs)\n        {\n            this.receiverId = receiverId;\n            this.sessionId = sessionId;\n            this.streamId = streamId;\n            this.lastPosition = lastPosition;\n            this.lastPositionPlusWindow = lastPositionPlusWindow;\n            this.timeOfLastStatusMessageNs = timeNs;\n            this.eosFlagged = false;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/AeronClient.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Aeron client library tracker.\n */\nfinal class AeronClient implements DriverManagedResource\n{\n    private final long clientId;\n    private final long clientLivenessTimeoutMs;\n    private boolean reachedEndOfLife = false;\n    private boolean closedByCommand = false;\n    private final AtomicCounter clientTimeouts;\n    private final AtomicCounter heartbeatTimestamp;\n\n    AeronClient(\n        final long clientId,\n        final long clientLivenessTimeoutNs,\n        final AtomicCounter clientTimeouts,\n        final AtomicCounter heartbeatTimestamp)\n    {\n        this.clientId = clientId;\n        this.clientLivenessTimeoutMs = Math.max(1, TimeUnit.NANOSECONDS.toMillis(clientLivenessTimeoutNs));\n        this.clientTimeouts = clientTimeouts;\n        this.heartbeatTimestamp = heartbeatTimestamp;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        heartbeatTimestamp.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTimeEvent(final long timeNs, final long timeMs, final DriverConductor conductor)\n    {\n        if (timeMs > (heartbeatTimestamp.get() + clientLivenessTimeoutMs))\n        {\n            reachedEndOfLife = true;\n\n            if (!closedByCommand)\n            {\n                clientTimeouts.incrementRelease();\n                conductor.clientTimeout(clientId);\n            }\n\n            conductor.unavailableCounter(clientId, heartbeatTimestamp.id());\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean hasReachedEndOfLife()\n    {\n        return reachedEndOfLife;\n    }\n\n\n    public String toString()\n    {\n        return \"AeronClient{\" +\n            \"clientId=\" + clientId +\n            \", clientLivenessTimeoutMs=\" + clientLivenessTimeoutMs +\n            \", reachedEndOfLife=\" + reachedEndOfLife +\n            \", closedByCommand=\" + closedByCommand +\n            \", clientTimeouts=\" + clientTimeouts +\n            \", heartbeatTimestamp=\" + heartbeatTimestamp +\n            '}';\n    }\n\n    long clientId()\n    {\n        return clientId;\n    }\n\n    boolean hasTimedOut()\n    {\n        return reachedEndOfLife;\n    }\n\n    void timeOfLastKeepaliveMs(final long nowMs)\n    {\n        heartbeatTimestamp.setRelease(nowMs);\n    }\n\n    void onClosedByCommand()\n    {\n        closedByCommand = true;\n        heartbeatTimestamp.setRelease(0);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ClientCommandAdapter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.ErrorCode;\nimport io.aeron.command.*;\nimport io.aeron.exceptions.ControlProtocolException;\nimport io.aeron.exceptions.StorageSpaceException;\nimport org.agrona.ErrorHandler;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.ControlledMessageHandler;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport static io.aeron.ChannelUri.SPY_QUALIFIER;\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.ErrorCode.GENERIC_ERROR;\nimport static io.aeron.ErrorCode.STORAGE_SPACE;\nimport static io.aeron.command.ControlProtocolEvents.*;\n\n/**\n * Receives commands from Aeron clients and dispatches them to the {@link DriverConductor} for processing.\n */\nfinal class ClientCommandAdapter implements ControlledMessageHandler\n{\n    private final PublicationMessageFlyweight publicationMsgFlyweight = new PublicationMessageFlyweight();\n    private final SubscriptionMessageFlyweight subscriptionMsgFlyweight = new SubscriptionMessageFlyweight();\n    private final CorrelatedMessageFlyweight correlatedMsgFlyweight = new CorrelatedMessageFlyweight();\n    private final RemoveCounterFlyweight removeCounterFlyweight = new RemoveCounterFlyweight();\n    private final RemovePublicationFlyweight removePublicationFlyweight = new RemovePublicationFlyweight();\n    private final RemoveSubscriptionFlyweight removeSubscriptionFlyweight = new RemoveSubscriptionFlyweight();\n    private final DestinationMessageFlyweight destinationMsgFlyweight = new DestinationMessageFlyweight();\n    private final CounterMessageFlyweight counterMsgFlyweight = new CounterMessageFlyweight();\n    private final StaticCounterMessageFlyweight staticCounterMessageFlyweight = new StaticCounterMessageFlyweight();\n    private final TerminateDriverFlyweight terminateDriverFlyweight = new TerminateDriverFlyweight();\n    private final RejectImageFlyweight rejectImageFlyweight = new RejectImageFlyweight();\n    private final DestinationByIdMessageFlyweight destinationByIdMessageFlyweight =\n        new DestinationByIdMessageFlyweight();\n    private final GetNextAvailableSessionIdMessageFlyweight getNextAvailableSessionIdMessageFlyweight =\n        new GetNextAvailableSessionIdMessageFlyweight();\n    private final DriverConductor conductor;\n    private final RingBuffer toDriverCommands;\n    private final ClientProxy clientProxy;\n    private final AtomicCounter errors;\n    private final ErrorHandler errorHandler;\n\n    ClientCommandAdapter(\n        final AtomicCounter errors,\n        final ErrorHandler errorHandler,\n        final RingBuffer toDriverCommands,\n        final ClientProxy clientProxy,\n        final DriverConductor driverConductor)\n    {\n        this.errors = errors;\n        this.errorHandler = errorHandler;\n        this.toDriverCommands = toDriverCommands;\n        this.clientProxy = clientProxy;\n        this.conductor = driverConductor;\n    }\n\n    int receive()\n    {\n        return toDriverCommands.controlledRead(this, Configuration.COMMAND_DRAIN_LIMIT);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @SuppressWarnings(\"MethodLength\")\n    public ControlledMessageHandler.Action onMessage(\n        final int msgTypeId, final MutableDirectBuffer buffer, final int index, final int length)\n    {\n        long correlationId = 0;\n\n        if (conductor.notAcceptingClientCommands())\n        {\n            return Action.ABORT;\n        }\n\n        try\n        {\n            switch (msgTypeId)\n            {\n                case ADD_PUBLICATION:\n                {\n                    publicationMsgFlyweight.wrap(buffer, index);\n                    publicationMsgFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = publicationMsgFlyweight.correlationId();\n                    addPublication(correlationId, false);\n                    break;\n                }\n\n                case REMOVE_PUBLICATION:\n                {\n                    removePublicationFlyweight.wrap(buffer, index);\n\n                    correlationId = removePublicationFlyweight.correlationId();\n                    conductor.onRemovePublication(\n                        removePublicationFlyweight.registrationId(),\n                        correlationId,\n                        removePublicationFlyweight.revoke(length));\n                    break;\n                }\n\n                case ADD_EXCLUSIVE_PUBLICATION:\n                {\n                    publicationMsgFlyweight.wrap(buffer, index);\n                    publicationMsgFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = publicationMsgFlyweight.correlationId();\n                    addPublication(correlationId, true);\n                    break;\n                }\n\n                case ADD_SUBSCRIPTION:\n                {\n                    subscriptionMsgFlyweight.wrap(buffer, index);\n                    subscriptionMsgFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = subscriptionMsgFlyweight.correlationId();\n                    final long clientId = subscriptionMsgFlyweight.clientId();\n                    final int streamId = subscriptionMsgFlyweight.streamId();\n                    final String channel = subscriptionMsgFlyweight.channel();\n\n                    if (channel.startsWith(IPC_CHANNEL))\n                    {\n                        conductor.onAddIpcSubscription(channel, streamId, correlationId, clientId);\n                    }\n                    else if (channel.startsWith(SPY_QUALIFIER))\n                    {\n                        conductor.onAddSpySubscription(channel, streamId, correlationId, clientId);\n                    }\n                    else\n                    {\n                        conductor.onAddNetworkSubscription(channel, streamId, correlationId, clientId);\n                    }\n                    break;\n                }\n\n                case REMOVE_SUBSCRIPTION:\n                {\n                    removeSubscriptionFlyweight.wrap(buffer, index);\n                    removeSubscriptionFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = removeSubscriptionFlyweight.correlationId();\n                    conductor.onRemoveSubscription(removeSubscriptionFlyweight.registrationId(), correlationId);\n                    break;\n                }\n\n                case ADD_DESTINATION:\n                {\n                    destinationMsgFlyweight.wrap(buffer, index);\n                    destinationMsgFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = destinationMsgFlyweight.correlationId();\n                    final long channelRegistrationId = destinationMsgFlyweight.registrationCorrelationId();\n                    final String channel = destinationMsgFlyweight.channel();\n\n                    conductor.onAddSendDestination(channelRegistrationId, channel, correlationId);\n                    break;\n                }\n\n                case REMOVE_DESTINATION:\n                {\n                    destinationMsgFlyweight.wrap(buffer, index);\n                    destinationMsgFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = destinationMsgFlyweight.correlationId();\n                    final long channelRegistrationId = destinationMsgFlyweight.registrationCorrelationId();\n                    final String channel = destinationMsgFlyweight.channel();\n\n                    conductor.onRemoveSendDestination(channelRegistrationId, channel, correlationId);\n                    break;\n                }\n\n                case CLIENT_KEEPALIVE:\n                {\n                    correlatedMsgFlyweight.wrap(buffer, index);\n                    correlatedMsgFlyweight.validateLength(msgTypeId, length);\n\n                    conductor.onClientKeepalive(correlatedMsgFlyweight.clientId());\n                    break;\n                }\n\n                case ADD_COUNTER:\n                {\n                    counterMsgFlyweight.wrap(buffer, index);\n                    counterMsgFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = counterMsgFlyweight.correlationId();\n                    final long clientId = counterMsgFlyweight.clientId();\n                    conductor.onAddCounter(\n                        counterMsgFlyweight.typeId(),\n                        buffer,\n                        index + counterMsgFlyweight.keyBufferOffset(),\n                        counterMsgFlyweight.keyBufferLength(),\n                        buffer,\n                        index + counterMsgFlyweight.labelBufferOffset(),\n                        counterMsgFlyweight.labelBufferLength(),\n                        correlationId,\n                        clientId);\n                    break;\n                }\n\n                case REMOVE_COUNTER:\n                {\n                    removeCounterFlyweight.wrap(buffer, index);\n                    removeCounterFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = removeCounterFlyweight.correlationId();\n                    conductor.onRemoveCounter(removeCounterFlyweight.registrationId(), correlationId);\n                    break;\n                }\n\n                case CLIENT_CLOSE:\n                {\n                    correlatedMsgFlyweight.wrap(buffer, index);\n                    correlatedMsgFlyweight.validateLength(msgTypeId, length);\n\n                    conductor.onClientClose(correlatedMsgFlyweight.clientId());\n                    break;\n                }\n\n                case ADD_RCV_DESTINATION:\n                {\n                    destinationMsgFlyweight.wrap(buffer, index);\n                    destinationMsgFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = destinationMsgFlyweight.correlationId();\n                    final long channelRegistrationId = destinationMsgFlyweight.registrationCorrelationId();\n                    final String channel = destinationMsgFlyweight.channel();\n\n                    conductor.onAddRcvDestination(channelRegistrationId, channel, correlationId);\n                    break;\n                }\n\n                case REMOVE_RCV_DESTINATION:\n                {\n                    destinationMsgFlyweight.wrap(buffer, index);\n                    destinationMsgFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = destinationMsgFlyweight.correlationId();\n                    final long channelRegistrationId = destinationMsgFlyweight.registrationCorrelationId();\n                    final String channel = destinationMsgFlyweight.channel();\n\n                    conductor.onRemoveRcvDestination(channelRegistrationId, channel, correlationId);\n                    break;\n                }\n\n                case TERMINATE_DRIVER:\n                {\n                    terminateDriverFlyweight.wrap(buffer, index);\n                    terminateDriverFlyweight.validateLength(msgTypeId, length);\n\n                    conductor.onTerminateDriver(\n                        buffer,\n                        terminateDriverFlyweight.tokenBufferOffset(),\n                        terminateDriverFlyweight.tokenBufferLength());\n                    break;\n                }\n\n                case ADD_STATIC_COUNTER:\n                {\n                    staticCounterMessageFlyweight.wrap(buffer, index);\n                    staticCounterMessageFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = staticCounterMessageFlyweight.correlationId();\n                    final long clientId = staticCounterMessageFlyweight.clientId();\n                    conductor.onAddStaticCounter(\n                        staticCounterMessageFlyweight.typeId(),\n                        buffer,\n                        index + staticCounterMessageFlyweight.keyBufferOffset(),\n                        staticCounterMessageFlyweight.keyBufferLength(),\n                        buffer,\n                        index + staticCounterMessageFlyweight.labelBufferOffset(),\n                        staticCounterMessageFlyweight.labelBufferLength(),\n                        staticCounterMessageFlyweight.registrationId(),\n                        correlationId,\n                        clientId);\n                    break;\n                }\n\n                case REJECT_IMAGE:\n                {\n                    rejectImageFlyweight.wrap(buffer, index);\n                    rejectImageFlyweight.validateLength(msgTypeId, length);\n                    correlationId = rejectImageFlyweight.correlationId();\n\n                    conductor.onRejectImage(\n                        correlationId,\n                        rejectImageFlyweight.imageCorrelationId(),\n                        rejectImageFlyweight.position(),\n                        rejectImageFlyweight.reason());\n                    break;\n                }\n\n                case REMOVE_DESTINATION_BY_ID:\n                {\n                    destinationByIdMessageFlyweight.wrap(buffer, index);\n                    destinationByIdMessageFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = destinationByIdMessageFlyweight.correlationId();\n                    conductor.onRemoveSendDestination(\n                        destinationByIdMessageFlyweight.resourceRegistrationId(),\n                        destinationByIdMessageFlyweight.destinationRegistrationId(),\n                        correlationId);\n\n                    break;\n                }\n\n                case GET_NEXT_AVAILABLE_SESSION_ID:\n                {\n                    getNextAvailableSessionIdMessageFlyweight.wrap(buffer, index);\n                    getNextAvailableSessionIdMessageFlyweight.validateLength(msgTypeId, length);\n\n                    correlationId = getNextAvailableSessionIdMessageFlyweight.correlationId();\n                    conductor.onNextAvailableSessionId(\n                        correlationId, getNextAvailableSessionIdMessageFlyweight.streamId());\n\n                    break;\n                }\n\n                default:\n                {\n                    final ControlProtocolException ex = new ControlProtocolException(\n                        ErrorCode.UNKNOWN_COMMAND_TYPE_ID, \"command typeId=\" + msgTypeId);\n\n                    onError(correlationId, ex);\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            onError(correlationId, ex);\n        }\n\n        return Action.CONTINUE;\n    }\n\n    private void addPublication(final long correlationId, final boolean isExclusive)\n    {\n        final long clientId = publicationMsgFlyweight.clientId();\n        final int streamId = publicationMsgFlyweight.streamId();\n        final String channel = publicationMsgFlyweight.channel();\n\n        if (channel.startsWith(IPC_CHANNEL))\n        {\n            conductor.onAddIpcPublication(channel, streamId, correlationId, clientId, isExclusive);\n        }\n        else\n        {\n            conductor.onAddNetworkPublication(channel, streamId, correlationId, clientId, isExclusive);\n        }\n    }\n\n    void onError(final long correlationId, final Exception error)\n    {\n        if (!errors.isClosed())\n        {\n            errors.increment();\n        }\n\n        errorHandler.onError(error);\n\n        if (error instanceof ControlProtocolException)\n        {\n            clientProxy.onError(correlationId, ((ControlProtocolException)error).errorCode(), error.getMessage());\n        }\n        else if (StorageSpaceException.isStorageSpaceError(error))\n        {\n            clientProxy.onError(correlationId, STORAGE_SPACE, error.getMessage());\n        }\n        else\n        {\n            final String errorMessage = error.getClass().getName() + \" : \" + error.getMessage();\n            clientProxy.onError(correlationId, GENERIC_ERROR, errorMessage);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ClientProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.ErrorCode;\nimport io.aeron.command.*;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.broadcast.BroadcastTransmitter;\n\nimport java.net.InetSocketAddress;\n\nimport static io.aeron.command.ControlProtocolEvents.*;\n\n/**\n * Proxy for communicating from the driver to the client conductor.\n */\nfinal class ClientProxy\n{\n    private final MutableDirectBuffer buffer = new ExpandableArrayBuffer(1024);\n    private final BroadcastTransmitter transmitter;\n\n    private final ErrorResponseFlyweight errorResponse = new ErrorResponseFlyweight();\n    private final PublicationErrorFrameFlyweight publicationErrorFrame = new PublicationErrorFrameFlyweight();\n    private final PublicationBuffersReadyFlyweight publicationReady = new PublicationBuffersReadyFlyweight();\n    private final SubscriptionReadyFlyweight subscriptionReady = new SubscriptionReadyFlyweight();\n    private final ImageBuffersReadyFlyweight imageReady = new ImageBuffersReadyFlyweight();\n    private final OperationSucceededFlyweight operationSucceeded = new OperationSucceededFlyweight();\n    private final ImageMessageFlyweight imageMessage = new ImageMessageFlyweight();\n    private final CounterUpdateFlyweight counterUpdate = new CounterUpdateFlyweight();\n    private final ClientTimeoutFlyweight clientTimeout = new ClientTimeoutFlyweight();\n    private final StaticCounterFlyweight staticCounter = new StaticCounterFlyweight();\n    private final NextAvailableSessionIdFlyweight nextSessionId = new NextAvailableSessionIdFlyweight();\n\n    ClientProxy(final BroadcastTransmitter transmitter)\n    {\n        this.transmitter = transmitter;\n\n        errorResponse.wrap(buffer, 0);\n        publicationErrorFrame.wrap(buffer, 0);\n        imageReady.wrap(buffer, 0);\n        publicationReady.wrap(buffer, 0);\n        subscriptionReady.wrap(buffer, 0);\n        operationSucceeded.wrap(buffer, 0);\n        imageMessage.wrap(buffer, 0);\n        counterUpdate.wrap(buffer, 0);\n        clientTimeout.wrap(buffer, 0);\n        staticCounter.wrap(buffer, 0);\n        nextSessionId.wrap(buffer, 0);\n    }\n\n    void onError(final long correlationId, final ErrorCode errorCode, final String errorMessage)\n    {\n        errorResponse\n            .offendingCommandCorrelationId(correlationId)\n            .errorCode(errorCode)\n            .errorMessage(errorMessage);\n\n        transmit(ON_ERROR, buffer, 0, errorResponse.length());\n    }\n\n    void onPublicationErrorFrame(\n        final long registrationId,\n        final long destinationRegistrationId, final int sessionId,\n        final int streamId,\n        final long receiverId,\n        final Long groupTag,\n        final InetSocketAddress srcAddress,\n        final int errorCode,\n        final String errorMessage)\n    {\n        publicationErrorFrame\n            .registrationId(registrationId)\n            .destinationRegistrationId(destinationRegistrationId)\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .receiverId(receiverId)\n            .groupTag(null == groupTag ? Aeron.NULL_VALUE : groupTag)\n            .sourceAddress(srcAddress)\n            .errorCode(ErrorCode.get(errorCode))\n            .errorMessage(errorMessage);\n\n        transmit(ON_PUBLICATION_ERROR, buffer, 0, publicationErrorFrame.length());\n    }\n\n    void onAvailableImage(\n        final long correlationId,\n        final int streamId,\n        final int sessionId,\n        final long subscriptionRegistrationId,\n        final int positionCounterId,\n        final String logFileName,\n        final String sourceIdentity)\n    {\n        imageReady\n            .correlationId(correlationId)\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .subscriptionRegistrationId(subscriptionRegistrationId)\n            .subscriberPositionId(positionCounterId)\n            .logFileName(logFileName)\n            .sourceIdentity(sourceIdentity);\n\n        transmit(ON_AVAILABLE_IMAGE, buffer, 0, imageReady.length());\n    }\n\n    void onPublicationReady(\n        final long correlationId,\n        final long registrationId,\n        final int streamId,\n        final int sessionId,\n        final String logFileName,\n        final int positionCounterId,\n        final int channelStatusCounterId,\n        final boolean isExclusive)\n    {\n        publicationReady\n            .correlationId(correlationId)\n            .registrationId(registrationId)\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .publicationLimitCounterId(positionCounterId)\n            .channelStatusCounterId(channelStatusCounterId)\n            .logFileName(logFileName);\n\n        final int msgTypeId = isExclusive ? ON_EXCLUSIVE_PUBLICATION_READY : ON_PUBLICATION_READY;\n        transmit(msgTypeId, buffer, 0, publicationReady.length());\n    }\n\n    void onSubscriptionReady(final long correlationId, final int channelStatusCounterId)\n    {\n        subscriptionReady\n            .correlationId(correlationId)\n            .channelStatusCounterId(channelStatusCounterId);\n\n        transmit(ON_SUBSCRIPTION_READY, buffer, 0, SubscriptionReadyFlyweight.LENGTH);\n    }\n\n    void operationSucceeded(final long correlationId)\n    {\n        operationSucceeded.correlationId(correlationId);\n\n        transmit(ON_OPERATION_SUCCESS, buffer, 0, OperationSucceededFlyweight.LENGTH);\n    }\n\n    void onUnavailableImage(\n        final long correlationId, final long subscriptionRegistrationId, final int streamId, final String channel)\n    {\n        imageMessage\n            .correlationId(correlationId)\n            .subscriptionRegistrationId(subscriptionRegistrationId)\n            .streamId(streamId)\n            .channel(channel);\n\n        transmit(ON_UNAVAILABLE_IMAGE, buffer, 0, imageMessage.length());\n    }\n\n    void onCounterReady(final long correlationId, final int counterId)\n    {\n        counterUpdate\n            .correlationId(correlationId)\n            .counterId(counterId);\n\n        transmit(ON_COUNTER_READY, buffer, 0, CounterUpdateFlyweight.LENGTH);\n    }\n\n    void onStaticCounter(final long correlationId, final int counterId)\n    {\n        staticCounter\n            .correlationId(correlationId)\n            .counterId(counterId);\n\n        transmit(ON_STATIC_COUNTER, buffer, 0, StaticCounterFlyweight.LENGTH);\n    }\n\n    void onUnavailableCounter(final long registrationId, final int counterId)\n    {\n        counterUpdate\n            .correlationId(registrationId)\n            .counterId(counterId);\n\n        transmit(ON_UNAVAILABLE_COUNTER, buffer, 0, CounterUpdateFlyweight.LENGTH);\n    }\n\n    void onClientTimeout(final long clientId)\n    {\n        clientTimeout.clientId(clientId);\n\n        transmit(ON_CLIENT_TIMEOUT, buffer, 0, ClientTimeoutFlyweight.LENGTH);\n    }\n\n    void onNextAvailableSessionId(final long correlationId, final int sessionId)\n    {\n        nextSessionId\n            .correlationId(correlationId)\n            .nextSessionId(sessionId);\n\n        transmit(ON_NEXT_AVAILABLE_SESSION_ID, buffer, 0, NextAvailableSessionIdFlyweight.LENGTH);\n    }\n\n    private void transmit(final int msgTypeId, final DirectBuffer buffer, final int index, final int length)\n    {\n        transmitter.transmit(msgTypeId, buffer, index, length);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ClientProxy{}\";\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/CommandProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.OneToOneConcurrentArrayQueue;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.util.function.Consumer;\n\nimport static io.aeron.driver.ThreadingMode.INVOKER;\nimport static io.aeron.driver.ThreadingMode.SHARED;\n\nabstract class CommandProxy\n{\n    static final Consumer<Runnable> RUN_TASK = Runnable::run;\n    private final ThreadingMode threadingMode;\n    private final OneToOneConcurrentArrayQueue<Runnable> commandQueue;\n    private final AtomicCounter failCount;\n    private final boolean notConcurrent;\n\n    CommandProxy(\n        final ThreadingMode threadingMode,\n        final OneToOneConcurrentArrayQueue<Runnable> commandQueue,\n        final AtomicCounter failCount)\n    {\n        this.threadingMode = threadingMode;\n        this.commandQueue = commandQueue;\n        this.failCount = failCount;\n        notConcurrent = SHARED == threadingMode || INVOKER == threadingMode;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return getClass().getSimpleName() + \"{\" +\n            \"threadingMode=\" + threadingMode +\n            \", failCount=\" + failCount +\n            '}';\n    }\n\n    final boolean notConcurrent()\n    {\n        return notConcurrent;\n    }\n\n    final ThreadingMode threadingMode()\n    {\n        return threadingMode;\n    }\n\n    final void offer(final Runnable cmd)\n    {\n        while (!commandQueue.offer(cmd))\n        {\n            if (!failCount.isClosed())\n            {\n                failCount.increment();\n            }\n\n            Thread.yield();\n            if (Thread.currentThread().isInterrupted())\n            {\n                throw new AgentTerminationException(\"interrupted\");\n            }\n        }\n    }\n\n    final boolean isApplyingBackpressure()\n    {\n        return commandQueue.remainingCapacity() < 1;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/Configuration.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.AsciiNumberFormatException;\nimport org.agrona.BitUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.Strings;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.concurrent.BackoffIdleStrategy;\nimport org.agrona.concurrent.BusySpinIdleStrategy;\nimport org.agrona.concurrent.ControllableIdleStrategy;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.NoOpIdleStrategy;\nimport org.agrona.concurrent.SleepingIdleStrategy;\nimport org.agrona.concurrent.SleepingMillisIdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.broadcast.BroadcastBufferDescriptor;\nimport org.agrona.concurrent.ringbuffer.RingBufferDescriptor;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.agrona.concurrent.status.StatusIndicator;\n\nimport java.net.InetSocketAddress;\nimport java.util.Objects;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.ThreadingMode.DEDICATED;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PAGE_MAX_SIZE;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PAGE_MIN_SIZE;\nimport static java.lang.Integer.getInteger;\nimport static java.lang.Long.getLong;\nimport static java.lang.System.getProperty;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.fromHex;\nimport static org.agrona.SystemUtil.getDurationInNanos;\nimport static org.agrona.SystemUtil.getSizeAsInt;\nimport static org.agrona.SystemUtil.getSizeAsLong;\n\n/**\n * Configuration options for the {@link MediaDriver}.\n */\npublic final class Configuration\n{\n    private Configuration()\n    {\n    }\n\n    /**\n     * Warn if the Aeron directory exists.\n     */\n    @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false)\n    public static final String DIR_WARN_IF_EXISTS_PROP_NAME = \"aeron.dir.warn.if.exists\";\n\n    /**\n     * Should the Media Driver attempt to immediately delete the directory {@link CommonContext#AERON_DIR_PROP_NAME}\n     * on start if it exists before performing any additional checks.\n     */\n    @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false)\n    public static final String DIR_DELETE_ON_START_PROP_NAME = \"aeron.dir.delete.on.start\";\n\n    /**\n     * Should driver attempt to delete {@link CommonContext#AERON_DIR_PROP_NAME} on shutdown.\n     */\n    @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false)\n    public static final String DIR_DELETE_ON_SHUTDOWN_PROP_NAME = \"aeron.dir.delete.on.shutdown\";\n\n    /**\n     * Should high resolution timer be used on Windows.\n     */\n    @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false, existsInC = false)\n    public static final String USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME = \"aeron.use.windows.high.res.timer\";\n\n    /**\n     * Property name for default boolean value for if subscriptions should have a tether for local flow control.\n     */\n    @Config(uriParam = CommonContext.TETHER_PARAM_NAME, defaultType = DefaultType.BOOLEAN, defaultBoolean = true)\n    public static final String TETHER_SUBSCRIPTIONS_PROP_NAME = \"aeron.tether.subscriptions\";\n\n    /**\n     * Property name for default boolean value for if a stream is reliable. True to NAK, false to gap fill.\n     */\n    @Config(\n        uriParam = CommonContext.RELIABLE_STREAM_PARAM_NAME, defaultType = DefaultType.BOOLEAN, defaultBoolean = true)\n    public static final String RELIABLE_STREAM_PROP_NAME = \"aeron.reliable.stream\";\n\n    /**\n     * Property name for boolean value if term buffers should be created sparse.\n     */\n    @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = false)\n    public static final String TERM_BUFFER_SPARSE_FILE_PROP_NAME = \"aeron.term.buffer.sparse.file\";\n\n    /**\n     * Property name for default boolean value for if subscriptions should be considered a group member or individual.\n     */\n    public static final String GROUP_RECEIVER_CONSIDERATION_PROP_NAME = \"aeron.receiver.group.consideration\";\n\n    /**\n     * Property name for page size to align all files to.\n     */\n    @Config\n    public static final String FILE_PAGE_SIZE_PROP_NAME = \"aeron.file.page.size\";\n\n    /**\n     * Default page size for alignment of all files.\n     */\n    @Config\n    public static final int FILE_PAGE_SIZE_DEFAULT = 4 * 1024;\n\n    /**\n     * Property name for boolean value for if storage checks should be performed when allocating files.\n     */\n    @Config(defaultType = DefaultType.BOOLEAN, defaultBoolean = true)\n    public static final String PERFORM_STORAGE_CHECKS_PROP_NAME = \"aeron.perform.storage.checks\";\n\n    /**\n     * Length (in bytes) of the log buffers for UDP publication terms.\n     */\n    @Config(uriParam = CommonContext.TERM_LENGTH_PARAM_NAME)\n    public static final String TERM_BUFFER_LENGTH_PROP_NAME = \"aeron.term.buffer.length\";\n\n    /**\n     * Default term buffer length.\n     */\n    @Config(configType = Config.Type.DEFAULT)\n    public static final int TERM_BUFFER_LENGTH_DEFAULT = 16 * 1024 * 1024;\n\n    /**\n     * Length (in bytes) of the log buffers for IPC publication terms.\n     */\n    @Config(uriParam = CommonContext.TERM_LENGTH_PARAM_NAME)\n    public static final String IPC_TERM_BUFFER_LENGTH_PROP_NAME = \"aeron.ipc.term.buffer.length\";\n\n    /**\n     * Default IPC term buffer length.\n     */\n    @Config(id = \"IPC_TERM_BUFFER_LENGTH\", configType = Config.Type.DEFAULT)\n    public static final int TERM_BUFFER_IPC_LENGTH_DEFAULT = 64 * 1024 * 1024;\n\n    /**\n     * Property name low file storage warning threshold in bytes.\n     */\n    @Config\n    public static final String LOW_FILE_STORE_WARNING_THRESHOLD_PROP_NAME = \"aeron.low.file.store.warning.threshold\";\n\n    /**\n     * Default value in bytes for low file storage warning threshold.\n     */\n    @Config\n    public static final long LOW_FILE_STORE_WARNING_THRESHOLD_DEFAULT = TERM_BUFFER_LENGTH_DEFAULT * 10L;\n\n    /**\n     * Length (in bytes) of the conductor buffer for control commands from the clients to the media driver conductor.\n     */\n    @Config(expectedCEnvVarFieldName = \"AERON_TO_CONDUCTOR_BUFFER_LENGTH_ENV_VAR\")\n    public static final String CONDUCTOR_BUFFER_LENGTH_PROP_NAME = \"aeron.conductor.buffer.length\";\n\n    /**\n     * Default buffer length for conductor buffers between the client and the media driver conductor.\n     */\n    @Config(\n        expectedCDefaultFieldName = \"AERON_TO_CONDUCTOR_BUFFER_LENGTH_DEFAULT\",\n        skipCDefaultValidation = true,\n        defaultType = DefaultType.INT,\n        defaultInt = (1024 * 1024) + 768)\n    public static final int CONDUCTOR_BUFFER_LENGTH_DEFAULT = (1024 * 1024) + RingBufferDescriptor.TRAILER_LENGTH;\n\n    /**\n     * Length (in bytes) of the broadcast buffers from the media driver to the clients.\n     */\n    @Config(expectedCEnvVarFieldName = \"AERON_TO_CLIENTS_BUFFER_LENGTH_ENV_VAR\")\n    public static final String TO_CLIENTS_BUFFER_LENGTH_PROP_NAME = \"aeron.clients.buffer.length\";\n\n    /**\n     * Default buffer length for broadcast buffers from the media driver and the clients.\n     */\n    @Config(\n        expectedCDefaultFieldName = \"AERON_TO_CLIENTS_BUFFER_LENGTH_DEFAULT\",\n        skipCDefaultValidation = true,\n        defaultType = DefaultType.INT,\n        defaultInt = (1024 * 1024) + 128)\n    public static final int TO_CLIENTS_BUFFER_LENGTH_DEFAULT = (1024 * 1024) + BroadcastBufferDescriptor.TRAILER_LENGTH;\n\n    /**\n     * Property name for length of the buffer for the counters.\n     * <p>\n     * Each counter uses {@link org.agrona.concurrent.status.CountersReader#COUNTER_LENGTH} bytes.\n     */\n    @Config(expectedCEnvVarFieldName = \"AERON_COUNTERS_VALUES_BUFFER_LENGTH_ENV_VAR\")\n    public static final String COUNTERS_VALUES_BUFFER_LENGTH_PROP_NAME = \"aeron.counters.buffer.length\";\n\n    /**\n     * Default length of the buffer for the counters file.\n     */\n    @Config(expectedCDefaultFieldName = \"AERON_COUNTERS_VALUES_BUFFER_LENGTH_DEFAULT\")\n    public static final int COUNTERS_VALUES_BUFFER_LENGTH_DEFAULT = 8 * 1024 * 1024;\n\n    /**\n     * Minimum counters buffer length.\n     */\n    public static final int COUNTERS_VALUES_BUFFER_LENGTH_MIN = 1024 * 1024;\n\n    /**\n     * Maximum counters buffer length.\n     */\n    public static final int COUNTERS_VALUES_BUFFER_LENGTH_MAX = 500 * 1024 * 1024;\n\n    /**\n     * Property name for length of the memory mapped buffer for the distinct error log.\n     */\n    @Config\n    public static final String ERROR_BUFFER_LENGTH_PROP_NAME = \"aeron.error.buffer.length\";\n\n    /**\n     * Default buffer length for the error buffer for the media driver.\n     */\n    @Config\n    public static final int ERROR_BUFFER_LENGTH_DEFAULT = 4 * 1024 * 1024;\n\n    /**\n     * Property name for length of the memory mapped buffer for the {@link io.aeron.driver.reports.LossReport}.\n     */\n    @Config\n    public static final String LOSS_REPORT_BUFFER_LENGTH_PROP_NAME = \"aeron.loss.report.buffer.length\";\n\n    /**\n     * Default buffer length for the {@link io.aeron.driver.reports.LossReport}.\n     */\n    @Config\n    public static final int LOSS_REPORT_BUFFER_LENGTH_DEFAULT = 1024 * 1024;\n\n    /**\n     * Property name for length of the initial window which must be sufficient for Bandwidth Delay Product (BDP).\n     */\n    @Config(uriParam = CommonContext.RECEIVER_WINDOW_LENGTH_PARAM_NAME)\n    public static final String INITIAL_WINDOW_LENGTH_PROP_NAME = \"aeron.rcv.initial.window.length\";\n\n    /**\n     * Default initial window length for flow control sender to receiver purposes. This assumes a system free of pauses.\n     * <p>\n     * Length of Initial Window:\n     * <p>\n     * RTT (LAN) = 100 usec\n     * Throughput = 10 Gbps\n     * <p>\n     * Buffer = Throughput * RTT\n     * Buffer = (10 * 1000 * 1000 * 1000 / 8) * 0.0001 = 125000\n     * Round to 128 KB\n     */\n    @Config(configType = Config.Type.DEFAULT)\n    public static final int INITIAL_WINDOW_LENGTH_DEFAULT = 128 * 1024;\n\n    /**\n     * Status message timeout in nanoseconds after which one will be sent when data flow has not triggered one.\n     */\n    @Config\n    public static final String STATUS_MESSAGE_TIMEOUT_PROP_NAME = \"aeron.rcv.status.message.timeout\";\n\n    /**\n     * Max timeout between Status messages (SM)s.\n     */\n    @Config(\n        defaultType = DefaultType.LONG,\n        defaultLong = 200 * 1000 * 1000,\n        expectedCDefaultFieldName = \"AERON_RCV_STATUS_MESSAGE_TIMEOUT_NS_DEFAULT\")\n    public static final long STATUS_MESSAGE_TIMEOUT_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(200);\n\n    /**\n     * Property name for ratio of sending data to polling status messages in the {@link Sender}.\n     */\n    @Config\n    public static final String SEND_TO_STATUS_POLL_RATIO_PROP_NAME = \"aeron.send.to.status.poll.ratio\";\n\n    /**\n     * The ratio for sending data to polling status messages in the Sender. This may be reduced for smaller windows.\n     */\n    @Config\n    public static final int SEND_TO_STATUS_POLL_RATIO_DEFAULT = 6;\n\n    /**\n     * Property name for the limit of the number of driver managed resources that can be freed in a single duty cycle.\n     */\n    @Config\n    public static final String RESOURCE_FREE_LIMIT_PROP_NAME = \"aeron.driver.resource.free.limit\";\n\n    /**\n     * Default value for the limit of the number of driver managed resources that can be freed in a single duty cycle.\n     */\n    @Config\n    public static final int RESOURCE_FREE_LIMIT_DEFAULT = 10;\n\n    /**\n     * Property name for SO_RCVBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP).\n     */\n    @Config(uriParam = CommonContext.SOCKET_RCVBUF_PARAM_NAME)\n    public static final String SOCKET_RCVBUF_LENGTH_PROP_NAME = \"aeron.socket.so_rcvbuf\";\n\n    /**\n     * Default SO_RCVBUF length.\n     */\n    @Config(configType = Config.Type.DEFAULT)\n    public static final int SOCKET_RCVBUF_LENGTH_DEFAULT = 128 * 1024;\n\n    /**\n     * Property name for SO_SNDBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP).\n     */\n    @Config(uriParam = CommonContext.SOCKET_SNDBUF_PARAM_NAME)\n    public static final String SOCKET_SNDBUF_LENGTH_PROP_NAME = \"aeron.socket.so_sndbuf\";\n\n    /**\n     * Default SO_SNDBUF length.\n     */\n    @Config(configType = Config.Type.DEFAULT)\n    public static final int SOCKET_SNDBUF_LENGTH_DEFAULT = 0;\n\n    /**\n     * Property name for IP_MULTICAST_TTL setting on UDP sockets.\n     */\n    @Config(uriParam = CommonContext.TTL_PARAM_NAME)\n    public static final String SOCKET_MULTICAST_TTL_PROP_NAME = \"aeron.socket.multicast.ttl\";\n\n    /**\n     * Multicast TTL value, 0 means use OS default.\n     */\n    @Config(configType = Config.Type.DEFAULT)\n    public static final int SOCKET_MULTICAST_TTL_DEFAULT = 0;\n\n    /**\n     * Property name for linger timeout after draining on {@link Publication}s so they can respond to NAKs.\n     */\n    @Config(uriParam = CommonContext.LINGER_PARAM_NAME)\n    public static final String PUBLICATION_LINGER_PROP_NAME = \"aeron.publication.linger.timeout\";\n\n    /**\n     * Default time for {@link Publication}s to linger after draining and before cleanup in nanoseconds.\n     */\n    @Config(\n        configType = Config.Type.DEFAULT,\n        expectedCDefaultFieldName = \"AERON_PUBLICATION_LINGER_TIMEOUT_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = 5L * 1000 * 1000 * 1000)\n    public static final long PUBLICATION_LINGER_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5);\n\n    /**\n     * Property name for {@link Aeron} client liveness timeout after which it is considered not alive.\n     */\n    @Config\n    public static final String CLIENT_LIVENESS_TIMEOUT_PROP_NAME = \"aeron.client.liveness.timeout\";\n\n    /**\n     * Default timeout for client liveness timeout after which it is considered not alive.\n     */\n    @Config(\n        configType = Config.Type.DEFAULT,\n        expectedCDefaultFieldName = \"AERON_CLIENT_LIVENESS_TIMEOUT_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = 10L * 1000 * 1000 * 1000)\n    public static final long CLIENT_LIVENESS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10);\n\n    /**\n     * {@link Image} liveness timeout for how long it stays active without heartbeats or lingers around after being\n     * drained.\n     */\n    @Config\n    public static final String IMAGE_LIVENESS_TIMEOUT_PROP_NAME = \"aeron.image.liveness.timeout\";\n\n    /**\n     * Default timeout for {@link Image} liveness timeout.\n     */\n    @Config(\n        configType = Config.Type.DEFAULT,\n        expectedCDefaultFieldName = \"AERON_IMAGE_LIVENESS_TIMEOUT_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = 10L * 1000 * 1000 * 1000)\n    public static final long IMAGE_LIVENESS_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10);\n\n    /**\n     * Property name for window limit on {@link Publication} side by which the publisher can get ahead of consumers.\n     */\n    @Config(\n        uriParam = CommonContext.PUBLICATION_WINDOW_LENGTH_PARAM_NAME,\n        defaultType = DefaultType.INT,\n        defaultInt = 0)\n    public static final String PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME = \"aeron.publication.term.window.length\";\n\n    /**\n     * Property name for window limit for IPC publications.\n     */\n    @Config(\n        uriParam = CommonContext.PUBLICATION_WINDOW_LENGTH_PARAM_NAME,\n        defaultType = DefaultType.INT,\n        defaultInt = 0)\n    public static final String IPC_PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME =\n        \"aeron.ipc.publication.term.window.length\";\n\n    /**\n     * {@link Publication} unblock timeout due to client crash or untimely commit.\n     * <p>\n     * A publication can become blocked if the client crashes while publishing or if\n     * {@link io.aeron.Publication#tryClaim(int, BufferClaim)} is used without following up by calling\n     * {@link BufferClaim#commit()} or {@link BufferClaim#abort()}.\n     */\n    @Config\n    public static final String PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME = \"aeron.publication.unblock.timeout\";\n\n    /**\n     * Timeout for {@link Publication} unblock in nanoseconds.\n     */\n    @Config(\n        configType = Config.Type.DEFAULT,\n        expectedCDefaultFieldName = \"AERON_PUBLICATION_UNBLOCK_TIMEOUT_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = 15L * 1000 * 1000 * 1000)\n    public static final long PUBLICATION_UNBLOCK_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(15);\n\n    /**\n     * Property name for {@link Publication} timeout due to lack of status messages which indicate a connection.\n     */\n    @Config\n    public static final String PUBLICATION_CONNECTION_TIMEOUT_PROP_NAME = \"aeron.publication.connection.timeout\";\n\n    /**\n     * Timeout for {@link Publication} connection timeout in nanoseconds.\n     */\n    @Config(\n        configType = Config.Type.DEFAULT,\n        expectedCDefaultFieldName = \"AERON_PUBLICATION_CONNECTION_TIMEOUT_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = 5L * 1000 * 1000 * 1000)\n    public static final long PUBLICATION_CONNECTION_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5);\n\n    /**\n     * Property name for if spy subscriptions simulate a connection to a network publication.\n     * <p>\n     * If true then this will override the min group size of the min and tagged flow control strategies.\n     */\n    @Config(\n        uriParam = CommonContext.SPIES_SIMULATE_CONNECTION_PARAM_NAME,\n        defaultType = DefaultType.BOOLEAN,\n        defaultBoolean = false)\n    public static final String SPIES_SIMULATE_CONNECTION_PROP_NAME = \"aeron.spies.simulate.connection\";\n\n    /**\n     * Default idle strategy for agents.\n     */\n    private static final String DEFAULT_IDLE_STRATEGY = \"org.agrona.concurrent.BackoffIdleStrategy\";\n\n    /**\n     * Spin on no activity before backing off to yielding.\n     */\n    public static final long IDLE_MAX_SPINS = 10;\n\n    /**\n     * Yield the thread so others can run before backing off to parking.\n     */\n    public static final long IDLE_MAX_YIELDS = 20;\n\n    /**\n     * Park for the minimum period of time which is typically 50-55 microseconds on 64-bit non-virtualised Linux.\n     * You will typically get 50-55 microseconds plus the number of nanoseconds requested if a core is available.\n     * On Windows expect to wait for at least 16ms or 1ms if the high-res timers are enabled.\n     */\n    public static final long IDLE_MIN_PARK_NS = 1000;\n\n    /**\n     * Maximum back-off park time which doubles on each interval stepping up from the min park idle.\n     */\n    public static final long IDLE_MAX_PARK_NS = TimeUnit.MILLISECONDS.toNanos(1);\n\n    /**\n     * {@link IdleStrategy} to be used when mode can be controlled via a counter.\n     */\n    public static final String CONTROLLABLE_IDLE_STRATEGY = \"org.agrona.concurrent.ControllableIdleStrategy\";\n\n    /**\n     * Property name for {@link IdleStrategy} to be employed by {@link Sender} for {@link ThreadingMode#DEDICATED}.\n     */\n    @Config(skipCDefaultValidation = true)\n    public static final String SENDER_IDLE_STRATEGY_PROP_NAME = \"aeron.sender.idle.strategy\";\n\n    /**\n     * Default idle strategy for the sender thread.\n     */\n    @Config\n    public static final String SENDER_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY;\n\n    /**\n     * Property name for {@link IdleStrategy} to be employed by {@link Receiver} for {@link ThreadingMode#DEDICATED}.\n     */\n    @Config(skipCDefaultValidation = true)\n    public static final String RECEIVER_IDLE_STRATEGY_PROP_NAME = \"aeron.receiver.idle.strategy\";\n\n    /**\n     * Default idle strategy for the receiver thread.\n     */\n    @Config\n    public static final String RECEIVER_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY;\n\n    /**\n     * Property name for {@link IdleStrategy} to be employed by {@link DriverConductor} for\n     * {@link ThreadingMode#DEDICATED} and {@link ThreadingMode#SHARED_NETWORK}.\n     */\n    @Config(skipCDefaultValidation = true)\n    public static final String CONDUCTOR_IDLE_STRATEGY_PROP_NAME = \"aeron.conductor.idle.strategy\";\n\n    /**\n     * Default idle strategy for the conductor thread.\n     */\n    @Config\n    public static final String CONDUCTOR_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY;\n\n    /**\n     * Property name for {@link IdleStrategy} to be employed by {@link Sender} and {@link Receiver} for\n     * {@link ThreadingMode#SHARED_NETWORK}.\n     */\n    @Config(skipCDefaultValidation = true)\n    public static final String SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME = \"aeron.sharednetwork.idle.strategy\";\n\n    /**\n     * Default idle strategy for the shared network thread.\n     */\n    @Config\n    public static final String SHARED_NETWORK_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY;\n\n    /**\n     * Property name for {@link IdleStrategy} to be employed by {@link Sender}, {@link Receiver},\n     * and {@link DriverConductor} for {@link ThreadingMode#SHARED}.\n     */\n    @Config(skipCDefaultValidation = true)\n    public static final String SHARED_IDLE_STRATEGY_PROP_NAME = \"aeron.shared.idle.strategy\";\n\n    /**\n     * Default idle strategy for the shared thread.\n     */\n    @Config\n    public static final String SHARED_IDLE_STRATEGY_DEFAULT = DEFAULT_IDLE_STRATEGY;\n\n    /**\n     * Property name for {@link FlowControl} to be employed for unicast channels.\n     */\n    @Config(existsInC = false, hasContext = false)\n    public static final String UNICAST_FLOW_CONTROL_STRATEGY_PROP_NAME = \"aeron.unicast.flow.control.strategy\";\n\n    /**\n     * Default flow control strategy for unicast.\n     */\n    @Config\n    public static final String UNICAST_FLOW_CONTROL_STRATEGY_DEFAULT = \"io.aeron.driver.UnicastFlowControl\";\n\n    /**\n     * {@link FlowControl} to be employed for unicast channels.\n     */\n    public static final String UNICAST_FLOW_CONTROL_STRATEGY = getProperty(\n        UNICAST_FLOW_CONTROL_STRATEGY_PROP_NAME, UNICAST_FLOW_CONTROL_STRATEGY_DEFAULT);\n\n    /**\n     * Property name for {@link FlowControl} to be employed for multicast channels.\n     */\n    @Config(existsInC = false, hasContext = false)\n    public static final String MULTICAST_FLOW_CONTROL_STRATEGY_PROP_NAME = \"aeron.multicast.flow.control.strategy\";\n\n    /**\n     *  Default flow control strategy for multicast.\n     */\n    @Config\n    public static final String MULTICAST_FLOW_CONTROL_STRATEGY_DEFAULT = \"io.aeron.driver.MaxMulticastFlowControl\";\n\n    /**\n     * {@link FlowControl} to be employed for multicast channels.\n     */\n    public static final String MULTICAST_FLOW_CONTROL_STRATEGY = getProperty(\n        MULTICAST_FLOW_CONTROL_STRATEGY_PROP_NAME, MULTICAST_FLOW_CONTROL_STRATEGY_DEFAULT);\n\n    /**\n     * Property name for {@link FlowControlSupplier} to be employed for unicast channels.\n     */\n    @Config(\n        expectedCDefault = \"aeron_unicast_flow_control_strategy_supplier\",\n        defaultType = DefaultType.STRING,\n        defaultString = \"io.aeron.driver.DefaultUnicastFlowControlSupplier\")\n    public static final String UNICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME = \"aeron.unicast.FlowControl.supplier\";\n\n    /**\n     * Property name for {@link FlowControlSupplier} to be employed for unicast channels.\n     */\n    @Config(\n        expectedCDefault = \"aeron_max_multicast_flow_control_strategy_supplier\",\n        defaultType = DefaultType.STRING,\n        defaultString = \"io.aeron.driver.DefaultMulticastFlowControlSupplier\")\n    public static final String MULTICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME =\n        \"aeron.multicast.FlowControl.supplier\";\n\n    /**\n     * Maximum UDP datagram payload size for IPv4. Jumbo datagrams from IPv6 are not supported.\n     * <p>\n     * Max length is 65,507 bytes as 65,535 minus 8 byte UDP header then minus 20 byte IP header.\n     * Then round down to the nearest multiple of {@link FrameDescriptor#FRAME_ALIGNMENT} giving 65,504.\n     */\n    public static final int MAX_UDP_PAYLOAD_LENGTH = 65504;\n\n    /**\n     * Length of the maximum transmission unit of the media driver's protocol. If this is greater\n     * than the network MTU for UDP then the packet will be fragmented and can amplify the impact of loss.\n     */\n    @Config(uriParam = CommonContext.MTU_LENGTH_PARAM_NAME)\n    public static final String MTU_LENGTH_PROP_NAME = \"aeron.mtu.length\";\n\n    /**\n     * The default is conservative to avoid fragmentation on IPv4 or IPv6 over Ethernet with PPPoE header,\n     * or for clouds such as Google, Azure, and AWS.\n     * <p>\n     * On networks that suffer little congestion then a larger value can be used to reduce syscall costs.\n     */\n    @Config(configType = Config.Type.DEFAULT)\n    public static final int MTU_LENGTH_DEFAULT = 1408;\n\n    /**\n     * Length of the maximum transmission unit of the media driver's protocol for IPC. This can be larger than the\n     * UDP version but if recorded replay needs to be considered.\n     */\n    @Config(uriParam = CommonContext.MTU_LENGTH_PARAM_NAME)\n    public static final String IPC_MTU_LENGTH_PROP_NAME = \"aeron.ipc.mtu.length\";\n\n    /**\n     * Default mtu length for IPC publications.\n     */\n    @Config(configType = Config.Type.DEFAULT)\n    public static final int IPC_MTU_LENGTH_DEFAULT = MTU_LENGTH_DEFAULT;\n\n    /**\n     * {@link ThreadingMode} to be used by the Aeron {@link MediaDriver}.\n     */\n    @Config(\n        expectedCDefault = \"AERON_THREADING_MODE_DEDICATED\",\n        defaultType = DefaultType.STRING,\n        defaultString = \"DEDICATED\")\n    public static final String THREADING_MODE_PROP_NAME = \"aeron.threading.mode\";\n\n    /**\n     * Interval between checks for timers and timeouts.\n     */\n    @Config\n    public static final String TIMER_INTERVAL_PROP_NAME = \"aeron.timer.interval\";\n\n    /**\n     * Default interval between checks for timers and timeouts.\n     */\n    @Config(\n        id = \"TIMER_INTERVAL\",\n        expectedCDefaultFieldName = \"AERON_TIMER_INTERVAL_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = 1000 * 1000 * 1000)\n    public static final long DEFAULT_TIMER_INTERVAL_NS = TimeUnit.SECONDS.toNanos(1);\n\n    /**\n     * Timeout between a counter being freed and being available to be reused.\n     */\n    @Config\n    public static final String COUNTER_FREE_TO_REUSE_TIMEOUT_PROP_NAME = \"aeron.counters.free.to.reuse.timeout\";\n\n    /**\n     * Default timeout between a counter being freed and being available to be reused.\n     */\n    @Config(\n        id = \"COUNTER_FREE_TO_REUSE_TIMEOUT\",\n        expectedCDefaultFieldName = \"AERON_COUNTERS_FREE_TO_REUSE_TIMEOUT_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = 1000 * 1000 * 1000)\n    public static final long DEFAULT_COUNTER_FREE_TO_REUSE_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(1);\n\n    /**\n     * Property name for {@link SendChannelEndpointSupplier}.\n     */\n    @Config(\n        existsInC = false,\n        defaultType = DefaultType.STRING,\n        defaultString = \"io.aeron.driver.DefaultSendChannelEndpointSupplier\")\n    public static final String SEND_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME = \"aeron.SendChannelEndpoint.supplier\";\n\n    /**\n     * Property name for {@link ReceiveChannelEndpointSupplier}.\n     */\n    @Config(\n        existsInC = false,\n        defaultType = DefaultType.STRING,\n        defaultString = \"io.aeron.driver.DefaultReceiveChannelEndpointSupplier\")\n    public static final String RECEIVE_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME = \"aeron.ReceiveChannelEndpoint.supplier\";\n\n    /**\n     * Property name for Application Specific Feedback added to Status Messages by the driver for flow control.\n     * <p>\n     * Replaced by {@link #RECEIVER_GROUP_TAG_PROP_NAME}.\n     */\n    @Deprecated\n    @Config(defaultType = DefaultType.STRING, defaultString = \"\", existsInC = false)\n    public static final String SM_APPLICATION_SPECIFIC_FEEDBACK_PROP_NAME =\n        \"aeron.flow.control.sm.applicationSpecificFeedback\";\n\n    /**\n     * Property name for {@link CongestionControlSupplier} to be employed for receivers.\n     */\n    @Config(\n        expectedCDefault = \"aeron_congestion_control_default_strategy_supplier\",\n        defaultType = DefaultType.STRING,\n        defaultString = \"io.aeron.driver.DefaultCongestionControlSupplier\")\n    public static final String CONGESTION_CONTROL_STRATEGY_SUPPLIER_PROP_NAME = \"aeron.CongestionControl.supplier\";\n\n    /**\n     * Property name for low end of the publication reserved session-id range which will not be automatically assigned.\n     */\n    @Config\n    public static final String PUBLICATION_RESERVED_SESSION_ID_LOW_PROP_NAME =\n        \"aeron.publication.reserved.session.id.low\";\n\n    /**\n     * Low-end of the publication reserved session-id range which will not be automatically assigned.\n     */\n    @Config\n    public static final int PUBLICATION_RESERVED_SESSION_ID_LOW_DEFAULT = -1;\n\n    /**\n     * High-end of the publication reserved session-id range which will not be automatically assigned.\n     */\n    @Config\n    public static final String PUBLICATION_RESERVED_SESSION_ID_HIGH_PROP_NAME =\n        \"aeron.publication.reserved.session.id.high\";\n\n    /**\n     * High-end of the publication reserved session-id range which will not be automatically assigned.\n     */\n    @Config\n    public static final int PUBLICATION_RESERVED_SESSION_ID_HIGH_DEFAULT = 1000;\n\n    /**\n     * Limit for the number of commands drained in one operation.\n     */\n    public static final int COMMAND_DRAIN_LIMIT = 1;\n\n    /**\n     * Capacity for the command queues used between driver agents.\n     */\n    public static final int CMD_QUEUE_CAPACITY = 1024;\n\n    /**\n     * Timeout on cleaning up pending SETUP message state on subscriber.\n     */\n    public static final long PENDING_SETUPS_TIMEOUT_NS = TimeUnit.MILLISECONDS.toNanos(1000);\n\n    /**\n     * Timeout between SETUP messages for publications during initial setup phase.\n     */\n    public static final long PUBLICATION_SETUP_TIMEOUT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n    /**\n     * Timeout between heartbeats for publications.\n     */\n    public static final long PUBLICATION_HEARTBEAT_TIMEOUT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n    /**\n     * Expected size of multicast receiver groups property name.\n     */\n    @Config\n    public static final String NAK_MULTICAST_GROUP_SIZE_PROP_NAME = \"aeron.nak.multicast.group.size\";\n\n    /**\n     * Default multicast receiver group size estimate for NAK delay randomisation.\n     */\n    @Config\n    public static final int NAK_MULTICAST_GROUP_SIZE_DEFAULT = 10;\n\n    /**\n     * Max backoff time for multicast NAK delay randomisation in nanoseconds.\n     */\n    @Config\n    public static final String NAK_MULTICAST_MAX_BACKOFF_PROP_NAME = \"aeron.nak.multicast.max.backoff\";\n\n    /**\n     * Default max backoff for NAK delay randomisation in nanoseconds.\n     */\n    @Config(\n        id = \"NAK_MULTICAST_MAX_BACKOFF\",\n        expectedCDefaultFieldName = \"AERON_NAK_MULTICAST_MAX_BACKOFF_NS_DEFAULT\",\n        defaultType = DefaultType.LONG,\n        defaultLong = 10L * 1000 * 1000)\n    public static final long NAK_MAX_BACKOFF_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(10);\n\n    /**\n     * Unicast NAK delay in nanoseconds property name.\n     */\n    @Config(uriParam = CommonContext.NAK_DELAY_PARAM_NAME)\n    public static final String NAK_UNICAST_DELAY_PROP_NAME = \"aeron.nak.unicast.delay\";\n\n    /**\n     * Minimum supported value for {@link #NAK_UNICAST_DELAY_PROP_NAME} which is {@code 1us} (one microsecond).\n     */\n    public static final long NAK_UNICAST_DELAY_MIN_VALUE_NS = 1000L;\n\n    /**\n     * Default Unicast NAK delay in nanoseconds.\n     */\n    @Config(\n        expectedCDefaultFieldName = \"AERON_NAK_UNICAST_DELAY_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultLong = NAK_UNICAST_DELAY_MIN_VALUE_NS)\n    public static final long NAK_UNICAST_DELAY_DEFAULT_NS = NAK_UNICAST_DELAY_MIN_VALUE_NS;\n\n    /**\n     * Unicast NAK retry delay ratio property name.\n     */\n    @Config\n    public static final String NAK_UNICAST_RETRY_DELAY_RATIO_PROP_NAME = \"aeron.nak.unicast.retry.delay.ratio\";\n\n    /**\n     * Default Unicast NAK retry delay ratio.\n     */\n    @Config\n    public static final long NAK_UNICAST_RETRY_DELAY_RATIO_DEFAULT = 100;\n\n    /**\n     * Property for setting how long to delay before sending a retransmit after receiving a NAK.\n     */\n    @Config\n    public static final String RETRANSMIT_UNICAST_DELAY_PROP_NAME = \"aeron.retransmit.unicast.delay\";\n\n    /**\n     * Default delay before retransmission of data for unicast in nanoseconds.\n     */\n    @Config(\n        expectedCDefaultFieldName = \"AERON_RETRANSMIT_UNICAST_DELAY_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = 0)\n    public static final long RETRANSMIT_UNICAST_DELAY_DEFAULT_NS = TimeUnit.NANOSECONDS.toNanos(0);\n\n    /**\n     * Property for setting how long to linger after delay on a NAK before responding to another NAK.\n     */\n    @Config\n    public static final String RETRANSMIT_UNICAST_LINGER_PROP_NAME = \"aeron.retransmit.unicast.linger\";\n\n    /**\n     * Default delay for linger for unicast in nanoseconds.\n     */\n    @Config(\n        expectedCDefaultFieldName = \"AERON_RETRANSMIT_UNICAST_LINGER_NS_DEFAULT\",\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = 10L * 1000 * 1000)\n    public static final long RETRANSMIT_UNICAST_LINGER_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(10);\n\n    /**\n     * Property name of the timeout for when an untethered subscription that is outside the window limit will\n     * participate in local flow control.\n     */\n    @Config(\n        uriParam = CommonContext.UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME,\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS)\n    public static final String UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME = \"aeron.untethered.window.limit.timeout\";\n\n    /**\n     * Default timeout for when an untethered subscription that is outside the window limit will participate in\n     * local flow control.\n     */\n    @Config(\n        defaultType = DefaultType.LONG,\n        defaultLong = 5_000_000_000L,\n        expectedCDefaultFieldName = \"AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS_DEFAULT\")\n    public static final long UNTETHERED_WINDOW_LIMIT_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5);\n\n    /**\n     * Property name of the timeout for when an untethered subscription is resting after not being able to keep up\n     * before it is allowed to rejoin a stream.\n     */\n    @Config(\n        uriParam = CommonContext.UNTETHERED_RESTING_TIMEOUT_PARAM_NAME,\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS)\n    public static final String UNTETHERED_RESTING_TIMEOUT_PROP_NAME = \"aeron.untethered.resting.timeout\";\n\n    /**\n     * Default timeout for when an untethered subscription is resting after not being able to keep up\n     * before it is allowed to rejoin a stream.\n     */\n    @Config(\n        defaultType = DefaultType.LONG,\n        defaultLong = 10_000_000_000L,\n        expectedCDefaultFieldName = \"AERON_UNTETHERED_RESTING_TIMEOUT_NS_DEFAULT\")\n    public static final long UNTETHERED_RESTING_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(10);\n\n    /**\n     * Property name of the timeout for an untethered subscription to stay in the linger state.\n     */\n    @Config(\n        uriParam = CommonContext.UNTETHERED_LINGER_TIMEOUT_PARAM_NAME,\n        isTimeValue = Config.IsTimeValue.TRUE,\n        timeUnit = TimeUnit.NANOSECONDS,\n        defaultType = DefaultType.LONG,\n        defaultLong = Aeron.NULL_VALUE)\n    public static final String UNTETHERED_LINGER_TIMEOUT_PROP_NAME = \"aeron.untethered.linger.timeout\";\n\n    /**\n     * Property name of the max number of active retransmissions tracked for udp streams with group semantics.\n     */\n    @Config\n    public static final String MAX_RESEND_PROP_NAME = \"aeron.max.resend\";\n\n    /**\n     * Default max number of active retransmissions per connected stream udp stream with group semantics.\n     */\n    @Config\n    public static final int MAX_RESEND_DEFAULT = 16;\n\n    /**\n     * Maximum value for the active retransmissions per connected stream udp stream with group semantics.\n     */\n    public static final int MAX_RESEND_MAX = 256;\n\n    /**\n     * Property name for the class used to validate if a driver should terminate based on token.\n     */\n    @Config(\n        defaultType = DefaultType.STRING,\n        defaultString = \"io.aeron.driver.DefaultDenyTerminationValidator\",\n        expectedCDefault = \"aeron_driver_termination_validator_default_deny\",\n        skipCDefaultValidation = true)\n    public static final String TERMINATION_VALIDATOR_PROP_NAME = \"aeron.driver.termination.validator\";\n\n    /**\n     * Property name for default boolean value for if a stream can be rejoined. True to allow stream rejoin.\n     */\n    @Config(uriParam = CommonContext.REJOIN_PARAM_NAME, defaultType = DefaultType.BOOLEAN, defaultBoolean = true)\n    public static final String REJOIN_STREAM_PROP_NAME = \"aeron.rejoin.stream\";\n\n    /**\n     * Property name for default group tag (gtag) to send in all Status Messages.\n     */\n    @Config(\n        uriParam = CommonContext.GROUP_TAG_PARAM_NAME,\n        defaultType = DefaultType.LONG,\n        defaultLong = 0,\n        expectedCDefaultFieldName = \"AERON_RECEIVER_GROUP_TAG_VALUE_DEFAULT\",\n        expectedCDefault = \"-1\")\n    public static final String RECEIVER_GROUP_TAG_PROP_NAME = \"aeron.receiver.group.tag\";\n\n    /**\n     * Property name for default group tag (gtag) used by the tagged flow control strategy to group receivers.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = -1)\n    public static final String FLOW_CONTROL_GROUP_TAG_PROP_NAME = \"aeron.flow.control.group.tag\";\n\n    /**\n     * Property name for default minimum group size used by flow control strategies to determine connectivity.\n     */\n    @Config(defaultType = DefaultType.INT, defaultInt = 0)\n    public static final String FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME = \"aeron.flow.control.group.min.size\";\n\n    /**\n     * Property name for flow control timeout after which with no status messages the receiver is considered gone.\n     */\n    @Config(expectedCDefaultFieldName = \"AERON_FLOW_CONTROL_RECEIVER_TIMEOUT_NS_DEFAULT\")\n    public static final String FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME = \"aeron.flow.control.receiver.timeout\";\n\n    /**\n     * Default value for the receiver timeout used to determine if the receiver should still be monitored for\n     * flow control purposes.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = 5_000_000_000L)\n    public static final long FLOW_CONTROL_RECEIVER_TIMEOUT_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5);\n\n    /**\n     * Property name for default retransmit receiver window multiple used by the unicast flow control strategy.\n     */\n    @Config(defaultType = DefaultType.INT, defaultInt = 16)\n    public static final String UNICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE_PROP_NAME =\n        \"aeron.unicast.flow.control.rrwm\";\n\n    /**\n     * Property name for default retransmit receiver window multiple used by multicast flow control strategies.\n     */\n    @Config(defaultType = DefaultType.INT, defaultInt = 4)\n    public static final String MULTICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE_PROP_NAME =\n        \"aeron.multicast.flow.control.rrwm\";\n\n    /**\n     * Property name for resolver name of the Media Driver used in name resolution.\n     */\n    @Config(defaultType = DefaultType.STRING, defaultString = \"\", skipCDefaultValidation = true)\n    public static final String RESOLVER_NAME_PROP_NAME = \"aeron.driver.resolver.name\";\n\n    /**\n     * Property name for resolver interface to which network connections are made.\n     *\n     * @see #RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME\n     */\n    @Config(defaultType = DefaultType.STRING, defaultString = \"\", skipCDefaultValidation = true)\n    public static final String RESOLVER_INTERFACE_PROP_NAME = \"aeron.driver.resolver.interface\";\n\n    /**\n     * Property name for resolver bootstrap neighbors for which it can bootstrap naming, format is comma separated list\n     * of {@code hostname:port} pairs.\n     *\n     * @see #RESOLVER_INTERFACE_PROP_NAME\n     */\n    @Config(defaultType = DefaultType.STRING, defaultString = \"\", skipCDefaultValidation = true)\n    public static final String RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME = \"aeron.driver.resolver.bootstrap.neighbor\";\n\n    /**\n     * Property name for re-resolution check interval for resolving names to IP address.\n     */\n    @Config\n    public static final String RE_RESOLUTION_CHECK_INTERVAL_PROP_NAME = \"aeron.driver.reresolution.check.interval\";\n\n    /**\n     * Default value for the re-resolution check interval.\n     */\n    @Config(\n        defaultType = DefaultType.LONG,\n        defaultLong = 1_000_000_000L,\n        expectedCDefaultFieldName = \"AERON_DRIVER_RERESOLUTION_CHECK_INTERVAL_NS_DEFAULT\")\n    public static final long RE_RESOLUTION_CHECK_INTERVAL_DEFAULT_NS = TimeUnit.SECONDS.toNanos(1);\n\n    /**\n     * Property name for threshold value for the conductor work cycle threshold to track for being exceeded.\n     */\n    @Config\n    public static final String CONDUCTOR_CYCLE_THRESHOLD_PROP_NAME = \"aeron.driver.conductor.cycle.threshold\";\n\n    /**\n     * Default threshold value for the conductor work cycle threshold to track for being exceeded.\n     */\n    @Config(\n        defaultType = DefaultType.LONG,\n        defaultLong = 100_000_000L,\n        expectedCDefaultFieldName = \"AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD_NS_DEFAULT\")\n    public static final long CONDUCTOR_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n    /**\n     * Property name for threshold value for the sender work cycle threshold to track for being exceeded.\n     */\n    @Config\n    public static final String SENDER_CYCLE_THRESHOLD_PROP_NAME = \"aeron.driver.sender.cycle.threshold\";\n\n    /**\n     * Default threshold value for the sender work cycle threshold to track for being exceeded.\n     */\n    @Config(\n        defaultType = DefaultType.LONG,\n        defaultLong = 100_000_000L,\n        expectedCDefaultFieldName = \"AERON_DRIVER_SENDER_CYCLE_THRESHOLD_NS_DEFAULT\")\n    public static final long SENDER_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n    /**\n     * Property name for threshold value for the receiver work cycle threshold to track for being exceeded.\n     */\n    @Config\n    public static final String RECEIVER_CYCLE_THRESHOLD_PROP_NAME = \"aeron.driver.receiver.cycle.threshold\";\n\n    /**\n     * Default threshold value for the receiver work cycle threshold to track for being exceeded.\n     */\n    @Config(\n        defaultType = DefaultType.LONG,\n        defaultLong = 100_000_000L,\n        expectedCDefaultFieldName = \"AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD_NS_DEFAULT\")\n    public static final long RECEIVER_CYCLE_THRESHOLD_DEFAULT_NS = TimeUnit.MILLISECONDS.toNanos(100);\n\n    /**\n     * Property name for threshold value for the name resolution threshold to track for being exceeded.\n     */\n    @Config(\n        expectedCEnvVarFieldName = \"AERON_DRIVER_NAME_RESOLVER_THRESHOLD_ENV_VAR\",\n        expectedCEnvVar = \"AERON_DRIVER_NAME_RESOLVER_THRESHOLD\",\n        expectedCDefaultFieldName = \"AERON_DRIVER_NAME_RESOLVER_THRESHOLD_NS_DEFAULT\")\n    public static final String NAME_RESOLVER_THRESHOLD_PROP_NAME = \"aeron.name.resolver.threshold\";\n\n    /**\n     * Default threshold value for the name resolution threshold to track for being exceeded.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = 5_000_000_000L)\n    public static final long NAME_RESOLVER_THRESHOLD_DEFAULT_NS = TimeUnit.SECONDS.toNanos(5);\n\n    /**\n     * Property name for wildcard port range for the Sender.\n     */\n    @Config(\n        defaultType = DefaultType.STRING,\n        defaultString = \"\",\n        expectedCEnvVarFieldName = \"AERON_DRIVER_SENDER_WILDCARD_PORT_RANGE_ENV_VAR\",\n        skipCDefaultValidation = true)\n    public static final String SENDER_WILDCARD_PORT_RANGE_PROP_NAME = \"aeron.sender.wildcard.port.range\";\n\n    /**\n     * Property name for wildcard port range for the Receiver.\n     */\n    @Config(\n        defaultType = DefaultType.STRING,\n        defaultString = \"\",\n        expectedCEnvVarFieldName = \"AERON_DRIVER_RECEIVER_WILDCARD_PORT_RANGE_ENV_VAR\",\n        skipCDefaultValidation = true)\n    public static final String RECEIVER_WILDCARD_PORT_RANGE_PROP_NAME = \"aeron.receiver.wildcard.port.range\";\n\n    /**\n     * Property name to configure the number of async executor threads. Defaults to {@code 1}. Negative value or zero\n     * means no asynchronous threads should be created, i.e. execution will be done on the conductor thread.\n     *\n     * @since 1.44.0\n     */\n    @Config(defaultType = DefaultType.INT, defaultInt = 1)\n    public static final String ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME = \"aeron.driver.async.executor.threads\";\n\n    /**\n     * Property name to set a limit on the number sessions allowed per stream on a subscription.\n     */\n    @Config(defaultType = DefaultType.INT, defaultInt = Integer.MAX_VALUE)\n    public static final String STREAM_SESSION_LIMIT_PROP_NAME = \"aeron.driver.stream.session.limit\";\n\n    /**\n     * Default number of sessions allowed per stream on a subscription. Default is to be effectively unlimited.\n     */\n    public static final int STREAM_SESSION_LIMIT_DEFAULT = Integer.MAX_VALUE;\n\n    /**\n     * {@link Executor} that run tasks on the caller thread.\n     */\n    public static final Executor CALLER_RUNS_TASK_EXECUTOR = Runnable::run;\n\n    /**\n     * Should the high-resolution timer be used when running on Windows.\n     *\n     * @return true if the high-resolution timer be used when running on Windows.\n     * @see #USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME\n     */\n    public static boolean useWindowsHighResTimer()\n    {\n        return \"true\".equals(getProperty(USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME));\n    }\n\n    /**\n     * Should a warning be printed if the aeron directory exist when starting.\n     *\n     * @return true if a warning be printed if the aeron directory exist when starting.\n     * @see #DIR_WARN_IF_EXISTS_PROP_NAME\n     */\n    public static boolean warnIfDirExists()\n    {\n        return \"true\".equals(getProperty(DIR_WARN_IF_EXISTS_PROP_NAME));\n    }\n\n    /**\n     * Should driver attempt to an immediate forced delete of {@link CommonContext#AERON_DIR_PROP_NAME} on start\n     * if it exists.\n     *\n     * @return true if the aeron directory be deleted on start without checking if active.\n     * @see #DIR_DELETE_ON_START_PROP_NAME\n     */\n    public static boolean dirDeleteOnStart()\n    {\n        return \"true\".equals(getProperty(DIR_DELETE_ON_START_PROP_NAME));\n    }\n\n    /**\n     * Should driver attempt to delete {@link CommonContext#AERON_DIR_PROP_NAME} on shutdown.\n     *\n     * @return true if driver should attempt to delete {@link CommonContext#AERON_DIR_PROP_NAME} on shutdown.\n     * @see #DIR_DELETE_ON_SHUTDOWN_PROP_NAME\n     */\n    public static boolean dirDeleteOnShutdown()\n    {\n        return \"true\".equals(getProperty(DIR_DELETE_ON_SHUTDOWN_PROP_NAME));\n    }\n\n    /**\n     * Should term buffers be created as sparse files. This can save space at the expense of latency when required.\n     *\n     * @return true if term buffers should be created as sparse files.\n     * @see #TERM_BUFFER_SPARSE_FILE_PROP_NAME\n     */\n    public static boolean termBufferSparseFile()\n    {\n        return \"true\".equals(getProperty(TERM_BUFFER_SPARSE_FILE_PROP_NAME, \"true\"));\n    }\n\n    /**\n     * Default for if subscriptions should be tethered.\n     *\n     * @return true if the default subscriptions should be tethered.\n     * @see #TETHER_SUBSCRIPTIONS_PROP_NAME\n     */\n    public static boolean tetherSubscriptions()\n    {\n        return \"true\".equals(getProperty(TETHER_SUBSCRIPTIONS_PROP_NAME, \"true\"));\n    }\n\n    /**\n     * Default boolean value for if a stream is reliable. True to NAK, false to gap fill.\n     *\n     * @return true if NAK is default or false to gap fill.\n     * @see #RELIABLE_STREAM_PROP_NAME\n     */\n    public static boolean reliableStream()\n    {\n        return \"true\".equals(getProperty(RELIABLE_STREAM_PROP_NAME, \"true\"));\n    }\n\n    /**\n     * Should storage checks should be performed before allocating files.\n     *\n     * @return true of storage checks should be performed before allocating files.\n     * @see #PERFORM_STORAGE_CHECKS_PROP_NAME\n     */\n    public static boolean performStorageChecks()\n    {\n        return \"true\".equals(getProperty(PERFORM_STORAGE_CHECKS_PROP_NAME, \"true\"));\n    }\n\n    /**\n     * Should spy subscriptions simulate a connection to a network publication.\n     * <p>\n     * If true then this will override the min group size of the min and tagged flow control strategies.\n     *\n     * @return true if spy subscriptions should simulate a connection to a network publication.\n     * @see #SPIES_SIMULATE_CONNECTION_PROP_NAME\n     */\n    public static boolean spiesSimulateConnection()\n    {\n        return \"true\".equals(getProperty(SPIES_SIMULATE_CONNECTION_PROP_NAME, \"false\"));\n    }\n\n    /**\n     * Should subscriptions should be considered a group member or individual connection, e.g. multicast vs unicast.\n     *\n     * @return FORCE_TRUE if subscriptions should be considered a group member or false if individual.\n     * @see #GROUP_RECEIVER_CONSIDERATION_PROP_NAME\n     */\n    public static CommonContext.InferableBoolean receiverGroupConsideration()\n    {\n        return CommonContext.InferableBoolean.parse(getProperty(GROUP_RECEIVER_CONSIDERATION_PROP_NAME));\n    }\n\n    /**\n     * Length (in bytes) of the conductor buffer for control commands from the clients to the media driver conductor.\n     *\n     * @return length (in bytes) of the conductor buffer for control commands from the clients to the media driver.\n     * @see #CONDUCTOR_BUFFER_LENGTH_PROP_NAME\n     */\n    public static int conductorBufferLength()\n    {\n        return getSizeAsInt(CONDUCTOR_BUFFER_LENGTH_PROP_NAME, CONDUCTOR_BUFFER_LENGTH_DEFAULT);\n    }\n\n    /**\n     * Length (in bytes) of the broadcast buffers from the media driver to the clients.\n     *\n     * @return length (in bytes) of the broadcast buffers from the media driver to the clients.\n     * @see #TO_CLIENTS_BUFFER_LENGTH_PROP_NAME\n     */\n    public static int toClientsBufferLength()\n    {\n        return getSizeAsInt(TO_CLIENTS_BUFFER_LENGTH_PROP_NAME, TO_CLIENTS_BUFFER_LENGTH_DEFAULT);\n    }\n\n    /**\n     * Length of the buffer for the counters.\n     * <p>\n     * Each counter uses {@link org.agrona.concurrent.status.CountersReader#COUNTER_LENGTH} bytes.\n     *\n     * @return Length of the buffer for the counters.\n     * @see #COUNTERS_VALUES_BUFFER_LENGTH_PROP_NAME\n     */\n    public static int counterValuesBufferLength()\n    {\n        return getSizeAsInt(COUNTERS_VALUES_BUFFER_LENGTH_PROP_NAME, COUNTERS_VALUES_BUFFER_LENGTH_DEFAULT);\n    }\n\n    /**\n     * Length of the memory mapped buffer for the distinct error log.\n     *\n     * @return length of the memory mapped buffer for the distinct error log.\n     */\n    public static int errorBufferLength()\n    {\n        return getSizeAsInt(ERROR_BUFFER_LENGTH_PROP_NAME, ERROR_BUFFER_LENGTH_DEFAULT);\n    }\n\n    /**\n     * Expected size of typical multicast receiver groups.\n     *\n     * @return expected size of typical multicast receiver groups.\n     * @see #NAK_MULTICAST_GROUP_SIZE_PROP_NAME\n     */\n    public static int nakMulticastGroupSize()\n    {\n        return getInteger(NAK_MULTICAST_GROUP_SIZE_PROP_NAME, NAK_MULTICAST_GROUP_SIZE_DEFAULT);\n    }\n\n    /**\n     * Max backoff time for multicast NAK delay randomisation in nanoseconds.\n     *\n     * @return max backoff time for multicast NAK delay randomisation in nanoseconds.\n     * @see #NAK_MULTICAST_MAX_BACKOFF_PROP_NAME\n     */\n    public static long nakMulticastMaxBackoffNs()\n    {\n        return getDurationInNanos(NAK_MULTICAST_MAX_BACKOFF_PROP_NAME, NAK_MAX_BACKOFF_DEFAULT_NS);\n    }\n\n    /**\n     * Unicast NAK delay in nanoseconds.\n     *\n     * @return unicast NAK delay in nanoseconds.\n     * @see #NAK_UNICAST_DELAY_PROP_NAME\n     */\n    public static long nakUnicastDelayNs()\n    {\n        return getDurationInNanos(NAK_UNICAST_DELAY_PROP_NAME, NAK_UNICAST_DELAY_DEFAULT_NS);\n    }\n\n    /**\n     * Unicast NAK retry delay ratio.\n     *\n     * @return unicast NAK delay in nanoseconds.\n     * @see #NAK_UNICAST_DELAY_PROP_NAME\n     */\n    public static long nakUnicastRetryDelayRatio()\n    {\n        return getSizeAsLong(NAK_UNICAST_RETRY_DELAY_RATIO_PROP_NAME, NAK_UNICAST_RETRY_DELAY_RATIO_DEFAULT);\n    }\n\n    /**\n     * Interval between checks for timers and timeouts.\n     *\n     * @return interval between checks for timers and timeouts.\n     * @see #TIMER_INTERVAL_PROP_NAME\n     */\n    public static long timerIntervalNs()\n    {\n        return getDurationInNanos(TIMER_INTERVAL_PROP_NAME, DEFAULT_TIMER_INTERVAL_NS);\n    }\n\n    /**\n     * Low file storage warning threshold in bytes for when performing storage checks.\n     *\n     * @return Low file storage warning threshold for when performing storage checks.\n     * @see #LOW_FILE_STORE_WARNING_THRESHOLD_PROP_NAME\n     * @see #PERFORM_STORAGE_CHECKS_PROP_NAME\n     */\n    public static long lowStorageWarningThreshold()\n    {\n        return getSizeAsLong(LOW_FILE_STORE_WARNING_THRESHOLD_PROP_NAME, LOW_FILE_STORE_WARNING_THRESHOLD_DEFAULT);\n    }\n\n    /**\n     * The window limit on UDP {@link Publication} side by which the publisher can get ahead of consumers.\n     *\n     * @return window limit on UDP {@link Publication} side by which the publisher can get ahead of consumers.\n     * @see #PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME\n     */\n    public static int publicationTermWindowLength()\n    {\n        return getSizeAsInt(PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME, 0);\n    }\n\n    /**\n     * The window limit on IPC {@link Publication} side by which the publisher can get ahead of consumers.\n     *\n     * @return window limit on IPC {@link Publication} side by which the publisher can get ahead of consumers.\n     * @see #IPC_PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME\n     */\n    public static int ipcPublicationTermWindowLength()\n    {\n        return getSizeAsInt(IPC_PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME, 0);\n    }\n\n    /**\n     * The timeout for when an untethered subscription that is outside the window limit will participate in local\n     * flow control.\n     *\n     * @return the timeout for when an untethered subscription that is outside the window limit will be included.\n     * @see #UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME\n     * @see #UNTETHERED_RESTING_TIMEOUT_PROP_NAME\n     * @see #TETHER_SUBSCRIPTIONS_PROP_NAME\n     */\n    public static long untetheredWindowLimitTimeoutNs()\n    {\n        return getDurationInNanos(\n            UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME, UNTETHERED_WINDOW_LIMIT_TIMEOUT_DEFAULT_NS);\n    }\n\n    /**\n     * The timeout for an untethered subscription to remain in the linger state.\n     *\n     * @return the timeout for an untethered subscription to remain in the linger state.\n     * @see #UNTETHERED_LINGER_TIMEOUT_PROP_NAME\n     * @since 1.48.0\n     */\n    public static long untetheredLingerTimeoutNs()\n    {\n        return getDurationInNanos(UNTETHERED_LINGER_TIMEOUT_PROP_NAME, Aeron.NULL_VALUE);\n    }\n\n    /**\n     * The timeout for when an untethered subscription is resting after not being able to keep up before it is allowed\n     * to rejoin a stream.\n     *\n     * @return The timeout for when an untethered subscription is resting before rejoining a stream.\n     * @see #UNTETHERED_RESTING_TIMEOUT_PROP_NAME\n     * @see #UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME\n     * @see #TETHER_SUBSCRIPTIONS_PROP_NAME\n     */\n    public static long untetheredRestingTimeoutNs()\n    {\n        return getDurationInNanos(UNTETHERED_RESTING_TIMEOUT_PROP_NAME, UNTETHERED_RESTING_TIMEOUT_DEFAULT_NS);\n    }\n\n    /**\n     * Max number of active retransmissions tracked for udp streams with group semantics.\n     *\n     * @return max retransmits\n     * @see #MAX_RESEND_PROP_NAME\n     */\n    public static int maxResend()\n    {\n        return Integer.min(\n            Integer.max(getInteger(MAX_RESEND_PROP_NAME, MAX_RESEND_DEFAULT), 1),\n            MAX_RESEND_MAX);\n    }\n\n    /**\n     * Default boolean value for if a stream can be rejoined. True to allow stream rejoin, false to not.\n     *\n     * @return boolean value for if a stream can be rejoined. True to allow stream rejoin, false to not.\n     * @see #REJOIN_STREAM_PROP_NAME\n     */\n    public static boolean rejoinStream()\n    {\n        return \"true\".equalsIgnoreCase(getProperty(REJOIN_STREAM_PROP_NAME, \"true\"));\n    }\n\n    /**\n     * Default group tag (gtag) to send in all Status Messages. If not provided then no gtag is sent.\n     *\n     * @return Default group tag (gtag) to send in all Status Messages.\n     * @see #RECEIVER_GROUP_TAG_PROP_NAME\n     */\n    public static Long groupTag()\n    {\n        return getLong(RECEIVER_GROUP_TAG_PROP_NAME, null);\n    }\n\n    /**\n     * Default group tag (gtag) used by the tagged flow control strategy to group receivers.\n     *\n     * @return group tag (gtag) used by the tagged flow control strategy to group receivers.\n     * @see #FLOW_CONTROL_GROUP_TAG_PROP_NAME\n     */\n    @SuppressWarnings(\"deprecation\")\n    public static long flowControlGroupTag()\n    {\n        final String propertyValue = getProperty(\n            PreferredMulticastFlowControl.PREFERRED_ASF_PROP_NAME, PreferredMulticastFlowControl.PREFERRED_ASF_DEFAULT);\n        final long legacyAsfValue = new UnsafeBuffer(BitUtil.fromHex(propertyValue)).getLong(0, LITTLE_ENDIAN);\n\n        return getLong(FLOW_CONTROL_GROUP_TAG_PROP_NAME, legacyAsfValue);\n    }\n\n    /**\n     * Default minimum group size used by flow control strategies to determine connectivity.\n     *\n     * @return default minimum group size used by flow control strategies to determine connectivity.\n     * @see #FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME\n     */\n    public static int flowControlGroupMinSize()\n    {\n        return getInteger(FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME, 0);\n    }\n\n    /**\n     * Flow control timeout after which with no status messages the receiver is considered gone.\n     *\n     * @return flow control timeout after which with no status messages the receiver is considered gone.\n     * @see #FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME\n     */\n    public static long flowControlReceiverTimeoutNs()\n    {\n        return getDurationInNanos(FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME, FLOW_CONTROL_RECEIVER_TIMEOUT_DEFAULT_NS);\n    }\n\n    /**\n     * Retransmit receiver window multiple used by the unicast flow control strategy to determine the maximum\n     * amount of data to retransmit.\n     *\n     * @return multiple.\n     */\n    public static int unicastFlowControlRetransmitReceiverWindowMultiple()\n    {\n        return getInteger(UNICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE_PROP_NAME, 16);\n    }\n\n    /**\n     * Retransmit receiver window multiple used by multicast flow control strategies to determine the maximum\n     * amount of data to retransmit.\n     *\n     * @return multiple.\n     */\n    public static int multicastFlowControlRetransmitReceiverWindowMultiple()\n    {\n        return getInteger(MULTICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE_PROP_NAME, 4);\n    }\n\n    /**\n     * Resolver name of the Media Driver used in name resolution.\n     *\n     * @return resolver name of the Media Driver used in name resolution.\n     * @see #RESOLVER_NAME_PROP_NAME\n     */\n    public static String resolverName()\n    {\n        return getProperty(RESOLVER_NAME_PROP_NAME);\n    }\n\n    /**\n     * Property name for resolver interface to which network connections are made, format is hostname:port.\n     *\n     * @return resolver interface to which network connections are made, format is hostname:port.\n     * @see #RESOLVER_INTERFACE_PROP_NAME\n     */\n    public static String resolverInterface()\n    {\n        return getProperty(RESOLVER_INTERFACE_PROP_NAME);\n    }\n\n    /**\n     * Resolver bootstrap neighbor for which it can bootstrap naming, format is hostname:port.\n     *\n     * @return resolver bootstrap neighbor for which it can bootstrap naming, format is hostname:port.\n     * @see #RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME\n     * @see #RESOLVER_INTERFACE_PROP_NAME\n     */\n    public static String resolverBootstrapNeighbor()\n    {\n        return getProperty(RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME);\n    }\n\n    /**\n     * Re-resolution check interval for resolving names to IP address when they may have changed.\n     *\n     * @return re-resolution check interval for resolving names to IP address when they may have changed.\n     * @see #RE_RESOLUTION_CHECK_INTERVAL_PROP_NAME\n     */\n    public static long reResolutionCheckIntervalNs()\n    {\n        return getDurationInNanos(RE_RESOLUTION_CHECK_INTERVAL_PROP_NAME, RE_RESOLUTION_CHECK_INTERVAL_DEFAULT_NS);\n    }\n\n    /**\n     * How far ahead a producer can get from a consumer position.\n     *\n     * @param termBufferLength        for when default is not set and considering an appropriate minimum.\n     * @param defaultTermWindowLength to take priority.\n     * @return the length to be used for the producer window.\n     */\n    public static int producerWindowLength(final int termBufferLength, final int defaultTermWindowLength)\n    {\n        int termWindowLength = termBufferLength >> 1;\n\n        if (0 != defaultTermWindowLength)\n        {\n            termWindowLength = Math.min(defaultTermWindowLength, termWindowLength);\n        }\n\n        return termWindowLength;\n    }\n\n    /**\n     * Length to be used for the receiver window taking into account initial window length and term buffer length.\n     *\n     * @param termBufferLength    for the publication image.\n     * @param initialWindowLength set for the channel.\n     * @return the length to be used for the receiver window.\n     */\n    public static int receiverWindowLength(final int termBufferLength, final int initialWindowLength)\n    {\n        return Math.min(initialWindowLength, termBufferLength >> 1);\n    }\n\n    /**\n     * Length (in bytes) of the log buffers for UDP publication terms.\n     *\n     * @return length (in bytes) of the log buffers for UDP publication terms.\n     * @see #TERM_BUFFER_LENGTH_PROP_NAME\n     */\n    public static int termBufferLength()\n    {\n        return getSizeAsInt(TERM_BUFFER_LENGTH_PROP_NAME, TERM_BUFFER_LENGTH_DEFAULT);\n    }\n\n    /**\n     * Length (in bytes) of the log buffers for IPC publication terms.\n     *\n     * @return length (in bytes) of the log buffers for IPC publication terms.\n     * @see #IPC_TERM_BUFFER_LENGTH_PROP_NAME\n     */\n    public static int ipcTermBufferLength()\n    {\n        return getSizeAsInt(IPC_TERM_BUFFER_LENGTH_PROP_NAME, TERM_BUFFER_IPC_LENGTH_DEFAULT);\n    }\n\n    /**\n     * Length of the initial window which must be sufficient for Bandwidth Delay Product (BDP).\n     *\n     * @return length of the initial window which must be sufficient for Bandwidth Delay Product (BDP).\n     * @see #INITIAL_WINDOW_LENGTH_PROP_NAME\n     */\n    public static int initialWindowLength()\n    {\n        return getSizeAsInt(INITIAL_WINDOW_LENGTH_PROP_NAME, INITIAL_WINDOW_LENGTH_DEFAULT);\n    }\n\n    /**\n     * SO_SNDBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP).\n     *\n     * @return SO_SNDBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP).\n     * @see #SOCKET_SNDBUF_LENGTH_PROP_NAME\n     */\n    public static int socketSndbufLength()\n    {\n        return getSizeAsInt(SOCKET_SNDBUF_LENGTH_PROP_NAME, SOCKET_SNDBUF_LENGTH_DEFAULT);\n    }\n\n    /**\n     * SO_RCVBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP).\n     *\n     * @return SO_RCVBUF setting on UDP sockets which must be sufficient for Bandwidth Delay Product (BDP).\n     * @see #SOCKET_RCVBUF_LENGTH_PROP_NAME\n     */\n    public static int socketRcvbufLength()\n    {\n        return getSizeAsInt(SOCKET_RCVBUF_LENGTH_PROP_NAME, SOCKET_RCVBUF_LENGTH_DEFAULT);\n    }\n\n    /**\n     * Length of the maximum transmission unit of the media driver's protocol. If this is greater\n     * than the network MTU for UDP then the packet will be fragmented and can amplify the impact of loss.\n     *\n     * @return length of the maximum transmission unit of the media driver's protocol.\n     * @see #MTU_LENGTH_PROP_NAME\n     */\n    public static int mtuLength()\n    {\n        return getSizeAsInt(MTU_LENGTH_PROP_NAME, MTU_LENGTH_DEFAULT);\n    }\n\n    /**\n     * Length of the maximum transmission unit of the media driver's protocol for IPC. This can be larger than the\n     * UDP version but if recorded replay needs to be considered.\n     *\n     * @return length of the maximum transmission unit of the media driver's protocol for IPC.\n     * @see #IPC_MTU_LENGTH_PROP_NAME\n     */\n    public static int ipcMtuLength()\n    {\n        return getSizeAsInt(IPC_MTU_LENGTH_PROP_NAME, IPC_MTU_LENGTH_DEFAULT);\n    }\n\n    /**\n     * IP_MULTICAST_TTL setting on UDP sockets.\n     *\n     * @return IP_MULTICAST_TTL setting on UDP sockets.\n     * @see #SOCKET_MULTICAST_TTL_PROP_NAME\n     */\n    public static int socketMulticastTtl()\n    {\n        return getInteger(SOCKET_MULTICAST_TTL_PROP_NAME, SOCKET_MULTICAST_TTL_DEFAULT);\n    }\n\n    /**\n     * Page size in bytes to align all files to. The file system must support the requested size.\n     *\n     * @return page size in bytes to align all files to.\n     * @see #FILE_PAGE_SIZE_PROP_NAME\n     */\n    public static int filePageSize()\n    {\n        return getSizeAsInt(FILE_PAGE_SIZE_PROP_NAME, FILE_PAGE_SIZE_DEFAULT);\n    }\n\n    /**\n     * Low-end of the publication reserved session-id range which will not be automatically assigned.\n     *\n     * @return low-end of the publication reserved session-id range which will not be automatically assigned.\n     * @see #PUBLICATION_RESERVED_SESSION_ID_LOW_PROP_NAME\n     */\n    public static int publicationReservedSessionIdLow()\n    {\n        return getInteger(PUBLICATION_RESERVED_SESSION_ID_LOW_PROP_NAME, PUBLICATION_RESERVED_SESSION_ID_LOW_DEFAULT);\n    }\n\n    /**\n     * High-end of the publication reserved session-id range which will not be automatically assigned.\n     *\n     * @return high-end of the publication reserved session-id range which will not be automatically assigned.\n     * @see #PUBLICATION_RESERVED_SESSION_ID_HIGH_PROP_NAME\n     */\n    public static int publicationReservedSessionIdHigh()\n    {\n        return getInteger(PUBLICATION_RESERVED_SESSION_ID_HIGH_PROP_NAME, PUBLICATION_RESERVED_SESSION_ID_HIGH_DEFAULT);\n    }\n\n    /**\n     * Status message timeout in nanoseconds after which one will be sent when data flow has not triggered one.\n     *\n     * @return status message timeout in nanoseconds after which one will be sent.\n     * @see #STATUS_MESSAGE_TIMEOUT_PROP_NAME\n     */\n    public static long statusMessageTimeoutNs()\n    {\n        return getDurationInNanos(STATUS_MESSAGE_TIMEOUT_PROP_NAME, STATUS_MESSAGE_TIMEOUT_DEFAULT_NS);\n    }\n\n    /**\n     * Ratio of sending data to polling status messages in the {@link Sender}.\n     *\n     * @return ratio of sending data to polling status messages in the {@link Sender}.\n     * @see #SEND_TO_STATUS_POLL_RATIO_PROP_NAME\n     */\n    public static int sendToStatusMessagePollRatio()\n    {\n        return getInteger(SEND_TO_STATUS_POLL_RATIO_PROP_NAME, SEND_TO_STATUS_POLL_RATIO_DEFAULT);\n    }\n\n    /**\n     * Limit the number of driver managed resources that can be freed in the same duty cycle.\n     *\n     * @return limit of the number of resources.\n     * @see #RESOURCE_FREE_LIMIT_PROP_NAME\n     * @see #RESOURCE_FREE_LIMIT_DEFAULT\n     */\n    public static int resourceFreeLimit()\n    {\n        return getInteger(RESOURCE_FREE_LIMIT_PROP_NAME, RESOURCE_FREE_LIMIT_DEFAULT);\n    }\n\n    /**\n     * Timeout between a counter being freed and being available to be reused.\n     *\n     * @return timeout between a counter being freed and being available to be reused.\n     * @see #COUNTER_FREE_TO_REUSE_TIMEOUT_PROP_NAME\n     */\n    public static long counterFreeToReuseTimeoutNs()\n    {\n        return getDurationInNanos(COUNTER_FREE_TO_REUSE_TIMEOUT_PROP_NAME, DEFAULT_COUNTER_FREE_TO_REUSE_TIMEOUT_NS);\n    }\n\n    /**\n     * {@link Aeron} client liveness timeout after which it is considered not alive.\n     *\n     * @return {@link Aeron} client liveness timeout after which it is considered not alive.\n     * @see #CLIENT_LIVENESS_TIMEOUT_PROP_NAME\n     */\n    public static long clientLivenessTimeoutNs()\n    {\n        return getDurationInNanos(CLIENT_LIVENESS_TIMEOUT_PROP_NAME, CLIENT_LIVENESS_TIMEOUT_DEFAULT_NS);\n    }\n\n    /**\n     * {@link Image} liveness timeout for how long it stays active without heartbeats or lingers around after being\n     * drained.\n     *\n     * @return {@link Image} liveness timeout for how long it stays active without heartbeats or lingers around after\n     * being drained.\n     * @see #IMAGE_LIVENESS_TIMEOUT_PROP_NAME\n     */\n    public static long imageLivenessTimeoutNs()\n    {\n        return getDurationInNanos(IMAGE_LIVENESS_TIMEOUT_PROP_NAME, IMAGE_LIVENESS_TIMEOUT_DEFAULT_NS);\n    }\n\n    /**\n     * {@link Publication} unblock timeout due to client crash or untimely commit.\n     * <p>\n     * A publication can become blocked if the client crashes while publishing or if\n     * {@link io.aeron.Publication#tryClaim(int, BufferClaim)} is used without following up by calling\n     * {@link BufferClaim#commit()} or {@link BufferClaim#abort()}.\n     *\n     * @return {@link Publication} unblock timeout due to client crash or untimely commit.\n     * @see #PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME\n     */\n    public static long publicationUnblockTimeoutNs()\n    {\n        return getDurationInNanos(PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME, PUBLICATION_UNBLOCK_TIMEOUT_DEFAULT_NS);\n    }\n\n    /**\n     * {@link Publication} timeout due to lack of status messages which indicate a connection.\n     *\n     * @return {@link Publication} timeout due to lack of status messages which indicate a connection.\n     * @see #PUBLICATION_CONNECTION_TIMEOUT_PROP_NAME\n     */\n    public static long publicationConnectionTimeoutNs()\n    {\n        return getDurationInNanos(PUBLICATION_CONNECTION_TIMEOUT_PROP_NAME, PUBLICATION_CONNECTION_TIMEOUT_DEFAULT_NS);\n    }\n\n    /**\n     * Linger timeout after draining on {@link Publication}s so they can respond to NAKs.\n     *\n     * @return linger timeout after draining on {@link Publication}s so they can respond to NAKs.\n     * @see #PUBLICATION_LINGER_PROP_NAME\n     */\n    public static long publicationLingerTimeoutNs()\n    {\n        return getDurationInNanos(PUBLICATION_LINGER_PROP_NAME, PUBLICATION_LINGER_DEFAULT_NS);\n    }\n\n    /**\n     * Setting how long to delay before sending a retransmit after receiving a NAK.\n     *\n     * @return setting how long to delay before sending a retransmit after receiving a NAK.\n     * @see #RETRANSMIT_UNICAST_DELAY_PROP_NAME\n     */\n    public static long retransmitUnicastDelayNs()\n    {\n        return getDurationInNanos(RETRANSMIT_UNICAST_DELAY_PROP_NAME, RETRANSMIT_UNICAST_DELAY_DEFAULT_NS);\n    }\n\n    /**\n     * Setting how long to linger after delay on a NAK before responding to another NAK.\n     *\n     * @return setting how long to linger after delay on a NAK before responding to another NAK.\n     * @see #RETRANSMIT_UNICAST_LINGER_PROP_NAME\n     */\n    public static long retransmitUnicastLingerNs()\n    {\n        return getDurationInNanos(RETRANSMIT_UNICAST_LINGER_PROP_NAME, RETRANSMIT_UNICAST_LINGER_DEFAULT_NS);\n    }\n\n    /**\n     * Length of the memory mapped buffer for the {@link io.aeron.driver.reports.LossReport}.\n     *\n     * @return length of the memory mapped buffer for the {@link io.aeron.driver.reports.LossReport}.\n     * @see #LOSS_REPORT_BUFFER_LENGTH_PROP_NAME\n     */\n    public static int lossReportBufferLength()\n    {\n        return getSizeAsInt(LOSS_REPORT_BUFFER_LENGTH_PROP_NAME, LOSS_REPORT_BUFFER_LENGTH_DEFAULT);\n    }\n\n    /**\n     * {@link ThreadingMode} to be used by the Aeron {@link MediaDriver}. This allows for CPU resource to be traded\n     * against throughput and latency.\n     *\n     * @return {@link ThreadingMode} to be used by the Aeron {@link MediaDriver}.\n     * @see #THREADING_MODE_PROP_NAME\n     */\n    public static ThreadingMode threadingMode()\n    {\n        final String propertyValue = getProperty(THREADING_MODE_PROP_NAME);\n        if (null == propertyValue)\n        {\n            return DEDICATED;\n        }\n\n        return ThreadingMode.valueOf(propertyValue);\n    }\n\n    /**\n     * Get threshold value for the conductor work cycle threshold to track for being exceeded.\n     *\n     * @return threshold value in nanoseconds.\n     */\n    public static long conductorCycleThresholdNs()\n    {\n        return getDurationInNanos(CONDUCTOR_CYCLE_THRESHOLD_PROP_NAME, CONDUCTOR_CYCLE_THRESHOLD_DEFAULT_NS);\n    }\n\n    /**\n     * Get threshold value for the sender work cycle threshold to track for being exceeded.\n     *\n     * @return threshold value in nanoseconds.\n     */\n    public static long senderCycleThresholdNs()\n    {\n        return getDurationInNanos(SENDER_CYCLE_THRESHOLD_PROP_NAME, SENDER_CYCLE_THRESHOLD_DEFAULT_NS);\n    }\n\n    /**\n     * Get threshold value for the receiver work cycle threshold to track for being exceeded.\n     *\n     * @return threshold value in nanoseconds.\n     */\n    public static long receiverCycleThresholdNs()\n    {\n        return getDurationInNanos(RECEIVER_CYCLE_THRESHOLD_PROP_NAME, RECEIVER_CYCLE_THRESHOLD_DEFAULT_NS);\n    }\n\n    /**\n     * Get threshold value for the name resolution time threshold to track for being exceeded.\n     *\n     * @return threshold value in nanoseconds.\n     */\n    public static long nameResolverThresholdNs()\n    {\n        return getDurationInNanos(NAME_RESOLVER_THRESHOLD_PROP_NAME, NAME_RESOLVER_THRESHOLD_DEFAULT_NS);\n    }\n\n    /**\n     * Get wildcard port range in use for the Sender.\n     *\n     * @return port range as string with the format \"low high\"\n     */\n    public static String senderWildcardPortRange()\n    {\n        return getProperty(SENDER_WILDCARD_PORT_RANGE_PROP_NAME);\n    }\n\n    /**\n     * Get wildcard port range in use for the Receiver.\n     *\n     * @return port range as string with the format \"low high\"\n     */\n    public static String receiverWildcardPortRange()\n    {\n        return getProperty(RECEIVER_WILDCARD_PORT_RANGE_PROP_NAME);\n    }\n\n\n    /**\n     * Number of async executor threads.\n     *\n     * @return number of threads, defaults to one.\n     * @see #ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME\n     * @since 1.44.0\n     */\n    public static int asyncTaskExecutorThreads()\n    {\n        return getInteger(ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME, 1);\n    }\n\n    /**\n     * Get the {@link IdleStrategy} that should be applied to {@link org.agrona.concurrent.Agent}s.\n     *\n     * @param strategyName       of the class to be created.\n     * @param controllableStatus status indicator for what the strategy should do.\n     * @return the newly created IdleStrategy.\n     */\n    public static IdleStrategy agentIdleStrategy(final String strategyName, final StatusIndicator controllableStatus)\n    {\n        IdleStrategy idleStrategy = null;\n\n        switch (strategyName)\n        {\n            case NoOpIdleStrategy.ALIAS:\n            case \"org.agrona.concurrent.NoOpIdleStrategy\":\n                idleStrategy = NoOpIdleStrategy.INSTANCE;\n                break;\n\n            case BusySpinIdleStrategy.ALIAS:\n            case \"org.agrona.concurrent.BusySpinIdleStrategy\":\n                idleStrategy = BusySpinIdleStrategy.INSTANCE;\n                break;\n\n            case YieldingIdleStrategy.ALIAS:\n            case \"org.agrona.concurrent.YieldingIdleStrategy\":\n                idleStrategy = YieldingIdleStrategy.INSTANCE;\n                break;\n\n            case SleepingIdleStrategy.ALIAS:\n            case \"org.agrona.concurrent.SleepingIdleStrategy\":\n                idleStrategy = new SleepingIdleStrategy();\n                break;\n\n            case SleepingMillisIdleStrategy.ALIAS:\n            case \"org.agrona.concurrent.SleepingMillisIdleStrategy\":\n                idleStrategy = new SleepingMillisIdleStrategy();\n                break;\n\n            case BackoffIdleStrategy.ALIAS:\n            case DEFAULT_IDLE_STRATEGY:\n                idleStrategy = new BackoffIdleStrategy(\n                    IDLE_MAX_SPINS, IDLE_MAX_YIELDS, IDLE_MIN_PARK_NS, IDLE_MAX_PARK_NS);\n                break;\n\n            case ControllableIdleStrategy.ALIAS:\n            case CONTROLLABLE_IDLE_STRATEGY:\n                Objects.requireNonNull(controllableStatus);\n                controllableStatus.setRelease(ControllableIdleStrategy.PARK);\n                idleStrategy = new ControllableIdleStrategy(controllableStatus);\n                break;\n\n            default:\n                try\n                {\n                    idleStrategy = (IdleStrategy)Class.forName(strategyName).getConstructor().newInstance();\n                }\n                catch (final Exception ex)\n                {\n                    LangUtil.rethrowUnchecked(ex);\n                }\n                break;\n        }\n\n        return idleStrategy;\n    }\n\n    /**\n     * {@link IdleStrategy} to be employed by {@link Sender} for {@link ThreadingMode#DEDICATED}.\n     *\n     * @param controllableStatus to allow control of {@link ControllableIdleStrategy}, which can be null if not used.\n     * @return {@link IdleStrategy} to be employed by {@link Sender} for {@link ThreadingMode#DEDICATED}.\n     * @see #SENDER_IDLE_STRATEGY_PROP_NAME\n     */\n    public static IdleStrategy senderIdleStrategy(final StatusIndicator controllableStatus)\n    {\n        return agentIdleStrategy(\n            getProperty(SENDER_IDLE_STRATEGY_PROP_NAME, SENDER_IDLE_STRATEGY_DEFAULT), controllableStatus);\n    }\n\n    /**\n     * {@link IdleStrategy} to be employed by {@link Receiver} for {@link ThreadingMode#DEDICATED}.\n     *\n     * @param controllableStatus to allow control of {@link ControllableIdleStrategy}, which can be null if not used.\n     * @return {@link IdleStrategy} to be employed by {@link Receiver} for {@link ThreadingMode#DEDICATED}.\n     * @see #RECEIVER_IDLE_STRATEGY_PROP_NAME\n     */\n    public static IdleStrategy receiverIdleStrategy(final StatusIndicator controllableStatus)\n    {\n        return agentIdleStrategy(\n            getProperty(RECEIVER_IDLE_STRATEGY_PROP_NAME, RECEIVER_IDLE_STRATEGY_DEFAULT), controllableStatus);\n    }\n\n    /**\n     * {@link IdleStrategy} to be employed by {@link DriverConductor} for {@link ThreadingMode#DEDICATED} and\n     * {@link ThreadingMode#SHARED_NETWORK}.\n     *\n     * @param controllableStatus to allow control of {@link ControllableIdleStrategy}, which can be null if not used.\n     * @return {@link IdleStrategy} to be employed by {@link DriverConductor} for {@link ThreadingMode#DEDICATED}\n     * and {@link ThreadingMode#SHARED_NETWORK}..\n     * @see #CONDUCTOR_IDLE_STRATEGY_PROP_NAME\n     */\n    public static IdleStrategy conductorIdleStrategy(final StatusIndicator controllableStatus)\n    {\n        return agentIdleStrategy(\n            getProperty(CONDUCTOR_IDLE_STRATEGY_PROP_NAME, CONDUCTOR_IDLE_STRATEGY_DEFAULT), controllableStatus);\n    }\n\n    /**\n     * {@link IdleStrategy} to be employed by {@link Sender} and {@link Receiver} for\n     * {@link ThreadingMode#SHARED_NETWORK}.\n     *\n     * @param controllableStatus to allow control of {@link ControllableIdleStrategy}, which can be null if not used.\n     * @return {@link IdleStrategy} to be employed by {@link Sender} and {@link Receiver} for\n     * {@link ThreadingMode#SHARED_NETWORK}.\n     * @see #SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME\n     */\n    public static IdleStrategy sharedNetworkIdleStrategy(final StatusIndicator controllableStatus)\n    {\n        return agentIdleStrategy(getProperty(SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME,\n            SHARED_NETWORK_IDLE_STRATEGY_DEFAULT), controllableStatus);\n    }\n\n    /**\n     * {@link IdleStrategy} to be employed by {@link Sender}, {@link Receiver}, and {@link DriverConductor} for\n     * {@link ThreadingMode#SHARED}.\n     *\n     * @param controllableStatus to allow control of {@link ControllableIdleStrategy}, which can be null if not used.\n     * @return {@link IdleStrategy} to be employed by {@link Sender}, {@link Receiver}, and {@link DriverConductor} for\n     * {@link ThreadingMode#SHARED}.\n     * @see #SHARED_IDLE_STRATEGY_PROP_NAME\n     */\n    public static IdleStrategy sharedIdleStrategy(final StatusIndicator controllableStatus)\n    {\n        return agentIdleStrategy(\n            getProperty(SHARED_IDLE_STRATEGY_PROP_NAME, SHARED_IDLE_STRATEGY_DEFAULT), controllableStatus);\n    }\n\n    /**\n     * @return Application Specific Feedback added to Status Messages by the driver for flow control.\n     * @see #SM_APPLICATION_SPECIFIC_FEEDBACK_PROP_NAME\n     * @deprecated see {@link #groupTag()}.\n     */\n    @Deprecated\n    public static byte[] applicationSpecificFeedback()\n    {\n        final String propertyValue = getProperty(SM_APPLICATION_SPECIFIC_FEEDBACK_PROP_NAME);\n        if (null == propertyValue)\n        {\n            return ArrayUtil.EMPTY_BYTE_ARRAY;\n        }\n\n        return fromHex(propertyValue);\n    }\n\n    /**\n     * Get the supplier of {@link SendChannelEndpoint}s which can be used for\n     * debugging, monitoring, or modifying the behaviour when sending to the channel.\n     *\n     * @return the {@link SendChannelEndpointSupplier}.\n     */\n    public static SendChannelEndpointSupplier sendChannelEndpointSupplier()\n    {\n        SendChannelEndpointSupplier supplier = null;\n        try\n        {\n            final String className = getProperty(SEND_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME);\n            if (null == className)\n            {\n                return new DefaultSendChannelEndpointSupplier();\n            }\n\n            supplier = (SendChannelEndpointSupplier)Class.forName(className).getConstructor().newInstance();\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return supplier;\n    }\n\n    /**\n     * Get the supplier of {@link ReceiveChannelEndpoint}s which can be used for\n     * debugging, monitoring, or modifying the behaviour when receiving from the channel.\n     *\n     * @return the {@link SendChannelEndpointSupplier}.\n     */\n    public static ReceiveChannelEndpointSupplier receiveChannelEndpointSupplier()\n    {\n        ReceiveChannelEndpointSupplier supplier = null;\n        try\n        {\n            final String className = getProperty(RECEIVE_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME);\n            if (null == className)\n            {\n                return new DefaultReceiveChannelEndpointSupplier();\n            }\n\n            supplier = (ReceiveChannelEndpointSupplier)Class.forName(className).getConstructor().newInstance();\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return supplier;\n    }\n\n    /**\n     * Get the supplier of {@link FlowControl}s which can be used for changing behavior of flow control for unicast\n     * publications.\n     *\n     * @return the {@link FlowControlSupplier}.\n     */\n    public static FlowControlSupplier unicastFlowControlSupplier()\n    {\n        FlowControlSupplier supplier = null;\n        try\n        {\n            final String className = getProperty(UNICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME);\n            if (null == className)\n            {\n                return new DefaultUnicastFlowControlSupplier();\n            }\n\n            supplier = (FlowControlSupplier)Class.forName(className).getConstructor().newInstance();\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return supplier;\n    }\n\n    /**\n     * Get the supplier of {@link FlowControl}s which can be used for changing behavior of flow control for multicast\n     * publications.\n     *\n     * @return the {@link FlowControlSupplier}.\n     */\n    public static FlowControlSupplier multicastFlowControlSupplier()\n    {\n        FlowControlSupplier supplier = null;\n        try\n        {\n            final String className = getProperty(MULTICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME);\n            if (null == className)\n            {\n                return new DefaultMulticastFlowControlSupplier();\n            }\n\n            supplier = (FlowControlSupplier)Class.forName(className).getConstructor().newInstance();\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return supplier;\n    }\n\n    /**\n     * Get the supplier of {@link CongestionControl} implementations which can be used for receivers.\n     *\n     * @return the {@link CongestionControlSupplier}\n     */\n    public static CongestionControlSupplier congestionControlSupplier()\n    {\n        CongestionControlSupplier supplier = null;\n        try\n        {\n            final String className = getProperty(CONGESTION_CONTROL_STRATEGY_SUPPLIER_PROP_NAME);\n            if (null == className)\n            {\n                return new DefaultCongestionControlSupplier();\n            }\n\n            supplier = (CongestionControlSupplier)Class.forName(className).getConstructor().newInstance();\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return supplier;\n    }\n\n    /**\n     * Get the configured limit for the number of streams per session.\n     *\n     * @return configured session limit\n     * @throws AsciiNumberFormatException if the property referenced by {@link #STREAM_SESSION_LIMIT_PROP_NAME} is not\n     *                                    a valid number\n     */\n    public static int streamSessionLimit()\n    {\n        final String streamSessionLimitString = getProperty(STREAM_SESSION_LIMIT_PROP_NAME);\n        try\n        {\n            return Strings.isEmpty(streamSessionLimitString) ?\n                STREAM_SESSION_LIMIT_DEFAULT : Integer.parseInt(streamSessionLimitString);\n        }\n        catch (final NumberFormatException ex)\n        {\n            throw new AsciiNumberFormatException(\n                \"Property \" + STREAM_SESSION_LIMIT_PROP_NAME + \"=\" + streamSessionLimitString + \" is not a number\");\n        }\n    }\n\n    /**\n     * Validate that the initial window length is greater than MTU.\n     *\n     * @param initialWindowLength to be validated.\n     * @param mtuLength           against which to validate.\n     */\n    public static void validateInitialWindowLength(final int initialWindowLength, final int mtuLength)\n    {\n        if (mtuLength > initialWindowLength)\n        {\n            throw new ConfigurationException(\n                \"mtuLength=\" + mtuLength + \" > initialWindowLength=\" + initialWindowLength);\n        }\n    }\n\n    /**\n     * Validate that the MTU is an appropriate length. MTU lengths must be a multiple of\n     * {@link FrameDescriptor#FRAME_ALIGNMENT}.\n     *\n     * @param mtuLength to be validated.\n     * @throws ConfigurationException if the MTU length is not valid.\n     */\n    public static void validateMtuLength(final int mtuLength)\n    {\n        if (mtuLength <= DataHeaderFlyweight.HEADER_LENGTH)\n        {\n            throw new ConfigurationException(\n                \"mtuLength=\" + mtuLength + \" <= HEADER_LENGTH=\" + DataHeaderFlyweight.HEADER_LENGTH);\n        }\n\n        if (mtuLength > MAX_UDP_PAYLOAD_LENGTH)\n        {\n            throw new ConfigurationException(\n                \"mtuLength=\" + mtuLength + \" > MAX_UDP_PAYLOAD_LENGTH=\" + MAX_UDP_PAYLOAD_LENGTH);\n        }\n\n        if ((mtuLength & (FrameDescriptor.FRAME_ALIGNMENT - 1)) != 0)\n        {\n            throw new ConfigurationException(\n                \"mtuLength=\" + mtuLength + \" is not a multiple of FRAME_ALIGNMENT=\" + FrameDescriptor.FRAME_ALIGNMENT);\n        }\n    }\n\n    /**\n     * Get the {@link TerminationValidator} implementations which can be used for validating a termination request\n     * sent to the driver to ensure the client has the right to terminate a driver.\n     *\n     * @return the {@link TerminationValidator}\n     */\n    public static TerminationValidator terminationValidator()\n    {\n        TerminationValidator validator = null;\n        try\n        {\n            final String className = getProperty(TERMINATION_VALIDATOR_PROP_NAME);\n            if (null == className)\n            {\n                return new DefaultDenyTerminationValidator();\n            }\n\n            validator = (TerminationValidator)Class.forName(className).getConstructor().newInstance();\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return validator;\n    }\n\n    /**\n     * Validate that the socket buffer lengths are sufficient for the media driver configuration.\n     *\n     * @param ctx to be validated.\n     */\n    public static void validateSocketBufferLengths(final MediaDriver.Context ctx)\n    {\n        if (ctx.osMaxSocketSndbufLength() < ctx.socketSndbufLength())\n        {\n            System.err.println(\n                \"WARNING: Could not set desired SO_SNDBUF, adjust OS to allow \" + SOCKET_SNDBUF_LENGTH_PROP_NAME +\n                \" attempted=\" + ctx.socketSndbufLength() + \", actual=\" + ctx.osMaxSocketSndbufLength());\n        }\n\n        if (ctx.osMaxSocketRcvbufLength() < ctx.socketRcvbufLength())\n        {\n            System.err.println(\n                \"WARNING: Could not set desired SO_RCVBUF, adjust OS to allow \" + SOCKET_RCVBUF_LENGTH_PROP_NAME +\n                \" attempted=\" + ctx.socketRcvbufLength() + \", actual=\" + ctx.osMaxSocketRcvbufLength());\n        }\n\n        final int soSndBuf = 0 == ctx.socketSndbufLength() ?\n            ctx.osDefaultSocketSndbufLength() : ctx.socketSndbufLength();\n\n        if (ctx.mtuLength() > soSndBuf)\n        {\n            throw new ConfigurationException(\n                \"mtuLength=\" + ctx.mtuLength() + \" > SO_SNDBUF=\" + soSndBuf +\n                \", increase \" + SOCKET_SNDBUF_LENGTH_PROP_NAME + \" to match MTU\");\n        }\n\n        final int soRcvBuf = 0 == ctx.socketRcvbufLength() ?\n            ctx.osDefaultSocketRcvbufLength() : ctx.socketRcvbufLength();\n\n        if (ctx.initialWindowLength() > soRcvBuf)\n        {\n            throw new ConfigurationException(\n                \"initialWindowLength=\" + ctx.initialWindowLength() + \" > SO_RCVBUF=\" + soRcvBuf +\n                \", increase \" + SOCKET_RCVBUF_LENGTH_PROP_NAME + \" limits to match initialWindowLength\");\n        }\n    }\n\n    /**\n     * Validate that page size is valid and alignment is valid.\n     *\n     * @param pageSize to be checked.\n     * @throws ConfigurationException if the size is not as expected.\n     */\n    public static void validatePageSize(final int pageSize)\n    {\n        validateValueRange(pageSize, PAGE_MIN_SIZE, PAGE_MAX_SIZE, \"filePageSize\");\n\n        if (!BitUtil.isPowerOfTwo(pageSize))\n        {\n            throw new ConfigurationException(\"filePageSize not a power of 2: \" + pageSize);\n        }\n    }\n\n    /**\n     * Validate the range of session ids based on a high and low value provided which accounts for the values wrapping.\n     *\n     * @param low  value in the range.\n     * @param high value in the range.\n     * @throws ConfigurationException if the values are not valid.\n     */\n    public static void validateSessionIdRange(final int low, final int high)\n    {\n        if (low > high)\n        {\n            throw new ConfigurationException(\"low session id value \" + low + \" must be <= high value \" + high);\n        }\n\n        if (Math.abs((long)high - low) > Integer.MAX_VALUE)\n        {\n            throw new ConfigurationException(\"reserved session range too large\");\n        }\n    }\n\n    /**\n     * Compute the length of the {@link org.agrona.concurrent.status.CountersManager} metadata buffer based on the\n     * length of the counters value buffer length.\n     *\n     * @param counterValuesBufferLength to compute the metadata buffer length from as a ratio.\n     * @return the length that should be used for the metadata buffer for counters.\n     */\n    public static int countersMetadataBufferLength(final int counterValuesBufferLength)\n    {\n        return counterValuesBufferLength * (CountersReader.METADATA_LENGTH / CountersReader.COUNTER_LENGTH);\n    }\n\n    /**\n     * Validate that the timeouts for unblocking publications from a client are valid.\n     *\n     * @param publicationUnblockTimeoutNs after which an uncommitted publication will be unblocked.\n     * @param clientLivenessTimeoutNs     after which a client will be considered not alive.\n     * @param timerIntervalNs             interval at which the driver will check timeouts.\n     * @throws ConfigurationException if the values are not valid.\n     */\n    public static void validateUnblockTimeout(\n        final long publicationUnblockTimeoutNs, final long clientLivenessTimeoutNs, final long timerIntervalNs)\n    {\n        if (publicationUnblockTimeoutNs <= clientLivenessTimeoutNs)\n        {\n            throw new ConfigurationException(\n                \"publicationUnblockTimeoutNs=\" + publicationUnblockTimeoutNs +\n                \" <= clientLivenessTimeoutNs=\" + clientLivenessTimeoutNs);\n        }\n\n        if (clientLivenessTimeoutNs <= timerIntervalNs)\n        {\n            throw new ConfigurationException(\n                \"clientLivenessTimeoutNs=\" + clientLivenessTimeoutNs +\n                \" <= timerIntervalNs=\" + timerIntervalNs);\n        }\n    }\n\n    /**\n     * Validate that the timeouts for untethered subscriptions are greater than timer interval.\n     *\n     * @param untetheredWindowLimitTimeoutNs after which an active untethered subscription will be lingered.\n     * @param untetheredLingerTimeoutNs      after which a lingering untethered subscription will transition to resting.\n     * @param untetheredRestingTimeoutNs     after which a resting untethered subscription will become active again.\n     * @param timerIntervalNs                interval at which the driver will check timeouts.\n     * @throws ConfigurationException if the values are not valid.\n     */\n    public static void validateUntetheredTimeouts(\n        final long untetheredWindowLimitTimeoutNs,\n        final long untetheredLingerTimeoutNs,\n        final long untetheredRestingTimeoutNs,\n        final long timerIntervalNs)\n    {\n        if (untetheredWindowLimitTimeoutNs <= timerIntervalNs)\n        {\n            throw new ConfigurationException(\n                \"untetheredWindowLimitTimeoutNs=\" + untetheredWindowLimitTimeoutNs +\n                \" <= timerIntervalNs=\" + timerIntervalNs);\n        }\n\n        if (untetheredRestingTimeoutNs <= timerIntervalNs)\n        {\n            throw new ConfigurationException(\n                \"untetheredRestingTimeoutNs=\" + untetheredRestingTimeoutNs +\n                \" <= timerIntervalNs=\" + timerIntervalNs);\n        }\n\n        if (Aeron.NULL_VALUE != untetheredLingerTimeoutNs && untetheredLingerTimeoutNs <= timerIntervalNs)\n        {\n            throw new ConfigurationException(\n                \"untetheredLingerTimeoutNs=\" + untetheredLingerTimeoutNs +\n                    \" <= timerIntervalNs=\" + timerIntervalNs);\n        }\n    }\n\n    /**\n     * Create a source identity for a given source address.\n     *\n     * @param srcAddress to be used for the identity.\n     * @return a source identity string for a given address.\n     */\n    public static String sourceIdentity(final InetSocketAddress srcAddress)\n    {\n        return srcAddress.getHostString() + ':' + srcAddress.getPort();\n    }\n\n    static void validateValueRange(final long value, final long minValue, final long maxValue, final String name)\n    {\n        if (value < minValue)\n        {\n            throw new ConfigurationException(\n                name + \" less than min size of \" + minValue + \": \" + value);\n        }\n\n        if (value > maxValue)\n        {\n            throw new ConfigurationException(\n                name + \" greater than max size of \" + maxValue + \": \" + value);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/CongestionControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Strategy for applying congestion control to determine the receiver window length of the Status Messages.\n */\npublic interface CongestionControl extends AutoCloseable\n{\n    /**\n     * Bit flag for if a status message should be forced out.\n     */\n    int FORCE_STATUS_MESSAGE_BIT = 0x1;\n\n    /**\n     * Pack values into a long, so they can be returned on the stack without allocation.\n     *\n     * @param receiverWindowLength to go in the lower bits.\n     * @param forceStatusMessage   to go in the higher bits.\n     * @return the packed value.\n     */\n    static long packOutcome(final int receiverWindowLength, final boolean forceStatusMessage)\n    {\n        final int flags = forceStatusMessage ? FORCE_STATUS_MESSAGE_BIT : 0x0;\n\n        return ((long)flags << 32) | receiverWindowLength;\n    }\n\n    /**\n     * Extract the receiver window length from the packed value.\n     *\n     * @param outcome as the packed value.\n     * @return the receiver window length.\n     */\n    static int receiverWindowLength(final long outcome)\n    {\n        return (int)(outcome & 0xFFFFFFFFL);\n    }\n\n    /**\n     * Extract the boolean value for if a status message should be forced from the packed value.\n     *\n     * @param outcome which is packed containing the force status message flag.\n     * @return true if the force status message bit has been set.\n     */\n    static boolean shouldForceStatusMessage(final long outcome)\n    {\n        return ((int)(outcome >>> 32) & FORCE_STATUS_MESSAGE_BIT) == FORCE_STATUS_MESSAGE_BIT;\n    }\n\n    /**\n     * Threshold increment in a window after which a status message should be scheduled.\n     *\n     * @param windowLength to calculate the threshold from.\n     * @return the threshold in the window after which a status message should be scheduled.\n     */\n    static int threshold(final int windowLength)\n    {\n        return windowLength >> 2;\n    }\n\n    /**\n     * Polled by {@link Receiver} to determine when to initiate an RTT measurement to a Sender.\n     *\n     * @param nowNs in nanoseconds\n     * @return true for should measure RTT now or false for no measurement\n     */\n    boolean shouldMeasureRtt(long nowNs);\n\n    /**\n     * Called by {@link Receiver} to record that a measurement request has been sent.\n     *\n     * @param nowNs in nanoseconds.\n     */\n    void onRttMeasurementSent(long nowNs);\n\n    /**\n     * Called by {@link Receiver} on reception of an RTT Measurement.\n     *\n     * @param nowNs      in nanoseconds\n     * @param rttNs      to the Sender in nanoseconds\n     * @param srcAddress of the Sender\n     */\n    void onRttMeasurement(long nowNs, long rttNs, InetSocketAddress srcAddress);\n\n    /**\n     * Called by {@link DriverConductor} upon execution of {@link PublicationImage#trackRebuild(long)} to\n     * pass on current status.\n     * <p>\n     * The return value must be packed using {@link CongestionControl#packOutcome(int, boolean)}.\n     *\n     * @param nowNs                   current time\n     * @param newConsumptionPosition  of the subscribers\n     * @param lastSmPosition          of the image\n     * @param hwmPosition             of the image\n     * @param startingRebuildPosition of the rebuild\n     * @param endingRebuildPosition   of the rebuild\n     * @param lossOccurred            during rebuild\n     * @return outcome of congestion control calculation containing window length and whether to force sending an SM.\n     */\n    long onTrackRebuild(\n        long nowNs,\n        long newConsumptionPosition,\n        long lastSmPosition,\n        long hwmPosition,\n        long startingRebuildPosition,\n        long endingRebuildPosition,\n        boolean lossOccurred);\n\n    /**\n     * Called by {@link DriverConductor} to initialise window length for a new {@link PublicationImage}.\n     *\n     * @return initial window length for flow and congestion control.\n     */\n    int initialWindowLength();\n\n    /**\n     * Called by {@link DriverConductor} limit the window length for a new {@link PublicationImage}.\n     *\n     * @return maximum window length for flow and congestion control.\n     */\n    int maxWindowLength();\n\n    /**\n     * {@inheritDoc}\n     */\n    void close();\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/CongestionControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Supplier of {@link CongestionControl} algorithm implementations to be used by receivers.\n */\n@FunctionalInterface\npublic interface CongestionControlSupplier\n{\n    /**\n     * Return a new {@link CongestionControl} instance.\n     *\n     * @param registrationId  for the publication image.\n     * @param udpChannel      for the publication image.\n     * @param streamId        for the publication image.\n     * @param sessionId       for the publication image.\n     * @param termLength      for the publication image.\n     * @param senderMtuLength for the publication image.\n     * @param controlAddress  for the publication image.\n     * @param sourceAddress   for the publication image.\n     * @param nanoClock       for the precise timing.\n     * @param context         for configuration options applied in the driver.\n     * @param countersManager for the driver.\n     * @return congestion control instance ready for immediate usage.\n     */\n    CongestionControl newInstance(\n        long registrationId,\n        UdpChannel udpChannel,\n        int streamId,\n        int sessionId,\n        int termLength,\n        int senderMtuLength,\n        InetSocketAddress controlAddress,\n        InetSocketAddress sourceAddress,\n        NanoClock nanoClock,\n        MediaDriver.Context context,\n        CountersManager countersManager);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/CounterLink.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Tracks an aeron client interest in a counter.\n */\nfinal class CounterLink implements DriverManagedResource\n{\n    private final long registrationId;\n    private final AtomicCounter counter;\n    private final AeronClient client;\n    private boolean reachedEndOfLife = false;\n\n    CounterLink(final AtomicCounter counter, final long registrationId, final AeronClient client)\n    {\n        this.registrationId = registrationId;\n        this.counter = counter;\n        this.client = client;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        counter.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTimeEvent(final long timeNs, final long timeMs, final DriverConductor conductor)\n    {\n        if (client.hasTimedOut())\n        {\n            reachedEndOfLife = true;\n            conductor.unavailableCounter(registrationId, counterId());\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean hasReachedEndOfLife()\n    {\n        return reachedEndOfLife;\n    }\n\n    int counterId()\n    {\n        return counter.id();\n    }\n\n    long registrationId()\n    {\n        return registrationId;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DataPacketDispatcher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.exceptions.UnknownSubscriptionException;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.exceptions.AeronEvent;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.collections.IntHashSet;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetSocketAddress;\n\nimport static io.aeron.driver.DataPacketDispatcher.SessionState.*;\n\n/**\n * Handling of dispatching data packets to {@link PublicationImage}s streams.\n * <p>\n * All methods should be called from the {@link Receiver} thread.\n */\npublic final class DataPacketDispatcher\n{\n    @SuppressWarnings(\"JavadocVariable\")\n    enum SessionState\n    {\n        ACTIVE,\n        PENDING_SETUP_FRAME,\n        INIT_IN_PROGRESS,\n        ON_COOL_DOWN,\n        NO_INTEREST\n    }\n\n    static class StreamInterest\n    {\n        boolean isAllSessions;\n        final Int2ObjectHashMap<SessionState> sessionInterestByIdMap = new Int2ObjectHashMap<>();\n        final Int2ObjectHashMap<PublicationImage> activeImageByIdMap = new Int2ObjectHashMap<>();\n        final IntHashSet subscribedSessionIds = new IntHashSet();\n\n        StreamInterest(final boolean isAllSessions)\n        {\n            this.isAllSessions = isAllSessions;\n        }\n\n        PublicationImage findActive(final int sessionId)\n        {\n            return activeImageByIdMap.get(sessionId);\n        }\n\n        public boolean isSessionLimitExceeded(final int sessionLimit)\n        {\n            return sessionLimit <= activeImageByIdMap.size();\n        }\n\n        void removeNonSessionSpecificInterest()\n        {\n            final Int2ObjectHashMap<PublicationImage>.EntryIterator activeIterator =\n                activeImageByIdMap.entrySet().iterator();\n\n            while (activeIterator.hasNext())\n            {\n                activeIterator.next();\n\n                final int sessionId = activeIterator.getIntKey();\n                if (!subscribedSessionIds.contains(sessionId))\n                {\n                    activeIterator.getValue().deactivate();\n                    activeIterator.remove();\n                }\n            }\n\n            final Int2ObjectHashMap<SessionState>.EntryIterator iterator =\n                sessionInterestByIdMap.entrySet().iterator();\n\n            while (iterator.hasNext())\n            {\n                iterator.next();\n\n                final int sessionId = iterator.getIntKey();\n                if (!subscribedSessionIds.contains(sessionId))\n                {\n                    iterator.remove();\n                }\n            }\n        }\n    }\n\n    private final Int2ObjectHashMap<StreamInterest> streamInterestByIdMap = new Int2ObjectHashMap<>();\n    private final DriverConductorProxy conductorProxy;\n    private final Receiver receiver;\n    private final int streamSessionLimit;\n\n    DataPacketDispatcher(\n        final DriverConductorProxy conductorProxy,\n        final Receiver receiver,\n        final int streamSessionLimit)\n    {\n        this.conductorProxy = conductorProxy;\n        this.receiver = receiver;\n        this.streamSessionLimit = streamSessionLimit;\n    }\n\n    /**\n     * Add a subscription to a channel for a given stream id and wildcard session id.\n     *\n     * @param streamId to capture within a channel.\n     */\n    public void addSubscription(final int streamId)\n    {\n        final StreamInterest streamInterest = streamInterestByIdMap.get(streamId);\n\n        if (null == streamInterest)\n        {\n            streamInterestByIdMap.put(streamId, new StreamInterest(true));\n        }\n        else if (!streamInterest.isAllSessions)\n        {\n            streamInterest.isAllSessions = true;\n\n            final Int2ObjectHashMap<SessionState>.ValueIterator iterator =\n                streamInterest.sessionInterestByIdMap.values().iterator();\n\n            while (iterator.hasNext())\n            {\n                if (NO_INTEREST == iterator.next())\n                {\n                    iterator.remove();\n                }\n            }\n        }\n    }\n\n    /**\n     * Add a subscription to a channel for given stream and session ids.\n     *\n     * @param streamId  to capture within a channel.\n     * @param sessionId to capture within a stream id.\n     */\n    public void addSubscription(final int streamId, final int sessionId)\n    {\n        StreamInterest streamInterest = streamInterestByIdMap.get(streamId);\n        if (null == streamInterest)\n        {\n            streamInterest = new StreamInterest(false);\n            streamInterestByIdMap.put(streamId, streamInterest);\n        }\n\n        streamInterest.subscribedSessionIds.add(sessionId);\n\n        final SessionState sessionState = streamInterest.sessionInterestByIdMap.get(sessionId);\n        if (NO_INTEREST == sessionState)\n        {\n            streamInterest.sessionInterestByIdMap.remove(sessionId);\n        }\n    }\n\n    /**\n     * Remove a subscription for a previously registered given stream id and wildcard session id.\n     *\n     * @param streamId to remove the capture for.\n     */\n    public void removeSubscription(final int streamId)\n    {\n        final StreamInterest streamInterest = streamInterestByIdMap.get(streamId);\n        if (null == streamInterest)\n        {\n            throw new UnknownSubscriptionException(\"no subscription for stream \" + streamId);\n        }\n\n        streamInterest.removeNonSessionSpecificInterest();\n\n        streamInterest.isAllSessions = false;\n\n        if (streamInterest.subscribedSessionIds.isEmpty())\n        {\n            streamInterestByIdMap.remove(streamId);\n        }\n    }\n\n    /**\n     * Remove a subscription for a previously registered given stream id and session id.\n     *\n     * @param streamId  to remove the capture for.\n     * @param sessionId within the given stream id.\n     */\n    public void removeSubscription(final int streamId, final int sessionId)\n    {\n        final StreamInterest streamInterest = streamInterestByIdMap.get(streamId);\n        if (null == streamInterest)\n        {\n            throw new UnknownSubscriptionException(\"no subscription for stream \" + streamId);\n        }\n\n        if (!streamInterest.isAllSessions)\n        {\n            final PublicationImage publicationImage = streamInterest.activeImageByIdMap.remove(sessionId);\n            if (null != publicationImage)\n            {\n                publicationImage.deactivate();\n            }\n            streamInterest.sessionInterestByIdMap.remove(sessionId);\n        }\n\n        streamInterest.subscribedSessionIds.remove(sessionId);\n\n        if (!streamInterest.isAllSessions && streamInterest.subscribedSessionIds.isEmpty())\n        {\n            streamInterestByIdMap.remove(streamId);\n        }\n    }\n\n    /**\n     * Add a {@link PublicationImage} to dispatch packets to.\n     *\n     * @param image to which the packets are dispatched.\n     */\n    public void addPublicationImage(final PublicationImage image)\n    {\n        final StreamInterest streamInterest = streamInterestByIdMap.get(image.streamId());\n        if (null != streamInterest)\n        {\n            streamInterest.sessionInterestByIdMap.remove(image.sessionId());\n            streamInterest.activeImageByIdMap.put(image.sessionId(), image);\n            image.activate();\n        }\n    }\n\n    /**\n     * Remove a previously added {@link PublicationImage} so packets are no longer dispatched to it.\n     *\n     * @param image to which the packets are dispatched.\n     */\n    public void removePublicationImage(final PublicationImage image)\n    {\n        final StreamInterest streamInterest = streamInterestByIdMap.get(image.streamId());\n        if (null != streamInterest)\n        {\n            final PublicationImage activeImage = streamInterest.activeImageByIdMap.get(image.sessionId());\n            if (null != activeImage && activeImage.correlationId() == image.correlationId())\n            {\n                streamInterest.activeImageByIdMap.remove(image.sessionId());\n\n                if (!image.isEndOfStream())\n                {\n                    streamInterest.sessionInterestByIdMap.put(image.sessionId(), ON_COOL_DOWN);\n                }\n            }\n        }\n\n        image.deactivate();\n    }\n\n    /**\n     * Remove a pending setup message action once it has been handled.\n     *\n     * @param sessionId of the registered interest.\n     * @param streamId  of the registered interest.\n     */\n    public void removePendingSetup(final int sessionId, final int streamId)\n    {\n        removeByState(sessionId, streamId, PENDING_SETUP_FRAME);\n    }\n\n    /**\n     * Remove a cool down action once it has expired.\n     *\n     * @param sessionId of the registered interest.\n     * @param streamId  of the registered interest.\n     */\n    public void removeCoolDown(final int sessionId, final int streamId)\n    {\n        removeByState(sessionId, streamId, ON_COOL_DOWN);\n    }\n\n    /**\n     * Dispatch a data packet to the registered interest.\n     *\n     * @param channelEndpoint on which the packet was received.\n     * @param header          of the data first frame.\n     * @param buffer          containing the data packet.\n     * @param length          of the data packet.\n     * @param srcAddress      from which the data packet was received.\n     * @param transportIndex  on which the packet was received.\n     * @return number of bytes applied as a result of this action.\n     */\n    public int onDataPacket(\n        final ReceiveChannelEndpoint channelEndpoint,\n        final DataHeaderFlyweight header,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final int transportIndex)\n    {\n        final int streamId = header.streamId();\n        final StreamInterest streamInterest = streamInterestByIdMap.get(streamId);\n\n        if (null != streamInterest)\n        {\n            final int sessionId = header.sessionId();\n\n            final PublicationImage image = streamInterest.findActive(sessionId);\n            if (null != image)\n            {\n                return image.insertPacket(\n                    header.termId(), header.termOffset(), buffer, length, transportIndex, srcAddress);\n            }\n            else if (!DataHeaderFlyweight.isEndOfStream(buffer) &&\n                !streamInterest.sessionInterestByIdMap.containsKey(sessionId))\n            {\n                if (streamInterest.isAllSessions || streamInterest.subscribedSessionIds.contains(sessionId))\n                {\n                    streamInterest.sessionInterestByIdMap.put(sessionId, PENDING_SETUP_FRAME);\n                    elicitSetupMessageFromSource(channelEndpoint, transportIndex, srcAddress, streamId, sessionId);\n                }\n                else\n                {\n                    streamInterest.sessionInterestByIdMap.put(sessionId, NO_INTEREST);\n                }\n            }\n        }\n\n        return 0;\n    }\n\n    /**\n     * Dispatch a setup message to registered interest.\n     *\n     * @param channelEndpoint of reception.\n     * @param msg             flyweight over the network packet.\n     * @param srcAddress      the message came from.\n     * @param transportIndex  on which the message was received.\n     */\n    public void onSetupMessage(\n        final ReceiveChannelEndpoint channelEndpoint,\n        final SetupFlyweight msg,\n        final InetSocketAddress srcAddress,\n        final int transportIndex)\n    {\n        final int streamId = msg.streamId();\n        final StreamInterest streamInterest = streamInterestByIdMap.get(streamId);\n\n        if (null != streamInterest)\n        {\n            final int sessionId = msg.sessionId();\n\n            if (streamInterest.isSessionLimitExceeded(streamSessionLimit))\n            {\n                throw new AeronEvent(\"exceeded session limit, streamId=\" + streamId + \" sourceAddress=\" + srcAddress);\n            }\n\n            final PublicationImage image = streamInterest.findActive(sessionId);\n            final SessionState sessionInterest = streamInterest.sessionInterestByIdMap.get(sessionId);\n\n            if (null != image)\n            {\n                image.addDestinationConnectionIfUnknown(transportIndex, srcAddress);\n            }\n            else if (null != sessionInterest)\n            {\n                if (PENDING_SETUP_FRAME == sessionInterest)\n                {\n                    streamInterest.sessionInterestByIdMap.put(sessionId, INIT_IN_PROGRESS);\n\n                    createPublicationImage(\n                        channelEndpoint,\n                        transportIndex,\n                        srcAddress,\n                        streamId,\n                        sessionId,\n                        msg.initialTermId(),\n                        msg.activeTermId(),\n                        msg.termOffset(),\n                        msg.termLength(),\n                        msg.mtuLength(),\n                        msg.ttl(),\n                        msg.flags());\n                }\n            }\n            else\n            {\n                if (streamInterest.isAllSessions || streamInterest.subscribedSessionIds.contains(sessionId))\n                {\n                    streamInterest.sessionInterestByIdMap.put(sessionId, INIT_IN_PROGRESS);\n                    createPublicationImage(\n                        channelEndpoint,\n                        transportIndex,\n                        srcAddress,\n                        streamId,\n                        sessionId,\n                        msg.initialTermId(),\n                        msg.activeTermId(),\n                        msg.termOffset(),\n                        msg.termLength(),\n                        msg.mtuLength(),\n                        msg.ttl(),\n                        msg.flags());\n                }\n                else\n                {\n                    streamInterest.sessionInterestByIdMap.put(sessionId, NO_INTEREST);\n                }\n            }\n        }\n    }\n\n    /**\n     * Dispatch an RTT measurement message to registered interest.\n     *\n     * @param channelEndpoint of reception.\n     * @param msg             flyweight over the network packet.\n     * @param srcAddress      the message came from.\n     * @param transportIndex  on which the message was received.\n     */\n    public void onRttMeasurement(\n        final ReceiveChannelEndpoint channelEndpoint,\n        final RttMeasurementFlyweight msg,\n        final InetSocketAddress srcAddress,\n        final int transportIndex)\n    {\n        final int streamId = msg.streamId();\n        final StreamInterest streamInterest = streamInterestByIdMap.get(streamId);\n\n        if (null != streamInterest)\n        {\n            final int sessionId = msg.sessionId();\n\n            final PublicationImage image = streamInterest.findActive(sessionId);\n            if (null != image)\n            {\n                if (RttMeasurementFlyweight.REPLY_FLAG == (msg.flags() & RttMeasurementFlyweight.REPLY_FLAG))\n                {\n                    final InetSocketAddress controlAddress = channelEndpoint.isMulticast(transportIndex) ?\n                        channelEndpoint.udpChannel(transportIndex).remoteControl() : srcAddress;\n\n                    channelEndpoint.sendRttMeasurement(\n                        transportIndex, controlAddress, sessionId, streamId, msg.echoTimestampNs(), 0, false);\n                }\n                else\n                {\n                    image.onRttMeasurement(msg, transportIndex, srcAddress);\n                }\n            }\n        }\n    }\n\n    /**\n     * Should a setup message be elicited for a channel given interest.\n     *\n     * @return true if there is interest otherwise false.\n     */\n    public boolean shouldElicitSetupMessage()\n    {\n        return !streamInterestByIdMap.isEmpty();\n    }\n\n    private void removeByState(final int sessionId, final int streamId, final SessionState state)\n    {\n        final StreamInterest streamInterest = streamInterestByIdMap.get(streamId);\n        if (null != streamInterest)\n        {\n            final SessionState sessionState = streamInterest.sessionInterestByIdMap.get(sessionId);\n            if (null != sessionState && state == sessionState)\n            {\n                streamInterest.sessionInterestByIdMap.remove(sessionId);\n            }\n        }\n    }\n\n    private void elicitSetupMessageFromSource(\n        final ReceiveChannelEndpoint channelEndpoint,\n        final int transportIndex,\n        final InetSocketAddress srcAddress,\n        final int streamId,\n        final int sessionId)\n    {\n        final InetSocketAddress controlAddress = channelEndpoint.isMulticast(transportIndex) ?\n            channelEndpoint.udpChannel(transportIndex).remoteControl() : srcAddress;\n\n        channelEndpoint.sendSetupElicitingStatusMessage(transportIndex, controlAddress, sessionId, streamId);\n        receiver.addPendingSetupMessage(sessionId, streamId, transportIndex, channelEndpoint, false, controlAddress);\n    }\n\n    private void createPublicationImage(\n        final ReceiveChannelEndpoint channelEndpoint,\n        final int transportIndex,\n        final InetSocketAddress srcAddress,\n        final int streamId,\n        final int sessionId,\n        final int initialTermId,\n        final int activeTermId,\n        final int termOffset,\n        final int termLength,\n        final int mtuLength,\n        final int setupTtl,\n        final short flags)\n    {\n        final InetSocketAddress controlAddress = channelEndpoint.isMulticast(transportIndex) ?\n            channelEndpoint.udpChannel(transportIndex).remoteControl() : srcAddress;\n\n        if (channelEndpoint.isMulticast(transportIndex) && channelEndpoint.multicastTtl(transportIndex) < setupTtl)\n        {\n            channelEndpoint.possibleTtlAsymmetryEncountered();\n        }\n\n        conductorProxy.createPublicationImage(\n            sessionId,\n            streamId,\n            initialTermId,\n            activeTermId,\n            termOffset,\n            termLength,\n            mtuLength,\n            transportIndex,\n            flags,\n            controlAddress,\n            srcAddress,\n            channelEndpoint);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DefaultAllowTerminationValidator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.DirectBuffer;\n\nimport java.io.File;\n\n/**\n * Default implementation of {@link TerminationValidator} that always allows termination.\n */\npublic class DefaultAllowTerminationValidator implements TerminationValidator\n{\n    /**\n     * Default constructor.\n     */\n    public DefaultAllowTerminationValidator()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean allowTermination(\n        final File aeronDir, final DirectBuffer tokenBuffer, final int tokenOffset, final int tokenLength)\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DefaultCongestionControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CommonContext;\nimport io.aeron.driver.ext.CubicCongestionControl;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Supplier of congestion control algorithms which is aware of channel URI params otherwise defaults to\n * {@link StaticWindowCongestionControl}.\n */\npublic class DefaultCongestionControlSupplier implements CongestionControlSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public DefaultCongestionControlSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public CongestionControl newInstance(\n        final long registrationId,\n        final UdpChannel udpChannel,\n        final int streamId,\n        final int sessionId,\n        final int termLength,\n        final int senderMtuLength,\n        final InetSocketAddress controlAddress,\n        final InetSocketAddress sourceAddress,\n        final NanoClock nanoClock,\n        final MediaDriver.Context context,\n        final CountersManager countersManager)\n    {\n        final String ccStr = udpChannel.channelUri().get(CommonContext.CONGESTION_CONTROL_PARAM_NAME);\n\n        if (null == ccStr || StaticWindowCongestionControl.CC_PARAM_VALUE.equals(ccStr))\n        {\n            return new StaticWindowCongestionControl(\n                registrationId,\n                udpChannel,\n                streamId,\n                sessionId,\n                termLength,\n                senderMtuLength,\n                controlAddress,\n                sourceAddress,\n                nanoClock,\n                context,\n                countersManager);\n        }\n        else if (CubicCongestionControl.CC_PARAM_VALUE.equals(ccStr))\n        {\n            return new CubicCongestionControl(\n                registrationId,\n                udpChannel,\n                streamId,\n                sessionId,\n                termLength,\n                senderMtuLength,\n                controlAddress,\n                sourceAddress,\n                nanoClock,\n                context,\n                countersManager);\n        }\n\n        throw new IllegalArgumentException(\"unsupported congestion control : cc=\" + ccStr);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DefaultDenyTerminationValidator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.DirectBuffer;\n\nimport java.io.File;\n\n/**\n * Default implementation of {@link TerminationValidator} that always denies termination.\n */\npublic class DefaultDenyTerminationValidator implements TerminationValidator\n{\n    /**\n     * Default constructor.\n     */\n    public DefaultDenyTerminationValidator()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean allowTermination(\n        final File aeronDir, final DirectBuffer tokenBuffer, final int tokenOffset, final int tokenLength)\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DefaultMulticastFlowControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CommonContext;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.LangUtil;\n\nimport static io.aeron.driver.Configuration.MULTICAST_FLOW_CONTROL_STRATEGY;\n\n/**\n * Default supplier of {@link FlowControl} strategies for multicast streams which supports defining the strategy in\n * the channel URI as a priority over {@link Configuration#MULTICAST_FLOW_CONTROL_STRATEGY_PROP_NAME}.\n */\npublic class DefaultMulticastFlowControlSupplier implements FlowControlSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public DefaultMulticastFlowControlSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public FlowControl newInstance(final UdpChannel udpChannel, final int streamId, final long registrationId)\n    {\n        final String fcStr = udpChannel.channelUri().get(CommonContext.FLOW_CONTROL_PARAM_NAME);\n        if (null != fcStr)\n        {\n            final int delimiter = fcStr.indexOf(',');\n            final String strategyStr = -1 == delimiter ? fcStr : fcStr.substring(0, delimiter);\n\n            switch (strategyStr)\n            {\n                case MaxMulticastFlowControl.FC_PARAM_VALUE:\n                    return new MaxMulticastFlowControl();\n\n                case MinMulticastFlowControl.FC_PARAM_VALUE:\n                    return new MinMulticastFlowControl();\n\n                case TaggedMulticastFlowControl.FC_PARAM_VALUE:\n                    return new TaggedMulticastFlowControl();\n\n                default:\n                    throw new IllegalArgumentException(\"unsupported multicast flow control strategy: fc=\" + fcStr);\n            }\n        }\n\n        if (MaxMulticastFlowControl.class.getName().equals(MULTICAST_FLOW_CONTROL_STRATEGY))\n        {\n            return new MaxMulticastFlowControl();\n        }\n        else if (MinMulticastFlowControl.class.getName().equals(MULTICAST_FLOW_CONTROL_STRATEGY))\n        {\n            return new MinMulticastFlowControl();\n        }\n        else if (TaggedMulticastFlowControl.class.getName().equals(MULTICAST_FLOW_CONTROL_STRATEGY))\n        {\n            return new TaggedMulticastFlowControl();\n        }\n\n        FlowControl flowControl = null;\n        try\n        {\n            flowControl = (FlowControl)Class.forName(MULTICAST_FLOW_CONTROL_STRATEGY)\n                .getConstructor()\n                .newInstance();\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return flowControl;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"DefaultMulticastFlowControlSupplier{flowControlClass=\" +\n            MULTICAST_FLOW_CONTROL_STRATEGY + \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DefaultNameResolver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\n/**\n * Use the default host name resolver via {@link InetAddress}.\n */\npublic class DefaultNameResolver implements NameResolver\n{\n    /**\n     * Default constructor.\n     */\n    public DefaultNameResolver()\n    {\n    }\n\n    /**\n     * Singleton instance which can be used to avoid allocation.\n     */\n    public static final DefaultNameResolver INSTANCE = new DefaultNameResolver();\n\n    /**\n     * {@inheritDoc}\n     */\n    public InetAddress resolve(final String name, final String uriParamName, final boolean isReResolution)\n    {\n        InetAddress resolvedAddress = null;\n        try\n        {\n            resolvedAddress = InetAddress.getByName(name);\n        }\n        catch (final UnknownHostException ignore)\n        {\n        }\n\n        return resolvedAddress;\n    }\n\n    /**\n     * Name resolution hook, useful for logging.\n     *\n     * @param resolverName    used to handle the resolution.\n     * @param hostname        that was resolved.\n     * @param resolvedAddress the resulting address or null if it can't be resolved.\n     * @deprecated No longer used for logging.\n     */\n    @Deprecated\n    public void resolveHook(final String resolverName, final String hostname, final InetAddress resolvedAddress)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DefaultReceiveChannelEndpointSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Supply the default implementation of the {@link ReceiveChannelEndpoint}.\n */\npublic class DefaultReceiveChannelEndpointSupplier implements ReceiveChannelEndpointSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public DefaultReceiveChannelEndpointSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public ReceiveChannelEndpoint newInstance(\n        final UdpChannel udpChannel,\n        final DataPacketDispatcher dispatcher,\n        final AtomicCounter statusIndicator,\n        final MediaDriver.Context context)\n    {\n        return new ReceiveChannelEndpoint(udpChannel, dispatcher, statusIndicator, context);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DefaultSendChannelEndpointSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Supply the default implementation of the {@link SendChannelEndpoint}.\n */\npublic class DefaultSendChannelEndpointSupplier implements SendChannelEndpointSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public DefaultSendChannelEndpointSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public SendChannelEndpoint newInstance(\n        final UdpChannel udpChannel, final AtomicCounter statusIndicator, final MediaDriver.Context context)\n    {\n        return new SendChannelEndpoint(udpChannel, statusIndicator, context);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DefaultUnicastFlowControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CommonContext;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.LangUtil;\n\nimport static io.aeron.driver.Configuration.UNICAST_FLOW_CONTROL_STRATEGY;\n\n/**\n * Default supplier of {@link FlowControl} strategies for unicast streams via\n * {@link Configuration#UNICAST_FLOW_CONTROL_STRATEGY_PROP_NAME}.\n */\npublic class DefaultUnicastFlowControlSupplier implements FlowControlSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public DefaultUnicastFlowControlSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public FlowControl newInstance(final UdpChannel udpChannel, final int streamId, final long registrationId)\n    {\n        final String fcStr = udpChannel.channelUri().get(CommonContext.FLOW_CONTROL_PARAM_NAME);\n        FlowControl flowControl = null;\n\n        if (null != fcStr)\n        {\n            throw new IllegalArgumentException(\"unsupported unicast flow control strategy: fc=\" + fcStr);\n        }\n\n        if (UnicastFlowControl.class.getName().equals(UNICAST_FLOW_CONTROL_STRATEGY))\n        {\n            return new UnicastFlowControl();\n        }\n\n        try\n        {\n            flowControl = (FlowControl)Class.forName(UNICAST_FLOW_CONTROL_STRATEGY)\n                .getConstructor()\n                .newInstance();\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return flowControl;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"DefaultUnicastFlowControlSupplier{flowControlClass=\" +\n            UNICAST_FLOW_CONTROL_STRATEGY + \"}\";\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DriverConductor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.ErrorCode;\nimport io.aeron.driver.MediaDriver.Context;\nimport io.aeron.driver.buffer.LogFactory;\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.exceptions.InvalidChannelException;\nimport io.aeron.driver.media.ControlMode;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.ReceiveDestinationTransport;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.status.ClientHeartbeatTimestamp;\nimport io.aeron.driver.status.PublisherLimit;\nimport io.aeron.driver.status.PublisherPos;\nimport io.aeron.driver.status.ReceiveChannelStatus;\nimport io.aeron.driver.status.ReceiveLocalSocketAddress;\nimport io.aeron.driver.status.ReceiverHwm;\nimport io.aeron.driver.status.ReceiverNaksSent;\nimport io.aeron.driver.status.ReceiverPos;\nimport io.aeron.driver.status.SendChannelStatus;\nimport io.aeron.driver.status.SendLocalSocketAddress;\nimport io.aeron.driver.status.SenderBpe;\nimport io.aeron.driver.status.SenderLimit;\nimport io.aeron.driver.status.SenderNaksReceived;\nimport io.aeron.driver.status.SenderPos;\nimport io.aeron.driver.status.SubscriberPos;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.exceptions.AeronEvent;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ControlProtocolException;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.Object2ObjectHashMap;\nimport org.agrona.collections.ObjectHashSet;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.ManyToOneConcurrentLinkedQueue;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.agrona.concurrent.status.Position;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\n\nimport java.net.InetSocketAddress;\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Objects;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.ChannelUri.SPY_QUALIFIER;\nimport static io.aeron.CommonContext.CHANNEL_RECEIVE_TIMESTAMP_OFFSET_PARAM_NAME;\nimport static io.aeron.CommonContext.CHANNEL_SEND_TIMESTAMP_OFFSET_PARAM_NAME;\nimport static io.aeron.CommonContext.CONTROL_MODE_RESPONSE;\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.CommonContext.IPC_MEDIA;\nimport static io.aeron.CommonContext.InferableBoolean;\nimport static io.aeron.CommonContext.InferableBoolean.FORCE_TRUE;\nimport static io.aeron.CommonContext.InferableBoolean.INFER;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_PARAM_NAME;\nimport static io.aeron.CommonContext.MDC_CONTROL_PARAM_NAME;\nimport static io.aeron.CommonContext.MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME;\nimport static io.aeron.CommonContext.MTU_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.RECEIVER_WINDOW_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.RESPONSE_CORRELATION_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.SOCKET_RCVBUF_PARAM_NAME;\nimport static io.aeron.CommonContext.SOCKET_SNDBUF_PARAM_NAME;\nimport static io.aeron.ErrorCode.GENERIC_ERROR;\nimport static io.aeron.ErrorCode.UNKNOWN_COUNTER;\nimport static io.aeron.ErrorCode.UNKNOWN_PUBLICATION;\nimport static io.aeron.ErrorCode.UNKNOWN_SUBSCRIPTION;\nimport static io.aeron.driver.PublicationParams.PROTOTYPE_VALUE_CORRELATION_ID;\nimport static io.aeron.driver.PublicationParams.confirmMatch;\nimport static io.aeron.driver.PublicationParams.getPublicationParams;\nimport static io.aeron.driver.PublicationParams.validateMtuForSndbuf;\nimport static io.aeron.driver.PublicationParams.validateSpiesSimulateConnection;\nimport static io.aeron.driver.SubscriptionParams.validateInitialWindowForRcvBuf;\nimport static io.aeron.driver.status.SystemCounterDescriptor.ERRORS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.FREE_FAILS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.IMAGES_REJECTED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.INVALID_PACKETS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.RESOLUTION_CHANGES;\nimport static io.aeron.driver.status.SystemCounterDescriptor.RETRANSMIT_OVERFLOW;\nimport static io.aeron.driver.status.SystemCounterDescriptor.UNBLOCKED_COMMANDS;\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_BUFFER_TYPE_CONCURRENT_PUBLICATION;\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_BUFFER_TYPE_EXCLUSIVE_PUBLICATION;\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_BUFFER_TYPE_PUBLICATION_IMAGE;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PARTITION_COUNT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.activeTermCount;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.correlationId;\nimport static io.aeron.logbuffer.LogBufferDescriptor.endOfStreamPosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.entityTag;\nimport static io.aeron.logbuffer.LogBufferDescriptor.group;\nimport static io.aeron.logbuffer.LogBufferDescriptor.indexByTerm;\nimport static io.aeron.logbuffer.LogBufferDescriptor.initialTermId;\nimport static io.aeron.logbuffer.LogBufferDescriptor.initialiseTailWithTermId;\nimport static io.aeron.logbuffer.LogBufferDescriptor.isPublicationRevoked;\nimport static io.aeron.logbuffer.LogBufferDescriptor.isResponse;\nimport static io.aeron.logbuffer.LogBufferDescriptor.lingerTimeoutNs;\nimport static io.aeron.logbuffer.LogBufferDescriptor.maxResend;\nimport static io.aeron.logbuffer.LogBufferDescriptor.mtuLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.nextPartitionIndex;\nimport static io.aeron.logbuffer.LogBufferDescriptor.osDefaultSocketRcvbufLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.osDefaultSocketSndbufLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.osMaxSocketRcvbufLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.osMaxSocketSndbufLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.packTail;\nimport static io.aeron.logbuffer.LogBufferDescriptor.pageSize;\nimport static io.aeron.logbuffer.LogBufferDescriptor.positionBitsToShift;\nimport static io.aeron.logbuffer.LogBufferDescriptor.publicationWindowLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.rawTail;\nimport static io.aeron.logbuffer.LogBufferDescriptor.receiverWindowLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.rejoin;\nimport static io.aeron.logbuffer.LogBufferDescriptor.reliable;\nimport static io.aeron.logbuffer.LogBufferDescriptor.responseCorrelationId;\nimport static io.aeron.logbuffer.LogBufferDescriptor.signalEos;\nimport static io.aeron.logbuffer.LogBufferDescriptor.socketRcvbufLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.socketSndbufLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.sparse;\nimport static io.aeron.logbuffer.LogBufferDescriptor.spiesSimulateConnection;\nimport static io.aeron.logbuffer.LogBufferDescriptor.storeDefaultFrameHeader;\nimport static io.aeron.logbuffer.LogBufferDescriptor.termLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.tether;\nimport static io.aeron.logbuffer.LogBufferDescriptor.type;\nimport static io.aeron.logbuffer.LogBufferDescriptor.untetheredLingerTimeoutNs;\nimport static io.aeron.logbuffer.LogBufferDescriptor.untetheredRestingTimeoutNs;\nimport static io.aeron.logbuffer.LogBufferDescriptor.untetheredWindowLimitTimeoutNs;\nimport static io.aeron.protocol.DataHeaderFlyweight.createDefaultHeader;\nimport static org.agrona.collections.ArrayListUtil.fastUnorderedRemove;\n\n/**\n * Driver Conductor that takes commands from publishers and subscribers, and orchestrates the media driver.\n */\npublic final class DriverConductor implements Agent\n{\n    private static final long CLOCK_UPDATE_INTERNAL_NS = TimeUnit.MILLISECONDS.toNanos(1);\n    private static final String[] INVALID_DESTINATION_KEYS = {\n        MTU_LENGTH_PARAM_NAME,\n        RECEIVER_WINDOW_LENGTH_PARAM_NAME,\n        SOCKET_RCVBUF_PARAM_NAME,\n        SOCKET_SNDBUF_PARAM_NAME,\n        RESPONSE_CORRELATION_ID_PARAM_NAME\n    };\n\n    static final long EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS = 1;\n\n    private int nextSessionId = BitUtil.generateRandomisedId();\n    private final long timerIntervalNs;\n    private final long clientLivenessTimeoutNs;\n    private long timeOfLastToDriverPositionChangeNs;\n    private long lastCommandConsumerPosition;\n    private long timerCheckDeadlineNs;\n    private long clockUpdateDeadlineNs;\n\n    private final Context ctx;\n    private final LogFactory logFactory;\n    private final ReceiverProxy receiverProxy;\n    private final SenderProxy senderProxy;\n    private final ClientProxy clientProxy;\n    private final RingBuffer toDriverCommands;\n    private final ClientCommandAdapter clientCommandAdapter;\n    private final ManyToOneConcurrentLinkedQueue<Runnable> driverCmdQueue;\n    private final Object2ObjectHashMap<String, SendChannelEndpoint> sendChannelEndpointByChannelMap =\n        new Object2ObjectHashMap<>();\n    private final Object2ObjectHashMap<String, ReceiveChannelEndpoint> receiveChannelEndpointByChannelMap =\n        new Object2ObjectHashMap<>();\n    private final ArrayList<NetworkPublication> networkPublications = new ArrayList<>();\n    private final ArrayList<IpcPublication> ipcPublications = new ArrayList<>();\n    private final ArrayList<PublicationImage> publicationImages = new ArrayList<>();\n    private final ArrayList<PublicationLink> publicationLinks = new ArrayList<>();\n    private final ArrayList<SubscriptionLink> subscriptionLinks = new ArrayList<>();\n    private final ArrayList<CounterLink> counterLinks = new ArrayList<>();\n    private final ArrayList<AeronClient> clients = new ArrayList<>();\n    private final ArrayDeque<DriverManagedResource> endOfLifeResources = new ArrayDeque<>();\n    private final ObjectHashSet<SessionKey> activeSessionSet = new ObjectHashSet<>();\n    private final EpochClock epochClock;\n    private final NanoClock nanoClock;\n    private final CachedEpochClock cachedEpochClock;\n    private final CachedNanoClock cachedNanoClock;\n    private final CountersManager countersManager;\n    private final NetworkPublicationThreadLocals networkPublicationThreadLocals = new NetworkPublicationThreadLocals();\n    private final MutableDirectBuffer tempBuffer;\n    private final DataHeaderFlyweight defaultDataHeader = new DataHeaderFlyweight(createDefaultHeader(0, 0, 0));\n    private final AtomicCounter errorCounter;\n    private final AtomicCounter imagesRejected;\n    private final DutyCycleTracker dutyCycleTracker;\n    private final Executor asyncTaskExecutor;\n    private final boolean asyncExecutionDisabled;\n    private boolean asyncClientCommandInFlight;\n    private TimeTrackingNameResolver nameResolver;\n\n    DriverConductor(final MediaDriver.Context ctx)\n    {\n        this.ctx = ctx;\n        timerIntervalNs = ctx.timerIntervalNs();\n        clientLivenessTimeoutNs = ctx.clientLivenessTimeoutNs();\n        driverCmdQueue = ctx.driverCommandQueue();\n        receiverProxy = ctx.receiverProxy();\n        senderProxy = ctx.senderProxy();\n        logFactory = ctx.logFactory();\n        epochClock = ctx.epochClock();\n        nanoClock = ctx.nanoClock();\n        cachedEpochClock = ctx.cachedEpochClock();\n        cachedNanoClock = ctx.cachedNanoClock();\n        toDriverCommands = ctx.toDriverCommands();\n        clientProxy = ctx.clientProxy();\n        tempBuffer = ctx.tempBuffer();\n        errorCounter = ctx.systemCounters().get(ERRORS);\n        imagesRejected = ctx.systemCounters().get(IMAGES_REJECTED);\n        dutyCycleTracker = ctx.conductorDutyCycleTracker();\n\n        asyncTaskExecutor = ctx.asyncTaskExecutor();\n        asyncExecutionDisabled = ctx.asyncTaskExecutorThreads() <= 0;\n\n        countersManager = ctx.countersManager();\n\n        clientCommandAdapter = new ClientCommandAdapter(\n            errorCounter,\n            ctx.errorHandler(),\n            toDriverCommands,\n            clientProxy,\n            this);\n\n        lastCommandConsumerPosition = toDriverCommands.consumerPosition();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        final long nowNs = nanoClock.nanoTime();\n        cachedNanoClock.update(nowNs);\n        cachedEpochClock.update(epochClock.time());\n        dutyCycleTracker.update(nowNs);\n        timerCheckDeadlineNs = nowNs + timerIntervalNs;\n        clockUpdateDeadlineNs = nowNs + CLOCK_UPDATE_INTERNAL_NS;\n        timeOfLastToDriverPositionChangeNs = nowNs;\n\n        nameResolver = new TimeTrackingNameResolver(\n            null == ctx.resolverInterface() ? ctx.nameResolver() : new DriverNameResolver(ctx),\n            nanoClock,\n            ctx.nameResolverTimeTracker());\n\n        final SystemCounters systemCounters = ctx.systemCounters();\n        systemCounters.get(RESOLUTION_CHANGES).appendToLabel(\": driverName=\" + ctx.resolverName());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        if (asyncTaskExecutor instanceof ExecutorService)\n        {\n            try\n            {\n                final ExecutorService executor = (ExecutorService)asyncTaskExecutor;\n                executor.shutdownNow();\n                if (!executor.awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS))\n                {\n                    ctx.errorHandler().onError(new AeronEvent(\"failed to shutdown async task executor\"));\n                }\n            }\n            catch (final Exception e)\n            {\n                ctx.errorHandler().onError(e);\n            }\n        }\n        CloseHelper.close(ctx.errorHandler(), nameResolver);\n        CloseHelper.closeAll(receiveChannelEndpointByChannelMap.values());\n        CloseHelper.closeAll(sendChannelEndpointByChannelMap.values());\n        publicationImages.forEach(PublicationImage::free);\n        networkPublications.forEach(NetworkPublication::free);\n        ipcPublications.forEach(IpcPublication::free);\n        freeEndOfLifeResources(Integer.MAX_VALUE);\n        toDriverCommands.consumerHeartbeatTime(NULL_VALUE);\n        ctx.cncByteBuffer().force();\n        ctx.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"driver-conductor\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        final long nowNs = nanoClock.nanoTime();\n        trackTime(nowNs);\n\n        int workCount = 0;\n        workCount += processTimers(nowNs);\n        if (!asyncClientCommandInFlight)\n        {\n            workCount += clientCommandAdapter.receive();\n        }\n        workCount += drainCommandQueue();\n        workCount += trackStreamPositions(workCount, nowNs);\n        workCount += nameResolver.doWork(cachedEpochClock.time());\n        workCount += freeEndOfLifeResources(ctx.resourceFreeLimit());\n\n        return workCount;\n    }\n\n    boolean notAcceptingClientCommands()\n    {\n        return senderProxy.isApplyingBackpressure() || receiverProxy.isApplyingBackpressure();\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    void onCreatePublicationImage(\n        final int sessionId,\n        final int streamId,\n        final int initialTermId,\n        final int activeTermId,\n        final int termOffset,\n        final int termBufferLength,\n        final int senderMtuLength,\n        final int transportIndex,\n        final short flags,\n        final InetSocketAddress controlAddress,\n        final InetSocketAddress sourceAddress,\n        final ReceiveChannelEndpoint channelEndpoint)\n    {\n        Configuration.validateMtuLength(senderMtuLength);\n\n        final UdpChannel subscriptionChannel = channelEndpoint.subscriptionUdpChannel();\n\n        final SubscriptionParams subscriptionParams =\n            SubscriptionParams.getSubscriptionParams(subscriptionChannel.channelUri(), ctx, termBufferLength);\n\n        Configuration.validateInitialWindowLength(subscriptionParams.receiverWindowLength, senderMtuLength);\n\n        final long joinPosition = computePosition(\n            activeTermId, termOffset, LogBufferDescriptor.positionBitsToShift(termBufferLength), initialTermId);\n        final ArrayList<SubscriberPosition> subscriberPositions = createSubscriberPositions(\n            sessionId, streamId, channelEndpoint, joinPosition);\n\n        if (!subscriberPositions.isEmpty())\n        {\n            RawLog rawLog = null;\n            CongestionControl congestionControl = null;\n            UnsafeBufferPosition hwmPos = null;\n            UnsafeBufferPosition rcvPos = null;\n            AtomicCounter rcvNaksSent = null;\n\n            try\n            {\n                final long registrationId = toDriverCommands.nextCorrelationId();\n                final SubscriptionLink subscription = subscriberPositions.get(0).subscription();\n                final boolean isMulticastSemantics =\n                    isMulticastSemantics(subscriptionChannel, subscription.group(), flags);\n                final boolean isReliable = subscription.isReliable();\n                final boolean isSparse = isOldestSubscriptionSparse(subscriberPositions);\n\n                rawLog = newPublicationImageLog(\n                    sessionId,\n                    streamId,\n                    initialTermId,\n                    termBufferLength,\n                    isReliable,\n                    isSparse,\n                    senderMtuLength,\n                    channelEndpoint.socketRcvbufLength(),\n                    channelEndpoint.socketSndbufLength(),\n                    termOffset,\n                    subscriptionParams,\n                    registrationId,\n                    isMulticastSemantics);\n\n                congestionControl = ctx.congestionControlSupplier().newInstance(\n                    registrationId,\n                    subscriptionChannel,\n                    streamId,\n                    sessionId,\n                    termBufferLength,\n                    senderMtuLength,\n                    controlAddress,\n                    sourceAddress,\n                    ctx.receiverCachedNanoClock(),\n                    ctx,\n                    countersManager);\n\n                final String uri = subscription.channel();\n                final long clientId = subscription.aeronClient().clientId();\n                hwmPos = ReceiverHwm.allocate(\n                    tempBuffer, countersManager, clientId, registrationId, sessionId, streamId, uri);\n                rcvPos = ReceiverPos.allocate(\n                    tempBuffer, countersManager, clientId, registrationId, sessionId, streamId, uri);\n                rcvNaksSent = ReceiverNaksSent.allocate(\n                    tempBuffer, countersManager, clientId, registrationId, sessionId, streamId, uri);\n\n                final String sourceIdentity = Configuration.sourceIdentity(sourceAddress);\n\n                final PublicationImage image = new PublicationImage(\n                    registrationId,\n                    ctx,\n                    channelEndpoint,\n                    transportIndex,\n                    controlAddress,\n                    sessionId,\n                    streamId,\n                    initialTermId,\n                    activeTermId,\n                    termOffset,\n                    flags,\n                    isReliable,\n                    subscriptionParams.untetheredWindowLimitTimeoutNs,\n                    subscriptionParams.untetheredLingerTimeoutNs,\n                    subscriptionParams.untetheredRestingTimeoutNs,\n                    rawLog,\n                    resolveDelayGenerator(ctx, subscriptionChannel, isMulticastSemantics, isReliable),\n                    subscriberPositions,\n                    hwmPos,\n                    rcvPos,\n                    rcvNaksSent,\n                    sourceIdentity,\n                    congestionControl);\n\n                channelEndpoint.incRefImages();\n                publicationImages.add(image);\n                receiverProxy.newPublicationImage(channelEndpoint, image);\n\n                for (int i = 0, size = subscriberPositions.size(); i < size; i++)\n                {\n                    final SubscriberPosition position = subscriberPositions.get(i);\n                    position.addLink(image);\n\n                    final int positionCounterId = position.positionCounterId();\n                    countersManager.setCounterReferenceId(positionCounterId, registrationId);\n\n                    clientProxy.onAvailableImage(\n                        registrationId,\n                        streamId,\n                        sessionId,\n                        position.subscription().registrationId(),\n                        positionCounterId,\n                        rawLog.fileName(),\n                        sourceIdentity);\n                }\n            }\n            catch (final Exception ex)\n            {\n                subscriberPositions.forEach((subscriberPosition) -> subscriberPosition.position().close());\n                CloseHelper.quietCloseAll(rawLog, congestionControl, hwmPos, rcvPos, rcvNaksSent);\n                throw ex;\n            }\n        }\n    }\n\n    void onPublicationError(\n        final long registrationId,\n        final long destinationRegistrationId,\n        final int sessionId,\n        final int streamId,\n        final long receiverId,\n        final long groupId,\n        final InetSocketAddress srcAddress,\n        final int errorCode,\n        final String errorMessage)\n    {\n        recordError(new AeronEvent(\n            \"onPublicationError: \" +\n            \"registrationId=\" + registrationId +\n            \", destinationRegistrationId=\" + destinationRegistrationId +\n            \", sessionId=\" + sessionId +\n            \", streamId=\" + streamId +\n            \", receiverId=\" + receiverId +\n            \", groupId=\" + groupId +\n            \", errorCode=\" + errorCode +\n            \", errorMessage=\" + errorMessage,\n            AeronException.Category.WARN));\n        clientProxy.onPublicationErrorFrame(\n            registrationId,\n            destinationRegistrationId,\n            sessionId,\n            streamId,\n            receiverId,\n            groupId,\n            srcAddress,\n            errorCode,\n            errorMessage);\n    }\n\n    void onReResolveEndpoint(\n        final String endpoint, final SendChannelEndpoint channelEndpoint, final InetSocketAddress address)\n    {\n        executeAsyncTask(\n            () -> UdpChannel.resolve(endpoint, ENDPOINT_PARAM_NAME, true, nameResolver),\n            (asyncResult) ->\n            {\n                try\n                {\n                    final InetSocketAddress newAddress = asyncResult.get();\n                    if (newAddress.isUnresolved())\n                    {\n                        recordError(new AeronEvent(\"could not re-resolve: endpoint=\" + endpoint));\n                    }\n                    else if (!address.equals(newAddress))\n                    {\n                        senderProxy.onResolutionChange(channelEndpoint, endpoint, newAddress);\n                    }\n                }\n                catch (final Exception ex)\n                {\n                    recordError(ex);\n                }\n            });\n    }\n\n    void onReResolveControl(\n        final String control,\n        final UdpChannel udpChannel,\n        final ReceiveChannelEndpoint channelEndpoint,\n        final InetSocketAddress address)\n    {\n        executeAsyncTask(\n            () -> UdpChannel.resolve(control, MDC_CONTROL_PARAM_NAME, true, nameResolver),\n            (asyncResult) ->\n            {\n                try\n                {\n                    final InetSocketAddress newAddress = asyncResult.get();\n                    if (newAddress.isUnresolved())\n                    {\n                        recordError(new AeronEvent(\"could not re-resolve: control=\" + control));\n                    }\n                    else if (!address.equals(newAddress))\n                    {\n                        receiverProxy.onResolutionChange(channelEndpoint, udpChannel, newAddress);\n                    }\n                }\n                catch (final Exception ex)\n                {\n                    recordError(ex);\n                }\n            });\n    }\n\n    IpcPublication getSharedIpcPublication(final long streamId, final long responseCorrelationId)\n    {\n        return findSharedIpcPublication(ipcPublications, streamId, responseCorrelationId);\n    }\n\n    IpcPublication getIpcPublication(final long registrationId)\n    {\n        for (int i = 0, size = ipcPublications.size(); i < size; i++)\n        {\n            final IpcPublication publication = ipcPublications.get(i);\n            if (publication.registrationId() == registrationId)\n            {\n                return publication;\n            }\n        }\n\n        return null;\n    }\n\n    NetworkPublication findNetworkPublicationByTag(final long tag)\n    {\n        for (int i = 0, size = networkPublications.size(); i < size; i++)\n        {\n            final NetworkPublication publication = networkPublications.get(i);\n            final long publicationTag = publication.tag();\n            if (publicationTag == tag && publicationTag != ChannelUri.INVALID_TAG)\n            {\n                return publication;\n            }\n        }\n\n        return null;\n    }\n\n    IpcPublication findIpcPublicationByTag(final long tag)\n    {\n        for (int i = 0, size = ipcPublications.size(); i < size; i++)\n        {\n            final IpcPublication publication = ipcPublications.get(i);\n            final long publicationTag = publication.tag();\n            if (publicationTag == tag && publicationTag != ChannelUri.INVALID_TAG)\n            {\n                return publication;\n            }\n        }\n\n        return null;\n    }\n\n    void onAddNetworkPublication(\n        final String channel,\n        final int streamId,\n        final long correlationId,\n        final long clientId,\n        final boolean isExclusive)\n    {\n        executeAsyncClientTask(\n            correlationId,\n            () -> UdpChannel.parse(channel, nameResolver, false),\n            (asyncResult) ->\n            {\n                final UdpChannel udpChannel = asyncResult.get();\n                final ChannelUri channelUri = udpChannel.channelUri();\n                final PublicationParams params =\n                    getPublicationParams(channelUri, ctx, this, streamId, udpChannel.canonicalForm());\n                validateExperimentalFeatures(ctx.enableExperimentalFeatures(), udpChannel);\n                validateEndpointForPublication(udpChannel);\n                validateControlForPublication(udpChannel);\n                validateResponseSubscription(params);\n\n                final SendChannelEndpoint channelEndpoint =\n                    getOrCreateSendChannelEndpoint(params, udpChannel, correlationId);\n\n                NetworkPublication publication = null;\n                if (!isExclusive)\n                {\n                    publication =\n                        findPublication(networkPublications, streamId, channelEndpoint, params.responseCorrelationId);\n                }\n\n                final PublicationImage responsePublicationImage = findResponsePublicationImage(params);\n\n                boolean isNewPublication = false;\n                if (null == publication)\n                {\n                    checkForSessionClash(params.sessionId, streamId, udpChannel.canonicalForm(), channel);\n                    publication = newNetworkPublication(\n                        correlationId, clientId, streamId, channel, udpChannel, channelEndpoint, params, isExclusive);\n                    isNewPublication = true;\n                }\n                else\n                {\n                    confirmMatch(\n                        channelUri,\n                        params,\n                        publication.rawLog(),\n                        publication.sessionId(),\n                        publication.channel(),\n                        publication.initialTermId(),\n                        publication.startingTermId(),\n                        publication.startingTermOffset());\n\n                    validateSpiesSimulateConnection(\n                        params, publication.spiesSimulateConnection(), channel, publication.channel());\n                }\n\n                publicationLinks.add(new PublicationLink(correlationId, getOrAddClient(clientId), publication));\n\n                clientProxy.onPublicationReady(\n                    correlationId,\n                    publication.registrationId(),\n                    streamId,\n                    publication.sessionId(),\n                    publication.rawLog().fileName(),\n                    publication.publisherLimitId(),\n                    channelEndpoint.statusIndicatorCounterId(),\n                    isExclusive);\n\n                if (isNewPublication)\n                {\n                    linkSpies(subscriptionLinks, publication);\n                }\n\n                if (null != responsePublicationImage)\n                {\n                    responsePublicationImage.responseSessionId(publication.sessionId());\n                }\n            });\n    }\n\n    private PublicationImage findResponsePublicationImage(final PublicationParams params)\n    {\n        if (!params.isResponse)\n        {\n            return null;\n        }\n\n        if (NULL_VALUE == params.responseCorrelationId)\n        {\n            throw new IllegalArgumentException(\n                \"control-mode=response was specified, but no response-correlation-id set\");\n        }\n\n        if (PROTOTYPE_VALUE_CORRELATION_ID == params.responseCorrelationId)\n        {\n            return null;\n        }\n\n        for (final PublicationImage publicationImage : publicationImages)\n        {\n            if (publicationImage.correlationId() == params.responseCorrelationId)\n            {\n                if (publicationImage.hasSendResponseSetup())\n                {\n                    return publicationImage;\n                }\n                else\n                {\n                    throw new IllegalArgumentException(\n                        \"image.correlationId=\" + params.responseCorrelationId + \" did not request a response channel\");\n                }\n            }\n        }\n\n        throw new IllegalArgumentException(\"image.correlationId=\" + params.responseCorrelationId + \" not found\");\n    }\n\n    private PublicationImage findPublicationImage(final long correlationId)\n    {\n        for (final PublicationImage publicationImage : publicationImages)\n        {\n            if (correlationId == publicationImage.correlationId())\n            {\n                return publicationImage;\n            }\n        }\n\n        return null;\n    }\n\n    void responseSetup(final long responseCorrelationId, final int responseSessionId)\n    {\n        for (int i = 0, subscriptionLinksSize = subscriptionLinks.size(); i < subscriptionLinksSize; i++)\n        {\n            final SubscriptionLink subscriptionLink = subscriptionLinks.get(i);\n            if (subscriptionLink.registrationId() == responseCorrelationId &&\n                subscriptionLink instanceof final NetworkSubscriptionLink link)\n            {\n                if (subscriptionLink.hasSessionId())\n                {\n                    receiverProxy.requestSetup(\n                        subscriptionLink.channelEndpoint(), subscriptionLink.streamId(), subscriptionLink.sessionId());\n                }\n                else\n                {\n                    link.sessionId(responseSessionId);\n                    addNetworkSubscriptionToReceiver(link);\n                    link.channelEndpoint().decResponseRefToStream(subscriptionLink.streamId);\n                }\n\n                break;\n            }\n        }\n    }\n\n    void responseConnected(final long responseCorrelationId)\n    {\n        for (final PublicationImage publicationImage : publicationImages)\n        {\n            if (publicationImage.correlationId() == responseCorrelationId)\n            {\n                if (publicationImage.hasSendResponseSetup())\n                {\n                    publicationImage.responseSessionId(null);\n                }\n            }\n        }\n    }\n\n    private void validateResponseSubscription(final PublicationParams params)\n    {\n        if (!params.isResponse && NULL_VALUE != params.responseCorrelationId)\n        {\n            for (final SubscriptionLink subscriptionLink : subscriptionLinks)\n            {\n                if (params.responseCorrelationId == subscriptionLink.registrationId())\n                {\n                    return;\n                }\n            }\n\n            throw new IllegalArgumentException(\n                \"unable to find response subscription for response-correlation-id=\" + params.responseCorrelationId);\n        }\n    }\n\n    void cleanupSpies(final NetworkPublication publication)\n    {\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            final SubscriptionLink link = subscriptionLinks.get(i);\n            if (link.isLinked(publication))\n            {\n                notifyUnavailableImageLink(publication.registrationId(), link);\n                link.unlink(publication);\n            }\n        }\n    }\n\n    void notifyUnavailableImageLink(final long resourceId, final SubscriptionLink link)\n    {\n        clientProxy.onUnavailableImage(resourceId, link.registrationId(), link.streamId(), link.channel());\n    }\n\n    void notifyAvailableImageLink(\n        final long resourceId,\n        final int sessionId,\n        final SubscriptionLink link,\n        final int positionCounterId,\n        final long joinPosition,\n        final String logFileName,\n        final String sourceIdentity)\n    {\n        countersManager.setCounterValue(positionCounterId, joinPosition);\n\n        final int streamId = link.streamId();\n        clientProxy.onAvailableImage(\n            resourceId, streamId, sessionId, link.registrationId(), positionCounterId, logFileName, sourceIdentity);\n    }\n\n    void cleanupPublication(final NetworkPublication publication)\n    {\n        senderProxy.removeNetworkPublication(publication);\n\n        final SendChannelEndpoint channelEndpoint = publication.channelEndpoint();\n        if (channelEndpoint.shouldBeClosed())\n        {\n            senderProxy.closeSendChannelEndpoint(channelEndpoint);\n        }\n\n        final String channel = channelEndpoint.udpChannel().canonicalForm();\n        activeSessionSet.remove(new SessionKey(publication.sessionId(), publication.streamId(), channel));\n    }\n\n    void sendChannelEndpointClosed(final SendChannelEndpoint channelEndpoint)\n    {\n        final String channel = channelEndpoint.udpChannel().canonicalForm();\n        sendChannelEndpointByChannelMap.remove(channel);\n        channelEndpoint.close();\n    }\n\n    void cleanupSubscriptionLink(final SubscriptionLink subscription)\n    {\n        final ReceiveChannelEndpoint channelEndpoint = subscription.channelEndpoint();\n        if (null != channelEndpoint)\n        {\n            if (subscription.hasSessionId())\n            {\n                if (0 == channelEndpoint.decRefToStreamAndSession(subscription.streamId(), subscription.sessionId()))\n                {\n                    receiverProxy.removeSubscription(\n                        channelEndpoint, subscription.streamId(), subscription.sessionId());\n                }\n            }\n            else if (subscription.isResponse())\n            {\n                channelEndpoint.decResponseRefToStream(subscription.streamId());\n            }\n            else\n            {\n                if (0 == channelEndpoint.decRefToStream(subscription.streamId()))\n                {\n                    receiverProxy.removeSubscription(channelEndpoint, subscription.streamId());\n                }\n            }\n\n            tryCloseReceiveChannelEndpoint(channelEndpoint);\n        }\n    }\n\n    void transitionToLinger(final PublicationImage image)\n    {\n        boolean rejoin = true;\n\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            final SubscriptionLink link = subscriptionLinks.get(i);\n            if (link.isLinked(image))\n            {\n                rejoin = link.isRejoin();\n                notifyUnavailableImageLink(image.correlationId(), link);\n            }\n        }\n\n        if (rejoin)\n        {\n            receiverProxy.removeCoolDown(image.channelEndpoint(), image.sessionId(), image.streamId());\n        }\n    }\n\n    void transitionToLinger(final IpcPublication publication)\n    {\n        activeSessionSet.remove(new SessionKey(publication.sessionId(), publication.streamId(), IPC_MEDIA));\n\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            final SubscriptionLink link = subscriptionLinks.get(i);\n            if (link.isLinked(publication))\n            {\n                notifyUnavailableImageLink(publication.registrationId(), link);\n            }\n        }\n    }\n\n    void cleanupImage(final PublicationImage image)\n    {\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            subscriptionLinks.get(i).unlink(image);\n        }\n    }\n\n    void cleanupIpcPublication(final IpcPublication publication)\n    {\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            subscriptionLinks.get(i).unlink(publication);\n        }\n    }\n\n    void unlinkIpcSubscriptions(final IpcPublication publication)\n    {\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            final SubscriptionLink link = subscriptionLinks.get(i);\n            if (link.isLinked(publication))\n            {\n                notifyUnavailableImageLink(publication.registrationId(), link);\n                link.unlink(publication);\n            }\n        }\n    }\n\n    void tryCloseReceiveChannelEndpoint(final ReceiveChannelEndpoint channelEndpoint)\n    {\n        if (channelEndpoint.shouldBeClosed())\n        {\n            channelEndpoint.indicateClosing();\n            receiverProxy.closeReceiveChannelEndpoint(channelEndpoint);\n        }\n    }\n\n    void receiveChannelEndpointClosed(final ReceiveChannelEndpoint channelEndpoint)\n    {\n        final String channel = channelEndpoint.subscriptionUdpChannel().canonicalForm();\n        receiveChannelEndpointByChannelMap.remove(channel);\n        channelEndpoint.close();\n    }\n\n    void clientTimeout(final long clientId)\n    {\n        clientProxy.onClientTimeout(clientId);\n    }\n\n    void unavailableCounter(final long registrationId, final int counterId)\n    {\n        clientProxy.onUnavailableCounter(registrationId, counterId);\n    }\n\n    void onAddIpcPublication(\n        final String channel,\n        final int streamId,\n        final long correlationId,\n        final long clientId,\n        final boolean isExclusive)\n    {\n        IpcPublication publication = null;\n        final ChannelUri channelUri = parseUri(channel);\n        final PublicationParams params = getPublicationParams(channelUri, ctx, this, streamId, IPC_MEDIA);\n\n        if (!isExclusive)\n        {\n            publication = findSharedIpcPublication(ipcPublications, streamId, params.responseCorrelationId);\n        }\n\n        boolean isNewPublication = false;\n        if (null == publication)\n        {\n            checkForSessionClash(params.sessionId, streamId, IPC_MEDIA, channel);\n            publication = addIpcPublication(correlationId, clientId, streamId, channel, isExclusive, params);\n            isNewPublication = true;\n        }\n        else\n        {\n            confirmMatch(\n                channelUri,\n                params,\n                publication.rawLog(),\n                publication.sessionId(),\n                publication.channel(),\n                publication.initialTermId(),\n                publication.startingTermId(),\n                publication.startingTermOffset());\n        }\n\n        publicationLinks.add(new PublicationLink(correlationId, getOrAddClient(clientId), publication));\n\n        clientProxy.onPublicationReady(\n            correlationId,\n            publication.registrationId(),\n            streamId,\n            publication.sessionId(),\n            publication.rawLog().fileName(),\n            publication.publisherLimitId(),\n            ChannelEndpointStatus.NO_ID_ALLOCATED,\n            isExclusive);\n\n        if (isNewPublication)\n        {\n            linkIpcSubscriptions(publication);\n        }\n    }\n\n    void onRemovePublication(final long registrationId, final long correlationId, final boolean revoke)\n    {\n        PublicationLink publicationLink = null;\n        final ArrayList<PublicationLink> publicationLinks = this.publicationLinks;\n        for (int i = 0, size = publicationLinks.size(); i < size; i++)\n        {\n            final PublicationLink publication = publicationLinks.get(i);\n            if (registrationId == publication.registrationId())\n            {\n                publicationLink = publication;\n                fastUnorderedRemove(publicationLinks, i);\n                break;\n            }\n        }\n\n        if (null == publicationLink)\n        {\n            throw new ControlProtocolException(UNKNOWN_PUBLICATION, \"unknown publication: \" + registrationId);\n        }\n\n        if (revoke)\n        {\n            publicationLink.revoke();\n        }\n        publicationLink.close();\n        clientProxy.operationSucceeded(correlationId);\n    }\n\n    void onAddSendDestination(final long registrationId, final String destinationChannel, final long correlationId)\n    {\n        final ChannelUri channelUri = parseUri(destinationChannel);\n        validateDestinationUri(channelUri, destinationChannel);\n        validateSendDestinationUri(channelUri, destinationChannel);\n\n        SendChannelEndpoint sendChannelEndpoint = null;\n\n        for (int i = 0, size = networkPublications.size(); i < size; i++)\n        {\n            final NetworkPublication publication = networkPublications.get(i);\n\n            if (registrationId == publication.registrationId())\n            {\n                sendChannelEndpoint = publication.channelEndpoint();\n                break;\n            }\n        }\n\n        if (null == sendChannelEndpoint)\n        {\n            throw new ControlProtocolException(UNKNOWN_PUBLICATION, \"unknown publication: \" + registrationId);\n        }\n\n        sendChannelEndpoint.validateAllowsManualControl();\n\n        final InetSocketAddress dstAddress = UdpChannel.destinationAddress(channelUri, nameResolver);\n        senderProxy.addDestination(sendChannelEndpoint, channelUri, dstAddress, correlationId);\n        clientProxy.operationSucceeded(correlationId);\n    }\n\n    void onRemoveSendDestination(final long registrationId, final String destinationChannel, final long correlationId)\n    {\n        SendChannelEndpoint sendChannelEndpoint = null;\n\n        for (int i = 0, size = networkPublications.size(); i < size; i++)\n        {\n            final NetworkPublication publication = networkPublications.get(i);\n\n            if (registrationId == publication.registrationId())\n            {\n                sendChannelEndpoint = publication.channelEndpoint();\n                break;\n            }\n        }\n\n        if (null == sendChannelEndpoint)\n        {\n            throw new ControlProtocolException(UNKNOWN_PUBLICATION, \"unknown publication: \" + registrationId);\n        }\n\n        sendChannelEndpoint.validateAllowsManualControl();\n\n        final ChannelUri channelUri = parseUri(destinationChannel);\n        final InetSocketAddress dstAddress = UdpChannel.destinationAddress(channelUri, nameResolver);\n        senderProxy.removeDestination(sendChannelEndpoint, channelUri, dstAddress);\n        clientProxy.operationSucceeded(correlationId);\n    }\n\n    void onRemoveSendDestination(\n        final long publicationRegistrationId, final long destinationRegistrationId, final long correlationId)\n    {\n        SendChannelEndpoint sendChannelEndpoint = null;\n\n        for (int i = 0, size = networkPublications.size(); i < size; i++)\n        {\n            final NetworkPublication publication = networkPublications.get(i);\n\n            if (publicationRegistrationId == publication.registrationId())\n            {\n                sendChannelEndpoint = publication.channelEndpoint();\n                break;\n            }\n        }\n\n        if (null == sendChannelEndpoint)\n        {\n            throw new ControlProtocolException(\n                UNKNOWN_PUBLICATION, \"unknown publication: \" + publicationRegistrationId);\n        }\n\n        sendChannelEndpoint.validateAllowsManualControl();\n\n        senderProxy.removeDestination(sendChannelEndpoint, destinationRegistrationId);\n        clientProxy.operationSucceeded(correlationId);\n    }\n\n    void onAddNetworkSubscription(\n        final String channel, final int streamId, final long registrationId, final long clientId)\n    {\n        executeAsyncClientTask(\n            registrationId,\n            () -> UdpChannel.parse(channel, nameResolver, false),\n            (asyncResult) ->\n            {\n                final UdpChannel udpChannel = asyncResult.get();\n                final ControlMode controlMode = udpChannel.controlMode();\n\n                validateExperimentalFeatures(ctx.enableExperimentalFeatures(), udpChannel);\n                validateControlForSubscription(udpChannel);\n                validateTimestampConfiguration(udpChannel);\n\n                final SubscriptionParams params =\n                    SubscriptionParams.getSubscriptionParams(udpChannel.channelUri(), ctx, 0);\n                checkForClashingSubscription(params, udpChannel, streamId);\n\n                final ReceiveChannelEndpoint channelEndpoint = getOrCreateReceiveChannelEndpoint(\n                    params, udpChannel, registrationId);\n\n                if (ChannelEndpointStatus.CLOSING == channelEndpoint.status())\n                {\n                    clientProxy.onError(\n                        registrationId,\n                        ErrorCode.RESOURCE_TEMPORARILY_UNAVAILABLE,\n                        \"ReceiveChannelEndpoint found in CLOSING state, please retry\"\n                    );\n                    return;\n                }\n\n                final NetworkSubscriptionLink subscription = new NetworkSubscriptionLink(\n                    registrationId, channelEndpoint, streamId, channel, getOrAddClient(clientId), params);\n\n                subscriptionLinks.add(subscription);\n\n                if (ControlMode.RESPONSE == controlMode)\n                {\n                    channelEndpoint.incResponseRefToStream(subscription.streamId);\n                }\n                else\n                {\n                    addNetworkSubscriptionToReceiver(subscription);\n                }\n\n                clientProxy.onSubscriptionReady(registrationId, channelEndpoint.statusIndicatorCounter().id());\n                linkMatchingImages(subscription);\n            });\n    }\n\n    private void addNetworkSubscriptionToReceiver(final NetworkSubscriptionLink subscription)\n    {\n        final ReceiveChannelEndpoint channelEndpoint = subscription.channelEndpoint();\n\n        if (subscription.hasSessionId())\n        {\n            if (1 == channelEndpoint.incRefToStreamAndSession(subscription.streamId(), subscription.sessionId()))\n            {\n                receiverProxy.addSubscription(channelEndpoint, subscription.streamId(), subscription.sessionId());\n            }\n        }\n        else\n        {\n            if (1 == channelEndpoint.incRefToStream(subscription.streamId()))\n            {\n                receiverProxy.addSubscription(channelEndpoint, subscription.streamId());\n            }\n        }\n    }\n\n    void onAddIpcSubscription(final String channel, final int streamId, final long registrationId, final long clientId)\n    {\n        final SubscriptionParams params = SubscriptionParams.getSubscriptionParams(parseUri(channel), ctx, 0);\n        final IpcSubscriptionLink subscriptionLink = new IpcSubscriptionLink(\n            registrationId, streamId, channel, getOrAddClient(clientId), params);\n\n        subscriptionLinks.add(subscriptionLink);\n        clientProxy.onSubscriptionReady(registrationId, ChannelEndpointStatus.NO_ID_ALLOCATED);\n\n        for (int i = 0, size = ipcPublications.size(); i < size; i++)\n        {\n            final IpcPublication publication = ipcPublications.get(i);\n            if (subscriptionLink.matches(publication) && publication.isAcceptingSubscriptions())\n            {\n                clientProxy.onAvailableImage(\n                    publication.registrationId(),\n                    streamId,\n                    publication.sessionId(),\n                    registrationId,\n                    linkIpcSubscription(publication, subscriptionLink).id(),\n                    publication.rawLog().fileName(),\n                    IPC_CHANNEL);\n            }\n        }\n    }\n\n    void onAddSpySubscription(final String channel, final int streamId, final long registrationId, final long clientId)\n    {\n        executeAsyncClientTask(\n            registrationId,\n            () -> UdpChannel.parse(channel, nameResolver, false),\n            (asyncResult) ->\n            {\n                final UdpChannel udpChannel = asyncResult.get();\n                final SubscriptionParams params =\n                    SubscriptionParams.getSubscriptionParams(udpChannel.channelUri(), ctx, 0);\n                final SpySubscriptionLink subscriptionLink = new SpySubscriptionLink(\n                    registrationId, udpChannel, streamId, getOrAddClient(clientId), params);\n\n                subscriptionLinks.add(subscriptionLink);\n                clientProxy.onSubscriptionReady(registrationId, ChannelEndpointStatus.NO_ID_ALLOCATED);\n\n                for (int i = 0, size = networkPublications.size(); i < size; i++)\n                {\n                    final NetworkPublication publication = networkPublications.get(i);\n                    if (subscriptionLink.matches(publication) && publication.isAcceptingSubscriptions())\n                    {\n                        clientProxy.onAvailableImage(\n                            publication.registrationId(),\n                            streamId,\n                            publication.sessionId(),\n                            registrationId,\n                            linkSpy(publication, subscriptionLink).id(),\n                            publication.rawLog().fileName(),\n                            IPC_CHANNEL);\n                    }\n                }\n            });\n    }\n\n    void onRemoveSubscription(final long registrationId, final long correlationId)\n    {\n        boolean isAnySubscriptionFound = false;\n        for (int lastIndex = subscriptionLinks.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final SubscriptionLink subscription = subscriptionLinks.get(i);\n            if (subscription.registrationId() == registrationId)\n            {\n                fastUnorderedRemove(subscriptionLinks, i, lastIndex--);\n\n                subscription.close();\n                cleanupSubscriptionLink(subscription);\n                isAnySubscriptionFound = true;\n            }\n        }\n\n        if (!isAnySubscriptionFound)\n        {\n            throw new ControlProtocolException(UNKNOWN_SUBSCRIPTION, \"unknown subscription: \" + registrationId);\n        }\n\n        clientProxy.operationSucceeded(correlationId);\n    }\n\n    void onClientKeepalive(final long clientId)\n    {\n        final AeronClient client = findClient(clients, clientId);\n        if (null != client)\n        {\n            client.timeOfLastKeepaliveMs(cachedEpochClock.time());\n        }\n    }\n\n    void onAddCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength,\n        final long correlationId,\n        final long clientId)\n    {\n        final AeronClient client = getOrAddClient(clientId);\n        final AtomicCounter counter = countersManager.newCounter(\n            typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength);\n\n        countersManager.setCounterRegistrationId(counter.id(), correlationId);\n        countersManager.setCounterOwnerId(counter.id(), clientId);\n        counterLinks.add(new CounterLink(counter, correlationId, client));\n        clientProxy.onCounterReady(correlationId, counter.id());\n    }\n\n    void onAddStaticCounter(\n        final int typeId,\n        final DirectBuffer keyBuffer,\n        final int keyOffset,\n        final int keyLength,\n        final DirectBuffer labelBuffer,\n        final int labelOffset,\n        final int labelLength,\n        final long registrationId,\n        final long correlationId,\n        final long clientId)\n    {\n        getOrAddClient(clientId);\n\n        final int counterId = countersManager.findByTypeIdAndRegistrationId(typeId, registrationId);\n        if (CountersReader.NULL_COUNTER_ID != counterId)\n        {\n            if (NULL_VALUE != countersManager.getCounterOwnerId(counterId))\n            {\n                clientProxy.onError(correlationId, GENERIC_ERROR, \"cannot add static counter, because a \" +\n                    \"non-static counter exists (counterId=\" + counterId + \") for typeId=\" + typeId + \" and \" +\n                    \"registrationId=\" + registrationId);\n            }\n            else\n            {\n                clientProxy.onStaticCounter(correlationId, counterId);\n            }\n        }\n        else\n        {\n            final AtomicCounter counter = countersManager.newCounter(\n                typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength);\n\n            countersManager.setCounterRegistrationId(counter.id(), registrationId);\n            countersManager.setCounterOwnerId(counter.id(), NULL_VALUE);\n            clientProxy.onStaticCounter(correlationId, counter.id());\n        }\n    }\n\n    void onRemoveCounter(final long registrationId, final long correlationId)\n    {\n        CounterLink counterLink = null;\n        final ArrayList<CounterLink> counterLinks = this.counterLinks;\n        for (int i = 0, size = counterLinks.size(); i < size; i++)\n        {\n            final CounterLink link = counterLinks.get(i);\n            if (registrationId == link.registrationId())\n            {\n                counterLink = link;\n                fastUnorderedRemove(counterLinks, i);\n                break;\n            }\n        }\n\n        if (null == counterLink)\n        {\n            throw new ControlProtocolException(UNKNOWN_COUNTER, \"unknown counter: \" + registrationId);\n        }\n\n        clientProxy.operationSucceeded(correlationId);\n        clientProxy.onUnavailableCounter(registrationId, counterLink.counterId());\n        counterLink.close();\n    }\n\n    void onClientClose(final long clientId)\n    {\n        final AeronClient client = findClient(clients, clientId);\n        if (null != client)\n        {\n            client.onClosedByCommand();\n        }\n    }\n\n    void onAddRcvDestination(final long registrationId, final String destinationChannel, final long correlationId)\n    {\n        if (destinationChannel.startsWith(IPC_CHANNEL))\n        {\n            onAddRcvIpcDestination(registrationId, destinationChannel, correlationId);\n        }\n        else if (destinationChannel.startsWith(SPY_QUALIFIER))\n        {\n            onAddRcvSpyDestination(registrationId, destinationChannel, correlationId);\n        }\n        else\n        {\n            onAddRcvNetworkDestination(registrationId, destinationChannel, correlationId);\n        }\n    }\n\n    void onAddRcvIpcDestination(final long registrationId, final String destinationChannel, final long correlationId)\n    {\n        final SubscriptionParams params =\n            SubscriptionParams.getSubscriptionParams(parseUri(destinationChannel), ctx, 0);\n        final SubscriptionLink mdsSubscriptionLink = findMdsSubscriptionLink(subscriptionLinks, registrationId);\n\n        if (null == mdsSubscriptionLink)\n        {\n            throw new ControlProtocolException(UNKNOWN_SUBSCRIPTION, \"unknown MDS subscription: \" + registrationId);\n        }\n\n        final IpcSubscriptionLink subscriptionLink = new IpcSubscriptionLink(\n            registrationId,\n            mdsSubscriptionLink.streamId(),\n            destinationChannel,\n            mdsSubscriptionLink.aeronClient(),\n            params);\n\n        subscriptionLinks.add(subscriptionLink);\n        clientProxy.operationSucceeded(correlationId);\n\n        for (int i = 0, size = ipcPublications.size(); i < size; i++)\n        {\n            final IpcPublication publication = ipcPublications.get(i);\n            if (subscriptionLink.matches(publication) && publication.isAcceptingSubscriptions())\n            {\n                clientProxy.onAvailableImage(\n                    publication.registrationId(),\n                    mdsSubscriptionLink.streamId(),\n                    publication.sessionId(),\n                    registrationId,\n                    linkIpcSubscription(publication, subscriptionLink).id(),\n                    publication.rawLog().fileName(),\n                    IPC_CHANNEL);\n            }\n        }\n    }\n\n    void onAddRcvSpyDestination(final long registrationId, final String destinationChannel, final long correlationId)\n    {\n        executeAsyncClientTask(\n            correlationId,\n            () -> UdpChannel.parse(destinationChannel, nameResolver, false),\n            (asyncResult) ->\n            {\n                final UdpChannel udpChannel = asyncResult.get();\n                final SubscriptionParams params =\n                    SubscriptionParams.getSubscriptionParams(udpChannel.channelUri(), ctx, 0);\n                final SubscriptionLink mdsSubscriptionLink = findMdsSubscriptionLink(subscriptionLinks, registrationId);\n\n                if (null == mdsSubscriptionLink)\n                {\n                    throw new ControlProtocolException(\n                        UNKNOWN_SUBSCRIPTION, \"unknown MDS subscription: \" + registrationId);\n                }\n\n                final SpySubscriptionLink subscriptionLink = new SpySubscriptionLink(\n                    registrationId,\n                    udpChannel,\n                    mdsSubscriptionLink.streamId(),\n                    mdsSubscriptionLink.aeronClient(),\n                    params);\n\n                subscriptionLinks.add(subscriptionLink);\n                clientProxy.operationSucceeded(correlationId);\n\n                for (int i = 0, size = networkPublications.size(); i < size; i++)\n                {\n                    final NetworkPublication publication = networkPublications.get(i);\n                    if (subscriptionLink.matches(publication) && publication.isAcceptingSubscriptions())\n                    {\n                        clientProxy.onAvailableImage(\n                            publication.registrationId(),\n                            mdsSubscriptionLink.streamId(),\n                            publication.sessionId(),\n                            registrationId,\n                            linkSpy(publication, subscriptionLink).id(),\n                            publication.rawLog().fileName(),\n                            IPC_CHANNEL);\n                    }\n                }\n            });\n    }\n\n    void onAddRcvNetworkDestination(\n        final long registrationId, final String destinationChannel, final long correlationId)\n    {\n        executeAsyncClientTask(\n            correlationId,\n            () -> UdpChannel.parse(destinationChannel, nameResolver, true),\n            (asyncResult) ->\n            {\n                final UdpChannel udpChannel = asyncResult.get();\n                validateDestinationUri(udpChannel.channelUri(), destinationChannel);\n\n                final SubscriptionLink mdsSubscriptionLink = findMdsSubscriptionLink(subscriptionLinks, registrationId);\n\n                if (null == mdsSubscriptionLink)\n                {\n                    throw new ControlProtocolException(\n                        UNKNOWN_SUBSCRIPTION, \"unknown MDS subscription: \" + registrationId);\n                }\n\n                final ReceiveChannelEndpoint receiveChannelEndpoint = mdsSubscriptionLink.channelEndpoint();\n\n                AtomicCounter localSocketAddressIndicator = null;\n                ReceiveDestinationTransport transport = null;\n\n                try\n                {\n                    localSocketAddressIndicator = ReceiveLocalSocketAddress.allocate(\n                        tempBuffer,\n                        countersManager,\n                        registrationId,\n                        receiveChannelEndpoint.statusIndicatorCounter().id());\n\n                    transport = new ReceiveDestinationTransport(\n                        udpChannel, ctx, localSocketAddressIndicator, receiveChannelEndpoint);\n\n                    transport.openChannel(null);\n                }\n                catch (final Exception ex)\n                {\n                    CloseHelper.closeAll(localSocketAddressIndicator, transport);\n                    throw ex;\n                }\n\n                receiverProxy.addDestination(receiveChannelEndpoint, transport);\n                clientProxy.operationSucceeded(correlationId);\n            });\n    }\n\n    void onRemoveRcvDestination(final long registrationId, final String destinationChannel, final long correlationId)\n    {\n        if (destinationChannel.startsWith(IPC_CHANNEL) || destinationChannel.startsWith(SPY_QUALIFIER))\n        {\n            onRemoveRcvIpcOrSpyDestination(registrationId, destinationChannel, correlationId);\n        }\n        else\n        {\n            onRemoveRcvNetworkDestination(registrationId, destinationChannel, correlationId);\n        }\n    }\n\n    void onRemoveRcvIpcOrSpyDestination(\n        final long registrationId, final String destinationChannel, final long correlationId)\n    {\n        final SubscriptionLink subscription =\n            removeSubscriptionLink(subscriptionLinks, registrationId, destinationChannel);\n\n        if (null == subscription)\n        {\n            throw new ControlProtocolException(UNKNOWN_SUBSCRIPTION, \"unknown subscription: \" + registrationId);\n        }\n\n        subscription.close();\n        cleanupSubscriptionLink(subscription);\n        clientProxy.operationSucceeded(correlationId);\n        subscription.notifyUnavailableImages(this);\n    }\n\n    void onRemoveRcvNetworkDestination(\n        final long registrationId, final String destinationChannel, final long correlationId)\n    {\n        ReceiveChannelEndpoint receiveChannelEndpoint = null;\n\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            final SubscriptionLink subscriptionLink = subscriptionLinks.get(i);\n            if (registrationId == subscriptionLink.registrationId())\n            {\n                receiveChannelEndpoint = subscriptionLink.channelEndpoint();\n                break;\n            }\n        }\n\n        if (null == receiveChannelEndpoint)\n        {\n            throw new ControlProtocolException(UNKNOWN_SUBSCRIPTION, \"unknown subscription: \" + registrationId);\n        }\n\n        receiveChannelEndpoint.validateAllowsDestinationControl();\n\n        final ReceiveChannelEndpoint endpoint = receiveChannelEndpoint;\n        executeAsyncClientTask(\n            correlationId,\n            () -> UdpChannel.parse(destinationChannel, nameResolver, true),\n            (asyncResult) ->\n            {\n                receiverProxy.removeDestination(endpoint, asyncResult.get());\n                clientProxy.operationSucceeded(correlationId);\n            });\n    }\n\n    void closeReceiveDestination(final ReceiveDestinationTransport destinationTransport)\n    {\n        destinationTransport.close();\n    }\n\n    void onTerminateDriver(final DirectBuffer tokenBuffer, final int tokenOffset, final int tokenLength)\n    {\n        if (ctx.terminationValidator().allowTermination(ctx.aeronDirectory(), tokenBuffer, tokenOffset, tokenLength))\n        {\n            ctx.terminationHook().run();\n        }\n    }\n\n    void onRejectImage(\n        final long correlationId,\n        final long imageCorrelationId,\n        final long position,\n        final String reason)\n    {\n        if (reason.length() > ErrorFlyweight.MAX_ERROR_MESSAGE_LENGTH)\n        {\n            throw new ControlProtocolException(GENERIC_ERROR, \"Invalidation reason must be \" +\n                ErrorFlyweight.MAX_ERROR_MESSAGE_LENGTH + \" bytes or less\");\n        }\n\n        final PublicationImage publicationImage = findPublicationImage(imageCorrelationId);\n\n        if (null == publicationImage)\n        {\n            final IpcPublication foundPublication = getIpcPublication(imageCorrelationId);\n\n            if (null == foundPublication)\n            {\n                throw new ControlProtocolException(\n                    GENERIC_ERROR, \"Unable to resolve image for correlationId=\" + imageCorrelationId);\n            }\n\n            foundPublication.reject(position, reason, this, cachedNanoClock.nanoTime());\n        }\n        else\n        {\n            receiverProxy.rejectImage(imageCorrelationId, position, reason);\n        }\n\n        imagesRejected.incrementRelease();\n\n        clientProxy.operationSucceeded(correlationId);\n    }\n\n    void onNextAvailableSessionId(final long correlationId, final int streamId)\n    {\n        outer:\n        while (true)\n        {\n            final int sessionId = advanceSessionId();\n\n            for (final SessionKey key : activeSessionSet)\n            {\n                if (streamId == key.streamId && sessionId == key.sessionId)\n                {\n                    continue outer;\n                }\n            }\n\n            clientProxy.onNextAvailableSessionId(correlationId, sessionId);\n            break;\n        }\n    }\n\n    int nextAvailableSessionId(final int streamId, final String channel)\n    {\n        final SessionKey sessionKey = new SessionKey(streamId, channel);\n        while (true)\n        {\n            final int sessionId = advanceSessionId();\n\n            sessionKey.sessionId = sessionId;\n            if (!activeSessionSet.contains(sessionKey))\n            {\n                return sessionId;\n            }\n        }\n    }\n\n    private int advanceSessionId()\n    {\n        int sessionId = nextSessionId++;\n\n        if (ctx.publicationReservedSessionIdLow() <= sessionId &&\n            sessionId <= ctx.publicationReservedSessionIdHigh())\n        {\n            nextSessionId = ctx.publicationReservedSessionIdHigh() + 1;\n            sessionId = nextSessionId++;\n        }\n        return sessionId;\n    }\n\n    private void heartbeatAndCheckTimers(final long nowNs)\n    {\n        final long nowMs = cachedEpochClock.time();\n        toDriverCommands.consumerHeartbeatTime(nowMs);\n\n        checkManagedResources(clients, nowNs, nowMs);\n        checkManagedResources(publicationLinks, nowNs, nowMs);\n        checkManagedResources(networkPublications, nowNs, nowMs);\n        checkManagedResources(subscriptionLinks, nowNs, nowMs);\n        checkManagedResources(publicationImages, nowNs, nowMs);\n        checkManagedResources(ipcPublications, nowNs, nowMs);\n        checkManagedResources(counterLinks, nowNs, nowMs);\n    }\n\n    private void checkForBlockedToDriverCommands(final long nowNs)\n    {\n        final long consumerPosition = toDriverCommands.consumerPosition();\n\n        if (consumerPosition == lastCommandConsumerPosition && toDriverCommands.producerPosition() > consumerPosition)\n        {\n            if ((timeOfLastToDriverPositionChangeNs + clientLivenessTimeoutNs) - nowNs < 0)\n            {\n                if (toDriverCommands.unblock())\n                {\n                    ctx.systemCounters().get(UNBLOCKED_COMMANDS).incrementRelease();\n                }\n            }\n        }\n        else\n        {\n            timeOfLastToDriverPositionChangeNs = nowNs;\n            lastCommandConsumerPosition = consumerPosition;\n        }\n    }\n\n    private static ChannelUri parseUri(final String channel)\n    {\n        try\n        {\n            return ChannelUri.parse(channel);\n        }\n        catch (final Exception ex)\n        {\n            throw new InvalidChannelException(ex);\n        }\n    }\n\n    private ArrayList<SubscriberPosition> createSubscriberPositions(\n        final int sessionId, final int streamId, final ReceiveChannelEndpoint channelEndpoint, final long joinPosition)\n    {\n        final ArrayList<SubscriberPosition> subscriberPositions = new ArrayList<>();\n\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            final SubscriptionLink subscription = subscriptionLinks.get(i);\n            if (subscription.matches(channelEndpoint, streamId, sessionId))\n            {\n                final Position position = SubscriberPos.allocate(\n                    tempBuffer,\n                    countersManager,\n                    subscription.aeronClient().clientId(),\n                    subscription.registrationId(),\n                    sessionId,\n                    streamId,\n                    subscription.channel(),\n                    joinPosition);\n\n                position.setRelease(joinPosition);\n                subscriberPositions.add(new SubscriberPosition(subscription, null, position));\n            }\n        }\n\n        return subscriberPositions;\n    }\n\n    private void executeAsyncClientTask(\n        final long correlationId,\n        final Supplier<UdpChannel> asyncTask,\n        final Consumer<Supplier<UdpChannel>> command)\n    {\n        if (asyncExecutionDisabled)\n        {\n            command.accept(asyncTask);\n        }\n        else\n        {\n            asyncClientCommandInFlight = true;\n            asyncTaskExecutor.execute(() ->\n            {\n                final AsyncResult<UdpChannel> asyncResult = AsyncResult.of(asyncTask);\n                addToCommandQueue(() ->\n                {\n                    try\n                    {\n                        command.accept(asyncResult);\n                    }\n                    catch (final Exception ex)\n                    {\n                        clientCommandAdapter.onError(correlationId, ex);\n                    }\n                    finally\n                    {\n                        asyncClientCommandInFlight = false;\n                    }\n                });\n            });\n        }\n    }\n\n    private <T> void executeAsyncTask(final Supplier<T> supplier, final Consumer<Supplier<T>> command)\n    {\n        if (asyncExecutionDisabled)\n        {\n            command.accept(supplier);\n        }\n        else\n        {\n            asyncTaskExecutor.execute(() ->\n            {\n                final AsyncResult<T> asyncResult = AsyncResult.of(supplier);\n                addToCommandQueue(() -> command.accept(asyncResult));\n            });\n        }\n    }\n\n    private void addToCommandQueue(final Runnable cmd)\n    {\n        if (!driverCmdQueue.offer(cmd))\n        {\n            // unreachable for ManyToOneConcurrentLinkedQueue\n            throw new IllegalStateException(driverCmdQueue.getClass().getSimpleName() + \".offer failed!\");\n        }\n    }\n\n    private static NetworkPublication findPublication(\n        final ArrayList<NetworkPublication> publications,\n        final int streamId,\n        final SendChannelEndpoint channelEndpoint,\n        final long responseCorrelationId)\n    {\n        for (int i = 0, size = publications.size(); i < size; i++)\n        {\n            final NetworkPublication publication = publications.get(i);\n\n            if (streamId == publication.streamId() &&\n                channelEndpoint == publication.channelEndpoint() &&\n                NetworkPublication.State.ACTIVE == publication.state() &&\n                !publication.isExclusive() &&\n                publication.responseCorrelationId() == responseCorrelationId)\n            {\n                return publication;\n            }\n        }\n\n        return null;\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    private NetworkPublication newNetworkPublication(\n        final long registrationId,\n        final long clientId,\n        final int streamId,\n        final String channel,\n        final UdpChannel udpChannel,\n        final SendChannelEndpoint channelEndpoint,\n        final PublicationParams params,\n        final boolean isExclusive)\n    {\n        if (params.isResponse &&\n            PROTOTYPE_VALUE_CORRELATION_ID == params.responseCorrelationId)\n        {\n            params.termLength = TERM_MIN_LENGTH;\n        }\n\n        final String canonicalForm = udpChannel.canonicalForm();\n\n        final FlowControl flowControl = udpChannel.isMulticast() || udpChannel.isMultiDestination() ?\n            ctx.multicastFlowControlSupplier().newInstance(udpChannel, streamId, registrationId) :\n            ctx.unicastFlowControlSupplier().newInstance(udpChannel, streamId, registrationId);\n        flowControl.initialize(\n            ctx,\n            countersManager,\n            udpChannel,\n            streamId,\n            params.sessionId,\n            registrationId,\n            params.initialTermId,\n            params.termLength);\n\n        final int termOffset = params.termOffset;\n\n        final RawLog rawLog = newNetworkPublicationLog(\n            isExclusive,\n            params.sessionId,\n            streamId,\n            params.initialTermId,\n            registrationId,\n            channelEndpoint.socketRcvbufLength(),\n            channelEndpoint.socketSndbufLength(),\n            termOffset,\n            params,\n            udpChannel.hasGroupSemantics());\n        UnsafeBufferPosition publisherPos = null;\n        UnsafeBufferPosition publisherLmt = null;\n        UnsafeBufferPosition senderPos = null;\n        UnsafeBufferPosition senderLmt = null;\n        AtomicCounter senderBpe = null;\n        AtomicCounter senderNaksReceived = null;\n        try\n        {\n            publisherPos = PublisherPos.allocate(\n                tempBuffer,\n                countersManager,\n                clientId,\n                registrationId,\n                params.sessionId,\n                streamId,\n                channel,\n                isExclusive);\n            publisherLmt = PublisherLimit.allocate(\n                tempBuffer, countersManager, clientId, registrationId, params.sessionId, streamId, channel);\n            senderPos = SenderPos.allocate(\n                tempBuffer, countersManager, clientId, registrationId, params.sessionId, streamId, channel);\n            senderLmt = SenderLimit.allocate(\n                tempBuffer, countersManager, clientId, registrationId, params.sessionId, streamId, channel);\n            senderBpe = SenderBpe.allocate(\n                tempBuffer, countersManager, clientId, registrationId, params.sessionId, streamId, channel);\n            senderNaksReceived = SenderNaksReceived.allocate(\n                tempBuffer, countersManager, clientId, registrationId, params.sessionId, streamId, channel);\n\n            final AtomicCounter retransmitOverflowCounter = ctx.systemCounters().get(RETRANSMIT_OVERFLOW);\n\n            if (params.hasPosition)\n            {\n                final int bits = LogBufferDescriptor.positionBitsToShift(params.termLength);\n                final long position = computePosition(params.termId, params.termOffset, bits, params.initialTermId);\n                publisherPos.setRelease(position);\n                publisherLmt.setRelease(position);\n                senderPos.setRelease(position);\n                senderLmt.setRelease(position);\n            }\n\n            final RetransmitHandler retransmitHandler = new RetransmitHandler(\n                ctx.senderCachedNanoClock(),\n                ctx.systemCounters().get(INVALID_PACKETS),\n                ctx.retransmitUnicastDelayGenerator(),\n                ctx.retransmitUnicastLingerGenerator(),\n                udpChannel.hasGroupSemantics(),\n                params.maxResend,\n                retransmitOverflowCounter);\n\n            final NetworkPublication publication = new NetworkPublication(\n                registrationId,\n                ctx,\n                params,\n                channelEndpoint,\n                rawLog,\n                params.publicationWindowLength,\n                publisherPos,\n                publisherLmt,\n                senderPos,\n                senderLmt,\n                senderBpe,\n                senderNaksReceived,\n                params.sessionId,\n                streamId,\n                params.initialTermId,\n                flowControl,\n                retransmitHandler,\n                networkPublicationThreadLocals,\n                isExclusive);\n\n            channelEndpoint.incRef();\n            networkPublications.add(publication);\n            senderProxy.newNetworkPublication(publication);\n            activeSessionSet.add(new SessionKey(params.sessionId, streamId, canonicalForm));\n\n            return publication;\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietCloseAll(\n                rawLog, publisherPos, publisherLmt, senderPos, senderLmt, senderBpe, senderNaksReceived);\n            throw ex;\n        }\n    }\n\n    private RawLog newNetworkPublicationLog(\n        final boolean isExclusive,\n        final int sessionId,\n        final int streamId,\n        final int initialTermId,\n        final long registrationId,\n        final int socketRcvBufLength,\n        final int socketSndBufLength,\n        final int termOffset,\n        final PublicationParams params,\n        final boolean hasGroupSemantics)\n    {\n        final RawLog rawLog = logFactory.newPublication(registrationId, params.termLength, params.isSparse);\n        final int receiverWindowLength = 0;\n        final boolean tether = false;\n        final boolean rejoin = false;\n        final boolean reliable = false;\n        initLogMetadata(\n            isExclusive ? LOG_BUFFER_TYPE_EXCLUSIVE_PUBLICATION : LOG_BUFFER_TYPE_CONCURRENT_PUBLICATION,\n            sessionId,\n            streamId,\n            initialTermId,\n            params.mtuLength,\n            registrationId,\n            socketRcvBufLength,\n            socketSndBufLength,\n            termOffset,\n            receiverWindowLength,\n            tether,\n            rejoin,\n            reliable,\n            params.isSparse,\n            hasGroupSemantics,\n            params.isResponse,\n            params.publicationWindowLength,\n            params.untetheredWindowLimitTimeoutNs,\n            params.untetheredLingerTimeoutNs,\n            params.untetheredRestingTimeoutNs,\n            params.maxResend,\n            params.lingerTimeoutNs,\n            params.signalEos,\n            params.spiesSimulateConnection,\n            params.entityTag,\n            params.responseCorrelationId,\n            rawLog);\n        initialisePositionCounters(initialTermId, params, rawLog.metaData());\n\n        return rawLog;\n    }\n\n    private RawLog newIpcPublicationLog(\n        final boolean isExclusive,\n        final int sessionId,\n        final int streamId,\n        final int initialTermId,\n        final long registrationId,\n        final int termOffset,\n        final PublicationParams params)\n    {\n        final RawLog rawLog = logFactory.newPublication(registrationId, params.termLength, params.isSparse);\n\n        final int socketRcvBufLength = 0;\n        final int socketSndbufLength = 0;\n        final int receiverWindowLength = 0;\n        final boolean tether = false;\n        final boolean rejoin = false;\n        final boolean reliable = false;\n        final boolean group = false;\n        initLogMetadata(\n            isExclusive ? LOG_BUFFER_TYPE_EXCLUSIVE_PUBLICATION : LOG_BUFFER_TYPE_CONCURRENT_PUBLICATION,\n            sessionId,\n            streamId,\n            initialTermId,\n            params.mtuLength,\n            registrationId,\n            socketRcvBufLength,\n            socketSndbufLength,\n            termOffset,\n            receiverWindowLength,\n            tether,\n            rejoin,\n            reliable,\n            params.isSparse,\n            group,\n            params.isResponse,\n            params.publicationWindowLength,\n            params.untetheredWindowLimitTimeoutNs,\n            params.untetheredLingerTimeoutNs,\n            params.untetheredRestingTimeoutNs,\n            params.maxResend,\n            params.lingerTimeoutNs,\n            params.signalEos,\n            params.spiesSimulateConnection,\n            params.entityTag,\n            params.responseCorrelationId,\n            rawLog);\n        initialisePositionCounters(initialTermId, params, rawLog.metaData());\n\n        return rawLog;\n    }\n\n    private void initLogMetadata(\n        final byte type,\n        final int sessionId,\n        final int streamId,\n        final int initialTermId,\n        final int mtuLength,\n        final long registrationId,\n        final int socketRcvBufLength,\n        final int socketSndbufLength,\n        final int termOffset,\n        final int receiverWindowLength,\n        final boolean tether,\n        final boolean rejoin,\n        final boolean reliable,\n        final boolean sparse,\n        final boolean group,\n        final boolean isResponse,\n        final int publicationWindowLength,\n        final long untetheredWindowLimitTimeoutNs,\n        final long untetheredLingerTimeoutNs,\n        final long untetheredRestingTimeoutNs,\n        final int maxResend,\n        final long lingerTimeoutNs,\n        final boolean signalEos,\n        final boolean spiesSimulateConnection,\n        final long entityTag,\n        final long responseCorrelationId,\n        final RawLog rawLog)\n    {\n        final UnsafeBuffer logMetaData = rawLog.metaData();\n\n        defaultDataHeader\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .termId(initialTermId)\n            .termOffset(termOffset);\n        storeDefaultFrameHeader(logMetaData, defaultDataHeader);\n\n        correlationId(logMetaData, registrationId);\n        initialTermId(logMetaData, initialTermId);\n        mtuLength(logMetaData, mtuLength);\n        termLength(logMetaData, rawLog.termLength());\n        pageSize(logMetaData, ctx.filePageSize());\n\n        publicationWindowLength(logMetaData, publicationWindowLength);\n        receiverWindowLength(logMetaData, receiverWindowLength);\n        socketSndbufLength(logMetaData, socketSndbufLength);\n        osDefaultSocketSndbufLength(logMetaData, ctx.osDefaultSocketSndbufLength());\n        osMaxSocketSndbufLength(logMetaData, ctx.osMaxSocketSndbufLength());\n        socketRcvbufLength(logMetaData, socketRcvBufLength);\n        osDefaultSocketRcvbufLength(logMetaData, ctx.osDefaultSocketRcvbufLength());\n        osMaxSocketRcvbufLength(logMetaData, ctx.osMaxSocketRcvbufLength());\n        maxResend(logMetaData, maxResend);\n\n        rejoin(logMetaData, rejoin);\n        reliable(logMetaData, reliable);\n        sparse(logMetaData, sparse);\n        signalEos(logMetaData, signalEos);\n        spiesSimulateConnection(logMetaData, spiesSimulateConnection);\n        tether(logMetaData, tether);\n        isPublicationRevoked(logMetaData, false);\n        group(logMetaData, group);\n        isResponse(logMetaData, isResponse);\n        type(logMetaData, type);\n\n        entityTag(logMetaData, entityTag);\n        responseCorrelationId(logMetaData, responseCorrelationId);\n        untetheredWindowLimitTimeoutNs(logMetaData, untetheredWindowLimitTimeoutNs);\n        untetheredLingerTimeoutNs(logMetaData, untetheredLingerTimeoutNs);\n        untetheredRestingTimeoutNs(logMetaData, untetheredRestingTimeoutNs);\n        lingerTimeoutNs(logMetaData, lingerTimeoutNs);\n\n        // Acts like a release fence; so this should be the last statement to ensure that all above writes\n        // are ordered before the eos-position.\n        endOfStreamPosition(logMetaData, Long.MAX_VALUE);\n    }\n\n\n    private static void initialisePositionCounters(\n        final int initialTermId, final PublicationParams params, final UnsafeBuffer logMetaData)\n    {\n        if (params.hasPosition)\n        {\n            final int termId = params.termId;\n            final int termCount = termId - initialTermId;\n            int activeIndex = indexByTerm(initialTermId, termId);\n\n            rawTail(logMetaData, activeIndex, packTail(termId, params.termOffset));\n            for (int i = 1; i < PARTITION_COUNT; i++)\n            {\n                final int expectedTermId = (termId + i) - PARTITION_COUNT;\n                activeIndex = nextPartitionIndex(activeIndex);\n                initialiseTailWithTermId(logMetaData, activeIndex, expectedTermId);\n            }\n\n            activeTermCount(logMetaData, termCount);\n        }\n        else\n        {\n            initialiseTailWithTermId(logMetaData, 0, initialTermId);\n            for (int i = 1; i < PARTITION_COUNT; i++)\n            {\n                final int expectedTermId = (initialTermId + i) - PARTITION_COUNT;\n                initialiseTailWithTermId(logMetaData, i, expectedTermId);\n            }\n        }\n    }\n\n    private RawLog newPublicationImageLog(\n        final int sessionId,\n        final int streamId,\n        final int initialTermId,\n        final int termBufferLength,\n        final boolean isReliable,\n        final boolean isSparse,\n        final int senderMtuLength,\n        final int socketRcvBufLength,\n        final int socketSndBufLength,\n        final int termOffset,\n        final SubscriptionParams params,\n        final long correlationId,\n        final boolean hasGroupSemantics)\n    {\n        final RawLog rawLog = logFactory.newImage(correlationId, termBufferLength, isSparse);\n\n        final int publicationWindowLength = 0;\n        final int maxResend = 0;\n        final long lingerTimeoutNs = 0;\n        final boolean signalEos = false;\n        final boolean spiesSimulateConnection = false;\n        final long entityTag = 0;\n        final long responseCorrelationId = 0;\n        initLogMetadata(\n            LOG_BUFFER_TYPE_PUBLICATION_IMAGE,\n            sessionId,\n            streamId,\n            initialTermId,\n            senderMtuLength,\n            correlationId,\n            socketRcvBufLength,\n            socketSndBufLength,\n            termOffset,\n            params.receiverWindowLength,\n            params.isTether,\n            params.isRejoin,\n            isReliable,\n            isSparse,\n            hasGroupSemantics,\n            params.isResponse,\n            publicationWindowLength,\n            params.untetheredWindowLimitTimeoutNs,\n            params.untetheredLingerTimeoutNs,\n            params.untetheredRestingTimeoutNs,\n            maxResend,\n            lingerTimeoutNs,\n            signalEos,\n            spiesSimulateConnection,\n            entityTag,\n            responseCorrelationId,\n            rawLog);\n\n        return rawLog;\n    }\n\n    private SendChannelEndpoint getOrCreateSendChannelEndpoint(\n        final PublicationParams params, final UdpChannel udpChannel, final long registrationId)\n    {\n        SendChannelEndpoint channelEndpoint = findExistingSendChannelEndpoint(udpChannel);\n        if (null == channelEndpoint)\n        {\n            AtomicCounter statusIndicator = null;\n            AtomicCounter localSocketAddressIndicator = null;\n            try\n            {\n                statusIndicator = SendChannelStatus.allocate(\n                    tempBuffer, countersManager, registrationId, udpChannel.originalUriString());\n\n                channelEndpoint = ctx.sendChannelEndpointSupplier().newInstance(udpChannel, statusIndicator, ctx);\n\n                localSocketAddressIndicator = SendLocalSocketAddress.allocate(\n                    tempBuffer, countersManager, registrationId, channelEndpoint.statusIndicatorCounterId());\n\n                channelEndpoint.localSocketAddressIndicator(localSocketAddressIndicator);\n                channelEndpoint.allocateDestinationsCounterForMdc(\n                    tempBuffer, countersManager, registrationId, udpChannel.originalUriString());\n\n                validateMtuForSndbuf(\n                    params, channelEndpoint.socketSndbufLength(), ctx, udpChannel.originalUriString(), null);\n\n                channelEndpoint.openChannel();\n                channelEndpoint.indicateActive();\n\n                senderProxy.registerSendChannelEndpoint(channelEndpoint);\n                sendChannelEndpointByChannelMap.put(udpChannel.canonicalForm(), channelEndpoint);\n            }\n            catch (final Exception ex)\n            {\n                CloseHelper.closeAll(statusIndicator, localSocketAddressIndicator, channelEndpoint);\n                throw ex;\n            }\n        }\n        else\n        {\n            validateChannelSendTimestampOffset(udpChannel, channelEndpoint);\n            validateMtuForSndbuf(\n                params,\n                channelEndpoint.socketSndbufLength(),\n                ctx,\n                udpChannel.originalUriString(),\n                channelEndpoint.originalUriString());\n            validateChannelBufferLength(\n                SOCKET_RCVBUF_PARAM_NAME,\n                udpChannel.socketRcvbufLength(),\n                channelEndpoint.socketRcvbufLength(),\n                udpChannel.originalUriString(),\n                channelEndpoint.originalUriString());\n            validateChannelBufferLength(\n                SOCKET_SNDBUF_PARAM_NAME,\n                udpChannel.socketSndbufLength(),\n                channelEndpoint.socketSndbufLength(),\n                udpChannel.originalUriString(),\n                channelEndpoint.originalUriString());\n        }\n\n        return channelEndpoint;\n    }\n\n    private void validateChannelSendTimestampOffset(\n        final UdpChannel udpChannel, final SendChannelEndpoint channelEndpoint)\n    {\n        if (udpChannel.channelSendTimestampOffset() != channelEndpoint.udpChannel().channelSendTimestampOffset())\n        {\n            throw new InvalidChannelException(\n                \"option conflicts with existing subscription: \" + CHANNEL_SEND_TIMESTAMP_OFFSET_PARAM_NAME + \"=\" +\n                    udpChannel.channelSendTimestampOffset() +\n                    \" existingChannel=\" + channelEndpoint.originalUriString() + \" channel=\" +\n                    udpChannel.originalUriString());\n        }\n    }\n\n    private void validateReceiveTimestampOffset(\n        final UdpChannel udpChannel, final ReceiveChannelEndpoint channelEndpoint)\n    {\n        if (udpChannel.channelReceiveTimestampOffset() !=\n            channelEndpoint.subscriptionUdpChannel().channelReceiveTimestampOffset())\n        {\n            throw new InvalidChannelException(\n                \"option conflicts with existing subscription: \" + CHANNEL_RECEIVE_TIMESTAMP_OFFSET_PARAM_NAME + \"=\" +\n                    udpChannel.channelReceiveTimestampOffset() +\n                    \" existingChannel=\" + channelEndpoint.originalUriString() + \" channel=\" +\n                    udpChannel.originalUriString());\n        }\n    }\n\n    private SendChannelEndpoint findExistingSendChannelEndpoint(final UdpChannel udpChannel)\n    {\n        if (udpChannel.hasTag())\n        {\n            for (final SendChannelEndpoint endpoint : sendChannelEndpointByChannelMap.values())\n            {\n                if (endpoint.matchesTag(udpChannel))\n                {\n                    return endpoint;\n                }\n            }\n\n            if (!udpChannel.hasExplicitControl() && !udpChannel.isManualControlMode() &&\n                !udpChannel.channelUri().containsKey(ENDPOINT_PARAM_NAME))\n            {\n                throw new InvalidChannelException(\n                    \"URI must have explicit control, endpoint, or be manual control-mode when original: channel=\" +\n                        udpChannel.originalUriString());\n            }\n        }\n\n        SendChannelEndpoint endpoint = sendChannelEndpointByChannelMap.get(udpChannel.canonicalForm());\n        if (null != endpoint && endpoint.udpChannel().hasTag() && udpChannel.hasTag() &&\n            endpoint.udpChannel().tag() != udpChannel.tag())\n        {\n            endpoint = null;\n        }\n\n        return endpoint;\n    }\n\n    private void checkForClashingSubscription(\n        final SubscriptionParams params, final UdpChannel udpChannel, final int streamId)\n    {\n        final ReceiveChannelEndpoint channelEndpoint = findExistingReceiveChannelEndpoint(udpChannel);\n        if (null != channelEndpoint)\n        {\n            validateReceiveTimestampOffset(udpChannel, channelEndpoint);\n\n            for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n            {\n                final SubscriptionLink subscription = subscriptionLinks.get(i);\n                final boolean matchesTag = !udpChannel.hasTag() || channelEndpoint.matchesTag(udpChannel);\n\n                if (matchesTag && subscription.matches(channelEndpoint, streamId, params))\n                {\n                    if (params.isReliable != subscription.isReliable())\n                    {\n                        throw new InvalidChannelException(\n                            \"option conflicts with existing subscription: reliable=\" + params.isReliable +\n                                \" existingChannel=\" + subscription.channel() + \" channel=\" +\n                                udpChannel.originalUriString());\n                    }\n\n                    if (params.isRejoin != subscription.isRejoin())\n                    {\n                        throw new InvalidChannelException(\n                            \"option conflicts with existing subscription: rejoin=\" + params.isRejoin +\n                                \" existingChannel=\" + subscription.channel() + \" channel=\" +\n                                udpChannel.originalUriString());\n                    }\n\n                    if (params.isResponse != subscription.isResponse())\n                    {\n                        throw new InvalidChannelException(\n                            \"option conflicts with existing subscription: isResponse=\" + params.isResponse +\n                                \" existingChannel=\" + subscription.channel() + \" channel=\" +\n                                udpChannel.originalUriString());\n                    }\n                }\n            }\n        }\n    }\n\n    private void linkMatchingImages(final SubscriptionLink subscriptionLink)\n    {\n        for (int i = 0, size = publicationImages.size(); i < size; i++)\n        {\n            final PublicationImage image = publicationImages.get(i);\n            if (subscriptionLink.matches(image) && image.isAcceptingSubscriptions())\n            {\n                final long registrationId = subscriptionLink.registrationId();\n                final long joinPosition = image.joinPosition();\n                final int sessionId = image.sessionId();\n                final int streamId = subscriptionLink.streamId();\n                final Position position = SubscriberPos.allocate(\n                    tempBuffer,\n                    countersManager,\n                    subscriptionLink.aeronClient().clientId(),\n                    registrationId,\n                    sessionId,\n                    streamId,\n                    subscriptionLink.channel(),\n                    joinPosition);\n\n                countersManager.setCounterReferenceId(position.id(), image.correlationId());\n\n                position.setRelease(joinPosition);\n                subscriptionLink.link(image, position);\n                image.addSubscriber(subscriptionLink, position, cachedNanoClock.nanoTime());\n\n                clientProxy.onAvailableImage(\n                    image.correlationId(),\n                    streamId,\n                    sessionId,\n                    registrationId,\n                    position.id(),\n                    image.rawLog().fileName(),\n                    image.sourceIdentity());\n            }\n        }\n    }\n\n    void linkIpcSubscriptions(final IpcPublication publication)\n    {\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            final SubscriptionLink subscription = subscriptionLinks.get(i);\n            if (subscription.matches(publication) &&\n                !subscription.isLinked(publication) &&\n                publication.isAcceptingSubscriptions())\n            {\n                clientProxy.onAvailableImage(\n                    publication.registrationId(),\n                    publication.streamId(),\n                    publication.sessionId(),\n                    subscription.registrationId,\n                    linkIpcSubscription(publication, subscription).id(),\n                    publication.rawLog().fileName(),\n                    IPC_CHANNEL);\n            }\n        }\n    }\n\n    private Position linkIpcSubscription(final IpcPublication publication, final SubscriptionLink subscription)\n    {\n        final long joinPosition = publication.joinPosition();\n        final long registrationId = subscription.registrationId();\n        final long clientId = subscription.aeronClient().clientId();\n        final int sessionId = publication.sessionId();\n        final int streamId = subscription.streamId();\n        final String channel = subscription.channel();\n\n        final Position position = SubscriberPos.allocate(\n            tempBuffer, countersManager, clientId, registrationId, sessionId, streamId, channel, joinPosition);\n\n        countersManager.setCounterReferenceId(position.id(), publication.registrationId());\n\n        position.setRelease(joinPosition);\n        subscription.link(publication, position);\n        publication.addSubscriber(subscription, position, cachedNanoClock.nanoTime());\n\n        return position;\n    }\n\n    private Position linkSpy(final NetworkPublication publication, final SubscriptionLink subscription)\n    {\n        final long joinPosition = publication.consumerPosition();\n        final long registrationId = subscription.registrationId();\n        final long clientId = subscription.aeronClient().clientId();\n        final int streamId = publication.streamId();\n        final int sessionId = publication.sessionId();\n        final String channel = subscription.channel();\n\n        final Position position = SubscriberPos.allocate(\n            tempBuffer, countersManager, clientId, registrationId, sessionId, streamId, channel, joinPosition);\n\n        countersManager.setCounterReferenceId(position.id(), publication.registrationId());\n\n        position.setRelease(joinPosition);\n        subscription.link(publication, position);\n        publication.addSubscriber(subscription, position, cachedNanoClock.nanoTime());\n\n        return position;\n    }\n\n    private ReceiveChannelEndpoint getOrCreateReceiveChannelEndpoint(\n        final SubscriptionParams params, final UdpChannel udpChannel, final long registrationId)\n    {\n        ReceiveChannelEndpoint channelEndpoint = findExistingReceiveChannelEndpoint(udpChannel);\n        if (null == channelEndpoint)\n        {\n            AtomicCounter channelStatus = null;\n            AtomicCounter localSocketAddressIndicator = null;\n            try\n            {\n                final String channel = udpChannel.originalUriString();\n                channelStatus = ReceiveChannelStatus.allocate(tempBuffer, countersManager, registrationId, channel);\n\n                final DataPacketDispatcher dispatcher = new DataPacketDispatcher(\n                    ctx.driverConductorProxy(), receiverProxy.receiver(), ctx.streamSessionLimit());\n                channelEndpoint = ctx.receiveChannelEndpointSupplier().newInstance(\n                    udpChannel, dispatcher, channelStatus, ctx);\n\n                if (!udpChannel.isManualControlMode())\n                {\n                    localSocketAddressIndicator = ReceiveLocalSocketAddress.allocate(\n                        tempBuffer, countersManager, registrationId, channelEndpoint.statusIndicatorCounter().id());\n\n                    channelEndpoint.localSocketAddressIndicator(localSocketAddressIndicator);\n                }\n\n                validateInitialWindowForRcvBuf(params, channel, channelEndpoint.socketRcvbufLength(), ctx, null);\n\n                channelEndpoint.openChannel();\n                channelEndpoint.indicateActive();\n\n                receiverProxy.registerReceiveChannelEndpoint(channelEndpoint);\n                receiveChannelEndpointByChannelMap.put(udpChannel.canonicalForm(), channelEndpoint);\n            }\n            catch (final Exception ex)\n            {\n                CloseHelper.closeAll(channelStatus, localSocketAddressIndicator, channelEndpoint);\n                throw ex;\n            }\n        }\n        else\n        {\n            validateInitialWindowForRcvBuf(\n                params,\n                udpChannel.originalUriString(),\n                channelEndpoint.socketRcvbufLength(),\n                ctx,\n                channelEndpoint.originalUriString());\n            validateChannelBufferLength(\n                SOCKET_RCVBUF_PARAM_NAME,\n                udpChannel.socketRcvbufLength(),\n                channelEndpoint.socketRcvbufLength(),\n                udpChannel.originalUriString(),\n                channelEndpoint.originalUriString());\n            validateChannelBufferLength(\n                SOCKET_SNDBUF_PARAM_NAME,\n                udpChannel.socketSndbufLength(),\n                channelEndpoint.socketSndbufLength(),\n                udpChannel.originalUriString(),\n                channelEndpoint.originalUriString());\n        }\n\n        return channelEndpoint;\n    }\n\n    private ReceiveChannelEndpoint findExistingReceiveChannelEndpoint(final UdpChannel udpChannel)\n    {\n        if (udpChannel.hasTag())\n        {\n            for (final ReceiveChannelEndpoint endpoint : receiveChannelEndpointByChannelMap.values())\n            {\n                if (endpoint.matchesTag(udpChannel))\n                {\n                    return endpoint;\n                }\n            }\n        }\n\n        ReceiveChannelEndpoint endpoint = receiveChannelEndpointByChannelMap.get(udpChannel.canonicalForm());\n        if (null != endpoint && endpoint.hasTag() && udpChannel.hasTag() && endpoint.tag() != udpChannel.tag())\n        {\n            endpoint = null;\n        }\n\n        return endpoint;\n    }\n\n    private AeronClient getOrAddClient(final long clientId)\n    {\n        AeronClient client = findClient(clients, clientId);\n        if (null == client)\n        {\n            final AtomicCounter counter = ClientHeartbeatTimestamp.allocate(tempBuffer, countersManager, clientId);\n            final int counterId = counter.id();\n\n            counter.setRelease(cachedEpochClock.time());\n            countersManager.setCounterRegistrationId(counterId, clientId);\n            countersManager.setCounterOwnerId(counterId, clientId);\n\n            client = new AeronClient(\n                clientId,\n                clientLivenessTimeoutNs,\n                ctx.systemCounters().get(SystemCounterDescriptor.CLIENT_TIMEOUTS),\n                counter);\n            clients.add(client);\n\n            clientProxy.onCounterReady(clientId, counterId);\n        }\n\n        return client;\n    }\n\n    private IpcPublication addIpcPublication(\n        final long registrationId,\n        final long clientId,\n        final int streamId,\n        final String channel,\n        final boolean isExclusive,\n        final PublicationParams params)\n    {\n        final int termOffset = params.termOffset;\n\n        final RawLog rawLog = newIpcPublicationLog(\n            isExclusive, params.sessionId, streamId, params.initialTermId, registrationId, termOffset, params);\n\n        UnsafeBufferPosition publisherPosition = null;\n        UnsafeBufferPosition publisherLimit = null;\n        try\n        {\n            publisherPosition = PublisherPos.allocate(\n                tempBuffer,\n                countersManager,\n                clientId,\n                registrationId,\n                params.sessionId,\n                streamId,\n                channel,\n                isExclusive);\n            publisherLimit = PublisherLimit.allocate(\n                tempBuffer, countersManager, clientId, registrationId, params.sessionId, streamId, channel);\n\n            if (params.hasPosition)\n            {\n                final int positionBitsToShift = positionBitsToShift(params.termLength);\n                final long position = computePosition(\n                    params.termId, params.termOffset, positionBitsToShift, params.initialTermId);\n                publisherPosition.setRelease(position);\n                publisherLimit.setRelease(position);\n            }\n\n            final IpcPublication publication = new IpcPublication(\n                registrationId,\n                channel,\n                ctx,\n                params.entityTag,\n                params.sessionId,\n                streamId,\n                publisherPosition,\n                publisherLimit,\n                rawLog,\n                isExclusive,\n                params);\n\n            findAndUpdateResponseIpcSubscription(params, publication);\n\n            ipcPublications.add(publication);\n            activeSessionSet.add(new SessionKey(params.sessionId, streamId, IPC_MEDIA));\n\n            return publication;\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietCloseAll(rawLog, publisherPosition, publisherLimit);\n            throw ex;\n        }\n    }\n\n    private void findAndUpdateResponseIpcSubscription(final PublicationParams params, final IpcPublication publication)\n    {\n        if (NULL_VALUE != params.responseCorrelationId)\n        {\n            for (final IpcPublication ipcPublication : ipcPublications)\n            {\n                if (ipcPublication.registrationId() == params.responseCorrelationId)\n                {\n                    for (int i = 0, n = subscriptionLinks.size(); i < n; i++)\n                    {\n                        final SubscriptionLink subscriptionLink = subscriptionLinks.get(i);\n                        if (ipcPublication.responseCorrelationId() == subscriptionLink.registrationId &&\n                            subscriptionLink instanceof IpcSubscriptionLink)\n                        {\n                            subscriptionLink.sessionId(publication.sessionId());\n                            break;\n                        }\n                    }\n\n                    break;\n                }\n            }\n        }\n    }\n\n    private static AeronClient findClient(final ArrayList<AeronClient> clients, final long clientId)\n    {\n        AeronClient aeronClient = null;\n\n        for (int i = 0, size = clients.size(); i < size; i++)\n        {\n            final AeronClient client = clients.get(i);\n            if (client.clientId() == clientId)\n            {\n                aeronClient = client;\n                break;\n            }\n        }\n\n        return aeronClient;\n    }\n\n    private static SubscriptionLink findMdsSubscriptionLink(\n        final ArrayList<SubscriptionLink> subscriptionLinks, final long registrationId)\n    {\n        SubscriptionLink subscriptionLink = null;\n\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            final SubscriptionLink subscription = subscriptionLinks.get(i);\n            if (subscription.registrationId() == registrationId && subscription.supportsMds())\n            {\n                subscriptionLink = subscription;\n                break;\n            }\n        }\n\n        return subscriptionLink;\n    }\n\n    private static SubscriptionLink removeSubscriptionLink(\n        final ArrayList<SubscriptionLink> subscriptionLinks, final long registrationId, final String channel)\n    {\n        SubscriptionLink subscriptionLink = null;\n\n        for (int i = 0, size = subscriptionLinks.size(); i < size; i++)\n        {\n            final SubscriptionLink subscription = subscriptionLinks.get(i);\n            if (subscription.registrationId() == registrationId && subscription.channel().equals(channel))\n            {\n                subscriptionLink = subscription;\n                fastUnorderedRemove(subscriptionLinks, i);\n                break;\n            }\n        }\n\n        return subscriptionLink;\n    }\n\n    private static IpcPublication findSharedIpcPublication(\n        final ArrayList<IpcPublication> ipcPublications,\n        final long streamId,\n        final long responseCorrelationId)\n    {\n        IpcPublication ipcPublication = null;\n\n        for (int i = 0, size = ipcPublications.size(); i < size; i++)\n        {\n            final IpcPublication publication = ipcPublications.get(i);\n            if (publication.streamId() == streamId &&\n                !publication.isExclusive() &&\n                IpcPublication.State.ACTIVE == publication.state() &&\n                publication.responseCorrelationId() == responseCorrelationId)\n            {\n                ipcPublication = publication;\n                break;\n            }\n        }\n\n        return ipcPublication;\n    }\n\n    private void checkForSessionClash(\n        final int sessionId, final int streamId, final String channel, final String originalChannel)\n    {\n        if (activeSessionSet.contains(new SessionKey(sessionId, streamId, channel)))\n        {\n            throw new InvalidChannelException(\"existing publication has clashing sessionId=\" + sessionId +\n                \" for streamId=\" + streamId + \" channel=\" + originalChannel);\n        }\n    }\n\n    private <T extends DriverManagedResource> void checkManagedResources(\n        final ArrayList<T> list, final long nowNs, final long nowMs)\n    {\n        for (int lastIndex = list.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final DriverManagedResource resource = list.get(i);\n\n            resource.onTimeEvent(nowNs, nowMs, this);\n\n            if (resource.hasReachedEndOfLife())\n            {\n                CloseHelper.close(ctx.errorHandler(), resource::close);\n                endOfLifeResources.add(resource);\n                fastUnorderedRemove(list, i, lastIndex--);\n            }\n        }\n    }\n\n    private int freeEndOfLifeResources(final int freeLimit)\n    {\n        int workCount = 0;\n\n        for (int i = 0; i < freeLimit; i++)\n        {\n            final DriverManagedResource resource = endOfLifeResources.pollFirst();\n            if (null == resource)\n            {\n                break;\n            }\n\n            if (resource.free())\n            {\n                workCount++;\n            }\n            else\n            {\n                ctx.systemCounters().get(FREE_FAILS).incrementRelease();\n                endOfLifeResources.addLast(resource);\n            }\n        }\n\n        return workCount;\n    }\n\n    private void linkSpies(final ArrayList<SubscriptionLink> links, final NetworkPublication publication)\n    {\n        for (int i = 0, size = links.size(); i < size; i++)\n        {\n            final SubscriptionLink subscription = links.get(i);\n            if (subscription.matches(publication) && !subscription.isLinked(publication))\n            {\n                clientProxy.onAvailableImage(\n                    publication.registrationId(),\n                    publication.streamId(),\n                    publication.sessionId(),\n                    subscription.registrationId(),\n                    linkSpy(publication, subscription).id(),\n                    publication.rawLog().fileName(),\n                    IPC_CHANNEL);\n            }\n        }\n    }\n\n    private void trackTime(final long nowNs)\n    {\n        cachedNanoClock.update(nowNs);\n        dutyCycleTracker.measureAndUpdate(nowNs);\n\n        if (clockUpdateDeadlineNs - nowNs < 0)\n        {\n            clockUpdateDeadlineNs = nowNs + CLOCK_UPDATE_INTERNAL_NS;\n            cachedEpochClock.update(epochClock.time());\n        }\n    }\n\n    private int processTimers(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (timerCheckDeadlineNs - nowNs < 0)\n        {\n            timerCheckDeadlineNs = nowNs + timerIntervalNs;\n            heartbeatAndCheckTimers(nowNs);\n            checkForBlockedToDriverCommands(nowNs);\n            workCount = 1;\n        }\n\n        return workCount;\n    }\n\n    private static boolean isOldestSubscriptionSparse(final ArrayList<SubscriberPosition> subscriberPositions)\n    {\n        final SubscriberPosition subscriberPosition = subscriberPositions.get(0);\n        long regId = subscriberPosition.subscription().registrationId();\n        boolean isSparse = subscriberPosition.subscription().isSparse();\n\n        for (int i = 1, size = subscriberPositions.size(); i < size; i++)\n        {\n            final SubscriptionLink subscription = subscriberPositions.get(i).subscription();\n            if (subscription.registrationId() < regId)\n            {\n                isSparse = subscription.isSparse();\n                regId = subscription.registrationId();\n            }\n        }\n\n        return isSparse;\n    }\n\n    private int trackStreamPositions(final int existingWorkCount, final long nowNs)\n    {\n        int workCount = existingWorkCount;\n\n        final ArrayList<PublicationImage> publicationImages = this.publicationImages;\n        for (int i = 0, size = publicationImages.size(); i < size; i++)\n        {\n            workCount += publicationImages.get(i).trackRebuild(nowNs);\n        }\n\n        final ArrayList<NetworkPublication> networkPublications = this.networkPublications;\n        for (int i = 0, size = networkPublications.size(); i < size; i++)\n        {\n            workCount += networkPublications.get(i).updatePublisherPositionAndLimit();\n        }\n\n        final ArrayList<IpcPublication> ipcPublications = this.ipcPublications;\n        for (int i = 0, size = ipcPublications.size(); i < size; i++)\n        {\n            workCount += ipcPublications.get(i).updatePublisherPositionAndLimit();\n        }\n\n        return workCount;\n    }\n\n    private int drainCommandQueue()\n    {\n        int workCount = 0;\n        for (int i = 0; i < Configuration.COMMAND_DRAIN_LIMIT; i++)\n        {\n            final Runnable command = driverCmdQueue.poll();\n            if (null != command)\n            {\n                command.run();\n                workCount++;\n            }\n            else\n            {\n                break;\n            }\n        }\n        return workCount;\n    }\n\n    private static void validateChannelBufferLength(\n        final String paramName,\n        final int newLength,\n        final int existingLength,\n        final String channel,\n        final String existingChannel)\n    {\n        if (0 != newLength && newLength != existingLength)\n        {\n            final Object existingValue = 0 == existingLength ? \"OS default\" : existingLength;\n            throw new InvalidChannelException(\n                paramName + \"=\" + newLength + \" does not match existing value of \" + existingValue +\n                    \": existingChannel=\" + existingChannel + \" channel=\" + channel);\n        }\n    }\n\n    private static void validateEndpointForPublication(final UdpChannel udpChannel)\n    {\n        if (!udpChannel.isMultiDestination() && udpChannel.hasExplicitEndpoint() &&\n            0 == udpChannel.remoteData().getPort())\n        {\n            throw new IllegalArgumentException(\n                ENDPOINT_PARAM_NAME + \" has port=0 for publication: channel=\" + udpChannel.originalUriString());\n        }\n    }\n\n    private static void validateControlForPublication(final UdpChannel udpChannel)\n    {\n        if (udpChannel.isDynamicControlMode() && !udpChannel.hasExplicitControl())\n        {\n            throw new IllegalArgumentException(\n                \"'control-mode=dynamic' requires that 'control' parameter is set, channel=\" +\n                    udpChannel.originalUriString());\n        }\n\n        if (udpChannel.hasExplicitControl() && !udpChannel.hasExplicitEndpoint() &&\n            ControlMode.NONE == udpChannel.controlMode())\n        {\n            throw new IllegalArgumentException(\n                \"'control' parameter requires that either 'endpoint' or 'control-mode' is specified, channel=\" +\n                    udpChannel.originalUriString());\n        }\n    }\n\n    private static void validateControlForSubscription(final UdpChannel udpChannel)\n    {\n        if (udpChannel.hasExplicitControl() &&\n            0 == udpChannel.localControl().getPort())\n        {\n            throw new IllegalArgumentException(MDC_CONTROL_PARAM_NAME + \" has port=0 for subscription: channel=\" +\n                udpChannel.originalUriString());\n        }\n    }\n\n    private static void validateTimestampConfiguration(final UdpChannel udpChannel)\n    {\n        if (null != udpChannel.channelUri().get(MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME))\n        {\n            throw new InvalidChannelException(\n                \"Media timestamps '\" + MEDIA_RCV_TIMESTAMP_OFFSET_PARAM_NAME +\n                    \"' are not supported in the Java driver: channel=\" + udpChannel.originalUriString());\n        }\n    }\n\n    private static void validateDestinationUri(final ChannelUri uri, final String destinationUri)\n    {\n        if (SPY_QUALIFIER.equals(uri.prefix()))\n        {\n            throw new InvalidChannelException(\"Aeron spies are invalid as send destinations: channel=\" +\n                destinationUri);\n        }\n\n        for (final String invalidKey : INVALID_DESTINATION_KEYS)\n        {\n            if (uri.containsKey(invalidKey))\n            {\n                throw new InvalidChannelException(\n                    \"destinations must not contain the key: \" + invalidKey + \" channel=\" + destinationUri);\n            }\n        }\n\n        if (Objects.equals(CONTROL_MODE_RESPONSE, uri.get(MDC_CONTROL_MODE_PARAM_NAME)))\n        {\n            throw new InvalidChannelException(\"destinations may not specify \" +\n                MDC_CONTROL_MODE_PARAM_NAME + \"=\" + CONTROL_MODE_RESPONSE);\n        }\n    }\n\n    private static void validateSendDestinationUri(final ChannelUri uri, final String destinationUri)\n    {\n        final String endpoint = uri.get(ENDPOINT_PARAM_NAME);\n\n        if (null != endpoint && endpoint.endsWith(\":0\"))\n        {\n            throw new InvalidChannelException(ENDPOINT_PARAM_NAME + \" has port=0 for send destination: channel=\" +\n                destinationUri);\n        }\n    }\n\n    @SuppressWarnings({ \"unused\", \"UnnecessaryReturnStatement\" })\n    private static void validateExperimentalFeatures(final boolean enableExperimentalFeatures, final UdpChannel channel)\n    {\n        if (enableExperimentalFeatures)\n        {\n            return;\n        }\n\n        /*\n         * Put experimental feature validation here.\n         */\n    }\n\n    static FeedbackDelayGenerator resolveDelayGenerator(\n        final Context ctx,\n        final UdpChannel channel,\n        final boolean isMulticastSemantics,\n        final boolean isReliable)\n    {\n        if (!isReliable)\n        {\n            return StaticDelayGenerator.ZERO_DELAY_GENERATOR;\n        }\n\n        if (isMulticastSemantics)\n        {\n            return ctx.multicastFeedbackDelayGenerator();\n        }\n\n        final Long nakDelayNs = channel.nakDelayNs();\n        if (null != nakDelayNs)\n        {\n            final long retryDelayNs =\n                Math.max(nakDelayNs, Configuration.NAK_UNICAST_DELAY_MIN_VALUE_NS) * ctx.nakUnicastRetryDelayRatio();\n            return new StaticDelayGenerator(nakDelayNs, retryDelayNs);\n        }\n        else\n        {\n            return ctx.unicastFeedbackDelayGenerator();\n        }\n    }\n\n    static boolean isMulticastSemantics(\n        final UdpChannel channel,\n        final InferableBoolean receiverGroupConsideration,\n        final short flags)\n    {\n        final boolean isGroupFromFlag = (flags & SetupFlyweight.GROUP_FLAG) == SetupFlyweight.GROUP_FLAG;\n\n        return receiverGroupConsideration == INFER ?\n            channel.isMulticast() || isGroupFromFlag :\n            receiverGroupConsideration == FORCE_TRUE;\n    }\n\n    private interface AsyncResult<T> extends Supplier<T>\n    {\n        T get();\n\n        static <T> AsyncResult<T> of(final Supplier<T> supplier)\n        {\n            try\n            {\n                final T value = supplier.get();\n                return () -> value;\n            }\n            catch (final Throwable t)\n            {\n                return () ->\n                {\n                    LangUtil.rethrowUnchecked(t);\n                    return null;\n                };\n            }\n        }\n    }\n\n    private void recordError(final Exception ex)\n    {\n        ctx.errorHandler().onError(ex);\n        errorCounter.increment();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DriverConductorProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.ReceiveDestinationTransport;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.ManyToOneConcurrentLinkedQueue;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.net.InetSocketAddress;\n\nimport static io.aeron.driver.ThreadingMode.INVOKER;\nimport static io.aeron.driver.ThreadingMode.SHARED;\n\n/**\n * Proxy for sending commands to the {@link DriverConductor}.\n */\npublic final class DriverConductorProxy\n{\n    private DriverConductor driverConductor;\n    private final ThreadingMode threadingMode;\n    private final ManyToOneConcurrentLinkedQueue<Runnable> commandQueue;\n    private final AtomicCounter failCount;\n    private final boolean notConcurrent;\n\n    DriverConductorProxy(\n        final ThreadingMode threadingMode,\n        final ManyToOneConcurrentLinkedQueue<Runnable> commandQueue,\n        final AtomicCounter failCount)\n    {\n        this.threadingMode = threadingMode;\n        this.commandQueue = commandQueue;\n        this.failCount = failCount;\n        notConcurrent = SHARED == threadingMode || INVOKER == threadingMode;\n    }\n\n    /**\n     * Is the driver conductor not concurrent with the sender and receiver threads.\n     *\n     * @return true if the {@link DriverConductor} is on the same thread as the sender and receiver.\n     */\n    public boolean notConcurrent()\n    {\n        return notConcurrent;\n    }\n\n    /**\n     * Get the threading mode of the driver.\n     *\n     * @return ThreadingMode of the driver.\n     */\n    public ThreadingMode threadingMode()\n    {\n        return threadingMode;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return getClass().getSimpleName() + \"{\" +\n            \"threadingMode=\" + threadingMode +\n            \", failCount=\" + failCount +\n            '}';\n    }\n\n    /**\n     * Notify the conductor indicating an error with a channel endpoint.\n     *\n     * @param statusIndicatorId representing the channel.\n     * @param ex                cause of the error.\n     */\n    public void channelEndpointError(final long statusIndicatorId, final Exception ex)\n    {\n    }\n\n    /**\n     * Request the conductor re-resolve an endpoint address.\n     *\n     * @param endpoint        in string format.\n     * @param channelEndpoint that the endpoint belongs to.\n     * @param address         of previous resolution.\n     */\n    public void reResolveEndpoint(\n        final String endpoint, final SendChannelEndpoint channelEndpoint, final InetSocketAddress address)\n    {\n        if (notConcurrent())\n        {\n            driverConductor.onReResolveEndpoint(endpoint, channelEndpoint, address);\n        }\n        else\n        {\n            offer(() -> driverConductor.onReResolveEndpoint(endpoint, channelEndpoint, address));\n        }\n    }\n\n    /**\n     * Re-resolve a control endpoint for a channel.\n     *\n     * @param endpoint        to be re-resolved.\n     * @param udpChannel      which contained the endpoint.\n     * @param channelEndpoint to which the endpoint belongs.\n     * @param address         of previous resolution.\n     */\n    public void reResolveControl(\n        final String endpoint,\n        final UdpChannel udpChannel,\n        final ReceiveChannelEndpoint channelEndpoint,\n        final InetSocketAddress address)\n    {\n        if (notConcurrent())\n        {\n            driverConductor.onReResolveControl(endpoint, udpChannel, channelEndpoint, address);\n        }\n        else\n        {\n            offer(() -> driverConductor.onReResolveControl(endpoint, udpChannel, channelEndpoint, address));\n        }\n    }\n\n    /**\n     * Close a receive destination.\n     *\n     * @param destinationTransport to have its indicators closed.\n     */\n    public void closeReceiveDestination(final ReceiveDestinationTransport destinationTransport)\n    {\n        if (notConcurrent())\n        {\n            driverConductor.closeReceiveDestination(destinationTransport);\n        }\n        else\n        {\n            offer(() -> driverConductor.closeReceiveDestination(destinationTransport));\n        }\n    }\n\n    /**\n     * Handle a response setup message from the remote \"server\".\n     *\n     * @param responseCorrelationId correlationId of the subscription that will handle responses.\n     * @param responseSessionId     sessionId that will be associated with the incoming messages.\n     */\n    public void responseSetup(final long responseCorrelationId, final int responseSessionId)\n    {\n        if (notConcurrent())\n        {\n            driverConductor.responseSetup(responseCorrelationId, responseSessionId);\n        }\n        else\n        {\n            offer(() -> driverConductor.responseSetup(responseCorrelationId, responseSessionId));\n        }\n    }\n\n    /**\n     * Handle notify that a response channel has connected.\n     *\n     * @param responseCorrelationId correlationId of the subscription that will handle responses.\n     */\n    public void responseConnected(final long responseCorrelationId)\n    {\n        if (notConcurrent())\n        {\n            driverConductor.responseConnected(responseCorrelationId);\n        }\n        else\n        {\n            offer(() -> driverConductor.responseConnected(responseCorrelationId));\n        }\n    }\n\n    void receiveChannelEndpointClosed(final ReceiveChannelEndpoint channelEndpoint)\n    {\n        if (notConcurrent())\n        {\n            driverConductor.receiveChannelEndpointClosed(channelEndpoint);\n        }\n        else\n        {\n            offer(() -> driverConductor.receiveChannelEndpointClosed(channelEndpoint));\n        }\n    }\n\n    void sendChannelEndpointClosed(final SendChannelEndpoint channelEndpoint)\n    {\n        if (notConcurrent())\n        {\n            driverConductor.sendChannelEndpointClosed(channelEndpoint);\n        }\n        else\n        {\n            offer(() -> driverConductor.sendChannelEndpointClosed(channelEndpoint));\n        }\n    }\n\n    void driverConductor(final DriverConductor driverConductor)\n    {\n        this.driverConductor = driverConductor;\n    }\n\n    void createPublicationImage(\n        final int sessionId,\n        final int streamId,\n        final int initialTermId,\n        final int activeTermId,\n        final int termOffset,\n        final int termLength,\n        final int mtuLength,\n        final int transportIndex,\n        final short flags,\n        final InetSocketAddress controlAddress,\n        final InetSocketAddress srcAddress,\n        final ReceiveChannelEndpoint channelEndpoint)\n    {\n        if (notConcurrent())\n        {\n            driverConductor.onCreatePublicationImage(\n                sessionId,\n                streamId,\n                initialTermId,\n                activeTermId,\n                termOffset,\n                termLength,\n                mtuLength,\n                transportIndex,\n                flags,\n                controlAddress,\n                srcAddress,\n                channelEndpoint);\n        }\n        else\n        {\n            offer(() -> driverConductor.onCreatePublicationImage(\n                sessionId,\n                streamId,\n                initialTermId,\n                activeTermId,\n                termOffset,\n                termLength,\n                mtuLength,\n                transportIndex,\n                flags,\n                controlAddress,\n                srcAddress,\n                channelEndpoint));\n        }\n    }\n\n    void onPublicationError(\n        final long registrationId,\n        final long destinationRegistrationId,\n        final int sessionId,\n        final int streamId,\n        final long receiverId,\n        final long groupId,\n        final InetSocketAddress srcAddress,\n        final int errorCode,\n        final String errorMessage)\n    {\n        if (notConcurrent())\n        {\n            driverConductor.onPublicationError(\n                registrationId,\n                destinationRegistrationId,\n                sessionId,\n                streamId,\n                receiverId,\n                groupId,\n                srcAddress,\n                errorCode,\n                errorMessage);\n        }\n        else\n        {\n            offer(() -> driverConductor.onPublicationError(\n                registrationId,\n                destinationRegistrationId,\n                sessionId,\n                streamId,\n                receiverId,\n                groupId,\n                srcAddress,\n                errorCode,\n                errorMessage));\n        }\n    }\n\n    private void offer(final Runnable cmd)\n    {\n        if (!commandQueue.offer(cmd))\n        {\n            throw new IllegalStateException(\"offer failed\");\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DriverManagedResource.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\n/**\n * Common behaviour supported for driver resources such as publications and images.\n */\npublic interface DriverManagedResource\n{\n    /**\n     * Free external resources such as files. If successful then return true.\n     *\n     * @return true if successful and false if it should be attempted again later.\n     */\n    default boolean free()\n    {\n        return true;\n    }\n\n    /**\n     * Close resources that are not external. This will not call {@link DriverManagedResource#free()}. That will\n     * need to be called separately at the appropriate time.\n     */\n    void close();\n\n    /**\n     * Inform resource of timeNs passing and pass it DriverConductor to inform of any state transitions.\n     *\n     * @param timeNs    now in nanoseconds.\n     * @param timeMs    now in milliseconds for epoch.\n     * @param conductor to inform of any state transitions.\n     */\n    void onTimeEvent(long timeNs, long timeMs, DriverConductor conductor);\n\n    /**\n     * Has resource reached end of its life and should be reclaimed?\n     *\n     * @return whether resource has reached end of life or not.\n     */\n    boolean hasReachedEndOfLife();\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DriverNameResolver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.AeronCounters;\nimport io.aeron.driver.media.NetworkUtil;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.media.UdpNameResolutionTransport;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.protocol.HeaderFlyweight;\nimport io.aeron.protocol.ResolutionEntryFlyweight;\nimport org.agrona.BufferUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.LangUtil;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.UnknownHostException;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.DriverNameResolverCache.fullLengthMatch;\nimport static io.aeron.driver.media.NetworkUtil.formatAddressAndPort;\nimport static io.aeron.protocol.ResolutionEntryFlyweight.*;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\n\n/**\n * Default {@link NameResolver} for the {@link MediaDriver}.\n */\nfinal class DriverNameResolver implements AutoCloseable, UdpNameResolutionTransport.UdpFrameHandler, NameResolver\n{\n    static NameResolver bootstrapNameResolver = DefaultNameResolver.INSTANCE;\n    private static final String RESOLVER_NEIGHBORS_COUNTER_LABEL = \"Resolver neighbors\";\n\n    // TODO: make these configurable\n    private static final long SELF_RESOLUTION_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);\n    static final long NEIGHBOR_RESOLUTION_INTERVAL_MS = TimeUnit.SECONDS.toMillis(2);\n    private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);\n    private static final long WORK_INTERVAL_MS = 10;\n\n    private final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(\n        Configuration.MAX_UDP_PAYLOAD_LENGTH, CACHE_LINE_LENGTH);\n    private final UnsafeBuffer unsafeBuffer = new UnsafeBuffer(byteBuffer);\n    private final HeaderFlyweight headerFlyweight = new HeaderFlyweight(unsafeBuffer);\n    private final ResolutionEntryFlyweight resolutionEntryFlyweight = new ResolutionEntryFlyweight();\n    private final ArrayList<Neighbor> neighborList = new ArrayList<>();\n\n    private final UdpNameResolutionTransport transport;\n    private final DriverNameResolverCache cache;\n    private final NameResolver delegateResolver;\n    private final AtomicCounter invalidPackets;\n    private final AtomicCounter shortSends;\n    private final AtomicCounter neighborsCounter;\n    private final AtomicCounter cacheEntriesCounter;\n    private final byte[] nameTempBuffer = new byte[ResolutionEntryFlyweight.MAX_NAME_LENGTH];\n    private final byte[] addressTempBuffer = new byte[ResolutionEntryFlyweight.ADDRESS_LENGTH_IP6];\n\n    private final String localDriverName;\n    private InetSocketAddress localSocketAddress;\n    private final byte[] localName;\n    private byte[] localAddress;\n\n    private final String[] bootstrapNeighbors;\n    private InetSocketAddress bootstrapNeighborAddress;\n    private long bootstrapNeighborResolveDeadlineMs;\n\n    private final long neighborTimeoutMs = TIMEOUT_MS;\n    private final long selfResolutionIntervalMs = SELF_RESOLUTION_INTERVAL_MS;\n    private final long neighborResolutionIntervalMs = NEIGHBOR_RESOLUTION_INTERVAL_MS;\n    private final int mtuLength;\n    private final boolean preferIPv6 = false;\n\n    private long workDeadlineMs = 0;\n    private long selfResolutionDeadlineMs;\n    private long neighborResolutionDeadlineMs;\n\n    DriverNameResolver(final MediaDriver.Context ctx)\n    {\n        mtuLength = ctx.mtuLength();\n        invalidPackets = ctx.systemCounters().get(SystemCounterDescriptor.INVALID_PACKETS);\n        shortSends = ctx.systemCounters().get(SystemCounterDescriptor.SHORT_SENDS);\n        delegateResolver = ctx.nameResolver();\n\n        localDriverName = ctx.resolverName();\n        localName = localDriverName.getBytes(StandardCharsets.US_ASCII);\n        localSocketAddress = UdpNameResolutionTransport.getInterfaceAddress(ctx.resolverInterface());\n        localAddress = localSocketAddress.getAddress().getAddress();\n\n        bootstrapNeighbors = null != ctx.resolverBootstrapNeighbor() ?\n            ctx.resolverBootstrapNeighbor().split(\",\") : null;\n        if (null != bootstrapNeighbors)\n        {\n            final long nowNs = ctx.nanoClock().nanoTime();\n            final DutyCycleTracker nameResolverTimeTracker = ctx.nameResolverTimeTracker();\n            nameResolverTimeTracker.update(nowNs);\n\n            bootstrapNeighborAddress = resolveBootstrapNeighbor();\n\n            final long endNs = ctx.nanoClock().nanoTime();\n            nameResolverTimeTracker.measureAndUpdate(endNs);\n        }\n        else\n        {\n            bootstrapNeighborAddress = null;\n        }\n\n        final long nowMs = ctx.epochClock().time();\n        bootstrapNeighborResolveDeadlineMs = nowMs + TIMEOUT_MS;\n\n        selfResolutionDeadlineMs = 0;\n        neighborResolutionDeadlineMs = nowMs + neighborResolutionIntervalMs;\n\n        cache = new DriverNameResolverCache(TIMEOUT_MS);\n\n        final UdpChannel resolverChannel =\n            UdpChannel.parse(\"aeron:udp?endpoint=\" +\n                NetworkUtil.formatAddressAndPort(localSocketAddress.getAddress(), localSocketAddress.getPort()),\n                delegateResolver);\n        transport = new UdpNameResolutionTransport(resolverChannel, localSocketAddress, unsafeBuffer, ctx);\n\n        neighborsCounter = ctx.countersManager().newCounter(\n            RESOLVER_NEIGHBORS_COUNTER_LABEL, AeronCounters.NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID);\n        cacheEntriesCounter = ctx.countersManager().newCounter(\n            \"Resolver cache entries: name=\" + localDriverName,\n            AeronCounters.NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID);\n\n        openDatagramChannel();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(transport);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (workDeadlineMs - nowMs < 0)\n        {\n            workDeadlineMs = nowMs + WORK_INTERVAL_MS;\n            workCount += transport.poll(this, nowMs);\n            workCount += cache.timeoutOldEntries(nowMs, cacheEntriesCounter);\n            workCount += timeoutNeighbors(nowMs);\n\n            if (nowMs > selfResolutionDeadlineMs)\n            {\n                sendSelfResolutions(nowMs);\n            }\n\n            if (nowMs > neighborResolutionDeadlineMs)\n            {\n                sendNeighborResolutions(nowMs);\n            }\n        }\n\n        return workCount;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public InetAddress resolve(final String name, final String uriParamName, final boolean isReResolution)\n    {\n        DriverNameResolverCache.CacheEntry entry;\n\n        if (preferIPv6)\n        {\n            entry = cache.lookup(name, RES_TYPE_NAME_TO_IP6_MD);\n            if (null == entry)\n            {\n                entry = cache.lookup(name, RES_TYPE_NAME_TO_IP4_MD);\n            }\n        }\n        else\n        {\n            entry = cache.lookup(name, RES_TYPE_NAME_TO_IP4_MD);\n        }\n\n        InetAddress resolvedAddress = null;\n        try\n        {\n            if (null == entry)\n            {\n                if (name.equals(localDriverName))\n                {\n                    return localSocketAddress.getAddress();\n                }\n\n                return delegateResolver.resolve(name, uriParamName, isReResolution);\n            }\n\n            resolvedAddress = InetAddress.getByAddress(entry.address);\n        }\n        catch (final UnknownHostException ignore)\n        {\n        }\n\n        return resolvedAddress;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String lookup(final String name, final String uriParamName, final boolean isReLookup)\n    {\n        // here we would look up advertised endpoints/control IP:port pairs by name. Currently, we just return delegate.\n        return delegateResolver.lookup(name, uriParamName, isReLookup);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int onFrame(\n        final UnsafeBuffer unsafeBuffer, final int length, final InetSocketAddress srcAddress, final long nowMs)\n    {\n        if (headerFlyweight.headerType() == HDR_TYPE_RES)\n        {\n            int offset = MIN_HEADER_LENGTH;\n\n            while (length > offset)\n            {\n                resolutionEntryFlyweight.wrap(unsafeBuffer, offset, length - offset);\n\n                if ((length - offset) < resolutionEntryFlyweight.entryLength())\n                {\n                    invalidPackets.increment();\n                    return 0;\n                }\n\n                onResolutionEntry(resolutionEntryFlyweight, srcAddress, nowMs);\n\n                offset += resolutionEntryFlyweight.entryLength();\n            }\n\n            return length;\n        }\n\n        return 0;\n    }\n\n    private void openDatagramChannel()\n    {\n        transport.openDatagramChannel(null);\n\n        final InetSocketAddress boundAddress = transport.boundAddress();\n        if (null != boundAddress)\n        {\n            localSocketAddress = boundAddress;\n            localAddress = boundAddress.getAddress().getAddress();\n\n            neighborsCounter.updateLabel(buildNeighborsCounterLabel());\n        }\n    }\n\n    private String buildNeighborsCounterLabel()\n    {\n        final StringBuilder builder = new StringBuilder(RESOLVER_NEIGHBORS_COUNTER_LABEL);\n        builder.append(\": bound \").append(transport.bindAddressAndPort());\n\n        if (null != bootstrapNeighborAddress)\n        {\n            builder.append(\" bootstrap \").append(\n                formatAddressAndPort(bootstrapNeighborAddress.getAddress(), bootstrapNeighborAddress.getPort()));\n        }\n\n        return builder.toString();\n    }\n\n    private int timeoutNeighbors(final long nowMs)\n    {\n        int workCount = 0;\n\n        final ArrayList<Neighbor> neighborList = this.neighborList;\n        for (int lastIndex = neighborList.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final Neighbor neighbor = neighborList.get(i);\n\n            if (nowMs > (neighbor.timeOfLastActivityMs + neighborTimeoutMs))\n            {\n                Neighbor.neighborRemoved(nowMs, neighbor.socketAddress);\n                ArrayListUtil.fastUnorderedRemove(neighborList, i, lastIndex--);\n                workCount++;\n            }\n        }\n\n        final int neighborCount = neighborList.size();\n        if (neighborsCounter.getPlain() != neighborCount)\n        {\n            neighborsCounter.setRelease(neighborCount);\n        }\n\n        return workCount;\n    }\n\n    private void sendSelfResolutions(final long nowMs)\n    {\n        byteBuffer.clear();\n\n        final int currentOffset = HeaderFlyweight.MIN_HEADER_LENGTH;\n        final byte resType = preferIPv6 ? RES_TYPE_NAME_TO_IP6_MD : RES_TYPE_NAME_TO_IP4_MD;\n\n        headerFlyweight\n            .headerType(HeaderFlyweight.HDR_TYPE_RES)\n            .flags((short)0)\n            .version(HeaderFlyweight.CURRENT_VERSION);\n\n        resolutionEntryFlyweight.wrap(unsafeBuffer, currentOffset, unsafeBuffer.capacity() - currentOffset);\n        resolutionEntryFlyweight\n            .resType(resType)\n            .flags(SELF_FLAG)\n            .udpPort((short)localSocketAddress.getPort())\n            .ageInMs(0)\n            .putAddress(localAddress)\n            .putName(localName);\n\n        final int length = resolutionEntryFlyweight.entryLength() + MIN_HEADER_LENGTH;\n        headerFlyweight.frameLength(length);\n\n        byteBuffer.limit(length);\n\n        boolean sendToBootstrap = null != bootstrapNeighborAddress;\n        for (final Neighbor neighbor : neighborList)\n        {\n            sendResolutionFrameTo(byteBuffer, neighbor.socketAddress);\n\n            if (sendToBootstrap && neighbor.socketAddress.equals(bootstrapNeighborAddress))\n            {\n                sendToBootstrap = false;\n            }\n        }\n\n        if (sendToBootstrap)\n        {\n            if (nowMs > bootstrapNeighborResolveDeadlineMs)\n            {\n                final InetSocketAddress oldAddress = this.bootstrapNeighborAddress;\n                bootstrapNeighborAddress = resolveBootstrapNeighbor();\n                bootstrapNeighborResolveDeadlineMs = nowMs + TIMEOUT_MS;\n\n                if (!oldAddress.equals(bootstrapNeighborAddress))\n                {\n                    neighborsCounter.updateLabel(buildNeighborsCounterLabel());\n\n                    // avoid sending resolution frame if new bootstrap is in the neighbors list\n                    for (final Neighbor neighbor : neighborList)\n                    {\n                        if (neighbor.socketAddress.equals(bootstrapNeighborAddress))\n                        {\n                            sendToBootstrap = false;\n                            break;\n                        }\n                    }\n                }\n            }\n\n            if (sendToBootstrap)\n            {\n                sendResolutionFrameTo(byteBuffer, bootstrapNeighborAddress);\n            }\n        }\n\n        selfResolutionDeadlineMs = nowMs + selfResolutionIntervalMs;\n    }\n\n    private void sendResolutionFrameTo(final ByteBuffer buffer, final InetSocketAddress remoteAddress)\n    {\n        buffer.position(0);\n\n        final int bytesRemaining = buffer.remaining();\n        final int bytesSent = transport.sendTo(buffer, remoteAddress);\n\n        if (0 <= bytesSent && bytesSent < bytesRemaining)\n        {\n            shortSends.increment();\n        }\n    }\n\n    private void onResolutionEntry(\n        final ResolutionEntryFlyweight resolutionEntry, final InetSocketAddress srcAddress, final long nowMs)\n    {\n        final byte resType = resolutionEntry.resType();\n        final boolean isSelf = SELF_FLAG == resolutionEntryFlyweight.flags();\n        byte[] addr = addressTempBuffer;\n\n        final int addressLength = resolutionEntryFlyweight.getAddress(addressTempBuffer);\n        if (isSelf && ResolutionEntryFlyweight.isAnyLocalAddress(addressTempBuffer, addressLength))\n        {\n            addr = srcAddress.getAddress().getAddress();\n        }\n\n        final int nameLength = resolutionEntryFlyweight.getName(nameTempBuffer);\n        final long timeOfLastActivity = nowMs - resolutionEntryFlyweight.ageInMs();\n        final int port = resolutionEntryFlyweight.udpPort();\n\n        // use name and port to indicate it is from this resolver instead of searching interfaces\n        if (port == localSocketAddress.getPort() && fullLengthMatch(localName, nameTempBuffer, nameLength))\n        {\n            return;\n        }\n\n        cache.addOrUpdateEntry(\n            nameTempBuffer, nameLength, timeOfLastActivity, resType, addr, port, cacheEntriesCounter);\n\n        Neighbor neighbor = findNeighborByAddress(addr, addressLength, port);\n        if (null == neighbor)\n        {\n            try\n            {\n                final byte[] neighborAddress = Arrays.copyOf(addr, addressLength);\n                neighbor = new Neighbor(\n                    new InetSocketAddress(InetAddress.getByAddress(neighborAddress), port),\n                    neighborAddress,\n                    timeOfLastActivity);\n                Neighbor.neighborAdded(nowMs, neighbor.socketAddress);\n                neighborList.add(neighbor);\n                neighborsCounter.setRelease(neighborList.size());\n            }\n            catch (final Exception ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n        }\n        else if (isSelf)\n        {\n            neighbor.timeOfLastActivityMs = timeOfLastActivity;\n        }\n    }\n\n    private Neighbor findNeighborByAddress(final byte[] address, final int addressLength, final int port)\n    {\n        for (final Neighbor neighbor : neighborList)\n        {\n            if (port == neighbor.socketAddress.getPort() &&\n                fullLengthMatch(neighbor.neighborAddress, address, addressLength))\n            {\n                return neighbor;\n            }\n        }\n\n        return null;\n    }\n\n    private void sendNeighborResolutions(final long nowMs)\n    {\n        for (final DriverNameResolverCache.Iterator iter = cache.resetIterator(); iter.hasNext(); )\n        {\n            byteBuffer.clear();\n            headerFlyweight\n                .headerType(HeaderFlyweight.HDR_TYPE_RES)\n                .flags((short)0)\n                .version(HeaderFlyweight.CURRENT_VERSION);\n\n            int currentOffset = HeaderFlyweight.MIN_HEADER_LENGTH;\n\n            while (iter.hasNext())\n            {\n                final DriverNameResolverCache.CacheEntry entry = iter.next();\n\n                if (currentOffset + entryLengthRequired(entry.type, entry.name.length) > mtuLength)\n                {\n                    iter.rewindNext();\n                    break;\n                }\n\n                resolutionEntryFlyweight.wrap(unsafeBuffer, currentOffset, unsafeBuffer.capacity() - currentOffset);\n                resolutionEntryFlyweight\n                    .resType(entry.type)\n                    .flags((short)0)\n                    .udpPort((short)entry.port)\n                    .ageInMs((int)(nowMs - entry.timeOfLastActivityMs))\n                    .putAddress(entry.address)\n                    .putName(entry.name);\n\n                final int length = resolutionEntryFlyweight.entryLength();\n                currentOffset += length;\n            }\n\n            headerFlyweight.frameLength(currentOffset);\n            byteBuffer.limit(currentOffset);\n\n            for (final Neighbor neighbor : neighborList)\n            {\n                sendResolutionFrameTo(byteBuffer, neighbor.socketAddress);\n            }\n        }\n\n        neighborResolutionDeadlineMs = nowMs + neighborResolutionIntervalMs;\n    }\n\n    private InetSocketAddress resolveBootstrapNeighbor()\n    {\n        Exception t = null;\n        for (final String neighbor : bootstrapNeighbors)\n        {\n            try\n            {\n                return UdpNameResolutionTransport.getInetSocketAddress(neighbor, bootstrapNameResolver);\n            }\n            catch (final Exception ex)\n            {\n                if (null == t)\n                {\n                    t = ex;\n                }\n                else\n                {\n                    t.addSuppressed(ex);\n                }\n            }\n        }\n\n        if (null != t)\n        {\n            LangUtil.rethrowUnchecked(t);\n        }\n\n        return null;\n    }\n\n    static class Neighbor\n    {\n        final InetSocketAddress socketAddress;\n        final byte[] neighborAddress;\n        long timeOfLastActivityMs;\n\n        Neighbor(final InetSocketAddress socketAddress, final byte[] neighborAddress, final long nowMs)\n        {\n            this.socketAddress = socketAddress;\n            this.neighborAddress = neighborAddress;\n            this.timeOfLastActivityMs = nowMs;\n        }\n\n        static void neighborAdded(final long nowMs, final InetSocketAddress address)\n        {\n//            System.out.println(nowMs + \" neighbor added: \" + address);\n        }\n\n        static void neighborRemoved(final long nowMs, final InetSocketAddress address)\n        {\n//            System.out.println(nowMs + \" neighbor removed: \" + address);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DriverNameResolverCache.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.protocol.ResolutionEntryFlyweight;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\nfinal class DriverNameResolverCache\n{\n    private final ArrayList<CacheEntry> entries = new ArrayList<>();\n    private final long timeoutMs;\n\n    DriverNameResolverCache(final long timeoutMs)\n    {\n        this.timeoutMs = timeoutMs;\n    }\n\n    CacheEntry lookup(final String name, final byte type)\n    {\n        for (final CacheEntry entry : entries)\n        {\n            if (type == entry.type && fullLengthMatch(entry.name, name))\n            {\n                return entry;\n            }\n        }\n\n        return null;\n    }\n\n    void addOrUpdateEntry(\n        final byte[] name,\n        final int nameLength,\n        final long nowMs,\n        final byte type,\n        final byte[] address,\n        final int port,\n        final AtomicCounter cacheEntriesCounter)\n    {\n        CacheEntry entry = findEntryIndexByNameAndType(name, nameLength, type);\n        final int addressLength = ResolutionEntryFlyweight.addressLength(type);\n\n        if (null == entry)\n        {\n            entry = new CacheEntry(\n                Arrays.copyOf(name, nameLength),\n                type,\n                nowMs,\n                nowMs + timeoutMs,\n                Arrays.copyOf(address, addressLength),\n                port);\n            entries.add(entry);\n            cacheEntriesCounter.setRelease(entries.size());\n        }\n        else\n        {\n            entry.timeOfLastActivityMs = nowMs;\n            entry.deadlineMs = nowMs + timeoutMs;\n\n            if (port != entry.port || !fullLengthMatch(entry.address, address, addressLength))\n            {\n                entry.address = Arrays.copyOf(address, addressLength);\n                entry.port = port;\n            }\n        }\n    }\n\n    int timeoutOldEntries(final long nowMs, final AtomicCounter cacheEntriesCounter)\n    {\n        int workCount = 0;\n\n        final ArrayList<CacheEntry> listOfEntries = this.entries;\n        for (int lastIndex = listOfEntries.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final CacheEntry entry = listOfEntries.get(i);\n\n            if (entry.deadlineMs - nowMs < 0)\n            {\n                ArrayListUtil.fastUnorderedRemove(listOfEntries, i, lastIndex--);\n                cacheEntriesCounter.setRelease(listOfEntries.size());\n                workCount++;\n            }\n        }\n\n        return workCount;\n    }\n\n    Iterator resetIterator()\n    {\n        iterator.cache = this;\n        iterator.index = -1;\n\n        return iterator;\n    }\n\n    static final class Iterator\n    {\n        int index = -1;\n        DriverNameResolverCache cache;\n\n        boolean hasNext()\n        {\n            return (index + 1) < cache.entries.size();\n        }\n\n        CacheEntry next()\n        {\n            return cache.entries.get(++index);\n        }\n\n        void rewindNext()\n        {\n            --index;\n        }\n    }\n\n    private final Iterator iterator = new Iterator();\n\n    static boolean fullLengthMatch(final byte[] expected, final byte[] actual, final int actualLength)\n    {\n        return actualLength == expected.length &&\n            Arrays.equals(expected, 0, actualLength, actual, 0, actualLength);\n    }\n\n    static boolean fullLengthMatch(final byte[] expected, final String actual)\n    {\n        final int length = actual.length();\n        if (length != expected.length)\n        {\n            return false;\n        }\n\n        for (int i = 0; i < length; i++)\n        {\n            if (expected[i] != actual.charAt(i))\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private CacheEntry findEntryIndexByNameAndType(final byte[] name, final int nameLength, final byte type)\n    {\n        for (final CacheEntry entry : entries)\n        {\n            if (type == entry.type && fullLengthMatch(entry.name, name, nameLength))\n            {\n                return entry;\n            }\n        }\n\n        return null;\n    }\n\n    static final class CacheEntry\n    {\n        long deadlineMs;\n        long timeOfLastActivityMs;\n        int port;\n        final byte type;\n        final byte[] name;\n        byte[] address;\n\n        CacheEntry(\n            final byte[] name,\n            final byte type,\n            final long timeOfLastActivityMs,\n            final long deadlineMs,\n            final byte[] address,\n            final int port)\n        {\n            this.name = name;\n            this.type = type;\n            this.timeOfLastActivityMs = timeOfLastActivityMs;\n            this.deadlineMs = deadlineMs;\n            this.address = address;\n            this.port = port;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/DutyCycleTracker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nabstract class DutyCycleTrackerLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nabstract class DutyCycleTrackerFields extends DutyCycleTrackerLhsPadding\n{\n    long timeOfLastUpdateNs;\n}\n\n/**\n * Tracker to handle tracking the duration of a duty cycle.\n */\npublic class DutyCycleTracker extends DutyCycleTrackerFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n\n    /**\n     * Default constructor.\n     */\n    public DutyCycleTracker()\n    {\n    }\n\n    /**\n     * Update the last known clock time.\n     *\n     * @param nowNs to update with.\n     */\n    public final void update(final long nowNs)\n    {\n        timeOfLastUpdateNs = nowNs;\n    }\n\n    /**\n     * Pass measurement to tracker and report updating last known clock time with time.\n     *\n     * @param nowNs of the measurement.\n     */\n    public final void measureAndUpdate(final long nowNs)\n    {\n        final long cycleTimeNs = nowNs - timeOfLastUpdateNs;\n\n        timeOfLastUpdateNs = nowNs;\n        reportMeasurement(cycleTimeNs);\n    }\n\n    /**\n     * Callback called to report duration of cycle.\n     *\n     * @param durationNs of the duty cycle.\n     */\n    public void reportMeasurement(final long durationNs)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"DutyCycleTracker{\" +\n            \"timeOfLastUpdateNs=\" + timeOfLastUpdateNs +\n            '}';\n    }\n}"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/FeedbackDelayGenerator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\n/**\n * Feedback delay generator.\n */\npublic interface FeedbackDelayGenerator\n{\n    /**\n     * Generate a new delay value on initial request.\n     *\n     * @return delay value in nanoseconds\n     */\n    long generateDelayNs();\n\n    /**\n     * Generate a new delay value on a retried request. Implementing this call is optional and will default to\n     * {@link #generateDelayNs()}.\n     *\n     * @return delay value in nanoseconds\n     */\n    default long retryDelayNs()\n    {\n        return generateDelayNs();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/FlowControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CommonContext;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Strategy for applying flow control to the {@link Sender} on each stream.\n */\npublic interface FlowControl extends AutoCloseable\n{\n    /**\n     * Calculates a retransmission length by clamping to a min of <code>resendLength</code>,\n     * <code>termBufferLength - termOffset</code> and <code>retransmitReceiverWindowMultiple *\n     * ${configured initial window length}</code>.\n     *\n     * @param resendLength                     requested length of a retransmit\n     * @param termBufferLength                 length of the current term.\n     * @param termOffset                       offset within the term.\n     * @param retransmitReceiverWindowMultiple multiplier to the receiver window length.\n     * @return the clamped retransmit length\n     */\n    static int calculateRetransmissionLength(\n        final int resendLength,\n        final int termBufferLength,\n        final int termOffset,\n        final int retransmitReceiverWindowMultiple)\n    {\n        final int lengthToEndOfTerm = termBufferLength - termOffset;\n        final int estimatedRetransmitLength = Configuration.receiverWindowLength(\n            termBufferLength, Configuration.INITIAL_WINDOW_LENGTH_DEFAULT) * retransmitReceiverWindowMultiple;\n\n        return (lengthToEndOfTerm < estimatedRetransmitLength) ?\n            Math.min(lengthToEndOfTerm, resendLength) : Math.min(estimatedRetransmitLength, resendLength);\n    }\n\n    /**\n     * Determines the retransmit receiver window multiple to use. If a value is specified in the\n     * channel URI, this will be used. Otherwise, the supplied default will be used.\n     *\n     * @param udpChannel for the stream.\n     * @param defaultRetransmitReceiverWindowMultiple window multiple to use when one is not set in the URI.\n     * @return receiver window multiple.\n     */\n    static int retransmitReceiverWindowMultiple(\n        final UdpChannel udpChannel, final int defaultRetransmitReceiverWindowMultiple)\n    {\n        final String fcValue = udpChannel.channelUri().get(CommonContext.FLOW_CONTROL_PARAM_NAME);\n        if (fcValue != null)\n        {\n            for (final String arg : fcValue.split(\",\"))\n            {\n                if (arg.startsWith(\"rrwm:\"))\n                {\n                    final int rrwm = Integer.parseInt(arg.substring(\"rrwm:\".length()));\n                    if (rrwm <= 0)\n                    {\n                        throw new IllegalArgumentException(\"Invalid retransmit receiver window multiple: \" + rrwm);\n                    }\n                    return rrwm;\n                }\n            }\n        }\n        return defaultRetransmitReceiverWindowMultiple;\n    }\n\n    /**\n     * Update the sender flow control strategy based on a status message from the receiver.\n     *\n     * @param flyweight           over the status message received.\n     * @param receiverAddress     of the receiver.\n     * @param senderLimit         the current sender position limit.\n     * @param initialTermId       for the term buffers.\n     * @param positionBitsToShift in use for the length of each term buffer.\n     * @param timeNs              current time (in nanoseconds).\n     * @return the new position limit to be employed by the sender.\n     */\n    long onStatusMessage(\n        StatusMessageFlyweight flyweight,\n        InetSocketAddress receiverAddress,\n        long senderLimit,\n        int initialTermId,\n        int positionBitsToShift,\n        long timeNs);\n\n    /**\n     * Update the sender flow control strategy based on a Status Message received triggering a setup to be sent.\n     *\n     * @param flyweight       over the Status Message received\n     * @param receiverAddress of the receiver.\n     * @param timeNs          current time (in nanoseconds).\n     */\n    void onTriggerSendSetup(\n        StatusMessageFlyweight flyweight,\n        InetSocketAddress receiverAddress,\n        long timeNs);\n\n    /**\n     * Update the sender flow control strategy based on an elicited setup message being sent out.\n     *\n     * @param flyweight           over the setup to be sent.\n     * @param senderLimit         for the current sender position.\n     * @param senderPosition      which has been sent.\n     * @param positionBitsToShift in use for the length of each term buffer.\n     * @param timeNs              current time in nanoseconds.\n     * @return the new position limit to be employed by the sender.\n     */\n    long onSetup(\n        SetupFlyweight flyweight,\n        long senderLimit,\n        long senderPosition,\n        int positionBitsToShift,\n        long timeNs);\n\n    /**\n     * Update the sender flow control strategy if an error comes from one of the receivers.\n     *\n     * @param errorFlyweight    over the error received.\n     * @param receiverAddress   the address of the receiver.\n     * @param timeNs            current time in nanoseconds\n     */\n    void onError(ErrorFlyweight errorFlyweight, InetSocketAddress receiverAddress, long timeNs);\n\n    /**\n     * Initialize the flow control strategy for a stream.\n     *\n     * @param context          to allow access to media driver configuration\n     * @param countersManager  to use for any counters in use by the strategy\n     * @param streamId         for the stream.\n     * @param sessionId        for the stream.\n     * @param registrationId   for the stream.\n     * @param udpChannel       for the stream.\n     * @param initialTermId    at which the stream started.\n     * @param termBufferLength to use as the length of each term buffer.\n     */\n    void initialize(\n        MediaDriver.Context context,\n        CountersManager countersManager,\n        UdpChannel udpChannel,\n        int streamId,\n        int sessionId,\n        long registrationId,\n        int initialTermId,\n        int termBufferLength);\n\n    /**\n     * Perform any maintenance needed by the flow control strategy and return current sender limit position.\n     *\n     * @param timeNs         current time in nanoseconds.\n     * @param senderLimit    for the current sender position.\n     * @param senderPosition which has been sent.\n     * @param isEos          is this end-of-stream for the sender.\n     * @return the position limit to be employed by the sender.\n     */\n    long onIdle(long timeNs, long senderLimit, long senderPosition, boolean isEos);\n\n    /**\n     * Has the flow control strategy its required group of receivers to be considered connected? The\n     * result of this feeds into the determination of if a publication is connected.\n     *\n     * @return true if the required group of receivers is connected, otherwise false.\n     */\n    boolean hasRequiredReceivers();\n\n    /**\n     * The maximum window length allowed to retransmit per NAK. Will limit it by an estimate of the window limit and to\n     * the end of the current term.\n     *\n     * @param termOffset       of the NAK.\n     * @param resendLength     of the NAK.\n     * @param termBufferLength of the publication.\n     * @param mtuLength        of the publication.\n     * @return the maximum window length allowed to retransmit per NAK.\n     */\n    int maxRetransmissionLength(int termOffset, int resendLength, int termBufferLength, int mtuLength);\n\n    /**\n     * {@inheritDoc}\n     */\n    void close();\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/FlowControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\n\n/**\n * Supplier of {@link FlowControl} strategies to be employed by senders for publications.\n */\n@FunctionalInterface\npublic interface FlowControlSupplier\n{\n    /**\n     * Return a new {@link FlowControl} instance.\n     *\n     * @param udpChannel     for the publication.\n     * @param streamId       for the publication.\n     * @param registrationId for the publication.\n     * @return flow control instance ready for immediate usage.\n     */\n    FlowControl newInstance(UdpChannel udpChannel, int streamId, long registrationId);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/IpcPublication.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.logbuffer.LogBufferUnblocker;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.Position;\nimport org.agrona.concurrent.status.ReadablePosition;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.util.*;\n\nimport static io.aeron.ErrorCode.IMAGE_REJECTED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Encapsulation of a stream used directly between publishers and subscribers for IPC over shared memory.\n */\npublic final class IpcPublication implements DriverManagedResource, Subscribable\n{\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        ACTIVE, DRAINING, LINGER, DONE\n    }\n\n    private static final ReadablePosition[] EMPTY_POSITIONS = new ReadablePosition[0];\n    private static final InetSocketAddress IPC_SRC_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);\n\n    private final long registrationId;\n    private final long tag;\n    private final long unblockTimeoutNs;\n    private final long untetheredWindowLimitTimeoutNs;\n    private final long untetheredLingerTimeoutNs;\n    private final long untetheredRestingTimeoutNs;\n    private final long imageLivenessTimeoutNs;\n    private final long responseCorrelationId;\n    private final String channel;\n    private final int sessionId;\n    private final int streamId;\n    private final int startingTermId;\n    private final int startingTermOffset;\n    private final int positionBitsToShift;\n    private final int termBufferLength;\n    private final int mtuLength;\n    private final int termWindowLength;\n    private final int initialTermId;\n    private final int tripGain;\n    private long tripLimit;\n    private long consumerPosition;\n    private long lastConsumerPosition;\n    private long timeOfLastConsumerPositionUpdateNs;\n    private long cleanPosition;\n    private int refCount = 0;\n    private boolean reachedEndOfLife = false;\n    private boolean inCoolDown = false;\n    private long coolDownExpireTimeNs = 0;\n    private final boolean isExclusive;\n    private State state = State.ACTIVE;\n    private final UnsafeBuffer[] termBuffers;\n    private final Position publisherPos;\n    private final Position publisherLimit;\n    private final UnsafeBuffer metaDataBuffer;\n    private ReadablePosition[] subscriberPositions = EMPTY_POSITIONS;\n    private final ArrayList<UntetheredSubscription> untetheredSubscriptions = new ArrayList<>();\n    private final RawLog rawLog;\n    private final AtomicCounter unblockedPublications;\n    private final AtomicCounter publicationsRevoked;\n    private final ErrorHandler errorHandler;\n\n    IpcPublication(\n        final long registrationId,\n        final String channel,\n        final MediaDriver.Context ctx,\n        final long tag,\n        final int sessionId,\n        final int streamId,\n        final Position publisherPos,\n        final Position publisherLimit,\n        final RawLog rawLog,\n        final boolean isExclusive,\n        final PublicationParams params)\n    {\n        this.registrationId = registrationId;\n        this.channel = channel;\n        this.tag = tag;\n        this.sessionId = sessionId;\n        this.streamId = streamId;\n        this.isExclusive = isExclusive;\n        this.termBuffers = rawLog.termBuffers();\n        this.initialTermId = LogBufferDescriptor.initialTermId(rawLog.metaData());\n        this.startingTermId = params.termId;\n        this.startingTermOffset = params.termOffset;\n        this.errorHandler = ctx.errorHandler();\n\n        final int termLength = params.termLength;\n        this.termBufferLength = termLength;\n        this.mtuLength = params.mtuLength;\n        this.positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n        this.termWindowLength = params.publicationWindowLength;\n        this.tripGain = termWindowLength >> 3;\n        this.publisherPos = publisherPos;\n        this.publisherLimit = publisherLimit;\n        this.rawLog = rawLog;\n        this.unblockTimeoutNs = ctx.publicationUnblockTimeoutNs();\n        untetheredWindowLimitTimeoutNs = params.untetheredWindowLimitTimeoutNs;\n        untetheredLingerTimeoutNs = params.untetheredLingerTimeoutNs;\n        untetheredRestingTimeoutNs = params.untetheredRestingTimeoutNs;\n        this.imageLivenessTimeoutNs = ctx.imageLivenessTimeoutNs();\n        this.responseCorrelationId = params.responseCorrelationId;\n\n        final SystemCounters systemCounters = ctx.systemCounters();\n        this.unblockedPublications = systemCounters.get(UNBLOCKED_PUBLICATIONS);\n        this.publicationsRevoked = systemCounters.get(PUBLICATIONS_REVOKED);\n\n        this.metaDataBuffer = rawLog.metaData();\n\n        consumerPosition = producerPosition();\n        lastConsumerPosition = consumerPosition;\n        cleanPosition = consumerPosition;\n        timeOfLastConsumerPositionUpdateNs = ctx.cachedNanoClock().nanoTime();\n    }\n\n    /**\n     * Channel URI string for this publication.\n     *\n     * @return channel URI string for this publication.\n     */\n    public String channel()\n    {\n        return channel;\n    }\n\n    /**\n     * Session id allocated to this stream.\n     *\n     * @return session id allocated to this stream.\n     */\n    public int sessionId()\n    {\n        return sessionId;\n    }\n\n    /**\n     * Stream id within the channel.\n     *\n     * @return stream id within the channel.\n     */\n    public int streamId()\n    {\n        return streamId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long subscribableRegistrationId()\n    {\n        return registrationId;\n    }\n\n    long registrationId()\n    {\n        return registrationId;\n    }\n\n    long tag()\n    {\n        return tag;\n    }\n\n    boolean isExclusive()\n    {\n        return isExclusive;\n    }\n\n    int initialTermId()\n    {\n        return initialTermId;\n    }\n\n    int startingTermId()\n    {\n        return startingTermId;\n    }\n\n    int startingTermOffset()\n    {\n        return startingTermOffset;\n    }\n\n    RawLog rawLog()\n    {\n        return rawLog;\n    }\n\n    int publisherLimitId()\n    {\n        return publisherLimit.id();\n    }\n\n    int termBufferLength()\n    {\n        return termBufferLength;\n    }\n\n    int mtuLength()\n    {\n        return mtuLength;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean free()\n    {\n        return rawLog.free();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(errorHandler, publisherPos);\n        CloseHelper.close(errorHandler, publisherLimit);\n        CloseHelper.closeAll(errorHandler, subscriberPositions);\n\n        for (int i = 0, size = untetheredSubscriptions.size(); i < size; i++)\n        {\n            final UntetheredSubscription untetheredSubscription = untetheredSubscriptions.get(i);\n            if (UntetheredSubscription.State.RESTING == untetheredSubscription.state)\n            {\n                CloseHelper.close(errorHandler, untetheredSubscription.position);\n            }\n        }\n    }\n\n    void reject(final long position, final String reason, final DriverConductor conductor, final long nowNs)\n    {\n        conductor.onPublicationError(\n            registrationId,\n            Aeron.NULL_VALUE,\n            sessionId(),\n            streamId(),\n            Aeron.NULL_VALUE,\n            Aeron.NULL_VALUE,\n            IPC_SRC_ADDRESS,\n            IMAGE_REJECTED.value(),\n            reason);\n\n        if (!inCoolDown)\n        {\n            updateConnectedStatus(false);\n\n            conductor.unlinkIpcSubscriptions(this);\n\n            CloseHelper.closeAll(errorHandler, subscriberPositions);\n            subscriberPositions = EMPTY_POSITIONS;\n\n            untetheredSubscriptions.clear();\n\n            inCoolDown = true;\n        }\n\n        coolDownExpireTimeNs = nowNs + imageLivenessTimeoutNs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void addSubscriber(\n        final SubscriptionLink subscriptionLink, final ReadablePosition subscriberPosition, final long nowNs)\n    {\n        subscriberPositions = ArrayUtil.add(subscriberPositions, subscriberPosition);\n        if (!subscriptionLink.isTether())\n        {\n            untetheredSubscriptions.add(new UntetheredSubscription(subscriptionLink, subscriberPosition, nowNs));\n        }\n\n        updateConnectedStatus(true);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void removeSubscriber(final SubscriptionLink subscriptionLink, final ReadablePosition subscriberPosition)\n    {\n        subscriberPositions = ArrayUtil.remove(subscriberPositions, subscriberPosition);\n        subscriberPosition.close();\n\n        if (!subscriptionLink.isTether())\n        {\n            for (int lastIndex = untetheredSubscriptions.size() - 1, i = lastIndex; i >= 0; i--)\n            {\n                if (untetheredSubscriptions.get(i).subscriptionLink == subscriptionLink)\n                {\n                    ArrayListUtil.fastUnorderedRemove(untetheredSubscriptions, i, lastIndex);\n                    break;\n                }\n            }\n        }\n\n        if (0 == subscriberPositions.length)\n        {\n            updateConnectedStatus(false);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTimeEvent(final long timeNs, final long timeMs, final DriverConductor conductor)\n    {\n        switch (state)\n        {\n            case ACTIVE:\n            {\n                if (isPublicationRevoked(metaDataBuffer))\n                {\n                    final long revokedPos = producerPosition();\n                    publisherLimit.setRelease(revokedPos);\n                    LogBufferDescriptor.endOfStreamPosition(metaDataBuffer, revokedPos);\n                    updateConnectedStatus(false);\n\n                    conductor.transitionToLinger(this);\n\n                    state = State.LINGER;\n\n                    logRevoke(revokedPos, sessionId(), streamId(), channel());\n                    publicationsRevoked.increment();\n                }\n                else\n                {\n                    checkUntetheredSubscriptions(timeNs, conductor);\n                    updateConnectedStatus(0 != subscriberPositions.length);\n                    final long producerPosition = producerPosition();\n                    publisherPos.setRelease(producerPosition);\n                    if (!isExclusive)\n                    {\n                        checkForBlockedPublisher(producerPosition, timeNs);\n                    }\n                    checkCoolDownStatus(timeNs, conductor);\n                }\n                break;\n            }\n\n            case DRAINING:\n            {\n                final long producerPosition = producerPosition();\n                publisherPos.setRelease(producerPosition);\n                if (isDrained(producerPosition))\n                {\n                    conductor.transitionToLinger(this);\n                    state = State.LINGER;\n                }\n                else if (LogBufferUnblocker.unblock(termBuffers, metaDataBuffer, consumerPosition, termBufferLength))\n                {\n                    unblockedPublications.incrementRelease();\n                }\n                break;\n            }\n\n            case LINGER:\n                if (0 == refCount)\n                {\n                    conductor.cleanupIpcPublication(this);\n                    reachedEndOfLife = true;\n                    state = State.DONE;\n                }\n                break;\n\n            case DONE:\n                break;\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean hasReachedEndOfLife()\n    {\n        return reachedEndOfLife;\n    }\n\n    void revoke()\n    {\n        LogBufferDescriptor.isPublicationRevoked(metaDataBuffer, true);\n    }\n\n    void incRef()\n    {\n        ++refCount;\n    }\n\n    void decRef()\n    {\n        if (0 == --refCount)\n        {\n            final long producerPosition = producerPosition();\n            publisherLimit.setRelease(producerPosition);\n            LogBufferDescriptor.endOfStreamPosition(metaDataBuffer, producerPosition);\n\n            if (!LogBufferDescriptor.isPublicationRevoked(metaDataBuffer))\n            {\n                state = State.DRAINING;\n            }\n        }\n    }\n\n    int updatePublisherPositionAndLimit()\n    {\n        int workCount = 0;\n\n        if (State.ACTIVE == state)\n        {\n            final long producerPosition = producerPosition();\n            publisherPos.setRelease(producerPosition);\n\n            if (subscriberPositions.length > 0)\n            {\n                long minSubscriberPosition = Long.MAX_VALUE;\n                long maxSubscriberPosition = consumerPosition;\n\n                for (final ReadablePosition subscriberPosition : subscriberPositions)\n                {\n                    final long position = subscriberPosition.getVolatile();\n                    minSubscriberPosition = Math.min(minSubscriberPosition, position);\n                    maxSubscriberPosition = Math.max(maxSubscriberPosition, position);\n                }\n\n                if (maxSubscriberPosition > consumerPosition)\n                {\n                    consumerPosition = maxSubscriberPosition;\n                }\n\n                final long newLimitPosition = minSubscriberPosition + termWindowLength;\n                if (newLimitPosition >= tripLimit)\n                {\n                    cleanBufferTo(minSubscriberPosition);\n                    publisherLimit.setRelease(newLimitPosition);\n                    tripLimit = newLimitPosition + tripGain;\n                    workCount = 1;\n                }\n            }\n            else if (publisherLimit.get() > consumerPosition)\n            {\n                tripLimit = consumerPosition;\n                publisherLimit.setRelease(consumerPosition);\n                cleanBufferTo(consumerPosition);\n                workCount = 1;\n            }\n        }\n\n        return workCount;\n    }\n\n    long joinPosition()\n    {\n        long position = consumerPosition;\n\n        for (final ReadablePosition subscriberPosition : subscriberPositions)\n        {\n            position = Math.min(subscriberPosition.getVolatile(), position);\n        }\n\n        return position;\n    }\n\n    long producerPosition()\n    {\n        final long rawTail = rawTailVolatile(metaDataBuffer);\n        final int termOffset = termOffset(rawTail, termBufferLength);\n\n        return computePosition(termId(rawTail), termOffset, positionBitsToShift, initialTermId);\n    }\n\n    long consumerPosition()\n    {\n        return consumerPosition;\n    }\n\n    State state()\n    {\n        return state;\n    }\n\n    boolean isAcceptingSubscriptions()\n    {\n        return !inCoolDown && (State.ACTIVE == state || (State.DRAINING == state && !isDrained(producerPosition())));\n    }\n\n    long responseCorrelationId()\n    {\n        return responseCorrelationId;\n    }\n\n    private void checkUntetheredSubscriptions(final long nowNs, final DriverConductor conductor)\n    {\n        final long untetheredWindowLimit = (consumerPosition - termWindowLength) + (termWindowLength >> 2);\n\n        for (int lastIndex = untetheredSubscriptions.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final UntetheredSubscription untethered = untetheredSubscriptions.get(i);\n            if (UntetheredSubscription.State.ACTIVE == untethered.state)\n            {\n                if (untethered.position.getVolatile() > untetheredWindowLimit)\n                {\n                    untethered.timeOfLastUpdateNs = nowNs;\n                }\n                else if ((untethered.timeOfLastUpdateNs + untetheredWindowLimitTimeoutNs) - nowNs <= 0)\n                {\n                    conductor.notifyUnavailableImageLink(registrationId, untethered.subscriptionLink);\n                    untethered.state(UntetheredSubscription.State.LINGER, nowNs, streamId, sessionId);\n                }\n            }\n            else if (UntetheredSubscription.State.LINGER == untethered.state)\n            {\n                if ((untethered.timeOfLastUpdateNs + untetheredLingerTimeoutNs) - nowNs <= 0)\n                {\n                    subscriberPositions = ArrayUtil.remove(subscriberPositions, untethered.position);\n                    if (untethered.subscriptionLink.isRejoin())\n                    {\n                        untethered.state(UntetheredSubscription.State.RESTING, nowNs, streamId, sessionId);\n                    }\n                    else\n                    {\n                        ArrayListUtil.fastUnorderedRemove(untetheredSubscriptions, i, lastIndex--);\n                        untethered.position.close();\n                    }\n                }\n            }\n            else if (UntetheredSubscription.State.RESTING == untethered.state)\n            {\n                if ((untethered.timeOfLastUpdateNs + untetheredRestingTimeoutNs) - nowNs <= 0)\n                {\n                    final long joinPosition = joinPosition();\n                    subscriberPositions = ArrayUtil.add(subscriberPositions, untethered.position);\n                    conductor.notifyAvailableImageLink(\n                        registrationId,\n                        sessionId,\n                        untethered.subscriptionLink,\n                        untethered.position.id(),\n                        joinPosition,\n                        rawLog.fileName(),\n                        CommonContext.IPC_CHANNEL);\n                    untethered.state(UntetheredSubscription.State.ACTIVE, nowNs, streamId, sessionId);\n                }\n            }\n        }\n    }\n\n    private void updateConnectedStatus(final boolean newStatus)\n    {\n        if (LogBufferDescriptor.isConnected(metaDataBuffer) != newStatus)\n        {\n            LogBufferDescriptor.isConnected(metaDataBuffer, newStatus);\n        }\n    }\n\n    private void checkCoolDownStatus(final long timeNs, final DriverConductor conductor)\n    {\n        if (inCoolDown && coolDownExpireTimeNs < timeNs)\n        {\n            inCoolDown = false;\n\n            conductor.linkIpcSubscriptions(this);\n\n            coolDownExpireTimeNs = 0;\n        }\n    }\n\n    private boolean isDrained(final long producerPosition)\n    {\n        for (final ReadablePosition subscriberPosition : subscriberPositions)\n        {\n            if (subscriberPosition.getVolatile() < producerPosition)\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private void checkForBlockedPublisher(final long producerPosition, final long timeNs)\n    {\n        final long consumerPosition = this.consumerPosition;\n\n        if (consumerPosition == lastConsumerPosition && isPossiblyBlocked(producerPosition, consumerPosition))\n        {\n            if ((timeOfLastConsumerPositionUpdateNs + unblockTimeoutNs) - timeNs < 0)\n            {\n                if (LogBufferUnblocker.unblock(termBuffers, metaDataBuffer, consumerPosition, termBufferLength))\n                {\n                    unblockedPublications.incrementRelease();\n                }\n            }\n        }\n        else\n        {\n            timeOfLastConsumerPositionUpdateNs = timeNs;\n            lastConsumerPosition = consumerPosition;\n        }\n    }\n\n    private boolean isPossiblyBlocked(final long producerPosition, final long consumerPosition)\n    {\n        final int producerTermCount = activeTermCount(metaDataBuffer);\n        final int expectedTermCount = (int)(consumerPosition >> positionBitsToShift);\n\n        if (producerTermCount != expectedTermCount)\n        {\n            return true;\n        }\n\n        return producerPosition > consumerPosition;\n    }\n\n    private void cleanBufferTo(final long position)\n    {\n        final long cleanPosition = this.cleanPosition;\n        if (position > cleanPosition)\n        {\n            final UnsafeBuffer dirtyTermBuffer = termBuffers[indexByPosition(cleanPosition, positionBitsToShift)];\n            final int bytesForCleaning = (int)(position - cleanPosition);\n            final int bufferCapacity = termBufferLength;\n            final int termOffset = (int)cleanPosition & (bufferCapacity - 1);\n            final int length = Math.min(bytesForCleaning, bufferCapacity - termOffset);\n\n            dirtyTermBuffer.setMemory(termOffset + SIZE_OF_LONG, length - SIZE_OF_LONG, (byte)0);\n            dirtyTermBuffer.putLongRelease(termOffset, 0);\n            this.cleanPosition = cleanPosition + length;\n        }\n    }\n\n    private static void logRevoke(\n        final long revokedPos,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/IpcSubscriptionLink.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nclass IpcSubscriptionLink extends SubscriptionLink\n{\n    IpcSubscriptionLink(\n        final long registrationId,\n        final int streamId,\n        final String channelUri,\n        final AeronClient aeronClient,\n        final SubscriptionParams params)\n    {\n        super(registrationId, streamId, channelUri, aeronClient, params);\n    }\n\n    boolean matches(final IpcPublication publication)\n    {\n        return publication.streamId() == streamId && isWildcardOrSessionIdMatch(publication.sessionId());\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/LossDetector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.logbuffer.TermGapScanner;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport static io.aeron.logbuffer.TermGapScanner.scanForGap;\n\n/**\n * Detecting and handling of gaps in a message stream.\n * <p>\n * Each detector only notifies a single run of a gap in a message stream.\n */\npublic class LossDetector implements TermGapScanner.GapHandler\n{\n    private long deadlineNs = Aeron.NULL_VALUE;\n\n    private int scannedTermId;\n    private int scannedTermOffset = -1;\n    private int scannedLength;\n\n    private int activeTermId;\n    private int activeTermOffset = -1;\n    private int activeLength;\n\n    private final FeedbackDelayGenerator delayGenerator;\n    private final LossHandler lossHandler;\n\n    /**\n     * Create a loss detector for a channel.\n     *\n     * @param delayGenerator to use for delay determination\n     * @param lossHandler    to call when signalling a gap\n     */\n    public LossDetector(final FeedbackDelayGenerator delayGenerator, final LossHandler lossHandler)\n    {\n        this.delayGenerator = delayGenerator;\n        this.lossHandler = lossHandler;\n    }\n\n    /**\n     * Scan for gaps and handle received data.\n     * <p>\n     * The handler keeps track from scan to scan what is a gap and what must have been repaired.\n     *\n     * @param termBuffer          to scan\n     * @param rebuildPosition     to start scanning from\n     * @param hwmPosition         to scan up to\n     * @param nowNs               time in nanoseconds\n     * @param termLengthMask      used for offset calculation\n     * @param positionBitsToShift used for position calculation\n     * @param initialTermId       used by the scanner\n     * @return packed outcome of the scan.\n     */\n    public long scan(\n        final UnsafeBuffer termBuffer,\n        final long rebuildPosition,\n        final long hwmPosition,\n        final long nowNs,\n        final int termLengthMask,\n        final int positionBitsToShift,\n        final int initialTermId)\n    {\n        boolean lossFound = false;\n        int rebuildOffset = (int)(rebuildPosition & termLengthMask);\n\n        if (rebuildPosition < hwmPosition)\n        {\n            final int rebuildTermCount = (int)(rebuildPosition >>> positionBitsToShift);\n            final int hwmTermCount = (int)(hwmPosition >>> positionBitsToShift);\n\n            final int rebuildTermId = initialTermId + rebuildTermCount;\n            final int hwmTermOffset = (int)(hwmPosition & termLengthMask);\n            final int limitOffset = rebuildTermCount == hwmTermCount ? hwmTermOffset : termLengthMask + 1;\n\n            rebuildOffset = scanForGap(termBuffer, rebuildTermId, rebuildOffset, limitOffset, this);\n            if (rebuildOffset < limitOffset)\n            {\n                if (scannedTermOffset != activeTermOffset ||\n                    scannedTermId != activeTermId ||\n                    scannedLength != activeLength)\n                {\n                    activateGap(nowNs);\n                    lossFound = true;\n                }\n\n                checkTimerExpiry(nowNs);\n            }\n        }\n\n        return pack(rebuildOffset, lossFound);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onGap(final int termId, final int offset, final int length)\n    {\n        scannedTermId = termId;\n        scannedTermOffset = offset;\n        scannedLength = length;\n    }\n\n    /**\n     * Pack the values for workCount and rebuildOffset into a long for returning on the stack.\n     *\n     * @param rebuildOffset value to be packed.\n     * @param lossFound     value to be packed.\n     * @return a long with rebuildOffset and lossFound packed into it.\n     */\n    public static long pack(final int rebuildOffset, final boolean lossFound)\n    {\n        return ((long)rebuildOffset << 32) | (lossFound ? 1 : 0);\n    }\n\n    /**\n     * Has loss been found in the scan?\n     *\n     * @param scanOutcome into which the fragments read value has been packed.\n     * @return if loss has been found or not.\n     */\n    public static boolean lossFound(final long scanOutcome)\n    {\n        return ((int)scanOutcome) != 0;\n    }\n\n    /**\n     * The offset up to which the log has been rebuilt.\n     *\n     * @param scanOutcome into which the offset value has been packed.\n     * @return the offset up to which the log has been rebuilt.\n     */\n    public static int rebuildOffset(final long scanOutcome)\n    {\n        return (int)(scanOutcome >>> 32);\n    }\n\n    private void activateGap(final long nowNs)\n    {\n        activeTermId = scannedTermId;\n        activeTermOffset = scannedTermOffset;\n        activeLength = scannedLength;\n\n        deadlineNs = nowNs + delayGenerator.generateDelayNs();\n    }\n\n    private void checkTimerExpiry(final long nowNs)\n    {\n        if (deadlineNs - nowNs <= 0)\n        {\n            lossHandler.onGapDetected(activeTermId, activeTermOffset, activeLength);\n            deadlineNs = nowNs + delayGenerator.retryDelayNs();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/LossHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\n/**\n * Handler for dealing with detected loss on a message stream.\n */\n@FunctionalInterface\npublic interface LossHandler\n{\n    /**\n     * Called when a gap in the message stream has been detected.\n     *\n     * @param termId     for the gap\n     * @param termOffset for the beginning of the gap\n     * @param length     of the gap\n     */\n    void onGapDetected(int termId, int termOffset, int length);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/MaxMulticastFlowControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.net.InetSocketAddress;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\n\n/**\n * Default multicast sender flow control strategy.\n * <p>\n * Max of right edges.\n * No tracking of receivers.\n */\npublic class MaxMulticastFlowControl implements FlowControl\n{\n    /**\n     * URI param value to identify this {@link FlowControl} strategy.\n     */\n    public static final String FC_PARAM_VALUE = \"max\";\n\n    /**\n     * Multiple of receiver window to allow for a retransmit action.\n     */\n    private int retransmitReceiverWindowMultiple;\n\n    /**\n     * Default constructor.\n     */\n    public MaxMulticastFlowControl()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void initialize(\n        final MediaDriver.Context context,\n        final CountersManager countersManager,\n        final UdpChannel udpChannel,\n        final int streamId,\n        final int sessionId,\n        final long registrationId,\n        final int initialTermId,\n        final int termBufferLength)\n    {\n        retransmitReceiverWindowMultiple = FlowControl.retransmitReceiverWindowMultiple(\n            udpChannel,\n            context.multicastFlowControlRetransmitReceiverWindowMultiple()\n        );\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onStatusMessage(\n        final StatusMessageFlyweight flyweight,\n        final InetSocketAddress receiverAddress,\n        final long senderLimit,\n        final int initialTermId,\n        final int positionBitsToShift,\n        final long timeNs)\n    {\n        final long position = computePosition(\n            flyweight.consumptionTermId(),\n            flyweight.consumptionTermOffset(),\n            positionBitsToShift,\n            initialTermId);\n\n        return Math.max(senderLimit, position + flyweight.receiverWindowLength());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTriggerSendSetup(\n        final StatusMessageFlyweight flyweight,\n        final InetSocketAddress receiverAddress,\n        final long timeNs)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onSetup(\n        final SetupFlyweight flyweight,\n        final long senderLimit,\n        final long senderPosition,\n        final int positionBitsToShift,\n        final long timeNs)\n    {\n        return senderLimit;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onIdle(final long timeNs, final long senderLimit, final long senderPosition, final boolean isEos)\n    {\n        return senderLimit;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onError(final ErrorFlyweight errorFlyweight, final InetSocketAddress receiverAddress, final long timeNs)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean hasRequiredReceivers()\n    {\n        return true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int maxRetransmissionLength(\n        final int termOffset,\n        final int resendLength,\n        final int termBufferLength,\n        final int mtuLength)\n    {\n        return FlowControl.calculateRetransmissionLength(\n            resendLength, termBufferLength, termOffset, retransmitReceiverWindowMultiple);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/MaxMulticastFlowControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\n\n/**\n * Supplier of {@link MaxMulticastFlowControl} strategy implementations.\n */\npublic class MaxMulticastFlowControlSupplier implements FlowControlSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public MaxMulticastFlowControlSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public FlowControl newInstance(final UdpChannel udpChannel, final int streamId, final long registrationId)\n    {\n        return new MaxMulticastFlowControl();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/MediaDriver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CncFileDescriptor;\nimport io.aeron.CommonContext;\nimport io.aeron.command.ControlProtocolEvents;\nimport io.aeron.config.Config;\nimport io.aeron.driver.buffer.FileStoreLogFactory;\nimport io.aeron.driver.buffer.LogFactory;\nimport io.aeron.driver.exceptions.ActiveDriverException;\nimport io.aeron.driver.media.*;\nimport io.aeron.driver.reports.LossReport;\nimport io.aeron.driver.status.DutyCycleStallTracker;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.exceptions.ConfigurationException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.version.Versioned;\nimport org.agrona.*;\nimport org.agrona.concurrent.*;\nimport org.agrona.concurrent.broadcast.BroadcastTransmitter;\nimport org.agrona.concurrent.errors.DistinctErrorLog;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\nimport org.agrona.concurrent.status.*;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.net.StandardSocketOptions;\nimport java.nio.ByteOrder;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.text.SimpleDateFormat;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Consumer;\n\nimport static io.aeron.CncFileDescriptor.*;\nimport static io.aeron.driver.Configuration.*;\nimport static io.aeron.driver.reports.LossReportUtil.mapLossReport;\nimport static io.aeron.driver.status.SystemCounterDescriptor.CONTROLLABLE_IDLE_STRATEGY;\nimport static io.aeron.driver.status.SystemCounterDescriptor.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MAX_LENGTH;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.IoUtil.mapNewFile;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\nimport static org.agrona.concurrent.status.CountersReader.METADATA_LENGTH;\n\n/**\n * Main class for JVM-based media driver.\n * <p>\n * Usage:\n * <code>\n * $ java -jar aeron-driver.jar\n * $ java -Doption=value -jar aeron-driver.jar\n * </code>\n *\n * @see Configuration\n */\n@Versioned\npublic final class MediaDriver implements AutoCloseable\n{\n    private boolean wasHighResTimerEnabled;\n    private final AgentRunner sharedRunner;\n    private final AgentRunner sharedNetworkRunner;\n    private final AgentRunner conductorRunner;\n    private final AgentRunner receiverRunner;\n    private final AgentRunner senderRunner;\n    private final AgentInvoker sharedInvoker;\n    private final Context ctx;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            MediaDriver ignore = MediaDriver.launch(new Context().terminationHook(barrier::signal)))\n        {\n            barrier.await();\n            System.out.println(\"Shutdown Driver...\");\n        }\n    }\n\n    /**\n     * Construct a media driver with the given ctx.\n     *\n     * @param ctx for the media driver parameters\n     */\n    private MediaDriver(final Context ctx)\n    {\n        ctx.concludeAeronDirectory();\n\n        ensureDirectoryIsRecreated(ctx);\n        validateSocketBufferLengths(ctx);\n\n        try\n        {\n            ctx.conclude();\n            this.ctx = ctx;\n\n            final DriverConductor conductor = new DriverConductor(ctx);\n            final Receiver receiver = new Receiver(ctx);\n            final Sender sender = new Sender(ctx);\n\n            ctx.receiverProxy().receiver(receiver);\n            ctx.senderProxy().sender(sender);\n            ctx.driverConductorProxy().driverConductor(conductor);\n\n            final AtomicCounter errorCounter = ctx.systemCounters().get(ERRORS);\n            final ErrorHandler errorHandler = ctx.errorHandler();\n\n            switch (ctx.threadingMode())\n            {\n                case INVOKER:\n                    sharedInvoker = new AgentInvoker(\n                        errorHandler,\n                        errorCounter,\n                        new NamedCompositeAgent(ctx.aeronDirectoryName(), sender, receiver, conductor));\n                    sharedRunner = null;\n                    sharedNetworkRunner = null;\n                    conductorRunner = null;\n                    receiverRunner = null;\n                    senderRunner = null;\n                    break;\n\n                case SHARED:\n                    sharedRunner = new AgentRunner(\n                        ctx.sharedIdleStrategy(),\n                        errorHandler,\n                        errorCounter,\n                        new NamedCompositeAgent(ctx.aeronDirectoryName(), sender, receiver, conductor));\n                    sharedNetworkRunner = null;\n                    conductorRunner = null;\n                    receiverRunner = null;\n                    senderRunner = null;\n                    sharedInvoker = null;\n                    break;\n\n                case SHARED_NETWORK:\n                    sharedNetworkRunner = new AgentRunner(\n                        ctx.sharedNetworkIdleStrategy(),\n                        errorHandler,\n                        errorCounter,\n                        new NamedCompositeAgent(ctx.aeronDirectoryName(), sender, receiver));\n                    conductorRunner = new AgentRunner(\n                        ctx.conductorIdleStrategy(), errorHandler, errorCounter, conductor);\n                    sharedRunner = null;\n                    receiverRunner = null;\n                    senderRunner = null;\n                    sharedInvoker = null;\n                    break;\n\n                case DEDICATED:\n                default:\n                    senderRunner = new AgentRunner(ctx.senderIdleStrategy(), errorHandler, errorCounter, sender);\n                    receiverRunner = new AgentRunner(ctx.receiverIdleStrategy(), errorHandler, errorCounter, receiver);\n                    conductorRunner = new AgentRunner(\n                        ctx.conductorIdleStrategy(), errorHandler, errorCounter, conductor);\n                    sharedNetworkRunner = null;\n                    sharedRunner = null;\n                    sharedInvoker = null;\n                    break;\n            }\n        }\n        catch (final ConcurrentConcludeException ex)\n        {\n            throw ex;\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietClose(ctx::close);\n            throw ex;\n        }\n    }\n\n    /**\n     * Launch an isolated MediaDriver embedded in the current process with a generated aeronDirectoryName that can be\n     * retrieved by calling aeronDirectoryName.\n     * <p>\n     * If the aeronDirectoryName is set as a system property to something different from\n     * {@link CommonContext#AERON_DIR_PROP_DEFAULT} then this set value will be used.\n     *\n     * @return the newly started MediaDriver.\n     */\n    public static MediaDriver launchEmbedded()\n    {\n        return launchEmbedded(new Context());\n    }\n\n    /**\n     * Launch an isolated MediaDriver embedded in the current process with a provided configuration ctx and a generated\n     * aeronDirectoryName (overwrites configured {@link Context#aeronDirectoryName()}) that can be retrieved by calling\n     * aeronDirectoryName.\n     * <p>\n     * If the aeronDirectoryName is set as a system property, or via context, to something different from\n     * {@link CommonContext#AERON_DIR_PROP_DEFAULT} then this set value will be used.\n     *\n     * @param ctx containing the configuration options.\n     * @return the newly started MediaDriver.\n     */\n    public static MediaDriver launchEmbedded(final Context ctx)\n    {\n        if (CommonContext.AERON_DIR_PROP_DEFAULT.equals(ctx.aeronDirectoryName()))\n        {\n            ctx.aeronDirectoryName(CommonContext.generateRandomDirName());\n        }\n\n        return launch(ctx);\n    }\n\n    /**\n     * Launch a MediaDriver embedded in the current process with default configuration.\n     *\n     * @return the newly started MediaDriver.\n     */\n    public static MediaDriver launch()\n    {\n        return launch(new Context());\n    }\n\n    /**\n     * Launch a MediaDriver embedded in the current process and provided a configuration ctx.\n     *\n     * @param ctx containing the configuration options.\n     * @return the newly created MediaDriver.\n     */\n    public static MediaDriver launch(final Context ctx)\n    {\n        final MediaDriver mediaDriver = new MediaDriver(ctx);\n\n        if (ctx.useWindowsHighResTimer() && SystemUtil.isWindows())\n        {\n            mediaDriver.wasHighResTimerEnabled = HighResolutionTimer.isEnabled();\n            if (!mediaDriver.wasHighResTimerEnabled)\n            {\n                HighResolutionTimer.enable();\n            }\n        }\n\n        if (null != mediaDriver.conductorRunner)\n        {\n            AgentRunner.startOnThread(mediaDriver.conductorRunner, ctx.conductorThreadFactory());\n        }\n\n        if (null != mediaDriver.senderRunner)\n        {\n            AgentRunner.startOnThread(mediaDriver.senderRunner, ctx.senderThreadFactory());\n        }\n\n        if (null != mediaDriver.receiverRunner)\n        {\n            AgentRunner.startOnThread(mediaDriver.receiverRunner, ctx.receiverThreadFactory());\n        }\n\n        if (null != mediaDriver.sharedNetworkRunner)\n        {\n            AgentRunner.startOnThread(mediaDriver.sharedNetworkRunner, ctx.sharedNetworkThreadFactory());\n        }\n\n        if (null != mediaDriver.sharedRunner)\n        {\n            AgentRunner.startOnThread(mediaDriver.sharedRunner, ctx.sharedThreadFactory());\n        }\n\n        if (null != mediaDriver.sharedInvoker)\n        {\n            mediaDriver.sharedInvoker.start();\n        }\n\n        return mediaDriver;\n    }\n\n    /**\n     * Get the {@link MediaDriver.Context} that is used by this {@link MediaDriver}.\n     *\n     * @return the {@link MediaDriver.Context} that is used by this {@link MediaDriver}.\n     */\n    public Context context()\n    {\n        return ctx;\n    }\n\n    /**\n     * Get the {@link AgentInvoker} for the shared agents when running without threads.\n     *\n     * @return the {@link AgentInvoker} for the shared agents when running without threads.\n     */\n    public AgentInvoker sharedAgentInvoker()\n    {\n        return sharedInvoker;\n    }\n\n    /**\n     * Shutdown the media driver by stopping all threads and freeing resources.\n     */\n    public void close()\n    {\n        try\n        {\n            CloseHelper.closeAll(\n                sharedRunner, sharedNetworkRunner, receiverRunner, senderRunner, conductorRunner, sharedInvoker);\n        }\n        finally\n        {\n            if (ctx.useWindowsHighResTimer() && SystemUtil.isWindows() && !wasHighResTimerEnabled)\n            {\n                HighResolutionTimer.disable();\n            }\n        }\n    }\n\n    /**\n     * Used to access the configured aeronDirectoryName for this MediaDriver, typically used after the\n     * {@link #launchEmbedded()} method is used.\n     *\n     * @return the context aeronDirectoryName\n     */\n    public String aeronDirectoryName()\n    {\n        return ctx.aeronDirectoryName();\n    }\n\n    private static void ensureDirectoryIsRecreated(final Context ctx)\n    {\n        if (ctx.aeronDirectory().isDirectory())\n        {\n            if (ctx.warnIfDirectoryExists())\n            {\n                System.err.println(\"WARNING: \" + ctx.aeronDirectory() + \" exists\");\n            }\n\n            if (!ctx.dirDeleteOnStart())\n            {\n                final Consumer<String> logger = ctx.warnIfDirectoryExists() ? System.err::println : (s) -> {};\n                final MappedByteBuffer cncByteBuffer = ctx.mapExistingCncFile(logger);\n                try\n                {\n                    if (CommonContext.isDriverActive(ctx.driverTimeoutMs(), logger, cncByteBuffer))\n                    {\n                        throw new ActiveDriverException(\"active driver detected\");\n                    }\n\n                    reportExistingErrors(ctx, cncByteBuffer);\n                }\n                finally\n                {\n                    BufferUtil.free(cncByteBuffer);\n                }\n            }\n\n            ctx.deleteDirectory();\n        }\n\n        IoUtil.ensureDirectoryExists(ctx.aeronDirectory(), \"aeron\");\n    }\n\n    private static void reportExistingErrors(final Context ctx, final MappedByteBuffer cncByteBuffer)\n    {\n        try\n        {\n            final ByteArrayOutputStream baos = new ByteArrayOutputStream();\n            final int observations = ctx.saveErrorLog(new PrintStream(baos, false, \"US-ASCII\"), cncByteBuffer);\n            if (observations > 0)\n            {\n                final StringBuilder builder = new StringBuilder(ctx.aeronDirectoryName());\n                IoUtil.removeTrailingSlashes(builder);\n\n                final SimpleDateFormat dateFormat = new SimpleDateFormat(\"-yyyy-MM-dd-HH-mm-ss-SSSZ\");\n                builder.append(dateFormat.format(new Date())).append(\"-error.log\");\n                final String errorLogFilename = builder.toString();\n\n                System.err.println(\"WARNING: Existing errors saved to: \" + errorLogFilename);\n                try (FileOutputStream out = new FileOutputStream(errorLogFilename))\n                {\n                    baos.writeTo(out);\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"MediaDriver{\" + ctx.aeronDirectoryName() + '}';\n    }\n\n    /**\n     * Context for the {@link MediaDriver} that can be used to provide overrides for {@link Configuration}.\n     * <p>\n     * <b>Note:</b> Do not reuse instances of this {@link Context} across different {@link MediaDriver}s.\n     * <p>\n     * The context will be owned by {@link DriverConductor} after a successful\n     * {@link MediaDriver#launch(Context)} and closed via {@link MediaDriver#close()}.\n     */\n    public static final class Context extends CommonContext\n    {\n        private static final VarHandle IS_CLOSED_VH;\n        static\n        {\n            try\n            {\n                IS_CLOSED_VH = MethodHandles.lookup().findVarHandle(Context.class, \"isClosed\", boolean.class);\n            }\n            catch (final ReflectiveOperationException ex)\n            {\n                throw new ExceptionInInitializerError(ex);\n            }\n        }\n\n        private volatile boolean isClosed;\n        private boolean printConfigurationOnStart = CommonContext.shouldPrintConfigurationOnStart();\n        private boolean useWindowsHighResTimer = Configuration.useWindowsHighResTimer();\n        private boolean warnIfDirectoryExists = Configuration.warnIfDirExists();\n        private boolean dirDeleteOnStart = Configuration.dirDeleteOnStart();\n        private boolean dirDeleteOnShutdown = Configuration.dirDeleteOnShutdown();\n        private boolean termBufferSparseFile = Configuration.termBufferSparseFile();\n        private boolean performStorageChecks = Configuration.performStorageChecks();\n        private boolean spiesSimulateConnection = Configuration.spiesSimulateConnection();\n        private boolean reliableStream = Configuration.reliableStream();\n        private boolean tetherSubscriptions = Configuration.tetherSubscriptions();\n        private boolean rejoinStream = Configuration.rejoinStream();\n        private long lowStorageWarningThreshold = Configuration.lowStorageWarningThreshold();\n        private long timerIntervalNs = Configuration.timerIntervalNs();\n        private long clientLivenessTimeoutNs = Configuration.clientLivenessTimeoutNs();\n        private long imageLivenessTimeoutNs = Configuration.imageLivenessTimeoutNs();\n        private long publicationUnblockTimeoutNs = Configuration.publicationUnblockTimeoutNs();\n        private long publicationConnectionTimeoutNs = Configuration.publicationConnectionTimeoutNs();\n        private long publicationLingerTimeoutNs = Configuration.publicationLingerTimeoutNs();\n        private long untetheredWindowLimitTimeoutNs = Configuration.untetheredWindowLimitTimeoutNs();\n        private long untetheredLingerTimeoutNs = Configuration.untetheredLingerTimeoutNs();\n        private long untetheredRestingTimeoutNs = Configuration.untetheredRestingTimeoutNs();\n        private long statusMessageTimeoutNs = Configuration.statusMessageTimeoutNs();\n        private long counterFreeToReuseTimeoutNs = Configuration.counterFreeToReuseTimeoutNs();\n        private long retransmitUnicastDelayNs = Configuration.retransmitUnicastDelayNs();\n        private long retransmitUnicastLingerNs = Configuration.retransmitUnicastLingerNs();\n        private long nakUnicastDelayNs = Configuration.nakUnicastDelayNs();\n        private long nakUnicastRetryDelayRatio = Configuration.nakUnicastRetryDelayRatio();\n        private long nakMulticastMaxBackoffNs = Configuration.nakMulticastMaxBackoffNs();\n        private long flowControlReceiverTimeoutNs = Configuration.flowControlReceiverTimeoutNs();\n        private int unicastFlowControlRetransmitReceiverWindowMultiple = Configuration\n            .unicastFlowControlRetransmitReceiverWindowMultiple();\n        private int multicastFlowControlRetransmitReceiverWindowMultiple = Configuration\n            .multicastFlowControlRetransmitReceiverWindowMultiple();\n        private long reResolutionCheckIntervalNs = Configuration.reResolutionCheckIntervalNs();\n        private long conductorCycleThresholdNs = Configuration.conductorCycleThresholdNs();\n        private long senderCycleThresholdNs = Configuration.senderCycleThresholdNs();\n        private long receiverCycleThresholdNs = Configuration.receiverCycleThresholdNs();\n        private long nameResolverThresholdNs = Configuration.nameResolverThresholdNs();\n\n        private int conductorBufferLength = Configuration.conductorBufferLength();\n        private int toClientsBufferLength = Configuration.toClientsBufferLength();\n        private int counterValuesBufferLength = Configuration.counterValuesBufferLength();\n        private int errorBufferLength = Configuration.errorBufferLength();\n        private int nakMulticastGroupSize = Configuration.nakMulticastGroupSize();\n        private int publicationTermBufferLength = Configuration.termBufferLength();\n        private int ipcTermBufferLength = Configuration.ipcTermBufferLength();\n        private int publicationTermWindowLength = Configuration.publicationTermWindowLength();\n        private int ipcPublicationTermWindowLength = Configuration.ipcPublicationTermWindowLength();\n        private int initialWindowLength = Configuration.initialWindowLength();\n        private int socketSndbufLength = Configuration.socketSndbufLength();\n        private int socketRcvbufLength = Configuration.socketRcvbufLength();\n        private int socketMulticastTtl = Configuration.socketMulticastTtl();\n        private int mtuLength = Configuration.mtuLength();\n        private int ipcMtuLength = Configuration.ipcMtuLength();\n        private int filePageSize = Configuration.filePageSize();\n        private int publicationReservedSessionIdLow = Configuration.publicationReservedSessionIdLow();\n        private int publicationReservedSessionIdHigh = Configuration.publicationReservedSessionIdHigh();\n        private int lossReportBufferLength = Configuration.lossReportBufferLength();\n        private int sendToStatusMessagePollRatio = Configuration.sendToStatusMessagePollRatio();\n        private int resourceFreeLimit = Configuration.resourceFreeLimit();\n        private int asyncTaskExecutorThreads = Configuration.asyncTaskExecutorThreads();\n        private int maxResend = Configuration.maxResend();\n\n        private Long receiverGroupTag = Configuration.groupTag();\n        private long flowControlGroupTag = Configuration.flowControlGroupTag();\n        private int flowControlGroupMinSize = Configuration.flowControlGroupMinSize();\n        private InferableBoolean receiverGroupConsideration = Configuration.receiverGroupConsideration();\n        private String resolverName = Configuration.resolverName();\n        private String resolverInterface = Configuration.resolverInterface();\n        private String resolverBootstrapNeighbor = Configuration.resolverBootstrapNeighbor();\n        private String senderWildcardPortRange = Configuration.senderWildcardPortRange();\n        private String receiverWildcardPortRange = Configuration.receiverWildcardPortRange();\n\n        private EpochClock epochClock;\n        private NanoClock nanoClock;\n        private CachedEpochClock cachedEpochClock;\n        private CachedNanoClock cachedNanoClock;\n        private CachedNanoClock senderCachedNanoClock;\n        private CachedNanoClock receiverCachedNanoClock;\n        private ThreadingMode threadingMode;\n        private ThreadFactory conductorThreadFactory;\n        private ThreadFactory senderThreadFactory;\n        private ThreadFactory receiverThreadFactory;\n        private ThreadFactory sharedThreadFactory;\n        private ThreadFactory sharedNetworkThreadFactory;\n        private Executor asyncTaskExecutor;\n        private IdleStrategy conductorIdleStrategy;\n        private IdleStrategy senderIdleStrategy;\n        private IdleStrategy receiverIdleStrategy;\n        private IdleStrategy sharedNetworkIdleStrategy;\n        private IdleStrategy sharedIdleStrategy;\n        private SendChannelEndpointSupplier sendChannelEndpointSupplier;\n        private ReceiveChannelEndpointSupplier receiveChannelEndpointSupplier;\n        private ReceiveChannelEndpointThreadLocals receiveChannelEndpointThreadLocals;\n        private MutableDirectBuffer tempBuffer;\n        private FlowControlSupplier unicastFlowControlSupplier;\n        private FlowControlSupplier multicastFlowControlSupplier;\n        private byte[] applicationSpecificFeedback;\n        private CongestionControlSupplier congestionControlSupplier;\n        private FeedbackDelayGenerator unicastFeedbackDelayGenerator;\n        private FeedbackDelayGenerator multicastFeedbackDelayGenerator;\n        private FeedbackDelayGenerator retransmitUnicastDelayGenerator;\n        private FeedbackDelayGenerator retransmitUnicastLingerGenerator;\n        private TerminationValidator terminationValidator;\n        private Runnable terminationHook;\n        private NameResolver nameResolver;\n\n        private DistinctErrorLog errorLog;\n        private ErrorHandler errorHandler;\n        private CountedErrorHandler countedErrorHandler;\n        private boolean useConcurrentCountersManager;\n        private CountersManager countersManager;\n        private SystemCounters systemCounters;\n        private LossReport lossReport;\n\n        private LogFactory logFactory;\n        private DataTransportPoller dataTransportPoller;\n        private ControlTransportPoller controlTransportPoller;\n        private ManyToOneConcurrentLinkedQueue<Runnable> driverCommandQueue;\n        private OneToOneConcurrentArrayQueue<Runnable> receiverCommandQueue;\n        private OneToOneConcurrentArrayQueue<Runnable> senderCommandQueue;\n        private ReceiverProxy receiverProxy;\n        private SenderProxy senderProxy;\n        private DriverConductorProxy driverConductorProxy;\n        private ClientProxy clientProxy;\n        private RingBuffer toDriverCommands;\n\n        private MappedByteBuffer lossReportBuffer;\n        private MappedByteBuffer cncByteBuffer;\n        private UnsafeBuffer cncMetaDataBuffer;\n\n        private int osDefaultSocketRcvbufLength = Aeron.NULL_VALUE;\n        private int osMaxSocketRcvbufLength = Aeron.NULL_VALUE;\n        private int osDefaultSocketSndbufLength = Aeron.NULL_VALUE;\n        private int osMaxSocketSndbufLength = Aeron.NULL_VALUE;\n        private EpochNanoClock channelReceiveTimestampClock;\n        private EpochNanoClock channelSendTimestampClock;\n\n        private DutyCycleTracker conductorDutyCycleTracker;\n        private DutyCycleTracker senderDutyCycleTracker;\n        private DutyCycleTracker receiverDutyCycleTracker;\n        private DutyCycleTracker nameResolverTimeTracker;\n        private PortManager senderPortManager;\n        private PortManager receiverPortManager;\n        private int streamSessionLimit = Configuration.streamSessionLimit();\n\n        /**\n         * Construct a Context using default values and loading from system properties.\n         */\n        public Context()\n        {\n        }\n\n        /**\n         * Perform a shallow copy of the object.\n         *\n         * @return a shallow copy of the object.\n         */\n        public Context clone()\n        {\n            return (Context)super.clone();\n        }\n\n        /**\n         * Free up resources but don't delete files in case they are required for debugging unless\n         * {@link #dirDeleteOnShutdown()} is set.\n         */\n        public void close()\n        {\n            if (IS_CLOSED_VH.compareAndSet(this, false, true))\n            {\n                CloseHelper.close(errorHandler, logFactory);\n                CloseHelper.close(errorHandler, countedErrorHandler);\n\n                if (null != systemCounters)\n                {\n                    final AtomicCounter errorCounter = systemCounters.get(ERRORS);\n                    errorCounter.disconnectCountersManager();\n                    errorCounter.close();\n                }\n\n                if (errorHandler instanceof AutoCloseable)\n                {\n                    CloseHelper.quietClose((AutoCloseable)errorHandler);\n                }\n\n                BufferUtil.free(lossReportBuffer);\n                this.lossReportBuffer = null;\n\n                BufferUtil.free(cncByteBuffer);\n                this.cncByteBuffer = null;\n\n                if (dirDeleteOnShutdown)\n                {\n                    this.deleteDirectory();\n                }\n\n                super.close();\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @SuppressWarnings(\"MethodLength\")\n        public Context conclude()\n        {\n            super.conclude();\n\n            try\n            {\n                concludeNullProperties();\n                resolveOsSocketBufLengths();\n\n                validateMtuLength(mtuLength);\n                validateMtuLength(ipcMtuLength);\n                validatePageSize(filePageSize);\n                validateValueRange(\n                    conductorBufferLength, CONDUCTOR_BUFFER_LENGTH_DEFAULT, Integer.MAX_VALUE, \"conductorBufferLength\");\n                validateValueRange(\n                    toClientsBufferLength,\n                    TO_CLIENTS_BUFFER_LENGTH_DEFAULT,\n                    Integer.MAX_VALUE,\n                    \"toClientsBufferLength\");\n                validateValueRange(\n                    counterValuesBufferLength,\n                    COUNTERS_VALUES_BUFFER_LENGTH_MIN,\n                    COUNTERS_VALUES_BUFFER_LENGTH_MAX,\n                    \"counterValuesBufferLength\");\n                validateValueRange(\n                    errorBufferLength, ERROR_BUFFER_LENGTH_DEFAULT, Integer.MAX_VALUE, \"errorBufferLength\");\n                validateValueRange(\n                    publicationTermWindowLength, 0, TERM_MAX_LENGTH, \"publicationTermWindowLength\");\n                validateValueRange(\n                    ipcPublicationTermWindowLength, 0, TERM_MAX_LENGTH, \"ipcPublicationTermWindowLength\");\n\n                validateValueRange(\n                    nakUnicastDelayNs, NAK_UNICAST_DELAY_MIN_VALUE_NS, Long.MAX_VALUE, \"nakUnicastDelayNs\");\n                validateValueRange(\n                    nakUnicastRetryDelayRatio, 1, Long.MAX_VALUE, \"nakUnicastRetryDelayRatio\");\n                Math.multiplyExact(nakUnicastDelayNs, nakUnicastRetryDelayRatio);\n\n                validateSessionIdRange(publicationReservedSessionIdLow, publicationReservedSessionIdHigh);\n\n                LogBufferDescriptor.checkTermLength(publicationTermBufferLength);\n                LogBufferDescriptor.checkTermLength(ipcTermBufferLength);\n                validateInitialWindowLength(initialWindowLength, mtuLength);\n                validateUnblockTimeout(publicationUnblockTimeoutNs(), clientLivenessTimeoutNs(), timerIntervalNs);\n                validateUntetheredTimeouts(\n                    untetheredWindowLimitTimeoutNs,\n                    untetheredLingerTimeoutNs,\n                    untetheredRestingTimeoutNs,\n                    timerIntervalNs);\n                validateValueRange(\n                    unicastFlowControlRetransmitReceiverWindowMultiple,\n                    1, Integer.MAX_VALUE,\n                    \"unicastFlowControlRetransmitReceiverWindowMultiple\"\n                );\n                validateValueRange(\n                    multicastFlowControlRetransmitReceiverWindowMultiple,\n                    1, Integer.MAX_VALUE,\n                    \"multicastFControlRetransmitReceiverWindowMultiple\"\n                );\n\n                final long cncFileLength = BitUtil.align(\n                    (long)META_DATA_LENGTH +\n                    conductorBufferLength +\n                    toClientsBufferLength +\n                    countersMetadataBufferLength(counterValuesBufferLength) +\n                    counterValuesBufferLength +\n                    errorBufferLength,\n                    filePageSize);\n                validateValueRange(cncFileLength, 0, Integer.MAX_VALUE, \"CnC file length\");\n                cncByteBuffer = mapNewFile(cncFile(), cncFileLength);\n\n                cncMetaDataBuffer = CncFileDescriptor.createMetaDataBuffer(cncByteBuffer);\n                CncFileDescriptor.fillMetaData(\n                    cncMetaDataBuffer,\n                    conductorBufferLength,\n                    toClientsBufferLength,\n                    countersMetadataBufferLength(counterValuesBufferLength),\n                    counterValuesBufferLength,\n                    clientLivenessTimeoutNs,\n                    errorBufferLength,\n                    epochClock.time(),\n                    SystemUtil.getPid(),\n                    filePageSize);\n\n                concludeCounters();\n                concludeDependantProperties();\n                concludeIdleStrategies();\n\n                systemCounters.get(BYTES_CURRENTLY_MAPPED).setRelease(cncFileLength + lossReportBufferLength);\n\n                toDriverCommands.nextCorrelationId();\n                toDriverCommands.consumerHeartbeatTime(epochClock.time());\n                CncFileDescriptor.signalCncReady(cncMetaDataBuffer);\n                cncByteBuffer.force();\n            }\n            catch (final Exception ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n\n            if (printConfigurationOnStart)\n            {\n                System.out.println(this);\n            }\n\n            return this;\n        }\n\n        /**\n         * Delete the directory used by the {@link MediaDriver} which delegates to\n         * {@link CommonContext#deleteAeronDirectory()}.\n         */\n        public void deleteDirectory()\n        {\n            if (null != aeronDirectory())\n            {\n                super.deleteAeronDirectory();\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public Context aeronDirectoryName(final String dirName)\n        {\n            super.aeronDirectoryName(dirName);\n            return this;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public Context driverTimeoutMs(final long value)\n        {\n            super.driverTimeoutMs(value);\n            return this;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public Context countersMetaDataBuffer(final UnsafeBuffer countersMetaDataBuffer)\n        {\n            super.countersMetaDataBuffer(countersMetaDataBuffer);\n            return this;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public Context countersValuesBuffer(final UnsafeBuffer countersValuesBuffer)\n        {\n            super.countersValuesBuffer(countersValuesBuffer);\n            return this;\n        }\n\n        /**\n         * Should the driver print its configuration on start to {@link System#out} at the end of {@link #conclude()}.\n         *\n         * @return true if the configuration should be printed on start.\n         * @see CommonContext#PRINT_CONFIGURATION_ON_START_PROP_NAME\n         */\n        public boolean printConfigurationOnStart()\n        {\n            return printConfigurationOnStart;\n        }\n\n        /**\n         * Should the driver print its configuration on start to {@link System#out} at the end of {@link #conclude()}.\n         *\n         * @param printConfigurationOnStart if the configuration should be printed on start.\n         * @return this for a fluent API.\n         * @see CommonContext#PRINT_CONFIGURATION_ON_START_PROP_NAME\n         */\n        public Context printConfigurationOnStart(final boolean printConfigurationOnStart)\n        {\n            this.printConfigurationOnStart = printConfigurationOnStart;\n            return this;\n        }\n\n        /**\n         * Should an attempt be made to use the high resolution timers for waiting on Windows.\n         *\n         * @param useWindowsHighResTimers Should an attempt be made to use the high-res timers for waiting on Windows.\n         * @return this for a fluent API.\n         * @see Configuration#USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME\n         */\n        public Context useWindowsHighResTimer(final boolean useWindowsHighResTimers)\n        {\n            this.useWindowsHighResTimer = useWindowsHighResTimers;\n            return this;\n        }\n\n        /**\n         * Should an attempt be made to use the high resolution timers for waiting on Windows.\n         *\n         * @return true if an attempt be made to use the high resolution timers for waiting on Windows.\n         * @see Configuration#USE_WINDOWS_HIGH_RES_TIMER_PROP_NAME\n         */\n        @Config\n        public boolean useWindowsHighResTimer()\n        {\n            return useWindowsHighResTimer;\n        }\n\n        /**\n         * Should a warning be issued if the {@link #aeronDirectoryName()} exists?\n         *\n         * @return should a warning be issued if the {@link #aeronDirectoryName()} exists?\n         * @see Configuration#DIR_WARN_IF_EXISTS_PROP_NAME\n         */\n        @Config(id = \"DIR_WARN_IF_EXISTS\")\n        public boolean warnIfDirectoryExists()\n        {\n            return warnIfDirectoryExists;\n        }\n\n        /**\n         * Should a warning be issued if the {@link #aeronDirectoryName()} exists?\n         *\n         * @param warnIfDirectoryExists warn if the {@link #aeronDirectoryName()} exists?\n         * @return this for a fluent API.\n         * @see Configuration#DIR_WARN_IF_EXISTS_PROP_NAME\n         */\n        public Context warnIfDirectoryExists(final boolean warnIfDirectoryExists)\n        {\n            this.warnIfDirectoryExists = warnIfDirectoryExists;\n            return this;\n        }\n\n        /**\n         * Will the driver attempt to immediately delete {@link #aeronDirectoryName()} on startup.\n         * WARNING: {@link #aeronDirectoryName()} will be recreated regardless, unless set to false\n         * and an active Media Driver is detected.\n         *\n         * @return true when directory will be recreated without checks, otherwise false.\n         * @see Configuration#DIR_DELETE_ON_START_PROP_NAME\n         */\n        @Config\n        public boolean dirDeleteOnStart()\n        {\n            return dirDeleteOnStart;\n        }\n\n        /**\n         * Should the driver attempt to immediately delete {@link #aeronDirectoryName()} on startup.\n         * WARNING: {@link #aeronDirectoryName()} will be recreated regardless, unless set to false\n         * and an active Media Driver is detected.\n         *\n         * @param dirDeleteOnStart Attempt deletion without checks.\n         * @return this for a fluent API.\n         * @see Configuration#DIR_DELETE_ON_START_PROP_NAME\n         */\n        public Context dirDeleteOnStart(final boolean dirDeleteOnStart)\n        {\n            this.dirDeleteOnStart = dirDeleteOnStart;\n            return this;\n        }\n\n        /**\n         * Will the driver attempt to delete {@link #aeronDirectoryName()} on shutdown.\n         *\n         * @return true when directory will be deleted, otherwise false.\n         * @see Configuration#DIR_DELETE_ON_SHUTDOWN_PROP_NAME\n         */\n        @Config\n        public boolean dirDeleteOnShutdown()\n        {\n            return dirDeleteOnShutdown;\n        }\n\n        /**\n         * Should the driver attempt to delete {@link #aeronDirectoryName()} on shutdown.\n         *\n         * @param dirDeleteOnShutdown Attempt deletion.\n         * @return this for a fluent API.\n         * @see Configuration#DIR_DELETE_ON_SHUTDOWN_PROP_NAME\n         */\n        public Context dirDeleteOnShutdown(final boolean dirDeleteOnShutdown)\n        {\n            this.dirDeleteOnShutdown = dirDeleteOnShutdown;\n            return this;\n        }\n\n        /**\n         * Should the term buffers be created with sparse files?\n         *\n         * @return should the term buffers be created with sparse files?\n         * @see Configuration#TERM_BUFFER_SPARSE_FILE_PROP_NAME\n         */\n        @Config\n        public boolean termBufferSparseFile()\n        {\n            return termBufferSparseFile;\n        }\n\n        /**\n         * Should the term buffer be created with sparse files?\n         *\n         * @param termBufferSparseFile should the term buffers be created with sparse files?\n         * @return this for a fluent API.\n         * @see Configuration#TERM_BUFFER_SPARSE_FILE_PROP_NAME\n         */\n        public Context termBufferSparseFile(final boolean termBufferSparseFile)\n        {\n            this.termBufferSparseFile = termBufferSparseFile;\n            return this;\n        }\n\n        /**\n         * Length of the {@link RingBuffer} for sending commands to the driver conductor from clients.\n         *\n         * @return length of the {@link RingBuffer} for sending commands to the driver conductor from clients.\n         * @see Configuration#CONDUCTOR_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config\n        public int conductorBufferLength()\n        {\n            return conductorBufferLength;\n        }\n\n        /**\n         * Length of the {@link RingBuffer} for sending commands to the driver conductor from clients.\n         *\n         * @param length of the {@link RingBuffer} for sending commands to the driver conductor from clients.\n         * @return this for a fluent API.\n         * @see Configuration#CONDUCTOR_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context conductorBufferLength(final int length)\n        {\n            conductorBufferLength = length;\n            return this;\n        }\n\n        /**\n         * Length of the {@link BroadcastTransmitter} buffer for sending events to the clients.\n         *\n         * @return length of the {@link BroadcastTransmitter} buffer for sending events to the clients.\n         * @see Configuration#TO_CLIENTS_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config\n        public int toClientsBufferLength()\n        {\n            return toClientsBufferLength;\n        }\n\n        /**\n         * Length of the {@link BroadcastTransmitter} buffer for sending events to the clients.\n         *\n         * @param length of the {@link BroadcastTransmitter} buffer for sending events to the clients.\n         * @return this for a fluent API.\n         * @see Configuration#TO_CLIENTS_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context toClientsBufferLength(final int length)\n        {\n            toClientsBufferLength = length;\n            return this;\n        }\n\n        /**\n         * Length of the buffer for storing values by the {@link CountersManager}.\n         *\n         * @return length of the buffer for storing values by the {@link CountersManager}.\n         * @see Configuration#COUNTERS_VALUES_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config(id = \"COUNTERS_VALUES_BUFFER_LENGTH\")\n        public int counterValuesBufferLength()\n        {\n            return counterValuesBufferLength;\n        }\n\n        /**\n         * Length of the buffer for storing values by the {@link CountersManager}.\n         *\n         * @param length of the buffer for storing values by the {@link CountersManager}.\n         * @return this for a fluent API.\n         * @see Configuration#COUNTERS_VALUES_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context counterValuesBufferLength(final int length)\n        {\n            counterValuesBufferLength = length;\n            return this;\n        }\n\n        /**\n         * Length of the {@link DistinctErrorLog} buffer for recording exceptions.\n         *\n         * @return length of the {@link DistinctErrorLog} buffer for recording exceptions.\n         * @see Configuration#ERROR_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config\n        public int errorBufferLength()\n        {\n            return errorBufferLength;\n        }\n\n        /**\n         * Length of the {@link DistinctErrorLog} buffer for recording exceptions.\n         *\n         * @param length of the {@link DistinctErrorLog} buffer for recording exceptions.\n         * @return this for a fluent API.\n         * @see Configuration#ERROR_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context errorBufferLength(final int length)\n        {\n            errorBufferLength = length;\n            return this;\n        }\n\n        /**\n         * Should the driver perform storage checks when allocating files.\n         *\n         * @return true if the driver should perform storage checks when allocating files.\n         * @see Configuration#PERFORM_STORAGE_CHECKS_PROP_NAME\n         */\n        @Config\n        public boolean performStorageChecks()\n        {\n            return performStorageChecks;\n        }\n\n        /**\n         * Should the driver perform storage checks when allocating files.\n         *\n         * @param performStorageChecks true if the driver should perform storage checks when allocating files.\n         * @return this for a fluent API.\n         * @see Configuration#PERFORM_STORAGE_CHECKS_PROP_NAME\n         */\n        public Context performStorageChecks(final boolean performStorageChecks)\n        {\n            this.performStorageChecks = performStorageChecks;\n            return this;\n        }\n\n        /**\n         * Get the threshold in bytes below which storage warnings are issued.\n         *\n         * @return the threshold below which storage warnings are issued.\n         * @see Configuration#LOW_FILE_STORE_WARNING_THRESHOLD_PROP_NAME\n         */\n        @Config(id = \"LOW_FILE_STORE_WARNING_THRESHOLD\")\n        public long lowStorageWarningThreshold()\n        {\n            return lowStorageWarningThreshold;\n        }\n\n        /**\n         * Get the threshold in bytes below which storage warnings are issued.\n         *\n         * @param lowStorageWarningThreshold to be set in bytes.\n         * @return this for a fluent API.\n         * @see Configuration#LOW_FILE_STORE_WARNING_THRESHOLD_PROP_NAME\n         */\n        public Context lowStorageWarningThreshold(final long lowStorageWarningThreshold)\n        {\n            this.lowStorageWarningThreshold = lowStorageWarningThreshold;\n            return this;\n        }\n\n        /**\n         * The length in bytes of the loss report buffer.\n         *\n         * @return the length in bytes of the loss report buffer.\n         * @see Configuration#LOSS_REPORT_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config\n        public int lossReportBufferLength()\n        {\n            return lossReportBufferLength;\n        }\n\n        /**\n         * The length in bytes of the loss report buffer.\n         *\n         * @param length of the buffer to be used for the loss report.\n         * @return this for a fluent API.\n         * @see Configuration#LOSS_REPORT_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context lossReportBufferLength(final int length)\n        {\n            lossReportBufferLength = length;\n            return this;\n        }\n\n        /**\n         * Page size for alignment of all files.\n         *\n         * @return page size for alignment of all files.\n         * @see Configuration#FILE_PAGE_SIZE_PROP_NAME\n         */\n        @Config\n        public int filePageSize()\n        {\n            return filePageSize;\n        }\n\n        /**\n         * Page size for alignment of all files.\n         *\n         * @param filePageSize for alignment of file sizes.\n         * @return this for a fluent API.\n         * @see Configuration#FILE_PAGE_SIZE_PROP_NAME\n         */\n        public Context filePageSize(final int filePageSize)\n        {\n            this.filePageSize = filePageSize;\n            return this;\n        }\n\n        /**\n         * Interval in nanoseconds between checks for timers and timeouts.\n         *\n         * @return nanoseconds between checks for timers and timeouts.\n         * @see Configuration#TIMER_INTERVAL_PROP_NAME\n         */\n        @Config\n        public long timerIntervalNs()\n        {\n            return timerIntervalNs;\n        }\n\n        /**\n         * Interval in nanoseconds between checks for timers and timeouts.\n         *\n         * @param timerIntervalNs nanoseconds between checks for timers and timeouts.\n         * @return this for a fluent API.\n         * @see Configuration#TIMER_INTERVAL_PROP_NAME\n         */\n        public Context timerIntervalNs(final long timerIntervalNs)\n        {\n            this.timerIntervalNs = timerIntervalNs;\n            return this;\n        }\n\n        /**\n         * Time in nanoseconds an Image will be kept alive for its subscribers to consume it once disconnected.\n         *\n         * @return nanoseconds that an Image will be kept alive for its subscribers to consume it.\n         * @see Configuration#IMAGE_LIVENESS_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long imageLivenessTimeoutNs()\n        {\n            return imageLivenessTimeoutNs;\n        }\n\n        /**\n         * Time in nanoseconds an Image will be kept alive after for its subscribers to consume it once disconnected.\n         *\n         * @param timeout for keeping an image alive for its subscribers to consume it.\n         * @return this for a fluent API.\n         * @see Configuration#IMAGE_LIVENESS_TIMEOUT_PROP_NAME\n         */\n        public Context imageLivenessTimeoutNs(final long timeout)\n        {\n            this.imageLivenessTimeoutNs = timeout;\n            return this;\n        }\n\n        /**\n         * Time in nanoseconds a publication will linger once it is drained to recover potential tail loss.\n         *\n         * @return nanoseconds that a publication will linger once it is drained.\n         * @see Configuration#PUBLICATION_LINGER_PROP_NAME\n         */\n        @Config(id = \"PUBLICATION_LINGER\")\n        public long publicationLingerTimeoutNs()\n        {\n            return publicationLingerTimeoutNs;\n        }\n\n        /**\n         * Time in nanoseconds a publication will linger once it is drained to recover potential tail loss.\n         *\n         * @param timeoutNs for keeping a publication once it is drained.\n         * @return this for a fluent API.\n         * @see Configuration#PUBLICATION_LINGER_PROP_NAME\n         */\n        public Context publicationLingerTimeoutNs(final long timeoutNs)\n        {\n            this.publicationLingerTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * The timeout for when an untethered subscription that is outside the window will participate\n         * in local flow control.\n         *\n         * @return timeout that an untethered subscription outside the window limit will participate in flow control.\n         * @see Configuration#UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long untetheredWindowLimitTimeoutNs()\n        {\n            return untetheredWindowLimitTimeoutNs;\n        }\n\n        /**\n         * The timeout for when an untethered subscription that is outside the window will participate\n         * in local flow control.\n         *\n         * @param timeoutNs that an untethered subscription outside the window limit will participate in flow control.\n         * @return this for a fluent API.\n         * @see Configuration#UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME\n         */\n        public Context untetheredWindowLimitTimeoutNs(final long timeoutNs)\n        {\n            this.untetheredWindowLimitTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * The linger timeout for an untethered subscription.\n         *\n         * @return timeout that an untethered subscription will linger.\n         * @see Configuration#UNTETHERED_LINGER_TIMEOUT_PROP_NAME\n         * @since 1.48.0\n         */\n        @Config\n        public long untetheredLingerTimeoutNs()\n        {\n            return untetheredLingerTimeoutNs;\n        }\n\n        /**\n         * The timeout for when an untethered subscription that is outside the window will participate\n         * in local flow control.\n         *\n         * @param timeoutNs that an untethered subscription to linger.\n         * @return this for a fluent API.\n         * @see Configuration#UNTETHERED_LINGER_TIMEOUT_PROP_NAME\n         */\n        public Context untetheredLingerTimeoutNs(final long timeoutNs)\n        {\n            this.untetheredLingerTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout for when an untethered subscription is resting after not being able to keep up before it is allowed\n         * to rejoin a stream.\n         *\n         * @return timeout that an untethered subscription is resting before being allowed to rejoin a stream.\n         * @see Configuration#UNTETHERED_RESTING_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long untetheredRestingTimeoutNs()\n        {\n            return untetheredRestingTimeoutNs;\n        }\n\n        /**\n         * Timeout for when an untethered subscription is resting after not being able to keep up before it is allowed\n         * to rejoin a stream.\n         *\n         * @param timeoutNs that an untethered subscription is resting before being allowed to rejoin a stream.\n         * @return this for a fluent API.\n         * @see Configuration#UNTETHERED_RESTING_TIMEOUT_PROP_NAME\n         */\n        public Context untetheredRestingTimeoutNs(final long timeoutNs)\n        {\n            this.untetheredRestingTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * The delay before retransmitting after a NAK.\n         *\n         * @return delay before retransmitting after a NAK.\n         * @see Configuration#RETRANSMIT_UNICAST_DELAY_PROP_NAME\n         */\n        public long retransmitUnicastDelayNs()\n        {\n            return retransmitUnicastDelayNs;\n        }\n\n        /**\n         * The delay before retransmitting after a NAK.\n         *\n         * @param retransmitUnicastDelayNs delay before retransmitting after a NAK.\n         * @return this for a fluent API.\n         * @see Configuration#RETRANSMIT_UNICAST_DELAY_PROP_NAME\n         */\n        public Context retransmitUnicastDelayNs(final long retransmitUnicastDelayNs)\n        {\n            this.retransmitUnicastDelayNs = retransmitUnicastDelayNs;\n            return this;\n        }\n\n        /**\n         * How long to linger after delay on a NAK before responding to another NAK.\n         *\n         * @return how long to linger after delay on a NAK before responding to another NAK.\n         * @see Configuration#RETRANSMIT_UNICAST_LINGER_PROP_NAME\n         */\n        @Config\n        public long retransmitUnicastLingerNs()\n        {\n            return retransmitUnicastLingerNs;\n        }\n\n        /**\n         * How long to linger after delay on a NAK before responding to another NAK.\n         *\n         * @param retransmitUnicastLingerNs how long to linger after delay on a NAK before responding to another NAK.\n         * @return this for a fluent API.\n         * @see Configuration#RETRANSMIT_UNICAST_LINGER_PROP_NAME\n         */\n        public Context retransmitUnicastLingerNs(final long retransmitUnicastLingerNs)\n        {\n            this.retransmitUnicastLingerNs = retransmitUnicastLingerNs;\n            return this;\n        }\n\n        /**\n         * The delay before retransmission after an NAK on unicast.\n         *\n         * @return delay before retransmitting after a NAK.\n         * @see Configuration#NAK_UNICAST_DELAY_PROP_NAME\n         */\n        @Config\n        public long nakUnicastDelayNs()\n        {\n            return nakUnicastDelayNs;\n        }\n\n        /**\n         * The delay before retransmission after an NAK on unicast.\n         *\n         * @param nakUnicastDelayNs delay before retransmission after an NAK on unicast.\n         * @return this for a fluent API.\n         * @see Configuration#NAK_UNICAST_DELAY_PROP_NAME\n         * @see Configuration#NAK_UNICAST_DELAY_DEFAULT_NS\n         */\n        public Context nakUnicastDelayNs(final long nakUnicastDelayNs)\n        {\n            this.nakUnicastDelayNs = nakUnicastDelayNs;\n            return this;\n        }\n\n        /**\n         * The ratio to apply to the retry delay for unicast.\n         *\n         * @return ratio to apply to the retry delay for unicast\n         * @see #nakUnicastRetryDelayRatio(long)\n         */\n        @Config\n        public long nakUnicastRetryDelayRatio()\n        {\n            return nakUnicastRetryDelayRatio;\n        }\n\n        /**\n         * The ratio to apply to the retry delay for unicast. This will be a multiplier applied to the\n         * {@link #nakUnicastDelayNs} when constructing the {@link StaticDelayGenerator} used for handling delays on\n         * unicast NAKs.\n         *\n         * @param nakUnicastRetryDelayRatio to multiply the {@link #nakUnicastDelayNs} by to calculate the retry delay.\n         * @return this for a fluent API.\n         * @see Configuration#NAK_UNICAST_RETRY_DELAY_RATIO_PROP_NAME\n         * @see Configuration#NAK_UNICAST_RETRY_DELAY_RATIO_DEFAULT\n         */\n        public Context nakUnicastRetryDelayRatio(final long nakUnicastRetryDelayRatio)\n        {\n            this.nakUnicastRetryDelayRatio = nakUnicastRetryDelayRatio;\n            return this;\n        }\n\n        /**\n         * The maximum time to backoff before sending a NAK on multicast.\n         *\n         * @return maximum time to backoff before sending a NAK on multicast.\n         * @see Configuration#NAK_MULTICAST_MAX_BACKOFF_PROP_NAME\n         */\n        @Config\n        public long nakMulticastMaxBackoffNs()\n        {\n            return nakMulticastMaxBackoffNs;\n        }\n\n        /**\n         * The maximum time to backoff before sending a NAK on multicast.\n         *\n         * @param nakMulticastMaxBackoffNs maximum time to backoff before sending a NAK on multicast.\n         * @return this for a fluent API.\n         * @see Configuration#NAK_MULTICAST_MAX_BACKOFF_PROP_NAME\n         */\n        public Context nakMulticastMaxBackoffNs(final long nakMulticastMaxBackoffNs)\n        {\n            this.nakMulticastMaxBackoffNs = nakMulticastMaxBackoffNs;\n            return this;\n        }\n\n        /**\n         * Estimate of the multicast receiver group size on a stream.\n         *\n         * @return estimate of the multicast receiver group size on a stream.\n         * @see Configuration#NAK_MULTICAST_GROUP_SIZE_PROP_NAME\n         */\n        @Config\n        public int nakMulticastGroupSize()\n        {\n            return nakMulticastGroupSize;\n        }\n\n        /**\n         * Estimate of the multicast receiver group size on a stream.\n         *\n         * @param nakMulticastGroupSize estimate of the multicast receiver group size on a stream.\n         * @return this for a fluent API.\n         * @see Configuration#NAK_MULTICAST_GROUP_SIZE_PROP_NAME\n         */\n        public Context nakMulticastGroupSize(final int nakMulticastGroupSize)\n        {\n            this.nakMulticastGroupSize = nakMulticastGroupSize;\n            return this;\n        }\n\n        /**\n         * Time in nanoseconds after which a client is considered dead if a keep alive is not received.\n         *\n         * @return time in nanoseconds after which a client is considered dead if a keep alive is not received.\n         * @see Configuration#CLIENT_LIVENESS_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long clientLivenessTimeoutNs()\n        {\n            return CommonContext.checkDebugTimeout(clientLivenessTimeoutNs, TimeUnit.NANOSECONDS);\n        }\n\n        /**\n         * Time in nanoseconds after which a client is considered dead if a keep alive is not received.\n         *\n         * @param timeoutNs in nanoseconds after which a client is considered dead if a keep alive is not received.\n         * @return this for a fluent API.\n         * @see Configuration#CLIENT_LIVENESS_TIMEOUT_PROP_NAME\n         */\n        public Context clientLivenessTimeoutNs(final long timeoutNs)\n        {\n            this.clientLivenessTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * Time in nanoseconds after which a status message will be sent if data is flowing slowly.\n         *\n         * @return time in nanoseconds after which a status message will be sent if data is flowing slowly.\n         * @see Configuration#STATUS_MESSAGE_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long statusMessageTimeoutNs()\n        {\n            return statusMessageTimeoutNs;\n        }\n\n        /**\n         * Time in nanoseconds after which a status message will be sent if data is flowing slowly.\n         *\n         * @param statusMessageTimeoutNs after which a status message will be sent if data is flowing slowly.\n         * @return this for a fluent API.\n         * @see Configuration#STATUS_MESSAGE_TIMEOUT_PROP_NAME\n         */\n        public Context statusMessageTimeoutNs(final long statusMessageTimeoutNs)\n        {\n            this.statusMessageTimeoutNs = statusMessageTimeoutNs;\n            return this;\n        }\n\n        /**\n         * Time in nanoseconds after which a freed counter may be reused.\n         *\n         * @return time in nanoseconds after which a freed counter may be reused.\n         * @see Configuration#COUNTER_FREE_TO_REUSE_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long counterFreeToReuseTimeoutNs()\n        {\n            return counterFreeToReuseTimeoutNs;\n        }\n\n        /**\n         * Time in nanoseconds after which a freed counter may be reused.\n         *\n         * @param counterFreeToReuseTimeoutNs after which a freed counter may be reused.\n         * @return this for a fluent API.\n         * @see Configuration#COUNTER_FREE_TO_REUSE_TIMEOUT_PROP_NAME\n         */\n        public Context counterFreeToReuseTimeoutNs(final long counterFreeToReuseTimeoutNs)\n        {\n            this.counterFreeToReuseTimeoutNs = counterFreeToReuseTimeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout in nanoseconds after which a publication will be unblocked if an offer is partially complete to allow\n         * other publishers to make progress.\n         * <p>\n         * A publication can become blocked if the client crashes while publishing or if\n         * {@link io.aeron.Publication#tryClaim(int, BufferClaim)} is used without following up by calling\n         * {@link BufferClaim#commit()} or {@link BufferClaim#abort()}.\n         *\n         * @return timeout in nanoseconds after which a publication will be unblocked.\n         * @see Configuration#PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long publicationUnblockTimeoutNs()\n        {\n            return CommonContext.checkDebugTimeout(publicationUnblockTimeoutNs, TimeUnit.NANOSECONDS, 1.5);\n        }\n\n        /**\n         * Timeout in nanoseconds after which a publication will be unblocked if an offer is partially complete to allow\n         * other publishers to make progress.\n         * <p>\n         * A publication can become blocked if the client crashes while publishing or if\n         * {@link io.aeron.Publication#tryClaim(int, BufferClaim)} is used without following up by calling\n         * {@link BufferClaim#commit()} or {@link BufferClaim#abort()}.\n         *\n         * @param timeoutNs in nanoseconds after which a publication will be unblocked.\n         * @return this for a fluent API.\n         * @see Configuration#PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME\n         */\n        public Context publicationUnblockTimeoutNs(final long timeoutNs)\n        {\n            this.publicationUnblockTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * Timeout in nanoseconds after which a publication will be considered not connected if no status messages are\n         * received.\n         *\n         * @return timeout in nanoseconds after which a publication is considered not connected.\n         * @see Configuration#PUBLICATION_CONNECTION_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long publicationConnectionTimeoutNs()\n        {\n            return publicationConnectionTimeoutNs;\n        }\n\n        /**\n         * Timeout in nanoseconds after which a publication will be considered not connected if no status messages are\n         * received.\n         *\n         * @param timeoutNs in nanoseconds after which a publication will be considered not connected.\n         * @return this for a fluent API.\n         * @see Configuration#PUBLICATION_CONNECTION_TIMEOUT_PROP_NAME\n         */\n        public Context publicationConnectionTimeoutNs(final long timeoutNs)\n        {\n            this.publicationConnectionTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * Does a spy subscription simulate a connection to a network publication.\n         * <p>\n         * If true then this will override the min group size of the min and tagged flow control strategies.\n         *\n         * @return true if a spy subscription should simulate a connection to a network publication.\n         * @see Configuration#SPIES_SIMULATE_CONNECTION_PROP_NAME\n         */\n        @Config\n        public boolean spiesSimulateConnection()\n        {\n            return spiesSimulateConnection;\n        }\n\n        /**\n         * Does a spy subscription simulate a connection to a network publication.\n         * <p>\n         * If true then this will override the min group size of the min and tagged flow control strategies.\n         *\n         * @param spiesSimulateConnection true if a spy subscription simulates a connection to a network publication.\n         * @return this for a fluent API.\n         * @see Configuration#SPIES_SIMULATE_CONNECTION_PROP_NAME\n         */\n        public Context spiesSimulateConnection(final boolean spiesSimulateConnection)\n        {\n            this.spiesSimulateConnection = spiesSimulateConnection;\n            return this;\n        }\n\n        /**\n         * Does a stream NAK when loss is detected, reliable=true, or gap fill, reliable=false.\n         * <p>\n         * The default can be overridden with a channel param.\n         *\n         * @return true if NAK on loss to be reliable otherwise false for gap fill.\n         * @see Configuration#RELIABLE_STREAM_PROP_NAME\n         * @see CommonContext#RELIABLE_STREAM_PARAM_NAME\n         */\n        @Config\n        public boolean reliableStream()\n        {\n            return reliableStream;\n        }\n\n        /**\n         * Does a stream NAK when loss is detected, reliable=true, or gap fill, reliable=false.\n         * <p>\n         * The default can be overridden with a channel param.\n         *\n         * @param reliableStream true if a stream should NAK on loss otherwise gap fill.\n         * @return this for a fluent API.\n         * @see Configuration#RELIABLE_STREAM_PROP_NAME\n         * @see CommonContext#RELIABLE_STREAM_PARAM_NAME\n         */\n        public Context reliableStream(final boolean reliableStream)\n        {\n            this.reliableStream = reliableStream;\n            return this;\n        }\n\n        /**\n         * Do subscriptions have a tether, so they participate in local flow control when more than one.\n         * <p>\n         * The default can be overridden with a channel param.\n         *\n         * @return true if subscriptions should have a tether for local flow control.\n         * @see Configuration#TETHER_SUBSCRIPTIONS_PROP_NAME\n         * @see CommonContext#TETHER_PARAM_NAME\n         */\n        @Config\n        public boolean tetherSubscriptions()\n        {\n            return tetherSubscriptions;\n        }\n\n        /**\n         * Do subscriptions have a tether so, they participate in local flow control when more than one.\n         * <p>\n         * The default can be overridden with a channel param.\n         *\n         * @param tetherSubscription true if subscriptions should have a tether for local flow control.\n         * @return this for a fluent API.\n         * @see Configuration#TETHER_SUBSCRIPTIONS_PROP_NAME\n         * @see CommonContext#TETHER_PARAM_NAME\n         */\n        public Context tetherSubscriptions(final boolean tetherSubscription)\n        {\n            this.tetherSubscriptions = tetherSubscription;\n            return this;\n        }\n\n        /**\n         * Should network subscriptions be considered part of a group even if using a unicast endpoint, should it be\n         * considered an individual even if using a multicast endpoint, or should the use of a unicast/multicast\n         * endpoint infer the usage.\n         * <p>\n         * The default can be overridden with a channel param.\n         *\n         * @return FORCE_TRUE if subscriptions should be considered a group member, FORCE_FALSE if not, or depends\n         * on endpoint.\n         * @see Configuration#GROUP_RECEIVER_CONSIDERATION_PROP_NAME\n         * @see CommonContext#GROUP_PARAM_NAME\n         */\n        public InferableBoolean receiverGroupConsideration()\n        {\n            return receiverGroupConsideration;\n        }\n\n        /**\n         * Should network subscriptions be considered part of a group even if using a unicast endpoint, should it be\n         * considered an individual even if using a multicast endpoint, or should the use of a unicast/multicast\n         * endpoint infer the usage.\n         * <p>\n         * The default can be overridden with a channel param.\n         *\n         * @param receiverGroupConsideration true if subscriptions should be considered a group member,\n         *                                   false if not, or infer from endpoint.\n         * @return this for a fluent API.\n         * @see Configuration#GROUP_RECEIVER_CONSIDERATION_PROP_NAME\n         * @see CommonContext#GROUP_PARAM_NAME\n         */\n        public Context receiverGroupConsideration(final InferableBoolean receiverGroupConsideration)\n        {\n            this.receiverGroupConsideration = receiverGroupConsideration;\n            return this;\n        }\n\n        /**\n         * Does a subscription attempt to rejoin an unavailable stream after a cooldown or not.\n         * <p>\n         * The default can be overridden with a channel param.\n         *\n         * @return true if subscription will rejoin after cooldown or false if not.\n         * @see Configuration#REJOIN_STREAM_PROP_NAME\n         * @see CommonContext#REJOIN_PARAM_NAME\n         */\n        @Config\n        public boolean rejoinStream()\n        {\n            return rejoinStream;\n        }\n\n        /**\n         * Does a subscription attempt to rejoin an unavailable stream after a cooldown or not.\n         * <p>\n         * The default can be overridden with a channel param.\n         *\n         * @param rejoinStream true if subscription will rejoin after cooldown or false if not.\n         * @return this for a fluent API.\n         * @see Configuration#REJOIN_STREAM_PROP_NAME\n         * @see CommonContext#REJOIN_PARAM_NAME\n         */\n        public Context rejoinStream(final boolean rejoinStream)\n        {\n            this.rejoinStream = rejoinStream;\n            return this;\n        }\n\n        /**\n         * Default length for a term buffer on a network publication.\n         *\n         * @return default length for a term buffer on a network publication.\n         * @see Configuration#TERM_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config(id = \"TERM_BUFFER_LENGTH\")\n        public int publicationTermBufferLength()\n        {\n            return publicationTermBufferLength;\n        }\n\n        /**\n         * Default length for a term buffer on a network publication.\n         * <p>\n         * This can be overridden on publication by using channel URI params.\n         *\n         * @param termBufferLength default length for a term buffer on a network publication.\n         * @return this for a fluent API.\n         * @see Configuration#TERM_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context publicationTermBufferLength(final int termBufferLength)\n        {\n            this.publicationTermBufferLength = termBufferLength;\n            return this;\n        }\n\n        /**\n         * Default length for a term buffer on an IPC publication.\n         *\n         * @return default length for a term buffer on an IPC publication.\n         * @see Configuration#IPC_TERM_BUFFER_LENGTH_PROP_NAME\n         */\n        @Config\n        public int ipcTermBufferLength()\n        {\n            return ipcTermBufferLength;\n        }\n\n        /**\n         * Default length for a term buffer on an IPC publication.\n         * <p>\n         * This can be overridden on publication by using channel URI params.\n         *\n         * @param termBufferLength default length for a term buffer on an IPC publication.\n         * @return this for a fluent API.\n         * @see Configuration#IPC_TERM_BUFFER_LENGTH_PROP_NAME\n         */\n        public Context ipcTermBufferLength(final int termBufferLength)\n        {\n            this.ipcTermBufferLength = termBufferLength;\n            return this;\n        }\n\n        /**\n         * Default length for a term buffer window on a network publication.\n         *\n         * @return default length for a term buffer window on a network publication.\n         * @see Configuration#PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME\n         */\n        @Config\n        public int publicationTermWindowLength()\n        {\n            return publicationTermWindowLength;\n        }\n\n        /**\n         * Default length for a term buffer window on a network publication.\n         *\n         * @param termWindowLength default length for a term buffer window on a network publication.\n         * @return this for a fluent API.\n         * @see Configuration#PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME\n         */\n        public Context publicationTermWindowLength(final int termWindowLength)\n        {\n            this.publicationTermWindowLength = termWindowLength;\n            return this;\n        }\n\n        /**\n         * Default length for a term buffer window on an IPC publication.\n         *\n         * @return default length for a term buffer window on an IPC publication.\n         * @see Configuration#IPC_PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME\n         */\n        @Config\n        public int ipcPublicationTermWindowLength()\n        {\n            return ipcPublicationTermWindowLength;\n        }\n\n        /**\n         * Default length for a term buffer window on an IPC publication.\n         *\n         * @param termWindowLength default length for a term buffer window on an IPC publication.\n         * @return this for a fluent API.\n         * @see Configuration#IPC_PUBLICATION_TERM_WINDOW_LENGTH_PROP_NAME\n         */\n        public Context ipcPublicationTermWindowLength(final int termWindowLength)\n        {\n            this.ipcPublicationTermWindowLength = termWindowLength;\n            return this;\n        }\n\n        /**\n         * The initial window for in flight data on a connection which must be less than\n         * {@link Configuration#SOCKET_RCVBUF_LENGTH_PROP_NAME}. This needs to be configured for throughput respecting\n         * BDP.\n         *\n         * @return The initial window for in flight data on a connection\n         * @see Configuration#INITIAL_WINDOW_LENGTH_PROP_NAME\n         */\n        @Config\n        public int initialWindowLength()\n        {\n            return initialWindowLength;\n        }\n\n        /**\n         * The initial window for in flight data on a connection which must be less than\n         * {@link Configuration#SOCKET_RCVBUF_LENGTH_PROP_NAME}. This needs to be configured for throughput respecting\n         * BDP.\n         *\n         * @param initialWindowLength The initial window for in flight data on a connection\n         * @return this for a fluent API.\n         * @see Configuration#INITIAL_WINDOW_LENGTH_PROP_NAME\n         */\n        public Context initialWindowLength(final int initialWindowLength)\n        {\n            this.initialWindowLength = initialWindowLength;\n            return this;\n        }\n\n        /**\n         * The socket send buffer length which is the OS SO_SNDBUF.\n         *\n         * @return the socket send buffer length.\n         * @see Configuration#SOCKET_SNDBUF_LENGTH_PROP_NAME\n         */\n        @Config\n        public int socketSndbufLength()\n        {\n            return socketSndbufLength;\n        }\n\n        /**\n         * The socket send buffer length which is the OS SO_SNDBUF.\n         *\n         * @param socketSndbufLength which is the OS SO_SNDBUF.\n         * @return this for a fluent API.\n         * @see Configuration#SOCKET_SNDBUF_LENGTH_PROP_NAME\n         */\n        public Context socketSndbufLength(final int socketSndbufLength)\n        {\n            this.socketSndbufLength = socketSndbufLength;\n            return this;\n        }\n\n        /**\n         * The socket send buffer length which is the OS SO_RCVBUF.\n         *\n         * @return the socket send buffer length.\n         * @see Configuration#SOCKET_RCVBUF_LENGTH_PROP_NAME\n         */\n        @Config\n        public int socketRcvbufLength()\n        {\n            return socketRcvbufLength;\n        }\n\n        /**\n         * The socket send buffer length which is the OS SO_RCVBUF.\n         *\n         * @param socketRcvbufLength which is the OS SO_RCVBUF.\n         * @return this for a fluent API.\n         * @see Configuration#SOCKET_RCVBUF_LENGTH_PROP_NAME\n         */\n        public Context socketRcvbufLength(final int socketRcvbufLength)\n        {\n            this.socketRcvbufLength = socketRcvbufLength;\n            return this;\n        }\n\n        /**\n         * The TTL value to be used for multicast sockets.\n         *\n         * @return TTL value to be used for multicast sockets.\n         * @see Configuration#SOCKET_MULTICAST_TTL_PROP_NAME\n         */\n        @Config\n        public int socketMulticastTtl()\n        {\n            return socketMulticastTtl;\n        }\n\n        /**\n         * TTL value to be used for multicast sockets.\n         *\n         * @param ttl value to be used for multicast sockets.\n         * @return this for a fluent API.\n         * @see Configuration#SOCKET_MULTICAST_TTL_PROP_NAME\n         */\n        public Context socketMulticastTtl(final int ttl)\n        {\n            socketMulticastTtl = ttl;\n            return this;\n        }\n\n        /**\n         * MTU in bytes for datagrams sent to the network. Messages larger than this are fragmented.\n         * <p>\n         * Larger MTUs reduce system call overhead at the expense of possible increase in loss which\n         * will need to be recovered. If this is greater than the network MTU for UDP then the packet will be\n         * fragmented and can amplify the impact of loss.\n         *\n         * @return MTU in bytes for datagrams sent to the network.\n         * @see Configuration#MTU_LENGTH_PROP_NAME\n         */\n        @Config\n        public int mtuLength()\n        {\n            return mtuLength;\n        }\n\n        /**\n         * MTU in bytes for datagrams sent to the network. Messages larger than this are fragmented.\n         * <p>\n         * Larger MTUs reduce system call overhead at the expense of possible increase in loss which\n         * will need to be recovered. If this is greater than the network MTU for UDP then the packet will be\n         * fragmented and can amplify the impact of loss.\n         *\n         * @param mtuLength in bytes for datagrams sent to the network.\n         * @return this for a fluent API.\n         * @see Configuration#MTU_LENGTH_PROP_NAME\n         */\n        public Context mtuLength(final int mtuLength)\n        {\n            this.mtuLength = mtuLength;\n            return this;\n        }\n\n        /**\n         * MTU in bytes for datagrams sent over shared memory. Messages larger than this are fragmented.\n         * <p>\n         * Larger MTUs reduce fragmentation. If an IPC stream is recorded to be later sent over the network then\n         * a large MTU may be an issue.\n         *\n         * @return MTU in bytes for message fragments.\n         * @see Configuration#IPC_MTU_LENGTH_PROP_NAME\n         */\n        @Config\n        public int ipcMtuLength()\n        {\n            return ipcMtuLength;\n        }\n\n        /**\n         * MTU in bytes for datagrams sent over shared memory. Messages larger than this are fragmented.\n         * <p>\n         * Larger MTUs reduce fragmentation. If an IPC stream is recorded to be later sent over the network then\n         * a large MTU may be an issue.\n         *\n         * @param ipcMtuLength in bytes for message fragments.\n         * @return this for a fluent API.\n         * @see Configuration#IPC_MTU_LENGTH_PROP_NAME\n         */\n        public Context ipcMtuLength(final int ipcMtuLength)\n        {\n            this.ipcMtuLength = ipcMtuLength;\n            return this;\n        }\n\n        /**\n         * The {@link EpochClock} as a source of time in milliseconds for wall clock time.\n         *\n         * @return the {@link EpochClock} as a source of time in milliseconds for wall clock time.\n         */\n        public EpochClock epochClock()\n        {\n            return epochClock;\n        }\n\n        /**\n         * The {@link EpochClock} as a source of time in milliseconds for wall clock time.\n         *\n         * @param clock to be used.\n         * @return this for a fluent API.\n         */\n        public Context epochClock(final EpochClock clock)\n        {\n            epochClock = clock;\n            return this;\n        }\n\n        /**\n         * The {@link NanoClock} as a source of time in nanoseconds for measuring duration.\n         *\n         * @return the {@link NanoClock} as a source of time in nanoseconds for measuring duration.\n         */\n        public NanoClock nanoClock()\n        {\n            return nanoClock;\n        }\n\n        /**\n         * The {@link NanoClock} as a source of time in nanoseconds for measuring duration.\n         *\n         * @param clock to be used.\n         * @return this for a fluent API.\n         */\n        public Context nanoClock(final NanoClock clock)\n        {\n            nanoClock = clock;\n            return this;\n        }\n\n        /**\n         * The {@link CachedEpochClock} as a source of time in milliseconds for wall clock time.\n         *\n         * @return the {@link CachedEpochClock} as a source of time in milliseconds for wall clock time.\n         */\n        public CachedEpochClock cachedEpochClock()\n        {\n            return cachedEpochClock;\n        }\n\n        /**\n         * The {@link CachedEpochClock} as a source of time in milliseconds for wall clock time.\n         *\n         * @param clock to be used.\n         * @return this for a fluent API.\n         */\n        public Context cachedEpochClock(final CachedEpochClock clock)\n        {\n            cachedEpochClock = clock;\n            return this;\n        }\n\n        /**\n         * The {@link CachedNanoClock} as a source of time in nanoseconds for measuring duration. This is updated\n         * once per work cycle of the {@link DriverConductor}.\n         *\n         * @return the {@link CachedNanoClock} as a source of time in nanoseconds for measuring duration.\n         */\n        public CachedNanoClock cachedNanoClock()\n        {\n            return cachedNanoClock;\n        }\n\n        /**\n         * The {@link CachedNanoClock} as a source of time in nanoseconds for measuring duration for the\n         * {@link DriverConductor}.\n         *\n         * @param clock to be used.\n         * @return this for a fluent API.\n         */\n        public Context cachedNanoClock(final CachedNanoClock clock)\n        {\n            cachedNanoClock = clock;\n            return this;\n        }\n\n        /**\n         * The {@link CachedNanoClock} as a source of time in nanoseconds for measuring duration. This is updated\n         * once per work cycle of the {@link Sender}.\n         *\n         * @return the {@link CachedNanoClock} as a source of time in nanoseconds for measuring duration.\n         */\n        public CachedNanoClock senderCachedNanoClock()\n        {\n            return senderCachedNanoClock;\n        }\n\n        /**\n         * The {@link CachedNanoClock} as a source of time in nanoseconds for measuring duration for the\n         * {@link Sender}.\n         *\n         * @param clock to be used.\n         * @return this for a fluent API.\n         */\n        public Context senderCachedNanoClock(final CachedNanoClock clock)\n        {\n            senderCachedNanoClock = clock;\n            return this;\n        }\n\n        /**\n         * The {@link CachedNanoClock} as a source of time in nanoseconds for measuring duration. This is updated\n         * once per work cycle of the {@link Receiver}.\n         *\n         * @return the {@link CachedNanoClock} as a source of time in nanoseconds for measuring duration.\n         */\n        public CachedNanoClock receiverCachedNanoClock()\n        {\n            return receiverCachedNanoClock;\n        }\n\n        /**\n         * The {@link CachedNanoClock} as a source of time in nanoseconds for measuring duration for the\n         * {@link Receiver}.\n         *\n         * @param clock to be used.\n         * @return this for a fluent API.\n         */\n        public Context receiverCachedNanoClock(final CachedNanoClock clock)\n        {\n            receiverCachedNanoClock = clock;\n            return this;\n        }\n\n        /**\n         * {@link ThreadingMode} that should be used for the driver.\n         *\n         * @return {@link ThreadingMode} that should be used for the driver.\n         * @see Configuration#THREADING_MODE_PROP_NAME\n         */\n        @Config\n        public ThreadingMode threadingMode()\n        {\n            return threadingMode;\n        }\n\n        /**\n         * {@link ThreadingMode} that should be used for the driver.\n         *\n         * @param threadingMode that should be used for the driver.\n         * @return this for a fluent API.\n         * @see Configuration#THREADING_MODE_PROP_NAME\n         */\n        public Context threadingMode(final ThreadingMode threadingMode)\n        {\n            this.threadingMode = threadingMode;\n            return this;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the {@link Sender} when running in\n         * {@link ThreadingMode#DEDICATED}.\n         *\n         * @return {@link ThreadFactory} to be used for creating agent thread for the {@link Sender}.\n         */\n        public ThreadFactory senderThreadFactory()\n        {\n            return senderThreadFactory;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the {@link Sender} when running in\n         * {@link ThreadingMode#DEDICATED}.\n         * <p>\n         * If none is provided then this will default a simple new operation.\n         *\n         * @param factory to be used for creating agent thread for the {@link Sender}.\n         * @return this for a fluent API.\n         */\n        public Context senderThreadFactory(final ThreadFactory factory)\n        {\n            senderThreadFactory = factory;\n            return this;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the {@link Receiver} when running in\n         * {@link ThreadingMode#DEDICATED}.\n         *\n         * @return {@link ThreadFactory} to be used for creating agent thread for the {@link Receiver}.\n         */\n        public ThreadFactory receiverThreadFactory()\n        {\n            return receiverThreadFactory;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the {@link Receiver} when running in\n         * {@link ThreadingMode#DEDICATED}.\n         * <p>\n         * If none is provided then this will default a simple new operation.\n         *\n         * @param factory to be used for creating agent thread for the {@link Receiver}.\n         * @return this for a fluent API.\n         */\n        public Context receiverThreadFactory(final ThreadFactory factory)\n        {\n            receiverThreadFactory = factory;\n            return this;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the {@link DriverConductor} when running in\n         * {@link ThreadingMode#DEDICATED} or {@link ThreadingMode#SHARED_NETWORK}.\n         *\n         * @return {@link ThreadFactory} to be used for creating agent thread for the {@link DriverConductor}.\n         */\n        public ThreadFactory conductorThreadFactory()\n        {\n            return conductorThreadFactory;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the {@link DriverConductor} when running in\n         * {@link ThreadingMode#DEDICATED} or {@link ThreadingMode#SHARED_NETWORK}.\n         * <p>\n         * If none is provided then this will default a simple new operation.\n         *\n         * @param factory to be used for creating agent thread for the {@link DriverConductor}.\n         * @return this for a fluent API.\n         */\n        public Context conductorThreadFactory(final ThreadFactory factory)\n        {\n            conductorThreadFactory = factory;\n            return this;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the all driver agents as a\n         * {@link CompositeAgent} when running in {@link ThreadingMode#SHARED}.\n         *\n         * @return {@link ThreadFactory} to be used for creating agent thread for the {@link CompositeAgent}.\n         */\n        public ThreadFactory sharedThreadFactory()\n        {\n            return sharedThreadFactory;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the all driver agents as a\n         * {@link CompositeAgent} when running in {@link ThreadingMode#SHARED}.\n         * <p>\n         * If none is provided then this will default a simple new operation.\n         *\n         * @param factory to be used for creating agent thread for the {@link CompositeAgent}.\n         * @return this for a fluent API.\n         */\n        public Context sharedThreadFactory(final ThreadFactory factory)\n        {\n            sharedThreadFactory = factory;\n            return this;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the sender and receiver agents as a\n         * {@link CompositeAgent} when running in {@link ThreadingMode#SHARED_NETWORK}.\n         *\n         * @return {@link ThreadFactory} to be used for creating agent thread for the {@link CompositeAgent}.\n         */\n        public ThreadFactory sharedNetworkThreadFactory()\n        {\n            return sharedNetworkThreadFactory;\n        }\n\n        /**\n         * {@link ThreadFactory} to be used for creating agent thread for the sender and receiver agents as a\n         * {@link CompositeAgent} when running in {@link ThreadingMode#SHARED_NETWORK}.\n         * <p>\n         * If none is provided then this will default a simple new operation.\n         *\n         * @param factory to be used for creating agent thread for the {@link CompositeAgent}.\n         * @return this for a fluent API.\n         */\n        public Context sharedNetworkThreadFactory(final ThreadFactory factory)\n        {\n            sharedNetworkThreadFactory = factory;\n            return this;\n        }\n\n        /**\n         * Returns the number of threads for async task executor.\n         *\n         * @return number of threads.\n         * @see Configuration#ASYNC_TASK_EXECUTOR_THREADS_PROP_NAME\n         * @since 1.44.0\n         */\n        @Config\n        public int asyncTaskExecutorThreads()\n        {\n            return asyncTaskExecutorThreads;\n        }\n\n        /**\n         * Sets the number of threads for async task executor.\n         *\n         * @param asyncTaskExecutorThreads number of async worker threads.\n         * @return this for a fluent API.\n         * @since 1.44.0\n         */\n        public Context asyncTaskExecutorThreads(final int asyncTaskExecutorThreads)\n        {\n            this.asyncTaskExecutorThreads = asyncTaskExecutorThreads;\n            return this;\n        }\n\n        /**\n         * {@link Executor} to be used for asynchronous task execution in the {@link DriverConductor}.\n         *\n         * @return executor service for asynchronous tasks. If not explicitly assigned uses\n         * {@link #asyncTaskExecutorThreads()} to size the thread pool.\n         * @since 1.44.0\n         */\n        public Executor asyncTaskExecutor()\n        {\n            return asyncTaskExecutor;\n        }\n\n        /**\n         * {@link Executor} to be used for asynchronous task execution in the {@link DriverConductor}.\n         *\n         * @param asyncTaskExecutor to be used for asynchronous task execution in the {@link DriverConductor}.\n         * @return this for a fluent API.\n         * @since 1.44.0\n         */\n        public Context asyncTaskExecutor(final Executor asyncTaskExecutor)\n        {\n            this.asyncTaskExecutor = asyncTaskExecutor;\n            return this;\n        }\n\n        /**\n         * Returns the number of outstanding retransmits.\n         *\n         * @return number of outstanding retransmits.\n         * @see Configuration#MAX_RESEND_PROP_NAME\n         * @since 1.45.0\n         */\n        @Config\n        public int maxResend()\n        {\n            return maxResend;\n        }\n\n        /**\n         * Sets the number of outstanding retransmits.\n         *\n         * @param maxResend number of outstanding retransmits allowed.\n         * @return this for a fluent API.\n         * @since 1.45.0\n         */\n        public Context maxResend(final int maxResend)\n        {\n            this.maxResend = maxResend;\n            return this;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link Sender} when in {@link ThreadingMode#DEDICATED}.\n         *\n         * @return {@link IdleStrategy} to be used by the {@link Sender} when in {@link ThreadingMode#DEDICATED}.\n         * @see Configuration#SENDER_IDLE_STRATEGY_PROP_NAME\n         */\n        @Config\n        public IdleStrategy senderIdleStrategy()\n        {\n            return senderIdleStrategy;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link Sender} when in {@link ThreadingMode#DEDICATED}.\n         *\n         * @param strategy to be used by the {@link Sender} when in {@link ThreadingMode#DEDICATED}.\n         * @return this for a fluent API.\n         * @see Configuration#SENDER_IDLE_STRATEGY_PROP_NAME\n         */\n        public Context senderIdleStrategy(final IdleStrategy strategy)\n        {\n            senderIdleStrategy = strategy;\n            return this;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link Receiver} when in {@link ThreadingMode#DEDICATED}.\n         *\n         * @return {@link IdleStrategy} used by the {@link Receiver} when in {@link ThreadingMode#DEDICATED}.\n         * @see Configuration#RECEIVER_IDLE_STRATEGY_PROP_NAME\n         */\n        @Config\n        public IdleStrategy receiverIdleStrategy()\n        {\n            return receiverIdleStrategy;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link Receiver} when in {@link ThreadingMode#DEDICATED}.\n         *\n         * @param strategy to be used by the {@link Receiver} when in {@link ThreadingMode#DEDICATED}.\n         * @return this for a fluent API.\n         * @see Configuration#RECEIVER_IDLE_STRATEGY_PROP_NAME\n         */\n        public Context receiverIdleStrategy(final IdleStrategy strategy)\n        {\n            receiverIdleStrategy = strategy;\n            return this;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link DriverConductor} when in {@link ThreadingMode#DEDICATED}\n         * or {@link ThreadingMode#SHARED_NETWORK}.\n         *\n         * @return {@link IdleStrategy} used by the {@link DriverConductor}\n         * @see Configuration#CONDUCTOR_IDLE_STRATEGY_PROP_NAME\n         */\n        @Config\n        public IdleStrategy conductorIdleStrategy()\n        {\n            return conductorIdleStrategy;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link DriverConductor} when in {@link ThreadingMode#DEDICATED}\n         * or {@link ThreadingMode#SHARED_NETWORK}.\n         *\n         * @param strategy to be used by the {@link DriverConductor}.\n         * @return this for a fluent API.\n         * @see Configuration#CONDUCTOR_IDLE_STRATEGY_PROP_NAME\n         */\n        public Context conductorIdleStrategy(final IdleStrategy strategy)\n        {\n            conductorIdleStrategy = strategy;\n            return this;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link Sender} and {@link Receiver} agents when in\n         * {@link ThreadingMode#SHARED_NETWORK}.\n         *\n         * @return {@link IdleStrategy} used by the {@link Sender} and {@link Receiver}.\n         * @see Configuration#SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME\n         */\n        @Config\n        public IdleStrategy sharedNetworkIdleStrategy()\n        {\n            return sharedNetworkIdleStrategy;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link Sender} and {@link Receiver} agents when in\n         * {@link ThreadingMode#SHARED_NETWORK}.\n         *\n         * @param strategy to be used by the {@link Sender} and {@link Receiver}.\n         * @return this for a fluent API.\n         * @see Configuration#SHARED_NETWORK_IDLE_STRATEGY_PROP_NAME\n         */\n        public Context sharedNetworkIdleStrategy(final IdleStrategy strategy)\n        {\n            sharedNetworkIdleStrategy = strategy;\n            return this;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link Sender}, {@link Receiver} and {@link DriverConductor}\n         * agents when in {@link ThreadingMode#SHARED}.\n         *\n         * @return {@link IdleStrategy} used by the {@link Sender}, {@link Receiver} and {@link DriverConductor}.\n         * @see Configuration#SHARED_IDLE_STRATEGY_PROP_NAME\n         */\n        @Config\n        public IdleStrategy sharedIdleStrategy()\n        {\n            return sharedIdleStrategy;\n        }\n\n        /**\n         * {@link IdleStrategy} to be used by the {@link Sender}, {@link Receiver} and {@link DriverConductor}\n         * agents when in {@link ThreadingMode#SHARED}.\n         *\n         * @param strategy to be used by the {@link Sender}, {@link Receiver} and {@link DriverConductor}.\n         * @return this for a fluent API.\n         * @see Configuration#SHARED_IDLE_STRATEGY_PROP_NAME\n         */\n        public Context sharedIdleStrategy(final IdleStrategy strategy)\n        {\n            sharedIdleStrategy = strategy;\n            return this;\n        }\n\n        /**\n         * Supplier of dynamically created {@link SendChannelEndpoint} subclasses for specialising interactions\n         * with the send side of a network channel.\n         *\n         * @return the supplier of dynamically created {@link SendChannelEndpoint} subclasses.\n         * @see Configuration#SEND_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME\n         */\n        @Config\n        public SendChannelEndpointSupplier sendChannelEndpointSupplier()\n        {\n            return sendChannelEndpointSupplier;\n        }\n\n        /**\n         * Supplier of dynamically created {@link SendChannelEndpoint} subclasses for specialising interactions\n         * with the send side of a network channel.\n         *\n         * @param supplier of dynamically created {@link SendChannelEndpoint} subclasses.\n         * @return this for a fluent API.\n         * @see Configuration#SEND_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME\n         */\n        public Context sendChannelEndpointSupplier(final SendChannelEndpointSupplier supplier)\n        {\n            sendChannelEndpointSupplier = supplier;\n            return this;\n        }\n\n        /**\n         * Supplier of dynamically created {@link ReceiveChannelEndpoint} subclasses for specialising interactions\n         * with the receiving side of a network channel.\n         *\n         * @return the supplier of dynamically created {@link ReceiveChannelEndpoint} subclasses.\n         * @see Configuration#RECEIVE_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME\n         */\n        @Config\n        public ReceiveChannelEndpointSupplier receiveChannelEndpointSupplier()\n        {\n            return receiveChannelEndpointSupplier;\n        }\n\n        /**\n         * Supplier of dynamically created {@link ReceiveChannelEndpoint} subclasses for specialising interactions\n         * with the receiving side of a network channel.\n         *\n         * @param supplier of dynamically created {@link ReceiveChannelEndpoint} subclasses.\n         * @return this for a fluent API.\n         * @see Configuration#RECEIVE_CHANNEL_ENDPOINT_SUPPLIER_PROP_NAME\n         */\n        public Context receiveChannelEndpointSupplier(final ReceiveChannelEndpointSupplier supplier)\n        {\n            receiveChannelEndpointSupplier = supplier;\n            return this;\n        }\n\n        /**\n         * The thread local buffers and associated objects for use by subclasses of {@link ReceiveChannelEndpoint}.\n         *\n         * @return thread local buffers and associated objects for use by subclasses of {@link ReceiveChannelEndpoint}.\n         */\n        public ReceiveChannelEndpointThreadLocals receiveChannelEndpointThreadLocals()\n        {\n            return receiveChannelEndpointThreadLocals;\n        }\n\n        /**\n         * The thread local buffers and associated objects for use by subclasses of {@link ReceiveChannelEndpoint}.\n         *\n         * @param threadLocals for use by subclasses of {@link ReceiveChannelEndpoint}.\n         * @return this for a fluent API.\n         */\n        public Context receiveChannelEndpointThreadLocals(final ReceiveChannelEndpointThreadLocals threadLocals)\n        {\n            receiveChannelEndpointThreadLocals = threadLocals;\n            return this;\n        }\n\n        /**\n         * The temporary buffer than can be used to build up counter labels to avoid allocation.\n         *\n         * @return the temporary buffer than can be used to build up counter labels to avoid allocation.\n         */\n        public MutableDirectBuffer tempBuffer()\n        {\n            return tempBuffer;\n        }\n\n        /**\n         * Set the temporary buffer than can be used to build up counter labels to avoid allocation.\n         *\n         * @param tempBuffer to be used to avoid allocation.\n         * @return the temporary buffer than can be used to build up counter labels to avoid allocation.\n         */\n        public Context tempBuffer(final MutableDirectBuffer tempBuffer)\n        {\n            this.tempBuffer = tempBuffer;\n            return this;\n        }\n\n        /**\n         * Supplier of dynamically created {@link FlowControl} strategies for unicast connections.\n         *\n         * @return supplier of dynamically created {@link FlowControl} strategies for unicast connections.\n         * @see Configuration#UNICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME\n         */\n        @Config(id = \"UNICAST_FLOW_CONTROL_STRATEGY_SUPPLIER\")\n        public FlowControlSupplier unicastFlowControlSupplier()\n        {\n            return unicastFlowControlSupplier;\n        }\n\n        /**\n         * Supplier of dynamically created {@link FlowControl} strategies for unicast connections.\n         *\n         * @param flowControlSupplier of {@link FlowControl} strategies for unicast connections.\n         * @return this for a fluent API.\n         * @see Configuration#UNICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME\n         */\n        public Context unicastFlowControlSupplier(final FlowControlSupplier flowControlSupplier)\n        {\n            unicastFlowControlSupplier = flowControlSupplier;\n            return this;\n        }\n\n        /**\n         * Supplier of dynamically created {@link FlowControl} strategies for multicast connections.\n         *\n         * @return supplier of dynamically created {@link FlowControl} strategies for multicast connections.\n         * @see Configuration#MULTICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME\n         */\n        @Config(id = \"MULTICAST_FLOW_CONTROL_STRATEGY_SUPPLIER\")\n        public FlowControlSupplier multicastFlowControlSupplier()\n        {\n            return multicastFlowControlSupplier;\n        }\n\n        /**\n         * Supplier of dynamically created {@link FlowControl} strategies for multicast connections.\n         *\n         * @param flowControlSupplier of {@link FlowControl} strategies for multicast connections.\n         * @return this for a fluent API.\n         * @see Configuration#MULTICAST_FLOW_CONTROL_STRATEGY_SUPPLIER_PROP_NAME\n         */\n        public Context multicastFlowControlSupplier(final FlowControlSupplier flowControlSupplier)\n        {\n            multicastFlowControlSupplier = flowControlSupplier;\n            return this;\n        }\n\n        /**\n         * Timeout for min multicast flow control strategy.\n         *\n         * @return timeout in ns.\n         * @see Configuration#FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME\n         */\n        @Config\n        public long flowControlReceiverTimeoutNs()\n        {\n            return flowControlReceiverTimeoutNs;\n        }\n\n        /**\n         * Timeout for min multicast flow control strategy.\n         *\n         * @param timeoutNs in ns.\n         * @return this for a fluent API.\n         * @see Configuration#FLOW_CONTROL_RECEIVER_TIMEOUT_PROP_NAME\n         */\n        public Context flowControlReceiverTimeoutNs(final long timeoutNs)\n        {\n            this.flowControlReceiverTimeoutNs = timeoutNs;\n            return this;\n        }\n\n        /**\n         * Flow control strategies may limit how much data is sent during a retransmission to\n         * avoid saturating the network and potentially causing more loss. The maximum\n         * retransmission size will be based on a multiple of the receiver window size.\n         * <p>\n         * See @{@link FlowControl#calculateRetransmissionLength(int, int, int, int)}\n         *\n         * @return window size multiple for the unicast strategy.\n         */\n        @Config\n        public int unicastFlowControlRetransmitReceiverWindowMultiple()\n        {\n            return unicastFlowControlRetransmitReceiverWindowMultiple;\n        }\n\n        /**\n         * Flow control strategies may limit how much data is sent during a retransmission to\n         * avoid saturating the network and potentially causing more loss. The maximum\n         * retransmission size will be based on a multiple of the receiver window size.\n         * <p>\n         * See @{@link FlowControl#calculateRetransmissionLength(int, int, int, int)}\n         *\n         * @param unicastFlowControlRetransmitReceiverWindowMultiple window size multiple for the unicast strategy.\n         * @return this for a fluent API.\n         */\n        public Context unicastFlowControlRetransmitReceiverWindowMultiple(\n            final int unicastFlowControlRetransmitReceiverWindowMultiple)\n        {\n            this.unicastFlowControlRetransmitReceiverWindowMultiple =\n                unicastFlowControlRetransmitReceiverWindowMultiple;\n            return this;\n        }\n\n        /**\n         * Flow control strategies may limit how much data is sent during a retransmission to\n         * avoid saturating the network and potentially causing more loss. The maximum\n         * retransmission size will be based on a multiple of the receiver window size.\n         * <p>\n         * See @{@link FlowControl#calculateRetransmissionLength(int, int, int, int)}\n         *\n         * @return window size multiple for multicast strategies.\n         */\n        @Config\n        public int multicastFlowControlRetransmitReceiverWindowMultiple()\n        {\n            return multicastFlowControlRetransmitReceiverWindowMultiple;\n        }\n\n        /**\n         * Flow control strategies may limit how much data is sent during a retransmission to\n         * avoid saturating the network and potentially causing more loss. The maximum\n         * retransmission size will be based on a multiple of the receiver window size.\n         * <p>\n         * See @{@link FlowControl#calculateRetransmissionLength(int, int, int, int)}\n         *\n         * @param multicastFlowControlRetransmitReceiverWindowMultiple window size multiple for multicast strategies.\n         * @return this for a fluent API.\n         */\n        public Context multicastFlowControlRetransmitReceiverWindowMultiple(\n            final int multicastFlowControlRetransmitReceiverWindowMultiple)\n        {\n            this.multicastFlowControlRetransmitReceiverWindowMultiple =\n                multicastFlowControlRetransmitReceiverWindowMultiple;\n            return this;\n        }\n\n        /**\n         * Application specific feedback used to identify a receiver group when using a\n         * {@link TaggedMulticastFlowControl} strategy which is added to Status Messages (SMs).\n         * <p>\n         * Replaced by {@link #receiverGroupTag()}.\n         *\n         * @return Application specific feedback used to identify receiver group for flow control.\n         * @see Configuration#SM_APPLICATION_SPECIFIC_FEEDBACK_PROP_NAME\n         */\n        @Deprecated\n        @Config(id = \"SM_APPLICATION_SPECIFIC_FEEDBACK\")\n        public byte[] applicationSpecificFeedback()\n        {\n            return applicationSpecificFeedback;\n        }\n\n        /**\n         * Application specific feedback used to identify a receiver group when using a\n         * {@link TaggedMulticastFlowControl} strategy which is added to Status Messages (SMs).\n         * <p>\n         * Replaced by {@link #receiverGroupTag(Long)}.\n         *\n         * @param asfBytes for identifying a receiver group.\n         * @return this for a fluent API.\n         * @see Configuration#SM_APPLICATION_SPECIFIC_FEEDBACK_PROP_NAME\n         */\n        @Deprecated\n        public Context applicationSpecificFeedback(final byte[] asfBytes)\n        {\n            this.applicationSpecificFeedback = asfBytes;\n            return this;\n        }\n\n        /**\n         * Supplier of dynamically created {@link CongestionControl} strategies for individual connections.\n         *\n         * @return supplier of dynamically created {@link CongestionControl} strategies for individual connections.\n         * @see Configuration#CONGESTION_CONTROL_STRATEGY_SUPPLIER_PROP_NAME\n         */\n        @Config(id = \"CONGESTION_CONTROL_STRATEGY_SUPPLIER\")\n        public CongestionControlSupplier congestionControlSupplier()\n        {\n            return congestionControlSupplier;\n        }\n\n        /**\n         * Supplier of dynamically created {@link CongestionControl} strategies for individual connections.\n         *\n         * @param supplier of dynamically created {@link CongestionControl} strategies for individual connections.\n         * @return this for a fluent API.\n         * @see Configuration#CONGESTION_CONTROL_STRATEGY_SUPPLIER_PROP_NAME\n         */\n        public Context congestControlSupplier(final CongestionControlSupplier supplier)\n        {\n            this.congestionControlSupplier = supplier;\n            return this;\n        }\n\n        /**\n         * {@link ErrorHandler} to be used for reporting errors during {@link Agent}s operations.\n         *\n         * @return the {@link ErrorHandler} to be used for reporting errors during {@link Agent}s operations.\n         */\n        public ErrorHandler errorHandler()\n        {\n            return errorHandler;\n        }\n\n        /**\n         * {@link ErrorHandler} to be used for reporting errors during {@link Agent}s operations.\n         * <p>\n         * The default {@link ErrorHandler} will delegate to the {@link #errorLog()} and output to {@link System#err}\n         * if the log is full.\n         * <p>\n         * <b>Note:</b> {@link ErrorHandler} should be thread safe.\n         *\n         * @param errorHandler to be used for reporting errors during {@link Agent}s operations.\n         * @return this for a fluent API.\n         */\n        public Context errorHandler(final ErrorHandler errorHandler)\n        {\n            this.errorHandler = errorHandler;\n            return this;\n        }\n\n        /**\n         * Log to which exceptions are recorded.\n         *\n         * @return log to which exceptions are recorded.\n         */\n        public DistinctErrorLog errorLog()\n        {\n            return errorLog;\n        }\n\n        /**\n         * Log to which exceptions are recorded.\n         *\n         * @param errorLog to which exceptions are recorded.\n         * @return this for a fluent API.\n         */\n        public Context errorLog(final DistinctErrorLog errorLog)\n        {\n            this.errorLog = errorLog;\n            return this;\n        }\n\n        /**\n         * Should a {@link ConcurrentCountersManager} be used to allow for cross thread usage.\n         *\n         * @return true if a {@link ConcurrentCountersManager} should be used otherwise false.\n         */\n        public boolean useConcurrentCountersManager()\n        {\n            return useConcurrentCountersManager;\n        }\n\n        /**\n         * Should a {@link ConcurrentCountersManager} be used to allow for cross thread usage.\n         * <p>\n         * The default is to use a normal {@link CountersManager} from only the {@link DriverConductor}. If the\n         * {@link MediaDriver} is to be composed into another services that allocates counters then this should be\n         * concurrent.\n         *\n         * @param useConcurrentCountersManager true if a {@link ConcurrentCountersManager} should be used.\n         * @return this for a fluent API.\n         */\n        public Context useConcurrentCountersManager(final boolean useConcurrentCountersManager)\n        {\n            this.useConcurrentCountersManager = useConcurrentCountersManager;\n            return this;\n        }\n\n        /**\n         * Get the {@link CountersManager} that has been concluded for this context.\n         *\n         * @return the {@link CountersManager} that has been concluded for this context.\n         */\n        public CountersManager countersManager()\n        {\n            return countersManager;\n        }\n\n        /**\n         * Set the {@link CountersManager} to override the one that would have been concluded.\n         *\n         * @param countersManager to override the one that would have been concluded.\n         * @return this for a fluent API.\n         */\n        public Context countersManager(final CountersManager countersManager)\n        {\n            this.countersManager = countersManager;\n            return this;\n        }\n\n        /**\n         * The {@link SystemCounters} for the driver for recording aggregate events of system status.\n         *\n         * @return the {@link SystemCounters} for the driver for recording aggregate events of system status.\n         */\n        public SystemCounters systemCounters()\n        {\n            return systemCounters;\n        }\n\n        /**\n         * The {@link SystemCounters} for the driver for recording aggregate events of system status.\n         * <p>\n         * The default should only be overridden for testing.\n         *\n         * @param systemCounters for the driver for recording aggregate events of system status.\n         * @return this for a fluent API.\n         */\n        public Context systemCounters(final SystemCounters systemCounters)\n        {\n            this.systemCounters = systemCounters;\n            return this;\n        }\n\n        /**\n         * {@link LossReport} for identifying loss issues on specific connections.\n         *\n         * @return {@link LossReport} for identifying loss issues on specific connections.\n         */\n        LossReport lossReport()\n        {\n            return lossReport;\n        }\n\n        /**\n         * {@link LossReport} for identifying loss issues on specific connections.\n         *\n         * @param lossReport for identifying loss issues on specific connections.\n         * @return this for a fluent API.\n         */\n        Context lossReport(final LossReport lossReport)\n        {\n            this.lossReport = lossReport;\n            return this;\n        }\n\n        /**\n         * Low end of the publication reserved session id range which will not be automatically assigned.\n         *\n         * @return low end of the publication reserved session id range which will not be automatically assigned.\n         * @see #publicationReservedSessionIdHigh()\n         * @see Configuration#PUBLICATION_RESERVED_SESSION_ID_LOW_PROP_NAME\n         */\n        @Config\n        public int publicationReservedSessionIdLow()\n        {\n            return publicationReservedSessionIdLow;\n        }\n\n        /**\n         * Low end of the publication reserved session id range which will not be automatically assigned.\n         *\n         * @param sessionId for low end of the publication reserved session id range which will not be automatically\n         *                  assigned.\n         * @return this for fluent API.\n         * @see #publicationReservedSessionIdHigh(int)\n         * @see Configuration#PUBLICATION_RESERVED_SESSION_ID_LOW_PROP_NAME\n         */\n        public Context publicationReservedSessionIdLow(final int sessionId)\n        {\n            publicationReservedSessionIdLow = sessionId;\n            return this;\n        }\n\n        /**\n         * High end of the publication reserved session id range which will not be automatically assigned.\n         *\n         * @return high end of the publication reserved session id range which will not be automatically assigned.\n         * @see #publicationReservedSessionIdLow()\n         * @see Configuration#PUBLICATION_RESERVED_SESSION_ID_HIGH_PROP_NAME\n         */\n        @Config\n        public int publicationReservedSessionIdHigh()\n        {\n            return publicationReservedSessionIdHigh;\n        }\n\n        /**\n         * High end of the publication reserved session id range which will not be automatically assigned.\n         *\n         * @param sessionId for high end of the publication reserved session id range which will not be automatically\n         *                  assigned.\n         * @return this for fluent API.\n         * @see #publicationReservedSessionIdLow(int)\n         * @see Configuration#PUBLICATION_RESERVED_SESSION_ID_HIGH_PROP_NAME\n         */\n        public Context publicationReservedSessionIdHigh(final int sessionId)\n        {\n            publicationReservedSessionIdHigh = sessionId;\n            return this;\n        }\n\n        /**\n         * {@link FeedbackDelayGenerator} for controlling the delay before sending a retransmit frame.\n         *\n         * @return {@link FeedbackDelayGenerator} for controlling the delay before sending a retransmit frame.\n         * @see Configuration#RETRANSMIT_UNICAST_DELAY_PROP_NAME\n         */\n        @Config(id = \"RETRANSMIT_UNICAST_DELAY\")\n        public FeedbackDelayGenerator retransmitUnicastDelayGenerator()\n        {\n            return retransmitUnicastDelayGenerator;\n        }\n\n        /**\n         * Set the {@link FeedbackDelayGenerator} for controlling the delay before sending a retransmit frame.\n         *\n         * @param feedbackDelayGenerator for controlling the delay before sending a retransmit frame.\n         * @return this for a fluent API\n         * @see Configuration#RETRANSMIT_UNICAST_DELAY_PROP_NAME\n         */\n        public Context retransmitUnicastDelayGenerator(final FeedbackDelayGenerator feedbackDelayGenerator)\n        {\n            retransmitUnicastDelayGenerator = feedbackDelayGenerator;\n            return this;\n        }\n\n        /**\n         * {@link FeedbackDelayGenerator} for controlling the linger after a retransmit.\n         *\n         * @return {@link FeedbackDelayGenerator} for controlling the linger after a retransmit.\n         * @see Configuration#RETRANSMIT_UNICAST_LINGER_PROP_NAME\n         */\n        public FeedbackDelayGenerator retransmitUnicastLingerGenerator()\n        {\n            return retransmitUnicastLingerGenerator;\n        }\n\n        /**\n         * Set the {@link FeedbackDelayGenerator} for controlling the time to linger after a retransmit frame is sent.\n         *\n         * @param feedbackDelayGenerator for controlling the linger after a retransmit.\n         * @return this for a fluent API\n         * @see Configuration#RETRANSMIT_UNICAST_LINGER_PROP_NAME\n         */\n        public Context retransmitUnicastLingerGenerator(final FeedbackDelayGenerator feedbackDelayGenerator)\n        {\n            retransmitUnicastLingerGenerator = feedbackDelayGenerator;\n            return this;\n        }\n\n        /**\n         * {@link FeedbackDelayGenerator} for controlling the delay of sending NAK feedback on unicast.\n         *\n         * @return {@link FeedbackDelayGenerator} for controlling the delay of sending NAK feedback.\n         * @see Configuration#NAK_UNICAST_DELAY_PROP_NAME\n         */\n        public FeedbackDelayGenerator unicastFeedbackDelayGenerator()\n        {\n            return unicastFeedbackDelayGenerator;\n        }\n\n        /**\n         * Set the {@link FeedbackDelayGenerator} for controlling the delay of sending NAK feedback on unicast.\n         *\n         * @param feedbackDelayGenerator for controlling the delay of sending NAK feedback.\n         * @return this for a fluent API\n         * @see Configuration#NAK_UNICAST_DELAY_PROP_NAME\n         */\n        public Context unicastFeedbackDelayGenerator(final FeedbackDelayGenerator feedbackDelayGenerator)\n        {\n            unicastFeedbackDelayGenerator = feedbackDelayGenerator;\n            return this;\n        }\n\n        /**\n         * {@link FeedbackDelayGenerator} for controlling the delay of sending NAK feedback on multicast.\n         *\n         * @return {@link FeedbackDelayGenerator} for controlling the delay of sending NAK feedback.\n         * @see Configuration#NAK_MULTICAST_MAX_BACKOFF_PROP_NAME\n         * @see Configuration#NAK_MULTICAST_GROUP_SIZE_PROP_NAME\n         */\n        public FeedbackDelayGenerator multicastFeedbackDelayGenerator()\n        {\n            return multicastFeedbackDelayGenerator;\n        }\n\n        /**\n         * Set the {@link FeedbackDelayGenerator} for controlling the delay of sending NAK feedback on multicast.\n         *\n         * @param feedbackDelayGenerator for controlling the delay of sending NAK feedback.\n         * @return this for a fluent API\n         * @see Configuration#NAK_MULTICAST_MAX_BACKOFF_PROP_NAME\n         * @see Configuration#NAK_MULTICAST_GROUP_SIZE_PROP_NAME\n         */\n        public Context multicastFeedbackDelayGenerator(final FeedbackDelayGenerator feedbackDelayGenerator)\n        {\n            multicastFeedbackDelayGenerator = feedbackDelayGenerator;\n            return this;\n        }\n\n        /**\n         * Set the {@link Runnable} that is called when the {@link MediaDriver} processes a valid termination request.\n         *\n         * @param terminationHook that can be used to terminate a driver.\n         * @return this for a fluent API.\n         */\n        public Context terminationHook(final Runnable terminationHook)\n        {\n            this.terminationHook = terminationHook;\n            return this;\n        }\n\n        /**\n         * Get the {@link Runnable} that is called when the {@link MediaDriver} processes a valid termination request.\n         * <p>\n         * The default action is nothing.\n         *\n         * @return the {@link Runnable} that can be used to terminate a consensus module.\n         */\n        public Runnable terminationHook()\n        {\n            return terminationHook;\n        }\n\n        /**\n         * Set the {@link TerminationValidator} to be used to validate termination requests.\n         *\n         * @param validator to validate termination requests.\n         * @return this for a fluent API.\n         */\n        public Context terminationValidator(final TerminationValidator validator)\n        {\n            this.terminationValidator = validator;\n            return this;\n        }\n\n        /**\n         * Get the {@link TerminationValidator} to be used to validate termination requests.\n         *\n         * @return {@link TerminationValidator} to validate termination requests.\n         */\n        @Config\n        public TerminationValidator terminationValidator()\n        {\n            return terminationValidator;\n        }\n\n        /**\n         * Get the ratio for sending data to polling status messages in the Sender.\n         *\n         * @return ratio for sending data to polling status messages in the Sender.\n         */\n        @Config(id = \"SEND_TO_STATUS_POLL_RATIO\")\n        public int sendToStatusMessagePollRatio()\n        {\n            return sendToStatusMessagePollRatio;\n        }\n\n        /**\n         * Set the ratio for sending data to polling status messages in the Sender.\n         *\n         * @param ratio to use.\n         * @return this for fluent API.\n         */\n        public Context sendToStatusMessagePollRatio(final int ratio)\n        {\n            this.sendToStatusMessagePollRatio = ratio;\n            return this;\n        }\n\n        /**\n         * Get the group tag (gtag) to be sent in Status Messages from the Receiver.\n         *\n         * @return group tag value or null if not set.\n         * @see Configuration#RECEIVER_GROUP_TAG_PROP_NAME\n         */\n        @Config\n        public Long receiverGroupTag()\n        {\n            return receiverGroupTag;\n        }\n\n        /**\n         * Set the group tag (gtag) to be sent in Status Messages from the Receiver.\n         *\n         * @param groupTag value to sent in Status Messages from the receiver or null if not set.\n         * @return this for fluent API.\n         * @see Configuration#RECEIVER_GROUP_TAG_PROP_NAME\n         */\n        public Context receiverGroupTag(final Long groupTag)\n        {\n            this.receiverGroupTag = groupTag;\n            return this;\n        }\n\n        /**\n         * Get the default group tag (gtag) to be used by the tagged flow control strategy.\n         *\n         * @return group tag value or null if not set.\n         * @see Configuration#FLOW_CONTROL_GROUP_TAG_PROP_NAME\n         */\n        @Config\n        public long flowControlGroupTag()\n        {\n            return flowControlGroupTag;\n        }\n\n        /**\n         * Set the default group tag (gtag) to be used by the tagged flow control strategy.\n         *\n         * @param groupTag value to use by default by the tagged flow control strategy.\n         * @return this for fluent API.\n         * @see Configuration#FLOW_CONTROL_GROUP_TAG_PROP_NAME\n         */\n        public Context flowControlGroupTag(final long groupTag)\n        {\n            this.flowControlGroupTag = groupTag;\n            return this;\n        }\n\n        /**\n         * Get the default min group size used by flow control strategies to indicate connectivity.\n         *\n         * @return required group size.\n         * @see Configuration#FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME\n         */\n        @Config\n        public int flowControlGroupMinSize()\n        {\n            return flowControlGroupMinSize;\n        }\n\n        /**\n         * Set the default min group size used by flow control strategies to indicate connectivity.\n         *\n         * @param groupSize minimum required group size used by the tagged flow control strategy.\n         * @return this for fluent API.\n         * @see Configuration#FLOW_CONTROL_GROUP_MIN_SIZE_PROP_NAME\n         */\n        public Context flowControlGroupMinSize(final int groupSize)\n        {\n            this.flowControlGroupMinSize = groupSize;\n            return this;\n        }\n\n        /**\n         * Get the {@link NameResolver} to use for resolving endpoints and control names.\n         *\n         * @return {@link NameResolver} to use for resolving endpoints and control names.\n         */\n        public NameResolver nameResolver()\n        {\n            return nameResolver;\n        }\n\n        /**\n         * Set the {@link NameResolver} to use for resolving endpoints and control names.\n         *\n         * @param nameResolver to use for resolving endpoints and control names.\n         * @return this for fluent API.\n         */\n        public Context nameResolver(final NameResolver nameResolver)\n        {\n            this.nameResolver = nameResolver;\n            return this;\n        }\n\n        /**\n         * Get the name of the {@link MediaDriver} for name resolver purposes.\n         *\n         * @return name of the {@link MediaDriver}.\n         * @see Configuration#RESOLVER_NAME_PROP_NAME\n         */\n        @Config\n        public String resolverName()\n        {\n            return resolverName;\n        }\n\n        /**\n         * Set the name of the {@link MediaDriver} for name resolver purposes.\n         *\n         * @param resolverName for the driver.\n         * @return this for a fluent API.\n         * @see Configuration#RESOLVER_NAME_PROP_NAME\n         */\n        public Context resolverName(final String resolverName)\n        {\n            this.resolverName = resolverName;\n            return this;\n        }\n\n        /**\n         * Get the interface of the {@link MediaDriver} for name resolver purposes.\n         * <p>\n         * The format is {@code hostname:port}. If set to null, then the name resolver will not be used.\n         *\n         * @return interface of the {@link MediaDriver}.\n         * @see Configuration#RESOLVER_INTERFACE_PROP_NAME\n         */\n        @Config\n        public String resolverInterface()\n        {\n            return resolverInterface;\n        }\n\n        /**\n         * Set the interface of the {@link MediaDriver} for name resolver purposes.\n         * <p>\n         * The format is {@code hostname:port}. If set to null, then the name resolver will not be used.\n         *\n         * @param resolverInterface to use for the name resolver.\n         * @return this for fluent API.\n         * @see Configuration#RESOLVER_INTERFACE_PROP_NAME\n         */\n        public Context resolverInterface(final String resolverInterface)\n        {\n            this.resolverInterface = resolverInterface;\n            return this;\n        }\n\n        /**\n         * Get the bootstrap neighbor of the {@link MediaDriver} for name resolver purposes.\n         * <p>\n         * The format is comma separated list of {@code hostname:port} pairs and follows the URI format for the\n         * endpoint parameter.\n         *\n         * @return bootstrap neighbor of the {@link MediaDriver}.\n         * @see Configuration#RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME\n         * @see CommonContext#ENDPOINT_PARAM_NAME\n         */\n        @Config\n        public String resolverBootstrapNeighbor()\n        {\n            return resolverBootstrapNeighbor;\n        }\n\n        /**\n         * Set the bootstrap neighbor of the {@link MediaDriver} for name resolver purposes.\n         * <p>\n         * The format is comma separated list of {@code hostname:port} pairs and follows the URI format for the\n         * endpoint parameter.\n         *\n         * @param resolverBootstrapNeighbor to use for the name resolver.\n         * @return this for fluent API.\n         * @see Configuration#RESOLVER_BOOTSTRAP_NEIGHBOR_PROP_NAME\n         * @see CommonContext#ENDPOINT_PARAM_NAME\n         */\n        public Context resolverBootstrapNeighbor(final String resolverBootstrapNeighbor)\n        {\n            this.resolverBootstrapNeighbor = resolverBootstrapNeighbor;\n            return this;\n        }\n\n        /**\n         * Get the interval for checking if a re-resolution for endpoints and controls should be done.\n         * <p>\n         * A value of 0 turns off checks and re-resolutions.\n         *\n         * @return timeout in ns.\n         * @see Configuration#RE_RESOLUTION_CHECK_INTERVAL_PROP_NAME\n         * @see Configuration#RE_RESOLUTION_CHECK_INTERVAL_DEFAULT_NS\n         */\n        @Config\n        public long reResolutionCheckIntervalNs()\n        {\n            return reResolutionCheckIntervalNs;\n        }\n\n        /**\n         * Set the interval for checking if a re-resolution for endpoints and controls should be done.\n         * <p>\n         * A value of 0 turns off checks and re-resolutions.\n         *\n         * @param reResolutionCheckIntervalNs to use for check\n         * @return this for fluent API.\n         * @see Configuration#RE_RESOLUTION_CHECK_INTERVAL_PROP_NAME\n         * @see Configuration#RE_RESOLUTION_CHECK_INTERVAL_DEFAULT_NS\n         */\n        public Context reResolutionCheckIntervalNs(final long reResolutionCheckIntervalNs)\n        {\n            this.reResolutionCheckIntervalNs = reResolutionCheckIntervalNs;\n            return this;\n        }\n\n        /**\n         * Set a threshold for the conductor work cycle time which when exceed it will increment the\n         * {@link io.aeron.driver.status.SystemCounterDescriptor#CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED} counter.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see Configuration#CONDUCTOR_CYCLE_THRESHOLD_PROP_NAME\n         * @see Configuration#CONDUCTOR_CYCLE_THRESHOLD_DEFAULT_NS\n         */\n        public Context conductorCycleThresholdNs(final long thresholdNs)\n        {\n            this.conductorCycleThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for the conductor work cycle time which when exceed it will increment the\n         * {@link io.aeron.driver.status.SystemCounterDescriptor#CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED} counter.\n         *\n         * @return threshold to track for the conductor work cycle time.\n         */\n        @Config\n        public long conductorCycleThresholdNs()\n        {\n            return conductorCycleThresholdNs;\n        }\n\n        /**\n         * Set the clock to be used to record channel receive timestamps.\n         *\n         * @param clock to provide ns-resolution timestamps since the epoch.\n         * @return this for a fluent API.\n         */\n        public Context channelReceiveTimestampClock(final EpochNanoClock clock)\n        {\n            channelReceiveTimestampClock = clock;\n            return this;\n        }\n\n        /**\n         * Set a threshold for the sender work cycle time which when exceed it will increment the\n         * {@link io.aeron.driver.status.SystemCounterDescriptor#SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED} counter.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see Configuration#SENDER_CYCLE_THRESHOLD_PROP_NAME\n         * @see Configuration#SENDER_CYCLE_THRESHOLD_DEFAULT_NS\n         */\n        public Context senderCycleThresholdNs(final long thresholdNs)\n        {\n            this.senderCycleThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for the sender work cycle time which when exceed it will increment the\n         * {@link io.aeron.driver.status.SystemCounterDescriptor#SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED} counter.\n         *\n         * @return threshold to track for the sender work cycle time.\n         */\n        @Config\n        public long senderCycleThresholdNs()\n        {\n            return senderCycleThresholdNs;\n        }\n\n        /**\n         * Set a threshold for the receiver work cycle time which when exceed it will increment the\n         * {@link io.aeron.driver.status.SystemCounterDescriptor#RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED} counter.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see Configuration#RECEIVER_CYCLE_THRESHOLD_PROP_NAME\n         * @see Configuration#RECEIVER_CYCLE_THRESHOLD_DEFAULT_NS\n         */\n        public Context receiverCycleThresholdNs(final long thresholdNs)\n        {\n            this.receiverCycleThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for the receiver work cycle time which when exceed it will increment the\n         * {@link io.aeron.driver.status.SystemCounterDescriptor#RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED} counter.\n         *\n         * @return threshold to track for the receiver work cycle time.\n         */\n        @Config\n        public long receiverCycleThresholdNs()\n        {\n            return receiverCycleThresholdNs;\n        }\n\n        /**\n         * Set a threshold for the {@link NameResolver} which when exceed it will increment the\n         * {@link io.aeron.driver.status.SystemCounterDescriptor#NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED} counter.\n         *\n         * @param thresholdNs value in nanoseconds\n         * @return this for fluent API.\n         * @see Configuration#NAME_RESOLVER_THRESHOLD_PROP_NAME\n         * @see Configuration#NAME_RESOLVER_THRESHOLD_DEFAULT_NS\n         */\n        public Context nameResolverThresholdNs(final long thresholdNs)\n        {\n            this.nameResolverThresholdNs = thresholdNs;\n            return this;\n        }\n\n        /**\n         * Threshold for the {@link NameResolver} which when exceed it will increment the\n         * {@link io.aeron.driver.status.SystemCounterDescriptor#NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED} counter.\n         *\n         * @return threshold to track for the name resolution.\n         */\n        @Config\n        public long nameResolverThresholdNs()\n        {\n            return nameResolverThresholdNs;\n        }\n\n        /**\n         * Maximum number of {@link DriverManagedResource}s to free within a single duty cycle of the conductor.\n         *\n         * @param resourceFreeLimit number of resources to limit to.\n         * @return this for a fluent API.\n         * @see Configuration#resourceFreeLimit()\n         * @see Configuration#RESOURCE_FREE_LIMIT_PROP_NAME\n         * @see Configuration#RESOURCE_FREE_LIMIT_DEFAULT\n         * @since 1.41.0\n         */\n        public Context resourceFreeLimit(final int resourceFreeLimit)\n        {\n            this.resourceFreeLimit = resourceFreeLimit;\n            return this;\n        }\n\n        /**\n         * Maximum number of {@link DriverManagedResource}s to free within a single duty cycle of the conductor.\n         *\n         * @return limit on the number of resources that can be freed.\n         * @since 1.41.0\n         */\n        @Config\n        public int resourceFreeLimit()\n        {\n            return resourceFreeLimit;\n        }\n\n        /**\n         * Clock used record channel receive timestamps.\n         *\n         * @return a clock instance.\n         */\n        public EpochNanoClock channelReceiveTimestampClock()\n        {\n            return channelReceiveTimestampClock;\n        }\n\n        /**\n         * Set the clock to be used to record channel send timestamps.\n         *\n         * @param clock to provide ns-resolution timestamps since the epoch.\n         * @return this for a fluent API.\n         */\n        public Context channelSendTimestampClock(final EpochNanoClock clock)\n        {\n            channelSendTimestampClock = clock;\n            return this;\n        }\n\n        /**\n         * Duty cycle tracker used for the conductor.\n         *\n         * @return conductor duty cycle tracker.\n         */\n        public DutyCycleTracker conductorDutyCycleTracker()\n        {\n            return conductorDutyCycleTracker;\n        }\n\n        /**\n         * Set the duty cycle tracker used for the conductor.\n         *\n         * @param dutyCycleTracker for the conductor.\n         * @return this for a fluent API.\n         */\n        public Context conductorDutyCycleTracker(final DutyCycleTracker dutyCycleTracker)\n        {\n            this.conductorDutyCycleTracker = dutyCycleTracker;\n            return this;\n        }\n\n        /**\n         * Duty cycle tracker used for the sender.\n         *\n         * @return sender duty cycle tracker.\n         */\n        public DutyCycleTracker senderDutyCycleTracker()\n        {\n            return senderDutyCycleTracker;\n        }\n\n        /**\n         * Set the duty cycle tracker used for the sender.\n         *\n         * @param dutyCycleTracker for the sender.\n         * @return this for a fluent API.\n         */\n        public Context senderDutyCycleTracker(final DutyCycleTracker dutyCycleTracker)\n        {\n            this.senderDutyCycleTracker = dutyCycleTracker;\n            return this;\n        }\n\n        /**\n         * Duty cycle tracker used for the receiver.\n         *\n         * @return receiver duty cycle tracker.\n         */\n        public DutyCycleTracker receiverDutyCycleTracker()\n        {\n            return receiverDutyCycleTracker;\n        }\n\n        /**\n         * Set the duty cycle tracker used for the receiver.\n         *\n         * @param dutyCycleTracker for the receiver.\n         * @return this for a fluent API.\n         */\n        public Context receiverDutyCycleTracker(final DutyCycleTracker dutyCycleTracker)\n        {\n            this.receiverDutyCycleTracker = dutyCycleTracker;\n            return this;\n        }\n\n        /**\n         * Duty cycle tracker used for the {@link NameResolver}.\n         *\n         * @return {@link NameResolver} duty cycle tracker.\n         */\n        public DutyCycleTracker nameResolverTimeTracker()\n        {\n            return nameResolverTimeTracker;\n        }\n\n        /**\n         * Set the duty cycle tracker used for the {@link NameResolver}.\n         *\n         * @param dutyCycleTracker for the {@link NameResolver}.\n         * @return this for a fluent API.\n         */\n        public Context nameResolverTimeTracker(final DutyCycleTracker dutyCycleTracker)\n        {\n            this.nameResolverTimeTracker = dutyCycleTracker;\n            return this;\n        }\n\n        /**\n         * Wildcard port range used for the Sender.\n         *\n         * @return port range as a string in the format \"low high\".\n         */\n        @Config\n        public String senderWildcardPortRange()\n        {\n            return senderWildcardPortRange;\n        }\n\n\n        /**\n         * Set the wildcard port range to be used for the Sender.\n         * <p>\n         * Format is a string in \"low high\". With low being the low port value and high being the high port value. For\n         * example, \"100 200\". A null value or a value of \"0 0\" will use OS wildcard functionality.\n         *\n         * @param portRange as a string in the format \"low high\".\n         * @return this for fluent API.\n         * @see Configuration#SENDER_WILDCARD_PORT_RANGE_PROP_NAME\n         */\n        public Context senderWildcardPortRange(final String portRange)\n        {\n            this.senderWildcardPortRange = portRange;\n            return this;\n        }\n\n        /**\n         * Wildcard port range used for the Receiver.\n         *\n         * @return port range as a string in the format \"low high\".\n         */\n        @Config\n        public String receiverWildcardPortRange()\n        {\n            return receiverWildcardPortRange;\n        }\n\n        /**\n         * Set the wildcard port range to be used for the Receiver.\n         * <p>\n         * Format is a string in \"low high\". With low being the low port value and high being the high port value. For\n         * example, \"100 200\". A null value or a value of \"0 0\" will use OS wildcard functionality.\n         *\n         * @param portRange as a string in the format \"low high\".\n         * @return this for fluent API.\n         * @see Configuration#RECEIVER_WILDCARD_PORT_RANGE_PROP_NAME\n         */\n        public Context receiverWildcardPortRange(final String portRange)\n        {\n            this.receiverWildcardPortRange = portRange;\n            return this;\n        }\n\n        /**\n         * Port manager used for the {@link Sender}.\n         *\n         * @return Sender {@link PortManager}.\n         */\n        public PortManager senderPortManager()\n        {\n            return senderPortManager;\n        }\n\n        /**\n         * Set the {@link PortManager} used for the {@link Sender}.\n         *\n         * @param portManager for the {@link Sender}\n         * @return this for a fluent API.\n         */\n        public Context senderPortManager(final PortManager portManager)\n        {\n            this.senderPortManager = portManager;\n            return this;\n        }\n\n        /**\n         * Port manager used for the {@link Receiver}.\n         *\n         * @return Receiver {@link PortManager}.\n         */\n        public PortManager receiverPortManager()\n        {\n            return receiverPortManager;\n        }\n\n        /**\n         * Set the {@link PortManager} used for the {@link Receiver}.\n         *\n         * @param portManager for the {@link Receiver}\n         * @return this for a fluent API.\n         */\n        public Context receiverPortManager(final PortManager portManager)\n        {\n            this.receiverPortManager = portManager;\n            return this;\n        }\n\n        /**\n         * Set the limit on the number of sessions allow per stream for subscriptions.\n         *\n         * @param sessionLimit the limit of sessions per stream\n         * @return this for a fluent API.\n         * @see Configuration#STREAM_SESSION_LIMIT_PROP_NAME\n         * @see Configuration#STREAM_SESSION_LIMIT_DEFAULT\n         * @see Configuration#streamSessionLimit()\n         */\n        public Context streamSessionLimit(final int sessionLimit)\n        {\n            this.streamSessionLimit = sessionLimit;\n            return this;\n        }\n\n        /**\n         * Get the limit on the number of sessions allow per stream for subscriptions.\n         *\n         * @return the limit of sessions per stream\n         * @see Context#streamSessionLimit(int)\n         */\n        public int streamSessionLimit()\n        {\n            return this.streamSessionLimit;\n        }\n\n        /**\n         * Clock used record channel send timestamps.\n         *\n         * @return a clock instance.\n         */\n        public EpochNanoClock channelSendTimestampClock()\n        {\n            return channelSendTimestampClock;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public Context enableExperimentalFeatures(final boolean enableExperimentalFeatures)\n        {\n            super.enableExperimentalFeatures(enableExperimentalFeatures);\n            return this;\n        }\n\n        /**\n         * Counted error handler that wraps {@link #errorHandler()} and\n         * {@link io.aeron.driver.status.SystemCounterDescriptor#ERRORS} counter.\n         *\n         * @return counted error handler.\n         */\n        public CountedErrorHandler countedErrorHandler()\n        {\n            return countedErrorHandler;\n        }\n\n        OneToOneConcurrentArrayQueue<Runnable> receiverCommandQueue()\n        {\n            return receiverCommandQueue;\n        }\n\n        Context receiverCommandQueue(final OneToOneConcurrentArrayQueue<Runnable> receiverCommandQueue)\n        {\n            this.receiverCommandQueue = receiverCommandQueue;\n            return this;\n        }\n\n        OneToOneConcurrentArrayQueue<Runnable> senderCommandQueue()\n        {\n            return senderCommandQueue;\n        }\n\n        Context senderCommandQueue(final OneToOneConcurrentArrayQueue<Runnable> senderCommandQueue)\n        {\n            this.senderCommandQueue = senderCommandQueue;\n            return this;\n        }\n\n        ManyToOneConcurrentLinkedQueue<Runnable> driverCommandQueue()\n        {\n            return driverCommandQueue;\n        }\n\n        Context driverCommandQueue(final ManyToOneConcurrentLinkedQueue<Runnable> queue)\n        {\n            this.driverCommandQueue = queue;\n            return this;\n        }\n\n        ClientProxy clientProxy()\n        {\n            return clientProxy;\n        }\n\n        Context clientProxy(final ClientProxy clientProxy)\n        {\n            this.clientProxy = clientProxy;\n            return this;\n        }\n\n        RingBuffer toDriverCommands()\n        {\n            return toDriverCommands;\n        }\n\n        Context toDriverCommands(final RingBuffer toDriverCommands)\n        {\n            this.toDriverCommands = toDriverCommands;\n            return this;\n        }\n\n        Context cncByteBuffer(final MappedByteBuffer cncByteBuffer)\n        {\n            this.cncByteBuffer = cncByteBuffer;\n            return this;\n        }\n\n        MappedByteBuffer cncByteBuffer()\n        {\n            return cncByteBuffer;\n        }\n\n        LogFactory logFactory()\n        {\n            return logFactory;\n        }\n\n        Context logFactory(final LogFactory logFactory)\n        {\n            this.logFactory = logFactory;\n            return this;\n        }\n\n        DataTransportPoller dataTransportPoller()\n        {\n            return dataTransportPoller;\n        }\n\n        Context dataTransportPoller(final DataTransportPoller transportPoller)\n        {\n            this.dataTransportPoller = transportPoller;\n            return this;\n        }\n\n        ControlTransportPoller controlTransportPoller()\n        {\n            return controlTransportPoller;\n        }\n\n        Context controlTransportPoller(final ControlTransportPoller transportPoller)\n        {\n            this.controlTransportPoller = transportPoller;\n            return this;\n        }\n\n        ReceiverProxy receiverProxy()\n        {\n            return receiverProxy;\n        }\n\n        Context receiverProxy(final ReceiverProxy receiverProxy)\n        {\n            this.receiverProxy = receiverProxy;\n            return this;\n        }\n\n        SenderProxy senderProxy()\n        {\n            return senderProxy;\n        }\n\n        Context senderProxy(final SenderProxy senderProxy)\n        {\n            this.senderProxy = senderProxy;\n            return this;\n        }\n\n        DriverConductorProxy driverConductorProxy()\n        {\n            return driverConductorProxy;\n        }\n\n        Context driverConductorProxy(final DriverConductorProxy driverConductorProxy)\n        {\n            this.driverConductorProxy = driverConductorProxy;\n            return this;\n        }\n\n        Context countedErrorHandler(final CountedErrorHandler countedErrorHandler)\n        {\n            this.countedErrorHandler = countedErrorHandler;\n            return this;\n        }\n\n        int osDefaultSocketRcvbufLength()\n        {\n            resolveOsSocketBufLengths();\n            return osDefaultSocketRcvbufLength;\n        }\n\n        int osMaxSocketRcvbufLength()\n        {\n            resolveOsSocketBufLengths();\n            return osMaxSocketRcvbufLength;\n        }\n\n        int osDefaultSocketSndbufLength()\n        {\n            resolveOsSocketBufLengths();\n            return osDefaultSocketSndbufLength;\n        }\n\n        int osMaxSocketSndbufLength()\n        {\n            resolveOsSocketBufLengths();\n            return osMaxSocketSndbufLength;\n        }\n\n        void resolveOsSocketBufLengths()\n        {\n            if (Aeron.NULL_VALUE != osMaxSocketRcvbufLength)\n            {\n                return;\n            }\n\n            try (DatagramChannel probe = DatagramChannel.open())\n            {\n                osDefaultSocketSndbufLength = probe.getOption(StandardSocketOptions.SO_SNDBUF);\n\n                probe.setOption(StandardSocketOptions.SO_SNDBUF, Integer.MAX_VALUE);\n                osMaxSocketSndbufLength = probe.getOption(StandardSocketOptions.SO_SNDBUF);\n\n                osDefaultSocketRcvbufLength = probe.getOption(StandardSocketOptions.SO_RCVBUF);\n\n                probe.setOption(StandardSocketOptions.SO_RCVBUF, Integer.MAX_VALUE);\n                osMaxSocketRcvbufLength = probe.getOption(StandardSocketOptions.SO_RCVBUF);\n            }\n            catch (final IOException ex)\n            {\n                throw new AeronException(\"probe socket: \" + ex, ex);\n            }\n        }\n\n        @SuppressWarnings({ \"MethodLength\", \"deprecation\" })\n        void concludeNullProperties()\n        {\n            if (null == tempBuffer)\n            {\n                tempBuffer = new UnsafeBuffer(new byte[METADATA_LENGTH]);\n            }\n\n            if (null == epochClock)\n            {\n                epochClock = SystemEpochClock.INSTANCE;\n            }\n\n            if (null == nanoClock)\n            {\n                nanoClock = SystemNanoClock.INSTANCE;\n            }\n\n            if (null == cachedEpochClock)\n            {\n                cachedEpochClock = new CachedEpochClock();\n            }\n\n            if (null == cachedNanoClock)\n            {\n                cachedNanoClock = new CachedNanoClock();\n            }\n\n            if (null == senderCachedNanoClock)\n            {\n                senderCachedNanoClock = new CachedNanoClock();\n            }\n\n            if (null == receiverCachedNanoClock)\n            {\n                receiverCachedNanoClock = new CachedNanoClock();\n            }\n\n            if (null == unicastFlowControlSupplier)\n            {\n                unicastFlowControlSupplier = Configuration.unicastFlowControlSupplier();\n            }\n\n            if (null == multicastFlowControlSupplier)\n            {\n                multicastFlowControlSupplier = Configuration.multicastFlowControlSupplier();\n            }\n\n            if (null == sendChannelEndpointSupplier)\n            {\n                sendChannelEndpointSupplier = Configuration.sendChannelEndpointSupplier();\n            }\n\n            if (null == receiveChannelEndpointSupplier)\n            {\n                receiveChannelEndpointSupplier = Configuration.receiveChannelEndpointSupplier();\n            }\n\n            if (null == applicationSpecificFeedback)\n            {\n                applicationSpecificFeedback = Configuration.applicationSpecificFeedback();\n            }\n\n            if (null == receiverGroupTag)\n            {\n                if (applicationSpecificFeedback.length > 0)\n                {\n                    if (applicationSpecificFeedback.length != SIZE_OF_LONG)\n                    {\n                        throw new IllegalArgumentException(\n                            \"applicationSpecificFeedback length must be equal to \" + SIZE_OF_LONG +\n                                \" bytes: length=\" + applicationSpecificFeedback.length);\n                    }\n\n                    final UnsafeBuffer buffer = new UnsafeBuffer(applicationSpecificFeedback);\n                    receiverGroupTag = buffer.getLong(0, ByteOrder.LITTLE_ENDIAN);\n                }\n            }\n\n            if (null == receiveChannelEndpointThreadLocals)\n            {\n                receiveChannelEndpointThreadLocals = new ReceiveChannelEndpointThreadLocals();\n            }\n\n            if (null == congestionControlSupplier)\n            {\n                congestionControlSupplier = Configuration.congestionControlSupplier();\n            }\n\n            if (null == threadingMode)\n            {\n                threadingMode = Configuration.threadingMode();\n            }\n\n            if (null == driverCommandQueue)\n            {\n                driverCommandQueue = new ManyToOneConcurrentLinkedQueue<>();\n            }\n\n            if (null == receiverCommandQueue)\n            {\n                receiverCommandQueue = new OneToOneConcurrentArrayQueue<>(CMD_QUEUE_CAPACITY);\n            }\n\n            if (null == senderCommandQueue)\n            {\n                senderCommandQueue = new OneToOneConcurrentArrayQueue<>(CMD_QUEUE_CAPACITY);\n            }\n\n            if (null == retransmitUnicastDelayGenerator)\n            {\n                retransmitUnicastDelayGenerator = new StaticDelayGenerator(retransmitUnicastDelayNs);\n            }\n\n            if (null == retransmitUnicastLingerGenerator)\n            {\n                retransmitUnicastLingerGenerator = new StaticDelayGenerator(retransmitUnicastLingerNs);\n            }\n\n            if (null == unicastFeedbackDelayGenerator)\n            {\n                unicastFeedbackDelayGenerator = new StaticDelayGenerator(\n                    nakUnicastDelayNs, nakUnicastDelayNs * nakUnicastRetryDelayRatio);\n            }\n\n            if (null == multicastFeedbackDelayGenerator)\n            {\n                multicastFeedbackDelayGenerator = new OptimalMulticastDelayGenerator(\n                    nakMulticastMaxBackoffNs, nakMulticastGroupSize);\n            }\n\n            if (null == terminationValidator)\n            {\n                terminationValidator = Configuration.terminationValidator();\n            }\n\n            if (null == nameResolver)\n            {\n                nameResolver = DefaultNameResolver.INSTANCE;\n            }\n\n            if (null == channelReceiveTimestampClock)\n            {\n                channelReceiveTimestampClock = new SystemEpochNanoClock();\n            }\n\n            if (null == channelSendTimestampClock)\n            {\n                channelSendTimestampClock = new SystemEpochNanoClock();\n            }\n\n            if (null == asyncTaskExecutor)\n            {\n                if (asyncTaskExecutorThreads <= 0)\n                {\n                    asyncTaskExecutor = CALLER_RUNS_TASK_EXECUTOR;\n                }\n                else\n                {\n                    asyncTaskExecutor = newDefaultAsyncTaskExecutor(asyncTaskExecutorThreads, aeronDirectoryName());\n                }\n            }\n\n            if (null != resolverInterface && Strings.isEmpty(resolverName))\n            {\n                throw new ConfigurationException(\"`resolverName` is required when `resolverInterface` is set\");\n            }\n        }\n\n        private static ThreadPoolExecutor newDefaultAsyncTaskExecutor(final int threadCount, final String dirName)\n        {\n            final AtomicInteger id = new AtomicInteger();\n            final ThreadPoolExecutor executor = new ThreadPoolExecutor(\n                threadCount,\n                threadCount,\n                0L,\n                TimeUnit.MILLISECONDS,\n                new LinkedBlockingQueue<>(),\n                (r) ->\n                {\n                    String threadName = \"async-executor\";\n                    if (threadCount > 1)\n                    {\n                        threadName += \"-\" + id.getAndIncrement();\n                    }\n                    threadName += \" \" + dirName;\n                    final Thread thread = new Thread(r, threadName);\n                    thread.setDaemon(true);\n                    return thread;\n                });\n            executor.prestartAllCoreThreads();\n            return executor;\n        }\n\n        private void concludeCounters()\n        {\n            if (null == countersManager)\n            {\n                if (countersMetaDataBuffer() == null)\n                {\n                    countersMetaDataBuffer(createCountersMetaDataBuffer(cncByteBuffer, cncMetaDataBuffer));\n                }\n\n                if (countersValuesBuffer() == null)\n                {\n                    countersValuesBuffer(createCountersValuesBuffer(cncByteBuffer, cncMetaDataBuffer));\n                }\n\n                final long reuseTimeoutMs = counterFreeToReuseTimeoutNs > 0 ?\n                    Math.max(TimeUnit.NANOSECONDS.toMillis(counterFreeToReuseTimeoutNs), 1) : 0;\n\n                countersManager = useConcurrentCountersManager ?\n                    new ConcurrentCountersManager(\n                        countersMetaDataBuffer(), countersValuesBuffer(), US_ASCII, cachedEpochClock, reuseTimeoutMs) :\n                    new CountersManager(\n                        countersMetaDataBuffer(), countersValuesBuffer(), US_ASCII, cachedEpochClock, reuseTimeoutMs);\n            }\n\n            if (null == systemCounters)\n            {\n                systemCounters = new SystemCounters(countersManager);\n            }\n\n            final int aeronVersion = SemanticVersion.compose(\n                MediaDriverVersion.MAJOR_VERSION, MediaDriverVersion.MINOR_VERSION, MediaDriverVersion.PATCH_VERSION);\n            systemCounters.get(AERON_VERSION).set(aeronVersion);\n            systemCounters.get(CONTROL_PROTOCOL_VERSION).set(ControlProtocolEvents.CONTROL_PROTOCOL_SEMANTIC_VERSION);\n\n            systemCounters.get(CONDUCTOR_MAX_CYCLE_TIME).appendToLabel(\": \" + threadingMode.name());\n            systemCounters.get(CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED).appendToLabel(\n                \": threshold=\" + SystemUtil.formatDuration(conductorCycleThresholdNs) +\n                    \" \" + threadingMode.name());\n\n            systemCounters.get(SENDER_MAX_CYCLE_TIME).appendToLabel(\": \" + threadingMode.name());\n            systemCounters.get(SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED).appendToLabel(\n                \": threshold=\" + SystemUtil.formatDuration(senderCycleThresholdNs) +\n                    \" \" + threadingMode.name());\n\n            systemCounters.get(RECEIVER_MAX_CYCLE_TIME).appendToLabel(\": \" + threadingMode.name());\n            systemCounters.get(RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED).appendToLabel(\n                \": threshold=\" + SystemUtil.formatDuration(receiverCycleThresholdNs) +\n                    \" \" + threadingMode.name());\n\n            systemCounters.get(NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED).appendToLabel(\n                \": threshold=\" + SystemUtil.formatDuration(nameResolverThresholdNs));\n        }\n\n        @SuppressWarnings(\"MethodLength\")\n        private void concludeDependantProperties()\n        {\n            clientProxy = new ClientProxy(new BroadcastTransmitter(\n                createToClientsBuffer(cncByteBuffer, cncMetaDataBuffer)));\n\n            toDriverCommands = new ManyToOneRingBuffer(createToDriverBuffer(cncByteBuffer, cncMetaDataBuffer));\n\n            if (null == errorLog)\n            {\n                errorLog = new DistinctErrorLog(\n                    createErrorLogBuffer(cncByteBuffer, cncMetaDataBuffer), epochClock, US_ASCII);\n            }\n\n            errorHandler = CommonContext.setupErrorHandler(errorHandler, errorLog);\n\n            if (null == countedErrorHandler)\n            {\n                countedErrorHandler = new CountedErrorHandler(errorHandler, systemCounters.get(ERRORS));\n            }\n\n            receiverProxy = new ReceiverProxy(\n                threadingMode, receiverCommandQueue, systemCounters.get(RECEIVER_PROXY_FAILS));\n            senderProxy = new SenderProxy(\n                threadingMode, senderCommandQueue, systemCounters.get(SENDER_PROXY_FAILS));\n            driverConductorProxy = new DriverConductorProxy(\n                threadingMode, driverCommandQueue, systemCounters.get(CONDUCTOR_PROXY_FAILS));\n\n            if (null == controlTransportPoller)\n            {\n                controlTransportPoller = new ControlTransportPoller(countedErrorHandler, driverConductorProxy);\n            }\n\n            if (null == dataTransportPoller)\n            {\n                dataTransportPoller = new DataTransportPoller(countedErrorHandler);\n            }\n\n            if (null == logFactory)\n            {\n                logFactory = new FileStoreLogFactory(\n                    aeronDirectoryName(),\n                    filePageSize,\n                    performStorageChecks,\n                    lowStorageWarningThreshold,\n                    errorHandler,\n                    systemCounters.get(BYTES_CURRENTLY_MAPPED));\n            }\n\n            if (null == lossReport)\n            {\n                final long pageAlignedLossReportBufferLength = BitUtil.align(lossReportBufferLength, filePageSize);\n                validateValueRange(\n                    pageAlignedLossReportBufferLength,\n                    LOSS_REPORT_BUFFER_LENGTH_DEFAULT,\n                    Integer.MAX_VALUE,\n                    \"lossReportBufferLength\");\n                lossReportBuffer = mapLossReport(\n                    aeronDirectoryName(),\n                    (int)pageAlignedLossReportBufferLength);\n                lossReport = new LossReport(new UnsafeBuffer(lossReportBuffer));\n            }\n\n            if (null == conductorDutyCycleTracker)\n            {\n                conductorDutyCycleTracker = new DutyCycleStallTracker(\n                    systemCounters.get(CONDUCTOR_MAX_CYCLE_TIME),\n                    systemCounters.get(CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED),\n                    conductorCycleThresholdNs);\n            }\n\n            if (null == senderDutyCycleTracker)\n            {\n                senderDutyCycleTracker = new DutyCycleStallTracker(\n                    systemCounters.get(SENDER_MAX_CYCLE_TIME),\n                    systemCounters.get(SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED),\n                    senderCycleThresholdNs);\n            }\n\n            if (null == receiverDutyCycleTracker)\n            {\n                receiverDutyCycleTracker = new DutyCycleStallTracker(\n                    systemCounters.get(RECEIVER_MAX_CYCLE_TIME),\n                    systemCounters.get(RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED),\n                    receiverCycleThresholdNs);\n            }\n\n            if (null == nameResolverTimeTracker)\n            {\n                nameResolverTimeTracker = new DutyCycleStallTracker(\n                    systemCounters.get(NAME_RESOLVER_MAX_TIME),\n                    systemCounters.get(NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED),\n                    nameResolverThresholdNs);\n            }\n\n            if (null == senderPortManager)\n            {\n                senderPortManager = new WildcardPortManager(\n                    WildcardPortManager.parsePortRange(senderWildcardPortRange), true);\n            }\n\n            if (null == receiverPortManager)\n            {\n                receiverPortManager = new WildcardPortManager(\n                    WildcardPortManager.parsePortRange(receiverWildcardPortRange), false);\n            }\n\n            nameResolver.init(countersManager, countersManager::newCounter);\n        }\n\n        private void concludeIdleStrategies()\n        {\n            final StatusIndicator indicator = new UnsafeBufferStatusIndicator(\n                countersManager.valuesBuffer(), CONTROLLABLE_IDLE_STRATEGY.id());\n\n            switch (threadingMode)\n            {\n                case INVOKER:\n                    break;\n\n                case SHARED:\n                    if (null == sharedThreadFactory)\n                    {\n                        sharedThreadFactory = Thread::new;\n                    }\n                    if (null == sharedIdleStrategy)\n                    {\n                        sharedIdleStrategy = Configuration.sharedIdleStrategy(indicator);\n                    }\n                    break;\n\n                case SHARED_NETWORK:\n                    if (null == conductorThreadFactory)\n                    {\n                        conductorThreadFactory = Thread::new;\n                    }\n                    if (null == conductorIdleStrategy)\n                    {\n                        conductorIdleStrategy = Configuration.conductorIdleStrategy(indicator);\n                    }\n                    if (null == sharedNetworkThreadFactory)\n                    {\n                        sharedNetworkThreadFactory = Thread::new;\n                    }\n                    if (null == sharedNetworkIdleStrategy)\n                    {\n                        sharedNetworkIdleStrategy = Configuration.sharedNetworkIdleStrategy(indicator);\n                    }\n                    break;\n\n                case DEDICATED:\n                    if (null == conductorThreadFactory)\n                    {\n                        conductorThreadFactory = Thread::new;\n                    }\n                    if (null == senderThreadFactory)\n                    {\n                        senderThreadFactory = Thread::new;\n                    }\n                    if (null == receiverThreadFactory)\n                    {\n                        receiverThreadFactory = Thread::new;\n                    }\n                    if (null == conductorIdleStrategy)\n                    {\n                        conductorIdleStrategy = Configuration.conductorIdleStrategy(indicator);\n                    }\n                    if (null == senderIdleStrategy)\n                    {\n                        senderIdleStrategy = Configuration.senderIdleStrategy(indicator);\n                    }\n                    if (null == receiverIdleStrategy)\n                    {\n                        receiverIdleStrategy = Configuration.receiverIdleStrategy(indicator);\n                    }\n                    break;\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @SuppressWarnings(\"MethodLength\")\n        public String toString()\n        {\n            return \"MediaDriver.Context\" +\n                \"\\n{\" +\n                \"\\n    isConcluded=\" + isConcluded() +\n                \"\\n    isClosed=\" + isClosed +\n                \"\\n    cncVersion=\" + SemanticVersion.toString(CNC_VERSION) +\n                \"\\n    aeronDirectory=\" + aeronDirectory() +\n                \"\\n    enabledExperimentalFeatures=\" + enableExperimentalFeatures() +\n                \"\\n    aeronDirectoryName='\" + aeronDirectoryName() + '\\'' +\n                \"\\n    cncFile=\" + cncFile() +\n                \"\\n    countersMetaDataBuffer=\" + countersMetaDataBuffer() +\n                \"\\n    countersValuesBuffer=\" + countersValuesBuffer() +\n                \"\\n    driverTimeoutMs=\" + driverTimeoutMs() +\n                \"\\n    printConfigurationOnStart=\" + printConfigurationOnStart +\n                \"\\n    useWindowsHighResTimer=\" + useWindowsHighResTimer +\n                \"\\n    warnIfDirectoryExists=\" + warnIfDirectoryExists +\n                \"\\n    dirDeleteOnStart=\" + dirDeleteOnStart +\n                \"\\n    dirDeleteOnShutdown=\" + dirDeleteOnShutdown +\n                \"\\n    termBufferSparseFile=\" + termBufferSparseFile +\n                \"\\n    performStorageChecks=\" + performStorageChecks +\n                \"\\n    spiesSimulateConnection=\" + spiesSimulateConnection +\n                \"\\n    reliableStream=\" + reliableStream +\n                \"\\n    tetherSubscriptions=\" + tetherSubscriptions +\n                \"\\n    rejoinStream=\" + rejoinStream +\n                \"\\n    receiverGroupConsideration=\" + receiverGroupConsideration +\n                \"\\n    conductorBufferLength=\" + conductorBufferLength +\n                \"\\n    toClientsBufferLength=\" + toClientsBufferLength +\n                \"\\n    counterValuesBufferLength=\" + counterValuesBufferLength +\n                \"\\n    errorBufferLength=\" + errorBufferLength +\n                \"\\n    lowStorageWarningThreshold=\" + lowStorageWarningThreshold +\n                \"\\n    timerIntervalNs=\" + timerIntervalNs +\n                \"\\n    clientLivenessTimeoutNs=\" + clientLivenessTimeoutNs +\n                \"\\n    imageLivenessTimeoutNs=\" + imageLivenessTimeoutNs +\n                \"\\n    publicationUnblockTimeoutNs=\" + publicationUnblockTimeoutNs +\n                \"\\n    publicationConnectionTimeoutNs=\" + publicationConnectionTimeoutNs +\n                \"\\n    publicationLingerTimeoutNs=\" + publicationLingerTimeoutNs +\n                \"\\n    untetheredWindowLimitTimeoutNs=\" + untetheredWindowLimitTimeoutNs +\n                \"\\n    untetheredRestingTimeoutNs=\" + untetheredRestingTimeoutNs +\n                \"\\n    retransmitUnicastDelayNs=\" + retransmitUnicastDelayNs +\n                \"\\n    retransmitUnicastLingerNs=\" + retransmitUnicastLingerNs +\n                \"\\n    nakUnicastDelayNs=\" + nakUnicastDelayNs +\n                \"\\n    nakMulticastMaxBackoffNs=\" + nakMulticastMaxBackoffNs +\n                \"\\n    nakMulticastGroupSize=\" + nakMulticastGroupSize +\n                \"\\n    statusMessageTimeoutNs=\" + statusMessageTimeoutNs +\n                \"\\n    counterFreeToReuseTimeoutNs=\" + counterFreeToReuseTimeoutNs +\n                \"\\n    conductorCycleThresholdNs=\" + conductorCycleThresholdNs +\n                \"\\n    senderCycleThresholdNs=\" + senderCycleThresholdNs +\n                \"\\n    receiverCycleThresholdNs=\" + receiverCycleThresholdNs +\n                \"\\n    nameResolverThresholdNs=\" + nameResolverThresholdNs +\n                \"\\n    publicationTermBufferLength=\" + publicationTermBufferLength +\n                \"\\n    ipcTermBufferLength=\" + ipcTermBufferLength +\n                \"\\n    publicationTermWindowLength=\" + publicationTermWindowLength +\n                \"\\n    ipcPublicationTermWindowLength=\" + ipcPublicationTermWindowLength +\n                \"\\n    initialWindowLength=\" + initialWindowLength +\n                \"\\n    socketSndbufLength=\" + socketSndbufLength +\n                \"\\n    socketRcvbufLength=\" + socketRcvbufLength +\n                \"\\n    socketMulticastTtl=\" + socketMulticastTtl +\n                \"\\n    mtuLength=\" + mtuLength +\n                \"\\n    ipcMtuLength=\" + ipcMtuLength +\n                \"\\n    filePageSize=\" + filePageSize +\n                \"\\n    publicationReservedSessionIdLow=\" + publicationReservedSessionIdLow +\n                \"\\n    publicationReservedSessionIdHigh=\" + publicationReservedSessionIdHigh +\n                \"\\n    lossReportBufferLength=\" + lossReportBufferLength +\n                \"\\n    epochClock=\" + epochClock +\n                \"\\n    nanoClock=\" + nanoClock +\n                \"\\n    cachedEpochClock=\" + cachedEpochClock +\n                \"\\n    cachedNanoClock=\" + cachedNanoClock +\n                \"\\n    threadingMode=\" + threadingMode +\n                \"\\n    conductorThreadFactory=\" + conductorThreadFactory +\n                \"\\n    senderThreadFactory=\" + senderThreadFactory +\n                \"\\n    receiverThreadFactory=\" + receiverThreadFactory +\n                \"\\n    sharedThreadFactory=\" + sharedThreadFactory +\n                \"\\n    sharedNetworkThreadFactory=\" + sharedNetworkThreadFactory +\n                \"\\n    conductorIdleStrategy=\" + conductorIdleStrategy +\n                \"\\n    senderIdleStrategy=\" + senderIdleStrategy +\n                \"\\n    receiverIdleStrategy=\" + receiverIdleStrategy +\n                \"\\n    sharedNetworkIdleStrategy=\" + sharedNetworkIdleStrategy +\n                \"\\n    sharedIdleStrategy=\" + sharedIdleStrategy +\n                \"\\n    sendChannelEndpointSupplier=\" + sendChannelEndpointSupplier +\n                \"\\n    receiveChannelEndpointSupplier=\" + receiveChannelEndpointSupplier +\n                \"\\n    receiveChannelEndpointThreadLocals=\" + receiveChannelEndpointThreadLocals +\n                \"\\n    tempBuffer=\" + tempBuffer +\n                \"\\n    unicastFlowControlSupplier=\" + unicastFlowControlSupplier +\n                \"\\n    multicastFlowControlSupplier=\" + multicastFlowControlSupplier +\n                \"\\n    applicationSpecificFeedback=\" + Arrays.toString(applicationSpecificFeedback) +\n                \"\\n    receiverGroupTag=\" + receiverGroupTag +\n                \"\\n    flowControlGroupTag=\" + flowControlGroupTag +\n                \"\\n    flowControlGroupMinSize=\" + flowControlGroupMinSize +\n                \"\\n    flowControlReceiverTimeoutNs=\" + flowControlReceiverTimeoutNs +\n                \"\\n    unicastFlowControlRetransmitReceiverWindowMultiple=\" +\n                unicastFlowControlRetransmitReceiverWindowMultiple +\n                \"\\n    multicastFControlRetransmitReceiverWindowMultiple=\" +\n                multicastFlowControlRetransmitReceiverWindowMultiple +\n                \"\\n    reResolutionCheckIntervalNs=\" + reResolutionCheckIntervalNs +\n                \"\\n    receiverGroupConsideration=\" + receiverGroupConsideration +\n                \"\\n    congestionControlSupplier=\" + congestionControlSupplier +\n                \"\\n    terminationValidator=\" + terminationValidator +\n                \"\\n    terminationHook=\" + terminationHook +\n                \"\\n    nameResolver=\" + nameResolver +\n                \"\\n    resolverName='\" + resolverName + '\\'' +\n                \"\\n    resolverInterface='\" + resolverInterface + '\\'' +\n                \"\\n    resolverBootstrapNeighbor='\" + resolverBootstrapNeighbor + '\\'' +\n                \"\\n    sendToStatusMessagePollRatio=\" + sendToStatusMessagePollRatio +\n                \"\\n    unicastFeedbackDelayGenerator=\" + unicastFeedbackDelayGenerator +\n                \"\\n    multicastFeedbackDelayGenerator=\" + multicastFeedbackDelayGenerator +\n                \"\\n    retransmitUnicastDelayGenerator=\" + retransmitUnicastDelayGenerator +\n                \"\\n    retransmitUnicastLingerGenerator=\" + retransmitUnicastLingerGenerator +\n                \"\\n    errorLog=\" + errorLog +\n                \"\\n    errorHandler=\" + errorHandler +\n                \"\\n    useConcurrentCountersManager=\" + useConcurrentCountersManager +\n                \"\\n    countersManager=\" + countersManager +\n                \"\\n    systemCounters=\" + systemCounters +\n                \"\\n    lossReport=\" + lossReport +\n                \"\\n    logFactory=\" + logFactory +\n                \"\\n    dataTransportPoller=\" + dataTransportPoller +\n                \"\\n    controlTransportPoller=\" + controlTransportPoller +\n                \"\\n    driverCommandQueue=\" + driverCommandQueue +\n                \"\\n    receiverCommandQueue=\" + receiverCommandQueue +\n                \"\\n    senderCommandQueue=\" + senderCommandQueue +\n                \"\\n    receiverProxy=\" + receiverProxy +\n                \"\\n    senderProxy=\" + senderProxy +\n                \"\\n    driverConductorProxy=\" + driverConductorProxy +\n                \"\\n    clientProxy=\" + clientProxy +\n                \"\\n    toDriverCommands=\" + toDriverCommands +\n                \"\\n    lossReportBuffer=\" + lossReportBuffer +\n                \"\\n    cncByteBuffer=\" + cncByteBuffer +\n                \"\\n    cncMetaDataBuffer=\" + cncMetaDataBuffer +\n                \"\\n    channelSendTimestampClock=\" + channelSendTimestampClock +\n                \"\\n    channelReceiveTimestampClock=\" + channelReceiveTimestampClock +\n                \"\\n    conductorDutyCycleTracker=\" + conductorDutyCycleTracker +\n                \"\\n    senderDutyCycleTracker=\" + senderDutyCycleTracker +\n                \"\\n    receiverDutyCycleTracker=\" + receiverDutyCycleTracker +\n                \"\\n    senderWildcardPortRange=\" + senderWildcardPortRange +\n                \"\\n    receiverWildcardPortRange=\" + receiverWildcardPortRange +\n                \"\\n    senderPortManager=\" + senderPortManager +\n                \"\\n    receiverPortManager=\" + receiverPortManager +\n                \"\\n    resourceFreeLimit=\" + resourceFreeLimit +\n                \"\\n    asyncTaskExecutorThreads=\" + asyncTaskExecutorThreads +\n                \"\\n    asyncTaskExecutor=\" + asyncTaskExecutor +\n                \"\\n    maxResend=\" + maxResend +\n                \"\\n}\";\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/MinMulticastFlowControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Minimum multicast sender flow control strategy. Uses the {@link AbstractMinMulticastFlowControl}, but specifies that\n * the group membership for a given receiver is always <code>true</code>, so it tracks the minimum for all receivers.\n */\npublic class MinMulticastFlowControl extends AbstractMinMulticastFlowControl\n{\n    /**\n     * URI param value to identify this {@link FlowControl} strategy.\n     */\n    public static final String FC_PARAM_VALUE = \"min\";\n\n    MinMulticastFlowControl()\n    {\n        super(false);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onStatusMessage(\n        final StatusMessageFlyweight flyweight,\n        final InetSocketAddress receiverAddress,\n        final long senderLimit,\n        final int initialTermId,\n        final int positionBitsToShift,\n        final long timeNs)\n    {\n        return processStatusMessage(flyweight, senderLimit, initialTermId, positionBitsToShift, timeNs, true);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTriggerSendSetup(\n        final StatusMessageFlyweight flyweight,\n        final InetSocketAddress receiverAddress,\n        final long timeNs)\n    {\n        processSendSetupTrigger(flyweight, receiverAddress, timeNs, true);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onError(final ErrorFlyweight errorFlyweight, final InetSocketAddress receiverAddress, final long timeNs)\n    {\n        processError(errorFlyweight, receiverAddress, timeNs, true);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/MinMulticastFlowControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\n\n/**\n * Supplier of {@link MinMulticastFlowControl} strategy implementations.\n */\npublic class MinMulticastFlowControlSupplier implements FlowControlSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public MinMulticastFlowControlSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public FlowControl newInstance(final UdpChannel udpChannel, final int streamId, final long registrationId)\n    {\n        return new MinMulticastFlowControl();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/NameResolver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CounterProvider;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.net.InetAddress;\n\n/**\n * Interface to allow resolving a name to an {@link InetAddress}.\n */\npublic interface NameResolver\n{\n    /**\n     * Resolve a name and return the most up to date {@link InetAddress} that represents the name.\n     *\n     * @param name           to resolve.\n     * @param uriParamName   that the resolution is for.\n     * @param isReResolution {@code true} if this is a re-resolution or {@code false} if initial resolution.\n     * @return address for the name that most recently represents the name or null if not resolvable currently.\n     * @see io.aeron.CommonContext#ENDPOINT_PARAM_NAME\n     * @see io.aeron.CommonContext#MDC_CONTROL_PARAM_NAME\n     */\n    InetAddress resolve(String name, String uriParamName, boolean isReResolution);\n\n    /**\n     * Lookup the name and return a string of the form {@code name:port} that represents the endpoint or control param\n     * of the URI.\n     *\n     * @param name         to lookup\n     * @param uriParamName that the lookup is for.\n     * @param isReLookup   {@code true} if this is a re-lookup or {@code true} if initial lookup.\n     * @return string in {@code name:port} form.\n     */\n    default String lookup(final String name, final String uriParamName, final boolean isReLookup)\n    {\n        return name;\n    }\n\n    /**\n     * Do post construction initialisation of the name resolver.  Happens during the conductor start lifecycle.  Can be\n     * used for actions like adding counters.\n     *\n     * @param context for the media driver that the name resolver is running in.\n     * @deprecated Use {@link #init(CountersReader, CounterProvider)} instead.\n     * @see #init(CountersReader, CounterProvider)\n     */\n    @Deprecated\n    default void init(final MediaDriver.Context context)\n    {\n        throw new UnsupportedOperationException(\"deprecated: use NameResolver.init(io.aeron.CounterFactory) instead\");\n    }\n\n    /**\n     * Do post construction initialisation of the name resolver.\n     *\n     * @param countersReader for finding existing counters.\n     * @param counterProvider for adding counters.\n     */\n    default void init(final CountersReader countersReader, final CounterProvider counterProvider)\n    {\n    }\n\n    /**\n     * Perform periodic work for the resolver.\n     *\n     * @param nowMs current epoch clock time in milliseconds\n     * @return work count\n     */\n    default int doWork(final long nowMs)\n    {\n        return 0;\n    }\n\n    /**\n     * Gets the name of the resolver, used for logging and debugging.\n     *\n     * @return name of the resolver, defaults to the qualified class name.\n     */\n    default String name()\n    {\n        return this.getClass().getName();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/NamedCompositeAgent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.Strings;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.CompositeAgent;\n\nimport java.util.List;\n\nclass NamedCompositeAgent extends CompositeAgent\n{\n    private final String name;\n\n    NamedCompositeAgent(final String name, final Agent... agents)\n    {\n        super(agents);\n        this.name = name;\n    }\n\n    NamedCompositeAgent(final String name, final List<? extends Agent> agents)\n    {\n        super(agents);\n        this.name = name;\n    }\n\n    public String roleName()\n    {\n        final String prefix = Strings.isEmpty(name) ? \"\" : name + \" \";\n        return prefix + super.roleName();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/NetworkPublication.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.logbuffer.LogBufferUnblocker;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.Position;\nimport org.agrona.concurrent.status.ReadablePosition;\n\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\n\nimport static io.aeron.driver.Configuration.PUBLICATION_HEARTBEAT_TIMEOUT_NS;\nimport static io.aeron.driver.Configuration.PUBLICATION_SETUP_TIMEOUT_NS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.HEARTBEATS_SENT;\nimport static io.aeron.driver.status.SystemCounterDescriptor.RETRANSMITS_SENT;\nimport static io.aeron.driver.status.SystemCounterDescriptor.RETRANSMITTED_BYTES;\nimport static io.aeron.driver.status.SystemCounterDescriptor.SENDER_FLOW_CONTROL_LIMITS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.SHORT_SENDS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.UNBLOCKED_PUBLICATIONS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.PUBLICATIONS_REVOKED;\nimport static io.aeron.logbuffer.LogBufferDescriptor.activeTermCount;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computeTermIdFromPosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.endOfStreamPosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.indexByPosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.rawTailVolatile;\nimport static io.aeron.logbuffer.LogBufferDescriptor.termId;\nimport static io.aeron.logbuffer.LogBufferDescriptor.termOffset;\nimport static io.aeron.logbuffer.TermScanner.available;\nimport static io.aeron.logbuffer.TermScanner.padding;\nimport static io.aeron.logbuffer.TermScanner.scanForAvailability;\nimport static io.aeron.protocol.DataHeaderFlyweight.BEGIN_AND_END_FLAGS;\nimport static io.aeron.protocol.DataHeaderFlyweight.BEGIN_END_AND_EOS_FLAGS;\nimport static io.aeron.protocol.DataHeaderFlyweight.BEGIN_END_EOS_AND_REVOKED_FLAGS;\nimport static io.aeron.protocol.StatusMessageFlyweight.END_OF_STREAM_FLAG;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\nclass NetworkPublicationPadding1\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nclass NetworkPublicationConductorFields extends NetworkPublicationPadding1\n{\n    static final ReadablePosition[] EMPTY_POSITIONS = new ReadablePosition[0];\n\n    long cleanPosition;\n    long timeOfLastActivityNs;\n    long lastSenderPosition;\n    int refCount = 0;\n    ReadablePosition[] spyPositions = EMPTY_POSITIONS;\n    final ArrayList<UntetheredSubscription> untetheredSubscriptions = new ArrayList<>();\n}\n\nclass NetworkPublicationPadding2 extends NetworkPublicationConductorFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\nclass NetworkPublicationSenderFields extends NetworkPublicationPadding2\n{\n    long timeOfLastDataOrHeartbeatNs;\n    long timeOfLastSetupNs;\n    long timeOfLastStatusMessageNs;\n    long timeOfLastUpdateReceivers;\n    boolean trackSenderLimits = false;\n    boolean isSetupElicited = false;\n    boolean hasInitialConnection = false;\n    byte extraPaddingByteForAlignment;\n    InetSocketAddress endpointAddress;\n}\n\nclass NetworkPublicationPadding3 extends NetworkPublicationSenderFields\n{\n    byte p128, p129, p130, p131, p132, p133, p134, p135, p136, p137, p138, p139, p140, p142, p143, p144;\n    byte p145, p146, p147, p148, p149, p150, p151, p152, p153, p154, p155, p156, p157, p158, p159, p160;\n    byte p161, p162, p163, p164, p165, p166, p167, p168, p169, p170, p171, p172, p173, p174, p175, p176;\n    byte p177, p178, p179, p180, p181, p182, p183, p184, p185, p186, p187, p189, p190, p191, p192, p193;\n}\n\n/**\n * Publication to be sent to connected subscribers.\n */\npublic final class NetworkPublication\n    extends NetworkPublicationPadding3\n    implements RetransmitSender, DriverManagedResource, Subscribable\n{\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        ACTIVE, DRAINING, LINGER, DONE\n    }\n\n    private final long registrationId;\n    private final long unblockTimeoutNs;\n    private final long connectionTimeoutNs;\n    private final long lingerTimeoutNs;\n    private final long untetheredWindowLimitTimeoutNs;\n    private final long untetheredLingerTimeoutNs;\n    private final long untetheredRestingTimeoutNs;\n    private final long tag;\n    private final long responseCorrelationId;\n    private final int positionBitsToShift;\n    private final int initialTermId;\n    private final int startingTermId;\n    private final int startingTermOffset;\n    private final int termBufferLength;\n    private final int termLengthMask;\n    private final int mtuLength;\n    private final int termWindowLength;\n    private final int sessionId;\n    private final int streamId;\n    private final boolean isExclusive;\n    private final boolean signalEos;\n    private final boolean isResponse;\n    private final boolean spiesSimulateConnection;\n    private volatile boolean hasSpies;\n    private volatile boolean hasReceivers;\n    private volatile boolean isConnected;\n    private volatile boolean isEndOfStream;\n    private volatile boolean hasSenderReleased;\n    private volatile boolean hasReceivedUnicastEos;\n    private State state = State.ACTIVE;\n\n    private final FlowControl flowControl;\n    private final UnsafeBuffer[] termBuffers;\n    private final ByteBuffer[] sendBuffers;\n    private final ErrorHandler errorHandler;\n    private final Position publisherPos;\n    private final Position publisherLimit;\n    private final Position senderPosition;\n    private final Position senderLimit;\n    private final SendChannelEndpoint channelEndpoint;\n    private final ByteBuffer heartbeatBuffer;\n    private final DataHeaderFlyweight heartbeatDataHeader;\n    private final ByteBuffer setupBuffer;\n    private final SetupFlyweight setupHeader;\n    private final ByteBuffer rttMeasurementBuffer;\n    private final RttMeasurementFlyweight rttMeasurementHeader;\n    private final CachedNanoClock cachedNanoClock;\n    private final RetransmitHandler retransmitHandler;\n    private final UnsafeBuffer metaDataBuffer;\n    private final RawLog rawLog;\n    private final AtomicCounter heartbeatsSent;\n    private final AtomicCounter retransmitsSent;\n    private final AtomicCounter retransmittedBytes;\n    private final AtomicCounter senderFlowControlLimits;\n    private final AtomicCounter senderBpe;\n    private final AtomicCounter senderNaksReceived;\n    private final AtomicCounter shortSends;\n    private final AtomicCounter unblockedPublications;\n    private final AtomicCounter publicationsRevoked;\n    private final ReceiverLivenessTracker livenessTracker = new ReceiverLivenessTracker();\n\n    NetworkPublication(\n        final long registrationId,\n        final MediaDriver.Context ctx,\n        final PublicationParams params,\n        final SendChannelEndpoint channelEndpoint,\n        final RawLog rawLog,\n        final int termWindowLength,\n        final Position publisherPos,\n        final Position publisherLimit,\n        final Position senderPosition,\n        final Position senderLimit,\n        final AtomicCounter senderBpe,\n        final AtomicCounter senderNaksReceived,\n        final int sessionId,\n        final int streamId,\n        final int initialTermId,\n        final FlowControl flowControl,\n        final RetransmitHandler retransmitHandler,\n        final NetworkPublicationThreadLocals threadLocals,\n        final boolean isExclusive)\n    {\n        this.registrationId = registrationId;\n        this.unblockTimeoutNs = ctx.publicationUnblockTimeoutNs();\n        this.connectionTimeoutNs = ctx.publicationConnectionTimeoutNs();\n        this.lingerTimeoutNs = params.lingerTimeoutNs;\n        this.untetheredWindowLimitTimeoutNs = params.untetheredWindowLimitTimeoutNs;\n        this.untetheredLingerTimeoutNs = params.untetheredLingerTimeoutNs;\n        this.untetheredRestingTimeoutNs = params.untetheredRestingTimeoutNs;\n        this.tag = params.entityTag;\n        this.channelEndpoint = channelEndpoint;\n        this.rawLog = rawLog;\n        this.cachedNanoClock = ctx.senderCachedNanoClock();\n        this.senderPosition = senderPosition;\n        this.senderLimit = senderLimit;\n        this.senderNaksReceived = senderNaksReceived;\n        this.flowControl = flowControl;\n        this.retransmitHandler = retransmitHandler;\n        this.publisherPos = publisherPos;\n        this.publisherLimit = publisherLimit;\n        this.mtuLength = params.mtuLength;\n        this.initialTermId = initialTermId;\n        this.sessionId = sessionId;\n        this.streamId = streamId;\n        this.spiesSimulateConnection = params.spiesSimulateConnection;\n        this.signalEos = params.signalEos;\n        this.isExclusive = isExclusive;\n        this.startingTermId = params.termId;\n        this.startingTermOffset = params.termOffset;\n        this.isResponse = params.isResponse;\n        this.responseCorrelationId = params.responseCorrelationId;\n\n        metaDataBuffer = rawLog.metaData();\n        setupBuffer = threadLocals.setupBuffer();\n        setupHeader = threadLocals.setupHeader();\n        heartbeatBuffer = threadLocals.heartbeatBuffer();\n        heartbeatDataHeader = threadLocals.heartbeatDataHeader();\n        rttMeasurementBuffer = threadLocals.rttMeasurementBuffer();\n        rttMeasurementHeader = threadLocals.rttMeasurementHeader();\n\n        final SystemCounters systemCounters = ctx.systemCounters();\n        heartbeatsSent = systemCounters.get(HEARTBEATS_SENT);\n        shortSends = systemCounters.get(SHORT_SENDS);\n        retransmitsSent = systemCounters.get(RETRANSMITS_SENT);\n        retransmittedBytes = systemCounters.get(RETRANSMITTED_BYTES);\n        senderFlowControlLimits = systemCounters.get(SENDER_FLOW_CONTROL_LIMITS);\n        unblockedPublications = systemCounters.get(UNBLOCKED_PUBLICATIONS);\n        publicationsRevoked = systemCounters.get(PUBLICATIONS_REVOKED);\n        this.senderBpe = senderBpe;\n\n        termBuffers = rawLog.termBuffers();\n        for (final UnsafeBuffer termBuffer : termBuffers)\n        {\n            termBuffer.verifyAlignment();\n        }\n\n        sendBuffers = rawLog.sliceTerms();\n        errorHandler = ctx.errorHandler();\n\n        final int termLength = rawLog.termLength();\n        termBufferLength = termLength;\n        termLengthMask = termLength - 1;\n\n        final long nowNs = cachedNanoClock.nanoTime();\n        timeOfLastDataOrHeartbeatNs = nowNs - PUBLICATION_HEARTBEAT_TIMEOUT_NS - 1;\n        timeOfLastSetupNs = nowNs - PUBLICATION_SETUP_TIMEOUT_NS - 1;\n\n        positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n        this.termWindowLength = termWindowLength;\n\n        lastSenderPosition = senderPosition.get();\n        cleanPosition = lastSenderPosition;\n        timeOfLastActivityNs = nowNs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean free()\n    {\n        return rawLog.free();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(errorHandler, publisherPos);\n        CloseHelper.close(errorHandler, publisherLimit);\n        CloseHelper.close(errorHandler, senderPosition);\n        CloseHelper.close(errorHandler, senderLimit);\n        CloseHelper.close(errorHandler, senderBpe);\n        CloseHelper.close(errorHandler, senderNaksReceived);\n        CloseHelper.closeAll(errorHandler, spyPositions);\n\n        for (int i = 0, size = untetheredSubscriptions.size(); i < size; i++)\n        {\n            final UntetheredSubscription untetheredSubscription = untetheredSubscriptions.get(i);\n            if (UntetheredSubscription.State.RESTING == untetheredSubscription.state)\n            {\n                CloseHelper.close(errorHandler, untetheredSubscription.position);\n            }\n        }\n\n        CloseHelper.close(flowControl);\n    }\n\n    /**\n     * Time of the last status message a from a receiver.\n     *\n     * @return this of the last status message a from a receiver.\n     */\n    public long timeOfLastStatusMessageNs()\n    {\n        return timeOfLastStatusMessageNs;\n    }\n\n    /**\n     * Channel URI string for this publication.\n     *\n     * @return channel URI string for this publication.\n     */\n    public String channel()\n    {\n        return channelEndpoint.originalUriString();\n    }\n\n    /**\n     * Session id allocated to this stream.\n     *\n     * @return session id allocated to this stream.\n     */\n    public int sessionId()\n    {\n        return sessionId;\n    }\n\n    /**\n     * Stream id within the channel.\n     *\n     * @return stream id within the channel.\n     */\n    public int streamId()\n    {\n        return streamId;\n    }\n\n    /**\n     * Trigger the sending of a SETUP frame so a connection can be established.\n     *\n     * @param msg        that triggers the SETUP.\n     * @param srcAddress of the source that triggers the SETUP.\n     */\n    public void triggerSendSetupFrame(final StatusMessageFlyweight msg, final InetSocketAddress srcAddress)\n    {\n        if (!isEndOfStream)\n        {\n            timeOfLastStatusMessageNs = cachedNanoClock.nanoTime();\n            isSetupElicited = true;\n            flowControl.onTriggerSendSetup(msg, srcAddress, timeOfLastStatusMessageNs);\n\n            if (isResponse)\n            {\n                this.endpointAddress = srcAddress;\n            }\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long subscribableRegistrationId()\n    {\n        return registrationId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void addSubscriber(\n        final SubscriptionLink subscriptionLink, final ReadablePosition position, final long nowNs)\n    {\n        addSpyPosition(position);\n\n        if (!subscriptionLink.isTether())\n        {\n            untetheredSubscriptions.add(new UntetheredSubscription(subscriptionLink, position, nowNs));\n        }\n\n        if (spiesSimulateConnection)\n        {\n            updateConnectedState(true);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void removeSubscriber(final SubscriptionLink subscriptionLink, final ReadablePosition position)\n    {\n        removeSpyPosition(position);\n        position.close();\n\n        if (!subscriptionLink.isTether())\n        {\n            for (int lastIndex = untetheredSubscriptions.size() - 1, i = lastIndex; i >= 0; i--)\n            {\n                if (untetheredSubscriptions.get(i).subscriptionLink == subscriptionLink)\n                {\n                    ArrayListUtil.fastUnorderedRemove(untetheredSubscriptions, i, lastIndex);\n                    break;\n                }\n            }\n        }\n\n        if (spiesSimulateConnection)\n        {\n            updateConnectedState(hasSubscribers());\n        }\n    }\n\n    /**\n     * Process a NAK message so a retransmit can occur.\n     *\n     * @param termId     in which the loss occurred.\n     * @param termOffset at which the loss begins.\n     * @param length     of the loss.\n     */\n    public void onNak(final int termId, final int termOffset, final int length)\n    {\n        senderNaksReceived.incrementRelease();\n        retransmitHandler.onNak(termId, termOffset, length, termBufferLength, mtuLength, flowControl, this);\n    }\n\n    /**\n     * Process a status message to track connectivity and apply flow control.\n     *\n     * @param msg            flyweight over the network packet.\n     * @param srcAddress     that the setup message has come from.\n     * @param conductorProxy to send messages back to the conductor.\n     */\n    public void onStatusMessage(\n        final StatusMessageFlyweight msg,\n        final InetSocketAddress srcAddress,\n        final DriverConductorProxy conductorProxy)\n    {\n        final boolean isEos = END_OF_STREAM_FLAG == (msg.flags() & END_OF_STREAM_FLAG);\n        final long timeNs = cachedNanoClock.nanoTime();\n\n        if (isEos)\n        {\n            livenessTracker.onRemoteClose(msg.receiverId());\n\n            if (!channelEndpoint.udpChannel().isMulticast() &&\n                !channelEndpoint.udpChannel().isMultiDestination())\n            {\n                hasReceivedUnicastEos = true;\n            }\n        }\n        else\n        {\n            livenessTracker.onStatusMessage(msg.receiverId(), timeNs);\n        }\n\n        final boolean isLive = livenessTracker.hasReceivers();\n        final boolean existingHasReceivers = hasReceivers;\n\n        if (!existingHasReceivers && isLive)\n        {\n            conductorProxy.responseConnected(responseCorrelationId);\n        }\n\n        if (existingHasReceivers != isLive)\n        {\n            hasReceivers = isLive;\n        }\n\n        if (!hasInitialConnection)\n        {\n            hasInitialConnection = true;\n        }\n\n        timeOfLastStatusMessageNs = timeNs;\n\n        senderLimit.setRelease(flowControl.onStatusMessage(\n            msg,\n            srcAddress,\n            senderLimit.get(),\n            initialTermId,\n            positionBitsToShift,\n            timeNs));\n\n        updateConnectedState(hasSubscribers());\n    }\n\n    /**\n     * Process an error message from a receiver.\n     *\n     * @param msg                       flyweight over the network packet.\n     * @param srcAddress                that the setup message has come from.\n     * @param destinationRegistrationId registrationId of the relevant MDC destination or {@link Aeron#NULL_VALUE}\n     * @param conductorProxy            to send messages back to the conductor.\n     */\n    public void onError(\n        final ErrorFlyweight msg,\n        final InetSocketAddress srcAddress,\n        final long destinationRegistrationId,\n        final DriverConductorProxy conductorProxy)\n    {\n        flowControl.onError(msg, srcAddress, cachedNanoClock.nanoTime());\n        if (livenessTracker.onRemoteClose(msg.receiverId()))\n        {\n            conductorProxy.onPublicationError(\n                registrationId,\n                destinationRegistrationId,\n                msg.sessionId(),\n                msg.streamId(),\n                msg.receiverId(),\n                msg.groupTag(),\n                srcAddress,\n                msg.errorCode(),\n                msg.errorMessage());\n        }\n    }\n\n    /**\n     * Process RTT (Round Trip Timing) message from a receiver.\n     *\n     * @param msg        flyweight over the network packet.\n     * @param srcAddress that the RTT message has come from.\n     */\n    public void onRttMeasurement(final RttMeasurementFlyweight msg, final InetSocketAddress srcAddress)\n    {\n        if (RttMeasurementFlyweight.REPLY_FLAG == (msg.flags() & RttMeasurementFlyweight.REPLY_FLAG))\n        {\n            rttMeasurementBuffer.clear();\n            rttMeasurementHeader\n                .receiverId(msg.receiverId())\n                .echoTimestampNs(msg.echoTimestampNs())\n                .receptionDelta(0)\n                .sessionId(sessionId)\n                .streamId(streamId)\n                .flags((short)0x0);\n\n            final int bytesSent = doSend(rttMeasurementBuffer);\n            if (RttMeasurementFlyweight.HEADER_LENGTH != bytesSent)\n            {\n                shortSends.increment();\n            }\n        }\n\n        // handling of RTT measurements would be done in an else clause here.\n    }\n\n    private int doSend(final ByteBuffer message)\n    {\n        if (isResponse)\n        {\n            if (null != endpointAddress)\n            {\n                return channelEndpoint.send(message, endpointAddress);\n            }\n            else\n            {\n                return 0;\n            }\n        }\n        else\n        {\n            return channelEndpoint.send(message);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void resend(final int termId, final int termOffset, final int length)\n    {\n        channelEndpoint.resendHook(sessionId, streamId, termId, termOffset, length);\n\n        final long senderPosition = this.senderPosition.get();\n        final long resendPosition = computePosition(termId, termOffset, positionBitsToShift, initialTermId);\n        final long bottomResendWindow =\n            senderPosition - (termBufferLength >> 1) - FrameDescriptor.computeMaxMessageLength(termBufferLength);\n\n        if (bottomResendWindow <= resendPosition && resendPosition < senderPosition)\n        {\n            final int activeIndex = indexByPosition(resendPosition, positionBitsToShift);\n            final UnsafeBuffer termBuffer = termBuffers[activeIndex];\n            final ByteBuffer sendBuffer = sendBuffers[activeIndex];\n\n            int remainingBytes = length;\n            int totalBytesSent = 0;\n            int bytesSent = 0;\n            int offset = termOffset;\n            do\n            {\n                offset += bytesSent;\n\n                final long scanOutcome = scanForAvailability(termBuffer, offset, Math.min(mtuLength, remainingBytes));\n                final int available = available(scanOutcome);\n                if (available <= 0)\n                {\n                    break;\n                }\n\n                sendBuffer.limit(offset + available).position(offset);\n\n                if (available != doSend(sendBuffer))\n                {\n                    shortSends.increment();\n                    break;\n                }\n\n                bytesSent = available + padding(scanOutcome);\n                remainingBytes -= bytesSent;\n                totalBytesSent += bytesSent;\n            }\n            while (remainingBytes > 0);\n\n            if (totalBytesSent > 0)\n            {\n                retransmitsSent.incrementRelease();\n                retransmittedBytes.getAndAddRelease(totalBytesSent);\n            }\n        }\n    }\n\n    int send(final long nowNs)\n    {\n        final long senderPosition = this.senderPosition.get();\n        final int activeTermId = computeTermIdFromPosition(senderPosition, positionBitsToShift, initialTermId);\n        final int termOffset = (int)senderPosition & termLengthMask;\n\n        if (!hasInitialConnection || isSetupElicited)\n        {\n            setupMessageCheck(nowNs, activeTermId, termOffset);\n        }\n\n        int bytesSent = sendData(nowNs, senderPosition, termOffset);\n\n        if (0 == bytesSent)\n        {\n            bytesSent = heartbeatMessageCheck(nowNs, activeTermId, termOffset);\n\n            if (spiesSimulateConnection && hasSpies && !hasReceivers)\n            {\n                final long newSenderPosition = maxSpyPosition(senderPosition);\n                this.senderPosition.setRelease(newSenderPosition);\n                senderLimit.setRelease(flowControl.onIdle(nowNs, newSenderPosition, newSenderPosition, isEndOfStream));\n            }\n            else\n            {\n                senderLimit.setRelease(flowControl.onIdle(nowNs, senderLimit.get(), senderPosition, isEndOfStream));\n            }\n\n            updateHasReceivers(nowNs);\n        }\n\n        retransmitHandler.processTimeouts(nowNs, this);\n\n        return bytesSent;\n    }\n\n    SendChannelEndpoint channelEndpoint()\n    {\n        return channelEndpoint;\n    }\n\n    RawLog rawLog()\n    {\n        return rawLog;\n    }\n\n    int publisherLimitId()\n    {\n        return publisherLimit.id();\n    }\n\n    long tag()\n    {\n        return tag;\n    }\n\n    int termBufferLength()\n    {\n        return termBufferLength;\n    }\n\n    int mtuLength()\n    {\n        return mtuLength;\n    }\n\n    long registrationId()\n    {\n        return registrationId;\n    }\n\n    boolean isExclusive()\n    {\n        return isExclusive;\n    }\n\n    boolean spiesSimulateConnection()\n    {\n        return spiesSimulateConnection;\n    }\n\n    int initialTermId()\n    {\n        return initialTermId;\n    }\n\n    int startingTermId()\n    {\n        return startingTermId;\n    }\n\n    int startingTermOffset()\n    {\n        return startingTermOffset;\n    }\n\n    boolean isAcceptingSubscriptions()\n    {\n        return State.ACTIVE == state ||\n            (State.DRAINING == state && hasSpies && producerPosition() > senderPosition.getVolatile());\n    }\n\n    /**\n     * Update the publisher position and limit for flow control as part of the conductor duty cycle.\n     *\n     * @return 1 if the limit has been updated otherwise 0.\n     */\n    int updatePublisherPositionAndLimit()\n    {\n        int workCount = 0;\n\n        if (State.ACTIVE == state)\n        {\n            final long producerPosition = producerPosition();\n            final long senderPosition = this.senderPosition.getVolatile();\n\n            publisherPos.setRelease(producerPosition);\n\n            if (hasSubscribers())\n            {\n                long minConsumerPosition = senderPosition;\n                for (final ReadablePosition spyPosition : spyPositions)\n                {\n                    minConsumerPosition = Math.min(minConsumerPosition, spyPosition.getVolatile());\n                }\n\n                final long newLimitPosition = minConsumerPosition + termWindowLength;\n                if (newLimitPosition > publisherLimit.get())\n                {\n                    cleanBufferTo(minConsumerPosition - termBufferLength);\n                    final long cleanPosition = this.cleanPosition;\n                    final int dirtyTermId =\n                        computeTermIdFromPosition(cleanPosition, positionBitsToShift, initialTermId);\n                    final int activeTermId =\n                        computeTermIdFromPosition(newLimitPosition, positionBitsToShift, initialTermId);\n                    final int termGap = activeTermId - dirtyTermId;\n                    if (termGap < 2 || (2 == termGap && 0 != (int)(cleanPosition & termLengthMask)))\n                    {\n                        publisherLimit.setRelease(newLimitPosition);\n                    }\n                    workCount = 1;\n                }\n            }\n            else if (publisherLimit.get() > senderPosition)\n            {\n                updateConnectedState(false);\n                publisherLimit.setRelease(senderPosition);\n                cleanBufferTo(senderPosition - termBufferLength);\n                workCount = 1;\n            }\n        }\n\n        return workCount;\n    }\n\n    boolean hasSpies()\n    {\n        return hasSpies;\n    }\n\n    void updateHasReceivers(final long timeNs)\n    {\n        livenessTracker.onIdle(timeNs, connectionTimeoutNs);\n        final boolean isLive = livenessTracker.hasReceivers();\n\n        if (hasReceivers != isLive)\n        {\n            hasReceivers = isLive;\n        }\n\n        timeOfLastUpdateReceivers = timeNs;\n    }\n\n    private int sendData(final long nowNs, final long senderPosition, final int termOffset)\n    {\n        int bytesSent = 0;\n        final int availableWindow = (int)(senderLimit.get() - senderPosition);\n        if (availableWindow > 0)\n        {\n            final int scanLimit = Math.min(availableWindow, mtuLength);\n            final int activeIndex = indexByPosition(senderPosition, positionBitsToShift);\n\n            final long scanOutcome = scanForAvailability(termBuffers[activeIndex], termOffset, scanLimit);\n            final int available = available(scanOutcome);\n            if (available > 0)\n            {\n                final ByteBuffer sendBuffer = sendBuffers[activeIndex];\n                sendBuffer.limit(termOffset + available).position(termOffset);\n\n                if (available == doSend(sendBuffer))\n                {\n                    timeOfLastDataOrHeartbeatNs = nowNs;\n                    trackSenderLimits = true;\n\n                    bytesSent = available + padding(scanOutcome);\n                    this.senderPosition.setRelease(senderPosition + bytesSent);\n                }\n                else\n                {\n                    shortSends.increment();\n                }\n            }\n            else if (available < 0)\n            {\n                if (trackSenderLimits)\n                {\n                    trackSenderLimits = false;\n                    senderBpe.incrementRelease();\n                    senderFlowControlLimits.incrementRelease();\n                }\n            }\n        }\n        else if (trackSenderLimits)\n        {\n            trackSenderLimits = false;\n            senderBpe.incrementRelease();\n            senderFlowControlLimits.incrementRelease();\n        }\n\n        return bytesSent;\n    }\n\n    private void setupMessageCheck(final long nowNs, final int activeTermId, final int termOffset)\n    {\n        if ((timeOfLastSetupNs + PUBLICATION_SETUP_TIMEOUT_NS) - nowNs < 0)\n        {\n            timeOfLastSetupNs = nowNs;\n\n            final int flags =\n                (isSendResponseSetupFlag() ? SetupFlyweight.SEND_RESPONSE_SETUP_FLAG : 0) |\n                (hasGroupSemantics() ? SetupFlyweight.GROUP_FLAG : 0);\n\n            setupBuffer.clear();\n            setupHeader\n                .activeTermId(activeTermId)\n                .termOffset(termOffset)\n                .sessionId(sessionId)\n                .streamId(streamId)\n                .initialTermId(initialTermId)\n                .termLength(termBufferLength)\n                .mtuLength(mtuLength)\n                .ttl(channelEndpoint.multicastTtl())\n                .flags((short)(flags & 0xFFFF));\n\n            if (isSetupElicited)\n            {\n                flowControl.onSetup(setupHeader, senderLimit.get(), senderPosition.get(), positionBitsToShift, nowNs);\n            }\n\n            if (SetupFlyweight.HEADER_LENGTH != doSend(setupBuffer))\n            {\n                shortSends.increment();\n            }\n\n            if (isSetupElicited && hasReceivers)\n            {\n                isSetupElicited = false;\n            }\n        }\n    }\n\n    private int heartbeatMessageCheck(\n        final long nowNs, final int activeTermId, final int termOffset)\n    {\n        int bytesSent = 0;\n\n        if (hasInitialConnection && (timeOfLastDataOrHeartbeatNs + PUBLICATION_HEARTBEAT_TIMEOUT_NS) - nowNs < 0)\n        {\n            final short flags;\n\n            if (LogBufferDescriptor.isPublicationRevoked(metaDataBuffer))\n            {\n                flags = BEGIN_END_EOS_AND_REVOKED_FLAGS;\n            }\n            else if (signalEos && isEndOfStream)\n            {\n                flags = BEGIN_END_AND_EOS_FLAGS;\n            }\n            else\n            {\n                flags = BEGIN_AND_END_FLAGS;\n            }\n\n            heartbeatBuffer.clear();\n            heartbeatDataHeader\n                .sessionId(sessionId)\n                .streamId(streamId)\n                .termId(activeTermId)\n                .termOffset(termOffset)\n                .flags(flags);\n\n            bytesSent = doSend(heartbeatBuffer);\n            if (DataHeaderFlyweight.HEADER_LENGTH != bytesSent)\n            {\n                shortSends.increment();\n            }\n\n            timeOfLastDataOrHeartbeatNs = nowNs;\n            heartbeatsSent.incrementRelease();\n        }\n\n        return bytesSent;\n    }\n\n    private void cleanBufferTo(final long position)\n    {\n        final long cleanPosition = this.cleanPosition;\n        if (position > cleanPosition)\n        {\n            final UnsafeBuffer dirtyTermBuffer = termBuffers[indexByPosition(cleanPosition, positionBitsToShift)];\n            final int bytesForCleaning = (int)(position - cleanPosition);\n            final int termOffset = (int)cleanPosition & termLengthMask;\n            final int length = Math.min(bytesForCleaning, termBufferLength - termOffset);\n\n            dirtyTermBuffer.setMemory(termOffset + SIZE_OF_LONG, length - SIZE_OF_LONG, (byte)0);\n            dirtyTermBuffer.putLongRelease(termOffset, 0);\n            this.cleanPosition = cleanPosition + length;\n        }\n    }\n\n    private void checkForBlockedPublisher(final long producerPosition, final long senderPosition, final long nowNs)\n    {\n        if (senderPosition == lastSenderPosition && isPossiblyBlocked(producerPosition, senderPosition))\n        {\n            if ((timeOfLastActivityNs + unblockTimeoutNs) - nowNs < 0)\n            {\n                if (LogBufferUnblocker.unblock(termBuffers, metaDataBuffer, senderPosition, termBufferLength))\n                {\n                    unblockedPublications.incrementRelease();\n                }\n            }\n        }\n        else\n        {\n            timeOfLastActivityNs = nowNs;\n            lastSenderPosition = senderPosition;\n        }\n    }\n\n    private boolean isPossiblyBlocked(final long producerPosition, final long consumerPosition)\n    {\n        final int producerTermCount = activeTermCount(metaDataBuffer);\n        final int expectedTermCount = (int)(consumerPosition >> positionBitsToShift);\n\n        if (producerTermCount != expectedTermCount)\n        {\n            return true;\n        }\n\n        return producerPosition > consumerPosition;\n    }\n\n    private boolean spiesFinishedConsuming(final DriverConductor conductor, final long eosPosition)\n    {\n        if (hasSpies)\n        {\n            for (final ReadablePosition spyPosition : spyPositions)\n            {\n                if (spyPosition.getVolatile() < eosPosition)\n                {\n                    return false;\n                }\n            }\n\n            hasSpies = false;\n            conductor.cleanupSpies(this);\n        }\n\n        return true;\n    }\n\n    private long maxSpyPosition(final long senderPosition)\n    {\n        long position = senderPosition;\n\n        for (final ReadablePosition spyPosition : spyPositions)\n        {\n            position = Math.max(position, spyPosition.getVolatile());\n        }\n\n        return position;\n    }\n\n    private void updateConnectedState(final boolean newConnectedState)\n    {\n        if (newConnectedState != isConnected)\n        {\n            LogBufferDescriptor.isConnected(metaDataBuffer, newConnectedState);\n            isConnected = newConnectedState;\n        }\n    }\n\n    private boolean hasSubscribers()\n    {\n        return (spiesSimulateConnection && hasSpies) || (hasReceivers && flowControl.hasRequiredReceivers());\n    }\n\n    private void checkUntetheredSubscriptions(final long nowNs, final DriverConductor conductor)\n    {\n        final ArrayList<UntetheredSubscription> untetheredSubscriptions = this.untetheredSubscriptions;\n        final int untetheredSubscriptionsSize = untetheredSubscriptions.size();\n        if (untetheredSubscriptionsSize > 0)\n        {\n            final long senderPosition = this.senderPosition.getVolatile();\n            final long untetheredWindowLimit = (senderPosition - termWindowLength) + (termWindowLength >> 2);\n\n            for (int lastIndex = untetheredSubscriptionsSize - 1, i = lastIndex; i >= 0; i--)\n            {\n                final UntetheredSubscription untethered = untetheredSubscriptions.get(i);\n                if (UntetheredSubscription.State.ACTIVE == untethered.state)\n                {\n                    if (untethered.position.getVolatile() > untetheredWindowLimit)\n                    {\n                        untethered.timeOfLastUpdateNs = nowNs;\n                    }\n                    else if ((untethered.timeOfLastUpdateNs + untetheredWindowLimitTimeoutNs) - nowNs <= 0)\n                    {\n                        conductor.notifyUnavailableImageLink(registrationId, untethered.subscriptionLink);\n                        untethered.state(UntetheredSubscription.State.LINGER, nowNs, streamId, sessionId);\n                    }\n                }\n                else if (UntetheredSubscription.State.LINGER == untethered.state)\n                {\n                    if ((untethered.timeOfLastUpdateNs + untetheredLingerTimeoutNs) - nowNs <= 0)\n                    {\n                        removeSpyPosition(untethered.position);\n                        if (untethered.subscriptionLink.isRejoin())\n                        {\n                            untethered.state(UntetheredSubscription.State.RESTING, nowNs, streamId, sessionId);\n                        }\n                        else\n                        {\n                            ArrayListUtil.fastUnorderedRemove(untetheredSubscriptions, i, lastIndex--);\n                            untethered.position.close();\n                        }\n                    }\n                }\n                else if (UntetheredSubscription.State.RESTING == untethered.state)\n                {\n                    if ((untethered.timeOfLastUpdateNs + untetheredRestingTimeoutNs) - nowNs <= 0)\n                    {\n                        addSpyPosition(untethered.position);\n                        conductor.notifyAvailableImageLink(\n                            registrationId,\n                            sessionId,\n                            untethered.subscriptionLink,\n                            untethered.position.id(),\n                            senderPosition,\n                            rawLog.fileName(),\n                            CommonContext.IPC_CHANNEL);\n                        untethered.state(UntetheredSubscription.State.ACTIVE, nowNs, streamId, sessionId);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTimeEvent(final long timeNs, final long timeMs, final DriverConductor conductor)\n    {\n        switch (state)\n        {\n            case ACTIVE:\n            {\n                if (LogBufferDescriptor.isPublicationRevoked(metaDataBuffer))\n                {\n                    final long revokedPos = producerPosition();\n                    publisherLimit.setRelease(revokedPos);\n                    endOfStreamPosition(metaDataBuffer, revokedPos);\n                    updateConnectedState(false);\n                    isConnected = false;\n\n                    isEndOfStream = true;\n\n                    conductor.cleanupSpies(this);\n\n                    state = State.LINGER;\n\n                    logRevoke(revokedPos, sessionId(), streamId(), channel());\n                    publicationsRevoked.increment();\n                }\n                else\n                {\n                    checkUntetheredSubscriptions(timeNs, conductor);\n                    updateConnectedState(hasSubscribers());\n                    final long producerPosition = producerPosition();\n                    publisherPos.setRelease(producerPosition);\n                    if (!isExclusive)\n                    {\n                        checkForBlockedPublisher(producerPosition, senderPosition.getVolatile(), timeNs);\n                    }\n                }\n                break;\n            }\n\n            case DRAINING:\n            {\n                final long producerPosition = producerPosition();\n                publisherPos.setRelease(producerPosition);\n                final long senderPosition = this.senderPosition.getVolatile();\n                if (producerPosition > senderPosition)\n                {\n                    if (LogBufferUnblocker.unblock(termBuffers, metaDataBuffer, senderPosition, termBufferLength))\n                    {\n                        unblockedPublications.incrementRelease();\n                        break;\n                    }\n\n                    if (hasReceivers)\n                    {\n                        break;\n                    }\n                }\n                else\n                {\n                    isEndOfStream = true;\n                }\n\n                if (spiesFinishedConsuming(conductor, producerPosition))\n                {\n                    timeOfLastActivityNs = timeNs;\n                    state = State.LINGER;\n                }\n                break;\n            }\n\n            case LINGER:\n                if (0 == refCount &&\n                    (hasReceivedUnicastEos || (timeOfLastActivityNs + lingerTimeoutNs) - timeNs < 0))\n                {\n                    channelEndpoint.decRef();\n                    conductor.cleanupPublication(this);\n                    timeOfLastActivityNs = timeNs;\n                    state = State.DONE;\n                }\n                break;\n\n            case DONE:\n                break;\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean hasReachedEndOfLife()\n    {\n        return hasSenderReleased;\n    }\n\n    /**\n     * Get the response correlation id for the publication.\n     *\n     * @return the response correlation id for the publication.\n     */\n    public long responseCorrelationId()\n    {\n        return responseCorrelationId;\n    }\n\n    void revoke()\n    {\n        LogBufferDescriptor.isPublicationRevoked(metaDataBuffer, true);\n    }\n\n    void decRef()\n    {\n        if (0 == --refCount)\n        {\n            final long producerPosition = producerPosition();\n            publisherLimit.setRelease(producerPosition);\n            endOfStreamPosition(metaDataBuffer, producerPosition);\n\n            if (!LogBufferDescriptor.isPublicationRevoked(metaDataBuffer))\n            {\n                if (senderPosition.getVolatile() >= producerPosition)\n                {\n                    isEndOfStream = true;\n                }\n\n                state = State.DRAINING;\n            }\n        }\n    }\n\n    void incRef()\n    {\n        ++refCount;\n    }\n\n    State state()\n    {\n        return state;\n    }\n\n    void senderRelease()\n    {\n        hasSenderReleased = true;\n    }\n\n    long producerPosition()\n    {\n        final long rawTail = rawTailVolatile(metaDataBuffer);\n        final int termOffset = termOffset(rawTail, termBufferLength);\n\n        return computePosition(termId(rawTail), termOffset, positionBitsToShift, initialTermId);\n    }\n\n    long consumerPosition()\n    {\n        return senderPosition.getVolatile();\n    }\n\n    private void addSpyPosition(final ReadablePosition position)\n    {\n        spyPositions = ArrayUtil.add(spyPositions, position);\n        hasSpies = true;\n    }\n\n    private void removeSpyPosition(final ReadablePosition position)\n    {\n        spyPositions = ArrayUtil.remove(spyPositions, position);\n        hasSpies = 0 != spyPositions.length;\n    }\n\n    private boolean isSendResponseSetupFlag()\n    {\n        return !isResponse && Aeron.NULL_VALUE != responseCorrelationId;\n    }\n\n    private boolean hasGroupSemantics()\n    {\n        return channelEndpoint().udpChannel().hasGroupSemantics();\n    }\n\n    private static void logRevoke(\n        final long revokedPos,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/NetworkPublicationThreadLocals.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport org.agrona.BufferUtil;\n\nimport java.nio.ByteBuffer;\n\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\n\nfinal class NetworkPublicationThreadLocals\n{\n    private final ByteBuffer heartbeatBuffer;\n    private final DataHeaderFlyweight dataHeader;\n    private final ByteBuffer setupBuffer;\n    private final SetupFlyweight setupHeader;\n    private final ByteBuffer rttMeasurementBuffer;\n    private final RttMeasurementFlyweight rttMeasurementHeader;\n\n    NetworkPublicationThreadLocals()\n    {\n        final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(CACHE_LINE_LENGTH * 4, CACHE_LINE_LENGTH);\n\n        byteBuffer.limit(DataHeaderFlyweight.HEADER_LENGTH);\n        heartbeatBuffer = byteBuffer.slice();\n        dataHeader = new DataHeaderFlyweight(heartbeatBuffer);\n\n        int position = CACHE_LINE_LENGTH;\n        byteBuffer.limit(position + SetupFlyweight.HEADER_LENGTH).position(position);\n        setupBuffer = byteBuffer.slice();\n        setupHeader = new SetupFlyweight(setupBuffer);\n\n        position += CACHE_LINE_LENGTH;\n        byteBuffer.limit(position + RttMeasurementFlyweight.HEADER_LENGTH).position(position);\n        rttMeasurementBuffer = byteBuffer.slice();\n        rttMeasurementHeader = new RttMeasurementFlyweight(rttMeasurementBuffer);\n\n        dataHeader\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .flags((byte)DataHeaderFlyweight.BEGIN_AND_END_FLAGS)\n            .headerType(HeaderFlyweight.HDR_TYPE_DATA)\n            .frameLength(0);\n\n        setupHeader\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .headerType(HeaderFlyweight.HDR_TYPE_SETUP)\n            .frameLength(SetupFlyweight.HEADER_LENGTH);\n\n        rttMeasurementHeader\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .headerType(HeaderFlyweight.HDR_TYPE_RTTM)\n            .frameLength(RttMeasurementFlyweight.HEADER_LENGTH);\n    }\n\n    ByteBuffer heartbeatBuffer()\n    {\n        return heartbeatBuffer;\n    }\n\n    DataHeaderFlyweight heartbeatDataHeader()\n    {\n        return dataHeader;\n    }\n\n    ByteBuffer setupBuffer()\n    {\n        return setupBuffer;\n    }\n\n    SetupFlyweight setupHeader()\n    {\n        return setupHeader;\n    }\n\n    ByteBuffer rttMeasurementBuffer()\n    {\n        return rttMeasurementBuffer;\n    }\n\n    RttMeasurementFlyweight rttMeasurementHeader()\n    {\n        return rttMeasurementHeader;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/NetworkSubscriptionLink.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\n\nclass NetworkSubscriptionLink extends SubscriptionLink\n{\n    private final boolean isReliable;\n    private final ReceiveChannelEndpoint channelEndpoint;\n\n    NetworkSubscriptionLink(\n        final long registrationId,\n        final ReceiveChannelEndpoint channelEndpoint,\n        final int streamId,\n        final String channelUri,\n        final AeronClient aeronClient,\n        final SubscriptionParams params)\n    {\n        super(registrationId, streamId, channelUri, aeronClient, params);\n\n        this.isReliable = params.isReliable;\n        this.channelEndpoint = channelEndpoint;\n    }\n\n    boolean isReliable()\n    {\n        return isReliable;\n    }\n\n    ReceiveChannelEndpoint channelEndpoint()\n    {\n        return channelEndpoint;\n    }\n\n    boolean matches(final PublicationImage image)\n    {\n        return image.channelEndpoint() == this.channelEndpoint &&\n            image.streamId() == this.streamId &&\n            isWildcardOrSessionIdMatch(image.sessionId());\n    }\n\n    boolean matches(final ReceiveChannelEndpoint channelEndpoint, final int streamId, final SubscriptionParams params)\n    {\n        return channelEndpoint == this.channelEndpoint &&\n            streamId == this.streamId &&\n            hasSessionId == params.hasSessionId &&\n            isWildcardOrSessionIdMatch(params.sessionId);\n    }\n\n    boolean matches(final ReceiveChannelEndpoint channelEndpoint, final int streamId, final int sessionId)\n    {\n        return channelEndpoint == this.channelEndpoint &&\n            streamId == this.streamId &&\n            isWildcardOrSessionIdMatch(sessionId);\n    }\n\n    boolean supportsMds()\n    {\n        return channelEndpoint.hasDestinationControl();\n    }\n\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/OptimalMulticastDelayGenerator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport java.util.concurrent.ThreadLocalRandom;\n\n/**\n * Feedback delay used for NAKs as well as for some retransmission use cases.\n * <p>\n * Generates delay based on Optimal Multicast Feedback<br>\n * <a target=\"_blank\" href=\"http://tools.ietf.org/html/rfc5401#page-13\">http://tools.ietf.org/html/rfc5401#page-13</a>\n * <p>\n * {@code maxBackoffT} is max interval for delay\n * <p>\n * C version of the code:\n * <pre>{@code\n * double RandomBackoff(double maxBackoffT, double groupSize)\n * {\n *     double lambda = log(groupSize) + 1;\n *     double x = UniformRand(lambda / maxBackoffT) + lambda / (maxBackoffT * (exp(lambda) - 1));\n *     return ((maxBackoffT / lambda) * log(x * (exp(lambda) - 1) * (maxBackoffT / lambda)));\n * }\n * }</pre>\n * where {@code UniformRand(x)} is uniform distribution from {@code 0..max}\n * <p>\n * In this implementation's calculation:\n * <ul>\n *     <li>the {@code groupSize} is a constant (could be configurable as system property)</li>\n *     <li>{@code maxBackoffT} is a constant (could be configurable as system property)</li>\n *     <li>{@code GRTT} is a constant (could be configurable as a system property)</li>\n * </ul>\n * <p>\n * {@code N} (the expected number of feedback messages per RTT) is:<br>\n * {@code N = exp(1.2 * L / (2 * maxBackoffT / GRTT))}\n * <p>\n * <b>Assumptions:</b>\n * <p>\n * {@code maxBackoffT = K * GRTT (K >= 1)}\n * <p>\n * Recommended {@code K}:\n * <ul>\n *     <li>K = 4 for situations where responses come from multiple places (i.e. for NAKs, multiple retransmitters)</li>\n *     <li>K = 6 for situations where responses come from single places (i.e. for NAKs, source only retransmit)</li>\n * </ul>\n */\npublic class OptimalMulticastDelayGenerator implements FeedbackDelayGenerator\n{\n    private final double randMax;\n    private final double baseX;\n    private final double constantT;\n    private final double factorT;\n\n    /**\n     * Create new feedback delay generator based on estimates. Pre-calculating some parameters upfront.\n     *\n     * @param maxBackoffT of the delay interval\n     * @param groupSize   estimate\n     */\n    public OptimalMulticastDelayGenerator(final double maxBackoffT, final double groupSize)\n    {\n        final double lambda = Math.log(groupSize) + 1;\n\n        this.randMax = lambda / maxBackoffT;\n        this.baseX = lambda / (maxBackoffT * (Math.exp(lambda) - 1));\n        this.constantT = maxBackoffT / lambda;\n        this.factorT = (Math.exp(lambda) - 1) * (maxBackoffT / lambda);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long generateDelayNs()\n    {\n        return (long)generateNewOptimalDelay();\n    }\n\n    /**\n     * Generate a new randomized delay value in the units of {@code maxBackoffT}}.\n     *\n     * @return delay in units of {@code maxBackoffT}.\n     */\n    public double generateNewOptimalDelay()\n    {\n        final double x = uniformRandom(randMax) + baseX;\n\n        return constantT * Math.log(x * factorT);\n    }\n\n    /**\n     * Return uniform random value in the range 0 to max.\n     *\n     * @param max of the random range\n     * @return random value\n     */\n    public static double uniformRandom(final double max)\n    {\n        return ThreadLocalRandom.current().nextDouble() * max;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"OptimalMulticastDelayGenerator{\" +\n            \"randMax=\" + randMax +\n            \", baseX=\" + baseX +\n            \", constantT=\" + constantT +\n            \", factorT=\" + factorT +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/PendingSetupMessageFromSource.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\n\nimport java.net.InetSocketAddress;\n\nfinal class PendingSetupMessageFromSource\n{\n    private final int sessionId;\n    private final int streamId;\n    private final int transportIndex;\n    private final boolean periodic;\n    private final ReceiveChannelEndpoint channelEndpoint;\n    private InetSocketAddress controlAddress;\n\n    private long timeOfStatusMessageNs;\n\n    PendingSetupMessageFromSource(\n        final int sessionId,\n        final int streamId,\n        final int transportIndex,\n        final ReceiveChannelEndpoint channelEndpoint,\n        final boolean periodic,\n        final InetSocketAddress controlAddress)\n    {\n        this.sessionId = sessionId;\n        this.streamId = streamId;\n        this.transportIndex = transportIndex;\n        this.channelEndpoint = channelEndpoint;\n        this.periodic = periodic;\n        this.controlAddress = controlAddress;\n    }\n\n    int sessionId()\n    {\n        return sessionId;\n    }\n\n    int streamId()\n    {\n        return streamId;\n    }\n\n    int transportIndex()\n    {\n        return transportIndex;\n    }\n\n    ReceiveChannelEndpoint channelEndpoint()\n    {\n        return channelEndpoint;\n    }\n\n    boolean isPeriodic()\n    {\n        return periodic;\n    }\n\n    boolean shouldElicitSetupMessage()\n    {\n        return channelEndpoint.dispatcher().shouldElicitSetupMessage();\n    }\n\n    void controlAddress(final InetSocketAddress newControlAddress)\n    {\n        this.controlAddress = newControlAddress;\n    }\n\n    InetSocketAddress controlAddress()\n    {\n        return controlAddress;\n    }\n\n    long timeOfStatusMessageNs()\n    {\n        return timeOfStatusMessageNs;\n    }\n\n    void timeOfStatusMessageNs(final long nowNs)\n    {\n        timeOfStatusMessageNs = nowNs;\n    }\n\n    void removeFromDataPacketDispatcher()\n    {\n        channelEndpoint.dispatcher().removePendingSetup(sessionId, streamId);\n    }\n\n    public String toString()\n    {\n        return \"PendingSetupMessageFromSource{\" +\n            \"sessionId=\" + sessionId +\n            \", streamId=\" + streamId +\n            \", transportIndex=\" + transportIndex +\n            \", periodic=\" + periodic +\n            \", channelEndpoint=\" + channelEndpoint +\n            \", controlAddress=\" + controlAddress +\n            \", timeOfStatusMessageNs=\" + timeOfStatusMessageNs +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/PreferredMulticastFlowControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.BitUtil;\n\nimport static java.lang.System.getProperty;\n\n/**\n * Older flow control using preferred, should not be used.\n *\n * @deprecated Use {@link TaggedMulticastFlowControl} instead\n */\n@Deprecated\npublic class PreferredMulticastFlowControl extends TaggedMulticastFlowControl\n{\n    /**\n     * Property name used to set Application Specific Feedback (ASF) in Status Messages to identify preferred receivers.\n     * <p>\n     * Replaced by {@link Configuration#RECEIVER_GROUP_TAG_PROP_NAME}.\n     */\n    public static final String PREFERRED_ASF_PROP_NAME = \"aeron.PreferredMulticastFlowControl.asf\";\n\n    /**\n     * Default Application Specific Feedback (ASF) value.\n     */\n    public static final String PREFERRED_ASF_DEFAULT = \"FFFFFFFFFFFFFFFF\";\n\n    /**\n     * Default constructor.\n     */\n    public PreferredMulticastFlowControl()\n    {\n    }\n\n    static final String PREFERRED_ASF = getProperty(PREFERRED_ASF_PROP_NAME, PREFERRED_ASF_DEFAULT);\n    static final byte[] PREFERRED_ASF_BYTES = BitUtil.fromHex(PREFERRED_ASF);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/PreferredMulticastFlowControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\n\n/**\n *\n * This has been replaced by {@link TaggedMulticastFlowControlSupplier}.\n *\n * @deprecated Use {@link TaggedMulticastFlowControlSupplier} instead.\n */\n@Deprecated\npublic class PreferredMulticastFlowControlSupplier implements FlowControlSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public PreferredMulticastFlowControlSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public FlowControl newInstance(final UdpChannel udpChannel, final int streamId, final long registrationId)\n    {\n        return new PreferredMulticastFlowControl();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/PublicationImage.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.media.ImageConnection;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.ReceiveDestinationTransport;\nimport io.aeron.driver.reports.LossReport;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.logbuffer.TermRebuilder;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.Position;\nimport org.agrona.concurrent.status.ReadablePosition;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.net.InetSocketAddress;\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\nimport static io.aeron.ErrorCode.IMAGE_REJECTED;\nimport static io.aeron.driver.LossDetector.lossFound;\nimport static io.aeron.driver.LossDetector.rebuildOffset;\nimport static io.aeron.driver.status.SystemCounterDescriptor.FLOW_CONTROL_OVER_RUNS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.FLOW_CONTROL_UNDER_RUNS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.HEARTBEATS_RECEIVED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.LOSS_GAP_FILLS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.NAK_MESSAGES_SENT;\nimport static io.aeron.driver.status.SystemCounterDescriptor.PUBLICATION_IMAGES_REVOKED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.STATUS_MESSAGES_SENT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_ACTIVE_TRANSPORT_COUNT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computeTermIdFromPosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.indexByPosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.indexByTerm;\nimport static io.aeron.logbuffer.LogBufferDescriptor.isPublicationRevoked;\nimport static io.aeron.logbuffer.TermGapFiller.tryFillGap;\nimport static io.aeron.protocol.SetupFlyweight.SEND_RESPONSE_SETUP_FLAG;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\nclass PublicationImagePadding1\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nclass PublicationImageConductorFields extends PublicationImagePadding1\n{\n    long cleanPosition;\n    final ArrayList<UntetheredSubscription> untetheredSubscriptions = new ArrayList<>();\n    ReadablePosition[] subscriberPositions;\n    int lossReportTermId;\n    int lossReportTermOffset;\n    int lossReportLength;\n    LossReport lossReport;\n    LossReport.ReportEntry reportEntry;\n    volatile Integer responseSessionId = null;\n}\n\nclass PublicationImagePadding2 extends PublicationImageConductorFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\nclass PublicationImageReceiverFields extends PublicationImagePadding2\n{\n    boolean isEndOfStream = false;\n    boolean isSendingEosSm = false;\n    long timeOfLastPacketNs;\n    ImageConnection[] imageConnections = new ImageConnection[1];\n    String rejectionReason = null;\n}\n\nclass PublicationImagePadding3 extends PublicationImageReceiverFields\n{\n    byte p128, p129, p130, p131, p132, p133, p134, p135, p136, p137, p138, p139, p140, p142, p143, p144;\n    byte p145, p146, p147, p148, p149, p150, p151, p152, p153, p154, p155, p156, p157, p158, p159, p160;\n    byte p161, p162, p163, p164, p165, p166, p167, p168, p169, p170, p171, p172, p173, p174, p175, p176;\n    byte p177, p178, p179, p180, p181, p182, p183, p184, p185, p186, p187, p189, p190, p191, p192, p193;\n}\n\n/**\n * State maintained for active sessionIds within a channel for receiver processing.\n */\npublic final class PublicationImage\n    extends PublicationImagePadding3\n    implements LossHandler, DriverManagedResource, Subscribable\n{\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        INIT, ACTIVE, DRAINING, LINGER, DONE\n    }\n\n    // expected minimum number of SMs with EOS bit set sent during draining.\n    private static final long SM_EOS_MULTIPLE = 5;\n\n    private static final VarHandle BEGIN_SM_CHANGE_VH;\n    private static final VarHandle END_SM_CHANGE_VH;\n    private static final VarHandle BEGIN_LOSS_CHANGE_VH;\n    private static final VarHandle END_LOSS_CHANGE_VH;\n\n    static\n    {\n        try\n        {\n            final MethodHandles.Lookup lookup = MethodHandles.lookup();\n            BEGIN_SM_CHANGE_VH = lookup\n                .findVarHandle(PublicationImage.class, \"beginSmChange\", long.class);\n            END_SM_CHANGE_VH = lookup\n                .findVarHandle(PublicationImage.class, \"endSmChange\", long.class);\n            BEGIN_LOSS_CHANGE_VH = lookup\n                .findVarHandle(PublicationImage.class, \"beginLossChange\", long.class);\n            END_LOSS_CHANGE_VH = lookup\n                .findVarHandle(PublicationImage.class, \"endLossChange\", long.class);\n        }\n        catch (final ReflectiveOperationException ex)\n        {\n            throw new ExceptionInInitializerError(ex);\n        }\n    }\n\n    private volatile long beginSmChange;\n    private volatile long endSmChange;\n    private long nextSmPosition;\n    private int nextSmReceiverWindowLength;\n\n    private long lastSmChangeNumber;\n    private long lastSmPosition;\n    private long lastOverrunThreshold;\n    private long nextSmDeadlineNs;\n    private final long smTimeoutNs;\n    private final long maxReceiverWindowLength;\n\n    volatile long beginLossChange;\n    volatile long endLossChange;\n    int lossTermId;\n    int lossTermOffset;\n    int lossLength;\n    private long lastLossChangeNumber;\n\n    private volatile long timeOfLastStateChangeNs;\n\n    private final long correlationId;\n    private final long imageLivenessTimeoutNs;\n    private final long untetheredWindowLimitTimeoutNs;\n    private final long untetheredLingerTimeoutNs;\n    private final long untetheredRestingTimeoutNs;\n    private final int sessionId;\n    private final int streamId;\n    private final int positionBitsToShift;\n    private final int termLengthMask;\n    private final int initialTermId;\n    private final short flags;\n    private final boolean isReliable;\n    private boolean smEnabled = true;\n\n    private boolean isRebuilding = true;\n    private volatile boolean isReceiverReleaseTriggered = false;\n    private volatile boolean hasReceiverReleased = false;\n    private volatile State state = State.INIT;\n\n    private final CachedNanoClock cachedNanoClock;\n    private final ReceiveChannelEndpoint channelEndpoint;\n    private final UnsafeBuffer[] termBuffers;\n    private final Position hwmPosition;\n    private final LossDetector lossDetector;\n    private final CongestionControl congestionControl;\n    private final ErrorHandler errorHandler;\n    private final Position rebuildPosition;\n    private final String sourceIdentity;\n    private final AtomicCounter heartbeatsReceived;\n    private final AtomicCounter statusMessagesSent;\n    private final AtomicCounter nakMessagesSent;\n    private final AtomicCounter receiverNaksSent;\n    private final AtomicCounter flowControlUnderRuns;\n    private final AtomicCounter flowControlOverRuns;\n    private final AtomicCounter lossGapFills;\n    private final AtomicCounter publicationImagesRevoked;\n    private final EpochClock epochClock;\n    private final NanoClock nanoClock;\n    private final RawLog rawLog;\n\n    PublicationImage(\n        final long correlationId,\n        final MediaDriver.Context ctx,\n        final ReceiveChannelEndpoint channelEndpoint,\n        final int transportIndex,\n        final InetSocketAddress controlAddress,\n        final int sessionId,\n        final int streamId,\n        final int initialTermId,\n        final int activeTermId,\n        final int termOffset,\n        final short flags,\n        final boolean isReliable,\n        final long untetheredWindowLimitTimeoutNs,\n        final long untetheredLingerTimeoutNs,\n        final long untetheredRestingTimeoutNs,\n        final RawLog rawLog,\n        final FeedbackDelayGenerator lossFeedbackDelayGenerator,\n        final ArrayList<SubscriberPosition> subscriberPositions,\n        final Position hwmPosition,\n        final Position rebuildPosition,\n        final AtomicCounter receiverNaksSent,\n        final String sourceIdentity,\n        final CongestionControl congestionControl)\n    {\n        this.correlationId = correlationId;\n        this.imageLivenessTimeoutNs = ctx.imageLivenessTimeoutNs();\n        this.receiverNaksSent = receiverNaksSent;\n        this.untetheredWindowLimitTimeoutNs = untetheredWindowLimitTimeoutNs;\n        this.untetheredLingerTimeoutNs = untetheredLingerTimeoutNs;\n        this.untetheredRestingTimeoutNs = untetheredRestingTimeoutNs;\n        this.smTimeoutNs = ctx.statusMessageTimeoutNs();\n        this.channelEndpoint = channelEndpoint;\n        this.sessionId = sessionId;\n        this.streamId = streamId;\n        this.flags = flags;\n        this.rawLog = rawLog;\n        this.hwmPosition = hwmPosition;\n        this.rebuildPosition = rebuildPosition;\n        this.sourceIdentity = sourceIdentity;\n        this.initialTermId = initialTermId;\n        this.congestionControl = congestionControl;\n        this.errorHandler = ctx.errorHandler();\n        this.lossReport = ctx.lossReport();\n\n        this.nanoClock = ctx.nanoClock();\n        this.epochClock = ctx.epochClock();\n        this.cachedNanoClock = ctx.receiverCachedNanoClock();\n\n        final long nowNs = cachedNanoClock.nanoTime();\n        this.timeOfLastStateChangeNs = nowNs;\n        this.timeOfLastPacketNs = nowNs;\n\n        this.subscriberPositions = positionArray(subscriberPositions, nowNs);\n        this.isReliable = isReliable;\n\n        final SystemCounters systemCounters = ctx.systemCounters();\n        heartbeatsReceived = systemCounters.get(HEARTBEATS_RECEIVED);\n        statusMessagesSent = systemCounters.get(STATUS_MESSAGES_SENT);\n        nakMessagesSent = systemCounters.get(NAK_MESSAGES_SENT);\n        flowControlUnderRuns = systemCounters.get(FLOW_CONTROL_UNDER_RUNS);\n        flowControlOverRuns = systemCounters.get(FLOW_CONTROL_OVER_RUNS);\n        lossGapFills = systemCounters.get(LOSS_GAP_FILLS);\n        publicationImagesRevoked = systemCounters.get(PUBLICATION_IMAGES_REVOKED);\n\n        imageConnections = ArrayUtil.ensureCapacity(imageConnections, transportIndex + 1);\n        imageConnections[transportIndex] = new ImageConnection(nowNs, controlAddress);\n\n        termBuffers = rawLog.termBuffers();\n        lossDetector = new LossDetector(lossFeedbackDelayGenerator, this);\n\n        final int termLength = rawLog.termLength();\n        termLengthMask = termLength - 1;\n        positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n\n        nextSmReceiverWindowLength = congestionControl.initialWindowLength();\n        maxReceiverWindowLength = congestionControl.maxWindowLength();\n        final long position = computePosition(activeTermId, termOffset, positionBitsToShift, initialTermId);\n        nextSmPosition = position;\n        lastSmPosition = position;\n        lastOverrunThreshold = position + (termLength >> 1);\n        cleanPosition = position;\n\n        hwmPosition.setRelease(position);\n        rebuildPosition.setRelease(position);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean free()\n    {\n        return rawLog.free();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(errorHandler, hwmPosition);\n        CloseHelper.close(errorHandler, rebuildPosition);\n        CloseHelper.close(errorHandler, receiverNaksSent);\n        CloseHelper.closeAll(errorHandler, subscriberPositions);\n\n        for (int i = 0, size = untetheredSubscriptions.size(); i < size; i++)\n        {\n            final UntetheredSubscription untetheredSubscription = untetheredSubscriptions.get(i);\n            if (UntetheredSubscription.State.RESTING == untetheredSubscription.state)\n            {\n                CloseHelper.close(errorHandler, untetheredSubscription.position);\n            }\n        }\n\n        CloseHelper.close(errorHandler, congestionControl);\n    }\n\n    /**\n     * The correlation id assigned by the driver when created.\n     *\n     * @return the correlation id assigned by the driver when created.\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * The session id of the channel from a publisher.\n     *\n     * @return session id of the channel from a publisher.\n     */\n    public int sessionId()\n    {\n        return sessionId;\n    }\n\n    /**\n     * The stream id of this image within a channel.\n     *\n     * @return stream id of this image within a channel.\n     */\n    public int streamId()\n    {\n        return streamId;\n    }\n\n    /**\n     * Get the string representation of the channel URI.\n     *\n     * @return the string representation of the channel URI.\n     */\n    public String channel()\n    {\n        return channelEndpoint.originalUriString();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long subscribableRegistrationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void addSubscriber(\n        final SubscriptionLink subscriptionLink, final ReadablePosition subscriberPosition, final long nowNs)\n    {\n        subscriberPositions = ArrayUtil.add(subscriberPositions, subscriberPosition);\n        if (!subscriptionLink.isTether())\n        {\n            untetheredSubscriptions.add(new UntetheredSubscription(subscriptionLink, subscriberPosition, nowNs));\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void removeSubscriber(final SubscriptionLink subscriptionLink, final ReadablePosition subscriberPosition)\n    {\n        subscriberPositions = ArrayUtil.remove(subscriberPositions, subscriberPosition);\n        subscriberPosition.close();\n\n        if (!subscriptionLink.isTether())\n        {\n            for (int lastIndex = untetheredSubscriptions.size() - 1, i = lastIndex; i >= 0; i--)\n            {\n                if (untetheredSubscriptions.get(i).subscriptionLink == subscriptionLink)\n                {\n                    ArrayListUtil.fastUnorderedRemove(untetheredSubscriptions, i, lastIndex);\n                    break;\n                }\n            }\n        }\n\n        if (0 == subscriberPositions.length)\n        {\n            isRebuilding = false;\n        }\n    }\n\n    /**\n     * Called from the {@link LossDetector} when gap is detected by the {@link DriverConductor} thread.\n     * <p>\n     * {@inheritDoc}\n     */\n    public void onGapDetected(final int termId, final int termOffset, final int length)\n    {\n        final long changeNumber = (long)BEGIN_LOSS_CHANGE_VH.get(this) + 1;\n\n        BEGIN_LOSS_CHANGE_VH.setRelease(this, changeNumber);\n        VarHandle.storeStoreFence();\n\n        lossTermId = termId;\n        lossTermOffset = termOffset;\n        lossLength = length;\n\n        END_LOSS_CHANGE_VH.setRelease(this, changeNumber);\n\n        final int lossReportEndOffset;\n        if (termId != lossReportTermId ||\n            termOffset >= (lossReportEndOffset = (lossReportTermOffset + lossReportLength)))\n        {\n            reportLoss(termId, termOffset, length, length);\n        }\n        else if (termOffset + length > lossReportEndOffset)\n        {\n            reportLoss(termId, termOffset, length, length - (lossReportEndOffset - termOffset));\n        }\n    }\n\n    /**\n     * Source identity for this stream.\n     *\n     * @return source identity for a source address.\n     * @see Configuration#sourceIdentity(InetSocketAddress)\n     */\n    String sourceIdentity()\n    {\n        return sourceIdentity;\n    }\n\n    /**\n     * Return the {@link ReceiveChannelEndpoint} that the image is attached to.\n     *\n     * @return {@link ReceiveChannelEndpoint} that the image is attached to.\n     */\n    ReceiveChannelEndpoint channelEndpoint()\n    {\n        return channelEndpoint;\n    }\n\n    /**\n     * Remove this image from the {@link DataPacketDispatcher} so it will process no further packets from the network.\n     * Called from the {@link Receiver} thread.\n     */\n    void removeFromDispatcher()\n    {\n        channelEndpoint.dispatcher().removePublicationImage(this);\n    }\n\n    /**\n     * Get the {@link RawLog} the back this image.\n     *\n     * @return the {@link RawLog} the back this image.\n     */\n    RawLog rawLog()\n    {\n        return rawLog;\n    }\n\n    /**\n     * Activate this image from the {@link Receiver}.\n     */\n    void activate()\n    {\n        timeOfLastStateChangeNs = cachedNanoClock.nanoTime();\n        state(State.ACTIVE);\n    }\n\n    /**\n     * Deactivate image by setting state to {@link State#DRAINING} if currently {@link State#ACTIVE} from the\n     * {@link Receiver}.\n     */\n    void deactivate()\n    {\n        if (State.ACTIVE == state)\n        {\n            final long nowNs = cachedNanoClock.nanoTime();\n\n            isRebuilding = false;\n            timeOfLastStateChangeNs = nowNs;\n\n            if (!isSendingEosSm)\n            {\n                isSendingEosSm = !isEndOfStream || rebuildPosition.getVolatile() == hwmPosition.get();\n            }\n\n            if (isSendingEosSm)\n            {\n                nextSmDeadlineNs = nowNs - 1;\n            }\n\n            state(State.DRAINING);\n        }\n    }\n\n    void receiverRelease()\n    {\n        hasReceiverReleased = true;\n    }\n\n    void addDestination(final int transportIndex, final ReceiveDestinationTransport transport)\n    {\n        imageConnections = ArrayUtil.ensureCapacity(imageConnections, transportIndex + 1);\n\n        if (transport.isMulticast())\n        {\n            imageConnections[transportIndex] = new ImageConnection(\n                cachedNanoClock.nanoTime(), transport.udpChannel().remoteControl());\n        }\n        else if (transport.hasExplicitControl())\n        {\n            imageConnections[transportIndex] = new ImageConnection(\n                cachedNanoClock.nanoTime(), transport.explicitControlAddress());\n        }\n    }\n\n    void removeDestination(final int transportIndex)\n    {\n        imageConnections[transportIndex] = null;\n        updateActiveTransportCount();\n    }\n\n    void addDestinationConnectionIfUnknown(final int transportIndex, final InetSocketAddress remoteAddress)\n    {\n        trackConnection(transportIndex, remoteAddress, cachedNanoClock.nanoTime());\n    }\n\n    int trackRebuild(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (isRebuilding)\n        {\n            final long hwmPosition = this.hwmPosition.getVolatile();\n            long minSubscriberPosition = Long.MAX_VALUE;\n            long maxSubscriberPosition = 0;\n\n            for (final ReadablePosition subscriberPosition : subscriberPositions)\n            {\n                final long position = subscriberPosition.getVolatile();\n                minSubscriberPosition = Math.min(minSubscriberPosition, position);\n                maxSubscriberPosition = Math.max(maxSubscriberPosition, position);\n            }\n\n            final long rebuildPosition = Math.max(this.rebuildPosition.get(), maxSubscriberPosition);\n            final long scanOutcome = lossDetector.scan(\n                termBuffers[indexByPosition(rebuildPosition, positionBitsToShift)],\n                rebuildPosition,\n                hwmPosition,\n                nowNs,\n                termLengthMask,\n                positionBitsToShift,\n                initialTermId);\n\n            final int rebuildTermOffset = (int)(rebuildPosition & termLengthMask);\n            final long newRebuildPosition = (rebuildPosition - rebuildTermOffset) + rebuildOffset(scanOutcome);\n            this.rebuildPosition.proposeMaxRelease(newRebuildPosition);\n\n            final long ccOutcome = congestionControl.onTrackRebuild(\n                nowNs,\n                minSubscriberPosition,\n                nextSmPosition,\n                hwmPosition,\n                rebuildPosition,\n                newRebuildPosition,\n                lossFound(scanOutcome));\n\n            final int windowLength = CongestionControl.receiverWindowLength(ccOutcome);\n            final int threshold = CongestionControl.threshold(windowLength);\n\n            if (CongestionControl.shouldForceStatusMessage(ccOutcome) ||\n                (minSubscriberPosition > (nextSmPosition + threshold)) ||\n                windowLength != nextSmReceiverWindowLength)\n            {\n                cleanBufferTo(minSubscriberPosition - (termLengthMask + 1));\n                scheduleStatusMessage(minSubscriberPosition, windowLength);\n                workCount += 1;\n            }\n        }\n\n        return workCount;\n    }\n\n    /**\n     * Insert frame into term buffer from the {@link Receiver}.\n     *\n     * @param termId         for the data packet to insert into the appropriate term.\n     * @param termOffset     for the start of the packet in the term.\n     * @param buffer         for the data packet to insert into the appropriate term.\n     * @param length         of the data packet.\n     * @param transportIndex from which the packet came.\n     * @param srcAddress     from which the packet came.\n     * @return number of bytes applied as a result of this insertion.\n     */\n    int insertPacket(\n        final int termId,\n        final int termOffset,\n        final UnsafeBuffer buffer,\n        final int length,\n        final int transportIndex,\n        final InetSocketAddress srcAddress)\n    {\n        if (null != rejectionReason)\n        {\n            return 0;\n        }\n\n        final boolean isHeartbeat = DataHeaderFlyweight.isHeartbeat(buffer, length);\n        final long packetPosition = computePosition(termId, termOffset, positionBitsToShift, initialTermId);\n        final long proposedPosition = isHeartbeat ? packetPosition : packetPosition + length;\n\n        if (!isFlowControlOverRun(proposedPosition))\n        {\n            if (isHeartbeat)\n            {\n                final long potentialWindowBottom = lastSmPosition - (termLengthMask + 1);\n                final long publicationWindowBottom = potentialWindowBottom < 0 ? 0 : potentialWindowBottom;\n\n                if (packetPosition >= publicationWindowBottom)\n                {\n                    final long nowNs = cachedNanoClock.nanoTime();\n                    timeOfLastPacketNs = nowNs;\n                    final ImageConnection imageConnection = trackConnection(transportIndex, srcAddress, nowNs);\n\n                    if (DataHeaderFlyweight.isEndOfStream(buffer))\n                    {\n                        imageConnection.eosPosition = packetPosition;\n                        imageConnection.isEos = true;\n\n                        if (!this.isEndOfStream && isAllConnectedEos())\n                        {\n                            final long eosPosition = findEosPosition();\n\n                            if (DataHeaderFlyweight.isRevoked(buffer))\n                            {\n                                isPublicationRevoked(rawLog.metaData(), true);\n\n                                logRevoke(eosPosition, sessionId(), streamId(), channel());\n                                publicationImagesRevoked.increment();\n                            }\n\n                            LogBufferDescriptor.endOfStreamPosition(rawLog.metaData(), eosPosition);\n                            this.isEndOfStream = true;\n                        }\n                    }\n\n                    hwmPosition.proposeMaxRelease(proposedPosition);\n                    heartbeatsReceived.incrementRelease();\n                }\n                else\n                {\n                    flowControlUnderRuns.incrementRelease();\n                }\n            }\n            else if (!isFlowControlUnderRun(packetPosition))\n            {\n                final long nowNs = cachedNanoClock.nanoTime();\n                timeOfLastPacketNs = nowNs;\n                trackConnection(transportIndex, srcAddress, nowNs);\n\n                final UnsafeBuffer termBuffer = termBuffers[indexByPosition(packetPosition, positionBitsToShift)];\n                TermRebuilder.insert(termBuffer, termOffset, buffer, length);\n\n                hwmPosition.proposeMaxRelease(proposedPosition);\n            }\n            else if (packetPosition >= (lastSmPosition - maxReceiverWindowLength))\n            {\n                trackConnection(transportIndex, srcAddress, cachedNanoClock.nanoTime());\n            }\n        }\n\n        return length;\n    }\n\n    private static void logRevoke(\n        final long revokedPos,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n    }\n\n    /**\n     * To be called from the {@link Receiver} to see if image should be dispatched to.\n     *\n     * @param nowNs current time to check against for activity.\n     * @return true if the image should be retained otherwise false.\n     */\n    boolean isConnected(final long nowNs)\n    {\n        return ((timeOfLastPacketNs + imageLivenessTimeoutNs) - nowNs >= 0) && !isReceiverReleaseTriggered;\n    }\n\n    boolean isEndOfStream()\n    {\n        return isEndOfStream;\n    }\n\n    /**\n     * Check for EOS from publication and switch to {@link State#DRAINING} if at end of stream position.\n     *\n     * @param nowNs current time of use.\n     */\n    void checkEosForDrainTransition(final long nowNs)\n    {\n        if (!isSendingEosSm)\n        {\n            if (isEndOfStream && rebuildPosition.getVolatile() == hwmPosition.get() && State.ACTIVE == state)\n            {\n                isRebuilding = false;\n                timeOfLastStateChangeNs = nowNs;\n\n                isSendingEosSm = true;\n                nextSmDeadlineNs = nowNs - 1;\n                state(State.DRAINING);\n            }\n        }\n    }\n\n    /**\n     * Called from the {@link Receiver} to send any pending Status Messages.\n     *\n     * @param nowNs current time.\n     * @return number of work items processed.\n     */\n    int sendPendingStatusMessage(final long nowNs)\n    {\n        int workCount = 0;\n        final long changeNumber = (long)END_SM_CHANGE_VH.getAcquire(this);\n        final boolean hasSmTimedOut = smEnabled && nextSmDeadlineNs - nowNs < 0;\n\n        if (null != rejectionReason)\n        {\n            if (hasSmTimedOut)\n            {\n                channelEndpoint.sendErrorFrame(\n                    imageConnections, sessionId, streamId, IMAGE_REJECTED.value(), rejectionReason);\n\n                nextSmDeadlineNs = nowNs + smTimeoutNs;\n                workCount++;\n            }\n\n            return workCount;\n        }\n\n        final Integer responseSessionId;\n        if (hasSmTimedOut && null != (responseSessionId = this.responseSessionId))\n        {\n            channelEndpoint.sendResponseSetup(imageConnections, sessionId, streamId, responseSessionId);\n        }\n\n        if (changeNumber != lastSmChangeNumber || hasSmTimedOut)\n        {\n            final long smPosition = nextSmPosition;\n            final int receiverWindowLength = nextSmReceiverWindowLength;\n\n            VarHandle.loadLoadFence();\n\n            if (changeNumber == (long)BEGIN_SM_CHANGE_VH.getAcquire(this))\n            {\n                final int termId = computeTermIdFromPosition(smPosition, positionBitsToShift, initialTermId);\n                final int termOffset = (int)smPosition & termLengthMask;\n                final int termLength = termLengthMask + 1;\n                final short flags = isSendingEosSm ? StatusMessageFlyweight.END_OF_STREAM_FLAG : 0;\n\n                channelEndpoint.sendStatusMessage(\n                    imageConnections, sessionId, streamId, termId, termOffset, receiverWindowLength, flags);\n\n                statusMessagesSent.incrementRelease();\n\n                lastSmPosition = smPosition;\n                lastOverrunThreshold = smPosition + (termLength >> 1);\n                lastSmChangeNumber = changeNumber;\n                nextSmDeadlineNs = nowNs + smTimeoutNs;\n\n                updateActiveTransportCount();\n            }\n\n            workCount = 1;\n        }\n\n        return workCount;\n    }\n\n    /**\n     * Called from the {@link Receiver} thread to processing any pending loss of packets.\n     *\n     * @return number of work items processed.\n     */\n    int processPendingLoss()\n    {\n        if (isPublicationRevoked(rawLog.metaData()))\n        {\n            return 0;\n        }\n\n        int workCount = 0;\n        final long changeNumber = (long)END_LOSS_CHANGE_VH.getAcquire(this);\n\n        if (changeNumber != lastLossChangeNumber)\n        {\n            final int termId = lossTermId;\n            final int termOffset = lossTermOffset;\n            final int length = lossLength;\n\n            VarHandle.loadLoadFence();\n\n            if (changeNumber == (long)BEGIN_LOSS_CHANGE_VH.getAcquire(this))\n            {\n                if (isReliable)\n                {\n                    channelEndpoint.sendNakMessage(imageConnections, sessionId, streamId, termId, termOffset, length);\n                    // FIXME: Increment only if sent successfully\n                    nakMessagesSent.incrementRelease();\n                    receiverNaksSent.incrementRelease();\n                }\n                else\n                {\n                    final UnsafeBuffer termBuffer = termBuffers[indexByTerm(initialTermId, termId)];\n                    if (tryFillGap(rawLog.metaData(), termBuffer, termId, termOffset, length))\n                    {\n                        lossGapFills.incrementRelease();\n                    }\n                }\n\n                lastLossChangeNumber = changeNumber;\n            }\n\n            workCount = 1;\n        }\n\n        return workCount;\n    }\n\n    /**\n     * Called from the {@link Receiver} thread to check for initiating an RTT measurement.\n     *\n     * @param nowNs in nanoseconds\n     * @return number of work items processed.\n     */\n    int initiateAnyRttMeasurements(final long nowNs)\n    {\n        int workCount = 0;\n\n        if (congestionControl.shouldMeasureRtt(nowNs))\n        {\n            final long preciseTimeNs = nanoClock.nanoTime();\n\n            channelEndpoint.sendRttMeasurement(imageConnections, sessionId, streamId, preciseTimeNs, 0, true);\n            congestionControl.onRttMeasurementSent(preciseTimeNs);\n\n            workCount = 1;\n        }\n\n        return workCount;\n    }\n\n    /**\n     * Called from the {@link Receiver} upon receiving an RTT Measurement that is a reply.\n     *\n     * @param header         of the measurement message.\n     * @param transportIndex that the RTT Measurement came in on.\n     * @param srcAddress     from the sender requesting the measurement\n     */\n    void onRttMeasurement(\n        final RttMeasurementFlyweight header, final int transportIndex, final InetSocketAddress srcAddress)\n    {\n        final long nowNs = nanoClock.nanoTime();\n        final long rttInNs = nowNs - header.echoTimestampNs() - header.receptionDelta();\n\n        congestionControl.onRttMeasurement(nowNs, rttInNs, srcAddress);\n    }\n\n    boolean isAcceptingSubscriptions()\n    {\n        if (subscriberPositions.length > 0)\n        {\n            final State state = this.state;\n            return State.INIT == state || State.ACTIVE == state || (State.DRAINING == state && !isDrained());\n        }\n        return false;\n    }\n\n    long joinPosition()\n    {\n        long position = rebuildPosition.get();\n\n        for (final ReadablePosition subscriberPosition : subscriberPositions)\n        {\n            position = Math.min(subscriberPosition.getVolatile(), position);\n        }\n\n        return position;\n    }\n\n    boolean hasSendResponseSetup()\n    {\n        return 0 != (SEND_RESPONSE_SETUP_FLAG & flags);\n    }\n\n    void responseSessionId(final Integer responseSessionId)\n    {\n        this.responseSessionId = responseSessionId;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @SuppressWarnings(\"fallthrough\")\n    public void onTimeEvent(final long timeNs, final long timesMs, final DriverConductor conductor)\n    {\n        switch (state)\n        {\n            case ACTIVE:\n                if (isPublicationRevoked(rawLog.metaData()))\n                {\n                    state(State.DRAINING);\n                }\n                else\n                {\n                    checkUntetheredSubscriptions(timeNs, conductor);\n                }\n                break;\n\n            case DRAINING:\n                if ((isDrained() && ((timeOfLastStateChangeNs + (SM_EOS_MULTIPLE * smTimeoutNs)) - timeNs < 0)) ||\n                    isPublicationRevoked(rawLog.metaData()))\n                {\n                    if (isPublicationRevoked(rawLog.metaData()))\n                    {\n                        isRebuilding = false;\n                        isSendingEosSm = true;\n\n                        nextSmDeadlineNs = timeNs - 1;\n                    }\n\n                    conductor.transitionToLinger(this);\n\n                    isReceiverReleaseTriggered = true;\n                    channelEndpoint.decRefImages();\n                    conductor.tryCloseReceiveChannelEndpoint(channelEndpoint);\n\n                    timeOfLastStateChangeNs = timeNs;\n                    state(State.LINGER);\n                }\n                break;\n\n            case LINGER:\n                if (hasNoSubscribers() || ((timeOfLastStateChangeNs + imageLivenessTimeoutNs) - timeNs < 0))\n                {\n                    conductor.cleanupImage(this);\n                    timeOfLastStateChangeNs = timeNs;\n                    state(State.DONE);\n                }\n                break;\n\n            case DONE:\n            case INIT:\n                break;\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean hasReachedEndOfLife()\n    {\n        return hasReceiverReleased && State.DONE == state;\n    }\n\n    void reject(final String reason)\n    {\n        rejectionReason = reason;\n    }\n\n    void stopStatusMessagesIfNotActive()\n    {\n        if (State.ACTIVE != state)\n        {\n            smEnabled = false;\n        }\n    }\n\n    private void reportLoss(final int termId, final int termOffset, final int length, final int bytesLost)\n    {\n        if (null != reportEntry)\n        {\n            reportEntry.recordObservation(bytesLost, epochClock.time());\n        }\n        else if (null != lossReport)\n        {\n            reportEntry = lossReport.createEntry(\n                bytesLost, epochClock.time(), sessionId, streamId, channel(), sourceIdentity);\n\n            if (null == reportEntry)\n            {\n                lossReport = null;\n            }\n        }\n\n        lossReportTermId = termId;\n        lossReportTermOffset = termOffset;\n        lossReportLength = length;\n    }\n\n    private void state(final State state)\n    {\n        this.state = state;\n    }\n\n    private boolean isDrained()\n    {\n        final long rebuildPosition = this.rebuildPosition.get();\n\n        for (final ReadablePosition subscriberPosition : subscriberPositions)\n        {\n            if (subscriberPosition.getVolatile() < rebuildPosition)\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private boolean hasNoSubscribers()\n    {\n        return subscriberPositions.length == 0;\n    }\n\n    private boolean isFlowControlUnderRun(final long packetPosition)\n    {\n        final boolean isFlowControlUnderRun = packetPosition < lastSmPosition;\n\n        if (isFlowControlUnderRun)\n        {\n            flowControlUnderRuns.incrementRelease();\n        }\n\n        return isFlowControlUnderRun;\n    }\n\n    private boolean isFlowControlOverRun(final long proposedPosition)\n    {\n        final boolean isFlowControlOverRun = proposedPosition > lastOverrunThreshold;\n\n        if (isFlowControlOverRun)\n        {\n            flowControlOverRuns.incrementRelease();\n        }\n\n        return isFlowControlOverRun;\n    }\n\n    private void cleanBufferTo(final long position)\n    {\n        final long cleanPosition = this.cleanPosition;\n        if (position > cleanPosition)\n        {\n            final int bytesForCleaning = (int)(position - cleanPosition);\n            final UnsafeBuffer dirtyTermBuffer = termBuffers[indexByPosition(cleanPosition, positionBitsToShift)];\n            final int termOffset = (int)cleanPosition & termLengthMask;\n            final int length = Math.min(bytesForCleaning, dirtyTermBuffer.capacity() - termOffset);\n\n            dirtyTermBuffer.setMemory(termOffset, length - SIZE_OF_LONG, (byte)0);\n            dirtyTermBuffer.putLongRelease(termOffset + (length - SIZE_OF_LONG), 0);\n            this.cleanPosition = cleanPosition + length;\n        }\n    }\n\n    private ImageConnection trackConnection(\n        final int transportIndex, final InetSocketAddress srcAddress, final long nowNs)\n    {\n        ImageConnection[] imageConnections = this.imageConnections;\n        if (transportIndex >= imageConnections.length)\n        {\n            this.imageConnections = imageConnections = Arrays.copyOf(imageConnections, transportIndex + 1);\n        }\n\n        ImageConnection imageConnection = imageConnections[transportIndex];\n        if (null == imageConnection)\n        {\n            imageConnection = new ImageConnection(nowNs, srcAddress);\n            imageConnections[transportIndex] = imageConnection;\n        }\n\n        imageConnection.timeOfLastActivityNs = nowNs;\n        imageConnection.timeOfLastFrameNs = nowNs;\n        return imageConnection;\n    }\n\n    private boolean isAllConnectedEos()\n    {\n        for (int i = 0, length = imageConnections.length; i < length; i++)\n        {\n            final ImageConnection imageConnection = imageConnections[i];\n\n            if (null != imageConnection && !imageConnection.isEos)\n            {\n                return false;\n            }\n            else if (null == imageConnection && channelEndpoint.hasDestination(i))\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private long findEosPosition()\n    {\n        long eosPosition = 0;\n\n        for (final ImageConnection imageConnection : imageConnections)\n        {\n            if (null != imageConnection && imageConnection.eosPosition > eosPosition)\n            {\n                eosPosition = imageConnection.eosPosition;\n            }\n        }\n\n        return eosPosition;\n    }\n\n    private void scheduleStatusMessage(final long smPosition, final int receiverWindowLength)\n    {\n        final long changeNumber = (long)BEGIN_SM_CHANGE_VH.get(this) + 1;\n\n        BEGIN_SM_CHANGE_VH.setRelease(this, changeNumber);\n        VarHandle.storeStoreFence();\n\n        nextSmPosition = smPosition;\n        nextSmReceiverWindowLength = receiverWindowLength;\n\n        END_SM_CHANGE_VH.setRelease(this, changeNumber);\n    }\n\n    private void checkUntetheredSubscriptions(final long nowNs, final DriverConductor conductor)\n    {\n        final ArrayList<UntetheredSubscription> untetheredSubscriptions = this.untetheredSubscriptions;\n        final int untetheredSubscriptionsSize = untetheredSubscriptions.size();\n        if (untetheredSubscriptionsSize > 0)\n        {\n            final long untetheredWindowLimit = untetheredWindowLimit();\n\n            for (int lastIndex = untetheredSubscriptionsSize - 1, i = lastIndex; i >= 0; i--)\n            {\n                final UntetheredSubscription untethered = untetheredSubscriptions.get(i);\n                if (UntetheredSubscription.State.ACTIVE == untethered.state)\n                {\n                    if (untethered.position.getVolatile() > untetheredWindowLimit)\n                    {\n                        untethered.timeOfLastUpdateNs = nowNs;\n                    }\n                    else if ((untethered.timeOfLastUpdateNs + untetheredWindowLimitTimeoutNs) - nowNs <= 0)\n                    {\n                        conductor.notifyUnavailableImageLink(correlationId, untethered.subscriptionLink);\n                        untethered.state(UntetheredSubscription.State.LINGER, nowNs, streamId, sessionId);\n                    }\n                }\n                else if (UntetheredSubscription.State.LINGER == untethered.state)\n                {\n                    if ((untethered.timeOfLastUpdateNs + untetheredLingerTimeoutNs) - nowNs <= 0)\n                    {\n                        subscriberPositions = ArrayUtil.remove(subscriberPositions, untethered.position);\n                        if (untethered.subscriptionLink.isRejoin())\n                        {\n                            untethered.state(UntetheredSubscription.State.RESTING, nowNs, streamId, sessionId);\n                        }\n                        else\n                        {\n                            ArrayListUtil.fastUnorderedRemove(untetheredSubscriptions, i, lastIndex--);\n                            untethered.position.close();\n                        }\n                    }\n                }\n                else if (UntetheredSubscription.State.RESTING == untethered.state)\n                {\n                    if ((untethered.timeOfLastUpdateNs + untetheredRestingTimeoutNs) - nowNs <= 0)\n                    {\n                        final long joinPosition = joinPosition();\n                        subscriberPositions = ArrayUtil.add(subscriberPositions, untethered.position);\n                        conductor.notifyAvailableImageLink(\n                            correlationId,\n                            sessionId,\n                            untethered.subscriptionLink,\n                            untethered.position.id(),\n                            joinPosition,\n                            rawLog.fileName(),\n                            sourceIdentity);\n                        untethered.state(UntetheredSubscription.State.ACTIVE, nowNs, streamId, sessionId);\n                    }\n                }\n            }\n        }\n    }\n\n    private long untetheredWindowLimit()\n    {\n        long maxConsumerPosition = 0;\n\n        for (final ReadablePosition subscriberPosition : subscriberPositions)\n        {\n            final long position = subscriberPosition.getVolatile();\n            if (position > maxConsumerPosition)\n            {\n                maxConsumerPosition = position;\n            }\n        }\n\n        final int windowLength = nextSmReceiverWindowLength;\n\n        return (maxConsumerPosition - windowLength) + (windowLength >> 2);\n    }\n\n    private void updateActiveTransportCount()\n    {\n        final long nowNs = cachedNanoClock.nanoTime();\n        int activeTransportCount = 0;\n\n        for (final ImageConnection imageConnection : imageConnections)\n        {\n            if (null != imageConnection && ((imageConnection.timeOfLastFrameNs + imageLivenessTimeoutNs) - nowNs > 0))\n            {\n                activeTransportCount++;\n            }\n        }\n\n        final UnsafeBuffer metaDataBuffer = rawLog.metaData();\n        if (metaDataBuffer.getInt(LOG_ACTIVE_TRANSPORT_COUNT) != activeTransportCount)\n        {\n            LogBufferDescriptor.activeTransportCount(metaDataBuffer, activeTransportCount);\n        }\n    }\n\n    private ReadablePosition[] positionArray(final ArrayList<SubscriberPosition> subscriberPositions, final long nowNs)\n    {\n        final int size = subscriberPositions.size();\n        final ReadablePosition[] positions = new ReadablePosition[subscriberPositions.size()];\n\n        for (int i = 0; i < size; i++)\n        {\n            final SubscriberPosition subscriberPosition = subscriberPositions.get(i);\n            positions[i] = subscriberPosition.position();\n\n            if (!subscriberPosition.subscription().isTether())\n            {\n                untetheredSubscriptions.add(new UntetheredSubscription(\n                    subscriberPosition.subscription(), subscriberPosition.position(), nowNs));\n            }\n        }\n\n        return positions;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"PublicationImage{\" +\n            \"state=\" + state +\n            \", sourceIdentity='\" + sourceIdentity + '\\'' +\n            \", streamId=\" + streamId +\n            \", sessionId=\" + sessionId +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/PublicationLink.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\n/**\n * Tracks an Aeron client interest registration in a {@link NetworkPublication} or {@link IpcPublication}.\n */\nfinal class PublicationLink implements DriverManagedResource\n{\n    private final long registrationId;\n    private final Object publication;\n    private final AeronClient client;\n    private boolean reachedEndOfLife = false;\n\n    PublicationLink(final long registrationId, final AeronClient client, final NetworkPublication publication)\n    {\n        this.registrationId = registrationId;\n        this.client = client;\n\n        this.publication = publication;\n        publication.incRef();\n    }\n\n    PublicationLink(final long registrationId, final AeronClient client, final IpcPublication publication)\n    {\n        this.registrationId = registrationId;\n        this.client = client;\n\n        this.publication = publication;\n        publication.incRef();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (publication instanceof NetworkPublication)\n        {\n            ((NetworkPublication)publication).decRef();\n        }\n        else\n        {\n            ((IpcPublication)publication).decRef();\n        }\n    }\n\n    void revoke()\n    {\n        if (publication instanceof NetworkPublication)\n        {\n            ((NetworkPublication)publication).revoke();\n        }\n        else\n        {\n            ((IpcPublication)publication).revoke();\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTimeEvent(final long timeNs, final long timeMs, final DriverConductor conductor)\n    {\n        if (client.hasTimedOut())\n        {\n            reachedEndOfLife = true;\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean hasReachedEndOfLife()\n    {\n        return reachedEndOfLife;\n    }\n\n    long registrationId()\n    {\n        return registrationId;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/PublicationParams.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.exceptions.InvalidChannelException;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.BitUtil;\nimport org.agrona.SystemUtil;\n\nimport static io.aeron.ChannelUri.INVALID_TAG;\nimport static io.aeron.CommonContext.CONTROL_MODE_RESPONSE;\nimport static io.aeron.CommonContext.EOS_PARAM_NAME;\nimport static io.aeron.CommonContext.INITIAL_TERM_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.LINGER_PARAM_NAME;\nimport static io.aeron.CommonContext.MAX_RESEND_PARAM_NAME;\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_PARAM_NAME;\nimport static io.aeron.CommonContext.MTU_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.PROTOTYPE_CORRELATION_ID;\nimport static io.aeron.CommonContext.PUBLICATION_WINDOW_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.RESPONSE_CORRELATION_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.SESSION_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.SPARSE_PARAM_NAME;\nimport static io.aeron.CommonContext.SPIES_SIMULATE_CONNECTION_PARAM_NAME;\nimport static io.aeron.CommonContext.STREAM_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.TERM_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.TERM_LENGTH_PARAM_NAME;\nimport static io.aeron.CommonContext.TERM_OFFSET_PARAM_NAME;\nimport static io.aeron.CommonContext.UNTETHERED_LINGER_TIMEOUT_PARAM_NAME;\nimport static io.aeron.CommonContext.UNTETHERED_RESTING_TIMEOUT_PARAM_NAME;\nimport static io.aeron.CommonContext.UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME;\n\nfinal class PublicationParams\n{\n    static final long PROTOTYPE_VALUE_CORRELATION_ID = -2L;\n\n    long lingerTimeoutNs;\n    long entityTag = ChannelUri.INVALID_TAG;\n    long untetheredWindowLimitTimeoutNs;\n    long untetheredLingerTimeoutNs;\n    long untetheredRestingTimeoutNs;\n    long responseCorrelationId = Aeron.NULL_VALUE;\n    int termLength;\n    int mtuLength;\n    int publicationWindowLength;\n    int initialTermId;\n    int termId;\n    int termOffset;\n    int sessionId;\n    int streamId;\n    int maxResend;\n    boolean hasPosition;\n    boolean isSessionIdTagged;\n    boolean signalEos;\n    boolean isSparse;\n    boolean spiesSimulateConnection;\n    boolean isResponse;\n\n    static PublicationParams getPublicationParams(\n        final ChannelUri channelUri,\n        final MediaDriver.Context ctx,\n        final DriverConductor driverConductor,\n        final int streamId,\n        final String canonicalForm)\n    {\n        final PublicationParams params = new PublicationParams();\n\n        params.termLength = channelUri.isIpc() ? ctx.ipcTermBufferLength() : ctx.publicationTermBufferLength();\n        params.mtuLength = channelUri.isIpc() ? ctx.ipcMtuLength() : ctx.mtuLength();\n\n        params.getStreamId(channelUri, streamId);\n        params.getEntityTag(channelUri, driverConductor);\n        params.getSessionId(channelUri, driverConductor, streamId, canonicalForm);\n        params.getTermBufferLength(channelUri);\n        params.getMtuLength(channelUri);\n        params.getPublicationWindowLength(channelUri, ctx);\n        params.getLingerTimeoutNs(channelUri, ctx);\n        params.getEos(channelUri);\n        params.getSparse(channelUri, ctx);\n        params.getSpiesSimulateConnection(channelUri, ctx);\n        params.getUntetheredWindowLimitTimeout(channelUri, ctx);\n        params.getUntetheredLingerTimeout(channelUri, ctx);\n        params.getUntetheredRestingTimeout(channelUri, ctx);\n        params.getMaxResend(channelUri, ctx);\n\n        int count = 0;\n\n        final String initialTermIdStr = channelUri.get(INITIAL_TERM_ID_PARAM_NAME);\n        count = initialTermIdStr != null ? count + 1 : count;\n\n        final String termIdStr = channelUri.get(TERM_ID_PARAM_NAME);\n        count = termIdStr != null ? count + 1 : count;\n\n        final String termOffsetStr = channelUri.get(TERM_OFFSET_PARAM_NAME);\n        count = termOffsetStr != null ? count + 1 : count;\n\n        if (count > 0)\n        {\n            if (count < 3)\n            {\n                throw new InvalidChannelException(\"params must be used as a complete set: \" +\n                    INITIAL_TERM_ID_PARAM_NAME + \" \" + TERM_ID_PARAM_NAME + \" \" + TERM_OFFSET_PARAM_NAME + \" channel=\" +\n                    channelUri);\n            }\n\n            params.initialTermId = parseInt(initialTermIdStr, INITIAL_TERM_ID_PARAM_NAME);\n            params.termId = parseInt(termIdStr, TERM_ID_PARAM_NAME);\n            params.termOffset = parseInt(termOffsetStr, TERM_OFFSET_PARAM_NAME);\n\n            if (params.termOffset > params.termLength)\n            {\n                throw new InvalidChannelException(\n                    TERM_OFFSET_PARAM_NAME + \"=\" + params.termOffset + \" > \" +\n                    TERM_LENGTH_PARAM_NAME + \"=\" + params.termLength + \": channel=\" + channelUri);\n            }\n\n            if (params.termOffset < 0 || params.termOffset > LogBufferDescriptor.TERM_MAX_LENGTH)\n            {\n                throw new InvalidChannelException(\n                    TERM_OFFSET_PARAM_NAME + \"=\" + params.termOffset + \" out of range: channel=\" + channelUri);\n            }\n\n            if ((params.termOffset & (FrameDescriptor.FRAME_ALIGNMENT - 1)) != 0)\n            {\n                throw new InvalidChannelException(\n                    TERM_OFFSET_PARAM_NAME + \"=\" + params.termOffset +\n                    \" must be a multiple of FRAME_ALIGNMENT: channel=\" + channelUri);\n            }\n\n            if (params.termId - params.initialTermId < 0)\n            {\n                throw new InvalidChannelException(\n                    \"difference greater than 2^31 - 1: \" + INITIAL_TERM_ID_PARAM_NAME + \"=\" +\n                    params.initialTermId + \" when \" + TERM_ID_PARAM_NAME + \"=\" + params.termId + \" channel=\" +\n                    channelUri);\n            }\n\n            params.hasPosition = true;\n        }\n        else\n        {\n            params.initialTermId = BitUtil.generateRandomisedId();\n            params.termId = params.initialTermId;\n            params.termOffset = 0;\n        }\n\n        params.isResponse = CONTROL_MODE_RESPONSE.equals(channelUri.get(MDC_CONTROL_MODE_PARAM_NAME));\n        params.responseCorrelationId = parseResponseCorrelationId(channelUri);\n\n        return params;\n    }\n\n    private void getPublicationWindowLength(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        final String pubWindowParam = channelUri.get(PUBLICATION_WINDOW_LENGTH_PARAM_NAME);\n        if (null != pubWindowParam)\n        {\n            final long pubWindow = SystemUtil.parseSize(PUBLICATION_WINDOW_LENGTH_PARAM_NAME, pubWindowParam);\n            if (pubWindow < mtuLength)\n            {\n                throw new InvalidChannelException(\n                    PUBLICATION_WINDOW_LENGTH_PARAM_NAME + \"=\" + pubWindow + \" cannot be less than the \" +\n                    MTU_LENGTH_PARAM_NAME + \"=\" + mtuLength);\n            }\n\n            if (pubWindow > (termLength >> 1))\n            {\n                throw new InvalidChannelException(\n                    PUBLICATION_WINDOW_LENGTH_PARAM_NAME + \"=\" + pubWindow + \" must not exceed half the \" +\n                    TERM_LENGTH_PARAM_NAME + \"=\" + termLength);\n            }\n            publicationWindowLength = (int)pubWindow;\n        }\n        else\n        {\n            publicationWindowLength = Configuration.producerWindowLength(\n                termLength,\n                channelUri.isIpc() ? ctx.ipcPublicationTermWindowLength() : ctx.publicationTermWindowLength());\n        }\n    }\n\n    private void getStreamId(final ChannelUri channelUri, final int streamId)\n    {\n        final String streamIdParam = channelUri.get(STREAM_ID_PARAM_NAME);\n        if (null != streamIdParam)\n        {\n            final int configuredStreamId = parseInt(streamIdParam, STREAM_ID_PARAM_NAME);\n            if (streamId != configuredStreamId)\n            {\n                throw new InvalidChannelException(\n                    STREAM_ID_PARAM_NAME + \"=\" + configuredStreamId + \" does not match provided streamId=\" + streamId);\n            }\n        }\n        this.streamId = streamId;\n    }\n\n    private void getEntityTag(final ChannelUri channelUri, final DriverConductor driverConductor)\n    {\n        final String tagParam = channelUri.entityTag();\n        if (null != tagParam)\n        {\n            this.entityTag = parseEntityTag(tagParam, driverConductor, channelUri);\n        }\n    }\n\n    private void getTermBufferLength(final ChannelUri channelUri)\n    {\n        final String termLengthParam = channelUri.get(TERM_LENGTH_PARAM_NAME);\n        if (null != termLengthParam)\n        {\n            final int termLength = (int)SystemUtil.parseSize(TERM_LENGTH_PARAM_NAME, termLengthParam);\n            LogBufferDescriptor.checkTermLength(termLength);\n            validateTermLength(this, termLength, channelUri);\n            this.termLength = termLength;\n        }\n    }\n\n    private void getMtuLength(final ChannelUri channelUri)\n    {\n        final String mtuParam = channelUri.get(MTU_LENGTH_PARAM_NAME);\n        if (null != mtuParam)\n        {\n            final int mtuLength = (int)SystemUtil.parseSize(MTU_LENGTH_PARAM_NAME, mtuParam);\n            Configuration.validateMtuLength(mtuLength);\n            validateMtuLength(this, mtuLength, channelUri);\n            this.mtuLength = mtuLength;\n        }\n\n        final int maxMessageLength = FrameDescriptor.computeMaxMessageLength(termLength);\n        if (mtuLength > maxMessageLength)\n        {\n            throw new InvalidChannelException(\"MTU greater than max message length for term length: mtu=\" +\n                mtuLength + \" maxMessageLength=\" + maxMessageLength + \" termLength=\" + termLength + \" channel=\" +\n                channelUri);\n        }\n    }\n\n    static void validateTermLength(\n        final PublicationParams params, final int explicitTermLength, final ChannelUri channelUri)\n    {\n        if (params.isSessionIdTagged && explicitTermLength != params.termLength)\n        {\n            throw new InvalidChannelException(\n                TERM_LENGTH_PARAM_NAME + \"=\" + explicitTermLength + \" does not match session-id tag value: channel=\" +\n                channelUri);\n        }\n    }\n\n    static void validateMtuLength(\n        final PublicationParams params, final int explicitMtuLength, final ChannelUri channelUri)\n    {\n        if (params.isSessionIdTagged && explicitMtuLength != params.mtuLength)\n        {\n            throw new InvalidChannelException(\n                MTU_LENGTH_PARAM_NAME + \"=\" + explicitMtuLength + \" does not match session-id tag value: channel=\" +\n                channelUri);\n        }\n    }\n\n    private static String formatMatchError(\n        final String paramName,\n        final String existingValue,\n        final String newValue,\n        final String existingChannelUri,\n        final String newChannelUri)\n    {\n        return \"existing publication has different '\" + paramName + \"': existing=\" +\n            existingValue + \" requested=\" + newValue + \" existingChannel=\" + existingChannelUri +\n            \" channel=\" + newChannelUri;\n    }\n\n    static void confirmMatch(\n        final ChannelUri channelUri,\n        final PublicationParams params,\n        final RawLog rawLog,\n        final int existingSessionId,\n        final String existingChannel,\n        final int existingInitialTermId,\n        final int existingTermId,\n        final int existingTermOffset)\n    {\n        final int mtuLength = LogBufferDescriptor.mtuLength(rawLog.metaData());\n        if (channelUri.containsKey(MTU_LENGTH_PARAM_NAME) && mtuLength != params.mtuLength)\n        {\n            throw new IllegalStateException(formatMatchError(\n                MTU_LENGTH_PARAM_NAME,\n                String.valueOf(mtuLength),\n                String.valueOf(params.mtuLength),\n                existingChannel,\n                channelUri.toString()));\n        }\n\n        if (channelUri.containsKey(TERM_LENGTH_PARAM_NAME) && rawLog.termLength() != params.termLength)\n        {\n            throw new IllegalStateException(formatMatchError(\n                TERM_LENGTH_PARAM_NAME,\n                String.valueOf(rawLog.termLength()),\n                String.valueOf(params.termLength),\n                existingChannel,\n                channelUri.toString()));\n        }\n\n        if (channelUri.containsKey(SESSION_ID_PARAM_NAME) && params.sessionId != existingSessionId)\n        {\n            throw new IllegalStateException(formatMatchError(\n                SESSION_ID_PARAM_NAME,\n                String.valueOf(existingSessionId),\n                String.valueOf(params.sessionId),\n                existingChannel,\n                channelUri.toString()));\n        }\n\n        if (channelUri.containsKey(INITIAL_TERM_ID_PARAM_NAME) && params.initialTermId != existingInitialTermId)\n        {\n            throw new IllegalStateException(formatMatchError(\n                INITIAL_TERM_ID_PARAM_NAME,\n                String.valueOf(existingInitialTermId),\n                String.valueOf(params.initialTermId),\n                existingChannel,\n                channelUri.toString()));\n        }\n\n        if (channelUri.containsKey(TERM_ID_PARAM_NAME) && params.termId != existingTermId)\n        {\n            throw new IllegalStateException(formatMatchError(\n                TERM_ID_PARAM_NAME,\n                String.valueOf(existingTermId),\n                String.valueOf(params.termId),\n                existingChannel,\n                channelUri.toString()));\n        }\n\n        if (channelUri.containsKey(TERM_OFFSET_PARAM_NAME) && params.termOffset != existingTermOffset)\n        {\n            throw new IllegalStateException(formatMatchError(\n                TERM_OFFSET_PARAM_NAME,\n                String.valueOf(existingTermOffset),\n                String.valueOf(params.termOffset),\n                existingChannel,\n                channelUri.toString()));\n        }\n    }\n\n    static void validateSpiesSimulateConnection(\n        final PublicationParams params,\n        final boolean existingSpiesSimulateConnection,\n        final String channel,\n        final String existingChannel)\n    {\n        if (params.spiesSimulateConnection != existingSpiesSimulateConnection)\n        {\n            throw new IllegalStateException(\"existing publication has different spiesSimulateConnection: existing=\" +\n                existingSpiesSimulateConnection + \" requested=\" + params.spiesSimulateConnection +\n                \" existingChannel=\" + existingChannel + \" channel=\" + channel);\n        }\n    }\n\n    static void validateMtuForSndbuf(\n        final PublicationParams params,\n        final int channelSocketSndbufLength,\n        final MediaDriver.Context ctx,\n        final String channel,\n        final String existingChannel)\n    {\n        if (0 != channelSocketSndbufLength && params.mtuLength > channelSocketSndbufLength)\n        {\n            throw new IllegalStateException(\n                \"MTU greater than SO_SNDBUF for channel: mtu=\" + params.mtuLength +\n                \" so-sndbuf=\" + channelSocketSndbufLength +\n                (null == existingChannel ? \"\" : (\" existingChannel=\" + existingChannel)) +\n                \" channel=\" + channel);\n        }\n        else if (0 == channelSocketSndbufLength && params.mtuLength > ctx.osDefaultSocketSndbufLength())\n        {\n            throw new IllegalStateException(\n                \"MTU greater than SO_SNDBUF for channel: mtu=\" + params.mtuLength +\n                \" so-sndbuf=\" + ctx.osDefaultSocketSndbufLength() + \" (OS default)\" +\n                (null == existingChannel ? \"\" : (\" existingChannel=\" + existingChannel)) +\n                \" channel=\" + channel);\n        }\n    }\n\n    private void getLingerTimeoutNs(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        final String lingerParam = channelUri.get(LINGER_PARAM_NAME);\n        if (null != lingerParam)\n        {\n            lingerTimeoutNs = SystemUtil.parseDuration(LINGER_PARAM_NAME, lingerParam);\n        }\n        else\n        {\n            lingerTimeoutNs = ctx.publicationLingerTimeoutNs();\n        }\n    }\n\n    private void getSessionId(\n        final ChannelUri channelUri,\n        final DriverConductor driverConductor,\n        final int streamId,\n        final String canonicalForm)\n    {\n        final String sessionIdStr = channelUri.get(SESSION_ID_PARAM_NAME);\n        if (null != sessionIdStr)\n        {\n            isSessionIdTagged = ChannelUri.isTagged(sessionIdStr);\n            if (isSessionIdTagged)\n            {\n                final long tag;\n                try\n                {\n                    tag = ChannelUri.getTag(sessionIdStr);\n                }\n                catch (final RuntimeException ex)\n                {\n                    throw new InvalidChannelException(\n                        SESSION_ID_PARAM_NAME + \"=\" + sessionIdStr + \" has an invalid tag: channel=\" + channelUri, ex);\n                }\n\n                if (channelUri.isIpc())\n                {\n                    final IpcPublication publication = driverConductor.findIpcPublicationByTag(tag);\n                    if (null == publication)\n                    {\n                        throw new InvalidChannelException(\n                            SESSION_ID_PARAM_NAME + \"=\" + sessionIdStr + \" must reference an IPC publication: \" +\n                            \"channel=\" + channelUri);\n                    }\n\n                    sessionId = publication.sessionId();\n                    mtuLength = publication.mtuLength();\n                    termLength = publication.termBufferLength();\n                }\n                else\n                {\n                    final NetworkPublication publication = driverConductor.findNetworkPublicationByTag(tag);\n                    if (null == publication)\n                    {\n                        throw new InvalidChannelException(\n                            SESSION_ID_PARAM_NAME + \"=\" + sessionIdStr + \" must reference a network publication: \" +\n                            \"channel=\" + channelUri);\n                    }\n\n                    sessionId = publication.sessionId();\n                    mtuLength = publication.mtuLength();\n                    termLength = publication.termBufferLength();\n                }\n            }\n            else\n            {\n                sessionId = parseInt(sessionIdStr, SESSION_ID_PARAM_NAME);\n            }\n        }\n        else\n        {\n            sessionId = driverConductor.nextAvailableSessionId(streamId, canonicalForm);\n        }\n    }\n\n    private void getEos(final ChannelUri channelUri)\n    {\n        final String eosStr = channelUri.get(EOS_PARAM_NAME);\n        signalEos = null == eosStr || \"true\".equals(eosStr);\n    }\n\n    private void getSparse(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        final String sparseStr = channelUri.get(SPARSE_PARAM_NAME);\n        isSparse = null != sparseStr ? \"true\".equals(sparseStr) : ctx.termBufferSparseFile();\n    }\n\n    private void getSpiesSimulateConnection(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        final String sscStr = channelUri.get(SPIES_SIMULATE_CONNECTION_PARAM_NAME);\n        spiesSimulateConnection = null != sscStr ? \"true\".equals(sscStr) : ctx.spiesSimulateConnection();\n    }\n\n    private long getTimeoutNs(final ChannelUri channelUri, final String paramName, final long defaultValue)\n    {\n        final String timeoutString = channelUri.get(paramName);\n        return null != timeoutString ? SystemUtil.parseDuration(paramName, timeoutString) : defaultValue;\n    }\n\n    private void getUntetheredWindowLimitTimeout(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        untetheredWindowLimitTimeoutNs = getTimeoutNs(\n            channelUri, UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME, ctx.untetheredWindowLimitTimeoutNs());\n    }\n\n    private void getUntetheredLingerTimeout(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        untetheredLingerTimeoutNs =\n            getTimeoutNs(channelUri, UNTETHERED_LINGER_TIMEOUT_PARAM_NAME, ctx.untetheredLingerTimeoutNs());\n        if (Aeron.NULL_VALUE == untetheredLingerTimeoutNs)\n        {\n            untetheredLingerTimeoutNs = untetheredWindowLimitTimeoutNs;\n        }\n    }\n\n    private void getUntetheredRestingTimeout(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        untetheredRestingTimeoutNs = getTimeoutNs(\n            channelUri, UNTETHERED_RESTING_TIMEOUT_PARAM_NAME, ctx.untetheredRestingTimeoutNs());\n    }\n\n    private void getMaxResend(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        final String maxRetransmtsString = channelUri.get(MAX_RESEND_PARAM_NAME);\n\n        if (maxRetransmtsString == null)\n        {\n            maxResend = ctx.maxResend();\n        }\n        else\n        {\n            maxResend = parseInt(maxRetransmtsString, MAX_RESEND_PARAM_NAME);\n\n            if (maxResend <= 0 || maxResend > Configuration.MAX_RESEND_MAX)\n            {\n                throw new InvalidChannelException(\n                    \"invalid \" + MAX_RESEND_PARAM_NAME + \"=\" + maxResend +\n                    \", must be > 0 and <= \" + Configuration.MAX_RESEND_MAX);\n            }\n        }\n    }\n\n    private static int parseInt(final String value, final String paramName)\n    {\n        try\n        {\n            return Integer.parseInt(value);\n        }\n        catch (final NumberFormatException ex)\n        {\n            throw new InvalidChannelException(\n                \"invalid \" + paramName + \", must be a number\", ex);\n        }\n    }\n\n    private static long parseLong(final String value, final String paramName)\n    {\n        try\n        {\n            return Long.parseLong(value);\n        }\n        catch (final NumberFormatException ex)\n        {\n            throw new InvalidChannelException(\n                \"invalid \" + paramName + \", must be a number\", ex);\n        }\n    }\n\n    private static long parseResponseCorrelationId(final ChannelUri channelUri)\n    {\n        final String idStr = channelUri.get(RESPONSE_CORRELATION_ID_PARAM_NAME, \"-1\");\n\n        if (PROTOTYPE_CORRELATION_ID.equals(idStr))\n        {\n            return PROTOTYPE_VALUE_CORRELATION_ID;\n        }\n\n        try\n        {\n            final long value = Long.parseLong(idStr);\n\n            if (value < -1)\n            {\n                throw new NumberFormatException(\"responseCorrelationId must be positive\");\n            }\n\n            return value;\n        }\n        catch (final NumberFormatException ex)\n        {\n            throw new InvalidChannelException(\"invalid \" + RESPONSE_CORRELATION_ID_PARAM_NAME +\n                \", must be a number greater than or equal to -1, or '\" + PROTOTYPE_CORRELATION_ID + \"'\", ex);\n        }\n    }\n\n    private static long parseEntityTag(\n        final String tagParam, final DriverConductor driverConductor, final ChannelUri channelUri)\n    {\n        final long entityTag;\n        try\n        {\n            entityTag = Long.parseLong(tagParam);\n        }\n        catch (final NumberFormatException ex)\n        {\n            throw new InvalidChannelException(\"invalid entity tag, must be a number\", ex);\n        }\n\n        if (INVALID_TAG == entityTag)\n        {\n            throw new InvalidChannelException(INVALID_TAG + \" tag is reserved: channel=\" + channelUri);\n        }\n\n        final NetworkPublication networkPublication = driverConductor.findNetworkPublicationByTag(entityTag);\n        if (null != networkPublication)\n        {\n            throw new InvalidChannelException(entityTag + \" entityTag already in use: existingChannel=\" +\n                networkPublication.channel() + \" channel=\" + channelUri);\n        }\n        final IpcPublication ipcPublication = driverConductor.findIpcPublicationByTag(entityTag);\n        if (null != ipcPublication)\n        {\n            throw new InvalidChannelException(entityTag + \" entityTag already in use: existingChannel=\" +\n                ipcPublication.channel() + \" channel=\" + channelUri);\n        }\n\n        return entityTag;\n    }\n\n    public String toString()\n    {\n        return \"PublicationParams{\" +\n            \"lingerTimeoutNs=\" + lingerTimeoutNs +\n            \", entityTag=\" + entityTag +\n            \", termLength=\" + termLength +\n            \", mtuLength=\" + mtuLength +\n            \", initialTermId=\" + initialTermId +\n            \", termId=\" + termId +\n            \", termOffset=\" + termOffset +\n            \", streamId=\" + streamId +\n            \", sessionId=\" + sessionId +\n            \", hasPosition=\" + hasPosition +\n            \", isSessionIdTagged=\" + isSessionIdTagged +\n            \", isSparse=\" + isSparse +\n            \", signalEos=\" + signalEos +\n            \", spiesSimulateConnection=\" + spiesSimulateConnection +\n            \", maxResend=\" + maxResend +\n            \", publicationWindowLength=\" + publicationWindowLength +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ReceiveChannelEndpointSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Supplier of channel endpoints which extend {@link ReceiveChannelEndpoint} to add specialised behaviour for the\n * receiver.\n */\n@FunctionalInterface\npublic interface ReceiveChannelEndpointSupplier\n{\n    /**\n     * A new instance of a specialised {@link ReceiveChannelEndpoint}.\n     *\n     * @param udpChannel      on which the receiver is listening.\n     * @param dispatcher      for dispatching packets to publication images.\n     * @param statusIndicator for the channel.\n     * @param context         for the configuration of the driver.\n     * @return new instance of a specialised {@link ReceiveChannelEndpoint}.\n     */\n    ReceiveChannelEndpoint newInstance(\n        UdpChannel udpChannel,\n        DataPacketDispatcher dispatcher,\n        AtomicCounter statusIndicator,\n        MediaDriver.Context context);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/Receiver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.DataTransportPoller;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.ReceiveDestinationTransport;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.OneToOneConcurrentArrayQueue;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.net.InetSocketAddress;\nimport java.util.ArrayList;\n\nimport static io.aeron.driver.Configuration.PENDING_SETUPS_TIMEOUT_NS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.BYTES_RECEIVED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.RESOLUTION_CHANGES;\n\n/**\n * Agent that receives messages streams and rebuilds {@link PublicationImage}s, plus iterates over them sending status\n * and control messages back to the {@link Sender}.\n */\npublic final class Receiver implements Agent\n{\n    private static final PublicationImage[] EMPTY_IMAGES = new PublicationImage[0];\n\n    private final long reResolutionCheckIntervalNs;\n    private long reResolutionDeadlineNs;\n    private final DataTransportPoller dataTransportPoller;\n    private final OneToOneConcurrentArrayQueue<Runnable> commandQueue;\n    private final AtomicCounter totalBytesReceived;\n    private final AtomicCounter resolutionChanges;\n    private final NanoClock nanoClock;\n    private final CachedNanoClock cachedNanoClock;\n    private PublicationImage[] publicationImages = EMPTY_IMAGES;\n    private final ArrayList<PendingSetupMessageFromSource> pendingSetupMessages = new ArrayList<>();\n    private final DriverConductorProxy conductorProxy;\n    private final DutyCycleTracker dutyCycleTracker;\n\n    Receiver(final MediaDriver.Context ctx)\n    {\n        dataTransportPoller = ctx.dataTransportPoller();\n        commandQueue = ctx.receiverCommandQueue();\n        totalBytesReceived = ctx.systemCounters().get(BYTES_RECEIVED);\n        resolutionChanges = ctx.systemCounters().get(RESOLUTION_CHANGES);\n        nanoClock = ctx.nanoClock();\n        cachedNanoClock = ctx.receiverCachedNanoClock();\n        conductorProxy = ctx.driverConductorProxy();\n        reResolutionCheckIntervalNs = ctx.reResolutionCheckIntervalNs();\n        dutyCycleTracker = ctx.receiverDutyCycleTracker();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        final long nowNs = nanoClock.nanoTime();\n        cachedNanoClock.update(nowNs);\n        dutyCycleTracker.update(nowNs);\n        reResolutionDeadlineNs = nowNs + reResolutionCheckIntervalNs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        dataTransportPoller.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"receiver\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        final long nowNs = nanoClock.nanoTime();\n        cachedNanoClock.update(nowNs);\n        dutyCycleTracker.measureAndUpdate(nowNs);\n\n        int workCount = commandQueue.drain(CommandProxy.RUN_TASK, Configuration.COMMAND_DRAIN_LIMIT);\n\n        final int bytesReceived = dataTransportPoller.pollTransports();\n        totalBytesReceived.getAndAddOrdered(bytesReceived);\n\n        final PublicationImage[] publicationImages = this.publicationImages;\n        for (int lastIndex = publicationImages.length - 1, i = lastIndex; i >= 0; i--)\n        {\n            final PublicationImage image = publicationImages[i];\n            if (image.isConnected(nowNs))\n            {\n                image.checkEosForDrainTransition(nowNs);\n\n                workCount += image.sendPendingStatusMessage(nowNs);\n                workCount += image.processPendingLoss();\n                workCount += image.initiateAnyRttMeasurements(nowNs);\n            }\n            else\n            {\n                this.publicationImages = 1 == this.publicationImages.length ?\n                    EMPTY_IMAGES : ArrayUtil.remove(this.publicationImages, i);\n                image.removeFromDispatcher();\n                image.receiverRelease();\n            }\n        }\n\n        checkPendingSetupMessages(nowNs);\n\n        if (reResolutionCheckIntervalNs > 0 && (reResolutionDeadlineNs - nowNs) < 0)\n        {\n            reResolutionDeadlineNs = nowNs + reResolutionCheckIntervalNs;\n            dataTransportPoller.checkForReResolutions(nowNs, conductorProxy);\n        }\n\n        return workCount + bytesReceived;\n    }\n\n    void addPendingSetupMessage(\n        final int sessionId,\n        final int streamId,\n        final int transportIndex,\n        final ReceiveChannelEndpoint channelEndpoint,\n        final boolean periodic,\n        final InetSocketAddress controlAddress)\n    {\n        final PendingSetupMessageFromSource cmd = new PendingSetupMessageFromSource(\n            sessionId, streamId, transportIndex, channelEndpoint, periodic, controlAddress);\n\n        cmd.timeOfStatusMessageNs(cachedNanoClock.nanoTime());\n        pendingSetupMessages.add(cmd);\n    }\n\n    void onAddSubscription(final ReceiveChannelEndpoint channelEndpoint, final int streamId)\n    {\n        channelEndpoint.dispatcher().addSubscription(streamId);\n    }\n\n    void onAddSubscription(final ReceiveChannelEndpoint channelEndpoint, final int streamId, final int sessionId)\n    {\n        channelEndpoint.dispatcher().addSubscription(streamId, sessionId);\n        if (channelEndpoint.hasExplicitControl())\n        {\n            channelEndpoint.sendSetupElicitingStatusMessage(\n                0, channelEndpoint.explicitControlAddress(), sessionId, streamId);\n        }\n    }\n\n    void onRequestSetup(final ReceiveChannelEndpoint channelEndpoint, final int streamId, final int sessionId)\n    {\n        if (channelEndpoint.hasExplicitControl())\n        {\n            channelEndpoint.sendSetupElicitingStatusMessage(\n                0, channelEndpoint.explicitControlAddress(), sessionId, streamId);\n        }\n    }\n\n    void onRemoveSubscription(final ReceiveChannelEndpoint channelEndpoint, final int streamId)\n    {\n        channelEndpoint.dispatcher().removeSubscription(streamId);\n    }\n\n    void onRemoveSubscription(final ReceiveChannelEndpoint channelEndpoint, final int streamId, final int sessionId)\n    {\n        channelEndpoint.dispatcher().removeSubscription(streamId, sessionId);\n\n        final ArrayList<PendingSetupMessageFromSource> pendingSetupMessages = this.pendingSetupMessages;\n        for (int lastIndex = pendingSetupMessages.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final PendingSetupMessageFromSource pending = pendingSetupMessages.get(i);\n\n            if (pending.channelEndpoint() == channelEndpoint &&\n                pending.streamId() == streamId &&\n                pending.sessionId() == sessionId)\n            {\n                ArrayListUtil.fastUnorderedRemove(pendingSetupMessages, i, lastIndex--);\n                pending.removeFromDataPacketDispatcher();\n            }\n        }\n    }\n\n    void onNewPublicationImage(final ReceiveChannelEndpoint channelEndpoint, final PublicationImage image)\n    {\n        disconnectInactiveImage(channelEndpoint, image.streamId(), image.sessionId());\n        publicationImages = ArrayUtil.add(publicationImages, image);\n        channelEndpoint.dispatcher().addPublicationImage(image);\n    }\n\n    void onRegisterReceiveChannelEndpoint(final ReceiveChannelEndpoint channelEndpoint)\n    {\n        if (!channelEndpoint.hasDestinationControl())\n        {\n            dataTransportPoller.registerForRead(channelEndpoint, channelEndpoint, 0);\n\n            if (channelEndpoint.hasExplicitControl())\n            {\n                addPendingSetupMessage(0, 0, 0, channelEndpoint, true, channelEndpoint.explicitControlAddress());\n                channelEndpoint.sendSetupElicitingStatusMessage(0, channelEndpoint.explicitControlAddress(), 0, 0);\n            }\n        }\n    }\n\n    void onCloseReceiveChannelEndpoint(final ReceiveChannelEndpoint channelEndpoint)\n    {\n        dataTransportPoller.cancelReadForAllTransports(channelEndpoint);\n\n        final ArrayList<PendingSetupMessageFromSource> pendingSetupMessages = this.pendingSetupMessages;\n        for (int lastIndex = pendingSetupMessages.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final PendingSetupMessageFromSource pending = pendingSetupMessages.get(i);\n\n            if (pending.channelEndpoint() == channelEndpoint)\n            {\n                ArrayListUtil.fastUnorderedRemove(pendingSetupMessages, i, lastIndex--);\n                pending.removeFromDataPacketDispatcher();\n            }\n        }\n\n        conductorProxy.receiveChannelEndpointClosed(channelEndpoint);\n    }\n\n    void onRemoveCoolDown(final ReceiveChannelEndpoint channelEndpoint, final int sessionId, final int streamId)\n    {\n        channelEndpoint.dispatcher().removeCoolDown(sessionId, streamId);\n    }\n\n    void onAddDestination(final ReceiveChannelEndpoint channelEndpoint, final ReceiveDestinationTransport transport)\n    {\n        final int transportIndex = channelEndpoint.addDestination(transport);\n        dataTransportPoller.registerForRead(channelEndpoint, transport, transportIndex);\n\n        if (transport.hasExplicitControl())\n        {\n            addPendingSetupMessage(0, 0, transportIndex, channelEndpoint, true, transport.explicitControlAddress());\n            channelEndpoint.sendSetupElicitingStatusMessage(transportIndex, transport.explicitControlAddress(), 0, 0);\n        }\n\n        for (final PublicationImage image : publicationImages)\n        {\n            if (channelEndpoint == image.channelEndpoint())\n            {\n                image.addDestination(transportIndex, transport);\n            }\n        }\n    }\n\n    void onRemoveDestination(final ReceiveChannelEndpoint channelEndpoint, final UdpChannel udpChannel)\n    {\n        final int transportIndex = channelEndpoint.destination(udpChannel);\n        if (ArrayUtil.UNKNOWN_INDEX != transportIndex)\n        {\n            final ReceiveDestinationTransport transport = channelEndpoint.destination(transportIndex);\n\n            dataTransportPoller.cancelRead(channelEndpoint, transport);\n            channelEndpoint.removeDestination(transportIndex);\n\n            for (final PublicationImage image : publicationImages)\n            {\n                if (channelEndpoint == image.channelEndpoint())\n                {\n                    image.removeDestination(transportIndex);\n                }\n            }\n\n            conductorProxy.closeReceiveDestination(transport);\n        }\n    }\n\n    void onResolutionChange(\n        final ReceiveChannelEndpoint channelEndpoint, final UdpChannel channel, final InetSocketAddress newAddress)\n    {\n        final int transportIndex = channelEndpoint.hasDestinationControl() ? channelEndpoint.destination(channel) : 0;\n\n        for (int i = 0, size = pendingSetupMessages.size(); i < size; i++)\n        {\n            final PendingSetupMessageFromSource pending = pendingSetupMessages.get(i);\n\n            if (pending.channelEndpoint() == channelEndpoint &&\n                pending.isPeriodic() &&\n                pending.transportIndex() == transportIndex)\n            {\n                pending.controlAddress(newAddress);\n                resolutionChanges.getAndAddRelease(1);\n            }\n        }\n\n        channelEndpoint.updateControlAddress(transportIndex, newAddress);\n    }\n\n    void onRejectImage(final long imageCorrelationId, final long position, final String reason)\n    {\n        for (final PublicationImage image : publicationImages)\n        {\n            if (imageCorrelationId == image.correlationId())\n            {\n                image.reject(reason);\n                break;\n            }\n        }\n    }\n\n    private void checkPendingSetupMessages(final long nowNs)\n    {\n        for (int lastIndex = pendingSetupMessages.size() - 1, i = lastIndex; i >= 0; i--)\n        {\n            final PendingSetupMessageFromSource pending = pendingSetupMessages.get(i);\n\n            if ((pending.timeOfStatusMessageNs() + PENDING_SETUPS_TIMEOUT_NS) - nowNs < 0)\n            {\n                if (!pending.isPeriodic())\n                {\n                    ArrayListUtil.fastUnorderedRemove(pendingSetupMessages, i, lastIndex--);\n                    pending.removeFromDataPacketDispatcher();\n                }\n                else if (pending.shouldElicitSetupMessage())\n                {\n                    pending.timeOfStatusMessageNs(nowNs);\n                    pending.channelEndpoint().sendSetupElicitingStatusMessage(\n                        pending.transportIndex(), pending.controlAddress(), pending.sessionId(), pending.streamId());\n                }\n            }\n        }\n    }\n\n    void disconnectInactiveImage(\n        final ReceiveChannelEndpoint channelEndpoint,\n        final int streamId,\n        final int sessionId)\n    {\n        for (final PublicationImage publicationImage : publicationImages)\n        {\n            if (publicationImage.channelEndpoint() == channelEndpoint &&\n                publicationImage.streamId() == streamId &&\n                publicationImage.sessionId() == sessionId)\n            {\n                publicationImage.stopStatusMessagesIfNotActive();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ReceiverLivenessTracker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.collections.Long2LongHashMap;\n\nclass ReceiverLivenessTracker\n{\n    private static final int MISSING_VALUE = -1;\n\n    private final Long2LongHashMap lastSmTimestampNsByReceiverIdMap = new Long2LongHashMap(MISSING_VALUE);\n\n    public boolean hasReceivers()\n    {\n        return !lastSmTimestampNsByReceiverIdMap.isEmpty();\n    }\n\n    public void onStatusMessage(final long receiverId, final long nowNs)\n    {\n        lastSmTimestampNsByReceiverIdMap.put(receiverId, nowNs);\n    }\n\n    public boolean onRemoteClose(final long receiverId)\n    {\n        return MISSING_VALUE != lastSmTimestampNsByReceiverIdMap.remove(receiverId);\n    }\n\n    public void onIdle(final long nowNs, final long timeoutNs)\n    {\n        final long timeoutThresholdNs = nowNs - timeoutNs;\n        final Long2LongHashMap.EntryIterator iterator = lastSmTimestampNsByReceiverIdMap.entrySet().iterator();\n        while (iterator.hasNext())\n        {\n            iterator.next();\n            if (iterator.getLongValue() <= timeoutThresholdNs)\n            {\n                iterator.remove();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ReceiverProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.ReceiveDestinationTransport;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.OneToOneConcurrentArrayQueue;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Proxy for offering into the {@link Receiver} Thread's command queue.\n */\nfinal class ReceiverProxy extends CommandProxy\n{\n    private Receiver receiver;\n\n    ReceiverProxy(\n        final ThreadingMode threadingMode,\n        final OneToOneConcurrentArrayQueue<Runnable> commandQueue,\n        final AtomicCounter failCount)\n    {\n        super(threadingMode, commandQueue, failCount);\n    }\n\n    void receiver(final Receiver receiver)\n    {\n        this.receiver = receiver;\n    }\n\n    Receiver receiver()\n    {\n        return receiver;\n    }\n\n    void addSubscription(final ReceiveChannelEndpoint mediaEndpoint, final int streamId)\n    {\n        if (notConcurrent())\n        {\n            receiver.onAddSubscription(mediaEndpoint, streamId);\n        }\n        else\n        {\n            offer(() -> receiver.onAddSubscription(mediaEndpoint, streamId));\n        }\n    }\n\n    void addSubscription(final ReceiveChannelEndpoint mediaEndpoint, final int streamId, final int sessionId)\n    {\n        if (notConcurrent())\n        {\n            receiver.onAddSubscription(mediaEndpoint, streamId, sessionId);\n        }\n        else\n        {\n            offer(() -> receiver.onAddSubscription(mediaEndpoint, streamId, sessionId));\n        }\n    }\n\n    void removeSubscription(final ReceiveChannelEndpoint mediaEndpoint, final int streamId)\n    {\n        if (notConcurrent())\n        {\n            receiver.onRemoveSubscription(mediaEndpoint, streamId);\n        }\n        else\n        {\n            offer(() -> receiver.onRemoveSubscription(mediaEndpoint, streamId));\n        }\n    }\n\n    void removeSubscription(final ReceiveChannelEndpoint mediaEndpoint, final int streamId, final int sessionId)\n    {\n        if (notConcurrent())\n        {\n            receiver.onRemoveSubscription(mediaEndpoint, streamId, sessionId);\n        }\n        else\n        {\n            offer(() -> receiver.onRemoveSubscription(mediaEndpoint, streamId, sessionId));\n        }\n    }\n\n    void newPublicationImage(final ReceiveChannelEndpoint channelEndpoint, final PublicationImage image)\n    {\n        if (notConcurrent())\n        {\n            receiver.onNewPublicationImage(channelEndpoint, image);\n        }\n        else\n        {\n            offer(() -> receiver.onNewPublicationImage(channelEndpoint, image));\n        }\n    }\n\n    void registerReceiveChannelEndpoint(final ReceiveChannelEndpoint channelEndpoint)\n    {\n        if (notConcurrent())\n        {\n            receiver.onRegisterReceiveChannelEndpoint(channelEndpoint);\n        }\n        else\n        {\n            offer(() -> receiver.onRegisterReceiveChannelEndpoint(channelEndpoint));\n        }\n    }\n\n    void closeReceiveChannelEndpoint(final ReceiveChannelEndpoint channelEndpoint)\n    {\n        if (notConcurrent())\n        {\n            receiver.onCloseReceiveChannelEndpoint(channelEndpoint);\n        }\n        else\n        {\n            offer(() -> receiver.onCloseReceiveChannelEndpoint(channelEndpoint));\n        }\n    }\n\n    void removeCoolDown(final ReceiveChannelEndpoint channelEndpoint, final int sessionId, final int streamId)\n    {\n        if (notConcurrent())\n        {\n            receiver.onRemoveCoolDown(channelEndpoint, sessionId, streamId);\n        }\n        else\n        {\n            offer(() -> receiver.onRemoveCoolDown(channelEndpoint, sessionId, streamId));\n        }\n    }\n\n    void addDestination(final ReceiveChannelEndpoint channelEndpoint, final ReceiveDestinationTransport transport)\n    {\n        if (notConcurrent())\n        {\n            receiver.onAddDestination(channelEndpoint, transport);\n        }\n        else\n        {\n            offer(() -> receiver.onAddDestination(channelEndpoint, transport));\n        }\n    }\n\n    void removeDestination(final ReceiveChannelEndpoint channelEndpoint, final UdpChannel udpChannel)\n    {\n        if (notConcurrent())\n        {\n            receiver.onRemoveDestination(channelEndpoint, udpChannel);\n        }\n        else\n        {\n            offer(() -> receiver.onRemoveDestination(channelEndpoint, udpChannel));\n        }\n    }\n\n    void onResolutionChange(\n        final ReceiveChannelEndpoint channelEndpoint, final UdpChannel udpChannel, final InetSocketAddress newAddress)\n    {\n        if (notConcurrent())\n        {\n            receiver.onResolutionChange(channelEndpoint, udpChannel, newAddress);\n        }\n        else\n        {\n            offer(() -> receiver.onResolutionChange(channelEndpoint, udpChannel, newAddress));\n        }\n    }\n\n    void requestSetup(\n        final ReceiveChannelEndpoint channelEndpoint,\n        final int streamId,\n        final int sessionId)\n    {\n        if (notConcurrent())\n        {\n            receiver.onRequestSetup(channelEndpoint, streamId, sessionId);\n        }\n        else\n        {\n            offer(() -> receiver.onRequestSetup(channelEndpoint, streamId, sessionId));\n        }\n    }\n\n    void rejectImage(final long imageCorrelationId, final long position, final String reason)\n    {\n        if (notConcurrent())\n        {\n            receiver.onRejectImage(imageCorrelationId, position, reason);\n        }\n        else\n        {\n            offer(() -> receiver.onRejectImage(imageCorrelationId, position, reason));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/RetransmitHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Tracking and handling of retransmit request, NAKs, for senders, and receivers.\n * <p>\n * When configured for multicast, a max number of retransmits is permitted by\n * {@link Configuration#MAX_RESEND_DEFAULT}. Additional received NAKs will be ignored if this maximum is reached.\n * When configured for unicast, a single outstanding retransmit is permitted, and additional received NAKs\n * will be ignored iff they overlap the current retransmit - otherwise the previous retransmit is assumed to have\n * 'worked' and the new NAK will take its place.\n */\npublic final class RetransmitHandler\n{\n    private final RetransmitAction[] retransmitActionPool;\n    private final NanoClock nanoClock;\n    private final FeedbackDelayGenerator delayGenerator;\n    private final FeedbackDelayGenerator lingerTimeoutGenerator;\n    private final AtomicCounter invalidPackets;\n    private final boolean hasGroupSemantics;\n    private final AtomicCounter retransmitOverflowCounter;\n\n    private int activeRetransmitCount = 0;\n\n    /**\n     * Create a handler for the dealing with the reception of frame request a frame to be retransmitted.\n     *\n     * @param nanoClock                 used to determine time.\n     * @param invalidPackets            for recording invalid packets.\n     * @param delayGenerator            to use for delay determination.\n     * @param lingerTimeoutGenerator    to use for linger timeout.\n     * @param hasGroupSemantics         indicates multicast/MDC semantics.\n     * @param maxRetransmits            max retransmits for when group semantics is enabled\n     * @param retransmitOverflowCounter counter to track overflows.\n     */\n    public RetransmitHandler(\n        final NanoClock nanoClock,\n        final AtomicCounter invalidPackets,\n        final FeedbackDelayGenerator delayGenerator,\n        final FeedbackDelayGenerator lingerTimeoutGenerator,\n        final boolean hasGroupSemantics,\n        final int maxRetransmits,\n        final AtomicCounter retransmitOverflowCounter)\n    {\n        this.nanoClock = nanoClock;\n        this.invalidPackets = invalidPackets;\n        this.delayGenerator = delayGenerator;\n        this.lingerTimeoutGenerator = lingerTimeoutGenerator;\n        this.hasGroupSemantics = hasGroupSemantics;\n        this.retransmitOverflowCounter = retransmitOverflowCounter;\n\n        final int actualMaxRetransmits = this.hasGroupSemantics ? maxRetransmits : 1;\n\n        retransmitActionPool = new RetransmitAction[actualMaxRetransmits];\n        for (int i = 0; i < actualMaxRetransmits; i++)\n        {\n            retransmitActionPool[i] = new RetransmitAction();\n        }\n    }\n\n    /**\n     * Called on reception of a NAK to start retransmit handling.\n     *\n     * @param termId           from the NAK and the term id of the buffer to retransmit from.\n     * @param termOffset       from the NAK and the offset of the data to retransmit.\n     * @param length           of the missing data.\n     * @param termLength       of the term buffer.\n     * @param mtuLength        for the publication.\n     * @param flowControl      for the publication (to clamp the retransmission length).\n     * @param retransmitSender to call if an immediate retransmit is required.\n     */\n    public void onNak(\n        final int termId,\n        final int termOffset,\n        final int length,\n        final int termLength,\n        final int mtuLength,\n        final FlowControl flowControl,\n        final RetransmitSender retransmitSender)\n    {\n        if (!isInvalid(termOffset, termLength, length) && 0 != length)\n        {\n            final int retransmitLength = flowControl.maxRetransmissionLength(termOffset, length, termLength, mtuLength);\n            final RetransmitAction action = scanForAvailableRetransmit(termId, termOffset, retransmitLength);\n            if (null != action)\n            {\n                action.termId = termId;\n                action.termOffset = termOffset;\n                action.length = retransmitLength;\n\n                final long delay = delayGenerator.generateDelayNs();\n                if (0 == delay)\n                {\n                    retransmitSender.resend(termId, termOffset, action.length);\n                    action.linger(lingerTimeoutGenerator.generateDelayNs(), nanoClock.nanoTime());\n                }\n                else\n                {\n                    action.delay(delay, nanoClock.nanoTime());\n                }\n            }\n        }\n    }\n\n    /**\n     * Called to indicate a retransmission is received that may obviate the need to send one ourselves.\n     * <p>\n     * NOTE: Currently only called from unit tests. Would be used for retransmitting from receivers for NAK suppression.\n     *\n     * @param termId     of the data.\n     * @param termOffset of the data.\n     */\n    public void onRetransmitReceived(final int termId, final int termOffset)\n    {\n        final RetransmitAction action = scanForExistingRetransmit(termId, termOffset);\n\n        if (null != action && RetransmitAction.State.DELAYED == action.state)\n        {\n            removeRetransmit(action);\n        }\n    }\n\n    /**\n     * Called to process any outstanding timeouts.\n     *\n     * @param nowNs            time in nanoseconds.\n     * @param retransmitSender to call on retransmissions.\n     */\n    public void processTimeouts(final long nowNs, final RetransmitSender retransmitSender)\n    {\n        if (activeRetransmitCount > 0)\n        {\n            for (final RetransmitAction action : retransmitActionPool)\n            {\n                if (RetransmitAction.State.DELAYED == action.state && (action.expiryNs - nowNs < 0))\n                {\n                    retransmitSender.resend(action.termId, action.termOffset, action.length);\n                    action.linger(lingerTimeoutGenerator.generateDelayNs(), nanoClock.nanoTime());\n                }\n                else if (RetransmitAction.State.LINGERING == action.state && (action.expiryNs - nowNs < 0))\n                {\n                    removeRetransmit(action);\n                }\n            }\n        }\n    }\n\n    private boolean isInvalid(final int termOffset, final int termLength, final int length)\n    {\n        final boolean isInvalid = (termOffset > (termLength - DataHeaderFlyweight.HEADER_LENGTH)) || (termOffset < 0) ||\n            (length < 0);\n\n        if (isInvalid)\n        {\n            invalidPackets.increment();\n        }\n\n        return isInvalid;\n    }\n\n    private RetransmitAction scanForAvailableRetransmit(final int termId, final int termOffset, final int length)\n    {\n        if (0 == activeRetransmitCount)\n        {\n            return addRetransmit(retransmitActionPool[0]);\n        }\n\n        RetransmitAction availableAction = null;\n        for (final RetransmitAction action : retransmitActionPool)\n        {\n            switch (action.state)\n            {\n                case INACTIVE:\n                    if (null == availableAction)\n                    {\n                        availableAction = action;\n                    }\n                    break;\n\n                case DELAYED:\n                case LINGERING:\n                    if (action.termId == termId &&\n                        action.termOffset <= termOffset && termOffset < action.termOffset + action.length)\n                    {\n                        return null;\n                    }\n\n                    if (!hasGroupSemantics)\n                    {\n                        // this is unicast, and the NAK does NOT overlap the previous one, so just reuse it\n                        availableAction = action;\n                    }\n                    break;\n            }\n        }\n\n        if (hasGroupSemantics)\n        {\n            if (null != availableAction)\n            {\n                return addRetransmit(availableAction);\n            }\n\n            retransmitOverflowCounter.increment();\n        }\n\n        return availableAction;\n    }\n\n    private RetransmitAction scanForExistingRetransmit(final int termId, final int termOffset)\n    {\n        if (0 == activeRetransmitCount)\n        {\n            return null;\n        }\n\n        for (final RetransmitAction action : retransmitActionPool)\n        {\n            switch (action.state)\n            {\n                case DELAYED:\n                case LINGERING:\n                    if (action.termId == termId && action.termOffset == termOffset)\n                    {\n                        return action;\n                    }\n                    break;\n\n                default:\n                    break;\n            }\n        }\n\n        return null;\n    }\n\n    private RetransmitAction addRetransmit(final RetransmitAction retransmitAction)\n    {\n        ++activeRetransmitCount;\n        return retransmitAction;\n    }\n\n    private void removeRetransmit(final RetransmitAction action)\n    {\n        --activeRetransmitCount;\n        action.cancel();\n    }\n\n    static final class RetransmitAction\n    {\n        @SuppressWarnings(\"JavadocVariable\")\n        enum State\n        {\n            DELAYED,\n            LINGERING,\n            INACTIVE\n        }\n\n        long expiryNs;\n        int termId;\n        int termOffset;\n        int length;\n        State state = State.INACTIVE;\n\n        void delay(final long delayNs, final long nowNs)\n        {\n            state = State.DELAYED;\n            expiryNs = nowNs + delayNs;\n        }\n\n        void linger(final long timeoutNs, final long nowNs)\n        {\n            state = State.LINGERING;\n            expiryNs = nowNs + timeoutNs;\n        }\n\n        void cancel()\n        {\n            state = State.INACTIVE;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/RetransmitSender.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\n/**\n * Handler for sending a retransmit.\n */\n@FunctionalInterface\npublic interface RetransmitSender\n{\n    /**\n     * Called when a frame should be sent to request the retransmission of data.\n     *\n     * @param termId     containing the data to NAK.\n     * @param termOffset of the data to NAK.\n     * @param length     of the data to NAK.\n     */\n    void resend(int termId, int termOffset, int length);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/SendChannelEndpointSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Supplier of channel endpoints which extend {@link SendChannelEndpoint} to add specialised behaviour for the sender.\n */\n@FunctionalInterface\npublic interface SendChannelEndpointSupplier\n{\n    /**\n     * A new instance of a specialised {@link SendChannelEndpoint}.\n     *\n     * @param udpChannel      on which the sender will send.\n     * @param statusIndicator for the channel.\n     * @param context         for the configuration of the driver.\n     * @return a new instance of a specialised {@link SendChannelEndpoint}.\n     */\n    SendChannelEndpoint newInstance(UdpChannel udpChannel, AtomicCounter statusIndicator, MediaDriver.Context context);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/Sender.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.driver.media.ControlTransportPoller;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.OneToOneConcurrentArrayQueue;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.net.InetSocketAddress;\n\nimport static io.aeron.driver.status.SystemCounterDescriptor.*;\n\nclass SenderLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nclass SenderHotFields extends SenderLhsPadding\n{\n    long controlPollDeadlineNs;\n    long reResolutionDeadlineNs;\n    int dutyCycleCounter;\n    int roundRobinIndex = 0;\n}\n\nclass SenderRhsPadding extends SenderHotFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\n/**\n * Agent that iterates over {@link NetworkPublication}s for sending them to {@link Receiver}s on behalf of registered\n * subscribers.\n */\npublic final class Sender extends SenderRhsPadding implements Agent\n{\n    private NetworkPublication[] networkPublications = new NetworkPublication[0];\n\n    private final long statusMessageReadTimeoutNs;\n    private final long reResolutionCheckIntervalNs;\n    private final int dutyCycleRatio;\n    private final ControlTransportPoller controlTransportPoller;\n    private final OneToOneConcurrentArrayQueue<Runnable> commandQueue;\n    private final AtomicCounter totalBytesSent;\n    private final AtomicCounter resolutionChanges;\n    private final AtomicCounter shortSends;\n    private final NanoClock nanoClock;\n    private final CachedNanoClock cachedNanoClock;\n    private final DriverConductorProxy conductorProxy;\n    private final DutyCycleTracker dutyCycleTracker;\n\n    Sender(final MediaDriver.Context ctx)\n    {\n        controlTransportPoller = ctx.controlTransportPoller();\n        commandQueue = ctx.senderCommandQueue();\n        totalBytesSent = ctx.systemCounters().get(BYTES_SENT);\n        resolutionChanges = ctx.systemCounters().get(RESOLUTION_CHANGES);\n        shortSends = ctx.systemCounters().get(SHORT_SENDS);\n        nanoClock = ctx.nanoClock();\n        cachedNanoClock = ctx.senderCachedNanoClock();\n        statusMessageReadTimeoutNs = ctx.statusMessageTimeoutNs() >> 1;\n        reResolutionCheckIntervalNs = ctx.reResolutionCheckIntervalNs();\n        dutyCycleRatio = ctx.sendToStatusMessagePollRatio();\n        conductorProxy = ctx.driverConductorProxy();\n        dutyCycleTracker = ctx.senderDutyCycleTracker();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        final long nowNs = nanoClock.nanoTime();\n        cachedNanoClock.update(nowNs);\n        dutyCycleTracker.update(nowNs);\n        reResolutionDeadlineNs = nowNs + reResolutionCheckIntervalNs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        controlTransportPoller.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        final long nowNs = nanoClock.nanoTime();\n        cachedNanoClock.update(nowNs);\n        dutyCycleTracker.measureAndUpdate(nowNs);\n\n        final int workCount = commandQueue.drain(CommandProxy.RUN_TASK, Configuration.COMMAND_DRAIN_LIMIT);\n\n        final long shortSendsBefore = shortSends.get();\n        final int bytesSent = doSend(nowNs);\n        int bytesReceived = 0;\n\n        if (0 == bytesSent ||\n            ++dutyCycleCounter >= dutyCycleRatio ||\n            (controlPollDeadlineNs - nowNs < 0) ||\n            shortSendsBefore < shortSends.get())\n        {\n            bytesReceived = controlTransportPoller.pollTransports();\n\n            dutyCycleCounter = 0;\n            controlPollDeadlineNs = nowNs + statusMessageReadTimeoutNs;\n        }\n\n        if (reResolutionCheckIntervalNs > 0 && (reResolutionDeadlineNs - nowNs) < 0)\n        {\n            reResolutionDeadlineNs = nowNs + reResolutionCheckIntervalNs;\n            controlTransportPoller.checkForReResolutions(nowNs, conductorProxy);\n        }\n\n        return workCount + bytesSent + bytesReceived;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"sender\";\n    }\n\n    void onRegisterSendChannelEndpoint(final SendChannelEndpoint channelEndpoint)\n    {\n        controlTransportPoller.registerForRead(channelEndpoint);\n    }\n\n    void onCloseSendChannelEndpoint(final SendChannelEndpoint channelEndpoint)\n    {\n        controlTransportPoller.cancelRead(channelEndpoint);\n        conductorProxy.sendChannelEndpointClosed(channelEndpoint);\n    }\n\n    void onNewNetworkPublication(final NetworkPublication publication)\n    {\n        networkPublications = ArrayUtil.add(networkPublications, publication);\n        publication.channelEndpoint().registerForSend(publication);\n    }\n\n    void onRemoveNetworkPublication(final NetworkPublication publication)\n    {\n        networkPublications = ArrayUtil.remove(networkPublications, publication);\n        publication.channelEndpoint().unregisterForSend(publication);\n        publication.senderRelease();\n    }\n\n    void onAddDestination(\n        final SendChannelEndpoint channelEndpoint,\n        final ChannelUri channelUri,\n        final InetSocketAddress address,\n        final long registrationId)\n    {\n        channelEndpoint.addDestination(channelUri, address, registrationId);\n    }\n\n    void onRemoveDestination(\n        final SendChannelEndpoint channelEndpoint, final ChannelUri channelUri, final InetSocketAddress address)\n    {\n        channelEndpoint.removeDestination(channelUri, address);\n    }\n\n    void onRemoveDestination(final SendChannelEndpoint channelEndpoint, final long destinationRegistrationId)\n    {\n        channelEndpoint.removeDestination(destinationRegistrationId);\n    }\n\n    void onResolutionChange(\n        final SendChannelEndpoint channelEndpoint, final String endpoint, final InetSocketAddress newAddress)\n    {\n        channelEndpoint.resolutionChange(endpoint, newAddress);\n        resolutionChanges.getAndAddRelease(1);\n    }\n\n    private int doSend(final long nowNs)\n    {\n        int bytesSent = 0;\n        final NetworkPublication[] publications = this.networkPublications;\n        final int length = publications.length;\n\n        int startingIndex = roundRobinIndex++;\n        if (startingIndex >= length)\n        {\n            roundRobinIndex = startingIndex = 0;\n        }\n\n        for (int i = startingIndex; i < length; i++)\n        {\n            bytesSent += publications[i].send(nowNs);\n        }\n\n        for (int i = 0; i < startingIndex; i++)\n        {\n            bytesSent += publications[i].send(nowNs);\n        }\n\n        totalBytesSent.getAndAddRelease(bytesSent);\n\n        return bytesSent;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/SenderProxy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport org.agrona.concurrent.OneToOneConcurrentArrayQueue;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Proxy for offering into the Sender Thread's command queue.\n */\nfinal class SenderProxy extends CommandProxy\n{\n    private Sender sender;\n\n    SenderProxy(\n        final ThreadingMode threadingMode,\n        final OneToOneConcurrentArrayQueue<Runnable> commandQueue,\n        final AtomicCounter failCount)\n    {\n        super(threadingMode, commandQueue, failCount);\n    }\n\n    void sender(final Sender sender)\n    {\n        this.sender = sender;\n    }\n\n    void registerSendChannelEndpoint(final SendChannelEndpoint channelEndpoint)\n    {\n        if (notConcurrent())\n        {\n            sender.onRegisterSendChannelEndpoint(channelEndpoint);\n        }\n        else\n        {\n            offer(() -> sender.onRegisterSendChannelEndpoint(channelEndpoint));\n        }\n    }\n\n    void closeSendChannelEndpoint(final SendChannelEndpoint channelEndpoint)\n    {\n        if (notConcurrent())\n        {\n            sender.onCloseSendChannelEndpoint(channelEndpoint);\n        }\n        else\n        {\n            offer(() -> sender.onCloseSendChannelEndpoint(channelEndpoint));\n        }\n    }\n\n    void removeNetworkPublication(final NetworkPublication publication)\n    {\n        if (notConcurrent())\n        {\n            sender.onRemoveNetworkPublication(publication);\n        }\n        else\n        {\n            offer(() -> sender.onRemoveNetworkPublication(publication));\n        }\n    }\n\n    void newNetworkPublication(final NetworkPublication publication)\n    {\n        if (notConcurrent())\n        {\n            sender.onNewNetworkPublication(publication);\n        }\n        else\n        {\n            offer(() -> sender.onNewNetworkPublication(publication));\n        }\n    }\n\n    void addDestination(\n        final SendChannelEndpoint channelEndpoint,\n        final ChannelUri channelUri,\n        final InetSocketAddress address,\n        final long registrationId)\n    {\n        if (notConcurrent())\n        {\n            sender.onAddDestination(channelEndpoint, channelUri, address, registrationId);\n        }\n        else\n        {\n            offer(() -> sender.onAddDestination(channelEndpoint, channelUri, address, registrationId));\n        }\n    }\n\n    void removeDestination(\n        final SendChannelEndpoint channelEndpoint, final ChannelUri channelUri, final InetSocketAddress address)\n    {\n        if (notConcurrent())\n        {\n            sender.onRemoveDestination(channelEndpoint, channelUri, address);\n        }\n        else\n        {\n            offer(() -> sender.onRemoveDestination(channelEndpoint, channelUri, address));\n        }\n    }\n\n    void removeDestination(\n        final SendChannelEndpoint channelEndpoint, final long destinationRegistrationId)\n    {\n        if (notConcurrent())\n        {\n            sender.onRemoveDestination(channelEndpoint, destinationRegistrationId);\n        }\n        else\n        {\n            offer(() -> sender.onRemoveDestination(channelEndpoint, destinationRegistrationId));\n        }\n    }\n\n    void onResolutionChange(\n        final SendChannelEndpoint channelEndpoint, final String endpoint, final InetSocketAddress newAddress)\n    {\n        if (notConcurrent())\n        {\n            sender.onResolutionChange(channelEndpoint, endpoint, newAddress);\n        }\n        else\n        {\n            offer(() -> sender.onResolutionChange(channelEndpoint, endpoint, newAddress));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/SessionKey.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\n/**\n * Key used to identify a session instance of a stream.\n */\nfinal class SessionKey\n{\n    int sessionId;\n    final int streamId;\n    final String channel;\n\n    SessionKey(final int streamId, final String channel)\n    {\n        this.streamId = streamId;\n        this.channel = channel;\n    }\n\n    SessionKey(final int sessionId, final int streamId, final String channel)\n    {\n        this.sessionId = sessionId;\n        this.streamId = streamId;\n        this.channel = channel;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean equals(final Object o)\n    {\n        if (this == o)\n        {\n            return true;\n        }\n\n        if (o == null || getClass() != o.getClass())\n        {\n            return false;\n        }\n\n        final SessionKey that = (SessionKey)o;\n\n        return sessionId == that.sessionId && streamId == that.streamId && channel.equals(that.channel);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int hashCode()\n    {\n        return 31 * sessionId * streamId * channel.hashCode();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"SessionKey{\" +\n            \"sessionId=\" + sessionId +\n            \", streamId=\" + streamId +\n            \", channel=\" + channel +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/SpySubscriptionLink.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\n\nclass SpySubscriptionLink extends SubscriptionLink\n{\n    private final UdpChannel udpChannel;\n\n    SpySubscriptionLink(\n        final long registrationId,\n        final UdpChannel spiedChannel,\n        final int streamId,\n        final AeronClient aeronClient,\n        final SubscriptionParams params)\n    {\n        super(registrationId, streamId, spiedChannel.originalUriString(), aeronClient, params);\n\n        this.udpChannel = spiedChannel;\n    }\n\n    boolean matches(final NetworkPublication publication)\n    {\n        final UdpChannel publicationChannel = publication.channelEndpoint().udpChannel();\n        final boolean isSameChannelTag = udpChannel.hasTag() && udpChannel.tag() == publicationChannel.tag();\n\n        return publication.streamId() == streamId &&\n            (isSameChannelTag ||\n            isWildcardOrSessionIdMatch(publication.sessionId()) &&\n            udpChannel.canonicalForm().equals(publicationChannel.canonicalForm()));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/StaticDelayGenerator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\n/**\n * Delay generator that simply returns a constant value for the delay and the retry delay.\n */\npublic final class StaticDelayGenerator implements FeedbackDelayGenerator\n{\n    /**\n     * {@link StaticDelayGenerator} instance with zero delay.\n     */\n    public static final StaticDelayGenerator ZERO_DELAY_GENERATOR = new StaticDelayGenerator(0);\n\n    private final long delayInNs;\n    private final long retryDelayNs;\n\n    /**\n     * Create a delayInNs generator that uses the specified delayNs.\n     *\n     * @param delayNs      initial delay (nanoseconds)\n     * @param retryDelayNs delay for retry (nanoseconds)\n     */\n    public StaticDelayGenerator(final long delayNs, final long retryDelayNs)\n    {\n        this.delayInNs = delayNs;\n        this.retryDelayNs = retryDelayNs;\n    }\n\n    /**\n     * Create a delayInNs generator that uses the specified delayNs for both initial and retry delays.\n     *\n     * @param delayNs      delay (nanoseconds)\n     */\n    public StaticDelayGenerator(final long delayNs)\n    {\n        this(delayNs, delayNs);\n    }\n\n    /**\n     * Deprecated constructor.\n     *\n     * @param delayNs           delay (nanoseconds)\n     * @param immediateFeedback immediately feedback (unused)\n     * @deprecated The <code>shouldImmediatelyFeedback</code> method has been removed from the interface so the\n     * <code>immediatelyFeedback</code> parameter is ignored. You can emulate the old behaviour with:\n     * <pre>\n     *     new StaticDelayGenerator(0, MILLISECONDS.toNanos(10));\n     * </pre>\n     * If used for unicast delays, then this is not recommended and a short delay like the default\n     * {@link Configuration#NAK_UNICAST_DELAY_DEFAULT_NS} should be used.\n     */\n    @Deprecated\n    public StaticDelayGenerator(final long delayNs, @SuppressWarnings(\"unused\") final boolean immediateFeedback)\n    {\n        this(delayNs, delayNs);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long generateDelayNs()\n    {\n        return delayInNs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long retryDelayNs()\n    {\n        return retryDelayNs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"StaticDelayGenerator{\" +\n            \"delayInNs=\" + delayInNs +\n            \", retryDelayNs=\" + retryDelayNs +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/StaticWindowCongestionControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Congestion control algorithm which uses the min of {@link MediaDriver.Context#initialWindowLength()} or half a term\n * length as a static window.\n */\npublic class StaticWindowCongestionControl implements CongestionControl\n{\n    /**\n     * URI param value to identify this {@link CongestionControl} strategy.\n     */\n    public static final String CC_PARAM_VALUE = \"static\";\n\n    private final long ccOutcome;\n\n    /**\n     * Construct a new {@link CongestionControl} instance for a received stream image using a static window algorithm.\n     *\n     * @param registrationId  for the publication image.\n     * @param udpChannel      for the publication image.\n     * @param streamId        for the publication image.\n     * @param sessionId       for the publication image.\n     * @param termLength      for the publication image.\n     * @param senderMtuLength for the publication image.\n     * @param controlAddress  for the publication image.\n     * @param sourceAddress   for the publication image.\n     * @param nanoClock       for the precise timing.\n     * @param context         for configuration options applied in the driver.\n     * @param countersManager for the driver.\n     */\n    public StaticWindowCongestionControl(\n        final long registrationId,\n        final UdpChannel udpChannel,\n        final int streamId,\n        final int sessionId,\n        final int termLength,\n        final int senderMtuLength,\n        final InetSocketAddress controlAddress,\n        final InetSocketAddress sourceAddress,\n        final NanoClock nanoClock,\n        final MediaDriver.Context context,\n        final CountersManager countersManager)\n    {\n        final int initialWindowLength = udpChannel.receiverWindowLength() != 0 ?\n            udpChannel.receiverWindowLength() : context.initialWindowLength();\n\n        ccOutcome = CongestionControl.packOutcome(\n            Configuration.receiverWindowLength(termLength, initialWindowLength), false);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean shouldMeasureRtt(final long nowNs)\n    {\n        return false;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onRttMeasurementSent(final long nowNs)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onRttMeasurement(final long nowNs, final long rttNs, final InetSocketAddress srcAddress)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onTrackRebuild(\n        final long nowNs,\n        final long newConsumptionPosition,\n        final long lastSmPosition,\n        final long hwmPosition,\n        final long startingRebuildPosition,\n        final long endingRebuildPosition,\n        final boolean lossOccurred)\n    {\n        return ccOutcome;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int initialWindowLength()\n    {\n        return CongestionControl.receiverWindowLength(ccOutcome);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int maxWindowLength()\n    {\n        return CongestionControl.receiverWindowLength(ccOutcome);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/Subscribable.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.concurrent.status.ReadablePosition;\n\n/**\n * Stream source that can be observed by subscribers which identify themselves the position they have read up to.\n */\npublic interface Subscribable\n{\n    /**\n     * Registration ID that is in use by the subscribable.\n     *\n     * @return registration ID for subscribable.\n     */\n    long subscribableRegistrationId();\n\n    /**\n     * Add a subscriber and its position used for tracking consumption.\n     *\n     * @param subscriptionLink   for identifying the subscriber.\n     * @param subscriberPosition for tracking the subscriber.\n     * @param nowNs              for the current time.\n     */\n    void addSubscriber(SubscriptionLink subscriptionLink, ReadablePosition subscriberPosition, long nowNs);\n\n    /**\n     * Remove a subscriber and its position used for tracking consumption.\n     * <p>\n     * <b>Note:</b> The {@link Subscribable} is responsible for calling {@link ReadablePosition#close()} on\n     * removed positions.\n     *\n     * @param subscriptionLink   for identifying the subscriber.\n     * @param subscriberPosition for tracking the subscriber.\n     */\n    void removeSubscriber(SubscriptionLink subscriptionLink, ReadablePosition subscriberPosition);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/SubscriberPosition.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.concurrent.status.Position;\n\n/**\n * Consumption position a subscriber has got to within a {@link SubscriptionLink}.\n */\nfinal class SubscriberPosition\n{\n    private final SubscriptionLink subscriptionLink;\n    private final Subscribable subscribable;\n    private final Position position;\n\n    SubscriberPosition(\n        final SubscriptionLink subscriptionLink, final Subscribable subscribable, final Position position)\n    {\n        this.subscriptionLink = subscriptionLink;\n        this.subscribable = subscribable;\n        this.position = position;\n    }\n\n    Position position()\n    {\n        return position;\n    }\n\n    int positionCounterId()\n    {\n        return position().id();\n    }\n\n    SubscriptionLink subscription()\n    {\n        return subscriptionLink;\n    }\n\n    void addLink(final PublicationImage image)\n    {\n        subscriptionLink.link(image, position);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"SubscriberPosition{\" +\n            \"subscriptionLink=\" + subscriptionLink +\n            \", subscribable=\" + subscribable +\n            \", position=\" + position +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/SubscriptionLink.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CommonContext;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport org.agrona.concurrent.status.ReadablePosition;\n\nimport java.util.IdentityHashMap;\nimport java.util.Map;\n\n/**\n * Subscription registration from a client used for liveness tracking.\n */\npublic abstract class SubscriptionLink implements DriverManagedResource\n{\n    final long registrationId;\n    final int streamId;\n    final boolean isSparse;\n    final boolean isTether;\n    final boolean isResponse;\n    final boolean isRejoin;\n    final CommonContext.InferableBoolean group;\n    final String channel;\n    final AeronClient aeronClient;\n    final IdentityHashMap<Subscribable, ReadablePosition> positionBySubscribableMap;\n    int sessionId;\n    boolean hasSessionId;\n    boolean reachedEndOfLife = false;\n\n    SubscriptionLink(\n        final long registrationId,\n        final int streamId,\n        final String channel,\n        final AeronClient aeronClient,\n        final SubscriptionParams params)\n    {\n        this.registrationId = registrationId;\n        this.streamId = streamId;\n        this.channel = channel;\n        this.aeronClient = aeronClient;\n        this.hasSessionId = params.hasSessionId;\n        this.sessionId = params.sessionId;\n        this.isSparse = params.isSparse;\n        this.isTether = params.isTether;\n        this.group = params.group;\n        this.isResponse = params.isResponse;\n        this.isRejoin = params.isRejoin;\n\n        positionBySubscribableMap = new IdentityHashMap<>(hasSessionId ? 1 : 8);\n    }\n\n    /**\n     * Channel URI the subscription is on.\n     *\n     * @return channel URI the subscription is on.\n     */\n    public final String channel()\n    {\n        return channel;\n    }\n\n    /**\n     * Stream id the subscription is on.\n     *\n     * @return stream id the subscription is on.\n     */\n    public final int streamId()\n    {\n        return streamId;\n    }\n\n    /**\n     * Registration id of the subscription.\n     *\n     * @return registration id of the subscription.\n     */\n    public final long registrationId()\n    {\n        return registrationId;\n    }\n\n    AeronClient aeronClient()\n    {\n        return aeronClient;\n    }\n\n    final int sessionId()\n    {\n        return sessionId;\n    }\n\n    void sessionId(final int sessionId)\n    {\n        this.hasSessionId = true;\n        this.sessionId = sessionId;\n    }\n\n    boolean isResponse()\n    {\n        return isResponse;\n    }\n\n    ReceiveChannelEndpoint channelEndpoint()\n    {\n        return null;\n    }\n\n    boolean isReliable()\n    {\n        return true;\n    }\n\n    boolean isRejoin()\n    {\n        return isRejoin;\n    }\n\n    boolean isTether()\n    {\n        return isTether;\n    }\n\n    boolean isSparse()\n    {\n        return isSparse;\n    }\n\n    CommonContext.InferableBoolean group()\n    {\n        return group;\n    }\n\n    boolean hasSessionId()\n    {\n        return hasSessionId;\n    }\n\n    boolean matches(final NetworkPublication publication)\n    {\n        return false;\n    }\n\n    boolean matches(final PublicationImage image)\n    {\n        return false;\n    }\n\n    boolean matches(final IpcPublication publication)\n    {\n        return false;\n    }\n\n    boolean matches(final ReceiveChannelEndpoint channelEndpoint, final int streamId, final SubscriptionParams params)\n    {\n        return false;\n    }\n\n    boolean matches(final ReceiveChannelEndpoint channelEndpoint, final int streamId, final int sessionId)\n    {\n        return false;\n    }\n\n    boolean isLinked(final Subscribable subscribable)\n    {\n        return positionBySubscribableMap.containsKey(subscribable);\n    }\n\n    void link(final Subscribable subscribable, final ReadablePosition position)\n    {\n        positionBySubscribableMap.put(subscribable, position);\n    }\n\n    void unlink(final Subscribable subscribable)\n    {\n        positionBySubscribableMap.remove(subscribable);\n    }\n\n    boolean isWildcardOrSessionIdMatch(final int sessionId)\n    {\n        return (!hasSessionId && !isResponse()) || this.sessionId == sessionId;\n    }\n\n    boolean supportsMds()\n    {\n        return false;\n    }\n\n    void notifyUnavailableImages(final DriverConductor conductor)\n    {\n        for (final Map.Entry<Subscribable, ReadablePosition> entry : positionBySubscribableMap.entrySet())\n        {\n            final Subscribable subscribable = entry.getKey();\n            conductor.notifyUnavailableImageLink(subscribable.subscribableRegistrationId(), this);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        for (final Map.Entry<Subscribable, ReadablePosition> entry : positionBySubscribableMap.entrySet())\n        {\n            final Subscribable subscribable = entry.getKey();\n            final ReadablePosition position = entry.getValue();\n            subscribable.removeSubscriber(this, position);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTimeEvent(final long timeNs, final long timeMs, final DriverConductor conductor)\n    {\n        if (aeronClient.hasTimedOut())\n        {\n            reachedEndOfLife = true;\n            conductor.cleanupSubscriptionLink(this);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean hasReachedEndOfLife()\n    {\n        return reachedEndOfLife;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return this.getClass().getName() + \"{\" +\n            \"registrationId=\" + registrationId +\n            \", streamId=\" + streamId +\n            \", sessionId=\" + sessionId +\n            \", hasSessionId=\" + hasSessionId +\n            \", isReliable=\" + isReliable() +\n            \", isSparse=\" + isSparse() +\n            \", isTether=\" + isTether() +\n            \", isRejoin=\" + isRejoin() +\n            \", reachedEndOfLife=\" + reachedEndOfLife +\n            \", group=\" + group +\n            \", channel='\" + channel + '\\'' +\n            \", aeronClient=\" + aeronClient +\n            \", positionBySubscribableMap=\" + positionBySubscribableMap +\n            '}';\n    }\n}\n\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/SubscriptionParams.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.SystemUtil;\n\nimport static io.aeron.CommonContext.*;\n\nfinal class SubscriptionParams\n{\n    int initialTermId = 0;\n    int termId = 0;\n    int termOffset = 0;\n    int sessionId = 0;\n    boolean hasJoinPosition = false;\n    boolean hasSessionId = false;\n    boolean isReliable = true;\n    boolean isRejoin = true;\n    boolean isSparse = true;\n    boolean isTether = true;\n    boolean isResponse = false;\n    InferableBoolean group = InferableBoolean.INFER;\n    int receiverWindowLength;\n    long untetheredWindowLimitTimeoutNs;\n    long untetheredLingerTimeoutNs;\n    long untetheredRestingTimeoutNs;\n\n    static SubscriptionParams getSubscriptionParams(\n        final ChannelUri channelUri, final MediaDriver.Context context, final int publisherTermBufferLength)\n    {\n        final SubscriptionParams params = new SubscriptionParams();\n\n        final String sessionIdStr = channelUri.get(CommonContext.SESSION_ID_PARAM_NAME);\n        if (null != sessionIdStr)\n        {\n            params.sessionId = Integer.parseInt(sessionIdStr);\n            params.hasSessionId = true;\n        }\n\n        if (CONTROL_MODE_RESPONSE.equals(channelUri.get(MDC_CONTROL_MODE_PARAM_NAME)))\n        {\n            params.isResponse = true;\n        }\n\n        int count = 0;\n\n        final String initialTermIdStr = channelUri.get(INITIAL_TERM_ID_PARAM_NAME);\n        count = initialTermIdStr != null ? count + 1 : count;\n\n        final String termIdStr = channelUri.get(TERM_ID_PARAM_NAME);\n        count = termIdStr != null ? count + 1 : count;\n\n        final String termOffsetStr = channelUri.get(TERM_OFFSET_PARAM_NAME);\n        count = termOffsetStr != null ? count + 1 : count;\n\n        if (count > 0)\n        {\n            if (count < 3)\n            {\n                throw new IllegalArgumentException(\"params must be used as a complete set: \" +\n                    INITIAL_TERM_ID_PARAM_NAME + \" \" +\n                    TERM_ID_PARAM_NAME + \" \" +\n                    TERM_OFFSET_PARAM_NAME + \" channel=\" + channelUri);\n            }\n\n            params.initialTermId = Integer.parseInt(initialTermIdStr);\n            params.termId = Integer.parseInt(termIdStr);\n            params.termOffset = Integer.parseInt(termOffsetStr);\n\n            if (params.termOffset < 0 || params.termOffset > LogBufferDescriptor.TERM_MAX_LENGTH)\n            {\n                throw new IllegalArgumentException(\n                    TERM_OFFSET_PARAM_NAME + \"=\" + params.termOffset + \" out of range: channel=\" + channelUri);\n            }\n\n            if ((params.termOffset & (FrameDescriptor.FRAME_ALIGNMENT - 1)) != 0)\n            {\n                throw new IllegalArgumentException(\n                    TERM_OFFSET_PARAM_NAME + \"=\" + params.termOffset +\n                    \" must be a multiple of FRAME_ALIGNMENT: channel=\" + channelUri);\n            }\n\n            if (params.termId - params.initialTermId < 0)\n            {\n                throw new IllegalStateException(\n                    \"difference greater than 2^31 - 1: \" + INITIAL_TERM_ID_PARAM_NAME + \"=\" +\n                    params.initialTermId + \" when \" + TERM_ID_PARAM_NAME + \"=\" + params.termId + \" channel=\" +\n                    channelUri);\n            }\n\n            params.hasJoinPosition = true;\n        }\n\n        final String reliableStr = channelUri.get(RELIABLE_STREAM_PARAM_NAME);\n        params.isReliable = null != reliableStr ? \"true\".equals(reliableStr) : context.reliableStream();\n\n        final String rejoinStr = channelUri.get(REJOIN_PARAM_NAME);\n        params.isRejoin = null != rejoinStr ? \"true\".equals(rejoinStr) : context.rejoinStream();\n\n        final String tetherStr = channelUri.get(TETHER_PARAM_NAME);\n        params.isTether = null != tetherStr ? \"true\".equals(tetherStr) : context.tetherSubscriptions();\n\n        final String sparseStr = channelUri.get(SPARSE_PARAM_NAME);\n        params.isSparse = null != sparseStr ? \"true\".equals(sparseStr) : context.termBufferSparseFile();\n\n        final String groupStr = channelUri.get(GROUP_PARAM_NAME);\n        params.group = null != groupStr ? InferableBoolean.parse(groupStr) : context.receiverGroupConsideration();\n\n        final int rcvWndLength = UdpChannel.parseBufferLength(channelUri, RECEIVER_WINDOW_LENGTH_PARAM_NAME);\n        params.receiverWindowLength = Configuration.receiverWindowLength(\n            0 != publisherTermBufferLength ? publisherTermBufferLength :\n            (channelUri.isIpc() ? context.ipcTermBufferLength() : context.publicationTermBufferLength()),\n            0 != rcvWndLength ? rcvWndLength : context.initialWindowLength());\n\n        params.getUntetheredWindowLimitTimeout(channelUri, context);\n        params.getUntetheredLingerTimeout(channelUri, context);\n        params.getUntetheredRestingTimeout(channelUri, context);\n        return params;\n    }\n\n    private void getUntetheredWindowLimitTimeout(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        untetheredWindowLimitTimeoutNs = getTimeoutNs(\n            channelUri, UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME, ctx.untetheredWindowLimitTimeoutNs());\n    }\n\n    private void getUntetheredLingerTimeout(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        untetheredLingerTimeoutNs =\n            getTimeoutNs(channelUri, UNTETHERED_LINGER_TIMEOUT_PARAM_NAME, ctx.untetheredLingerTimeoutNs());\n        if (Aeron.NULL_VALUE == untetheredLingerTimeoutNs)\n        {\n            untetheredLingerTimeoutNs = untetheredWindowLimitTimeoutNs;\n        }\n    }\n\n    private void getUntetheredRestingTimeout(final ChannelUri channelUri, final MediaDriver.Context ctx)\n    {\n        untetheredRestingTimeoutNs = getTimeoutNs(\n            channelUri, UNTETHERED_RESTING_TIMEOUT_PARAM_NAME, ctx.untetheredRestingTimeoutNs());\n    }\n\n    private static long getTimeoutNs(final ChannelUri channelUri, final String paramName, final long defaultValue)\n    {\n        final String timeoutString = channelUri.get(paramName);\n        return null != timeoutString ? SystemUtil.parseDuration(paramName, timeoutString) : defaultValue;\n    }\n\n    static void validateInitialWindowForRcvBuf(\n        final SubscriptionParams params,\n        final String channel,\n        final int channelSocketRcvbufLength,\n        final MediaDriver.Context ctx,\n        final String existingChannel)\n    {\n        if (0 != channelSocketRcvbufLength && params.receiverWindowLength > channelSocketRcvbufLength)\n        {\n            throw new IllegalStateException(\n                \"Initial window greater than SO_RCVBUF for channel: rcv-wnd=\" + params.receiverWindowLength +\n                \" so-rcvbuf=\" + channelSocketRcvbufLength +\n                (null == existingChannel ? \"\" : (\" existingChannel=\" + existingChannel)) + \" channel=\" + channel);\n        }\n        else if (0 == channelSocketRcvbufLength && params.receiverWindowLength > ctx.osDefaultSocketRcvbufLength())\n        {\n            throw new IllegalStateException(\n                \"Initial window greater than SO_RCVBUF for channel: rcv-wnd=\" + params.receiverWindowLength +\n                \" so-rcvbuf=\" + ctx.osDefaultSocketRcvbufLength() + \" (OS default)\" +\n                (null == existingChannel ? \"\" : (\" existingChannel=\" + existingChannel)) + \" channel=\" + channel);\n        }\n    }\n\n    public String toString()\n    {\n        return \"SubscriptionParams\" +\n            \"\\n{\" +\n            \"\\n    initialTermId=\" + initialTermId +\n            \"\\n    termId=\" + termId +\n            \"\\n    termOffset=\" + termOffset +\n            \"\\n    sessionId=\" + sessionId +\n            \"\\n    hasJoinPosition=\" + hasJoinPosition +\n            \"\\n    hasSessionId=\" + hasSessionId +\n            \"\\n    isReliable=\" + isReliable +\n            \"\\n    isRejoin=\" + isRejoin +\n            \"\\n    isSparse=\" + isSparse +\n            \"\\n    isTether=\" + isTether +\n            \"\\n    isResponse=\" + isResponse +\n            \"\\n    group=\" + group +\n            \"\\n    receiverWindowLength=\" + receiverWindowLength +\n            \"\\n    untetheredWindowLimitTimeoutNs=\" + untetheredWindowLimitTimeoutNs +\n            \"\\n    untetheredRestingTimeoutNs=\" + untetheredRestingTimeoutNs +\n            \"\\n    untetheredLingerTimeoutNs=\" + untetheredLingerTimeoutNs +\n            \"\\n}\";\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/TaggedMulticastFlowControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\n\nimport java.net.InetSocketAddress;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Minimum multicast sender flow control strategy only for tagged members identified by a receiver tag or ASF key.\n * <p>\n * Flow control is set to minimum of tracked tagged receivers.\n * <p>\n * Tracking of tagged receivers is done as long as they continue to send Status Messages. Once SMs stop, the receiver\n * tracking for that receiver will time out after a given number of nanoseconds.\n */\npublic class TaggedMulticastFlowControl extends AbstractMinMulticastFlowControl\n{\n    /**\n     * URI param value to identify this {@link FlowControl} strategy.\n     */\n    public static final String FC_PARAM_VALUE = \"tagged\";\n\n    TaggedMulticastFlowControl()\n    {\n        super(true);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onStatusMessage(\n        final StatusMessageFlyweight flyweight,\n        final InetSocketAddress receiverAddress,\n        final long senderLimit,\n        final int initialTermId,\n        final int positionBitsToShift,\n        final long timeNs)\n    {\n        return processStatusMessage(\n            flyweight, senderLimit, initialTermId, positionBitsToShift, timeNs, matchesTag(flyweight));\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTriggerSendSetup(\n        final StatusMessageFlyweight flyweight,\n        final InetSocketAddress receiverAddress,\n        final long timeNs)\n    {\n        processSendSetupTrigger(flyweight, receiverAddress, timeNs, matchesTag(flyweight));\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onError(final ErrorFlyweight errorFlyweight, final InetSocketAddress receiverAddress, final long timeNs)\n    {\n        processError(errorFlyweight, receiverAddress, timeNs, matchesTag(errorFlyweight));\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    private boolean matchesTag(final StatusMessageFlyweight flyweight)\n    {\n        final int asfLength = flyweight.asfLength();\n        boolean result = false;\n\n        if (asfLength == SIZE_OF_LONG)\n        {\n            if (flyweight.groupTag() == super.groupTag())\n            {\n                result = true;\n            }\n        }\n        else if (asfLength >= SIZE_OF_INT)\n        {\n            // compatible check for ASF of first 4 bytes\n            final int offset = StatusMessageFlyweight.groupTagFieldOffset();\n\n            if (flyweight.getByte(offset) == PreferredMulticastFlowControl.PREFERRED_ASF_BYTES[0] &&\n                flyweight.getByte(offset + 1) == PreferredMulticastFlowControl.PREFERRED_ASF_BYTES[1] &&\n                flyweight.getByte(offset + 2) == PreferredMulticastFlowControl.PREFERRED_ASF_BYTES[2] &&\n                flyweight.getByte(offset + 3) == PreferredMulticastFlowControl.PREFERRED_ASF_BYTES[3])\n            {\n                result = true;\n            }\n        }\n\n        return result;\n    }\n\n    private boolean matchesTag(final ErrorFlyweight errorFlyweight)\n    {\n        return errorFlyweight.hasGroupTag() && errorFlyweight.groupTag() == super.groupTag();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/TaggedMulticastFlowControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\n\n/**\n * Supplier of {@link TaggedMulticastFlowControl} implementation strategies.\n */\npublic class TaggedMulticastFlowControlSupplier implements FlowControlSupplier\n{\n    /**\n     * Default constructor.\n     */\n    public TaggedMulticastFlowControlSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public FlowControl newInstance(final UdpChannel udpChannel, final int streamId, final long registrationId)\n    {\n        return new TaggedMulticastFlowControl();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/TerminationValidator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.DirectBuffer;\n\nimport java.io.File;\n\n/**\n * Validate if the driver should terminate based on the provided token.\n */\n@FunctionalInterface\npublic interface TerminationValidator\n{\n    /**\n     * Should the given termination request be considered valid or not.\n     *\n     * @param aeronDir    of the driver.\n     * @param tokenBuffer of the token in the request.\n     * @param tokenOffset of the token within the buffer.\n     * @param tokenLength of the token within the buffer.\n     * @return true if request is to be considered valid and the driver termination hook should be run or false if not.\n     */\n    boolean allowTermination(File aeronDir, DirectBuffer tokenBuffer, int tokenOffset, int tokenLength);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ThreadingMode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\n/**\n * Threading mode to be employed by the {@link org.agrona.concurrent.Agent}s in the {@link MediaDriver}.\n */\npublic enum ThreadingMode\n{\n    /**\n     * No threads are started in the {@link MediaDriver}.\n     * <p>\n     * All 3 {@link org.agrona.concurrent.Agent}s will be composed a {@link org.agrona.concurrent.CompositeAgent} and\n     * made runnable via an {@link org.agrona.concurrent.AgentInvoker} in the {@link MediaDriver.Context}.\n     */\n    INVOKER,\n\n    /**\n     * One thread shared by all 3 {@link org.agrona.concurrent.Agent}s.\n     */\n    SHARED,\n\n    /**\n     * One thread shared by both the {@link Sender} and {@link Receiver} agents,\n     * plus one for the {@link DriverConductor}.\n     */\n    SHARED_NETWORK,\n\n    /**\n     * 3 Threads, one dedicated to each of the {@link org.agrona.concurrent.Agent}s.\n     */\n    DEDICATED,\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/TimeTrackingNameResolver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CounterProvider;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.net.InetAddress;\n\nimport static java.util.Objects.requireNonNull;\n\nfinal class TimeTrackingNameResolver implements NameResolver, AutoCloseable\n{\n    private final NameResolver delegateResolver;\n    private final NanoClock clock;\n    private final DutyCycleTracker maxTimeTracker;\n\n    TimeTrackingNameResolver(\n        final NameResolver delegateResolver,\n        final NanoClock clock,\n        final DutyCycleTracker maxTimeTracker)\n    {\n        this.delegateResolver = requireNonNull(delegateResolver);\n        this.clock = requireNonNull(clock);\n        this.maxTimeTracker = requireNonNull(maxTimeTracker);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public InetAddress resolve(final String name, final String uriParamName, final boolean isReResolution)\n    {\n        final long beginNs = clock.nanoTime();\n        maxTimeTracker.update(beginNs);\n        InetAddress address = null;\n        try\n        {\n            address = delegateResolver.resolve(name, uriParamName, isReResolution);\n            return address;\n        }\n        finally\n        {\n            final long endNs = clock.nanoTime();\n            maxTimeTracker.measureAndUpdate(endNs);\n            logResolve(delegateResolver.getClass().getSimpleName(), endNs - beginNs, name, isReResolution, address);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String lookup(final String name, final String uriParamName, final boolean isReLookup)\n    {\n        final long beginNs = clock.nanoTime();\n        maxTimeTracker.update(beginNs);\n        String resolvedName = null;\n        try\n        {\n            resolvedName = delegateResolver.lookup(name, uriParamName, isReLookup);\n            return resolvedName;\n        }\n        finally\n        {\n            final long endNs = clock.nanoTime();\n            maxTimeTracker.measureAndUpdate(endNs);\n            logLookup(delegateResolver.getClass().getSimpleName(), endNs - beginNs, name, isReLookup, resolvedName);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void init(final CountersReader countersReader, final CounterProvider counterProvider)\n    {\n        delegateResolver.init(countersReader, counterProvider);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork(final long nowMs)\n    {\n        return delegateResolver.doWork(nowMs);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (delegateResolver instanceof AutoCloseable)\n        {\n            CloseHelper.close((AutoCloseable)delegateResolver);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String name()\n    {\n        return \"TimeTracking(\" + delegateResolver.name() + \")\";\n    }\n\n    static void logHostName(final long durationNs, final String hostName)\n    {\n    }\n\n    private static void logResolve(\n        final String resolverName,\n        final long durationNs,\n        final String name,\n        final boolean isReResolution,\n        final InetAddress resolvedAddress)\n    {\n    }\n\n    private static void logLookup(\n        final String resolverName,\n        final long durationNs,\n        final String name,\n        final boolean isReLookup,\n        final String resolvedName)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/UnicastFlowControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.net.InetSocketAddress;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\n\n/**\n * Default unicast sender flow control strategy.\n * <p>\n * Max of right edges.\n * No tracking of receivers.\n */\npublic class UnicastFlowControl implements FlowControl\n{\n    /**\n     * Multiple of receiver window to allow for a retransmit action.\n     */\n    private int retransmitReceiverWindowMultiple;\n\n    /**\n     * Default constructor.\n     */\n    public UnicastFlowControl()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onStatusMessage(\n        final StatusMessageFlyweight flyweight,\n        final InetSocketAddress receiverAddress,\n        final long senderLimit,\n        final int initialTermId,\n        final int positionBitsToShift,\n        final long timeNs)\n    {\n        final long position = computePosition(\n            flyweight.consumptionTermId(),\n            flyweight.consumptionTermOffset(),\n            positionBitsToShift,\n            initialTermId);\n\n        return Math.max(senderLimit, position + flyweight.receiverWindowLength());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTriggerSendSetup(\n        final StatusMessageFlyweight flyweight,\n        final InetSocketAddress receiverAddress,\n        final long timeNs)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onSetup(\n        final SetupFlyweight flyweight,\n        final long senderLimit,\n        final long senderPosition,\n        final int positionBitsToShift,\n        final long timeNs)\n    {\n        return senderLimit;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onError(final ErrorFlyweight errorFlyweight, final InetSocketAddress receiverAddress, final long timeNs)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void initialize(\n        final MediaDriver.Context context,\n        final CountersManager countersManager,\n        final UdpChannel udpChannel,\n        final int streamId,\n        final int sessionId,\n        final long registrationId,\n        final int initialTermId,\n        final int termBufferLength)\n    {\n        retransmitReceiverWindowMultiple = FlowControl.retransmitReceiverWindowMultiple(\n            udpChannel,\n            context.unicastFlowControlRetransmitReceiverWindowMultiple()\n        );\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onIdle(final long timeNs, final long senderLimit, final long senderPosition, final boolean isEos)\n    {\n        return senderLimit;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean hasRequiredReceivers()\n    {\n        return true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int maxRetransmissionLength(\n        final int termOffset,\n        final int resendLength,\n        final int termBufferLength,\n        final int mtuLength)\n    {\n        return FlowControl.calculateRetransmissionLength(\n            resendLength, termBufferLength, termOffset, retransmitReceiverWindowMultiple);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/UntetheredSubscription.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.concurrent.status.ReadablePosition;\n\nclass UntetheredSubscription\n{\n    @SuppressWarnings(\"JavadocVariable\")\n    enum State\n    {\n        ACTIVE,\n        LINGER,\n        RESTING\n    }\n\n    State state = State.ACTIVE;\n    long timeOfLastUpdateNs;\n    final SubscriptionLink subscriptionLink;\n    final ReadablePosition position;\n\n    UntetheredSubscription(final SubscriptionLink subscriptionLink, final ReadablePosition position, final long timeNs)\n    {\n        this.subscriptionLink = subscriptionLink;\n        this.position = position;\n        this.timeOfLastUpdateNs = timeNs;\n    }\n\n    void state(final State newState, final long nowNs, final int streamId, final int sessionId)\n    {\n        logStateChange(state, newState, subscriptionLink.registrationId, streamId, sessionId, nowNs);\n        state = newState;\n        timeOfLastUpdateNs = nowNs;\n    }\n\n    private void logStateChange(\n        final State oldState,\n        final State newState,\n        final long subscriptionId,\n        final int streamId,\n        final int sessionId,\n        final long nowNs)\n    {\n//        System.out.println(nowNs + \": subscriptionId=\" + subscriptionId + \", streamId=\" + streamId +\n//            \", sessionId=\" + sessionId + \", \" + oldState + \" -> \" + newState);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/buffer/FileStoreLogFactory.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.buffer;\n\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.StorageSpaceException;\nimport org.agrona.ErrorHandler;\nimport org.agrona.IoUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.nio.file.FileStore;\nimport java.nio.file.Files;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.computeLogLength;\n\n/**\n * Factory for creating {@link RawLog}s in the source publications or publication images directories as appropriate.\n */\npublic class FileStoreLogFactory implements LogFactory\n{\n    private static final String PUBLICATIONS = \"publications\";\n    private static final String IMAGES = \"images\";\n\n    private final long lowStorageWarningThreshold;\n    private final int filePageSize;\n    private final boolean checkStorage;\n    private final ErrorHandler errorHandler;\n    private final File publicationsDir;\n    private final File imagesDir;\n    private final FileStore fileStore;\n    private final AtomicCounter mappedBytesCounter;\n\n    /**\n     * Construct a {@link LogFactory} over a file store.\n     *\n     * @param dataDirectoryName          where the log buffers will be created.\n     * @param filePageSize               of the filesystem.\n     * @param checkStorage               for sufficient space before allocating files.\n     * @param lowStorageWarningThreshold when warnings about remaining space will begin.\n     * @param errorHandler               to call when an error is encountered.\n     * @param mappedBytesCounter         used to keep track of how many bytes are mapped by the driver.\n     */\n    public FileStoreLogFactory(\n        final String dataDirectoryName,\n        final int filePageSize,\n        final boolean checkStorage,\n        final long lowStorageWarningThreshold,\n        final ErrorHandler errorHandler,\n        final AtomicCounter mappedBytesCounter)\n    {\n        this.filePageSize = filePageSize;\n        this.lowStorageWarningThreshold = lowStorageWarningThreshold;\n        this.checkStorage = checkStorage;\n        this.errorHandler = errorHandler;\n        this.mappedBytesCounter = mappedBytesCounter;\n\n        final File dataDir = new File(dataDirectoryName);\n\n        publicationsDir = new File(dataDir, PUBLICATIONS);\n        imagesDir = new File(dataDir, IMAGES);\n\n        IoUtil.ensureDirectoryExists(publicationsDir, PUBLICATIONS);\n        IoUtil.ensureDirectoryExists(imagesDir, IMAGES);\n\n        try\n        {\n            fileStore = checkStorage ? Files.getFileStore(dataDir.toPath()) : null;\n        }\n        catch (final IOException ex)\n        {\n            throw new UncheckedIOException(ex);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n    }\n\n    /**\n     * Create new {@link RawLog} in the publications' directory for the supplied triplet.\n     *\n     * @param correlationId    to use to distinguish this publication\n     * @param termBufferLength length of each term\n     * @param useSparseFiles   for the log buffer.\n     * @return the newly allocated {@link RawLog}\n     */\n    public RawLog newPublication(final long correlationId, final int termBufferLength, final boolean useSparseFiles)\n    {\n        return newInstance(publicationsDir, correlationId, termBufferLength, useSparseFiles);\n    }\n\n    /**\n     * Create new {@link RawLog} in the rebuilt publication images directory for the supplied triplet.\n     *\n     * @param correlationId    to use to distinguish this connection\n     * @param termBufferLength to use for the log buffer\n     * @param useSparseFiles   for the log buffer.\n     * @return the newly allocated {@link RawLog}\n     */\n    public RawLog newImage(final long correlationId, final int termBufferLength, final boolean useSparseFiles)\n    {\n        return newInstance(imagesDir, correlationId, termBufferLength, useSparseFiles);\n    }\n\n    private RawLog newInstance(\n        final File rootDir,\n        final long correlationId,\n        final int termLength,\n        final boolean useSparseFiles)\n    {\n        final long logLength = computeLogLength(termLength, filePageSize);\n        checkStorage(logLength);\n\n        final File location = streamLocation(rootDir, correlationId);\n\n        return new MappedRawLog(\n            location, useSparseFiles, logLength, termLength, filePageSize, errorHandler, mappedBytesCounter);\n    }\n\n    private void checkStorage(final long logLength)\n    {\n        if (checkStorage)\n        {\n            final long usableSpace = getUsableSpace();\n\n            if (usableSpace < logLength)\n            {\n                throw new StorageSpaceException(\n                    \"insufficient usable storage for new log of length=\" + logLength + \" usable=\" + usableSpace +\n                    \" in \" + fileStore);\n            }\n\n            if (usableSpace <= lowStorageWarningThreshold)\n            {\n                final String msg =\n                    \"space is running low: threshold=\" + lowStorageWarningThreshold +\n                    \" usable=\" + usableSpace + \" in \" + fileStore;\n\n                errorHandler.onError(new AeronException(msg, AeronException.Category.WARN));\n            }\n        }\n    }\n\n    private long getUsableSpace()\n    {\n        long usableSpace = 0;\n\n        try\n        {\n            usableSpace = fileStore.getUsableSpace();\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return usableSpace;\n    }\n\n    private static File streamLocation(final File rootDir, final long correlationId)\n    {\n        final String fileName = correlationId + \".logbuffer\";\n\n        return new File(rootDir, fileName);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/buffer/LogFactory.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.buffer;\n\n/**\n * Factory interface for creating the log buffers under publications and images.\n */\npublic interface LogFactory extends AutoCloseable\n{\n    /**\n     * {@inheritDoc}\n     */\n    void close();\n\n    /**\n     * Create a new {@link RawLog} for a publication.\n     *\n     * @param correlationId    which is the original registration id for a publication.\n     * @param termBufferLength length of the buffer for each term.\n     * @param useSparseFiles   should the file be sparse so the pages are only allocated as required.\n     * @return the newly created {@link RawLog}\n     */\n    RawLog newPublication(long correlationId, int termBufferLength, boolean useSparseFiles);\n\n    /**\n     * Create a new {@link RawLog} for an image of a publication.\n     *\n     * @param correlationId    assigned to uniquely identify an image on a driver.\n     * @param termBufferLength length of the buffer for each term.\n     * @param useSparseFiles   should the file be sparse so the pages are only allocated as required.\n     * @return the newly created {@link RawLog}\n     */\n    RawLog newImage(long correlationId, int termBufferLength, boolean useSparseFiles);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/buffer/MappedRawLog.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.buffer;\n\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.BufferUtil;\nimport org.agrona.ErrorHandler;\nimport org.agrona.IoUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.StandardOpenOption;\nimport java.util.EnumSet;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static java.nio.channels.FileChannel.MapMode.READ_WRITE;\nimport static java.nio.file.StandardOpenOption.*;\nimport static org.agrona.BitUtil.align;\n\n/**\n * Encapsulates responsibility for mapping the files into memory used by the log partitions.\n */\nclass MappedRawLog implements RawLog\n{\n    private static final int ONE_GIG = 1 << 30;\n    private static final EnumSet<StandardOpenOption> FILE_OPTIONS = EnumSet.of(CREATE_NEW, READ, WRITE);\n    private static final EnumSet<StandardOpenOption> SPARSE_FILE_OPTIONS = EnumSet.of(CREATE_NEW, READ, WRITE, SPARSE);\n\n    private final int termLength;\n    private final long logLength;\n    private final UnsafeBuffer[] termBuffers = new UnsafeBuffer[PARTITION_COUNT];\n    private final UnsafeBuffer logMetaDataBuffer;\n    private final ErrorHandler errorHandler;\n    private final AtomicCounter mappedBytesCounter;\n    private File logFile;\n    private MappedByteBuffer[] mappedBuffers;\n\n    MappedRawLog(\n        final File location,\n        final boolean useSparseFiles,\n        final long logLength,\n        final int termLength,\n        final int filePageSize,\n        final ErrorHandler errorHandler,\n        final AtomicCounter mappedBytesCounter)\n    {\n        this.termLength = termLength;\n        this.errorHandler = errorHandler;\n        this.logFile = location;\n        this.logLength = logLength;\n        this.mappedBytesCounter = mappedBytesCounter;\n\n        final EnumSet<StandardOpenOption> options = useSparseFiles ? SPARSE_FILE_OPTIONS : FILE_OPTIONS;\n        try\n        {\n            try (FileChannel logChannel = FileChannel.open(logFile.toPath(), options))\n            {\n                logChannel.truncate(logLength); // set file size like the C driver does\n                if (logLength <= Integer.MAX_VALUE)\n                {\n                    final MappedByteBuffer mappedBuffer = logChannel.map(READ_WRITE, 0, logLength);\n                    mappedBuffer.order(ByteOrder.LITTLE_ENDIAN);\n                    mappedBuffers = new MappedByteBuffer[]{ mappedBuffer };\n\n                    for (int i = 0; i < PARTITION_COUNT; i++)\n                    {\n                        termBuffers[i] = new UnsafeBuffer(mappedBuffer, i * termLength, termLength);\n                    }\n\n                    logMetaDataBuffer = new UnsafeBuffer(\n                        mappedBuffer, (int)(logLength - LOG_META_DATA_LENGTH), LOG_META_DATA_LENGTH);\n                }\n                else\n                {\n                    mappedBuffers = new MappedByteBuffer[PARTITION_COUNT + 1];\n\n                    for (int i = 0; i < PARTITION_COUNT; i++)\n                    {\n                        final MappedByteBuffer buffer = logChannel.map(READ_WRITE, termLength * (long)i, termLength);\n                        buffer.order(ByteOrder.LITTLE_ENDIAN);\n                        mappedBuffers[i] = buffer;\n                        termBuffers[i] = new UnsafeBuffer(buffer, 0, termLength);\n                    }\n\n                    final int metaDataMappingLength = align(LOG_META_DATA_LENGTH, filePageSize);\n                    final long metaDataSectionOffset = termLength * (long)PARTITION_COUNT;\n\n                    final MappedByteBuffer metaDataMappedBuffer = logChannel.map(\n                        READ_WRITE, metaDataSectionOffset, metaDataMappingLength);\n                    metaDataMappedBuffer.order(ByteOrder.LITTLE_ENDIAN);\n\n                    mappedBuffers[LOG_META_DATA_SECTION_INDEX] = metaDataMappedBuffer;\n                    logMetaDataBuffer = new UnsafeBuffer(\n                        metaDataMappedBuffer,\n                        metaDataMappingLength - LOG_META_DATA_LENGTH,\n                        LOG_META_DATA_LENGTH);\n                }\n\n                if (!useSparseFiles)\n                {\n                    preTouchPages(termBuffers, termLength, filePageSize);\n                }\n\n                mappedBytesCounter.getAndAddRelease(logLength);\n            }\n        }\n        catch (final IOException ex)\n        {\n            IoUtil.delete(logFile, true);\n            throw new UncheckedIOException(ex);\n        }\n    }\n\n    public int termLength()\n    {\n        return termLength;\n    }\n\n    public boolean free()\n    {\n        final MappedByteBuffer[] mappedBuffers = this.mappedBuffers;\n        if (null != mappedBuffers)\n        {\n            this.mappedBuffers = null;\n            for (int i = 0; i < mappedBuffers.length; i++)\n            {\n                BufferUtil.free(mappedBuffers[i]);\n            }\n\n            mappedBytesCounter.getAndAddRelease(-logLength);\n\n            logMetaDataBuffer.wrap(0, 0);\n            for (int i = 0; i < termBuffers.length; i++)\n            {\n                termBuffers[i].wrap(0, 0);\n            }\n        }\n\n        if (null != logFile)\n        {\n            if (!logFile.delete() && logFile.exists())\n            {\n                return false;\n            }\n\n            logFile = null;\n        }\n\n        return true;\n    }\n\n    public void close()\n    {\n        if (!free())\n        {\n            errorHandler.onError(new AeronException(\"unable to delete \" + logFile, AeronException.Category.WARN));\n        }\n    }\n\n    public UnsafeBuffer[] termBuffers()\n    {\n        return termBuffers;\n    }\n\n    public UnsafeBuffer metaData()\n    {\n        return logMetaDataBuffer;\n    }\n\n    public ByteBuffer[] sliceTerms()\n    {\n        final ByteBuffer[] terms = new ByteBuffer[PARTITION_COUNT];\n\n        if (termLength < ONE_GIG)\n        {\n            final MappedByteBuffer buffer = mappedBuffers[0];\n            for (int i = 0; i < PARTITION_COUNT; i++)\n            {\n                buffer.limit((termLength * i) + termLength).position(termLength * i);\n                terms[i] = buffer.slice();\n            }\n        }\n        else\n        {\n            for (int i = 0; i < PARTITION_COUNT; i++)\n            {\n                terms[i] = mappedBuffers[i].duplicate();\n            }\n        }\n\n        return terms;\n    }\n\n    public String fileName()\n    {\n        return logFile.getAbsolutePath();\n    }\n\n    private static void preTouchPages(final UnsafeBuffer[] buffers, final int length, final int pageSize)\n    {\n        for (final UnsafeBuffer buffer : buffers)\n        {\n            for (long i = 0; i < length; i += pageSize)\n            {\n                buffer.putByte((int)i, (byte)0);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/buffer/RawLog.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.buffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\n/**\n * Represents the collection of raw metadata and associated term buffers for publication and image logs.\n */\npublic interface RawLog extends AutoCloseable\n{\n    /**\n     * The length of each term in bytes.\n     *\n     * @return the length of each term in bytes.\n     */\n    int termLength();\n\n    /**\n     * An array of term buffer partitions.\n     *\n     * @return an array of the term buffer partitions.\n     */\n    UnsafeBuffer[] termBuffers();\n\n    /**\n     * The meta data storage for the overall log.\n     *\n     * @return the meta data storage for the overall log.\n     */\n    UnsafeBuffer metaData();\n\n    /**\n     * Slice the underlying buffer to provide an array of term buffers in order.\n     *\n     * @return slices of the underlying buffer to provide an array of term buffers in order.\n     */\n    ByteBuffer[] sliceTerms();\n\n    /**\n     * Get the fully qualified file name for the log file.\n     *\n     * @return the fully qualified file name for the log file.\n     */\n    String fileName();\n\n    /**\n     * Free the mapped buffers and delete the file.\n     *\n     * @return true if successful or false if it should be reattempted.\n     */\n    boolean free();\n\n    /**\n     * Close the resource regardless of if {@link #free()} has succeeded or not.\n     */\n    void close();\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/buffer/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Encapsulates the creation and management of buffers used to contain the log buffers.\n */\npackage io.aeron.driver.buffer;"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/exceptions/ActiveDriverException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.exceptions;\n\nimport io.aeron.exceptions.AeronException;\n\n/**\n * Indicates a currently active driver has been detected in {@code aeron.dir} during startup.\n */\npublic class ActiveDriverException extends AeronException\n{\n    private static final long serialVersionUID = 2539364003811295552L;\n\n    /**\n     * Exception with provided message and {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     *\n     * @param message to detail the exception.\n     */\n    public ActiveDriverException(final String message)\n    {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/exceptions/InvalidChannelException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.exceptions;\n\nimport io.aeron.ErrorCode;\nimport io.aeron.exceptions.ControlProtocolException;\n\n/**\n * Indicates an invalid URI for a channel has been received by the driver from a client.\n */\npublic class InvalidChannelException extends ControlProtocolException\n{\n    private static final long serialVersionUID = 8395688431913848255L;\n\n    /**\n     * Exception with provided message and {@link ErrorCode#INVALID_CHANNEL}.\n     *\n     * @param message to detail the exception.\n     */\n    public InvalidChannelException(final String message)\n    {\n        super(ErrorCode.INVALID_CHANNEL, message);\n    }\n\n    /**\n     * Exception with provided cause and {@link ErrorCode#INVALID_CHANNEL}.\n     *\n     * @param cause of the exception.\n     */\n    public InvalidChannelException(final Exception cause)\n    {\n        super(ErrorCode.INVALID_CHANNEL, cause);\n    }\n\n    /**\n     * Exception with provided message, cause and {@link ErrorCode#INVALID_CHANNEL}.\n     *\n     * @param message to detail the exception.\n     * @param cause of the exception.\n     */\n    public InvalidChannelException(final String message, final Exception cause)\n    {\n        super(ErrorCode.INVALID_CHANNEL, message, cause);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/exceptions/UnknownSubscriptionException.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.exceptions;\n\nimport io.aeron.exceptions.AeronException;\n\n/**\n * Indicates the subscription is unknown to the driver when a request to remove it has been received.\n */\npublic class UnknownSubscriptionException extends AeronException\n{\n    private static final long serialVersionUID = 8934006869519172332L;\n\n    /**\n     * Exception with provided message and {@link io.aeron.exceptions.AeronException.Category#ERROR}.\n     *\n     * @param message to detail the exception.\n     */\n    public UnknownSubscriptionException(final String message)\n    {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/exceptions/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * {@link io.aeron.driver.MediaDriver} specific exceptions which are all unchecked.\n */\npackage io.aeron.driver.exceptions;"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/CubicCongestionControl.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport io.aeron.driver.Configuration;\nimport io.aeron.driver.CongestionControl;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.status.PerImageIndicator;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.net.InetSocketAddress;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.CongestionControl.packOutcome;\n\n/**\n * CUBIC congestion control manipulation of the receiver window length.\n * <p>\n * <a target=\"_blank\" href=\"https://tools.ietf.org/id/draft-rhee-tcpm-cubic-02.txt\">\n * https://tools.ietf.org/id/draft-rhee-tcpm-cubic-02.txt</a> and\n * <a target=\"_blank\" href=\"https://dl.acm.org/doi/10.1145/1400097.1400105\">\n * https://dl.acm.org/doi/10.1145/1400097.1400105</a>\n * <p>\n * {@code W_cubic = C(T - K)^3 + w_max}\n * <p>\n * {@code K = cbrt(w_max * B / C)}\n * {@code w_max} = window size before reduction\n * {@code T} = time since last decrease\n * <p>\n * {@code C} = scaling constant (default 0.4)\n * {@code B} = multiplicative decrease (default 0.2)\n * <p>\n * at MTU=4K, max window=128KB (w_max = 32 MTUs), then K ~= 2.5 seconds.\n */\npublic class CubicCongestionControl implements CongestionControl\n{\n    /**\n     * URI param value to identify this {@link CongestionControl} strategy.\n     */\n    public static final String CC_PARAM_VALUE = \"cubic\";\n\n    private static final long SECOND_IN_NS = TimeUnit.SECONDS.toNanos(1);\n    private static final int INITCWND = 10;\n    private static final int RTT_TIMEOUT_MULTIPLE = 4;\n\n    private static final double C = 0.4;\n    private static final double B = 0.2;\n\n    private final int mtu;\n    private final int maxCwnd;\n    private final int initialWindowLength;\n    private final int maxWindowLength;\n\n    private double k;\n    private int w_max;\n    private int windowLength;\n    private int cwnd;\n\n    private long lastUpdateTimestampNs;\n    private long lastLossTimestampNs;\n    private long lastRttTimestampNs = 0;\n    private long rttNs;\n    private long rttTimeoutNs;\n    private final long windowUpdateTimeoutNs;\n\n    private final ErrorHandler errorHandler;\n    private final AtomicCounter rttIndicator;\n    private final AtomicCounter windowIndicator;\n\n    /**\n     * Construct a new {@link CongestionControl} instance for a received stream image using the Cubic algorithm.\n     *\n     * @param registrationId  for the publication image.\n     * @param udpChannel      for the publication image.\n     * @param streamId        for the publication image.\n     * @param sessionId       for the publication image.\n     * @param termLength      for the publication image.\n     * @param senderMtuLength for the publication image.\n     * @param controlAddress  for the publication image.\n     * @param sourceAddress   for the publication image.\n     * @param nanoClock       for the precise timing.\n     * @param context         for configuration options applied in the driver.\n     * @param countersManager for the driver.\n     */\n    @SuppressWarnings(\"this-escape\")\n    public CubicCongestionControl(\n        final long registrationId,\n        final UdpChannel udpChannel,\n        final int streamId,\n        final int sessionId,\n        final int termLength,\n        final int senderMtuLength,\n        final InetSocketAddress controlAddress,\n        final InetSocketAddress sourceAddress,\n        final NanoClock nanoClock,\n        final MediaDriver.Context context,\n        final CountersManager countersManager)\n    {\n        try\n        {\n            errorHandler = context.errorHandler();\n            mtu = senderMtuLength;\n\n            final int receiverWindowLength = 0 != udpChannel.receiverWindowLength() ?\n                udpChannel.receiverWindowLength() : context.initialWindowLength();\n            maxWindowLength = Configuration.receiverWindowLength(termLength, receiverWindowLength);\n\n            maxCwnd = maxWindowLength / mtu;\n            cwnd = Math.min(INITCWND, maxCwnd);\n            this.initialWindowLength = cwnd * mtu;\n            w_max = maxCwnd; // initially set w_max to max window and act in the TCP and concave region initially\n            k = StrictMath.cbrt((double)w_max * B / C);\n\n            // determine interval for adjustment based on heuristic of MTU, max window, and/or RTT estimate\n            rttNs = CubicCongestionControlConfiguration.INITIAL_RTT_NS;\n            windowUpdateTimeoutNs = rttNs;\n            rttTimeoutNs = rttNs * RTT_TIMEOUT_MULTIPLE;\n\n            rttIndicator = PerImageIndicator.allocate(\n                context.tempBuffer(),\n                \"rcv-cc-cubic-rtt\",\n                countersManager,\n                registrationId,\n                sessionId,\n                streamId,\n                udpChannel.originalUriString());\n\n            windowIndicator = PerImageIndicator.allocate(\n                context.tempBuffer(),\n                \"rcv-cc-cubic-wnd\",\n                countersManager,\n                registrationId,\n                sessionId,\n                streamId,\n                udpChannel.originalUriString());\n\n            windowLength = this.initialWindowLength;\n            rttIndicator.setRelease(0);\n            windowIndicator.setRelease(this.initialWindowLength);\n\n            lastLossTimestampNs = nanoClock.nanoTime();\n            lastUpdateTimestampNs = lastLossTimestampNs;\n        }\n        catch (final Exception ex)\n        {\n            close();\n            throw ex;\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(errorHandler, rttIndicator);\n        CloseHelper.close(errorHandler, windowIndicator);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean shouldMeasureRtt(final long nowNs)\n    {\n        return CubicCongestionControlConfiguration.MEASURE_RTT && ((lastRttTimestampNs + rttTimeoutNs) - nowNs < 0);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onRttMeasurementSent(final long nowNs)\n    {\n        lastRttTimestampNs = nowNs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onRttMeasurement(final long nowNs, final long rttNs, final InetSocketAddress srcAddress)\n    {\n        lastRttTimestampNs = nowNs;\n        this.rttNs = rttNs;\n        rttIndicator.setRelease(rttNs);\n        rttTimeoutNs = Math.max(rttNs, CubicCongestionControlConfiguration.INITIAL_RTT_NS) * RTT_TIMEOUT_MULTIPLE;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public long onTrackRebuild(\n        final long nowNs,\n        final long newConsumptionPosition,\n        final long lastSmPosition,\n        final long hwmPosition,\n        final long startingRebuildPosition,\n        final long endingRebuildPosition,\n        final boolean lossOccurred)\n    {\n        boolean forceStatusMessage = false;\n\n        if (lossOccurred)\n        {\n            forceStatusMessage = true;\n            w_max = cwnd;\n            k = StrictMath.cbrt((double)w_max * B / C);\n            cwnd = Math.max(1, (int)(cwnd * (1.0 - B)));\n            windowLength = cwnd * mtu;\n            windowIndicator.setRelease(windowLength);\n\n            lastLossTimestampNs = nowNs;\n        }\n        else if (cwnd < maxCwnd && ((lastUpdateTimestampNs + windowUpdateTimeoutNs) - nowNs < 0))\n        {\n            // W_cubic = C(T - K)^3 + w_max\n            final double durationSinceDecr = (double)(nowNs - lastLossTimestampNs) / (double)SECOND_IN_NS;\n            final double diffToK = durationSinceDecr - k;\n            final double incr = C * diffToK * diffToK * diffToK;\n\n            cwnd = Math.min(maxCwnd, w_max + (int)incr);\n\n            // if using TCP mode, then check to see if we are in the TCP region\n            if (CubicCongestionControlConfiguration.TCP_MODE && cwnd < w_max)\n            {\n                // W_tcp(t) = w_max * (1 - B) + 3 * B / (2 - B) * t / RTT\n                final double rttInSeconds = (double)rttNs / (double)SECOND_IN_NS;\n                final double wTcp = (double)w_max * (1.0 - B) +\n                    ((3.0 * B / (2.0 - B)) * (durationSinceDecr / rttInSeconds));\n\n                cwnd = Math.max(cwnd, (int)wTcp);\n            }\n\n            final int windowLength = cwnd * mtu;\n            if (windowLength != this.windowLength)\n            {\n                this.windowLength = windowLength;\n                windowIndicator.setRelease(windowLength);\n            }\n\n            lastUpdateTimestampNs = nowNs;\n        }\n        else if (1 == cwnd && newConsumptionPosition > lastSmPosition)\n        {\n            // force out an SM (and update of nextSmPosition) whenever the consumption position moves when\n            // window is at minimum.\n            forceStatusMessage = true;\n        }\n\n        return packOutcome(windowLength, forceStatusMessage);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int initialWindowLength()\n    {\n        return initialWindowLength;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int maxWindowLength()\n    {\n        return maxWindowLength;\n    }\n\n    int maxCongestionWindow()\n    {\n        return maxCwnd;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/CubicCongestionControlConfiguration.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport org.agrona.SystemUtil;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Configuration options to be applied when {@link CubicCongestionControl} is loaded.\n */\npublic final class CubicCongestionControlConfiguration\n{\n    private CubicCongestionControlConfiguration()\n    {\n    }\n\n    /**\n     * Property name for measuring RTT or using static constant based on initial value.\n     *\n     * @see CubicCongestionControlConfiguration#INITIAL_RTT_NS_PROP_NAME\n     */\n    public static final String MEASURE_RTT_PROP_NAME = \"aeron.CubicCongestionControl.measureRtt\";\n\n    /**\n     * Property name for initial RTT measurement in nanoseconds.\n     */\n    public static final String INITIAL_RTT_NS_PROP_NAME = \"aeron.CubicCongestionControl.initialRtt\";\n\n    /**\n     * Default initial RTT measurement in nanoseconds.\n     */\n    public static final long INITIAL_RTT_NS_DEFAULT = TimeUnit.MICROSECONDS.toNanos(100);\n\n    /**\n     * Property name for accounting for TCP behavior in low RTT values after a loss.\n     * <p>\n     * <b>WARNING:</b> Be aware that throughput utilization becomes important. Turning this on can result in being\n     * drastically be off the necessary throughput if utilization is low.\n     */\n    public static final String TCP_MODE_PROP_NAME = \"aeron.CubicCongestionControl.tcpMode\";\n\n    /**\n     * Should RTT be measured. Default is false.\n     * @see #MEASURE_RTT_PROP_NAME\n     */\n    public static final boolean MEASURE_RTT = \"true\".equals(System.getProperty(MEASURE_RTT_PROP_NAME));\n\n    /**\n     * Setting to be used for the initial RTT time when not measuring.\n     * @see #INITIAL_RTT_NS_PROP_NAME\n     */\n    public static final long INITIAL_RTT_NS = SystemUtil.getDurationInNanos(\n        INITIAL_RTT_NS_PROP_NAME, INITIAL_RTT_NS_DEFAULT);\n\n    /**\n     * Should TCP behaviour mode be on or off. Default is false (off).\n     * @see #TCP_MODE_PROP_NAME\n     */\n    public static final boolean TCP_MODE = \"true\".equals(System.getProperty(TCP_MODE_PROP_NAME));\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/CubicCongestionControlSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport io.aeron.driver.CongestionControl;\nimport io.aeron.driver.CongestionControlSupplier;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Supplier of {@link CubicCongestionControl} implementations.\n * <p>\n * <a target=\"_blank\" href=\"https://research.csc.ncsu.edu/netsrv/?q=content/bic-and-cubic\">\n *     https://research.csc.ncsu.edu/netsrv/?q=content/bic-and-cubic</a>\n */\npublic class CubicCongestionControlSupplier implements CongestionControlSupplier\n{\n    /**\n     * Construct a supplier for CubicCongestionControl.\n     */\n    public CubicCongestionControlSupplier()\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public CongestionControl newInstance(\n        final long registrationId,\n        final UdpChannel udpChannel,\n        final int streamId,\n        final int sessionId,\n        final int termLength,\n        final int senderMtuLength,\n        final InetSocketAddress controlAddress,\n        final InetSocketAddress sourceAddress,\n        final NanoClock nanoClock,\n        final MediaDriver.Context context,\n        final CountersManager countersManager)\n    {\n        return new CubicCongestionControl(\n            registrationId,\n            udpChannel,\n            streamId,\n            sessionId,\n            termLength,\n            senderMtuLength,\n            controlAddress,\n            sourceAddress,\n            nanoClock,\n            context,\n            countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/DebugChannelEndpointConfiguration.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport io.aeron.config.Config;\nimport io.aeron.config.DefaultType;\n\nimport static java.lang.Long.getLong;\nimport static java.lang.System.getProperty;\n\n/**\n * Configuration options to be applied when {@link DebugSendChannelEndpoint} and {@link DebugReceiveChannelEndpoint}\n * are load.\n */\npublic final class DebugChannelEndpointConfiguration\n{\n    private DebugChannelEndpointConfiguration()\n    {\n    }\n\n    /**\n     * Property name for receiver inbound data loss rate.\n     */\n    @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = false, existsInC = false)\n    public static final String RECEIVE_DATA_LOSS_RATE_PROP_NAME = \"aeron.debug.receive.data.loss.rate\";\n\n    /**\n     * Property name for receiver inbound data loss seed.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = false, existsInC = false)\n    public static final String RECEIVE_DATA_LOSS_SEED_PROP_NAME = \"aeron.debug.receive.data.loss.seed\";\n\n    /**\n     * Property name for receiver outbound control loss rate.\n     */\n    @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = false, existsInC = false)\n    public static final String RECEIVE_CONTROL_LOSS_RATE_PROP_NAME = \"aeron.debug.receive.control.loss.rate\";\n\n    /**\n     * Property name for receiver outbound control loss seed.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = false, existsInC = false)\n    public static final String RECEIVE_CONTROL_LOSS_SEED_PROP_NAME = \"aeron.debug.receive.control.loss.seed\";\n\n    /**\n     * Property name for sender outbound data loss rate.\n     */\n    @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = false, existsInC = false)\n    public static final String SEND_DATA_LOSS_RATE_PROP_NAME = \"aeron.debug.send.data.loss.rate\";\n\n    /**\n     * Property name for sender outbound data loss seed.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = false, existsInC = false)\n    public static final String SEND_DATA_LOSS_SEED_PROP_NAME = \"aeron.debug.send.data.loss.seed\";\n\n    /**\n     * Property name for sender inbound control loss rate.\n     */\n    @Config(defaultType = DefaultType.DOUBLE, defaultDouble = 0.0, hasContext = false, existsInC = false)\n    public static final String SEND_CONTROL_LOSS_RATE_PROP_NAME = \"aeron.debug.send.control.loss.rate\";\n\n    /**\n     * Property name for sender inbound control loss seed.\n     */\n    @Config(defaultType = DefaultType.LONG, defaultLong = -1, hasContext = false, existsInC = false)\n    public static final String SEND_CONTROL_LOSS_SEED_PROP_NAME = \"aeron.debug.send.control.loss.seed\";\n\n    private static final long RECEIVE_DATA_LOSS_SEED = getLong(RECEIVE_DATA_LOSS_SEED_PROP_NAME, -1);\n\n    private static final double RECEIVE_DATA_LOSS_RATE =\n        Double.parseDouble(getProperty(RECEIVE_DATA_LOSS_RATE_PROP_NAME, \"0.0\"));\n\n    private static final long RECEIVE_CONTROL_LOSS_SEED = getLong(RECEIVE_CONTROL_LOSS_SEED_PROP_NAME, -1);\n\n    private static final double RECEIVE_CONTROL_LOSS_RATE =\n        Double.parseDouble(getProperty(RECEIVE_CONTROL_LOSS_RATE_PROP_NAME, \"0.0\"));\n\n    private static final long SEND_DATA_LOSS_SEED = getLong(SEND_DATA_LOSS_SEED_PROP_NAME, -1);\n\n    private static final double SEND_DATA_LOSS_RATE =\n        Double.parseDouble(getProperty(SEND_DATA_LOSS_RATE_PROP_NAME, \"0.0\"));\n\n    private static final long SEND_CONTROL_LOSS_SEED = getLong(SEND_CONTROL_LOSS_SEED_PROP_NAME, -1);\n\n    private static final double SEND_CONTROL_LOSS_RATE =\n        Double.parseDouble(getProperty(SEND_CONTROL_LOSS_RATE_PROP_NAME, \"0.0\"));\n\n    /**\n     * Supplier of {@link LossGenerator}s with provided loss rate and seed for randomisation.\n     *\n     * @param lossRate to generate at.\n     * @param lossSeed to initialise.\n     * @return a {@link LossGenerator}s with provided loss rate and seed.\n     */\n    public static LossGenerator lossGeneratorSupplier(final double lossRate, final long lossSeed)\n    {\n        if (0 == lossRate)\n        {\n            return (address, buffer, length) -> false;\n        }\n\n        return new RandomLossGenerator(lossRate, lossSeed);\n    }\n\n    /**\n     * The supplier of {@link LossGenerator}s for the receiving end of a data stream.\n     *\n     * @return the supplier of {@link LossGenerator}s for the receiving end of a data stream.\n     */\n    public static LossGenerator receiveDataLossGeneratorSupplier()\n    {\n        return lossGeneratorSupplier(RECEIVE_DATA_LOSS_RATE, RECEIVE_DATA_LOSS_SEED);\n    }\n\n    /**\n     * The supplier of {@link LossGenerator}s for the receiving end of a control stream.\n     *\n     * @return the supplier of {@link LossGenerator}s for the receiving end of a control stream.\n     */\n    public static LossGenerator receiveControlLossGeneratorSupplier()\n    {\n        return lossGeneratorSupplier(RECEIVE_CONTROL_LOSS_RATE, RECEIVE_CONTROL_LOSS_SEED);\n    }\n\n    /**\n     * The supplier of {@link LossGenerator}s for the send end of a data stream.\n     *\n     * @return the supplier of {@link LossGenerator}s for the send end of a data stream.\n     */\n    public static LossGenerator sendDataLossGeneratorSupplier()\n    {\n        return lossGeneratorSupplier(SEND_DATA_LOSS_RATE, SEND_DATA_LOSS_SEED);\n    }\n\n    /**\n     * The supplier of {@link LossGenerator}s for the send end of a control stream.\n     *\n     * @return the supplier of {@link LossGenerator}s for the send end of a control stream.\n     */\n    public static LossGenerator sendControlLossGeneratorSupplier()\n    {\n        return lossGeneratorSupplier(SEND_CONTROL_LOSS_RATE, SEND_CONTROL_LOSS_SEED);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/DebugReceiveChannelEndpoint.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.DataPacketDispatcher;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\n\n/**\n * Debug implementation which can introduce loss.\n */\npublic class DebugReceiveChannelEndpoint extends ReceiveChannelEndpoint\n{\n    private final LossGenerator dataLossGenerator;\n    private final LossGenerator controlLossGenerator;\n    private final UnsafeBuffer controlBuffer = new UnsafeBuffer(ByteBuffer.allocate(0));\n\n    /**\n     * Construct a {@link ReceiveChannelEndpoint} with defaults for loss from {@link DebugChannelEndpointConfiguration}.\n     *\n     * @param udpChannel      for the media.\n     * @param dispatcher      for forwarding packets.\n     * @param statusIndicator for the endpoint for the channel.\n     * @param context         for configuration.\n     */\n    public DebugReceiveChannelEndpoint(\n        final UdpChannel udpChannel,\n        final DataPacketDispatcher dispatcher,\n        final AtomicCounter statusIndicator,\n        final MediaDriver.Context context)\n    {\n        this(\n            udpChannel,\n            dispatcher,\n            statusIndicator,\n            context,\n            DebugChannelEndpointConfiguration.receiveDataLossGeneratorSupplier(),\n            DebugChannelEndpointConfiguration.receiveControlLossGeneratorSupplier());\n    }\n\n    /**\n     * Construct a {@link ReceiveChannelEndpoint} with configuration for loss rate and seed.\n     *\n     * @param udpChannel           for the media.\n     * @param dispatcher           for forwarding packets.\n     * @param statusIndicator      for the endpoint for the channel.\n     * @param context              for configuration.\n     * @param dataLossGenerator    for the random loss on the data stream.\n     * @param controlLossGenerator for the random loss on the control stream.\n     */\n    public DebugReceiveChannelEndpoint(\n        final UdpChannel udpChannel,\n        final DataPacketDispatcher dispatcher,\n        final AtomicCounter statusIndicator,\n        final MediaDriver.Context context,\n        final LossGenerator dataLossGenerator,\n        final LossGenerator controlLossGenerator)\n    {\n        super(udpChannel, dispatcher, statusIndicator, context);\n\n        this.dataLossGenerator = dataLossGenerator;\n        this.controlLossGenerator = controlLossGenerator;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int sendTo(final ByteBuffer buffer, final InetSocketAddress remoteAddress)\n    {\n        int result = buffer.remaining();\n\n        controlBuffer.wrap(buffer, buffer.position(), buffer.remaining());\n        if (!controlLossGenerator.shouldDropFrame(remoteAddress, controlBuffer, buffer.remaining()))\n        {\n            result = super.sendTo(buffer, remoteAddress);\n        }\n\n        return result;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int onDataPacket(\n        final DataHeaderFlyweight header,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final int transportIndex)\n    {\n        int result = 0;\n\n        if (!dataLossGenerator.shouldDropFrame(\n            srcAddress, buffer, header.streamId(), header.sessionId(), header.termId(), header.termOffset(), length))\n        {\n            result = super.onDataPacket(header, buffer, length, srcAddress, transportIndex);\n        }\n\n        return result;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onSetupMessage(\n        final SetupFlyweight header,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final int transportIndex)\n    {\n        if (!dataLossGenerator.shouldDropFrame(srcAddress, buffer, header.frameLength()))\n        {\n            super.onSetupMessage(header, buffer, length, srcAddress, transportIndex);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onRttMeasurement(\n        final RttMeasurementFlyweight header,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final int transportIndex)\n    {\n        if (!dataLossGenerator.shouldDropFrame(srcAddress, buffer, header.frameLength()))\n        {\n            super.onRttMeasurement(header, buffer, length, srcAddress, transportIndex);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/DebugReceiveChannelEndpointSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.DataPacketDispatcher;\nimport io.aeron.driver.ReceiveChannelEndpointSupplier;\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Supply a debug implementation of a {@link ReceiveChannelEndpoint} for testing loss.\n */\npublic class DebugReceiveChannelEndpointSupplier implements ReceiveChannelEndpointSupplier\n{\n    /**\n     * Create a supplier for the DebugReceiveChannelEndpoint.\n     */\n    public DebugReceiveChannelEndpointSupplier()\n    {\n    }\n\n    /**\n     * Supply a new instance of a {@link DebugReceiveChannelEndpoint} for testing loss.\n     *\n     * @param udpChannel      on which the receiver is listening.\n     * @param dispatcher      for dispatching packets to publication images.\n     * @param statusIndicator for the channel.\n     * @param context         for the configuration of the driver.\n     * @return a new instance of a {@link DebugReceiveChannelEndpoint} for testing loss.\n     */\n    public ReceiveChannelEndpoint newInstance(\n        final UdpChannel udpChannel,\n        final DataPacketDispatcher dispatcher,\n        final AtomicCounter statusIndicator,\n        final MediaDriver.Context context)\n    {\n        return new DebugReceiveChannelEndpoint(udpChannel, dispatcher, statusIndicator, context);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/DebugSendChannelEndpoint.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport io.aeron.driver.DriverConductorProxy;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.protocol.NakFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\n\n/**\n * Debug implementation which can introduce loss.\n */\npublic class DebugSendChannelEndpoint extends SendChannelEndpoint\n{\n    private final LossGenerator dataLossGenerator;\n    private final LossGenerator controlLossGenerator;\n    private final UnsafeBuffer dataBuffer = new UnsafeBuffer();\n\n    /**\n     * Construct a {@link SendChannelEndpoint} with defaults for loss from {@link DebugChannelEndpointConfiguration}.\n     *\n     * @param udpChannel      for the media.\n     * @param statusIndicator for the endpoint for the channel.\n     * @param context         for configuration.\n     */\n    public DebugSendChannelEndpoint(\n        final UdpChannel udpChannel, final AtomicCounter statusIndicator, final MediaDriver.Context context)\n    {\n        this(\n            udpChannel,\n            statusIndicator,\n            context,\n            DebugChannelEndpointConfiguration.sendDataLossGeneratorSupplier(),\n            DebugChannelEndpointConfiguration.sendControlLossGeneratorSupplier());\n    }\n\n    /**\n     * Construct a {@link SendChannelEndpoint} with configuration for loss rate and seed.\n     *\n     * @param udpChannel           for the media.\n     * @param statusIndicator      for the endpoint for the channel.\n     * @param context              for configuration.\n     * @param dataLossGenerator    for the random loss on the data stream.\n     * @param controlLossGenerator for the random loss on the control stream.\n     */\n    public DebugSendChannelEndpoint(\n        final UdpChannel udpChannel,\n        final AtomicCounter statusIndicator,\n        final MediaDriver.Context context,\n        final LossGenerator dataLossGenerator,\n        final LossGenerator controlLossGenerator)\n    {\n        super(udpChannel, statusIndicator, context);\n\n        this.dataLossGenerator = dataLossGenerator;\n        this.controlLossGenerator = controlLossGenerator;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int send(final ByteBuffer buffer)\n    {\n        int count = buffer.remaining();\n\n        dataBuffer.wrap(buffer, buffer.position(), count);\n        if (!dataLossGenerator.shouldDropFrame(connectAddress, dataBuffer, count))\n        {\n            count = super.send(buffer);\n        }\n\n        return count;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStatusMessage(\n        final StatusMessageFlyweight msg,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress, final DriverConductorProxy conductorProxy)\n    {\n        if (!controlLossGenerator.shouldDropFrame(srcAddress, msg, msg.frameLength()))\n        {\n            super.onStatusMessage(msg, buffer, length, srcAddress, conductorProxy);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onNakMessage(\n        final NakFlyweight msg,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress)\n    {\n        if (!controlLossGenerator.shouldDropFrame(srcAddress, msg, msg.frameLength()))\n        {\n            super.onNakMessage(msg, buffer, length, srcAddress);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onRttMeasurement(\n        final RttMeasurementFlyweight msg,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress)\n    {\n        if (!controlLossGenerator.shouldDropFrame(srcAddress, msg, msg.frameLength()))\n        {\n            super.onRttMeasurement(msg, buffer, length, srcAddress);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/DebugSendChannelEndpointSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.SendChannelEndpointSupplier;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.status.AtomicCounter;\n\n/**\n * Supply a debug implementation of a {@link SendChannelEndpoint} for testing loss.\n */\npublic class DebugSendChannelEndpointSupplier implements SendChannelEndpointSupplier\n{\n    /**\n     * Create a supplier for the DebugSendChannelEndpoint.\n     */\n    public DebugSendChannelEndpointSupplier()\n    {\n    }\n\n    /**\n     * Supply a new instance of a {@link DebugSendChannelEndpoint} for testing loss.\n     *\n     * @param udpChannel      on which the sender will send.\n     * @param statusIndicator for the channel.\n     * @param context         for the configuration of the driver.\n     * @return a new instance of a {@link DebugSendChannelEndpoint} for testing loss.\n     */\n    public SendChannelEndpoint newInstance(\n        final UdpChannel udpChannel, final AtomicCounter statusIndicator, final MediaDriver.Context context)\n    {\n        return new DebugSendChannelEndpoint(udpChannel, statusIndicator, context);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/FixedLossGenerator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport org.agrona.collections.BiInt2ObjectMap;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetSocketAddress;\n\n/**\n * A loss generator implementation that will lose a fixed block of data from a specific term-id/term-offset pair. It\n * will only lose the selected block of data once for a given stream-id/session-id pair.\n */\npublic class FixedLossGenerator implements LossGenerator\n{\n    private final int termId;\n    private final int termOffset;\n    private final int length;\n    private final BiInt2ObjectMap<MutableInteger> streamAndSessionIdToOffsetMap = new BiInt2ObjectMap<>();\n\n    /**\n     * Set the range of messages to be dropped.\n     *\n     * @param termId        to be dropped\n     * @param termOffset    to be dropped\n     * @param length        to be dropped\n     */\n    public FixedLossGenerator(final int termId, final int termOffset, final int length)\n    {\n        this.termId = termId;\n        this.termOffset = termOffset;\n        this.length = length;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean shouldDropFrame(final InetSocketAddress address, final UnsafeBuffer buffer, final int length)\n    {\n        return false;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean shouldDropFrame(\n        final InetSocketAddress address,\n        final UnsafeBuffer buffer,\n        final int streamId,\n        final int sessionId,\n        final int termId,\n        final int termOffset,\n        final int length)\n    {\n        if (this.termId == termId)\n        {\n            MutableInteger maximumDroppedOffset = streamAndSessionIdToOffsetMap.get(streamId, sessionId);\n            if (null == maximumDroppedOffset)\n            {\n                maximumDroppedOffset = new MutableInteger(termOffset);\n                streamAndSessionIdToOffsetMap.put(streamId, sessionId, maximumDroppedOffset);\n            }\n\n            final boolean isRetransmission = maximumDroppedOffset.get() > termOffset;\n\n            if (!isRetransmission)\n            {\n                final int dropRegionOffset = this.termOffset;\n                final int dropRegionLimit = this.termOffset + this.length;\n                final int frameLimit = termOffset + length;\n                final boolean shouldDrop = frameLimit > dropRegionOffset && termOffset < dropRegionLimit;\n                if (shouldDrop)\n                {\n                    maximumDroppedOffset.set(termOffset + length);\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/LossGenerator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetSocketAddress;\n\n/**\n * Interface for loss generators.\n */\n@FunctionalInterface\npublic interface LossGenerator\n{\n    /**\n     * Should a frame be dropped?\n     *\n     * @param address The source address of the frame if inbound or the remote address if outbound.\n     * @param buffer  The buffer containing the frame data.\n     * @param length  The length of the frame.\n     * @return true to drop, false to process\n     */\n    boolean shouldDropFrame(InetSocketAddress address, UnsafeBuffer buffer, int length);\n\n    /**\n     * Should a frame be dropped?\n     *\n     * @param address    The source address of the frame if inbound or the remote address if outbound.\n     * @param buffer     The buffer containing the frame data.\n     * @param streamId   of the incoming frame.\n     * @param sessionId  of the incoming frame\n     * @param termId     of the incoming frame.\n     * @param termOffset of the incoming frame.\n     * @param length     of the incoming frame.\n     * @return true to drop, false to process\n     */\n    default boolean shouldDropFrame(\n        final InetSocketAddress address,\n        final UnsafeBuffer buffer,\n        final int streamId,\n        final int sessionId,\n        final int termId,\n        final int termOffset,\n        final int length)\n    {\n        return shouldDropFrame(address, buffer, length);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/MultiGapLossGenerator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport org.agrona.BitUtil;\nimport org.agrona.collections.BiInt2ObjectMap;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetSocketAddress;\n\n/**\n * A loss generator implementation that will introduce multiple data gaps from a specific term-id/term-offset pair. It\n * will only gaps once for a given stream-id/session-id pair.\n */\npublic class MultiGapLossGenerator implements LossGenerator\n{\n    private final int termId;\n    private final int gapRadixBits;\n    private final int gapRadixMask;\n    private final int gapLength;\n    private final int lastGapLimit;\n    private final BiInt2ObjectMap<MutableInteger> streamAndSessionIdToOffsetMap = new BiInt2ObjectMap<>();\n\n    /**\n     * Set the range of messages to be dropped.\n     *\n     * @param termId     to be dropped\n     * @param gapRadix   the initial offset and subsequent interval between gaps - must be a power of 2\n     * @param gapLength  length of each gap\n     * @param totalGaps  the total number of gaps\n     */\n    public MultiGapLossGenerator(\n        final int termId,\n        final int gapRadix,\n        final int gapLength,\n        final int totalGaps)\n    {\n        final int actualGapRadix = BitUtil.findNextPositivePowerOfTwo(gapRadix);\n\n        if (gapLength >= actualGapRadix)\n        {\n            throw new IllegalArgumentException(\"gapLength must be smaller than gapRadix\");\n        }\n\n        this.termId = termId;\n        this.gapRadixBits = Integer.numberOfTrailingZeros(actualGapRadix);\n        this.gapRadixMask = -actualGapRadix;\n        this.gapLength = gapLength;\n        this.lastGapLimit = (totalGaps * actualGapRadix) + gapLength;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean shouldDropFrame(final InetSocketAddress address, final UnsafeBuffer buffer, final int length)\n    {\n        return false;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean shouldDropFrame(\n        final InetSocketAddress address,\n        final UnsafeBuffer buffer,\n        final int streamId,\n        final int sessionId,\n        final int termId,\n        final int termOffset,\n        final int length)\n    {\n        if (this.termId != termId) // wrong term\n        {\n            return false;\n        }\n\n        if (termOffset > this.lastGapLimit) // this packet is past the last offset we'll drop\n        {\n            return false;\n        }\n\n        MutableInteger maximumDroppedOffset = streamAndSessionIdToOffsetMap.get(streamId, sessionId);\n        if (null == maximumDroppedOffset) // first time we've seen this term/stream/session\n        {\n            maximumDroppedOffset = new MutableInteger(termOffset);\n            streamAndSessionIdToOffsetMap.put(streamId, sessionId, maximumDroppedOffset);\n        }\n\n        if (maximumDroppedOffset.get() > termOffset) // this is an rx (offset is lower than max dropped)\n        {\n            return false;\n        }\n\n        final int frameLimit = termOffset + length;\n\n        if (termOffset != 0 && Integer.numberOfTrailingZeros(termOffset) >= this.gapRadixBits)\n        {\n            maximumDroppedOffset.set(frameLimit);\n            return true;\n        }\n\n        final int previousGapOffset = termOffset & this.gapRadixMask;\n        final int previousGapLimit = previousGapOffset + this.gapLength;\n\n        if (previousGapOffset > 0 && termOffset < previousGapLimit)\n        {\n            maximumDroppedOffset.set(frameLimit);\n            return true;\n        }\n\n        final int nextGapOffset = ((termOffset >> this.gapRadixBits) + 1) << this.gapRadixBits;\n        final int nextGapLimit = nextGapOffset + this.gapLength;\n\n        if (frameLimit > nextGapOffset && termOffset < nextGapLimit)\n        {\n            maximumDroppedOffset.set(frameLimit);\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/RandomLossGenerator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetSocketAddress;\nimport java.util.Random;\n\n/**\n * Uniform random loss generator which can be used for testing loss scenarios.\n */\npublic class RandomLossGenerator implements LossGenerator\n{\n    private final double lossRate;\n    private final Random random;\n\n    /**\n     * Construct loss generator with given loss rate as percentage.\n     *\n     * @param lossRate for generating loss.\n     */\n    public RandomLossGenerator(final double lossRate)\n    {\n        this(lossRate, -1);\n    }\n\n    /**\n     * Construct loss generator with given loss rate as percentage and random seed.\n     *\n     * @param lossRate for generating loss.\n     * @param lossSeed for random seeding.\n     */\n    public RandomLossGenerator(final double lossRate, final long lossSeed)\n    {\n        this.random = -1 == lossSeed ? new Random() : new Random(lossSeed);\n        this.lossRate = lossRate;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean shouldDropFrame(final InetSocketAddress address, final UnsafeBuffer buffer, final int length)\n    {\n        return random.nextDouble() <= lossRate;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/ext/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Package for extending the behaviour of the driver with features such as debug endpoints and congestion control\n * implementations.\n */\npackage io.aeron.driver.ext;"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/ControlMode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\n/**\n * Represents the control mode specified on a channel URI.\n */\npublic enum ControlMode\n{\n    /**\n     * The default when no control mode is specified. Will mean that the stream is a normal one without MDC or similar.\n     */\n    NONE,\n    /**\n     * The stream should use dynamic MDC.\n     */\n    DYNAMIC,\n    /**\n     * The stream should use manual MDC.\n     */\n    MANUAL,\n    /**\n     * The stream is being used as part of a response channel.\n     */\n    RESPONSE;\n\n    /**\n     * Indicates if this is a multi-destination control mode.\n     *\n     * @return <code>true</code> if this is multi-destination <code>false</code> otherwise.\n     */\n    public boolean isMultiDestination()\n    {\n        return this == DYNAMIC || this == MANUAL;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/ControlTransportPoller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.driver.Configuration;\nimport io.aeron.driver.DriverConductorProxy;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.NakFlyweight;\nimport io.aeron.protocol.ResponseSetupFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.BufferUtil;\nimport org.agrona.ErrorHandler;\nimport org.agrona.LangUtil;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.nio.TransportPoller;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ClosedChannelException;\nimport java.nio.channels.SelectionKey;\nimport java.util.ArrayList;\nimport java.util.function.Consumer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.frameType;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_ERR;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_NAK;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_RSP_SETUP;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_RTTM;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_SM;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\n\n/**\n * Encapsulates the polling of control {@link UdpChannelTransport}s using whatever means provides the lowest latency.\n */\npublic final class ControlTransportPoller extends UdpTransportPoller\n{\n    private final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(\n        Configuration.MAX_UDP_PAYLOAD_LENGTH, CACHE_LINE_LENGTH);\n    private final UnsafeBuffer unsafeBuffer = new UnsafeBuffer(byteBuffer);\n    private final NakFlyweight nakMessage = new NakFlyweight(unsafeBuffer);\n    private final StatusMessageFlyweight statusMessage = new StatusMessageFlyweight(unsafeBuffer);\n    private final RttMeasurementFlyweight rttMeasurement = new RttMeasurementFlyweight(unsafeBuffer);\n    private final ResponseSetupFlyweight responseSetup = new ResponseSetupFlyweight(unsafeBuffer);\n    private final ErrorFlyweight error = new ErrorFlyweight(unsafeBuffer);\n    private final DriverConductorProxy conductorProxy;\n    private final Consumer<SelectionKey> selectorPoller =\n        (selectionKey) -> poll((SendChannelEndpoint)selectionKey.attachment());\n    private final ArrayList<Transport> transports = new ArrayList<>();\n    private int totalBytesReceived;\n\n    /**\n     * Construct a new {@link TransportPoller} with an {@link ErrorHandler} for logging.\n     *\n     * @param errorHandler   which can be used to log errors and continue.\n     * @param conductorProxy to send message back to the conductor.\n     */\n    public ControlTransportPoller(final ErrorHandler errorHandler, final DriverConductorProxy conductorProxy)\n    {\n        super(errorHandler);\n        this.conductorProxy = conductorProxy;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        for (final Transport transport : transports)\n        {\n            cancelSingleKey(transport.selectionKey);\n            transport.sendChannelEndpoint.close();\n        }\n        super.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int pollTransports()\n    {\n        totalBytesReceived = 0;\n\n        if (transports.size() <= ITERATION_THRESHOLD)\n        {\n            for (final Transport transport : transports)\n            {\n                poll(transport.sendChannelEndpoint);\n            }\n        }\n        else\n        {\n            try\n            {\n                selector.selectNow(selectorPoller);\n            }\n            catch (final IOException ex)\n            {\n                errorHandler.onError(ex);\n            }\n        }\n\n        return totalBytesReceived;\n    }\n\n    /**\n     * Register channel for read.\n     *\n     * @param sendChannelEndpoint to associate with read.\n     */\n    public void registerForRead(final SendChannelEndpoint sendChannelEndpoint)\n    {\n        try\n        {\n            final SelectionKey key = sendChannelEndpoint.receiveDatagramChannel()\n                .register(selector, SelectionKey.OP_READ, sendChannelEndpoint);\n            transports.add(new Transport(sendChannelEndpoint, key));\n        }\n        catch (final ClosedChannelException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * Cancel a previous read registration.\n     *\n     * @param sendChannelEndpoint to be canceled and removed.\n     */\n    public void cancelRead(final SendChannelEndpoint sendChannelEndpoint)\n    {\n        for (int i = transports.size() - 1; i >= 0; i--)\n        {\n            final Transport transport = transports.get(i);\n            if (sendChannelEndpoint == transport.sendChannelEndpoint)\n            {\n                cancelSingleKey(transport.selectionKey);\n                ArrayListUtil.fastUnorderedRemove(transports, i);\n                break;\n            }\n        }\n    }\n\n    /**\n     * Check if any of the registered channels require re-resolution.\n     *\n     * @param nowNs          as the current time.\n     * @param conductorProxy for sending re-resolution requests.\n     */\n    public void checkForReResolutions(final long nowNs, final DriverConductorProxy conductorProxy)\n    {\n        for (final Transport transport : transports)\n        {\n            transport.sendChannelEndpoint.checkForReResolution(nowNs, conductorProxy);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ControlTransportPoller{}\";\n    }\n\n    private void poll(final SendChannelEndpoint channelEndpoint)\n    {\n        try\n        {\n            receive(channelEndpoint);\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n    }\n\n    private void receive(final SendChannelEndpoint channelEndpoint)\n    {\n        final InetSocketAddress srcAddress = channelEndpoint.receive(byteBuffer);\n\n        if (null != srcAddress)\n        {\n            final int length = byteBuffer.position();\n            totalBytesReceived += length;\n            if (channelEndpoint.isValidFrame(unsafeBuffer, length))\n            {\n                channelEndpoint.receiveHook(unsafeBuffer, length, srcAddress);\n\n                final int frameType = frameType(unsafeBuffer, 0);\n                if (HDR_TYPE_NAK == frameType)\n                {\n                    channelEndpoint.onNakMessage(nakMessage, unsafeBuffer, length, srcAddress);\n                }\n                else if (HDR_TYPE_SM == frameType)\n                {\n                    channelEndpoint.onStatusMessage(\n                        statusMessage, unsafeBuffer, length, srcAddress, conductorProxy);\n                }\n                else if (HDR_TYPE_ERR == frameType)\n                {\n                    channelEndpoint.onError(\n                        error, unsafeBuffer, length, srcAddress, conductorProxy);\n                }\n                else if (HDR_TYPE_RTTM == frameType)\n                {\n                    channelEndpoint.onRttMeasurement(rttMeasurement, unsafeBuffer, length, srcAddress);\n                }\n                else if (HDR_TYPE_RSP_SETUP == frameType)\n                {\n                    channelEndpoint.onResponseSetup(\n                        responseSetup, unsafeBuffer, length, srcAddress, conductorProxy);\n                }\n            }\n        }\n    }\n\n    private void cancelSingleKey(final SelectionKey selectionKey)\n    {\n        selectionKey.cancel();\n        selectNowWithoutProcessing();\n    }\n\n    private record Transport(SendChannelEndpoint sendChannelEndpoint, SelectionKey selectionKey)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/DataTransportPoller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.driver.Configuration;\nimport io.aeron.driver.DriverConductorProxy;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport org.agrona.BufferUtil;\nimport org.agrona.ErrorHandler;\nimport org.agrona.LangUtil;\nimport org.agrona.collections.ArrayListUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.nio.TransportPoller;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ClosedChannelException;\nimport java.nio.channels.SelectionKey;\nimport java.util.ArrayList;\nimport java.util.function.Consumer;\n\nimport static io.aeron.logbuffer.FrameDescriptor.frameType;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_DATA;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_PAD;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_RTTM;\nimport static io.aeron.protocol.HeaderFlyweight.HDR_TYPE_SETUP;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\n\n/**\n * Encapsulates the polling of data {@link UdpChannelTransport}s using whatever means provides the lowest latency.\n */\npublic final class DataTransportPoller extends UdpTransportPoller\n{\n    private final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(\n        Configuration.MAX_UDP_PAYLOAD_LENGTH, CACHE_LINE_LENGTH);\n    private final UnsafeBuffer unsafeBuffer = new UnsafeBuffer(byteBuffer);\n    private final DataHeaderFlyweight dataMessage = new DataHeaderFlyweight(unsafeBuffer);\n    private final SetupFlyweight setupMessage = new SetupFlyweight(unsafeBuffer);\n    private final RttMeasurementFlyweight rttMeasurement = new RttMeasurementFlyweight(unsafeBuffer);\n    private final Consumer<SelectionKey> selectorPoller =\n        (selectionKey) -> poll((ChannelAndTransport)selectionKey.attachment());\n    private final ArrayList<ChannelAndTransport> channelAndTransports = new ArrayList<>();\n    private int totalBytesReceived;\n\n    /**\n     * Construct a new {@link TransportPoller} with an {@link ErrorHandler} for logging.\n     *\n     * @param errorHandler which can be used to log errors and continue.\n     */\n    public DataTransportPoller(final ErrorHandler errorHandler)\n    {\n        super(errorHandler);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        for (final ChannelAndTransport transport : channelAndTransports)\n        {\n            final ReceiveChannelEndpoint receiveChannelEndpoint = transport.channelEndpoint;\n            cancelSingle(transport);\n            receiveChannelEndpoint.close();\n        }\n        super.close();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int pollTransports()\n    {\n        totalBytesReceived = 0;\n\n        if (channelAndTransports.size() <= ITERATION_THRESHOLD)\n        {\n            for (final ChannelAndTransport channelAndTransport : channelAndTransports)\n            {\n                poll(channelAndTransport);\n            }\n        }\n        else\n        {\n            try\n            {\n                selector.selectNow(selectorPoller);\n            }\n            catch (final IOException ex)\n            {\n                errorHandler.onError(ex);\n            }\n        }\n\n        return totalBytesReceived;\n    }\n\n    /**\n     * Register transport for reading with the poller.\n     *\n     * @param channelEndpoint to which the transport belongs.\n     * @param transport       new transport to be registered.\n     * @param transportIndex  for the transport in the channel.\n     */\n    public void registerForRead(\n        final ReceiveChannelEndpoint channelEndpoint, final UdpChannelTransport transport, final int transportIndex)\n    {\n        try\n        {\n            final ChannelAndTransport channelAndTransport = new ChannelAndTransport(\n                channelEndpoint, transport, transportIndex);\n\n            channelAndTransport.selectionKey = transport.receiveDatagramChannel()\n                .register(selector, SelectionKey.OP_READ, channelAndTransport);\n            channelAndTransports.add(channelAndTransport);\n        }\n        catch (final ClosedChannelException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * Cancel the reading of a given transport.\n     *\n     * @param channelEndpoint to which the transport belongs.\n     * @param transport       transport which was previously registered.\n     */\n    public void cancelRead(final ReceiveChannelEndpoint channelEndpoint, final UdpChannelTransport transport)\n    {\n        final ArrayList<ChannelAndTransport> transports = channelAndTransports;\n        for (int i = transports.size() - 1; i >= 0; i--)\n        {\n            final ChannelAndTransport channelAndTransport = transports.get(i);\n            if (channelEndpoint == channelAndTransport.channelEndpoint && transport == channelAndTransport.transport)\n            {\n                cancelSingle(channelAndTransport);\n                ArrayListUtil.fastUnorderedRemove(transports, i);\n                break;\n            }\n        }\n    }\n\n    /**\n     * Cancel all reads of a given channel.\n     *\n     * @param channelEndpoint to which the transport belongs.\n     */\n    public void cancelReadForAllTransports(final ReceiveChannelEndpoint channelEndpoint)\n    {\n        final ArrayList<ChannelAndTransport> transports = channelAndTransports;\n        for (int i = transports.size() - 1; i >= 0; i--)\n        {\n            final ChannelAndTransport transport = transports.get(i);\n            if (channelEndpoint == transport.channelEndpoint)\n            {\n                transport.selectionKey.cancel();\n                ArrayListUtil.fastUnorderedRemove(transports, i);\n            }\n        }\n\n        selectNowWithoutProcessing();\n    }\n\n    private void cancelSingle(final ChannelAndTransport transport)\n    {\n        transport.selectionKey.cancel();\n        selectNowWithoutProcessing();\n    }\n\n    /**\n     * Check if any of the registered channels or transports require re-resolution.\n     *\n     * @param nowNs          as the current time.\n     * @param conductorProxy for sending re-resolution requests.\n     */\n    public void checkForReResolutions(final long nowNs, final DriverConductorProxy conductorProxy)\n    {\n        for (final ChannelAndTransport channelAndTransport : channelAndTransports)\n        {\n            channelAndTransport.channelEndpoint.checkForReResolution(nowNs, conductorProxy);\n        }\n    }\n\n    private void poll(final ChannelAndTransport channelAndTransport)\n    {\n        try\n        {\n            receive(channelAndTransport);\n        }\n        catch (final Exception ex)\n        {\n            errorHandler.onError(ex);\n        }\n    }\n\n    private void receive(final ChannelAndTransport channelAndTransport)\n    {\n        final InetSocketAddress srcAddress = channelAndTransport.transport.receive(byteBuffer);\n\n        if (null != srcAddress)\n        {\n            final int length = byteBuffer.position();\n            totalBytesReceived += length;\n            final ReceiveChannelEndpoint channelEndpoint = channelAndTransport.channelEndpoint;\n\n            if (channelEndpoint.isValidFrame(unsafeBuffer, length))\n            {\n                channelEndpoint.receiveHook(unsafeBuffer, length, srcAddress);\n\n                final int frameType = frameType(unsafeBuffer, 0);\n                if (HDR_TYPE_DATA == frameType || HDR_TYPE_PAD == frameType)\n                {\n                    channelEndpoint.onDataPacket(\n                        dataMessage, unsafeBuffer, length, srcAddress, channelAndTransport.transportIndex);\n                }\n                else if (HDR_TYPE_SETUP == frameType)\n                {\n                    channelEndpoint.onSetupMessage(\n                        setupMessage, unsafeBuffer, length, srcAddress, channelAndTransport.transportIndex);\n                }\n                else if (HDR_TYPE_RTTM == frameType)\n                {\n                    channelEndpoint.onRttMeasurement(\n                        rttMeasurement, unsafeBuffer, length, srcAddress, channelAndTransport.transportIndex);\n                }\n            }\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"DataTransportPoller{}\";\n    }\n\n    static class ChannelAndTransport\n    {\n        final ReceiveChannelEndpoint channelEndpoint;\n        final UdpChannelTransport transport;\n        final int transportIndex;\n        SelectionKey selectionKey;\n\n        ChannelAndTransport(\n            final ReceiveChannelEndpoint channelEndpoint, final UdpChannelTransport transport, final int transportIndex)\n        {\n            this.channelEndpoint = channelEndpoint;\n            this.transport = transport;\n            this.transportIndex = transportIndex;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/ImageConnection.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport java.net.InetSocketAddress;\n\nabstract class ImageConnectionLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nabstract class ImageConnectionFields extends ImageConnectionLhsPadding\n{\n    /**\n     * Time of the last observed activity on this connection for tracking liveness.\n     */\n    public long timeOfLastActivityNs;\n\n    /**\n     * Time of the last observed from the source.\n     */\n    public long timeOfLastFrameNs;\n\n    /**\n     * End of the stream position.\n     */\n    public long eosPosition = Long.MAX_VALUE;\n\n    /**\n     * Was the end of the stream from source observed?\n     */\n    public boolean isEos;\n\n    /**\n     * Control address for the source.\n     */\n    public final InetSocketAddress controlAddress;\n\n    ImageConnectionFields(final InetSocketAddress controlAddress)\n    {\n        this.controlAddress = controlAddress;\n    }\n}\n\n/**\n * State tracking for a connection endpoint to an image from transport.\n */\npublic final class ImageConnection extends ImageConnectionFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n\n    /**\n     * Construct a representation of a connection to an image.\n     *\n     * @param timeOfLastActivityNs seen on this image.\n     * @param controlAddress       for the source of the image.\n     */\n    public ImageConnection(final long timeOfLastActivityNs, final InetSocketAddress controlAddress)\n    {\n        super(controlAddress);\n        this.timeOfLastActivityNs = timeOfLastActivityNs;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/InterfaceSearchAddress.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.agrona.AsciiEncoding;\nimport org.agrona.Strings;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.NetworkInterface;\nimport java.net.ProtocolFamily;\nimport java.net.SocketException;\nimport java.net.UnknownHostException;\n\nimport static io.aeron.driver.media.NetworkUtil.filterBySubnet;\nimport static io.aeron.driver.media.NetworkUtil.findAddressOnInterface;\nimport static java.lang.System.lineSeparator;\nimport static java.util.Objects.requireNonNull;\n\nrecord InterfaceSearchAddress(InetSocketAddress address, int subnetPrefix) implements UnresolvedInterface\n{\n    private static final InterfaceSearchAddress WILDCARD = new InterfaceSearchAddress(new InetSocketAddress(0), 0);\n\n    InterfaceSearchAddress\n    {\n        requireNonNull(address, \"address must not be null\");\n    }\n\n    public ResolvedInterface resolve(final boolean multicast, final ProtocolFamily protocolFamily)\n        throws SocketException\n    {\n        final NetworkInterface localInterface;\n        final InetSocketAddress resolvedAddress;\n\n        if (!multicast && address.getAddress().isAnyLocalAddress())\n        {\n            localInterface = null;\n            resolvedAddress = address;\n        }\n        else\n        {\n            localInterface = findInterface();\n            resolvedAddress = resolveToAddressOfInterface(localInterface);\n        }\n\n        return new ResolvedInterface(localInterface, resolvedAddress);\n    }\n\n    private InetSocketAddress resolveToAddressOfInterface(final NetworkInterface localInterface)\n    {\n        final InetAddress interfaceAddress = findAddressOnInterface(localInterface, address.getAddress(), subnetPrefix);\n\n        if (null == interfaceAddress)\n        {\n            throw new IllegalStateException(\"failed to find \" + address.getAddress() + \"/\" + subnetPrefix + \" in \" +\n                                            localInterface.getInterfaceAddresses());\n        }\n\n        return new InetSocketAddress(interfaceAddress, address.getPort());\n    }\n\n    private NetworkInterface findInterface() throws SocketException\n    {\n        final NetworkInterface[] filteredInterfaces = filterBySubnet(address.getAddress(), subnetPrefix);\n\n        for (final NetworkInterface networkInterface : filteredInterfaces)\n        {\n            if (networkInterface.isUp() && (networkInterface.supportsMulticast() || networkInterface.isLoopback()))\n            {\n                return networkInterface;\n            }\n        }\n\n        throw new IllegalArgumentException(noMatchingInterfacesError(filteredInterfaces));\n    }\n\n    private String noMatchingInterfacesError(final NetworkInterface[] filteredInterfaces) throws SocketException\n    {\n        final StringBuilder builder = new StringBuilder()\n            .append(\"Unable to find multicast or loopback interface matching criteria: \")\n            .append(address.getAddress())\n            .append('/')\n            .append(subnetPrefix);\n\n        if (filteredInterfaces.length > 0)\n        {\n            builder.append(lineSeparator()).append(\"  Candidates:\");\n\n            for (final NetworkInterface ifc : filteredInterfaces)\n            {\n                builder\n                    .append(lineSeparator())\n                    .append(\"  - Name: \")\n                    .append(ifc.getDisplayName())\n                    .append(\", addresses: \")\n                    .append(ifc.getInterfaceAddresses())\n                    .append(\", multicast: \")\n                    .append(ifc.supportsMulticast())\n                    .append(\", loopback: \")\n                    .append(ifc.isLoopback())\n                    .append(\", state: \")\n                    .append(ifc.isUp() ? \"UP\" : \"DOWN\");\n            }\n        }\n\n        return builder.toString();\n    }\n\n    static InterfaceSearchAddress wildcard()\n    {\n        return WILDCARD;\n    }\n\n    static InterfaceSearchAddress parse(final String addressAndPort) throws UnknownHostException\n    {\n        if (Strings.isEmpty(addressAndPort))\n        {\n            throw new IllegalArgumentException(\"search address string is null or empty\");\n        }\n\n        int slashIndex = -1;\n        int colonIndex = -1;\n        int rightAngleBraceIndex = -1;\n\n        for (int i = 0, length = addressAndPort.length(); i < length; i++)\n        {\n            switch (addressAndPort.charAt(i))\n            {\n                case '/':\n                    slashIndex = i;\n                    break;\n\n                case ':':\n                    colonIndex = i;\n                    break;\n\n                case ']':\n                    rightAngleBraceIndex = i;\n                    break;\n            }\n        }\n\n        final String addressString = getAddress(addressAndPort, slashIndex, colonIndex, rightAngleBraceIndex);\n        final InetAddress hostAddress = InetAddress.getByName(addressString);\n        final int port = getPort(addressAndPort, slashIndex, colonIndex, rightAngleBraceIndex);\n        final int defaultSubnetPrefix = hostAddress.getAddress().length * 8;\n        final int subnetPrefix = getSubnet(addressAndPort, slashIndex, defaultSubnetPrefix);\n\n        return new InterfaceSearchAddress(new InetSocketAddress(hostAddress, port), subnetPrefix);\n    }\n\n    private static int getSubnet(final String s, final int slashIndex, final int defaultSubnetPrefix)\n    {\n        if (slashIndex < 0)\n        {\n            return defaultSubnetPrefix;\n        }\n        else if (s.length() - 1 == slashIndex)\n        {\n            throw new IllegalArgumentException(\"invalid subnet: \" + s);\n        }\n\n        final int subnetStringBegin = slashIndex + 1;\n\n        return AsciiEncoding.parseIntAscii(s, subnetStringBegin, s.length() - subnetStringBegin);\n    }\n\n    private static int getPort(\n        final String s, final int slashIndex, final int colonIndex, final int rightAngleBraceIndex)\n    {\n        if (colonIndex < 0 || rightAngleBraceIndex > colonIndex)\n        {\n            return 0;\n        }\n        else if (s.length() - 1 == colonIndex)\n        {\n            throw new IllegalArgumentException(\"invalid port: \" + s);\n        }\n\n        final int portStringBegin = colonIndex + 1;\n        final int portStringEnd = slashIndex > 0 ? slashIndex : s.length();\n\n        return AsciiEncoding.parseIntAscii(s, portStringBegin, portStringEnd - portStringBegin);\n    }\n\n    private static String getAddress(\n        final String s, final int slashIndex, final int colonIndex, final int rightAngleBraceIndex)\n    {\n        int addressEnd = s.length();\n\n        if (slashIndex >= 0)\n        {\n            addressEnd = slashIndex;\n        }\n\n        if (colonIndex >= 0 && colonIndex > rightAngleBraceIndex)\n        {\n            addressEnd = colonIndex;\n        }\n\n        return s.substring(0, addressEnd);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/MultiRcvDestination.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.CommonContext;\nimport io.aeron.driver.DriverConductorProxy;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.ArrayUtil;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.PortUnreachableException;\nimport java.nio.ByteBuffer;\nimport java.util.Arrays;\n\nimport static io.aeron.driver.media.ReceiveChannelEndpoint.DESTINATION_ADDRESS_TIMEOUT;\nimport static io.aeron.driver.media.UdpChannelTransport.onSendError;\n\nfinal class MultiRcvDestination\n{\n    private static final ReceiveDestinationTransport[] EMPTY_TRANSPORTS = new ReceiveDestinationTransport[0];\n\n    private ReceiveDestinationTransport[] transports = EMPTY_TRANSPORTS;\n\n    void close(final ErrorHandler errorHandler)\n    {\n        CloseHelper.closeAll(errorHandler, transports);\n    }\n\n    void cancelRead(final ReceiveChannelEndpoint endpoint, final DataTransportPoller poller)\n    {\n        for (final ReceiveDestinationTransport transport : transports)\n        {\n            if (null != transport)\n            {\n                poller.cancelRead(endpoint, transport);\n            }\n        }\n    }\n\n    int addDestination(final ReceiveDestinationTransport transport)\n    {\n        int index = transports.length;\n\n        for (int i = 0, length = transports.length; i < length; i++)\n        {\n            if (null == transports[i])\n            {\n                index = i;\n                break;\n            }\n        }\n\n        transports = ArrayUtil.ensureCapacity(transports, index + 1);\n        transports[index] = transport;\n\n        return index;\n    }\n\n    void removeDestination(final int transportIndex)\n    {\n        transports[transportIndex] = null;\n    }\n\n    boolean hasDestination(final int transportIndex)\n    {\n        return transports.length > transportIndex && null != transports[transportIndex];\n    }\n\n    ReceiveDestinationTransport transport(final int transportIndex)\n    {\n        return transports[transportIndex];\n    }\n\n    int transport(final UdpChannel udpChannel)\n    {\n        final ReceiveDestinationTransport[] transports = this.transports;\n        int index = ArrayUtil.UNKNOWN_INDEX;\n\n        for (int i = 0, length = transports.length; i < length; i++)\n        {\n            final ReceiveDestinationTransport transport = transports[i];\n\n            if (null != transport && transport.udpChannel().equals(udpChannel))\n            {\n                index = i;\n                break;\n            }\n        }\n\n        return index;\n    }\n\n    void checkForReResolution(\n        final ReceiveChannelEndpoint channelEndpoint, final long nowNs, final DriverConductorProxy conductorProxy)\n    {\n        for (final ReceiveDestinationTransport transport : transports)\n        {\n            if (null != transport)\n            {\n                final UdpChannel udpChannel = transport.udpChannel();\n\n                if (udpChannel.hasExplicitControl() &&\n                    (transport.timeOfLastActivityNs() + DESTINATION_ADDRESS_TIMEOUT) < nowNs)\n                {\n                    transport.timeOfLastActivityNs(nowNs);\n                    conductorProxy.reResolveControl(\n                        udpChannel.channelUri().get(CommonContext.MDC_CONTROL_PARAM_NAME),\n                        udpChannel,\n                        channelEndpoint,\n                        transport.currentControlAddress());\n                }\n            }\n        }\n    }\n\n    void updateControlAddress(final int transportIndex, final InetSocketAddress newAddress)\n    {\n        if (ArrayUtil.UNKNOWN_INDEX != transportIndex)\n        {\n            final ReceiveDestinationTransport transport = transports[transportIndex];\n\n            if (null != transport)\n            {\n                transport.currentControlAddress(newAddress);\n            }\n        }\n    }\n\n    int sendToAll(\n        final ImageConnection[] imageConnections, final ByteBuffer buffer, final int bytesToSend, final long nowNs)\n    {\n        final ReceiveDestinationTransport[] transports = this.transports;\n        int minBytesSent = bytesToSend;\n\n        for (int lastIndex = imageConnections.length - 1, i = lastIndex; i >= 0; i--)\n        {\n            final ImageConnection connection = imageConnections[i];\n\n            if (null != connection)\n            {\n                final UdpChannelTransport transport = transports[i];\n                if (null != transport && ((connection.timeOfLastActivityNs + DESTINATION_ADDRESS_TIMEOUT) - nowNs > 0))\n                {\n                    buffer.position(0);\n                    minBytesSent = Math.min(minBytesSent, sendTo(transport, buffer, connection.controlAddress));\n                }\n            }\n        }\n\n        return minBytesSent;\n    }\n\n    static int sendTo(\n        final UdpChannelTransport transport, final ByteBuffer buffer, final InetSocketAddress remoteAddress)\n    {\n        int bytesSent = 0;\n        try\n        {\n            if (null != transport && null != transport.sendDatagramChannel && transport.sendDatagramChannel.isOpen())\n            {\n                transport.sendHook(buffer, remoteAddress);\n                bytesSent = transport.sendDatagramChannel.send(buffer, remoteAddress);\n            }\n        }\n        catch (final PortUnreachableException ignore)\n        {\n        }\n        catch (final IOException ex)\n        {\n            onSendError(ex, remoteAddress, transport.errorHandler);\n        }\n\n        return bytesSent;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"MultiRcvDestination{\" +\n            \"transports=\" + Arrays.toString(transports) +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/NamedInterface.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.agrona.Strings;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.InterfaceAddress;\nimport java.net.NetworkInterface;\nimport java.net.ProtocolFamily;\nimport java.net.SocketException;\n\nimport static io.aeron.driver.media.NetworkUtil.getProtocolFamily;\n\nrecord NamedInterface(String name, int port) implements UnresolvedInterface\n{\n    static final char OPENING_CHAR = '{';\n\n    public ResolvedInterface resolve(final boolean multicast, final ProtocolFamily protocolFamily)\n        throws SocketException\n    {\n        final NetworkInterface localInterface = NetworkInterface.getByName(name);\n        if (null == localInterface)\n        {\n            throw new IllegalArgumentException(\"unknown interface \" + name);\n        }\n        final InetSocketAddress address = resolveToFirstAddressOfFamily(localInterface, protocolFamily);\n        return new ResolvedInterface(localInterface, address);\n    }\n\n    private InetSocketAddress resolveToFirstAddressOfFamily(\n        final NetworkInterface localInterface,\n        final ProtocolFamily protocolFamily)\n    {\n        for (final InterfaceAddress interfaceAddress : localInterface.getInterfaceAddresses())\n        {\n            final InetAddress address = interfaceAddress.getAddress();\n            if (getProtocolFamily(address) == protocolFamily)\n            {\n                return new InetSocketAddress(address, port);\n            }\n        }\n\n        throw new IllegalStateException(\n            \"no \" + protocolFamily + \" addresses found on interface \" + localInterface.getName());\n    }\n\n    static NamedInterface parse(final String str)\n    {\n        if (Strings.isEmpty(str) || str.charAt(0) != OPENING_CHAR)\n        {\n            throw parseException(str);\n        }\n\n        final int nameEnd = str.lastIndexOf('}');\n        if (nameEnd <= 1)\n        {\n            throw parseException(str);\n        }\n        final String name = str.substring(1, nameEnd);\n\n        int port = 0;\n        final int trailing = str.length() - nameEnd - 1;\n        if (trailing > 0)\n        {\n            if (trailing == 1 || str.charAt(nameEnd + 1) != ':')\n            {\n                throw parseException(str);\n            }\n\n            try\n            {\n                port = Integer.parseUnsignedInt(str, nameEnd + 2, str.length(), 10);\n            }\n            catch (final NumberFormatException e)\n            {\n                throw parseException(str, e);\n            }\n\n            if (port > 0xFFFF)\n            {\n                throw parseException(\"port out of range: \" + port);\n            }\n        }\n\n        return new NamedInterface(name, port);\n    }\n\n    private static IllegalArgumentException parseException(final String str)\n    {\n        return parseException(str, null);\n    }\n\n    private static IllegalArgumentException parseException(final String str, final Throwable cause)\n    {\n        return new IllegalArgumentException(\n            \"expected format is '{interface_name}' or '{interface_name}:port', but got \" +\n            (str == null ? null : '\\'' + str + '\\''),\n            cause);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/NetworkInterfaceShim.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport java.net.InterfaceAddress;\nimport java.net.NetworkInterface;\nimport java.net.SocketException;\nimport java.util.Enumeration;\nimport java.util.List;\n\ninterface NetworkInterfaceShim\n{\n    Enumeration<NetworkInterface> getNetworkInterfaces() throws SocketException;\n\n    List<InterfaceAddress> getInterfaceAddresses(NetworkInterface ifc);\n\n    boolean isLoopback(NetworkInterface ifc) throws SocketException;\n\n    @SuppressWarnings(\"JavadocVariable\")\n    NetworkInterfaceShim DEFAULT = new NetworkInterfaceShim()\n    {\n        public Enumeration<NetworkInterface> getNetworkInterfaces() throws SocketException\n        {\n            return NetworkInterface.getNetworkInterfaces();\n        }\n\n        public List<InterfaceAddress> getInterfaceAddresses(final NetworkInterface ifc)\n        {\n            return ifc.getInterfaceAddresses();\n        }\n\n        public boolean isLoopback(final NetworkInterface ifc) throws SocketException\n        {\n            return ifc.isLoopback();\n        }\n    };\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/NetworkUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.agrona.BufferUtil;\n\nimport java.net.*;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.Enumeration;\nimport java.util.Objects;\n\nimport static java.lang.Boolean.compare;\nimport static java.lang.Integer.compare;\nimport static java.lang.Math.max;\nimport static java.lang.Math.min;\nimport static java.util.Collections.sort;\n\n/**\n * Collection of network specific utility functions.\n */\npublic final class NetworkUtil\n{\n    private NetworkUtil()\n    {\n    }\n\n    /**\n     * Search for a list of network interfaces that match the specified address and subnet prefix.\n     * The results will be ordered by the length of the subnet prefix\n     * ({@link InterfaceAddress#getNetworkPrefixLength()}). If no results match, then the collection\n     * will be empty.\n     *\n     * @param address      to search for on the {@link NetworkInterface}s.\n     * @param subnetPrefix to limit the search.\n     * @return {@link NetworkInterface}s that match the supplied criteria, ordered by the length\n     * of the subnet prefix. Empty if none match.\n     * @throws SocketException if an error occurs\n     */\n    public static NetworkInterface[] filterBySubnet(final InetAddress address, final int subnetPrefix)\n        throws SocketException\n    {\n        return filterBySubnet(NetworkInterfaceShim.DEFAULT, address, subnetPrefix);\n    }\n\n    /**\n     * Allocate a direct {@link ByteBuffer} that is padded at the end with at least alignment bytes.\n     *\n     * @param capacity  for the buffer.\n     * @param alignment for the buffer.\n     * @return the direct {@link ByteBuffer}.\n     */\n    public static ByteBuffer allocateDirectAlignedAndPadded(final int capacity, final int alignment)\n    {\n        final ByteBuffer buffer = BufferUtil.allocateDirectAligned(capacity + alignment, alignment);\n\n        buffer.limit(buffer.limit() - alignment);\n\n        return buffer.slice();\n    }\n\n    /**\n     * Format an address and port pair, so they can be used in a URI endpoint.\n     *\n     * @param address part of the endpoint.\n     * @param port    part of the endpoint.\n     * @return The formatted string for an address, IPv4 or IPv6, and port separated by a ':'.\n     */\n    public static String formatAddressAndPort(final InetAddress address, final int port)\n    {\n        if (address instanceof Inet6Address)\n        {\n            return \"[\" + address.getHostAddress() + \"]:\" + port;\n        }\n        else\n        {\n            return address.getHostAddress() + \":\" + port;\n        }\n    }\n\n    /**\n     * Get the {@link ProtocolFamily} to which the address belongs.\n     *\n     * @param address to get the {@link ProtocolFamily} for.\n     * @return the {@link ProtocolFamily} to which the address belongs.\n     */\n    public static ProtocolFamily getProtocolFamily(final InetAddress address)\n    {\n        if (address instanceof Inet4Address)\n        {\n            return StandardProtocolFamily.INET;\n        }\n        else if (address instanceof Inet6Address)\n        {\n            return StandardProtocolFamily.INET6;\n        }\n        else\n        {\n            throw new IllegalStateException(\"Unknown ProtocolFamily\");\n        }\n    }\n\n    /**\n     * Scans through to the local interfaces and returns the first bound InetAddress that matches the locally\n     * defined address and network mask. Useful for determining a return path network address for machines with\n     * a simple network topology. May not give the ideal result when a machine has multiple interfaces that match\n     * an outgoing network address.\n     *\n     * @param address used to scan for a matching local address\n     * @return first matching address bound to a local interface or null if none match.\n     * @throws SocketException if an underlying SocketException is thrown, e.g. when getting the network interfaces.\n     */\n    public static InetAddress findFirstMatchingLocalAddress(final InetAddress address) throws SocketException\n    {\n        final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();\n        while (networkInterfaces.hasMoreElements())\n        {\n            final NetworkInterface networkInterface = networkInterfaces.nextElement();\n            for (final InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses())\n            {\n                if (NetworkUtil.isMatchWithPrefix(\n                    address.getAddress(),\n                    interfaceAddress.getAddress().getAddress(),\n                    interfaceAddress.getNetworkPrefixLength()))\n                {\n                    return interfaceAddress.getAddress();\n                }\n            }\n        }\n\n        return null;\n    }\n\n\n    static NetworkInterface[] filterBySubnet(\n        final NetworkInterfaceShim shim, final InetAddress address, final int subnetPrefix)\n        throws SocketException\n    {\n        final ArrayList<FilterResult> filterResults = new ArrayList<>();\n        final byte[] queryAddress = address.getAddress();\n\n        final Enumeration<NetworkInterface> interfaces = shim.getNetworkInterfaces();\n        while (interfaces.hasMoreElements())\n        {\n            final NetworkInterface networkInterface = interfaces.nextElement();\n            final InterfaceAddress interfaceAddress = findAddressOnInterface(\n                shim, networkInterface, queryAddress, subnetPrefix);\n\n            if (null != interfaceAddress)\n            {\n                filterResults.add(new FilterResult(\n                    interfaceAddress, networkInterface, shim.isLoopback(networkInterface)));\n            }\n        }\n\n        sort(filterResults);\n\n        final int size = filterResults.size();\n        final NetworkInterface[] results = new NetworkInterface[size];\n        for (int i = 0; i < size; i++)\n        {\n            results[i] = filterResults.get(i).networkInterface;\n        }\n\n        return results;\n    }\n\n    static InetAddress findAddressOnInterface(\n        final NetworkInterface networkInterface, final InetAddress address, final int subnetPrefix)\n    {\n        final InterfaceAddress interfaceAddress = findAddressOnInterface(\n            NetworkInterfaceShim.DEFAULT, networkInterface, address.getAddress(), subnetPrefix);\n\n        return null == interfaceAddress ? null : interfaceAddress.getAddress();\n    }\n\n    static InterfaceAddress findAddressOnInterface(\n        final NetworkInterfaceShim shim,\n        final NetworkInterface networkInterface,\n        final byte[] queryAddress,\n        final int prefixLength)\n    {\n        for (final InterfaceAddress interfaceAddress : shim.getInterfaceAddresses(networkInterface))\n        {\n            if (null != interfaceAddress)\n            {\n                final InetAddress address = interfaceAddress.getAddress();\n                if (null != address)\n                {\n                    if (isMatchWithPrefix(address.getAddress(), queryAddress, prefixLength))\n                    {\n                        return interfaceAddress;\n                    }\n                }\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Matches to network address with the specified prefix length.  Only works with 4 and 16 byte addresses.\n     *\n     * @param candidate address to be matched\n     * @param expected address to match against (could be a network address - with 0s at the end)\n     * @param prefixLength the number of bit required to match.\n     * @return true if the leading prefixLength number of bits of the two addresses match.\n     */\n    public static boolean isMatchWithPrefix(final byte[] candidate, final byte[] expected, final int prefixLength)\n    {\n        if (candidate.length != expected.length)\n        {\n            return false;\n        }\n\n        if (candidate.length == 4)\n        {\n            final int mask = prefixLengthToIpV4Mask(prefixLength);\n\n            return (toInt(candidate) & mask) == (toInt(expected) & mask);\n        }\n        else if (candidate.length == 16)\n        {\n            final long upperMask = prefixLengthToIpV6Mask(min(prefixLength, 64));\n            final long lowerMask = prefixLengthToIpV6Mask(max(prefixLength - 64, 0));\n\n            return\n                (upperMask & toLong(candidate, 0)) == (upperMask & toLong(expected, 0)) &&\n                (lowerMask & toLong(candidate, 8)) == (lowerMask & toLong(expected, 8));\n        }\n\n        throw new IllegalArgumentException(\"how many bytes does an IP address have again?\");\n    }\n\n    static int prefixLengthToIpV4Mask(final int subnetPrefix)\n    {\n        return 0 == subnetPrefix ? 0 : -(1 << 32 - subnetPrefix);\n    }\n\n    private static long prefixLengthToIpV6Mask(final int subnetPrefix)\n    {\n        return 0 == subnetPrefix ? 0 : -(1L << 64 - subnetPrefix);\n    }\n\n    private static int toInt(final byte[] b)\n    {\n        return ((b[3] & 0xFF)) + ((b[2] & 0xFF) << 8) + ((b[1] & 0xFF) << 16) + ((b[0]) << 24);\n    }\n\n    static long toLong(final byte[] b, final int offset)\n    {\n        return ((b[offset + 7] & 0xFFL)) +\n            ((b[offset + 6] & 0xFFL) << 8) +\n            ((b[offset + 5] & 0xFFL) << 16) +\n            ((b[offset + 4] & 0xFFL) << 24) +\n            ((b[offset + 3] & 0xFFL) << 32) +\n            ((b[offset + 2] & 0xFFL) << 40) +\n            ((b[offset + 1] & 0xFFL) << 48) +\n            (((long)b[offset]) << 56);\n    }\n\n    static class FilterResult implements Comparable<FilterResult>\n    {\n        private final InterfaceAddress interfaceAddress;\n        private final NetworkInterface networkInterface;\n        private final boolean isLoopback;\n\n        FilterResult(\n            final InterfaceAddress interfaceAddress,\n            final NetworkInterface networkInterface,\n            final boolean isLoopback)\n        {\n            this.interfaceAddress = interfaceAddress;\n            this.networkInterface = networkInterface;\n            this.isLoopback = isLoopback;\n        }\n\n        public int compareTo(final FilterResult other)\n        {\n            if (isLoopback == other.isLoopback)\n            {\n                return -compare(\n                    interfaceAddress.getNetworkPrefixLength(),\n                    other.interfaceAddress.getNetworkPrefixLength());\n            }\n            else\n            {\n                return compare(isLoopback, other.isLoopback);\n            }\n        }\n\n        public boolean equals(final Object o)\n        {\n            return o instanceof FilterResult && compareTo((FilterResult)o) == 0;\n        }\n\n        public int hashCode()\n        {\n            return Objects.hash(interfaceAddress, networkInterface, isLoopback);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/PortManager.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport java.net.BindException;\nimport java.net.InetSocketAddress;\n\n/**\n * Interface for managing ports in use by UdpChannelTransports.\n */\npublic interface PortManager\n{\n    /**\n     * Called before an OS bind to adjust the bound address and to notify of the bind for the port manager.\n     *\n     * @param udpChannel  for the UDP endpoint being bound.\n     * @param bindAddress for the bind.\n     * @return InetSocketAddress to use for the bind.\n     * @throws BindException if the bind should not be performed.\n     */\n    InetSocketAddress getManagedPort(\n        UdpChannel udpChannel,\n        InetSocketAddress bindAddress) throws BindException;\n\n    /**\n     * Called after the bound socket is to be closed.\n     *\n     * @param bindAddress used for the bind previously.\n     */\n    void freeManagedPort(InetSocketAddress bindAddress);\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpoint.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.CommonContext;\nimport io.aeron.ErrorCode;\nimport io.aeron.driver.DataPacketDispatcher;\nimport io.aeron.driver.DriverConductorProxy;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ControlProtocolException;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.NakFlyweight;\nimport io.aeron.protocol.ResponseSetupFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.collections.Hashing;\nimport org.agrona.collections.Int2IntCounterMap;\nimport org.agrona.collections.Long2LongCounterMap;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.EpochNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.PortUnreachableException;\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.status.SystemCounterDescriptor.POSSIBLE_TTL_ASYMMETRY;\nimport static io.aeron.driver.status.SystemCounterDescriptor.SHORT_SENDS;\nimport static io.aeron.protocol.StatusMessageFlyweight.SEND_SETUP_FLAG;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\nabstract class ReceiveChannelEndpointLhsPadding extends UdpChannelTransport\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n\n    ReceiveChannelEndpointLhsPadding(\n        final UdpChannel udpChannel,\n        final InetSocketAddress endPointAddress,\n        final InetSocketAddress bindAddress,\n        final InetSocketAddress connectAddress,\n        final MediaDriver.Context context)\n    {\n        super(udpChannel, endPointAddress, bindAddress, connectAddress, context.receiverPortManager(), context);\n    }\n}\n\nabstract class ReceiveChannelEndpointHotFields extends ReceiveChannelEndpointLhsPadding\n{\n    /**\n     * Counter for the number of errors frames send back by this channel endpoint.\n     */\n    protected final AtomicCounter errorFramesSent;\n    long timeOfLastActivityNs;\n\n    ReceiveChannelEndpointHotFields(\n        final UdpChannel udpChannel,\n        final InetSocketAddress endPointAddress,\n        final InetSocketAddress bindAddress,\n        final InetSocketAddress connectAddress,\n        final MediaDriver.Context context)\n    {\n        super(udpChannel, endPointAddress, bindAddress, connectAddress, context);\n        errorFramesSent = context.systemCounters().get(SystemCounterDescriptor.ERROR_FRAMES_SENT);\n    }\n}\n\nabstract class ReceiveChannelEndpointRhsPadding extends ReceiveChannelEndpointHotFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n\n    ReceiveChannelEndpointRhsPadding(\n        final UdpChannel udpChannel,\n        final InetSocketAddress endPointAddress,\n        final InetSocketAddress bindAddress,\n        final InetSocketAddress connectAddress,\n        final MediaDriver.Context context)\n    {\n        super(udpChannel, endPointAddress, bindAddress, connectAddress, context);\n    }\n}\n\n/**\n * Aggregator of multiple subscriptions onto a single transport channel for the receiving of data and setup frames\n * plus sending status and NAK frames.\n */\npublic class ReceiveChannelEndpoint extends ReceiveChannelEndpointRhsPadding\n{\n    static final long DESTINATION_ADDRESS_TIMEOUT = TimeUnit.SECONDS.toNanos(5);\n\n    private final DataPacketDispatcher dispatcher;\n    private final ByteBuffer smBuffer;\n    private final StatusMessageFlyweight statusMessageFlyweight;\n    private final ByteBuffer nakBuffer;\n    private final NakFlyweight nakFlyweight;\n    private final ByteBuffer rttMeasurementBuffer;\n    private final RttMeasurementFlyweight rttMeasurementFlyweight;\n    private final ByteBuffer responseSetupBuffer;\n    private final ResponseSetupFlyweight responseSetupHeader;\n    private final ByteBuffer errorBuffer;\n    private final ErrorFlyweight errorFlyweight;\n    private final AtomicCounter shortSends;\n    private final AtomicCounter possibleTtlAsymmetry;\n    private final AtomicCounter statusIndicator;\n    private final Int2IntCounterMap refCountByStreamIdMap = new Int2IntCounterMap(0);\n    private final Long2LongCounterMap refCountByStreamIdAndSessionIdMap = new Long2LongCounterMap(0);\n    private final Int2IntCounterMap responseRefCountByStreamIdMap = new Int2IntCounterMap(0);\n    private final MultiRcvDestination multiRcvDestination;\n    private final CachedNanoClock cachedNanoClock;\n    private final Long groupTag;\n    private final boolean isChannelReceiveTimestampEnabled;\n    private final EpochNanoClock channelReceiveTimestampClock;\n\n    private final long receiverId;\n    private InetSocketAddress currentControlAddress;\n    private AtomicCounter localSocketAddressIndicator;\n    private int imageRefCount;\n\n    /**\n     * Construct the receiver end for data streams.\n     *\n     * @param udpChannel      configuration for the media.\n     * @param dispatcher      for forwarding packets.\n     * @param statusIndicator to indicate the status of the channel endpoint.\n     * @param context         for configuration.\n     */\n    public ReceiveChannelEndpoint(\n        final UdpChannel udpChannel,\n        final DataPacketDispatcher dispatcher,\n        final AtomicCounter statusIndicator,\n        final MediaDriver.Context context)\n    {\n        super(udpChannel, udpChannel.remoteData(), udpChannel.remoteData(), null, context);\n\n        this.dispatcher = dispatcher;\n        this.statusIndicator = statusIndicator;\n\n        shortSends = context.systemCounters().get(SHORT_SENDS);\n        possibleTtlAsymmetry = context.systemCounters().get(POSSIBLE_TTL_ASYMMETRY);\n\n        final ReceiveChannelEndpointThreadLocals threadLocals = context.receiveChannelEndpointThreadLocals();\n        smBuffer = threadLocals.statusMessageBuffer();\n        statusMessageFlyweight = threadLocals.statusMessageFlyweight();\n        nakBuffer = threadLocals.nakBuffer();\n        nakFlyweight = threadLocals.nakFlyweight();\n        rttMeasurementBuffer = threadLocals.rttMeasurementBuffer();\n        rttMeasurementFlyweight = threadLocals.rttMeasurementFlyweight();\n        responseSetupBuffer = threadLocals.responseSetupBuffer();\n        responseSetupHeader = threadLocals.responseSetupHeader();\n        errorBuffer = threadLocals.errorBuffer();\n        errorFlyweight = threadLocals.errorFlyweight();\n        cachedNanoClock = context.receiverCachedNanoClock();\n        timeOfLastActivityNs = cachedNanoClock.nanoTime();\n        receiverId = threadLocals.nextReceiverId();\n\n        this.groupTag = (null == udpChannel.groupTag()) ? context.receiverGroupTag() : udpChannel.groupTag();\n\n        multiRcvDestination = udpChannel.isManualControlMode() ? new MultiRcvDestination() : null;\n        currentControlAddress = udpChannel.localControl();\n\n        channelReceiveTimestampClock = context.channelReceiveTimestampClock();\n        isChannelReceiveTimestampEnabled = udpChannel.isChannelReceiveTimestampEnabled();\n    }\n\n    /**\n     * Set a channel binding status counter, if required (not used by control-mode=manual).\n     *\n     * @param counter to be set.\n     */\n    public void localSocketAddressIndicator(final AtomicCounter counter)\n    {\n        if (null != multiRcvDestination)\n        {\n            throw new IllegalStateException(\"local socket address indicator not used for MDS\");\n        }\n\n        localSocketAddressIndicator = counter;\n    }\n\n    /**\n     * Send contents of {@link java.nio.ByteBuffer} to the remote address.\n     *\n     * @param buffer        to send containing the payload.\n     * @param remoteAddress to send the payload to.\n     * @return number of bytes sent.\n     */\n    public int sendTo(final ByteBuffer buffer, final InetSocketAddress remoteAddress)\n    {\n        int bytesSent = 0;\n        try\n        {\n            if (null != sendDatagramChannel)\n            {\n                if (sendDatagramChannel.isOpen())\n                {\n                    sendHook(buffer, remoteAddress);\n                    bytesSent = sendDatagramChannel.send(buffer, remoteAddress);\n                }\n            }\n        }\n        catch (final PortUnreachableException ignore)\n        {\n        }\n        catch (final IOException ex)\n        {\n            onSendError(ex, remoteAddress, errorHandler);\n        }\n\n        return bytesSent;\n    }\n\n    /**\n     * The original URI String used when a subscription was added.\n     *\n     * @return the original URI String used when a subscription was added.\n     */\n    public String originalUriString()\n    {\n        return subscriptionUdpChannel().originalUriString();\n    }\n\n    /**\n     * Counter which indicates the status of the channel.\n     *\n     * @return counter which indicates the status of the channel.\n     */\n    public AtomicCounter statusIndicatorCounter()\n    {\n        return statusIndicator;\n    }\n\n    /**\n     * Indicate that the channel as active after successfully opening it.\n     */\n    public void indicateActive()\n    {\n        final long currentStatus = statusIndicator.get();\n        if (currentStatus != ChannelEndpointStatus.INITIALIZING)\n        {\n            throw new AeronException(\"channel cannot be registered unless INITIALIZING: status=\" +\n                ChannelEndpointStatus.status(currentStatus));\n        }\n\n        if (null == multiRcvDestination)\n        {\n            final String bindAddressAndPort = bindAddressAndPort();\n            statusIndicator.appendToLabel(bindAddressAndPort);\n            updateLocalSocketAddress(bindAddressAndPort);\n        }\n\n        statusIndicator.setRelease(ChannelEndpointStatus.ACTIVE);\n    }\n\n    /**\n     * Indicate that the channel is closing and should not be used for new subscriptions.\n     */\n    public void indicateClosing()\n    {\n        statusIndicator.setRelease(ChannelEndpointStatus.CLOSING);\n    }\n\n    /**\n     * The {@link ChannelEndpointStatus} value for the channel.\n     *\n     * <p>Must not be called after calling {@link #close()}, as it accesses a counter.</p>\n     *\n     * @return the {@link ChannelEndpointStatus} value for the channel.\n     */\n    public long status()\n    {\n        return statusIndicator.getAcquire();\n    }\n\n    /**\n     * Open the underlying sockets for the channel.\n     */\n    public void openChannel()\n    {\n        if (null == multiRcvDestination)\n        {\n            openDatagramChannel(statusIndicator);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        super.close();\n\n        CloseHelper.close(errorHandler, statusIndicator);\n        CloseHelper.close(errorHandler, localSocketAddressIndicator);\n        if (null != multiRcvDestination)\n        {\n            multiRcvDestination.close(errorHandler);\n        }\n    }\n\n    /**\n     * Increment the {@link io.aeron.driver.status.SystemCounterDescriptor#POSSIBLE_TTL_ASYMMETRY} counter.\n     */\n    public void possibleTtlAsymmetryEncountered()\n    {\n        possibleTtlAsymmetry.incrementRelease();\n    }\n\n    /**\n     * Called from the {@link io.aeron.driver.DriverConductor} to\n     * increment the reference count for a given stream id.\n     *\n     * @param streamId to increment the reference for.\n     * @return current reference count after the increment.\n     */\n    public int incRefToStream(final int streamId)\n    {\n        return refCountByStreamIdMap.incrementAndGet(streamId);\n    }\n\n    /**\n     * Called from the {@link io.aeron.driver.DriverConductor} to\n     * decrement the reference count for a given stream id.\n     *\n     * @param streamId to decrement the reference for.\n     * @return current reference count after the decrement.\n     */\n    public int decRefToStream(final int streamId)\n    {\n        final int count = refCountByStreamIdMap.decrementAndGet(streamId);\n\n        if (-1 == count)\n        {\n            refCountByStreamIdMap.remove(streamId);\n            throw new IllegalStateException(\"unknown stream Id: \" + streamId);\n        }\n\n        return count;\n    }\n\n    /**\n     * Called from the {@link io.aeron.driver.DriverConductor} to\n     * increment the reference count for a given stream id and session id.\n     *\n     * @param streamId  to increment the reference for.\n     * @param sessionId to increment the reference for.\n     * @return current reference count after the increment.\n     */\n    public long incRefToStreamAndSession(final int streamId, final int sessionId)\n    {\n        return refCountByStreamIdAndSessionIdMap.incrementAndGet(Hashing.compoundKey(streamId, sessionId));\n    }\n\n    /**\n     * Called from the {@link io.aeron.driver.DriverConductor} to\n     * decrement the reference count for a given stream id and session id.\n     *\n     * @param streamId  to increment the reference for.\n     * @param sessionId to increment the reference for.\n     * @return current reference count after the decrement.\n     */\n    public long decRefToStreamAndSession(final int streamId, final int sessionId)\n    {\n        final long key = Hashing.compoundKey(streamId, sessionId);\n        final long count = refCountByStreamIdAndSessionIdMap.decrementAndGet(key);\n\n        if (-1 == count)\n        {\n            refCountByStreamIdAndSessionIdMap.remove(key);\n            throw new IllegalStateException(\"unknown stream Id + session Id: \" + streamId + \" \" + sessionId);\n        }\n\n        return count;\n    }\n\n    /**\n     * Called from the {@link io.aeron.driver.DriverConductor} to\n     * increment the reference count for a given stream id for a response subscription while it is waiting to be\n     * connected.\n     *\n     * @param streamId to increment the reference for.\n     * @return current reference count after the increment.\n     */\n    public int incResponseRefToStream(final int streamId)\n    {\n        return responseRefCountByStreamIdMap.incrementAndGet(streamId);\n    }\n\n\n    /**\n     * Called from the {@link io.aeron.driver.DriverConductor} to\n     * decrement the reference count for a given stream id for a response subscription while it is waiting to be\n     * connected.\n     *\n     * @param streamId to decrement the reference for.\n     * @return current reference count after the decrement.\n     */\n    public int decResponseRefToStream(final int streamId)\n    {\n        final int count = responseRefCountByStreamIdMap.decrementAndGet(streamId);\n\n        if (-1 == count)\n        {\n            responseRefCountByStreamIdMap.remove(streamId);\n            throw new IllegalStateException(\"unknown stream Id: \" + streamId);\n        }\n\n        return count;\n    }\n\n    /**\n     * Total count of distinct subscriptions to streams.\n     *\n     * @return total count of distinct subscriptions to streams.\n     */\n    public int distinctSubscriptionCount()\n    {\n        return refCountByStreamIdMap.size() + refCountByStreamIdAndSessionIdMap.size();\n    }\n\n    /**\n     * Called from the {@link io.aeron.driver.DriverConductor} to\n     * determine if the channel should be closed for cleanup.\n     *\n     * @return true if the channel should be closed for cleanup.\n     */\n    public boolean shouldBeClosed()\n    {\n        return refCountByStreamIdMap.isEmpty() &&\n            refCountByStreamIdAndSessionIdMap.isEmpty() &&\n            responseRefCountByStreamIdMap.isEmpty() &&\n            !statusIndicator.isClosed() &&\n            imageRefCount <= 0;\n    }\n\n    /**\n     * Does the channel have an explicit control address as used with multi-destination-cast or not?\n     *\n     * @return does channel have an explicit control address or not?\n     */\n    public boolean hasExplicitControl()\n    {\n        return udpChannel.hasExplicitControl();\n    }\n\n    /**\n     * Does the channel have an explicit control address as used with multi-destination-cast or not?\n     *\n     * @return does channel have an explicit control address or not?\n     */\n    public InetSocketAddress explicitControlAddress()\n    {\n        return udpChannel.hasExplicitControl() ? currentControlAddress : null;\n    }\n\n    /**\n     * Has the channel got control of destinations for MDS.\n     *\n     * @return true if the channel got control of destinations for MDS.\n     */\n    public boolean hasDestinationControl()\n    {\n        return null != multiRcvDestination;\n    }\n\n    /**\n     * Validate that the channel allows destination control.\n     * <p>\n     * If not then a {@link ControlProtocolException} will be thrown.\n     */\n    public void validateAllowsDestinationControl()\n    {\n        if (null == multiRcvDestination)\n        {\n            throw new ControlProtocolException(ErrorCode.INVALID_CHANNEL, \"channel does not allow manual control\");\n        }\n    }\n\n    /**\n     * Is the primary transport multicast?\n     *\n     * @return true if the primary transport is multicast.\n     */\n    public boolean isMulticast()\n    {\n        return isMulticast(0);\n    }\n\n    /**\n     * Is a given transport index multicast?\n     *\n     * @param transportIndex to check for multicast.\n     * @return true if the transport index is multicast.\n     */\n    public boolean isMulticast(final int transportIndex)\n    {\n        if (null != multiRcvDestination)\n        {\n            return multiRcvDestination.transport(transportIndex).isMulticast();\n        }\n        else if (0 == transportIndex)\n        {\n            return super.isMulticast();\n        }\n\n        throw new IllegalStateException(\"isMulticast for unknown index \" + transportIndex);\n    }\n\n    /**\n     * Return the {@link UdpChannel} for the Subscription channel.\n     *\n     * @return {@link UdpChannel} for the Subscription channel.\n     */\n    public UdpChannel subscriptionUdpChannel()\n    {\n        return super.udpChannel;\n    }\n\n    /**\n     * Get the {@link UdpChannel} for the primary transport.\n     *\n     * @return the {@link UdpChannel} for the primary transport.\n     */\n    public UdpChannel udpChannel()\n    {\n        return udpChannel(0);\n    }\n\n    /**\n     * Get the {@link UdpChannel} for the transport index.\n     *\n     * @param transportIndex to the {@link UdpChannel}.\n     * @return the {@link UdpChannel} for the transport index.\n     */\n    public UdpChannel udpChannel(final int transportIndex)\n    {\n        if (null != multiRcvDestination && multiRcvDestination.hasDestination(transportIndex))\n        {\n            return multiRcvDestination.transport(transportIndex).udpChannel();\n        }\n        else if (0 == transportIndex)\n        {\n            return super.udpChannel();\n        }\n\n        throw new IllegalStateException(\"udpChannel for unknown index \" + transportIndex);\n    }\n\n    /**\n     * Has the channel been tagged?\n     *\n     * @return true if the channel has a tag identity.\n     */\n    public boolean hasTag()\n    {\n        return super.udpChannel.hasTag();\n    }\n\n    /**\n     * The tag identity for the channel.\n     *\n     * @return the tag identity for the channel.\n     */\n    public long tag()\n    {\n        return super.udpChannel.tag();\n    }\n\n    /**\n     * Does the channel have a matching tag?\n     *\n     * @param udpChannel with tag to match against.\n     * @return true if the channel matches on tag identity.\n     */\n    public boolean matchesTag(final UdpChannel udpChannel)\n    {\n        return udpChannel.matchesTag(super.udpChannel, currentControlAddress, null);\n    }\n\n    /**\n     * Get the multicast TTL for the primary transport.\n     *\n     * @return the multicast TTL for the primary transport.\n     */\n    public int multicastTtl()\n    {\n        return multicastTtl(0);\n    }\n\n    /**\n     * Get the multicast TTL for the transport index.\n     *\n     * @param transportIndex to get the multicast TTL for.\n     * @return the multicast TTL for the transport index.\n     */\n    public int multicastTtl(final int transportIndex)\n    {\n        if (null != multiRcvDestination)\n        {\n            return multiRcvDestination.transport(transportIndex).multicastTtl();\n        }\n        else if (0 == transportIndex)\n        {\n            return super.multicastTtl();\n        }\n\n        throw new IllegalStateException(\"multicastTtl for unknown index \" + transportIndex);\n    }\n\n    /**\n     * Add a destination to the channel to receive on.\n     *\n     * @param transport to add for the destination.\n     * @return index for the transport.\n     */\n    public int addDestination(final ReceiveDestinationTransport transport)\n    {\n        return multiRcvDestination.addDestination(transport);\n    }\n\n    /**\n     * Remove transport by index from the channel.\n     *\n     * @param transportIndex to be removed.\n     */\n    public void removeDestination(final int transportIndex)\n    {\n        multiRcvDestination.removeDestination(transportIndex);\n    }\n\n    /**\n     * Get the transport index for a given channel.\n     *\n     * @param udpChannel to look up the transport index for.\n     * @return the transport index if found otherwise {@link ArrayUtil#UNKNOWN_INDEX}.\n     */\n    public int destination(final UdpChannel udpChannel)\n    {\n        return multiRcvDestination.transport(udpChannel);\n    }\n\n    /**\n     * Get the transport destination for a given index.\n     *\n     * @param transportIndex to get.\n     * @return the transport destination for a given index.\n     */\n    public ReceiveDestinationTransport destination(final int transportIndex)\n    {\n        return multiRcvDestination.transport(transportIndex);\n    }\n\n    /**\n     * Does the channel have a destination for a given index value.\n     *\n     * @param transportIndex to check if a destination exists for.\n     * @return true if the channel has a given transport index.\n     */\n    public boolean hasDestination(final int transportIndex)\n    {\n        return null == multiRcvDestination ? (0 == transportIndex) : multiRcvDestination.hasDestination(transportIndex);\n    }\n\n    /**\n     * Callback to handle a received data packet.\n     *\n     * @param header         of the data first frame.\n     * @param buffer         containing the data packet.\n     * @param length         of the data packet.\n     * @param srcAddress     from which the data packet was received.\n     * @param transportIndex on which the packet was received.\n     * @return number of bytes applied as a result of this action.\n     */\n    public int onDataPacket(\n        final DataHeaderFlyweight header,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final int transportIndex)\n    {\n        if (isChannelReceiveTimestampEnabled && 0 != (header.flags() & DataHeaderFlyweight.BEGIN_FLAG))\n        {\n            applyChannelReceiveTimestamp(buffer, length);\n        }\n        updateTimeOfLastActivityNs(cachedNanoClock.nanoTime(), transportIndex);\n\n        return dispatcher.onDataPacket(this, header, buffer, length, srcAddress, transportIndex);\n    }\n\n    /**\n     * Callback to handle a received setup frame.\n     *\n     * @param header         of the setup frame.\n     * @param buffer         containing the setup frame.\n     * @param length         of the setup frame.\n     * @param srcAddress     the message came from.\n     * @param transportIndex on which the message was received.\n     */\n    public void onSetupMessage(\n        final SetupFlyweight header,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final int transportIndex)\n    {\n        updateTimeOfLastActivityNs(cachedNanoClock.nanoTime(), transportIndex);\n        dispatcher.onSetupMessage(this, header, srcAddress, transportIndex);\n    }\n\n    /**\n     * Callback to handle a received RTT Measurement frame.\n     *\n     * @param header         of the RTT Measurement frame.\n     * @param buffer         containing the RTT Measurement frame.\n     * @param length         of the RTT Measurement frame.\n     * @param srcAddress     the message came from.\n     * @param transportIndex on which the message was received.\n     */\n    public void onRttMeasurement(\n        final RttMeasurementFlyweight header,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final int transportIndex)\n    {\n        final long requestedReceiverId = header.receiverId();\n        if (requestedReceiverId == receiverId || requestedReceiverId == 0)\n        {\n            updateTimeOfLastActivityNs(cachedNanoClock.nanoTime(), transportIndex);\n            dispatcher.onRttMeasurement(this, header, srcAddress, transportIndex);\n        }\n    }\n\n    /**\n     * Send a Setup Eliciting Status Message to a source.\n     *\n     * @param transportIndex for the source.\n     * @param controlAddress for the source.\n     * @param sessionId      for the image.\n     * @param streamId       for the image.\n     */\n    public void sendSetupElicitingStatusMessage(\n        final int transportIndex, final InetSocketAddress controlAddress, final int sessionId, final int streamId)\n    {\n        smBuffer.clear();\n        statusMessageFlyweight\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .consumptionTermId(0)\n            .consumptionTermOffset(0)\n            .receiverWindowLength(0)\n            .receiverId(receiverId)\n            .groupTag(groupTag)\n            .flags(SEND_SETUP_FLAG);\n        smBuffer.limit(statusMessageFlyweight.frameLength());\n\n        send(smBuffer, statusMessageFlyweight.frameLength(), transportIndex, controlAddress);\n    }\n\n    /**\n     * Send RTT Measurement frame to a source.\n     *\n     * @param transportIndex  for the source.\n     * @param controlAddress  for the source.\n     * @param sessionId       for the image.\n     * @param streamId        for the image.\n     * @param echoTimestampNs timestamp to echo in a reply.\n     * @param receptionDelta  time in nanoseconds between receiving original request and sending Reply RTT Measurement.\n     * @param isReply         true if a reply.\n     */\n    public void sendRttMeasurement(\n        final int transportIndex,\n        final InetSocketAddress controlAddress,\n        final int sessionId,\n        final int streamId,\n        final long echoTimestampNs,\n        final long receptionDelta,\n        final boolean isReply)\n    {\n        rttMeasurementBuffer.clear();\n        rttMeasurementFlyweight\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .receiverId(receiverId)\n            .echoTimestampNs(echoTimestampNs)\n            .receptionDelta(receptionDelta)\n            .flags(isReply ? RttMeasurementFlyweight.REPLY_FLAG : 0);\n\n        send(rttMeasurementBuffer, RttMeasurementFlyweight.HEADER_LENGTH, transportIndex, controlAddress);\n    }\n\n    /**\n     * Send a Status Message back to a sources.\n     *\n     * @param controlAddresses of the sources.\n     * @param sessionId        of the image.\n     * @param streamId         of the image.\n     * @param termId           of the image to indicate position.\n     * @param termOffset       of the image to indicate position.\n     * @param windowLength     for available buffer from the position.\n     * @param flags            for the header.\n     */\n    public void sendStatusMessage(\n        final ImageConnection[] controlAddresses,\n        final int sessionId,\n        final int streamId,\n        final int termId,\n        final int termOffset,\n        final int windowLength,\n        final short flags)\n    {\n        smBuffer.clear();\n        statusMessageFlyweight\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .consumptionTermId(termId)\n            .consumptionTermOffset(termOffset)\n            .receiverWindowLength(windowLength)\n            .receiverId(receiverId)\n            .groupTag(groupTag)\n            .flags(flags);\n        smBuffer.limit(statusMessageFlyweight.frameLength());\n\n        send(smBuffer, statusMessageFlyweight.frameLength(), controlAddresses);\n    }\n\n    /**\n     * Send a NAK message back to the sources.\n     *\n     * @param controlAddresses of the sources.\n     * @param sessionId        of the image.\n     * @param streamId         of the image.\n     * @param termId           of the image to indicate position.\n     * @param termOffset       of the image to indicate position.\n     * @param length           of the range to be re-transmitted.\n     */\n    public void sendNakMessage(\n        final ImageConnection[] controlAddresses,\n        final int sessionId,\n        final int streamId,\n        final int termId,\n        final int termOffset,\n        final int length)\n    {\n        nakBuffer.clear();\n        nakFlyweight\n            .streamId(streamId)\n            .sessionId(sessionId)\n            .termId(termId)\n            .termOffset(termOffset)\n            .length(length);\n\n        send(nakBuffer, NakFlyweight.HEADER_LENGTH, controlAddresses);\n    }\n\n    /**\n     * Send RTT Measurement frame to the sources.\n     *\n     * @param controlAddresses of the sources.\n     * @param sessionId        for the image.\n     * @param streamId         for the image.\n     * @param echoTimestampNs  timestamp to echo in a reply.\n     * @param receptionDelta   time in nanoseconds between receiving original request and sending Reply RTT Measurement.\n     * @param isReply          true if a reply.\n     */\n    public void sendRttMeasurement(\n        final ImageConnection[] controlAddresses,\n        final int sessionId,\n        final int streamId,\n        final long echoTimestampNs,\n        final long receptionDelta,\n        final boolean isReply)\n    {\n        rttMeasurementBuffer.clear();\n        rttMeasurementFlyweight\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .receiverId(receiverId)\n            .echoTimestampNs(echoTimestampNs)\n            .receptionDelta(receptionDelta)\n            .flags(isReply ? RttMeasurementFlyweight.REPLY_FLAG : 0);\n\n        send(rttMeasurementBuffer, RttMeasurementFlyweight.HEADER_LENGTH, controlAddresses);\n    }\n\n    /**\n     * Send a response setup message.\n     *\n     * @param controlAddresses  of the sources.\n     * @param sessionId         for the image.\n     * @param streamId          for the image.\n     * @param responseSessionId to be used by the remote subscription to listen for responses.\n     */\n    public void sendResponseSetup(\n        final ImageConnection[] controlAddresses,\n        final int sessionId,\n        final int streamId,\n        final int responseSessionId)\n    {\n        responseSetupBuffer.clear();\n        responseSetupHeader\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .responseSessionId(responseSessionId);\n\n        send(responseSetupBuffer, ResponseSetupFlyweight.HEADER_LENGTH, controlAddresses);\n    }\n\n    /**\n     * Send an error frame back to the source publications to indicate this image has errored.\n     *\n     * @param controlAddresses of the sources.\n     * @param sessionId        for the image.\n     * @param streamId         for the image.\n     * @param errorCode        for the error being sent.\n     * @param errorMessage     to be sent back to the publication.\n     */\n    public void sendErrorFrame(\n        final ImageConnection[] controlAddresses,\n        final int sessionId,\n        final int streamId,\n        final int errorCode,\n        final String errorMessage)\n    {\n        errorFramesSent.increment();\n\n        errorBuffer.clear();\n        errorFlyweight\n            .sessionId(sessionId)\n            .streamId(streamId)\n            .receiverId(receiverId)\n            .groupTag(groupTag)\n            .errorCode(errorCode)\n            .errorMessage(errorMessage);\n        errorBuffer.limit(errorFlyweight.frameLength());\n\n        send(errorBuffer, errorFlyweight.frameLength(), controlAddresses);\n    }\n\n    /**\n     * Dispatcher for the channel.\n     *\n     * @return dispatcher for the channel.\n     */\n    public DataPacketDispatcher dispatcher()\n    {\n        return dispatcher;\n    }\n\n    /**\n     * Update the control address for a channel transport when re-resolution occurs.\n     *\n     * @param transportIndex to update the control address for.\n     * @param newAddress     for the control of the transport.\n     */\n    public void updateControlAddress(final int transportIndex, final InetSocketAddress newAddress)\n    {\n        if (null != multiRcvDestination)\n        {\n            multiRcvDestination.updateControlAddress(transportIndex, newAddress);\n        }\n        else if (udpChannel.hasExplicitControl())\n        {\n            currentControlAddress = newAddress;\n        }\n    }\n\n    /**\n     * Send a frame to the image connections.\n     *\n     * @param buffer           containing the frame.\n     * @param length           of the frame in the buffer.\n     * @param imageConnections to send the frame to.\n     */\n    protected void send(final ByteBuffer buffer, final int length, final ImageConnection[] imageConnections)\n    {\n        final int bytesSent = null == multiRcvDestination ?\n            sendTo(buffer, imageConnections[0].controlAddress) :\n            multiRcvDestination.sendToAll(imageConnections, buffer, length, cachedNanoClock.nanoTime());\n\n        if (length != bytesSent)\n        {\n            shortSends.increment();\n        }\n    }\n\n    /**\n     * Send a frame to a source.\n     *\n     * @param buffer         containing the frame.\n     * @param length         of the frame in the buffer.\n     * @param transportIndex to send the frame on.\n     * @param remoteAddress  to send the frame to.\n     */\n    protected void send(\n        final ByteBuffer buffer, final int length, final int transportIndex, final InetSocketAddress remoteAddress)\n    {\n        final int bytesSent = null == multiRcvDestination ?\n            sendTo(buffer, remoteAddress) :\n            MultiRcvDestination.sendTo(multiRcvDestination.transport(transportIndex), buffer, remoteAddress);\n\n        if (length != bytesSent)\n        {\n            shortSends.increment();\n        }\n    }\n\n    void checkForReResolution(final long nowNs, final DriverConductorProxy conductorProxy)\n    {\n        if (null != multiRcvDestination)\n        {\n            multiRcvDestination.checkForReResolution(this, nowNs, conductorProxy);\n        }\n        else if (udpChannel.hasExplicitControl() && (timeOfLastActivityNs + DESTINATION_ADDRESS_TIMEOUT) - nowNs < 0)\n        {\n            timeOfLastActivityNs = nowNs;\n            conductorProxy.reResolveControl(\n                udpChannel.channelUri().get(CommonContext.MDC_CONTROL_PARAM_NAME),\n                udpChannel,\n                this,\n                currentControlAddress);\n        }\n    }\n\n    /**\n     * Called from the {@link io.aeron.driver.DriverConductor} to\n     * increment image ref count for this channel endpoint.\n     */\n    public void incRefImages()\n    {\n        imageRefCount++;\n    }\n\n    /**\n     * Called from the {@link io.aeron.driver.DriverConductor} to\n     * decrement image ref count for this channel endpoint.\n     */\n    public void decRefImages()\n    {\n        --imageRefCount;\n    }\n\n    private void updateTimeOfLastActivityNs(final long nowNs, final int transportIndex)\n    {\n        if (null == multiRcvDestination)\n        {\n            timeOfLastActivityNs = nowNs;\n        }\n        else\n        {\n            multiRcvDestination.transport(transportIndex).timeOfLastActivityNs(nowNs);\n        }\n    }\n\n    private void updateLocalSocketAddress(final String bindAddressAndPort)\n    {\n        if (null != localSocketAddressIndicator)\n        {\n            LocalSocketAddressStatus.updateBindAddress(\n                localSocketAddressIndicator, bindAddressAndPort, context.countersMetaDataBuffer());\n            localSocketAddressIndicator.setRelease(ChannelEndpointStatus.ACTIVE);\n        }\n    }\n\n    private void applyChannelReceiveTimestamp(final UnsafeBuffer buffer, final int length)\n    {\n        if (length > DataHeaderFlyweight.HEADER_LENGTH)\n        {\n            final int offset = udpChannel.channelReceiveTimestampOffset();\n\n            if (DataHeaderFlyweight.DATA_OFFSET + offset + BitUtil.SIZE_OF_LONG < length)\n            {\n                buffer.putLong(\n                    DataHeaderFlyweight.DATA_OFFSET + offset, channelReceiveTimestampClock.nanoTime(), LITTLE_ENDIAN);\n            }\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ReceiveChannelEndpoint{\" +\n            \"groupTag=\" + groupTag +\n            \", isChannelReceiveTimestampEnabled=\" + isChannelReceiveTimestampEnabled +\n            \", receiverId=\" + receiverId +\n            \", currentControlAddress=\" + currentControlAddress +\n            \", imageRefCount=\" + imageRefCount +\n            \", udpChannel=\" + udpChannel +\n            \", connectAddress=\" + connectAddress +\n            \", multiRcvDestination=\" + multiRcvDestination +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/ReceiveChannelEndpointThreadLocals.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport io.aeron.protocol.NakFlyweight;\nimport io.aeron.protocol.ResponseSetupFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\n\nimport java.nio.ByteBuffer;\nimport java.util.UUID;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Thread local variables that will only be accessed in the context of the Receiver agent thread from within a\n * {@link ReceiveChannelEndpoint} subclass.\n */\npublic final class ReceiveChannelEndpointThreadLocals\n{\n    private final ByteBuffer smBuffer;\n    private final StatusMessageFlyweight statusMessageFlyweight;\n    private final ByteBuffer nakBuffer;\n    private final NakFlyweight nakFlyweight;\n    private final ByteBuffer rttMeasurementBuffer;\n    private final RttMeasurementFlyweight rttMeasurementFlyweight;\n    private final ByteBuffer responseSetupBuffer;\n    private final ResponseSetupFlyweight responseSetupHeader;\n    private final ByteBuffer errorBuffer;\n    private final ErrorFlyweight errorFlyweight;\n    private long nextReceiverId;\n\n    /**\n     * Construct a set of local state to be used by the receiver thread.\n     */\n    public ReceiveChannelEndpointThreadLocals()\n    {\n        final int smLength = StatusMessageFlyweight.HEADER_LENGTH + SIZE_OF_LONG;\n        final int bufferLength =\n            BitUtil.align(smLength, CACHE_LINE_LENGTH) +\n            BitUtil.align(NakFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH) +\n            BitUtil.align(RttMeasurementFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH) +\n            BitUtil.align(ResponseSetupFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH) +\n            BitUtil.align(ErrorFlyweight.MAX_ERROR_FRAME_LENGTH, CACHE_LINE_LENGTH);\n\n        final UUID uuid = UUID.randomUUID();\n        nextReceiverId = uuid.getMostSignificantBits() ^ uuid.getLeastSignificantBits();\n\n        final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(bufferLength, CACHE_LINE_LENGTH);\n\n        byteBuffer.limit(smLength);\n        smBuffer = byteBuffer.slice();\n        statusMessageFlyweight = new StatusMessageFlyweight(smBuffer);\n\n        final int nakMessageOffset = BitUtil.align(smLength, FRAME_ALIGNMENT);\n        byteBuffer.limit(nakMessageOffset + NakFlyweight.HEADER_LENGTH).position(nakMessageOffset);\n        nakBuffer = byteBuffer.slice();\n        nakFlyweight = new NakFlyweight(nakBuffer);\n\n        final int rttMeasurementOffset = nakMessageOffset + BitUtil.align(NakFlyweight.HEADER_LENGTH, FRAME_ALIGNMENT);\n        byteBuffer.limit(rttMeasurementOffset + RttMeasurementFlyweight.HEADER_LENGTH).position(rttMeasurementOffset);\n        rttMeasurementBuffer = byteBuffer.slice();\n        rttMeasurementFlyweight = new RttMeasurementFlyweight(rttMeasurementBuffer);\n\n        final int responseSetupOffset = rttMeasurementOffset + BitUtil.align(\n            RttMeasurementFlyweight.HEADER_LENGTH, CACHE_LINE_LENGTH);\n        byteBuffer.limit(responseSetupOffset + ResponseSetupFlyweight.HEADER_LENGTH).position(responseSetupOffset);\n        responseSetupBuffer = byteBuffer.slice();\n        responseSetupHeader = new ResponseSetupFlyweight(responseSetupBuffer);\n\n        final int errorOffset = responseSetupOffset + BitUtil.align(\n            ResponseSetupFlyweight.HEADER_LENGTH, FRAME_ALIGNMENT);\n        byteBuffer.limit(errorOffset + ErrorFlyweight.MAX_ERROR_FRAME_LENGTH).position(errorOffset);\n        errorBuffer = byteBuffer.slice();\n        errorFlyweight = new ErrorFlyweight(errorBuffer);\n\n        statusMessageFlyweight\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .headerType(HeaderFlyweight.HDR_TYPE_SM)\n            .frameLength(StatusMessageFlyweight.HEADER_LENGTH);\n\n        nakFlyweight\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .headerType(HeaderFlyweight.HDR_TYPE_NAK)\n            .frameLength(NakFlyweight.HEADER_LENGTH);\n\n        rttMeasurementFlyweight\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .headerType(HeaderFlyweight.HDR_TYPE_RTTM)\n            .frameLength(RttMeasurementFlyweight.HEADER_LENGTH);\n\n        responseSetupHeader\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .headerType(HeaderFlyweight.HDR_TYPE_RSP_SETUP)\n            .frameLength(ResponseSetupFlyweight.HEADER_LENGTH);\n\n        errorFlyweight\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .headerType(HeaderFlyweight.HDR_TYPE_ERR)\n            .frameLength(ErrorFlyweight.HEADER_LENGTH);\n    }\n\n    /**\n     * Buffer for writing status messages to send.\n     *\n     * @return buffer for writing status messages to send.\n     */\n    public ByteBuffer statusMessageBuffer()\n    {\n        return smBuffer;\n    }\n\n    /**\n     * Flyweight over the {@link #statusMessageBuffer()}.\n     *\n     * @return flyweight over the {@link #statusMessageBuffer()}.\n     */\n    public StatusMessageFlyweight statusMessageFlyweight()\n    {\n        return statusMessageFlyweight;\n    }\n\n    /**\n     * Buffer for writing NAK messages to send.\n     *\n     * @return buffer for writing NAK messages to send.\n     */\n    public ByteBuffer nakBuffer()\n    {\n        return nakBuffer;\n    }\n\n    /**\n     * Flyweight over the {@link #nakBuffer()}.\n     *\n     * @return flyweight over the {@link #nakBuffer()}.\n     */\n    public NakFlyweight nakFlyweight()\n    {\n        return nakFlyweight;\n    }\n\n    /**\n     * Buffer for writing RTT measurement messages to send.\n     *\n     * @return buffer for writing RTT measurement messages to send.\n     */\n    public ByteBuffer rttMeasurementBuffer()\n    {\n        return rttMeasurementBuffer;\n    }\n\n    /**\n     * Flyweight over the {@link #rttMeasurementBuffer()}.\n     *\n     * @return flyweight over the {@link #rttMeasurementBuffer()}.\n     */\n    public RttMeasurementFlyweight rttMeasurementFlyweight()\n    {\n        return rttMeasurementFlyweight;\n    }\n\n    /**\n     * Buffer for writing Response Setup messages to send.\n     *\n     * @return buffer for writing Response Setup messages to send.\n     */\n    public ByteBuffer responseSetupBuffer()\n    {\n        return responseSetupBuffer;\n    }\n\n    /**\n     * Flyweight over the {@link #responseSetupBuffer()}.\n     *\n     * @return flyweight over the {@link #responseSetupBuffer()}.\n     */\n    public ResponseSetupFlyweight responseSetupHeader()\n    {\n        return responseSetupHeader;\n    }\n\n    /**\n     * Buffer for writing the Error messages to send.\n     *\n     * @return buffer for writing the error messages to send.\n     */\n    public ByteBuffer errorBuffer()\n    {\n        return errorBuffer;\n    }\n\n    /**\n     * Flyweight over the {@link #errorBuffer()}.\n     *\n     * @return flyweight over the {@link #errorBuffer()}.\n     */\n    public ErrorFlyweight errorFlyweight()\n    {\n        return errorFlyweight;\n    }\n\n    /**\n     * Get the next receiver id to be used for a receiver channel identity.\n     *\n     * @return the next receiver id to be used for a receiver channel identity.\n     */\n    public long nextReceiverId()\n    {\n        return nextReceiverId++;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ReceiveChannelEndpointThreadLocals{\" +\n            \"nextReceiverId=\" + nextReceiverId +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/ReceiveDestinationTransport.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.net.InetSocketAddress;\n\nabstract class ReceiveDestinationTransportLhsPadding extends UdpChannelTransport\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n\n    ReceiveDestinationTransportLhsPadding(\n        final UdpChannel udpChannel,\n        final InetSocketAddress endPointAddress,\n        final InetSocketAddress bindAddress,\n        final InetSocketAddress connectAddress,\n        final MediaDriver.Context context,\n        final int socketRcvbufLength,\n        final int socketSndbufLength)\n    {\n        super(\n            udpChannel, endPointAddress, bindAddress, connectAddress, context.receiverPortManager(),\n            context, socketRcvbufLength, socketSndbufLength);\n    }\n}\n\nabstract class ReceiveDestinationTransportHotFields extends ReceiveDestinationTransportLhsPadding\n{\n    long timeOfLastActivityNs;\n\n    ReceiveDestinationTransportHotFields(\n        final UdpChannel udpChannel,\n        final InetSocketAddress endPointAddress,\n        final InetSocketAddress bindAddress,\n        final InetSocketAddress connectAddress,\n        final MediaDriver.Context context,\n        final int socketRcvbufLength,\n        final int socketSndbufLength)\n    {\n        super(\n            udpChannel, endPointAddress, bindAddress, connectAddress, context, socketRcvbufLength, socketSndbufLength);\n    }\n}\n\nabstract class ReceiveDestinationTransportRhsPadding extends ReceiveDestinationTransportHotFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n\n    ReceiveDestinationTransportRhsPadding(\n        final UdpChannel udpChannel,\n        final InetSocketAddress endPointAddress,\n        final InetSocketAddress bindAddress,\n        final InetSocketAddress connectAddress,\n        final MediaDriver.Context context,\n        final int socketRcvbufLength,\n        final int socketSndbufLength)\n    {\n        super(\n            udpChannel, endPointAddress, bindAddress, connectAddress, context, socketRcvbufLength, socketSndbufLength);\n    }\n}\n\n/**\n * Destination endpoint representation for reception into an image from a UDP transport.\n */\npublic final class ReceiveDestinationTransport extends ReceiveDestinationTransportRhsPadding\n{\n    private InetSocketAddress currentControlAddress;\n    private final AtomicCounter localSocketAddressIndicator;\n\n    /**\n     * Construct transport for a receiving destination.\n     *\n     * @param udpChannel                  for the destination.\n     * @param context                     for configuration.\n     * @param localSocketAddressIndicator to indicate status of the transport.\n     * @param receiveChannelEndpoint      to which this destination belongs.\n     */\n    public ReceiveDestinationTransport(\n        final UdpChannel udpChannel,\n        final MediaDriver.Context context,\n        final AtomicCounter localSocketAddressIndicator,\n        final ReceiveChannelEndpoint receiveChannelEndpoint)\n    {\n        super(\n            udpChannel,\n            udpChannel.remoteData(),\n            udpChannel.remoteData(),\n            null,\n            context,\n            receiveChannelEndpoint.socketRcvbufLength(),\n            receiveChannelEndpoint.socketSndbufLength());\n\n        this.timeOfLastActivityNs = context.receiverCachedNanoClock().nanoTime();\n        this.currentControlAddress = udpChannel.hasExplicitControl() ? udpChannel.localControl() : null;\n        this.localSocketAddressIndicator = localSocketAddressIndicator;\n    }\n\n    /**\n     * Open the channel by the receiver.\n     *\n     * @param statusIndicator for the channel.\n     */\n    public void openChannel(final AtomicCounter statusIndicator)\n    {\n        openDatagramChannel(statusIndicator);\n\n        LocalSocketAddressStatus.updateBindAddress(\n            localSocketAddressIndicator, bindAddressAndPort(), context.countersMetaDataBuffer());\n        localSocketAddressIndicator.setRelease(ChannelEndpointStatus.ACTIVE);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        super.close();\n        CloseHelper.close(errorHandler, localSocketAddressIndicator);\n    }\n\n    /**\n     * Has the channel explicit control address.\n     *\n     * @return true if the channel has explicit control address.\n     */\n    public boolean hasExplicitControl()\n    {\n        return udpChannel.hasExplicitControl();\n    }\n\n    /**\n     * Get the explicit control address for the channel.\n     *\n     * @return the explicit control address for the channel if set, otherwise null.\n     */\n    public InetSocketAddress explicitControlAddress()\n    {\n        return udpChannel.hasExplicitControl() ? currentControlAddress : null;\n    }\n\n    /**\n     * Store the time of last activity for the destination.\n     *\n     * @param nowNs the time of last activity for the destination.\n     */\n    public void timeOfLastActivityNs(final long nowNs)\n    {\n        this.timeOfLastActivityNs = nowNs;\n    }\n\n    /**\n     * Get the time of last activity for the destination.\n     *\n     * @return the time of last activity for the destination.\n     */\n    public long timeOfLastActivityNs()\n    {\n        return timeOfLastActivityNs;\n    }\n\n    /**\n     * {@link UdpChannel} associated with the destination.\n     *\n     * @return the {@link UdpChannel} associated with the destination.\n     */\n    public UdpChannel udpChannel()\n    {\n        return udpChannel;\n    }\n\n    /**\n     * Store the time current control address for the destination.\n     *\n     * @param newAddress control address for the destination.\n     */\n    public void currentControlAddress(final InetSocketAddress newAddress)\n    {\n        this.currentControlAddress = newAddress;\n    }\n\n    /**\n     * The current control address for the destination.\n     *\n     * @return the time current control address for the destination.\n     */\n    public InetSocketAddress currentControlAddress()\n    {\n        return currentControlAddress;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ReceiveDestinationTransport{\" +\n            \"currentControlAddress=\" + currentControlAddress +\n            \", localSocketAddressIndicator=\" + localSocketAddressIndicator +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/ResolvedInterface.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport java.net.InetSocketAddress;\nimport java.net.NetworkInterface;\n\nimport static java.util.Objects.requireNonNull;\n\nrecord ResolvedInterface(NetworkInterface localInterface, InetSocketAddress address)\n{\n    ResolvedInterface\n    {\n        requireNonNull(address, \"address must not be null\");\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/SendChannelEndpoint.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.ErrorCode;\nimport io.aeron.driver.DriverConductorProxy;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.NetworkPublication;\nimport io.aeron.driver.Sender;\nimport io.aeron.driver.status.MdcDestinations;\nimport io.aeron.exceptions.ControlProtocolException;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.NakFlyweight;\nimport io.aeron.protocol.ResponseSetupFlyweight;\nimport io.aeron.protocol.RttMeasurementFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.EpochNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.PortUnreachableException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.util.Arrays;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.media.SendChannelEndpoint.DESTINATION_TIMEOUT;\nimport static io.aeron.driver.media.UdpChannelTransport.onSendError;\nimport static io.aeron.driver.status.SystemCounterDescriptor.ERROR_FRAMES_RECEIVED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.NAK_MESSAGES_RECEIVED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.STATUS_MESSAGES_RECEIVED;\nimport static io.aeron.protocol.StatusMessageFlyweight.SEND_SETUP_FLAG;\nimport static io.aeron.status.ChannelEndpointStatus.status;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.util.Objects.requireNonNull;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.collections.Hashing.compoundKey;\n\n/**\n * Aggregator of multiple {@link NetworkPublication}s onto a single transport channel for the\n * sending of data and setup frames plus the receiving of status and NAK frames.\n */\npublic class SendChannelEndpoint extends UdpChannelTransport\n{\n    static final long DESTINATION_TIMEOUT = TimeUnit.SECONDS.toNanos(5);\n\n    private int refCount = 0;\n    private long timeOfLastResolutionNs;\n    private final Long2ObjectHashMap<NetworkPublication> publicationBySessionAndStreamId = new Long2ObjectHashMap<>();\n    private final MultiSndDestination multiSndDestination;\n    private final AtomicCounter statusMessagesReceived;\n    private final AtomicCounter nakMessagesReceived;\n    private final AtomicCounter statusIndicator;\n    private final AtomicCounter errorMessagesReceived;\n    private final boolean isChannelSendTimestampEnabled;\n    private final EpochNanoClock sendTimestampClock;\n    private final UnsafeBuffer bufferForTimestamping = new UnsafeBuffer();\n    private AtomicCounter localSocketAddressIndicator;\n    private AtomicCounter mdcDestinationsCounter;\n\n    /**\n     * Construct the sender end for data streams.\n     *\n     * @param udpChannel      configuration for the media.\n     * @param statusIndicator to indicate the status of the channel endpoint.\n     * @param context         for configuration.\n     */\n    public SendChannelEndpoint(\n        final UdpChannel udpChannel, final AtomicCounter statusIndicator, final MediaDriver.Context context)\n    {\n        super(\n            udpChannel,\n            udpChannel.remoteControl(),\n            udpChannel.localControl(),\n            udpChannel.isMultiDestination() || udpChannel.isResponseControlMode() ? null : udpChannel.remoteData(),\n            context.senderPortManager(),\n            context);\n\n        nakMessagesReceived = context.systemCounters().get(NAK_MESSAGES_RECEIVED);\n        statusMessagesReceived = context.systemCounters().get(STATUS_MESSAGES_RECEIVED);\n        errorMessagesReceived = context.systemCounters().get(ERROR_FRAMES_RECEIVED);\n        this.statusIndicator = statusIndicator;\n\n        MultiSndDestination multiSndDestination = null;\n        if (udpChannel.isManualControlMode())\n        {\n            multiSndDestination = new ManualSndMultiDestination(context.senderCachedNanoClock(), errorHandler);\n        }\n        else if (udpChannel.isDynamicControlMode())\n        {\n            multiSndDestination = new DynamicSndMultiDestination(context.senderCachedNanoClock(), errorHandler);\n        }\n\n        this.multiSndDestination = multiSndDestination;\n        this.isChannelSendTimestampEnabled = udpChannel.isChannelSendTimestampEnabled();\n        this.sendTimestampClock = context.channelSendTimestampClock();\n    }\n\n    /**\n     * Set a channel binding status counter.\n     *\n     * @param counter to be set.\n     */\n    public void localSocketAddressIndicator(final AtomicCounter counter)\n    {\n        localSocketAddressIndicator = counter;\n    }\n\n    /**\n     * Decrement the reference count to the channel.\n     */\n    public void decRef()\n    {\n        --refCount;\n    }\n\n    /**\n     * Increment the reference count to the channel.\n     */\n    public void incRef()\n    {\n        ++refCount;\n    }\n\n    /**\n     * Open the underlying sockets for the channel.\n     */\n    public void openChannel()\n    {\n        openDatagramChannel(statusIndicator);\n\n        LocalSocketAddressStatus.updateBindAddress(\n            requireNonNull(localSocketAddressIndicator, \"localSocketAddressIndicator not allocated\"),\n            bindAddressAndPort(),\n            context.countersMetaDataBuffer());\n        localSocketAddressIndicator.setRelease(ChannelEndpointStatus.ACTIVE);\n    }\n\n    /**\n     * The original URI String used when a subscription was added.\n     *\n     * @return the original URI String used when a subscription was added.\n     */\n    public String originalUriString()\n    {\n        return udpChannel().originalUriString();\n    }\n\n    /**\n     * Counter id of the channel status indicator counter.\n     *\n     * @return id of the channel status indicator counter.\n     */\n    public int statusIndicatorCounterId()\n    {\n        return statusIndicator.id();\n    }\n\n    /**\n     * Indicate that the channel as active after successfully opening it.\n     */\n    public void indicateActive()\n    {\n        final long currentStatus = statusIndicator.get();\n        if (currentStatus != ChannelEndpointStatus.INITIALIZING)\n        {\n            throw new IllegalStateException(\n                \"channel cannot be registered unless INITIALIZING: status=\" + status(currentStatus));\n        }\n\n        statusIndicator.appendToLabel(bindAddressAndPort());\n        statusIndicator.setRelease(ChannelEndpointStatus.ACTIVE);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        super.close();\n        CloseHelper.close(errorHandler, statusIndicator);\n        CloseHelper.close(errorHandler, localSocketAddressIndicator);\n        CloseHelper.close(errorHandler, mdcDestinationsCounter);\n    }\n\n    /**\n     * Called by to determine if the channel endpoint should be closed.\n     *\n     * @return true if ready to be closed.\n     */\n    public boolean shouldBeClosed()\n    {\n        return 0 == refCount && !statusIndicator.isClosed();\n    }\n\n    /**\n     * Called from the {@link Sender} to add information to the control packet dispatcher.\n     *\n     * @param publication to add to the dispatcher\n     */\n    public void registerForSend(final NetworkPublication publication)\n    {\n        publicationBySessionAndStreamId.put(compoundKey(publication.sessionId(), publication.streamId()), publication);\n    }\n\n    /**\n     * Called from the {@link Sender} to remove information from the control packet dispatcher.\n     *\n     * @param publication to remove\n     */\n    public void unregisterForSend(final NetworkPublication publication)\n    {\n        publicationBySessionAndStreamId.remove(compoundKey(publication.sessionId(), publication.streamId()));\n    }\n\n    /**\n     * Send contents of a {@link ByteBuffer} to connected address.\n     * This is used on the sender side for performance over send(ByteBuffer, SocketAddress).\n     *\n     * @param buffer to send\n     * @return number of bytes sent\n     */\n    public int send(final ByteBuffer buffer)\n    {\n        int bytesSent = 0;\n\n        if (isChannelSendTimestampEnabled)\n        {\n            applyChannelSendTimestamp(buffer);\n        }\n\n        if (null != sendDatagramChannel)\n        {\n            final int bytesToSend = buffer.remaining();\n\n            if (null == multiSndDestination)\n            {\n                try\n                {\n                    sendHook(buffer, connectAddress);\n                    if (sendDatagramChannel.isConnected())\n                    {\n                        bytesSent = sendDatagramChannel.write(buffer);\n                    }\n                }\n                catch (final PortUnreachableException ignore)\n                {\n                }\n                catch (final IOException ex)\n                {\n                    onSendError(ex, connectAddress, errorHandler);\n                }\n            }\n            else\n            {\n                bytesSent = multiSndDestination.send(sendDatagramChannel, buffer, this, bytesToSend);\n            }\n        }\n\n        return bytesSent;\n    }\n\n    /**\n     * Send contents of a {@link ByteBuffer} to connected address.\n     * This is used on the sender side for performance over send(ByteBuffer, SocketAddress).\n     *\n     * @param buffer          to send\n     * @param endpointAddress to send data to.\n     * @return number of bytes sent\n     */\n    public int send(final ByteBuffer buffer, final InetSocketAddress endpointAddress)\n    {\n        int bytesSent = 0;\n\n        if (isChannelSendTimestampEnabled)\n        {\n            applyChannelSendTimestamp(buffer);\n        }\n\n        if (null != sendDatagramChannel)\n        {\n            try\n            {\n                sendHook(buffer, endpointAddress);\n                bytesSent = sendDatagramChannel.send(buffer, endpointAddress);\n            }\n            catch (final PortUnreachableException ignore)\n            {\n            }\n            catch (final IOException ex)\n            {\n                onSendError(ex, connectAddress, errorHandler);\n            }\n        }\n\n        return bytesSent;\n    }\n\n    /**\n     * Check sockets may need to be re-resolved due to no activity.\n     *\n     * @param nowNs          to test against for activity.\n     * @param conductorProxy to notify of any addresses which may need to be re-resolved.\n     */\n    public void checkForReResolution(final long nowNs, final DriverConductorProxy conductorProxy)\n    {\n        if (udpChannel.isManualControlMode())\n        {\n            multiSndDestination.checkForReResolution(this, nowNs, conductorProxy);\n        }\n        else if (udpChannel.hasExplicitEndpoint() && !udpChannel.isMulticast())\n        {\n            if (statusMessageTimeout(nowNs) && ((timeOfLastResolutionNs + DESTINATION_TIMEOUT) - nowNs) < 0)\n            {\n                timeOfLastResolutionNs = nowNs;\n                final String endpoint = udpChannel.channelUri().get(CommonContext.ENDPOINT_PARAM_NAME);\n                conductorProxy.reResolveEndpoint(endpoint, this, connectAddress);\n            }\n        }\n    }\n\n    /**\n     * Callback back handler for received status messages.\n     *\n     * @param msg            flyweight over the status message.\n     * @param buffer         containing the message.\n     * @param length         of the message.\n     * @param srcAddress     of the message.\n     * @param conductorProxy to send messages back to the conductor.\n     */\n    public void onStatusMessage(\n        final StatusMessageFlyweight msg,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final DriverConductorProxy conductorProxy)\n    {\n        final int sessionId = msg.sessionId();\n        final int streamId = msg.streamId();\n\n        statusMessagesReceived.incrementRelease();\n\n        if (null != multiSndDestination)\n        {\n            multiSndDestination.onStatusMessage(msg, srcAddress);\n        }\n\n        final NetworkPublication publication = publicationBySessionAndStreamId.get(compoundKey(sessionId, streamId));\n        if (null != publication)\n        {\n            if (SEND_SETUP_FLAG == (msg.flags() & SEND_SETUP_FLAG))\n            {\n                publication.triggerSendSetupFrame(msg, srcAddress);\n            }\n            else\n            {\n                publication.onStatusMessage(msg, srcAddress, conductorProxy);\n            }\n        }\n    }\n\n\n    /**\n     * Callback back handler for received error messages.\n     *\n     * @param msg            flyweight over the status message.\n     * @param buffer         containing the message.\n     * @param length         of the message.\n     * @param srcAddress     of the message.\n     * @param conductorProxy to send messages back to the conductor.\n     */\n    public void onError(\n        final ErrorFlyweight msg,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final DriverConductorProxy conductorProxy)\n    {\n        final int sessionId = msg.sessionId();\n        final int streamId = msg.streamId();\n\n        errorMessagesReceived.incrementRelease();\n\n        final long destinationRegistrationId = (null != multiSndDestination) ?\n            multiSndDestination.findRegistrationId(msg, srcAddress) : Aeron.NULL_VALUE;\n\n        final NetworkPublication publication = publicationBySessionAndStreamId.get(compoundKey(sessionId, streamId));\n        if (null != publication)\n        {\n            publication.onError(msg, srcAddress, destinationRegistrationId, conductorProxy);\n        }\n    }\n\n    /**\n     * Callback back handler for received NAK messages.\n     *\n     * @param msg        flyweight over the NAK message.\n     * @param buffer     containing the message.\n     * @param length     of the message.\n     * @param srcAddress of the message.\n     */\n    public void onNakMessage(\n        final NakFlyweight msg,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress)\n    {\n        final long key = compoundKey(msg.sessionId(), msg.streamId());\n        final NetworkPublication publication = publicationBySessionAndStreamId.get(key);\n\n        if (null != publication)\n        {\n            publication.onNak(msg.termId(), msg.termOffset(), msg.length());\n            nakMessagesReceived.incrementRelease();\n        }\n    }\n\n    /**\n     * Callback back handler for received RTT Measurement messages.\n     *\n     * @param msg        flyweight over the RTT message.\n     * @param buffer     containing the message.\n     * @param length     of the message.\n     * @param srcAddress of the message.\n     */\n    public void onRttMeasurement(\n        final RttMeasurementFlyweight msg,\n        final UnsafeBuffer buffer,\n        final int length,\n        final InetSocketAddress srcAddress)\n    {\n        final long key = compoundKey(msg.sessionId(), msg.streamId());\n        final NetworkPublication publication = publicationBySessionAndStreamId.get(key);\n        if (null != publication)\n        {\n            publication.onRttMeasurement(msg, srcAddress);\n        }\n    }\n\n\n    /**\n     * Callback to handle a received Response Setup frame.\n     *\n     * @param msg            of the Response Setup frame.\n     * @param unsafeBuffer   containing the Response Setup frame.\n     * @param length         of the Response Setup frame.\n     * @param srcAddress     the message came from.\n     * @param conductorProxy to send messages back to the conductor.\n     */\n    public void onResponseSetup(\n        final ResponseSetupFlyweight msg,\n        final UnsafeBuffer unsafeBuffer,\n        final int length,\n        final InetSocketAddress srcAddress,\n        final DriverConductorProxy conductorProxy)\n    {\n        final long key = compoundKey(msg.sessionId(), msg.streamId());\n        final NetworkPublication publication = publicationBySessionAndStreamId.get(key);\n\n        if (null != publication)\n        {\n            final long responseCorrelationId = publication.responseCorrelationId();\n            if (Aeron.NULL_VALUE != responseCorrelationId)\n            {\n                conductorProxy.responseSetup(responseCorrelationId, msg.responseSessionId());\n            }\n        }\n    }\n\n    /**\n     * Validate that the channel allows manual control for destinations.\n     * <p>\n     * If not then a {@link ControlProtocolException} will be thrown.\n     */\n    public void validateAllowsManualControl()\n    {\n        if (!(multiSndDestination instanceof ManualSndMultiDestination))\n        {\n            throw new ControlProtocolException(ErrorCode.INVALID_CHANNEL, \"channel does not allow manual control\");\n        }\n    }\n\n    /**\n     * Add a destination for an MDC channel.\n     *\n     * @param channelUri     for the destination to be added.\n     * @param address        of the destination to be added.\n     * @param registrationId of the destination.\n     */\n    public void addDestination(final ChannelUri channelUri, final InetSocketAddress address, final long registrationId)\n    {\n        multiSndDestination.addDestination(channelUri, address, registrationId);\n    }\n\n    /**\n     * Remove a destination from an MDC channel.\n     *\n     * @param channelUri for the destination to be removed.\n     * @param address    of the destination to be removed.\n     */\n    public void removeDestination(final ChannelUri channelUri, final InetSocketAddress address)\n    {\n        multiSndDestination.removeDestination(channelUri, address);\n    }\n\n    /**\n     * Remove a destination from an MDC channel.\n     *\n     * @param destinationRegistrationId the registration id of the destination.\n     */\n    public void removeDestination(final long destinationRegistrationId)\n    {\n        multiSndDestination.removeDestination(destinationRegistrationId);\n    }\n\n    /**\n     * Update the endpoint for the channel on address change.\n     *\n     * @param endpoint   associated with the address.\n     * @param newAddress for the endpoint.\n     */\n    public void resolutionChange(final String endpoint, final InetSocketAddress newAddress)\n    {\n        if (null != multiSndDestination)\n        {\n            multiSndDestination.updateDestination(endpoint, newAddress);\n        }\n        else\n        {\n            updateEndpoint(newAddress, statusIndicator);\n        }\n    }\n\n    /**\n     * Allocate a destinations counter if the channel uses multiple destinations.\n     *\n     * @param tempBuffer        to use for metadata formatting.\n     * @param countersManager   for the driver.\n     * @param registrationId    of the endpoint.\n     * @param originalUriString of the channel.\n     */\n    public void allocateDestinationsCounterForMdc(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long registrationId,\n        final String originalUriString)\n    {\n        if (null != multiSndDestination)\n        {\n            mdcDestinationsCounter = MdcDestinations.allocate(\n                tempBuffer, countersManager, registrationId, originalUriString);\n            multiSndDestination.destinationsCounter(mdcDestinationsCounter);\n        }\n    }\n\n    private boolean statusMessageTimeout(final long nowNs)\n    {\n        for (final NetworkPublication publication : publicationBySessionAndStreamId.values())\n        {\n            if (((publication.timeOfLastStatusMessageNs() + DESTINATION_TIMEOUT) - nowNs) >= 0)\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private void applyChannelSendTimestamp(final ByteBuffer buffer)\n    {\n        final int length = buffer.remaining();\n\n        if (length >= DataHeaderFlyweight.HEADER_LENGTH)\n        {\n            bufferForTimestamping.wrap(buffer, buffer.position(), length);\n\n            final int type =\n                bufferForTimestamping.getShort(DataHeaderFlyweight.TYPE_FIELD_OFFSET, LITTLE_ENDIAN) & 0xFFFF;\n            final int flags = bufferForTimestamping.getByte(DataHeaderFlyweight.FLAGS_FIELD_OFFSET) & 0xFF;\n\n            if (DataHeaderFlyweight.HDR_TYPE_DATA == type &&\n                0 != (DataHeaderFlyweight.BEGIN_FLAG & flags) &&\n                !DataHeaderFlyweight.isHeartbeat(bufferForTimestamping, length))\n            {\n                final int offset = udpChannel.channelSendTimestampOffset();\n\n                if (DataHeaderFlyweight.DATA_OFFSET + offset + SIZE_OF_LONG <= length)\n                {\n                    bufferForTimestamping.putLong(\n                        DataHeaderFlyweight.DATA_OFFSET + offset, sendTimestampClock.nanoTime(), LITTLE_ENDIAN);\n                }\n            }\n        }\n    }\n\n    /**\n     * Does the channel have a matching tag?\n     *\n     * @param udpChannel with tag to match against.\n     * @return true if the channel matches on tag identity.\n     */\n    public boolean matchesTag(final UdpChannel udpChannel)\n    {\n        return udpChannel.matchesTag(super.udpChannel, null, connectAddress);\n    }\n}\n\nabstract class MultiSndDestinationLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nabstract class MultiSndDestinationHotFields extends MultiSndDestinationLhsPadding\n{\n    int roundRobinIndex = 0;\n}\n\nabstract class MultiSndDestinationRhsPadding extends MultiSndDestinationHotFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\nabstract class MultiSndDestination extends MultiSndDestinationRhsPadding\n{\n    static final Destination[] EMPTY_DESTINATIONS = new Destination[0];\n\n    Destination[] destinations = EMPTY_DESTINATIONS;\n    final CachedNanoClock nanoClock;\n    final ErrorHandler errorHandler;\n    AtomicCounter destinationsCounter = null;\n\n    MultiSndDestination(final CachedNanoClock nanoClock, final ErrorHandler errorHandler)\n    {\n        this.nanoClock = nanoClock;\n        this.errorHandler = errorHandler;\n    }\n\n    abstract int send(DatagramChannel channel, ByteBuffer buffer, SendChannelEndpoint channelEndpoint, int bytesToSend);\n\n    abstract void onStatusMessage(StatusMessageFlyweight msg, InetSocketAddress address);\n\n    void addDestination(final ChannelUri channelUri, final InetSocketAddress address, final long registrationId)\n    {\n    }\n\n    void removeDestination(final ChannelUri channelUri, final InetSocketAddress address)\n    {\n    }\n\n    void removeDestination(final long destinationRegistrationId)\n    {\n    }\n\n    void checkForReResolution(\n        final SendChannelEndpoint channelEndpoint, final long nowNs, final DriverConductorProxy conductorProxy)\n    {\n    }\n\n    void updateDestination(final String endpoint, final InetSocketAddress newAddress)\n    {\n    }\n\n    void destinationsCounter(final AtomicCounter destinationsCounter)\n    {\n        this.destinationsCounter = destinationsCounter;\n    }\n\n    static int send(\n        final DatagramChannel datagramChannel,\n        final ByteBuffer buffer,\n        final SendChannelEndpoint channelEndpoint,\n        final int bytesToSend,\n        final int position,\n        final InetSocketAddress destination,\n        final ErrorHandler errorHandler)\n    {\n        int bytesSent = 0;\n        try\n        {\n            if (destination.isUnresolved())\n            {\n                bytesSent = bytesToSend;\n            }\n            else if (datagramChannel.isOpen())\n            {\n                buffer.position(position);\n                channelEndpoint.sendHook(buffer, destination);\n                bytesSent = datagramChannel.send(buffer, destination);\n            }\n        }\n        catch (final PortUnreachableException ignore)\n        {\n        }\n        catch (final IOException ex)\n        {\n            onSendError(ex, destination, errorHandler);\n        }\n\n        return bytesSent;\n    }\n\n    public long findRegistrationId(final ErrorFlyweight msg, final InetSocketAddress srcAddress)\n    {\n        return Aeron.NULL_VALUE;\n    }\n}\n\nclass ManualSndMultiDestination extends MultiSndDestination\n{\n    ManualSndMultiDestination(final CachedNanoClock nanoClock, final ErrorHandler errorHandler)\n    {\n        super(nanoClock, errorHandler);\n    }\n\n    void onStatusMessage(final StatusMessageFlyweight msg, final InetSocketAddress address)\n    {\n        final long receiverId = msg.receiverId();\n        final long nowNs = nanoClock.nanoTime();\n\n        for (final Destination destination : destinations)\n        {\n            if (destination.isMatch(msg.receiverId(), address))\n            {\n                if (!destination.isReceiverIdValid)\n                {\n                    destination.receiverId = receiverId;\n                    destination.isReceiverIdValid = true;\n                }\n\n                destination.timeOfLastActivityNs = nowNs;\n                break;\n            }\n        }\n    }\n\n    int send(\n        final DatagramChannel channel,\n        final ByteBuffer buffer,\n        final SendChannelEndpoint channelEndpoint,\n        final int bytesToSend)\n    {\n        final int position = buffer.position();\n        final int length = destinations.length;\n\n        int startingIndex = roundRobinIndex++;\n        if (startingIndex >= length)\n        {\n            roundRobinIndex = startingIndex = 0;\n        }\n\n        int result = bytesToSend;\n        for (int i = startingIndex; i < length; i++)\n        {\n            final Destination destination = destinations[i];\n\n            final int bytesSent = send(\n                channel, buffer, channelEndpoint, bytesToSend, position, destination.address, errorHandler);\n            if (bytesSent < bytesToSend)\n            {\n                result = bytesSent;\n            }\n        }\n\n        for (int i = 0; i < startingIndex; i++)\n        {\n            final Destination destination = destinations[i];\n\n            final int bytesSent = send(\n                channel, buffer, channelEndpoint, bytesToSend, position, destination.address, errorHandler);\n            if (bytesSent < bytesToSend)\n            {\n                result = bytesSent;\n            }\n        }\n\n        return result;\n    }\n\n    void addDestination(final ChannelUri channelUri, final InetSocketAddress address, final long registrationId)\n    {\n        final Destination destination = new Destination(\n            nanoClock.nanoTime(), channelUri.get(CommonContext.ENDPOINT_PARAM_NAME), address, registrationId);\n        destinations = ArrayUtil.add(destinations, destination);\n        destinationsCounter.setRelease(destinations.length);\n    }\n\n    void removeDestination(final ChannelUri channelUri, final InetSocketAddress address)\n    {\n        boolean found = false;\n        int index = 0;\n        for (final Destination destination : destinations)\n        {\n            if (destination.address.equals(address))\n            {\n                found = true;\n                break;\n            }\n\n            index++;\n        }\n\n        if (found)\n        {\n            if (1 == destinations.length)\n            {\n                destinations = EMPTY_DESTINATIONS;\n            }\n            else\n            {\n                destinations = ArrayUtil.remove(destinations, index);\n            }\n        }\n\n        destinationsCounter.setRelease(destinations.length);\n    }\n\n    void removeDestination(final long destinationRegistrationId)\n    {\n        boolean found = false;\n        int index = 0;\n        for (final Destination destination : destinations)\n        {\n            if (destination.registrationId == destinationRegistrationId)\n            {\n                found = true;\n                break;\n            }\n\n            index++;\n        }\n\n        if (found)\n        {\n            if (1 == destinations.length)\n            {\n                destinations = EMPTY_DESTINATIONS;\n            }\n            else\n            {\n                destinations = ArrayUtil.remove(destinations, index);\n            }\n        }\n\n        destinationsCounter.setRelease(destinations.length);\n    }\n\n    void checkForReResolution(\n        final SendChannelEndpoint channelEndpoint, final long nowNs, final DriverConductorProxy conductorProxy)\n    {\n        for (final Destination destination : destinations)\n        {\n            if ((destination.timeOfLastActivityNs + DESTINATION_TIMEOUT) - nowNs < 0)\n            {\n                destination.timeOfLastActivityNs = nowNs;\n                conductorProxy.reResolveEndpoint(destination.endpoint, channelEndpoint, destination.address);\n            }\n        }\n    }\n\n    void updateDestination(final String endpoint, final InetSocketAddress newAddress)\n    {\n        for (final Destination destination : destinations)\n        {\n            if (endpoint.equals(destination.endpoint))\n            {\n                destination.address = newAddress;\n                destination.port = newAddress.getPort();\n            }\n        }\n    }\n\n    public long findRegistrationId(final ErrorFlyweight msg, final InetSocketAddress address)\n    {\n        for (final Destination destination : destinations)\n        {\n            if (destination.isMatch(msg.receiverId(), address))\n            {\n                return destination.registrationId;\n            }\n        }\n\n        return Aeron.NULL_VALUE;\n    }\n}\n\nclass DynamicSndMultiDestination extends MultiSndDestination\n{\n    DynamicSndMultiDestination(final CachedNanoClock nanoClock, final ErrorHandler errorHandler)\n    {\n        super(nanoClock, errorHandler);\n    }\n\n    void onStatusMessage(final StatusMessageFlyweight msg, final InetSocketAddress address)\n    {\n        final long receiverId = msg.receiverId();\n        final long nowNs = nanoClock.nanoTime();\n        boolean isExisting = false;\n\n        for (final Destination destination : destinations)\n        {\n            if (receiverId == destination.receiverId && address.getPort() == destination.port)\n            {\n                destination.timeOfLastActivityNs = nowNs;\n                isExisting = true;\n                break;\n            }\n        }\n\n        if (!isExisting)\n        {\n            add(new Destination(nowNs, receiverId, address));\n        }\n    }\n\n    int send(\n        final DatagramChannel channel,\n        final ByteBuffer buffer,\n        final SendChannelEndpoint channelEndpoint,\n        final int bytesToSend)\n    {\n        final long nowNs = nanoClock.nanoTime();\n        final int position = buffer.position();\n        final int length = destinations.length;\n        int inactiveDestinationCount = 0;\n\n        int startingIndex = roundRobinIndex++;\n        if (startingIndex >= length)\n        {\n            roundRobinIndex = startingIndex = 0;\n        }\n\n        int result = bytesToSend;\n\n        for (int i = startingIndex; i < length; i++)\n        {\n            final Destination destination = destinations[i];\n\n            if ((destination.timeOfLastActivityNs + DESTINATION_TIMEOUT) - nowNs >= 0)\n            {\n                final int bytesSent = send(\n                    channel, buffer, channelEndpoint, bytesToSend, position, destination.address, errorHandler);\n                if (bytesSent < bytesToSend)\n                {\n                    result = bytesSent;\n                }\n            }\n            else\n            {\n                inactiveDestinationCount++;\n            }\n        }\n\n        for (int i = 0; i < startingIndex; i++)\n        {\n            final Destination destination = destinations[i];\n\n            if ((destination.timeOfLastActivityNs + DESTINATION_TIMEOUT) - nowNs >= 0)\n            {\n                final int bytesSent = send(\n                    channel, buffer, channelEndpoint, bytesToSend, position, destination.address, errorHandler);\n                if (bytesSent < bytesToSend)\n                {\n                    result = bytesSent;\n                }\n            }\n            else\n            {\n                inactiveDestinationCount++;\n            }\n        }\n\n        if (inactiveDestinationCount > 0)\n        {\n            removeInactiveDestinations(nowNs);\n        }\n\n        return result;\n    }\n\n    private void add(final Destination destination)\n    {\n        destinations = ArrayUtil.add(destinations, destination);\n        destinationsCounter.setRelease(destinations.length);\n    }\n\n    private void truncateDestinations(final int removedCount)\n    {\n        final int length = destinations.length;\n        final int newLength = length - removedCount;\n\n        if (0 == newLength)\n        {\n            destinations = EMPTY_DESTINATIONS;\n        }\n        else\n        {\n            destinations = Arrays.copyOf(destinations, newLength);\n        }\n\n        destinationsCounter.setRelease(destinations.length);\n    }\n\n    private void removeInactiveDestinations(final long nowNs)\n    {\n        int removedCount = 0;\n\n        for (int lastIndex = destinations.length - 1, i = lastIndex; i >= 0; i--)\n        {\n            final Destination destination = destinations[i];\n            if ((destination.timeOfLastActivityNs + DESTINATION_TIMEOUT) - nowNs < 0)\n            {\n                if (i != lastIndex)\n                {\n                    destinations[i] = destinations[lastIndex--];\n                }\n                removedCount++;\n            }\n        }\n\n        if (removedCount > 0)\n        {\n            truncateDestinations(removedCount);\n        }\n    }\n}\n\nabstract class DestinationLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nabstract class DestinationHotFields extends DestinationLhsPadding\n{\n    long timeOfLastActivityNs;\n}\n\nabstract class DestinationRhsPadding extends DestinationHotFields\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\nfinal class Destination extends DestinationRhsPadding\n{\n    long receiverId;\n    final long registrationId;\n    boolean isReceiverIdValid;\n    int port;\n    InetSocketAddress address;\n    final String endpoint;\n\n    Destination(final long nowNs, final long receiverId, final InetSocketAddress address)\n    {\n        this.timeOfLastActivityNs = nowNs;\n        this.receiverId = receiverId;\n        this.isReceiverIdValid = true;\n        this.endpoint = null;\n        this.address = address;\n        this.port = address.getPort();\n        this.registrationId = Aeron.NULL_VALUE;\n    }\n\n    Destination(final long nowMs, final String endpoint, final InetSocketAddress address, final long registrationId)\n    {\n        this.timeOfLastActivityNs = nowMs;\n        this.receiverId = 0;\n        this.isReceiverIdValid = false;\n        this.endpoint = endpoint;\n        this.address = address;\n        this.port = address.getPort();\n        this.registrationId = registrationId;\n    }\n\n    boolean isMatch(final long receiverId, final InetSocketAddress address)\n    {\n        return\n            (isReceiverIdValid && receiverId == this.receiverId && address.getPort() == this.port) ||\n                (!isReceiverIdValid &&\n                    address.getPort() == this.port && address.getAddress().equals(this.address.getAddress()));\n    }\n}"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/SocketAddressParser.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.driver.NameResolver;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.Strings;\n\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\n\nclass SocketAddressParser\n{\n    @SuppressWarnings(\"JavadocVariable\")\n    enum IpV4State\n    {\n        HOST, PORT\n    }\n\n    @SuppressWarnings(\"JavadocVariable\")\n    enum IpV6State\n    {\n        START_ADDR, HOST, SCOPE, END_ADDR, PORT\n    }\n\n    /**\n     * Parse socket addresses from a {@link String}.\n     * <p>\n     * Supports hostname:port, ipV4Address:port, [ipV6Address]:port, and name.\n     *\n     * @param value          to be parsed for the socket address.\n     * @param uriParamName   for the parse.\n     * @param isReResolution for the parse.\n     * @param nameResolver   to be used for resolving hostnames.\n     * @return An {@link InetSocketAddress} for the parsed input.\n     */\n    static InetSocketAddress parse(\n        final String value, final String uriParamName, final boolean isReResolution, final NameResolver nameResolver)\n    {\n        if (Strings.isEmpty(value))\n        {\n            throw new NullPointerException(\"input string must not be null or empty\");\n        }\n\n        final String nameAndPort = nameResolver.lookup(value, uriParamName, isReResolution);\n        ParseResult result = tryParseIpV4(nameAndPort);\n        if (null == result)\n        {\n            result = tryParseIpV6(nameAndPort);\n        }\n        if (null == result)\n        {\n            throw new IllegalArgumentException(\"invalid format: \" + nameAndPort);\n        }\n\n        final InetAddress inetAddress = nameResolver.resolve(result.host, uriParamName, isReResolution);\n        return null == inetAddress ? InetSocketAddress.createUnresolved(result.host, result.port) :\n        new InetSocketAddress(inetAddress, result.port);\n    }\n\n    static boolean isMulticastAddress(final String hostAndPort)\n    {\n        ParseResult result = tryParseIpV4(hostAndPort);\n        if (null != result)\n        {\n            final String host = result.host;\n            for (int i = 0, dotIndex = 0, end = host.length() - 1; i <= end; i++)\n            {\n                final char c = host.charAt(i);\n                if ('.' == c || end == i)\n                {\n                    final int length = end == i ? i - dotIndex : i - 1 - dotIndex;\n                    if (length <= 0 || length > 3)\n                    {\n                        return false;\n                    }\n                    if (0 == dotIndex)\n                    {\n                        final int firstByte = AsciiEncoding.parseIntAscii(host, 0, i);\n                        // IPv4 multicast addresses are defined by the most-significant bit pattern of 1110\n                        if (firstByte > 0xFF || 0xE0 != (firstByte & 0xF0))\n                        {\n                            return false;\n                        }\n                    }\n                    dotIndex = i;\n                }\n                else if (c < '0' || c > '9')\n                {\n                    return false;\n                }\n            }\n            return true;\n        }\n        else\n        {\n            result = tryParseIpV6(hostAndPort);\n            if (null != result)\n            {\n                final String firstByte = result.host.substring(0, 2);\n                return \"ff\".equalsIgnoreCase(firstByte);\n            }\n            throw new IllegalArgumentException(\"invalid format: \" + hostAndPort);\n        }\n    }\n\n    private static ParseResult tryParseIpV4(final String str)\n    {\n        IpV4State state = IpV4State.HOST;\n        int separatorIndex = -1;\n        final int length = str.length();\n\n        for (int i = 0; i < length; i++)\n        {\n            final char c = str.charAt(i);\n            switch (state)\n            {\n                case HOST:\n                    if (':' == c)\n                    {\n                        separatorIndex = i;\n                        state = IpV4State.PORT;\n                    }\n                    break;\n\n                case PORT:\n                    if (c < '0' || '9' < c)\n                    {\n                        return null;\n                    }\n                    break;\n            }\n        }\n\n        if (-1 != separatorIndex && 1 < length - separatorIndex)\n        {\n            final String hostname = str.substring(0, separatorIndex);\n            final int portIndex = separatorIndex + 1;\n            final int port = AsciiEncoding.parseIntAscii(str, portIndex, length - portIndex);\n            return new ParseResult(hostname, port);\n        }\n\n        throw new IllegalArgumentException(\"address:port is required for ipv4: \" + str);\n    }\n\n    private static ParseResult tryParseIpV6(final String str)\n    {\n        IpV6State state = IpV6State.START_ADDR;\n        int portIndex = -1;\n        int scopeIndex = -1;\n        final int length = str.length();\n\n        for (int i = 0; i < length; i++)\n        {\n            final char c = str.charAt(i);\n\n            switch (state)\n            {\n                case START_ADDR:\n                    if ('[' == c)\n                    {\n                        state = IpV6State.HOST;\n                    }\n                    else\n                    {\n                        return null;\n                    }\n                    break;\n\n                case HOST:\n                    if (']' == c)\n                    {\n                        state = IpV6State.END_ADDR;\n                    }\n                    else if ('%' == c)\n                    {\n                        scopeIndex = i;\n                        state = IpV6State.SCOPE;\n                    }\n                    else if (':' != c && (c < 'a' || 'f' < c) && (c < 'A' || 'F' < c) && (c < '0' || '9' < c))\n                    {\n                        return null;\n                    }\n                    break;\n\n                case SCOPE:\n                    if (']' == c)\n                    {\n                        state = IpV6State.END_ADDR;\n                    }\n                    else if ('_' != c && '.' != c && '~' != c && '-' != c &&\n                        (c < 'a' || 'z' < c) && (c < 'A' || 'Z' < c) && (c < '0' || '9' < c))\n                    {\n                        return null;\n                    }\n                    break;\n\n                case END_ADDR:\n                    if (':' == c)\n                    {\n                        portIndex = i;\n                        state = IpV6State.PORT;\n                    }\n                    else\n                    {\n                        return null;\n                    }\n                    break;\n\n                case PORT:\n                    if (c < '0' || '9' < c)\n                    {\n                        return null;\n                    }\n                    break;\n            }\n        }\n\n        if (-1 != portIndex && 1 < length - portIndex)\n        {\n            final String hostname = str.substring(1, scopeIndex != -1 ? scopeIndex : portIndex - 1);\n            portIndex++;\n            final int port = AsciiEncoding.parseIntAscii(str, portIndex, length - portIndex);\n            return new ParseResult(hostname, port);\n        }\n\n        throw new IllegalArgumentException(\"[address]:port is required for ipv6: \" + str);\n    }\n\n    private static final class ParseResult\n    {\n        final String host;\n        final int port;\n\n        private ParseResult(final String host, final int port)\n        {\n            this.host = host;\n            this.port = port;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/UdpChannel.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.driver.DefaultNameResolver;\nimport io.aeron.driver.NameResolver;\nimport io.aeron.driver.exceptions.InvalidChannelException;\nimport org.agrona.BitUtil;\nimport org.agrona.SystemUtil;\n\nimport java.net.*;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static io.aeron.CommonContext.*;\nimport static io.aeron.driver.media.NetworkUtil.*;\nimport static java.net.InetAddress.getByAddress;\n\n/**\n * The media configuration for Aeron UDP channels as an instantiation of the socket addresses for a {@link ChannelUri}.\n *\n * @see ChannelUri\n * @see io.aeron.ChannelUriStringBuilder\n */\npublic final class UdpChannel\n{\n    /**\n     * The offset from the beginning of a payload where the reserved value begins.\n     */\n    public static final int RESERVED_VALUE_MESSAGE_OFFSET = -8;\n\n    private static final AtomicInteger UNIQUE_CANONICAL_FORM_VALUE = new AtomicInteger();\n    private static final InetSocketAddress ANY_IPV4 = new InetSocketAddress(\"0.0.0.0\", 0);\n    private static final InetSocketAddress ANY_IPV6 = new InetSocketAddress(\"::\", 0);\n\n    private final ControlMode controlMode;\n    private final boolean hasExplicitControl;\n    private final boolean hasExplicitEndpoint;\n    private final boolean isMulticast;\n    private final boolean hasMulticastTtl;\n    private final boolean hasTag;\n    private final int multicastTtl;\n    private final int socketRcvbufLength;\n    private final int socketSndbufLength;\n    private final int receiverWindowLength;\n    private final long tag;\n    private final InetSocketAddress remoteData;\n    private final InetSocketAddress localData;\n    private final InetSocketAddress remoteControl;\n    private final InetSocketAddress localControl;\n    private final String uriStr;\n    private final String canonicalForm;\n    private final NetworkInterface localInterface;\n    private final ProtocolFamily protocolFamily;\n    private final ChannelUri channelUri;\n    private final int channelReceiveTimestampOffset;\n    private final int channelSendTimestampOffset;\n    private final Long groupTag;\n    private final Long nakDelayNs;\n\n    private UdpChannel(final Context context)\n    {\n        controlMode = context.controlMode;\n        hasExplicitEndpoint = context.hasExplicitEndpoint;\n        hasExplicitControl = context.hasExplicitControl;\n        isMulticast = context.isMulticast;\n        hasTag = context.hasTagId;\n        tag = context.tagId;\n        hasMulticastTtl = context.hasMulticastTtl;\n        multicastTtl = context.multicastTtl;\n        remoteData = context.remoteData;\n        localData = context.localData;\n        remoteControl = context.remoteControl;\n        localControl = context.localControl;\n        uriStr = context.uriStr;\n        canonicalForm = context.canonicalForm;\n        localInterface = context.localInterface;\n        protocolFamily = context.protocolFamily;\n        channelUri = context.channelUri;\n        socketRcvbufLength = context.socketRcvbufLength;\n        socketSndbufLength = context.socketSndbufLength;\n        receiverWindowLength = context.receiverWindowLength;\n        channelReceiveTimestampOffset = context.channelReceiveTimestampOffset;\n        channelSendTimestampOffset = context.channelSendTimestampOffset;\n        groupTag = context.groupTag;\n        nakDelayNs = context.nakDelayNs;\n    }\n\n    /**\n     * Parse channel URI and create a {@link UdpChannel} using the default name resolver.\n     *\n     * @param channelUriString to parse.\n     * @return a new {@link UdpChannel} as the result of parsing.\n     * @throws InvalidChannelException if an error occurs.\n     */\n    public static UdpChannel parse(final String channelUriString)\n    {\n        return parse(channelUriString, DefaultNameResolver.INSTANCE, false);\n    }\n\n    /**\n     * Parse channel URI and create a {@link UdpChannel}.\n     *\n     * @param channelUriString to parse.\n     * @param nameResolver     to use for resolving names.\n     * @return a new {@link UdpChannel} as the result of parsing.\n     * @throws InvalidChannelException if an error occurs.\n     */\n    public static UdpChannel parse(final String channelUriString, final NameResolver nameResolver)\n    {\n        return parse(channelUriString, nameResolver, false);\n    }\n\n    /**\n     * Parse channel URI and create a {@link UdpChannel}.\n     *\n     * @param channelUriString to parse.\n     * @param nameResolver     to use for resolving names.\n     * @param isDestination    to identify if it is a destination within a channel.\n     * @return a new {@link UdpChannel} as the result of parsing.\n     * @throws InvalidChannelException if an error occurs.\n     */\n    @SuppressWarnings(\"MethodLength\")\n    public static UdpChannel parse(\n        final String channelUriString, final NameResolver nameResolver, final boolean isDestination)\n    {\n        try\n        {\n            final ChannelUri channelUri = ChannelUri.parse(channelUriString);\n            validateConfiguration(channelUri);\n\n            InetSocketAddress endpointAddress = getEndpointAddress(channelUri, nameResolver);\n            final InetSocketAddress controlAddress = getExplicitControlAddress(channelUri, nameResolver);\n\n            final String tagIdStr = channelUri.channelTag();\n            final ControlMode controlMode = parseControlMode(channelUri);\n\n            final int socketRcvbufLength = parseBufferLength(channelUri, SOCKET_RCVBUF_PARAM_NAME);\n            final int socketSndbufLength = parseBufferLength(channelUri, SOCKET_SNDBUF_PARAM_NAME);\n            final int receiverWindowLength = parseBufferLength(\n                channelUri, RECEIVER_WINDOW_LENGTH_PARAM_NAME);\n\n            final boolean requiresAdditionalSuffix = !isDestination &&\n                (null == endpointAddress && null == controlAddress ||\n                (null != endpointAddress && endpointAddress.getPort() == 0) ||\n                (null != controlAddress && controlAddress.getPort() == 0));\n\n            final boolean hasNoDistinguishingCharacteristic =\n                null == endpointAddress && null == controlAddress && null == tagIdStr;\n\n            if (ControlMode.DYNAMIC == controlMode && null == controlAddress)\n            {\n                throw new IllegalArgumentException(\n                    \"explicit control expected with dynamic control mode: \" + channelUriString);\n            }\n\n            if (hasNoDistinguishingCharacteristic && ControlMode.MANUAL != controlMode &&\n                ControlMode.RESPONSE != controlMode)\n            {\n                throw new IllegalArgumentException(\n                    \"URIs for UDP must specify an endpoint, control, tags, or control-mode=manual/response: \" +\n                    channelUriString);\n            }\n\n            if (null != endpointAddress && endpointAddress.isUnresolved())\n            {\n                throw new UnknownHostException(\"could not resolve endpoint address: \" + endpointAddress);\n            }\n\n            if (null != controlAddress && controlAddress.isUnresolved())\n            {\n                throw new UnknownHostException(\"could not resolve control address: \" + controlAddress);\n            }\n\n            boolean hasExplicitEndpoint = true;\n            if (null == endpointAddress)\n            {\n                hasExplicitEndpoint = false;\n                endpointAddress = null != controlAddress && controlAddress.getAddress() instanceof Inet6Address ?\n                    ANY_IPV6 : ANY_IPV4;\n            }\n\n            final ProtocolFamily protocolFamily = getProtocolFamily(endpointAddress.getAddress());\n\n            final Context context = new Context()\n                .protocolFamily(protocolFamily)\n                .isMulticast(false)\n                .hasExplicitControl(false)\n                .hasMulticastTtl(false)\n                .hasTagId(false)\n                .uriStr(channelUriString)\n                .channelUri(channelUri)\n                .controlMode(controlMode)\n                .hasExplicitEndpoint(hasExplicitEndpoint)\n                .hasNoDistinguishingCharacteristic(hasNoDistinguishingCharacteristic)\n                .socketRcvbufLength(socketRcvbufLength)\n                .socketSndbufLength(socketSndbufLength)\n                .receiverWindowLength(receiverWindowLength)\n                .nakDelayNs(parseOptionalDurationNs(channelUri, NAK_DELAY_PARAM_NAME));\n\n            if (null != tagIdStr)\n            {\n                context.hasTagId(true).tagId(Long.parseLong(tagIdStr));\n            }\n\n            if (endpointAddress.getAddress().isMulticastAddress())\n            {\n                final UnresolvedInterface unresolvedInterface = getInterface(channelUri);\n                final ResolvedInterface resolvedInterface = unresolvedInterface.resolve(true, protocolFamily);\n                final NetworkInterface localInterface = resolvedInterface.localInterface();\n                final InetSocketAddress resolvedAddress = resolvedInterface.address();\n\n                context\n                    .isMulticast(true)\n                    .remoteControlAddress(getMulticastControlAddress(endpointAddress))\n                    .remoteDataAddress(endpointAddress)\n                    .localControlAddress(resolvedAddress)\n                    .localDataAddress(resolvedAddress)\n                    .localInterface(localInterface)\n                    .canonicalForm(canonicalise(null, resolvedAddress, null, endpointAddress));\n\n                final String ttlValue = channelUri.get(TTL_PARAM_NAME);\n                if (null != ttlValue)\n                {\n                    context.hasMulticastTtl(true).multicastTtl(Integer.parseInt(ttlValue));\n                }\n            }\n            else if (null != controlAddress)\n            {\n                final String controlVal = channelUri.get(MDC_CONTROL_PARAM_NAME);\n                final String endpointVal = channelUri.get(ENDPOINT_PARAM_NAME);\n\n                String suffix = \"\";\n                if (requiresAdditionalSuffix)\n                {\n                    suffix = null != tagIdStr ? \"#\" + tagIdStr : \"-\" + UNIQUE_CANONICAL_FORM_VALUE.getAndAdd(1);\n                }\n\n                final String canonicalForm = canonicalise(\n                    controlVal, controlAddress, endpointVal, endpointAddress) + suffix;\n\n                context\n                    .hasExplicitControl(true)\n                    .remoteControlAddress(endpointAddress)\n                    .remoteDataAddress(endpointAddress)\n                    .localControlAddress(controlAddress)\n                    .localDataAddress(controlAddress)\n                    .canonicalForm(canonicalForm);\n            }\n            else\n            {\n                final UnresolvedInterface unresolvedInterface = getInterface(channelUri);\n                final ResolvedInterface resolvedInterface = unresolvedInterface.resolve(false, protocolFamily);\n                final InetSocketAddress localAddress = resolvedInterface.address();\n\n                final String endpointVal = channelUri.get(ENDPOINT_PARAM_NAME);\n                String suffix = \"\";\n                if (requiresAdditionalSuffix)\n                {\n                    suffix = (null != tagIdStr) ? \"#\" + tagIdStr : (\"-\" + UNIQUE_CANONICAL_FORM_VALUE.getAndAdd(1));\n                }\n\n                context\n                    .remoteControlAddress(endpointAddress)\n                    .remoteDataAddress(endpointAddress)\n                    .localControlAddress(localAddress)\n                    .localDataAddress(localAddress)\n                    .canonicalForm(canonicalise(null, localAddress, endpointVal, endpointAddress) + suffix);\n            }\n\n            context.channelReceiveTimestampOffset(\n                parseTimestampOffset(channelUri, CHANNEL_RECEIVE_TIMESTAMP_OFFSET_PARAM_NAME));\n            context.channelSendTimestampOffset(\n                parseTimestampOffset(channelUri, CHANNEL_SEND_TIMESTAMP_OFFSET_PARAM_NAME));\n\n            final Long groupTag = parseOptionalLong(channelUri, GROUP_TAG_PARAM_NAME);\n            context.groupTag(groupTag);\n\n            return new UdpChannel(context);\n        }\n        catch (final Exception ex)\n        {\n            throw new InvalidChannelException(ex);\n        }\n    }\n\n    private static int parseTimestampOffset(final ChannelUri channelUri, final String timestampOffsetParamName)\n    {\n        final String offsetStr = channelUri.get(timestampOffsetParamName);\n        if (null == offsetStr)\n        {\n            return Aeron.NULL_VALUE;\n        }\n        else if (RESERVED_OFFSET.equals(offsetStr))\n        {\n            return RESERVED_VALUE_MESSAGE_OFFSET;\n        }\n        else\n        {\n            try\n            {\n                return Integer.parseInt(offsetStr);\n            }\n            catch (final NumberFormatException ex)\n            {\n                throw new IllegalArgumentException(\n                    \"timestamp offset must be a valid integer or the 'reserved' keyword\", ex);\n            }\n        }\n    }\n\n    private static Long parseOptionalLong(final ChannelUri channelUri, final String paramName)\n    {\n        final String longAsString = channelUri.get(paramName);\n        if (null == longAsString)\n        {\n            return null;\n        }\n\n        try\n        {\n            return Long.valueOf(longAsString);\n        }\n        catch (final NumberFormatException ex)\n        {\n            throw new IllegalArgumentException(\"'\" + paramName + \"' does not contain a valid long value\", ex);\n        }\n    }\n\n    /**\n     * Parse a buffer length for a given URI paramName with a format specified by\n     * {@link SystemUtil#parseSize(String, String)}, clamping the range to 0 &lt;= x &lt;= Integer.MAX_VALUE.\n     *\n     * @param channelUri to get the value from.\n     * @param paramName  key for the parameter.\n     * @return value as an integer.\n     * @see SystemUtil#parseSize(String, String)\n     */\n    public static int parseBufferLength(final ChannelUri channelUri, final String paramName)\n    {\n        int socketBufferLength = 0;\n\n        final String paramValue = channelUri.get(paramName);\n        if (null != paramValue)\n        {\n            final long size = SystemUtil.parseSize(paramName, paramValue);\n            if (size < 0 || size > Integer.MAX_VALUE)\n            {\n                throw new IllegalArgumentException(\"Invalid \" + paramName + \" length: \" + size);\n            }\n            socketBufferLength = (int)size;\n        }\n\n        return socketBufferLength;\n    }\n\n    /**\n     * Parse the control mode from the channel URI. If the value is null or unknown then {@link ControlMode#NONE} will\n     * be used.\n     *\n     * @param channelUri to parse the control mode from.\n     * @return an enum value representing the control mode.\n     */\n    public static ControlMode parseControlMode(final ChannelUri channelUri)\n    {\n        final String paramValue = channelUri.get(MDC_CONTROL_MODE_PARAM_NAME);\n        if (null == paramValue)\n        {\n            return ControlMode.NONE;\n        }\n\n        switch (paramValue)\n        {\n            case MDC_CONTROL_MODE_DYNAMIC:\n                return ControlMode.DYNAMIC;\n            case MDC_CONTROL_MODE_MANUAL:\n                return ControlMode.MANUAL;\n            case CONTROL_MODE_RESPONSE:\n                return ControlMode.RESPONSE;\n            default:\n                return ControlMode.NONE;\n        }\n    }\n\n    /**\n     * Parses out a duration from a channel URI and caters for unit suffix information.\n     *\n     * @param channelUri    to read the value from\n     * @param paramName     specific field to access in the URI\n     * @return              duration in nanoseconds, null if not present\n     */\n    public static Long parseOptionalDurationNs(final ChannelUri channelUri, final String paramName)\n    {\n        final String valueStr = channelUri.get(paramName);\n        if (null == valueStr)\n        {\n            return null;\n        }\n\n        return SystemUtil.parseDuration(paramName, valueStr);\n    }\n\n    /**\n     * Return a string which is a canonical form of the channel suitable for use as a file or directory\n     * name and also as a method of hashing, etc.\n     * <p>\n     * The general format is:\n     * UDP-interface:localPort-remoteAddress:remotePort\n     *\n     * @param localParamValue  interface or MDC control param value or null for not set.\n     * @param localData        address/interface for the channel.\n     * @param remoteParamValue endpoint param value or null if not set.\n     * @param remoteData       address for the channel.\n     * @return canonical representation as a string.\n     */\n    public static String canonicalise(\n        final String localParamValue,\n        final InetSocketAddress localData,\n        final String remoteParamValue,\n        final InetSocketAddress remoteData)\n    {\n        final StringBuilder builder = new StringBuilder(48);\n\n        builder.append(\"UDP-\");\n\n        if (null == localParamValue)\n        {\n            builder.append(localData.getHostString())\n                .append(':')\n                .append(localData.getPort());\n        }\n        else\n        {\n            builder.append(localParamValue);\n        }\n\n        builder.append('-');\n\n        if (null == remoteParamValue)\n        {\n            builder.append(remoteData.getHostString())\n                .append(':')\n                .append(remoteData.getPort());\n        }\n        else\n        {\n            builder.append(remoteParamValue);\n        }\n\n        return builder.toString();\n    }\n\n    /**\n     * Remote data address and port.\n     *\n     * @return remote data address and port.\n     */\n    public InetSocketAddress remoteData()\n    {\n        return remoteData;\n    }\n\n    /**\n     * Local data address and port.\n     *\n     * @return local data address port.\n     */\n    public InetSocketAddress localData()\n    {\n        return localData;\n    }\n\n    /**\n     * Remote control address information.\n     *\n     * @return remote control address information.\n     */\n    public InetSocketAddress remoteControl()\n    {\n        return remoteControl;\n    }\n\n    /**\n     * Local control address and port.\n     *\n     * @return local control address and port.\n     */\n    public InetSocketAddress localControl()\n    {\n        return localControl;\n    }\n\n    /**\n     * Get the {@link ChannelUri} for this channel.\n     *\n     * @return the {@link ChannelUri} for this channel.\n     */\n    public ChannelUri channelUri()\n    {\n        return channelUri;\n    }\n\n    /**\n     * Has this channel got a multicast TTL value set so that {@link #multicastTtl()} is valid.\n     *\n     * @return true if this channel is a multicast TTL set otherwise false.\n     */\n    public boolean hasMulticastTtl()\n    {\n        return hasMulticastTtl;\n    }\n\n    /**\n     * Multicast TTL value.\n     *\n     * @return multicast TTL value.\n     */\n    public int multicastTtl()\n    {\n        return multicastTtl;\n    }\n\n    /**\n     * The canonical form for the channel.\n     *\n     * @return canonical form for channel.\n     * @see UdpChannel#canonicalise\n     */\n    public String canonicalForm()\n    {\n        return canonicalForm;\n    }\n\n    /**\n     * The {@link #canonicalForm()} for the channel.\n     *\n     * @return the {@link #canonicalForm()} for the channel.\n     */\n    public String toString()\n    {\n        return canonicalForm;\n    }\n\n    /**\n     * Is the channel UDP multicast.\n     *\n     * @return true if the channel is UDP multicast.\n     */\n    public boolean isMulticast()\n    {\n        return isMulticast;\n    }\n\n    /**\n     * Local interface to be used by the channel.\n     *\n     * @return {@link NetworkInterface} for the local interface used by the channel.\n     */\n    public NetworkInterface localInterface()\n    {\n        return localInterface;\n    }\n\n    /**\n     * Original URI of the channel URI.\n     *\n     * @return the original uri string from the client.\n     */\n    public String originalUriString()\n    {\n        return uriStr;\n    }\n\n    /**\n     * Get the {@link ProtocolFamily} for this channel.\n     *\n     * @return the {@link ProtocolFamily} for this channel.\n     */\n    public ProtocolFamily protocolFamily()\n    {\n        return protocolFamily;\n    }\n\n    /**\n     * Get the tag value on the channel which is only valid if {@link #hasTag()} is true.\n     *\n     * @return the tag value on the channel.\n     */\n    public long tag()\n    {\n        return tag;\n    }\n\n    /**\n     * The channel's control mode.\n     *\n     * @return the control mode for the channel.\n     */\n    public ControlMode controlMode()\n    {\n        return controlMode;\n    }\n\n\n    /**\n     * Does the channel have manual control mode specified.\n     *\n     * @return does channel have manual control mode specified.\n     */\n    public boolean isManualControlMode()\n    {\n        return ControlMode.MANUAL == controlMode;\n    }\n\n    /**\n     * Does the channel have dynamic control mode specified.\n     *\n     * @return does channel have dynamic control mode specified.\n     */\n    public boolean isDynamicControlMode()\n    {\n        return ControlMode.DYNAMIC == controlMode;\n    }\n\n    /**\n     * Does the channel have response control mode specified.\n     *\n     * @return does the channel have response control mode specified.\n     */\n    public boolean isResponseControlMode()\n    {\n        return ControlMode.RESPONSE == controlMode;\n    }\n\n    /**\n     * Does the channel have an explicit endpoint address?\n     *\n     * @return does channel have an explicit endpoint address or not?\n     */\n    public boolean hasExplicitEndpoint()\n    {\n        return hasExplicitEndpoint;\n    }\n\n    /**\n     * Does the channel have an explicit control address as used with multi-destination-cast or not?\n     *\n     * @return does channel have an explicit control address or not?\n     */\n    public boolean hasExplicitControl()\n    {\n        return hasExplicitControl;\n    }\n\n    /**\n     * Has the URI a tag to indicate entity relationships and if {@link #tag()} is valid.\n     *\n     * @return true if the channel has a tag.\n     */\n    public boolean hasTag()\n    {\n        return hasTag;\n    }\n\n    /**\n     * Is the channel configured as multi-destination.\n     *\n     * @return true if the channel configured as multi-destination.\n     */\n    public boolean isMultiDestination()\n    {\n        return controlMode.isMultiDestination();\n    }\n\n    /**\n     * Does the channel have group semantics (multicast or MDC).\n     *\n     * @return true if the channel has group semantics.\n     */\n    public boolean hasGroupSemantics()\n    {\n        return isMulticast || isMultiDestination();\n    }\n\n    /**\n     * Get the socket receive buffer length.\n     *\n     * @return socket receive buffer length or 0 if not specified.\n     */\n    public int socketRcvbufLength()\n    {\n        return socketRcvbufLength;\n    }\n\n    /**\n     * Get the socket receive buffer length.\n     *\n     * @param defaultValue to be used if the UdpChannel's value is 0 (unspecified).\n     * @return socket receive buffer length or 0 if not specified.\n     */\n    public int socketRcvbufLengthOrDefault(final int defaultValue)\n    {\n        return 0 != socketRcvbufLength ? socketRcvbufLength : defaultValue;\n    }\n\n    /**\n     * Get the socket send buffer length.\n     *\n     * @return socket send buffer length or 0 if not specified.\n     */\n    public int socketSndbufLength()\n    {\n        return socketSndbufLength;\n    }\n\n    /**\n     * Get the socket send buffer length.\n     *\n     * @param defaultValue to be used if the UdpChannel's value is 0 (unspecified).\n     * @return socket send buffer length or defaultValue if not specified.\n     */\n    public int socketSndbufLengthOrDefault(final int defaultValue)\n    {\n        return 0 != socketSndbufLength ? socketSndbufLength : defaultValue;\n    }\n\n    /**\n     * Get the receiver window length used as the initial window length for congestion control.\n     *\n     * @return receiver window length or 0 if not specified.\n     */\n    public int receiverWindowLength()\n    {\n        return receiverWindowLength;\n    }\n\n    /**\n     * Get the receiver window length used as the initial window length for congestion control.\n     *\n     * @param defaultValue to be used if the UdpChannel's value is 0 (unspecified).\n     * @return receiver window length or defaultValue if not specified.\n     */\n    public int receiverWindowLengthOrDefault(final int defaultValue)\n    {\n        return 0 != receiverWindowLength() ? receiverWindowLength() : defaultValue;\n    }\n\n    /**\n     * The length of the initial nak delay to be used by the LossDetector on this channel.\n     *\n     * @return delay in nanoseconds or null if the value is not set.\n     */\n    public Long nakDelayNs()\n    {\n        return nakDelayNs;\n    }\n\n    /**\n     * Does this channel have a tag match to another channel having INADDR_ANY endpoints.\n     *\n     * @param udpChannel to match against.\n     * @param localAddress local address override to use for this channel.\n     * @param remoteAddress remote address override to use for this channel.\n     * @return true if there is a match otherwise false.\n     */\n    public boolean matchesTag(\n        final UdpChannel udpChannel,\n        final InetSocketAddress localAddress,\n        final InetSocketAddress remoteAddress)\n    {\n        if (!hasTag || !udpChannel.hasTag() || tag != udpChannel.tag())\n        {\n            return false;\n        }\n\n        if (!hasMatchingControlMode(udpChannel))\n        {\n            throw new IllegalArgumentException(\n                \"matching tag=\" + tag + \" has mismatched control-mode: \" + uriStr + \" <> \" + udpChannel.uriStr);\n        }\n\n        if (!hasMatchingAddress(udpChannel, localAddress, remoteAddress))\n        {\n            throw new IllegalArgumentException(\n                \"matching tag=\" + tag + \" has mismatched endpoint or control: \" + uriStr + \" <> \" + udpChannel.uriStr);\n        }\n\n        return true;\n    }\n\n    private boolean isWildcard()\n    {\n        return remoteData.getAddress().isAnyLocalAddress() &&\n            remoteData.getPort() == 0 &&\n            localData.getAddress().isAnyLocalAddress() &&\n            localData.getPort() == 0;\n    }\n\n    private boolean hasMatchingControlMode(final UdpChannel udpChannel)\n    {\n        return controlMode() == ControlMode.NONE || controlMode() == udpChannel.controlMode();\n    }\n\n    private boolean hasMatchingAddress(\n        final UdpChannel udpChannel,\n        final InetSocketAddress localAddress,\n        final InetSocketAddress remoteAddress)\n    {\n        final InetSocketAddress otherLocalData = localAddress != null ? localAddress : udpChannel.localData();\n        final InetSocketAddress otherRemoteData = remoteAddress != null ? remoteAddress : udpChannel.remoteData();\n\n        return isWildcard() || remoteData().getAddress().equals(otherRemoteData.getAddress()) &&\n            remoteData().getPort() == otherRemoteData.getPort() &&\n            localData().getAddress().equals(otherLocalData.getAddress()) &&\n            localData().getPort() == otherLocalData.getPort();\n    }\n\n    /**\n     * Used for debugging to get a human-readable description of the channel.\n     *\n     * @return a human-readable description of the channel.\n     */\n    public String description()\n    {\n        return \"localData: \" + formatAddressAndPort(localData.getAddress(), localData.getPort()) +\n            \", remoteData: \" + formatAddressAndPort(remoteData.getAddress(), remoteData.getPort()) +\n            \", ttl: \" + multicastTtl;\n    }\n\n    /**\n     * Channels are considered equal if the {@link #canonicalForm()} is equal.\n     *\n     * @param o object to be compared with.\n     * @return true if the {@link #canonicalForm()} is equal, otherwise false.\n     */\n    public boolean equals(final Object o)\n    {\n        if (this == o)\n        {\n            return true;\n        }\n\n        if (o == null || getClass() != o.getClass())\n        {\n            return false;\n        }\n\n        final UdpChannel that = (UdpChannel)o;\n\n        return Objects.equals(canonicalForm, that.canonicalForm);\n    }\n\n    /**\n     * The hash code for the {@link #canonicalForm()}.\n     *\n     * @return the hash code for the {@link #canonicalForm()}.\n     */\n    public int hashCode()\n    {\n        return canonicalForm != null ? canonicalForm.hashCode() : 0;\n    }\n\n    /**\n     * Get the endpoint destination address from the URI.\n     *\n     * @param uri          to check.\n     * @param nameResolver to use for resolution.\n     * @return endpoint address for URI.\n     */\n    public static InetSocketAddress destinationAddress(final ChannelUri uri, final NameResolver nameResolver)\n    {\n        try\n        {\n            validateConfiguration(uri);\n\n            final String endpointValue = uri.get(ENDPOINT_PARAM_NAME);\n            return SocketAddressParser.parse(endpointValue, ENDPOINT_PARAM_NAME, false, nameResolver);\n        }\n        catch (final Exception ex)\n        {\n            throw new InvalidChannelException(ex);\n        }\n    }\n\n    /**\n     * Check if the address pointed to by the endpoint is multicast.\n     *\n     * @param uri to check.\n     * @return {@code true} if the destination uses multicast address.\n     * @since 1.44.0\n     */\n    public static boolean isMulticastDestinationAddress(final ChannelUri uri)\n    {\n        try\n        {\n            validateConfiguration(uri);\n\n            final String endpointValue = uri.get(ENDPOINT_PARAM_NAME);\n            return isMulticastEndpoint(endpointValue);\n        }\n        catch (final Exception ex)\n        {\n            throw new InvalidChannelException(ex);\n        }\n    }\n\n    /**\n     * Check if the endpoint is multicast.\n     *\n     * @param endpoint to check.\n     * @return {@code true} if the destination uses multicast address.\n     * @since 1.47.0\n     */\n    public static boolean isMulticastEndpoint(final String endpoint)\n    {\n        return SocketAddressParser.isMulticastAddress(endpoint);\n    }\n\n    /**\n     * Resolve and endpoint into a {@link InetSocketAddress}.\n     *\n     * @param endpoint       to resolve.\n     * @param uriParamName   for the resolution.\n     * @param isReResolution for the resolution.\n     * @param nameResolver   to be used for hostname.\n     * @return address for endpoint.\n     */\n    public static InetSocketAddress resolve(\n        final String endpoint, final String uriParamName, final boolean isReResolution, final NameResolver nameResolver)\n    {\n        return SocketAddressParser.parse(endpoint, uriParamName, isReResolution, nameResolver);\n    }\n\n    /**\n     * Offset to store the channel receive timestamp in a user message.\n     *\n     * @return offset of channel receive timestamps.\n     */\n    public int channelReceiveTimestampOffset()\n    {\n        return channelReceiveTimestampOffset;\n    }\n\n    /**\n     * Check if channel receive timestamps should be recorded.\n     *\n     * @return true if channel receive timestamps should be collected false otherwise.\n     */\n    public boolean isChannelReceiveTimestampEnabled()\n    {\n        return RESERVED_VALUE_MESSAGE_OFFSET == channelReceiveTimestampOffset || 0 <= channelReceiveTimestampOffset;\n    }\n\n    /**\n     * Check if channel send timestamps should be recorded.\n     *\n     * @return true if channel send timestamps should be collected false otherwise.\n     */\n    public boolean isChannelSendTimestampEnabled()\n    {\n        return RESERVED_VALUE_MESSAGE_OFFSET == channelSendTimestampOffset || 0 <= channelSendTimestampOffset;\n    }\n\n    /**\n     * Offset to store the channel send timestamp in a user message.\n     *\n     * @return offset of channel send timestamps.\n     */\n    public int channelSendTimestampOffset()\n    {\n        return channelSendTimestampOffset;\n    }\n\n    /**\n     * Group tag specified for this channel or null if not specified.\n     *\n     * @return group tag for the channel\n     */\n    public Long groupTag()\n    {\n        return groupTag;\n    }\n\n\n    private static InetSocketAddress getMulticastControlAddress(final InetSocketAddress endpointAddress)\n        throws UnknownHostException\n    {\n        final byte[] addressAsBytes = endpointAddress.getAddress().getAddress();\n        validateDataAddress(addressAsBytes);\n\n        addressAsBytes[addressAsBytes.length - 1]++;\n        return new InetSocketAddress(getByAddress(addressAsBytes), endpointAddress.getPort());\n    }\n\n    private static UnresolvedInterface getInterface(final ChannelUri uri) throws UnknownHostException\n    {\n        final String interfaceValue = uri.get(INTERFACE_PARAM_NAME);\n        if (null != interfaceValue)\n        {\n            return UnresolvedInterface.parse(interfaceValue);\n        }\n\n        return InterfaceSearchAddress.wildcard();\n    }\n\n    private static InetSocketAddress getEndpointAddress(final ChannelUri uri, final NameResolver nameResolver)\n        throws UnknownHostException\n    {\n        InetSocketAddress address = null;\n        final String endpointValue = uri.get(ENDPOINT_PARAM_NAME);\n        if (null != endpointValue)\n        {\n            address = SocketAddressParser.parse(\n                endpointValue, ENDPOINT_PARAM_NAME, false, nameResolver);\n\n            if (address.isUnresolved())\n            {\n                throw new UnknownHostException(\n                    \"unresolved - \" + ENDPOINT_PARAM_NAME + \"=\" + endpointValue +\n                    \", name-resolver=\" + nameResolver.name());\n            }\n        }\n\n        return address;\n    }\n\n    private static InetSocketAddress getExplicitControlAddress(final ChannelUri uri, final NameResolver nameResolver)\n        throws UnknownHostException\n    {\n        InetSocketAddress address = null;\n        final String controlValue = uri.get(MDC_CONTROL_PARAM_NAME);\n        if (null != controlValue)\n        {\n            address = SocketAddressParser.parse(\n                controlValue, MDC_CONTROL_PARAM_NAME, false, nameResolver);\n\n            if (address.isUnresolved())\n            {\n                throw new UnknownHostException(\n                    \"unresolved - \" + MDC_CONTROL_PARAM_NAME + \"=\" + controlValue +\n                    \", name-resolver=\" + nameResolver.getClass().getName());\n            }\n        }\n\n        return address;\n    }\n\n    private static void validateDataAddress(final byte[] addressAsBytes)\n    {\n        if (BitUtil.isEven(addressAsBytes[addressAsBytes.length - 1]))\n        {\n            throw new IllegalArgumentException(\"multicast data address must be odd\");\n        }\n    }\n\n    private static void validateConfiguration(final ChannelUri uri)\n    {\n        validateMedia(uri);\n    }\n\n    private static void validateMedia(final ChannelUri uri)\n    {\n        if (!uri.isUdp())\n        {\n            throw new IllegalArgumentException(\"UdpChannel only supports UDP media: \" + uri);\n        }\n    }\n\n    static final class Context\n    {\n        ControlMode controlMode = ControlMode.NONE;\n        boolean hasExplicitEndpoint = false;\n        boolean hasExplicitControl = false;\n        boolean isMulticast = false;\n        boolean hasMulticastTtl = false;\n        boolean hasTagId = false;\n        boolean hasNoDistinguishingCharacteristic = false;\n        int socketRcvbufLength = 0;\n        int socketSndbufLength = 0;\n        int receiverWindowLength = 0;\n        int multicastTtl;\n        long tagId;\n        InetSocketAddress remoteData;\n        InetSocketAddress localData;\n        InetSocketAddress remoteControl;\n        InetSocketAddress localControl;\n        String uriStr;\n        String canonicalForm;\n        NetworkInterface localInterface;\n        ProtocolFamily protocolFamily;\n        ChannelUri channelUri;\n        int channelReceiveTimestampOffset;\n        int channelSendTimestampOffset;\n        Long groupTag = null;\n        Long nakDelayNs = null;\n\n        Context uriStr(final String uri)\n        {\n            uriStr = uri;\n            return this;\n        }\n\n        Context remoteDataAddress(final InetSocketAddress remoteData)\n        {\n            this.remoteData = remoteData;\n            return this;\n        }\n\n        Context localDataAddress(final InetSocketAddress localData)\n        {\n            this.localData = localData;\n            return this;\n        }\n\n        Context remoteControlAddress(final InetSocketAddress remoteControl)\n        {\n            this.remoteControl = remoteControl;\n            return this;\n        }\n\n        Context localControlAddress(final InetSocketAddress localControl)\n        {\n            this.localControl = localControl;\n            return this;\n        }\n\n        @SuppressWarnings(\"UnusedReturnValue\")\n        Context canonicalForm(final String canonicalForm)\n        {\n            this.canonicalForm = canonicalForm;\n            return this;\n        }\n\n        Context localInterface(final NetworkInterface networkInterface)\n        {\n            this.localInterface = networkInterface;\n            return this;\n        }\n\n        Context protocolFamily(final ProtocolFamily protocolFamily)\n        {\n            this.protocolFamily = protocolFamily;\n            return this;\n        }\n\n        Context hasMulticastTtl(final boolean hasMulticastTtl)\n        {\n            this.hasMulticastTtl = hasMulticastTtl;\n            return this;\n        }\n\n        @SuppressWarnings(\"UnusedReturnValue\")\n        Context multicastTtl(final int multicastTtl)\n        {\n            this.multicastTtl = multicastTtl;\n            return this;\n        }\n\n        @SuppressWarnings(\"UnusedReturnValue\")\n        Context tagId(final long tagId)\n        {\n            this.tagId = tagId;\n            return this;\n        }\n\n        Context channelUri(final ChannelUri channelUri)\n        {\n            this.channelUri = channelUri;\n            return this;\n        }\n\n        Context controlMode(final ControlMode controlMode)\n        {\n            this.controlMode = controlMode;\n            return this;\n        }\n\n        Context hasExplicitEndpoint(final boolean hasExplicitEndpoint)\n        {\n            this.hasExplicitEndpoint = hasExplicitEndpoint;\n            return this;\n        }\n\n        Context hasExplicitControl(final boolean hasExplicitControl)\n        {\n            this.hasExplicitControl = hasExplicitControl;\n            return this;\n        }\n\n        Context isMulticast(final boolean isMulticast)\n        {\n            this.isMulticast = isMulticast;\n            return this;\n        }\n\n        Context hasTagId(final boolean hasTagId)\n        {\n            this.hasTagId = hasTagId;\n            return this;\n        }\n\n        Context hasNoDistinguishingCharacteristic(final boolean hasNoDistinguishingCharacteristic)\n        {\n            this.hasNoDistinguishingCharacteristic = hasNoDistinguishingCharacteristic;\n            return this;\n        }\n\n        Context socketRcvbufLength(final int socketRcvbufLength)\n        {\n            this.socketRcvbufLength = socketRcvbufLength;\n            return this;\n        }\n\n        Context socketSndbufLength(final int socketSndbufLength)\n        {\n            this.socketSndbufLength = socketSndbufLength;\n            return this;\n        }\n\n        Context receiverWindowLength(final int receiverWindowLength)\n        {\n            this.receiverWindowLength = receiverWindowLength;\n            return this;\n        }\n\n        @SuppressWarnings(\"UnusedReturnValue\")\n        Context channelReceiveTimestampOffset(final int timestampOffset)\n        {\n            this.channelReceiveTimestampOffset = timestampOffset;\n            return this;\n        }\n\n        @SuppressWarnings(\"UnusedReturnValue\")\n        Context channelSendTimestampOffset(final int timestampOffset)\n        {\n            this.channelSendTimestampOffset = timestampOffset;\n            return this;\n        }\n\n        Context nakDelayNs(final Long nakDelayNs)\n        {\n            this.nakDelayNs = nakDelayNs;\n            return this;\n        }\n\n        public void groupTag(final Long groupTag)\n        {\n            this.groupTag = groupTag;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/UdpChannelTransport.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.exceptions.AeronEvent;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.protocol.HeaderFlyweight;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.PortUnreachableException;\nimport java.net.StandardSocketOptions;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\n\nimport static io.aeron.logbuffer.FrameDescriptor.frameVersion;\nimport static java.net.StandardSocketOptions.SO_RCVBUF;\nimport static java.net.StandardSocketOptions.SO_SNDBUF;\n\n/**\n * Base class for UDP channel transports which is specialised for send or receive endpoints.\n */\npublic abstract class UdpChannelTransport implements AutoCloseable\n{\n    /**\n     * Context for configuration.\n     */\n    protected final MediaDriver.Context context;\n\n    /**\n     * {@link ErrorHandler} for logging errors and progressing with throwing.\n     */\n    protected final ErrorHandler errorHandler;\n\n    /**\n     * Media configuration for the channel.\n     */\n    protected final UdpChannel udpChannel;\n\n    /**\n     * Channel to be used for sending frames from the perspective of the endpoint.\n     */\n    protected DatagramChannel sendDatagramChannel;\n\n    /**\n     * Channel to be used for receiving frames from the perspective of the endpoint.\n     */\n    protected DatagramChannel receiveDatagramChannel;\n\n    /**\n     * Address to connect to if appropriate for sending.\n     */\n    protected InetSocketAddress connectAddress;\n\n    private InetSocketAddress bindAddress;\n    private final InetSocketAddress endPointAddress;\n    private final AtomicCounter invalidPackets;\n    private final PortManager portManager;\n\n    private int multicastTtl = 0;\n    private final int socketSndbufLength;\n    private final int socketRcvbufLength;\n\n    /**\n     * Construct transport for a given channel.\n     *\n     * @param udpChannel         configuration for the media.\n     * @param endPointAddress    to which data will be sent.\n     * @param bindAddress        for listening on.\n     * @param connectAddress     for sending data to.\n     * @param context            for configuration.\n     * @param portManager        for port binding.\n     * @param socketRcvbufLength set SO_RCVBUF for socket, 0 for OS default.\n     * @param socketSndbufLength set SO_SNDBUF for socket, 0 for OS default.\n     */\n    protected UdpChannelTransport(\n        final UdpChannel udpChannel,\n        final InetSocketAddress endPointAddress,\n        final InetSocketAddress bindAddress,\n        final InetSocketAddress connectAddress,\n        final PortManager portManager,\n        final MediaDriver.Context context,\n        final int socketRcvbufLength,\n        final int socketSndbufLength)\n    {\n        this.context = context;\n        this.udpChannel = udpChannel;\n        this.errorHandler = context.countedErrorHandler();\n        this.portManager = portManager;\n        this.endPointAddress = endPointAddress;\n        this.bindAddress = bindAddress;\n        this.connectAddress = connectAddress;\n        this.invalidPackets = context.systemCounters().get(SystemCounterDescriptor.INVALID_PACKETS);\n        this.socketRcvbufLength = socketRcvbufLength;\n        this.socketSndbufLength = socketSndbufLength;\n    }\n\n    /**\n     * Construct transport for a given channel.\n     *\n     * @param udpChannel      configuration for the media.\n     * @param endPointAddress to which data will be sent.\n     * @param bindAddress     for listening on.\n     * @param connectAddress  for sending data to.\n     * @param portManager     for port binding.\n     * @param context         for configuration.\n     */\n    protected UdpChannelTransport(\n        final UdpChannel udpChannel,\n        final InetSocketAddress endPointAddress,\n        final InetSocketAddress bindAddress,\n        final InetSocketAddress connectAddress,\n        final PortManager portManager,\n        final MediaDriver.Context context)\n    {\n        this(\n            udpChannel,\n            endPointAddress,\n            bindAddress,\n            connectAddress,\n            portManager,\n            context,\n            udpChannel.socketRcvbufLengthOrDefault(context.socketRcvbufLength()),\n            udpChannel.socketSndbufLengthOrDefault(context.socketSndbufLength()));\n    }\n\n    /**\n     * Throw a {@link AeronException} with a message for a send error.\n     *\n     * @param bytesToSend expected to be sent to the network.\n     * @param ex          experienced.\n     * @param destination to which the send operation was addressed.\n     * @see #onSendError(IOException, InetSocketAddress, ErrorHandler)\n     * @deprecated {@link #onSendError(IOException, InetSocketAddress, ErrorHandler)} is used instead.\n     */\n    @Deprecated(forRemoval = true, since = \"1.46.6\")\n    public static void sendError(final int bytesToSend, final IOException ex, final InetSocketAddress destination)\n    {\n        throw new AeronException(\n            \"failed to send \" + bytesToSend + \" byte packet to \" + destination, ex, AeronException.Category.WARN);\n    }\n\n    /**\n     * Report an {@link AeronEvent} with a message for a send error.\n     *\n     * @param ex           experienced.\n     * @param destination  to which the send operation was addressed.\n     * @param errorHandler to report error to.\n     */\n    public static void onSendError(\n        final IOException ex, final InetSocketAddress destination, final ErrorHandler errorHandler)\n    {\n        errorHandler.onError(new AeronEvent(\n            \"failed to send datagram to \" + destination + \", cause: \" + ex, AeronException.Category.WARN));\n    }\n\n    /**\n     * Open the underlying channel for reading and writing.\n     *\n     * @param statusIndicator to set for {@link ChannelEndpointStatus} which could be\n     *                        {@link ChannelEndpointStatus#ERRORED}.\n     */\n    public void openDatagramChannel(final AtomicCounter statusIndicator)\n    {\n        try\n        {\n            sendDatagramChannel = DatagramChannel.open(udpChannel.protocolFamily());\n            receiveDatagramChannel = sendDatagramChannel;\n\n            if (udpChannel.isMulticast())\n            {\n                if (null != connectAddress)\n                {\n                    receiveDatagramChannel = DatagramChannel.open(udpChannel.protocolFamily());\n                }\n\n                receiveDatagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);\n                receiveDatagramChannel.bind(new InetSocketAddress(endPointAddress.getPort()));\n                receiveDatagramChannel.join(endPointAddress.getAddress(), udpChannel.localInterface());\n                sendDatagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, udpChannel.localInterface());\n\n                if (udpChannel.hasMulticastTtl())\n                {\n                    sendDatagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_TTL, udpChannel.multicastTtl());\n                    multicastTtl = sendDatagramChannel.getOption(StandardSocketOptions.IP_MULTICAST_TTL);\n                }\n                else if (context.socketMulticastTtl() != 0)\n                {\n                    sendDatagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_TTL, context.socketMulticastTtl());\n                    multicastTtl = sendDatagramChannel.getOption(StandardSocketOptions.IP_MULTICAST_TTL);\n                }\n            }\n            else\n            {\n                bindAddress = portManager.getManagedPort(udpChannel, bindAddress);\n                sendDatagramChannel.bind(bindAddress);\n            }\n\n            if (null != connectAddress)\n            {\n                sendDatagramChannel.connect(connectAddress);\n            }\n\n            if (0 != socketSndbufLength())\n            {\n                sendDatagramChannel.setOption(SO_SNDBUF, socketSndbufLength());\n            }\n\n            if (0 != socketRcvbufLength())\n            {\n                receiveDatagramChannel.setOption(SO_RCVBUF, socketRcvbufLength());\n            }\n\n            sendDatagramChannel.configureBlocking(false);\n            receiveDatagramChannel.configureBlocking(false);\n        }\n        catch (final IOException ex)\n        {\n            if (null != statusIndicator)\n            {\n                statusIndicator.setRelease(ChannelEndpointStatus.ERRORED);\n            }\n\n            CloseHelper.quietClose(sendDatagramChannel);\n            if (receiveDatagramChannel != sendDatagramChannel)\n            {\n                CloseHelper.quietClose(receiveDatagramChannel);\n            }\n\n            sendDatagramChannel = null;\n            receiveDatagramChannel = null;\n\n            final String message = \"channel error - \" + ex.getMessage() +\n                \" (at \" + ex.getStackTrace()[0].toString() + \"): \" + udpChannel.originalUriString();\n\n            throw new AeronException(message, ex);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.close(errorHandler, sendDatagramChannel);\n        if (receiveDatagramChannel != sendDatagramChannel)\n        {\n            CloseHelper.close(errorHandler, receiveDatagramChannel);\n        }\n        portManager.freeManagedPort(bindAddress);\n    }\n\n    /**\n     * Return underlying {@link UdpChannel}.\n     *\n     * @return underlying channel.\n     */\n    public UdpChannel udpChannel()\n    {\n        return udpChannel;\n    }\n\n    /**\n     * The {@link DatagramChannel} for this transport channel.\n     *\n     * @return {@link DatagramChannel} for this transport channel.\n     */\n    public DatagramChannel receiveDatagramChannel()\n    {\n        return receiveDatagramChannel;\n    }\n\n    /**\n     * Get the multicast TTL value for sending datagrams on the channel.\n     *\n     * @return the multicast TTL value for sending datagrams on the channel.\n     */\n    public int multicastTtl()\n    {\n        return multicastTtl;\n    }\n\n    /**\n     * Get the bind address and port in endpoint-style format (ip:port).\n     * <p>\n     * Must be called after the channel is opened.\n     *\n     * @return the bind address and port in endpoint-style format (ip:port).\n     */\n    public String bindAddressAndPort()\n    {\n        try\n        {\n            final InetSocketAddress localAddress = (InetSocketAddress)receiveDatagramChannel.getLocalAddress();\n            if (null != localAddress)\n            {\n                return NetworkUtil.formatAddressAndPort(localAddress.getAddress(), localAddress.getPort());\n            }\n        }\n        catch (final IOException ignore)\n        {\n        }\n\n        return \"\";\n    }\n\n    /**\n     * Is transport representing a multicast media?\n     *\n     * @return true if transport is multicast media, otherwise false.\n     */\n    public boolean isMulticast()\n    {\n        return udpChannel.isMulticast();\n    }\n\n    /**\n     * Is the received frame valid. This method will do some basic checks on the header and can be\n     * overridden in a subclass for further validation.\n     *\n     * @param buffer containing the frame.\n     * @param length of the frame.\n     * @return true if the frame is believed valid otherwise false.\n     */\n    public boolean isValidFrame(final UnsafeBuffer buffer, final int length)\n    {\n        if (length >= HeaderFlyweight.MIN_HEADER_LENGTH &&\n            frameVersion(buffer, 0) == HeaderFlyweight.CURRENT_VERSION)\n        {\n            return true;\n        }\n\n        invalidPackets.increment();\n        return false;\n    }\n\n    /**\n     * Send packet hook that can be used for logging.\n     *\n     * @param buffer  containing the packet.\n     * @param address to which the packet will be sent.\n     */\n    @SuppressWarnings(\"unused\")\n    public void sendHook(final ByteBuffer buffer, final InetSocketAddress address)\n    {\n    }\n\n    /**\n     * Receive packet hook that can be useful for logging.\n     *\n     * @param buffer  containing the packet.\n     * @param length  length of the packet in bytes.\n     * @param address from which the packet came.\n     */\n    @SuppressWarnings(\"unused\")\n    public void receiveHook(final UnsafeBuffer buffer, final int length, final InetSocketAddress address)\n    {\n    }\n\n    /**\n     * Useful hook for logging resend calls.\n     *\n     * @param sessionId  to resend\n     * @param streamId   to resend\n     * @param termId     to resend\n     * @param termOffset to resend\n     * @param length     to resend\n     */\n    @SuppressWarnings(\"unused\")\n    public void resendHook(\n        final int sessionId, final int streamId, final int termId, final int termOffset, final int length)\n    {\n    }\n\n    /**\n     * Receive a datagram from the media layer.\n     *\n     * @param buffer into which the datagram will be received.\n     * @return the source address of the datagram if one is available otherwise false.\n     */\n    public InetSocketAddress receive(final ByteBuffer buffer)\n    {\n        buffer.clear();\n\n        InetSocketAddress address = null;\n        try\n        {\n            if (receiveDatagramChannel.isOpen())\n            {\n                address = (InetSocketAddress)receiveDatagramChannel.receive(buffer);\n            }\n        }\n        catch (final PortUnreachableException ignored)\n        {\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return address;\n    }\n\n    /**\n     * Endpoint has moved to a new address. Handle this.\n     *\n     * @param newAddress      to send data to.\n     * @param statusIndicator for the channel\n     */\n    public void updateEndpoint(final InetSocketAddress newAddress, final AtomicCounter statusIndicator)\n    {\n        try\n        {\n            if (null != sendDatagramChannel)\n            {\n                sendDatagramChannel.disconnect();\n                sendDatagramChannel.connect(newAddress);\n                connectAddress = newAddress;\n\n                if (null != statusIndicator)\n                {\n                    statusIndicator.setRelease(ChannelEndpointStatus.ACTIVE);\n                }\n            }\n        }\n        catch (final Exception ex)\n        {\n            if (null != statusIndicator)\n            {\n                statusIndicator.setRelease(ChannelEndpointStatus.ERRORED);\n            }\n\n            final String message = \"re-resolve endpoint channel error - \" + ex.getMessage() +\n                \" (at \" + ex.getStackTrace()[0].toString() + \"): \" + udpChannel.originalUriString();\n\n            throw new AeronException(message, ex);\n        }\n    }\n\n    /**\n     * Get the configured OS send socket buffer length (SO_SNDBUF) for the endpoint's socket.\n     *\n     * @return OS socket send buffer length or 0 if using OS default.\n     */\n    public int socketSndbufLength()\n    {\n        return socketSndbufLength;\n    }\n\n    /**\n     * Get the configured OS receive socket buffer length (SO_RCVBUF) for the endpoint's socket.\n     *\n     * @return OS socket receive buffer length or 0 if using OS default.\n     */\n    public int socketRcvbufLength()\n    {\n        return socketRcvbufLength;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/UdpNameResolutionTransport.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.CommonContext;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.NameResolver;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.UnknownHostException;\nimport java.nio.ByteBuffer;\n\n/**\n * {@link UdpChannelTransport} specialised for name resolution between {@link MediaDriver}s.\n */\npublic final class UdpNameResolutionTransport extends UdpChannelTransport\n{\n    /**\n     * Handler for processing the received frames.\n     */\n    @FunctionalInterface\n    public interface UdpFrameHandler\n    {\n        /**\n         * Callback for processing the received frames.\n         *\n         * @param unsafeBuffer containing the received frame.\n         * @param length       of the frame in the buffer.\n         * @param srcAddress   the frame came from.\n         * @param nowMs        current time.\n         * @return the number of bytes received.\n         */\n        int onFrame(UnsafeBuffer unsafeBuffer, int length, InetSocketAddress srcAddress, long nowMs);\n    }\n\n    private final UnsafeBuffer unsafeBuffer;\n    private final ByteBuffer byteBuffer;\n\n    /**\n     * Construct a new channel transport for name resolution.\n     *\n     * @param udpChannel      associated with the transport.\n     * @param resolverAddress to listen on.\n     * @param unsafeBuffer    for reading frames.\n     * @param context         for configuration.\n     */\n    public UdpNameResolutionTransport(\n        final UdpChannel udpChannel,\n        final InetSocketAddress resolverAddress,\n        final UnsafeBuffer unsafeBuffer,\n        final MediaDriver.Context context)\n    {\n        super(udpChannel, null, resolverAddress, null, context.receiverPortManager(), context);\n\n        this.unsafeBuffer = unsafeBuffer;\n        this.byteBuffer = unsafeBuffer.byteBuffer();\n    }\n\n    /**\n     * Poll the transport for received frames to be delivered to a {@link UdpFrameHandler}.\n     *\n     * @param handler for processing the frames.\n     * @param nowMs   current time.\n     * @return number of bytes received.\n     */\n    public int poll(final UdpFrameHandler handler, final long nowMs)\n    {\n        int bytesReceived = 0;\n        final InetSocketAddress srcAddress = receive(byteBuffer);\n\n        if (null != srcAddress)\n        {\n            final int length = byteBuffer.position();\n\n            if (isValidFrame(unsafeBuffer, length))\n            {\n                receiveHook(unsafeBuffer, length, srcAddress);\n                bytesReceived = handler.onFrame(unsafeBuffer, length, srcAddress, nowMs);\n            }\n        }\n\n        return bytesReceived;\n    }\n\n    /**\n     * Send contents of {@link java.nio.ByteBuffer} to the remote address.\n     *\n     * @param buffer        to send containing the payload.\n     * @param remoteAddress to send the payload to.\n     * @return number of bytes sent.\n     */\n    public int sendTo(final ByteBuffer buffer, final InetSocketAddress remoteAddress)\n    {\n        int bytesSent = 0;\n        try\n        {\n            if (null != sendDatagramChannel)\n            {\n                if (sendDatagramChannel.isOpen())\n                {\n                    sendHook(buffer, remoteAddress);\n                    bytesSent = sendDatagramChannel.send(buffer, remoteAddress);\n                }\n            }\n        }\n        catch (final IOException ex)\n        {\n            onSendError(ex, remoteAddress, errorHandler);\n        }\n\n        return bytesSent;\n    }\n\n    /**\n     * The {@link InetSocketAddress} which the resolver is bound to for listening to requests.\n     *\n     * @return the {@link InetSocketAddress} which the resolver is bound to for listening to requests.\n     */\n    public InetSocketAddress boundAddress()\n    {\n        try\n        {\n            return (InetSocketAddress)receiveDatagramChannel.getLocalAddress();\n        }\n        catch (final IOException ex)\n        {\n            return null;\n        }\n    }\n\n    /**\n     * Get {@link InetSocketAddress} for an interface of an address and port.\n     *\n     * @param addressAndPort for the endpoint.\n     * @return the {@link InetSocketAddress} if successful or null if not.\n     */\n    public static InetSocketAddress getInterfaceAddress(final String addressAndPort)\n    {\n        try\n        {\n            return InterfaceSearchAddress.parse(addressAndPort).address();\n        }\n        catch (final UnknownHostException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n            return null;\n        }\n    }\n\n    /**\n     * Get the {@link InetSocketAddress} for a host and port endpoint using the default name resolver.\n     *\n     * @param hostAndPort  to parse.\n     * @param nameResolver to resolve a name to an {@link InetAddress}.\n     * @return the resolved {@link InetSocketAddress} if successful or null if not.\n     */\n    public static InetSocketAddress getInetSocketAddress(final String hostAndPort, final NameResolver nameResolver)\n    {\n        InetSocketAddress address = null;\n\n        try\n        {\n            address = SocketAddressParser.parse(\n                hostAndPort, CommonContext.ENDPOINT_PARAM_NAME, false, nameResolver);\n\n            if (address.isUnresolved())\n            {\n                throw new UnknownHostException(\n                    \"unresolved - \" + CommonContext.ENDPOINT_PARAM_NAME + \"=\" + hostAndPort +\n                    \", name-resolver=\" + nameResolver.getClass().getName());\n            }\n        }\n        catch (final UnknownHostException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return address;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/UdpTransportPoller.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.agrona.ErrorHandler;\nimport org.agrona.nio.TransportPoller;\n\n/**\n * Encapsulates the polling of a number of {@link UdpChannelTransport}s using whatever means provides the\n * lowest latency.\n */\npublic abstract class UdpTransportPoller extends TransportPoller\n{\n    /**\n     * {@link ErrorHandler} which can be used to log errors and continue.\n     */\n    protected final ErrorHandler errorHandler;\n\n    /**\n     * Construct a new {@link TransportPoller} with an {@link ErrorHandler} for logging.\n     *\n     * @param errorHandler which can be used to log errors and continue.\n     */\n    public UdpTransportPoller(final ErrorHandler errorHandler)\n    {\n        this.errorHandler = errorHandler;\n    }\n\n    /**\n     * Explicit event loop processing as a poll.\n     *\n     * @return the number of frames processed.\n     */\n    public abstract int pollTransports();\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/UnresolvedInterface.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.agrona.Strings;\n\nimport java.net.ProtocolFamily;\nimport java.net.SocketException;\nimport java.net.UnknownHostException;\n\n/**\n * Interface specification as found, for example, in the {@code interface=} channel parameter.\n */\nsealed interface UnresolvedInterface permits InterfaceSearchAddress, NamedInterface\n{\n    ResolvedInterface resolve(boolean multicast, ProtocolFamily protocolFamily) throws SocketException;\n\n    static UnresolvedInterface parse(final String str) throws UnknownHostException\n    {\n        if (Strings.isEmpty(str))\n        {\n            throw new IllegalArgumentException(\"interface is null or empty\");\n        }\n\n        if (str.charAt(0) == NamedInterface.OPENING_CHAR)\n        {\n            return NamedInterface.parse(str);\n        }\n        else\n        {\n            return InterfaceSearchAddress.parse(str);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/WildcardPortManager.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.agrona.collections.IntHashSet;\n\nimport java.net.BindException;\nimport java.net.InetSocketAddress;\n\n/**\n * Class for managing wildcard ports for UDP channel transports within a specific range.\n * <p>\n * Does not track per interface, but as a range over all interfaces.\n */\npublic class WildcardPortManager implements PortManager\n{\n    /**\n     * Placeholder to represent an empty port range.\n     */\n    public static final int[] EMPTY_PORT_RANGE = new int[2];\n\n    final IntHashSet portSet = new IntHashSet();\n    final int highPort;\n    final int lowPort;\n    final boolean isOsWildcard;\n    final boolean isSender;\n    int nextPort;\n\n    /**\n     * Instantiate a wildcard port manager with the given port range.\n     *\n     * @param portRange for the port manager\n     * @param isSender for this port manager\n     */\n    public WildcardPortManager(final int[] portRange, final boolean isSender)\n    {\n        this.lowPort = portRange[0];\n        this.highPort = portRange[1];\n        this.isOsWildcard = lowPort == highPort && 0 == lowPort;\n        this.nextPort = lowPort;\n        this.isSender = isSender;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public InetSocketAddress getManagedPort(\n        final UdpChannel udpChannel,\n        final InetSocketAddress bindAddress) throws BindException\n    {\n        InetSocketAddress address = bindAddress;\n\n        if (bindAddress.getPort() != 0)\n        {\n            portSet.add(bindAddress.getPort());\n        }\n        else if (!isOsWildcard)\n        {\n            // do not map if not a subscription and does not have a control address. We want to use an ephemeral port\n            // for the control channel on publications.\n            if (!isSender || udpChannel.hasExplicitControl())\n            {\n                address = new InetSocketAddress(bindAddress.getAddress(), allocateOpenPort());\n            }\n        }\n\n        return address;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void freeManagedPort(final InetSocketAddress bindAddress)\n    {\n        if (bindAddress.getPort() != 0)\n        {\n            portSet.remove(bindAddress.getPort());\n        }\n    }\n\n    /**\n     * Parse a port range in the format \"low high\".\n     *\n     * @param value for the port range in string format.\n     * @return port range as low index 0 and high index 1.\n     */\n    public static int[] parsePortRange(final String value)\n    {\n        if (null == value || value.isEmpty())\n        {\n            return EMPTY_PORT_RANGE;\n        }\n\n        final String[] ports = value.split(\" +\");\n        if (ports.length != 2)\n        {\n            throw new IllegalArgumentException(\"port range \\\"\" + value + \"\\\" incorrect format\");\n        }\n\n        final int[] portRange = new int[2];\n\n        try\n        {\n            portRange[0] = Integer.parseUnsignedInt(ports[0]);\n            portRange[1] = Integer.parseUnsignedInt(ports[1]);\n\n            if (portRange[0] > 65535 || portRange[1] > 65535)\n            {\n                throw new NumberFormatException(value + \":port must be an integer value between 0 and 65535\");\n            }\n\n            if (portRange[0] > portRange[1])\n            {\n                throw new NumberFormatException(value + \":low port value must be lower than high port value\");\n            }\n        }\n        catch (final NumberFormatException ex)\n        {\n            throw new IllegalArgumentException(\"invalid port value \" + value, ex);\n        }\n\n        return portRange;\n    }\n\n    private int findOpenPort()\n    {\n        for (int i = nextPort; i <= highPort; i++)\n        {\n            if (!portSet.contains(i))\n            {\n                return i;\n            }\n        }\n\n        for (int i = lowPort; i < nextPort; i++)\n        {\n            if (!portSet.contains(i))\n            {\n                return i;\n            }\n        }\n\n        return 0;\n    }\n\n    private int allocateOpenPort() throws BindException\n    {\n        final int port = findOpenPort();\n\n        if (0 == port)\n        {\n            throw new BindException(\"no available ports in range \" + this.lowPort + \" \" + this.highPort);\n        }\n\n        nextPort = port + 1;\n        if (nextPort > highPort)\n        {\n            nextPort = lowPort;\n        }\n\n        portSet.add(port);\n\n        return port;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/media/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Media, such as UDP, specific implementations used by the driver.\n */\npackage io.aeron.driver.media;"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * The {@link io.aeron.driver.MediaDriver} manages sending and receiving from the underlying media and handling\n * request from the Aeron clients. The media driver can run in-process, or out of process, from the Aeron clients.\n */\npackage io.aeron.driver;"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/reports/LossReport.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.reports;\n\nimport org.agrona.BitUtil;\nimport org.agrona.concurrent.AtomicBuffer;\n\nimport static org.agrona.BitUtil.*;\n\n/**\n * A report of loss events on a message stream.\n * <p>\n * The provided {@link AtomicBuffer} can wrap a memory-mapped file so logging can be out of process. This provides\n * the benefit that if a crash or lockup occurs then the log can be read externally without loss of data.\n * <p>\n * <b>Note:</b>This class is NOT threadsafe to be used from multiple logging threads.\n * <p>\n * The error records are recorded to the memory mapped buffer in the following format.\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |R|                    Observation Count                        |\n *  |                                                               |\n *  +-+-------------------------------------------------------------+\n *  |R|                     Total Bytes Lost                        |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                 First Observation Timestamp                   |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                  Last Observation Timestamp                   |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                          Session ID                           |\n *  +---------------------------------------------------------------+\n *  |                           Stream ID                           |\n *  +---------------------------------------------------------------+\n *  |                 Channel encoded in US-ASCII                  ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n *  |                  Source encoded in US-ASCII                  ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic final class LossReport\n{\n    /**\n     * Alignment to be applied for each entry offset.\n     */\n    public static final int ENTRY_ALIGNMENT = CACHE_LINE_LENGTH;\n\n    /**\n     * Offset within an entry at which the observation count begins.\n     */\n    public static final int OBSERVATION_COUNT_OFFSET = 0;\n\n    /**\n     * Offset within an entry at which the total bytes field begins.\n     */\n    public static final int TOTAL_BYTES_LOST_OFFSET = OBSERVATION_COUNT_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * Offset within an entry at which the first observation field begins.\n     */\n    public static final int FIRST_OBSERVATION_OFFSET = TOTAL_BYTES_LOST_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * Offset within an entry at which the last observation field begins.\n     */\n    public static final int LAST_OBSERVATION_OFFSET = FIRST_OBSERVATION_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * Offset within an entry at which the session id field begins.\n     */\n    public static final int SESSION_ID_OFFSET = LAST_OBSERVATION_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * Offset within an entry at which the stream id field begins.\n     */\n    public static final int STREAM_ID_OFFSET = SESSION_ID_OFFSET + SIZE_OF_INT;\n\n    /**\n     * Offset within an entry at which the channel field begins.\n     */\n    public static final int CHANNEL_OFFSET = STREAM_ID_OFFSET + SIZE_OF_INT;\n\n    private int nextRecordOffset = 0;\n    private final AtomicBuffer buffer;\n\n    /**\n     * Create a loss report which wraps a buffer which is ideally memory mapped, so it can\n     * be read from another process.\n     *\n     * @param buffer to be wrapped.\n     */\n    public LossReport(final AtomicBuffer buffer)\n    {\n        buffer.verifyAlignment();\n        this.buffer = buffer;\n    }\n\n    /**\n     * Create a new entry for recording loss on a given stream.\n     * <p>\n     * If not space is remaining in the error report then null is returned.\n     *\n     * @param initialBytesLost on the stream.\n     * @param timestampMs      at which the first loss was observed.\n     * @param sessionId        for the stream.\n     * @param streamId         for the stream.\n     * @param channel          for the stream.\n     * @param source           of the stream.\n     * @return a new record or null if the error log has insufficient space.\n     */\n    public ReportEntry createEntry(\n        final long initialBytesLost,\n        final long timestampMs,\n        final int sessionId,\n        final int streamId,\n        final String channel,\n        final String source)\n    {\n        ReportEntry reportEntry = null;\n\n        final int requiredCapacity =\n            CHANNEL_OFFSET +\n            BitUtil.align(SIZE_OF_INT + channel.length(), SIZE_OF_INT) +\n            SIZE_OF_INT + source.length();\n\n        if (requiredCapacity <= (buffer.capacity() - nextRecordOffset))\n        {\n            final int offset = nextRecordOffset;\n\n            buffer.putLong(offset + TOTAL_BYTES_LOST_OFFSET, initialBytesLost);\n            buffer.putLong(offset + FIRST_OBSERVATION_OFFSET, timestampMs);\n            buffer.putLong(offset + LAST_OBSERVATION_OFFSET, timestampMs);\n            buffer.putInt(offset + SESSION_ID_OFFSET, sessionId);\n            buffer.putInt(offset + STREAM_ID_OFFSET, streamId);\n\n            final int encodedChannelLength = buffer.putStringAscii(offset + CHANNEL_OFFSET, channel);\n\n            buffer.putStringAscii(\n                offset + CHANNEL_OFFSET + BitUtil.align(encodedChannelLength, SIZE_OF_INT), source);\n\n            buffer.putLongRelease(offset + OBSERVATION_COUNT_OFFSET, 1);\n\n            reportEntry = new ReportEntry(buffer, offset);\n            nextRecordOffset += BitUtil.align(requiredCapacity, ENTRY_ALIGNMENT);\n        }\n\n        return reportEntry;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"LossReport{\" +\n            \"nextRecordOffset=\" + nextRecordOffset +\n            \", buffer=\" + buffer +\n            '}';\n    }\n\n    /**\n     * Report entry for a specific stream. Once an entry has been created it can then be used repeatably\n     * to capture the aggregate loss on a stream.\n     */\n    public static final class ReportEntry\n    {\n        private final AtomicBuffer buffer;\n        private final int offset;\n\n        ReportEntry(final AtomicBuffer buffer, final int offset)\n        {\n            this.buffer = buffer;\n            this.offset = offset;\n        }\n\n        /**\n         * Record a loss observation for a particular stream.\n         *\n         * @param bytesLost   in this observation.\n         * @param timestampMs when this observation occurred.\n         */\n        public void recordObservation(final long bytesLost, final long timestampMs)\n        {\n            buffer.putLongRelease(offset + LAST_OBSERVATION_OFFSET, timestampMs);\n            buffer.getAndAddLong(offset + TOTAL_BYTES_LOST_OFFSET, bytesLost);\n            buffer.getAndAddLong(offset + OBSERVATION_COUNT_OFFSET, 1);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/reports/LossReportReader.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.reports;\n\nimport org.agrona.BitUtil;\nimport org.agrona.concurrent.AtomicBuffer;\n\nimport java.io.PrintStream;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\nimport static io.aeron.driver.reports.LossReport.*;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\n\n/**\n * Reader that provides the function to read entries from a {@link LossReport}.\n */\npublic final class LossReportReader\n{\n    /**\n     * CSV style header for using with {@link #defaultEntryConsumer(PrintStream)}.\n     */\n    public static final String LOSS_REPORT_CSV_HEADER =\n        \"#OBSERVATION_COUNT,TOTAL_BYTES_LOST,FIRST_OBSERVATION,LAST_OBSERVATION,SESSION_ID,STREAM_ID,CHANNEL,SOURCE\";\n\n    /**\n     * Consumer function to be implemented by caller of the read method.\n     */\n    @FunctionalInterface\n    public interface EntryConsumer\n    {\n        /**\n         * Accept an entry from the loss report, so it can be consumed.\n         *\n         * @param observationCount          for the stream instance.\n         * @param totalBytesLost            for the stream instance.\n         * @param firstObservationTimestamp on the stream instance.\n         * @param lastObservationTimestamp  on the stream instance.\n         * @param sessionId                 identifying the stream.\n         * @param streamId                  identifying the stream.\n         * @param channel                   to which the stream belongs.\n         * @param source                    of the stream.\n         */\n        void accept(\n            long observationCount,\n            long totalBytesLost,\n            long firstObservationTimestamp,\n            long lastObservationTimestamp,\n            int sessionId,\n            int streamId,\n            String channel,\n            String source);\n    }\n\n    private LossReportReader()\n    {\n    }\n\n    /**\n     * Create a default {@link EntryConsumer} which outputs to a provided {@link PrintStream}.\n     *\n     * @param out to write entries to.\n     * @return a new {@link EntryConsumer} which outputs to a provided {@link PrintStream}.\n     */\n    public static EntryConsumer defaultEntryConsumer(final PrintStream out)\n    {\n        final SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSSZ\");\n\n        return\n            (observationCount,\n            totalBytesLost,\n            firstObservationTimestamp,\n            lastObservationTimestamp,\n            sessionId,\n            streamId,\n            channel,\n            source) ->\n            {\n                out.format(\n                    \"%d,%d,%s,%s,%d,%d,%s,%s%n\",\n                    observationCount,\n                    totalBytesLost,\n                    dateFormat.format(new Date(firstObservationTimestamp)),\n                    dateFormat.format(new Date(lastObservationTimestamp)),\n                    sessionId,\n                    streamId,\n                    channel,\n                    source);\n            };\n    }\n\n    /**\n     * Read a {@link LossReport} contained in the buffer. This can be done concurrently.\n     *\n     * @param buffer        containing the loss report.\n     * @param entryConsumer to be called to accept each entry in the report.\n     * @return the number of entries read.\n     */\n    public static int read(final AtomicBuffer buffer, final EntryConsumer entryConsumer)\n    {\n        final int capacity = buffer.capacity();\n\n        int recordsRead = 0;\n        int offset = 0;\n\n        while (offset < capacity)\n        {\n            final long observationCount = buffer.getLongVolatile(offset + OBSERVATION_COUNT_OFFSET);\n            if (observationCount <= 0)\n            {\n                break;\n            }\n\n            ++recordsRead;\n\n            final String channel = buffer.getStringAscii(offset + CHANNEL_OFFSET);\n            final String source = buffer.getStringAscii(\n                offset + CHANNEL_OFFSET + BitUtil.align(SIZE_OF_INT + channel.length(), SIZE_OF_INT));\n\n            entryConsumer.accept(\n                observationCount,\n                buffer.getLongVolatile(offset + TOTAL_BYTES_LOST_OFFSET),\n                buffer.getLong(offset + FIRST_OBSERVATION_OFFSET),\n                buffer.getLongVolatile(offset + LAST_OBSERVATION_OFFSET),\n                buffer.getInt(offset + SESSION_ID_OFFSET),\n                buffer.getInt(offset + STREAM_ID_OFFSET),\n                channel,\n                source);\n\n            final int recordLength =\n                CHANNEL_OFFSET +\n                BitUtil.align(SIZE_OF_INT + channel.length(), SIZE_OF_INT) +\n                SIZE_OF_INT + source.length();\n            offset += BitUtil.align(recordLength, ENTRY_ALIGNMENT);\n        }\n\n        return recordsRead;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/reports/LossReportUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.reports;\n\nimport java.io.File;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\n\nimport static org.agrona.IoUtil.mapExistingFile;\nimport static org.agrona.IoUtil.mapNewFile;\n\n/**\n * Utility functions for dealing with the Loss Report.\n */\npublic final class LossReportUtil\n{\n    /**\n     * Name of the error report file in the Aeron directory.\n     */\n    public static final String LOSS_REPORT_FILE_NAME = \"loss-report.dat\";\n\n    private LossReportUtil()\n    {\n    }\n\n    /**\n     * Create a new {@link File} object for the loss report.\n     *\n     * @param aeronDirectoryName in which the loss report should exist.\n     * @return the new {@link File} for the loss report.\n     */\n    public static File file(final String aeronDirectoryName)\n    {\n        return new File(aeronDirectoryName, LOSS_REPORT_FILE_NAME);\n    }\n\n    /**\n     * Map a new loss report in the Aeron directory for a given length.\n     *\n     * @param aeronDirectoryName in which to create the file.\n     * @param reportFileLength   for the file.\n     * @return the newly mapped buffer for the file.\n     */\n    public static MappedByteBuffer mapLossReport(final String aeronDirectoryName, final int reportFileLength)\n    {\n        return mapNewFile(file(aeronDirectoryName), reportFileLength, false);\n    }\n\n    /**\n     * Map a new loss report in the Aeron directory for a given length.\n     *\n     * @param aeronDirectoryName containing the file\n     * @return the read only mapped buffer for the file.\n     */\n    public static MappedByteBuffer mapLossReportReadOnly(final String aeronDirectoryName)\n    {\n        return mapExistingFile(file(aeronDirectoryName), FileChannel.MapMode.READ_ONLY, \"Loss Report\");\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/reports/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Reports of driver activity such the {@link io.aeron.driver.reports.LossReport} which tracks loss per stream.\n */\npackage io.aeron.driver.reports;"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/ClientHeartbeatTimestamp.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.status.HeartbeatTimestamp;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport static io.aeron.status.HeartbeatTimestamp.HEARTBEAT_TYPE_ID;\n\n/**\n * Counter for tracking the timestamp of a last heartbeat from an Aeron client.\n *\n * @see HeartbeatTimestamp\n */\npublic final class ClientHeartbeatTimestamp\n{\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"client-heartbeat\";\n\n    private ClientHeartbeatTimestamp()\n    {\n    }\n\n    /**\n     * Allocate a new counter by delegating to\n     * {@link HeartbeatTimestamp#allocate(MutableDirectBuffer, String, int, CountersManager, long)}.\n     *\n     * @param tempBuffer      for writing the metadata.\n     * @param countersManager for the counter.\n     * @param registrationId  for the client.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer, final CountersManager countersManager, final long registrationId)\n    {\n        return HeartbeatTimestamp.allocate(tempBuffer, NAME, HEARTBEAT_TYPE_ID, countersManager, registrationId);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/DutyCycleStallTracker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.driver.DutyCycleTracker;\nimport org.agrona.concurrent.status.AtomicCounter;\n\nimport static java.util.Objects.requireNonNull;\n\n/**\n * Duty cycle tracker that detects when a cycle exceeds a threshold and tracks max cycle time reporting both through\n * counters.\n */\npublic class DutyCycleStallTracker extends DutyCycleTracker\n{\n    private final AtomicCounter maxCycleTime;\n    private final AtomicCounter cycleTimeThresholdExceededCount;\n    private final long cycleTimeThresholdNs;\n\n    /**\n     * Create a tracker to track max cycle time and excesses of a threshold.\n     *\n     * @param maxCycleTime                    counter for tracking.\n     * @param cycleTimeThresholdExceededCount counter for tracking.\n     * @param cycleTimeThresholdNs            to use for tracking excesses.\n     */\n    public DutyCycleStallTracker(\n        final AtomicCounter maxCycleTime,\n        final AtomicCounter cycleTimeThresholdExceededCount,\n        final long cycleTimeThresholdNs)\n    {\n        this.maxCycleTime = requireNonNull(maxCycleTime);\n        this.cycleTimeThresholdExceededCount = requireNonNull(cycleTimeThresholdExceededCount);\n        this.cycleTimeThresholdNs = cycleTimeThresholdNs;\n    }\n\n    /**\n     * Get max cycle time counter.\n     *\n     * @return max cycle time counter.\n     */\n    public AtomicCounter maxCycleTime()\n    {\n        return maxCycleTime;\n    }\n\n    /**\n     * Get threshold exceeded counter.\n     *\n     * @return threshold exceeded counter.\n     */\n    public AtomicCounter cycleTimeThresholdExceededCount()\n    {\n        return cycleTimeThresholdExceededCount;\n    }\n\n    /**\n     * Get threshold value.\n     *\n     * @return threshold value.\n     */\n    public long cycleTimeThresholdNs()\n    {\n        return cycleTimeThresholdNs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void reportMeasurement(final long durationNs)\n    {\n        if (!maxCycleTime.isClosed())\n        {\n            maxCycleTime.proposeMaxRelease(durationNs);\n\n            if (durationNs > cycleTimeThresholdNs)\n            {\n                cycleTimeThresholdExceededCount.incrementRelease();\n            }\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"DutyCycleStallTracker{\" +\n            \"maxCycleTime=\" + maxCycleTime +\n            \", cycleTimeThresholdExceededCount=\" + cycleTimeThresholdExceededCount +\n            \", cycleTimeThresholdNs=\" + cycleTimeThresholdNs +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/FlowControlReceivers.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * The number of active receivers represented as a counter value.\n */\npublic final class FlowControlReceivers\n{\n    private FlowControlReceivers()\n    {\n    }\n\n    /**\n     * Type id of number of receivers counter.\n     */\n    public static final int FLOW_CONTROL_RECEIVERS_COUNTER_TYPE_ID =\n        AeronCounters.FLOW_CONTROL_RECEIVERS_COUNTER_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"fc-receivers\";\n\n    /**\n     * Allocate a new flow control receivers counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            FLOW_CONTROL_RECEIVERS_COUNTER_TYPE_ID,\n            countersManager,\n            NULL_VALUE,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new AtomicCounter(countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/MdcDestinations.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\n/**\n * The number of destinations represented as a counter value.\n */\npublic final class MdcDestinations\n{\n    /**\n     * Type id of number of destinations counter.\n     */\n    public static final int MDC_DESTINATIONS_COUNTER_TYPE_ID = AeronCounters.MDC_DESTINATIONS_COUNTER_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"mdc-num-dest\";\n\n    private MdcDestinations()\n    {\n    }\n\n    /**\n     * Allocate a new MDC destinations counter for an endpoint.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param registrationId  associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long registrationId,\n        final String channel)\n    {\n        return ChannelEndpointStatus.allocate(\n            tempBuffer, NAME, MDC_DESTINATIONS_COUNTER_TYPE_ID, countersManager, registrationId, channel);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/PerImageIndicator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * Allocates {@link AtomicCounter} indicating a per {@link io.aeron.driver.PublicationImage} indication of presence for\n * {@link io.aeron.driver.CongestionControl}.\n */\npublic final class PerImageIndicator\n{\n    /**\n     * Type id of a per Image indicator.\n     */\n    public static final int PER_IMAGE_TYPE_ID = AeronCounters.DRIVER_PER_IMAGE_TYPE_ID;\n\n    private PerImageIndicator()\n    {\n    }\n\n    /**\n     * Allocate a per {@link io.aeron.driver.PublicationImage} indicator.\n     *\n     * @param tempBuffer      to be used for labels and key.\n     * @param name            of the counter for the label.\n     * @param countersManager from which the underlying storage is allocated.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       for the stream of messages.\n     * @param streamId        for the stream of messages.\n     * @param channel         for the stream of messages.\n     * @return a new {@link AtomicCounter} for tracking the indicator.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final String name,\n        final CountersManager countersManager,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            name,\n            PER_IMAGE_TYPE_ID,\n            countersManager,\n            NULL_VALUE,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new AtomicCounter(countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/PublisherLimit.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * The limit as a position in bytes applied to publishers on a session-channel-stream tuple. Publishers will experience\n * back pressure when this position is passed as a means of flow control.\n */\npublic final class PublisherLimit\n{\n    /**\n     * Type id of a publisher limit counter.\n     */\n    public static final int PUBLISHER_LIMIT_TYPE_ID = AeronCounters.DRIVER_PUBLISHER_LIMIT_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"pub-lmt\";\n\n    private PublisherLimit()\n    {\n    }\n\n    /**\n     * Allocate a new publisher limit counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static UnsafeBufferPosition allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            PUBLISHER_LIMIT_TYPE_ID,\n            countersManager,\n            clientId,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new UnsafeBufferPosition((UnsafeBuffer)countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/PublisherPos.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * The position in bytes a publication has reached appending to the log.\n * <p>\n * <b>Note:</b> This is a not a real-time value like the other and is updated each conductor duty cycle\n * for monitoring purposes.\n */\npublic final class PublisherPos\n{\n    /**\n     * Type id of a publisher limit counter.\n     */\n    public static final int PUBLISHER_POS_TYPE_ID = AeronCounters.DRIVER_PUBLISHER_POS_TYPE_ID;\n\n    private static final String NAME_CONCURRENT = \"pub-pos (concurrent)\";\n    private static final String NAME_EXCLUSIVE = \"pub-pos (exclusive)\";\n\n    private PublisherPos()\n    {\n    }\n\n    /**\n     * Allocate a new publication position counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @param isExclusive     {code true} if exclusive publication.\n     * @return the allocated counter.\n     */\n    public static UnsafeBufferPosition allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel,\n        final boolean isExclusive)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            isExclusive ? NAME_EXCLUSIVE : NAME_CONCURRENT,\n            PUBLISHER_POS_TYPE_ID,\n            countersManager,\n            clientId,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new UnsafeBufferPosition((UnsafeBuffer)countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/ReceiveChannelStatus.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\n/**\n * The status of a receiving channel endpoint represented as a counter value.\n */\npublic final class ReceiveChannelStatus\n{\n    /**\n     * Type id of a receive channel status indicator.\n     */\n    public static final int RECEIVE_CHANNEL_STATUS_TYPE_ID = AeronCounters.DRIVER_RECEIVE_CHANNEL_STATUS_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"rcv-channel\";\n\n    private ReceiveChannelStatus()\n    {\n    }\n\n    /**\n     * Allocate a new channel status counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param registrationId  associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long registrationId,\n        final String channel)\n    {\n        return ChannelEndpointStatus.allocate(\n            tempBuffer, NAME, RECEIVE_CHANNEL_STATUS_TYPE_ID, countersManager, registrationId, channel);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/ReceiveLocalSocketAddress.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.status.LocalSocketAddressStatus;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\n/**\n * The receiving end of a local socket address, i.e. the destination or endpoint for a subscription.\n */\npublic final class ReceiveLocalSocketAddress\n{\n    /**\n     * The human-readable name for the beginning of a label.\n     */\n    public static final String NAME = \"rcv-local-sockaddr\";\n\n    private ReceiveLocalSocketAddress()\n    {\n    }\n\n    /**\n     * Allocate a counter to represent a local socket address associated with a receiving channel.\n     *\n     * @param tempBuffer      for building up the key and label.\n     * @param countersManager which will allocate the counter.\n     * @param registrationId  of the action the counter is associated with.\n     * @param channelStatusId with which the new counter is associated.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long registrationId,\n        final int channelStatusId)\n    {\n        return LocalSocketAddressStatus.allocate(\n            tempBuffer,\n            countersManager,\n            registrationId,\n            channelStatusId,\n            NAME,\n            LocalSocketAddressStatus.LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/ReceiverHwm.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * The highest position the Receiver has observed on a session-channel-stream tuple while rebuilding the stream.\n * It is possible the stream is not complete to this point if the stream has experienced loss.\n */\npublic final class ReceiverHwm\n{\n    /**\n     * Type id of a receiver high-water-mark position counter.\n     */\n    public static final int RECEIVER_HWM_TYPE_ID = AeronCounters.DRIVER_RECEIVER_HWM_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"rcv-hwm\";\n\n    private ReceiverHwm()\n    {\n    }\n\n    /**\n     * Allocate a new receiver high-water-mark position counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static UnsafeBufferPosition allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            RECEIVER_HWM_TYPE_ID,\n            countersManager,\n            clientId,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new UnsafeBufferPosition((UnsafeBuffer)countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/ReceiverNaksSent.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * Count the number of NAK messages sent by the Receiver. This is a per-stream event count for\n * that which is aggregated in {@link SystemCounterDescriptor#NAK_MESSAGES_SENT}.\n */\npublic final class ReceiverNaksSent\n{\n    /**\n     * Type id of a counter.\n     */\n    public static final int TYPE_ID = AeronCounters.DRIVER_RECEIVER_NAKS_SENT_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"rcv-naks-sent\";\n\n    private ReceiverNaksSent()\n    {\n    }\n\n    /**\n     * Allocate a new sender back-pressure counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            TYPE_ID,\n            countersManager,\n            clientId, registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new AtomicCounter(countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/ReceiverPos.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * The highest position the Receiver has rebuilt up to on a session-channel-stream tuple while rebuilding the stream.\n * The stream is complete up to this point.\n */\npublic final class ReceiverPos\n{\n    /**\n     * Type id of a receiver position counter.\n     */\n    public static final int RECEIVER_POS_TYPE_ID = AeronCounters.DRIVER_RECEIVER_POS_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"rcv-pos\";\n\n    private ReceiverPos()\n    {\n    }\n\n    /**\n     * Allocate a new receiver position counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static UnsafeBufferPosition allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            RECEIVER_POS_TYPE_ID,\n            countersManager,\n            clientId,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new UnsafeBufferPosition((UnsafeBuffer)countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/SendChannelStatus.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\n/**\n * The status of a sending channel endpoint represented as a counter value.\n */\npublic final class SendChannelStatus\n{\n    /**\n     * Type id of a sending channel status indicator.\n     */\n    public static final int SEND_CHANNEL_STATUS_TYPE_ID = AeronCounters.DRIVER_SEND_CHANNEL_STATUS_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"snd-channel\";\n\n    private SendChannelStatus()\n    {\n    }\n\n    /**\n     * Allocate a new channel status counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param registrationId  associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long registrationId,\n        final String channel)\n    {\n        return ChannelEndpointStatus.allocate(\n            tempBuffer, NAME, SEND_CHANNEL_STATUS_TYPE_ID, countersManager, registrationId, channel);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/SendLocalSocketAddress.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.status.LocalSocketAddressStatus;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\n/**\n * The sending end of a local socket address, i.e. control endpoint of a publication.\n */\npublic final class SendLocalSocketAddress\n{\n    /**\n     * The human-readable name for the beginning of a label.\n     */\n    public static final String NAME = \"snd-local-sockaddr\";\n\n    private SendLocalSocketAddress()\n    {\n    }\n\n    /**\n     * Allocate a counter to represent a local socket address associated with a sending channel endpoint.\n     *\n     * @param tempBuffer      for building up the key and label.\n     * @param countersManager which will allocate the counter.\n     * @param registrationId  of the action the counter is associated with.\n     * @param channelStatusId with which the new counter is associated.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long registrationId,\n        final int channelStatusId)\n    {\n        return LocalSocketAddressStatus.allocate(\n            tempBuffer,\n            countersManager,\n            registrationId,\n            channelStatusId,\n            NAME,\n            LocalSocketAddressStatus.LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/SenderBpe.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * Count of back-pressure events (BPE)s a sender has experienced on a stream. This is a per-stream event count for\n * that which is aggregated in {@link SystemCounterDescriptor#SENDER_FLOW_CONTROL_LIMITS}.\n */\npublic final class SenderBpe\n{\n    /**\n     * Type id of a sender back-pressure event counter.\n     */\n    public static final int SENDER_BPE_TYPE_ID = AeronCounters.DRIVER_SENDER_BPE_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"snd-bpe\";\n\n    private SenderBpe()\n    {\n    }\n\n    /**\n     * Allocate a new sender back-pressure counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            SENDER_BPE_TYPE_ID,\n            countersManager,\n            clientId,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new AtomicCounter(countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/SenderLimit.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * The position the Sender can immediately send up-to on a session-channel-stream tuple.\n */\npublic final class SenderLimit\n{\n    /**\n     * Type id of a sender position counter.\n     */\n    public static final int SENDER_LIMIT_TYPE_ID = AeronCounters.DRIVER_SENDER_LIMIT_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"snd-lmt\";\n\n    private SenderLimit()\n    {\n    }\n\n    /**\n     * Allocate a new sender limit counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static UnsafeBufferPosition allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            SENDER_LIMIT_TYPE_ID,\n            countersManager,\n            clientId,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new UnsafeBufferPosition((UnsafeBuffer)countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/SenderNaksReceived.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * Count the number of NAK messages received by the Sender. This is a per-stream event count for\n * that which is aggregated in {@link SystemCounterDescriptor#NAK_MESSAGES_RECEIVED}.\n */\npublic final class SenderNaksReceived\n{\n    /**\n     * Type id of a counter.\n     */\n    public static final int TYPE_ID = AeronCounters.DRIVER_SENDER_NAKS_RECEIVED_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"snd-naks-received\";\n\n    private SenderNaksReceived()\n    {\n    }\n\n    /**\n     * Allocate a new sender back-pressure counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static AtomicCounter allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            TYPE_ID,\n            countersManager,\n            clientId,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new AtomicCounter(countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/SenderPos.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\n\nimport static io.aeron.Aeron.NULL_VALUE;\n\n/**\n * The position the Sender has reached for sending data to the media on a session-channel-stream tuple.\n */\npublic final class SenderPos\n{\n    /**\n     * Type id of a sender position counter.\n     */\n    public static final int SENDER_POSITION_TYPE_ID = AeronCounters.DRIVER_SENDER_POSITION_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"snd-pos\";\n\n    private SenderPos()\n    {\n    }\n\n    /**\n     * Allocate a new sender position counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @return the allocated counter.\n     */\n    public static UnsafeBufferPosition allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            SENDER_POSITION_TYPE_ID,\n            countersManager,\n            clientId,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            NULL_VALUE);\n\n        return new UnsafeBufferPosition((UnsafeBuffer)countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/StatusUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.Aeron;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.agrona.concurrent.status.StatusIndicator;\nimport org.agrona.concurrent.status.StatusIndicatorReader;\nimport org.agrona.concurrent.status.UnsafeBufferStatusIndicator;\n\n/**\n * Functions for working with status counters.\n */\npublic final class StatusUtil\n{\n    private StatusUtil()\n    {\n    }\n\n    /**\n     * Return the controllable idle strategy {@link StatusIndicator}.\n     *\n     * @param countersReader that holds the status indicator.\n     * @return status indicator to use or null if not found.\n     */\n    public static StatusIndicator controllableIdleStrategy(final CountersReader countersReader)\n    {\n        StatusIndicator statusIndicator = null;\n        final MutableInteger id = new MutableInteger(-1);\n\n        countersReader.forEach(\n            (counterId, label) ->\n            {\n                if (counterId == SystemCounterDescriptor.CONTROLLABLE_IDLE_STRATEGY.id() &&\n                    label.equals(SystemCounterDescriptor.CONTROLLABLE_IDLE_STRATEGY.label()))\n                {\n                    id.value = counterId;\n                }\n            });\n\n        if (Aeron.NULL_VALUE != id.value)\n        {\n            statusIndicator = new UnsafeBufferStatusIndicator(countersReader.valuesBuffer(), id.value);\n        }\n\n        return statusIndicator;\n    }\n\n    /**\n     * Return the read-only status indicator for the given send channel URI.\n     *\n     * @param countersReader that holds the status indicator.\n     * @param channel        for the send channel.\n     * @return read-only status indicator that can be used to query the status of the send channel or null.\n     * @see ChannelEndpointStatus for status values and indications.\n     */\n    public static StatusIndicatorReader sendChannelStatus(final CountersReader countersReader, final String channel)\n    {\n        StatusIndicatorReader statusReader = null;\n        final MutableInteger id = new MutableInteger(-1);\n\n        countersReader.forEach(\n            (counterId, typeId, keyBuffer, label) ->\n            {\n                if (typeId == SendChannelStatus.SEND_CHANNEL_STATUS_TYPE_ID)\n                {\n                    if (channel.startsWith(keyBuffer.getStringAscii(ChannelEndpointStatus.CHANNEL_OFFSET)))\n                    {\n                        id.value = counterId;\n                    }\n                }\n            });\n\n        if (Aeron.NULL_VALUE != id.value)\n        {\n            statusReader = new UnsafeBufferStatusIndicator(countersReader.valuesBuffer(), id.value);\n        }\n\n        return statusReader;\n    }\n\n    /**\n     * Return the read-only status indicator for the given receive channel URI.\n     *\n     * @param countersReader that holds the status indicator.\n     * @param channel        for the receiving endpoint\n     * @return read-only status indicator that can be used to query the status of the receive channel endpoint or null.\n     * @see ChannelEndpointStatus for status values and indications.\n     */\n    public static StatusIndicatorReader receiveChannelStatus(final CountersReader countersReader, final String channel)\n    {\n        StatusIndicatorReader statusReader = null;\n        final MutableInteger id = new MutableInteger(-1);\n\n        countersReader.forEach(\n            (counterId, typeId, keyBuffer, label) ->\n            {\n                if (typeId == ReceiveChannelStatus.RECEIVE_CHANNEL_STATUS_TYPE_ID)\n                {\n                    if (channel.startsWith(keyBuffer.getStringAscii(ChannelEndpointStatus.CHANNEL_OFFSET)))\n                    {\n                        id.value = counterId;\n                    }\n                }\n            });\n\n        if (Aeron.NULL_VALUE != id.value)\n        {\n            statusReader = new UnsafeBufferStatusIndicator(countersReader.valuesBuffer(), id.value);\n        }\n\n        return statusReader;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/StreamCounter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport org.agrona.BitUtil;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.concurrent.status.CountersReader.MAX_LABEL_LENGTH;\n\n/**\n * Allocates counters on a stream of messages.\n * <p>\n * Positions tracked in bytes include:\n * <ul>\n * <li>{@link PublisherPos}: Highest position on a {@link io.aeron.Publication} reached for offers and claims as an\n *     approximation which is sampled once per second.</li>\n * <li>{@link PublisherLimit}: Limit for flow controlling a {@link io.aeron.Publication} offers and claims.</li>\n * <li>{@link SenderPos}: Highest position on a {@link io.aeron.Publication} stream sent to the media.</li>\n * <li>{@link SenderLimit}: Limit for flow controlling a {@link io.aeron.driver.Sender} of a stream.</li>\n * <li>{@link ReceiverHwm}: Highest position observed by the {@link io.aeron.driver.Receiver} when rebuilding an\n *     {@link io.aeron.Image} of a stream.</li>\n * <li>{@link ReceiverPos}: Highest contiguous position rebuilt by the {@link io.aeron.driver.Receiver} on an\n *     {@link io.aeron.Image} of a stream.</li>\n * <li>{@link SubscriberPos}: Consumption position on an {@link io.aeron.Image} of a stream by an individual\n *     Subscriber.</li>\n * </ul>\n */\npublic final class StreamCounter\n{\n    /**\n     * Offset in the key metadata for the registration id of the counter.\n     */\n    public static final int REGISTRATION_ID_OFFSET = 0;\n\n    /**\n     * Offset in the key metadata for the session id of the counter.\n     */\n    public static final int SESSION_ID_OFFSET = REGISTRATION_ID_OFFSET + SIZE_OF_LONG;\n\n    /**\n     * Offset in the key metadata for the stream id of the counter.\n     */\n    public static final int STREAM_ID_OFFSET = SESSION_ID_OFFSET + SIZE_OF_INT;\n\n    /**\n     * Offset in the key metadata for the channel of the counter.\n     */\n    public static final int CHANNEL_OFFSET = STREAM_ID_OFFSET + SIZE_OF_INT;\n\n    /**\n     * The maximum length in bytes of the encoded channel identity.\n     */\n    public static final int MAX_CHANNEL_LENGTH = CountersReader.MAX_KEY_LENGTH - (CHANNEL_OFFSET + SIZE_OF_INT);\n\n    private StreamCounter()\n    {\n    }\n\n    /**\n     * Allocate a counter id for tracking a position on a stream of messages.\n     *\n     * @param tempBuffer      to be used for labels and key.\n     * @param name            of the counter for the label.\n     * @param typeId          of the counter for classification.\n     * @param countersManager from which the underlying storage is allocated.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  to be associated with the counter.\n     * @param sessionId       for the stream of messages.\n     * @param streamId        for the stream of messages.\n     * @param channel         for the stream of messages.\n     * @param joinPosition    relevant for `sub-pos` only.\n     * @return the id of the allocated counter.\n     */\n    public static int allocateCounterId(\n        final MutableDirectBuffer tempBuffer,\n        final String name,\n        final int typeId,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel,\n        final long joinPosition)\n    {\n        tempBuffer.putLong(REGISTRATION_ID_OFFSET, registrationId);\n        tempBuffer.putInt(SESSION_ID_OFFSET, sessionId);\n        tempBuffer.putInt(STREAM_ID_OFFSET, streamId);\n\n        final int channelLength = tempBuffer.putStringWithoutLengthAscii(\n            CHANNEL_OFFSET + SIZE_OF_INT, channel, 0, MAX_CHANNEL_LENGTH);\n        tempBuffer.putInt(CHANNEL_OFFSET, channelLength);\n        final int keyLength = CHANNEL_OFFSET + SIZE_OF_INT + channelLength;\n\n        final int labelOffset = BitUtil.align(keyLength, SIZE_OF_INT);\n        int labelLength = 0;\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, name);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, \": \");\n        labelLength += tempBuffer.putLongAscii(labelOffset + labelLength, registrationId);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, \" \");\n        labelLength += tempBuffer.putIntAscii(labelOffset + labelLength, sessionId);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, \" \");\n        labelLength += tempBuffer.putIntAscii(labelOffset + labelLength, streamId);\n        labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, \" \");\n        labelLength += tempBuffer.putStringWithoutLengthAscii(\n            labelOffset + labelLength, channel, 0, MAX_LABEL_LENGTH - labelLength);\n\n        if (NULL_VALUE != joinPosition && labelLength < (MAX_LABEL_LENGTH - 20))\n        {\n            labelLength += tempBuffer.putStringWithoutLengthAscii(labelOffset + labelLength, \" @\");\n            labelLength += tempBuffer.putLongAscii(labelOffset + labelLength, joinPosition);\n        }\n\n        final int counterId = countersManager.allocate(\n            typeId, tempBuffer, 0, keyLength, tempBuffer, labelOffset, labelLength);\n\n        countersManager.setCounterRegistrationId(counterId, registrationId);\n        countersManager.setCounterOwnerId(counterId, clientId);\n\n        return counterId;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/SubscriberPos.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\n\n/**\n * The position an individual Subscriber has reached on a session-channel-stream tuple. It is possible to have multiple\n * Subscribers on the same machine tracked by a {@link io.aeron.driver.MediaDriver}.\n */\npublic final class SubscriberPos\n{\n    /**\n     * Type id of a subscriber position counter.\n     */\n    public static final int SUBSCRIBER_POSITION_TYPE_ID = AeronCounters.DRIVER_SUBSCRIBER_POSITION_TYPE_ID;\n\n    /**\n     * Human-readable name for the counter.\n     */\n    public static final String NAME = \"sub-pos\";\n\n    private SubscriberPos()\n    {\n    }\n\n    /**\n     * Allocate a new subscriber position counter for a stream.\n     *\n     * @param tempBuffer      to build the label.\n     * @param countersManager to allocate the counter from.\n     * @param clientId        to set as counter owner.\n     * @param registrationId  associated with the counter.\n     * @param sessionId       associated with the counter.\n     * @param streamId        associated with the counter.\n     * @param channel         associated with the counter.\n     * @param joinPosition    for the stream.\n     * @return the allocated counter.\n     */\n    public static UnsafeBufferPosition allocate(\n        final MutableDirectBuffer tempBuffer,\n        final CountersManager countersManager,\n        final long clientId,\n        final long registrationId,\n        final int sessionId,\n        final int streamId,\n        final String channel,\n        final long joinPosition)\n    {\n        final int counterId = StreamCounter.allocateCounterId(\n            tempBuffer,\n            NAME,\n            SUBSCRIBER_POSITION_TYPE_ID,\n            countersManager,\n            clientId,\n            registrationId,\n            sessionId,\n            streamId,\n            channel,\n            joinPosition);\n\n        return new UnsafeBufferPosition((UnsafeBuffer)countersManager.valuesBuffer(), counterId, countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/SystemCounterDescriptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport io.aeron.AeronCounters;\nimport io.aeron.driver.MediaDriverVersion;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_AERON_VERSION;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_BYTES_CURRENTLY_MAPPED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_BYTES_RECEIVED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_BYTES_SENT;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_CLIENT_TIMEOUTS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_CONDUCTOR_MAX_CYCLE_TIME;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_CONDUCTOR_PROXY_FAILS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_CONTROLLABLE_IDLE_STRATEGY;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_ERRORS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_ERROR_FRAMES_RECEIVED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_ERROR_FRAMES_SENT;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_FLOW_CONTROL_OVER_RUNS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_FLOW_CONTROL_UNDER_RUNS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_FREE_FAILS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_HEARTBEATS_RECEIVED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_HEARTBEATS_SENT;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_IMAGES_REJECTED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_INVALID_PACKETS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_LOSS_GAP_FILLS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_NAK_MESSAGES_RECEIVED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_NAK_MESSAGES_SENT;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_NAME_RESOLVER_MAX_TIME;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_POSSIBLE_TTL_ASYMMETRY;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_PUBLICATIONS_REVOKED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_PUBLICATION_IMAGES_REVOKED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_RECEIVER_MAX_CYCLE_TIME;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_RECEIVER_PROXY_FAILS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_RESOLUTION_CHANGES;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_RETRANSMITS_SENT;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_RETRANSMITTED_BYTES;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_RETRANSMIT_OVERFLOW;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_SENDER_FLOW_CONTROL_LIMITS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_SENDER_MAX_CYCLE_TIME;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_SENDER_PROXY_FAILS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_SHORT_SENDS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_STATUS_MESSAGES_RECEIVED;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_STATUS_MESSAGES_SENT;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_UNBLOCKED_COMMANDS;\nimport static io.aeron.AeronCounters.SYSTEM_COUNTER_ID_UNBLOCKED_PUBLICATIONS;\n\n/**\n * System-wide counters for monitoring. These are separate from counters used for position tracking on streams.\n */\npublic enum SystemCounterDescriptor\n{\n    /**\n     * Running total of bytes sent for data over UDP, excluding IP headers.\n     */\n    BYTES_SENT(SYSTEM_COUNTER_ID_BYTES_SENT, \"Bytes sent\"),\n\n    /**\n     * Running total of bytes received for data over UDP, excluding IP headers.\n     */\n    BYTES_RECEIVED(SYSTEM_COUNTER_ID_BYTES_RECEIVED, \"Bytes received\"),\n\n    /**\n     * Failed offers to the receiver proxy suggesting back-pressure.\n     */\n    RECEIVER_PROXY_FAILS(SYSTEM_COUNTER_ID_RECEIVER_PROXY_FAILS, \"Failed offers to ReceiverProxy\"),\n\n    /**\n     * Failed offers to the sender proxy suggesting back-pressure.\n     */\n    SENDER_PROXY_FAILS(SYSTEM_COUNTER_ID_SENDER_PROXY_FAILS, \"Failed offers to SenderProxy\"),\n\n    /**\n     * Failed offers to the driver conductor proxy suggesting back-pressure.\n     */\n    CONDUCTOR_PROXY_FAILS(SYSTEM_COUNTER_ID_CONDUCTOR_PROXY_FAILS, \"Failed offers to DriverConductorProxy\"),\n\n    /**\n     * Count of NAKs sent back to senders requesting re-transmits.\n     */\n    NAK_MESSAGES_SENT(SYSTEM_COUNTER_ID_NAK_MESSAGES_SENT, \"NAKs sent\"),\n\n    /**\n     * Count of NAKs received from receivers requesting re-transmits.\n     */\n    NAK_MESSAGES_RECEIVED(SYSTEM_COUNTER_ID_NAK_MESSAGES_RECEIVED, \"NAKs received\"),\n\n    /**\n     * Count of status messages sent back to senders for flow control.\n     */\n    STATUS_MESSAGES_SENT(SYSTEM_COUNTER_ID_STATUS_MESSAGES_SENT, \"Status Messages sent\"),\n\n    /**\n     * Count of status messages received from receivers for flow control.\n     */\n    STATUS_MESSAGES_RECEIVED(SYSTEM_COUNTER_ID_STATUS_MESSAGES_RECEIVED, \"Status Messages received\"),\n\n    /**\n     * Count of heartbeat data frames sent to indicate liveness in the absence of data to send.\n     */\n    HEARTBEATS_SENT(SYSTEM_COUNTER_ID_HEARTBEATS_SENT, \"Heartbeats sent\"),\n\n    /**\n     * Count of heartbeat data frames received to indicate liveness in the absence of data to send.\n     */\n    HEARTBEATS_RECEIVED(SYSTEM_COUNTER_ID_HEARTBEATS_RECEIVED, \"Heartbeats received\"),\n\n    /**\n     * Count of data packets re-transmitted as a result of NAKs.\n     */\n    RETRANSMITS_SENT(SYSTEM_COUNTER_ID_RETRANSMITS_SENT, \"Retransmits sent\"),\n\n    /**\n     * Count of packets received which under-run the current flow control window for images.\n     */\n    FLOW_CONTROL_UNDER_RUNS(SYSTEM_COUNTER_ID_FLOW_CONTROL_UNDER_RUNS, \"Flow control under runs\"),\n\n    /**\n     * Count of packets received which over-run the current flow control window for images.\n     */\n    FLOW_CONTROL_OVER_RUNS(SYSTEM_COUNTER_ID_FLOW_CONTROL_OVER_RUNS, \"Flow control over runs\"),\n\n    /**\n     * Count of invalid packets received.\n     */\n    INVALID_PACKETS(SYSTEM_COUNTER_ID_INVALID_PACKETS, \"Invalid packets\"),\n\n    /**\n     * Count of errors observed by the driver and an indication to read the distinct error log.\n     */\n    ERRORS(SYSTEM_COUNTER_ID_ERRORS,\n        \"Errors: \" + AeronCounters.formatVersionInfo(MediaDriverVersion.VERSION, MediaDriverVersion.GIT_SHA)),\n\n    /**\n     * Count of socket send operation which resulted in less than the packet length being sent.\n     */\n    SHORT_SENDS(SYSTEM_COUNTER_ID_SHORT_SENDS, \"Short sends\"),\n\n    /**\n     * Count of attempts to free log buffers no longer required by the driver which as still held by clients.\n     */\n    FREE_FAILS(SYSTEM_COUNTER_ID_FREE_FAILS, \"Failed attempts to free log buffers\"),\n\n    /**\n     * Count of the times a sender has entered the state of being back-pressured when it could have sent faster.\n     */\n    SENDER_FLOW_CONTROL_LIMITS(SYSTEM_COUNTER_ID_SENDER_FLOW_CONTROL_LIMITS,\n        \"Sender flow control limits, i.e. back-pressure events\"),\n\n    /**\n     * Count of the times a publication has been unblocked after a client failed to complete an offer within a timeout.\n     */\n    UNBLOCKED_PUBLICATIONS(SYSTEM_COUNTER_ID_UNBLOCKED_PUBLICATIONS, \"Unblocked Publications\"),\n\n    /**\n     * Count of the times a command has been unblocked after a client failed to complete an offer within a timeout.\n     */\n    UNBLOCKED_COMMANDS(SYSTEM_COUNTER_ID_UNBLOCKED_COMMANDS, \"Unblocked Control Commands\"),\n\n    /**\n     * Count of the times the channel endpoint detected a possible TTL asymmetry between its config and new connection.\n     */\n    POSSIBLE_TTL_ASYMMETRY(SYSTEM_COUNTER_ID_POSSIBLE_TTL_ASYMMETRY, \"Possible TTL Asymmetry\"),\n\n    /**\n     * Current status of the {@link org.agrona.concurrent.ControllableIdleStrategy} if configured.\n     */\n    CONTROLLABLE_IDLE_STRATEGY(SYSTEM_COUNTER_ID_CONTROLLABLE_IDLE_STRATEGY, \"ControllableIdleStrategy status\"),\n\n    /**\n     * Count of the times a loss gap has been filled when NAKs have been disabled.\n     */\n    LOSS_GAP_FILLS(SYSTEM_COUNTER_ID_LOSS_GAP_FILLS, \"Loss gap fills\"),\n\n    /**\n     * Count of the Aeron clients that have timed out without a graceful close.\n     */\n    CLIENT_TIMEOUTS(SYSTEM_COUNTER_ID_CLIENT_TIMEOUTS, \"Client liveness timeouts\"),\n\n    /**\n     * Count of the times a connection endpoint has been re-resolved resulting in a change.\n     */\n    RESOLUTION_CHANGES(SYSTEM_COUNTER_ID_RESOLUTION_CHANGES, \"Resolution changes\"),\n\n    /**\n     * The maximum time spent by the conductor between work cycles.\n     */\n    CONDUCTOR_MAX_CYCLE_TIME(SYSTEM_COUNTER_ID_CONDUCTOR_MAX_CYCLE_TIME,\n        \"Conductor max cycle time doing its work in ns\"),\n\n    /**\n     * Count of the number of times the cycle time threshold has been exceeded by the conductor in its work cycle.\n     */\n    CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED(SYSTEM_COUNTER_ID_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED,\n        \"Conductor work cycle exceeded threshold count\"),\n\n    /**\n     * The maximum time spent by the sender between work cycles.\n     */\n    SENDER_MAX_CYCLE_TIME(SYSTEM_COUNTER_ID_SENDER_MAX_CYCLE_TIME,\n        \"Sender max cycle time doing its work in ns\"),\n\n    /**\n     * Count of the number of times the cycle time threshold has been exceeded by the sender in its work cycle.\n     */\n    SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED(SYSTEM_COUNTER_ID_SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED,\n        \"Sender work cycle exceeded threshold count\"),\n\n    /**\n     * The maximum time spent by the receiver between work cycles.\n     */\n    RECEIVER_MAX_CYCLE_TIME(SYSTEM_COUNTER_ID_RECEIVER_MAX_CYCLE_TIME, \"Receiver max cycle time doing its work in ns\"),\n\n    /**\n     * Count of the number of times the cycle time threshold has been exceeded by the receiver in its work cycle.\n     */\n    RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED(SYSTEM_COUNTER_ID_RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED,\n        \"Receiver work cycle exceeded threshold count\"),\n\n    /**\n     * The maximum time spent by the NameResolver in one of its operations.\n     *\n     * @since 1.41.0\n     */\n    NAME_RESOLVER_MAX_TIME(SYSTEM_COUNTER_ID_NAME_RESOLVER_MAX_TIME, \"NameResolver max time in ns\"),\n\n    /**\n     * Count of the number of times the time threshold has been exceeded by the NameResolver.\n     *\n     * @since 1.41.0\n     */\n    NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED(SYSTEM_COUNTER_ID_NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED,\n        \"NameResolver exceeded threshold count\"),\n\n    /**\n     * The version of the media driver.\n     *\n     * @since 1.43.0\n     */\n    AERON_VERSION(SYSTEM_COUNTER_ID_AERON_VERSION, \"Aeron software: \" +\n        AeronCounters.formatVersionInfo(MediaDriverVersion.VERSION, MediaDriverVersion.GIT_SHA)),\n\n    /**\n     * The total number of bytes currently mapped in log buffers, CnC file, and loss report.\n     *\n     * @since 1.44.0\n     */\n    BYTES_CURRENTLY_MAPPED(SYSTEM_COUNTER_ID_BYTES_CURRENTLY_MAPPED, \"Bytes currently mapped\"),\n\n    /**\n     * A minimum bound on the number of bytes re-transmitted as a result of NAKs.\n     * <p>\n     * MDC retransmits are only counted once; therefore, this is a minimum bound rather than the actual number\n     * of retransmitted bytes. We may change this in the future.\n     * <p>\n     * Note that retransmitted bytes are not included in the {@link SystemCounterDescriptor#BYTES_SENT}\n     * counter value. We may change this in the future.\n     *\n     * @since 1.45.0\n     */\n    RETRANSMITTED_BYTES(SYSTEM_COUNTER_ID_RETRANSMITTED_BYTES, \"Retransmitted bytes\"),\n\n    /**\n     * A count of the number of times that the retransmit pool has been overflowed.\n     *\n     * @since 1.45.0\n     */\n    RETRANSMIT_OVERFLOW(SYSTEM_COUNTER_ID_RETRANSMIT_OVERFLOW, \"Retransmit Pool Overflow count\"),\n\n    /**\n     * A count of the number of error frames received by this driver.\n     *\n     * @since 1.47.0\n     */\n    ERROR_FRAMES_RECEIVED(SYSTEM_COUNTER_ID_ERROR_FRAMES_RECEIVED, \"Error Frames received\"),\n\n    /**\n     * A count of the number of error frames sent by this driver.\n     *\n     * @since 1.47.0\n     */\n    ERROR_FRAMES_SENT(SYSTEM_COUNTER_ID_ERROR_FRAMES_SENT, \"Error Frames sent\"),\n\n    /**\n     * A count of the number of publications that have been revoked.\n     *\n     * @since 1.48.0\n     */\n    PUBLICATIONS_REVOKED(SYSTEM_COUNTER_ID_PUBLICATIONS_REVOKED, \"Publications Revoked\"),\n\n    /**\n     * A count of the number of publication images that have been revoked.\n     *\n     * @since 1.48.0\n     */\n    PUBLICATION_IMAGES_REVOKED(SYSTEM_COUNTER_ID_PUBLICATION_IMAGES_REVOKED, \"Publication Images Revoked\"),\n\n    /**\n     * A count of the number of images that have been rejected.\n     *\n     * @since 1.48.0\n     */\n    IMAGES_REJECTED(SYSTEM_COUNTER_ID_IMAGES_REJECTED, \"Images rejected\"),\n\n    /**\n     * The semantic version of the control protocol between clients and media driver.\n     *\n     * @since 1.49.0\n     */\n    CONTROL_PROTOCOL_VERSION(SYSTEM_COUNTER_ID_CONTROL_PROTOCOL_VERSION, \"Control protocol version\");\n\n    /**\n     * All system counters have the same type id, i.e. system counters are the same type. Other types can exist.\n     */\n    public static final int SYSTEM_COUNTER_TYPE_ID = AeronCounters.DRIVER_SYSTEM_COUNTER_TYPE_ID;\n\n    private static final Int2ObjectHashMap<SystemCounterDescriptor> DESCRIPTOR_BY_ID_MAP = new Int2ObjectHashMap<>();\n\n    static\n    {\n        for (final SystemCounterDescriptor descriptor : SystemCounterDescriptor.values())\n        {\n            final SystemCounterDescriptor other = DESCRIPTOR_BY_ID_MAP.put(descriptor.id, descriptor);\n            if (null != other)\n            {\n                throw new IllegalStateException(descriptor + \" uses the same id as \" + other);\n            }\n        }\n    }\n\n    /**\n     * Get the {@link SystemCounterDescriptor} for a given id.\n     *\n     * @param id for the descriptor.\n     * @return the descriptor if found otherwise null.\n     */\n    public static SystemCounterDescriptor get(final int id)\n    {\n        return DESCRIPTOR_BY_ID_MAP.get(id);\n    }\n\n    private final int id;\n    private final String label;\n\n    SystemCounterDescriptor(final int id, final String label)\n    {\n        this.id = id;\n        this.label = label;\n    }\n\n    /**\n     * The unique identity for the system counter.\n     *\n     * @return the unique identity for the system counter.\n     */\n    public int id()\n    {\n        return id;\n    }\n\n    /**\n     * The human-readable label to identify a system counter.\n     *\n     * @return the human-readable label to identify a system counter.\n     */\n    public String label()\n    {\n        return label;\n    }\n\n    /**\n     * Create a new counter for the enumerated descriptor.\n     *\n     * @param countersManager for managing the underlying storage.\n     * @return a new counter for the enumerated descriptor.\n     */\n    public AtomicCounter newCounter(final CountersManager countersManager)\n    {\n        final AtomicCounter counter =\n            countersManager.newCounter(label, SYSTEM_COUNTER_TYPE_ID, (buffer) -> buffer.putInt(0, id));\n        countersManager.setCounterRegistrationId(counter.id(), id);\n        countersManager.setCounterOwnerId(counter.id(), NULL_VALUE);\n        return counter;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/SystemCounters.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\n\nimport java.util.EnumMap;\n\n/**\n * Aggregate entry point for managing counters of system status.\n */\npublic final class SystemCounters implements AutoCloseable\n{\n    private final EnumMap<SystemCounterDescriptor, AtomicCounter> counterByDescriptorMap =\n        new EnumMap<>(SystemCounterDescriptor.class);\n\n    /**\n     * Construct the counters for this system.\n     *\n     * @param countersManager which will manage the underlying storage.\n     */\n    public SystemCounters(final CountersManager countersManager)\n    {\n        for (final SystemCounterDescriptor descriptor : SystemCounterDescriptor.values())\n        {\n            counterByDescriptorMap.put(descriptor, descriptor.newCounter(countersManager));\n        }\n    }\n\n    /**\n     * Get the counter for a particular descriptor.\n     *\n     * @param descriptor by which the counter should be looked up.\n     * @return the counter for the given descriptor.\n     */\n    public AtomicCounter get(final SystemCounterDescriptor descriptor)\n    {\n        return counterByDescriptorMap.get(descriptor);\n    }\n\n    /**\n     * Close all the counters.\n     */\n    public void close()\n    {\n        CloseHelper.closeAll(counterByDescriptorMap.values());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"SystemCounters{\" +\n            \"counterByDescriptorMap=\" + counterByDescriptorMap +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/main/java/io/aeron/driver/status/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Status counters that track progress or events experienced by the driver.\n */\npackage io.aeron.driver.status;"
  },
  {
    "path": "aeron-driver/src/main/resources/aeron-ipc.properties",
    "content": "aeron.ipc.mtu.length=8k\naeron.threading.mode=SHARED\naeron.shared.idle.strategy=spin\nagrona.disable.bounds.checks=true\n"
  },
  {
    "path": "aeron-driver/src/main/resources/aeron-throughput.properties",
    "content": "aeron.term.buffer.sparse.file=false\naeron.mtu.length=8k\naeron.ipc.mtu.length=8k\naeron.socket.so_sndbuf=2m\naeron.socket.so_rcvbuf=2m\naeron.rcv.initial.window.length=2m\nagrona.disable.bounds.checks=true\n"
  },
  {
    "path": "aeron-driver/src/main/resources/debug-loss-10.properties",
    "content": "aeron.SendChannelEndpoint.supplier=io.aeron.driver.ext.DebugSendChannelEndpointSupplier\naeron.ReceiveChannelEndpoint.supplier=io.aeron.driver.ext.DebugReceiveChannelEndpointSupplier\naeron.debug.receive.data.loss.rate=0.10\naeron.debug.receive.data.loss.seed=-1\n"
  },
  {
    "path": "aeron-driver/src/main/resources/high-stream-count.properties",
    "content": "aeron.socket.so_sndbuf=2m\naeron.socket.so_rcvbuf=2m\naeron.term.buffer.length=256k\naeron.conductor.idle.strategy=spin\naeron.term.buffer.sparse.file=true\nagrona.disable.bounds.checks=true\n"
  },
  {
    "path": "aeron-driver/src/main/resources/low-latency.properties",
    "content": "aeron.term.buffer.sparse.file=false\naeron.pre.touch.mapped.memory=true\naeron.socket.so_sndbuf=2m\naeron.socket.so_rcvbuf=2m\naeron.rcv.initial.window.length=2m\naeron.threading.mode=DEDICATED\naeron.sender.idle.strategy=noop\naeron.receiver.idle.strategy=noop\naeron.conductor.idle.strategy=spin\nagrona.disable.bounds.checks=true\n"
  },
  {
    "path": "aeron-driver/src/test/c/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nif (MSVC AND \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\")\n    set(AERON_LIB_WINSOCK_LIBS wsock32 ws2_32 Iphlpapi)\nendif ()\n\ninclude_directories(${AERON_DRIVER_SOURCE_PATH})\n\nset(TEST_HEADERS\n    aeron_driver_conductor_test.h\n    aeron_receiver_test.h\n    EmbeddedMediaDriver.h\n    aeron_test_udp_bindings.h\n    aeron_test_base.h)\n\nset(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h)\ncheck_type_size(\"struct mmsghdr\" STRUCT_MMSGHDR_TYPE_EXISTS)\nset(CMAKE_EXTRA_INCLUDE_FILES)\n\nfunction(aeron_driver_test name file)\n    add_executable(${name} ${file} ${TEST_HEADERS})\n    add_dependencies(${name} gmock)\n    target_link_libraries(${name} aeron_driver gmock_main ${CMAKE_THREAD_LIBS_INIT} ${AERON_LIB_WINSOCK_LIBS})\n    target_compile_definitions(${name} PUBLIC \"_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING\")\n    if (STRUCT_MMSGHDR_TYPE_EXISTS)\n        target_compile_definitions(${name} PUBLIC -DHAVE_STRUCT_MMSGHDR)\n    endif ()\n    add_test(NAME ${name} COMMAND ${name})\nendfunction()\n\nif (AERON_UNIT_TESTS)\n    aeron_driver_test(counters_manager_test aeron_counters_manager_test.cpp)\n    aeron_driver_test(driver_conductor_ipc_test aeron_driver_conductor_ipc_test.cpp)\n    aeron_driver_test(driver_conductor_network_test aeron_driver_conductor_network_test.cpp)\n    aeron_driver_test(driver_conductor_pub_sub_test aeron_driver_conductor_pub_sub_test.cpp)\n    aeron_driver_test(driver_conductor_spy_test aeron_driver_conductor_spy_test.cpp)\n    aeron_driver_test(driver_conductor_counter_test aeron_driver_conductor_counter_test.cpp)\n    aeron_driver_test(driver_conductor_clock_test aeron_driver_conductor_clock_test.cpp)\n    aeron_driver_test(driver_conductor_config_test aeron_driver_conductor_config_test.cpp)\n    aeron_driver_test(driver_conductor_subscribable_test aeron_driver_conductor_subscribable_test.cpp)\n    aeron_driver_test(driver_uri_test aeron_driver_uri_test.cpp)\n    aeron_driver_test(udp_channel_test aeron_udp_channel_test.cpp)\n    aeron_driver_test(term_scanner_test aeron_term_scanner_test.cpp)\n    aeron_driver_test(loss_detector_test aeron_loss_detector_test.cpp)\n    aeron_driver_test(retransmit_handler_test aeron_retransmit_handler_test.cpp)\n    aeron_driver_test(loss_reporter_test aeron_loss_reporter_test.cpp)\n    aeron_driver_test(logbuffer_unblocker aeron_logbuffer_unblocker_test.cpp)\n    aeron_driver_test(term_gap_filler_test aeron_term_gap_filler_test.cpp)\n    aeron_driver_test(parse_util_test aeron_parse_util_test.cpp)\n    aeron_driver_test(properties_test aeron_properties_test.cpp)\n    aeron_driver_test(driver_configuration_test aeron_driver_configuration_test.cpp)\n    aeron_driver_test(driver_agent_test agent/aeron_driver_agent_test.cpp)\n    aeron_driver_test(udp_channel_transport_loss_test media/aeron_udp_channel_transport_loss_test.cpp)\n    aeron_driver_test(udp_channel_transport_multi_gap_loss_test media/aeron_udp_channel_transport_multi_gap_loss_test.cpp)\n    aeron_driver_test(flow_control_test aeron_flow_control_test.cpp)\n    aeron_driver_test(name_resolver_test aeron_name_resolver_test.cpp)\n    aeron_driver_test(name_resolver_cache_test aeron_name_resolver_cache_test.cpp)\n    aeron_driver_test(data_packet_dispatcher_test aeron_data_packet_dispatcher_test.cpp)\n    aeron_driver_test(publication_image_test aeron_publication_image_test.cpp)\n    aeron_driver_test(port_manager_test aeron_port_manager_test.cpp)\n    set_tests_properties(publication_image_test PROPERTIES RUN_SERIAL TRUE)\n\n    aeron_driver_test(c_terminate_test aeron_c_terminate_test.cpp)\n    set_tests_properties(c_terminate_test PROPERTIES TIMEOUT 60)\n    set_tests_properties(c_terminate_test PROPERTIES RUN_SERIAL TRUE)\n\n    aeron_driver_test(c_system_test aeron_c_system_test.cpp)\n    set_tests_properties(c_system_test PROPERTIES TIMEOUT 120)\n    set_tests_properties(c_system_test PROPERTIES RUN_SERIAL TRUE)\n\n    aeron_driver_test(c_multi_destination_test aeron_c_multi_destination_test.cpp)\n    set_tests_properties(c_multi_destination_test PROPERTIES TIMEOUT 60)\n    set_tests_properties(c_multi_destination_test PROPERTIES RUN_SERIAL TRUE)\n\n    aeron_driver_test(c_local_addresses_test aeron_c_local_addresses_test.cpp)\n    set_tests_properties(c_local_addresses_test PROPERTIES TIMEOUT 60)\n    set_tests_properties(c_local_addresses_test PROPERTIES RUN_SERIAL TRUE)\n\n    aeron_driver_test(c_errors_test aeron_errors_test.cpp)\n    set_tests_properties(c_errors_test PROPERTIES TIMEOUT 60)\n    set_tests_properties(c_errors_test PROPERTIES RUN_SERIAL TRUE)\n\n    aeron_driver_test(c_cnc_test aeron_c_cnc_test.cpp)\n    set_tests_properties(c_cnc_test PROPERTIES TIMEOUT 60)\n    set_tests_properties(c_cnc_test PROPERTIES RUN_SERIAL TRUE)\n\n    aeron_driver_test(congestion_control_test aeron_congestion_control_test.cpp)\n\n    aeron_driver_test(ipc_publication_test aeron_ipc_publication_test.cpp)\n    aeron_driver_test(network_publication_test aeron_network_publication_test.cpp)\n\n    aeron_driver_test(timestamps_test_recvmmsg aeron_timestamps_test.cpp)\n    set_tests_properties(timestamps_test_recvmmsg PROPERTIES ENVIRONMENT \"AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND=2;AERON_RECEIVER_IO_VECTOR_CAPACITY=3;AERON_SENDER_IO_VECTOR_CAPACITY=4\")\n\n    aeron_driver_test(timestamps_test_recvmsg aeron_timestamps_test.cpp)\n    set_tests_properties(timestamps_test_recvmsg PROPERTIES ENVIRONMENT \"AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND=1;AERON_RECEIVER_IO_VECTOR_CAPACITY=1;AERON_SENDER_IO_VECTOR_CAPACITY=1\")\n\n    aeron_driver_test(position_test aeron_position_test.cpp)\n    aeron_driver_test(driver_context_config_test aeron_driver_context_config_test.cpp)\n\n    if (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n        add_executable(aeronmd_signal_test aeronmd_signal_test.cpp)\n        add_dependencies(aeronmd_signal_test gmock aeronmd)\n        target_compile_definitions(aeronmd_signal_test PUBLIC \"_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING\")\n        target_link_libraries(aeronmd_signal_test aeron gmock_main ${CMAKE_THREAD_LIBS_INIT} ${AERON_LIB_WINSOCK_LIBS})\n        add_test(NAME aeronmd_signal_test COMMAND aeronmd_signal_test $<TARGET_FILE:aeronmd>)\n    endif ()\n\nendif ()"
  },
  {
    "path": "aeron-driver/src/test/c/EmbeddedMediaDriver.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_EMBEDDED_MEDIA_DRIVER_H\n#define AERON_EMBEDDED_MEDIA_DRIVER_H\n\n#if defined(__linux__)\n#ifndef _BSD_SOURCE\n#define _BSD_SOURCE\n#endif\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n#endif\n\n#include <string>\n#include <thread>\n#include <atomic>\n#include <stdexcept>\n#include <functional>\n\nextern \"C\"\n{\n#include \"aeronmd.h\"\n}\n\nnamespace aeron\n{\n\nconst char *TERMINATION_KEY = \"Exit please\";\n\nclass EmbeddedMediaDriver\n{\npublic:\n    explicit EmbeddedMediaDriver(std::function<void(aeron_driver_context_t *)> setContextFunc = [](aeron_driver_context_t *) {})\n        : m_setContextFunc(setContextFunc)\n    {\n    }\n\n    ~EmbeddedMediaDriver()\n    {\n        if (0 != aeron_driver_close(m_driver))\n        {\n            fprintf(stderr, \"ERROR: driver close (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        }\n\n        if (0 != aeron_driver_context_close(m_context))\n        {\n            fprintf(stderr, \"ERROR: driver context close (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        }\n    }\n\n    void driverLoop()\n    {\n        while (m_running)\n        {\n            aeron_driver_main_idle_strategy(m_driver, aeron_driver_main_do_work(m_driver));\n        }\n    }\n\n    void stop()\n    {\n        m_running = false;\n        if (m_thread.joinable())\n        {\n            m_thread.join();\n        }\n    }\n\n    void joinAndClose()\n    {\n        m_running = false;\n        if (m_thread.joinable())\n        {\n            m_thread.join();\n        }\n\n        aeron_driver_close(m_driver);\n        aeron_driver_context_close(m_context);\n\n        m_driver = nullptr;\n        m_context = nullptr;\n    }\n\n    void start()\n    {\n        if (init() < 0)\n        {\n            throw std::runtime_error(\"failed to initialize\");\n        }\n\n        m_thread = std::thread(\n            [&]()\n            {\n                driverLoop();\n            });\n    }\n\n    const char *directory()\n    {\n        return aeron_driver_context_get_dir(m_context);\n    }\n\nprotected:\n    int init()\n    {\n        if (aeron_driver_context_init(&m_context) < 0)\n        {\n            fprintf(stderr, \"ERROR: context init (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n            return -1;\n        }\n\n        aeron_driver_context_set_threading_mode(m_context, AERON_THREADING_MODE_SHARED);\n        aeron_driver_context_set_dir_delete_on_start(m_context, true);\n        aeron_driver_context_set_dir_delete_on_shutdown(m_context, true);\n        aeron_driver_context_set_shared_idle_strategy(m_context, \"sleep-ns\");\n        aeron_driver_context_set_term_buffer_sparse_file(m_context, true);\n        aeron_driver_context_set_term_buffer_length(m_context, 64 * 1024);\n        aeron_driver_context_set_driver_termination_validator(m_context, validateTermination, nullptr);\n        aeron_driver_context_set_driver_termination_hook(m_context, terminationHook, this);\n\n        m_setContextFunc(m_context);\n\n        if (aeron_driver_init(&m_driver, m_context) < 0)\n        {\n            fprintf(stderr, \"ERROR: driver init (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n            return -1;\n        }\n\n        if (aeron_driver_start(m_driver, true) < 0)\n        {\n            fprintf(stderr, \"ERROR: driver start (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n            return -1;\n        }\n\n        return 0;\n    }\n\nprivate:\n    std::atomic<bool> m_running = { true };\n    std::thread m_thread;\n    aeron_driver_context_t *m_context = nullptr;\n    aeron_driver_t *m_driver = nullptr;\n    std::function<void(aeron_driver_context_t *)> m_setContextFunc;\n\n    static bool validateTermination(void *state, uint8_t *buffer, int32_t length)\n    {\n        auto key_length = static_cast<int32_t>(strlen(TERMINATION_KEY));\n        return key_length == length && 0 == memcmp(TERMINATION_KEY, buffer, static_cast<size_t>(length));\n    }\n\n    static void terminationHook(void *clientd)\n    {\n        auto *driver = static_cast<EmbeddedMediaDriver *>(clientd);\n        driver->m_running = false;\n    }\n};\n\n}\n\n#endif //AERON_EMBEDDED_MEDIA_DRIVER_H\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_c_cnc_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\n#include \"aeron_test_base.h\"\n\nextern \"C\"\n{\n#include \"aeron_socket.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"aeron_system_counters.h\"\n}\n\n#define URI_RESERVED \"aeron:udp?endpoint=127.0.0.1:24325\"\n#define PUB_URI_2 \"aeron:udp?endpoint=127.0.0.1:24326\"\n#define STREAM_ID (117)\n\nusing namespace aeron;\n\nclass CounterIdFilter\n{\npublic:\n    explicit CounterIdFilter(std::int32_t filterId) :\n        m_filterId(filterId)\n    {\n    }\n\n    void apply(int32_t id, int64_t value)\n    {\n        if (id == m_filterId)\n        {\n            m_foundValue = value;\n            m_matchCount++;\n        }\n        m_totalCounters++;\n    }\n\n    bool matches() const\n    {\n        return 0 < m_matchCount;\n    }\n\n    std::int64_t value() const\n    {\n        return m_foundValue;\n    }\n\nprivate:\n    std::int32_t m_filterId = 0;\n    std::int32_t m_totalCounters = 0;\n    std::int64_t m_foundValue = 0;\n    std::int32_t m_matchCount = 0;\n};\n\nclass CncTest : public CSystemTestBase, public testing::Test\n{\nprotected:\n    CncTest() : CSystemTestBase(\n        std::vector<std::pair<std::string, std::string>>\n        {\n            { \"AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS\",        \"loss\" },\n            { \"AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_LOSS_ARGS\", \"rate=0.2|recv-msg-mask=0xF\" }\n        })\n    {\n    }\n\n    void TearDown() override\n    {\n        if (nullptr != m_cnc)\n        {\n            aeron_cnc_close(m_cnc);\n        }\n        if (-1 != m_socketFd)\n        {\n            aeron_close_socket(m_socketFd);\n        }\n    }\n\nprotected:\n    aeron_cnc_t *m_cnc = nullptr;\n    aeron_socket_t m_socketFd = -1;\n\n    bool bindToLocalUdpSocket(uint16_t port)\n    {\n        sockaddr_in bind_address = {};\n        bind_address.sin_family = AF_INET;\n        bind_address.sin_port = htons(port);\n        inet_pton(AF_INET, \"127.0.0.1\", &bind_address.sin_addr);\n\n        m_socketFd = aeron_socket(PF_INET, SOCK_DGRAM, 0);\n        if (m_socketFd < 0)\n        {\n            return false;\n        }\n\n        if (bind(m_socketFd, (const sockaddr *)&bind_address, sizeof(bind_address)) < 0)\n        {\n            return false;\n        }\n\n        return true;\n    }\n\n    bool openAeronCnc()\n    {\n        return 0 == aeron_cnc_init(&m_cnc, m_driver.directory(), 1000);\n    }\n\n    static void countingErrorLogCallback(\n        int32_t observation_count,\n        int64_t first_observation_timestamp,\n        int64_t last_observation_timestamp,\n        const char *error,\n        size_t error_length,\n        void *clientd)\n    {\n        int *counter = reinterpret_cast<int *>(clientd);\n        (*counter)++;\n    }\n\n    static void counterFilterCallback(\n        int64_t value,\n        int32_t id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_length,\n        const char *label,\n        size_t label_length,\n        void *clientd)\n    {\n        auto *filter = reinterpret_cast<CounterIdFilter *>(clientd);\n        filter->apply(id, value);\n    }\n\n    static void countingLossReader(\n        void *clientd,\n        int64_t observation_count,\n        int64_t total_bytes_lost,\n        int64_t first_observation_timestamp,\n        int64_t last_observation_timestamp,\n        int32_t session_id,\n        int32_t stream_id,\n        const char *channel,\n        int32_t channel_length,\n        const char *source,\n        int32_t source_length)\n    {\n        int *counter = reinterpret_cast<int *>(clientd);\n        (*counter)++;\n    }\n};\n\nTEST_F(CncTest, shouldGetCncConstants)\n{\n    ASSERT_TRUE(openAeronCnc()) << aeron_errmsg();\n    aeron_cnc_constants_t constants;\n    aeron_cnc_constants(m_cnc, &constants);\n\n    ASSERT_LT(0, constants.pid);\n    ASSERT_LT(0, constants.start_timestamp);\n    ASSERT_LT(0, constants.cnc_version);\n}\n\nTEST_F(CncTest, shouldGetCountersAndDistinctErrorLogs)\n{\n    ASSERT_TRUE(connect());\n    ASSERT_TRUE(openAeronCnc()) << aeron_errmsg();\n    int errorCallbackCounter = 0;\n\n    aeron_counters_reader_t *counters = aeron_cnc_counters_reader(m_cnc);\n    CounterIdFilter filter{ AERON_SYSTEM_COUNTER_ERRORS };\n\n    aeron_counters_reader_foreach_counter(counters, counterFilterCallback, &filter);\n    ASSERT_TRUE(filter.matches());\n    ASSERT_EQ(0, filter.value());\n\n    ASSERT_EQ(0U, aeron_cnc_error_log_read(m_cnc, countingErrorLogCallback, &errorCallbackCounter, 0));\n\n    aeron_socket_t fd = bindToLocalUdpSocket(24325);\n    ASSERT_LT(0, fd);\n\n    aeron_async_add_subscription_t *async;\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async, m_aeron, URI_RESERVED, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0) << aeron_errmsg();\n    ASSERT_EQ(nullptr, awaitSubscriptionOrError(async));\n\n    filter = CounterIdFilter{ AERON_SYSTEM_COUNTER_ERRORS };\n\n    int64_t deadline_ms = aeron_epoch_clock() + 1000;\n    while (filter.value() < 1)\n    {\n        aeron_counters_reader_foreach_counter(counters, counterFilterCallback, &filter);\n        ASSERT_LT(aeron_epoch_clock(), deadline_ms) << \"Timed out waiting for error counter\";\n    }\n\n    ASSERT_EQ(1U, aeron_cnc_error_log_read(m_cnc, countingErrorLogCallback, &errorCallbackCounter, 0));\n    ASSERT_EQ(1, errorCallbackCounter);\n}\n\nTEST_F(CncTest, shouldGetLossReport)\n{\n    ASSERT_TRUE(connect());\n    ASSERT_TRUE(openAeronCnc()) << aeron_errmsg();\n    int lossCallbackCounter = 0;\n\n    aeron_async_add_subscription_t *async_sub;\n    aeron_subscription_t *subscription;\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, PUB_URI_2, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_TRUE((subscription = awaitSubscriptionOrError(async_sub))) << aeron_errmsg();\n\n    aeron_async_add_publication_t *async_pub;\n    aeron_publication_t *publication;\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, PUB_URI_2, STREAM_ID), 0);\n    ASSERT_TRUE((publication = awaitPublicationOrError(async_pub))) << aeron_errmsg();\n\n    const char *message = \"hello world\";\n\n    poll_handler_t handler =\n        [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        {\n        };\n\n    aeron_counters_reader_t *counters = aeron_cnc_counters_reader(m_cnc);\n    volatile int64_t *retransmitsSentCounter = aeron_counters_reader_addr(counters, AERON_SYSTEM_COUNTER_RETRANSMITS_SENT);\n\n    int64_t retransmits = 0;\n    for (int i = 0; i < 100 && 0 == retransmits; i++)\n    {\n        int64_t offer;\n        do\n        {\n            offer = aeron_publication_offer(\n                publication,\n                reinterpret_cast<const uint8_t *>(message),\n                strlen(message),\n                nullptr,\n                nullptr);\n\n            ASSERT_NE(AERON_PUBLICATION_ERROR, offer) << aeron_errmsg();\n        }\n        while (offer < 0);\n\n        while (poll(subscription, handler, 1) < 1)\n        {\n        }\n\n        AERON_GET_ACQUIRE(retransmits, *retransmitsSentCounter);\n    }\n\n    ASSERT_TRUE(0 < aeron_cnc_loss_reporter_read(m_cnc, countingLossReader, &lossCallbackCounter)) << aeron_errmsg();\n    ASSERT_NE(0, lossCallbackCounter);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_c_local_addresses_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"EmbeddedMediaDriver.h\"\n\n#include \"aeron_test_base.h\"\n\nextern \"C\"\n{\n#include \"concurrent/aeron_atomic.h\"\n}\n\n#if defined(GTEST_USES_POSIX_RE)\n#define RESOLVED_ADDRESS_PATTERN \"^127\\\\.0\\\\.0\\\\.1:[1-9][0-9]*$\"\n#define RESOLVED_IPV6_ADDRESS_PATTERN \"^\\\\[::1\\\\]:[1-9][0-9]*$\"\n#elif defined(GTEST_USES_SIMPLE_RE)\n#define RESOLVED_ADDRESS_PATTERN \"^127\\\\.0\\\\.0\\\\.1:\\\\d*$\"\n#define RESOLVED_IPV6_ADDRESS_PATTERN \"^\\\\[::1\\\\]:\\\\d*$\"\n#endif\n#define PUB_URI_ENDPOINT \"127.0.0.1\"\n#define PUB_URI_CONTROL \"127.0.0.1:24326\"\n#define URI_RESERVED \"aeron:udp?endpoint=\" PUB_URI_ENDPOINT \":0|control=\" PUB_URI_CONTROL\n#define URI_PUB \"aeron:udp?endpoint=\" PUB_URI_ENDPOINT \":1024|control=\" PUB_URI_CONTROL\n#define PUB_URI_IPV6 \"aeron:udp?endpoint=[::1]:0\"\n#define STREAM_ID (117)\n\n#define NUM_BUFFERS (4)\n\nusing namespace aeron;\n\nclass CLocalAddressesTest : public CSystemTestBase, public testing::Test\n{\npublic:\n    CLocalAddressesTest()\n    {\n        for (int i = 0; i < NUM_BUFFERS; i++)\n        {\n            m_addrs[i].iov_base = m_buffers[i];\n            m_addrs[i].iov_len = sizeof(m_buffers[i]);\n        }\n    }\n\n    static int awaitSubscriptionDestinationOrError(aeron_async_destination_t *async)\n    {\n        while (true)\n        {\n            std::this_thread::yield();\n            int result = aeron_subscription_async_destination_poll(async);\n            if (result != 0)\n            {\n                return result;\n            }\n        }\n    }\n\nprotected:\n    uint8_t m_buffers[NUM_BUFFERS][AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN] = {};\n    aeron_iovec_t m_addrs[NUM_BUFFERS] = {};\n};\n\nTEST_F(CLocalAddressesTest, shouldGetAddressForPublication)\n{\n    std::atomic<bool> publicationClosedFlag(false);\n    aeron_async_add_publication_t *async;\n    aeron_publication_t *publication;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_publication(&async, m_aeron, URI_PUB, STREAM_ID), 0);\n    ASSERT_TRUE((publication = awaitPublicationOrError(async))) << aeron_errmsg();\n\n    ASSERT_EQ(1, aeron_publication_local_sockaddrs(publication, m_addrs, NUM_BUFFERS));\n    ASSERT_STREQ(PUB_URI_CONTROL, reinterpret_cast<char *>(m_addrs[0].iov_base));\n\n    aeron_publication_close(publication, setFlagOnClose, &publicationClosedFlag);\n\n    while (!publicationClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n}\n\nTEST_F(CLocalAddressesTest, shouldGetAddressForExclusivePublication)\n{\n    std::atomic<bool> publicationClosedFlag(false);\n    aeron_async_add_exclusive_publication_t *async = nullptr;\n    aeron_exclusive_publication_t *publication;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication(&async, m_aeron, URI_PUB, STREAM_ID), 0);\n    ASSERT_TRUE((publication = awaitExclusivePublicationOrError(async))) << aeron_errmsg();\n\n    ASSERT_EQ(1, aeron_exclusive_publication_local_sockaddrs(publication, m_addrs, NUM_BUFFERS));\n    ASSERT_STREQ(PUB_URI_CONTROL, reinterpret_cast<char *>(m_addrs[0].iov_base));\n\n    aeron_exclusive_publication_close(publication, setFlagOnClose, &publicationClosedFlag);\n\n    while (!publicationClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n}\n\n\nTEST_F(CLocalAddressesTest, shouldGetAddressForSubscription)\n{\n    std::atomic<bool> subscriptionClosedFlag(false);\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async, m_aeron, URI_RESERVED, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_TRUE((subscription = awaitSubscriptionOrError(async))) << aeron_errmsg();\n\n    ASSERT_EQ(1, aeron_subscription_local_sockaddrs(subscription, m_addrs, NUM_BUFFERS));\n    ASSERT_THAT(reinterpret_cast<char *>(m_addrs[0].iov_base), testing::ContainsRegex(RESOLVED_ADDRESS_PATTERN));\n\n    ASSERT_EQ(1, aeron_subscription_resolved_endpoint(subscription, reinterpret_cast<char *>(m_buffers[0]), 1024));\n    ASSERT_THAT(reinterpret_cast<char *>(m_buffers[0]), testing::ContainsRegex(RESOLVED_ADDRESS_PATTERN));\n\n    std::string resolvedEndpointParam = \"endpoint=\" + std::string(reinterpret_cast<char *>(m_addrs[0].iov_base));\n    char uriWithResolvedEndpoint[1024] = { 0 };\n    aeron_subscription_try_resolve_channel_endpoint_port(\n        subscription, uriWithResolvedEndpoint, sizeof(uriWithResolvedEndpoint));\n\n    ASSERT_THAT(uriWithResolvedEndpoint, testing::HasSubstr(resolvedEndpointParam));\n\n    aeron_subscription_close(subscription, setFlagOnClose, &subscriptionClosedFlag);\n\n    while (!subscriptionClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n}\n\nTEST_F(CLocalAddressesTest, shouldGetIPv6AddressForSubscription)\n{\n    std::atomic<bool> subscriptionClosedFlag(false);\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async, m_aeron, PUB_URI_IPV6, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_TRUE((subscription = awaitSubscriptionOrError(async))) << aeron_errmsg();\n\n    ASSERT_EQ(1, aeron_subscription_local_sockaddrs(subscription, m_addrs, NUM_BUFFERS));\n    ASSERT_THAT(reinterpret_cast<char *>(m_addrs[0].iov_base), testing::ContainsRegex(RESOLVED_IPV6_ADDRESS_PATTERN));\n\n    ASSERT_EQ(1, aeron_subscription_resolved_endpoint(subscription, reinterpret_cast<char *>(m_buffers[0]), 1024));\n    ASSERT_THAT(reinterpret_cast<char *>(m_buffers[0]), testing::ContainsRegex(RESOLVED_IPV6_ADDRESS_PATTERN));\n\n    std::string resolvedEndpointParam = \"endpoint=\" + std::string(reinterpret_cast<char *>(m_addrs[0].iov_base));\n    char uriWithResolvedEndpoint[1024] = { 0 };\n    aeron_subscription_try_resolve_channel_endpoint_port(\n        subscription, uriWithResolvedEndpoint, sizeof(uriWithResolvedEndpoint));\n\n    ASSERT_THAT(uriWithResolvedEndpoint, testing::HasSubstr(resolvedEndpointParam));\n\n    aeron_subscription_close(subscription, setFlagOnClose, &subscriptionClosedFlag);\n\n    while (!subscriptionClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n}\n\nTEST_F(CLocalAddressesTest, shouldGetOriginalAddressWhenNoWildcardSpecified)\n{\n    std::atomic<bool> subscriptionClosedFlag(false);\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription;\n    const char *uri = \"aeron:udp?endpoint=[::1]:12345\";\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async, m_aeron, uri, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_TRUE((subscription = awaitSubscriptionOrError(async))) << aeron_errmsg();\n\n    char uriWithResolvedEndpoint[1024] = { 0 };\n\n    ASSERT_LT(0, aeron_subscription_try_resolve_channel_endpoint_port(\n        subscription, uriWithResolvedEndpoint, sizeof(uriWithResolvedEndpoint)));\n    ASSERT_STREQ(uri, uriWithResolvedEndpoint);\n\n    aeron_subscription_close(subscription, setFlagOnClose, &subscriptionClosedFlag);\n\n    while (!subscriptionClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n}\n\nTEST_F(CLocalAddressesTest, shouldGetAddressesForMultiDestinationSubscription)\n{\n    std::atomic<bool> subscriptionClosedFlag(false);\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    aeron_async_destination_t *async_dest = nullptr;\n    aeron_subscription_t *subscription;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, \"aeron:udp?control-mode=manual\", STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_TRUE((subscription = awaitSubscriptionOrError(async_sub))) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_subscription_async_add_destination(\n        &async_dest, m_aeron, subscription, \"aeron:udp?endpoint=127.0.0.1:9090\"));\n    ASSERT_EQ(1, awaitSubscriptionDestinationOrError(async_dest)) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_subscription_async_add_destination(\n        &async_dest, m_aeron, subscription, \"aeron:udp?endpoint=127.0.0.1:9091\"));\n    ASSERT_EQ(1, awaitSubscriptionDestinationOrError(async_dest)) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_subscription_async_add_destination(\n        &async_dest, m_aeron, subscription, \"aeron:udp?endpoint=127.0.0.1:9093\"));\n    ASSERT_EQ(1, awaitSubscriptionDestinationOrError(async_dest)) << aeron_errmsg();\n\n    ASSERT_EQ(3, aeron_subscription_local_sockaddrs(subscription, m_addrs, NUM_BUFFERS));\n    ASSERT_STREQ(\"127.0.0.1:9090\", reinterpret_cast<char *>(m_addrs[0].iov_base));\n    ASSERT_STREQ(\"127.0.0.1:9091\", reinterpret_cast<char *>(m_addrs[1].iov_base));\n    ASSERT_STREQ(\"127.0.0.1:9093\", reinterpret_cast<char *>(m_addrs[2].iov_base));\n\n    ASSERT_EQ(3, aeron_subscription_local_sockaddrs(subscription, m_addrs, 2));\n\n    aeron_subscription_close(subscription, setFlagOnClose, &subscriptionClosedFlag);\n\n    while (!subscriptionClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n}\n\nclass CLocalAddressesTestParameterized : public CSystemTestBase, public testing::TestWithParam<std::string>\n{\npublic:\n    CLocalAddressesTestParameterized()\n    {\n        for (int i = 0; i < NUM_BUFFERS; i++)\n        {\n            m_addrs[i].iov_base = m_buffers[i];\n            m_addrs[i].iov_len = sizeof(m_buffers[i]);\n        }\n    }\n\nprotected:\n    uint8_t m_buffers[NUM_BUFFERS][AERON_CLIENT_MAX_LOCAL_ADDRESS_STR_LEN] = {};\n    aeron_iovec_t m_addrs[NUM_BUFFERS] = {};\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    CLocalAddressesTestParameterized,\n    CLocalAddressesTestParameterized,\n    testing::Values(\"aeron:ipc?so-rcvbuf=32k|alias=test|term-length=64k|mtu=2048\", \"aeron:udp?endpoint=localhost:8092|alias=test\", \"aeron:udp?mtu=8|rcv-wnd=64k|control-mode=response\", \"aeron:udp?control-mode=manual|endpoint=localhost:0\"));\n\nTEST_P(CLocalAddressesTestParameterized, shouldGetOriginalChannelWhenNoWildcardSubstitutionToBePerformed)\n{\n    const std::string& channel = GetParam();\n    std::atomic<bool> subscriptionClosedFlag(false);\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_subscription_t *subscription;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async, m_aeron, channel.c_str(), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_TRUE((subscription = awaitSubscriptionOrError(async))) << aeron_errmsg();\n\n    char uriWithResolvedEndpoint[1024] = { 0 };\n\n    ASSERT_LT(0, aeron_subscription_try_resolve_channel_endpoint_port(\n        subscription, uriWithResolvedEndpoint, sizeof(uriWithResolvedEndpoint)));\n    ASSERT_STREQ(channel.c_str(), uriWithResolvedEndpoint);\n\n    aeron_subscription_close(subscription, setFlagOnClose, &subscriptionClosedFlag);\n\n    while (!subscriptionClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_c_multi_destination_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\n#include \"aeron_test_base.h\"\n\nextern \"C\"\n{\n#include \"concurrent/aeron_atomic.h\"\n#include \"agent/aeron_driver_agent.h\"\n#include \"aeron_driver_context.h\"\n}\n\n#define PUB_URI_1 \"aeron:udp?endpoint=localhost:24324\"\n#define PUB_URI_2 \"aeron:udp?endpoint=localhost:24325\"\n#define CONTROL_URI \"aeron:udp?control=localhost:24326\"\n#define MDC_URI (CONTROL_URI \"|control-mode=dynamic\")\n#define MDC_DEST_URI (CONTROL_URI \"|endpoint=localhost:24327\")\n#define MDS_URI \"aeron:udp?control-mode=manual\"\n#define STREAM_ID (117)\n\nclass CMultiDestinationTest : public CSystemTestBase, public testing::Test\n{\nprotected:\n    CMultiDestinationTest() : CSystemTestBase(\n        std::vector<std::pair<std::string, std::string>>\n            {\n            })\n    {\n    }\n};\n\nstatic void mdsParameters(\n    aeron_exclusive_publication_t *pub,\n    std::int32_t *initialTermId,\n    std::int32_t *termId,\n    std::int32_t *termOffset,\n    std::int32_t *sessionId)\n{\n    aeron_publication_constants_t pubConstants = {};\n    aeron_exclusive_publication_constants(pub, &pubConstants);\n\n    int64_t position = aeron_exclusive_publication_position(pub);\n    *initialTermId = pubConstants.initial_term_id;\n    *termId = aeron_logbuffer_compute_term_id_from_position(\n        position, pubConstants.position_bits_to_shift, pubConstants.initial_term_id);\n    *termOffset = (int32_t)(position & (pubConstants.term_buffer_length - 1));\n    *sessionId = pubConstants.session_id;\n}\n\nTEST_F(CMultiDestinationTest, shouldAddTwoPublicationDestinationsForMds)\n{\n    aeron_async_add_subscription_t *async_sub = nullptr;\n\n    ASSERT_TRUE(connect());\n\n    aeron_async_add_exclusive_publication_t *async_pub1 = nullptr;\n    ASSERT_EQ(aeron_async_add_exclusive_publication(&async_pub1, m_aeron, PUB_URI_1, STREAM_ID), 0);\n    aeron_exclusive_publication_t *pub1 = awaitExclusivePublicationOrError(async_pub1);\n    ASSERT_TRUE(pub1) << aeron_errmsg();\n\n    std::int32_t initialTermId;\n    std::int32_t termId;\n    std::int32_t termOffset;\n    std::int32_t sessionId;\n    mdsParameters(pub1, &initialTermId, &termId, &termOffset, &sessionId);\n\n    auto pubUri2 = std::string(PUB_URI_2)\n        .append(\"|init-term-id=\").append(std::to_string(initialTermId))\n        .append(\"|term-id=\").append(std::to_string(termId))\n        .append(\"|term-offset=\").append(std::to_string(termOffset))\n        .append(\"|session-id=\").append(std::to_string(sessionId));\n\n    aeron_async_add_exclusive_publication_t *async_pub2 = nullptr;\n    ASSERT_EQ(0, aeron_async_add_exclusive_publication(&async_pub2, m_aeron, pubUri2.c_str(), STREAM_ID));\n    aeron_exclusive_publication_t *pub2 = awaitExclusivePublicationOrError(async_pub2);\n    ASSERT_TRUE(pub2) << aeron_errmsg();\n\n\n    ASSERT_EQ(0, aeron_async_add_subscription(\n        &async_sub, m_aeron, MDS_URI, STREAM_ID, nullptr, nullptr, nullptr, nullptr));\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    aeron_async_destination_t *dest1;\n    aeron_subscription_async_add_destination(&dest1, m_aeron, subscription, PUB_URI_1);\n    ASSERT_TRUE(awaitDestinationOrError(dest1)) << aeron_errmsg();\n\n    aeron_async_destination_t *dest2;\n    aeron_subscription_async_add_destination(&dest2, m_aeron, subscription, PUB_URI_2);\n    ASSERT_TRUE(awaitDestinationOrError(dest2)) << aeron_errmsg();\n\n    awaitConnected(subscription);\n\n    EXPECT_EQ(aeron_exclusive_publication_close(pub1, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_exclusive_publication_close(pub2, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_F(CMultiDestinationTest, shouldAddPublicationAndMdcPublicationForMds)\n{\n    aeron_async_add_subscription_t *async_sub = nullptr;\n\n    ASSERT_TRUE(connect());\n\n    aeron_async_add_exclusive_publication_t *async_pub1 = nullptr;\n    ASSERT_EQ(aeron_async_add_exclusive_publication(&async_pub1, m_aeron, MDC_URI, STREAM_ID), 0);\n    aeron_exclusive_publication_t *pub1 = awaitExclusivePublicationOrError(async_pub1);\n    ASSERT_TRUE(pub1) << aeron_errmsg();\n\n    std::int32_t initialTermId;\n    std::int32_t termId;\n    std::int32_t termOffset;\n    std::int32_t sessionId;\n    mdsParameters(pub1, &initialTermId, &termId, &termOffset, &sessionId);\n\n    auto pubUri2 = std::string(PUB_URI_2)\n        .append(\"|init-term-id=\").append(std::to_string(initialTermId))\n        .append(\"|term-id=\").append(std::to_string(termId))\n        .append(\"|term-offset=\").append(std::to_string(termOffset))\n        .append(\"|session-id=\").append(std::to_string(sessionId));\n\n    aeron_async_add_exclusive_publication_t *async_pub2 = nullptr;\n    ASSERT_EQ(0, aeron_async_add_exclusive_publication(&async_pub2, m_aeron, pubUri2.c_str(), STREAM_ID));\n    aeron_exclusive_publication_t *pub2 = awaitExclusivePublicationOrError(async_pub2);\n    ASSERT_TRUE(pub2) << aeron_errmsg();\n\n\n    ASSERT_EQ(0, aeron_async_add_subscription(\n        &async_sub, m_aeron, MDS_URI, STREAM_ID, nullptr, nullptr, nullptr, nullptr));\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    aeron_async_destination_t *dest1;\n    aeron_subscription_async_add_destination(&dest1, m_aeron, subscription, MDC_DEST_URI);\n    ASSERT_TRUE(awaitDestinationOrError(dest1)) << aeron_errmsg();\n\n    aeron_async_destination_t *dest2;\n    aeron_subscription_async_add_destination(&dest2, m_aeron, subscription, PUB_URI_2);\n    ASSERT_TRUE(awaitDestinationOrError(dest2)) << aeron_errmsg();\n\n    awaitConnected(subscription);\n\n    EXPECT_EQ(aeron_exclusive_publication_close(pub1, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_exclusive_publication_close(pub2, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_F(CMultiDestinationTest, shouldNotAllowChannelsWithControlButWithoutEndpointOrControlMode)\n{\n    ASSERT_TRUE(connect());\n\n    aeron_async_add_exclusive_publication_t *async_pub1 = nullptr;\n    ASSERT_EQ(aeron_async_add_exclusive_publication(&async_pub1, m_aeron, CONTROL_URI, STREAM_ID), 0);\n    aeron_exclusive_publication_t *pub1 = awaitExclusivePublicationOrError(async_pub1);\n    ASSERT_EQ(nullptr, pub1);\n}\n\nTEST_F(CMultiDestinationTest, shouldNotAllowChannelsWithControlModeDynamicButWithoutControl)\n{\n    ASSERT_TRUE(connect());\n\n    aeron_async_add_exclusive_publication_t *async_pub1 = nullptr;\n    ASSERT_EQ(aeron_async_add_exclusive_publication(\n        &async_pub1, m_aeron, \"aeron:udp?control-mode=dynamic\", STREAM_ID), 0);\n    aeron_exclusive_publication_t *pub1 = awaitExclusivePublicationOrError(async_pub1);\n    ASSERT_EQ(nullptr, pub1);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_c_system_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"aeron_test_base.h\"\n\nextern \"C\"\n{\n#include \"aeron_counters.h\"\n#include \"aeron_image.h\"\n#include \"agent/aeron_driver_agent.h\"\n#include \"aeron_driver_context.h\"\n}\n\n#define PUB_URI \"aeron:udp?endpoint=localhost:24325\"\n#define STREAM_ID (117)\n\nclass CSystemTest : public CSystemTestBase, public testing::TestWithParam<std::tuple<const char *>>\n{\nprotected:\n    CSystemTest() : CSystemTestBase(\n        std::vector<std::pair<std::string, std::string>>\n            {\n                { AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR, \"17\" },\n                { AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR, \"17\" },\n                { AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_ENV_VAR, \"17\" }\n            })\n    {\n    }\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    CSystemTestWithParams,\n    CSystemTest,\n    testing::Values(std::make_tuple(PUB_URI), std::make_tuple(AERON_IPC_CHANNEL)));\n\nstatic int64_t set_reserved_value(void *clientd, uint8_t *buffer, size_t frame_length)\n{\n    return *(int64_t *)clientd;\n}\n\nstatic int64_t subscription_registration_id(aeron_subscription_t *subscription)\n{\n    aeron_subscription_constants_t constants;\n    aeron_subscription_constants(subscription, &constants);\n    return constants.registration_id;\n}\n\nTEST_F(CSystemTest, shouldSpinUpDriverAndConnectSuccessfully)\n{\n    aeron_context_t *context;\n    aeron_t *aeron;\n\n    ASSERT_EQ(aeron_context_init(&context), 0);\n    ASSERT_EQ(aeron_init(&aeron, context), 0);\n\n    ASSERT_EQ(aeron_start(aeron), 0);\n\n    aeron_close(aeron);\n    aeron_context_close(context);\n}\n\nTEST_F(CSystemTest, shouldReallocateBindingsClientd)\n{\n    aeron_driver_context_t *context = nullptr;\n    aeron_driver_t *driver = nullptr;\n    char aeron_dir[AERON_MAX_PATH] = { 0 };\n    const char *name0 = \"name0\";\n    int val0 = 10;\n    const char *name1 = \"name1\";\n    int val1 = 11;\n\n    aeron_temp_filename(aeron_dir, sizeof(aeron_dir));\n\n    aeron_env_set(\"AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS\", \"loss\");\n\n    ASSERT_EQ(aeron_driver_context_init(&context), 0);\n\n    aeron_driver_context_set_dir(context, aeron_dir);\n    aeron_driver_context_set_dir_delete_on_shutdown(context, true);\n\n    ASSERT_EQ(2U, context->num_bindings_clientd_entries);\n\n    context->bindings_clientd_entries[0].name = name0;\n    context->bindings_clientd_entries[0].clientd = &val0;\n    context->bindings_clientd_entries[1].name = name1;\n    context->bindings_clientd_entries[1].clientd = &val1;\n\n    aeron_driver_agent_logging_events_init(\"FRAME_IN\", \"\");\n    aeron_driver_agent_init_logging_events_interceptors(context);\n\n    ASSERT_EQ(0, aeron_driver_init(&driver, context)) << aeron_errmsg();\n\n    ASSERT_EQ(4U, context->num_bindings_clientd_entries);\n\n    ASSERT_STREQ(name0, context->bindings_clientd_entries[0].name);\n    ASSERT_EQ((void *)&val0, context->bindings_clientd_entries[0].clientd);\n    ASSERT_STREQ(name1, context->bindings_clientd_entries[1].name);\n    ASSERT_EQ((void *)&val1, context->bindings_clientd_entries[1].clientd);\n\n    aeron_driver_close(driver);\n    aeron_driver_context_close(context);\n}\n\nTEST_P(CSystemTest, shouldAddAndClosePublication)\n{\n    std::atomic<bool> publicationClosedFlag(false);\n    aeron_async_add_publication_t *async = nullptr;\n    aeron_publication_constants_t publication_constants;\n\n    aeron_async_add_subscription_t *asyncSub = nullptr;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &asyncSub, m_aeron, std::get<0>(GetParam()), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(asyncSub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_publication(&async, m_aeron, std::get<0>(GetParam()), STREAM_ID), 0);\n    std::int64_t registration_id = aeron_async_add_publication_get_registration_id(async);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_publication_constants(publication, &publication_constants)) << aeron_errmsg();\n    ASSERT_EQ(registration_id, publication_constants.registration_id);\n\n    while (!aeron_publication_is_connected(publication))\n    {\n        std::this_thread::yield();\n    }\n\n    const auto countersReader = aeron_counters_reader(m_aeron);\n    const int32_t pubPosCounterId = aeron_counters_reader_find_by_type_id_and_registration_id(\n        countersReader, AERON_COUNTER_PUBLISHER_POSITION_TYPE_ID, registration_id);\n    ASSERT_NE(AERON_NULL_COUNTER_ID, pubPosCounterId);\n    int counterState;\n    ASSERT_EQ(0, aeron_counters_reader_counter_state(countersReader, pubPosCounterId, &counterState));\n    ASSERT_EQ(AERON_COUNTER_RECORD_ALLOCATED, counterState);\n\n    ASSERT_EQ(0, aeron_publication_close(publication, setFlagOnClose, &publicationClosedFlag));\n    ASSERT_TRUE(aeron_publication_is_closed(publication));\n\n    while (!publicationClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n\n    do\n    {\n        ASSERT_EQ(0, aeron_counters_reader_counter_state(countersReader, pubPosCounterId, &counterState));\n        std::this_thread::yield();\n    }\n    while (AERON_COUNTER_RECORD_RECLAIMED != counterState);\n\n    ASSERT_EQ(0, aeron_subscription_close(subscription, nullptr, nullptr));\n}\n\nTEST_P(CSystemTest, shouldAddAndCloseExclusivePublication)\n{\n    std::atomic<bool> publicationClosedFlag(false);\n    aeron_async_add_exclusive_publication_t *async = nullptr;\n    aeron_publication_constants_t publication_constants;\n    aeron_async_add_subscription_t *asyncSub = nullptr;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &asyncSub, m_aeron, std::get<0>(GetParam()), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(asyncSub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication(&async, m_aeron, std::get<0>(GetParam()), STREAM_ID), 0);\n    std::int64_t registration_id = aeron_async_add_exclusive_exclusive_publication_get_registration_id(async);\n\n    aeron_exclusive_publication_t *publication = awaitExclusivePublicationOrError(async);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n    ASSERT_EQ(0, aeron_exclusive_publication_constants(publication, &publication_constants));\n    ASSERT_EQ(registration_id, publication_constants.registration_id);\n\n    while (!aeron_exclusive_publication_is_connected(publication))\n    {\n        std::this_thread::yield();\n    }\n\n    const auto countersReader = aeron_counters_reader(m_aeron);\n    const int32_t pubPosCounterId = aeron_counters_reader_find_by_type_id_and_registration_id(\n        countersReader, AERON_COUNTER_PUBLISHER_POSITION_TYPE_ID, registration_id);\n    ASSERT_NE(AERON_NULL_COUNTER_ID, pubPosCounterId);\n    int counterState;\n    ASSERT_EQ(0, aeron_counters_reader_counter_state(countersReader, pubPosCounterId, &counterState));\n    ASSERT_EQ(AERON_COUNTER_RECORD_ALLOCATED, counterState);\n\n    ASSERT_EQ(0, aeron_exclusive_publication_close(publication, setFlagOnClose, &publicationClosedFlag));\n    ASSERT_TRUE(aeron_exclusive_publication_is_closed(publication));\n\n    while (!publicationClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n\n    do\n    {\n        ASSERT_EQ(0, aeron_counters_reader_counter_state(countersReader, pubPosCounterId, &counterState));\n        std::this_thread::yield();\n    }\n    while (AERON_COUNTER_RECORD_RECLAIMED != counterState);\n\n    ASSERT_EQ(0, aeron_subscription_close(subscription, nullptr, nullptr));\n}\n\nTEST_P(CSystemTest, shouldAddAndCloseSubscription)\n{\n    std::atomic<bool> subscriptionClosedFlag(false);\n    aeron_async_add_subscription_t *async = nullptr;\n    aeron_async_add_exclusive_publication_t *asyncPub = nullptr;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication(&asyncPub, m_aeron, std::get<0>(GetParam()), STREAM_ID), 0);\n    aeron_exclusive_publication_t *publication = awaitExclusivePublicationOrError(asyncPub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async, m_aeron, std::get<0>(GetParam()), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    const int64_t registrationId = aeron_async_add_subscription_get_registration_id(async);\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    while (!aeron_subscription_is_connected(subscription))\n    {\n        std::this_thread::yield();\n    }\n\n    const auto countersReader = aeron_counters_reader(m_aeron);\n    const int32_t subPosCounterId = aeron_counters_reader_find_by_type_id_and_registration_id(\n        countersReader, AERON_COUNTER_SUBSCRIPTION_POSITION_TYPE_ID, registrationId);\n    ASSERT_NE(AERON_NULL_COUNTER_ID, subPosCounterId);\n    int counterState;\n    ASSERT_EQ(0, aeron_counters_reader_counter_state(countersReader, subPosCounterId, &counterState));\n    ASSERT_EQ(AERON_COUNTER_RECORD_ALLOCATED, counterState);\n\n    ASSERT_EQ(0, aeron_subscription_close(subscription, setFlagOnClose, &subscriptionClosedFlag));\n\n    while (!subscriptionClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n\n    do\n    {\n        ASSERT_EQ(0, aeron_counters_reader_counter_state(countersReader, subPosCounterId, &counterState));\n        std::this_thread::yield();\n    }\n    while (AERON_COUNTER_RECORD_RECLAIMED != counterState);\n\n    ASSERT_EQ(0, aeron_exclusive_publication_close(publication, nullptr, nullptr));\n}\n\nTEST_F(CSystemTest, shouldAddAndCloseCounter)\n{\n    std::atomic<bool> counterClosedFlag(false);\n    aeron_async_add_counter_t *async = nullptr;\n    aeron_counter_constants_t counter_constants;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_counter(\n        &async, m_aeron, 12, nullptr, 0, \"my counter\", strlen(\"my counter\")), 0);\n    std::int64_t registration_id = aeron_async_add_counter_get_registration_id(async);\n\n    aeron_counter_t *counter = awaitCounterOrError(async);\n    ASSERT_TRUE(counter) << aeron_errmsg();\n    ASSERT_EQ(0, aeron_counter_constants(counter, &counter_constants));\n    ASSERT_EQ(registration_id, counter_constants.registration_id);\n\n    aeron_counter_close(counter, setFlagOnClose, &counterClosedFlag);\n\n    while (!counterClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n\n    aeron_counters_reader_t *counters_reader = aeron_counters_reader(m_aeron);\n    int32_t state = AERON_COUNTER_RECORD_ALLOCATED;\n    while(AERON_COUNTER_RECORD_RECLAIMED != state)\n    {\n        ASSERT_EQ(0, aeron_counters_reader_counter_state(counters_reader, counter_constants.counter_id, &state));\n    }\n}\n\nTEST_F(CSystemTest, shouldAddStaticCounter)\n{\n    std::atomic<bool> counterClosedFlag(false);\n    aeron_async_add_counter_t *async = nullptr;\n    aeron_counter_constants_t counter_constants;\n    aeron_counter_constants_t counter_constants2;\n    aeron_counter_constants_t counter_constants3;\n\n    ASSERT_TRUE(connect());\n\n    const char *key = \"my static key\";\n    size_t key_length = strlen(key);\n    const char *label = \"my static counter label\";\n    size_t label_length = strlen(label);\n    int type_id = 12;\n    int64_t registration_id = -51515155188822;\n    ASSERT_EQ(aeron_async_add_static_counter(\n        &async, m_aeron, type_id, (uint8_t *)key, key_length, label, label_length, registration_id), 0);\n\n    aeron_counter_t *counter = awaitStaticCounterOrError(async);\n    ASSERT_TRUE(counter) << aeron_errmsg();\n    ASSERT_EQ(0, aeron_counter_constants(counter, &counter_constants));\n    ASSERT_EQ(registration_id, counter_constants.registration_id);\n\n    ASSERT_EQ(aeron_async_add_static_counter(\n        &async, m_aeron, type_id, nullptr, 0, \"test\", 4, registration_id), 0);\n\n    aeron_counter_t *counter2 = awaitStaticCounterOrError(async);\n    ASSERT_TRUE(counter2) << aeron_errmsg();\n    ASSERT_EQ(0, aeron_counter_constants(counter2, &counter_constants2));\n    ASSERT_EQ(counter_constants.counter_id, counter_constants2.counter_id);\n    ASSERT_EQ(counter_constants.registration_id, counter_constants2.registration_id);\n    ASSERT_NE(counter, counter2);\n\n    counterClosedFlag = false;\n    aeron_counter_close(counter, setFlagOnClose, &counterClosedFlag);\n    while (!counterClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n\n    counterClosedFlag = false;\n    aeron_counter_close(counter2, setFlagOnClose, &counterClosedFlag);\n    while (!counterClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n\n    ASSERT_EQ(aeron_async_add_static_counter(\n        &async, m_aeron, type_id, nullptr, 0, \"another counter\", 4, 999999999), 0);\n\n    aeron_counter_t *counter3 = awaitStaticCounterOrError(async);\n    ASSERT_TRUE(counter3) << aeron_errmsg();\n    ASSERT_EQ(0, aeron_counter_constants(counter3, &counter_constants3));\n    ASSERT_EQ(999999999, counter_constants3.registration_id);\n    ASSERT_NE(counter_constants.counter_id, counter_constants3.counter_id);\n\n    counterClosedFlag = false;\n    aeron_counter_close(counter3, setFlagOnClose, &counterClosedFlag);\n    while (!counterClosedFlag)\n    {\n        std::this_thread::yield();\n    }\n\n    // verify that the closed static counter was not deleted\n    aeron_counters_reader_t *counters_reader = aeron_counters_reader(m_aeron);\n    int32_t state;\n    ASSERT_EQ(0, aeron_counters_reader_counter_state(counters_reader, counter_constants.counter_id, &state));\n    ASSERT_EQ(AERON_COUNTER_RECORD_ALLOCATED, state);\n}\n\nTEST_F(CSystemTest, shouldGetNextAvailableSessionId)\n{\n    aeron_async_get_next_available_session_id_t *async = nullptr;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_next_session_id(&async, m_aeron, 555), 0);\n\n    int64_t next_session_id = awaitNextSessionIdOrError(async);\n    ASSERT_NE(next_session_id, INT64_MAX) << aeron_errmsg();\n\n    async = nullptr;\n    ASSERT_EQ(aeron_async_next_session_id(&async, m_aeron, 777), 0);\n\n    int64_t next_session_id2 = awaitNextSessionIdOrError(async);\n    ASSERT_NE(next_session_id, INT64_MAX) << aeron_errmsg();\n    ASSERT_EQ(next_session_id2, next_session_id + 1);\n\n    aeron_async_add_publication_t *asyncPub1 = nullptr, *asyncPub2 = nullptr;\n\n    ASSERT_EQ(aeron_async_add_publication(\n        &asyncPub1,\n        m_aeron,\n        std::string(\"aeron:ipc?term-length=64k\").append(\"|session-id=\").append(std::to_string(next_session_id2 + 1)).c_str(),\n        555), 0);\n    ASSERT_EQ(aeron_async_add_publication(\n        &asyncPub2,\n        m_aeron,\n        std::string(\"aeron:udp?term-length=64k|endpoint=localhost:8989\").append(\"|session-id=\").append(std::to_string(next_session_id2 + 2)).c_str(),\n        777), 0);\n\n    aeron_publication_t *pub1 = awaitPublicationOrError(asyncPub1);\n    ASSERT_TRUE(pub1) << aeron_errmsg();\n\n    aeron_publication_t *pub2 = awaitPublicationOrError(asyncPub2);\n    ASSERT_TRUE(pub2) << aeron_errmsg();\n\n    async = nullptr;\n    ASSERT_EQ(aeron_async_next_session_id(&async, m_aeron, 555), 0);\n\n    next_session_id = awaitNextSessionIdOrError(async);\n    ASSERT_NE(next_session_id, INT64_MAX) << aeron_errmsg();\n    ASSERT_EQ(next_session_id, next_session_id2 + 2);\n\n    async = nullptr;\n    ASSERT_EQ(aeron_async_next_session_id(&async, m_aeron, 777), 0);\n\n    next_session_id = awaitNextSessionIdOrError(async);\n    ASSERT_NE(next_session_id, INT64_MAX) << aeron_errmsg();\n    ASSERT_EQ(next_session_id, next_session_id2 + 3);\n\n    EXPECT_EQ(aeron_publication_close(pub1, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_publication_close(pub2, nullptr, nullptr), 0);\n}\n\nTEST_P(CSystemTest, shouldAddPublicationAndSubscription)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, std::get<0>(GetParam()), STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, std::get<0>(GetParam()), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    awaitConnected(subscription);\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_P(CSystemTest, shouldOfferAndPollOneMessage)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    const char message[] = \"message\";\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, std::get<0>(GetParam()), STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, std::get<0>(GetParam()), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n    int64_t reserved_value = 0x12345678;\n\n    while (aeron_publication_offer(\n        publication, (const uint8_t *)message, strlen(message), set_reserved_value, &reserved_value) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    int poll_result;\n    bool called = false;\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        EXPECT_EQ(length, strlen(message));\n        aeron_header_values_t header_values;\n        aeron_header_values(header, &header_values);\n        ASSERT_EQ(reserved_value, header_values.frame.reserved_value);\n        called = true;\n    };\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_TRUE(called);\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_P(CSystemTest, shouldOfferAndPollThreeTermsOfMessages)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    const char message[1024] = \"message\";\n    size_t num_messages = 64 * 3 + 1;\n    const char *uri = std::get<0>(GetParam());\n    bool isIpc = 0 == strncmp(AERON_IPC_CHANNEL, uri, sizeof(AERON_IPC_CHANNEL));\n    std::string pubUri = isIpc ? std::string(uri) : std::string(uri) + \"|term-length=64k\";\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, pubUri.c_str(), STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    for (size_t i = 0; i < num_messages; i++)\n    {\n        while (aeron_publication_offer(\n            publication, (const uint8_t *)message, sizeof(message), nullptr, nullptr) < 0)\n        {\n            std::this_thread::yield();\n        }\n\n        int poll_result;\n        bool called = false;\n        poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        {\n            EXPECT_EQ(length, sizeof(message));\n            called = true;\n        };\n\n        while ((poll_result = poll(subscription, handler, 1)) == 0)\n        {\n            std::this_thread::yield();\n        }\n        EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n        EXPECT_TRUE(called);\n    }\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_P(CSystemTest, shouldOfferAndPollThreeTermsOfMessagesWithTryClaim)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    const char message[1024] = \"message\";\n    size_t num_messages = 64 * 3 + 1;\n    const char *uri = std::get<0>(GetParam());\n    bool isIpc = 0 == strncmp(AERON_IPC_CHANNEL, uri, sizeof(AERON_IPC_CHANNEL));\n    std::string pubUri = isIpc ? std::string(uri) : std::string(uri) + \"|term-length=64k\";\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, pubUri.c_str(), STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    for (size_t i = 0; i < num_messages; i++)\n    {\n        aeron_buffer_claim_t buffer_claim;\n\n        while (aeron_publication_try_claim(publication, sizeof(message), &buffer_claim) < 0)\n        {\n            std::this_thread::yield();\n        }\n\n        memcpy(buffer_claim.data, message, sizeof(message));\n        ASSERT_EQ(aeron_buffer_claim_commit(&buffer_claim), 0);\n\n        int poll_result;\n        bool called = false;\n        poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        {\n            EXPECT_EQ(length, sizeof(message));\n            called = true;\n        };\n\n        while ((poll_result = poll(subscription, handler, 1)) == 0)\n        {\n            std::this_thread::yield();\n        }\n        EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n        EXPECT_TRUE(called);\n    }\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_P(CSystemTest, shouldOfferAndPollThreeTermsOfMessagesWithExclusivePublicationTryClaim)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    const char message[1024] = \"message\";\n    size_t num_messages = 64 * 3 + 1;\n    const char *uri = std::get<0>(GetParam());\n    bool isIpc = 0 == strncmp(AERON_IPC_CHANNEL, uri, sizeof(AERON_IPC_CHANNEL));\n    std::string pubUri = isIpc ? std::string(uri) : std::string(uri) + \"|term-length=64k\";\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_exclusive_publication(&async_pub, m_aeron, pubUri.c_str(), STREAM_ID), 0);\n\n    aeron_exclusive_publication_t *publication = awaitExclusivePublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    for (size_t i = 0; i < num_messages; i++)\n    {\n        aeron_buffer_claim_t buffer_claim;\n\n        while (aeron_exclusive_publication_try_claim(publication, sizeof(message), &buffer_claim) < 0)\n        {\n            std::this_thread::yield();\n        }\n\n        memcpy(buffer_claim.data, message, sizeof(message));\n        ASSERT_EQ(aeron_buffer_claim_commit(&buffer_claim), 0);\n\n        int poll_result;\n        bool called = false;\n        poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        {\n            EXPECT_EQ(length, sizeof(message));\n            called = true;\n        };\n\n        while ((poll_result = poll(subscription, handler, 1)) == 0)\n        {\n            std::this_thread::yield();\n        }\n        EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n        EXPECT_TRUE(called);\n    }\n\n    EXPECT_EQ(aeron_exclusive_publication_close(publication, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_P(CSystemTest, shouldAllowImageToGoUnavailableWithNoPollAfter)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    const char message[1024] = \"message\";\n    size_t num_messages = 11;\n    std::atomic<bool> on_unavailable_image_called = { false };\n    const char *uri = std::get<0>(GetParam());\n    bool isIpc = 0 == strncmp(AERON_IPC_CHANNEL, uri, sizeof(AERON_IPC_CHANNEL));\n    std::string pubUri = isIpc ? std::string(uri) : std::string(uri) + \"|linger=0\";\n\n    m_onUnavailableImage = [&](aeron_subscription_t *, aeron_image_t *)\n    {\n        on_unavailable_image_called = true;\n    };\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, pubUri.c_str(), STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri, STREAM_ID, nullptr, nullptr, onUnavailableImage, this), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    for (size_t i = 0; i < num_messages; i++)\n    {\n        while (aeron_publication_offer(\n            publication, (const uint8_t *)message, sizeof(message), nullptr, nullptr) < 0)\n        {\n            std::this_thread::yield();\n        }\n\n        int poll_result;\n        bool called = false;\n        poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        {\n            EXPECT_EQ(length, sizeof(message));\n            called = true;\n        };\n\n        while ((poll_result = poll(subscription, handler, 10)) == 0)\n        {\n            std::this_thread::yield();\n        }\n        EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n        EXPECT_TRUE(called);\n    }\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n\n    while (!on_unavailable_image_called)\n    {\n        std::this_thread::yield();\n    }\n\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_P(CSystemTest, shouldAllowImageToGoUnavailableWithPollAfter)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    const char message[1024] = \"message\";\n    size_t num_messages = 11;\n    std::atomic<bool> on_unavailable_image_called = { false };\n    const char *uri = std::get<0>(GetParam());\n    bool isIpc = 0 == strncmp(AERON_IPC_CHANNEL, uri, sizeof(AERON_IPC_CHANNEL));\n    std::string pubUri = isIpc ? std::string(uri) : std::string(uri) + \"|linger=0\";\n\n    m_onUnavailableImage = [&](aeron_subscription_t *, aeron_image_t *)\n    {\n        on_unavailable_image_called = true;\n    };\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, pubUri.c_str(), STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri, STREAM_ID, nullptr, nullptr, onUnavailableImage, this), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    for (size_t i = 0; i < num_messages; i++)\n    {\n        while (aeron_publication_offer(\n            publication, (const uint8_t *)message, sizeof(message), nullptr, nullptr) < 0)\n        {\n            std::this_thread::yield();\n        }\n\n        int poll_result;\n        bool called = false;\n        poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        {\n            EXPECT_EQ(length, sizeof(message));\n            called = true;\n        };\n\n        while ((poll_result = poll(subscription, handler, 10)) == 0)\n        {\n            std::this_thread::yield();\n        }\n        EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n        EXPECT_TRUE(called);\n    }\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n\n    while (!on_unavailable_image_called)\n    {\n        std::this_thread::yield();\n    }\n\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n    };\n\n    poll(subscription, handler, 1);\n\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\n\nTEST_P(CSystemTest, shouldAllowImageToGoUnavailableAndThenRejoin)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    const char message[1024] = \"message\";\n    size_t num_messages = 11;\n    std::atomic<bool> on_unavailable_image_called = { false };\n    const char *uri = std::get<0>(GetParam());\n    bool isIpc = 0 == strncmp(AERON_IPC_CHANNEL, uri, sizeof(AERON_IPC_CHANNEL));\n    \n    // Fixed session id, to ensure rejoin\n    std::string pubUri = isIpc ? std::string(uri) : std::string(uri) + \"|linger=0|session-id=123\";\n\n    m_onUnavailableImage = [&](aeron_subscription_t *, aeron_image_t *)\n    {\n        on_unavailable_image_called = true;\n    };\n\n    ASSERT_TRUE(connect());\n\n    // Setup subscription\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri, STREAM_ID, nullptr, nullptr, onUnavailableImage, this), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    \n    auto createPublicationAndOffer = [&](){\n        ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, pubUri.c_str(), STREAM_ID), 0);\n        aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n        ASSERT_TRUE(publication) << aeron_errmsg();\n\n        awaitConnected(subscription);\n\n        for (size_t i = 0; i < num_messages; i++)\n        {\n            while (aeron_publication_offer(\n                publication, (const uint8_t *)message, sizeof(message), nullptr, nullptr) < 0)\n            {\n                std::this_thread::yield();\n            }\n\n            int poll_result;\n            bool called = false;\n            poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n            {\n                EXPECT_EQ(length, sizeof(message));\n                called = true;\n            };\n\n            while ((poll_result = poll(subscription, handler, 10)) == 0)\n            {\n                std::this_thread::yield();\n            }\n            EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n            EXPECT_TRUE(called);\n        }\n\n        EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n\n        while (!on_unavailable_image_called)\n        {\n            std::this_thread::yield();\n        }\n\n        poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n        {\n        };\n\n        poll(subscription, handler, 1);\n    };\n\n    createPublicationAndOffer();\n\n    createPublicationAndOffer();\n\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_P(CSystemTest, shouldAddMultipleSubscriptionsUsingSameImage)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n\n    ASSERT_TRUE(connect());\n\n    const char *uri = std::get<0>(GetParam());\n    bool isIpc = 0 == strncmp(AERON_IPC_CHANNEL, uri, sizeof(AERON_IPC_CHANNEL));\n    std::string pubUri = isIpc ? std::string(uri) : std::string(uri) + \"|linger=0\";\n\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, pubUri.c_str(), STREAM_ID), 0);\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n\n    std::vector<std::pair<int64_t, int64_t>> unavailable_images;\n    std::atomic<int64_t> unavailable_count(0);\n\n    m_onUnavailableImage = [&](aeron_subscription_t *sub, aeron_image_t *img)\n    {\n        int64_t sub_id = subscription_registration_id(sub);\n\n        aeron_image_constants_t img_constants;\n        aeron_image_constants(img, &img_constants);\n\n        int64_t img_sub_id = subscription_registration_id(img_constants.subscription);\n\n        unavailable_images.emplace_back(sub_id, img_sub_id);\n        unavailable_count.fetch_add(1, std::memory_order_release);\n    };\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, std::get<0>(GetParam()), STREAM_ID, nullptr, nullptr, onUnavailableImage, this), 0);\n    aeron_subscription_t *subscription1 = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription1) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, std::get<0>(GetParam()), STREAM_ID, nullptr, nullptr, onUnavailableImage, this), 0);\n    aeron_subscription_t *subscription2 = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription2) << aeron_errmsg();\n\n    awaitConnected(subscription1);\n    awaitConnected(subscription2);\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n\n    int64_t start_time_ms = aeron_epoch_clock();\n    while (unavailable_count.load(std::memory_order_acquire) < 2)\n    {\n        if (aeron_epoch_clock() - start_time_ms > 10000)\n        {\n            throw std::runtime_error(std::string(\"timeout, count=\").append(std::to_string(unavailable_count)));\n        }\n        std::this_thread::yield();\n    }\n\n    int64_t id1 = subscription_registration_id(subscription1);\n    int64_t id2 = subscription_registration_id(subscription2);\n    EXPECT_THAT(unavailable_images, testing::UnorderedElementsAre(\n        std::pair<int64_t, int64_t>(id1, id1),\n        std::pair<int64_t, int64_t>(id2, id2)));\n}\n\nTEST_F(CSystemTest, shouldNotifyImageGoingUnavailableWhenSubscriptionIsClosed)\n{\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    std::atomic<bool> on_available_image_called = { false };\n    std::atomic<bool> on_unavailable_image_called = { false };\n    const char *uri = \"aeron:ipc?term-length=64k\";\n    size_t num_messages = 3;\n    const char message[37] = \"this is a test\";\n    int64_t final_position;\n\n    m_onAvailableImage = [&](aeron_subscription_t *, aeron_image_t *)\n    {\n        on_available_image_called = true;\n    };\n\n    m_onUnavailableImage = [&](aeron_subscription_t *, aeron_image_t *image)\n    {\n        EXPECT_EQ(image->final_position, final_position);\n        EXPECT_EQ(image->eos_position, INT64_MAX);\n        EXPECT_FALSE(image->is_eos);\n        EXPECT_TRUE(image->is_closed);\n        on_unavailable_image_called = true;\n    };\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, uri, STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri, STREAM_ID, onAvailableImage, this, onUnavailableImage, this), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    while (!on_available_image_called)\n    {\n        std::this_thread::yield();\n    }\n\n    for (size_t i = 0; i < num_messages; i++)\n    {\n        while (aeron_publication_offer(\n            publication, (const uint8_t *)message, sizeof(message), nullptr, nullptr) < 0)\n        {\n            std::this_thread::yield();\n        }\n    }\n\n    int poll_result;\n    bool called = false;\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        EXPECT_EQ(length, sizeof(message));\n        called = true;\n    };\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1);\n    EXPECT_TRUE(called);\n\n    auto image = aeron_subscription_image_at_index(subscription, 0);\n    final_position = aeron_image_position(image);\n    aeron_subscription_image_release(subscription, image);\n\n    std::atomic<bool> subscription_closed = { false };\n    aeron_notification_t on_close = [](void *clientd)\n    {\n        auto flag = reinterpret_cast<std::atomic<bool> *>(clientd);\n        *flag = true;\n    };\n    EXPECT_EQ(aeron_subscription_close(subscription, on_close, &subscription_closed), 0);\n\n    while (!subscription_closed)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_TRUE(on_unavailable_image_called);\n}\n\nTEST_F(CSystemTest, shouldSetClientName)\n{\n    aeron_context_t *context;\n    ASSERT_EQ(aeron_context_init(&context), 0);\n    aeron_context_set_client_name(context, \"this is a name\");\n\n    ASSERT_STREQ(\"this is a name\", aeron_context_get_client_name(context));\n\n    aeron_context_close(context);\n}\n\nTEST_F(CSystemTest, shouldSetNullClientName)\n{\n    aeron_context_t *context;\n    ASSERT_EQ(aeron_context_init(&context), 0);\n    ASSERT_EQ(-1, aeron_context_set_client_name(context, nullptr));\n\n    aeron_context_close(context);\n}\n\nTEST_F(CSystemTest, shouldSetClientNameOverLong)\n{\n    const char *name =\n        \"this is a very long value that we are hoping with be reject when the value gets \"\n        \"set on the the context without causing issues will labels\";\n\n    aeron_context_t *context;\n    ASSERT_EQ(aeron_context_init(&context), 0);\n    ASSERT_EQ(-1, aeron_context_set_client_name(context, name));\n    ASSERT_EQ(EINVAL, aeron_errcode());\n\n    aeron_context_close(context);\n}\n\nTEST_F(CSystemTest, shouldBeNotifiedWhenClientIsClosed)\n{\n    ASSERT_TRUE(connect());\n    const auto countersReader = aeron_counters_reader(m_aeron);\n\n    aeron_context_t *context;\n    aeron_t *aeron;\n\n    ASSERT_EQ(aeron_context_init(&context), 0);\n    ASSERT_EQ(aeron_init(&aeron, context), 0);\n\n    ASSERT_EQ(aeron_start(aeron), 0);\n\n    const std::int64_t key = 1234567890;\n    const auto counterName = \"test\";\n    aeron_async_add_counter_t *async_add_counter;\n    ASSERT_EQ(0, aeron_async_add_counter(\n        &async_add_counter,\n        aeron,\n        10001,\n        (const uint8_t *)&key,\n        sizeof(key),\n        counterName,\n        strlen(counterName))) << aeron_errmsg();\n\n    aeron_counter_t *counter = nullptr;\n    aeron_async_add_counter_poll(&counter, async_add_counter);\n    while (nullptr == counter)\n    {\n        std::this_thread::yield();\n        aeron_async_add_counter_poll(&counter, async_add_counter);\n    }\n\n    const auto clientId = aeron_client_id(aeron);\n    int32_t heartbeatCounterId = AERON_NULL_COUNTER_ID;\n    while (AERON_NULL_COUNTER_ID == heartbeatCounterId)\n    {\n        heartbeatCounterId = aeron_counters_reader_find_by_type_id_and_registration_id(\n            countersReader, AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID, clientId);\n        std::this_thread::yield();\n    }\n\n    aeron_close(aeron);\n    aeron_context_close(context);\n\n    int32_t state = AERON_COUNTER_RECORD_ALLOCATED;\n    while (AERON_COUNTER_RECORD_ALLOCATED == state)\n    {\n        ASSERT_EQ(0, aeron_counters_reader_counter_state(countersReader, heartbeatCounterId, &state));\n        std::this_thread::yield();\n    }\n    ASSERT_EQ(0, aeron_counter_get_acquire(aeron_counters_reader_addr(countersReader, AERON_SYSTEM_COUNTER_CLIENT_TIMEOUTS)));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_c_terminate_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n#include <cmath>\n#include <gtest/gtest.h>\n\n#include \"EmbeddedMediaDriver.h\"\n\nextern \"C\"\n{\n#include \"aeron_client.h\"\n#include \"aeron_cnc_file_descriptor.h\"\n}\n\nusing namespace aeron;\n\nclass TerminateTest : public testing::Test\n{\npublic:\n    TerminateTest() = default;\n};\n\nTEST_F(TerminateTest, shouldShutdownDriver)\n{\n    EmbeddedMediaDriver driver;\n    driver.start();\n\n    char path[AERON_MAX_PATH] = { 0 };\n    aeron_cnc_resolve_filename(driver.directory(), path, sizeof(path));\n\n    EXPECT_EQ(1, aeron_context_request_driver_termination(\n        driver.directory(), (uint8_t *)TERMINATION_KEY, strlen(TERMINATION_KEY))) << aeron_errmsg();\n\n    driver.joinAndClose();\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_congestion_control_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include <array>\n\nextern \"C\"\n{\n#include \"aeron_congestion_control.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_counters.h\"\n#include \"util/aeron_env.h\"\n#include \"media/aeron_udp_channel.h\"\n\nint32_t aeron_cubic_congestion_control_strategy_get_max_cwnd(void *state);\n}\n\n#define CAPACITY (32 * 1024)\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\ntypedef std::array<std::uint8_t, 4 * CAPACITY> buffer_4x_t;\n\nclass CongestionControlTest : public testing::Test\n{\npublic:\n    CongestionControlTest()\n    {\n        reset_env();\n        if (aeron_driver_context_init(&m_context) < 0)\n        {\n            throw std::runtime_error(\"could not init context: \" + std::string(aeron_errmsg()));\n        }\n\n        m_counter_value_buffer.fill(0);\n        m_counter_meta_buffer.fill(0);\n\n        aeron_counters_manager_init(\n            &m_counters_manager,\n            m_counter_meta_buffer.data(), m_counter_meta_buffer.size(),\n            m_counter_value_buffer.data(), m_counter_value_buffer.size(),\n            m_context->cached_clock,\n            1000);\n\n        aeron_default_name_resolver_supplier(&m_resolver, nullptr, nullptr);\n    }\n\n    ~CongestionControlTest() override\n    {\n        aeron_driver_context_close(m_context);\n        aeron_counters_manager_close(&m_counters_manager);\n        reset_env();\n    }\n\n    static void reset_env()\n    {\n        aeron_env_unset(AERON_CUBICCONGESTIONCONTROL_INITIALRTT_ENV_VAR);\n        aeron_env_unset(AERON_CUBICCONGESTIONCONTROL_TCPMODE_ENV_VAR);\n        aeron_env_unset(AERON_CUBICCONGESTIONCONTROL_MEASURERTT_ENV_VAR);\n        aeron_env_unset(AERON_CONGESTIONCONTROL_SUPPLIER_ENV_VAR);\n    }\n\n    void test_static_window_congestion_control(\n        const aeron_congestion_control_strategy_supplier_func_t func,\n        const char *channel,\n        const int32_t term_length,\n        const int32_t expected_window_length)\n    {\n        aeron_udp_channel_t *udp_channel = parse_udp_channel(channel);\n        aeron_congestion_control_strategy_t *congestion_control_strategy = nullptr;\n\n        const int result = func(\n            &congestion_control_strategy,\n            udp_channel,\n            42,\n            5,\n            11,\n            term_length,\n            1408,\n            nullptr,\n            nullptr,\n            m_context,\n            nullptr);\n\n        EXPECT_EQ(result, 0);\n        EXPECT_NE(nullptr, congestion_control_strategy);\n        void *const state = congestion_control_strategy->state;\n        EXPECT_NE(nullptr, state);\n        EXPECT_NE(nullptr, congestion_control_strategy->on_rttm_sent);\n        EXPECT_NE(nullptr, congestion_control_strategy->on_rttm);\n        EXPECT_NE(nullptr, congestion_control_strategy->should_measure_rtt);\n        EXPECT_NE(nullptr, congestion_control_strategy->initial_window_length);\n        EXPECT_NE(nullptr, congestion_control_strategy->on_track_rebuild);\n        EXPECT_NE(nullptr, congestion_control_strategy->fini);\n\n        EXPECT_FALSE(congestion_control_strategy->should_measure_rtt(state, 100LL));\n        EXPECT_EQ(expected_window_length, congestion_control_strategy->initial_window_length(state));\n\n        congestion_control_strategy->fini(congestion_control_strategy);\n    }\n\n    typedef struct counters_clientd_stct\n    {\n        const aeron_counters_manager_t *counters;\n        int32_t type_id;\n        const char *label_prefix;\n        int32_t id;\n        int64_t value;\n    }\n    counters_clientd_t;\n\n    static void filter_counters(\n        int32_t id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_length,\n        const uint8_t *label,\n        size_t label_length,\n        void *clientd)\n    {\n        auto *counters_clientd = static_cast<CongestionControlTest::counters_clientd_t *>(clientd);\n        if (counters_clientd->type_id == type_id &&\n            0 == memcmp(label, counters_clientd->label_prefix, strlen(counters_clientd->label_prefix)))\n        {\n            counters_clientd->id = id;\n            int64_t *counter_addr = aeron_counters_manager_addr(\n                (aeron_counters_manager_t *)counters_clientd->counters, id);\n            counters_clientd->value = aeron_counter_get_plain(counter_addr);\n        }\n    }\n\n    static int32_t find_counter_by_label_prefix(\n        const aeron_counters_manager_t *counters, const int32_t type_id, const char *label_prefix)\n    {\n        counters_clientd_t clientd;\n        clientd.counters = counters;\n        clientd.type_id = type_id;\n        clientd.label_prefix = label_prefix;\n        clientd.id = -1;\n        clientd.value = -1;\n\n        aeron_counters_reader_foreach_metadata(\n            counters->metadata, counters->metadata_length, filter_counters, &clientd);\n\n        return clientd.id;\n    }\n\n    aeron_udp_channel_t *parse_udp_channel(const char *channel)\n    {\n        aeron_udp_channel_t *udp_channel = nullptr;\n        int result = aeron_udp_channel_parse(strlen(channel), channel, &m_resolver, &udp_channel, false);\n        EXPECT_TRUE(0 <= result) << \" '\" << channel << \"' \" << aeron_errmsg();\n\n        m_udp_channels.push_back(udp_channel);\n        return udp_channel;\n    }\n\n    int32_t get_counter_state(int32_t counter_id) const\n    {\n        const aeron_counter_metadata_descriptor_t *metadata = (aeron_counter_metadata_descriptor_t *)\n            (m_counters_manager.metadata + (counter_id * AERON_COUNTERS_MANAGER_METADATA_LENGTH));\n        return metadata->state;\n    }\n\nprotected:\n    void TearDown() override\n    {\n        auto it = m_udp_channels.begin();\n        while (it != m_udp_channels.end())\n        {\n            auto udp_channel = *it;\n            aeron_udp_channel_delete(udp_channel);\n            it = m_udp_channels.erase(it);\n        }\n    }\n\n    aeron_driver_context_t *m_context = nullptr;\n    aeron_counters_manager_t m_counters_manager = {};\n    std::vector<aeron_udp_channel_t *> m_udp_channels;\n    aeron_name_resolver_t m_resolver = {};\n    AERON_DECL_ALIGNED(buffer_t m_counter_value_buffer, 16) = {};\n    AERON_DECL_ALIGNED(buffer_4x_t m_counter_meta_buffer, 16) = {};\n};\n\nTEST_F(CongestionControlTest, contextShouldUseDefaultCongestionControlStrategySupplier)\n{\n    EXPECT_NE(nullptr, m_context->congestion_control_supplier_func);\n}\n\nTEST_F(CongestionControlTest, contextShouldResolveCongestionControlStrategySupplierFromENV)\n{\n    aeron_driver_context_close(m_context);\n\n    aeron_env_set(\n        AERON_CONGESTIONCONTROL_SUPPLIER_ENV_VAR, \"aeron_static_window_congestion_control_strategy_supplier\");\n    EXPECT_EQ(aeron_driver_context_init(&m_context), 0);\n\n    EXPECT_NE(nullptr, m_context->congestion_control_supplier_func);\n\n    const char *channel = \"aeron:udp?endpoint=192.168.0.1:9999\\0\";\n    test_static_window_congestion_control(\n        m_context->congestion_control_supplier_func,\n        channel,\n        8192,\n        4096);\n}\n\nTEST_F(CongestionControlTest, shouldSetExplicitCongestionControlStrategySupplier)\n{\n    const aeron_congestion_control_strategy_supplier_func_t supplier =\n        &aeron_cubic_congestion_control_strategy_supplier;\n\n    aeron_driver_context_set_congestioncontrol_supplier(m_context, supplier);\n\n    EXPECT_EQ(supplier, m_context->congestion_control_supplier_func);\n    EXPECT_EQ(supplier, aeron_driver_context_get_congestioncontrol_supplier(m_context));\n}\n\nTEST_F(CongestionControlTest, shouldReturnDefaultCongestionControlStrategySupplierWhenContextIsNull)\n{\n    const aeron_congestion_control_strategy_supplier_func_t supplier =\n        aeron_driver_context_get_congestioncontrol_supplier(nullptr);\n\n    EXPECT_NE(nullptr, supplier);\n\n    const char *channel = \"aeron:udp?endpoint=192.168.0.1:9999\\0\";\n    test_static_window_congestion_control(supplier, channel, 8192, 4096);\n}\n\nTEST_F(CongestionControlTest, defaultStrategySupplierShouldChooseStaticWindowCongestionControlWhenNoCcParam)\n{\n    const char *channel = \"aeron:udp?endpoint=192.168.0.1:9999\\0\";\n    const auto initial_window_length = (int32_t)m_context->initial_window_length;\n    test_static_window_congestion_control(\n        aeron_congestion_control_default_strategy_supplier,\n        channel,\n        initial_window_length * 10,\n        initial_window_length);\n}\n\nTEST_F(CongestionControlTest, defaultStrategySupplierShouldChooseStaticWindowCongestionControlWhenCcParamIsStatic)\n{\n    const char *channel = \"aeron:udp?endpoint=192.168.0.1:9999|cc=static|rcv-wnd=65536\\0\";\n    test_static_window_congestion_control(\n        aeron_congestion_control_default_strategy_supplier,\n        channel,\n        1000000,\n        65536);\n}\n\nTEST_F(CongestionControlTest, staticWindowCongestionControlStrategySupplier)\n{\n    const char *channel = \"aeron:udp?endpoint=192.168.0.1:9999\\0\";\n    test_static_window_congestion_control(\n        aeron_static_window_congestion_control_strategy_supplier,\n        channel,\n        8192,\n        4096);\n}\n\nTEST_F(CongestionControlTest, defaultStrategySupplierShouldChooseCubicCongestionControlWhenCcParamIsCubic)\n{\n    const char *channel = \"aeron:udp?endpoint=192.168.0.1:9999|cc=cubic|rcv-wnd=65536\\0\";\n    aeron_udp_channel_t *udp_channel = parse_udp_channel(channel);\n    aeron_congestion_control_strategy_t *congestion_control_strategy = nullptr;\n\n    const int stream_id = 42;\n    const int session_id = 5;\n    const int registration_id = 11;\n    const int sender_mtu_length = 1408;\n    const int term_length = 65536 << 2;\n    const int result = aeron_congestion_control_default_strategy_supplier(\n        &congestion_control_strategy,\n        udp_channel,\n        stream_id,\n        session_id,\n        registration_id,\n        term_length,\n        sender_mtu_length,\n        nullptr,\n        nullptr,\n        m_context,\n        &m_counters_manager);\n\n    EXPECT_EQ(result, 0);\n    EXPECT_NE(nullptr, congestion_control_strategy);\n    void *const state = congestion_control_strategy->state;\n    EXPECT_NE(nullptr, state);\n    EXPECT_NE(nullptr, congestion_control_strategy->on_rttm_sent);\n    EXPECT_NE(nullptr, congestion_control_strategy->on_rttm);\n    EXPECT_NE(nullptr, congestion_control_strategy->should_measure_rtt);\n    EXPECT_NE(nullptr, congestion_control_strategy->initial_window_length);\n    EXPECT_NE(nullptr, congestion_control_strategy->on_track_rebuild);\n    EXPECT_NE(nullptr, congestion_control_strategy->fini);\n\n    const int32_t rtt_indicator_counter_id = find_counter_by_label_prefix(\n        &m_counters_manager,\n        AERON_COUNTER_PER_IMAGE_TYPE_ID,\n        AERON_CUBICCONGESTIONCONTROL_RTT_INDICATOR_COUNTER_NAME);\n    EXPECT_EQ(0, aeron_counter_get_plain(aeron_counters_manager_addr(&m_counters_manager, rtt_indicator_counter_id)));\n\n    const int32_t window_counter_id = find_counter_by_label_prefix(\n        &m_counters_manager,\n        AERON_COUNTER_PER_IMAGE_TYPE_ID,\n        AERON_CUBICCONGESTIONCONTROL_WINDOW_INDICATOR_COUNTER_NAME);\n    EXPECT_EQ(\n        sender_mtu_length * 10,\n        aeron_counter_get_plain(aeron_counters_manager_addr(&m_counters_manager, window_counter_id)));\n\n    EXPECT_FALSE(congestion_control_strategy->should_measure_rtt(state, 777LL));\n    EXPECT_EQ(sender_mtu_length * 10, congestion_control_strategy->initial_window_length(state));\n    EXPECT_EQ(\n        65536 / sender_mtu_length,\n        aeron_cubic_congestion_control_strategy_get_max_cwnd(congestion_control_strategy->state));\n\n    congestion_control_strategy->fini(congestion_control_strategy);\n}\n\nTEST_F(CongestionControlTest, defaultStrategySupplierShouldReturnNegativeResultWhenCcParamIsUnknown)\n{\n    const char *channel = \"aeron:udp?endpoint=192.168.0.1:9999|cc=static1234\\0\";\n    aeron_congestion_control_strategy_t *congestion_control_strategy = nullptr;\n    aeron_udp_channel_t *udp_channel = parse_udp_channel(channel);\n\n    const int result = aeron_congestion_control_default_strategy_supplier(\n        &congestion_control_strategy,\n        udp_channel,\n        2,\n        15,\n        1,\n        1024,\n        9000,\n        nullptr,\n        nullptr,\n        m_context,\n        nullptr);\n\n    EXPECT_EQ(-1, result);\n    EXPECT_EQ(nullptr, congestion_control_strategy);\n}\n\nTEST_F(CongestionControlTest, cubicCongestionControlSupplierReturnsNegativeValueIfInitialRttIsInvalid)\n{\n    const char *channel = \"aeron:udp?endpoint=192.168.0.1:9999\\0\";\n    aeron_congestion_control_strategy_t *congestion_control_strategy = nullptr;\n    aeron_udp_channel_t *udp_channel = parse_udp_channel(channel);\n\n    aeron_env_set(AERON_CUBICCONGESTIONCONTROL_INITIALRTT_ENV_VAR, \"initial_rtt wrong value\");\n\n    const int result = aeron_cubic_congestion_control_strategy_supplier(\n        &congestion_control_strategy,\n        udp_channel,\n        2,\n        15,\n        1,\n        1024,\n        9000,\n        nullptr,\n        nullptr,\n        m_context,\n        &m_counters_manager);\n\n    EXPECT_EQ(-1, result);\n    EXPECT_EQ(nullptr, congestion_control_strategy);\n}\n\nTEST_F(CongestionControlTest, cubicCongestionControlStrategyConfiguration)\n{\n    aeron_env_set(AERON_CUBICCONGESTIONCONTROL_TCPMODE_ENV_VAR, \"true\");\n    aeron_env_set(AERON_CUBICCONGESTIONCONTROL_MEASURERTT_ENV_VAR, \"true\");\n    aeron_env_set(AERON_CUBICCONGESTIONCONTROL_INITIALRTT_ENV_VAR, \"1s\");\n\n    const char *channel = \"aeron:udp?endpoint=192.168.0.1:9999\\0\";\n    aeron_congestion_control_strategy_t *congestion_control_strategy = nullptr;\n    aeron_udp_channel_t *udp_channel = parse_udp_channel(channel);\n\n    const int stream_id = 42;\n    const int session_id = 5;\n    const int registration_id = 11;\n    const int sender_mtu_length = 1408;\n    const int term_length = 8096;\n    const int result = aeron_cubic_congestion_control_strategy_supplier(\n        &congestion_control_strategy,\n        udp_channel,\n        stream_id,\n        session_id,\n        registration_id,\n        term_length,\n        sender_mtu_length,\n        nullptr,\n        nullptr,\n        m_context,\n        &m_counters_manager);\n\n    EXPECT_EQ(result, 0);\n    EXPECT_NE(nullptr, congestion_control_strategy);\n    void *const state = congestion_control_strategy->state;\n    EXPECT_NE(nullptr, state);\n    EXPECT_NE(nullptr, congestion_control_strategy->on_rttm_sent);\n    EXPECT_NE(nullptr, congestion_control_strategy->on_rttm);\n    EXPECT_NE(nullptr, congestion_control_strategy->should_measure_rtt);\n    EXPECT_NE(nullptr, congestion_control_strategy->initial_window_length);\n    EXPECT_NE(nullptr, congestion_control_strategy->on_track_rebuild);\n    EXPECT_NE(nullptr, congestion_control_strategy->fini);\n\n    const int32_t rtt_indicator_counter_id = find_counter_by_label_prefix(\n        &m_counters_manager,\n        AERON_COUNTER_PER_IMAGE_TYPE_ID,\n        AERON_CUBICCONGESTIONCONTROL_RTT_INDICATOR_COUNTER_NAME);\n    EXPECT_EQ(AERON_COUNTER_RECORD_ALLOCATED, get_counter_state(rtt_indicator_counter_id));\n    EXPECT_EQ(0, aeron_counter_get_plain(aeron_counters_manager_addr(&m_counters_manager, rtt_indicator_counter_id)));\n\n    const int32_t window_counter_id = find_counter_by_label_prefix(\n        &m_counters_manager,\n        AERON_COUNTER_PER_IMAGE_TYPE_ID,\n        AERON_CUBICCONGESTIONCONTROL_WINDOW_INDICATOR_COUNTER_NAME);\n    EXPECT_EQ(AERON_COUNTER_RECORD_ALLOCATED, get_counter_state(window_counter_id));\n    EXPECT_EQ(\n        sender_mtu_length * 2,\n        aeron_counter_get_plain(aeron_counters_manager_addr(&m_counters_manager, window_counter_id)));\n\n    EXPECT_TRUE(congestion_control_strategy->should_measure_rtt(state, 10000000000LL));\n\n    congestion_control_strategy->on_rttm_sent(state, 10000000000LL);\n    EXPECT_FALSE(congestion_control_strategy->should_measure_rtt(state, 10000000000LL));\n\n    congestion_control_strategy->on_rttm(state, 20000000000LL, 555LL, nullptr);\n    EXPECT_EQ(555LL, aeron_counter_get_plain(aeron_counters_manager_addr(&m_counters_manager, rtt_indicator_counter_id)));\n\n    EXPECT_TRUE(congestion_control_strategy->should_measure_rtt(state, 30000000000LL));\n\n    congestion_control_strategy->fini(congestion_control_strategy);\n\n    // assert that counters were freed\n    EXPECT_EQ(AERON_COUNTER_RECORD_RECLAIMED, get_counter_state(rtt_indicator_counter_id));\n    EXPECT_EQ(AERON_COUNTER_RECORD_RECLAIMED, get_counter_state(window_counter_id));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_counters_manager_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <cstdint>\n#include <thread>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_counters_manager.h\"\n}\n\n#define FREE_TO_REUSE_TIMEOUT_MS (1000L)\n\nclass CountersManagerTest : public testing::Test\n{\npublic:\n    CountersManagerTest() = default;\n\n    ~CountersManagerTest() override\n    {\n        aeron_counters_manager_close(&m_manager);\n    }\n\n    void SetUp() override\n    {\n        m_metadata.fill(0);\n        m_values.fill(0);\n    }\n\n    int counters_manager_init()\n    {\n        return aeron_counters_manager_init(\n            &m_manager,\n            m_metadata.data(),\n            m_metadata.size(),\n            m_values.data(),\n            m_values.size(),\n            &m_cached_clock,\n            0);\n    }\n\n    int counters_manager_with_cool_down_init()\n    {\n        return aeron_counters_manager_init(\n            &m_manager,\n            m_metadata.data(),\n            m_metadata.size(),\n            m_values.data(),\n            m_values.size(),\n            &m_cached_clock,\n            FREE_TO_REUSE_TIMEOUT_MS);\n    }\n\n    static const size_t NUM_COUNTERS = 4;\n    std::array<std::uint8_t, NUM_COUNTERS * AERON_COUNTERS_MANAGER_METADATA_LENGTH> m_metadata = {};\n    std::array<std::uint8_t, NUM_COUNTERS * AERON_COUNTERS_MANAGER_VALUE_LENGTH> m_values = {};\n    aeron_counters_manager_t m_manager = {};\n    aeron_clock_cache_t m_cached_clock = {};\n};\n\nvoid func_should_never_be_called(\n    int32_t id,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const uint8_t *label,\n    size_t label_length,\n    void *clientd)\n{\n    FAIL();\n}\n\nstatic void test_concurrent_aeron_counter_increment(\n    int num_threads, size_t iterations, std::atomic<int>& started_threads, int64_t *addr)\n{\n    started_threads++;\n    while (started_threads < num_threads)\n    {\n        std::this_thread::yield();\n    }\n\n    for (size_t j = 0; j < iterations; j++)\n    {\n        aeron_counter_increment(addr);\n    }\n}\n\nstatic void test_concurrent_aeron_counter_get_and_add(\n    int num_threads, size_t iterations, std::atomic<int>& started_threads, int64_t *addr, int64_t value)\n{\n    started_threads++;\n    while (started_threads < num_threads)\n    {\n        std::this_thread::yield();\n    }\n\n    for (size_t j = 0; j < iterations; j++)\n    {\n        aeron_counter_get_and_add(addr, value);\n    }\n}\n\nTEST_F(CountersManagerTest, shouldNotIterateOverEmptyCounters)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    aeron_counters_reader_foreach_metadata(m_metadata.data(), m_metadata.size(), func_should_never_be_called, nullptr);\n}\n\nTEST_F(CountersManagerTest, shouldEnsureAlignmentOfKeyLayout)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    size_t counter_key_header_size = (2 * sizeof(int32_t)) + sizeof(int64_t);\n    EXPECT_GE(2u * AERON_CACHE_LINE_LENGTH, sizeof(aeron_stream_position_counter_key_layout_t) + counter_key_header_size);\n    EXPECT_GE(2u * AERON_CACHE_LINE_LENGTH, sizeof(aeron_channel_endpoint_status_key_layout_t) + counter_key_header_size);\n    EXPECT_GE(2u * AERON_CACHE_LINE_LENGTH, sizeof(aeron_local_sockaddr_key_layout_t) + counter_key_header_size);\n}\n\nTEST_F(CountersManagerTest, shouldErrorOnAllocatingWhenFull)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    EXPECT_GE(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"lab0\", 4), 0);\n    EXPECT_GE(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"lab1\", 4), 0);\n    EXPECT_GE(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"lab2\", 4), 0);\n    EXPECT_GE(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"lab3\", 4), 0);\n    EXPECT_EQ(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"lab4\", 4), -1);\n}\n\nvoid func_check_and_remove_from_map(\n    int32_t id,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const uint8_t *label,\n    size_t label_length,\n    void *clientd)\n{\n    auto allocated = reinterpret_cast<std::map<int32_t, std::string> *>(clientd);\n\n    ASSERT_EQ(allocated->at(id), std::string((const char *)label, label_length));\n    allocated->erase(allocated->find(id));\n}\n\nTEST_F(CountersManagerTest, shouldAllocateIntoEmptyCounters)\n{\n    std::vector<std::string> labels = { \"lab0\", \"lab1\", \"lab2\", \"lab3\" };\n    std::map<int32_t, std::string> allocated;\n\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    for (auto &label: labels)\n    {\n        int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, label.c_str(), label.length());\n\n        ASSERT_GE(id, 0);\n        allocated[id] = label;\n    }\n\n    aeron_counters_reader_foreach_metadata(\n        m_metadata.data(), m_metadata.size(), func_check_and_remove_from_map, &allocated);\n\n    ASSERT_TRUE(allocated.empty());\n}\n\nTEST_F(CountersManagerTest, shouldRecycleCounterIdWhenFreed)\n{\n    std::vector<std::string> labels = { \"lab0\", \"lab1\", \"lab2\", \"lab3\" };\n\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    for (auto &label: labels)\n    {\n        ASSERT_GE(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, label.c_str(), label.length()), 0);\n    }\n\n    ASSERT_EQ(aeron_counters_manager_free(&m_manager, 2), 0);\n    EXPECT_EQ(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"newLab2\", 7), 2);\n}\n\nTEST_F(CountersManagerTest, shouldFreeAndReuseCounters)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    int32_t def = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"def\", 3);\n    aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"ghi\", 3);\n\n    ASSERT_EQ(aeron_counters_manager_free(&m_manager, def), 0);\n    EXPECT_EQ(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"the next label\", 14), def);\n}\n\nTEST_F(CountersManagerTest, shouldFreeAndNotReuseCountersThatHaveCoolDown)\n{\n    ASSERT_EQ(counters_manager_with_cool_down_init(), 0);\n\n    aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    int32_t def = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"def\", 3);\n    int32_t ghi = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"ghi\", 3);\n\n    ASSERT_EQ(aeron_counters_manager_free(&m_manager, def), 0);\n\n    aeron_clock_update_cached_time(&m_cached_clock, FREE_TO_REUSE_TIMEOUT_MS - 1, 0);\n    EXPECT_GT(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"the next label\", 14), ghi);\n}\n\nTEST_F(CountersManagerTest, shouldFreeAndReuseCountersAfterCoolDown)\n{\n    ASSERT_EQ(counters_manager_with_cool_down_init(), 0);\n\n    aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    int32_t def = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"def\", 3);\n    aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"ghi\", 3);\n\n    ASSERT_EQ(aeron_counters_manager_free(&m_manager, def), 0);\n\n    aeron_clock_update_cached_time(&m_cached_clock, FREE_TO_REUSE_TIMEOUT_MS, 0);\n    EXPECT_EQ(aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"the next label\", 14), def);\n}\n\nTEST_F(CountersManagerTest, shouldStoreAndLoadCounterValue)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    ASSERT_GE(id, 0);\n\n    const int64_t value = 7L;\n    int64_t *addr = aeron_counters_manager_addr(&m_manager, id);\n\n    aeron_counter_set_release(addr, value);\n    EXPECT_EQ(aeron_counter_get_plain(addr), value);\n    EXPECT_EQ(aeron_counter_get_acquire(addr), value);\n}\n\nTEST_F(CountersManagerTest, shouldIncrementValueWithVolatileSemantics)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    ASSERT_GE(id, 0);\n\n    int64_t *addr = aeron_counters_manager_addr(&m_manager, id);\n\n    int64_t initial_value = 1010101010101;\n    aeron_counter_set_release(addr, initial_value);\n    EXPECT_EQ(aeron_counter_get_plain(addr), initial_value);\n\n    const int num_threads = 3;\n    const size_t iterations = 777777;\n    std::vector<std::thread> threads;\n    std::atomic<int> started_threads(0);\n    for (int i = 0; i < num_threads; i++)\n    {\n        std::thread x(test_concurrent_aeron_counter_increment, num_threads, iterations, std::ref(started_threads), addr);\n        threads.push_back(std::move(x));\n    }\n\n    for (int i = 0; i < num_threads; i++)\n    {\n        threads[i].join();\n    }\n\n    EXPECT_EQ(aeron_counter_get_plain(addr), initial_value + (num_threads * iterations));\n}\n\nTEST_F(CountersManagerTest, shouldIncrementValueWithReleaseSemantics)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    ASSERT_GE(id, 0);\n\n    int64_t *addr = aeron_counters_manager_addr(&m_manager, id);\n\n    EXPECT_EQ(aeron_counter_get_plain(addr), 0);\n\n    EXPECT_EQ(aeron_counter_increment_release(addr), 0);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 1);\n\n    EXPECT_EQ(aeron_counter_increment_release(addr), 1);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 2);\n}\n\nTEST_F(CountersManagerTest, shouldIncrementValueWithPlainSemantics)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    ASSERT_GE(id, 0);\n\n    int64_t *addr = aeron_counters_manager_addr(&m_manager, id);\n\n    EXPECT_EQ(aeron_counter_get_plain(addr), 0);\n\n    EXPECT_EQ(aeron_counter_increment_plain(addr), 0);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 1);\n\n    EXPECT_EQ(aeron_counter_increment_plain(addr), 1);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 2);\n}\n\nTEST_F(CountersManagerTest, shouldGetAndAddValueWithVolatileSemantics)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    ASSERT_GE(id, 0);\n\n    int64_t *addr = aeron_counters_manager_addr(&m_manager, id);\n\n    int64_t initial_value = 567;\n    aeron_counter_set_release(addr, initial_value);\n    EXPECT_EQ(aeron_counter_get_plain(addr), initial_value);\n\n    const int num_threads = 2;\n    const size_t iterations = 777777;\n    std::atomic<int> started_threads(0);\n\n    const int64_t v1 = 19, v2 = 64;\n    std::thread t1(test_concurrent_aeron_counter_get_and_add, num_threads, iterations, std::ref(started_threads), addr, v1);\n    std::thread t2(test_concurrent_aeron_counter_get_and_add, num_threads, iterations, std::ref(started_threads), addr, v2);\n\n    t1.join();\n    t2.join();\n\n    EXPECT_EQ(aeron_counter_get_plain(addr), initial_value + iterations * v1 + iterations * v2);\n}\n\nTEST_F(CountersManagerTest, shouldGetAndAddValueWithReleaseSemantics)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    ASSERT_GE(id, 0);\n\n    int64_t *addr = aeron_counters_manager_addr(&m_manager, id);\n\n    EXPECT_EQ(aeron_counter_get_plain(addr), 0);\n\n    EXPECT_EQ(aeron_counter_get_and_add_release(addr, 5), 0);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 5);\n\n    EXPECT_EQ(aeron_counter_get_and_add_release(addr, -2), 5);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 3);\n\n    EXPECT_EQ(aeron_counter_get_and_add_release(addr, 10), 3);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 13);\n}\n\nTEST_F(CountersManagerTest, shouldGetAndAddValueWithPlainSemantics)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    ASSERT_GE(id, 0);\n\n    int64_t *addr = aeron_counters_manager_addr(&m_manager, id);\n\n    EXPECT_EQ(aeron_counter_get_plain(addr), 0);\n\n    EXPECT_EQ(aeron_counter_get_and_add_plain(addr, 5), 0);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 5);\n\n    EXPECT_EQ(aeron_counter_get_and_add_plain(addr, -2), 5);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 3);\n\n    EXPECT_EQ(aeron_counter_get_and_add_plain(addr, 10), 3);\n    EXPECT_EQ(aeron_counter_get_plain(addr), 13);\n}\n\nTEST_F(CountersManagerTest, shouldProposeMaxValueWithReleaseSemantics)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    ASSERT_GE(id, 0);\n\n    int64_t *addr = aeron_counters_manager_addr(&m_manager, id);\n\n    EXPECT_EQ(aeron_counter_get_plain(addr), 0);\n\n    EXPECT_TRUE(aeron_counter_propose_max_release(addr, 5));\n    EXPECT_EQ(aeron_counter_get_plain(addr), 5);\n\n    EXPECT_FALSE(aeron_counter_propose_max_release(addr, 5));\n    EXPECT_EQ(aeron_counter_get_plain(addr), 5);\n\n    EXPECT_FALSE(aeron_counter_propose_max_release(addr, -1));\n    EXPECT_EQ(aeron_counter_get_plain(addr), 5);\n\n    EXPECT_TRUE(aeron_counter_propose_max_release(addr, 100));\n    EXPECT_EQ(aeron_counter_get_plain(addr), 100);\n}\n\nTEST_F(CountersManagerTest, shouldProposeMaxValueWithPlainSemantics)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    int32_t id = aeron_counters_manager_allocate(&m_manager, 0, nullptr, 0, \"abc\", 3);\n    ASSERT_GE(id, 0);\n\n    int64_t *addr = aeron_counters_manager_addr(&m_manager, id);\n\n    EXPECT_TRUE(aeron_counter_propose_max_plain(addr, 111));\n    EXPECT_EQ(aeron_counter_get_plain(addr), 111);\n\n    EXPECT_FALSE(aeron_counter_propose_max_plain(addr, 0));\n    EXPECT_EQ(aeron_counter_get_plain(addr), 111);\n\n    EXPECT_TRUE(aeron_counter_propose_max_plain(addr, 1000));\n    EXPECT_EQ(aeron_counter_get_plain(addr), 1000);\n}\n\nstruct metadata_test_stct\n{\n    std::string label;\n    int32_t type_id;\n    int32_t counter_id;\n    int64_t key;\n};\n\nvoid func_should_store_metadata(\n    int32_t id, int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const uint8_t *label,\n    size_t label_length,\n    void *clientd)\n{\n    auto *info = reinterpret_cast<struct metadata_test_stct *>(clientd);\n    static size_t times_called = 0;\n\n    ASSERT_LT(times_called, 2u);\n\n    EXPECT_EQ(id, info[times_called].counter_id);\n    EXPECT_EQ(type_id, info[times_called].type_id);\n    EXPECT_EQ(*(int64_t *)key, info[times_called].key);\n    EXPECT_EQ(std::string((const char *)label, label_length), info[times_called].label);\n    times_called++;\n}\n\nTEST_F(CountersManagerTest, shouldStoreMetaData)\n{\n    ASSERT_EQ(counters_manager_init(), 0);\n\n    struct metadata_test_stct info[2] =\n        {\n            {\n                \"lab0\", 333, 0, 777L\n            },\n            {\n                \"lab1\", 222, 1, 444L\n            }\n        };\n\n    ASSERT_EQ(\n        aeron_counters_manager_allocate(\n            &m_manager,\n            info[0].type_id,\n            (const uint8_t *)&(info[0].key),\n            sizeof(info[0].key),\n            info[0].label.c_str(),\n            info[0].label.length()),\n        info[0].counter_id);\n\n    ASSERT_EQ(\n        aeron_counters_manager_allocate(\n            &m_manager,\n            info[1].type_id,\n            (const uint8_t *)&(info[1].key),\n            sizeof(info[1].key),\n            info[1].label.c_str(),\n            info[1].label.length()),\n        info[1].counter_id);\n\n    aeron_counters_reader_foreach_metadata(m_metadata.data(), m_metadata.size(), func_should_store_metadata, info);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_data_packet_dispatcher_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <gtest/gtest.h>\n#include \"aeron_receiver_test.h\"\n\nextern \"C\"\n{\n#include \"util/aeron_dlopen.h\"\n#include \"aeron_publication_image.h\"\n#include \"aeron_data_packet_dispatcher.h\"\n#include \"aeron_driver_receiver.h\"\n\nint aeron_driver_ensure_dir_is_recreated(aeron_driver_context_t *context);\n}\n\n#define CAPACITY (32 * 1024)\n#define TERM_BUFFER_SIZE (64 * 1024)\n#define MTU (4096)\n#define STREAM_SESSION_LIMIT (5)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\ntypedef std::array<std::uint8_t, 4 * CAPACITY> buffer_4x_t;\n\nvoid *get_on_publication_image_fptr()\n{\n    return aeron_dlsym(RTLD_DEFAULT, \"aeron_driver_conductor_on_create_publication_image\");\n}\n\nclass DataPacketDispatcherTest : public ReceiverTestBase, public ::testing::Test\n{\nprotected:\n    void SetUp() final\n    {\n        DoSetUp();\n        aeron_driver_context_set_stream_session_limit(m_context, STREAM_SESSION_LIMIT);\n\n        m_receive_endpoint = createEndpoint(\"aeron:udp?endpoint=localhost:9090\");\n        ASSERT_NE(nullptr, m_receive_endpoint) << aeron_errmsg();\n        m_destination = m_receive_endpoint->destinations.array[0].destination;\n        m_dispatcher = &m_receive_endpoint->dispatcher;\n        m_test_bindings_state =\n            static_cast<aeron_test_udp_bindings_state_t *>(m_destination->transport.bindings_clientd);\n    }\n\n    void TearDown() final\n    {\n        DoTearDown();\n    }\n\n    aeron_publication_image_t *createImage(int32_t stream_id, int32_t session_id, int64_t correlation_id = 0)\n    {\n        return ReceiverTestBase::createImage(m_receive_endpoint, m_destination, stream_id, session_id, correlation_id);\n    }\n\n    bool isEmpty(aeron_mpsc_rb_t *ring_buffer)\n    {\n        return aeron_mpsc_rb_consumer_position(ring_buffer) == aeron_mpsc_rb_producer_position(ring_buffer);\n    }\n\n    aeron_receive_channel_endpoint_t *m_receive_endpoint = nullptr;\n    aeron_receive_destination_t *m_destination = nullptr;\n    aeron_data_packet_dispatcher_t *m_dispatcher = nullptr;\n    aeron_test_udp_bindings_state_t *m_test_bindings_state = nullptr;\n};\n\nTEST_F(DataPacketDispatcherTest, shouldInsertDataInputSubscribedPublicationImage)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    aeron_publication_image_t *image = createImage(stream_id, session_id);\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n    aeron_data_header_t *data_header = dataPacket(data_buffer, stream_id, session_id);\n    size_t len = sizeof(aeron_data_header_t) + 8;\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image));\n\n    int bytes_written = aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_EQ((int)len, bytes_written);\n    ASSERT_EQ((int64_t)len, *image->rcv_hwm_position.value_addr);\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_ACTIVE, image->conductor_fields.state);\n}\n\nTEST_F(DataPacketDispatcherTest, shouldNotInsertDataInputWithNoSubscription)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    aeron_publication_image_t *image = createImage(stream_id, session_id);\n    aeron_data_header_t *data_header = dataPacket(data_buffer, stream_id, session_id);\n    size_t len = sizeof(aeron_data_header_t) + 8;\n\n    int32_t other_stream_id = stream_id + 1;\n\n    // Subscribe to a difference id\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, other_stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image));\n\n    int64_t position_before_data = *image->rcv_hwm_position.value_addr;\n    int64_t expected_position_after_data = position_before_data;\n\n    int bytes_written = aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_EQ(0, bytes_written);\n    ASSERT_EQ(expected_position_after_data, *image->rcv_hwm_position.value_addr);\n}\n\nTEST_F(DataPacketDispatcherTest, shouldElicitSetupMessageForSubscriptionWithoutImage)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    aeron_data_header_t *data_header = dataPacket(data_buffer, stream_id, session_id);\n    size_t len = sizeof(aeron_data_header_t) + 8;\n\n    // No publication added...\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data));\n\n    ASSERT_EQ(1, m_test_bindings_state->sm_count);\n\n    aeron_data_packet_dispatcher_remove_pending_setup(m_dispatcher, session_id, stream_id);\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data));\n\n    ASSERT_EQ(2, m_test_bindings_state->sm_count);\n}\n\nTEST_F(DataPacketDispatcherTest, shouldRequestCreateImageUponReceivingSetupOnceForTheSameStreamSessionId)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n\n    aeron_setup_header_t *setup_header = setupPacket(data_buffer, stream_id, session_id);\n\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_header,\n        data_buffer.data(),\n        sizeof(*setup_header),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_header,\n        data_buffer.data(),\n        sizeof(*setup_header),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_header,\n        data_buffer.data(),\n        sizeof(*setup_header),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_EQ(UINT64_C(1), aeron_mpsc_rb_read(\n        m_conductor_proxy.command_queue,\n        verify_conductor_cmd_function,\n        get_on_publication_image_fptr(),\n        3));\n}\n\nTEST_F(DataPacketDispatcherTest, shouldRequestCreateImageUponReceivingSetupMultipleTimeForDifferentSessionIds)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t stream_id = 434523;\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n\n    aeron_setup_header_t *setup_header_1 = setupPacket(data_buffer, stream_id, 1001);\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_header_1,\n        data_buffer.data(),\n        sizeof(*setup_header_1),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    aeron_setup_header_t *setup_header_2 = setupPacket(data_buffer, stream_id, 1002);\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_header_2,\n        data_buffer.data(),\n        sizeof(*setup_header_1),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    aeron_setup_header_t *setup_header_3 = setupPacket(data_buffer, stream_id, 1003);\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_header_3,\n        data_buffer.data(),\n        sizeof(*setup_header_1),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_EQ(UINT64_C(3), aeron_mpsc_rb_read(\n        m_conductor_proxy.command_queue,\n        verify_conductor_cmd_function,\n        get_on_publication_image_fptr(),\n        3));\n}\n\nTEST_F(DataPacketDispatcherTest, DISABLED_shouldSetImageInactiveOnRemoveSubscription)\n{\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    aeron_publication_image_t *image = createImage(stream_id, session_id);\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_remove_subscription(m_dispatcher, stream_id));\n\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_DRAINING, image->conductor_fields.state);\n}\n\nTEST_F(DataPacketDispatcherTest, DISABLED_shouldSetImageInactiveOnRemoveImage)\n{\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    aeron_publication_image_t *image = createImage(stream_id, session_id);\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_remove_publication_image(m_dispatcher, image));\n\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_DRAINING, image->conductor_fields.state);\n}\n\nTEST_F(DataPacketDispatcherTest, shouldIgnoreDataAndSetupAfterImageRemoved)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    aeron_publication_image_t *image = createImage(stream_id, session_id);\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_remove_publication_image(m_dispatcher, image));\n\n\n    aeron_data_header_t *data_header = dataPacket(data_buffer, stream_id, session_id);\n    size_t len = sizeof(aeron_data_header_t) + 8;\n    aeron_setup_header_t *setup_header = setupPacket(data_buffer, stream_id, session_id);\n\n    aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n    ASSERT_TRUE(isEmpty(m_conductor_proxy.command_queue));\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_header,\n        data_buffer.data(),\n        sizeof(*setup_header),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_EQ(0, m_test_bindings_state->msg_count + m_test_bindings_state->mmsg_count);\n    ASSERT_TRUE(isEmpty(m_conductor_proxy.command_queue));\n}\n\nTEST_F(DataPacketDispatcherTest, shouldNotIgnoreDataAndSetupAfterImageRemovedAndCoolDownRemoved)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    aeron_publication_image_t *image = createImage(stream_id, session_id);\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_remove_publication_image(m_dispatcher, image));\n    aeron_data_packet_dispatcher_remove_cool_down(m_dispatcher, session_id, stream_id);\n\n    aeron_data_header_t *data_header = dataPacket(data_buffer, stream_id, session_id);\n    size_t len = sizeof(aeron_data_header_t) + 8;\n    aeron_setup_header_t *setup_header = setupPacket(data_buffer, stream_id, session_id);\n\n    aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        setup_header,\n        data_buffer.data(),\n        sizeof(*setup_header),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    EXPECT_EQ(1, m_test_bindings_state->sm_count);\n    ASSERT_EQ(UINT64_C(1), aeron_mpsc_rb_read(\n        m_conductor_proxy.command_queue,\n        verify_conductor_cmd_function,\n        get_on_publication_image_fptr(),\n        1));\n}\n\nTEST_F(DataPacketDispatcherTest, shouldNotRemoveNewPublicationImageFromOldRemovePublicationImageAfterRemoveSubscription)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    aeron_publication_image_t *image1 = createImage(stream_id, session_id, 0);\n    aeron_publication_image_t *image2 = createImage(stream_id, session_id, 1);\n\n    aeron_data_header_t *data_header = dataPacket(data_buffer, stream_id, session_id);\n    size_t len = sizeof(aeron_data_header_t) + 8;\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image1));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_remove_subscription(m_dispatcher, stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image2));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_remove_publication_image(m_dispatcher, image1));\n\n    int bytes_written = aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_EQ((int)len, bytes_written);\n    ASSERT_EQ((int64_t)len, *image2->rcv_hwm_position.value_addr);\n}\n\nTEST_F(DataPacketDispatcherTest, shouldAddSessionSpecificSubscriptionAndIgnoreOtherSession)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id1 = 123123;\n    int32_t session_id2 = session_id1 + 1;\n    int32_t stream_id = 434523;\n\n    aeron_setup_header_t *setup_session1 = setupPacket(data_buffer, stream_id, session_id1);\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription_by_session(m_dispatcher, stream_id, session_id1));\n\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_session1,\n        data_buffer.data(),\n        sizeof(*setup_session1),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_EQ((size_t)1, aeron_mpsc_rb_read(\n        m_conductor_proxy.command_queue,\n        verify_conductor_cmd_function,\n        get_on_publication_image_fptr(),\n        1));\n\n    ASSERT_TRUE(isEmpty(m_conductor_proxy.command_queue));\n\n    aeron_setup_header_t *setup_session2_ignored = setupPacket(data_buffer, stream_id, session_id2);\n    aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_session2_ignored,\n        data_buffer.data(),\n        sizeof(*setup_session2_ignored),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_TRUE(isEmpty(m_conductor_proxy.command_queue));\n}\n\nTEST_F(DataPacketDispatcherTest, shouldRemoveSessionSpecificSubscriptionAndStillReceiveIntoImage)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id = 123123;\n    int32_t stream_id = 434523;\n\n    aeron_publication_image_t *image = createImage(stream_id, session_id);\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n    aeron_data_header_t *data_header = dataPacket(data_buffer, stream_id, session_id);\n    size_t len = sizeof(aeron_data_header_t) + 8;\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, stream_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription_by_session(m_dispatcher, stream_id, session_id));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_remove_subscription_by_session(m_dispatcher, stream_id, session_id));\n\n    int bytes_written = aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_EQ((int)len, bytes_written);\n    ASSERT_EQ((int64_t)len, *image->rcv_hwm_position.value_addr);\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_ACTIVE, image->conductor_fields.state);\n}\n\nTEST_F(DataPacketDispatcherTest, shouldNotRemoveStreamInterestOnRemovalOfSessionSpecificSubscriptionIfNoImage)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t session_id_1 = 123123;\n    int32_t session_id_2 = 123124;\n    int32_t stream_id = 434523;\n\n    aeron_publication_image_t *image = createImage(stream_id, session_id_2);\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n    aeron_data_header_t *data_header = dataPacket(data_buffer, stream_id, session_id_2);\n    size_t len = sizeof(aeron_data_header_t) + 8;\n\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription_by_session(m_dispatcher, stream_id, session_id_1));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription_by_session(m_dispatcher, stream_id, session_id_2));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_remove_subscription_by_session(m_dispatcher, stream_id, session_id_1));\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image));\n\n    int bytes_written = aeron_data_packet_dispatcher_on_data(\n        m_dispatcher,\n        m_receive_endpoint,\n        m_destination,\n        data_header,\n        data_buffer.data(),\n        len,\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data);\n\n    ASSERT_EQ((int)len, bytes_written);\n    ASSERT_EQ((int64_t)len, *image->rcv_hwm_position.value_addr);\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_ACTIVE, image->conductor_fields.state);\n}\n\nTEST_F(DataPacketDispatcherTest, shouldPreventNewSessionsOnceStreamSessionLimitIsExceeded)\n{\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16);\n\n    int32_t streamId = 434523;\n    ASSERT_EQ(0, aeron_data_packet_dispatcher_add_subscription(m_dispatcher, streamId));\n\n    for (int i = 0; i < STREAM_SESSION_LIMIT; i++)\n    {\n        int32_t sessionId = 1000 + i;\n\n        aeron_setup_header_t *setup_header = setupPacket(data_buffer, streamId, sessionId);\n\n        ASSERT_EQ(0, aeron_data_packet_dispatcher_on_setup(\n            m_dispatcher,\n            m_receive_endpoint,\n            nullptr,\n            setup_header,\n            data_buffer.data(),\n            sizeof(*setup_header),\n            &m_receive_endpoint->conductor_fields.udp_channel->local_data));\n\n        aeron_publication_image_t *image = createImage(streamId, sessionId, 2000 + i);\n        ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n        ASSERT_EQ(0, aeron_data_packet_dispatcher_add_publication_image(m_dispatcher, image));\n    }\n\n    aeron_setup_header_t *setup_header = setupPacket(data_buffer, streamId, 10000, 20000);\n\n    ASSERT_EQ(-1, aeron_data_packet_dispatcher_on_setup(\n        m_dispatcher,\n        m_receive_endpoint,\n        nullptr,\n        setup_header,\n        data_buffer.data(),\n        sizeof(*setup_header),\n        &m_receive_endpoint->conductor_fields.udp_channel->local_data));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_conductor_clock_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_driver_conductor_test.h\"\n\nclass DriverConductorClockTest : public DriverConductorTest, public testing::Test\n{\n};\n\nTEST_F(DriverConductorClockTest, shouldUpdateCachedClockOnMsBoundary)\n{\n    doWork();\n    int64_t initial_nano_time = aeron_clock_cached_nano_time(m_conductor.m_conductor.context->cached_clock);\n    int64_t initial_epoch_time = aeron_clock_cached_epoch_time(m_conductor.m_conductor.context->cached_clock);\n    ASSERT_NE(0, m_conductor.m_conductor.clock_update_deadline_ns);\n    int64_t half_increment_for_clock_update =\n        (m_conductor.m_conductor.clock_update_deadline_ns - initial_nano_time) / 2;\n\n    test_increment_nano_time(half_increment_for_clock_update);\n    doWork();\n    ASSERT_EQ(initial_nano_time + half_increment_for_clock_update,\n        aeron_clock_cached_nano_time(m_conductor.m_conductor.context->cached_clock));\n    ASSERT_EQ(initial_epoch_time, aeron_clock_cached_epoch_time(m_conductor.m_conductor.context->cached_clock));\n\n    test_increment_nano_time(half_increment_for_clock_update);\n    doWork();\n    ASSERT_LE(\n        initial_nano_time + AERON_DRIVER_CONDUCTOR_CLOCK_UPDATE_INTERNAL_NS,\n        aeron_clock_cached_nano_time(m_conductor.m_conductor.context->cached_clock));\n    ASSERT_LT(\n        initial_epoch_time,\n        aeron_clock_cached_epoch_time(m_conductor.m_conductor.context->cached_clock));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_conductor_config_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_driver_conductor_test.h\"\n\nusing testing::_;\nusing testing::HasSubstr;\n\nclass DriverConductorConfigTest : public DriverConductorTest, public testing::Test\n{\n};\n\nTEST_F(DriverConductorConfigTest, shouldRejectResponsePublicationDestination)\n{\n    addPublication(1234, 1234, \"aeron:udp?control=localhost:4000|control-mode=manual\", 1234, false);\n    doWork();\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    addDestination(1234, 1234, 1234, \"aeron:udp?endpoint=localhost:8001|control-mode=response\");\n    doWork();\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _))\n    .WillOnce(\n        [&](std::int32_t msgTypeId, uint8_t *buffer, size_t length)\n        {\n            const aeron_error_response_t *msg = reinterpret_cast<aeron_error_response_t *>(buffer);\n            char *msg_buf = (char *)malloc(msg->error_message_length + 1);\n\n            memset(msg_buf, 0, msg->error_message_length + 1);\n            memcpy(msg_buf, buffer + sizeof(aeron_error_response_t), msg->error_message_length);\n\n            EXPECT_THAT(msg_buf, HasSubstr(AERON_UDP_CHANNEL_CONTROL_MODE_KEY));\n            EXPECT_THAT(msg_buf, HasSubstr(AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE));\n\n            free(msg_buf);\n        });\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    addDestination(1234, 1234, 1234, \"aeron:udp?endpoint=localhost:8001|response-correlation-id=2345\");\n    doWork();\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _))\n        .WillOnce(\n            [&](std::int32_t msgTypeId, uint8_t *buffer, size_t length)\n            {\n                const aeron_error_response_t *msg = reinterpret_cast<aeron_error_response_t *>(buffer);\n                char *msg_buf = (char *)malloc(msg->error_message_length + 1);\n\n                memset(msg_buf, 0, msg->error_message_length + 1);\n                memcpy(msg_buf, buffer + sizeof(aeron_error_response_t), msg->error_message_length);\n\n                EXPECT_THAT(msg_buf, HasSubstr(AERON_URI_RESPONSE_CORRELATION_ID_KEY));\n\n                free(msg_buf);\n            });\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorConfigTest, shouldRejectResponseSubscriptionDestination)\n{\n    addNetworkSubscription(1234, 1234, \"aeron:udp?control-mode=manual\", 1234);\n    doWork();\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    addReceiveDestination(1234, 1234, 1234, \"aeron:udp?endpoint=localhost:8001|control-mode=response\");\n    doWork();\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _))\n        .WillOnce(\n            [&](std::int32_t msgTypeId, uint8_t *buffer, size_t length)\n            {\n                const aeron_error_response_t *msg = reinterpret_cast<aeron_error_response_t *>(buffer);\n                char *msg_buf = (char *)malloc(msg->error_message_length + 1);\n\n                memset(msg_buf, 0, msg->error_message_length + 1);\n                memcpy(msg_buf, buffer + sizeof(aeron_error_response_t), msg->error_message_length);\n\n                EXPECT_THAT(msg_buf, HasSubstr(AERON_UDP_CHANNEL_CONTROL_MODE_KEY));\n                EXPECT_THAT(msg_buf, HasSubstr(AERON_UDP_CHANNEL_CONTROL_MODE_RESPONSE_VALUE));\n\n                free(msg_buf);\n            });\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    addReceiveDestination(1234, 1234, 1234, \"aeron:udp?endpoint=localhost:8001|response-correlation-id=2345\");\n    doWork();\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _))\n        .WillOnce(\n            [&](std::int32_t msgTypeId, uint8_t *buffer, size_t length)\n            {\n                const aeron_error_response_t *msg = reinterpret_cast<aeron_error_response_t *>(buffer);\n                char *msg_buf = (char *)malloc(msg->error_message_length + 1);\n\n                memset(msg_buf, 0, msg->error_message_length + 1);\n                memcpy(msg_buf, buffer + sizeof(aeron_error_response_t), msg->error_message_length);\n\n                EXPECT_THAT(msg_buf, HasSubstr(AERON_URI_RESPONSE_CORRELATION_ID_KEY));\n\n                free(msg_buf);\n            });\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_conductor_counter_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_driver_conductor_test.h\"\n\n#define COUNTER_LABEL \"counter label\"\n#define COUNTER_TYPE_ID (102)\n#define COUNTER_KEY_LENGTH (sizeof(int64_t) + 3)\n\nusing testing::_;\n\nclass DriverConductorCounterTest : public DriverConductorTest, public testing::Test\n{\npublic:\n\nprotected:\n\n    std::string m_label = COUNTER_LABEL;\n    uint8_t m_key[COUNTER_KEY_LENGTH] = { 0 };\n    size_t m_key_length = COUNTER_KEY_LENGTH;\n};\n\nTEST_F(DriverConductorCounterTest, shouldBeAbleToAddSingleCounter)\n{\n    int64_t client_id = 66;\n    int64_t reg_id = nextCorrelationId();\n\n    memcpy(m_key, &reg_id, sizeof(reg_id));\n    ASSERT_EQ(addCounter(client_id, reg_id, COUNTER_TYPE_ID, m_key, m_key_length, m_label), 0);\n    doWork();\n\n    int32_t client_counter_id;\n    int32_t counter_id;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _))\n        .With(IsCounterUpdate(client_id, _))\n        .WillOnce(CaptureCounterId(&client_counter_id));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _))\n        .With(IsCounterUpdate(reg_id, _))\n        .WillOnce(CaptureCounterId(&counter_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    EXPECT_CALL(m_mockCallbacks, onCounter(_, _, _, _, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, onCounter(_, COUNTER_TYPE_ID, _, _, _, _)).\n        With(IsIdCounter(reg_id, m_label));\n    EXPECT_CALL(m_mockCallbacks, onCounter(_, AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID, _, _, _, _)).\n        With(IsIdCounter(client_id, std::string(\"client-heartbeat: id=66\")));\n    readCounters(mock_counter_handler);\n}\n\nTEST_F(DriverConductorCounterTest, shouldRemoveSingleCounter)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t reg_id = nextCorrelationId();\n\n    ASSERT_EQ(addCounter(client_id, reg_id, COUNTER_TYPE_ID, m_key, m_key_length, m_label), 0);\n    doWork();\n\n    int32_t counter_id;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _))\n        .With(IsCounterUpdate(reg_id, _))\n        .WillOnce(CaptureCounterId(&counter_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    int64_t remove_correlation_id = nextCorrelationId();\n    ASSERT_EQ(removeCounter(client_id, remove_correlation_id, reg_id), 0);\n    doWork();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_COUNTER, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(remove_correlation_id));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_COUNTER, _, _))\n        .With(IsCounterUpdate(reg_id, counter_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorCounterTest, shouldRemoveCounterOnClientTimeout)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t reg_id = nextCorrelationId();\n\n    ASSERT_EQ(addCounter(client_id, reg_id, COUNTER_TYPE_ID, m_key, m_key_length, m_label), 0);\n    doWork();\n\n    int32_t counter_id;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _))\n        .With(IsCounterUpdate(reg_id, _))\n        .WillOnce(CaptureCounterId(&counter_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    doWorkForNs((int64_t)(m_context.m_context->client_liveness_timeout_ns * 2));\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 0u);\n\n    EXPECT_CALL(m_mockCallbacks, onCounter(_, _, _, _, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, onCounter(counter_id, _, _, _, _, _)).Times(0);\n    readCounters(mock_counter_handler);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_CLIENT_TIMEOUT, _, _))\n        .With(IsTimeout(client_id));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_COUNTER, _, _))\n        .With(IsCounterUpdate(reg_id, counter_id));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_COUNTER, _, _))\n        .With(IsCounterUpdate(client_id, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorCounterTest, shouldRemoveMultipleCountersOnClientTimeout)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t reg_id1 = nextCorrelationId();\n    int64_t reg_id2 = nextCorrelationId();\n    int total_client_plus_custom_counters = 3;\n\n    ASSERT_EQ(addCounter(client_id, reg_id1, COUNTER_TYPE_ID, m_key, m_key_length, m_label), 0);\n    ASSERT_EQ(addCounter(client_id, reg_id2, COUNTER_TYPE_ID, m_key, m_key_length, m_label), 0);\n    doWork();\n    doWork();\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    doWorkForNs((int64_t)(m_context.m_context->client_liveness_timeout_ns * 2));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_CLIENT_TIMEOUT, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_COUNTER, _, _))\n        .Times(total_client_plus_custom_counters);\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorCounterTest, shouldRemoveMultipleCountersOnClientClose)\n{\n    test_set_nano_time(INT64_C(100) * 1000 * 1000 * 1000);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t reg_id1 = nextCorrelationId();\n    int64_t reg_id2 = nextCorrelationId();\n    int total_client_plus_custom_counters = 3;\n\n    ASSERT_EQ(addCounter(client_id, reg_id1, COUNTER_TYPE_ID, m_key, m_key_length, m_label), 0);\n    ASSERT_EQ(addCounter(client_id, reg_id2, COUNTER_TYPE_ID, m_key, m_key_length, m_label), 0);\n    doWork();\n\n    closeClient(client_id);\n    doWork();\n    doWorkForNs((int64_t)(m_context.m_context->timer_interval_ns));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _))\n        .Times(total_client_plus_custom_counters);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_COUNTER, _, _))\n        .Times(total_client_plus_custom_counters);\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorCounterTest, shouldNotRemoveCounterOnClientKeepalive)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t reg_id = nextCorrelationId();\n\n    ASSERT_EQ(addCounter(client_id, reg_id, COUNTER_TYPE_ID, m_key, m_key_length, m_label), 0);\n    doWork();\n\n    int32_t counter_id;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _))\n        .With(IsCounterUpdate(reg_id, _))\n        .WillOnce(CaptureCounterId(&counter_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    int64_t timeout = (int64_t)m_context.m_context->client_liveness_timeout_ns * 2;\n\n    doWorkForNs(\n        timeout,\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 1u);\n\n    EXPECT_CALL(m_mockCallbacks, onCounter(_, _, _, _, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, onCounter(counter_id, _, _, _, _, _)).Times(1);\n    readCounters(mock_counter_handler);\n}\n\nTEST_F(DriverConductorCounterTest, shouldIncrementCounterOnConductorThresholdExceeded)\n{\n    volatile int64_t *maxCycleTimeCounter = aeron_counters_manager_addr(\n        &m_conductor.m_conductor.counters_manager, AERON_SYSTEM_COUNTER_CONDUCTOR_MAX_CYCLE_TIME);\n    volatile int64_t *thresholdExceededCounter = aeron_counters_manager_addr(\n        &m_conductor.m_conductor.counters_manager, AERON_SYSTEM_COUNTER_CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED);\n\n    nano_time = 0;\n    doWork();\n    nano_time = INT64_C(750) * 1000 * 1000;\n    doWork();\n    nano_time = INT64_C(1750) * 1000 * 1000;\n    doWork();\n    nano_time = INT64_C(2250) * 1000 * 1000;\n    doWork();\n    nano_time = INT64_C(2850) * 1000 * 1000;\n    doWork();\n    nano_time = INT64_C(3451) * 1000 * 1000;\n    doWork();\n\n    int64_t maxCycleTime;\n    AERON_GET_ACQUIRE(maxCycleTime, *maxCycleTimeCounter);\n\n    ASSERT_EQ(1000 * 1000 * 1000, maxCycleTime);\n\n    int64_t thresholdExceeded;\n    AERON_GET_ACQUIRE(thresholdExceeded, *thresholdExceededCounter);\n\n    ASSERT_EQ(3, thresholdExceeded);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_conductor_ipc_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_driver_conductor_test.h\"\n\nusing testing::_;\n\nclass DriverConductorIpcTest : public DriverConductorTest, public testing::Test\n{\nprotected:\n    inline size_t subscriberCount(aeron_ipc_publication_t *publication)\n    {\n        return publication->conductor_fields.subscribable.length;\n    }\n};\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldBeAbleToAddSingleIpcSubscriptionThenAddSingleIpcPublication)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addIpcPublication(client_id, pub_id, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_ipc_publication_t *publication = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, session_id, log_file_name.c_str(), AERON_IPC_CHANNEL));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldBeAbleToAddSingleIpcPublicationThenAddSingleIpcSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addIpcPublication(client_id, pub_id, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id, STREAM_ID_1, -1), 0);\n    doWorkUntilDone();\n\n    aeron_ipc_publication_t *publication = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, session_id, log_file_name.c_str(), AERON_IPC_CHANNEL));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldBeAbleToAddMultipleIpcSubscriptionWithSameStreamIdThenAddSingleIpcPublication)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id_2, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addIpcPublication(client_id, pub_id, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_ipc_publication_t *publication = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 2u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence s1, s2;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id_1))\n        .InSequence(s1);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id_2))\n        .InSequence(s2);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(s1, s2)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 4);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id_1, STREAM_ID_1, session_id, log_file_name.c_str(), AERON_IPC_CHANNEL));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id_2, STREAM_ID_1, session_id, log_file_name.c_str(), AERON_IPC_CHANNEL));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldAddSingleIpcSubscriptionThenAddMultipleExclusiveIpcPublicationsWithSameStreamId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addIpcPublication(client_id, pub_id_1, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addIpcPublication(client_id, pub_id_2, STREAM_ID_1, true), 0);\n    doWorkUntilDone();\n\n    aeron_ipc_publication_t *publication_1 = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id_1);\n    EXPECT_EQ(subscriberCount(publication_1), 1u);\n    aeron_ipc_publication_t *publication_2 = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id_2);\n    EXPECT_EQ(subscriberCount(publication_2), 1u);\n\n    int32_t session_id_1 = 0;\n    int32_t session_id_2 = 0;\n    std::string log_file_name_1;\n    std::string log_file_name_2;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY, _, _))\n        .WillOnce(CapturePublicationReady(&session_id_1, &log_file_name_1));\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, session_id_1, log_file_name_1.c_str(), AERON_IPC_CHANNEL));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY, _, _))\n        .WillOnce(CapturePublicationReady(&session_id_2, &log_file_name_2));\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 2);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, session_id_2, log_file_name_2.c_str(), AERON_IPC_CHANNEL));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldNotLinkSubscriptionOnAddPublicationAfterFirstAddPublication)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addIpcPublication(client_id, pub_id_1, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addIpcPublication(client_id, pub_id_2, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_ipc_publication_t *publication = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id_1);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_ipc_subscriptions(&m_conductor.m_conductor, STREAM_ID_1), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, session_id, log_file_name.c_str(), AERON_IPC_CHANNEL));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .With(IsPubReadyWithFile(pub_id_1, pub_id_2, STREAM_ID_1, session_id, log_file_name));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\n// TODO: Paramterise\nTEST_F(DriverConductorIpcTest, shouldBeAbleToTimeoutMultipleIpcSubscriptions)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id1 = nextCorrelationId();\n    int64_t sub_id2 = nextCorrelationId();\n    int64_t sub_id3 = nextCorrelationId();\n\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id1, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id2, STREAM_ID_2, false), 0);\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id3, STREAM_ID_3, false), 0);\n\n    doWorkUntilDone();\n\n    EXPECT_EQ(aeron_driver_conductor_num_ipc_subscriptions(&m_conductor.m_conductor), 3u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    uint64_t durationNs =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n    doWorkForNs(static_cast<int64_t>(durationNs));\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_ipc_subscriptions(&m_conductor.m_conductor), 0u);\n}\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldBeAbleToTimeoutIpcPublicationWithActiveIpcSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addIpcPublication(client_id, pub_id, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addIpcSubscription(client_id, sub_id, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    ASSERT_EQ(removePublication(client_id, remove_correlation_id, pub_id), 0);\n    doWorkUntilDone();\n\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    auto timeout = static_cast<int64_t>(m_context.m_context->publication_linger_timeout_ns * 2);\n\n    doWorkForNs(\n        timeout,\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_ipc_publications(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_ipc_subscriptions(&m_conductor.m_conductor, STREAM_ID_1), 0u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_IMAGE, _, _))\n        .With(IsUnavailableImage(STREAM_ID_1, pub_id, sub_id, AERON_IPC_CHANNEL));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldAddIpcPublicationThenSubscriptionWithSessionId)\n{\n    const int64_t client_id = nextCorrelationId();\n    const int64_t pub_id = nextCorrelationId();\n    const int64_t sub_id = nextCorrelationId();\n    const int32_t ipc_session_id = -4097;\n    std::string channel =\n        std::string(AERON_IPC_CHANNEL) + \"?\" + std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(ipc_session_id);\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addSubscription(client_id, sub_id, channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_ipc_publication_t *publication = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, session_id, log_file_name.c_str(), AERON_IPC_CHANNEL));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldAddIpcSubscriptionThenPublicationWithSessionId)\n{\n    const int64_t client_id = nextCorrelationId();\n    const int64_t pub_id = nextCorrelationId();\n    const int64_t sub_id = nextCorrelationId();\n    const int32_t ipc_session_id = -4097;\n    std::string channel =\n        std::string(AERON_IPC_CHANNEL) + \"?\" + std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(ipc_session_id);\n\n    ASSERT_EQ(addSubscription(client_id, sub_id, channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id, channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_ipc_publication_t *publication = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, session_id, log_file_name.c_str(), AERON_IPC_CHANNEL));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldNotAddIpcPublicationThenSubscriptionWithDifferentSessionId)\n{\n    const int64_t client_id = nextCorrelationId();\n    const int64_t pub_id = nextCorrelationId();\n    const int64_t sub_id = nextCorrelationId();\n    const int32_t pub_session_id = -4097;\n    const int32_t sub_session_id = -4098;\n    std::string pub_channel =\n        std::string(AERON_IPC_CHANNEL) + \"?\" + std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(pub_session_id);\n    std::string sub_channel =\n        std::string(AERON_IPC_CHANNEL) + \"?\" + std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(sub_session_id);\n\n    ASSERT_EQ(addPublication(client_id, pub_id, pub_channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addSubscription(client_id, sub_id, sub_channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_ipc_publication_t *publication = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 0u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, pub_session_id, log_file_name.c_str(), AERON_IPC_CHANNEL))\n        .Times(0);\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\n// TODO: Parameterise\nTEST_F(DriverConductorIpcTest, shouldNotAddIpcSubscriptionThenPublicationWithDifferentSessionId)\n{\n    const int64_t client_id = nextCorrelationId();\n    const int64_t pub_id = nextCorrelationId();\n    const int64_t sub_id = nextCorrelationId();\n    const int32_t pub_session_id = -4097;\n    const int32_t sub_session_id = -4098;\n    std::string pub_channel =\n        std::string(AERON_IPC_CHANNEL) + \"?\" + std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(pub_session_id);\n    std::string sub_channel =\n        std::string(AERON_IPC_CHANNEL) + \"?\" + std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(sub_session_id);\n\n    ASSERT_EQ(addSubscription(client_id, sub_id, sub_channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id, pub_channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_ipc_publication_t *publication = aeron_driver_conductor_find_ipc_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 0u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, pub_session_id, log_file_name.c_str(), AERON_IPC_CHANNEL))\n        .Times(0);\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_conductor_network_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cinttypes>\n#include \"aeron_driver_conductor_test.h\"\n\nusing testing::_;\nusing testing::Eq;\nusing testing::Ne;\nusing testing::Args;\nusing testing::Mock;\nusing testing::AnyNumber;\n\nclass DriverConductorNetworkTest : public DriverConductorTest, public testing::Test\n{\nprotected:\n    aeron_publication_image_t *aeron_driver_conductor_find_publication_image(\n        aeron_driver_conductor_t *conductor, aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id)\n    {\n        for (size_t i = 0, length = conductor->publication_images.length; i < length; i++)\n        {\n            aeron_publication_image_t *image = conductor->publication_images.array[i].image;\n\n            if (endpoint == image->endpoint && stream_id == image->stream_id)\n            {\n                return image;\n            }\n        }\n\n        return nullptr;\n    }\n\n    inline size_t aeron_publication_image_subscriber_count(aeron_publication_image_t *image)\n    {\n        return image->conductor_fields.subscribable.length;\n    }\n};\n\nTEST_F(DriverConductorNetworkTest, shouldBeAbleToAddMultipleNetworkSubscriptionsWithDifferentChannelSameStreamId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n    int64_t sub_id_3 = nextCorrelationId();\n    int64_t sub_id_4 = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_1, CHANNEL_1, STREAM_ID_1), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_2, CHANNEL_2, STREAM_ID_1), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_3, CHANNEL_3, STREAM_ID_1), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_4, CHANNEL_4, STREAM_ID_1), 0);\n\n    doWorkUntilDone();\n\n    aeron_receive_channel_endpoint_t *endpoint_1 = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n    aeron_receive_channel_endpoint_t *endpoint_2 = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_2);\n    aeron_receive_channel_endpoint_t *endpoint_3 = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_3);\n    aeron_receive_channel_endpoint_t *endpoint_4 = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_4);\n\n    ASSERT_NE(endpoint_1, (aeron_receive_channel_endpoint_t *)nullptr);\n    ASSERT_NE(endpoint_2, (aeron_receive_channel_endpoint_t *)nullptr);\n    ASSERT_NE(endpoint_3, (aeron_receive_channel_endpoint_t *)nullptr);\n    ASSERT_NE(endpoint_4, (aeron_receive_channel_endpoint_t *)nullptr);\n    ASSERT_EQ(aeron_driver_conductor_num_receive_channel_endpoints(&m_conductor.m_conductor), 4u);\n    ASSERT_EQ(aeron_driver_conductor_num_network_subscriptions(&m_conductor.m_conductor), 4u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id_1));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id_2));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id_3));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id_4));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldKeepSubscriptionMediaEndpointUponRemovalOfAllButOneSubscriber)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n    int64_t sub_id_3 = nextCorrelationId();\n    int64_t sub_id_4 = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_1, CHANNEL_1, STREAM_ID_1), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_2, CHANNEL_1, STREAM_ID_2), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_3, CHANNEL_1, STREAM_ID_3), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_4, CHANNEL_1, STREAM_ID_4), 0);\n\n    doWorkUntilDone();\n\n    int64_t remove_correlation_id_1 = nextCorrelationId();\n    int64_t remove_correlation_id_2 = nextCorrelationId();\n    int64_t remove_correlation_id_3 = nextCorrelationId();\n\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id_1, sub_id_1), 0);\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id_2, sub_id_2), 0);\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id_3, sub_id_3), 0);\n\n    doWorkUntilDone();\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    ASSERT_NE(endpoint, (aeron_receive_channel_endpoint_t *)nullptr);\n    ASSERT_EQ(aeron_driver_conductor_num_receive_channel_endpoints(&m_conductor.m_conductor), 1u);\n    ASSERT_EQ(aeron_driver_conductor_num_network_subscriptions(&m_conductor.m_conductor), 1u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _)).Times(4);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _)).Times(3);\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldCreatePublicationImageForActiveNetworkSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    createPublicationImage(endpoint, STREAM_ID_1, 1000);\n\n    EXPECT_EQ(aeron_driver_conductor_num_images(&m_conductor.m_conductor), 1u);\n\n    aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image(\n        &m_conductor.m_conductor, endpoint, STREAM_ID_1);\n\n    EXPECT_NE(image, (aeron_publication_image_t *)nullptr);\n    EXPECT_EQ(aeron_publication_image_subscriber_count(image), 1u);\n\n    int64_t image_registration_id = aeron_publication_image_registration_id(image);\n    const char *log_file_name = aeron_publication_image_log_file_name(image);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(image_registration_id, sub_id, STREAM_ID_1, SESSION_ID, log_file_name, SOURCE_IDENTITY));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldNotCreatePublicationImageForNonActiveNetworkSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    createPublicationImage(endpoint, STREAM_ID_2, 1000);\n\n    EXPECT_EQ(aeron_driver_conductor_num_images(&m_conductor.m_conductor), 0u);\n\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldRemoveSubscriptionFromImageWhenRemoveSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    createPublicationImage(endpoint, STREAM_ID_1, 1000);\n\n    EXPECT_EQ(aeron_driver_conductor_num_images(&m_conductor.m_conductor), 1u);\n\n    aeron_publication_image_t *image =\n        aeron_driver_conductor_find_publication_image(&m_conductor.m_conductor, endpoint, STREAM_ID_1);\n\n    EXPECT_NE(image, (aeron_publication_image_t *)nullptr);\n    EXPECT_EQ(aeron_publication_image_subscriber_count(image), 1u);\n\n    int64_t remove_correlation_id = nextCorrelationId();\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id, sub_id), 0);\n    doWork();\n\n    EXPECT_EQ(aeron_publication_image_subscriber_count(image), 0u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(remove_correlation_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldTimeoutImageAndSendUnavailableImageWhenNoActivity)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    createPublicationImage(endpoint, STREAM_ID_1, 1000);\n\n    EXPECT_EQ(aeron_driver_conductor_num_images(&m_conductor.m_conductor), 1u);\n\n    aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image(\n        &m_conductor.m_conductor, endpoint, STREAM_ID_1);\n\n    EXPECT_NE(image, (aeron_publication_image_t *)nullptr);\n    EXPECT_EQ(aeron_publication_image_subscriber_count(image), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t image_correlation_id = image->conductor_fields.managed_resource.registration_id;\n\n    uint64_t timeoutNs = m_context.m_context->image_liveness_timeout_ns +\n        (m_context.m_context->client_liveness_timeout_ns * 2) + (m_context.m_context->timer_interval_ns * 3);\n\n    doWorkForNs(\n        static_cast<int64_t>(timeoutNs),\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_EQ(aeron_driver_conductor_num_images(&m_conductor.m_conductor), 0u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_IMAGE, _, _))\n        .With(IsUnavailableImage(STREAM_ID_1, image_correlation_id, sub_id, CHANNEL_1));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldRemoveSubscriptionAfterImageTimeout)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    createPublicationImage(endpoint, STREAM_ID_1, 1000);\n\n    uint64_t timeoutNs = m_context.m_context->image_liveness_timeout_ns +\n        (m_context.m_context->client_liveness_timeout_ns * 2) + (m_context.m_context->timer_interval_ns * 3);\n\n    doWorkForNs(\n        static_cast<int64_t>(timeoutNs),\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n    EXPECT_EQ(aeron_driver_conductor_num_images(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_network_subscriptions(&m_conductor.m_conductor, CHANNEL_1, STREAM_ID_1), 0u);\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id, sub_id), 0);\n\n    doWorkUntilDone();\n    EXPECT_EQ(aeron_driver_conductor_num_network_subscriptions(&m_conductor.m_conductor), 0u);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldRetryFreeOperationsAfterSubscrptionIsClosed)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    createPublicationImage(endpoint, STREAM_ID_1, 1000);\n\n    uint64_t timeoutNs = m_context.m_context->image_liveness_timeout_ns +\n        (m_context.m_context->client_liveness_timeout_ns * 2) + (m_context.m_context->timer_interval_ns * 3);\n\n    free_map_raw_log = false;\n    int64_t *free_fails_counter = aeron_system_counter_addr(\n        &m_conductor.m_conductor.system_counters, AERON_SYSTEM_COUNTER_FREE_FAILS);\n    EXPECT_EQ(aeron_counter_get_plain(free_fails_counter), 0);\n\n    doWorkForNs(\n        static_cast<int64_t>(timeoutNs),\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n    const int64_t free_fails = aeron_counter_get_plain(free_fails_counter);\n    EXPECT_GT(free_fails, 0);\n    EXPECT_EQ(aeron_driver_conductor_num_images(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_network_subscriptions(&m_conductor.m_conductor, CHANNEL_1, STREAM_ID_1), 0u);\n    free_map_raw_log = true;\n\n    doWorkUntilDone();\n\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n    EXPECT_EQ(aeron_counter_get_plain(free_fails_counter), free_fails);\n    EXPECT_EQ(aeron_driver_conductor_num_images(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_network_subscriptions(&m_conductor.m_conductor, CHANNEL_1, STREAM_ID_1), 0u);\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id, sub_id), 0);\n\n    doWorkUntilDone();\n    EXPECT_EQ(aeron_driver_conductor_num_network_subscriptions(&m_conductor.m_conductor), 0u);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldSendAvailableImageForMultipleSubscriptions)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_1, CHANNEL_1, STREAM_ID_1), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_2, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    createPublicationImage(endpoint, STREAM_ID_1, 1000);\n\n    aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image(\n        &m_conductor.m_conductor, endpoint, STREAM_ID_1);\n\n    EXPECT_NE(image, (aeron_publication_image_t *)nullptr);\n    EXPECT_EQ(aeron_publication_image_subscriber_count(image), 2u);\n\n    int64_t image_registration_id = aeron_publication_image_registration_id(image);\n    const char *log_file_name = aeron_publication_image_log_file_name(image);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(image_registration_id, sub_id_1, STREAM_ID_1, SESSION_ID, log_file_name, SOURCE_IDENTITY));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(image_registration_id, sub_id_2, STREAM_ID_1, SESSION_ID, log_file_name, SOURCE_IDENTITY));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldSendAvailableImageForSecondSubscriptionAfterCreatingImage)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_1, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    createPublicationImage(endpoint, STREAM_ID_1, 1000);\n\n    aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image(\n        &m_conductor.m_conductor, endpoint, STREAM_ID_1);\n\n    EXPECT_NE(image, (aeron_publication_image_t *)nullptr);\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_2, CHANNEL_1, STREAM_ID_1), 0);\n    doWork();\n\n    int64_t image_registration_id = aeron_publication_image_registration_id(image);\n    const char *log_file_name = aeron_publication_image_log_file_name(image);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    {\n        testing::InSequence sequenced;\n\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id_1));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n            .With(IsAvailableImage(image_registration_id, sub_id_1, STREAM_ID_1, SESSION_ID, log_file_name, SOURCE_IDENTITY));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id_2));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n            .With(IsAvailableImage(image_registration_id, sub_id_2, STREAM_ID_1, SESSION_ID, log_file_name, SOURCE_IDENTITY));\n    }\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldTimeoutImageAndSendUnavailableImageWhenNoActivityForMultipleSubscriptions)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_1, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    aeron_receive_channel_endpoint_t *endpoint = aeron_driver_conductor_find_receive_channel_endpoint(\n        &m_conductor.m_conductor, CHANNEL_1);\n\n    createPublicationImage(endpoint, STREAM_ID_1, 1000);\n\n    aeron_publication_image_t *image = aeron_driver_conductor_find_publication_image(\n        &m_conductor.m_conductor, endpoint, STREAM_ID_1);\n\n    EXPECT_NE(image, (aeron_publication_image_t *)nullptr);\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_2, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    int64_t image_correlation_id = aeron_publication_image_registration_id(image);\n    uint64_t timeoutNs = m_context.m_context->image_liveness_timeout_ns +\n        (m_context.m_context->client_liveness_timeout_ns * 2);\n\n    doWorkForNs(\n        static_cast<int64_t>(timeoutNs),\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    {\n        testing::InSequence sequenced;\n\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id_1));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id_2));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_IMAGE, _, _))\n            .With(IsUnavailableImage(STREAM_ID_1, image_correlation_id, sub_id_1, CHANNEL_1));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_UNAVAILABLE_IMAGE, _, _))\n            .With(IsUnavailableImage(STREAM_ID_1, image_correlation_id, sub_id_2, CHANNEL_1));\n    }\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldUseExistingChannelEndpointOnAddPublicationWithSameTagIdAndSameStreamId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, CHANNEL_1 \"|tags=1001\", STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, \"aeron:udp?tags=1001\", STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(aeron_driver_conductor_num_send_channel_endpoints(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_network_publications(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    uint64_t timeoutNs =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n    doWorkForNs(static_cast<int64_t>(timeoutNs));\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_network_publications(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_send_channel_endpoints(&m_conductor.m_conductor), 0u);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldUseExistingChannelEndpointOnAddPublicationWithSameTagIdDifferentStreamId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, CHANNEL_1 \"|tags=1001\", STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, \"aeron:udp?tags=1001\", STREAM_ID_2, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(aeron_driver_conductor_num_send_channel_endpoints(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_network_publications(&m_conductor.m_conductor), 2u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    uint64_t timeoutNs =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n    doWorkForNs(static_cast<int64_t>(timeoutNs));\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_network_publications(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_send_channel_endpoints(&m_conductor.m_conductor), 0u);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldUseExistingChannelEndpointOnAddSubscriptionWithSameTagId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_1, CHANNEL_1 \"|tags=1001\", STREAM_ID_1), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_2, \"aeron:udp?tags=1001\", STREAM_ID_1), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(aeron_driver_conductor_num_receive_channel_endpoints(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_network_subscriptions(&m_conductor.m_conductor), 2u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    uint64_t timeoutNs =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n    doWorkForNs(static_cast<int64_t>(timeoutNs));\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_network_subscriptions(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_receive_channel_endpoints(&m_conductor.m_conductor), 0u);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldUseExistingPublicationOnAddPublicationWithSameSessionTagIdAndSameStreamId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, CHANNEL_1 \"|tags=1001,1002\", STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, CHANNEL_2 \"|session-id=tag:1002\", STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(aeron_driver_conductor_num_send_channel_endpoints(&m_conductor.m_conductor), 2u);\n    EXPECT_EQ(aeron_driver_conductor_num_network_publications(&m_conductor.m_conductor), 2u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    aeron_network_publication_t *pub_1 = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id_1);\n    aeron_network_publication_t *pub_2 = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id_2);\n\n    EXPECT_EQ(pub_1->session_id, pub_2->session_id);\n\n    uint64_t timeoutNs =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n    doWorkForNs(static_cast<int64_t>(timeoutNs));\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_network_publications(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_send_channel_endpoints(&m_conductor.m_conductor), 0u);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldErrorWithUnknownSessionIdTag)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, CHANNEL_2 \"|session-id=tag:1002\", STREAM_ID_1, false), 0);\n    doWork();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldErrorWithInvalidSessionIdTag)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, CHANNEL_1 \"|tags=1001,1002\", STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, CHANNEL_2 \"|session-id=tag:1002a\", STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldBeAbleToAddAndRemoveDestinationToManualMdcPublication)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n    int64_t add_destination_id = nextCorrelationId();\n    int64_t remove_destination_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, CHANNEL_MDC_MANUAL, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(aeron_driver_conductor_num_network_publications(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    ASSERT_EQ(addDestination(client_id, add_destination_id, pub_id, CHANNEL_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(add_destination_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    ASSERT_EQ(removeDestination(client_id, remove_destination_id, pub_id, CHANNEL_1), 0);\n    doWork();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(remove_destination_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldFailToAddSubscriptionWithDifferentReliabilityParameter)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1_UNRELIABLE, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldAllowDifferentReliabilityParameterWithSpecificSession)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1_UNRELIABLE, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1_WITH_SESSION_ID_1, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldAddMdsWithSingleUnicastSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_MDC_MANUAL, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    int64_t dest_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addReceiveDestination(client_id, dest_correlation_id, sub_id, CHANNEL_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldFailToAddMdsWithSingleUnicastSubscriptionWithInvalidRegistrationId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t invalid_sub_id = sub_id + 1000000;\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_MDC_MANUAL, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    int64_t dest_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addReceiveDestination(client_id, dest_correlation_id, invalid_sub_id, CHANNEL_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldFailToAddMdsWithSingleUnicastSubscriptionWithInvalidNonManualControlEndpoint)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_2, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    int64_t dest_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addReceiveDestination(client_id, dest_correlation_id, sub_id, CHANNEL_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldAddAndRemoveMdsWithSingleUnicastSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_MDC_MANUAL, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t dest_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addReceiveDestination(client_id, dest_correlation_id, sub_id, CHANNEL_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(removeReceiveDestination(client_id, remove_correlation_id, sub_id, CHANNEL_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldFailToRemoveMdsWithSingleUnicastSubscriptionWithInvalidSusbcriptionId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t invalid_sub_id = sub_id + 1000000;\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_MDC_MANUAL, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t dest_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addReceiveDestination(client_id, dest_correlation_id, sub_id, CHANNEL_1), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(removeReceiveDestination(client_id, remove_correlation_id, invalid_sub_id, CHANNEL_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldFailToAddPublicationWithAtsEnabled)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, CHANNEL_1 \"|ats=true\", STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorNetworkTest, shouldFailToAddSubscriptionWithAtsEnabled)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, CHANNEL_1 \"|ats=true\", STREAM_ID_1), 0);\n    doWorkUntilDone();\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _));\n    EXPECT_EQ(1, readAllBroadcastsFromConductor(mock_broadcast_handler));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_conductor_pub_sub_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cinttypes>\n#include \"aeron_driver_conductor_test.h\"\n\nusing testing::_;\nusing testing::Eq;\nusing testing::Ne;\nusing testing::Args;\nusing testing::Mock;\nusing testing::AnyNumber;\n\nclass ConductorTestParam\n{\npublic:\n    const char *m_name;\n    const char *m_channel;\n    char m_initial_separator_char;\n\n    ConductorTestParam(const char *name, const char *channel, char initial_separator_char) :\n        m_name(name), m_channel(channel), m_initial_separator_char(initial_separator_char)\n    {\n    }\n\n    virtual bool publicationExists(aeron_driver_conductor_t *conductor, int64_t publication_id) = 0;\n    virtual size_t numSubscriptions(aeron_driver_conductor_t *conductor) = 0;\n    virtual size_t numPublications(aeron_driver_conductor_t *conductor) = 0;\n    virtual bool publicationHasRefCnt(aeron_driver_conductor_t *conductor, int64_t publication_id, int32_t refcnt) = 0;\n\n    virtual bool sendEndpointExists(aeron_driver_conductor_t *conductor, const char *channel)\n    {\n        return true;\n    }\n\n    virtual bool receiveEndpointExists(aeron_driver_conductor_t *conductor, const char *channel)\n    {\n        return true;\n    }\n\n    virtual bool receiveEndpointHasRefCnt(\n        aeron_driver_conductor_t *conductor, const char *channel, int32_t stream_id, int32_t session_id, int32_t refcnt)\n    {\n        return true;\n    }\n\n    virtual bool receiveEndpointHasStatus(\n        aeron_driver_conductor_t *conductor, const char *channel, aeron_receive_channel_endpoint_status_t status)\n    {\n        return true;\n    }\n\n    virtual bool hasReceiveEndpointCount(aeron_driver_conductor_t *conductor, size_t count)\n    {\n        return true;\n    }\n\n    virtual bool hasSendEndpointCount(aeron_driver_conductor_t *conductor, size_t count)\n    {\n        return true;\n    }\n\n    virtual void channelWithParams(char *path, size_t len, int32_t session_id, int32_t mtu, int32_t term_length)\n    {\n        int offset = snprintf(path, len - 1, \"%s%csession-id=%\" PRId32, m_channel, m_initial_separator_char, session_id);\n        if (0 != mtu)\n        {\n            offset += snprintf(&path[offset], len - (offset + 1), \"|mtu=%\" PRId32, mtu);\n        }\n        if (0 != term_length)\n        {\n            offset += snprintf(&path[offset], len - (offset + 1), \"|term-length=%\" PRId32, term_length);\n        }\n    }\n};\n\nclass NetworkTestParam : public ConductorTestParam\n{\npublic:\n    explicit NetworkTestParam(const char *channel) : ConductorTestParam(\"UDP\", channel, '|') {}\n\n    static NetworkTestParam *instance()\n    {\n        static NetworkTestParam instance{ CHANNEL_1 };\n        return &instance;\n    }\n\n    bool publicationExists(aeron_driver_conductor_t *conductor, int64_t publication_id) override\n    {\n        return nullptr != aeron_driver_conductor_find_network_publication(conductor, publication_id);\n    }\n\n    size_t numPublications(aeron_driver_conductor_t *conductor) override\n    {\n        return aeron_driver_conductor_num_network_publications(conductor);\n    }\n\n    bool publicationHasRefCnt(aeron_driver_conductor_t *conductor, int64_t publication_id, int32_t refcnt) override\n    {\n        aeron_network_publication_t *pub = aeron_driver_conductor_find_network_publication(conductor, publication_id);\n        return nullptr != pub && refcnt == pub->conductor_fields.refcnt;\n    }\n\n    bool sendEndpointExists(aeron_driver_conductor_t *conductor, const char *channel) override\n    {\n        return nullptr != aeron_driver_conductor_find_send_channel_endpoint(conductor, channel);\n    }\n\n    bool receiveEndpointExists(aeron_driver_conductor_t *conductor, const char *channel) override\n    {\n        return nullptr != aeron_driver_conductor_find_receive_channel_endpoint(conductor, channel);\n    }\n\n    bool hasReceiveEndpointCount(aeron_driver_conductor_t *conductor, size_t count) override\n    {\n        return count == aeron_driver_conductor_num_receive_channel_endpoints(conductor);\n    }\n\n    bool receiveEndpointHasRefCnt(\n        aeron_driver_conductor_t *conductor, const char *channel, int32_t stream_id, int32_t session_id, int32_t refcnt)\n        override\n    {\n        aeron_receive_channel_endpoint_t *receive_endpoint =\n            aeron_driver_conductor_find_receive_channel_endpoint(conductor, channel);\n\n        return nullptr != receive_endpoint && refcnt == aeron_int64_counter_map_get(\n            &receive_endpoint->stream_and_session_id_to_refcnt_map,\n            aeron_map_compound_key(STREAM_ID_1, SESSION_ID_1));\n    }\n\n    bool receiveEndpointHasStatus(\n        aeron_driver_conductor_t *conductor, const char *channel, aeron_receive_channel_endpoint_status_t status)\n        override\n    {\n        aeron_receive_channel_endpoint_t *receive_endpoint =\n            aeron_driver_conductor_find_receive_channel_endpoint(conductor, channel);\n\n        return nullptr != receive_endpoint && status == receive_endpoint->conductor_fields.status;\n    }\n\n    size_t numSubscriptions(aeron_driver_conductor_t *conductor) override\n    {\n        return aeron_driver_conductor_num_network_subscriptions(conductor);\n    }\n\n    bool hasSendEndpointCount(aeron_driver_conductor_t *conductor, size_t count) override\n    {\n        return count == aeron_driver_conductor_num_send_channel_endpoints(conductor);\n    }\n};\n\nclass IpcTestParam : public ConductorTestParam\n{\npublic:\n    explicit IpcTestParam(const char *channel) : ConductorTestParam(\"IPC\", channel, '?') {}\n\n    static IpcTestParam *instance()\n    {\n        static IpcTestParam instance{AERON_IPC_CHANNEL};\n        return &instance;\n    };\n\n    bool publicationExists(aeron_driver_conductor_t *conductor, int64_t publication_id) override\n    {\n        return nullptr != aeron_driver_conductor_find_ipc_publication(conductor, publication_id);\n    }\n\n    size_t numPublications(aeron_driver_conductor_t *conductor) override\n    {\n        return aeron_driver_conductor_num_ipc_publications(conductor);\n    }\n\n    bool publicationHasRefCnt(aeron_driver_conductor_t *conductor, int64_t publication_id, int32_t refcnt) override\n    {\n        aeron_ipc_publication_t *pub = aeron_driver_conductor_find_ipc_publication(conductor, publication_id);\n        return nullptr != pub && refcnt == pub->conductor_fields.refcnt;\n    }\n\n    size_t numSubscriptions(aeron_driver_conductor_t *conductor) override\n    {\n        return aeron_driver_conductor_num_ipc_subscriptions(conductor);\n    }\n};\n\nclass DriverConductorPubSubTest :\n    public DriverConductorTest,\n    public testing::TestWithParam<ConductorTestParam *>\n{\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    DriverConductorPubSubParameterisedTest,\n    DriverConductorPubSubTest,\n    testing::Values(NetworkTestParam::instance(), IpcTestParam::instance()),\n    [](const testing::TestParamInfo<ConductorTestParam *> &info)\n    {\n        return std::string(info.param->m_name);\n    });\n\nTEST_P(DriverConductorPubSubTest, shouldRejectAddPublicationIfChannelIsTooLong)\n{\n    const auto channel = std::string(GetParam()->m_channel).append(\"|alias=\").append(AERON_URI_MAX_LENGTH, 'x');\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel.c_str(), STREAM_ID_1, false), 0);\n\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _))\n        .With(IsError(pub_id));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddAndRemoveSingleNetworkPublication)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n    int32_t counter_id;\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel, STREAM_ID_1, false), 0);\n\n    doWorkUntilDone();\n\n    ASSERT_TRUE(GetParam()->sendEndpointExists(&m_conductor.m_conductor, channel));\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _))\n        .WillOnce(CaptureCounterId(&counter_id));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .With(IsPublicationReady(pub_id, Eq(STREAM_ID_1), _));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    EXPECT_CALL(m_mockCallbacks, onCounter(_, _, _, _, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, onCounter(counter_id, AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID, _, _, _, _)).\n        With(IsIdCounter(client_id, std::string(\"client-heartbeat: id=0\")));\n    readCounters(mock_counter_handler);\n\n    ASSERT_EQ(removePublication(client_id, remove_correlation_id, pub_id), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(remove_correlation_id));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddAndRemoveSingleNetworkSubscription)\n{\n    const char *channel = GetParam()->m_channel;\n\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, channel, STREAM_ID_1), 0);\n\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 1u) << \"channel: \" << GetParam()->m_channel;\n    ASSERT_TRUE(GetParam()->receiveEndpointExists(&m_conductor.m_conductor, channel));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id, sub_id), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 0u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(remove_correlation_id));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddAndRemoveSingleNetworkSubscriptionBySession)\n{\n    char channel_with_session[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel_with_session, AERON_URI_MAX_LENGTH, SESSION_ID_1, 0, 0);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, channel_with_session, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id));\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 1u);\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    ASSERT_TRUE(GetParam()->receiveEndpointHasRefCnt(&m_conductor.m_conductor, channel_with_session, STREAM_ID_1, SESSION_ID_1, 1));\n\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id, sub_id), 0);\n    doWorkUntilDone();\n\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 0u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(remove_correlation_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    ASSERT_TRUE(GetParam()->receiveEndpointHasRefCnt(&m_conductor.m_conductor, channel_with_session, STREAM_ID_1, SESSION_ID_1, 0));\n    ASSERT_TRUE(GetParam()->receiveEndpointHasStatus(&m_conductor.m_conductor, channel_with_session, AERON_RECEIVE_CHANNEL_ENDPOINT_STATUS_CLOSED));\n}\n\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddMultipleNetworkPublications)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n    int64_t pub_id_3 = nextCorrelationId();\n    int64_t pub_id_4 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, channel, STREAM_ID_2, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_3, channel, STREAM_ID_3, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_4, channel, STREAM_ID_4, false), 0);\n    doWorkUntilDone();\n\n    ASSERT_TRUE(GetParam()->sendEndpointExists(&m_conductor.m_conductor, channel));\n\n    // TODO:\n    ASSERT_TRUE(GetParam()->hasSendEndpointCount(&m_conductor.m_conductor, 1u));\n\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id_1));\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id_2));\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id_3));\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id_4));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .With(IsPublicationReady(pub_id_1, Eq(STREAM_ID_1), _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .With(IsPublicationReady(pub_id_2, Eq(STREAM_ID_2), _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .With(IsPublicationReady(pub_id_3, Eq(STREAM_ID_3), _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .With(IsPublicationReady(pub_id_4, Eq(STREAM_ID_4), _));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddAndRemoveMultipleNetworkPublicationsToSameChannelSameStreamId)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n    int64_t pub_id_3 = nextCorrelationId();\n    int64_t pub_id_4 = nextCorrelationId();\n    int64_t remove_correlation_id_1 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_3, channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_4, channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id_1));\n    ASSERT_TRUE(GetParam()->publicationHasRefCnt(&m_conductor.m_conductor, pub_id_1, 4));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .With(IsPublicationReady(_, Eq(STREAM_ID_1), _))\n        .Times(4);\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    ASSERT_EQ(removePublication(client_id, remove_correlation_id_1, pub_id_2), 0);\n    doWorkUntilDone();\n\n    ASSERT_TRUE(GetParam()->publicationHasRefCnt(&m_conductor.m_conductor, pub_id_1, 3));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(remove_correlation_id_1));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddMultipleExclusiveNetworkPublicationsWithSameChannelSameStreamId)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n    int64_t pub_id_3 = nextCorrelationId();\n    int64_t pub_id_4 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, channel, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, channel, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_3, channel, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_4, channel, STREAM_ID_1, true), 0);\n    doWorkUntilDone();\n\n    ASSERT_TRUE(GetParam()->sendEndpointExists(&m_conductor.m_conductor, channel));\n    ASSERT_TRUE(GetParam()->hasSendEndpointCount(&m_conductor.m_conductor, 1u));\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id_1));\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id_2));\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id_3));\n    ASSERT_TRUE(GetParam()->publicationExists(&m_conductor.m_conductor, pub_id_4));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY, _, _))\n        .With(IsPublicationReady(_, Eq(STREAM_ID_1), _))\n        .Times(4);\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddAndRemoveSingleNetworkPublicationWithExplicitSessionId)\n{\n    char channel_with_session_id[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel_with_session_id, AERON_URI_MAX_LENGTH, SESSION_ID_1, 0, 0);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel_with_session_id, STREAM_ID_1, false), 0);\n\n    doWorkUntilDone();\n\n    GetParam()->sendEndpointExists(&m_conductor.m_conductor, channel_with_session_id);\n    GetParam()->publicationExists(&m_conductor.m_conductor, pub_id);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .With(IsPublicationReady(_, Eq(STREAM_ID_1), _));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    ASSERT_EQ(removePublication(client_id, remove_correlation_id, pub_id), 0);\n    doWork();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(remove_correlation_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldAddSecondNetworkPublicationWithSpecifiedSessionIdAndSameMtu)\n{\n    char channel[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel, AERON_URI_MAX_LENGTH, SESSION_ID_1, MTU_1, 0);\n\n    int64_t client_id1 = nextCorrelationId();\n    int64_t pub_id1 = nextCorrelationId();\n    int64_t client_id2 = nextCorrelationId();\n    int64_t pub_id2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id1, pub_id1, channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    ASSERT_EQ(addPublication(client_id2, pub_id2, channel, STREAM_ID_1, false), 0);\n\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldFailToAddSecondNetworkPublicationWithSpecifiedSessionIdAndDifferentMtu)\n{\n    char channel1[AERON_URI_MAX_LENGTH];\n    char channel2[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel1, AERON_URI_MAX_LENGTH, SESSION_ID_1, MTU_1, 0);\n    GetParam()->channelWithParams(channel2, AERON_URI_MAX_LENGTH, SESSION_ID_1, MTU_2, 0);\n\n    int64_t client_id1 = nextCorrelationId();\n    int64_t pub_id1 = nextCorrelationId();\n    int64_t client_id2 = nextCorrelationId();\n    int64_t pub_id2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id1, pub_id1, channel1, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    ASSERT_EQ(addPublication(client_id2, pub_id2, channel2, STREAM_ID_1, false), 0);\n\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldAddSecondNetworkPublicationWithSpecifiedSessionIdAndSameTermLength)\n{\n    char channel[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel, AERON_URI_MAX_LENGTH, SESSION_ID_1, MTU_1, TERM_LENGTH);\n\n    int64_t client_id1 = nextCorrelationId();\n    int64_t pub_id1 = nextCorrelationId();\n    int64_t client_id2 = nextCorrelationId();\n    int64_t pub_id2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id1, pub_id1, channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n    testing::Mock::VerifyAndClear(&m_mockCallbacks);\n\n    ASSERT_EQ(addPublication(client_id2, pub_id2, channel, STREAM_ID_1, false), 0);\n\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldFailToAddSecondNetworkPublicationWithSpecifiedSessionIdAndDifferentTermLength)\n{\n    char channel1[AERON_URI_MAX_LENGTH];\n    char channel2[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel1, AERON_URI_MAX_LENGTH, SESSION_ID_1, MTU_1, TERM_LENGTH);\n    GetParam()->channelWithParams(channel2, AERON_URI_MAX_LENGTH, SESSION_ID_1, MTU_1, TERM_LENGTH * 2);\n\n    int64_t client_id1 = nextCorrelationId();\n    int64_t pub_id1 = nextCorrelationId();\n    int64_t client_id2 = nextCorrelationId();\n    int64_t pub_id2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id1, pub_id1, channel1, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    ASSERT_EQ(addPublication(client_id2, pub_id2, channel2, STREAM_ID_1, false), 0);\n\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _)).With(IsError(pub_id2));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddSingleNetworkPublicationThatAvoidCollisionWithSpecifiedSessionId)\n{\n    char channel_with_session_id[AERON_URI_MAX_LENGTH];\n    const char *channel = GetParam()->m_channel;\n    int32_t next_session_id = SESSION_ID_1;\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n    GetParam()->channelWithParams(channel_with_session_id, AERON_URI_MAX_LENGTH, next_session_id, 0, 0);\n\n    m_conductor.manuallySetNextSessionId(next_session_id);\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel_with_session_id, STREAM_ID_1, true), 0);\n\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel, STREAM_ID_1, true), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 2u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY, _, _))\n        .With(IsPublicationReady(pub_id, STREAM_ID_1, Ne(next_session_id)));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldErrorOnDuplicateExclusivePublicationWithSameSessionId)\n{\n    char channel_with_session_id[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel_with_session_id, AERON_URI_MAX_LENGTH, SESSION_ID_1, 0, 0);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, channel_with_session_id, STREAM_ID_1, true), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n    doWorkUntilDone();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_2, channel_with_session_id, STREAM_ID_1, true), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _)).With(IsError(pub_id_2));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldErrorOnDuplicateSharedPublicationWithDifferentSessionId)\n{\n    char channel1[AERON_URI_MAX_LENGTH];\n    char channel2[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel1, AERON_URI_MAX_LENGTH, SESSION_ID_1, 0, 0);\n    GetParam()->channelWithParams(channel2, AERON_URI_MAX_LENGTH, SESSION_ID_3, 0, 0);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, channel1, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n    doWorkUntilDone();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_2, channel2, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _)).With(IsError(pub_id_2));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldErrorOnDuplicateSharedPublicationWithExclusivePublicationWithSameSessionId)\n{\n    char channel_with_session_id[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel_with_session_id, AERON_URI_MAX_LENGTH, SESSION_ID_1, 0, 0);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, channel_with_session_id, STREAM_ID_1, true), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n    doWorkUntilDone();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_2, channel_with_session_id, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _)).With(IsError(pub_id_2));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldErrorOnDuplicateExclusivePublicationWithSharedPublicationWithSameSessionId)\n{\n    char channel_with_session_id[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel_with_session_id, AERON_URI_MAX_LENGTH, SESSION_ID_1, 0, 0);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, channel_with_session_id, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n    doWorkUntilDone();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_2, channel_with_session_id, STREAM_ID_1, true), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _)).With(IsError(pub_id_2));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddMultipleNetworkSubscriptionsWithSameChannelSameStreamId)\n{\n    const char *channel = GetParam()->m_channel;\n\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n    int64_t sub_id_3 = nextCorrelationId();\n    int64_t sub_id_4 = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_1, channel, STREAM_ID_1), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_2, channel, STREAM_ID_1), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_3, channel, STREAM_ID_1), 0);\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id_4, channel, STREAM_ID_1), 0);\n\n    doWorkUntilDone();\n\n    ASSERT_TRUE(GetParam()->receiveEndpointExists(&m_conductor.m_conductor, channel));\n    ASSERT_TRUE(GetParam()->hasReceiveEndpointCount(&m_conductor.m_conductor, 1u));\n    ASSERT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 4u);\n\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n}\n\nTEST_F(DriverConductorPubSubTest, shouldErrorOnRemovePublicationOnUnknownRegistrationId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(removePublication(client_id, remove_correlation_id, pub_id), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _)).With(IsError(remove_correlation_id));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorPubSubTest, shouldErrorOnRemoveSubscriptionOnUnknownRegistrationId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id, sub_id), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _)).With(IsError(remove_correlation_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorPubSubTest, shouldErrorOnAddPublicationWithInvalidUri)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, INVALID_URI, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _)).With(IsError(pub_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorPubSubTest, shouldErrorOnAddSubscriptionWithInvalidUri)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, INVALID_URI, STREAM_ID_1), 0);\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_ERROR, _, _)).With(IsError(sub_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToTimeoutNetworkPublication)\n{\n    const char *channel = GetParam()->m_channel;\n\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n    EXPECT_TRUE(GetParam()->hasSendEndpointCount(&m_conductor.m_conductor, 1u));\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    doWorkForNs(\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2));\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 0u);\n    EXPECT_TRUE(GetParam()->hasSendEndpointCount(&m_conductor.m_conductor, 0u));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_CLIENT_TIMEOUT, _, _)).With(IsTimeout(client_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToNotTimeoutNetworkPublicationOnKeepalive)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t timeout =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n\n    doWorkForNs(\n        timeout,\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToTimeoutNetworkSubscription)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, channel, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    EXPECT_TRUE(GetParam()->hasReceiveEndpointCount(&m_conductor.m_conductor, 1u));\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    doWorkForNs(\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2));\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 0u);\n    EXPECT_TRUE(GetParam()->hasReceiveEndpointCount(&m_conductor.m_conductor, 0u));\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 0u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_CLIENT_TIMEOUT, _, _)).With(IsTimeout(client_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToNotTimeoutNetworkSubscriptionOnKeepalive)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, channel, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t timeout =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n\n    doWorkForNs(\n        timeout,\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 1u);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToTimeoutSendChannelEndpointWithClientKeepaliveAfterRemovePublication)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n    ASSERT_EQ(removePublication(client_id, remove_correlation_id, pub_id), 0);\n    doWork();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t timeout =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n\n    doWorkForNs(\n        timeout,\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 0u);\n    EXPECT_TRUE(GetParam()->hasSendEndpointCount(&m_conductor.m_conductor, 0u));\n}\n\nTEST_P(DriverConductorPubSubTest, shouldRetryFreeOperationsAfterTimeoutSendChannelEndpointWithClientKeepaliveAfterRemovePublication)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 1u);\n    ASSERT_EQ(removePublication(client_id, remove_correlation_id, pub_id), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    const int64_t timeout =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n\n    free_map_raw_log = false;\n    int64_t *free_fails_counter = aeron_system_counter_addr(\n        &m_conductor.m_conductor.system_counters, AERON_SYSTEM_COUNTER_FREE_FAILS);\n    EXPECT_EQ(aeron_counter_get_plain(free_fails_counter), 0);\n\n    doWorkForNs(\n        timeout,\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    const int64_t free_fails = aeron_counter_get_plain(free_fails_counter);\n    EXPECT_GT(free_fails, 1);\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(GetParam()->numPublications(&m_conductor.m_conductor), 0u);\n    EXPECT_TRUE(GetParam()->hasSendEndpointCount(&m_conductor.m_conductor, 0u));\n\n    const int64_t resource_check_interval = m_context.m_context->timer_interval_ns * 2;\n    free_map_raw_log = true;\n\n    doWorkForNs(\n        resource_check_interval,\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_EQ(aeron_counter_get_plain(free_fails_counter), free_fails);\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 1u);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToTimeoutReceiveChannelEndpointWithClientKeepaliveAfterRemoveSubscription)\n{\n    const char *channel = GetParam()->m_channel;\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, channel, STREAM_ID_1), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 1u);\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id, sub_id), 0);\n    doWorkUntilDone();\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t timeout = m_context.m_context->client_liveness_timeout_ns;\n\n    doWorkForNs(\n        timeout,\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 0u);\n    EXPECT_TRUE(GetParam()->hasReceiveEndpointCount(&m_conductor.m_conductor, 0u));\n}\n\nTEST_P(DriverConductorPubSubTest, shouldNotAddDynamicSessionIdInReservedRange)\n{\n    const char *channel = GetParam()->m_channel;\n    m_conductor.manuallySetNextSessionId(m_conductor.m_conductor.publication_reserved_session_id_low);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, channel, STREAM_ID_1, false), 0);\n\n    doWorkUntilDone();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .WillRepeatedly(\n            [&](std::int32_t msgTypeId, uint8_t *buffer, size_t length)\n            {\n                const aeron_publication_buffers_ready_t *msg = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n\n                EXPECT_TRUE(\n                    msg->session_id < m_conductor.m_conductor.publication_reserved_session_id_low ||\n                        m_conductor.m_conductor.publication_reserved_session_id_high < msg->session_id)\n                            << \"Session Id [\" << msg->session_id << \"] should not be in the range: \"\n                            << m_conductor.m_conductor.publication_reserved_session_id_low\n                            << \" to \"\n                            << m_conductor.m_conductor.publication_reserved_session_id_high;\n\n            });\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldNotAccidentallyBumpIntoExistingSessionId)\n{\n    const char *channel = GetParam()->m_channel;\n    char channel1[AERON_URI_MAX_LENGTH];\n    char channel2[AERON_URI_MAX_LENGTH];\n    char channel3[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel1, AERON_URI_MAX_LENGTH, SESSION_ID_3, 0, 0);\n    GetParam()->channelWithParams(channel2, AERON_URI_MAX_LENGTH, SESSION_ID_4, 0, 0);\n    GetParam()->channelWithParams(channel3, AERON_URI_MAX_LENGTH, SESSION_ID_5, 0, 0);\n\n    int next_session_id = SESSION_ID_3;\n    m_conductor.manuallySetNextSessionId(next_session_id);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n    int64_t pub_id_3 = nextCorrelationId();\n    int64_t pub_id_4 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, channel1, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, channel2, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_3, channel3, STREAM_ID_1, true), 0);\n\n    doWorkUntilDone();\n\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    ASSERT_EQ(addPublication(client_id, pub_id_4, channel, STREAM_ID_1, true), 0);\n\n    doWork();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY, _, _))\n        .WillRepeatedly(\n            [&](std::int32_t msgTypeId, uint8_t *buffer, size_t length)\n            {\n                const aeron_publication_buffers_ready_t *msg = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n\n                EXPECT_EQ(msg->correlation_id, pub_id_4);\n                EXPECT_NE(msg->session_id, SESSION_ID_3);\n                EXPECT_NE(msg->session_id, SESSION_ID_4);\n                EXPECT_NE(msg->session_id, SESSION_ID_5);\n            });\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldNotAccidentallyBumpIntoExistingSessionIdWithSessionIdWrapping)\n{\n    int32_t session_id_1 = INT32_MAX - 1;\n    int32_t session_id_2 = session_id_1 + 1;\n    int32_t session_id_3 = INT32_MIN;\n    int32_t session_id_4 = session_id_3 + 1;\n    const char *channel = GetParam()->m_channel;\n    char channel1[AERON_URI_MAX_LENGTH];\n    char channel2[AERON_URI_MAX_LENGTH];\n    char channel3[AERON_URI_MAX_LENGTH];\n    char channel4[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel1, AERON_URI_MAX_LENGTH, session_id_1, 0, 0);\n    GetParam()->channelWithParams(channel2, AERON_URI_MAX_LENGTH, session_id_2, 0, 0);\n    GetParam()->channelWithParams(channel3, AERON_URI_MAX_LENGTH, session_id_3, 0, 0);\n    GetParam()->channelWithParams(channel4, AERON_URI_MAX_LENGTH, session_id_4, 0, 0);\n\n    m_conductor.manuallySetNextSessionId(session_id_1);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n    int64_t pub_id_3 = nextCorrelationId();\n    int64_t pub_id_4 = nextCorrelationId();\n    int64_t pub_id_5 = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id_1, channel1, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, channel2, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_3, channel3, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_4, channel4, STREAM_ID_1, true), 0);\n\n    doWorkUntilDone();\n\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    ASSERT_EQ(addPublication(client_id, pub_id_5, channel, STREAM_ID_1, true), 0);\n\n    doWork();\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY, _, _))\n        .WillRepeatedly(\n            [&](std::int32_t msgTypeId, uint8_t *buffer, size_t length)\n            {\n                const aeron_publication_buffers_ready_t *msg = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n\n                EXPECT_EQ(msg->correlation_id, pub_id_5);\n                EXPECT_NE(msg->session_id, session_id_1);\n                EXPECT_NE(msg->session_id, session_id_2);\n                EXPECT_NE(msg->session_id, session_id_3);\n                EXPECT_NE(msg->session_id, session_id_4);\n            });\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_P(DriverConductorPubSubTest, shouldBeAbleToAddSingleNetworkSubscriptionWithSpecifiedSessionId)\n{\n    char channel_with_session_id[AERON_URI_MAX_LENGTH];\n    GetParam()->channelWithParams(channel_with_session_id, AERON_URI_MAX_LENGTH, SESSION_ID_1, 0, 0);\n\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addNetworkSubscription(client_id, sub_id, channel_with_session_id, STREAM_ID_1), 0);\n\n    doWorkUntilDone();\n\n    ASSERT_EQ(GetParam()->numSubscriptions(&m_conductor.m_conductor), 1u);\n    ASSERT_TRUE(GetParam()->receiveEndpointExists(&m_conductor.m_conductor, channel_with_session_id));\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_conductor_spy_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_driver_conductor_test.h\"\n\nusing testing::_;\n\nclass DriverConductorSpyTest : public DriverConductorTest, public testing::Test\n{\nprotected:\n    inline size_t subscriberCount(aeron_network_publication_t *publication)\n    {\n        return publication->conductor_fields.subscribable.length;\n    }\n};\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddSingleSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1, -1), 0);\n\n    doWorkUntilDone();\n\n    int32_t client_counter_id;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_COUNTER_READY, _, _))\n        .With(IsCounterUpdate(client_id, _))\n        .WillOnce(CaptureCounterId(&client_counter_id));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n\n    EXPECT_EQ(aeron_driver_conductor_num_spy_subscriptions(&m_conductor.m_conductor), 1u);\n\n    EXPECT_CALL(m_mockCallbacks, onCounter(_, _, _, _, _, _)).Times(testing::AnyNumber());\n    EXPECT_CALL(m_mockCallbacks, onCounter(_, AERON_COUNTER_CLIENT_HEARTBEAT_TIMESTAMP_TYPE_ID, _, _, _, _)).\n        With(IsIdCounter(client_id, std::string(\"client-heartbeat: id=0\")));\n    readCounters(mock_counter_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddAndRemoveSingleSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t remove_correlation_id = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1, -1), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(aeron_driver_conductor_num_spy_subscriptions(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    ASSERT_EQ(removeSubscription(client_id, remove_correlation_id, sub_id), 0);\n    doWorkUntilDone();\n\n    EXPECT_EQ(aeron_driver_conductor_num_spy_subscriptions(&m_conductor.m_conductor), 0u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_OPERATION_SUCCESS, _, _))\n        .With(IsOperationSuccess(remove_correlation_id));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddMultipleSubscriptionsWithSameChannelSameStreamId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n    int64_t sub_id_3 = nextCorrelationId();\n    int64_t sub_id_4 = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_1, CHANNEL_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_2, CHANNEL_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_3, CHANNEL_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_4, CHANNEL_1, STREAM_ID_1, -1), 0);\n\n    doWorkUntilDone();\n\n    ASSERT_EQ(aeron_driver_conductor_num_spy_subscriptions(&m_conductor.m_conductor), 4u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _)).Times(4);\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddMultipleSubscriptionsWithDifferentChannelSameStreamId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n    int64_t sub_id_3 = nextCorrelationId();\n    int64_t sub_id_4 = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_1, CHANNEL_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_2, CHANNEL_2, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_3, CHANNEL_3, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_4, CHANNEL_4, STREAM_ID_1, -1), 0);\n\n    doWorkUntilDone();\n\n    ASSERT_EQ(aeron_driver_conductor_num_spy_subscriptions(&m_conductor.m_conductor), 4u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _)).Times(4);\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddSingleSubscriptionThenAddSinglePublication)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id, CHANNEL_1, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_spy_subscriptions(&m_conductor.m_conductor, CHANNEL_1, STREAM_ID_1), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    {\n        testing::InSequence s;\n\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n                    session_id = response->session_id;\n                    log_file_name\n                        .append((char *)buffer + sizeof(aeron_publication_buffers_ready_t), (size_t)response->log_file_length);\n                });\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_image_buffers_ready_t *>(buffer);\n                    EXPECT_THAT(response, IsImageBuffersReady(sub_id, STREAM_ID_1, session_id, log_file_name, std::string(AERON_IPC_CHANNEL)));\n                });\n    }\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddSingleSubscriptionWithTagThenAddSinglePublication)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_TAG_1001, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id, CHANNEL_1_WITH_TAG_1001, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_spy_subscriptions(\n        &m_conductor.m_conductor, CHANNEL_TAG_1001, STREAM_ID_1), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    {\n        testing::InSequence s;\n\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n                    session_id = response->session_id;\n                    log_file_name.append(\n                        (char *)buffer + sizeof(aeron_publication_buffers_ready_t), (size_t)response->log_file_length);\n                });\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_image_buffers_ready_t *>(buffer);\n                    EXPECT_THAT(response,\n                        IsImageBuffersReady(sub_id, STREAM_ID_1, session_id, log_file_name, std::string(AERON_IPC_CHANNEL)));\n                });\n    }\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddSinglePublicationThenAddSingleSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, CHANNEL_1, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1, -1), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_spy_subscriptions(&m_conductor.m_conductor, CHANNEL_1, STREAM_ID_1), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    {\n        testing::InSequence s;\n\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n                    session_id = response->session_id;\n                    log_file_name.append(\n                        (char *)buffer + sizeof(aeron_publication_buffers_ready_t), (size_t)response->log_file_length);\n                });\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_image_buffers_ready_t *>(buffer);\n                    EXPECT_THAT(response,\n                        IsImageBuffersReady(sub_id, STREAM_ID_1, session_id, log_file_name, std::string(AERON_IPC_CHANNEL)));\n                });\n    }\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddSinglePublicationThenAddSingleSubscriptionWithTag)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addPublication(client_id, pub_id, CHANNEL_1_WITH_TAG_1001, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_TAG_1001, STREAM_ID_1, -1), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_spy_subscriptions(\n        &m_conductor.m_conductor, CHANNEL_TAG_1001, STREAM_ID_1), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    {\n        testing::InSequence s;\n\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n                    session_id = response->session_id;\n                    log_file_name\n                        .append((char *)buffer + sizeof(aeron_publication_buffers_ready_t), (size_t)response->log_file_length);\n                });\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_image_buffers_ready_t *>(buffer);\n                    EXPECT_THAT(response,\n                        IsImageBuffersReady(sub_id, STREAM_ID_1, session_id, log_file_name, std::string(AERON_IPC_CHANNEL)));\n                });\n    }\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddMultipleSubscriptionWithSameStreamIdThenAddSinglePublication)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id_1 = nextCorrelationId();\n    int64_t sub_id_2 = nextCorrelationId();\n    int64_t pub_id = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_1, CHANNEL_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addSpySubscription(client_id, sub_id_2, CHANNEL_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id, CHANNEL_1, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 2u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_spy_subscriptions(&m_conductor.m_conductor, CHANNEL_1, STREAM_ID_1), 2u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence s1, s2;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id_1))\n        .InSequence(s1);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id_2))\n        .InSequence(s2);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(s1, s2)\n        .WillOnce(\n            [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n            {\n                auto *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n                session_id = response->session_id;\n                log_file_name\n                    .append((char *)buffer + sizeof(aeron_publication_buffers_ready_t), (size_t)response->log_file_length);\n            });\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .Times(2)\n        .InSequence(s1, s2)\n        .WillRepeatedly(\n            [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n            {\n                auto *response = reinterpret_cast<aeron_image_buffers_ready_t *>(buffer);\n                EXPECT_THAT(\n                    response, testing::AnyOf(\n                        IsImageBuffersReady(sub_id_1, STREAM_ID_1, session_id, log_file_name, std::string(AERON_IPC_CHANNEL)),\n                        IsImageBuffersReady(sub_id_2, STREAM_ID_1, session_id, log_file_name, std::string(AERON_IPC_CHANNEL))\n                ));\n            });\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToAddSingleSubscriptionThenAddMultipleExclusivePublicationsWithSameStreamId)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_1, CHANNEL_1, STREAM_ID_1, true), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, CHANNEL_1, STREAM_ID_1, true), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication_1 = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id_1);\n    EXPECT_EQ(subscriberCount(publication_1), 1u);\n    aeron_network_publication_t *publication_2 = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id_2);\n    EXPECT_EQ(subscriberCount(publication_2), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_spy_subscriptions(&m_conductor.m_conductor, CHANNEL_1, STREAM_ID_1), 2u);\n\n    int32_t session_id_1 = 0;\n    int32_t session_id_2 = 0;\n    std::string log_file_name_1;\n    std::string log_file_name_2;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    {\n        testing::InSequence s;\n\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n                    session_id_1 = response->session_id;\n                    log_file_name_1.append(\n                        (char *)buffer + sizeof(aeron_publication_buffers_ready_t), (size_t)response->log_file_length);\n                });\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_image_buffers_ready_t *>(buffer);\n                    EXPECT_THAT(response,\n                        IsImageBuffersReady(sub_id, STREAM_ID_1, session_id_1, log_file_name_1, std::string(AERON_IPC_CHANNEL)));\n                });\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n                    session_id_2 = response->session_id;\n                    log_file_name_2.append(\n                        (char *)buffer + sizeof(aeron_publication_buffers_ready_t), (size_t)response->log_file_length);\n                });\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_image_buffers_ready_t *>(buffer);\n                    EXPECT_THAT(response,\n                        IsImageBuffersReady(sub_id, STREAM_ID_1, session_id_2, log_file_name_2, std::string(AERON_IPC_CHANNEL)));\n                });\n    }\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldNotLinkSubscriptionOnAddPublicationAfterFirstAddPublication)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n    int64_t pub_id_1 = nextCorrelationId();\n    int64_t pub_id_2 = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1, -1), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_1, CHANNEL_1, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id_2, CHANNEL_1, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id_1);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_active_spy_subscriptions(&m_conductor.m_conductor, CHANNEL_1, STREAM_ID_1), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    {\n        testing::InSequence s;\n\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n            .With(IsSubscriptionReady(sub_id));\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n            .With(IsPublicationReady(pub_id_1, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n                    session_id = response->session_id;\n                    log_file_name.append(\n                        (char *)buffer + sizeof(aeron_publication_buffers_ready_t), (size_t)response->log_file_length);\n                });\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_image_buffers_ready_t *>(buffer);\n                    EXPECT_THAT(response,\n                        IsImageBuffersReady(sub_id, STREAM_ID_1, session_id, log_file_name, std::string(AERON_IPC_CHANNEL)));\n                });\n        EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n            .With(IsPublicationReady(pub_id_2, _, _))\n            .WillOnce(\n                [&](int32_t msg_type_id, uint8_t *buffer, size_t length)\n                {\n                    auto *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(buffer);\n                    session_id = response->session_id;\n                    std::string response_log_file_name(\n                        (char *)buffer + sizeof(aeron_publication_buffers_ready_t), (size_t)response->log_file_length);\n                    EXPECT_EQ(pub_id_2, response->correlation_id);\n                    EXPECT_EQ(pub_id_1, response->registration_id);\n                    EXPECT_EQ(log_file_name, response_log_file_name);\n                });\n    }\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToTimeoutSubscription)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1, false), 0);\n    doWorkUntilDone();;\n    EXPECT_EQ(aeron_driver_conductor_num_spy_subscriptions(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    doWorkForNs(\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2));\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 0u);\n    EXPECT_EQ(aeron_driver_conductor_num_spy_subscriptions(&m_conductor.m_conductor), 0u);\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_CLIENT_TIMEOUT, _, _))\n        .With(IsTimeout(client_id));\n\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldBeAbleToNotTimeoutSubscriptionOnKeepalive)\n{\n    int64_t client_id = nextCorrelationId();\n    int64_t sub_id = nextCorrelationId();\n\n    ASSERT_EQ(addSpySubscription(client_id, sub_id, CHANNEL_1, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n    EXPECT_EQ(aeron_driver_conductor_num_spy_subscriptions(&m_conductor.m_conductor), 1u);\n    readAllBroadcastsFromConductor(null_broadcast_handler);\n\n    int64_t timeout =\n        m_context.m_context->publication_linger_timeout_ns + (m_context.m_context->client_liveness_timeout_ns * 2);\n\n    doWorkForNs(\n        timeout,\n        100,\n        [&]()\n        {\n            clientKeepalive(client_id);\n        });\n\n    EXPECT_EQ(aeron_driver_conductor_num_clients(&m_conductor.m_conductor), 1u);\n    EXPECT_EQ(aeron_driver_conductor_num_spy_subscriptions(&m_conductor.m_conductor), 1u);\n}\n\nTEST_F(DriverConductorSpyTest, shouldAddNetworkPublicationThenSingleSpyWithSameSessionId)\n{\n    const int64_t client_id = nextCorrelationId();\n    const int64_t pub_id = nextCorrelationId();\n    const int64_t sub_id = nextCorrelationId();\n    const int32_t ipc_session_id = -4097;\n    std::string pub_channel =\n        std::string(CHANNEL_1) + \"|\" +\n        std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(ipc_session_id);\n    std::string sub_channel =\n        std::string(AERON_SPY_PREFIX) + std::string(CHANNEL_1) + \"|\" +\n        std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(ipc_session_id);\n\n    ASSERT_EQ(addPublication(client_id, pub_id, pub_channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addSubscription(client_id, sub_id, sub_channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, session_id, log_file_name.c_str(), AERON_IPC_CHANNEL));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldAddSingleSpyThenNetworkPublicationWithSameSessionId)\n{\n    const int64_t client_id = nextCorrelationId();\n    const int64_t pub_id = nextCorrelationId();\n    const int64_t sub_id = nextCorrelationId();\n    const int32_t ipc_session_id = -4097;\n    std::string pub_channel =\n        std::string(CHANNEL_1) + \"|\" + std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(ipc_session_id);\n    std::string sub_channel =\n        std::string(AERON_SPY_PREFIX) + std::string(CHANNEL_1) + \"|\" +\n            std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(ipc_session_id);\n\n    ASSERT_EQ(addSubscription(client_id, sub_id, sub_channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id, pub_channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 1u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, session_id, log_file_name.c_str(), AERON_IPC_CHANNEL));\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldNotAddNetworkPublicationThenSingleSpyWithDifferentSessionId)\n{\n    const int64_t client_id = nextCorrelationId();\n    const int64_t pub_id = nextCorrelationId();\n    const int64_t sub_id = nextCorrelationId();\n    const int32_t pub_session_id = -4097;\n    const int32_t sub_session_id = -4098;\n    std::string pub_channel =\n        std::string(CHANNEL_1) + \"|\" +\n        std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(pub_session_id);\n    std::string sub_channel =\n        std::string(AERON_SPY_PREFIX) + std::string(CHANNEL_1) + \"|\" +\n        std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(sub_session_id);\n\n    ASSERT_EQ(addPublication(client_id, pub_id, pub_channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addSubscription(client_id, sub_id, sub_channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 0u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, pub_session_id, log_file_name.c_str(), AERON_IPC_CHANNEL))\n        .Times(0);\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n\nTEST_F(DriverConductorSpyTest, shouldNotAddSingleSpyThenNetworkPublicationWithDifferentSessionId)\n{\n    const int64_t client_id = nextCorrelationId();\n    const int64_t pub_id = nextCorrelationId();\n    const int64_t sub_id = nextCorrelationId();\n    const int32_t pub_session_id = -4097;\n    const int32_t sub_session_id = -4098;\n    std::string pub_channel =\n        std::string(CHANNEL_1) + \"|\" +\n        std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(pub_session_id);\n    std::string sub_channel =\n        std::string(AERON_SPY_PREFIX) + std::string(CHANNEL_1) + \"|\" +\n        std::string(AERON_URI_SESSION_ID_KEY) + \"=\" + std::to_string(sub_session_id);\n\n    ASSERT_EQ(addSubscription(client_id, sub_id, sub_channel, STREAM_ID_1, false), 0);\n    ASSERT_EQ(addPublication(client_id, pub_id, pub_channel, STREAM_ID_1, false), 0);\n    doWorkUntilDone();\n\n    aeron_network_publication_t *publication = aeron_driver_conductor_find_network_publication(\n        &m_conductor.m_conductor, pub_id);\n    EXPECT_EQ(subscriberCount(publication), 0u);\n\n    int32_t session_id = 0;\n    std::string log_file_name;\n    testing::Sequence sequence;\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(_, _, _));\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_SUBSCRIPTION_READY, _, _))\n        .With(IsSubscriptionReady(sub_id))\n        .InSequence(sequence);\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_PUBLICATION_READY, _, _))\n        .InSequence(sequence)\n        .WillOnce(CapturePublicationReady(&session_id, &log_file_name));\n    readAllBroadcastsFromConductor(mock_broadcast_handler, 3);  // Limit the poll to capture the publication ready.\n\n    EXPECT_CALL(m_mockCallbacks, broadcastToClient(AERON_RESPONSE_ON_AVAILABLE_IMAGE, _, _))\n        .With(IsAvailableImage(_, sub_id, STREAM_ID_1, pub_session_id, log_file_name.c_str(), AERON_IPC_CHANNEL))\n        .Times(0);\n    readAllBroadcastsFromConductor(mock_broadcast_handler);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_conductor_subscribable_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeron_driver_conductor_test.h\"\n\nextern \"C\"\n{\n    void null_hook(void *clientd, volatile int64_t *value_addr)\n    {\n    }\n\n    int aeron_driver_subscribable_add_position(\n        aeron_subscribable_t *subscribable,\n        aeron_subscription_link_t *link,\n        int32_t counter_id,\n        int64_t *value_addr,\n        int64_t now_ns);\n}\n\nclass DriverConductorSubscribableTest : public DriverConductorTest, public testing::Test\n{\n\nprotected:\n    aeron_subscribable_t m_subscribable = {};\n\n    void TearDown() override\n    {\n        aeron_free(m_subscribable.array);\n    }\n\n    static aeron_tetherable_position_t *findTetherablePosition(aeron_subscribable_t *subscribable, int32_t counter_id)\n    {\n        for (size_t i = 0; i < subscribable->length; i++)\n        {\n            aeron_tetherable_position_t *position = &subscribable->array[i];\n            if (position->counter_id == counter_id)\n            {\n                return position;\n            }\n        }\n\n        return nullptr;\n    }\n\n    static void no_op_log(\n        aeron_tetherable_position_t *tetherable_position,\n        int64_t now_ns,\n        aeron_subscription_tether_state_t old_state,\n        aeron_subscription_tether_state_t new_state,\n        int32_t stream_id,\n        int32_t session_id)\n    {\n    }\n};\n\nTEST_F(DriverConductorSubscribableTest, shouldHaveNoWorkingWhenOnlySubscriptionIsResting)\n{\n    int64_t now_ns = 908234769237;\n\n    m_subscribable.array = nullptr;\n    m_subscribable.length = 0;\n    m_subscribable.capacity = 0;\n    m_subscribable.add_position_hook_func = null_hook;\n    m_subscribable.remove_position_hook_func = null_hook;\n    m_subscribable.clientd = nullptr;\n\n    ASSERT_FALSE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n\n    aeron_subscription_link_t untethered_link = {};\n    strcpy(untethered_link.channel, \"aeron:ipc\");\n    untethered_link.is_tether = false;\n    const int32_t untethered_link_counter_id = 276342;\n    aeron_driver_subscribable_add_position(\n        &m_subscribable, &untethered_link, untethered_link_counter_id, nullptr, now_ns);\n\n    aeron_tetherable_position_t *position;\n\n    int32_t stream_id = 42;\n    int32_t session_id = -188;\n\n    position = findTetherablePosition(&m_subscribable, untethered_link_counter_id);\n    ASSERT_TRUE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n\n    aeron_driver_subscribable_state(&m_subscribable, position, AERON_SUBSCRIPTION_TETHER_LINGER, now_ns, stream_id, session_id, no_op_log);\n    position = findTetherablePosition(&m_subscribable, untethered_link_counter_id);\n    ASSERT_TRUE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n\n    aeron_driver_subscribable_state(&m_subscribable, position, AERON_SUBSCRIPTION_TETHER_RESTING, now_ns, stream_id, session_id, no_op_log);\n    position = findTetherablePosition(&m_subscribable, untethered_link_counter_id);\n    ASSERT_FALSE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n\n    aeron_driver_subscribable_state(&m_subscribable, position, AERON_SUBSCRIPTION_TETHER_ACTIVE, now_ns, stream_id, session_id, no_op_log);\n    position = findTetherablePosition(&m_subscribable, untethered_link_counter_id);\n    ASSERT_TRUE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n}\n\nTEST_F(DriverConductorSubscribableTest, shouldHaveWorkingWhenOneSubscriptionRestingWithOtherInDifferentStates)\n{\n    int64_t now_ns = 908234769237;\n\n    m_subscribable.array = nullptr;\n    m_subscribable.length = 0;\n    m_subscribable.capacity = 0;\n    m_subscribable.add_position_hook_func = null_hook;\n    m_subscribable.remove_position_hook_func = null_hook;\n    m_subscribable.clientd = nullptr;\n    aeron_tetherable_position_t *position;\n\n    int32_t stream_id = 5;\n    int32_t session_id = 0;\n\n    aeron_subscription_link_t resting_link = {};\n    strcpy(resting_link.channel, \"aeron:ipc\");\n    resting_link.is_tether = false;\n    const int32_t resting_link_counter_id = 276342;\n    aeron_driver_subscribable_add_position(\n        &m_subscribable, &resting_link, resting_link_counter_id, nullptr, now_ns);\n    position = findTetherablePosition(&m_subscribable, resting_link_counter_id);\n    aeron_driver_subscribable_state(&m_subscribable, position, AERON_SUBSCRIPTION_TETHER_RESTING, now_ns, stream_id, session_id, no_op_log);\n\n    aeron_subscription_link_t active_link = {};\n    strcpy(active_link.channel, \"aeron:ipc\");\n    active_link.is_tether = false;\n    const int32_t active_link_counter_id = 276343;\n    aeron_driver_subscribable_add_position(\n        &m_subscribable, &active_link, active_link_counter_id, nullptr, now_ns);\n    position = findTetherablePosition(&m_subscribable, active_link_counter_id);\n    aeron_driver_subscribable_state(&m_subscribable, position, AERON_SUBSCRIPTION_TETHER_ACTIVE, now_ns, stream_id, session_id, no_op_log);\n\n    aeron_subscription_link_t lingering_link = {};\n    strcpy(lingering_link.channel, \"aeron:ipc\");\n    lingering_link.is_tether = false;\n    const int32_t lingering_link_counter_id = 276344;\n    aeron_driver_subscribable_add_position(\n        &m_subscribable, &lingering_link, lingering_link_counter_id, nullptr, now_ns);\n    position = findTetherablePosition(&m_subscribable, lingering_link_counter_id);\n    aeron_driver_subscribable_state(&m_subscribable, position, AERON_SUBSCRIPTION_TETHER_LINGER, now_ns, stream_id, session_id, no_op_log);\n\n    ASSERT_TRUE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n\n    aeron_driver_subscribable_remove_position(&m_subscribable, active_link_counter_id);\n    ASSERT_TRUE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n\n    aeron_driver_subscribable_remove_position(&m_subscribable, lingering_link_counter_id);\n    ASSERT_FALSE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n\n    aeron_driver_subscribable_add_position(\n        &m_subscribable, &active_link, active_link_counter_id, nullptr, now_ns);\n    aeron_driver_subscribable_state(&m_subscribable, position, AERON_SUBSCRIPTION_TETHER_ACTIVE, now_ns, stream_id, session_id, no_op_log);\n    ASSERT_TRUE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n\n    aeron_driver_subscribable_remove_position(&m_subscribable, resting_link_counter_id);\n    ASSERT_TRUE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n\n    aeron_driver_subscribable_remove_position(&m_subscribable, active_link_counter_id);\n    ASSERT_FALSE(aeron_driver_subscribable_has_working_positions(&m_subscribable));\n}\n\nTEST_F(DriverConductorSubscribableTest, shouldApplyMultipleRandomActionsAndEnsureThatTheStateIsCorrectlyManaged)\n{\n    int64_t now_ns = 908234769237;\n\n    m_subscribable.array = nullptr;\n    m_subscribable.length = 0;\n    m_subscribable.capacity = 0;\n    m_subscribable.add_position_hook_func = null_hook;\n    m_subscribable.remove_position_hook_func = null_hook;\n    m_subscribable.clientd = nullptr;\n    \n    int32_t iterations = 1000;\n    int32_t counter_id = 0;\n    int32_t stream_id = -165;\n    int32_t session_id = 573485834;\n\n    for (int32_t i = 0; i < iterations; i++)\n    {\n        switch ((static_cast<uint32_t>(aeron_randomised_int32()) % 3))\n        {\n            case 0: //add\n            {\n                aeron_subscription_link_t link = {};\n                strcpy(link.channel, \"aeron:ipc\");\n                link.is_tether = false;\n                const int32_t active_link_counter_id = ++counter_id;\n                aeron_driver_subscribable_add_position(\n                    &m_subscribable, &link, active_link_counter_id, nullptr, now_ns);\n                aeron_tetherable_position_t *position = findTetherablePosition(&m_subscribable, active_link_counter_id);\n                aeron_driver_subscribable_state(&m_subscribable, position, AERON_SUBSCRIPTION_TETHER_ACTIVE, now_ns, stream_id, session_id, no_op_log);\n\n                break;\n            }\n\n            case 1: //remove\n            {\n                if (0 != m_subscribable.length)\n                {\n                    size_t index = (static_cast<uint32_t>(aeron_randomised_int32()) % m_subscribable.length);\n                    int32_t counter_id_to_remove = m_subscribable.array[index].counter_id;\n                    aeron_driver_subscribable_remove_position(&m_subscribable, counter_id_to_remove);\n                }\n                break;\n            }\n\n            case 2: //change state\n            {\n                if (0 != m_subscribable.length)\n                {\n                    size_t index = (static_cast<uint32_t>(aeron_randomised_int32()) % m_subscribable.length);\n                    int state_as_int = (static_cast<uint32_t>(aeron_randomised_int32()) % 3);\n                    auto state = static_cast<aeron_subscription_tether_state_t>(state_as_int);\n                    aeron_tetherable_position_t *position = &m_subscribable.array[index];\n\n                    aeron_driver_subscribable_state(&m_subscribable, position, state, 0, stream_id, session_id, no_op_log);\n                }\n                break;\n            }\n\n            default:\n                FAIL();\n        }\n\n        size_t resting_count = 0;\n        size_t working_count = 0;\n        for (int j = (int)m_subscribable.length - 1; j >= 0; j--)\n        {\n            if (AERON_SUBSCRIPTION_TETHER_RESTING == m_subscribable.array[j].state)\n            {\n                resting_count++;\n            }\n            else\n            {\n                working_count++;\n            }\n        }\n\n        ASSERT_EQ(resting_count, m_subscribable.inactive_count) << i;\n        ASSERT_EQ(working_count, aeron_driver_subscribable_working_position_count(&m_subscribable));\n        ASSERT_LE(m_subscribable.inactive_count, m_subscribable.length);\n        ASSERT_EQ(resting_count == m_subscribable.length, !aeron_driver_subscribable_has_working_positions(&m_subscribable));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_conductor_test.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_DRIVER_CONDUCTOR_TEST_H\n#define AERON_DRIVER_CONDUCTOR_TEST_H\n\n#include <array>\n#include <cstdint>\n#include <thread>\n#include <exception>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\nextern \"C\"\n{\n#include \"aeron_driver_conductor.h\"\n#include \"util/aeron_error.h\"\n#include \"aeron_driver_sender.h\"\n#include \"aeron_driver_receiver.h\"\n#include \"aeron_counters.h\"\n#include \"concurrent/aeron_broadcast_receiver.h\"\n#include \"concurrent/aeron_counters_manager.h\"\n#include \"concurrent/aeron_mpsc_rb.h\"\n}\n\n#define STR_HELPER(x) #x\n#define STR(x) STR_HELPER(x)\n\n#define CHANNEL_1 \"aeron:udp?endpoint=localhost:10001\"\n#define CHANNEL_1_UNRELIABLE \"aeron:udp?endpoint=localhost:10001|reliable=false\"\n#define CHANNEL_2 \"aeron:udp?endpoint=localhost:10002\"\n#define CHANNEL_3 \"aeron:udp?endpoint=localhost:10003\"\n#define CHANNEL_4 \"aeron:udp?endpoint=localhost:10004\"\n#define CHANNEL_MDC_MANUAL \"aeron:udp?control-mode=manual\"\n#define INVALID_URI \"aeron:udp://\"\n\n#define STREAM_ID_1 (101)\n#define STREAM_ID_2 (102)\n#define STREAM_ID_3 (103)\n#define STREAM_ID_4 (104)\n\n#define SESSION_ID_1_ 1000\n\n#define MTU_1 (4096)\n#define MTU_2 (8192)\n\n#define CHANNEL_1_WITH_TAG_1001 \"aeron:udp?endpoint=localhost:10001|tags=1001\"\n#define CHANNEL_TAG_1001 \"aeron:udp?tags=1001\"\n\n#define CHANNEL_1_WITH_SESSION_ID_1 \"aeron:udp?endpoint=localhost:10001|session-id=\" STR(SESSION_ID_1_)\n\n#define SESSION_ID_1 (SESSION_ID_1_)\n#define SESSION_ID_3 (100000)\n#define SESSION_ID_4 (100002)\n#define SESSION_ID_5 (100003)\n\n#define SESSION_ID (0x5E5510)\n#define INITIAL_TERM_ID (0x3456)\n\n#define TERM_LENGTH (64 * 1024)\n\n#define SRC_IP_ADDR \"127.0.0.1\"\n#define SRC_UDP_PORT (43657)\n#define SOURCE_IDENTITY \"127.0.0.1:43657\"\n\n#define CONTROL_IP_ADDR \"127.0.0.1\"\n#define CONTROL_UDP_PORT (43657)\n\n#define TEST_CONDUCTOR_CYCLE_TIME_THRESHOLD (600 * 1000 * 1000)\n\n#define CAPACITY (32 * 1024)\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\n\nstatic int64_t nano_time = 0;\nstatic bool free_map_raw_log = true;\n\nstatic int64_t test_nano_clock()\n{\n    return nano_time;\n}\n\nstatic int64_t test_epoch_clock()\n{\n    return nano_time / (1000 * 1000);\n}\n\nstatic void test_set_nano_time(int64_t timestamp_ns)\n{\n    nano_time = timestamp_ns;\n}\n\nstatic void test_increment_nano_time(int64_t delta_ns)\n{\n    nano_time += delta_ns;\n}\n\nstatic int test_malloc_raw_log_map(\n    aeron_mapped_raw_log_t *log, const char *path, bool use_sparse_file, uint64_t term_length, uint64_t page_size)\n{\n    uint64_t log_length = aeron_logbuffer_compute_log_length(term_length, page_size);\n\n    log->mapped_file.length = 0;\n    log->mapped_file.addr = malloc(log_length);\n\n    memset(log->mapped_file.addr, 0, log_length);\n\n    for (size_t i = 0; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n    {\n        log->term_buffers[i].addr = (uint8_t *)log->mapped_file.addr + (i * term_length);\n        log->term_buffers[i].length = term_length;\n    }\n\n    log->log_meta_data.addr = (uint8_t *)log->mapped_file.addr + (log_length - AERON_LOGBUFFER_META_DATA_LENGTH);\n    log->log_meta_data.length = AERON_LOGBUFFER_META_DATA_LENGTH;\n\n    log->term_length = term_length;\n\n    return 0;\n}\n\nstatic int test_malloc_raw_log_close(aeron_mapped_raw_log_t *log, const char *filename)\n{\n    free(log->mapped_file.addr);\n    log->mapped_file.addr = nullptr;\n    return 0;\n}\n\nstatic bool test_malloc_raw_log_free(aeron_mapped_raw_log_t *log, const char *filename)\n{\n    if (free_map_raw_log)\n    {\n        test_malloc_raw_log_close(log, filename);\n        return true;\n    }\n    return false;\n}\n\nstatic uint64_t test_uint64_max_usable_fs_space(const char *path)\n{\n    return UINT64_MAX;\n}\n\nclass DriverCallbacks\n{\npublic:\n    virtual ~DriverCallbacks() = default;\n\n    virtual void broadcastToClient(int32_t type_id, uint8_t *buffer, size_t length) = 0;\n\n    virtual void onCounter(\n        int32_t id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_len,\n        const uint8_t *label,\n        size_t label_len) = 0;\n};\n\nclass MockDriverCallbacks : public DriverCallbacks\n{\npublic:\n    MOCK_METHOD3(broadcastToClient, void(int32_t, uint8_t *, size_t));\n    MOCK_METHOD6(onCounter, void(int32_t, int32_t, const uint8_t *, size_t, const uint8_t *, size_t));\n};\n\nvoid mock_broadcast_handler(int32_t type_id, uint8_t *buffer, size_t length, void *clientd)\n{\n    auto *callback = static_cast<DriverCallbacks *>(clientd);\n    callback->broadcastToClient(type_id, buffer, length);\n}\n\nvoid null_broadcast_handler(int32_t type_id, uint8_t *buffer, size_t length, void *clientd)\n{\n}\n\nvoid mock_counter_handler(\n    int32_t id,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_len,\n    const uint8_t *label,\n    size_t label_len,\n    void *clientd)\n{\n    auto *callback = static_cast<DriverCallbacks *>(clientd);\n    callback->onCounter(id, type_id, key, key_len, label, label_len);\n}\n\nstruct TestDriverContext\n{\n    TestDriverContext()\n    {\n        test_set_nano_time(0); /* single threaded */\n\n        if (aeron_driver_context_init(&m_context) < 0)\n        {\n            throw std::runtime_error(\"could not init context: \" + std::string(aeron_errmsg()));\n        }\n\n        m_context->threading_mode = AERON_THREADING_MODE_SHARED;\n        m_context->async_executor_threads = 0;\n        m_context->cnc_map.length = aeron_cnc_length(m_context);\n        m_cnc = std::unique_ptr<uint8_t[]>(new uint8_t[m_context->cnc_map.length]);\n        m_context->cnc_map.addr = m_cnc.get();\n\n        memset(m_context->cnc_map.addr, 0, m_context->cnc_map.length);\n\n        aeron_driver_fill_cnc_metadata(m_context);\n\n        m_context->term_buffer_length = TERM_LENGTH;\n        m_context->ipc_term_buffer_length = TERM_LENGTH;\n        m_context->term_buffer_sparse_file = true;\n\n        /* control time */\n        m_context->nano_clock = test_nano_clock;\n        m_context->epoch_clock = test_epoch_clock;\n\n        /* control files */\n        m_context->usable_fs_space_func = test_uint64_max_usable_fs_space;\n        m_context->raw_log_map_func = test_malloc_raw_log_map;\n        m_context->raw_log_close_func = test_malloc_raw_log_close;\n        m_context->raw_log_free_func = test_malloc_raw_log_free;\n\n        aeron_driver_context_set_conductor_cycle_threshold_ns(m_context, TEST_CONDUCTOR_CYCLE_TIME_THRESHOLD);\n    }\n\n    virtual ~TestDriverContext()\n    {\n        m_context->cnc_map.addr = nullptr;\n        aeron_driver_context_close(m_context);\n    }\n\n    aeron_driver_context_t *m_context = nullptr;\n    aeron_counters_manager_t m_counters_manager = {};\n    aeron_system_counters_t m_system_counters = {};\n    aeron_distinct_error_log_t m_error_log = {};\n    AERON_DECL_ALIGNED(buffer_t m_error_log_buffer, 16) = {};\n\n    std::unique_ptr<uint8_t[]> m_cnc;\n};\n\nstruct TestDriverConductor\n{\n    explicit TestDriverConductor(TestDriverContext &context)\n    {\n        if (aeron_driver_conductor_init(&m_conductor, context.m_context) < 0)\n        {\n            throw std::runtime_error(\"could not init context: \" + std::string(aeron_errmsg()));\n        }\n\n        context.m_context->conductor_proxy = &m_conductor.conductor_proxy;\n\n        if (aeron_driver_sender_init(\n            &m_sender, context.m_context, &m_conductor.system_counters, &m_conductor.error_log) < 0)\n        {\n            throw std::runtime_error(\"could not init sender: \" + std::string(aeron_errmsg()));\n        }\n\n        context.m_context->sender_proxy = &m_sender.sender_proxy;\n\n        if (aeron_driver_receiver_init(\n            &m_receiver, context.m_context, &m_conductor.system_counters, &m_conductor.error_log) < 0)\n        {\n            throw std::runtime_error(\"could not init receiver: \" + std::string(aeron_errmsg()));\n        }\n\n        context.m_context->receiver_proxy = &m_receiver.receiver_proxy;\n        m_destination.has_control_addr = false;\n    }\n\n    virtual ~TestDriverConductor()\n    {\n        aeron_driver_conductor_on_close(&m_conductor);\n        aeron_driver_sender_on_close(&m_sender);\n        aeron_driver_receiver_on_close(&m_receiver);\n    }\n\n    void manuallySetNextSessionId(int32_t nextSessionId)\n    {\n        m_conductor.next_session_id = nextSessionId;\n    }\n\n    aeron_driver_conductor_t m_conductor = {};\n    aeron_driver_sender_t m_sender = {};\n    aeron_driver_receiver_t m_receiver = {};\n    aeron_receive_destination_t m_destination = {};\n};\n\nclass DriverConductorTest\n{\npublic:\n\n    DriverConductorTest() :\n        m_conductor(m_context)\n    {\n        aeron_mpsc_rb_init(\n            &m_to_driver, m_context.m_context->to_driver_buffer, m_context.m_context->to_driver_buffer_length);\n        aeron_broadcast_receiver_init(\n            &m_broadcast_receiver,\n            m_context.m_context->to_clients_buffer,\n            m_context.m_context->to_clients_buffer_length);\n\n        int64_t free_to_reuse_timeout_ms = 0;\n        if (m_context.m_context->counter_free_to_reuse_ns > 0)\n        {\n            free_to_reuse_timeout_ms = (int64_t)m_context.m_context->counter_free_to_reuse_ns / (1000 * 1000LL);\n            free_to_reuse_timeout_ms = free_to_reuse_timeout_ms <= 0 ? 1 : free_to_reuse_timeout_ms;\n        }\n\n        aeron_counters_manager_init(\n            &m_context.m_counters_manager,\n            m_context.m_context->counters_metadata_buffer,\n            AERON_COUNTERS_METADATA_BUFFER_LENGTH(m_context.m_context->counters_values_buffer_length),\n            m_context.m_context->counters_values_buffer,\n            m_context.m_context->counters_values_buffer_length,\n            m_context.m_context->cached_clock,\n            free_to_reuse_timeout_ms);\n\n        aeron_system_counters_init(&m_context.m_system_counters, &m_context.m_counters_manager);\n\n        aeron_distinct_error_log_init(\n            &m_context.m_error_log, m_context.m_error_log_buffer.data(), m_context.m_error_log_buffer.size(), aeron_epoch_clock);\n\n        m_context.m_context->counters_manager = &m_context.m_counters_manager;\n        m_context.m_context->system_counters = &m_context.m_system_counters;\n        m_context.m_context->error_log = &m_context.m_error_log;\n        m_context.m_context->error_buffer = m_context.m_error_log_buffer.data();\n        m_context.m_context->error_buffer_length = m_context.m_error_log_buffer.size();\n    }\n\n    ~DriverConductorTest()\n    {\n        aeron_broadcast_receiver_close(&m_broadcast_receiver);\n        aeron_system_counters_close(&m_context.m_system_counters);\n        aeron_counters_manager_close(&m_context.m_counters_manager);\n        aeron_distinct_error_log_close(&m_context.m_error_log);\n    }\n\n    size_t readAllBroadcastsFromConductor(aeron_broadcast_receiver_handler_t handler, size_t message_limit = SIZE_MAX)\n    {\n        size_t messages = 0;\n        while (messages < message_limit &&\n            0 != aeron_broadcast_receiver_receive(&m_broadcast_receiver, handler, &m_mockCallbacks))\n        {\n            messages++;\n        }\n\n        return messages;\n    }\n\n    size_t readCounters(aeron_counters_reader_foreach_metadata_func_t callback)\n    {\n        aeron_driver_context_t *ctx = m_context.m_context;\n        aeron_counters_reader_t reader;\n        aeron_counters_reader_init(\n            &reader,\n            ctx->counters_metadata_buffer,\n            AERON_COUNTERS_METADATA_BUFFER_LENGTH(ctx->counters_values_buffer_length),\n            ctx->counters_values_buffer,\n            ctx->counters_values_buffer_length);\n\n        aeron_counters_reader_foreach_metadata(\n            reader.metadata, reader.metadata_length, callback, &m_mockCallbacks);\n        return 0;\n    }\n\n    int64_t nextCorrelationId()\n    {\n        return aeron_mpsc_rb_next_correlation_id(&m_to_driver);\n    }\n\n    inline int writeCommand(int32_t msg_type_id, size_t length)\n    {\n        return aeron_mpsc_rb_write(&m_to_driver, msg_type_id, m_command_buffer, length);\n    }\n\n    int addIpcPublication(int64_t client_id, int64_t correlation_id, int32_t stream_id, bool is_exclusive)\n    {\n        return addPublication(client_id, correlation_id, AERON_IPC_CHANNEL, stream_id, is_exclusive);\n    }\n\n    int addPublication(\n        int64_t client_id, int64_t correlation_id, std::string & channel, int32_t stream_id, bool is_exclusive)\n    {\n        return addPublication(client_id, correlation_id, channel.c_str(), stream_id, is_exclusive);\n    }\n\n    int addPublication(\n        int64_t client_id, int64_t correlation_id, const char *channel, int32_t stream_id, bool is_exclusive)\n    {\n        auto *cmd = reinterpret_cast<aeron_publication_command_t *>(m_command_buffer);\n\n        int32_t msg_type_id = is_exclusive ? AERON_COMMAND_ADD_EXCLUSIVE_PUBLICATION : AERON_COMMAND_ADD_PUBLICATION;\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->stream_id = stream_id;\n        cmd->channel_length = (int32_t)strlen(channel);\n        memcpy(m_command_buffer + sizeof(aeron_publication_command_t), channel, (size_t)cmd->channel_length);\n\n        return writeCommand(msg_type_id, sizeof(aeron_publication_command_t) + cmd->channel_length);\n    }\n\n    int removePublication(int64_t client_id, int64_t correlation_id, int64_t registration_id)\n    {\n        auto *cmd = reinterpret_cast<aeron_remove_publication_command_t *>(m_command_buffer);\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->registration_id = registration_id;\n        cmd->flags = 0;\n\n        return writeCommand(AERON_COMMAND_REMOVE_PUBLICATION, sizeof(aeron_remove_publication_command_t));\n    }\n\n    int addIpcSubscription(int64_t client_id, int64_t correlation_id, int32_t stream_id, int64_t registration_id)\n    {\n        return addNetworkSubscription(client_id, correlation_id, AERON_IPC_CHANNEL, stream_id);\n    }\n\n    int addSubscription(\n        int64_t client_id, int64_t correlation_id, std::string &channel, int32_t stream_id, int64_t registration_id)\n    {\n        return addNetworkSubscription(client_id, correlation_id, channel.c_str(), stream_id);\n    }\n\n    int addNetworkSubscription(int64_t client_id, int64_t correlation_id, const char *channel, int32_t stream_id)\n    {\n        auto *cmd = reinterpret_cast<aeron_subscription_command_t *>(m_command_buffer);\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->stream_id = stream_id;\n        cmd->registration_correlation_id = -1;\n        cmd->channel_length = (int32_t)strlen(channel);\n        memcpy(m_command_buffer + sizeof(aeron_subscription_command_t), channel, (size_t)cmd->channel_length);\n\n        return writeCommand(AERON_COMMAND_ADD_SUBSCRIPTION, sizeof(aeron_subscription_command_t) + cmd->channel_length);\n    }\n\n    int addSpySubscription(\n        int64_t client_id, int64_t correlation_id, const char *channel, int32_t stream_id, int64_t registration_id)\n    {\n        std::string channel_str(AERON_SPY_PREFIX + std::string(channel));\n        return addNetworkSubscription(client_id, correlation_id, channel_str.c_str(), stream_id);\n    }\n\n    int removeSubscription(int64_t client_id, int64_t correlation_id, int64_t registration_id)\n    {\n        auto *cmd = reinterpret_cast<aeron_remove_subscription_command_t *>(m_command_buffer);\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->registration_id = registration_id;\n\n        return writeCommand(AERON_COMMAND_REMOVE_SUBSCRIPTION, sizeof(aeron_remove_subscription_command_t));\n    }\n\n    int clientKeepalive(int64_t client_id)\n    {\n        auto *cmd = reinterpret_cast<aeron_correlated_command_t *>(m_command_buffer);\n\n        cmd->client_id = client_id;\n\n        return writeCommand(AERON_COMMAND_CLIENT_KEEPALIVE, sizeof(aeron_correlated_command_t));\n    }\n\n    int addCounter(\n        int64_t client_id,\n        int64_t correlation_id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_length,\n        std::string &label)\n    {\n        auto *cmd = reinterpret_cast<aeron_counter_command_t *>(m_command_buffer);\n        auto *cmd_ptr = reinterpret_cast<uint8_t *>(m_command_buffer);\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->type_id = type_id;\n\n        uint8_t *cursor = cmd_ptr + sizeof(aeron_counter_command_t);\n        memcpy(cursor, &key_length, sizeof(int32_t));\n        cursor += sizeof(int32_t);\n        memcpy(cursor, key, key_length);\n        cursor += AERON_ALIGN(key_length, sizeof(int32_t));\n\n        size_t label_len = label.length();\n        memcpy(cursor, &label_len, sizeof(int32_t));\n        cursor += sizeof(int32_t);\n        memcpy(cursor, label.c_str(), label_len);\n        cursor += label_len;\n\n        size_t message_len = cursor - cmd_ptr;\n        return writeCommand(AERON_COMMAND_ADD_COUNTER, message_len);\n    }\n\n    int removeCounter(int64_t client_id, int64_t correlation_id, int64_t registration_id)\n    {\n        auto *cmd = reinterpret_cast<aeron_remove_counter_command_t *>(m_command_buffer);\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->registration_id = registration_id;\n\n        return writeCommand(AERON_COMMAND_REMOVE_COUNTER, sizeof(aeron_remove_counter_command_t));\n    }\n\n    int addDestination(\n        int64_t client_id, int64_t correlation_id, int64_t publication_registration_id, const char *channel)\n    {\n        auto *cmd = reinterpret_cast<aeron_destination_command_t *>(m_command_buffer);\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->registration_id = publication_registration_id;\n        cmd->channel_length = (int32_t)strlen(channel);\n        memcpy(m_command_buffer + sizeof(aeron_destination_command_t), channel, (size_t)cmd->channel_length);\n\n        return writeCommand(AERON_COMMAND_ADD_DESTINATION, sizeof(aeron_destination_command_t) + cmd->channel_length);\n    }\n\n    int addReceiveDestination(int64_t client_id, int64_t correlation_id, int64_t subscription_id, const char *channel)\n    {\n        auto *cmd = reinterpret_cast<aeron_destination_command_t *>(m_command_buffer);\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->registration_id = subscription_id;\n        cmd->channel_length = (int32_t)strlen(channel);\n        memcpy(m_command_buffer + sizeof(aeron_destination_command_t), channel, (size_t)cmd->channel_length);\n\n        return writeCommand(AERON_COMMAND_ADD_RCV_DESTINATION, sizeof(aeron_destination_command_t) + cmd->channel_length);\n    }\n\n    int removeReceiveDestination(int64_t client_id, int64_t correlation_id, int64_t subscription_id, const char *channel)\n    {\n        auto *cmd = reinterpret_cast<aeron_destination_command_t *>(m_command_buffer);\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->registration_id = subscription_id;\n        cmd->channel_length = (int32_t)strlen(channel);\n        memcpy(m_command_buffer + sizeof(aeron_destination_command_t), channel, (size_t)cmd->channel_length);\n\n        return writeCommand(AERON_COMMAND_REMOVE_RCV_DESTINATION, sizeof(aeron_destination_command_t) + cmd->channel_length);\n    }\n\n    int removeDestination(\n        int64_t client_id, int64_t correlation_id, int64_t publication_registration_id, const char *channel)\n    {\n        auto *cmd = reinterpret_cast<aeron_destination_command_t *>(m_command_buffer);\n\n        cmd->correlated.client_id = client_id;\n        cmd->correlated.correlation_id = correlation_id;\n        cmd->registration_id = publication_registration_id;\n        cmd->channel_length = (int32_t)strlen(channel);\n        memcpy(m_command_buffer + sizeof(aeron_destination_command_t), channel, (size_t)cmd->channel_length);\n\n        return writeCommand(AERON_COMMAND_REMOVE_DESTINATION, sizeof(aeron_destination_command_t) + cmd->channel_length);\n    }\n\n    int closeClient(int64_t client_id)\n    {\n        auto *cmd = reinterpret_cast<aeron_correlated_command_t *>(m_command_buffer);\n\n        cmd->client_id = client_id;\n        cmd->correlation_id = 0;\n\n        return writeCommand(AERON_COMMAND_CLIENT_CLOSE, sizeof(aeron_correlated_command_t));\n    }\n\n    int doWork()\n    {\n        return aeron_driver_conductor_do_work(&m_conductor.m_conductor);\n    }\n\n    void doWorkUntilDone()\n    {\n        while (true)\n        {\n            if (0 == doWork())\n            {\n                break;\n            }\n        }\n    }\n\n    void doWorkForNs(int64_t duration_ns, int64_t num_increments = 100, std::function<void()> func = [](){})\n    {\n        int64_t initial_ns = test_nano_clock();\n        int64_t increment_ns = duration_ns / num_increments;\n\n        if (increment_ns <= 0)\n        {\n            throw std::runtime_error(\"increment must be positive\");\n        }\n\n        do\n        {\n            test_increment_nano_time(increment_ns);\n            func();\n            doWork();\n        }\n        while ((test_nano_clock() - initial_ns) <= duration_ns);\n    }\n\n    static void fill_sockaddr_ipv4(struct sockaddr_storage *addr, const char *ip, unsigned short int port)\n    {\n        auto *ipv4addr = (struct sockaddr_in *)addr;\n\n        ipv4addr->sin_family = AF_INET;\n        if (inet_pton(AF_INET, ip, &ipv4addr->sin_addr) != 1)\n        {\n            throw std::runtime_error(\"can't get IPv4 address\");\n        }\n        ipv4addr->sin_port = htons(port);\n    }\n\n    void createPublicationImage(aeron_receive_channel_endpoint_t *endpoint, int32_t stream_id, int64_t position)\n    {\n        aeron_command_create_publication_image_t cmd;\n        auto position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes(TERM_LENGTH);\n\n        cmd.base.func = aeron_driver_conductor_on_create_publication_image;\n        cmd.base.item = nullptr;\n        cmd.endpoint = endpoint;\n        cmd.destination = &m_conductor.m_destination;\n        cmd.session_id = SESSION_ID;\n        cmd.stream_id = stream_id;\n        cmd.term_offset = 0;\n        cmd.active_term_id = aeron_logbuffer_compute_term_id_from_position(\n            position, position_bits_to_shift, INITIAL_TERM_ID);\n        cmd.initial_term_id = INITIAL_TERM_ID;\n        cmd.mtu_length = (int32_t)m_context.m_context->mtu_length;\n        cmd.term_length = TERM_LENGTH;\n\n        fill_sockaddr_ipv4(&cmd.src_address, SRC_IP_ADDR, SRC_UDP_PORT);\n        fill_sockaddr_ipv4(&cmd.control_address, CONTROL_IP_ADDR, CONTROL_UDP_PORT);\n\n        aeron_driver_conductor_on_create_publication_image(&m_conductor.m_conductor, &cmd);\n    }\n\nprotected:\n    uint8_t m_command_buffer[128 * 1024] = {};\n    TestDriverContext m_context = {};\n    TestDriverConductor m_conductor;\n    aeron_broadcast_receiver_t m_broadcast_receiver = {};\n    aeron_mpsc_rb_t m_to_driver = {};\n    MockDriverCallbacks m_mockCallbacks = {};\n};\n\nvoid aeron_image_buffers_ready_get_log_file_name(\n    const aeron_image_buffers_ready_t *msg, const char **log_file_name, int32_t *log_file_name_len)\n{\n    uint8_t *log_file_name_ptr = ((uint8_t *)msg) + sizeof(aeron_image_buffers_ready_t);\n    memcpy(log_file_name_len, log_file_name_ptr, sizeof(int32_t));\n    *log_file_name = reinterpret_cast<const char *>(log_file_name_ptr + sizeof(int32_t));\n}\n\nvoid aeron_publication_buffers_ready_get_log_file_name(\n    const aeron_publication_buffers_ready_t *msg, const char **log_file_name, int32_t *log_file_name_len)\n{\n    *log_file_name_len = msg->log_file_length;\n    *log_file_name = ((char *)msg) + sizeof(aeron_publication_buffers_ready_t);\n}\n\nvoid aeron_image_buffers_ready_get_source_identity(\n    const aeron_image_buffers_ready_t *msg, const char **source_identity, int32_t *source_identity_len)\n{\n    uint8_t *log_file_name_ptr = ((uint8_t *)msg) + sizeof(aeron_image_buffers_ready_t);\n    int32_t log_file_name_len;\n    memcpy(&log_file_name_len, log_file_name_ptr, sizeof(int32_t));\n    int32_t aligned_log_file_name_len = AERON_ALIGN(log_file_name_len, sizeof(int32_t));\n    uint8_t *source_identity_ptr = log_file_name_ptr + sizeof(int32_t) + aligned_log_file_name_len;\n    memcpy(source_identity_len, source_identity_ptr, sizeof(int32_t));\n    *source_identity = reinterpret_cast<const char *>(source_identity_ptr + sizeof(int32_t));\n}\n\nvoid aeron_image_message_get_channel(\n    const aeron_image_message_t *msg, const char **channel, int32_t *channel_len)\n{\n    uint8_t *channel_ptr = ((uint8_t *)msg) + sizeof(aeron_image_message_t);\n    *channel_len = msg->channel_length;\n    *channel = reinterpret_cast<const char *>(channel_ptr);\n}\n\nMATCHER_P(\n    IsSubscriptionReady,\n    correlation_id,\n    std::string(\"IsSubscriptionReady: correlationId = \").append(testing::PrintToString(correlation_id)))\n{\n    const aeron_subscription_ready_t *response = reinterpret_cast<aeron_subscription_ready_t *>(std::get<1>(arg));\n    const bool result = response->correlation_id == correlation_id;\n\n    if (!result)\n    {\n        *result_listener << \"response.correlation_id = \" << response->correlation_id;\n    }\n\n    return result;\n}\n\nMATCHER_P(\n    IsError,\n    correlation_id,\n    std::string(\"IsError: correlation_id = \").append(testing::PrintToString(correlation_id)))\n{\n    const aeron_error_response_t *response = reinterpret_cast<aeron_error_response_t *>(std::get<1>(arg));\n    const bool result = response->offending_command_correlation_id == correlation_id;\n\n    if (!result)\n    {\n        *result_listener << \"response.offending_command_correlation_id = \" << response->offending_command_correlation_id;\n    }\n\n    return result;\n}\n\nMATCHER_P(\n    IsOperationSuccess,\n    correlation_id,\n    std::string(\"IsOperationSuccess: correlationId = \").append(testing::PrintToString(correlation_id)))\n{\n    const aeron_operation_succeeded_t *response  = reinterpret_cast<aeron_operation_succeeded_t *>(std::get<1>(arg));\n    const bool result = response->correlation_id == correlation_id;\n\n    if (!result)\n    {\n        *result_listener << \"response.correlationId() = \" << response->correlation_id;\n    }\n\n    return result;\n}\n\nMATCHER_P3(\n    IsPublicationReady,\n    correlation_id,\n    stream_id,\n    session_id,\n    std::string(\"IsPublicationReady: correlationId = \").append(testing::PrintToString(correlation_id))\n        .append(\", streamId = \").append(testing::DescribeMatcher<int32_t>(stream_id))\n        .append(\", sessionId = \").append(testing::DescribeMatcher<int32_t>(session_id)))\n{\n    const aeron_publication_buffers_ready_t *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(\n        std::get<1>(arg));\n    bool result = testing::Value(response->stream_id, stream_id) &&\n        testing::Value(response->session_id, session_id) &&\n        testing::Value(response->correlation_id, correlation_id) &&\n        0 < response->log_file_length;\n\n    if (!result)\n    {\n        *result_listener <<\n            \"response.streamId() = \" << response->stream_id <<\n            \", response.correlationId() = \" << response->stream_id <<\n            \", response.logFileName().length() = \" << response->log_file_length;\n    }\n\n    return result;\n}\n\nMATCHER_P5(\n    IsPubReadyWithFile,\n    registration_id,\n    correlation_id,\n    stream_id,\n    session_id,\n    log_file_name,\n    std::string(\"IsPubReadyWithFile: \")\n        .append(\"registration_id = \").append(testing::DescribeMatcher<int64_t>(correlation_id))\n        .append(\", correlation_id = \").append(testing::DescribeMatcher<int64_t>(correlation_id))\n        .append(\", stream_id = \").append(testing::DescribeMatcher<int32_t>(stream_id))\n        .append(\", session_id = \").append(testing::DescribeMatcher<int32_t>(session_id))\n        .append(\", log_file_name = \").append(testing::DescribeMatcher<std::string>(log_file_name)))\n{\n    const aeron_publication_buffers_ready_t *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(\n        std::get<1>(arg));\n    bool result = true;\n    result &= testing::Value(response->stream_id, stream_id);\n    result &= testing::Value(response->session_id, session_id);\n    result &= testing::Value(response->correlation_id, correlation_id);\n    result &= testing::Value(response->registration_id, registration_id);\n\n    const char *response_log_file_name;\n    int32_t log_file_name_length;\n    aeron_publication_buffers_ready_get_log_file_name(response, &response_log_file_name, &log_file_name_length);\n    std::string log_file_name_str = std::string(response_log_file_name, (size_t)log_file_name_length);\n\n    result &= testing::Value(log_file_name_str, log_file_name);\n\n    if (!result)\n    {\n        *result_listener <<\n             \"response.stream_id = \" << response->stream_id <<\n             \", response.session_id = \" << response->session_id <<\n             \", response.correlation_id = \" << response->correlation_id <<\n             \", response.registration_id = \" << response->registration_id <<\n             \", response.log_file_name = \" << log_file_name_str;\n    }\n\n    return result;\n}\n\nACTION_P2(CapturePublicationReady, session_id_out, log_file_name_out)\n{\n    const aeron_publication_buffers_ready_t *response = reinterpret_cast<aeron_publication_buffers_ready_t *>(arg1);\n    *session_id_out = response->session_id;\n    const char *log_file_name;\n    int32_t log_file_name_length;\n    aeron_publication_buffers_ready_get_log_file_name(response, &log_file_name, &log_file_name_length);\n    log_file_name_out->append(log_file_name, log_file_name_length);\n}\n\nMATCHER_P(\n    IsTimeout,\n    client_id,\n    std::string(\"IsTimeout: client_id = \").append(testing::PrintToString(client_id)))\n{\n    const aeron_client_timeout_t *response = reinterpret_cast<aeron_client_timeout_t *>(std::get<1>(arg));\n    bool result = testing::Value(response->client_id, client_id);\n\n    if (!result)\n    {\n        *result_listener << \"response.client_id = \" << response->client_id;\n    }\n\n    return result;\n}\n\nMATCHER_P6(\n    IsAvailableImage,\n    image_registration_id,\n    subscription_registration_id,\n    stream_id,\n    session_id,\n    log_file_name,\n    source_identity,\n    std::string(\"IsAvailableImage: \")\n        .append(\"image_registration_id = \").append(testing::PrintToString(image_registration_id))\n        .append(\", subscription_registration_id = \").append(testing::PrintToString(subscription_registration_id))\n        .append(\", stream_id = \").append(testing::PrintToString(stream_id))\n        .append(\", session_id = \").append(testing::PrintToString(session_id))\n        .append(\", log_file_name = \").append(testing::PrintToString(log_file_name))\n        .append(\", source_identity = \").append(testing::PrintToString(source_identity)))\n{\n    const aeron_image_buffers_ready_t *response = reinterpret_cast<aeron_image_buffers_ready_t *>(std::get<1>(arg));\n    bool result = true;\n    result &= response->session_id == session_id;\n    result &= response->stream_id == stream_id;\n    result &= testing::Value(response->correlation_id, image_registration_id);\n    result &= response->subscriber_registration_id == subscription_registration_id;\n\n    int32_t response_log_file_name_length;\n    const char *response_log_file_name;\n    aeron_image_buffers_ready_get_log_file_name(response, &response_log_file_name, &response_log_file_name_length);\n\n    int32_t response_source_identity_length;\n    const char *response_source_identity;\n    aeron_image_buffers_ready_get_source_identity(response, &response_source_identity, &response_source_identity_length);\n    const std::string str_log_file_name = std::string(response_log_file_name, (size_t)response_log_file_name_length);\n    const std::string str_source_identity = std::string(response_source_identity, (size_t)response_source_identity_length);\n\n    result &= 0 == std::string(log_file_name).compare(str_log_file_name);\n    result &= 0 == std::string(source_identity).compare(str_source_identity);\n\n    if (!result)\n    {\n        *result_listener <<\n            \"response.correlation_id = \" << response->correlation_id <<\n            \", response.subscription_registration_id = \" << response->subscriber_registration_id <<\n            \", response.stream_id = \" << response->stream_id <<\n            \", response.session_id = \" << response->session_id <<\n            \", response.log_file_name = \" << str_log_file_name <<\n            \", response.source_identity = \" << str_source_identity;\n    }\n\n    return result;\n}\n\nMATCHER_P5(\n    IsImageBuffersReady,\n    subscription_registration_id,\n    stream_id,\n    session_id,\n    log_file_name,\n    source_identity,\n    std::string(\"IsAvailableImage: \")\n        .append(\", subscription_registration_id = \").append(testing::PrintToString(subscription_registration_id))\n        .append(\", stream_id = \").append(testing::PrintToString(stream_id))\n        .append(\", session_id = \").append(testing::PrintToString(session_id))\n        .append(\", log_file_name = \").append(testing::PrintToString(log_file_name))\n        .append(\", source_identity = \").append(testing::PrintToString(source_identity)))\n{\n    bool result = true;\n    result &= arg->session_id == session_id;\n    result &= arg->stream_id == stream_id;\n    result &= arg->subscriber_registration_id == subscription_registration_id;\n\n    int32_t response_log_file_name_length;\n    const char *response_log_file_name;\n    aeron_image_buffers_ready_get_log_file_name(arg, &response_log_file_name, &response_log_file_name_length);\n\n    int32_t response_source_identity_length;\n    const char *response_source_identity;\n    aeron_image_buffers_ready_get_source_identity(arg, &response_source_identity, &response_source_identity_length);\n    const std::string str_log_file_name = std::string(response_log_file_name, (size_t)response_log_file_name_length);\n    const std::string str_source_identity = std::string(response_source_identity, (size_t)response_source_identity_length);\n\n    result &= 0 == log_file_name.compare(str_log_file_name);\n    result &= 0 == source_identity.compare(str_source_identity);\n\n    if (!result)\n    {\n        *result_listener <<\n             \"response.correlation_id = \" << arg->correlation_id <<\n             \", response.subscription_registration_id = \" << arg->subscriber_registration_id <<\n             \", response.stream_id = \" << arg->stream_id <<\n             \", response.session_id = \" << arg->session_id <<\n             \", response.log_file_name = \" << str_log_file_name <<\n             \", response.source_identity = \" << str_source_identity;\n    }\n\n    return result;\n}\n\nMATCHER_P4(\n    IsUnavailableImage,\n    stream_id,\n    correlation_id,\n    subscription_registration_id,\n    channel,\n    std::string(\"IsAvailableImage: \")\n        .append(\"stream_id = \").append(testing::PrintToString(stream_id))\n        .append(\", correlation_id = \").append(testing::PrintToString(correlation_id))\n        .append(\", subscription_registration_id = \").append(testing::PrintToString(subscription_registration_id))\n        .append(\", channel = \").append(testing::PrintToString(channel)))\n{\n    const aeron_image_message_t *response = reinterpret_cast<aeron_image_message_t *>(std::get<1>(arg));\n    bool result = true;\n    result &= response->correlation_id == correlation_id;\n    result &= response->stream_id == stream_id;\n    result &= response->subscription_registration_id == subscription_registration_id;\n\n    int32_t response_channel_len;\n    const char *response_channel;\n    aeron_image_message_get_channel(response, &response_channel, &response_channel_len);\n    const std::string str_channel = std::string(response_channel, (size_t)response_channel_len);\n\n    result &= str_channel == std::string(channel);\n\n    if (!result)\n    {\n        *result_listener <<\n            \"response.correlation_id = \" << response->correlation_id <<\n            \", response.subscription_registration_id = \" << response->subscription_registration_id <<\n            \", response.stream_id = \" << response->stream_id <<\n            \", response.channel = \" << str_channel;\n    }\n\n    return result;\n}\n\nMATCHER_P2(\n    IsCounterUpdate,\n    correlation_id,\n    counter_id,\n    std::string(\"IsCounterUnavailable: \")\n        .append(\"correlation_id = \").append(testing::PrintToString(correlation_id))\n        .append(\", counter_id = \").append(testing::PrintToString(counter_id))\n    )\n{\n    const aeron_counter_update_t *response = reinterpret_cast<aeron_counter_update_t *>(std::get<1>(arg));\n\n    bool result = true;\n    result &= testing::Value(response->correlation_id, correlation_id);\n    result &= testing::Value(response->counter_id, counter_id);\n\n    if (!result)\n    {\n        *result_listener <<\n            \"response.correlation_id = \" << response->correlation_id <<\n            \", response.counter_id = \" << response->counter_id;\n    }\n\n    return result;\n}\n\nMATCHER_P2(\n    IsIdCounter,\n    id,\n    label,\n    std::string(\"IsIdCounter: \")\n        .append(\"key = \").append(testing::PrintToString(id))\n        .append(\", label = \").append(label)\n    )\n{\n    const uint8_t *key_buffer = std::get<2>(arg);\n    int64_t counter_id;\n    memcpy(&counter_id, key_buffer, sizeof(counter_id));\n    const std::string counter_label = std::string((char *)std::get<4>(arg), std::get<5>(arg));\n\n    bool result = true;\n    result &= testing::Value(counter_id, id);\n    result &= testing::Value(counter_label, label);\n\n    if (!result)\n    {\n        *result_listener <<\n            \"counter.id = \" << counter_id <<\n            \"counter.label = \" << counter_label;\n    }\n\n    return result;\n}\n\nACTION_P(CaptureCounterId, counter_id_out)\n{\n    const aeron_counter_update_t *response = reinterpret_cast<aeron_counter_update_t *>(arg1);\n    *counter_id_out = response->counter_id;\n}\n\n#endif //AERON_DRIVER_CONDUCTOR_TEST_H\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_configuration_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeronmd.h\"\n}\n\nclass DriverConfigurationTest : public testing::Test\n{\npublic:\n    DriverConfigurationTest()\n    {\n        if (aeron_driver_context_init(&m_context) < 0)\n        {\n            throw std::runtime_error(\"could not init context: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    ~DriverConfigurationTest() override\n    {\n        aeron_driver_context_close(m_context);\n    }\n\nprotected:\n    aeron_driver_context_t *m_context = nullptr;\n};\n\nTEST_F(DriverConfigurationTest, shouldFindAllBuiltinFlowControlStrategies)\n{\n    EXPECT_NE(aeron_flow_control_strategy_supplier_by_name(AERON_MULTICAST_MIN_FLOW_CONTROL_STRATEGY_NAME), nullptr);\n    EXPECT_NE(aeron_flow_control_strategy_supplier_by_name(AERON_MULTICAST_MAX_FLOW_CONTROL_STRATEGY_NAME), nullptr);\n    EXPECT_NE(aeron_flow_control_strategy_supplier_by_name(AERON_UNICAST_MAX_FLOW_CONTROL_STRATEGY_NAME), nullptr);\n    EXPECT_NE(aeron_flow_control_strategy_supplier_by_name(AERON_MULTICAST_TAGGED_FLOW_CONTROL_STRATEGY_NAME), nullptr);\n}\n\nTEST_F(DriverConfigurationTest, shouldNotFindNonBuiltinFlowControlStrategies)\n{\n    EXPECT_EQ(aeron_flow_control_strategy_supplier_by_name(\"should not be found\"), nullptr);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_context_config_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"aeron_test_base.h\"\n#include \"aeronmd.h\"\n\nusing namespace aeron;\n\nstatic const uint32_t DEFAULT_VALUE = UINT32_C(4);\nstatic const uint32_t MIN_VALUE = UINT32_C(1);\nstatic const uint32_t MAX_VALUE = UINT32_C(16);\n\nclass DriverContextConfigTest : public testing::Test\n{\nprotected:\n    void TearDown() override\n    {\n        aeron_env_unset(AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR);\n        aeron_env_unset(AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR);\n    }\n};\n\nTEST_F(DriverContextConfigTest, shouldValidateReceiverIoVectorCapacity)\n{\n    aeron_driver_context_t *context;\n\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(DEFAULT_VALUE, aeron_driver_context_get_receiver_io_vector_capacity(context));\n    aeron_driver_context_set_receiver_io_vector_capacity(context, 0);\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_receiver_io_vector_capacity(context));\n    aeron_driver_context_set_receiver_io_vector_capacity(context, 2);\n    EXPECT_EQ(2, aeron_driver_context_get_receiver_io_vector_capacity(context));\n    aeron_driver_context_set_receiver_io_vector_capacity(context, 16);\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_receiver_io_vector_capacity(context));\n    aeron_driver_context_set_receiver_io_vector_capacity(context, 17);\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_receiver_io_vector_capacity(context));\n\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR, \"-1\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_receiver_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR, \"0\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_receiver_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR, \"17\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_receiver_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR, \"1\");\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_receiver_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_RECEIVER_IO_VECTOR_CAPACITY_ENV_VAR, \"16\");\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_receiver_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldValidateSenderIoVectorCapacity)\n{\n    aeron_driver_context_t *context;\n\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(DEFAULT_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    EXPECT_EQ(DEFAULT_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_set_sender_io_vector_capacity(context, 0);\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_set_sender_io_vector_capacity(context, 2);\n    EXPECT_EQ(2, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_set_sender_io_vector_capacity(context, 16);\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_set_sender_io_vector_capacity(context, 17);\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR, \"-1\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR, \"0\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR, \"17\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR, \"1\");\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_SENDER_IO_VECTOR_CAPACITY_ENV_VAR, \"16\");\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_sender_io_vector_capacity(context));\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldValidateMaxMessagesPerSendBuffers)\n{\n    aeron_driver_context_t *context;\n\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(DEFAULT_VALUE, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_set_network_publication_max_messages_per_send(context, 0);\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_set_network_publication_max_messages_per_send(context, 2);\n    EXPECT_EQ(2, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_set_network_publication_max_messages_per_send(context, 16);\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_set_network_publication_max_messages_per_send(context, 17);\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_ENV_VAR, \"-1\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_ENV_VAR, \"0\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_ENV_VAR, \"17\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_ENV_VAR, \"1\");\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MIN_VALUE, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND_ENV_VAR, \"16\");\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(MAX_VALUE, aeron_driver_context_get_network_publication_max_messages_per_send(context));\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldHandleValuesOutsideOfUint32Range)\n{\n    aeron_driver_context_t *context;\n\n    const char *uint32_max_plus_one = \"4294967296\";\n    aeron_env_set(AERON_DRIVER_RESOURCE_FREE_LIMIT_ENV_VAR, uint32_max_plus_one);\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(INT32_MAX, aeron_driver_context_get_resource_free_limit(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_DRIVER_RESOURCE_FREE_LIMIT_ENV_VAR, \"-1\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(1, aeron_driver_context_get_resource_free_limit(context));\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldReturnDefaultLowFileStoreWarningThresholdIfNoneProvided)\n{\n    const uint64_t default_low_storage_warning_threshold = 160 * 1024 * 1024;\n\n    aeron_driver_context_t *context = nullptr;\n    EXPECT_EQ(default_low_storage_warning_threshold, aeron_driver_context_get_low_file_store_warning_threshold(context));\n\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(default_low_storage_warning_threshold, aeron_driver_context_get_low_file_store_warning_threshold(context));\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldAssignLowStoreWarningThreshold)\n{\n    aeron_driver_context_t *context = nullptr;\n    EXPECT_EQ(-1, aeron_driver_context_set_low_file_store_warning_threshold(context, 42));\n\n    const uint64_t threshold = 1024 * 1024;\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(0, aeron_driver_context_set_low_file_store_warning_threshold(context, threshold));\n    EXPECT_EQ(threshold, aeron_driver_context_get_low_file_store_warning_threshold(context));\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldReadLowFileStoreWarningThresholdFromAnEnvironmentVariable)\n{\n    aeron_driver_context_t *context = nullptr;\n    aeron_env_set(AERON_LOW_FILE_STORE_WARNING_THRESHOLD_ENV_VAR, \"2m\");\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(2 * 1024 * 1024, aeron_driver_context_get_low_file_store_warning_threshold(context));\n    aeron_driver_context_close(context);\n\n    const uint64_t default_low_storage_warning_threshold = 160 * 1024 * 1024;\n    aeron_env_set(AERON_LOW_FILE_STORE_WARNING_THRESHOLD_ENV_VAR, \"garbage\");\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(default_low_storage_warning_threshold, aeron_driver_context_get_low_file_store_warning_threshold(context));\n    aeron_driver_context_close(context);\n}\n\n\nTEST_F(DriverContextConfigTest, shouldHonorUntetheredLingerFromAnEnvironmentVariable)\n{\n    aeron_driver_context_t *context = nullptr;\n\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(AERON_NULL_VALUE, aeron_driver_context_get_untethered_linger_timeout_ns(context));\n    aeron_driver_context_close(context);\n\n    aeron_env_set(AERON_UNTETHERED_LINGER_TIMEOUT_ENV_VAR, \"5000000\");\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    EXPECT_EQ(5000000, aeron_driver_context_get_untethered_linger_timeout_ns(context));\n    aeron_driver_context_close(context);\n\n    ASSERT_EQ(0, aeron_driver_context_init(&context));\n    aeron_driver_context_set_untethered_linger_timeout_ns(context, 3000000);\n    EXPECT_EQ(3000000, aeron_driver_context_get_untethered_linger_timeout_ns(context));\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldInitializeNakUnicastDelayToDefaultValue)\n{\n    aeron_driver_context_t *context;\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n\n    EXPECT_EQ(1000, aeron_driver_context_get_nak_unicast_delay_ns(context));\n    EXPECT_EQ(100, aeron_driver_context_get_nak_unicast_retry_delay_ratio(context));\n\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldInitializeNakUnicastDelayFromEnvironment)\n{\n    aeron_driver_context_t *context;\n    aeron_env_set(AERON_NAK_UNICAST_DELAY_ENV_VAR, \"3s\");\n    aeron_env_set(AERON_NAK_UNICAST_RETRY_DELAY_RATIO_ENV_VAR, \"171\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n\n    EXPECT_EQ(3ul * 1000 * 1000 * 1000, aeron_driver_context_get_nak_unicast_delay_ns(context));\n    EXPECT_EQ(171, aeron_driver_context_get_nak_unicast_retry_delay_ratio(context));\n\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldUseMinimumValuesIsNakDelayIsOutOfBounds)\n{\n    aeron_driver_context_t *context;\n    aeron_env_set(AERON_NAK_UNICAST_DELAY_ENV_VAR, \"876\");\n    aeron_env_set(AERON_NAK_UNICAST_RETRY_DELAY_RATIO_ENV_VAR, \"0\");\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n\n    EXPECT_EQ(1000, aeron_driver_context_get_nak_unicast_delay_ns(context));\n    EXPECT_EQ(1, aeron_driver_context_get_nak_unicast_retry_delay_ratio(context));\n\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldSetNakDelayExplicitly)\n{\n    aeron_driver_context_t *context;\n    EXPECT_EQ(0, aeron_driver_context_init(&context));\n\n    const uint64_t nakDelayNs = 15ul * 1000 * 1245;\n    EXPECT_EQ(0, aeron_driver_context_set_nak_unicast_delay_ns(context, nakDelayNs));\n    EXPECT_EQ(nakDelayNs, aeron_driver_context_get_nak_unicast_delay_ns(context));\n\n    const uint64_t nakDelayRatio = INT64_MAX;\n    EXPECT_EQ(0, aeron_driver_context_set_nak_unicast_retry_delay_ratio(context, nakDelayRatio));\n    EXPECT_EQ(nakDelayRatio, aeron_driver_context_get_nak_unicast_retry_delay_ratio(context));\n\n    aeron_driver_context_close(context);\n}\n\nTEST_F(DriverContextConfigTest, shouldFailInitIfNakDelayComboIsOutOfBounds)\n{\n    aeron_driver_context_t *context;\n    aeron_env_set(AERON_NAK_UNICAST_DELAY_ENV_VAR, \"1000000s\");\n    aeron_env_set(AERON_NAK_UNICAST_RETRY_DELAY_RATIO_ENV_VAR, \"567890473482340000\");\n    EXPECT_EQ(-1, aeron_driver_context_init(&context));\n    EXPECT_NE(\n        std::string::npos,\n        std::string(aeron_errmsg()).find(\"nak_unicast_delay_ns (1000000000000000) * nak_unicast_retry_delay_ratio (567890473482340000) exceeds 9223372036854775807\"));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_driver_uri_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\nextern \"C\"\n{\n#include \"uri/aeron_uri.h\"\n#include \"util/aeron_netutil.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_driver_context.h\"\n#include \"aeron_driver_conductor.h\"\n#include \"aeron_name_resolver.h\"\n}\n\nclass DriverUriTest : public testing::Test\n{\npublic:\n    DriverUriTest()\n    {\n        if (aeron_driver_context_init(&m_context) < 0)\n        {\n            throw std::runtime_error(\"could not init context: \" + std::string(aeron_errmsg()));\n        }\n\n        m_conductor.context = m_context;\n    }\n\n    ~DriverUriTest() override\n    {\n        aeron_uri_close(&m_uri);\n        aeron_driver_context_close(m_context);\n    }\n\nprotected:\n    aeron_uri_t m_uri = {};\n    aeron_driver_context_t *m_context = nullptr;\n    aeron_driver_conductor_t m_conductor = {};\n};\n\n#define AERON_URI_PARSE(uri_str, uri) aeron_uri_parse(strlen(uri_str), uri_str, uri)\n\nTEST_F(DriverUriTest, shouldParseCongestionControlParam)\n{\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|cc=static\", &m_uri), 0);\n    ASSERT_EQ(m_uri.type, AERON_URI_UDP);\n    EXPECT_EQ(std::string(m_uri.params.udp.endpoint), \"224.10.9.8\");\n    EXPECT_EQ(m_uri.params.udp.additional_params.length, 1u);\n    EXPECT_EQ(std::string(m_uri.params.udp.additional_params.array[0].key), AERON_URI_CC_KEY);\n    EXPECT_EQ(std::string(m_uri.params.udp.additional_params.array[0].value),\n        AERON_STATICWINDOWCONGESTIONCONTROL_CC_PARAM_VALUE);\n}\n\nTEST_F(DriverUriTest, shouldParseNoPublicationParams)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamLingerTimeout)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|linger=7777\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_EQ(params.linger_timeout_ns, 7777u);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamUdpTermLength)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|term-length=131072\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_EQ(params.term_length, 131072u);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamIpcTermLength)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?term-length=262144\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_EQ(params.term_length, 262144u);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamIpcTermLengthOddValue)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?term-length=262143\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamIpcTermLength32K)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?term-length=32768\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamIpcTermLength2T)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?term-length=2147483648\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamUdpEndpointAndMtuLength)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|mtu=18432\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_EQ(params.mtu_length, 18432u);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamIpcMtuLength32K)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?mtu=32768\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_EQ(params.mtu_length, 32768u);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamIpcMtuLengthNonPowerOf2)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?mtu=66560\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamIpcMtuLengthLessThanHeaderLength)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?mtu=10\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamIpcMtuLengthNotMultipleOfFrameAlignment)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?mtu=255\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamIpcSparse)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?sparse=true\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_EQ(params.is_sparse, true);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamsForReplayUdp)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|init-term-id=120|term-id=127|term-offset=64\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, true), 0) << aeron_errmsg();\n    EXPECT_EQ(params.has_position, true);\n    EXPECT_EQ(params.initial_term_id, 120l);\n    EXPECT_EQ(params.term_id, 127l);\n    EXPECT_EQ(params.term_offset, 64u);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamsForReplayIpc)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?init-term-id=250|term-id=257|term-offset=128\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, true), 0) << aeron_errmsg();\n    EXPECT_EQ(params.has_position, true);\n    EXPECT_EQ(params.initial_term_id, 250l);\n    EXPECT_EQ(params.term_id, 257l);\n    EXPECT_EQ(params.term_offset, 128u);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamsForReplayIpcNegativeTermIds)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?init-term-id=-257|term-id=-250|term-offset=128\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, true), 0) << aeron_errmsg();\n    EXPECT_EQ(params.initial_term_id, -257l);\n    EXPECT_EQ(params.term_id, -250l);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamsForReplayIpcOddTermOffset)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:ipc?init-term-id=-257|term-id=-250|term-offset=127\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, true), -1);\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationParamsForReplayIpcTermOffsetBeyondTermLength)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\n        \"aeron:ipc?term-length=65536|init-term-id=-257|term-id=-250|term-offset=65537\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, true), -1);\n}\n\nTEST_F(DriverUriTest, shouldErrorParsingTooLargeTermIdRange)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\n        \"aeron:ipc?term-length=65536|init-term-id=-2147483648|term-id=0|term-offset=0\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, true), -1);\n    EXPECT_THAT(std::string(aeron_errmsg()), ::testing::HasSubstr(\"Param difference greater than 2^31 - 1\"));\n}\n\nTEST_F(DriverUriTest, shouldParsePublicationSessionId)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|session-id=1001\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_EQ(params.has_session_id, true);\n    EXPECT_EQ(params.session_id, 1001);\n}\n\nTEST_F(DriverUriTest, shouldErrorParsingNonNumericSessionId)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|session-id=foobar\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n}\n\nTEST_F(DriverUriTest, shouldErrorParsingOutOfRangeSessionId)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|session-id=2147483648\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n}\n\nTEST_F(DriverUriTest, shouldParseSubscriptionParamReliable)\n{\n    aeron_driver_uri_subscription_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|reliable=false\", &m_uri), 0);\n    EXPECT_EQ(aeron_driver_uri_subscription_params(&m_uri, &params, &m_conductor), 0);\n    EXPECT_EQ(params.is_reliable, false);\n}\n\nTEST_F(DriverUriTest, shouldParseSubscriptionParamReliableDefault)\n{\n    aeron_driver_uri_subscription_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8\", &m_uri), 0);\n    EXPECT_EQ(aeron_driver_uri_subscription_params(&m_uri, &params, &m_conductor), 0);\n    EXPECT_EQ(params.is_reliable, true);\n}\n\nTEST_F(DriverUriTest, shouldParseSubscriptionSessionId)\n{\n    aeron_driver_uri_subscription_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|session-id=1001\", &m_uri), 0);\n    EXPECT_EQ(aeron_driver_uri_subscription_params(&m_uri, &params, &m_conductor), 0);\n    EXPECT_EQ(params.has_session_id, true);\n    EXPECT_EQ(params.session_id, 1001);\n}\n\nTEST_F(DriverUriTest, shouldGetMediaReceiveTimestampOffset)\n{\n    aeron_driver_uri_subscription_params_t params = {};\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|session-id=1001|media-rcv-ts-offset=reserved\", &m_uri), 0);\n    EXPECT_EQ(aeron_driver_uri_subscription_params(&m_uri, &params, &m_conductor), 0);\n\n    int32_t offset = 0;\n    ASSERT_NE(-1, aeron_driver_uri_get_timestamp_offset(&m_uri, \"media-rcv-ts-offset\", &offset));\n    EXPECT_EQ(AERON_UDP_CHANNEL_RESERVED_VALUE_OFFSET, offset);\n}\n\nTEST_F(DriverUriTest, shouldDefaultMediaReceiveTimestampOffsetToAeronNullValue)\n{\n    aeron_driver_uri_subscription_params_t params = {};\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|session-id=1001\", &m_uri), 0);\n    EXPECT_EQ(aeron_driver_uri_subscription_params(&m_uri, &params, &m_conductor), 0);\n\n    int32_t offset = 0;\n    ASSERT_NE(-1, aeron_driver_uri_get_timestamp_offset(&m_uri, \"media-rcv-ts-offset\", &offset));\n    EXPECT_EQ(AERON_NULL_VALUE, offset);\n}\n\nTEST_F(DriverUriTest, shouldParseAndDefaultResponseCorrelationId)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_EQ(INT64_C(-1), params.response_correlation_id);\n}\n\nTEST_F(DriverUriTest, shouldNotHaveMaxRetransmits)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_FALSE(params.has_max_resend);\n}\n\nTEST_F(DriverUriTest, shouldHaveMaxRetransmits)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|max-resend=100\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), 0);\n    EXPECT_TRUE(params.has_max_resend);\n    EXPECT_EQ(INT64_C(100), params.max_resend);\n}\n\nTEST_F(DriverUriTest, shouldFailWithNegativeMaxRetransmits)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|max-resend=-1234\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n    EXPECT_THAT(std::string(aeron_errmsg()), ::testing::HasSubstr(\"could not parse max-resend\"));\n}\n\nTEST_F(DriverUriTest, shouldFailWithZeroMaxRetransmits)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|max-resend=0\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n    EXPECT_THAT(std::string(aeron_errmsg()), ::testing::HasSubstr(\"must be > 0\"));\n}\n\nTEST_F(DriverUriTest, shouldFailWithTooBigMaxRetransmits)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|max-resend=10000\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n    EXPECT_THAT(std::string(aeron_errmsg()), ::testing::HasSubstr(\"and <=\"));\n}\n\nTEST_F(DriverUriTest, shouldFailWithInvalidMaxRetransmits)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|max-resend=notanumber\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n    EXPECT_THAT(std::string(aeron_errmsg()), ::testing::HasSubstr(\"could not parse max-resend\"));\n}\n\nTEST_F(DriverUriTest, shouldFailWithPublicationWindowLessThanMtu)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|pub-wnd=2048|mtu=4096\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n    EXPECT_THAT(std::string(aeron_errmsg()), ::testing::HasSubstr(\"pub-wnd=2048 cannot be less than the mtu=4096\"));\n}\n\nTEST_F(DriverUriTest, shouldFailWithPublicationWindowMoreThanHalfTermLength)\n{\n    aeron_driver_uri_publication_params_t params;\n\n    EXPECT_EQ(AERON_URI_PARSE(\"aeron:udp?endpoint=224.10.9.8|pub-wnd=262144|term-length=65536\", &m_uri), 0);\n    EXPECT_EQ(aeron_diver_uri_publication_params(&m_uri, &params, &m_conductor, false), -1);\n    EXPECT_THAT(std::string(aeron_errmsg()), ::testing::HasSubstr(\"pub-wnd=262144 must not exceed half the term-length=65536\"));\n}\n\nTEST_F(DriverUriTest, ipcResponseChannels)\n{\n    EXPECT_EQ(0, AERON_URI_PARSE(\"aeron:ipc?control-mode=response|alias=client1\", &m_uri));\n    aeron_driver_uri_subscription_params_t params = {};\n    aeron_driver_uri_subscription_params(&m_uri, &params, &m_conductor);\n    EXPECT_TRUE(params.is_response);\n}\n\nclass UriResolverTest : public testing::Test\n{\npublic:\n    UriResolverTest() :\n        addr_in((struct sockaddr_in *)&m_addr),\n        addr_in6((struct sockaddr_in6 *)&m_addr)\n    {\n        aeron_default_name_resolver_supplier(&m_resolver, nullptr, nullptr);\n    }\n\n    static bool ipv4_match(const char *addr1_str, const char *addr2_str, size_t prefixlen)\n    {\n        struct sockaddr_in addr1{}, addr2{};\n\n        if (inet_pton(AF_INET, addr1_str, &addr1.sin_addr) != 1 || inet_pton(AF_INET, addr2_str, &addr2.sin_addr) != 1)\n        {\n            throw std::runtime_error(\"could not convert address\");\n        }\n\n        return aeron_ipv4_does_prefix_match(&addr1.sin_addr, &addr2.sin_addr, prefixlen);\n    }\n\n    static bool ipv6_match(const char *addr1_str, const char *addr2_str, size_t prefixlen)\n    {\n        struct sockaddr_in6 addr1{}, addr2{};\n\n        if (inet_pton(AF_INET6, addr1_str, &addr1.sin6_addr) != 1 ||\n            inet_pton(AF_INET6, addr2_str, &addr2.sin6_addr) != 1)\n        {\n            throw std::runtime_error(\"could not convert address\");\n        }\n\n        return aeron_ipv6_does_prefix_match(&addr1.sin6_addr, &addr2.sin6_addr, prefixlen);\n    }\n\n    static size_t ipv6_prefixlen(const char *aadr_str)\n    {\n        struct sockaddr_in6 addr{};\n\n        if (inet_pton(AF_INET6, aadr_str, &addr.sin6_addr) != 1)\n        {\n            throw std::runtime_error(\"could not convert address\");\n        }\n\n        return aeron_ipv6_netmask_to_prefixlen(&addr.sin6_addr);\n    }\n\n    static size_t ipv4_prefixlen(const char *addr_str)\n    {\n        struct sockaddr_in addr{};\n\n        if (inet_pton(AF_INET, addr_str, &addr.sin_addr) != 1)\n        {\n            throw std::runtime_error(\"could not convert address\");\n        }\n\n        return aeron_ipv4_netmask_to_prefixlen(&addr.sin_addr);\n    }\n\n    int resolve_host_and_port(const char *address_str, struct sockaddr_storage *address)\n    {\n        return aeron_name_resolver_resolve_host_and_port(&m_resolver, address_str, \"endpoint\", false, address);\n    }\n\nprotected:\n    aeron_uri_t m_uri = {};\n    struct sockaddr_storage m_addr = {};\n    struct sockaddr_in *addr_in = nullptr;\n    struct sockaddr_in6 *addr_in6 = nullptr;\n    size_t m_prefixlen = 0;\n    aeron_name_resolver_t m_resolver = {};\n};\n\nTEST_F(UriResolverTest, shouldResolveIpv4DottedDecimalAndPort)\n{\n    char buffer[AERON_URI_MAX_LENGTH];\n\n    ASSERT_EQ(resolve_host_and_port(\"192.168.1.20:55\", &m_addr), 0) << aeron_errmsg();\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"192.168.1.20\");\n    EXPECT_EQ(addr_in->sin_port, htons(55));\n}\n\nTEST_F(UriResolverTest, shouldResolveIpv4MaxPort)\n{\n    char buffer[AERON_URI_MAX_LENGTH];\n\n    const std::string uri = std::string(\"127.0.0.1:\") + std::to_string(UINT16_MAX);\n    ASSERT_EQ(resolve_host_and_port(uri.c_str(), &m_addr), 0) << aeron_errmsg();\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"127.0.0.1\");\n    EXPECT_EQ(addr_in->sin_port, htons(UINT16_MAX));\n}\n\nTEST_F(UriResolverTest, shouldResolveIpv4MulticastDottedDecimalAndPort)\n{\n    ASSERT_EQ(resolve_host_and_port(\"223.255.255.255:1234\", &m_addr), 0) << aeron_errmsg();\n    ASSERT_EQ(resolve_host_and_port(\"224.0.0.0:1234\", &m_addr), 0) << aeron_errmsg();\n    ASSERT_EQ(resolve_host_and_port(\"239.255.255.255:1234\", &m_addr), 0) << aeron_errmsg();\n    ASSERT_EQ(resolve_host_and_port(\"240.0.0.0:1234\", &m_addr), 0) << aeron_errmsg();\n}\n\nTEST_F(UriResolverTest, shouldResolveIpv6AndPort)\n{\n    char buffer[AERON_URI_MAX_LENGTH];\n\n    ASSERT_EQ(resolve_host_and_port(\"[::1]:1234\", &m_addr), 0) << aeron_errmsg();\n    EXPECT_EQ(m_addr.ss_family, AF_INET6);\n    EXPECT_EQ(addr_in6->sin6_family, AF_INET6);\n    EXPECT_STREQ(inet_ntop(AF_INET6, &addr_in6->sin6_addr, buffer, sizeof(buffer)), \"::1\");\n    EXPECT_EQ(addr_in->sin_port, htons(1234));\n\n    ASSERT_EQ(resolve_host_and_port(\"[::1%eth0]:1234\", &m_addr), 0) << aeron_errmsg();\n    EXPECT_EQ(m_addr.ss_family, AF_INET6);\n    EXPECT_EQ(addr_in6->sin6_family, AF_INET6);\n    EXPECT_STREQ(inet_ntop(AF_INET6, &addr_in6->sin6_addr, buffer, sizeof(buffer)), \"::1\");\n    EXPECT_EQ(addr_in->sin_port, htons(1234));\n\n    ASSERT_EQ(resolve_host_and_port(\"[::1%12~_.-34]:1234\", &m_addr), 0) << aeron_errmsg();\n}\n\nTEST_F(UriResolverTest, shouldResolveIpv6MulticastAndPort)\n{\n    ASSERT_EQ(resolve_host_and_port(\n        \"[FEFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:1234\", &m_addr), 0) << aeron_errmsg();\n    ASSERT_EQ(resolve_host_and_port(\"[FF00::]:1234\", &m_addr), 0) << aeron_errmsg();\n    ASSERT_EQ(resolve_host_and_port(\n        \"[FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:1234\", &m_addr), 0) << aeron_errmsg();\n}\n\nTEST_F(UriResolverTest, shouldResolveLocalhost)\n{\n    char buffer[AERON_URI_MAX_LENGTH];\n\n    ASSERT_EQ(resolve_host_and_port(\"localhost:1234\", &m_addr), 0) << aeron_errmsg();\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"127.0.0.1\");\n    EXPECT_EQ(addr_in->sin_port, htons(1234));\n}\n\nTEST_F(UriResolverTest, shouldNotResolveInvalidPort)\n{\n    EXPECT_EQ(resolve_host_and_port(\"192.168.1.20:aa\", &m_addr), -1);\n\n    // Regex is ? for port so it's not mandatory\n    EXPECT_EQ(resolve_host_and_port(\"192.168.1.20\", &m_addr), -1);\n\n    EXPECT_EQ(resolve_host_and_port(\"192.168.1.20:\", &m_addr), -1);\n    EXPECT_EQ(resolve_host_and_port(\"[::1]:aa\", &m_addr), -1);\n\n    // Regex is ? for port so it's not mandatory\n    EXPECT_EQ(resolve_host_and_port(\"[::1]\", &m_addr), -1);\n\n    EXPECT_EQ(resolve_host_and_port(\"[::1]:\", &m_addr), -1);\n}\n\nTEST_F(UriResolverTest, shouldNotResolvePortBeyoundMax)\n{\n    const std::string uri = std::string(\"127.0.0.1:\") + std::to_string(UINT16_MAX + 1);\n    EXPECT_EQ(resolve_host_and_port(uri.c_str(), &m_addr), -1);\n}\n\nTEST_F(UriResolverTest, shouldResolveIpv4Interface)\n{\n    char buffer[AERON_URI_MAX_LENGTH];\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"192.168.1.20\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 32u);\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"192.168.1.20\");\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"192.168.1.20/24\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 24u);\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"192.168.1.20:1234\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 32u);\n    EXPECT_EQ(addr_in->sin_port, htons(1234));\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"192.168.1.20:1234/24\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 24u);\n    EXPECT_EQ(addr_in->sin_port, htons(1234));\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"0.0.0.0/0\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 0u);\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"0.0.0.0\");\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"127.0.0.1\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 32u);\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"127.0.0.1\");\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"127.0.0.3/8\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 8u);\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"127.0.0.3\");\n}\n\nTEST_F(UriResolverTest, shouldResolveHostNameInTheInterfaceSpecification)\n{\n    char buffer[AERON_URI_MAX_LENGTH];\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"localhost:5551\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 32u);\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_port, htons(5551));\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"127.0.0.1\");\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"localhost:0/24\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 24u);\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_port, htons(0));\n    EXPECT_STREQ(inet_ntop(AF_INET, &addr_in->sin_addr, buffer, sizeof(buffer)), \"127.0.0.1\");\n\n    EXPECT_EQ(0, gethostname(buffer, sizeof(buffer)));\n    std::string host_name = std::string(buffer).append(\":8989/16\");\n    ASSERT_EQ(aeron_interface_parse_and_resolve(host_name.c_str(), &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 16u);\n    EXPECT_EQ(m_addr.ss_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_family, AF_INET);\n    EXPECT_EQ(addr_in->sin_port, htons(8989));\n}\n\nTEST_F(UriResolverTest, shouldResolveIpv6Interface)\n{\n    char buffer[AERON_URI_MAX_LENGTH];\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"[::1]\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 128u);\n    EXPECT_EQ(m_addr.ss_family, AF_INET6);\n    EXPECT_EQ(addr_in->sin_family, AF_INET6);\n    EXPECT_STREQ(inet_ntop(AF_INET6, &addr_in6->sin6_addr, buffer, sizeof(buffer)), \"::1\");\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"[::1]/48\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 48u);\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"[::1]:1234\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 128u);\n    EXPECT_EQ(addr_in6->sin6_port, htons(1234));\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"[::1]:1234/48\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 48u);\n    EXPECT_EQ(addr_in6->sin6_port, htons(1234));\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:1111\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 128u);\n    EXPECT_EQ(addr_in6->sin6_port, htons(1111));\n    EXPECT_STREQ(inet_ntop(AF_INET6, &addr_in6->sin6_addr, buffer, sizeof(buffer)), \"2001:db8:85a3::8a2e:370:7334\");\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"[fe80:3::1ff:fe23:4567:890a]/24\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 24u);\n    EXPECT_EQ(addr_in6->sin6_port, htons(0));\n    EXPECT_STREQ(inet_ntop(AF_INET6, &addr_in6->sin6_addr, buffer, sizeof(buffer)), \"fe80:3::1ff:fe23:4567:890a\");\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"[64:ff9b::]/96\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 96u);\n    EXPECT_EQ(addr_in6->sin6_port, htons(0));\n    EXPECT_STREQ(inet_ntop(AF_INET6, &addr_in6->sin6_addr, buffer, sizeof(buffer)), \"64:ff9b::\");\n\n    ASSERT_EQ(aeron_interface_parse_and_resolve(\"[ff01::1]:22222\", &m_addr, &m_prefixlen), 0) << aeron_errmsg();\n    EXPECT_EQ(m_prefixlen, 128u);\n    EXPECT_EQ(addr_in6->sin6_port, htons(22222));\n    EXPECT_STREQ(inet_ntop(AF_INET6, &addr_in6->sin6_addr, buffer, sizeof(buffer)), \"ff01::1\");\n    EXPECT_TRUE(aeron_is_addr_multicast(&m_addr));\n}\n\nTEST_F(UriResolverTest, shouldMatchIpv4)\n{\n    EXPECT_TRUE(ipv4_match(\"127.0.0.0\", \"127.0.0.0\", 24));\n    EXPECT_TRUE(ipv4_match(\"127.0.0.0\", \"127.0.0.1\", 24));\n    EXPECT_TRUE(ipv4_match(\"127.0.0.0\", \"127.0.0.255\", 24));\n    EXPECT_TRUE(ipv4_match(\"127.1.1.1\", \"127.0.0.0\", 8));\n    EXPECT_TRUE(ipv4_match(\"127.255.1.1\", \"127.255.255.255\", 16));\n    EXPECT_TRUE(ipv4_match(\"127.255.254.1\", \"127.255.254.255\", 24));\n    EXPECT_TRUE(ipv4_match(\"127.255.254.16\", \"127.255.254.16\", 32));\n}\n\nTEST_F(UriResolverTest, shouldNotMatchIpv4)\n{\n    EXPECT_FALSE(ipv4_match(\"127.0.1.0\", \"127.0.0.1\", 24));\n    EXPECT_FALSE(ipv4_match(\"127.0.0.1\", \"127.0.0.2\", 32));\n    EXPECT_FALSE(ipv4_match(\"127.0.0.1\", \"126.0.0.2\", 8));\n}\n\nTEST_F(UriResolverTest, shouldMatchIpv6)\n{\n    EXPECT_TRUE(ipv6_match(\"fe80:0001:abcd::\", \"fe80:0001:abcd::1\", 48));\n    EXPECT_TRUE(ipv6_match(\"fe80:0001:abcd::\", \"fe80:0001:abcd::ff\", 48));\n    EXPECT_TRUE(ipv6_match(\"fe80:0001:abcd::\", \"fe80:0001:abcd::\", 48));\n    EXPECT_TRUE(ipv6_match(\"fe80:0001:abcd::1\", \"fe80:0001:abcd::1\", 128));\n}\n\nTEST_F(UriResolverTest, shouldNotMatchIpv6)\n{\n    EXPECT_FALSE(ipv6_match(\"fe80:0001:abcd::\", \"fe80:0001:abce::\", 48));\n    EXPECT_FALSE(ipv6_match(\"fe80:0001:abcf::\", \"fe80:0001:abce::\", 48));\n    EXPECT_FALSE(ipv6_match(\"fe80:0001:abcd::\", \"fe80:0001:abcd::1\", 128));\n    EXPECT_FALSE(ipv6_match(\"fe80:0001:abcd::\", \"fe80:0001:abcd::ff\", 128));\n}\n\nTEST_F(UriResolverTest, shouldCalculateIpv4PrefixlenFromNetmask)\n{\n    EXPECT_EQ(ipv4_prefixlen(\"255.255.255.0\"), 24u);\n    EXPECT_EQ(ipv4_prefixlen(\"255.255.255.255\"), 32u);\n    EXPECT_EQ(ipv4_prefixlen(\"255.255.128.0\"), 17u);\n    EXPECT_EQ(ipv4_prefixlen(\"255.255.0.0\"), 16u);\n    EXPECT_EQ(ipv4_prefixlen(\"255.0.0.0\"), 8u);\n    EXPECT_EQ(ipv4_prefixlen(\"255.240.0.0\"), 12u);\n    EXPECT_EQ(ipv4_prefixlen(\"0.0.0.0\"), 0u);\n}\n\nTEST_F(UriResolverTest, shouldCalculateIpv6PrefixlenFromNetmask)\n{\n    EXPECT_EQ(ipv6_prefixlen(\"FFFF:FFFF:FFFF:FFFF::\"), 64u);\n    EXPECT_EQ(ipv6_prefixlen(\"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF\"), 128u);\n    EXPECT_EQ(ipv6_prefixlen(\"FFFF:FFFF:FFF0::\"), 44u);\n    EXPECT_EQ(ipv6_prefixlen(\"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FF00::\"), 104u);\n    EXPECT_EQ(ipv6_prefixlen(\"FFFF:FF80::\"), 25u);\n    EXPECT_EQ(ipv6_prefixlen(\"0000:0000:0000:0000:0000:0000:0000:0000\"), 0u);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_errors_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n#include <utility>\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include \"aeron_test_base.h\"\n\nextern \"C\"\n{\n#include \"aeron_system_counters.h\"\n#include \"command/aeron_control_protocol.h\"\n#include \"aeron_csv_table_name_resolver.h\"\n#include \"util/aeron_error.h\"\n}\n\n#define URI_RESERVED \"aeron:udp?endpoint=localhost:24325\"\n#define STREAM_ID (117)\n\nclass ErrorCallbackValidation\n{\npublic:\n    explicit ErrorCallbackValidation(std::vector<std::string> expectedSubstrings) : \n        m_expectedSubstrings(std::move(expectedSubstrings))\n    {}\n\n    void reset()\n    {\n        m_observations.clear();\n    }\n\n    void validate(std::string &errorStr) \n    {\n        m_observations.push_back(errorStr);\n\n        for (auto &subString : m_expectedSubstrings)\n        {\n            if (std::string::npos == errorStr.find(subString))\n            {\n                return;\n            }\n        }\n        \n        m_validated = true;\n    }\n    \n    bool validated() const\n    {\n        return m_validated;\n    }\n\n    friend std::ostream& operator<<(std::ostream& os, const ErrorCallbackValidation& bar)\n    {\n        os << \"Unable to find: \";\n        os << \"{ \";\n        for (auto &subString : bar.m_expectedSubstrings)\n        {\n            os << subString << \"; \";\n        }\n        os << \"} in:\";\n        os << std::endl;\n        for (auto &subString : bar.m_observations)\n        {\n            os << subString;\n        }\n\n        return os;\n    }\n\nprivate:\n    std::vector<std::string> m_expectedSubstrings;\n    std::vector<std::string> m_observations;\n    bool m_validated = false;\n};\n\nstatic const char *EXPECTED_RESOLVER_ERROR = \"Unable to resolve host\";\n\nstatic int resolveLocalhostOnly(\n    aeron_name_resolver_t *resolver,\n    const char *name,\n    const char *uri_param_name,\n    bool is_re_resolution,\n    struct sockaddr_storage *address)\n{\n    if (!is_re_resolution && 0 == strcmp(\"localhost\", name))\n    {\n        auto *address_in = reinterpret_cast<sockaddr_in *>(address);\n        inet_pton(AF_INET, \"127.0.0.1\", &address_in->sin_addr);\n        address_in->sin_family = AF_INET;\n        return 0;\n    }\n    else\n    {\n        AERON_SET_ERR(-AERON_ERROR_CODE_UNKNOWN_HOST, \"%s\", EXPECTED_RESOLVER_ERROR);\n        return -1;\n    }\n}\n\nstatic int testResolverSupplier(\n    aeron_name_resolver_t *resolver,\n    const char *args,\n    aeron_driver_context_t *context)\n{\n    int i = aeron_default_name_resolver_supplier(resolver, args, context);\n    resolver->resolve_func = resolveLocalhostOnly;\n    return i;\n}\n\nclass CErrorsTest : public CSystemTestBase, public testing::Test\n{\npublic:\n    CErrorsTest() : CSystemTestBase(\n        std::vector<std::pair<std::string, std::string>>{\n            { \"AERON_COUNTERS_BUFFER_LENGTH\", \"32768\" },\n        },\n        [](aeron_driver_context_t *ctx)\n        {\n            aeron_driver_context_set_name_resolver_supplier(ctx, testResolverSupplier);\n        })\n    {\n    }\n\nprotected:\n    volatile std::int64_t *m_errorCounter = nullptr;\n    std::int64_t m_initialErrorCount = 0;\n    aeron_counters_reader_t *m_countersReader = nullptr;\n\n    aeron_t *connect() override\n    {\n        aeron_t *aeron = CSystemTestBase::connect();\n\n        m_countersReader = aeron_counters_reader(aeron);\n        m_errorCounter = aeron_counters_reader_addr(m_countersReader, AERON_SYSTEM_COUNTER_ERRORS);\n        AERON_GET_ACQUIRE(m_initialErrorCount, *m_errorCounter);\n\n        return aeron;\n    }\n\n    void waitForErrorCounterIncrease()\n    {\n        int64_t currentErrorCount = 0;\n        do\n        {\n            std::this_thread::yield();\n            AERON_GET_ACQUIRE(currentErrorCount, *m_errorCounter);\n        }\n        while (currentErrorCount <= m_initialErrorCount);\n    }\n    \n    void verifyDistinctErrorLogContains(const char *text, std::int64_t timeoutMs = 0)\n    {\n        aeron_cnc_t *aeronCnc = nullptr;\n        const char *aeron_dir = aeron_context_get_dir(m_context);\n        int result = aeron_cnc_init(&aeronCnc, aeron_dir, 1000);\n        ASSERT_EQ(0, result) << \"CnC file not available: \" << aeron_dir;\n\n        ErrorCallbackValidation errorCallbackValidation\n        {\n            std::vector<std::string>{ text }\n        };\n\n        std::int64_t deadlineMs = aeron_epoch_clock() + timeoutMs;\n        do\n        {\n            std::this_thread::yield();\n            errorCallbackValidation.reset();\n            aeron_cnc_error_log_read(aeronCnc, errorCallback, &errorCallbackValidation, 0);\n        }\n        while (!errorCallbackValidation.validated() && aeron_epoch_clock() <= deadlineMs);\n\n        EXPECT_TRUE(errorCallbackValidation.validated()) << errorCallbackValidation;\n        \n        aeron_cnc_close(aeronCnc);\n    }\n\n    static void errorCallback(\n        int32_t observation_count,\n        int64_t first_observation_timestamp,\n        int64_t last_observation_timestamp,\n        const char *error,\n        size_t error_length,\n        void *clientd)\n    {\n        auto *callbackValidation = reinterpret_cast<ErrorCallbackValidation *>(clientd);\n        std::string errorStr = std::string(error, error_length);\n        callbackValidation->validate(errorStr);\n    }\n\n};\n\nTEST_F(CErrorsTest, shouldErrorOnAddCounterPoll)\n{\n    ASSERT_EQ(-1, aeron_async_add_counter_poll(nullptr, nullptr));\n    std::string errorMessage = std::string(aeron_errmsg());\n    ASSERT_THAT(errorMessage, testing::HasSubstr(\"Invalid argument\"));\n    ASSERT_THAT(errorMessage, testing::HasSubstr(\"Parameters must not be null\"));\n}\n\nTEST_F(CErrorsTest, shouldValidatePollType)\n{\n    aeron_t *aeron = connect();\n    aeron_async_add_publication_t *pub_async = nullptr;\n    aeron_publication_t *pub = nullptr;\n    aeron_counter_t *counter = nullptr;\n\n    ASSERT_EQ(0, aeron_async_add_publication(&pub_async, aeron, \"aeron:ipc\", 1001));\n    ASSERT_EQ(-1, aeron_async_add_counter_poll(&counter, (aeron_async_add_counter_t *)pub_async)) << aeron_errmsg();\n    std::string errorMessage = std::string(aeron_errmsg());\n    ASSERT_THAT(errorMessage, testing::HasSubstr(\"Invalid argument\"));\n    ASSERT_THAT(errorMessage, testing::HasSubstr(\"Parameters must be valid, async->type\"));\n\n    while (1 != aeron_async_add_publication_poll(&pub, pub_async))\n    {\n        std::this_thread::yield();\n    }\n\n    aeron_publication_close(pub, nullptr, nullptr);\n}\n\nTEST_F(CErrorsTest, publicationErrorIncludesClientAndDriverErrorAndReportsInDistinctLog)\n{\n    aeron_t *aeron = connect();\n    aeron_async_add_publication_t *pub_async = nullptr;\n    aeron_publication_t *pub = nullptr;\n\n    ASSERT_EQ(0, aeron_async_add_publication(&pub_async, aeron, \"aeron:tcp?endpoint=localhost:21345\", 1001));\n\n    int result;\n    while (0 == (result = aeron_async_add_publication_poll(&pub, pub_async)))\n    {\n        std::this_thread::yield();\n    }\n\n    ASSERT_EQ(-1, result);\n    std::string errorMessage = std::string(aeron_errmsg());\n    const char *expectedDriverMessage = \"invalid URI scheme or transport: aeron:tcp?endpoint=localhost:21345\";\n\n    ASSERT_THAT(-AERON_ERROR_CODE_INVALID_CHANNEL, aeron_errcode());\n    ASSERT_THAT(errorMessage, testing::HasSubstr(\"async_add_publication registration\"));\n    ASSERT_THAT(errorMessage, testing::HasSubstr(expectedDriverMessage));\n\n    waitForErrorCounterIncrease();\n    verifyDistinctErrorLogContains(expectedDriverMessage);\n}\n\nTEST_F(CErrorsTest, exclusivePublicationErrorIncludesClientAndDriverErrorAndReportsInDistinctLog)\n{\n    aeron_t *aeron = connect();\n    aeron_async_add_exclusive_publication_t *pub_async = nullptr;\n    aeron_exclusive_publication_t *pub = nullptr;\n\n    ASSERT_EQ(0, aeron_async_add_exclusive_publication(&pub_async, aeron, \"aeron:tcp?endpoint=localhost:21345\", 1001));\n\n    int result;\n    while (0 == (result = aeron_async_add_exclusive_publication_poll(&pub, pub_async)))\n    {\n        std::this_thread::yield();\n    }\n\n    ASSERT_EQ(-1, result);\n    std::string errorMessage = std::string(aeron_errmsg());\n    const char *expectedDriverMessage = \"invalid URI scheme or transport: aeron:tcp?endpoint=localhost:21345\";\n\n    ASSERT_THAT(-AERON_ERROR_CODE_INVALID_CHANNEL, aeron_errcode());\n    ASSERT_THAT(errorMessage, testing::HasSubstr(\"async_add_exclusive_publication registration\"));\n    ASSERT_THAT(errorMessage, testing::HasSubstr(expectedDriverMessage));\n\n    waitForErrorCounterIncrease();\n    verifyDistinctErrorLogContains(expectedDriverMessage);\n}\n\nTEST_F(CErrorsTest, subscriptionErrorIncludesClientAndDriverErrorAndReportsInDistinctLog)\n{\n    aeron_t *aeron = connect();\n    aeron_async_add_subscription_t *sub_async = nullptr;\n    aeron_subscription_t *sub = nullptr;\n\n    ASSERT_EQ(0, aeron_async_add_subscription(\n        &sub_async, aeron, \"aeron:tcp?endpoint=localhost:21345\", 1001, nullptr, nullptr, nullptr, nullptr));\n\n    int result;\n    while (0 == (result = aeron_async_add_subscription_poll(&sub, sub_async)))\n    {\n        std::this_thread::yield();\n    }\n\n    ASSERT_EQ(-1, result) << aeron_errmsg();\n    std::string errorMessage = std::string(aeron_errmsg());\n    const char *expectedDriverMessage = \"invalid URI scheme or transport: aeron:tcp?endpoint=localhost:21345\";\n\n    ASSERT_THAT(-AERON_ERROR_CODE_INVALID_CHANNEL, aeron_errcode());\n    ASSERT_THAT(errorMessage, testing::HasSubstr(\"async_add_subscription registration\"));\n    ASSERT_THAT(errorMessage, testing::HasSubstr(expectedDriverMessage));\n\n    waitForErrorCounterIncrease();\n    verifyDistinctErrorLogContains(expectedDriverMessage);\n}\n\nTEST_F(CErrorsTest, destinationErrorIncludesClientAndDriverErrorAndReportsInDistinctLog)\n{\n    aeron_t *aeron = connect();\n    aeron_async_add_exclusive_publication_t *pub_async = nullptr;\n    aeron_async_destination_t *dest_async = nullptr;\n    aeron_exclusive_publication_t *pub = nullptr;\n\n    ASSERT_EQ(0, aeron_async_add_exclusive_publication(&pub_async, aeron, \"aeron:udp?control-mode=manual\", 1001));\n\n    int result;\n    while (0 == (result = aeron_async_add_exclusive_publication_poll(&pub, pub_async)))\n    {\n        std::this_thread::yield();\n    }\n\n    ASSERT_EQ(1, result) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_exclusive_publication_async_add_destination(\n        &dest_async, aeron, pub, \"aeron:tcp?endpoint=localhost:21345\"));\n\n    while (0 == (result = aeron_exclusive_publication_async_destination_poll(dest_async)))\n    {\n        std::this_thread::yield();\n    }\n\n    ASSERT_EQ(-1, result);\n    std::string errorMessage = std::string(aeron_errmsg());\n\n    const char *expectedDriverMessage = \"invalid URI scheme or transport: aeron:tcp?endpoint=localhost:21345\";\n\n    ASSERT_THAT(-AERON_ERROR_CODE_INVALID_CHANNEL, aeron_errcode());\n    ASSERT_THAT(errorMessage, testing::HasSubstr(\"async_add_destination registration\"));\n    ASSERT_THAT(errorMessage, testing::HasSubstr(expectedDriverMessage));\n\n    waitForErrorCounterIncrease();\n    verifyDistinctErrorLogContains(expectedDriverMessage);\n}\n\nTEST_F(CErrorsTest, shouldFailToResovleNameOnPublication)\n{\n    aeron_t *aeron = connect();\n    aeron_async_add_publication_t *pub_async = nullptr;\n    aeron_publication_t *pub = nullptr;\n\n    ASSERT_EQ(0, aeron_async_add_publication(&pub_async, aeron, \"aeron:udp?endpoint=foo.example.com:20202\", 1001));\n\n    int result;\n    while (0 == (result = aeron_async_add_publication_poll(&pub, pub_async)))\n    {\n        std::this_thread::yield();\n    }\n\n    ASSERT_EQ(-1, result);\n    std::string errorMessage = std::string(aeron_errmsg());\n    const char *expectedDriverMessage = \"Unable to resolve host\";\n\n    ASSERT_THAT(-AERON_ERROR_CODE_UNKNOWN_HOST, aeron_errcode());\n    ASSERT_THAT(errorMessage, testing::HasSubstr(\"async_add_publication registration\"));\n    ASSERT_THAT(errorMessage, testing::HasSubstr(expectedDriverMessage));\n\n    waitForErrorCounterIncrease();\n    verifyDistinctErrorLogContains(expectedDriverMessage);\n}\n\nTEST_F(CErrorsTest, shouldRecordDistinctErrorCorrectlyOnReresolve)\n{\n    aeron_t *aeron = connect();\n\n    aeron_async_add_publication_t *pub_async = nullptr;\n    aeron_publication_t *pub = nullptr;\n\n    ASSERT_EQ(0, aeron_async_add_publication(&pub_async, aeron, \"aeron:udp?endpoint=localhost:21345\", 1001));\n\n    int result;\n    while (0 == (result = aeron_async_add_publication_poll(&pub, pub_async)))\n    {\n        std::this_thread::yield();\n    }\n\n    ASSERT_EQ(1, result) << aeron_errmsg();\n\n    waitForErrorCounterIncrease();\n    verifyDistinctErrorLogContains(EXPECTED_RESOLVER_ERROR, 10000);\n}"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_flow_control_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"util/aeron_properties_util.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"aeron_flow_control.h\"\n#include \"media/aeron_udp_channel.h\"\n#include \"aeron_driver_context.h\"\n}\n\n#define WINDOW_LENGTH (2000)\n\ntypedef std::array<std::uint8_t, 1024> buffer_t;\n\nstatic int64_t now()\n{\n    return 123123;\n}\n\n#define FREE_TO_REUSE_TIMEOUT_MS (1000L)\n\nclass FlowControlTest : public testing::Test\n{\npublic:\n    FlowControlTest()\n    {\n        aeron_default_name_resolver_supplier(&m_resolver, nullptr, nullptr);\n    };\n\n    int64_t apply_status_message(\n        aeron_flow_control_strategy_t *strategy,\n        int64_t receiver_id,\n        int32_t term_offset,\n        int64_t gtag,\n        int64_t now_ns = 0,\n        bool send_gtag = true,\n        int64_t snd_lmt = 0)\n    {\n        uint8_t msg[1024];\n        auto *sm = (aeron_status_message_header_t *)msg;\n        auto *sm_optional = (aeron_status_message_optional_header_t *)(msg + sizeof(aeron_status_message_header_t));\n\n        sm->frame_header.frame_length = send_gtag ?\n            sizeof(aeron_status_message_header_t) + sizeof(aeron_status_message_optional_header_t) :\n            sizeof(aeron_status_message_header_t);\n        sm->frame_header.flags = 0;\n        sm->consumption_term_id = 0;\n        sm->consumption_term_offset = term_offset;\n        sm->receiver_window = WINDOW_LENGTH;\n        sm->receiver_id = receiver_id;\n        sm_optional->group_tag = gtag;\n\n        return strategy->on_status_message(\n            strategy->state, (uint8_t *)sm, sizeof(struct sockaddr_storage), &address, snd_lmt, 0, 0, now_ns);\n    }\n\n    int64_t apply_old_asf_status_message(\n        aeron_flow_control_strategy_t *strategy,\n        int64_t receiver_id,\n        int32_t term_offset,\n        int32_t asf_value)\n    {\n        uint8_t msg[1024];\n        auto *sm = (aeron_status_message_header_t *)msg;\n        auto *asf = (int32_t *)(msg + sizeof(aeron_status_message_header_t));\n\n        sm->frame_header.frame_length = sizeof(aeron_status_message_header_t) + sizeof(int32_t);\n        sm->frame_header.flags = 0;\n        sm->consumption_term_id = 0;\n        sm->consumption_term_offset = term_offset;\n        sm->receiver_window = WINDOW_LENGTH;\n        sm->receiver_id = receiver_id;\n        *asf = asf_value;\n\n        return strategy->on_status_message(\n            strategy->state, (uint8_t *)sm, sizeof(struct sockaddr_storage), &address, sizeof(address), 0, 0, 0);\n    }\n\n    void initialise_channel(const char *uri)\n    {\n        aeron_udp_channel_parse(strlen(uri), uri, &m_resolver, &m_channel, false);\n        m_channels.push_back(m_channel);\n    }\n\n    struct sockaddr_storage address = {};\n    aeron_udp_channel_t *m_channel = nullptr;\n    aeron_driver_context_t *context = nullptr;\n    aeron_distinct_error_log_t error_log = {};\n    buffer_t buffer = {};\n    aeron_name_resolver_t m_resolver= {};\n    aeron_flow_control_strategy_t *m_strategy = nullptr;\n    std::vector<aeron_udp_channel_t *> m_channels;\n\n    static const size_t NUM_COUNTERS = 4;\n    std::array<std::uint8_t, NUM_COUNTERS * AERON_COUNTERS_MANAGER_METADATA_LENGTH> m_counters_metadata = {};\n    std::array<std::uint8_t, NUM_COUNTERS * AERON_COUNTERS_MANAGER_VALUE_LENGTH> m_counters_values = {};\n    aeron_counters_manager_t m_counters_manager = {};\n    aeron_clock_cache_t m_cached_clock = {};\n\nprotected:\n    void TearDown() override\n    {\n        for (auto channel : m_channels)\n        {\n            aeron_udp_channel_delete(channel);\n        }\n\n        if (nullptr != m_strategy)\n        {\n            m_strategy->fini(m_strategy);\n        }\n\n        aeron_driver_context_close(context);\n        aeron_distinct_error_log_close(&error_log);\n        aeron_counters_manager_close(&m_counters_manager);\n    }\n\n    void SetUp() override\n    {\n        m_channel = nullptr;\n        m_strategy = nullptr;\n        aeron_distinct_error_log_init(&error_log, buffer.data(), buffer.size(), now);\n        aeron_driver_context_init(&context);\n        context->error_log = &error_log;\n        context->multicast_flow_control_supplier_func = aeron_min_flow_control_strategy_supplier;\n\n        m_counters_metadata.fill(0);\n        m_counters_values.fill(0);\n        aeron_counters_manager_init(\n            &m_counters_manager,\n            m_counters_metadata.data(),\n            m_counters_metadata.size(),\n            m_counters_values.data(),\n            m_counters_values.size(),\n            &m_cached_clock,\n            0);\n    }\n};\n\nclass TaggedFlowControlTest : public FlowControlTest\n{\n};\n\nclass MinFlowControlTest : public FlowControlTest\n{\n};\n\nclass MaxFlowControlTest : public FlowControlTest\n{\n};\n\nclass ParameterisedSuccessfulOptionsParsingTest :\n    public testing::TestWithParam<std::tuple<const char *, const char *, uint64_t, bool, int32_t, bool, int32_t>>\n{\n};\n\nclass ParameterisedFailingOptionsParsingTest :\n    public testing::TestWithParam<std::tuple<const char *, int>>\n{\n};\n\nclass RetransmitReceiverWindowMultipleParsingTest :\n    public testing::TestWithParam<std::tuple<const char *, size_t, int>>\n{\n};\n\nTEST_F(MinFlowControlTest, shouldFallbackToMinStrategy)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001,0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 1, 1000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 2, 2000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 994, apply_status_message(m_strategy, 3, 994, 2));\n}\n\nTEST_F(MinFlowControlTest, shouldUseMinStrategy)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 1, 1000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 2, 2000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 994, apply_status_message(m_strategy, 3, 994, 2));\n}\n\n\nINSTANTIATE_TEST_SUITE_P(\n    RetransmitReceiverWindowMultipleParsingTests,\n    RetransmitReceiverWindowMultipleParsingTest,\n    testing::Values(\n        std::make_tuple(\"min,rrwm:2\", 2, 0),\n        std::make_tuple(\"min,rrwm:2,t:100ms\", 2, 0),\n        std::make_tuple(\"min,g:0,rrwm:2,t:100ms\", 2, 0),\n        std::make_tuple(\"min,rrwm:12,t:100ms\", 12, 0),\n        std::make_tuple(\"min,rrwm:0\", 0, EINVAL),\n        std::make_tuple(\"min,rrwm:-1\", 0, EINVAL),\n        std::make_tuple(\"min,rrwm:foo\", 0, EINVAL),\n        std::make_tuple(\"min,rrwm: \", 0, EINVAL),\n        std::make_tuple(\"min,rrwm:,t:100ms\", 0, EINVAL),\n        std::make_tuple(\"min,rrwm:\", 0, EINVAL)\n    )\n);\n\nTEST_P(RetransmitReceiverWindowMultipleParsingTest, parsesRetransmitReceiverWindowMultipleFromUri)\n{\n    const char *fc_options = std::get<0>(GetParam());\n    const size_t expectedRrwm = std::get<1>(GetParam());\n    const int expectedErrorCode = std::get<2>(GetParam());\n    const int expectedReturnCode = expectedErrorCode == 0 ? 1 : -1;\n\n    aeron_flow_control_tagged_options_t options;\n    options.multicast_flow_control_rrwm = 0;\n    int returnCode = aeron_flow_control_parse_tagged_options(strlen(fc_options), fc_options, &options);\n\n    ASSERT_EQ(expectedReturnCode, returnCode);\n    ASSERT_EQ(expectedRrwm, options.multicast_flow_control_rrwm);\n    if (1 != expectedReturnCode)\n    {\n        ASSERT_EQ(expectedErrorCode, aeron_errcode());\n    }\n}\n\nTEST_F(FlowControlTest, unicastStrategyShouldUseDefaultWindowMultipleWhenNotSet)\n{\n    size_t expected_rrwm = AERON_UNICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE; // <-- expect default\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    initialise_channel(\"aeron:udp?endpoint=localhost\");\n\n    ASSERT_EQ(0, aeron_unicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 3000000, 1024 * 1024 * 3, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(FlowControlTest, unicastStrategyShouldUseWindowMultipleFromContext)\n{\n    size_t expected_rrwm = 2;\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    initialise_channel(\"aeron:udp?endpoint=localhost\");\n    context->unicast_flow_control_rrwm = expected_rrwm; // <-- expect context value\n\n    ASSERT_EQ(0, aeron_unicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 3000000, 1024 * 1024 * 3, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(MinFlowControlTest, minStrategyShouldUseDefaultWindowMultipleWhenNotSet)\n{\n    size_t expected_rrwm = AERON_MULTICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE; // <-- expect default\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|fc=min\");\n\n    ASSERT_EQ(0, aeron_min_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 1000000, 1024 * 1024, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(MinFlowControlTest, minStrategyShouldUseDefaultWindowMultipleFromContext)\n{\n    size_t expected_rrwm = 2;\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|fc=min\");\n\n    context->multicast_flow_control_rrwm = expected_rrwm; // <-- expect context value\n\n    ASSERT_EQ(0, aeron_min_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 1000000, 1024 * 1024, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(MinFlowControlTest, minStrategyShouldUseDefaultWindowMultipleFromUri)\n{\n    size_t expected_rrwm = 6;\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|fc=min,rrwm:6\");  // <-- expect uri value\n\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    context->multicast_flow_control_rrwm = 2;\n\n    ASSERT_EQ(0, aeron_min_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 1000000, 1024 * 1024, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(TaggedFlowControlTest, taggedStrategyShouldUseDefaultWindowMultipleWhenNotSet)\n{\n    size_t expected_rrwm = AERON_MULTICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE; // <-- expect default\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|fc=tagged\");\n\n    ASSERT_EQ(0, aeron_tagged_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 1000000, 1024 * 1024, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(TaggedFlowControlTest, taggedStrategyShouldUseDefaultWindowMultipleFromContext)\n{\n    size_t expected_rrwm = 2;\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|fc=tagged\");\n\n    context->multicast_flow_control_rrwm = expected_rrwm; // <-- expect context value\n\n    ASSERT_EQ(0, aeron_tagged_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 1000000, 1024 * 1024, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(TaggedFlowControlTest, taggedStrategyShouldUseDefaultWindowMultipleFromUri)\n{\n    size_t expected_rrwm = 6;\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|fc=tagged,rrwm:6\");  // <-- expect uri value\n\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    context->multicast_flow_control_rrwm = 2;\n\n    ASSERT_EQ(0, aeron_tagged_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 1000000, 1024 * 1024, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(MaxFlowControlTest, maxStrategyShouldUseDefaultWindowMultipleWhenNotSet)\n{\n    size_t expected_rrwm = AERON_MULTICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE; // <-- expect default\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|fc=max\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 1000000, 1024 * 1024, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(MaxFlowControlTest, maxStrategyShouldUseDefaultWindowMultipleFromContext)\n{\n    size_t expected_rrwm = 2;\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|fc=max\");\n\n    context->multicast_flow_control_rrwm = expected_rrwm; // <-- expect context value\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 1000000, 1024 * 1024, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(MaxFlowControlTest, maxStrategyShouldUseDefaultWindowMultipleFromUri)\n{\n    size_t expected_rrwm = 6;\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|fc=max,rrwm:6\");  // <-- expect uri value\n\n    size_t window_length = aeron_driver_context_get_rcv_initial_window_length(nullptr);\n    uint64_t expected_retransmit_length = window_length * expected_rrwm;\n\n    context->multicast_flow_control_rrwm = 2;\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t rrwm = m_strategy->max_retransmission_length(m_strategy->state, 0, 1000000, 1024 * 1024, 1408);\n    ASSERT_EQ(expected_retransmit_length, rrwm);\n}\n\nTEST_F(FlowControlTest, shouldUseMinStrategyAndIgnoreGroupParams)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:123\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 1, 1000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 2, 2000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 994, apply_status_message(m_strategy, 3, 994, 2));\n}\n\nTEST_F(MinFlowControlTest, shouldTimeoutWithMinStrategy)\n{\n    const int64_t sender_limit = 5000;\n    const int64_t position_recv_1 = 1000;\n    const int64_t position_recv_2 = 2000;\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,t:500ms\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + position_recv_1, apply_status_message(m_strategy, 1, position_recv_1, 0, 100 * 1000000));\n    ASSERT_EQ(WINDOW_LENGTH + position_recv_1, apply_status_message(m_strategy, 2, position_recv_2, 0, 200 * 1000000));\n\n    ASSERT_EQ(\n        WINDOW_LENGTH + position_recv_1, m_strategy->on_idle(m_strategy->state, 599 * 1000000, sender_limit, 0, false));\n    ASSERT_EQ(\n        WINDOW_LENGTH + position_recv_2, m_strategy->on_idle(m_strategy->state, 601 * 1000000, sender_limit, 0, false));\n    ASSERT_EQ(sender_limit, m_strategy->on_idle(m_strategy->state, 701 * 1000000, sender_limit, 0, false));\n}\n\nTEST_F(MaxFlowControlTest, shouldFallbackToMaxStrategy)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\");\n    context->multicast_flow_control_supplier_func = aeron_max_multicast_flow_control_strategy_supplier;\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 1, 1000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 2000, apply_status_message(m_strategy, 2, 2000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 994, apply_status_message(m_strategy, 3, 994, 2));\n}\n\nTEST_F(MaxFlowControlTest, shouldUseMaxStrategy)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=max\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 1, 1000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 2000, apply_status_message(m_strategy, 2, 2000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 994, apply_status_message(m_strategy, 3, 994, 2));\n}\n\nTEST_F(TaggedFlowControlTest, shouldUseFallbackToTaggedStrategy)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=max\");\n    context->multicast_flow_control_supplier_func = aeron_tagged_flow_control_strategy_supplier;\n    context->receiver_group_tag.is_present = true;\n    context->receiver_group_tag.value = 1;\n    context->flow_control.group_tag = 1;\n    context->flow_control.group_min_size = 0;\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 1, 1000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 2000, apply_status_message(m_strategy, 2, 2000, 1));\n    ASSERT_EQ(WINDOW_LENGTH + 994, apply_status_message(m_strategy, 3, 994, 2));\n}\n\nTEST_F(TaggedFlowControlTest, shouldUseTaggedStrategy)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 1, 1000, 123));\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 2, 2000, 123));\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 3, 994, 0));\n}\n\nTEST_F(TaggedFlowControlTest, shouldAllowUseTaggedStrategyWhenGroupMissing)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n}\n\nTEST_F(TaggedFlowControlTest, shouldUseTaggedStrategyWith8ByteTag)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:3000000000\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 1, 1000, 3000000000));\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 2, 2000, 3000000000));\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 3, 994, -1294967296));\n}\n\nTEST_F(TaggedFlowControlTest, shouldUseTaggedStrategyWithOldAsfValue)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    size_t initial_observations = aeron_distinct_error_log_num_observations(&error_log);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_old_asf_status_message(m_strategy, 1, 1000, 123));\n\n    ASSERT_LT(initial_observations, aeron_distinct_error_log_num_observations(&error_log));\n}\n\nTEST_F(TaggedFlowControlTest, shouldUsePositionAndWindowFromStatusMessageWhenReceiverAreEmpty)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(WINDOW_LENGTH + 1000, apply_status_message(m_strategy, 1, 1000, 0));\n}\n\nTEST_F(TaggedFlowControlTest, shouldAlwaysUsePositionFromReceiversIfPresent)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    int tagged_term_offset = 1000;\n    ASSERT_EQ(WINDOW_LENGTH + tagged_term_offset, apply_status_message(m_strategy, 1, tagged_term_offset, 123));\n    ASSERT_EQ(WINDOW_LENGTH + tagged_term_offset, apply_status_message(m_strategy, 1, tagged_term_offset + 1, -1));\n    ASSERT_EQ(WINDOW_LENGTH + tagged_term_offset, apply_status_message(m_strategy, 1, tagged_term_offset - 1, -1));\n}\n\nTEST_F(TaggedFlowControlTest, shouldTimeout)\n{\n    const int64_t sender_position = 5000;\n    const int64_t position_recv_1 = 1000;\n    const int64_t position_recv_2 = 2000;\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123,t:500ms\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n    ASSERT_NE(nullptr, m_strategy);\n\n    ASSERT_EQ(position_recv_1 + WINDOW_LENGTH,\n              apply_status_message(m_strategy, 1, position_recv_1, 123, 100 * 1000000));\n    ASSERT_EQ(position_recv_1 + WINDOW_LENGTH,\n              apply_status_message(m_strategy, 2, position_recv_2, 123, 200 * 1000000));\n\n    ASSERT_EQ(\n        position_recv_1 + WINDOW_LENGTH,\n        m_strategy->on_idle(m_strategy->state, 599 * 1000000, sender_position, 0, false));\n    ASSERT_EQ(\n        position_recv_2 + WINDOW_LENGTH,\n        m_strategy->on_idle(m_strategy->state, 601 * 1000000, sender_position, 0, false));\n    ASSERT_EQ(sender_position, m_strategy->on_idle(m_strategy->state, 701 * 1000000, sender_position, 0, false));\n}\n\nTEST_F(MaxFlowControlTest, shouldAlwaysHaveRequiredReceivers)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=max\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_TRUE(m_strategy->has_required_receivers(m_strategy));\n}\n\nTEST_F(MinFlowControlTest, shouldAlwaysHaveRequiredReceiversByDefault)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_TRUE(m_strategy->has_required_receivers(m_strategy));\n}\n\nTEST_F(TaggedFlowControlTest, shouldAlwaysHaveRequiredReceiverByDefault)\n{\n    char buffer[1024];\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_TRUE(nullptr != m_strategy);\n\n    aeron_tagged_flow_control_strategy_to_string(m_strategy, buffer, sizeof(buffer));\n    ASSERT_TRUE(m_strategy->has_required_receivers(m_strategy)) << buffer;\n}\n\nTEST_F(TaggedFlowControlTest, shouldOnlyHaveRequiredReceiversWhenGroupSizeMet)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123/3\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n\n    apply_status_message(m_strategy, 1, 1000, -1);\n    apply_status_message(m_strategy, 2, 1000, -1);\n    apply_status_message(m_strategy, 3, 1000, -1);\n\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n\n    apply_status_message(m_strategy, 4, 1000, 123);\n\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n\n    apply_status_message(m_strategy, 5, 1000, 123);\n\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n\n    apply_status_message(m_strategy, 6, 1000, 123);\n\n    ASSERT_TRUE(m_strategy->has_required_receivers(m_strategy));\n}\n\nTEST_F(MinFlowControlTest, shouldOnlyHaveRequiredReceiversWhenGroupSizeMet)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:/3\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n\n    apply_status_message(m_strategy, 1, 1000, -1);\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n\n    apply_status_message(m_strategy, 2, 1000, -1);\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n\n    apply_status_message(m_strategy, 2, 1010, -1);\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n\n    apply_status_message(m_strategy, 3, 1000, -1);\n    ASSERT_TRUE(m_strategy->has_required_receivers(m_strategy));\n}\n\nTEST_F(MinFlowControlTest, shouldUseSenderLimitWhenRequiredReceiverNotMet)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:/3\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    int sender_limit = 500;\n    int term_offset = 1000;\n\n    ASSERT_EQ(sender_limit, apply_status_message(m_strategy, 1, term_offset, -1, 0, false, sender_limit));\n    ASSERT_EQ(sender_limit, m_strategy->on_idle(m_strategy->state, 0, sender_limit, 0, false));\n\n    ASSERT_EQ(sender_limit, apply_status_message(m_strategy, 2, term_offset, -1, 0, false, sender_limit));\n    ASSERT_EQ(sender_limit, m_strategy->on_idle(m_strategy->state, 0, sender_limit, 0, false));\n\n    ASSERT_EQ(term_offset + WINDOW_LENGTH,\n              apply_status_message(m_strategy, 3, term_offset, -1, 0, false, sender_limit));\n    ASSERT_EQ(term_offset + WINDOW_LENGTH, m_strategy->on_idle(m_strategy->state, 0, sender_limit, 0, false));\n}\n\nTEST_F(TaggedFlowControlTest, shouldUseSenderLimitWhenRequiredReceiversNotMet)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123/3\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    int sender_limit = 500;\n    int term_offset = 1000;\n    int gtag = 123;\n\n    ASSERT_EQ(sender_limit, apply_status_message(m_strategy, 1, term_offset, gtag, 0, true, sender_limit));\n    ASSERT_EQ(sender_limit, m_strategy->on_idle(m_strategy->state, 0, sender_limit, 0, false));\n\n    ASSERT_EQ(sender_limit, apply_status_message(m_strategy, 2, term_offset, gtag, 0, true, sender_limit));\n    ASSERT_EQ(sender_limit, m_strategy->on_idle(m_strategy->state, 0, sender_limit, 0, false));\n\n    ASSERT_EQ(\n        term_offset + WINDOW_LENGTH,\n        apply_status_message(m_strategy, 3, term_offset, gtag, 0, true, sender_limit));\n    ASSERT_EQ(term_offset + WINDOW_LENGTH, m_strategy->on_idle(m_strategy->state, 0, sender_limit, 0, false));\n}\n\nTEST_F(MinFlowControlTest, shouldNotIncludeReceiverMoreThanWindowLengthBehind)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:/2\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel, 1001, 1001, 1001, 0, 64 * 1024));\n\n    int sender_limit = 500;\n    int term_offset_0 = 5000 + WINDOW_LENGTH;\n    int term_offset_1 = term_offset_0 - (WINDOW_LENGTH + 1);\n    int term_offset_2 = term_offset_0 - WINDOW_LENGTH;\n\n    ASSERT_EQ(sender_limit, apply_status_message(m_strategy, 1, term_offset_0, -1, 0, false, sender_limit));\n    ASSERT_EQ(sender_limit, apply_status_message(m_strategy, 2, term_offset_1, -1, 0, false, sender_limit));\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n    ASSERT_EQ(WINDOW_LENGTH + term_offset_2, apply_status_message(m_strategy, 2, term_offset_2, -1, 0, false, sender_limit));\n    ASSERT_TRUE(m_strategy->has_required_receivers(m_strategy));\n}\n\nTEST_F(TaggedFlowControlTest, shouldNotIncludeReceiverMoreThanWindowLengthBehind)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123/2\");\n\n    ASSERT_EQ(0, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel, 1001, 1001, 1001, 0, 64 * 1024));\n\n    int gtag = 123;\n    int sender_limit = 500;\n    int term_offset_0 = 5000 + WINDOW_LENGTH;\n    int term_offset_1 = term_offset_0 - (WINDOW_LENGTH + 1);\n    int term_offset_2 = term_offset_0 - WINDOW_LENGTH;\n\n    ASSERT_EQ(sender_limit, apply_status_message(m_strategy, 1, term_offset_0, gtag, 0, true, sender_limit));\n    ASSERT_EQ(sender_limit, apply_status_message(m_strategy, 2, term_offset_1, gtag, 0, true, sender_limit));\n    ASSERT_FALSE(m_strategy->has_required_receivers(m_strategy));\n    ASSERT_EQ(WINDOW_LENGTH + term_offset_2, apply_status_message(m_strategy, 3, term_offset_2, gtag, 0, true, sender_limit));\n    ASSERT_TRUE(m_strategy->has_required_receivers(m_strategy));\n}\n\nTEST_P(ParameterisedSuccessfulOptionsParsingTest, shouldBeValid)\n{\n    const char *fc_options = std::get<0>(GetParam());\n    const char *strategy = std::get<1>(GetParam());\n\n    aeron_flow_control_tagged_options_t options;\n    ASSERT_EQ(1, aeron_flow_control_parse_tagged_options(strlen(fc_options), fc_options, &options));\n\n    ASSERT_EQ(strlen(strategy), options.strategy_name_length);\n    ASSERT_TRUE(0 == strncmp(strategy, options.strategy_name, options.strategy_name_length));\n    ASSERT_EQ(std::get<2>(GetParam()), options.timeout_ns.value);\n    ASSERT_EQ(std::get<3>(GetParam()), options.group_tag.is_present);\n    ASSERT_EQ(std::get<4>(GetParam()), options.group_tag.value);\n    ASSERT_EQ(std::get<5>(GetParam()), options.group_min_size.is_present);\n    ASSERT_EQ(std::get<6>(GetParam()), options.group_min_size.value);\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    ParsingTests,\n    ParameterisedSuccessfulOptionsParsingTest,\n    testing::Values(\n        std::make_tuple(\"max\", \"max\", 0, false, -1, false, 0),\n        std::make_tuple(\"min\", \"min\", 0, false, -1, false, 0),\n        std::make_tuple(\"min,t:10s\", \"min\", 10000000000, false, -1, false, 0),\n        std::make_tuple(\"tagged,g:-1\", \"tagged\", 0, true, -1, false, 0),\n        std::make_tuple(\"tagged,g:100\", \"tagged\", 0, true, 100, false, 0),\n        std::make_tuple(\"tagged,t:10s,g:100\", \"tagged\", 10000000000, true, 100, false, 0),\n        std::make_tuple(\"tagged,t:10s,g:100/0\", \"tagged\", 10000000000, true, 100, true, 0),\n        std::make_tuple(\"tagged,t:10s,g:100/10\", \"tagged\", 10000000000, true, 100, true, 10),\n        std::make_tuple(\"tagged,g:/10\", \"tagged\", 0, false, -1, true, 10)));\n\nTEST_F(FlowControlTest, shouldParseNull)\n{\n    aeron_flow_control_tagged_options_t options;\n    ASSERT_EQ(0, aeron_flow_control_parse_tagged_options(0, nullptr, &options));\n\n    ASSERT_EQ(0U, options.strategy_name_length);\n    ASSERT_EQ(nullptr, options.strategy_name);\n    ASSERT_EQ(false, options.timeout_ns.is_present);\n    ASSERT_EQ(0U, options.timeout_ns.value);\n    ASSERT_EQ(false, options.group_tag.is_present);\n    ASSERT_EQ(-1, options.group_tag.value);\n}\n\nTEST_F(FlowControlTest, shouldFallWithInvalidStrategyName)\n{\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=minute\");\n    ASSERT_EQ(-1, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=maximilien\");\n    ASSERT_EQ(-1, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n\n    initialise_channel(\"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=taggedagado\");\n    ASSERT_EQ(-1, aeron_default_multicast_flow_control_strategy_supplier(\n        &m_strategy, context, &m_counters_manager, m_channel,\n        1001, 1001, 1001, 0, 64 * 1024));\n}\n\nTEST_P(ParameterisedFailingOptionsParsingTest, shouldBeInvalid)\n{\n    const char *fc_options = std::get<0>(GetParam());\n\n    aeron_flow_control_tagged_options_t options;\n    ASSERT_EQ(-1, aeron_flow_control_parse_tagged_options(strlen(fc_options), fc_options, &options));\n    ASSERT_EQ(std::get<1>(GetParam()), aeron_errcode());\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    ParsingTests,\n    ParameterisedFailingOptionsParsingTest,\n    testing::Values(\n        std::make_tuple(\"min,t1a0s\", EINVAL),\n        std::make_tuple(\"min,g:1f2\", EINVAL),\n        std::make_tuple(\"min,t:10s,g:1b2\", EINVAL),\n        std::make_tuple(\"min,o:-1\", EINVAL),\n        std::make_tuple(\"tagged,g:1/\", EINVAL),\n        std::make_tuple(\"tagged,g:\", EINVAL),\n        std::make_tuple(\"tagged,g:/\", EINVAL)));\n\nTEST(CalculateRetransmissionTest, shouldUseResendLengthIfSmallestValue)\n{\n    int resend_length = 1024;\n\n    ASSERT_EQ(resend_length,\n        aeron_flow_control_calculate_retransmission_length(\n            resend_length,\n            64 * 1024,\n            0,\n            16));\n}\n\nTEST(CalculateRetransmissionTest, shouldClampToTheEndOfTheBuffer)\n{\n    int expected_length = 512;\n    int term_length = 64 * 1024;\n    int term_offset = term_length - expected_length;\n\n    ASSERT_EQ(expected_length,\n        aeron_flow_control_calculate_retransmission_length(\n            1024,\n            term_length,\n            term_offset,\n            16));\n}\n\nTEST(CalculateRetransmissionTest, shouldClampToReceiverWindow)\n{\n    int multiplier = 16;\n    size_t expected_length = aeron_driver_context_get_rcv_initial_window_length(nullptr) * multiplier;\n\n    ASSERT_EQ(expected_length,\n        aeron_flow_control_calculate_retransmission_length(\n            4 * 1024 * 1024,\n            8 * 1024 * 1024,\n            0,\n            16));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_ipc_publication_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeron_ipc_publication.h\"\n#include \"aeron_driver_sender.h\"\n#include \"aeron_position.h\"\n\nint aeron_driver_ensure_dir_is_recreated(aeron_driver_context_t *context);\n}\n\n#define CAPACITY (32 * 1024)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\ntypedef std::array<std::uint8_t, 4 * CAPACITY> buffer_4x_t;\n\nclass IpcPublicationTest : public testing::Test\n{\nprotected:\n    void SetUp() override\n    {\n        aeron_driver_context_init(&m_context);\n        aeron_driver_context_set_dir_delete_on_start(m_context, true);\n\n        aeron_counters_manager_init(\n            &m_counters_manager,\n            m_counter_meta_buffer.data(), m_counter_meta_buffer.size(),\n            m_counter_value_buffer.data(), m_counter_value_buffer.size(),\n            &m_cached_clock,\n            1000);\n\n        aeron_system_counters_init(&m_system_counters, &m_counters_manager);\n\n        aeron_distinct_error_log_init(\n            &m_error_log, m_error_log_buffer.data(), m_error_log_buffer.size(), aeron_epoch_clock);\n\n        m_context->error_log = &m_error_log;\n        m_context->error_buffer = m_error_log_buffer.data();\n        m_context->error_buffer_length = m_error_log_buffer.size();\n\n        aeron_driver_ensure_dir_is_recreated(m_context);\n    }\n\n    void TearDown() override\n    {\n        for (auto publication : m_publications)\n        {\n            aeron_ipc_publication_close(&m_counters_manager, publication);\n            aeron_ipc_publication_free(publication);\n        }\n\n        aeron_system_counters_close(&m_system_counters);\n        aeron_counters_manager_close(&m_counters_manager);\n        aeron_distinct_error_log_close(&m_error_log);\n        aeron_driver_context_close(m_context);\n    }\n\n    aeron_ipc_publication_t *createPublication(const char *uri)\n    {\n        int64_t registration_id = 1;\n        int32_t stream_id = 10;\n        int32_t session_id = 10;\n        size_t uri_length = strlen(uri);\n\n        aeron_position_t pub_pos_position;\n        aeron_position_t pub_lmt_position;\n\n        int64_t client_id = 42;\n        bool is_exclusive = false;\n\n        pub_pos_position.counter_id = aeron_counter_publisher_position_allocate(\n            &m_counters_manager,\n            client_id,\n            registration_id,\n            session_id,\n            stream_id,\n            uri_length,\n            uri,\n            is_exclusive);\n        pub_lmt_position.counter_id = aeron_counter_publisher_limit_allocate(\n            &m_counters_manager, client_id, registration_id, session_id, stream_id, uri_length, uri);\n\n        if (pub_pos_position.counter_id < 0 || pub_lmt_position.counter_id < 0)\n        {\n            return nullptr;\n        }\n\n        pub_pos_position.value_addr = aeron_counters_manager_addr(\n            &m_counters_manager, pub_pos_position.counter_id);\n        pub_lmt_position.value_addr = aeron_counters_manager_addr(\n            &m_counters_manager, pub_lmt_position.counter_id);\n\n        aeron_driver_uri_publication_params_t params = {};\n\n        aeron_ipc_publication_t *publication = nullptr;\n        if (aeron_ipc_publication_create(\n            &publication,\n            m_context,\n            session_id,\n            stream_id,\n            registration_id,\n            &pub_pos_position,\n            &pub_lmt_position,\n            0,\n            &params,\n            is_exclusive,\n            &m_system_counters,\n            uri_length,\n            uri) < 0)\n        {\n            return nullptr;\n        }\n\n        m_publications.push_back(publication);\n\n        return publication;\n    }\n\n    aeron_driver_context_t *m_context = nullptr;\nprivate:\n    aeron_clock_cache_t m_cached_clock = {};\n    aeron_counters_manager_t m_counters_manager = {};\n    aeron_system_counters_t m_system_counters = {};\n    aeron_distinct_error_log_t m_error_log = {};\n    AERON_DECL_ALIGNED(buffer_t m_counter_value_buffer, 16) = {};\n    AERON_DECL_ALIGNED(buffer_4x_t m_counter_meta_buffer, 16) = {};\n    AERON_DECL_ALIGNED(buffer_t m_error_log_buffer, 16) = {};\n    std::vector<aeron_ipc_publication_t *> m_publications;\n};\n\nTEST_F(IpcPublicationTest, shouldCreatePublication)\n{\n    auto channel = \"aeron:ipc|alias=test|mtu=2048\";\n    auto channel_length = strlen(channel);\n\n    aeron_ipc_publication_t *publication = createPublication(channel);\n\n    ASSERT_NE(nullptr, publication) << aeron_errmsg();\n    EXPECT_EQ(static_cast<int32_t>(channel_length), publication->channel_length);\n    EXPECT_EQ(0, strncmp(channel, publication->channel, channel_length));\n    EXPECT_FALSE(publication->is_exclusive);\n}\n\nTEST_F(IpcPublicationTest, shouldReturnStorageSpaceErrorIfNotEnoughStorageSpaceAvailable)\n{\n    m_context->usable_fs_space_func = [](const char* path) -> uint64_t\n    {\n        return 2049;\n    };\n    m_context->perform_storage_checks = true;\n\n    aeron_ipc_publication_t *publication = createPublication(\"aeron:ipc\");\n\n    ASSERT_EQ(nullptr, publication) << aeron_errmsg();\n    EXPECT_EQ(-AERON_ERROR_CODE_STORAGE_SPACE, aeron_errcode());\n    auto expected_error_text =\n        std::string(\"insufficient usable storage for new log of length=4096 usable=2049 in \")\n            .append(m_context->aeron_dir);\n    EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(expected_error_text));\n}\n\nTEST_F(IpcPublicationTest, shouldWarnIfRemainingStorageSpaceIsLow)\n{\n    m_context->usable_fs_space_func = [](const char *path) -> uint64_t\n    {\n        return 1000000;\n    };\n    m_context->low_file_store_warning_threshold = 2020202020ULL;\n    m_context->perform_storage_checks = true;\n\n    aeron_ipc_publication_t *publication = createPublication(\"aeron:ipc\");\n\n    ASSERT_NE(nullptr, publication) << aeron_errmsg();\n    EXPECT_EQ(0, aeron_errcode());\n    auto errors_list = m_context->error_log->observation_list;\n    EXPECT_NE(nullptr, errors_list);\n    EXPECT_NE(0, errors_list->num_observations);\n    auto last_error = errors_list->observations[errors_list->num_observations - 1];\n    EXPECT_EQ(-AERON_ERROR_CODE_STORAGE_SPACE, last_error.error_code);\n    auto error_text = std::string(last_error.description);\n    EXPECT_EQ(error_text.size(), last_error.description_length);\n    auto expected_warning =\n        std::string(\"WARNING: space is running low: threshold=2020202020 usable=1000000 in \")\n            .append(m_context->aeron_dir);\n    EXPECT_NE(std::string::npos, error_text.find(expected_warning));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_logbuffer_unblocker_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_logbuffer_unblocker.h\"\n}\n\n#define TERM_LENGTH (AERON_LOGBUFFER_TERM_MIN_LENGTH)\n#define MTU_LENGTH (1024)\n#define TERM_ID (1)\n\ntypedef std::array<std::uint8_t, TERM_LENGTH> buffer_t;\ntypedef std::array<std::uint8_t, AERON_LOGBUFFER_META_DATA_LENGTH> log_meta_data_buffer_t;\n\nclass TermUnblockerTest : public testing::Test\n{\npublic:\n    TermUnblockerTest() :\n        m_log_meta_data(reinterpret_cast<aeron_logbuffer_metadata_t *>(m_log_meta_data_buffer.data()))\n    {\n        m_term_buffer.fill(0);\n        m_log_meta_data_buffer.fill(0);\n    }\n\nprotected:\n    buffer_t m_term_buffer = {};\n    log_meta_data_buffer_t m_log_meta_data_buffer = {};\n    aeron_logbuffer_metadata_t *m_log_meta_data = nullptr;\n};\n\nTEST_F(TermUnblockerTest, shouldTakeNoActionWhenMessageIsComplete)\n{\n    auto *frame_header = (aeron_frame_header_t *)m_term_buffer.data();\n\n    frame_header->frame_length = AERON_DATA_HEADER_LENGTH;\n\n    int32_t term_offset = 0;\n    int32_t tail_offset = TERM_LENGTH;\n\n    EXPECT_EQ(\n        aeron_term_unblocker_unblock(\n            m_log_meta_data, m_term_buffer.data(), TERM_LENGTH, term_offset, tail_offset, TERM_ID),\n        AERON_TERM_UNBLOCKER_STATUS_NO_ACTION);\n}\n\nTEST_F(TermUnblockerTest, shouldTakeNoActionWhenNoUnblockedMessage)\n{\n    int32_t term_offset = 0;\n    int32_t tail_offset = TERM_LENGTH / 2;\n\n    EXPECT_EQ(\n        aeron_term_unblocker_unblock(\n            m_log_meta_data, m_term_buffer.data(), TERM_LENGTH, term_offset, tail_offset, TERM_ID),\n        AERON_TERM_UNBLOCKER_STATUS_NO_ACTION);\n}\n\nTEST_F(TermUnblockerTest, shouldPatchNonCommittedMessage)\n{\n    int32_t message_length = AERON_DATA_HEADER_LENGTH * 4;\n    auto *data_header = (aeron_data_header_t *)m_term_buffer.data();\n\n    data_header->frame_header.frame_length = -message_length;\n\n    int32_t term_offset = 0;\n    int32_t tail_offset = message_length;\n\n    EXPECT_EQ(\n        aeron_term_unblocker_unblock(\n            m_log_meta_data, m_term_buffer.data(), TERM_LENGTH, term_offset, tail_offset, TERM_ID),\n        AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED);\n    EXPECT_EQ(data_header->frame_header.type, AERON_HDR_TYPE_PAD);\n    EXPECT_EQ(data_header->term_offset, term_offset);\n    EXPECT_EQ(data_header->frame_header.frame_length, message_length);\n}\n\nTEST_F(TermUnblockerTest, shouldPatchToEndOfPartition)\n{\n    int32_t message_length = AERON_DATA_HEADER_LENGTH * 4;\n    int32_t term_offset = TERM_LENGTH - message_length;\n    int32_t tail_offset = TERM_LENGTH;\n\n    auto *data_header = (aeron_data_header_t *)(m_term_buffer.data() + term_offset);\n\n    EXPECT_EQ(\n        aeron_term_unblocker_unblock(\n            m_log_meta_data, m_term_buffer.data(), TERM_LENGTH, term_offset, tail_offset, TERM_ID),\n        AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED_TO_END);\n    EXPECT_EQ(data_header->frame_header.type, AERON_HDR_TYPE_PAD);\n    EXPECT_EQ(data_header->term_offset, term_offset);\n    EXPECT_EQ(data_header->frame_header.frame_length, message_length);\n}\n\nTEST_F(TermUnblockerTest, shouldScanForwardForNextCompleteMessage)\n{\n    int32_t message_length = AERON_DATA_HEADER_LENGTH * 4;\n    int32_t term_offset = 0;\n    int32_t tail_offset = message_length * 2;\n\n    aeron_data_header_t *data_header;\n\n    data_header = (aeron_data_header_t *)(m_term_buffer.data() + message_length);\n    data_header->frame_header.frame_length = message_length;\n\n    data_header = (aeron_data_header_t *)(m_term_buffer.data() + term_offset);\n\n    EXPECT_EQ(\n        aeron_term_unblocker_unblock(\n            m_log_meta_data, m_term_buffer.data(), TERM_LENGTH, term_offset, tail_offset, TERM_ID),\n        AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED);\n    EXPECT_EQ(data_header->frame_header.type, AERON_HDR_TYPE_PAD);\n    EXPECT_EQ(data_header->term_offset, term_offset);\n    EXPECT_EQ(data_header->frame_header.frame_length, message_length);\n}\n\nTEST_F(TermUnblockerTest, shouldScanForwardForNextNonCommittedMessage)\n{\n    int32_t message_length = AERON_DATA_HEADER_LENGTH * 4;\n    int32_t term_offset = 0;\n    int32_t tail_offset = message_length * 2;\n\n    aeron_data_header_t *data_header;\n\n    data_header = (aeron_data_header_t *)(m_term_buffer.data() + message_length);\n    data_header->frame_header.frame_length = -message_length;\n\n    data_header = (aeron_data_header_t *)(m_term_buffer.data() + term_offset);\n\n    EXPECT_EQ(\n        aeron_term_unblocker_unblock(\n            m_log_meta_data, m_term_buffer.data(), TERM_LENGTH, term_offset, tail_offset, TERM_ID),\n        AERON_TERM_UNBLOCKER_STATUS_UNBLOCKED);\n    EXPECT_EQ(data_header->frame_header.type, AERON_HDR_TYPE_PAD);\n    EXPECT_EQ(data_header->term_offset, term_offset);\n    EXPECT_EQ(data_header->frame_header.frame_length, message_length);\n}\n\n#define PARTITION_INDEX (0)\n\nclass LogBufferUnblockerTest : public TermUnblockerTest\n{\npublic:\n    LogBufferUnblockerTest() :\n        m_log_meta_data(reinterpret_cast<aeron_logbuffer_metadata_t *>(m_log_meta_data_buffer.data()))\n    {\n        for (size_t i = 0; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n        {\n            m_term_buffers[i].fill(0);\n            m_mapped_buffers[i].addr = m_term_buffers[i].data();\n            m_mapped_buffers[i].length = m_term_buffers[i].size();\n        }\n\n        m_position_bits_to_shift = (size_t)aeron_number_of_trailing_zeroes(TERM_LENGTH);\n        m_log_meta_data_buffer.fill(0);\n        m_log_meta_data->initial_term_id = TERM_ID;\n        m_log_meta_data->term_tail_counters[0] = (int64_t)TERM_ID << 32;\n\n        for (int i = 1; i < AERON_LOGBUFFER_PARTITION_COUNT; i++)\n        {\n            const int64_t expected_term_id = (TERM_ID + i) - AERON_LOGBUFFER_PARTITION_COUNT;\n            m_log_meta_data->term_tail_counters[i] = expected_term_id << 32;\n        }\n    }\n\nprotected:\n    buffer_t m_term_buffers[AERON_LOGBUFFER_PARTITION_COUNT] = {};\n    aeron_mapped_buffer_t m_mapped_buffers[AERON_LOGBUFFER_PARTITION_COUNT] = {};\n    log_meta_data_buffer_t m_log_meta_data_buffer = {};\n    aeron_logbuffer_metadata_t *m_log_meta_data = nullptr;\n    size_t m_position_bits_to_shift = 0;\n};\n\nTEST_F(LogBufferUnblockerTest, shouldNotUnblockWhenPositionHasCompleteMessage)\n{\n    int32_t blocked_offset = AERON_DATA_HEADER_LENGTH * 4;\n    int64_t blocked_position = aeron_logbuffer_compute_position(\n        TERM_ID, blocked_offset, m_position_bits_to_shift, TERM_ID);\n    const size_t active_index = aeron_logbuffer_index_by_position(blocked_position, m_position_bits_to_shift);\n\n    aeron_data_header_t *data_header;\n    data_header = (aeron_data_header_t *)(m_term_buffers[active_index].data() + blocked_offset);\n\n    data_header->frame_header.frame_length = AERON_DATA_HEADER_LENGTH;\n\n    ASSERT_FALSE(aeron_logbuffer_unblocker_unblock(m_mapped_buffers, m_log_meta_data, blocked_position));\n\n    int64_t raw_tail;\n    AERON_LOGBUFFER_RAWTAIL_VOLATILE(raw_tail, m_log_meta_data);\n    EXPECT_EQ(\n        aeron_logbuffer_compute_position(\n            aeron_logbuffer_term_id(raw_tail), blocked_offset, m_position_bits_to_shift, TERM_ID),\n        blocked_position);\n}\n\nTEST_F(LogBufferUnblockerTest, shouldUnblockWhenPositionHasNonCommittedMessageAndTailWithinTerm)\n{\n    int32_t blocked_offset = AERON_DATA_HEADER_LENGTH * 4;\n    int32_t message_length = AERON_DATA_HEADER_LENGTH * 4;\n    int64_t blocked_position = aeron_logbuffer_compute_position(\n        TERM_ID, blocked_offset, m_position_bits_to_shift, TERM_ID);\n    const size_t active_index = aeron_logbuffer_index_by_position(blocked_position, m_position_bits_to_shift);\n\n    aeron_data_header_t *data_header;\n    data_header = (aeron_data_header_t *)(m_term_buffers[active_index].data() + blocked_offset);\n\n    data_header->frame_header.frame_length = -message_length;\n\n    ASSERT_TRUE(aeron_logbuffer_unblocker_unblock(m_mapped_buffers, m_log_meta_data, blocked_position));\n\n    int64_t raw_tail;\n    AERON_LOGBUFFER_RAWTAIL_VOLATILE(raw_tail, m_log_meta_data);\n    EXPECT_EQ(\n        aeron_logbuffer_compute_position(\n            aeron_logbuffer_term_id(raw_tail), blocked_offset + message_length, m_position_bits_to_shift, TERM_ID),\n        blocked_position + message_length);\n}\n\nTEST_F(LogBufferUnblockerTest, shouldUnblockWhenPositionHasNonCommittedMessageAndTailAtEndOfTerm)\n{\n    int32_t message_length = AERON_DATA_HEADER_LENGTH * 4;\n    int32_t blocked_offset = TERM_LENGTH - message_length;\n    int64_t blocked_position = aeron_logbuffer_compute_position(\n        TERM_ID, blocked_offset, m_position_bits_to_shift, TERM_ID);\n\n    m_log_meta_data->term_tail_counters[0] = (int64_t)TERM_ID << 32 | TERM_LENGTH;\n\n    ASSERT_TRUE(aeron_logbuffer_unblocker_unblock(m_mapped_buffers, m_log_meta_data, blocked_position));\n\n    EXPECT_EQ(m_log_meta_data->active_term_count, 1);\n\n    int64_t raw_tail;\n    AERON_LOGBUFFER_RAWTAIL_VOLATILE(raw_tail, m_log_meta_data);\n    EXPECT_EQ(\n        aeron_logbuffer_compute_position(\n            aeron_logbuffer_term_id(raw_tail), 0, m_position_bits_to_shift, TERM_ID),\n        blocked_position + message_length);\n}\n\nTEST_F(LogBufferUnblockerTest, shouldUnblockWhenPositionHasCommittedMessageAndTailAtEndOfTermButNotRotated)\n{\n    int64_t blocked_position = TERM_LENGTH;\n\n    m_log_meta_data->term_tail_counters[0] = (int64_t)TERM_ID << 32 | TERM_LENGTH;\n\n    ASSERT_TRUE(aeron_logbuffer_unblocker_unblock(m_mapped_buffers, m_log_meta_data, blocked_position));\n\n    EXPECT_EQ(m_log_meta_data->active_term_count, 1);\n\n    int64_t raw_tail;\n    AERON_LOGBUFFER_RAWTAIL_VOLATILE(raw_tail, m_log_meta_data);\n    const int32_t term_id = aeron_logbuffer_term_id(raw_tail);\n\n    EXPECT_EQ(term_id, (TERM_ID + 1));\n    EXPECT_EQ(aeron_logbuffer_compute_position(term_id, 0, m_position_bits_to_shift, TERM_ID), blocked_position);\n}\n\nTEST_F(LogBufferUnblockerTest, shouldUnblockWhenPositionHasNonCommittedMessageAndTailPastEndOfTerm)\n{\n    int32_t message_length = AERON_DATA_HEADER_LENGTH * 4;\n    int32_t blocked_offset = TERM_LENGTH - message_length;\n    int64_t blocked_position = aeron_logbuffer_compute_position(\n        TERM_ID, blocked_offset, m_position_bits_to_shift, TERM_ID);\n\n    m_log_meta_data->term_tail_counters[0] = (int64_t)TERM_ID << 32 | (TERM_LENGTH + AERON_DATA_HEADER_LENGTH);\n\n    ASSERT_TRUE(aeron_logbuffer_unblocker_unblock(m_mapped_buffers, m_log_meta_data, blocked_position));\n\n    EXPECT_EQ(m_log_meta_data->active_term_count, 1);\n\n    int64_t raw_tail;\n    AERON_LOGBUFFER_RAWTAIL_VOLATILE(raw_tail, m_log_meta_data);\n    EXPECT_EQ(\n        aeron_logbuffer_compute_position(\n            aeron_logbuffer_term_id(raw_tail), 0, m_position_bits_to_shift, TERM_ID),\n        blocked_position + message_length);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_loss_detector_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeron_loss_detector.h\"\n}\n\n#define CAPACITY (AERON_LOGBUFFER_TERM_MIN_LENGTH)\n#define POSITION_BITS_TO_SHIFT (aeron_number_of_trailing_zeroes(CAPACITY))\n#define MASK (CAPACITY - 1)\n\n#define TERM_ID (0x1234)\n#define SESSION_ID (0xDEADBEEF)\n#define STREAM_ID (0x12A)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\n\nclass TermGapScannerTest : public testing::Test\n{\npublic:\n    TermGapScannerTest() :\n        m_ptr(m_buffer.data())\n    {\n        m_buffer.fill(0);\n    }\n\n    static void on_gap_detected(void *clientd, int32_t term_id, int32_t term_offset, size_t length)\n    {\n        auto *t = (TermGapScannerTest *)clientd;\n\n        t->m_on_gap_detected(term_id, term_offset, length);\n    }\n\nprotected:\n    buffer_t m_buffer = {};\n    uint8_t *m_ptr = nullptr;\n    std::function<void(int32_t, int32_t, size_t)> m_on_gap_detected;\n};\n\nTEST_F(TermGapScannerTest, shouldReportGapAtBeginningOfBuffer)\n{\n    const int32_t frame_offset = AERON_ALIGN((AERON_DATA_HEADER_LENGTH * 3), AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    const int32_t high_water_mark = frame_offset + AERON_DATA_HEADER_LENGTH;\n    auto *hdr = (aeron_frame_header_t *)(m_ptr + frame_offset);\n\n    hdr->frame_length = AERON_DATA_HEADER_LENGTH;\n\n    bool on_gap_detected_called = false;\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, 0);\n            EXPECT_EQ(length, (size_t)frame_offset);\n            on_gap_detected_called = true;\n        };\n\n    ASSERT_EQ(aeron_term_gap_scanner_scan_for_gap(\n        m_ptr, TERM_ID, 0, high_water_mark, TermGapScannerTest::on_gap_detected, this), 0);\n\n    EXPECT_TRUE(on_gap_detected_called);\n}\n\nTEST_F(TermGapScannerTest, shouldReportSingleGapWhenBufferNotFull)\n{\n    const int32_t tail = AERON_DATA_HEADER_LENGTH;\n    const int32_t high_water_mark = AERON_LOGBUFFER_FRAME_ALIGNMENT * 3;\n\n    aeron_frame_header_t *hdr;\n\n    hdr = (aeron_frame_header_t *)(m_ptr + tail - (AERON_ALIGN(AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT)));\n    hdr->frame_length = AERON_DATA_HEADER_LENGTH;\n\n    hdr = (aeron_frame_header_t *)(m_ptr + tail);\n    hdr->frame_length = 0;\n\n    hdr = (aeron_frame_header_t *)(m_ptr + high_water_mark - (AERON_ALIGN(AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT)));\n    hdr->frame_length = AERON_DATA_HEADER_LENGTH;\n\n    bool on_gap_detected_called = false;\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, tail);\n            EXPECT_EQ(length, (size_t)AERON_ALIGN(AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT));\n            on_gap_detected_called = true;\n        };\n\n    ASSERT_EQ(aeron_term_gap_scanner_scan_for_gap(\n        m_ptr, TERM_ID, tail, high_water_mark, TermGapScannerTest::on_gap_detected, this), tail);\n\n    EXPECT_TRUE(on_gap_detected_called);\n}\n\nTEST_F(TermGapScannerTest, shouldReportSingleGapWhenBufferIsFull)\n{\n    const int32_t tail = CAPACITY - (AERON_DATA_HEADER_LENGTH * 2);\n    const int32_t high_water_mark = CAPACITY;\n\n    aeron_frame_header_t *hdr;\n\n    hdr = (aeron_frame_header_t *)(m_ptr + tail - (AERON_ALIGN(AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT)));\n    hdr->frame_length = AERON_DATA_HEADER_LENGTH;\n\n    hdr = (aeron_frame_header_t *)(m_ptr + tail);\n    hdr->frame_length = 0;\n\n    hdr = (aeron_frame_header_t *)(m_ptr + high_water_mark - (AERON_ALIGN(AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT)));\n    hdr->frame_length = AERON_DATA_HEADER_LENGTH;\n\n    bool on_gap_detected_called = false;\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, tail);\n            EXPECT_EQ(length, (size_t)AERON_ALIGN(AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT));\n            on_gap_detected_called = true;\n        };\n\n    ASSERT_EQ(aeron_term_gap_scanner_scan_for_gap(\n        m_ptr, TERM_ID, tail, high_water_mark, TermGapScannerTest::on_gap_detected, this), tail);\n\n    EXPECT_TRUE(on_gap_detected_called);\n}\n\nTEST_F(TermGapScannerTest, shouldReportNoGapWhenHwmIsInPadding)\n{\n    const int32_t padding_length = AERON_DATA_HEADER_LENGTH * 2;\n    const int32_t tail = CAPACITY - padding_length;\n    const int32_t high_water_mark = CAPACITY - padding_length + AERON_DATA_HEADER_LENGTH;\n\n    aeron_frame_header_t *hdr;\n\n    hdr = (aeron_frame_header_t *)(m_ptr + tail);\n    hdr->frame_length = padding_length;\n\n    hdr = (aeron_frame_header_t *)(m_ptr + tail + AERON_DATA_HEADER_LENGTH);\n    hdr->frame_length = 0;\n\n    bool on_gap_detected_called = false;\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            on_gap_detected_called = true;\n        };\n\n    ASSERT_EQ(aeron_term_gap_scanner_scan_for_gap(\n        m_ptr, TERM_ID, tail, high_water_mark, TermGapScannerTest::on_gap_detected, this), CAPACITY);\n\n    EXPECT_FALSE(on_gap_detected_called);\n}\n\n#define DATA_LENGTH (36)\n#define MESSAGE_LENGTH (DATA_LENGTH + AERON_DATA_HEADER_LENGTH)\n#define ALIGNED_FRAME_LENGTH (AERON_ALIGN(MESSAGE_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT))\n\nclass LossDetectorTest : public testing::Test\n{\npublic:\n    LossDetectorTest() :\n        m_ptr(m_buffer.data()),\n        m_time(0)\n    {\n        m_buffer.fill(0);\n    }\n\n    static void on_gap_detected(void *clientd, int32_t term_id, int32_t term_offset, size_t length)\n    {\n        auto *t = (LossDetectorTest *)clientd;\n\n        t->m_on_gap_detected(term_id, term_offset, length);\n    }\n\n    static int32_t offset_of_message(int index)\n    {\n        return index * ALIGNED_FRAME_LENGTH;\n    }\n\n    void insert_frame(int32_t offset)\n    {\n        auto *hdr = (aeron_data_header_t *)(m_ptr + offset);\n\n        hdr->frame_header.frame_length = MESSAGE_LENGTH;\n    }\n\n    int feedback_delay_state_init(bool should_immediate_feedback)\n    {\n        return aeron_feedback_delay_state_init(\n            &m_delay_generator_state,\n            aeron_loss_detector_nak_unicast_delay_generator,\n            should_immediate_feedback ? 0 : 20 * 1000 * 1000LL,\n            20 * 1000 * 1000LL,\n            1);\n    }\n\nprotected:\n    buffer_t m_buffer = {};\n    uint8_t *m_ptr = nullptr;\n    int64_t m_time = 0;\n    aeron_loss_detector_t m_detector = {};\n    aeron_feedback_delay_generator_state_t m_delay_generator_state = {};\n    std::function<void(int32_t, int32_t, size_t)> m_on_gap_detected;\n};\n\nTEST_F(LossDetectorTest, shouldNotSendNakWhenBufferIsEmpty)\n{\n    const int64_t rebuild_position = 0;\n    const int64_t hwm_position = 0;\n    bool loss_found;\n\n    ASSERT_EQ(feedback_delay_state_init(true), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            FAIL();\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        hwm_position);\n    EXPECT_FALSE(loss_found);\n\n    m_time = 100 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        hwm_position);\n    EXPECT_FALSE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldNotNakIfNoMissingData)\n{\n    const int64_t rebuild_position = 0;\n    const int64_t hwm_position = rebuild_position + (ALIGNED_FRAME_LENGTH * 3);\n    bool loss_found;\n\n    insert_frame(offset_of_message(0));\n    insert_frame(offset_of_message(1));\n    insert_frame(offset_of_message(2));\n\n    ASSERT_EQ(feedback_delay_state_init(true), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            FAIL();\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        hwm_position);\n    EXPECT_FALSE(loss_found);\n\n    m_time = 40 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        hwm_position);\n    EXPECT_FALSE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldNakMissingData)\n{\n    const int64_t rebuild_position = 0;\n    const int64_t hwm_position = rebuild_position + (ALIGNED_FRAME_LENGTH * 3);\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(0));\n    insert_frame(offset_of_message(2));\n\n    ASSERT_EQ(feedback_delay_state_init(false), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(1));\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH);\n            called++;\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 0);\n    EXPECT_TRUE(loss_found);\n\n    m_time = 40 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 1);\n    EXPECT_FALSE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldRetransmitNakForMissingData)\n{\n    const int64_t rebuild_position = 0;\n    const int64_t hwm_position = rebuild_position + (ALIGNED_FRAME_LENGTH * 3);\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(0));\n    insert_frame(offset_of_message(2));\n\n    ASSERT_EQ(feedback_delay_state_init(false), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(1));\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH);\n            called++;\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 0);\n    EXPECT_TRUE(loss_found);\n\n    m_time = 30 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 1);\n    EXPECT_FALSE(loss_found);\n\n    m_time = 60 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 2);\n    EXPECT_FALSE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldStopNakOnReceivingData)\n{\n    int64_t rebuild_position = 0;\n    const int64_t hwm_position = rebuild_position + (ALIGNED_FRAME_LENGTH * 3);\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(0));\n    insert_frame(offset_of_message(2));\n\n    ASSERT_EQ(feedback_delay_state_init(false), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(1));\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH);\n            called++;\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 0);\n    EXPECT_TRUE(loss_found);\n\n    m_time = 20 * 1000 * 1000L;\n    insert_frame(offset_of_message(1));\n    rebuild_position += (3 * ALIGNED_FRAME_LENGTH);\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        hwm_position);\n    EXPECT_EQ(called, 0);\n    EXPECT_FALSE(loss_found);\n\n    m_time = 100 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        hwm_position);\n    EXPECT_EQ(called, 0);\n    EXPECT_FALSE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldHandleMoreThan2Gaps)\n{\n    int64_t rebuild_position = 0;\n    const int64_t hwm_position = rebuild_position + (ALIGNED_FRAME_LENGTH * 7);\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(0));\n    insert_frame(offset_of_message(2));\n    insert_frame(offset_of_message(4));\n    insert_frame(offset_of_message(6));\n\n    ASSERT_EQ(feedback_delay_state_init(false), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH);\n            called++;\n\n            if (1 == called)\n            {\n                EXPECT_EQ(term_offset, offset_of_message(1));\n            }\n            else if (2 == called)\n            {\n                EXPECT_EQ(term_offset, offset_of_message(3));\n            }\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 0);\n    EXPECT_TRUE(loss_found);\n\n    m_time = 40 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 1);\n    EXPECT_FALSE(loss_found);\n\n    insert_frame(offset_of_message(1));\n    rebuild_position += (3 * ALIGNED_FRAME_LENGTH);\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_EQ(called, 1);\n    EXPECT_TRUE(loss_found);\n\n    m_time = 80 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_EQ(called, 2);\n    EXPECT_FALSE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldReplaceOldNakWithNewNak)\n{\n    int64_t rebuild_position = 0;\n    int64_t hwm_position = rebuild_position + (ALIGNED_FRAME_LENGTH * 3);\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(0));\n    insert_frame(offset_of_message(2));\n\n    ASSERT_EQ(feedback_delay_state_init(false), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH);\n            EXPECT_EQ(term_offset, offset_of_message(3));\n            called++;\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 0);\n    EXPECT_TRUE(loss_found);\n\n    m_time = 10 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 0);\n    EXPECT_FALSE(loss_found);\n\n    insert_frame(offset_of_message(4));\n    insert_frame(offset_of_message(1));\n    rebuild_position += (3 * ALIGNED_FRAME_LENGTH);\n    hwm_position = (ALIGNED_FRAME_LENGTH * 5);\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_EQ(called, 0);\n    EXPECT_TRUE(loss_found);\n\n    m_time = 100 * 1000 * 1000L;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_EQ(called, 1);\n    EXPECT_FALSE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldHandleImmediateNak)\n{\n    const int64_t rebuild_position = 0;\n    const int64_t hwm_position = rebuild_position + (ALIGNED_FRAME_LENGTH * 3);\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(0));\n    insert_frame(offset_of_message(2));\n\n    ASSERT_EQ(feedback_delay_state_init(true), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(1));\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH);\n            called++;\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 1);\n    EXPECT_TRUE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldNotNakImmediatelyByDefault)\n{\n    const int64_t rebuild_position = 0;\n    const int64_t hwm_position = rebuild_position + (ALIGNED_FRAME_LENGTH * 3);\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(0));\n    insert_frame(offset_of_message(2));\n\n    ASSERT_EQ(feedback_delay_state_init(false), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(1));\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH);\n            called++;\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 0);\n    EXPECT_TRUE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldOnlySendNaksOnceOnMultipleScans)\n{\n    const int64_t rebuild_position = 0;\n    const int64_t hwm_position = rebuild_position + (ALIGNED_FRAME_LENGTH * 3);\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(0));\n    insert_frame(offset_of_message(2));\n\n    ASSERT_EQ(feedback_delay_state_init(true), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(1));\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH);\n            called++;\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 1);\n    EXPECT_TRUE(loss_found);\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 1);\n    EXPECT_FALSE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldHandleHwmGreaterThanCompletedBuffer)\n{\n    int64_t rebuild_position = 0;\n    const int64_t hwm_position = rebuild_position + CAPACITY + ALIGNED_FRAME_LENGTH;\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(0));\n    rebuild_position += ALIGNED_FRAME_LENGTH;\n\n    ASSERT_EQ(feedback_delay_state_init(true), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(1));\n            EXPECT_EQ(length, CAPACITY - (size_t)rebuild_position);\n            called++;\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(1));\n    EXPECT_EQ(called, 1);\n    EXPECT_TRUE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldHandleNonZeroInitialTermOffset)\n{\n    int64_t rebuild_position = ALIGNED_FRAME_LENGTH * 3;\n    const int64_t hwm_position = ALIGNED_FRAME_LENGTH * 5;\n    bool loss_found;\n    int called = 0;\n\n    insert_frame(offset_of_message(2));\n    insert_frame(offset_of_message(4));\n\n    ASSERT_EQ(feedback_delay_state_init(true), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(3));\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH);\n            called++;\n        };\n\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, hwm_position, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_EQ(called, 1);\n    EXPECT_TRUE(loss_found);\n}\n\nTEST_F(LossDetectorTest, shouldDetectChangesInTheGapLength)\n{\n    const int64_t rebuild_position = ALIGNED_FRAME_LENGTH * 3;\n    bool loss_found;\n    bool called = false;\n\n    insert_frame(offset_of_message(2));\n    insert_frame(offset_of_message(5));\n\n    ASSERT_EQ(feedback_delay_state_init(true), 0);\n    ASSERT_EQ(aeron_loss_detector_init(\n        &m_detector, &m_delay_generator_state, LossDetectorTest::on_gap_detected, this), 0);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(3));\n            EXPECT_EQ(length, 32);\n            called = true;\n        };\n\n    called = false;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, rebuild_position + 32, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_TRUE(called);\n    EXPECT_TRUE(loss_found);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(3));\n            EXPECT_EQ(length, 64);\n            called = true;\n        };\n\n    called = false;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, rebuild_position + 64, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_TRUE(called);\n    EXPECT_TRUE(loss_found);\n\n    called = false;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, rebuild_position + 64, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_FALSE(called);\n    EXPECT_FALSE(loss_found);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(3));\n            EXPECT_EQ(length, 32);\n            called = true;\n        };\n\n    called = false;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, rebuild_position + 32, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_TRUE(called);\n    EXPECT_TRUE(loss_found);\n\n    m_on_gap_detected =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, offset_of_message(3));\n            EXPECT_EQ(length, ALIGNED_FRAME_LENGTH * 2);\n            called = true;\n        };\n\n    called = false;\n    ASSERT_EQ(aeron_loss_detector_scan(\n        &m_detector, &loss_found, m_ptr, rebuild_position, rebuild_position + CAPACITY, m_time, MASK, POSITION_BITS_TO_SHIFT, TERM_ID),\n        offset_of_message(3));\n    EXPECT_TRUE(called);\n    EXPECT_TRUE(loss_found);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_loss_reporter_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"reports/aeron_loss_reporter.h\"\n}\n\n#define CAPACITY (1024)\n\n#define SESSION_ID (0x0EADBEEF)\n#define STREAM_ID (0x12A)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\n\nclass LossReporterTest : public testing::Test\n{\npublic:\n    LossReporterTest() :\n        m_ptr(m_buffer.data())\n    {\n        m_buffer.fill(0);\n    }\n\n    static void on_loss_entry(\n        void *clientd,\n        int64_t observation_count,\n        int64_t total_bytes_lost,\n        int64_t first_observation_timestamp,\n        int64_t last_observation_timestamp,\n        int32_t session_id,\n        int32_t stream_id,\n        const char *channel,\n        int32_t channel_length,\n        const char *source,\n        int32_t source_length)\n    {\n        auto *t = (LossReporterTest *)clientd;\n\n        t->m_on_loss_entry(\n            observation_count,\n            total_bytes_lost,\n            first_observation_timestamp,\n            last_observation_timestamp,\n            session_id,\n            stream_id,\n            channel,\n            channel_length,\n            source,\n            source_length);\n    }\n\nprotected:\n    buffer_t m_buffer = {};\n    uint8_t *m_ptr = nullptr;\n    aeron_loss_reporter_t m_reporter = {};\n    std::function<void(int64_t, int64_t, int64_t, int64_t, int32_t, int32_t, const char *, int32_t, const char *, int32_t)>\n        m_on_loss_entry;\n};\n\nTEST_F(LossReporterTest, shouldCreateEntry)\n{\n    ASSERT_EQ(aeron_loss_reporter_init(&m_reporter, m_ptr, CAPACITY), 0);\n\n    const int64_t initial_bytes_lost = 32;\n    const int64_t timestamp_ms = 7;\n    const char *channel = \"aeron:udp://stuff\";\n    const char *source = \"127.0.0.1:8888\";\n\n    aeron_loss_reporter_entry_offset_t offset = aeron_loss_reporter_create_entry(\n        &m_reporter,\n        initial_bytes_lost,\n        timestamp_ms,\n        SESSION_ID,\n        STREAM_ID,\n        channel,\n        strlen(channel),\n        source,\n        strlen(source));\n\n    EXPECT_EQ(offset, 0);\n\n    auto *entry = (aeron_loss_reporter_entry_t *)m_ptr;\n    EXPECT_EQ(entry->observation_count, 1);\n    EXPECT_EQ(entry->total_bytes_lost, initial_bytes_lost);\n    EXPECT_EQ(entry->first_observation_timestamp, timestamp_ms);\n    EXPECT_EQ(entry->last_observation_timestamp, timestamp_ms);\n    EXPECT_EQ(entry->session_id, SESSION_ID);\n    EXPECT_EQ(entry->stream_id, STREAM_ID);\n\n    const char *channel_ptr = (const char *)(m_ptr + sizeof(aeron_loss_reporter_entry_t));\n    EXPECT_EQ(*(int32_t *)(channel_ptr), (int32_t)strlen(channel));\n    EXPECT_EQ(std::string(channel_ptr + sizeof(int32_t), strlen(channel)), std::string(channel));\n\n    const char *source_ptr = (const char *)(\n        m_ptr +\n            sizeof(aeron_loss_reporter_entry_t) +\n            AERON_ALIGN((sizeof(int32_t) + strlen(channel)), sizeof(int32_t)));\n    EXPECT_EQ(*(int32_t *)(source_ptr), (int32_t)strlen(source));\n    EXPECT_EQ(std::string(source_ptr + sizeof(int32_t), strlen(source)), std::string(source));\n}\n\nTEST_F(LossReporterTest, shouldUpdateEntry)\n{\n    ASSERT_EQ(aeron_loss_reporter_init(&m_reporter, m_ptr, CAPACITY), 0);\n\n    const int64_t initial_bytes_lost = 32;\n    const int64_t timestamp_ms = 7;\n    const char *channel = \"aeron:udp://stuff\";\n    const char *source = \"127.0.0.1:8888\";\n\n    aeron_loss_reporter_entry_offset_t offset = aeron_loss_reporter_create_entry(\n        &m_reporter,\n        initial_bytes_lost,\n        timestamp_ms,\n        SESSION_ID,\n        STREAM_ID,\n        channel,\n        strlen(channel),\n        source,\n        strlen(source));\n\n    EXPECT_EQ(offset, 0);\n\n    int64_t additional_bytes_lost = 64;\n    int64_t latest_timestamp = 10;\n\n    aeron_loss_reporter_record_observation(&m_reporter, offset, additional_bytes_lost, latest_timestamp);\n\n    auto *entry = (aeron_loss_reporter_entry_t *)m_ptr;\n    EXPECT_EQ(entry->observation_count, 2);\n    EXPECT_EQ(entry->total_bytes_lost, initial_bytes_lost + additional_bytes_lost);\n    EXPECT_EQ(entry->last_observation_timestamp, latest_timestamp);\n}\n\nTEST_F(LossReporterTest, shouldReadNoEntriesInEmptyReport)\n{\n    size_t called = 0;\n    m_on_loss_entry =\n        [&](int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *channel,\n            int32_t channel_length,\n            const char *source,\n            int32_t source_length)\n        {\n            called++;\n        };\n\n    EXPECT_EQ(aeron_loss_reporter_read(m_ptr, CAPACITY, LossReporterTest::on_loss_entry, this), 0u);\n    EXPECT_EQ(called, 0u);\n}\n\nTEST_F(LossReporterTest, shouldReadOneEntry)\n{\n    ASSERT_EQ(aeron_loss_reporter_init(&m_reporter, m_ptr, CAPACITY), 0);\n\n    const int64_t initial_bytes_lost = 32;\n    const int64_t timestamp_ms = 7;\n    const char *channel = \"aeron:udp://stuff\";\n    const char *source = \"127.0.0.1:8888\";\n\n    aeron_loss_reporter_entry_offset_t offset = aeron_loss_reporter_create_entry(\n        &m_reporter,\n        initial_bytes_lost,\n        timestamp_ms,\n        SESSION_ID,\n        STREAM_ID,\n        channel,\n        strlen(channel),\n        source,\n        strlen(source));\n\n    EXPECT_EQ(offset, 0);\n\n    size_t called = 0;\n    m_on_loss_entry =\n        [&](int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *read_channel,\n            int32_t channel_length,\n            const char *read_source,\n            int32_t source_length)\n        {\n            EXPECT_EQ(observation_count, 1);\n            EXPECT_EQ(total_bytes_lost, initial_bytes_lost);\n            EXPECT_EQ(first_observation_timestamp, timestamp_ms);\n            EXPECT_EQ(last_observation_timestamp, timestamp_ms);\n            EXPECT_EQ(session_id, SESSION_ID);\n            EXPECT_EQ(stream_id, STREAM_ID);\n            EXPECT_EQ(std::string(read_channel, channel_length), std::string(channel));\n            EXPECT_EQ(std::string(read_source, source_length), std::string(source));\n            called++;\n        };\n\n    EXPECT_EQ(aeron_loss_reporter_read(m_ptr, CAPACITY, LossReporterTest::on_loss_entry, this), 1u);\n    EXPECT_EQ(called, 1u);\n}\n\nTEST_F(LossReporterTest, shouldReadTwoEntries)\n{\n    ASSERT_EQ(aeron_loss_reporter_init(&m_reporter, m_ptr, CAPACITY), 0);\n\n    const int64_t initial_bytes_lost_1 = 32;\n    const int64_t timestamp_ms_1 = 7;\n    const int32_t session_id_1 = SESSION_ID;\n    const int32_t stream_id_1 = STREAM_ID;\n    const char *channel_1 = \"aeron:udp://stuff\";\n    const char *source_1 = \"127.0.0.1:8888\";\n\n    const int64_t initial_bytes_lost_2 = 48;\n    const int64_t timestamp_ms_2 = 17;\n    const int32_t session_id_2 = SESSION_ID + 1;\n    const int32_t stream_id_2 = STREAM_ID + 1;\n    const char *channel_2 = \"aeron:udp://stuff2\";\n    const char *source_2 = \"127.0.0.1:9999\";\n\n    aeron_loss_reporter_entry_offset_t offset_1 = aeron_loss_reporter_create_entry(\n        &m_reporter,\n        initial_bytes_lost_1,\n        timestamp_ms_1,\n        session_id_1,\n        stream_id_1,\n        channel_1,\n        strlen(channel_1),\n        source_1,\n        strlen(source_1));\n\n    aeron_loss_reporter_entry_offset_t offset_2 = aeron_loss_reporter_create_entry(\n        &m_reporter,\n        initial_bytes_lost_2,\n        timestamp_ms_2,\n        session_id_2,\n        stream_id_2,\n        channel_2,\n        strlen(channel_2),\n        source_2,\n        strlen(source_2));\n\n    EXPECT_EQ(offset_1, 0);\n    EXPECT_GT(offset_2, 0);\n\n    size_t called = 0;\n    m_on_loss_entry =\n        [&](\n            int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *read_channel,\n            int32_t channel_length,\n            const char *read_source,\n            int32_t source_length)\n        {\n            called++;\n\n            if (1 == called)\n            {\n                EXPECT_EQ(observation_count, 1);\n                EXPECT_EQ(total_bytes_lost, initial_bytes_lost_1);\n                EXPECT_EQ(first_observation_timestamp, timestamp_ms_1);\n                EXPECT_EQ(last_observation_timestamp, timestamp_ms_1);\n                EXPECT_EQ(session_id, session_id_1);\n                EXPECT_EQ(stream_id, stream_id_1);\n                EXPECT_EQ(std::string(read_channel, channel_length), std::string(channel_1));\n                EXPECT_EQ(std::string(read_source, source_length), std::string(source_1));\n            }\n            else if (2 == called)\n            {\n                EXPECT_EQ(observation_count, 1);\n                EXPECT_EQ(total_bytes_lost, initial_bytes_lost_2);\n                EXPECT_EQ(first_observation_timestamp, timestamp_ms_2);\n                EXPECT_EQ(last_observation_timestamp, timestamp_ms_2);\n                EXPECT_EQ(session_id, session_id_2);\n                EXPECT_EQ(stream_id, stream_id_2);\n                EXPECT_EQ(std::string(read_channel, channel_length), std::string(channel_2));\n                EXPECT_EQ(std::string(read_source, source_length), std::string(source_2));\n            }\n        };\n\n    EXPECT_EQ(aeron_loss_reporter_read(m_ptr, CAPACITY, LossReporterTest::on_loss_entry, this), 2u);\n    EXPECT_EQ(called, 2u);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_name_resolver_cache_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include <stdlib.h>\n#include <string.h>\n#include \"protocol/aeron_udp_protocol.h\"\n#include \"aeron_name_resolver_cache.h\"\n}\n\nclass NameResolverCacheTest : public testing::Test\n{\npublic:\n    NameResolverCacheTest() = default;\n\nprotected:\n    void TearDown() override\n    {\n        aeron_name_resolver_cache_close(&m_cache);\n    }\n\n    aeron_name_resolver_cache_t m_cache = {};\n    int64_t m_counter = 0;\n};\n\nTEST_F(NameResolverCacheTest, shouldAddAndLookupEntry)\n{\n    aeron_name_resolver_cache_init(&m_cache, 0);\n    aeron_name_resolver_cache_addr_t cache_addr = {};\n\n    for (int i = 0; i < 1000; i++)\n    {\n        aeron_name_resolver_cache_entry_t *cache_entry = nullptr;\n        char name[32] = { 0 };\n\n        snprintf(name, sizeof(name) - 1, \"hostname%d\", i);\n        cache_addr.res_type = (i & 1) == 1 ? AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD : AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD;\n        size_t address_length = (i & 1) == 1 ? 16 : 4;\n\n        for (size_t j = 0; j < sizeof(cache_addr.address); j++)\n        {\n            cache_addr.address[j] = rand();\n        }\n        cache_addr.port = rand();\n\n        ASSERT_EQ(\n            1, aeron_name_resolver_cache_add_or_update(\n            &m_cache, name, strlen(name), &cache_addr, 0, &m_counter))\n                            << \"Iteration: \" << i;\n        ASSERT_LE(0,\n            aeron_name_resolver_cache_lookup_by_name(&m_cache, name, strlen(name), cache_addr.res_type, &cache_entry));\n        ASSERT_EQ(cache_addr.res_type, cache_entry->cache_addr.res_type);\n        ASSERT_EQ(cache_addr.port, cache_entry->cache_addr.port);\n        ASSERT_EQ(0, memcmp(cache_addr.address, &cache_entry->cache_addr.address, address_length)) << i;\n    }\n}\n\nTEST_F(NameResolverCacheTest, shouldTimeoutEntries)\n{\n    aeron_name_resolver_cache_init(&m_cache, 2000);\n    aeron_name_resolver_cache_addr_t cache_addr = {};\n    cache_addr.res_type = AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD;\n\n    int64_t now_ms = 0;\n    for (int i = 0; i < 5; i++)\n    {\n        now_ms = i * 1000;\n\n        char name[32] = { 0 };\n        snprintf(name, sizeof(name) - 1, \"hostname%d\", i);\n\n        for (size_t j = 0; j < sizeof(cache_addr.address); j++)\n        {\n            cache_addr.address[j] = rand();\n        }\n\n        cache_addr.port = rand();\n\n        aeron_name_resolver_cache_add_or_update(&m_cache, name, strlen(name), &cache_addr, now_ms, &m_counter);\n    }\n\n    ASSERT_EQ(m_counter, (int64_t)m_cache.entries.length);\n\n    aeron_name_resolver_cache_add_or_update(\n        &m_cache, \"hostname1\", strlen(\"hostname1\"), &cache_addr, now_ms, &m_counter);\n\n    ASSERT_EQ(2, aeron_name_resolver_cache_timeout_old_entries(&m_cache, now_ms, &m_counter));\n    ASSERT_LE(0, aeron_name_resolver_cache_lookup_by_name(\n        &m_cache, \"hostname1\", strlen(\"hostname1\"), cache_addr.res_type, nullptr));\n    ASSERT_LE(0, aeron_name_resolver_cache_lookup_by_name(\n        &m_cache, \"hostname3\", strlen(\"hostname3\"), cache_addr.res_type, nullptr));\n    ASSERT_LE(0, aeron_name_resolver_cache_lookup_by_name(\n        &m_cache, \"hostname4\", strlen(\"hostname4\"), cache_addr.res_type, nullptr));\n    ASSERT_EQ(-1, aeron_name_resolver_cache_lookup_by_name(\n        &m_cache, \"hostname0\", strlen(\"hostname0\"), cache_addr.res_type, nullptr));\n    ASSERT_EQ(-1, aeron_name_resolver_cache_lookup_by_name(\n        &m_cache, \"hostname2\", strlen(\"hostname2\"), cache_addr.res_type, nullptr));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_name_resolver_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include <stdlib.h>\n#include \"util/aeron_parse_util.h\"\n#include \"util/aeron_env.h\"\n#include \"aeron_name_resolver.h\"\n#include \"aeron_name_resolver_cache.h\"\n#include \"aeron_driver_name_resolver.h\"\n#include \"aeron_system_counters.h\"\n#include \"agent/aeron_driver_agent.h\"\n#include \"aeron_csv_table_name_resolver.h\"\n}\n\n#define METADATA_LENGTH (32 * 1024)\n#define VALUES_LENGTH (METADATA_LENGTH / 4)\n#define ERROR_LOG_LENGTH (8192)\n\nclass NameResolverTest : public testing::Test\n{\npublic:\n    NameResolverTest()\n    {\n        m_a.context = nullptr;\n        m_b.context = nullptr;\n        m_c.context = nullptr;\n    }\n\nprotected:\n    typedef struct resolver_fields_stct\n    {\n        aeron_driver_context_t *context;\n        aeron_name_resolver_t resolver;\n        aeron_counters_manager_t counters;\n        aeron_counters_reader_t counters_reader;\n        aeron_system_counters_t system_counters;\n        aeron_distinct_error_log_t error_log;\n        uint8_t counters_buffer[METADATA_LENGTH + VALUES_LENGTH];\n        uint8_t error_log_buffer[ERROR_LOG_LENGTH];\n    }\n    resolver_fields_t;\n\n    void SetUp() override\n    {\n        unresolvable_address = nullptr;\n        aeron_err_clear();\n    }\n\n    void TearDown() override\n    {\n        close(&m_a);\n        close(&m_b);\n        close(&m_c);\n    }\n\n    void initResolver(\n        resolver_fields_t *resolver_fields,\n        const char *resolver_supplier_name,\n        const char *args,\n        int64_t now_ms,\n        const char *driver_resolver_name = nullptr,\n        const char *driver_resolver_interface = nullptr,\n        const char *driver_bootstrap_neighbour = nullptr,\n        const aeron_name_resolver_supplier_func_t name_resolver_bootstrap_supplier_func = nullptr)\n    {\n        aeron_name_resolver_supplier_func_t supplier_func = aeron_name_resolver_supplier_load(resolver_supplier_name);\n        ASSERT_NE(nullptr, supplier_func);\n\n        aeron_driver_context_init(&resolver_fields->context);\n\n        aeron_clock_update_cached_time(resolver_fields->context->cached_clock, now_ms, now_ms + 1000000);\n        aeron_driver_context_set_resolver_name(resolver_fields->context, driver_resolver_name);\n        aeron_driver_context_set_resolver_interface(resolver_fields->context, driver_resolver_interface);\n        aeron_driver_context_set_resolver_bootstrap_neighbor(resolver_fields->context, driver_bootstrap_neighbour);\n        resolver_fields->context->driver_name_resolver_bootstrap_resolver_supplier_func =\n            name_resolver_bootstrap_supplier_func;\n\n        aeron_counters_manager_init(\n            &resolver_fields->counters,\n            &resolver_fields->counters_buffer[0], METADATA_LENGTH,\n            &resolver_fields->counters_buffer[METADATA_LENGTH], VALUES_LENGTH,\n            &m_cached_clock, 1000);\n        aeron_system_counters_init(&resolver_fields->system_counters, &resolver_fields->counters);\n\n        aeron_counters_reader_init(\n            &resolver_fields->counters_reader,\n            resolver_fields->counters.metadata,\n            resolver_fields->counters.metadata_length,\n            resolver_fields->counters.values,\n            resolver_fields->counters.values_length);\n\n        aeron_distinct_error_log_init(\n            &resolver_fields->error_log, resolver_fields->error_log_buffer, ERROR_LOG_LENGTH, aeron_epoch_clock);\n\n        resolver_fields->context->counters_manager = &resolver_fields->counters;\n        resolver_fields->context->system_counters = &resolver_fields->system_counters;\n        resolver_fields->context->error_log = &resolver_fields->error_log;\n\n        ASSERT_EQ(0, supplier_func(&resolver_fields->resolver, args, resolver_fields->context)) << aeron_errmsg();\n    }\n\n    typedef struct counters_clientd_stct\n    {\n        int32_t type_id;\n        int64_t value;\n    }\n    counters_clientd_t;\n\n    typedef struct counter_label_clientd_stct\n    {\n        int32_t type_id;\n        size_t label_length;\n        char label[sizeof(aeron_counter_metadata_descriptor_t::label)];\n    }\n    counter_label_clientd_t;\n\n    typedef struct find_name_counter_clientd_stct\n    {\n        const char *name;\n        int32_t counter_id;\n    }\n    find_name_counter_clientd_t;\n\n    static void findNameCounterCallback(\n        int32_t id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_length,\n        const uint8_t *label,\n        size_t label_length,\n        void *clientd)\n    {\n        auto *find_name_clientd = (find_name_counter_clientd_t *)clientd;\n        size_t query_name_length = strlen(find_name_clientd->name);\n\n        if (AERON_NAME_RESOLVER_CSV_ENTRY_COUNTER_TYPE_ID == type_id)\n        {\n            uint32_t key_name_length;\n            memcpy(&key_name_length, key, sizeof(key_name_length));\n            if (query_name_length == key_name_length &&\n                0 == memcmp(find_name_clientd->name, &key[sizeof(key_name_length)], key_name_length))\n            {\n                find_name_clientd->counter_id = id;\n            }\n        }\n    }\n\n    static int64_t *nameCounterAddrByHostname(resolver_fields_t *resolverFields, const char *name)\n    {\n        find_name_counter_clientd_t clientd = { name, -1 };\n\n        aeron_counters_reader_foreach_metadata(\n            resolverFields->counters_reader.metadata,\n            resolverFields->counters_reader.metadata_length,\n            findNameCounterCallback,\n            (void *)&clientd);\n\n        if (-1 < clientd.counter_id)\n        {\n            return aeron_counters_reader_addr(&resolverFields->counters_reader, clientd.counter_id);\n        }\n\n        return nullptr;\n    }\n\n    static void foreachFilterByTypeId(\n        int64_t value,\n        int32_t id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_length,\n        const char *label,\n        size_t label_length,\n        void *clientd)\n    {\n        auto *counters_clientd = static_cast<NameResolverTest::counters_clientd_t *>(clientd);\n        if (counters_clientd->type_id == type_id)\n        {\n            counters_clientd->value = value;\n        }\n    }\n\n    static void foreachFilterByTypeIdGetLabel(\n        int64_t value,\n        int32_t id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_length,\n        const char *label,\n        size_t label_length,\n        void *clientd)\n    {\n        auto *label_clientd = static_cast<NameResolverTest::counter_label_clientd_t *>(clientd);\n        if (label_clientd->type_id == type_id)\n        {\n            label_clientd->label_length = label_length;\n            strncpy(label_clientd->label, label, label_length);\n        }\n    }\n\n    static int64_t readCounterByTypeId(const aeron_counters_reader_t *counters_reader, int32_t type_id)\n    {\n        counters_clientd_t clientd = { type_id, -1 };\n\n        aeron_counters_reader_foreach_counter(\n            (aeron_counters_reader_t *)counters_reader, foreachFilterByTypeId, &clientd);\n\n        return clientd.value;\n    }\n\n    static counter_label_clientd_t readCounterLabelByTypeId(\n        const aeron_counters_reader_t *counters_reader, int32_t type_id)\n    {\n        counter_label_clientd_t clientd = { type_id, 0, { '\\0' }};\n\n        aeron_counters_reader_foreach_counter(\n            (aeron_counters_reader_t *)counters_reader, foreachFilterByTypeIdGetLabel, &clientd);\n\n        return clientd;\n    }\n\n    static int64_t readNeighborCounter(const resolver_fields_t *resolver)\n    {\n        return readCounterByTypeId(&resolver->counters_reader, AERON_COUNTER_NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID);\n    }\n\n    static int64_t readCacheEntriesCounter(const resolver_fields_t *resolver)\n    {\n        return readCounterByTypeId(\n            &resolver->counters_reader, AERON_COUNTER_NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID);\n    }\n\n    static int64_t readSystemCounter(const resolver_fields_t *resolver, aeron_system_counter_enum_t counter)\n    {\n        return aeron_counter_get_plain(aeron_system_counter_addr(resolver->context->system_counters, counter));\n    }\n\n    static int64_t shortSends(const resolver_fields_t *resolver)\n    {\n        return readSystemCounter(resolver, AERON_SYSTEM_COUNTER_SHORT_SENDS);\n    }\n\n    static void printCounters(std::ostream &output, const resolver_fields_t *resolver, const char *name)\n    {\n        if (nullptr != resolver->context)\n        {\n            output\n                << \" \" << name << \"(\" << shortSends(resolver) << \",\" << readNeighborCounter(resolver)\n                << \",\" << readCacheEntriesCounter(resolver) << \")\";\n        }\n    }\n\n    static void assert_neighbor_counter_label_is(const resolver_fields_t *resolver, const char *expected_label)\n    {\n        auto result = readCounterLabelByTypeId(\n            &resolver->counters_reader, AERON_COUNTER_NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID);\n        EXPECT_EQ(result.label_length, strlen(expected_label));\n        ASSERT_EQ(0, strncmp(\n            expected_label, result.label,\n            result.label_length)) << \"Expected: \" << expected_label << \", actual: \" << result.label;\n    }\n\n    static int ignore_unreslovable_address_lookup_function(\n        aeron_name_resolver_t *resolver,\n        const char *name,\n        const char *uri_param_name,\n        bool is_re_resolution,\n        const char **resolved_name)\n    {\n        if (nullptr != unresolvable_address &&\n            0 == strncmp(unresolvable_address, name, strlen(unresolvable_address) + 1))\n        {\n            *resolved_name = nullptr;\n            return -1;\n        }\n        *resolved_name = name;\n        return 0;\n    }\n\n    static int bootstrap_name_resolver_supplier(\n        aeron_name_resolver_t *resolver, const char *args, aeron_driver_context_t *context)\n    {\n        if (0 == aeron_default_name_resolver_supplier(resolver, args, context))\n        {\n            resolver->lookup_func = ignore_unreslovable_address_lookup_function;\n            resolver->name = \"test-bootstrap\";\n            return 0;\n        }\n\n        return -1;\n    }\n\n    friend std::ostream &operator<<(std::ostream &output, const NameResolverTest &t)\n    {\n        printCounters(output, &t.m_a, \"A\");\n        printCounters(output, &t.m_b, \"B\");\n        printCounters(output, &t.m_c, \"C\");\n        return output;\n    }\n\n    static void close(resolver_fields_t *resolver_fields)\n    {\n        if (nullptr != resolver_fields->context)\n        {\n            resolver_fields->resolver.close_func(&resolver_fields->resolver);\n            aeron_system_counters_close(&resolver_fields->system_counters);\n            aeron_counters_manager_close(&resolver_fields->counters);\n            aeron_distinct_error_log_close(&resolver_fields->error_log);\n            aeron_driver_context_close(resolver_fields->context);\n        }\n    }\n\n    resolver_fields_t m_a = {};\n    resolver_fields_t m_b = {};\n    resolver_fields_t m_c = {};\n    aeron_clock_cache_t m_cached_clock = {};\n    static const char *unresolvable_address;\n};\n\nconst char *NameResolverTest::unresolvable_address;\n\n#define NAME_0 \"server0\"\n#define HOST_0A \"127.0.0.1\"\n#define HOST_0B \"127.0.0.2\"\n\n#define NAME_1 \"server1\"\n#define HOST_1A \"127.0.0.3\"\n#define HOST_1B \"127.0.0.4\"\n\nTEST_F(NameResolverTest, shouldUseStaticLookupTable)\n{\n    in_addr host_0a = {};\n    inet_pton(AF_INET, HOST_0A, &host_0a);\n    in_addr host_0b = {};\n    inet_pton(AF_INET, HOST_0B, &host_0b);\n    in_addr host_1a = {};\n    inet_pton(AF_INET, HOST_1A, &host_1a);\n    in_addr host_1b = {};\n    inet_pton(AF_INET, HOST_1B, &host_1b);\n\n    const char *config_param =\n        NAME_0 \",\" HOST_0A \",\" HOST_0B \"|\"\n        NAME_1 \",\" HOST_1A \",\" HOST_1B \"|\"\n        \"NAME_2\" \",\" HOST_1A \",\" HOST_1B \"|\"\n        \"NAME_3\" \",\" HOST_1A \",\" HOST_1B \"|\"\n        \"NAME_4\" \",\" HOST_1A \",\" HOST_1B \"|\"\n        \"NAME_5\" \",\" HOST_1A \",\" HOST_1B \"|\"\n        \"NAME_6\" \",\" HOST_1A \",\" HOST_1B \"|\"\n        \"NAME_7\" \",\" HOST_1A \",\" HOST_1B \"|\"\n        \"NAME_8\" \",\" HOST_1A \",\" HOST_1B \"|\"\n        \"NAME_9\" \",\" HOST_1A \",\" HOST_1B \"|\";\n\n    initResolver(&m_a, AERON_NAME_RESOLVER_CSV_TABLE, config_param, 0);\n\n    int64_t *name0ToggleAddr = nameCounterAddrByHostname(&m_a, NAME_0);\n    int64_t *name1ToggleAddr = nameCounterAddrByHostname(&m_a, NAME_1);\n\n    struct sockaddr_in address = {};\n    address.sin_family = AF_INET;\n    auto *addr_ptr = (struct sockaddr_storage *)&address;\n\n    ASSERT_EQ(0, m_a.resolver.resolve_func(&m_a.resolver, NAME_0, AERON_UDP_CHANNEL_ENDPOINT_KEY, false, addr_ptr));\n    ASSERT_EQ(host_0a.s_addr, address.sin_addr.s_addr);\n\n    aeron_counter_set_release(name0ToggleAddr, AERON_NAME_RESOLVER_CSV_USE_RE_RESOLUTION_HOST_OP);\n\n    ASSERT_EQ(0, m_a.resolver.resolve_func(&m_a.resolver, NAME_0, AERON_UDP_CHANNEL_ENDPOINT_KEY, true, addr_ptr));\n    ASSERT_EQ(host_0b.s_addr, address.sin_addr.s_addr);\n\n    ASSERT_EQ(0, m_a.resolver.resolve_func(&m_a.resolver, NAME_1, AERON_UDP_CHANNEL_ENDPOINT_KEY, false, addr_ptr));\n    ASSERT_EQ(host_1a.s_addr, address.sin_addr.s_addr);\n\n    aeron_counter_set_release(name1ToggleAddr, AERON_NAME_RESOLVER_CSV_USE_RE_RESOLUTION_HOST_OP);\n\n    ASSERT_EQ(0, m_a.resolver.resolve_func(&m_a.resolver, NAME_1, AERON_UDP_CHANNEL_ENDPOINT_KEY, true, addr_ptr));\n    ASSERT_EQ(host_1b.s_addr, address.sin_addr.s_addr);\n}\n\nTEST_F(NameResolverTest, shouldSeeNeighborFromBootstrapAndHandleIPv4WildCard)\n{\n    int64_t timestamp_ms = INTMAX_C(8932472347945);\n\n    initResolver(&m_a, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"A\", \"0.0.0.0:8050\");\n    initResolver(&m_b, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"B\", \"0.0.0.0:8051\", \"just:wrong,non_existing_host:8050,localhost:8050\");\n\n    timestamp_ms += 2000;\n\n    int64_t deadline_ms = aeron_epoch_clock() + (5 * 1000);\n    while (m_b.resolver.do_work_func(&m_b.resolver, timestamp_ms) <= 0)\n    {\n        ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n        ASSERT_LT(aeron_epoch_clock(), deadline_ms) << \"Timed out waiting for resolver b to do work\" << *this;\n        aeron_micro_sleep(10000);\n        timestamp_ms += 10;\n    }\n\n    deadline_ms = aeron_epoch_clock() + (5 * 1000);\n    while (m_a.resolver.do_work_func(&m_a.resolver, timestamp_ms) <= 0)\n    {\n        ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n        ASSERT_LT(aeron_epoch_clock(), deadline_ms) << \"Timed out waiting for resolver a to do work\" << *this;\n        aeron_micro_sleep(10000);\n        timestamp_ms += 10;\n    }\n\n    struct sockaddr_storage resolved_address_of_b = {};\n    resolved_address_of_b.ss_family = AF_INET;\n    ASSERT_LE(0, m_a.resolver.resolve_func(&m_a.resolver, \"B\", \"endpoint\", false, &resolved_address_of_b));\n    ASSERT_EQ(AF_INET, resolved_address_of_b.ss_family);\n    auto *in_addr_b = (struct sockaddr_in *)&resolved_address_of_b;\n    ASSERT_NE(INADDR_ANY, in_addr_b->sin_addr.s_addr);\n\n    assert_neighbor_counter_label_is(&m_a, \"Resolver neighbors: bound 0.0.0.0:8050\");\n    assert_neighbor_counter_label_is(&m_b, \"Resolver neighbors: bound 0.0.0.0:8051 bootstrap 127.0.0.1:8050\");\n}\n\nTEST_F(NameResolverTest, DISABLED_shouldSeeNeighborFromBootstrapAndHandleIPv6WildCard)\n{\n    int64_t timestamp_ms = INTMAX_C(8932472347945);\n\n    initResolver(&m_a, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"A\", \"[::]:8050\");\n    initResolver(&m_b, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"B\", \"[::]:8051\", \"localhost:8050\");\n\n    timestamp_ms += 2000;\n\n    int64_t deadline_ms = aeron_epoch_clock() + (5 * 1000);\n    while (m_b.resolver.do_work_func(&m_b.resolver, timestamp_ms) <= 0)\n    {\n        ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n        ASSERT_LT(aeron_epoch_clock(), deadline_ms) << \"Timed out waiting for resolver b to do work\" << *this;\n        aeron_micro_sleep(10000);\n        timestamp_ms += 10;\n    }\n\n    deadline_ms = aeron_epoch_clock() + (5 * 1000);\n    while (m_a.resolver.do_work_func(&m_a.resolver, timestamp_ms) <= 0)\n    {\n        ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n        ASSERT_LT(aeron_epoch_clock(), deadline_ms) << \"Timed out waiting for resolver a to do work\" << *this;\n        aeron_micro_sleep(10000);\n        timestamp_ms += 10;\n    }\n\n    struct sockaddr_storage resolved_address_of_b = {};\n    resolved_address_of_b.ss_family = AF_INET6;\n    ASSERT_LE(0, m_a.resolver.resolve_func(&m_a.resolver, \"B\", \"endpoint\", false, &resolved_address_of_b));\n    ASSERT_EQ(AF_INET6, resolved_address_of_b.ss_family);\n    auto *in_addr_b = (struct sockaddr_in6 *)&resolved_address_of_b;\n    ASSERT_NE(0, memcmp(&in6addr_any, &in_addr_b->sin6_addr, sizeof(in6addr_any)));\n}\n\nTEST_F(NameResolverTest, shouldSeeNeighborFromGossip)\n{\n    int64_t timestamp_ms = INTMAX_C(8932472347945);\n    initResolver(&m_a, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"A\", \"0.0.0.0:8050\");\n    initResolver(&m_b, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"B\", \"0.0.0.0:8051\", \"localhost:8050\");\n    initResolver(&m_c, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"C\", \"0.0.0.0:8052\", \"localhost:8051\");\n\n    int64_t deadline_ms = aeron_epoch_clock() + (5 * 1000);\n    while (2 > readNeighborCounter(&m_a) || 2 > readNeighborCounter(&m_b) || 2 > readNeighborCounter(&m_c))\n    {\n        timestamp_ms += 1000;\n        aeron_clock_update_cached_epoch_time(m_a.context->cached_clock, timestamp_ms);\n        aeron_clock_update_cached_epoch_time(m_b.context->cached_clock, timestamp_ms);\n        aeron_clock_update_cached_epoch_time(m_c.context->cached_clock, timestamp_ms);\n\n        int work_done;\n        do\n        {\n            work_done = 0;\n            work_done += m_c.resolver.do_work_func(&m_c.resolver, timestamp_ms);\n            ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n\n            work_done += m_b.resolver.do_work_func(&m_b.resolver, timestamp_ms);\n            ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n\n            work_done += m_a.resolver.do_work_func(&m_a.resolver, timestamp_ms);\n            ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n\n            aeron_micro_sleep(10000);\n            timestamp_ms += 10;\n\n            aeron_clock_update_cached_epoch_time(m_a.context->cached_clock, timestamp_ms);\n            aeron_clock_update_cached_epoch_time(m_b.context->cached_clock, timestamp_ms);\n            aeron_clock_update_cached_epoch_time(m_c.context->cached_clock, timestamp_ms);\n        }\n        while (0 != work_done);\n\n        ASSERT_LT(aeron_epoch_clock(), deadline_ms) << \"Timed out waiting for neighbors\" << *this;\n    }\n\n    struct sockaddr_storage resolved_address = {};\n    resolved_address.ss_family = AF_INET;\n\n    ASSERT_LE(0, m_a.resolver.resolve_func(&m_a.resolver, \"B\", \"endpoint\", false, &resolved_address));\n    ASSERT_LE(0, m_b.resolver.resolve_func(&m_b.resolver, \"B\", \"endpoint\", false, &resolved_address));\n    ASSERT_LE(0, m_c.resolver.resolve_func(&m_c.resolver, \"B\", \"endpoint\", false, &resolved_address));\n\n    ASSERT_LE(0, m_a.resolver.resolve_func(&m_a.resolver, \"C\", \"endpoint\", false, &resolved_address));\n    ASSERT_LE(0, m_b.resolver.resolve_func(&m_b.resolver, \"C\", \"endpoint\", false, &resolved_address));\n    ASSERT_LE(0, m_c.resolver.resolve_func(&m_c.resolver, \"C\", \"endpoint\", false, &resolved_address));\n\n    ASSERT_LE(0, m_c.resolver.resolve_func(&m_c.resolver, \"A\", \"endpoint\", false, &resolved_address));\n    ASSERT_LE(0, m_b.resolver.resolve_func(&m_b.resolver, \"A\", \"endpoint\", false, &resolved_address));\n    ASSERT_LE(0, m_a.resolver.resolve_func(&m_a.resolver, \"A\", \"endpoint\", false, &resolved_address));\n}\n\nTEST_F(NameResolverTest, shouldUseAnotherNeighborIfCurrentBecomesUnavailable)\n{\n    int64_t timestamp_ms = INTMAX_C(8932472347945);\n    initResolver(&m_a, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"A\", \"0.0.0.0:8050\");\n    initResolver(\n        &m_b,\n        AERON_NAME_RESOLVER_DRIVER,\n        \"\",\n        timestamp_ms,\n        \"B\",\n        \"0.0.0.0:8051\",\n        \"localhost:8050,test,localhost:8052,more garbage\",\n        bootstrap_name_resolver_supplier);\n    initResolver(\n        &m_c,\n        AERON_NAME_RESOLVER_DRIVER,\n        \"\",\n        timestamp_ms,\n        \"C\",\n        \"0.0.0.0:8052\",\n        \"localhost:8050,x:y,localhost:8051,here too\",\n        bootstrap_name_resolver_supplier);\n\n    int64_t deadline_ms = aeron_epoch_clock() + (5 * 1000);\n    while (2 > readNeighborCounter(&m_a) || 2 > readNeighborCounter(&m_b) || 2 > readNeighborCounter(&m_c))\n    {\n        timestamp_ms += 1000;\n        aeron_clock_update_cached_epoch_time(m_a.context->cached_clock, timestamp_ms);\n        aeron_clock_update_cached_epoch_time(m_b.context->cached_clock, timestamp_ms);\n        aeron_clock_update_cached_epoch_time(m_c.context->cached_clock, timestamp_ms);\n\n        int work_done;\n        do\n        {\n            work_done = 0;\n            work_done += m_c.resolver.do_work_func(&m_c.resolver, timestamp_ms);\n            ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n\n            work_done += m_b.resolver.do_work_func(&m_b.resolver, timestamp_ms);\n            ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n\n            work_done += m_a.resolver.do_work_func(&m_a.resolver, timestamp_ms);\n            ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n\n            aeron_micro_sleep(10000);\n            timestamp_ms += 10;\n\n            aeron_clock_update_cached_epoch_time(m_a.context->cached_clock, timestamp_ms);\n            aeron_clock_update_cached_epoch_time(m_b.context->cached_clock, timestamp_ms);\n            aeron_clock_update_cached_epoch_time(m_c.context->cached_clock, timestamp_ms);\n        }\n        while (0 != work_done);\n\n        ASSERT_LT(aeron_epoch_clock(), deadline_ms) << \"Timed out waiting for neighbors\" << *this;\n    }\n\n    assert_neighbor_counter_label_is(&m_a, \"Resolver neighbors: bound 0.0.0.0:8050\");\n    assert_neighbor_counter_label_is(&m_b, \"Resolver neighbors: bound 0.0.0.0:8051 bootstrap 127.0.0.1:8050\");\n    assert_neighbor_counter_label_is(&m_c, \"Resolver neighbors: bound 0.0.0.0:8052 bootstrap 127.0.0.1:8050\");\n\n    close(&m_a);\n    m_a.context = nullptr;\n\n    timestamp_ms += AERON_NAME_RESOLVER_DRIVER_TIMEOUT_MS;\n\n    unresolvable_address = \"localhost:8050\"; // ensure that A is now unresolvable\n\n    m_c.resolver.do_work_func(&m_c.resolver, timestamp_ms);\n    ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n\n    m_b.resolver.do_work_func(&m_b.resolver, timestamp_ms);\n    ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n\n    assert_neighbor_counter_label_is(&m_b, \"Resolver neighbors: bound 0.0.0.0:8051 bootstrap 127.0.0.1:8052\");\n    assert_neighbor_counter_label_is(&m_c, \"Resolver neighbors: bound 0.0.0.0:8052 bootstrap 127.0.0.1:8051\");\n}\n\nTEST_F(NameResolverTest, shouldHandleSettingNameOnHeader)\n{\n    uint8_t buffer[1024] = { 0 };\n    const char *hostname = \"this.is.the.hostname\";\n    auto *resolution_header = (aeron_resolution_header_t *)&buffer[0];\n    uint8_t flags = 0;\n    struct sockaddr_storage address = {};\n\n    address.ss_family = AF_INET6;\n    ASSERT_EQ(48, aeron_driver_name_resolver_set_resolution_header_from_sockaddr(\n        resolution_header, sizeof(buffer), flags, &address, hostname, strlen(hostname)));\n    ASSERT_EQ(48, aeron_driver_name_resolver_set_resolution_header_from_sockaddr(\n        resolution_header, 48, flags, &address, hostname, strlen(hostname)));\n    ASSERT_EQ(0, aeron_driver_name_resolver_set_resolution_header_from_sockaddr(\n        resolution_header, 47, flags, &address, hostname, strlen(hostname)));\n\n    address.ss_family = AF_INET;\n    ASSERT_EQ(40, aeron_driver_name_resolver_set_resolution_header_from_sockaddr(\n        resolution_header, sizeof(buffer), flags, &address, hostname, strlen(hostname)));\n    ASSERT_EQ(40, aeron_driver_name_resolver_set_resolution_header_from_sockaddr(\n        resolution_header, 40, flags, &address, hostname, strlen(hostname)));\n    ASSERT_EQ(0, aeron_driver_name_resolver_set_resolution_header_from_sockaddr(\n        resolution_header, 39, flags, &address, hostname, strlen(hostname)));\n\n    address.ss_family = AF_UNIX;\n    ASSERT_EQ(-1, aeron_driver_name_resolver_set_resolution_header_from_sockaddr(\n        resolution_header, sizeof(buffer), flags, &address, hostname, strlen(hostname)));\n}\n\nTEST_F(NameResolverTest, shouldTimeoutNeighbor)\n{\n    aeron_name_resolver_supplier_func_t supplier_func = aeron_name_resolver_supplier_load(AERON_NAME_RESOLVER_DRIVER);\n    ASSERT_NE(nullptr, supplier_func);\n    struct sockaddr_storage address = {};\n    int64_t timestamp_ms = INTMAX_C(8932472347945);\n\n    initResolver(&m_a, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"A\", \"0.0.0.0:8050\");\n\n    initResolver(&m_b, AERON_NAME_RESOLVER_DRIVER, \"\", timestamp_ms, \"B\", \"0.0.0.0:8051\", \"localhost:8050\");\n\n    int64_t deadline_ms = aeron_epoch_clock() + (5 * 1000);\n    while (m_b.resolver.do_work_func(&m_b.resolver, timestamp_ms) <= 0)\n    {\n        ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n        ASSERT_LT(aeron_epoch_clock(), deadline_ms) << \"Timed out waiting for resolver b to do work\" << *this;\n        aeron_micro_sleep(10000);\n        timestamp_ms += 10;\n    }\n\n    deadline_ms = aeron_epoch_clock() + (5 * 1000);\n    while (m_a.resolver.do_work_func(&m_a.resolver, timestamp_ms) <= 0)\n    {\n        ASSERT_EQ(0, aeron_errcode()) << aeron_errmsg();\n        ASSERT_LT(aeron_epoch_clock(), deadline_ms) << \"Timed out waiting for resolver a to do work\" << *this;\n        aeron_micro_sleep(10000);\n        timestamp_ms += 10;\n    }\n\n    // A sees B.\n    ASSERT_LE(0, m_a.resolver.resolve_func(&m_a.resolver, \"B\", \"endpoint\", false, &address));\n\n    ASSERT_EQ(1, readCacheEntriesCounter(&m_a));\n    ASSERT_EQ(1, readNeighborCounter(&m_a));\n\n    timestamp_ms += AERON_NAME_RESOLVER_DRIVER_TIMEOUT_MS;\n    timestamp_ms += 2000;\n\n    // B's not pushed it self resolution recently enough\n    ASSERT_LT(0, m_a.resolver.do_work_func(&m_a.resolver, timestamp_ms));\n\n    ASSERT_EQ(-1, m_a.resolver.resolve_func(&m_a.resolver, \"B\", \"endpoint\", false, &address));\n    ASSERT_EQ(0, readCacheEntriesCounter(&m_a));\n    ASSERT_EQ(0, readNeighborCounter(&m_a));\n}\n\nTEST_F(NameResolverTest, shouldHandleDissection)\n{\n    uint8_t buffer[128] = { 0 };\n    initResolver(&m_a, AERON_NAME_RESOLVER_DRIVER, \"\", 0, \"A\", \"[::1]:8050\");\n\n    auto *log_header = reinterpret_cast<aeron_driver_agent_frame_log_header_t *>(&buffer[0]);\n    log_header->sockaddr_len = sizeof(struct sockaddr_in);\n\n    size_t socket_offset = sizeof(aeron_driver_agent_frame_log_header_t);\n    auto *socket_address = reinterpret_cast<sockaddr_in *>(&buffer[socket_offset]);\n    ASSERT_EQ(1, inet_pton(AF_INET, \"127.0.0.1\", &socket_address->sin_addr));\n    socket_address->sin_port = htons(5555);\n    socket_address->sin_family = AF_INET;\n\n    size_t frame_offset = socket_offset + log_header->sockaddr_len;\n    auto *frame = reinterpret_cast<aeron_frame_header_t *>(&buffer[frame_offset]);\n\n    size_t res_offset = sizeof(aeron_frame_header_t) + frame_offset;\n    auto *res = reinterpret_cast<aeron_resolution_header_ipv6_t *>(&buffer[res_offset]);\n\n    res->resolution_header.res_type = AERON_RES_HEADER_TYPE_NAME_TO_IP6_MD;\n    res->resolution_header.res_flags = AERON_RES_HEADER_SELF_FLAG;\n    res->resolution_header.age_in_ms = 100;\n    res->resolution_header.udp_port = 9872;\n    inet_pton(AF_INET6, \"::1\", &res->addr);\n    res->name_length = 8;\n    memcpy(&buffer[res_offset + sizeof(aeron_resolution_header_ipv6_t)], \"ABCDEFHG\", res->name_length);\n    res_offset += aeron_res_header_entry_length_ipv6(res);\n\n    auto *res2 = reinterpret_cast<aeron_resolution_header_ipv4_t *>(&buffer[res_offset]);\n\n    res2->resolution_header.res_type = AERON_RES_HEADER_TYPE_NAME_TO_IP4_MD;\n    res2->resolution_header.res_flags = 0b00110011;\n    res2->resolution_header.age_in_ms = 333;\n    res2->resolution_header.udp_port = 8080;\n    inet_pton(AF_INET, \"127.0.0.1\", &res2->addr);\n    res2->name_length = 4;\n    memcpy(&buffer[res_offset + sizeof(aeron_resolution_header_ipv4_t)], \"test\", res2->name_length);\n    res_offset += aeron_res_header_entry_length_ipv4(res2);\n\n    frame->type = AERON_HDR_TYPE_RES;\n    frame->frame_length = (int32_t)res_offset;\n    frame->flags = 0b10101011;\n    log_header->message_len = frame->frame_length;\n\n    aeron_env_set(AERON_EVENT_LOG_ENV_VAR, AERON_DRIVER_AGENT_ALL_EVENTS);\n    aeron_driver_agent_context_init(m_a.context);\n\n    testing::internal::CaptureStdout();\n    aeron_driver_agent_log_dissector(AERON_DRIVER_EVENT_FRAME_IN, buffer, res_offset, nullptr);\n\n#if AERON_COMPILER_MSVC\n    GTEST_SKIP();\n#endif\n    std::string output = testing::internal::GetCapturedStdout();\n    EXPECT_EQ(\"[0.000000000] DRIVER: FRAME_IN [104/104]: address=127.0.0.1:5555 type=RES flags=10101011 frameLength=104 [resType=2 flags=10000000 port=9872 ageInMs=100 address=::1 name=ABCDEFHG] [resType=1 flags=00110011 port=8080 ageInMs=333 address=127.0.0.1 name=test]\\n\", output);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_network_publication_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeron_network_publication.h\"\n#include \"media/aeron_send_channel_endpoint.h\"\n#include \"aeron_test_udp_bindings.h\"\n#include \"aeron_driver_sender.h\"\n#include \"aeron_position.h\"\n\nint aeron_driver_ensure_dir_is_recreated(aeron_driver_context_t *context);\n}\n\n#define CAPACITY (32 * 1024)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\ntypedef std::array<std::uint8_t, 4 * CAPACITY> buffer_4x_t;\n\nclass NetworkPublicationTest : public testing::Test\n{\nprotected:\n    void SetUp() override\n    {\n        aeron_test_udp_bindings_load(&m_transport_bindings);\n        aeron_default_name_resolver_supplier(&m_resolver, nullptr, nullptr);\n\n        aeron_driver_context_init(&m_context);\n        aeron_driver_context_set_dir_delete_on_start(m_context, true);\n        aeron_driver_context_set_congestioncontrol_supplier(\n            m_context, aeron_static_window_congestion_control_strategy_supplier);\n        aeron_driver_context_set_udp_channel_transport_bindings(m_context, &m_transport_bindings);\n\n        aeron_counters_manager_init(\n            &m_counters_manager,\n            m_counter_meta_buffer.data(), m_counter_meta_buffer.size(),\n            m_counter_value_buffer.data(), m_counter_value_buffer.size(),\n            &m_cached_clock,\n            1000);\n\n        aeron_system_counters_init(&m_system_counters, &m_counters_manager);\n\n        aeron_distinct_error_log_init(\n            &m_error_log, m_error_log_buffer.data(), m_error_log_buffer.size(), aeron_epoch_clock);\n\n        aeron_driver_sender_init(&m_sender, m_context, &m_system_counters, nullptr);\n\n        m_sender_proxy.sender = &m_sender;\n        m_context->sender_proxy = &m_sender_proxy;\n        m_context->error_log = &m_error_log;\n        m_context->error_buffer = m_error_log_buffer.data();\n        m_context->error_buffer_length = m_error_log_buffer.size();\n\n        aeron_driver_ensure_dir_is_recreated(m_context);\n    }\n\n    void TearDown() override\n    {\n        for (auto publication : m_publications)\n        {\n            aeron_network_publication_close(&m_counters_manager, publication);\n            aeron_network_publication_free(publication);\n        }\n\n        for (auto endpoint : m_endpoints)\n        {\n            aeron_send_channel_endpoint_delete(&m_counters_manager, endpoint);\n        }\n\n        aeron_driver_sender_on_close(&m_sender);\n        aeron_system_counters_close(&m_system_counters);\n        aeron_counters_manager_close(&m_counters_manager);\n        aeron_distinct_error_log_close(&m_error_log);\n        aeron_driver_context_close(m_context);\n    }\n\n    aeron_send_channel_endpoint_t *createEndpoint(\n            const char *uri, aeron_driver_uri_publication_params_t *params, bool is_exclusive)\n    {\n        aeron_udp_channel_t *channel = nullptr;\n        if (0 != aeron_udp_channel_parse(strlen(uri), uri, &m_resolver, &channel, false))\n        {\n            return nullptr;\n        }\n\n        aeron_driver_conductor_t conductor = {};\n        conductor.context = m_context;\n        if (aeron_diver_uri_publication_params(&channel->uri, params, &conductor, is_exclusive) < 0)\n        {\n            return nullptr;\n        }\n        aeron_send_channel_endpoint_t *endpoint = nullptr;\n        if (aeron_send_channel_endpoint_create(&endpoint, channel, params, m_context, &m_counters_manager, 1) < 0)\n        {\n            return nullptr;\n        }\n        m_endpoints.push_back(endpoint);\n\n        return endpoint;\n    }\n\n    aeron_network_publication_t *createPublication(const char *uri)\n    {\n        bool is_exclusive = false;\n        aeron_driver_uri_publication_params_t params = {};\n        params.mtu_length = 1408;\n        params.has_mtu_length = true;\n        params.term_length = 65536;\n        params.has_term_length = true;\n        params.publication_window_length = (int32_t)(params.term_length >> 1);\n        params.has_publication_window_length = true;\n\n        aeron_send_channel_endpoint_t *endpoint = createEndpoint(uri, &params, is_exclusive);\n        if (nullptr == endpoint)\n        {\n            return nullptr;\n        }\n\n        int64_t client_id = 8888888;\n        int64_t registration_id = 1;\n        int32_t stream_id = 10;\n        int32_t session_id = 10;\n        size_t uri_length = strlen(uri);\n\n        aeron_position_t pub_pos_position;\n        aeron_position_t pub_lmt_position;\n        aeron_position_t snd_pos_position;\n        aeron_position_t snd_lmt_position;\n        aeron_atomic_counter_t snd_bpe_counter;\n        aeron_atomic_counter_t snd_naks_received_counter;\n\n        pub_pos_position.counter_id = aeron_counter_publisher_position_allocate(\n            &m_counters_manager,\n            client_id,\n            registration_id,\n            session_id,\n            stream_id,\n            uri_length,\n            uri,\n            is_exclusive);\n        pub_lmt_position.counter_id = aeron_counter_publisher_limit_allocate(\n            &m_counters_manager, client_id, registration_id, session_id, stream_id, uri_length, uri);\n        snd_pos_position.counter_id = aeron_counter_sender_position_allocate(\n            &m_counters_manager, client_id, registration_id, session_id, stream_id, uri_length, uri);\n        snd_lmt_position.counter_id = aeron_counter_sender_limit_allocate(\n            &m_counters_manager, client_id, registration_id, session_id, stream_id, uri_length, uri);\n        snd_bpe_counter.counter_id = aeron_counter_sender_bpe_allocate(\n            &m_counters_manager, client_id, registration_id, session_id, stream_id, uri_length, uri);\n        snd_naks_received_counter.counter_id = aeron_counter_sender_naks_received_allocate(\n            &m_counters_manager, client_id, registration_id, session_id, stream_id, uri_length, uri);\n\n        if (pub_pos_position.counter_id < 0 || pub_lmt_position.counter_id < 0 ||\n            snd_pos_position.counter_id < 0 || snd_lmt_position.counter_id < 0 ||\n            snd_bpe_counter.counter_id < 0 || snd_naks_received_counter.counter_id < 0)\n        {\n            return nullptr;\n        }\n\n        aeron_counters_manager_counter_owner_id(\n            &m_counters_manager, pub_lmt_position.counter_id, 1);\n\n        pub_pos_position.value_addr = aeron_counters_manager_addr(\n            &m_counters_manager, pub_pos_position.counter_id);\n        pub_lmt_position.value_addr = aeron_counters_manager_addr(\n            &m_counters_manager, pub_lmt_position.counter_id);\n        snd_pos_position.value_addr = aeron_counters_manager_addr(\n            &m_counters_manager, snd_pos_position.counter_id);\n        snd_lmt_position.value_addr = aeron_counters_manager_addr(\n            &m_counters_manager, snd_lmt_position.counter_id);\n        snd_bpe_counter.value_addr = aeron_counters_manager_addr(\n            &m_counters_manager, snd_bpe_counter.counter_id);\n        snd_naks_received_counter.value_addr = aeron_counters_manager_addr(\n            &m_counters_manager, snd_naks_received_counter.counter_id);\n\n        if (params.has_position)\n        {\n            const int64_t initial_position = aeron_logbuffer_compute_position(\n                    params.term_id,\n                    (int32_t)params.term_offset,\n                    (size_t)aeron_number_of_trailing_zeroes((int32_t)params.term_length),\n                    params.initial_term_id);\n\n            aeron_counter_set_release(pub_pos_position.value_addr, initial_position);\n            aeron_counter_set_release(pub_lmt_position.value_addr, initial_position);\n            aeron_counter_set_release(snd_pos_position.value_addr, initial_position);\n            aeron_counter_set_release(snd_lmt_position.value_addr, initial_position);\n        }\n\n        aeron_flow_control_strategy_t *flow_control;\n        aeron_unicast_flow_control_strategy_supplier(&flow_control, m_context, nullptr, nullptr, 0, 0, 0, 0, 0);\n\n        aeron_network_publication_t *publication = nullptr;\n        if (aeron_network_publication_create(\n            &publication,\n            endpoint,\n            m_context,\n            registration_id,\n            session_id,\n            stream_id,\n            0,\n            &pub_pos_position,\n            &pub_lmt_position,\n            &snd_pos_position,\n            &snd_lmt_position,\n            &snd_bpe_counter,\n            &snd_naks_received_counter,\n            flow_control,\n            &params,\n            is_exclusive,\n            &m_system_counters) < 0)\n        {\n            flow_control->fini(flow_control);\n            return nullptr;\n        }\n\n        m_publications.push_back(publication);\n\n        return publication;\n    }\n\n    aeron_driver_context_t *m_context = nullptr;\nprivate:\n    aeron_clock_cache_t m_cached_clock = {};\n    aeron_udp_channel_transport_bindings_t m_transport_bindings = {};\n    aeron_counters_manager_t m_counters_manager = {};\n    aeron_system_counters_t m_system_counters = {};\n    aeron_distinct_error_log_t m_error_log = {};\n    AERON_DECL_ALIGNED(buffer_t m_counter_value_buffer, 16) = {};\n    AERON_DECL_ALIGNED(buffer_4x_t m_counter_meta_buffer, 16) = {};\n    AERON_DECL_ALIGNED(buffer_t m_error_log_buffer, 16) = {};\n    std::vector<aeron_send_channel_endpoint_t *> m_endpoints;\n    std::vector<aeron_network_publication_t *> m_publications;\n    aeron_name_resolver_t m_resolver = {};\n    aeron_driver_sender_t m_sender = {};\n    aeron_driver_sender_proxy_t m_sender_proxy = {};\n};\n\nTEST_F(NetworkPublicationTest, shouldSendSetupMessageInitially)\n{\n    aeron_network_publication_t *publication = createPublication(\"aeron:udp?endpoint=localhost:23245\");\n    ASSERT_NE(nullptr, publication) << aeron_errmsg();\n\n    auto *test_bindings_state =\n        static_cast<aeron_test_udp_bindings_state_t *>(publication->endpoint->transport.bindings_clientd);\n\n    aeron_network_publication_send(publication, 0);\n\n    ASSERT_EQ(1, test_bindings_state->setup_count);\n}\n\nTEST_F(NetworkPublicationTest, shouldSendHeartbeatWhileSendingPeriodicSetups)\n{\n    int64_t time_ns = 0;\n\n    aeron_driver_conductor_t conductor = {};\n    aeron_driver_conductor_proxy_t proxy = {};\n    proxy.conductor = &conductor;\n    proxy.threading_mode = AERON_THREADING_MODE_INVOKER;\n\n    aeron_network_publication_t *publication = createPublication(\"aeron:udp?endpoint=localhost:23245\");\n    ASSERT_NE(nullptr, publication) << aeron_errmsg();\n\n    auto *test_bindings_state =\n        static_cast<aeron_test_udp_bindings_state_t *>(publication->endpoint->transport.bindings_clientd);\n\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16) = {};\n    sockaddr_storage sockaddr = {};\n\n    aeron_network_publication_on_status_message(\n        publication, &proxy, data_buffer.data(), sizeof(aeron_status_message_header_t), &sockaddr);\n    aeron_network_publication_send(publication, time_ns);\n\n    ASSERT_TRUE(publication->has_receivers);\n    ASSERT_EQ(1, test_bindings_state->heartbeat_count);\n\n    time_ns += (AERON_NETWORK_PUBLICATION_SETUP_TIMEOUT_NS + 10);\n\n    aeron_network_publication_send(publication, time_ns);\n    ASSERT_EQ(2, test_bindings_state->heartbeat_count);\n\n    time_ns += (AERON_NETWORK_PUBLICATION_SETUP_TIMEOUT_NS + 10);\n\n    aeron_network_publication_trigger_send_setup_frame(\n        publication, data_buffer.data(), sizeof(aeron_status_message_header_t), &sockaddr);\n    aeron_network_publication_send(publication, time_ns);\n    ASSERT_EQ(1, test_bindings_state->setup_count);\n    ASSERT_EQ(3, test_bindings_state->heartbeat_count);\n}\n\nTEST_F(NetworkPublicationTest, shouldReturnStorageSpaceErrorIfNotEnoughStorageSpaceAvailable)\n{\n    m_context->usable_fs_space_func = [](const char* path) -> uint64_t\n    {\n        return 190;\n    };\n    m_context->perform_storage_checks = true;\n\n    aeron_network_publication_t *publication = createPublication(\"aeron:udp?endpoint=localhost:23245|term-length=1m\");\n\n    ASSERT_EQ(nullptr, publication) << aeron_errmsg();\n    EXPECT_EQ(-AERON_ERROR_CODE_STORAGE_SPACE, aeron_errcode());\n    auto expected_error_text =\n        std::string(\"insufficient usable storage for new log of length=3149824 usable=190 in \")\n            .append(m_context->aeron_dir);\n    const auto error_text = std::string(aeron_errmsg());\n    EXPECT_NE(std::string::npos, error_text.find(expected_error_text));\n}\n\nTEST_F(NetworkPublicationTest, shouldWarnIfRemainingStorageSpaceIsLow)\n{\n    m_context->usable_fs_space_func = [](const char *path) -> uint64_t\n    {\n        return 1048576;\n    };\n    m_context->low_file_store_warning_threshold = 4194304ULL;\n    m_context->perform_storage_checks = true;\n\n    aeron_network_publication_t *publication = createPublication(\"aeron:udp?endpoint=localhost:23245|term-length=128k\");\n\n    ASSERT_NE(nullptr, publication) << aeron_errmsg();\n    EXPECT_EQ(0, aeron_errcode());\n    auto errors_list = m_context->error_log->observation_list;\n    EXPECT_NE(nullptr, errors_list);\n    EXPECT_NE(0, errors_list->num_observations);\n    auto last_error = errors_list->observations[errors_list->num_observations - 1];\n    EXPECT_EQ(-AERON_ERROR_CODE_STORAGE_SPACE, last_error.error_code);\n    auto error_text = std::string(last_error.description);\n    EXPECT_EQ(error_text.size(), last_error.description_length);\n    auto expected_warning =\n        std::string(\"WARNING: space is running low: threshold=4194304 usable=1048576 in \")\n            .append(m_context->aeron_dir);\n    EXPECT_NE(std::string::npos, error_text.find(expected_warning));\n}\n\nTEST_F(NetworkPublicationTest, shouldCleanDirtyTermBuffersOneTermBehindTheMinConsumerPosition)\n{\n    const int32_t term_length = 64 * 1024;\n    const int32_t publication_window_length = term_length / 2;\n    const int32_t initial_term_id = 5;\n    const int32_t term_id = 112004;\n    const int32_t term_offset = 384;\n    const int64_t initial_position = (int64_t)term_length * (term_id - initial_term_id) + term_offset;\n    const std::string uri = std::string(\"aeron:udp?endpoint=localhost:23245\")\n            .append(\"|term-length=\").append(std::to_string(term_length))\n            .append(\"|init-term-id=\").append(std::to_string(initial_term_id))\n            .append(\"|term-id=\").append(std::to_string(term_id))\n            .append(\"|term-offset=\").append(std::to_string(term_offset));\n    aeron_network_publication_t *publication = createPublication(uri.c_str());\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->pub_pos_position.value_addr));\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->snd_pos_position.value_addr));\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->snd_lmt_position.value_addr));\n    ASSERT_EQ(initial_position, publication->conductor_fields.clean_position);\n\n    aeron_driver_conductor_t conductor = {};\n    aeron_driver_conductor_proxy_t proxy = {};\n    proxy.conductor = &conductor;\n    proxy.threading_mode = AERON_THREADING_MODE_INVOKER;\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16) = {};\n    sockaddr_storage sockaddr = {};\n    aeron_network_publication_on_status_message(\n            publication, &proxy, data_buffer.data(), sizeof(aeron_status_message_header_t), &sockaddr);\n\n    ASSERT_TRUE(publication->has_receivers);\n\n    EXPECT_EQ(1, aeron_network_publication_update_pub_pos_and_lmt(publication));\n\n    // initial pub-lmt increase\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position, publication->conductor_fields.clean_position);\n\n    // snd-pos increase less than a term\n    aeron_counter_set_release(publication->snd_pos_position.value_addr, initial_position + 4128);\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + 4128 + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position, publication->conductor_fields.clean_position);\n\n    // snd-pos increase exactly one term\n    aeron_counter_set_release(publication->snd_pos_position.value_addr, initial_position + term_length);\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + term_length + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position, publication->conductor_fields.clean_position);\n\n    // snd-pos increase beyond a term\n    aeron_counter_set_release(publication->snd_pos_position.value_addr, initial_position + term_length + 192);\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + term_length + 192 + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position + 192, publication->conductor_fields.clean_position);\n\n    // clean the rest of the first term\n    aeron_counter_set_release(publication->snd_pos_position.value_addr, initial_position + 2 * term_length + 32);\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + 2 * term_length + 32 + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position - term_offset + term_length, publication->conductor_fields.clean_position);\n\n    // snd-pos didn't change => no op\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + 2 * term_length + 32 + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position - term_offset + term_length, publication->conductor_fields.clean_position);\n\n    // clean the next buffer\n    aeron_counter_set_release(publication->snd_pos_position.value_addr, initial_position + 2 * term_length + 8192);\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + 2 * term_length + 8192 + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position + term_length + 8192, publication->conductor_fields.clean_position);\n}\n\nTEST_F(NetworkPublicationTest, publicationLimitShouldNotCrossIntoPreviousTermIfTheEntireTermIsDirty)\n{\n    const int32_t term_length = 64 * 1024;\n    const int32_t publication_window_length = term_length / 2;\n    const int32_t initial_term_id = 5;\n    const int32_t term_id = 7;\n    const int32_t term_offset = 65280;\n    const int64_t initial_position = (int64_t)term_length * (term_id - initial_term_id) + term_offset;\n    const std::string uri = std::string(\"aeron:udp?endpoint=localhost:23245\")\n            .append(\"|term-length=\").append(std::to_string(term_length))\n            .append(\"|init-term-id=\").append(std::to_string(initial_term_id))\n            .append(\"|term-id=\").append(std::to_string(term_id))\n            .append(\"|term-offset=\").append(std::to_string(term_offset));\n    aeron_network_publication_t *publication = createPublication(uri.c_str());\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->pub_pos_position.value_addr));\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->snd_pos_position.value_addr));\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->snd_lmt_position.value_addr));\n    ASSERT_EQ(initial_position, publication->conductor_fields.clean_position);\n\n    aeron_driver_conductor_t conductor = {};\n    aeron_driver_conductor_proxy_t proxy = {};\n    proxy.conductor = &conductor;\n    proxy.threading_mode = AERON_THREADING_MODE_INVOKER;\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16) = {};\n    sockaddr_storage sockaddr = {};\n    aeron_network_publication_on_status_message(\n            publication, &proxy, data_buffer.data(), sizeof(aeron_status_message_header_t), &sockaddr);\n\n    ASSERT_TRUE(publication->has_receivers);\n\n    EXPECT_EQ(1, aeron_network_publication_update_pub_pos_and_lmt(publication));\n\n    // pub-lmt can be in the previous term if clean position offset is not zero\n    aeron_counter_set_release(publication->snd_pos_position.value_addr, initial_position + term_length);\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + term_length + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position, publication->conductor_fields.clean_position);\n\n    // pub-lmt cannot be in the previous term if clean position points to the start of the dirty buffer\n    aeron_counter_set_release(publication->snd_pos_position.value_addr, initial_position + 2 * term_length + 64);\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + term_length + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position - term_offset + term_length, publication->conductor_fields.clean_position);\n\n    // after cleanup the pub-lmt can move again\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + 2 * term_length + 64 + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position + term_length + 64, publication->conductor_fields.clean_position);\n}\n\nTEST_F(NetworkPublicationTest, publicationLimitShouldNotCrossIntoTheDirtyTerm)\n{\n    const int32_t term_length = 64 * 1024;\n    const int32_t publication_window_length = term_length / 2;\n    const int64_t initial_position = 0;\n    const std::string uri = std::string(\"aeron:udp?endpoint=localhost:23245\")\n            .append(\"|term-length=\").append(std::to_string(term_length));\n    aeron_network_publication_t *publication = createPublication(uri.c_str());\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->pub_pos_position.value_addr));\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->snd_pos_position.value_addr));\n    ASSERT_EQ(initial_position, aeron_counter_get_plain(publication->snd_lmt_position.value_addr));\n    ASSERT_EQ(initial_position, publication->conductor_fields.clean_position);\n\n    aeron_driver_conductor_t conductor = {};\n    aeron_driver_conductor_proxy_t proxy = {};\n    proxy.conductor = &conductor;\n    proxy.threading_mode = AERON_THREADING_MODE_INVOKER;\n    AERON_DECL_ALIGNED(buffer_t data_buffer, 16) = {};\n    sockaddr_storage sockaddr = {};\n    aeron_network_publication_on_status_message(\n            publication, &proxy, data_buffer.data(), sizeof(aeron_status_message_header_t), &sockaddr);\n\n    ASSERT_TRUE(publication->has_receivers);\n\n    EXPECT_EQ(1, aeron_network_publication_update_pub_pos_and_lmt(publication));\n\n    // initial pub-lmt\n    aeron_counter_set_release(publication->snd_pos_position.value_addr, initial_position + 256);\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + 256 + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position, publication->conductor_fields.clean_position);\n\n    // new pub-lmt intersects with the clean position\n    aeron_counter_set_release(publication->snd_pos_position.value_addr, initial_position + 2 * term_length + 192 + publication_window_length);\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + 256 + publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position + term_length, publication->conductor_fields.clean_position);\n\n    // after cleanup the pub-lmt can move again\n    aeron_network_publication_update_pub_pos_and_lmt(publication);\n    EXPECT_EQ(initial_position + 2 * term_length + 192 + 2 * publication_window_length, aeron_counter_get_plain(publication->pub_lmt_position.value_addr));\n    EXPECT_EQ(initial_position + term_length + 192 + publication_window_length, publication->conductor_fields.clean_position);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_parse_util_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n#include <climits>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"util/aeron_error.h\"\n#include \"util/aeron_parse_util.h\"\n}\n\nclass ParseUtilTest : public testing::Test\n{\npublic:\n    ParseUtilTest() = default;\n\n    ~ParseUtilTest() override = default;\n};\n\nTEST_F(ParseUtilTest, shouldNotParseInvalidNumber)\n{\n    uint64_t value = 0;\n\n    EXPECT_EQ(aeron_parse_size64(nullptr, &value), -1);\n    EXPECT_EQ(aeron_parse_size64(\"\", &value), -1);\n    EXPECT_EQ(aeron_parse_size64(\"rubbish\", &value), -1);\n    EXPECT_EQ(aeron_parse_size64(\"-8\", &value), -1);\n    EXPECT_EQ(aeron_parse_size64(\"123Z\", &value), -1);\n    EXPECT_EQ(aeron_parse_size64(\"k\", &value), -1);\n}\n\nclass ParseUtilTestValidSize : public testing::TestWithParam<std::tuple<const char *, uint64_t>>\n{\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    ParseUtilTestValidSize,\n    ParseUtilTestValidSize,\n    testing::Values(\n        std::make_tuple(\"0\", 0ULL),\n        std::make_tuple(\"0k\", 0ULL),\n        std::make_tuple(\"0m\", 0ULL),\n        std::make_tuple(\"0g\", 0ULL),\n        std::make_tuple(\"1\", 1ULL),\n        std::make_tuple(\"77777777\", 77777777ULL),\n        std::make_tuple(\"9223372036854775807\", 9223372036854775807ULL),\n        std::make_tuple(\"1K\", 1024ULL),\n        std::make_tuple(\"1M\", 1024 * 1024ULL),\n        std::make_tuple(\"1G\", 1024 * 1024 * 1024ULL),\n        std::make_tuple(\"5023k\", 5023 * 1024ULL),\n        std::make_tuple(\"9m\", 9 * 1024 * 1024ULL),\n        std::make_tuple(\"5g\", 5 * 1024 * 1024 * 1024ULL),\n        std::make_tuple(\"8589934591g\", 8589934591 * 1024 * 1024 * 1024ULL),\n        std::make_tuple(\"8796093022207m\", 8796093022207 * 1024 * 1024ULL),\n        std::make_tuple(\"9007199254740991k\", 9007199254740991 * 1024ULL)));\n\nTEST_P(ParseUtilTestValidSize, shouldParseValidSize)\n{\n    uint64_t value;\n    EXPECT_EQ(aeron_parse_size64(std::get<0>(GetParam()), &value), 0);\n    EXPECT_EQ(value, std::get<1>(GetParam()));\n}\n\nclass ParseUtilTestTooLargeSize : public testing::TestWithParam<const char *>\n{\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    ParseUtilTestTooLargeSize,\n    ParseUtilTestTooLargeSize,\n    testing::Values(\n        \"8589934592g\",\n        \"8796093022208m\",\n        \"9007199254740992k\",\n        \"9223372036854775807g\",\n        \"9223372036854775807m\",\n        \"9223372036854775807k\" ));\n\nTEST_P(ParseUtilTestTooLargeSize, shouldRejectTooLargeValue)\n{\n    uint64_t value = 0;\n    EXPECT_EQ(aeron_parse_size64(GetParam(), &value), -1);\n    EXPECT_EQ(value, 0);\n}\n\nTEST_F(ParseUtilTest, formatSizeShouldRejectValuesLargerThanLLongMaxValue)\n{\n    char buff[64] = {};\n    EXPECT_EQ(-1, aeron_format_size64(ULLONG_MAX, buff, 0));\n    EXPECT_EQ(EINVAL, aeron_errcode());\n    std::string err = std::string(aeron_errmsg());\n    EXPECT_NE(std::string::npos, err.find(\"value is out of range: 18446744073709551615\"));\n}\n\nclass ParseUtilTestFormatSize : public testing::TestWithParam<std::tuple<uint64_t, const char *, int>>\n{\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    ParseUtilTestFormatSize,\n    ParseUtilTestFormatSize,\n    testing::Values(\n        std::make_tuple(0ULL, \"0\", 1),\n        std::make_tuple(1ULL, \"1\", 1),\n        std::make_tuple(77777777ULL, \"77777777\", 8),\n        std::make_tuple(LLONG_MAX, \"9223372036854775807\", 19),\n        std::make_tuple(1024ULL, \"1k\", 2),\n        std::make_tuple(1024 * 1024ULL, \"1m\", 2),\n        std::make_tuple(1024 * 1024 * 1024ULL, \"1g\", 2),\n        std::make_tuple(5023 * 1024ULL,\"5023k\", 5),\n        std::make_tuple(9 * 1024 * 1024ULL, \"9m\", 2),\n        std::make_tuple(5 * 1024 * 1024 * 1024ULL, \"5g\", 2),\n        std::make_tuple(8589934591 * 1024 * 1024 * 1024ULL, \"8589934591g\", 11),\n        std::make_tuple(8796093022207 * 1024 * 1024ULL, \"8796093022207m\", 14),\n        std::make_tuple(9007199254740991 * 1024ULL, \"9007199254740991k\", 17)));\n\nTEST_P(ParseUtilTestFormatSize, shouldFormatSize)\n{\n    char buff[64] = {};\n\n    EXPECT_EQ(aeron_format_size64(std::get<0>(GetParam()), buff, sizeof(buff)), std::get<2>(GetParam()));\n    EXPECT_STREQ(buff, std::get<1>(GetParam()));\n}\n\nTEST_F(ParseUtilTest, shouldNotParseInvalidDuration)\n{\n    uint64_t duration_ns = 0;\n\n    EXPECT_EQ(aeron_parse_duration_ns(nullptr, &duration_ns), -1);\n    EXPECT_EQ(aeron_parse_duration_ns(\"\", &duration_ns), -1);\n    EXPECT_EQ(aeron_parse_duration_ns(\"rubbish\", &duration_ns), -1);\n    EXPECT_EQ(aeron_parse_duration_ns(\"-8\", &duration_ns), -1);\n    EXPECT_EQ(aeron_parse_duration_ns(\"123ps\", &duration_ns), -1);\n    EXPECT_EQ(aeron_parse_duration_ns(\"s\", &duration_ns), -1);\n}\n\nclass ParseUtilTestValidDuration : public testing::TestWithParam<std::tuple<const char *, uint64_t>>\n{\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    ParseUtilTestValidDuration,\n    ParseUtilTestValidDuration,\n    testing::Values(\n        std::make_tuple(\"0\", 0ULL),\n        std::make_tuple(\"0ns\", 0ULL),\n        std::make_tuple(\"0us\", 0ULL),\n        std::make_tuple(\"0ms\", 0ULL),\n        std::make_tuple(\"0s\", 0ULL),\n        std::make_tuple(\"12345\", 12345ULL),\n        std::make_tuple(\"12345NS\", 12345ULL),\n        std::make_tuple(\"456nS\", 456ULL),\n        std::make_tuple(\"789Ns\", 789ULL),\n        std::make_tuple(\"456US\", 456000ULL),\n        std::make_tuple(\"1000uS\", 1000000ULL),\n        std::make_tuple(\"2000Us\", 2000000ULL),\n        std::make_tuple(\"123MS\", 123000000ULL),\n        std::make_tuple(\"1ms\", 1000000ULL),\n        std::make_tuple(\"1Ms\", 1000000ULL),\n        std::make_tuple(\"66mS\", 66000000ULL),\n        std::make_tuple(\"5S\", 5000000000ULL),\n        std::make_tuple(\"345s\", 345000000000ULL),\n        std::make_tuple(\"700ms\", 700000000ULL)));\n\nTEST_P(ParseUtilTestValidDuration, shouldParseValidDuration)\n{\n    uint64_t duration_ns;\n    EXPECT_EQ(aeron_parse_duration_ns(std::get<0>(GetParam()), &duration_ns), 0);\n    EXPECT_EQ(std::get<1>(GetParam()), duration_ns);\n}\n\nclass ParseUtilTestMaxDuration : public testing::TestWithParam<std::tuple<const char *, uint64_t>>\n{\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    ParseUtilTestMaxDuration,\n    ParseUtilTestMaxDuration,\n    testing::Values(\n        std::make_tuple(\"9223372036854775us\", 9223372036854775000ULL),\n        std::make_tuple(\"9223372036854ms\", 9223372036854000000ULL),\n        std::make_tuple(\"9223372036s\", 9223372036000000000ULL),\n        std::make_tuple(\"9223372036854776us\", (uint64_t)LLONG_MAX),\n        std::make_tuple(\"9223372036855ms\", (uint64_t)LLONG_MAX),\n        std::make_tuple(\"9223372037s\", (uint64_t)LLONG_MAX),\n        std::make_tuple(\"70000000000s\", (uint64_t)LLONG_MAX)));\n\nTEST_P(ParseUtilTestMaxDuration, shouldParseMaxQualifiedDuration)\n{\n    uint64_t duration_ns;\n    EXPECT_EQ(aeron_parse_duration_ns(std::get<0>(GetParam()), &duration_ns), 0);\n    EXPECT_EQ(std::get<1>(GetParam()), duration_ns);\n}\n\nTEST_F(ParseUtilTest, formatDurationShouldRejectValuesLargerThanLLongMaxValue)\n{\n    char buff[64] = {};\n    EXPECT_EQ(-1, aeron_format_duration_ns(ULLONG_MAX, buff, 0));\n    EXPECT_EQ(EINVAL, aeron_errcode());\n    std::string err = std::string(aeron_errmsg());\n    EXPECT_NE(std::string::npos, err.find(\"duration is out of range: 18446744073709551615\"));\n}\n\nclass ParseUtilTestFormatDuration : public testing::TestWithParam<std::tuple<uint64_t, const char *, int>>\n{\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    ParseUtilTestFormatDuration,\n    ParseUtilTestFormatDuration,\n    testing::Values(\n        std::make_tuple(0ULL, \"0ns\", 3),\n        std::make_tuple(12345ULL, \"12345ns\", 7),\n        std::make_tuple(456000ULL, \"456us\",5),\n        std::make_tuple(1000000ULL, \"1ms\", 3),\n        std::make_tuple(123000000ULL, \"123ms\", 5),\n        std::make_tuple(1ULL, \"1ns\", 3),\n        std::make_tuple(1000ULL, \"1us\", 3),\n        std::make_tuple(1000000ULL, \"1ms\", 3),\n        std::make_tuple(1000000000ULL, \"1s\", 2),\n        std::make_tuple(66000000ULL, \"66ms\", 4),\n        std::make_tuple(5000000000ULL, \"5s\", 2),\n        std::make_tuple(345000000000ULL, \"345s\", 4),\n        std::make_tuple(700000000ULL, \"700ms\", 5),\n        std::make_tuple(9223372036854775000ULL, \"9223372036854775us\", 18),\n        std::make_tuple(9223372036854000000ULL, \"9223372036854ms\", 15),\n        std::make_tuple(9223372036000000000ULL, \"9223372036s\", 11),\n        std::make_tuple(LLONG_MAX, \"9223372036854775807ns\", 21)));\n\nTEST_P(ParseUtilTestFormatDuration, shouldFormatDuration)\n{\n    char buff[64] = {};\n    EXPECT_EQ(aeron_format_duration_ns(std::get<0>(GetParam()), buff, sizeof(buff)), std::get<2>(GetParam()));\n    EXPECT_STREQ(buff, std::get<1>(GetParam()));\n}\n\nTEST_F(ParseUtilTest, shouldSplitAddress)\n{\n    aeron_parsed_address_t split_address;\n\n    EXPECT_EQ(aeron_address_split(\"localhost:1234\", &split_address), 0);\n    EXPECT_EQ(std::string(split_address.host), \"localhost\");\n    EXPECT_EQ(std::string(split_address.port), \"1234\");\n    EXPECT_EQ(split_address.ip_version_hint, 4);\n\n    EXPECT_EQ(aeron_address_split(\"127.0.0.1:777\", &split_address), 0);\n    EXPECT_EQ(std::string(split_address.host), \"127.0.0.1\");\n    EXPECT_EQ(std::string(split_address.port), \"777\");\n    EXPECT_EQ(split_address.ip_version_hint, 4);\n\n    EXPECT_EQ(aeron_address_split(\"localhost.local\", &split_address), 0);\n    EXPECT_EQ(std::string(split_address.host), \"localhost.local\");\n    EXPECT_EQ(std::string(split_address.port), \"\");\n    EXPECT_EQ(split_address.ip_version_hint, 4);\n\n    EXPECT_EQ(aeron_address_split(\":123\", &split_address), 0);\n    EXPECT_EQ(std::string(split_address.host), \"\");\n    EXPECT_EQ(std::string(split_address.port), \"123\");\n    EXPECT_EQ(split_address.ip_version_hint, 4);\n\n    EXPECT_EQ(aeron_address_split(\"[FF01::FD]:40456\", &split_address), 0);\n    EXPECT_EQ(std::string(split_address.host), \"FF01::FD\");\n    EXPECT_EQ(std::string(split_address.port), \"40456\");\n    EXPECT_EQ(split_address.ip_version_hint, 6);\n}\n\nTEST_F(ParseUtilTest, shouldSplitInterface)\n{\n    aeron_parsed_interface_t split_interface;\n\n    EXPECT_EQ(aeron_interface_split(\"localhost:1234/24\", &split_interface), 0);\n    EXPECT_EQ(std::string(split_interface.host), \"localhost\");\n    EXPECT_EQ(std::string(split_interface.port), \"1234\");\n    EXPECT_EQ(std::string(split_interface.prefix), \"24\");\n    EXPECT_EQ(split_interface.ip_version_hint, 4);\n\n    EXPECT_EQ(aeron_interface_split(\"127.0.0.1:777\", &split_interface), 0);\n    EXPECT_EQ(std::string(split_interface.host), \"127.0.0.1\");\n    EXPECT_EQ(std::string(split_interface.port), \"777\");\n    EXPECT_EQ(std::string(split_interface.prefix), \"\");\n    EXPECT_EQ(split_interface.ip_version_hint, 4);\n\n    EXPECT_EQ(aeron_interface_split(\"localhost.local\", &split_interface), 0);\n    EXPECT_EQ(std::string(split_interface.host), \"localhost.local\");\n    EXPECT_EQ(std::string(split_interface.port), \"\");\n    EXPECT_EQ(std::string(split_interface.prefix), \"\");\n    EXPECT_EQ(split_interface.ip_version_hint, 4);\n\n    EXPECT_EQ(aeron_interface_split(\":123\", &split_interface), 0);\n    EXPECT_EQ(std::string(split_interface.host), \"\");\n    EXPECT_EQ(std::string(split_interface.port), \"123\");\n    EXPECT_EQ(std::string(split_interface.prefix), \"\");\n    EXPECT_EQ(split_interface.ip_version_hint, 4);\n\n    EXPECT_EQ(aeron_interface_split(\"[FF01::FD]:40456/8\", &split_interface), 0);\n    EXPECT_EQ(std::string(split_interface.host), \"FF01::FD\");\n    EXPECT_EQ(std::string(split_interface.port), \"40456\");\n    EXPECT_EQ(std::string(split_interface.prefix), \"8\");\n    EXPECT_EQ(split_interface.ip_version_hint, 6);\n\n    EXPECT_EQ(aeron_interface_split(\"[FF01::FD]:40456\", &split_interface), 0);\n    EXPECT_EQ(std::string(split_interface.host), \"FF01::FD\");\n    EXPECT_EQ(std::string(split_interface.port), \"40456\");\n    EXPECT_EQ(std::string(split_interface.prefix), \"\");\n    EXPECT_EQ(split_interface.ip_version_hint, 6);\n\n    EXPECT_EQ(aeron_interface_split(\"[FF01::FD]/128\", &split_interface), 0);\n    EXPECT_EQ(std::string(split_interface.host), \"FF01::FD\");\n    EXPECT_EQ(std::string(split_interface.port), \"\");\n    EXPECT_EQ(std::string(split_interface.prefix), \"128\");\n    EXPECT_EQ(split_interface.ip_version_hint, 6);\n\n    EXPECT_EQ(aeron_interface_split(\"[FF01::FD%eth0]\", &split_interface), 0);\n    EXPECT_EQ(std::string(split_interface.host), \"FF01::FD\");\n    EXPECT_EQ(std::string(split_interface.port), \"\");\n    EXPECT_EQ(std::string(split_interface.prefix), \"\");\n    EXPECT_EQ(split_interface.ip_version_hint, 6);\n\n    EXPECT_EQ(aeron_interface_split(\"[::1%eth0]:1234\", &split_interface), 0);\n    EXPECT_EQ(std::string(split_interface.host), \"::1\");\n    EXPECT_EQ(std::string(split_interface.port), \"1234\");\n    EXPECT_EQ(std::string(split_interface.prefix), \"\");\n    EXPECT_EQ(split_interface.ip_version_hint, 6);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_port_manager_test.cpp",
    "content": "/*\n * Copyright 2023 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\nextern \"C\"\n{\n#include \"aeron_port_manager.h\"\n#include \"media/aeron_udp_channel.h\"\n#include \"aeron_name_resolver.h\"\n#include \"aeron_socket.h\"\n}\n\nclass WildcardPortManagerTest : public testing::Test\n{\nprotected:\n    void SetUp() override\n    {\n        aeron_default_name_resolver_supplier(&m_resolver, nullptr, nullptr);\n\n        parse_udp_channel(\"aeron:udp?endpoint=localhost:0\", &m_dynamic_endpoint_channel);\n        parse_udp_channel(\"aeron:udp?control=localhost:0\", &m_dynamic_control_channel);\n    }\n\n    void TearDown() override\n    {\n        aeron_wildcard_port_manager_delete(&m_port_manager);\n\n        aeron_udp_channel_delete(m_dynamic_endpoint_channel);\n        aeron_udp_channel_delete(m_dynamic_control_channel);\n\n        m_resolver.close_func(&m_resolver);\n    }\n\n    int get_managed_port(aeron_udp_channel_t *channel)\n    {\n        return aeron_wildcard_port_manager_get_managed_port(&m_port_manager, &m_bind_addr_out, channel, &m_bind_addr);\n    }\n\n    void set_bind_addr(uint32_t addr, uint16_t port)\n    {\n        auto *ptr = (struct sockaddr_in *)&m_bind_addr;\n        ptr->sin_family = AF_INET;\n        ptr->sin_port = htons(port);\n        ptr->sin_addr.s_addr = htonl(addr);\n    }\n\n    void assert_managed_port_eq(uint16_t expected_port, aeron_udp_channel_t *channel, uint16_t bind_port)\n    {\n        set_bind_addr(INADDR_LOOPBACK, bind_port);\n\n        ASSERT_EQ(0, get_managed_port(channel)) << aeron_errmsg();\n\n        auto *ptr = (struct sockaddr_in *)&m_bind_addr_out;\n        ASSERT_EQ(AF_INET, ptr->sin_family);\n        ASSERT_EQ(expected_port, ntohs(ptr->sin_port));\n        ASSERT_EQ(INADDR_LOOPBACK, ntohl(ptr->sin_addr.s_addr));\n    }\n\n    void assert_managed_port_err(aeron_udp_channel_t *channel, uint16_t bind_port)\n    {\n        set_bind_addr(INADDR_LOOPBACK, bind_port);\n\n        ASSERT_EQ(-1, get_managed_port(channel));\n    }\n\n    void free_managed_port(uint16_t port)\n    {\n        set_bind_addr(INADDR_LOOPBACK, port);\n\n        aeron_wildcard_port_manager_free_managed_port(&m_port_manager, &m_bind_addr);\n    }\n\n    aeron_wildcard_port_manager_t m_port_manager = {};\n    aeron_name_resolver_t m_resolver = {};\n    aeron_udp_channel_t *m_dynamic_endpoint_channel = nullptr;\n    aeron_udp_channel_t *m_dynamic_control_channel = nullptr;\n    struct sockaddr_storage m_bind_addr = {};\n    struct sockaddr_storage m_bind_addr_out = {};\n\nprivate:\n    void parse_udp_channel(const char *channel, aeron_udp_channel_t **out)\n    {\n        int result = aeron_udp_channel_parse(strlen(channel), channel, &m_resolver, out, false);\n        ASSERT_GE(result, 0) << \" '\" << channel << \"' \" << aeron_errmsg();\n    }\n};\n\nTEST_F(WildcardPortManagerTest, ShouldAllocateConsecutivePortsInRange)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, false));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    assert_managed_port_eq(20000, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_eq(20001, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_eq(20002, m_dynamic_endpoint_channel, 0);\n}\n\nTEST_F(WildcardPortManagerTest, ShouldPassThrough0WithNullRanges)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, false));\n\n    assert_managed_port_eq(0, m_dynamic_endpoint_channel, 0);\n}\n\nTEST_F(WildcardPortManagerTest, ShouldPassThrough0WithSenderPubWithoutControlAddress)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, true));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    assert_managed_port_eq(0, m_dynamic_endpoint_channel, 0);\n}\n\nTEST_F(WildcardPortManagerTest, ShouldPassThroughWithExplicitBindAddressOutsideRange)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, true));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    assert_managed_port_eq(1000, m_dynamic_endpoint_channel, 1000);\n}\n\nTEST_F(WildcardPortManagerTest, ShouldPassThroughWithExplicitBindAddressInsideRange)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, true));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    assert_managed_port_eq(20003, m_dynamic_endpoint_channel, 20003);\n}\n\nTEST_F(WildcardPortManagerTest, ShouldAllocateForPubWithExplicitControlAddress)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, true));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    assert_managed_port_eq(20000, m_dynamic_control_channel, 0);\n}\n\nTEST_F(WildcardPortManagerTest, ShouldReturnErrorOnExhaustion)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, false));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    assert_managed_port_eq(20000, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_eq(20001, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_eq(20002, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_eq(20003, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_err(m_dynamic_endpoint_channel, 0);\n    ASSERT_THAT(aeron_errmsg(), testing::HasSubstr(\"no available ports in range 20000 20003\"));\n}\n\nTEST_F(WildcardPortManagerTest, ShouldAllocateOnCyclingThroughRange)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, false));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    assert_managed_port_eq(20000, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_eq(20001, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_eq(20002, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_eq(20003, m_dynamic_endpoint_channel, 0);\n    free_managed_port(20000);\n    assert_managed_port_eq(20000, m_dynamic_control_channel, 0);\n}\n\nTEST_F(WildcardPortManagerTest, ShouldAllocateSkippingInUsePort)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, false));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    assert_managed_port_eq(20000, m_dynamic_endpoint_channel, 0);\n    assert_managed_port_eq(20001, m_dynamic_endpoint_channel, 20001);\n    assert_managed_port_eq(20002, m_dynamic_control_channel, 0);\n}\n\nTEST_F(WildcardPortManagerTest, ShouldAllocateSkippingInUseOnCycle)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, false));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    assert_managed_port_eq(20000, m_dynamic_endpoint_channel, 20000);\n    assert_managed_port_eq(20001, m_dynamic_endpoint_channel, 20001);\n    assert_managed_port_eq(20002, m_dynamic_endpoint_channel, 20002);\n    free_managed_port(20002);\n    assert_managed_port_eq(20003, m_dynamic_endpoint_channel, 20003);\n    assert_managed_port_eq(20002, m_dynamic_control_channel, 0);\n}\n\nTEST_F(WildcardPortManagerTest, ShouldSupportIPv6)\n{\n    ASSERT_EQ(0, aeron_wildcard_port_manager_init(&m_port_manager, false));\n    aeron_wildcard_port_manager_set_range(&m_port_manager, 20000, 20003);\n\n    auto *in = (struct sockaddr_in6 *)&m_bind_addr;\n    in->sin6_family = AF_INET6;\n    in->sin6_port = htons(0);\n    in->sin6_addr = IN6ADDR_LOOPBACK_INIT;\n\n    ASSERT_EQ(0, get_managed_port(m_dynamic_endpoint_channel)) << aeron_errmsg();\n\n    auto *out = (struct sockaddr_in6 *)&m_bind_addr_out;\n    ASSERT_EQ(AF_INET6, out->sin6_family);\n    ASSERT_EQ(20000, ntohs(out->sin6_port));\n    ASSERT_EQ(0, memcmp(&out->sin6_addr, &in6addr_loopback, sizeof(in6addr_loopback)));\n}\n\nTEST(PortManagerTest, ShouldParsePortRange)\n{\n    uint16_t low_port;\n    uint16_t high_port;\n\n    EXPECT_EQ(0, aeron_parse_port_range(\"1 2\", &low_port, &high_port));\n    EXPECT_EQ(1, low_port);\n    EXPECT_EQ(2, high_port);\n\n    EXPECT_EQ(0, aeron_parse_port_range(\"10000 65535\", &low_port, &high_port));\n    EXPECT_EQ(10000, low_port);\n    EXPECT_EQ(65535, high_port);\n\n    EXPECT_EQ(-1, aeron_parse_port_range(\"1\", &low_port, &high_port));\n    EXPECT_EQ(-1, aeron_parse_port_range(\"1 \", &low_port, &high_port));\n    EXPECT_EQ(-1, aeron_parse_port_range(\"1 a\", &low_port, &high_port));\n    EXPECT_EQ(-1, aeron_parse_port_range(\"a\", &low_port, &high_port));\n    EXPECT_EQ(-1, aeron_parse_port_range(\"2 1\", &low_port, &high_port));\n    EXPECT_EQ(-1, aeron_parse_port_range(\"-2 -1\", &low_port, &high_port));\n    EXPECT_EQ(-1, aeron_parse_port_range(\"1 65536\", &low_port, &high_port));\n    EXPECT_EQ(-1, aeron_parse_port_range(\"65536 65537\", &low_port, &high_port));\n    EXPECT_EQ(-1, aeron_parse_port_range(\"9223372036854775808 2000\", &low_port, &high_port));\n    EXPECT_EQ(-1, aeron_parse_port_range(\"2000 9223372036854775808\", &low_port, &high_port));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_position_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeron_network_publication.h\"\n#include \"media/aeron_send_channel_endpoint.h\"\n#include \"aeron_test_udp_bindings.h\"\n#include \"aeron_driver_sender.h\"\n#include \"aeron_position.h\"\n\nint aeron_driver_ensure_dir_is_recreated(aeron_driver_context_t *context);\n}\n\n#define CAPACITY (32 * 1024)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\ntypedef std::array<std::uint8_t, 4 * CAPACITY> buffer_4x_t;\n\nclass PositionTest : public testing::Test\n{\nprotected:\n    void SetUp() override\n    {\n        aeron_counters_manager_init(\n            &m_counters_manager,\n            m_counter_meta_buffer.data(), m_counter_meta_buffer.size(),\n            m_counter_value_buffer.data(), m_counter_value_buffer.size(),\n            &m_cached_clock,\n            1000);\n    }\n\n    void TearDown() override\n    {\n        aeron_counters_manager_close(&m_counters_manager);\n    }\n\n    typedef struct counters_clientd_stct\n    {\n        int32_t id;\n        const char *expected_channel;\n        int32_t expended_channel_length;\n    }\n        counters_clientd_t;\n\n    static void verify_channel_uri_channel_endpoint(\n        int32_t id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_length,\n        const uint8_t *label,\n        size_t label_length,\n        void *clientd)\n    {\n        auto *counters_clientd = static_cast<PositionTest::counters_clientd_t *>(clientd);\n        if (counters_clientd->id == id)\n        {\n            auto *layout = (aeron_channel_endpoint_status_key_layout_t *)key;\n            ASSERT_EQ(counters_clientd->expended_channel_length, layout->channel_length);\n            ASSERT_EQ(\n                0,\n                strncmp(counters_clientd->expected_channel, layout->channel, layout->channel_length));\n        }\n    }\n\n    static void verify_channel_uri_stream_counter(\n        int32_t id,\n        int32_t type_id,\n        const uint8_t *key,\n        size_t key_length,\n        const uint8_t *label,\n        size_t label_length,\n        void *clientd)\n    {\n        auto *counters_clientd = static_cast<PositionTest::counters_clientd_t *>(clientd);\n        if (counters_clientd->id == id)\n        {\n            auto *layout = (aeron_stream_position_counter_key_layout_t *)key;\n            ASSERT_EQ(counters_clientd->expended_channel_length, layout->channel_length);\n            ASSERT_EQ(\n                0,\n                strncmp(counters_clientd->expected_channel, layout->channel, layout->channel_length));\n        }\n    }\n\nprotected:\n    aeron_counters_manager_t m_counters_manager = {};\nprivate:\n    aeron_clock_cache_t m_cached_clock = {};\n    AERON_DECL_ALIGNED(buffer_t m_counter_value_buffer, 16) = {};\n    AERON_DECL_ALIGNED(buffer_4x_t m_counter_meta_buffer, 16) = {};\n};\n\nTEST_F(PositionTest, channelEndpointStatusShouldTruncateChannelUriIfTooLong)\n{\n    const std::string uri_prefix =\n        \"aeron:udp?endpoint=localhost:23245|mtu=1408|term-length=65536|term-offset=0|alias=very-long-alias-that-will-\";\n    const std::string full_uri = uri_prefix + \"-be-truncated|sparse=true\";\n\n    const int32_t id = aeron_channel_endpoint_status_allocate(\n        &m_counters_manager,\n        \"test-channel-endpoint\",\n        AERON_COUNTER_SEND_CHANNEL_STATUS_TYPE_ID,\n        42,\n        full_uri.length(),\n        full_uri.c_str());\n    ASSERT_NE(-1, id) << aeron_errmsg();\n\n    counters_clientd_t clientd;\n    clientd.id = id;\n    clientd.expended_channel_length = (int32_t)uri_prefix.length();\n    clientd.expected_channel = uri_prefix.c_str();\n\n    aeron_counters_reader_foreach_metadata(\n        m_counters_manager.metadata, m_counters_manager.metadata_length, verify_channel_uri_channel_endpoint, &clientd);\n}\n\nTEST_F(PositionTest, channelEndpointStatusShouldEncodeChannelUriDirectly)\n{\n    const std::string channel = \"aeron:udp?endpoint=localhost:23245|mtu=1408|term-length=65536|term-offset=0\";\n\n    const int32_t id = aeron_channel_endpoint_status_allocate(\n        &m_counters_manager,\n        \"test-receive-endpoint\",\n        AERON_COUNTER_RECEIVE_CHANNEL_STATUS_TYPE_ID,\n        21,\n        channel.length(),\n        channel.c_str());\n    ASSERT_NE(-1, id) << aeron_errmsg();\n\n    counters_clientd_t clientd;\n    clientd.id = id;\n    clientd.expended_channel_length = (int32_t)channel.length();\n    clientd.expected_channel = channel.c_str();\n\n    aeron_counters_reader_foreach_metadata(\n        m_counters_manager.metadata, m_counters_manager.metadata_length, verify_channel_uri_channel_endpoint, &clientd);\n}\n\n\nTEST_F(PositionTest, streamCounterShouldTruncateChannelUriIfTooLong)\n{\n    const std::string uri_prefix = \"aeron:udp?endpoint=localhost:23245|mtu=1408|term-length=65536|term-offset=0|alias=that-will-\";\n    const std::string full_uri = uri_prefix + \"be-truncated|sparse=true\";\n\n    const int32_t id = aeron_stream_counter_allocate(\n        &m_counters_manager,\n        AERON_COUNTER_SENDER_BPE_NAME,\n        AERON_COUNTER_SENDER_BPE_TYPE_ID,\n        -143,\n        42,\n        5,\n        -189,\n        full_uri.length(),\n        full_uri.c_str(),\n        \"\");\n    ASSERT_NE(-1, id) << aeron_errmsg();\n\n    counters_clientd_t clientd;\n    clientd.id = id;\n    clientd.expended_channel_length = (int32_t)uri_prefix.length();\n    clientd.expected_channel = uri_prefix.c_str();\n\n    aeron_counters_reader_foreach_metadata(\n        m_counters_manager.metadata, m_counters_manager.metadata_length, verify_channel_uri_stream_counter, &clientd);\n}\n\nTEST_F(PositionTest, streamCounterShouldEncodeChannelUriDirectly)\n{\n    const std::string channel = \"aeron:udp?endpoint=localhost:23245|mtu=1408|term-length=65536|term-offset=0\";\n\n    const int32_t id = aeron_stream_counter_allocate(\n        &m_counters_manager,\n        AERON_COUNTER_PUBLISHER_LIMIT_NAME,\n        AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID,\n        821,\n        42,\n        5,\n        -189,\n        channel.length(),\n        channel.c_str(),\n        \"other\");\n    ASSERT_NE(-1, id) << aeron_errmsg();\n\n    counters_clientd_t clientd;\n    clientd.id = id;\n    clientd.expended_channel_length = (int32_t)channel.length();\n    clientd.expected_channel = channel.c_str();\n\n    aeron_counters_reader_foreach_metadata(\n        m_counters_manager.metadata, m_counters_manager.metadata_length, verify_channel_uri_stream_counter, &clientd);\n\n    int64_t owner_id;\n    aeron_counters_reader_t countersReader = {};\n    aeron_counters_reader_init(\n        &countersReader,\n        m_counters_manager.metadata,\n        m_counters_manager.metadata_length,\n        m_counters_manager.values,\n        m_counters_manager.values_length);\n    aeron_counters_reader_counter_owner_id(&countersReader, id, &owner_id);\n    EXPECT_EQ(821, owner_id);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_properties_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"util/aeron_properties_util.h\"\n#include \"util/aeron_error.h\"\n#include \"util/aeron_http_util.h\"\n#include \"aeronmd.h\"\n}\n\nclass DriverConfigurationTest : public testing::Test\n{\npublic:\n    DriverConfigurationTest()\n    {\n        aeron_properties_parse_init(&m_state);\n    }\n\n    static int propertyHandler(void *clientd, const char *name, const char *value)\n    {\n        auto test = reinterpret_cast<DriverConfigurationTest *>(clientd);\n\n        test->m_name = std::string(name);\n        test->m_value = std::string(value);\n        return 1;\n    }\n\n    int parseLine(const char *line)\n    {\n        std::string lineStr(line);\n        m_name = \"\";\n        m_value = \"\";\n\n        return aeron_properties_parse_line(&m_state, lineStr.c_str(), lineStr.length(), propertyHandler, this);\n    }\n\n\nprotected:\n    aeron_properties_parser_state_t m_state = {};\n    std::string m_name;\n    std::string m_value;\n};\n\nTEST_F(DriverConfigurationTest, shouldNotParseMalformedPropertyLine)\n{\n    EXPECT_EQ(parseLine(\" airon\"), -1);\n    EXPECT_EQ(parseLine(\"=\"), -1);\n    EXPECT_EQ(parseLine(\"=val\"), -1);\n}\n\nTEST_F(DriverConfigurationTest, shouldNotParseTooLongALine)\n{\n    EXPECT_EQ(aeron_properties_parse_line(&m_state, \"line\", sizeof(m_state.property_str), propertyHandler, this), -1);\n}\n\nTEST_F(DriverConfigurationTest, shouldIgnoreComments)\n{\n    EXPECT_EQ(parseLine(\" #\"), 0);\n    EXPECT_EQ(parseLine(\"# comment\"), 0);\n    EXPECT_EQ(parseLine(\"! bang\"), 0);\n    EXPECT_EQ(parseLine(\"        ! bang\"), 0);\n}\n\nTEST_F(DriverConfigurationTest, shouldIgnoreBlankLines)\n{\n    EXPECT_EQ(parseLine(\"\"), 0);\n    EXPECT_EQ(parseLine(\" \"), 0);\n}\n\nTEST_F(DriverConfigurationTest, shouldParseSimpleLine)\n{\n    EXPECT_EQ(parseLine(\"propertyName=propertyValue\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n\n    EXPECT_EQ(parseLine(\"propertyName:propertyValue\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n}\n\nTEST_F(DriverConfigurationTest, shouldParseSimpleLineWithNameWhiteSpace)\n{\n    EXPECT_EQ(parseLine(\"   propertyName=propertyValue\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n\n    EXPECT_EQ(parseLine(\"propertyName :propertyValue\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n\n    EXPECT_EQ(parseLine(\"\\tpropertyName  =propertyValue\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n}\n\nTEST_F(DriverConfigurationTest, shouldParseSimpleLineWithLeadingValueWhiteSpace)\n{\n    EXPECT_EQ(parseLine(\"propertyName=  propertyValue\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n\n    EXPECT_EQ(parseLine(\"propertyName:\\tpropertyValue\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n}\n\nTEST_F(DriverConfigurationTest, shouldParseSimpleLineWithNoValue)\n{\n    EXPECT_EQ(parseLine(\"propertyName=\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"\");\n\n    EXPECT_EQ(parseLine(\"   propertyName=\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"\");\n\n    EXPECT_EQ(parseLine(\"propertyName :\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"\");\n}\n\nTEST_F(DriverConfigurationTest, shouldParseSimpleContinuation)\n{\n    EXPECT_EQ(parseLine(\"propertyName=\\\\\"), 0);\n    EXPECT_EQ(parseLine(\"propertyValue\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n}\n\nTEST_F(DriverConfigurationTest, shouldParseSimpleContinuationWithWhitespace)\n{\n    EXPECT_EQ(parseLine(\"propertyName= property\\\\\"), 0);\n    EXPECT_EQ(parseLine(\"   Value\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n}\n\nTEST_F(DriverConfigurationTest, shouldParseContinuationWithComment)\n{\n    EXPECT_EQ(parseLine(\"propertyName= property\\\\\"), 0);\n    EXPECT_EQ(parseLine(\"#\"), 0);\n    EXPECT_EQ(parseLine(\"   Value\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n}\n\nTEST_F(DriverConfigurationTest, shouldParseContinuationWithBlankLine)\n{\n    EXPECT_EQ(parseLine(\"propertyName= property\\\\\"), 0);\n    EXPECT_EQ(parseLine(\"\\\\\"), 0);\n    EXPECT_EQ(parseLine(\"   Value\"), 1);\n    EXPECT_EQ(m_name, \"propertyName\");\n    EXPECT_EQ(m_value, \"propertyValue\");\n}\n\nTEST_F(DriverConfigurationTest, DISABLED_shouldHttpRetrieve)\n{\n    aeron_http_response_t *response = nullptr;\n    int result = aeron_http_retrieve(&response, \"http://localhost:8000/aeron-throughput.properties\", -1L);\n\n    if (-1 == result)\n    {\n        std::cout << aeron_errmsg() << std::endl;\n    }\n    else\n    {\n        std::cout << std::to_string(response->status_code) << std::endl;\n        std::cout << std::to_string(response->content_length) << std::endl;\n        std::cout << std::to_string(response->length) << std::endl;\n        std::cout << std::to_string(response->headers_offset) << std::endl;\n        std::cout << std::to_string(response->body_offset) << std::endl;\n        std::cout << (response->buffer + response->body_offset) << std::endl;\n    }\n}\n\nTEST_F(DriverConfigurationTest, DISABLED_shouldHttpRetrieveProperties)\n{\n    int result = aeron_properties_http_load(\"http://localhost:8000/aeron-throughput.properties\");\n\n    if (-1 == result)\n    {\n        std::cout << aeron_errmsg() << std::endl;\n    }\n\n    std::cout << getenv(\"AERON_MTU_LENGTH\") << std::endl;\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_publication_image_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <vector>\n#include \"aeron_receiver_test.h\"\n\nextern \"C\"\n{\n#include \"aeron_publication_image.h\"\n#include \"aeron_data_packet_dispatcher.h\"\n#include \"aeron_driver_receiver.h\"\n}\n\n#define CAPACITY (32 * 1024)\n#define TERM_BUFFER_SIZE (64 * 1024)\n#define MTU (4096)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\ntypedef std::array<std::uint8_t, 4 * CAPACITY> buffer_4x_t;\n\nstatic bool always_measure_rtt(void *state, int64_t now_ns)\n{\n    return true;\n}\n\nclass PublicationImageTest : public ReceiverTestBase, public testing::Test\n{\npublic:\n    void SetUp() final\n    {\n        DoSetUp();\n    }\n\n    void TearDown() final\n    {\n        DoTearDown();\n    }\n};\n\n\nclass ZeroDelayFeedbackGeneratorTest : public ReceiverTestBase, public testing::TestWithParam<bool>\n{\npublic:\n    void SetUp() final\n    {\n        DoSetUp();\n    }\n\n    void TearDown() final\n    {\n        DoTearDown();\n    }\n};\n\nINSTANTIATE_TEST_SUITE_P(\n    ZeroDelayFeedbackGeneratorTests,\n    ZeroDelayFeedbackGeneratorTest,\n    testing::Values(true, false)\n);\n\nTEST_P(ZeroDelayFeedbackGeneratorTest, shouldConfiguredZeroDelayFeedbackGeneratorIfSubscriptionIsUnreliable)\n{\n    bool treat_as_multicast = GetParam();\n\n    const char *uri = \"aeron:udp?endpoint=localhost:9090|nak-delay=5ms\";\n    aeron_receive_channel_endpoint_t *endpoint = createEndpoint(uri);\n    int32_t stream_id = 777;\n    int32_t session_id = 42;\n    int64_t registration_id = 0;\n\n    aeron_udp_channel_t *channel;\n    aeron_receive_destination_t *dest;\n\n    ASSERT_EQ(0, aeron_udp_channel_parse(strlen(uri), uri, &m_resolver, &channel, false));\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest,\n        channel,\n        channel,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n\n    aeron_publication_image_t *image = createImage(endpoint, dest, stream_id, session_id, 111, false, treat_as_multicast);\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n    auto delay_generator_state = image->loss_detector.feedback_delay_state;\n    EXPECT_EQ(0, delay_generator_state->static_delay.delay_ns);\n    EXPECT_EQ(0, delay_generator_state->static_delay.retry_ns);\n    EXPECT_NE(&m_context->multicast_delay_feedback_generator, delay_generator_state);\n    EXPECT_NE(&m_context->unicast_delay_feedback_generator, delay_generator_state);\n\n    aeron_publication_image_remove_destination(image, endpoint->conductor_fields.udp_channel);\n    endpoint->transport_bindings->poller_remove_func(&m_receiver.poller, &dest->transport);\n    endpoint->transport_bindings->close_func(&dest->transport);\n    aeron_receive_destination_delete(dest, &m_counters_manager);\n}\n\nTEST_F(PublicationImageTest, shouldAddAndRemoveDestination)\n{\n    const char *uri_1 = \"aeron:udp?endpoint=localhost:9090\";\n    const char *uri_2 = \"aeron:udp?endpoint=localhost:9091\";\n    const char *uri_3 = \"aeron:udp?endpoint=localhost:9093\";\n    aeron_receive_channel_endpoint_t *endpoint = createMdsEndpoint();\n    int64_t registration_id = 0;\n    int32_t stream_id = 1001;\n    int32_t session_id = 1000001;\n    aeron_receive_destination_t *destination = nullptr;\n\n    aeron_udp_channel_t *channel_1 = createChannel(uri_1);\n    aeron_receive_destination_t *dest_1;\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_1,\n        channel_1,\n        channel_1,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_add_destination(endpoint, dest_1));\n\n    aeron_publication_image_t *image = createImage(endpoint, dest_1, stream_id, session_id);\n\n    aeron_udp_channel_t *channel_2 = createChannel(uri_2);\n    aeron_receive_destination_t *dest_2;\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_2,\n        channel_2,\n        channel_2,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(2, aeron_receive_channel_endpoint_add_destination(endpoint, dest_2));\n\n    ASSERT_EQ(2, aeron_publication_image_add_destination(image, dest_2));\n\n    aeron_udp_channel_t *remove_channel_1 = createChannel(uri_1, &m_channels_for_tear_down);\n\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_remove_destination(endpoint, remove_channel_1, &destination));\n    endpoint->transport_bindings->poller_remove_func(&m_receiver.poller, &dest_1->transport);\n    endpoint->transport_bindings->close_func(&dest_1->transport);\n\n    ASSERT_EQ(1u, endpoint->destinations.length);\n    ASSERT_EQ(1, aeron_publication_image_remove_destination(image, remove_channel_1));\n    ASSERT_EQ(1u, image->connections.length);\n    ASSERT_EQ(dest_1, destination);\n    aeron_receive_destination_delete(dest_1, &m_counters_manager);\n\n    aeron_udp_channel_t *channel_not_added = createChannel(uri_3, &m_channels_for_tear_down);\n\n    destination = nullptr;\n    ASSERT_EQ(0, aeron_receive_channel_endpoint_remove_destination(endpoint, channel_not_added, &destination));\n    ASSERT_EQ(1u, endpoint->destinations.length);\n    ASSERT_EQ(0, aeron_publication_image_remove_destination(image, channel_not_added));\n    ASSERT_EQ(1u, image->connections.length);\n    ASSERT_EQ((aeron_receive_destination_t *) nullptr, destination);\n\n    aeron_udp_channel_t *remove_channel_2 = createChannel(uri_2, &m_channels_for_tear_down);\n\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_remove_destination(endpoint, remove_channel_2, &destination));\n    endpoint->transport_bindings->poller_remove_func(&m_receiver.poller, &dest_2->transport);\n    endpoint->transport_bindings->close_func(&dest_2->transport);\n\n    ASSERT_EQ(0u, endpoint->destinations.length);\n    ASSERT_EQ(1, aeron_publication_image_remove_destination(image, remove_channel_2));\n    ASSERT_EQ(0u, image->connections.length);\n    ASSERT_EQ(dest_2, destination);\n    aeron_receive_destination_delete(dest_2, &m_counters_manager);\n}\n\nTEST_F(PublicationImageTest, shouldSendControlMessagesToAllDestinations)\n{\n    struct sockaddr_storage addr = {}; // Don't really care what value this is.\n    uint8_t data[128];\n    auto *message = reinterpret_cast<aeron_data_header_t *>(data);\n    const char *uri_1 = \"aeron:udp?endpoint=localhost:9090\";\n    const char *uri_2 = \"aeron:udp?endpoint=localhost:9091\";\n    aeron_receive_channel_endpoint_t *endpoint = createMdsEndpoint();\n    int32_t stream_id = 1001;\n    int32_t session_id = 1000001;\n    int64_t registration_id = 0;\n\n    aeron_udp_channel_t *channel_1;\n    aeron_receive_destination_t *dest_1;\n    aeron_udp_channel_t *channel_2;\n    aeron_receive_destination_t *dest_2;\n\n    aeron_udp_channel_parse(strlen(uri_1), uri_1, &m_resolver, &channel_1, false);\n    aeron_udp_channel_parse(strlen(uri_2), uri_2, &m_resolver, &channel_2, false);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_1,\n        channel_1,\n        channel_1,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_add_destination(endpoint, dest_1));\n\n    aeron_publication_image_t *image = createImage(endpoint, dest_1, stream_id, session_id);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_2,\n        channel_2,\n        channel_2,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(2, aeron_receive_channel_endpoint_add_destination(endpoint, dest_2));\n\n    ASSERT_EQ(2, aeron_publication_image_add_destination(image, dest_2));\n\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_ACTIVE, image->conductor_fields.state);\n    image->congestion_control->should_measure_rtt = always_measure_rtt;\n\n    auto *bindings_state_dest1 = static_cast<aeron_test_udp_bindings_state_t *>(dest_1->transport.bindings_clientd);\n    auto *bindings_state_dest2 = static_cast<aeron_test_udp_bindings_state_t *>(dest_2->transport.bindings_clientd);\n\n    aeron_publication_image_schedule_status_message(image, 0, TERM_BUFFER_SIZE);\n    aeron_publication_image_send_pending_status_message(image, 1000000000);\n    ASSERT_EQ(1, bindings_state_dest1->sm_count);\n    ASSERT_EQ(0, bindings_state_dest2->sm_count);\n\n    aeron_publication_image_on_gap_detected(image, 0, 0, 1);\n    aeron_publication_image_send_pending_loss(image);\n    ASSERT_EQ(1, bindings_state_dest1->nak_count);\n\n    aeron_publication_image_initiate_rttm(image, 1000000000);\n    ASSERT_EQ(1, bindings_state_dest1->rttm_count);\n\n    message->stream_id = stream_id;\n    message->session_id = session_id;\n    message->frame_header.frame_length = 64;\n    message->term_id = 0;\n    message->term_offset = 0;\n\n    aeron_publication_image_insert_packet(image, dest_2, 0, 0, data, 64, &addr);\n\n    aeron_publication_image_schedule_status_message(image, 1, TERM_BUFFER_SIZE);\n    aeron_publication_image_send_pending_status_message(image, 2000000000);\n    ASSERT_EQ(2, bindings_state_dest1->sm_count);\n    ASSERT_EQ(1, bindings_state_dest2->sm_count);\n    ASSERT_EQ(3, aeron_counter_get_plain(image->status_messages_sent_counter));\n\n    aeron_publication_image_on_gap_detected(image, 0, 0, 1);\n    aeron_publication_image_send_pending_loss(image);\n    ASSERT_EQ(2, bindings_state_dest1->nak_count);\n    ASSERT_EQ(1, bindings_state_dest2->nak_count);\n    ASSERT_EQ(3, aeron_counter_get_plain(image->nak_messages_sent_counter));\n\n    aeron_publication_image_initiate_rttm(image, 2000000000);\n    ASSERT_EQ(2, bindings_state_dest1->rttm_count);\n}\n\nTEST_F(PublicationImageTest, shouldHandleEosAcrossDestinations)\n{\n    struct sockaddr_storage addr = {}; // Don't really care what value this is.\n    uint8_t data[128];\n    memset(data, 0, sizeof(data));\n\n    auto *heartbeat = reinterpret_cast<aeron_data_header_t *>(data);\n    const char *uri_1 = \"aeron:udp?endpoint=localhost:9090\";\n    const char *uri_2 = \"aeron:udp?endpoint=localhost:9091\";\n    aeron_receive_channel_endpoint_t *endpoint = createMdsEndpoint();\n    int32_t stream_id = 1001;\n    int32_t session_id = 1000001;\n    int64_t registration_id = 0;\n\n    aeron_udp_channel_t *channel_1;\n    aeron_receive_destination_t *dest_1;\n    aeron_udp_channel_t *channel_2;\n    aeron_receive_destination_t *dest_2;\n\n    aeron_udp_channel_parse(strlen(uri_1), uri_1, &m_resolver, &channel_1, false);\n    aeron_udp_channel_parse(strlen(uri_2), uri_2, &m_resolver, &channel_2, false);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_1,\n        channel_1,\n        channel_1,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_add_destination(endpoint, dest_1));\n\n    aeron_publication_image_t *image = createImage(endpoint, dest_1, stream_id, session_id);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_2,\n        channel_2,\n        channel_2,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(2, aeron_receive_channel_endpoint_add_destination(endpoint, dest_2));\n\n    ASSERT_EQ(2, aeron_publication_image_add_destination(image, dest_2));\n\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_ACTIVE, image->conductor_fields.state);\n    image->congestion_control->should_measure_rtt = always_measure_rtt;\n\n    heartbeat->stream_id = stream_id;\n    heartbeat->session_id = session_id;\n    heartbeat->frame_header.frame_length = 0;\n    heartbeat->term_id = 0;\n    heartbeat->term_offset = 0;\n    heartbeat->frame_header.flags |= AERON_DATA_HEADER_EOS_FLAG;\n\n    bool is_eos = true;\n    AERON_GET_ACQUIRE(is_eos, image->is_end_of_stream);\n    ASSERT_EQ(false, is_eos);\n\n    aeron_publication_image_insert_packet(image, dest_2, 0, 0, data, AERON_DATA_HEADER_LENGTH, &addr);\n\n    AERON_GET_ACQUIRE(is_eos, image->is_end_of_stream);\n    ASSERT_EQ(false, is_eos);\n\n    aeron_publication_image_insert_packet(image, dest_1, 0, 0, data, AERON_DATA_HEADER_LENGTH, &addr);\n\n    AERON_GET_ACQUIRE(is_eos, image->is_end_of_stream);\n    ASSERT_EQ(true, is_eos);\n}\n\nTEST_F(PublicationImageTest, shouldNotSendControlMessagesToAllDestinationThatHaveNotBeenActive)\n{\n    struct sockaddr_storage addr = {}; // Don't really care what value this is.\n    uint8_t data[128];\n    auto *message = reinterpret_cast<aeron_data_header_t *>(data);\n    const char *uri_1 = \"aeron:udp?endpoint=localhost:9090\";\n    const char *uri_2 = \"aeron:udp?endpoint=localhost:9091\";\n    aeron_receive_channel_endpoint_t *endpoint = createMdsEndpoint();\n    int32_t stream_id = 1001;\n    int32_t session_id = 1000001;\n    int64_t registration_id = 0;\n\n    int64_t t0_ns = 1000 * 1000 * 1000;\n    int64_t t1_ns = t0_ns + (2 * AERON_RECEIVE_DESTINATION_TIMEOUT_NS);\n\n    aeron_udp_channel_t *channel_1;\n    aeron_receive_destination_t *dest_1;\n    aeron_udp_channel_t *channel_2;\n    aeron_receive_destination_t *dest_2;\n\n    aeron_udp_channel_parse(strlen(uri_1), uri_1, &m_resolver, &channel_1, false);\n    aeron_udp_channel_parse(strlen(uri_2), uri_2, &m_resolver, &channel_2, false);\n\n    aeron_clock_update_cached_nano_time(m_context->receiver_cached_clock, t0_ns);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_1,\n        channel_1,\n        channel_1,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_add_destination(endpoint, dest_1));\n\n    aeron_publication_image_t *image = createImage(endpoint, dest_1, stream_id, session_id);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_2,\n        channel_2,\n        channel_2,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(2, aeron_receive_channel_endpoint_add_destination(endpoint, dest_2));\n\n    ASSERT_EQ(2, aeron_publication_image_add_destination(image, dest_2));\n\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_ACTIVE, image->conductor_fields.state);\n    image->congestion_control->should_measure_rtt = always_measure_rtt;\n\n    auto *bindings_state_dest1 = static_cast<aeron_test_udp_bindings_state_t *>(dest_1->transport.bindings_clientd);\n\n    size_t message_length = 64;\n\n    message->stream_id = stream_id;\n    message->session_id = session_id;\n    message->frame_header.frame_length = (int32_t) message_length;\n    message->term_id = 0;\n    message->term_offset = 0;\n\n    aeron_publication_image_insert_packet(image, dest_1, 0, 0, data, message_length, &addr);\n    aeron_publication_image_insert_packet(image, dest_2, 0, 0, data, message_length, &addr);\n\n    aeron_clock_update_cached_nano_time(m_context->receiver_cached_clock, t1_ns);\n\n    auto next_offset = (int32_t) message_length;\n    message->term_offset = next_offset;\n\n    aeron_publication_image_insert_packet(image, dest_2, 0, next_offset, data, message_length, &addr);\n\n    aeron_publication_image_schedule_status_message(image, 1, TERM_BUFFER_SIZE);\n    aeron_publication_image_send_pending_status_message(image, t1_ns);\n    EXPECT_EQ(0, bindings_state_dest1->sm_count);\n\n    aeron_publication_image_on_gap_detected(image, 0, 0, 1);\n    aeron_publication_image_send_pending_loss(image);\n    EXPECT_EQ(0, bindings_state_dest1->nak_count);\n\n    aeron_publication_image_initiate_rttm(image, t1_ns);\n    EXPECT_EQ(0, bindings_state_dest1->rttm_count);\n}\n\nTEST_F(PublicationImageTest, shouldTrackActiveTransportAccountBasedOnFrames)\n{\n    struct sockaddr_storage addr = {}; // Don't really care what value this is.\n    uint8_t data[128];\n    auto *message = reinterpret_cast<aeron_data_header_t *>(data);\n    const char *uri_1 = \"aeron:udp?endpoint=localhost:9090\";\n    const char *uri_2 = \"aeron:udp?endpoint=localhost:9091\";\n    aeron_receive_channel_endpoint_t *endpoint = createMdsEndpoint();\n    int32_t stream_id = 1001;\n    int32_t session_id = 1000001;\n    int64_t registration_id = 0;\n\n    int64_t t0_ns = static_cast<int64_t>(2 * m_context->image_liveness_timeout_ns);\n\n    aeron_udp_channel_t *channel_1;\n    aeron_receive_destination_t *dest_1;\n    aeron_udp_channel_t *channel_2;\n    aeron_receive_destination_t *dest_2;\n\n    aeron_udp_channel_parse(strlen(uri_1), uri_1, &m_resolver, &channel_1, false);\n    aeron_udp_channel_parse(strlen(uri_2), uri_2, &m_resolver, &channel_2, false);\n\n    aeron_clock_update_cached_nano_time(m_context->receiver_cached_clock, t0_ns);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_1,\n        channel_1,\n        channel_1,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_add_destination(endpoint, dest_1));\n\n    aeron_publication_image_t *image = createImage(endpoint, dest_1, stream_id, session_id);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_2,\n        channel_2,\n        channel_2,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(2, aeron_receive_channel_endpoint_add_destination(endpoint, dest_2));\n\n    ASSERT_EQ(2, aeron_publication_image_add_destination(image, dest_2));\n\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_ACTIVE, image->conductor_fields.state);\n    image->congestion_control->should_measure_rtt = always_measure_rtt;\n\n    auto *test_bindings_state = static_cast<aeron_test_udp_bindings_state_t *>(dest_1->transport.bindings_clientd);\n\n    aeron_publication_image_schedule_status_message(image, 0, TERM_BUFFER_SIZE);\n    aeron_publication_image_send_pending_status_message(image, t0_ns);\n    ASSERT_EQ(1, test_bindings_state->sm_count);\n\n    ASSERT_EQ(0, image->log_meta_data->active_transport_count);\n\n    message->stream_id = stream_id;\n    message->session_id = session_id;\n    message->frame_header.frame_length = 64;\n    message->term_id = 0;\n    message->term_offset = 0;\n\n    aeron_publication_image_insert_packet(image, dest_2, 0, 0, data, 64, &addr);\n    aeron_publication_image_schedule_status_message(image, 0, TERM_BUFFER_SIZE);\n    aeron_publication_image_send_pending_status_message(image, t0_ns);\n\n    ASSERT_EQ(1, image->log_meta_data->active_transport_count);\n\n    aeron_publication_image_insert_packet(image, dest_1, 0, 0, data, 64, &addr);\n    aeron_publication_image_schedule_status_message(image, 0, TERM_BUFFER_SIZE);\n    aeron_publication_image_send_pending_status_message(image, t0_ns);\n\n    ASSERT_EQ(2, image->log_meta_data->active_transport_count);\n}\n\nTEST_F(PublicationImageTest, shouldTrackUnderRunningTransportsWithLastSmAndReceiverWindowLength)\n{\n    struct sockaddr_storage addr = {}; // Don't really care what value this is.\n    uint8_t data[128];\n    auto *message = reinterpret_cast<aeron_data_header_t *>(data);\n    const char *uri_1 = \"aeron:udp?endpoint=localhost:9090\";\n    const char *uri_2 = \"aeron:udp?endpoint=localhost:9091\";\n    aeron_receive_channel_endpoint_t *endpoint = createMdsEndpoint();\n    int32_t stream_id = 1001;\n    int32_t session_id = 1000001;\n    int64_t registration_id = 0;\n    size_t message_length = 64;\n\n    int64_t t0_ns = 10 * AERON_RECEIVE_DESTINATION_TIMEOUT_NS;\n    int64_t t1_ns = t0_ns + AERON_RECEIVE_DESTINATION_TIMEOUT_NS;\n\n    aeron_udp_channel_t *channel_1;\n    aeron_receive_destination_t *dest_1;\n    aeron_udp_channel_t *channel_2;\n    aeron_receive_destination_t *dest_2;\n\n    aeron_udp_channel_parse(strlen(uri_1), uri_1, &m_resolver, &channel_1, false);\n    aeron_udp_channel_parse(strlen(uri_2), uri_2, &m_resolver, &channel_2, false);\n\n    aeron_clock_update_cached_nano_time(m_context->receiver_cached_clock, t0_ns);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_1,\n        channel_1,\n        channel_1,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_add_destination(endpoint, dest_1));\n\n    aeron_publication_image_t *image = createImage(endpoint, dest_1, stream_id, session_id);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest_2,\n        channel_2,\n        channel_2,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(2, aeron_receive_channel_endpoint_add_destination(endpoint, dest_2));\n\n    ASSERT_EQ(2, aeron_publication_image_add_destination(image, dest_2));\n\n    ASSERT_EQ(AERON_PUBLICATION_IMAGE_STATE_ACTIVE, image->conductor_fields.state);\n    image->congestion_control->should_measure_rtt = always_measure_rtt;\n\n    auto *bindings_state_dest1 = static_cast<aeron_test_udp_bindings_state_t *>(dest_1->transport.bindings_clientd);\n    auto *bindings_state_dest2 = static_cast<aeron_test_udp_bindings_state_t *>(dest_2->transport.bindings_clientd);\n\n    aeron_publication_image_schedule_status_message(image, 0, TERM_BUFFER_SIZE);\n    aeron_publication_image_send_pending_status_message(image, t0_ns);\n    ASSERT_EQ(1, bindings_state_dest1->sm_count);\n\n    aeron_clock_update_cached_nano_time(m_context->receiver_cached_clock, t1_ns);\n\n    message->stream_id = stream_id;\n    message->session_id = session_id;\n    message->frame_header.frame_length = (int32_t) message_length;\n    message->term_id = 0;\n    message->term_offset = 0;\n\n    aeron_publication_image_insert_packet(image, dest_2, 0, 0, data, message_length, &addr);\n\n    aeron_publication_image_schedule_status_message(image, message_length, TERM_BUFFER_SIZE);\n    aeron_publication_image_send_pending_status_message(image, t1_ns);\n\n    ASSERT_EQ(1, bindings_state_dest1->sm_count);\n\n    aeron_publication_image_insert_packet(image, dest_1, 0, 0, data, message_length, &addr);\n\n    aeron_publication_image_schedule_status_message(image, message_length, TERM_BUFFER_SIZE);\n    aeron_publication_image_send_pending_status_message(image, t1_ns);\n\n    ASSERT_EQ(2, bindings_state_dest1->sm_count);\n    ASSERT_EQ(2, bindings_state_dest2->sm_count);\n}\n\nTEST_F(PublicationImageTest, shouldReturnStorageSpaceErrorIfNotEnoughStorageSpaceAvailable)\n{\n    const char *uri = \"aeron:udp?endpoint=localhost:9090\";\n    aeron_receive_channel_endpoint_t *endpoint = createMdsEndpoint();\n    int32_t stream_id = 1001;\n    int32_t session_id = 1000001;\n    int64_t registration_id = 0;\n\n    aeron_udp_channel_t *channel;\n    aeron_receive_destination_t *dest;\n\n    aeron_udp_channel_parse(strlen(uri), uri, &m_resolver, &channel, false);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest,\n        channel,\n        channel,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_add_destination(endpoint, dest));\n\n    m_context->usable_fs_space_func = [](const char *path) -> uint64_t\n    {\n        return 42;\n    };\n    aeron_publication_image_t *image = createImage(endpoint, dest, stream_id, session_id);\n\n    ASSERT_EQ(nullptr, image) << aeron_errmsg();\n    EXPECT_EQ(-AERON_ERROR_CODE_STORAGE_SPACE, aeron_errcode());\n    auto expected_error_text =\n        std::string(\"insufficient usable storage for new log of length=200704 usable=42 in \")\n        .append(m_context->aeron_dir);\n    EXPECT_NE(std::string::npos, std::string(aeron_errmsg()).find(expected_error_text));\n}\n\nTEST_F(PublicationImageTest, shouldLogWarningIfStorageSpaceIsLow)\n{\n    const char *uri = \"aeron:udp?endpoint=localhost:9090\";\n    aeron_receive_channel_endpoint_t *endpoint = createMdsEndpoint();\n    int32_t stream_id = 1001;\n    int32_t session_id = 1000001;\n    int64_t registration_id = 0;\n\n    aeron_udp_channel_t *channel;\n    aeron_receive_destination_t *dest;\n\n    aeron_udp_channel_parse(strlen(uri), uri, &m_resolver, &channel, false);\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest,\n        channel,\n        channel,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n    ASSERT_EQ(1, aeron_receive_channel_endpoint_add_destination(endpoint, dest));\n\n    m_context->usable_fs_space_func = [](const char *path) -> uint64_t\n    {\n        return 123456789;\n    };\n    m_context->low_file_store_warning_threshold = 987654321ULL;\n    aeron_publication_image_t *image = createImage(endpoint, dest, stream_id, session_id);\n\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n    EXPECT_EQ(0, aeron_errcode());\n    auto errors_list = m_context->error_log->observation_list;\n    EXPECT_NE(0, errors_list->num_observations);\n    auto last_error = errors_list->observations[errors_list->num_observations - 1];\n    EXPECT_EQ(-AERON_ERROR_CODE_STORAGE_SPACE, last_error.error_code);\n    auto error_text = std::string(last_error.description);\n    EXPECT_EQ(error_text.size(), last_error.description_length);\n    auto expected_warning =\n        std::string(\"WARNING: space is running low: threshold=987654321 usable=123456789 in \")\n        .append(m_context->aeron_dir);\n    EXPECT_NE(std::string::npos, error_text.find(expected_warning));\n}\n\nTEST_F(PublicationImageTest, shouldReportUniqueLoss)\n{\n    const char *uri = \"aeron:udp?endpoint=localhost:9090\";\n    aeron_receive_channel_endpoint_t *endpoint = createEndpoint(uri);\n    int32_t stream_id = 777;\n    int32_t session_id = 42;\n    int64_t registration_id = 0;\n\n    aeron_udp_channel_t *channel;\n    aeron_receive_destination_t *dest;\n\n    ASSERT_EQ(0, aeron_udp_channel_parse(strlen(uri), uri, &m_resolver, &channel, false));\n\n    ASSERT_LE(0, aeron_receive_destination_create(\n        &dest,\n        channel,\n        channel,\n        m_context,\n        &m_counters_manager,\n        registration_id,\n        endpoint->channel_status.counter_id));\n\n    aeron_publication_image_t *image = createImage(endpoint, dest, stream_id, session_id);\n    ASSERT_NE(nullptr, image) << aeron_errmsg();\n\n    const int32_t term_id = 111;\n    const int32_t offset = 128;\n    const size_t length = 192;\n\n    // initial loss report\n    aeron_publication_image_on_gap_detected(image, term_id, offset, length);\n    EXPECT_EQ(1, image->begin_loss_change);\n    EXPECT_EQ(term_id, image->loss_term_id);\n    EXPECT_EQ(offset, image->loss_term_offset);\n    EXPECT_EQ(length, image->loss_length);\n    EXPECT_EQ(1, image->end_loss_change);\n    EXPECT_EQ(1, aeron_loss_reporter_read(\n        m_loss_reporter_buffer.data(),\n        m_loss_reporter_buffer.size(),\n        [](\n            void *clientd,\n            int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *channel,\n            int32_t channel_length,\n            const char *source,\n            int32_t source_length)\n        {\n            EXPECT_EQ(1, observation_count);\n            EXPECT_EQ(192, total_bytes_lost);\n            EXPECT_EQ(first_observation_timestamp, last_observation_timestamp);\n            EXPECT_EQ(42, session_id);\n            EXPECT_EQ(777, stream_id);\n        },\n        nullptr));\n\n    // same loss => no reporting\n    aeron_publication_image_on_gap_detected(image, term_id, offset, length);\n    EXPECT_EQ(2, image->begin_loss_change);\n    EXPECT_EQ(term_id, image->loss_term_id);\n    EXPECT_EQ(offset, image->loss_term_offset);\n    EXPECT_EQ(length, image->loss_length);\n    EXPECT_EQ(2, image->end_loss_change);\n    EXPECT_EQ(1, aeron_loss_reporter_read(\n        m_loss_reporter_buffer.data(),\n        m_loss_reporter_buffer.size(),\n        [](\n            void *clientd,\n            int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *channel,\n            int32_t channel_length,\n            const char *source,\n            int32_t source_length)\n        {\n            EXPECT_EQ(1, observation_count);\n            EXPECT_EQ(192, total_bytes_lost);\n            EXPECT_EQ(first_observation_timestamp, last_observation_timestamp);\n            EXPECT_EQ(42, session_id);\n            EXPECT_EQ(777, stream_id);\n        },\n        nullptr));\n\n    // less loss => no reporting\n    aeron_publication_image_on_gap_detected(image, term_id, offset, 32);\n    EXPECT_EQ(3, image->begin_loss_change);\n    EXPECT_EQ(term_id, image->loss_term_id);\n    EXPECT_EQ(offset, image->loss_term_offset);\n    EXPECT_EQ(32, image->loss_length);\n    EXPECT_EQ(3, image->end_loss_change);\n    EXPECT_EQ(1, aeron_loss_reporter_read(\n        m_loss_reporter_buffer.data(),\n        m_loss_reporter_buffer.size(),\n        [](\n            void *clientd,\n            int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *channel,\n            int32_t channel_length,\n            const char *source,\n            int32_t source_length)\n        {\n            EXPECT_EQ(1, observation_count);\n            EXPECT_EQ(192, total_bytes_lost);\n            EXPECT_EQ(first_observation_timestamp, last_observation_timestamp);\n            EXPECT_EQ(42, session_id);\n            EXPECT_EQ(777, stream_id);\n        },\n        nullptr));\n\n    // larger loss => report\n    aeron_publication_image_on_gap_detected(image, term_id, offset, 1500);\n    EXPECT_EQ(4, image->begin_loss_change);\n    EXPECT_EQ(term_id, image->loss_term_id);\n    EXPECT_EQ(offset, image->loss_term_offset);\n    EXPECT_EQ(1500, image->loss_length);\n    EXPECT_EQ(4, image->end_loss_change);\n    EXPECT_EQ(1, aeron_loss_reporter_read(\n        m_loss_reporter_buffer.data(),\n        m_loss_reporter_buffer.size(),\n        [](\n            void *clientd,\n            int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *channel,\n            int32_t channel_length,\n            const char *source,\n            int32_t source_length)\n        {\n            EXPECT_EQ(2, observation_count);\n            EXPECT_EQ(1500, total_bytes_lost);\n            EXPECT_LE(first_observation_timestamp, last_observation_timestamp);\n            EXPECT_EQ(42, session_id);\n            EXPECT_EQ(777, stream_id);\n        },\n        nullptr));\n\n    // overlapping loss => report\n    aeron_publication_image_on_gap_detected(image, term_id, offset + 996, 700);\n    EXPECT_EQ(5, image->begin_loss_change);\n    EXPECT_EQ(term_id, image->loss_term_id);\n    EXPECT_EQ(offset + 996, image->loss_term_offset);\n    EXPECT_EQ(700, image->loss_length);\n    EXPECT_EQ(5, image->end_loss_change);\n    EXPECT_EQ(1, aeron_loss_reporter_read(\n        m_loss_reporter_buffer.data(),\n        m_loss_reporter_buffer.size(),\n        [](\n            void *clientd,\n            int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *channel,\n            int32_t channel_length,\n            const char *source,\n            int32_t source_length)\n        {\n            EXPECT_EQ(3, observation_count);\n            EXPECT_EQ(1696, total_bytes_lost);\n            EXPECT_LE(first_observation_timestamp, last_observation_timestamp);\n            EXPECT_EQ(42, session_id);\n            EXPECT_EQ(777, stream_id);\n        },\n        nullptr));\n\n    // non-overlapping loss => report\n    aeron_publication_image_on_gap_detected(image, term_id, offset + 4096, 128);\n    EXPECT_EQ(6, image->begin_loss_change);\n    EXPECT_EQ(term_id, image->loss_term_id);\n    EXPECT_EQ(offset + 4096, image->loss_term_offset);\n    EXPECT_EQ(128, image->loss_length);\n    EXPECT_EQ(6, image->end_loss_change);\n    EXPECT_EQ(1, aeron_loss_reporter_read(\n        m_loss_reporter_buffer.data(),\n        m_loss_reporter_buffer.size(),\n        [](\n            void *clientd,\n            int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *channel,\n            int32_t channel_length,\n            const char *source,\n            int32_t source_length)\n        {\n            EXPECT_EQ(4, observation_count);\n            EXPECT_EQ(1824, total_bytes_lost);\n            EXPECT_LE(first_observation_timestamp, last_observation_timestamp);\n            EXPECT_EQ(42, session_id);\n            EXPECT_EQ(777, stream_id);\n        },\n        nullptr));\n\n    // loss in another term => report\n    aeron_publication_image_on_gap_detected(image, term_id + 3, 0, 400);\n    EXPECT_EQ(7, image->begin_loss_change);\n    EXPECT_EQ(term_id + 3, image->loss_term_id);\n    EXPECT_EQ(0, image->loss_term_offset);\n    EXPECT_EQ(400, image->loss_length);\n    EXPECT_EQ(7, image->end_loss_change);\n    EXPECT_EQ(1, aeron_loss_reporter_read(\n        m_loss_reporter_buffer.data(),\n        m_loss_reporter_buffer.size(),\n        [](\n            void *clientd,\n            int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *channel,\n            int32_t channel_length,\n            const char *source,\n            int32_t source_length)\n        {\n            EXPECT_EQ(5, observation_count);\n            EXPECT_EQ(2224, total_bytes_lost);\n            EXPECT_LE(first_observation_timestamp, last_observation_timestamp);\n            EXPECT_EQ(42, session_id);\n            EXPECT_EQ(777, stream_id);\n        },\n        nullptr));\n\n    // same loss => no report\n    aeron_publication_image_on_gap_detected(image, term_id + 3, 0, 400);\n    EXPECT_EQ(8, image->begin_loss_change);\n    EXPECT_EQ(term_id + 3, image->loss_term_id);\n    EXPECT_EQ(0, image->loss_term_offset);\n    EXPECT_EQ(400, image->loss_length);\n    EXPECT_EQ(8, image->end_loss_change);\n    EXPECT_EQ(1, aeron_loss_reporter_read(\n        m_loss_reporter_buffer.data(),\n        m_loss_reporter_buffer.size(),\n        [](\n            void *clientd,\n            int64_t observation_count,\n            int64_t total_bytes_lost,\n            int64_t first_observation_timestamp,\n            int64_t last_observation_timestamp,\n            int32_t session_id,\n            int32_t stream_id,\n            const char *channel,\n            int32_t channel_length,\n            const char *source,\n            int32_t source_length)\n        {\n            EXPECT_EQ(5, observation_count);\n            EXPECT_EQ(2224, total_bytes_lost);\n            EXPECT_LE(first_observation_timestamp, last_observation_timestamp);\n            EXPECT_EQ(42, session_id);\n            EXPECT_EQ(777, stream_id);\n        },\n        nullptr));\n\n    aeron_publication_image_remove_destination(image, channel);\n    endpoint->transport_bindings->poller_remove_func(&m_receiver.poller, &dest->transport);\n    endpoint->transport_bindings->close_func(&dest->transport);\n    aeron_receive_destination_delete(dest, &m_counters_manager);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_receiver_test.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_RECEIVER_TEST_H\n#define AERON_AERON_RECEIVER_TEST_H\n\n#include <gtest/gtest.h>\n#include <array>\n\nextern \"C\"\n{\n#include \"util/aeron_fileutil.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"concurrent/aeron_distinct_error_log.h\"\n#include \"aeron_publication_image.h\"\n#include \"aeron_data_packet_dispatcher.h\"\n#include \"aeron_driver_receiver.h\"\n#include \"aeron_position.h\"\n#include \"aeron_publication_image.h\"\n#include \"aeron_test_udp_bindings.h\"\n\nint aeron_driver_ensure_dir_is_recreated(aeron_driver_context_t *context);\n}\n\n#define CAPACITY (32 * 1024)\n#define TERM_BUFFER_SIZE (64 * 1024)\n#define MTU (4096)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\ntypedef std::array<std::uint8_t, 4 * CAPACITY> buffer_4x_t;\n\nvoid verify_conductor_cmd_function(int32_t msg_type_id, const void *item, size_t length, void *clientd)\n{\n    auto *cmd = (aeron_command_base_t *)item;\n    ASSERT_EQ(clientd, (void *)cmd->func);\n}\n\nclass ReceiverTestBase\n{\npublic:\n    ReceiverTestBase() :\n        m_error_log_buffer(),\n        m_counter_value_buffer(),\n        m_counter_meta_buffer()\n    {\n    }\n\nprotected:\n    void DoSetUp()\n    {\n        aeron_test_udp_bindings_load(&m_transport_bindings);\n        aeron_driver_context_init(&m_context);\n        aeron_driver_context_set_dir_delete_on_start(m_context, true);\n        aeron_driver_context_set_congestioncontrol_supplier(\n            m_context, aeron_static_window_congestion_control_strategy_supplier);\n        aeron_driver_context_set_udp_channel_transport_bindings(m_context, &m_transport_bindings);\n\n        aeron_driver_ensure_dir_is_recreated(m_context);\n\n        void *command_buffer;\n        int command_buffer_capacity = (512 * 1024) + AERON_RB_TRAILER_LENGTH;\n        aeron_alloc(&command_buffer, command_buffer_capacity);\n        aeron_mpsc_rb_init(&m_conductor_command_queue, command_buffer, command_buffer_capacity);\n\n        m_conductor_proxy.command_queue = &m_conductor_command_queue;\n        m_conductor_proxy.threading_mode = AERON_THREADING_MODE_DEDICATED;\n        m_conductor_proxy.fail_counter = &m_conductor_fail_counter;\n        m_conductor_proxy.conductor = nullptr;\n\n        m_context->conductor_proxy = &m_conductor_proxy;\n\n        aeron_default_name_resolver_supplier(&m_resolver, nullptr, nullptr);\n\n        m_counter_value_buffer.fill(0);\n        m_counter_meta_buffer.fill(0);\n\n        aeron_counters_manager_init(\n            &m_counters_manager,\n            m_counter_meta_buffer.data(), m_counter_meta_buffer.size(),\n            m_counter_value_buffer.data(), m_counter_value_buffer.size(),\n            &m_cached_clock,\n            1000);\n\n        aeron_system_counters_init(&m_system_counters, &m_counters_manager);\n\n        aeron_distinct_error_log_init(\n            &m_error_log, m_error_log_buffer.data(), m_error_log_buffer.size(), aeron_epoch_clock);\n        aeron_driver_receiver_init(&m_receiver, m_context, &m_system_counters, &m_error_log);\n\n        aeron_loss_reporter_init(&m_loss_reporter, m_loss_reporter_buffer.data(), m_loss_reporter_buffer.size());\n\n        m_receiver_proxy.receiver = &m_receiver;\n        m_context->receiver_proxy = &m_receiver_proxy;\n        m_context->error_log = &m_error_log;\n        m_context->error_buffer = m_error_log_buffer.data();\n        m_context->error_buffer_length = m_error_log_buffer.size();\n    }\n\n    void DoTearDown()\n    {\n        for (auto image : m_images)\n        {\n            aeron_publication_image_close(&m_counters_manager, image);\n            aeron_publication_image_free(image);\n        }\n\n        for (auto endpoint : m_endpoints)\n        {\n            aeron_receive_channel_endpoint_delete(&m_counters_manager, endpoint);\n        }\n\n        for (auto channel : m_channels_for_tear_down)\n        {\n            aeron_udp_channel_delete(channel);\n        }\n\n        aeron_driver_receiver_on_close(&m_receiver);\n        free(m_conductor_proxy.command_queue->buffer);\n        aeron_distinct_error_log_close(&m_error_log);\n        aeron_system_counters_close(&m_system_counters);\n        aeron_counters_manager_close(&m_counters_manager);\n        aeron_driver_context_close(m_context);\n    }\n\n    aeron_receive_channel_endpoint_t *createEndpoint(aeron_udp_channel_t *channel)\n    {\n        aeron_atomic_counter_t status_indicator;\n        status_indicator.counter_id = aeron_counter_receive_channel_status_allocate(\n            &m_counters_manager, 0, channel->uri_length, channel->original_uri);\n        status_indicator.value_addr = aeron_counters_manager_addr(&m_counters_manager, status_indicator.counter_id);\n\n        aeron_receive_destination_t *destination = nullptr;\n        if (AERON_UDP_CHANNEL_CONTROL_MODE_MANUAL != channel->control_mode)\n        {\n            if (0 != aeron_receive_destination_create(\n                &destination, channel, channel, m_context, &m_counters_manager, 0, status_indicator.counter_id))\n            {\n                return nullptr;\n            }\n        }\n\n        aeron_receive_channel_endpoint_t *endpoint = nullptr;\n        if (0 != aeron_receive_channel_endpoint_create(\n            &endpoint, channel, destination, &status_indicator, &m_system_counters, m_context))\n        {\n            return nullptr;\n        }\n        m_endpoints.push_back(endpoint);\n\n        return endpoint;\n    }\n\n    aeron_receive_channel_endpoint_t *createEndpoint(const char *uri)\n    {\n        aeron_udp_channel_t *channel = nullptr;\n        if (0 != aeron_udp_channel_parse(strlen(uri), uri, &m_resolver, &channel, false))\n        {\n            return nullptr;\n        }\n\n        return createEndpoint(channel);\n    }\n\n    aeron_receive_channel_endpoint_t *createMdsEndpoint()\n    {\n        return createEndpoint(\"aeron:udp?control-mode=manual\");\n    }\n\n    aeron_udp_channel_t *createChannel(const char *uri, std::vector<aeron_udp_channel_t *> *tracker = nullptr)\n    {\n        aeron_udp_channel_t *channel = nullptr;\n        aeron_udp_channel_parse(strlen(uri), uri, &m_resolver, &channel, false);\n\n        if (nullptr != tracker)\n        {\n            tracker->push_back(channel);\n        }\n\n        return channel;\n    }\n\n    aeron_publication_image_t *createImage(\n        aeron_receive_channel_endpoint_t *endpoint,\n        aeron_receive_destination_t *destination,\n        int32_t stream_id,\n        int32_t session_id,\n        int64_t correlation_id = 0,\n        bool is_reliable = true,\n        bool treat_as_multicast = false)\n    {\n        aeron_publication_image_t *image;\n        aeron_congestion_control_strategy_t *congestion_control_strategy;\n\n        // Counters are copied...\n        aeron_position_t hwm_position;\n        aeron_position_t pos_position;\n        aeron_atomic_counter_t rcv_naks_sent;\n\n        bool is_exclusive = false;\n        int64_t client_id = 9999999;\n        pos_position.counter_id = aeron_counter_publisher_position_allocate(\n            &m_counters_manager, client_id, 0, session_id, stream_id, strlen(\"foo\"), \"foo\", is_exclusive);\n        pos_position.value_addr = aeron_counters_manager_addr(&m_counters_manager, pos_position.counter_id);\n        hwm_position.counter_id = aeron_counter_publisher_position_allocate(\n            &m_counters_manager, client_id, 0, session_id, stream_id, strlen(\"foo\"), \"foo\", is_exclusive);\n        hwm_position.value_addr = aeron_counters_manager_addr(&m_counters_manager, hwm_position.counter_id);\n        rcv_naks_sent.counter_id = aeron_counter_receiver_naks_sent_allocate(\n            &m_counters_manager, client_id, 0, session_id, stream_id, strlen(\"foo\"), \"foo\");\n        rcv_naks_sent.value_addr = aeron_counters_manager_addr(&m_counters_manager, rcv_naks_sent.counter_id);\n\n        aeron_udp_channel_t *channel = endpoint->conductor_fields.udp_channel;\n        m_context->congestion_control_supplier_func(\n            &congestion_control_strategy, channel,\n            0,\n            0,\n            0,\n            TERM_BUFFER_SIZE, MTU,\n            &channel->remote_control,\n            &channel->remote_data,\n            m_context,\n            &m_counters_manager);\n\n        aeron_driver_conductor_t conductor; // the conductor struct is only used for its context\n        conductor.context = m_context;\n\n        if (aeron_publication_image_create(\n            &image,\n            endpoint,\n            destination,\n            &conductor,\n            correlation_id,\n            session_id,\n            stream_id,\n            0,\n            0,\n            0,\n            &hwm_position,\n            &pos_position,\n            &rcv_naks_sent,\n            congestion_control_strategy,\n            &channel->remote_control,\n            &channel->local_data,\n            TERM_BUFFER_SIZE,\n            MTU,\n            UINT8_C(0),\n            &m_loss_reporter,\n            is_reliable,\n            true,\n            treat_as_multicast,\n            &m_system_counters) < 0)\n        {\n            congestion_control_strategy->fini(congestion_control_strategy);\n            return nullptr;\n        }\n\n        m_images.push_back(image);\n\n        return image;\n    }\n\n    static aeron_data_header_t *dataPacket(\n        buffer_t &buffer, int32_t stream_id, int32_t session_id, int32_t term_id = 0, int32_t term_offset = 0)\n    {\n        auto *data_header = (aeron_data_header_t *)buffer.data();\n        data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n        data_header->frame_header.flags = 0;\n        data_header->stream_id = stream_id;\n        data_header->session_id = session_id;\n        data_header->term_id = term_id;\n        data_header->term_offset = term_offset;\n\n        return data_header;\n    }\n\n    static aeron_setup_header_t *setupPacket(\n        buffer_t &buffer, int32_t stream_id, int32_t session_id, int32_t term_id = 0, int32_t term_offset = 0)\n    {\n        auto *setup_header = (aeron_setup_header_t *)buffer.data();\n        setup_header->frame_header.type = AERON_HDR_TYPE_SETUP;\n        setup_header->stream_id = stream_id;\n        setup_header->session_id = session_id;\n        setup_header->initial_term_id = term_id;\n        setup_header->active_term_id = term_id;\n        setup_header->term_offset = term_offset;\n        setup_header->term_length = TERM_BUFFER_SIZE;\n\n        return setup_header;\n    }\n\n    aeron_clock_cache_t m_cached_clock = {};\n    aeron_udp_channel_transport_bindings_t m_transport_bindings = {};\n    aeron_driver_context_t *m_context = nullptr;\n    aeron_counters_manager_t m_counters_manager = {};\n    aeron_system_counters_t m_system_counters = {};\n    aeron_name_resolver_t m_resolver = {};\n    aeron_driver_conductor_proxy_t m_conductor_proxy = {};\n    aeron_driver_receiver_proxy_t m_receiver_proxy = {};\n    aeron_mpsc_rb_t m_conductor_command_queue = {};\n    int64_t m_conductor_fail_counter = 0;\n    aeron_driver_receiver_t m_receiver = {};\n    aeron_distinct_error_log_t m_error_log = {};\n    aeron_loss_reporter_t m_loss_reporter = {};\n    AERON_DECL_ALIGNED(buffer_t m_loss_reporter_buffer, 16) = {};\n    AERON_DECL_ALIGNED(buffer_t m_error_log_buffer, 16) = {};\n    AERON_DECL_ALIGNED(buffer_t m_counter_value_buffer, 16) = {};\n    AERON_DECL_ALIGNED(buffer_4x_t m_counter_meta_buffer, 16) = {};\n    std::vector<aeron_receive_channel_endpoint_t *> m_endpoints;\n    std::vector<aeron_udp_channel_t *> m_channels_for_tear_down;\n    std::vector<aeron_publication_image_t *> m_images;\n};\n\n#endif //AERON_AERON_RECEIVER_TEST_H\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_retransmit_handler_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n#include <functional>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"aeron_retransmit_handler.h\"\n#include \"aeron_flow_control.h\"\n#include \"aeron_counter.h\"\n#include \"concurrent/aeron_logbuffer_descriptor.h\"\n}\n\n#define TERM_LENGTH (AERON_LOGBUFFER_TERM_MIN_LENGTH)\n#define HEADER_LENGTH (AERON_DATA_HEADER_LENGTH)\n\n#define TERM_ID (0x1234)\n\n#define DATA_LENGTH (36)\n#define MESSAGE_LENGTH (DATA_LENGTH + HEADER_LENGTH)\n#define ALIGNED_FRAME_LENGTH (AERON_ALIGN(MESSAGE_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT))\n\n#define DELAY_TIMEOUT_20MS (20 * 1000 * UINT64_C(1000))\n#define LINGER_TIMEOUT_20MS (20 * 1000 * UINT64_C(1000))\n\n#define MTU_LENGTH (1234) // this value is ignored\n\nclass RetransmitHandlerTest : public testing::Test\n{\npublic:\n    RetransmitHandlerTest()\n    {\n        m_flow_control.state = nullptr;\n        m_flow_control.max_retransmission_length = max_retransmission_length;\n    }\n\n    ~RetransmitHandlerTest() override\n    {\n        aeron_retransmit_handler_close(&m_handler);\n    }\n\n    static int on_resend(void *clientd, int32_t term_id, int32_t term_offset, size_t length)\n    {\n        auto *t = (RetransmitHandlerTest *)clientd;\n\n        return t->m_resend(term_id, term_offset, length);\n    }\n\n    static size_t max_retransmission_length(\n        void *state,\n        size_t term_offset,\n        size_t resend_length,\n        size_t term_buffer_length,\n        size_t mtu_length)\n    {\n        return aeron_flow_control_calculate_retransmission_length(\n            resend_length,\n            term_buffer_length,\n            term_offset,\n            AERON_MULTICAST_FLOW_CONTROL_RETRANSMIT_RECEIVER_WINDOW_MULTIPLE);\n    }\n\nprotected:\n    int64_t m_time = 0;\n    int64_t m_invalid_packet_counter = 0;\n    int64_t m_retransmit_overflow_counter = 0;\n    aeron_retransmit_handler_t m_handler = {};\n    aeron_flow_control_strategy_t m_flow_control = {};\n    std::function<int(int32_t, int32_t, size_t)> m_resend;\n};\n\nTEST_F(RetransmitHandlerTest, shouldImmediateRetransmitOnNak)\n{\n    ASSERT_EQ(aeron_retransmit_handler_init(\n        &m_handler, &m_invalid_packet_counter, 0, LINGER_TIMEOUT_20MS, true, 16, &m_retransmit_overflow_counter), 0);\n\n    const int32_t nak_offset = (ALIGNED_FRAME_LENGTH * 2);\n    const size_t nak_length = ALIGNED_FRAME_LENGTH;\n\n    size_t called = 0;\n    m_resend =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, nak_offset);\n            EXPECT_EQ(length, nak_length);\n            called++;\n            return 0;\n        };\n\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n        &m_handler, TERM_ID, nak_offset, nak_length, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(called, 1u);\n}\n\nTEST_F(RetransmitHandlerTest, shouldNotRetransmitOnNakWhileInLinger)\n{\n    ASSERT_EQ(aeron_retransmit_handler_init(\n        &m_handler, &m_invalid_packet_counter, 0, LINGER_TIMEOUT_20MS, true, 16, &m_retransmit_overflow_counter), 0);\n\n    const int32_t nak_offset = (ALIGNED_FRAME_LENGTH * 2);\n    const size_t nak_length = ALIGNED_FRAME_LENGTH;\n\n    size_t called = 0;\n    m_resend =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, nak_offset);\n            EXPECT_EQ(length, nak_length);\n            called++;\n            return 0;\n        };\n\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n        &m_handler, TERM_ID, nak_offset, nak_length, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(called, 1u);\n\n    m_time = 10 * 1000 * 1000L;\n    EXPECT_EQ(aeron_retransmit_handler_process_timeouts(&m_handler, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n        &m_handler, TERM_ID, nak_offset, nak_length, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(called, 1u);\n}\n\nTEST_F(RetransmitHandlerTest, shouldRetransmitOnNakAfterLinger)\n{\n    ASSERT_EQ(aeron_retransmit_handler_init(\n        &m_handler, &m_invalid_packet_counter, 0, LINGER_TIMEOUT_20MS, true, 16, &m_retransmit_overflow_counter), 0);\n\n    const int32_t nak_offset = (ALIGNED_FRAME_LENGTH * 2);\n    const size_t nak_length = ALIGNED_FRAME_LENGTH;\n\n    size_t called = 0;\n    m_resend =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            EXPECT_EQ(term_id, TERM_ID);\n            EXPECT_EQ(term_offset, nak_offset);\n            EXPECT_EQ(length, nak_length);\n            called++;\n            return 0;\n        };\n\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n        &m_handler, TERM_ID, nak_offset, nak_length, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(called, 1u);\n\n    m_time = 30 * 1000 * 1000L;\n    EXPECT_EQ(aeron_retransmit_handler_process_timeouts(&m_handler, m_time, RetransmitHandlerTest::on_resend, this), 1);\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n        &m_handler, TERM_ID, nak_offset, nak_length, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(called, 2u);\n}\n\nTEST_F(RetransmitHandlerTest, shouldRetransmitOnMultipleNaks)\n{\n    ASSERT_EQ(aeron_retransmit_handler_init(\n        &m_handler, &m_invalid_packet_counter, 0, LINGER_TIMEOUT_20MS, true, 16, &m_retransmit_overflow_counter), 0);\n\n    const int32_t nak_offset_1 = (ALIGNED_FRAME_LENGTH * 2);\n    const size_t nak_length_1 = ALIGNED_FRAME_LENGTH;\n    const int32_t nak_offset_2 = (ALIGNED_FRAME_LENGTH * 5);\n    const size_t nak_length_2 = ALIGNED_FRAME_LENGTH * 2;\n\n    size_t called = 0;\n    m_resend =\n        [&](int32_t term_id, int32_t term_offset, size_t length)\n        {\n            called++;\n\n            EXPECT_EQ(term_id, TERM_ID);\n            if (1 == called)\n            {\n                EXPECT_EQ(term_offset, nak_offset_1);\n                EXPECT_EQ(length, nak_length_1);\n            }\n            else if (2 == called)\n            {\n                EXPECT_EQ(term_offset, nak_offset_2);\n                EXPECT_EQ(length, nak_length_2);\n            }\n            return 0;\n        };\n\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n        &m_handler, TERM_ID, nak_offset_1, nak_length_1, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(called, 1u);\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n        &m_handler, TERM_ID, nak_offset_2, nak_length_2, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(called, 2u);\n}\n\nTEST_F(RetransmitHandlerTest, errorOnRetransmitOverflow)\n{\n    ASSERT_EQ(aeron_retransmit_handler_init(\n        &m_handler,\n        &m_invalid_packet_counter,\n        DELAY_TIMEOUT_20MS,\n        LINGER_TIMEOUT_20MS,\n        true,\n        16,\n        &m_retransmit_overflow_counter), 0);\n\n    int64_t initial_overflow_value = aeron_counter_get_plain(&m_retransmit_overflow_counter);\n\n    EXPECT_EQ(m_handler.active_retransmit_count, 0);\n\n    int32_t i = 0;\n    for (i = 0; i < AERON_RETRANSMIT_HANDLER_MAX_RESEND; i++)\n    {\n        EXPECT_EQ(aeron_retransmit_handler_on_nak(\n            &m_handler, TERM_ID, i, 1, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    }\n\n    EXPECT_EQ(m_handler.active_retransmit_count, AERON_RETRANSMIT_HANDLER_MAX_RESEND);\n\n    // there should be no more available retransmit actions\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n        &m_handler, TERM_ID, i, 1, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n\n    EXPECT_NE(initial_overflow_value, aeron_counter_get_plain(&m_retransmit_overflow_counter));\n\n    // these will all be duplicates of previous NAKs\n    for (i = 0; i < AERON_RETRANSMIT_HANDLER_MAX_RESEND; i++)\n    {\n        EXPECT_EQ(aeron_retransmit_handler_on_nak(\n            &m_handler, TERM_ID, i, 1, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    }\n\n    EXPECT_EQ(m_handler.active_retransmit_count, AERON_RETRANSMIT_HANDLER_MAX_RESEND);\n}\n\nTEST_F(RetransmitHandlerTest, shouldSkipInvalidNaks)\n{\n    ASSERT_EQ(aeron_retransmit_handler_init(\n        &m_handler,\n        &m_invalid_packet_counter,\n        DELAY_TIMEOUT_20MS,\n        LINGER_TIMEOUT_20MS,\n        true,\n        16,\n        &m_retransmit_overflow_counter), 0);\n\n    EXPECT_EQ(m_handler.active_retransmit_count, 0);\n\n    // invalid `term-offset`\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n            &m_handler, TERM_ID, TERM_LENGTH - 5, 1, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(1, aeron_counter_get_plain(&m_invalid_packet_counter));\n\n    // negative `term-offset`\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n            &m_handler, TERM_ID, -1024, 1, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(2, aeron_counter_get_plain(&m_invalid_packet_counter));\n\n    // negative `length`\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n            &m_handler, TERM_ID, 0, -32, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(3, aeron_counter_get_plain(&m_invalid_packet_counter));\n\n    EXPECT_EQ(m_handler.active_retransmit_count, 0);\n}\n\nTEST_F(RetransmitHandlerTest, shouldIgnoreEmptyNak)\n{\n    ASSERT_EQ(aeron_retransmit_handler_init(\n        &m_handler,\n        &m_invalid_packet_counter,\n        DELAY_TIMEOUT_20MS,\n        LINGER_TIMEOUT_20MS,\n        true,\n        16,\n        &m_retransmit_overflow_counter), 0);\n\n    EXPECT_EQ(m_handler.active_retransmit_count, 0);\n\n    // negative `length`\n    EXPECT_EQ(aeron_retransmit_handler_on_nak(\n            &m_handler, TERM_ID, 0, 0, TERM_LENGTH, MTU_LENGTH, &m_flow_control, m_time, RetransmitHandlerTest::on_resend, this), 0);\n    EXPECT_EQ(m_handler.active_retransmit_count, 0);\n    EXPECT_EQ(0, aeron_counter_get_plain(&m_invalid_packet_counter));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_term_gap_filler_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_term_gap_filler.h\"\n}\n\n#define INITIAL_TERM_ID (11)\n#define TERM_ID (22)\n#define SESSION_ID (333)\n#define STREAM_ID (7)\n\ntypedef std::array<std::uint8_t, AERON_LOGBUFFER_TERM_MIN_LENGTH> buffer_t;\ntypedef std::array<std::uint8_t, AERON_LOGBUFFER_META_DATA_LENGTH> log_meta_data_buffer_t;\n\nclass TermGapFillerTest : public testing::Test\n{\npublic:\n    TermGapFillerTest() :\n        m_buffer(m_term_buffer.data()),\n        m_log_meta_data(reinterpret_cast<aeron_logbuffer_metadata_t *>(m_log_meta_data_buffer.data()))\n    {\n        m_term_buffer.fill(0);\n        m_log_meta_data_buffer.fill(0);\n        m_log_meta_data->initial_term_id = INITIAL_TERM_ID;\n        aeron_logbuffer_fill_default_header((uint8_t *)m_log_meta_data, SESSION_ID, STREAM_ID, INITIAL_TERM_ID);\n    }\n\nprotected:\n    buffer_t m_term_buffer = {};\n    log_meta_data_buffer_t m_log_meta_data_buffer = {};\n    uint8_t *m_buffer = nullptr;\n    aeron_logbuffer_metadata_t *m_log_meta_data = nullptr;\n};\n\nTEST_F(TermGapFillerTest, shouldFillGapAtBeginningOfTerm)\n{\n    int32_t gap_offset = 0;\n    int32_t gap_length = 64;\n\n    ASSERT_TRUE(aeron_term_gap_filler_try_fill_gap(m_log_meta_data, m_buffer, TERM_ID, gap_offset, gap_length));\n\n    auto *data_header = (aeron_data_header_t *)m_buffer;\n\n    EXPECT_EQ(data_header->frame_header.frame_length, gap_length);\n    EXPECT_EQ(data_header->term_offset, gap_offset);\n    EXPECT_EQ(data_header->session_id, SESSION_ID);\n    EXPECT_EQ(data_header->stream_id, STREAM_ID);\n    EXPECT_EQ(data_header->term_id, TERM_ID);\n    EXPECT_EQ(data_header->frame_header.type, AERON_HDR_TYPE_PAD);\n    EXPECT_EQ(data_header->frame_header.flags, (AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG));\n}\n\nTEST_F(TermGapFillerTest, shouldNotOverwriteExistingFrame)\n{\n    int32_t gap_offset = 0;\n    int32_t gap_length = 64;\n    auto *data_header = (aeron_data_header_t *)m_buffer;\n\n    data_header->frame_header.frame_length = 32;\n\n    ASSERT_FALSE(aeron_term_gap_filler_try_fill_gap(m_log_meta_data, m_buffer, TERM_ID, gap_offset, gap_length));\n}\n\nTEST_F(TermGapFillerTest, shouldFillGapAfterExistingFrame)\n{\n    int32_t gap_offset = 128;\n    int32_t gap_length = 64;\n    auto *data_header = (aeron_data_header_t *)m_buffer;\n\n    data_header->frame_header.frame_length = gap_offset;\n    data_header->frame_header.flags = (AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    data_header->session_id = SESSION_ID;\n    data_header->stream_id = STREAM_ID;\n    data_header->term_id = TERM_ID;\n\n    ASSERT_TRUE(aeron_term_gap_filler_try_fill_gap(m_log_meta_data, m_buffer, TERM_ID, gap_offset, gap_length));\n\n    data_header = (aeron_data_header_t *)(m_buffer + gap_offset);\n\n    EXPECT_EQ(data_header->frame_header.frame_length, gap_length);\n    EXPECT_EQ(data_header->term_offset, gap_offset);\n    EXPECT_EQ(data_header->session_id, SESSION_ID);\n    EXPECT_EQ(data_header->stream_id, STREAM_ID);\n    EXPECT_EQ(data_header->term_id, TERM_ID);\n    EXPECT_EQ(data_header->frame_header.type, AERON_HDR_TYPE_PAD);\n    EXPECT_EQ(data_header->frame_header.flags, (AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG));\n}\n\nTEST_F(TermGapFillerTest, shouldFillGapBetweenExistingFrames)\n{\n    int32_t gap_offset = 128;\n    int32_t gap_length = 64;\n    auto *data_header = (aeron_data_header_t *)m_buffer;\n\n    data_header->frame_header.frame_length = gap_offset;\n    data_header->frame_header.flags = (AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    data_header->session_id = SESSION_ID;\n    data_header->stream_id = STREAM_ID;\n    data_header->term_offset = 0;\n    data_header->term_id = TERM_ID;\n\n    data_header = (aeron_data_header_t *)(m_buffer + gap_offset + gap_length);\n\n    data_header->frame_header.frame_length = 64;\n    data_header->frame_header.flags = (AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    data_header->session_id = SESSION_ID;\n    data_header->stream_id = STREAM_ID;\n    data_header->term_offset = gap_offset + gap_length;\n    data_header->term_id = TERM_ID;\n\n    ASSERT_TRUE(aeron_term_gap_filler_try_fill_gap(m_log_meta_data, m_buffer, TERM_ID, gap_offset, gap_length));\n\n    data_header = (aeron_data_header_t *)(m_buffer + gap_offset);\n\n    EXPECT_EQ(data_header->frame_header.frame_length, gap_length);\n    EXPECT_EQ(data_header->term_offset, gap_offset);\n    EXPECT_EQ(data_header->session_id, SESSION_ID);\n    EXPECT_EQ(data_header->stream_id, STREAM_ID);\n    EXPECT_EQ(data_header->term_id, TERM_ID);\n    EXPECT_EQ(data_header->frame_header.type, AERON_HDR_TYPE_PAD);\n    EXPECT_EQ(data_header->frame_header.flags, (AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG));\n}\n\nTEST_F(TermGapFillerTest, shouldFillGapAtEndOfTerm)\n{\n    int32_t gap_offset = (int32_t)m_term_buffer.size() - 64;\n    int32_t gap_length = 64;\n    auto *data_header = (aeron_data_header_t *)m_buffer;\n\n    data_header->frame_header.frame_length = (int32_t)m_term_buffer.size() - gap_offset;\n    data_header->frame_header.flags = (AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG);\n    data_header->session_id = SESSION_ID;\n    data_header->stream_id = STREAM_ID;\n    data_header->term_id = TERM_ID;\n\n    ASSERT_TRUE(aeron_term_gap_filler_try_fill_gap(m_log_meta_data, m_buffer, TERM_ID, gap_offset, gap_length));\n\n    data_header = (aeron_data_header_t *)(m_buffer + gap_offset);\n\n    EXPECT_EQ(data_header->frame_header.frame_length, gap_length);\n    EXPECT_EQ(data_header->term_offset, gap_offset);\n    EXPECT_EQ(data_header->session_id, SESSION_ID);\n    EXPECT_EQ(data_header->stream_id, STREAM_ID);\n    EXPECT_EQ(data_header->term_id, TERM_ID);\n    EXPECT_EQ(data_header->frame_header.type, AERON_HDR_TYPE_PAD);\n    EXPECT_EQ(data_header->frame_header.flags, (AERON_DATA_HEADER_BEGIN_FLAG | AERON_DATA_HEADER_END_FLAG));\n}"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_term_scanner_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <array>\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"concurrent/aeron_term_scanner.h\"\n}\n\n#define CAPACITY (AERON_LOGBUFFER_TERM_MIN_LENGTH)\n#define MTU_LENGTH (1024)\n\ntypedef std::array<std::uint8_t, CAPACITY> buffer_t;\n\nclass TermScannerTest : public testing::Test\n{\npublic:\n    TermScannerTest() :\n        m_ptr(m_buffer.data())\n    {\n        m_buffer.fill(0);\n    }\n\nprotected:\n    buffer_t m_buffer = {};\n    uint8_t *m_ptr = nullptr;\n    int32_t m_padding = 0;\n};\n\nTEST_F(TermScannerTest, shouldReturnZeroOnEmptyLog)\n{\n    EXPECT_EQ(aeron_term_scanner_scan_for_availability(m_ptr, CAPACITY, MTU_LENGTH, &m_padding), 0);\n    EXPECT_EQ(m_padding, 0);\n}\n\nTEST_F(TermScannerTest, shouldScanSingleMessage)\n{\n    int32_t frame_length = AERON_DATA_HEADER_LENGTH + 1;\n    int32_t aligned_frame_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    auto *data_header = (aeron_data_header_t *)m_ptr;\n\n    data_header->frame_header.frame_length = aligned_frame_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    EXPECT_EQ(aeron_term_scanner_scan_for_availability(m_ptr, CAPACITY, MTU_LENGTH, &m_padding), aligned_frame_length);\n    EXPECT_EQ(m_padding, 0);\n}\n\nTEST_F(TermScannerTest, shouldFailToScanMessageLargerThanMaxLength)\n{\n    int32_t frame_length = AERON_DATA_HEADER_LENGTH + 1;\n    int32_t aligned_frame_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    int32_t max_length = aligned_frame_length - 1;\n    auto *data_header = (aeron_data_header_t *)m_ptr;\n\n    data_header->frame_header.frame_length = aligned_frame_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    EXPECT_EQ(aeron_term_scanner_scan_for_availability(m_ptr, CAPACITY, max_length, &m_padding), -aligned_frame_length);\n    EXPECT_EQ(m_padding, 0);\n}\n\nTEST_F(TermScannerTest, shouldScanTwoMessagesThatFitInSingleMtu)\n{\n    int32_t frame_length = AERON_DATA_HEADER_LENGTH + 100;\n    int32_t aligned_frame_length = AERON_ALIGN(frame_length, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    auto *data_header = (aeron_data_header_t *)m_ptr;\n\n    data_header->frame_header.frame_length = aligned_frame_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    data_header = (aeron_data_header_t *)(m_ptr + aligned_frame_length);\n    data_header->frame_header.frame_length = aligned_frame_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    EXPECT_EQ(aeron_term_scanner_scan_for_availability(\n        m_ptr, CAPACITY, MTU_LENGTH, &m_padding), 2 * aligned_frame_length);\n    EXPECT_EQ(m_padding, 0);\n}\n\nTEST_F(TermScannerTest, shouldScanTwoMessagesAndStopAtMtuBoundary)\n{\n    int32_t frame_two_length = AERON_ALIGN((AERON_DATA_HEADER_LENGTH + 1), AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    int32_t frame_one_length = MTU_LENGTH - frame_two_length;\n    auto *data_header = (aeron_data_header_t *)m_ptr;\n\n    data_header->frame_header.frame_length = frame_one_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    data_header = (aeron_data_header_t *)(m_ptr + frame_one_length);\n    data_header->frame_header.frame_length = frame_two_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    EXPECT_EQ(aeron_term_scanner_scan_for_availability(\n        m_ptr, CAPACITY, MTU_LENGTH, &m_padding), frame_one_length + frame_two_length);\n    EXPECT_EQ(m_padding, 0);\n}\n\nTEST_F(TermScannerTest, shouldScanTwoMessagesAndStopAtSecondThatSpansMtu)\n{\n    int32_t frame_two_length = AERON_ALIGN((AERON_DATA_HEADER_LENGTH + 2), AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    int32_t frame_one_length = MTU_LENGTH - (frame_two_length / 2);\n    auto *data_header = (aeron_data_header_t *)m_ptr;\n\n    data_header->frame_header.frame_length = frame_one_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    data_header = (aeron_data_header_t *)(m_ptr + frame_one_length);\n    data_header->frame_header.frame_length = frame_two_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    EXPECT_EQ(aeron_term_scanner_scan_for_availability(\n        m_ptr, CAPACITY, MTU_LENGTH, &m_padding), frame_one_length);\n    EXPECT_EQ(m_padding, 0);\n}\n\nTEST_F(TermScannerTest, shouldScanLastFrameInBuffer)\n{\n    int32_t aligned_frame_length = AERON_ALIGN((AERON_DATA_HEADER_LENGTH * 2), AERON_LOGBUFFER_FRAME_ALIGNMENT);\n\n    m_ptr += CAPACITY - aligned_frame_length;\n\n    auto *data_header = (aeron_data_header_t *)m_ptr;\n    data_header->frame_header.frame_length = aligned_frame_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    EXPECT_EQ(aeron_term_scanner_scan_for_availability(\n        m_ptr, aligned_frame_length, MTU_LENGTH, &m_padding), aligned_frame_length);\n    EXPECT_EQ(m_padding, 0);\n}\n\nTEST_F(TermScannerTest, shouldScanLastMessageInBufferPlusPadding)\n{\n    int32_t aligned_frame_length = AERON_ALIGN((AERON_DATA_HEADER_LENGTH * 2), AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    int32_t padding_frame_length = AERON_ALIGN((AERON_DATA_HEADER_LENGTH * 3), AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    int32_t offset = CAPACITY - (aligned_frame_length + padding_frame_length);\n\n    m_ptr += offset;\n\n    auto *data_header = (aeron_data_header_t *)m_ptr;\n\n    data_header->frame_header.frame_length = aligned_frame_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    data_header = (aeron_data_header_t *)(m_ptr + aligned_frame_length);\n    data_header->frame_header.frame_length = padding_frame_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_PAD;\n\n    EXPECT_EQ(aeron_term_scanner_scan_for_availability(\n        m_ptr, CAPACITY - offset, MTU_LENGTH, &m_padding), aligned_frame_length + AERON_DATA_HEADER_LENGTH);\n    EXPECT_EQ(m_padding, padding_frame_length - AERON_DATA_HEADER_LENGTH);\n}\n\nTEST_F(TermScannerTest, shouldScanLastMessageInBufferMinusPaddingLimitedByMtu)\n{\n    int32_t aligned_frame_length = AERON_ALIGN(AERON_DATA_HEADER_LENGTH, AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    int32_t offset = CAPACITY - AERON_ALIGN((AERON_DATA_HEADER_LENGTH * 3), AERON_LOGBUFFER_FRAME_ALIGNMENT);\n    int32_t mtu = aligned_frame_length + 8;\n\n    m_ptr += offset;\n\n    auto *data_header = (aeron_data_header_t *)m_ptr;\n\n    data_header->frame_header.frame_length = aligned_frame_length;\n    data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n\n    data_header = (aeron_data_header_t *)(m_ptr + aligned_frame_length);\n    data_header->frame_header.frame_length = aligned_frame_length * 2;\n    data_header->frame_header.type = AERON_HDR_TYPE_PAD;\n\n    EXPECT_EQ(aeron_term_scanner_scan_for_availability(\n        m_ptr, CAPACITY - offset, mtu, &m_padding), aligned_frame_length);\n    EXPECT_EQ(m_padding, 0);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_test_base.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_TEST_BASE_H\n#define AERON_AERON_TEST_BASE_H\n\n#include \"aeronc.h\"\n#include \"EmbeddedMediaDriver.h\"\n\nextern \"C\"\n{\n#include \"util/aeron_env.h\"\n}\n\nclass CSystemTestBase\n{\npublic:\n    using poll_handler_t = std::function<void(const uint8_t *, size_t, aeron_header_t *)>;\n    using image_handler_t = std::function<void(aeron_subscription_t *, aeron_image_t *)>;\n\n    explicit CSystemTestBase(\n        std::vector<std::pair<std::string, std::string>> environment = {},\n        std::function<void(aeron_driver_context_t *)> setContextFunc = [](aeron_driver_context_t *) {}) :\n        m_driver(setContextFunc)\n    {\n        for (auto &envVar : environment)\n        {\n            aeron_env_set(envVar.first.data(), envVar.second.data());\n        }\n        m_driver.start();\n    }\n\n    virtual ~CSystemTestBase()\n    {\n        if (m_aeron)\n        {\n            aeron_close(m_aeron);\n        }\n\n        if (m_context)\n        {\n            aeron_context_close(m_context);\n        }\n\n        m_driver.stop();\n    }\n\n    virtual aeron_t *connect()\n    {\n        if (aeron_context_init(&m_context) < 0)\n        {\n            throw std::runtime_error(aeron_errmsg());\n        }\n\n        if (aeron_init(&m_aeron, m_context) < 0)\n        {\n            throw std::runtime_error(aeron_errmsg());\n        }\n\n        if (aeron_start(m_aeron) < 0)\n        {\n            throw std::runtime_error(aeron_errmsg());\n        }\n\n        return m_aeron;\n    }\n\n    static aeron_publication_t *awaitPublicationOrError(aeron_async_add_publication_t *async)\n    {\n        aeron_publication_t *publication = nullptr;\n\n        do\n        {\n            std::this_thread::yield();\n            if (aeron_async_add_publication_poll(&publication, async) < 0)\n            {\n                return nullptr;\n            }\n        }\n        while (!publication);\n\n        return publication;\n    }\n\n    static aeron_exclusive_publication_t *awaitExclusivePublicationOrError(\n        aeron_async_add_exclusive_publication_t *async)\n    {\n        aeron_exclusive_publication_t *publication = nullptr;\n\n        do\n        {\n            std::this_thread::yield();\n            if (aeron_async_add_exclusive_publication_poll(&publication, async) < 0)\n            {\n                return nullptr;\n            }\n        }\n        while (!publication);\n\n        return publication;\n    }\n\n    static aeron_subscription_t *awaitSubscriptionOrError(aeron_async_add_subscription_t *async)\n    {\n        aeron_subscription_t *subscription = nullptr;\n\n        do\n        {\n            std::this_thread::yield();\n            if (aeron_async_add_subscription_poll(&subscription, async) < 0)\n            {\n                return nullptr;\n            }\n        }\n        while (!subscription);\n\n        return subscription;\n    }\n\n    static bool awaitDestinationOrError(aeron_async_destination_t *async)\n    {\n        do\n        {\n            std::this_thread::yield();\n            switch (aeron_subscription_async_destination_poll(async))\n            {\n                case -1:\n                    return false;\n                case 1:\n                    return true;\n            }\n        }\n        while (true);\n    }\n\n    static aeron_counter_t *awaitCounterOrError(aeron_async_add_counter_t *async)\n    {\n        aeron_counter_t *counter = nullptr;\n\n        do\n        {\n            std::this_thread::yield();\n            if (aeron_async_add_counter_poll(&counter, async) < 0)\n            {\n                return nullptr;\n            }\n        }\n        while (!counter);\n\n        return counter;\n    }\n\n    static aeron_counter_t *awaitStaticCounterOrError(aeron_async_add_counter_t *async)\n    {\n        aeron_counter_t *counter = nullptr;\n\n        do\n        {\n            std::this_thread::yield();\n            if (aeron_async_add_counter_poll(&counter, async) < 0)\n            {\n                return nullptr;\n            }\n        }\n        while (!counter);\n\n        return counter;\n    }\n\n    static int64_t awaitNextSessionIdOrError(aeron_async_add_counter_t *async)\n    {\n        int32_t next_session_id;\n        while (true)\n        {\n            std::this_thread::yield();\n            int result = aeron_async_next_session_id_poll(&next_session_id, async);\n            if (result < 0)\n            {\n                return INT64_MAX;\n            }\n            else if (1 == result)\n            {\n                return next_session_id;\n            }\n        }\n    }\n\n    static void awaitConnected(aeron_subscription_t *subscription)\n    {\n        while (!aeron_subscription_is_connected(subscription))\n        {\n            std::this_thread::yield();\n        }\n    }\n\n    static void poll_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        auto test = reinterpret_cast<CSystemTestBase *>(clientd);\n\n        test->m_poll_handler(buffer, length, header);\n    }\n\n    int poll(aeron_subscription_t *subscription, poll_handler_t &handler, int fragment_limit)\n    {\n        m_poll_handler = handler;\n        return aeron_subscription_poll(subscription, poll_handler, this, (size_t)fragment_limit);\n    }\n\n    static void onAvailableImage(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image)\n    {\n        auto test = reinterpret_cast<CSystemTestBase *>(clientd);\n\n        if (test->m_onAvailableImage)\n        {\n            test->m_onAvailableImage(subscription, image);\n        }\n    }\n\n    static void onUnavailableImage(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image)\n    {\n        auto test = reinterpret_cast<CSystemTestBase *>(clientd);\n\n        if (test->m_onUnavailableImage)\n        {\n            test->m_onUnavailableImage(subscription, image);\n        }\n    }\n\n    static void setFlagOnClose(void *clientd)\n    {\n        auto *flag = static_cast<std::atomic<bool> *>(clientd);\n        flag->store(true);\n    }\n\nprotected:\n    aeron::EmbeddedMediaDriver m_driver;\n    aeron_context_t *m_context = nullptr;\n    aeron_t *m_aeron = nullptr;\n\n    poll_handler_t m_poll_handler = nullptr;\n    image_handler_t m_onAvailableImage = nullptr;\n    image_handler_t m_onUnavailableImage = nullptr;\n};\n\n#endif //AERON_AERON_TEST_BASE_H\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_test_udp_bindings.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_AERON_TEST_UDP_BINDINGS_H\n#define AERON_AERON_TEST_UDP_BINDINGS_H\n\n#include \"aeronmd.h\"\n#include \"media/aeron_udp_channel_transport_bindings.h\"\n\ntypedef struct aeron_test_udp_bindings_state_stct\n{\n    int mmsg_count;\n    int msg_count;\n    int sm_count;\n    int nak_count;\n    int setup_count;\n    int rttm_count;\n    int heartbeat_count;\n}\naeron_test_udp_bindings_state_t;\n\nint aeron_test_udp_channel_transport_init(\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *bind_addr,\n    struct sockaddr_storage *multicast_if_addr,\n    struct sockaddr_storage *connect_addr,\n    aeron_udp_channel_transport_params_t *params,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity)\n{\n    aeron_test_udp_bindings_state_t *state;\n\n    if (aeron_alloc((void **)&state, sizeof(aeron_test_udp_bindings_state_t)) < 0)\n    {\n        return -1;\n    }\n\n    transport->bindings_clientd = state;\n\n    return 0;\n}\n\nint aeron_test_udp_channel_transport_close(aeron_udp_channel_transport_t *transport)\n{\n    aeron_free(transport->bindings_clientd);\n\n    return 0;\n}\n\nint aeron_test_udp_channel_transport_recvmmsg(\n    aeron_udp_channel_transport_t *transport,\n    struct mmsghdr *msgvec,\n    size_t vlen,\n    int64_t *bytes_rcved,\n    aeron_udp_transport_recv_func_t recv_func,\n    void *clientd)\n{\n    return 0;\n}\n\nint aeron_test_udp_channel_transport_send(\n    aeron_udp_channel_data_paths_t *data_paths,\n    aeron_udp_channel_transport_t *transport,\n    struct sockaddr_storage *address,\n    struct iovec *iov,\n    size_t iov_length,\n    int64_t *bytes_sent)\n{\n    aeron_test_udp_bindings_state_t *state = (aeron_test_udp_bindings_state_t *)transport->bindings_clientd;\n\n    for (size_t i = 0; i < iov_length; i++)\n    {\n        state->msg_count++;\n        aeron_frame_header_t *header = (aeron_frame_header_t *)iov[i].iov_base;\n        *bytes_sent = iov[i].iov_len;\n\n        switch (header->type)\n        {\n            case AERON_HDR_TYPE_SETUP:\n                state->setup_count++;\n                break;\n\n            case AERON_HDR_TYPE_SM:\n                state->sm_count++;\n                break;\n\n            case AERON_HDR_TYPE_NAK:\n                state->nak_count++;\n                break;\n\n            case AERON_HDR_TYPE_RTTM:\n                state->rttm_count++;\n                break;\n\n            case AERON_HDR_TYPE_DATA:\n                state->heartbeat_count++;\n\n            default:\n                break;\n        }\n    }\n\n    return 0;\n}\n\n\nint aeron_test_udp_channel_transport_get_so_rcvbuf(aeron_udp_channel_transport_t *transport, size_t *so_rcvbuf)\n{\n    return 0;\n}\n\nint aeron_test_udp_channel_transport_bind_addr_and_port(\n    aeron_udp_channel_transport_t *transport, char *buffer, size_t length)\n{\n    return 0;\n}\n\nint aeron_test_udp_transport_poller_init(\n    aeron_udp_transport_poller_t *poller,\n    aeron_driver_context_t *context,\n    aeron_udp_channel_transport_affinity_t affinity)\n{\n    return 0;\n}\n\nint aeron_test_udp_transport_poller_close(aeron_udp_transport_poller_t *poller)\n{\n    return 0;\n}\n\nint aeron_test_udp_transport_poller_add(\n    aeron_udp_transport_poller_t *poller, aeron_udp_channel_transport_t *transport)\n{\n    return 0;\n}\n\nint aeron_test_udp_transport_poller_remove(\n    aeron_udp_transport_poller_t *poller, aeron_udp_channel_transport_t *transport)\n{\n    return 0;\n}\n\nint aeron_test_udp_transport_poller_poll(\n    aeron_udp_transport_poller_t *poller,\n    struct mmsghdr *msgvec,\n    size_t vlen,\n    int64_t *bytes_rcved,\n    aeron_udp_transport_recv_func_t recv_func,\n    aeron_udp_channel_transport_recvmmsg_func_t recvmmsg_func,\n    void *clientd)\n{\n    return 0;\n}\n\nvoid aeron_test_udp_bindings_load(aeron_udp_channel_transport_bindings_t *bindings)\n{\n    bindings->meta_info.type = \"test\";\n    bindings->meta_info.name = \"counting\";\n    bindings->meta_info.source_symbol = \"header\";\n    bindings->close_func = aeron_test_udp_channel_transport_close;\n\n    bindings->poller_close_func = aeron_test_udp_transport_poller_close;\n    bindings->poller_remove_func = aeron_test_udp_transport_poller_remove;\n    bindings->poller_add_func = aeron_test_udp_transport_poller_add;\n    bindings->poller_init_func = aeron_test_udp_transport_poller_init;\n    bindings->poller_poll_func = aeron_test_udp_transport_poller_poll;\n\n    bindings->send_func = aeron_test_udp_channel_transport_send;\n    bindings->recvmmsg_func = aeron_test_udp_channel_transport_recvmmsg;\n    bindings->init_func = aeron_test_udp_channel_transport_init;\n    bindings->bind_addr_and_port_func = aeron_test_udp_channel_transport_bind_addr_and_port;\n    bindings->get_so_rcvbuf_func = aeron_test_udp_channel_transport_get_so_rcvbuf;\n}\n\n#endif //AERON_AERON_TEST_UDP_BINDINGS_H\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_timestamps_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n\n#include \"aeron_test_base.h\"\n\nextern \"C\"\n{\n#include \"concurrent/aeron_atomic.h\"\n#include \"agent/aeron_driver_agent.h\"\n#include \"aeron_driver_context.h\"\n}\n\n#define URI \"aeron:udp?endpoint=localhost:24325\"\n#define STREAM_ID (117)\n\nstruct message_t\n{\n    int64_t timestamp_1;\n    int64_t timestamp_2;\n    char text[16];\n};\n\nclass TimestampsTest : public CSystemTestBase, public testing::Test\n{\npublic:\n    TimestampsTest() : CSystemTestBase(std::vector<std::pair<std::string, std::string>>{\n            { \"AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS\", \"loss\" },\n            { \"AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_LOSS_ARGS\", \"rate=0\" }\n        })\n    {}\n};\n\nint64_t null_reserved_value(void *clientd, uint8_t *buffer, size_t frame_length)\n{\n    return AERON_NULL_VALUE;\n}\n\nTEST_F(TimestampsTest, shouldPutTimestampInMessagesReservedValue)\n{\n#if !defined(__linux__)\n    GTEST_SKIP();\n#endif\n\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    std::string uri = std::string(URI);\n    const char *uri_s = uri.append(\"|media-rcv-ts-offset=reserved\").c_str();\n\n    struct message_t message = {};\n    message.timestamp_1 = AERON_NULL_VALUE;\n    message.timestamp_2 = AERON_NULL_VALUE;\n    strcpy(message.text, \"hello\");\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, uri_s, STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    while (aeron_publication_offer(\n        publication, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    int poll_result;\n    bool called = false;\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        aeron_header_values_t header_values;\n        aeron_header_values(header, &header_values);\n        auto *incoming = (message_t *)buffer;\n        EXPECT_NE(AERON_NULL_VALUE, header_values.frame.reserved_value);\n        EXPECT_EQ(AERON_NULL_VALUE, incoming->timestamp_1);\n        EXPECT_EQ(AERON_NULL_VALUE, incoming->timestamp_2);\n        EXPECT_STREQ(incoming->text, message.text);\n        called = true;\n    };\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_TRUE(called);\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_F(TimestampsTest, shouldPutTimestampInMessagesAtOffset)\n{\n#if !defined(__linux__)\n    GTEST_SKIP();\n#endif\n\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    std::stringstream uriStream;\n    uriStream << URI << \"|media-rcv-ts-offset=\" << offsetof(message_t, timestamp_2) << '\\0';\n    std::string uri = uriStream.str();\n    const char *uri_s = uri.c_str();\n\n    struct message_t message = {};\n    message.timestamp_1 = AERON_NULL_VALUE;\n    message.timestamp_2 = AERON_NULL_VALUE;\n    strcpy(message.text, \"hello\");\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, uri_s, STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    while (aeron_publication_offer(\n        publication, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    int poll_result;\n    bool called = false;\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        aeron_header_values_t header_values;\n        aeron_header_values(header, &header_values);\n        auto *incoming = (message_t *)buffer;\n        EXPECT_EQ(AERON_NULL_VALUE, header_values.frame.reserved_value);\n        EXPECT_EQ(AERON_NULL_VALUE, incoming->timestamp_1);\n        EXPECT_NE(AERON_NULL_VALUE, incoming->timestamp_2);\n        EXPECT_STREQ(incoming->text, message.text);\n        called = true;\n    };\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_TRUE(called);\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_F(TimestampsTest, shouldNotPutTimestampInMessagesAtIfOffsetExceedsMessage)\n{\n#if !defined(__linux__)\n    GTEST_SKIP();\n#endif\n\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    std::stringstream uriStream;\n    uriStream << URI << \"|media-rcv-ts-offset=\" << sizeof(message_t) - 4 << '\\0';\n    std::string uri = uriStream.str();\n    const char *uri_s = uri.c_str();\n\n    struct message_t message = {};\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, uri_s, STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    while (aeron_publication_offer(\n        publication, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    int poll_result;\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        aeron_header_values_t header_values;\n        aeron_header_values(header, &header_values);\n        auto *incoming = (message_t *)buffer;\n        EXPECT_EQ(AERON_NULL_VALUE, header_values.frame.reserved_value);\n        EXPECT_EQ(0, incoming->timestamp_1);\n        EXPECT_EQ(0, incoming->timestamp_2);\n        EXPECT_EQ(0, memcmp(incoming->text, message.text, sizeof(incoming->text)));\n    };\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_F(TimestampsTest, shouldErrorIfMediaReceiveTimestampConfigurationClashes)\n{\n    aeron_async_add_subscription_t *async_sub = nullptr;\n\n    std::string uriOriginal = std::string(URI);\n    const char *uriOriginal_s = uriOriginal.append(\"|media-rcv-ts-offset=8\").c_str();\n\n    const char *uriNotSpecified_s = URI;\n\n    std::string uriDifferentOffset = std::string(URI);\n    const char *uriDifferentOffset_s = uriDifferentOffset.append(\"|media-rcv-ts-offset=reserved\").c_str();\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uriOriginal_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uriNotSpecified_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_FALSE(awaitSubscriptionOrError(async_sub));\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uriDifferentOffset_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_FALSE(awaitSubscriptionOrError(async_sub));\n}\n\nTEST_F(TimestampsTest, shouldErrorIfChannelReceiveTimestampConfigurationClashes)\n{\n    aeron_async_add_subscription_t *async_sub = nullptr;\n\n    std::string uriOriginal = std::string(URI);\n    const char *uriOriginal_s = uriOriginal.append(\"|channel-rcv-ts-offset=8\").c_str();\n\n    const char *uriNotSpecified_s = URI;\n\n    std::string uriDifferentOffset = std::string(URI);\n    const char *uriDifferentOffset_s = uriDifferentOffset.append(\"|channel-rcv-ts-offset=reserved\").c_str();\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uriOriginal_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uriNotSpecified_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_FALSE(awaitSubscriptionOrError(async_sub));\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uriDifferentOffset_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n    ASSERT_FALSE(awaitSubscriptionOrError(async_sub));\n}\n\nTEST_F(TimestampsTest, shouldErrorIfChannelSendTimestampConfigurationClashes)\n{\n    aeron_async_add_subscription_t *async_pub = nullptr;\n\n    std::string uriOriginal = std::string(URI);\n    const char *uriOriginal_s = uriOriginal.append(\"|channel-snd-ts-offset=8\").c_str();\n\n    const char *uriNotSpecified_s = URI;\n\n    std::string uriDifferentOffset = std::string(URI);\n    const char *uriDifferentOffset_s = uriDifferentOffset.append(\"|channel-snd-ts-offset=reserved\").c_str();\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, uriOriginal_s, STREAM_ID), 0);\n    ASSERT_TRUE(awaitPublicationOrError(async_pub)) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, uriNotSpecified_s, STREAM_ID), 0);\n    ASSERT_FALSE(awaitPublicationOrError(async_pub));\n\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, uriDifferentOffset_s, STREAM_ID), 0);\n    ASSERT_FALSE(awaitPublicationOrError(async_pub));\n}\n\nTEST_F(TimestampsTest, shouldPutTimestampInMessagesReservedValueWithMergedMds)\n{\n#if !defined(__linux__)\n    GTEST_SKIP();\n#endif\n\n    aeron_async_add_exclusive_publication_t *asyncPubA = nullptr;\n    aeron_async_add_exclusive_publication_t *asyncPubB = nullptr;\n    aeron_async_add_subscription_t *asyncSub = nullptr;\n    aeron_async_destination_t *asyncDestA = nullptr;\n    aeron_async_destination_t *asyncDestB = nullptr;\n    std::string destinationA = std::string(\"aeron:udp?endpoint=localhost:24325\");\n    std::string destinationB = std::string(\"aeron:udp?endpoint=localhost:24326\");\n    std::string mdsUri = std::string(\"aeron:udp?control-mode=manual|media-rcv-ts-offset=reserved\");\n\n    struct message_t message = {};\n    message.timestamp_1 = AERON_NULL_VALUE;\n    message.timestamp_2 = AERON_NULL_VALUE;\n    strcpy(message.text, \"hello\");\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &asyncSub, m_aeron, mdsUri.c_str(), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(asyncSub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_subscription_async_add_destination(&asyncDestA, m_aeron, subscription, destinationA.c_str()));\n    ASSERT_TRUE(awaitDestinationOrError(asyncDestA));\n\n    ASSERT_EQ(0, aeron_subscription_async_add_destination(&asyncDestB, m_aeron, subscription, destinationB.c_str()));\n    ASSERT_TRUE(awaitDestinationOrError(asyncDestB));\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication(&asyncPubA, m_aeron, destinationA.c_str(), STREAM_ID), 0);\n    aeron_exclusive_publication_t *publicationA = awaitExclusivePublicationOrError(asyncPubA);\n    ASSERT_TRUE(publicationA) << aeron_errmsg();\n\n    aeron_publication_constants_t pubAConstants;\n    aeron_exclusive_publication_constants(publicationA, &pubAConstants);\n    int64_t pubAPosition = aeron_exclusive_publication_position(publicationA);\n\n    int32_t termId = aeron_logbuffer_compute_term_id_from_position(\n        pubAPosition,\n        pubAConstants.position_bits_to_shift,\n        pubAConstants.initial_term_id);\n    auto termOffset = (int32_t)(pubAPosition & (pubAConstants.term_buffer_length - 1));\n\n    std::stringstream publicationBStream;\n    publicationBStream << destinationB;\n    publicationBStream << \"|session-id=\" << pubAConstants.session_id;\n    publicationBStream << \"|init-term-id=\" << pubAConstants.initial_term_id;\n    publicationBStream << \"|term-id=\" << termId;\n    publicationBStream << \"|term-offset=\" << termOffset;\n    std::string destB = publicationBStream.str();\n\n    ASSERT_EQ(aeron_async_add_exclusive_publication(&asyncPubB, m_aeron, destB.c_str(), STREAM_ID), 0);\n    aeron_exclusive_publication_t *publicationB = awaitExclusivePublicationOrError(asyncPubB);\n    ASSERT_TRUE(publicationB) << aeron_errmsg();\n\n    awaitConnected(subscription);\n\n    int poll_result;\n    int called = 0;\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        aeron_header_values_t header_values;\n        aeron_header_values(header, &header_values);\n        auto *incoming = (message_t *)buffer;\n        EXPECT_NE(AERON_NULL_VALUE, header_values.frame.reserved_value);\n        EXPECT_EQ(AERON_NULL_VALUE, incoming->timestamp_1);\n        EXPECT_EQ(AERON_NULL_VALUE, incoming->timestamp_2);\n        EXPECT_STREQ(incoming->text, message.text);\n        called++;\n    };\n\n    while (aeron_exclusive_publication_offer(\n        publicationA, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_EQ(1, called);\n\n    while (aeron_exclusive_publication_offer(\n        publicationB, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    // Check that publicationB's first message is merged (i.e. not visible to the subscription).\n    for (int i = 0; i < 500; i++)\n    {\n        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n        ASSERT_EQ(0, poll(subscription, handler, 1));\n    }\n\n    while (aeron_exclusive_publication_offer(\n        publicationB, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_EQ(2, called);\n\n    EXPECT_EQ(aeron_exclusive_publication_close(publicationA, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_exclusive_publication_close(publicationB, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_F(TimestampsTest, shouldPutTimestampInMessagesReservedValueWithNonMergedMds)\n{\n#if !defined(__linux__)\n    GTEST_SKIP();\n#endif\n\n    aeron_async_add_publication_t *asyncPubA = nullptr;\n    aeron_async_add_publication_t *asyncPubB = nullptr;\n    aeron_async_add_subscription_t *asyncSub = nullptr;\n    aeron_async_destination_t *asyncDestA = nullptr;\n    aeron_async_destination_t *asyncDestB = nullptr;\n    std::string destinationA = std::string(\"aeron:udp?endpoint=localhost:24325\");\n    std::string destinationB = std::string(\"aeron:udp?endpoint=localhost:24326\");\n    std::string mdsUri = std::string(\"aeron:udp?control-mode=manual|media-rcv-ts-offset=reserved\");\n\n    struct message_t message = {};\n    message.timestamp_1 = AERON_NULL_VALUE;\n    message.timestamp_2 = AERON_NULL_VALUE;\n    strcpy(message.text, \"hello\");\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &asyncSub, m_aeron, mdsUri.c_str(), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(asyncSub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_subscription_async_add_destination(&asyncDestA, m_aeron, subscription, destinationA.c_str()));\n    ASSERT_TRUE(awaitDestinationOrError(asyncDestA));\n\n    ASSERT_EQ(0, aeron_subscription_async_add_destination(&asyncDestB, m_aeron, subscription, destinationB.c_str()));\n    ASSERT_TRUE(awaitDestinationOrError(asyncDestB));\n\n    ASSERT_EQ(aeron_async_add_publication(&asyncPubA, m_aeron, destinationA.c_str(), STREAM_ID), 0);\n    aeron_publication_t *publicationA = awaitPublicationOrError(asyncPubA);\n    ASSERT_TRUE(publicationA) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_publication(&asyncPubB, m_aeron, destinationB.c_str(), STREAM_ID), 0);\n    aeron_publication_t *publicationB = awaitPublicationOrError(asyncPubB);\n    ASSERT_TRUE(publicationB) << aeron_errmsg();\n\n    awaitConnected(subscription);\n\n    while (aeron_publication_offer(\n        publicationA, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    while (aeron_publication_offer(\n        publicationB, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    int poll_result;\n    int called = 0;\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        aeron_header_values_t header_values;\n        aeron_header_values(header, &header_values);\n        auto *incoming = (message_t *)buffer;\n        EXPECT_NE(AERON_NULL_VALUE, header_values.frame.reserved_value);\n        EXPECT_EQ(AERON_NULL_VALUE, incoming->timestamp_1);\n        EXPECT_EQ(AERON_NULL_VALUE, incoming->timestamp_2);\n        EXPECT_STREQ(incoming->text, message.text);\n        called++;\n    };\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_EQ(1, called);\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_EQ(2, called);\n\n    EXPECT_EQ(aeron_publication_close(publicationA, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_F(TimestampsTest, shouldPutChannelSendAndReceivesTimestampsInMessagesAtOffset)\n{\n#if !defined(__linux__)\n    GTEST_SKIP();\n#endif\n\n    aeron_async_add_publication_t *async_pub = nullptr;\n    aeron_async_add_subscription_t *async_sub = nullptr;\n    std::stringstream uriStream;\n    uriStream << URI <<\n        \"|media-rcv-ts-offset=reserved\" <<\n        \"|channel-snd-ts-offset=\" << offsetof(message_t, timestamp_2) <<\n        \"|channel-rcv-ts-offset=\" << offsetof(message_t, timestamp_1) << '\\0';\n\n    std::string uri = uriStream.str();\n    const char *uri_s = uri.c_str();\n\n    struct message_t message = {};\n    message.timestamp_1 = AERON_NULL_VALUE;\n    message.timestamp_2 = AERON_NULL_VALUE;\n    strcpy(message.text, \"hello\");\n\n    ASSERT_TRUE(connect());\n    ASSERT_EQ(aeron_async_add_publication(&async_pub, m_aeron, uri_s, STREAM_ID), 0);\n\n    aeron_publication_t *publication = awaitPublicationOrError(async_pub);\n    ASSERT_TRUE(publication) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &async_sub, m_aeron, uri_s, STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subscription = awaitSubscriptionOrError(async_sub);\n    ASSERT_TRUE(subscription) << aeron_errmsg();\n    awaitConnected(subscription);\n\n    while (aeron_publication_offer(\n        publication, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    int poll_result;\n    bool called = false;\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        aeron_header_values_t header_values;\n        aeron_header_values(header, &header_values);\n        auto *incoming = (message_t*)buffer;\n        EXPECT_NE(AERON_NULL_VALUE, header_values.frame.reserved_value);\n        EXPECT_NE(AERON_NULL_VALUE, incoming->timestamp_1);\n        EXPECT_NE(AERON_NULL_VALUE, incoming->timestamp_2);\n        EXPECT_STREQ(incoming->text, message.text);\n        called = true;\n    };\n\n    while ((poll_result = poll(subscription, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_TRUE(called);\n\n    EXPECT_EQ(aeron_publication_close(publication, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subscription, nullptr, nullptr), 0);\n}\n\nTEST_F(TimestampsTest, shouldSendTimestampAllMessagesInReservedValueWithMdc)\n{\n#if !defined(__linux__)\n    GTEST_SKIP();\n#endif\n\n    aeron_async_add_publication_t *asyncPub = nullptr;\n    aeron_async_add_subscription_t *asyncSubA = nullptr;\n    aeron_async_add_subscription_t *asyncSubB = nullptr;\n    aeron_async_destination_t *asyncDestA = nullptr;\n    aeron_async_destination_t *asyncDestB = nullptr;\n    std::string destinationA = std::string(\"aeron:udp?endpoint=localhost:24325\");\n    std::string destinationB = std::string(\"aeron:udp?endpoint=localhost:24326\");\n    std::string mdcUri = std::string(\"aeron:udp?control-mode=manual|channel-snd-ts-offset=reserved\");\n\n    struct message_t message = {};\n    message.timestamp_1 = AERON_NULL_VALUE;\n    message.timestamp_2 = AERON_NULL_VALUE;\n    strcpy(message.text, \"hello\");\n\n    ASSERT_TRUE(connect());\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &asyncSubA, m_aeron, destinationA.c_str(), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subA = awaitSubscriptionOrError(asyncSubA);\n    ASSERT_TRUE(subA) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_subscription(\n        &asyncSubB, m_aeron, destinationB.c_str(), STREAM_ID, nullptr, nullptr, nullptr, nullptr), 0);\n\n    aeron_subscription_t *subB = awaitSubscriptionOrError(asyncSubB);\n    ASSERT_TRUE(subB) << aeron_errmsg();\n\n    ASSERT_EQ(aeron_async_add_publication(&asyncPub, m_aeron, mdcUri.c_str(), STREAM_ID), 0);\n    aeron_publication_t *pub = awaitPublicationOrError(asyncPub);\n    ASSERT_TRUE(pub) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_publication_async_add_destination(&asyncDestA, m_aeron, pub, destinationA.c_str()));\n    ASSERT_TRUE(awaitDestinationOrError(asyncDestA)) << aeron_errmsg();\n\n    ASSERT_EQ(0, aeron_publication_async_add_destination(&asyncDestB, m_aeron, pub, destinationB.c_str()));\n    ASSERT_TRUE(awaitDestinationOrError(asyncDestB));\n\n    awaitConnected(subA);\n    awaitConnected(subB);\n\n    while (aeron_publication_offer(\n        pub, (const uint8_t *)&message, sizeof(message), null_reserved_value, nullptr) < 0)\n    {\n        std::this_thread::yield();\n    }\n\n    int poll_result;\n    int called = 0;\n    poll_handler_t handler = [&](const uint8_t *buffer, size_t length, aeron_header_t *header)\n    {\n        aeron_header_values_t header_values;\n        aeron_header_values(header, &header_values);\n        auto *incoming = (message_t*)buffer;\n        EXPECT_NE(AERON_NULL_VALUE, header_values.frame.reserved_value);\n        EXPECT_STREQ(incoming->text, message.text);\n        called++;\n    };\n\n    while ((poll_result = poll(subA, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_EQ(1, called);\n\n    while ((poll_result = poll(subB, handler, 1)) == 0)\n    {\n        std::this_thread::yield();\n    }\n    EXPECT_EQ(poll_result, 1) << aeron_errmsg();\n    EXPECT_EQ(2, called);\n\n    EXPECT_EQ(aeron_publication_close(pub, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subA, nullptr, nullptr), 0);\n    EXPECT_EQ(aeron_subscription_close(subB, nullptr, nullptr), 0);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeron_udp_channel_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n\n#include <regex>\n\nextern \"C\"\n{\n#include \"media/aeron_udp_channel.h\"\n#include \"util/aeron_env.h\"\n}\n\nclass UdpChannelTestBase\n{\npublic:\n    UdpChannelTestBase()\n    {\n        aeron_default_name_resolver_supplier(&m_resolver, nullptr, nullptr);\n    }\n\n    virtual ~UdpChannelTestBase()\n    {\n        aeron_udp_channel_delete(m_channel);\n        m_resolver.close_func(&m_resolver);\n    }\n\n    static struct sockaddr_in *ipv4_addr(struct sockaddr_storage *addr)\n    {\n        return (struct sockaddr_in *)addr;\n    }\n\n    static struct sockaddr_in6 *ipv6_addr(struct sockaddr_storage *addr)\n    {\n        return (struct sockaddr_in6 *)addr;\n    }\n\n    const char *inet_ntop(struct sockaddr_storage *addr)\n    {\n        if (AF_INET == addr->ss_family)\n        {\n            return ::inet_ntop(AF_INET, &(ipv4_addr(addr)->sin_addr), m_buffer, sizeof(m_buffer));\n        }\n        else if (AF_INET6 == addr->ss_family)\n        {\n            return ::inet_ntop(AF_INET6, &(ipv6_addr(addr)->sin6_addr), m_buffer, sizeof(m_buffer));\n        }\n\n        return nullptr;\n    }\n\n    static int port(struct sockaddr_storage *addr)\n    {\n        if (AF_INET == addr->ss_family)\n        {\n            return ntohs(ipv4_addr(addr)->sin_port);\n        }\n        else if (AF_INET6 == addr->ss_family)\n        {\n            return ntohs(ipv6_addr(addr)->sin6_port);\n        }\n\n        return 0;\n    }\n\n    int parse_udp_channel(const char *uri)\n    {\n        aeron_udp_channel_delete(m_channel);\n        m_channel = nullptr;\n\n        return aeron_udp_channel_parse(strlen(uri), uri, &m_resolver, &m_channel, false);\n    }\n\n    static void assert_error_message_contains(const char* text)\n    {\n        auto error = std::string(aeron_errmsg());\n        EXPECT_NE(0, error.length());\n        EXPECT_NE(std::string::npos, error.find(text));\n    }\n\nprotected:\n    char m_buffer[AERON_NETUTIL_FORMATTED_MAX_LENGTH] = {};\n    aeron_udp_channel_t *m_channel = nullptr;\n    aeron_name_resolver_t m_resolver = {};\n};\n\nclass UdpChannelTest : public UdpChannelTestBase, public testing::Test\n{\nprotected:\n    void TearDown() override\n    {\n        Test::TearDown();\n        aeron_env_unset(AERON_NAME_RESOLVER_CSV_TABLE_ARGS_ENV_VAR);\n    }\n};\n\nclass UdpChannelNamesParameterisedTest :\n    public testing::TestWithParam<std::tuple<const char *, const char *, const char *, const char *, const char *>>,\n    public UdpChannelTestBase\n{\n};\n\nclass UdpChannelEqualityParameterisedTest :\n    public testing::TestWithParam<std::tuple<bool, const char *, const char *>>,\n    public UdpChannelTestBase\n{\n};\n\n\nTEST_F(UdpChannelTest, shouldComputeMaxMessageLength)\n{\n    EXPECT_EQ(0, aeron_compute_max_message_length(0));\n    EXPECT_EQ(4, aeron_compute_max_message_length(32));\n    EXPECT_EQ(8192, aeron_compute_max_message_length(64 * 1024));\n    EXPECT_EQ(8 * 1024 * 1024, aeron_compute_max_message_length(64 * 1024 * 1024));\n    EXPECT_EQ(AERON_FRAME_MAX_MESSAGE_LENGTH, aeron_compute_max_message_length(1024 * 1024 * 1024));\n    EXPECT_EQ(16 * 1024 * 1024, aeron_compute_max_message_length(512 * 1024 * 1024));\n}\n\nTEST_F(UdpChannelTest, shouldCheckIfFrameIsValid)\n{\n    aeron_frame_header_t header = {};\n\n    EXPECT_FALSE(aeron_is_frame_valid(&header, 1)); // length below min header length\n\n    header.version = 123;\n    EXPECT_FALSE(aeron_is_frame_valid(&header, AERON_FRAME_HEADER_LENGTH)); // wrong version\n\n    header.version = AERON_FRAME_HEADER_VERSION;\n    header.frame_length = 100;\n    EXPECT_TRUE(aeron_is_frame_valid(&header, AERON_FRAME_HEADER_LENGTH + 1));\n    header.type = 123;\n    EXPECT_TRUE(aeron_is_frame_valid(&header, header.frame_length));\n    header.flags = 0xAE;\n    EXPECT_TRUE(aeron_is_frame_valid(&header, INT32_MAX));\n}\n\nTEST_F(UdpChannelTest, shouldParseExplicitLocalAddressAndPortFormat)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=localhost:40123|endpoint=localhost:40124\"), 0) << aeron_errmsg();\n\n    EXPECT_EQ(m_channel->local_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_data), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->local_data), 40123);\n\n    EXPECT_EQ(m_channel->local_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_control), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->local_control), 40123);\n\n    EXPECT_EQ(m_channel->remote_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_data), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->remote_data), 40124);\n\n    EXPECT_EQ(m_channel->remote_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_control), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->remote_control), 40124);\n}\n\nTEST_F(UdpChannelTest, shouldParseImpliedLocalAddressAndPortFormat)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?endpoint=localhost:40124\"), 0) << aeron_errmsg();\n\n    EXPECT_EQ(m_channel->local_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_data), \"0.0.0.0\");\n    EXPECT_EQ(port(&m_channel->local_data), 0);\n\n    EXPECT_EQ(m_channel->local_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_control), \"0.0.0.0\");\n    EXPECT_EQ(port(&m_channel->local_control), 0);\n\n    EXPECT_EQ(m_channel->remote_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_data), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->remote_data), 40124);\n\n    EXPECT_EQ(m_channel->remote_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_control), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->remote_control), 40124);\n}\n\nTEST_F(UdpChannelTest, shouldErrorForIncorrectScheme)\n{\n    ASSERT_EQ(parse_udp_channel(\"unknownudp://localhost:40124\"), -1);\n}\n\nTEST_F(UdpChannelTest, shouldErrorForMissingAddressWithAeronUri)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp\"), -1);\n}\n\nTEST_F(UdpChannelTest, shouldErrorOnEvenMulticastAddress)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?endpoint=224.10.9.8\"), -1);\n}\n\nTEST_F(UdpChannelTest, shouldParseValidMulticastAddress)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=localhost|endpoint=224.10.9.9:40124\"), 0) << aeron_errmsg();\n\n    EXPECT_EQ(m_channel->local_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_data), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->local_data), 0);\n\n    EXPECT_EQ(m_channel->local_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_control), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->local_control), 0);\n\n    EXPECT_EQ(m_channel->remote_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_data), \"224.10.9.9\");\n    EXPECT_EQ(port(&m_channel->remote_data), 40124);\n\n    EXPECT_EQ(m_channel->remote_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_control), \"224.10.9.10\");\n    EXPECT_EQ(port(&m_channel->remote_control), 40124);\n}\n\nTEST_F(UdpChannelTest, shouldParseImpliedLocalPortFormat)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=localhost|endpoint=localhost:40124\"), 0) << aeron_errmsg();\n\n    EXPECT_EQ(m_channel->local_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_data), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->local_data), 0);\n\n    EXPECT_EQ(m_channel->local_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_control), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->local_control), 0);\n\n    EXPECT_EQ(m_channel->remote_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_data), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->remote_data), 40124);\n\n    EXPECT_EQ(m_channel->remote_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_control), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->remote_control), 40124);\n}\n\nTEST_F(UdpChannelTest, shouldParseIpv4AnyAddressAsInterfaceAddressForUnicast)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=0.0.0.0|endpoint=localhost:40124\"), 0) << aeron_errmsg();\n\n    EXPECT_EQ(m_channel->local_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_data), \"0.0.0.0\");\n    EXPECT_EQ(port(&m_channel->local_data), 0);\n\n    EXPECT_EQ(m_channel->local_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_control), \"0.0.0.0\");\n    EXPECT_EQ(port(&m_channel->local_control), 0);\n\n    EXPECT_EQ(m_channel->remote_data.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_data), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->remote_data), 40124);\n\n    EXPECT_EQ(m_channel->remote_control.ss_family, AF_INET);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_control), \"127.0.0.1\");\n    EXPECT_EQ(port(&m_channel->remote_control), 40124);\n}\n\nTEST_F(UdpChannelTest, shouldParseIpv6AnyAddressAsInterfaceAddressForUnicast)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=[::]|endpoint=[::1]:40124\"), 0) << aeron_errmsg();\n\n    EXPECT_EQ(m_channel->local_data.ss_family, AF_INET6);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_data), \"::\");\n    EXPECT_EQ(port(&m_channel->local_data), 0);\n\n    EXPECT_EQ(m_channel->local_control.ss_family, AF_INET6);\n    EXPECT_STREQ(inet_ntop(&m_channel->local_control), \"::\");\n    EXPECT_EQ(port(&m_channel->local_control), 0);\n\n    EXPECT_EQ(m_channel->remote_data.ss_family, AF_INET6);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_data), \"::1\");\n    EXPECT_EQ(port(&m_channel->remote_data), 40124);\n\n    EXPECT_EQ(m_channel->remote_control.ss_family, AF_INET6);\n    EXPECT_STREQ(inet_ntop(&m_channel->remote_control), \"::1\");\n    EXPECT_EQ(port(&m_channel->remote_control), 40124);\n}\n\nTEST_F(UdpChannelTest, shouldCanonicalizeIpv4ForUnicast)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?endpoint=192.168.0.1:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-0.0.0.0:0-192.168.0.1:40456\");\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=127.0.0.1|endpoint=192.168.0.1:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-127.0.0.1:0-192.168.0.1:40456\");\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=127.0.0.1:40455|endpoint=192.168.0.1:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-127.0.0.1:40455-192.168.0.1:40456\");\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=localhost|endpoint=localhost:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-127.0.0.1:0-localhost:40456\");\n}\n\nTEST_F(UdpChannelTest, shouldCanonicalizeIpv6ForUnicastWithMixedAddressTypes)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=127.0.0.1:40455|endpoint=[fe80::5246:5dff:fe73:df06]:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-127.0.0.1:40455-[fe80::5246:5dff:fe73:df06]:40456\");\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?endpoint=192.168.0.1:40456|interface=[::1]\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-[::1]:0-192.168.0.1:40456\");\n}\n\nTEST_F(UdpChannelTest, shouldNotDoubleFreeParsedUdpChannel)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=127.0.0.1:40455|endpoint=224.0.1.1:40456\"), 0) << aeron_errmsg();\n    ASSERT_NE(m_channel, nullptr);\n\n    ASSERT_EQ(parse_udp_channel(\"this should fail\"), -1) << aeron_errmsg();\n    ASSERT_EQ(parse_udp_channel(\"and this as well\"), -1) << aeron_errmsg();\n    ASSERT_EQ(m_channel, nullptr); // previous channel was removed\n}\n\nMATCHER_P(ReMatch, pattern, std::string(\"ReMatch(\").append(pattern).append(\")\"))\n{\n    return std::regex_match(arg, std::regex(pattern));\n}\n\nTEST_F(UdpChannelTest, shouldCanonicalizeForIpv4Multicast)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=127.0.0.1:40455|endpoint=224.0.1.1:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(\"UDP-127.0.0.1:40455-224.0.1.1:40456\", m_channel->canonical_form);\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=localhost|endpoint=224.0.1.1:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(\"UDP-127.0.0.1:0-224.0.1.1:40456\", m_channel->canonical_form);\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=127.0.0.1|endpoint=224.0.1.1:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(\"UDP-127.0.0.1:0-224.0.1.1:40456\", m_channel->canonical_form);\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=127.0.0.0:40455/29|endpoint=224.0.1.1:40456\"), 0) << aeron_errmsg();\n    EXPECT_THAT((char *)m_channel->canonical_form, ReMatch(\"UDP-127\\\\.0\\\\.0\\\\.[1-7]:40455-224\\\\.0\\\\.1\\\\.1:40456\"));\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=localhost/29|endpoint=224.0.1.1:40456\"), 0) << aeron_errmsg();\n    EXPECT_THAT((char *)m_channel->canonical_form, ReMatch(\"UDP-127\\\\.0\\\\.0\\\\.[1-7]:0-224\\\\.0\\\\.1\\\\.1:40456\"));\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=127.0.0.0/29|endpoint=224.0.1.1:40456\"), 0) << aeron_errmsg();\n    EXPECT_THAT((char *)m_channel->canonical_form, ReMatch(\"UDP-127\\\\.0\\\\.0\\\\.[1-7]:0-224\\\\.0\\\\.1\\\\.1:40456\"));\n}\n\nTEST_F(UdpChannelTest, shouldCanonicalizeForIpv6Multicast)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=[::1]|endpoint=[FF01::FD]:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-[::1]:0-[ff01::fd]:40456\");\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?interface=[::1]:54321/64|endpoint=[FF01::FD]:40456\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-[::1]:54321-[ff01::fd]:40456\");\n}\n\nTEST_F(UdpChannelTest, shouldUseTagInCanonicalFormIfWildcardsInUseIPv6)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?tags=1001|endpoint=[::1]:0\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-[::]:0-[::1]:0#1001\");\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?tags=1001|endpoint=[::1]:9999|control=[::1]:0\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-[::1]:0-[::1]:9999#1001\");\n}\n\nTEST_F(UdpChannelTest, shouldUseTagInCanonicalFormIfWildcardsInUseIPv4)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?tags=1001|endpoint=127.0.0.1:0\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-0.0.0.0:0-127.0.0.1:0#1001\");\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?tags=1001|endpoint=127.0.0.1:9999|control=127.0.0.1:0\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->canonical_form, \"UDP-127.0.0.1:0-127.0.0.1:9999#1001\");\n}\n\nTEST_F(UdpChannelTest, shouldUseUniqueIdInCanonicalFormIfWildcardsInUseIPv6)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?endpoint=[::1]:0\"), 0) << aeron_errmsg();\n    EXPECT_EQ(0u, std::string(m_channel->canonical_form).rfind(\"UDP-[::]:0-[::1]:0-\"));\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?endpoint=[::1]:9999|control=[::1]:0\"), 0) << aeron_errmsg();\n    EXPECT_EQ(0u, std::string(m_channel->canonical_form).rfind(\"UDP-[::1]:0-[::1]:9999-\"));\n}\n\nTEST_F(UdpChannelTest, shouldUseUniqueIdInCanonicalFormIfWildcardsInUseIPv4)\n{\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?endpoint=127.0.0.1:0\"), 0) << aeron_errmsg();\n    EXPECT_EQ(0u, std::string(m_channel->canonical_form).rfind(\"UDP-0.0.0.0:0-127.0.0.1:0-\"));\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?endpoint=127.0.0.1:9999|control=127.0.0.1:0\"), 0) << aeron_errmsg();\n    EXPECT_EQ(0u, std::string(m_channel->canonical_form).rfind(\"UDP-127.0.0.1:0-127.0.0.1:9999-\"));\n}\n\nTEST_F(UdpChannelTest, DISABLED_shouldResolveWithNameLookup)\n{\n    const char *config_param =\n        \"NAME_0,\" AERON_UDP_CHANNEL_ENDPOINT_KEY \",localhost:9001,localhost:9001|\"\n        \"NAME_1,\" AERON_UDP_CHANNEL_CONTROL_KEY \",localhost:9002,localhost:9002|\";\n\n    aeron_name_resolver_supplier_func_t csv_supplier_func = aeron_name_resolver_supplier_load(\n        AERON_NAME_RESOLVER_CSV_TABLE);\n    csv_supplier_func(&m_resolver, config_param, nullptr);\n\n    ASSERT_EQ(parse_udp_channel(\"aeron:udp?endpoint=NAME_0|control=NAME_1\"), 0) << aeron_errmsg();\n    EXPECT_STREQ(m_channel->uri.params.udp.endpoint, \"NAME_0\");\n    EXPECT_STREQ(m_channel->uri.params.udp.control, \"NAME_1\");\n}\n\nTEST_F(UdpChannelTest, shouldFormatIPv4Address)\n{\n    char buffer[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n    struct sockaddr_storage addr{};\n    auto *addr_in = reinterpret_cast<sockaddr_in *>(&addr);\n    inet_pton(AF_INET, \"192.168.10.1\", &addr_in->sin_addr);\n    addr_in->sin_family = AF_INET;\n    addr_in->sin_port = htons(UINT16_C(65535));\n\n    aeron_format_source_identity(buffer, sizeof(buffer), &addr);\n\n    ASSERT_STREQ(\"192.168.10.1:65535\", buffer);\n}\n\nTEST_F(UdpChannelTest, shouldFormatIPv6Address)\n{\n    char buffer[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n    struct sockaddr_storage addr{};\n    auto *addr_in = reinterpret_cast<sockaddr_in6 *>(&addr);\n    inet_pton(AF_INET6, \"::1\", &addr_in->sin6_addr);\n    addr_in->sin6_family = AF_INET6;\n    addr_in->sin6_port = htons(UINT16_C(65535));\n\n    aeron_format_source_identity(buffer, sizeof(buffer), &addr);\n\n    ASSERT_STREQ(\"[::1]:65535\", buffer);\n}\n\nTEST_F(UdpChannelTest, shouldHandleMaxLengthIPv6)\n{\n    char buffer[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n    struct sockaddr_storage addr{};\n    auto *addr_in = reinterpret_cast<sockaddr_in6 *>(&addr);\n    inet_pton(AF_INET6, \"ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255\", &addr_in->sin6_addr);\n    addr_in->sin6_family = AF_INET6;\n    addr_in->sin6_port = htons(UINT16_C(65535));\n\n    aeron_format_source_identity(buffer, sizeof(buffer), &addr);\n\n    ASSERT_STREQ(\"[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535\", buffer);\n}\n\nTEST_F(UdpChannelTest, shouldHandleTooSmallBuffer)\n{\n    char buffer[AERON_NETUTIL_FORMATTED_MAX_LENGTH];\n    struct sockaddr_storage addr{};\n    auto *addr_in = reinterpret_cast<sockaddr_in6 *>(&addr);\n    inet_pton(AF_INET6, \"ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255\", &addr_in->sin6_addr);\n    addr_in->sin6_family = AF_INET6;\n    addr_in->sin6_port = UINT16_C(65535);\n\n    ASSERT_LE(aeron_format_source_identity(buffer, AERON_NETUTIL_FORMATTED_MAX_LENGTH - 1, &addr), 0);\n}\n\nTEST_F(UdpChannelTest, shouldParseSocketBufferParameters)\n{\n    const char *uri = \"aeron:udp?interface=localhost|endpoint=224.10.9.9:40124|so-sndbuf=8k|so-rcvbuf=4k\";\n    ASSERT_EQ(parse_udp_channel(uri), 0) << aeron_errmsg();\n\n    ASSERT_EQ(8192u, m_channel->socket_sndbuf_length);\n    ASSERT_EQ(4096u, m_channel->socket_rcvbuf_length);\n}\n\nTEST_F(UdpChannelTest, shouldParseReceiverWindow)\n{\n    const char *uri = \"aeron:udp?interface=localhost|endpoint=224.10.9.9:40124|rcv-wnd=8k\";\n    ASSERT_EQ(parse_udp_channel(uri), 0) << aeron_errmsg();\n\n    ASSERT_EQ(8192u, m_channel->receiver_window_length);\n}\n\nTEST_F(UdpChannelTest, shouldParseTimestampOffsets)\n{\n    const char *uri = \"aeron:udp?endpoint=localhost:0|media-rcv-ts-offset=reserved|channel-snd-ts-offset=0|channel-rcv-ts-offset=8\";\n    ASSERT_EQ(0, parse_udp_channel(uri)) << aeron_errmsg();\n\n    EXPECT_EQ(AERON_UDP_CHANNEL_RESERVED_VALUE_OFFSET, m_channel->media_rcv_timestamp_offset);\n    EXPECT_EQ(0, m_channel->channel_snd_timestamp_offset);\n    EXPECT_EQ(8, m_channel->channel_rcv_timestamp_offset);\n}\n\nTEST_F(UdpChannelTest, shouldDefaultTimestampOffsetsToMinusOne)\n{\n    const char *uri = \"aeron:udp?endpoint=localhost:0\";\n    ASSERT_EQ(0, parse_udp_channel(uri)) << aeron_errmsg();\n\n    EXPECT_EQ(AERON_NULL_VALUE, m_channel->media_rcv_timestamp_offset);\n    EXPECT_EQ(AERON_NULL_VALUE, m_channel->channel_rcv_timestamp_offset);\n    EXPECT_EQ(AERON_NULL_VALUE, m_channel->channel_snd_timestamp_offset);\n}\n\nTEST_F(UdpChannelTest, shouldValidateOffsetsDoNotOverlap)\n{\n    EXPECT_EQ(-1, parse_udp_channel(\"aeron:udp?endpoint=localhost:0|media-rcv-ts-offset=2|channel-rcv-ts-offset=9\")) << aeron_errmsg();\n    assert_error_message_contains(\"media-rcv-ts-offset and channel-rcv-ts-offset overlap\");\n\n    EXPECT_EQ(-1, parse_udp_channel(\"aeron:udp?endpoint=localhost:0|media-rcv-ts-offset=14|channel-snd-ts-offset=19\")) << aeron_errmsg();\n    assert_error_message_contains(\"media-rcv-ts-offset and channel-snd-ts-offset overlap\");\n\n    EXPECT_EQ(-1, parse_udp_channel(\"aeron:udp?endpoint=localhost:0|media-rcv-ts-offset=reserved|channel-snd-ts-offset=3|channel-rcv-ts-offset=10\"));\n    assert_error_message_contains(\"channel-rcv-ts-offset and channel-snd-ts-offset overlap\");\n}\n\nTEST_P(UdpChannelNamesParameterisedTest, DISABLED_shouldBeValid)\n{\n    const char *endpoint_name = std::get<0>(GetParam());\n    const char *endpoint_address = std::get<1>(GetParam());\n    const char *control_name = std::get<2>(GetParam());\n    const char *control_address = std::get<3>(GetParam());\n    const char *canonical_form = std::get<4>(GetParam());\n    std::stringstream params_ss;\n    std::stringstream uri_ss;\n\n    if (nullptr != endpoint_name)\n    {\n        params_ss << endpoint_name << ',' <<\n            AERON_UDP_CHANNEL_ENDPOINT_KEY << ',' <<\n            endpoint_address << \":40124\" << ',' <<\n            endpoint_address << \":40124\" << '|';\n    }\n\n    if (nullptr != control_name)\n    {\n        params_ss << control_name << ',' <<\n            AERON_UDP_CHANNEL_CONTROL_KEY << ',' <<\n            control_address << \":40124\" << ',' <<\n            control_address << \":40124\" << '|';\n    }\n\n    const std::string params_string = params_ss.str();\n    const char *config_params = params_string.c_str();\n\n    aeron_name_resolver_supplier_func_t csv_supplier_func = aeron_name_resolver_supplier_load(\n        AERON_NAME_RESOLVER_CSV_TABLE);\n    csv_supplier_func(&m_resolver, config_params, nullptr);\n\n    uri_ss << \"aeron:udp?interface=localhost\";\n\n    if (nullptr != endpoint_name)\n    {\n        uri_ss << \"|endpoint=\" << endpoint_name;\n    }\n\n    if (nullptr != control_address)\n    {\n        uri_ss << \"|control=\" << control_name;\n    }\n\n    uri_ss << '\\0';\n\n    const std::string uri_string = uri_ss.str();\n    const char *uri = uri_string.c_str();\n\n    ASSERT_EQ(parse_udp_channel(uri), 0) << aeron_errmsg();\n    ASSERT_STREQ(canonical_form, m_channel->canonical_form);\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    UdpChannelNameTests,\n    UdpChannelNamesParameterisedTest,\n    testing::Values(\n        std::make_tuple(\n            \"NAME_ENDPOINT\", \"192.168.1.1\", (const char *)NULL, (const char *)NULL, \"UDP-127.0.0.1:0-NAME_ENDPOINT\"),\n        std::make_tuple(\n            \"NAME_ENDPOINT\", \"224.0.1.1\", (const char *)NULL, (const char *)NULL, \"UDP-127.0.0.1:0-224.0.1.1:40124\"),\n        std::make_tuple(\n            \"NAME_ENDPOINT\", \"192.168.1.1\", \"NAME_CONTROL\", \"192.168.1.2\", \"UDP-NAME_CONTROL-NAME_ENDPOINT\"),\n        std::make_tuple(\n            \"NAME_ENDPOINT\", \"224.0.1.1\", \"NAME_CONTROL\", \"127.0.0.1\", \"UDP-127.0.0.1:0-224.0.1.1:40124\"),\n        std::make_tuple(\n            \"192.168.1.1:40124\", \"192.168.1.1\", \"NAME_CONTROL\", \"192.168.1.2\", \"UDP-NAME_CONTROL-192.168.1.1:40124\"),\n        std::make_tuple(\n            \"192.168.1.1:40124\", \"192.168.1.1\", \"192.168.1.2:40192\", \"192.168.1.2\",\n            \"UDP-192.168.1.2:40192-192.168.1.1:40124\"),\n        std::make_tuple(\n            \"[fe80::5246:5dff:fe73:df06]:40456\", \"[fe80::5246:5dff:fe73:df06]\", (const char *)NULL, (const char *)NULL,\n            \"UDP-127.0.0.1:0-[fe80::5246:5dff:fe73:df06]:40456\")));\n\nTEST_P(UdpChannelEqualityParameterisedTest, shouldMatch)\n{\n    const bool should_match = std::get<0>(GetParam());\n    const char *uri_1 = std::get<1>(GetParam());\n    const char *uri_2 = std::get<2>(GetParam());\n\n    aeron_udp_channel_t *channel_1 = nullptr;\n    aeron_udp_channel_t *channel_2 = nullptr;\n\n    aeron_name_resolver_t resolver;\n    aeron_default_name_resolver_supplier(&resolver, nullptr, nullptr);\n\n    if (nullptr != uri_1)\n    {\n        ASSERT_LE(0, aeron_udp_channel_parse(strlen(uri_1), uri_1, &resolver, &channel_1, false)) << uri_1;\n    }\n\n    if (nullptr != uri_2)\n    {\n        ASSERT_LE(0, aeron_udp_channel_parse(strlen(uri_2), uri_2, &resolver, &channel_2, false)) << uri_2;\n    }\n\n    EXPECT_EQ(should_match, aeron_udp_channel_equals(channel_1, channel_2))\n        << uri_1 << \"(\" << (nullptr != channel_1 ? channel_1->canonical_form : \"null\") << \")\"\n        << \" vs \"\n        << uri_2 << \"(\" << (nullptr != channel_2 ? channel_2->canonical_form : \"null\") << \")\";\n\n    aeron_udp_channel_delete(channel_1);\n    aeron_udp_channel_delete(channel_2);\n}\n\nINSTANTIATE_TEST_SUITE_P(\n    UdpChannelEqualityTest,\n    UdpChannelEqualityParameterisedTest,\n    testing::Values(\n        std::make_tuple(true, \"aeron:udp?endpoint=localhost:9090\", \"aeron:udp?endpoint=localhost:9090\"),\n        std::make_tuple(true, \"aeron:udp?endpoint=localhost:9090|session-id=12\", \"aeron:udp?endpoint=localhost:9090\"),\n        std::make_tuple(\n            true,\n            \"aeron:udp?endpoint=localhost:9090|session-id=12\",\n            \"aeron:udp?endpoint=localhost:9090|session-id=13\"),\n        std::make_tuple(\n            true,\n            \"aeron:udp?endpoint=localhost:9090|interface=127.0.0.1:9090\",\n            \"aeron:udp?endpoint=localhost:9090|interface=127.0.0.1:9090\"),\n        std::make_tuple(\n            false,\n            \"aeron:udp?endpoint=localhost:9090|interface=127.0.0.1:9090\",\n            \"aeron:udp?endpoint=localhost:9090|interface=127.0.0.1:9091\"),\n        std::make_tuple(false, \"aeron:udp?endpoint=localhost:9090\", \"aeron:udp?endpoint=127.0.0.1:9090\"),\n        std::make_tuple(false, (const char *)NULL, \"aeron:udp?endpoint=localhost:9091\"),\n        std::make_tuple(true, (const char *)NULL, (const char *)NULL),\n        std::make_tuple(true, \"aeron:udp?endpoint=localhost:9090\", \"aeron:udp?endpoint=localhost:9090\")));\n"
  },
  {
    "path": "aeron-driver/src/test/c/aeronmd_signal_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"aeronc.h\"\n\nextern \"C\"\n{\nint argc;\nchar **argv;\n}\n\n#include <gtest/gtest.h>\n#include <random>\n#include <sys/syscall.h>\n#include <unistd.h>\n#include <sys/wait.h>\n\nclass AeronmdSignalTest : public testing::Test\n{\n};\n\nTEST_F(AeronmdSignalTest, shouldSupportSigTerm)\n{\n#if !defined(__linux__)\n    GTEST_SKIP();\n#endif\n\n    if (argc < 2)\n    {\n        FAIL() << \"aeronmd path is not set\";\n    }\n\n    FILE *aeronmdOutput = popen(argv[1], \"r\");\n    ASSERT_NE(nullptr, aeronmdOutput);\n\n    aeron_context_t *context;\n    ASSERT_EQ(0, aeron_context_init(&context));\n\n    aeron_t *aeron;\n    ASSERT_EQ(0, aeron_init(&aeron, context));\n\n    aeron_close(aeron);\n    aeron_context_close(context);\n\n    FILE *psgrepOutput = popen(\"/usr/bin/pgrep -xn aeronmd\", \"r\");\n\n    char buf[1024] = {};\n    if (nullptr == fgets(buf, sizeof(buf) - 1, psgrepOutput))\n    {\n        FAIL() << \"pgrep does not exist\";\n    }\n\n    int pid = atoi(buf);\n    ASSERT_LT(0, pid) << \"aeronmd is not running\";\n\n    kill(pid, SIGTERM);\n\n    waitpid(pid, nullptr, WSTOPPED);\n\n    std::stringstream ss;\n    while (nullptr != fgets(buf, sizeof(buf) - 1, aeronmdOutput))\n    {\n        ss << std::string(buf) << std::endl;\n    }\n\n    ASSERT_NE(std::string::npos, ss.str().find(\"Shutting down driver\"));\n\n    pclose(aeronmdOutput);\n}\n\nGTEST_API_ int main(int t_argc, char **t_argv){\n\n    argc = t_argc;\n    argv = t_argv;\n\n    printf(\"Initializing gtest \\n\");\n    ::testing::InitGoogleTest(&argc, argv);\n\n    return RUN_ALL_TESTS();\n}"
  },
  {
    "path": "aeron-driver/src/test/c/agent/aeron_driver_agent_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <functional>\n\n#include <gtest/gtest.h>\n#include <cinttypes>\n\n#include \"aeron_driver_version.h\"\n\nextern \"C\"\n{\n#include \"aeron_driver_context.h\"\n#include \"agent/aeron_driver_agent.h\"\n}\n\nclass DriverAgentTest : public testing::Test\n{\npublic:\n    DriverAgentTest()\n    {\n        if (aeron_driver_context_init(&m_context) < 0)\n        {\n            throw std::runtime_error(\"could not init context: \" + std::string(aeron_errmsg()));\n        }\n    }\n\n    ~DriverAgentTest() override\n    {\n        if (0 != aeron_driver_context_close(m_context))\n        {\n            fprintf(stderr, \"ERROR: driver context close (%d) %s\\n\", aeron_errcode(), aeron_errmsg());\n        }\n\n        aeron_driver_agent_logging_ring_buffer_free();\n        aeron_driver_agent_logging_events_free();\n    }\n\nprotected:\n    aeron_driver_context_t *m_context = nullptr;\n\n    static void assert_all_events_disabled()\n    {\n        for (size_t i = 0; i < aeron_driver_agent_max_event_count(); i++)\n        {\n            auto event_id = static_cast<aeron_driver_agent_event_t>(i);\n            EXPECT_FALSE(aeron_driver_agent_is_event_enabled(event_id));\n        }\n    }\n\n    static void assert_all_events_enabled()\n    {\n        for (size_t i = 0; i < aeron_driver_agent_max_event_count(); i++)\n        {\n            auto event_id = static_cast<aeron_driver_agent_event_t>(i);\n            auto event_name = aeron_driver_agent_event_name(event_id);\n            bool expected = 0 != strncmp(\n                AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME, event_name, strlen(AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME) + 1);\n            EXPECT_EQ(expected, aeron_driver_agent_is_event_enabled(event_id)) << event_name;\n        }\n\n        EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_UNKNOWN_EVENT));\n    }\n\n    static void assert_admin_events_enabled(const bool is_enabled)\n    {\n        for (size_t i = 0; i < aeron_driver_agent_max_event_count(); i++)\n        {\n            auto event_id = static_cast<aeron_driver_agent_event_t>(i);\n            if (AERON_DRIVER_EVENT_FRAME_IN != event_id &&\n                AERON_DRIVER_EVENT_FRAME_OUT != event_id &&\n                AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR != event_id &&\n                AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT != event_id)\n            {\n                auto event_name = aeron_driver_agent_event_name(event_id);\n                if (0 != strncmp(\n                    AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME,\n                    event_name,\n                    strlen(AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME) + 1))\n                {\n                    EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(event_id)) << event_name;\n                }\n            }\n        }\n    }\n\n    static void assert_cmd_id_events_enabled(const bool is_enabled)\n    {\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_ADD_PUBLICATION));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_REMOVE_PUBLICATION));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_ADD_SUBSCRIPTION));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_REMOVE_SUBSCRIPTION));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_KEEPALIVE_CLIENT));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_ADD_DESTINATION));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_REMOVE_DESTINATION));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_ADD_EXCLUSIVE_PUBLICATION));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_ADD_COUNTER));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_REMOVE_COUNTER));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_CLIENT_CLOSE));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_ADD_RCV_DESTINATION));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_REMOVE_RCV_DESTINATION));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_TERMINATE_DRIVER));\n    }\n\n    static void assert_cmd_out_events_enabled(const bool is_enabled)\n    {\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_PUBLICATION_READY));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_AVAILABLE_IMAGE));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_ON_OPERATION_SUCCESS));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_ON_UNAVAILABLE_IMAGE));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_EXCLUSIVE_PUBLICATION_READY));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_ERROR));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_SUBSCRIPTION_READY));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_COUNTER_READY));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_ON_UNAVAILABLE_COUNTER));\n        EXPECT_EQ(is_enabled, aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_ON_CLIENT_TIMEOUT));\n    }\n};\n\nTEST_F(DriverAgentTest, allLoggingEventsShouldHaveUniqueNames)\n{\n    std::set<std::string> names;\n    std::string unknown_name = std::string(AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME);\n\n    for (size_t i = 0; i < aeron_driver_agent_max_event_count(); i++)\n    {\n        auto event_id = static_cast<aeron_driver_agent_event_t>(i);\n        auto event_name = std::string(aeron_driver_agent_event_name(event_id));\n        if (0 == i || (i > 8 && i < 12) || (i > 17 && i < 23) || (i > 26 && i < 30)) // gaps\n        {\n            EXPECT_EQ(unknown_name, event_name);\n        }\n        else\n        {\n            EXPECT_NE(unknown_name, event_name);\n            auto result = names.insert(event_name);\n            EXPECT_TRUE(result.second) << event_name << \" is duplicated\";\n        }\n    }\n\n    EXPECT_EQ(unknown_name, std::string(aeron_driver_agent_event_name(AERON_DRIVER_EVENT_UNKNOWN_EVENT)));\n}\n\nTEST_F(DriverAgentTest, shouldHaveAllEventsDisabledByDefault)\n{\n    assert_all_events_disabled();\n}\n\nTEST_F(DriverAgentTest, shouldEnabledAllLoggingEvents)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(AERON_DRIVER_AGENT_ALL_EVENTS, nullptr));\n\n    assert_all_events_enabled();\n}\n\nTEST_F(DriverAgentTest, shouldEnabledAdminLoggingEvents)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(AERON_DRIVER_AGENT_ADMIN_EVENTS, nullptr));\n\n    assert_admin_events_enabled(true);\n\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_IN));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_OUT));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(static_cast<aeron_driver_agent_event_t>(0)));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(static_cast<aeron_driver_agent_event_t>(9)));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(static_cast<aeron_driver_agent_event_t>(27)));\n}\n\nTEST_F(DriverAgentTest, shouldEnableEventByName)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"CMD_OUT_AVAILABLE_IMAGE\", nullptr));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_AVAILABLE_IMAGE));\n}\n\nTEST_F(DriverAgentTest, shouldEnableEventByValue)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"3\", nullptr));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_ADD_PUBLICATION));\n}\n\nTEST_F(DriverAgentTest, shouldNotEnableEventByNamePrefix)\n{\n    EXPECT_FALSE(aeron_driver_agent_logging_events_init(\"CMD_OUT_AVAILABLE_IMAGEx\", nullptr));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_AVAILABLE_IMAGE));\n}\n\nTEST_F(DriverAgentTest, shouldNotEnableEventByNameSuffix)\n{\n    EXPECT_FALSE(aeron_driver_agent_logging_events_init(\"xREMOVE_SUBSCRIPTION_CLEANUP\", nullptr));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_REMOVE_SUBSCRIPTION_CLEANUP));\n}\n\nTEST_F(DriverAgentTest, shouldNotEnableUnknownEventByReservedName)\n{\n    EXPECT_FALSE(aeron_driver_agent_logging_events_init(AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME, nullptr));\n}\n\nTEST_F(DriverAgentTest, shouldNotEnableUnknownEventByName)\n{\n    EXPECT_FALSE(aeron_driver_agent_logging_events_init(\"What is this event?\", nullptr));\n}\n\nTEST_F(DriverAgentTest, shouldNotEnableUnknownEventByValue)\n{\n    EXPECT_FALSE(aeron_driver_agent_logging_events_init(\"9\", nullptr));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(static_cast<aeron_driver_agent_event_t>(9)));\n}\n\nTEST_F(DriverAgentTest, shouldNotEnableUnknownEventByReservedValue)\n{\n    EXPECT_FALSE(aeron_driver_agent_logging_events_init(\"-1\", nullptr));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_UNKNOWN_EVENT));\n}\n\nTEST_F(DriverAgentTest, shouldEnableMultipleEventsSplitByComma)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\n        \"CMD_IN_REMOVE_COUNTER,33,NAME_RESOLUTION_NEIGHBOR_ADDED,CMD_OUT_ERROR,FRAME_OUT,\", nullptr));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_IN_REMOVE_COUNTER));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_EXCLUSIVE_PUBLICATION_READY));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_OUT));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_ADDED));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_ERROR));\n}\n\nTEST_F(DriverAgentTest, shouldDisableMultipleEventsSplitByComma)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\n        \"all\", \"CMD_IN_REMOVE_COUNTER,33,NAME_RESOLUTION_NEIGHBOR_ADDED,CMD_OUT_ERROR,FRAME_OUT,\"));\n\n    for (size_t i = 0; i < aeron_driver_agent_max_event_count(); i++)\n    {\n        auto event_id = static_cast<aeron_driver_agent_event_t>(i);\n        bool expected =\n            event_id != AERON_DRIVER_EVENT_CMD_IN_REMOVE_COUNTER &&\n            event_id != AERON_DRIVER_EVENT_CMD_OUT_EXCLUSIVE_PUBLICATION_READY &&\n            event_id != AERON_DRIVER_EVENT_FRAME_OUT &&\n            event_id != AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_ADDED &&\n            event_id != AERON_DRIVER_EVENT_CMD_OUT_ERROR;\n\n        auto event_name = aeron_driver_agent_event_name(event_id);\n        expected &= 0 != strncmp(\n            AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME, event_name, strlen(AERON_DRIVER_AGENT_EVENT_UNKNOWN_NAME) + 1);\n\n        EXPECT_EQ(expected, aeron_driver_agent_is_event_enabled(event_id)) << event_name << \" is set incorrectly\";\n    }\n}\n\nTEST_F(DriverAgentTest, shouldNotInitIfDisabledEventsAreIncorrect)\n{\n    EXPECT_FALSE(aeron_driver_agent_logging_events_init(\"all\", \"NOT_A_VALID_EVENT\"));\n}\n\nTEST_F(DriverAgentTest, shouldAllowSpecialEventNamesInTheList)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"NAME_RESOLUTION_NEIGHBOR_REMOVED,admin,FRAME_IN\", nullptr));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_REMOVED));\n    assert_admin_events_enabled(true);\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_IN));\n}\n\nTEST_F(DriverAgentTest, shouldEnableAllEventsUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0xFFFF\", nullptr));\n\n    assert_all_events_enabled();\n}\n\nTEST_F(DriverAgentTest, shouldEnableAllEventsUsingMaskLowerCase)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0xffff\", nullptr));\n\n    assert_all_events_enabled();\n}\n\nTEST_F(DriverAgentTest, shouldEnableAllEventsUsingMaskMixedCase)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0xfFFf\", nullptr));\n\n    assert_all_events_enabled();\n}\n\nTEST_F(DriverAgentTest, shouldEnableAllCmdInEventsUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0x1\", nullptr));\n\n    assert_cmd_id_events_enabled(true);\n}\n\nTEST_F(DriverAgentTest, shouldDisableAllCmdInEventsUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"all\", \"0x1\"));\n\n    assert_cmd_id_events_enabled(false);\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_SEND_CHANNEL_CLOSE));\n}\n\nTEST_F(DriverAgentTest, shouldEnableAllCmdOutEventsUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0x2\", nullptr));\n\n    assert_cmd_out_events_enabled(true);\n}\n\nTEST_F(DriverAgentTest, shouldDisableAllCmdOutEventsUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"all\", \"0x2\"));\n\n    assert_cmd_out_events_enabled(false);\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_SEND_CHANNEL_CLOSE));\n}\n\nTEST_F(DriverAgentTest, shouldEnableFrameInEventUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0x4\", nullptr));\n\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_IN));\n}\n\nTEST_F(DriverAgentTest, shouldEnableFrameOutEventUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0x8\", nullptr));\n\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_OUT));\n}\n\nTEST_F(DriverAgentTest, shouldEnableFrameOutEventUsingMaskSecondValue)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0x10\", nullptr));\n\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_OUT));\n}\n\nTEST_F(DriverAgentTest, shouldEnableUntetheredStateChangeEventUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0x80\", nullptr));\n\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_UNTETHERED_SUBSCRIPTION_STATE_CHANGE));\n}\n\nTEST_F(DriverAgentTest, shouldEnableDynamicDissectorEventUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0x100\", nullptr));\n\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT));\n}\n\nTEST_F(DriverAgentTest, shouldEnableMultipleEventsUsingMask)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0x8F\", nullptr));\n\n    assert_cmd_id_events_enabled(true);\n    assert_cmd_out_events_enabled(true);\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_IN));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_OUT));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_UNTETHERED_SUBSCRIPTION_STATE_CHANGE));\n}\n\nTEST_F(DriverAgentTest, shouldStopWhenMaskIsDetected)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"0x4,CMD_OUT_ON_UNAVAILABLE_COUNTER\", nullptr));\n\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_FRAME_IN));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_CMD_OUT_ON_UNAVAILABLE_COUNTER));\n}\n\nTEST_F(DriverAgentTest, shouldNotEnableAnyEventsIfInvalidMask)\n{\n    EXPECT_FALSE(aeron_driver_agent_logging_events_init(\"0x200,REMOVE_IMAGE_CLEANUP,FRAME_IN\", nullptr));\n\n    assert_all_events_disabled();\n}\n\nTEST_F(DriverAgentTest, shouldCleanUpProperlyIfParsingOfDisabledEventsFails)\n{\n    char disabled_events[129];\n    disabled_events[128] = '\\0';\n    for (int i = 0, len = sizeof(disabled_events) - 1; i < len; i += 2)\n    {\n        disabled_events[i] = 'x';\n        disabled_events[i + 1] = ',';\n    }\n\n    EXPECT_FALSE(aeron_driver_agent_logging_events_init(\"all\", disabled_events));\n\n    assert_all_events_disabled();\n}\n\nTEST_F(DriverAgentTest, shouldDissectLogHeader)\n{\n    int64_t time_ns = 3274398573945794359LL;\n    auto id = AERON_DRIVER_EVENT_CMD_OUT_EXCLUSIVE_PUBLICATION_READY;\n    auto capture_length = 59;\n    auto message_length = 256;\n\n    auto log_header = aeron_driver_agent_dissect_log_header(time_ns, id, capture_length, message_length);\n\n    EXPECT_EQ(\n        std::string(\"[3274398573.945794359] DRIVER: CMD_OUT_EXCLUSIVE_PUBLICATION_READY [59/256]\"),\n        std::string(log_header));\n}\n\nTEST_F(DriverAgentTest, shouldInitializeUntetheredStateChangeInterceptor)\n{\n    aeron_untethered_subscription_state_change_func_t func = m_context->log.untethered_subscription_on_state_change;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"UNTETHERED_SUBSCRIPTION_STATE_CHANGE\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.untethered_subscription_on_state_change, func);\n}\n\nTEST_F(DriverAgentTest, shouldKeepOriginalUntetheredStateChangeFunctionIfEventNotEnabled)\n{\n    aeron_untethered_subscription_state_change_func_t func = m_context->log.untethered_subscription_on_state_change;\n\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(m_context->log.untethered_subscription_on_state_change, func);\n}\n\nTEST_F(DriverAgentTest, shouldLogUntetheredSubscriptionStateChange)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    aeron_subscription_tether_state_t old_state = AERON_SUBSCRIPTION_TETHER_RESTING;\n    aeron_subscription_tether_state_t new_state = AERON_SUBSCRIPTION_TETHER_ACTIVE;\n    int64_t now_ns = -432482364273648LL;\n    int32_t stream_id = 777;\n    int32_t session_id = 21;\n    int64_t subscription_id = 56;\n    aeron_tetherable_position_t tetherable_position = {};\n    tetherable_position.state = AERON_SUBSCRIPTION_TETHER_LINGER;\n    tetherable_position.subscription_registration_id = subscription_id;\n\n    aeron_driver_agent_untethered_subscription_state_change(\n        &tetherable_position,\n        now_ns,\n        old_state,\n        new_state,\n        stream_id,\n        session_id);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_UNTETHERED_SUBSCRIPTION_STATE_CHANGE);\n\n            auto *data = (aeron_driver_agent_untethered_subscription_state_change_log_header_t *)msg;\n            EXPECT_EQ(data->new_state, AERON_SUBSCRIPTION_TETHER_ACTIVE);\n            EXPECT_EQ(data->old_state, AERON_SUBSCRIPTION_TETHER_RESTING);\n            EXPECT_EQ(data->subscription_id, 56);\n            EXPECT_EQ(data->stream_id, 777);\n            EXPECT_EQ(data->session_id, 21);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogConductorToDriverCommand)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n    ASSERT_TRUE(aeron_driver_agent_logging_events_init(\"CMD_IN_ADD_SUBSCRIPTION\", nullptr));\n\n    const size_t length = sizeof(aeron_publication_command_t) + 4;\n    char buffer[AERON_MAX_PATH] = {};\n    auto *command = (aeron_publication_command_t *)buffer;\n\n    command->correlated.correlation_id = 11;\n    command->correlated.client_id = 42;\n    command->stream_id = 7;\n    command->channel_length = 4;\n    memcpy(buffer + sizeof(aeron_publication_command_t), \"test\", strlen(\"test\"));\n\n    aeron_driver_agent_conductor_to_driver_interceptor(AERON_COMMAND_ADD_SUBSCRIPTION, command, length, nullptr);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_CMD_IN_ADD_SUBSCRIPTION);\n\n            char *buffer = (char *)msg;\n            auto *hdr = (aeron_driver_agent_cmd_log_header_t *)buffer;\n            EXPECT_EQ(hdr->cmd_id, AERON_COMMAND_ADD_SUBSCRIPTION);\n            EXPECT_NE(hdr->time_ns, 0);\n\n            auto *payload = (aeron_publication_command_t *)(buffer + sizeof(aeron_driver_agent_cmd_log_header_t));\n            EXPECT_EQ(payload->correlated.correlation_id, 11);\n            EXPECT_EQ(payload->correlated.client_id, 42);\n            EXPECT_EQ(payload->stream_id, 7);\n            EXPECT_EQ(payload->channel_length, 4);\n            EXPECT_EQ(\n                strcmp(\"test\", buffer + sizeof(aeron_driver_agent_cmd_log_header_t) + sizeof(aeron_publication_command_t)),\n                0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogConductorToDriverCommandBigMessage)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n    ASSERT_TRUE(aeron_driver_agent_logging_events_init(\"CMD_IN_ADD_COUNTER\", nullptr));\n\n    const size_t length = AERON_MAX_FRAME_LENGTH * 5;\n    char buffer[length] = {};\n    auto *command = (aeron_publication_command_t *)buffer;\n\n    command->correlated.correlation_id = 118;\n    command->correlated.client_id = 9;\n    command->stream_id = 42;\n    command->channel_length = length - sizeof(aeron_publication_command_t);\n    memset(buffer + sizeof(aeron_publication_command_t), 'a', 1);\n    memset(buffer + length - 1, 'z', 1);\n\n    aeron_driver_agent_conductor_to_driver_interceptor(AERON_COMMAND_ADD_COUNTER, command, length, nullptr);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_CMD_IN_ADD_COUNTER);\n\n            char *buffer = (char *)msg;\n            auto *hdr = (aeron_driver_agent_cmd_log_header_t *)buffer;\n            EXPECT_EQ(hdr->cmd_id, AERON_COMMAND_ADD_COUNTER);\n            EXPECT_NE(hdr->time_ns, 0);\n\n            const size_t payload_length = AERON_MAX_FRAME_LENGTH * 5;\n            auto *payload = (aeron_publication_command_t *)(buffer + sizeof(aeron_driver_agent_cmd_log_header_t));\n            EXPECT_EQ(payload->correlated.correlation_id, 118);\n            EXPECT_EQ(payload->correlated.client_id, 9);\n            EXPECT_EQ(payload->stream_id, 42);\n            EXPECT_EQ(payload->channel_length, (int32_t)(payload_length - sizeof(aeron_publication_command_t)));\n            EXPECT_EQ(\n                memcmp(\"a\", buffer + sizeof(aeron_driver_agent_cmd_log_header_t) + sizeof(aeron_publication_command_t), 1),\n                0);\n            EXPECT_EQ(memcmp(\"z\", buffer + length - 1, 1), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogConductorToClientCommand)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n    ASSERT_TRUE(aeron_driver_agent_logging_events_init(\"CMD_OUT_ON_OPERATION_SUCCESS\", nullptr));\n\n    const size_t length = sizeof(aeron_publication_command_t) + 4;\n    char buffer[AERON_MAX_PATH] = {};\n    auto *command = (aeron_publication_command_t *)buffer;\n\n    command->correlated.correlation_id = 11;\n    command->correlated.client_id = 42;\n    command->stream_id = 7;\n    command->channel_length = 4;\n    memcpy(buffer + sizeof(aeron_publication_command_t), \"test\", strlen(\"test\"));\n\n    aeron_driver_agent_conductor_to_client_interceptor(nullptr, AERON_RESPONSE_ON_OPERATION_SUCCESS, command, length);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_CMD_OUT_ON_OPERATION_SUCCESS);\n\n            char *buffer = (char *)msg;\n            auto *hdr = (aeron_driver_agent_cmd_log_header_t *)buffer;\n            EXPECT_EQ(hdr->cmd_id, AERON_RESPONSE_ON_OPERATION_SUCCESS);\n            EXPECT_NE(hdr->time_ns, 0);\n\n            auto *payload = (aeron_publication_command_t *)(buffer + sizeof(aeron_driver_agent_cmd_log_header_t));\n            EXPECT_EQ(payload->correlated.correlation_id, 11);\n            EXPECT_EQ(payload->correlated.client_id, 42);\n            EXPECT_EQ(payload->stream_id, 7);\n            EXPECT_EQ(payload->channel_length, 4);\n            EXPECT_EQ(\n                strcmp(\"test\", buffer + sizeof(aeron_driver_agent_cmd_log_header_t) + sizeof(aeron_publication_command_t)),\n                0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogConductorToClientCommandBigMessage)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n    ASSERT_TRUE(aeron_driver_agent_logging_events_init(\"CMD_OUT_EXCLUSIVE_PUBLICATION_READY\", nullptr));\n\n    const size_t length = AERON_MAX_FRAME_LENGTH * 15;\n    char buffer[length] = {};\n    auto *command = (aeron_subscription_command_t *)buffer;\n\n    command->correlated.correlation_id = 8;\n    command->correlated.client_id = 91;\n    command->stream_id = 142;\n    command->channel_length = length - sizeof(aeron_subscription_command_t);\n    memset(buffer + sizeof(aeron_subscription_command_t), 'a', 1);\n    memset(buffer + length - 1, 'z', 1);\n\n    aeron_driver_agent_conductor_to_client_interceptor(nullptr, AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY, command, length);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_CMD_OUT_EXCLUSIVE_PUBLICATION_READY);\n\n            char *buffer = (char *)msg;\n            auto *hdr = (aeron_driver_agent_cmd_log_header_t *)buffer;\n            EXPECT_EQ(hdr->cmd_id, AERON_RESPONSE_ON_EXCLUSIVE_PUBLICATION_READY);\n            EXPECT_NE(hdr->time_ns, 0);\n\n            const size_t payload_length = AERON_MAX_FRAME_LENGTH * 15;\n            auto *payload = (aeron_subscription_command_t *)(buffer + sizeof(aeron_driver_agent_cmd_log_header_t));\n            EXPECT_EQ(payload->correlated.correlation_id, 8);\n            EXPECT_EQ(payload->correlated.client_id, 91);\n            EXPECT_EQ(payload->stream_id, 142);\n            EXPECT_EQ(payload->channel_length, (int32_t)(payload_length - sizeof(aeron_subscription_command_t)));\n            EXPECT_EQ(\n                memcmp(\"a\", buffer + sizeof(aeron_driver_agent_cmd_log_header_t) + sizeof(aeron_subscription_command_t), 1),\n                0);\n            EXPECT_EQ(memcmp(\"z\", buffer + length - 1, 1), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogSmallAgentLogFrames)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    struct sockaddr_storage addr {};\n    struct msghdr message {};\n    struct iovec iov {};\n\n    const int message_length = 100;\n    uint8_t buffer[message_length];\n    buffer[message_length - 1] = 'c';\n\n    iov.iov_base = buffer;\n    iov.iov_len = (uint32_t)message_length;\n    message.msg_iovlen = 1;\n    message.msg_iov = &iov;\n    message.msg_name = &addr;\n    message.msg_control = nullptr;\n    message.msg_controllen = 0;\n    message.msg_namelen = sizeof(struct sockaddr_storage);\n\n    aeron_driver_agent_log_frame(22, &message, message_length);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, 22);\n            EXPECT_EQ(length,\n                sizeof(aeron_driver_agent_frame_log_header_t) + sizeof(struct sockaddr_storage) + 100);\n\n            char *buffer = (char *)msg;\n            auto *hdr = (aeron_driver_agent_frame_log_header_t *)buffer;\n            EXPECT_NE(hdr->time_ns, 0);\n            EXPECT_EQ(hdr->sockaddr_len, (int32_t)sizeof(struct sockaddr_storage));\n            EXPECT_EQ(memcmp(buffer + length - 1, \"c\", 1), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogAgentLogFramesAndCopyUpToMaxFrameLengthMessage)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    struct sockaddr_storage addr {};\n    struct msghdr message {};\n    struct iovec iov {};\n\n    const int message_length = AERON_MAX_FRAME_LENGTH * 5;\n    EXPECT_EQ(AERON_MAX_FRAME_LENGTH, AERON_MAX_UDP_PAYLOAD_LENGTH);\n    uint8_t buffer[message_length];\n    memset(buffer, 'x', message_length);\n\n    iov.iov_base = buffer;\n    iov.iov_len = (uint32_t)message_length;\n    message.msg_iovlen = 1;\n    message.msg_iov = &iov;\n    message.msg_name = &addr;\n    message.msg_control = nullptr;\n    message.msg_controllen = 0;\n    message.msg_namelen = sizeof(struct sockaddr_storage);\n\n    aeron_driver_agent_log_frame(13, &message, message_length);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, 13);\n            EXPECT_EQ(length,\n                sizeof(aeron_driver_agent_frame_log_header_t) + sizeof(struct sockaddr_storage) + AERON_MAX_FRAME_LENGTH);\n\n            char *buffer = (char *)msg;\n            auto *hdr = (aeron_driver_agent_frame_log_header_t *)buffer;\n            EXPECT_NE(hdr->time_ns, 0);\n            EXPECT_EQ(hdr->sockaddr_len, (int32_t)sizeof(struct sockaddr_storage));\n            char tmp[AERON_MAX_FRAME_LENGTH];\n            memset(tmp, 'x', AERON_MAX_FRAME_LENGTH);\n            EXPECT_EQ(memcmp(buffer + sizeof(aeron_driver_agent_frame_log_header_t) + sizeof(struct sockaddr_storage), tmp, AERON_MAX_FRAME_LENGTH), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeNameResolutionOnNeighborAddedInterceptor)\n{\n    aeron_driver_name_resolver_on_neighbor_change_func_t func = m_context->log.name_resolution_on_neighbor_added;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"NAME_RESOLUTION_NEIGHBOR_ADDED\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.name_resolution_on_neighbor_added, func);\n}\n\nTEST_F(DriverAgentTest, shouldKeepOriginalNameResolutionOnNeighborAddedFunctionIfEventNotEnabled)\n{\n    aeron_driver_name_resolver_on_neighbor_change_func_t func = m_context->log.name_resolution_on_neighbor_added;\n\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(m_context->log.name_resolution_on_neighbor_added, func);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeNameResolutionOnNeighborRemovedInterceptor)\n{\n    aeron_driver_name_resolver_on_neighbor_change_func_t func = m_context->log.name_resolution_on_neighbor_removed;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"NAME_RESOLUTION_NEIGHBOR_REMOVED\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.name_resolution_on_neighbor_removed, func);\n}\n\nTEST_F(DriverAgentTest, shouldKeepOriginalNameResolutionOnNeighborRemovedFunctionIfEventNotEnabled)\n{\n    aeron_driver_name_resolver_on_neighbor_change_func_t func = m_context->log.name_resolution_on_neighbor_removed;\n\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(m_context->log.name_resolution_on_neighbor_removed, func);\n}\n\nTEST_F(DriverAgentTest, shouldLogNameResolutionNeighborAdded)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    struct sockaddr_storage address = {};\n    auto *ipv4_addr = (struct sockaddr_in *)(&address);\n    ipv4_addr->sin_port = 5090;\n    ipv4_addr->sin_family = AF_INET;\n\n    aeron_driver_agent_name_resolution_on_neighbor_added(&address);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_ADDED);\n\n            auto *data = (aeron_driver_agent_log_header_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            auto *addr = (const struct sockaddr_in *)((const char *)msg + sizeof(aeron_driver_agent_log_header_t));\n            EXPECT_NE(nullptr, addr);\n            EXPECT_EQ(AF_INET, addr->sin_family);\n            EXPECT_EQ(5090, addr->sin_port);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogNameResolutionNeighborRemoved)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    struct sockaddr_storage address = {};\n    auto *ipv6_addr = (struct sockaddr_in6 *)(&address);\n    ipv6_addr->sin6_port = 7070;\n    ipv6_addr->sin6_family = AF_INET6;\n\n    aeron_driver_agent_name_resolution_on_neighbor_removed(&address);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_NAME_RESOLUTION_NEIGHBOR_REMOVED);\n\n            auto *data = (aeron_driver_agent_log_header_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            auto *addr = (const struct sockaddr_in6 *)((const char *)msg + sizeof(aeron_driver_agent_log_header_t));\n            EXPECT_NE(nullptr, addr);\n            EXPECT_EQ(AF_INET6, addr->sin6_family);\n            EXPECT_EQ(7070, addr->sin6_port);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeRemovePublicationCleanupInterceptor)\n{\n    const aeron_on_remove_publication_cleanup_func_t func = m_context->log.remove_publication_cleanup;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"REMOVE_PUBLICATION_CLEANUP\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.remove_publication_cleanup, func);\n}\n\nTEST_F(DriverAgentTest, shouldLogRemovePublicationCleanup)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    aeron_driver_agent_remove_publication_cleanup(42, 10, 5, \"channel\");\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_REMOVE_PUBLICATION_CLEANUP);\n\n            auto *data = (aeron_driver_agent_remove_resource_cleanup_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            EXPECT_EQ(AERON_NULL_VALUE, data->id);\n            EXPECT_EQ(42, data->session_id);\n            EXPECT_EQ(10, data->stream_id);\n            EXPECT_EQ(5, data->channel_length);\n            EXPECT_EQ(memcmp((const char *)msg + sizeof(aeron_driver_agent_remove_resource_cleanup_t), \"chann\", 5), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeRemoveSubscriptionCleanupInterceptor)\n{\n    const aeron_on_remove_subscription_cleanup_func_t func = m_context->log.remove_subscription_cleanup;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"REMOVE_SUBSCRIPTION_CLEANUP\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.remove_subscription_cleanup, func);\n}\n\nTEST_F(DriverAgentTest, shouldLogRemoveSubscriptionCleanup)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    aeron_driver_agent_remove_subscription_cleanup(1000000000000, -28, 10, \"channel 10\");\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_REMOVE_SUBSCRIPTION_CLEANUP);\n\n            auto *data = (aeron_driver_agent_remove_resource_cleanup_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            EXPECT_EQ(1000000000000, data->id);\n            EXPECT_EQ(AERON_NULL_VALUE, data->session_id);\n            EXPECT_EQ(-28, data->stream_id);\n            EXPECT_EQ(10, data->channel_length);\n            EXPECT_EQ(memcmp((const char *)msg + sizeof(aeron_driver_agent_remove_resource_cleanup_t), \"channel 10\", 10), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeRemoveImageCleanupInterceptor)\n{\n    const aeron_on_remove_image_cleanup_func_t func = m_context->log.remove_image_cleanup;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"REMOVE_IMAGE_CLEANUP\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.remove_image_cleanup, func);\n}\n\nTEST_F(DriverAgentTest, shouldLogRemoveImageCleanup)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    const int channel_length = AERON_MAX_PATH * 3;\n    char channel[channel_length + 1];\n    memset(channel, '*', channel_length);\n    channel[channel_length] = '\\0';\n\n    aeron_driver_agent_remove_image_cleanup(-2396483568542, 777, 1, channel_length, channel);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_REMOVE_IMAGE_CLEANUP);\n\n            auto *data = (aeron_driver_agent_remove_resource_cleanup_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            EXPECT_EQ(-2396483568542, data->id);\n            EXPECT_EQ(777, data->session_id);\n            EXPECT_EQ(1, data->stream_id);\n\n            const int channel_length = AERON_MAX_PATH * 3;\n            EXPECT_EQ(channel_length, data->channel_length);\n            char channel[channel_length + 1];\n            memset(channel, '*', channel_length);\n            channel[channel_length] = '\\0';\n            EXPECT_EQ(memcmp((const char *)msg + sizeof(aeron_driver_agent_remove_resource_cleanup_t), channel, channel_length), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeSendChannelCreationInterceptor)\n{\n    const aeron_on_endpoint_change_func_t func = m_context->log.sender_proxy_on_add_endpoint;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"SEND_CHANNEL_CREATION\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.sender_proxy_on_add_endpoint, func);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeSendChannelCloseInterceptor)\n{\n    const aeron_on_endpoint_change_func_t func = m_context->log.sender_proxy_on_remove_endpoint;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"SEND_CHANNEL_CLOSE\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.sender_proxy_on_remove_endpoint, func);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeReceiveChannelCreationInterceptor)\n{\n    const aeron_on_endpoint_change_func_t func = m_context->log.receiver_proxy_on_add_endpoint;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"RECEIVE_CHANNEL_CREATION\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.receiver_proxy_on_add_endpoint, func);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeReceiveChannelCloseInterceptor)\n{\n    const aeron_on_endpoint_change_func_t func = m_context->log.receiver_proxy_on_remove_endpoint;\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"RECEIVE_CHANNEL_CLOSE\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(m_context->log.receiver_proxy_on_remove_endpoint, func);\n}\n\nTEST_F(DriverAgentTest, shouldLogSendChannelCreation)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    struct sockaddr_storage local_address = {};\n    auto *ipv4_addr = (struct sockaddr_in *)(&local_address);\n    ipv4_addr->sin_port = 5090;\n    ipv4_addr->sin_family = AF_INET;\n\n    struct sockaddr_storage remote_address = {};\n    auto *ipv6_addr = (struct sockaddr_in6 *)(&remote_address);\n    ipv6_addr->sin6_port = 7070;\n    ipv6_addr->sin6_family = AF_INET6;\n\n    aeron_udp_channel_t channel = {};\n    channel.local_data = local_address;\n    channel.remote_data = remote_address;\n    channel.multicast_ttl = 42;\n\n    aeron_driver_agent_sender_proxy_on_add_endpoint(&channel);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_SEND_CHANNEL_CREATION);\n\n            auto *data = (aeron_driver_agent_on_endpoint_change_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            auto *local_addr = (const struct sockaddr_in *)(&data->local_data);\n            EXPECT_NE(nullptr, local_addr);\n            EXPECT_EQ(AF_INET, local_addr->sin_family);\n            EXPECT_EQ(5090, local_addr->sin_port);\n            auto *remote_addr = (const struct sockaddr_in6 *)(&data->remote_data);\n            EXPECT_EQ(AF_INET6, remote_addr->sin6_family);\n            EXPECT_EQ(7070, remote_addr->sin6_port);\n            EXPECT_EQ(42, data->multicast_ttl);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogSendChannelClose)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    struct sockaddr_storage local_address = {};\n    auto *ipv4_addr = (struct sockaddr_in *)(&local_address);\n    ipv4_addr->sin_port = 5090;\n    ipv4_addr->sin_family = AF_INET;\n\n    struct sockaddr_storage remote_address = {};\n    auto *ipv6_addr = (struct sockaddr_in6 *)(&remote_address);\n    ipv6_addr->sin6_port = 7070;\n    ipv6_addr->sin6_family = AF_INET6;\n\n    aeron_udp_channel_t channel = {};\n    channel.local_data = local_address;\n    channel.remote_data = remote_address;\n    channel.multicast_ttl = 42;\n\n    aeron_driver_agent_sender_proxy_on_remove_endpoint(&channel);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_SEND_CHANNEL_CLOSE);\n\n            auto *data = (aeron_driver_agent_on_endpoint_change_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            auto *local_addr = (const struct sockaddr_in *)(&data->local_data);\n            EXPECT_NE(nullptr, local_addr);\n            EXPECT_EQ(AF_INET, local_addr->sin_family);\n            EXPECT_EQ(5090, local_addr->sin_port);\n            auto *remote_addr = (const struct sockaddr_in6 *)(&data->remote_data);\n            EXPECT_EQ(AF_INET6, remote_addr->sin6_family);\n            EXPECT_EQ(7070, remote_addr->sin6_port);\n            EXPECT_EQ(42, data->multicast_ttl);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogReceiveChannelCreation)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    struct sockaddr_storage local_address = {};\n    auto *ipv6_addr = (struct sockaddr_in6 *)(&local_address);\n    ipv6_addr->sin6_port = 5050;\n    ipv6_addr->sin6_family = AF_INET6;\n\n    struct sockaddr_storage remote_address = {};\n    auto *ipv4_addr = (struct sockaddr_in *)(&remote_address);\n    ipv4_addr->sin_port = 9090;\n    ipv4_addr->sin_family = AF_INET;\n\n    aeron_udp_channel_t channel = {};\n    channel.local_data = local_address;\n    channel.remote_data = remote_address;\n    channel.multicast_ttl = 5;\n\n    aeron_driver_agent_receiver_proxy_on_add_endpoint(&channel);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CREATION);\n\n            auto *data = (aeron_driver_agent_on_endpoint_change_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            auto *local_addr = (const struct sockaddr_in6 *)(&data->local_data);\n            EXPECT_EQ(AF_INET6, local_addr->sin6_family);\n            EXPECT_EQ(5050, local_addr->sin6_port);\n            EXPECT_EQ(5, data->multicast_ttl);\n            auto *remote_addr = (const struct sockaddr_in *)(&data->remote_data);\n            EXPECT_NE(nullptr, remote_addr);\n            EXPECT_EQ(AF_INET, remote_addr->sin_family);\n            EXPECT_EQ(9090, remote_addr->sin_port);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogReceiveChannelClose)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    struct sockaddr_storage local_address = {};\n    auto *ipv6_addr = (struct sockaddr_in6 *)(&local_address);\n    ipv6_addr->sin6_port = 5050;\n    ipv6_addr->sin6_family = AF_INET6;\n\n    struct sockaddr_storage remote_address = {};\n    auto *ipv4_addr = (struct sockaddr_in *)(&remote_address);\n    ipv4_addr->sin_port = 9090;\n    ipv4_addr->sin_family = AF_INET;\n\n    aeron_udp_channel_t channel = {};\n    channel.local_data = local_address;\n    channel.remote_data = remote_address;\n    channel.multicast_ttl = 5;\n\n    aeron_driver_agent_receiver_proxy_on_remove_endpoint(&channel);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_RECEIVE_CHANNEL_CLOSE);\n\n            auto *data = (aeron_driver_agent_on_endpoint_change_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            auto *local_addr = (const struct sockaddr_in6 *)(&data->local_data);\n            EXPECT_EQ(AF_INET6, local_addr->sin6_family);\n            EXPECT_EQ(5050, local_addr->sin6_port);\n            EXPECT_EQ(5, data->multicast_ttl);\n            auto *remote_addr = (const struct sockaddr_in *)(&data->remote_data);\n            EXPECT_NE(nullptr, remote_addr);\n            EXPECT_EQ(AF_INET, remote_addr->sin_family);\n            EXPECT_EQ(9090, remote_addr->sin_port);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldNotAddDynamicDissectorIfDynamicDissectorEventIsDisabled)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n    ASSERT_TRUE(aeron_driver_agent_logging_events_init(\"ADD_DYNAMIC_DISSECTOR\", nullptr));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT));\n\n    aeron_driver_agent_generic_dissector_func_t dynamic_dissector =\n        [](FILE *fpout, const char *log_header_str, const void *message, size_t len)\n        {\n        };\n\n    EXPECT_EQ(-1, aeron_driver_agent_add_dynamic_dissector(dynamic_dissector));\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)0);\n    EXPECT_EQ(timesCalled, (size_t)0);\n}\n\nTEST_F(DriverAgentTest, shouldAddDynamicDissectorIfDynamicDissectorEventIsEnabled)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n    ASSERT_TRUE(aeron_driver_agent_logging_events_init(\"DYNAMIC_DISSECTOR_EVENT\", nullptr));\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT));\n\n    aeron_driver_agent_generic_dissector_func_t dynamic_dissector =\n        [](FILE *fpout, const char *log_header_str, const void *message, size_t len)\n        {\n        };\n\n    EXPECT_EQ(0, aeron_driver_agent_add_dynamic_dissector(dynamic_dissector));\n    EXPECT_EQ(1, aeron_driver_agent_add_dynamic_dissector(dynamic_dissector));\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_ADD_DYNAMIC_DISSECTOR);\n            EXPECT_EQ(length, sizeof(aeron_driver_agent_add_dissector_header_t));\n\n            char *buffer = (char *)msg;\n            auto *hdr = (aeron_driver_agent_add_dissector_header_t *)buffer;\n            EXPECT_NE(hdr->time_ns, 0);\n            EXPECT_EQ(hdr->index, (int64_t)((*count) - 1));\n            EXPECT_NE(hdr->dissector_func, nullptr);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 10);\n\n    EXPECT_EQ(messagesRead, (size_t)2);\n    EXPECT_EQ(timesCalled, (size_t)2);\n}\n\nTEST_F(DriverAgentTest, shouldNotLogDynamicEventIfDisabled)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n    ASSERT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT));\n\n    aeron_driver_agent_log_dynamic_event(5, \"test\", 4);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)0);\n    EXPECT_EQ(timesCalled, (size_t)0);\n}\n\nTEST_F(DriverAgentTest, shouldLogDynamicEventSmallMessage)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n    ASSERT_TRUE(aeron_driver_agent_logging_events_init(\"DYNAMIC_DISSECTOR_EVENT\", nullptr));\n\n    const int message_length = 200;\n    char message[message_length];\n    memset(message, 'x', message_length);\n\n    aeron_driver_agent_log_dynamic_event(111, &message, message_length);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT);\n            EXPECT_EQ(length, sizeof(aeron_driver_agent_dynamic_event_header_t) + 200);\n\n            char *buffer = (char *)msg;\n            auto *hdr = (aeron_driver_agent_dynamic_event_header_t *)buffer;\n            EXPECT_NE(hdr->time_ns, 0);\n            EXPECT_EQ(hdr->index, 111);\n            EXPECT_EQ(memcmp(buffer + length - 1, \"x\", 1), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldLogDynamicEventBigMessage)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n    ASSERT_TRUE(aeron_driver_agent_logging_events_init(\"DYNAMIC_DISSECTOR_EVENT\", nullptr));\n\n    const int message_length = AERON_MAX_FRAME_LENGTH * 3;\n    char message[message_length];\n    memset(message, 'z', message_length);\n\n    aeron_driver_agent_log_dynamic_event(5, &message, message_length);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_DYNAMIC_DISSECTOR_EVENT);\n            EXPECT_EQ(length, sizeof(aeron_driver_agent_dynamic_event_header_t) + AERON_MAX_FRAME_LENGTH);\n\n            char *buffer = (char *)msg;\n            auto *hdr = (aeron_driver_agent_dynamic_event_header_t *)buffer;\n            EXPECT_NE(hdr->time_ns, 0);\n            EXPECT_EQ(hdr->index, 5);\n            EXPECT_EQ(memcmp(buffer + length - 1, \"z\", 1), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeFlowControlOnReceiverAddedInterceptor)\n{\n    aeron_driver_flow_control_strategy_on_receiver_change_func_t func = m_context->log.flow_control_on_receiver_added;\n    EXPECT_EQ(nullptr, func);\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"FLOW_CONTROL_RECEIVER_ADDED\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(nullptr, m_context->log.flow_control_on_receiver_added);\n}\n\nTEST_F(DriverAgentTest, shouldNotAssignFlowControlOnReceiverAddedInterceptorWhenNotConfigured)\n{\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(nullptr, m_context->log.flow_control_on_receiver_added);\n}\n\nTEST_F(DriverAgentTest, shouldLogFlowControlReceiverAdded)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    aeron_driver_agent_flow_control_on_receiver_added(888LL, 42, 1, 5, \"channel 5\", 3);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_ADDED);\n\n            auto *data = (aeron_driver_agent_flow_control_receiver_change_log_header_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            EXPECT_EQ(888LL, data->receiver_id);\n            EXPECT_EQ(42, data->session_id);\n            EXPECT_EQ(1, data->stream_id);\n            EXPECT_EQ(5, data->channel_length);\n            EXPECT_EQ(3, data->receiver_count);\n            EXPECT_EQ(memcmp((const char *)msg + sizeof(aeron_driver_agent_flow_control_receiver_change_log_header_t), \"chann\", 5), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeFlowControlOnReceiverRemovedInterceptor)\n{\n    aeron_driver_flow_control_strategy_on_receiver_change_func_t func = m_context->log.flow_control_on_receiver_removed;\n    EXPECT_EQ(nullptr, func);\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"FLOW_CONTROL_RECEIVER_REMOVED\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(nullptr, m_context->log.flow_control_on_receiver_removed);\n}\n\nTEST_F(DriverAgentTest, shouldNotAssignFlowControlOnReceiverRemovedInterceptorWhenNotConfigured)\n{\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(nullptr, m_context->log.flow_control_on_receiver_removed);\n}\n\nTEST_F(DriverAgentTest, shouldLogFlowControlReceiverRemoved)\n{\n    aeron_driver_agent_logging_ring_buffer_init();\n\n    aeron_driver_agent_flow_control_on_receiver_removed(1010101010101010LL, -9, -41, 10, \"channel 10\", 1);\n\n    auto message_handler =\n        [](int32_t msg_type_id, const void *msg, size_t length, void *clientd)\n        {\n            auto *count = (size_t *)clientd;\n            (*count)++;\n\n            EXPECT_EQ(msg_type_id, AERON_DRIVER_EVENT_FLOW_CONTROL_RECEIVER_REMOVED);\n\n            auto *data = (aeron_driver_agent_flow_control_receiver_change_log_header_t *)msg;\n            EXPECT_NE(data->time_ns, 0LL);\n            EXPECT_EQ(1010101010101010LL, data->receiver_id);\n            EXPECT_EQ(-9, data->session_id);\n            EXPECT_EQ(-41, data->stream_id);\n            EXPECT_EQ(10, data->channel_length);\n            EXPECT_EQ(1, data->receiver_count);\n            EXPECT_EQ(memcmp((const char *)msg + sizeof(aeron_driver_agent_flow_control_receiver_change_log_header_t),\n                \"channel 10\", 10), 0);\n        };\n\n    size_t timesCalled = 0;\n    size_t messagesRead = aeron_mpsc_rb_read(aeron_driver_agent_mpsc_rb(), message_handler, &timesCalled, 1);\n\n    EXPECT_EQ(messagesRead, (size_t)1);\n    EXPECT_EQ(timesCalled, (size_t)1);\n}\n\nTEST_F(DriverAgentTest, dissecLogStartShouldFormatNanoTimeWithMicrosecondPrecision)\n{\n    int64_t time_ns = 55555001234567;\n    int64_t time_ms = 1234567890987;\n\n    auto result = std::string(aeron_driver_agent_dissect_log_start(time_ns, time_ms));\n\n    std::string startString = \"[55555.001234567] log started 2009-02-1\";\n    std::string endString = std::string(\", enabled loggers: {DRIVER:\")\n    .append(\" version=\")\n    .append(aeron_driver_version_text())\n    .append(\" commit=\")\n    .append(aeron_driver_version_git_sha())\n    .append(\"}\");\n\n    EXPECT_EQ(0, result.find(startString));\n    EXPECT_NE(std::string::npos, result.find(endString, startString.length()));\n}\n\nTEST_F(DriverAgentTest, dissecLogHeaderShouldFormatNanoTimeWithMicrosecondPrecision)\n{\n    int64_t time_ns = 333000555000999001;\n    aeron_driver_agent_event_t event = AERON_DRIVER_EVENT_CMD_IN_CLIENT_CLOSE;\n    size_t capture_length = 100;\n    size_t message_length = 200;\n\n    auto result = std::string(aeron_driver_agent_dissect_log_header(time_ns, event, capture_length, message_length));\n\n    ASSERT_EQ(\"[333000555.000999001] DRIVER: CMD_IN_CLIENT_CLOSE [100/200]\", result);\n}\n\nTEST_F(DriverAgentTest, shouldNotAssignOnNameResolveFunctionWhenNotConfigured)\n{\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(nullptr, m_context->log.on_name_resolve);\n}\n\nTEST_F(DriverAgentTest, shouldNotAssignOnNameLookupFunctionWhenNotConfigured)\n{\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(nullptr, m_context->log.on_name_lookup);\n}\n\nTEST_F(DriverAgentTest, shouldNotHostNameFunctionWhenNotConfigured)\n{\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(nullptr, m_context->log.on_host_name);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeOnNameResolveFunction)\n{\n    EXPECT_EQ(nullptr, m_context->log.on_name_resolve);\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"NAME_RESOLUTION_RESOLVE\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(nullptr, m_context->log.on_name_resolve);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeOnNameLookupFunction)\n{\n    EXPECT_EQ(nullptr, m_context->log.on_name_lookup);\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"NAME_RESOLUTION_LOOKUP\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(nullptr, m_context->log.on_name_lookup);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeNameResolverFunctions)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\n        \"NAME_RESOLUTION_LOOKUP,NAME_RESOLUTION_RESOLVE,NAME_RESOLUTION_HOST_NAME\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(nullptr, m_context->log.on_name_resolve);\n    EXPECT_NE(nullptr, m_context->log.on_name_lookup);\n    EXPECT_NE(nullptr, m_context->log.on_host_name);\n    EXPECT_NE((void *)m_context->log.on_name_resolve, (void *)m_context->log.on_name_lookup);\n}\n\nTEST_F(DriverAgentTest, shouldNotSendNakMessageFunctionWhenNotConfigured)\n{\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(nullptr, m_context->log.send_nak_message);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeSendNakMessageFunction)\n{\n    EXPECT_EQ(nullptr, m_context->log.send_nak_message);\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"SEND_NAK_MESSAGE\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(nullptr, m_context->log.send_nak_message);\n}\n\nTEST_F(DriverAgentTest, shouldNotRsendFunctionWhenNotConfigured)\n{\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_EQ(nullptr, m_context->log.resend);\n}\n\nTEST_F(DriverAgentTest, shouldInitializeResendFunction)\n{\n    EXPECT_EQ(nullptr, m_context->log.resend);\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"RESEND\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(nullptr, m_context->log.resend);\n}\n\nTEST_F(DriverAgentTest, shouldDissect)\n{\n    EXPECT_EQ(nullptr, m_context->log.resend);\n\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"RESEND\", nullptr));\n    aeron_driver_agent_init_logging_events_interceptors(m_context);\n\n    EXPECT_NE(nullptr, m_context->log.resend);\n}\n\nTEST_F(DriverAgentTest, shouldEnableNakSentUsingOldName)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"SEND_NAK_MESSAGE\", \"\"));\n\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAK_SENT));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAK_RECEIVED));\n}\n\nTEST_F(DriverAgentTest, shouldEnableNakSentUsingNewName)\n{\n    EXPECT_TRUE(aeron_driver_agent_logging_events_init(\"NAK_SENT\", \"\"));\n\n    EXPECT_TRUE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAK_SENT));\n    EXPECT_FALSE(aeron_driver_agent_is_event_enabled(AERON_DRIVER_EVENT_NAK_RECEIVED));\n}\n\nTEST_F(DriverAgentTest, dissecErrorFrame)\n{\n    uint8_t buff[1024];\n    std::string errorMessage = \"test 61007\";\n    auto *error = reinterpret_cast<aeron_error_t *>(&buff);\n    error->frame_header.type = AERON_HDR_TYPE_ERR;\n    error->frame_header.flags = AERON_ERROR_HAS_GROUP_TAG_FLAG;\n    error->frame_header.frame_length = static_cast<int32_t>(sizeof(aeron_error_t) + errorMessage.length());\n    error->session_id = -77666;\n    error->stream_id = 8888;\n    error->receiver_id = 32134164361243612ul;\n    error->group_tag = 500505;\n    error->error_code = 19;\n    error->error_length = static_cast<int32_t>(errorMessage.length());\n    memcpy((char *)buff + sizeof(aeron_error_t), errorMessage.c_str(), errorMessage.length());\n\n    EXPECT_STREQ(\n        \"type=ERR flags=00001000 frameLength=50 sessionId=-77666 streamId=8888 receiverId=32134164361243612 groupTag=500505 errorCode=19 errorMessage=\\\"test 61007\\\"\",\n        aeron_driver_agent_dissect_frame(&buff, sizeof(buff)));\n}\n\nTEST_F(DriverAgentTest, dissecUknownFrame)\n{\n    uint8_t buff[1024];\n    auto *error = reinterpret_cast<aeron_error_t *>(&buff);\n    error->frame_header.version = 0xB;\n    error->frame_header.type = 0xFE;\n    error->frame_header.flags = 0xAC;\n    error->frame_header.frame_length = 150;\n    error->session_id = -77666;\n    error->stream_id = 8888;\n    error->receiver_id = 32134164361243612ul;\n    error->group_tag = 500505;\n    error->error_code = 19;\n\n    EXPECT_STREQ(\n        \"type=unknown frame type (0xfe) version=0xb flags=10101100 frameLength=150\",\n        aeron_driver_agent_dissect_frame(&buff, sizeof(buff)));\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/media/aeron_udp_channel_transport_loss_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"media/aeron_udp_channel_transport.h\"\n#include \"media/aeron_udp_channel_transport_loss.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n}\n\n#define TEMP_URL_LEN (128)\n\nclass UdpChannelTransportLossTest : public testing::Test\n{\npublic:\n    UdpChannelTransportLossTest() = default;\n};\n\ntypedef struct delegate_recv_state_stct\n{\n    int messages_received;\n    int bytes_received;\n}\ndelegate_recv_state_t;\n\nvoid aeron_udp_channel_interceptor_loss_incoming_delegate(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp)\n{\n    auto *state = (delegate_recv_state_t *)interceptor_state;\n\n    state->messages_received++;\n    state->bytes_received += (int)length;\n}\n\nTEST_F(UdpChannelTransportLossTest, shouldDiscardAllPacketsWithRateOfOne)\n{\n    uint16_t msg_type = AERON_HDR_TYPE_DATA;\n    aeron_udp_channel_incoming_interceptor_t delegate;\n    delegate_recv_state_t delegate_recv_state = { 0, 0 };\n    aeron_udp_channel_interceptor_loss_params_t params;\n    uint8_t data_0[1024];\n    uint8_t data_1[1024];\n    aeron_frame_header_t *frame_header;\n\n    frame_header = (aeron_frame_header_t *)data_0;\n    frame_header->type = msg_type;\n    frame_header = (aeron_frame_header_t *)data_1;\n    frame_header->type = msg_type;\n\n    params.rate = 1.0;\n    params.recv_msg_type_mask = 1U << msg_type;\n    params.seed = 0;\n\n    delegate.incoming_func = aeron_udp_channel_interceptor_loss_incoming_delegate;\n    delegate.interceptor_state = &delegate_recv_state;\n\n    aeron_udp_channel_interceptor_loss_configure(&params);\n\n    aeron_udp_channel_interceptor_loss_incoming(\n        nullptr, &delegate, nullptr, nullptr, nullptr, nullptr, data_0, 1024, nullptr, nullptr);\n    aeron_udp_channel_interceptor_loss_incoming(\n        nullptr, &delegate, nullptr, nullptr, nullptr, nullptr, data_1, 1024, nullptr, nullptr);\n\n    EXPECT_EQ(delegate_recv_state.messages_received, 0);\n}\n\nTEST_F(UdpChannelTransportLossTest, shouldNotDiscardAllPacketsWithRateOfOneWithDifferentMessageType)\n{\n    uint16_t loss_msg_type = AERON_HDR_TYPE_DATA;\n    uint16_t data_msg_type = AERON_HDR_TYPE_SETUP;\n    aeron_udp_channel_incoming_interceptor_t delegate;\n    delegate_recv_state_t delegate_recv_state = { 0, 0 };\n    aeron_udp_channel_interceptor_loss_params_t params;\n    uint8_t data_0[1024];\n    uint8_t data_1[1024];\n    aeron_frame_header_t *frame_header;\n\n    frame_header = (aeron_frame_header_t *)data_0;\n    frame_header->type = data_msg_type;\n    frame_header = (aeron_frame_header_t *)data_1;\n    frame_header->type = data_msg_type;\n\n    params.rate = 1.0;\n    params.recv_msg_type_mask = 1U << loss_msg_type;\n    params.seed = 0;\n\n    delegate.incoming_func = aeron_udp_channel_interceptor_loss_incoming_delegate;\n    delegate.interceptor_state = &delegate_recv_state;\n\n    aeron_udp_channel_interceptor_loss_configure(&params);\n\n    aeron_udp_channel_interceptor_loss_incoming(\n        nullptr, &delegate, nullptr, nullptr, nullptr, nullptr, data_0, 1024, nullptr, nullptr);\n    aeron_udp_channel_interceptor_loss_incoming(\n        nullptr, &delegate, nullptr, nullptr, nullptr, nullptr, data_1, 1024, nullptr, nullptr);\n\n    EXPECT_EQ(delegate_recv_state.messages_received, 2);\n}\n\nTEST_F(UdpChannelTransportLossTest, shouldNotDiscardAllPacketsWithRateOfZero)\n{\n    uint16_t loss_msg_type = AERON_HDR_TYPE_DATA;\n    aeron_udp_channel_incoming_interceptor_t delegate;\n    delegate_recv_state_t delegate_recv_state = { 0, 0 };\n    aeron_udp_channel_interceptor_loss_params_t params;\n    uint8_t data_0[1024];\n    uint8_t data_1[1024];\n    aeron_frame_header_t *frame_header;\n\n    frame_header = (aeron_frame_header_t *)data_0;\n    frame_header->type = loss_msg_type;\n    frame_header = (aeron_frame_header_t *)data_1;\n    frame_header->type = loss_msg_type;\n\n    params.rate = 0.0;\n    params.recv_msg_type_mask = 1U << loss_msg_type;\n    params.seed = 0;\n\n    delegate.incoming_func = aeron_udp_channel_interceptor_loss_incoming_delegate;\n    delegate.interceptor_state = &delegate_recv_state;\n\n    aeron_udp_channel_interceptor_loss_configure(&params);\n\n    aeron_udp_channel_interceptor_loss_incoming(\n        nullptr, &delegate, nullptr, nullptr, nullptr, nullptr, data_0, 1024, nullptr, nullptr);\n    aeron_udp_channel_interceptor_loss_incoming(\n        nullptr, &delegate, nullptr, nullptr, nullptr, nullptr, data_1, 1024, nullptr, nullptr);\n\n    EXPECT_EQ(delegate_recv_state.messages_received, 2);\n}\n\nTEST_F(UdpChannelTransportLossTest, shouldDiscardRoughlyHalfTheMessages)\n{\n    uint16_t msg_type = AERON_HDR_TYPE_DATA;\n    aeron_udp_channel_incoming_interceptor_t delegate;\n    delegate_recv_state_t delegate_recv_state = { 0, 0 };\n    aeron_udp_channel_interceptor_loss_params_t params;\n    aeron_frame_header_t *frame_header;\n\n    const size_t vlen = 10;\n    uint8_t data[vlen * 1024];\n\n    params.rate = 0.5;\n    params.recv_msg_type_mask = 1U << msg_type;\n    params.seed = 23764;\n\n    delegate.incoming_func = aeron_udp_channel_interceptor_loss_incoming_delegate;\n    delegate.interceptor_state = &delegate_recv_state;\n\n    aeron_udp_channel_interceptor_loss_configure(&params);\n\n    for (size_t i = 0; i < vlen; i++)\n    {\n        frame_header = (aeron_frame_header_t *)(data + (i * 1024));\n        frame_header->type = msg_type;\n\n        aeron_udp_channel_interceptor_loss_incoming(\n            nullptr, &delegate, nullptr, nullptr, nullptr, nullptr, data + (i * 1024), 1024, nullptr, nullptr);\n    }\n\n    EXPECT_LT(delegate_recv_state.messages_received, static_cast<int>(vlen));\n    EXPECT_GT(delegate_recv_state.messages_received, 0);\n    EXPECT_LT(delegate_recv_state.bytes_received, static_cast<int64_t>(vlen * 1024));\n    EXPECT_GT(delegate_recv_state.bytes_received, 0);\n    EXPECT_EQ(delegate_recv_state.messages_received, 6);\n}\n\nTEST_F(UdpChannelTransportLossTest, shouldParseAllParams)\n{\n    char uri[TEMP_URL_LEN];\n    aeron_udp_channel_interceptor_loss_params_t params;\n    strncpy(uri, \"rate=0.20|seed=10|recv-msg-mask=0xF\", TEMP_URL_LEN);\n\n    int i = aeron_udp_channel_interceptor_loss_parse_params(uri, &params);\n\n    EXPECT_EQ(i, 0);\n    EXPECT_EQ(params.rate, 0.2);\n    EXPECT_EQ(params.seed, 10ull);\n    EXPECT_EQ(params.recv_msg_type_mask, 0xFul);\n}\n\nTEST_F(UdpChannelTransportLossTest, shouldFailOnInvalidRate)\n{\n    char uri[TEMP_URL_LEN];\n    aeron_udp_channel_interceptor_loss_params_t params;\n    strncpy(uri, \"rate=abc\", TEMP_URL_LEN);\n\n    int i = aeron_udp_channel_interceptor_loss_parse_params(uri, &params);\n\n    EXPECT_EQ(i, -1);\n}\n\nTEST_F(UdpChannelTransportLossTest, shouldFailOnInvalidSeed)\n{\n    char uri[TEMP_URL_LEN];\n    aeron_udp_channel_interceptor_loss_params_t params;\n    strncpy(uri, \"seed=abc\", TEMP_URL_LEN);\n\n    int i = aeron_udp_channel_interceptor_loss_parse_params(uri, &params);\n\n    EXPECT_EQ(i, -1);\n}\n\nTEST_F(UdpChannelTransportLossTest, shouldFailOnInvalidRecvMsgMask)\n{\n    char uri[TEMP_URL_LEN];\n    aeron_udp_channel_interceptor_loss_params_t params;\n    strncpy(uri, \"recv-msg-mask=zzz\", TEMP_URL_LEN);\n\n    int i = aeron_udp_channel_interceptor_loss_parse_params(uri, &params);\n\n    EXPECT_EQ(i, -1);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/c/media/aeron_udp_channel_transport_multi_gap_loss_test.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\nextern \"C\"\n{\n#include \"media/aeron_udp_channel_transport.h\"\n#include \"media/aeron_udp_channel_transport_multi_gap_loss.h\"\n#include \"protocol/aeron_udp_protocol.h\"\n\n#if !defined(HAVE_STRUCT_MMSGHDR)\nstruct mmsghdr\n{\n    struct msghdr msg_hdr;\n    unsigned int msg_len;\n};\n#endif\n}\n\n#define TEMP_URL_LEN (128)\n\ntypedef struct delegate_recv_state_stct\n{\n    int messages_received;\n}\ndelegate_recv_state_t;\n\nvoid aeron_udp_channel_interceptor_multi_gap_loss_incoming_delegate(\n    void *interceptor_state,\n    aeron_udp_channel_incoming_interceptor_t *delegate,\n    aeron_udp_channel_transport_t *transport,\n    void *receiver_clientd,\n    void *endpoint_clientd,\n    void *destination_clientd,\n    uint8_t *buffer,\n    size_t length,\n    struct sockaddr_storage *addr,\n    struct timespec *media_timestamp)\n{\n    ((delegate_recv_state_t *)interceptor_state)->messages_received++;\n}\n\nclass UdpChannelTransportMultiGapLossTest : public testing::Test\n{\npublic:\n    UdpChannelTransportMultiGapLossTest()\n    {\n        m_delegate_recv_state = { 0 };\n\n        m_delegate.incoming_func = aeron_udp_channel_interceptor_multi_gap_loss_incoming_delegate;\n        m_delegate.interceptor_state = &m_delegate_recv_state;\n        m_interceptor_state = nullptr;\n    }\n\n    void init(const char *const_uri)\n    {\n        aeron_udp_channel_interceptor_multi_gap_loss_init_incoming(&m_interceptor_state, nullptr, AERON_UDP_CHANNEL_TRANSPORT_AFFINITY_SENDER);\n        parse_params(&m_params, const_uri);\n        aeron_udp_channel_interceptor_multi_gap_loss_configure(&m_params);\n    }\n\n    void close() const\n    {\n        aeron_udp_channel_interceptor_multi_gap_loss_close_incoming(m_interceptor_state);\n    }\n\n    static void parse_params(aeron_udp_channel_interceptor_multi_gap_loss_params_t *params, const char *const_uri)\n    {\n        char uri[TEMP_URL_LEN + 1];\n        strncpy(uri, const_uri, TEMP_URL_LEN);\n\n        EXPECT_EQ(aeron_udp_channel_interceptor_multi_gap_loss_parse_params(uri, params), 0);\n    }\n\n    void incoming(int32_t offset, size_t length, bool should_drop)\n    {\n        uint8_t buffer[1024];\n\n        auto *data_header = (aeron_data_header_t *)buffer;\n        data_header->frame_header.type = AERON_HDR_TYPE_DATA;\n        data_header->stream_id = 123;\n        data_header->session_id = 456;\n        data_header->term_id = 0;\n        data_header->term_offset = offset;\n\n        int expected_messages_received = m_delegate_recv_state.messages_received + (should_drop ? 0 : 1);\n        aeron_udp_channel_interceptor_multi_gap_loss_incoming(\n            m_interceptor_state, &m_delegate, nullptr, nullptr, nullptr, nullptr, buffer, length, nullptr, nullptr);\n        EXPECT_EQ(expected_messages_received, m_delegate_recv_state.messages_received);\n    }\n\n    aeron_udp_channel_interceptor_multi_gap_loss_params_t m_params = {};\n    aeron_udp_channel_incoming_interceptor_t m_delegate = {};\n    delegate_recv_state_t m_delegate_recv_state = {};\n    void *m_interceptor_state;\n};\n\nTEST_F(UdpChannelTransportMultiGapLossTest, singleSmallGap)\n{\n    init(\"term-id=0|gap-radix=8|gap-length=4|total-gaps=1\");\n\n    incoming(0, 8, false);\n    incoming(8, 8, true);\n    incoming(16, 8, false);\n    incoming(24, 8, false);\n\n    close();\n}\n\nTEST_F(UdpChannelTransportMultiGapLossTest, singleSmallGapRx)\n{\n    init(\"term-id=0|gap-radix=8|gap-length=4|total-gaps=1\");\n\n    incoming(0, 8, false);\n    incoming(8, 8, true);\n    incoming(16, 8, false);\n\n    incoming(8, 8, false);\n\n    close();\n}\n\nTEST_F(UdpChannelTransportMultiGapLossTest, multiSmallGap)\n{\n    init(\"term-id=0|gap-radix=16|gap-length=8|total-gaps=4\");\n\n    incoming(0, 8, false);\n    incoming(8, 8, false);\n    incoming(16, 8, true);\n    incoming(24, 8, false);\n    incoming(32, 8, true);\n    incoming(40, 8, false);\n    incoming(48, 4, true);\n    incoming(52, 8, true);\n    incoming(60, 2, false);\n    incoming(62, 10, true);\n\n    close();\n}\n\nTEST_F(UdpChannelTransportMultiGapLossTest, shouldParseAllParams)\n{\n    aeron_udp_channel_interceptor_multi_gap_loss_params_t params;\n    parse_params(&params, \"term-id=1|gap-radix=16|gap-length=4|total-gaps=10\");\n\n    EXPECT_EQ(params.term_id, 1);\n    EXPECT_EQ(params.gap_radix, 16);\n    EXPECT_EQ(params.gap_length, 4);\n    EXPECT_EQ(params.total_gaps, 10);\n\n    EXPECT_EQ(params.gap_radix_bits, 4);\n    EXPECT_EQ(params.gap_radix_mask, ~(0xF));\n    EXPECT_EQ(params.last_gap_limit, 164);\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/ClientCommandAdapterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.ErrorCode;\nimport io.aeron.command.ControlProtocolEvents;\nimport io.aeron.command.PublicationMessageFlyweight;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ControlProtocolException;\nimport io.aeron.exceptions.StorageSpaceException;\nimport org.agrona.ErrorHandler;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.InOrder;\n\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nclass ClientCommandAdapterTest\n{\n    private final AtomicCounter errors = mock(AtomicCounter.class);\n    private final ErrorHandler errorHandler = mock(ErrorHandler.class);\n    private final RingBuffer toDriverCommands = mock(RingBuffer.class);\n    private final ClientProxy clientProxy = mock(ClientProxy.class);\n    private final DriverConductor driverConductor = mock(DriverConductor.class);\n    private final ClientCommandAdapter clientCommandAdapter = new ClientCommandAdapter(\n        errors, errorHandler, toDriverCommands, clientProxy, driverConductor);\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[1024]);\n    private final PublicationMessageFlyweight publicationMsgFlyweight = new PublicationMessageFlyweight();\n\n    @BeforeEach\n    void before()\n    {\n        when(errors.isClosed()).thenReturn(false);\n    }\n\n    @Test\n    void shouldThrowControlProtocolExceptionIfUnknownCommand()\n    {\n        final int msgTypeId = 932467234;\n        final ErrorCode expectedErrorCode = ErrorCode.UNKNOWN_COMMAND_TYPE_ID;\n        final String expectedErrorMessage = \"ERROR - command typeId=\" + msgTypeId;\n\n        clientCommandAdapter.onMessage(msgTypeId, buffer, 0, 5);\n\n        final ArgumentCaptor<ControlProtocolException> exceptionArgumentCaptor =\n            ArgumentCaptor.forClass(ControlProtocolException.class);\n        final InOrder inOrder = inOrder(errors, errorHandler, clientProxy);\n        inOrder.verify(errors).isClosed();\n        inOrder.verify(errors).increment();\n        inOrder.verify(errorHandler).onError(exceptionArgumentCaptor.capture());\n        inOrder.verify(clientProxy).onError(0, expectedErrorCode, expectedErrorMessage);\n        inOrder.verifyNoMoreInteractions();\n\n        final ControlProtocolException exception = exceptionArgumentCaptor.getValue();\n        assertEquals(expectedErrorCode, exception.errorCode());\n        assertEquals(AeronException.Category.ERROR, exception.category());\n        assertEquals(expectedErrorMessage, exception.getMessage());\n    }\n\n    @Test\n    void shouldHandleGenericException()\n    {\n        final long clientId = Long.MIN_VALUE + 63847263784238L;\n        final long correlationId = 4213;\n        final int streamId = -19;\n        final String channel = \"aeron:udp?alias=test|endpoint=localhost:8080\";\n        final IllegalArgumentException exception = new IllegalArgumentException(new IOException(\"dummy error\"));\n        final ErrorCode expectedErrorCode = ErrorCode.GENERIC_ERROR;\n        final String expectedErrorMessage = exception.getClass().getName() + \" : \" + exception.getMessage();\n        doThrow(exception)\n            .when(driverConductor)\n            .onAddNetworkPublication(channel, streamId, correlationId, clientId, false);\n        publicationMsgFlyweight.wrap(buffer, 0)\n            .clientId(clientId)\n            .correlationId(correlationId);\n        publicationMsgFlyweight\n            .streamId(streamId)\n            .channel(channel);\n\n        clientCommandAdapter.onMessage(\n            ControlProtocolEvents.ADD_PUBLICATION, buffer, 0, publicationMsgFlyweight.length());\n\n        final InOrder inOrder = inOrder(driverConductor, errors, errorHandler, clientProxy);\n        inOrder.verify(driverConductor).onAddNetworkPublication(channel, streamId, correlationId, clientId, false);\n        inOrder.verify(errors).isClosed();\n        inOrder.verify(errors).increment();\n        inOrder.verify(errorHandler).onError(exception);\n        inOrder.verify(clientProxy).onError(correlationId, expectedErrorCode, expectedErrorMessage);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldHandleStorageSpaceException()\n    {\n        final long clientId = 109;\n        final long correlationId = 42;\n        final int streamId = 100;\n        final String channel = \"aeron:ipc\";\n        final StorageSpaceException exception = new StorageSpaceException(\"storage error\");\n        final ErrorCode expectedErrorCode = ErrorCode.STORAGE_SPACE;\n        final String expectedErrorMessage = exception.getMessage();\n        doThrow(exception)\n            .when(driverConductor)\n            .onAddIpcPublication(channel, streamId, correlationId, clientId, false);\n        publicationMsgFlyweight.wrap(buffer, 0)\n            .clientId(clientId)\n            .correlationId(correlationId);\n        publicationMsgFlyweight\n            .streamId(streamId)\n            .channel(channel);\n\n        clientCommandAdapter.onMessage(\n            ControlProtocolEvents.ADD_PUBLICATION, buffer, 0, publicationMsgFlyweight.length());\n\n        final InOrder inOrder = inOrder(driverConductor, errors, errorHandler, clientProxy);\n        inOrder.verify(driverConductor).onAddIpcPublication(channel, streamId, correlationId, clientId, false);\n        inOrder.verify(errors).isClosed();\n        inOrder.verify(errors).increment();\n        inOrder.verify(errorHandler).onError(exception);\n        inOrder.verify(clientProxy).onError(correlationId, expectedErrorCode, expectedErrorMessage);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"noSpaceLeftExceptions\")\n    void shouldHandleNoSpaceLeftOnTheDeviceIOException(final Exception exception)\n    {\n        final long clientId = 109;\n        final long correlationId = 42;\n        final int streamId = 100;\n        final String channel = \"aeron:ipc\";\n        final ErrorCode expectedErrorCode = ErrorCode.STORAGE_SPACE;\n        final String expectedErrorMessage = exception.getMessage();\n        doAnswer(invocation ->\n        {\n            LangUtil.rethrowUnchecked(exception);\n            return null;\n        }).when(driverConductor)\n            .onAddIpcPublication(channel, streamId, correlationId, clientId, false);\n        publicationMsgFlyweight.wrap(buffer, 0)\n            .clientId(clientId)\n            .correlationId(correlationId);\n        publicationMsgFlyweight\n            .streamId(streamId)\n            .channel(channel);\n\n        clientCommandAdapter.onMessage(\n            ControlProtocolEvents.ADD_PUBLICATION, buffer, 0, publicationMsgFlyweight.length());\n\n        final InOrder inOrder = inOrder(driverConductor, errors, errorHandler, clientProxy);\n        inOrder.verify(driverConductor).onAddIpcPublication(channel, streamId, correlationId, clientId, false);\n        inOrder.verify(errors).isClosed();\n        inOrder.verify(errors).increment();\n        inOrder.verify(errorHandler).onError(exception);\n        inOrder.verify(clientProxy).onError(correlationId, expectedErrorCode, expectedErrorMessage);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    private static List<Throwable> noSpaceLeftExceptions()\n    {\n        return Arrays.asList(\n            new IOException(\"No space left on device\"),\n            new UncheckedIOException(new IOException(\"No space left on device\")),\n            new RuntimeException(new IOException(\"No space left on device\")));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/ConfigurationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass ConfigurationTest\n{\n    @Test\n    void shouldUseSparseFilesByDefault()\n    {\n        System.clearProperty(Configuration.TERM_BUFFER_SPARSE_FILE_PROP_NAME);\n        assertTrue(Configuration.termBufferSparseFile());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldUseSparseFilesByDefault(final boolean useSparseFiles)\n    {\n        System.setProperty(Configuration.TERM_BUFFER_SPARSE_FILE_PROP_NAME, String.valueOf(useSparseFiles));\n        try\n        {\n            assertEquals(useSparseFiles, Configuration.termBufferSparseFile());\n        }\n        finally\n        {\n            System.clearProperty(Configuration.TERM_BUFFER_SPARSE_FILE_PROP_NAME);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/DataPacketDispatcherTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetSocketAddress;\n\nimport static org.mockito.Mockito.*;\n\nclass DataPacketDispatcherTest\n{\n    private static final long CORRELATION_ID_1 = 101;\n    private static final long CORRELATION_ID_2 = 102;\n    private static final int STREAM_ID = 1010;\n    private static final int INITIAL_TERM_ID = 3;\n    private static final int ACTIVE_TERM_ID = 3;\n    private static final int SESSION_ID = 1;\n    private static final int TERM_OFFSET = 0;\n    private static final int LENGTH = DataHeaderFlyweight.HEADER_LENGTH + 100;\n    private static final int MTU_LENGTH = 1024;\n    private static final int TERM_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final InetSocketAddress SRC_ADDRESS = new InetSocketAddress(\"localhost\", 4510);\n    private static final int STREAM_SESSION_LIMIT = 10;\n\n    private final DriverConductorProxy mockConductorProxy = mock(DriverConductorProxy.class);\n    private final Receiver mockReceiver = mock(Receiver.class);\n    private final DataPacketDispatcher dispatcher = new DataPacketDispatcher(\n        mockConductorProxy, mockReceiver, STREAM_SESSION_LIMIT);\n    private final DataHeaderFlyweight mockHeader = mock(DataHeaderFlyweight.class);\n    private final SetupFlyweight mockSetupHeader = mock(SetupFlyweight.class);\n    private final UnsafeBuffer mockBuffer = mock(UnsafeBuffer.class);\n    private final PublicationImage mockImage = mock(PublicationImage.class);\n    private final ReceiveChannelEndpoint mockChannelEndpoint = mock(ReceiveChannelEndpoint.class);\n\n    @BeforeEach\n    void setUp()\n    {\n        when(mockHeader.sessionId()).thenReturn(SESSION_ID);\n        when(mockHeader.streamId()).thenReturn(STREAM_ID);\n        when(mockHeader.termId()).thenReturn(ACTIVE_TERM_ID);\n        when(mockHeader.termOffset()).thenReturn(TERM_OFFSET);\n\n        when(mockImage.sessionId()).thenReturn(SESSION_ID);\n        when(mockImage.streamId()).thenReturn(STREAM_ID);\n\n        when(mockSetupHeader.sessionId()).thenReturn(SESSION_ID);\n        when(mockSetupHeader.streamId()).thenReturn(STREAM_ID);\n        when(mockSetupHeader.activeTermId()).thenReturn(ACTIVE_TERM_ID);\n        when(mockSetupHeader.initialTermId()).thenReturn(INITIAL_TERM_ID);\n        when(mockSetupHeader.termOffset()).thenReturn(TERM_OFFSET);\n        when(mockSetupHeader.mtuLength()).thenReturn(MTU_LENGTH);\n        when(mockSetupHeader.termLength()).thenReturn(TERM_LENGTH);\n    }\n\n    @Test\n    void shouldElicitSetupMessageWhenDataArrivesForSubscriptionWithoutImage()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n\n        verify(mockImage, never()).insertPacket(anyInt(), anyInt(), any(), anyInt(), anyInt(), any());\n        verify(mockChannelEndpoint).sendSetupElicitingStatusMessage(0, SRC_ADDRESS, SESSION_ID, STREAM_ID);\n        verify(mockReceiver).addPendingSetupMessage(SESSION_ID, STREAM_ID, 0, mockChannelEndpoint, false, SRC_ADDRESS);\n    }\n\n    @Test\n    void shouldOnlyElicitSetupMessageOnceWhenDataArrivesForSubscriptionWithoutImage()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n\n        verify(mockImage, never()).insertPacket(anyInt(), anyInt(), any(), anyInt(), anyInt(), any());\n        verify(mockChannelEndpoint).sendSetupElicitingStatusMessage(0, SRC_ADDRESS, SESSION_ID, STREAM_ID);\n        verify(mockReceiver).addPendingSetupMessage(SESSION_ID, STREAM_ID, 0, mockChannelEndpoint, false, SRC_ADDRESS);\n    }\n\n    @Test\n    void shouldElicitSetupMessageAgainWhenDataArrivesForSubscriptionWithoutImageAfterRemovePendingSetup()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n        dispatcher.removePendingSetup(SESSION_ID, STREAM_ID);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n\n        verify(mockImage, never()).insertPacket(anyInt(), anyInt(), any(), anyInt(), anyInt(), any());\n        verify(mockChannelEndpoint, times(2)).sendSetupElicitingStatusMessage(0, SRC_ADDRESS, SESSION_ID, STREAM_ID);\n        verify(mockReceiver, times(2))\n            .addPendingSetupMessage(SESSION_ID, STREAM_ID, 0, mockChannelEndpoint, false, SRC_ADDRESS);\n    }\n\n    @Test\n    void shouldRequestCreateImageUponReceivingSetup()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n\n        verify(mockConductorProxy).createPublicationImage(\n            SESSION_ID, STREAM_ID, INITIAL_TERM_ID, ACTIVE_TERM_ID, TERM_OFFSET, TERM_LENGTH,\n            MTU_LENGTH, 0, (short)0, SRC_ADDRESS, SRC_ADDRESS, mockChannelEndpoint);\n    }\n\n    @Test\n    void shouldOnlyRequestCreateImageOnceUponReceivingSetup()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n        dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n        dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n\n        verify(mockConductorProxy).createPublicationImage(\n            SESSION_ID, STREAM_ID, INITIAL_TERM_ID, ACTIVE_TERM_ID, TERM_OFFSET, TERM_LENGTH,\n            MTU_LENGTH, 0, (short)0, SRC_ADDRESS, SRC_ADDRESS, mockChannelEndpoint);\n    }\n\n    @Test\n    void shouldNotRequestCreateImageOnceUponReceivingSetupAfterImageAdded()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage);\n        dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n\n        verifyNoInteractions(mockConductorProxy);\n    }\n\n    @Test\n    void shouldSetImageInactiveOnRemoveSubscription()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage);\n        dispatcher.removeSubscription(STREAM_ID);\n\n        verify(mockImage).deactivate();\n    }\n\n    @Test\n    void shouldSetImageInactiveOnRemoveImage()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage);\n        dispatcher.removePublicationImage(mockImage);\n\n        verify(mockImage).deactivate();\n    }\n\n    @Test\n    void shouldIgnoreDataAndSetupAfterImageRemovedButHasNotEndedStream()\n    {\n        when(mockImage.isEndOfStream()).thenReturn(false);\n\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage);\n        dispatcher.removePublicationImage(mockImage);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n        dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n\n        verify(mockImage, never()).insertPacket(anyInt(), anyInt(), any(), anyInt(), anyInt(), any());\n\n        verifyNoInteractions(mockConductorProxy);\n        verifyNoInteractions(mockReceiver);\n    }\n\n    @Test\n    void shouldIgnoreDataButAllowSetupAfterImageRemovedWhenEndOfStreamReached()\n    {\n        when(mockImage.isEndOfStream()).thenReturn(true);\n\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage);\n        dispatcher.removePublicationImage(mockImage);\n\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n        verify(mockImage, never()).insertPacket(anyInt(), anyInt(), any(), anyInt(), anyInt(), any());\n\n        dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n        verify(mockConductorProxy).createPublicationImage(\n            anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),\n            anyShort(), any(), any(), any());\n        verify(mockReceiver).addPendingSetupMessage(anyInt(), anyInt(), anyInt(), any(), anyBoolean(), any());\n\n        verifyNoMoreInteractions(mockConductorProxy);\n        verifyNoMoreInteractions(mockReceiver);\n    }\n\n    @Test\n    void shouldNotIgnoreDataAndSetupAfterImageRemovedAndCoolDownRemoved()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage);\n        dispatcher.removePublicationImage(mockImage);\n        dispatcher.removeCoolDown(SESSION_ID, STREAM_ID);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n        dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n\n        verify(mockImage, never()).insertPacket(anyInt(), anyInt(), any(), anyInt(), anyInt(), any());\n\n        final InOrder inOrder = inOrder(mockChannelEndpoint, mockReceiver, mockConductorProxy);\n        inOrder.verify(mockChannelEndpoint).sendSetupElicitingStatusMessage(0, SRC_ADDRESS, SESSION_ID, STREAM_ID);\n        inOrder.verify(mockReceiver)\n            .addPendingSetupMessage(SESSION_ID, STREAM_ID, 0, mockChannelEndpoint, false, SRC_ADDRESS);\n        inOrder.verify(mockConductorProxy).createPublicationImage(\n            SESSION_ID, STREAM_ID, INITIAL_TERM_ID, ACTIVE_TERM_ID, TERM_OFFSET, TERM_LENGTH,\n            MTU_LENGTH, 0, (short)0, SRC_ADDRESS, SRC_ADDRESS, mockChannelEndpoint);\n    }\n\n    @Test\n    void shouldDispatchDataToCorrectImage()\n    {\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n\n        verify(mockImage).activate();\n        verify(mockImage).insertPacket(ACTIVE_TERM_ID, TERM_OFFSET, mockBuffer, LENGTH, 0, SRC_ADDRESS);\n    }\n\n    @Test\n    void shouldNotRemoveNewPublicationImageFromOldRemovePublicationImageAfterRemoveSubscription()\n    {\n        final PublicationImage mockImage1 = mock(PublicationImage.class);\n        final PublicationImage mockImage2 = mock(PublicationImage.class);\n\n        when(mockImage1.sessionId()).thenReturn(SESSION_ID);\n        when(mockImage1.streamId()).thenReturn(STREAM_ID);\n        when(mockImage1.correlationId()).thenReturn(CORRELATION_ID_1);\n\n        when(mockImage2.sessionId()).thenReturn(SESSION_ID);\n        when(mockImage2.streamId()).thenReturn(STREAM_ID);\n        when(mockImage2.correlationId()).thenReturn(CORRELATION_ID_2);\n\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage1);\n        dispatcher.removeSubscription(STREAM_ID);\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage2);\n        dispatcher.removePublicationImage(mockImage1);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n\n        verify(mockImage1, never()).insertPacket(anyInt(), anyInt(), any(), anyInt(), anyInt(), any());\n        verify(mockImage2).insertPacket(ACTIVE_TERM_ID, TERM_OFFSET, mockBuffer, LENGTH, 0, SRC_ADDRESS);\n    }\n\n    @Test\n    void shouldRemoveSessionSpecificSubscriptionWithoutAny()\n    {\n        dispatcher.addSubscription(STREAM_ID, SESSION_ID);\n        dispatcher.removeSubscription(STREAM_ID, SESSION_ID);\n    }\n\n    @Test\n    void shouldRemoveSessionSpecificSubscriptionAndStillReceiveIntoImage()\n    {\n        final PublicationImage mockImage = mock(PublicationImage.class);\n\n        when(mockImage.sessionId()).thenReturn(SESSION_ID);\n        when(mockImage.streamId()).thenReturn(STREAM_ID);\n        when(mockImage.correlationId()).thenReturn(CORRELATION_ID_1);\n\n        dispatcher.addSubscription(STREAM_ID);\n        dispatcher.addPublicationImage(mockImage);\n        dispatcher.addSubscription(STREAM_ID, SESSION_ID);\n        dispatcher.removeSubscription(STREAM_ID, SESSION_ID);\n        dispatcher.onDataPacket(mockChannelEndpoint, mockHeader, mockBuffer, LENGTH, SRC_ADDRESS, 0);\n\n        verify(mockImage).insertPacket(ACTIVE_TERM_ID, TERM_OFFSET, mockBuffer, LENGTH, 0, SRC_ADDRESS);\n    }\n\n    @Test\n    void shouldPreventNewSessionsOnceStreamSessionLimitIsExceeded()\n    {\n        final PublicationImage mockImage = mock(PublicationImage.class);\n\n        when(mockImage.streamId()).thenReturn(STREAM_ID);\n        when(mockImage.correlationId()).thenReturn(CORRELATION_ID_1);\n\n        dispatcher.addSubscription(STREAM_ID);\n\n        int sessionId = SESSION_ID;\n        for (int i = 0; i < STREAM_SESSION_LIMIT; i++)\n        {\n            when(mockImage.sessionId()).thenReturn(sessionId);\n            when(mockSetupHeader.sessionId()).thenReturn(sessionId);\n            sessionId++;\n\n            dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n            dispatcher.addPublicationImage(mockImage);\n        }\n        verify(mockConductorProxy, times(10)).createPublicationImage(\n            anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),\n            anyShort(), any(), any(), any());\n\n        when(mockSetupHeader.sessionId()).thenReturn(sessionId);\n        try\n        {\n            dispatcher.onSetupMessage(mockChannelEndpoint, mockSetupHeader, SRC_ADDRESS, 0);\n        }\n        catch (final Exception ignore)\n        {\n        }\n        verifyNoMoreInteractions(mockConductorProxy);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/DefaultMulticastFlowControlSupplierTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass DefaultMulticastFlowControlSupplierTest\n{\n    private final DefaultMulticastFlowControlSupplier supplier = new DefaultMulticastFlowControlSupplier();\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=min\",\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=min,t:100ms\",\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=min,t:100ms,g:10\",\n    })\n    void shouldReturnMinFlowControl(final String uri)\n    {\n        assertEquals(MinMulticastFlowControl.class, supplier.newInstance(UdpChannel.parse(uri), 0, 0).getClass());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=max\",\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=max,t:100ms\",\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=max,t:100ms,g:10\",\n    })\n    void shouldReturnMaxFlowControl(final String uri)\n    {\n        assertEquals(MaxMulticastFlowControl.class, supplier.newInstance(UdpChannel.parse(uri), 0, 0).getClass());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=tagged\",\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=tagged,t:100ms\",\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=tagged,t:100ms,g:10\",\n    })\n    void shouldReturnTaggedFlowControl(final String uri)\n    {\n        assertEquals(TaggedMulticastFlowControl.class, supplier.newInstance(UdpChannel.parse(uri), 0, 0).getClass());\n    }\n\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=minute\",\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=maximillian\",\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=taggedalong\",\n        \"aeron:udp?endpoint=224.20.30.39:54326|interface=localhost|fc=foobar\",\n    })\n    void shouldRejectInvalidFlowControl(final String uri)\n    {\n        assertThrows(IllegalArgumentException.class, () -> supplier.newInstance(UdpChannel.parse(uri), 0, 0));\n    }\n}"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/DefaultNameResolverTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.net.InetAddress;\nimport java.util.UUID;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nclass DefaultNameResolverTest\n{\n    private final DefaultNameResolver nameResolver = new DefaultNameResolver();\n\n    @Test\n    void resolveReturnsNullForUnknownHost()\n    {\n        final String hostName = UUID.randomUUID().toString();\n        final String uriParamName = \"endpoint\";\n        final boolean isReResolution = false;\n\n        assertNull(nameResolver.resolve(hostName, uriParamName, isReResolution));\n    }\n\n    @Test\n    void resolveResolvesLocalhostAddress()\n    {\n        final String hostName = \"localhost\";\n        final String uriParamName = \"control\";\n        final boolean isReResolution = true;\n\n        final InetAddress address = nameResolver.resolve(hostName, uriParamName, isReResolution);\n\n        assertEquals(InetAddress.getLoopbackAddress(), address);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/DriverConductorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.DriverProxy;\nimport io.aeron.ErrorCode;\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.buffer.TestLogFactory;\nimport io.aeron.driver.exceptions.InvalidChannelException;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.ReceiveChannelEndpointThreadLocals;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.media.WildcardPortManager;\nimport io.aeron.driver.status.DutyCycleStallTracker;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.exceptions.AeronEvent;\nimport io.aeron.logbuffer.HeaderWriter;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport io.aeron.test.Tests;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.IoUtil;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.ManyToOneConcurrentLinkedQueue;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.InOrder;\nimport org.mockito.stubbing.Answer;\n\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.MappedByteBuffer;\nimport java.nio.file.Path;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.BooleanSupplier;\nimport java.util.function.LongConsumer;\n\nimport static io.aeron.CommonContext.*;\nimport static io.aeron.ErrorCode.*;\nimport static io.aeron.driver.Configuration.*;\nimport static io.aeron.driver.DriverConductor.EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS;\nimport static io.aeron.driver.status.SystemCounterDescriptor.*;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.frameLengthOrdered;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.DataHeaderFlyweight.createDefaultHeader;\nimport static io.aeron.status.HeartbeatTimestamp.HEARTBEAT_TYPE_ID;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.agrona.BitUtil.align;\nimport static org.agrona.concurrent.status.CountersReader.*;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass DriverConductorTest\n{\n    private static final String CHANNEL_4000 = \"aeron:udp?endpoint=localhost:4000\";\n    private static final String CHANNEL_4001 = \"aeron:udp?endpoint=localhost:4001\";\n    private static final String CHANNEL_4002 = \"aeron:udp?endpoint=localhost:4002\";\n    private static final String CHANNEL_4003 = \"aeron:udp?endpoint=localhost:4003\";\n    private static final String CHANNEL_4004 = \"aeron:udp?endpoint=localhost:4004\";\n    private static final String CHANNEL_4000_TAG_ID_1 = \"aeron:udp?endpoint=localhost:4000|tags=1001\";\n    private static final String CHANNEL_TAG_ID_1 = \"aeron:udp?tags=1001\";\n    private static final String CHANNEL_SUB_CONTROL_MODE_MANUAL = \"aeron:udp?control-mode=manual\";\n    private static final String CHANNEL_IPC = \"aeron:ipc\";\n    private static final String INVALID_URI = \"aeron:udp://\";\n    private static final String COUNTER_LABEL = \"counter label\";\n    private static final int SESSION_ID = 100;\n    private static final int STREAM_ID_1 = 1010;\n    private static final int STREAM_ID_2 = 1020;\n    private static final int STREAM_ID_3 = 1030;\n    private static final int STREAM_ID_4 = 1040;\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int BUFFER_LENGTH = 16 * 1024;\n    private static final int COUNTER_TYPE_ID = 101;\n    private static final int COUNTER_KEY_OFFSET = 0;\n    private static final int COUNTER_KEY_LENGTH = 12;\n    private static final int COUNTER_LABEL_OFFSET = COUNTER_KEY_OFFSET + COUNTER_KEY_LENGTH;\n    private static final int COUNTER_LABEL_LENGTH = COUNTER_LABEL.length();\n    private static final long CLIENT_LIVENESS_TIMEOUT_NS = CLIENT_LIVENESS_TIMEOUT_DEFAULT_NS;\n    private static final long PUBLICATION_LINGER_TIMEOUT_NS = PUBLICATION_LINGER_DEFAULT_NS;\n    private static final int MTU_LENGTH = MTU_LENGTH_DEFAULT;\n\n    private final ByteBuffer conductorBuffer = ByteBuffer.allocateDirect(CONDUCTOR_BUFFER_LENGTH_DEFAULT);\n    private final UnsafeBuffer counterKeyAndLabel = new UnsafeBuffer(new byte[BUFFER_LENGTH]);\n\n    private final RingBuffer toDriverCommands = spy(new ManyToOneRingBuffer(new UnsafeBuffer(conductorBuffer)));\n    private final ClientProxy mockClientProxy = mock(ClientProxy.class);\n\n    private final ErrorHandler mockErrorHandler = mock(ErrorHandler.class);\n    private final AtomicCounter mockErrorCounter = mock(AtomicCounter.class);\n\n    private final SenderProxy senderProxy = mock(SenderProxy.class);\n    private final ReceiverProxy receiverProxy = mock(ReceiverProxy.class);\n    private final DriverConductorProxy mockDriverConductorProxy = mock(DriverConductorProxy.class);\n    private ReceiveChannelEndpoint receiveChannelEndpoint = null;\n\n    private final CachedNanoClock nanoClock = new CachedNanoClock();\n    private final CachedEpochClock epochClock = new CachedEpochClock();\n\n    private SystemCounters spySystemCounters;\n\n    private CountersManager spyCountersManager;\n    private MediaDriver.Context ctx;\n    private DriverProxy driverProxy;\n    private DriverConductor driverConductor;\n\n    private final Answer<Void> closeChannelEndpointAnswer =\n        (invocation) ->\n        {\n            final Object[] args = invocation.getArguments();\n            final ReceiveChannelEndpoint channelEndpoint = (ReceiveChannelEndpoint)args[0];\n            channelEndpoint.close();\n\n            return null;\n        };\n\n    @BeforeEach\n    void before(@TempDir final Path dir)\n    {\n        counterKeyAndLabel.putInt(COUNTER_KEY_OFFSET, 42);\n        counterKeyAndLabel.putStringAscii(COUNTER_LABEL_OFFSET, COUNTER_LABEL);\n\n        spyCountersManager = spy(Tests.newCountersManager(BUFFER_LENGTH));\n        spySystemCounters = spy(new SystemCounters(spyCountersManager));\n\n        when(spySystemCounters.get(SystemCounterDescriptor.ERRORS)).thenReturn(mockErrorCounter);\n        when(mockErrorCounter.appendToLabel(any())).thenReturn(mockErrorCounter);\n\n        final DutyCycleStallTracker conductorDutyCycleTracker = new DutyCycleStallTracker(\n            spySystemCounters.get(CONDUCTOR_MAX_CYCLE_TIME),\n            spySystemCounters.get(CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED),\n            600_000_000);\n\n        final DutyCycleStallTracker nameResolverTimeTracker = new DutyCycleStallTracker(\n            spySystemCounters.get(NAME_RESOLVER_MAX_TIME),\n            spySystemCounters.get(NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED),\n            1_000_000_000);\n\n        ctx = new MediaDriver.Context()\n            .tempBuffer(new UnsafeBuffer(new byte[METADATA_LENGTH]))\n            .timerIntervalNs(DEFAULT_TIMER_INTERVAL_NS)\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .ipcTermBufferLength(TERM_BUFFER_LENGTH)\n            .unicastFlowControlSupplier(Configuration.unicastFlowControlSupplier())\n            .multicastFlowControlSupplier(Configuration.multicastFlowControlSupplier())\n            .driverCommandQueue(new ManyToOneConcurrentLinkedQueue<>())\n            .errorHandler(mockErrorHandler)\n            .logFactory(new TestLogFactory())\n            .countersManager(spyCountersManager)\n            .epochClock(epochClock)\n            .nanoClock(nanoClock)\n            .senderCachedNanoClock(nanoClock)\n            .receiverCachedNanoClock(nanoClock)\n            .cachedEpochClock(new CachedEpochClock())\n            .cachedNanoClock(new CachedNanoClock())\n            .sendChannelEndpointSupplier(Configuration.sendChannelEndpointSupplier())\n            .receiveChannelEndpointSupplier(Configuration.receiveChannelEndpointSupplier())\n            .congestControlSupplier(Configuration.congestionControlSupplier())\n            .toDriverCommands(toDriverCommands)\n            .clientProxy(mockClientProxy)\n            .systemCounters(spySystemCounters)\n            .receiverProxy(receiverProxy)\n            .senderProxy(senderProxy)\n            .driverConductorProxy(mockDriverConductorProxy)\n            .receiveChannelEndpointThreadLocals(new ReceiveChannelEndpointThreadLocals())\n            .conductorCycleThresholdNs(600_000_000)\n            .nameResolver(DefaultNameResolver.INSTANCE)\n            .threadingMode(ThreadingMode.DEDICATED)\n            .conductorDutyCycleTracker(conductorDutyCycleTracker)\n            .nameResolverTimeTracker(nameResolverTimeTracker)\n            .senderPortManager(new WildcardPortManager(WildcardPortManager.EMPTY_PORT_RANGE, true))\n            .receiverPortManager(new WildcardPortManager(WildcardPortManager.EMPTY_PORT_RANGE, false))\n            .asyncTaskExecutor(CALLER_RUNS_TASK_EXECUTOR)\n            .asyncTaskExecutorThreads(0)\n            .cncByteBuffer(IoUtil.mapNewFile(dir.resolve(\"test.cnc\").toFile(), 1024))\n            .countersMetaDataBuffer((UnsafeBuffer)spyCountersManager.metaDataBuffer())\n            .countersValuesBuffer((UnsafeBuffer)spyCountersManager.valuesBuffer());\n\n        driverProxy = new DriverProxy(toDriverCommands, toDriverCommands.nextCorrelationId());\n        driverConductor = new DriverConductor(ctx);\n        driverConductor.onStart();\n\n        doAnswer(closeChannelEndpointAnswer).when(receiverProxy).closeReceiveChannelEndpoint(any());\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.close(receiveChannelEndpoint);\n        driverConductor.onClose();\n    }\n\n    @Test\n    void shouldErrorWhenOriginalPublicationHasNoDistinguishingCharacteristicBeyondTag()\n    {\n        final String expectedMessage =\n            \"URI must have explicit control, endpoint, or be manual control-mode when original:\";\n\n        driverProxy.addPublication(\"aeron:udp?tags=1001\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockErrorHandler).onError(argThat(\n            (ex) ->\n            {\n                assertThat(ex, instanceOf(InvalidChannelException.class));\n                assertThat(ex.getMessage(), containsString(expectedMessage));\n                return true;\n            }));\n    }\n\n\n    @Test\n    void shouldBeAbleToAddSinglePublication()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        verify(senderProxy).registerSendChannelEndpoint(any());\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n\n        final NetworkPublication publication = captor.getValue();\n        assertEquals(STREAM_ID_1, publication.streamId());\n\n        verify(mockClientProxy).onPublicationReady(\n            anyLong(), anyLong(), eq(STREAM_ID_1), anyInt(), any(), anyInt(), anyInt(), eq(false));\n    }\n\n    @Test\n    void shouldBeAbleToAddPublicationForReplay()\n    {\n        final int mtu = 1024 * 8;\n        final int termLength = 128 * 1024;\n        final int initialTermId = 7;\n        final int termId = 11;\n        final int termOffset = 64;\n        final String params =\n            \"|mtu=\" + mtu +\n            \"|term-length=\" + termLength +\n            \"|init-term-id=\" + initialTermId +\n            \"|term-id=\" + termId +\n            \"|term-offset=\" + termOffset;\n\n        driverProxy.addExclusivePublication(CHANNEL_4000 + params, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        verify(senderProxy).registerSendChannelEndpoint(any());\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n\n        final NetworkPublication publication = captor.getValue();\n        assertEquals(STREAM_ID_1, publication.streamId());\n        assertEquals(mtu, publication.mtuLength());\n\n        final long expectedPosition = termLength * (termId - initialTermId) + termOffset;\n        assertEquals(expectedPosition, publication.producerPosition());\n        assertEquals(expectedPosition, publication.consumerPosition());\n\n        verify(mockClientProxy).onPublicationReady(\n            anyLong(), anyLong(), eq(STREAM_ID_1), anyInt(), any(), anyInt(), anyInt(), eq(true));\n    }\n\n    @Test\n    void shouldBeAbleToAddIpcPublicationForReplay()\n    {\n        final int termLength = 128 * 1024;\n        final int initialTermId = 7;\n        final int termId = 11;\n        final int termOffset = 64;\n        final String params =\n            \"?term-length=\" + termLength +\n            \"|init-term-id=\" + initialTermId +\n            \"|term-id=\" + termId +\n            \"|term-offset=\" + termOffset;\n\n        driverProxy.addExclusivePublication(CHANNEL_IPC + params, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);\n        verify(mockClientProxy).onPublicationReady(\n            anyLong(), captor.capture(), eq(STREAM_ID_1), anyInt(), any(), anyInt(), anyInt(), eq(true));\n\n        final long registrationId = captor.getValue();\n        final IpcPublication publication = driverConductor.getIpcPublication(registrationId);\n        assertNotNull(publication);\n        assertEquals(STREAM_ID_1, publication.streamId());\n\n        final long expectedPosition = termLength * (termId - initialTermId) + termOffset;\n        assertEquals(expectedPosition, publication.producerPosition());\n        assertEquals(expectedPosition, publication.consumerPosition());\n    }\n\n    @Test\n    void shouldBeAbleToAddSingleSubscription()\n    {\n        final long id = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor.capture());\n        receiveChannelEndpoint = captor.getValue();\n\n        verify(receiverProxy).addSubscription(any(), eq(STREAM_ID_1));\n        verify(mockClientProxy).onSubscriptionReady(eq(id), anyInt());\n    }\n\n    @Test\n    void shouldBeAbleToAddAndRemoveSingleSubscription()\n    {\n        final long id = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n        driverProxy.removeSubscription(id);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        verify(receiverProxy).registerReceiveChannelEndpoint(any());\n        verify(receiverProxy).closeReceiveChannelEndpoint(any());\n    }\n\n    @Test\n    void shouldEventuallyAddSubscriptionMatchingAClosedSubscriptionChannel()\n    {\n        final long id1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n        driverProxy.removeSubscription(id1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        verify(receiverProxy).registerReceiveChannelEndpoint(any());\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).closeReceiveChannelEndpoint(captor.capture());\n\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        verify(mockClientProxy, times(1)).onError(id2, ErrorCode.RESOURCE_TEMPORARILY_UNAVAILABLE,\n            \"ReceiveChannelEndpoint found in CLOSING state, please retry\");\n\n        driverConductor.receiveChannelEndpointClosed(captor.getValue());\n\n        final long id3 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        verify(mockClientProxy, times(1)).onSubscriptionReady(eq(id3), anyInt());\n    }\n\n    @Test\n    void shouldBeAbleToAddMultipleStreams()\n    {\n        driverProxy.addPublication(CHANNEL_4001, STREAM_ID_1);\n        driverProxy.addPublication(CHANNEL_4002, STREAM_ID_2);\n        driverProxy.addPublication(CHANNEL_4003, STREAM_ID_3);\n        driverProxy.addPublication(CHANNEL_4004, STREAM_ID_4);\n\n        while (true)\n        {\n            if (0 == driverConductor.doWork())\n            {\n                break;\n            }\n        }\n\n        verify(senderProxy, times(4)).newNetworkPublication(any());\n    }\n\n    @Test\n    void shouldBeAbleToRemoveSingleStream()\n    {\n        final long id = driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        driverProxy.removePublication(id, false);\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS + PUBLICATION_LINGER_TIMEOUT_NS * 2) - nanoClock.nanoTime() < 0);\n\n        verify(senderProxy).registerSendChannelEndpoint(any());\n        verify(senderProxy).removeNetworkPublication(any());\n    }\n\n    @Test\n    void shouldBeAbleToRemoveMultipleStreams()\n    {\n        final long id1 = driverProxy.addPublication(CHANNEL_4001, STREAM_ID_1);\n        final long id2 = driverProxy.addPublication(CHANNEL_4002, STREAM_ID_2);\n        final long id3 = driverProxy.addPublication(CHANNEL_4003, STREAM_ID_3);\n        final long id4 = driverProxy.addPublication(CHANNEL_4004, STREAM_ID_4);\n\n        driverProxy.removePublication(id1, false);\n        driverProxy.removePublication(id2, false);\n        driverProxy.removePublication(id3, false);\n        driverProxy.removePublication(id4, false);\n\n        doWorkUntil(\n            () -> (CLIENT_LIVENESS_TIMEOUT_NS * 2 + PUBLICATION_LINGER_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        verify(senderProxy, times(4)).removeNetworkPublication(any());\n    }\n\n    @Test\n    void shouldKeepSubscriptionMediaEndpointUponRemovalOfAllButOneSubscriber()\n    {\n        final long id1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_2);\n        driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_3);\n\n        while (true)\n        {\n            if (0 == driverConductor.doWork())\n            {\n                break;\n            }\n        }\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor.capture());\n        receiveChannelEndpoint = captor.getValue();\n\n        assertNotNull(receiveChannelEndpoint);\n        assertEquals(3, receiveChannelEndpoint.distinctSubscriptionCount());\n\n        driverProxy.removeSubscription(id1);\n        driverProxy.removeSubscription(id2);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        assertEquals(1, receiveChannelEndpoint.distinctSubscriptionCount());\n    }\n\n    @Test\n    void shouldOnlyRemoveSubscriptionMediaEndpointUponRemovalOfAllSubscribers()\n    {\n        final long id1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_2);\n        final long id3 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_3);\n\n        while (true)\n        {\n            if (0 == driverConductor.doWork())\n            {\n                break;\n            }\n        }\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor.capture());\n        receiveChannelEndpoint = captor.getValue();\n\n        assertNotNull(receiveChannelEndpoint);\n        assertEquals(3, receiveChannelEndpoint.distinctSubscriptionCount());\n\n        driverProxy.removeSubscription(id2);\n        driverProxy.removeSubscription(id3);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        assertEquals(1, receiveChannelEndpoint.distinctSubscriptionCount());\n\n        driverProxy.removeSubscription(id1);\n\n        driverConductor.doWork();\n\n        verify(receiverProxy).closeReceiveChannelEndpoint(receiveChannelEndpoint);\n    }\n\n    @Test\n    void shouldErrorOnRemovePublicationOnUnknownRegistrationId()\n    {\n        final long id = driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        driverProxy.removePublication(id + 1, false);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final InOrder inOrder = inOrder(senderProxy, mockClientProxy);\n\n        inOrder.verify(senderProxy).newNetworkPublication(any());\n        inOrder.verify(mockClientProxy).onPublicationReady(\n            anyLong(), eq(id), eq(STREAM_ID_1), anyInt(), any(), anyInt(), anyInt(), eq(false));\n        inOrder.verify(mockClientProxy).onError(anyLong(), eq(UNKNOWN_PUBLICATION), anyString());\n        inOrder.verifyNoMoreInteractions();\n\n        verify(mockErrorCounter).increment();\n        verify(mockErrorHandler).onError(any(Throwable.class));\n    }\n\n    @Test\n    void shouldAddPublicationWithMtu()\n    {\n        final int mtuLength = 4096;\n        final String mtuParam = \"|\" + CommonContext.MTU_LENGTH_PARAM_NAME + \"=\" + mtuLength;\n        driverProxy.addPublication(CHANNEL_4000 + mtuParam, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> argumentCaptor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy).newNetworkPublication(argumentCaptor.capture());\n\n        assertEquals(mtuLength, argumentCaptor.getValue().mtuLength());\n    }\n\n    @Test\n    void shouldErrorOnRemoveSubscriptionOnUnknownRegistrationId()\n    {\n        final long id1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n        driverProxy.removeSubscription(id1 + 100);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final InOrder inOrder = inOrder(receiverProxy, mockClientProxy);\n\n        inOrder.verify(receiverProxy).addSubscription(any(), anyInt());\n        inOrder.verify(mockClientProxy).onSubscriptionReady(eq(id1), anyInt());\n        inOrder.verify(mockClientProxy).onError(anyLong(), eq(UNKNOWN_SUBSCRIPTION), anyString());\n        inOrder.verifyNoMoreInteractions();\n\n        verify(mockErrorHandler).onError(any(Throwable.class));\n    }\n\n    @Test\n    void shouldErrorOnAddSubscriptionWithInvalidChannel()\n    {\n        driverProxy.addSubscription(INVALID_URI, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        verify(senderProxy, never()).newNetworkPublication(any());\n\n        verify(mockClientProxy).onError(anyLong(), eq(INVALID_CHANNEL), anyString());\n        verify(mockClientProxy, never()).operationSucceeded(anyLong());\n\n        verify(mockErrorCounter).increment();\n        verify(mockErrorHandler).onError(any(Throwable.class));\n    }\n\n    @Test\n    void shouldTimeoutPublication()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n\n        final NetworkPublication publication = captor.getValue();\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS + PUBLICATION_LINGER_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        verify(senderProxy).removeNetworkPublication(eq(publication));\n        verify(senderProxy).registerSendChannelEndpoint(any());\n    }\n\n    @Test\n    void shouldNotTimeoutPublicationOnKeepAlive()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n\n        final NetworkPublication publication = captor.getValue();\n        final AtomicCounter heartbeatCounter = clientHeartbeatCounter(spyCountersManager);\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS / 2) - nanoClock.nanoTime() <= 0);\n\n        heartbeatCounter.setRelease(epochClock.time());\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS + 1000) - nanoClock.nanoTime() <= 0);\n\n        heartbeatCounter.setRelease(epochClock.time());\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2);\n\n        verify(senderProxy, never()).removeNetworkPublication(eq(publication));\n    }\n\n    @Test\n    void shouldTimeoutPublicationWithNoKeepaliveButNotDrained()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n\n        final NetworkPublication publication = captor.getValue();\n\n        final int termId = 101;\n        final int index = LogBufferDescriptor.indexByTerm(termId, termId);\n        final RawLog rawLog = publication.rawLog();\n        LogBufferDescriptor.rawTail(rawLog.metaData(), index, LogBufferDescriptor.packTail(termId, 0));\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[256]);\n        final HeaderWriter headerWriter = HeaderWriter.newInstance(\n            createDefaultHeader(SESSION_ID, STREAM_ID_1, termId));\n\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(termId);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(10);\n\n        publication.onStatusMessage(msg, new InetSocketAddress(\"localhost\", 4059), mockDriverConductorProxy);\n        appendUnfragmentedMessage(\n            rawLog, index, 0, termId, headerWriter, srcBuffer, 0, 256);\n\n        assertEquals(NetworkPublication.State.ACTIVE, publication.state());\n\n        doWorkUntil(\n            () -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 1.25,\n            (timeNs) ->\n            {\n                publication.onStatusMessage(msg, new InetSocketAddress(\"localhost\", 4059), mockDriverConductorProxy);\n                publication.updateHasReceivers(timeNs);\n            });\n\n        assertThat(publication.state(),\n            Matchers.anyOf(is(NetworkPublication.State.DRAINING), is(NetworkPublication.State.LINGER)));\n\n        final long endTime = nanoClock.nanoTime() + publicationConnectionTimeoutNs() + DEFAULT_TIMER_INTERVAL_NS;\n        doWorkUntil(() -> nanoClock.nanoTime() >= endTime, publication::updateHasReceivers);\n\n        assertThat(publication.state(),\n            Matchers.anyOf(is(NetworkPublication.State.LINGER), is(NetworkPublication.State.DONE)));\n\n        nanoClock.advance(DEFAULT_TIMER_INTERVAL_NS + PUBLICATION_LINGER_TIMEOUT_NS);\n        driverConductor.doWork();\n        assertEquals(NetworkPublication.State.DONE, publication.state());\n\n        verify(senderProxy).removeNetworkPublication(eq(publication));\n        verify(senderProxy).registerSendChannelEndpoint(any());\n    }\n\n    @Test\n    void shouldTimeoutSubscription()\n    {\n        driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor.capture());\n        receiveChannelEndpoint = captor.getValue();\n\n        verify(receiverProxy).addSubscription(eq(receiveChannelEndpoint), eq(STREAM_ID_1));\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2);\n\n        verify(receiverProxy, times(1))\n            .removeSubscription(eq(receiveChannelEndpoint), eq(STREAM_ID_1));\n\n        verify(receiverProxy).closeReceiveChannelEndpoint(receiveChannelEndpoint);\n    }\n\n    @Test\n    void shouldNotTimeoutSubscriptionOnKeepAlive()\n    {\n        driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor.capture());\n        receiveChannelEndpoint = captor.getValue();\n\n        verify(receiverProxy).addSubscription(eq(receiveChannelEndpoint), eq(STREAM_ID_1));\n        final AtomicCounter heartbeatCounter = clientHeartbeatCounter(spyCountersManager);\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS);\n\n        heartbeatCounter.setRelease(epochClock.time());\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS + 1000);\n\n        heartbeatCounter.setRelease(epochClock.time());\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2);\n\n        verify(receiverProxy, never()).removeSubscription(any(), anyInt());\n    }\n\n    @Test\n    void shouldCreateImageOnSubscription()\n    {\n        final InetSocketAddress sourceAddress = new InetSocketAddress(\"localhost\", 4400);\n        final int initialTermId = 1;\n        final int activeTermId = 2;\n        final int termOffset = 160;\n\n        driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor1 = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor1.capture());\n        receiveChannelEndpoint = captor1.getValue();\n\n        driverConductor.onCreatePublicationImage(\n            SESSION_ID, STREAM_ID_1, initialTermId, activeTermId, termOffset, TERM_BUFFER_LENGTH, MTU_LENGTH, 0,\n            (short)0, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint);\n\n        final ArgumentCaptor<PublicationImage> captor2 = ArgumentCaptor.forClass(PublicationImage.class);\n        verify(receiverProxy).newPublicationImage(eq(receiveChannelEndpoint), captor2.capture());\n\n        final PublicationImage publicationImage = captor2.getValue();\n        assertEquals(SESSION_ID, publicationImage.sessionId());\n        assertEquals(STREAM_ID_1, publicationImage.streamId());\n\n        verify(mockClientProxy).onAvailableImage(\n            anyLong(), eq(STREAM_ID_1), eq(SESSION_ID), anyLong(), anyInt(), anyString(), anyString());\n    }\n\n    @Test\n    void shouldNotCreateImageOnUnknownSubscription()\n    {\n        final InetSocketAddress sourceAddress = new InetSocketAddress(\"localhost\", 4400);\n\n        driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor.capture());\n        receiveChannelEndpoint = captor.getValue();\n\n        driverConductor.onCreatePublicationImage(\n            SESSION_ID, STREAM_ID_2, 1, 1, 0, TERM_BUFFER_LENGTH, MTU_LENGTH, 0,\n            (short)0, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint);\n\n        verify(receiverProxy, never()).newPublicationImage(any(), any());\n        verify(mockClientProxy, never()).onAvailableImage(\n            anyLong(), anyInt(), anyInt(), anyLong(), anyInt(), anyString(), anyString());\n    }\n\n    @Test\n    void shouldSignalInactiveImageWhenImageTimesOut()\n    {\n        final InetSocketAddress sourceAddress = new InetSocketAddress(\"localhost\", 4400);\n\n        final long subId = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor1 = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor1.capture());\n        receiveChannelEndpoint = captor1.getValue();\n\n        driverConductor.onCreatePublicationImage(\n            SESSION_ID, STREAM_ID_1, 1, 1, 0, TERM_BUFFER_LENGTH, MTU_LENGTH, 0,\n            (short)0, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint);\n\n        final ArgumentCaptor<PublicationImage> captor2 = ArgumentCaptor.forClass(PublicationImage.class);\n        verify(receiverProxy).newPublicationImage(eq(receiveChannelEndpoint), captor2.capture());\n\n        final PublicationImage publicationImage = captor2.getValue();\n\n        publicationImage.activate();\n        publicationImage.deactivate();\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= imageLivenessTimeoutNs() + 1000);\n\n        verify(mockClientProxy).onUnavailableImage(\n            eq(publicationImage.correlationId()), eq(subId), eq(STREAM_ID_1), anyString());\n    }\n\n    @Test\n    void shouldAlwaysGiveNetworkPublicationCorrelationIdToClientCallbacks()\n    {\n        final InetSocketAddress sourceAddress = new InetSocketAddress(\"localhost\", 4400);\n\n        final long subId1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor1 = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor1.capture());\n        receiveChannelEndpoint = captor1.getValue();\n\n        driverConductor.onCreatePublicationImage(\n            SESSION_ID, STREAM_ID_1, 1, 1, 0, TERM_BUFFER_LENGTH, MTU_LENGTH, 0,\n            (short)0, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint);\n\n        final ArgumentCaptor<PublicationImage> captor2 = ArgumentCaptor.forClass(PublicationImage.class);\n        verify(receiverProxy).newPublicationImage(eq(receiveChannelEndpoint), captor2.capture());\n\n        final PublicationImage publicationImage = captor2.getValue();\n\n        publicationImage.activate();\n\n        final long subId2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        publicationImage.deactivate();\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= imageLivenessTimeoutNs() + 1000);\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy, times(2)).onAvailableImage(\n            eq(publicationImage.correlationId()),\n            eq(STREAM_ID_1),\n            eq(SESSION_ID),\n            anyLong(),\n            anyInt(),\n            anyString(),\n            anyString());\n        inOrder.verify(mockClientProxy, times(1)).onUnavailableImage(\n            eq(publicationImage.correlationId()), eq(subId1), eq(STREAM_ID_1), anyString());\n        inOrder.verify(mockClientProxy, times(1)).onUnavailableImage(\n            eq(publicationImage.correlationId()), eq(subId2), eq(STREAM_ID_1), anyString());\n    }\n\n    @Test\n    void shouldNotSendAvailableImageWhileImageNotActiveOnAddSubscription()\n    {\n        final InetSocketAddress sourceAddress = new InetSocketAddress(\"localhost\", 4400);\n\n        final long subOneId = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<ReceiveChannelEndpoint> captor1 = ArgumentCaptor.forClass(ReceiveChannelEndpoint.class);\n        verify(receiverProxy).registerReceiveChannelEndpoint(captor1.capture());\n        receiveChannelEndpoint = captor1.getValue();\n\n        driverConductor.onCreatePublicationImage(\n            SESSION_ID, STREAM_ID_1, 1, 1, 0, TERM_BUFFER_LENGTH, MTU_LENGTH, 0,\n            (short)0, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint);\n\n        final ArgumentCaptor<PublicationImage> captor2 = ArgumentCaptor.forClass(PublicationImage.class);\n        verify(receiverProxy).newPublicationImage(eq(receiveChannelEndpoint), captor2.capture());\n\n        final PublicationImage publicationImage = captor2.getValue();\n\n        publicationImage.activate();\n        publicationImage.deactivate();\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= imageLivenessTimeoutNs() / 2);\n\n        final AtomicCounter heartbeatCounter = clientHeartbeatCounter(spyCountersManager);\n        heartbeatCounter.setRelease(epochClock.time());\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= imageLivenessTimeoutNs() + 1000);\n\n        final long subTwoId = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy, times(1)).onSubscriptionReady(eq(subOneId), anyInt());\n        inOrder.verify(mockClientProxy, times(1)).onAvailableImage(\n            eq(publicationImage.correlationId()),\n            eq(STREAM_ID_1),\n            eq(SESSION_ID),\n            anyLong(),\n            anyInt(),\n            anyString(),\n            anyString());\n        inOrder.verify(mockClientProxy, times(1)).onUnavailableImage(\n            eq(publicationImage.correlationId()), eq(subOneId), eq(STREAM_ID_1), anyString());\n        inOrder.verify(mockClientProxy, times(1)).onSubscriptionReady(eq(subTwoId), anyInt());\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void shouldBeAbleToAddSingleIpcPublication()\n    {\n        final long id = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        assertNotNull(driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE));\n        verify(mockClientProxy).onPublicationReady(\n            anyLong(), eq(id), eq(STREAM_ID_1), anyInt(), any(), anyInt(), anyInt(), eq(false));\n    }\n\n    @Test\n    void shouldBeAbleToAddIpcPublicationThenSubscription()\n    {\n        final long idPub = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n        final long idSub = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy).onPublicationReady(\n            anyLong(), eq(idPub), eq(STREAM_ID_1), anyInt(), any(), anyInt(), anyInt(), eq(false));\n        inOrder.verify(mockClientProxy).onSubscriptionReady(eq(idSub), anyInt());\n        inOrder.verify(mockClientProxy).onAvailableImage(\n            eq(ipcPublication.registrationId()), eq(STREAM_ID_1), eq(ipcPublication.sessionId()),\n            anyLong(), anyInt(), eq(ipcPublication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldBeAbleToAddThenRemoveTheAddIpcPublicationWithExistingSubscription()\n    {\n        final long idSub = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1);\n        final long idPubOne = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final IpcPublication ipcPublicationOne = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublicationOne);\n\n        final long idPubOneRemove = driverProxy.removePublication(idPubOne, false);\n        driverConductor.doWork();\n\n        final long idPubTwo = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n        driverConductor.doWork();\n\n        final IpcPublication ipcPublicationTwo = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublicationTwo);\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy).onSubscriptionReady(eq(idSub), anyInt());\n        inOrder.verify(mockClientProxy).onPublicationReady(\n            anyLong(), eq(idPubOne), eq(STREAM_ID_1), anyInt(), any(), anyInt(), anyInt(), eq(false));\n        inOrder.verify(mockClientProxy).onAvailableImage(\n            eq(ipcPublicationOne.registrationId()), eq(STREAM_ID_1), eq(ipcPublicationOne.sessionId()),\n            anyLong(), anyInt(), eq(ipcPublicationOne.rawLog().fileName()), anyString());\n        inOrder.verify(mockClientProxy).operationSucceeded(eq(idPubOneRemove));\n        inOrder.verify(mockClientProxy).onPublicationReady(\n            anyLong(), eq(idPubTwo), eq(STREAM_ID_1), anyInt(), any(), anyInt(), anyInt(), eq(false));\n        inOrder.verify(mockClientProxy).onAvailableImage(\n            eq(ipcPublicationTwo.registrationId()), eq(STREAM_ID_1), eq(ipcPublicationTwo.sessionId()),\n            anyLong(), anyInt(), eq(ipcPublicationTwo.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldBeAbleToAddSubscriptionThenIpcPublication()\n    {\n        final long idSub = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1);\n        final long idPub = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy).onSubscriptionReady(eq(idSub), anyInt());\n        inOrder.verify(mockClientProxy).onPublicationReady(\n            anyLong(), eq(idPub), eq(STREAM_ID_1), anyInt(), any(), anyInt(), anyInt(), eq(false));\n        inOrder.verify(mockClientProxy).onAvailableImage(\n            eq(ipcPublication.registrationId()), eq(STREAM_ID_1), eq(ipcPublication.sessionId()),\n            anyLong(), anyInt(), eq(ipcPublication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldBeAbleToAddAndRemoveIpcPublication()\n    {\n        final long idAdd = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n        driverProxy.removePublication(idAdd, false);\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS);\n\n        final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNull(ipcPublication);\n    }\n\n    @Test\n    void shouldBeAbleToAddAndRemoveSubscriptionToIpcPublication()\n    {\n        final long idAdd = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1);\n        driverProxy.removeSubscription(idAdd);\n\n        doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS);\n\n        final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNull(ipcPublication);\n    }\n\n    @Test\n    void shouldBeAbleToAddAndRemoveTwoIpcPublications()\n    {\n        final long idAdd1 = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n        final long idAdd2 = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n        driverProxy.removePublication(idAdd1, false);\n\n        driverConductor.doWork();\n\n        IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        driverProxy.removePublication(idAdd2, false);\n\n        doWorkUntil(() -> CLIENT_LIVENESS_TIMEOUT_NS - nanoClock.nanoTime() <= 0);\n\n        ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNull(ipcPublication);\n    }\n\n    @Test\n    void shouldBeAbleToAddAndRemoveIpcPublicationAndSubscription()\n    {\n        final long idAdd1 = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1);\n        final long idAdd2 = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n        driverProxy.removeSubscription(idAdd1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        driverProxy.removePublication(idAdd2, false);\n\n        doWorkUntil(() -> CLIENT_LIVENESS_TIMEOUT_NS - nanoClock.nanoTime() <= 0);\n\n        ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNull(ipcPublication);\n    }\n\n    @Test\n    void shouldTimeoutIpcPublication()\n    {\n        driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNull(ipcPublication);\n    }\n\n    @Test\n    void shouldNotTimeoutIpcPublicationWithKeepalive()\n    {\n        driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        doWorkUntil(() -> CLIENT_LIVENESS_TIMEOUT_NS - nanoClock.nanoTime() <= 0);\n\n        final AtomicCounter heartbeatCounter = clientHeartbeatCounter(spyCountersManager);\n        heartbeatCounter.setRelease(epochClock.time());\n\n        doWorkUntil(() -> CLIENT_LIVENESS_TIMEOUT_NS - nanoClock.nanoTime() <= 0);\n\n        ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n    }\n\n    @Test\n    void shouldBeAbleToAddSingleSpy()\n    {\n        final long id = driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        verify(receiverProxy, never()).registerReceiveChannelEndpoint(any());\n        verify(receiverProxy, never()).addSubscription(any(), eq(STREAM_ID_1));\n        verify(mockClientProxy).onSubscriptionReady(eq(id), anyInt());\n    }\n\n    @Test\n    void shouldBeAbleToAddNetworkPublicationThenSingleSpy()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        final long idSpy = driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertTrue(publication.hasSpies());\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy).onSubscriptionReady(eq(idSpy), anyInt());\n        inOrder.verify(mockClientProxy).onAvailableImage(\n            eq(networkPublicationCorrelationId(publication)), eq(STREAM_ID_1), eq(publication.sessionId()),\n            anyLong(), anyInt(), eq(publication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldBeAbleToAddSingleSpyThenNetworkPublication()\n    {\n        final long idSpy = driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1);\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertTrue(publication.hasSpies());\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy).onSubscriptionReady(eq(idSpy), anyInt());\n        inOrder.verify(mockClientProxy).onAvailableImage(\n            eq(networkPublicationCorrelationId(publication)), eq(STREAM_ID_1), eq(publication.sessionId()),\n            anyLong(), anyInt(), eq(publication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldBeAbleToAddNetworkPublicationThenSingleSpyThenRemoveSpy()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        final long idSpy = driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1);\n        driverProxy.removeSubscription(idSpy);\n\n        while (true)\n        {\n            if (0 == driverConductor.doWork())\n            {\n                break;\n            }\n        }\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertFalse(publication.hasSpies());\n    }\n\n    @Test\n    void shouldTimeoutSpy()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertTrue(publication.hasSpies());\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        assertFalse(publication.hasSpies());\n    }\n\n    @Test\n    void shouldNotTimeoutSpyWithKeepalive()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertTrue(publication.hasSpies());\n\n        doWorkUntil(() -> CLIENT_LIVENESS_TIMEOUT_NS - nanoClock.nanoTime() <= 0);\n\n        final AtomicCounter heartbeatCounter = clientHeartbeatCounter(spyCountersManager);\n        heartbeatCounter.setRelease(epochClock.time());\n\n        doWorkUntil(() -> CLIENT_LIVENESS_TIMEOUT_NS - nanoClock.nanoTime() <= 0);\n\n        assertTrue(publication.hasSpies());\n    }\n\n    @Test\n    void shouldTimeoutNetworkPublicationWithSpy()\n    {\n        final long clientId = toDriverCommands.nextCorrelationId();\n        final DriverProxy spyDriverProxy = new DriverProxy(toDriverCommands, clientId);\n\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        final long subId = spyDriverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n        final AtomicCounter heartbeatCounter = clientHeartbeatCounter(spyCountersManager);\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS / 2) - nanoClock.nanoTime() <= 0);\n\n        heartbeatCounter.setRelease(epochClock.time());\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS + 1000) - nanoClock.nanoTime() <= 0);\n\n        heartbeatCounter.setRelease(0);\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        verify(mockClientProxy).onUnavailableImage(\n            eq(networkPublicationCorrelationId(publication)), eq(subId), eq(STREAM_ID_1), anyString());\n    }\n\n    @Test\n    void shouldOnlyCloseSendChannelEndpointOnceWithMultiplePublications()\n    {\n        final long id1 = driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        final long id2 = driverProxy.addPublication(CHANNEL_4000, STREAM_ID_2);\n        driverProxy.removePublication(id1, false);\n        driverProxy.removePublication(id2, false);\n\n        doWorkUntil(() -> (PUBLICATION_LINGER_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        verify(senderProxy, times(1)).closeSendChannelEndpoint(any());\n    }\n\n    @Test\n    void shouldOnlyCloseReceiveChannelEndpointOnceWithMultipleSubscriptions()\n    {\n        final long id1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_2);\n        driverProxy.removeSubscription(id1);\n        driverProxy.removeSubscription(id2);\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        verify(receiverProxy, times(1)).closeReceiveChannelEndpoint(any());\n    }\n\n    @Test\n    void shouldErrorWhenConflictingUnreliableSubscriptionAdded()\n    {\n        driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n        driverConductor.doWork();\n\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000 + \"|reliable=false\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onError(eq(id2), any(ErrorCode.class), anyString());\n    }\n\n    @Test\n    void shouldErrorWhenConflictingUnreliableSessionSpecificSubscriptionAdded()\n    {\n        driverProxy.addSubscription(CHANNEL_4000 + \"|session-id=1024\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000 + \"|session-id=1024|reliable=false\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onError(eq(id2), any(ErrorCode.class), anyString());\n    }\n\n    @Test\n    void shouldNotErrorWhenConflictingUnreliableSessionSpecificSubscriptionAddedToDifferentSessions()\n    {\n        final long id1 = driverProxy.addSubscription(CHANNEL_4000 + \"|session-id=1024|reliable=true\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000 + \"|session-id=1025|reliable=false\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onSubscriptionReady(eq(id1), anyInt());\n        verify(mockClientProxy).onSubscriptionReady(eq(id2), anyInt());\n    }\n\n    @Test\n    void shouldNotErrorWhenConflictingUnreliableSessionSpecificSubscriptionAddedToDifferentSessionsVsWildcard()\n    {\n        final long id1 = driverProxy.addSubscription(CHANNEL_4000 + \"|session-id=1024|reliable=false\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000 + \"|reliable=true\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        final long id3 = driverProxy.addSubscription(CHANNEL_4000 + \"|session-id=1025|reliable=false\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onSubscriptionReady(eq(id1), anyInt());\n        verify(mockClientProxy).onSubscriptionReady(eq(id2), anyInt());\n        verify(mockClientProxy).onSubscriptionReady(eq(id3), anyInt());\n    }\n\n    @Test\n    void shouldErrorWhenConflictingDefaultReliableSubscriptionAdded()\n    {\n        driverProxy.addSubscription(CHANNEL_4000 + \"|reliable=false\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onError(eq(id2), any(ErrorCode.class), anyString());\n    }\n\n    @Test\n    void shouldErrorWhenConflictingReliableSubscriptionAdded()\n    {\n        driverProxy.addSubscription(CHANNEL_4000 + \"|reliable=false\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        final long id2 = driverProxy.addSubscription(CHANNEL_4000 + \"|reliable=true\", STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onError(eq(id2), any(ErrorCode.class), anyString());\n    }\n\n    @Test\n    void shouldAddSingleCounter()\n    {\n        final long registrationId = driverProxy.addCounter(\n            COUNTER_TYPE_ID,\n            counterKeyAndLabel,\n            COUNTER_KEY_OFFSET,\n            COUNTER_KEY_LENGTH,\n            counterKeyAndLabel,\n            COUNTER_LABEL_OFFSET,\n            COUNTER_LABEL_LENGTH);\n\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onCounterReady(eq(registrationId), anyInt());\n        verify(spyCountersManager).newCounter(\n            eq(COUNTER_TYPE_ID),\n            any(),\n            anyInt(),\n            eq(COUNTER_KEY_LENGTH),\n            any(),\n            anyInt(),\n            eq(COUNTER_LABEL_LENGTH));\n    }\n\n    @Test\n    void shouldRemoveSingleCounter()\n    {\n        final long registrationId = driverProxy.addCounter(\n            COUNTER_TYPE_ID,\n            counterKeyAndLabel,\n            COUNTER_KEY_OFFSET,\n            COUNTER_KEY_LENGTH,\n            counterKeyAndLabel,\n            COUNTER_LABEL_OFFSET,\n            COUNTER_LABEL_LENGTH);\n\n        driverConductor.doWork();\n\n        final long removeCorrelationId = driverProxy.removeCounter(registrationId);\n        driverConductor.doWork();\n\n        final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy).onCounterReady(eq(registrationId), captor.capture());\n        inOrder.verify(mockClientProxy).operationSucceeded(removeCorrelationId);\n\n        verify(spyCountersManager).free(captor.getValue());\n    }\n\n    @Test\n    void shouldRemoveCounterOnClientTimeout()\n    {\n        final long registrationId = driverProxy.addCounter(\n            COUNTER_TYPE_ID,\n            counterKeyAndLabel,\n            COUNTER_KEY_OFFSET,\n            COUNTER_KEY_LENGTH,\n            counterKeyAndLabel,\n            COUNTER_LABEL_OFFSET,\n            COUNTER_LABEL_LENGTH);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);\n\n        verify(mockClientProxy).onCounterReady(eq(registrationId), captor.capture());\n\n        doWorkUntil(() -> (CLIENT_LIVENESS_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        verify(spyCountersManager).free(captor.getValue());\n    }\n\n    @Test\n    void shouldNotRemoveCounterOnClientKeepalive()\n    {\n        final long registrationId = driverProxy.addCounter(\n            COUNTER_TYPE_ID,\n            counterKeyAndLabel,\n            COUNTER_KEY_OFFSET,\n            COUNTER_KEY_LENGTH,\n            counterKeyAndLabel,\n            COUNTER_LABEL_OFFSET,\n            COUNTER_LABEL_LENGTH);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);\n\n        verify(mockClientProxy).onCounterReady(eq(registrationId), captor.capture());\n        final AtomicCounter heartbeatCounter = clientHeartbeatCounter(spyCountersManager);\n\n        doWorkUntil(() ->\n        {\n            heartbeatCounter.setRelease(epochClock.time());\n            return (CLIENT_LIVENESS_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0;\n        });\n\n        verify(spyCountersManager, never()).free(captor.getValue());\n    }\n\n    @Test\n    void shouldInformClientsOfRemovedCounter()\n    {\n        final long registrationId = driverProxy.addCounter(\n            COUNTER_TYPE_ID,\n            counterKeyAndLabel,\n            COUNTER_KEY_OFFSET,\n            COUNTER_KEY_LENGTH,\n            counterKeyAndLabel,\n            COUNTER_LABEL_OFFSET,\n            COUNTER_LABEL_LENGTH);\n\n        driverConductor.doWork();\n\n        final long removeCorrelationId = driverProxy.removeCounter(registrationId);\n        driverConductor.doWork();\n\n        final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy).onCounterReady(eq(registrationId), captor.capture());\n        inOrder.verify(mockClientProxy).operationSucceeded(removeCorrelationId);\n        inOrder.verify(mockClientProxy).onUnavailableCounter(eq(registrationId), captor.capture());\n\n        verify(spyCountersManager).free(captor.getValue());\n    }\n\n    @Test\n    void shouldAddPublicationWithSessionId()\n    {\n        final int sessionId = 4096;\n        final String sessionIdParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionId;\n        driverProxy.addPublication(CHANNEL_4000 + sessionIdParam, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> argumentCaptor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy).newNetworkPublication(argumentCaptor.capture());\n\n        assertEquals(sessionId, argumentCaptor.getValue().sessionId());\n    }\n\n    @Test\n    void shouldAddExclusivePublicationWithSessionId()\n    {\n        final int sessionId = 4096;\n        final String sessionIdParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionId;\n        driverProxy.addExclusivePublication(CHANNEL_4000 + sessionIdParam, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> argumentCaptor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy).newNetworkPublication(argumentCaptor.capture());\n\n        assertEquals(sessionId, argumentCaptor.getValue().sessionId());\n    }\n\n    @Test\n    void shouldAddPublicationWithSameSessionId()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> argumentCaptor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy).newNetworkPublication(argumentCaptor.capture());\n\n        final int sessionId = argumentCaptor.getValue().sessionId();\n        final String sessionIdParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionId;\n        driverProxy.addPublication(CHANNEL_4000 + sessionIdParam, STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy, times(2)).onPublicationReady(\n            anyLong(), anyLong(), eq(STREAM_ID_1), eq(sessionId), anyString(), anyInt(), anyInt(), eq(false));\n    }\n\n    @Test\n    void shouldAddExclusivePublicationWithSameSessionId()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> argumentCaptor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy).newNetworkPublication(argumentCaptor.capture());\n\n        final int sessionId = argumentCaptor.getValue().sessionId();\n        final String sessionIdParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + (sessionId + 1);\n        driverProxy.addExclusivePublication(CHANNEL_4000 + sessionIdParam, STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onPublicationReady(\n            anyLong(), anyLong(), eq(STREAM_ID_1), eq(sessionId), anyString(), anyInt(), anyInt(), eq(false));\n        verify(mockClientProxy).onPublicationReady(\n            anyLong(), anyLong(), eq(STREAM_ID_1), eq(sessionId + 1), anyString(), anyInt(), anyInt(), eq(true));\n    }\n\n    @Test\n    void shouldErrorOnAddPublicationWithNonEqualSessionId()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> argumentCaptor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy).newNetworkPublication(argumentCaptor.capture());\n\n        final String sessionIdParam =\n            \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + (argumentCaptor.getValue().sessionId() + 1);\n        final long correlationId = driverProxy.addPublication(CHANNEL_4000 + sessionIdParam, STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onError(eq(correlationId), eq(GENERIC_ERROR), anyString());\n        verify(mockErrorCounter).increment();\n        verify(mockErrorHandler).onError(any(Throwable.class));\n    }\n\n    @Test\n    void shouldErrorOnAddPublicationWithClashingSessionId()\n    {\n        driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1);\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> argumentCaptor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy).newNetworkPublication(argumentCaptor.capture());\n\n        final String sessionIdParam =\n            \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + argumentCaptor.getValue().sessionId();\n        final long correlationId = driverProxy.addExclusivePublication(CHANNEL_4000 + sessionIdParam, STREAM_ID_1);\n        driverConductor.doWork();\n\n        verify(mockClientProxy).onError(eq(correlationId), eq(INVALID_CHANNEL), anyString());\n        verify(mockErrorCounter).increment();\n        verify(mockErrorHandler).onError(any(Throwable.class));\n    }\n\n    @Test\n    void shouldAddIpcPublicationThenSubscriptionWithSessionId()\n    {\n        final int sessionId = -4097;\n        final String sessionIdParam = \"?\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionId;\n        final String channelIpcAndSessionId = CHANNEL_IPC + sessionIdParam;\n\n        driverProxy.addPublication(channelIpcAndSessionId, STREAM_ID_1);\n        driverProxy.addSubscription(channelIpcAndSessionId, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        verify(mockClientProxy).onAvailableImage(\n            eq(ipcPublication.registrationId()), eq(STREAM_ID_1), eq(ipcPublication.sessionId()),\n            anyLong(), anyInt(), eq(ipcPublication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldAddIpcSubscriptionThenPublicationWithSessionId()\n    {\n        final int sessionId = -4097;\n        final String sessionIdParam = \"?\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionId;\n        final String channelIpcAndSessionId = CHANNEL_IPC + sessionIdParam;\n\n        driverProxy.addSubscription(channelIpcAndSessionId, STREAM_ID_1);\n        driverProxy.addPublication(channelIpcAndSessionId, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        verify(mockClientProxy).onAvailableImage(\n            eq(ipcPublication.registrationId()), eq(STREAM_ID_1), eq(ipcPublication.sessionId()),\n            anyLong(), anyInt(), eq(ipcPublication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldNotAddIpcPublicationThenSubscriptionWithDifferentSessionId()\n    {\n        final int sessionIdPub = -4097;\n        final int sessionIdSub = -4098;\n        final String sessionIdPubParam = \"?\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionIdPub;\n        final String sessionIdSubParam = \"?\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionIdSub;\n\n        driverProxy.addPublication(CHANNEL_IPC + sessionIdPubParam, STREAM_ID_1);\n        driverProxy.addSubscription(CHANNEL_IPC + sessionIdSubParam, STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        verify(mockClientProxy, never()).onAvailableImage(\n            anyLong(), eq(STREAM_ID_1), anyInt(), anyLong(), anyInt(), anyString(), anyString());\n    }\n\n    @Test\n    void shouldNotAddIpcSubscriptionThenPublicationWithDifferentSessionId()\n    {\n        final int sessionIdPub = -4097;\n        final int sessionIdSub = -4098;\n        final String sessionIdPubParam = \"?\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionIdPub;\n        final String sessionIdSubParam = \"?\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionIdSub;\n\n        driverProxy.addSubscription(CHANNEL_IPC + sessionIdSubParam, STREAM_ID_1);\n        driverProxy.addPublication(CHANNEL_IPC + sessionIdPubParam, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1, Aeron.NULL_VALUE);\n        assertNotNull(ipcPublication);\n\n        verify(mockClientProxy, never()).onAvailableImage(\n            anyLong(), eq(STREAM_ID_1), anyInt(), anyLong(), anyInt(), anyString(), anyString());\n    }\n\n    @Test\n    void shouldAddNetworkPublicationThenSingleSpyWithSameSessionId()\n    {\n        final int sessionId = -4097;\n        final String sessionIdParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionId;\n        driverProxy.addPublication(CHANNEL_4000 + sessionIdParam, STREAM_ID_1);\n        driverProxy.addSubscription(spyForChannel(CHANNEL_4000 + sessionIdParam), STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertTrue(publication.hasSpies());\n\n        verify(mockClientProxy).onAvailableImage(\n            eq(networkPublicationCorrelationId(publication)), eq(STREAM_ID_1), eq(publication.sessionId()),\n            anyLong(), anyInt(), eq(publication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldNotAddNetworkPublicationThenSingleSpyWithDifferentSessionId()\n    {\n        final int sessionIdPub = -4097;\n        final int sessionIdSub = -4098;\n        final String sessionIdPubParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionIdPub;\n        final String sessionIdSubParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionIdSub;\n        driverProxy.addPublication(CHANNEL_4000 + sessionIdPubParam, STREAM_ID_1);\n        driverProxy.addSubscription(spyForChannel(CHANNEL_4000 + sessionIdSubParam), STREAM_ID_1);\n\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertFalse(publication.hasSpies());\n\n        verify(mockClientProxy, never()).onAvailableImage(\n            anyLong(), eq(STREAM_ID_1), anyInt(), anyLong(), anyInt(), anyString(), anyString());\n    }\n\n    @Test\n    void shouldAddSingleSpyThenNetworkPublicationWithSameSessionId()\n    {\n        final int sessionId = -4097;\n        final String sessionIdParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionId;\n        driverProxy.addSubscription(spyForChannel(CHANNEL_4000 + sessionIdParam), STREAM_ID_1);\n        driverConductor.doWork();\n        driverProxy.addPublication(CHANNEL_4000 + sessionIdParam, STREAM_ID_1);\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertTrue(publication.hasSpies());\n\n        verify(mockClientProxy).onAvailableImage(\n            eq(networkPublicationCorrelationId(publication)), eq(STREAM_ID_1), eq(publication.sessionId()),\n            anyLong(), anyInt(), eq(publication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldNotAddSingleSpyThenNetworkPublicationWithDifferentSessionId()\n    {\n        final int sessionIdPub = -4097;\n        final int sessionIdSub = -4098;\n        final String sessionIdPubParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionIdPub;\n        final String sessionIdSubParam = \"|\" + CommonContext.SESSION_ID_PARAM_NAME + \"=\" + sessionIdSub;\n        driverProxy.addSubscription(spyForChannel(CHANNEL_4000 + sessionIdSubParam), STREAM_ID_1);\n        driverProxy.addPublication(CHANNEL_4000 + sessionIdPubParam, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertFalse(publication.hasSpies());\n\n        verify(mockClientProxy, never()).onAvailableImage(\n            anyLong(), eq(STREAM_ID_1), anyInt(), anyLong(), anyInt(), anyString(), anyString());\n    }\n\n    @Test\n    void shouldUseExistingChannelEndpointOnAddPublicationWithSameTagIdAndSameStreamId()\n    {\n        final long id1 = driverProxy.addPublication(CHANNEL_4000_TAG_ID_1, STREAM_ID_1);\n        final long id2 = driverProxy.addPublication(CHANNEL_TAG_ID_1, STREAM_ID_1);\n\n        driverConductor.doWork();\n        verify(mockErrorHandler, never()).onError(any());\n\n        verify(senderProxy).registerSendChannelEndpoint(any());\n        verify(senderProxy).newNetworkPublication(any());\n\n        driverProxy.removePublication(id1, false);\n        driverProxy.removePublication(id2, false);\n\n        doWorkUntil(\n            () -> (PUBLICATION_LINGER_TIMEOUT_NS * 2 + CLIENT_LIVENESS_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        verify(senderProxy).closeSendChannelEndpoint(any());\n    }\n\n    @Test\n    void shouldUseExistingChannelEndpointOnAddPublicationWithSameTagIdDifferentStreamId()\n    {\n        final long id1 = driverProxy.addPublication(CHANNEL_4000_TAG_ID_1, STREAM_ID_1);\n        final long id2 = driverProxy.addPublication(CHANNEL_TAG_ID_1, STREAM_ID_2);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n        verify(mockErrorHandler, never()).onError(any());\n\n        verify(senderProxy).registerSendChannelEndpoint(any());\n        verify(senderProxy, times(2)).newNetworkPublication(any());\n\n        driverProxy.removePublication(id1, false);\n        driverProxy.removePublication(id2, false);\n\n        doWorkUntil(\n            () -> (PUBLICATION_LINGER_TIMEOUT_NS * 2 + CLIENT_LIVENESS_TIMEOUT_NS * 2) - nanoClock.nanoTime() <= 0);\n\n        verify(senderProxy).closeSendChannelEndpoint(any());\n    }\n\n    @Test\n    void shouldUseExistingChannelEndpointOnAddSubscriptionWithSameTagId()\n    {\n        final long id1 = driverProxy.addSubscription(CHANNEL_4000_TAG_ID_1, STREAM_ID_1);\n        final long id2 = driverProxy.addSubscription(CHANNEL_TAG_ID_1, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n        verify(mockErrorHandler, never()).onError(any());\n\n        verify(receiverProxy).registerReceiveChannelEndpoint(any());\n\n        driverProxy.removeSubscription(id1);\n        driverProxy.removeSubscription(id2);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        verify(receiverProxy).closeReceiveChannelEndpoint(any());\n    }\n\n    @Test\n    void shouldUseUniqueChannelEndpointOnAddSubscriptionWithNoDistinguishingCharacteristics()\n    {\n        final long id1 = driverProxy.addSubscription(CHANNEL_SUB_CONTROL_MODE_MANUAL, STREAM_ID_1);\n        final long id2 = driverProxy.addSubscription(CHANNEL_SUB_CONTROL_MODE_MANUAL, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        verify(receiverProxy, times(2)).registerReceiveChannelEndpoint(any());\n\n        driverProxy.removeSubscription(id1);\n        driverProxy.removeSubscription(id2);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        verify(receiverProxy, times(2)).closeReceiveChannelEndpoint(any());\n\n        verify(mockErrorHandler, never()).onError(any());\n    }\n\n    @Test\n    void shouldBeAbleToAddNetworkPublicationThenSingleSpyWithTag()\n    {\n        driverProxy.addPublication(CHANNEL_4000_TAG_ID_1, STREAM_ID_1);\n        final long idSpy = driverProxy.addSubscription(spyForChannel(CHANNEL_TAG_ID_1), STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertTrue(publication.hasSpies());\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy).onSubscriptionReady(eq(idSpy), anyInt());\n        inOrder.verify(mockClientProxy).onAvailableImage(\n            eq(networkPublicationCorrelationId(publication)), eq(STREAM_ID_1), eq(publication.sessionId()),\n            anyLong(), anyInt(), eq(publication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldBeAbleToAddSingleSpyThenNetworkPublicationWithTag()\n    {\n        final long idSpy = driverProxy.addSubscription(spyForChannel(CHANNEL_TAG_ID_1), STREAM_ID_1);\n        driverProxy.addPublication(CHANNEL_4000_TAG_ID_1, STREAM_ID_1);\n\n        driverConductor.doWork();\n        driverConductor.doWork();\n\n        final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class);\n        verify(senderProxy, times(1)).newNetworkPublication(captor.capture());\n        final NetworkPublication publication = captor.getValue();\n\n        assertTrue(publication.hasSpies());\n\n        final InOrder inOrder = inOrder(mockClientProxy);\n        inOrder.verify(mockClientProxy).onSubscriptionReady(eq(idSpy), anyInt());\n        inOrder.verify(mockClientProxy).onAvailableImage(\n            eq(networkPublicationCorrelationId(publication)), eq(STREAM_ID_1), eq(publication.sessionId()),\n            anyLong(), anyInt(), eq(publication.rawLog().fileName()), anyString());\n    }\n\n    @Test\n    void shouldIncrementCounterOnConductorThresholdExceeded()\n    {\n        final AtomicCounter maxCycleTime = spySystemCounters.get(CONDUCTOR_MAX_CYCLE_TIME);\n        final AtomicCounter thresholdExceeded = spySystemCounters.get(CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED);\n\n        driverConductor.doWork();\n        nanoClock.advance(MILLISECONDS.toNanos(750));\n        driverConductor.doWork();\n        nanoClock.advance(MILLISECONDS.toNanos(1000));\n        driverConductor.doWork();\n        nanoClock.advance(MILLISECONDS.toNanos(500));\n        driverConductor.doWork();\n        nanoClock.advance(MILLISECONDS.toNanos(600));\n        driverConductor.doWork();\n        nanoClock.advance(MILLISECONDS.toNanos(601));\n        driverConductor.doWork();\n\n        assertEquals(SECONDS.toNanos(1), maxCycleTime.get());\n        assertEquals(3, thresholdExceeded.get());\n    }\n\n    @Test\n    void shouldThrowExceptionWhenSendDestinationHasControlModeResponseSet()\n    {\n        final Exception exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> driverConductor.onAddSendDestination(13, \"aeron:udp?control-mode=response\", 19)\n        );\n\n        assertThat(exception.getMessage(), containsString(MDC_CONTROL_MODE_PARAM_NAME));\n        assertThat(exception.getMessage(), containsString(CONTROL_MODE_RESPONSE));\n    }\n\n    @Test\n    void shouldThrowExceptionWhenSendDestinationHasResponseCorrelationIdSet()\n    {\n        final Exception exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> driverConductor.onAddSendDestination(4, \"aeron:udp?response-correlation-id=1234\", 8)\n        );\n\n        assertThat(exception.getMessage(), containsString(RESPONSE_CORRELATION_ID_PARAM_NAME));\n    }\n\n    @Test\n    void shouldThrowExceptionWhenRcvDestinationHasControlModeResponseSet()\n    {\n        final Exception exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> driverConductor.onAddRcvDestination(5, \"aeron:udp?control-mode=response\", 7)\n        );\n\n        assertEquals(\n            \"ERROR - destinations may not specify \" + MDC_CONTROL_MODE_PARAM_NAME + \"=\" + CONTROL_MODE_RESPONSE,\n            exception.getMessage());\n    }\n\n    @Test\n    void shouldThrowExceptionWhenRcvDestinationHasResponseCorrelationIdSet()\n    {\n        final Exception exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> driverConductor.onAddRcvDestination(\n            42, \"aeron:udp?endpoint=localhost:8080|response-correlation-id=1234\", 1)\n        );\n\n        assertThat(\n            exception.getMessage(),\n            CoreMatchers.startsWith(\"ERROR - destinations must not contain the key: response-correlation-id\"));\n    }\n\n    @Test\n    void onCloseMustShutdownAsyncExecutor(@TempDir final Path dir) throws InterruptedException\n    {\n        final ExecutorService asyncTaskExecutor = mock(ExecutorService.class);\n        final DriverConductor conductor = new DriverConductor(ctx.clone()\n            .cncByteBuffer(IoUtil.mapNewFile(dir.resolve(\"some.txt\").toFile(), 64))\n            .asyncTaskExecutor(asyncTaskExecutor));\n\n        conductor.onClose();\n\n        final InOrder inOrder = inOrder(asyncTaskExecutor);\n        inOrder.verify(asyncTaskExecutor).shutdownNow();\n        inOrder.verify(asyncTaskExecutor).awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS, SECONDS);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void onCloseHandlesExceptionFromClosingAsyncExecutor(@TempDir final Path dir)\n    {\n        final ExecutorService asyncTaskExecutor = mock(ExecutorService.class);\n        final IllegalStateException closeException = new IllegalStateException(\"executor failed\");\n        doThrow(closeException).when(asyncTaskExecutor).shutdownNow();\n        final DriverConductor conductor = new DriverConductor(ctx.clone()\n            .cncByteBuffer(IoUtil.mapNewFile(dir.resolve(\"some.txt\").toFile(), 64))\n            .asyncTaskExecutor(asyncTaskExecutor));\n\n        conductor.onClose();\n\n        final InOrder inOrder = inOrder(asyncTaskExecutor, mockErrorHandler);\n        inOrder.verify(asyncTaskExecutor).shutdownNow();\n        inOrder.verify(mockErrorHandler).onError(closeException);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void onCloseHandlesExceptionFromClosingAsyncExecutor2(@TempDir final Path dir) throws InterruptedException\n    {\n        final ExecutorService asyncTaskExecutor = mock(ExecutorService.class);\n        final IllegalStateException closeException = new IllegalStateException(\"executor failed\");\n        doThrow(closeException).when(asyncTaskExecutor).awaitTermination(anyLong(), any(TimeUnit.class));\n        final DriverConductor conductor = new DriverConductor(ctx.clone()\n            .cncByteBuffer(IoUtil.mapNewFile(dir.resolve(\"some.txt\").toFile(), 64))\n            .asyncTaskExecutor(asyncTaskExecutor));\n\n        conductor.onClose();\n\n        final InOrder inOrder = inOrder(asyncTaskExecutor, mockErrorHandler);\n        inOrder.verify(asyncTaskExecutor).shutdownNow();\n        inOrder.verify(asyncTaskExecutor).awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS, SECONDS);\n        inOrder.verify(mockErrorHandler).onError(closeException);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void onCloseShouldNotifyIfExecutorDoesNotCloseOnTime(@TempDir final Path dir) throws InterruptedException\n    {\n        final ExecutorService asyncTaskExecutor = mock(ExecutorService.class);\n        final DriverConductor conductor = new DriverConductor(ctx.clone()\n            .cncByteBuffer(IoUtil.mapNewFile(dir.resolve(\"some.txt\").toFile(), 64))\n            .asyncTaskExecutor(asyncTaskExecutor));\n\n        conductor.onClose();\n\n        final InOrder inOrder = inOrder(asyncTaskExecutor, mockErrorHandler);\n        inOrder.verify(asyncTaskExecutor).shutdownNow();\n        inOrder.verify(asyncTaskExecutor).awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS, SECONDS);\n        inOrder.verify(mockErrorHandler).onError(argThat(\n            arg -> arg instanceof AeronEvent &&\n            arg.getMessage().equals(\"WARN - failed to shutdown async task executor\")));\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void onCloseShouldCallForceOnTheCncByteBuffer(@TempDir final Path dir)\n    {\n        final MappedByteBuffer cncByteBuffer = spy(IoUtil.mapNewFile(dir.resolve(\"test.cnc\").toFile(), 1024));\n        final DriverConductor conductor = new DriverConductor(ctx.clone().cncByteBuffer(cncByteBuffer));\n\n        conductor.onClose();\n\n        final InOrder inOrder = inOrder(toDriverCommands, cncByteBuffer);\n        inOrder.verify(toDriverCommands).consumerHeartbeatTime(Aeron.NULL_VALUE);\n        inOrder.verify(cncByteBuffer).force();\n    }\n\n    private void doWorkUntil(final BooleanSupplier condition, final LongConsumer timeConsumer)\n    {\n        while (!condition.getAsBoolean())\n        {\n            final long millisecondsToAdvance = 16;\n\n            nanoClock.advance(TimeUnit.MILLISECONDS.toNanos(millisecondsToAdvance));\n            epochClock.advance(millisecondsToAdvance);\n            timeConsumer.accept(nanoClock.nanoTime());\n            driverConductor.doWork();\n        }\n    }\n\n    private void doWorkUntil(final BooleanSupplier condition)\n    {\n        doWorkUntil(condition, (j) -> {});\n    }\n\n    private static String spyForChannel(final String channel)\n    {\n        return CommonContext.SPY_PREFIX + channel;\n    }\n\n    private static long networkPublicationCorrelationId(final NetworkPublication publication)\n    {\n        return LogBufferDescriptor.correlationId(publication.rawLog().metaData());\n    }\n\n    private static AtomicCounter clientHeartbeatCounter(final CountersReader countersReader)\n    {\n        for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n        {\n            final int counterState = countersReader.getCounterState(counterId);\n            if (counterState == RECORD_ALLOCATED && countersReader.getCounterTypeId(counterId) == HEARTBEAT_TYPE_ID)\n            {\n                return new AtomicCounter(countersReader.valuesBuffer(), counterId);\n            }\n            else if (RECORD_UNUSED == counterState)\n            {\n                break;\n            }\n        }\n\n        throw new IllegalStateException(\"could not find client heartbeat counter\");\n    }\n\n    private void appendUnfragmentedMessage(\n        final RawLog rawLog,\n        final int partitionIndex,\n        final int termId,\n        final int termOffset,\n        final HeaderWriter header,\n        final DirectBuffer srcBuffer,\n        final int srcOffset,\n        final int length)\n    {\n        final UnsafeBuffer termBuffer = rawLog.termBuffers()[partitionIndex];\n        final int frameLength = length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int resultingOffset = termOffset + alignedLength;\n        final long rawTail = LogBufferDescriptor.packTail(termId, resultingOffset);\n\n        LogBufferDescriptor.rawTail(rawLog.metaData(), partitionIndex, rawTail);\n\n        header.write(termBuffer, termOffset, frameLength, termId);\n        termBuffer.putBytes(termOffset + HEADER_LENGTH, srcBuffer, srcOffset, length);\n\n        frameLengthOrdered(termBuffer, termOffset, frameLength);\n    }\n\n    @Test\n    void shouldInferFeedbackGeneratorBasedOnMulticastAddress()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .multicastFeedbackDelayGenerator(new OptimalMulticastDelayGenerator(10, 10))\n            .unicastFeedbackDelayGenerator(new StaticDelayGenerator(10));\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=224.20.30.39:24326\");\n\n        assertTrue(DriverConductor.isMulticastSemantics(udpChannel, InferableBoolean.INFER, (short)0));\n        assertSame(context.multicastFeedbackDelayGenerator(), DriverConductor.resolveDelayGenerator(\n            context, udpChannel, true, true));\n    }\n\n    @Test\n    void shouldInferFeedbackGeneratorBasedOnUnicastAddress()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .multicastFeedbackDelayGenerator(new OptimalMulticastDelayGenerator(10, 10))\n            .unicastFeedbackDelayGenerator(new StaticDelayGenerator(10));\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=192.168.0.1:24326\");\n\n        assertFalse(DriverConductor.isMulticastSemantics(udpChannel, InferableBoolean.INFER, (short)0));\n        assertSame(context.unicastFeedbackDelayGenerator(), DriverConductor.resolveDelayGenerator(\n            context, udpChannel, false, true));\n    }\n\n    @Test\n    void shouldFixMulticastFeedbackGeneratorBasedOnReceiverGroupConsideration()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .multicastFeedbackDelayGenerator(new OptimalMulticastDelayGenerator(10, 10))\n            .unicastFeedbackDelayGenerator(new StaticDelayGenerator(10));\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=192.168.0.1:24326\");\n\n        assertTrue(DriverConductor.isMulticastSemantics(udpChannel, InferableBoolean.FORCE_TRUE, (short)0));\n        assertSame(context.multicastFeedbackDelayGenerator(), DriverConductor.resolveDelayGenerator(\n            context, udpChannel, true, true));\n    }\n\n    @Test\n    void shouldFixUnicastFeedbackGeneratorBasedOnReceiverGroupConsideration()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .multicastFeedbackDelayGenerator(new OptimalMulticastDelayGenerator(10, 10))\n            .unicastFeedbackDelayGenerator(new StaticDelayGenerator(10));\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=192.168.0.1:24326\");\n\n        assertFalse(DriverConductor.isMulticastSemantics(udpChannel, InferableBoolean.FORCE_FALSE, (short)0));\n        assertSame(context.unicastFeedbackDelayGenerator(), DriverConductor.resolveDelayGenerator(\n            context, udpChannel, false, true));\n    }\n\n    @Test\n    void shouldInferFeedbackGeneratorBasedOnReceiverGroupFlag()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .multicastFeedbackDelayGenerator(new OptimalMulticastDelayGenerator(10, 10))\n            .unicastFeedbackDelayGenerator(new StaticDelayGenerator(10));\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=192.168.0.1:24326\");\n\n        assertTrue(DriverConductor.isMulticastSemantics(udpChannel, InferableBoolean.INFER, SetupFlyweight.GROUP_FLAG));\n        assertSame(context.multicastFeedbackDelayGenerator(), DriverConductor.resolveDelayGenerator(\n            context, udpChannel, true, true));\n    }\n\n    @Test\n    void shouldCreateFeedbackGeneratorFromManualNakDelayConfiguration()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .multicastFeedbackDelayGenerator(new OptimalMulticastDelayGenerator(10, 10))\n            .unicastFeedbackDelayGenerator(new StaticDelayGenerator(10))\n            .nakUnicastRetryDelayRatio(55);\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=192.168.0.1:24326|nak-delay=14ms\");\n\n        final FeedbackDelayGenerator feedbackDelayGenerator = DriverConductor.resolveDelayGenerator(\n            context, udpChannel, false, true);\n        assertNotNull(feedbackDelayGenerator);\n        assertInstanceOf(StaticDelayGenerator.class, feedbackDelayGenerator);\n        assertEquals(MILLISECONDS.toNanos(14), feedbackDelayGenerator.generateDelayNs());\n        assertEquals(MILLISECONDS.toNanos(14 * 55), feedbackDelayGenerator.retryDelayNs());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=192.168.0.1:24326\",\n        \"aeron:udp?endpoint=224.20.30.39:5555\",\n        \"aeron:udp?endpoint=192.168.0.1:10000|nak-delay=14ms\"\n    })\n    void shouldUseZeroDelayFeedbackGeneratorIfSubscriptionIsUnreliable(final String uri)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .multicastFeedbackDelayGenerator(new OptimalMulticastDelayGenerator(123, 10))\n            .unicastFeedbackDelayGenerator(new StaticDelayGenerator(321, 456));\n        final UdpChannel udpChannel = UdpChannel.parse(uri);\n        final boolean isMulticastSemantics =\n            DriverConductor.isMulticastSemantics(udpChannel, InferableBoolean.INFER, (short)0);\n\n        final FeedbackDelayGenerator feedbackDelayGenerator = DriverConductor.resolveDelayGenerator(\n            context, udpChannel, isMulticastSemantics, false);\n\n        assertSame(StaticDelayGenerator.ZERO_DELAY_GENERATOR, feedbackDelayGenerator);\n        assertEquals(0, feedbackDelayGenerator.generateDelayNs());\n        assertEquals(0, feedbackDelayGenerator.retryDelayNs());\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/DriverNameResolverCacheTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\n\nimport java.util.Arrays;\n\nimport static io.aeron.protocol.ResolutionEntryFlyweight.ADDRESS_LENGTH_IP6;\nimport static io.aeron.protocol.ResolutionEntryFlyweight.RES_TYPE_NAME_TO_IP4_MD;\nimport static io.aeron.protocol.ResolutionEntryFlyweight.RES_TYPE_NAME_TO_IP6_MD;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\n\nclass DriverNameResolverCacheTest\n{\n    private final AtomicCounter cacheEntriesCounter = mock(AtomicCounter.class);\n    private final byte[] name = new byte[32];\n    private final byte[] address = new byte[ADDRESS_LENGTH_IP6];\n\n    @Test\n    void shouldAddNewEntries()\n    {\n        final DriverNameResolverCache cache = new DriverNameResolverCache(1000);\n\n        name[0] = 'A';\n        Arrays.fill(address, (byte)0);\n        cache.addOrUpdateEntry(name, 1, 1, RES_TYPE_NAME_TO_IP4_MD, address, 8050, cacheEntriesCounter);\n\n        Arrays.fill(name, (byte)'A');\n        Arrays.fill(address, (byte)1);\n        cache.addOrUpdateEntry(name, 2, 2, RES_TYPE_NAME_TO_IP4_MD, address, 8050, cacheEntriesCounter);\n\n        Arrays.fill(name, (byte)'B');\n        Arrays.fill(address, (byte)2);\n        cache.addOrUpdateEntry(name, 1, 3, RES_TYPE_NAME_TO_IP4_MD, address, 8052, cacheEntriesCounter);\n\n        Arrays.fill(name, (byte)'A');\n        Arrays.fill(address, (byte)127);\n        cache.addOrUpdateEntry(name, 2, 4, RES_TYPE_NAME_TO_IP4_MD, address, 9090, cacheEntriesCounter);\n\n        Arrays.fill(name, (byte)'B');\n        Arrays.fill(address, (byte)5);\n        cache.addOrUpdateEntry(name, 1, 5, RES_TYPE_NAME_TO_IP6_MD, address, 8050, cacheEntriesCounter);\n\n        final DriverNameResolverCache.Iterator iterator = cache.resetIterator();\n        assertCacheEntry(\n            iterator.next(), new byte[]{ 'A' }, RES_TYPE_NAME_TO_IP4_MD, 8050, new byte[]{ 0, 0, 0, 0 }, 1, 1001);\n\n        assertCacheEntry(\n            iterator.next(),\n            new byte[]{ 'A', 'A' },\n            RES_TYPE_NAME_TO_IP4_MD,\n            9090,\n            new byte[]{ 127, 127, 127, 127 },\n            4,\n            1004);\n\n        assertCacheEntry(\n            iterator.next(), new byte[]{ 'B' }, RES_TYPE_NAME_TO_IP4_MD, 8052, new byte[]{ 2, 2, 2, 2 }, 3, 1003);\n\n        assertCacheEntry(\n            iterator.next(),\n            new byte[]{ 'B' },\n            RES_TYPE_NAME_TO_IP6_MD,\n            8050,\n            new byte[]{ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 },\n            5,\n            1005);\n\n        assertFalse(iterator.hasNext());\n\n        final InOrder inOrder = inOrder(cacheEntriesCounter);\n        inOrder.verify(cacheEntriesCounter).setRelease(1);\n        inOrder.verify(cacheEntriesCounter).setRelease(2);\n        inOrder.verify(cacheEntriesCounter).setRelease(3);\n        inOrder.verify(cacheEntriesCounter).setRelease(4);\n    }\n\n    @Test\n    void shouldLookupByTypeAndName()\n    {\n        final DriverNameResolverCache cache = new DriverNameResolverCache(5000);\n\n        Arrays.fill(name, (byte)'A');\n        Arrays.fill(address, (byte)0);\n        cache.addOrUpdateEntry(name, 1, 1, RES_TYPE_NAME_TO_IP4_MD, address, 5555, cacheEntriesCounter);\n\n        Arrays.fill(address, (byte)1);\n        cache.addOrUpdateEntry(name, 2, 2, RES_TYPE_NAME_TO_IP4_MD, address, 6666, cacheEntriesCounter);\n\n        Arrays.fill(address, (byte)2);\n        cache.addOrUpdateEntry(name, 1, 3, RES_TYPE_NAME_TO_IP6_MD, address, 7777, cacheEntriesCounter);\n\n        Arrays.fill(name, (byte)'B');\n        Arrays.fill(address, (byte)1);\n        cache.addOrUpdateEntry(name, 2, 4, RES_TYPE_NAME_TO_IP4_MD, address, 8888, cacheEntriesCounter);\n\n        Arrays.fill(name, (byte)'C');\n        Arrays.fill(address, (byte)0);\n        cache.addOrUpdateEntry(name, 1, 5, RES_TYPE_NAME_TO_IP6_MD, address, 9999, cacheEntriesCounter);\n\n        DriverNameResolverCache.CacheEntry entry = cache.lookup(\"A\", RES_TYPE_NAME_TO_IP4_MD);\n        assertCacheEntry(\n            entry,\n            new byte[]{ 'A' },\n            RES_TYPE_NAME_TO_IP4_MD,\n            5555,\n            new byte[]{ 0, 0, 0, 0 },\n            1,\n            5001);\n\n        entry = cache.lookup(\"A\", RES_TYPE_NAME_TO_IP6_MD);\n        assertCacheEntry(\n            entry,\n            new byte[]{ 'A' },\n            RES_TYPE_NAME_TO_IP6_MD,\n            7777,\n            new byte[]{ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 },\n            3,\n            5003);\n\n        // case-sensitive match\n        assertNull(cache.lookup(\"a\", RES_TYPE_NAME_TO_IP4_MD));\n\n        // wrong type\n        assertNull(cache.lookup(\"AA\", RES_TYPE_NAME_TO_IP6_MD));\n\n        // only full matches allowed\n        assertNull(cache.lookup(\"B\", RES_TYPE_NAME_TO_IP4_MD));\n\n        entry = cache.lookup(\"BB\", RES_TYPE_NAME_TO_IP4_MD);\n        assertCacheEntry(\n            entry,\n            new byte[]{ 'B', 'B' },\n            RES_TYPE_NAME_TO_IP4_MD,\n            8888,\n            new byte[]{ 1, 1, 1, 1 },\n            4,\n            5004);\n    }\n\n    private static void assertCacheEntry(\n        final DriverNameResolverCache.CacheEntry entry,\n        final byte[] name,\n        final byte type,\n        final int port,\n        final byte[] address,\n        final long timeOfLastActivityMs,\n        final long deadlineMs)\n    {\n        assertNotNull(entry);\n        assertArrayEquals(name, entry.name);\n        assertEquals(type, entry.type);\n        assertEquals(port, entry.port);\n        assertArrayEquals(address, entry.address);\n        assertEquals(timeOfLastActivityMs, entry.timeOfLastActivityMs);\n        assertEquals(deadlineMs, entry.deadlineMs);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/FlowControlTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.test.Tests;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.MockedStatic;\n\nimport java.util.stream.Stream;\n\nimport static io.aeron.driver.FlowControl.calculateRetransmissionLength;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mockStatic;\n\nclass FlowControlTest\n{\n    private static final int MTU_LENGTH = 1024;\n    private static final int COUNTERS_BUFFER_LENGTH = 16 * 1024;\n    private static final int TERM_BUFFER_LENGTH = 64 * 1024;\n\n    private static Stream<Arguments> multicastFlowControlStrategies()\n    {\n        return Stream.of(\n            Arguments.of(\n                new MaxMulticastFlowControl(), \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=max\"\n            ),\n            Arguments.of(\n                new MinMulticastFlowControl(), \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min\"\n            ),\n            Arguments.of(\n                new TaggedMulticastFlowControl(), \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged\"\n            )\n        );\n    }\n\n    @Test\n    void shouldUseResendLengthIfSmallestValue()\n    {\n        final int resendLength = 1024;\n\n        assertEquals(resendLength, calculateRetransmissionLength(resendLength, 64 * 1024, 0, 16));\n    }\n\n    @Test\n    void shouldClampToTheEndOfTheBuffer()\n    {\n        final int expectedLength = 512;\n        final int termLength = 64 * 1024;\n        final int termOffset = termLength - expectedLength;\n\n        assertEquals(expectedLength, calculateRetransmissionLength(1024, termLength, termOffset, 16));\n    }\n\n    @Test\n    void shouldClampToReceiverWindow()\n    {\n        final int multiplier = 16;\n        final int expectedLength = Configuration.INITIAL_WINDOW_LENGTH_DEFAULT * multiplier;\n\n        assertEquals(expectedLength, calculateRetransmissionLength(4 * 1024 * 1024, 8 * 1024 * 1024, 0, 16));\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=max\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\"\n    })\n    void shouldUseDefaultRetransmitReceiverWindowMultipleWhenNotSetInUri(final String uri)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context();\n\n        final int rrwm = FlowControl.retransmitReceiverWindowMultiple(\n            UdpChannel.parse(uri),\n            context.multicastFlowControlRetransmitReceiverWindowMultiple()\n        );\n        final int defaultRrwm = Configuration.multicastFlowControlRetransmitReceiverWindowMultiple();\n        assertEquals(defaultRrwm, rrwm);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=max,rrwm:8\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=max,foo:bar,rrwm:8,a:b\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,rrwm:8\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:100/10,t:100ms,rrwm:8\"\n    })\n    void shouldUseRetransmitReceiverWindowMultipleFromUri(final String uri)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .multicastFlowControlRetransmitReceiverWindowMultiple(2);\n\n        final int rrwm = FlowControl.retransmitReceiverWindowMultiple(\n            UdpChannel.parse(uri),\n            context.multicastFlowControlRetransmitReceiverWindowMultiple()\n        );\n        assertEquals(8, rrwm);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"a\", \"\", \" \", \"foo\", \"0\", \"-1\" })\n    void shouldRejectInvalidRetransmitReceiverWindowMultiple(final String rrwmValue)\n    {\n        final String uri = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=max,rrwm:\" + rrwmValue;\n\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> FlowControl.retransmitReceiverWindowMultiple(UdpChannel.parse(uri), 2)\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"multicastFlowControlStrategies\")\n    void usesDefaultMulticastRetransmitReceiverWindowMultipleWhenNotSet(final FlowControl flowControl, final String uri)\n    {\n        final int expectedRrwm = Configuration.multicastFlowControlRetransmitReceiverWindowMultiple();\n        final CountersManager countersManager = Tests.newCountersManager(COUNTERS_BUFFER_LENGTH);\n\n        try (MockedStatic<FlowControl> mockedFlowControl = mockStatic(FlowControl.class))\n        {\n            mockedFlowControl.when(\n                () -> FlowControl.retransmitReceiverWindowMultiple(any(), anyInt())\n            ).thenCallRealMethod();\n\n            mockedFlowControl.when(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), anyInt())\n            ).thenCallRealMethod();\n\n            final MediaDriver.Context context = new MediaDriver.Context()\n                .tempBuffer(new UnsafeBuffer(new byte[TERM_BUFFER_LENGTH]));\n            flowControl.initialize(context, countersManager, UdpChannel.parse(uri), 0, 0, 0, 0, 0);\n\n            flowControl.maxRetransmissionLength(0, 32, TERM_BUFFER_LENGTH, MTU_LENGTH);\n\n            mockedFlowControl.verify(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), eq(expectedRrwm))\n            );\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"multicastFlowControlStrategies\")\n    void usesMulticastRetransmitReceiverWindowMultipleFromContextWhenNotSetInUri(\n        final FlowControl flowControl, final String uri)\n    {\n        final int expectedRrwm = 2;\n        final CountersManager countersManager = Tests.newCountersManager(COUNTERS_BUFFER_LENGTH);\n\n        try (MockedStatic<FlowControl> mockedFlowControl = mockStatic(FlowControl.class))\n        {\n            mockedFlowControl.when(\n                () -> FlowControl.retransmitReceiverWindowMultiple(any(), anyInt())\n            ).thenCallRealMethod();\n\n            mockedFlowControl.when(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), anyInt())\n            ).thenCallRealMethod();\n\n            final MediaDriver.Context context = new MediaDriver.Context()\n                .tempBuffer(new UnsafeBuffer(new byte[TERM_BUFFER_LENGTH]))\n                .multicastFlowControlRetransmitReceiverWindowMultiple(expectedRrwm);\n            flowControl.initialize(context, countersManager, UdpChannel.parse(uri), 0, 0, 0, 0, 0);\n\n            flowControl.maxRetransmissionLength(0, 32, TERM_BUFFER_LENGTH, MTU_LENGTH);\n\n            mockedFlowControl.verify(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), eq(expectedRrwm))\n            );\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"multicastFlowControlStrategies\")\n    void usesMulticastRetransmitReceiverWindowMultipleFromUriWhenSet(final FlowControl flowControl, final String uri)\n    {\n        final int expectedRrwm = 8;\n        final String fullUri = uri + \",rrwm:\" + expectedRrwm;\n        final CountersManager countersManager = Tests.newCountersManager(COUNTERS_BUFFER_LENGTH);\n\n        try (MockedStatic<FlowControl> mockedFlowControl = mockStatic(FlowControl.class))\n        {\n            mockedFlowControl.when(\n                () -> FlowControl.retransmitReceiverWindowMultiple(any(), anyInt())\n            ).thenCallRealMethod();\n\n            mockedFlowControl.when(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), anyInt())\n            ).thenCallRealMethod();\n\n            final MediaDriver.Context context = new MediaDriver.Context()\n                .tempBuffer(new UnsafeBuffer(new byte[TERM_BUFFER_LENGTH]))\n                .multicastFlowControlRetransmitReceiverWindowMultiple(expectedRrwm / 2);\n            flowControl.initialize(context, countersManager, UdpChannel.parse(fullUri), 0, 0, 0, 0, 0);\n\n            flowControl.maxRetransmissionLength(0, 32, TERM_BUFFER_LENGTH, MTU_LENGTH);\n\n            mockedFlowControl.verify(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), eq(expectedRrwm))\n            );\n        }\n    }\n\n    @Test\n    void usesDefaultUnicastRetransmitReceiverWindowMultipleWhenNotSetInContext()\n    {\n        final String uri = \"aeron:udp?endpoint=localhost:0\";\n        final int expectedRrwm = Configuration.unicastFlowControlRetransmitReceiverWindowMultiple();\n        final CountersManager countersManager = Tests.newCountersManager(COUNTERS_BUFFER_LENGTH);\n\n        try (MockedStatic<FlowControl> mockedFlowControl = mockStatic(FlowControl.class))\n        {\n            mockedFlowControl.when(\n                () -> FlowControl.retransmitReceiverWindowMultiple(any(), anyInt())\n            ).thenCallRealMethod();\n\n            mockedFlowControl.when(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), anyInt())\n            ).thenCallRealMethod();\n\n            final MediaDriver.Context context = new MediaDriver.Context()\n                .tempBuffer(new UnsafeBuffer(new byte[TERM_BUFFER_LENGTH]));\n            final UnicastFlowControl flowControl = new UnicastFlowControl();\n            flowControl.initialize(context, countersManager, UdpChannel.parse(uri), 0, 0, 0, 0, 0);\n\n            flowControl.maxRetransmissionLength(0, 32, TERM_BUFFER_LENGTH, MTU_LENGTH);\n\n            mockedFlowControl.verify(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), eq(expectedRrwm))\n            );\n        }\n    }\n\n    @Test\n    void usesUnicastRetransmitReceiverWindowMultipleFromContextWhenSet()\n    {\n        final String uri = \"aeron:udp?endpoint=localhost:0\";\n        final int expectedRrwm = 2;\n        final CountersManager countersManager = Tests.newCountersManager(COUNTERS_BUFFER_LENGTH);\n\n        try (MockedStatic<FlowControl> mockedFlowControl = mockStatic(FlowControl.class))\n        {\n            mockedFlowControl.when(\n                () -> FlowControl.retransmitReceiverWindowMultiple(any(), anyInt())\n            ).thenCallRealMethod();\n\n            mockedFlowControl.when(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), anyInt())\n            ).thenCallRealMethod();\n\n            final MediaDriver.Context context = new MediaDriver.Context()\n                .tempBuffer(new UnsafeBuffer(new byte[TERM_BUFFER_LENGTH]))\n                .unicastFlowControlRetransmitReceiverWindowMultiple(expectedRrwm);\n            final UnicastFlowControl flowControl = new UnicastFlowControl();\n\n            flowControl.initialize(context, countersManager, UdpChannel.parse(uri), 0, 0, 0, 0, 0);\n\n            flowControl.maxRetransmissionLength(0, 32, TERM_BUFFER_LENGTH, MTU_LENGTH);\n\n            mockedFlowControl.verify(\n                () -> FlowControl.calculateRetransmissionLength(anyInt(), anyInt(), anyInt(), eq(expectedRrwm))\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/IpcPublicationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.DriverProxy;\nimport io.aeron.driver.buffer.TestLogFactory;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.Tests;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.ManyToOneConcurrentLinkedQueue;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.Position;\nimport org.agrona.concurrent.status.UnsafeBufferPosition;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.ByteBuffer;\n\nimport static org.agrona.concurrent.status.CountersReader.METADATA_LENGTH;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.Mockito.mock;\n\nclass IpcPublicationTest\n{\n    private static final long CLIENT_ID = 7L;\n    private static final int STREAM_ID = 1010;\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int BUFFER_LENGTH = 16 * 1024;\n\n    private Position publisherLimit;\n    private IpcPublication ipcPublication;\n\n    private DriverProxy driverProxy;\n    private DriverConductor driverConductor;\n\n    @BeforeEach\n    void setUp()\n    {\n        final RingBuffer toDriverCommands = new ManyToOneRingBuffer(new UnsafeBuffer(\n            ByteBuffer.allocateDirect(Configuration.CONDUCTOR_BUFFER_LENGTH_DEFAULT)));\n\n        final CountersManager countersManager = Tests.newCountersManager(BUFFER_LENGTH);\n        final SystemCounters systemCounters = new SystemCounters(countersManager);\n\n        final SenderProxy senderProxy = mock(SenderProxy.class);\n        final ReceiverProxy receiverProxy = mock(ReceiverProxy.class);\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .tempBuffer(new UnsafeBuffer(new byte[METADATA_LENGTH]))\n            .ipcTermBufferLength(TERM_BUFFER_LENGTH)\n            .toDriverCommands(toDriverCommands)\n            .logFactory(new TestLogFactory())\n            .clientProxy(mock(ClientProxy.class))\n            .senderProxy(senderProxy)\n            .receiverProxy(receiverProxy)\n            .driverCommandQueue(new ManyToOneConcurrentLinkedQueue<>())\n            .epochClock(SystemEpochClock.INSTANCE)\n            .cachedEpochClock(new CachedEpochClock())\n            .cachedNanoClock(new CachedNanoClock())\n            .countersManager(countersManager)\n            .systemCounters(systemCounters)\n            .nameResolver(DefaultNameResolver.INSTANCE)\n            .nanoClock(new CachedNanoClock())\n            .threadingMode(ThreadingMode.DEDICATED)\n            .conductorDutyCycleTracker(new DutyCycleTracker())\n            .nameResolverTimeTracker(new DutyCycleTracker());\n\n        driverProxy = new DriverProxy(toDriverCommands, CLIENT_ID);\n        driverConductor = new DriverConductor(ctx);\n        driverConductor.onStart();\n\n        driverProxy.addPublication(CommonContext.IPC_CHANNEL, STREAM_ID);\n        driverConductor.doWork();\n\n        ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID, Aeron.NULL_VALUE);\n        publisherLimit = new UnsafeBufferPosition(\n            (UnsafeBuffer)countersManager.valuesBuffer(), ipcPublication.publisherLimitId());\n    }\n\n    @Test\n    void shouldStartWithPublisherLimitSetToZero()\n    {\n        assertThat(publisherLimit.get(), is(0L));\n    }\n\n    @Test\n    void shouldKeepPublisherLimitZeroOnNoSubscriptionUpdate()\n    {\n        ipcPublication.updatePublisherPositionAndLimit();\n        assertThat(publisherLimit.get(), is(0L));\n    }\n\n    @Test\n    void shouldHaveJoiningPositionZeroWhenNoSubscriptions()\n    {\n        assertThat(ipcPublication.joinPosition(), is(0L));\n    }\n\n    @Test\n    void shouldIncrementPublisherLimitOnSubscription()\n    {\n        driverProxy.addSubscription(CommonContext.IPC_CHANNEL, STREAM_ID);\n        driverConductor.doWork();\n\n        assertThat(publisherLimit.get(), is(greaterThan(0L)));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/LossDetectorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.TermRebuilder;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.LossDetector.lossFound;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.*;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\nimport static org.agrona.BitUtil.align;\n\nclass LossDetectorTest\n{\n    private static final int TERM_BUFFER_LENGTH = TERM_MIN_LENGTH;\n    private static final int POSITION_BITS_TO_SHIFT = LogBufferDescriptor.positionBitsToShift(TERM_BUFFER_LENGTH);\n    private static final int MASK = TERM_BUFFER_LENGTH - 1;\n\n    private static final byte[] DATA = new byte[36];\n\n    static\n    {\n        for (int i = 0; i < DATA.length; i++)\n        {\n            DATA[i] = (byte)i;\n        }\n    }\n\n    private static final int MESSAGE_LENGTH = DataHeaderFlyweight.HEADER_LENGTH + DATA.length;\n    private static final int ALIGNED_FRAME_LENGTH = align(MESSAGE_LENGTH, FrameDescriptor.FRAME_ALIGNMENT);\n    private static final int SESSION_ID = 0x5E55101D;\n    private static final int STREAM_ID = 0xC400E;\n    private static final int TERM_ID = 0xEE81D;\n    private static final long ACTIVE_TERM_POSITION = computePosition(TERM_ID, 0, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n    private static final StaticDelayGenerator DELAY_GENERATOR = new StaticDelayGenerator(\n        TimeUnit.MILLISECONDS.toNanos(20));\n\n    private static final StaticDelayGenerator DELAY_GENERATOR_WITH_LONGER_RETRY = new StaticDelayGenerator(\n        TimeUnit.MILLISECONDS.toNanos(20), TimeUnit.MILLISECONDS.toNanos(200));\n\n    private final UnsafeBuffer termBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(TERM_BUFFER_LENGTH));\n    private final UnsafeBuffer rcvBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(MESSAGE_LENGTH));\n    private final DataHeaderFlyweight dataHeader = new DataHeaderFlyweight();\n\n    private final LossHandler lossHandler = mock(LossHandler.class);\n    private LossDetector lossDetector = new LossDetector(DELAY_GENERATOR, lossHandler);\n    private long currentTimeNs = 0;\n\n    {\n        dataHeader.wrap(rcvBuffer);\n    }\n\n    @Test\n    void shouldNotSendNakWhenBufferIsEmpty()\n    {\n        final long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION;\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(100);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verifyNoInteractions(lossHandler);\n    }\n\n    @Test\n    void shouldNotNakIfNoMissingData()\n    {\n        final long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n\n        insertDataFrame(offsetOfMessage(0));\n        insertDataFrame(offsetOfMessage(1));\n        insertDataFrame(offsetOfMessage(2));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(40);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verifyNoInteractions(lossHandler);\n    }\n\n    @Test\n    void shouldNakMissingData()\n    {\n        final long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n\n        insertDataFrame(offsetOfMessage(0));\n        insertDataFrame(offsetOfMessage(2));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(40);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(1), ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldRetransmitNakForMissingData()\n    {\n        final long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n\n        insertDataFrame(offsetOfMessage(0));\n        insertDataFrame(offsetOfMessage(2));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(30);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(60);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verify(lossHandler, atLeast(2)).onGapDetected(TERM_ID, offsetOfMessage(1), ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldStopNakOnReceivingData()\n    {\n        long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n\n        insertDataFrame(offsetOfMessage(0));\n        insertDataFrame(offsetOfMessage(2));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(20);\n        insertDataFrame(offsetOfMessage(1));\n        rebuildPosition += (ALIGNED_FRAME_LENGTH * 3L);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(100);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verifyNoInteractions(lossHandler);\n    }\n\n    @Test\n    void shouldHandleMoreThan2Gaps()\n    {\n        long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 7L);\n\n        insertDataFrame(offsetOfMessage(0));\n        insertDataFrame(offsetOfMessage(2));\n        insertDataFrame(offsetOfMessage(4));\n        insertDataFrame(offsetOfMessage(6));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(40);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        insertDataFrame(offsetOfMessage(1));\n        rebuildPosition += (ALIGNED_FRAME_LENGTH * 3L);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(80);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        final InOrder inOrder = inOrder(lossHandler);\n        inOrder.verify(lossHandler, atLeast(1)).onGapDetected(TERM_ID, offsetOfMessage(1), ALIGNED_FRAME_LENGTH);\n        inOrder.verify(lossHandler, atLeast(1)).onGapDetected(TERM_ID, offsetOfMessage(3), ALIGNED_FRAME_LENGTH);\n        inOrder.verify(lossHandler, never()).onGapDetected(TERM_ID, offsetOfMessage(5), ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldReplaceOldNakWithNewNak()\n    {\n        long rebuildPosition = ACTIVE_TERM_POSITION;\n        long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n\n        insertDataFrame(offsetOfMessage(0));\n        insertDataFrame(offsetOfMessage(2));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(20);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        insertDataFrame(offsetOfMessage(4));\n        insertDataFrame(offsetOfMessage(1));\n        rebuildPosition += (ALIGNED_FRAME_LENGTH * 3L);\n        hwmPosition = (ALIGNED_FRAME_LENGTH * 5L);\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(100);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verify(lossHandler, atLeast(1)).onGapDetected(TERM_ID, offsetOfMessage(3), ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldHandleLongerRetryDelay()\n    {\n        lossDetector = getLossHandlerWithLongRetry();\n\n        final long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n\n        insertDataFrame(offsetOfMessage(0));\n        insertDataFrame(offsetOfMessage(2));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        verifyNoInteractions(lossHandler);\n\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(40);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(1), ALIGNED_FRAME_LENGTH);\n\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(80);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verifyNoMoreInteractions(lossHandler);\n\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(240);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verify(lossHandler, times(2)).onGapDetected(TERM_ID, offsetOfMessage(1), ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldNotNakImmediatelyByDefault()\n    {\n        final long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n\n        insertDataFrame(offsetOfMessage(0));\n        insertDataFrame(offsetOfMessage(2));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verifyNoInteractions(lossHandler);\n    }\n\n    @Test\n    void shouldOnlySendNaksOnceOnMultipleScans()\n    {\n        lossDetector = getLossHandlerWithLongRetry();\n\n        final long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n\n        insertDataFrame(offsetOfMessage(0));\n        insertDataFrame(offsetOfMessage(2));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(40);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(1), ALIGNED_FRAME_LENGTH);\n    }\n\n    @Test\n    void shouldHandleHwmGreaterThanCompletedBuffer()\n    {\n        lossDetector = getLossHandlerWithLongRetry();\n\n        long rebuildPosition = ACTIVE_TERM_POSITION;\n        final long hwmPosition = ACTIVE_TERM_POSITION + TERM_BUFFER_LENGTH + ALIGNED_FRAME_LENGTH;\n\n        insertDataFrame(offsetOfMessage(0));\n        rebuildPosition += ALIGNED_FRAME_LENGTH;\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(40);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(1), TERM_BUFFER_LENGTH - (int)rebuildPosition);\n    }\n\n    @Test\n    void shouldHandleNonZeroInitialTermOffset()\n    {\n        lossDetector = getLossHandlerWithLongRetry();\n\n        final long rebuildPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n        final long hwmPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 5L);\n\n        insertDataFrame(offsetOfMessage(2));\n        insertDataFrame(offsetOfMessage(4));\n\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(40);\n        lossDetector.scan(\n            termBuffer, rebuildPosition, hwmPosition, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID);\n\n        verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(3), ALIGNED_FRAME_LENGTH);\n        verifyNoMoreInteractions(lossHandler);\n    }\n\n    @Test\n    void shouldDetectChangesInTheGapLength()\n    {\n        lossDetector = getLossHandlerWithLongRetry();\n\n        final long rebuildPosition = ACTIVE_TERM_POSITION + (ALIGNED_FRAME_LENGTH * 3L);\n\n        insertDataFrame(offsetOfMessage(2));\n        insertDataFrame(offsetOfMessage(5));\n\n        assertTrue(lossFound(lossDetector.scan(\n            termBuffer, rebuildPosition, rebuildPosition + 32, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID)));\n\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(100);\n        assertFalse(lossFound(lossDetector.scan(\n            termBuffer, rebuildPosition, rebuildPosition + 32, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID)));\n\n        assertTrue(lossFound(lossDetector.scan(\n            termBuffer, rebuildPosition, rebuildPosition + 64, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID)));\n\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(200);\n        assertFalse(lossFound(lossDetector.scan(\n            termBuffer, rebuildPosition, rebuildPosition + 64, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID)));\n\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(300);\n        assertTrue(lossFound(lossDetector.scan(\n            termBuffer,\n            rebuildPosition,\n            rebuildPosition + ALIGNED_FRAME_LENGTH,\n            currentTimeNs,\n            MASK,\n            POSITION_BITS_TO_SHIFT,\n            TERM_ID)));\n\n        assertTrue(lossFound(lossDetector.scan(\n            termBuffer,\n            rebuildPosition,\n            rebuildPosition + ALIGNED_FRAME_LENGTH * 2L,\n            currentTimeNs,\n            MASK,\n            POSITION_BITS_TO_SHIFT,\n            TERM_ID)));\n\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(400);\n        assertFalse(lossFound(lossDetector.scan(\n            termBuffer,\n            rebuildPosition,\n            rebuildPosition + ALIGNED_FRAME_LENGTH * 2L,\n            currentTimeNs,\n            MASK,\n            POSITION_BITS_TO_SHIFT,\n            TERM_ID)));\n\n        insertDataFrame(offsetOfMessage(4));\n        assertTrue(lossFound(lossDetector.scan(\n            termBuffer,\n            rebuildPosition,\n            rebuildPosition + ALIGNED_FRAME_LENGTH * 2L,\n            currentTimeNs,\n            MASK,\n            POSITION_BITS_TO_SHIFT,\n            TERM_ID)));\n\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(500);\n        assertFalse(lossFound(lossDetector.scan(\n            termBuffer,\n            rebuildPosition,\n            rebuildPosition + ALIGNED_FRAME_LENGTH * 2L,\n            currentTimeNs,\n            MASK,\n            POSITION_BITS_TO_SHIFT,\n            TERM_ID)));\n\n        assertTrue(lossFound(lossDetector.scan(\n            termBuffer, rebuildPosition, rebuildPosition + 64, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID)));\n\n        currentTimeNs = TimeUnit.MILLISECONDS.toNanos(600);\n        assertFalse(lossFound(lossDetector.scan(\n            termBuffer, rebuildPosition, rebuildPosition + 64, currentTimeNs, MASK, POSITION_BITS_TO_SHIFT, TERM_ID)));\n\n        final InOrder inOrder = inOrder(lossHandler);\n        inOrder.verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(3), 32);\n        inOrder.verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(3), 64);\n        inOrder.verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(3), ALIGNED_FRAME_LENGTH * 2);\n        inOrder.verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(3), ALIGNED_FRAME_LENGTH);\n        inOrder.verify(lossHandler).onGapDetected(TERM_ID, offsetOfMessage(3), 64);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    private LossDetector getLossHandlerWithLongRetry()\n    {\n        return new LossDetector(DELAY_GENERATOR_WITH_LONGER_RETRY, lossHandler);\n    }\n\n    private void insertDataFrame(final int offset)\n    {\n        insertDataFrame(offset, DATA);\n    }\n\n    private void insertDataFrame(final int offset, final byte[] payload)\n    {\n        dataHeader\n            .termId(TERM_ID)\n            .streamId(STREAM_ID)\n            .sessionId(SESSION_ID)\n            .termOffset(offset)\n            .frameLength(payload.length + DataHeaderFlyweight.HEADER_LENGTH)\n            .headerType(HeaderFlyweight.HDR_TYPE_DATA)\n            .flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS)\n            .version(HeaderFlyweight.CURRENT_VERSION);\n\n        rcvBuffer.putBytes(dataHeader.dataOffset(), payload);\n\n        TermRebuilder.insert(termBuffer, offset, rcvBuffer, payload.length + DataHeaderFlyweight.HEADER_LENGTH);\n    }\n\n    private int offsetOfMessage(final int index)\n    {\n        return index * ALIGNED_FRAME_LENGTH;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/MediaDriverContextTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CncFileDescriptor;\nimport io.aeron.CommonContext;\nimport io.aeron.driver.MediaDriver.Context;\nimport io.aeron.driver.media.ControlTransportPoller;\nimport io.aeron.driver.media.DataTransportPoller;\nimport io.aeron.driver.media.UdpTransportPoller;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.exceptions.ConfigurationException;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.ObjectHashSet;\nimport org.agrona.concurrent.CountedErrorHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.hamcrest.CoreMatchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CopyOnWriteArraySet;\nimport java.util.concurrent.CyclicBarrier;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.Configuration.CALLER_RUNS_TASK_EXECUTOR;\nimport static io.aeron.driver.Configuration.COUNTERS_VALUES_BUFFER_LENGTH_MAX;\nimport static io.aeron.driver.Configuration.COUNTERS_VALUES_BUFFER_LENGTH_MIN;\nimport static io.aeron.driver.Configuration.ERROR_BUFFER_LENGTH_DEFAULT;\nimport static io.aeron.driver.Configuration.LOSS_REPORT_BUFFER_LENGTH_DEFAULT;\nimport static io.aeron.driver.Configuration.NAK_MAX_BACKOFF_DEFAULT_NS;\nimport static io.aeron.driver.Configuration.NAK_MULTICAST_MAX_BACKOFF_PROP_NAME;\nimport static io.aeron.driver.Configuration.NAK_UNICAST_DELAY_MIN_VALUE_NS;\nimport static io.aeron.driver.Configuration.UNTETHERED_LINGER_TIMEOUT_PROP_NAME;\nimport static io.aeron.driver.Configuration.UNTETHERED_WINDOW_LIMIT_TIMEOUT_DEFAULT_NS;\nimport static io.aeron.driver.Configuration.UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MAX_LENGTH;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\n\nclass MediaDriverContextTest\n{\n    private final Context context = new Context();\n\n    @AfterEach\n    void afterEach()\n    {\n        context.close();\n    }\n\n    @Test\n    void nakMulticastMaxBackoffNsDefaultValue()\n    {\n        assertEquals(NAK_MAX_BACKOFF_DEFAULT_NS, context.nakMulticastMaxBackoffNs());\n    }\n\n    @Test\n    void nakMulticastMaxBackoffNsValueFromSystemProperty()\n    {\n        System.setProperty(NAK_MULTICAST_MAX_BACKOFF_PROP_NAME, \"333\");\n        try\n        {\n            final Context context = new Context();\n            assertEquals(333, context.nakMulticastMaxBackoffNs());\n        }\n        finally\n        {\n            System.clearProperty(NAK_MULTICAST_MAX_BACKOFF_PROP_NAME);\n        }\n    }\n\n    @Test\n    void nakMulticastMaxBackoffNsExplicitValue()\n    {\n        context.nakMulticastMaxBackoffNs(Long.MIN_VALUE);\n        assertEquals(Long.MIN_VALUE, context.nakMulticastMaxBackoffNs());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { Integer.MIN_VALUE, -5, 0, 1024 * 1024, 1024 * 1024 + 64 * 12 - 1 })\n    void conductorBufferLengthMustBeWithinRange(final int length)\n    {\n        context.conductorBufferLength(length);\n\n        final ConfigurationException exception = assertThrows(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().contains(\"conductorBufferLength\"));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { Integer.MIN_VALUE, -5, 0, 1024 * 1024, 1024 * 1024 + 64 * 2 - 1 })\n    void toClientsBufferLengthMustBeWithinRange(final int length)\n    {\n        context.toClientsBufferLength(length);\n\n        final ConfigurationException exception = assertThrows(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().contains(\"toClientsBufferLength\"));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -76, 0, COUNTERS_VALUES_BUFFER_LENGTH_MIN - 1, COUNTERS_VALUES_BUFFER_LENGTH_MAX + 1 })\n    void counterValuesBufferLengthMustBeWithinRange(final int length)\n    {\n        context.counterValuesBufferLength(length);\n\n        final ConfigurationException exception = assertThrows(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().contains(\"counterValuesBufferLength\"));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -76, 0, ERROR_BUFFER_LENGTH_DEFAULT - 1 })\n    void errorBufferLengthMustBeWithinRange(final int length)\n    {\n        context.errorBufferLength(length);\n\n        final ConfigurationException exception = assertThrows(ConfigurationException.class, context::conclude);\n        assertTrue(exception.getMessage().contains(\"errorBufferLength\"));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -6, 0, 5, LOSS_REPORT_BUFFER_LENGTH_DEFAULT - 4096 })\n    void lossReportBufferLengthMustBeWithinRange(final int length, final @TempDir Path temp) throws IOException\n    {\n        final Path aeronDir = temp.resolve(\"aeron\");\n        Files.createDirectories(aeronDir);\n\n        context.aeronDirectoryName(aeronDir.toString());\n        context.lossReportBufferLength(length);\n\n        final ConfigurationException exception = assertThrows(ConfigurationException.class, context::conclude);\n        assertThat(exception.getMessage(), containsString(\"lossReportBufferLength\"));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -76, -3, TERM_MAX_LENGTH + 1 })\n    void publicationTermWindowLengthMustBeWithinRange(final int length)\n    {\n        context.publicationTermWindowLength(length);\n\n        final ConfigurationException exception = assertThrows(ConfigurationException.class, context::conclude);\n        assertThat(exception.getMessage(), containsString(\"publicationTermWindowLength\"));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -76, -3, TERM_MAX_LENGTH + 1 })\n    void ipcPublicationTermWindowLengthMustBeWithinRange(final int length)\n    {\n        context.ipcPublicationTermWindowLength(length);\n\n        final ConfigurationException exception = assertThrows(ConfigurationException.class, context::conclude);\n        assertThat(exception.getMessage(), containsString(\"ipcPublicationTermWindowLength\"));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -100, 42, Integer.MAX_VALUE })\n    void asyncTaskExecutorThreadCount(final int threadCount)\n    {\n        assertEquals(1, context.asyncTaskExecutorThreads());\n\n        context.asyncTaskExecutorThreads(threadCount);\n        assertEquals(threadCount, context.asyncTaskExecutorThreads());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { -5, 0 })\n    void shouldDisableAsyncExecutionIfThreadsAreNotConfigured(final int asyncExecutorThreadCount)\n    {\n        context.asyncTaskExecutorThreads(asyncExecutorThreadCount);\n        assertNull(context.asyncTaskExecutor());\n\n        context.concludeNullProperties();\n\n        final Executor asyncTaskExecutor = context.asyncTaskExecutor();\n        assertNotNull(asyncTaskExecutor);\n        assertSame(CALLER_RUNS_TASK_EXECUTOR, asyncTaskExecutor);\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 1, 4 })\n    void shouldCreateFixedThreadPoolExecutor(final int asyncExecutorThreadCount) throws Exception\n    {\n        assertNull(context.asyncTaskExecutor());\n        context.asyncTaskExecutorThreads(asyncExecutorThreadCount);\n\n        context.concludeNullProperties();\n\n        final Executor asyncTaskExecutor = context.asyncTaskExecutor();\n        assertNotNull(asyncTaskExecutor);\n        assertInstanceOf(ThreadPoolExecutor.class, asyncTaskExecutor);\n\n        final ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor)asyncTaskExecutor;\n        assertEquals(asyncExecutorThreadCount, threadPoolExecutor.getCorePoolSize());\n        assertEquals(asyncExecutorThreadCount, threadPoolExecutor.getPoolSize());\n\n        final CyclicBarrier barrier = new CyclicBarrier(asyncExecutorThreadCount + 1);\n        final CopyOnWriteArraySet<Thread> threads = new CopyOnWriteArraySet<>();\n        final Callable<Void> task = () ->\n        {\n            threads.add(Thread.currentThread());\n            barrier.await();\n            return null;\n        };\n        for (int i = 0; i < asyncExecutorThreadCount; i++)\n        {\n            threadPoolExecutor.submit(task);\n        }\n\n        barrier.await(10, TimeUnit.SECONDS);\n\n        assertEquals(asyncExecutorThreadCount, threads.size());\n        assertEquals(asyncExecutorThreadCount, threadPoolExecutor.getPoolSize());\n\n        final ObjectHashSet<String> uniqueNames = new ObjectHashSet<>(asyncExecutorThreadCount);\n        for (final Thread t : threads)\n        {\n            assertThat(t.getName(), CoreMatchers.startsWith(\"async-executor\"));\n            assertTrue(t.isDaemon());\n            assertTrue(uniqueNames.add(t.getName()));\n        }\n    }\n\n    @Test\n    void shouldAllowSettingTheAsyncTaskExecutor()\n    {\n        final Executor asynTaskExecutor = mock(Executor.class);\n        context.asyncTaskExecutor(asynTaskExecutor);\n        assertSame(asynTaskExecutor, context.asyncTaskExecutor());\n\n        context.concludeNullProperties();\n\n        assertSame(asynTaskExecutor, context.asyncTaskExecutor());\n    }\n\n    @Test\n    void shouldNotCloseAsyncTaskExecutor()\n    {\n        final ExecutorService asyncTaskExecutor = mock(ExecutorService.class);\n        context.asyncTaskExecutor(asyncTaskExecutor);\n\n        context.close();\n\n        verify(asyncTaskExecutor, never()).shutdownNow();\n    }\n\n    @ParameterizedTest\n    @NullAndEmptySource\n    void resolverNameIsRequiredWhenDriverNameResolutionIsUsed(final String noName)\n    {\n        context.resolverName(noName)\n            .resolverInterface(\"127.0.0.1\");\n\n        final ConfigurationException exception = assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\"ERROR - `resolverName` is required when `resolverInterface` is set\", exception.getMessage());\n    }\n\n    @Test\n    void shouldAssignCountedErrorHandler(@TempDir final Path tempDir)\n    {\n        context.aeronDirectoryName(tempDir.toString());\n        assertNull(context.countedErrorHandler());\n        final CountedErrorHandler countedErrorHandler = mock(CountedErrorHandler.class);\n        context.countedErrorHandler(countedErrorHandler);\n        assertSame(countedErrorHandler, context.countedErrorHandler());\n\n        context.conclude();\n        assertSame(countedErrorHandler, context.countedErrorHandler());\n    }\n\n    @Test\n    void shouldWrapErrorHandler(@TempDir final Path tempDir)\n    {\n        context.aeronDirectoryName(tempDir.toString());\n        final ErrorHandler customHandler = mock(ErrorHandler.class);\n        context.errorHandler(customHandler);\n        assertNull(context.countedErrorHandler());\n\n        context.conclude();\n\n        final ErrorHandler errorHandler = context.errorHandler();\n        assertNotNull(errorHandler);\n        assertNotSame(customHandler, errorHandler);\n        final CountedErrorHandler countedErrorHandler = context.countedErrorHandler();\n        assertNotNull(countedErrorHandler);\n\n        final RuntimeException exception = new RuntimeException(\"test\");\n        countedErrorHandler.onError(exception);\n\n        verify(customHandler).onError(exception);\n        verifyNoMoreInteractions(customHandler);\n        final AtomicCounter errorCounter = context.systemCounters().get(SystemCounterDescriptor.ERRORS);\n        assertEquals(1, errorCounter.get());\n    }\n\n    @Test\n    void shouldCreateControlTransportPollerWithCountedErrorHandler(@TempDir final Path tempDir) throws Exception\n    {\n        context.aeronDirectoryName(tempDir.toString());\n        context.controlTransportPoller(null);\n\n        context.conclude();\n\n        final ControlTransportPoller controlTransportPoller = context.controlTransportPoller();\n        assertNotNull(controlTransportPoller);\n\n        assertSame(context.countedErrorHandler(), getErrorHandler(controlTransportPoller));\n    }\n\n    @Test\n    void shouldCreateDataTransportPollerWithCountedErrorHandler(@TempDir final Path tempDir) throws Exception\n    {\n        context.aeronDirectoryName(tempDir.toString());\n        context.dataTransportPoller(null);\n\n        context.conclude();\n\n        final DataTransportPoller dataTransportPoller = context.dataTransportPoller();\n        assertNotNull(dataTransportPoller);\n\n        assertSame(context.countedErrorHandler(), getErrorHandler(dataTransportPoller));\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 4096, 2 * 1024 * 1024 })\n    void shouldAddFilePageSizeToTheCncFile(final int filePageSize) throws IOException\n    {\n        final Path dir = Paths.get(CommonContext.generateRandomDirName());\n        Files.createDirectories(dir);\n        context\n            .aeronDirectoryName(dir.toString())\n            .dirDeleteOnShutdown(true)\n            .filePageSize(filePageSize);\n\n        context.conclude();\n\n        final UnsafeBuffer metaDataBuffer = CncFileDescriptor.createMetaDataBuffer(context.cncByteBuffer());\n        assertEquals(filePageSize, CncFileDescriptor.filePageSize(metaDataBuffer));\n    }\n\n    private static ErrorHandler getErrorHandler(final UdpTransportPoller transportPoller) throws Exception\n    {\n        final Field field = UdpTransportPoller.class.getDeclaredField(\"errorHandler\");\n        field.setAccessible(true);\n        return (ErrorHandler)field.get(transportPoller);\n    }\n\n    @Test\n    void shouldTestDefaultUntetheredWindowLimitTimeout()\n    {\n        assertEquals(UNTETHERED_WINDOW_LIMIT_TIMEOUT_DEFAULT_NS, context.untetheredWindowLimitTimeoutNs());\n    }\n\n    @Test\n    void shouldTestDefaultUntetheredLingerTimeout()\n    {\n        assertEquals(Aeron.NULL_VALUE, context.untetheredLingerTimeoutNs());\n    }\n\n    @Test\n    void shouldHonorSystemPropertyUntetheredLingerTimeout()\n    {\n        System.setProperty(UNTETHERED_LINGER_TIMEOUT_PROP_NAME, \"222ms\");\n        try\n        {\n            final Context ctx = new Context();\n            assertEquals(TimeUnit.MILLISECONDS.toNanos(222), ctx.untetheredLingerTimeoutNs());\n        }\n        finally\n        {\n            System.clearProperty(UNTETHERED_LINGER_TIMEOUT_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldHonorSystemPropertyWindowLimitTimeout()\n    {\n        System.setProperty(UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME, \"444ms\");\n        try\n        {\n            final Context ctx = new Context();\n            assertEquals(TimeUnit.MILLISECONDS.toNanos(444), ctx.untetheredWindowLimitTimeoutNs());\n        }\n        finally\n        {\n            System.clearProperty(UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldHonorSystemPropertyOverrideUntetheredLingerTimeoutAndWindowTimeout()\n    {\n        System.setProperty(UNTETHERED_LINGER_TIMEOUT_PROP_NAME, \"222ms\");\n        System.setProperty(UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME, \"444ms\");\n        try\n        {\n            final Context ctx = new Context();\n            assertEquals(TimeUnit.MILLISECONDS.toNanos(222), ctx.untetheredLingerTimeoutNs());\n            assertEquals(TimeUnit.MILLISECONDS.toNanos(444), ctx.untetheredWindowLimitTimeoutNs());\n        }\n        finally\n        {\n            System.clearProperty(UNTETHERED_LINGER_TIMEOUT_PROP_NAME);\n            System.clearProperty(UNTETHERED_WINDOW_LIMIT_TIMEOUT_PROP_NAME);\n        }\n    }\n\n    @Test\n    void shouldUseNullForUntetheredLingerTimeoutEvenIfWindowIsSet()\n    {\n        final Context ctx = new Context().untetheredWindowLimitTimeoutNs(35326745);\n        assertEquals(Aeron.NULL_VALUE, ctx.untetheredLingerTimeoutNs());\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { Long.MIN_VALUE, -1, 0, NAK_UNICAST_DELAY_MIN_VALUE_NS - 1 })\n    void shouldRejectInvalidNakUnicastDelay(final long nakUnicastDelayNs)\n    {\n        context.nakUnicastDelayNs(nakUnicastDelayNs);\n\n        final ConfigurationException exception =\n            assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - nakUnicastDelayNs less than min size of \" + NAK_UNICAST_DELAY_MIN_VALUE_NS + \": \" +\n                nakUnicastDelayNs,\n            exception.getMessage());\n    }\n\n    @Test\n    void shouldAcceptMinNakUnicastDelay(final @TempDir Path tempDir)\n    {\n        context\n            .aeronDirectoryName(tempDir.toString())\n            .nakUnicastDelayNs(NAK_UNICAST_DELAY_MIN_VALUE_NS);\n\n        context.conclude();\n\n        assertEquals(NAK_UNICAST_DELAY_MIN_VALUE_NS, context.nakUnicastDelayNs());\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { Long.MIN_VALUE, -1, 0 })\n    void shouldRejectInvalidNakUnicastRetryDelayRatio(final long nakUnicastRetryDelayRatio)\n    {\n        context.nakUnicastRetryDelayRatio(nakUnicastRetryDelayRatio);\n\n        final ConfigurationException exception =\n            assertThrowsExactly(ConfigurationException.class, context::conclude);\n        assertEquals(\n            \"ERROR - nakUnicastRetryDelayRatio less than min size of 1: \" +\n                nakUnicastRetryDelayRatio,\n            exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @CsvSource({\"5000000000,1000000000000\"})\n    void shouldRejectInvalidNakUnicastDelayRetryCombination(\n        final long nakUnicastDelayNs, final long nakUnicastRetryDelayRatio)\n    {\n        context\n            .nakUnicastDelayNs(nakUnicastDelayNs)\n            .nakUnicastRetryDelayRatio(nakUnicastRetryDelayRatio);\n\n        final ArithmeticException exception =\n            assertThrowsExactly(ArithmeticException.class, context::conclude);\n        assertEquals(\"long overflow\", exception.getMessage());\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/MediaDriverTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.driver.status.DutyCycleStallTracker;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.status.SystemCounterDescriptor.*;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.endsWith;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MediaDriverTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    void shouldPrintConfigOnStart()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .threadingMode(ThreadingMode.SHARED)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .printConfigurationOnStart(true);\n\n        final ByteArrayOutputStream os = new ByteArrayOutputStream();\n        final PrintStream printStream = new PrintStream(os);\n        final PrintStream out = System.out;\n        System.setOut(printStream);\n\n        try (MediaDriver mediaDriver = MediaDriver.launch(context))\n        {\n            assertTrue(mediaDriver.context().printConfigurationOnStart());\n            assertThat(os.toString(), containsString(\"printConfigurationOnStart=true\"));\n        }\n        finally\n        {\n            System.setOut(out);\n        }\n    }\n\n    @ParameterizedTest\n    @EnumSource(ThreadingMode.class)\n    void shouldInitializeDutyCycleTrackersWhenNotSet(\n        final ThreadingMode threadingMode, final @TempDir Path tempDir) throws IOException\n    {\n        final Path aeronDir = tempDir.resolve(\"aeron\");\n        Files.createDirectories(aeronDir);\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.toString())\n            .threadingMode(threadingMode)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .conductorCycleThresholdNs(TimeUnit.SECONDS.toNanos(1))\n            .senderCycleThresholdNs(TimeUnit.MILLISECONDS.toNanos(50))\n            .receiverCycleThresholdNs(TimeUnit.MICROSECONDS.toNanos(3))\n            .nameResolverThresholdNs(101010);\n\n        assertNull(context.countersManager());\n        assertNull(context.systemCounters());\n        assertNull(context.conductorDutyCycleTracker());\n        assertNull(context.senderDutyCycleTracker());\n        assertNull(context.receiverDutyCycleTracker());\n        assertNull(context.nameResolverTimeTracker());\n\n        try\n        {\n            context.conclude();\n\n            verifyStallTracker(\n                context.conductorDutyCycleTracker(),\n                CONDUCTOR_MAX_CYCLE_TIME,\n                CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED,\n                context.conductorCycleThresholdNs(),\n                \": \" + threadingMode,\n                \": threshold=1s \" + threadingMode);\n            verifyStallTracker(\n                context.senderDutyCycleTracker(),\n                SENDER_MAX_CYCLE_TIME,\n                SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED,\n                context.senderCycleThresholdNs(),\n                \": \" + threadingMode,\n                \": threshold=50ms \" + threadingMode);\n            verifyStallTracker(\n                context.receiverDutyCycleTracker(),\n                RECEIVER_MAX_CYCLE_TIME,\n                RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED,\n                context.receiverCycleThresholdNs(),\n                \": \" + threadingMode,\n                \": threshold=3us \" + threadingMode);\n            verifyStallTracker(\n                context.nameResolverTimeTracker(),\n                NAME_RESOLVER_MAX_TIME,\n                NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED,\n                context.nameResolverThresholdNs(),\n                NAME_RESOLVER_MAX_TIME.label(),\n                \": threshold=101010ns\");\n        }\n        finally\n        {\n            context.close();\n        }\n    }\n\n    @Test\n    void shouldUseProvidedDutyCycleTrackers(final @TempDir Path tempDir) throws IOException\n    {\n        final Path aeronDir = tempDir.resolve(\"aeron\");\n        Files.createDirectories(aeronDir);\n        final DutyCycleTracker conductorDutyCycleTracker = new DutyCycleTracker();\n        final DutyCycleTracker senderDutyCycleTracker = new DutyCycleTracker();\n        final DutyCycleTracker receiverDutyCycleTracker = new DutyCycleTracker();\n        final DutyCycleTracker nameResolverTimeTracker = new DutyCycleTracker();\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.toString())\n            .threadingMode(ThreadingMode.SHARED)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .conductorDutyCycleTracker(conductorDutyCycleTracker)\n            .senderDutyCycleTracker(senderDutyCycleTracker)\n            .receiverDutyCycleTracker(receiverDutyCycleTracker)\n            .nameResolverTimeTracker(nameResolverTimeTracker);\n\n        assertSame(conductorDutyCycleTracker, context.conductorDutyCycleTracker());\n        assertSame(senderDutyCycleTracker, context.senderDutyCycleTracker());\n        assertSame(receiverDutyCycleTracker, context.receiverDutyCycleTracker());\n        assertSame(nameResolverTimeTracker, context.nameResolverTimeTracker());\n\n        try\n        {\n            context.conclude();\n\n            assertSame(conductorDutyCycleTracker, context.conductorDutyCycleTracker());\n            assertSame(senderDutyCycleTracker, context.senderDutyCycleTracker());\n            assertSame(receiverDutyCycleTracker, context.receiverDutyCycleTracker());\n            assertSame(nameResolverTimeTracker, context.nameResolverTimeTracker());\n        }\n        finally\n        {\n            context.close();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldExecuteAsyncCommandsInOrder(final @TempDir Path tempDir)\n    {\n        final Path aeronDir = tempDir.resolve(\"aeron\");\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.toString())\n            .threadingMode(ThreadingMode.DEDICATED)\n            .asyncTaskExecutorThreads(2);\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(context, systemTestWatcher))\n        {\n            systemTestWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n            try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDir.toString())))\n            {\n                final String channel = \"aeron:udp?endpoint=localhost:5050\";\n                final int streamId = 1111;\n                final long pubId1 = aeron.asyncAddExclusivePublication(channel, streamId);\n                aeron.asyncRemovePublication(pubId1);\n\n                final long pubId2 = aeron.asyncAddExclusivePublication(channel, streamId);\n\n                Tests.await(() -> null != aeron.getExclusivePublication(pubId2));\n\n                assertNull(aeron.getExclusivePublication(pubId1));\n                assertNotNull(aeron.getExclusivePublication(pubId2));\n            }\n        }\n    }\n\n    private static void verifyStallTracker(\n        final DutyCycleTracker dutyCycleTracker,\n        final SystemCounterDescriptor maxCycleTimeCounter,\n        final SystemCounterDescriptor cycleTimeThresholdExceededCounter,\n        final long cycleTimeThresholdNs,\n        final String maxCycleLabelSuffix,\n        final String thresholdLabelSuffix)\n    {\n        final DutyCycleStallTracker stallTracker = assertInstanceOf(DutyCycleStallTracker.class, dutyCycleTracker);\n        assertEquals(maxCycleTimeCounter.id(), stallTracker.maxCycleTime().id());\n        assertEquals(cycleTimeThresholdExceededCounter.id(), stallTracker.cycleTimeThresholdExceededCount().id());\n        assertEquals(cycleTimeThresholdNs, stallTracker.cycleTimeThresholdNs());\n        assertThat(stallTracker.maxCycleTime().label(), endsWith(maxCycleLabelSuffix));\n        assertThat(stallTracker.cycleTimeThresholdExceededCount().label(), endsWith(thresholdLabelSuffix));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/MinMulticastFlowControlTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport io.aeron.test.Tests;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass MinMulticastFlowControlTest\n{\n    private static final int DEFAULT_GROUP_SIZE = 3;\n    private static final long DEFAULT_TIMEOUT = Configuration.flowControlReceiverTimeoutNs();\n    private static final int WINDOW_LENGTH = 16 * 1024;\n    private static final int COUNTERS_BUFFER_LENGTH = 16 * 1024;\n\n    private final UnsafeBuffer tempBuffer = new UnsafeBuffer(new byte[8192]);\n    private final MinMulticastFlowControl flowControl = new MinMulticastFlowControl();\n    private final CountersManager countersManager = Tests.newCountersManager(COUNTERS_BUFFER_LENGTH);\n\n    private static Stream<Arguments> validUris()\n    {\n        return Stream.of(\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min\",\n                DEFAULT_GROUP_SIZE, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,t:100ms\",\n                DEFAULT_GROUP_SIZE, 100_000_000),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:123\",\n                DEFAULT_GROUP_SIZE, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:3000000000\",\n                DEFAULT_GROUP_SIZE, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:123,t:100ms\",\n                DEFAULT_GROUP_SIZE, 100_000_000),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:100/10\",\n                10, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:/10\",\n                10, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:100/10,t:100ms\",\n                10, 100_000_000));\n    }\n\n    MediaDriver.Context newContext()\n    {\n        return new MediaDriver.Context().tempBuffer(tempBuffer);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"validUris\")\n    void shouldParseValidFlowControlConfiguration(final String uri, final int groupSize, final long timeout)\n    {\n        flowControl.initialize(\n            newContext().flowControlGroupMinSize(DEFAULT_GROUP_SIZE),\n            countersManager, UdpChannel.parse(uri), 0, 0, 0, 0, 0);\n\n        assertEquals(groupSize, flowControl.groupMinSize());\n        assertEquals(timeout, flowControl.receiverTimeoutNs());\n    }\n\n    @Test\n    void shouldNotBeConnectedUntilGroupMinSizeReached()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:/3\");\n\n        flowControl.initialize(\n            newContext(), countersManager, udpChannel, 0, 0, 0, 0, 0);\n\n        onStatusMessage(flowControl, 1, 0, 5000);\n        assertFalse(flowControl.hasRequiredReceivers());\n        onStatusMessage(flowControl, 2, 0, 5000);\n        assertFalse(flowControl.hasRequiredReceivers());\n        onStatusMessage(flowControl, 2, 0, 5000);\n        assertFalse(flowControl.hasRequiredReceivers());\n        onStatusMessage(flowControl, 3, 0, 5000);\n        assertTrue(flowControl.hasRequiredReceivers());\n    }\n\n    @Test\n    void shouldReportSenderLimitUntilGroupMinSizeIsReached()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:/3\");\n\n        flowControl.initialize(\n            newContext(), countersManager, udpChannel, 0, 0, 0, 0, 0);\n\n        final int senderLimit = 5000;\n        final int termOffset = 6000;\n        assertEquals(senderLimit, onStatusMessage(flowControl, 1, termOffset, senderLimit));\n        assertEquals(senderLimit, onIdle(flowControl, senderLimit));\n        assertEquals(senderLimit, onStatusMessage(flowControl, 2, termOffset, senderLimit));\n        assertEquals(senderLimit, onIdle(flowControl, senderLimit));\n        assertEquals(senderLimit, onStatusMessage(flowControl, 2, termOffset, senderLimit));\n        assertEquals(senderLimit, onIdle(flowControl, senderLimit));\n        assertEquals(termOffset + WINDOW_LENGTH, onStatusMessage(flowControl, 3, termOffset, senderLimit));\n        assertEquals(termOffset + WINDOW_LENGTH, onIdle(flowControl, senderLimit));\n    }\n\n    @Test\n    void shouldNotIncludeReceiverMoreThanWindowSizeBehindMinPosition()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:/2\");\n\n        flowControl.initialize(\n            newContext(), countersManager, udpChannel, 0, 0, 0, 0, 0);\n\n        final int senderLimit = 5000;\n        final int termOffset0 = WINDOW_LENGTH * 2;\n        final int termOffset1 = termOffset0 - (WINDOW_LENGTH + 1);\n        final int termOffset2 = termOffset0 - (WINDOW_LENGTH);\n\n        assertEquals(senderLimit, onStatusMessage(flowControl, 1, termOffset0, senderLimit));\n        assertEquals(senderLimit, onStatusMessage(flowControl, 2, termOffset1, senderLimit));\n        assertEquals(termOffset2 + WINDOW_LENGTH, onStatusMessage(flowControl, 3, termOffset2, senderLimit));\n    }\n\n    private long onStatusMessage(\n        final MinMulticastFlowControl flowControl, final long receiverId, final int termOffset, final long senderLimit)\n    {\n        final StatusMessageFlyweight statusMessageFlyweight = new StatusMessageFlyweight();\n        statusMessageFlyweight.wrap(new byte[1024]);\n\n        statusMessageFlyweight.receiverId(receiverId);\n        statusMessageFlyweight.consumptionTermId(0);\n        statusMessageFlyweight.consumptionTermOffset(termOffset);\n        statusMessageFlyweight.receiverWindowLength(WINDOW_LENGTH);\n\n        return flowControl.onStatusMessage(statusMessageFlyweight, null, senderLimit, 0, 0, 0);\n    }\n\n    private long onIdle(final MinMulticastFlowControl flowControl, final long senderLimit)\n    {\n        return flowControl.onIdle(0, senderLimit, 0, false);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/NetworkPublicationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.AtomicLongPosition;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.Position;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.util.stream.Stream;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass NetworkPublicationTest\n{\n    private static final int REGISTRATION_ID = 42;\n    private static final int PUBLICATION_WINDOW_LENGTH = TERM_MIN_LENGTH / 2;\n    private static final int SESSION_ID = 8;\n    private static final int STREAM_ID = 101;\n    private static final int INITIAL_TERM_ID = 99;\n    private static final NetworkPublicationThreadLocals NETWORK_PUBLICATION_THREAD_LOCALS =\n        new NetworkPublicationThreadLocals();\n    private static final UnsafeBuffer[] TERM_BUFFERS = new UnsafeBuffer[PARTITION_COUNT];\n\n    static\n    {\n        for (int i = 0; i < TERM_BUFFERS.length; i++)\n        {\n            TERM_BUFFERS[i] = new UnsafeBuffer(BufferUtil.allocateDirectAligned(TERM_MIN_LENGTH, CACHE_LINE_LENGTH));\n        }\n    }\n\n    private final CountersManager countersManager = new CountersManager(\n        new UnsafeBuffer(BufferUtil.allocateDirectAligned(TERM_MIN_LENGTH, CACHE_LINE_LENGTH)),\n        new UnsafeBuffer(BufferUtil.allocateDirectAligned(8192, CACHE_LINE_LENGTH)));\n    private final MediaDriver.Context ctx = new MediaDriver.Context()\n        .senderCachedNanoClock(new CachedNanoClock())\n        .systemCounters(new SystemCounters(countersManager));\n    private final PublicationParams params = new PublicationParams();\n    private final SendChannelEndpoint sendChannelEndpoint = mock(SendChannelEndpoint.class);\n    private final RawLog rawLog = mock(RawLog.class);\n    private final Position publisherPos = new AtomicLongPosition();\n    private final Position publisherLimit = new AtomicLongPosition();\n    private final Position senderPosition = new AtomicLongPosition();\n    private final Position senderLimit = new AtomicLongPosition();\n    private final AtomicCounter senderBpe = countersManager.newCounter(\"snd-bpe\");\n    private final AtomicCounter senderNaksReceived = countersManager.newCounter(\"snd-naks-received\");\n    private final FlowControl flowControl = mock(FlowControl.class);\n    private final RetransmitHandler retransmitHandler = mock(RetransmitHandler.class);\n    private final StatusMessageFlyweight statusMessageFlyweight = mock(StatusMessageFlyweight.class);\n    private final InetSocketAddress inetSocketAddress = mock(InetSocketAddress.class);\n    private final DriverConductorProxy driverConductorProxy = mock(DriverConductorProxy.class);\n\n    @BeforeEach\n    void before()\n    {\n        final UnsafeBuffer metadataBuffer = new UnsafeBuffer(new byte[LOG_META_DATA_LENGTH]);\n        termLength(metadataBuffer, TERM_MIN_LENGTH);\n        mtuLength(metadataBuffer, 8192);\n        initialTermId(metadataBuffer, INITIAL_TERM_ID);\n        initialiseTailWithTermId(metadataBuffer, 0, INITIAL_TERM_ID);\n        for (int i = 1; i < PARTITION_COUNT; i++)\n        {\n            final int expectedTermId = (INITIAL_TERM_ID + i) - PARTITION_COUNT;\n            initialiseTailWithTermId(metadataBuffer, i, expectedTermId);\n        }\n        isConnected(metadataBuffer, true);\n\n        when(rawLog.metaData()).thenReturn(metadataBuffer);\n        when(rawLog.termBuffers()).thenReturn(TERM_BUFFERS);\n        when(rawLog.sliceTerms()).thenReturn(\n            Stream.of(TERM_BUFFERS).map(UnsafeBuffer::byteBuffer).toArray(ByteBuffer[]::new));\n        when(rawLog.termLength()).thenReturn(TERM_MIN_LENGTH);\n\n        for (final UnsafeBuffer termBuffer : TERM_BUFFERS)\n        {\n            termBuffer.setMemory(0, termBuffer.capacity(), (byte)0xFF);\n        }\n\n        when(flowControl.hasRequiredReceivers()).thenReturn(true);\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    @ParameterizedTest\n    @ValueSource(longs = { 0L, 512L, 7281770624L })\n    void shouldCleanLogBufferOneTermBehindMinSubPosition(final long initialPosition)\n    {\n        publisherPos.set(initialPosition);\n        publisherLimit.set(initialPosition);\n        senderPosition.set(initialPosition);\n        senderLimit.set(initialPosition);\n\n        final NetworkPublication publication = new NetworkPublication(\n            REGISTRATION_ID,\n            ctx,\n            params,\n            sendChannelEndpoint,\n            rawLog,\n            PUBLICATION_WINDOW_LENGTH,\n            publisherPos,\n            publisherLimit,\n            senderPosition,\n            senderLimit,\n            senderBpe,\n            senderNaksReceived,\n            SESSION_ID,\n            STREAM_ID,\n            INITIAL_TERM_ID,\n            flowControl,\n            retransmitHandler,\n            NETWORK_PUBLICATION_THREAD_LOCALS,\n            true);\n        assertEquals(initialPosition, publication.cleanPosition);\n        final long initialTermBasePosition = initialPosition - (initialPosition & (TERM_MIN_LENGTH - 1));\n\n        // setup initial connection\n        publication.onStatusMessage(statusMessageFlyweight, inetSocketAddress, driverConductorProxy);\n\n        // first iteration: only pub-lmt is updated\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition, publication.cleanPosition);\n\n        // consumer position didn't change -> no op\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition, publication.cleanPosition);\n\n        // consumer position moved -> update limit\n        senderPosition.set(initialPosition + 3072);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + 3072 + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition, publication.cleanPosition);\n\n        // consumer position moved -> update limit\n        senderPosition.set(initialPosition + TERM_MIN_LENGTH / 8);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + TERM_MIN_LENGTH / 8 + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition, publication.cleanPosition);\n\n        // consumer position moved by an entire term -> nothing to clean yet\n        senderPosition.set(initialPosition + TERM_MIN_LENGTH);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + TERM_MIN_LENGTH + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition, publication.cleanPosition);\n\n        // consumer position moved again => clean bytes within a first buffer\n        senderPosition.set(initialPosition + TERM_MIN_LENGTH + 128);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + TERM_MIN_LENGTH + 128 + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition + 128, publication.cleanPosition);\n\n        // consumer position moved again => clean the entire first buffer\n        senderPosition.set(initialPosition + 2 * TERM_MIN_LENGTH + 192);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + 2 * TERM_MIN_LENGTH + 192 + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialTermBasePosition + TERM_MIN_LENGTH, publication.cleanPosition);\n\n        // buffer clean trails snd-pos updates by one term\n        senderPosition.set(initialPosition + 2 * TERM_MIN_LENGTH + 4096);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + 2 * TERM_MIN_LENGTH + 4096 + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition + TERM_MIN_LENGTH + 4096, publication.cleanPosition);\n    }\n\n    @Test\n    void pubLimitShouldNotCrossToThePreviousTermIfAnEntireTermIsDirty()\n    {\n        final long initialPosition = TERM_MIN_LENGTH - 1440;\n        publisherPos.set(initialPosition);\n        publisherLimit.set(initialPosition);\n        senderPosition.set(initialPosition);\n        senderLimit.set(initialPosition);\n\n        final NetworkPublication publication = new NetworkPublication(\n            REGISTRATION_ID,\n            ctx,\n            params,\n            sendChannelEndpoint,\n            rawLog,\n            PUBLICATION_WINDOW_LENGTH,\n            publisherPos,\n            publisherLimit,\n            senderPosition,\n            senderLimit,\n            senderBpe,\n            senderNaksReceived,\n            SESSION_ID,\n            STREAM_ID,\n            INITIAL_TERM_ID,\n            flowControl,\n            retransmitHandler,\n            NETWORK_PUBLICATION_THREAD_LOCALS,\n            true);\n        assertEquals(initialPosition, publication.cleanPosition);\n\n        // setup initial connection\n        publication.onStatusMessage(statusMessageFlyweight, inetSocketAddress, driverConductorProxy);\n\n        // no buffer cleaning at first\n        senderPosition.set(initialPosition + TERM_MIN_LENGTH - 128);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + TERM_MIN_LENGTH - 128 + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition, publication.cleanPosition);\n\n        // pub-lmt cannot be changed as clean position is at the start of a dirty term\n        senderPosition.set(initialPosition + 2 * TERM_MIN_LENGTH);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + TERM_MIN_LENGTH - 128 + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(TERM_MIN_LENGTH, publication.cleanPosition);\n\n        // pub-lmt is allowed to update after clean position moves from the start of a dirty buffer\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(senderPosition.get() + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition + TERM_MIN_LENGTH, publication.cleanPosition);\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = {0, TERM_MIN_LENGTH - PUBLICATION_WINDOW_LENGTH})\n    void pubLimitShouldNotCrossToTheDirtyTerm(final long initialPosition)\n    {\n        publisherPos.set(initialPosition);\n        publisherLimit.set(initialPosition);\n        senderPosition.set(initialPosition);\n        senderLimit.set(initialPosition);\n\n        final NetworkPublication publication = new NetworkPublication(\n            REGISTRATION_ID,\n            ctx,\n            params,\n            sendChannelEndpoint,\n            rawLog,\n            PUBLICATION_WINDOW_LENGTH,\n            publisherPos,\n            publisherLimit,\n            senderPosition,\n            senderLimit,\n            senderBpe,\n            senderNaksReceived,\n            SESSION_ID,\n            STREAM_ID,\n            INITIAL_TERM_ID,\n            flowControl,\n            retransmitHandler,\n            NETWORK_PUBLICATION_THREAD_LOCALS,\n            true);\n        assertEquals(initialPosition, publication.cleanPosition);\n\n        // setup initial connection\n        publication.onStatusMessage(statusMessageFlyweight, inetSocketAddress, driverConductorProxy);\n\n        // initial position\n        senderPosition.set(initialPosition + 256);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + 256 + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition, publication.cleanPosition);\n\n        // new pub-lmt intersects with the clean position\n        senderPosition.set(initialPosition + 2 * TERM_MIN_LENGTH + PUBLICATION_WINDOW_LENGTH);\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + 256 + PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(TERM_MIN_LENGTH, publication.cleanPosition);\n\n        // after cleanup pub-lmt can move again\n        publication.updatePublisherPositionAndLimit();\n        assertEquals(initialPosition + 2 * TERM_MIN_LENGTH + 2 * PUBLICATION_WINDOW_LENGTH, publisherLimit.get());\n        assertEquals(initialPosition + TERM_MIN_LENGTH + PUBLICATION_WINDOW_LENGTH, publication.cleanPosition);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/OptimalMulticastDelayGeneratorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\n\nclass OptimalMulticastDelayGeneratorTest\n{\n    private static final long MAX_BACKOFF = TimeUnit.MILLISECONDS.toNanos(60);\n    private static final long GROUP_SIZE = 10;\n\n    @Test\n    void shouldNotExceedTmaxBackoff()\n    {\n        final OptimalMulticastDelayGenerator generator = new OptimalMulticastDelayGenerator(MAX_BACKOFF, GROUP_SIZE);\n\n        for (int i = 0; i < 100_000; i++)\n        {\n            final double delay = generator.generateNewOptimalDelay();\n            assertThat(delay, lessThanOrEqualTo((double)MAX_BACKOFF));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/PublicationImageTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.reports.LossReport;\nimport io.aeron.driver.status.ReceiverHwm;\nimport io.aeron.driver.status.ReceiverNaksSent;\nimport io.aeron.driver.status.ReceiverPos;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.BitUtil;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.Position;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\n\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_META_DATA_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PARTITION_COUNT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.indexByPosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.positionBitsToShift;\nimport static io.aeron.protocol.DataHeaderFlyweight.BEGIN_AND_END_FLAGS;\nimport static io.aeron.protocol.DataHeaderFlyweight.CURRENT_VERSION;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_DATA;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_PAD;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass PublicationImageTest\n{\n    private static final int TERM_LENGTH = 64 * 1024;\n    private static final int INITIAL_WINDOW_LENGTH = 128 * 1024;\n    private static final int MAX_WINDOW_LENGHT = 1024 * 1024;\n    private static final long CORRELATION_ID = 42;\n    private static final int TRANSPORT_INDEX = 3;\n    private static final int SESSION_ID = 888;\n    private static final int STREAM_ID = 101010;\n    private static final int INITIAL_TERM_ID = -444666;\n    private static final int ACTIVE_TERM_ID = INITIAL_TERM_ID + 111;\n    private static final int TERM_OFFSET = TERM_LENGTH - TERM_LENGTH / 4;\n    private static final short FLAGS = FrameDescriptor.UNFRAGMENTED;\n    private static final String SOURCE_IDENTITY = \"aeron:udp?endpoint=localhost:5555\";\n    private final MediaDriver.Context ctx = new MediaDriver.Context();\n    private final ReceiveChannelEndpoint receiveChannelEndpoint = mock(ReceiveChannelEndpoint.class);\n    private final InetSocketAddress controlAddress = mock(InetSocketAddress.class);\n    private final RawLog rawLog = mock(RawLog.class);\n    private final FeedbackDelayGenerator feedbackDelayGenerator = mock(FeedbackDelayGenerator.class);\n    private final CongestionControl congestionControl = mock(CongestionControl.class);\n    private final CachedEpochClock epochClock = new CachedEpochClock();\n    private final CachedNanoClock nanoClock = new CachedNanoClock();\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[1024]);\n    private final CountersManager countersManager = new CountersManager(\n        new UnsafeBuffer(ByteBuffer.allocateDirect(256 * 1024)),\n        new UnsafeBuffer(ByteBuffer.allocateDirect(64 * 1024)),\n        StandardCharsets.US_ASCII);\n    private final DataHeaderFlyweight headerFlyweight = new DataHeaderFlyweight();\n    private final LossReport lossReport = mock(LossReport.class);\n    private Position hwmPosition;\n    private Position rcvPosition;\n    private AtomicCounter rcvNaksSent;\n    private PublicationImage image;\n\n    @BeforeEach\n    void before()\n    {\n        epochClock.update(TimeUnit.HOURS.toMillis(1));\n        nanoClock.update(TimeUnit.HOURS.toNanos(1));\n        ctx\n            .receiverCachedNanoClock(nanoClock)\n            .nanoClock(nanoClock)\n            .epochClock(epochClock)\n            .imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(10))\n            .untetheredWindowLimitTimeoutNs(TimeUnit.SECONDS.toNanos(1))\n            .untetheredLingerTimeoutNs(TimeUnit.MILLISECONDS.toNanos(150))\n            .untetheredRestingTimeoutNs(TimeUnit.MILLISECONDS.toNanos(800))\n            .statusMessageTimeoutNs(TimeUnit.MILLISECONDS.toNanos(150))\n            .systemCounters(new SystemCounters(countersManager))\n            .lossReport(lossReport);\n\n        final String channel = \"aeron:udp?endpoint=localhost:5555\";\n        final ChannelUri channelUri = ChannelUri.parse(channel);\n        final UdpChannel udpChannel = mock(UdpChannel.class);\n        when(udpChannel.channelUri()).thenReturn(channelUri);\n        when(receiveChannelEndpoint.subscriptionUdpChannel()).thenReturn(udpChannel);\n        when(receiveChannelEndpoint.originalUriString()).thenReturn(channel);\n\n        final SubscriptionLink subscriptionLink1 = mock(SubscriptionLink.class);\n        when(subscriptionLink1.isReliable()).thenReturn(true);\n        when(subscriptionLink1.isTether()).thenReturn(true);\n        final SubscriberPosition subscriberPosition1 = mock(SubscriberPosition.class);\n        when(subscriberPosition1.subscription()).thenReturn(subscriptionLink1);\n        final SubscriptionLink subscriptionLink2 = mock(SubscriptionLink.class);\n        when(subscriptionLink1.isReliable()).thenReturn(false);\n        when(subscriptionLink1.isTether()).thenReturn(false);\n        final SubscriberPosition subscriberPosition2 = mock(SubscriberPosition.class);\n        when(subscriberPosition2.subscription()).thenReturn(subscriptionLink2);\n        final ArrayList<SubscriberPosition> subscriberPositions = new ArrayList<>();\n        subscriberPositions.add(subscriberPosition1);\n        subscriberPositions.add(subscriberPosition2);\n\n        final UnsafeBuffer[] termBuffers = new UnsafeBuffer[PARTITION_COUNT];\n        for (int i = 0; i < termBuffers.length; i++)\n        {\n            termBuffers[i] = new UnsafeBuffer(new byte[TERM_LENGTH]);\n        }\n        when(rawLog.termBuffers()).thenReturn(termBuffers);\n        when(rawLog.metaData()).thenReturn(new UnsafeBuffer(new byte[LOG_META_DATA_LENGTH]));\n        when(rawLog.termLength()).thenReturn(TERM_LENGTH);\n\n        when(congestionControl.initialWindowLength()).thenReturn(INITIAL_WINDOW_LENGTH);\n        when(congestionControl.maxWindowLength()).thenReturn(MAX_WINDOW_LENGHT);\n\n        final long clientId = 117;\n        final long registrationId = 73249234983274L;\n        final ExpandableArrayBuffer tempBuffer = new ExpandableArrayBuffer();\n        hwmPosition = ReceiverHwm.allocate(\n            tempBuffer, countersManager, clientId, registrationId, SESSION_ID, STREAM_ID, channel);\n        rcvPosition = ReceiverPos.allocate(\n            tempBuffer, countersManager, clientId, registrationId, SESSION_ID, STREAM_ID, channel);\n        rcvNaksSent = ReceiverNaksSent.allocate(\n            tempBuffer, countersManager, clientId, registrationId, SESSION_ID, STREAM_ID, channel);\n\n        assertEquals(clientId, countersManager.getCounterOwnerId(hwmPosition.id()));\n        assertEquals(clientId, countersManager.getCounterOwnerId(rcvPosition.id()));\n        assertEquals(clientId, countersManager.getCounterOwnerId(rcvNaksSent.id()));\n\n        image = new PublicationImage(\n            CORRELATION_ID,\n            ctx,\n            receiveChannelEndpoint,\n            TRANSPORT_INDEX,\n            controlAddress,\n            SESSION_ID,\n            STREAM_ID,\n            INITIAL_TERM_ID,\n            ACTIVE_TERM_ID,\n            TERM_OFFSET,\n            FLAGS,\n            subscriptionLink1.isReliable(),\n            ctx.untetheredWindowLimitTimeoutNs(),\n            ctx.untetheredLingerTimeoutNs(),\n            ctx.untetheredRestingTimeoutNs(),\n            rawLog,\n            feedbackDelayGenerator,\n            subscriberPositions,\n            hwmPosition,\n            rcvPosition,\n            rcvNaksSent,\n            SOURCE_IDENTITY,\n            congestionControl);\n\n        final long position = computePosition(\n            ACTIVE_TERM_ID, TERM_OFFSET, positionBitsToShift(TERM_LENGTH), INITIAL_TERM_ID);\n        assertEquals(position, hwmPosition.get());\n        assertEquals(position, rcvPosition.get());\n\n        ThreadLocalRandom.current().nextBytes(buffer.byteArray());\n    }\n\n    @Test\n    void shouldAdvanceHighWaterMarkByPacketLengthWhenItContainsPaddingFrame()\n    {\n        final int totalLength = 512;\n        final int packetLength = 288;\n        final int termId = ACTIVE_TERM_ID;\n        final int termOffset = TERM_LENGTH - totalLength;\n        int offset = 0;\n        offset += writeFrame(offset, termOffset, termId, 65, BEGIN_AND_END_FLAGS, HDR_TYPE_DATA, 65);\n        offset += writeFrame(offset, termOffset + offset, termId, 96, BEGIN_AND_END_FLAGS, HDR_TYPE_DATA, 96);\n        offset += writeFrame(offset, termOffset + offset, termId, 224, BEGIN_AND_END_FLAGS, HDR_TYPE_PAD, 0x888AA888);\n        assertEquals(totalLength, offset);\n        final InetSocketAddress srcAddress = mock(InetSocketAddress.class);\n\n        final int bytes = image.insertPacket(termId, termOffset, buffer, packetLength, TRANSPORT_INDEX, srcAddress);\n\n        assertEquals(packetLength, bytes);\n        final int positionBitsToShift = positionBitsToShift(TERM_LENGTH);\n        final long packetPosition = computePosition(termId, termOffset, positionBitsToShift, INITIAL_TERM_ID);\n        assertEquals(packetPosition + packetLength, hwmPosition.get());\n        final UnsafeBuffer activeTermBuffer =\n            rawLog.termBuffers()[indexByPosition(packetPosition, positionBitsToShift)];\n        for (int i = 0; i < packetLength; i++)\n        {\n            assertEquals(buffer.getByte(i), activeTermBuffer.getByte(termOffset + i));\n        }\n        for (int i = packetLength; i < totalLength; i++)\n        {\n            assertEquals(0, activeTermBuffer.getByte(termOffset + i));\n        }\n    }\n\n    @Test\n    void shouldAdvanceHighWaterMarkPositionOnHeartbeat()\n    {\n        final int termId = ACTIVE_TERM_ID;\n        final int termOffset = TERM_OFFSET + 1024;\n        writeFrame(0, termOffset, termId, 0, BEGIN_AND_END_FLAGS, HDR_TYPE_DATA, -1);\n        FrameDescriptor.frameLengthOrdered(buffer, 0, 0);\n        final InetSocketAddress srcAddress = mock(InetSocketAddress.class);\n        final int packetLength = HEADER_LENGTH;\n        final AtomicCounter heartBeatsCounter = ctx.systemCounters().get(SystemCounterDescriptor.HEARTBEATS_RECEIVED);\n        final long oldHeartBeatCount = heartBeatsCounter.getPlain();\n\n        final int bytes = image.insertPacket(termId, termOffset, buffer, packetLength, TRANSPORT_INDEX, srcAddress);\n\n        assertEquals(packetLength, bytes);\n        final int positionBitsToShift = positionBitsToShift(TERM_LENGTH);\n        final long packetPosition = computePosition(termId, termOffset, positionBitsToShift, INITIAL_TERM_ID);\n        assertEquals(packetPosition, hwmPosition.get());\n        assertEquals(oldHeartBeatCount + 1, heartBeatsCounter.getPlain());\n        final UnsafeBuffer activeTermBuffer =\n            rawLog.termBuffers()[indexByPosition(packetPosition, positionBitsToShift)];\n        for (int i = 0; i < packetLength; i++)\n        {\n            assertEquals(0, activeTermBuffer.getByte(termOffset + i));\n        }\n    }\n\n    @Test\n    void shouldOnlyRecordUniqueLoss()\n    {\n        final LossReport.ReportEntry reportEntry = mock(LossReport.ReportEntry.class);\n        when(lossReport.createEntry(anyLong(), anyLong(), anyInt(), anyInt(), anyString(), anyString()))\n            .thenReturn(reportEntry);\n        final InOrder inOrder = inOrder(lossReport, reportEntry);\n\n        final int termId = 0;\n        final int offset = 0;\n        final int length = 1024;\n\n        // first activation => must be recorded\n        epochClock.update(100);\n        image.onGapDetected(termId, offset, length);\n        assertEquals(1L, image.beginLossChange);\n        assertEquals(termId, image.lossTermId);\n        assertEquals(offset, image.lossTermOffset);\n        assertEquals(length, image.lossLength);\n        assertEquals(1L, image.endLossChange);\n\n        // same loss => no reporting\n        epochClock.update(200);\n        image.onGapDetected(termId, offset, length);\n        assertEquals(2L, image.beginLossChange);\n        assertEquals(termId, image.lossTermId);\n        assertEquals(offset, image.lossTermOffset);\n        assertEquals(length, image.lossLength);\n        assertEquals(2L, image.endLossChange);\n\n        // smaller loss => no reporting\n        epochClock.update(300);\n        image.onGapDetected(termId, offset, 32);\n        assertEquals(3L, image.beginLossChange);\n        assertEquals(termId, image.lossTermId);\n        assertEquals(offset, image.lossTermOffset);\n        assertEquals(32, image.lossLength);\n        assertEquals(3L, image.endLossChange);\n\n        // loss length increased => record\n        epochClock.update(400);\n        image.onGapDetected(termId, offset, length + 128);\n        assertEquals(4L, image.beginLossChange);\n        assertEquals(termId, image.lossTermId);\n        assertEquals(offset, image.lossTermOffset);\n        assertEquals(length + 128, image.lossLength);\n        assertEquals(4L, image.endLossChange);\n\n        // overlapping loss => record\n        epochClock.update(500);\n        image.onGapDetected(termId, offset + 512, 800);\n        assertEquals(5L, image.beginLossChange);\n        assertEquals(termId, image.lossTermId);\n        assertEquals(offset + 512, image.lossTermOffset);\n        assertEquals(800, image.lossLength);\n        assertEquals(5L, image.endLossChange);\n\n        // non-overlapping loss => record\n        epochClock.update(600);\n        image.onGapDetected(termId, offset + 512 + 800, 32);\n        assertEquals(6L, image.beginLossChange);\n        assertEquals(termId, image.lossTermId);\n        assertEquals(offset + 512 + 800, image.lossTermOffset);\n        assertEquals(32, image.lossLength);\n        assertEquals(6L, image.endLossChange);\n\n        // non-overlapping loss => record\n        epochClock.update(700);\n        image.onGapDetected(termId, offset + 4096, 2048);\n        assertEquals(7L, image.beginLossChange);\n        assertEquals(termId, image.lossTermId);\n        assertEquals(offset + 4096, image.lossTermOffset);\n        assertEquals(2048, image.lossLength);\n        assertEquals(7L, image.endLossChange);\n\n        // loss in different term => record\n        epochClock.update(800);\n        image.onGapDetected(termId + 11, 0, 256);\n        assertEquals(8L, image.beginLossChange);\n        assertEquals(termId + 11, image.lossTermId);\n        assertEquals(0, image.lossTermOffset);\n        assertEquals(256, image.lossLength);\n        assertEquals(8L, image.endLossChange);\n\n        inOrder.verify(lossReport).createEntry(\n            length, 100, SESSION_ID, STREAM_ID, receiveChannelEndpoint.originalUriString(), SOURCE_IDENTITY);\n        inOrder.verify(reportEntry).recordObservation(128, 400);\n        inOrder.verify(reportEntry).recordObservation(160, 500);\n        inOrder.verify(reportEntry).recordObservation(32, 600);\n        inOrder.verify(reportEntry).recordObservation(2048, 700);\n        inOrder.verify(reportEntry).recordObservation(256, 800);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    private int writeFrame(\n        final int offset,\n        final int termOffset,\n        final int termId,\n        final int length,\n        final short flags,\n        final int type,\n        final int reservedValue)\n    {\n        final int frameLength = length + HEADER_LENGTH;\n        headerFlyweight.wrap(buffer, offset, frameLength);\n        headerFlyweight\n            .frameLength(frameLength)\n            .version(CURRENT_VERSION)\n            .flags(flags)\n            .headerType(type);\n        headerFlyweight\n            .termOffset(termOffset)\n            .sessionId(SESSION_ID)\n            .streamId(STREAM_ID)\n            .termId(termId)\n            .reservedValue(reservedValue);\n\n        return BitUtil.align(frameLength, FrameDescriptor.FRAME_ALIGNMENT);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/PublicationParamsTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.driver.exceptions.InvalidChannelException;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.CommonContext.SESSION_ID_PARAM_NAME;\nimport static io.aeron.CommonContext.STREAM_ID_PARAM_NAME;\nimport static java.util.concurrent.TimeUnit.MICROSECONDS;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass PublicationParamsTest\n{\n    private final MediaDriver.Context ctx = new MediaDriver.Context();\n    private final DriverConductor conductor = mock(DriverConductor.class);\n\n    @Test\n    void basicParse()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:1010\");\n        final PublicationParams params = PublicationParams.getPublicationParams(uri, ctx, conductor, 42, \"UDP\");\n        assertEquals(ctx.maxResend(), params.maxResend);\n    }\n\n    @Test\n    void hasMaxRetransmits()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:1010|\" +\n            CommonContext.MAX_RESEND_PARAM_NAME + \"=100\");\n        final PublicationParams params = PublicationParams.getPublicationParams(uri, ctx, conductor, 8, \"UDP\");\n        assertEquals(100, params.maxResend);\n    }\n\n    @Test\n    void hasNegativeMaxRetransmits()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:1010|\" +\n            CommonContext.MAX_RESEND_PARAM_NAME + \"=-1234\");\n        final InvalidChannelException exception = assertThrows(\n            InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(uri, ctx, conductor, -19, \"UDP\"));\n\n        assertTrue(exception.getMessage().contains(\"must be > 0\"));\n    }\n\n    @Test\n    void hasTooBigMaxRetransmits()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:1010|\" +\n            CommonContext.MAX_RESEND_PARAM_NAME + \"=\" + Configuration.MAX_RESEND_MAX * 2);\n        final InvalidChannelException exception = assertThrows(\n            InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(uri, ctx, conductor, 22, \"UDP\"));\n\n        assertTrue(exception.getMessage().contains(\"and <= \" + Configuration.MAX_RESEND_MAX));\n    }\n\n    @Test\n    void hasInvalidMaxRetransmits()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:1010|\" +\n            CommonContext.MAX_RESEND_PARAM_NAME + \"=notanumber\");\n        final InvalidChannelException exception = assertThrows(\n            InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(uri, ctx, conductor, 8, \"UDP\"));\n\n        assertTrue(exception.getMessage().contains(\"must be a number\"));\n    }\n\n    @Test\n    void shouldSetStreamId()\n    {\n        final int streamId = 42;\n        final ChannelUri uri = ChannelUri.parse(\"aeron:ipc?alias=x|stream-id=\" + streamId);\n        final PublicationParams params = PublicationParams.getPublicationParams(uri, ctx, conductor, 42, \"IPC\");\n\n        assertEquals(streamId, params.streamId);\n    }\n\n    @Test\n    void shouldRejectStreamIdThatIsNotANumber()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:8080|stream-id=abc\");\n        final InvalidChannelException exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(uri, ctx, conductor, 42, \"UDP\"));\n        assertEquals(\"ERROR - invalid \" + STREAM_ID_PARAM_NAME + \", must be a number\", exception.getMessage());\n    }\n\n    @Test\n    void shouldRejectStreamIdTIfItDoesNotMatch()\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:8080|stream-id=-19\");\n        final InvalidChannelException exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(uri, ctx, conductor, 2112, \"UDP\"));\n        assertEquals(\"ERROR - stream-id=-19 does not match provided streamId=2112\", exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, 1407 })\n    void shouldRejectPublicationWindowLessThanMtu(final int pubWindow)\n    {\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:8080|mtu=1408|pub-wnd=\" + pubWindow);\n        final InvalidChannelException exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(uri, ctx, conductor, 2112, \"UDP\"));\n        assertEquals(\"ERROR - pub-wnd=\" + pubWindow + \" cannot be less than the mtu=1408\", exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 100_000, 32 * 1024 + 1 })\n    void shouldRejectPublicationWindowLargerThanHalfATermLength(final int pubWindow)\n    {\n        final ChannelUri uri =\n            ChannelUri.parse(\"aeron:udp?endpoint=localhost:8080|term-length=64k|pub-wnd=\" + pubWindow);\n        final InvalidChannelException exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(uri, ctx, conductor, 2112, \"UDP\"));\n        assertEquals(\n            \"ERROR - pub-wnd=\" + pubWindow + \" must not exceed half the term-length=65536\",\n            exception.getMessage());\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 2048, 32 * 1024 })\n    void shouldSetPublicationWindowFromUriParameter(final int pubWindow)\n    {\n        final ChannelUri uri =\n            ChannelUri.parse(\"aeron:udp?endpoint=localhost:8080|term-length=64k|pub-wnd=\" + pubWindow);\n        final PublicationParams params = PublicationParams.getPublicationParams(uri, ctx, conductor, 2112, \"UDP\");\n\n        assertEquals(pubWindow, params.publicationWindowLength);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc?term-length=1m\", \"aeron:udp?endpoint=localhost:5555|term-length=128k\" })\n    void shouldSetPublicationWindowToHalfATermIfNotSet(final String uri)\n    {\n        ctx.ipcPublicationTermWindowLength(0).publicationTermWindowLength(0);\n\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(ChannelUri.parse(uri), ctx, conductor, 100, \"test\");\n\n        assertEquals(params.termLength / 2, params.publicationWindowLength);\n    }\n\n    @Test\n    void shouldSetPublicationWindowFromConfigIpc()\n    {\n        ctx.ipcPublicationTermWindowLength(128 * 1024).publicationTermWindowLength(0);\n\n        final ChannelUri uri = ChannelUri.parse(\"aeron:ipc?term-length=4m\");\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(uri, ctx, conductor, 100, \"IPC\");\n\n        assertEquals(ctx.ipcPublicationTermWindowLength(), params.publicationWindowLength);\n    }\n\n    @Test\n    void shouldSetPublicationWindowFromConfigUdp()\n    {\n        ctx.ipcPublicationTermWindowLength(0).publicationTermWindowLength(4096);\n\n        final ChannelUri uri = ChannelUri.parse(\"aeron:udp?term-length=16m|endpoint=localhost:7777\");\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(uri, ctx, conductor, 100, \"UDP\");\n\n        assertEquals(ctx.publicationTermWindowLength(), params.publicationWindowLength);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc?term-length=1m\", \"aeron:udp?endpoint=localhost:5555|term-length=128k\" })\n    void shouldGenerateSessionIdWhenNotSet(final String uri)\n    {\n        final int streamId = -98;\n        final int sessionId = 134238032;\n        when(conductor.nextAvailableSessionId(anyInt(), anyString())).thenReturn(sessionId);\n\n        final ChannelUri channelUri = ChannelUri.parse(uri);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, streamId, channelUri.media());\n\n        assertEquals(sessionId, params.sessionId);\n        verify(conductor).nextAvailableSessionId(streamId, channelUri.media());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc?mtu=1408\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldSetSessionIdFromTheUriParameter(final String uri)\n    {\n        final int streamId = ThreadLocalRandom.current().nextInt();\n        final int sessionId = ThreadLocalRandom.current().nextInt();\n\n        final ChannelUri channelUri = ChannelUri.parse(uri + \"|\" + SESSION_ID_PARAM_NAME + \"=\" + sessionId);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, streamId, channelUri.media());\n\n        assertEquals(sessionId, params.sessionId);\n        verifyNoInteractions(conductor);\n    }\n\n    @Test\n    void shouldRejectTaggedSessionIfTagIsInvalid()\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:5555|session-id=tag:abc\");\n        final InvalidChannelException exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(channelUri, ctx, conductor, 42, channelUri.media()));\n        assertEquals(\n            \"ERROR - session-id=tag:abc has an invalid tag: \" +\n            \"channel=aeron:udp?session-id=tag:abc|endpoint=localhost:5555\",\n            exception.getMessage());\n    }\n\n    @Test\n    void shouldRejectTaggedSessionIfUnknownPublicationReferenceUdp()\n    {\n        final long tag = ThreadLocalRandom.current().nextLong();\n\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:5555|session-id=tag:\" + tag);\n        final InvalidChannelException exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(channelUri, ctx, conductor, 42, channelUri.media()));\n        assertEquals(\n            \"ERROR - session-id=tag:\" + tag + \" must reference a network publication: channel=\" + channelUri,\n            exception.getMessage());\n\n        verify(conductor).findNetworkPublicationByTag(tag);\n    }\n\n    @Test\n    void shouldRejectTaggedSessionIfUnknownPublicationReferenceIpc()\n    {\n        final long tag = ThreadLocalRandom.current().nextLong();\n\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:ipc?session-id=tag:\" + tag);\n        final InvalidChannelException exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(channelUri, ctx, conductor, 42, channelUri.media()));\n        assertEquals(\n            \"ERROR - session-id=tag:\" + tag + \" must reference an IPC publication: channel=\" + channelUri,\n            exception.getMessage());\n\n        verify(conductor).findIpcPublicationByTag(tag);\n    }\n\n    @Test\n    void shouldInitializeSessionIdTermBufferLengthAndMtuFromReferencedUdpPublicationIfTagged()\n    {\n        final long tag = 42;\n        final int mtu = 1408;\n        final int termLength = 1024 * 1024;\n        final int sessionId = -4658437;\n        final NetworkPublication pub = mock(NetworkPublication.class);\n        when(pub.sessionId()).thenReturn(sessionId);\n        when(pub.mtuLength()).thenReturn(mtu);\n        when(pub.termBufferLength()).thenReturn(termLength);\n        when(conductor.findNetworkPublicationByTag(tag)).thenReturn(pub);\n\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:udp?endpoint=localhost:5555|session-id=tag:\" + tag);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 42, channelUri.media());\n\n        assertEquals(sessionId, params.sessionId);\n        assertEquals(mtu, params.mtuLength);\n        assertEquals(termLength, params.termLength);\n        verify(conductor).findNetworkPublicationByTag(tag);\n    }\n\n    @Test\n    void shouldInitializeSessionIdTermBufferLengthAndMtuFromReferencedIpcPublicationIfTagged()\n    {\n        final long tag = -5;\n        final int mtu = 2048;\n        final int termLength = 64 * 1024;\n        final int sessionId = 100;\n        final IpcPublication pub = mock(IpcPublication.class);\n        when(pub.sessionId()).thenReturn(sessionId);\n        when(pub.mtuLength()).thenReturn(mtu);\n        when(pub.termBufferLength()).thenReturn(termLength);\n        when(conductor.findIpcPublicationByTag(tag)).thenReturn(pub);\n\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:ipc?session-id=tag:\" + tag);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, -87, channelUri.media());\n\n        assertEquals(sessionId, params.sessionId);\n        assertEquals(mtu, params.mtuLength);\n        assertEquals(termLength, params.termLength);\n        verify(conductor).findIpcPublicationByTag(tag);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldReturnTrueForEosIfNotDefined(final String uri)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(uri);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertTrue(params.signalEos);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldSetEosFromUri(final boolean eos)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:ipc?eos=\" + eos);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(eos, params.signalEos);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldReturnConfigValueForSparse(final String uri)\n    {\n        ctx.termBufferSparseFile(ThreadLocalRandom.current().nextBoolean());\n\n        final ChannelUri channelUri = ChannelUri.parse(uri);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(ctx.termBufferSparseFile(), params.isSparse);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldSetSparseFromUri(final boolean sprase)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:ipc?sparse=\" + sprase);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(sprase, params.isSparse);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldReturnConfigValueForSpiesSimulateConnectionIfNotDefined(final String uri)\n    {\n        ctx.spiesSimulateConnection(ThreadLocalRandom.current().nextBoolean());\n\n        final ChannelUri channelUri = ChannelUri.parse(uri);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(ctx.spiesSimulateConnection(), params.spiesSimulateConnection);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldSetSpiesSimulateConnectionFromUri(final boolean sprase)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:ipc?ssc=\" + sprase);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(sprase, params.spiesSimulateConnection);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldReturnLingerTimeoutFromConfigIfNotSet(final String uri)\n    {\n        ctx.publicationLingerTimeoutNs(SECONDS.toNanos(7));\n\n        final ChannelUri channelUri = ChannelUri.parse(uri);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(ctx.publicationLingerTimeoutNs(), params.lingerTimeoutNs);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc?mtu=4k\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldSetLingerTimeout(final String uri)\n    {\n        final long lingerTimeoutMs = 321;\n\n        final ChannelUri channelUri = ChannelUri.parse(uri + \"|linger=\" + lingerTimeoutMs + \"ms\");\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(MILLISECONDS.toNanos(lingerTimeoutMs), params.lingerTimeoutNs);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldReturnUntetheredWindowLimitTimeoutFromConfigIfNotSet(final String uri)\n    {\n        ctx.untetheredWindowLimitTimeoutNs(SECONDS.toNanos(7));\n\n        final ChannelUri channelUri = ChannelUri.parse(uri);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(ctx.untetheredWindowLimitTimeoutNs(), params.untetheredWindowLimitTimeoutNs);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc?mtu=4k\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldSetUntetheredWindowLimitTimeout(final String uri)\n    {\n        final long timeoutUs = 456;\n\n        final ChannelUri channelUri = ChannelUri.parse(uri + \"|untethered-window-limit-timeout=\" + timeoutUs + \"us\");\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(MICROSECONDS.toNanos(timeoutUs), params.untetheredWindowLimitTimeoutNs);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldReturnUntetheredRestingTimeoutFromConfigIfNotSet(final String uri)\n    {\n        ctx.untetheredRestingTimeoutNs(SECONDS.toNanos(7));\n\n        final ChannelUri channelUri = ChannelUri.parse(uri);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(ctx.untetheredRestingTimeoutNs(), params.untetheredRestingTimeoutNs);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:ipc?mtu=4k\", \"aeron:udp?endpoint=localhost:5555\" })\n    void shouldSetUntetheredRestingTimeout(final String uri)\n    {\n        final long timeoutNs = 456;\n\n        final ChannelUri channelUri = ChannelUri.parse(uri + \"|untethered-resting-timeout=\" + timeoutNs);\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(timeoutNs, params.untetheredRestingTimeoutNs);\n    }\n\n    @Test\n    void shouldHonorPrecedenceForUntetheredLingerTimeoutWithDefaultsInContext()\n    {\n\n        // context\n        final ChannelUri channelUri = ChannelUri.parse(\n            \"aeron:udp?endpoint=localhost:5555|untethered-window-limit-timeout=444ms\");\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(444), params.untetheredWindowLimitTimeoutNs);\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(444), params.untetheredLingerTimeoutNs);\n\n    }\n\n    @Test\n    void shouldHonorPrecedenceForUntetheredLingerTimeoutWithOverriddenContext()\n    {\n        ctx.untetheredLingerTimeoutNs(TimeUnit.MILLISECONDS.toNanos(555));\n\n        // context\n        final ChannelUri channelUri = ChannelUri.parse(\n            \"aeron:udp?endpoint=localhost:5555|untethered-window-limit-timeout=444ms\");\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(444), params.untetheredWindowLimitTimeoutNs);\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(555), params.untetheredLingerTimeoutNs);\n    }\n\n    @Test\n    void shouldHonorChannelOverrideForUntetheredLingerTimeout()\n    {\n        ctx.untetheredLingerTimeoutNs(TimeUnit.MILLISECONDS.toNanos(555));\n\n        // context\n        final ChannelUri channelUri = ChannelUri.parse(\n            \"aeron:udp?endpoint=localhost:5555|untethered-window-limit-timeout=444ms|untethered-linger-timeout=333ms\");\n        final PublicationParams params =\n            PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media());\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(444), params.untetheredWindowLimitTimeoutNs);\n        assertEquals(TimeUnit.MILLISECONDS.toNanos(333), params.untetheredLingerTimeoutNs);\n    }\n\n    @Test\n    void shouldDiscardSessionIdIfNotInteger()\n    {\n        final ChannelUri channelUri = ChannelUri.parse(\"aeron:ipc?session-id=abc\");\n        final InvalidChannelException exception = assertThrowsExactly(InvalidChannelException.class,\n            () -> PublicationParams.getPublicationParams(channelUri, ctx, conductor, 1, channelUri.media()));\n        assertEquals(\"ERROR - invalid session-id, must be a number\", exception.getMessage());\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/ReceiverLivenessTrackerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass ReceiverLivenessTrackerTest\n{\n    @Test\n    void shouldNotBeLiveIfNoReceiversAdded()\n    {\n        final ReceiverLivenessTracker receiverLivenessTracker = new ReceiverLivenessTracker();\n        assertFalse(receiverLivenessTracker.hasReceivers());\n    }\n\n    @Test\n    void shouldLiveIfOneReceiverAdded()\n    {\n        final ReceiverLivenessTracker receiverLivenessTracker = new ReceiverLivenessTracker();\n\n        final long receiverId = 10001;\n        final long nowNs = 10000000000L;\n\n        receiverLivenessTracker.onStatusMessage(receiverId, nowNs);\n\n        assertTrue(receiverLivenessTracker.hasReceivers());\n    }\n\n    @Test\n    void shouldBeLiveIfReceiverNotTimedOut()\n    {\n        final long receiverId = 10001;\n        final long nowNs = 10000000000L;\n        final long timeoutNs = 5000000L;\n\n        final ReceiverLivenessTracker receiverLivenessTracker = new ReceiverLivenessTracker();\n        receiverLivenessTracker.onStatusMessage(receiverId, nowNs);\n        receiverLivenessTracker.onIdle(nowNs + (timeoutNs - 1), timeoutNs);\n        assertTrue(receiverLivenessTracker.hasReceivers());\n    }\n\n    @Test\n    void shouldNotBeLiveIfReceiverTimedOutExactly()\n    {\n        final long receiverId = 10001;\n        final long nowNs = 10000000000L;\n        final long timeoutNs = 5000000L;\n\n        final ReceiverLivenessTracker receiverLivenessTracker = new ReceiverLivenessTracker();\n        receiverLivenessTracker.onStatusMessage(receiverId, nowNs);\n        receiverLivenessTracker.onIdle(nowNs + timeoutNs, timeoutNs);\n        assertFalse(receiverLivenessTracker.hasReceivers());\n    }\n\n    @Test\n    void shouldNotBeLiveIfReceiverTimedOutAfter()\n    {\n        final long receiverId = 10001;\n        final long nowNs = 10000000000L;\n        final long timeoutNs = 5000000L;\n\n        final ReceiverLivenessTracker receiverLivenessTracker = new ReceiverLivenessTracker();\n        receiverLivenessTracker.onStatusMessage(receiverId, nowNs);\n        receiverLivenessTracker.onIdle(nowNs + (timeoutNs + 1), timeoutNs);\n        assertFalse(receiverLivenessTracker.hasReceivers());\n    }\n\n    @Test\n    void shouldNotBeLiveIfReceiverRemoved()\n    {\n        final long receiverId1 = 10001;\n        final long receiverId2 = 10002;\n        final long receiverId3 = 10003;\n        final long nowNs = 10000000000L;\n\n        final ReceiverLivenessTracker receiverLivenessTracker = new ReceiverLivenessTracker();\n        receiverLivenessTracker.onStatusMessage(receiverId1, nowNs);\n        receiverLivenessTracker.onStatusMessage(receiverId2, nowNs);\n        receiverLivenessTracker.onStatusMessage(receiverId3, nowNs);\n\n        receiverLivenessTracker.onRemoteClose(receiverId1);\n        assertTrue(receiverLivenessTracker.hasReceivers());\n\n        receiverLivenessTracker.onRemoteClose(receiverId2);\n        assertTrue(receiverLivenessTracker.hasReceivers());\n\n        receiverLivenessTracker.onRemoteClose(receiverId3);\n        assertFalse(receiverLivenessTracker.hasReceivers());\n    }\n\n    @Test\n    void shouldReturnFalseIfAlreadyRemoved()\n    {\n        final long receiverId1 = 10001;\n        final long receiverId2 = 10002;\n        final long receiverId3 = 10003;\n        final long nowNs = 10000000000L;\n\n        final ReceiverLivenessTracker receiverLivenessTracker = new ReceiverLivenessTracker();\n        receiverLivenessTracker.onStatusMessage(receiverId1, nowNs);\n        receiverLivenessTracker.onStatusMessage(receiverId2, nowNs);\n        receiverLivenessTracker.onStatusMessage(receiverId3, nowNs);\n\n        assertTrue(receiverLivenessTracker.onRemoteClose(receiverId1));\n        assertFalse(receiverLivenessTracker.onRemoteClose(receiverId1));\n        assertTrue(receiverLivenessTracker.hasReceivers());\n\n        assertTrue(receiverLivenessTracker.onRemoteClose(receiverId2));\n        assertFalse(receiverLivenessTracker.onRemoteClose(receiverId2));\n        assertTrue(receiverLivenessTracker.hasReceivers());\n\n        assertTrue(receiverLivenessTracker.onRemoteClose(receiverId3));\n        assertFalse(receiverLivenessTracker.onRemoteClose(receiverId3));\n        assertFalse(receiverLivenessTracker.hasReceivers());\n    }\n}"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/ReceiverTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.buffer.TestLogFactory;\nimport io.aeron.driver.media.ControlTransportPoller;\nimport io.aeron.driver.media.DataTransportPoller;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.ReceiveChannelEndpointThreadLocals;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.media.WildcardPortManager;\nimport io.aeron.driver.reports.LossReport;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.logbuffer.TermReader;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.ManyToOneConcurrentLinkedQueue;\nimport org.agrona.concurrent.OneToOneConcurrentArrayQueue;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.AtomicLongPosition;\nimport org.agrona.concurrent.status.Position;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.util.ArrayList;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.indexByTerm;\nimport static org.agrona.BitUtil.align;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.Is.is;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyBoolean;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ReceiverTest\n{\n    private static final int TERM_BUFFER_LENGTH = TERM_MIN_LENGTH;\n    private static final int POSITION_BITS_TO_SHIFT = LogBufferDescriptor.positionBitsToShift(TERM_BUFFER_LENGTH);\n    private static final String URI = \"aeron:udp?endpoint=localhost:4005\";\n    private static final UdpChannel UDP_CHANNEL = UdpChannel.parse(URI);\n    private static final long UNTETHERED_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(1);\n    private static final long CORRELATION_ID = 20;\n    private static final int STREAM_ID = 1010;\n    private static final int INITIAL_TERM_ID = 3;\n    private static final int ACTIVE_TERM_ID = 3;\n    private static final int SESSION_ID = 1;\n    private static final int INITIAL_TERM_OFFSET = 0;\n    private static final int ACTIVE_INDEX = indexByTerm(ACTIVE_TERM_ID, ACTIVE_TERM_ID);\n    private static final byte[] FAKE_PAYLOAD = \"Hello there, message!\".getBytes();\n    private static final int INITIAL_WINDOW_LENGTH = Configuration.INITIAL_WINDOW_LENGTH_DEFAULT;\n    private static final long STATUS_MESSAGE_TIMEOUT = Configuration.STATUS_MESSAGE_TIMEOUT_DEFAULT_NS;\n    private static final InetSocketAddress SOURCE_ADDRESS = new InetSocketAddress(\"localhost\", 45679);\n    private static final String SOURCE_IDENTITY = Configuration.sourceIdentity(SOURCE_ADDRESS);\n    private static final boolean IS_RELIABLE = true;\n\n    private static final Position POSITION = mock(Position.class);\n    private static final ArrayList<SubscriberPosition> POSITIONS = new ArrayList<>();\n\n    private final FeedbackDelayGenerator mockFeedbackDelayGenerator = mock(FeedbackDelayGenerator.class);\n    private final DataTransportPoller mockDataTransportPoller = mock(DataTransportPoller.class);\n    private final ControlTransportPoller mockControlTransportPoller = mock(ControlTransportPoller.class);\n\n    private final SystemCounters mockSystemCounters = mock(SystemCounters.class);\n    private final Position mockHighestReceivedPosition = spy(new AtomicLongPosition());\n    private final Position mockRebuildPosition = spy(new AtomicLongPosition());\n    private final Position mockSubscriberPosition = mock(Position.class);\n    private final AtomicCounter rcvNaksSent = mock(AtomicCounter.class);\n    private final UnsafeBuffer dataBuffer = new UnsafeBuffer(new byte[2 * 1024]);\n    private final UnsafeBuffer setupBuffer = new UnsafeBuffer(new byte[SetupFlyweight.HEADER_LENGTH]);\n\n    private final ErrorHandler mockErrorHandler = mock(ErrorHandler.class);\n\n    private final DataHeaderFlyweight dataHeader = new DataHeaderFlyweight();\n    private final StatusMessageFlyweight statusHeader = new StatusMessageFlyweight();\n    private final SetupFlyweight setupHeader = new SetupFlyweight();\n\n    private final CachedNanoClock nanoClock = new CachedNanoClock();\n    private final CachedEpochClock epochClock = new CachedEpochClock();\n    private final LossReport mockLossReport = mock(LossReport.class);\n\n    private final RawLog rawLog = TestLogFactory.newLogBuffers(TERM_BUFFER_LENGTH);\n\n    private final Header header = new Header(INITIAL_TERM_ID, TERM_BUFFER_LENGTH);\n    private UnsafeBuffer[] termBuffers;\n    private DatagramChannel senderChannel;\n\n    private final InetSocketAddress senderAddress = new InetSocketAddress(\"localhost\", 40123);\n    private Receiver receiver;\n    private ReceiverProxy receiverProxy;\n    private final ManyToOneConcurrentLinkedQueue<Runnable> toConductorQueue = new ManyToOneConcurrentLinkedQueue<>();\n    private final DriverConductorProxy driverConductorProxy =\n        new DriverConductorProxy(ThreadingMode.DEDICATED, toConductorQueue, mock(AtomicCounter.class));\n    private final CongestionControl congestionControl = mock(CongestionControl.class);\n    private final MediaDriver.Context ctx = new MediaDriver.Context()\n        .systemCounters(mockSystemCounters)\n        .errorHandler(mockErrorHandler)\n        .nanoClock(nanoClock)\n        .epochClock(epochClock)\n        .cachedNanoClock(nanoClock)\n        .senderCachedNanoClock(nanoClock)\n        .receiverCachedNanoClock(nanoClock)\n        .lossReport(mockLossReport)\n        .receiverDutyCycleTracker(new DutyCycleTracker());\n\n    private ReceiveChannelEndpoint receiveChannelEndpoint;\n\n    @BeforeEach\n    void setUp() throws Exception\n    {\n        final SubscriptionLink subscriptionLink = mock(SubscriptionLink.class);\n        when(subscriptionLink.isTether()).thenReturn(Boolean.TRUE);\n        when(subscriptionLink.isReliable()).thenReturn(Boolean.TRUE);\n        POSITIONS.add(new SubscriberPosition(subscriptionLink, null, POSITION));\n\n        when(POSITION.getVolatile())\n            .thenReturn(computePosition(ACTIVE_TERM_ID, 0, POSITION_BITS_TO_SHIFT, ACTIVE_TERM_ID));\n        when(mockSystemCounters.get(any())).thenReturn(mock(AtomicCounter.class));\n        when(congestionControl.onTrackRebuild(\n            anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyLong(), anyBoolean()))\n            .thenReturn(CongestionControl.packOutcome(INITIAL_WINDOW_LENGTH, false));\n        when(congestionControl.initialWindowLength()).thenReturn(INITIAL_WINDOW_LENGTH);\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .driverCommandQueue(toConductorQueue)\n            .dataTransportPoller(mockDataTransportPoller)\n            .controlTransportPoller(mockControlTransportPoller)\n            .logFactory(new TestLogFactory())\n            .systemCounters(mockSystemCounters)\n            .receiverCommandQueue(new OneToOneConcurrentArrayQueue<>(Configuration.CMD_QUEUE_CAPACITY))\n            .nanoClock(nanoClock)\n            .cachedNanoClock(nanoClock)\n            .senderCachedNanoClock(nanoClock)\n            .receiverCachedNanoClock(nanoClock)\n            .receiveChannelEndpointThreadLocals(new ReceiveChannelEndpointThreadLocals())\n            .driverConductorProxy(driverConductorProxy)\n            .receiverDutyCycleTracker(new DutyCycleTracker());\n\n        receiverProxy = new ReceiverProxy(\n            ThreadingMode.DEDICATED, ctx.receiverCommandQueue(), mock(AtomicCounter.class));\n\n        receiver = new Receiver(ctx);\n        receiverProxy.receiver(receiver);\n\n        senderChannel = DatagramChannel.open();\n        senderChannel.bind(senderAddress);\n        senderChannel.configureBlocking(false);\n\n        termBuffers = rawLog.termBuffers();\n\n        final MediaDriver.Context receiverChannelContext = new MediaDriver.Context()\n            .receiveChannelEndpointThreadLocals(new ReceiveChannelEndpointThreadLocals())\n            .systemCounters(mockSystemCounters)\n            .cachedNanoClock(nanoClock)\n            .senderCachedNanoClock(nanoClock)\n            .receiverCachedNanoClock(nanoClock)\n            .senderPortManager(new WildcardPortManager(WildcardPortManager.EMPTY_PORT_RANGE, true))\n            .receiverPortManager(new WildcardPortManager(WildcardPortManager.EMPTY_PORT_RANGE, false));\n\n        receiveChannelEndpoint = new ReceiveChannelEndpoint(\n            UdpChannel.parse(URI),\n            new DataPacketDispatcher(driverConductorProxy, receiver, ctx.streamSessionLimit()),\n            mock(AtomicCounter.class),\n            receiverChannelContext);\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(receiveChannelEndpoint, senderChannel, receiver::onClose);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldCreateRcvTermAndSendSmOnSetup() throws IOException\n    {\n        receiveChannelEndpoint.openChannel();\n        receiverProxy.registerReceiveChannelEndpoint(receiveChannelEndpoint);\n        receiverProxy.addSubscription(receiveChannelEndpoint, STREAM_ID);\n\n        receiver.doWork();\n        receiver.doWork();\n\n        fillSetupFrame(setupHeader);\n        receiveChannelEndpoint.onSetupMessage(\n            setupHeader, setupBuffer, SetupFlyweight.HEADER_LENGTH, senderAddress, 0);\n\n        final PublicationImage image = new PublicationImage(\n            CORRELATION_ID,\n            ctx,\n            receiveChannelEndpoint,\n            0,\n            senderAddress,\n            SESSION_ID,\n            STREAM_ID,\n            INITIAL_TERM_ID,\n            ACTIVE_TERM_ID,\n            INITIAL_TERM_OFFSET,\n            (short)0,\n            IS_RELIABLE,\n            UNTETHERED_TIMEOUT_NS,\n            UNTETHERED_TIMEOUT_NS,\n            UNTETHERED_TIMEOUT_NS,\n            rawLog,\n            mockFeedbackDelayGenerator,\n            POSITIONS,\n            mockHighestReceivedPosition,\n            mockRebuildPosition,\n            rcvNaksSent,\n            SOURCE_IDENTITY,\n            congestionControl);\n\n        final int messagesRead = drainConductorQueue(\n            (e) ->\n            {\n                // pass in new term buffer from conductor, which should trigger SM\n                receiverProxy.newPublicationImage(receiveChannelEndpoint, image);\n            });\n\n        assertThat(messagesRead, is(1));\n\n        nanoClock.advance(STATUS_MESSAGE_TIMEOUT * 2);\n        receiver.doWork();\n\n        final ByteBuffer rcvBuffer = ByteBuffer.allocateDirect(256);\n        InetSocketAddress rcvAddress;\n\n        do\n        {\n            rcvAddress = (InetSocketAddress)senderChannel.receive(rcvBuffer);\n        }\n        while (null == rcvAddress);\n\n        statusHeader.wrap(new UnsafeBuffer(rcvBuffer));\n\n        assertNotNull(rcvAddress);\n        assertThat(rcvAddress.getPort(), is(UDP_CHANNEL.remoteData().getPort()));\n        assertThat(statusHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_SM));\n        assertThat(statusHeader.streamId(), is(STREAM_ID));\n        assertThat(statusHeader.sessionId(), is(SESSION_ID));\n        assertThat(statusHeader.consumptionTermId(), is(ACTIVE_TERM_ID));\n        assertThat(statusHeader.frameLength(), is(StatusMessageFlyweight.HEADER_LENGTH));\n    }\n\n    @Test\n    void shouldInsertDataIntoLogAfterInitialExchange()\n    {\n        receiveChannelEndpoint.openChannel();\n        receiverProxy.registerReceiveChannelEndpoint(receiveChannelEndpoint);\n        receiverProxy.addSubscription(receiveChannelEndpoint, STREAM_ID);\n\n        receiver.doWork();\n        receiver.doWork();\n\n        fillSetupFrame(setupHeader);\n        receiveChannelEndpoint.onSetupMessage(setupHeader, setupBuffer, SetupFlyweight.HEADER_LENGTH, senderAddress, 0);\n\n        final int commandsRead = drainConductorQueue(\n            (e) ->\n            {\n                final PublicationImage image = new PublicationImage(\n                    CORRELATION_ID,\n                    ctx,\n                    receiveChannelEndpoint,\n                    0,\n                    senderAddress,\n                    SESSION_ID,\n                    STREAM_ID,\n                    INITIAL_TERM_ID,\n                    ACTIVE_TERM_ID,\n                    INITIAL_TERM_OFFSET,\n                    (short)0,\n                    IS_RELIABLE,\n                    UNTETHERED_TIMEOUT_NS,\n                    UNTETHERED_TIMEOUT_NS,\n                    UNTETHERED_TIMEOUT_NS,\n                    rawLog,\n                    mockFeedbackDelayGenerator,\n                    POSITIONS,\n                    mockHighestReceivedPosition,\n                    mockRebuildPosition,\n                    rcvNaksSent,\n                    SOURCE_IDENTITY,\n                    congestionControl);\n\n                receiverProxy.newPublicationImage(receiveChannelEndpoint, image);\n            });\n\n        assertThat(commandsRead, is(1));\n\n        receiver.doWork();\n\n        fillDataFrame(dataHeader, 0);\n        receiveChannelEndpoint.onDataPacket(dataHeader, dataBuffer, dataHeader.frameLength(), senderAddress, 0);\n\n        final int readOutcome = TermReader.read(\n            termBuffers[ACTIVE_INDEX],\n            INITIAL_TERM_OFFSET,\n            (buffer, offset, length, header) ->\n            {\n                assertThat(header.type(), is(HeaderFlyweight.HDR_TYPE_DATA));\n                assertThat(header.termId(), is(ACTIVE_TERM_ID));\n                assertThat(header.streamId(), is(STREAM_ID));\n                assertThat(header.sessionId(), is(SESSION_ID));\n                assertThat(header.termOffset(), is(0));\n                assertThat(header.frameLength(), is(DataHeaderFlyweight.HEADER_LENGTH + FAKE_PAYLOAD.length));\n            },\n            Integer.MAX_VALUE,\n            header,\n            mockErrorHandler,\n            0,\n            mockSubscriberPosition);\n\n        assertThat(readOutcome, is(1));\n    }\n\n    @Test\n    void shouldNotOverwriteDataFrameWithHeartbeat()\n    {\n        receiveChannelEndpoint.openChannel();\n        receiverProxy.registerReceiveChannelEndpoint(receiveChannelEndpoint);\n        receiverProxy.addSubscription(receiveChannelEndpoint, STREAM_ID);\n\n        receiver.doWork();\n        receiver.doWork();\n\n        fillSetupFrame(setupHeader);\n        receiveChannelEndpoint.onSetupMessage(setupHeader, setupBuffer, SetupFlyweight.HEADER_LENGTH, senderAddress, 0);\n\n        final int commandsRead = drainConductorQueue(\n            (e) ->\n            {\n                final PublicationImage image = new PublicationImage(\n                    CORRELATION_ID,\n                    ctx,\n                    receiveChannelEndpoint,\n                    0,\n                    senderAddress,\n                    SESSION_ID,\n                    STREAM_ID,\n                    INITIAL_TERM_ID,\n                    ACTIVE_TERM_ID,\n                    INITIAL_TERM_OFFSET,\n                    (short)0,\n                    IS_RELIABLE,\n                    UNTETHERED_TIMEOUT_NS,\n                    UNTETHERED_TIMEOUT_NS,\n                    UNTETHERED_TIMEOUT_NS,\n                    rawLog,\n                    mockFeedbackDelayGenerator,\n                    POSITIONS,\n                    mockHighestReceivedPosition,\n                    mockRebuildPosition,\n                    rcvNaksSent,\n                    SOURCE_IDENTITY,\n                    congestionControl);\n\n                receiverProxy.newPublicationImage(receiveChannelEndpoint, image);\n            });\n\n        assertThat(commandsRead, is(1));\n\n        receiver.doWork();\n\n        fillDataFrame(dataHeader, 0);  // initial data frame\n        receiveChannelEndpoint.onDataPacket(dataHeader, dataBuffer, dataHeader.frameLength(), senderAddress, 0);\n\n        fillDataFrame(dataHeader, 0);  // heartbeat with same term offset\n        receiveChannelEndpoint.onDataPacket(dataHeader, dataBuffer, dataHeader.frameLength(), senderAddress, 0);\n\n        final int readOutcome = TermReader.read(\n            termBuffers[ACTIVE_INDEX],\n            INITIAL_TERM_OFFSET,\n            (buffer, offset, length, header) ->\n            {\n                assertThat(header.type(), is(HeaderFlyweight.HDR_TYPE_DATA));\n                assertThat(header.termId(), is(ACTIVE_TERM_ID));\n                assertThat(header.streamId(), is(STREAM_ID));\n                assertThat(header.sessionId(), is(SESSION_ID));\n                assertThat(header.termOffset(), is(0));\n                assertThat(header.frameLength(), is(DataHeaderFlyweight.HEADER_LENGTH + FAKE_PAYLOAD.length));\n            },\n            Integer.MAX_VALUE,\n            header,\n            mockErrorHandler,\n            0,\n            mockSubscriberPosition);\n\n        assertThat(readOutcome, is(1));\n    }\n\n    @Test\n    void shouldOverwriteHeartbeatWithDataFrame()\n    {\n        receiveChannelEndpoint.openChannel();\n        receiverProxy.registerReceiveChannelEndpoint(receiveChannelEndpoint);\n        receiver.doWork();\n        receiverProxy.addSubscription(receiveChannelEndpoint, STREAM_ID);\n        receiver.doWork();\n\n        fillSetupFrame(setupHeader);\n        receiveChannelEndpoint.onSetupMessage(setupHeader, setupBuffer, SetupFlyweight.HEADER_LENGTH, senderAddress, 0);\n\n        final int commandsRead = drainConductorQueue(\n            (e) ->\n            {\n                final PublicationImage image = new PublicationImage(\n                    CORRELATION_ID,\n                    ctx,\n                    receiveChannelEndpoint,\n                    0,\n                    senderAddress,\n                    SESSION_ID,\n                    STREAM_ID,\n                    INITIAL_TERM_ID,\n                    ACTIVE_TERM_ID,\n                    INITIAL_TERM_OFFSET,\n                    (short)0,\n                    IS_RELIABLE,\n                    UNTETHERED_TIMEOUT_NS,\n                    UNTETHERED_TIMEOUT_NS,\n                    UNTETHERED_TIMEOUT_NS,\n                    rawLog,\n                    mockFeedbackDelayGenerator,\n                    POSITIONS,\n                    mockHighestReceivedPosition,\n                    mockRebuildPosition,\n                    rcvNaksSent,\n                    SOURCE_IDENTITY,\n                    congestionControl);\n\n                receiverProxy.newPublicationImage(receiveChannelEndpoint, image);\n            });\n\n        assertThat(commandsRead, is(1));\n\n        receiver.doWork();\n\n        fillDataFrame(dataHeader, 0);  // heartbeat with same term offset\n        receiveChannelEndpoint.onDataPacket(dataHeader, dataBuffer, dataHeader.frameLength(), senderAddress, 0);\n\n        fillDataFrame(dataHeader, 0);  // initial data frame\n        receiveChannelEndpoint.onDataPacket(dataHeader, dataBuffer, dataHeader.frameLength(), senderAddress, 0);\n\n        final int readOutcome = TermReader.read(\n            termBuffers[ACTIVE_INDEX],\n            INITIAL_TERM_OFFSET,\n            (buffer, offset, length, header) ->\n            {\n                assertThat(header.type(), is(HeaderFlyweight.HDR_TYPE_DATA));\n                assertThat(header.termId(), is(ACTIVE_TERM_ID));\n                assertThat(header.streamId(), is(STREAM_ID));\n                assertThat(header.sessionId(), is(SESSION_ID));\n                assertThat(header.termOffset(), is(0));\n                assertThat(header.frameLength(), is(DataHeaderFlyweight.HEADER_LENGTH + FAKE_PAYLOAD.length));\n            },\n            Integer.MAX_VALUE,\n            header,\n            mockErrorHandler,\n            0,\n            mockSubscriberPosition);\n\n        assertThat(readOutcome, is(1));\n    }\n\n    @Test\n    void shouldHandleNonZeroTermOffsetCorrectly()\n    {\n        final int initialTermOffset = align(TERM_BUFFER_LENGTH / 16, FrameDescriptor.FRAME_ALIGNMENT);\n        final int alignedDataFrameLength =\n            align(DataHeaderFlyweight.HEADER_LENGTH + FAKE_PAYLOAD.length, FrameDescriptor.FRAME_ALIGNMENT);\n\n        receiveChannelEndpoint.openChannel();\n        receiverProxy.registerReceiveChannelEndpoint(receiveChannelEndpoint);\n        receiverProxy.addSubscription(receiveChannelEndpoint, STREAM_ID);\n\n        receiver.doWork();\n        receiver.doWork();\n\n        fillSetupFrame(setupHeader, initialTermOffset);\n        receiveChannelEndpoint.onSetupMessage(setupHeader, setupBuffer, SetupFlyweight.HEADER_LENGTH, senderAddress, 0);\n\n        final int commandsRead = drainConductorQueue(\n            (e) ->\n            {\n                final PublicationImage image = new PublicationImage(\n                    CORRELATION_ID,\n                    ctx,\n                    receiveChannelEndpoint,\n                    0,\n                    senderAddress,\n                    SESSION_ID,\n                    STREAM_ID,\n                    INITIAL_TERM_ID,\n                    ACTIVE_TERM_ID,\n                    initialTermOffset,\n                    (short)0,\n                    IS_RELIABLE,\n                    UNTETHERED_TIMEOUT_NS,\n                    UNTETHERED_TIMEOUT_NS,\n                    UNTETHERED_TIMEOUT_NS,\n                    rawLog,\n                    mockFeedbackDelayGenerator,\n                    POSITIONS,\n                    mockHighestReceivedPosition,\n                    mockRebuildPosition,\n                    rcvNaksSent,\n                    SOURCE_IDENTITY,\n                    congestionControl);\n\n                receiverProxy.newPublicationImage(receiveChannelEndpoint, image);\n            });\n\n        assertThat(commandsRead, is(1));\n\n        verify(mockHighestReceivedPosition).setRelease(initialTermOffset);\n\n        receiver.doWork();\n\n        fillDataFrame(dataHeader, initialTermOffset);  // initial data frame\n        receiveChannelEndpoint.onDataPacket(dataHeader, dataBuffer, alignedDataFrameLength, senderAddress, 0);\n\n        verify(mockHighestReceivedPosition).proposeMaxRelease(initialTermOffset + alignedDataFrameLength);\n\n        final int readOutcome = TermReader.read(\n            termBuffers[ACTIVE_INDEX],\n            initialTermOffset,\n            (buffer, offset, length, header) ->\n            {\n                assertThat(header.type(), is(HeaderFlyweight.HDR_TYPE_DATA));\n                assertThat(header.termId(), is(ACTIVE_TERM_ID));\n                assertThat(header.streamId(), is(STREAM_ID));\n                assertThat(header.sessionId(), is(SESSION_ID));\n                assertThat(header.termOffset(), is(initialTermOffset));\n                assertThat(header.frameLength(), is(DataHeaderFlyweight.HEADER_LENGTH + FAKE_PAYLOAD.length));\n            },\n            Integer.MAX_VALUE,\n            header,\n            mockErrorHandler,\n            0,\n            mockSubscriberPosition);\n\n        assertThat(readOutcome, is(1));\n    }\n\n    @Test\n    void shouldRemoveImageFromDispatcherWithNoActivity()\n    {\n        receiveChannelEndpoint.openChannel();\n        receiverProxy.registerReceiveChannelEndpoint(receiveChannelEndpoint);\n        receiverProxy.addSubscription(receiveChannelEndpoint, STREAM_ID);\n\n        receiver.doWork();\n        receiver.doWork();\n\n        fillSetupFrame(setupHeader);\n        receiveChannelEndpoint.onSetupMessage(setupHeader, setupBuffer, SetupFlyweight.HEADER_LENGTH, senderAddress, 0);\n\n        final PublicationImage mockImage = mock(PublicationImage.class);\n        when(mockImage.sessionId()).thenReturn(SESSION_ID);\n        when(mockImage.streamId()).thenReturn(STREAM_ID);\n        when(mockImage.isConnected(anyLong())).thenReturn(false);\n\n        receiver.onNewPublicationImage(receiveChannelEndpoint, mockImage);\n        receiver.doWork();\n\n        verify(mockImage).removeFromDispatcher();\n    }\n\n    @Test\n    void shouldNotRemoveImageFromDispatcherOnRemoveSubscription()\n    {\n        receiveChannelEndpoint.openChannel();\n        receiverProxy.registerReceiveChannelEndpoint(receiveChannelEndpoint);\n        receiverProxy.addSubscription(receiveChannelEndpoint, STREAM_ID);\n\n        receiver.doWork();\n        receiver.doWork();\n\n        fillSetupFrame(setupHeader);\n        receiveChannelEndpoint.onSetupMessage(setupHeader, setupBuffer, SetupFlyweight.HEADER_LENGTH, senderAddress, 0);\n\n        final PublicationImage mockImage = mock(PublicationImage.class);\n        when(mockImage.sessionId()).thenReturn(SESSION_ID);\n        when(mockImage.streamId()).thenReturn(STREAM_ID);\n        when(mockImage.isConnected(anyLong())).thenReturn(true);\n\n        receiver.onNewPublicationImage(receiveChannelEndpoint, mockImage);\n        receiver.onRemoveSubscription(receiveChannelEndpoint, STREAM_ID);\n        receiver.doWork();\n\n        verify(mockImage).deactivate();\n        verify(mockImage, never()).removeFromDispatcher();\n    }\n\n    private void fillDataFrame(final DataHeaderFlyweight header, final int termOffset)\n    {\n        header.wrap(dataBuffer);\n        header\n            .termOffset(termOffset)\n            .termId(ACTIVE_TERM_ID)\n            .streamId(STREAM_ID)\n            .sessionId(SESSION_ID)\n            .frameLength(DataHeaderFlyweight.HEADER_LENGTH + ReceiverTest.FAKE_PAYLOAD.length)\n            .headerType(HeaderFlyweight.HDR_TYPE_DATA)\n            .flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS)\n            .version(HeaderFlyweight.CURRENT_VERSION);\n\n        if (0 < ReceiverTest.FAKE_PAYLOAD.length)\n        {\n            dataBuffer.putBytes(header.dataOffset(), ReceiverTest.FAKE_PAYLOAD);\n        }\n    }\n\n    private void fillSetupFrame(final SetupFlyweight header)\n    {\n        fillSetupFrame(header, 0);\n    }\n\n    private void fillSetupFrame(final SetupFlyweight header, final int termOffset)\n    {\n        header.wrap(setupBuffer);\n        header\n            .streamId(STREAM_ID)\n            .sessionId(SESSION_ID)\n            .initialTermId(INITIAL_TERM_ID)\n            .activeTermId(ACTIVE_TERM_ID)\n            .termOffset(termOffset)\n            .frameLength(SetupFlyweight.HEADER_LENGTH)\n            .headerType(HeaderFlyweight.HDR_TYPE_SETUP)\n            .flags((byte)0)\n            .version(HeaderFlyweight.CURRENT_VERSION);\n    }\n\n    private int drainConductorQueue(final Consumer<Runnable> consumer)\n    {\n        int workCount = 0;\n        for (;;)\n        {\n            final Runnable command = toConductorQueue.poll();\n            if (null != command)\n            {\n                consumer.accept(command);\n                workCount++;\n            }\n            else\n            {\n                break;\n            }\n        }\n        return workCount;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/RetransmitHandlerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.HeaderWriter;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.logbuffer.TermRebuilder;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.InOrder;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.BiConsumer;\nimport java.util.stream.IntStream;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.frameLengthOrdered;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static java.util.Arrays.asList;\nimport static org.agrona.BitUtil.align;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nclass RetransmitHandlerTest\n{\n    private static final int MTU_LENGTH = 1024;\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final byte[] DATA = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };\n    private static final int MESSAGE_LENGTH = DataHeaderFlyweight.HEADER_LENGTH + DATA.length;\n    private static final int ALIGNED_FRAME_LENGTH = align(MESSAGE_LENGTH, FrameDescriptor.FRAME_ALIGNMENT);\n    private static final int TWO_MESSAGE_FRAME_LENGTH = 2 * align(MESSAGE_LENGTH, FrameDescriptor.FRAME_ALIGNMENT);\n    private static final int SESSION_ID = 0x5E55101D;\n    private static final int STREAM_ID = 0x5400E;\n    private static final int TERM_ID = 0x7F003355;\n\n    private static final FeedbackDelayGenerator DELAY_GENERATOR =\n        new StaticDelayGenerator(TimeUnit.MILLISECONDS.toNanos(20));\n    private static final FeedbackDelayGenerator ZERO_DELAY_GENERATOR =\n        new StaticDelayGenerator(TimeUnit.MILLISECONDS.toNanos(0));\n    private static final FeedbackDelayGenerator LINGER_GENERATOR =\n        new StaticDelayGenerator(TimeUnit.MILLISECONDS.toNanos(40));\n\n    private final UnsafeBuffer termBuffer = new UnsafeBuffer(new byte[TERM_BUFFER_LENGTH]);\n    private final UnsafeBuffer metaDataBuffer = new UnsafeBuffer(new byte[LogBufferDescriptor.LOG_META_DATA_LENGTH]);\n\n    private final UnsafeBuffer rcvBuffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n    private final DataHeaderFlyweight dataHeader = new DataHeaderFlyweight();\n\n    private long currentTime = 0;\n\n    private final RetransmitSender sender = mock(RetransmitSender.class);\n    private final FlowControl fc = mock(FlowControl.class);\n    private final AtomicCounter invalidPackets = mock(AtomicCounter.class);\n    private final AtomicCounter retransmitOverflow = mock(AtomicCounter.class);\n\n    private final HeaderWriter headerWriter = HeaderWriter.newInstance(\n        DataHeaderFlyweight.createDefaultHeader(0, 0, 0));\n\n    private RetransmitHandler handler = new RetransmitHandler(() ->\n        currentTime,\n        invalidPackets,\n        DELAY_GENERATOR,\n        LINGER_GENERATOR,\n        true,\n        16,\n        retransmitOverflow);\n\n    @BeforeEach\n    void before()\n    {\n        LogBufferDescriptor.rawTail(metaDataBuffer, 0, LogBufferDescriptor.packTail(TERM_ID, 0));\n        when(fc.maxRetransmissionLength(anyInt(), anyInt(), anyInt(), anyInt())).then(this::returnResendLength);\n    }\n\n    private static List<BiConsumer<RetransmitHandlerTest, Integer>> consumers()\n    {\n        return asList(\n            (retransmitHandlerTest, i) -> retransmitHandlerTest.addSentDataFrame(),\n            RetransmitHandlerTest::addReceivedDataFrame);\n    }\n\n    private Integer returnResendLength(final InvocationOnMock invocation)\n    {\n        return invocation.getArgument(1, Integer.class);\n    }\n\n    private Answer<?> clampResendLengthTo(final int length)\n    {\n        return (invocation) -> length;\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldRetransmitOnNak(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldClampRetransmitViaFlowControlOnNak(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        final int expectedResendLength = 32;\n        when(fc.maxRetransmissionLength(anyInt(), anyInt(), anyInt(), anyInt()))\n            .then(clampResendLengthTo(expectedResendLength));\n\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(0), expectedResendLength);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldNotRetransmitOnNakWhileInLinger(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(40);\n        handler.processTimeouts(currentTime, sender);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldNotRetransmitOnNakWhileInLingerWithDifferentOffsetByContainedWithinExistingRetransmit(\n        final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), TWO_MESSAGE_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(40);\n        handler.processTimeouts(currentTime, sender);\n        handler.onNak(TERM_ID, offsetOfFrame(1), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender, times(1)).resend(eq(TERM_ID), anyInt(), anyInt());\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldRetransmitOnNakAfterLinger(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(40);\n        handler.processTimeouts(currentTime, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(200);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender, times(2)).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldRetransmitOnMultipleNaks(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        handler.onNak(TERM_ID, offsetOfFrame(1), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        final InOrder inOrder = inOrder(sender);\n        inOrder.verify(sender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH);\n        inOrder.verify(sender).resend(TERM_ID, offsetOfFrame(1), ALIGNED_FRAME_LENGTH);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldRetransmitOnNakOverMessageLength(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 10);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH * 5, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH * 5);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldRetransmitOnNakOverMtuLength(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        final int numFramesPerMtu = MTU_LENGTH / ALIGNED_FRAME_LENGTH;\n        createTermBuffer(creator, numFramesPerMtu * 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), MTU_LENGTH * 2, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(0), MTU_LENGTH * 2);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldNotRetransmitOnNakWhileInLingerWithDifferentOffsetButOffsetInRangeOfExistingNak(\n        final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), TWO_MESSAGE_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(40);\n        handler.processTimeouts(currentTime, sender);\n        handler.onNak(TERM_ID, offsetOfFrame(1), TWO_MESSAGE_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(0), TWO_MESSAGE_FRAME_LENGTH);\n        verify(sender, never()).resend(TERM_ID, offsetOfFrame(1), TWO_MESSAGE_FRAME_LENGTH);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldRetransmitOnNakWhileInLingerWithDifferentOffsetButJustOutsideRangeOfExistingNak(\n        final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), TWO_MESSAGE_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(40);\n        handler.processTimeouts(currentTime, sender);\n        handler.onNak(TERM_ID, offsetOfFrame(2), TWO_MESSAGE_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(0), TWO_MESSAGE_FRAME_LENGTH);\n        verify(sender).resend(TERM_ID, offsetOfFrame(2), TWO_MESSAGE_FRAME_LENGTH);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldStopRetransmitOnRetransmitReception(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        handler.onRetransmitReceived(TERM_ID, offsetOfFrame(0));\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verifyNoInteractions(sender);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldStopOneRetransmitOnRetransmitReception(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        handler.onNak(TERM_ID, offsetOfFrame(1), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        handler.onRetransmitReceived(TERM_ID, offsetOfFrame(0));\n        currentTime = TimeUnit.MILLISECONDS.toNanos(100);\n        handler.processTimeouts(currentTime, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(1), ALIGNED_FRAME_LENGTH);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldImmediateRetransmitOnNak(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler = newZeroDelayRetransmitHandler();\n\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldGoIntoLingerOnImmediateRetransmit(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler = newZeroDelayRetransmitHandler();\n\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n        currentTime = TimeUnit.MILLISECONDS.toNanos(40);\n        handler.processTimeouts(currentTime, sender);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n\n        verify(sender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldOnlyRetransmitOnNakWhenConfiguredTo(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n\n        verifyNoInteractions(sender);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"consumers\")\n    void shouldIncrementOverflowCounter(final BiConsumer<RetransmitHandlerTest, Integer> creator)\n    {\n        createTermBuffer(creator, 5);\n        final int termLength = 128 * 1024;\n\n        for (int i = 0; i < 16; i++)\n        {\n            final int termOffset = offsetOfFrame(i * 64);\n            handler.onNak(TERM_ID, termOffset, ALIGNED_FRAME_LENGTH, termLength, MTU_LENGTH, fc, sender);\n        }\n\n        handler.onNak(\n            TERM_ID, offsetOfFrame(16 * 64), ALIGNED_FRAME_LENGTH, termLength, MTU_LENGTH, fc, sender);\n\n        verify(retransmitOverflow).increment();\n    }\n\n    @Test\n    void shouldDetectInvalidNaksInvalidOffset()\n    {\n        handler = newZeroDelayRetransmitHandler();\n\n        handler.onNak(\n            TERM_ID, TERM_BUFFER_LENGTH - 16, ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n\n        verify(invalidPackets).increment();\n        verifyNoInteractions(sender);\n    }\n\n    @Test\n    void shouldDetectInvalidNaksNegativeOffset()\n    {\n        handler = newZeroDelayRetransmitHandler();\n\n        handler.onNak(TERM_ID, -128, ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n\n        verify(invalidPackets).increment();\n        verifyNoInteractions(sender);\n    }\n\n    @Test\n    void shouldDetectInvalidNaksNegativeLength()\n    {\n        handler = newZeroDelayRetransmitHandler();\n\n        handler.onNak(TERM_ID, 0, -ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n\n        verify(invalidPackets).increment();\n        verifyNoInteractions(sender);\n    }\n\n    @Test\n    void shouldIgnoreEmptyNak()\n    {\n        handler = newZeroDelayRetransmitHandler();\n\n        handler.onNak(TERM_ID, 0, 0, TERM_BUFFER_LENGTH, MTU_LENGTH, fc, sender);\n\n        verifyNoInteractions(sender);\n    }\n\n    private RetransmitHandler newZeroDelayRetransmitHandler()\n    {\n        return new RetransmitHandler(() ->\n            currentTime,\n            invalidPackets,\n            ZERO_DELAY_GENERATOR,\n            LINGER_GENERATOR,\n            true,\n            16,\n            retransmitOverflow);\n    }\n\n    private void createTermBuffer(final BiConsumer<RetransmitHandlerTest, Integer> creator, final int num)\n    {\n        IntStream.range(0, num).forEach((i) -> creator.accept(this, i));\n    }\n\n    private static int offsetOfFrame(final int index)\n    {\n        return index * ALIGNED_FRAME_LENGTH;\n    }\n\n    private void addSentDataFrame()\n    {\n        rcvBuffer.putBytes(0, DATA);\n\n        final int frameLength = DATA.length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final long rawTail = LogBufferDescriptor.packTail(TERM_ID, alignedLength);\n\n        LogBufferDescriptor.rawTail(metaDataBuffer, 0, rawTail);\n\n        headerWriter.write(termBuffer, 0, frameLength, TERM_ID);\n        termBuffer.putBytes(HEADER_LENGTH, rcvBuffer, 0, DATA.length);\n\n        frameLengthOrdered(termBuffer, 0, frameLength);\n    }\n\n    private void addReceivedDataFrame(final int msgNum)\n    {\n        dataHeader.wrap(rcvBuffer);\n\n        dataHeader.termId(TERM_ID)\n            .streamId(STREAM_ID)\n            .sessionId(SESSION_ID)\n            .termOffset(offsetOfFrame(msgNum))\n            .frameLength(MESSAGE_LENGTH)\n            .headerType(HeaderFlyweight.HDR_TYPE_DATA)\n            .flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS)\n            .version(HeaderFlyweight.CURRENT_VERSION);\n\n        rcvBuffer.putBytes(dataHeader.dataOffset(), DATA);\n\n        TermRebuilder.insert(termBuffer, offsetOfFrame(msgNum), rcvBuffer, MESSAGE_LENGTH);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/SelectorAndTransportTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.ControlTransportPoller;\nimport io.aeron.driver.media.DataTransportPoller;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.ReceiveChannelEndpointThreadLocals;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.media.UdpTransportPoller;\nimport io.aeron.driver.media.WildcardPortManager;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport org.agrona.BitUtil;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.StandardProtocolFamily;\nimport java.net.StandardSocketOptions;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass SelectorAndTransportTest\n{\n    private static final int RCV_PORT = 40123;\n    private static final int SRC_PORT = 40124;\n    private static final int SESSION_ID = 0xdeadbeef;\n    private static final int STREAM_ID = 0x44332211;\n    private static final int TERM_ID = 0x99887766;\n    private static final int FRAME_LENGTH = 24;\n\n    private static final UdpChannel SRC_DST =\n        UdpChannel.parse(\"aeron:udp?interface=localhost:\" + SRC_PORT + \"|endpoint=localhost:\" + RCV_PORT);\n    private static final UdpChannel RCV_DST = UdpChannel.parse(\"aeron:udp?endpoint=localhost:\" + RCV_PORT);\n\n    private final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(256);\n    private final UnsafeBuffer buffer = new UnsafeBuffer(byteBuffer);\n\n    private final DataHeaderFlyweight encodeDataHeader = new DataHeaderFlyweight();\n    private final StatusMessageFlyweight statusMessage = new StatusMessageFlyweight();\n\n    private final InetSocketAddress rcvRemoteAddress = new InetSocketAddress(\"localhost\", SRC_PORT);\n\n    private final SystemCounters mockSystemCounters = mock(SystemCounters.class);\n    private final AtomicCounter mockStatusMessagesReceivedCounter = mock(AtomicCounter.class);\n    private final AtomicCounter mockSendStatusIndicator = mock(AtomicCounter.class);\n    private final AtomicCounter mockReceiveStatusIndicator = mock(AtomicCounter.class);\n\n    private final DataPacketDispatcher mockDispatcher = mock(DataPacketDispatcher.class);\n    private final NetworkPublication mockPublication = mock(NetworkPublication.class);\n    private final DriverConductorProxy mockDriverConductorProxy = mock(DriverConductorProxy.class);\n    private final ErrorHandler errorHandler = mock(ErrorHandler.class);\n\n    private final DataTransportPoller dataTransportPoller = new DataTransportPoller(errorHandler);\n    private final ControlTransportPoller controlTransportPoller = new ControlTransportPoller(\n        errorHandler, mockDriverConductorProxy);\n    private SendChannelEndpoint sendChannelEndpoint;\n    private ReceiveChannelEndpoint receiveChannelEndpoint;\n\n    private final CachedNanoClock nanoClock = new CachedNanoClock();\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .systemCounters(mockSystemCounters)\n        .cachedNanoClock(nanoClock)\n        .senderCachedNanoClock(nanoClock)\n        .receiverCachedNanoClock(nanoClock)\n        .receiveChannelEndpointThreadLocals(new ReceiveChannelEndpointThreadLocals())\n        .senderPortManager(new WildcardPortManager(WildcardPortManager.EMPTY_PORT_RANGE, true))\n        .receiverPortManager(new WildcardPortManager(WildcardPortManager.EMPTY_PORT_RANGE, false));\n\n    @BeforeEach\n    void setup()\n    {\n        when(mockSystemCounters.get(any())).thenReturn(mockStatusMessagesReceivedCounter);\n        when(mockPublication.streamId()).thenReturn(STREAM_ID);\n        when(mockPublication.sessionId()).thenReturn(SESSION_ID);\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        try\n        {\n            if (null != sendChannelEndpoint)\n            {\n                sendChannelEndpoint.close();\n                processLoop(controlTransportPoller, 5);\n            }\n\n            if (null != receiveChannelEndpoint)\n            {\n                receiveChannelEndpoint.close();\n                processLoop(dataTransportPoller, 5);\n            }\n\n            dataTransportPoller.close();\n            controlTransportPoller.close();\n        }\n        catch (final Exception ex)\n        {\n            throw ex;\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldHandleBasicSetupAndTearDown()\n    {\n        receiveChannelEndpoint = new ReceiveChannelEndpoint(\n            RCV_DST, mockDispatcher, mockReceiveStatusIndicator, context);\n        sendChannelEndpoint = new SendChannelEndpoint(SRC_DST, mockSendStatusIndicator, context);\n\n        receiveChannelEndpoint.openDatagramChannel(mockReceiveStatusIndicator);\n        dataTransportPoller.registerForRead(receiveChannelEndpoint, receiveChannelEndpoint, 0);\n        sendChannelEndpoint.openDatagramChannel(mockSendStatusIndicator);\n        controlTransportPoller.registerForRead(sendChannelEndpoint);\n\n        processLoop(dataTransportPoller, 5);\n    }\n\n    @Test\n    void shouldSetSocketBufferSizesFromUdpChannelForReceiveChannel() throws IOException\n    {\n        final DatagramChannel spyChannel = spy(DatagramChannel.open(StandardProtocolFamily.INET));\n        final UdpChannel channel = UdpChannel.parse(\n            \"aeron:udp?endpoint=localhost:\" + RCV_PORT + \"|so-sndbuf=8k|so-rcvbuf=4k\");\n        receiveChannelEndpoint = new ReceiveChannelEndpoint(\n            channel, mockDispatcher, mockReceiveStatusIndicator, context);\n\n        try (MockedStatic<DatagramChannel> mockDatagramChannel = Mockito.mockStatic(DatagramChannel.class))\n        {\n            mockDatagramChannel.when(() -> DatagramChannel.open(StandardProtocolFamily.INET)).thenReturn(spyChannel);\n            receiveChannelEndpoint.openDatagramChannel(mockReceiveStatusIndicator);\n\n            verify(spyChannel).setOption(StandardSocketOptions.SO_SNDBUF, 8192);\n            verify(spyChannel).setOption(StandardSocketOptions.SO_RCVBUF, 4096);\n        }\n    }\n\n    @Test\n    void shouldSetSocketBufferSizesFromUdpChannelForSendChannel() throws IOException\n    {\n        final DatagramChannel spyChannel = spy(DatagramChannel.open(StandardProtocolFamily.INET));\n        final UdpChannel channel = UdpChannel.parse(\n            \"aeron:udp?endpoint=localhost:\" + RCV_PORT + \"|so-sndbuf=8k|so-rcvbuf=4k\");\n        sendChannelEndpoint = new SendChannelEndpoint(channel, mockReceiveStatusIndicator, context);\n\n        try (MockedStatic<DatagramChannel> mockDatagramChannel = Mockito.mockStatic(DatagramChannel.class))\n        {\n            mockDatagramChannel.when(() -> DatagramChannel.open(StandardProtocolFamily.INET)).thenReturn(spyChannel);\n            sendChannelEndpoint.openDatagramChannel(mockReceiveStatusIndicator);\n\n            verify(spyChannel).setOption(StandardSocketOptions.SO_SNDBUF, 8192);\n            verify(spyChannel).setOption(StandardSocketOptions.SO_RCVBUF, 4096);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSendEmptyDataFrameUnicastFromSourceToReceiver()\n    {\n        final MutableInteger dataHeadersReceived = new MutableInteger(0);\n\n        doAnswer(\n            (invocation) ->\n            {\n                dataHeadersReceived.value++;\n                return null;\n            })\n            .when(mockDispatcher).onDataPacket(\n                any(ReceiveChannelEndpoint.class),\n                any(DataHeaderFlyweight.class),\n                any(UnsafeBuffer.class),\n                anyInt(),\n                any(InetSocketAddress.class),\n                anyInt());\n\n        receiveChannelEndpoint = new ReceiveChannelEndpoint(\n            RCV_DST, mockDispatcher, mockReceiveStatusIndicator, context);\n        sendChannelEndpoint = new SendChannelEndpoint(SRC_DST, mockSendStatusIndicator, context);\n\n        receiveChannelEndpoint.openDatagramChannel(mockReceiveStatusIndicator);\n        dataTransportPoller.registerForRead(receiveChannelEndpoint, receiveChannelEndpoint, 0);\n        sendChannelEndpoint.openDatagramChannel(mockSendStatusIndicator);\n        controlTransportPoller.registerForRead(sendChannelEndpoint);\n\n        encodeDataHeader.wrap(buffer);\n        encodeDataHeader\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS)\n            .headerType(HeaderFlyweight.HDR_TYPE_DATA)\n            .frameLength(FRAME_LENGTH);\n        encodeDataHeader\n            .sessionId(SESSION_ID)\n            .streamId(STREAM_ID)\n            .termId(TERM_ID);\n        byteBuffer.position(0).limit(FRAME_LENGTH);\n\n        processLoop(dataTransportPoller, 5);\n        sendChannelEndpoint.send(byteBuffer);\n        while (dataHeadersReceived.get() < 1)\n        {\n            processLoop(dataTransportPoller, 1);\n        }\n\n        assertEquals(1, dataHeadersReceived.get());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSendMultipleDataFramesPerDatagramUnicastFromSourceToReceiver()\n    {\n        final MutableInteger dataHeadersReceived = new MutableInteger(0);\n\n        doAnswer(\n            (invocation) ->\n            {\n                dataHeadersReceived.value++;\n                return null;\n            })\n            .when(mockDispatcher).onDataPacket(\n                any(ReceiveChannelEndpoint.class),\n                any(DataHeaderFlyweight.class),\n                any(UnsafeBuffer.class),\n                anyInt(),\n                any(InetSocketAddress.class),\n                anyInt());\n\n        receiveChannelEndpoint = new ReceiveChannelEndpoint(\n            RCV_DST, mockDispatcher, mockReceiveStatusIndicator, context);\n        sendChannelEndpoint = new SendChannelEndpoint(SRC_DST, mockSendStatusIndicator, context);\n\n        receiveChannelEndpoint.openDatagramChannel(mockReceiveStatusIndicator);\n        dataTransportPoller.registerForRead(receiveChannelEndpoint, receiveChannelEndpoint, 0);\n        sendChannelEndpoint.openDatagramChannel(mockSendStatusIndicator);\n        controlTransportPoller.registerForRead(sendChannelEndpoint);\n\n        encodeDataHeader.wrap(buffer);\n        encodeDataHeader\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS)\n            .headerType(HeaderFlyweight.HDR_TYPE_DATA)\n            .frameLength(FRAME_LENGTH);\n        encodeDataHeader\n            .sessionId(SESSION_ID)\n            .streamId(STREAM_ID)\n            .termId(TERM_ID);\n\n        final int alignedFrameLength = BitUtil.align(FRAME_LENGTH, FrameDescriptor.FRAME_ALIGNMENT);\n        encodeDataHeader.wrap(buffer, alignedFrameLength, buffer.capacity() - alignedFrameLength);\n        encodeDataHeader\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS)\n            .headerType(HeaderFlyweight.HDR_TYPE_DATA)\n            .frameLength(24);\n        encodeDataHeader\n            .sessionId(SESSION_ID)\n            .streamId(STREAM_ID)\n            .termId(TERM_ID);\n\n        byteBuffer.position(0).limit(2 * alignedFrameLength);\n\n        processLoop(dataTransportPoller, 5);\n        sendChannelEndpoint.send(byteBuffer);\n        while (dataHeadersReceived.get() < 1)\n        {\n            processLoop(dataTransportPoller, 1);\n        }\n\n        assertEquals(1, dataHeadersReceived.get());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldHandleSmFrameFromReceiverToSender()\n    {\n        final MutableInteger controlMessagesReceived = new MutableInteger(0);\n\n        doAnswer(\n            (invocation) ->\n            {\n                controlMessagesReceived.value++;\n                return null;\n            })\n            .when(mockPublication).onStatusMessage(any(), any(), any());\n\n        receiveChannelEndpoint = new ReceiveChannelEndpoint(\n            RCV_DST, mockDispatcher, mockReceiveStatusIndicator, context);\n        sendChannelEndpoint = new SendChannelEndpoint(SRC_DST, mockSendStatusIndicator, context);\n        sendChannelEndpoint.registerForSend(mockPublication);\n\n        receiveChannelEndpoint.openDatagramChannel(mockReceiveStatusIndicator);\n        dataTransportPoller.registerForRead(receiveChannelEndpoint, receiveChannelEndpoint, 0);\n        sendChannelEndpoint.openDatagramChannel(mockSendStatusIndicator);\n        controlTransportPoller.registerForRead(sendChannelEndpoint);\n\n        statusMessage.wrap(buffer);\n        statusMessage\n            .streamId(STREAM_ID)\n            .sessionId(SESSION_ID)\n            .consumptionTermId(TERM_ID)\n            .receiverWindowLength(1000)\n            .consumptionTermOffset(0)\n            .version(HeaderFlyweight.CURRENT_VERSION)\n            .flags((short)0)\n            .headerType(HeaderFlyweight.HDR_TYPE_SM)\n            .frameLength(StatusMessageFlyweight.HEADER_LENGTH);\n        byteBuffer.position(0).limit(statusMessage.frameLength());\n\n        processLoop(dataTransportPoller, 5);\n        receiveChannelEndpoint.sendTo(byteBuffer, rcvRemoteAddress);\n\n        while (controlMessagesReceived.get() < 1)\n        {\n            processLoop(controlTransportPoller, 1);\n        }\n\n        verify(mockStatusMessagesReceivedCounter, times(1)).incrementRelease();\n    }\n\n    private void processLoop(final UdpTransportPoller transportPoller, final int iterations)\n    {\n        for (int i = 0; i < iterations; i++)\n        {\n            transportPoller.pollTransports();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/SenderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.buffer.TestLogFactory;\nimport io.aeron.driver.media.ControlTransportPoller;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.logbuffer.HeaderWriter;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.protocol.HeaderFlyweight;\nimport io.aeron.protocol.SetupFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.OneToOneConcurrentArrayQueue;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.AtomicLongPosition;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.stubbing.Answer;\n\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayDeque;\nimport java.util.Queue;\n\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.frameLengthOrdered;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.agrona.BitUtil.align;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.core.Is.is;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.when;\n\nclass SenderTest\n{\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int MAX_FRAME_LENGTH = 1024;\n    private static final int SESSION_ID = 1;\n    private static final int STREAM_ID = 1002;\n    private static final int INITIAL_TERM_ID = 3;\n    private static final byte[] PAYLOAD = \"Payload is here!\".getBytes();\n\n    private static final UnsafeBuffer HEADER = DataHeaderFlyweight.createDefaultHeader(\n        SESSION_ID, STREAM_ID, INITIAL_TERM_ID);\n    private static final int FRAME_LENGTH = HEADER.capacity() + PAYLOAD.length;\n    private static final int ALIGNED_FRAME_LENGTH = align(FRAME_LENGTH, FRAME_ALIGNMENT);\n\n    private final ControlTransportPoller mockTransportPoller = mock(ControlTransportPoller.class);\n\n    private final RawLog rawLog = TestLogFactory.newLogBuffers(TERM_BUFFER_LENGTH);\n    private NetworkPublication publication;\n    private Sender sender;\n\n    private final FlowControl flowControl = spy(new UnicastFlowControl());\n    private final RetransmitHandler mockRetransmitHandler = mock(RetransmitHandler.class);\n    private final DriverConductorProxy mockDriverConductorProxy = mock(DriverConductorProxy.class);\n\n    private final CachedNanoClock nanoClock = new CachedNanoClock();\n\n    private final Queue<ByteBuffer> receivedFrames = new ArrayDeque<>();\n\n    private final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=localhost:40123\");\n    private final InetSocketAddress rcvAddress = udpChannel.remoteData();\n    private final DataHeaderFlyweight dataHeader = new DataHeaderFlyweight();\n    private final SetupFlyweight setupHeader = new SetupFlyweight();\n    private final SystemCounters mockSystemCounters = mock(SystemCounters.class);\n    private final OneToOneConcurrentArrayQueue<Runnable> senderCommandQueue =\n        new OneToOneConcurrentArrayQueue<>(Configuration.CMD_QUEUE_CAPACITY);\n\n    private final HeaderWriter headerWriter = HeaderWriter.newInstance(HEADER);\n\n    private final Answer<Integer> saveByteBufferAnswer =\n        (invocation) ->\n        {\n            final Object[] args = invocation.getArguments();\n            final ByteBuffer buffer = (ByteBuffer)args[0];\n\n            final int length = buffer.limit() - buffer.position();\n            receivedFrames.add(ByteBuffer.allocateDirect(length).put(buffer));\n\n            // we don't pass on the args, so don't reset buffer.position() back\n            return length;\n        };\n\n    private final ErrorHandler errorHandler = mock(ErrorHandler.class);\n\n    @BeforeEach\n    void setUp()\n    {\n        final SendChannelEndpoint mockSendChannelEndpoint = mock(SendChannelEndpoint.class);\n        when(mockSendChannelEndpoint.udpChannel()).thenReturn(udpChannel);\n        when(mockSendChannelEndpoint.send(any())).thenAnswer(saveByteBufferAnswer);\n        when(mockSystemCounters.get(any())).thenReturn(mock(AtomicCounter.class));\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .cachedEpochClock(new CachedEpochClock())\n            .cachedNanoClock(nanoClock)\n            .senderCachedNanoClock(nanoClock)\n            .receiverCachedNanoClock(nanoClock)\n            .controlTransportPoller(mockTransportPoller)\n            .systemCounters(mockSystemCounters)\n            .senderCommandQueue(senderCommandQueue)\n            .nanoClock(nanoClock)\n            .errorHandler(errorHandler)\n            .senderDutyCycleTracker(new DutyCycleTracker());\n        sender = new Sender(ctx);\n\n        LogBufferDescriptor.initialiseTailWithTermId(rawLog.metaData(), 0, INITIAL_TERM_ID);\n\n        final PublicationParams params = new PublicationParams();\n        params.entityTag = 101;\n        params.mtuLength = MAX_FRAME_LENGTH;\n        params.lingerTimeoutNs = Configuration.publicationLingerTimeoutNs();\n        params.signalEos = true;\n\n        publication = new NetworkPublication(\n            1,\n            ctx,\n            params,\n            mockSendChannelEndpoint,\n            rawLog,\n            Configuration.producerWindowLength(TERM_BUFFER_LENGTH, Configuration.publicationTermWindowLength()),\n            new AtomicLongPosition(),\n            new AtomicLongPosition(),\n            new AtomicLongPosition(),\n            new AtomicLongPosition(),\n            mock(AtomicCounter.class),\n            mock(AtomicCounter.class),\n            SESSION_ID,\n            STREAM_ID,\n            INITIAL_TERM_ID,\n            flowControl,\n            mockRetransmitHandler,\n            new NetworkPublicationThreadLocals(),\n            false);\n\n        assertTrue(senderCommandQueue.offer(() -> sender.onNewNetworkPublication(publication)));\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        sender.onClose();\n    }\n\n    @Test\n    void shouldSendSetupFrameOnChannelWhenTimeoutWithoutStatusMessage()\n    {\n        sender.doWork();\n        assertThat(receivedFrames.size(), is(1));\n        nanoClock.advance(Configuration.PUBLICATION_SETUP_TIMEOUT_NS - 1);\n        sender.doWork();\n        assertThat(receivedFrames.size(), is(1));\n\n        nanoClock.advance(10);\n        sender.doWork();\n        assertThat(receivedFrames.size(), is(2));\n\n        setupHeader.wrap(new UnsafeBuffer(receivedFrames.remove()));\n        assertThat(setupHeader.frameLength(), is(SetupFlyweight.HEADER_LENGTH));\n        assertThat(setupHeader.initialTermId(), is(INITIAL_TERM_ID));\n        assertThat(setupHeader.activeTermId(), is(INITIAL_TERM_ID));\n        assertThat(setupHeader.streamId(), is(STREAM_ID));\n        assertThat(setupHeader.sessionId(), is(SESSION_ID));\n        assertThat(setupHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_SETUP));\n        assertThat(setupHeader.flags(), is((short)0));\n        assertThat(setupHeader.version(), is((short)HeaderFlyweight.CURRENT_VERSION));\n    }\n\n    @Test\n    void shouldSendMultipleSetupFramesOnChannelWhenTimeoutWithoutStatusMessage()\n    {\n        sender.doWork();\n        assertThat(receivedFrames.size(), is(1));\n\n        nanoClock.advance(Configuration.PUBLICATION_SETUP_TIMEOUT_NS - 1);\n        sender.doWork();\n\n        nanoClock.advance(10);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(2));\n    }\n\n    @Test\n    void shouldNotSendSetupFrameOnlyOnceAfterReceivingStatusMessage()\n    {\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(INITIAL_TERM_ID);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(0);\n\n        publication.onStatusMessage(msg, rcvAddress, mockDriverConductorProxy);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1));\n        dataHeader.wrap(receivedFrames.remove());\n        assertThat(dataHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_DATA)); // heartbeat\n        assertThat(dataHeader.frameLength(), is(0));\n\n        nanoClock.advance(Configuration.PUBLICATION_SETUP_TIMEOUT_NS + 10);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1));\n        dataHeader.wrap(receivedFrames.remove());\n        assertThat(dataHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_DATA)); // heartbeat\n        assertThat(dataHeader.termOffset(), is(offsetOfMessage(1)));\n    }\n\n    @Test\n    void shouldSendSetupFrameAfterReceivingStatusMessageWithSetupBit()\n    {\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(INITIAL_TERM_ID);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(ALIGNED_FRAME_LENGTH);\n\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1)); // setup frame\n        receivedFrames.remove();\n\n        publication.onStatusMessage(msg, rcvAddress, mockDriverConductorProxy);\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(PAYLOAD.length));\n        buffer.putBytes(0, PAYLOAD);\n\n        appendUnfragmentedMessage(rawLog, 0, INITIAL_TERM_ID, 0, headerWriter, buffer, 0, PAYLOAD.length);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1)); // data\n        receivedFrames.remove();\n\n        publication.triggerSendSetupFrame(msg, rcvAddress);\n\n        sender.doWork();\n        assertThat(receivedFrames.size(), is(0)); // setup has been sent already, have to wait\n\n        nanoClock.advance(Configuration.PUBLICATION_SETUP_TIMEOUT_NS + 10);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(2));\n\n        setupHeader.wrap(new UnsafeBuffer(receivedFrames.remove()));\n        assertThat(setupHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_SETUP));\n        dataHeader.wrap(receivedFrames.remove());\n        assertThat(dataHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_DATA));\n        assertThat(dataHeader.frameLength(), is(0));\n    }\n\n    @Test\n    void shouldSendHeartbeatsEvenIfSendingPeriodicSetupFrames()\n    {\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(INITIAL_TERM_ID);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(ALIGNED_FRAME_LENGTH);\n\n        publication.onStatusMessage(msg, rcvAddress, mockDriverConductorProxy);\n\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1)); // heartbeat\n        receivedFrames.remove();\n\n        publication.triggerSendSetupFrame(msg, rcvAddress);\n        nanoClock.advance(Configuration.PUBLICATION_SETUP_TIMEOUT_NS + 10);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(2));\n\n        setupHeader.wrap(new UnsafeBuffer(receivedFrames.remove()));\n        assertThat(setupHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_SETUP));\n        dataHeader.wrap(receivedFrames.remove());\n        assertThat(dataHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_DATA)); // heartbeat is sent after setup\n        assertThat(dataHeader.frameLength(), is(0));\n    }\n\n    @Test\n    void shouldBeAbleToSendOnChannel()\n    {\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(INITIAL_TERM_ID);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(ALIGNED_FRAME_LENGTH);\n\n        publication.onStatusMessage(msg, rcvAddress, mockDriverConductorProxy);\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(PAYLOAD.length));\n        buffer.putBytes(0, PAYLOAD);\n\n        appendUnfragmentedMessage(rawLog, 0, INITIAL_TERM_ID, 0, headerWriter, buffer, 0, PAYLOAD.length);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1));\n\n        dataHeader.wrap(new UnsafeBuffer(receivedFrames.remove()));\n        assertThat(dataHeader.frameLength(), is(FRAME_LENGTH));\n        assertThat(dataHeader.termId(), is(INITIAL_TERM_ID));\n        assertThat(dataHeader.streamId(), is(STREAM_ID));\n        assertThat(dataHeader.sessionId(), is(SESSION_ID));\n        assertThat(dataHeader.termOffset(), is(offsetOfMessage(1)));\n        assertThat(dataHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_DATA));\n        assertThat(dataHeader.flags(), is(DataHeaderFlyweight.BEGIN_AND_END_FLAGS));\n        assertThat(dataHeader.version(), is((short)HeaderFlyweight.CURRENT_VERSION));\n    }\n\n    @Test\n    void shouldBeAbleToSendOnChannelTwice()\n    {\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(INITIAL_TERM_ID);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(2 * ALIGNED_FRAME_LENGTH);\n\n        publication.onStatusMessage(msg, rcvAddress, mockDriverConductorProxy);\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(PAYLOAD.length));\n        buffer.putBytes(0, PAYLOAD);\n\n        final int offset = appendUnfragmentedMessage(\n            rawLog, 0, INITIAL_TERM_ID, 0, headerWriter, buffer, 0, PAYLOAD.length);\n\n        sender.doWork();\n\n        appendUnfragmentedMessage(rawLog, 0, INITIAL_TERM_ID, offset, headerWriter, buffer, 0, PAYLOAD.length);\n\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(2));\n\n        dataHeader.wrap(new UnsafeBuffer(receivedFrames.remove()));\n        assertThat(dataHeader.frameLength(), is(FRAME_LENGTH));\n        assertThat(dataHeader.termId(), is(INITIAL_TERM_ID));\n        assertThat(dataHeader.streamId(), is(STREAM_ID));\n        assertThat(dataHeader.sessionId(), is(SESSION_ID));\n        assertThat(dataHeader.termOffset(), is(offsetOfMessage(1)));\n        assertThat(dataHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_DATA));\n        assertThat(dataHeader.flags(), is(DataHeaderFlyweight.BEGIN_AND_END_FLAGS));\n        assertThat(dataHeader.version(), is((short)HeaderFlyweight.CURRENT_VERSION));\n\n        dataHeader.wrap(new UnsafeBuffer(receivedFrames.remove()));\n        assertThat(dataHeader.frameLength(), is(FRAME_LENGTH));\n        assertThat(dataHeader.termId(), is(INITIAL_TERM_ID));\n        assertThat(dataHeader.streamId(), is(STREAM_ID));\n        assertThat(dataHeader.sessionId(), is(SESSION_ID));\n        assertThat(dataHeader.termOffset(), is(offsetOfMessage(2)));\n        assertThat(dataHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_DATA));\n        assertThat(dataHeader.flags(), is(DataHeaderFlyweight.BEGIN_AND_END_FLAGS));\n        assertThat(dataHeader.version(), is((short)HeaderFlyweight.CURRENT_VERSION));\n    }\n\n    @Test\n    void shouldNotSendUntilStatusMessageReceived()\n    {\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(PAYLOAD.length));\n        buffer.putBytes(0, PAYLOAD);\n        appendUnfragmentedMessage(rawLog, 0, INITIAL_TERM_ID, 0, headerWriter, buffer, 0, PAYLOAD.length);\n\n        sender.doWork();\n        assertThat(receivedFrames.size(), is(1));\n        setupHeader.wrap(receivedFrames.remove());\n        assertThat(setupHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_SETUP));\n\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(INITIAL_TERM_ID);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(ALIGNED_FRAME_LENGTH);\n\n        publication.onStatusMessage(msg, rcvAddress, mockDriverConductorProxy);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1));\n\n        dataHeader.wrap(new UnsafeBuffer(receivedFrames.remove()));\n\n        assertThat(dataHeader.frameLength(), is(FRAME_LENGTH));\n        assertThat(dataHeader.termId(), is(INITIAL_TERM_ID));\n        assertThat(dataHeader.streamId(), is(STREAM_ID));\n        assertThat(dataHeader.sessionId(), is(SESSION_ID));\n        assertThat(dataHeader.termOffset(), is(offsetOfMessage(1)));\n        assertThat(dataHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_DATA));\n        assertThat(dataHeader.flags(), is(DataHeaderFlyweight.BEGIN_AND_END_FLAGS));\n        assertThat(dataHeader.version(), is((short)HeaderFlyweight.CURRENT_VERSION));\n    }\n\n    @Test\n    void shouldNotBeAbleToSendAfterUsingUpYourWindow()\n    {\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(PAYLOAD.length));\n        buffer.putBytes(0, PAYLOAD);\n        appendUnfragmentedMessage(rawLog, 0, INITIAL_TERM_ID, 0, headerWriter, buffer, 0, PAYLOAD.length);\n\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(INITIAL_TERM_ID);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(ALIGNED_FRAME_LENGTH);\n\n        publication.onStatusMessage(msg, rcvAddress, mockDriverConductorProxy);\n\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1));\n\n        dataHeader.wrap(new UnsafeBuffer(receivedFrames.remove()));\n        assertThat(dataHeader.frameLength(), is(FRAME_LENGTH));\n        assertThat(dataHeader.termId(), is(INITIAL_TERM_ID));\n        assertThat(dataHeader.streamId(), is(STREAM_ID));\n        assertThat(dataHeader.sessionId(), is(SESSION_ID));\n        assertThat(dataHeader.termOffset(), is(offsetOfMessage(1)));\n        assertThat(dataHeader.headerType(), is(HeaderFlyweight.HDR_TYPE_DATA));\n        assertThat(dataHeader.flags(), is(DataHeaderFlyweight.BEGIN_AND_END_FLAGS));\n        assertThat(dataHeader.version(), is((short)HeaderFlyweight.CURRENT_VERSION));\n\n        appendUnfragmentedMessage(rawLog, 0, INITIAL_TERM_ID, 0, headerWriter, buffer, 0, PAYLOAD.length);\n\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(0));\n    }\n\n    @Test\n    void shouldSendLastDataFrameAsHeartbeatWhenIdle()\n    {\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(INITIAL_TERM_ID);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(ALIGNED_FRAME_LENGTH);\n\n        publication.onStatusMessage(msg, rcvAddress, mockDriverConductorProxy);\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(PAYLOAD.length));\n        buffer.putBytes(0, PAYLOAD);\n\n        appendUnfragmentedMessage(rawLog, 0, INITIAL_TERM_ID, 0, headerWriter, buffer, 0, PAYLOAD.length);\n\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1));  // should send ticks\n        receivedFrames.remove();                   // skip data frame\n\n        nanoClock.advance(Configuration.PUBLICATION_HEARTBEAT_TIMEOUT_NS - 1);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(0));  // should not send yet\n        nanoClock.advance(10);\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), greaterThanOrEqualTo(1));  // should send ticks\n\n        dataHeader.wrap(receivedFrames.remove());\n        assertThat(dataHeader.frameLength(), is(0));\n        assertThat(dataHeader.termOffset(), is(offsetOfMessage(2)));\n    }\n\n    @Test\n    void shouldSendMultipleDataFramesAsHeartbeatsWhenIdle()\n    {\n        final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class);\n        when(msg.consumptionTermId()).thenReturn(INITIAL_TERM_ID);\n        when(msg.consumptionTermOffset()).thenReturn(0);\n        when(msg.receiverWindowLength()).thenReturn(ALIGNED_FRAME_LENGTH);\n\n        publication.onStatusMessage(msg, rcvAddress, mockDriverConductorProxy);\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(PAYLOAD.length));\n        buffer.putBytes(0, PAYLOAD);\n\n        appendUnfragmentedMessage(rawLog, 0, INITIAL_TERM_ID, 0, headerWriter, buffer, 0, PAYLOAD.length);\n\n        sender.doWork();\n\n        assertThat(receivedFrames.size(), is(1));  // should send ticks\n        receivedFrames.remove();                   // skip data (heartbeat) frame\n\n        nanoClock.advance(Configuration.PUBLICATION_HEARTBEAT_TIMEOUT_NS - 1);\n        sender.doWork();\n        assertThat(receivedFrames.size(), is(0));  // should not send yet\n\n        nanoClock.advance(10);\n        sender.doWork();\n        assertThat(receivedFrames.size(), greaterThanOrEqualTo(1));  // should send ticks\n\n        dataHeader.wrap(receivedFrames.remove());\n        assertThat(dataHeader.frameLength(), is(0));\n        assertThat(dataHeader.termOffset(), is(offsetOfMessage(2)));\n\n        nanoClock.advance(Configuration.PUBLICATION_HEARTBEAT_TIMEOUT_NS - 1);\n        sender.doWork();\n        assertThat(receivedFrames.size(), is(0));  // should not send yet\n\n        nanoClock.advance(10);\n        sender.doWork();\n        assertThat(receivedFrames.size(), greaterThanOrEqualTo(1));  // should send ticks\n\n        dataHeader.wrap(receivedFrames.remove());\n        assertThat(dataHeader.frameLength(), is(0));\n        assertThat(dataHeader.termOffset(), is(offsetOfMessage(2)));\n    }\n\n    private int offsetOfMessage(final int offset)\n    {\n        return (offset - 1) * align(HEADER.capacity() + PAYLOAD.length, FRAME_ALIGNMENT);\n    }\n\n    private int appendUnfragmentedMessage(\n        final RawLog rawLog,\n        final int partitionIndex,\n        final int termId,\n        final int termOffset,\n        final HeaderWriter header,\n        final DirectBuffer srcBuffer,\n        final int srcOffset,\n        final int length)\n    {\n        final UnsafeBuffer termBuffer = rawLog.termBuffers()[partitionIndex];\n        final int frameLength = length + HEADER_LENGTH;\n        final int alignedLength = align(frameLength, FRAME_ALIGNMENT);\n        final int resultingOffset = termOffset + alignedLength;\n        final long rawTail = LogBufferDescriptor.packTail(termId, resultingOffset);\n\n        LogBufferDescriptor.rawTail(rawLog.metaData(), partitionIndex, rawTail);\n\n        header.write(termBuffer, termOffset, frameLength, termId);\n        termBuffer.putBytes(termOffset + HEADER_LENGTH, srcBuffer, srcOffset, length);\n\n        frameLengthOrdered(termBuffer, termOffset, frameLength);\n\n        return resultingOffset;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/StaticWindowCongestionControlTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass StaticWindowCongestionControlTest\n{\n    @Test\n    void shouldSetWindowLengthFromChannel()\n    {\n        final UdpChannel channelWithWindow = UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:9999|rcv-wnd=8192\");\n        final MediaDriver.Context context = new MediaDriver.Context().initialWindowLength(16536);\n        final int termLength = 1_000_000;\n\n        final StaticWindowCongestionControl staticWindowCongestionControl = new StaticWindowCongestionControl(\n            0, channelWithWindow, 0, 0, termLength, 0, null, null, null, context, null);\n\n        assertEquals(8192, staticWindowCongestionControl.initialWindowLength());\n    }\n\n    @Test\n    void shouldSetWindowLengthFromContext()\n    {\n        final UdpChannel channelWithoutWindow = UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:9999\");\n        final MediaDriver.Context context = new MediaDriver.Context().initialWindowLength(16536);\n        final int termLength = 1_000_000;\n\n        final StaticWindowCongestionControl staticWindowCongestionControl = new StaticWindowCongestionControl(\n            0, channelWithoutWindow, 0, 0, termLength, 0, null, null, null, context, null);\n\n        assertEquals(16536, staticWindowCongestionControl.initialWindowLength());\n    }\n\n    @Test\n    void shouldSetWindowLengthFromTermLength()\n    {\n        final UdpChannel channelWithWindow = UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:9999|rcv-wnd=8192\");\n        final MediaDriver.Context context = new MediaDriver.Context().initialWindowLength(16536);\n        final int termLength = 8192;\n\n        final StaticWindowCongestionControl staticWindowCongestionControl = new StaticWindowCongestionControl(\n            0, channelWithWindow, 0, 0, termLength, 0, null, null, null, context, null);\n\n        assertEquals(termLength / 2, staticWindowCongestionControl.initialWindowLength());\n    }\n}"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/TaggedMulticastFlowControlTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.protocol.ErrorFlyweight;\nimport io.aeron.protocol.StatusMessageFlyweight;\nimport io.aeron.test.Tests;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.ByteBuffer;\nimport java.util.stream.Stream;\n\nimport static io.aeron.protocol.ErrorFlyweight.MAX_ERROR_FRAME_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass TaggedMulticastFlowControlTest\n{\n    private static final int DEFAULT_GROUP_SIZE = 0;\n    private static final long DEFAULT_GROUP_TAG = Configuration.flowControlGroupTag();\n    private static final long DEFAULT_TIMEOUT = Configuration.flowControlReceiverTimeoutNs();\n    private static final int WINDOW_LENGTH = 16 * 1024;\n    private static final int COUNTERS_BUFFER_LENGTH = 16 * 1024;\n\n    private final UnsafeBuffer tempBuffer = new UnsafeBuffer(new byte[8192]);\n    private final CountersManager countersManager = Tests.newCountersManager(COUNTERS_BUFFER_LENGTH);\n    private final TaggedMulticastFlowControl flowControl = new TaggedMulticastFlowControl();\n\n    private static Stream<Arguments> validUris()\n    {\n        return Stream.of(\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged\",\n                DEFAULT_GROUP_TAG, DEFAULT_GROUP_SIZE, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,t:100ms\",\n                DEFAULT_GROUP_TAG, DEFAULT_GROUP_SIZE, 100_000_000),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123\",\n                123, DEFAULT_GROUP_SIZE, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:3000000000\",\n                3_000_000_000L, DEFAULT_GROUP_SIZE, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123,t:100ms\",\n                123, DEFAULT_GROUP_SIZE, 100_000_000),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:100/10\",\n                100, 10, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:/10\",\n                DEFAULT_GROUP_TAG, 10, DEFAULT_TIMEOUT),\n            Arguments.of(\n                \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:100/10,t:100ms\",\n                100, 10, 100_000_000));\n    }\n\n    MediaDriver.Context newContext()\n    {\n        return new MediaDriver.Context().tempBuffer(tempBuffer);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"validUris\")\n    void shouldParseValidFlowControlConfiguration(\n        final String uri, final long groupTag, final int groupSize, final long timeout)\n    {\n        flowControl.initialize(\n            newContext(), countersManager, UdpChannel.parse(uri), 0, 0, 0, 0, 0);\n\n        assertEquals(groupTag, flowControl.groupTag());\n        assertEquals(groupSize, flowControl.groupMinSize());\n        assertEquals(timeout, flowControl.receiverTimeoutNs());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:100/\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:/\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,t:\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:100,t:\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,t:100ms,g:100/\",\n    })\n    void shouldFailWithInvalidUris(final String uri)\n    {\n        assertThrows(\n            Exception.class,\n            () -> flowControl.initialize(\n            newContext(), countersManager, UdpChannel.parse(uri), 0, 0, 0, 0, 0));\n    }\n\n    @Test\n    void shouldClampToSenderLimitUntilMinimumGroupSizeIsMet()\n    {\n        final UdpChannel channelGroupSizeThree = UdpChannel.parse(\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123/3\");\n\n        flowControl.initialize(\n            newContext(), countersManager, channelGroupSizeThree, 0, 0, 0, 0, 0);\n        final long groupTag = 123L;\n\n        final long senderLimit = 5000L;\n        final int termOffset = 10_000;\n\n        assertEquals(senderLimit, onStatusMessage(flowControl, 0, termOffset, senderLimit, null));\n        assertEquals(senderLimit, onIdle(flowControl, senderLimit));\n        assertEquals(senderLimit, onStatusMessage(flowControl, 1, termOffset, senderLimit, groupTag));\n        assertEquals(senderLimit, onIdle(flowControl, senderLimit));\n        assertEquals(senderLimit, onStatusMessage(flowControl, 2, termOffset, senderLimit, groupTag));\n        assertEquals(senderLimit, onIdle(flowControl, senderLimit));\n        assertEquals(senderLimit, onStatusMessage(flowControl, 3, termOffset, senderLimit, null));\n        assertEquals(senderLimit, onIdle(flowControl, senderLimit));\n        assertEquals(termOffset + WINDOW_LENGTH, onStatusMessage(flowControl, 4, termOffset, senderLimit, groupTag));\n        assertEquals(termOffset + WINDOW_LENGTH, onIdle(flowControl, senderLimit));\n    }\n\n    @Test\n    void shouldReturnLastWindowWhenUntilReceiversAreInGroupWithNoMinSize()\n    {\n        final UdpChannel channelGroupSizeThree = UdpChannel.parse(\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123\");\n\n        flowControl.initialize(\n            newContext(), countersManager, channelGroupSizeThree, 0, 0, 0, 0, 0);\n        final long groupTag = 123L;\n\n        final long senderLimit = 5000L;\n        final int termOffset0 = 10_000;\n        final int termOffset1 = 9_999;\n\n        assertEquals(termOffset0 + WINDOW_LENGTH, onStatusMessage(flowControl, 0, termOffset0, senderLimit, null));\n        assertEquals(\n            termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 1, termOffset1, senderLimit, groupTag));\n        assertEquals(termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 0, termOffset0, senderLimit, null));\n    }\n\n    @Test\n    void shouldNotIncludeReceiverMoreThanWindowSizeBehindMinPosition()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:123/2\");\n\n        flowControl.initialize(\n            newContext(), countersManager, udpChannel, 0, 0, 0, 0, 0);\n\n        final int senderLimit = 5000;\n        final int termOffset0 = WINDOW_LENGTH * 2;\n        final int termOffset1 = termOffset0 - (WINDOW_LENGTH + 1);\n        final int termOffset2 = termOffset0 - (WINDOW_LENGTH);\n\n        assertEquals(senderLimit, onStatusMessage(flowControl, 1, termOffset0, senderLimit, 123L));\n        assertEquals(senderLimit, onStatusMessage(flowControl, 2, termOffset1, senderLimit, 123L));\n        assertEquals(termOffset2 + WINDOW_LENGTH, onStatusMessage(flowControl, 3, termOffset2, senderLimit, 123L));\n    }\n\n    @Test\n    void shouldRemoveEntryFromFlowControlOnEndOfStream()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:123/2\");\n\n        flowControl.initialize(\n            newContext(), countersManager, udpChannel, 0, 0, 0, 0, 0);\n\n        final int senderLimit = 5000;\n        final int termOffset1 = WINDOW_LENGTH;\n        final int termOffset2 = WINDOW_LENGTH + 1;\n        final int termOffset3 = WINDOW_LENGTH + 2;\n\n        assertEquals(senderLimit, onStatusMessage(flowControl, 1, termOffset1, senderLimit, 123L));\n        assertEquals(termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 2, termOffset2, senderLimit, 123L));\n        assertEquals(termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 3, termOffset3, senderLimit, 123L));\n\n        final long publicationLimit = onEosStatusMessage(flowControl, 1, termOffset1, senderLimit, 123L);\n\n        assertEquals(termOffset1 + WINDOW_LENGTH, publicationLimit);\n        assertEquals(termOffset2 + WINDOW_LENGTH, onIdle(flowControl, senderLimit));\n    }\n\n    @Test\n    void shouldRemoveEntryFromFlowControlOnError()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:123/2\");\n\n        flowControl.initialize(\n            newContext(), countersManager, udpChannel, 0, 0, 0, 0, 0);\n\n        final int senderLimit = 5000;\n        final int termOffset1 = WINDOW_LENGTH;\n        final int termOffset2 = WINDOW_LENGTH + 1;\n        final int termOffset3 = WINDOW_LENGTH + 2;\n\n        assertEquals(senderLimit, onStatusMessage(flowControl, 1, termOffset1, senderLimit, 123L));\n        assertEquals(termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 2, termOffset2, senderLimit, 123L));\n        assertEquals(termOffset1 + WINDOW_LENGTH, onStatusMessage(flowControl, 3, termOffset3, senderLimit, 123L));\n\n        onErrorMessage(flowControl, 1, 123L);\n\n        assertEquals(termOffset2 + WINDOW_LENGTH, onIdle(flowControl, senderLimit));\n    }\n\n    private long onStatusMessage(\n        final TaggedMulticastFlowControl flowControl,\n        final long receiverId,\n        final int termOffset,\n        final long senderLimit,\n        final Long groupTag)\n    {\n        return onStatusMessage(flowControl, receiverId, termOffset, senderLimit, groupTag, false);\n    }\n\n    private long onEosStatusMessage(\n        final TaggedMulticastFlowControl flowControl,\n        final long receiverId,\n        final int termOffset,\n        final long senderLimit,\n        final Long groupTag)\n    {\n        return onStatusMessage(flowControl, receiverId, termOffset, senderLimit, groupTag, true);\n    }\n\n    private void onErrorMessage(\n        final TaggedMulticastFlowControl flowControl,\n        final long receiverId,\n        final Long groupTag)\n    {\n        final ErrorFlyweight error = new ErrorFlyweight(ByteBuffer.allocate(MAX_ERROR_FRAME_LENGTH));\n        error\n            .receiverId(receiverId)\n            .groupTag(groupTag);\n\n        flowControl.onError(error, null, 0);\n    }\n\n    private static long onStatusMessage(\n        final TaggedMulticastFlowControl flowControl,\n        final long receiverId,\n        final int termOffset,\n        final long senderLimit,\n        final Long groupTag,\n        final boolean isEos)\n    {\n        final StatusMessageFlyweight statusMessageFlyweight = new StatusMessageFlyweight(ByteBuffer.allocate(1024));\n        final short flags = (isEos ? StatusMessageFlyweight.END_OF_STREAM_FLAG : 0);\n\n        statusMessageFlyweight.receiverId(receiverId);\n        statusMessageFlyweight.consumptionTermId(0);\n        statusMessageFlyweight.consumptionTermOffset(termOffset);\n        statusMessageFlyweight.groupTag(groupTag);\n        statusMessageFlyweight.flags(flags);\n        statusMessageFlyweight.receiverWindowLength(WINDOW_LENGTH);\n\n        return flowControl.onStatusMessage(statusMessageFlyweight, null, senderLimit, 0, 0, 0);\n    }\n\n    private long onIdle(final TaggedMulticastFlowControl flowControl, final long senderLimit)\n    {\n        return flowControl.onIdle(0, senderLimit, 0, false);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/TerminateDriverTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CommonContext;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass TerminateDriverTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    void shouldCallTerminationHookUponValidRequest()\n    {\n        final TerminationValidator mockTerminationValidator = mock(TerminationValidator.class);\n        final AtomicBoolean hasTerminated = new AtomicBoolean(false);\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .terminationHook(() -> hasTerminated.set(true))\n            .terminationValidator(mockTerminationValidator);\n\n        when(mockTerminationValidator.allowTermination(any(), any(), anyInt(), anyInt())).thenReturn(true);\n\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(ctx, systemTestWatcher))\n        {\n            assertTrue(CommonContext.requestDriverTermination(mediaDriver.context().aeronDirectory(), null, 0, 0));\n\n            do\n            {\n                Tests.yield();\n            }\n            while (!hasTerminated.get());\n        }\n\n        verify(mockTerminationValidator).allowTermination(any(), any(), anyInt(), anyInt());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotCallTerminationHookUponInvalidRequest()\n    {\n        final AtomicBoolean hasTerminatedByHook = new AtomicBoolean(false);\n        final AtomicBoolean hasCalledTerminationValidator = new AtomicBoolean(false);\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .terminationHook(() -> hasTerminatedByHook.set(true))\n            .terminationValidator((dir, buffer, offset, length) ->\n            {\n                hasCalledTerminationValidator.set(true);\n                return false;\n            });\n\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(ctx, systemTestWatcher))\n        {\n            assertFalse(hasCalledTerminationValidator.get());\n            assertTrue(CommonContext.requestDriverTermination(mediaDriver.context().aeronDirectory(), null, 0, 0));\n\n            do\n            {\n                Tests.yield();\n            }\n            while (!hasCalledTerminationValidator.get());\n        }\n\n        assertFalse(hasTerminatedByHook.get());\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/TimeTrackingNameResolverTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.CounterProvider;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\n\nimport java.net.InetAddress;\n\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass TimeTrackingNameResolverTest\n{\n    @Test\n    void throwsNullPointerExceptionIfDelegateResolverIsNull()\n    {\n        assertThrowsExactly(\n            NullPointerException.class,\n            () -> new TimeTrackingNameResolver(null, mock(NanoClock.class), mock(DutyCycleTracker.class)));\n    }\n\n    @Test\n    void throwsNullPointerExceptionIfNanoClockIsNull()\n    {\n        assertThrowsExactly(\n            NullPointerException.class,\n            () -> new TimeTrackingNameResolver(mock(NameResolver.class), null, mock(DutyCycleTracker.class)));\n    }\n\n    @Test\n    void throwsNullPointerExceptionIfDutyCycleTrackerIsNull()\n    {\n        assertThrowsExactly(\n            NullPointerException.class,\n            () -> new TimeTrackingNameResolver(mock(NameResolver.class), mock(NanoClock.class), null));\n    }\n\n    @Test\n    void closeIsANoOpIfDelegateResolverIsNotCloseable()\n    {\n        final NameResolver delegateResolver = mock(NameResolver.class);\n        final NanoClock clock = mock(NanoClock.class);\n        final DutyCycleTracker maxTime = mock(DutyCycleTracker.class);\n        final TimeTrackingNameResolver resolver = new TimeTrackingNameResolver(delegateResolver, clock, maxTime);\n\n        resolver.close();\n\n        verifyNoInteractions(delegateResolver, clock, maxTime);\n    }\n\n    @Test\n    void closeIShouldCloseDelegateResolver() throws Exception\n    {\n        final NameResolver delegateResolver = mock(\n            NameResolver.class, withSettings().extraInterfaces(AutoCloseable.class));\n        final NanoClock clock = mock(NanoClock.class);\n        final DutyCycleTracker maxTime = mock(DutyCycleTracker.class);\n        final TimeTrackingNameResolver resolver = new TimeTrackingNameResolver(delegateResolver, clock, maxTime);\n\n        resolver.close();\n\n        verify((AutoCloseable)delegateResolver).close();\n        verifyNoMoreInteractions(delegateResolver);\n        verifyNoInteractions(clock, maxTime);\n    }\n\n    @Test\n    void doWorkShouldCallActualMethod()\n    {\n        final NameResolver delegateResolver = mock(NameResolver.class);\n        final NanoClock clock = mock(NanoClock.class);\n        final DutyCycleTracker maxTime = mock(DutyCycleTracker.class);\n        final TimeTrackingNameResolver resolver = new TimeTrackingNameResolver(delegateResolver, clock, maxTime);\n\n        final long nowMs = 1111;\n        resolver.doWork(nowMs);\n\n        verify(delegateResolver).doWork(nowMs);\n        verifyNoMoreInteractions(delegateResolver);\n        verifyNoInteractions(clock, maxTime);\n    }\n\n    @Test\n    void initShouldCallActualMethod()\n    {\n        final NameResolver delegateResolver = mock(NameResolver.class);\n        final NanoClock clock = mock(NanoClock.class);\n        final DutyCycleTracker maxTime = mock(DutyCycleTracker.class);\n        final TimeTrackingNameResolver resolver = new TimeTrackingNameResolver(delegateResolver, clock, maxTime);\n\n        final CountersReader countersReader = mock(CountersReader.class);\n        final CounterProvider factory = mock(CounterProvider.class);\n        resolver.init(countersReader, factory);\n\n        verify(delegateResolver).init(countersReader, factory);\n        verifyNoMoreInteractions(delegateResolver);\n        verifyNoInteractions(clock, maxTime);\n    }\n\n    @Test\n    void lookupShouldMeasureExecutionTime()\n    {\n        final NameResolver delegateResolver = mock(NameResolver.class);\n        when(delegateResolver.lookup(anyString(), anyString(), anyBoolean()))\n            .thenAnswer(invocation ->\n            {\n                final String name = invocation.getArgument(0);\n                return name.substring(0, name.indexOf(':'));\n            });\n        final NanoClock clock = mock(NanoClock.class);\n        final long beginNs = 0;\n        final long endNs = 123456789;\n        when(clock.nanoTime()).thenReturn(beginNs, endNs);\n        final DutyCycleTracker maxTime = mock(DutyCycleTracker.class);\n        final TimeTrackingNameResolver resolver = new TimeTrackingNameResolver(delegateResolver, clock, maxTime);\n\n        final String name = \"my-host:8080\";\n        final String endpoint = \"endpoint\";\n        final boolean isReLookup = false;\n        assertEquals(\"my-host\", resolver.lookup(name, endpoint, isReLookup));\n\n        final InOrder inOrder = inOrder(delegateResolver, clock, maxTime);\n        inOrder.verify(clock).nanoTime();\n        inOrder.verify(maxTime).update(beginNs);\n        inOrder.verify(delegateResolver).lookup(name, endpoint, isReLookup);\n        inOrder.verify(clock).nanoTime();\n        inOrder.verify(maxTime).measureAndUpdate(endNs);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void lookupShouldMeasureExecutionTimeEvenIfExceptionIsThrown()\n    {\n        final NameResolver delegateResolver = mock(NameResolver.class);\n        final Error error = new Error(\"broken\");\n        when(delegateResolver.lookup(anyString(), anyString(), anyBoolean())).thenThrow(error);\n        final NanoClock clock = mock(NanoClock.class);\n        final long beginNs = 236745823658245L;\n        final long endNs = 7534957349857893459L;\n        when(clock.nanoTime()).thenReturn(beginNs, endNs);\n        final DutyCycleTracker maxTime = mock(DutyCycleTracker.class);\n        final TimeTrackingNameResolver resolver = new TimeTrackingNameResolver(delegateResolver, clock, maxTime);\n\n        final String name = \"test:555\";\n        final String endpoint = \"control\";\n        final boolean isReLookup = true;\n        final Error exception = assertThrowsExactly(Error.class, () -> resolver.lookup(name, endpoint, isReLookup));\n        assertSame(error, exception);\n\n        final InOrder inOrder = inOrder(delegateResolver, clock, maxTime);\n        inOrder.verify(clock).nanoTime();\n        inOrder.verify(maxTime).update(beginNs);\n        inOrder.verify(delegateResolver).lookup(name, endpoint, isReLookup);\n        inOrder.verify(clock).nanoTime();\n        inOrder.verify(maxTime).measureAndUpdate(endNs);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void resolveShouldMeasureExecutionTime()\n    {\n        final NameResolver delegateResolver = mock(NameResolver.class);\n        when(delegateResolver.resolve(anyString(), anyString(), anyBoolean()))\n            .thenAnswer(invocation -> InetAddress.getByName(invocation.getArgument(0)));\n        final NanoClock clock = mock(NanoClock.class);\n        final long beginNs = SECONDS.toNanos(1);\n        final long endNs = SECONDS.toNanos(9);\n        when(clock.nanoTime()).thenReturn(beginNs, endNs);\n        final DutyCycleTracker maxTime = mock(DutyCycleTracker.class);\n        final TimeTrackingNameResolver resolver = new TimeTrackingNameResolver(delegateResolver, clock, maxTime);\n\n        final String name = \"localhost\";\n        final String endpoint = \"endpoint\";\n        final boolean isReLookup = true;\n        assertEquals(InetAddress.getLoopbackAddress(), resolver.resolve(name, endpoint, isReLookup));\n\n        final InOrder inOrder = inOrder(delegateResolver, clock, maxTime);\n        inOrder.verify(clock).nanoTime();\n        inOrder.verify(maxTime).update(beginNs);\n        inOrder.verify(delegateResolver).resolve(name, endpoint, isReLookup);\n        inOrder.verify(clock).nanoTime();\n        inOrder.verify(maxTime).measureAndUpdate(endNs);\n        inOrder.verifyNoMoreInteractions();\n    }\n\n    @Test\n    void resolveShouldMeasureExecutionTimeEvenWhenExceptionIsThrown()\n    {\n        final NameResolver delegateResolver = mock(NameResolver.class);\n        final IllegalStateException exception = new IllegalStateException(\"error\");\n        when(delegateResolver.resolve(anyString(), anyString(), anyBoolean()))\n            .thenThrow(exception);\n        final NanoClock clock = mock(NanoClock.class);\n        final long beginNs = SECONDS.toNanos(0);\n        final long endNs = SECONDS.toNanos(3);\n        when(clock.nanoTime()).thenReturn(beginNs, endNs);\n        final DutyCycleTracker maxTime = mock(DutyCycleTracker.class);\n        final TimeTrackingNameResolver resolver = new TimeTrackingNameResolver(delegateResolver, clock, maxTime);\n\n        final String name = \"localhost\";\n        final String endpoint = \"endpoint\";\n        final boolean isReLookup = true;\n        final IllegalStateException error =\n            assertThrowsExactly(IllegalStateException.class, () -> resolver.resolve(name, endpoint, isReLookup));\n        assertSame(exception, error);\n\n        final InOrder inOrder = inOrder(delegateResolver, clock, maxTime);\n        inOrder.verify(clock).nanoTime();\n        inOrder.verify(maxTime).update(beginNs);\n        inOrder.verify(delegateResolver).resolve(name, endpoint, isReLookup);\n        inOrder.verify(clock).nanoTime();\n        inOrder.verify(maxTime).measureAndUpdate(endNs);\n        inOrder.verifyNoMoreInteractions();\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/UdpChannelTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.driver.exceptions.InvalidChannelException;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.LangUtil;\nimport org.agrona.Strings;\nimport org.hamcrest.Description;\nimport org.hamcrest.Matcher;\nimport org.hamcrest.TypeSafeMatcher;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.IOException;\nimport java.net.*;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Pattern;\n\nimport static io.aeron.driver.media.ControlMode.DYNAMIC;\nimport static io.aeron.driver.media.ControlMode.MANUAL;\nimport static io.aeron.driver.media.ControlMode.RESPONSE;\nimport static java.net.InetAddress.getByName;\nimport static java.util.concurrent.TimeUnit.MICROSECONDS;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\nclass UdpChannelTest\n{\n    @Test\n    void shouldHandleExplicitLocalAddressAndPortFormat()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?interface=localhost:40123|endpoint=localhost:40124\");\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"localhost\", 40123)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"localhost\", 40123)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"localhost\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"localhost\", 40124)));\n    }\n\n    @Test\n    void shouldHandleExplicitLocalControlAddressAndPortFormatIPv4()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?control=localhost:40124|control-mode=dynamic\");\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"localhost\", 40124)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"localhost\", 40124)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"0.0.0.0\", 0)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"0.0.0.0\", 0)));\n    }\n\n    @Test\n    void shouldHandleExplicitLocalControlAddressAndPortFormatIPv6()\n    {\n        assumeTrue(System.getProperty(\"java.net.preferIPv4Stack\") == null);\n\n        final UdpChannel udpChannel = UdpChannel.parse(\n            \"aeron:udp?control=[fe80::5246:5dff:fe73:df06]:40124|control-mode=dynamic\");\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"fe80::5246:5dff:fe73:df06\", 40124)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"fe80::5246:5dff:fe73:df06\", 40124)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"::\", 0)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"::\", 0)));\n    }\n\n    @Test\n    void shouldNotAllowDynamicControlModeWithoutExplicitControl()\n    {\n        try\n        {\n            UdpChannel.parse(\"aeron:udp?control-mode=dynamic\");\n            fail(\"InvalidChannelException expected\");\n        }\n        catch (final InvalidChannelException ex)\n        {\n            final Throwable cause = ex.getCause();\n            assertNotNull(cause);\n            assertThat(cause.getMessage(), containsString(\"explicit control expected with dynamic control mode\"));\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldHandleExplicitLocalAddressAndPortFormatWithAeronUri(\n        final String endpointKey, final String interfaceKey)\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            uri(endpointKey, \"localhost:40124\", interfaceKey, \"localhost:40123\"));\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"localhost\", 40123)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"localhost\", 40123)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"localhost\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"localhost\", 40124)));\n    }\n\n    @Test\n    void shouldHandleImpliedLocalAddressAndPortFormat()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=localhost:40124\");\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"0.0.0.0\", 0)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"0.0.0.0\", 0)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"localhost\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"localhost\", 40124)));\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldHandleImpliedLocalAddressAndPortFormatWithAeronUri(final String endpointKey)\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(uri(endpointKey, \"localhost:40124\"));\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"0.0.0.0\", 0)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"0.0.0.0\", 0)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"localhost\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"localhost\", 40124)));\n    }\n\n    @Test\n    void shouldThrowExceptionForIncorrectScheme()\n    {\n        assertThrows(InvalidChannelException.class, () -> UdpChannel.parse(\"unknownudp://localhost:40124\"));\n    }\n\n    @Test\n    void shouldThrowExceptionForMissingAddressWithAeronUri()\n    {\n        assertThrows(InvalidChannelException.class, () -> UdpChannel.parse(\"aeron:udp\"));\n    }\n\n    @Test\n    void shouldThrowExceptionOnEvenMulticastAddress()\n    {\n        assertThrows(InvalidChannelException.class, () -> UdpChannel.parse(\"aeron:udp?endpoint=224.10.9.8\"));\n    }\n\n    @Test\n    void shouldParseValidMulticastAddress() throws IOException\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?interface=localhost|endpoint=224.10.9.9:40124\");\n\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"localhost\", 0)));\n        assertThat(udpChannel.remoteControl(), isMulticastAddress(\"224.10.9.10\", 40124));\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"localhost\", 0)));\n        assertThat(udpChannel.remoteData(), isMulticastAddress(\"224.10.9.9\", 40124));\n        assertThat(udpChannel.localInterface(),\n            is(NetworkInterface.getByInetAddress(getByName(\"localhost\"))));\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldParseValidMulticastAddressWithAeronUri(final String endpointKey, final String interfaceKey)\n        throws IOException\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            uri(endpointKey, \"224.10.9.9:40124\", interfaceKey, \"localhost\"));\n\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"localhost\", 0)));\n        assertThat(udpChannel.remoteControl(), isMulticastAddress(\"224.10.9.10\", 40124));\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"localhost\", 0)));\n        assertThat(udpChannel.remoteData(), isMulticastAddress(\"224.10.9.9\", 40124));\n        assertThat(udpChannel.localInterface(),\n            is(NetworkInterface.getByInetAddress(getByName(\"localhost\"))));\n    }\n\n    private Matcher<InetSocketAddress> isMulticastAddress(final String addressName, final int port)\n        throws UnknownHostException\n    {\n        final InetAddress inetAddress = getByName(addressName);\n        return is(new InetSocketAddress(inetAddress, port));\n    }\n\n    @Test\n    void shouldHandleImpliedLocalPortFormat()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?interface=localhost|endpoint=localhost:40124\");\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"localhost\", 0)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"localhost\", 0)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"localhost\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"localhost\", 40124)));\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldHandleImpliedLocalPortFormatWithAeronUri(final String endpointKey, final String interfaceKey)\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            uri(endpointKey, \"localhost:40124\", interfaceKey, \"localhost\"));\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"localhost\", 0)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"localhost\", 0)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"localhost\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"localhost\", 40124)));\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldHandleIPv4AnyAddressAsInterfaceAddressForUnicast(\n        final String endpointKey, final String interfaceKey)\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\n            uri(endpointKey, \"localhost:40124\", interfaceKey, \"0.0.0.0\"));\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"0.0.0.0\", 0)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"0.0.0.0\", 0)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"localhost\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"localhost\", 40124)));\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldHandleIPv6AnyAddressAsInterfaceAddressForUnicast(\n        final String endpointKey, final String interfaceKey)\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(uri(endpointKey, \"[::1]:40124\", interfaceKey, \"[::]\"));\n\n        assertThat(udpChannel.localData(), is(new InetSocketAddress(\"::\", 0)));\n        assertThat(udpChannel.localControl(), is(new InetSocketAddress(\"::\", 0)));\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"::1\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"::1\", 40124)));\n    }\n\n    @Test\n    void shouldHandleLocalhostLookup()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=localhost:40124\");\n\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"127.0.0.1\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"127.0.0.1\", 40124)));\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = \"endpoint\")\n    void shouldHandleLocalhostLookupWithAeronUri(final String endpointKey)\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(uri(endpointKey, \"localhost:40124\"));\n\n        assertThat(udpChannel.remoteData(), is(new InetSocketAddress(\"127.0.0.1\", 40124)));\n        assertThat(udpChannel.remoteControl(), is(new InetSocketAddress(\"127.0.0.1\", 40124)));\n    }\n\n    @Test\n    void shouldHandleBeingUsedAsMapKey()\n    {\n        final UdpChannel udpChannel1 = UdpChannel.parse(\"aeron:udp?endpoint=localhost:40124\");\n        final UdpChannel udpChannel2 = UdpChannel.parse(\"aeron:udp?endpoint=localhost:40124\");\n\n        final Map<UdpChannel, Integer> map = new HashMap<>();\n\n        map.put(udpChannel1, 1);\n        assertThat(map.get(udpChannel2), is(1));\n    }\n\n    @Test\n    void shouldThrowExceptionWhenNoPortSpecified()\n    {\n        assertThrows(InvalidChannelException.class, () -> UdpChannel.parse(\"aeron:udp?endpoint=localhost\"));\n    }\n\n    @Test\n    void shouldHandleCanonicalFormForUnicastCorrectly()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=192.168.0.1:40456\");\n        final UdpChannel udpChannelLocal = UdpChannel.parse(\"aeron:udp?interface=127.0.0.1|endpoint=192.168.0.1:40456\");\n        final UdpChannel udpChannelLocalPort = UdpChannel.parse(\n            \"aeron:udp?interface=127.0.0.1:40455|endpoint=192.168.0.1:40456\");\n        final UdpChannel udpChannelLocalhost = UdpChannel.parse(\n            \"aeron:udp?interface=localhost|endpoint=localhost:40456\");\n\n        assertThat(udpChannel.canonicalForm(), is(\"UDP-0.0.0.0:0-192.168.0.1:40456\"));\n        assertThat(udpChannelLocal.canonicalForm(), is(\"UDP-127.0.0.1:0-192.168.0.1:40456\"));\n        assertThat(udpChannelLocalPort.canonicalForm(), is(\"UDP-127.0.0.1:40455-192.168.0.1:40456\"));\n        assertThat(udpChannelLocalhost.canonicalForm(), is(\"UDP-127.0.0.1:0-localhost:40456\"));\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldHandleIpV6CanonicalFormForUnicastCorrectly(\n        final String endpointKey, final String interfaceKey)\n    {\n        assumeTrue(System.getProperty(\"java.net.preferIPv4Stack\") == null);\n\n        final UdpChannel udpChannelLocal =\n            UdpChannel.parse(uri(endpointKey, \"192.168.0.1:40456\", interfaceKey, \"[::1]\"));\n        final UdpChannel udpChannelLocalPort =\n            UdpChannel.parse(uri(endpointKey, \"[fe80::5246:5dff:fe73:df06]:40456\", interfaceKey, \"127.0.0.1:40455\"));\n\n        assertThat(\n            udpChannelLocal.canonicalForm(),\n            is(\"UDP-\" + udpChannelLocal.localData().getHostString() + \":0-192.168.0.1:40456\"));\n        assertThat(\n            udpChannelLocalPort.canonicalForm(),\n            is(\"UDP-127.0.0.1:40455-[fe80::5246:5dff:fe73:df06]:40456\"));\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldHandleCanonicalFormForUnicastCorrectlyWithAeronUri(\n        final String endpointKey, final String interfaceKey)\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(uri(endpointKey, \"192.168.0.1:40456\"));\n        final UdpChannel udpChannelLocal =\n            UdpChannel.parse(uri(endpointKey, \"192.168.0.1:40456\", interfaceKey, \"127.0.0.1\"));\n        final UdpChannel udpChannelLocalPort =\n            UdpChannel.parse(uri(endpointKey, \"192.168.0.1:40456\", interfaceKey, \"127.0.0.1:40455\"));\n        final UdpChannel udpChannelLocalhost =\n            UdpChannel.parse(uri(endpointKey, \"localhost:40456\", interfaceKey, \"localhost\"));\n\n        assertThat(udpChannel.canonicalForm(), is(\"UDP-0.0.0.0:0-192.168.0.1:40456\"));\n        assertThat(udpChannelLocal.canonicalForm(), is(\"UDP-127.0.0.1:0-192.168.0.1:40456\"));\n        assertThat(udpChannelLocalPort.canonicalForm(), is(\"UDP-127.0.0.1:40455-192.168.0.1:40456\"));\n        assertThat(udpChannelLocalhost.canonicalForm(), is(\"UDP-127.0.0.1:0-localhost:40456\"));\n    }\n\n    @Test\n    void shouldGetProtocolFamilyForIpV4()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:12345|interface=127.0.0.1\");\n        assertThat(udpChannel.protocolFamily(), is(StandardProtocolFamily.INET));\n    }\n\n    @Test\n    void shouldGetProtocolFamilyForIpV6()\n    {\n        assumeTrue(System.getProperty(\"java.net.preferIPv4Stack\") == null);\n\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=[::1]:12345|interface=[::1]\");\n        assertThat(udpChannel.protocolFamily(), is(StandardProtocolFamily.INET6));\n    }\n\n    @Test\n    void shouldGetProtocolFamilyForIpV4WithoutLocalSpecified()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:12345\");\n        assertThat(udpChannel.protocolFamily(), is(StandardProtocolFamily.INET));\n    }\n\n    @Test\n    void shouldGetProtocolFamilyForIpV6WithoutLocalSpecified()\n    {\n        assumeTrue(System.getProperty(\"java.net.preferIPv4Stack\") == null);\n\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?endpoint=[::1]:12345\");\n        assertThat(udpChannel.protocolFamily(), is(StandardProtocolFamily.INET6));\n    }\n\n    @Test\n    void shouldHandleCanonicalFormWithNsLookup()\n    {\n        final UdpChannel udpChannelExampleCom = UdpChannel.parse(\"aeron:udp?endpoint=localhost:40456\");\n        assertThat(udpChannelExampleCom.canonicalForm(), is(\"UDP-0.0.0.0:0-localhost:40456\"));\n    }\n\n    @Test\n    void shouldHandleCanonicalFormForMulticastWithLocalPort()\n    {\n        final UdpChannel udpChannelLocalPort = UdpChannel.parse(\n            \"aeron:udp?interface=127.0.0.1:40455|endpoint=224.0.1.1:40456\");\n        assertThat(udpChannelLocalPort.canonicalForm(), is(\"UDP-127.0.0.1:40455-224.0.1.1:40456\"));\n\n        final UdpChannel udpChannelSubnetLocalPort =\n            UdpChannel.parse(\"aeron:udp?interface=127.0.0.0:40455/29|endpoint=224.0.1.1:40456\");\n        assertThat(\n            udpChannelSubnetLocalPort.canonicalForm(),\n            matchesPattern(\"UDP-127\\\\.0\\\\.0\\\\.[1-7]:40455-224\\\\.0\\\\.1\\\\.1:40456\"));\n    }\n\n    @Test\n    void shouldHandleCanonicalFormForMulticastCorrectly()\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(\"aeron:udp?interface=localhost|endpoint=224.0.1.1:40456\");\n        final UdpChannel udpChannelLocal = UdpChannel.parse(\"aeron:udp?interface=127.0.0.1|endpoint=224.0.1.1:40456\");\n        final UdpChannel udpChannelAllSystems = UdpChannel.parse(\n            \"aeron:udp?interface=localhost|endpoint=224.0.1.1:40456\");\n        final UdpChannel udpChannelDefault = UdpChannel.parse(\"aeron:udp?endpoint=224.0.1.1:40456\");\n\n        final UdpChannel udpChannelSubnet = UdpChannel.parse(\n            \"aeron:udp?interface=localhost/29|endpoint=224.0.1.1:40456\");\n        final UdpChannel udpChannelSubnetLocal = UdpChannel.parse(\n            \"aeron:udp?interface=127.0.0.0/29|endpoint=224.0.1.1:40456\");\n\n        assertThat(udpChannel.canonicalForm(), is(\"UDP-127.0.0.1:0-224.0.1.1:40456\"));\n        assertThat(udpChannelLocal.canonicalForm(), is(\"UDP-127.0.0.1:0-224.0.1.1:40456\"));\n\n        final Pattern canonicalFormPattern = Pattern.compile(\"UDP-127\\\\.0\\\\.0\\\\.[1-7]:0-224\\\\.0\\\\.1\\\\.1:40456\");\n        assertThat(udpChannelAllSystems.canonicalForm(), matchesPattern(canonicalFormPattern));\n        assertThat(udpChannelSubnet.canonicalForm(), matchesPattern(canonicalFormPattern));\n        assertThat(udpChannelSubnetLocal.canonicalForm(), matchesPattern(canonicalFormPattern));\n\n        assertThat(udpChannelDefault.localInterface(), supportsMulticastOrIsLoopback());\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldHandleCanonicalFormForMulticastCorrectlyWithAeronUri(\n        final String endpointKey, final String interfaceKey)\n    {\n        final UdpChannel udpChannel = UdpChannel.parse(uri(endpointKey, \"224.0.1.1:40456\", interfaceKey, \"localhost\"));\n        final UdpChannel udpChannelLocal =\n            UdpChannel.parse(uri(endpointKey, \"224.0.1.1:40456\", interfaceKey, \"127.0.0.1\"));\n        final UdpChannel udpChannelAllSystems =\n            UdpChannel.parse(uri(endpointKey, \"224.0.0.1:40456\", interfaceKey, \"127.0.0.1\"));\n        final UdpChannel udpChannelDefault = UdpChannel.parse(uri(endpointKey, \"224.0.1.1:40456\"));\n        final UdpChannel udpChannelSubnet =\n            UdpChannel.parse(uri(endpointKey, \"224.0.1.1:40456\", interfaceKey, \"localhost/24\"));\n        final UdpChannel udpChannelSubnetLocal =\n            UdpChannel.parse(uri(endpointKey, \"224.0.1.1:40456\", interfaceKey, \"127.0.0.0/24\"));\n\n        assertThat(udpChannel.canonicalForm(), is(\"UDP-127.0.0.1:0-224.0.1.1:40456\"));\n        assertThat(udpChannelLocal.canonicalForm(), is(\"UDP-127.0.0.1:0-224.0.1.1:40456\"));\n        assertThat(udpChannelAllSystems.canonicalForm(), is(\"UDP-127.0.0.1:0-224.0.0.1:40456\"));\n\n        final Pattern canonicalFormPattern = Pattern.compile(\"UDP-127\\\\.0\\\\.0\\\\.[1-7]:0-224\\\\.0\\\\.1\\\\.1:40456\");\n        assertThat(udpChannelSubnet.canonicalForm(), matchesPattern(canonicalFormPattern));\n        assertThat(udpChannelSubnetLocal.canonicalForm(), matchesPattern(canonicalFormPattern));\n        assertThat(udpChannelDefault.localInterface(), supportsMulticastOrIsLoopback());\n    }\n\n    @ParameterizedTest\n    @CsvSource(\"endpoint,interface\")\n    void shouldHandleIpV6CanonicalFormForMulticastCorrectly(final String endpointKey, final String interfaceKey)\n    {\n        assumeTrue(System.getProperty(\"java.net.preferIPv4Stack\") == null);\n\n        final UdpChannel udpChannel =\n            UdpChannel.parse(uri(endpointKey, \"[FF01::FD]:40456\", interfaceKey, \"localhost\"));\n        final UdpChannel udpChannelLocal =\n            UdpChannel.parse(uri(endpointKey, \"224.0.1.1:40456\", interfaceKey, \"[::1]:54321/64\"));\n\n        assertThat(\n            udpChannel.canonicalForm(),\n            is(\"UDP-127.0.0.1:0-\" + udpChannel.remoteData().getHostString() + \":40456\"));\n        assertThat(\n            udpChannelLocal.canonicalForm(),\n            is(\"UDP-\" + udpChannelLocal.localData().getHostString() + \":54321-224.0.1.1:40456\"));\n    }\n\n    @Test\n    void shouldUseTagsInCanonicalFormForMdsUris()\n    {\n        assertEquals(\n            \"UDP-0.0.0.0:0-0.0.0.0:0#1001\",\n            UdpChannel.parse(\"aeron:udp?control-mode=manual|tags=1001\").canonicalForm());\n    }\n\n    @Test\n    void shouldParseResponseControlMode()\n    {\n        final UdpChannel channel = UdpChannel.parse(\"aeron:udp?control-mode=response|control=127.0.0.1:10001\");\n        assertEquals(\"UDP-127.0.0.1:10001-0.0.0.0:0\", channel.canonicalForm());\n        assertTrue(channel.isResponseControlMode());\n    }\n\n    @Test\n    void shouldUseTagsInCanonicalFormForWildcardPorts()\n    {\n        assertEquals(\n            \"UDP-127.0.0.1:0-127.0.0.1:9999#1001\",\n            UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:9999|control=127.0.0.1:0|tags=1001\").canonicalForm());\n        assertEquals(\n            \"UDP-0.0.0.0:0-127.0.0.1:0#1001\",\n            UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:0|tags=1001\").canonicalForm());\n    }\n\n    @Test\n    void shouldParseSocketRcvAndSndBufSizes()\n    {\n        final UdpChannel udpChannelWithBufferSizes = UdpChannel.parse(\n            \"aeron:udp?endpoint=127.0.0.1:9999|so-sndbuf=4096|so-rcvbuf=8192\");\n        assertEquals(4096, udpChannelWithBufferSizes.socketSndbufLength());\n        assertEquals(8192, udpChannelWithBufferSizes.socketRcvbufLength());\n\n        final UdpChannel udpChannelWithoutBufferSizes = UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:9999\");\n        assertEquals(0, udpChannelWithoutBufferSizes.socketRcvbufLength());\n        assertEquals(0, udpChannelWithoutBufferSizes.socketSndbufLength());\n    }\n\n    @Test\n    void shouldParseReceiverWindow()\n    {\n        final UdpChannel udpChannelWithBufferSizes = UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:9999|rcv-wnd=8192\");\n        assertEquals(8192, udpChannelWithBufferSizes.receiverWindowLength());\n    }\n\n    @Test\n    void shouldParseChannelSendAndReceiveTimestampOffsets()\n    {\n        final UdpChannel channel = UdpChannel.parse(\n            \"aeron:udp?endpoint=localhost:0|channel-rcv-ts-offset=reserved|channel-snd-ts-offset=8\");\n\n        assertEquals(UdpChannel.RESERVED_VALUE_MESSAGE_OFFSET, channel.channelReceiveTimestampOffset());\n        assertEquals(8, channel.channelSendTimestampOffset());\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"NAME_ENDPOINT,192.168.1.1,,,UDP-127.0.0.1:0-NAME_ENDPOINT\",\n        \"localhost:41024,127.0.0.1,,,UDP-127.0.0.1:0-localhost:41024\",\n        \"[fe80::5246:5dff:fe73:df06]:40456,[fe80::5246:5dff:fe73:df06],,,\" +\n            \"UDP-127.0.0.1:0-[fe80::5246:5dff:fe73:df06]:40456\",\n        \"NAME_ENDPOINT,224.0.1.1,,,UDP-127.0.0.1:0-224.0.1.1:40124\",\n        \"NAME_ENDPOINT,192.168.1.1,NAME_CONTROL,192.168.1.2,UDP-NAME_CONTROL-NAME_ENDPOINT\",\n        \"NAME_ENDPOINT,224.0.1.1,NAME_CONTROL,127.0.0.1,UDP-127.0.0.1:0-224.0.1.1:40124\",\n        \"192.168.1.1:40124,192.168.1.1,NAME_CONTROL,192.168.1.2,UDP-NAME_CONTROL-192.168.1.1:40124\",\n        \"192.168.1.1:40124,192.168.1.1,192.168.1.2:40192,192.168.1.2,UDP-192.168.1.2:40192-192.168.1.1:40124\",\n    })\n    void shouldParseWithNameResolver(\n        final String endpointName,\n        final String endpointAddress,\n        final String controlName,\n        final String controlAddress,\n        final String canonicalForm)\n    {\n        final int port = 40124;\n\n        final NameResolver resolver = new NameResolver()\n        {\n            public InetAddress resolve(final String name, final String uriParamName, final boolean isReResolution)\n            {\n                return DefaultNameResolver.INSTANCE.resolve(name, uriParamName, isReResolution);\n            }\n\n            public String lookup(final String name, final String uriParamName, final boolean isReLookup)\n            {\n                if (endpointName.equals(name))\n                {\n                    return endpointAddress + \":\" + port;\n                }\n                else if (controlName.equals(name))\n                {\n                    return controlAddress + \":\" + port;\n                }\n\n                return name;\n            }\n        };\n\n        final ChannelUriStringBuilder uriBuilder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .networkInterface(\"localhost\");\n\n        if (!Strings.isEmpty(endpointName))\n        {\n            uriBuilder.endpoint(endpointName);\n        }\n\n        if (!Strings.isEmpty(controlName))\n        {\n            uriBuilder.controlEndpoint(controlName);\n        }\n\n        final UdpChannel udpChannel = UdpChannel.parse(uriBuilder.build(), resolver);\n\n        assertThat(udpChannel.canonicalForm(), is(canonicalForm));\n    }\n\n    @Test\n    void reservedValueOffsetIsCorrect()\n    {\n        assertEquals(\n            DataHeaderFlyweight.RESERVED_VALUE_OFFSET,\n            DataHeaderFlyweight.HEADER_LENGTH + UdpChannel.RESERVED_VALUE_MESSAGE_OFFSET);\n    }\n\n    @Test\n    void invalidGroupTagThrowsException()\n    {\n        assertThrows(\n            InvalidChannelException.class, () -> UdpChannel.parse(\"aeron:udp?endpoint=localhost:8080|gtag=foo\"));\n    }\n\n    @Test\n    void validGroupTagAvailable()\n    {\n        final UdpChannel channel = UdpChannel.parse(\"aeron:udp?endpoint=localhost:8080|gtag=1234\");\n        assertEquals(1234L, channel.groupTag());\n    }\n\n    @Test\n    void shouldSpecifyControlIfDynamicControlModeSpecified()\n    {\n        assertThrows(InvalidChannelException.class, () -> UdpChannel.parse(\"aeron:udp?control-mode=dynamic\"));\n    }\n\n    @Test\n    void shouldParseControlMode()\n    {\n        assertEquals(DYNAMIC, UdpChannel.parse(\"aeron:udp?control=localhost:8080|control-mode=dynamic\").controlMode());\n        assertEquals(MANUAL, UdpChannel.parse(\"aeron:udp?control=localhost:8080|control-mode=manual\").controlMode());\n        assertEquals(\n            RESPONSE, UdpChannel.parse(\"aeron:udp?control=localhost:8080|control-mode=response\").controlMode());\n    }\n\n    @Test\n    void shouldParseNakDelay()\n    {\n        assertEquals(\n            MILLISECONDS.toNanos(34),\n            UdpChannel.parse(\"aeron:udp?endpoint=localhost:8080|nak-delay=34ms\").nakDelayNs());\n        assertEquals(\n            MICROSECONDS.toNanos(27),\n            UdpChannel.parse(\"aeron:udp?endpoint=localhost:8080|nak-delay=27us\").nakDelayNs());\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"invalidMulticastAddresses\")\n    void isMulticastDestinationAddressThrowsInvalidChannelExceptionIfInvalid(final ChannelUri uri)\n    {\n        assertThrowsExactly(InvalidChannelException.class, () -> UdpChannel.isMulticastDestinationAddress(uri));\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"aeron:udp?mtu=8192|endpoint=192.168.0.1:5656, false\",\n        \"aeron:udp?mtu=8192|endpoint=224.0.0.1:5656, true\",\n        \"aeron:udp?endpoint=239.255.255.250:1010, true\",\n        \"aeron:udp?endpoint=[FF01:0:0:0:0:0:0:18C]:5555, true\",\n        \"aeron:udp?endpoint=[ff02::1]:1234, true\",\n        \"aeron:udp?endpoint=[fe80::ce81:b1c:bd2c:69e%utun3]:8080, false\"\n    })\n    void shouldDetectMulticastAddress(final String uri, final boolean expected)\n    {\n        assertEquals(expected, UdpChannel.isMulticastDestinationAddress(ChannelUri.parse(uri)));\n    }\n\n    private static List<ChannelUri> invalidMulticastAddresses()\n    {\n        return Arrays.asList(\n            null,\n            ChannelUri.parse(\"aeron:ipc\"),\n            ChannelUri.parse(\"aeron:udp?mtu=4096\"),\n            ChannelUri.parse(\"aeron:udp?endpoint=test\"),\n            ChannelUri.parse(\"aeron:udp?endpoint=test:a\"),\n            ChannelUri.parse(\"aeron:udp?endpoint=[xx:xx:xx:xx]:5656\"));\n    }\n\n    private static Matcher<NetworkInterface> supportsMulticastOrIsLoopback()\n    {\n        return new NetworkInterfaceTypeSafeMatcher();\n    }\n\n    private static String uri(\n        final String endpointKey, final String endpointValue, final String interfaceKey, final String interfaceValue)\n    {\n        return \"aeron:udp?\" + endpointKey + \"=\" + endpointValue + \"|\" + interfaceKey + \"=\" + interfaceValue;\n    }\n\n    private static String uri(final String endpointKey, final String endpointValue)\n    {\n        return \"aeron:udp?\" + endpointKey + \"=\" + endpointValue;\n    }\n\n    static class NetworkInterfaceTypeSafeMatcher extends TypeSafeMatcher<NetworkInterface>\n    {\n        public void describeTo(final Description description)\n        {\n            description.appendText(\"Interface supports multicast or is loopack\");\n        }\n\n        protected boolean matchesSafely(final NetworkInterface networkInterface)\n        {\n            boolean matchesSafely = false;\n            try\n            {\n                matchesSafely = networkInterface.supportsMulticast() || networkInterface.isLoopback();\n            }\n            catch (final SocketException ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n\n            return matchesSafely;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/UntetheredSubscriptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.driver.buffer.TestLogFactory;\nimport io.aeron.driver.status.SystemCounters;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.status.AtomicLongPosition;\nimport org.agrona.concurrent.status.Position;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nclass UntetheredSubscriptionTest\n{\n    private static final long REGISTRATION_ID = 1;\n    private static final int TAG_ID = 0;\n    private static final int SESSION_ID = 777;\n    private static final int STREAM_ID = 1003;\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int TERM_WINDOW_LENGTH = TERM_BUFFER_LENGTH / 2;\n    private static final long TIME_NS = 1000;\n    private static final long UNTETHERED_WINDOW_LIMIT_TIMEOUT_NS = Configuration.untetheredWindowLimitTimeoutNs();\n    private static final long UNTETHERED_LINGER_TIMEOUT_NS = Configuration.untetheredLingerTimeoutNs();\n    private static final long UNTETHERED_RESTING_TIMEOUT_NS = Configuration.untetheredRestingTimeoutNs();\n    private static final String CHANNEL = CommonContext.IPC_CHANNEL + \"?term-length=\" + TERM_BUFFER_LENGTH;\n\n    private final RawLog rawLog = TestLogFactory.newLogBuffers(TERM_BUFFER_LENGTH);\n    private final AtomicLongPosition publisherLimit = new AtomicLongPosition();\n    private final MediaDriver.Context ctx = new MediaDriver.Context()\n        .cachedNanoClock(new CachedNanoClock())\n        .systemCounters(mock(SystemCounters.class));\n    private final DriverConductor conductor = mock(DriverConductor.class);\n\n    private IpcPublication ipcPublication;\n    private PublicationParams params;\n\n    @BeforeEach\n    void before()\n    {\n        ctx.cachedNanoClock().update(TIME_NS);\n        params = PublicationParams.getPublicationParams(\n            ChannelUri.parse(CHANNEL), ctx, conductor, STREAM_ID, \"ipc\");\n\n        ipcPublication = new IpcPublication(\n            REGISTRATION_ID,\n            CHANNEL,\n            ctx,\n            TAG_ID,\n            SESSION_ID,\n            STREAM_ID,\n            mock(Position.class),\n            publisherLimit,\n            rawLog,\n            true,\n            params);\n    }\n\n    @Test\n    void shouldLifeCycleTimeoutsAndRelink()\n    {\n        final Position tetheredPosition = new AtomicLongPosition();\n        tetheredPosition.set(TERM_WINDOW_LENGTH - 1);\n        final Position untetheredPosition = new AtomicLongPosition();\n\n        final SubscriptionLink tetheredLink = newLink(true);\n        final SubscriptionLink untetheredLink = newLink(false);\n\n        ipcPublication.addSubscriber(tetheredLink, tetheredPosition, ctx.cachedNanoClock().nanoTime());\n        ipcPublication.addSubscriber(untetheredLink, untetheredPosition, ctx.cachedNanoClock().nanoTime());\n\n        ipcPublication.updatePublisherPositionAndLimit();\n\n        final long timeNs = TIME_NS + 1;\n        ipcPublication.onTimeEvent(timeNs, 0, conductor);\n        verify(conductor, never()).notifyUnavailableImageLink(REGISTRATION_ID, untetheredLink);\n\n        final long windowLimitTimeoutNs = timeNs + params.untetheredWindowLimitTimeoutNs;\n        ipcPublication.onTimeEvent(windowLimitTimeoutNs, 0, conductor);\n        verify(conductor, times(1)).notifyUnavailableImageLink(REGISTRATION_ID, untetheredLink);\n\n        ipcPublication.updatePublisherPositionAndLimit();\n        assertEquals(TERM_WINDOW_LENGTH, publisherLimit.get());\n\n        final long afterLingerTimeoutNs = windowLimitTimeoutNs + params.untetheredLingerTimeoutNs;\n        ipcPublication.onTimeEvent(afterLingerTimeoutNs, 0, conductor);\n        ipcPublication.updatePublisherPositionAndLimit();\n        assertEquals(tetheredPosition.get() + TERM_WINDOW_LENGTH, publisherLimit.get());\n\n        final long afterRestingTimeoutNs = afterLingerTimeoutNs + UNTETHERED_RESTING_TIMEOUT_NS;\n        ipcPublication.onTimeEvent(afterRestingTimeoutNs, 0, conductor);\n\n        verify(conductor, times(1)).notifyAvailableImageLink(\n            eq(REGISTRATION_ID),\n            eq(SESSION_ID),\n            eq(untetheredLink),\n            anyInt(),\n            eq(tetheredPosition.get()),\n            eq(rawLog.fileName()),\n            eq(CommonContext.IPC_CHANNEL));\n    }\n\n    IpcSubscriptionLink newLink(final boolean isTether)\n    {\n        final SubscriptionParams params = new SubscriptionParams();\n        params.isTether = isTether;\n\n        return new IpcSubscriptionLink(1, STREAM_ID, CHANNEL, null, params);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/buffer/FileStoreLogFactoryTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.buffer;\n\nimport io.aeron.driver.Configuration;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.StorageSpaceException;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.IoUtil;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.function.Executable;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.FileStore;\nimport java.nio.file.Files;\nimport java.util.List;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.PARTITION_COUNT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computeLogLength;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass FileStoreLogFactoryTest\n{\n    private static final int CREATION_ID = 102;\n    private static final File DATA_DIR = new File(SystemUtil.tmpDirName(), \"dataDirName\");\n    private static final int TERM_BUFFER_LENGTH = Configuration.TERM_BUFFER_LENGTH_DEFAULT;\n    private static final long LOW_STORAGE_THRESHOLD = Configuration.LOW_FILE_STORE_WARNING_THRESHOLD_DEFAULT;\n    private static final int PAGE_SIZE = 4 * 1024;\n    private static final boolean PRE_ZERO_LOG = true;\n    private static final boolean PERFORM_STORAGE_CHECKS = true;\n    private final AtomicCounter mockBytesMappedCounter = mock(AtomicCounter.class);\n    private FileStoreLogFactory fileStoreLogFactory;\n    private RawLog rawLog;\n\n    @BeforeEach\n    void createDataDir()\n    {\n        IoUtil.ensureDirectoryExists(DATA_DIR, \"data\");\n        final String absolutePath = DATA_DIR.getAbsolutePath();\n        fileStoreLogFactory = new FileStoreLogFactory(\n            absolutePath,\n            PAGE_SIZE,\n            PERFORM_STORAGE_CHECKS,\n            LOW_STORAGE_THRESHOLD,\n            mock(ErrorHandler.class),\n            mockBytesMappedCounter);\n    }\n\n    @AfterEach\n    void cleanupFiles()\n    {\n        CloseHelper.close(rawLog);\n        CloseHelper.close(fileStoreLogFactory);\n        IoUtil.delete(DATA_DIR, false);\n    }\n\n    @Test\n    void shouldCreateCorrectLengthAndZeroedFilesForPublication()\n    {\n        rawLog = fileStoreLogFactory.newPublication(CREATION_ID, TERM_BUFFER_LENGTH, PRE_ZERO_LOG);\n\n        assertEquals(TERM_BUFFER_LENGTH, rawLog.termLength());\n\n        final UnsafeBuffer[] termBuffers = rawLog.termBuffers();\n        assertEquals(PARTITION_COUNT, termBuffers.length);\n\n        for (final UnsafeBuffer termBuffer : termBuffers)\n        {\n            assertEquals(TERM_BUFFER_LENGTH, termBuffer.capacity());\n            assertEquals(0, termBuffer.getByte(0));\n            assertEquals(0, termBuffer.getByte(TERM_BUFFER_LENGTH - 1));\n        }\n\n        final UnsafeBuffer metaData = rawLog.metaData();\n\n        assertEquals(LogBufferDescriptor.LOG_META_DATA_LENGTH, metaData.capacity());\n        assertEquals(0, metaData.getByte(0));\n        assertEquals(0, metaData.getByte(LogBufferDescriptor.LOG_META_DATA_LENGTH - 1));\n    }\n\n    @Test\n    void shouldCreateCorrectLengthAndZeroedFilesForImage()\n    {\n        final int imageTermBufferLength = TERM_BUFFER_LENGTH / 2;\n        rawLog = fileStoreLogFactory.newImage(CREATION_ID, imageTermBufferLength, PRE_ZERO_LOG);\n\n        assertEquals(imageTermBufferLength, rawLog.termLength());\n\n        final UnsafeBuffer[] termBuffers = rawLog.termBuffers();\n        assertEquals(PARTITION_COUNT, termBuffers.length);\n\n        for (final UnsafeBuffer termBuffer : termBuffers)\n        {\n            assertEquals(imageTermBufferLength, termBuffer.capacity());\n            assertEquals(0, termBuffer.getByte(0));\n            assertEquals(0, termBuffer.getByte(imageTermBufferLength - 1));\n        }\n\n        final UnsafeBuffer metaData = rawLog.metaData();\n\n        assertEquals(LogBufferDescriptor.LOG_META_DATA_LENGTH, metaData.capacity());\n        assertEquals(0, metaData.getByte(0));\n        assertEquals(0, metaData.getByte(LogBufferDescriptor.LOG_META_DATA_LENGTH - 1));\n    }\n\n    @Test\n    void shouldThrowInsufficientUsableStorageExceptionIfNotEnoughSpaceOnDisc() throws IOException\n    {\n        final FileStore fileStore = mock(FileStore.class);\n        final long usableSpace = 117L;\n        when(fileStore.getUsableSpace()).thenReturn(usableSpace);\n        when(fileStore.toString()).thenReturn(\"test-fs\");\n        final ErrorHandler errorHandler = mock(ErrorHandler.class);\n\n        try (MockedStatic<Files> files = Mockito.mockStatic(Files.class))\n        {\n            files.when(() -> Files.getFileStore(any())).thenReturn(fileStore);\n\n            try (FileStoreLogFactory logFactory = new FileStoreLogFactory(\n                DATA_DIR.getAbsolutePath(),\n                PAGE_SIZE, true,\n                LOW_STORAGE_THRESHOLD,\n                errorHandler,\n                mockBytesMappedCounter))\n            {\n                final int imageTermBufferLength = 64 * 1024;\n                assertThrowsStorageSpaceException(\n                    fileStore,\n                    imageTermBufferLength,\n                    usableSpace,\n                    () -> logFactory.newImage(1, imageTermBufferLength, true));\n\n                final int publicationTermBufferLength = 1024 * 1024;\n                assertThrowsStorageSpaceException(\n                    fileStore,\n                    publicationTermBufferLength,\n                    usableSpace,\n                    () -> logFactory.newPublication(2, publicationTermBufferLength, false));\n\n            }\n        }\n\n        verifyNoInteractions(errorHandler);\n    }\n\n    @Test\n    void shouldWarnAboutLowStorageSpace() throws IOException\n    {\n        final int termLength = 64 * 1024;\n        final long logLength = computeLogLength(termLength, PAGE_SIZE);\n        final long lowStorageWarningThreshold = logLength * 3;\n        final FileStore fileStore = mock(FileStore.class);\n        when(fileStore.getUsableSpace()).thenReturn(logLength * 3, logLength);\n        when(fileStore.toString()).thenReturn(\"test-fs\");\n        final ErrorHandler errorHandler = mock(ErrorHandler.class);\n\n        try (MockedStatic<Files> files = Mockito.mockStatic(Files.class))\n        {\n            files.when(() -> Files.getFileStore(any())).thenReturn(fileStore);\n\n            try (FileStoreLogFactory logFactory = new FileStoreLogFactory(\n                DATA_DIR.getAbsolutePath(),\n                PAGE_SIZE,\n                true,\n                lowStorageWarningThreshold,\n                errorHandler,\n                mockBytesMappedCounter))\n            {\n                try (RawLog rawLog = logFactory.newPublication(11, termLength, true))\n                {\n                    assertNotNull(rawLog);\n                }\n                try (RawLog rawLog = logFactory.newImage(2222, termLength, false))\n                {\n                    assertNotNull(rawLog);\n                }\n            }\n        }\n\n        final ArgumentCaptor<AeronException> exceptionArgumentCaptor = ArgumentCaptor.forClass(AeronException.class);\n        verify(errorHandler, times(2)).onError(exceptionArgumentCaptor.capture());\n        final List<AeronException> exceptions = exceptionArgumentCaptor.getAllValues();\n        for (int i = 0; i < 2; i++)\n        {\n            final long usableSpace = 0 == i ? logLength * 3 : logLength;\n            final AeronException exception = exceptions.get(i);\n            assertEquals(AeronException.Category.WARN, exception.category());\n            assertEquals(\"WARN - space is running low: threshold=\" + lowStorageWarningThreshold +\n                \" usable=\" + usableSpace + \" in \" + fileStore, exception.getMessage());\n        }\n        verifyNoMoreInteractions(errorHandler);\n    }\n\n    private static void assertThrowsStorageSpaceException(\n        final FileStore fileStore, final int termBufferLength, final long usableSpace, final Executable executable)\n    {\n        final StorageSpaceException exception = assertThrowsExactly(StorageSpaceException.class, executable);\n        assertEquals(\n            \"ERROR - insufficient usable storage for new log of length=\" +\n            computeLogLength(termBufferLength, PAGE_SIZE) + \" usable=\" + usableSpace + \" in \" + fileStore,\n            exception.getMessage());\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/buffer/TestLogFactory.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.buffer;\n\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_META_DATA_LENGTH;\nimport static io.aeron.logbuffer.LogBufferDescriptor.PARTITION_COUNT;\n\npublic class TestLogFactory implements LogFactory\n{\n    public void close()\n    {\n    }\n\n    public RawLog newPublication(final long correlationId, final int termBufferLength, final boolean useSparseFiles)\n    {\n        return newLogBuffers(termBufferLength);\n    }\n\n    public RawLog newImage(final long correlationId, final int termBufferLength, final boolean useSparseFiles)\n    {\n        return newLogBuffers(termBufferLength);\n    }\n\n    public static RawLog newLogBuffers(final int termLength)\n    {\n        return new RawLog()\n        {\n            private final UnsafeBuffer[] termBuffers = new UnsafeBuffer[]\n            {\n                newLogBuffer(termLength),\n                newLogBuffer(termLength),\n                newLogBuffer(termLength),\n            };\n\n            private final UnsafeBuffer logMetaData = new UnsafeBuffer(ByteBuffer.allocateDirect(LOG_META_DATA_LENGTH));\n\n            public int termLength()\n            {\n                return termBuffers[0].capacity();\n            }\n\n            public UnsafeBuffer[] termBuffers()\n            {\n                return termBuffers;\n            }\n\n            public UnsafeBuffer metaData()\n            {\n                return logMetaData;\n            }\n\n            public ByteBuffer[] sliceTerms()\n            {\n                final ByteBuffer[] terms = new ByteBuffer[PARTITION_COUNT];\n                for (int i = 0; i < PARTITION_COUNT; i++)\n                {\n                    terms[i] = termBuffers[i].byteBuffer().duplicate();\n                }\n\n                return terms;\n            }\n\n            public String fileName()\n            {\n                return \"stream.log\";\n            }\n\n            public boolean free()\n            {\n                return true;\n            }\n\n            public void close()\n            {\n            }\n        };\n    }\n\n    private static UnsafeBuffer newLogBuffer(final int termBufferLength)\n    {\n        return new UnsafeBuffer(ByteBuffer.allocateDirect(termBufferLength));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/ext/CubicCongestionControlTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.media.UdpChannel;\nimport org.agrona.concurrent.CachedNanoClock;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass CubicCongestionControlTest\n{\n    public static final int CONTEXT_RECEIVER_WINDOW_LENGTH = 65536;\n    public static final int CHANNEL_RECEIVER_WINDOW_LENGTH = 8192;\n    public static final int MTU_LENGTH = 1024;\n\n    private final CountersManager countersManager = mock(CountersManager.class);\n    private final UnsafeBuffer tempBuffer = new UnsafeBuffer(new byte[8192]);\n    private final UnsafeBuffer valuesBuffer = new UnsafeBuffer(new byte[8192]);\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .initialWindowLength(CONTEXT_RECEIVER_WINDOW_LENGTH)\n        .tempBuffer(tempBuffer);\n    private final UdpChannel channelWithWindow = UdpChannel.parse(\n        \"aeron:udp?endpoint=127.0.0.1:9999|rcv-wnd=\" + CHANNEL_RECEIVER_WINDOW_LENGTH);\n    private final UdpChannel channelWithoutWindow = UdpChannel.parse(\"aeron:udp?endpoint=127.0.0.1:9999\");\n    private final int bigTermLength = 1_000_000;\n    private final NanoClock nanoClock = new CachedNanoClock();\n\n    @BeforeEach\n    void setUp()\n    {\n        when(countersManager.valuesBuffer()).thenReturn(valuesBuffer);\n    }\n\n    @Test\n    void shouldSetWindowLengthFromChannel()\n    {\n        final CubicCongestionControl cubicCongestionControl = new CubicCongestionControl(\n            0, channelWithWindow, 0, 0, bigTermLength, MTU_LENGTH, null, null, nanoClock, context, countersManager);\n\n        assertEquals(CHANNEL_RECEIVER_WINDOW_LENGTH / MTU_LENGTH, cubicCongestionControl.maxCongestionWindow());\n    }\n\n    @Test\n    void shouldSetWindowLengthFromContext()\n    {\n        final CubicCongestionControl cubicCongestionControl = new CubicCongestionControl(\n            0, channelWithoutWindow, 0, 0, bigTermLength, MTU_LENGTH, null, null, nanoClock, context, countersManager);\n\n        assertEquals(CONTEXT_RECEIVER_WINDOW_LENGTH / MTU_LENGTH, cubicCongestionControl.maxCongestionWindow());\n    }\n\n    @Test\n    void shouldSetWindowLengthFromTermLength()\n    {\n        final int smallTermLength = 8192;\n        final CubicCongestionControl cubicCongestionControl = new CubicCongestionControl(\n            0, channelWithWindow, 0, 0, smallTermLength, MTU_LENGTH, null, null, nanoClock, context, countersManager);\n\n        assertEquals(smallTermLength / 2 / MTU_LENGTH, cubicCongestionControl.maxCongestionWindow());\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/ext/FixedLossGeneratorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass FixedLossGeneratorTest\n{\n    @Test\n    void shouldDropSingleFrameOnce()\n    {\n        final FixedLossGenerator fixedLossGenerator = new FixedLossGenerator(0, 0, 1408);\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 0, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 0, 1408));\n    }\n\n    @Test\n    void shouldDropFirstTwoFramesOnceUnaligned()\n    {\n        final FixedLossGenerator fixedLossGenerator = new FixedLossGenerator(0, 50, 1408);\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 0, 1408));\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 2 * 1408, 1408));\n\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 0, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 2 * 1408, 1408));\n    }\n\n    @Test\n    void shouldDropForIndependentStreams()\n    {\n        final FixedLossGenerator fixedLossGenerator = new FixedLossGenerator(0, 50, 1408);\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 0, 1408));\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 2 * 1408, 1408));\n\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 123, 457, 0, 0, 1408));\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 123, 457, 0, 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 457, 0, 2 * 1408, 1408));\n\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 124, 456, 0, 0, 1408));\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 124, 456, 0, 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 124, 456, 0, 2 * 1408, 1408));\n\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 124, 457, 0, 0, 1408));\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 124, 457, 0, 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 124, 457, 0, 2 * 1408, 1408));\n    }\n\n    @Test\n    void shouldOnlyDropInMatchingFrames()\n    {\n        final FixedLossGenerator fixedLossGenerator = new FixedLossGenerator(1, 2000, 1408);\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 0, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 2 * 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 0, 3 * 1408, 1408));\n\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 1, 0, 1408));\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 1, 1408, 1408));\n        assertTrue(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 1, 2 * 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 1, 3 * 1408, 1408));\n\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 2, 0, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 2, 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 2, 2 * 1408, 1408));\n        assertFalse(fixedLossGenerator.shouldDropFrame(null, null, 123, 456, 2, 3 * 1408, 1408));\n    }\n}"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/ext/MultiGapLossGeneratorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.ext;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass MultiGapLossGeneratorTest\n{\n    @Test\n    void singleSmallGap()\n    {\n        final MultiGapLossGenerator generator = new MultiGapLossGenerator(0, 8, 4, 1);\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 0, 8));\n        assertTrue(generator.shouldDropFrame(null, null, 123, 456, 0, 8, 8));\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 16, 8));\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 24, 8));\n\n        assertFalse(generator.shouldDropFrame(null, null, 124, 456, 0, 0, 8));\n        assertTrue(generator.shouldDropFrame(null, null, 124, 456, 0, 8, 8));\n        assertFalse(generator.shouldDropFrame(null, null, 124, 456, 0, 16, 8));\n        assertFalse(generator.shouldDropFrame(null, null, 124, 456, 0, 24, 8));\n    }\n\n    @Test\n    void singleSmallGapRX()\n    {\n        final MultiGapLossGenerator generator = new MultiGapLossGenerator(0, 8, 4, 1);\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 0, 8));\n        assertTrue(generator.shouldDropFrame(null, null, 123, 456, 0, 8, 8));\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 16, 8));\n\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 8, 8));\n    }\n\n    @Test\n    void multiSmallGap()\n    {\n        final MultiGapLossGenerator generator = new MultiGapLossGenerator(0, 16, 8, 4);\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 0, 8));\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 8, 8));\n        assertTrue(generator.shouldDropFrame(null, null, 123, 456, 0, 16, 8));\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 24, 8));\n        assertTrue(generator.shouldDropFrame(null, null, 123, 456, 0, 32, 8));\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 40, 8));\n        assertTrue(generator.shouldDropFrame(null, null, 123, 456, 0, 48, 4));\n        assertTrue(generator.shouldDropFrame(null, null, 123, 456, 0, 52, 8));\n        assertFalse(generator.shouldDropFrame(null, null, 123, 456, 0, 60, 2));\n        assertTrue(generator.shouldDropFrame(null, null, 123, 456, 0, 62, 10));\n    }\n}"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/media/NamedInterfaceTest.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.NullAndEmptySource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\nclass NamedInterfaceTest\n{\n    @ParameterizedTest\n    @NullAndEmptySource\n    @ValueSource(strings = {\n        \" \",\n        \"eth0\",\n        \"eth0:0\",\n        \"{eth0\",\n        \"{eth0}:\",\n        \"{eth0}0\",\n        \"{eth0}:-1000\",\n        \"{eth0};1000\",\n        \"{eth0}:1000:2000\",\n        \"{eth0}:65536\",\n        \"{}\",\n    })\n    void shouldThrowIfParseInputIsInvalid(final String invalid)\n    {\n        assertThrows(IllegalArgumentException.class, () -> NamedInterface.parse(invalid));\n    }\n\n    static Stream<Arguments> validInputs()\n    {\n        return Stream.of(\n            arguments(\"{eth0}\", \"eth0\", 0),\n            arguments(\"{eth0}:0\", \"eth0\", 0),\n            arguments(\"{eth0}:1234\", \"eth0\", 1234),\n            arguments(\"{eth0:0}:0\", \"eth0:0\", 0),\n            arguments(\"{eth0:1}:2000\", \"eth0:1\", 2000),\n            arguments(\"{eth0.100}:80\", \"eth0.100\", 80),\n            arguments(\"{x}:5678\", \"x\", 5678),\n            arguments(\"{lo}:65535\", \"lo\", 65535),\n            arguments(\"{Ethernet 1}\", \"Ethernet 1\", 0),\n            arguments(\"{ethernet_32768}\", \"ethernet_32768\", 0),\n            arguments(\"{foo{bar-baz-12345}}:9999\", \"foo{bar-baz-12345}\", 9999)\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"validInputs\")\n    void shouldParseValidInputs(final String str, final String name, final int port)\n    {\n        final NamedInterface namedInterface = NamedInterface.parse(str);\n        assertEquals(name, namedInterface.name());\n        assertEquals(port, namedInterface.port());\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/media/NetworkUtilTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.net.*;\nimport java.util.*;\n\nimport static io.aeron.driver.media.NetworkUtil.*;\nimport static java.lang.Short.parseShort;\nimport static java.net.InetAddress.getByName;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.sameInstance;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass NetworkUtilTest\n{\n    @Test\n    void shouldNotMatchIfLengthsAreDifferent()\n    {\n        assertFalse(isMatchWithPrefix(new byte[0], new byte[3], 0));\n        assertFalse(isMatchWithPrefix(new byte[1], new byte[2], 0));\n        assertFalse(isMatchWithPrefix(new byte[5], new byte[5000], 0));\n    }\n\n    @Test\n    void shouldMatchIfAllBytesMatch()\n    {\n        final byte[] a = { 'a', 'b', 'c', 'd' };\n        final byte[] b = { 'a', 'b', 'c', 'd' };\n        assertTrue(isMatchWithPrefix(a, b, 32));\n    }\n\n    @Test\n    void shouldMatchIfAllBytesWithPrefixMatch()\n    {\n        final byte[] a = { 'a', 'b', 'c', 'd' };\n        final byte[] b = { 'a', 'b', 'c', 'e' };\n        assertTrue(isMatchWithPrefix(a, b, 24));\n    }\n\n    @Test\n    void shouldNotMatchIfNotAllBytesWithPrefixMatch()\n    {\n        final byte[] a = { 'a', 'b', 'c', 'd' };\n        final byte[] b = { 'a', 'b', 'd', 'd' };\n        assertFalse(isMatchWithPrefix(a, b, 24));\n    }\n\n    @Test\n    void shouldMatchIfAllBytesWithPrefixUnalignedMatch()\n    {\n        assertTrue(isMatchWithPrefix(\n            asBytes(0b10101010_11111111_00000000_00000000),\n            asBytes(0b10101010_11111110_00000000_00000000),\n            15));\n    }\n\n    @Test\n    void shouldNotMatchIfNotAllBytesWithUnalignedPrefixMatch()\n    {\n        assertFalse(isMatchWithPrefix(\n            asBytes(0b10101010_11111111_00000000_00000000),\n            asBytes(0b10101010_11111111_10000000_00000000),\n            17));\n    }\n\n    @Test\n    void shouldFilterBySubnetAndFindOneResult() throws Exception\n    {\n        final NetworkInterfaceStub stub = new NetworkInterfaceStub();\n\n        final NetworkInterface ifc1 = stub.add(\"192.168.0.1/24\");\n        stub.add(\"10.0.0.2/8\");\n\n        final NetworkInterface[] filteredBySubnet = filterBySubnet(stub, getByName(\"192.168.0.0\"), 24);\n\n        assertEquals(1, filteredBySubnet.length);\n        assertEquals(ifc1, filteredBySubnet[0]);\n    }\n\n    @Test\n    void shouldFilterBySubnetAndFindNoResults() throws Exception\n    {\n        final NetworkInterfaceStub stub = new NetworkInterfaceStub();\n\n        stub.add(\"192.168.0.1/24\");\n        stub.add(\"10.0.0.2/8\");\n\n        final NetworkInterface[] filteredBySubnet = filterBySubnet(stub, getByName(\"192.169.0.0\"), 24);\n\n        assertEquals(0, filteredBySubnet.length);\n    }\n\n    @Test\n    void shouldFilterBySubnetAndFindMultipleResultsOrderedByMatchLength() throws Exception\n    {\n        final NetworkInterfaceStub stub = new NetworkInterfaceStub();\n\n        stub.add(\"10.0.0.2/8\");\n        final NetworkInterface ifc1 = stub.add(\"192.0.0.0/8\");\n        final NetworkInterface ifc2 = stub.add(\"192.168.1.1/24\");\n        final NetworkInterface ifc3 = stub.add(\"192.168.0.0/16\");\n\n        final NetworkInterface[] filteredBySubnet = filterBySubnet(stub, getByName(\"192.0.0.0\"), 8);\n\n        assertEquals(3, filteredBySubnet.length);\n        assertThat(filteredBySubnet[0], sameInstance(ifc2));\n        assertThat(filteredBySubnet[1], sameInstance(ifc3));\n        assertThat(filteredBySubnet[2], sameInstance(ifc1));\n    }\n\n    @Test\n    void shouldFilterBySubnetAndFindOneIpV6Result() throws Exception\n    {\n        final NetworkInterfaceStub stub = new NetworkInterfaceStub();\n\n        final NetworkInterface ifc1 = stub.add(\"fe80:0:0:0001:0002:0:0:1/80\");\n        stub.add(\"fe80:0:0:0002:0003:0:0:1/80\");\n\n        final NetworkInterface[] filteredBySubnet = filterBySubnet(stub, getByName(\"fe80:0:0:0001:0002:0:0:0\"), 80);\n\n        assertEquals(1, filteredBySubnet.length);\n        assertEquals(ifc1, filteredBySubnet[0]);\n    }\n\n    @Test\n    void shouldFilterBySubnetAndFindNoIpV6Results() throws Exception\n    {\n        final NetworkInterfaceStub stub = new NetworkInterfaceStub();\n\n        stub.add(\"fe80:0:0:0001:0:0:0:1/64\");\n        stub.add(\"fe80:0:0:0002:0:0:0:1/64\");\n\n        final NetworkInterface[] filteredBySubnet = filterBySubnet(stub, getByName(\"fe80:0:0:0004:0:0:0:0\"), 64);\n\n        assertEquals(0, filteredBySubnet.length);\n    }\n\n    @Test\n    void shouldFilterBySubnetAndFindMultipleIpV6ResultsOrderedByMatchLength() throws Exception\n    {\n        final NetworkInterfaceStub stub = new NetworkInterfaceStub();\n\n        stub.add(\"ee80:0:0:0001:0:0:0:1/64\");\n        final NetworkInterface ifc1 = stub.add(\"fe80:0:0:0:0:0:0:1/16\");\n        final NetworkInterface ifc2 = stub.add(\"fe80:0001:0:0:0:0:0:1/32\");\n        final NetworkInterface ifc3 = stub.add(\"fe80:0001:abcd:0:0:0:0:1/48\");\n\n        final NetworkInterface[] filteredBySubnet = filterBySubnet(stub, getByName(\"fe80:0:0:0:0:0:0:0\"), 16);\n\n        assertEquals(3, filteredBySubnet.length);\n        assertThat(filteredBySubnet[0], sameInstance(ifc3));\n        assertThat(filteredBySubnet[1], sameInstance(ifc2));\n        assertThat(filteredBySubnet[2], sameInstance(ifc1));\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"0,0\",\n        \"1,0x80000000\",\n        \"2,0xC0000000\",\n        \"3,0xE0000000\",\n        \"4,0xF0000000\",\n        \"5,0xF8000000\",\n        \"6,0xFC000000\",\n        \"7,0xFE000000\",\n        \"8,0xFF000000\",\n        \"9,0xFF800000\",\n        \"10,0xFFC00000\",\n        \"11,0xFFE00000\",\n        \"12,0xFFF00000\",\n        \"13,0xFFF80000\",\n        \"14,0xFFFC0000\",\n        \"15,0xFFFE0000\",\n        \"16,0xFFFF0000\",\n        \"17,0xFFFF8000\",\n        \"18,0xFFFFC000\",\n        \"19,0xFFFFE000\",\n        \"20,0xFFFFF000\",\n        \"21,0xFFFFF800\",\n        \"22,0xFFFFFC00\",\n        \"23,0xFFFFFE00\",\n        \"24,0xFFFFFF00\",\n        \"25,0xFFFFFF80\",\n        \"26,0xFFFFFFC0\",\n        \"27,0xFFFFFFE0\",\n        \"28,0xFFFFFFF0\",\n        \"29,0xFFFFFFF8\",\n        \"30,0xFFFFFFFC\",\n        \"31,0xFFFFFFFE\",\n        \"32,0xFFFFFFFF\",\n    })\n    void shouldConvertSubnetPrefixIpV4PrefixToMask(final int subnetPrefix, final long expectedMask)\n    {\n        assertEquals(expectedMask + Integer.MIN_VALUE, prefixLengthToIpV4Mask(subnetPrefix) + Integer.MIN_VALUE);\n    }\n\n    static class NetworkInterfaceStub implements NetworkInterfaceShim\n    {\n        private int counter = 0;\n\n        private final IdentityHashMap<NetworkInterface, List<InterfaceAddress>> addressesByInterface =\n            new IdentityHashMap<>();\n\n        public Enumeration<NetworkInterface> getNetworkInterfaces()\n        {\n            return Collections.enumeration(addressesByInterface.keySet());\n        }\n\n        public List<InterfaceAddress> getInterfaceAddresses(final NetworkInterface ifc)\n        {\n            return addressesByInterface.get(ifc);\n        }\n\n        public boolean isLoopback(final NetworkInterface ifc)\n        {\n            return false;\n        }\n\n        public NetworkInterface add(final String... ips) throws UnknownHostException\n        {\n            final List<InterfaceAddress> ias = new ArrayList<>();\n            for (final String ip : ips)\n            {\n                final String[] parts = ip.split(\"/\");\n                ias.add(newInterfaceAddress(getByName(parts[0]), parseShort(parts[1])));\n            }\n\n            final NetworkInterface ifc = newNetworkInterface(String.valueOf(counter++));\n            addressesByInterface.put(ifc, ias);\n\n            return ifc;\n        }\n    }\n\n    private static NetworkInterface newNetworkInterface(final String name)\n    {\n        final NetworkInterface networkInterface = mock(NetworkInterface.class);\n\n        when(networkInterface.getName()).thenReturn(name);\n\n        return networkInterface;\n    }\n\n    private static InterfaceAddress newInterfaceAddress(final InetAddress inetAddress, final short maskLength)\n    {\n        final InterfaceAddress interfaceAddress = mock(InterfaceAddress.class);\n\n        when(interfaceAddress.getAddress()).thenReturn(inetAddress);\n        when(interfaceAddress.getNetworkPrefixLength()).thenReturn(maskLength);\n\n        return interfaceAddress;\n    }\n\n    private static byte[] asBytes(final int i)\n    {\n        final byte[] bs = new byte[4];\n        bs[0] = (byte)((i >> 24) & 0xFF);\n        bs[1] = (byte)((i >> 16) & 0xFF);\n        bs[2] = (byte)((i >> 8) & 0xFF);\n        bs[3] = (byte)(i & 0xFF);\n\n        return bs;\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/media/SocketAddressParserTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport io.aeron.driver.DefaultNameResolver;\nimport io.aeron.driver.NameResolver;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.UnknownHostException;\n\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass SocketAddressParserTest\n{\n    static final DefaultNameResolver DEFAULT_RESOLVER = DefaultNameResolver.INSTANCE;\n\n    static final String LOOKUP_RESOLVER_NAME = \"StreamX\";\n    static final String LOOKUP_RESOLVER_HOSTNAME = \"192.168.1.20\";\n    static final int LOOKUP_RESOLVER_PORT = 55;\n    static final NameResolver LOOKUP_RESOLVER = new NameResolver()\n    {\n        public InetAddress resolve(final String name, final String uriParamName, final boolean isReResolution)\n        {\n            return DEFAULT_RESOLVER.resolve(name, uriParamName, isReResolution);\n        }\n\n        public String lookup(final String name, final String uriParamName, final boolean isReLookup)\n        {\n            if (name.equals(LOOKUP_RESOLVER_NAME))\n            {\n                return LOOKUP_RESOLVER_HOSTNAME + \":\" + LOOKUP_RESOLVER_PORT;\n            }\n\n            return name;\n        }\n    };\n\n    @Test\n    void shouldParseIpV4AddressAndPort() throws Exception\n    {\n        assertCorrectParse(\"192.168.1.20\", 55);\n    }\n\n    @Test\n    void shouldParseHostAddressAndPort() throws Exception\n    {\n        assertCorrectParse(\"localhost\", 55);\n    }\n\n    @Test\n    void shouldRejectOnInvalidPort()\n    {\n        assertThrows(IllegalArgumentException.class, () -> SocketAddressParser.parse(\n            \"192.168.1.20:aa\", ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER));\n    }\n\n    @Test\n    void shouldRejectOnInvalidPort2()\n    {\n        assertThrows(IllegalArgumentException.class, () -> SocketAddressParser.parse(\n            \"192.168.1.20::123\", ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER));\n    }\n\n    @Test\n    void shouldRejectOnMissingPort()\n    {\n        assertThrows(IllegalArgumentException.class, () -> SocketAddressParser.parse(\n            \"192.168.1.20\", ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER));\n    }\n\n    @Test\n    void shouldRejectOnEmptyPort()\n    {\n        assertThrows(IllegalArgumentException.class, () -> SocketAddressParser.parse(\n            \"192.168.1.20:\", ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER));\n    }\n\n    @Test\n    void shouldRejectOnEmptyIpV6Port()\n    {\n        assertThrows(IllegalArgumentException.class, () -> SocketAddressParser.parse(\n            \"[::1]:\", ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER));\n    }\n\n    @Test\n    void shouldRejectOnInvalidIpV6()\n    {\n        assertThrows(IllegalArgumentException.class, () -> SocketAddressParser.parse(\n            \"[FG07::789:1:0:0:3]:111\", ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER));\n    }\n\n    @Test\n    void shouldRejectOnInvalidIpV6Scope()\n    {\n        assertThrows(IllegalArgumentException.class, () -> SocketAddressParser.parse(\n            \"[FC07::789:1:0:0:3%^]:111\", ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER));\n    }\n\n    @Test\n    void shouldParseIpV6() throws Exception\n    {\n        assertCorrectParseIpV6(\"::1\", 54321);\n        assertCorrectParseIpV6(\"FC07::789:1:0:0:3\", 54321);\n        assertCorrectParseIpV6(\"fc07::789:1:0:0:3\", 54321);\n    }\n\n    @Test\n    void shouldParseWithScope()\n    {\n        final InetSocketAddress address = SocketAddressParser.parse(\n            \"[::1%12~_.-34]:1234\", ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER);\n        assertThat(address.getAddress(), instanceOf(Inet6Address.class));\n    }\n\n    @Test\n    void shouldParseAndLookupResolverForName() throws UnknownHostException\n    {\n        final InetSocketAddress socketAddress = SocketAddressParser.parse(\n            LOOKUP_RESOLVER_NAME, ENDPOINT_PARAM_NAME, false, LOOKUP_RESOLVER);\n        assertEquals(InetAddress.getByName(LOOKUP_RESOLVER_HOSTNAME), socketAddress.getAddress());\n        assertEquals(LOOKUP_RESOLVER_PORT, socketAddress.getPort());\n    }\n\n    @Test\n    void shouldParseAndPassThroughLookupForUnknownName() throws UnknownHostException\n    {\n        final InetSocketAddress socketAddress = SocketAddressParser.parse(\n            LOOKUP_RESOLVER_HOSTNAME + \":\" + LOOKUP_RESOLVER_PORT, ENDPOINT_PARAM_NAME, false, LOOKUP_RESOLVER);\n        assertEquals(InetAddress.getByName(LOOKUP_RESOLVER_HOSTNAME), socketAddress.getAddress());\n        assertEquals(LOOKUP_RESOLVER_PORT, socketAddress.getPort());\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"127.0.0.1:8080, false\",\n        \"224.0.0.0:24, true\",\n        \"224.0.0.255:44444, true\",\n        \"239.255.255.250:1010, true\",\n        \"239.1.1.1:1111, true\",\n        \"233.251.255.255:5555, true\",\n        \"999.255.255.250:7777, false\",\n        \"a.b.c.d:7777, false\",\n        \"192.b.c.d:7777, false\",\n        \"192.168.0.1:1234, false\",\n        \"test:1234, false\",\n        \"[ff02::1]:1234, true\",\n        \"[Ff05::1:3]:80, true\",\n        \"[fF9E::108]:5555, true\",\n        \"[FF01:0:0:0:0:0:0:18C]:5555, true\",\n        \"[fe80::ce81:b1c:bd2c:69e%utun3]:8080, false\",\n        \"[fe80::1%lo0]:5555, false\",\n        \"[ff02:0:0:0:0:2]:7777, true\"\n    })\n    void shouldCheckForMulticastAddress(final String hostAndPort, final boolean expected)\n    {\n        assertEquals(expected, SocketAddressParser.isMulticastAddress(hostAndPort));\n    }\n\n    @Test\n    void shouldThrowNullPointerExceptionIfValueIsNull()\n    {\n        assertThrowsExactly(NullPointerException.class, () -> SocketAddressParser.isMulticastAddress(null));\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"\", \"a\", \"1.2.3\", \"[x]\", \"[f0:xx]:5\" })\n    void shouldThrowIllegalArgumentExceptionIfValueIsInvalid(final String hostAndPort)\n    {\n        final IllegalArgumentException exception = assertThrowsExactly(\n            IllegalArgumentException.class,\n            () -> SocketAddressParser.isMulticastAddress(hostAndPort));\n        assertThat(exception.getMessage(), containsString(hostAndPort));\n    }\n\n    private void assertCorrectParse(final String host, final int port) throws UnknownHostException\n    {\n        final InetSocketAddress address = SocketAddressParser.parse(\n            host + \":\" + port, ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER);\n        assertEquals(InetAddress.getByName(host), address.getAddress());\n        assertEquals(port, address.getPort());\n    }\n\n    private void assertCorrectParseIpV6(final String host, final int port) throws UnknownHostException\n    {\n        final InetSocketAddress address = SocketAddressParser.parse(\n            \"[\" + host + \"]:\" + port, ENDPOINT_PARAM_NAME, false, DEFAULT_RESOLVER);\n        assertEquals(InetAddress.getByName(host), address.getAddress());\n        assertEquals(port, address.getPort());\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/media/UnresolvedInterfaceTest.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass UnresolvedInterfaceTest\n{\n    @Test\n    void shouldParseValidInterfaceSpecifications() throws Exception\n    {\n        assertInstanceOf(InterfaceSearchAddress.class, UnresolvedInterface.parse(\"localhost\"));\n        assertInstanceOf(InterfaceSearchAddress.class, UnresolvedInterface.parse(\"10.0.0.1\"));\n        assertInstanceOf(InterfaceSearchAddress.class, UnresolvedInterface.parse(\"[2001:db8::1]\"));\n        assertInstanceOf(NamedInterface.class, UnresolvedInterface.parse(\"{lo}\"));\n    }\n\n    @Test\n    void shouldThrowOnInvalidInterfaceSpecifications()\n    {\n        assertThrows(IllegalArgumentException.class, () -> UnresolvedInterface.parse(null));\n        assertThrows(IllegalArgumentException.class, () -> UnresolvedInterface.parse(\"\"));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/media/WildcardPortManagerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.media;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.net.BindException;\nimport java.net.InetSocketAddress;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.Is.is;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass WildcardPortManagerTest\n{\n    final int[] portRange = new int[]{ 20000, 20003};\n    final UdpChannel udpChannelPort0 = UdpChannel.parse(\"aeron:udp?endpoint=localhost:0\");\n    final UdpChannel udpChannelPubControl = UdpChannel.parse(\"aeron:udp?control=localhost:0|endpoint=localhost:9999\");\n\n    @Test\n    void shouldAllocateConsecutivePortsInRange() throws BindException\n    {\n        final WildcardPortManager manager = new WildcardPortManager(portRange, false);\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 0);\n\n        assertThat(manager.getManagedPort(\n            udpChannelPort0, bindAddress), is(new InetSocketAddress(\"localhost\", 20000)));\n        assertThat(manager.getManagedPort(\n            udpChannelPort0, bindAddress), is(new InetSocketAddress(\"localhost\", 20001)));\n        assertThat(manager.getManagedPort(\n            udpChannelPort0, bindAddress), is(new InetSocketAddress(\"localhost\", 20002)));\n    }\n\n    @Test\n    void shouldPassThrough0WithNullRanges() throws BindException\n    {\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 0);\n        final WildcardPortManager manager = new WildcardPortManager(WildcardPortManager.EMPTY_PORT_RANGE, false);\n\n        assertThat(manager.getManagedPort(\n            udpChannelPort0, bindAddress), is(new InetSocketAddress(\"localhost\", 0)));\n    }\n\n    @Test\n    void shouldPassThrough0WithSenderPubWithoutControlAddress() throws BindException\n    {\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 0);\n        final WildcardPortManager manager = new WildcardPortManager(portRange, true);\n\n        assertThat(manager.getManagedPort(\n            udpChannelPort0, bindAddress), is(new InetSocketAddress(\"localhost\", 0)));\n    }\n\n    @Test\n    void shouldPassThroughWithExplicitBindAddressOutSideRange() throws BindException\n    {\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 1000);\n        final WildcardPortManager manager = new WildcardPortManager(portRange, true);\n\n        assertThat(manager.getManagedPort(\n            udpChannelPort0, bindAddress), is(new InetSocketAddress(\"localhost\", 1000)));\n    }\n\n    @Test\n    void shouldPassThroughWithExplicitBindAddressInsideRange() throws BindException\n    {\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 20003);\n        final WildcardPortManager manager = new WildcardPortManager(portRange, true);\n\n        assertThat(manager.getManagedPort(\n            udpChannelPort0, bindAddress), is(new InetSocketAddress(\"localhost\", 20003)));\n    }\n\n    @Test\n    void shouldAllocateForPubWithExplicitControlAddress() throws BindException\n    {\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 0);\n        final WildcardPortManager manager = new WildcardPortManager(portRange, true);\n\n        assertThat(manager.getManagedPort(\n            udpChannelPubControl, bindAddress), is(new InetSocketAddress(\"localhost\", 20000)));\n    }\n\n    @Test\n    void shouldThrowOnExhaustion() throws BindException\n    {\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 0);\n        final WildcardPortManager manager = new WildcardPortManager(portRange, false);\n\n        manager.getManagedPort(udpChannelPort0, bindAddress);\n        manager.getManagedPort(udpChannelPort0, bindAddress);\n        manager.getManagedPort(udpChannelPort0, bindAddress);\n        manager.getManagedPort(udpChannelPort0, bindAddress);\n        assertThrows(BindException.class, () -> manager.getManagedPort(udpChannelPort0, bindAddress));\n    }\n\n    @Test\n    void shouldAllocateOnCyclingThroughRange() throws BindException\n    {\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 0);\n        final WildcardPortManager manager = new WildcardPortManager(portRange, false);\n\n        manager.getManagedPort(udpChannelPort0, bindAddress);\n        manager.getManagedPort(udpChannelPort0, bindAddress);\n        manager.getManagedPort(udpChannelPort0, bindAddress);\n        manager.getManagedPort(udpChannelPort0, bindAddress);\n        manager.freeManagedPort(new InetSocketAddress(\"localhost\", 20000));\n        assertThat(manager.getManagedPort(\n            udpChannelPubControl, bindAddress), is(new InetSocketAddress(\"localhost\", 20000)));\n    }\n\n    @Test\n    void shouldAllocateSkippingInUsePort() throws BindException\n    {\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 0);\n        final WildcardPortManager manager = new WildcardPortManager(portRange, false);\n\n        manager.getManagedPort(udpChannelPort0, bindAddress);\n        manager.getManagedPort(udpChannelPort0, new InetSocketAddress(\"localhost\", 20001));\n        assertThat(manager.getManagedPort(\n            udpChannelPubControl, bindAddress), is(new InetSocketAddress(\"localhost\", 20002)));\n    }\n\n    @Test\n    void shouldAllocateSkippingInUseOnCycle() throws BindException\n    {\n        final InetSocketAddress bindAddress = new InetSocketAddress(\"localhost\", 0);\n        final WildcardPortManager manager = new WildcardPortManager(portRange, false);\n\n        manager.getManagedPort(udpChannelPort0, new InetSocketAddress(\"localhost\", 20000));\n        manager.getManagedPort(udpChannelPort0, new InetSocketAddress(\"localhost\", 20001));\n        manager.getManagedPort(udpChannelPort0, new InetSocketAddress(\"localhost\", 20002));\n        manager.freeManagedPort(new InetSocketAddress(\"localhost\", 20002));\n        manager.getManagedPort(udpChannelPort0, new InetSocketAddress(\"localhost\", 20003));\n        assertThat(manager.getManagedPort(\n            udpChannelPubControl, bindAddress), is(new InetSocketAddress(\"localhost\", 20002)));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/reports/LossReportReaderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.reports;\n\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\n\nimport java.nio.ByteBuffer;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\nclass LossReportReaderTest\n{\n    private static final int CAPACITY = 1024;\n    private final AtomicBuffer buffer = new UnsafeBuffer(ByteBuffer.allocateDirect(CAPACITY));\n    private final LossReport lossReport = new LossReport(buffer);\n    private final LossReportReader.EntryConsumer entryConsumer = mock(LossReportReader.EntryConsumer.class);\n\n    @Test\n    void shouldReadNoEntriesInEmptyReport()\n    {\n        assertEquals(0, LossReportReader.read(buffer, entryConsumer));\n\n        verifyNoInteractions(entryConsumer);\n    }\n\n    @Test\n    void shouldReadOneEntry()\n    {\n        final long initialBytesLost = 32;\n        final int timestampMs = 7;\n        final int sessionId = 3;\n        final int streamId = 1;\n        final String channel = \"aeron:udp://stuff\";\n        final String source = \"127.0.0.1:8888\";\n\n        lossReport.createEntry(initialBytesLost, timestampMs, sessionId, streamId, channel, source);\n\n        assertEquals(1, LossReportReader.read(buffer, entryConsumer));\n\n        verify(entryConsumer).accept(\n            1L, initialBytesLost, timestampMs, timestampMs, sessionId, streamId, channel, source);\n\n        verifyNoMoreInteractions(entryConsumer);\n    }\n\n    @Test\n    void shouldReadTwoEntries()\n    {\n        final long initialBytesLostOne = 32;\n        final int timestampMsOne = 7;\n        final int sessionIdOne = 3;\n        final int streamIdOne = 1;\n        final String channelOne = \"aeron:udp://stuffOne\";\n        final String sourceOne = \"127.0.0.1:8888\";\n\n        final long initialBytesLostTwo = 48;\n        final int timestampMsTwo = 17;\n        final int sessionIdTwo = 13;\n        final int streamIdTwo = 11;\n        final String channelTwo = \"aeron:udp://stuffTwo\";\n        final String sourceTwo = \"127.0.0.1:9999\";\n\n        lossReport.createEntry(initialBytesLostOne, timestampMsOne, sessionIdOne, streamIdOne, channelOne, sourceOne);\n        lossReport.createEntry(initialBytesLostTwo, timestampMsTwo, sessionIdTwo, streamIdTwo, channelTwo, sourceTwo);\n\n        assertEquals(2, LossReportReader.read(buffer, entryConsumer));\n\n        final InOrder inOrder = inOrder(entryConsumer);\n        inOrder.verify(entryConsumer).accept(\n            1L, initialBytesLostOne, timestampMsOne, timestampMsOne, sessionIdOne, streamIdOne, channelOne, sourceOne);\n        inOrder.verify(entryConsumer).accept(\n            1L, initialBytesLostTwo, timestampMsTwo, timestampMsTwo, sessionIdTwo, streamIdTwo, channelTwo, sourceTwo);\n\n        verifyNoMoreInteractions(entryConsumer);\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/reports/LossReportTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.reports;\n\nimport org.agrona.BitUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.driver.reports.LossReport.*;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.mockito.Mockito.*;\n\nclass LossReportTest\n{\n    private static final int CAPACITY = 1024;\n    private final UnsafeBuffer unsafeBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(CAPACITY));\n    private final AtomicBuffer buffer = spy(unsafeBuffer);\n    private final LossReport lossReport = new LossReport(buffer);\n\n    @Test\n    void shouldCreateEntry()\n    {\n        final long initialBytesLost = 32;\n        final int timestampMs = 7;\n        final int sessionId = 3;\n        final int streamId = 1;\n        final String channel = \"aeron:udp://stuff\";\n        final String source = \"127.0.0.1:8888\";\n\n        assertNotNull(lossReport.createEntry(initialBytesLost, timestampMs, sessionId, streamId, channel, source));\n\n        final InOrder inOrder = inOrder(buffer);\n        inOrder.verify(buffer).putLong(TOTAL_BYTES_LOST_OFFSET, initialBytesLost);\n        inOrder.verify(buffer).putLong(FIRST_OBSERVATION_OFFSET, timestampMs);\n        inOrder.verify(buffer).putLong(LAST_OBSERVATION_OFFSET, timestampMs);\n        inOrder.verify(buffer).putInt(SESSION_ID_OFFSET, sessionId);\n        inOrder.verify(buffer).putInt(STREAM_ID_OFFSET, streamId);\n        inOrder.verify(buffer).putStringAscii(CHANNEL_OFFSET, channel);\n        inOrder.verify(buffer).putStringAscii(\n            CHANNEL_OFFSET + BitUtil.align(SIZE_OF_INT + channel.length(), SIZE_OF_INT), source);\n        inOrder.verify(buffer).putLongRelease(OBSERVATION_COUNT_OFFSET, 1L);\n    }\n\n    @Test\n    void shouldUpdateEntry()\n    {\n        final long initialBytesLost = 32;\n        final int timestampMs = 7;\n        final int sessionId = 3;\n        final int streamId = 1;\n        final String channel = \"aeron:udp://stuff\";\n        final String source = \"127.0.0.1:8888\";\n\n        final ReportEntry entry = lossReport.createEntry(\n            initialBytesLost, timestampMs, sessionId, streamId, channel, source);\n\n        final long additionBytesLost = 64;\n        final long latestTimestamp = 10;\n        entry.recordObservation(additionBytesLost, latestTimestamp);\n\n        assertEquals(latestTimestamp, unsafeBuffer.getLong(LAST_OBSERVATION_OFFSET));\n        assertEquals(initialBytesLost + additionBytesLost, unsafeBuffer.getLong(TOTAL_BYTES_LOST_OFFSET));\n        assertEquals(2L, unsafeBuffer.getLong(OBSERVATION_COUNT_OFFSET));\n    }\n}\n"
  },
  {
    "path": "aeron-driver/src/test/java/io/aeron/driver/status/DutyCycleStallTrackerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver.status;\n\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.mockito.Mockito.*;\n\nclass DutyCycleStallTrackerTest\n{\n    @Test\n    void throwsNullPointerExceptionIfMaxTimeCounterIsNull()\n    {\n        assertThrowsExactly(\n            NullPointerException.class,\n            () -> new DutyCycleStallTracker(null, mock(AtomicCounter.class), 1));\n    }\n\n    @Test\n    void throwsNullPointerExceptionIfThresholdCounterIsNull()\n    {\n        assertThrowsExactly(\n            NullPointerException.class,\n            () -> new DutyCycleStallTracker(mock(AtomicCounter.class), null, 1));\n    }\n\n    @Test\n    void reportMeasurementIsANoOpIfMaxCounterIsClosed()\n    {\n        final AtomicCounter maxCycleTime = mock(AtomicCounter.class);\n        when(maxCycleTime.isClosed()).thenReturn(true);\n        final AtomicCounter cycleTimeThresholdExceededCount = mock(AtomicCounter.class);\n        final DutyCycleStallTracker dutyCycleStallTracker =\n            new DutyCycleStallTracker(maxCycleTime, cycleTimeThresholdExceededCount, 1);\n\n        dutyCycleStallTracker.reportMeasurement(555);\n\n        verify(maxCycleTime).isClosed();\n        verifyNoMoreInteractions(maxCycleTime);\n        verifyNoInteractions(cycleTimeThresholdExceededCount);\n    }\n\n    @Test\n    void reportMeasurementOnlyUpdatesThresholdCounterWhenExceeded()\n    {\n        final AtomicCounter maxCycleTime = mock(AtomicCounter.class);\n        when(maxCycleTime.isClosed()).thenReturn(false);\n        final AtomicCounter cycleTimeThresholdExceededCount = mock(AtomicCounter.class);\n        final int cycleTimeThresholdNs = 1000;\n        final DutyCycleStallTracker dutyCycleStallTracker =\n            new DutyCycleStallTracker(maxCycleTime, cycleTimeThresholdExceededCount, cycleTimeThresholdNs);\n\n        dutyCycleStallTracker.reportMeasurement(555);\n        dutyCycleStallTracker.reportMeasurement(1000);\n        dutyCycleStallTracker.reportMeasurement(1001);\n\n        verify(maxCycleTime, times(3)).isClosed();\n        verify(maxCycleTime).proposeMaxRelease(555L);\n        verify(maxCycleTime).proposeMaxRelease(1000L);\n        verify(maxCycleTime).proposeMaxRelease(1001L);\n        verify(cycleTimeThresholdExceededCount).incrementRelease();\n        verifyNoMoreInteractions(maxCycleTime, cycleTimeThresholdExceededCount);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/README.md",
    "content": "# Aeron Samples\n\nHere you will find a collection of samples and tools for Aeron.\n\nEach of the Samples can be run with a simple script that can be found in:\n\n    aeron-samples/scripts/\n\nBefore running any of the samples below the media driver needs to be launched by running one of:\n\n    aeron-samples/scripts/media-driver <optional properties file>\n    aeron-samples/scripts/low-latency-media-driver <optional properties file>\n    \nHere is a brief list of the samples and what each tries to do:\n\n- __BasicSubscriber__: A simple subscriber that prints the contents of messages it receives.\n- __BasicPublisher__: A simple publisher that sends a number of messages with a one-second pause between them.\n- __RateSubscriber__: A subscriber that prints the rate of reception of messages.\n- __StreamingPublisher__: A publisher that streams out messages as fast as possible, displaying rate of publication.\n- __Ping__: Ping side of Ping/Pong latency testing tool.\n- __Pong__: Pong side of Ping/Pong latency testing tool.\n\nHere is a brief list of monitoring and diagnostic tools:\n\n- __AeronStat__: A monitoring tool that prints the labels and values of the counters in use by a media driver.\n- __ErrorStat__: A monitoring tool that prints the distinct errors observed by the media driver.\n- __LossStat__: A Monitoring tool that prints a report of loss recorded by stream.\n- __BacklogStat__: A monitoring tool that prints a report of stream positions to give and indication of backlog for processing on each stream.\n- __LogInspector__: A diagnostic tool that prints out the contents of a log buffer for a given stream for debugging.\n\nAlso included is some performance tests that can run all in the same process for convenience without a media driver,\n or across processes for illustration, with variants for throughput or latency measurement:\n\n- __embedded__: Tests tend to run in the same process.\n- __media__: Variants for IPC using shared memory or UDP via the network.\n\n## Aeron Archive Samples\n\nIn the [archive](https://github.com/aeron-io/aeron/tree/master/aeron-samples/scripts/archive) sub-directory, \nor package, you can find samples for recording and replay of streams from an Archive.\n\n    aeron-samples/scripts/archive/\n"
  },
  {
    "path": "aeron-samples/scripts/aeron-stat",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.AeronStat \"$@\""
  },
  {
    "path": "aeron-samples/scripts/aeron-stat.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.AeronStat %*"
  },
  {
    "path": "aeron-samples/scripts/archive/README.md",
    "content": "# Aeron Archive Samples\n\nThe scripts in this directory can launch the sample applications for the [Aeron Archive](https://github.com/aeron-io/aeron/tree/master/aeron-archive) service with the code samples\n[here](https://github.com/aeron-io/aeron/tree/master/aeron-samples/src/main/java/io/aeron/samples/archive) and\nsystems tests [here](https://github.com/aeron-io/aeron/tree/master/aeron-system-tests/src/test/java/io/aeron/archive).\n\n## Embedded Throughput Samples\n\nSuitable for measuring IO performance of your system. The samples will default to creating\nan archive directory on the temporary file system. It is recommended that a properties file is provided\nby passing it as argument to the script. The `aeron.archive.dir` property should be located on fast storage.\n\n### Recording Throughput\n\n`embedded-recording-throughput`: Will record a number of messages using the `SampleConfiguration` properties then \nask if the test should be repeated. Doing it multiple times allows the system to warm up.\n\nIt is worth trying different levels of write synchronisation for durability.\n\n- `aeron.archive.file.sync.level=0`: for normal writes to the OS page cache for background writing to disk.\n- `aeron.archive.file.sync.level=1`: for forcing the dirty data pages to disk. \n- `aeron.archive.file.sync.level=2`: for forcing the dirty data pages and file metadata to disk. \n\n### Replay Throughput\n\n`embedded-replay-throughput`: Will record a number of messages using the `SampleConfiguration` properties then\nreplay them on a new stream. The test will ask to repeat the replay of record message and doing it multiple times\nallows for the system to warm up.\n\nIt is worth playing with different messages lengths and threading configurations.\n\n## Basic Publication and Subscription to an archived stream\n\n1. Start the archiving media driver in its own console.\n\n```\n    $ archiving-media-driver <config properties file>\n```\n\n2. Start the publisher with its recorded publication in its own console.\n\n```\n    $ recorded-basic-publisher <config properties file>\n```\n    \n\n3. Start a normal subscriber so that the publication connects and can record.\n\n```\n    $ cd ..\n    $ basic-subscriber <config properties file>\n```\n\n4. Start a subscriber that requests replay of the recorded stream.\n\n```\n    $ replay-basic-subscriber <config properties file>\n```\n\n5. Optionally run AeronStat and observe status, look out for the `rec-pos` counter for the recorded stream. \n  \n```\n    $ aeron-stat\n```\n\n6. Check for errors.\n\n```\n    $ error-stat\n```\n\n## ReplayMerge\n\n1. Start the archiving media driver in its own terminal.\n\n```\n./archiving-media-driver\n```\n\n2. Start the MDC publisher with its recorded publication in another terminal.\n\n```\nexport JVM_OPTS=\"-Daeron.sample.channel=aeron:udp?control=localhost:20550|control-mode=dynamic|alias=replay-merge-sample\"\n\n./recorded-basic-publisher\n```\n\n3. Start a basic subscriber so that the publication connects and can record. Make sure `aeron.sample.channel` matches the publication channel.\n```\nexport JVM_OPTS=\"-Daeron.sample.channel=aeron:udp?control=localhost:20550\"\n\n../basic-subscriber\n```\n\n4. Wait a moment, then start a ReplayMerge subscriber, setting `aeron.sample.channel` to the same channel as the publication.\n\n```\nexport JVM_OPTS=\"-Daeron.sample.channel=aeron:udp?control=localhost:20550\"\n\n./replay-merge-subscriber\n```"
  },
  {
    "path": "aeron-samples/scripts/archive/archiving-media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n  -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 \\\n  -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 \\\n  -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 \\\n  io.aeron.archive.ArchivingMediaDriver \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/archive/archiving-media-driver.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 ^\r\n    -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 ^\r\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 ^\r\n    io.aeron.archive.ArchivingMediaDriver %*\r\n\r\n"
  },
  {
    "path": "aeron-samples/scripts/archive/embedded-recording-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.sample.messages=100000000 \\\n    -Daeron.term.buffer.sparse.file=false \\\n    -Daeron.mtu.length=8k \\\n    -Daeron.ipc.mtu.length=8k \\\n    -Daeron.archive.control.mtu.length=4k \\\n    -Daeron.socket.so_sndbuf=2m \\\n    -Daeron.socket.so_rcvbuf=2m \\\n    -Daeron.rcv.initial.window.length=2m \\\n    -Daeron.archive.file.sync.level=0 \\\n    -Daeron.archive.file.io.max.length=1m \\\n    -Daeron.sample.channel=aeron:ipc \\\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 \\\n    -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 \\\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 \\\n    ${JVM_OPTS} io.aeron.samples.archive.EmbeddedRecordingThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/archive/embedded-recording-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.sample.messages=100000000 ^\r\n    -Daeron.term.buffer.sparse.file=false ^\r\n    -Daeron.mtu.length=8k ^\r\n    -Daeron.ipc.mtu.length=8k ^\r\n    -Daeron.archive.control.mtu.length=4k ^\r\n    -Daeron.socket.so_sndbuf=2m ^\r\n    -Daeron.socket.so_rcvbuf=2m ^\r\n    -Daeron.rcv.initial.window.length=2m ^\r\n    -Daeron.archive.file.sync.level=0 ^\r\n    -Daeron.archive.file.io.max.length=1m ^\r\n    -Daeron.sample.channel=aeron:ipc ^\r\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 ^\r\n    -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 ^\r\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 ^\r\n    %JVM_OPTS% io.aeron.samples.archive.EmbeddedRecordingThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/archive/embedded-replay-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.sample.messages=100000000 \\\n    -Daeron.term.buffer.sparse.file=false \\\n    -Daeron.mtu.length=8k \\\n    -Daeron.ipc.mtu.length=8k \\\n    -Daeron.archive.control.mtu.length=4k \\\n    -Daeron.socket.so_sndbuf=2m \\\n    -Daeron.socket.so_rcvbuf=2m \\\n    -Daeron.rcv.initial.window.length=2m \\\n    -Daeron.archive.file.io.max.length=1m \\\n    -Daeron.sample.channel=aeron:ipc \\\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 \\\n    -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 \\\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 \\\n    ${JVM_OPTS} io.aeron.samples.archive.EmbeddedReplayThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/archive/embedded-replay-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.sample.messages=100000000 ^\r\n    -Daeron.term.buffer.sparse.file=false ^\r\n    -Daeron.mtu.length=8k ^\r\n    -Daeron.ipc.mtu.length=8k ^\r\n    -Daeron.archive.control.mtu.length=4k ^\r\n    -Daeron.socket.so_sndbuf=2m ^\r\n    -Daeron.socket.so_rcvbuf=2m ^\r\n    -Daeron.rcv.initial.window.length=2m ^\r\n    -Daeron.archive.file.io.max.length=1m ^\r\n    -Daeron.sample.channel=aeron:ipc ^\r\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 ^\r\n    -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 ^\r\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 ^\r\n    %JVM_OPTS% io.aeron.samples.archive.EmbeddedReplayThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/archive/high-throughput-archive.properties",
    "content": "aeron.archive.dir=../../build/archive\naeron.archive.threading.mode=DEDICATED\naeron.archive.file.sync.level=0\naeron.archive.file.io.max.length=1m\naeron.archive.segment.file.length=1g\naeron.spies.simulate.connection=true\naeron.threading.mode=DEDICATED\naeron.term.buffer.sparse.file=false\naeron.mtu.length=8k\naeron.ipc.mtu.length=8k\naeron.socket.so_sndbuf=2m\naeron.socket.so_rcvbuf=2m\naeron.rcv.initial.window.length=2m\naeron.sender.idle.strategy=noop\naeron.receiver.idle.strategy=noop\naeron.conductor.idle.strategy=spin\nagrona.disable.bounds.checks=true"
  },
  {
    "path": "aeron-samples/scripts/archive/lightweight-archive.properties",
    "content": "aeron.archive.dir=../../build/archive\naeron.archive.threading.mode=SHARED\naeron.archive.file.sync.level=0\naeron.archive.file.io.max.length=1m\naeron.spies.simulate.connection=true\naeron.threading.mode=INVOKER\naeron.term.buffer.sparse.file=true\n"
  },
  {
    "path": "aeron-samples/scripts/archive/logging-archiving-media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java-logging\" \\\n    -Daeron.event.log=all \\\n    -Daeron.event.log=admin \\\n    -Daeron.event.archive.log=all \\\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 \\\n    -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 \\\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 \\\n    ${JVM_OPTS} io.aeron.archive.ArchivingMediaDriver \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/archive/logging-archiving-media-driver.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java-logging\" ^\r\n    -Daeron.event.log=admin ^\r\n    -Daeron.event.archive.log=all ^\r\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 ^\r\n    -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 ^\r\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 ^\r\n    %JVM_OPTS% io.aeron.archive.ArchivingMediaDriver %*\r\n"
  },
  {
    "path": "aeron-samples/scripts/archive/recorded-basic-publisher",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n  -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 \\\n  -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 \\\n  -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 \\\n  io.aeron.samples.archive.RecordedBasicPublisher\n"
  },
  {
    "path": "aeron-samples/scripts/archive/recorded-basic-publisher.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 ^\r\n    -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 ^\r\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 ^\r\n    io.aeron.samples.archive.RecordedBasicPublisher"
  },
  {
    "path": "aeron-samples/scripts/archive/recording-replicator",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n  io.aeron.samples.archive.RecordingReplicator \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/archive/recording-replicator.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    io.aeron.samples.archive.RecordingReplicator %*\r\n"
  },
  {
    "path": "aeron-samples/scripts/archive/replay-merge-subscriber",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2026 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n  -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 \\\n  -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 \\\n  io.aeron.samples.archive.ReplayMergeSubscriber"
  },
  {
    "path": "aeron-samples/scripts/archive/replay-merge-subscriber.cmd",
    "content": "::\r\n:: Copyright 2014-2026 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 ^\r\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 ^\r\n    io.aeron.samples.archive.ReplayMergeSubscriber"
  },
  {
    "path": "aeron-samples/scripts/archive/replayed-basic-subscriber",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n  -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 \\\n  -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 \\\n  -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 \\\n  io.aeron.samples.archive.ReplayedBasicSubscriber\n"
  },
  {
    "path": "aeron-samples/scripts/archive/replayed-basic-subscriber.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Daeron.archive.control.channel=aeron:udp?endpoint=localhost:8010 ^\r\n    -Daeron.archive.replication.channel=aeron:udp?endpoint=localhost:0 ^\r\n    -Daeron.archive.control.response.channel=aeron:udp?endpoint=localhost:0 ^\r\n    io.aeron.samples.archive.ReplayedBasicSubscriber"
  },
  {
    "path": "aeron-samples/scripts/archive/segment-inspector",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" io.aeron.samples.archive.SegmentInspector \"$@\""
  },
  {
    "path": "aeron-samples/scripts/archive/segment-inspector.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" io.aeron.samples.archive.SegmentInspector %*"
  },
  {
    "path": "aeron-samples/scripts/archive/standard-archive.properties",
    "content": "aeron.archive.dir=../../build/archive\naeron.archive.threading.mode=SHARED\naeron.archive.file.sync.level=0\naeron.archive.file.io.max.length=1m\naeron.spies.simulate.connection=true\naeron.threading.mode=SHARED\naeron.term.buffer.sparse.file=true\n"
  },
  {
    "path": "aeron-samples/scripts/backlog-stat",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.BacklogStat"
  },
  {
    "path": "aeron-samples/scripts/backlog-stat.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.BacklogStat"
  },
  {
    "path": "aeron-samples/scripts/basic-publisher",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.BasicPublisher"
  },
  {
    "path": "aeron-samples/scripts/basic-publisher.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.BasicPublisher"
  },
  {
    "path": "aeron-samples/scripts/basic-subscriber",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.BasicSubscriber"
  },
  {
    "path": "aeron-samples/scripts/basic-subscriber.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.BasicSubscriber"
  },
  {
    "path": "aeron-samples/scripts/cluster/agent-ns",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nset -euo pipefail\n\nsource ./script-common\n\nmkdir -p logs\n\nfunction startNode() {\n\n    namespace=ns_node${1}\n\n    sudo ip netns exec ${namespace} su - ${USER} -c \"cd ${PWD} && \\\n    ${JAVA_HOME}/bin/java \\\n        -Xmx16m \\\n        -cp ../../../aeron-all/build/libs/aeron-all-${VERSION}.jar:../../../aeron-test-support/build/libs/aeron-test-support-${VERSION}.jar \\\n        -javaagent:../../../aeron-agent/build/libs/aeron-agent-${VERSION}.jar \\\n        -XX:+UnlockExperimentalVMOptions \\\n        -XX:+TrustFinalNonStaticFields \\\n        -XX:+UnlockDiagnosticVMOptions \\\n        -XX:GuaranteedSafepointInterval=300000 \\\n        -XX:+UseParallelGC \\\n        ${JVM_OPTS:-} ${ADD_OPENS} \\\n        io.aeron.test.launcher.RemoteLaunchServer > logs/launch-${1}.log &\"\n}\n\nfunction startAll() {\n  startNode 0\n  startNode 1\n  startNode 2\n}\n\nif [[ $# -lt 1 ]]; then\n  startAll\nelif [[ ${1} == 0 ]] || [[ ${1} == 1 ]] || [[ ${1} == 2 ]]; then\n  echo \"starting node ${1}\"\n  startNode ${1}\nelse\n  echo \"Usage: ./agent-ns [0,1,2]\"\n  exit 1\nfi\n\n\n"
  },
  {
    "path": "aeron-samples/scripts/cluster/basic-auction-client",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nsource ./script-common\n\n# tag::start_jvm[]\nfunction startNode() {\n    ${JAVA_HOME}/bin/java \\\n        -cp ../../../aeron-all/build/libs/aeron-all-${VERSION}.jar \\\n        -XX:+UnlockExperimentalVMOptions \\\n        -XX:+TrustFinalNonStaticFields \\\n        -XX:+UnlockDiagnosticVMOptions \\\n        -XX:GuaranteedSafepointInterval=300000 \\\n        -XX:+UseParallelGC \\\n        -Daeron.cluster.tutorial.customerId=\"${1}\" \\\n        -Daeron.cluster.tutorial.numOfBids=\"${2}\" \\\n        -Daeron.cluster.tutorial.bidIntervalMs=\"${3}\" \\\n        ${JVM_OPTS} ${ADD_OPENS} \\\n        io.aeron.samples.cluster.tutorial.BasicAuctionClusterClient\n}\n\nif [[ $# -lt 1 ]]; then\n  echo \"Usage: ./basic-action-client <customer id> [number of bids (default 10)] [bid interval ms (default 1000)]\"\n  exit 1\nfi\n\nCUSTOMER_ID=${1}\nNUM_OF_BIDS=${2:-10}\nBID_INTERVAL_MS=${3:-1000}\n\nstartNode ${CUSTOMER_ID} ${NUM_OF_BIDS} ${BID_INTERVAL_MS}\n# end::start_jvm[]\n\n"
  },
  {
    "path": "aeron-samples/scripts/cluster/basic-auction-client-ns",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nset -euo pipefail\n\nsource ./script-common\n\n# tag::start_jvm[]\nfunction startNode() {\n    ${JAVA_HOME}/bin/java \\\n        -cp ../../../aeron-all/build/libs/aeron-all-${VERSION}.jar \\\n        -XX:+UnlockExperimentalVMOptions \\\n        -XX:+TrustFinalNonStaticFields \\\n        -XX:+UnlockDiagnosticVMOptions \\\n        -XX:GuaranteedSafepointInterval=300000 \\\n        -XX:+UseParallelGC \\\n        -Daeron.cluster.tutorial.customerId=\"${1}\" \\\n        -Daeron.cluster.tutorial.numOfBids=\"${2}\" \\\n        -Daeron.cluster.tutorial.bidIntervalMs=\"${3}\" \\\n        -Daeron.cluster.tutorial.hostnames=10.42.0.10,10.42.0.11,10.42.0.12 \\\n        ${JVM_OPTS:-} ${ADD_OPENS} \\\n        io.aeron.samples.cluster.tutorial.BasicAuctionClusterClient\n}\n\nif [[ $# -lt 1 ]]; then\n  echo \"Usage: ./basic-action-client <customer id> [number of bids (default 10)] [bid interval ms (default 1000)]\"\n  exit 1\nfi\n\nCUSTOMER_ID=${1}\nNUM_OF_BIDS=${2:-10}\nBID_INTERVAL_MS=${3:-1000}\n\nstartNode ${CUSTOMER_ID} ${NUM_OF_BIDS} ${BID_INTERVAL_MS}\n# end::start_jvm[]\n\n"
  },
  {
    "path": "aeron-samples/scripts/cluster/basic-auction-cluster",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nsource ./script-common\n\nmkdir -p logs\n\n# tag::start_jvm[]\nfunction startNode() {\n    ${JAVA_HOME}/bin/java \\\n        -cp ../../../aeron-all/build/libs/aeron-all-${VERSION}.jar \\\n        -javaagent:../../../aeron-agent/build/libs/aeron-agent-${VERSION}.jar \\\n        -XX:+UnlockExperimentalVMOptions \\\n        -XX:+TrustFinalNonStaticFields \\\n        -XX:+UnlockDiagnosticVMOptions \\\n        -XX:GuaranteedSafepointInterval=300000 \\\n        -XX:+UseParallelGC \\\n        -Daeron.event.cluster.log=all \\\n        -Daeron.event.cluster.log.disable=CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION \\\n        -Daeron.cluster.tutorial.nodeId=${1} \\\n        ${JVM_OPTS} ${ADD_OPENS} \\\n        io.aeron.samples.cluster.tutorial.BasicAuctionClusteredServiceNode > logs/cluster-${1}.log &\n}\n\nfunction startAll() {\n  startNode 0\n  startNode 1\n  startNode 2\n\n  tail -n 100 -F logs/cluster-*.log\n}\n# end::start_jvm[]\n\nif [[ $# -lt 1 ]]; then\n  startAll\nelif [[ ${1} == 0 ]] || [[ ${1} == 1 ]] || [[ ${1} == 2 ]]; then\n  echo \"starting node ${1}\"\n  startNode ${1}\nelse\n  echo \"Usage: ./basic-auction-cluster [0,1,2]\"\n  exit 1\nfi\n\n\n"
  },
  {
    "path": "aeron-samples/scripts/cluster/basic-auction-cluster-ns",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nset -euo pipefail\n\nsource ./script-common\n\nmkdir -p logs\n\n# tag::start_jvm[]\nfunction startNode() {\n\n    namespace=ns_cluster$1\n\n    sudo ip netns exec ${namespace} su - ${USER} -c \"cd ${PWD} && \\\n    ${JAVA_HOME}/bin/java \\\n        -cp ../../../aeron-all/build/libs/aeron-all-${VERSION}.jar \\\n        -javaagent:../../../aeron-agent/build/libs/aeron-agent-${VERSION}.jar \\\n        -XX:+UnlockExperimentalVMOptions \\\n        -XX:+TrustFinalNonStaticFields \\\n        -XX:+UnlockDiagnosticVMOptions \\\n        -XX:GuaranteedSafepointInterval=300000 \\\n        -XX:+UseParallelGC \\\n        -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.ssl.need.client.auth=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false \\\n        -Daeron.event.cluster.log=all \\\n        -Daeron.cluster.tutorial.hostnames=10.42.0.10,10.42.0.11,10.42.0.12 \\\n        -Daeron.cluster.tutorial.nodeId=${1} \\\n        ${JVM_OPTS:-} ${ADD_OPENS} \\\n        io.aeron.samples.cluster.tutorial.BasicAuctionClusteredServiceNode > logs/cluster-${1}.log\" &\n}\n\nfunction startAll() {\n  startNode 0\n  startNode 1\n  startNode 2\n\n  tail -n 100 -F logs/cluster-*.log\n}\n# end::start_jvm[]\n\nif [[ $# -lt 1 ]]; then\n  startAll\nelif [[ ${1} == 0 ]] || [[ ${1} == 1 ]] || [[ ${1} == 2 ]]; then\n  echo \"starting node ${1}\"\n  startNode ${1}\nelse\n  echo \"Usage: ./basic-auction-cluster [0,1,2]\"\n  exit 1\nfi\n\n\n"
  },
  {
    "path": "aeron-samples/scripts/cluster/remove-namespaces",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nsudo ip netns delete ns_node0\nsudo ip netns delete ns_node1\nsudo ip netns delete ns_node2\nsudo ip link delete br_a\nsudo ip link delete br_b\n\n\n"
  },
  {
    "path": "aeron-samples/scripts/cluster/script-common",
    "content": "#!/usr/bin/env bash\n\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nif [ -z \"${JAVA_HOME}\" ]; then\n  echo \"Please set the JAVA_HOME environment variable\"\n  exit 1\nfi\n\nVERSION=$(cat ../../../version.txt)\n\nJDK_VERSION=$(${JAVA_HOME}/bin/java -version 2>&1 | awk -F '\"' '/version/ {print $2}')\n\nADD_OPENS=\"--add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/java.util.zip=ALL-UNNAMED\"\n"
  },
  {
    "path": "aeron-samples/scripts/cluster/setup-namespaces",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nset -euox pipefail\n\n#\n# Two networks a (10.42.0.0/24) and b (10.42.1.0/24)\n# Three nodes 0 (x.x.x.10), 1 (x.x.x.11), and 2 (x.x.x.12).\n# *_<network><node>, e.g. veth_a0: virtual ethernet, network a, node 0 (10.42.0.10/24).\n#\n\nsudo ip netns add ns_node0\nsudo ip netns add ns_node1\nsudo ip netns add ns_node2\n\nsudo ip link add name br_a type bridge\nsudo ip link set br_a up\n\nsudo ip link add name br_b type bridge\nsudo ip link set br_b up\n\nsudo ip link add veth_a0 type veth peer name peer_a0\nsudo ip link set veth_a0 netns ns_node0\nsudo ip netns exec ns_node0 ip addr add 10.42.0.10/24 dev veth_a0\nsudo ip netns exec ns_node0 ip link set veth_a0 up\nsudo ip link set peer_a0 up\n\nsudo ip link add veth_b0 type veth peer name peer_b0\nsudo ip link set veth_b0 netns ns_node0\nsudo ip netns exec ns_node0 ip addr add 10.42.1.10/24 dev veth_b0\nsudo ip netns exec ns_node0 ip link set veth_b0 up\nsudo ip link set peer_b0 up\n\nsudo ip link add veth_a1 type veth peer name peer_a1\nsudo ip link set veth_a1 netns ns_node1\nsudo ip netns exec ns_node1 ip addr add 10.42.0.11/24 dev veth_a1\nsudo ip netns exec ns_node1 ip link set veth_a1 up\nsudo ip link set peer_a1 up\n\nsudo ip link add veth_b1 type veth peer name peer_b1\nsudo ip link set veth_b1 netns ns_node1\nsudo ip netns exec ns_node1 ip addr add 10.42.1.11/24 dev veth_b1\nsudo ip netns exec ns_node1 ip link set veth_b1 up\nsudo ip link set peer_b1 up\n\nsudo ip link add veth_a2 type veth peer name peer_a2\nsudo ip link set veth_a2 netns ns_node2\nsudo ip netns exec ns_node2 ip addr add 10.42.0.12/24 dev veth_a2\nsudo ip netns exec ns_node2 ip link set veth_a2 up\nsudo ip link set peer_a2 up\n\nsudo ip link add veth_b2 type veth peer name peer_b2\nsudo ip link set veth_b2 netns ns_node2\nsudo ip netns exec ns_node2 ip addr add 10.42.1.12/24 dev veth_b2\nsudo ip netns exec ns_node2 ip link set veth_b2 up\nsudo ip link set peer_b2 up\n\nsudo ip link set peer_a0 master br_a\nsudo ip link set peer_a1 master br_a\nsudo ip link set peer_a2 master br_a\nsudo ip addr add 10.42.0.1/24 brd + dev br_a\n\nsudo ip link set peer_b0 master br_b\nsudo ip link set peer_b1 master br_b\nsudo ip link set peer_b2 master br_b\nsudo ip addr add 10.42.1.1/24 brd + dev br_b\n"
  },
  {
    "path": "aeron-samples/scripts/dynamic-logging",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nif [ \"$#\" -lt 2 ]; then\n  echo \"Usage: <PID> <command> [config files...]\"\n  echo \"  <PID> - Java process ID to attach logging agent to.\"\n  echo \"  <command> - either 'start' to start logging or 'stop' to stop it.\"\n  echo \"  [config files...] - an optional list of property files to configure logging options.\"\n  echo \"Alternatively logging options can be specified via the 'JVM_OPTS' env variable, e.g.:\"\n  echo \"JVM_OPTS=\\\"-Daeron.event.log=admin -Daeron.event.archive.log=all\\\" ./dynamic-logging 1111 start\"\n  exit 1\nfi\n\nsource \"${DIR}/java-common\"\n\nAGENT_JAR=\"${DIR}/../../aeron-agent/build/libs/aeron-agent-${VERSION}.jar\"\n\nexec \"${JAVA_HOME}/bin/java\" \\\n  -cp \"${AGENT_JAR}\" \\\n  ${JAVA_OPTIONS} \\\n  ${ADD_OPENS} \\\n  ${JVM_OPTS} \\\n  io.aeron.agent.DynamicLoggingAgent \\\n  \"${AGENT_JAR}\" \\\n  \"$@\""
  },
  {
    "path": "aeron-samples/scripts/dynamic-logging.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\nset numArgs=0\r\nfor %%x in (%*) do set /A numArgs+=1\r\n\r\nif %numArgs% LSS 2 (\r\n  echo \"Usage: <PID> <command> [config files...]\"\r\n  echo \"  <PID> - Java process ID to attach logging agent to.\"\r\n  echo \"  <command> - either 'start' to start logging or 'stop' to stop it.\"\r\n  echo \"  [config files...] - an optional list of property files to configure logging options.\"\r\n  echo \"Alternatively logging options can be specified via the 'JVM_OPTS' env variable, e.g.:\"\r\n  echo \"set \\\"JVM_OPTS=-Daeron.event.log=admin -Daeron.event.archive.log=all\\\" && dynamic-logging 1111 start\"\r\n  exit /b 1\r\n)\r\n\r\ncall \"%DIR%\\java-common\"\r\n\r\nset \"AGENT_JAR=%DIR%\\..\\..\\aeron-agent\\build\\libs\\aeron-agent-%VERSION%.jar\"\r\n\r\n\"%JAVA_HOME%\\bin\\java\" ^\r\n  -cp \"%AGENT_JAR%\" ^\r\n  !JAVA_OPTIONS! ^\r\n  !ADD_OPENS! ^\r\n  %JVM_OPTS% ^\r\n  io.aeron.agent.DynamicLoggingAgent ^\r\n  %AGENT_JAR% ^\r\n  %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-claim-ipc-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedBufferClaimIpcThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/embedded-claim-ipc-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedBufferClaimIpcThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-dual-exclusive-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.sample.messages=500000000 \\\n    -Daeron.term.buffer.sparse.file=false \\\n    -Daeron.mtu.length=8k \\\n    -Daeron.socket.so_sndbuf=2m \\\n    -Daeron.socket.so_rcvbuf=2m \\\n    -Daeron.rcv.initial.window.length=2m \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedDualExclusiveThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/embedded-dual-exclusive-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.sample.messages=500000000 ^\r\n    -Daeron.term.buffer.sparse.file=false ^\r\n    -Daeron.mtu.length=8k ^\r\n    -Daeron.socket.so_sndbuf=2m ^\r\n    -Daeron.socket.so_rcvbuf=2m ^\r\n    -Daeron.rcv.initial.window.length=2m ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedDualExclusiveThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-claim-ipc-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.ipc.mtu.length=8k \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedExclusiveBufferClaimIpcThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-claim-ipc-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.ipc.mtu.length=8k ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedExclusiveBufferClaimIpcThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-ipc-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.ipc.mtu.length=8k \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedExclusiveIpcThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-ipc-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.ipc.mtu.length=8k ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedExclusiveIpcThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-spied-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.sample.messages=500000000 \\\n    -Daeron.term.buffer.sparse.file=false \\\n    -Daeron.mtu.length=8k \\\n    -Daeron.socket.so_sndbuf=2m \\\n    -Daeron.socket.so_rcvbuf=2m \\\n    -Daeron.rcv.initial.window.length=2m \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedExclusiveSpiedThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-spied-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.sample.messages=500000000 ^\r\n    -Daeron.term.buffer.sparse.file=false ^\r\n    -Daeron.mtu.length=8k ^\r\n    -Daeron.socket.so_sndbuf=2m ^\r\n    -Daeron.socket.so_rcvbuf=2m ^\r\n    -Daeron.rcv.initial.window.length=2m ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedExclusiveSpiedThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.sample.messages=500000000 \\\n    -Daeron.term.buffer.sparse.file=false \\\n    -Daeron.mtu.length=8k \\\n    -Daeron.socket.so_sndbuf=2m \\\n    -Daeron.socket.so_rcvbuf=2m \\\n    -Daeron.rcv.initial.window.length=2m \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedExclusiveThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.sample.messages=500000000 ^\r\n    -Daeron.term.buffer.sparse.file=false ^\r\n    -Daeron.mtu.length=8k ^\r\n    -Daeron.socket.so_sndbuf=2m ^\r\n    -Daeron.socket.so_rcvbuf=2m ^\r\n    -Daeron.rcv.initial.window.length=2m ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedExclusiveThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-vectored-ipc-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.ipc.mtu.length=8k \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedExclusiveVectoredIpcThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/embedded-exclusive-vectored-ipc-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.ipc.mtu.length=8k ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedExclusiveVectoredIpcThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-ipc-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.ipc.mtu.length=8k \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedIpcThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/embedded-ipc-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.ipc.mtu.length=8k ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedIpcThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-ping-pong",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.term.buffer.sparse.file=false \\\n    -Daeron.pre.touch.mapped.memory=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.sample.messages=1000000 \\\n    -Daeron.sample.exclusive.publications=true \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedPingPong \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/embedded-ping-pong.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.pre.touch.mapped.memory=true ^\r\n    -Daeron.term.buffer.sparse.file=false ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.sample.messages=1000000 ^\r\n    -Daeron.sample.exclusive.publications=true ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedPingPong %*"
  },
  {
    "path": "aeron-samples/scripts/embedded-throughput",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.messageLength=32 \\\n    -Daeron.sample.messages=500000000 \\\n    -Daeron.term.buffer.sparse.file=false \\\n    -Daeron.mtu.length=8k \\\n    -Daeron.socket.so_sndbuf=2m \\\n    -Daeron.socket.so_rcvbuf=2m \\\n    -Daeron.rcv.initial.window.length=2m \\\n    ${JVM_OPTS} io.aeron.samples.EmbeddedThroughput \"$@\""
  },
  {
    "path": "aeron-samples/scripts/embedded-throughput.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.sample.messages=500000000 ^\r\n    -Daeron.term.buffer.sparse.file=false ^\r\n    -Daeron.mtu.length=8k ^\r\n    -Daeron.socket.so_sndbuf=2m ^\r\n    -Daeron.socket.so_rcvbuf=2m ^\r\n    -Daeron.rcv.initial.window.length=2m ^\r\n    %JVM_OPTS% io.aeron.samples.EmbeddedThroughput %*"
  },
  {
    "path": "aeron-samples/scripts/error-stat",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.ErrorStat"
  },
  {
    "path": "aeron-samples/scripts/error-stat.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.ErrorStat"
  },
  {
    "path": "aeron-samples/scripts/file-receiver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.FileReceiver \"$@\""
  },
  {
    "path": "aeron-samples/scripts/file-receiver.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.FileReceiver %*"
  },
  {
    "path": "aeron-samples/scripts/file-sender",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.FileSender \"$@\""
  },
  {
    "path": "aeron-samples/scripts/file-sender.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.FileSender %*"
  },
  {
    "path": "aeron-samples/scripts/ipc-c-media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nAERON_BUILD_DIR=../../cppbuild/Release\n\nexport AERON_TERM_BUFFER_SPARSE_FILE=\"0\"\nexport AERON_MTU_LENGTH=\"8k\"\nexport AERON_THREADING_MODE=\"SHARED\"\nexport AERON_SHARED_IDLE_STRATEGY=\"spin\"\n\n${AERON_BUILD_DIR}/binaries/aeronmd\n"
  },
  {
    "path": "aeron-samples/scripts/java-common",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nif [ -z \"${JAVA_HOME}\" ]; then\n  echo \"Please set the JAVA_HOME environment variable\"\n  exit 1\nfi\n\nscripts_dir=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nVERSION=$(cat \"$scripts_dir/../../version.txt\")\n\nJAVA_OPTIONS=\"\\\n  -XX:+UnlockExperimentalVMOptions \\\n  -XX:+TrustFinalNonStaticFields \\\n  -XX:+UnlockDiagnosticVMOptions \\\n  -XX:GuaranteedSafepointInterval=300000 \\\n  -XX:+UseParallelGC\"\n\nADD_OPENS=\"\\\n--add-opens java.base/jdk.internal.misc=ALL-UNNAMED \\\n--add-opens java.base/java.util.zip=ALL-UNNAMED\"\n"
  },
  {
    "path": "aeron-samples/scripts/java-common.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n@echo off\r\n\r\nif \"%JAVA_HOME%\" == \"\" (\r\n  echo \"Please set the JAVA_HOME environment variable\"\r\n  exit /b 1\r\n)\r\n\r\nset /p VERSION=<\"%~dp0\\..\\..\\version.txt\"\r\n\r\nset JAVA_OPTIONS=^\r\n  -XX:+UnlockExperimentalVMOptions ^\r\n  -XX:+TrustFinalNonStaticFields ^\r\n  -XX:+UnlockDiagnosticVMOptions ^\r\n  -XX:GuaranteedSafepointInterval=300000 ^\r\n  -XX:+UseParallelGC\r\n\r\nset ADD_OPENS=^\r\n--add-opens java.base/jdk.internal.misc=ALL-UNNAMED ^\r\n--add-opens java.base/java.util.zip=ALL-UNNAMED\r\n"
  },
  {
    "path": "aeron-samples/scripts/linux-qdisc-basic",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nPORT_TO_FILTER=20121\n\nsudo tc qdisc del dev lo root\nsudo tc qdisc add dev lo root handle 1: prio\nsudo tc filter add dev lo protocol ip parent 1: prio 1 u32 match ip dport ${PORT_TO_FILTER} 0xffff match ip protocol 17 0xff flowid 1:1\nsudo tc qdisc add dev lo parent 1:1 handle 20: netem loss 5% delay 10ms\ntc qdisc show dev lo\n"
  },
  {
    "path": "aeron-samples/scripts/log-inspector",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.LogInspector \"$@\""
  },
  {
    "path": "aeron-samples/scripts/log-inspector.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.LogInspector %*"
  },
  {
    "path": "aeron-samples/scripts/logging-c-media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nAERON_BUILD_DIR=../../cppbuild/Release\n\nexport AERON_EVENT_LOG=\"all\"\n\n${AERON_BUILD_DIR}/binaries/aeronmd \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/logging-media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java-logging\" \\\n    -Daeron.event.log=all \\\n    ${JVM_OPTS} io.aeron.driver.MediaDriver \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/logging-media-driver.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java-logging\"  ^\r\n    -Daeron.event.log=all ^\r\n    %JVM_OPTS% io.aeron.driver.MediaDriver %*\r\n"
  },
  {
    "path": "aeron-samples/scripts/loss-rate-c-media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nAERON_BUILD_DIR=../../cppbuild/Release\n\nexport AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS=\"loss\"\nexport AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_LOSS_ARGS=\"rate=0.2|recv-msg-mask=0xF\"\n\n${AERON_BUILD_DIR}/binaries/aeronmd \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/loss-stat",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.LossStat"
  },
  {
    "path": "aeron-samples/scripts/loss-stat.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.LossStat"
  },
  {
    "path": "aeron-samples/scripts/low-latency-c-media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nAERON_BUILD_DIR=../../cppbuild/Release\n\nexport AERON_TERM_BUFFER_SPARSE_FILE=\"0\"\nexport AERON_SOCKET_SO_RCVBUF=\"2m\"\nexport AERON_SOCKET_SO_SNDBUF=\"2m\"\nexport AERON_RCV_INITIAL_WINDOW_LENGTH=\"2m\"\nexport AERON_THREADING_MODE=\"DEDICATED\"\nexport AERON_CONDUCTOR_IDLE_STRATEGY=\"spin\"\nexport AERON_SENDER_IDLE_STRATEGY=\"noop\"\nexport AERON_RECEIVER_IDLE_STRATEGY=\"noop\"\nexport AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND=\"2\"\nexport AERON_PRINT_CONFIGURATION=\"1\"\n\n${AERON_BUILD_DIR}/binaries/aeronmd\n"
  },
  {
    "path": "aeron-samples/scripts/low-latency-c-media-driver.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\nset \"AERON_BUILD_DIR=%DIR%\\..\\..\\cppbuild\\Release\"\r\n\r\nset \"AERON_TERM_BUFFER_SPARSE_FILE=0\"\r\nset \"AERON_SOCKET_SO_RCVBUF=2m\"\r\nset \"AERON_SOCKET_SO_SNDBUF=2m\"\r\nset \"AERON_RCV_INITIAL_WINDOW_LENGTH=2m\"\r\nset \"AERON_THREADING_MODE=DEDICATED\"\r\nset \"AERON_CONDUCTOR_IDLE_STRATEGY=spin\"\r\nset \"AERON_SENDER_IDLE_STRATEGY=noop\"\r\nset \"AERON_RECEIVER_IDLE_STRATEGY=noop\"\r\nset \"AERON_NETWORK_PUBLICATION_MAX_MESSAGES_PER_SEND=2\"\r\nset \"AERON_PRINT_CONFIGURATION=1\"\r\n\r\n\"%AERON_BUILD_DIR%\\binaries\\Release\\aeronmd.exe\"\r\n"
  },
  {
    "path": "aeron-samples/scripts/low-latency-media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.driver.MediaDriver \\\n    low-latency.properties \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/low-latency-media-driver.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.driver.MediaDriver ^\r\n    low-latency.properties %*\r\n"
  },
  {
    "path": "aeron-samples/scripts/media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.driver.MediaDriver \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/media-driver.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.driver.MediaDriver %*\r\n"
  },
  {
    "path": "aeron-samples/scripts/ping",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Daeron.sample.messages=1000000 \\\n    -Daeron.sample.messageLength=32 \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.pre.touch.mapped.memory=true \\\n    -Daeron.sample.exclusive.publications=true \\\n    ${JVM_OPTS} io.aeron.samples.Ping"
  },
  {
    "path": "aeron-samples/scripts/ping.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.pre.touch.mapped.memory=true ^\r\n    -Daeron.sample.messages=1000000 ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.sample.exclusive.publications=true ^\r\n    %JVM_OPTS% io.aeron.samples.Ping"
  },
  {
    "path": "aeron-samples/scripts/pong",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.pre.touch.mapped.memory=true \\\n    -Daeron.sample.exclusive.publications=true \\\n    ${JVM_OPTS} io.aeron.samples.Pong"
  },
  {
    "path": "aeron-samples/scripts/pong.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.pre.touch.mapped.memory=true ^\r\n    -Daeron.sample.exclusive.publications=true ^\r\n    %JVM_OPTS% io.aeron.samples.Pong"
  },
  {
    "path": "aeron-samples/scripts/rate-subscriber",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" \\\n    -Dagrona.disable.bounds.checks=true \\\n    -Daeron.sample.frameCountLimit=256 \\\n    ${JVM_OPTS} io.aeron.samples.RateSubscriber"
  },
  {
    "path": "aeron-samples/scripts/rate-subscriber.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.frameCountLimit=256 ^\r\n    %JVM_OPTS% io.aeron.samples.RateSubscriber"
  },
  {
    "path": "aeron-samples/scripts/raw/hack-select-receive-send-udp-pong",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    ${JVM_OPTS} io.aeron.samples.raw.HackSelectReceiveSendUdpPong \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/raw/hack-select-receive-send-udp-pong.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    %JVM_OPTS% io.aeron.samples.raw.HackSelectReceiveSendUdpPong %*"
  },
  {
    "path": "aeron-samples/scripts/raw/receive-send-udp-pong",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    ${JVM_OPTS} io.aeron.samples.raw.ReceiveSendUdpPong \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/raw/receive-send-udp-pong.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    %JVM_OPTS% io.aeron.samples.raw.ReceiveSendUdpPong %*"
  },
  {
    "path": "aeron-samples/scripts/raw/send-hack-select-receive-udp-ping",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    ${JVM_OPTS} io.aeron.samples.raw.SendHackSelectReceiveUdpPing \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/raw/send-hack-select-receive-udp-ping.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    %JVM_OPTS% io.aeron.samples.raw.SendHackSelectReceiveUdpPing %*"
  },
  {
    "path": "aeron-samples/scripts/raw/send-receive-udp-ping",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/../run-java\" \\\n    -Djava.net.preferIPv4Stack=true \\\n    ${JVM_OPTS} io.aeron.samples.raw.SendReceiveUdpPing \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/raw/send-receive-udp-ping.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\..\\run-java\" ^\r\n    -Djava.net.preferIPv4Stack=true ^\r\n    %JVM_OPTS% io.aeron.samples.raw.SendReceiveUdpPing %*"
  },
  {
    "path": "aeron-samples/scripts/run-java",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nsource \"${DIR}/java-common\"\n\nexec \"${JAVA_HOME}/bin/java\" \\\n  -cp \"${DIR}/../../aeron-all/build/libs/aeron-all-${VERSION}.jar\" \\\n  ${JAVA_OPTIONS} \\\n  ${ADD_OPENS} \\\n  ${JVM_OPTS} \\\n  \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/run-java-logging",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nsource \"${DIR}/java-common\"\n\nexec \"${JAVA_HOME}/bin/java\" \\\n  -cp \"${DIR}/../../aeron-all/build/libs/aeron-all-${VERSION}.jar\" \\\n  -javaagent:\"${DIR}/../../aeron-agent/build/libs/aeron-agent-${VERSION}.jar\" \\\n  ${JAVA_OPTIONS} \\\n  ${ADD_OPENS} \\\n  ${JVM_OPTS} \\\n  \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/run-java-logging.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n@echo off\r\nsetlocal EnableExtensions EnableDelayedExpansion\r\n\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\java-common\"\r\n\r\n\"%JAVA_HOME%\\bin\\java\" ^\r\n  -cp \"%DIR%\\..\\..\\aeron-all\\build\\libs\\aeron-all-%VERSION%.jar\" ^\r\n  -javaagent:\"%DIR%\\..\\..\\aeron-agent\\build\\libs\\aeron-agent-%VERSION%.jar\" ^\r\n  !JAVA_OPTIONS! ^\r\n  !ADD_OPENS! ^\r\n  %JVM_OPTS% ^\r\n  %*\r\n"
  },
  {
    "path": "aeron-samples/scripts/run-java.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n@echo off\r\nsetlocal EnableExtensions EnableDelayedExpansion\r\n\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\java-common\"\r\n\r\n\"%JAVA_HOME%\\bin\\java\" ^\r\n  -cp \"%DIR%\\..\\..\\aeron-all\\build\\libs\\aeron-all-%VERSION%.jar\" ^\r\n  %JAVA_OPTIONS% ^\r\n  %ADD_OPENS% ^\r\n  %JVM_OPTS% ^\r\n  %*\r\n"
  },
  {
    "path": "aeron-samples/scripts/show_thread_affinity.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\npname=${1:-aeronmd}  # default to 'aeronmd'\n\npid=$(pgrep \"${pname}\")\n\necho \"PID: ${pid} (${pname})\"\nps -T -o pid,tid,comm,psr -p \"${pid}\"\n"
  },
  {
    "path": "aeron-samples/scripts/stream-stat",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.StreamStat \"$@\"\n"
  },
  {
    "path": "aeron-samples/scripts/stream-stat.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" io.aeron.samples.StreamStat %*"
  },
  {
    "path": "aeron-samples/scripts/streaming-publisher",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" >/dev/null 2>&1 && pwd)\"\n\nexec \"${DIR}/run-java\" io.aeron.samples.StreamingPublisher"
  },
  {
    "path": "aeron-samples/scripts/streaming-publisher.cmd",
    "content": "::\r\n:: Copyright 2014-2025 Real Logic Limited.\r\n::\r\n:: Licensed under the Apache License, Version 2.0 (the \"License\");\r\n:: you may not use this file except in compliance with the License.\r\n:: You may obtain a copy of the License at\r\n::\r\n:: https://www.apache.org/licenses/LICENSE-2.0\r\n::\r\n:: Unless required by applicable law or agreed to in writing, software\r\n:: distributed under the License is distributed on an \"AS IS\" BASIS,\r\n:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n:: See the License for the specific language governing permissions and\r\n:: limitations under the License.\r\n::\r\n\r\n@echo off\r\nset \"DIR=%~dp0\"\r\n\r\ncall \"%DIR%\\run-java\" ^\r\n    -Dagrona.disable.bounds.checks=true ^\r\n    -Daeron.sample.messageLength=32 ^\r\n    -Daeron.sample.messages=500000000 ^\r\n    %JVM_OPTS% io.aeron.samples.StreamingPublisher"
  },
  {
    "path": "aeron-samples/scripts/throughput-c-media-driver",
    "content": "#!/usr/bin/env bash\n##\n## Copyright 2014-2025 Real Logic Limited.\n##\n## Licensed under the Apache License, Version 2.0 (the \"License\");\n## you may not use this file except in compliance with the License.\n## You may obtain a copy of the License at\n##\n## https://www.apache.org/licenses/LICENSE-2.0\n##\n## Unless required by applicable law or agreed to in writing, software\n## distributed under the License is distributed on an \"AS IS\" BASIS,\n## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n## See the License for the specific language governing permissions and\n## limitations under the License.\n##\n\nAERON_BUILD_DIR=../../cppbuild/Release\n\nexport AERON_TERM_BUFFER_SPARSE_FILE=\"0\"\nexport AERON_MTU_LENGTH=\"8k\"\nexport AERON_SOCKET_SO_RCVBUF=\"2m\"\nexport AERON_SOCKET_SO_SNDBUF=\"2m\"\nexport AERON_RCV_INITIAL_WINDOW_LENGTH=\"2m\"\n\n${AERON_BUILD_DIR}/binaries/aeronmd\n"
  },
  {
    "path": "aeron-samples/src/docs/asciidoc/Cluster-Tutorial.asciidoc",
    "content": "\n////\n* Copyright 2014-2025 Real Logic Limited.\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* https://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n////\n\n= Aeron Cluster Tutorial\n:sampleBaseDir: ../../..\n:sampleSourceDir: {sampleBaseDir}/src/main/java\n:aeronVersion: [aeronVersion]\n:sectnums:\n:source-highlighter: prettify\n:autofit-option:\n\nThis tutorial assumes that the user already has a basic working knowledge of Aeron Messaging.\n\n== Introduction\n\nAeron Cluster is a framework for high-performance in-memory fault-tolerant services. It implements the\nhttps://raft.github.io/[Raft Consensus Algorithm] to provide log replication, to allow multiple nodes to maintain the\nsame state and automated leader election to ensure that there is a single leader within the cluster. Systems built using\nAeron Cluster have a linearizable consistency model and can cope with partial system failure, including failure of the\nleader node, as long as a quorum of `(n / 2) + 1` nodes remain available.\n\n=== Raft Basics\n\nIt is not the intention of this tutorial to go into a full description of the https://raft.github.io/[Raft Consensus\nAlgorithm], however it is difficult explain Aeron Cluster without a few definitions up front. The key concepts\nare (some are Aeron specific):\n\n- Node, a physical server, container instance, VM or group of processes that represents a logical server within a\ncluster.\n- Leader, a node within the cluster responsible for replicating messages to the other nodes and waiting for\nacknowledgements.\n- Follower, other nodes within the cluster that receive messages replicated from the leader.\n- Election, process by which the cluster agrees on a new leader.\n- Client, node external to the cluster.\n- Ingress, the messages from a client into the cluster.\n- Egress, response messages from the cluster back to a client.\n- Snapshot, serialised representation of the application logic's state at a point in time.\n\n=== Replication and Recovery\n\nWithin Aeron Cluster, replication (ensuring that a follower node has the same state as the leader) and recovery\n(restoring a stopped node to its previous state)footnote:[Where stopping could include crashing or manually shutting\ndown] are the same problem, or at least at similar enough, to use the same mechanism. They rely on three functions.\nFirstly, for some known initial state, this could either be the state when first provisioned (a 'null' or empty state),\nor some snapshot of state at a point in time. Secondly, an ordered log of all input messages, this is handled by\nCluster's Raft implementation. Thirdly, the application logic needs to be deterministic, i.e. it must derive all of its\nresulting state and output events from the initial state, and the input messages, such that for the same inputs it will\nalways get the same result. This can be a trickier than it first seems as it excludes some functionality that we may\nnormally take for granted.\n\n[TIP]\n====\nBe careful to ensure determinism in how you code a clustered service, common pitfalls include:\n\n- Timestamps\n- Random Numbers\n- Reading from configuration files.\n- Iterating over some types of collections, e.g. HashMaps.\n====\n\nTime, as timestamps, is provided by Aeron Cluster (will be shown later). Random numbers are best avoided or use a seed\nfixed in the initial state or via a message. Configuration changes should be pushed into the application logic via\nmessages. Iterating over collections should happen in a known order, with a collection that preserves ordering (e.g.\nTreeMap, LinkedHashMap) through sorting the values from the collection before sending them as messages. Alternatively,\nthe Agrona collections can be used with their consistent iteration order and low allocation characteristics.\n\nAs it may be impractical to replay all data from the \"beginning of time\" the system should take snapshots periodically,\ne.g. daily or hourly. The frequency of snapshots should be determined by the volume of data into the system, the\nthroughput of the business logic, and the desired mean time to recovery. It is not uncommon to have systems that may\ntake an hour or two to recover from a days worth of messages, in those systems snapshotting every 30 minutes may be\nmore appropriate.\n\n=== Components of Aeron Cluster\n\nOne of the key design goals of Aeron is to build a system that is highly composable. For example, Aeron Archive which\nprovides a means to persist the Raft log created by Aeron Cluster. Aeron Cluster sends messages via a Media Driver.\nTherefore, Aeron Cluster is an aggregation of a number of existing Aeron components and a few new ones. To successfully\nrun a cluster node it is necessary to have one (or at least one in the case of `ClusteredServiceContainer`) of each of\nthe Aeron components running. Because all communication between these components within a single node uses IPC they can\nbe run all in the same process, in separate processes or any arbitrary combination.\n\n==== MediaDriver\n\nThe Media Driver is the means by which the cluster communicates. Aeron Cluster reuses the `Publication`, `Subscription`,\nand `Counter` functionality in Aeron to handle all distributed and inter-process communication.\n\n==== Archive\n\nRaft is primarily a log replication protocol, so Aeron Cluster uses Aeron Archive to persist its log.\n\n==== ConsensusModule\n\nThe Consensus Module is the key component in Aeron Cluster and ensures the nodes have a consistent copy of the\nreplicated log. The Consensus Module will coordinate with the Archive to persist messages, replicate/ack messages\nto/from other nodes and deliver messages through to the Clustered Services.\n\n==== ClusteredServiceContainer\n\nThis is the service running the developer supplied application logic. There can be one or more clustered services per\nnode. Aeron Cluster provides a container for the application logic. There is a `ClusteredService` interface that must be\nimplemented by the application to receive the events and messages from Cluster.\n\n== Getting Started Aeron Cluster\n\nInstall JDK 8, 11, or 17 and ensure that `$JAVA_HOME` is set and `$JAVA_HOME/bin` is on your `$PATH`.\n\nThe typical way to get started with Aeron in your own application is to use the\nhttps://search.maven.org/artifact/io.aeron/aeron-all[aeron-all jar]. However, for this tutorial we are going to\nrecommend that you check out the source code from GitHub.\n\n[#usingthesource]\n=== Using the Source From this Tutorial\n\nIn order to look at the examples and run the code from this tutorial you will need to checkout the full Aeron source\ncode, use the appropriate release version and build the projects.\n\n[subs = \"attributes+\"]\n----\n> git clone https://github.com/aeron-io/aeron.git\n> cd aeron\n> git checkout -b my_tutorial {aeronVersion}\n> ./gradlew\n----\n\nThe location on your computer where this has been checked out to will be referred to as `<AERON_HOME>`.\n\nThis tutorial will include snippets of code from the working example. The scripts and classes for this tutorial are:\n\n * <AERON_HOME>/aeron-samples/src/main/java/io/aeron/samples/tutorial/cluster/\n ** BasicAuctionClusteredService.java\n ** BasicAuctionClusteredServiceNode.java\n ** BasicAuctionClusterClient.java\n * <AERON_HOME>/aeron-samples/scripts/cluster/\n ** basic-auction-cluster\n ** basic-auction-client\n\nYou may wish to open these up in your favourite IDE or editor while we proceed with the tutorial.\n\n== Implementing a Clustered Service\n\nThe first step to setting up a cluster is to implement the application logic. For this tutorial we are going to\nimplement simple auction service. It will have one auction and will track the best price, and an id for the customer\nwho bid that price. To properly demonstrate the state management and recovery features of cluster it is important to\nhave some functionality that is stateful, rather than something that is stateless like an echo service.\n\nNOTE: In order to understandably show the various features of Cluster, we've kept the various aspects of the code\n(data serialisation, application logic, Cluster integration) very close together. It is unlikely you would do this in a\nreal world application. You would probably want to have greater separation of concerns and perhaps use libraries to\nhandle functionality like serialisation.\n\nWe must define the link between the application logic and Aeron Cluster. This is done by implementing the\n`ClusteredService` interface. The `ClusteredService` interface defines a number of callbacks which deliver messages\nand lifecycle events as they occur, as well as providing the hooks that our service can use to interact with the\ncluster (e.g. sending response messages and taking snapshots).\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredService.java[tag=new_service]\n----\n\n=== Start Up\n\nThe clustered service container will notify the service it has started using the `onStart` callback. This will occur\nbefore input messages are received, either from log replay or live from a client. It is during this phase that we need\nto load the initial state of the service. Aeron Cluster passes in an `Image` that will contain the most recent valid\nsnapshot of the service. The service should take care of deserializing the data from the image and initialise its state.\nWe will come back to the details of how the snapshot is loaded after we've looked how messages are handled and how a\nsnapshot is stored.\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredService.java[tag=start]\n----\n\n<1> Take a reference to the cluster, so that we can have access to features of the cluster within the other callbacks.\n\n<2> Take a reference to the cluster's idle strategy. This will be used anytime we enter a busy loop within service.\n\n<3> The snapshot can be null (this occurs the first time the service is started).\n\n=== Handling Messages\n\nThe `onSessionMessage` callback is the main entry point for requests coming into the cluster. Messages reach here by\nbeing published to the Cluster's ingress channel. This method will also be passed messages during log replay. It is from\nwithin this method we will interact with our application logic. Cluster also provides a reliable timestamp as a\nparameter to this method. As mentioned earlier this is one of the challenges of building deterministic systems. Use\nthis value as the timestamp within your application state, so it will be consistent under replay.\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredService.java[tag=state]\n----\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredService.java[tag=message]\n----\n\nFor our input message we have 3 fields, a `correlationId` which is the customer supplied identifier for the message,\n`customerId` to identify the customer placing the bid, and a `price` (we've used a long for this, which represents the\nvalue in cents).\n\n<1> Pull the data out of the message. This is similar to the pattern used in an Aeron Subscription's `onFragment`\ncallback when doing a poll.\n\n<2> Execute the business logic, in our case this is applying the incoming bid to the auction to see if it is a winner.\n\n<3> The `ClientSession` allows the service to get information about the calling client, but also provide a means to\nreturn responses back to the client.\n\n<4> Serialise response message.\n\n<5> Calling `offer` on the client session will send the response on the egress channel. Make sure to check the return\nvalue of `offer` as it is a non-blocking call and not guaranteed to succeed.\n\n<6> When doing any busy loops within the clustered application use the Cluster's `IdleStrategy::idle(int)` within the\nwait loop to allow the service to wait while allowing other threads to progress. It will take care of handling thread\ninterrupts and ensure the node fails correctly.\n\n=== Storing State\n\nAs was mentioned earlier we need to regularly take snapshots of the service's state in order to reduce the mean time to\nrecovery and facilitate release migration. There is a callback `onTakeSnapshot` that will be called when it is time to\nsnapshot the state of the service.\n\nAeron Cluster provides an `ExclusivePublication` to write a snapshot as the serialised representation of the application\nlogic's state. For real world applications snapshotting can become tricky. The two big concerns you have includes:\nensuring that you consistently write snapshots, and dealing with fragmentation of the application state across messages.\nFor now, our state is so simple neither of those will impact us.\n\n[source%autofit, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredService.java[tag=takeSnapshot]\n----\n\n<1> Write the persistent part of the application logic to a message buffer. In our case, the currently winning customer\nid and bid price.\n\n<2> Write the message to the publication, again we need to check the return from the `offer` call and use\nthe Cluster's `IdleStrategy::idle` inside the busy wait loop.\n\n=== Loading State\n\nOur `onStart` implementation contained a method to load the snapshot. Now we have seen how to take a snapshot, we\ncan now look at how it is loaded. The `Image` provided to the `onStart` has a method that will indicate if there is no\nmore data available. However, application code should also encode enough information that the end of the stream can be\ndetected from the stored messages. The two approaches can be used to sanity check all the received data.\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredService.java[tag=loadSnapshot]\n----\n\n<1> Our snapshot is a stream of messages written to a `Publication`, we then use the `Image::poll` method for\nextracting data from the snapshot.\n\n<2> Our total snapshot length (16 bytes) is going to be smaller than any reasonable MTU therefore we can assume that all\nthe data will come in a single message fragment.\n\n<3> Once all data is loaded, we can initialise the application logic state from the snapshot.\n\n<4> The method `Image::isEndOfStream` can be used to determine if there is going to be any more input.\n\n<5> Once we've loaded all the data for the application we can break out of the snapshot loading loop.\n\n<6> Again make sure we use the Cluster's `IdleStrategy::idle` in the tight loops.  It could take time for the snapshot\nto be propagated to the service, so the number of fragments can be zero on any given invocation.\n\n<7> It is also worthwhile having some sanity checks, these ensure that the snapshot store/load code and Aeron agree on\nwhere the end of the input data is. We've used asserts, but other mechanisms, (e.g. log message or exceptions) could\nalso be used to indicate an issue.\n\n=== Other events\n\nThere a number of other events received by the `ClusteredService` interface, such as timer events, clients connecting\nand disconnecting from the cluster, plus leadership and role changes. We won't look at these in this tutorial.\n\n== Configuring a Cluster\n\nNow we have our application implemented we can move onto running it in a Cluster. There are a number of moving parts to\nsetting up a cluster node, one of the trickiest parts of using Aeron Cluster is getting the configuration correct. We\nare going to start with a static three-node cluster with a simplified configuration where all the components\n(`MediaDriver`, `Archive`, `ConsensusModule`, and `ClusteredServiceContainer`) for a single node within a single\nprocess. Later we will then start the cluster as three separate processes.\n\n=== Running Multiple Nodes on the Same Host\n\nIn a production deployment, you will want to run the 3 instances on 3 separate servers, however for our example we\nwant to run the services on a single machine. This does make port allocation a concern as each node within the cluster\nneeds to bind to a number of ports, so we need to make sure are no clashes. We could do this with VMs or containers,\nbut in the interest of simplicity we are going to specify a port range for each node. For example:\n\n- Node 0, ports: 9000-9099\n- Node 1, ports: 9100-9199\n- Node 2, ports: 9200-9299\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=ports]\n----\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=udp_channel]\n----\n\nWhile we don't really need 100 ports for each node (closer to 10), it does make it clear which ports are assigned to\neach service. Each endpoint will be an offset from the first port in a node's range. E.g. the archive control request\nport has an offset of 1, so for Node 2 it will have port 9201.\n\nTo start the cluster node we are going define our own class with a main method. It will construct and start all the\nnecessary components. As indicated above we are going to give each node of the cluster a unique id (0, 1, and 2). We\nare also going to use this value as the cluster member id.\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=new_service]\n----\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=main]\n----\n\n<1> Pass in the node id as a command line parameter to the service so that we can reuse the code for each instance of\nthe service.\n\n<2> Define a set of hostnames for the cluster. For now everything is running on localhost, but we could put actual\nhostnames or IP addresses in this list and run the same example across multiple servers.\n\n<3> For each node we need a location on disk that will hold the persistent data. This will include the raft log, mark\nfiles for each service component, and the recording log used to track snapshots within the log. In a production system\nthis location is likely to be fairly important as you will want to map this to a fast disk in order to get the best\nperformance out the system.\n\n=== Configuration\n\n==== Media Driver\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=media_driver]\n----\n\nNote we've specified the custom `aeronDirName` to allow multiple Media Drivers on the same host. There is nothing\nspecial in the configuration in the Media Driver specific to Cluster.\n\n==== Archive\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=archive]\n----\n\nAgain nothing special in the Archive configuration specific to Cluster. We've used the same `aeronDirName` as used by\nthe Media Driver. The `controlChannel` uses node specific port in the construction of its UDP channel.\n\n==== Archive Client\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=archive_client]\n----\n\nBecause Cluster's Consensus Module requires that we write a log file to support the Raft protocol, we need a client for\nthe constructed Archive for it to use. As the Consensus Module should always be deployed on the same node as its\nArchive, we're going to use the local (IPC) configuration. It is also worth noting that we haven't changed the\n`controlRequestStreamId` in our setup. The defaults in this situation are the most appropriate (from Aeron version\n1.33.0 onwards).\n\n==== Consensus Module\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=consensus_module]\n----\n\nThis is first of the two configuration sections that are specific to Cluster. The Consensus Module is responsible for\nhandling the main aspects of the Raft protocol, e.g. leader election, ensuring consensus-based consistency of the data\nin the log and passing properly replicated messages onto the Cluster Container.\n\n<1> Each Consensus Module needs an identifier within the Cluster, we are going to use the `nodeId` that we have used to\nseparate each of the nodes.\n\n<2> This is probably the trickiest part of the configuration. It specifies all the static members of the cluster\nalong with all the endpoints that they require for the various operations of the Consensus Module. These are encoded\ninto a single string of the form.\n+\n----\n0,ingress:port,consensus:port,log:port,catchup:port,archive:port|\\\n1,ingress:port,consensus:port,log:port,catchup:port,archive:port|...\n----\nWhere each of the leading numeric values is a member id for the cluster (as specified in the `clusterMemberId` method).\nThe values for each endpoint represent:\n\n* ingress, where the client will connect to for the ingress channel.\n* consensus, where other members of the cluster communicate with each other to achieve cluster consensus.\n* log, used to replicate logs from leader to followers.\n* catchup, used for a stream that members can use to catch up to a leader when behind the current log position.\n* archive, the same endpoint used to control the Archive running on this node.\n+\nIn our example we've used the same hostname for each of the endpoints (localhost), however each endpoint allows the\nspecification of a host, so that traffic could potentially be separated if required, e.g. running consensus traffic\non a separate network to the ingress traffic.\n\n<3> Specify the data directory for the Consensus Module. Make sure it is node specific.\n\n<4> Define the ingress channel for the cluster. Note this value does not need to be the full channel URI. It can be a\ntemplate that specifies the parameters, but excludes the endpoint, which will be filled using value from the\n`clusterMembersString` as appropriate for this node.\n\n<5> Specify the replication channel. This channel is the one that the local archive for a node will receive replication\nresponses from other archives when the log or snapshot replication step is required. This was added in version 1.33.0\nand is a required parameter. It is important in a production environment that this channel's endpoint is not set to\nlocalhost, but instead a hostname/ip address that is reachable by the other nodes in the cluster.\n\n<6> Clone the Archive Client context that the Consensus Module will use to talk to the Archive.\n\n==== Clustered Service Container\n\nThe final part of the configuration is the component to run our application logic. It is possible to have multiple\nClustered Service Containers per Consensus Module and have them talk to each other using IPC. In our simplified case we\njust have the one.\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=clustered_service]\n----\n\n<1> Again we use the node specific Media Driver.\n\n<2> Plus the node's Archive, via the Archive Client configuration.\n\n<3> This is the point where we bind an instance of our application logic to the cluster.\n\n=== Running the Cluster\n\nNow we have a configured cluster, we can start it running. The code for launching the service is as follows.\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java[tag=running]\n----\n<1> Create a shutdown barrier that will be used to trap exit signals and allow the service to exit cleanly.\n\n<2> Launches a `ClusteredMediaDriver` that includes instances of the Media Driver, Archive and Consensus Module.\n\n<3> Immediately afterward we launch a `ClusteredServiceContainer` which is our application.\n\n<4> Use the shutdown barrier to await a signal to shut down (e.g. using `SIG_TERM` or `SIG_INT` on Unix).\n\nThere is a script provide to launch the cluster. Assuming you've followed the <<usingthesource>> step above you\nshould be able to do the following:\n\n// TODO need to add windows command examples.\n\n----\n> cd <AERON_HOME>/aeron-samples/scripts/cluster\n> ./basic-auction-cluster\n----\n\nThis will spit out a lot of logging but should end with something like:\n\n----\n[4452.593894535] CLUSTER: STATE_CHANGE [22/22]: memberId=0 INIT -> ACTIVE\n[4452.594377676] CLUSTER: ELECTION_STATE_CHANGE [23/23]: memberId=0 INIT -> CANVASS\n[4452.599524774] CLUSTER: CANVASS_POSITION [28/28]: logLeadershipTermId=-1 leadershipTermId=-1 logPosition=0 followerMemberId=1\n[4452.60341655] CLUSTER: CANVASS_POSITION [28/28]: logLeadershipTermId=-1 leadershipTermId=-1 logPosition=0 followerMemberId=2\n[4452.603442309] CLUSTER: ELECTION_STATE_CHANGE [27/27]: memberId=0 CANVASS -> NOMINATE\n[4452.612230425] CLUSTER: ELECTION_STATE_CHANGE [36/36]: memberId=0 NOMINATE -> CANDIDATE_BALLOT\n[4452.612255072] CLUSTER: ROLE_CHANGE [29/29]: memberId=0 FOLLOWER -> CANDIDATE\n[4452.6157387] CLUSTER: ELECTION_STATE_CHANGE [50/50]: memberId=0 CANDIDATE_BALLOT -> LEADER_LOG_REPLICATION\n[4452.627815713] CLUSTER: ELECTION_STATE_CHANGE [47/47]: memberId=0 LEADER_LOG_REPLICATION -> LEADER_REPLAY\n[4452.627835581] CLUSTER: ELECTION_STATE_CHANGE [36/36]: memberId=0 LEADER_REPLAY -> LEADER_INIT\n[4452.627844468] CLUSTER: ROLE_CHANGE [27/27]: memberId=0 CANDIDATE -> LEADER\n[4452.653330292] CLUSTER: ELECTION_STATE_CHANGE [35/35]: memberId=0 LEADER_INIT -> LEADER_READY\n[4452.759977752] CLUSTER: ELECTION_STATE_CHANGE [30/30]: memberId=0 LEADER_READY -> CLOSED\n----\n\nWe now have our new service running in a cluster.\n\nYou can shutdown the nodes using the following:\n----\n> pkill -f BasicAuctionClusteredServiceNode\n----\n\nYou should also be able to see the stored data for each of the nodes in the working directory.\n\n----\n> ls\nbasic-auction-client  basic-auction-cluster  logs  node0  node1  node2  script-common\n----\n\nLet's look briefly how the script launches the code.\n\n[source, Bash, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleBaseDir}/scripts/cluster/basic-auction-cluster[tag=start_jvm]\n----\n\nThe `-javaagent:../../../aeron-agent/build/libs/aeron-agent-${VERSION}-all.jar` allows the weaving of the Aeron's\nlogging agent into the running code. Specifying `-Daeron.event.cluster.log=all` tells the agent to log all the events\nrelating to the cluster operation.\n\n== Using a Cluster Client\n\nWhile we now have our cluster up and running, we can't do anything with it until we have a client that will interact\nwith the cluster.\n\n=== Connecting to a Cluster\n\nFirst off, we need a way to connect our client to the cluster. All the messaging between the client and the cluster\nhappens via Aeron, but we have an additional layer called `AeronCluster` that handles connections to the independent\nnodes and provides methods for offering messages into the cluster and polling for responses.\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusterClient.java[tag=client]\n----\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusterClient.java[tag=connect]\n----\n\n<1> Launch a media driver that will allow communication with the cluster nodes. We're using an embedded instance here\nto make it easier to launch multiple drivers on the same machine for use in this tutorial, but it would be perfectly\nviable to launch a single media driver and have multiple clients on the same host sharing the same media driver. We're\nalso avoiding using the media drivers that each of the cluster nodes are using.\n\n<2> This is where we bind our client application code to the responses from the cluster. This requires implementing the\n`EgressListener` interface, which provides callbacks for session messages, session related events (e.g. errors), and\ncluster events (e.g. newly elected leaders).\n\n<3> Specify the channel to receive egress responses back from the cluster. In our case we are using a UDP unicast\nresponse, which will limit responses to the session that sent the ingress message. We are using port 0 to allow for\nmultiple clients on the same machine with multiple media drivers and to avoid having to specify an actual port per\ncluster client.\n\n+\n\nIt is possible to specify a multicast address here if you wanted all clients to see all responses. Some systems may\nfind this useful, e.g. an exchange with multiple gateways may wish to have the execution for a trade sent to a client\nthat was on the passive side of a trade.\n\n<4> Specify the ingress channel for the cluster. In our case as we are using multiple destinations we need to use the\ntemplate style approach that was used when setting up the Consensus Module. We just identify that access to the cluster\nis via UDP.\n\n<5> Identify the actual static endpoints for the cluster. Will be merged into the configuration specified for the\ningress channel.\n\nNOTE: This example is using UDP unicast for the ingress and egress channels. It is possible to use multicast, but you\nwill need to be in an environment where multicast is supported. In cloud deployments UDP multicast is typically not\nsupported or in very early stages, so unicast is currently recommended in those environments.\n\n=== Publishing to the Cluster\n\nNext we are going to push messages into the cluster itself. So that the client is able to fail over to different nodes,\nas nodes come and go, in the cluster the Aeron publications are contained in a class called `AeronCluster`. This is the\nsecond of the two items that are connected at start up. This class provides an API very similar to `Publication`.\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusterClient.java[tag=publish]\n----\n\n<1> In much the same way that we would send messages to a `Publication` we put the data that we want to send into an\nAgrona `DirectBuffer`. The `AeronCluster` contains offer methods that behave in the same way as the `Publication` offer\nmethods.\n\n<2> Publish the data in the same way we would with a `Publication`. We must check the return value to ensure that the\nmessage has been sent because back-pressure could happen at any time.\n\n<3> If we fail to send, and need to run an idle loop, we should utilise an `IdleStrategy` to ensure that we don't\ninappropriately overuse the CPU. With cluster clients we should also be continually polling the egress stream to consume\nany messages that have been sent back from the cluster, including errors or session status messages.\n\n=== Receiving Responses\n\nAs was mentioned previously, our client must have a class which implements the `EgressListener` interface to consume\nmessages that come back from the cluster. The responses can be application messages, session events (e.g. error\nmessages), or notifications of a new leader. In this tutorial there is not a lot to do, therefore we simply print out\nthe responses.\n\n[source, Java, indent=0, options=\"nowrap\"]\n----\ninclude::{sampleSourceDir}/io/aeron/samples/cluster/tutorial/BasicAuctionClusterClient.java[tag=response]\n----\n\n=== Using the Cluster\n\nNow we have all the pieces in place we can start to run the cluster and client together and see how the cluster behaves.\nYou will need at least three terminal windows as we will leave the one we used to start the cluster open showing logging\ninformation for the cluster.\n\nFirstly, start up the cluster.\n\n----\n> cd <AERON_HOME>/aeron-samples/scripts/cluster\n> ./basic-auction-cluster\n----\n\nWait until you see a message that indicates one of the nodes is now leader.\n\n----\n[57240.190127954] CLUSTER: ELECTION_STATE_CHANGE [29/29]: memberId=1, LEADER_READY -> CLOSE\n----\n\nNow in a second terminal window, run a client that will place some bids into our cluster hosted auction. The `10`\nparameter is the customer id to use for the client, change this number if you want to run multiple clients.\n\n----\n> cd <AERON_HOME>/aeron-samples/scripts/cluster\n> ./basic-auction-client 10\n----\n\nWe should see some output similar to the following:\n\n----\nSent(8474093274723075706, 10, 105) bidsRemaining=9\nSessionMessage(1, 8474093274723075706, 10, 105, true)\nSent(8474093274723075707, 10, 109) bidsRemaining=8\nSessionMessage(1, 8474093274723075707, 10, 109, true)\nSent(8474093274723075708, 10, 110) bidsRemaining=7\nSessionMessage(1, 8474093274723075708, 10, 110,true)\nSent(8474093274723075709, 10, 119) bidsRemaining=6\nSessionMessage(1, 8474093274723075709, 10, 119, true)\nSent(8474093274723075710, 10, 122) bidsRemaining=5\nSessionMessage(1, 8474093274723075710, 10, 122, true)\nSent(8474093274723075711, 10, 127) bidsRemaining=4\nSessionMessage(1, 8474093274723075711, 10, 127, true)\nSent(8474093274723075712, 10, 129) bidsRemaining=3\nSessionMessage(1, 8474093274723075712, 10, 129, true)\nSent(8474093274723075713, 10, 135) bidsRemaining=2\nSessionMessage(1, 8474093274723075713, 10, 135, true)\nSent(8474093274723075714, 10, 136) bidsRemaining=1\nSessionMessage(1, 8474093274723075714, 10, 136,true)\nSent(8474093274723075715, 10, 142) bidsRemaining=0\nSessionMessage(1, 8474093274723075715, 10, 142, true)\n----\n\n=== Failed Nodes and Leader Election\n\nNow the cluster is up and running, and we are able to publish messages and subscribe to responses and events, we can\nlook at some additional tools available for Cluster and experiment with the fault tolerance functionality.\n\nFirst thing we are interested in is which of the three node is the leader, with this information we can try shutdown the\nleader and see if our client is able to continue.\n\n[subs = \"attributes+\"]\n----\n> java -cp <AERON_HOME>/aeron-all/build/libs/aeron-all-{aeronVersion}.jar \\\n  io.aeron.cluster.ClusterTool node0/cluster list-members\n----\n\nThis will show a list of all the members in the cluster, similar to the following:\n\n----\ncurrentTimeNs=1579564309666000000, leaderMemberId=0, memberId=0, activeMembers=[ClusterMember{isBallotSent=false,\nisLeader=false, hasRequestedJoin=false, id=0, leadershipTermId=5, logPosition=11872, candidateTermId=-1,\ncatchupReplaySessionId=-1, correlationId=-1, removalPosition=-1, timeOfLastAppendPositionNs=1579564309666000000,\ningressEndpoint='localhost:9003', consensusEndpoint='localhost:9004', logEndpoint='localhost:9005',\ncatchupEndpoint='localhost:9006', archiveEndpoint='localhost:9001',\nendpoints='localhost:9003,localhost:9004,localhost:9005,localhost:9006,localhost:9001', publication=null,\nvote=null}, ClusterMember{isBallotSent=false, isLeader=false, hasRequestedJoin=false, id=1, leadershipTermId=5,\nlogPosition=11872, candidateTermId=-1, catchupReplaySessionId=-1, correlationId=-1, removalPosition=-1,\ntimeOfLastAppendPositionNs=1579564309495000000, ingressEndpoint='localhost:9103',\nconsensusEndpoint='localhost:9104', logEndpoint='localhost:9105', catchupEndpoint='localhost:9106',\narchiveEndpoint='localhost:9101',\nendpoints='localhost:9103,localhost:9104,localhost:9105,localhost:9106,localhost:9101', publication=null,\nvote=null}, ClusterMember{isBallotSent=false, isLeader=false, hasRequestedJoin=false, id=2, leadershipTermId=5,\nlogPosition=11872, candidateTermId=-1, catchupReplaySessionId=-1, correlationId=-1, removalPosition=-1,\ntimeOfLastAppendPositionNs=1579564309552000000, ingressEndpoint='localhost:9203',\nconsensusEndpoint='localhost:9204', logEndpoint='localhost:9205', catchupEndpoint='localhost:9206',\narchiveEndpoint='localhost:9201',\nendpoints='localhost:9203,localhost:9204,localhost:9205,localhost:9206,localhost:9201', publication=null,\nvote=null}], passiveMembers=[]\n----\n\nThe part we are interested in is the `leaderMemberId`, this allows us to identify the leader in order to shut it down.\nWe can discover the `pid` for that member using the following command:\n\n[subs = \"attributes+\"]\n----\n> java -cp <AERON_HOME>/aeron-all/build/libs/aeron-all-{aeronVersion}.jar \\\n  io.aeron.cluster.ClusterTool node<leaderMemberId>/cluster pid\n15060\n----\n\nNote the second argument to the `ClusterTool` is the directory where the Consensus Module's stores its data, and we have\nsubstituted the `leaderMemberId` to locate the correct directory for the leader.\n\nThis will have printed out a simple number which is the pid of the leader process.\n\nNext start the client again, but this time we will let it run a bit longer and attempt to trigger a leader election\nwithin the cluster while it is running.\n\n----\n> cd <AERON_HOME>/aeron-samples/scripts/cluster\n> ./basic-auction-client 10 1000 3000\n----\n\nOnce the client is running, switch to a different terminal and kill the leader process using the pid from the earlier\nstep.\n\n----\n> kill <pid>\n----\n\nThe system will stall for a short while until the followers timeout after not receiving a heartbeat from the leader\nbefore deciding to elect a new one. The logs for the cluster will contain something similar to:\n\n----\n==> logs/cluster-0.log <==\n[0] Exiting\nConsensus Module\nio.aeron.cluster.client.ClusterException: heartbeat timeout from leader\n        at io.aeron.cluster.ConsensusModuleAgent.slowTickWork(ConsensusModuleAgent.java:1884)\n        at io.aeron.cluster.ConsensusModuleAgent.doWork(ConsensusModuleAgent.java:288)\n        at org.agrona.concurrent.AgentRunner.doDutyCycle(AgentRunner.java:283)\n        at org.agrona.concurrent.AgentRunner.run(AgentRunner.java:164)\n        at java.base/java.lang.Thread.run(Thread.java:834)\nConsensus Module\nio.aeron.cluster.client.ClusterException: heartbeat timeout from leader\n        at io.aeron.cluster.ConsensusModuleAgent.slowTickWork(ConsensusModuleAgent.java:1884)\n        at io.aeron.cluster.ConsensusModuleAgent.doWork(ConsensusModuleAgent.java:288)\n        at org.agrona.concurrent.AgentRunner.doDutyCycle(AgentRunner.java:283)\n        at org.agrona.concurrent.AgentRunner.run(AgentRunner.java:164)\n        at java.base/java.lang.Thread.run(Thread.java:834)\n...\n[65188.006731058] CLUSTER: ELECTION_STATE_CHANGE [42/42]: memberId=1, FOLLOWER_BALLOT -> FOLLOWER_REPLAY\n[65188.006791979] CLUSTER: ELECTION_STATE_CHANGE [54/54]: memberId=1, FOLLOWER_REPLAY -> FOLLOWER_CATCHUP_TRANSITION\n[65188.009951316] CLUSTER: ELECTION_STATE_CHANGE [55/55]: memberId=1, FOLLOWER_CATCHUP_TRANSITION -> FOLLOWER_CATCHUP\n[65188.028244817] CLUSTER: ELECTION_STATE_CHANGE [47/47]: memberId=1, FOLLOWER_CATCHUP -> FOLLOWER_TRANSITION\n[65188.028355173] CLUSTER: ELECTION_STATE_CHANGE [45/45]: memberId=1, FOLLOWER_TRANSITION -> FOLLOWER_READY\n[65188.02995347] CLUSTER: ELECTION_STATE_CHANGE [31/31]: memberId=1, FOLLOWER_READY -> CLOSE\n\n==> logs/cluster-2.log <==\n[65188.033401987] CLUSTER: ELECTION_STATE_CHANGE [29/29]: memberId=2, LEADER_READY -> CLOSE\n----\n\nShowing that a new leader has been elected to lead the cluster. At this point the cluster will not be able to tolerate\nanother failure and continue.\n\nThe client should automatically resume sending messages into the cluster and receiving responses. There may, or may\nnot, be an exception logged by the client depending on timing.\n\n----\nSent(-7819190107498904370, 10, 12969) bidsRemaining=980\nSessionMessage(20, -7819190107498904370, 10, 12969, true)\nSent(-7819190107498904369, 10, 12969) bidsRemaining=979\nSent(-7819190107498904368, 10, 12970) bidsRemaining=978\nSent(-7819190107498904367, 10, 12978) bidsRemaining=977\nSent(-7819190107498904366, 10, 12975) bidsRemaining=976\nSent(-7819190107498904365, 10, 12971) bidsRemaining=975\nConsensus Module\nio.aeron.cluster.client.ClusterException: heartbeat timeout from leader\n        at io.aeron.cluster.ConsensusModuleAgent.slowTickWork(ConsensusModuleAgent.java:1884)\n        at io.aeron.cluster.ConsensusModuleAgent.doWork(ConsensusModuleAgent.java:288)\n        at org.agrona.concurrent.AgentRunner.doDutyCycle(AgentRunner.java:283)\n        at org.agrona.concurrent.AgentRunner.run(AgentRunner.java:164)\n        at java.lang.Thread.run(Thread.java:748)\nNewLeader(20, 7, 1)\nSent(-7819190107498904364, 10, 12977) bidsRemaining=974\nSent(-7819190107498904363, 10, 12974) bidsRemaining=973\nSessionMessage(20, -7819190107498904364, 10, 12977, true)\nSessionMessage(20, -7819190107498904363, 10, 12977, false)\n----\n\nIMPORTANT: If you look carefully at the `correlationId` values you will notice there are no `SessionMessage` log entries\nfor a number of the messages that were sent. This is because when the leader fails some messages sent into the cluster\ncan be lost. For this reason it is important for the client to track responses for its messages and provide timeouts to\nthe caller if required.\n\nWe can restore the failed node back into service:\n\n----\n> cd <AERON_HOME>/aeron-samples/scripts/cluster\n> ./basic-auction-cluster <leaderMemberId>\n----\n\nWhere the `leaderMemberId` is the id of the member that was killed (not the new leader of the cluster). The cluster logs\nwill show the member rejoining the cluster:\n\n----\n[0] Started Cluster Node on localhost...\n[66250.672741109] CLUSTER: STATE_CHANGE [22/22]: memberId=0, INIT -> ACTIVE\n[66250.687237677] CLUSTER: ELECTION_STATE_CHANGE [23/23]: memberId=0, INIT -> CANVASS\n[66250.689468615] CLUSTER: NEW_LEADERSHIP_TERM [40/40]: logLeadershipTermId=6, leadershipTermId=6, logPosition=19424, timestamp=1579566538529, leaderMemberId=2, logSessionId=-1889384498\n[66250.689493099] CLUSTER: ELECTION_STATE_CHANGE [34/34]: memberId=0, CANVASS -> FOLLOWER_REPLAY\nonSessionOpen(ClientSession{id=1, responseStreamId=102, responseChannel='aeron:udp?endpoint=localhost:19132', encodedPrincipal=[], clusteredServiceAgent=io.aeron.cluster.service.ClusteredServiceAgent@19a05123, responsePublication=null, isClosing=false})\nattemptBid(this=Auction{bestPrice=0, currentWinningCustomerId=-1}, price=123,customerId=132)\nonSessionClose(ClientSession{id=1, responseStreamId=102, responseChannel='aeron:udp?endpoint=localhost:19132', encodedPrincipal=[], clusteredServiceAgent=io.aeron.cluster.service.ClusteredServiceAgent@19a05123, responsePublication=null, isClosing=false})\nonSessionOpen(ClientSession{id=2, responseStreamId=102, responseChannel='aeron:udp?endpoint=localhost:19132', encodedPrincipal=[], clusteredServiceAgent=io.aeron.cluster.service.ClusteredServiceAgent@19a05123, responsePublication=null, isClosing=false})\n----\n\nYou can see that the new node looks like it is re-executing application messages. That is because it is. It is\nreplaying the log of the messages that have been sent into the cluster up to this point in order to restore its\nin-memory state to what it was before the failure.\n\n=== Snapshots\n\nEarlier in the tutorial we looked at how a node can store a snapshot of its current state so that it would not need to\nreplay every single application message since the beginning of time. We can look at the recording log for a node to see\nthe logs, and snapshots, that are stored for a node in the cluster. We can also use the tool to trigger a snapshot.\n\nFirstly lets look at the recording log.\n\n[subs=\"attributes+\"]\n----\n> java -cp <AERON_HOME>/aeron-all/build/libs/aeron-all-{aeronVersion}.jar \\\n  io.aeron.cluster.ClusterTool node0/cluster recording-log\n----\n\nWhich should show a recording log with a single entry:\n\n----\nRecordingLog{entries=[Entry{recordingId=0, leadershipTermId=0, termBaseLogPosition=0, logPosition=-1, timestamp=1579569542227, serviceId=-1, type=0, isValid=true, entryIndex=0}], cacheIndex={0=0}}\n----\n\nWe can trigger a snapshot on the leader.\n\n----\n> java -cp <AERON_HOME>/aeron-all/build/libs/aeron-all-{aeronVersion}.jar \\\n  io.aeron.cluster.ClusterTool node2/cluster snapshot\n----\n\nThe recording log will now have additional entries.\n\n----\nRecordingLog{entries=[Entry{recordingId=0, leadershipTermId=0, termBaseLogPosition=0, logPosition=-1, timestamp=1579569542227, serviceId=-1, type=0, isValid=true, entryIndex=0}, Entry{recordingId=1, leadershipTermId=0, termBaseLogPosition=0, logPosition=1376, timestamp=1579570195606, serviceId=0, type=1, isValid=true, entryIndex=1}, Entry{recordingId=2, leadershipTermId=0, termBaseLogPosition=0, logPosition=1376, timestamp=1579570195606, serviceId=-1, type=1, isValid=true, entryIndex=2}], cacheIndex={0=0}}\n----\n\nThere are two more entries. One for the snapshot just taken and a new term entry to indicate the point within the log\nthe service should start recovering from where it to use the new snapshot on re-start.\n\n== Exercises\n\nHere are some exercises that you can try out.\n\n. Have multiple customers competing on the same auction.\n. Use multicast for a generic egress channel (common to all clients) so that customers can see others prices to make informed bids.\n. Implement a second message type that will allow customers to query for the current bid price of the auction.\n\n//== Timers\n//\n//== Multiple Services Per Cluster Node\n//\n//== More Complex State\n//\n//== Dynamically Add/Removing Nodes from a Cluster\n\n\n////\n<1> To manage fragmenting the arbitrary sized state we are going to serialize our state to a single buffer. In this case\nwe are using the `snapshotMessageBuffer` as our temporary buffer for the keys and their counts. This makes sense when\nthe state of the system is as simple as this. However, in a more realistic scenario you probably wouldn't want to\nserialize the whole state of a service to a single buffer, you may want to break up this by\nhttps://dddcommunity.org/resources/ddd_terms/[repository or entity]\n\n<2> You'll notice that we are sorting the output before writing to the buffer. While not strictly required, it is highly\nrecommended that snapshots be written with data in a consistent order. This means that if at some point in the future\nyou wanted to verify snapshots written by different services that have logically consumed the same messages then it can\nbe done as a straight forward binary comparison. A sort is required here because `HashMap` iteration is not\ndeterministic (especially in cases where services are at the same point in the log, but initialised from snapshots\ntaken at different times). An alternative would be to use a collection that has a known order, e.g. `TreeMap`.\n\n<3> Two things are happening here. Firstly, we mentioned the necessity of having a protocol to handle fragmentation of\nour data when sending via a `Publication`. In our case we are going to have a very minimal header, consisting of two\n32-bit values, the current offset in to the full buffer of data and the total size. This will allow a consumer to know\nhow far through the total set of data this fragment is, and can be used for sanity checking on load. Secondly, we are\ndetermining the size of the individual Aeron message being sent. Given that we will be handling fragmentation ourselves,\nthere is no point in having Aeron fragment and reconstruct messages for us, so we should limit the size of the messages\nsuch that we do not exceed the `MTU`, so this calculation tells us the maximum amount of the encoded\n`snapshotMessageBuffer` we can send with each message.\n\n<4> Write out our header to the first two 32-bit entries in the fragment.\n\n<5> Get the amount of data from the full serialized message that we want to send, either fill up the MTU or use whatever\nis remaining, whichever is smallest.\n\n<6> Write the header and the message data to the publication using the gathering API.\n\nBefore moving onto configuring our service to run in a cluster, a couple of points to remember.\n\n[TIP]\n====\n. Treat snapshot store and load as a messaging problem using a protocol to efficiently deal with fragmentation.\n. Ensure message serialisation is encapsulated, preferably with a tool that provides a schema for the serialised data.\n. Write data to the snapshot in a deterministic order to allow for verification later.\n====\n////"
  },
  {
    "path": "aeron-samples/src/main/c/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n    set(CMAKE_REQUIRED_DEFINITIONS \"-D_GNU_SOURCE\")\n    add_definitions(-D_DEFAULT_SOURCE)\nendif ()\n\nif (LINK_SAMPLES_CLIENT_SHARED OR MSVC)\n    set(CLIENT_LINK_LIB \"aeron\")\n    add_definitions(-DCLIENT_SHARED)\nelse()\n    set(CLIENT_LINK_LIB \"aeron_static\")\nendif ()\n\ninclude_directories(${AERON_CLIENT_SOURCE_PATH})\n\nset(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -DDISABLE_BOUNDS_CHECKS\")\n\nset(SOURCES\n    sample_util.c)\n\nset(HEADERS\n    samples_configuration.h\n    sample_util.h)\n\nadd_executable(basic_publisher basic_publisher.c ${HEADERS} ${SOURCES})\nadd_executable(basic_subscriber basic_subscriber.c ${HEADERS} ${SOURCES})\nadd_executable(basic_mds_subscriber basic_mds_subscriber.c ${HEADERS} ${SOURCES})\nadd_executable(rate_subscriber rate_subscriber.c ${HEADERS} ${SOURCES})\nadd_executable(streaming_publisher streaming_publisher.c ${HEADERS} ${SOURCES})\nadd_executable(streaming_exclusive_publisher streaming_exclusive_publisher.c ${HEADERS} ${SOURCES})\nadd_executable(cpong cpong.c ${HEADERS} ${SOURCES})\nadd_executable(cping cping.c ${HEADERS} ${SOURCES})\nadd_executable(response_client response/response_client.c ${HEADERS} ${SOURCES})\nadd_executable(response_server response/response_server.c ${HEADERS} ${SOURCES})\n\nif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n    add_executable(ping_pong_raw raw/ping_pong_raw.c)\n    add_dependencies(ping_pong_raw hdr_histogram)\n    target_link_libraries(ping_pong_raw m hdr_histogram_static)\nendif ()\n\ntarget_link_libraries(basic_publisher\n    ${CLIENT_LINK_LIB})\n\ntarget_link_libraries(basic_subscriber\n    ${CLIENT_LINK_LIB})\n\ntarget_link_libraries(basic_mds_subscriber\n    ${CLIENT_LINK_LIB})\n\ntarget_link_libraries(rate_subscriber\n    ${CLIENT_LINK_LIB})\n\ntarget_link_libraries(streaming_publisher\n    ${CLIENT_LINK_LIB})\n\ntarget_link_libraries(streaming_exclusive_publisher\n    ${CLIENT_LINK_LIB})\n\ntarget_link_libraries(cpong\n    ${CLIENT_LINK_LIB})\n\ntarget_link_libraries(cping\n    ${CLIENT_LINK_LIB}\n    hdr_histogram_static)\n\ntarget_link_libraries(response_client\n    ${CLIENT_LINK_LIB})\n\ntarget_link_libraries(response_server\n    ${CLIENT_LINK_LIB})\n\nadd_dependencies(cping hdr_histogram)\n\nadd_executable(AeronStat aeron_stat.c ${HEADERS})\ntarget_link_libraries(AeronStat ${CLIENT_LINK_LIB})\n\nadd_executable(DriverTool driver_tool.c ${HEADERS})\ntarget_link_libraries(DriverTool ${CLIENT_LINK_LIB})\n\nadd_executable(ErrorStat error_stat.c ${HEADERS})\ntarget_link_libraries(ErrorStat ${CLIENT_LINK_LIB})\n\nadd_executable(LossStat loss_stat.c ${HEADERS})\ntarget_link_libraries(LossStat ${CLIENT_LINK_LIB})\n\nif (AERON_INSTALL_TARGETS)\n    install(\n        TARGETS\n        basic_publisher\n        basic_subscriber\n        rate_subscriber\n        streaming_publisher\n        streaming_exclusive_publisher\n        cpong\n        cping\n        response_client\n        response_server\n        AeronStat\n        DriverTool\n        ErrorStat\n        LossStat\n        DESTINATION bin)\nendif ()\n"
  },
  {
    "path": "aeron-samples/src/main/c/aeron_stat.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <signal.h>\n#include <inttypes.h>\n#include <errno.h>\n#include <string.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#include <getopt.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"aeron_common.h\"\n#include \"util/aeron_strutil.h\"\n#include \"concurrent/aeron_thread.h\"\n#include \"concurrent/aeron_atomic.h\"\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\nstatic const char *aeron_stat_usage(void)\n{\n    return\n        \"    -h               Displays help information.\\n\"\n        \"    -d basePath      Base Path to shared memory. Default: /dev/shm/aeron-[user]\\n\"\n        \"    -u update period Update period in milliseconds. Default: 1000\\n\"\n        \"    -t timeout       Number of milliseconds to wait to see if the driver metadata is available. Default: 1000\\n\"\n        \"    -w watch         Set to 'false' to print stats and exit immediately. Default: true\\n\";\n}\n\nstatic void aeron_stat_print_error_and_usage(const char *message)\n{\n    fprintf(stderr, \"%s\\n%s\", message, aeron_stat_usage());\n}\n\ntypedef struct aeron_stat_settings_stct\n{\n    const char *base_path;\n    int64_t update_interval_ms;\n    int64_t timeout_ms;\n    bool watch;\n}\naeron_stat_settings_t;\n\nstatic void aeron_stat_print_counter(\n    int64_t value,\n    int32_t id,\n    int32_t type_id,\n    const uint8_t *key,\n    size_t key_length,\n    const char *label,\n    size_t label_length,\n    void *clientd)\n{\n    const char *format_string = (char *)clientd;\n    char value_str[AERON_FORMAT_NUMBER_TO_LOCALE_STR_LEN];\n    printf(\n        format_string,\n        id,\n        aeron_format_number_to_locale(value, value_str, sizeof(value_str)),\n        (int)label_length,\n        label);\n}\n\nstatic void printStats(\n    aeron_cnc_t *aeron_cnc,\n    aeron_cnc_constants_t *cnc_constants,\n    aeron_counters_reader_t *counters_reader,\n    const char *cnc_version)\n{\n    int64_t now_ms = aeron_epoch_clock();\n    char currentTime[AERON_FORMAT_DATE_MAX_LENGTH];\n    aeron_format_date(currentTime, sizeof(currentTime) - 1, now_ms);\n    const int64_t heartbeat_ms = aeron_cnc_to_driver_heartbeat(aeron_cnc);\n\n    printf(\n        \"%s - Aeron Stat (CnC v%s), pid %\" PRId64 \", heartbeat age %\" PRId64 \"ms\\n\",\n        currentTime,\n        cnc_version,\n        (*cnc_constants).pid,\n        now_ms - heartbeat_ms);\n    printf(\"======================================================================\\n\");\n\n    int max_id_width = aeron_digit_count(aeron_counters_reader_max_counter_id(counters_reader));\n    char counter_format_string[30];\n    snprintf(\n        counter_format_string,\n        sizeof(counter_format_string),\n        \"%%%\" PRId32 \"%s: %%26s - %%.*s\\n\",\n        max_id_width,\n        PRId32);\n\n    aeron_counters_reader_foreach_counter(counters_reader, aeron_stat_print_counter, counter_format_string);\n}\n\nint main(int argc, char **argv)\n{\n    char default_directory[AERON_MAX_PATH];\n    aeron_default_path(default_directory, sizeof(default_directory));\n    aeron_stat_settings_t settings =\n        {\n            .base_path = default_directory,\n            .update_interval_ms = 1000,\n            .timeout_ms = 1000,\n            .watch = true\n        };\n\n\n    int opt;\n\n    while ((opt = getopt(argc, argv, \"d:u:t:w:h\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'd':\n                settings.base_path = optarg;\n                break;\n\n            case 't':\n            {\n                errno = 0;\n                char *endptr;\n                settings.timeout_ms = strtoll(optarg, &endptr, 10);\n                if (0 != errno || '\\0' != endptr[0])\n                {\n                    aeron_stat_print_error_and_usage(\"Invalid timeout\");\n                    return EXIT_FAILURE;\n                }\n                break;\n            }\n\n            case 'u':\n            {\n                errno = 0;\n                char *endptr;\n                settings.update_interval_ms = strtoll(optarg, &endptr, 10);\n                if (0 != errno || '\\0' != endptr[0])\n                {\n                    aeron_stat_print_error_and_usage(\"Invalid update interval\");\n                    return EXIT_FAILURE;\n                }\n                break;\n            }\n\n            case 'w':\n            {\n                if (strcmp(optarg, \"false\") == 0)\n                {\n                    settings.watch = false;\n                }\n                break;\n            }\n\n            case 'h':\n                aeron_stat_print_error_and_usage(argv[0]);\n                return EXIT_SUCCESS;\n\n            default:\n                aeron_stat_print_error_and_usage(\"Unknown option\");\n                return EXIT_FAILURE;\n        }\n    }\n\n    aeron_cnc_t *aeron_cnc = NULL;\n    if (aeron_cnc_init(&aeron_cnc, settings.base_path, settings.timeout_ms) < 0)\n    {\n        aeron_stat_print_error_and_usage(aeron_errmsg());\n        return EXIT_FAILURE;\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    aeron_cnc_constants_t cnc_constants;\n    aeron_cnc_constants(aeron_cnc, &cnc_constants);\n    aeron_counters_reader_t *counters_reader = aeron_cnc_counters_reader(aeron_cnc);\n\n    char cnc_version[12];\n    snprintf(cnc_version, sizeof(cnc_version), \"%\" PRIu8 \".%\" PRIu8 \".%\" PRIu8,\n        aeron_semantic_version_major(cnc_constants.cnc_version),\n        aeron_semantic_version_minor(cnc_constants.cnc_version),\n        aeron_semantic_version_patch(cnc_constants.cnc_version));\n\n    if (settings.watch)\n    {\n\n        while (running)\n        {\n            printf(\"\\033[2J\\033[H\");\n            printStats(aeron_cnc, &cnc_constants, counters_reader, cnc_version);\n            aeron_micro_sleep((unsigned int)(settings.update_interval_ms * 1000));\n        }\n    }\n    else\n    {\n        printStats(aeron_cnc, &cnc_constants, counters_reader, cnc_version);\n    }\n\n    aeron_cnc_close(aeron_cnc);\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/c/basic_mds_subscriber.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"aeron_agent.h\"\n\n#include \"samples_configuration.h\"\n#include \"sample_util.h\"\n\nconst char usage_str[] =\n    \"[-h][-v][-c uri][-p prefix][-s stream-id]\\n\"\n    \"    -h               help\\n\"\n    \"    -v               show version and exit\\n\"\n    \"    -c uri           use channel specified in uri\\n\"\n    \"    -p prefix        aeron.dir location specified as prefix\\n\"\n    \"    -s stream-id     stream-id to use\\n\";\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nvoid poll_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_subscription_t *subscription = (aeron_subscription_t *)clientd;\n    aeron_subscription_constants_t subscription_constants;\n    aeron_header_values_t header_values;\n\n    if (aeron_subscription_constants(subscription, &subscription_constants) < 0)\n    {\n        fprintf(stderr, \"could not get subscription constants: %s\\n\", aeron_errmsg());\n        return;\n    }\n\n    aeron_header_values(header, &header_values);\n\n    printf(\n        \"Message to stream %\" PRId32 \" from session %\" PRId32 \" (%\" PRIu64 \" bytes) <<%.*s>>\\n\",\n        subscription_constants.stream_id,\n        header_values.frame.session_id,\n        (uint64_t)length,\n        (int)length,\n        buffer);\n}\n\nint main(int argc, char **argv)\n{\n    int status = EXIT_FAILURE, opt;\n    aeron_context_t *context = NULL;\n    aeron_t *aeron = NULL;\n    aeron_async_add_subscription_t *async = NULL;\n    aeron_async_destination_t *async_dest = NULL;\n    aeron_subscription_t *subscription = NULL;\n    aeron_fragment_assembler_t *fragment_assembler = NULL;\n    const char *channel = DEFAULT_CHANNEL;\n    const char *aeron_dir = NULL;\n    const uint64_t idle_duration_ns = UINT64_C(1000) * UINT64_C(1000); /* 1ms */\n    int32_t stream_id = DEFAULT_STREAM_ID;\n\n    while ((opt = getopt(argc, argv, \"hvc:p:s:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'c':\n            {\n                channel = optarg;\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 's':\n            {\n                stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    printf(\"Subscribing to channel %s on Stream ID %\" PRId32 \"\\n\", channel, stream_id);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_async_add_subscription(\n        &async,\n        aeron,\n        \"aeron:udp?control-mode=manual\",\n        stream_id,\n        print_available_image,\n        NULL,\n        print_unavailable_image,\n        NULL) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_subscription: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == subscription)\n    {\n        if (aeron_async_add_subscription_poll(&subscription, async) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    if (aeron_subscription_async_add_destination(&async_dest, aeron, subscription, channel))\n    {\n        fprintf(stderr, \"aeron_subscription_async_add_destination: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    do\n    {\n        const int add_dest_poll_result = aeron_subscription_async_destination_poll(async_dest);\n        if (1 == add_dest_poll_result)\n        {\n            break;\n        }\n        else if (add_dest_poll_result < 0)\n        {\n            fprintf(stderr, \"aeron_subscription_async_destination_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n    while (true);\n\n    printf(\"Subscription channel status %\" PRIu64 \"\\n\", aeron_subscription_channel_status(subscription));\n\n    if (aeron_fragment_assembler_create(&fragment_assembler, poll_handler, subscription) < 0)\n    {\n        fprintf(stderr, \"aeron_fragment_assembler_create: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (is_running())\n    {\n        int fragments_read = aeron_subscription_poll(\n            subscription, aeron_fragment_assembler_handler, fragment_assembler, DEFAULT_FRAGMENT_COUNT_LIMIT);\n\n        if (fragments_read < 0)\n        {\n            fprintf(stderr, \"aeron_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        aeron_idle_strategy_sleeping_idle((void *)&idle_duration_ns, fragments_read);\n    }\n\n    printf(\"Shutting down...\\n\");\n    status = EXIT_SUCCESS;\n\ncleanup:\n    aeron_subscription_close(subscription, NULL, NULL);\n    aeron_close(aeron);\n    aeron_context_close(context);\n    aeron_fragment_assembler_delete(fragment_assembler);\n\n    return status;\n}\n\nextern bool is_running(void);\n"
  },
  {
    "path": "aeron-samples/src/main/c/basic_publisher.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_agent.h\"\n\n#include \"samples_configuration.h\"\n\nconst char usage_str[] =\n    \"[-h][-v][-c uri][-l linger][-m messages][-p prefix][-s stream-id]\\n\"\n    \"    -h               help\\n\"\n    \"    -v               show version and exit\\n\"\n    \"    -f               use a message that will fill the whole MTU\\n\"\n    \"    -c uri           use channel specified in uri\\n\"\n    \"    -l linger        linger at end of publishing for linger seconds\\n\"\n    \"    -m messages      number of messages to send\\n\"\n    \"    -p prefix        aeron.dir location specified as prefix\\n\"\n    \"    -s stream-id     stream-id to use\\n\";\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nint build_large_message(char *buf, size_t len)\n{\n    if (len < 2)\n    {\n        len = 2;\n    }\n\n    int limit = (int)len;\n    buf[0] = '\\n';\n    len -= 1;\n\n    char *current_buf = &buf[1];\n    int line_counter = 0;\n\n    while (limit > 0)\n    {\n        int written = snprintf(\n            current_buf,\n            limit,\n            \"[%3d] This is a line of text fill out a large message to test whether large MTUs cause issues...\\n\",\n            line_counter);\n\n        limit -= written;\n        line_counter++;\n        current_buf = &current_buf[written];\n    }\n\n    return (int)len;\n}\n\nint main(int argc, char **argv)\n{\n    char small_message[256] = { 0 };\n    char large_message[8192] = { 0 };\n    const char *message;\n    int message_len = sizeof(small_message);\n    int status = EXIT_FAILURE, opt;\n    bool fill_mtu = false;\n    aeron_context_t *context = NULL;\n    aeron_t *aeron = NULL;\n    aeron_async_add_publication_t *async = NULL;\n    aeron_publication_t *publication = NULL;\n    const char *channel = DEFAULT_CHANNEL;\n    const char *aeron_dir = NULL;\n    uint64_t linger_ns = DEFAULT_LINGER_TIMEOUT_MS * UINT64_C(1000) * UINT64_C(1000);\n    uint64_t messages = DEFAULT_NUMBER_OF_MESSAGES;\n    int32_t stream_id = DEFAULT_STREAM_ID;\n\n    while ((opt = getopt(argc, argv, \"hvfc:l:m:p:s:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'c':\n            {\n                channel = optarg;\n                break;\n            }\n\n            case 'l':\n            {\n                if (aeron_parse_duration_ns(optarg, &linger_ns) < 0)\n                {\n                    fprintf(stderr, \"malformed linger %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'm':\n            {\n                if (aeron_parse_size64(optarg, &messages) < 0)\n                {\n                    fprintf(stderr, \"malformed number of messages %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 's':\n            {\n                stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'f':\n            {\n                fill_mtu = true;\n                break;\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    printf(\"Publishing to channel %s on Stream ID %\" PRId32 \"\\n\", channel, stream_id);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_async_add_publication(&async, aeron, channel, stream_id) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_publication: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == publication)\n    {\n        if (aeron_async_add_publication_poll(&publication, async) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_publication_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Publication channel status %\" PRIu64 \"\\n\", aeron_publication_channel_status(publication));\n\n    if (fill_mtu)\n    {\n        aeron_publication_constants_t pub_constants = { 0 };\n        aeron_publication_constants(publication, &pub_constants);\n        message_len = build_large_message(large_message, pub_constants.max_payload_length);\n        printf(\"Using message of %d bytes\\n\", message_len);\n    }\n\n    for (size_t i = 0; i < messages && is_running(); i++)\n    {\n        if (fill_mtu)\n        {\n            message = large_message;\n        }\n        else\n        {\n#if defined(_MSC_VER)\n                message_len = sprintf_s(small_message, sizeof(small_message) - 1, \"Hello World! %\" PRIu64, (uint64_t)i);\n#else\n                message_len = snprintf(small_message, sizeof(small_message) - 1, \"Hello World! %\" PRIu64, (uint64_t)i);\n#endif\n            message = small_message;\n        }\n        printf(\"offering %\" PRIu64 \"/%\" PRIu64 \" - \", (uint64_t)i, (uint64_t)messages);\n        fflush(stdout);\n\n        int64_t result = aeron_publication_offer(publication, (const uint8_t *)message, message_len, NULL, NULL);\n\n        if (result > 0)\n        {\n            printf(\"yay!\\n\");\n        }\n        else if (AERON_PUBLICATION_BACK_PRESSURED == result)\n        {\n            printf(\"Offer failed due to back pressure\\n\");\n        }\n        else if (AERON_PUBLICATION_NOT_CONNECTED == result)\n        {\n            printf(\"Offer failed because publisher is not connected to a subscriber\\n\");\n        }\n        else if (AERON_PUBLICATION_ADMIN_ACTION == result)\n        {\n            printf(\"Offer failed because of an administration action in the system\\n\");\n        }\n        else if (AERON_PUBLICATION_CLOSED == result)\n        {\n            printf(\"Offer failed because publication is closed\\n\");\n        }\n        else\n        {\n            printf(\"Offer failed due to unknown reason %\" PRId64 \"\\n\", result);\n        }\n\n        if (!aeron_publication_is_connected(publication))\n        {\n            printf(\"No active subscribers detected\\n\");\n        }\n\n        aeron_nano_sleep(1000ul * 1000ul * 1000ul);\n    }\n\n    printf(\"Done sending.\\n\");\n\n    if (linger_ns > 0)\n    {\n        printf(\"Lingering for %\" PRIu64 \" nanoseconds\\n\", linger_ns);\n        aeron_nano_sleep(linger_ns);\n    }\n\ncleanup:\n    aeron_publication_close(publication, NULL, NULL);\n    aeron_close(aeron);\n    aeron_context_close(context);\n\n    return status;\n}\n\nextern bool is_running(void);\n"
  },
  {
    "path": "aeron-samples/src/main/c/basic_subscriber.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"aeron_agent.h\"\n\n#include \"samples_configuration.h\"\n#include \"sample_util.h\"\n\nconst char usage_str[] =\n    \"[-h][-v][-c uri][-p prefix][-s stream-id]\\n\"\n    \"    -h               help\\n\"\n    \"    -v               show version and exit\\n\"\n    \"    -c uri           use channel specified in uri\\n\"\n    \"    -p prefix        aeron.dir location specified as prefix\\n\"\n    \"    -s stream-id     stream-id to use\\n\";\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nvoid poll_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_subscription_t *subscription = (aeron_subscription_t *)clientd;\n    aeron_subscription_constants_t subscription_constants;\n    aeron_header_values_t header_values;\n\n    if (aeron_subscription_constants(subscription, &subscription_constants) < 0)\n    {\n        fprintf(stderr, \"could not get subscription constants: %s\\n\", aeron_errmsg());\n        return;\n    }\n\n    aeron_header_values(header, &header_values);\n\n    printf(\n        \"Message to stream %\" PRId32 \" from session %\" PRId32 \" (%\" PRIu64 \" bytes) <<%.*s>>\\n\",\n        subscription_constants.stream_id,\n        header_values.frame.session_id,\n        (uint64_t)length,\n        (int)length,\n        buffer);\n}\n\nint main(int argc, char **argv)\n{\n    int status = EXIT_FAILURE, opt;\n    aeron_context_t *context = NULL;\n    aeron_t *aeron = NULL;\n    aeron_async_add_subscription_t *async = NULL;\n    aeron_subscription_t *subscription = NULL;\n    aeron_fragment_assembler_t *fragment_assembler = NULL;\n    const char *channel = DEFAULT_CHANNEL;\n    const char *aeron_dir = NULL;\n    const uint64_t idle_duration_ns = UINT64_C(1000) * UINT64_C(1000); /* 1ms */\n    int32_t stream_id = DEFAULT_STREAM_ID;\n\n    while ((opt = getopt(argc, argv, \"hvc:p:s:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'c':\n            {\n                channel = optarg;\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 's':\n            {\n                stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    printf(\"Subscribing to channel %s on Stream ID %\" PRId32 \"\\n\", channel, stream_id);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_async_add_subscription(\n        &async,\n        aeron,\n        channel,\n        stream_id,\n        print_available_image,\n        NULL,\n        print_unavailable_image,\n        NULL) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_subscription: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == subscription)\n    {\n        if (aeron_async_add_subscription_poll(&subscription, async) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Subscription channel status %\" PRIu64 \"\\n\", aeron_subscription_channel_status(subscription));\n\n    if (aeron_fragment_assembler_create(&fragment_assembler, poll_handler, subscription) < 0)\n    {\n        fprintf(stderr, \"aeron_fragment_assembler_create: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (is_running())\n    {\n        int fragments_read = aeron_subscription_poll(\n            subscription, aeron_fragment_assembler_handler, fragment_assembler, DEFAULT_FRAGMENT_COUNT_LIMIT);\n\n        if (fragments_read < 0)\n        {\n            fprintf(stderr, \"aeron_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        aeron_idle_strategy_sleeping_idle((void *)&idle_duration_ns, fragments_read);\n    }\n\n    printf(\"Shutting down...\\n\");\n    status = EXIT_SUCCESS;\n\ncleanup:\n    aeron_subscription_close(subscription, NULL, NULL);\n    aeron_close(aeron);\n    aeron_context_close(context);\n    aeron_fragment_assembler_delete(fragment_assembler);\n\n    return status;\n}\n\nextern bool is_running(void);\n"
  },
  {
    "path": "aeron-samples/src/main/c/cping.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n#include <string.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include <hdr/hdr_histogram.h>\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"aeron_agent.h\"\n\n#include \"samples_configuration.h\"\n#include \"sample_util.h\"\n#include \"aeron_alloc.h\"\n\nconst char usage_str[] =\n    \"[-h][-v][-C uri][-c uri][-p prefix][-S stream-id][-s stream-id]\\n\"\n    \"    -h               help\\n\"\n    \"    -v               show version and exit\\n\"\n    \"    -C uri           use channel specified in uri for pong channel\\n\"\n    \"    -c uri           use channel specified in uri for ping channel\\n\"\n    \"    -L length        use message length of length bytes\\n\"\n    \"    -m messages      number of messages to send\\n\"\n    \"    -p prefix        aeron.dir location specified as prefix\\n\"\n    \"    -S stream-id     stream-id to use for pong channel\\n\"\n    \"    -s stream-id     stream-id to use for ping channel\\n\"\n    \"    -w messages      number of warm up messages to send\\n\";\n\ntypedef struct aeron_cping_pong_handler_data_stct\n{\n    size_t receive_count;\n    struct hdr_histogram *histogram;\n}\naeron_cping_pong_handler_data_t;\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nvoid pong_measuring_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_cping_pong_handler_data_t *data = (aeron_cping_pong_handler_data_t *)clientd;\n    data->receive_count++;\n\n    int64_t end_ns = aeron_nano_clock();\n    int64_t start_ns;\n\n    memcpy(&start_ns, buffer, sizeof(uint64_t));\n    hdr_record_value(data->histogram, end_ns - start_ns);\n}\n\nvoid send_ping_and_receive_pong(\n    aeron_exclusive_publication_t *publication,\n    aeron_image_t *image,\n    aeron_fragment_handler_t fragment_handler,\n    void *poll_clientd,\n    aeron_cping_pong_handler_data_t *pong_handler_data,\n    uint64_t messages,\n    const uint8_t *message,\n    size_t message_length)\n{\n    pong_handler_data->receive_count = 0;\n\n    int64_t *timestamp = (int64_t *)message;\n\n    for (size_t i = 0; i < messages && is_running(); i++)\n    {\n        do\n        {\n            *timestamp = aeron_nano_clock();\n        }\n        while (aeron_exclusive_publication_offer(\n            publication, message, message_length, NULL, NULL) < 0);\n\n        while (pong_handler_data->receive_count < (i + 1))\n        {\n            if (aeron_image_poll(image, fragment_handler, poll_clientd, DEFAULT_FRAGMENT_COUNT_LIMIT) <= 0)\n            {\n                aeron_idle_strategy_busy_spinning_idle(NULL, 0);\n            }\n        }\n    }\n}\n\nint main(int argc, char **argv)\n{\n    int status = EXIT_FAILURE, opt;\n    aeron_context_t *context = NULL;\n    aeron_t *aeron = NULL;\n    aeron_async_add_subscription_t *async_pong_sub = NULL;\n    aeron_async_add_exclusive_publication_t *async_ping_pub = NULL;\n    aeron_subscription_t *subscription = NULL;\n    aeron_image_t *image = NULL;\n    aeron_exclusive_publication_t *publication = NULL;\n    aeron_image_fragment_assembler_t *fragment_assembler = NULL;\n    const char *pong_channel = DEFAULT_PONG_CHANNEL;\n    const char *ping_channel = DEFAULT_PING_CHANNEL;\n    const char *aeron_dir = NULL;\n    uint64_t messages = DEFAULT_NUMBER_OF_MESSAGES;\n    uint64_t message_length = DEFAULT_MESSAGE_LENGTH;\n    uint64_t warm_up_messages = DEFAULT_NUMBER_OF_WARM_UP_MESSAGES;\n    int32_t pong_stream_id = DEFAULT_PONG_STREAM_ID;\n    int32_t ping_stream_id = DEFAULT_PING_STREAM_ID;\n    uint8_t *message = NULL;\n    aeron_cping_pong_handler_data_t *pong_handler_data = NULL;\n\n    while ((opt = getopt(argc, argv, \"hvC:c:L:m:p:S:s:w:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'C':\n            {\n                pong_channel = optarg;\n                break;\n            }\n\n            case 'c':\n            {\n                ping_channel = optarg;\n                break;\n            }\n\n            case 'L':\n            {\n                if (aeron_parse_size64(optarg, &message_length) < 0)\n                {\n                    fprintf(stderr, \"malformed message length %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'm':\n            {\n                if (aeron_parse_size64(optarg, &messages) < 0)\n                {\n                    fprintf(stderr, \"malformed number of messages %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 'S':\n            {\n                pong_stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 's':\n            {\n                ping_stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'w':\n            {\n                if (aeron_parse_size64(optarg, &warm_up_messages) < 0)\n                {\n                    fprintf(stderr, \"malformed number of warm up messages %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    printf(\"Publishing Ping at channel %s on Stream ID %\" PRId32 \"\\n\", ping_channel, ping_stream_id);\n    printf(\"Subscribing Pong at channel %s on Stream ID %\" PRId32 \"\\n\", pong_channel, pong_stream_id);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_async_add_subscription(\n        &async_pong_sub,\n        aeron,\n        pong_channel,\n        pong_stream_id,\n        print_available_image,\n        NULL,\n        print_unavailable_image,\n        NULL) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_subscription: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == subscription)\n    {\n        if (aeron_async_add_subscription_poll(&subscription, async_pong_sub) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        if (!is_running())\n        {\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Subscription channel status %\" PRIu64 \"\\n\", aeron_subscription_channel_status(subscription));\n\n    if (aeron_async_add_exclusive_publication(\n        &async_ping_pub,\n        aeron,\n        ping_channel,\n        ping_stream_id) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_exclusive_publication: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == publication)\n    {\n        if (aeron_async_add_exclusive_publication_poll(&publication, async_ping_pub) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_exclusive_publication_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        if (!is_running())\n        {\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Publication channel status %\" PRIu64 \"\\n\", aeron_exclusive_publication_channel_status(publication));\n\n    while (!aeron_subscription_is_connected(subscription))\n    {\n        if (!is_running())\n        {\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    if ((image = aeron_subscription_image_at_index(subscription, 0)) == NULL)\n    {\n        fprintf(stderr, \"%s\", \"could not find image\\n\");\n        goto cleanup;\n    }\n\n    if (aeron_alloc((void**)&message, message_length) < 0)\n    {\n        fprintf(stderr, \"could not allocate message buffer: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n    memset(message, 0, message_length);\n\n    if (aeron_alloc((void **)&pong_handler_data, sizeof(aeron_cping_pong_handler_data_t)) < 0)\n    {\n        fprintf(stderr, \"could not allocate: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (hdr_init(1, 10 * 1000 * 1000 * INT64_C(1000), 3, &pong_handler_data->histogram) < 0)\n    {\n        fprintf(stderr, \"hdr_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_image_fragment_assembler_create(&fragment_assembler, pong_measuring_handler, pong_handler_data) < 0)\n    {\n        fprintf(stderr, \"aeron_image_fragment_assembler_create: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    printf(\"Warming up the media driver with %\" PRIu64 \" messages of length %\" PRIu64 \" bytes\\n\",\n        warm_up_messages, message_length);\n\n    int64_t start_warm_up_ns = aeron_nano_clock();\n\n    send_ping_and_receive_pong(\n        publication,\n        image,\n        aeron_image_fragment_assembler_handler,\n        fragment_assembler,\n        pong_handler_data,\n        warm_up_messages,\n        message,\n        message_length);\n\n    printf(\"Warm up complete in %\" PRId64 \"ns\\n\", aeron_nano_clock() - start_warm_up_ns);\n    printf(\"Pinging %\" PRIu64 \" messages of length %\" PRIu64 \" bytes\\n\", messages, message_length);\n\n    hdr_reset(pong_handler_data->histogram);\n\n    send_ping_and_receive_pong(\n        publication,\n        image,\n        aeron_image_fragment_assembler_handler,\n        fragment_assembler,\n        pong_handler_data,\n        messages,\n        message,\n        message_length);\n\n    hdr_percentiles_print(pong_handler_data->histogram, stdout, 5, 1000.0, CLASSIC);\n    fflush(stdout);\n\n    printf(\"Shutting down...\\n\");\n    status = EXIT_SUCCESS;\n\ncleanup:\n    aeron_subscription_image_release(subscription, image);\n    aeron_subscription_close(subscription, NULL, NULL);\n    aeron_exclusive_publication_close(publication, NULL, NULL);\n    aeron_close(aeron);\n    aeron_context_close(context);\n    aeron_image_fragment_assembler_delete(fragment_assembler);\n    if (NULL != pong_handler_data)\n    {\n        hdr_close(pong_handler_data->histogram);\n    }\n    aeron_free(pong_handler_data);\n    aeron_free(message);\n\n    return status;\n}\n\nextern bool is_running(void);\n"
  },
  {
    "path": "aeron-samples/src/main/c/cpong.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n#include <string.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_agent.h\"\n\n#include \"samples_configuration.h\"\n#include \"sample_util.h\"\n\nconst char usage_str[] =\n    \"[-h][-v][-C uri][-c uri][-p prefix][-S stream-id][-s stream-id]\\n\"\n    \"    -h               help\\n\"\n    \"    -v               show version and exit\\n\"\n    \"    -C uri           use channel specified in uri for pong channel\\n\"\n    \"    -c uri           use channel specified in uri for ping channel\\n\"\n    \"    -p prefix        aeron.dir location specified as prefix\\n\"\n    \"    -S stream-id     stream-id to use for pong channel\\n\"\n    \"    -s stream-id     stream-id to use for ping channel\\n\";\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nvoid ping_poll_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_exclusive_publication_t *publication = (aeron_exclusive_publication_t *)clientd;\n\n    while (aeron_exclusive_publication_offer(publication, buffer, length, NULL, NULL) < 0)\n    {\n        aeron_idle_strategy_busy_spinning_idle(NULL, 0);\n    }\n}\n\nint main(int argc, char **argv)\n{\n    int status = EXIT_FAILURE, opt;\n    aeron_context_t *context = NULL;\n    aeron_t *aeron = NULL;\n    aeron_async_add_subscription_t *async_ping_sub = NULL;\n    aeron_async_add_exclusive_publication_t *async_pong_pub = NULL;\n    aeron_subscription_t *subscription = NULL;\n    aeron_image_t *image = NULL;\n    aeron_exclusive_publication_t *publication = NULL;\n    aeron_image_fragment_assembler_t *fragment_assembler = NULL;\n    const char *pong_channel = DEFAULT_PONG_CHANNEL;\n    const char *ping_channel = DEFAULT_PING_CHANNEL;\n    const char *aeron_dir = NULL;\n    int32_t pong_stream_id = DEFAULT_PONG_STREAM_ID;\n    int32_t ping_stream_id = DEFAULT_PING_STREAM_ID;\n\n    while ((opt = getopt(argc, argv, \"hvC:c:p:S:s:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'C':\n            {\n                pong_channel = optarg;\n                break;\n            }\n\n            case 'c':\n            {\n                ping_channel = optarg;\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 'S':\n            {\n                pong_stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 's':\n            {\n                ping_stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    printf(\"Subscribing Ping at channel %s on Stream ID %\" PRId32 \"\\n\", ping_channel, ping_stream_id);\n    printf(\"Publishing Pong at channel %s on Stream ID %\" PRId32 \"\\n\", pong_channel, pong_stream_id);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_async_add_subscription(\n        &async_ping_sub,\n        aeron,\n        ping_channel,\n        ping_stream_id,\n        print_available_image,\n        NULL,\n        print_unavailable_image,\n        NULL) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_subscription: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == subscription)\n    {\n        if (aeron_async_add_subscription_poll(&subscription, async_ping_sub) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        if (!is_running())\n        {\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Subscription channel status %\" PRIu64 \"\\n\", aeron_subscription_channel_status(subscription));\n\n    if (aeron_async_add_exclusive_publication(\n        &async_pong_pub,\n        aeron,\n        pong_channel,\n        pong_stream_id) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_exclusive_publication: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == publication)\n    {\n        if (aeron_async_add_exclusive_publication_poll(&publication, async_pong_pub) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_exclusive_publication_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        if (!is_running())\n        {\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Publication channel status %\" PRIu64 \"\\n\", aeron_exclusive_publication_channel_status(publication));\n\n    while (!aeron_subscription_is_connected(subscription))\n    {\n        if (!is_running())\n        {\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    if ((image = aeron_subscription_image_at_index(subscription, 0)) == NULL)\n    {\n        fprintf(stderr, \"%s\", \"could not find image\\n\");\n        goto cleanup;\n    }\n\n    if (aeron_image_fragment_assembler_create(&fragment_assembler, ping_poll_handler, publication) < 0)\n    {\n        fprintf(stderr, \"aeron_image_fragment_assembler_create: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (is_running())\n    {\n        int fragments_read = aeron_image_poll(\n            image, aeron_image_fragment_assembler_handler, fragment_assembler, DEFAULT_FRAGMENT_COUNT_LIMIT);\n\n        if (fragments_read < 0)\n        {\n            fprintf(stderr, \"aeron_image_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        aeron_idle_strategy_busy_spinning_idle(NULL, fragments_read);\n    }\n\n    printf(\"Shutting down...\\n\");\n    status = EXIT_SUCCESS;\n\ncleanup:\n    aeron_subscription_image_release(subscription, image);\n    aeron_subscription_close(subscription, NULL, NULL);\n    aeron_exclusive_publication_close(publication, NULL, NULL);\n    aeron_close(aeron);\n    aeron_context_close(context);\n    aeron_image_fragment_assembler_delete(fragment_assembler);\n\n    return status;\n}\n\nextern bool is_running(void);\n"
  },
  {
    "path": "aeron-samples/src/main/c/driver_tool.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#ifndef _MSC_VER\n\n#include <unistd.h>\n#include <getopt.h>\n\n#endif\n\n#include \"aeronc.h\"\n#include \"aeron_common.h\"\n#include \"util/aeron_strutil.h\"\n\ntypedef struct aeron_driver_tool_settings_stct\n{\n    const char *base_path;\n    bool pid_only;\n    bool terminate_driver;\n    long long timeout_ms;\n}\naeron_driver_tool_settings_t;\n\nstatic const char *aeron_driver_tool_usage(void)\n{\n    return\n        \"    -P            Print PID only without anything else.\\n\"\n        \"    -T            Request driver to terminate.\\n\"\n        \"    -d basePath   Base Path to shared memory. Default: /dev/shm/aeron-[user]\\n\"\n        \"    -h            Displays help information.\\n\"\n        \"    -t timeout    Number of milliseconds to wait to see if the driver metadata is available. Default: 1000\\n\";\n}\n\nstatic void aeron_driver_tool_print_error_and_usage(const char *message)\n{\n    fprintf(stderr, \"%s\\n%s\", message, aeron_driver_tool_usage());\n}\n\nint main(int argc, char **argv)\n{\n    char default_directory[AERON_MAX_PATH];\n    if (aeron_default_path(default_directory, sizeof(default_directory)) < 0)\n    {\n        aeron_driver_tool_print_error_and_usage(\"failed to resolve aeron directory path\");\n        return EXIT_FAILURE;\n    }\n\n    aeron_driver_tool_settings_t settings =\n        {\n            .base_path = default_directory,\n            .pid_only = false,\n            .terminate_driver = false,\n            .timeout_ms = 1000\n        };\n\n\n    int opt;\n\n    while ((opt = getopt(argc, argv, \"d:t:PTh\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'd':\n                settings.base_path = optarg;\n                break;\n\n            case 't':\n            {\n                errno = 0;\n                char *endptr;\n                settings.timeout_ms = strtoll(optarg, &endptr, 10);\n                if (0 != errno || '\\0' != endptr[0])\n                {\n                    aeron_driver_tool_print_error_and_usage(\"Invalid timeout\");\n                    return EXIT_FAILURE;\n                }\n                break;\n            }\n\n            case 'P':\n                settings.pid_only = true;\n                break;\n\n            case 'T':\n                settings.terminate_driver = true;\n                break;\n\n            case 'h':\n                aeron_driver_tool_print_error_and_usage(argv[0]);\n                return EXIT_SUCCESS;\n\n            default:\n                aeron_driver_tool_print_error_and_usage(\"Unknown option\");\n                return EXIT_FAILURE;\n        }\n    }\n\n    aeron_cnc_t *aeron_cnc = NULL;\n    aeron_cnc_constants_t cnc_constants;\n\n    if (aeron_cnc_init(&aeron_cnc, settings.base_path, settings.timeout_ms) < 0)\n    {\n        aeron_driver_tool_print_error_and_usage(aeron_errmsg());\n        return EXIT_FAILURE;\n    }\n\n    aeron_cnc_constants(aeron_cnc, &cnc_constants);\n\n    if (settings.pid_only)\n    {\n        printf(\"%\" PRId64 \"\\n\", cnc_constants.pid);\n    }\n    else if (settings.terminate_driver)\n    {\n        aeron_context_request_driver_termination(settings.base_path, NULL, 0);\n    }\n    else\n    {\n        const char *cnc_filename = aeron_cnc_filename(aeron_cnc);\n        int64_t now_ms = aeron_epoch_clock();\n        const int64_t heartbeat_ms = aeron_cnc_to_driver_heartbeat(aeron_cnc);\n\n        char now_timestamp_buffer[AERON_FORMAT_DATE_MAX_LENGTH];\n        char start_timestamp_buffer[AERON_FORMAT_DATE_MAX_LENGTH];\n        char heartbeat_timestamp_buffer[AERON_FORMAT_DATE_MAX_LENGTH];\n        aeron_format_date(now_timestamp_buffer, sizeof(now_timestamp_buffer), now_ms);\n        aeron_format_date(start_timestamp_buffer, sizeof(start_timestamp_buffer), cnc_constants.start_timestamp);\n        aeron_format_date(heartbeat_timestamp_buffer, sizeof(heartbeat_timestamp_buffer), heartbeat_ms);\n\n        fprintf(stdout, \"Command 'n Control cnc_file: %s\\n\", cnc_filename);\n        fprintf(stdout, \"Version: %\" PRId32 \", PID: %\" PRId64 \"\\n\", cnc_constants.cnc_version, cnc_constants.pid);\n        fprintf(\n            stdout,\n            \"%s (start: %s, activity: %s)\\n\",\n            now_timestamp_buffer,\n            start_timestamp_buffer,\n            heartbeat_timestamp_buffer);\n    }\n\n    aeron_cnc_close(aeron_cnc);\n\n    return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/c/error_stat.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <errno.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#include <getopt.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"aeron_common.h\"\n#include \"aeron_cnc_file_descriptor.h\"\n#include \"concurrent/aeron_distinct_error_log.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_error.h\"\n\ntypedef struct aeron_error_stat_settings_stct\n{\n    const char *base_path;\n    int64_t timeout_ms;\n    const char *error_file;\n    size_t error_file_offset;\n}\naeron_error_stat_settings_t;\n\nstatic const char *aeron_error_stat_usage(void)\n{\n    return\n        \"    -h                 Displays help information.\\n\"\n        \"    -d basePath        Base Path to shared memory. Default: /dev/shm/aeron-[user].\\n\"\n        \"    -t timeout         Number of milliseconds to wait to see if the driver metadata is available. Default: 1000.\\n\"\n        \"    -f errorFile       Read from a specific file used as a distinct error log (takes precedence over basePath).\\n\"\n        \"    -o errorFileOffset Offset to read error messages from when using a specific file (default 0).\\n\";\n}\n\nstatic void aeron_error_stat_print_error_and_usage(const char *message)\n{\n    fprintf(stderr, \"%s\\n%s\", message, aeron_error_stat_usage());\n}\n\nvoid aeron_error_stat_on_observation(\n    int32_t observation_count,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    const char *error,\n    size_t error_length,\n    void *clientd)\n{\n    char first_timestamp[AERON_FORMAT_DATE_MAX_LENGTH];\n    char last_timestamp[AERON_FORMAT_DATE_MAX_LENGTH];\n\n    aeron_format_date(first_timestamp, sizeof(first_timestamp), first_observation_timestamp);\n    aeron_format_date(last_timestamp, sizeof(last_timestamp), last_observation_timestamp);\n\n    fprintf(\n        stdout,\n        \"***\\n%d observations from %s to %s for:\\n %.*s\\n\",\n        observation_count,\n        first_timestamp,\n        last_timestamp,\n        (int)error_length,\n        error);\n}\n\n\nint main(int argc, char **argv)\n{\n    char default_directory[AERON_MAX_PATH];\n    aeron_default_path(default_directory, sizeof(default_directory));\n    aeron_error_stat_settings_t settings =\n        {\n            .base_path = default_directory,\n            .timeout_ms = 1000,\n            .error_file = NULL,\n            .error_file_offset = 0\n        };\n\n    int opt;\n\n    while ((opt = getopt(argc, argv, \"d:t:f:o:h\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'd':\n                settings.base_path = optarg;\n                break;\n\n            case 't':\n            {\n                errno = 0;\n                char *endptr;\n                settings.timeout_ms = strtoll(optarg, &endptr, 10);\n                if (0 != errno || '\\0' != endptr[0])\n                {\n                    aeron_error_stat_print_error_and_usage(\"Invalid timeout\");\n                    return EXIT_FAILURE;\n                }\n                break;\n            }\n\n            case 'f':\n                settings.error_file = optarg;\n                break;\n\n            case 'o':\n            {\n                errno = 0;\n                char *endptr;\n                settings.error_file_offset = strtoull(optarg, &endptr, 10);\n                if (0 != errno || '\\0' != endptr[0])\n                {\n                    aeron_error_stat_print_error_and_usage(\"Invalid file offset\");\n                    return EXIT_FAILURE;\n                }\n                break;\n            }\n\n            case 'h':\n                aeron_error_stat_print_error_and_usage(argv[0]);\n                return EXIT_SUCCESS;\n\n            default:\n                aeron_error_stat_print_error_and_usage(\"Unknown option\");\n                return EXIT_FAILURE;\n        }\n    }\n\n    size_t count;\n    if (NULL != settings.error_file)\n    {\n        aeron_mapped_file_t error_mmap;\n\n        if (aeron_map_existing_file(&error_mmap, settings.error_file) < 0)\n        {\n            aeron_error_stat_print_error_and_usage(aeron_errmsg());\n            return EXIT_FAILURE;\n        }\n\n        const uint8_t *error_buffer = (uint8_t *)error_mmap.addr + settings.error_file_offset;\n        const size_t error_buffer_length = error_mmap.length - settings.error_file_offset;\n\n        count = aeron_error_log_read(error_buffer, error_buffer_length, aeron_error_stat_on_observation, NULL, 0);\n\n        aeron_unmap(&error_mmap);\n    }\n    else\n    {\n        aeron_cnc_t *aeron_cnc = NULL;\n\n        if (aeron_cnc_init(&aeron_cnc, settings.base_path, settings.timeout_ms) < 0)\n        {\n            aeron_error_stat_print_error_and_usage(aeron_errmsg());\n            return EXIT_FAILURE;\n        }\n\n        count = aeron_cnc_error_log_read(aeron_cnc, aeron_error_stat_on_observation, NULL, 0);\n\n        aeron_cnc_close(aeron_cnc);\n    }\n\n    fprintf(stdout, \"\\n%\" PRIu64 \" distinct errors observed.\\n\", (uint64_t)count);\n\n    return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/c/loss_stat.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <inttypes.h>\n#include <errno.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#include <getopt.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"aeron_common.h\"\n#include \"util/aeron_strutil.h\"\n\nstatic const char *aeron_loss_stat_usage(void)\n{\n    return\n        \"    -h            Displays help information.\\n\"\n        \"    -d basePath   Base Path to shared memory. Default: /dev/shm/aeron-[user]\\n\"\n        \"    -t timeout    Number of milliseconds to wait to see if the driver metadata is available. Default: 1000\\n\";\n}\n\nstatic void aeron_loss_stat_print_error_and_usage(const char *message)\n{\n    fprintf(stderr, \"%s\\n%s\", message, aeron_loss_stat_usage());\n}\n\ntypedef struct aeron_loss_stat_settings_stct\n{\n    const char *base_path;\n    int64_t timeout_ms;\n}\naeron_loss_stat_settings_t;\n\nstatic void aeron_loss_stat_print_observation(\n    void *clientd,\n    int64_t observation_count,\n    int64_t total_bytes_lost,\n    int64_t first_observation_timestamp,\n    int64_t last_observation_timestamp,\n    int32_t session_id,\n    int32_t stream_id,\n    const char *channel,\n    int32_t channel_length,\n    const char *source,\n    int32_t source_length)\n{\n    char first_timestamp_str[AERON_FORMAT_DATE_MAX_LENGTH];\n    char last_timestamp_str[AERON_FORMAT_DATE_MAX_LENGTH];\n    aeron_format_date(first_timestamp_str, sizeof(first_timestamp_str), first_observation_timestamp);\n    aeron_format_date(last_timestamp_str, sizeof(last_timestamp_str), last_observation_timestamp);\n    printf(\n        \"%\" PRId64 \",%\" PRId64 \",%s,%s,%\" PRId32 \",%\" PRId32 \",%.*s,%.*s\\n\", \n        observation_count, \n        total_bytes_lost, \n        first_timestamp_str, \n        last_timestamp_str,\n        session_id,\n        stream_id,\n        (int)channel_length,\n        channel,\n        (int)source_length,\n        source);\n}\n\nint main(int argc, char **argv)\n{\n    char default_directory[AERON_MAX_PATH];\n    aeron_default_path(default_directory, sizeof(default_directory));\n    aeron_loss_stat_settings_t settings =\n        {\n            .base_path = default_directory,\n            .timeout_ms = 1000\n        };\n\n    int opt;\n\n    while ((opt = getopt(argc, argv, \"d:t:h\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'd':\n                settings.base_path = optarg;\n                break;\n\n            case 't':\n            {\n                errno = 0;\n                char *endptr;\n                settings.timeout_ms = strtoll(optarg, &endptr, 10);\n                if (0 != errno || '\\0' != endptr[0])\n                {\n                    aeron_loss_stat_print_error_and_usage(\"Invalid timeout\");\n                    return EXIT_FAILURE;\n                }\n                break;\n            }\n\n            case 'h':\n                aeron_loss_stat_print_error_and_usage(argv[0]);\n                return EXIT_SUCCESS;\n\n            default:\n                aeron_loss_stat_print_error_and_usage(\"Unknown option\");\n                return EXIT_FAILURE;\n        }\n    }\n\n    aeron_cnc_t *aeron_cnc = NULL;\n\n    if (aeron_cnc_init(&aeron_cnc, settings.base_path, settings.timeout_ms) < 0)\n    {\n        aeron_loss_stat_print_error_and_usage(aeron_errmsg());\n        return EXIT_FAILURE;\n    }\n\n    int exit_result = EXIT_FAILURE;\n    printf(\n        \"%s\\n\", \n        \"OBSERVATION_COUNT, TOTAL_BYTES_LOST, FIRST_OBSERVATION,\"\n        \"LAST_OBSERVATION, SESSION_ID, STREAM_ID, CHANNEL, SOURCE\");\n    int entries_read = aeron_cnc_loss_reporter_read(aeron_cnc, aeron_loss_stat_print_observation, NULL);\n\n    if (entries_read < 0)\n    {\n        printf(\"%s\\n\", aeron_errmsg());\n    }\n    else\n    {\n        printf(\"%d entries read\\n\", entries_read);\n        exit_result = EXIT_SUCCESS;\n    }\n\n    aeron_cnc_close(aeron_cnc);\n\n    return exit_result;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/c/rate_subscriber.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_agent.h\"\n\n#include \"samples_configuration.h\"\n#include \"sample_util.h\"\n\nconst char usage_str[] =\n    \"[-h][-v][-c uri][-f fragments][-p prefix][-s stream-id]\\n\"\n    \"    -h               help\\n\"\n    \"    -v               show version and exit\\n\"\n    \"    -c uri           use channel specified in uri\\n\"\n    \"    -f limit         limit to fragments per poll\\n\"\n    \"    -p prefix        aeron.dir location specified as prefix\\n\"\n    \"    -s stream-id     stream-id to use\\n\";\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nint main(int argc, char **argv)\n{\n    rate_reporter_t rate_reporter;\n    int status = EXIT_FAILURE, opt;\n    aeron_context_t *context = NULL;\n    aeron_t *aeron = NULL;\n    aeron_async_add_subscription_t *async = NULL;\n    aeron_subscription_t *subscription = NULL;\n    aeron_fragment_assembler_t *fragment_assembler = NULL;\n    const char *channel = DEFAULT_CHANNEL;\n    const char *aeron_dir = NULL;\n    int32_t stream_id = DEFAULT_STREAM_ID;\n    size_t fragment_limit = DEFAULT_FRAGMENT_COUNT_LIMIT;\n\n    while ((opt = getopt(argc, argv, \"hvc:f:p:s:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'c':\n            {\n                channel = optarg;\n                break;\n            }\n\n            case 'f':\n            {\n                fragment_limit = strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 's':\n            {\n                stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    printf(\"Subscribing to channel %s on Stream ID %\" PRId32 \"\\n\", channel, stream_id);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_async_add_subscription(\n        &async,\n        aeron,\n        channel,\n        stream_id,\n        print_available_image,\n        NULL,\n        print_unavailable_image,\n        NULL) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_subscription: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == subscription)\n    {\n        if (aeron_async_add_subscription_poll(&subscription, async) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Subscription channel status %\" PRIu64 \"\\n\", aeron_subscription_channel_status(subscription));\n\n    if (aeron_fragment_assembler_create(&fragment_assembler, rate_reporter_poll_handler, &rate_reporter) < 0)\n    {\n        fprintf(stderr, \"aeron_fragment_assembler_create: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (rate_reporter_start(&rate_reporter, print_rate_report) < 0)\n    {\n        fprintf(stderr, \"rate_reporter_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (is_running())\n    {\n        int fragments_read = aeron_subscription_poll(\n            subscription, aeron_fragment_assembler_handler, fragment_assembler, fragment_limit);\n\n        if (fragments_read < 0)\n        {\n            fprintf(stderr, \"aeron_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        aeron_idle_strategy_busy_spinning_idle(NULL, fragments_read);\n    }\n\n    printf(\"Shutting down...\\n\");\n    rate_reporter_halt(&rate_reporter);\n    status = EXIT_SUCCESS;\n\ncleanup:\n    aeron_subscription_close(subscription, NULL, NULL);\n    aeron_close(aeron);\n    aeron_context_close(context);\n    aeron_fragment_assembler_delete(fragment_assembler);\n\n    return status;\n}\n\nextern bool is_running(void);\n"
  },
  {
    "path": "aeron-samples/src/main/c/raw/ping_pong_raw.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdbool.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <time.h>\n#include <netinet/in.h>\n#include <sys/socket.h>\n#include <net/if.h>\n#include <netinet/ip.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n#include <ifaddrs.h>\n#include <poll.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include <hdr/hdr_histogram.h>\n\n#define AERON_RAW_DEFAULT_PING_HOST \"127.0.0.1\"\n#define AERON_RAW_DEFAULT_PING_PORT (13334)\n#define AERON_RAW_DEFAULT_PONG_HOST \"127.0.0.1\"\n#define AERON_RAW_DEFAULT_PONG_PORT (13335)\n#define AERON_RAW_DEFAULT_TRANSMIT_TYPE (1)\n#define AERON_RAW_DEFAULT_RECEIVE_TYPE (1)\n\nconst char usage_str[] =\n    \"[-h][-v][-h host][-p port][-H host][-P port][-m messages][-w messages]\\n\"\n    \"    -?               help\\n\"\n    \"    -s               run in echo server mode\\n\"\n    \"    -t               transmit type (1=connect/sendto, 2=sendto, 3=sendmsg, 4=sendmmsg)\\n\"\n    \"    -r               receive type (1=recvfrom, 2=recvmsg, 3=recvmmsg)\\n\"\n    \"    -h host          ping host (default 127.0.0.1)\\n\"\n    \"    -p port          ping port (default 13334)\\n\"\n    \"    -H host          pong host (default 127.0.0.1)\\n\"\n    \"    -P port          pong port (default 13335)\\n\"\n    \"    -m messages      number of messages (default 0)\\n\"\n    \"    -w messages      number of warm up messages to send (default 0)\\n\";\n\ntypedef int (*aeron_ping_pong_raw_connect)(\n    int send_fd,\n    const struct sockaddr *addr,\n    socklen_t addr_len);\n\ntypedef int (*aeron_ping_pong_raw_send)(\n    int send_fd,\n    const struct sockaddr *addr,\n    socklen_t addr_len,\n    void *buffer,\n    size_t buffer_len);\n\ntypedef int (*aeron_ping_pong_raw_recv)(\n    int recv_fd,\n    struct sockaddr *addr,\n    socklen_t addr_len,\n    void *buffer,\n    size_t buffer_len);\n\nint aeron_ping_pong_raw_sendto_connected(\n    int send_fd,\n    const struct sockaddr *addr,\n    socklen_t addr_len,\n    void *buffer,\n    size_t buffer_len)\n{\n    int sendto_result = sendto(send_fd, buffer, buffer_len, 0, NULL, 0);\n    if (sendto_result < 0)\n    {\n        fprintf(\n            stderr,\n            \"failed sendto(send_fd, msghdr.msg_iov->iov_base, recvmsg_result, 0, NULL, 0), %s\\n\",\n            strerror(errno));\n        return -1;\n    }\n\n    return sendto_result;\n}\n\nint aeron_ping_pong_raw_sendto_unconnected(\n    int send_fd,\n    const struct sockaddr *addr,\n    socklen_t addr_len,\n    void *buffer,\n    size_t buffer_len)\n{\n    int sendto_result = sendto(send_fd, buffer, buffer_len, 0, addr, addr_len);\n    if (sendto_result < 0)\n    {\n        fprintf(\n            stderr,\n            \"failed sendto(send_fd, msghdr.msg_iov->iov_base, recvmsg_result, 0, addr, addr_len), %s\\n\",\n            strerror(errno));\n        return -1;\n    }\n\n    return sendto_result;\n}\n\nint aeron_ping_pong_raw_sendmsg(\n    int send_fd,\n    const struct sockaddr *addr,\n    socklen_t addr_len,\n    void *buffer,\n    size_t buffer_len)\n{\n    struct msghdr send_msghdr;\n    struct iovec send_iov = { 0 };\n    send_msghdr.msg_iov = &send_iov;\n    send_iov.iov_base = buffer;\n    send_iov.iov_len = buffer_len;\n    send_msghdr.msg_control = NULL;\n    send_msghdr.msg_controllen = 0;\n    send_msghdr.msg_name = (void *)addr;\n    send_msghdr.msg_namelen = addr_len;\n    send_msghdr.msg_iovlen = 1;\n\n    int sendmsg_result = sendmsg(send_fd, &send_msghdr, 0);\n    if (sendmsg_result < 0)\n    {\n        fprintf(stderr, \"failed sendmsg(send_fd, &send_msghdr, 0), %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    return sendmsg_result;\n}\n\nint aeron_ping_pong_raw_sendmmsg(\n    int send_fd,\n    const struct sockaddr *addr,\n    socklen_t addr_len,\n    void *buffer,\n    size_t buffer_len)\n{\n    struct mmsghdr send_msghdr;\n    struct iovec send_iov = { 0 };\n    send_msghdr.msg_hdr.msg_iov = &send_iov;\n    send_iov.iov_base = buffer;\n    send_iov.iov_len = buffer_len;\n    send_msghdr.msg_hdr.msg_control = NULL;\n    send_msghdr.msg_hdr.msg_controllen = 0;\n    send_msghdr.msg_hdr.msg_name = (void *)addr;\n    send_msghdr.msg_hdr.msg_namelen = addr_len;\n    send_msghdr.msg_hdr.msg_iovlen = 1;\n    send_msghdr.msg_hdr.msg_controllen = 0;\n    send_msghdr.msg_hdr.msg_control = NULL;\n\n    int sendmsg_result = sendmmsg(send_fd, &send_msghdr, 1, 0);\n    if (sendmsg_result < 0)\n    {\n        fprintf(stderr, \"failed sendmsg(send_fd, &send_msghdr, 0), %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    return send_msghdr.msg_len;\n}\n\nint aeron_ping_pong_raw_recvmsg(\n    int recv_fd,\n    struct sockaddr *addr,\n    socklen_t addr_len,\n    void *buffer,\n    size_t buffer_len)\n{\n    struct msghdr msghdr = { 0 };\n    struct iovec iov = { 0 };\n    iov.iov_base = buffer;\n    iov.iov_len = buffer_len;\n    msghdr.msg_iov = &iov;\n    msghdr.msg_iovlen = 1;\n    msghdr.msg_name = addr;\n    msghdr.msg_namelen = addr_len;\n\n    ssize_t recvmsg_result = recvmsg(recv_fd, &msghdr, 0);\n    if (recvmsg_result < 0)\n    {\n        if (errno == EAGAIN)\n        {\n            return 0;\n        }\n        else\n        {\n            fprintf(stderr, \"failed recvmsg(recv_fd, &msghdr, 0), %s\\n\", strerror(errno));\n            return -1;\n        }\n    }\n\n    return recvmsg_result;\n}\n\nint aeron_ping_pong_raw_recvfrom(\n    int recv_fd,\n    struct sockaddr *addr,\n    socklen_t addr_len,\n    void *buffer,\n    size_t buffer_len)\n{\n    ssize_t recvmsg_result = recvfrom(recv_fd, buffer, buffer_len, 0, addr, &addr_len);\n    if (recvmsg_result < 0)\n    {\n        if (errno == EAGAIN)\n        {\n            return 0;\n        }\n        else\n        {\n            fprintf(stderr, \"failed recvmsg(recv_fd, &msghdr, 0), %s\\n\", strerror(errno));\n            return -1;\n        }\n    }\n\n    return recvmsg_result;\n}\n\nint aeron_ping_pong_raw_recvmmsg(\n    int recv_fd,\n    struct sockaddr *addr,\n    socklen_t addr_len,\n    void *buffer,\n    size_t buffer_len)\n{\n    struct mmsghdr mmsghdr;\n    struct iovec iov = { 0 };\n    iov.iov_base = buffer;\n    iov.iov_len = buffer_len;\n    mmsghdr.msg_hdr.msg_iov = &iov;\n    mmsghdr.msg_hdr.msg_iovlen = 1;\n    mmsghdr.msg_hdr.msg_name = addr;\n    mmsghdr.msg_hdr.msg_namelen = addr_len;\n    mmsghdr.msg_hdr.msg_controllen = 0;\n    mmsghdr.msg_hdr.msg_control = NULL;\n    mmsghdr.msg_len = 0;\n    struct timespec tv = { .tv_nsec = 0, .tv_sec = 0 };\n\n    ssize_t recvmsg_result = recvmmsg(recv_fd, &mmsghdr, 1, 0, &tv);\n    if (recvmsg_result < 0)\n    {\n        if (errno == EAGAIN)\n        {\n            return 0;\n        }\n        else\n        {\n            fprintf(stderr, \"failed recvmsg(recv_fd, &msghdr, 0), %s\\n\", strerror(errno));\n            return -1;\n        }\n    }\n\n    return mmsghdr.msg_len;\n}\n\nint aeron_ping_pong_raw_socket_connect(\n    int send_fd,\n    const struct sockaddr *addr,\n    socklen_t addr_len)\n{\n    int connect_result = connect(send_fd, addr, addr_len);\n    if (connect_result < 0)\n    {\n        fprintf(stderr, \"failed send connect(send_fd, send_addr, sizeof(struct sockaddr_in)), %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    return connect_result;\n}\n\nint aeron_ping_pong_raw_socket_null_connect(\n    int send_fd,\n    const struct sockaddr *addr,\n    socklen_t addr_len)\n{\n    return 0;\n}\n\nstruct aeron_ping_pong_config_stct\n{\n    struct sockaddr_storage ping_host;\n    struct sockaddr_storage pong_host;\n    long messages;\n    long warmup_messages;\n    bool show_help;\n    bool is_server;\n    int transmit_type;\n    int receive_type;\n    aeron_ping_pong_raw_connect connect_func;\n    aeron_ping_pong_raw_send send_func;\n    aeron_ping_pong_raw_recv recv_func;\n};\ntypedef struct aeron_ping_pong_config_stct aeron_ping_pong_config_t;\n\nint aeron_ping_pong_parse_config(int argc, char **argv, aeron_ping_pong_config_t *config)\n{\n    int opt;\n    struct sockaddr_in *ping_addr = (struct sockaddr_in *)&config->ping_host;\n    struct sockaddr_in *pong_addr = (struct sockaddr_in *)&config->pong_host;\n\n    ping_addr->sin_family = AF_INET;\n    inet_pton(AF_INET, AERON_RAW_DEFAULT_PING_HOST, &ping_addr->sin_addr);\n    ping_addr->sin_port = htons(AERON_RAW_DEFAULT_PING_PORT);\n    pong_addr->sin_family = AF_INET;\n    inet_pton(AF_INET, AERON_RAW_DEFAULT_PONG_HOST, &pong_addr->sin_addr);\n    pong_addr->sin_port = htons(AERON_RAW_DEFAULT_PONG_PORT);\n    config->transmit_type = AERON_RAW_DEFAULT_TRANSMIT_TYPE;\n    config->receive_type = AERON_RAW_DEFAULT_RECEIVE_TYPE;\n\n    while ((opt = getopt(argc, argv, \"?h:p:H:P:m:w:t:r:s\")) != -1)\n    {\n        switch (opt)\n        {\n            case '?':\n                config->show_help = true;\n                break;\n\n            case 'h':\n                if (-1 == inet_pton(AF_INET, optarg, &ping_addr->sin_addr))\n                {\n                    return -1;\n                }\n                break;\n\n            case 'H':\n                if (-1 == inet_pton(AF_INET, optarg, &pong_addr->sin_addr))\n                {\n                    return -1;\n                }\n                break;\n\n            case 'm':\n                // TODO: error handling\n                config->messages = strtol(optarg, NULL, 10);\n                break;\n\n            case 'w':\n                // TODO: error handling\n                config->warmup_messages = strtol(optarg, NULL, 10);\n                break;\n\n            case 't':\n            {\n                config->transmit_type = atoi(optarg);\n                break;\n            }\n\n            case 'r':\n            {\n                config->receive_type = atoi(optarg);\n                break;\n            }\n\n            case 's':\n                config->is_server = true;\n                break;\n        }\n    }\n\n    switch (config->transmit_type)\n    {\n        case 1:\n            config->connect_func = aeron_ping_pong_raw_socket_connect;\n            config->send_func = aeron_ping_pong_raw_sendto_connected;\n            printf(\"Transmit: connect/sendto\\n\");\n            break;\n\n        case 2:\n            config->connect_func = aeron_ping_pong_raw_socket_null_connect;\n            config->send_func = aeron_ping_pong_raw_sendto_unconnected;\n            printf(\"Transmit: sendto\\n\");\n            break;\n\n        case 3:\n            config->connect_func = aeron_ping_pong_raw_socket_null_connect;\n            config->send_func = aeron_ping_pong_raw_sendmsg;\n            printf(\"Transmit: sendmsg\\n\");\n            break;\n\n        case 4:\n            config->connect_func = aeron_ping_pong_raw_socket_null_connect;\n            config->send_func = aeron_ping_pong_raw_sendmmsg;\n            printf(\"Transmit: sendmmsg\\n\");\n            break;\n\n\n        default:\n            fprintf(stderr, \"Invalid transmit type: %d\\n\", config->transmit_type);\n            return -1;\n    }\n\n    switch (config->receive_type)\n    {\n        case 1:\n            config->recv_func = aeron_ping_pong_raw_recvmsg;\n            printf(\"Receive: recvmsg\\n\");\n            break;\n\n        case 2:\n            config->recv_func = aeron_ping_pong_raw_recvfrom;\n            printf(\"Receive: recvfrom\\n\");\n            break;\n\n        case 3:\n            config->recv_func = aeron_ping_pong_raw_recvmmsg;\n            printf(\"Receive: recvmmsg\\n\");\n            break;\n\n        default:\n            fprintf(stderr, \"Invalid receive type: %d\\n\", config->receive_type);\n            return -1;\n    }\n\n    return 0;\n}\n\nint aeron_ping_pong_raw_set_socket_non_blocking(int fd)\n{\n    int flags;\n    if ((flags = fcntl(fd, F_GETFL, 0)) < 0)\n    {\n        return -1;\n    }\n\n    flags |= O_NONBLOCK;\n    if (fcntl(fd, F_SETFL, flags) < 0)\n    {\n        return -1;\n    }\n\n    return 0;\n}\n\nint recv_then_send(aeron_ping_pong_config_t *config, int send_fd, int recv_fd)\n{\n    int8_t buf[64 * 1024];\n    struct sockaddr_in addr = { 0 };\n    socklen_t addr_len = sizeof(addr);\n\n    while (true)\n    {\n        ssize_t recvmsg_result = config->recv_func(recv_fd, (struct sockaddr *)&addr, addr_len, buf, sizeof(buf));\n        if (recvmsg_result < 0)\n        {\n            return -1;\n        }\n        else if (0 < recvmsg_result)\n        {\n            if (config->send_func(\n                send_fd,\n                (const struct sockaddr *)&config->pong_host,\n                sizeof(struct sockaddr_in),\n                buf,\n                recvmsg_result) < 0)\n            {\n                return -1;\n            }\n        }\n    }\n}\n\nint send_then_recv(aeron_ping_pong_config_t *config, int send_fd, int recv_fd, long messages, struct hdr_histogram *h)\n{\n    int8_t send_buf[32];\n    int8_t buf[64 * 1024];\n    struct timespec send_ts;\n    struct timespec recv_ts;\n    struct sockaddr_in addr = { 0 };\n    socklen_t addr_len = sizeof(addr);\n\n    for (int i = 0; i < messages; i++)\n    {\n        clock_gettime(CLOCK_MONOTONIC, &send_ts);\n        if (config->send_func(\n            send_fd,\n            (const struct sockaddr *)&config->ping_host,\n            sizeof(struct sockaddr_in),\n            send_buf,\n            sizeof(send_buf)) < 0)\n        {\n            return -1;\n        }\n\n        do\n        {\n            int recvmsg_result = config->recv_func(recv_fd, (struct sockaddr*)&addr, addr_len, buf, sizeof(buf));\n            if (recvmsg_result < 0)\n            {\n                return -1;\n            }\n            else if (recvmsg_result > 0)\n            {\n                break;\n            }\n        }\n        while (true);\n\n        clock_gettime(CLOCK_MONOTONIC, &recv_ts);\n        int64_t delta_ns = ((recv_ts.tv_sec * INT64_C(1000000000)) + recv_ts.tv_nsec) - ((send_ts.tv_sec * INT64_C(1000000000)) + send_ts.tv_nsec);\n        hdr_record_value(h, delta_ns);\n    }\n\n    return 0;\n}\n\nint main(int argc, char **argv)\n{\n    aeron_ping_pong_config_t config;\n    memset(&config, 0, sizeof(config));\n    aeron_ping_pong_parse_config(argc, argv, &config);\n\n    if (config.show_help)\n    {\n        printf(\"%s\\n\", usage_str);\n        return 1;\n    }\n\n    struct sockaddr_in *send_addr = config.is_server ? (struct sockaddr_in *)&config.pong_host : (struct sockaddr_in *)&config.ping_host;\n    struct sockaddr_in *recv_addr = config.is_server ? (struct sockaddr_in *)&config.ping_host : (struct sockaddr_in *)&config.pong_host;\n    struct hdr_histogram *histogram;\n    if (hdr_init(1, 1000000000, 3, &histogram) < 0)\n    {\n        fprintf(stderr, \"failed to hdr_init(0, 1000000000, 3, &histogram)\\n\");\n        return -1;\n    }\n\n    int send_fd = socket(AF_INET, SOCK_DGRAM, 0);\n    if (send_fd < 0)\n    {\n        fprintf(stderr, \"failed send socket(AF_INET, SOCK_DGRAM, 0), %s\\n\", strerror(errno));\n        return -1;\n    }\n\n\n    if (config.connect_func(send_fd, (const struct sockaddr *)send_addr, sizeof(*send_addr)))\n    {\n        return -1;\n    }\n\n    if (aeron_ping_pong_raw_set_socket_non_blocking(send_fd) < 0)\n    {\n        fprintf(stderr, \"failed send aeron_ping_pong_raw_set_socket_non_blocking(send_fd), %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    int recv_fd = socket(AF_INET, SOCK_DGRAM, 0);\n    if (recv_fd < 0)\n    {\n        fprintf(stderr, \"failed recv socket(AF_INET, SOCK_DGRAM, 0), %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    if (bind(recv_fd, (const struct sockaddr *)recv_addr, sizeof(*recv_addr)) < 0)\n    {\n        fprintf(stderr, \"failed recv bind(recv_fd, recv_addr, sizeof(struct sockaddr_in)), %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    if (aeron_ping_pong_raw_set_socket_non_blocking(recv_fd) < 0)\n    {\n        fprintf(stderr, \"failed send aeron_ping_pong_raw_set_socket_non_blocking(recv_fd), %s\\n\", strerror(errno));\n        return -1;\n    }\n\n    char send_addr_str[64];\n    char recv_addr_str[64];\n\n    inet_ntop(AF_INET, &send_addr->sin_addr, send_addr_str, sizeof(send_addr_str));\n    inet_ntop(AF_INET, &recv_addr->sin_addr, recv_addr_str, sizeof(recv_addr_str));\n\n    fprintf(stdout, \"send %s:%\" PRIu16 \"\\n\", send_addr_str, ntohs(send_addr->sin_port));\n    fprintf(stdout, \"recv %s:%\" PRIu16 \"\\n\", recv_addr_str, ntohs(recv_addr->sin_port));\n\n    if (config.is_server)\n    {\n        recv_then_send(&config, send_fd, recv_fd);\n    }\n    else\n    {\n        printf(\"sending %ld warmup messages\\n\", config.warmup_messages);\n        if (send_then_recv(&config, send_fd, recv_fd, config.warmup_messages, histogram) < 0)\n        {\n            return -1;\n        }\n\n        hdr_reset(histogram);\n        printf(\"sending %ld real messages\\n\", config.messages);\n        if (send_then_recv(&config, send_fd, recv_fd, config.messages, histogram) < 0)\n        {\n            return -1;\n        }\n        hdr_percentiles_print(histogram, stdout, 1, 1000.0, CLASSIC);\n    }\n\n    return 0;\n}\n\n"
  },
  {
    "path": "aeron-samples/src/main/c/response/response_client.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_alloc.h\"\n\n#include \"../samples_configuration.h\"\n#include \"../sample_util.h\"\n#include \"uri/aeron_uri.h\"\n\ntypedef struct response_client_stct response_client_t;\n\nint response_client_create(\n    response_client_t **response_clientp,\n    aeron_t *aeron,\n    aeron_fragment_handler_t delegate,\n    const char *request_channel,\n    int32_t request_stream_id,\n    const char *response_control_channel,\n    int32_t response_stream_id);\n\nvoid response_client_delete(response_client_t *response_client);\n\nint64_t response_client_offer(response_client_t *response_client, const uint8_t *buffer, size_t length);\n\nbool response_client_is_connected(response_client_t *response_client);\n\nint response_client_do_work(response_client_t *response_client);\n\nconst char usage_str[] =\n    \"[-h][-v][-c request-uri][-d response-uri][-l linger][-m messages][-p prefix][-r response-stream-id][-s request-stream-id]\\n\"\n    \"    -h                       help\\n\"\n    \"    -v                       show version and exit\\n\"\n    \"    -c request-uri           use request channel specified in uri\\n\"\n    \"    -d response-uri          use response control channel specified in uri\\n\"\n    \"    -l linger                linger at end of publishing for linger seconds\\n\"\n    \"    -m messages              number of messages to send\\n\"\n    \"    -p prefix                aeron.dir location specified as prefix\\n\"\n    \"    -r response-stream-id    response stream-id to use\\n\"\n    \"    -s request-stream-id     request stream-id to use\\n\"\n;\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nvoid poll_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    aeron_subscription_t *subscription = (aeron_subscription_t *)clientd;\n    aeron_subscription_constants_t subscription_constants;\n    aeron_header_values_t header_values;\n\n    if (aeron_subscription_constants(subscription, &subscription_constants) < 0)\n    {\n        fprintf(stderr, \"could not get subscription constants: %s\\n\", aeron_errmsg());\n        return;\n    }\n\n    aeron_header_values(header, &header_values);\n\n    printf(\n        \"Response to stream %\" PRId32 \" from session %\" PRId32 \" (%\" PRIu64 \" bytes) <<%.*s>>\\n\",\n        subscription_constants.stream_id,\n        header_values.frame.session_id,\n        (uint64_t)length,\n        (int)length,\n        buffer);\n}\n\nint main(int argc, char **argv)\n{\n    char small_message[256] = { 0 };\n    const char *message;\n    int message_len;\n\n    int status = EXIT_FAILURE, opt;\n\n    aeron_context_t *context = NULL;\n    const char *aeron_dir = NULL;\n    aeron_t *aeron = NULL;\n\n    const char *response_control_channel = DEFAULT_RESPONSE_CONTROL_CHANNEL;\n    int32_t response_stream_id = DEFAULT_RESPONSE_STREAM_ID;\n    const char *request_channel = DEFAULT_REQUEST_CHANNEL;\n    int32_t request_stream_id = DEFAULT_REQUEST_STREAM_ID;\n    uint64_t linger_ns = DEFAULT_LINGER_TIMEOUT_MS * UINT64_C(1000) * UINT64_C(1000);\n    uint64_t messages = DEFAULT_NUMBER_OF_MESSAGES;\n\n    response_client_t *response_client = NULL;\n\n    while ((opt = getopt(argc, argv, \"hvc:d:l:m:p:r:s:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'c':\n            {\n                request_channel = optarg;\n                break;\n            }\n\n            case 'd':\n            {\n                response_control_channel = optarg;\n                break;\n            }\n\n            case 'l':\n            {\n                if (aeron_parse_duration_ns(optarg, &linger_ns) < 0)\n                {\n                    fprintf(stderr, \"malformed linger %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'm':\n            {\n                if (aeron_parse_size64(optarg, &messages) < 0)\n                {\n                    fprintf(stderr, \"malformed number of messages %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 'r':\n            {\n                response_stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 's':\n            {\n                request_stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (response_client_create(\n        &response_client,\n        aeron,\n        poll_handler,\n        request_channel,\n        request_stream_id,\n        response_control_channel,\n        response_stream_id) < 0)\n    {\n        fprintf(stderr, \"response_client_create: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    for (size_t i = 0; i < messages && is_running(); i++)\n    {\n        message_len = SNPRINTF(small_message, sizeof(small_message) - 1, \"Hello World! %\" PRIu64, (uint64_t)i);\n        message = small_message;\n        printf(\"offering %\" PRIu64 \"/%\" PRIu64 \" - \", (uint64_t)i, (uint64_t)messages);\n        fflush(stdout);\n\n        int64_t result = response_client_offer(response_client, (const uint8_t *)message, message_len);\n\n        if (result > 0)\n        {\n            printf(\"yay!\\n\");\n        }\n        else if (AERON_PUBLICATION_BACK_PRESSURED == result)\n        {\n            printf(\"Offer failed due to back pressure\\n\");\n        }\n        else if (AERON_PUBLICATION_NOT_CONNECTED == result)\n        {\n            printf(\"Offer failed because publisher is not connected to a subscriber\\n\");\n        }\n        else if (AERON_PUBLICATION_ADMIN_ACTION == result)\n        {\n            printf(\"Offer failed because of an administration action in the system\\n\");\n        }\n        else if (AERON_PUBLICATION_CLOSED == result)\n        {\n            printf(\"Offer failed because publication is closed\\n\");\n        }\n        else\n        {\n            printf(\"Offer failed due to unknown reason %\" PRId64 \"\\n\", result);\n        }\n\n        if (!response_client_is_connected(response_client))\n        {\n            printf(\"No active subscribers detected\\n\");\n        }\n\n        aeron_nano_sleep(1000ul * 1000ul * 1000ul);\n\n        int fragments_read = response_client_do_work(response_client);\n\n        if (fragments_read < 0)\n        {\n            fprintf(stderr, \"aeron_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    printf(\"Done sending.\\n\");\n\n    if (linger_ns > 0)\n    {\n        printf(\"Lingering for %\" PRIu64 \" nanoseconds\\n\", linger_ns);\n        aeron_nano_sleep(linger_ns);\n    }\n\ncleanup:\n    response_client_delete(response_client);\n    aeron_close(aeron);\n    aeron_context_close(context);\n\n    return status;\n}\n\nextern bool is_running(void);\n\nstruct response_client_stct\n{\n    aeron_t *aeron;\n    aeron_subscription_t *subscription;\n    aeron_fragment_assembler_t *fragment_assembler;\n    aeron_publication_t *publication;\n};\n\nint response_client_create(\n    response_client_t **response_clientp,\n    aeron_t *aeron,\n    aeron_fragment_handler_t delegate,\n    const char *request_channel,\n    int32_t request_stream_id,\n    const char *response_control_channel,\n    int32_t response_stream_id)\n{\n    response_client_t *response_client;\n    aeron_async_add_subscription_t *async_add_sub;\n    aeron_async_add_publication_t *async_add_pub;\n    char _response_channel_buf[AERON_URI_MAX_LENGTH] = { 0 };\n    char _channel_buf[AERON_URI_MAX_LENGTH] = { 0 };\n    int64_t subscriber_registration_id;\n    aeron_subscription_constants_t constants;\n\n    aeron_alloc((void **)&response_client, sizeof(response_client_t));\n\n    SNPRINTF(_response_channel_buf, sizeof(_response_channel_buf) - 1, \"%s|control-mode=response\", response_control_channel);\n\n    printf(\"Subscribing to response channel %s on Stream ID %\" PRId32 \"\\n\", _response_channel_buf, response_stream_id);\n\n    if (aeron_async_add_subscription(\n        &async_add_sub,\n        aeron,\n        _response_channel_buf,\n        response_stream_id,\n        print_available_image,\n        NULL,\n        print_unavailable_image,\n        NULL) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_subscription: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    response_client->subscription = NULL;\n    while (NULL == response_client->subscription)\n    {\n        if (aeron_async_add_subscription_poll(&response_client->subscription, async_add_sub) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Subscription channel status %\" PRIu64 \"\\n\", aeron_subscription_channel_status(response_client->subscription));\n\n    if (aeron_fragment_assembler_create(&response_client->fragment_assembler, delegate, response_client->subscription) < 0)\n    {\n        fprintf(stderr, \"aeron_fragment_assembler_create: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_subscription_constants(response_client->subscription, &constants) < 0)\n    {\n        fprintf(stderr, \"aeron_subscription_constants: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    subscriber_registration_id = constants.registration_id;\n\n    SNPRINTF(_channel_buf, sizeof(_channel_buf) - 1, \"%s|response-correlation-id=%\" PRIi64, request_channel, subscriber_registration_id);\n\n    printf(\"Publishing to channel %s on Stream ID %\" PRId32 \"\\n\", _channel_buf, request_stream_id);\n\n    if (aeron_async_add_publication(&async_add_pub, aeron, _channel_buf, request_stream_id) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_publication: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == response_client->publication)\n    {\n        if (aeron_async_add_publication_poll(&response_client->publication, async_add_pub) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_publication_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Publication channel status %\" PRIu64 \"\\n\", aeron_publication_channel_status(response_client->publication));\n\n    *response_clientp = response_client;\n\n    return 0;\n\ncleanup:\n    response_client_delete(response_client);\n\n    return -1;\n}\n\nvoid response_client_delete(response_client_t *response_client)\n{\n    if (NULL != response_client)\n    {\n        aeron_subscription_close(response_client->subscription, NULL, NULL);\n        aeron_publication_close(response_client->publication, NULL, NULL);\n        aeron_fragment_assembler_delete(response_client->fragment_assembler);\n\n        aeron_free(response_client);\n    }\n}\n\nint64_t response_client_offer(response_client_t *response_client, const uint8_t *buffer, size_t length)\n{\n    return aeron_publication_offer(response_client->publication, buffer, length, NULL, NULL);\n}\n\nbool response_client_is_connected(response_client_t *response_client)\n{\n    return aeron_publication_is_connected(response_client->publication);\n}\n\nint response_client_do_work(response_client_t *response_client)\n{\n    return aeron_subscription_poll(\n        response_client->subscription,\n        aeron_fragment_assembler_handler,\n        response_client->fragment_assembler,\n        DEFAULT_FRAGMENT_COUNT_LIMIT);\n}\n"
  },
  {
    "path": "aeron-samples/src/main/c/response/response_server.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"collections/aeron_int64_to_ptr_hash_map.h\"\n#include \"uri/aeron_uri.h\"\n#include \"util/aeron_strutil.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_alloc.h\"\n\n#include \"../samples_configuration.h\"\n#include \"../sample_util.h\"\n\ntypedef struct response_channel_info_stct response_channel_info_t;\n\ntypedef struct response_server_stct response_server_t;\n\nint response_server_create(\n    response_server_t **response_serverp,\n    aeron_t *aeron,\n    aeron_fragment_handler_t delegate,\n    const char *request_channel,\n    int32_t request_stream_id,\n    const char *response_control_channel,\n    int32_t response_stream_id);\n\nvoid response_server_delete(response_server_t *response_server);\n\nint32_t response_server_subscription_constants(response_channel_info_t *response_channel_info, aeron_subscription_constants_t *subscription_constants);\n\nint64_t response_server_publication_offer(response_channel_info_t *response_channel_info, const uint8_t *buffer, size_t length);\n\nint response_server_do_work(response_server_t *response_server);\n\nconst char usage_str[] =\n    \"[-h][-v][-c request-uri][-d response-uri][-p prefix][-r response-stream-id][-s request-stream-id]\\n\"\n    \"    -h                       help\\n\"\n    \"    -v                       show version and exit\\n\"\n    \"    -c request-uri           use request channel specified in uri\\n\"\n    \"    -d response-uri          use response control channel specified in uri\\n\"\n    \"    -p prefix                aeron.dir location specified as prefix\\n\"\n    \"    -r response-stream-id    response stream-id to use\\n\"\n    \"    -s request-stream-id     request stream-id to use\\n\"\n    ;\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nvoid poll_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    response_channel_info_t *response_channel_info = clientd;\n    aeron_subscription_constants_t subscription_constants;\n    aeron_header_values_t header_values;\n\n    if (response_server_subscription_constants(response_channel_info, &subscription_constants) < 0)\n    {\n        fprintf(stderr, \"could not get subscription constants: %s\\n\", aeron_errmsg());\n        return;\n    }\n\n    aeron_header_values(header, &header_values);\n\n    printf(\n        \"Message to stream %\" PRId32 \" from session %\" PRId32 \" (%\" PRIu64 \" bytes) <<%.*s>>\\n\",\n        subscription_constants.stream_id,\n        header_values.frame.session_id,\n        (uint64_t)length,\n        (int)length,\n        buffer);\n\n    char message[256] = { 0 };\n    int message_len;\n\n    message_len = SNPRINTF(message, sizeof(message) - 1, \"responding to message: %.*s\", (int)length, buffer);\n\n    int64_t result = response_server_publication_offer(response_channel_info, (const uint8_t *)message, message_len);\n\n    if (result > 0)\n    {\n        printf(\"response sent!\\n\");\n    }\n    else if (AERON_PUBLICATION_BACK_PRESSURED == result)\n    {\n        printf(\"Offer failed due to back pressure\\n\");\n    }\n    else if (AERON_PUBLICATION_NOT_CONNECTED == result)\n    {\n        printf(\"Offer failed because publisher is not connected to a subscriber\\n\");\n    }\n    else if (AERON_PUBLICATION_ADMIN_ACTION == result)\n    {\n        printf(\"Offer failed because of an administration action in the system\\n\");\n    }\n    else if (AERON_PUBLICATION_CLOSED == result)\n    {\n        printf(\"Offer failed because publication is closed\\n\");\n    }\n    else\n    {\n        printf(\"Offer failed due to unknown reason %\" PRId64 \"\\n\", result);\n    }\n}\n\nint main(int argc, char **argv)\n{\n    int status = EXIT_FAILURE, opt;\n\n    aeron_context_t *context = NULL;\n    const char *aeron_dir = NULL;\n    aeron_t *aeron = NULL;\n\n    const char *request_channel = DEFAULT_REQUEST_CHANNEL;\n    int32_t request_stream_id = DEFAULT_REQUEST_STREAM_ID;\n    const char *response_control_channel = DEFAULT_RESPONSE_CONTROL_CHANNEL;\n    int32_t response_stream_id = DEFAULT_RESPONSE_STREAM_ID;\n    const uint64_t idle_duration_ns = UINT64_C(1000) * UINT64_C(1000); /* 1ms */\n\n    response_server_t *response_server = NULL;\n\n    while ((opt = getopt(argc, argv, \"hvc:d:p:r:s:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'c':\n            {\n                request_channel = optarg;\n                break;\n            }\n\n            case 'd':\n            {\n                response_control_channel = optarg;\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 'r':\n            {\n                response_stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 's':\n            {\n                request_stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    printf(\"Subscribing to channel %s on Stream ID %\" PRId32 \"\\n\", request_channel, request_stream_id);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (response_server_create(\n        &response_server,\n        aeron,\n        poll_handler,\n        request_channel,\n        request_stream_id,\n        response_control_channel,\n        response_stream_id) < 0)\n    {\n        fprintf(stderr, \"response_server_create: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (is_running())\n    {\n        int total_fragments_read = response_server_do_work(response_server);\n\n        aeron_idle_strategy_sleeping_idle((void *)&idle_duration_ns, total_fragments_read);\n    }\n\n    printf(\"Shutting down...\\n\");\n    status = EXIT_SUCCESS;\n\ncleanup:\n    response_server_delete(response_server);\n    aeron_close(aeron);\n    aeron_context_close(context);\n\n    return status;\n}\n\nextern bool is_running(void);\n\nstruct response_server_stct\n{\n    aeron_t *aeron;\n    aeron_subscription_t *subscription;\n    aeron_fragment_handler_t delegate;\n    char response_control_channel[AERON_URI_MAX_LENGTH];\n    int32_t response_stream_id;\n    aeron_int64_to_ptr_hash_map_t response_channel_info_map;\n    aeron_mutex_t info_lock;\n};\n\nstruct response_channel_info_stct\n{\n    aeron_image_t *image;\n    aeron_subscription_t *subscription;\n    aeron_async_add_publication_t *async_add_pub;\n    aeron_publication_t *publication;\n    aeron_fragment_assembler_t *fragment_assembler;\n};\n\nvoid response_server_handle_available_image(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image)\n{\n    response_server_t *response_server = clientd;\n    response_channel_info_t *response_channel_info = NULL;\n    aeron_image_constants_t constants;\n\n    print_available_image(NULL, subscription, image);\n\n    if (aeron_image_constants(image, &constants) < 0)\n    {\n        fprintf(stderr, \"aeron_image_constants: %s\\n\", aeron_errmsg());\n        return;\n    }\n\n    aeron_alloc((void **)&response_channel_info, sizeof(response_channel_info_t));\n\n    response_channel_info->image = image;\n    response_channel_info->subscription = subscription;\n\n    {\n        char _channel_buf[AERON_URI_MAX_LENGTH] = { 0 };\n\n        SNPRINTF(\n            _channel_buf,\n            sizeof(_channel_buf) - 1,\n            \"%.*s|control-mode=response|response-correlation-id=%\" PRIi64,\n            (int)strlen(response_server->response_control_channel),\n            response_server->response_control_channel,\n            constants.correlation_id);\n\n        printf(\"Responding on channel %s on Stream ID %\" PRId32 \"\\n\", _channel_buf, response_server->response_stream_id);\n\n        if (aeron_async_add_publication(&response_channel_info->async_add_pub, response_server->aeron, _channel_buf, response_server->response_stream_id) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_publication: %s\\n\", aeron_errmsg());\n            return;\n        }\n    }\n\n    response_channel_info->publication = NULL;\n\n    if (aeron_fragment_assembler_create(&response_channel_info->fragment_assembler, response_server->delegate, response_channel_info) < 0)\n    {\n        fprintf(stderr, \"aeron_fragment_assembler_create: %s\\n\", aeron_errmsg());\n        return;\n    }\n\n    aeron_mutex_lock(&response_server->info_lock);\n    if (aeron_int64_to_ptr_hash_map_put(&response_server->response_channel_info_map, constants.correlation_id, response_channel_info) < 0)\n    {\n        fprintf(stderr, \"aeron_int64_to_ptr_hash_map_put: %s\\n\", aeron_errmsg());\n    }\n    aeron_mutex_unlock(&response_server->info_lock);\n}\n\nvoid response_server_handle_unavailable_image(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image)\n{\n    response_server_t *response_server = clientd;\n    response_channel_info_t *response_channel_info = NULL;\n    aeron_image_constants_t constants;\n\n    print_unavailable_image(NULL, subscription, image);\n\n    if (aeron_image_constants(image, &constants) < 0)\n    {\n        fprintf(stderr, \"aeron_image_constants: %s\\n\", aeron_errmsg());\n        return;\n    }\n\n    aeron_mutex_lock(&response_server->info_lock);\n    response_channel_info = aeron_int64_to_ptr_hash_map_remove(&response_server->response_channel_info_map, constants.correlation_id);\n    aeron_mutex_unlock(&response_server->info_lock);\n\n    if (NULL != response_channel_info)\n    {\n        aeron_publication_close(response_channel_info->publication, NULL, NULL);\n        aeron_fragment_assembler_delete(response_channel_info->fragment_assembler);\n        aeron_free(response_channel_info);\n    }\n}\n\nint response_server_create(\n        response_server_t **response_serverp,\n        aeron_t *aeron,\n        aeron_fragment_handler_t delegate,\n        const char *request_channel,\n        int32_t request_stream_id,\n        const char *response_control_channel,\n        int32_t response_stream_id)\n{\n    response_server_t *response_server;\n    aeron_async_add_subscription_t *async = NULL;\n\n    aeron_alloc((void **)&response_server, sizeof(response_server_t));\n\n    response_server->aeron = aeron,\n    strncpy(response_server->response_control_channel, response_control_channel, sizeof(response_server->response_control_channel) - 1);\n    response_server->response_stream_id = response_stream_id;\n    response_server->delegate = delegate;\n\n    if (aeron_int64_to_ptr_hash_map_init(&response_server->response_channel_info_map, 8, AERON_MAP_DEFAULT_LOAD_FACTOR) < 0)\n    {\n        fprintf(stderr, \"aeron_int64_to_ptr_hash_map_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    aeron_mutex_init(&response_server->info_lock);\n\n    if (aeron_async_add_subscription(\n        &async,\n        aeron,\n        request_channel,\n        request_stream_id,\n        response_server_handle_available_image,\n        response_server,\n        response_server_handle_unavailable_image,\n        response_server) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_subscription: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    response_server->subscription = NULL;\n    while (NULL == response_server->subscription)\n    {\n        if (aeron_async_add_subscription_poll(&response_server->subscription, async) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_subscription_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Subscription channel status %\" PRIu64 \"\\n\", aeron_subscription_channel_status(response_server->subscription));\n\n    *response_serverp = response_server;\n\n    return 0;\n\ncleanup:\n    response_server_delete(response_server);\n\n    return -1;\n}\n\nvoid response_server_delete(response_server_t *response_server)\n{\n    if (NULL != response_server)\n    {\n        aeron_subscription_close(response_server->subscription, NULL, NULL);\n        aeron_mutex_lock(&response_server->info_lock);\n        aeron_int64_to_ptr_hash_map_delete(&response_server->response_channel_info_map);\n        aeron_mutex_unlock(&response_server->info_lock);\n        aeron_mutex_destroy(&response_server->info_lock);\n\n        aeron_free(response_server);\n    }\n}\n\nvoid response_server_process_response_channel_info(void *clientd, int64_t key, void *value)\n{\n    response_channel_info_t *response_channel_info = value;\n\n    if (NULL != response_channel_info->async_add_pub)\n    {\n        int rc;\n\n        rc = aeron_async_add_publication_poll(&response_channel_info->publication, response_channel_info->async_add_pub);\n\n        if (rc == 0)\n        {\n            return; // still waiting\n        }\n\n        if (rc < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_publication_poll: %s\\n\", aeron_errmsg());\n        }\n\n        // if we're here, _poll returned either 1 or -1.  Either way, we're done with the async_add_pub\n        response_channel_info->async_add_pub = NULL;\n    }\n\n    int fragments_read = aeron_image_poll(\n        response_channel_info->image,\n        aeron_fragment_assembler_handler,\n        response_channel_info->fragment_assembler,\n        DEFAULT_FRAGMENT_COUNT_LIMIT);\n\n    if (fragments_read < 0)\n    {\n        fprintf(stderr, \"aeron_image_poll: %s\\n\", aeron_errmsg());\n    }\n    else\n    {\n        int *total_fragments_read = (int *)clientd;\n\n        *total_fragments_read += fragments_read;\n    }\n}\n\nint32_t response_server_subscription_constants(response_channel_info_t *response_channel_info, aeron_subscription_constants_t *subscription_constants)\n{\n    return aeron_subscription_constants(response_channel_info->subscription, subscription_constants);\n}\n\nint64_t response_server_publication_offer(response_channel_info_t *response_channel_info, const uint8_t *buffer, size_t length)\n{\n    if (NULL == response_channel_info->publication)\n    {\n        return AERON_PUBLICATION_NOT_CONNECTED;\n    }\n\n    return aeron_publication_offer(response_channel_info->publication, buffer, length, NULL, NULL);\n}\n\nint response_server_do_work(response_server_t *response_server)\n{\n    int total_fragments_read = 0;\n\n    aeron_mutex_lock(&response_server->info_lock);\n    aeron_int64_to_ptr_hash_map_for_each(\n        &response_server->response_channel_info_map,\n        response_server_process_response_channel_info,\n        &total_fragments_read);\n    aeron_mutex_unlock(&response_server->info_lock);\n\n    return total_fragments_read;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/c/sample_util.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdio.h>\n#include <inttypes.h>\n\n#include \"sample_util.h\"\n\nvoid print_available_image(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image)\n{\n    aeron_subscription_constants_t subscription_constants;\n    aeron_image_constants_t image_constants;\n\n    if (aeron_subscription_constants(subscription, &subscription_constants) < 0 ||\n        aeron_image_constants(image, &image_constants) < 0)\n    {\n        fprintf(stderr, \"could not get subscription/image constants: %s\\n\", aeron_errmsg());\n    }\n    else\n    {\n        printf(\n            \"Available image on %s streamId=%\" PRId32 \" sessionId=%\" PRId32 \" mtu=%\" PRId32 \" term-length=%\" PRId32 \" from %s\\n\",\n            subscription_constants.channel,\n            subscription_constants.stream_id,\n            image_constants.session_id,\n            (int32_t)image_constants.mtu_length,\n            (int32_t)image_constants.term_buffer_length,\n            image_constants.source_identity);\n    }\n}\n\nvoid print_unavailable_image(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image)\n{\n    aeron_subscription_constants_t subscription_constants;\n    aeron_image_constants_t image_constants;\n\n    if (aeron_subscription_constants(subscription, &subscription_constants) < 0 ||\n        aeron_image_constants(image, &image_constants) < 0)\n    {\n        fprintf(stderr, \"could not get subscription/image constants: %s\\n\", aeron_errmsg());\n    }\n    else\n    {\n        printf(\n            \"Unavailable image on %s streamId=%\" PRId32 \" sessionId=%\" PRId32 \"\\n\",\n            subscription_constants.channel,\n            subscription_constants.stream_id,\n            image_constants.session_id);\n    }\n}\n\nvoid print_rate_report(uint64_t duration_ns, double mps, double bps, uint64_t total_messages, uint64_t total_bytes)\n{\n    printf(\"%\" PRIu64 \"ms, %.04g msgs/sec, %.04g bytes/sec, totals %\" PRIu64 \" messages %\" PRIu64 \" MB payloads\\n\",\n        duration_ns / (UINT64_C(1000) * UINT64_C(1000)), mps, bps, total_messages, total_bytes / (UINT64_C(1024) * UINT64_C(1024)));\n}\n\nint rate_reporter_do_work(void *state)\n{\n    rate_reporter_t *reporter = (rate_reporter_t *)state;\n    uint64_t current_total_bytes, current_total_messages;\n    int64_t current_timestamp_ns, duration_ns;\n    double mps, bps;\n\n    AERON_GET_ACQUIRE(current_total_bytes, reporter->polling_fields.total_bytes);\n    AERON_GET_ACQUIRE(current_total_messages, reporter->polling_fields.total_messages);\n    current_timestamp_ns = aeron_nano_clock();\n    duration_ns = current_timestamp_ns - reporter->last_timestamp_ns;\n    mps = ((double)(current_total_messages - reporter->last_total_messages) *\n        (double)reporter->idle_duration_ns) / (double)duration_ns;\n    bps = ((double)(current_total_bytes - reporter->last_total_bytes) *\n        (double)reporter->idle_duration_ns) / (double)duration_ns;\n\n    reporter->on_report(duration_ns, mps, bps, current_total_messages, current_total_bytes);\n\n    reporter->last_total_bytes = current_total_bytes;\n    reporter->last_total_messages = current_total_messages;\n    reporter->last_timestamp_ns = current_timestamp_ns;\n\n    return 0;\n}\n\nint rate_reporter_start(rate_reporter_t *reporter, on_rate_report_t on_report)\n{\n    reporter->idle_duration_ns = UINT64_C(1000) * UINT64_C(1000) * UINT64_C(1000);\n    reporter->on_report = on_report;\n    reporter->last_total_bytes = 0;\n    reporter->last_total_messages = 0;\n    reporter->last_timestamp_ns = aeron_nano_clock();\n    reporter->polling_fields.total_bytes = 0;\n    reporter->polling_fields.total_messages = 0;\n\n    if (aeron_agent_init(\n        &reporter->runner,\n        \"rate reporter\",\n        reporter,\n        NULL,\n        NULL,\n        rate_reporter_do_work,\n        NULL,\n        aeron_idle_strategy_sleeping_idle,\n        &reporter->idle_duration_ns) < 0)\n    {\n        return -1;\n    }\n\n    if (aeron_agent_start(&reporter->runner) < 0)\n    {\n        return -1;\n    }\n\n    return 0;\n}\n\nint rate_reporter_halt(rate_reporter_t *reporter)\n{\n    aeron_agent_stop(&reporter->runner);\n    aeron_agent_close(&reporter->runner);\n\n    return 0;\n}\n\nextern void rate_reporter_poll_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header);\nextern void rate_reporter_on_message(rate_reporter_t *reporter, size_t length);\n"
  },
  {
    "path": "aeron-samples/src/main/c/sample_util.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_SAMPLE_UTIL_H\n#define AERON_SAMPLE_UTIL_H\n\n#include \"aeronc.h\"\n#include \"aeron_agent.h\"\n#include \"util/aeron_bitutil.h\"\n\nvoid print_available_image(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image);\nvoid print_unavailable_image(void *clientd, aeron_subscription_t *subscription, aeron_image_t *image);\n\nvoid print_rate_report(uint64_t duration_ns, double mps, double bps, uint64_t total_messages, uint64_t total_bytes);\n\ntypedef void (*on_rate_report_t)(\n    uint64_t duration_ns, double mps, double bps, uint64_t total_messages, uint64_t total_bytes);\n\ntypedef struct rate_reporter_stct\n{\n    aeron_agent_runner_t runner;\n    uint64_t idle_duration_ns;\n    on_rate_report_t on_report;\n\n    uint64_t last_total_bytes;\n    uint64_t last_total_messages;\n    int64_t last_timestamp_ns;\n\n    struct polling_elements_stct\n    {\n        uint8_t pre_pad[AERON_CACHE_LINE_LENGTH * 2];\n        volatile uint64_t total_bytes;\n        volatile uint64_t total_messages;\n        uint8_t post_pad[AERON_CACHE_LINE_LENGTH * 2];\n    }\n    polling_fields;\n}\nrate_reporter_t;\n\nint rate_reporter_start(rate_reporter_t *reporter, on_rate_report_t on_report);\nint rate_reporter_halt(rate_reporter_t *reporter);\n\ninline void rate_reporter_poll_handler(void *clientd, const uint8_t *buffer, size_t length, aeron_header_t *header)\n{\n    rate_reporter_t *reporter = (rate_reporter_t *)clientd;\n\n    AERON_SET_RELEASE(reporter->polling_fields.total_bytes, reporter->polling_fields.total_bytes + length);\n    AERON_SET_RELEASE(reporter->polling_fields.total_messages, reporter->polling_fields.total_messages + 1);\n}\n\ninline void rate_reporter_on_message(rate_reporter_t *reporter, size_t length)\n{\n    AERON_SET_RELEASE(reporter->polling_fields.total_bytes, reporter->polling_fields.total_bytes + length);\n    AERON_SET_RELEASE(reporter->polling_fields.total_messages, reporter->polling_fields.total_messages + 1);\n}\n\n#if defined(_MSC_VER)\n#define SNPRINTF(_buf, _len, _fmt, ...) sprintf_s(_buf, _len, _fmt, __VA_ARGS__)\n#else\n#define SNPRINTF(_buf, _len, _fmt, ...) snprintf(_buf, _len, _fmt, __VA_ARGS__)\n#endif\n\n#endif //AERON_SAMPLE_UTIL_H\n"
  },
  {
    "path": "aeron-samples/src/main/c/samples_configuration.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_SAMPLES_CONFIGURATION_H\n#define AERON_SAMPLES_CONFIGURATION_H\n\n#define DEFAULT_CHANNEL \"aeron:udp?endpoint=localhost:20121\"\n#define DEFAULT_PING_CHANNEL \"aeron:udp?endpoint=localhost:20123\"\n#define DEFAULT_PONG_CHANNEL \"aeron:udp?endpoint=localhost:20124\"\n#define DEFAULT_STREAM_ID (1001)\n#define DEFAULT_PING_STREAM_ID (1002)\n#define DEFAULT_PONG_STREAM_ID (1003)\n#define DEFAULT_NUMBER_OF_WARM_UP_MESSAGES (100000)\n#define DEFAULT_NUMBER_OF_MESSAGES (10000000)\n#define DEFAULT_MESSAGE_LENGTH (32)\n#define DEFAULT_LINGER_TIMEOUT_MS (0)\n#define DEFAULT_FRAGMENT_COUNT_LIMIT (10)\n#define DEFAULT_RANDOM_MESSAGE_LENGTH (false)\n#define DEFAULT_PUBLICATION_RATE_PROGRESS (false)\n\n#define DEFAULT_RESPONSE_CONTROL_CHANNEL \"aeron:udp?control=localhost:20126\"\n#define DEFAULT_RESPONSE_STREAM_ID (10001)\n\n#define DEFAULT_REQUEST_CHANNEL \"aeron:udp?endpoint=localhost:20125\"\n#define DEFAULT_REQUEST_STREAM_ID (10000)\n\n#endif //AERON_SAMPLES_CONFIGURATION_H\n"
  },
  {
    "path": "aeron-samples/src/main/c/streaming_exclusive_publisher.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"aeron_agent.h\"\n#include \"sample_util.h\"\n\n#include \"samples_configuration.h\"\n\nconst char usage_str[] =\n    \"[-h][-P][-v][-c uri][-L length][-l linger][-m messages][-p prefix][-s stream-id]\\n\"\n    \"    -h               help\\n\"\n    \"    -P               print progress\\n\"\n    \"    -v               show version and exit\\n\"\n    \"    -c uri           use channel specified in uri\\n\"\n    \"    -L length        use message length of length bytes\\n\"\n    \"    -l linger        linger at end of publishing for linger seconds\\n\"\n    \"    -m messages      number of messages to send\\n\"\n    \"    -p prefix        aeron.dir location specified as prefix\\n\"\n    \"    -s stream-id     stream-id to use\\n\";\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nint main(int argc, char **argv)\n{\n    rate_reporter_t rate_reporter;\n    aeron_buffer_claim_t buffer_claim;\n    int status = EXIT_FAILURE, opt;\n    aeron_context_t *context = NULL;\n    aeron_t *aeron = NULL;\n    aeron_async_add_exclusive_publication_t *async = NULL;\n    aeron_exclusive_publication_t *publication = NULL;\n    const char *channel = DEFAULT_CHANNEL;\n    const char *aeron_dir = NULL;\n    uint64_t linger_ns = DEFAULT_LINGER_TIMEOUT_MS * UINT64_C(1000) * UINT64_C(1000);\n    uint64_t messages = DEFAULT_NUMBER_OF_MESSAGES;\n    uint64_t message_length = DEFAULT_MESSAGE_LENGTH;\n    uint64_t back_pressure_count = 0, message_sent_count = 0;\n    int64_t start_timestamp_ns, duration_ns;\n    int32_t stream_id = DEFAULT_STREAM_ID;\n    bool show_rate_progress = false;\n\n    while ((opt = getopt(argc, argv, \"hPvc:L:l:m:p:s:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'c':\n            {\n                channel = optarg;\n                break;\n            }\n\n            case 'L':\n            {\n                if (aeron_parse_size64(optarg, &messages) < 0)\n                {\n                    fprintf(stderr, \"malformed message length %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'l':\n            {\n                if (aeron_parse_duration_ns(optarg, &linger_ns) < 0)\n                {\n                    fprintf(stderr, \"malformed linger %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'm':\n            {\n                if (aeron_parse_size64(optarg, &messages) < 0)\n                {\n                    fprintf(stderr, \"malformed number of messages %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'P':\n            {\n                show_rate_progress = true;\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 's':\n            {\n                stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    printf(\"Streaming %\" PRIu64 \" messages of payload length %\" PRIu64 \" bytes to %s on stream id %\" PRId32 \"\\n\",\n        messages, message_length, channel, stream_id);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_async_add_exclusive_publication(&async, aeron, channel, stream_id) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_exclusive_publication: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == publication)\n    {\n        if (aeron_async_add_exclusive_publication_poll(&publication, async) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_exclusive_publication_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Publication channel status %\" PRId64 \"\\n\", aeron_exclusive_publication_channel_status(publication));\n\n    if (show_rate_progress)\n    {\n        if (rate_reporter_start(&rate_reporter, print_rate_report) < 0)\n        {\n            fprintf(stderr, \"rate_reporter_start: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    start_timestamp_ns = aeron_nano_clock();\n    for (uint64_t i = 0; i < messages && is_running();)\n    {\n        int64_t result = aeron_exclusive_publication_try_claim(publication, message_length, &buffer_claim);\n\n        if (result == AERON_PUBLICATION_ERROR)\n        {\n            fprintf(stderr, \"aeron_exclusive_publication_try_claim: %s\\n\", aeron_errmsg());\n            break;\n        }\n        else if (result < 0)\n        {\n            back_pressure_count++;\n            aeron_idle_strategy_busy_spinning_idle(NULL, 0);\n        }\n        else\n        {\n            *((uint64_t *)buffer_claim.data) = i;\n            aeron_buffer_claim_commit(&buffer_claim);\n\n            if (show_rate_progress)\n            {\n                rate_reporter_on_message(&rate_reporter, message_length);\n            }\n\n            message_sent_count++;\n            i++;\n        }\n    }\n    duration_ns = aeron_nano_clock() - start_timestamp_ns;\n\n    printf(\"Done sending.\\n\");\n\n    if (show_rate_progress)\n    {\n        rate_reporter_halt(&rate_reporter);\n    }\n\n    printf(\"Publisher back pressure ratio %g\\n\", (double)back_pressure_count / (double)message_sent_count);\n    printf(\n        \"Total: %\" PRId64 \"ms, %.04g msgs/sec, %.04g bytes/sec, totals %\" PRIu64 \" messages %\" PRIu64 \" MB payloads\\n\",\n        duration_ns / (1000 * 1000),\n        ((double)messages * (double)(1000 * 1000 * 1000) / (double)duration_ns),\n        ((double)(messages * message_length) * (double)(1000 * 1000 * 1000) / (double)duration_ns),\n        messages,\n        messages * message_length / (1024 * 1024));\n\n    if (linger_ns > 0)\n    {\n        printf(\"Lingering for %\" PRIu64 \" nanoseconds\\n\", linger_ns);\n        aeron_nano_sleep(linger_ns);\n    }\n\n    status = EXIT_SUCCESS;\n\ncleanup:\n    aeron_exclusive_publication_close(publication, NULL, NULL);\n    aeron_close(aeron);\n    aeron_context_close(context);\n\n    return status;\n}\n\nextern bool is_running(void);\n"
  },
  {
    "path": "aeron-samples/src/main/c/streaming_publisher.c",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if defined(__linux__)\n#define _BSD_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <stdlib.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <inttypes.h>\n#include <string.h>\n\n#if !defined(_MSC_VER)\n#include <unistd.h>\n#endif\n\n#include \"aeronc.h\"\n#include \"concurrent/aeron_atomic.h\"\n#include \"util/aeron_strutil.h\"\n#include \"util/aeron_parse_util.h\"\n#include \"aeron_agent.h\"\n#include \"aeron_alloc.h\"\n#include \"sample_util.h\"\n\n#include \"samples_configuration.h\"\n\nconst char usage_str[] =\n    \"[-h][-v][-c uri][-L length][-l linger][-m messages][-p prefix][-s stream-id]\\n\"\n    \"    -h               help\\n\"\n    \"    -v               show version and exit\\n\"\n    \"    -c uri           use channel specified in uri\\n\"\n    \"    -L length        use message length of length bytes\\n\"\n    \"    -l linger        linger at end of publishing for linger seconds\\n\"\n    \"    -m messages      number of messages to send\\n\"\n    \"    -p prefix        aeron.dir location specified as prefix\\n\"\n    \"    -s stream-id     stream-id to use\\n\";\n\nvolatile bool running = true;\n\nvoid sigint_handler(int signal)\n{\n    AERON_SET_RELEASE(running, false);\n}\n\ninline bool is_running(void)\n{\n    bool result;\n    AERON_GET_ACQUIRE(result, running);\n    return result;\n}\n\nint main(int argc, char **argv)\n{\n    rate_reporter_t rate_reporter;\n    int status = EXIT_FAILURE, opt;\n    aeron_context_t *context = NULL;\n    aeron_t *aeron = NULL;\n    aeron_async_add_publication_t *async = NULL;\n    aeron_publication_t *publication = NULL;\n    const char *channel = DEFAULT_CHANNEL;\n    const char *aeron_dir = NULL;\n    uint8_t *message = NULL;\n    uint64_t linger_ns = DEFAULT_LINGER_TIMEOUT_MS * UINT64_C(1000) * UINT64_C(1000);\n    uint64_t messages = DEFAULT_NUMBER_OF_MESSAGES;\n    uint64_t message_length = DEFAULT_MESSAGE_LENGTH;\n    uint64_t back_pressure_count = 0, message_sent_count = 0;\n    int64_t start_timestamp_ns, duration_ns;\n    int32_t stream_id = DEFAULT_STREAM_ID;\n\n    while ((opt = getopt(argc, argv, \"hvc:L:l:m:p:s:\")) != -1)\n    {\n        switch (opt)\n        {\n            case 'c':\n            {\n                channel = optarg;\n                break;\n            }\n\n            case 'L':\n            {\n                if (aeron_parse_size64(optarg, &message_length) < 0)\n                {\n                    fprintf(stderr, \"malformed message length %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'l':\n            {\n                if (aeron_parse_duration_ns(optarg, &linger_ns) < 0)\n                {\n                    fprintf(stderr, \"malformed linger %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'm':\n            {\n                if (aeron_parse_size64(optarg, &messages) < 0)\n                {\n                    fprintf(stderr, \"malformed number of messages %s: %s\\n\", optarg, aeron_errmsg());\n                    exit(status);\n                }\n                break;\n            }\n\n            case 'p':\n            {\n                aeron_dir = optarg;\n                break;\n            }\n\n            case 's':\n            {\n                stream_id = (int32_t)strtoul(optarg, NULL, 0);\n                break;\n            }\n\n            case 'v':\n            {\n                printf(\n                    \"%s <%s> major %d minor %d patch %d git %s\\n\",\n                    argv[0],\n                    aeron_version_full(),\n                    aeron_version_major(),\n                    aeron_version_minor(),\n                    aeron_version_patch(),\n                    aeron_version_gitsha());\n                exit(EXIT_SUCCESS);\n            }\n\n            case 'h':\n            default:\n                fprintf(stderr, \"Usage: %s %s\", argv[0], usage_str);\n                exit(status);\n        }\n    }\n\n    signal(SIGINT, sigint_handler);\n\n    if (aeron_alloc((void **)&message, message_length) < 0)\n    {\n        fprintf(stderr, \"allocating message: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    memset(message, 0, message_length);\n\n    printf(\"Streaming %\" PRIu64 \" messages of payload length %\" PRIu64 \" bytes to %s on stream id %\" PRId32 \"\\n\",\n        messages, message_length, channel, stream_id);\n\n    if (aeron_context_init(&context) < 0)\n    {\n        fprintf(stderr, \"aeron_context_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (NULL != aeron_dir)\n    {\n        if (aeron_context_set_dir(context, aeron_dir) < 0)\n        {\n            fprintf(stderr, \"aeron_context_set_dir: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n    }\n\n    if (aeron_init(&aeron, context) < 0)\n    {\n        fprintf(stderr, \"aeron_init: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_start(aeron) < 0)\n    {\n        fprintf(stderr, \"aeron_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    if (aeron_async_add_publication(&async, aeron, channel, stream_id) < 0)\n    {\n        fprintf(stderr, \"aeron_async_add_publication: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    while (NULL == publication)\n    {\n        if (aeron_async_add_publication_poll(&publication, async) < 0)\n        {\n            fprintf(stderr, \"aeron_async_add_publication_poll: %s\\n\", aeron_errmsg());\n            goto cleanup;\n        }\n\n        sched_yield();\n    }\n\n    printf(\"Publication channel status %\" PRId64 \"\\n\", aeron_publication_channel_status(publication));\n\n    if (rate_reporter_start(&rate_reporter, print_rate_report) < 0)\n    {\n        fprintf(stderr, \"rate_reporter_start: %s\\n\", aeron_errmsg());\n        goto cleanup;\n    }\n\n    start_timestamp_ns = aeron_nano_clock();\n    for (uint64_t i = 0; i < messages && is_running(); i++)\n    {\n        *((uint64_t *)message) = i;\n        while (aeron_publication_offer(publication, message, message_length, NULL, NULL) < 0)\n        {\n            ++back_pressure_count;\n\n            if (!is_running())\n            {\n                break;\n            }\n\n            aeron_idle_strategy_busy_spinning_idle(NULL, 0);\n        }\n\n        ++message_sent_count;\n        rate_reporter_on_message(&rate_reporter, message_length);\n    }\n    duration_ns = aeron_nano_clock() - start_timestamp_ns;\n\n    printf(\"Done sending.\\n\");\n\n    rate_reporter_halt(&rate_reporter);\n\n    printf(\"Publisher back pressure ratio %g\\n\", (double)back_pressure_count / (double)message_sent_count);\n    printf(\n        \"Total: %\" PRId64 \"ms, %.04g msgs/sec, %.04g bytes/sec, totals %\" PRIu64 \" messages %\" PRIu64 \" MB payloads\\n\",\n        duration_ns / (1000 * 1000),\n        ((double)messages * (double)(1000 * 1000 * 1000) / (double)duration_ns),\n        ((double)(messages * message_length) * (double)(1000 * 1000 * 1000) / (double)duration_ns),\n        messages,\n        messages * message_length / (1024 * 1024));\n\n    if (linger_ns > 0)\n    {\n        printf(\"Lingering for %\" PRIu64 \" nanoseconds\\n\", linger_ns);\n        aeron_nano_sleep(linger_ns);\n    }\n\n    status = EXIT_SUCCESS;\n\ncleanup:\n    aeron_publication_close(publication, NULL, NULL);\n    aeron_close(aeron);\n    aeron_context_close(context);\n    aeron_free(message);\n\n    return status;\n}\n\nextern bool is_running(void);\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/BasicPublisher.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdint>\n#include <cstdio>\n#include <thread>\n#include <array>\n#include <csignal>\n#include <cinttypes>\n\n#include \"util/CommandOptionParser.h\"\n#include \"Configuration.h\"\n#include \"Aeron.h\"\n\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nstatic const char optHelp = 'h';\nstatic const char optPrefix = 'p';\nstatic const char optChannel = 'c';\nstatic const char optStreamId = 's';\nstatic const char optMessages = 'm';\nstatic const char optLinger = 'l';\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string channel = samples::configuration::DEFAULT_CHANNEL;\n    std::int32_t streamId = samples::configuration::DEFAULT_STREAM_ID;\n    long long numberOfMessages = samples::configuration::DEFAULT_NUMBER_OF_MESSAGES;\n    int lingerTimeoutMs = samples::configuration::DEFAULT_LINGER_TIMEOUT_MS;\n};\n\ntypedef std::array<std::uint8_t, 256> buffer_t;\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.channel = cp.getOption(optChannel).getParam(0, s.channel);\n    s.streamId = cp.getOption(optStreamId).getParamAsInt(0, 1, INT32_MAX, s.streamId);\n    s.numberOfMessages = cp.getOption(optMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfMessages);\n    s.lingerTimeoutMs = cp.getOption(optLinger).getParamAsInt(0, 0, 60 * 60 * 1000, s.lingerTimeoutMs);\n\n    return s;\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,     0, 0, \"                Displays help information.\"));\n    cp.addOption(CommandOption(optPrefix,   1, 1, \"dir             Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optChannel,  1, 1, \"channel         Channel.\"));\n    cp.addOption(CommandOption(optStreamId, 1, 1, \"streamId        Stream ID.\"));\n    cp.addOption(CommandOption(optMessages, 1, 1, \"number          Number of Messages.\"));\n    cp.addOption(CommandOption(optLinger,   1, 1, \"milliseconds    Linger timeout in milliseconds.\"));\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Publishing to channel \" << settings.channel << \" on Stream ID \" << settings.streamId << std::endl;\n\n        aeron::Context context;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newPublicationHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int32_t sessionId, std::int64_t correlationId)\n            {\n                std::cout << \"Publication: \" << channel << \" \" << correlationId << \":\" << streamId << \":\" << sessionId << std::endl;\n            });\n\n        std::shared_ptr<Aeron> aeron = Aeron::connect(context);\n        signal(SIGINT, sigIntHandler);\n        // add the publication to start the process\n        std::int64_t id = aeron->addPublication(settings.channel, settings.streamId);\n\n        std::shared_ptr<Publication> publication = aeron->findPublication(id);\n        // wait for the publication to be valid\n        while (!publication)\n        {\n            std::this_thread::yield();\n            publication = aeron->findPublication(id);\n        }\n\n        const std::int64_t channelStatus = publication->channelStatus();\n\n        std::cout << \"Publication channel status (id=\" << publication->channelStatusId() << \") \"\n                  << (channelStatus == ChannelEndpointStatus::CHANNEL_ENDPOINT_ACTIVE ?\n                      \"ACTIVE\" : std::to_string(channelStatus))\n                  << std::endl;\n\n        AERON_DECL_ALIGNED(buffer_t buffer, 16);\n        concurrent::AtomicBuffer srcBuffer(&buffer[0], buffer.size());\n        char message[256];\n\n        for (std::int64_t i = 0; i < settings.numberOfMessages && running; i++)\n        {\n#if _MSC_VER\n            const int messageLen = ::sprintf_s(message, sizeof(message), \"Hello World! %\" PRId64, i);\n#else\n            const int messageLen = ::snprintf(message, sizeof(message), \"Hello World! %\" PRId64, i);\n#endif\n\n            srcBuffer.putBytes(0, reinterpret_cast<std::uint8_t *>(message), messageLen);\n\n            std::cout << \"offering \" << i << \"/\" << settings.numberOfMessages << \" - \";\n            std::cout.flush();\n\n            const std::int64_t result = publication->offer(srcBuffer, 0, messageLen);\n\n            if (result > 0)\n            {\n                std::cout << \"yay!\" << std::endl;\n            }\n            else if (BACK_PRESSURED == result)\n            {\n                std::cout << \"Offer failed due to back pressure\" << std::endl;\n            }\n            else if (NOT_CONNECTED == result)\n            {\n                std::cout << \"Offer failed because publisher is not connected to a subscriber\" << std::endl;\n            }\n            else if (ADMIN_ACTION == result)\n            {\n                std::cout << \"Offer failed because of an administration action in the system\" << std::endl;\n            }\n            else if (PUBLICATION_CLOSED == result)\n            {\n                std::cout << \"Offer failed because publication is closed\" << std::endl;\n            }\n            else\n            {\n                std::cout << \"Offer failed due to unknown reason \" << result << std::endl;\n            }\n\n            if (!publication->isConnected())\n            {\n                std::cout << \"No active subscribers detected\" << std::endl;\n            }\n\n            std::this_thread::sleep_for(std::chrono::seconds(1));\n        }\n\n        std::cout << \"Done sending.\" << std::endl;\n\n        if (settings.lingerTimeoutMs > 0)\n        {\n            std::cout << \"Lingering for \" << settings.lingerTimeoutMs << \" milliseconds.\" << std::endl;\n            std::this_thread::sleep_for(std::chrono::milliseconds(settings.lingerTimeoutMs));\n        }\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/BasicSubscriber.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdint>\n#include <thread>\n#include <csignal>\n\n#include \"Configuration.h\"\n#include \"util/CommandOptionParser.h\"\n#include \"Aeron.h\"\n#include \"FragmentAssembler.h\"\n#include \"concurrent/SleepingIdleStrategy.h\"\n\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nstatic const char optHelp = 'h';\nstatic const char optPrefix = 'p';\nstatic const char optChannel = 'c';\nstatic const char optStreamId = 's';\n\nstatic const std::chrono::duration<long, std::milli> IDLE_SLEEP_MS(1);\nstatic const int FRAGMENTS_LIMIT = 10;\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string channel = samples::configuration::DEFAULT_CHANNEL;\n    std::int32_t streamId = samples::configuration::DEFAULT_STREAM_ID;\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.channel = cp.getOption(optChannel).getParam(0, s.channel);\n    s.streamId = cp.getOption(optStreamId).getParamAsInt(0, 1, INT32_MAX, s.streamId);\n\n    return s;\n}\n\nfragment_handler_t printStringMessage()\n{\n    return [&](const AtomicBuffer &buffer, util::index_t offset, util::index_t length, const Header &header)\n    {\n        std::cout\n            << \"Message to stream \" << header.streamId() << \" from session \" << header.sessionId()\n            << \"(\" << length << \"@\" << offset << \") <<\"\n            << std::string(reinterpret_cast<const char *>(buffer.buffer()) + offset, static_cast<std::size_t>(length))\n            << \">>\" << std::endl;\n    };\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,     0, 0, \"            Displays help information.\"));\n    cp.addOption(CommandOption(optPrefix,   1, 1, \"dir         Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optChannel,  1, 1, \"channel     Channel.\"));\n    cp.addOption(CommandOption(optStreamId, 1, 1, \"streamId    Stream ID.\"));\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Subscribing to channel \" << settings.channel << \" on Stream ID \" << settings.streamId << std::endl;\n\n        aeron::Context context;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newSubscriptionHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int64_t correlationId)\n            {\n                std::cout << \"Subscription: \" << channel << \" \" << correlationId << \":\" << streamId << std::endl;\n            });\n\n        context.availableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Available image correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n            });\n\n        context.unavailableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Unavailable image on correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n            });\n\n        std::shared_ptr<Aeron> aeron = Aeron::connect(context);\n        signal(SIGINT, sigIntHandler);\n        // add the subscription to start the process\n        std::int64_t id = aeron->addSubscription(settings.channel, settings.streamId);\n\n        std::shared_ptr<Subscription> subscription = aeron->findSubscription(id);\n        // wait for the subscription to be valid\n        while (!subscription)\n        {\n            std::this_thread::yield();\n            subscription = aeron->findSubscription(id);\n        }\n\n        const std::int64_t channelStatus = subscription->channelStatus();\n\n        std::cout\n            << \"Subscription channel status (id=\" << subscription->channelStatusId() << \") \"\n            << (channelStatus == ChannelEndpointStatus::CHANNEL_ENDPOINT_ACTIVE ? \"ACTIVE\" : std::to_string(channelStatus))\n            << std::endl;\n\n        FragmentAssembler fragmentAssembler(printStringMessage());\n        fragment_handler_t handler = fragmentAssembler.handler();\n        SleepingIdleStrategy idleStrategy(IDLE_SLEEP_MS);\n\n        while (running)\n        {\n            const int fragmentsRead = subscription->poll(handler, FRAGMENTS_LIMIT);\n            idleStrategy.idle(fragmentsRead);\n        }\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/CMakeLists.txt",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nset(CLIENT_WRAPPER_LINK_LIB \"aeron_client_wrapper\")\nset(C_CLIENT_LINK_LIB \"aeron_static\")\nif (LINK_SAMPLES_CLIENT_SHARED OR MSVC)\n    set(C_CLIENT_LINK_LIB \"aeron\")\n    add_definitions(-DCLIENT_SHARED)\nendif ()\n\nset(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -DDISABLE_BOUNDS_CHECKS\")\n\nset(HEADERS\n    Configuration.h\n    RateReporter.h)\n\nadd_executable(BasicPublisher BasicPublisher.cpp ${HEADERS})\nadd_executable(BasicSubscriber BasicSubscriber.cpp ${HEADERS})\nadd_executable(StreamingPublisher StreamingPublisher.cpp ${HEADERS})\nadd_executable(RateSubscriber RateSubscriber.cpp ${HEADERS})\nadd_executable(Pong Pong.cpp ${HEADERS})\nadd_executable(Ping Ping.cpp ${HEADERS})\nadd_executable(Throughput Throughput.cpp ${HEADERS})\nadd_executable(PingPong PingPong.cpp ${HEADERS})\nadd_executable(ExclusivePingPong ExclusivePingPong.cpp ${HEADERS})\n\ntarget_include_directories(BasicPublisher\n    PUBLIC ${AERON_C_CLIENT_SOURCE_PATH} ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\ntarget_link_libraries(BasicPublisher\n    ${CLIENT_WRAPPER_LINK_LIB} ${C_CLIENT_LINK_LIB})\n\ntarget_include_directories(BasicSubscriber\n    PUBLIC ${AERON_C_CLIENT_SOURCE_PATH} ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\ntarget_link_libraries(BasicSubscriber\n    ${CLIENT_WRAPPER_LINK_LIB} ${C_CLIENT_LINK_LIB})\n\ntarget_include_directories(StreamingPublisher\n    PUBLIC ${AERON_C_CLIENT_SOURCE_PATH} ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\ntarget_link_libraries(StreamingPublisher\n    ${CLIENT_WRAPPER_LINK_LIB} ${C_CLIENT_LINK_LIB})\n\ntarget_include_directories(RateSubscriber\n    PUBLIC ${AERON_C_CLIENT_SOURCE_PATH} ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\ntarget_link_libraries(RateSubscriber\n    ${CLIENT_WRAPPER_LINK_LIB} ${C_CLIENT_LINK_LIB})\n\ntarget_include_directories(Pong\n    PUBLIC ${AERON_C_CLIENT_SOURCE_PATH} ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\ntarget_link_libraries(Pong\n    ${CLIENT_WRAPPER_LINK_LIB} ${C_CLIENT_LINK_LIB})\n\ntarget_include_directories(Ping\n    PUBLIC ${AERON_C_CLIENT_SOURCE_PATH} ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\ntarget_link_libraries(Ping\n    ${CLIENT_WRAPPER_LINK_LIB} ${C_CLIENT_LINK_LIB}\n    hdr_histogram_static)\nadd_dependencies(Ping hdr_histogram)\n\ntarget_include_directories(PingPong\n    PUBLIC ${AERON_C_CLIENT_SOURCE_PATH} ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\ntarget_link_libraries(PingPong\n    ${CLIENT_WRAPPER_LINK_LIB} ${C_CLIENT_LINK_LIB}\n    hdr_histogram_static)\nadd_dependencies(PingPong hdr_histogram)\n\ntarget_include_directories(ExclusivePingPong\n    PUBLIC ${AERON_C_CLIENT_SOURCE_PATH} ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\ntarget_link_libraries(ExclusivePingPong\n    ${CLIENT_WRAPPER_LINK_LIB} ${C_CLIENT_LINK_LIB}\n    hdr_histogram_static)\nadd_dependencies(ExclusivePingPong hdr_histogram)\n\ntarget_include_directories(Throughput\n    PUBLIC ${AERON_C_CLIENT_SOURCE_PATH} ${AERON_CLIENT_WRAPPER_SOURCE_PATH})\ntarget_link_libraries(Throughput\n    ${CLIENT_WRAPPER_LINK_LIB} ${C_CLIENT_LINK_LIB})\n\nif (AERON_INSTALL_TARGETS)\n    install(\n        TARGETS\n        BasicPublisher\n        BasicSubscriber\n        RateSubscriber\n        Ping\n        Pong\n        PingPong\n        ExclusivePingPong\n        Throughput\n        DESTINATION bin)\nendif ()\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/Configuration.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef INCLUDED_AERON_SAMPLES_CONFIGURATION__\n#define INCLUDED_AERON_SAMPLES_CONFIGURATION__\n\n#include <string>\n#include <cstdint>\n\nnamespace aeron { namespace samples { namespace configuration {\n\nconst static std::string DEFAULT_CHANNEL = \"aeron:udp?endpoint=localhost:20121\";\nconst static std::string DEFAULT_PING_CHANNEL = \"aeron:udp?endpoint=localhost:20123\";\nconst static std::string DEFAULT_PONG_CHANNEL = \"aeron:udp?endpoint=localhost:20124\";\nconst static std::int32_t DEFAULT_STREAM_ID = 1001;\nconst static std::int32_t DEFAULT_PING_STREAM_ID = 1002;\nconst static std::int32_t DEFAULT_PONG_STREAM_ID = 1003;\nconst static long long DEFAULT_NUMBER_OF_WARM_UP_MESSAGES = 100000;\nconst static long long DEFAULT_NUMBER_OF_MESSAGES = 10000000;\nconst static int DEFAULT_MESSAGE_LENGTH = 32;\nconst static int DEFAULT_LINGER_TIMEOUT_MS = 0;\nconst static int DEFAULT_FRAGMENT_COUNT_LIMIT = 10;\nconst static bool DEFAULT_RANDOM_MESSAGE_LENGTH = false;\nconst static bool DEFAULT_PUBLICATION_RATE_PROGRESS = false;\n\n}}}\n\n#endif\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/ExclusivePingPong.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdint>\n#include <cstdio>\n#include <thread>\n#include <csignal>\n\nextern \"C\"\n{\n#include <hdr/hdr_histogram.h>\n}\n\n#include \"Configuration.h\"\n#include \"concurrent/BusySpinIdleStrategy.h\"\n#include \"util/CommandOptionParser.h\"\n#include \"FragmentAssembler.h\"\n#include \"Aeron.h\"\n\nusing namespace std::chrono;\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nstatic const char optHelp           = 'h';\nstatic const char optPrefix         = 'p';\nstatic const char optPingChannel    = 'c';\nstatic const char optPongChannel    = 'C';\nstatic const char optPingStreamId   = 's';\nstatic const char optPongStreamId   = 'S';\nstatic const char optFrags          = 'f';\nstatic const char optMessages       = 'm';\nstatic const char optLength         = 'L';\nstatic const char optWarmupMessages = 'w';\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string pingChannel = samples::configuration::DEFAULT_PING_CHANNEL;\n    std::string pongChannel = samples::configuration::DEFAULT_PONG_CHANNEL;\n    std::int32_t pingStreamId = samples::configuration::DEFAULT_PING_STREAM_ID;\n    std::int32_t pongStreamId = samples::configuration::DEFAULT_PONG_STREAM_ID;\n    long long numberOfWarmupMessages = samples::configuration::DEFAULT_NUMBER_OF_WARM_UP_MESSAGES;\n    long long numberOfMessages = samples::configuration::DEFAULT_NUMBER_OF_MESSAGES;\n    int messageLength = samples::configuration::DEFAULT_MESSAGE_LENGTH;\n    int fragmentCountLimit = samples::configuration::DEFAULT_FRAGMENT_COUNT_LIMIT;\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.pingChannel = cp.getOption(optPingChannel).getParam(0, s.pingChannel);\n    s.pongChannel = cp.getOption(optPongChannel).getParam(0, s.pongChannel);\n    s.pingStreamId = cp.getOption(optPingStreamId).getParamAsInt(0, 1, INT32_MAX, s.pingStreamId);\n    s.pongStreamId = cp.getOption(optPongStreamId).getParamAsInt(0, 1, INT32_MAX, s.pongStreamId);\n    s.numberOfMessages = cp.getOption(optMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfMessages);\n    s.messageLength = cp.getOption(optLength).getParamAsInt(0, sizeof(std::int64_t), INT32_MAX, s.messageLength);\n    s.fragmentCountLimit = cp.getOption(optFrags).getParamAsInt(0, 1, INT32_MAX, s.fragmentCountLimit);\n    s.numberOfWarmupMessages = cp.getOption(optWarmupMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfWarmupMessages);\n\n    return s;\n}\n\nvoid sendPingAndReceivePong(\n    const fragment_handler_t &fragmentHandler,\n    ExclusivePublication &publication,\n    Subscription &subscription,\n    const Settings &settings)\n{\n    std::unique_ptr<std::uint8_t[]> buffer(new std::uint8_t[settings.messageLength]);\n    concurrent::AtomicBuffer srcBuffer(buffer.get(), static_cast<size_t>(settings.messageLength));\n    BusySpinIdleStrategy idleStrategy;\n\n    while (!subscription.isConnected())\n    {\n        std::this_thread::yield();\n    }\n\n    std::shared_ptr<Image> imageSharedPtr = subscription.imageByIndex(0);\n    Image &image = *imageSharedPtr;\n\n    for (std::int64_t i = 0; i < settings.numberOfMessages; i++)\n    {\n        std::int64_t position;\n\n        do\n        {\n            // timestamps in the message are relative to this app, so just send the timestamp directly.\n            steady_clock::time_point start = steady_clock::now();\n            srcBuffer.putBytes(0, (std::uint8_t *)&start, sizeof(steady_clock::time_point));\n        }\n        while ((position = publication.offer(srcBuffer, 0, settings.messageLength)) < 0L);\n\n        while (image.position() < position)\n        {\n            int fragments = image.poll(fragmentHandler, settings.fragmentCountLimit);\n            idleStrategy.idle(fragments);\n        }\n    }\n}\n\nstd::shared_ptr<Subscription> findSubscription(std::shared_ptr<Aeron> aeron, std::int64_t id)\n{\n    std::shared_ptr<Subscription> subscription = aeron->findSubscription(id);\n\n    while (!subscription)\n    {\n        std::this_thread::yield();\n        subscription = aeron->findSubscription(id);\n    }\n\n    return subscription;\n}\n\nstd::shared_ptr<ExclusivePublication> findPublication(std::shared_ptr<Aeron> aeron, std::int64_t id)\n{\n    std::shared_ptr<ExclusivePublication> publication = aeron->findExclusivePublication(id);\n\n    while (!publication)\n    {\n        std::this_thread::yield();\n        publication = aeron->findExclusivePublication(id);\n    }\n\n    return publication;\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,           0, 0, \"                Displays help information.\"));\n    cp.addOption(CommandOption(optPrefix,         1, 1, \"dir             Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optPingChannel,    1, 1, \"channel         Ping Channel.\"));\n    cp.addOption(CommandOption(optPongChannel,    1, 1, \"channel         Pong Channel.\"));\n    cp.addOption(CommandOption(optPingStreamId,   1, 1, \"streamId        Ping Stream ID.\"));\n    cp.addOption(CommandOption(optPongStreamId,   1, 1, \"streamId        Pong Stream ID.\"));\n    cp.addOption(CommandOption(optMessages,       1, 1, \"number          Number of Messages.\"));\n    cp.addOption(CommandOption(optLength,         1, 1, \"length          Length of Messages.\"));\n    cp.addOption(CommandOption(optFrags,          1, 1, \"limit           Fragment Count Limit.\"));\n    cp.addOption(CommandOption(optWarmupMessages, 1, 1, \"number          Number of Messages for warmup.\"));\n\n    std::shared_ptr<std::thread> pongThread;\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Pong at \" << settings.pongChannel << \" on Stream ID \" << settings.pongStreamId << std::endl;\n        std::cout << \"Ping at \" << settings.pingChannel << \" on Stream ID \" << settings.pingStreamId << std::endl;\n\n        aeron::Context context;\n        std::atomic<int> countDown(1);\n        std::int64_t pongSubscriptionId, pingPublicationId, pingSubscriptionId, pongPublicationId;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newSubscriptionHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int64_t correlationId)\n            {\n                std::cout << \"Subscription: \" << channel << \" \" << correlationId << \":\" << streamId << std::endl;\n            });\n\n        context.newPublicationHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int32_t sessionId, std::int64_t correlationId)\n            {\n                std::cout << \"Publication: \" << channel << \" \" << correlationId << \":\" << streamId << \":\" << sessionId << std::endl;\n            });\n\n        context.availableImageHandler(\n            [&](Image &image)\n            {\n                std::cout << \"Available image correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n\n                if (image.subscriptionRegistrationId() == pongSubscriptionId)\n                {\n                    countDown--;\n                }\n            });\n\n        context.unavailableImageHandler([](Image &image)\n        {\n            std::cout << \"Unavailable image on correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n            std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n        });\n\n        context.preTouchMappedMemory(true);\n\n        std::shared_ptr<Aeron> aeron = Aeron::connect(context);\n        signal(SIGINT, sigIntHandler);\n        pongSubscriptionId = aeron->addSubscription(settings.pongChannel, settings.pongStreamId);\n        pingPublicationId = aeron->addExclusivePublication(settings.pingChannel, settings.pingStreamId);\n        pingSubscriptionId = aeron->addSubscription(settings.pingChannel, settings.pingStreamId);\n        pongPublicationId = aeron->addExclusivePublication(settings.pongChannel, settings.pongStreamId);\n\n        std::shared_ptr<Subscription> pongSubscription, pingSubscription;\n        std::shared_ptr<ExclusivePublication> pingPublication, pongPublication;\n\n        pongSubscription = findSubscription(aeron, pongSubscriptionId);\n        pingSubscription = findSubscription(aeron, pingSubscriptionId);\n        pingPublication = findPublication(aeron, pingPublicationId);\n        pongPublication = findPublication(aeron, pongPublicationId);\n\n        while (countDown > 0)\n        {\n            std::this_thread::yield();\n        }\n\n        ExclusivePublication &pongPublicationRef = *pongPublication;\n        Subscription &pingSubscriptionRef = *pingSubscription;\n        BusySpinIdleStrategy idleStrategy;\n        BusySpinIdleStrategy pingHandlerIdleStrategy;\n        FragmentAssembler pingFragmentAssembler(\n            [&](AtomicBuffer &buffer, index_t offset, index_t length, const Header &header)\n            {\n                if (pongPublicationRef.offer(buffer, offset, length) > 0L)\n                {\n                    return;\n                }\n\n                while (pongPublicationRef.offer(buffer, offset, length) < 0L)\n                {\n                    pingHandlerIdleStrategy.idle();\n                }\n            });\n\n        fragment_handler_t ping_handler = pingFragmentAssembler.handler();\n\n        pongThread = std::make_shared<std::thread>(\n            [&]()\n            {\n                while (!pingSubscriptionRef.isConnected())\n                {\n                    std::this_thread::yield();\n                }\n\n                std::shared_ptr<Image> imageSharedPtr = pingSubscriptionRef.imageByIndex(0);\n                Image &image = *imageSharedPtr;\n\n                while (running)\n                {\n                    idleStrategy.idle(image.poll(ping_handler, settings.fragmentCountLimit));\n                }\n            });\n\n        aeron::util::OnScopeExit tidy(\n            [&]()\n            {\n                running = false;\n\n                if (nullptr != pongThread && pongThread->joinable())\n                {\n                    pongThread->join();\n                    pongThread = nullptr;\n                }\n            });\n\n        if (settings.numberOfWarmupMessages > 0)\n        {\n            Settings warmupSettings = settings;\n            warmupSettings.numberOfMessages = warmupSettings.numberOfWarmupMessages;\n\n            const steady_clock::time_point start = steady_clock::now();\n\n            std::cout << \"Warming up the media driver with \"\n                      << toStringWithCommas(warmupSettings.numberOfWarmupMessages) << \" messages of length \"\n                      << toStringWithCommas(warmupSettings.messageLength) << std::endl;\n\n            sendPingAndReceivePong(\n                [](AtomicBuffer &, index_t, index_t, Header &){},\n                *pingPublication,\n                *pongSubscription,\n                warmupSettings);\n\n            std::int64_t nanoDuration = duration<std::int64_t, std::nano>(steady_clock::now() - start).count();\n\n            std::cout << \"Warmed up the media driver in \" << nanoDuration << \" [ns]\" << std::endl;\n        }\n\n        hdr_histogram *histogram;\n        hdr_init(1, 10 * 1000 * 1000 * 1000LL, 3, &histogram);\n\n        do\n        {\n            hdr_reset(histogram);\n\n            FragmentAssembler fragmentAssembler(\n                [&](const AtomicBuffer &buffer, index_t offset, index_t length, const Header &header)\n                {\n                    steady_clock::time_point end = steady_clock::now();\n                    steady_clock::time_point start;\n\n                    buffer.getBytes(offset, (std::uint8_t *)&start, sizeof(steady_clock::time_point));\n                    std::int64_t nanoRtt = duration<std::int64_t, std::nano>(end - start).count();\n\n                    hdr_record_value(histogram, nanoRtt);\n                });\n\n            std::cout << \"Pinging \"\n                      << toStringWithCommas(settings.numberOfMessages) << \" messages of length \"\n                      << toStringWithCommas(settings.messageLength) << \" bytes\" << std::endl;\n\n            steady_clock::time_point startRun = steady_clock::now();\n            sendPingAndReceivePong(fragmentAssembler.handler(), *pingPublication, *pongSubscription, settings);\n            steady_clock::time_point endRun = steady_clock::now();\n\n            hdr_percentiles_print(histogram, stdout, 5, 1000.0, CLASSIC);\n            fflush(stdout);\n\n            double runDuration = duration<double>(endRun - startRun).count();\n            std::cout << \"Throughput of \"\n                      << toStringWithCommas((double)settings.numberOfMessages / runDuration)\n                      << \" RTTs/sec\" << std::endl;\n        }\n        while (running && continuationBarrier(\"Execute again?\"));\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/ExclusiveThroughput.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdio>\n#include <csignal>\n#include <thread>\n#include <cinttypes>\n\n#include \"util/CommandOptionParser.h\"\n#include \"concurrent/BusySpinIdleStrategy.h\"\n#include \"Aeron.h\"\n#include \"Configuration.h\"\n#include \"RateReporter.h\"\n#include \"FragmentAssembler.h\"\n\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nstatic const char optHelp     = 'h';\nstatic const char optPrefix   = 'p';\nstatic const char optChannel  = 'c';\nstatic const char optStreamId = 's';\nstatic const char optMessages = 'm';\nstatic const char optLinger   = 'l';\nstatic const char optLength   = 'L';\nstatic const char optProgress = 'P';\nstatic const char optFrags    = 'f';\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string channel = samples::configuration::DEFAULT_CHANNEL;\n    std::int32_t streamId = samples::configuration::DEFAULT_STREAM_ID;\n    long long numberOfMessages = samples::configuration::DEFAULT_NUMBER_OF_MESSAGES;\n    int messageLength = samples::configuration::DEFAULT_MESSAGE_LENGTH;\n    int lingerTimeoutMs = samples::configuration::DEFAULT_LINGER_TIMEOUT_MS;\n    int fragmentCountLimit = samples::configuration::DEFAULT_FRAGMENT_COUNT_LIMIT;\n    bool progress = samples::configuration::DEFAULT_PUBLICATION_RATE_PROGRESS;\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.channel = cp.getOption(optChannel).getParam(0, s.channel);\n    s.streamId = cp.getOption(optStreamId).getParamAsInt(0, 1, INT32_MAX, s.streamId);\n    s.numberOfMessages = cp.getOption(optMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfMessages);\n    s.messageLength = cp.getOption(optLength).getParamAsInt(0, sizeof(std::int64_t), INT32_MAX, s.messageLength);\n    s.lingerTimeoutMs = cp.getOption(optLinger).getParamAsInt(0, 0, 60 * 60 * 1000, s.lingerTimeoutMs);\n    s.fragmentCountLimit = cp.getOption(optFrags).getParamAsInt(0, 1, INT32_MAX, s.fragmentCountLimit);\n    s.progress = cp.getOption(optProgress).isPresent();\n\n    return s;\n}\n\nstd::atomic<bool> printingActive;\n\nvoid printRate(double messagesPerSec, double bytesPerSec, std::int64_t totalFragments, std::int64_t totalBytes)\n{\n    if (printingActive)\n    {\n        std::printf(\n            \"%.04g msgs/sec, %.04g bytes/sec, totals %\" PRId64 \" messages %\" PRId64 \" MB payloads\\n\",\n            messagesPerSec, bytesPerSec, totalFragments, totalBytes / (1024 * 1024));\n    }\n}\n\nfragment_handler_t rateReporterHandler(RateReporter &rateReporter)\n{\n    return\n        [&rateReporter](AtomicBuffer &, util::index_t, util::index_t length, Header &)\n        {\n            rateReporter.onMessage(1, length);\n        };\n}\n\ninline bool isRunning()\n{\n    return std::atomic_load_explicit(&running, std::memory_order_relaxed);\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,     0, 0, \"                Displays help information.\"));\n    cp.addOption(CommandOption(optProgress, 0, 0, \"                Print rate progress while sending.\"));\n    cp.addOption(CommandOption(optPrefix,   1, 1, \"dir             Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optChannel,  1, 1, \"channel         Channel.\"));\n    cp.addOption(CommandOption(optStreamId, 1, 1, \"streamId        Stream ID.\"));\n    cp.addOption(CommandOption(optMessages, 1, 1, \"number          Number of Messages.\"));\n    cp.addOption(CommandOption(optLength,   1, 1, \"length          Length of Messages.\"));\n    cp.addOption(CommandOption(optLinger,   1, 1, \"milliseconds    Linger timeout in milliseconds.\"));\n    cp.addOption(CommandOption(optFrags,    1, 1, \"limit           Fragment Count Limit.\"));\n\n    std::shared_ptr<std::thread> rateReporterThread;\n    std::shared_ptr<std::thread> pollThread;\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Subscribing to channel \" << settings.channel << \" on Stream ID \" << settings.streamId << std::endl;\n\n        std::cout << \"Streaming \" << toStringWithCommas(settings.numberOfMessages) << \" messages of payload length \"\n                  << settings.messageLength << \" bytes to \"\n                  << settings.channel << \" on stream ID \"\n                  << settings.streamId << std::endl;\n\n        aeron::Context context;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newPublicationHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int32_t sessionId, std::int64_t correlationId)\n            {\n                std::cout << \"Publication: \" << channel << \" \" << correlationId << \":\" << streamId << \":\" << sessionId << std::endl;\n            });\n\n        context.newSubscriptionHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int64_t correlationId)\n            {\n                std::cout << \"Subscription: \" << channel << \" \" << correlationId << \":\" << streamId << std::endl;\n            });\n\n        context.availableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Available image correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n            });\n\n        context.unavailableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Unavailable image on correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << std::endl;\n            });\n\n        Aeron aeron(context);\n        signal(SIGINT, sigIntHandler);\n        std::int64_t subscriptionId = aeron.addSubscription(settings.channel, settings.streamId);\n        std::int64_t publicationId = aeron.addExclusivePublication(settings.channel, settings.streamId);\n\n        std::shared_ptr<Subscription> subscription = aeron.findSubscription(subscriptionId);\n        while (!subscription)\n        {\n            std::this_thread::yield();\n            subscription = aeron.findSubscription(subscriptionId);\n        }\n\n        std::shared_ptr<ExclusivePublication> publication = aeron.findExclusivePublication(publicationId);\n        while (!publication)\n        {\n            std::this_thread::yield();\n            publication = aeron.findExclusivePublication(publicationId);\n        }\n\n        if (settings.messageLength > publication->maxPayloadLength())\n        {\n            std::cerr << \"ERROR - tryClaim limit: messageLength=\" << settings.messageLength\n                      << \" > maxPayloadLength=\" << publication->maxPayloadLength()\n                      << \", use publication offer or increase MTU.\" << std::endl;\n            return -1;\n        }\n\n        RateReporter rateReporter(std::chrono::seconds(1), printRate);\n        FragmentAssembler fragmentAssembler(rateReporterHandler(rateReporter));\n        auto handler = fragmentAssembler.handler();\n\n        ExclusivePublication *publicationPtr = publication.get();\n        Subscription *subscriptionPtr = subscription.get();\n\n        aeron::util::OnScopeExit tidy(\n            [&]()\n            {\n                running = false;\n                rateReporter.halt();\n\n                if (nullptr != pollThread && pollThread->joinable())\n                {\n                    pollThread->join();\n                    pollThread = nullptr;\n                }\n\n                if (nullptr != rateReporterThread && rateReporterThread->joinable())\n                {\n                    rateReporterThread->join();\n                    rateReporterThread = nullptr;\n                }\n            });\n\n        if (settings.progress)\n        {\n            rateReporterThread = std::make_shared<std::thread>([&rateReporter](){ rateReporter.run(); });\n        }\n\n        std::uint64_t failedPolls = 0;\n        std::uint64_t successfulPolls = 0;\n\n        pollThread = std::make_shared<std::thread>(\n            [&]()\n            {\n                while (!subscriptionPtr->isConnected())\n                {\n                    std::this_thread::yield();\n                }\n\n                std::shared_ptr<Image> imageSharedPtr = subscriptionPtr->imageByIndex(0);\n                Image &image = *imageSharedPtr;\n                BusySpinIdleStrategy idleStrategy;\n\n                while (isRunning())\n                {\n                    int fragments = image.poll(handler, settings.fragmentCountLimit);\n                    if (0 == fragments)\n                    {\n                        ++failedPolls;\n                    }\n                    else\n                    {\n                        ++successfulPolls;\n                    }\n\n                    idleStrategy.idle(fragments);\n                }\n            });\n\n        do\n        {\n            BufferClaim bufferClaim;\n            std::uint64_t backPressureCount = 0;\n\n            printingActive = true;\n\n            if (nullptr == rateReporterThread)\n            {\n                rateReporter.reset();\n            }\n\n            for (std::int64_t i = 0; i < settings.numberOfMessages && isRunning(); i++)\n            {\n                while (publicationPtr->tryClaim(settings.messageLength, bufferClaim) < 0L)\n                {\n                    ++backPressureCount;\n                    if (!isRunning())\n                    {\n                        break;\n                    }\n                }\n\n                bufferClaim.buffer().putInt64(bufferClaim.offset(), i);\n                bufferClaim.commit();\n            }\n\n            if (nullptr == rateReporterThread)\n            {\n                rateReporter.report();\n            }\n\n            std::cout << \"Done streaming.\" << std::endl;\n            std::cout << \"Publication back pressure ratio \";\n            std::cout << ((double)backPressureCount / (double)settings.numberOfMessages) << std::endl;\n\n            std::cout << \"Subscription failure ratio \";\n            std::cout << ((double)failedPolls / (double)(failedPolls + successfulPolls)) << std::endl;\n\n            if (isRunning() && settings.lingerTimeoutMs > 0)\n            {\n                std::cout << \"Lingering for \" << settings.lingerTimeoutMs << \" milliseconds.\" << std::endl;\n                std::this_thread::sleep_for(std::chrono::milliseconds(settings.lingerTimeoutMs));\n            }\n\n            printingActive = false;\n        }\n        while (isRunning() && continuationBarrier(\"Execute again?\"));\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/LossStat.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <iostream>\n#include <thread>\n#include <cstdio>\n#include <cinttypes>\n\n#include \"util/CommandOptionParser.h\"\n#include \"concurrent/reports/LossReportReader.h\"\n#include \"Context.h\"\n\nusing namespace aeron;\nusing namespace aeron::util;\nusing namespace aeron::concurrent;\nusing namespace aeron::concurrent::reports;\nusing namespace std::chrono;\n\nstatic const char optHelp = 'h';\nstatic const char optPath = 'p';\n\nstruct Settings\n{\n    std::string basePath = Context::defaultAeronPath();\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.basePath = cp.getOption(optPath).getParam(0, s.basePath);\n\n    return s;\n}\n\nstd::string formatDate(std::int64_t millisecondsSinceEpoch)\n{\n    // yyyy-MM-dd HH:mm:ss.SSSZ\n    milliseconds msSinceEpoch(millisecondsSinceEpoch);\n    milliseconds msAfterSec(millisecondsSinceEpoch % 1000);\n    system_clock::time_point tp(msSinceEpoch);\n\n    std::time_t tm = system_clock::to_time_t(tp);\n\n    char timeBuffer[80];\n    char msecBuffer[8];\n    char tzBuffer[8];\n    struct tm localTm{};\n\n#ifdef _MSC_VER\n    localtime_s(&localTm, &tm);\n#else\n    ::localtime_r(&tm, &localTm);\n#endif\n\n    std::strftime(timeBuffer, sizeof(timeBuffer) - 1, \"%Y-%m-%d %H:%M:%S.\", &localTm);\n    std::snprintf(msecBuffer, sizeof(msecBuffer) - 1, \"%03\" PRId64, msAfterSec.count());\n    std::strftime(tzBuffer, sizeof(tzBuffer) - 1, \"%z\", &localTm);\n\n    return std::string(timeBuffer) + std::string(msecBuffer) + std::string(tzBuffer);\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,   0, 0, \"              Displays help information.\"));\n    cp.addOption(CommandOption(optPath,   1, 1, \"basePath      Base Path to shared memory. Default: \" + Context::defaultAeronPath()));\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n        const std::string filename = LossReportDescriptor::file(settings.basePath);\n\n        if (MemoryMappedFile::getFileSize(filename.c_str()) < 0)\n        {\n            std::cerr << \"Loss report does not exist: \" << filename << std::endl;\n            std::exit(1);\n        }\n\n        MemoryMappedFile::ptr_t lossReportFile = MemoryMappedFile::mapExistingReadOnly(filename.c_str());\n        AtomicBuffer buffer(lossReportFile->getMemoryPtr(), lossReportFile->getMemorySize());\n\n        std::cout <<\n            \"OBSERVATION_COUNT, \" <<\n            \"TOTAL_BYTES_LOST, \" <<\n            \"FIRST_OBSERVATION, \" <<\n            \"LAST_OBSERVATION, \" <<\n            \"SESSION_ID, \" <<\n            \"STREAM_ID, \" <<\n            \"CHANNEL, \" <<\n            \"SOURCE\" << std::endl;\n\n        const int entriesRead = LossReportReader::read(\n            buffer,\n            [](\n                std::int64_t observationCount,\n                std::int64_t totalBytesLost,\n                std::int64_t firstObservationTimestamp,\n                std::int64_t lastObservationTimestamp,\n                std::int32_t sessionId,\n                std::int32_t streamId,\n                const std::string &channel,\n                const std::string &source)\n            {\n                std::cout << std::to_string(observationCount) << \",\";\n                std::cout << std::to_string(totalBytesLost) << \",\";\n                std::cout << formatDate(firstObservationTimestamp) << \",\";\n                std::cout << formatDate(lastObservationTimestamp) << \",\";\n                std::cout << std::to_string(sessionId) << \",\";\n                std::cout << std::to_string(streamId) << \",\";\n                std::cout << channel << \",\";\n                std::cout << source << std::endl;\n            });\n\n        std::cout << std::to_string(entriesRead) << \" entries read\" << std::endl;\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/Ping.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdint>\n#include <cstdio>\n#include <csignal>\n#include <thread>\n\nextern \"C\"\n{\n#include <hdr/hdr_histogram.h>\n}\n\n#include \"util/CommandOptionParser.h\"\n#include \"concurrent/BusySpinIdleStrategy.h\"\n#include \"FragmentAssembler.h\"\n#include \"Configuration.h\"\n#include \"Aeron.h\"\n\nusing namespace std::chrono;\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nvoid checkInterrupt()\n{\n    if (!running)\n    {\n        throw std::runtime_error(\"Interrupted\");\n    }\n}\n\nstatic const char optHelp           = 'h';\nstatic const char optPrefix         = 'p';\nstatic const char optPingChannel    = 'c';\nstatic const char optPongChannel    = 'C';\nstatic const char optPingStreamId   = 's';\nstatic const char optPongStreamId   = 'S';\nstatic const char optFrags          = 'f';\nstatic const char optMessages       = 'm';\nstatic const char optLength         = 'L';\nstatic const char optWarmupMessages = 'w';\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string pingChannel = samples::configuration::DEFAULT_PING_CHANNEL;\n    std::string pongChannel = samples::configuration::DEFAULT_PONG_CHANNEL;\n    std::int32_t pingStreamId = samples::configuration::DEFAULT_PING_STREAM_ID;\n    std::int32_t pongStreamId = samples::configuration::DEFAULT_PONG_STREAM_ID;\n    long long numberOfWarmupMessages = samples::configuration::DEFAULT_NUMBER_OF_WARM_UP_MESSAGES;\n    long long numberOfMessages = samples::configuration::DEFAULT_NUMBER_OF_MESSAGES;\n    int messageLength = samples::configuration::DEFAULT_MESSAGE_LENGTH;\n    int fragmentCountLimit = samples::configuration::DEFAULT_FRAGMENT_COUNT_LIMIT;\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.pingChannel = cp.getOption(optPingChannel).getParam(0, s.pingChannel);\n    s.pongChannel = cp.getOption(optPongChannel).getParam(0, s.pongChannel);\n    s.pingStreamId = cp.getOption(optPingStreamId).getParamAsInt(0, 1, INT32_MAX, s.pingStreamId);\n    s.pongStreamId = cp.getOption(optPongStreamId).getParamAsInt(0, 1, INT32_MAX, s.pongStreamId);\n    s.numberOfMessages = cp.getOption(optMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfMessages);\n    s.messageLength = cp.getOption(optLength).getParamAsInt(0, sizeof(std::int64_t), INT32_MAX, s.messageLength);\n    s.fragmentCountLimit = cp.getOption(optFrags).getParamAsInt(0, 1, INT32_MAX, s.fragmentCountLimit);\n    s.numberOfWarmupMessages = cp.getOption(optWarmupMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfWarmupMessages);\n\n    return s;\n}\n\nvoid sendPingAndReceivePong(\n    const fragment_handler_t &fragmentHandler,\n    ExclusivePublication &publication,\n    Subscription &subscription,\n    const Settings &settings)\n{\n    std::unique_ptr<std::uint8_t[]> buffer(new std::uint8_t[settings.messageLength]);\n    concurrent::AtomicBuffer srcBuffer(buffer.get(), static_cast<size_t>(settings.messageLength));\n    BusySpinIdleStrategy idleStrategy;\n    std::shared_ptr<Image> imageSharedPtr = subscription.imageByIndex(0);\n    Image &image = *imageSharedPtr;\n\n    for (std::int64_t i = 0; i < settings.numberOfMessages; i++)\n    {\n        std::int64_t position;\n\n        do\n        {\n            checkInterrupt();\n\n            // timestamps in the message are relative to this app, so just send the timestamp directly.\n            steady_clock::time_point start = steady_clock::now();\n            srcBuffer.putBytes(0, (std::uint8_t *)&start, sizeof(steady_clock::time_point));\n        }\n        while ((position = publication.offer(srcBuffer, 0, settings.messageLength)) < 0L);\n\n        while (image.position() < position)\n        {\n            checkInterrupt();\n\n            int fragments = image.poll(fragmentHandler, settings.fragmentCountLimit);\n            idleStrategy.idle(fragments);\n        }\n    }\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,          0, 0, \"                Displays help information.\"));\n    cp.addOption(CommandOption(optPrefix,        1, 1, \"dir             Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optPingChannel,   1, 1, \"channel         Ping Channel.\"));\n    cp.addOption(CommandOption(optPongChannel,   1, 1, \"channel         Pong Channel.\"));\n    cp.addOption(CommandOption(optPingStreamId,  1, 1, \"streamId        Ping Stream ID.\"));\n    cp.addOption(CommandOption(optPongStreamId,  1, 1, \"streamId        Pong Stream ID.\"));\n    cp.addOption(CommandOption(optMessages,      1, 1, \"number          Number of Messages.\"));\n    cp.addOption(CommandOption(optLength,        1, 1, \"length          Length of Messages.\"));\n    cp.addOption(CommandOption(optFrags,         1, 1, \"limit           Fragment Count Limit.\"));\n    cp.addOption(CommandOption(optWarmupMessages,1, 1, \"number          Number of Messages for warmup.\"));\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Subscribing Pong at \" << settings.pongChannel << \" on Stream ID \" << settings.pongStreamId << std::endl;\n        std::cout << \"Publishing Ping at \" << settings.pingChannel << \" on Stream ID \" << settings.pingStreamId << std::endl;\n\n        aeron::Context context;\n        std::atomic<int> countDown(1);\n        std::int64_t subscriptionId;\n        std::int64_t publicationId;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newSubscriptionHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int64_t correlationId)\n            {\n                std::cout << \"Subscription: \" << channel << \" \" << correlationId << \":\" << streamId << std::endl;\n            });\n\n        context.newPublicationHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int32_t sessionId, std::int64_t correlationId)\n            {\n                std::cout << \"Publication: \" << channel << \" \" << correlationId << \":\" << streamId << \":\" << sessionId << std::endl;\n            });\n\n        context.availableImageHandler(\n            [&](Image &image)\n            {\n                std::cout << \"Available image correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n\n                if (image.subscriptionRegistrationId() == subscriptionId)\n                {\n                    countDown--;\n                }\n            });\n\n        context.unavailableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Unavailable image on correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n            });\n\n        context.preTouchMappedMemory(true);\n\n        Aeron aeron(context);\n        signal(SIGINT, sigIntHandler);\n        subscriptionId = aeron.addSubscription(settings.pongChannel, settings.pongStreamId);\n        publicationId = aeron.addExclusivePublication(settings.pingChannel, settings.pingStreamId);\n\n        std::shared_ptr<Subscription> pongSubscription = aeron.findSubscription(subscriptionId);\n        while (!pongSubscription)\n        {\n            checkInterrupt();\n\n            std::this_thread::yield();\n            pongSubscription = aeron.findSubscription(subscriptionId);\n        }\n\n        std::shared_ptr<ExclusivePublication> pingPublication = aeron.findExclusivePublication(publicationId);\n        while (!pingPublication)\n        {\n            checkInterrupt();\n\n            std::this_thread::yield();\n            pingPublication = aeron.findExclusivePublication(publicationId);\n        }\n\n        while (countDown > 0)\n        {\n            checkInterrupt();\n\n            std::this_thread::yield();\n        }\n\n        if (settings.numberOfWarmupMessages > 0)\n        {\n            Settings warmupSettings = settings;\n            warmupSettings.numberOfMessages = warmupSettings.numberOfWarmupMessages;\n\n            const steady_clock::time_point start = steady_clock::now();\n\n            std::cout << \"Warming up the media driver with \"\n                      << toStringWithCommas(warmupSettings.numberOfWarmupMessages) << \" messages of length \"\n                      << toStringWithCommas(warmupSettings.messageLength) << std::endl;\n\n            sendPingAndReceivePong(\n                [](AtomicBuffer&, index_t, index_t, Header&){}, *pingPublication, *pongSubscription, warmupSettings);\n\n            std::int64_t nanoDuration = duration<std::int64_t, std::nano>(steady_clock::now() - start).count();\n\n            std::cout << \"Warmed up the media driver in \" << nanoDuration << \" [ns]\" << std::endl;\n        }\n\n        hdr_histogram *histogram;\n        hdr_init(1, 10 * 1000 * 1000 * 1000LL, 3, &histogram);\n\n        do\n        {\n            hdr_reset(histogram);\n\n            FragmentAssembler fragmentAssembler(\n                [&](const AtomicBuffer &buffer, index_t offset, index_t length, const Header &header)\n                {\n                    steady_clock::time_point end = steady_clock::now();\n                    steady_clock::time_point start;\n\n                    buffer.getBytes(offset, (std::uint8_t *)&start, sizeof(steady_clock::time_point));\n                    std::int64_t nanoRtt = duration<std::int64_t, std::nano>(end - start).count();\n\n                    hdr_record_value(histogram, nanoRtt);\n                });\n\n            std::cout << \"Pinging \"\n                      << toStringWithCommas(settings.numberOfMessages) << \" messages of length \"\n                      << toStringWithCommas(settings.messageLength) << \" bytes\" << std::endl;\n\n            sendPingAndReceivePong(fragmentAssembler.handler(), *pingPublication, *pongSubscription, settings);\n\n            hdr_percentiles_print(histogram, stdout, 5, 1000.0, CLASSIC);\n            fflush(stdout);\n        }\n        while (running && continuationBarrier(\"Execute again?\"));\n\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/PingPong.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdint>\n#include <cstdio>\n#include <thread>\n#include <csignal>\n\nextern \"C\"\n{\n#include <hdr/hdr_histogram.h>\n}\n\n#include \"Configuration.h\"\n#include \"concurrent/BusySpinIdleStrategy.h\"\n#include \"util/CommandOptionParser.h\"\n#include \"FragmentAssembler.h\"\n#include \"Aeron.h\"\n\nusing namespace std::chrono;\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nstatic const char optHelp           = 'h';\nstatic const char optPrefix         = 'p';\nstatic const char optPingChannel    = 'c';\nstatic const char optPongChannel    = 'C';\nstatic const char optPingStreamId   = 's';\nstatic const char optPongStreamId   = 'S';\nstatic const char optFrags          = 'f';\nstatic const char optMessages       = 'm';\nstatic const char optLength         = 'L';\nstatic const char optWarmupMessages = 'w';\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string pingChannel = samples::configuration::DEFAULT_PING_CHANNEL;\n    std::string pongChannel = samples::configuration::DEFAULT_PONG_CHANNEL;\n    std::int32_t pingStreamId = samples::configuration::DEFAULT_PING_STREAM_ID;\n    std::int32_t pongStreamId = samples::configuration::DEFAULT_PONG_STREAM_ID;\n    long long numberOfWarmupMessages = samples::configuration::DEFAULT_NUMBER_OF_WARM_UP_MESSAGES;\n    long long numberOfMessages = samples::configuration::DEFAULT_NUMBER_OF_MESSAGES;\n    int messageLength = samples::configuration::DEFAULT_MESSAGE_LENGTH;\n    int fragmentCountLimit = samples::configuration::DEFAULT_FRAGMENT_COUNT_LIMIT;\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.pingChannel = cp.getOption(optPingChannel).getParam(0, s.pingChannel);\n    s.pongChannel = cp.getOption(optPongChannel).getParam(0, s.pongChannel);\n    s.pingStreamId = cp.getOption(optPingStreamId).getParamAsInt(0, 1, INT32_MAX, s.pingStreamId);\n    s.pongStreamId = cp.getOption(optPongStreamId).getParamAsInt(0, 1, INT32_MAX, s.pongStreamId);\n    s.numberOfMessages = cp.getOption(optMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfMessages);\n    s.messageLength = cp.getOption(optLength).getParamAsInt(0, sizeof(std::int64_t), INT32_MAX, s.messageLength);\n    s.fragmentCountLimit = cp.getOption(optFrags).getParamAsInt(0, 1, INT32_MAX, s.fragmentCountLimit);\n    s.numberOfWarmupMessages = cp.getOption(optWarmupMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfWarmupMessages);\n\n    return s;\n}\n\nvoid sendPingAndReceivePong(\n    const fragment_handler_t &fragmentHandler,\n    Publication &publication,\n    Subscription &subscription,\n    const Settings &settings)\n{\n    std::unique_ptr<std::uint8_t[]> buffer(new std::uint8_t[settings.messageLength]);\n    concurrent::AtomicBuffer srcBuffer(buffer.get(), static_cast<size_t>(settings.messageLength));\n    BusySpinIdleStrategy idleStrategy;\n\n    while (!subscription.isConnected())\n    {\n        std::this_thread::yield();\n    }\n\n    std::shared_ptr<Image> imageSharedPtr = subscription.imageByIndex(0);\n    Image &image = *imageSharedPtr;\n\n    for (std::int64_t i = 0; i < settings.numberOfMessages; i++)\n    {\n        std::int64_t position;\n\n        do\n        {\n            // timestamps in the message are relative to this app, so just send the timestamp directly.\n            steady_clock::time_point start = steady_clock::now();\n            srcBuffer.putBytes(0, (std::uint8_t *)&start, sizeof(steady_clock::time_point));\n        }\n        while ((position = publication.offer(srcBuffer, 0, settings.messageLength)) < 0L);\n\n        while (image.position() < position)\n        {\n            int fragments = image.poll(fragmentHandler, settings.fragmentCountLimit);\n            idleStrategy.idle(fragments);\n        }\n    }\n}\n\nstd::shared_ptr<Subscription> findSubscription(std::shared_ptr<Aeron> aeron, std::int64_t id)\n{\n    std::shared_ptr<Subscription> subscription = aeron->findSubscription(id);\n\n    while (!subscription)\n    {\n        std::this_thread::yield();\n        subscription = aeron->findSubscription(id);\n    }\n\n    return subscription;\n}\n\nstd::shared_ptr<Publication> findPublication(std::shared_ptr<Aeron> aeron, std::int64_t id)\n{\n    std::shared_ptr<Publication> publication = aeron->findPublication(id);\n\n    while (!publication)\n    {\n        std::this_thread::yield();\n        publication = aeron->findPublication(id);\n    }\n\n    return publication;\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,           0, 0, \"                Displays help information.\"));\n    cp.addOption(CommandOption(optPrefix,         1, 1, \"dir             Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optPingChannel,    1, 1, \"channel         Ping Channel.\"));\n    cp.addOption(CommandOption(optPongChannel,    1, 1, \"channel         Pong Channel.\"));\n    cp.addOption(CommandOption(optPingStreamId,   1, 1, \"streamId        Ping Stream ID.\"));\n    cp.addOption(CommandOption(optPongStreamId,   1, 1, \"streamId        Pong Stream ID.\"));\n    cp.addOption(CommandOption(optMessages,       1, 1, \"number          Number of Messages.\"));\n    cp.addOption(CommandOption(optLength,         1, 1, \"length          Length of Messages.\"));\n    cp.addOption(CommandOption(optFrags,          1, 1, \"limit           Fragment Count Limit.\"));\n    cp.addOption(CommandOption(optWarmupMessages, 1, 1, \"number          Number of Messages for warmup.\"));\n\n    std::shared_ptr<std::thread> pongThread;\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Pong at \" << settings.pongChannel << \" on Stream ID \" << settings.pongStreamId << std::endl;\n        std::cout << \"Ping at \" << settings.pingChannel << \" on Stream ID \" << settings.pingStreamId << std::endl;\n\n        aeron::Context context;\n        std::atomic<int> countDown(1);\n        std::int64_t pongSubscriptionId, pingPublicationId, pingSubscriptionId, pongPublicationId;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newSubscriptionHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int64_t correlationId)\n            {\n                std::cout << \"Subscription: \" << channel << \" \" << correlationId << \":\" << streamId << std::endl;\n            });\n\n        context.newPublicationHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int32_t sessionId, std::int64_t correlationId)\n            {\n                std::cout << \"Publication: \" << channel << \" \" << correlationId << \":\" << streamId << \":\" << sessionId << std::endl;\n            });\n\n        context.availableImageHandler(\n            [&](Image &image)\n            {\n                std::cout << \"Available image correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n\n                if (image.subscriptionRegistrationId() == pongSubscriptionId)\n                {\n                    countDown--;\n                }\n            });\n\n        context.unavailableImageHandler([](Image &image)\n        {\n            std::cout << \"Unavailable image on correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n            std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n        });\n\n        context.preTouchMappedMemory(true);\n\n        std::shared_ptr<Aeron> aeron = Aeron::connect(context);\n        signal(SIGINT, sigIntHandler);\n        pongSubscriptionId = aeron->addSubscription(settings.pongChannel, settings.pongStreamId);\n        pingPublicationId = aeron->addPublication(settings.pingChannel, settings.pingStreamId);\n        pingSubscriptionId = aeron->addSubscription(settings.pingChannel, settings.pingStreamId);\n        pongPublicationId = aeron->addPublication(settings.pongChannel, settings.pongStreamId);\n\n        std::shared_ptr<Subscription> pongSubscription, pingSubscription;\n        std::shared_ptr<Publication> pingPublication, pongPublication;\n\n        pongSubscription = findSubscription(aeron, pongSubscriptionId);\n        pingSubscription = findSubscription(aeron, pingSubscriptionId);\n        pingPublication = findPublication(aeron, pingPublicationId);\n        pongPublication = findPublication(aeron, pongPublicationId);\n\n        while (countDown > 0)\n        {\n            std::this_thread::yield();\n        }\n\n        Publication &pongPublicationRef = *pongPublication;\n        Subscription &pingSubscriptionRef = *pingSubscription;\n        BusySpinIdleStrategy idleStrategy;\n        BusySpinIdleStrategy pingHandlerIdleStrategy;\n        FragmentAssembler pingFragmentAssembler(\n            [&](AtomicBuffer &buffer, index_t offset, index_t length, const Header &header)\n            {\n                if (pongPublicationRef.offer(buffer, offset, length) > 0L)\n                {\n                    return;\n                }\n\n                while (pongPublicationRef.offer(buffer, offset, length) < 0L)\n                {\n                    pingHandlerIdleStrategy.idle();\n                }\n            });\n\n        fragment_handler_t ping_handler = pingFragmentAssembler.handler();\n\n        pongThread = std::make_shared<std::thread>(\n            [&]()\n            {\n                while (!pingSubscriptionRef.isConnected())\n                {\n                    std::this_thread::yield();\n                }\n\n                std::shared_ptr<Image> imageSharedPtr = pingSubscriptionRef.imageByIndex(0);\n                Image &image = *imageSharedPtr;\n\n                while (running)\n                {\n                    idleStrategy.idle(image.poll(ping_handler, settings.fragmentCountLimit));\n                }\n            });\n\n        aeron::util::OnScopeExit tidy(\n            [&]()\n            {\n                running = false;\n\n                if (nullptr != pongThread && pongThread->joinable())\n                {\n                    pongThread->join();\n                    pongThread = nullptr;\n                }\n            });\n\n        if (settings.numberOfWarmupMessages > 0)\n        {\n            Settings warmupSettings = settings;\n            warmupSettings.numberOfMessages = warmupSettings.numberOfWarmupMessages;\n\n            const steady_clock::time_point start = steady_clock::now();\n\n            std::cout << \"Warming up the media driver with \"\n                      << toStringWithCommas(warmupSettings.numberOfWarmupMessages) << \" messages of length \"\n                      << toStringWithCommas(warmupSettings.messageLength) << std::endl;\n\n            sendPingAndReceivePong(\n                [](AtomicBuffer &, index_t, index_t, Header &){},\n                *pingPublication,\n                *pongSubscription,\n                warmupSettings);\n\n            std::int64_t nanoDuration = duration<std::int64_t, std::nano>(steady_clock::now() - start).count();\n\n            std::cout << \"Warmed up the media driver in \" << nanoDuration << \" [ns]\" << std::endl;\n        }\n\n        hdr_histogram *histogram;\n        hdr_init(1, 10 * 1000 * 1000 * 1000LL, 3, &histogram);\n\n        do\n        {\n            hdr_reset(histogram);\n\n            FragmentAssembler fragmentAssembler(\n                [&](const AtomicBuffer &buffer, index_t offset, index_t length, const Header &header)\n                {\n                    steady_clock::time_point end = steady_clock::now();\n                    steady_clock::time_point start;\n\n                    buffer.getBytes(offset, (std::uint8_t *)&start, sizeof(steady_clock::time_point));\n                    std::int64_t nanoRtt = duration<std::int64_t, std::nano>(end - start).count();\n\n                    hdr_record_value(histogram, nanoRtt);\n                });\n\n            std::cout << \"Pinging \"\n                      << toStringWithCommas(settings.numberOfMessages) << \" messages of length \"\n                      << toStringWithCommas(settings.messageLength) << \" bytes\" << std::endl;\n\n            steady_clock::time_point startRun = steady_clock::now();\n            sendPingAndReceivePong(fragmentAssembler.handler(), *pingPublication, *pongSubscription, settings);\n            steady_clock::time_point endRun = steady_clock::now();\n\n            hdr_percentiles_print(histogram, stdout, 5, 1000.0, CLASSIC);\n            fflush(stdout);\n\n            double runDuration = duration<double>(endRun - startRun).count();\n            std::cout << \"Throughput of \"\n                      << toStringWithCommas((double)settings.numberOfMessages / runDuration)\n                      << \" RTTs/sec\" << std::endl;\n        }\n        while (running && continuationBarrier(\"Execute again?\"));\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/Pong.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdint>\n#include <csignal>\n#include <thread>\n\n#include \"util/CommandOptionParser.h\"\n#include \"concurrent/BusySpinIdleStrategy.h\"\n#include \"FragmentAssembler.h\"\n#include \"Configuration.h\"\n#include \"Aeron.h\"\n\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nvoid checkInterrupt()\n{\n    if (!running)\n    {\n        throw std::runtime_error(\"Interrupted\");\n    }\n}\n\n\nstatic const char optHelp         = 'h';\nstatic const char optPrefix       = 'p';\nstatic const char optPingChannel  = 'c';\nstatic const char optPongChannel  = 'C';\nstatic const char optPingStreamId = 's';\nstatic const char optPongStreamId = 'S';\nstatic const char optFrags        = 'f';\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string pingChannel = samples::configuration::DEFAULT_PING_CHANNEL;\n    std::string pongChannel = samples::configuration::DEFAULT_PONG_CHANNEL;\n    std::int32_t pingStreamId = samples::configuration::DEFAULT_PING_STREAM_ID;\n    std::int32_t pongStreamId = samples::configuration::DEFAULT_PONG_STREAM_ID;\n    int fragmentCountLimit = samples::configuration::DEFAULT_FRAGMENT_COUNT_LIMIT;\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.pingChannel = cp.getOption(optPingChannel).getParam(0, s.pingChannel);\n    s.pongChannel = cp.getOption(optPongChannel).getParam(0, s.pongChannel);\n    s.pingStreamId = cp.getOption(optPingStreamId).getParamAsInt(0, 1, INT32_MAX, s.pingStreamId);\n    s.pongStreamId = cp.getOption(optPongStreamId).getParamAsInt(0, 1, INT32_MAX, s.pongStreamId);\n    s.fragmentCountLimit = cp.getOption(optFrags).getParamAsInt(0, 1, INT32_MAX, s.fragmentCountLimit);\n\n    return s;\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,         0, 0, \"                Displays help information.\"));\n    cp.addOption(CommandOption(optPrefix,       1, 1, \"dir             Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optPingChannel,  1, 1, \"channel         Ping Channel.\"));\n    cp.addOption(CommandOption(optPongChannel,  1, 1, \"channel         Pong Channel.\"));\n    cp.addOption(CommandOption(optPingStreamId, 1, 1, \"streamId        Ping Stream ID.\"));\n    cp.addOption(CommandOption(optPongStreamId, 1, 1, \"streamId        Pong Stream ID.\"));\n    cp.addOption(CommandOption(optFrags,        1, 1, \"limit           Fragment Count Limit.\"));\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Subscribing Ping at \" << settings.pingChannel << \" on Stream ID \" << settings.pingStreamId << std::endl;\n        std::cout << \"Publishing Pong at \" << settings.pongChannel << \" on Stream ID \" << settings.pongStreamId << std::endl;\n\n        aeron::Context context;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newSubscriptionHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int64_t correlationId)\n            {\n                std::cout << \"Subscription: \" << channel << \" \" << correlationId << \":\" << streamId << std::endl;\n            });\n\n        context.newPublicationHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int32_t sessionId, std::int64_t correlationId)\n            {\n                std::cout << \"Publication: \" << channel << \" \" << correlationId << \":\" << streamId << \":\" << sessionId << std::endl;\n            });\n\n        context.availableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Available image correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n            });\n\n        context.unavailableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Unavailable image on correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n            });\n\n        context.preTouchMappedMemory(true);\n\n        Aeron aeron(context);\n        signal(SIGINT, sigIntHandler);\n        std::int64_t subscriptionId = aeron.addSubscription(settings.pingChannel, settings.pingStreamId);\n        std::int64_t publicationId = aeron.addExclusivePublication(settings.pongChannel, settings.pongStreamId);\n\n        std::shared_ptr<Subscription> pingSubscription = aeron.findSubscription(subscriptionId);\n        while (!pingSubscription)\n        {\n            checkInterrupt();\n\n            std::this_thread::yield();\n            pingSubscription = aeron.findSubscription(subscriptionId);\n        }\n\n        std::shared_ptr<ExclusivePublication> pongPublication = aeron.findExclusivePublication(publicationId);\n        while (!pongPublication)\n        {\n            checkInterrupt();\n\n            std::this_thread::yield();\n            pongPublication = aeron.findExclusivePublication(publicationId);\n        }\n\n        ExclusivePublication &pongPublicationRef = *pongPublication;\n        Subscription &pingSubscriptionRef = *pingSubscription;\n\n        BusySpinIdleStrategy idleStrategy;\n        BusySpinIdleStrategy pingHandlerIdleStrategy;\n        FragmentAssembler fragmentAssembler(\n            [&](AtomicBuffer &buffer, index_t offset, index_t length, const Header &header)\n            {\n                if (pongPublicationRef.offer(buffer, offset, length) > 0L)\n                {\n                    return;\n                }\n\n                pingHandlerIdleStrategy.reset();\n                while (pongPublicationRef.offer(buffer, offset, length) < 0L)\n                {\n                    pingHandlerIdleStrategy.idle();\n                }\n            });\n\n        fragment_handler_t handler = fragmentAssembler.handler();\n\n        while (running)\n        {\n            idleStrategy.idle(pingSubscriptionRef.poll(handler, settings.fragmentCountLimit));\n        }\n\n        std::cout << \"Shutting down...\\n\";\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/RateReporter.h",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef AERON_RATEREPORTER_H\n#define AERON_RATEREPORTER_H\n\n#include <functional>\n#include <atomic>\n#include <thread>\n#include <chrono>\n\n#include \"util/BitUtil.h\"\n\nnamespace aeron\n{\n\nusing namespace std::chrono;\n\nclass RateReporter\n{\npublic:\n    typedef std::function<void(double, double, std::int64_t, std::int64_t)> on_rate_report_t;\n\n    RateReporter(nanoseconds reportInterval, const on_rate_report_t &onReport) :\n        m_reportInterval(reportInterval),\n        m_onReport(onReport),\n        m_lastTimestamp(steady_clock::now())\n    {\n        static_cast<void>(m_paddingBefore);\n        static_cast<void>(m_paddingAfter);\n    }\n\n    void run()\n    {\n        while (m_running)\n        {\n            std::this_thread::sleep_for(m_reportInterval);\n            report();\n        }\n    }\n\n    void report()\n    {\n        std::int64_t totalBytes = std::atomic_load_explicit(&m_totalBytes, std::memory_order_acquire);\n        std::int64_t totalMessages = std::atomic_load_explicit(&m_totalMessages, std::memory_order_acquire);\n        steady_clock::time_point timestamp = steady_clock::now();\n\n        const double timeSpanSec = duration<double, std::ratio<1, 1>>(timestamp - m_lastTimestamp).count();\n        const double messagesPerSec = static_cast<double>(totalMessages - m_lastTotalMessages) / timeSpanSec;\n        const double bytesPerSec = static_cast<double>(totalBytes - m_lastTotalBytes) / timeSpanSec;\n\n        m_onReport(messagesPerSec, bytesPerSec, totalMessages, totalBytes);\n\n        m_lastTotalBytes = totalBytes;\n        m_lastTotalMessages = totalMessages;\n        m_lastTimestamp = timestamp;\n    }\n\n    void reset()\n    {\n        std::int64_t currentTotalBytes = std::atomic_load_explicit(&m_totalBytes, std::memory_order_relaxed);\n        std::int64_t currentTotalMessages = std::atomic_load_explicit(&m_totalMessages, std::memory_order_relaxed);\n        steady_clock::time_point currentTimestamp = steady_clock::now();\n\n        m_lastTotalBytes = currentTotalBytes;\n        m_lastTotalMessages = currentTotalMessages;\n        m_lastTimestamp = currentTimestamp;\n    }\n\n    inline void halt()\n    {\n        m_running = false;\n    }\n\n    inline void onMessage(std::int64_t messages, std::int64_t bytes)\n    {\n        std::int64_t totalBytes = std::atomic_load_explicit(&m_totalBytes, std::memory_order_relaxed);\n        std::int64_t totalMessages = std::atomic_load_explicit(&m_totalMessages, std::memory_order_relaxed);\n\n        std::atomic_store_explicit(&m_totalBytes, totalBytes + bytes, std::memory_order_release);\n        std::atomic_store_explicit(&m_totalMessages, totalMessages + messages, std::memory_order_release);\n    }\n\nprivate:\n    const nanoseconds m_reportInterval;\n    const on_rate_report_t m_onReport;\n\n    char m_paddingBefore[aeron::util::BitUtil::CACHE_LINE_LENGTH] = {};\n    std::atomic<bool> m_running = { true };\n    std::atomic<std::int64_t> m_totalBytes = { 0 };\n    std::atomic<std::int64_t> m_totalMessages = { 0 };\n    char m_paddingAfter[aeron::util::BitUtil::CACHE_LINE_LENGTH] = {};\n\n    std::int64_t m_lastTotalBytes = 0;\n    std::int64_t m_lastTotalMessages = 0;\n\n    steady_clock::time_point m_lastTimestamp;\n};\n\n}\n\n#endif //AERON_RATEREPORTER_H\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/RateSubscriber.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdint>\n#include <cstdio>\n#include <csignal>\n#include <thread>\n#include <cinttypes>\n\n#include \"util/CommandOptionParser.h\"\n#include \"concurrent/BusySpinIdleStrategy.h\"\n#include \"Configuration.h\"\n#include \"RateReporter.h\"\n#include \"FragmentAssembler.h\"\n#include \"Aeron.h\"\n\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nstatic const char optHelp = 'h';\nstatic const char optPrefix = 'p';\nstatic const char optChannel = 'c';\nstatic const char optStreamId = 's';\nstatic const char optFrags = 'f';\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string channel = samples::configuration::DEFAULT_CHANNEL;\n    std::int32_t streamId = samples::configuration::DEFAULT_STREAM_ID;\n    int fragmentCountLimit = samples::configuration::DEFAULT_FRAGMENT_COUNT_LIMIT;\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.channel = cp.getOption(optChannel).getParam(0, s.channel);\n    s.streamId = cp.getOption(optStreamId).getParamAsInt(0, 1, INT32_MAX, s.streamId);\n    s.fragmentCountLimit = cp.getOption(optFrags).getParamAsInt(0, 1, INT32_MAX, s.fragmentCountLimit);\n\n    return s;\n}\n\nvoid printRate(double messagesPerSec, double bytesPerSec, std::int64_t totalFragments, std::int64_t totalBytes)\n{\n    std::printf(\n        \"%.04g msgs/sec, %.04g bytes/sec, totals %\" PRId64 \" messages %\" PRId64 \" MB payloads\\n\",\n        messagesPerSec, bytesPerSec, totalFragments, totalBytes / (1024 * 1024));\n}\n\nfragment_handler_t rateReporterHandler(RateReporter &rateReporter)\n{\n    return\n        [&](AtomicBuffer &, util::index_t, util::index_t length, Header &)\n        {\n            rateReporter.onMessage(1, length);\n        };\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,     0, 0, \"                Displays help information.\"));\n    cp.addOption(CommandOption(optPrefix,   1, 1, \"dir             Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optChannel,  1, 1, \"channel         Channel.\"));\n    cp.addOption(CommandOption(optStreamId, 1, 1, \"streamId        Stream ID.\"));\n    cp.addOption(CommandOption(optFrags,    1, 1, \"limit           Fragment Count Limit.\"));\n\n    std::shared_ptr<std::thread> rateReporterThread;\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Subscribing to channel \" << settings.channel << \" on Stream ID \" << settings.streamId << std::endl;\n\n        aeron::Context context;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newSubscriptionHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int64_t correlationId)\n            {\n                std::cout << \"Subscription: \" << channel << \" \" << correlationId << \":\" << streamId << std::endl;\n            });\n\n        context.availableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Available image correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n            });\n\n        context.unavailableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Unavailable image on correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n            });\n\n        Aeron aeron(context);\n        signal(SIGINT, sigIntHandler);\n        // add the subscription to start the process\n        std::int64_t id = aeron.addSubscription(settings.channel, settings.streamId);\n\n        std::shared_ptr<Subscription> subscription = aeron.findSubscription(id);\n        // wait for the subscription to be valid\n        while (!subscription)\n        {\n            std::this_thread::yield();\n            subscription = aeron.findSubscription(id);\n        }\n\n        BusySpinIdleStrategy idleStrategy;\n        RateReporter rateReporter(std::chrono::seconds(1), printRate);\n        FragmentAssembler fragmentAssembler(rateReporterHandler(rateReporter));\n        fragment_handler_t handler = fragmentAssembler.handler();\n        Subscription *subscriptionPtr = subscription.get();\n\n        aeron::util::OnScopeExit tidy(\n            [&]()\n            {\n                rateReporter.halt();\n\n                if (nullptr != rateReporterThread && rateReporterThread->joinable())\n                {\n                    rateReporterThread->join();\n                    rateReporterThread = nullptr;\n                }\n            });\n\n        rateReporterThread = std::make_shared<std::thread>([&](){ rateReporter.run(); });\n\n        while (running)\n        {\n            idleStrategy.idle(subscriptionPtr->poll(handler, settings.fragmentCountLimit));\n        }\n\n        std::cout << \"Shutting down...\\n\";\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/StreamingPublisher.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdio>\n#include <thread>\n#include <cinttypes>\n#include <csignal>\n#include <climits>\n#include <random>\n\n#include \"util/CommandOptionParser.h\"\n#include \"concurrent/BusySpinIdleStrategy.h\"\n#include \"Configuration.h\"\n#include \"RateReporter.h\"\n#include \"Aeron.h\"\n\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nstatic const char optHelp     = 'h';\nstatic const char optPrefix   = 'p';\nstatic const char optChannel  = 'c';\nstatic const char optStreamId = 's';\nstatic const char optMessages = 'm';\nstatic const char optLinger   = 'l';\nstatic const char optLength   = 'L';\nstatic const char optRandLen  = 'r';\nstatic const char optProgress = 'P';\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string channel = samples::configuration::DEFAULT_CHANNEL;\n    std::int32_t streamId = samples::configuration::DEFAULT_STREAM_ID;\n    long long numberOfMessages = samples::configuration::DEFAULT_NUMBER_OF_MESSAGES;\n    int messageLength = samples::configuration::DEFAULT_MESSAGE_LENGTH;\n    int lingerTimeoutMs = samples::configuration::DEFAULT_LINGER_TIMEOUT_MS;\n    bool randomMessageLength = samples::configuration::DEFAULT_RANDOM_MESSAGE_LENGTH;\n    bool progress = samples::configuration::DEFAULT_PUBLICATION_RATE_PROGRESS;\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.channel = cp.getOption(optChannel).getParam(0, s.channel);\n    s.streamId = cp.getOption(optStreamId).getParamAsInt(0, 1, INT32_MAX, s.streamId);\n    s.numberOfMessages = cp.getOption(optMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfMessages);\n    s.messageLength = cp.getOption(optLength).getParamAsInt(0, sizeof(std::int64_t), INT32_MAX, s.messageLength);\n    s.lingerTimeoutMs = cp.getOption(optLinger).getParamAsInt(0, 0, 60 * 60 * 1000, s.lingerTimeoutMs);\n    s.randomMessageLength = cp.getOption(optRandLen).isPresent();\n    s.progress = cp.getOption(optProgress).isPresent();\n\n    return s;\n}\n\nstd::atomic<bool> printingActive;\n\nvoid printRate(double messagesPerSec, double bytesPerSec, std::int64_t totalFragments, std::int64_t totalBytes)\n{\n    if (printingActive)\n    {\n        std::printf(\n            \"%.04g msgs/sec, %.04g bytes/sec, totals %\" PRId64 \" messages %\" PRId64 \" MB payloads\\n\",\n            messagesPerSec, bytesPerSec, totalFragments, totalBytes / (1024 * 1024));\n    }\n}\n\ntypedef std::function<int()> on_new_length_t;\n\nstatic std::random_device randomDevice;\nstatic std::default_random_engine randomEngine(randomDevice());\nstatic std::uniform_int_distribution<int> uniformLengthDistribution;\n\non_new_length_t composeLengthGenerator(bool random, int max)\n{\n    if (random)\n    {\n        std::uniform_int_distribution<int>::param_type param(sizeof(std::int64_t), max);\n        uniformLengthDistribution.param(param);\n\n        return [&]() { return uniformLengthDistribution(randomEngine); };\n    }\n    else\n    {\n        return [max]() { return max; };\n    }\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,     0, 0, \"                Displays help information.\"));\n    cp.addOption(CommandOption(optRandLen,  0, 0, \"                Random Message Length.\"));\n    cp.addOption(CommandOption(optProgress, 0, 0, \"                Print rate progress while sending.\"));\n    cp.addOption(CommandOption(optPrefix,   1, 1, \"dir             Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optChannel,  1, 1, \"channel         Channel.\"));\n    cp.addOption(CommandOption(optStreamId, 1, 1, \"streamId        Stream ID.\"));\n    cp.addOption(CommandOption(optMessages, 1, 1, \"number          Number of Messages.\"));\n    cp.addOption(CommandOption(optLength,   1, 1, \"length          Length of Messages.\"));\n    cp.addOption(CommandOption(optLinger,   1, 1, \"milliseconds    Linger timeout in milliseconds.\"));\n\n    std::shared_ptr<std::thread> rateReporterThread;\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Streaming \" << toStringWithCommas(settings.numberOfMessages) << \" messages of\"\n                  << (settings.randomMessageLength ? \" random\" : \"\") << \" payload length \"\n                  << settings.messageLength << \" bytes to \"\n                  << settings.channel << \" on stream ID \"\n                  << settings.streamId << std::endl;\n\n        aeron::Context context;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newPublicationHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int32_t sessionId, std::int64_t correlationId)\n            {\n                std::cout << \"Publication: \" << channel << \" \" << correlationId << \":\" << streamId << \":\" << sessionId << std::endl;\n            });\n\n        Aeron aeron(context);\n        signal(SIGINT, sigIntHandler);\n        std::int64_t id = aeron.addPublication(settings.channel, settings.streamId);\n        std::shared_ptr<Publication> publication = aeron.findPublication(id);\n\n        while (!publication)\n        {\n            std::this_thread::yield();\n            publication = aeron.findPublication(id);\n        }\n\n        std::unique_ptr<std::uint8_t[]> buffer(new std::uint8_t[settings.messageLength]);\n        concurrent::AtomicBuffer srcBuffer(buffer.get(), static_cast<size_t>(settings.messageLength));\n        srcBuffer.setMemory(0, settings.messageLength, 0);\n\n        BusySpinIdleStrategy offerIdleStrategy;\n        on_new_length_t lengthGenerator = composeLengthGenerator(settings.randomMessageLength, settings.messageLength);\n        RateReporter rateReporter(std::chrono::seconds(1), printRate);\n        Publication *publicationPtr = publication.get();\n\n        aeron::util::OnScopeExit tidy(\n            [&]()\n            {\n                rateReporter.halt();\n\n                if (nullptr != rateReporterThread && rateReporterThread->joinable())\n                {\n                    rateReporterThread->join();\n                    rateReporterThread = nullptr;\n                }\n            });\n\n        if (settings.progress)\n        {\n            rateReporterThread = std::make_shared<std::thread>([&](){ rateReporter.run(); });\n        }\n\n        do\n        {\n            printingActive = true;\n            long backPressureCount = 0;\n\n            if (nullptr == rateReporterThread)\n            {\n                rateReporter.reset();\n            }\n\n            for (std::int64_t i = 0; i < settings.numberOfMessages && running; i++)\n            {\n                const int length = lengthGenerator();\n                srcBuffer.putInt64(0, i);\n\n                offerIdleStrategy.reset();\n                while (publicationPtr->offer(srcBuffer, 0, length) < 0L)\n                {\n                    backPressureCount++;\n\n                    if (!running)\n                    {\n                        break;\n                    }\n\n                    offerIdleStrategy.idle();\n                }\n\n                rateReporter.onMessage(1, length);\n            }\n\n            if (nullptr == rateReporterThread)\n            {\n                rateReporter.report();\n            }\n\n            std::cout << \"Done streaming. Back pressure ratio \";\n            std::cout << ((double)backPressureCount / (double)settings.numberOfMessages) << std::endl;\n\n            if (running && settings.lingerTimeoutMs > 0)\n            {\n                std::cout << \"Lingering for \" << settings.lingerTimeoutMs << \" milliseconds.\" << std::endl;\n                std::this_thread::sleep_for(std::chrono::milliseconds(settings.lingerTimeoutMs));\n            }\n\n            printingActive = false;\n        }\n        while (running && continuationBarrier(\"Execute again?\"));\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/cpp/Throughput.cpp",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstdio>\n#include <csignal>\n#include <thread>\n#include <cinttypes>\n\n#include \"util/CommandOptionParser.h\"\n#include \"concurrent/BusySpinIdleStrategy.h\"\n#include \"Configuration.h\"\n#include \"RateReporter.h\"\n#include \"FragmentAssembler.h\"\n#include \"Aeron.h\"\n\nusing namespace aeron::util;\nusing namespace aeron;\n\nstd::atomic<bool> running(true);\n\nvoid sigIntHandler(int)\n{\n    running = false;\n}\n\nstatic const char optHelp     = 'h';\nstatic const char optPrefix   = 'p';\nstatic const char optChannel  = 'c';\nstatic const char optStreamId = 's';\nstatic const char optMessages = 'm';\nstatic const char optLinger   = 'l';\nstatic const char optLength   = 'L';\nstatic const char optProgress = 'P';\nstatic const char optFrags    = 'f';\n\nstruct Settings\n{\n    std::string dirPrefix;\n    std::string channel = samples::configuration::DEFAULT_CHANNEL;\n    std::int32_t streamId = samples::configuration::DEFAULT_STREAM_ID;\n    long long numberOfMessages = samples::configuration::DEFAULT_NUMBER_OF_MESSAGES;\n    int messageLength = samples::configuration::DEFAULT_MESSAGE_LENGTH;\n    int lingerTimeoutMs = samples::configuration::DEFAULT_LINGER_TIMEOUT_MS;\n    int fragmentCountLimit = samples::configuration::DEFAULT_FRAGMENT_COUNT_LIMIT;\n    bool progress = samples::configuration::DEFAULT_PUBLICATION_RATE_PROGRESS;\n};\n\nSettings parseCmdLine(CommandOptionParser &cp, int argc, char **argv)\n{\n    cp.parse(argc, argv);\n    if (cp.getOption(optHelp).isPresent())\n    {\n        cp.displayOptionsHelp(std::cout);\n        exit(0);\n    }\n\n    Settings s;\n\n    s.dirPrefix = cp.getOption(optPrefix).getParam(0, s.dirPrefix);\n    s.channel = cp.getOption(optChannel).getParam(0, s.channel);\n    s.streamId = cp.getOption(optStreamId).getParamAsInt(0, 1, INT32_MAX, s.streamId);\n    s.numberOfMessages = cp.getOption(optMessages).getParamAsLong(0, 0, INT64_MAX, s.numberOfMessages);\n    s.messageLength = cp.getOption(optLength).getParamAsInt(0, sizeof(std::int64_t), INT32_MAX, s.messageLength);\n    s.lingerTimeoutMs = cp.getOption(optLinger).getParamAsInt(0, 0, 60 * 60 * 1000, s.lingerTimeoutMs);\n    s.fragmentCountLimit = cp.getOption(optFrags).getParamAsInt(0, 1, INT32_MAX, s.fragmentCountLimit);\n    s.progress = cp.getOption(optProgress).isPresent();\n\n    return s;\n}\n\nstd::atomic<bool> printingActive;\n\nvoid printRate(double messagesPerSec, double bytesPerSec, std::int64_t totalFragments, std::int64_t totalBytes)\n{\n    if (printingActive)\n    {\n        std::printf(\n            \"%.04g msgs/sec, %.04g bytes/sec, totals %\" PRId64 \" messages %\" PRId64 \" MB payloads\\n\",\n            messagesPerSec, bytesPerSec, totalFragments, totalBytes / (1024 * 1024));\n    }\n}\n\nfragment_handler_t rateReporterHandler(RateReporter &rateReporter)\n{\n    return [&rateReporter](AtomicBuffer&, util::index_t, util::index_t length, Header&) { rateReporter.onMessage(1, length); };\n}\n\ninline bool isRunning()\n{\n    return std::atomic_load_explicit(&running, std::memory_order_relaxed);\n}\n\nint main(int argc, char **argv)\n{\n    CommandOptionParser cp;\n    cp.addOption(CommandOption(optHelp,     0, 0, \"                Displays help information.\"));\n    cp.addOption(CommandOption(optProgress, 0, 0, \"                Print rate progress while sending.\"));\n    cp.addOption(CommandOption(optPrefix,   1, 1, \"dir             Prefix directory for aeron driver.\"));\n    cp.addOption(CommandOption(optChannel,  1, 1, \"channel         Channel.\"));\n    cp.addOption(CommandOption(optStreamId, 1, 1, \"streamId        Stream ID.\"));\n    cp.addOption(CommandOption(optMessages, 1, 1, \"number          Number of Messages.\"));\n    cp.addOption(CommandOption(optLength,   1, 1, \"length          Length of Messages.\"));\n    cp.addOption(CommandOption(optLinger,   1, 1, \"milliseconds    Linger timeout in milliseconds.\"));\n    cp.addOption(CommandOption(optFrags,    1, 1, \"limit           Fragment Count Limit.\"));\n\n    std::shared_ptr<std::thread> rateReporterThread;\n    std::shared_ptr<std::thread> pollThread;\n\n    try\n    {\n        Settings settings = parseCmdLine(cp, argc, argv);\n\n        std::cout << \"Subscribing to channel \" << settings.channel << \" on Stream ID \" << settings.streamId << std::endl;\n\n        std::cout << \"Streaming \" << toStringWithCommas(settings.numberOfMessages) << \" messages of payload length \"\n                  << settings.messageLength << \" bytes to \"\n                  << settings.channel << \" on stream ID \"\n                  << settings.streamId << std::endl;\n\n        aeron::Context context;\n\n        if (!settings.dirPrefix.empty())\n        {\n            context.aeronDir(settings.dirPrefix);\n        }\n\n        context.newPublicationHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int32_t sessionId, std::int64_t correlationId)\n            {\n                std::cout << \"Publication: \" << channel << \" \" << correlationId << \":\" << streamId << \":\" << sessionId << std::endl;\n            });\n\n        context.newSubscriptionHandler(\n            [](const std::string &channel, std::int32_t streamId, std::int64_t correlationId)\n            {\n                std::cout << \"Subscription: \" << channel << \" \" << correlationId << \":\" << streamId << std::endl;\n            });\n\n        context.availableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Available image correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << \" from \" << image.sourceIdentity() << std::endl;\n            });\n\n        context.unavailableImageHandler(\n            [](Image &image)\n            {\n                std::cout << \"Unavailable image on correlationId=\" << image.correlationId() << \" sessionId=\" << image.sessionId();\n                std::cout << \" at position=\" << image.position() << std::endl;\n            });\n\n        Aeron aeron(context);\n        signal(SIGINT, sigIntHandler);\n        std::int64_t subscriptionId = aeron.addSubscription(settings.channel, settings.streamId);\n        std::int64_t publicationId = aeron.addPublication(settings.channel, settings.streamId);\n\n        std::shared_ptr<Subscription> subscription = aeron.findSubscription(subscriptionId);\n        while (!subscription)\n        {\n            std::this_thread::yield();\n            subscription = aeron.findSubscription(subscriptionId);\n        }\n\n        std::shared_ptr<Publication> publication = aeron.findPublication(publicationId);\n        while (!publication)\n        {\n            std::this_thread::yield();\n            publication = aeron.findPublication(publicationId);\n        }\n\n        if (settings.messageLength > publication->maxPayloadLength())\n        {\n            std::cerr << \"ERROR - tryClaim limit: messageLength=\" << settings.messageLength\n                      << \" > maxPayloadLength=\" << publication->maxPayloadLength()\n                      << \", use publication offer or increase MTU.\" << std::endl;\n            return -1;\n        }\n\n        BusySpinIdleStrategy offerIdleStrategy;\n        BusySpinIdleStrategy pollIdleStrategy;\n\n        RateReporter rateReporter(std::chrono::seconds(1), printRate);\n        FragmentAssembler fragmentAssembler(rateReporterHandler(rateReporter));\n        fragment_handler_t handler = fragmentAssembler.handler();\n\n        Publication *publicationPtr = publication.get();\n\n        aeron::util::OnScopeExit tidy(\n            [&]()\n            {\n                running = false;\n                rateReporter.halt();\n\n                if (nullptr != pollThread && pollThread->joinable())\n                {\n                    pollThread->join();\n                    pollThread = nullptr;\n                }\n\n                if (nullptr != rateReporterThread && rateReporterThread->joinable())\n                {\n                    rateReporterThread->join();\n                    rateReporterThread = nullptr;\n                }\n            });\n\n        if (settings.progress)\n        {\n            rateReporterThread = std::make_shared<std::thread>([&rateReporter](){ rateReporter.run(); });\n        }\n\n        pollThread = std::make_shared<std::thread>(\n            [&subscription, &pollIdleStrategy, &settings, &handler]()\n            {\n                Subscription *subscriptionPtr = subscription.get();\n\n                while (isRunning())\n                {\n                    pollIdleStrategy.idle(subscriptionPtr->poll(handler, settings.fragmentCountLimit));\n                }\n            });\n\n        do\n        {\n            BufferClaim bufferClaim;\n            long backPressureCount = 0;\n\n            printingActive = true;\n\n            if (nullptr == rateReporterThread)\n            {\n                rateReporter.reset();\n            }\n\n            for (std::int64_t i = 0; i < settings.numberOfMessages && isRunning(); i++)\n            {\n                offerIdleStrategy.reset();\n                while (publicationPtr->tryClaim(settings.messageLength, bufferClaim) < 0L)\n                {\n                    backPressureCount++;\n                    offerIdleStrategy.idle();\n                }\n\n                bufferClaim.buffer().putInt64(bufferClaim.offset(), i);\n                bufferClaim.commit();\n            }\n\n            if (nullptr == rateReporterThread)\n            {\n                rateReporter.report();\n            }\n\n            std::cout << \"Done streaming. Back pressure ratio \";\n            std::cout << ((double)backPressureCount / (double)settings.numberOfMessages) << std::endl;\n\n            if (isRunning() && settings.lingerTimeoutMs > 0)\n            {\n                std::cout << \"Lingering for \" << settings.lingerTimeoutMs << \" milliseconds.\" << std::endl;\n                std::this_thread::sleep_for(std::chrono::milliseconds(settings.lingerTimeoutMs));\n            }\n\n            printingActive = false;\n        }\n        while (isRunning() && continuationBarrier(\"Execute again?\"));\n    }\n    catch (const CommandOptionException &e)\n    {\n        std::cerr << \"ERROR: \" << e.what() << std::endl << std::endl;\n        cp.displayOptionsHelp(std::cerr);\n        return -1;\n    }\n    catch (const SourcedException &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << e.where() << std::endl;\n        return -1;\n    }\n    catch (const std::exception &e)\n    {\n        std::cerr << \"FAILED: \" << e.what() << \" : \" << std::endl;\n        return -1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/cluster/ConsensusModuleSnapshotPendingServiceMessagesPatch.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.*;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.codecs.ConsensusModuleEncoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.PendingMessageTrackerEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderEncoder;\nimport io.aeron.cluster.service.ClusterNodeControlProperties;\nimport io.aeron.samples.archive.RecordingSignalCapture;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.Publication.*;\nimport static java.lang.Math.max;\nimport static java.lang.Math.min;\n\n/**\n * A tool to patch the latest consensus module snapshot if it has divergence in the pending service messages state.\n * This tool patches the state on a single node. It is recommended to run the tool on the leader node and distribute\n * the patched snapshot to all other nodes in a cluster.\n * <p>\n * The tool will attempt to connect to the running MediaDriver and the Archive of the cluster node.\n * <p>\n * <em>Note: Run the tool only if the cluster is suspended or completely stopped!</em>\n */\npublic class ConsensusModuleSnapshotPendingServiceMessagesPatch\n{\n    static final int SNAPSHOT_REPLAY_STREAM_ID = 103;\n    static final int SNAPSHOT_RECORDING_STREAM_ID = 107;\n    static final String PATCH_CHANNEL = \"aeron:ipc?alias=consensus-module-snapshot-patch|term-length=64m\";\n    private final String archiveLocalRequestChannel;\n    private final int archiveLocalRequestStreamId;\n\n    /**\n     * Create new patch instance this default Archive values initialized to\n     * {@link AeronArchive.Configuration#localControlChannel()} and\n     * {@link AeronArchive.Configuration#localControlStreamId()}.\n     */\n    public ConsensusModuleSnapshotPendingServiceMessagesPatch()\n    {\n        this(AeronArchive.Configuration.localControlChannel(), AeronArchive.Configuration.localControlStreamId());\n    }\n\n    ConsensusModuleSnapshotPendingServiceMessagesPatch(\n        final String archiveLocalRequestChannel, final int archiveRequestStreamId)\n    {\n        this.archiveLocalRequestChannel = archiveLocalRequestChannel;\n        this.archiveLocalRequestStreamId = archiveRequestStreamId;\n    }\n\n    /**\n     * Execute the code to patch the latest snapshot.\n     *\n     * @param clusterDir of the cluster node to run the patch tool on.\n     * @return {@code true} if the patch was applied or {@code false} if consensus module state was already correct.\n     * @throws NullPointerException     if {@code null == clusterDir}.\n     * @throws IllegalArgumentException if {@code clusterDir} does not exist or is not a directory.\n     * @throws ClusterException         if {@code clusterDir} does not contain a recording log or if the log does not\n     *                                  contain a valid snapshot.\n     */\n    public boolean execute(final File clusterDir)\n    {\n        if (!clusterDir.exists() || !clusterDir.isDirectory())\n        {\n            throw new IllegalArgumentException(\"invalid cluster directory: \" + clusterDir.getAbsolutePath());\n        }\n\n        final RecordingLog.Entry entry = ClusterTool.findLatestValidSnapshot(clusterDir);\n        if (null == entry)\n        {\n            throw new ClusterException(\"no valid snapshot found\");\n        }\n\n        final long recordingId = entry.recordingId;\n        final ClusterNodeControlProperties properties = ClusterTool.loadControlProperties(clusterDir);\n        final RecordingSignalCapture recordingSignalCapture = new RecordingSignalCapture();\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(properties.aeronDirectoryName));\n            AeronArchive archive = AeronArchive.connect(new AeronArchive.Context()\n                .controlRequestChannel(archiveLocalRequestChannel)\n                .controlRequestStreamId(archiveLocalRequestStreamId)\n                .controlResponseChannel(IPC_CHANNEL)\n                .recordingSignalConsumer(recordingSignalCapture)\n                .aeron(aeron)))\n        {\n            final SnapshotReader snapshotReader = new SnapshotReader();\n            replayLocalSnapshotRecording(aeron, archive, recordingId, snapshotReader);\n\n            final TargetState[] targetStates =\n                TargetState.compute(snapshotReader.serviceCount, snapshotReader.pendingMessageTrackers);\n\n            if (snapshotIsNotValid(snapshotReader, targetStates))\n            {\n                final long tempRecordingId = createNewSnapshotRecording(aeron, archive, recordingId, targetStates);\n\n                final long stopPosition = awaitRecordingStopPosition(archive, recordingId);\n                final long newStopPosition = awaitRecordingStopPosition(archive, tempRecordingId);\n                if (stopPosition != newStopPosition)\n                {\n                    throw new ClusterException(\"new snapshot recording incomplete: expectedStopPosition=\" +\n                        stopPosition + \", actualStopPosition=\" + newStopPosition);\n                }\n\n                recordingSignalCapture.reset();\n                archive.truncateRecording(recordingId, 0);\n                recordingSignalCapture.awaitSignalForRecordingId(archive, recordingId, RecordingSignal.DELETE);\n\n                final long replicationId = archive.replicate(\n                    tempRecordingId, recordingId, archive.context().controlRequestStreamId(), IPC_CHANNEL, null);\n\n                recordingSignalCapture.reset();\n                recordingSignalCapture.awaitSignalForCorrelationId(archive, replicationId, RecordingSignal.SYNC);\n\n                final long replicatedStopPosition = recordingSignalCapture.position();\n                if (stopPosition != replicatedStopPosition)\n                {\n                    throw new ClusterException(\"incomplete replication of the new recording: expectedStopPosition=\" +\n                        stopPosition + \", replicatedStopPosition=\" + replicatedStopPosition);\n                }\n\n                recordingSignalCapture.reset();\n                archive.purgeRecording(tempRecordingId);\n                recordingSignalCapture.awaitSignalForRecordingId(archive, tempRecordingId, RecordingSignal.DELETE);\n\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    static void replayLocalSnapshotRecording(\n        final Aeron aeron,\n        final AeronArchive archive,\n        final long recordingId,\n        final ConsensusModuleSnapshotListener listener)\n    {\n        final String channel = IPC_CHANNEL;\n        final int streamId = SNAPSHOT_REPLAY_STREAM_ID;\n        final int sessionId = (int)archive.startReplay(recordingId, 0, AeronArchive.NULL_LENGTH, channel, streamId);\n        try\n        {\n            final String replayChannel = ChannelUri.addSessionId(channel, sessionId);\n            try (Subscription subscription = aeron.addSubscription(replayChannel, streamId))\n            {\n                Image image;\n                while (null == (image = subscription.imageBySessionId(sessionId)))\n                {\n                    archive.checkForErrorResponse();\n                    Thread.yield();\n                }\n\n                final ConsensusModuleSnapshotAdapter adapter = new ConsensusModuleSnapshotAdapter(image, listener);\n                while (true)\n                {\n                    final int fragments = adapter.poll();\n                    if (adapter.isDone())\n                    {\n                        break;\n                    }\n\n                    if (0 == fragments)\n                    {\n                        if (image.isClosed())\n                        {\n                            throw new ClusterException(\"snapshot ended unexpectedly: \" + image);\n                        }\n\n                        archive.checkForErrorResponse();\n                        Thread.yield();\n                    }\n                }\n            }\n        }\n        finally\n        {\n            archive.stopAllReplays(recordingId);\n        }\n    }\n\n    private static boolean snapshotIsNotValid(final SnapshotReader snapshotReader, final TargetState[] targetStates)\n    {\n        for (int i = 0; i < targetStates.length; i++)\n        {\n            final TargetState targetState = targetStates[i];\n            final PendingMessageTrackerState actualState = snapshotReader.pendingMessageTrackers[i];\n            if (targetState.nextServiceSessionId != actualState.nextServiceSessionId ||\n                targetState.logServiceSessionId != actualState.logServiceSessionId ||\n                0 != actualState.pendingServiceMessageCount &&\n                (targetState.logServiceSessionId + 1 != actualState.minClusterSessionId ||\n                targetState.nextServiceSessionId - 1 != actualState.maxClusterSessionId))\n            {\n                return true;\n            }\n        }\n\n        final TargetState defaultTracker = targetStates[0];\n        return defaultTracker.nextServiceSessionId != snapshotReader.nextServiceSessionId ||\n            defaultTracker.logServiceSessionId != snapshotReader.logServiceSessionId;\n    }\n\n    private static long createNewSnapshotRecording(\n        final Aeron aeron, final AeronArchive archive, final long oldRecordingId, final TargetState[] targetStates)\n    {\n        try (ExclusivePublication publication = archive.addRecordedExclusivePublication(\n            PATCH_CHANNEL, SNAPSHOT_RECORDING_STREAM_ID))\n        {\n            try\n            {\n                final int publicationSessionId = publication.sessionId();\n                final CountersReader countersReader = aeron.countersReader();\n                final int counterId = awaitRecordingCounter(publicationSessionId, archive.archiveId(), countersReader);\n                final long newRecordingId = RecordingPos.getRecordingId(countersReader, counterId);\n\n                replayLocalSnapshotRecording(\n                    aeron, archive, oldRecordingId, new SnapshotWriter(publication, targetStates));\n\n                awaitRecordingComplete(countersReader, counterId, publication.position(), newRecordingId);\n\n                return newRecordingId;\n            }\n            finally\n            {\n                archive.stopRecording(publication);\n            }\n        }\n    }\n\n    private static int awaitRecordingCounter(\n        final int publicationSessionId, final long archiveId, final CountersReader countersReader)\n    {\n        int counterId;\n        while (NULL_VALUE ==\n            (counterId = RecordingPos.findCounterIdBySession(countersReader, publicationSessionId, archiveId)))\n        {\n            Thread.yield();\n        }\n\n        return counterId;\n    }\n\n    private static void awaitRecordingComplete(\n        final CountersReader counters, final int counterId, final long position, final long recordingId)\n    {\n        while (counters.getCounterValue(counterId) < position)\n        {\n            Thread.yield();\n            if (!RecordingPos.isActive(counters, counterId, recordingId))\n            {\n                throw new ClusterException(\"recording has stopped unexpectedly: \" + recordingId);\n            }\n        }\n    }\n\n    private static long awaitRecordingStopPosition(final AeronArchive archive, final long recordingId)\n    {\n        long stopPosition;\n        while (NULL_VALUE == (stopPosition = archive.getStopPosition(recordingId)))\n        {\n            Thread.yield();\n        }\n\n        return stopPosition;\n    }\n\n    /**\n     * Entry point to launch the tool. Requires a single parameter of a cluster directory.\n     *\n     * @param args for the tool.\n     */\n    public static void main(final String[] args)\n    {\n        if (1 != args.length)\n        {\n            System.out.println(\"Usage: <cluster-dir>\");\n            System.exit(-1);\n        }\n\n        new ConsensusModuleSnapshotPendingServiceMessagesPatch().execute(new File(args[0]));\n    }\n\n    private static final class TargetState\n    {\n        final long nextServiceSessionId;\n        final long logServiceSessionId;\n        long clusterSessionId;\n\n        private TargetState(final long nextServiceSessionId, final long logServiceSessionId)\n        {\n            this.nextServiceSessionId = nextServiceSessionId;\n            this.logServiceSessionId = logServiceSessionId;\n            clusterSessionId = logServiceSessionId + 1;\n        }\n\n        static TargetState[] compute(final int serviceCount, final PendingMessageTrackerState[] states)\n        {\n            final TargetState[] targetStates = new TargetState[serviceCount];\n            for (int i = 0; i < serviceCount; i++)\n            {\n                final PendingMessageTrackerState state = states[i];\n                final long targetNextServiceSessionId = max(\n                    max(state.nextServiceSessionId, state.maxClusterSessionId + 1),\n                    state.logServiceSessionId + 1 + state.pendingServiceMessageCount);\n                final long targetLogServiceSessionId =\n                    targetNextServiceSessionId - 1 - state.pendingServiceMessageCount;\n                targetStates[i] = new TargetState(targetNextServiceSessionId, targetLogServiceSessionId);\n            }\n            return targetStates;\n        }\n    }\n\n    private static final class PendingMessageTrackerState\n    {\n        long nextServiceSessionId = Long.MIN_VALUE;\n        long logServiceSessionId = Long.MIN_VALUE;\n        long minClusterSessionId = Long.MAX_VALUE;\n        long maxClusterSessionId = Long.MIN_VALUE;\n        int pendingServiceMessageCount;\n    }\n\n    private static final class SnapshotReader implements ConsensusModuleSnapshotListener\n    {\n        long nextServiceSessionId = Long.MIN_VALUE;\n        long logServiceSessionId = Long.MIN_VALUE;\n        final PendingMessageTrackerState[] pendingMessageTrackers = new PendingMessageTrackerState[127];\n        int serviceCount;\n\n        public void onLoadBeginSnapshot(\n            final int appVersion,\n            final TimeUnit timeUnit,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n        }\n\n        public void onLoadConsensusModuleState(\n            final long nextSessionId,\n            final long nextServiceSessionId,\n            final long logServiceSessionId,\n            final int pendingMessageCapacity,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            this.nextServiceSessionId = nextServiceSessionId;\n            this.logServiceSessionId = logServiceSessionId;\n        }\n\n        public void onLoadPendingMessage(\n            final long clusterSessionId, final DirectBuffer buffer, final int offset, final int length)\n        {\n            final int serviceId = PendingServiceMessageTracker.serviceIdFromLogMessage(clusterSessionId);\n            final PendingMessageTrackerState trackerState = tracker(serviceId);\n            trackerState.pendingServiceMessageCount++;\n            trackerState.minClusterSessionId = min(trackerState.minClusterSessionId, clusterSessionId);\n            trackerState.maxClusterSessionId = max(trackerState.maxClusterSessionId, clusterSessionId);\n        }\n\n        public void onLoadClusterSession(\n            final long clusterSessionId,\n            final long correlationId,\n            final long openedLogPosition,\n            final long timeOfLastActivity,\n            final CloseReason closeReason,\n            final int responseStreamId,\n            final String responseChannel,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n        }\n\n        public void onLoadTimer(\n            final long correlationId,\n            final long deadline,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n        }\n\n        public void onLoadPendingMessageTracker(\n            final long nextServiceSessionId,\n            final long logServiceSessionId,\n            final int pendingMessageCapacity,\n            final int serviceId,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            final PendingMessageTrackerState trackerState = tracker(serviceId);\n            trackerState.nextServiceSessionId = nextServiceSessionId;\n            trackerState.logServiceSessionId = logServiceSessionId;\n        }\n\n        public void onLoadEndSnapshot(final DirectBuffer buffer, final int offset, final int length)\n        {\n        }\n\n        private PendingMessageTrackerState tracker(final int serviceId)\n        {\n            PendingMessageTrackerState trackerState = pendingMessageTrackers[serviceId];\n            if (null == trackerState)\n            {\n                trackerState = new PendingMessageTrackerState();\n                pendingMessageTrackers[serviceId] = trackerState;\n                serviceCount++;\n            }\n            return trackerState;\n        }\n    }\n\n    private static final class SnapshotWriter implements ConsensusModuleSnapshotListener\n    {\n        private final ExpandableArrayBuffer tempBuffer = new ExpandableArrayBuffer(1024);\n        private final ConsensusModuleEncoder consensusModuleEncoder = new ConsensusModuleEncoder();\n        private final SessionMessageHeaderEncoder sessionMessageHeaderEncoder = new SessionMessageHeaderEncoder();\n        private final PendingMessageTrackerEncoder pendingMessageTrackerEncoder = new PendingMessageTrackerEncoder();\n        private final ExclusivePublication snapshotPublication;\n        private final TargetState[] targetStates;\n\n        SnapshotWriter(\n            final ExclusivePublication snapshotPublication,\n            final TargetState[] targetStates)\n        {\n            this.snapshotPublication = snapshotPublication;\n            this.targetStates = targetStates;\n        }\n\n        public void onLoadBeginSnapshot(\n            final int appVersion,\n            final TimeUnit timeUnit,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            writeToSnapshot(buffer, offset, length);\n        }\n\n        public void onLoadConsensusModuleState(\n            final long nextSessionId,\n            final long nextServiceSessionId,\n            final long logServiceSessionId,\n            final int pendingMessageCapacity,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            final TargetState defaultTrackerState = targetStates[0];\n\n            tempBuffer.putBytes(0, buffer, offset, length);\n            consensusModuleEncoder\n                .wrap(tempBuffer, MessageHeaderEncoder.ENCODED_LENGTH)\n                .logServiceSessionId(defaultTrackerState.logServiceSessionId)\n                .nextServiceSessionId(defaultTrackerState.nextServiceSessionId);\n\n            writeToSnapshot(tempBuffer, 0, length);\n        }\n\n        public void onLoadPendingMessage(\n            final long clusterSessionId,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            final int serviceId = PendingServiceMessageTracker.serviceIdFromLogMessage(clusterSessionId);\n            final TargetState targetState = targetStates[serviceId];\n\n            tempBuffer.putBytes(0, buffer, offset, length);\n            sessionMessageHeaderEncoder\n                .wrap(tempBuffer, MessageHeaderEncoder.ENCODED_LENGTH)\n                .clusterSessionId(targetState.clusterSessionId++);\n\n            writeToSnapshot(tempBuffer, 0, length);\n        }\n\n        public void onLoadClusterSession(\n            final long clusterSessionId,\n            final long correlationId,\n            final long openedLogPosition,\n            final long timeOfLastActivity,\n            final CloseReason closeReason,\n            final int responseStreamId,\n            final String responseChannel,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            writeToSnapshot(buffer, offset, length);\n        }\n\n        public void onLoadTimer(\n            final long correlationId,\n            final long deadline,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            writeToSnapshot(buffer, offset, length);\n        }\n\n        public void onLoadPendingMessageTracker(\n            final long nextServiceSessionId,\n            final long logServiceSessionId,\n            final int pendingMessageCapacity,\n            final int serviceId,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            final TargetState targetState = targetStates[serviceId];\n\n            tempBuffer.putBytes(0, buffer, offset, length);\n            pendingMessageTrackerEncoder\n                .wrap(tempBuffer, MessageHeaderEncoder.ENCODED_LENGTH)\n                .logServiceSessionId(targetState.logServiceSessionId)\n                .nextServiceSessionId(targetState.nextServiceSessionId);\n\n            writeToSnapshot(tempBuffer, 0, length);\n        }\n\n        public void onLoadEndSnapshot(final DirectBuffer buffer, final int offset, final int length)\n        {\n            writeToSnapshot(buffer, offset, length);\n        }\n\n        private void writeToSnapshot(final DirectBuffer buffer, final int offset, final int length)\n        {\n            long position;\n            while ((position = snapshotPublication.offer(buffer, offset, length)) < 0)\n            {\n                if (position == CLOSED ||\n                    position == NOT_CONNECTED ||\n                    position == MAX_POSITION_EXCEEDED)\n                {\n                    throw new ClusterException(\"cannot offer into a snapshot: \" + Publication.errorString(position));\n                }\n\n                Thread.yield();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/response/ResponseClient.java",
    "content": "/*\n * Copyright 2014-2023 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.response;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Subscription;\nimport io.aeron.logbuffer.FragmentHandler;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.Agent;\n\nimport java.util.function.Function;\n\n/**\n * Sample client to be used with the response server.\n */\npublic class ResponseClient implements AutoCloseable, Agent\n{\n    private final Aeron aeron;\n    private final FragmentHandler handler;\n    private final String requestEndpoint;\n    private final int requestStreamId;\n    private final String responseControl;\n    private final int responseStreamId;\n    private final ChannelUriStringBuilder requestUriBuilder;\n    private final ChannelUriStringBuilder responseUriBuilder;\n    private ExclusivePublication publication;\n    private Subscription subscription;\n\n    /**\n     * Construct a response client with the associated request endpoint. The response endpoint is not required as\n     * the server will manage sending this to the client\n     *\n     * @param aeron            client to use to connect to the server.\n     * @param handler          callback to handle response messages.\n     * @param requestEndpoint  request publication's endpoint.\n     * @param requestStreamId  request publication streamId\n     * @param responseControl  control address for the response subscription.\n     * @param responseStreamId response response streamId\n     * @param requestChannel   channel fragment to allow for configuration of parameters on the request publication.\n     *                         May be null. The 'endpoint' parameter is not required and will be replaced by the\n     *                         <code>requestEndpoint</code> if specified.\n     * @param responseChannel  channel fragment to allow for configuration parameters on the response subscription.\n     *                         May be null. The 'control' parameter is not required and will be removed if specified.\n     */\n    public ResponseClient(\n        final Aeron aeron,\n        final FragmentHandler handler,\n        final String requestEndpoint,\n        final int requestStreamId,\n        final String responseControl,\n        final int responseStreamId,\n        final String requestChannel,\n        final String responseChannel)\n    {\n        this.aeron = aeron;\n        this.handler = handler;\n        this.requestEndpoint = requestEndpoint;\n        this.requestStreamId = requestStreamId;\n        this.responseControl = responseControl;\n        this.responseStreamId = responseStreamId;\n\n        requestUriBuilder = null != requestChannel ?\n            new ChannelUriStringBuilder(requestChannel) : new ChannelUriStringBuilder();\n        requestUriBuilder\n            .media(\"udp\")\n            .endpoint(requestEndpoint);\n        responseUriBuilder = null != responseChannel ?\n            new ChannelUriStringBuilder(responseChannel) : new ChannelUriStringBuilder();\n        responseUriBuilder\n            .media(\"udp\")\n            .controlMode(\"response\")\n            .controlEndpoint(responseControl);\n    }\n\n    /**\n     * Overload for {@link ResponseServer#ResponseServer(Aeron, Function, String, int, String, int, String, String)}\n     * that defaults the channels to null.\n     *\n     * @param aeron            client to use to connect to the server.\n     * @param handler          callback to handle response messages.\n     * @param requestEndpoint  request publication's endpoint.\n     * @param requestStreamId  request publication streamId\n     * @param responseControl  control address for the response subscription.\n     * @param responseStreamId response response streamId\n     */\n    public ResponseClient(\n        final Aeron aeron,\n        final FragmentHandler handler,\n        final String requestEndpoint,\n        final int requestStreamId,\n        final String responseControl,\n        final int responseStreamId)\n    {\n        this(aeron, handler, requestEndpoint, requestStreamId, responseControl, responseStreamId, null, null);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n\n        if (null == subscription)\n        {\n            subscription = aeron.addSubscription(responseUriBuilder.build(), responseStreamId);\n        }\n\n        if (null == publication && null != subscription)\n        {\n            publication = aeron.addExclusivePublication(\n                requestUriBuilder.responseCorrelationId(subscription.registrationId()).build(),\n                requestStreamId);\n\n            workCount++;\n        }\n\n        if (null != subscription)\n        {\n            workCount += subscription.poll(handler, 10);\n        }\n\n        return workCount;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"ResponseClient\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        publication.revokeOnClose();\n        CloseHelper.quietCloseAll(publication, subscription);\n    }\n\n    /**\n     * Check that the client is connected.\n     *\n     * @return true if the client is connected.\n     */\n    public boolean isConnected()\n    {\n        return null != subscription && subscription.isConnected() && null != publication && publication.isConnected();\n    }\n\n    /**\n     * Offer a message on the request channel.\n     *\n     * @param message to be sent\n     * @return result code from the publication.\n     */\n    public long offer(final DirectBuffer message)\n    {\n        return publication.offer(message);\n    }\n\n    /**\n     * Get the response control for the server.\n     *\n     * @return response control for the server.\n     */\n    public String responseControl()\n    {\n        return responseControl;\n    }\n\n    /**\n     * Get the request endpoint for the server.\n     *\n     * @return request endpoint for the server.\n     */\n    public String requestEndpoint()\n    {\n        return requestEndpoint;\n    }\n\n    /**\n     * Get the response subscription for the client.\n     *\n     * @return response subscription for the client.\n     */\n    public Subscription subscription()\n    {\n        return subscription;\n    }\n\n    /**\n     * Get the request publication for the client.\n     *\n     * @return request publication for the client.\n     */\n    public ExclusivePublication publication()\n    {\n        return publication;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"ResponseClient{\" +\n            \"publication.isConnected=\" + publication.isConnected() +\n            \", subscription.isConnected=\" + subscription.isConnected() +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/response/ResponseServer.java",
    "content": "/*\n * Copyright 2014-2023 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.response;\n\nimport io.aeron.*;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.OneToOneConcurrentArrayQueue;\n\nimport java.util.Objects;\nimport java.util.function.Function;\n\nimport static io.aeron.CommonContext.PROTOTYPE_CORRELATION_ID;\n\n/**\n * A basic sample response server that allows the users to specify a simple function to represent the handling of a\n * request and then return a response. This approach will be effective when request processing is very short. For\n * certain types of response servers, e.g. returning a large volume of data from a database, this pattern will be\n * ineffective. For those types of use cases, something more complex, likely involving thread pooling would be required.\n */\npublic class ResponseServer implements AutoCloseable, Agent\n{\n    /**\n     * Interface to manage callback from the response server onto a session.\n     */\n    public interface ResponseHandler\n    {\n        /**\n         * Called when a message is received via the request subscription.\n         *\n         * @param buffer              containing the data.\n         * @param offset              at which the data begins.\n         * @param length              of the data in bytes.\n         * @param header              representing the metadata for the data.\n         * @param responsePublication to send responses back to the client.\n         * @return <code>true</code> if the message was processed otherwise.\n         */\n        boolean onMessage(DirectBuffer buffer, int offset, int length, Header header, Publication responsePublication);\n    }\n\n    private final Aeron aeron;\n    private final Long2ObjectHashMap<ResponseSession> clientToPublicationMap = new Long2ObjectHashMap<>();\n    private final OneToOneConcurrentArrayQueue<Image> availableImages =\n        new OneToOneConcurrentArrayQueue<>(1024);\n    private final OneToOneConcurrentArrayQueue<Image> unavailableImages =\n        new OneToOneConcurrentArrayQueue<>(1024);\n    private final Function<Image, ResponseHandler> handlerFactory;\n    private final int requestStreamId;\n    private final int responseStreamId;\n    private final ChannelUriStringBuilder requestUriBuilder;\n    private final ChannelUriStringBuilder responseUriBuilder;\n    private final ControlledFragmentAssembler requestAssembler = new ControlledFragmentAssembler(\n        this::onControlledRequestMessage);\n\n    private boolean usePrototype = true;\n\n    private Subscription serverSubscription;\n    private ExclusivePublication prototypeResponsePublication;\n\n    /**\n     * Constructs the server.\n     *\n     * @param aeron            connected Aeron client.\n     * @param handlerFactory   factor to produce handler instances for each incoming session.\n     * @param requestEndpoint  channel to listen for requests and new connections.\n     * @param requestStreamId  streamId to listen for requests and new connections.\n     * @param responseControl  channel to send responses back on.\n     * @param responseStreamId streamId to send responses back on.\n     * @param requestChannel   fragment to aid in configuration of the subscription (can be null)\n     * @param responseChannel  fragment to aid in configuration of the response publication (can be null)\n     */\n    public ResponseServer(\n        final Aeron aeron,\n        final Function<Image, ResponseHandler> handlerFactory,\n        final String requestEndpoint,\n        final int requestStreamId,\n        final String responseControl,\n        final int responseStreamId,\n        final String requestChannel,\n        final String responseChannel)\n    {\n        this.aeron = aeron;\n        this.handlerFactory = handlerFactory;\n        this.requestStreamId = requestStreamId;\n        this.responseStreamId = responseStreamId;\n\n        Objects.requireNonNull(requestEndpoint, \"subscriptionEndpoint must not be null\");\n        Objects.requireNonNull(responseControl, \"responseEndpoint must not be null\");\n\n        requestUriBuilder = null == requestChannel ?\n            new ChannelUriStringBuilder() : new ChannelUriStringBuilder(requestChannel);\n        requestUriBuilder\n            .media(\"udp\")\n            .endpoint(requestEndpoint)\n            .responseEndpoint(responseControl);\n        responseUriBuilder = null == responseChannel ?\n            new ChannelUriStringBuilder() : new ChannelUriStringBuilder(responseChannel);\n        responseUriBuilder\n            .media(\"udp\")\n            .controlMode(\"response\")\n            .controlEndpoint(responseControl);\n    }\n\n    /**\n     * Indicate whether or not to create a prototype response publication.\n     *\n     * @param usePrototype true or false.\n     */\n    public void usePrototype(final boolean usePrototype)\n    {\n        this.usePrototype = usePrototype;\n    }\n\n    /**\n     * Poll the server process messages and state.\n     *\n     * @return amount of work done.\n     */\n    public int doWork()\n    {\n        int workCount = 0;\n\n        if (null == serverSubscription)\n        {\n            serverSubscription = aeron.addSubscription(\n                requestUriBuilder.build(),\n                requestStreamId,\n                this::enqueueAvailableImage,\n                this::enqueueUnavailableImage);\n\n            if (usePrototype)\n            {\n                prototypeResponsePublication = aeron.addExclusivePublication(\n                    responseUriBuilder.responseCorrelationId(PROTOTYPE_CORRELATION_ID).build(),\n                    responseStreamId);\n            }\n\n            workCount++;\n        }\n\n        Image image;\n        while (null != (image = availableImages.poll()))\n        {\n            workCount++;\n            getOrCreateSession(image);\n        }\n\n        while (null != (image = unavailableImages.poll()))\n        {\n            workCount++;\n            removeSession(image);\n        }\n\n        workCount += serverSubscription.controlledPoll(requestAssembler, 1);\n\n        return workCount;\n    }\n\n    /**\n     * The number of connected clients.\n     *\n     * @return the number of connected clients.\n     */\n    public int sessionCount()\n    {\n        return clientToPublicationMap.size();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.quietCloseAll(serverSubscription, prototypeResponsePublication);\n        clientToPublicationMap.values().forEach(CloseHelper::quietClose);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"ResponseServer\";\n    }\n\n    private void enqueueAvailableImage(final Image image)\n    {\n        if (!availableImages.offer(image))\n        {\n            throw new RuntimeException(\"Unable to enqueue new image\");\n        }\n    }\n\n    private void enqueueUnavailableImage(final Image image)\n    {\n        if (!unavailableImages.offer(image))\n        {\n            throw new RuntimeException(\"Unable to enqueue removed image\");\n        }\n    }\n\n    private ControlledFragmentHandler.Action onControlledRequestMessage(\n        final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final ResponseSession session = getOrCreateSession((Image)header.context());\n        final boolean processed = session.process(buffer, offset, length, header);\n\n        return processed ? ControlledFragmentHandler.Action.CONTINUE : ControlledFragmentHandler.Action.ABORT;\n    }\n\n\n    private ResponseSession getOrCreateSession(final Image image)\n    {\n        ResponseSession session = clientToPublicationMap.get(image.correlationId());\n\n        if (null == session)\n        {\n            final Publication responsePublication = aeron.addPublication(\n                responseUriBuilder.responseCorrelationId(image.correlationId()).build(),\n                responseStreamId);\n\n            final ResponseHandler handler = handlerFactory.apply(image);\n            session = new ResponseSession(responsePublication, handler);\n\n            clientToPublicationMap.put(image.correlationId(), session);\n        }\n\n        return session;\n    }\n\n    private void removeSession(final Image image)\n    {\n        requestAssembler.freeSessionBuffer(image.sessionId());\n        final ResponseSession session = clientToPublicationMap.remove(image.correlationId());\n        CloseHelper.quietClose(session);\n    }\n\n    private static final class ResponseSession implements AutoCloseable\n    {\n        private final Publication publication;\n        private final ResponseHandler handler;\n\n        ResponseSession(final Publication publication, final ResponseHandler handler)\n        {\n            this.publication = publication;\n            this.handler = handler;\n        }\n\n        public boolean process(final DirectBuffer buffer, final int offset, final int length, final Header header)\n        {\n            return handler.onMessage(buffer, offset, length, header, publication);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void close()\n        {\n            CloseHelper.close(publication);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/AeronStat.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.CncFileDescriptor;\nimport io.aeron.status.ChannelEndpointStatus;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.IOException;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Supplier;\nimport java.util.regex.Pattern;\n\nimport static io.aeron.driver.status.PerImageIndicator.PER_IMAGE_TYPE_ID;\nimport static io.aeron.driver.status.PublisherLimit.PUBLISHER_LIMIT_TYPE_ID;\nimport static io.aeron.driver.status.PublisherPos.PUBLISHER_POS_TYPE_ID;\nimport static io.aeron.driver.status.ReceiveChannelStatus.RECEIVE_CHANNEL_STATUS_TYPE_ID;\nimport static io.aeron.driver.status.ReceiverPos.RECEIVER_POS_TYPE_ID;\nimport static io.aeron.driver.status.SendChannelStatus.SEND_CHANNEL_STATUS_TYPE_ID;\nimport static io.aeron.driver.status.SenderLimit.SENDER_LIMIT_TYPE_ID;\nimport static io.aeron.driver.status.StreamCounter.CHANNEL_OFFSET;\nimport static io.aeron.driver.status.StreamCounter.REGISTRATION_ID_OFFSET;\nimport static io.aeron.driver.status.StreamCounter.SESSION_ID_OFFSET;\nimport static io.aeron.driver.status.StreamCounter.STREAM_ID_OFFSET;\nimport static io.aeron.driver.status.SystemCounterDescriptor.SYSTEM_COUNTER_TYPE_ID;\n\n/**\n * Tool for printing out Aeron counters. A command-and-control (CnC) file is maintained by media driver\n * in shared memory. This application reads the CnC file and prints the counters. Layout of the CnC file is\n * described in {@link CncFileDescriptor}.\n * <p>\n * This tool accepts filters on the command line, e.g. for connections only see example below:\n * <p>\n * <code>\n * java -cp aeron-samples/build/libs/samples.jar io.aeron.samples.AeronStat type=[1-9] identity=12345\n * </code>\n */\npublic class AeronStat\n{\n    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(\"HH:mm:ss\");\n    private static final String ANSI_CLS = \"\\u001b[2J\";\n    private static final String ANSI_HOME = \"\\u001b[H\";\n\n    /**\n     * The delay in seconds between each update.\n     */\n    private static final String DELAY = \"delay\";\n\n    /**\n     * Whether to watch for updates or run once.\n     */\n    private static final String WATCH = \"watch\";\n\n    /**\n     * Types of the counters.\n     * <ul>\n     * <li>0: System Counters</li>\n     * <li>1 - 5, 9, 10, 11: Stream Positions and Indicators</li>\n     * <li>6 - 7: Channel Endpoint Status</li>\n     * </ul>\n     */\n    private static final String COUNTER_TYPE_ID = \"type\";\n\n    /**\n     * The identity of each counter that can either be the system counter id or registration id for positions.\n     */\n    private static final String COUNTER_IDENTITY = \"identity\";\n\n    /**\n     * Session id filter to be used for position counters.\n     */\n    private static final String COUNTER_SESSION_ID = \"session\";\n\n    /**\n     * Stream id filter to be used for position counters.\n     */\n    private static final String COUNTER_STREAM_ID = \"stream\";\n\n    /**\n     * Channel filter to be used for position counters.\n     */\n    private static final String COUNTER_CHANNEL = \"channel\";\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws IOException          if an error occurs writing to the console.\n     * @throws InterruptedException if the thread sleep delay is interrupted.\n     */\n    public static void main(final String[] args) throws IOException, InterruptedException\n    {\n        long delayMs = 1000L;\n        boolean watch = true;\n        Pattern typeFilter = null;\n        Pattern identityFilter = null;\n        Pattern sessionFilter = null;\n        Pattern streamFilter = null;\n        Pattern channelFilter = null;\n\n        if (0 != args.length)\n        {\n            checkForHelp(args);\n\n            for (final String arg : args)\n            {\n                final int equalsIndex = arg.indexOf('=');\n                if (-1 == equalsIndex)\n                {\n                    System.out.println(\"Arguments must be in name=pattern format: Invalid '\" + arg + \"'\");\n                    return;\n                }\n\n                final String argName = arg.substring(0, equalsIndex);\n                final String argValue = arg.substring(equalsIndex + 1);\n\n                switch (argName)\n                {\n                    case WATCH:\n                        watch = Boolean.parseBoolean(argValue);\n                        break;\n\n                    case DELAY:\n                        delayMs = Long.parseLong(argValue) * 1000L;\n                        break;\n\n                    case COUNTER_TYPE_ID:\n                        typeFilter = Pattern.compile(argValue);\n                        break;\n\n                    case COUNTER_IDENTITY:\n                        identityFilter = Pattern.compile(argValue);\n                        break;\n\n                    case COUNTER_SESSION_ID:\n                        sessionFilter = Pattern.compile(argValue);\n                        break;\n\n                    case COUNTER_STREAM_ID:\n                        streamFilter = Pattern.compile(argValue);\n                        break;\n\n                    case COUNTER_CHANNEL:\n                        channelFilter = Pattern.compile(argValue);\n                        break;\n\n                    default:\n                        System.out.println(\"Unrecognised argument: '\" + arg + \"'\");\n                        return;\n                }\n            }\n        }\n\n        final CncFileReader cncFileReader = CncFileReader.map();\n\n        final CounterFilter counterFilter = new CounterFilter(\n            typeFilter, identityFilter, sessionFilter, streamFilter, channelFilter);\n\n        if (watch)\n        {\n            workLoop(delayMs, () -> printOutput(cncFileReader, counterFilter));\n        }\n        else\n        {\n            printOutput(cncFileReader, counterFilter);\n        }\n    }\n\n    @SuppressWarnings(\"try\")\n    private static void workLoop(final long delayMs, final Runnable outputPrinter)\n        throws IOException, InterruptedException\n    {\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false)))\n        {\n            do\n            {\n                clearScreen();\n                outputPrinter.run();\n                Thread.sleep(delayMs);\n            }\n            while (running.get());\n        }\n    }\n\n    private static void printOutput(final CncFileReader cncFileReader, final CounterFilter counterFilter)\n    {\n        System.out.print(DATE_FORMAT.format(new Date()));\n        System.out.println(\n            \" - Aeron Stat (CnC v\" + cncFileReader.semanticVersion() + \")\" +\n            \", pid \" + cncFileReader.driverPid() +\n            \", heartbeat age \" + cncFileReader.driverHeartbeatAgeMs() + \"ms\");\n        System.out.println(\"======================================================================\");\n\n        final CountersReader counters = cncFileReader.countersReader();\n        final int maxIdWidth = AsciiEncoding.digitCount(counters.maxCounterId());\n        final String formatString = \"%\" + maxIdWidth + \"d: %,26d - %s%n\";\n\n        counters.forEach(\n            (counterId, typeId, keyBuffer, label) ->\n            {\n                if (counterFilter.filter(typeId, keyBuffer))\n                {\n                    final long value = counters.getCounterValue(counterId);\n                    System.out.format(formatString, counterId, value, label);\n                }\n            }\n        );\n\n        System.out.println(\"--\");\n    }\n\n    private static void checkForHelp(final String[] args)\n    {\n        for (final String arg : args)\n        {\n            if (\"-?\".equals(arg) || \"-h\".equals(arg) || \"-help\".equals(arg))\n            {\n                System.out.format(\n                    \"Usage: [-Daeron.dir=<directory containing CnC file>] AeronStat%n\" +\n                    \"\\t[delay=<seconds between updates>]%n\" +\n                    \"\\t[watch=<true|false>]%n\" +\n                    \"filter by optional regex patterns:%n\" +\n                    \"\\t[type=<pattern>]%n\" +\n                    \"\\t[identity=<pattern>]%n\" +\n                    \"\\t[session=<pattern>]%n\" +\n                    \"\\t[stream=<pattern>]%n\" +\n                    \"\\t[channel=<pattern>]%n\");\n\n                System.exit(0);\n            }\n        }\n    }\n\n    private static void clearScreen() throws IOException, InterruptedException\n    {\n        if (SystemUtil.isWindows())\n        {\n            new ProcessBuilder(\"C:\\\\Windows\\\\System32\\\\cmd.exe\", \"/c\", \"cls\").inheritIO().start().waitFor();\n        }\n        else\n        {\n            System.out.print(ANSI_CLS + ANSI_HOME);\n        }\n    }\n\n    static class CounterFilter\n    {\n        private final Pattern typeFilter;\n        private final Pattern identityFilter;\n        private final Pattern sessionFilter;\n        private final Pattern streamFilter;\n        private final Pattern channelFilter;\n\n        CounterFilter(\n            final Pattern typeFilter,\n            final Pattern identityFilter,\n            final Pattern sessionFilter,\n            final Pattern streamFilter,\n            final Pattern channelFilter)\n        {\n            this.typeFilter = typeFilter;\n            this.identityFilter = identityFilter;\n            this.sessionFilter = sessionFilter;\n            this.streamFilter = streamFilter;\n            this.channelFilter = channelFilter;\n        }\n\n        private static boolean match(final Pattern pattern, final Supplier<String> supplier)\n        {\n            return null == pattern || pattern.matcher(supplier.get()).find();\n        }\n\n        boolean filter(final int typeId, final DirectBuffer keyBuffer)\n        {\n            if (!match(typeFilter, () -> Integer.toString(typeId)))\n            {\n                return false;\n            }\n\n            if (SYSTEM_COUNTER_TYPE_ID == typeId && !match(identityFilter, () -> Integer.toString(keyBuffer.getInt(0))))\n            {\n                return false;\n            }\n            else if ((typeId >= PUBLISHER_LIMIT_TYPE_ID && typeId <= RECEIVER_POS_TYPE_ID) ||\n                typeId == SENDER_LIMIT_TYPE_ID || typeId == PER_IMAGE_TYPE_ID || typeId == PUBLISHER_POS_TYPE_ID)\n            {\n                return\n                    match(identityFilter, () -> Long.toString(keyBuffer.getLong(REGISTRATION_ID_OFFSET))) &&\n                    match(sessionFilter, () -> Integer.toString(keyBuffer.getInt(SESSION_ID_OFFSET))) &&\n                    match(streamFilter, () -> Integer.toString(keyBuffer.getInt(STREAM_ID_OFFSET))) &&\n                    match(channelFilter, () -> keyBuffer.getStringAscii(CHANNEL_OFFSET));\n            }\n            else if (typeId >= SEND_CHANNEL_STATUS_TYPE_ID && typeId <= RECEIVE_CHANNEL_STATUS_TYPE_ID)\n            {\n                return match(channelFilter, () -> keyBuffer.getStringAscii(ChannelEndpointStatus.CHANNEL_OFFSET));\n            }\n\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/BacklogStat.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport static io.aeron.driver.status.PerImageIndicator.PER_IMAGE_TYPE_ID;\nimport static io.aeron.driver.status.PublisherLimit.PUBLISHER_LIMIT_TYPE_ID;\nimport static io.aeron.driver.status.PublisherPos.PUBLISHER_POS_TYPE_ID;\nimport static io.aeron.driver.status.ReceiverPos.RECEIVER_POS_TYPE_ID;\nimport static io.aeron.driver.status.SenderLimit.SENDER_LIMIT_TYPE_ID;\nimport static io.aeron.driver.status.StreamCounter.CHANNEL_OFFSET;\nimport static io.aeron.driver.status.StreamCounter.REGISTRATION_ID_OFFSET;\nimport static io.aeron.driver.status.StreamCounter.SESSION_ID_OFFSET;\nimport static io.aeron.driver.status.StreamCounter.STREAM_ID_OFFSET;\n\nimport io.aeron.Aeron;\nimport io.aeron.driver.status.PublisherLimit;\nimport io.aeron.driver.status.PublisherPos;\nimport io.aeron.driver.status.ReceiverHwm;\nimport io.aeron.driver.status.ReceiverPos;\nimport io.aeron.driver.status.SenderLimit;\nimport io.aeron.driver.status.SenderPos;\nimport io.aeron.driver.status.SubscriberPos;\n\nimport java.io.PrintStream;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.SortedMap;\nimport java.util.TreeMap;\n\nimport org.agrona.concurrent.status.CountersReader;\n\n/**\n * Tool for taking a snapshot of Aeron streams backlog information and some explanation for the\n * {@link StreamStat} counters.\n * <p>\n * Each stream managed by the {@link io.aeron.driver.MediaDriver} will be sampled and printed out on {@link System#out}.\n */\npublic class BacklogStat\n{\n    private final CountersReader counters;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        final CountersReader counters = SamplesUtil.mapCounters();\n        final BacklogStat backlogStat = new BacklogStat(counters);\n\n        backlogStat.print(System.out);\n    }\n\n    /**\n     * Construct by using a {@link CountersReader} which can be obtained from {@link Aeron#countersReader()}.\n     *\n     * @param counters to read for tracking positions.\n     */\n    public BacklogStat(final CountersReader counters)\n    {\n        this.counters = counters;\n    }\n\n    /**\n     * Take a snapshot of all the backlog information and group by stream.\n     *\n     * @return a snapshot of all the backlog information and group by stream.\n     */\n    public Map<StreamCompositeKey, StreamBacklog> snapshot()\n    {\n        final Map<StreamCompositeKey, StreamBacklog> streams = new HashMap<>();\n\n        counters.forEach(\n            (counterId, typeId, keyBuffer, label) ->\n            {\n                if ((typeId >= PUBLISHER_LIMIT_TYPE_ID && typeId <= RECEIVER_POS_TYPE_ID) ||\n                    typeId == SENDER_LIMIT_TYPE_ID || typeId == PER_IMAGE_TYPE_ID || typeId == PUBLISHER_POS_TYPE_ID)\n                {\n                    final StreamCompositeKey key = new StreamCompositeKey(\n                        keyBuffer.getInt(SESSION_ID_OFFSET),\n                        keyBuffer.getInt(STREAM_ID_OFFSET),\n                        keyBuffer.getStringAscii(CHANNEL_OFFSET));\n\n                    final StreamBacklog streamBacklog = streams.computeIfAbsent(key, (ignore) -> new StreamBacklog());\n                    final long registrationId = keyBuffer.getLong(REGISTRATION_ID_OFFSET);\n                    final long value = counters.getCounterValue(counterId);\n                    switch (typeId)\n                    {\n                        case PublisherLimit.PUBLISHER_LIMIT_TYPE_ID:\n                            streamBacklog.createPublisherIfAbsent().registrationId(registrationId);\n                            streamBacklog.createPublisherIfAbsent().limit(value);\n                            break;\n\n                        case PublisherPos.PUBLISHER_POS_TYPE_ID:\n                            streamBacklog.createPublisherIfAbsent().registrationId(registrationId);\n                            streamBacklog.createPublisherIfAbsent().position(value);\n                            break;\n\n                        case SenderPos.SENDER_POSITION_TYPE_ID:\n                            streamBacklog.createSenderIfAbsent().registrationId(registrationId);\n                            streamBacklog.createSenderIfAbsent().position(value);\n                            break;\n\n                        case SenderLimit.SENDER_LIMIT_TYPE_ID:\n                            streamBacklog.createSenderIfAbsent().registrationId(registrationId);\n                            streamBacklog.createSenderIfAbsent().limit(value);\n                            break;\n\n                        case ReceiverHwm.RECEIVER_HWM_TYPE_ID:\n                            streamBacklog.createReceiverIfAbsent().registrationId(registrationId);\n                            streamBacklog.createReceiverIfAbsent().highWaterMark(value);\n                            break;\n\n                        case ReceiverPos.RECEIVER_POS_TYPE_ID:\n                            streamBacklog.createReceiverIfAbsent().registrationId(registrationId);\n                            streamBacklog.createReceiverIfAbsent().position(value);\n                            break;\n\n                        case SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID:\n                            streamBacklog.subscriberBacklogs().put(registrationId, new Subscriber(value));\n                            break;\n                    }\n                }\n            });\n\n        return streams;\n    }\n\n    /**\n     * Print a snapshot of the stream backlog with some explanation to a {@link PrintStream}.\n     * <p>\n     * Each stream will be printed in its own section.\n     *\n     * @param out to which the stream backlog will be written.\n     */\n    public void print(final PrintStream out)\n    {\n        final StringBuilder builder = new StringBuilder();\n\n        for (final Map.Entry<StreamCompositeKey, StreamBacklog> entry : snapshot().entrySet())\n        {\n            builder.setLength(0);\n            final StreamCompositeKey key = entry.getKey();\n\n            builder\n                .append(\"sessionId=\").append(key.sessionId())\n                .append(\" streamId=\").append(key.streamId())\n                .append(\" channel=\").append(key.channel())\n                .append(\" : \");\n\n\n            final StreamBacklog streamBacklog = entry.getValue();\n            if (streamBacklog.publisher() != null)\n            {\n                builder\n                    .append(\"\\n┌─for publisher \")\n                    .append(streamBacklog.publisher().registrationId())\n                    .append(\" the last sampled position is \")\n                    .append(streamBacklog.publisher().position())\n                    .append(\" (~\")\n                    .append(streamBacklog.publisher().remainingWindow())\n                    .append(\" bytes before back-pressure)\");\n\n                final Sender sender = streamBacklog.sender();\n                if (sender != null)\n                {\n                    final long senderBacklog = sender.backlog(streamBacklog.publisher().position());\n\n                    builder.append(\"\\n└─sender \").append(sender.registrationId());\n\n                    if (senderBacklog >= 0)\n                    {\n                        builder.append(\" has to send \").append(senderBacklog).append(\" bytes\");\n                    }\n                    else\n                    {\n                        builder.append(\" is at position \").append(sender.position());\n                    }\n\n                    builder.append(\" (\").append(sender.window()).append(\" bytes remaining in the sender window)\");\n                }\n                else\n                {\n                    builder.append(\"\\n└─no sender\");\n                }\n            }\n\n            if (streamBacklog.receiver() != null)\n            {\n                builder\n                    .append(\"\\n┌─receiver \")\n                    .append(streamBacklog.receiver().registrationId())\n                    .append(\" is at position \")\n                    .append(streamBacklog.receiver().position());\n\n                final Iterator<Map.Entry<Long, Subscriber>> subscriberIterator =\n                    streamBacklog.subscriberBacklogs().entrySet().iterator();\n\n                while (subscriberIterator.hasNext())\n                {\n                    final Map.Entry<Long, Subscriber> subscriber = subscriberIterator.next();\n                    builder\n                        .append(subscriberIterator.hasNext() ? \"\\n├\" : \"\\n└\")\n                        .append(\"─subscriber \")\n                        .append(subscriber.getKey())\n                        .append(\" has \")\n                        .append(subscriber.getValue().backlog(streamBacklog.receiver().highWaterMark()))\n                        .append(\" backlog bytes\");\n                }\n            }\n\n            builder.append('\\n');\n            out.println(builder);\n        }\n    }\n\n    /**\n     * Composite key which identifies an Aeron stream of messages.\n     */\n    public static final class StreamCompositeKey\n    {\n        private final int sessionId;\n        private final int streamId;\n        private final String channel;\n\n        /**\n         * Construct a key to represent a unique stream.\n         *\n         * @param sessionId for the stream.\n         * @param streamId  for the stream within a channel.\n         * @param channel   as a URI.\n         */\n        public StreamCompositeKey(final int sessionId, final int streamId, final String channel)\n        {\n            Objects.requireNonNull(channel, \"channel cannot be null\");\n\n            this.sessionId = sessionId;\n            this.streamId = streamId;\n            this.channel = channel;\n        }\n\n        /**\n         * The session id of the stream.\n         *\n         * @return session id of the stream.\n         */\n        public int sessionId()\n        {\n            return sessionId;\n        }\n\n        /**\n         * The stream id within a channel.\n         *\n         * @return  stream id within a channel.\n         */\n        public int streamId()\n        {\n            return streamId;\n        }\n\n        /**\n         * The channel as a URI.\n         *\n         * @return channel as a URI.\n         */\n        public String channel()\n        {\n            return channel;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public boolean equals(final Object o)\n        {\n            if (this == o)\n            {\n                return true;\n            }\n\n            if (!(o instanceof StreamCompositeKey))\n            {\n                return false;\n            }\n\n            final StreamCompositeKey that = (StreamCompositeKey)o;\n\n            return this.sessionId == that.sessionId &&\n                this.streamId == that.streamId &&\n                this.channel.equals(that.channel);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public int hashCode()\n        {\n            int result = sessionId;\n            result = 31 * result + streamId;\n            result = 31 * result + channel.hashCode();\n\n            return result;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"StreamCompositeKey{\" +\n                \"sessionId=\" + sessionId +\n                \", streamId=\" + streamId +\n                \", channel='\" + channel + '\\'' +\n                '}';\n        }\n    }\n\n    /**\n     * Represents the backlog information for a particular stream of messages.\n     */\n    public static final class StreamBacklog\n    {\n        private Publisher publisher;\n        private Sender sender;\n        private Receiver receiver;\n        private final SortedMap<Long, Subscriber> subscriberBacklogs = new TreeMap<>();\n\n        Publisher publisher()\n        {\n            return publisher;\n        }\n\n        Sender sender()\n        {\n            return sender;\n        }\n\n        Receiver receiver()\n        {\n            return receiver;\n        }\n\n        Map<Long, Subscriber> subscriberBacklogs()\n        {\n            return subscriberBacklogs;\n        }\n\n        Publisher createPublisherIfAbsent()\n        {\n            return publisher == null ? publisher = new Publisher() : publisher;\n        }\n\n        Sender createSenderIfAbsent()\n        {\n            return sender == null ? sender = new Sender() : sender;\n        }\n\n        Receiver createReceiverIfAbsent()\n        {\n            return receiver == null ? receiver = new Receiver() : receiver;\n        }\n    }\n\n    static class AeronEntity\n    {\n        private long registrationId;\n\n        long registrationId()\n        {\n            return registrationId;\n        }\n\n        void registrationId(final long registrationId)\n        {\n            this.registrationId = registrationId;\n        }\n    }\n\n    static class Publisher extends AeronEntity\n    {\n        private long limit;\n        private long position;\n\n        void limit(final long limit)\n        {\n            this.limit = limit;\n        }\n\n        void position(final long position)\n        {\n            this.position = position;\n        }\n\n        long position()\n        {\n            return position;\n        }\n\n        long remainingWindow()\n        {\n            return limit - position;\n        }\n    }\n\n    static class Sender extends AeronEntity\n    {\n        private long position;\n        private long limit;\n\n        void position(final long publisherPosition)\n        {\n            this.position = publisherPosition;\n        }\n\n        long position()\n        {\n            return position;\n        }\n\n        void limit(final long limit)\n        {\n            this.limit = limit;\n        }\n\n        long backlog(final long publisherPosition)\n        {\n            return publisherPosition - position;\n        }\n\n        long window()\n        {\n            return limit - position;\n        }\n    }\n\n    static class Receiver extends AeronEntity\n    {\n        private long highWaterMark;\n        private long position;\n\n        void highWaterMark(final long highWaterMark)\n        {\n            this.highWaterMark = highWaterMark;\n        }\n\n        long highWaterMark()\n        {\n            return highWaterMark;\n        }\n\n        void position(final long position)\n        {\n            this.position = position;\n        }\n\n        long position()\n        {\n            return position;\n        }\n    }\n\n    static class Subscriber\n    {\n        private final long position;\n\n        Subscriber(final long position)\n        {\n            this.position = position;\n        }\n\n        long backlog(final long receiverHwm)\n        {\n            return receiverHwm - position;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/BasicPublisher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Publication;\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Basic Aeron publisher application.\n * <p>\n * This publisher sends a fixed number of messages on a channel and stream ID,\n * then lingers to allow any consumers that may have experienced loss a chance to NAK for\n * and recover any missing data.\n * The default values for number of messages, channel, and stream ID are\n * defined in {@link SampleConfiguration} and can be overridden by\n * setting their corresponding properties via the command-line; e.g.:\n * -Daeron.sample.channel=aeron:udp?endpoint=localhost:5555 -Daeron.sample.streamId=20\n */\npublic class BasicPublisher\n{\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final long LINGER_TIMEOUT_MS = SampleConfiguration.LINGER_TIMEOUT_MS;\n    private static final boolean EMBEDDED_MEDIA_DRIVER = SampleConfiguration.EMBEDDED_MEDIA_DRIVER;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread sleep delay is interrupted.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        System.out.println(\"Publishing to \" + CHANNEL + \" on stream id \" + STREAM_ID);\n\n        // If configured to do so, create an embedded media driver within this application rather\n        // than relying on an external one.\n        try (MediaDriver driver = EMBEDDED_MEDIA_DRIVER ? MediaDriver.launchEmbedded() : null)\n        {\n\n            final Aeron.Context ctx = new Aeron.Context();\n            if (EMBEDDED_MEDIA_DRIVER)\n            {\n                ctx.aeronDirectoryName(driver.aeronDirectoryName());\n            }\n\n            // Connect a new Aeron instance to the media driver and create a publication on\n            // the given channel and stream ID.\n            // The Aeron and Publication classes implement \"AutoCloseable\" and will automatically\n            // clean up resources when this try block is finished\n            try (Aeron aeron = Aeron.connect(ctx);\n                Publication publication = aeron.addPublication(CHANNEL, STREAM_ID))\n            {\n                final UnsafeBuffer buffer = new UnsafeBuffer(BufferUtil.allocateDirectAligned(256, 64));\n\n                for (long i = 0; i < NUMBER_OF_MESSAGES; i++)\n                {\n                    System.out.print(\"Offering \" + i + \"/\" + NUMBER_OF_MESSAGES + \" - \");\n\n                    final int length = buffer.putStringWithoutLengthAscii(0, \"Hello World! \" + i);\n                    final long position = publication.offer(buffer, 0, length);\n\n                    if (position > 0)\n                    {\n                        System.out.println(\"yay!\");\n                    }\n                    else if (position == Publication.BACK_PRESSURED)\n                    {\n                        System.out.println(\"Offer failed due to back pressure\");\n                    }\n                    else if (position == Publication.NOT_CONNECTED)\n                    {\n                        System.out.println(\"Offer failed because publisher is not connected to a subscriber\");\n                    }\n                    else if (position == Publication.ADMIN_ACTION)\n                    {\n                        System.out.println(\"Offer failed because of an administration action in the system\");\n                    }\n                    else if (position == Publication.CLOSED)\n                    {\n                        System.out.println(\"Offer failed because publication is closed\");\n                        break;\n                    }\n                    else if (position == Publication.MAX_POSITION_EXCEEDED)\n                    {\n                        System.out.println(\"Offer failed due to publication reaching its max position\");\n                        break;\n                    }\n                    else\n                    {\n                        System.out.println(\"Offer failed due to unknown reason: \" + position);\n                    }\n\n                    if (!publication.isConnected())\n                    {\n                        System.out.println(\"No active subscribers detected\");\n                    }\n\n                    Thread.sleep(TimeUnit.SECONDS.toMillis(1));\n                }\n\n                System.out.println(\"Done sending.\");\n\n                if (LINGER_TIMEOUT_MS > 0)\n                {\n                    System.out.println(\"Lingering for \" + LINGER_TIMEOUT_MS + \" milliseconds...\");\n                    Thread.sleep(LINGER_TIMEOUT_MS);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/BasicSubscriber.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.FragmentHandler;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * This is a Basic Aeron subscriber application.\n * <p>\n * The application subscribes to a default channel and stream ID. These defaults can\n * be overwritten by changing their value in {@link SampleConfiguration} or by\n * setting their corresponding Java system properties at the command line, e.g.:\n * -Daeron.sample.channel=aeron:udp?endpoint=localhost:5555 -Daeron.sample.streamId=20\n * This application only handles non-fragmented data. A DataHandler method is called\n * for every received message or message fragment.\n * For an example that implements reassembly of large, fragmented messages, see\n * {@link MultipleSubscribersWithFragmentAssembly}.\n */\npublic class BasicSubscriber\n{\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final boolean EMBEDDED_MEDIA_DRIVER = SampleConfiguration.EMBEDDED_MEDIA_DRIVER;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        System.out.println(\"Subscribing to \" + CHANNEL + \" on stream id \" + STREAM_ID);\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver driver = EMBEDDED_MEDIA_DRIVER ?\n                MediaDriver.launchEmbedded(new MediaDriver.Context().terminationHook(barrier::signalAll)) : null)\n        {\n            final Aeron.Context ctx = new Aeron.Context()\n                .availableImageHandler(SamplesUtil::printAvailableImage)\n                .unavailableImageHandler(SamplesUtil::printUnavailableImage);\n\n            if (EMBEDDED_MEDIA_DRIVER)\n            {\n                ctx.aeronDirectoryName(driver.aeronDirectoryName());\n            }\n\n            final FragmentHandler fragmentHandler = SamplesUtil.printAsciiMessage(STREAM_ID);\n\n            // Create an Aeron instance using the configured Context and create a\n            // Subscription on that instance that subscribes to the configured\n            // channel and stream ID.\n            // The Aeron and Subscription classes implement \"AutoCloseable\" and will automatically\n            // clean up resources when this try block is finished\n            try (Aeron aeron = Aeron.connect(ctx);\n                Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID))\n            {\n                SamplesUtil.subscriberLoop(fragmentHandler, FRAGMENT_COUNT_LIMIT, running).accept(subscription);\n\n                System.out.println(\"Shutting down...\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/CncFileReader.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.CncFileDescriptor;\nimport io.aeron.CommonContext;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.BufferUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SemanticVersion;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.ringbuffer.RingBufferDescriptor;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.nio.MappedByteBuffer;\nimport java.nio.charset.StandardCharsets;\n\nimport static io.aeron.CncFileDescriptor.*;\nimport static io.aeron.samples.SamplesUtil.mapExistingFileReadOnly;\n\n/**\n * Reader for Aeron CnC file represented by {@link CncFileDescriptor} which can be used for observability.\n */\npublic final class CncFileReader implements AutoCloseable\n{\n    private boolean isClosed = false;\n    private final int driverPid;\n    private final int cncVersion;\n    private final String cncSemanticVersion;\n    private final MappedByteBuffer cncByteBuffer;\n    private final CountersReader countersReader;\n    private final UnsafeBuffer toDriverBuffer;\n\n    private CncFileReader(final MappedByteBuffer cncByteBuffer)\n    {\n        this.cncByteBuffer = cncByteBuffer;\n\n        final DirectBuffer cncMetaDataBuffer = createMetaDataBuffer(cncByteBuffer);\n        driverPid = cncMetaDataBuffer.getInt(pidOffset(0));\n        final int cncVersion = cncMetaDataBuffer.getInt(cncVersionOffset(0));\n\n        try\n        {\n            checkVersion(cncVersion);\n        }\n        catch (final AeronException e)\n        {\n            BufferUtil.free(cncByteBuffer);\n            throw e;\n        }\n\n        this.cncVersion = cncVersion;\n        this.cncSemanticVersion = SemanticVersion.toString(cncVersion);\n\n        this.toDriverBuffer = CncFileDescriptor.createToDriverBuffer(cncByteBuffer, cncMetaDataBuffer);\n\n        this.countersReader = new CountersReader(\n            createCountersMetaDataBuffer(cncByteBuffer, cncMetaDataBuffer),\n            createCountersValuesBuffer(cncByteBuffer, cncMetaDataBuffer),\n            StandardCharsets.US_ASCII);\n    }\n\n    /**\n     * Map an existing CnC file.\n     *\n     * @return the {@link CncFileReader} wrapper for reading useful data from the cnc file.\n     * @throws AeronException if the cnc version major version is not compatible.\n     */\n    public static CncFileReader map()\n    {\n        final File cncFile = CommonContext.newDefaultCncFile();\n        final MappedByteBuffer cncByteBuffer = mapExistingFileReadOnly(cncFile);\n\n        return new CncFileReader(cncByteBuffer);\n    }\n\n    /**\n     * Get the cnc version.\n     *\n     * @return the cnc version.\n     */\n    public int cncVersion()\n    {\n        return cncVersion;\n    }\n\n    /**\n     * Get the cnc semantic version.\n     *\n     * @return the cnc semantic version.\n     */\n    public String semanticVersion()\n    {\n        return cncSemanticVersion;\n    }\n\n    /**\n     * Get the counters reader for querying counter values.\n     *\n     * @return the counters' reader.\n     */\n    public CountersReader countersReader()\n    {\n        return countersReader;\n    }\n\n    /**\n     * Get the epoch timestamp (ms) of the last driver heartbeat.\n     *\n     * @return the epoch timestamp (ms) of the last driver heartbeat.\n     */\n    public long driverHeartbeatMs()\n    {\n        final int timestampOffset = (toDriverBuffer.capacity() - RingBufferDescriptor.TRAILER_LENGTH) +\n            RingBufferDescriptor.CONSUMER_HEARTBEAT_OFFSET;\n\n        return toDriverBuffer.getLongVolatile(timestampOffset);\n    }\n\n    /**\n     * Return media driver PID that is stored in the CnC file.\n     *\n     * @return media driver PID.\n     */\n    public int driverPid()\n    {\n        return driverPid;\n    }\n\n    /**\n     * Get the number of milliseconds since the last driver heartbeat.\n     *\n     * @return the number of milliseconds since the last driver heartbeat.\n     */\n    public long driverHeartbeatAgeMs()\n    {\n        return System.currentTimeMillis() - driverHeartbeatMs();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (!isClosed)\n        {\n            isClosed = true;\n            BufferUtil.free(cncByteBuffer);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/DriverTool.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.CncFileDescriptor;\nimport io.aeron.CommonContext;\nimport io.aeron.DriverProxy;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.DirectBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;\n\nimport java.io.File;\nimport java.nio.MappedByteBuffer;\nimport java.util.Date;\n\nimport static io.aeron.CncFileDescriptor.*;\n\n/**\n * Tool for printing out Aeron Media Driver Information. A command-and-control (CnC) file is maintained by a\n * media driver in shared memory. This application reads the CnC file and prints its status. Layout of the Cnc file is\n * described in {@link CncFileDescriptor}.\n */\npublic class DriverTool\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        boolean printPidOnly = false;\n        boolean terminateDriver = false;\n\n        if (0 != args.length)\n        {\n            checkForHelp(args);\n\n            if (args[0].equals(\"pid\"))\n            {\n                printPidOnly = true;\n            }\n            else if (args[0].equals(\"terminate\"))\n            {\n                terminateDriver = true;\n            }\n        }\n\n        final File cncFile = CommonContext.newDefaultCncFile();\n        final MappedByteBuffer cncByteBuffer = IoUtil.mapExistingFile(cncFile, \"cnc\");\n        final DirectBuffer cncMetaData = createMetaDataBuffer(cncByteBuffer);\n        final int cncVersion = cncMetaData.getInt(cncVersionOffset(0));\n\n        checkVersion(cncVersion);\n\n        final ManyToOneRingBuffer toDriver = new ManyToOneRingBuffer(createToDriverBuffer(cncByteBuffer, cncMetaData));\n\n        if (printPidOnly)\n        {\n            System.out.println(pid(cncMetaData));\n        }\n        else if (terminateDriver)\n        {\n            final DriverProxy driverProxy = new DriverProxy(toDriver, toDriver.nextCorrelationId());\n\n            if (!driverProxy.terminateDriver(null, 0, 0))\n            {\n                throw new AeronException(\"could not send termination request.\");\n            }\n        }\n        else\n        {\n            System.out.println(\"Command `n Control file: \" + cncFile);\n            System.out.println(\"Version: \" + cncVersion + \", PID: \" + pid(cncMetaData));\n            printDateActivityAndStartTimestamps(startTimestampMs(cncMetaData), toDriver.consumerHeartbeatTime());\n        }\n    }\n\n    private static void printDateActivityAndStartTimestamps(final long startTimestamp, final long activityTimestamp)\n    {\n        System.out.format(\n            \"%1$tH:%1$tM:%1$tS (start: %2$tF %2$tH:%2$tM:%2$tS, activity: %3$tF %3$tH:%3$tM:%3$tS)%n\",\n            new Date(),\n            new Date(startTimestamp),\n            new Date(activityTimestamp));\n    }\n\n    private static void checkForHelp(final String[] args)\n    {\n        for (final String arg : args)\n        {\n            if (\"-?\".equals(arg) || \"-h\".equals(arg) || \"-help\".equals(arg))\n            {\n                System.out.format(\n                    \"Usage: [-Daeron.dir=<directory containing CnC file>] DriverTool <pid> <terminate>%n\" +\n                    \"    pid: prints PID of driver only%n\" +\n                    \"    terminate: request the driver to terminate%n\");\n                System.out.flush();\n                System.exit(0);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedBufferClaimIpcThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Throughput test using {@link ExclusivePublication#tryClaim(int, BufferClaim)} over UDP transport.\n */\npublic class EmbeddedBufferClaimIpcThroughput\n{\n    private static final int BURST_LENGTH = 1_000_000;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String CHANNEL = CommonContext.IPC_CHANNEL;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the join on other threads is interrupted.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .threadingMode(ThreadingMode.SHARED);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver mediaDriver = MediaDriver.launch(ctx.terminationHook(barrier::signalAll));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID);\n            Publication publication = aeron.addPublication(CHANNEL, STREAM_ID))\n        {\n            final ImageRateSubscriber subscriber = new ImageRateSubscriber(FRAGMENT_COUNT_LIMIT, running, subscription);\n            final Thread subscriberThread = new Thread(subscriber);\n            subscriberThread.setName(\"subscriber\");\n            final Thread publisherThread = new Thread(new Publisher(running, publication));\n            publisherThread.setName(\"publisher\");\n            final Thread rateReporterThread = new Thread(new ImageRateReporter(MESSAGE_LENGTH, running, subscriber));\n            rateReporterThread.setName(\"rate-reporter\");\n\n            rateReporterThread.start();\n            subscriberThread.start();\n            publisherThread.start();\n\n            barrier.await();\n\n            subscriberThread.join();\n            publisherThread.join();\n            rateReporterThread.join();\n        }\n    }\n\n    static final class Publisher implements Runnable\n    {\n        private final AtomicBoolean running;\n        private final Publication publication;\n\n        Publisher(final AtomicBoolean running, final Publication publication)\n        {\n            this.running = running;\n            this.publication = publication;\n        }\n\n        public void run()\n        {\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n            final AtomicBoolean running = this.running;\n            final Publication publication = this.publication;\n            final BufferClaim bufferClaim = new BufferClaim();\n            long backPressureCount = 0;\n            long totalMessageCount = 0;\n\n            outputResults:\n            while (running.get())\n            {\n                for (int i = 0; i < BURST_LENGTH; i++)\n                {\n                    idleStrategy.reset();\n                    while (publication.tryClaim(MESSAGE_LENGTH, bufferClaim) <= 0)\n                    {\n                        ++backPressureCount;\n                        if (!running.get())\n                        {\n                            break outputResults;\n                        }\n                        idleStrategy.idle();\n                    }\n\n                    final int offset = bufferClaim.offset();\n                    bufferClaim.buffer().putInt(offset, i); // Example field write\n                    // Real app would write whatever fields are required via a flyweight like SBE\n\n                    bufferClaim.commit();\n\n                    ++totalMessageCount;\n                }\n            }\n\n            final double backPressureRatio = backPressureCount / (double)totalMessageCount;\n            System.out.format(\"Publisher back pressure ratio: %f%n\", backPressureRatio);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedDualExclusiveThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.*;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.console.ContinueBarrier;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static io.aeron.samples.SamplesUtil.rateReporterHandler;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Throughput test with dual {@link ExclusivePublication}s using {@link ExclusivePublication#tryClaim(int, BufferClaim)}\n * for testing two sources into a single {@link Subscription} over UDP transport.\n */\npublic class EmbeddedDualExclusiveThroughput\n{\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final long LINGER_TIMEOUT_MS = SampleConfiguration.LINGER_TIMEOUT_MS;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n\n    private static final UnsafeBuffer OFFER_BUFFER = new UnsafeBuffer(\n        BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH));\n\n    private static volatile boolean printingActive = true;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread sleep delay is interrupted.\n     */\n    @SuppressWarnings(\"MethodLength\")\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final RateReporter reporter = new RateReporter(\n            TimeUnit.SECONDS.toNanos(1), EmbeddedDualExclusiveThroughput::printRate);\n        final ExecutorService executor = Executors.newFixedThreadPool(2);\n        final AtomicBoolean running = new AtomicBoolean(true);\n        final AvailableImageHandler handler =\n            (image) -> System.out.println(\"source connection=\" + image.sourceIdentity());\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .controlMode(CommonContext.MDC_CONTROL_MODE_MANUAL);\n\n        final String sourceUriOne = builder.controlEndpoint(\"localhost:20550\").tags(\"1\").build();\n        final String sourceUriTwo = builder.controlEndpoint(\"localhost:20551\").tags(\"2\").build();\n\n        try (MediaDriver mediaDriver = MediaDriver.launch();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID, handler, null);\n            ExclusivePublication publicationOne = aeron.addExclusivePublication(sourceUriOne, STREAM_ID);\n            ExclusivePublication publicationTwo = aeron.addExclusivePublication(sourceUriTwo, STREAM_ID))\n        {\n            publicationOne.addDestination(CHANNEL);\n            publicationTwo.addDestination(CHANNEL);\n\n            while (subscription.imageCount() < 2)\n            {\n                Thread.yield();\n            }\n\n            executor.execute(reporter);\n            executor.execute(() -> SamplesUtil.subscriberLoop(\n                rateReporterHandler(reporter), FRAGMENT_COUNT_LIMIT, running).accept(subscription));\n\n            final ContinueBarrier barrier = new ContinueBarrier(\"Execute again?\");\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n\n            do\n            {\n                System.out.format(\n                    \"%nStreaming %,d messages of payload length %d bytes to %s on stream id %d%n\",\n                    NUMBER_OF_MESSAGES * 2, MESSAGE_LENGTH, CHANNEL, STREAM_ID);\n\n                printingActive = true;\n\n                long backPressureCountOne = 0;\n                long backPressureCountTwo = 0;\n\n                for (long a = 0, b = 0; a < NUMBER_OF_MESSAGES || b < NUMBER_OF_MESSAGES;)\n                {\n                    idleStrategy.reset();\n                    boolean failedOne = false;\n                    boolean failedTwo = false;\n\n                    if (a < NUMBER_OF_MESSAGES)\n                    {\n                        if (publicationOne.offer(OFFER_BUFFER, 0, MESSAGE_LENGTH, null) > 0)\n                        {\n                            a++;\n                        }\n                        else\n                        {\n                            backPressureCountOne++;\n                            failedOne = true;\n                        }\n                    }\n\n                    if (b < NUMBER_OF_MESSAGES)\n                    {\n                        if (publicationTwo.offer(OFFER_BUFFER, 0, MESSAGE_LENGTH, null) > 0)\n                        {\n                            b++;\n                        }\n                        else\n                        {\n                            backPressureCountTwo++;\n                            failedTwo = true;\n                        }\n                    }\n\n                    if (failedOne || failedTwo)\n                    {\n                        idleStrategy.idle();\n                    }\n                }\n\n                System.out.println(\"Done streaming.\" +\n                    \" backPressureRatioOne=\" + ((double)backPressureCountOne / NUMBER_OF_MESSAGES) +\n                    \" backPressureRatioTwo=\" + ((double)backPressureCountTwo / NUMBER_OF_MESSAGES));\n\n                if (LINGER_TIMEOUT_MS > 0)\n                {\n                    System.out.println(\"Lingering for \" + LINGER_TIMEOUT_MS + \" milliseconds...\");\n                    Thread.sleep(LINGER_TIMEOUT_MS);\n                }\n\n                printingActive = false;\n            }\n            while (barrier.await());\n\n            running.set(false);\n            reporter.halt();\n            executor.shutdown();\n        }\n    }\n\n    private static void printRate(\n        final double messagesPerSec, final double bytesPerSec, final long totalFragments, final long totalBytes)\n    {\n        if (printingActive)\n        {\n            System.out.format(\n                \"%.04g msgs/sec, %.04g bytes/sec, totals %d messages %d MB payloads%n\",\n                messagesPerSec, bytesPerSec, totalFragments, totalBytes / (1024 * 1024));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedExclusiveBufferClaimIpcThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Throughput test using {@link ExclusivePublication#tryClaim(int, BufferClaim)} over IPC transport.\n */\npublic class EmbeddedExclusiveBufferClaimIpcThroughput\n{\n    private static final int BURST_LENGTH = 1_000_000;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String CHANNEL = CommonContext.IPC_CHANNEL;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the join on the created threads is interrupted.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .threadingMode(ThreadingMode.SHARED);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver mediaDriver = MediaDriver.launch(ctx.terminationHook(barrier::signalAll));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID);\n            Publication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n        {\n            final ImageRateSubscriber subscriber =\n                new ImageRateSubscriber(FRAGMENT_COUNT_LIMIT, running, subscription);\n            final Thread subscriberThread = new Thread(subscriber);\n            subscriberThread.setName(\"subscriber\");\n            final Thread publisherThread = new Thread(new Publisher(running, publication));\n            publisherThread.setName(\"publisher\");\n            final Thread rateReporterThread =\n                new Thread(new ImageRateReporter(MESSAGE_LENGTH, running, subscriber));\n            rateReporterThread.setName(\"rate-reporter\");\n\n            rateReporterThread.start();\n            subscriberThread.start();\n            publisherThread.start();\n\n            barrier.await();\n\n            subscriberThread.join();\n            publisherThread.join();\n            rateReporterThread.join();\n        }\n    }\n\n    static final class Publisher implements Runnable\n    {\n        private final AtomicBoolean running;\n        private final Publication publication;\n\n        Publisher(final AtomicBoolean running, final Publication publication)\n        {\n            this.running = running;\n            this.publication = publication;\n        }\n\n        public void run()\n        {\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n            final AtomicBoolean running = this.running;\n            final Publication publication = this.publication;\n            final BufferClaim bufferClaim = new BufferClaim();\n            long backPressureCount = 0;\n            long totalMessageCount = 0;\n\n            outputResults:\n            while (running.get())\n            {\n                for (int i = 0; i < BURST_LENGTH; i++)\n                {\n                    idleStrategy.reset();\n                    while (publication.tryClaim(MESSAGE_LENGTH, bufferClaim) <= 0)\n                    {\n                        ++backPressureCount;\n                        if (!running.get())\n                        {\n                            break outputResults;\n                        }\n                        idleStrategy.idle();\n                    }\n\n                    final int offset = bufferClaim.offset();\n                    bufferClaim.buffer().putInt(offset, i); // Example field write\n                    // Real app would write whatever fields are required via a flyweight like SBE\n\n                    bufferClaim.commit();\n\n                    ++totalMessageCount;\n                }\n            }\n\n            final double backPressureRatio = backPressureCount / (double)totalMessageCount;\n            System.out.format(\"Publisher back pressure ratio: %f%n\", backPressureRatio);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedExclusiveIpcThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport org.agrona.BufferUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Throughput test using {@link ExclusivePublication#offer(DirectBuffer, int, int)} over IPC transport.\n */\npublic class EmbeddedExclusiveIpcThroughput\n{\n    private static final int BURST_LENGTH = 1_000_000;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String CHANNEL = CommonContext.IPC_CHANNEL;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread is interrupted while waiting on the threads to join.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver mediaDriver = MediaDriver.launch(new MediaDriver.Context()\n                .threadingMode(ThreadingMode.SHARED).terminationHook(barrier::signalAll));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n        {\n            final ImageRateSubscriber subscriber = new ImageRateSubscriber(FRAGMENT_COUNT_LIMIT, running, subscription);\n            final Thread subscriberThread = new Thread(subscriber);\n            subscriberThread.setName(\"subscriber\");\n            final Thread publisherThread = new Thread(new Publisher(running, publication));\n            publisherThread.setName(\"publisher\");\n            final Thread rateReporterThread = new Thread(new ImageRateReporter(MESSAGE_LENGTH, running, subscriber));\n            rateReporterThread.setName(\"rate-reporter\");\n\n            rateReporterThread.start();\n            subscriberThread.start();\n            publisherThread.start();\n\n            subscriberThread.join();\n            publisherThread.join();\n            rateReporterThread.join();\n        }\n    }\n\n    static final class Publisher implements Runnable\n    {\n        private final AtomicBoolean running;\n        private final ExclusivePublication publication;\n\n        Publisher(final AtomicBoolean running, final ExclusivePublication publication)\n        {\n            this.running = running;\n            this.publication = publication;\n        }\n\n        public void run()\n        {\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n            final AtomicBoolean running = this.running;\n            final ExclusivePublication publication = this.publication;\n            final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(\n                publication.maxMessageLength(), CACHE_LINE_LENGTH);\n            final UnsafeBuffer buffer = new UnsafeBuffer(byteBuffer);\n            long backPressureCount = 0;\n            long totalMessageCount = 0;\n\n            outputResults:\n            while (running.get())\n            {\n                for (int i = 0; i < BURST_LENGTH; i++)\n                {\n                    idleStrategy.reset();\n                    while (publication.offer(buffer, 0, MESSAGE_LENGTH, null) <= 0)\n                    {\n                        ++backPressureCount;\n                        if (!running.get())\n                        {\n                            break outputResults;\n                        }\n\n                        idleStrategy.idle();\n                    }\n\n                    ++totalMessageCount;\n                }\n            }\n\n            final double backPressureRatio = backPressureCount / (double)totalMessageCount;\n            System.out.format(\"Publisher back pressure ratio: %f%n\", backPressureRatio);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedExclusiveSpiedThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.console.ContinueBarrier;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static io.aeron.samples.SamplesUtil.rateReporterHandler;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Throughput test using {@link ExclusivePublication#offer(DirectBuffer, int, int)} over UDP transport by spying via\n * IPC.\n */\npublic class EmbeddedExclusiveSpiedThroughput\n{\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final long LINGER_TIMEOUT_MS = SampleConfiguration.LINGER_TIMEOUT_MS;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n\n    private static final UnsafeBuffer OFFER_BUFFER = new UnsafeBuffer(\n        BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH));\n\n    private static volatile boolean printingActive = true;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if interrupted during linger.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final RateReporter reporter = new RateReporter(\n            TimeUnit.SECONDS.toNanos(1), EmbeddedExclusiveSpiedThroughput::printRate);\n        final ExecutorService executor = Executors.newFixedThreadPool(2);\n        final AtomicBoolean running = new AtomicBoolean(true);\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .spiesSimulateConnection(true);\n\n        try (MediaDriver mediaDriver = MediaDriver.launch(ctx);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CommonContext.SPY_PREFIX + CHANNEL, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n        {\n            executor.execute(reporter);\n            executor.execute(() -> SamplesUtil.subscriberLoop(\n                rateReporterHandler(reporter), FRAGMENT_COUNT_LIMIT, running).accept(subscription));\n\n            final ContinueBarrier barrier = new ContinueBarrier(\"Execute again?\");\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n\n            do\n            {\n                System.out.format(\n                    \"%nStreaming %,d messages of payload length %d bytes to %s on stream id %d%n\",\n                    NUMBER_OF_MESSAGES, MESSAGE_LENGTH, CHANNEL, STREAM_ID);\n\n                printingActive = true;\n\n                long backPressureCount = 0;\n                for (long i = 0; i < NUMBER_OF_MESSAGES; i++)\n                {\n                    OFFER_BUFFER.putLong(0, i);\n\n                    idleStrategy.reset();\n                    while (publication.offer(OFFER_BUFFER, 0, MESSAGE_LENGTH, null) < 0)\n                    {\n                        backPressureCount++;\n                        idleStrategy.idle();\n                    }\n                }\n\n                System.out.println(\n                    \"Done streaming. backPressureRatio=\" + ((double)backPressureCount / NUMBER_OF_MESSAGES));\n\n                if (LINGER_TIMEOUT_MS > 0)\n                {\n                    System.out.println(\"Lingering for \" + LINGER_TIMEOUT_MS + \" milliseconds...\");\n                    Thread.sleep(LINGER_TIMEOUT_MS);\n                }\n\n                printingActive = false;\n            }\n            while (barrier.await());\n\n            running.set(false);\n            reporter.halt();\n            executor.shutdown();\n        }\n    }\n\n    private static void printRate(\n        final double messagesPerSec, final double bytesPerSec, final long totalFragments, final long totalBytes)\n    {\n        if (printingActive)\n        {\n            System.out.format(\n                \"%.04g msgs/sec, %.04g bytes/sec, totals %d messages %d MB payloads%n\",\n                messagesPerSec, bytesPerSec, totalFragments, totalBytes / (1024 * 1024));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedExclusiveThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.*;\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.console.ContinueBarrier;\n\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static io.aeron.samples.SamplesUtil.rateReporterHandler;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Throughput test using {@link ExclusivePublication#offer(DirectBuffer, int, int)} over UDP transport.\n */\npublic class EmbeddedExclusiveThroughput\n{\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final long LINGER_TIMEOUT_MS = SampleConfiguration.LINGER_TIMEOUT_MS;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n\n    private static final UnsafeBuffer OFFER_BUFFER = new UnsafeBuffer(\n        BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH));\n\n    private static volatile boolean printingActive = true;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread is interrupted during linger.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final RateReporter reporter = new RateReporter(\n            TimeUnit.SECONDS.toNanos(1), EmbeddedExclusiveThroughput::printRate);\n        final ExecutorService executor = Executors.newFixedThreadPool(2);\n        final AtomicBoolean running = new AtomicBoolean(true);\n\n        try (MediaDriver mediaDriver = MediaDriver.launch();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n        {\n            executor.execute(reporter);\n            executor.execute(() -> SamplesUtil.subscriberLoop(\n                rateReporterHandler(reporter), FRAGMENT_COUNT_LIMIT, running).accept(subscription));\n\n            final ContinueBarrier barrier = new ContinueBarrier(\"Execute again?\");\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n\n            do\n            {\n                System.out.format(\n                    \"%nStreaming %,d messages of payload length %d bytes to %s on stream id %d%n\",\n                    NUMBER_OF_MESSAGES, MESSAGE_LENGTH, CHANNEL, STREAM_ID);\n\n                printingActive = true;\n\n                long backPressureCount = 0;\n                for (long i = 0; i < NUMBER_OF_MESSAGES; i++)\n                {\n                    OFFER_BUFFER.putLong(0, i);\n\n                    idleStrategy.reset();\n                    while (publication.offer(OFFER_BUFFER, 0, MESSAGE_LENGTH, null) < 0)\n                    {\n                        backPressureCount++;\n                        idleStrategy.idle();\n                    }\n                }\n\n                System.out.println(\n                    \"Done streaming. backPressureRatio=\" + ((double)backPressureCount / NUMBER_OF_MESSAGES));\n\n                if (LINGER_TIMEOUT_MS > 0)\n                {\n                    System.out.println(\"Lingering for \" + LINGER_TIMEOUT_MS + \" milliseconds...\");\n                    Thread.sleep(LINGER_TIMEOUT_MS);\n                }\n\n                printingActive = false;\n            }\n            while (barrier.await());\n\n            running.set(false);\n            reporter.halt();\n            executor.shutdown();\n        }\n    }\n\n    private static void printRate(\n        final double messagesPerSec, final double bytesPerSec, final long totalFragments, final long totalBytes)\n    {\n        if (printingActive)\n        {\n            System.out.format(\n                \"%.04g msgs/sec, %.04g bytes/sec, totals %d messages %d MB payloads%n\",\n                messagesPerSec, bytesPerSec, totalFragments, totalBytes / (1024 * 1024));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedExclusiveVectoredIpcThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.DirectBufferVector;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Throughput test using {@link ExclusivePublication#offer(DirectBufferVector[])} over IPC transport.\n */\npublic class EmbeddedExclusiveVectoredIpcThroughput\n{\n    private static final int BURST_LENGTH = 1_000_000;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int VEC_ONE_LENGTH = 16;\n    private static final int VEC_TWO_LENGTH = MESSAGE_LENGTH - VEC_ONE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String CHANNEL = CommonContext.IPC_CHANNEL;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread is interrupted while waiting on the threads to join.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .threadingMode(ThreadingMode.SHARED);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver mediaDriver = MediaDriver.launch(ctx.terminationHook(barrier::signalAll));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n        {\n            final ImageRateSubscriber subscriber = new ImageRateSubscriber(FRAGMENT_COUNT_LIMIT, running, subscription);\n            final Thread subscriberThread = new Thread(subscriber);\n            subscriberThread.setName(\"subscriber\");\n            final Thread publisherThread = new Thread(new Publisher(running, publication));\n            publisherThread.setName(\"publisher\");\n            final Thread rateReporterThread = new Thread(new ImageRateReporter(MESSAGE_LENGTH, running, subscriber));\n            rateReporterThread.setName(\"rate-reporter\");\n\n            rateReporterThread.start();\n            subscriberThread.start();\n            publisherThread.start();\n\n            subscriberThread.join();\n            publisherThread.join();\n            rateReporterThread.join();\n        }\n    }\n\n    static final class Publisher implements Runnable\n    {\n        private final AtomicBoolean running;\n        private final ExclusivePublication publication;\n\n        Publisher(final AtomicBoolean running, final ExclusivePublication publication)\n        {\n            this.running = running;\n            this.publication = publication;\n        }\n\n        public void run()\n        {\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n            final AtomicBoolean running = this.running;\n            final ExclusivePublication publication = this.publication;\n            final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, CACHE_LINE_LENGTH);\n            final UnsafeBuffer bufferOne = new UnsafeBuffer(byteBuffer, 0, VEC_ONE_LENGTH);\n            final UnsafeBuffer bufferTwo = new UnsafeBuffer(byteBuffer, VEC_ONE_LENGTH, VEC_TWO_LENGTH);\n            final DirectBufferVector[] vectors = new DirectBufferVector[]\n            {\n                new DirectBufferVector(bufferOne, 0, VEC_ONE_LENGTH),\n                new DirectBufferVector(bufferTwo, 0, VEC_TWO_LENGTH),\n            };\n\n            long backPressureCount = 0;\n            long totalMessageCount = 0;\n\n            outputResults:\n            while (running.get())\n            {\n                for (int i = 0; i < BURST_LENGTH; i++)\n                {\n                    idleStrategy.reset();\n                    while (publication.offer(vectors, null) <= 0)\n                    {\n                        ++backPressureCount;\n                        if (!running.get())\n                        {\n                            break outputResults;\n                        }\n\n                        idleStrategy.idle();\n                    }\n\n                    ++totalMessageCount;\n                }\n            }\n\n            final double backPressureRatio = backPressureCount / (double)totalMessageCount;\n            System.out.format(\"Publisher back pressure ratio: %f%n\", backPressureRatio);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedIpcThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.ConcurrentPublication;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport org.agrona.BufferUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Throughput test using {@link ConcurrentPublication#offer(DirectBuffer, int, int)} over IPC transport.\n */\npublic class EmbeddedIpcThroughput\n{\n    private static final int BURST_LENGTH = 1_000_000;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String CHANNEL = CommonContext.IPC_CHANNEL;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread is interrupted while waiting on the threads to join.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver mediaDriver = MediaDriver.launch(new MediaDriver.Context()\n                .threadingMode(ThreadingMode.SHARED).terminationHook(barrier::signalAll));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID);\n            Publication publication = aeron.addPublication(CHANNEL, STREAM_ID))\n        {\n            final ImageRateSubscriber subscriber = new ImageRateSubscriber(FRAGMENT_COUNT_LIMIT, running, subscription);\n            final Thread subscriberThread = new Thread(subscriber);\n            subscriberThread.setName(\"subscriber\");\n            final Thread publisherThread = new Thread(new Publisher(running, publication));\n            publisherThread.setName(\"publisher\");\n            final Thread rateReporterThread = new Thread(new ImageRateReporter(MESSAGE_LENGTH, running, subscriber));\n            rateReporterThread.setName(\"rate-reporter\");\n\n            rateReporterThread.start();\n            subscriberThread.start();\n            publisherThread.start();\n\n            subscriberThread.join();\n            publisherThread.join();\n            rateReporterThread.join();\n        }\n    }\n\n    static final class Publisher implements Runnable\n    {\n        private final AtomicBoolean running;\n        private final Publication publication;\n\n        Publisher(final AtomicBoolean running, final Publication publication)\n        {\n            this.running = running;\n            this.publication = publication;\n        }\n\n        public void run()\n        {\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n            final Publication publication = this.publication;\n            final AtomicBoolean running = this.running;\n            final ByteBuffer byteBuffer = BufferUtil.allocateDirectAligned(\n                publication.maxMessageLength(), CACHE_LINE_LENGTH);\n            final UnsafeBuffer buffer = new UnsafeBuffer(byteBuffer);\n            long backPressureCount = 0;\n            long totalMessageCount = 0;\n\n            outputResults:\n            while (running.get())\n            {\n                for (int i = 0; i < BURST_LENGTH; i++)\n                {\n                    idleStrategy.reset();\n                    while (publication.offer(buffer, 0, MESSAGE_LENGTH) <= 0)\n                    {\n                        ++backPressureCount;\n                        if (!running.get())\n                        {\n                            break outputResults;\n                        }\n\n                        idleStrategy.idle();\n                    }\n\n                    ++totalMessageCount;\n                }\n            }\n\n            final double backPressureRatio = backPressureCount / (double)totalMessageCount;\n            System.out.format(\"Publisher back pressure ratio: %f%n\", backPressureRatio);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedPingPong.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.*;\nimport org.HdrHistogram.Histogram;\n\nimport io.aeron.Aeron;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.*;\nimport org.agrona.console.ContinueBarrier;\n\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Latency test using a ping-pong approach to measure RTT and store all results in a {@link Histogram}.\n */\npublic class EmbeddedPingPong\n{\n    private static final int PING_STREAM_ID = SampleConfiguration.PING_STREAM_ID;\n    private static final int PONG_STREAM_ID = SampleConfiguration.PONG_STREAM_ID;\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final long WARMUP_NUMBER_OF_MESSAGES = SampleConfiguration.WARMUP_NUMBER_OF_MESSAGES;\n    private static final int WARMUP_NUMBER_OF_ITERATIONS = SampleConfiguration.WARMUP_NUMBER_OF_ITERATIONS;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final int FRAME_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String PING_CHANNEL = SampleConfiguration.PING_CHANNEL;\n    private static final String PONG_CHANNEL = SampleConfiguration.PONG_CHANNEL;\n    private static final boolean EXCLUSIVE_PUBLICATIONS = SampleConfiguration.EXCLUSIVE_PUBLICATIONS;\n\n    private static final UnsafeBuffer OFFER_BUFFER = new UnsafeBuffer(\n        BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH));\n    private static final Histogram HISTOGRAM = new Histogram(TimeUnit.SECONDS.toNanos(10), 3);\n    private static final CountDownLatch PONG_IMAGE_LATCH = new CountDownLatch(1);\n    private static final IdleStrategy PING_HANDLER_IDLE_STRATEGY = SampleConfiguration.newIdleStrategy();\n    private static final IdleStrategy PONG_HANDLER_IDLE_STRATEGY = SampleConfiguration.newIdleStrategy();\n    private static final AtomicBoolean RUNNING = new AtomicBoolean(true);\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread is interrupted.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .threadingMode(ThreadingMode.DEDICATED)\n            .conductorIdleStrategy(new BackoffIdleStrategy(1, 1, 1000, 1000))\n            .receiverIdleStrategy(NoOpIdleStrategy.INSTANCE)\n            .senderIdleStrategy(NoOpIdleStrategy.INSTANCE);\n\n        try (MediaDriver mediaDriver = MediaDriver.launch(ctx);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName())))\n        {\n            final Thread pongThread = startPong(aeron);\n            pongThread.start();\n\n            runPing(aeron);\n            RUNNING.set(false);\n            pongThread.join();\n\n            System.out.println(\"Shutdown Driver...\");\n        }\n    }\n\n    private static void runPing(final Aeron aeron) throws InterruptedException\n    {\n        System.out.println(\"Publishing Ping at \" + PING_CHANNEL + \" on stream id \" + PING_STREAM_ID);\n        System.out.println(\"Subscribing Pong at \" + PONG_CHANNEL + \" on stream id \" + PONG_STREAM_ID);\n        System.out.println(\"Message payload length of \" + MESSAGE_LENGTH + \" bytes\");\n        System.out.println(\"Using exclusive publications: \" + EXCLUSIVE_PUBLICATIONS);\n\n        final FragmentAssembler dataHandler = new FragmentAssembler(EmbeddedPingPong::pongHandler);\n\n        try (Subscription pongSubscription = aeron.addSubscription(\n            PONG_CHANNEL, PONG_STREAM_ID, EmbeddedPingPong::availablePongImageHandler, null);\n            Publication pingPublication = EXCLUSIVE_PUBLICATIONS ?\n                aeron.addExclusivePublication(PING_CHANNEL, PING_STREAM_ID) :\n                aeron.addPublication(PING_CHANNEL, PING_STREAM_ID))\n        {\n            System.out.println(\"Waiting for new image from Pong...\");\n            PONG_IMAGE_LATCH.await();\n\n            System.out.format(\"Warming up... %d iterations of %,d messages%n\",\n                WARMUP_NUMBER_OF_ITERATIONS, WARMUP_NUMBER_OF_MESSAGES);\n\n            for (int i = 0; i < WARMUP_NUMBER_OF_ITERATIONS; i++)\n            {\n                roundTripMessages(dataHandler, pingPublication, pongSubscription, WARMUP_NUMBER_OF_MESSAGES);\n                Thread.yield();\n            }\n\n            Thread.sleep(100);\n            final ContinueBarrier barrier = new ContinueBarrier(\"Execute again?\");\n\n            do\n            {\n                HISTOGRAM.reset();\n                System.out.format(\"Pinging %,d messages%n\", NUMBER_OF_MESSAGES);\n\n                roundTripMessages(dataHandler, pingPublication, pongSubscription, NUMBER_OF_MESSAGES);\n\n                System.out.println(\"Histogram of RTT latencies in microseconds.\");\n                HISTOGRAM.outputPercentileDistribution(System.out, 1000.0);\n            }\n            while (barrier.await());\n        }\n    }\n\n    private static Thread startPong(final Aeron aeron)\n    {\n        return new Thread(() ->\n        {\n            System.out.println(\"Subscribing Ping at \" + PING_CHANNEL + \" on stream id \" + PING_STREAM_ID);\n            System.out.println(\"Publishing Pong at \" + PONG_CHANNEL + \" on stream id \" + PONG_STREAM_ID);\n\n            try (Subscription pingSubscription = aeron.addSubscription(PING_CHANNEL, PING_STREAM_ID);\n                Publication pongPublication = EXCLUSIVE_PUBLICATIONS ?\n                    aeron.addExclusivePublication(PONG_CHANNEL, PONG_STREAM_ID) :\n                    aeron.addPublication(PONG_CHANNEL, PONG_STREAM_ID))\n            {\n                final BufferClaim bufferClaim = new BufferClaim();\n                final FragmentHandler fragmentHandler = (buffer, offset, length, header) ->\n                    pingHandler(bufferClaim, pongPublication, buffer, offset, length, header);\n\n                while (RUNNING.get())\n                {\n                    PING_HANDLER_IDLE_STRATEGY.idle(pingSubscription.poll(fragmentHandler, FRAME_COUNT_LIMIT));\n                }\n\n                System.out.println(\"Shutting down...\");\n            }\n        });\n    }\n\n    private static void roundTripMessages(\n        final FragmentHandler fragmentHandler,\n        final Publication pingPublication,\n        final Subscription pongSubscription,\n        final long numMessages)\n    {\n        while (!pongSubscription.isConnected())\n        {\n            Thread.yield();\n        }\n\n        final Image image = pongSubscription.imageAtIndex(0);\n\n        for (long i = 0; i < numMessages; i++)\n        {\n            long offeredPosition;\n\n            do\n            {\n                OFFER_BUFFER.putLong(0, System.nanoTime());\n            }\n            while ((offeredPosition = pingPublication.offer(OFFER_BUFFER, 0, MESSAGE_LENGTH, null)) < 0L);\n\n            while (image.position() < offeredPosition)\n            {\n                final int fragments = image.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n                PONG_HANDLER_IDLE_STRATEGY.idle(fragments);\n            }\n        }\n    }\n\n    private static void pongHandler(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final long pingTimestamp = buffer.getLong(offset);\n        final long rttNs = System.nanoTime() - pingTimestamp;\n\n        HISTOGRAM.recordValue(rttNs);\n    }\n\n    private static void availablePongImageHandler(final Image image)\n    {\n        final Subscription subscription = image.subscription();\n        if (PONG_STREAM_ID == subscription.streamId() && PONG_CHANNEL.equals(subscription.channel()))\n        {\n            PONG_IMAGE_LATCH.countDown();\n        }\n    }\n\n    static void pingHandler(\n        final BufferClaim bufferClaim,\n        final Publication pongPublication,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        PING_HANDLER_IDLE_STRATEGY.reset();\n        while (pongPublication.tryClaim(length, bufferClaim) <= 0)\n        {\n            PING_HANDLER_IDLE_STRATEGY.idle();\n        }\n\n        bufferClaim\n            .flags(header.flags())\n            .putBytes(buffer, offset, length)\n            .commit();\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/EmbeddedThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport static io.aeron.samples.SamplesUtil.rateReporterHandler;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport io.aeron.Aeron;\nimport io.aeron.ConcurrentPublication;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.console.ContinueBarrier;\n\n/**\n * Throughput test using {@link ConcurrentPublication#offer(DirectBuffer, int, int)} over UDP transport.\n */\npublic class EmbeddedThroughput\n{\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final long LINGER_TIMEOUT_MS = SampleConfiguration.LINGER_TIMEOUT_MS;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n\n    private static final UnsafeBuffer OFFER_BUFFER = new UnsafeBuffer(\n        BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH));\n\n    private static volatile boolean printingActive = true;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread is interrupted during linger.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        loadPropertiesFiles(args);\n\n        final RateReporter reporter = new RateReporter(TimeUnit.SECONDS.toNanos(1), EmbeddedThroughput::printRate);\n        final ExecutorService executor = Executors.newFixedThreadPool(2);\n        final AtomicBoolean running = new AtomicBoolean(true);\n\n        try (MediaDriver mediaDriver = MediaDriver.launch();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID);\n            Publication publication = aeron.addPublication(CHANNEL, STREAM_ID))\n        {\n            executor.execute(reporter);\n            executor.execute(() -> SamplesUtil.subscriberLoop(\n                rateReporterHandler(reporter), FRAGMENT_COUNT_LIMIT, running).accept(subscription));\n\n            final ContinueBarrier barrier = new ContinueBarrier(\"Execute again?\");\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n\n            do\n            {\n                System.out.format(\n                    \"%nStreaming %,d messages of payload length %d bytes to %s on stream id %d%n\",\n                    NUMBER_OF_MESSAGES, MESSAGE_LENGTH, CHANNEL, STREAM_ID);\n\n                printingActive = true;\n\n                long backPressureCount = 0;\n                for (long i = 0; i < NUMBER_OF_MESSAGES; i++)\n                {\n                    OFFER_BUFFER.putLong(0, i);\n\n                    idleStrategy.reset();\n                    while (publication.offer(OFFER_BUFFER, 0, MESSAGE_LENGTH, null) < 0)\n                    {\n                        backPressureCount++;\n                        idleStrategy.idle();\n                    }\n                }\n\n                System.out.println(\n                    \"Done streaming. backPressureRatio=\" + ((double)backPressureCount / NUMBER_OF_MESSAGES));\n\n                if (LINGER_TIMEOUT_MS > 0)\n                {\n                    System.out.println(\"Lingering for \" + LINGER_TIMEOUT_MS + \" milliseconds...\");\n                    Thread.sleep(LINGER_TIMEOUT_MS);\n                }\n\n                printingActive = false;\n            }\n            while (barrier.await());\n\n            running.set(false);\n            reporter.halt();\n            executor.shutdown();\n        }\n    }\n\n    private static void printRate(\n        final double messagesPerSec, final double bytesPerSec, final long totalFragments, final long totalBytes)\n    {\n        if (printingActive)\n        {\n            System.out.format(\n                \"%.04g msgs/sec, %.04g bytes/sec, totals %d messages %d MB payloads%n\",\n                messagesPerSec, bytesPerSec, totalFragments, totalBytes / (1024 * 1024));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/ErrorStat.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.CncFileDescriptor;\nimport io.aeron.CommonContext;\nimport org.agrona.IoUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.errors.ErrorLogReader;\n\nimport java.io.File;\nimport java.nio.MappedByteBuffer;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.function.Function;\n\n/**\n * Application to print out errors recorded in the command-and-control (CnC) file is maintained by media driver in\n * shared memory. This application reads the CnC file and prints the distinct errors. Layout of the CnC file is\n * described in {@link CncFileDescriptor}.\n */\npublic class ErrorStat\n{\n    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSSZ\");\n\n    private static final String ERROR_FILE_NAME_PROP = \"aeron.samples.error.file.name\";\n    private static final String ERROR_FILE_NAME;\n\n    private static final String ERROR_FILE_OFFSET_PROP = \"aeron.samples.error.file.offset\";\n    private static final int ERROR_FILE_OFFSET;\n\n    static\n    {\n        ERROR_FILE_NAME = System.getProperty(ERROR_FILE_NAME_PROP);\n        ERROR_FILE_OFFSET = Integer.parseInt(System.getProperty(ERROR_FILE_OFFSET_PROP, \"0\"));\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        final MappedByteBuffer errorMmap;\n        final Function<MappedByteBuffer, AtomicBuffer> mmapToErrorBuffer;\n\n        if (null != ERROR_FILE_NAME)\n        {\n            final File errorFile = new File(ERROR_FILE_NAME);\n            System.out.println(\"Error file \" + errorFile);\n            errorMmap = SamplesUtil.mapExistingFileReadOnly(errorFile);\n            mmapToErrorBuffer = (mmap) -> new UnsafeBuffer(\n                mmap, ERROR_FILE_OFFSET, errorMmap.capacity() - ERROR_FILE_OFFSET);\n        }\n        else\n        {\n            final File cncFile = CommonContext.newDefaultCncFile();\n            System.out.println(\"Command `n Control file \" + cncFile);\n            errorMmap = SamplesUtil.mapExistingFileReadOnly(cncFile);\n            mmapToErrorBuffer = CommonContext::errorLogBuffer;\n        }\n\n        try\n        {\n            final AtomicBuffer buffer = mmapToErrorBuffer.apply(errorMmap);\n            final int distinctErrorCount = ErrorLogReader.read(buffer, ErrorStat::accept);\n            System.out.println();\n            System.out.println(distinctErrorCount + \" distinct errors observed.\");\n        }\n        finally\n        {\n            IoUtil.unmap(errorMmap);\n        }\n    }\n\n    private static void accept(\n        final int observationCount,\n        final long firstObservationTimestamp,\n        final long lastObservationTimestamp,\n        final String encodedException)\n    {\n        final String fromDate = DATE_FORMAT.format(new Date(firstObservationTimestamp));\n        final String toDate = DATE_FORMAT.format(new Date(lastObservationTimestamp));\n\n        System.out.println();\n        System.out.println(observationCount + \" observations from \" + fromDate + \" to \" + toDate + \" for:\");\n        System.out.println(encodedException);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/FileReceiver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.SleepingMillisIdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Receives files in chunks and saves them in a directory provided as the first command line option or the\n * temporary directory if no command line arguments are provided.\n * <p>\n * Protocol is to receive a {@code file-create} followed by 1 or more {@code file-chunk} messages that are all\n * linked via the correlation id. Messages are encoded in {@link java.nio.ByteOrder#LITTLE_ENDIAN}.\n * <p>\n * The chunk size if best determined by {@link io.aeron.Publication#maxPayloadLength()} minus header for the chunk.\n * <p>\n * <b>file-create</b>\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Version                              |\n *  +---------------------------------------------------------------+\n *  |                      Message Type = 1                         |\n *  +---------------------------------------------------------------+\n *  |                       Correlation ID                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        File Length                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                      File Name Length                         |\n *  +---------------------------------------------------------------+\n *  |                         File Name                            ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n * <b>file-chunk</b>\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Version                              |\n *  +---------------------------------------------------------------+\n *  |                      Message Type = 2                         |\n *  +---------------------------------------------------------------+\n *  |                       Correlation ID                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Chunk Offset                           |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Chunk Length                           |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Chunk Payload                         ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre> * @see FileSender\n */\npublic class FileReceiver\n{\n    static final int VERSION = 0;\n    static final int FILE_CREATE_TYPE = 1;\n    static final int FILE_CHUNK_TYPE = 2;\n\n    static final int VERSION_OFFSET = 0;\n    static final int TYPE_OFFSET = VERSION_OFFSET + SIZE_OF_INT;\n    static final int CORRELATION_ID_OFFSET = TYPE_OFFSET + SIZE_OF_INT;\n    static final int FILE_LENGTH_OFFSET = CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n\n    static final int FILE_NAME_OFFSET = FILE_LENGTH_OFFSET + SIZE_OF_LONG;\n\n    static final int CHUNK_OFFSET_OFFSET = CORRELATION_ID_OFFSET + SIZE_OF_LONG;\n    static final int CHUNK_LENGTH_OFFSET = CHUNK_OFFSET_OFFSET + SIZE_OF_LONG;\n    static final int CHUNK_PAYLOAD_OFFSET = CHUNK_LENGTH_OFFSET + SIZE_OF_LONG;\n\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n    private static final int FRAGMENT_LIMIT = 10;\n\n    private final File storageDir;\n    private final Subscription subscription;\n    private final FragmentAssembler assembler = new FragmentAssembler(this::onFragment);\n    private final Long2ObjectHashMap<UnsafeBuffer> fileSessionByIdMap = new Long2ObjectHashMap<>();\n\n    FileReceiver(final File storageDir, final Subscription subscription)\n    {\n        this.storageDir = storageDir;\n        this.subscription = subscription;\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        final File storageDir;\n        if (args.length > 1)\n        {\n            storageDir = new File(args[0]);\n            if (!storageDir.isDirectory())\n            {\n                System.out.println(args[0] + \" is not a directory\");\n                System.exit(1);\n            }\n        }\n        else\n        {\n            storageDir = new File(SystemUtil.tmpDirName());\n        }\n\n        System.out.println(\"Files stored to \" + storageDir.getAbsolutePath());\n\n        final IdleStrategy idleStrategy = new SleepingMillisIdleStrategy(1);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver mediaDriver = MediaDriver.launch(new MediaDriver.Context().terminationHook(barrier::signalAll));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID))\n        {\n            System.out.println(\"Receiving from \" + CHANNEL + \" on stream id \" + STREAM_ID);\n            final FileReceiver fileReceiver = new FileReceiver(storageDir, subscription);\n\n            while (running.get())\n            {\n                idleStrategy.idle(fileReceiver.doWork());\n            }\n        }\n    }\n\n    private void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final int version = buffer.getInt(offset + VERSION_OFFSET, LITTLE_ENDIAN);\n        if (VERSION != version)\n        {\n            throw new IllegalArgumentException(\"unsupported version \" + version + \" expected \" + VERSION);\n        }\n\n        final int messageType = buffer.getInt(offset + TYPE_OFFSET, LITTLE_ENDIAN);\n        switch (messageType)\n        {\n            case FILE_CREATE_TYPE:\n                createFile(\n                    buffer.getLong(offset + CORRELATION_ID_OFFSET, LITTLE_ENDIAN),\n                    buffer.getLong(offset + FILE_LENGTH_OFFSET, LITTLE_ENDIAN),\n                    buffer.getStringUtf8(offset + FILE_NAME_OFFSET, LITTLE_ENDIAN));\n                break;\n\n            case FILE_CHUNK_TYPE:\n                fileChunk(\n                    buffer.getLong(offset + CORRELATION_ID_OFFSET, LITTLE_ENDIAN),\n                    buffer.getLong(offset + CHUNK_OFFSET_OFFSET, LITTLE_ENDIAN),\n                    buffer.getLong(offset + CHUNK_LENGTH_OFFSET, LITTLE_ENDIAN),\n                    buffer,\n                    offset);\n                break;\n\n            default:\n                throw new IllegalArgumentException(\"unknown message type: \" + messageType);\n        }\n    }\n\n    private void createFile(final long correlationId, final long length, final String filename)\n    {\n        if (fileSessionByIdMap.containsKey(correlationId))\n        {\n            throw new IllegalStateException(\"correlationId is in use: \" + correlationId);\n        }\n\n        final File file = new File(storageDir, filename);\n        if (file.exists() && !file.delete())\n        {\n            throw new IllegalStateException(\"failed to delete existing file: \" + file);\n        }\n\n        if (length == 0)\n        {\n            try\n            {\n                if (!file.createNewFile())\n                {\n                    throw new IllegalStateException(\"failed to create \" + filename);\n                }\n            }\n            catch (final IOException ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n        }\n        else\n        {\n            fileSessionByIdMap.put(correlationId, new UnsafeBuffer(IoUtil.mapNewFile(file, length, false)));\n        }\n    }\n\n    private void fileChunk(\n        final long correlationId,\n        final long chunkOffset,\n        final long chunkLength,\n        final DirectBuffer buffer,\n        final int offset)\n    {\n        final UnsafeBuffer fileBuffer = fileSessionByIdMap.get(correlationId);\n        buffer.getBytes(offset + CHUNK_PAYLOAD_OFFSET, fileBuffer, (int)chunkOffset, (int)chunkLength);\n\n        if ((chunkOffset + chunkLength) >= fileBuffer.capacity())\n        {\n            fileSessionByIdMap.remove(correlationId);\n            IoUtil.unmap(fileBuffer.byteBuffer());\n        }\n    }\n\n    private int doWork()\n    {\n        return subscription.poll(assembler, FRAGMENT_LIMIT);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/FileSender.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Publication;\nimport io.aeron.logbuffer.BufferClaim;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\n\nimport static io.aeron.samples.FileReceiver.*;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * Sends a large file in chunks to a {@link FileReceiver}.\n * <p>\n * Protocol is to send a {@code file-create} followed by 1 or more {@code file-chunk} messages that are all\n * linked via the correlation id. Messages are encoded in {@link java.nio.ByteOrder#LITTLE_ENDIAN}.\n * <p>\n * The chunk size if best determined by {@link io.aeron.Publication#maxPayloadLength()} minus header for the chunk.\n * <p>\n * <b>file-create</b>\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Version                              |\n *  +---------------------------------------------------------------+\n *  |                      Message Type = 1                         |\n *  +---------------------------------------------------------------+\n *  |                       Correlation ID                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        File Length                            |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                      File Name Length                         |\n *  +---------------------------------------------------------------+\n *  |                         File Name                            ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n * <b>file-chunk</b>\n * <pre>\n *   0                   1                   2                   3\n *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *  |                          Version                              |\n *  +---------------------------------------------------------------+\n *  |                      Message Type = 2                         |\n *  +---------------------------------------------------------------+\n *  |                       Correlation ID                          |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Chunk Offset                           |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Chunk Length                           |\n *  |                                                               |\n *  +---------------------------------------------------------------+\n *  |                        Chunk Payload                         ...\n * ...                                                              |\n *  +---------------------------------------------------------------+\n * </pre>\n */\npublic class FileSender\n{\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread is interrupted when sleeping.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        if (args.length != 1)\n        {\n            System.out.println(\"Filename to be sent must be supplied as a command line argument\");\n            System.exit(1);\n        }\n\n        try (Aeron aeron = Aeron.connect();\n            Publication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n        {\n            while (!publication.isConnected())\n            {\n                Thread.sleep(1);\n            }\n\n            final File file = new File(args[0]);\n            final UnsafeBuffer buffer = new UnsafeBuffer(IoUtil.mapExistingFile(file, \"sending\"));\n            final long correlationId = aeron.nextCorrelationId();\n\n            sendFileCreate(publication, correlationId, buffer.capacity(), file.getName());\n            streamChunks(publication, correlationId, buffer);\n        }\n    }\n\n    private static void sendFileCreate(\n        final Publication publication, final long correlationId, final int length, final String filename)\n    {\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n\n        buffer.putInt(VERSION_OFFSET, VERSION, LITTLE_ENDIAN);\n        buffer.putInt(TYPE_OFFSET, FILE_CREATE_TYPE, LITTLE_ENDIAN);\n        buffer.putLong(CORRELATION_ID_OFFSET, correlationId, LITTLE_ENDIAN);\n        buffer.putLong(FILE_LENGTH_OFFSET, length, LITTLE_ENDIAN);\n\n        final int msgLength = FILE_NAME_OFFSET + buffer.putStringUtf8(FILE_NAME_OFFSET, filename);\n\n        long position;\n        while ((position = publication.offer(buffer, 0, msgLength)) < 0)\n        {\n            checkResult(position);\n            Thread.yield();\n        }\n    }\n\n    private static void streamChunks(final Publication publication, final long correlationId, final UnsafeBuffer buffer)\n    {\n        final BufferClaim bufferClaim = new BufferClaim();\n        final int fileLength = buffer.capacity();\n        final int maxChunkLength = publication.maxPayloadLength() - CHUNK_PAYLOAD_OFFSET;\n        int chunkOffset = 0;\n\n        while (chunkOffset < fileLength)\n        {\n            final int chunkLength = Math.min(maxChunkLength, fileLength - chunkOffset);\n            sendChunk(publication, bufferClaim, correlationId, buffer, chunkOffset, chunkLength);\n            chunkOffset += chunkLength;\n        }\n    }\n\n    private static void sendChunk(\n        final Publication publication,\n        final BufferClaim bufferClaim,\n        final long correlationId,\n        final UnsafeBuffer fileBuffer,\n        final int chunkOffset,\n        final int chunkLength)\n    {\n        long position;\n        while ((position = publication.tryClaim(CHUNK_PAYLOAD_OFFSET + chunkLength, bufferClaim)) < 0)\n        {\n            checkResult(position);\n            Thread.yield();\n        }\n\n        final MutableDirectBuffer buffer = bufferClaim.buffer();\n        final int offset = bufferClaim.offset();\n\n        buffer.putInt(offset + VERSION_OFFSET, VERSION, LITTLE_ENDIAN);\n        buffer.putInt(offset + TYPE_OFFSET, FILE_CHUNK_TYPE, LITTLE_ENDIAN);\n        buffer.putLong(offset + CORRELATION_ID_OFFSET, correlationId, LITTLE_ENDIAN);\n        buffer.putLong(offset + CHUNK_OFFSET_OFFSET, chunkOffset, LITTLE_ENDIAN);\n        buffer.putLong(offset + CHUNK_LENGTH_OFFSET, chunkLength, LITTLE_ENDIAN);\n        buffer.putBytes(offset + CHUNK_PAYLOAD_OFFSET, fileBuffer, chunkOffset, chunkLength);\n\n        bufferClaim.commit();\n    }\n\n    private static void checkResult(final long position)\n    {\n        if (position == Publication.CLOSED)\n        {\n            throw new IllegalStateException(\"Connection has been closed\");\n        }\n\n        if (position == Publication.NOT_CONNECTED)\n        {\n            throw new IllegalStateException(\"Connection is no longer available\");\n        }\n\n        if (position == Publication.MAX_POSITION_EXCEEDED)\n        {\n            throw new IllegalStateException(\"Publication failed due to max position being reached\");\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/ImageRateReporter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.locks.LockSupport;\n\n/**\n * Report the rate received to an {@link io.aeron.Image} and print to {@link System#out}.\n */\npublic final class ImageRateReporter implements Runnable\n{\n    private final int messageLength;\n    private final AtomicBoolean running;\n    private final ImageRateSubscriber subscriber;\n\n    /**\n     * Construct a reporter for a single image.\n     *\n     * @param messageLength of each message.\n     * @param running       flag to control reporter, so it can be stopped running.\n     * @param subscriber    for the image.\n     */\n    public ImageRateReporter(final int messageLength, final AtomicBoolean running, final ImageRateSubscriber subscriber)\n    {\n        this.messageLength = messageLength;\n        this.running = running;\n        this.subscriber = subscriber;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void run()\n    {\n        long lastTimestampMs = System.currentTimeMillis();\n        long lastTotalBytes = subscriber.totalBytes();\n        final int messageLength = this.messageLength;\n\n        while (running.get())\n        {\n            LockSupport.parkNanos(1_000_000_000);\n\n            final long newTimestampMs = System.currentTimeMillis();\n            final long newTotalBytes = subscriber.totalBytes();\n\n            final long durationMs = newTimestampMs - lastTimestampMs;\n            final long bytesTransferred = newTotalBytes - lastTotalBytes;\n\n            System.out.format(\n                \"Duration %dms - %,d messages - %,d payload bytes%n\",\n                durationMs, bytesTransferred / messageLength, bytesTransferred);\n\n            lastTimestampMs = newTimestampMs;\n            lastTotalBytes = newTotalBytes;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/ImageRateSubscriber.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.*;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicLongFieldUpdater;\n\nclass ImageRateSubscriberLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nclass ImageRateSubscriberValues extends ImageRateSubscriberLhsPadding\n{\n    static final AtomicLongFieldUpdater<ImageRateSubscriberValues> TOTAL_BYTES_UPDATER =\n        AtomicLongFieldUpdater.newUpdater(ImageRateSubscriberValues.class, \"totalBytes\");\n\n    volatile long totalBytes;\n}\n\nclass ImageRateSubscriberRhsPadding extends ImageRateSubscriberValues\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\n/**\n * {@link Runnable} which picks up a single {@link Image} from a {@link Subscription} and reports the rate of\n * consumption.\n */\npublic final class ImageRateSubscriber extends ImageRateSubscriberRhsPadding implements Runnable\n{\n    private final int fragmentLimit;\n    private final AtomicBoolean running;\n    private final Subscription subscription;\n\n    /**\n     * Construct the rate subscriber over a {@link Subscription}.\n     *\n     * @param fragmentLimit per poll operation.\n     * @param running       atomic flag to indicate if it should keep running.\n     * @param subscription  to pick up the {@link Image} with.\n     */\n    public ImageRateSubscriber(final int fragmentLimit, final AtomicBoolean running, final Subscription subscription)\n    {\n        this.fragmentLimit = fragmentLimit;\n        this.running = running;\n        this.subscription = subscription;\n    }\n\n    /**\n     * Total bytes consumed.\n     *\n     * @return total bytes consumed.\n     */\n    public long totalBytes()\n    {\n        return totalBytes;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void run()\n    {\n        while (!subscription.isConnected())\n        {\n            Thread.yield();\n        }\n\n        final Image image = subscription.images().get(0);\n        final ImageFragmentAssembler fragmentAssembler = new ImageFragmentAssembler(this::onFragment);\n        final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n        final int fragmentLimit = this.fragmentLimit;\n\n        long failedPolls = 0;\n        long successfulPolls = 0;\n\n        while (running.get())\n        {\n            final int fragmentsRead = image.poll(fragmentAssembler, fragmentLimit);\n            if (0 == fragmentsRead)\n            {\n                ++failedPolls;\n            }\n            else\n            {\n                ++successfulPolls;\n            }\n\n            idleStrategy.idle(fragmentsRead);\n        }\n\n        final double failureRatio = failedPolls / (double)(successfulPolls + failedPolls);\n        System.out.format(\"Subscriber poll failure ratio: %f%n\", failureRatio);\n    }\n\n    private void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        TOTAL_BYTES_UPDATER.lazySet(this, totalBytes + length);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/LogInspector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.LogBuffers;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.PrintStream;\nimport java.util.Date;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static java.lang.Math.min;\n\n/**\n * Command line utility for inspecting a log buffer to see what terms and messages it contains.\n */\npublic class LogInspector\n{\n    /**\n     * Data format property name for fragments which can be ASCII or HEX.\n     */\n    public static final String AERON_LOG_DATA_FORMAT_PROP_NAME = \"aeron.log.inspector.data.format\";\n\n    /**\n     * Data format for fragments which can be ASCII or HEX.\n     */\n    public static final String AERON_LOG_DATA_FORMAT = System.getProperty(\n        AERON_LOG_DATA_FORMAT_PROP_NAME, \"hex\").toLowerCase();\n\n    /**\n     * Property name for if the default header should be skipped for output.\n     */\n    public static final String AERON_LOG_SKIP_DEFAULT_HEADER_PROP_NAME = \"aeron.log.inspector.skipDefaultHeader\";\n\n    /**\n     * Should the default header be skipped for output.\n     */\n    public static final boolean AERON_LOG_SKIP_DEFAULT_HEADER =\n        \"true\".equals(System.getProperty(AERON_LOG_SKIP_DEFAULT_HEADER_PROP_NAME));\n\n    /**\n     * Property name for if zeros should be skipped in the output to reduce noise.\n     */\n    public static final String AERON_LOG_SCAN_OVER_ZEROES_PROP_NAME = \"aeron.log.inspector.scanOverZeroes\";\n\n    /**\n     * Should zeros be skipped in the output to reduce noise.\n     */\n    public static final boolean AERON_LOG_SCAN_OVER_ZEROES =\n        \"true\".equals(System.getProperty(AERON_LOG_SCAN_OVER_ZEROES_PROP_NAME));\n\n    private static final char[] HEX_ARRAY = \"0123456789ABCDEF\".toCharArray();\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"methodLength\")\n    public static void main(final String[] args)\n    {\n        final PrintStream out = System.out;\n        if (args.length < 1)\n        {\n            out.println(\"Usage: LogInspector <logFileName> [dump limit in bytes per message]\");\n            return;\n        }\n\n        final String logFileName = args[0];\n        final int messageDumpLimit = args.length >= 2 ? Integer.parseInt(args[1]) : Integer.MAX_VALUE;\n\n        try (LogBuffers logBuffers = new LogBuffers(logFileName))\n        {\n            out.println(\"======================================================================\");\n            out.println(new Date() + \" Inspection dump for \" + logFileName);\n            out.println(\"======================================================================\");\n\n            final DataHeaderFlyweight dataHeaderFlyweight = new DataHeaderFlyweight();\n            final UnsafeBuffer[] termBuffers = logBuffers.duplicateTermBuffers();\n            final int termLength = logBuffers.termLength();\n            final UnsafeBuffer metaDataBuffer = logBuffers.metaDataBuffer();\n            final int initialTermId = initialTermId(metaDataBuffer);\n\n            out.println(\"   Is connected: \" + isConnected(metaDataBuffer));\n            out.println(\"Initial term id: \" + initialTermId);\n            out.println(\"     Term count: \" + activeTermCount(metaDataBuffer));\n            out.println(\"   Active index: \" + indexByTermCount(activeTermCount(metaDataBuffer)));\n            out.println(\"    Term length: \" + termLength);\n            out.println(\"     MTU length: \" + mtuLength(metaDataBuffer));\n            out.println(\"      Page size: \" + pageSize(metaDataBuffer));\n            out.println(\"   EOS position: \" + endOfStreamPosition(metaDataBuffer));\n            out.println();\n\n            if (!AERON_LOG_SKIP_DEFAULT_HEADER)\n            {\n                dataHeaderFlyweight.wrap(defaultFrameHeader(metaDataBuffer));\n                out.println(\"default \" + dataHeaderFlyweight);\n            }\n            out.println();\n\n            for (int i = 0; i < PARTITION_COUNT; i++)\n            {\n                final long rawTail = rawTailVolatile(metaDataBuffer, i);\n                final long termOffset = rawTail & 0xFFFF_FFFFL;\n                final int termId = termId(rawTail);\n                final int offset = (int)Math.min(termOffset, termLength);\n                final int positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n                final long position = computePosition(termId, offset, positionBitsToShift, initialTermId);\n\n                out.println(\"Index \" + i + \" Term Meta Data\" +\n                    \" termOffset=\" + termOffset +\n                    \" termId=\" + termId +\n                    \" rawTail=\" + rawTail +\n                    \" position=\" + position);\n            }\n\n            for (int i = 0; i < PARTITION_COUNT; i++)\n            {\n                out.println();\n                out.println(\"======================================================================\");\n                out.println(\"Index \" + i + \" Term Data\");\n                out.println();\n\n                final UnsafeBuffer termBuffer = termBuffers[i];\n                int offset = 0;\n                do\n                {\n                    dataHeaderFlyweight.wrap(termBuffer, offset, termLength - offset);\n                    out.println(offset + \": \" + dataHeaderFlyweight);\n\n                    final int frameLength = dataHeaderFlyweight.frameLength();\n                    if (frameLength < DataHeaderFlyweight.HEADER_LENGTH)\n                    {\n                        if (0 == frameLength && AERON_LOG_SCAN_OVER_ZEROES)\n                        {\n                            offset += FrameDescriptor.FRAME_ALIGNMENT;\n                            continue;\n                        }\n\n                        try\n                        {\n                            final int limit = min(termLength - (offset + HEADER_LENGTH), messageDumpLimit);\n                            out.println(formatBytes(termBuffer, offset + HEADER_LENGTH, limit));\n                        }\n                        catch (final Exception ex)\n                        {\n                            System.err.println(\"frameLength=\" + frameLength + \" offset=\" + offset);\n                            ex.printStackTrace();\n                        }\n\n                        break;\n                    }\n\n                    final int limit = min(frameLength - HEADER_LENGTH, messageDumpLimit);\n                    out.println(formatBytes(termBuffer, offset + HEADER_LENGTH, limit));\n\n                    offset += BitUtil.align(frameLength, FrameDescriptor.FRAME_ALIGNMENT);\n                }\n                while (offset < termLength);\n            }\n        }\n    }\n\n    /**\n     * Format bytes in a buffer to a char array.\n     *\n     * @param buffer containing the bytes to be formatted.\n     * @param offset in the buffer at which the bytes begin.\n     * @param length of the bytes in the buffer.\n     * @return a char array of the formatted bytes.\n     */\n    public static char[] formatBytes(final DirectBuffer buffer, final int offset, final int length)\n    {\n        switch (AERON_LOG_DATA_FORMAT)\n        {\n            case \"us-ascii\":\n            case \"us_ascii\":\n            case \"ascii\":\n                return bytesToAscii(buffer, offset, length);\n\n            default:\n                return bytesToHex(buffer, offset, length);\n        }\n    }\n\n    private static char[] bytesToAscii(final DirectBuffer buffer, final int offset, final int length)\n    {\n        final char[] chars = new char[length];\n\n        for (int i = 0; i < length; i++)\n        {\n            byte b = buffer.getByte(offset + i);\n\n            if (b < 0)\n            {\n                b = 0;\n            }\n\n            chars[i] = (char)b;\n        }\n\n        return chars;\n    }\n\n    /**\n     * Format bytes to HEX for printing.\n     *\n     * @param buffer containing the bytes.\n     * @param offset in the buffer at which the bytes begin.\n     * @param length of the bytes in the buffer.\n     * @return a char array of the formatted bytes in HEX.\n     */\n    public static char[] bytesToHex(final DirectBuffer buffer, final int offset, final int length)\n    {\n        final char[] chars = new char[length * 2];\n\n        for (int i = 0; i < length; i++)\n        {\n            final int b = buffer.getByte(offset + i) & 0xFF;\n\n            chars[i * 2] = HEX_ARRAY[b >>> 4];\n            chars[i * 2 + 1] = HEX_ARRAY[b & 0x0F];\n        }\n\n        return chars;\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/LossStat.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.driver.reports.LossReportReader;\nimport io.aeron.driver.reports.LossReportUtil;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.nio.MappedByteBuffer;\n\nimport static io.aeron.CommonContext.AERON_DIR_PROP_DEFAULT;\nimport static io.aeron.CommonContext.AERON_DIR_PROP_NAME;\nimport static java.lang.System.getProperty;\n\n/**\n * Application that prints a report of loss observed by stream to {@link System#out}.\n */\npublic class LossStat\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        final String aeronDirectoryName = getProperty(AERON_DIR_PROP_NAME, AERON_DIR_PROP_DEFAULT);\n        final File lossReportFile = LossReportUtil.file(aeronDirectoryName);\n\n        if (!lossReportFile.exists())\n        {\n            System.err.print(\"Loss report does not exist: \" + lossReportFile);\n            System.exit(1);\n        }\n\n        final MappedByteBuffer mappedByteBuffer = SamplesUtil.mapExistingFileReadOnly(lossReportFile);\n        final AtomicBuffer buffer = new UnsafeBuffer(mappedByteBuffer);\n\n        System.out.println(LossReportReader.LOSS_REPORT_CSV_HEADER);\n        final int entriesRead = LossReportReader.read(buffer, LossReportReader.defaultEntryConsumer(System.out));\n        System.out.println(entriesRead + \" loss entries\");\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/LowLatencyMediaDriver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport org.agrona.concurrent.BusySpinIdleStrategy;\nimport org.agrona.concurrent.NoOpIdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Sample setup for a {@link MediaDriver} that is configured for low latency communications. This configuration\n * requires sufficient CPU resource to delivery low latency performance, i.e. 3 active polling threads.\n */\npublic class LowLatencyMediaDriver\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process which will be used for loading properties files.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .termBufferSparseFile(false)\n            .useWindowsHighResTimer(true)\n            .threadingMode(ThreadingMode.DEDICATED)\n            .conductorIdleStrategy(BusySpinIdleStrategy.INSTANCE)\n            .receiverIdleStrategy(NoOpIdleStrategy.INSTANCE)\n            .senderIdleStrategy(NoOpIdleStrategy.INSTANCE);\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            MediaDriver ignore = MediaDriver.launch(ctx.terminationHook(barrier::signalAll)))\n        {\n            barrier.await();\n\n            System.out.println(\"Shutdown Driver...\");\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/MultiplePublishersWithFragmentation.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n * Copyright 2015 Kaazing Corporation\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Publication;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\n/**\n * A publisher application with multiple publications which send fragmented messages to a channel and two different\n * stream IDs. The default STREAM_ID and CHANNEL are configured in {@link SampleConfiguration}.\n * <p>\n * The default channel and stream IDs can be changed by setting Java system properties at the command line, e.g.:\n * {@code -Daeron.sample.channel=aeron:udp?endpoint=localhost:5555 -Daeron.sample.streamId=20}\n */\npublic class MultiplePublishersWithFragmentation\n{\n    private static final int STREAM_ID_1 = SampleConfiguration.STREAM_ID;\n    private static final int STREAM_ID_2 = SampleConfiguration.STREAM_ID + 1;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n    private static final UnsafeBuffer BUFFER_1 = new UnsafeBuffer(\n        BufferUtil.allocateDirectAligned(10000, BitUtil.CACHE_LINE_LENGTH));\n    private static final UnsafeBuffer BUFFER_2 = new UnsafeBuffer(\n        BufferUtil.allocateDirectAligned(9000, BitUtil.CACHE_LINE_LENGTH));\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        System.out.println(\n            \"Publishing to \" + CHANNEL + \" on stream id \" + STREAM_ID_1 + \" and stream id \" + STREAM_ID_2);\n\n        try (Aeron aeron = Aeron.connect();\n            Publication publication1 = aeron.addPublication(CHANNEL, STREAM_ID_1);\n            Publication publication2 = aeron.addPublication(CHANNEL, STREAM_ID_2))\n        {\n            int j = 1;\n            int k = 1;\n            final String message1 = \"Hello World! \" + j;\n            BUFFER_1.putBytes(0, message1.getBytes());\n            final String message2 = \"Hello World! \" + k;\n            BUFFER_2.putBytes(0, message2.getBytes());\n\n            while (j <= 5000 || k <= 5000)\n            {\n                boolean offerStatus1 = false;\n                boolean offerStatus2 = false;\n                long result1;\n                long result2;\n\n                while (!(offerStatus1 || offerStatus2))\n                {\n                    if (j <= 5000)\n                    {\n                        result1 = publication1.offer(BUFFER_1, 0, BUFFER_1.capacity());\n                        if (result1 > 0)\n                        {\n                            j++;\n                            offerStatus1 = true;\n                            System.out.println(\"Successfully sent data on stream id \" +\n                                STREAM_ID_1 + \" and data length \" + BUFFER_1.capacity() + \" at offset \" + result1);\n                        }\n                        else\n                        {\n                            if (result1 == Publication.BACK_PRESSURED)\n                            {\n                                System.out.println(\" Offer failed due to back pressure for stream id \" + STREAM_ID_1);\n                            }\n                            else if (result1 == Publication.NOT_CONNECTED)\n                            {\n                                System.out.println(\" Offer failed because publisher is not yet \" +\n                                    \"connected to subscriber for stream id \" + STREAM_ID_1);\n                            }\n                            else\n                            {\n                                System.out.println(\" Offer failed due to unexpected reason: \" + result1);\n                            }\n\n                            offerStatus1 = false;\n                        }\n                    }\n\n                    if (k <= 5000)\n                    {\n                        result2 = publication2.offer(BUFFER_2, 0, BUFFER_2.capacity());\n                        if (result2 > 0)\n                        {\n                            k++;\n                            offerStatus2 = true;\n                            System.out.println(\"Successfully sent data on stream id \" + STREAM_ID_2 +\n                                \" and data length \" + BUFFER_2.capacity() + \" at offset \" + result2);\n                        }\n                        else\n                        {\n                            if (result2 == Publication.BACK_PRESSURED)\n                            {\n                                System.out.println(\" Offer failed because publisher is not yet \" +\n                                    \"connected to subscriber for stream id \" + STREAM_ID_2);\n                            }\n                            else if (result2 == Publication.NOT_CONNECTED)\n                            {\n                                System.out.println(\n                                    \"Offer failed - publisher is not yet connected to subscriber\" + STREAM_ID_2);\n                            }\n                            else\n                            {\n                                System.out.println(\"Offer failed due to unexpected reason: \" + result2);\n                            }\n                            offerStatus2 = false;\n                        }\n                    }\n                }\n            }\n\n            System.out.println(\"Done sending total messages for stream id \" +\n                STREAM_ID_1 + \" = \" + (j - 1) + \" and stream id \" + STREAM_ID_2 + \" = \" + (k - 1));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/MultipleSubscribersWithFragmentAssembly.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n * Copyright 2015 Kaazing Corporation\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.logbuffer.FragmentHandler;\nimport org.agrona.concurrent.BackoffIdleStrategy;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * A subscriber application with two subscriptions which can receive fragmented messages.\n * <p>\n * Creates two subscriptions on a given channel subscribed to two different stream IDs.\n * The default STREAM_ID and CHANNEL are configured in {@link SampleConfiguration}. The default\n * channel and stream IDs can be changed by setting Java system properties at the command line, e.g.:\n * {@code -Daeron.sample.channel=aeron:udp?endpoint=localhost:5555 -Daeron.sample.streamId=20}\n */\npublic class MultipleSubscribersWithFragmentAssembly\n{\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final int STREAM_ID_1 = SampleConfiguration.STREAM_ID;\n    private static final int STREAM_ID_2 = SampleConfiguration.STREAM_ID + 1;\n\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        System.out.format(\"Subscribing to %s on stream ID %d and stream ID %d%n\",\n            CHANNEL, STREAM_ID_1, STREAM_ID_2);\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .availableImageHandler(MultipleSubscribersWithFragmentAssembly::eventAvailableImage)\n            .unavailableImageHandler(MultipleSubscribersWithFragmentAssembly::eventUnavailableImage);\n\n        final FragmentAssembler assembler1 = new FragmentAssembler(reassembledMessage1(STREAM_ID_1));\n        final FragmentAssembler assembler2 = new FragmentAssembler(reassembledMessage2(STREAM_ID_2));\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false));\n            Aeron aeron = Aeron.connect(ctx);\n            Subscription subscription1 = aeron.addSubscription(CHANNEL, STREAM_ID_1);\n            Subscription subscription2 = aeron.addSubscription(CHANNEL, STREAM_ID_2))\n        {\n            final IdleStrategy idleStrategy = new BackoffIdleStrategy(\n                100, 10, TimeUnit.MICROSECONDS.toNanos(1), TimeUnit.MICROSECONDS.toNanos(100));\n\n            int idleCount = 0;\n\n            while (running.get())\n            {\n                final int fragmentsRead1 = subscription1.poll(assembler1, FRAGMENT_COUNT_LIMIT);\n                final int fragmentsRead2 = subscription2.poll(assembler2, FRAGMENT_COUNT_LIMIT);\n\n                if ((fragmentsRead1 + fragmentsRead2) == 0)\n                {\n                    idleStrategy.idle(idleCount++);\n                }\n                else\n                {\n                    idleCount = 0;\n                }\n            }\n\n            System.out.println(\"Shutting down...\");\n        }\n    }\n\n    /**\n     * Print the information for an available image to stdout.\n     *\n     * @param image that has been created.\n     */\n    public static void eventAvailableImage(final Image image)\n    {\n        final Subscription subscription = image.subscription();\n        System.out.format(\n            \"new image on %s streamId %x sessionId %x from %s%n\",\n            subscription.channel(), subscription.streamId(), image.sessionId(), image.sourceIdentity());\n    }\n\n    /**\n     * This handler is called when image is unavailable.\n     *\n     * @param image that has gone inactive.\n     */\n    public static void eventUnavailableImage(final Image image)\n    {\n        final Subscription subscription = image.subscription();\n        System.out.format(\n            \"inactive image on %s streamId %d sessionId %x%n\",\n            subscription.channel(), subscription.streamId(), image.sessionId());\n    }\n\n    /**\n     * Return a reusable, parameterized {@link FragmentHandler} that prints to stdout for the first stream(STREAM).\n     *\n     * @param streamId to show when printing.\n     * @return subscription data handler function that prints the message contents.\n     */\n    public static FragmentHandler reassembledMessage1(final int streamId)\n    {\n        return (buffer, offset, length, header) ->\n        {\n            System.out.format(\n                \"message to stream %d from session %x term id %x term offset %d (%d@%d)%n\",\n                streamId, header.sessionId(), header.termId(), header.termOffset(), length, offset);\n\n            if (length != 10000)\n            {\n                System.out.format(\n                    \"Received message was not assembled properly;\" +\n                    \" received length was %d, but was expecting 10000%n\",\n                    length);\n            }\n        };\n    }\n\n    /**\n     * A reusable, parameterised {@link FragmentHandler} that prints to stdout for the second stream (STREAM + 1).\n     *\n     * @param streamId to show when printing.\n     * @return subscription data handler function that prints the message contents.\n     */\n    public static FragmentHandler reassembledMessage2(final int streamId)\n    {\n        return (buffer, offset, length, header) ->\n        {\n            System.out.format(\n                \"message to stream %d from session %x term id %x term offset %d (%d@%d)%n\",\n                streamId, header.sessionId(), header.termId(), header.termOffset(), length, offset);\n\n            if (length != 9000)\n            {\n                System.out.format(\n                    \"Received message was not assembled properly; received length was %d, but was expecting 9000%n\",\n                    length);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/Ping.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Image;\nimport io.aeron.ImageFragmentAssembler;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.FragmentHandler;\nimport org.HdrHistogram.Histogram;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.BusySpinIdleStrategy;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.console.ContinueBarrier;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Ping component of Ping-Pong latency test recorded to a histogram to capture full distribution.\n * <p>\n * Sends message to {@link Pong} for echoing and records times on return.\n *\n * @see Pong\n */\npublic class Ping\n{\n    private static final int PING_STREAM_ID = SampleConfiguration.PING_STREAM_ID;\n    private static final int PONG_STREAM_ID = SampleConfiguration.PONG_STREAM_ID;\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final long WARMUP_NUMBER_OF_MESSAGES = SampleConfiguration.WARMUP_NUMBER_OF_MESSAGES;\n    private static final int WARMUP_NUMBER_OF_ITERATIONS = SampleConfiguration.WARMUP_NUMBER_OF_ITERATIONS;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final boolean EMBEDDED_MEDIA_DRIVER = SampleConfiguration.EMBEDDED_MEDIA_DRIVER;\n    private static final String PING_CHANNEL = SampleConfiguration.PING_CHANNEL;\n    private static final String PONG_CHANNEL = SampleConfiguration.PONG_CHANNEL;\n    private static final boolean EXCLUSIVE_PUBLICATIONS = SampleConfiguration.EXCLUSIVE_PUBLICATIONS;\n\n    private static final UnsafeBuffer OFFER_BUFFER = new UnsafeBuffer(\n        BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH));\n    private static final Histogram HISTOGRAM = new Histogram(TimeUnit.SECONDS.toNanos(10), 3);\n    private static final CountDownLatch LATCH = new CountDownLatch(1);\n    private static final IdleStrategy POLLING_IDLE_STRATEGY = new BusySpinIdleStrategy();\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread is interrupted.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        try (MediaDriver driver = EMBEDDED_MEDIA_DRIVER ? MediaDriver.launchEmbedded() : null)\n        {\n            final Aeron.Context ctx = new Aeron.Context()\n                .availableImageHandler(Ping::availablePongImageHandler)\n                .unavailableImageHandler(SamplesUtil::printUnavailableImage);\n            final MutableLong receiveCount = new MutableLong();\n            final FragmentHandler fragmentHandler = new ImageFragmentAssembler(\n                (buffer, offset, length, header) -> pongHandler(buffer, offset, receiveCount));\n\n            if (EMBEDDED_MEDIA_DRIVER)\n            {\n                ctx.aeronDirectoryName(driver.aeronDirectoryName());\n            }\n\n            System.out.println(\"Publishing Ping at \" + PING_CHANNEL + \" on stream id \" + PING_STREAM_ID);\n            System.out.println(\"Subscribing Pong at \" + PONG_CHANNEL + \" on stream id \" + PONG_STREAM_ID);\n            System.out.println(\"Message length of \" + MESSAGE_LENGTH + \" bytes\");\n            System.out.println(\"Using exclusive publications \" + EXCLUSIVE_PUBLICATIONS);\n\n            try (Aeron aeron = Aeron.connect(ctx);\n                Subscription subscription = aeron.addSubscription(PONG_CHANNEL, PONG_STREAM_ID);\n                Publication publication = EXCLUSIVE_PUBLICATIONS ?\n                    aeron.addExclusivePublication(PING_CHANNEL, PING_STREAM_ID) :\n                    aeron.addPublication(PING_CHANNEL, PING_STREAM_ID))\n            {\n                System.out.println(\"Waiting for new image from Pong...\");\n                LATCH.await();\n\n                System.out.println(\n                    \"Warming up... \" + WARMUP_NUMBER_OF_ITERATIONS +\n                    \" iterations of \" + WARMUP_NUMBER_OF_MESSAGES + \" messages\");\n\n                for (int i = 0; i < WARMUP_NUMBER_OF_ITERATIONS; i++)\n                {\n                    roundTripMessages(\n                        fragmentHandler, publication, subscription, receiveCount, WARMUP_NUMBER_OF_MESSAGES);\n                    Thread.yield();\n                }\n\n                Thread.sleep(100);\n                final ContinueBarrier barrier = new ContinueBarrier(\"Execute again?\");\n\n                do\n                {\n                    HISTOGRAM.reset();\n                    System.out.println(\"Pinging \" + NUMBER_OF_MESSAGES + \" messages\");\n\n                    roundTripMessages(fragmentHandler, publication, subscription, receiveCount, NUMBER_OF_MESSAGES);\n                    System.out.println(\"Histogram of RTT latencies in microseconds.\");\n\n                    HISTOGRAM.outputPercentileDistribution(System.out, 1000.0);\n                }\n                while (barrier.await());\n            }\n        }\n    }\n\n    private static void roundTripMessages(\n        final FragmentHandler fragmentHandler,\n        final Publication publication,\n        final Subscription subscription,\n        final MutableLong receiveCount,\n        final long count)\n    {\n        while (!subscription.isConnected())\n        {\n            Thread.yield();\n        }\n\n        final Image image = subscription.imageAtIndex(0);\n        receiveCount.set(0);\n\n        for (long i = 0; i < count; i++)\n        {\n            do\n            {\n                OFFER_BUFFER.putLong(0, System.nanoTime());\n            }\n            while (publication.offer(OFFER_BUFFER, 0, MESSAGE_LENGTH, null) < 0L);\n\n            POLLING_IDLE_STRATEGY.reset();\n            while (receiveCount.get() != (i + 1))\n            {\n                final int fragments = image.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n                POLLING_IDLE_STRATEGY.idle(fragments);\n            }\n        }\n    }\n\n    private static void pongHandler(\n        final DirectBuffer buffer,\n        final int offset,\n        final MutableLong receiveCount)\n    {\n        receiveCount.increment();\n        final long pingTimestamp = buffer.getLong(offset);\n        final long rttNs = System.nanoTime() - pingTimestamp;\n\n        HISTOGRAM.recordValue(rttNs);\n    }\n\n    private static void availablePongImageHandler(final Image image)\n    {\n        final Subscription subscription = image.subscription();\n        SamplesUtil.printAvailableImage(image);\n\n        if (PONG_STREAM_ID == subscription.streamId() && PONG_CHANNEL.equals(subscription.channel()))\n        {\n            LATCH.countDown();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/Pong.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Image;\nimport io.aeron.ImageFragmentAssembler;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.FragmentHandler;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.BusySpinIdleStrategy;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Pong component of Ping-Pong.\n * <p>\n * Echoes back messages from {@link Ping}.\n *\n * @see Ping\n */\npublic class Pong\n{\n    private static final int PING_STREAM_ID = SampleConfiguration.PING_STREAM_ID;\n    private static final int PONG_STREAM_ID = SampleConfiguration.PONG_STREAM_ID;\n    private static final int FRAME_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final String PING_CHANNEL = SampleConfiguration.PING_CHANNEL;\n    private static final String PONG_CHANNEL = SampleConfiguration.PONG_CHANNEL;\n    private static final boolean EMBEDDED_MEDIA_DRIVER = SampleConfiguration.EMBEDDED_MEDIA_DRIVER;\n    private static final boolean EXCLUSIVE_PUBLICATIONS = SampleConfiguration.EXCLUSIVE_PUBLICATIONS;\n\n    private static final IdleStrategy PING_HANDLER_IDLE_STRATEGY = new BusySpinIdleStrategy();\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver driver = EMBEDDED_MEDIA_DRIVER ?\n                MediaDriver.launchEmbedded(new MediaDriver.Context().terminationHook(barrier::signalAll)) : null)\n        {\n            final Aeron.Context ctx = new Aeron.Context();\n            ctx.availableImageHandler(SamplesUtil::printAvailableImage);\n            ctx.unavailableImageHandler(SamplesUtil::printUnavailableImage);\n            if (EMBEDDED_MEDIA_DRIVER)\n            {\n                ctx.aeronDirectoryName(driver.aeronDirectoryName());\n            }\n\n            final IdleStrategy idleStrategy = new BusySpinIdleStrategy();\n\n            System.out.println(\"Subscribing Ping at \" + PING_CHANNEL + \" on stream id \" + PING_STREAM_ID);\n            System.out.println(\"Publishing Pong at \" + PONG_CHANNEL + \" on stream id \" + PONG_STREAM_ID);\n            System.out.println(\"Using exclusive publications \" + EXCLUSIVE_PUBLICATIONS);\n\n            try (Aeron aeron = Aeron.connect(ctx);\n                Subscription subscription = aeron.addSubscription(PING_CHANNEL, PING_STREAM_ID);\n                Publication publication = EXCLUSIVE_PUBLICATIONS ?\n                    aeron.addExclusivePublication(PONG_CHANNEL, PONG_STREAM_ID) :\n                    aeron.addPublication(PONG_CHANNEL, PONG_STREAM_ID))\n            {\n                idleStrategy.reset();\n                while (!subscription.isConnected())\n                {\n                    idleStrategy.idle();\n                }\n\n                final Image image = subscription.imageAtIndex(0);\n                final FragmentHandler fragmentHandler = new ImageFragmentAssembler(\n                    (buffer, offset, length, header) ->\n                    pingHandler(publication, buffer, offset, length));\n\n                while (running.get())\n                {\n                    idleStrategy.idle(image.poll(fragmentHandler, FRAME_COUNT_LIMIT));\n                }\n\n                System.out.println(\"Shutting down...\");\n            }\n        }\n    }\n\n    private static void pingHandler(\n        final Publication pongPublication,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length)\n    {\n        PING_HANDLER_IDLE_STRATEGY.reset();\n        while (pongPublication.offer(buffer, offset, length) <= 0)\n        {\n            PING_HANDLER_IDLE_STRATEGY.idle();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/RateReporter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport java.util.concurrent.atomic.AtomicLongFieldUpdater;\nimport java.util.concurrent.locks.LockSupport;\n\nclass RateReporterLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nclass RateReporterValues extends RateReporterLhsPadding\n{\n    static final AtomicLongFieldUpdater<RateReporterValues> TOTAL_BYTES_UPDATER =\n        AtomicLongFieldUpdater.newUpdater(RateReporterValues.class, \"totalBytes\");\n    static final AtomicLongFieldUpdater<RateReporterValues> TOTAL_MESSAGES_UPDATER =\n        AtomicLongFieldUpdater.newUpdater(RateReporterValues.class, \"totalMessages\");\n\n    volatile long totalBytes;\n    volatile long totalMessages;\n}\n\nclass RateReporterRhsPadding extends RateReporterValues\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\n/**\n * Tracker and reporter of throughput rates.\n */\npublic final class RateReporter extends RateReporterRhsPadding implements Runnable\n{\n    /**\n     * Interface for reporting of rate information.\n     */\n    @FunctionalInterface\n    public interface Reporter\n    {\n        /**\n         * Called for a rate report.\n         *\n         * @param messagesPerSec since last report.\n         * @param bytesPerSec    since last report.\n         * @param totalMessages  since beginning of reporting.\n         * @param totalBytes     since beginning of reporting.\n         */\n        void onReport(double messagesPerSec, double bytesPerSec, long totalMessages, long totalBytes);\n    }\n\n    private final long reportIntervalNs;\n    private final long parkNs;\n    private long lastTotalBytes;\n    private long lastTotalMessages;\n    private long lastTimestamp;\n    private volatile boolean running = true;\n    private final Reporter reportingFunc;\n\n    /**\n     * Create a rate reporter with the given report interval in nanoseconds and the reporting function.\n     *\n     * @param reportInterval in nanoseconds.\n     * @param reportingFunc  to call for reporting rates.\n     */\n    public RateReporter(final long reportInterval, final Reporter reportingFunc)\n    {\n        this.reportIntervalNs = reportInterval;\n        this.parkNs = reportInterval;\n        this.reportingFunc = reportingFunc;\n        lastTimestamp = System.nanoTime();\n    }\n\n    /**\n     * Run loop for the rate reporter.\n     */\n    public void run()\n    {\n        do\n        {\n            LockSupport.parkNanos(parkNs);\n\n            final long currentTotalMessages = totalMessages;\n            final long currentTotalBytes = totalBytes;\n            final long currentTimestamp = System.nanoTime();\n\n            final long timeSpanNs = currentTimestamp - lastTimestamp;\n            final double messagesPerSec =\n                ((currentTotalMessages - lastTotalMessages) * (double)reportIntervalNs) / (double)timeSpanNs;\n            final double bytesPerSec =\n                ((currentTotalBytes - lastTotalBytes) * (double)reportIntervalNs) / (double)timeSpanNs;\n\n            reportingFunc.onReport(messagesPerSec, bytesPerSec, currentTotalMessages, currentTotalBytes);\n\n            lastTotalBytes = currentTotalBytes;\n            lastTotalMessages = currentTotalMessages;\n            lastTimestamp = currentTimestamp;\n        }\n        while (running);\n    }\n\n    /**\n     * Signal the run loop to exit. Does not block.\n     */\n    public void halt()\n    {\n        running = false;\n    }\n\n    /**\n     * Notify rate reporter of number of messages and length received, sent, etc.\n     *\n     * @param length received, sent, etc.\n     */\n    public void onMessage(final long length)\n    {\n        TOTAL_BYTES_UPDATER.lazySet(this, totalBytes + length);\n        TOTAL_MESSAGES_UPDATER.lazySet(this, totalMessages + 1);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/RateSubscriber.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static io.aeron.samples.SamplesUtil.rateReporterHandler;\n\n/**\n * Example that displays current throughput rate while receiving data.\n */\npublic class RateSubscriber\n{\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final boolean EMBEDDED_MEDIA_DRIVER = SampleConfiguration.EMBEDDED_MEDIA_DRIVER;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the task is interrupted\n     * @throws ExecutionException   if the {@link Future} has an error.\n     */\n    public static void main(final String[] args) throws InterruptedException, ExecutionException\n    {\n        System.out.println(\"Subscribing to \" + CHANNEL + \" on stream id \" + STREAM_ID);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver driver = EMBEDDED_MEDIA_DRIVER ?\n                MediaDriver.launchEmbedded(new MediaDriver.Context().terminationHook(barrier::signalAll)) : null)\n        {\n            final ExecutorService executor = Executors.newFixedThreadPool(2);\n            final Aeron.Context ctx = new Aeron.Context()\n                .availableImageHandler(SamplesUtil::printAvailableImage)\n                .unavailableImageHandler(SamplesUtil::printUnavailableImage);\n\n            if (EMBEDDED_MEDIA_DRIVER)\n            {\n                ctx.aeronDirectoryName(driver.aeronDirectoryName());\n            }\n\n            final RateReporter reporter = new RateReporter(TimeUnit.SECONDS.toNanos(1), SamplesUtil::printRate);\n\n            try (Aeron aeron = Aeron.connect(ctx);\n                Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID))\n            {\n                executor.submit(() -> SamplesUtil.subscriberLoop(\n                    rateReporterHandler(reporter), FRAGMENT_COUNT_LIMIT, running).accept(subscription));\n                executor.submit(reporter);\n\n                barrier.await();\n\n                System.out.println(\"Shutting down...\");\n\n                executor.shutdown();\n                if (!executor.awaitTermination(5, TimeUnit.SECONDS))\n                {\n                    System.out.println(\"Warning: not all tasks completed promptly\");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/SampleConfiguration.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.driver.Configuration;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.IdleStrategy;\n\n/**\n * Configuration used for samples with defaults which can be overridden by system properties.\n */\n@SuppressWarnings(\"JavadocVariable\")\npublic class SampleConfiguration\n{\n    public static final String CHANNEL_PROP = \"aeron.sample.channel\";\n    public static final String STREAM_ID_PROP = \"aeron.sample.streamId\";\n\n    public static final String PING_CHANNEL_PROP = \"aeron.sample.ping.channel\";\n    public static final String PONG_CHANNEL_PROP = \"aeron.sample.pong.channel\";\n    public static final String PING_STREAM_ID_PROP = \"aeron.sample.ping.streamId\";\n    public static final String PONG_STREAM_ID_PROP = \"aeron.sample.pong.streamId\";\n    public static final String WARMUP_NUMBER_OF_MESSAGES_PROP = \"aeron.sample.warmup.messages\";\n    public static final String WARMUP_NUMBER_OF_ITERATIONS_PROP = \"aeron.sample.warmup.iterations\";\n    public static final String RANDOM_MESSAGE_LENGTH_PROP = \"aeron.sample.randomMessageLength\";\n\n    public static final String FRAME_COUNT_LIMIT_PROP = \"aeron.sample.frameCountLimit\";\n    public static final String MESSAGE_LENGTH_PROP = \"aeron.sample.messageLength\";\n    public static final String NUMBER_OF_MESSAGES_PROP = \"aeron.sample.messages\";\n    public static final String LINGER_TIMEOUT_MS_PROP = \"aeron.sample.lingerTimeout\";\n    public static final String EMBEDDED_MEDIA_DRIVER_PROP = \"aeron.sample.embeddedMediaDriver\";\n    public static final String EXCLUSIVE_PUBLICATIONS_PROP = \"aeron.sample.exclusive.publications\";\n    public static final String IDLE_STRATEGY_PROP = \"aeron.sample.idleStrategy\";\n\n    public static final String CHANNEL;\n    public static final String PING_CHANNEL;\n    public static final String PONG_CHANNEL;\n    public static final String IDLE_STRATEGY_NAME;\n\n    public static final boolean EMBEDDED_MEDIA_DRIVER;\n    public static final boolean RANDOM_MESSAGE_LENGTH;\n    public static final int STREAM_ID;\n    public static final int PING_STREAM_ID;\n    public static final int PONG_STREAM_ID;\n    public static final int FRAGMENT_COUNT_LIMIT;\n    public static final int MESSAGE_LENGTH;\n    public static final int WARMUP_NUMBER_OF_ITERATIONS;\n    public static final long WARMUP_NUMBER_OF_MESSAGES;\n    public static final long NUMBER_OF_MESSAGES;\n    public static final long LINGER_TIMEOUT_MS;\n    public static final boolean EXCLUSIVE_PUBLICATIONS;\n\n    static\n    {\n        CHANNEL = System.getProperty(CHANNEL_PROP, \"aeron:udp?endpoint=localhost:20121\");\n        STREAM_ID = Integer.getInteger(STREAM_ID_PROP, 1001);\n        PING_CHANNEL = System.getProperty(PING_CHANNEL_PROP, \"aeron:udp?endpoint=localhost:20123\");\n        PONG_CHANNEL = System.getProperty(PONG_CHANNEL_PROP, \"aeron:udp?endpoint=localhost:20124\");\n        IDLE_STRATEGY_NAME = System.getProperty(IDLE_STRATEGY_PROP, \"org.agrona.concurrent.BusySpinIdleStrategy\");\n        LINGER_TIMEOUT_MS = Long.getLong(LINGER_TIMEOUT_MS_PROP, 0);\n        PING_STREAM_ID = Integer.getInteger(PING_STREAM_ID_PROP, 1002);\n        PONG_STREAM_ID = Integer.getInteger(PONG_STREAM_ID_PROP, 1003);\n        FRAGMENT_COUNT_LIMIT = Integer.getInteger(FRAME_COUNT_LIMIT_PROP, 10);\n        MESSAGE_LENGTH = SystemUtil.getSizeAsInt(MESSAGE_LENGTH_PROP, 32);\n        RANDOM_MESSAGE_LENGTH = \"true\".equals(System.getProperty(RANDOM_MESSAGE_LENGTH_PROP));\n        NUMBER_OF_MESSAGES = Long.getLong(NUMBER_OF_MESSAGES_PROP, 10_000_000);\n        WARMUP_NUMBER_OF_MESSAGES = Long.getLong(WARMUP_NUMBER_OF_MESSAGES_PROP, 10_000);\n        WARMUP_NUMBER_OF_ITERATIONS = Integer.getInteger(WARMUP_NUMBER_OF_ITERATIONS_PROP, 10);\n        EMBEDDED_MEDIA_DRIVER = \"true\".equals(System.getProperty(EMBEDDED_MEDIA_DRIVER_PROP));\n        EXCLUSIVE_PUBLICATIONS = \"true\".equals(System.getProperty(EXCLUSIVE_PUBLICATIONS_PROP));\n    }\n\n    /**\n     * Create a new {@link IdleStrategy} based on the {@link #IDLE_STRATEGY_NAME}.\n     *\n     * @return a new {@link IdleStrategy} based on the {@link #IDLE_STRATEGY_NAME}.\n     */\n    public static IdleStrategy newIdleStrategy()\n    {\n        return Configuration.agentIdleStrategy(IDLE_STRATEGY_NAME, null);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/SamplesUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.CommonContext;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.protocol.HeaderFlyweight;\nimport org.agrona.DirectBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\n\nimport static io.aeron.CncFileDescriptor.*;\nimport static java.nio.channels.FileChannel.MapMode.READ_ONLY;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\n\n/**\n * Utility functions for the samples.\n */\npublic class SamplesUtil\n{\n    /**\n     * Return a reusable, parametrised event loop that calls a default {@link IdleStrategy} when no messages\n     * are received.\n     *\n     * @param fragmentHandler to be called back for each message.\n     * @param limit           passed to {@link Subscription#poll(FragmentHandler, int)}.\n     * @param running         indication for loop.\n     * @return loop function.\n     */\n    public static Consumer<Subscription> subscriberLoop(\n        final FragmentHandler fragmentHandler, final int limit, final AtomicBoolean running)\n    {\n        return subscriberLoop(fragmentHandler, limit, running, SampleConfiguration.newIdleStrategy());\n    }\n\n    /**\n     * Return a reusable, parametrised event loop that calls and idler when no messages are received.\n     *\n     * @param fragmentHandler to be called back for each message.\n     * @param limit           passed to {@link Subscription#poll(FragmentHandler, int)}.\n     * @param running         indication for loop.\n     * @param idleStrategy    to use for loop.\n     * @return loop function.\n     */\n    public static Consumer<Subscription> subscriberLoop(\n        final FragmentHandler fragmentHandler,\n        final int limit,\n        final AtomicBoolean running,\n        final IdleStrategy idleStrategy)\n    {\n        return\n            (subscription) ->\n            {\n                final FragmentAssembler assembler = new FragmentAssembler(fragmentHandler);\n                while (running.get())\n                {\n                    final int fragmentsRead = subscription.poll(assembler, limit);\n                    idleStrategy.idle(fragmentsRead);\n                }\n            };\n    }\n\n    /**\n     * Return a reusable, parametrised {@link FragmentHandler} that prints to stdout.\n     *\n     * @param streamId to show when printing.\n     * @return subscription data handler function that prints the message contents.\n     */\n    public static FragmentHandler printAsciiMessage(final int streamId)\n    {\n        return (buffer, offset, length, header) ->\n        {\n            final String msg = buffer.getStringWithoutLengthAscii(offset, length);\n            System.out.printf(\n                \"Message to stream %d from session %d (%d@%d) <<%s>>%n\",\n                streamId, header.sessionId(), length, offset, msg);\n        };\n    }\n\n    /**\n     * Return a reusable, parametrised {@link FragmentHandler} that calls into a\n     * {@link RateReporter}.\n     *\n     * @param reporter for the rate.\n     * @return {@link FragmentHandler} that records the rate information.\n     */\n    public static FragmentHandler rateReporterHandler(final RateReporter reporter)\n    {\n        return (buffer, offset, length, header) -> reporter.onMessage(length);\n    }\n\n    /**\n     * Generic error handler that just prints message to stdout.\n     *\n     * @param channel   for the error.\n     * @param streamId  for the error.\n     * @param sessionId for the error, if it has a source.\n     * @param message   indicating what the error was.\n     * @param cause     of the error.\n     */\n    public static void printError(\n        final String channel,\n        final int streamId,\n        final int sessionId,\n        final String message,\n        final HeaderFlyweight cause)\n    {\n        System.out.println(message);\n    }\n\n    /**\n     * Print the rates to stdout.\n     *\n     * @param messagesPerSec being reported.\n     * @param bytesPerSec    being reported.\n     * @param totalMessages  being reported.\n     * @param totalBytes     being reported.\n     */\n    public static void printRate(\n        final double messagesPerSec,\n        final double bytesPerSec,\n        final long totalMessages,\n        final long totalBytes)\n    {\n        System.out.printf(\n            \"%.04g msgs/sec, %.04g payload bytes/sec, totals %d messages %d MB%n\",\n            messagesPerSec, bytesPerSec, totalMessages, totalBytes / (1024 * 1024));\n    }\n\n    /**\n     * Print the information for an available image to stdout.\n     *\n     * @param image that has been created.\n     */\n    public static void printAvailableImage(final Image image)\n    {\n        final Subscription subscription = image.subscription();\n        System.out.printf(\n            \"Available image on %s streamId=%d sessionId=%d mtu=%d term-length=%d from %s%n\",\n            subscription.channel(), subscription.streamId(), image.sessionId(), image.mtuLength(),\n            image.termBufferLength(), image.sourceIdentity());\n    }\n\n    /**\n     * Print the information for an unavailable image to stdout.\n     *\n     * @param image that has gone inactive.\n     */\n    public static void printUnavailableImage(final Image image)\n    {\n        final Subscription subscription = image.subscription();\n        System.out.printf(\n            \"Unavailable image on %s streamId=%d sessionId=%d%n\",\n            subscription.channel(), subscription.streamId(), image.sessionId());\n    }\n\n    /**\n     * Map an existing file as a read only buffer.\n     *\n     * @param location of file to map.\n     * @return the mapped file.\n     */\n    public static MappedByteBuffer mapExistingFileReadOnly(final File location)\n    {\n        if (!location.exists())\n        {\n            final String msg = \"file not found: \" + location.getAbsolutePath();\n            throw new IllegalStateException(msg);\n        }\n\n        MappedByteBuffer mappedByteBuffer = null;\n        try (RandomAccessFile file = new RandomAccessFile(location, \"r\");\n            FileChannel channel = file.getChannel())\n        {\n            mappedByteBuffer = channel.map(READ_ONLY, 0, channel.size());\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n\n        return mappedByteBuffer;\n    }\n\n    /**\n     * Map an existing CnC file.\n     *\n     * @return the {@link CountersReader} over the CnC file.\n     */\n    public static CountersReader mapCounters()\n    {\n        final File cncFile = CommonContext.newDefaultCncFile();\n        System.out.println(\"Command `n Control file \" + cncFile);\n\n        final MappedByteBuffer cncByteBuffer = mapExistingFileReadOnly(cncFile);\n        final DirectBuffer cncMetaData = createMetaDataBuffer(cncByteBuffer);\n        final int cncVersion = cncMetaData.getInt(cncVersionOffset(0));\n\n        checkVersion(cncVersion);\n\n        return new CountersReader(\n            createCountersMetaDataBuffer(cncByteBuffer, cncMetaData),\n            createCountersValuesBuffer(cncByteBuffer, cncMetaData),\n            US_ASCII);\n    }\n\n    /**\n     * Map an existing CnC file.\n     *\n     * @param cncFileVersion to set as value of file.\n     * @return the {@link CountersReader} over the CnC file.\n     */\n    public static CountersReader mapCounters(final MutableInteger cncFileVersion)\n    {\n        final File cncFile = CommonContext.newDefaultCncFile();\n        System.out.println(\"Command `n Control file \" + cncFile);\n\n        final MappedByteBuffer cncByteBuffer = mapExistingFileReadOnly(cncFile);\n        final DirectBuffer cncMetaData = createMetaDataBuffer(cncByteBuffer);\n        final int cncVersion = cncMetaData.getInt(cncVersionOffset(0));\n\n        cncFileVersion.set(cncVersion);\n        checkVersion(cncVersion);\n\n        return new CountersReader(\n            createCountersMetaDataBuffer(cncByteBuffer, cncMetaData),\n            createCountersValuesBuffer(cncByteBuffer, cncMetaData),\n            US_ASCII);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/SetControllableIdleStrategy.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.driver.status.StatusUtil;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.agrona.concurrent.status.StatusIndicator;\n\n/**\n * Allows a {@link org.agrona.concurrent.ControllableIdleStrategy} to be set via the command line.\n * <p>\n * The first command line arg should be an integer value representing one of constants in\n * {@link org.agrona.concurrent.ControllableIdleStrategy}.\n */\npublic class SetControllableIdleStrategy\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        if (args.length != 1)\n        {\n            System.out.format(\"Usage: SetControllableIdleStrategy <n>\");\n            System.exit(0);\n        }\n\n        try (Aeron aeron = Aeron.connect())\n        {\n            final CountersReader countersReader = aeron.countersReader();\n            final StatusIndicator statusIndicator = StatusUtil.controllableIdleStrategy(countersReader);\n\n            if (null != statusIndicator)\n            {\n                final int status = Integer.parseInt(args[0]);\n                statusIndicator.setRelease(status);\n                System.out.println(\"Set ControllableIdleStrategy status to \" + status);\n            }\n            else\n            {\n                System.out.println(\"Could not find ControllableIdleStrategy status.\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/SimplePublisher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n * Copyright 2015 Kaazing Corporation\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Publication;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A very simple Aeron publisher application which publishes a fixed size message on a fixed channel and stream.\n */\npublic class SimplePublisher\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread sleep delay is interrupted.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        // Allocate enough buffer size to hold maximum message length\n        // The UnsafeBuffer class is part of the Agrona library and is used for efficient buffer management\n        final UnsafeBuffer buffer = new UnsafeBuffer(BufferUtil.allocateDirectAligned(512, BitUtil.CACHE_LINE_LENGTH));\n\n        // The channel (an endpoint identifier) to send the message to\n        final String channel = \"aeron:udp?endpoint=localhost:40123\";\n\n        // A unique identifier for a stream within a channel. Stream ID 0 is reserved\n        // for internal use and should not be used by applications.\n        final int streamId = 10;\n\n        System.out.println(\"Publishing to \" + channel + \" on stream id \" + streamId);\n\n        // Create a context, needed for client connection to media driver\n        // A separate media driver process needs to be running prior to starting this application\n        final Aeron.Context ctx = new Aeron.Context();\n\n        // Create an Aeron instance with client-provided context configuration and connect to the\n        // media driver, and create a Publication. The Aeron and Publication classes implement\n        // AutoCloseable, and will automatically clean up resources when this try block is finished.\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication publication = aeron.addPublication(channel, streamId))\n        {\n            final String message = \"Hello World! \";\n            final byte[] messageBytes = message.getBytes();\n            buffer.putBytes(0, messageBytes);\n\n            // Wait for 5 seconds to connect to a subscriber\n            final long deadlineNs = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);\n            while (!publication.isConnected())\n            {\n                if ((deadlineNs - System.nanoTime()) < 0)\n                {\n                    System.out.println(\"Failed to connect to subscriber\");\n                    return;\n                }\n\n                Thread.sleep(1);\n            }\n\n            // Try to publish the buffer. 'offer' is a non-blocking call.\n            // If it returns less than 0, the message was not sent, and the offer should be retried.\n            final long position = publication.offer(buffer, 0, messageBytes.length);\n\n            if (position < 0L)\n            {\n                if (position == Publication.BACK_PRESSURED)\n                {\n                    System.out.println(\" Offer failed due to back pressure\");\n                }\n                else if (position == Publication.NOT_CONNECTED)\n                {\n                    System.out.println(\" Offer failed because publisher is not connected to subscriber\");\n                }\n                else if (position == Publication.ADMIN_ACTION)\n                {\n                    System.out.println(\"Offer failed because of an administration action in the system\");\n                }\n                else if (position == Publication.CLOSED)\n                {\n                    System.out.println(\"Offer failed publication is closed\");\n                }\n                else if (position == Publication.MAX_POSITION_EXCEEDED)\n                {\n                    System.out.println(\"Offer failed due to publication reaching max position\");\n                }\n                else\n                {\n                    System.out.println(\" Offer failed due to unknown reason\");\n                }\n            }\n            else\n            {\n                System.out.println(\" yay !!\");\n            }\n\n            System.out.println(\"Done sending.\");\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/SimpleSubscriber.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n * Copyright 2015 Kaazing Corporation\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Subscription;\nimport io.aeron.logbuffer.FragmentHandler;\nimport org.agrona.concurrent.BackoffIdleStrategy;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * A very simple Aeron subscriber application which can receive small non-fragmented messages\n * on a fixed channel and stream ID. The DataHandler method 'printStringMessage' is called when data\n * is received. This application doesn't handle large fragmented messages. For an example of\n * fragmented message reception, see {@link MultipleSubscribersWithFragmentAssembly}.\n */\npublic class SimpleSubscriber\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        // Maximum number of message fragments to receive during a single 'poll' operation\n        final int fragmentLimitCount = 10;\n\n        // The channel (an endpoint identifier) to receive messages from\n        final String channel = \"aeron:udp?endpoint=localhost:40123\";\n\n        // A unique identifier for a stream within a channel. Stream ID 0 is reserved\n        // for internal use and should not be used by applications.\n        final int streamId = 10;\n\n        System.out.println(\"Subscribing to \" + channel + \" on stream id \" + streamId);\n\n        // Create a context, needed for client connection to media driver\n        // A separate media driver process need to run prior to running this application\n        final Aeron.Context ctx = new Aeron.Context();\n\n        // Create an Aeron instance with client-provided context configuration, connect to the\n        // media driver, and add a subscription for the given channel and stream using the supplied\n        // dataHandler method, which will be called with new messages as they are received.\n        // The Aeron and Subscription classes implement AutoCloseable, and will automatically\n        // clean up resources when this try block is finished.\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false));\n            Aeron aeron = Aeron.connect(ctx);\n            Subscription subscription = aeron.addSubscription(channel, streamId))\n        {\n            final IdleStrategy idleStrategy = new BackoffIdleStrategy(\n                100, 10, TimeUnit.MICROSECONDS.toNanos(1), TimeUnit.MICROSECONDS.toNanos(100));\n\n            // dataHandler method is called for every new datagram received\n            final FragmentHandler fragmentHandler =\n                (buffer, offset, length, header) ->\n                {\n                    final byte[] data = new byte[length];\n                    buffer.getBytes(offset, data);\n\n                    System.out.printf(\n                        \"Received message (%s) to stream %d from session %x term id %x term offset %d (%d@%d)%n\",\n                        new String(data), streamId, header.sessionId(),\n                        header.termId(), header.termOffset(), length, offset);\n\n                    // Received the intended message, time to exit the program\n                    running.set(false);\n                };\n\n            // Try to read the data from subscriber\n            while (running.get())\n            {\n                // poll delivers messages to the dataHandler as they arrive\n                // and returns number of fragments read, or 0\n                // if no data is available.\n                final int fragmentsRead = subscription.poll(fragmentHandler, fragmentLimitCount);\n                // Give the IdleStrategy a chance to spin/yield/sleep to reduce CPU\n                // use if no messages were received.\n                idleStrategy.idle(fragmentsRead);\n            }\n\n            System.out.println(\"Shutting down...\");\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/StreamStat.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.driver.status.SubscriberPos;\nimport org.agrona.collections.Hashing;\nimport org.agrona.collections.Object2ObjectHashMap;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.PrintStream;\nimport java.util.*;\n\nimport static io.aeron.driver.status.StreamCounter.*;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\n\n/**\n * Tool for taking a snapshot of Aeron streams and relevant position counters.\n * <p>\n * Each stream managed by the {@link io.aeron.driver.MediaDriver} will be sampled and a line of text\n * output per stream with each of the position counters for that stream.\n * <p>\n * Each counter has the format:\n * {@code <label-name>:<registration-id>:<position value>}\n */\npublic final class StreamStat\n{\n    private static final Comparator<StreamCompositeKey> LINES_COMPARATOR =\n        Comparator.comparingLong(StreamCompositeKey::sessionId)\n            .thenComparingInt(StreamCompositeKey::streamId)\n            .thenComparing(StreamCompositeKey::channel);\n\n    private final CountersReader counters;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        final CountersReader counters = SamplesUtil.mapCounters();\n        final StreamStat streamStat = new StreamStat(counters);\n\n        streamStat.print(System.out);\n    }\n\n    /**\n     * Construct by using a {@link CountersReader} which can be obtained from {@link Aeron#countersReader()}.\n     *\n     * @param counters to read for tracking positions.\n     */\n    public StreamStat(final CountersReader counters)\n    {\n        this.counters = counters;\n    }\n\n    /**\n     * Take a snapshot of all the counters and group them by streams.\n     *\n     * @return a snapshot of all the counters and group them by streams.\n     */\n    public Map<StreamCompositeKey, List<StreamPosition>> snapshot()\n    {\n        final Map<StreamCompositeKey, List<StreamPosition>> streams = new TreeMap<>(LINES_COMPARATOR);\n        final Object2ObjectHashMap<StreamCompositeKey, String> fullChannelByStream = new Object2ObjectHashMap<>();\n\n        counters.forEach(\n            (counterId, typeId, keyBuffer, label) ->\n            {\n                if (isStreamCounterType(typeId))\n                {\n                    final int channelLength = keyBuffer.getInt(CHANNEL_OFFSET, LITTLE_ENDIAN);\n                    final String channel =\n                        keyBuffer.getStringWithoutLengthAscii(CHANNEL_OFFSET + SIZE_OF_INT, channelLength);\n\n                    final StreamCompositeKey key = new StreamCompositeKey(\n                        keyBuffer.getInt(SESSION_ID_OFFSET), keyBuffer.getInt(STREAM_ID_OFFSET), channel);\n\n                    final StreamPosition position = new StreamPosition(\n                        label.substring(0, label.indexOf(':')),\n                        keyBuffer.getLong(REGISTRATION_ID_OFFSET),\n                        counters.getCounterValue(counterId)\n                    );\n\n                    final List<StreamPosition> positions = streams.computeIfAbsent(key, k -> new ArrayList<>());\n                    positions.add(position);\n\n                    final String fullChannel = extractChannelInformation(typeId, label, channel);\n                    final String existingFullChannel = fullChannelByStream.get(key);\n                    if (null == existingFullChannel || fullChannel.length() > existingFullChannel.length())\n                    {\n                        fullChannelByStream.put(key, fullChannel);\n                    }\n                }\n            });\n\n        streams.keySet().forEach(k -> k.fullChannel = fullChannelByStream.get(k));\n\n        return streams;\n    }\n\n    /**\n     * Print a snapshot of the stream positions to a {@link PrintStream}.\n     * <p>\n     * Each stream will be printed on its own line.\n     *\n     * @param out to which the stream snapshot will be written.\n     * @return the number of streams printed.\n     */\n    public int print(final PrintStream out)\n    {\n        final Map<StreamCompositeKey, List<StreamPosition>> streams = snapshot();\n        final StringBuilder builder = new StringBuilder();\n\n        for (final Map.Entry<StreamCompositeKey, List<StreamPosition>> entry : streams.entrySet())\n        {\n            builder.setLength(0);\n            final StreamCompositeKey key = entry.getKey();\n\n            builder\n                .append(\"sessionId=\").append(key.sessionId())\n                .append(\" streamId=\").append(key.streamId())\n                .append(\" channel=\").append(key.fullChannel)\n                .append(\" :\");\n\n            for (final StreamPosition streamPosition : entry.getValue())\n            {\n                builder\n                    .append(' ')\n                    .append(streamPosition.name())\n                    .append(':').append(streamPosition.registrationId())\n                    .append(':').append(streamPosition.value());\n            }\n\n            out.println(builder);\n        }\n\n        return streams.size();\n    }\n\n    private static boolean isStreamCounterType(final int typeId)\n    {\n        return switch (typeId)\n        {\n            case AeronCounters.DRIVER_PUBLISHER_LIMIT_TYPE_ID,\n                 AeronCounters.DRIVER_SENDER_POSITION_TYPE_ID,\n                 AeronCounters.DRIVER_RECEIVER_HWM_TYPE_ID,\n                 AeronCounters.DRIVER_SUBSCRIBER_POSITION_TYPE_ID,\n                 AeronCounters.DRIVER_RECEIVER_POS_TYPE_ID,\n                 AeronCounters.DRIVER_SENDER_LIMIT_TYPE_ID,\n                 AeronCounters.DRIVER_PUBLISHER_POS_TYPE_ID,\n                 AeronCounters.DRIVER_SENDER_BPE_TYPE_ID,\n                 AeronCounters.DRIVER_SENDER_NAKS_RECEIVED_TYPE_ID,\n                 AeronCounters.DRIVER_RECEIVER_NAKS_SENT_TYPE_ID -> true;\n            default -> false;\n        };\n    }\n\n    private static String extractChannelInformation(final int typeId, final String label, final String channel)\n    {\n        final int uriIndex = label.indexOf(\"aeron:\");\n        final String fullChannel;\n        if (uriIndex >= 0)\n        {\n            final int joinPositionIndex;\n            if (SubscriberPos.SUBSCRIBER_POSITION_TYPE_ID == typeId &&\n                (joinPositionIndex = label.lastIndexOf(\" @\")) > uriIndex)\n            {\n                fullChannel = label.substring(uriIndex, joinPositionIndex);\n            }\n            else\n            {\n                fullChannel = label.substring(uriIndex);\n            }\n        }\n        else\n        {\n            fullChannel = channel;\n        }\n        return fullChannel;\n    }\n\n    /**\n     * Composite key which identifies an Aeron stream of messages.\n     */\n    public static final class StreamCompositeKey\n    {\n        private final int sessionId;\n        private final int streamId;\n        private final String channel;\n        String fullChannel;\n\n        /**\n         * Construct a new key representing a unique stream.\n         *\n         * @param sessionId to identify the stream.\n         * @param streamId  within a channel.\n         * @param channel   as a URI.\n         */\n        public StreamCompositeKey(final int sessionId, final int streamId, final String channel)\n        {\n            Objects.requireNonNull(channel, \"Channel cannot be null\");\n\n            this.sessionId = sessionId;\n            this.streamId = streamId;\n            this.channel = channel;\n        }\n\n        /**\n         * The session id of the stream.\n         *\n         * @return session id of the stream.\n         */\n        public int sessionId()\n        {\n            return sessionId;\n        }\n\n        /**\n         * The stream id within a channel.\n         *\n         * @return stream id within a channel.\n         */\n        public int streamId()\n        {\n            return streamId;\n        }\n\n        /**\n         * The channel as a URI.\n         *\n         * @return channel as a URI.\n         */\n        public String channel()\n        {\n            return channel;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public boolean equals(final Object o)\n        {\n            if (this == o)\n            {\n                return true;\n            }\n\n            if (!(o instanceof StreamCompositeKey))\n            {\n                return false;\n            }\n\n            final StreamCompositeKey that = (StreamCompositeKey)o;\n\n            return this.sessionId == that.sessionId &&\n                this.streamId == that.streamId &&\n                this.channel.equals(that.channel);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public int hashCode()\n        {\n            int result = sessionId;\n            result = 31 * result + streamId;\n            result = 31 * result + channel.hashCode();\n\n            return Hashing.hash(result);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"StreamCompositeKey{\" +\n                \"sessionId=\" + sessionId +\n                \", streamId=\" + streamId +\n                \", channel='\" + channel + '\\'' +\n                '}';\n        }\n    }\n\n    /**\n     * Represents a position within a particular stream of messages.\n     *\n     * @param name           of the counter.\n     * @param registrationId of the registered entity.\n     * @param value          of the position.\n     */\n    public record StreamPosition(String name, long registrationId, long value)\n    {\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"StreamPosition{\" +\n                \"name=\" + name +\n                \", registrationId=\" + registrationId +\n                \", value=\" + value +\n                '}';\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/StreamingPublisher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.Publication;\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.console.ContinueBarrier;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.IntSupplier;\n\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Publisher that sends a given number of messages at a given length as fast as possible.\n */\npublic class StreamingPublisher\n{\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final long LINGER_TIMEOUT_MS = SampleConfiguration.LINGER_TIMEOUT_MS;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final boolean EMBEDDED_MEDIA_DRIVER = SampleConfiguration.EMBEDDED_MEDIA_DRIVER;\n    private static final boolean RANDOM_MESSAGE_LENGTH = SampleConfiguration.RANDOM_MESSAGE_LENGTH;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n    private static final UnsafeBuffer OFFER_BUFFER = new UnsafeBuffer(\n        BufferUtil.allocateDirectAligned(MESSAGE_LENGTH, BitUtil.CACHE_LINE_LENGTH));\n    private static final IntSupplier LENGTH_GENERATOR = composeLengthGenerator(RANDOM_MESSAGE_LENGTH, MESSAGE_LENGTH);\n\n    private static volatile boolean printingActive = true;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if interrupted during linger.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        if (MESSAGE_LENGTH < SIZE_OF_LONG)\n        {\n            throw new IllegalArgumentException(\"Message length must be at least \" + SIZE_OF_LONG + \" bytes\");\n        }\n\n        try (MediaDriver driver = EMBEDDED_MEDIA_DRIVER ? MediaDriver.launchEmbedded() : null)\n        {\n            final Aeron.Context context = new Aeron.Context();\n\n            if (EMBEDDED_MEDIA_DRIVER)\n            {\n                context.aeronDirectoryName(driver.aeronDirectoryName());\n            }\n\n            final RateReporter reporter = new RateReporter(TimeUnit.SECONDS.toNanos(1), StreamingPublisher::printRate);\n            final ExecutorService executor = Executors.newFixedThreadPool(1);\n\n            executor.execute(reporter);\n\n            // Connect to media driver and add publication to send messages on the configured channel and stream ID.\n            // The Aeron and Publication classes implement AutoCloseable, and will automatically\n            // clean up resources when this try block is finished.\n            try (Aeron aeron = Aeron.connect(context);\n                Publication publication = aeron.addPublication(CHANNEL, STREAM_ID))\n            {\n                final ContinueBarrier barrier = new ContinueBarrier(\"Execute again?\");\n                final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n\n                do\n                {\n                    printingActive = true;\n\n                    System.out.format(\n                        \"%nStreaming %,d messages of%s size %d bytes to %s on stream id %d%n\",\n                        NUMBER_OF_MESSAGES,\n                        RANDOM_MESSAGE_LENGTH ? \" random\" : \"\",\n                        MESSAGE_LENGTH,\n                        CHANNEL,\n                        STREAM_ID);\n\n                    long backPressureCount = 0;\n\n                    for (long i = 0; i < NUMBER_OF_MESSAGES; i++)\n                    {\n                        final int length = LENGTH_GENERATOR.getAsInt();\n\n                        OFFER_BUFFER.putLong(0, i);\n                        idleStrategy.reset();\n                        while (publication.offer(OFFER_BUFFER, 0, length, null) < 0L)\n                        {\n                            // The offer failed, which is usually due to the publication\n                            // being temporarily blocked. Retry the offer after a short\n                            // spin/yield/sleep, depending on the chosen IdleStrategy.\n                            backPressureCount++;\n                            idleStrategy.idle();\n                        }\n\n                        reporter.onMessage(length);\n                    }\n\n                    System.out.println(\n                        \"Done streaming. Back pressure ratio \" + ((double)backPressureCount / NUMBER_OF_MESSAGES));\n\n                    if (LINGER_TIMEOUT_MS > 0)\n                    {\n                        System.out.println(\"Lingering for \" + LINGER_TIMEOUT_MS + \" milliseconds...\");\n                        Thread.sleep(LINGER_TIMEOUT_MS);\n                    }\n\n                    printingActive = false;\n                }\n                while (barrier.await());\n            }\n\n            reporter.halt();\n            executor.shutdown();\n        }\n    }\n\n    private static void printRate(\n        final double messagesPerSec, final double bytesPerSec, final long totalFragments, final long totalBytes)\n    {\n        if (printingActive)\n        {\n            System.out.format(\n                \"%.02g msgs/sec, %.02g bytes/sec, totals %d messages %d MB%n\",\n                messagesPerSec, bytesPerSec, totalFragments, totalBytes / (1024 * 1024));\n        }\n    }\n\n    private static IntSupplier composeLengthGenerator(final boolean random, final int max)\n    {\n        if (random)\n        {\n            return () -> ThreadLocalRandom.current().nextInt(SIZE_OF_LONG, max);\n        }\n        else\n        {\n            return () -> max;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/ArchiveCreator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.Publication;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.archive.ArchivingMediaDriver;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.Archive.Configuration.ARCHIVE_DIR_DEFAULT;\n\n/**\n * Command line utility for creating a new Archive for migration testing and replay.\n * <p>\n * Creates 2 recordings, one starts at position 0 and the other starts in the second term.\n */\npublic class ArchiveCreator\n{\n    private static final String MESSAGE_PREFIX = \"Message-Prefix-\";\n    private static final long CATALOG_CAPACITY = 128 * 1024;\n    private static final int TERM_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int SEGMENT_LENGTH = TERM_LENGTH * 2;\n    private static final int STREAM_ID = 33;\n\n    private static int recordingNumber = 0;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        final String archiveDirName = Archive.Configuration.archiveDirName();\n        final File archiveDir =\n            ARCHIVE_DIR_DEFAULT.equals(archiveDirName) ? new File(\"archive\") : new File(archiveDirName);\n\n        final MediaDriver.Context driverContext = new MediaDriver.Context()\n            .publicationTermBufferLength(TERM_LENGTH)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .errorHandler(Throwable::printStackTrace)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true);\n\n        final Archive.Context archiveContext = new Archive.Context()\n            .catalogCapacity(CATALOG_CAPACITY)\n            .segmentFileLength(SEGMENT_LENGTH)\n            .deleteArchiveOnStart(true)\n            .archiveDir(archiveDir)\n            .fileSyncLevel(0)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n        System.out.println(\"Creating basic archive at \" + archiveContext.archiveDir());\n\n        try (ArchivingMediaDriver ignore = ArchivingMediaDriver.launch(driverContext, archiveContext);\n            Aeron aeron = Aeron.connect();\n            AeronArchive aeronArchive = AeronArchive.connect(new AeronArchive.Context().aeron(aeron)))\n        {\n            createRecording(\n                aeron,\n                aeronArchive,\n                0,\n                (SEGMENT_LENGTH * 5L) + 1);\n\n            createRecording(\n                aeron,\n                aeronArchive,\n                (long)TERM_LENGTH + (FrameDescriptor.FRAME_ALIGNMENT * 2),\n                (SEGMENT_LENGTH * 3L) + 1);\n\n        }\n        catch (final Exception ex)\n        {\n            ex.printStackTrace();\n        }\n    }\n\n    private static void createRecording(\n        final Aeron aeron, final AeronArchive aeronArchive, final long startPosition, final long targetPosition)\n    {\n        final int initialTermId = 7;\n        recordingNumber++;\n        final ChannelUriStringBuilder uriBuilder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"localhost:\" + recordingNumber)\n            .termLength(TERM_LENGTH);\n\n        if (startPosition > 0)\n        {\n            uriBuilder.initialPosition(startPosition, initialTermId, TERM_LENGTH);\n        }\n\n        try (Publication publication = aeronArchive.addRecordedExclusivePublication(uriBuilder.build(), STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            System.out.println(\n                \"recordingId=\" + recordingId +\n                \" position \" + publication.position() +\n                \" to \" + targetPosition);\n\n            offerToPosition(publication, targetPosition);\n            awaitPosition(counters, counterId, publication.position());\n\n            aeronArchive.stopRecording(publication);\n        }\n    }\n\n    private static void checkInterruptStatus()\n    {\n        if (Thread.interrupted())\n        {\n            LangUtil.rethrowUnchecked(new InterruptedException());\n        }\n    }\n\n    private static int awaitRecordingCounterId(final CountersReader counters, final int sessionId, final long archiveId)\n    {\n        int counterId;\n        while (NULL_VALUE == (counterId = RecordingPos.findCounterIdBySession(counters, sessionId, archiveId)))\n        {\n            Thread.yield();\n            checkInterruptStatus();\n        }\n\n        return counterId;\n    }\n\n    private static void offerToPosition(final Publication publication, final long minimumPosition)\n    {\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n\n        for (int i = 0; publication.position() < minimumPosition; i++)\n        {\n            final int length = buffer.putStringWithoutLengthAscii(0, MESSAGE_PREFIX + i);\n\n            while (publication.offer(buffer, 0, length) <= 0)\n            {\n                Thread.yield();\n                checkInterruptStatus();\n            }\n        }\n    }\n\n    private static void awaitPosition(final CountersReader counters, final int counterId, final long position)\n    {\n        while (counters.getCounterValue(counterId) < position)\n        {\n            if (counters.getCounterState(counterId) != CountersReader.RECORD_ALLOCATED)\n            {\n                throw new IllegalStateException(\"count not active: \" + counterId);\n            }\n\n            Thread.yield();\n            checkInterruptStatus();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/EmbeddedRecordingThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchivingMediaDriver;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.samples.SampleConfiguration;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.agrona.console.ContinueBarrier;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.archive.Archive.Configuration.ARCHIVE_DIR_DEFAULT;\nimport static io.aeron.samples.archive.Samples.MEGABYTE;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.BufferUtil.allocateDirectAligned;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * Tests the throughput when recording a stream of messages.\n */\npublic class EmbeddedRecordingThroughput implements AutoCloseable\n{\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n\n    private final ArchivingMediaDriver archivingMediaDriver;\n    private final Aeron aeron;\n    private final AeronArchive aeronArchive;\n    private final UnsafeBuffer buffer = new UnsafeBuffer(allocateDirectAligned(MESSAGE_LENGTH, CACHE_LINE_LENGTH));\n    private final RecordingSignalCapture recordingSignalCapture;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (EmbeddedRecordingThroughput test = new EmbeddedRecordingThroughput())\n        {\n            test.startRecording();\n            long previousRecordingId = Aeron.NULL_VALUE;\n\n            final ContinueBarrier barrier = new ContinueBarrier(\"Execute again?\");\n            do\n            {\n                if (Aeron.NULL_VALUE != previousRecordingId)\n                {\n                    test.truncateRecording(previousRecordingId);\n                }\n\n                previousRecordingId = test.streamMessagesForRecording();\n            }\n            while (barrier.await());\n        }\n    }\n\n    EmbeddedRecordingThroughput()\n    {\n        final String archiveDirName = Archive.Configuration.archiveDirName();\n        final File archiveDir = ARCHIVE_DIR_DEFAULT.equals(archiveDirName) ?\n            Samples.createTempDir() : new File(archiveDirName);\n\n        archivingMediaDriver = ArchivingMediaDriver.launch(\n            new MediaDriver.Context()\n                .spiesSimulateConnection(true)\n                .dirDeleteOnStart(true),\n            new Archive.Context()\n                .recordingEventsEnabled(false)\n                .archiveDir(archiveDir));\n\n        aeron = Aeron.connect();\n\n        recordingSignalCapture = new RecordingSignalCapture();\n\n        aeronArchive = AeronArchive.connect(\n            new AeronArchive.Context()\n                .aeron(aeron)\n                .recordingSignalConsumer(recordingSignalCapture));\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.closeAll(\n            aeronArchive,\n            aeron,\n            archivingMediaDriver,\n            () -> archivingMediaDriver.archive().context().deleteDirectory(),\n            () -> archivingMediaDriver.mediaDriver().context().deleteDirectory());\n    }\n\n    private long streamMessagesForRecording()\n    {\n        try (ExclusivePublication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n        {\n            final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n            while (!publication.isConnected())\n            {\n                idleStrategy.idle();\n            }\n\n            final long startNs = System.nanoTime();\n            final UnsafeBuffer buffer = this.buffer;\n\n            for (long i = 0; i < NUMBER_OF_MESSAGES; i++)\n            {\n                buffer.putLong(0, i);\n\n                idleStrategy.reset();\n                while (publication.offer(buffer, 0, MESSAGE_LENGTH) < 0)\n                {\n                    idleStrategy.idle();\n                }\n            }\n\n            final long stopPosition = publication.position();\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                RecordingPos.findCounterIdBySession(counters, publication.sessionId(), aeronArchive.archiveId());\n\n            idleStrategy.reset();\n            while (counters.getCounterValue(counterId) < stopPosition)\n            {\n                idleStrategy.idle();\n            }\n\n            final long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);\n            final double dataRate = (stopPosition * 1000.0d / durationMs) / MEGABYTE;\n            final double recordingMb = stopPosition / MEGABYTE;\n            final long msgRate = (NUMBER_OF_MESSAGES / durationMs) * 1000L;\n\n            System.out.printf(\n                \"Recorded %.02f MB @ %.02f MB/s - %,d msg/sec - %d byte payload + 32 byte header%n\",\n                recordingMb, dataRate, msgRate, MESSAGE_LENGTH);\n\n            return RecordingPos.getRecordingId(counters, counterId);\n        }\n    }\n\n    private void startRecording()\n    {\n        aeronArchive.startRecording(CHANNEL, STREAM_ID, SourceLocation.LOCAL);\n    }\n\n    private void truncateRecording(final long previousRecordingId)\n    {\n        recordingSignalCapture.reset();\n        aeronArchive.truncateRecording(previousRecordingId, 0L);\n\n        recordingSignalCapture.awaitSignalForRecordingId(aeronArchive, previousRecordingId, RecordingSignal.DELETE);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/EmbeddedReplayThroughput.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.*;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchivingMediaDriver;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.RecordingDescriptorConsumer;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.samples.SampleConfiguration;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.*;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.agrona.console.ContinueBarrier;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.archive.Archive.Configuration.ARCHIVE_DIR_DEFAULT;\nimport static io.aeron.samples.archive.Samples.MEGABYTE;\nimport static io.aeron.samples.archive.Samples.NOOP_FRAGMENT_HANDLER;\nimport static org.agrona.BitUtil.CACHE_LINE_LENGTH;\nimport static org.agrona.BufferUtil.allocateDirectAligned;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\nabstract class EmbeddedReplayThroughputLhsPadding\n{\n    byte p000, p001, p002, p003, p004, p005, p006, p007, p008, p009, p010, p011, p012, p013, p014, p015;\n    byte p016, p017, p018, p019, p020, p021, p022, p023, p024, p025, p026, p027, p028, p029, p030, p031;\n    byte p032, p033, p034, p035, p036, p037, p038, p039, p040, p041, p042, p043, p044, p045, p046, p047;\n    byte p048, p049, p050, p051, p052, p053, p054, p055, p056, p057, p058, p059, p060, p061, p062, p063;\n}\n\nabstract class EmbeddedReplayThroughputValue extends EmbeddedReplayThroughputLhsPadding\n{\n    long messageCount;\n}\n\nabstract class EmbeddedReplayThroughputRhsPadding extends EmbeddedReplayThroughputValue\n{\n    byte p064, p065, p066, p067, p068, p069, p070, p071, p072, p073, p074, p075, p076, p077, p078, p079;\n    byte p080, p081, p082, p083, p084, p085, p086, p087, p088, p089, p090, p091, p092, p093, p094, p095;\n    byte p096, p097, p098, p099, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111;\n    byte p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127;\n}\n\n/**\n * Tests the throughput when replaying a recorded stream of messages.\n */\npublic class EmbeddedReplayThroughput extends EmbeddedReplayThroughputRhsPadding implements AutoCloseable\n{\n    private static final int REPLAY_STREAM_ID = 101;\n    private static final String REPLAY_URI = \"aeron:ipc\";\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n    private static final int MESSAGE_LENGTH = SampleConfiguration.MESSAGE_LENGTH;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n\n    private final ArchivingMediaDriver archivingMediaDriver;\n    private final Aeron aeron;\n    private final AeronArchive aeronArchive;\n    private final UnsafeBuffer buffer = new UnsafeBuffer(allocateDirectAligned(MESSAGE_LENGTH, CACHE_LINE_LENGTH));\n    private int publicationSessionId;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        try (EmbeddedReplayThroughput test = new EmbeddedReplayThroughput())\n        {\n            System.out.println(\"Making a recording for playback...\");\n            final long recordingLength = test.makeRecording();\n\n            System.out.println(\"Finding the recording...\");\n            final long recordingId = test.findRecordingId(ChannelUri.addSessionId(CHANNEL, test.publicationSessionId));\n            final ContinueBarrier barrier = new ContinueBarrier(\"Execute again?\");\n\n            do\n            {\n                System.out.printf(\"Replaying %,d messages%n\", NUMBER_OF_MESSAGES);\n                final long startNs = System.nanoTime();\n\n                test.replayRecording(recordingLength, recordingId);\n\n                final long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);\n                final double dataRate = (recordingLength * 1000.0d / durationMs) / MEGABYTE;\n                final double recordingMb = recordingLength / MEGABYTE;\n                final long msgRate = (NUMBER_OF_MESSAGES / durationMs) * 1000L;\n\n                System.out.println(\"Performance inclusive of replay request and connection setup:\");\n                System.out.printf(\n                    \"Replayed %.02f MB @ %.02f MB/s - %,d msg/sec - %d byte payload + 32 byte header%n\",\n                    recordingMb, dataRate, msgRate, MESSAGE_LENGTH);\n            }\n            while (barrier.await());\n        }\n    }\n\n    EmbeddedReplayThroughput()\n    {\n        final String archiveDirName = Archive.Configuration.archiveDirName();\n        final File archiveDir = ARCHIVE_DIR_DEFAULT.equals(archiveDirName) ?\n            Samples.createTempDir() : new File(archiveDirName);\n\n        archivingMediaDriver = ArchivingMediaDriver.launch(\n            new MediaDriver.Context()\n                .dirDeleteOnStart(true),\n            new Archive.Context()\n                .archiveDir(archiveDir)\n                .recordingEventsEnabled(false));\n\n        aeron = Aeron.connect();\n\n        aeronArchive = AeronArchive.connect(\n            new AeronArchive.Context()\n                .aeron(aeron));\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.closeAll(\n            aeronArchive,\n            aeron,\n            archivingMediaDriver,\n            () -> archivingMediaDriver.archive().context().deleteDirectory(),\n            () -> archivingMediaDriver.mediaDriver().context().deleteDirectory());\n    }\n\n    void onMessage(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final long count = buffer.getLong(offset);\n        if (count != messageCount)\n        {\n            throw new IllegalStateException(\"invalid message count=\" + count + \" @ \" + messageCount);\n        }\n\n        messageCount++;\n    }\n\n    private long makeRecording()\n    {\n        try (Publication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n        {\n            publicationSessionId = publication.sessionId();\n            final String channel = ChannelUri.addSessionId(CHANNEL, publicationSessionId);\n            final long subscriptionId = aeronArchive.startRecording(channel, STREAM_ID, SourceLocation.LOCAL);\n            final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n\n            final CountersReader countersReader = aeron.countersReader();\n            final long archiveId = aeronArchive.archiveId();\n            int recordingCounterId;\n            while (Aeron.NULL_VALUE == (recordingCounterId = RecordingPos.findCounterIdBySession(\n                countersReader, publicationSessionId, archiveId)))\n            {\n                idleStrategy.idle();\n            }\n\n            try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID))\n            {\n                idleStrategy.reset();\n                while (!subscription.isConnected())\n                {\n                    idleStrategy.idle();\n                }\n\n                final Image image = subscription.imageBySessionId(publicationSessionId);\n\n                long i = 0;\n                while (i < NUMBER_OF_MESSAGES)\n                {\n                    int workCount = 0;\n                    buffer.putLong(0, i);\n\n                    if (publication.offer(buffer, 0, MESSAGE_LENGTH) > 0)\n                    {\n                        i++;\n                        workCount += 1;\n                    }\n\n                    final int fragments = image.poll(NOOP_FRAGMENT_HANDLER, 10);\n                    if (0 == fragments && image.isClosed())\n                    {\n                        throw new IllegalStateException(\"image closed unexpectedly\");\n                    }\n\n                    workCount += fragments;\n                    idleStrategy.idle(workCount);\n                }\n\n                final long position = publication.position();\n                while (image.position() < position)\n                {\n                    final int fragments = image.poll(NOOP_FRAGMENT_HANDLER, 10);\n                    if (0 == fragments && image.isClosed())\n                    {\n                        throw new IllegalStateException(\"image closed unexpectedly\");\n                    }\n                    idleStrategy.idle(fragments);\n                }\n\n                awaitRecordingComplete(recordingCounterId, position, idleStrategy);\n\n                return position;\n            }\n            finally\n            {\n                aeronArchive.stopRecording(subscriptionId);\n            }\n        }\n    }\n\n    private void awaitRecordingComplete(final int counterId, final long position, final IdleStrategy idleStrategy)\n    {\n        final CountersReader counters = aeron.countersReader();\n        idleStrategy.reset();\n        while (counters.getCounterValue(counterId) < position)\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    private void replayRecording(final long recordingLength, final long recordingId)\n    {\n        try (Subscription subscription = aeronArchive.replay(\n            recordingId, 0L, recordingLength, REPLAY_URI, REPLAY_STREAM_ID))\n        {\n            final IdleStrategy idleStrategy = SampleConfiguration.newIdleStrategy();\n            while (!subscription.isConnected())\n            {\n                idleStrategy.idle();\n            }\n\n            messageCount = 0;\n            final Image image = subscription.imageAtIndex(0);\n            final ImageFragmentAssembler fragmentAssembler = new ImageFragmentAssembler(this::onMessage);\n\n            while (messageCount < NUMBER_OF_MESSAGES)\n            {\n                final int fragments = image.poll(fragmentAssembler, FRAGMENT_COUNT_LIMIT);\n                if (0 == fragments && image.isClosed())\n                {\n                    System.out.println(\"\\n*** unexpected end of stream at message count: \" + messageCount);\n                    break;\n                }\n\n                idleStrategy.idle(fragments);\n            }\n        }\n    }\n\n    private long findRecordingId(final String expectedChannel)\n    {\n        final MutableLong foundRecordingId = new MutableLong();\n\n        final RecordingDescriptorConsumer consumer =\n            (controlSessionId,\n            correlationId,\n            recordingId,\n            startTimestamp,\n            stopTimestamp,\n            startPosition,\n            stopPosition,\n            initialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId,\n            streamId,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity) -> foundRecordingId.set(recordingId);\n\n        final int recordingsFound = aeronArchive.listRecordingsForUri(\n            0L, 10, expectedChannel, STREAM_ID, consumer);\n\n        if (1 != recordingsFound)\n        {\n            throw new IllegalStateException(\"should have been only one recording\");\n        }\n\n        return foundRecordingId.get();\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/IndexedReplicatedRecording.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.*;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.archive.ArchivingMediaDriver;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.LongArrayList;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.nio.ByteOrder;\nimport java.util.Random;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.getAeronDirectoryName;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Example of how to create a recorded stream that is replicated and indexed. Indexers run in parallel to the recording\n * for greater throughput and lower latency.\n * <p>\n * The index allows for the lookup of message start position in a recording based on message index plus a basic\n * time series for when a message was published.\n * <p>\n * The secondary (destination) archive launches after the primary (source) archive and replicates from the sources\n * the history of the stream it missed then when it catches up to live it will merge with the live stream and stop\n * the replay replication from the source.\n */\npublic class IndexedReplicatedRecording implements AutoCloseable\n{\n    static final int MESSAGE_INDEX_OFFSET = 0;\n    static final int TIMESTAMP_OFFSET = SIZE_OF_LONG;\n    static final int HEADER_LENGTH = TIMESTAMP_OFFSET + SIZE_OF_LONG;\n    static final int MESSAGE_BURST_COUNT = 10_000;\n\n    private static final int TERM_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final long CATALOG_CAPACITY = 64 * 1024;\n    private static final int SRC_CONTROL_STREAM_ID = AeronArchive.Configuration.CONTROL_STREAM_ID_DEFAULT;\n    private static final String SRC_CONTROL_REQUEST_CHANNEL = \"aeron:udp?endpoint=localhost:8090\";\n    private static final String SRC_CONTROL_RESPONSE_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n    private static final String DST_CONTROL_REQUEST_CHANNEL = \"aeron:udp?endpoint=localhost:8095\";\n    private static final String DST_CONTROL_RESPONSE_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n    private static final String SRC_REPLICATION_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n    private static final String DST_REPLICATION_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n\n    private static final int LIVE_STREAM_ID = 1033;\n    private static final String LIVE_CHANNEL = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .controlMode(CommonContext.MDC_CONTROL_MODE_DYNAMIC)\n        .controlEndpoint(\"localhost:8100\")\n        .termLength(TERM_LENGTH)\n        .build();\n\n    private static final int INDEX_STREAM_ID = 1097;\n    private static final String INDEX_CHANNEL = new ChannelUriStringBuilder()\n        .media(\"ipc\")\n        .termLength(TERM_LENGTH)\n        .build();\n\n    private final ArchivingMediaDriver srcArchivingMediaDriver;\n    private final ArchivingMediaDriver dstArchivingMediaDriver;\n    private final Aeron srcAeron;\n    private final Aeron dstAeron;\n    private final AeronArchive srcAeronArchive;\n    private final AeronArchive dstAeronArchive;\n\n    IndexedReplicatedRecording()\n    {\n        final String srcAeronDirectoryName = getAeronDirectoryName() + \"-src\";\n        System.out.println(\"srcAeronDirectoryName=\" + srcAeronDirectoryName);\n        final String dstAeronDirectoryName = getAeronDirectoryName() + \"-dst\";\n        System.out.println(\"dstAeronDirectoryName=\" + dstAeronDirectoryName);\n\n        final File srcArchiveDir = new File(SystemUtil.tmpDirName(), \"src-archive\");\n        System.out.println(\"srcArchiveDir=\" + srcArchiveDir);\n        srcArchivingMediaDriver = ArchivingMediaDriver.launch(\n            new MediaDriver.Context()\n                .aeronDirectoryName(srcAeronDirectoryName)\n                .termBufferSparseFile(true)\n                .threadingMode(ThreadingMode.SHARED)\n                .errorHandler(Throwable::printStackTrace)\n                .spiesSimulateConnection(true)\n                .dirDeleteOnShutdown(true)\n                .dirDeleteOnStart(true),\n            new Archive.Context()\n                .catalogCapacity(CATALOG_CAPACITY)\n                .controlChannel(SRC_CONTROL_REQUEST_CHANNEL)\n                .archiveClientContext(new AeronArchive.Context().controlResponseChannel(SRC_CONTROL_RESPONSE_CHANNEL))\n                .replicationChannel(SRC_REPLICATION_CHANNEL)\n                .deleteArchiveOnStart(true)\n                .archiveDir(srcArchiveDir)\n                .fileSyncLevel(0)\n                .threadingMode(ArchiveThreadingMode.SHARED));\n\n        final File dstArchiveDir = new File(SystemUtil.tmpDirName(), \"dst-archive\");\n        System.out.println(\"dstArchiveDir=\" + dstArchiveDir);\n        dstArchivingMediaDriver = ArchivingMediaDriver.launch(\n            new MediaDriver.Context()\n                .aeronDirectoryName(dstAeronDirectoryName)\n                .termBufferSparseFile(true)\n                .threadingMode(ThreadingMode.SHARED)\n                .errorHandler(Throwable::printStackTrace)\n                .spiesSimulateConnection(true)\n                .dirDeleteOnShutdown(true)\n                .dirDeleteOnStart(true),\n            new Archive.Context()\n                .catalogCapacity(CATALOG_CAPACITY)\n                .controlChannel(DST_CONTROL_REQUEST_CHANNEL)\n                .archiveClientContext(new AeronArchive.Context().controlResponseChannel(DST_CONTROL_RESPONSE_CHANNEL))\n                .replicationChannel(DST_REPLICATION_CHANNEL)\n                .deleteArchiveOnStart(true)\n                .archiveDir(dstArchiveDir)\n                .fileSyncLevel(0)\n                .threadingMode(ArchiveThreadingMode.SHARED));\n\n        srcAeron = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(srcAeronDirectoryName));\n\n        dstAeron = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(dstAeronDirectoryName));\n\n        srcAeronArchive = AeronArchive.connect(\n            new AeronArchive.Context()\n                .idleStrategy(YieldingIdleStrategy.INSTANCE)\n                .controlRequestChannel(SRC_CONTROL_REQUEST_CHANNEL)\n                .controlResponseChannel(SRC_CONTROL_RESPONSE_CHANNEL)\n                .aeron(srcAeron));\n\n        dstAeronArchive = AeronArchive.connect(\n            new AeronArchive.Context()\n                .idleStrategy(YieldingIdleStrategy.INSTANCE)\n                .controlRequestChannel(DST_CONTROL_REQUEST_CHANNEL)\n                .controlResponseChannel(DST_CONTROL_RESPONSE_CHANNEL)\n                .aeron(dstAeron));\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        CloseHelper.closeAll(\n            srcAeronArchive,\n            dstAeronArchive,\n            srcAeron,\n            dstAeron,\n            srcArchivingMediaDriver,\n            dstArchivingMediaDriver);\n\n        srcArchivingMediaDriver.archive().context().deleteDirectory();\n        dstArchivingMediaDriver.archive().context().deleteDirectory();\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread is interrupted.\n     */\n    public static void main(final String[] args) throws InterruptedException\n    {\n        try (IndexedReplicatedRecording test = new IndexedReplicatedRecording())\n        {\n            final Publication publication = test.srcAeron.addExclusivePublication(LIVE_CHANNEL, LIVE_STREAM_ID);\n            final String sessionSpecificLiveChannel = LIVE_CHANNEL + \"|session-id=\" + publication.sessionId();\n            final Sequencer sequencer = new Sequencer(MESSAGE_BURST_COUNT, publication);\n\n            final Indexer primaryIndexer = new Indexer(\n                test.srcAeron.addSubscription(CommonContext.SPY_PREFIX + sessionSpecificLiveChannel, LIVE_STREAM_ID),\n                test.srcAeron.addExclusivePublication(INDEX_CHANNEL, INDEX_STREAM_ID),\n                publication.sessionId());\n            test.srcAeronArchive.startRecording(INDEX_CHANNEL, INDEX_STREAM_ID, SourceLocation.LOCAL, true);\n            final Thread primaryIndexerThread = Indexer.start(primaryIndexer);\n\n            final long srcRecordingSubscriptionId = test.srcAeronArchive.startRecording(\n                sessionSpecificLiveChannel, LIVE_STREAM_ID, SourceLocation.LOCAL, true);\n            final CountersReader srcCounters = test.srcAeron.countersReader();\n            final int srcCounterId =\n                awaitRecordingCounterId(srcCounters, publication.sessionId(), test.srcAeronArchive.archiveId());\n            final long srcRecordingId = RecordingPos.getRecordingId(srcCounters, srcCounterId);\n\n            sequencer.sendBurst();\n\n            final long channelTagId = test.dstAeron.nextCorrelationId();\n            final long subscriptionTagId = test.dstAeron.nextCorrelationId();\n            final String taggedChannel =\n                \"aeron:udp?control-mode=manual|rejoin=false|tags=\" + channelTagId + \",\" + subscriptionTagId;\n\n            final Indexer secondaryIndexer = new Indexer(\n                test.dstAeron.addSubscription(taggedChannel, LIVE_STREAM_ID),\n                test.dstAeron.addExclusivePublication(INDEX_CHANNEL, INDEX_STREAM_ID),\n                publication.sessionId());\n            test.dstAeronArchive.startRecording(INDEX_CHANNEL, INDEX_STREAM_ID, SourceLocation.LOCAL, true);\n            final Thread secondaryIndexerThread = Indexer.start(secondaryIndexer);\n\n            final long replicationId = test.dstAeronArchive.taggedReplicate(\n                srcRecordingId,\n                NULL_VALUE,\n                channelTagId,\n                subscriptionTagId,\n                SRC_CONTROL_STREAM_ID,\n                SRC_CONTROL_REQUEST_CHANNEL,\n                LIVE_CHANNEL);\n\n            sequencer.sendBurst();\n            sequencer.sendBurst();\n\n            final long position = publication.position();\n            awaitPosition(srcCounters, srcCounterId, position);\n            primaryIndexer.awaitPosition(position);\n\n            final CountersReader dstCounters = test.dstAeron.countersReader();\n            final int dstCounterId =\n                awaitRecordingCounterId(dstCounters, publication.sessionId(), test.srcAeronArchive.archiveId());\n            awaitPosition(dstCounters, dstCounterId, position);\n            secondaryIndexer.awaitPosition(position);\n\n            primaryIndexerThread.interrupt();\n            primaryIndexerThread.join();\n            primaryIndexer.close();\n\n            secondaryIndexerThread.interrupt();\n            secondaryIndexerThread.join();\n            secondaryIndexer.close();\n\n            test.dstAeronArchive.stopReplication(replicationId);\n            test.srcAeronArchive.stopRecording(srcRecordingSubscriptionId);\n\n            assertEquals(\"index\", sequencer.nextMessageIndex(), primaryIndexer.nextMessageIndex());\n            assertEquals(\"index\", sequencer.nextMessageIndex(), secondaryIndexer.nextMessageIndex());\n\n            assertEquals(\"positions\", primaryIndexer.messagePositions(), secondaryIndexer.messagePositions());\n            assertEquals(\"timestamps\", primaryIndexer.timestamps(), secondaryIndexer.timestamps());\n            assertEquals(\n                \"timestamp positions\", primaryIndexer.timestampPositions(), secondaryIndexer.timestampPositions());\n        }\n    }\n\n    static int awaitRecordingCounterId(final CountersReader counters, final int sessionId, final long archiveId)\n        throws InterruptedException\n    {\n        int counterId;\n        while (NULL_VALUE == (counterId = RecordingPos.findCounterIdBySession(counters, sessionId, archiveId)))\n        {\n            Thread.yield();\n            if (Thread.interrupted())\n            {\n                throw new InterruptedException();\n            }\n        }\n\n        return counterId;\n    }\n\n    static void awaitPosition(final CountersReader counters, final int counterId, final long position)\n        throws InterruptedException\n    {\n        while (counters.getCounterValue(counterId) < position)\n        {\n            if (counters.getCounterState(counterId) != CountersReader.RECORD_ALLOCATED)\n            {\n                throw new IllegalStateException(\"count not active: \" + counterId);\n            }\n\n            Thread.yield();\n            if (Thread.interrupted())\n            {\n                throw new InterruptedException();\n            }\n        }\n    }\n\n    static void assertEquals(final String type, final long srcValue, final long dstValue)\n    {\n        if (srcValue != dstValue)\n        {\n            throw new IllegalStateException(type + \" not equal: srcValue=\" + srcValue + \" dstValue=\" + dstValue);\n        }\n    }\n\n    static void assertEquals(final String type, final LongArrayList srcList, final LongArrayList dstList)\n    {\n        final int srcSize = srcList.size();\n        final int dstSize = dstList.size();\n        if (srcSize != dstSize)\n        {\n            throw new IllegalStateException(type + \" not equal: srcList.size=\" + srcSize + \" dstList.size=\" + dstSize);\n        }\n\n        for (int i = 0; i < srcSize; i++)\n        {\n            final long srcVal = srcList.getLong(i);\n            final long dstVal = srcList.getLong(i);\n            if (srcVal != dstVal)\n            {\n                throw new IllegalStateException(\n                    type + \" [\" + i + \"] not equal: srcVal=\" + srcVal + \" dstVal=\" + dstVal);\n            }\n        }\n    }\n\n    static class Sequencer implements AutoCloseable\n    {\n        private static final int MAX_MESSAGE_LENGTH = 1000;\n\n        private long nextMessageIndex;\n        private final int burstLength;\n        private final Publication publication;\n        private final Random random = new Random();\n        private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[HEADER_LENGTH + MAX_MESSAGE_LENGTH]);\n\n        Sequencer(final int burstLength, final Publication publication)\n        {\n            this.burstLength = burstLength;\n            this.publication = publication;\n            buffer.setMemory(HEADER_LENGTH, MAX_MESSAGE_LENGTH, (byte)'X');\n        }\n\n        public void close()\n        {\n            CloseHelper.close(publication);\n        }\n\n        long nextMessageIndex()\n        {\n            return nextMessageIndex;\n        }\n\n        void sendBurst() throws InterruptedException\n        {\n            for (int i = 0; i < burstLength; i++)\n            {\n                appendMessage();\n            }\n        }\n\n        private void appendMessage() throws InterruptedException\n        {\n            final int variableLength = random.nextInt(MAX_MESSAGE_LENGTH);\n            buffer.putLong(MESSAGE_INDEX_OFFSET, nextMessageIndex, ByteOrder.LITTLE_ENDIAN);\n            buffer.putLong(TIMESTAMP_OFFSET, System.currentTimeMillis(), ByteOrder.LITTLE_ENDIAN);\n\n            while (publication.offer(buffer, 0, HEADER_LENGTH + variableLength) < 0)\n            {\n                Thread.yield();\n                if (Thread.interrupted())\n                {\n                    throw new InterruptedException();\n                }\n            }\n\n            ++nextMessageIndex;\n        }\n    }\n\n    static class Indexer implements AutoCloseable, Runnable, ControlledFragmentHandler\n    {\n        private static final int FRAGMENT_LIMIT = 10;\n        private static final int INDEX_BUFFER_CAPACITY = 1024;\n        private static final int BATCH_SIZE = 126; // Fits 1k payload - message index, timestamp, positions...\n\n        private final int sessionId;\n        private int nextMessageIndex = 0;\n        private int batchIndex = 0;\n        private long lastMessagePosition = Aeron.NULL_VALUE;\n        private final Subscription subscription;\n        private final Publication publication;\n        private Image image;\n        private final LongArrayList messagePositions = new LongArrayList();\n        private final LongArrayList timestamps = new LongArrayList();\n        private final LongArrayList timestampPositions = new LongArrayList();\n        private final UnsafeBuffer indexBuffer = new UnsafeBuffer(new byte[INDEX_BUFFER_CAPACITY]);\n\n        static Thread start(final Indexer indexer)\n        {\n            final Thread thread = new Thread(indexer);\n            thread.setName(\"indexer\");\n            thread.setDaemon(true);\n            thread.start();\n\n            return thread;\n        }\n\n        Indexer(final Subscription subscription, final Publication publication, final int sessionId)\n        {\n            this.subscription = subscription;\n            this.publication = publication;\n            this.sessionId = sessionId;\n        }\n\n        public void close()\n        {\n            CloseHelper.close(subscription);\n        }\n\n        long position()\n        {\n            if (null == image)\n            {\n                return Aeron.NULL_VALUE;\n            }\n\n            return image.position();\n        }\n\n        void awaitPosition(final long position)\n        {\n            while (position() < position)\n            {\n                Thread.yield();\n            }\n        }\n\n        long nextMessageIndex()\n        {\n            return nextMessageIndex;\n        }\n\n        LongArrayList messagePositions()\n        {\n            return messagePositions;\n        }\n\n        LongArrayList timestamps()\n        {\n            return timestamps;\n        }\n\n        LongArrayList timestampPositions()\n        {\n            return timestampPositions;\n        }\n\n        public void run()\n        {\n            while (!subscription.isConnected() || !publication.isConnected())\n            {\n                try\n                {\n                    Thread.sleep(1);\n                }\n                catch (final InterruptedException ignore)\n                {\n                    Thread.currentThread().interrupt();\n                    return;\n                }\n            }\n\n            final Image image = subscription.imageBySessionId(sessionId);\n            this.image = image;\n            if (null == image)\n            {\n                throw new IllegalStateException(\"session not found\");\n            }\n\n            lastMessagePosition = image.joinPosition();\n\n            final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n            while (true)\n            {\n                final int fragments = image.controlledPoll(this, FRAGMENT_LIMIT);\n                if (0 == fragments)\n                {\n                    if (Thread.interrupted() || image.isClosed())\n                    {\n                        return;\n                    }\n                }\n\n                idleStrategy.idle(fragments);\n            }\n        }\n\n        public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n        {\n            final long currentPosition = lastMessagePosition;\n            final long index = buffer.getLong(offset + MESSAGE_INDEX_OFFSET, ByteOrder.LITTLE_ENDIAN);\n            if (index != nextMessageIndex)\n            {\n                throw new IllegalStateException(\"invalid index: expected=\" + nextMessageIndex + \" actual=\" + index);\n            }\n\n            if (0 == batchIndex)\n            {\n                final long timestamp = buffer.getLong(offset + TIMESTAMP_OFFSET, ByteOrder.LITTLE_ENDIAN);\n                timestamps.addLong(timestamp);\n                timestampPositions.addLong(currentPosition);\n                indexBuffer.putLong(MESSAGE_INDEX_OFFSET, nextMessageIndex, ByteOrder.LITTLE_ENDIAN);\n                indexBuffer.putLong(TIMESTAMP_OFFSET, timestamp, ByteOrder.LITTLE_ENDIAN);\n            }\n\n            final int positionOffset = HEADER_LENGTH + (batchIndex * SIZE_OF_LONG);\n            indexBuffer.putLong(positionOffset, currentPosition, ByteOrder.LITTLE_ENDIAN);\n\n            if (++batchIndex >= BATCH_SIZE)\n            {\n                if (publication.offer(indexBuffer, 0, INDEX_BUFFER_CAPACITY) <= 0)\n                {\n                    --batchIndex;\n                    return Action.ABORT;\n                }\n\n                batchIndex = 0;\n            }\n\n            messagePositions.addLong(currentPosition);\n            lastMessagePosition = header.position();\n            ++nextMessageIndex;\n\n            return Action.CONTINUE;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/RecordedBasicPublisher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.Publication;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.samples.SampleConfiguration;\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Basic Aeron publisher application which is recorded in an archive.\n * This publisher sends a fixed number of messages on a channel and stream ID.\n * <p>\n * The default values for number of messages, channel, and stream ID are\n * defined in {@link SampleConfiguration} and can be overridden by\n * setting their corresponding properties via the command-line; e.g.:\n * {@code -Daeron.sample.channel=aeron:udp?endpoint=localhost:5555 -Daeron.sample.streamId=20}\n */\npublic class RecordedBasicPublisher\n{\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n    private static final long NUMBER_OF_MESSAGES = SampleConfiguration.NUMBER_OF_MESSAGES;\n\n    private static final UnsafeBuffer BUFFER = new UnsafeBuffer(BufferUtil.allocateDirectAligned(256, 64));\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws InterruptedException if the thread sleep delay is interrupted.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args) throws InterruptedException\n    {\n        System.out.println(\"Publishing to \" + CHANNEL + \" on stream id \" + STREAM_ID);\n\n        // Create a unique response stream id so not to clash with other archive clients.\n        final AeronArchive.Context archiveCtx = new AeronArchive.Context()\n            .controlResponseStreamId(AeronArchive.Configuration.controlResponseStreamId() + 1);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false));\n            AeronArchive archive = AeronArchive.connect(archiveCtx))\n        {\n            archive.startRecording(CHANNEL, STREAM_ID, SourceLocation.LOCAL);\n\n            try (Publication publication = archive.context().aeron().addPublication(CHANNEL, STREAM_ID))\n            {\n                final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n                // Wait for recording to have started before publishing.\n                final CountersReader counters = archive.context().aeron().countersReader();\n                final long archiveId = archive.archiveId();\n                int counterId = RecordingPos.findCounterIdBySession(counters, publication.sessionId(), archiveId);\n                while (CountersReader.NULL_COUNTER_ID == counterId)\n                {\n                    if (!running.get())\n                    {\n                        return;\n                    }\n\n                    idleStrategy.idle();\n                    counterId = RecordingPos.findCounterIdBySession(counters, publication.sessionId(), archiveId);\n                }\n\n                final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n                System.out.println(\"Recording started: recordingId = \" + recordingId);\n\n                for (long i = 0; i < NUMBER_OF_MESSAGES && running.get(); i++)\n                {\n                    final String message = \"Hello World! \" + i;\n                    final byte[] messageBytes = message.getBytes();\n                    BUFFER.putBytes(0, messageBytes);\n\n                    System.out.print(\"Offering \" + i + \"/\" + NUMBER_OF_MESSAGES + \" - \");\n\n                    final long position = publication.offer(BUFFER, 0, messageBytes.length);\n                    checkResult(position);\n\n                    final String errorMessage = archive.pollForErrorResponse();\n                    if (null != errorMessage)\n                    {\n                        throw new IllegalStateException(errorMessage);\n                    }\n\n                    Thread.sleep(TimeUnit.SECONDS.toMillis(1));\n                }\n\n                idleStrategy.reset();\n                while (counters.getCounterValue(counterId) < publication.position())\n                {\n                    if (!RecordingPos.isActive(counters, counterId, recordingId))\n                    {\n                        throw new IllegalStateException(\"recording has stopped unexpectedly: \" + recordingId);\n                    }\n\n                    idleStrategy.idle();\n                }\n            }\n            finally\n            {\n                System.out.println(\"Done sending.\");\n                archive.stopRecording(CHANNEL, STREAM_ID);\n            }\n        }\n    }\n\n    private static void checkResult(final long position)\n    {\n        if (position > 0)\n        {\n            System.out.println(\"yay!\");\n        }\n        else if (position == Publication.BACK_PRESSURED)\n        {\n            System.out.println(\"Offer failed due to back pressure\");\n        }\n        else if (position == Publication.ADMIN_ACTION)\n        {\n            System.out.println(\"Offer failed because of an administration action in the system\");\n        }\n        else if (position == Publication.NOT_CONNECTED)\n        {\n            System.out.println(\"Offer failed because publisher is not connected to subscriber\");\n        }\n        else if (position == Publication.CLOSED)\n        {\n            System.out.println(\"Offer failed publication is closed\");\n        }\n        else if (position == Publication.MAX_POSITION_EXCEEDED)\n        {\n            throw new IllegalStateException(\"Offer failed due to publication reaching max position\");\n        }\n        else\n        {\n            System.out.println(\"Offer failed due to unknown result code: \" + position);\n        }\n    }\n}\n\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/RecordingDescriptor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.Image;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\n\n/**\n * Class that collects the parameters of the <code>RecordingDescriptor Consumer</code> callback for use with the\n * <code>RecordingDescriptorCollector</code>.\n *\n * @see io.aeron.archive.client.RecordingDescriptorConsumer\n * @see RecordingDescriptorCollector\n */\npublic class RecordingDescriptor\n{\n    private long controlSessionId;\n    private long correlationId;\n    private long recordingId;\n    private long startTimestamp;\n    private long stopTimestamp;\n    private long startPosition;\n    private long stopPosition;\n    private int initialTermId;\n    private int segmentFileLength;\n    private int termBufferLength;\n    private int mtuLength;\n    private int sessionId;\n    private int streamId;\n    private String strippedChannel;\n    private String originalChannel;\n    private String sourceIdentity;\n    private boolean isRetained = false;\n\n    RecordingDescriptor reset()\n    {\n        this.controlSessionId = Aeron.NULL_VALUE;\n        this.correlationId = Aeron.NULL_VALUE;\n        this.recordingId = Aeron.NULL_VALUE;\n        this.startTimestamp = AeronArchive.NULL_TIMESTAMP;\n        this.stopTimestamp = AeronArchive.NULL_TIMESTAMP;\n        this.startPosition = AeronArchive.NULL_POSITION;\n        this.stopPosition = AeronArchive.NULL_POSITION;\n        this.initialTermId = 0;\n        this.segmentFileLength = 0;\n        this.termBufferLength = 0;\n        this.mtuLength = 0;\n        this.sessionId = 0;\n        this.streamId = 0;\n        this.strippedChannel = null;\n        this.originalChannel = null;\n        this.sourceIdentity = null;\n\n        return this;\n    }\n\n    RecordingDescriptor set(\n        final long controlSessionId,\n        final long correlationId,\n        final long recordingId,\n        final long startTimestamp,\n        final long stopTimestamp,\n        final long startPosition,\n        final long stopPosition,\n        final int initialTermId,\n        final int segmentFileLength,\n        final int termBufferLength,\n        final int mtuLength,\n        final int sessionId,\n        final int streamId,\n        final String strippedChannel,\n        final String originalChannel,\n        final String sourceIdentity)\n    {\n        this.controlSessionId = controlSessionId;\n        this.correlationId = correlationId;\n        this.recordingId = recordingId;\n        this.startTimestamp = startTimestamp;\n        this.stopTimestamp = stopTimestamp;\n        this.startPosition = startPosition;\n        this.stopPosition = stopPosition;\n        this.initialTermId = initialTermId;\n        this.segmentFileLength = segmentFileLength;\n        this.termBufferLength = termBufferLength;\n        this.mtuLength = mtuLength;\n        this.sessionId = sessionId;\n        this.streamId = streamId;\n        this.strippedChannel = strippedChannel;\n        this.originalChannel = originalChannel;\n        this.sourceIdentity = sourceIdentity;\n\n        return this;\n    }\n\n    /**\n     * controlSessionId of the originating session requesting to list recordings.\n     *\n     * @return controlSessionId\n     */\n    public long controlSessionId()\n    {\n        return controlSessionId;\n    }\n\n    /**\n     * correlationId of the associated request to list recordings.\n     *\n     * @return correlationId\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * recordingId of this recording descriptor.\n     *\n     * @return recordingId\n     */\n    public long recordingId()\n    {\n        return recordingId;\n    }\n\n    /**\n     * startTimestamp of the recording.\n     *\n     * @return startTimestamp\n     */\n    public long startTimestamp()\n    {\n        return startTimestamp;\n    }\n\n    /**\n     * stopTimestamp of the recording.\n     *\n     * @return stopTimestamp\n     */\n    public long stopTimestamp()\n    {\n        return stopTimestamp;\n    }\n\n    /**\n     * startPosition of the recording against the recorded publication, the {@link Image#joinPosition()}.\n     *\n     * @return startPosition\n     */\n    public long startPosition()\n    {\n        return startPosition;\n    }\n\n    /**\n     * stopPosition reached for the recording, final position for {@link Image#position()}.\n     *\n     * @return stopPosition\n     */\n    public long stopPosition()\n    {\n        return stopPosition;\n    }\n\n    /**\n     * initialTermId of the recorded stream, {@link Image#initialTermId()}.\n     *\n     * @return initialTermId\n     */\n    public int initialTermId()\n    {\n        return initialTermId;\n    }\n\n    /**\n     * segmentFileLength of the recording which is a multiple of termBufferLength.\n     *\n     * @return segmentFileLength\n     */\n    public int segmentFileLength()\n    {\n        return segmentFileLength;\n    }\n\n    /**\n     * termBufferLength of the recorded stream, {@link Image#termBufferLength()}.\n     *\n     * @return termBufferLength\n     */\n    public int termBufferLength()\n    {\n        return termBufferLength;\n    }\n\n    /**\n     * mtuLength of the recorded stream, {@link Image#mtuLength()}.\n     *\n     * @return mtuLength\n     */\n    public int mtuLength()\n    {\n        return mtuLength;\n    }\n\n    /**\n     * sessionId of the recorded stream, this will be the most recent session id for extended recordings.\n     *\n     * @return sessionId\n     */\n    public int sessionId()\n    {\n        return sessionId;\n    }\n\n    /**\n     * streamId of the recorded stream, {@link Subscription#streamId()}.\n     *\n     * @return streamId\n     */\n    public int streamId()\n    {\n        return streamId;\n    }\n\n    /**\n     * strippedChannel of the recorded stream which is used for the recording subscription in the archive.\n     *\n     * @return strippedChannel\n     */\n    public String strippedChannel()\n    {\n        return strippedChannel;\n    }\n\n    /**\n     * originalChannel of the recorded stream provided to the start recording request, {@link Subscription#channel()}.\n     *\n     * @return originalChannel\n     */\n    public String originalChannel()\n    {\n        return originalChannel;\n    }\n\n    /**\n     * sourceIdentity of the recorded stream, the {@link Image#sourceIdentity()}.\n     *\n     * @return sourceIdentity\n     */\n    public String sourceIdentity()\n    {\n        return sourceIdentity;\n    }\n\n    /**\n     * Indicate that this descriptor instance should not be returned to the pool for reuse.\n     *\n     * @return this for a fluent API.\n     */\n    public RecordingDescriptor retain()\n    {\n        isRetained = true;\n        return this;\n    }\n\n    /**\n     * Has this instance been retained to prevent reuse.\n     *\n     * @return true if this instance has been flagged to be excluded from the pool.\n     */\n    public boolean isRetained()\n    {\n        return isRetained;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"RecordingDescriptor{\" +\n            \"controlSessionId=\" + controlSessionId +\n            \", correlationId=\" + correlationId +\n            \", recordingId=\" + recordingId +\n            \", startTimestamp=\" + startTimestamp +\n            \", stopTimestamp=\" + stopTimestamp +\n            \", startPosition=\" + startPosition +\n            \", stopPosition=\" + stopPosition +\n            \", initialTermId=\" + initialTermId +\n            \", segmentFileLength=\" + segmentFileLength +\n            \", termBufferLength=\" + termBufferLength +\n            \", mtuLength=\" + mtuLength +\n            \", sessionId=\" + sessionId +\n            \", streamId=\" + streamId +\n            \", strippedChannel='\" + strippedChannel + '\\'' +\n            \", originalChannel='\" + originalChannel + '\\'' +\n            \", sourceIdentity='\" + sourceIdentity + '\\'' +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/RecordingDescriptorCollector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.archive.client.RecordingDescriptorConsumer;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Deque;\nimport java.util.List;\n\n/**\n * Sample utility class for collecting {@link RecordingDescriptor}s. This aims to be memory and allocation\n * efficient by pooling instances of the descriptors. The user must decide on the size of the pool at allocation\n * time. If the pool size is set to <code>0</code> then no pooling will occur and all instances will be recreated.\n * This may be desirable if the user wanted to hold onto descriptors over multiple calls to\n * <code>AeronArchive</code>'s list recording methods. If the user wants to have pooling, but needs to retain some\n * instances, then they can call the <code>retain</code> method on the <code>RecordingDescriptor</code>.\n * <br>\n * <br>\n * Typical usage may be something like:\n * <br>\n * <br>\n * <pre>\n * final int pageSize;\n * final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(pageSize);\n * int count;\n * long fromRecordingId = 0;\n * while (0 != (count = aeronArchive.listRecordings(fromRecordingId, pageSize, collector.reset())\n * {\n *     for (final RecordingDescriptor descriptor : collector.descriptors())\n *     {\n *         if (\\/* some interesting condition *\\/)\n *         {\n *             // Want to hang onto this one...\n *             descriptor.retain();\n *         }\n *     }\n *\n *     fromRecordingId += count;\n * }\n * </pre>\n * <br>\n * <br>\n *\n * @see io.aeron.archive.client.AeronArchive#listRecordings(long, int, RecordingDescriptorConsumer)\n * @see io.aeron.archive.client.AeronArchive#listRecording(long, RecordingDescriptorConsumer)\n * @see io.aeron.archive.client.AeronArchive#listRecordingsForUri(long, int, String, int, RecordingDescriptorConsumer)\n * @see RecordingDescriptor#retain()\n */\npublic class RecordingDescriptorCollector\n{\n    private final ArrayList<RecordingDescriptor> descriptors = new ArrayList<>();\n    private final Deque<RecordingDescriptor> pool = new ArrayDeque<>();\n    private final int poolSize;\n\n    @SuppressWarnings(\"Convert2Lambda\")\n    private final RecordingDescriptorConsumer consumer = new RecordingDescriptorConsumer()\n    {\n        public void onRecordingDescriptor(\n            final long controlSessionId,\n            final long correlationId,\n            final long recordingId,\n            final long startTimestamp,\n            final long stopTimestamp,\n            final long startPosition,\n            final long stopPosition,\n            final int initialTermId,\n            final int segmentFileLength,\n            final int termBufferLength,\n            final int mtuLength,\n            final int sessionId,\n            final int streamId,\n            final String strippedChannel,\n            final String originalChannel,\n            final String sourceIdentity)\n        {\n            RecordingDescriptor recordingDescriptor = pool.pollLast();\n            if (null == recordingDescriptor)\n            {\n                recordingDescriptor = new RecordingDescriptor();\n            }\n\n            descriptors.add(recordingDescriptor.set(\n                controlSessionId,\n                correlationId,\n                recordingId,\n                startTimestamp,\n                stopTimestamp,\n                startPosition,\n                stopPosition,\n                initialTermId,\n                segmentFileLength,\n                termBufferLength,\n                mtuLength,\n                sessionId,\n                streamId,\n                strippedChannel,\n                originalChannel,\n                sourceIdentity));\n        }\n    };\n\n    /**\n     * Construct the collector with the specified pool size. If the list recordings call returns more descriptors than\n     * the size of the pool then this class will still work, but will allocate new instances and leave them to be\n     * garbage collected on <code>reset()</code>. Therefore, setting <code>poolSize</code> to <code>0</code> will\n     * effectively disable pooling.\n     *\n     * @param poolSize size of the descriptor pool\n     */\n    public RecordingDescriptorCollector(final int poolSize)\n    {\n        this.poolSize = poolSize;\n    }\n\n    /**\n     * Reset the result list for the collector. Removes all the instances from the collection and returns them the pool\n     * if the size of the pool and the retained status of the descriptor allows.\n     *\n     * @return the consumer to be passed into the <code>AeronArchives</code>'s list recording methods.\n     */\n    public RecordingDescriptorConsumer reset()\n    {\n        for (int i = descriptors.size(); -1 < --i;)\n        {\n            final RecordingDescriptor removed = descriptors.remove(i);\n            if (pool.size() < poolSize && !removed.isRetained())\n            {\n                pool.addLast(removed.reset());\n            }\n        }\n\n        return consumer;\n    }\n\n    /**\n     * The results from the list of recording descriptors. This collection is emptied everytime {@link\n     * RecordingDescriptorCollector#reset()} is called and refilled on subsequent queries.\n     *\n     * @return the result list of descriptors.\n     */\n    public List<RecordingDescriptor> descriptors()\n    {\n        return descriptors;\n    }\n\n    /**\n     * The configured pool size.\n     *\n     * @return number of descriptors that will be pooled.\n     */\n    public int poolSize()\n    {\n        return poolSize;\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/RecordingReplicator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ReplicationParams;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport org.agrona.Strings;\n\nimport java.util.Scanner;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.Configuration.CONTROL_STREAM_ID_DEFAULT;\nimport static java.lang.Integer.parseInt;\nimport static java.lang.Long.parseLong;\nimport static java.util.Objects.requireNonNull;\nimport static org.agrona.SystemUtil.getProperty;\nimport static org.agrona.SystemUtil.loadPropertiesFiles;\n\n/**\n * {@code RecordingReplicator} allows replicating a recording from the source Archive to the destination Archive either\n * as a new recording or by replacing an existing recording.\n * <p>\n * <em>Note: If {@link #DESTINATION_RECORDING_ID_PROP_NAME} is set then the existing destination recording will\n * be completely replaced, i.e. truncated and overwritten with the data from the source recording.</em>\n * <p>\n * Configuration properties:\n * <ul>\n *     <li>{@link #SOURCE_RECORDING_ID_PROP_NAME} - required, specifies id of source recording to be replicated.</li>\n *     <li>{@link #DESTINATION_RECORDING_ID_PROP_NAME} - optional, specifies id of destination recording to be\n *     replaced. If omitted or set to {@link Aeron#NULL_VALUE} then the new recording will be created in the destination\n *     Archive.</li>\n *     <li>{@link #SOURCE_ARCHIVE_CONTROL_REQUEST_CHANNEL} - required, specifies the control request channel of the\n *     source Archive. Must be reachable from the destination Archive.</li>\n *     <li>{@link #SOURCE_ARCHIVE_CONTROL_REQUEST_STREAM_ID} - optional, specifies the control request stream id for\n *     connecting to the source Archive from the destination Archive. Defaults to\n *     {@link io.aeron.archive.client.AeronArchive.Configuration#CONTROL_STREAM_ID_DEFAULT}</li>\n *     <li>{@link #REPLICATION_CHANNEL} - optional, specifies the replication channel to be used for the replication of\n *     the source recording. When non-empty overrides the {@link io.aeron.archive.Archive.Context#replicationChannel()}\n *     of the destination Archive, otherwise the latter is used.</li>\n *     <li>{@link io.aeron.CommonContext#AERON_DIR_PROP_NAME} - optional, specifies the aeron directory for connecting\n *     to the MediaDriver. If not specified the the default Aeron directory is assumed\n *     ({@link io.aeron.CommonContext#AERON_DIR_PROP_DEFAULT}).</li>\n *     <li>{@link io.aeron.archive.client.AeronArchive.Configuration#CONTROL_CHANNEL_PROP_NAME} - required, specifies\n *     control request channel for connecting to the destination Archive.</li>\n *     <li>{@link io.aeron.archive.client.AeronArchive.Configuration#CONTROL_STREAM_ID_PROP_NAME} - optional, specifies\n *     control request stream id for connecting to the destination Archive. Defaults to\n *     {@link io.aeron.archive.client.AeronArchive.Configuration#CONTROL_STREAM_ID_DEFAULT}.\n *     </li>\n *     <li>{@link io.aeron.archive.client.AeronArchive.Configuration#CONTROL_RESPONSE_CHANNEL_PROP_NAME} - required,\n *     specifies control response channel for receiving responses from the destination Archive.</li>\n *     <li>{@link io.aeron.archive.client.AeronArchive.Configuration#CONTROL_RESPONSE_STREAM_ID_PROP_NAME} - optional,\n *     specifies control response stream id for receiving responses from the destination Archive. Defaults to\n *     {@link io.aeron.archive.client.AeronArchive.Configuration#CONTROL_RESPONSE_STREAM_ID_DEFAULT}.</li>\n * </ul>\n * <p>\n * The easiest way is to pass the configuration via file, e.g.:\n * <pre>\n * {@code java -cp ... io.aeron.samples.Archive.RecordingReplicator <prop file name>}\n * </pre>\n * <p>\n * An alternative is to pass properties using the {@code -D} JVM flag, e.g.:\n * <pre>\n * {@code java -cp ... -Daeron.dir=some-dir ... io.aeron.samples.Archive.RecordingReplicator}\n * </pre>\n */\npublic final class RecordingReplicator\n{\n    /**\n     * Name of the required system property for specifying the id of the source recording that must be replicated to\n     * the destination Archive.\n     */\n    public static final String SOURCE_RECORDING_ID_PROP_NAME = \"aeron.sample.archive.replicate.source.recording.id\";\n\n    /**\n     * Name of the optional system property for specifying the destination recording id. Defaults to\n     * {@link io.aeron.Aeron#NULL_VALUE} in which case the source recording will be replicated as a new recording in\n     * the destination Archive.\n     */\n    public static final String DESTINATION_RECORDING_ID_PROP_NAME =\n        \"aeron.sample.archive.replicate.destination.recording.id\";\n\n    /**\n     * Name of the required system property for specifying the control request channel to connect to the source Archive\n     * from the destination Archive.\n     */\n    public static final String SOURCE_ARCHIVE_CONTROL_REQUEST_CHANNEL =\n        \"aeron.sample.archive.replicate.source.control.request.channel\";\n\n    /**\n     * Name of the optional system property for specifying the control request stream id to connect to the source\n     * Archive from the destination Archive. Defaults to\n     * {@link io.aeron.archive.client.AeronArchive.Configuration#CONTROL_STREAM_ID_DEFAULT}.\n     */\n    public static final String SOURCE_ARCHIVE_CONTROL_REQUEST_STREAM_ID =\n        \"aeron.sample.archive.replicate.source.control.request.stream.id\";\n\n    /**\n     * Name of the optional system property for specifying an explicit replication channel for recording replication\n     * between source and destination archives. If not specified then the\n     * {@link io.aeron.archive.Archive.Context#replicationChannel()} of the destination Archive will be used.\n     */\n    public static final String REPLICATION_CHANNEL = \"aeron.sample.archive.replicate.replication.channel\";\n\n    private final AeronArchive aeronArchive;\n    private final RecordingSignalCapture signalCapture;\n    private final long srcRecordingId;\n    private final String srcArchiveRequestChannel;\n    private final int srcArchiveRequestStreamId;\n    private final long dstRecordingId;\n    private final String replicationChannel;\n\n    /**\n     * @param aeronArchive              client for the destination Archive.\n     * @param srcRecordingId            id of the recording from the source Archive that must be replicated.\n     * @param dstRecordingId            id of existing recording in the destination Archive that must be\n     *                                  replaced. If set as {@link Aeron#NULL_VALUE} in which case a new recording\n     *                                  will be created.\n     * @param srcArchiveRequestChannel  request channel for sending commands to the source Archive.\n     * @param srcArchiveRequestStreamId request stream id for sending commands to the source Archive.\n     * @param replicationChannel        override the channel via which the recording data is replicated between\n     *                                  the source and destination Archives. If {@code null} or empty then the\n     *                                  {@link io.aeron.archive.Archive.Context#replicationChannel()} of the\n     *                                  destination Archive will be used.\n     * @throws NullPointerException     if any of the required properties are not set.\n     * @throws IllegalArgumentException if {@link io.aeron.archive.client.AeronArchive.Context#recordingSignalConsumer()}\n     *                                  is not set.\n     * @throws ClassCastException       if {@link io.aeron.archive.client.AeronArchive.Context#recordingSignalConsumer()}\n     *                                  is not an instance of the {@link RecordingSignalCapture} class.\n     */\n    public RecordingReplicator(\n        final AeronArchive aeronArchive,\n        final long srcRecordingId,\n        final long dstRecordingId,\n        final String srcArchiveRequestChannel,\n        final int srcArchiveRequestStreamId,\n        final String replicationChannel)\n    {\n        this.aeronArchive = aeronArchive;\n        signalCapture = (RecordingSignalCapture)aeronArchive.context().recordingSignalConsumer();\n\n        if (null == signalCapture)\n        {\n            throw new IllegalArgumentException(\"RecordingSignalConsumer not configured!\");\n        }\n\n        if (NULL_VALUE == srcRecordingId)\n        {\n            throw new IllegalArgumentException(SOURCE_RECORDING_ID_PROP_NAME + \" must be specified\");\n        }\n\n        this.srcRecordingId = srcRecordingId;\n        this.srcArchiveRequestChannel =\n            requireNonNull(trimToNull(srcArchiveRequestChannel), SOURCE_ARCHIVE_CONTROL_REQUEST_CHANNEL);\n        this.srcArchiveRequestStreamId = srcArchiveRequestStreamId;\n        this.dstRecordingId = dstRecordingId;\n        this.replicationChannel = trimToNull(replicationChannel);\n    }\n\n    /**\n     * Replicate source recording to the destination Archive.\n     *\n     * @return id of the destination recording (created or replaced).\n     */\n    public long replicate()\n    {\n        if (NULL_VALUE != dstRecordingId)\n        {\n            final RecordingDescriptorCollector recordingDescriptorCollector = new RecordingDescriptorCollector(1);\n            if (1 != aeronArchive.listRecording(dstRecordingId, recordingDescriptorCollector.reset()))\n            {\n                throw new IllegalArgumentException(\"unknown destination recording id: \" + dstRecordingId);\n            }\n            final RecordingDescriptor recordingDescriptor = recordingDescriptorCollector.descriptors().get(0).retain();\n\n            signalCapture.reset();\n            aeronArchive.truncateRecording(dstRecordingId, recordingDescriptor.startPosition());\n            signalCapture.awaitSignalForRecordingId(aeronArchive, dstRecordingId, RecordingSignal.DELETE);\n        }\n\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .replicationChannel(replicationChannel)\n            .dstRecordingId(dstRecordingId);\n        final long replicationId = aeronArchive.replicate(\n            srcRecordingId, srcArchiveRequestStreamId, srcArchiveRequestChannel, replicationParams);\n\n        signalCapture.reset();\n        signalCapture.awaitSignalForCorrelationId(aeronArchive, replicationId, RecordingSignal.SYNC);\n        final long recordingId = signalCapture.recordingId();\n\n        signalCapture.reset();\n        signalCapture.awaitSignalForCorrelationId(aeronArchive, replicationId, RecordingSignal.REPLICATE_END);\n\n        return recordingId;\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        loadPropertiesFiles(args);\n\n        final long srcRecordingId = parseLong(getProperty(SOURCE_RECORDING_ID_PROP_NAME));\n        final long dstRecordingId = parseLong(getProperty(DESTINATION_RECORDING_ID_PROP_NAME));\n        final String srcArchiveRequestChannel = getProperty(SOURCE_ARCHIVE_CONTROL_REQUEST_CHANNEL);\n        final int srcArchiveRequestStreamId = parseInt(\n            getProperty(SOURCE_ARCHIVE_CONTROL_REQUEST_STREAM_ID, String.valueOf(CONTROL_STREAM_ID_DEFAULT)));\n        final String replicationChannel = getProperty(REPLICATION_CHANNEL);\n\n        if (NULL_VALUE != dstRecordingId)\n        {\n            System.out.println(\"Destination recording=\" + dstRecordingId + \" will be replaced with source recording=\" +\n                srcRecordingId + \". Continue? (y/n)\");\n            final String answer = new Scanner(System.in).nextLine();\n            if (!\"y\".equalsIgnoreCase(answer) && !\"yes\".equalsIgnoreCase(answer))\n            {\n                System.out.println(\"Action aborted!\");\n                System.exit(-1);\n                return;\n            }\n        }\n\n        final RecordingSignalCapture signalCapture = new RecordingSignalCapture();\n        try (AeronArchive aeronArchive = AeronArchive.connect(\n            new AeronArchive.Context().recordingSignalConsumer(signalCapture)))\n        {\n            final RecordingReplicator replicator = new RecordingReplicator(\n                aeronArchive,\n                srcRecordingId,\n                dstRecordingId,\n                srcArchiveRequestChannel,\n                srcArchiveRequestStreamId,\n                replicationChannel);\n\n            final long newRecordingId = replicator.replicate();\n            System.out.println(\"Source recordingId=\" + srcRecordingId + \" replicated to the destination recordingId=\" +\n                newRecordingId + \".\");\n        }\n    }\n\n    private static String trimToNull(final String value)\n    {\n        if (Strings.isEmpty(value))\n        {\n            return null;\n        }\n\n        final String result = value.trim();\n\n        return result.isEmpty() ? null : result;\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/RecordingSignalCapture.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.RecordingSignalConsumer;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.exceptions.AeronException;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\n\n/**\n * A {@link RecordingSignalConsumer} implementation that captures recording signal data.\n *\n * <p>Usage example:\n * <pre>\n * {@code\n *     final RecordingSignalCapture recordingSignalCapture = new RecordingSignalCapture();\n *     try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDirectoryName));\n *          AeronArchive archive = AeronArchive.connect(new AeronArchive.Context()\n *              .recordingSignalConsumer(recordingSignalCapture)\n *              .aeron(aeron)))\n *     {\n *         ...\n *         recordingSignalCapture.reset();\n *         archive.replicate(srcRecordingId, dstRecordingId, ...);\n *\n *         recordingSignalCapture.awaitSignalForRecordingId(archive, dstRecordingId, RecordingSignal.STOP);\n *         final long stopPosition = recordingSignalCapture.position();\n *\n *         recordingSignalCapture.reset();\n *         recordingSignalCapture.awaitSignalForRecordingId(archive, dstRecordingId, RecordingSignal.REPLICATE_END);\n *         ...\n *     }\n * }\n * </pre>\n */\npublic final class RecordingSignalCapture implements RecordingSignalConsumer\n{\n    private long controlSessionId;\n    private long correlationId;\n    private long subscriptionId;\n    private long recordingId;\n    private long position;\n    private RecordingSignal signal;\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onSignal(\n        final long controlSessionId,\n        final long correlationId,\n        final long recordingId,\n        final long subscriptionId,\n        final long position,\n        final RecordingSignal signal)\n    {\n        this.controlSessionId = controlSessionId;\n        this.correlationId = correlationId;\n        this.subscriptionId = subscriptionId;\n        if (NULL_VALUE != recordingId)\n        {\n            this.recordingId = recordingId;\n        }\n        if (NULL_POSITION != position)\n        {\n            this.position = position;\n        }\n        this.signal = signal;\n    }\n\n    /**\n     * Uses {@link AeronArchive#pollForRecordingSignals()} until the specified signal is received.\n     *\n     * @param archive               client to poll for signals on.\n     * @param expectedCorrelationId to match the signal.\n     * @param expectedSignal        to await.\n     */\n    public void awaitSignalForCorrelationId(\n        final AeronArchive archive, final long expectedCorrelationId, final RecordingSignal expectedSignal)\n    {\n        while (expectedCorrelationId != correlationId || expectedSignal != signal)\n        {\n            if (0 == archive.pollForRecordingSignals())\n            {\n                Thread.yield();\n                if (Thread.currentThread().isInterrupted())\n                {\n                    throw new AeronException(\"unexpected interrupt\");\n                }\n            }\n            else\n            {\n                if (expectedCorrelationId == correlationId)\n                {\n                    if (expectedSignal == signal)\n                    {\n                        return;\n                    }\n                    else if (RecordingSignal.REPLICATE_END == signal)\n                    {\n                        throw new AeronException(\"unexpected end of replication: correlationId=\" + correlationId);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Uses {@link AeronArchive#pollForRecordingSignals()} until the specified signal for the specified recording is\n     * received.\n     *\n     * @param archive             client to poll for signals on.\n     * @param expectedRecordingId that should be delivered with the signal.\n     * @param expectedSignal      to await.\n     */\n    public void awaitSignalForRecordingId(\n        final AeronArchive archive, final long expectedRecordingId, final RecordingSignal expectedSignal)\n    {\n        while (expectedRecordingId != recordingId || expectedSignal != signal)\n        {\n            if (0 == archive.pollForRecordingSignals())\n            {\n                Thread.yield();\n                if (Thread.currentThread().isInterrupted())\n                {\n                    throw new AeronException(\"unexpected interrupt\");\n                }\n            }\n            else\n            {\n                if (expectedRecordingId == recordingId)\n                {\n                    if (expectedSignal == signal)\n                    {\n                        return;\n                    }\n                    else if (RecordingSignal.REPLICATE_END == signal)\n                    {\n                        throw new AeronException(\"unexpected end of replication: correlationId=\" + correlationId);\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * Reset internal state before awaiting next signal.\n     */\n    public void reset()\n    {\n        correlationId = NULL_VALUE;\n        controlSessionId = NULL_VALUE;\n        subscriptionId = NULL_VALUE;\n        recordingId = NULL_VALUE;\n        position = NULL_POSITION;\n        signal = null;\n    }\n\n    /**\n     * Control session id captured when the {@link #onSignal(long, long, long, long, long, RecordingSignal)} was last\n     * invoked.\n     *\n     * @return last captured control session id.\n     * @see #onSignal(long, long, long, long, long, RecordingSignal)\n     */\n    public long controlSessionId()\n    {\n        return controlSessionId;\n    }\n\n    /**\n     * Correlation id captured when the {@link #onSignal(long, long, long, long, long, RecordingSignal)} was last\n     * invoked.\n     *\n     * @return last captured correlation id.\n     * @see #onSignal(long, long, long, long, long, RecordingSignal)\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * Subscription id captured when the {@link #onSignal(long, long, long, long, long, RecordingSignal)} was last\n     * invoked.\n     *\n     * @return last captured subscription id.\n     * @see #onSignal(long, long, long, long, long, RecordingSignal)\n     */\n    public long subscriptionId()\n    {\n        return subscriptionId;\n    }\n\n    /**\n     * Recording id captured when the {@link #onSignal(long, long, long, long, long, RecordingSignal)} was last invoked.\n     *\n     * @return last captured recording id.\n     * @see #onSignal(long, long, long, long, long, RecordingSignal)\n     */\n    public long recordingId()\n    {\n        return recordingId;\n    }\n\n    /**\n     * Recording position captured when the {@link #onSignal(long, long, long, long, long, RecordingSignal)} was last\n     * invoked.\n     *\n     * @return last captured recording position.\n     * @see #onSignal(long, long, long, long, long, RecordingSignal)\n     */\n    public long position()\n    {\n        return position;\n    }\n\n    /**\n     * Recording signal captured when the {@link #onSignal(long, long, long, long, long, RecordingSignal)} was last\n     * invoked.\n     *\n     * @return last captured recording signal.\n     * @see #onSignal(long, long, long, long, long, RecordingSignal)\n     */\n    public RecordingSignal signal()\n    {\n        return signal;\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/ReplayMergeSubscriber.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ReplayMerge;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.samples.SampleConfiguration;\nimport io.aeron.samples.SamplesUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\nimport org.agrona.concurrent.YieldingIdleStrategy;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * This is an Aeron subscriber utilising {@link io.aeron.archive.client.ReplayMerge}.\n * <p>\n * The application uses {@code ReplayMerge} to replay historical messages, before joining the live stream.\n * It uses a default channel for replay and live, and the same default stream ID for both.\n * These defaults can be overwritten by setting their corresponding Java system properties\n * at the command line, for example:\n * export JVM_OPTS=\"-Daeron.sample.channel=aeron:udp?endpoint=localhost:5555 -Daeron.sample.streamId=20\"\n * <p>\n * This application only handles non-fragmented data.\n */\npublic class ReplayMergeSubscriber\n{\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n    private static final String LIVE_DESTINATION = SampleConfiguration.CHANNEL;\n    private static final String REPLAY_DESTINATION = \"aeron:udp?endpoint=localhost:0\";\n    private static final String MDS_CHANNEL = \"aeron:udp?control-mode=manual\";\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n    private static final boolean EMBEDDED_MEDIA_DRIVER = SampleConfiguration.EMBEDDED_MEDIA_DRIVER;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        System.out.println(\"Subscribing to live \" + LIVE_DESTINATION + \", and replay \" + REPLAY_DESTINATION +\n            \" on stream id \" + STREAM_ID);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        final AtomicBoolean isLive = new AtomicBoolean(false);\n        final IdleStrategy idleStrategy = new YieldingIdleStrategy();\n        final FragmentHandler fragmentHandler = new FragmentAssembler(\n            (final DirectBuffer buffer, final int offset, final int length, final Header header) ->\n            {\n                final String msg = buffer.getStringWithoutLengthAscii(offset, length);\n                final String streamState = isLive.get() ? \"live\" : \"replay\";\n\n                System.out.printf(\"Message to %s stream %d from session %d (%d@%d) <<%s>>%n\",\n                    streamState, STREAM_ID, header.sessionId(), length, offset, msg);\n            });\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier(() -> running.set(false));\n            MediaDriver driver = EMBEDDED_MEDIA_DRIVER ?\n                MediaDriver.launchEmbedded(new MediaDriver.Context().terminationHook(barrier::signalAll)) : null)\n        {\n            final Aeron.Context ctx = new Aeron.Context()\n                .availableImageHandler(SamplesUtil::printAvailableImage)\n                .unavailableImageHandler(SamplesUtil::printUnavailableImage);\n\n            if (EMBEDDED_MEDIA_DRIVER)\n            {\n                ctx.aeronDirectoryName(driver.aeronDirectoryName());\n            }\n\n            final AeronArchive.Context aeronArchiveCtx = new AeronArchive.Context();\n\n            // Create Aeron and AeronArchive instances using the configured Context.\n            try (Aeron aeron = Aeron.connect(ctx);\n                AeronArchive aeronArchive = AeronArchive.connect(aeronArchiveCtx.aeron(aeron)))\n            {\n                final RecordingDescriptor descriptor = getLastDescriptor(aeronArchive);\n\n                if (descriptor == null)\n                {\n                    System.out.println(\"No recordings found for channel \" + LIVE_DESTINATION);\n                    return;\n                }\n                final String replayChannel = CommonContext.UDP_CHANNEL + \"?session-id=\" + descriptor.sessionId();\n                final String subscriptionChannel = MDS_CHANNEL + \"|session-id=\" + descriptor.sessionId();\n\n                // Create a Multi-Destination Subscription on the Aeron instance for the ReplayMerge instance, then\n                // create a ReplayMerge instance.\n                try (Subscription subscription = aeron.addSubscription(subscriptionChannel, STREAM_ID);\n                    ReplayMerge replayMerge = new ReplayMerge(\n                        subscription,\n                        aeronArchive,\n                        replayChannel,\n                        REPLAY_DESTINATION,\n                        LIVE_DESTINATION,\n                        descriptor.recordingId(),\n                        descriptor.startPosition()))\n                {\n                    while (running.get())\n                    {\n                        if (replayMerge.hasFailed())\n                        {\n                            throw new IllegalStateException(\"ReplayMerge has failed, \" + replayMerge);\n                        }\n\n                        if (replayMerge.isMerged() && !isLive.get())\n                        {\n                            System.out.println(\"===========\");\n                            System.out.println(\"ReplayMerge has joined live stream.\");\n                            System.out.println(\"===========\");\n                            isLive.set(true);\n                        }\n\n                        final int fragments = replayMerge.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n\n                        idleStrategy.idle(fragments);\n                    }\n                    System.out.println(\"Shutting down...\");\n                }\n            }\n        }\n    }\n\n    private static RecordingDescriptor getLastDescriptor(final AeronArchive aeronArchive)\n    {\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(1);\n\n        if (0 == aeronArchive.listRecordingsForUri(\n            0, Integer.MAX_VALUE, \"alias=replay-merge-sample\", STREAM_ID, collector.reset()))\n        {\n            return null;\n        }\n\n        final int lastIndex = collector.descriptors().size() - 1;\n        return collector.descriptors().get(lastIndex);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/ReplayedBasicSubscriber.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.RecordingDescriptorConsumer;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.samples.SampleConfiguration;\nimport io.aeron.samples.SamplesUtil;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * A basic subscriber application which requests a replay from the archive and consumes it.\n */\npublic class ReplayedBasicSubscriber\n{\n    private static final int STREAM_ID = SampleConfiguration.STREAM_ID;\n\n    // Use a different stream id to avoid clash with live stream\n    private static final int REPLAY_STREAM_ID = SampleConfiguration.STREAM_ID + 1;\n\n    private static final String CHANNEL = SampleConfiguration.CHANNEL;\n    private static final int FRAGMENT_COUNT_LIMIT = SampleConfiguration.FRAGMENT_COUNT_LIMIT;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        System.out.println(\"Subscribing to \" + CHANNEL + \" on stream id \" + STREAM_ID);\n\n        final FragmentHandler fragmentHandler = SamplesUtil.printAsciiMessage(STREAM_ID);\n\n        // Create a unique response stream id so not to clash with other archive clients.\n        final AeronArchive.Context archiveCtx = new AeronArchive.Context()\n            .controlResponseStreamId(AeronArchive.Configuration.controlResponseStreamId() + 2);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false));\n            AeronArchive archive = AeronArchive.connect(archiveCtx))\n        {\n            final long recordingId = findLatestRecording(archive);\n            final long position = 0L;\n            final long length = Long.MAX_VALUE;\n\n            final long sessionId = archive.startReplay(recordingId, position, length, CHANNEL, REPLAY_STREAM_ID);\n            final String channel = ChannelUri.addSessionId(CHANNEL, (int)sessionId);\n\n            try (Subscription subscription = archive.context().aeron().addSubscription(channel, REPLAY_STREAM_ID))\n            {\n                SamplesUtil.subscriberLoop(fragmentHandler, FRAGMENT_COUNT_LIMIT, running).accept(subscription);\n                System.out.println(\"Shutting down...\");\n            }\n        }\n    }\n\n    private static long findLatestRecording(final AeronArchive archive)\n    {\n        final MutableLong lastRecordingId = new MutableLong();\n\n        final RecordingDescriptorConsumer consumer =\n            (controlSessionId,\n            correlationId,\n            recordingId,\n            startTimestamp,\n            stopTimestamp,\n            startPosition,\n            stopPosition,\n            initialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId,\n            streamId,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity) -> lastRecordingId.set(recordingId);\n\n        final long fromRecordingId = 0L;\n        final int recordCount = 100;\n\n        final int foundCount = archive.listRecordingsForUri(fromRecordingId, recordCount, CHANNEL, STREAM_ID, consumer);\n\n        if (foundCount == 0)\n        {\n            throw new IllegalStateException(\"no recordings found\");\n        }\n\n        return lastRecordingId.get();\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/SampleAuthenticator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.SessionProxy;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.collections.Long2ObjectHashMap;\n\nimport java.nio.charset.StandardCharsets;\n\n/**\n * A sample {@link Authenticator} to demonstrate usage based on some hardcoded credentials.\n */\npublic final class SampleAuthenticator implements Authenticator\n{\n    private static final String CREDENTIALS_STRING_NO_CHALLENGE = \"admin:admin\";\n    private static final String CREDENTIALS_STRING_REQUIRING_CHALLENGE = \"admin:adminC\";\n    private static final String CHALLENGE_CREDENTIALS_STRING = \"admin:CSadmin\";\n    private static final String CHALLENGE_STRING = \"challenge!\";\n\n    /**\n     * A sample principal representing an administrator.\n     */\n    public static final String PRINCIPAL = \"admin\";\n\n    @SuppressWarnings(\"JavadocVariable\")\n    enum SessionState\n    {\n        CHALLENGE, AUTHENTICATED, REJECT\n    }\n\n    private final Long2ObjectHashMap<SessionState> sessionIdToStateMap = new Long2ObjectHashMap<>();\n\n    /**\n     * Byte array encoded representation of the sample principal.\n     *\n     * @return byte array representation of {@link #PRINCIPAL}\n     * @see #PRINCIPAL\n     */\n    public byte[] encodedPrincipal()\n    {\n        return PRINCIPAL.getBytes(StandardCharsets.US_ASCII);\n    }\n\n    /**\n     * Client is attempting to connect.\n     *\n     * @param sessionId          to identify the client session connecting.\n     * @param encodedCredentials from the Connect Request. Will not be null, but may be 0 length.\n     * @param nowMs              current epoch time in milliseconds.\n     */\n    public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n    {\n        final String credentialsString = new String(encodedCredentials, StandardCharsets.US_ASCII);\n\n        if (credentialsString.equals(CREDENTIALS_STRING_NO_CHALLENGE))\n        {\n            sessionIdToStateMap.put(sessionId, SessionState.AUTHENTICATED);\n        }\n        else if (credentialsString.equals(CREDENTIALS_STRING_REQUIRING_CHALLENGE))\n        {\n            sessionIdToStateMap.put(sessionId, SessionState.CHALLENGE);\n        }\n        else\n        {\n            sessionIdToStateMap.put(sessionId, SessionState.REJECT);\n        }\n    }\n\n    /**\n     * Client has returned a response to a challenge.\n     *\n     * @param sessionId          to identify the client session connecting.\n     * @param encodedCredentials from the Challenge Response. Will not be null, but may be 0 length.\n     * @param nowMs              current epoch time in milliseconds.\n     */\n    public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n    {\n        final String credentialsString = new String(encodedCredentials, StandardCharsets.US_ASCII);\n        final SessionState sessionState = sessionIdToStateMap.get(sessionId);\n\n        if (SessionState.CHALLENGE == sessionState && credentialsString.equals(CHALLENGE_CREDENTIALS_STRING))\n        {\n            sessionIdToStateMap.put(sessionId, SessionState.AUTHENTICATED);\n        }\n        else if (!credentialsString.equals(CHALLENGE_CREDENTIALS_STRING))\n        {\n            sessionIdToStateMap.put(sessionId, SessionState.REJECT);\n        }\n    }\n\n    /**\n     * Client session is now connected so a response can be sent.\n     *\n     * @param sessionProxy to use to inform client of status.\n     * @param nowMs        current epoch time in milliseconds.\n     */\n    public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n    {\n        final long sessionId = sessionProxy.sessionId();\n        final SessionState sessionState = sessionIdToStateMap.get(sessionId);\n\n        if (null != sessionState)\n        {\n            switch (sessionState)\n            {\n                case CHALLENGE:\n                    sessionProxy.challenge((CHALLENGE_STRING.getBytes()));\n                    break;\n\n                case AUTHENTICATED:\n                    if (sessionProxy.authenticate(encodedPrincipal()))\n                    {\n                        sessionIdToStateMap.remove(sessionId);\n                    }\n                    break;\n\n                case REJECT:\n                    sessionProxy.reject();\n                    sessionIdToStateMap.remove(sessionId);\n                    break;\n            }\n        }\n    }\n\n    /**\n     * The client has been challenged and is awaiting a response.\n     *\n     * @param sessionProxy to use to inform client of status.\n     * @param nowMs        current epoch time in milliseconds.\n     */\n    public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n    {\n        final long sessionId = sessionProxy.sessionId();\n        final SessionState sessionState = sessionIdToStateMap.get(sessionId);\n\n        if (null != sessionState)\n        {\n            switch (sessionState)\n            {\n                case CHALLENGE:\n                    break;\n\n                case AUTHENTICATED:\n                    if (sessionProxy.authenticate(ArrayUtil.EMPTY_BYTE_ARRAY))\n                    {\n                        sessionIdToStateMap.remove(sessionId);\n                    }\n                    break;\n\n                case REJECT:\n                    sessionProxy.reject();\n                    sessionIdToStateMap.remove(sessionId);\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/SampleAuthenticatorSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.AuthenticatorSupplier;\n\n/**\n * Sample {@link AuthenticatorSupplier} which returns a new {@link SampleAuthenticator}.\n */\npublic class SampleAuthenticatorSupplier implements AuthenticatorSupplier\n{\n    /**\n     * {@inheritDoc}\n     */\n    public Authenticator get()\n    {\n        return new SampleAuthenticator();\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/SampleAuthorisationService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.security.AuthorisationService;\nimport org.agrona.collections.ObjectHashSet;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collection;\n\n/**\n * Trivial authorisation service that allows a set of US-ASCII encoded principals access to all services.\n */\npublic class SampleAuthorisationService implements AuthorisationService\n{\n    private final ObjectHashSet<String> allowedPrincipals = new ObjectHashSet<>();\n\n    /**\n     * Create with a collection of principals that are allowed access to resources governed by this service.\n     *\n     * @param allowedPrincipals the collection of principals allowed to access service\n     */\n    public SampleAuthorisationService(final Collection<String> allowedPrincipals)\n    {\n        this.allowedPrincipals.addAll(allowedPrincipals);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isAuthorised(\n        final int protocolId,\n        final int actionId,\n        final Object type,\n        final byte[] encodedPrincipal)\n    {\n        final String principal = new String(encodedPrincipal, StandardCharsets.US_ASCII);\n        return allowedPrincipals.contains(principal);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/Samples.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.logbuffer.FragmentHandler;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\n\n/**\n * Common constants and functions used in the Archive samples.\n */\nclass Samples\n{\n    /**\n     * One MB constant for using in calculations.\n     */\n    public static final double MEGABYTE = 1024.0d * 1024.0d;\n\n    /**\n     * A {@link FragmentHandler} that consumes fragments with no side effects.\n     */\n    public static final FragmentHandler NOOP_FRAGMENT_HANDLER = (buffer, offset, length, header) -> {};\n\n    /**\n     * Create a temporary directory for storing a sample archive.\n     *\n     * @return a temporary directory for storing a sample archive.\n     */\n    public static File createTempDir()\n    {\n        final File tempDirForTest;\n        try\n        {\n            tempDirForTest = Files.createTempFile(\"archive\", \"tmp\").toFile();\n        }\n        catch (final IOException ex)\n        {\n            throw new RuntimeException(ex);\n        }\n\n        if (!tempDirForTest.delete())\n        {\n            throw new IllegalStateException(\"failed to delete: \" + tempDirForTest);\n        }\n\n        if (!tempDirForTest.mkdir())\n        {\n            throw new IllegalStateException(\"failed to create: \" + tempDirForTest);\n        }\n\n        return tempDirForTest;\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/SegmentInspector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.samples.LogInspector;\nimport io.aeron.samples.SamplesUtil;\nimport org.agrona.BitUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.nio.ByteBuffer;\nimport java.util.Date;\n\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static java.lang.Math.min;\n\n/**\n * Command line utility for inspecting the data contents of an archive segment file.\n * <p>\n * Supports {@link LogInspector#AERON_LOG_DATA_FORMAT_PROP_NAME}\n */\npublic class SegmentInspector\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        final PrintStream out = System.out;\n        if (args.length < 1)\n        {\n            out.println(\"Usage: SegmentInspector <segmentFileName> [dump limit in bytes per message]\");\n            return;\n        }\n\n        final String fileName = args[0];\n        final int messageDumpLimit = args.length >= 2 ? Integer.parseInt(args[1]) : Integer.MAX_VALUE;\n        final File file = new File(fileName);\n        final ByteBuffer byteBuffer = SamplesUtil.mapExistingFileReadOnly(file);\n        final UnsafeBuffer segmentBuffer = new UnsafeBuffer(byteBuffer);\n\n        out.println(\"======================================================================\");\n        out.format(\"%s Inspection dump for %s%n\", new Date(), fileName);\n        out.println(\"======================================================================\");\n        out.println();\n\n        dumpSegment(out, messageDumpLimit, segmentBuffer);\n    }\n\n    /**\n     * Dump the contents of a segment file to a {@link PrintStream}.\n     *\n     * @param out              for the dumped contents.\n     * @param messageDumpLimit for the number of bytes per message fragment to dump.\n     * @param buffer           the wraps the segment file.\n     */\n    public static void dumpSegment(final PrintStream out, final int messageDumpLimit, final UnsafeBuffer buffer)\n    {\n        final DataHeaderFlyweight dataHeaderFlyweight = new DataHeaderFlyweight();\n        final int length = buffer.capacity();\n        int offset = 0;\n\n        while (offset < length)\n        {\n            dataHeaderFlyweight.wrap(buffer, offset, length - offset);\n\n            final int frameLength = dataHeaderFlyweight.frameLength();\n            if (frameLength < DataHeaderFlyweight.HEADER_LENGTH)\n            {\n                break;\n            }\n            out.println(offset + \": \" + dataHeaderFlyweight);\n\n            final int limit = min(frameLength - HEADER_LENGTH, messageDumpLimit);\n            out.println(LogInspector.formatBytes(buffer, offset + HEADER_LENGTH, limit));\n            offset += BitUtil.align(frameLength, FrameDescriptor.FRAME_ALIGNMENT);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/archive/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Samples for recording and replaying message streams to disk while measuring throughput.\n */\npackage io.aeron.samples.archive;"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/cluster/ClusterConfig.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.cluster;\n\nimport io.aeron.CommonContext;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.NoOpLock;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Wrapper class to simplify cluster configuration. This is sample code and is intended to show a mechanism for\n * managing the configuration of the components required for Aeron Cluster. This code may change between versions\n * and API compatibility is not guaranteed.\n */\npublic final class ClusterConfig\n{\n    /**\n     * Number of ports per node reserved.\n     */\n    public static final int PORTS_PER_NODE = 100;\n\n    /**\n     * Offset from base port that the archive control channel is on.\n     */\n    public static final int ARCHIVE_CONTROL_PORT_OFFSET = 1;\n\n    /**\n     * Offset from base port that the client facing port is on for ingress.\n     */\n    public static final int CLIENT_FACING_PORT_OFFSET = 2;\n\n    /**\n     * Offset from base port that the member listens on for consensus traffic.\n     */\n    public static final int MEMBER_FACING_PORT_OFFSET = 3;\n\n    /**\n     * Offset from base port that the cluster log is on.\n     */\n    public static final int LOG_PORT_OFFSET = 4;\n\n    /**\n     * Offset from base port that the transfer of files is on.\n     */\n    public static final int TRANSFER_PORT_OFFSET = 5;\n\n    /**\n     * Subdirectory into which archive files are stored.\n     */\n    public static final String ARCHIVE_SUB_DIR = \"archive\";\n\n    /**\n     * Subdirectory into which cluster files are stored.\n     */\n    public static final String CLUSTER_SUB_DIR = \"cluster\";\n\n    private final int memberId;\n    private final String ingressHostname;\n    private final String clusterHostname;\n    private final MediaDriver.Context mediaDriverContext;\n    private final Archive.Context archiveContext;\n    private final AeronArchive.Context aeronArchiveContext;\n    private final ConsensusModule.Context consensusModuleContext;\n    private final List<ClusteredServiceContainer.Context> clusteredServiceContexts;\n\n    ClusterConfig(\n        final int memberId,\n        final String ingressHostname,\n        final String clusterHostname,\n        final MediaDriver.Context mediaDriverContext,\n        final Archive.Context archiveContext,\n        final AeronArchive.Context aeronArchiveContext,\n        final ConsensusModule.Context consensusModuleContext,\n        final List<ClusteredServiceContainer.Context> clusteredServiceContexts)\n    {\n        this.memberId = memberId;\n        this.ingressHostname = ingressHostname;\n        this.clusterHostname = clusterHostname;\n        this.mediaDriverContext = mediaDriverContext;\n        this.archiveContext = archiveContext;\n        this.aeronArchiveContext = aeronArchiveContext;\n        this.consensusModuleContext = consensusModuleContext;\n        this.clusteredServiceContexts = clusteredServiceContexts;\n    }\n\n    /**\n     * Create a new ClusterConfig. This call allows for 2 separate lists of hostnames, so that there can be 'external'\n     * addresses for ingress requests and 'internal' addresses that will handle all the cluster replication and\n     * control traffic.\n     *\n     * @param startingMemberId   id for the first member in the list of entries.\n     * @param memberId           id for this node.\n     * @param ingressHostnames   list of hostnames that will receive ingress request traffic.\n     * @param clusterHostnames   list of hostnames that will receive cluster traffic.\n     * @param portBase           base port to derive remaining ports from.\n     * @param parentDir          directory under which the persistent directories will be created.\n     * @param clusteredService   instance of the clustered service that will run with this configuration.\n     * @param additionalServices instances of additional clustered services that will run with this configuration.\n     * @return configuration that wraps all aeron service configuration.\n     */\n    public static ClusterConfig create(\n        final int startingMemberId,\n        final int memberId,\n        final List<String> ingressHostnames,\n        final List<String> clusterHostnames,\n        final int portBase,\n        final File parentDir,\n        final ClusteredService clusteredService,\n        final ClusteredService... additionalServices)\n    {\n        if (memberId < startingMemberId || (startingMemberId + ingressHostnames.size()) <= memberId)\n        {\n            throw new IllegalArgumentException(\n                \"memberId=\" + memberId + \" is invalid, should be \" + startingMemberId +\n                \" <= memberId < \" + startingMemberId + ingressHostnames.size());\n        }\n\n        final String clusterMembers = clusterMembers(startingMemberId, ingressHostnames, clusterHostnames, portBase);\n\n        final String aeronDirName = CommonContext.getAeronDirectoryName() + \"-\" + memberId + \"-driver\";\n        final File baseDir = new File(parentDir, \"aeron-cluster-\" + memberId);\n\n        final String ingressHostname = ingressHostnames.get(memberId - startingMemberId);\n        final String hostname = clusterHostnames.get(memberId - startingMemberId);\n\n        final MediaDriver.Context mediaDriverContext = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDirName);\n\n        final AeronArchive.Context replicationArchiveContext = new AeronArchive.Context()\n            .controlResponseChannel(\"aeron:udp?endpoint=\" + hostname + \":0\");\n\n        final Archive.Context archiveContext = new Archive.Context()\n            .aeronDirectoryName(aeronDirName)\n            .archiveDir(new File(baseDir, ARCHIVE_SUB_DIR))\n            .controlChannel(udpChannel(memberId, hostname, portBase, ARCHIVE_CONTROL_PORT_OFFSET))\n            .archiveClientContext(replicationArchiveContext)\n            .localControlChannel(\"aeron:ipc?term-length=64k\")\n            .replicationChannel(\"aeron:udp?endpoint=\" + hostname + \":0\")\n            .recordingEventsEnabled(false);\n\n        final AeronArchive.Context aeronArchiveContext = new AeronArchive.Context()\n            .lock(NoOpLock.INSTANCE)\n            .controlRequestChannel(archiveContext.localControlChannel())\n            .controlRequestStreamId(archiveContext.localControlStreamId())\n            .controlResponseChannel(archiveContext.localControlChannel())\n            .aeronDirectoryName(aeronDirName);\n\n        final ConsensusModule.Context consensusModuleContext = new ConsensusModule.Context()\n            .clusterMemberId(memberId)\n            .clusterMembers(clusterMembers)\n            .clusterDir(new File(baseDir, CLUSTER_SUB_DIR))\n            .archiveContext(aeronArchiveContext.clone())\n            .serviceCount(1 + additionalServices.length)\n            .replicationChannel(\"aeron:udp?endpoint=\" + hostname + \":0\");\n\n        final List<ClusteredServiceContainer.Context> serviceContexts = new ArrayList<>();\n\n        final ClusteredServiceContainer.Context clusteredServiceContext = new ClusteredServiceContainer.Context()\n            .aeronDirectoryName(aeronDirName)\n            .archiveContext(aeronArchiveContext.clone())\n            .clusterDir(new File(baseDir, CLUSTER_SUB_DIR))\n            .clusteredService(clusteredService)\n            .serviceId(0);\n        serviceContexts.add(clusteredServiceContext);\n\n        for (int i = 0; i < additionalServices.length; i++)\n        {\n            final ClusteredServiceContainer.Context additionalServiceContext = new ClusteredServiceContainer.Context()\n                .aeronDirectoryName(aeronDirName)\n                .archiveContext(aeronArchiveContext.clone())\n                .clusterDir(new File(baseDir, CLUSTER_SUB_DIR))\n                .clusteredService(additionalServices[i])\n                .serviceId(i + 1);\n            serviceContexts.add(additionalServiceContext);\n        }\n\n        return new ClusterConfig(\n            memberId,\n            ingressHostname,\n            hostname,\n            mediaDriverContext,\n            archiveContext,\n            aeronArchiveContext,\n            consensusModuleContext,\n            serviceContexts);\n    }\n\n    /**\n     * Create a new ClusterConfig. This call allows for 2 separate lists of hostnames, so that there can be 'external'\n     * addresses for ingress requests and 'internal' addresses that will handle all the cluster replication and\n     * control traffic.\n     *\n     * @param nodeId             id for this node.\n     * @param ingressHostnames   list of hostnames that will receive ingress request traffic.\n     * @param clusterHostnames   list of hostnames that will receive cluster traffic.\n     * @param portBase           base port to derive remaining ports from.\n     * @param clusteredService   instance of the clustered service that will run with this configuration.\n     * @param additionalServices instances of additional clustered services that will run with this configuration.\n     * @return configuration that wraps all aeron service configuration.\n     */\n    public static ClusterConfig create(\n        final int nodeId,\n        final List<String> ingressHostnames,\n        final List<String> clusterHostnames,\n        final int portBase,\n        final ClusteredService clusteredService,\n        final ClusteredService... additionalServices)\n    {\n        return create(\n            0,\n            nodeId,\n            ingressHostnames,\n            clusterHostnames,\n            portBase,\n            new File(System.getProperty(\"user.dir\")),\n            clusteredService,\n            additionalServices);\n    }\n\n    /**\n     * Create a new ClusterConfig. This only supports a single lists of hostnames.\n     *\n     * @param nodeId           id for this node.\n     * @param hostnames        list of hostnames that will receive ingress request and cluster traffic.\n     * @param portBase         base port to derive remaining ports from.\n     * @param clusteredService instance of the clustered service that will run on this node.\n     * @return configuration that wraps the detailed aeron service configuration.\n     */\n    public static ClusterConfig create(\n        final int nodeId,\n        final List<String> hostnames,\n        final int portBase,\n        final ClusteredService clusteredService)\n    {\n        return create(nodeId, hostnames, hostnames, portBase, clusteredService);\n    }\n\n    /**\n     * Set the same error handler for all contexts.\n     *\n     * @param errorHandler to receive errors.\n     */\n    public void errorHandler(final ErrorHandler errorHandler)\n    {\n        this.mediaDriverContext.errorHandler(errorHandler);\n        this.archiveContext.errorHandler(errorHandler);\n        this.aeronArchiveContext.errorHandler(errorHandler);\n        this.consensusModuleContext.errorHandler(errorHandler);\n        this.clusteredServiceContexts.forEach((ctx) -> ctx.errorHandler(errorHandler));\n    }\n\n    /**\n     * Set the aeron directory for all configuration contexts.\n     *\n     * @param aeronDir directory to use for aeron.\n     */\n    public void aeronDirectoryName(final String aeronDir)\n    {\n        this.mediaDriverContext.aeronDirectoryName(aeronDir);\n        this.archiveContext.aeronDirectoryName(aeronDir);\n        this.aeronArchiveContext.aeronDirectoryName(aeronDir);\n        this.consensusModuleContext.aeronDirectoryName(aeronDir);\n        this.clusteredServiceContexts.forEach(ctx -> ctx.aeronDirectoryName(aeronDir));\n    }\n\n    /**\n     * Set the base directory for cluster and archive.\n     *\n     * @param baseDir parent directory to be used for archive and cluster stored data.\n     */\n    public void baseDir(final File baseDir)\n    {\n        this.archiveContext.archiveDir(new File(baseDir, ARCHIVE_SUB_DIR));\n        this.consensusModuleContext.clusterDir(new File(baseDir, CLUSTER_SUB_DIR));\n        this.clusteredServiceContexts.forEach((ctx) -> ctx.clusterDir(new File(baseDir, CLUSTER_SUB_DIR)));\n    }\n\n    /**\n     * Gets the configuration's media driver context.\n     *\n     * @return configured {@link io.aeron.driver.MediaDriver.Context}.\n     * @see io.aeron.driver.MediaDriver.Context\n     */\n    public MediaDriver.Context mediaDriverContext()\n    {\n        return mediaDriverContext;\n    }\n\n    /**\n     * Gets the configuration's archive context.\n     *\n     * @return configured {@link io.aeron.archive.Archive.Context}.\n     * @see io.aeron.archive.Archive.Context\n     */\n    public Archive.Context archiveContext()\n    {\n        return archiveContext;\n    }\n\n    /**\n     * Gets the configuration's aeron archive context.\n     *\n     * @return configured {@link io.aeron.archive.Archive.Context}.\n     * @see io.aeron.archive.client.AeronArchive.Context\n     */\n    public AeronArchive.Context aeronArchiveContext()\n    {\n        return aeronArchiveContext;\n    }\n\n    /**\n     * Gets the configuration's consensus module context.\n     *\n     * @return configured {@link io.aeron.cluster.ConsensusModule.Context}.\n     * @see io.aeron.cluster.ConsensusModule.Context\n     */\n    public ConsensusModule.Context consensusModuleContext()\n    {\n        return consensusModuleContext;\n    }\n\n    /**\n     * Gets the configuration's clustered service container context.\n     *\n     * @return configured {@link io.aeron.cluster.service.ClusteredServiceContainer.Context}.\n     * @see io.aeron.cluster.service.ClusteredServiceContainer.Context\n     */\n    public ClusteredServiceContainer.Context clusteredServiceContext()\n    {\n        return clusteredServiceContexts.get(0);\n    }\n\n    /**\n     * Gets the configuration's list of clustered service container contexts.\n     *\n     * @return configured list of {@link io.aeron.cluster.service.ClusteredServiceContainer.Context}.\n     * @see io.aeron.cluster.service.ClusteredServiceContainer.Context\n     */\n    public List<ClusteredServiceContainer.Context> clusteredServiceContexts()\n    {\n        return clusteredServiceContexts;\n    }\n\n    /**\n     * memberId of this node.\n     *\n     * @return memberId.\n     */\n    public int memberId()\n    {\n        return memberId;\n    }\n\n    /**\n     * Hostname of this node that will receive ingress traffic.\n     *\n     * @return ingress hostname.\n     */\n    public String ingressHostname()\n    {\n        return ingressHostname;\n    }\n\n    /**\n     * Hostname of this node that will receive cluster traffic.\n     *\n     * @return cluster hostname\n     */\n    public String clusterHostname()\n    {\n        return clusterHostname;\n    }\n\n    /**\n     * String representing the cluster members configuration which can be used for\n     * {@link io.aeron.cluster.ClusterMember#parse(String)}.\n     *\n     * @param ingressHostnames of the cluster members.\n     * @param clusterHostnames of the cluster members internal address (can be the same as 'hostnames').\n     * @param portBase         initial port to derive other port from via appropriate node id and offset.\n     * @return the String which can be used for {@link io.aeron.cluster.ClusterMember#parse(String)}.\n     */\n    public static String clusterMembers(\n        final List<String> ingressHostnames, final List<String> clusterHostnames, final int portBase)\n    {\n        return clusterMembers(0, ingressHostnames, clusterHostnames, portBase);\n    }\n\n    /**\n     * String representing the cluster members configuration which can be used for\n     * {@link io.aeron.cluster.ClusterMember#parse(String)}.\n     *\n     * @param startingMemberId first memberId to be used in the list of clusterMembers. The memberId will increment by 1\n     *                         from that value for each entry.\n     * @param ingressHostnames of the cluster members.\n     * @param clusterHostnames of the cluster members internal address (can be the same as 'hostnames').\n     * @param portBase         initial port to derive other port from via appropriate node id and offset.\n     * @return the String which can be used for {@link io.aeron.cluster.ClusterMember#parse(String)}.\n     */\n    public static String clusterMembers(\n        final int startingMemberId,\n        final List<String> ingressHostnames,\n        final List<String> clusterHostnames,\n        final int portBase)\n    {\n        if (ingressHostnames.size() != clusterHostnames.size())\n        {\n            throw new IllegalArgumentException(\"ingressHostnames and clusterHostnames must be the same size\");\n        }\n\n        final StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < ingressHostnames.size(); i++)\n        {\n            final int memberId = i + startingMemberId;\n            sb.append(memberId);\n            sb.append(',').append(endpoint(memberId, ingressHostnames.get(i), portBase, CLIENT_FACING_PORT_OFFSET));\n            sb.append(',').append(endpoint(memberId, clusterHostnames.get(i), portBase, MEMBER_FACING_PORT_OFFSET));\n            sb.append(',').append(endpoint(memberId, clusterHostnames.get(i), portBase, LOG_PORT_OFFSET));\n            sb.append(',').append(endpoint(memberId, clusterHostnames.get(i), portBase, TRANSFER_PORT_OFFSET));\n            sb.append(',').append(endpoint(memberId, clusterHostnames.get(i), portBase, ARCHIVE_CONTROL_PORT_OFFSET));\n            sb.append('|');\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * Ingress endpoints generated from a list of hostnames.\n     *\n     * @param hostnames              for the cluster members.\n     * @param portBase               Base port for the cluster\n     * @param clientFacingPortOffset Offset for the client facing port\n     * @return a formatted string of ingress endpoints for connecting to a cluster.\n     */\n    public static String ingressEndpoints(\n        final List<String> hostnames,\n        final int portBase,\n        final int clientFacingPortOffset)\n    {\n        return ingressEndpoints(0, hostnames, portBase, clientFacingPortOffset);\n    }\n\n\n    /**\n     * Ingress endpoints generated from a list of hostnames.\n     *\n     * @param startingMemberId       first memberId to be used when generating the ports.\n     * @param hostnames              for the cluster members.\n     * @param portBase               Base port for the cluster\n     * @param clientFacingPortOffset Offset for the client facing port\n     * @return a formatted string of ingress endpoints for connecting to a cluster.\n     */\n    public static String ingressEndpoints(\n        final int startingMemberId,\n        final List<String> hostnames,\n        final int portBase,\n        final int clientFacingPortOffset)\n    {\n        final StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < hostnames.size(); i++)\n        {\n            final int memberId = i + startingMemberId;\n            sb.append(memberId).append('=');\n            sb.append(hostnames.get(i)).append(':').append(calculatePort(memberId, portBase, clientFacingPortOffset));\n            sb.append(',');\n        }\n\n        sb.setLength(sb.length() - 1);\n\n        return sb.toString();\n    }\n\n    /**\n     * Calculates a port for use with a node based on a specific offset.  Can be used with the predefined offsets, e.g.\n     * {@link ClusterConfig#ARCHIVE_CONTROL_PORT_OFFSET} or with custom offsets. For custom offsets select a value\n     * larger than largest predefined offsets.  A value larger than the largest predefined offset, but less than\n     * {@link ClusterConfig#PORTS_PER_NODE} is required.\n     *\n     * @param nodeId   The id for the member of the cluster.\n     * @param portBase The port base to be used.\n     * @param offset   The offset to add onto the port base\n     * @return a calculated port, which should be unique for the specified criteria.\n     */\n    public static int calculatePort(final int nodeId, final int portBase, final int offset)\n    {\n        return portBase + (nodeId * PORTS_PER_NODE) + offset;\n    }\n\n    private static String udpChannel(final int nodeId, final String hostname, final int portBase, final int portOffset)\n    {\n        final int port = calculatePort(nodeId, portBase, portOffset);\n        return \"aeron:udp?endpoint=\" + hostname + \":\" + port;\n    }\n\n    private static String endpoint(final int nodeId, final String hostname, final int portBase, final int portOffset)\n    {\n        return hostname + \":\" + calculatePort(nodeId, portBase, portOffset);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/cluster/EchoService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.cluster;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\n\nclass EchoService implements ClusteredService\n{\n    private static final int SEND_ATTEMPTS = 3;\n    protected Cluster cluster;\n    protected IdleStrategy idleStrategy;\n\n    public void onStart(final Cluster cluster, final Image snapshotImage)\n    {\n        this.cluster = cluster;\n        this.idleStrategy = cluster.idleStrategy();\n    }\n\n    public void onSessionOpen(final ClientSession session, final long timestamp)\n    {\n    }\n\n    public void onSessionClose(final ClientSession session, final long timestamp, final CloseReason closeReason)\n    {\n    }\n\n    public void onSessionMessage(\n        final ClientSession session,\n        final long timestamp,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        idleStrategy.reset();\n        int attempts = SEND_ATTEMPTS;\n        do\n        {\n            final long result = session.offer(buffer, offset, length);\n            if (result > 0)\n            {\n                return;\n            }\n            idleStrategy.idle();\n        }\n        while (--attempts > 0);\n    }\n\n    public void onTimerEvent(final long correlationId, final long timestamp)\n    {\n    }\n\n    public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n    {\n    }\n\n    public void onRoleChange(final Cluster.Role newRole)\n    {\n    }\n\n    public void onTerminate(final Cluster cluster)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/cluster/EchoServiceNode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.cluster;\n\nimport io.aeron.cluster.ClusteredMediaDriver;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.samples.cluster.tutorial.BasicAuctionClusteredService;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static java.lang.Integer.parseInt;\n\n/**\n * Node that launches the service for the {@link BasicAuctionClusteredService}.\n */\npublic final class EchoServiceNode\n{\n    private static ErrorHandler errorHandler(final String context)\n    {\n        return\n            (Throwable throwable) ->\n            {\n                System.err.println(context);\n                throwable.printStackTrace(System.err);\n            };\n    }\n\n    private static final int PORT_BASE = 9000;\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        final int nodeId = parseInt(System.getProperty(\"aeron.cluster.tutorial.nodeId\"));\n        final String hostnamesStr = System.getProperty(\n            \"aeron.cluster.tutorial.hostnames\", \"localhost,localhost,localhost\");\n        final String internalHostnamesStr = System.getProperty(\n            \"aeron.cluster.tutorial.hostnames.internal\", hostnamesStr);\n        final List<String> hostnames = Arrays.asList(hostnamesStr.split(\",\"));\n        final List<String> internalHostnames = Arrays.asList(internalHostnamesStr.split(\",\"));\n\n        final ClusterConfig clusterConfig = ClusterConfig.create(\n            nodeId, hostnames, internalHostnames, PORT_BASE, new EchoService());\n\n        clusterConfig.mediaDriverContext().errorHandler(EchoServiceNode.errorHandler(\"Media Driver\"));\n        clusterConfig.archiveContext()\n            .errorHandler(EchoServiceNode.errorHandler(\"Archive\"));\n        clusterConfig.aeronArchiveContext()\n            .errorHandler(EchoServiceNode.errorHandler(\"Aeron Archive\"));\n        clusterConfig.consensusModuleContext()\n            .errorHandler(errorHandler(\"Consensus Module\"));\n        clusterConfig.clusteredServiceContext()\n            .errorHandler(errorHandler(\"Clustered Service\"));\n\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            ClusteredMediaDriver ignore = ClusteredMediaDriver.launch(\n                clusterConfig.mediaDriverContext().terminationHook(barrier::signalAll),\n                clusterConfig.archiveContext(),\n                clusterConfig.consensusModuleContext().terminationHook(barrier::signalAll));\n            ClusteredServiceContainer ignore2 = ClusteredServiceContainer.launch(\n                clusterConfig.clusteredServiceContext().terminationHook(barrier::signalAll)))\n        {\n            System.out.println(\"[\" + nodeId + \"] Started Cluster Node on \" + hostnames.get(nodeId) + \"...\");\n            barrier.await();\n            System.out.println(\"[\" + nodeId + \"] Exiting\");\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/cluster/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Samples and supporting classes for using Aeron Cluster.\n */\npackage io.aeron.samples.cluster;"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/cluster/tutorial/BasicAuctionClusterClient.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.cluster.tutorial;\n\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.EgressListener;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.BackoffIdleStrategy;\nimport org.agrona.concurrent.IdleStrategy;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.samples.cluster.tutorial.BasicAuctionClusteredService.*;\nimport static io.aeron.samples.cluster.tutorial.BasicAuctionClusteredServiceNode.calculatePort;\n\n/**\n * Client for communicating with {@link BasicAuctionClusteredService}.\n */\n// tag::client[]\npublic class BasicAuctionClusterClient implements EgressListener\n// end::client[]\n{\n    private final MutableDirectBuffer actionBidBuffer = new ExpandableArrayBuffer();\n    private final IdleStrategy idleStrategy = new BackoffIdleStrategy();\n    private final long customerId;\n    private final int numOfBids;\n    private final int bidIntervalMs;\n\n    private long nextCorrelationId = 0;\n    private long lastBidSeen = 100;\n\n    /**\n     * Construct a new cluster client for the auction.\n     *\n     * @param customerId    for the client.\n     * @param numOfBids     to make as a client.\n     * @param bidIntervalMs between the bids.\n     */\n    public BasicAuctionClusterClient(final long customerId, final int numOfBids, final int bidIntervalMs)\n    {\n        this.customerId = customerId;\n        this.numOfBids = numOfBids;\n        this.bidIntervalMs = bidIntervalMs;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    // tag::response[]\n    public void onMessage(\n        final long clusterSessionId,\n        final long timestamp,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        final long correlationId = buffer.getLong(offset + CORRELATION_ID_OFFSET);\n        final long customerId = buffer.getLong(offset + CUSTOMER_ID_OFFSET);\n        final long currentPrice = buffer.getLong(offset + PRICE_OFFSET);\n        final boolean bidSucceed = 0 != buffer.getByte(offset + BID_SUCCEEDED_OFFSET);\n\n        lastBidSeen = currentPrice;\n\n        printOutput(\n            \"SessionMessage(\" + clusterSessionId + \", \" + correlationId + \",\" +\n            customerId + \", \" + currentPrice + \", \" + bidSucceed + \")\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onSessionEvent(\n        final long correlationId,\n        final long clusterSessionId,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final EventCode code,\n        final String detail)\n    {\n        printOutput(\n            \"SessionEvent(\" + correlationId + \", \" + leadershipTermId + \", \" +\n            leaderMemberId + \", \" + code + \", \" + detail + \")\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onNewLeader(\n        final long clusterSessionId,\n        final long leadershipTermId,\n        final int leaderMemberId,\n        final String ingressEndpoints)\n    {\n        printOutput(\"NewLeader(\" + clusterSessionId + \", \" + leadershipTermId + \", \" + leaderMemberId + \")\");\n    }\n    // end::response[]\n\n    private void bidInAuction(final AeronCluster aeronCluster)\n    {\n        long keepAliveDeadlineMs = 0;\n        long nextBidDeadlineMs = System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000);\n        int bidsLeftToSend = numOfBids;\n\n        while (!Thread.currentThread().isInterrupted())\n        {\n            final long currentTimeMs = System.currentTimeMillis();\n\n            if (nextBidDeadlineMs <= currentTimeMs && bidsLeftToSend > 0)\n            {\n                final long price = lastBidSeen + ThreadLocalRandom.current().nextInt(10);\n                final long correlationId = sendBid(aeronCluster, price);\n\n                nextBidDeadlineMs = currentTimeMs + ThreadLocalRandom.current().nextInt(bidIntervalMs);\n                keepAliveDeadlineMs = currentTimeMs + 1_000;       // <1>\n                --bidsLeftToSend;\n\n                printOutput(\n                    \"Sent(\" + (correlationId) + \", \" + customerId + \", \" + price + \") bidsRemaining=\" +\n                    bidsLeftToSend);\n            }\n            else if (keepAliveDeadlineMs <= currentTimeMs)         // <2>\n            {\n                if (bidsLeftToSend > 0)\n                {\n                    aeronCluster.sendKeepAlive();\n                    keepAliveDeadlineMs = currentTimeMs + 1_000;   // <3>\n                }\n                else\n                {\n                    break;\n                }\n            }\n\n            idleStrategy.idle(aeronCluster.pollEgress());\n        }\n    }\n\n    // tag::publish[]\n    private long sendBid(final AeronCluster aeronCluster, final long price)\n    {\n        final long correlationId = nextCorrelationId++;\n        actionBidBuffer.putLong(CORRELATION_ID_OFFSET, correlationId);            // <1>\n        actionBidBuffer.putLong(CUSTOMER_ID_OFFSET, customerId);\n        actionBidBuffer.putLong(PRICE_OFFSET, price);\n\n        idleStrategy.reset();\n        while (aeronCluster.offer(actionBidBuffer, 0, BID_MESSAGE_LENGTH) < 0)    // <2>\n        {\n            idleStrategy.idle(aeronCluster.pollEgress());                         // <3>\n        }\n\n        return correlationId;\n    }\n    // end::publish[]\n\n    /**\n     * Ingress endpoints generated from a list of hostnames.\n     *\n     * @param hostnames for the cluster members.\n     * @return a formatted string of ingress endpoints for connecting to a cluster.\n     */\n    public static String ingressEndpoints(final List<String> hostnames)\n    {\n        final StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < hostnames.size(); i++)\n        {\n            sb.append(i).append('=');\n            sb.append(hostnames.get(i)).append(':').append(\n                calculatePort(i, BasicAuctionClusteredServiceNode.CLIENT_FACING_PORT_OFFSET));\n            sb.append(',');\n        }\n\n        sb.setLength(sb.length() - 1);\n\n        return sb.toString();\n    }\n\n    private void printOutput(final String message)\n    {\n        System.out.println(message);\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        final int customerId = Integer.parseInt(System.getProperty(\"aeron.cluster.tutorial.customerId\"));       // <1>\n        final int numOfBids = Integer.parseInt(System.getProperty(\"aeron.cluster.tutorial.numOfBids\"));         // <2>\n        final int bidIntervalMs = Integer.parseInt(System.getProperty(\"aeron.cluster.tutorial.bidIntervalMs\")); // <3>\n\n        final String[] hostnames = System.getProperty(\n            \"aeron.cluster.tutorial.hostnames\", \"localhost,localhost,localhost\").split(\",\");\n        final String ingressEndpoints = ingressEndpoints(Arrays.asList(hostnames));\n\n        final BasicAuctionClusterClient client = new BasicAuctionClusterClient(customerId, numOfBids, bidIntervalMs);\n\n        // tag::connect[]\n        try (\n            MediaDriver mediaDriver = MediaDriver.launchEmbedded(new MediaDriver.Context()                      // <1>\n                .threadingMode(ThreadingMode.SHARED)\n                .dirDeleteOnStart(true)\n                .dirDeleteOnShutdown(true));\n            AeronCluster aeronCluster = AeronCluster.connect(\n                new AeronCluster.Context()\n                .egressListener(client)                                                                         // <2>\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\")                                                // <3>\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .ingressChannel(\"aeron:udp\")                                                                    // <4>\n                .ingressEndpoints(ingressEndpoints)))                                                           // <5>\n        {\n        // end::connect[]\n            client.bidInAuction(aeronCluster);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.cluster.tutorial;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.*;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.concurrent.IdleStrategy;\n\nimport java.util.Objects;\n\n/**\n * Auction service implementing the business logic.\n */\n// tag::new_service[]\npublic class BasicAuctionClusteredService implements ClusteredService\n// end::new_service[]\n{\n    static final int CORRELATION_ID_OFFSET = 0;\n    static final int CUSTOMER_ID_OFFSET = CORRELATION_ID_OFFSET + BitUtil.SIZE_OF_LONG;\n    static final int PRICE_OFFSET = CUSTOMER_ID_OFFSET + BitUtil.SIZE_OF_LONG;\n    static final int BID_MESSAGE_LENGTH = PRICE_OFFSET + BitUtil.SIZE_OF_LONG;\n    static final int BID_SUCCEEDED_OFFSET = BID_MESSAGE_LENGTH;\n    static final int EGRESS_MESSAGE_LENGTH = BID_SUCCEEDED_OFFSET + BitUtil.SIZE_OF_BYTE;\n\n    static final int SNAPSHOT_CUSTOMER_ID_OFFSET = 0;\n    static final int SNAPSHOT_PRICE_OFFSET = SNAPSHOT_CUSTOMER_ID_OFFSET + BitUtil.SIZE_OF_LONG;\n    static final int SNAPSHOT_MESSAGE_LENGTH = SNAPSHOT_PRICE_OFFSET + BitUtil.SIZE_OF_LONG;\n\n    private final MutableDirectBuffer egressMessageBuffer = new ExpandableArrayBuffer();\n    private final MutableDirectBuffer snapshotBuffer = new ExpandableArrayBuffer();\n\n    // tag::state[]\n    private final Auction auction = new Auction();\n    // end::state[]\n    private Cluster cluster;\n    private IdleStrategy idleStrategy;\n\n    /**\n     * {@inheritDoc}\n     */\n    // tag::start[]\n    public void onStart(final Cluster cluster, final Image snapshotImage)\n    {\n        this.cluster = cluster;                      // <1>\n        this.idleStrategy = cluster.idleStrategy();  // <2>\n\n        if (null != snapshotImage)                   // <3>\n        {\n            loadSnapshot(cluster, snapshotImage);\n        }\n    }\n    // end::start[]\n\n    /**\n     * {@inheritDoc}\n     */\n    // tag::message[]\n    public void onSessionMessage(\n        final ClientSession session,\n        final long timestamp,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        final long correlationId = buffer.getLong(offset + CORRELATION_ID_OFFSET);                   // <1>\n        final long customerId = buffer.getLong(offset + CUSTOMER_ID_OFFSET);\n        final long price = buffer.getLong(offset + PRICE_OFFSET);\n\n        final boolean bidSucceeded = auction.attemptBid(price, customerId);                          // <2>\n\n        if (null != session)                                                                         // <3>\n        {\n            egressMessageBuffer.putLong(CORRELATION_ID_OFFSET, correlationId);                       // <4>\n            egressMessageBuffer.putLong(CUSTOMER_ID_OFFSET, auction.getCurrentWinningCustomerId());\n            egressMessageBuffer.putLong(PRICE_OFFSET, auction.getBestPrice());\n            egressMessageBuffer.putByte(BID_SUCCEEDED_OFFSET, bidSucceeded ? (byte)1 : (byte)0);\n\n            idleStrategy.reset();\n            while (session.offer(egressMessageBuffer, 0, EGRESS_MESSAGE_LENGTH) < 0)                 // <5>\n            {\n                idleStrategy.idle();                                                                 // <6>\n            }\n        }\n    }\n    // end::message[]\n\n    /**\n     * {@inheritDoc}\n     */\n    // tag::takeSnapshot[]\n    public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n    {\n        snapshotBuffer.putLong(SNAPSHOT_CUSTOMER_ID_OFFSET, auction.getCurrentWinningCustomerId());  // <1>\n        snapshotBuffer.putLong(SNAPSHOT_PRICE_OFFSET, auction.getBestPrice());\n\n        idleStrategy.reset();\n        while (snapshotPublication.offer(snapshotBuffer, 0, SNAPSHOT_MESSAGE_LENGTH) < 0)            // <2>\n        {\n            idleStrategy.idle();\n        }\n    }\n    // end::takeSnapshot[]\n\n    // tag::loadSnapshot[]\n    private void loadSnapshot(final Cluster cluster, final Image snapshotImage)\n    {\n        final MutableBoolean isAllDataLoaded = new MutableBoolean(false);\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) ->         // <1>\n        {\n            assert length >= SNAPSHOT_MESSAGE_LENGTH;                                       // <2>\n\n            final long customerId = buffer.getLong(offset + SNAPSHOT_CUSTOMER_ID_OFFSET);\n            final long price = buffer.getLong(offset + SNAPSHOT_PRICE_OFFSET);\n\n            auction.loadInitialState(price, customerId);                                    // <3>\n\n            isAllDataLoaded.set(true);\n        };\n\n        while (!snapshotImage.isEndOfStream())                                              // <4>\n        {\n            final int fragmentsPolled = snapshotImage.poll(fragmentHandler, 1);\n\n            if (isAllDataLoaded.value)                                                      // <5>\n            {\n                break;\n            }\n\n            idleStrategy.idle(fragmentsPolled);                                             // <6>\n        }\n\n        assert snapshotImage.isEndOfStream();                                               // <7>\n        assert isAllDataLoaded.value;\n    }\n    // end::loadSnapshot[]\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onRoleChange(final Cluster.Role newRole)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTerminate(final Cluster cluster)\n    {\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onSessionOpen(final ClientSession session, final long timestamp)\n    {\n        System.out.println(\"onSessionOpen(\" + session + \")\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onSessionClose(final ClientSession session, final long timestamp, final CloseReason closeReason)\n    {\n        System.out.println(\"onSessionClose(\" + session + \")\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onTimerEvent(final long correlationId, final long timestamp)\n    {\n    }\n\n    static class Auction\n    {\n        private long bestPrice = 0;\n        private long currentWinningCustomerId = -1;\n\n        void loadInitialState(final long price, final long customerId)\n        {\n            bestPrice = price;\n            currentWinningCustomerId = customerId;\n        }\n\n        boolean attemptBid(final long price, final long customerId)\n        {\n            System.out.println(\"attemptBid(this=\" + this + \", price=\" + price + \",customerId=\" + customerId + \")\");\n\n            if (price <= bestPrice)\n            {\n                return false;\n            }\n\n            bestPrice = price;\n            currentWinningCustomerId = customerId;\n\n            return true;\n        }\n\n        long getBestPrice()\n        {\n            return bestPrice;\n        }\n\n        long getCurrentWinningCustomerId()\n        {\n            return currentWinningCustomerId;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public boolean equals(final Object o)\n        {\n            if (this == o)\n            {\n                return true;\n            }\n\n            if (o == null || getClass() != o.getClass())\n            {\n                return false;\n            }\n\n            final Auction auction = (Auction)o;\n\n            return bestPrice == auction.bestPrice && currentWinningCustomerId == auction.currentWinningCustomerId;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public int hashCode()\n        {\n            return Objects.hash(bestPrice, currentWinningCustomerId);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public String toString()\n        {\n            return \"Auction{\" +\n                \"bestPrice=\" + bestPrice +\n                \", currentWinningCustomerId=\" + currentWinningCustomerId +\n                '}';\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean equals(final Object o)\n    {\n        if (this == o)\n        {\n            return true;\n        }\n\n        if (o == null || getClass() != o.getClass())\n        {\n            return false;\n        }\n\n        final BasicAuctionClusteredService that = (BasicAuctionClusteredService)o;\n\n        return auction.equals(that.auction);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int hashCode()\n    {\n        return Objects.hash(auction);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"BasicAuctionClusteredService{\" +\n            \"auction=\" + auction +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/cluster/tutorial/BasicAuctionClusteredServiceNode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.cluster.tutorial;\n\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.ClusteredMediaDriver;\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.MinMulticastFlowControlSupplier;\nimport io.aeron.driver.ThreadingMode;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.io.File;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static java.lang.Integer.parseInt;\n\n/**\n * Node that launches the service for the {@link BasicAuctionClusteredService}.\n */\n// tag::new_service[]\npublic class BasicAuctionClusteredServiceNode\n// end::new_service[]\n{\n    private static ErrorHandler errorHandler(final String context)\n    {\n        return\n            (Throwable throwable) ->\n            {\n                System.err.println(context);\n                throwable.printStackTrace(System.err);\n            };\n    }\n\n    // tag::ports[]\n    private static final int PORT_BASE = 9000;\n    private static final int PORTS_PER_NODE = 100;\n    private static final int ARCHIVE_CONTROL_PORT_OFFSET = 1;\n    static final int CLIENT_FACING_PORT_OFFSET = 2;\n    private static final int MEMBER_FACING_PORT_OFFSET = 3;\n    private static final int LOG_PORT_OFFSET = 4;\n    private static final int TRANSFER_PORT_OFFSET = 5;\n    private static final int LOG_CONTROL_PORT_OFFSET = 6;\n    private static final int TERM_LENGTH = 64 * 1024;\n\n    static int calculatePort(final int nodeId, final int offset)\n    {\n        return PORT_BASE + (nodeId * PORTS_PER_NODE) + offset;\n    }\n    // end::ports[]\n\n    // tag::udp_channel[]\n    private static String udpChannel(final int nodeId, final String hostname, final int portOffset)\n    {\n        final int port = calculatePort(nodeId, portOffset);\n        return new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .termLength(TERM_LENGTH)\n            .endpoint(hostname + \":\" + port)\n            .build();\n    }\n    // end::udp_channel[]\n\n    private static String logControlChannel(final int nodeId, final String hostname, final int portOffset)\n    {\n        final int port = calculatePort(nodeId, portOffset);\n        return new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .termLength(TERM_LENGTH)\n            .controlMode(CommonContext.MDC_CONTROL_MODE_MANUAL)\n            .controlEndpoint(hostname + \":\" + port)\n            .build();\n    }\n\n    private static String logReplicationChannel(final String hostname)\n    {\n        return new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(hostname + \":0\")\n            .build();\n    }\n\n    private static String clusterMembers(final List<String> hostnames)\n    {\n        final StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < hostnames.size(); i++)\n        {\n            sb.append(i);\n            sb.append(',').append(hostnames.get(i)).append(':').append(calculatePort(i, CLIENT_FACING_PORT_OFFSET));\n            sb.append(',').append(hostnames.get(i)).append(':').append(calculatePort(i, MEMBER_FACING_PORT_OFFSET));\n            sb.append(',').append(hostnames.get(i)).append(':').append(calculatePort(i, LOG_PORT_OFFSET));\n            sb.append(',').append(hostnames.get(i)).append(':').append(calculatePort(i, TRANSFER_PORT_OFFSET));\n            sb.append(',').append(hostnames.get(i)).append(':')\n                .append(calculatePort(i, ARCHIVE_CONTROL_PORT_OFFSET));\n            sb.append('|');\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    @SuppressWarnings(\"try\")\n    // tag::main[]\n    public static void main(final String[] args)\n    {\n        final int nodeId = parseInt(System.getProperty(\"aeron.cluster.tutorial.nodeId\"));                        // <1>\n        final String[] hostnames =\n            System.getProperty(\"aeron.cluster.tutorial.hostnames\", \"localhost,localhost,localhost\").split(\",\");  // <2>\n        final String hostname = hostnames[nodeId];\n\n        final File baseDir = new File(System.getProperty(\"user.dir\"), \"node\" + nodeId);                          // <3>\n        final String aeronDirName = CommonContext.getAeronDirectoryName() + \"-\" + nodeId + \"-driver\";\n\n        // end::main[]\n\n        // tag::media_driver[]\n        final MediaDriver.Context mediaDriverContext = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDirName)\n            .threadingMode(ThreadingMode.SHARED)\n            .termBufferSparseFile(true)\n            .multicastFlowControlSupplier(new MinMulticastFlowControlSupplier())\n            .errorHandler(BasicAuctionClusteredServiceNode.errorHandler(\"Media Driver\"));\n        // end::media_driver[]\n\n        final AeronArchive.Context replicationArchiveContext = new AeronArchive.Context()\n            .controlResponseChannel(\"aeron:udp?endpoint=\" + hostname + \":0\");\n\n        // tag::archive[]\n        final Archive.Context archiveContext = new Archive.Context()\n            .aeronDirectoryName(aeronDirName)\n            .archiveDir(new File(baseDir, \"archive\"))\n            .controlChannel(udpChannel(nodeId, hostname, ARCHIVE_CONTROL_PORT_OFFSET))\n            .archiveClientContext(replicationArchiveContext)\n            .localControlChannel(\"aeron:ipc?term-length=64k\")\n            .recordingEventsEnabled(false)\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .replicationChannel(\"aeron:udp?endpoint=\" + hostname + \":0\");\n        // end::archive[]\n\n        // tag::archive_client[]\n        final AeronArchive.Context aeronArchiveContext = new AeronArchive.Context()\n            .lock(NoOpLock.INSTANCE)\n            .controlRequestChannel(archiveContext.localControlChannel())\n            .controlResponseChannel(archiveContext.localControlChannel())\n            .aeronDirectoryName(aeronDirName);\n        // end::archive_client[]\n\n        // tag::consensus_module[]\n        final ConsensusModule.Context consensusModuleContext = new ConsensusModule.Context()\n            .errorHandler(errorHandler(\"Consensus Module\"))\n            .clusterMemberId(nodeId)                                                            // <1>\n            .clusterMembers(clusterMembers(Arrays.asList(hostnames)))                           // <2>\n            .clusterDir(new File(baseDir, \"cluster\"))                                           // <3>\n            .ingressChannel(\"aeron:udp?term-length=64k\")                                        // <4>\n            .replicationChannel(logReplicationChannel(hostname))                                // <5>\n            .archiveContext(aeronArchiveContext.clone());                                       // <6>\n        // end::consensus_module[]\n\n        // tag::clustered_service[]\n        final ClusteredServiceContainer.Context clusteredServiceContext =\n            new ClusteredServiceContainer.Context()\n            .aeronDirectoryName(aeronDirName)                                                   // <1>\n            .archiveContext(aeronArchiveContext.clone())                                        // <2>\n            .clusterDir(new File(baseDir, \"cluster\"))\n            .clusteredService(new BasicAuctionClusteredService())                               // <3>\n            .errorHandler(errorHandler(\"Clustered Service\"));\n        // end::clustered_service[]\n\n        // tag::running[]\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();                       // <1>\n            ClusteredMediaDriver clusteredMediaDriver = ClusteredMediaDriver.launch(\n                mediaDriverContext.terminationHook(barrier::signalAll),\n                archiveContext,\n                consensusModuleContext.terminationHook(barrier::signalAll));                    // <2>\n            ClusteredServiceContainer container = ClusteredServiceContainer.launch(\n                clusteredServiceContext.terminationHook(barrier::signalAll)))                   // <3>\n        {\n            System.out.println(\"[\" + nodeId + \"] Started Cluster Node on \" + hostname + \"...\");\n            barrier.await();                                                                    // <4>\n            System.out.println(\"[\" + nodeId + \"] Exiting...\");\n        }\n        // end::running[]\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/cluster/tutorial/SingleNodeCluster.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.cluster.tutorial;\n\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.cluster.ClusterControl;\nimport io.aeron.cluster.ClusteredMediaDriver;\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.EgressListener;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.samples.cluster.ClusterConfig;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.console.ContinueBarrier;\n\nimport java.util.Collections;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.samples.cluster.ClusterConfig.*;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\n\n/**\n * Single Node Cluster that includes everything needed to run all in one place. Includes a simple service to show\n * event processing, and includes a cluster client.\n * <p>\n * Perfect for playing around with the Cluster.\n */\npublic final class SingleNodeCluster implements AutoCloseable\n{\n    private static final int SEND_ATTEMPTS = 3;\n    private static final int MESSAGE_ID = 1;\n    private static final int TIMER_ID = 2;\n    private static final int PORT_BASE = 9000;\n\n    // cluster side\n    private final ClusterConfig config;\n    private final ClusteredMediaDriver clusteredMediaDriver;\n    private final ClusteredServiceContainer container;\n\n    // cluster client side\n    private MediaDriver clientMediaDriver;\n    private AeronCluster client;\n    private final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n    private final ExpandableArrayBuffer msgBuffer = new ExpandableArrayBuffer();\n    private final EgressListener egressMessageListener = new EgressListener()\n    {\n        /**\n         * {@inheritDoc}\n         */\n        public void onMessage(\n            final long clusterSessionId,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            System.out.println(\"egress onMessage \" + clusterSessionId);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onNewLeader(\n            final long clusterSessionId,\n            final long leadershipTermId,\n            final int leaderMemberId,\n            final String ingressEndpoints)\n        {\n            System.out.println(\"SingleNodeCluster.onNewLeader\");\n        }\n    };\n\n    static class Service implements ClusteredService\n    {\n        protected Cluster cluster;\n        protected IdleStrategy idleStrategy;\n        private int messageCount = 0;\n        private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onStart(final Cluster cluster, final Image snapshotImage)\n        {\n            this.cluster = cluster;\n            this.idleStrategy = cluster.idleStrategy();\n\n            if (null != snapshotImage)\n            {\n                System.out.println(\"onStart load snapshot\");\n                final FragmentHandler fragmentHandler =\n                    (buffer, offset, length, header) -> messageCount = buffer.getInt(offset);\n\n                idleStrategy.reset();\n                while (snapshotImage.poll(fragmentHandler, 1) <= 0)\n                {\n                    idleStrategy.idle();\n                }\n\n                System.out.println(\"snapshot messageCount=\" + messageCount);\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onSessionOpen(final ClientSession session, final long timestamp)\n        {\n            System.out.println(\"onSessionOpen \" + session.id());\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onSessionClose(final ClientSession session, final long timestamp, final CloseReason closeReason)\n        {\n            System.out.println(\"onSessionClose \" + session.id() + \" \" + closeReason);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onSessionMessage(\n            final ClientSession session,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            messageCount++;\n            System.out.println(cluster.role() + \" onSessionMessage \" + session.id() + \" count=\" + messageCount);\n\n            final int id = buffer.getInt(offset);\n            if (TIMER_ID == id)\n            {\n                idleStrategy.reset();\n                while (!cluster.scheduleTimer(serviceCorrelationId(1), cluster.time() + 1_000))\n                {\n                    idleStrategy.idle();\n                }\n            }\n            else\n            {\n                echoMessage(session, buffer, offset, length);\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onTimerEvent(final long correlationId, final long timestamp)\n        {\n            System.out.println(\"onTimerEvent \" + correlationId);\n\n            final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n            buffer.putInt(0, 1);\n\n            cluster.forEachClientSession((clientSession) -> echoMessage(clientSession, buffer, 0, SIZE_OF_INT));\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n        {\n            System.out.println(\"onTakeSnapshot messageCount=\" + messageCount);\n\n            buffer.putInt(0, messageCount);\n            idleStrategy.reset();\n            while (snapshotPublication.offer(buffer, 0, 4) < 0)\n            {\n                idleStrategy.idle();\n            }\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onRoleChange(final Cluster.Role newRole)\n        {\n            System.out.println(\"onRoleChange \" + newRole);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onTerminate(final Cluster cluster)\n        {\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public void onNewLeadershipTermEvent(\n            final long leadershipTermId,\n            final long logPosition,\n            final long timestamp,\n            final long termBaseLogPosition,\n            final int leaderMemberId,\n            final int logSessionId,\n            final TimeUnit timeUnit,\n            final int appVersion)\n        {\n            System.out.println(\"onNewLeadershipTermEvent\");\n        }\n\n        protected long serviceCorrelationId(final int correlationId)\n        {\n            return ((long)cluster.context().serviceId()) << 56 | correlationId;\n        }\n\n        private void echoMessage(\n            final ClientSession session, final DirectBuffer buffer, final int offset, final int length)\n        {\n            idleStrategy.reset();\n            int attempts = SEND_ATTEMPTS;\n            do\n            {\n                final long result = session.offer(buffer, offset, length);\n                if (result > 0)\n                {\n                    return;\n                }\n                idleStrategy.idle();\n            }\n            while (--attempts > 0);\n        }\n    }\n\n    /**\n     * Create and launch a new single node cluster.\n     *\n     * @param externalService to run in the container.\n     * @param isCleanStart    to indicate if a clean start should be made.\n     */\n    public SingleNodeCluster(final ClusteredService externalService, final boolean isCleanStart)\n    {\n        final ClusteredService service = null == externalService ? new SingleNodeCluster.Service() : externalService;\n        config = ClusterConfig.create(0, Collections.singletonList(\"localhost\"), PORT_BASE, service);\n\n        config.mediaDriverContext().dirDeleteOnStart(true);\n        config.archiveContext().deleteArchiveOnStart(isCleanStart);\n        config.consensusModuleContext()\n            .deleteDirOnStart(isCleanStart)\n            .ingressChannel(\"aeron:udp?endpoint=\" + config.ingressHostname() + \":\" +\n            calculatePort(config.memberId(), PORT_BASE, CLIENT_FACING_PORT_OFFSET));\n\n        clusteredMediaDriver = ClusteredMediaDriver.launch(\n            config.mediaDriverContext(),\n            config.archiveContext(),\n            config.consensusModuleContext());\n\n        container = ClusteredServiceContainer.launch(config.clusteredServiceContext());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        final ErrorHandler errorHandler = clusteredMediaDriver.mediaDriver().context().errorHandler();\n        CloseHelper.close(errorHandler, client);\n        CloseHelper.close(errorHandler, clientMediaDriver);\n        CloseHelper.close(errorHandler, clusteredMediaDriver.consensusModule());\n        CloseHelper.close(errorHandler, container);\n        CloseHelper.close(clusteredMediaDriver); // ErrorHandler will be closed during that call so can't use it\n    }\n\n    void connectClientToCluster()\n    {\n        final String aeronDirectoryName = CommonContext.getAeronDirectoryName() + \"-client\";\n\n        clientMediaDriver = MediaDriver.launch(\n            new MediaDriver.Context()\n                .threadingMode(ThreadingMode.SHARED)\n                .dirDeleteOnStart(true)\n                .dirDeleteOnShutdown(true)\n                .errorHandler(Throwable::printStackTrace)\n                .aeronDirectoryName(aeronDirectoryName));\n\n        client = AeronCluster.connect(\n            new AeronCluster.Context()\n                .errorHandler(Throwable::printStackTrace)\n                .egressListener(egressMessageListener)\n                .aeronDirectoryName(aeronDirectoryName)\n                .ingressChannel(\"aeron:udp\")\n                .ingressEndpoints(ingressEndpoints(\n                    Collections.singletonList(config.ingressHostname()), PORT_BASE, CLIENT_FACING_PORT_OFFSET)));\n    }\n\n    void sendMessageToCluster(final int id, final int messageLength)\n    {\n        msgBuffer.putInt(0, id);\n        idleStrategy.reset();\n        while (client.offer(msgBuffer, 0, messageLength) < 0)\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    int pollEgress()\n    {\n        return null == client ? 0 : client.pollEgress();\n    }\n\n    void pollEgressUntilMessage()\n    {\n        idleStrategy.reset();\n        while (pollEgress() <= 0)\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    void takeSnapshot()\n    {\n        final ConsensusModule.Context consensusModuleContext = clusteredMediaDriver.consensusModule().context();\n        final AtomicCounter snapshotCounter = consensusModuleContext.snapshotCounter();\n        final long snapshotCount = snapshotCounter.get();\n\n        final AtomicCounter controlToggle = ClusterControl.findControlToggle(\n            clusteredMediaDriver.mediaDriver().context().countersManager(),\n            consensusModuleContext.clusterId());\n        ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle);\n\n        idleStrategy.reset();\n        while (snapshotCounter.get() <= snapshotCount)\n        {\n            idleStrategy.idle();\n        }\n    }\n\n    static void sendSingleMessageAndEchoBack()\n    {\n        try (SingleNodeCluster cluster = new SingleNodeCluster(null, true))\n        {\n            cluster.connectClientToCluster();\n            cluster.sendMessageToCluster(MESSAGE_ID, 4);\n            cluster.pollEgressUntilMessage();\n\n            final ContinueBarrier barrier = new ContinueBarrier(\"continue\");\n            barrier.await();\n        }\n    }\n\n    static void loadPreviousLogAndSendAnotherMessageAndEchoBack()\n    {\n        try (SingleNodeCluster cluster = new SingleNodeCluster(null, false))\n        {\n            cluster.connectClientToCluster();\n            cluster.sendMessageToCluster(MESSAGE_ID, 4);\n            cluster.pollEgressUntilMessage();\n\n            final ContinueBarrier barrier = new ContinueBarrier(\"continue\");\n            barrier.await();\n        }\n    }\n\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     */\n    public static void main(final String[] args)\n    {\n        sendSingleMessageAndEchoBack();\n        loadPreviousLogAndSendAnotherMessageAndEchoBack();\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/cluster/tutorial/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Samples for getting an overview of using Aeron Cluster with the basic tutorial.\n */\npackage io.aeron.samples.cluster.tutorial;"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/CreateEchoPair.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo;\n\nclass CreateEchoPair extends ProvisioningMessage\n{\n    final long correlationId;\n    final String subscriptionChannel;\n    final int subscriptionStream;\n    final String publicationChannel;\n    final int publicationStream;\n\n    CreateEchoPair(\n        final long correlationId,\n        final String subscriptionChannel,\n        final int subscriptionStream,\n        final String publicationChannel,\n        final int publicationStream)\n    {\n        this.correlationId = correlationId;\n        this.subscriptionChannel = subscriptionChannel;\n        this.subscriptionStream = subscriptionStream;\n        this.publicationChannel = publicationChannel;\n        this.publicationStream = publicationStream;\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/EchoPair.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo;\n\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.samples.echo.api.EchoMonitorMBean;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\n\n/**\n * Pub/sub pair that will copy all incoming fragments from the subscription onto the publication.\n */\npublic class EchoPair implements ControlledFragmentHandler, AutoCloseable\n{\n    private static final int FRAGMENT_LIMIT = 10;\n\n    private final long correlationId;\n    private final Subscription subscription;\n    private final Publication publication;\n\n    private long notConnectedCount = 0;\n    private long backPressureCount = 0;\n    private long adminActionCount = 0;\n    private long closedCount = 0;\n    private long maxSessionExceededCount = 0;\n\n    private long fragmentCount = 0;\n    private long byteCount = 0;\n\n    /**\n     * Construct the echo pair.\n     *\n     * @param correlationId user supplied correlation id.\n     * @param subscription  to read fragments from.\n     * @param publication   to send fragments back to.\n     */\n    public EchoPair(final long correlationId, final Subscription subscription, final Publication publication)\n    {\n        this.correlationId = correlationId;\n        this.subscription = subscription;\n        this.publication = publication;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final long offerPosition = publication.offer(buffer, offset, length);\n        if (Publication.NOT_CONNECTED == offerPosition)\n        {\n            notConnectedCount++;\n            return Action.ABORT;\n        }\n        else if (Publication.BACK_PRESSURED == offerPosition)\n        {\n            backPressureCount++;\n            return Action.ABORT;\n        }\n        else if (Publication.ADMIN_ACTION == offerPosition)\n        {\n            adminActionCount++;\n            return Action.ABORT;\n        }\n        else if (Publication.CLOSED == offerPosition)\n        {\n            closedCount++;\n            return Action.CONTINUE;\n        }\n        else if (Publication.MAX_POSITION_EXCEEDED == offerPosition)\n        {\n            maxSessionExceededCount++;\n            return Action.CONTINUE;\n        }\n        else\n        {\n            fragmentCount++;\n            byteCount += length;\n            return Action.COMMIT;\n        }\n    }\n\n    /**\n     * Poll subscription of the echo pair.\n     *\n     * @return number of fragments processed.\n     */\n    public int poll()\n    {\n        return subscription.controlledPoll(this, FRAGMENT_LIMIT);\n    }\n\n    /**\n     * Get the correlationId.\n     *\n     * @return user supplied correlationId.\n     */\n    public long correlationId()\n    {\n        return correlationId;\n    }\n\n    /**\n     * Get the monitoring MBean for this echo pair.\n     *\n     * @return An instance of the monitoring MBean that can be installed into a JMX container.\n     */\n    public EchoMonitorMBean monitor()\n    {\n        return new EchoMonitor();\n    }\n\n    /**\n     * Close the echo pair.\n     */\n    public void close()\n    {\n        CloseHelper.quietCloseAll(publication, subscription);\n    }\n\n    private final class EchoMonitor implements EchoMonitorMBean\n    {\n        /**\n         * Get the correlationId.\n         *\n         * @return correlationId.\n         */\n        public long getCorrelationId()\n        {\n            return correlationId;\n        }\n\n        /**\n         * Number of times echo pair has experienced back pressure when copying to the publication.\n         *\n         * @return number of back pressure events.\n         */\n        public long getBackPressureCount()\n        {\n            return backPressureCount;\n        }\n\n        /**\n         * Number of fragments processed.\n         *\n         * @return number of fragments.\n         */\n        public long getFragmentCount()\n        {\n            return fragmentCount;\n        }\n\n        /**\n         * Number of bytes processed.\n         *\n         * @return number of bytes.\n         */\n        public long getByteCount()\n        {\n            return byteCount;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/Provisioning.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo;\n\nimport io.aeron.Aeron;\nimport io.aeron.ConcurrentPublication;\nimport io.aeron.Subscription;\nimport io.aeron.samples.echo.api.EchoMonitorMBean;\nimport io.aeron.samples.echo.api.ProvisioningConstants;\nimport io.aeron.samples.echo.api.ProvisioningMBean;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.concurrent.ManyToOneConcurrentArrayQueue;\n\nimport javax.management.*;\nimport java.lang.management.ManagementFactory;\n\n/**\n * Implementation of the ProvisionMBean to manage pub/sub echo pairs for testing.\n */\npublic class Provisioning implements ProvisioningMBean\n{\n    private final Aeron aeron;\n    private final Long2ObjectHashMap<EchoPair> echoPairByCorrelationId = new Long2ObjectHashMap<>();\n    private final ManyToOneConcurrentArrayQueue<ProvisioningMessage> provisioningMessageQ =\n        new ManyToOneConcurrentArrayQueue<>(1024);\n\n    /**\n     * Construct using the specified Aeron instance.\n     *\n     * @param aeron Aeron client instance.\n     */\n    public Provisioning(final Aeron aeron)\n    {\n        this.aeron = aeron;\n    }\n\n    /**\n     * poll and send messages.\n     *\n     * @return amount of work done.\n     */\n    public int doWork()\n    {\n        int workDone = 0;\n\n        workDone += pollProvisioningQueue();\n        workDone += pollEchoPairs();\n\n        return workDone;\n    }\n\n    /**\n     * Remove all existing echo pairs.\n     */\n    public void removeAll()\n    {\n        final RemoveAllEchoPairs removeAllEchoPairs = new RemoveAllEchoPairs();\n\n        provisioningMessageQ.add(removeAllEchoPairs);\n\n        try\n        {\n            removeAllEchoPairs.await();\n        }\n        catch (final InterruptedException ex)\n        {\n            throw new RuntimeException(ex);\n        }\n    }\n\n    /**\n     * Create a pub/sub echo pair.\n     *\n     * @param correlationId user specified correlationId to track the echo pair.\n     * @param subChannel    channel used for subscription.\n     * @param subStreamId   stream id used for subscription.\n     * @param pubChannel    channel used for publication.\n     * @param pubStreamId   stream id used for publication.\n     */\n    public void createEchoPair(\n        final long correlationId,\n        final String subChannel,\n        final int subStreamId,\n        final String pubChannel,\n        final int pubStreamId)\n    {\n        final CreateEchoPair createEchoPair = new CreateEchoPair(\n            correlationId,\n            subChannel,\n            subStreamId,\n            pubChannel,\n            pubStreamId);\n\n        provisioningMessageQ.add(createEchoPair);\n\n        try\n        {\n            createEchoPair.await();\n        }\n        catch (final InterruptedException ex)\n        {\n            throw new RuntimeException(ex);\n        }\n    }\n\n    private void handleCreateEchoPair(final CreateEchoPair create) throws Exception\n    {\n        final ConcurrentPublication publication = aeron.addPublication(\n            create.publicationChannel,\n            create.publicationStream);\n\n        final Subscription subscription;\n        try\n        {\n            subscription = aeron.addSubscription(\n                create.subscriptionChannel,\n                create.subscriptionStream);\n        }\n        catch (final Exception ex)\n        {\n            CloseHelper.quietClose(publication);\n            throw ex;\n        }\n\n        final EchoPair echoPair = new EchoPair(create.correlationId, subscription, publication);\n        try\n        {\n            ManagementFactory.getPlatformMBeanServer().registerMBean(\n                new StandardMBean(echoPair.monitor(), EchoMonitorMBean.class),\n                new ObjectName(ProvisioningConstants.echoPairObjectName(create.correlationId)));\n        }\n        catch (final InstanceAlreadyExistsException |\n            MBeanRegistrationException |\n            NotCompliantMBeanException |\n            MalformedObjectNameException ex)\n        {\n            CloseHelper.quietCloseAll(subscription, publication);\n            throw ex;\n        }\n\n        echoPairByCorrelationId.put(echoPair.correlationId(), echoPair);\n    }\n\n    private int pollEchoPairs()\n    {\n        int workDone = 0;\n\n        for (final EchoPair echoPair : echoPairByCorrelationId.values())\n        {\n            workDone += echoPair.poll();\n        }\n\n        return workDone;\n    }\n\n    private int pollProvisioningQueue()\n    {\n        int workDone = 0;\n        ProvisioningMessage poll;\n\n        while (null != (poll = provisioningMessageQ.poll()))\n        {\n            workDone++;\n\n            try\n            {\n                if (poll instanceof CreateEchoPair)\n                {\n                    handleCreateEchoPair((CreateEchoPair)poll);\n                }\n                else if (poll instanceof RemoveAllEchoPairs)\n                {\n                    handleRemoveAll();\n                }\n\n                poll.complete(\"OK\");\n            }\n            catch (final Exception ex)\n            {\n                poll.complete(ex);\n            }\n        }\n\n        return workDone;\n    }\n\n    private void handleRemoveAll()\n    {\n        for (final EchoPair echoPair : echoPairByCorrelationId.values())\n        {\n            try\n            {\n                ManagementFactory.getPlatformMBeanServer().unregisterMBean(\n                    new ObjectName(ProvisioningConstants.echoPairObjectName(echoPair.correlationId())));\n            }\n            catch (final InstanceNotFoundException ignore)\n            {\n            }\n            catch (final MBeanRegistrationException | MalformedObjectNameException ex)\n            {\n                ex.printStackTrace();\n            }\n\n            CloseHelper.quietClose(echoPair);\n        }\n\n        echoPairByCorrelationId.clear();\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/ProvisioningClientMain.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo;\n\nimport io.aeron.samples.echo.api.EchoMonitorMBean;\nimport io.aeron.samples.echo.api.ProvisioningConstants;\nimport io.aeron.samples.echo.api.ProvisioningMBean;\n\nimport javax.management.JMX;\nimport javax.management.MBeanServerConnection;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.ObjectName;\nimport javax.management.remote.JMXConnector;\nimport javax.management.remote.JMXConnectorFactory;\nimport javax.management.remote.JMXServiceURL;\nimport java.io.IOException;\n\n/**\n * Test client for the provisioning service.\n */\npublic class ProvisioningClientMain\n{\n    /**\n     * Main entry point.\n     *\n     * @param args command line parameters\n     * @throws IOException if an I/O exception occurs.\n     * @throws MalformedObjectNameException if any of the JMX names are invalid.\n     */\n    public static void main(final String[] args) throws IOException, MalformedObjectNameException\n    {\n        final JMXServiceURL url = new JMXServiceURL(\"service:jmx:rmi:///jndi/rmi://localhost:10000/jmxrmi\");\n        final JMXConnector connector = JMXConnectorFactory.connect(url);\n        final MBeanServerConnection mBeanServerConnection = connector.getMBeanServerConnection();\n\n        final ProvisioningMBean provisioningMBean = JMX.newMBeanProxy(\n            mBeanServerConnection,\n            new ObjectName(ProvisioningConstants.IO_AERON_TYPE_PROVISIONING_NAME_TESTING),\n            ProvisioningMBean.class);\n\n        final long correlationId = 1L;\n        provisioningMBean.createEchoPair(\n            correlationId, \"aeron:udp?endpoint=localhost:9001\", 1001, \"aeron:udp?endpoint=localhost:9002\", 1001);\n\n        final EchoMonitorMBean echoMonitorMBean = JMX.newMBeanProxy(\n            mBeanServerConnection,\n            new ObjectName(ProvisioningConstants.echoPairObjectName(correlationId)),\n            EchoMonitorMBean.class);\n\n        System.out.println(echoMonitorMBean.getBackPressureCount());\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/ProvisioningMessage.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo;\n\n/**\n * Message to provision a new echo pair.\n */\npublic class ProvisioningMessage\n{\n    private final Object mutex = new Object();\n    private Object result = null;\n\n    /**\n     * Wait for provision message to be processed.\n     *\n     * @throws InterruptedException if the thread is interrupted while waiting for the response.\n     */\n    public void await() throws InterruptedException\n    {\n        synchronized (mutex)\n        {\n            while (null == result)\n            {\n                mutex.wait();\n            }\n        }\n\n        if (result instanceof Exception)\n        {\n            throw new RuntimeException((Exception)result);\n        }\n    }\n\n    /**\n     * Provide a response for the provisioning request.\n     *\n     * @param value to be returned to the caller.\n     */\n    public void complete(final Object value)\n    {\n        synchronized (mutex)\n        {\n            result = value;\n            mutex.notifyAll();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/ProvisioningServerMain.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.samples.echo.api.ProvisioningConstants;\nimport io.aeron.samples.echo.api.ProvisioningMBean;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.BackoffIdleStrategy;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport javax.management.InstanceAlreadyExistsException;\nimport javax.management.InstanceNotFoundException;\nimport javax.management.MBeanRegistrationException;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.NotCompliantMBeanException;\nimport javax.management.ObjectName;\nimport javax.management.StandardMBean;\nimport java.lang.management.ManagementFactory;\n\nimport static java.util.Objects.requireNonNull;\n\n/**\n * Main class for starting the provisioning service.\n */\npublic final class ProvisioningServerMain implements Agent, AutoCloseable\n{\n    private final MediaDriver driver;\n    private final Aeron aeron;\n    private final Provisioning provisioning;\n    private final AgentRunner runner;\n    private volatile ObjectName beanName = null;\n\n    private ProvisioningServerMain(final MediaDriver driver, final Aeron aeron)\n    {\n        this.driver = driver;\n        this.aeron = requireNonNull(aeron);\n        this.provisioning = new Provisioning(aeron);\n        runner = new AgentRunner(new BackoffIdleStrategy(), Throwable::printStackTrace, null, this);\n    }\n\n    /**\n     * Entry point for starting the provisioning service.\n     *\n     * @param args command line arguments.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args)\n    {\n        try (ShutdownSignalBarrier barrier = new ShutdownSignalBarrier();\n            ProvisioningServerMain ignore = ProvisioningServerMain.launch(new Aeron.Context()))\n        {\n            barrier.await();\n            System.out.println(\"Shutdown Provisioning Server...\");\n        }\n    }\n\n    /**\n     * Launch the provisioning server.\n     *\n     * @param context Aeron client context to connect to the local media driver.\n     * @return new ProvisionServerMain instance.\n     */\n    public static ProvisioningServerMain launch(final Aeron.Context context)\n    {\n        MediaDriver driver = null;\n        if (null == System.getProperty(CommonContext.AERON_DIR_PROP_NAME))\n        {\n            driver = MediaDriver.launchEmbedded();\n            context.aeronDirectoryName(driver.aeronDirectoryName());\n        }\n        final Aeron aeron = Aeron.connect(context);\n        final ProvisioningServerMain provisioningServerMain = new ProvisioningServerMain(driver, aeron);\n\n        AgentRunner.startOnThread(provisioningServerMain.runner);\n\n        return provisioningServerMain;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        try\n        {\n            this.beanName = new ObjectName(ProvisioningConstants.IO_AERON_TYPE_PROVISIONING_NAME_TESTING);\n            final StandardMBean object = new StandardMBean(provisioning, ProvisioningMBean.class);\n            ManagementFactory.getPlatformMBeanServer().registerMBean(object, beanName);\n        }\n        catch (final InstanceAlreadyExistsException |\n            MBeanRegistrationException |\n            NotCompliantMBeanException |\n            MalformedObjectNameException e)\n        {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        return provisioning.doWork();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"EchoProvisioningServer\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void close()\n    {\n        if (null != beanName)\n        {\n            try\n            {\n                ManagementFactory.getPlatformMBeanServer().unregisterMBean(beanName);\n            }\n            catch (final InstanceNotFoundException | MBeanRegistrationException ignore)\n            {\n            }\n        }\n\n        CloseHelper.quietCloseAll(runner, aeron, driver);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/RemoveAllEchoPairs.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo;\n\n/**\n * Message to remove all the echo pairs from the service.\n */\npublic class RemoveAllEchoPairs extends ProvisioningMessage\n{\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/api/EchoMonitorMBean.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo.api;\n\n/**\n * MBean interface for interacting with a provisioned Echo sub/pub pair.\n */\npublic interface EchoMonitorMBean\n{\n    /**\n     * Get the correlationId used to provision the pair.\n     *\n     * @return original caller supplied correlationId.\n     */\n    long getCorrelationId();\n\n    /**\n     * Get the measured count of back pressure events when trying to echo from the subscription to the publication.\n     *\n     * @return current back pressure count.\n     */\n    long getBackPressureCount();\n\n    /**\n     * Get the number of fragments echoed from the subscription to the publication.\n     *\n     * @return current fragment count.\n     */\n    long getFragmentCount();\n\n    /**\n     * Get the number of bytes echoed through the pair.\n     *\n     * @return number of bytes.\n     */\n    long getByteCount();\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/api/ProvisioningConstants.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo.api;\n\n/**\n * Constants used by the provisioning API.\n */\npublic class ProvisioningConstants\n{\n    /**\n     * TODO.\n     */\n    public static final String IO_AERON_TYPE_PROVISIONING_NAME_TESTING = \"io.aeron:type=Provisioning,name=testing\";\n\n    /**\n     * TODO.\n     */\n    public static final String IO_AERON_TYPE_ECHO_PAIR_PREFIX = \"io.aeron:type=EchoPair,name=\";\n\n    /**\n     * Generate MBean echo pair object name from the specified correlationId.\n     *\n     * @param correlationId user defined correlationId.\n     * @return a legal JMX object name.\n     */\n    public static String echoPairObjectName(final long correlationId)\n    {\n        return IO_AERON_TYPE_ECHO_PAIR_PREFIX + correlationId;\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/echo/api/ProvisioningMBean.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.echo.api;\n\n/**\n * MBean interface for the provisioning service to request manage the creation of pub/sub echo pairs.\n */\npublic interface ProvisioningMBean\n{\n    /**\n     * Provision a pub/sub echo pair on the remote provisioning service.\n     *\n     * @param correlationId user supplied correlationId\n     * @param subChannel channel for echo subscription.\n     * @param subStreamId stream id for echo subscription.\n     * @param pubChannel channel for echo publication.\n     * @param pubStreamId stream id for echo publication.\n     */\n    void createEchoPair(\n        long correlationId,\n        String subChannel,\n        int subStreamId,\n        String pubChannel,\n        int pubStreamId);\n\n    /**\n     * Removes all echo pairs on remote provisioning service.\n     */\n    void removeAll();\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Samples for using the major features and measuring throughput and latency.\n */\npackage io.aeron.samples;"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/raw/BurstSendReceiveUdpPing.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.raw;\n\nimport io.aeron.driver.Configuration;\nimport org.HdrHistogram.Histogram;\nimport org.agrona.BitUtil;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.HighResolutionTimer;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.locks.LockSupport;\n\nimport static io.aeron.samples.raw.Common.init;\nimport static java.lang.Math.max;\nimport static java.lang.Math.min;\n\n/**\n * Benchmark used to calculate latency of underlying system.\n *\n * @see ReceiveSendUdpPong\n */\npublic class BurstSendReceiveUdpPing\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws IOException if an error occurs with the channel.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args) throws IOException\n    {\n        String remoteHost = \"localhost\";\n        if (1 <= args.length)\n        {\n            remoteHost = args[0];\n        }\n\n        int packetSize = 16;\n        if (2 <= args.length)\n        {\n            packetSize = min(Configuration.MTU_LENGTH_DEFAULT, max(packetSize, Integer.parseInt(args[1])));\n        }\n\n        int burstSize = 1;\n        if (3 <= args.length)\n        {\n            burstSize = min(1024, Integer.parseInt(args[2]));\n        }\n\n        if (SystemUtil.isWindows())\n        {\n            HighResolutionTimer.enable();\n        }\n\n        System.out.printf(\"Remote host: %s, packet size: %d, burstSize: %d%n\", remoteHost, packetSize, burstSize);\n\n        final Histogram histogram = new Histogram(TimeUnit.SECONDS.toNanos(10), 3);\n\n        final ByteBuffer buffer = ByteBuffer.allocateDirect(Configuration.MTU_LENGTH_DEFAULT);\n        for (int i = 0, length = buffer.capacity(); i < length; i++)\n        {\n            buffer.put(i, (byte)0xFF);\n        }\n\n        final DatagramChannel receiveChannel = DatagramChannel.open();\n        receiveChannel.bind(new InetSocketAddress(\"0.0.0.0\", Common.PONG_PORT));\n\n        final InetSocketAddress sendAddress = new InetSocketAddress(remoteHost, Common.PING_PORT);\n        final DatagramChannel sendChannel = DatagramChannel.open();\n        init(sendChannel);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false)))\n        {\n            while (running.get())\n            {\n                measureRoundTrip(\n                    histogram,\n                    sendAddress,\n                    buffer,\n                    packetSize,\n                    burstSize,\n                    receiveChannel,\n                    sendChannel,\n                    running);\n\n                histogram.reset();\n                System.gc();\n                LockSupport.parkNanos(1_000_000_000L);\n            }\n        }\n    }\n\n    private static void measureRoundTrip(\n        final Histogram histogram,\n        final InetSocketAddress sendAddress,\n        final ByteBuffer buffer,\n        final int packetSize,\n        final int burstSize,\n        final DatagramChannel receiveChannel,\n        final DatagramChannel sendChannel,\n        final AtomicBoolean running)\n        throws IOException\n    {\n        for (int sequenceNumber = 0; sequenceNumber < Common.NUM_MESSAGES; sequenceNumber += burstSize)\n        {\n            for (int i = 0; i < burstSize; i++)\n            {\n                final long timestampNs = System.nanoTime();\n\n                buffer.clear();\n                buffer.putLong(sequenceNumber + i);\n                buffer.putLong(timestampNs);\n                buffer.position(packetSize);\n                buffer.flip();\n\n                sendChannel.send(buffer, sendAddress);\n            }\n\n            for (int i = 0; i < burstSize; i++)\n            {\n                buffer.clear();\n                while (running.get())\n                {\n                    if (null != receiveChannel.receive(buffer))\n                    {\n                        break;\n                    }\n                    Thread.onSpinWait();\n                }\n\n                final long receivedSequenceNumber = buffer.getLong(0);\n                if (receivedSequenceNumber != sequenceNumber + i)\n                {\n                    throw new IllegalStateException(\"Data Loss: \" + sequenceNumber + \" to \" + receivedSequenceNumber);\n                }\n\n                final long durationNs = System.nanoTime() - buffer.getLong(BitUtil.SIZE_OF_LONG);\n                histogram.recordValue(durationNs);\n            }\n        }\n\n        histogram.outputPercentileDistribution(System.out, 1000.0);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/raw/Common.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.raw;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.StandardSocketOptions;\nimport java.nio.channels.DatagramChannel;\n\n/**\n * Common configuration and functions used across raw samples.\n */\npublic class Common\n{\n    /**\n     * Number of message to exchange.\n     */\n    public static final int NUM_MESSAGES = 10_000;\n\n    /**\n     * UDP port on which Pong will listen.\n     */\n    public static final int PONG_PORT = 20123;\n\n    /**\n     * UDP port on which Ping will listen.\n     */\n    public static final int PING_PORT = 20124;\n\n    /**\n     * Address to send ping messages to. Pong should listen to this address.\n     */\n    public static final String PING_DEST = System.getProperty(\"io.aeron.raw.ping.dest\", \"localhost\");\n\n    /**\n     * Address to send pong messages to. Ping should listen to this address\n     */\n    public static final String PONG_DEST = System.getProperty(\"io.aeron.raw.pong.dest\", \"localhost\");\n\n    static void init(final DatagramChannel channel) throws IOException\n    {\n        channel.configureBlocking(false);\n        channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);\n    }\n\n    static void init(final DatagramChannel channel, final InetSocketAddress sendAddress) throws IOException\n    {\n        channel.configureBlocking(false);\n        channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);\n        channel.connect(sendAddress);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/raw/ReceiveSendUdpPong.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.raw;\n\nimport io.aeron.driver.Configuration;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.HighResolutionTimer;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static io.aeron.samples.raw.Common.init;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Benchmark used to calculate latency of underlying system.\n *\n * @see SendReceiveUdpPing\n */\npublic class ReceiveSendUdpPong\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws IOException if an error occurs with the channel.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args) throws IOException\n    {\n        int numChannels = 1;\n        if (1 <= args.length)\n        {\n            numChannels = Integer.parseInt(args[0]);\n        }\n\n        String remoteHost = \"localhost\";\n        if (2 <= args.length)\n        {\n            remoteHost = args[1];\n        }\n\n        if (SystemUtil.isWindows())\n        {\n            HighResolutionTimer.enable();\n        }\n\n        System.out.printf(\"Number of channels: %d, Remote host: %s%n\", numChannels, remoteHost);\n\n        final ByteBuffer buffer = ByteBuffer.allocateDirect(Configuration.MTU_LENGTH_DEFAULT);\n\n        final DatagramChannel[] receiveChannels = new DatagramChannel[numChannels];\n        for (int i = 0; i < receiveChannels.length; i++)\n        {\n            receiveChannels[i] = DatagramChannel.open();\n            init(receiveChannels[i]);\n            receiveChannels[i].bind(new InetSocketAddress(\"0.0.0.0\", Common.PING_PORT + i));\n        }\n\n        final InetSocketAddress sendAddress = new InetSocketAddress(remoteHost, Common.PONG_PORT);\n        final DatagramChannel sendChannel = DatagramChannel.open();\n        Common.init(sendChannel);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false)))\n        {\n            while (true)\n            {\n                buffer.clear();\n\n                boolean available = false;\n                while (!available)\n                {\n                    Thread.onSpinWait();\n                    if (!running.get())\n                    {\n                        return;\n                    }\n\n                    for (int i = receiveChannels.length - 1; i >= 0; i--)\n                    {\n                        if (null != receiveChannels[i].receive(buffer))\n                        {\n                            available = true;\n                            break;\n                        }\n                    }\n                }\n\n                buffer.flip();\n                final int length = buffer.remaining();\n                final long receivedSequenceNumber = buffer.getLong(0);\n                final long receivedTimestamp = buffer.getLong(SIZE_OF_LONG);\n\n                buffer.clear();\n                buffer.putLong(receivedSequenceNumber);\n                buffer.putLong(receivedTimestamp);\n                buffer.position(length);\n                buffer.flip();\n\n                sendChannel.send(buffer, sendAddress);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/raw/ReceiveWriteUdpPong.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.raw;\n\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.HighResolutionTimer;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static io.aeron.driver.Configuration.MTU_LENGTH_DEFAULT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Benchmark used to calculate latency of underlying system.\n *\n * @see WriteReceiveUdpPing\n */\npublic class ReceiveWriteUdpPong\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws IOException if an error occurs with the channel.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args) throws IOException\n    {\n        int numChannels = 1;\n        if (1 == args.length)\n        {\n            numChannels = Integer.parseInt(args[0]);\n        }\n\n        if (SystemUtil.isWindows())\n        {\n            HighResolutionTimer.enable();\n        }\n\n        final ByteBuffer buffer = ByteBuffer.allocateDirect(MTU_LENGTH_DEFAULT);\n\n        final DatagramChannel[] receiveChannels = new DatagramChannel[numChannels];\n        for (int i = 0; i < receiveChannels.length; i++)\n        {\n            receiveChannels[i] = DatagramChannel.open();\n            Common.init(receiveChannels[i]);\n            receiveChannels[i].bind(new InetSocketAddress(\"localhost\", Common.PING_PORT + i));\n        }\n\n        final InetSocketAddress writeAddress = new InetSocketAddress(\"localhost\", Common.PONG_PORT);\n        final DatagramChannel writeChannel = DatagramChannel.open();\n        Common.init(writeChannel, writeAddress);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false)))\n        {\n            while (true)\n            {\n                buffer.clear();\n\n                boolean available = false;\n                while (!available)\n                {\n                    Thread.onSpinWait();\n                    if (!running.get())\n                    {\n                        return;\n                    }\n\n                    for (int i = receiveChannels.length - 1; i >= 0; i--)\n                    {\n                        if (null != receiveChannels[i].receive(buffer))\n                        {\n                            available = true;\n                            break;\n                        }\n                    }\n                }\n\n                final long receivedSequenceNumber = buffer.getLong(0);\n                final long receivedTimestamp = buffer.getLong(SIZE_OF_LONG);\n\n                buffer.clear();\n                buffer.putLong(receivedSequenceNumber);\n                buffer.putLong(receivedTimestamp);\n                buffer.flip();\n\n                writeChannel.write(buffer);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/raw/SelectReceiveSendUdpPong.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.raw;\n\nimport io.aeron.driver.Configuration;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.HighResolutionTimer;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.util.Iterator;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.IntSupplier;\n\nimport static java.nio.channels.SelectionKey.OP_READ;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Benchmark used to calculate latency of underlying system.\n */\npublic class SelectReceiveSendUdpPong\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws IOException if an error occurs with the channel.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args) throws IOException\n    {\n        if (SystemUtil.isWindows())\n        {\n            HighResolutionTimer.enable();\n        }\n\n        final InetSocketAddress sendAddress = new InetSocketAddress(\"localhost\", Common.PONG_PORT);\n        final ByteBuffer buffer = ByteBuffer.allocateDirect(Configuration.MTU_LENGTH_DEFAULT);\n\n        final DatagramChannel receiveChannel = DatagramChannel.open();\n        Common.init(receiveChannel);\n        receiveChannel.bind(new InetSocketAddress(\"localhost\", Common.PING_PORT));\n\n        final DatagramChannel sendChannel = DatagramChannel.open();\n        Common.init(sendChannel);\n\n        final Selector selector = Selector.open();\n\n        final IntSupplier handler =\n            () ->\n            {\n                try\n                {\n                    buffer.clear();\n                    receiveChannel.receive(buffer);\n\n                    final long receivedSequenceNumber = buffer.getLong(0);\n                    final long receivedTimestamp = buffer.getLong(SIZE_OF_LONG);\n\n                    buffer.clear();\n                    buffer.putLong(receivedSequenceNumber);\n                    buffer.putLong(receivedTimestamp);\n                    buffer.flip();\n\n                    sendChannel.send(buffer, sendAddress);\n                }\n                catch (final IOException ex)\n                {\n                    ex.printStackTrace();\n                }\n\n                return 1;\n            };\n\n        receiveChannel.register(selector, OP_READ, handler);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false)))\n        {\n            while (true)\n            {\n                while (selector.selectNow() == 0)\n                {\n                    if (!running.get())\n                    {\n                        return;\n                    }\n\n                    Thread.onSpinWait();\n                }\n\n                final Set<SelectionKey> selectedKeys = selector.selectedKeys();\n                final Iterator<SelectionKey> iter = selectedKeys.iterator();\n\n                while (iter.hasNext())\n                {\n                    final SelectionKey key = iter.next();\n                    if (key.isReadable())\n                    {\n                        ((IntSupplier)key.attachment()).getAsInt();\n                    }\n\n                    iter.remove();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/raw/SendReceiveUdpPing.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.raw;\n\nimport io.aeron.driver.Configuration;\nimport org.HdrHistogram.Histogram;\nimport org.agrona.BitUtil;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.HighResolutionTimer;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.locks.LockSupport;\n\nimport static io.aeron.samples.raw.Common.init;\n\n/**\n * Benchmark used to calculate latency of underlying system.\n *\n * @see ReceiveSendUdpPong\n */\npublic class SendReceiveUdpPing\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws IOException if an error occurs with the channel.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args) throws IOException\n    {\n        int numChannels = 1;\n        if (1 <= args.length)\n        {\n            numChannels = Integer.parseInt(args[0]);\n        }\n\n        String remoteHost = \"localhost\";\n        if (2 <= args.length)\n        {\n            remoteHost = args[1];\n        }\n\n        if (SystemUtil.isWindows())\n        {\n            HighResolutionTimer.enable();\n        }\n\n        System.out.printf(\"Number of channels: %d, Remote host: %s%n\", numChannels, remoteHost);\n\n        final Histogram histogram = new Histogram(TimeUnit.SECONDS.toNanos(10), 3);\n        final ByteBuffer buffer = ByteBuffer.allocateDirect(Configuration.MTU_LENGTH_DEFAULT);\n\n        final DatagramChannel[] receiveChannels = new DatagramChannel[numChannels];\n        for (int i = 0; i < receiveChannels.length; i++)\n        {\n            receiveChannels[i] = DatagramChannel.open();\n            init(receiveChannels[i]);\n            receiveChannels[i].bind(new InetSocketAddress(\"0.0.0.0\", Common.PONG_PORT + i));\n        }\n\n        final InetSocketAddress sendAddress = new InetSocketAddress(remoteHost, Common.PING_PORT);\n        final DatagramChannel sendChannel = DatagramChannel.open();\n        init(sendChannel);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false)))\n        {\n            while (running.get())\n            {\n                measureRoundTrip(histogram, sendAddress, buffer, receiveChannels, sendChannel, running);\n\n                histogram.reset();\n                System.gc();\n                LockSupport.parkNanos(1_000_000_000L);\n            }\n        }\n    }\n\n    private static void measureRoundTrip(\n        final Histogram histogram,\n        final InetSocketAddress sendAddress,\n        final ByteBuffer buffer,\n        final DatagramChannel[] receiveChannels,\n        final DatagramChannel sendChannel,\n        final AtomicBoolean running)\n        throws IOException\n    {\n        for (int sequenceNumber = 0; sequenceNumber < Common.NUM_MESSAGES; sequenceNumber++)\n        {\n            final long timestampNs = System.nanoTime();\n\n            buffer.clear();\n            buffer.putLong(sequenceNumber);\n            buffer.putLong(timestampNs);\n            buffer.flip();\n\n            sendChannel.send(buffer, sendAddress);\n\n            buffer.clear();\n            boolean available = false;\n            while (!available)\n            {\n                Thread.onSpinWait();\n                if (!running.get())\n                {\n                    return;\n                }\n\n                for (int i = receiveChannels.length - 1; i >= 0; i--)\n                {\n                    if (null != receiveChannels[i].receive(buffer))\n                    {\n                        available = true;\n                        break;\n                    }\n                }\n            }\n\n            final long receivedSequenceNumber = buffer.getLong(0);\n            final long receivedTimestamp = buffer.getLong(BitUtil.SIZE_OF_LONG);\n\n            if (receivedSequenceNumber != sequenceNumber)\n            {\n                throw new IllegalStateException(\"Data Loss: \" + sequenceNumber + \" to \" + receivedSequenceNumber);\n            }\n\n            final long durationNs = System.nanoTime() - receivedTimestamp;\n            histogram.recordValue(durationNs);\n        }\n\n        histogram.outputPercentileDistribution(System.out, 1000.0);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/raw/SendSelectReceiveUdpPing.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.raw;\n\nimport io.aeron.driver.Configuration;\nimport org.HdrHistogram.Histogram;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.HighResolutionTimer;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.util.Iterator;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.locks.LockSupport;\nimport java.util.function.IntSupplier;\n\nimport static java.nio.channels.SelectionKey.OP_READ;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Benchmark used to calculate latency of underlying system.\n */\npublic class SendSelectReceiveUdpPing\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws IOException if an error occurs with the channel.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args) throws IOException\n    {\n        if (SystemUtil.isWindows())\n        {\n            HighResolutionTimer.enable();\n        }\n\n        final Histogram histogram = new Histogram(TimeUnit.SECONDS.toNanos(10), 3);\n        final ByteBuffer buffer = ByteBuffer.allocateDirect(Configuration.MTU_LENGTH_DEFAULT);\n\n        final DatagramChannel receiveChannel = DatagramChannel.open();\n        Common.init(receiveChannel);\n        receiveChannel.bind(new InetSocketAddress(\"localhost\", Common.PONG_PORT));\n\n        final InetSocketAddress sendAddress = new InetSocketAddress(\"localhost\", Common.PING_PORT);\n        final DatagramChannel sendChannel = DatagramChannel.open();\n        Common.init(sendChannel);\n\n        final Selector selector = Selector.open();\n        final MutableLong sequence = new MutableLong();\n\n        final IntSupplier handler =\n            () ->\n            {\n                try\n                {\n                    buffer.clear();\n                    receiveChannel.receive(buffer);\n\n                    final long receivedSequenceNumber = buffer.getLong(0);\n                    final long receivedTimestampNs = buffer.getLong(SIZE_OF_LONG);\n\n                    if (receivedSequenceNumber != sequence.get())\n                    {\n                        throw new IllegalStateException(\"Data Loss: \" + sequence + \" to \" + receivedSequenceNumber);\n                    }\n\n                    final long durationNs = System.nanoTime() - receivedTimestampNs;\n                    histogram.recordValue(durationNs);\n                }\n                catch (final IOException ex)\n                {\n                    ex.printStackTrace();\n                }\n\n                return 1;\n            };\n\n        receiveChannel.register(selector, OP_READ, handler);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false)))\n        {\n            while (running.get())\n            {\n                measureRoundTrip(histogram, sendAddress, buffer, sendChannel, selector, sequence, running);\n\n                histogram.reset();\n                System.gc();\n                LockSupport.parkNanos(1_000_000_000L);\n            }\n        }\n    }\n\n    private static void measureRoundTrip(\n        final Histogram histogram,\n        final InetSocketAddress sendAddress,\n        final ByteBuffer buffer,\n        final DatagramChannel sendChannel,\n        final Selector selector,\n        final MutableLong sequence,\n        final AtomicBoolean running)\n        throws IOException\n    {\n        for (int i = 0; i < Common.NUM_MESSAGES; i++)\n        {\n            sequence.set(i);\n\n            final long timestamp = System.nanoTime();\n\n            buffer.clear();\n            buffer.putLong(i);\n            buffer.putLong(timestamp);\n            buffer.flip();\n\n            sendChannel.send(buffer, sendAddress);\n\n            while (selector.selectNow() == 0)\n            {\n                if (!running.get())\n                {\n                    return;\n                }\n\n                Thread.onSpinWait();\n            }\n\n            final Set<SelectionKey> selectedKeys = selector.selectedKeys();\n            final Iterator<SelectionKey> iter = selectedKeys.iterator();\n\n            while (iter.hasNext())\n            {\n                final SelectionKey key = iter.next();\n                if (key.isReadable())\n                {\n                    ((IntSupplier)key.attachment()).getAsInt();\n                }\n\n                iter.remove();\n            }\n        }\n\n        histogram.outputPercentileDistribution(System.out, 1000.0);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/raw/WriteReceiveUdpPing.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.raw;\n\nimport org.HdrHistogram.Histogram;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.HighResolutionTimer;\nimport org.agrona.concurrent.ShutdownSignalBarrier;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.DatagramChannel;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.locks.LockSupport;\n\nimport static io.aeron.driver.Configuration.MTU_LENGTH_DEFAULT;\nimport static io.aeron.samples.raw.Common.PING_PORT;\nimport static io.aeron.samples.raw.Common.PONG_PORT;\nimport static io.aeron.samples.raw.Common.init;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\n\n/**\n * Benchmark used to calculate latency of underlying system.\n *\n * @see ReceiveWriteUdpPong\n */\npublic class WriteReceiveUdpPing\n{\n    /**\n     * Main method for launching the process.\n     *\n     * @param args passed to the process.\n     * @throws IOException if an error occurs with the channel.\n     */\n    @SuppressWarnings(\"try\")\n    public static void main(final String[] args) throws IOException\n    {\n        int numChannels = 1;\n        if (1 == args.length)\n        {\n            numChannels = Integer.parseInt(args[0]);\n        }\n\n        if (SystemUtil.isWindows())\n        {\n            HighResolutionTimer.enable();\n        }\n\n        final Histogram histogram = new Histogram(TimeUnit.SECONDS.toNanos(10), 3);\n        final ByteBuffer buffer = ByteBuffer.allocateDirect(MTU_LENGTH_DEFAULT);\n\n        final DatagramChannel[] receiveChannels = new DatagramChannel[numChannels];\n        for (int i = 0; i < receiveChannels.length; i++)\n        {\n            receiveChannels[i] = DatagramChannel.open();\n            init(receiveChannels[i]);\n            receiveChannels[i].bind(new InetSocketAddress(\"localhost\", PONG_PORT + i));\n        }\n\n        final InetSocketAddress writeAddress = new InetSocketAddress(\"localhost\", PING_PORT);\n        final DatagramChannel writeChannel = DatagramChannel.open();\n        init(writeChannel, writeAddress);\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        try (ShutdownSignalBarrier ignore = new ShutdownSignalBarrier(() -> running.set(false)))\n        {\n            while (running.get())\n            {\n                measureRoundTrip(histogram, buffer, receiveChannels, writeChannel, running);\n\n                histogram.reset();\n                System.gc();\n                LockSupport.parkNanos(1_000_000_000L);\n            }\n        }\n    }\n\n    private static void measureRoundTrip(\n        final Histogram histogram,\n        final ByteBuffer buffer,\n        final DatagramChannel[] receiveChannels,\n        final DatagramChannel writeChannel,\n        final AtomicBoolean running)\n        throws IOException\n    {\n        for (int sequenceNumber = 0; sequenceNumber < Common.NUM_MESSAGES; sequenceNumber++)\n        {\n            final long timestampNs = System.nanoTime();\n\n            buffer.clear();\n            buffer.putLong(sequenceNumber);\n            buffer.putLong(timestampNs);\n            buffer.flip();\n\n            writeChannel.write(buffer);\n\n            buffer.clear();\n            boolean available = false;\n            while (!available)\n            {\n                Thread.onSpinWait();\n                if (!running.get())\n                {\n                    return;\n                }\n\n                for (int i = receiveChannels.length - 1; i >= 0; i--)\n                {\n                    if (null != receiveChannels[i].receive(buffer))\n                    {\n                        available = true;\n                        break;\n                    }\n                }\n            }\n\n            final long receivedSequenceNumber = buffer.getLong(0);\n            if (receivedSequenceNumber != sequenceNumber)\n            {\n                throw new IllegalStateException(\"Data Loss: \" + sequenceNumber + \" to \" + receivedSequenceNumber);\n            }\n\n            final long durationNs = System.nanoTime() - buffer.getLong(SIZE_OF_LONG);\n            histogram.recordValue(durationNs);\n        }\n\n        histogram.outputPercentileDistribution(System.out, 1000.0);\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/raw/package-info.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Samples for measuring the raw Java NIO and network performance.\n */\npackage io.aeron.samples.raw;"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/security/SimpleAuthenticator.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.security;\n\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.CredentialsSupplier;\nimport io.aeron.security.SessionProxy;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\n/**\n * An authenticator that works off a simple principal/credential pair constructed by a builder. It only supports simple\n * authentication, but not challenge/response.\n */\npublic final class SimpleAuthenticator implements Authenticator\n{\n    private final Object2ObjectHashMap<Credentials, Principal> principalsByCredentialsMap =\n        new Object2ObjectHashMap<>();\n\n    private final Long2ObjectHashMap<Principal> authenticatedSessionIdToPrincipalMap = new Long2ObjectHashMap<>();\n\n    private SimpleAuthenticator(final Builder builder)\n    {\n        for (final Principal principal : builder.principals)\n        {\n            principalsByCredentialsMap.put(principal.credentials, principal);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n    {\n        final Principal principal = principalsByCredentialsMap.get(new Credentials(encodedCredentials));\n        if (null != principal && principal.credentialsMatch(encodedCredentials))\n        {\n            authenticatedSessionIdToPrincipalMap.put(sessionId, principal);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n    {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n    {\n        final long sessionId = sessionProxy.sessionId();\n        final Principal principal = authenticatedSessionIdToPrincipalMap.get(sessionId);\n        if (null != principal)\n        {\n            if (sessionProxy.authenticate(principal.encodedPrincipal))\n            {\n                authenticatedSessionIdToPrincipalMap.remove(sessionId);\n            }\n        }\n        else\n        {\n            sessionProxy.reject();\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n    {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * Builder to create instances of SimpleAuthenticator.\n     */\n    public static class Builder\n    {\n        private final ArrayList<Principal> principals = new ArrayList<>();\n\n        /**\n         * Add a principal/credentials pair to the list supported by this authenticator. Note that the\n         * {@link SimpleAuthenticator} keys the principals by the credentials, so the encoded credentials should\n         * include the encoded principal. The associated {@link CredentialsSupplier} used on the client should handle\n         * encoding these credentials correctly as well. E.g.\n         * <pre>\n         * final SimpleAuthenticator simpleAuthenticator = new SimpleAuthenticator.Builder()\n         *     .principal(\"user\".getBytes(US_ASCII), \"user:pass\".getBytes(US_ASCII))\n         *     .newInstance();\n         * </pre>\n         *\n         * @param encodedPrincipal   principal encoded as a byte array.\n         * @param encodedCredentials credentials encoded as a byte array.\n         * @return this for a fluent API.\n         */\n        public Builder principal(final byte[] encodedPrincipal, final byte[] encodedCredentials)\n        {\n            principals.add(new Principal(encodedPrincipal, encodedCredentials));\n            return this;\n        }\n\n        /**\n         * Construct a new instance of the SimpleAuthenticator.\n         *\n         * @return a new SimpleAuthenticator instance.\n         */\n        public SimpleAuthenticator newInstance()\n        {\n            return new SimpleAuthenticator(this);\n        }\n    }\n\n    private static final class Principal\n    {\n        private final byte[] encodedPrincipal;\n        private final Credentials credentials;\n\n        private Principal(final byte[] encodedPrincipal, final byte[] encodedCredentials)\n        {\n            this.encodedPrincipal = encodedPrincipal;\n            this.credentials = new Credentials(encodedCredentials);\n        }\n\n        public boolean credentialsMatch(final byte[] encodedCredentials)\n        {\n            return Arrays.equals(credentials.encodedCredentials, encodedCredentials);\n        }\n    }\n\n    private static final class Credentials\n    {\n        private final byte[] encodedCredentials;\n\n        private Credentials(final byte[] encodedCredentials)\n        {\n            this.encodedCredentials = encodedCredentials;\n        }\n\n        public boolean equals(final Object o)\n        {\n            if (this == o)\n            {\n                return true;\n            }\n            if (o == null || getClass() != o.getClass())\n            {\n                return false;\n            }\n            final Credentials that = (Credentials)o;\n            return Arrays.equals(encodedCredentials, that.encodedCredentials);\n        }\n\n        public int hashCode()\n        {\n            return Arrays.hashCode(encodedCredentials);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/security/SimpleAuthorisationService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.security;\n\nimport io.aeron.security.AuthorisationService;\nimport org.agrona.collections.BiInt2ObjectMap;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.collections.Object2ObjectHashMap;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Authorisation service that supports setting general and per-principal rules as well as scoping to protocol, action\n * and type. Uses a fluent API to add authorisation rules.\n */\npublic final class SimpleAuthorisationService implements AuthorisationService\n{\n    private final AuthorisationService defaultAuthorisation;\n    private final Object2ObjectHashMap<ByteArrayAsKey, Principal> principalByKeyMap = new Object2ObjectHashMap<>();\n    private final Principal defaultPrincipal;\n\n    private SimpleAuthorisationService(final Builder builder)\n    {\n        defaultAuthorisation = builder.defaultAuthorisation;\n        principalByKeyMap.putAll(builder.principalByKeyMap);\n        defaultPrincipal = builder.defaultPrincipal;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public boolean isAuthorised(\n        final int protocolId,\n        final int actionId,\n        final Object type,\n        final byte[] encodedPrincipal)\n    {\n        Boolean authorised;\n\n        authorised = isAuthorised(\n            principalByKeyMap.get(new ByteArrayAsKey(encodedPrincipal)),\n            protocolId,\n            actionId,\n            type);\n        if (null != authorised)\n        {\n            return authorised;\n        }\n\n        authorised = isAuthorised(defaultPrincipal, protocolId, actionId, type);\n        if (null != authorised)\n        {\n            return authorised;\n        }\n\n        return defaultAuthorisation.isAuthorised(protocolId, actionId, type, encodedPrincipal);\n    }\n\n    private Boolean isAuthorised(\n        final Principal principal,\n        final int protocolId,\n        final int actionId,\n        final Object type)\n    {\n        if (null == principal)\n        {\n            return null;\n        }\n\n        return principal.isAuthorised(protocolId, actionId, type);\n    }\n\n    /**\n     * Builder to create the authorisation service.\n     */\n    public static class Builder\n    {\n        private AuthorisationService defaultAuthorisation = AuthorisationService.DENY_ALL;\n        private final Object2ObjectHashMap<ByteArrayAsKey, Principal> principalByKeyMap = new Object2ObjectHashMap<>();\n        private final Principal defaultPrincipal = new Principal(new byte[0]);\n\n        /**\n         * Set the default authorisation if the authorisation request does not match any of the supplied rules.\n         *\n         * @param defaultAuthorisation an authorisation service to fall back to.\n         * @return this for a fluent API\n         * @see AuthorisationService#ALLOW_ALL\n         * @see AuthorisationService#DENY_ALL\n         */\n        public Builder defaultAuthorisation(final AuthorisationService defaultAuthorisation)\n        {\n            this.defaultAuthorisation = defaultAuthorisation;\n            return this;\n        }\n\n        /**\n         * Add rule for a specific principal that is scope to a protocolId, actionId, and type.\n         *\n         * @param protocolId       <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param actionId         <code>sbe:message@id</code> value that the rule applies to.\n         * @param type             a parameter of the message can be used to narrow the scope of the authorisation\n         *                         rule. This is message dependent.\n         * @param encodedPrincipal The principal encoded as byte array.\n         * @param isAllowed        If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addPrincipalRule(\n            final int protocolId,\n            final int actionId,\n            final Object type,\n            final byte[] encodedPrincipal,\n            final boolean isAllowed)\n        {\n            final Principal principal = principalByKeyMap.computeIfAbsent(\n                new ByteArrayAsKey(encodedPrincipal), (key) -> new Principal(key.data));\n\n            final BiInt2ObjectMap<Set<Object>> byTypeMap = isAllowed ?\n                principal.byProtocolActionTypeAllowed : principal.byProtocolActionTypeDenied;\n\n            byTypeMap.computeIfAbsent(protocolId, actionId, (a, b) -> new HashSet<>()).add(type);\n\n            return this;\n        }\n\n        /**\n         * Add rule for a specific principal that is scope to a protocolId and actionId.\n         *\n         * @param protocolId       <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param actionId         <code>sbe:message@id</code> value that the rule applies to.\n         * @param encodedPrincipal The principal encoded as byte array.\n         * @param isAllowed        If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addPrincipalRule(\n            final int protocolId,\n            final int actionId,\n            final byte[] encodedPrincipal,\n            final boolean isAllowed)\n        {\n            final Principal principal = principalByKeyMap.computeIfAbsent(\n                new ByteArrayAsKey(encodedPrincipal), (key) -> new Principal(key.data));\n\n            principal.byProtocolAction.put(protocolId, actionId, isAllowed);\n            return this;\n        }\n\n        /**\n         * Add rule for a specific principal that is scope to a protocolId.\n         *\n         * @param protocolId       <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param encodedPrincipal The principal encoded as byte array.\n         * @param isAllowed        If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addPrincipalRule(\n            final int protocolId,\n            final byte[] encodedPrincipal,\n            final boolean isAllowed)\n        {\n            final Principal principal = principalByKeyMap.computeIfAbsent(\n                new ByteArrayAsKey(encodedPrincipal), (key) -> new Principal(key.data));\n\n            principal.byProtocol.put(protocolId, Boolean.valueOf(isAllowed));\n            return this;\n        }\n\n        /**\n         * Add rule for all principals that is scoped to a protocolId, actionId and type.\n         *\n         * @param protocolId <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param actionId   <code>sbe:message@id</code> value that the rule applies to.\n         * @param type       a parameter of the message can be used to narrow the scope of the authorisation\n         *                   rule. This is message dependent.\n         * @param isAllowed  If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addGeneralRule(\n            final int protocolId,\n            final int actionId,\n            final Object type,\n            final boolean isAllowed)\n        {\n            final BiInt2ObjectMap<Set<Object>> byTypeMap = isAllowed ?\n                defaultPrincipal.byProtocolActionTypeAllowed : defaultPrincipal.byProtocolActionTypeDenied;\n            byTypeMap.computeIfAbsent(protocolId, actionId, (a, b) -> new HashSet<>()).add(type);\n\n            return this;\n        }\n\n        /**\n         * Add rule for all principals that is scoped to a protocolId and actionId.\n         *\n         * @param protocolId <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param actionId   <code>sbe:message@id</code> value that the rule applies to.\n         * @param isAllowed  If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addGeneralRule(final int protocolId, final int actionId, final boolean isAllowed)\n        {\n            defaultPrincipal.byProtocolAction.put(protocolId, actionId, isAllowed);\n            return this;\n        }\n\n        /**\n         * Add rule for all principals that is scoped to a protocolId.\n         *\n         * @param protocolId <code>sbe:messageSchema@id</code> value that the rule applies to.\n         * @param isAllowed  If the rule should allow or deny access.\n         * @return this for a fluent API.\n         */\n        public Builder addGeneralRule(final int protocolId, final boolean isAllowed)\n        {\n            defaultPrincipal.byProtocol.put(protocolId, (Boolean)isAllowed);\n            return this;\n        }\n\n        /**\n         * Create and instance of the SimpleAuthorisationService.\n         *\n         * @return new SimpleAuthorisationService.\n         */\n        public SimpleAuthorisationService newInstance()\n        {\n            return new SimpleAuthorisationService(this);\n        }\n    }\n\n    private static final class Principal\n    {\n        private final Int2ObjectHashMap<Boolean> byProtocol = new Int2ObjectHashMap<>();\n        private final BiInt2ObjectMap<Boolean> byProtocolAction = new BiInt2ObjectMap<>();\n        private final BiInt2ObjectMap<Set<Object>> byProtocolActionTypeAllowed = new BiInt2ObjectMap<>();\n        private final BiInt2ObjectMap<Set<Object>> byProtocolActionTypeDenied = new BiInt2ObjectMap<>();\n        private final byte[] encodedPrincipal;\n\n        private Principal(final byte[] encodedPrincipal)\n        {\n            this.encodedPrincipal = encodedPrincipal;\n        }\n\n        public Boolean isAuthorised(final int protocolId, final int actionId, final Object type)\n        {\n            final Set<Object> typesAllowed = byProtocolActionTypeAllowed.getOrDefault(\n                protocolId, actionId, Collections.emptySet());\n            if (typesAllowed.contains(type))\n            {\n                return Boolean.TRUE;\n            }\n\n            final Set<Object> typesDenied = byProtocolActionTypeDenied.getOrDefault(\n                protocolId, actionId, Collections.emptySet());\n            if (typesDenied.contains(type))\n            {\n                return Boolean.FALSE;\n            }\n\n            final Boolean authorised = byProtocolAction.get(protocolId, actionId);\n            if (null != authorised)\n            {\n                return authorised;\n            }\n\n            return byProtocol.get(protocolId);\n        }\n    }\n\n    private static final class ByteArrayAsKey\n    {\n        private final byte[] data;\n\n        private ByteArrayAsKey(final byte[] data)\n        {\n            this.data = data;\n        }\n\n        public boolean equals(final Object o)\n        {\n            if (this == o)\n            {\n                return true;\n            }\n            if (o == null || getClass() != o.getClass())\n            {\n                return false;\n            }\n            final ByteArrayAsKey that = (ByteArrayAsKey)o;\n            return Arrays.equals(data, that.data);\n        }\n\n        public int hashCode()\n        {\n            return Arrays.hashCode(data);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/stress/CRC64.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.stress;\n\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\n\n/**\n * CRC-64 implementation with ability to combine checksums calculated over different blocks of data.\n */\npublic class CRC64\n{\n    private static final long POLY = 0xc96c5795d7870f42L; // ECMA-182\n\n    /* CRC64 calculation table. */\n    private static final long[] TABLE;\n\n    /* Current CRC value. */\n    private long value;\n\n    static\n    {\n        TABLE = new long[256];\n\n        for (int n = 0; n < 256; n++)\n        {\n            long crc = n;\n            for (int k = 0; k < 8; k++)\n            {\n                if ((crc & 1) == 1)\n                {\n                    crc = (crc >>> 1) ^ POLY;\n                }\n                else\n                {\n                    crc = (crc >>> 1);\n                }\n            }\n            TABLE[n] = crc;\n        }\n    }\n\n    /**\n     * Construct new CRC.\n     */\n    public CRC64()\n    {\n        this.value = 0;\n    }\n\n    long recalculate(final byte[] b, final int offset, final int length)\n    {\n        value = 0;\n        update(b, offset, length);\n        return value;\n    }\n\n    long recalculate(final DirectBuffer b, final int offset, final int length)\n    {\n        value = 0;\n        update(b, offset, length);\n        return value;\n    }\n\n    /**\n     * Update CRC64 with new byte block.\n     *\n     * @param b         input.\n     * @param offset    the position to read from.\n     * @param len       length to process.\n     */\n    public void update(final byte[] b, final int offset, final int len)\n    {\n        int remaining = len;\n        int idx = offset;\n        this.value = ~this.value;\n        while (remaining > 0)\n        {\n            this.value = TABLE[((int)(this.value ^ b[idx])) & 0xff] ^ (this.value >>> 8);\n            idx++;\n            remaining--;\n        }\n        this.value = ~this.value;\n    }\n\n    /**\n     * Update CRC64 with new DirectBuffer.\n     *\n     * @param b         input.\n     * @param offset    the position to read from.\n     * @param len       length to process.\n     */\n    public void update(final DirectBuffer b, final int offset, final int len)\n    {\n        int remaining = len;\n        int idx = offset;\n        this.value = ~this.value;\n        while (remaining > 0)\n        {\n            this.value = TABLE[((int)(this.value ^ b.getByte(idx))) & 0xff] ^ (this.value >>> 8);\n            idx++;\n            remaining--;\n        }\n        this.value = ~this.value;\n    }\n\n    private static void test(final byte[] b, final int len, final long crcValue) throws Exception\n    {\n        /* Test CRC64 default calculation. */\n        final CRC64 crc = new CRC64();\n\n        final long recalculate = crc.recalculate(new UnsafeBuffer(b), 0, len);\n        if (recalculate != crcValue)\n        {\n            throw new Exception(\n                \"mismatch: \" + String.format(\"%016x\", recalculate) +\n                \" should be \" + String.format(\"%016x\", crcValue));\n        }\n    }\n\n    /**\n     * Entry point.\n     *\n     * @param args command line args.\n     * @throws Exception on failure.\n     */\n    public static void main(final String[] args) throws Exception\n    {\n        final byte[] test1 = \"123456789\".getBytes();\n        final int testlen1 = 9;\n        final long testcrc1 = 0x995dc9bbdf1939faL; // ECMA.\n        test(test1, testlen1, testcrc1);\n\n        final byte[] test2 = \"This is a test of the emergency broadcast system.\".getBytes();\n        final int testlen2 = 49;\n        final long testcrc2 = 0x27db187fc15bbc72L; // ECMA.\n        test(test2, testlen2, testcrc2);\n\n        final byte[] test3 = \"IHATEMATH\".getBytes();\n        final int testlen3 = 9;\n        final long testcrc3 = 0x3920e0f66b6ee0c8L; // ECMA.\n        test(test3, testlen3, testcrc3);\n    }\n}"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/stress/SimpleReservedValueSupplier.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.stress;\n\nimport io.aeron.ReservedValueSupplier;\nimport org.agrona.DirectBuffer;\n\nclass SimpleReservedValueSupplier implements ReservedValueSupplier\n{\n    private long value;\n    public long get(final DirectBuffer termBuffer, final int termOffset, final int frameLength)\n    {\n        return value;\n    }\n\n    ReservedValueSupplier set(final long value)\n    {\n        this.value = value;\n        return this;\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/stress/StressMdcClient.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.stress;\n\nimport io.aeron.Aeron;\nimport io.aeron.Counter;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.Long2LongCounterMap;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\n\nimport java.util.Random;\n\nimport static io.aeron.driver.Configuration.MAX_UDP_PAYLOAD_LENGTH;\nimport static io.aeron.samples.stress.StressUtil.MDC_STREAM_ID;\nimport static io.aeron.samples.stress.StressUtil.MTU_LENGTHS;\nimport static io.aeron.samples.stress.StressUtil.clientAddress;\nimport static io.aeron.samples.stress.StressUtil.info;\nimport static io.aeron.samples.stress.StressUtil.mdcReqPubChannel;\nimport static io.aeron.samples.stress.StressUtil.serverAddress;\nimport static io.aeron.samples.stress.StressUtil.validateMessage;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * Client to stress to aeron driver with varying message sizes and MTU.\n */\npublic class StressMdcClient implements Agent\n{\n    private static final long TIMEOUT_MS = 5_000;\n    private static final int EXPECTED_RESPONSE_COUNT = 4;\n    private final String serverAddress;\n    private final String clientAddress;\n    private final EpochClock clock;\n    private final MutableDirectBuffer msg;\n    private final Long2LongCounterMap inflightMessages = new Long2LongCounterMap(-1);\n    private final FragmentAssembler mdcRspAssembler1 = new FragmentAssembler(this::mdcRspHandler);\n    private final FragmentAssembler mdcRspAssembler2 = new FragmentAssembler(this::mdcRspHandler);\n    private final int maxInflight;\n    private final int totalToSend;\n    private final int mtu;\n    private final byte[] buffer = new byte[2 * MAX_UDP_PAYLOAD_LENGTH];\n    private final CRC64 crc = new CRC64();\n\n    private Aeron aeron;\n    private Publication mdcPublication;\n    private Subscription mdcSubscription1;\n    private Subscription mdcSubscription2;\n    private long correlationId = 0;\n    private long lastMessageSent = 0;\n    private int messageLength;\n    private Counter clientReceiveCount;\n    private Counter clientSendCount;\n\n    /**\n     * Construct Stress Client.\n     *\n     * @param serverAddress server address to connect to.\n     * @param clientAddress local address to get responses.\n     * @param clock         for timing.\n     * @param maxInflight   maximum number of messages in flight.\n     * @param totalToSend   total number of messages to send.\n     * @param mtu           the mtu to use.\n     */\n    public StressMdcClient(\n        final String serverAddress,\n        final String clientAddress,\n        final EpochClock clock,\n        final int maxInflight,\n        final int totalToSend,\n        final int mtu)\n    {\n        this.serverAddress = serverAddress;\n        this.clientAddress = clientAddress;\n        this.clock = clock;\n        this.maxInflight = maxInflight;\n        this.totalToSend = totalToSend;\n        this.mtu = mtu;\n\n        final Random r = new Random(42);\n        r.nextBytes(buffer);\n        msg = new UnsafeBuffer(buffer);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        aeron = Aeron.connect(new Aeron.Context());\n\n        mdcPublication = aeron.addExclusivePublication(\n            mdcReqPubChannel(clientAddress).mtu(mtu).linger(0L).build(), MDC_STREAM_ID);\n\n        mdcSubscription1 = aeron.addSubscription(\n            StressUtil.mdcRspSubChannel1(serverAddress, clientAddress).build(),\n            MDC_STREAM_ID,\n            StressUtil::imageAvailable,\n            StressUtil::imageUnavailable);\n        mdcSubscription2 = aeron.addSubscription(\n            StressUtil.mdcRspSubChannel2(serverAddress, clientAddress).build(),\n            MDC_STREAM_ID,\n            StressUtil::imageAvailable,\n            StressUtil::imageUnavailable);\n\n        clientReceiveCount = aeron.addCounter(StressUtil.CLIENT_RECV_COUNT, \"Client Receive Count\");\n        clientSendCount = aeron.addCounter(StressUtil.CLIENT_SEND_COUNT, \"Client Send Count\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        if (!mdcSubscription1.isConnected() || !mdcSubscription2.isConnected())\n        {\n            return 0;\n        }\n\n        if (0 == messageLength)\n        {\n            throw new IllegalStateException(\"messageLength has not been set\");\n        }\n\n        int sendCount = 0;\n        while (inflightMessages.size() < maxInflight && correlationId < totalToSend &&\n            0 < mdcPublication.offer(msg, 0, messageLength, this::currentCorrelationId))\n        {\n            inflightMessages.put(correlationId, 0);\n            correlationId++;\n            sendCount++;\n            lastMessageSent = clock.time();\n\n            clientSendCount.increment();\n        }\n\n        int recvCount = 0;\n        int count;\n        while (0 != (count = poll(maxInflight)))\n        {\n            recvCount += count;\n            // Spin\n        }\n\n        if (0 < correlationId && 0 == recvCount && !inflightMessages.isEmpty())\n        {\n            final long timeSinceLastMessageMs = clock.time() - lastMessageSent;\n            if (TIMEOUT_MS < timeSinceLastMessageMs)\n            {\n                throw new RuntimeException(\"No response received for \" + timeSinceLastMessageMs + \"ms, client=\" + this);\n            }\n        }\n\n        return sendCount;\n    }\n\n    private int poll(final int pollLimit)\n    {\n        int count = 0;\n        count += mdcSubscription1.poll(mdcRspAssembler1, pollLimit);\n        count += mdcSubscription2.poll(mdcRspAssembler2, pollLimit);\n        return count;\n    }\n\n    private boolean isComplete()\n    {\n        return totalToSend <= correlationId && inflightMessages.isEmpty();\n    }\n\n    private void mdcRspHandler(\n        final DirectBuffer msg,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        final long correlationId = header.reservedValue();\n\n        validateMessage(crc, msg, offset, length, correlationId);\n\n        clientReceiveCount.increment();\n\n        final long responseCount = inflightMessages.incrementAndGet(correlationId);\n        if (EXPECTED_RESPONSE_COUNT <= responseCount)\n        {\n            inflightMessages.remove(correlationId);\n        }\n    }\n\n    private long currentCorrelationId(final DirectBuffer message, final int offset, final int length)\n    {\n        return correlationId;\n    }\n\n    void reset(final int messageLength)\n    {\n        this.messageLength = messageLength;\n        this.correlationId = 0;\n        final int payloadLength = messageLength - BitUtil.SIZE_OF_LONG;\n        final long crcValue = crc.recalculate(buffer, BitUtil.SIZE_OF_LONG, payloadLength);\n        msg.putLong(0, crcValue, LITTLE_ENDIAN);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        CloseHelper.quietCloseAll(aeron);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return null;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"StressClient{\" +\n            \"inflightMessages=\" + inflightMessages +\n            \", mtu=\" + mtu +\n            \", messageLength=\" + messageLength +\n            \", correlationId=\" + correlationId +\n            \", lastMessageSent=\" + lastMessageSent +\n            '}';\n    }\n\n    /**\n     * Entry point.\n     *\n     * @param args command line arguments.\n     */\n    public static void main(final String[] args)\n    {\n        final IdleStrategy idleStrategy = new YieldingIdleStrategy();\n\n        final int startMessageLength = 32;\n        final int maxMessageLength = 2 * MAX_UDP_PAYLOAD_LENGTH;\n        final int totalToSend = 100;\n\n        for (final int mtu : MTU_LENGTHS)\n        {\n            final StressMdcClient client = new StressMdcClient(\n                serverAddress(), clientAddress(), new SystemEpochClock(), 20, totalToSend, mtu);\n\n            try\n            {\n                client.onStart();\n\n                for (int messageLength = startMessageLength; messageLength <= maxMessageLength; messageLength += 1024)\n                {\n                    client.reset(messageLength);\n\n                    while (!client.isComplete())\n                    {\n                        idleStrategy.idle(client.doWork());\n                    }\n                }\n\n                info(\"Complete mtu=\" + mtu);\n            }\n            finally\n            {\n                client.onClose();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/stress/StressMdcServer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.stress;\n\nimport io.aeron.Aeron;\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Counter;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.Agent;\n\nimport java.util.concurrent.locks.LockSupport;\n\nimport static io.aeron.samples.stress.StressUtil.MDC_STREAM_ID;\nimport static io.aeron.samples.stress.StressUtil.clientAddress;\nimport static io.aeron.samples.stress.StressUtil.mdcRspPubChannel;\nimport static io.aeron.samples.stress.StressUtil.serverAddress;\nimport static io.aeron.samples.stress.StressUtil.validateMessage;\n\n/**\n * A server that will echo back messages on specific channels to cover a variety of test scenarios designed to stress\n * some of the publication edge cases. E.g. IP fragmentation.\n */\npublic class StressMdcServer implements Agent\n{\n    private final String serverAddress;\n    private final String clientAddress;\n    private final ControlledFragmentAssembler mdcFragmentAssembler1 = new ControlledFragmentAssembler(\n        this::mdcReqHandler);\n    private final ControlledFragmentAssembler mdcFragmentAssembler2 = new ControlledFragmentAssembler(\n        this::mdcReqHandler);\n    private final SimpleReservedValueSupplier valueSupplier = new SimpleReservedValueSupplier();\n    private final CRC64 crc = new CRC64();\n\n    private Aeron aeron;\n    private Subscription mdcSubscription1;\n    private Subscription mdcSubscription2;\n    private Publication mdcPublication;\n    private Counter serverReceiveCount;\n    private Counter serverSendCount;\n\n    /**\n     * Construct stress server.\n     *\n     * @param serverAddress local address for the server to listen for requests.\n     * @param clientAddress remote address for the server to send responses.\n     */\n    public StressMdcServer(final String serverAddress, final String clientAddress)\n    {\n        this.serverAddress = serverAddress;\n        this.clientAddress = clientAddress;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        aeron = Aeron.connect(new Aeron.Context());\n\n        mdcSubscription1 = aeron.addSubscription(\n            StressUtil.mdcReqSubChannel1(serverAddress, clientAddress).build(),\n            MDC_STREAM_ID,\n            StressUtil::imageAvailable,\n            StressUtil::imageUnavailable);\n        mdcSubscription2 = aeron.addSubscription(\n            StressUtil.mdcReqSubChannel2(serverAddress, clientAddress).build(),\n            MDC_STREAM_ID,\n            StressUtil::imageAvailable,\n            StressUtil::imageUnavailable);\n\n        mdcPublication = aeron.addPublication(mdcRspPubChannel(serverAddress).linger(0L).build(), MDC_STREAM_ID);\n\n        serverReceiveCount = aeron.addCounter(StressUtil.SERVER_RECV_COUNT, \"Server Receive Count\");\n        serverSendCount = aeron.addCounter(StressUtil.SERVER_SEND_COUNT, \"Server Send Count\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        if (!mdcSubscription1.isConnected() || !mdcSubscription2.isConnected())\n        {\n            return 0;\n        }\n\n        int count = 0;\n\n        count += pollUnicast();\n\n        return count;\n    }\n\n    private int pollUnicast()\n    {\n        int count = 0;\n\n        count += mdcSubscription1.controlledPoll(mdcFragmentAssembler1, 1);\n        count += mdcSubscription2.controlledPoll(mdcFragmentAssembler2, 1);\n\n        return count;\n    }\n\n    private ControlledFragmentHandler.Action mdcReqHandler(\n        final DirectBuffer msg,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        final long correlationId = header.reservedValue();\n\n        validateMessage(crc, msg, offset, length, correlationId);\n\n        final long result = mdcPublication.offer(msg, offset, length, valueSupplier.set(correlationId));\n\n        if (result > 0)\n        {\n            serverReceiveCount.increment();\n            serverSendCount.increment();\n        }\n\n        return result < 0 ? ControlledFragmentHandler.Action.ABORT : ControlledFragmentHandler.Action.COMMIT;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"Stress MDC Server\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        CloseHelper.quietCloseAll(mdcSubscription1, mdcSubscription2, mdcPublication, aeron);\n        LockSupport.parkNanos(1_000_000_000L);\n    }\n\n    /**\n     * Entry point.\n     *\n     * @param args command line args.\n     */\n    public static void main(final String[] args)\n    {\n        final StressMdcServer server = new StressMdcServer(serverAddress(), clientAddress());\n        server.onStart();\n        try\n        {\n            while (!Thread.currentThread().isInterrupted())\n            {\n                server.doWork();\n            }\n        }\n        finally\n        {\n            server.onClose();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/stress/StressUnicastClient.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.stress;\n\nimport io.aeron.Aeron;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.Long2ObjectHashMap;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\n\nimport java.util.Random;\n\nimport static io.aeron.driver.Configuration.MAX_UDP_PAYLOAD_LENGTH;\nimport static io.aeron.samples.stress.StressUtil.MTU_LENGTHS;\nimport static io.aeron.samples.stress.StressUtil.UNICAST_STREAM_ID;\nimport static io.aeron.samples.stress.StressUtil.clientAddress;\nimport static io.aeron.samples.stress.StressUtil.info;\nimport static io.aeron.samples.stress.StressUtil.serverAddress;\nimport static io.aeron.samples.stress.StressUtil.unicastReqChannel;\nimport static io.aeron.samples.stress.StressUtil.unicastRspChannel;\nimport static io.aeron.samples.stress.StressUtil.validateMessage;\nimport static java.lang.Boolean.TRUE;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * Client to stress to aeron driver with varying message sizes and MTU.\n */\npublic class StressUnicastClient implements Agent\n{\n    private static final long TIMEOUT_MS = 5_000;\n    private final String serverAddress;\n    private final String clientAddress;\n    private final EpochClock clock;\n    private final MutableDirectBuffer msg;\n    private final Long2ObjectHashMap<Boolean> inflightMessages = new Long2ObjectHashMap<>();\n    private final FragmentAssembler unicastRspAssembler = new FragmentAssembler(this::unicastRspHandler);\n    private final int maxInflight;\n    private final int totalToSend;\n    private final int mtu;\n    private final byte[] buffer = new byte[2 * MAX_UDP_PAYLOAD_LENGTH];\n    private int messageLength;\n    private final CRC64 crc = new CRC64();\n\n    private Aeron aeron;\n    private Publication unicastPublication;\n    private Subscription unicastSubscription;\n    private long correlationId = 0;\n    private long lastMessageSent = 0;\n\n    /**\n     * Construct Stress Client.\n     *\n     * @param serverAddress server address to connect to.\n     * @param clientAddress local address to get responses.\n     * @param clock         for timing.\n     * @param maxInflight   maximum number of messages in flight.\n     * @param totalToSend   total number of messages to send.\n     * @param mtu           the mtu to use.\n     */\n    public StressUnicastClient(\n        final String serverAddress,\n        final String clientAddress,\n        final EpochClock clock,\n        final int maxInflight,\n        final int totalToSend,\n        final int mtu)\n    {\n        this.serverAddress = serverAddress;\n        this.clientAddress = clientAddress;\n        this.clock = clock;\n        this.maxInflight = maxInflight;\n        this.totalToSend = totalToSend;\n        this.mtu = mtu;\n\n        final Random r = new Random(42);\n        r.nextBytes(buffer);\n        msg = new UnsafeBuffer(buffer);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        info(\"server=\" + serverAddress + \", client=\" + clientAddress);\n        aeron = Aeron.connect(new Aeron.Context());\n        info(\"Connected to Aeron dir=\" + aeron.context().aeronDirectoryName());\n\n        unicastPublication = aeron.addExclusivePublication(\n            unicastReqChannel(serverAddress).mtu(mtu).linger(0L).build(), UNICAST_STREAM_ID);\n        unicastSubscription = aeron.addSubscription(unicastRspChannel(clientAddress).build(), UNICAST_STREAM_ID);\n\n        info(\"publications and subscriptions created\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        if (0 == messageLength)\n        {\n            throw new IllegalStateException(\"messageLength has not been set\");\n        }\n\n        int sendCount = 0;\n        while (inflightMessages.size() < maxInflight && correlationId < totalToSend &&\n            0 < unicastPublication.offer(msg, 0, messageLength, this::currentCorrelationId))\n        {\n            inflightMessages.put(correlationId, TRUE);\n            correlationId++;\n            sendCount++;\n            lastMessageSent = clock.time();\n        }\n\n        int recvCount = 0;\n        int count;\n        while (0 != (count = unicastSubscription.poll(unicastRspAssembler, maxInflight)))\n        {\n            recvCount += count;\n            // Spin\n        }\n\n        if (0 < correlationId && 0 == recvCount && !inflightMessages.isEmpty())\n        {\n            final long timeSinceLastMessageMs = clock.time() - lastMessageSent;\n            if (TIMEOUT_MS < timeSinceLastMessageMs)\n            {\n                throw new RuntimeException(\"No response received for \" + timeSinceLastMessageMs + \"ms, client=\" + this);\n            }\n        }\n\n        return sendCount;\n    }\n\n    private boolean isComplete()\n    {\n        return totalToSend <= correlationId && inflightMessages.isEmpty();\n    }\n\n    private void unicastRspHandler(\n        final DirectBuffer msg,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        final long correlationId = header.reservedValue();\n        validateMessage(crc, msg, offset, length, correlationId);\n        inflightMessages.remove(correlationId);\n    }\n\n    private long currentCorrelationId(final DirectBuffer message, final int offset, final int length)\n    {\n        return correlationId;\n    }\n\n    void reset(final int messageLength)\n    {\n        this.messageLength = messageLength;\n        this.correlationId = 0;\n        final int payloadLength = messageLength - BitUtil.SIZE_OF_LONG;\n        final long crcValue = crc.recalculate(buffer, BitUtil.SIZE_OF_LONG, payloadLength);\n        msg.putLong(0, crcValue, LITTLE_ENDIAN);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        CloseHelper.quietCloseAll(aeron);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return null;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String toString()\n    {\n        return \"StressClient{\" +\n            \"inflightMessages=\" + inflightMessages +\n            \", mtu=\" + mtu +\n            \", messageLength=\" + messageLength +\n            \", correlationId=\" + correlationId +\n            \", lastMessageSent=\" + lastMessageSent +\n            '}';\n    }\n\n    /**\n     * Entry point.\n     *\n     * @param args command line arguments.\n     */\n    public static void main(final String[] args)\n    {\n        final IdleStrategy idleStrategy = new YieldingIdleStrategy();\n        final int maxMessageLength = 2 * MAX_UDP_PAYLOAD_LENGTH;\n        final int totalToSend = 100;\n\n        for (final int mtu : MTU_LENGTHS)\n        {\n            final StressUnicastClient stressClient = new StressUnicastClient(\n                serverAddress(), clientAddress(), new SystemEpochClock(), 20, totalToSend, mtu);\n            stressClient.onStart();\n\n            try\n            {\n                for (int messageLength = 32; messageLength <= maxMessageLength; messageLength += 1024)\n                {\n                    stressClient.reset(messageLength);\n\n                    while (!stressClient.isComplete())\n                    {\n                        idleStrategy.idle(stressClient.doWork());\n                    }\n\n                }\n\n                info(\"Complete mtu=\" + mtu);\n            }\n            finally\n            {\n                stressClient.onClose();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/stress/StressUnicastServer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.stress;\n\nimport io.aeron.Aeron;\nimport io.aeron.ControlledFragmentAssembler;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.Agent;\n\nimport static io.aeron.samples.stress.StressUtil.UNICAST_STREAM_ID;\nimport static io.aeron.samples.stress.StressUtil.clientAddress;\nimport static io.aeron.samples.stress.StressUtil.info;\nimport static io.aeron.samples.stress.StressUtil.serverAddress;\nimport static io.aeron.samples.stress.StressUtil.unicastReqChannel;\nimport static io.aeron.samples.stress.StressUtil.unicastRspChannel;\nimport static io.aeron.samples.stress.StressUtil.validateMessage;\n\n/**\n * A server that will echo back messages on specific channels to cover a variety of test scenarios designed to stress\n * some of the publication edge cases. E.g. IP fragmentation.\n */\npublic class StressUnicastServer implements Agent\n{\n    private final String serverAddress;\n    private final String clientAddress;\n    private final ControlledFragmentAssembler unicastFragmentAssembler = new ControlledFragmentAssembler(\n        this::unicastReqHandler);\n    private final SimpleReservedValueSupplier valueSupplier = new SimpleReservedValueSupplier();\n    private final CRC64 crc = new CRC64();\n\n    private Aeron aeron;\n    private Subscription unicastSubscription;\n    private Publication unicastPublication;\n\n    /**\n     * Construct stress server.\n     *\n     * @param serverAddress local address for the server to listen for requests.\n     * @param clientAddress remote address for the server to send responses.\n     */\n    public StressUnicastServer(final String serverAddress, final String clientAddress)\n    {\n        this.serverAddress = serverAddress;\n        this.clientAddress = clientAddress;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onStart()\n    {\n        info(\"server=\" + serverAddress + \", client=\" + clientAddress);\n        aeron = Aeron.connect();\n        info(\"Connected to Aeron dir=\" + aeron.context().aeronDirectoryName());\n\n        unicastSubscription = aeron.addSubscription(\n            unicastReqChannel(serverAddress).build(),\n            UNICAST_STREAM_ID,\n            StressUtil::imageAvailable,\n            StressUtil::imageUnavailable);\n        unicastPublication = aeron.addPublication(unicastRspChannel(clientAddress).build(), UNICAST_STREAM_ID);\n\n        info(\"publications and subscriptions created\");\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public int doWork()\n    {\n        int count = 0;\n\n        count += pollUnicast();\n\n        return count;\n    }\n\n    private int pollUnicast()\n    {\n        return unicastSubscription.controlledPoll(unicastFragmentAssembler, 1);\n    }\n\n    private ControlledFragmentHandler.Action unicastReqHandler(\n        final DirectBuffer msg,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n        final long correlationId = header.reservedValue();\n\n        validateMessage(crc, msg, offset, length, correlationId);\n\n        final long result = unicastPublication.offer(msg, offset, length, valueSupplier.set(correlationId));\n        return result < 0 ? ControlledFragmentHandler.Action.ABORT : ControlledFragmentHandler.Action.COMMIT;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public String roleName()\n    {\n        return \"Stress Server\";\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void onClose()\n    {\n        CloseHelper.quietCloseAll(unicastSubscription, unicastPublication, aeron);\n    }\n\n    /**\n     * Entry point.\n     *\n     * @param args command line args.\n     */\n    public static void main(final String[] args)\n    {\n        final StressUnicastServer server = new StressUnicastServer(serverAddress(), clientAddress());\n        server.onStart();\n        try\n        {\n            while (!Thread.currentThread().isInterrupted())\n            {\n                server.doWork();\n            }\n        }\n        finally\n        {\n            server.onClose();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/main/java/io/aeron/samples/stress/StressUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.stress;\n\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.Image;\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static io.aeron.driver.Configuration.MAX_UDP_PAYLOAD_LENGTH;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\n\n/**\n * Simple common methods and constants for the stress client/server.\n */\npublic class StressUtil\n{\n    static final int BASE_PORT = 9000;\n    static final int BASE_STREAM_ID = 10000;\n    static final int UNICAST_REQ_OFFSET = 1;\n    static final int UNICAST_RSP_OFFSET = 2;\n    static final int UNICAST_STREAM_ID_OFFSET = 1;\n    static final int MDC_STREAM_ID_OFFSET = 2;\n    static final int UNICAST_STREAM_ID = BASE_STREAM_ID + UNICAST_STREAM_ID_OFFSET;\n    static final int MDC_STREAM_ID = BASE_STREAM_ID + MDC_STREAM_ID_OFFSET;\n    static final int MDC_REQ_OFFSET_1 = 3;\n    static final int MDC_REQ_OFFSET_2 = 4;\n    static final int MDC_REQ_CONTROL_OFFSET = 5;\n    static final int MDC_RSP_OFFSET_1 = 6;\n    static final int MDC_RSP_OFFSET_2 = 7;\n    static final int MDC_RSP_CONTROL_OFFSET = 8;\n    static final long RSP_GROUP_TAG = 1001;\n    static final long REQ_GROUP_TAG = 1002;\n    static final int SERVER_RECV_COUNT = 1000;\n    static final int SERVER_SEND_COUNT = 1001;\n    static final int CLIENT_RECV_COUNT = 1002;\n    static final int CLIENT_SEND_COUNT = 1003;\n    static final List<Integer> MTU_LENGTHS = Arrays.asList(\n        1408, 4000, 8192, 1 << 14, 1 << 15, MAX_UDP_PAYLOAD_LENGTH);\n\n    /**\n     * Log a simple message.\n     *\n     * @param message to be logged\n     */\n    public static void info(final String message)\n    {\n        System.out.println(message);\n    }\n\n    /**\n     * Start forming unicast request channel for the specific server.\n     *\n     * @param serverAddress as the endpoint receiver of the unicast traffic.\n     * @return a partially constructed URI with the endpoint set.\n     */\n    public static ChannelUriStringBuilder unicastReqChannel(final String serverAddress)\n    {\n        return new ChannelUriStringBuilder().media(\"udp\")\n            .endpoint(serverAddress + \":\" + (BASE_PORT + UNICAST_REQ_OFFSET));\n    }\n\n    /**\n     * Start forming unicast response channel for the specific server.\n     *\n     * @param clientAddress as the endpoint receiver of the unicast traffic.\n     * @return a partially constructed URI with the endpoint set.\n     */\n    public static ChannelUriStringBuilder unicastRspChannel(final String clientAddress)\n    {\n        return new ChannelUriStringBuilder().media(\"udp\")\n            .endpoint(clientAddress + \":\" + (BASE_PORT + UNICAST_RSP_OFFSET));\n    }\n\n    private static ChannelUriStringBuilder mdcChannel(\n        final String endpointAddress,\n        final int endpointOffset,\n        final String controlAddress,\n        final int controlOffset)\n    {\n        return new ChannelUriStringBuilder().media(\"udp\")\n            .endpoint(endpointAddress + \":\" + (BASE_PORT + endpointOffset))\n            .controlEndpoint(controlAddress + \":\" + (BASE_PORT + controlOffset));\n    }\n\n    static ChannelUriStringBuilder mdcReqSubChannel1(final String serverAddress, final String clientAddress)\n    {\n        return mdcChannel(serverAddress, MDC_REQ_OFFSET_1, clientAddress, MDC_REQ_CONTROL_OFFSET)\n            .groupTag(REQ_GROUP_TAG)\n            .alias(\"req_sub1\");\n    }\n\n    static ChannelUriStringBuilder mdcReqSubChannel2(final String serverAddress, final String clientAddress)\n    {\n        return mdcChannel(serverAddress, MDC_REQ_OFFSET_2, clientAddress, MDC_REQ_CONTROL_OFFSET)\n            .groupTag(REQ_GROUP_TAG)\n            .alias(\"req_sub2\");\n    }\n\n    static ChannelUriStringBuilder mdcReqPubChannel(final String clientAddress)\n    {\n        return new ChannelUriStringBuilder().media(\"udp\")\n            .controlEndpoint(clientAddress + \":\" + (BASE_PORT + MDC_REQ_CONTROL_OFFSET))\n            .taggedFlowControl(REQ_GROUP_TAG, 2, \"5s\")\n            .alias(\"req_pub\");\n    }\n\n    static ChannelUriStringBuilder mdcRspSubChannel1(final String serverAddress, final String clientAddress)\n    {\n        return mdcChannel(clientAddress, MDC_RSP_OFFSET_1, serverAddress, MDC_RSP_CONTROL_OFFSET)\n            .groupTag(RSP_GROUP_TAG)\n            .alias(\"rsp_sub1\");\n    }\n\n    static ChannelUriStringBuilder mdcRspSubChannel2(final String serverAddress, final String clientAddress)\n    {\n        return mdcChannel(clientAddress, MDC_RSP_OFFSET_2, serverAddress, MDC_RSP_CONTROL_OFFSET)\n            .groupTag(RSP_GROUP_TAG)\n            .alias(\"rsp_sub2\");\n    }\n\n    static ChannelUriStringBuilder mdcRspPubChannel(final String serverAddress)\n    {\n        return new ChannelUriStringBuilder().media(\"udp\")\n            .controlEndpoint(serverAddress + \":\" + (BASE_PORT + MDC_RSP_CONTROL_OFFSET))\n            .taggedFlowControl(RSP_GROUP_TAG, 2, \"5s\")\n            .alias(\"rsp_pub\");\n    }\n\n    static void imageAvailable(final Image image)\n    {\n        info(\"Available image=\" + image);\n    }\n\n    static void imageUnavailable(final Image image)\n    {\n        info(\"Unavailable image=\" + image);\n    }\n\n    static void error(final String message)\n    {\n        System.err.println(message);\n    }\n\n    static String serverAddress()\n    {\n        return System.getProperty(\"aeron.stress.server.address\", \"localhost\");\n    }\n\n    static String clientAddress()\n    {\n        return System.getProperty(\"aeron.stress.client.address\", \"localhost\");\n    }\n\n    static boolean crcMatches(final DirectBuffer msg, final int offset, final int length, final CRC64 crc)\n    {\n        final long recvCrc = msg.getLong(offset, LITTLE_ENDIAN);\n        final long calcCrc = crc.recalculate(msg, offset + BitUtil.SIZE_OF_LONG, length - BitUtil.SIZE_OF_LONG);\n\n        return calcCrc == recvCrc;\n    }\n\n    static void validateMessage(\n        final CRC64 crc,\n        final DirectBuffer msg,\n        final int offset,\n        final int length,\n        final long correlationId)\n    {\n        final long recvCrc = msg.getLong(offset, LITTLE_ENDIAN);\n        final long calcCrc = crc.recalculate(msg, offset + BitUtil.SIZE_OF_LONG, length - BitUtil.SIZE_OF_LONG);\n\n        if (calcCrc != recvCrc)\n        {\n            throw new RuntimeException(\n                \"CRC validation failed, correlationId=\" + correlationId +\n                \", length=\" + length + \", calc=\" + calcCrc + \", recv=\" + recvCrc);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/test/java/io/aeron/cluster/ConsensusModuleSnapshotPendingServiceMessagesPatchTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.codecs.ConsensusModuleDecoder;\nimport io.aeron.cluster.codecs.ConsensusModuleEncoder;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.PendingMessageTrackerDecoder;\nimport io.aeron.cluster.codecs.PendingMessageTrackerEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderDecoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderEncoder;\nimport io.aeron.samples.archive.RecordingReplicator;\nimport io.aeron.samples.archive.RecordingSignalCapture;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.collections.IntArrayList;\nimport org.agrona.collections.LongArrayList;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.MappedByteBuffer;\nimport java.util.ArrayList;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.IntFunction;\n\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.CommonContext.NULL_SESSION_ID;\nimport static io.aeron.cluster.ConsensusModuleSnapshotPendingServiceMessagesPatch.replayLocalSnapshotRecording;\nimport static io.aeron.cluster.PendingServiceMessageTracker.*;\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ConsensusModuleSnapshotPendingServiceMessagesPatchTest\n{\n    private static final int NUM_MESSAGES = 1987;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @AfterEach\n    void tearDown()\n    {\n        TestNode.MessageTrackingService.delaySessionMessageProcessing(false);\n    }\n\n    @Test\n    void executeThrowsNullPointerExceptionIfClusterDirIsNull()\n    {\n        //noinspection DataFlowIssue\n        assertThrowsExactly(\n            NullPointerException.class,\n            () -> new ConsensusModuleSnapshotPendingServiceMessagesPatch().execute(null));\n    }\n\n    @Test\n    void executeThrowsIllegalArgumentExceptionIfClusterDirDoesNotExist()\n    {\n        final File clusterDir = new File(\"non-existing-file-blah-blah\");\n\n        final IllegalArgumentException exception = assertThrowsExactly(\n            IllegalArgumentException.class,\n            () -> new ConsensusModuleSnapshotPendingServiceMessagesPatch().execute(clusterDir));\n        assertEquals(\"invalid cluster directory: \" + clusterDir.getAbsolutePath(), exception.getMessage());\n    }\n\n    @Test\n    void executeThrowsIllegalArgumentExceptionIfClusterDirIsNotADirectory(\n        final @TempDir File tempDir) throws IOException\n    {\n        final File clusterDir = new File(tempDir, \"file.txt\");\n        assertTrue(clusterDir.createNewFile());\n\n        final IllegalArgumentException exception = assertThrowsExactly(\n            IllegalArgumentException.class,\n            () -> new ConsensusModuleSnapshotPendingServiceMessagesPatch().execute(clusterDir));\n        assertEquals(\"invalid cluster directory: \" + clusterDir.getAbsolutePath(), exception.getMessage());\n    }\n\n    @Test\n    void executeThrowsClusterExceptionIfClusterDirDoesNotContainARecordingLog(final @TempDir File tempDir)\n    {\n        final File clusterDir = new File(tempDir, \"cluster-dir\");\n        assertTrue(clusterDir.mkdir());\n\n        final ClusterException exception = assertThrowsExactly(\n            ClusterException.class,\n            () -> new ConsensusModuleSnapshotPendingServiceMessagesPatch().execute(clusterDir));\n        assertInstanceOf(IOException.class, exception.getCause());\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(60)\n    void executeIsANoOpIfTheSnapshotIsValid()\n    {\n        final IntFunction<TestNode.TestService[]> servicesSupplier =\n            (i) -> new TestNode.TestService[]\n            {\n                new TestNode.MessageTrackingService(1, i),\n                new TestNode.MessageTrackingService(2, i)\n            };\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withTimerServiceSupplier(new PriorityHeapTimerServiceSupplier())\n            .withServiceSupplier(servicesSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        final int serviceCount = cluster.node(0).services().length;\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n\n        TestNode.MessageTrackingService.delaySessionMessageProcessing(true);\n        int messageCount = 0;\n        final ExpandableArrayBuffer msgBuffer = cluster.msgBuffer();\n        for (int i = 0; i < NUM_MESSAGES; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n        TestNode.MessageTrackingService.delaySessionMessageProcessing(false);\n\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitServiceMessages(cluster, serviceCount, messageCount);\n        stopConsensusModulesAndServices(cluster);\n\n        final File leaderClusterDir = leader.consensusModule().context().clusterDir();\n        final RecordingLog.Entry leaderSnapshot = ClusterTool.findLatestValidSnapshot(leaderClusterDir);\n        assertNotNull(leaderSnapshot);\n\n        final MutableLong mutableNextSessionId = new MutableLong(NULL_SESSION_ID);\n        final MutableLong mutableNextServiceSessionId = new MutableLong(NULL_SESSION_ID);\n        final MutableLong mutableLogServiceSessionId = new MutableLong(NULL_SESSION_ID);\n        final LongArrayList pendingMessageClusterSessionIds = new LongArrayList();\n        final ConsensusModuleSnapshotListener stateReader = new NoOpConsensusModuleSnapshotListener()\n        {\n            public void onLoadConsensusModuleState(\n                final long nextSessionId,\n                final long nextServiceSessionId,\n                final long logServiceSessionId,\n                final int pendingMessageCapacity,\n                final DirectBuffer buffer,\n                final int offset,\n                final int length)\n            {\n                mutableNextSessionId.set(nextSessionId);\n                mutableNextServiceSessionId.set(nextServiceSessionId);\n                mutableLogServiceSessionId.set(logServiceSessionId);\n            }\n\n            public void onLoadPendingMessage(\n                final long clusterSessionId, final DirectBuffer buffer, final int offset, final int length)\n            {\n                pendingMessageClusterSessionIds.add(clusterSessionId);\n            }\n        };\n\n        readSnapshotRecording(leader, leaderSnapshot.recordingId, stateReader);\n\n        final long beforeNextSessionId = mutableNextSessionId.get();\n        final long beforeNextServiceSessionId = mutableNextServiceSessionId.get();\n        final long beforeLogServiceSessionId = mutableLogServiceSessionId.get();\n        final long[] beforeClusterSessionIds = pendingMessageClusterSessionIds.toLongArray();\n        assertNotEquals(NULL_SESSION_ID, beforeNextSessionId);\n        assertNotEquals(NULL_SESSION_ID, beforeNextServiceSessionId);\n        assertNotEquals(NULL_SESSION_ID, beforeLogServiceSessionId);\n        assertNotEquals(beforeNextSessionId, beforeNextServiceSessionId);\n        assertNotEquals(beforeNextSessionId, beforeLogServiceSessionId);\n        assertNotEquals(beforeNextServiceSessionId, beforeLogServiceSessionId);\n        assertNotEquals(0, beforeClusterSessionIds.length);\n\n        final ConsensusModuleSnapshotPendingServiceMessagesPatch snapshotPatch =\n            new ConsensusModuleSnapshotPendingServiceMessagesPatch(\n            leader.archive().context().localControlChannel(),\n            leader.archive().context().localControlStreamId());\n        assertFalse(snapshotPatch.execute(leaderClusterDir));\n\n        mutableNextSessionId.set(NULL_SESSION_ID);\n        mutableNextServiceSessionId.set(NULL_SESSION_ID);\n        mutableLogServiceSessionId.set(NULL_SESSION_ID);\n\n        pendingMessageClusterSessionIds.clear();\n        readSnapshotRecording(leader, leaderSnapshot.recordingId, stateReader);\n\n        assertEquals(beforeNextSessionId, mutableNextSessionId.get());\n        assertEquals(beforeNextServiceSessionId, mutableNextServiceSessionId.get());\n        assertEquals(beforeLogServiceSessionId, mutableLogServiceSessionId.get());\n        assertArrayEquals(beforeClusterSessionIds, pendingMessageClusterSessionIds.toLongArray());\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"-9193372036854775999, -9223372036854775808, -9223372036854775808, -9200000000000000000, LogServiceSessionId\",\n        \"1000000, 1000, 500000, 1000000, LogServiceSessionId\",\n        \"1000000, $compute, 1000001, 1000999, LogServiceSessionId\",\n        \"5000, 200000, 100000, 150000, NextServiceSessionId\",\n        \"$compute, 200000, 1, 199999, NextServiceSessionId\",\n        \"7777, 9999, 1000000000000000, 1223372036854775, MaxClusterSessionId\" })\n    @SlowTest\n    @InterruptAfter(60)\n    @SuppressWarnings(\"MethodLength\")\n    void executeShouldPatchTheStateOfTheLeaderSnapshot(\n        final String baseLogServiceSessionId,\n        final String baseNextServiceSessionId,\n        final long clusterSessionIdLowerBound,\n        final long clusterSessionIdUpperBound,\n        final String mode)\n    {\n        final IntFunction<TestNode.TestService[]> servicesSupplier =\n            (i) -> new TestNode.TestService[]\n            {\n                new TestNode.MessageTrackingService(1, i),\n                new TestNode.MessageTrackingService(2, i)\n            };\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withTimerServiceSupplier(new PriorityHeapTimerServiceSupplier())\n            .withServiceSupplier(servicesSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        final int serviceCount = cluster.node(0).services().length;\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n\n        TestNode.MessageTrackingService.delaySessionMessageProcessing(true);\n        int messageCount = 0;\n        final ExpandableArrayBuffer msgBuffer = cluster.msgBuffer();\n        for (int i = 0; i < NUM_MESSAGES; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n        TestNode.MessageTrackingService.delaySessionMessageProcessing(false);\n\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitServiceMessages(cluster, serviceCount, messageCount);\n        stopConsensusModulesAndServices(cluster);\n\n        final File leaderClusterDir = leader.consensusModule().context().clusterDir();\n        final RecordingLog.Entry leaderSnapshot = ClusterTool.findLatestValidSnapshot(leaderClusterDir);\n        assertNotNull(leaderSnapshot);\n\n        final MutableLong mutableNextSessionId = new MutableLong();\n        final MutableLong mutableNextServiceSessionId = new MutableLong();\n        final MutableLong mutableLogServiceSessionId = new MutableLong();\n        final MutableInteger consensusModuleStateOffset = new MutableInteger();\n        final IntArrayList pendingMessageTrackerOffsets = new IntArrayList();\n        final IntArrayList[] pendingMessageOffsets = new IntArrayList[serviceCount];\n        for (int i = 0; i < serviceCount; i++)\n        {\n            pendingMessageOffsets[i] = new IntArrayList();\n        }\n        readSnapshotRecording(\n            leader,\n            leaderSnapshot.recordingId,\n            new NoOpConsensusModuleSnapshotListener()\n            {\n                public void onLoadConsensusModuleState(\n                    final long nextSessionId,\n                    final long nextServiceSessionId,\n                    final long logServiceSessionId,\n                    final int pendingMessageCapacity,\n                    final DirectBuffer buffer,\n                    final int offset,\n                    final int length)\n                {\n                    mutableNextSessionId.set(nextSessionId);\n                    mutableNextServiceSessionId.set(nextServiceSessionId);\n                    mutableLogServiceSessionId.set(logServiceSessionId);\n                    consensusModuleStateOffset.set(offset);\n                    assertEquals(MessageHeaderDecoder.ENCODED_LENGTH + ConsensusModuleDecoder.BLOCK_LENGTH, length);\n                }\n\n                public void onLoadPendingMessageTracker(\n                    final long nextServiceSessionId,\n                    final long logServiceSessionId,\n                    final int pendingMessageCapacity,\n                    final int serviceId,\n                    final DirectBuffer buffer,\n                    final int offset,\n                    final int length)\n                {\n                    pendingMessageTrackerOffsets.add(offset);\n                    assertEquals(\n                        MessageHeaderDecoder.ENCODED_LENGTH + PendingMessageTrackerDecoder.BLOCK_LENGTH,\n                        length);\n                }\n\n                public void onLoadPendingMessage(\n                    final long clusterSessionId, final DirectBuffer buffer, final int offset, final int length)\n                {\n                    final int serviceId = serviceIdFromLogMessage(clusterSessionId);\n                    pendingMessageOffsets[serviceId].addInt(offset);\n                    assertEquals(\n                        MessageHeaderDecoder.ENCODED_LENGTH + SessionMessageHeaderDecoder.BLOCK_LENGTH + SIZE_OF_INT,\n                        length);\n                }\n            });\n        assertNotEquals(0, consensusModuleStateOffset.get());\n        assertEquals(serviceCount, pendingMessageTrackerOffsets.size());\n        for (int i = 0; i < serviceCount; i++)\n        {\n            assertNotEquals(0, pendingMessageOffsets[i].size());\n        }\n\n        final long expectedNextSessionId = mutableNextSessionId.get();\n        assertNotEquals(Long.toString(mutableLogServiceSessionId.get()), baseLogServiceSessionId);\n        assertNotEquals(Long.toString(mutableNextServiceSessionId.get()), baseNextServiceSessionId);\n\n        modifySnapshot(\n            leader,\n            leaderSnapshot,\n            consensusModuleStateOffset,\n            pendingMessageTrackerOffsets,\n            pendingMessageOffsets,\n            baseLogServiceSessionId,\n            baseNextServiceSessionId,\n            clusterSessionIdLowerBound,\n            clusterSessionIdUpperBound);\n\n        final ConsensusModuleSnapshotPendingServiceMessagesPatch snapshotPatch =\n            new ConsensusModuleSnapshotPendingServiceMessagesPatch(\n            leader.archive().context().localControlChannel(),\n            leader.archive().context().localControlStreamId());\n        assertTrue(snapshotPatch.execute(leaderClusterDir));\n\n        final MutableBoolean hasLoadedConsensusModuleState = new MutableBoolean();\n        final MutableInteger[] onLoadPendingMessageCounters = new MutableInteger[serviceCount];\n        for (int i = 0; i < serviceCount; i++)\n        {\n            onLoadPendingMessageCounters[i] = new MutableInteger();\n        }\n        readSnapshotRecording(\n            leader,\n            leaderSnapshot.recordingId,\n            new NoOpConsensusModuleSnapshotListener()\n            {\n                final long[] nextClusterSessionIds = new long[serviceCount];\n\n                public void onLoadConsensusModuleState(\n                    final long nextSessionId,\n                    final long nextServiceSessionId,\n                    final long logServiceSessionId,\n                    final int pendingMessageCapacity,\n                    final DirectBuffer buffer,\n                    final int offset,\n                    final int length)\n                {\n                    assertEquals(expectedNextSessionId, nextSessionId);\n                    final int numPendingMessages = pendingMessageOffsets[0].size();\n\n                    switch (mode)\n                    {\n                        case \"LogServiceSessionId\":\n                        {\n                            assertEquals(Long.parseLong(baseLogServiceSessionId), logServiceSessionId);\n                            assertEquals(logServiceSessionId + 1 + numPendingMessages, nextServiceSessionId);\n                            break;\n                        }\n\n                        case \"NextServiceSessionId\":\n                        {\n                            assertEquals(Long.parseLong(baseNextServiceSessionId), nextServiceSessionId);\n                            assertEquals(nextServiceSessionId - 1 - numPendingMessages, logServiceSessionId);\n                            break;\n                        }\n\n                        case \"MaxClusterSessionId\":\n                        {\n                            assertEquals(clusterSessionIdUpperBound - numPendingMessages, logServiceSessionId);\n                            assertEquals(clusterSessionIdUpperBound + 1, nextServiceSessionId);\n                            break;\n                        }\n\n                        default:\n                        {\n                            fail(\"unknown mode: \" + mode);\n                        }\n                    }\n\n                    hasLoadedConsensusModuleState.set(true);\n                }\n\n                public void onLoadPendingMessageTracker(\n                    final long nextServiceSessionId,\n                    final long logServiceSessionId,\n                    final int pendingMessageCapacity,\n                    final int serviceId,\n                    final DirectBuffer buffer,\n                    final int offset,\n                    final int length)\n                {\n                    nextClusterSessionIds[serviceId] = logServiceSessionId + 1;\n                }\n\n                public void onLoadPendingMessage(\n                    final long clusterSessionId, final DirectBuffer buffer, final int offset, final int length)\n                {\n                    final int serviceId = serviceIdFromLogMessage(clusterSessionId);\n                    onLoadPendingMessageCounters[serviceId].increment();\n                    assertEquals(\n                        nextClusterSessionIds[serviceId]++, clusterSessionId, \"Invalid pending message header!\");\n                }\n            });\n        assertTrue(hasLoadedConsensusModuleState.get());\n        for (int i = 0; i < serviceCount; i++)\n        {\n            assertEquals(pendingMessageOffsets[i].size(), onLoadPendingMessageCounters[i].get());\n        }\n\n        for (int i = 0; i < 3; i++)\n        {\n            if (i != leader.index())\n            {\n                replicatePatchedSnapshotToFollower(leader, leaderSnapshot.recordingId, cluster.node(i));\n            }\n        }\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n        cluster.connectClient();\n\n        for (int i = 0; i < 10; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitServiceMessages(cluster, serviceCount, messageCount);\n    }\n\n    private static void awaitServiceMessages(final TestCluster cluster, final int serviceCount, final int messageCount)\n    {\n        for (int i = 0; i < 3; i++)\n        {\n            final TestNode node = cluster.node(i);\n            final TestNode.TestService[] services = node.services();\n            for (final TestNode.TestService service : services)\n            {\n                // 1 client message + 3 service messages x number of services\n                cluster.awaitServiceMessageCount(node, service, messageCount + (messageCount * 3 * serviceCount));\n                // 2 timers x number of services\n                cluster.awaitTimerEventCount(node, service, messageCount * 2 * serviceCount);\n            }\n        }\n    }\n\n    private static void stopConsensusModulesAndServices(final TestCluster cluster)\n    {\n        final TestNode leader = cluster.findLeader();\n        for (int i = 0; i < 3; i++)\n        {\n            final TestNode node = cluster.node(i);\n            if (null != node && node != leader)\n            {\n                node.isTerminationExpected(true);\n                node.stopServiceContainers();\n                node.consensusModule().close();\n            }\n        }\n\n        leader.isTerminationExpected(true);\n        leader.stopServiceContainers();\n        leader.consensusModule().close();\n    }\n\n    private static void modifySnapshot(\n        final TestNode leader,\n        final RecordingLog.Entry leaderSnapshot,\n        final MutableInteger consensusModuleStateOffset,\n        final IntArrayList pendingMessageTrackerOffsets,\n        final IntArrayList[] pendingMessageOffsets,\n        final String baseLogServiceSessionId,\n        final String baseNextServiceSessionId,\n        final long clusterSessionIdLowerBound,\n        final long clusterSessionIdUpperBound)\n    {\n        final int numberOfPendingMessages = pendingMessageOffsets[0].size();\n        final long logServiceSessionId;\n        if (\"$compute\".equals(baseLogServiceSessionId))\n        {\n            logServiceSessionId = Long.parseLong(baseNextServiceSessionId) - 1 - numberOfPendingMessages;\n        }\n        else\n        {\n            logServiceSessionId = Long.parseLong(baseLogServiceSessionId);\n        }\n\n        final long nextServiceSessionId;\n        if (\"$compute\".equals(baseNextServiceSessionId))\n        {\n            nextServiceSessionId = Long.parseLong(baseLogServiceSessionId) + 1 + numberOfPendingMessages;\n        }\n        else\n        {\n            nextServiceSessionId = Long.parseLong(baseNextServiceSessionId);\n        }\n\n        final ArrayList<File> segmentFiles = listSegmentFiles(leader, leaderSnapshot.recordingId);\n        assertEquals(1, segmentFiles.size());\n        final MappedByteBuffer mappedByteBuffer = IoUtil.mapExistingFile(segmentFiles.get(0), \"snapshot file\");\n        try\n        {\n            final UnsafeBuffer snapshotBuffer = new UnsafeBuffer(mappedByteBuffer);\n            final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n            final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n\n            // Set the ConsensusModuleState values\n            final ConsensusModuleEncoder consensusModuleEncoder = new ConsensusModuleEncoder();\n            consensusModuleEncoder\n                .wrapAndApplyHeader(snapshotBuffer, consensusModuleStateOffset.get(), messageHeaderEncoder)\n                .logServiceSessionId(logServiceSessionId)\n                .nextServiceSessionId(nextServiceSessionId);\n\n            final PendingMessageTrackerEncoder pendingMessageTrackerEncoder = new PendingMessageTrackerEncoder();\n            final int serviceCount = pendingMessageTrackerOffsets.size();\n            for (int i = 0; i < serviceCount; i++)\n            {\n                final int offset = pendingMessageTrackerOffsets.get(i);\n                pendingMessageTrackerEncoder\n                    .wrapAndApplyHeader(snapshotBuffer, offset, messageHeaderEncoder)\n                    .logServiceSessionId(serviceSessionId(i, logServiceSessionId))\n                    .nextServiceSessionId(serviceSessionId(i, nextServiceSessionId));\n            }\n\n            // Now randomize clusterSessionId of every pending service message\n            final SessionMessageHeaderEncoder sessionMessageHeaderEncoder = new SessionMessageHeaderEncoder();\n            final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n            final MutableInteger count = new MutableInteger();\n            for (int i = 0; i < serviceCount; i++)\n            {\n                final IntArrayList offsets = pendingMessageOffsets[i];\n                offsets.forEachInt(\n                    (offset) ->\n                    {\n                        final long clusterSessionId = switch (count.getAndIncrement())\n                        {\n                            case 0 -> clusterSessionIdLowerBound;\n                            case 1 -> clusterSessionIdUpperBound;\n                            default -> ThreadLocalRandom.current().nextLong(\n                            clusterSessionIdLowerBound + 1, clusterSessionIdUpperBound);\n                        };\n\n                        final long originalClusterSessionId = sessionMessageHeaderDecoder\n                            .wrapAndApplyHeader(snapshotBuffer, offset, messageHeaderDecoder)\n                            .clusterSessionId();\n                        final int serviceId = serviceIdFromLogMessage(originalClusterSessionId);\n                        final long newClusterSessionId = serviceSessionId(serviceId, clusterSessionId);\n\n                        sessionMessageHeaderEncoder\n                            .wrapAndApplyHeader(snapshotBuffer, offset, messageHeaderEncoder)\n                            .clusterSessionId(newClusterSessionId);\n                    });\n            }\n        }\n        finally\n        {\n            IoUtil.unmap(mappedByteBuffer);\n        }\n    }\n\n    private static ArrayList<File> listSegmentFiles(final TestNode node, final long recordingId)\n    {\n        final File[] files = node.archive().context().archiveDir().listFiles();\n        assertNotNull(files);\n        final String segmentFileNamePrefix = recordingId + \"-\";\n        final ArrayList<File> segmentFiles = new ArrayList<>();\n        for (final File file : files)\n        {\n            if (null != file)\n            {\n                final String fileName = file.getName();\n                if (fileName.startsWith(segmentFileNamePrefix) && fileName.endsWith(\".rec\"))\n                {\n                    segmentFiles.add(file);\n                }\n            }\n        }\n\n        return segmentFiles;\n    }\n\n    private static void readSnapshotRecording(\n        final TestNode node,\n        final long recordingId,\n        final ConsensusModuleSnapshotListener snapshotListener)\n    {\n        try (\n            Aeron aeron = Aeron.connect(new Aeron.Context()\n                .aeronDirectoryName(node.mediaDriver().aeronDirectoryName()));\n            AeronArchive archive = AeronArchive.connect(new AeronArchive.Context()\n                .controlRequestChannel(node.archive().context().localControlChannel())\n                .controlRequestStreamId(node.archive().context().localControlStreamId())\n                .controlResponseChannel(IPC_CHANNEL)\n                .aeron(aeron)))\n        {\n            replayLocalSnapshotRecording(\n                aeron,\n                archive,\n                recordingId,\n                snapshotListener);\n        }\n    }\n\n    private static void replicatePatchedSnapshotToFollower(\n        final TestNode leader, final long snapshotRecordingId, final TestNode follower)\n    {\n        final File clusterDir = follower.consensusModule().context().clusterDir();\n        final RecordingLog.Entry followerSnapshot = ClusterTool.findLatestValidSnapshot(clusterDir);\n        assertNotNull(followerSnapshot, \"follower without a snapshot node\");\n\n        final String aeronDirectoryName = follower.mediaDriver().aeronDirectoryName();\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDirectoryName));\n            AeronArchive aeronArchive = AeronArchive.connect(new AeronArchive.Context()\n                .controlRequestChannel(follower.archive().context().localControlChannel())\n                .controlRequestStreamId(follower.archive().context().localControlStreamId())\n                .controlResponseChannel(IPC_CHANNEL)\n                .aeron(aeron)\n                .recordingSignalConsumer(new RecordingSignalCapture())))\n        {\n            final Archive.Context leaderArchiveContext = leader.archive().context();\n            final RecordingReplicator recordingReplicator = new RecordingReplicator(\n                aeronArchive,\n                snapshotRecordingId,\n                followerSnapshot.recordingId,\n                leaderArchiveContext.controlChannel(),\n                leaderArchiveContext.controlStreamId(),\n                null);\n\n            assertEquals(followerSnapshot.recordingId, recordingReplicator.replicate());\n        }\n    }\n\n    private static class NoOpConsensusModuleSnapshotListener implements ConsensusModuleSnapshotListener\n    {\n        public void onLoadBeginSnapshot(\n            final int appVersion,\n            final TimeUnit timeUnit,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n        }\n\n        public void onLoadConsensusModuleState(\n            final long nextSessionId,\n            final long nextServiceSessionId,\n            final long logServiceSessionId,\n            final int pendingMessageCapacity,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n        }\n\n        public void onLoadPendingMessage(\n            final long clusterSessionId, final DirectBuffer buffer, final int offset, final int length)\n        {\n        }\n\n        public void onLoadClusterSession(\n            final long clusterSessionId,\n            final long correlationId,\n            final long openedLogPosition,\n            final long timeOfLastActivity,\n            final CloseReason closeReason,\n            final int responseStreamId,\n            final String responseChannel,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n        }\n\n        public void onLoadTimer(\n            final long correlationId,\n            final long deadline,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n        }\n\n        public void onLoadPendingMessageTracker(\n            final long nextServiceSessionId,\n            final long logServiceSessionId,\n            final int pendingMessageCapacity,\n            final int serviceId,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n        }\n\n        public void onLoadEndSnapshot(final DirectBuffer buffer, final int offset, final int length)\n        {\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/test/java/io/aeron/samples/LogInspectorAsciiFormatBytesTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\npublic class LogInspectorAsciiFormatBytesTest\n{\n    private String originalDataFormatProperty;\n\n    private static Stream<Arguments> data()\n    {\n        return Stream.of(\n            arguments((byte)0x17, (char)0x17),\n            arguments((byte)0, (char)0),\n            arguments((byte)-1, (char)0),\n            arguments(Byte.MAX_VALUE, (char)Byte.MAX_VALUE),\n            arguments(Byte.MIN_VALUE, (char)0));\n    }\n\n    @BeforeEach\n    public void before()\n    {\n        originalDataFormatProperty = System.getProperty(LogInspector.AERON_LOG_DATA_FORMAT_PROP_NAME);\n    }\n\n    @AfterEach\n    public void after()\n    {\n        if (null == originalDataFormatProperty)\n        {\n            System.clearProperty(LogInspector.AERON_LOG_DATA_FORMAT_PROP_NAME);\n        }\n        else\n        {\n            System.setProperty(LogInspector.AERON_LOG_DATA_FORMAT_PROP_NAME, originalDataFormatProperty);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    public void shouldFormatBytesToAscii(final byte buffer, final char expected)\n    {\n        System.setProperty(LogInspector.AERON_LOG_DATA_FORMAT_PROP_NAME, \"ascii\");\n        final char[] formattedBytes = LogInspector.formatBytes(new UnsafeBuffer(new byte[]{ buffer }), 0, 1);\n\n        assertEquals(expected, formattedBytes[0]);\n    }\n}"
  },
  {
    "path": "aeron-samples/src/test/java/io/aeron/samples/StreamStatTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples;\n\nimport io.aeron.Aeron;\nimport io.aeron.ConcurrentPublication;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.status.StreamCounter;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.SortedMap;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.AeronCounters.DRIVER_PUBLISHER_LIMIT_TYPE_ID;\nimport static io.aeron.AeronCounters.DRIVER_RECEIVER_POS_TYPE_ID;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass StreamStatTest\n{\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setup(@TempDir final Path tempDir)\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(tempDir.resolve(\"aeron\").toString()), testWatcher);\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void teardown()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    @InterruptAfter(10)\n    void shouldCollectStreamStats()\n    {\n        final ExclusivePublication pub1 = aeron.addExclusivePublication(\n            \"aeron:ipc?alias=test-should-collect-stats|term-length=65536|tether=true|reliable=true|\" +\n            \"ssc=true|sparse=false|linger=60s|mtu=4096\",\n            2000);\n        final ConcurrentPublication pub2 =\n            aeron.addPublication(\"aeron:udp?session-id=\" + (pub1.sessionId() - 333) +\n            \"|endpoint=localhost:5555|mtu=2k|term-length=128K\",\n            909);\n        final ConcurrentPublication pub3 = aeron.addPublication(pub2.channel(), pub2.streamId());\n        final ExclusivePublication pub4 = aeron.addExclusivePublication(\n            \"aeron:udp?endpoint=localhost:7777|interface=127.0.0.1|init-term-id=-6548658|term-id=42|\" +\n            \"term-offset=1024|mtu=8192|ttl=5|group=true|term-length=256K|so-rcvbuf=128K|so-sndbuf=128K|rcv-wnd=128K|\" +\n            \"sparse=true|linger=0|nak-delay=100ms|tether=false|reliable=false|ssc=false|\" +\n            Tests.generateStringWithSuffix(\"alias=\", \"x\", 300),\n            500);\n        final ExclusivePublication pub5 = aeron.addExclusivePublication(\n            \"aeron:udp?session-id=\" + pub2.sessionId() +\n            \"|alias= @test @not @split @channel @uri @by @at @even @if @no @subscription @exists|\" +\n            \"endpoint=localhost:6666|interface=127.0.0.1|term-length=512K|mtu=1376\", 1);\n        assertEquals(pub1.sessionId() + 1, pub4.sessionId());\n        assertEquals(pub1.sessionId() - 333, pub2.sessionId());\n        assertEquals(pub2.sessionId(), pub3.sessionId());\n        assertNotSame(pub2, pub3);\n        assertEquals(pub2.sessionId(), pub5.sessionId());\n\n        final Subscription sub1 = aeron.addSubscription(pub1.channel(), pub1.streamId());\n        final Subscription sub2 = aeron.addSubscription(pub2.channel(), pub2.streamId());\n        final Subscription sub4 = aeron.addSubscription(pub4.channel(), pub4.streamId());\n\n        Tests.awaitConnected(pub1);\n        Tests.awaitConnected(pub2);\n        Tests.awaitConnected(pub3);\n        Tests.awaitConnected(pub4);\n        Tests.awaitConnected(sub1);\n        Tests.awaitConnected(sub2);\n        Tests.awaitConnected(sub4);\n\n        final int sub2RcvPosCounterId = findCounterIdByStream(\n            aeron.countersReader(), pub2.streamId());\n        final int sub4RcvPosCounterId = findCounterIdByStream(\n            aeron.countersReader(), pub4.streamId());\n\n        final UnsafeBuffer msg = new UnsafeBuffer(new byte[128]);\n        ThreadLocalRandom.current().nextBytes(msg.byteArray());\n        for (int i = 0; i < 5; i++)\n        {\n            while ((pub1.offer(msg) < 0))\n            {\n                Tests.yield();\n            }\n            while ((pub2.offer(msg, 0, 64) < 0))\n            {\n                Tests.yield();\n            }\n            while ((pub3.offer(msg, 0, 80) < 0))\n            {\n                Tests.yield();\n            }\n            while ((pub4.offer(msg, 105, 13) < 0))\n            {\n                Tests.yield();\n            }\n        }\n\n        final Subscription sub5 = aeron.addSubscription(pub4.channel(), pub4.streamId());\n        assertNotSame(sub4, sub5);\n        Tests.awaitConnected(sub5);\n        final CountingFragmentHandler sub1Handler = new CountingFragmentHandler(sub1);\n        final CountingFragmentHandler sub2Handler = new CountingFragmentHandler(sub2);\n        final CountingFragmentHandler sub4Handler = new CountingFragmentHandler(sub4);\n\n        sub1Handler.pollUntil(3);\n        sub2Handler.pollUntil(7);\n        sub4Handler.pollUntil(5);\n\n        while (1120 != aeron.countersReader().getCounterValue(sub2RcvPosCounterId))\n        {\n            Tests.yield();\n        }\n\n        while (1716702414144L != aeron.countersReader().getCounterValue(sub4RcvPosCounterId))\n        {\n            Tests.yield();\n        }\n\n        final StreamStat streamStat = new StreamStat(aeron.countersReader());\n        final Map<StreamStat.StreamCompositeKey, List<StreamStat.StreamPosition>> snapshot = streamStat.snapshot();\n        assertInstanceOf(SortedMap.class, snapshot);\n        final List<Map.Entry<StreamStat.StreamCompositeKey, List<StreamStat.StreamPosition>>> entries =\n            new ArrayList<>(snapshot.entrySet());\n\n        Map.Entry<StreamStat.StreamCompositeKey, List<StreamStat.StreamPosition>> entry = entries.get(0);\n        assertStreamInfo(\n            entry.getKey(),\n            pub5.sessionId(),\n            pub5.streamId(),\n            pub5.channel().substring(0, StreamCounter.MAX_CHANNEL_LENGTH),\n            pub5.channel());\n        assertEquals(6, entry.getValue().size());\n        assertStreamPosition(entry.getValue().get(0), 0);\n        assertStreamPosition(entry.getValue().get(1), 0);\n        assertStreamPosition(entry.getValue().get(2), 0);\n        assertStreamPosition(entry.getValue().get(3), 0);\n        assertStreamPosition(entry.getValue().get(4), 0);\n        assertStreamPosition(entry.getValue().get(5), 0);\n\n        entry = entries.get(1);\n        assertStreamInfo(entry.getKey(), pub2.sessionId(), pub2.streamId(), pub2.channel(), pub2.channel());\n        assertEquals(10, entry.getValue().size());\n        assertStreamPosition(entry.getValue().get(0), 1120);\n        assertStreamPosition(entry.getValue().get(1), 66656);\n        assertStreamPosition(entry.getValue().get(2), 1120);\n        assertStreamPosition(entry.getValue().get(3), 65536);\n        assertStreamPosition(entry.getValue().get(4), 0);\n        assertStreamPosition(entry.getValue().get(5), 0);\n        assertStreamPosition(entry.getValue().get(6), 448);\n        assertStreamPosition(entry.getValue().get(7), 1120);\n        assertStreamPosition(entry.getValue().get(8), 1120);\n        assertStreamPosition(entry.getValue().get(9), 0);\n\n        entry = entries.get(2);\n        assertStreamInfo(\n            entry.getKey(),\n            pub1.sessionId(),\n            pub1.streamId(),\n            pub1.channel().substring(0, StreamCounter.MAX_CHANNEL_LENGTH),\n            pub1.channel());\n        assertEquals(3, entry.getValue().size());\n        assertStreamPosition(entry.getValue().get(0), 800);\n        assertStreamPosition(entry.getValue().get(1), 32768);\n        assertStreamPosition(entry.getValue().get(2), 320);\n\n        entry = entries.get(3);\n        final int pub4LimitCounterId = aeron.countersReader()\n            .findByTypeIdAndRegistrationId(DRIVER_PUBLISHER_LIMIT_TYPE_ID, pub4.registrationId());\n        assertNotEquals(CountersReader.NULL_COUNTER_ID, pub4LimitCounterId);\n        final String pub4LimitLabel = aeron.countersReader().getCounterLabel(pub4LimitCounterId);\n        assertStreamInfo(\n            entry.getKey(),\n            pub4.sessionId(),\n            pub4.streamId(),\n            pub4.channel().substring(0, StreamCounter.MAX_CHANNEL_LENGTH),\n            pub4LimitLabel.substring(pub4LimitLabel.indexOf(\"aeron:\")));\n        assertEquals(11, entry.getValue().size());\n        assertStreamPosition(entry.getValue().get(0), 1716702414144L);\n        assertStreamPosition(entry.getValue().get(1), 1716702545216L);\n        assertStreamPosition(entry.getValue().get(2), 1716702414144L);\n        assertStreamPosition(entry.getValue().get(3), 1716702544896L);\n        assertStreamPosition(entry.getValue().get(4), 0L);\n        assertStreamPosition(entry.getValue().get(5), 0L);\n        assertStreamPosition(entry.getValue().get(6), 1716702414016L);\n        assertStreamPosition(entry.getValue().get(7), 1716702414144L);\n        assertStreamPosition(entry.getValue().get(8), 1716702414144L);\n        assertStreamPosition(entry.getValue().get(9), 0L);\n        assertStreamPosition(entry.getValue().get(10), 1716702413824L);\n    }\n\n    private int findCounterIdByStream(final CountersReader countersReader, final int streamId)\n    {\n        final MutableInteger counterId = new MutableInteger(-1);\n\n        countersReader.forEach(\n            (counterId1, typeId1, keyBuffer, label) ->\n            {\n                final int counterStreamId = keyBuffer.getInt(StreamCounter.STREAM_ID_OFFSET);\n                if (DRIVER_RECEIVER_POS_TYPE_ID == typeId1 && streamId == counterStreamId)\n                {\n                    assertEquals(-1, counterId.intValue(), () -> \"multiple rcv-pos found for streamId=\" + streamId);\n                    counterId.set(counterId1);\n                }\n            });\n\n        assertNotEquals(-1, counterId.intValue());\n\n        return counterId.intValue();\n    }\n\n    private static void assertStreamInfo(\n        final StreamStat.StreamCompositeKey key,\n        final int expectedSessionId,\n        final int expectedStreamId,\n        final String expectedChannel,\n        final String expectedFullChannel)\n    {\n        assertEquals(expectedSessionId, key.sessionId());\n        assertEquals(expectedStreamId, key.streamId());\n        assertEquals(expectedChannel, key.channel());\n        assertEquals(expectedFullChannel, key.fullChannel);\n    }\n\n    private static void assertStreamPosition(\n        final StreamStat.StreamPosition position,\n        final long expectedValue)\n    {\n        assertEquals(expectedValue, position.value());\n    }\n\n    private static final class CountingFragmentHandler implements FragmentHandler\n    {\n        private final Subscription subscription;\n        private int count;\n\n        private CountingFragmentHandler(final Subscription subscription)\n        {\n\n            this.subscription = subscription;\n        }\n\n        public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n        {\n            count++;\n        }\n\n        public void pollUntil(final int expectedCount)\n        {\n            while (count < expectedCount)\n            {\n                final int poll = subscription.poll(this, 1);\n                if (poll <= 0)\n                {\n                    Tests.yield();\n                }\n                else\n                {\n                    count += poll;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/test/java/io/aeron/samples/archive/RecordingDescriptorCollectorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.Publication;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.test.*;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.nio.file.Path;\nimport java.util.List;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith(InterruptingTestCallback.class)\n@SlowTest\n@SuppressWarnings(\"try\")\npublic class RecordingDescriptorCollectorTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(10)\n    void shouldCollectPagesOfRecordingDescriptors(@TempDir final Path tempDir)\n    {\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(CommonContext.generateRandomDirName()), systemTestWatcher);\n            Archive ignore = Archive.launch(TestContexts.localhostArchive()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .archiveDir(tempDir.resolve(\"archive\").toFile())\n                .deleteArchiveOnStart(true));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            AeronArchive aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive()\n                .aeron(aeron)\n                .ownsAeronClient(false)))\n        {\n            final int numRecordings = 10;\n            createRecordings(aeronArchive, numRecordings);\n\n            final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(3);\n            long fromRecordingId = 0;\n            int count;\n            while (0 < (count = aeronArchive.listRecordings(fromRecordingId, collector.poolSize(), collector.reset())))\n            {\n                final List<RecordingDescriptor> descriptors = collector.descriptors();\n                assertThat(count, lessThanOrEqualTo(collector.poolSize()));\n                assertThat(descriptors.size(), lessThanOrEqualTo(collector.poolSize()));\n\n                //noinspection OptionalGetWithoutIsPresent\n                final long maxRecordingId = descriptors.stream()\n                    .mapToLong(RecordingDescriptor::recordingId)\n                    .max()\n                    .getAsLong();\n\n                fromRecordingId = maxRecordingId + 1;\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAllowUserToRetainDescriptorsToPreventReuse(@TempDir final Path tempDir)\n    {\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(CommonContext.generateRandomDirName()), systemTestWatcher);\n            Archive ignore = Archive.launch(TestContexts.localhostArchive()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .archiveDir(tempDir.resolve(\"archive\").toFile())\n                .deleteArchiveOnStart(true));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            AeronArchive aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive()\n                .aeron(aeron)\n                .ownsAeronClient(false)))\n        {\n            createRecordings(aeronArchive, 3);\n\n            final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(1);\n            long fromRecordingId = 0;\n            int count = aeronArchive.listRecordings(fromRecordingId, collector.poolSize(), collector.reset());\n            assertEquals(1, count);\n\n            final RecordingDescriptor desc0 = collector.descriptors().get(0);\n\n            fromRecordingId += count;\n            count = aeronArchive.listRecordings(fromRecordingId, collector.poolSize(), collector.reset());\n            assertEquals(1, count);\n\n            final RecordingDescriptor desc1 = collector.descriptors().get(0);\n\n            assertEquals(desc0.recordingId(), desc1.recordingId());\n            desc1.retain();\n\n            fromRecordingId += count;\n            count = aeronArchive.listRecordings(fromRecordingId, collector.poolSize(), collector.reset());\n            assertEquals(1, count);\n\n            final RecordingDescriptor desc2 = collector.descriptors().get(0);\n\n            assertNotEquals(desc1.recordingId(), desc2.recordingId());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldShouldNotReuseDescriptorIfPoolSizeIsZero(@TempDir final Path tempDir)\n    {\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(CommonContext.generateRandomDirName()), systemTestWatcher);\n            Archive ignore = Archive.launch(TestContexts.localhostArchive()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .archiveDir(tempDir.resolve(\"archive\").toFile())\n                .deleteArchiveOnStart(true));\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            AeronArchive aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive()\n                .aeron(aeron)\n                .ownsAeronClient(false)))\n        {\n            createRecordings(aeronArchive, 3);\n\n            final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(0);\n            long fromRecordingId = 0;\n            int count = aeronArchive.listRecordings(fromRecordingId, 1, collector.reset());\n            assertEquals(1, count);\n\n            final RecordingDescriptor desc0 = collector.descriptors().get(0);\n\n            fromRecordingId += count;\n            count = aeronArchive.listRecordings(fromRecordingId, 1, collector.reset());\n            assertEquals(1, count);\n\n            final RecordingDescriptor desc1 = collector.descriptors().get(0);\n\n            assertNotEquals(desc0.recordingId(), desc1.recordingId());\n        }\n    }\n\n    private void createRecordings(final AeronArchive aeronArchive, final int numRecordings)\n    {\n        final UnsafeBuffer message = new UnsafeBuffer(\"this is some data\".getBytes());\n\n        for (int i = 0; i < numRecordings; i++)\n        {\n            try (Publication publication = aeronArchive.addRecordedPublication(\"aeron:ipc?ssc=true\", 10000 + i))\n            {\n                long expectedPosition;\n                while ((expectedPosition = publication.offer(message, 0, message.capacity())) < 0)\n                {\n                    Tests.yield();\n                }\n\n                long recordingId;\n                while ((recordingId = aeronArchive.findLastMatchingRecording(\n                    0, \"aeron:ipc\", publication.streamId(), publication.sessionId())) == Aeron.NULL_VALUE)\n                {\n                    Tests.yield();\n                }\n\n                while (expectedPosition < aeronArchive.getRecordingPosition(recordingId))\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/test/java/io/aeron/samples/archive/RecordingReplicatorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.archive;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Publication;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.file.Path;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass RecordingReplicatorTest\n{\n    private static final String SRC_ARCHIVE_CONTROL_CHANNEL =\n        \"aeron:udp?alias=src-request-channel|endpoint=localhost:8091\";\n    private static final int SRC_ARCHIVE_CONTROL_STREAM_ID = 1001;\n    private static final String SRC_ARCHIVE_REPLICATION_CHANNEL =\n        \"aeron:udp?alias=dst-replication-channel|endpoint=localhost:9998\";\n    private static final String DST_ARCHIVE_CONTROL_CHANNEL =\n        \"aeron:udp?alias=dst-request-channel|endpoint=localhost:8092\";\n    private static final int DST_ARCHIVE_CONTROL_STREAM_ID = 2002;\n    private static final String DST_ARCHIVE_REPLICATION_CHANNEL =\n        \"aeron:udp?alias=dst-replication-channel|endpoint=localhost:9999\";\n    private static final int TERM_BUFFER_LENGTH = 128 * 1024;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver srcMediaDriver;\n    private TestMediaDriver dstMediaDriver;\n    private Archive srcArchive;\n    private Archive dstArchive;\n    private AeronArchive srcAeronArchive;\n    private AeronArchive dstAeronArchive;\n\n    @BeforeEach\n    void setup(@TempDir final Path tempDir)\n    {\n        srcMediaDriver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(tempDir.resolve(\"src-driver\").toString())\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .ipcTermBufferLength(TERM_BUFFER_LENGTH)\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true),\n            testWatcher);\n        testWatcher.dataCollector().add(srcMediaDriver.context().aeronDirectory());\n\n        dstMediaDriver = TestMediaDriver.launch(new MediaDriver.Context()\n            .spiesSimulateConnection(true)\n            .aeronDirectoryName(tempDir.resolve(\"dst-driver\").toString())\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .ipcTermBufferLength(TERM_BUFFER_LENGTH)\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true),\n            testWatcher);\n        testWatcher.dataCollector().add(dstMediaDriver.context().aeronDirectory());\n\n        srcArchive = Archive.launch(new Archive.Context()\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .recordingEventsEnabled(false)\n            .aeronDirectoryName(srcMediaDriver.aeronDirectoryName())\n            .archiveDir(tempDir.resolve(\"src-archive\").toFile())\n            .controlChannel(SRC_ARCHIVE_CONTROL_CHANNEL)\n            .controlStreamId(SRC_ARCHIVE_CONTROL_STREAM_ID)\n            .replicationChannel(SRC_ARCHIVE_REPLICATION_CHANNEL));\n        testWatcher.dataCollector().add(srcArchive.context().archiveDir());\n\n        dstArchive = Archive.launch(new Archive.Context()\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .recordingEventsEnabled(false)\n            .aeronDirectoryName(dstMediaDriver.aeronDirectoryName())\n            .archiveDir(tempDir.resolve(\"dst-archive\").toFile())\n            .controlChannel(DST_ARCHIVE_CONTROL_CHANNEL)\n            .controlStreamId(DST_ARCHIVE_CONTROL_STREAM_ID)\n            .replicationChannel(DST_ARCHIVE_REPLICATION_CHANNEL));\n        testWatcher.dataCollector().add(dstArchive.context().archiveDir());\n\n        srcAeronArchive = AeronArchive.connect(new AeronArchive.Context()\n            .aeronDirectoryName(srcMediaDriver.aeronDirectoryName())\n            .controlRequestChannel(SRC_ARCHIVE_CONTROL_CHANNEL)\n            .controlRequestStreamId(SRC_ARCHIVE_CONTROL_STREAM_ID)\n            .controlResponseChannel(\"aeron:udp?alias=src-archive-response|endpoint=localhost:0\")\n            .recordingSignalConsumer(new RecordingSignalCapture()));\n\n        dstAeronArchive = AeronArchive.connect(new AeronArchive.Context()\n            .aeronDirectoryName(dstMediaDriver.aeronDirectoryName())\n            .controlRequestChannel(DST_ARCHIVE_CONTROL_CHANNEL)\n            .controlRequestStreamId(DST_ARCHIVE_CONTROL_STREAM_ID)\n            .controlResponseChannel(\"aeron:udp?alias=dst-archive-response|endpoint=localhost:0\")\n            .recordingSignalConsumer(new RecordingSignalCapture()));\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(dstAeronArchive, srcAeronArchive, dstArchive, srcArchive, dstMediaDriver, srcMediaDriver);\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, 9 })\n    @InterruptAfter(30)\n    void replicateAsNewRecording(final int srcMessageCount)\n    {\n        createRecording(srcAeronArchive, IPC_CHANNEL, 555, 3);\n        createRecording(srcAeronArchive, \"aeron:udp?endpoint=localhost:8108\", 666, 1);\n        final long srcRecordingId = createRecording(\n            srcAeronArchive, IPC_CHANNEL + \"?alias=third\", 1010101010, srcMessageCount);\n\n        final RecordingReplicator recordingReplicator = new RecordingReplicator(\n            dstAeronArchive,\n            srcRecordingId,\n            NULL_VALUE,\n            SRC_ARCHIVE_CONTROL_CHANNEL,\n            SRC_ARCHIVE_CONTROL_STREAM_ID,\n            null);\n        final long replicatedRecordingId = recordingReplicator.replicate();\n\n        verifyRecordingReplicated(srcRecordingId, replicatedRecordingId);\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, 7, 21 })\n    @InterruptAfter(30)\n    void replicateOverAnExistingRecording(final int srcMessageCount)\n    {\n        createRecording(srcAeronArchive, IPC_CHANNEL, 555, 1);\n        createRecording(srcAeronArchive, \"aeron:udp?endpoint=localhost:8108\", 666, 1);\n        final long srcRecordingId = createRecording(\n            srcAeronArchive, IPC_CHANNEL + \"?alias=third\", 1010101010, srcMessageCount);\n\n        createRecording(dstAeronArchive, IPC_CHANNEL + \"?alias=one\", 111, 3);\n        final long dstRecordingId = createRecording(\n            dstAeronArchive,\n            \"aeron:udp?endpoint=localhost:8114|init-term-id=13|term-id=27|term-offset=1024|term-length=64K\",\n            444, 19);\n\n        try (AeronArchive aeronArchive = AeronArchive.connect(new AeronArchive.Context()\n            .aeronDirectoryName(dstMediaDriver.aeronDirectoryName())\n            .controlRequestChannel(AeronArchive.Configuration.localControlChannel())\n            .controlRequestStreamId(AeronArchive.Configuration.localControlStreamId())\n            .controlResponseChannel(AeronArchive.Configuration.localControlChannel())\n            .recordingSignalConsumer(new RecordingSignalCapture())))\n        {\n            final RecordingReplicator recordingReplicator = new RecordingReplicator(\n                aeronArchive,\n                srcRecordingId,\n                dstRecordingId,\n                SRC_ARCHIVE_CONTROL_CHANNEL,\n                SRC_ARCHIVE_CONTROL_STREAM_ID,\n                \"aeron:udp?alias=test-channel|endpoint=localhost:9393\");\n            final long replicatedRecordingId = recordingReplicator.replicate();\n            assertEquals(dstRecordingId, replicatedRecordingId);\n\n            verifyRecordingReplicated(srcRecordingId, replicatedRecordingId);\n        }\n    }\n\n    private long createRecording(\n        final AeronArchive aeronArchive,\n        final String channel,\n        final int streamId,\n        final int numMessages)\n    {\n        try (ExclusivePublication publication = aeronArchive.addRecordedExclusivePublication(channel, streamId))\n        {\n            final CountersReader counters = aeronArchive.context().aeron().countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(\n                counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n            final BufferClaim bufferClaim = new BufferClaim();\n\n            for (int i = 0; i < numMessages; i++)\n            {\n                final int messageSize = ThreadLocalRandom.current().nextInt(8, 500);\n                long position;\n                while ((position = publication.tryClaim(messageSize, bufferClaim)) < 0)\n                {\n                    if (position == Publication.CLOSED ||\n                        position == Publication.NOT_CONNECTED ||\n                        position == Publication.MAX_POSITION_EXCEEDED)\n                    {\n                        fail(\"tryClaim failed: \" + Publication.errorString(position));\n                    }\n                    Tests.yield();\n                }\n\n                final MutableDirectBuffer buffer = bufferClaim.buffer();\n                final int offset = bufferClaim.offset();\n                buffer.putInt(offset, ThreadLocalRandom.current().nextInt(), LITTLE_ENDIAN);\n                buffer.putInt(\n                    offset + (messageSize - SIZE_OF_INT), ThreadLocalRandom.current().nextInt(), LITTLE_ENDIAN);\n                bufferClaim.commit();\n            }\n\n            Tests.awaitPosition(counters, counterId, publication.position());\n\n            final RecordingSignalCapture signalCapture =\n                (RecordingSignalCapture)aeronArchive.context().recordingSignalConsumer();\n            signalCapture.reset();\n            aeronArchive.stopRecording(publication);\n            signalCapture.awaitSignalForRecordingId(aeronArchive, recordingId, RecordingSignal.STOP);\n\n            if (numMessages > 0)\n            {\n                final long startPosition = aeronArchive.getStartPosition(recordingId);\n                final long stopPosition = aeronArchive.getStopPosition(recordingId);\n                assertNotEquals(startPosition, stopPosition);\n            }\n\n            return recordingId;\n        }\n    }\n\n    private void verifyRecordingReplicated(final long srcRecordingId, final long dstRecordingId)\n    {\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(1);\n\n        assertEquals(1, srcAeronArchive.listRecording(srcRecordingId, collector.reset()));\n        final RecordingDescriptor srcRecording = collector.descriptors().get(0).retain();\n\n        while (dstAeronArchive.getStopPosition(dstRecordingId) == AeronArchive.NULL_POSITION)\n        {\n            Tests.yield();\n        }\n\n        assertEquals(1, dstAeronArchive.listRecording(dstRecordingId, collector.reset()));\n        final RecordingDescriptor dstRecording = collector.descriptors().get(0).retain();\n\n        assertNotEquals(srcRecording.recordingId(), dstRecording.recordingId());\n        assertEquals(srcRecording.startTimestamp(), dstRecording.startTimestamp());\n        assertEquals(srcRecording.startPosition(), dstRecording.startPosition());\n        assertEquals(srcRecording.stopPosition(), dstRecording.stopPosition());\n        assertEquals(srcRecording.initialTermId(), dstRecording.initialTermId());\n        assertEquals(srcRecording.segmentFileLength(), dstRecording.segmentFileLength());\n        assertEquals(srcRecording.termBufferLength(), dstRecording.termBufferLength());\n        assertEquals(srcRecording.mtuLength(), dstRecording.mtuLength());\n        assertEquals(srcRecording.sessionId(), dstRecording.sessionId());\n        assertEquals(srcRecording.streamId(), dstRecording.streamId());\n        assertEquals(srcRecording.strippedChannel(), dstRecording.strippedChannel());\n        assertEquals(srcRecording.originalChannel(), dstRecording.originalChannel());\n        assertEquals(srcRecording.sourceIdentity(), dstRecording.sourceIdentity());\n        assertEquals(dstRecording.controlSessionId(), dstRecording.controlSessionId());\n    }\n}\n"
  },
  {
    "path": "aeron-samples/src/test/java/io/aeron/samples/security/SimpleAuthenticatorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.security;\n\nimport io.aeron.security.SessionProxy;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass SimpleAuthenticatorTest\n{\n    @Test\n    void shouldAuthenticate()\n    {\n        final SessionProxy mockSessionProxy = mock(SessionProxy.class);\n        final long nowMs = 1_000_000L;\n        final long sessionId = 982374;\n\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final byte[] encodedCredentials = \"user:pass\".getBytes(US_ASCII);\n\n        when(mockSessionProxy.sessionId()).thenReturn(sessionId);\n\n        final SimpleAuthenticator simpleAuthenticator = new SimpleAuthenticator.Builder()\n            .principal(encodedPrincipal, encodedCredentials)\n            .newInstance();\n\n        simpleAuthenticator.onConnectRequest(sessionId, encodedCredentials, nowMs);\n        simpleAuthenticator.onConnectedSession(mockSessionProxy, nowMs);\n\n        verify(mockSessionProxy).authenticate(encodedPrincipal);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"user:wrong\", \"wrong:pass\"})\n    void shouldReject(final String incorrectCredentials)\n    {\n        final SessionProxy mockSessionProxy = mock(SessionProxy.class);\n        final long nowMs = 1_000_000L;\n        final long sessionId = 982374;\n\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final byte[] encodedCredentials = \"user:pass\".getBytes(US_ASCII);\n\n        when(mockSessionProxy.sessionId()).thenReturn(sessionId);\n\n        final SimpleAuthenticator simpleAuthenticator = new SimpleAuthenticator.Builder()\n            .principal(encodedPrincipal, encodedCredentials)\n            .newInstance();\n\n        simpleAuthenticator.onConnectRequest(sessionId, incorrectCredentials.getBytes(US_ASCII), nowMs);\n        simpleAuthenticator.onConnectedSession(mockSessionProxy, nowMs);\n        verify(mockSessionProxy).reject();\n    }\n\n    @Test\n    void shouldHandleMultipleConcurrentAuthenticationRequests()\n    {\n        final long nowMs = 9283479L;\n        final String[][] users = {\n            { \"user1\", \"user1:pass1\" },\n            { \"user2\", \"user2:pass2\" },\n            { \"user3\", \"user3:pass3\" },\n            { \"user4\", \"user4:pass4\" },\n            { \"user5\", \"user5:pass5\" },\n        };\n        final SessionProxy mockSessionProxy = mock(SessionProxy.class);\n\n        final SimpleAuthenticator.Builder builder = new SimpleAuthenticator.Builder();\n\n        for (final String[] user : users)\n        {\n            builder.principal(user[0].getBytes(US_ASCII), user[1].getBytes(US_ASCII));\n        }\n\n        final SimpleAuthenticator simpleAuthenticator = builder.newInstance();\n\n        for (int i = 0; i < users.length; i++)\n        {\n            simpleAuthenticator.onConnectRequest(i + 1000, users[i][1].getBytes(US_ASCII), nowMs);\n        }\n\n        for (int i = 0; i < users.length; i++)\n        {\n            when(mockSessionProxy.sessionId()).thenReturn(i + 1000L);\n            simpleAuthenticator.onConnectedSession(mockSessionProxy, nowMs);\n            verify(mockSessionProxy).authenticate(users[i][0].getBytes(US_ASCII));\n        }\n    }\n}"
  },
  {
    "path": "aeron-samples/src/test/java/io/aeron/samples/security/SimpleAuthorisationServiceTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.samples.security;\n\nimport io.aeron.archive.codecs.MessageHeaderDecoder;\nimport io.aeron.archive.codecs.ReplicateRequest2Decoder;\nimport io.aeron.archive.codecs.StartRecordingRequestDecoder;\nimport io.aeron.archive.codecs.TruncateRecordingRequestDecoder;\nimport io.aeron.cluster.codecs.BackupQueryDecoder;\nimport io.aeron.security.AuthorisationService;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static io.aeron.security.AuthorisationService.ALLOW_ALL;\nimport static io.aeron.security.AuthorisationService.DENY_ALL;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@SuppressWarnings(\"checkstyle:Indentation\")\nclass SimpleAuthorisationServiceTest\n{\n    private static final int ARCHIVE_PROTOCOL_ID = MessageHeaderDecoder.SCHEMA_ID;\n    private static final int CLUSTER_PROTOCOL_ID = io.aeron.cluster.codecs.MessageHeaderDecoder.SCHEMA_ID;\n    private static final int OTHER_PROTOCOL_ID = 873648576;\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableGeneralAuthorisationForProtocol(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, true)\n            .addGeneralRule(CLUSTER_PROTOCOL_ID, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            OTHER_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, null, encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableGeneralAuthorisationForProtocolAndMessage(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, true)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, null, encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableGeneralAuthorisationForProtocolAndMessageAndType(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n        final String typeAllowed = \"allowed\";\n        final String typeDenied = \"denied\";\n        final String typeUnspecified = \"unspecified\";\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, true)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeDenied, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeDenied, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeUnspecified, encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableUserSpecificAuthorisationForProtocol(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addPrincipalRule(ARCHIVE_PROTOCOL_ID, encodedPrincipal, true)\n            .addPrincipalRule(CLUSTER_PROTOCOL_ID, encodedPrincipal, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, BackupQueryDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, BackupQueryDecoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            OTHER_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            OTHER_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableUserSpecificAuthorisationForProtocolAndMessage(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addPrincipalRule(ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, encodedPrincipal, true)\n            .addPrincipalRule(ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, encodedPrincipal, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableUserSpecificAuthorisationForProtocolAndMessageAndType(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final String typeAllowed = \"allowed\";\n        final String typeDenied = \"denied\";\n        final String typeUnspecified = \"unspecified\";\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addPrincipalRule(\n                ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal, true)\n            .addPrincipalRule(\n                ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeDenied, encodedPrincipal, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeDenied, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeUnspecified, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldPrioritisePrincipalOverGeneralRules(final boolean accept)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final byte[] unknownPrincipal = \"unknownUser\".getBytes(US_ASCII);\n        final String typeAllowed = \"allowed\";\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(!accept ? ALLOW_ALL : DENY_ALL)\n            .addPrincipalRule(\n                ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal, accept)\n            .addGeneralRule(\n                ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, !accept)\n            .addPrincipalRule(\n                ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, encodedPrincipal, accept)\n            .addGeneralRule(\n                ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, !accept)\n            .addPrincipalRule(\n                CLUSTER_PROTOCOL_ID, encodedPrincipal, accept)\n            .addGeneralRule(\n                CLUSTER_PROTOCOL_ID, !accept)\n            .newInstance();\n\n        assertEquals(accept, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal));\n        assertEquals(accept, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n        assertEquals(accept, simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, BackupQueryDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(!accept, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, unknownPrincipal));\n        assertEquals(!accept, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, null, unknownPrincipal));\n        assertEquals(!accept, simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, BackupQueryDecoder.TEMPLATE_ID, null, unknownPrincipal));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/CMakeLists.txt",
    "content": "if (AERON_SYSTEM_TESTS)\n    add_test(\n        NAME java_system_tests_c_media_driver\n        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n        COMMAND ${CMAKE_COMMAND}\n                -E env\n                JAVA_HOME=${JAVA_HOME}\n                BUILD_JAVA_HOME=${BUILD_JAVA_HOME}\n                BUILD_JAVA_VERSION=${BUILD_JAVA_VERSION}\n                ${GRADLE_WRAPPER}\n                -Daeron.test.system.aeronmd.path=$<TARGET_FILE:aeronmd>\n                :aeron-system-tests:cleanTest\n                :aeron-system-tests:test\n                --no-daemon\n                --console=plain)\n    set_tests_properties(java_system_tests_c_media_driver PROPERTIES RUN_SERIAL TRUE)\nendif ()\n\nif (AERON_SLOW_SYSTEM_TESTS)\n    add_test(\n        NAME java_slow_system_tests_c_media_driver\n        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n        COMMAND ${CMAKE_COMMAND}\n                -E env\n                JAVA_HOME=${JAVA_HOME}\n                BUILD_JAVA_HOME=${BUILD_JAVA_HOME}\n                BUILD_JAVA_VERSION=${BUILD_JAVA_VERSION}\n                ${GRADLE_WRAPPER}\n                -Daeron.test.system.aeronmd.path=$<TARGET_FILE:aeronmd>\n                :aeron-system-tests:cleanSlowTest\n                :aeron-system-tests:slowTest\n                --no-daemon\n                --console=plain)\n    set_tests_properties(java_slow_system_tests_c_media_driver PROPERTIES TIMEOUT 3600)\n    set_tests_properties(java_slow_system_tests_c_media_driver PROPERTIES RUN_SERIAL TRUE)\nendif ()\n"
  },
  {
    "path": "aeron-system-tests/README.md",
    "content": "Aeron System Tests\n===\n\n[![Javadocs](http://www.javadoc.io/badge/io.aeron/aeron-all.svg)](http://www.javadoc.io/doc/io.aeron/aeron-all)\n\nSystem tests which exercise end-to-end features of Aeron that act as acceptance tests for CI."
  },
  {
    "path": "aeron-system-tests/scripts/provisioning/README.md",
    "content": "# Remote Provisioning for Bindings Test\n\nThis script use the fabric deployment tool (https://docs.fabfile.org) v2.\nIt should be run from the root directory of the project. It will:\n\n1. Build a copy of aeron-all-<version>.jar\n2. Copy the jar to a remote server\n3. Ssh to a remote server, start an instance of the remote provision service\n4. Run the RemoteEchoTest\n5. Stop the service\n\nUsage\n\n```shell\n$ fab -r aeron-system-tests/scripts/provisioning \\\n  -H <ip address to run the remote service on>\n  stop \\\n  prepare-deploy \\\n  deploy \\ \n    --java-home=<location of the java home directory on the remote server> \\\n  test \\ \n    --test-host=<optional, ip address of the host running RemoteEchoTest> \\ \n    --aeron-dir=<optional, path to an already running media driver>  \n```\n"
  },
  {
    "path": "aeron-system-tests/scripts/provisioning/fabfile.py",
    "content": "from invoke import task\nfrom invoke import run as local\nfrom fabric import Connection\nimport re\n\n@task()\ndef prepare_deploy(c):\n    local(\"./gradlew --console=verbose clean\", env = {\"JAVA_HOME\": \"/home/mike/opt/jdk/jdk8\"})\n    local(\"./gradlew --console=verbose :aeron-all:jar\", env = {\"JAVA_HOME\": \"/home/mike/opt/jdk/jdk8\"})\n\ndef parse_version(version_string):\n    g = re.search(\"version \\\"(.*)\\\\.(.*)\\\\..*\\\"\", version_string)\n\n    if (g is None):\n        raise RuntimeError(\"Unable to get version information: {}\".format(r.stdout))\n\n    if (g.group(1) == \"1\"):\n        version = int(g.group(2))\n    else:\n        version = int(g.group(1))\n\n    return version\n\n@task()\ndef version(c, java_home=None):\n    if (java_home is None):\n        raise RuntimeError(\"--java-home must be specified\")\n\n    r = c.run(\"{}/bin/java -version 2>&1\".format(java_home), hide=True)\n    print(parse_version(r.stdout.splitlines()[0]))\n\n@task()\ndef deploy(c, java_home=None, provisioning_host=None):\n    if (java_home is None):\n        raise RuntimeError(\"--java-home must be specified\")\n\n    r = c.run(\"{}/bin/java -version 2>&1\".format(java_home), hide=True)\n    java_version = parse_version(r.stdout.splitlines()[0])\n\n    if (provisioning_host is None):\n        provisioning_host = c.host\n\n    command = [\n      \"{}/bin/java\".format(java_home),\n      \"-Dcom.sun.management.jmxremote\",\n      \"-Dcom.sun.management.jmxremote.authenticate=false\",\n      \"-Dcom.sun.management.jmxremote.ssl=false\",\n      \"-Dcom.sun.management.jmxremote.port=10000\",\n      \"-Djava.rmi.server.hostname={}\".format(provisioning_host),\n      \"-cp ./provisioning/aeron-all-1.38.0-SNAPSHOT.jar\",\n      \"io.aeron.samples.echo.ProvisioningServerMain\",\n      \"< /dev/null > ./provisioning/log 2>&1\",\n      \"&\"\n    ]\n\n    if (8 < java_version):\n        command.insert(1, \"--add-opens java.base/jdk.internal.misc=ALL-UNNAMED\")\n\n    with open('version.txt') as f:\n        lines = f.readlines()\n        c.run(\"rm -rf provisioning\")\n        c.run(\"mkdir -p provisioning\")\n        c.put(\"aeron-all/build/libs/aeron-all-{}.jar\".format(lines[0]), \"provisioning/.\")\n        c.run(\" \".join(command))\n        c.run(\"sleep 2 ; pgrep -f io.aeron.samples.echo.ProvisioningServerMain\")\n\n@task()\ndef test(c, provisioning_host=None, test_host=None, aeron_dir=None):\n    if (provisioning_host is None):\n        provisioning_host = c.host\n\n    command = [\n        \"./gradlew\",\n        \"-Daeron.test.system.binding.remote.host={}\".format(provisioning_host),\n        \"--console=verbose\",\n        \":aeron-system-test:test\",\n        \"--tests\",\n        \"'*RemoteEchoTest'\",\n    ]\n\n    if (not test_host is None):\n        command.insert(1, \"-Daeron.test.system.binding.local.host={}\".format(test_host))\n\n    if (not aeron_dir is None):\n        command.insert(1, \"-Daeron.test.system.aeron.dir={}\".format(aeron_dir))\n\n    local(\" \".join(command))\n\n@task()\ndef stop(c):\n    c.run(\"pkill -f io.aeron.samples.echo.ProvisioningServerMain\", warn=True)\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/AsyncResourceTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.test.*;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.MutableReference;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mock;\n\n@SuppressWarnings(\"try\")\n@ExtendWith(InterruptingTestCallback.class)\nclass AsyncResourceTest\n{\n    private static final int STREAM_ID = 7777;\n    private static final String AERON_IPC = \"aeron:ipc\";\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    @BeforeEach\n    void setUp()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driver = TestMediaDriver.launch(driverCtx, testWatcher);\n        testWatcher.dataCollector().add(driverCtx.aeronDirectory());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.close(driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAddAsyncPublications()\n    {\n        final Aeron.Context clientCtx = new Aeron.Context()\n            .useConductorAgentInvoker(true)\n            .errorHandler(Tests::onError);\n\n        try (Aeron aeron = Aeron.connect(clientCtx))\n        {\n            final long registrationIdOne = aeron.asyncAddPublication(AERON_IPC, STREAM_ID);\n            final long registrationIdTwo = aeron.asyncAddExclusivePublication(AERON_IPC, STREAM_ID);\n\n            ExclusivePublication publicationTwo;\n            while (null == (publicationTwo = aeron.getExclusivePublication(registrationIdTwo)))\n            {\n                Tests.yield();\n            }\n\n            ConcurrentPublication publicationOne;\n            while (null == (publicationOne = aeron.getPublication(registrationIdOne)))\n            {\n                Tests.yield();\n            }\n\n            assertFalse(aeron.isCommandActive(registrationIdOne));\n            assertFalse(aeron.isCommandActive(registrationIdTwo));\n            assertFalse(aeron.hasActiveCommands());\n\n            assertNotNull(publicationOne);\n            assertNotNull(publicationTwo);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAsyncRemovePublication()\n    {\n        final Aeron.Context clientCtx = new Aeron.Context()\n            .errorHandler(Tests::onError);\n\n        try (Aeron aeron = Aeron.connect(clientCtx))\n        {\n            final long registrationId = aeron.asyncAddPublication(AERON_IPC, STREAM_ID);\n\n            Publication publication;\n            while (null == (publication = aeron.getPublication(registrationId)))\n            {\n                Tests.yield();\n            }\n\n            assertFalse(aeron.hasActiveCommands());\n            assertEquals(registrationId, publication.registrationId());\n\n            aeron.asyncRemovePublication(registrationId);\n            assertTrue(publication.isClosed());\n            assertNull(aeron.getPublication(registrationId));\n        }\n    }\n\n    @ParameterizedTest(name = \"{0}\")\n    @InterruptAfter(10)\n    @MethodSource(\"resourcesAddAndGet\")\n    void shouldDetectInvalidUri(final String name, final Resource resource)\n    {\n        final MutableReference<Throwable> mockClientErrorHandler = new MutableReference<>();\n        final Aeron.Context clientCtx = new Aeron.Context()\n            .errorHandler(mockClientErrorHandler::set);\n\n        try (Aeron aeron = Aeron.connect(clientCtx))\n        {\n            testWatcher.ignoreErrorsMatching(\n                (s) -> s.contains(\"Aeron URIs must start with\") || s.contains(\"invalid channel\"));\n\n            final long registrationId = resource.add(aeron, \"invalid\" + AERON_IPC, STREAM_ID);\n\n            try\n            {\n                while (null == resource.get(aeron, registrationId))\n                {\n                    Tests.yield();\n                }\n\n                fail(\"RegistrationException not thrown\");\n            }\n            catch (final RegistrationException ignore)\n            {\n                // Expected\n            }\n\n            assertFalse(aeron.isCommandActive(registrationId));\n            assertFalse(aeron.hasActiveCommands());\n        }\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(60)\n    void shouldDetectUnknownHost()\n    {\n        final ErrorHandler mockClientErrorHandler = mock(ErrorHandler.class);\n        final Aeron.Context clientCtx = new Aeron.Context()\n            .errorHandler(mockClientErrorHandler);\n\n        try (Aeron aeron = Aeron.connect(clientCtx))\n        {\n            testWatcher.ignoreErrorsMatching(\n                (s) -> s.contains(\"unresolved\") || s.contains(\"unknown host\"));\n\n            final long registrationId = aeron.asyncAddPublication(\"aeron:udp?endpoint=wibble:1234\", STREAM_ID);\n\n            try\n            {\n                while (null == aeron.getPublication(registrationId))\n                {\n                    Tests.yield();\n                }\n\n                fail(\"RegistrationException not thrown\");\n            }\n            catch (final RegistrationException ignore)\n            {\n                // Expected\n            }\n\n            assertFalse(aeron.isCommandActive(registrationId));\n            assertFalse(aeron.hasActiveCommands());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAddAsyncSubscriptions()\n    {\n        final Aeron.Context clientCtx = new Aeron.Context()\n            .errorHandler(Tests::onError);\n\n        try (Aeron aeron = Aeron.connect(clientCtx))\n        {\n            final AvailableImageHandler availableImageHandler = image -> {};\n            final UnavailableImageHandler unavailableImageHandler = image -> {};\n\n            final long registrationIdOne = aeron.asyncAddSubscription(\n                AERON_IPC, STREAM_ID, availableImageHandler, unavailableImageHandler);\n            final long registrationIdTwo = aeron.asyncAddSubscription(AERON_IPC, STREAM_ID);\n\n            Subscription subscriptionOne;\n            while (null == (subscriptionOne = aeron.getSubscription(registrationIdOne)))\n            {\n                Tests.yield();\n            }\n\n            Subscription subscriptionTwo;\n            while (null == (subscriptionTwo = aeron.getSubscription(registrationIdTwo)))\n            {\n                Tests.yield();\n            }\n\n            assertFalse(aeron.isCommandActive(registrationIdOne));\n            assertFalse(aeron.isCommandActive(registrationIdTwo));\n            assertFalse(aeron.hasActiveCommands());\n\n            assertNotNull(subscriptionOne);\n            assertNotNull(subscriptionTwo);\n        }\n    }\n\n    interface Resource\n    {\n        long add(Aeron aeron, String channel, int streamId);\n\n        Object get(Aeron aeron, long registrationId);\n    }\n\n    private static Stream<Arguments> resourcesAddAndGet()\n    {\n        return Stream.of(\n            Arguments.of(\n                \"asyncAddPublication\",\n                new Resource()\n                {\n                    public long add(final Aeron aeron, final String channel, final int streamId)\n                    {\n                        return aeron.asyncAddPublication(channel, streamId);\n                    }\n\n                    public Object get(final Aeron aeron, final long registrationId)\n                    {\n                        return aeron.getPublication(registrationId);\n                    }\n                }),\n            Arguments.of(\n                \"asyncAddExclusivePublication\",\n                new Resource()\n                {\n                    public long add(final Aeron aeron, final String channel, final int streamId)\n                    {\n                        return aeron.asyncAddExclusivePublication(channel, streamId);\n                    }\n\n                    public Object get(final Aeron aeron, final long registrationId)\n                    {\n                        return aeron.getExclusivePublication(registrationId);\n                    }\n                }),\n            Arguments.of(\n                \"asyncAddSubscription\",\n                new Resource()\n                {\n                    public long add(final Aeron aeron, final String channel, final int streamId)\n                    {\n                        return aeron.asyncAddSubscription(channel, streamId);\n                    }\n\n                    public Object get(final Aeron aeron, final long registrationId)\n                    {\n                        return aeron.getSubscription(registrationId);\n                    }\n                }));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/BufferClaimMessageTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.nio.ByteBuffer;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass BufferClaimMessageTest\n{\n    private static List<String> channels()\n    {\n        return Arrays.asList(\"aeron:udp?endpoint=localhost:24325\", CommonContext.IPC_CHANNEL);\n    }\n\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n    private static final int MESSAGE_LENGTH = 200;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n                .errorHandler(Tests::onError)\n                .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n                .threadingMode(ThreadingMode.SHARED),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldReceivePublishedMessageWithInterleavedAbort(final String channel)\n    {\n        final MutableInteger fragmentCount = new MutableInteger();\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> fragmentCount.value++;\n\n        final BufferClaim bufferClaim = new BufferClaim();\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocate(MESSAGE_LENGTH));\n\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            Publication publication = aeron.addPublication(channel, STREAM_ID))\n        {\n            publishMessage(srcBuffer, publication);\n\n            while (publication.tryClaim(MESSAGE_LENGTH, bufferClaim) < 0L)\n            {\n                Tests.yield();\n            }\n\n            publishMessage(srcBuffer, publication);\n\n            bufferClaim.abort();\n\n            final int expectedNumberOfFragments = 2;\n            int numFragments = 0;\n            do\n            {\n                final int fragments = subscription.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n                if (0 == fragments)\n                {\n                    Tests.yield();\n                }\n\n                numFragments += fragments;\n            }\n            while (numFragments < expectedNumberOfFragments);\n\n            assertEquals(expectedNumberOfFragments, fragmentCount.value);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldTransferReservedValue(final String channel)\n    {\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            Publication publication = aeron.addPublication(channel, STREAM_ID))\n        {\n            final BufferClaim bufferClaim = new BufferClaim();\n            while (publication.tryClaim(MESSAGE_LENGTH, bufferClaim) < 0L)\n            {\n                Tests.yield();\n            }\n\n            final long reservedValue = System.currentTimeMillis();\n            bufferClaim.reservedValue(reservedValue);\n            bufferClaim.commit();\n\n            final MutableBoolean done = new MutableBoolean();\n            final FragmentHandler fragmentHandler =\n                (buffer, offset, length, header) ->\n                {\n                    assertEquals(MESSAGE_LENGTH, length);\n                    assertEquals(reservedValue, header.reservedValue());\n                    done.value = true;\n                };\n\n            while (!done.get())\n            {\n                final int fragments = subscription.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n                if (0 == fragments)\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n\n    private static void publishMessage(final UnsafeBuffer srcBuffer, final Publication publication)\n    {\n        while (publication.offer(srcBuffer, 0, MESSAGE_LENGTH) < 0L)\n        {\n            Tests.yield();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/BusySocketTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.anyOf;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass BusySocketTest\n{\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n    private TestMediaDriver driver1, driver2;\n\n    @BeforeEach\n    void setup()\n    {\n        driver1 = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED), testWatcher);\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(driver1, driver2);\n    }\n\n    @InterruptAfter(20)\n    @ParameterizedTest\n    @MethodSource(\"subscriptionTests\")\n    void subscriptionShouldConnectToASocketOnceItIsFree(\n        final ThreadingMode threadingMode, final String pubChannel, final String subChannel)\n    {\n        driver2 = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .dirDeleteOnShutdown(true)\n            .threadingMode(threadingMode), testWatcher);\n\n        final int streamId = 10001;\n\n        try (Aeron aeron1 = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron aeron2 = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName())))\n        {\n            final ExclusivePublication publication = aeron2.addExclusivePublication(pubChannel, streamId);\n            final Subscription subscription = aeron1.addSubscription(subChannel, streamId);\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(publication);\n\n            final int channelStatusId = subscription.channelStatusId();\n\n            for (int i = 0; i < 5; i++)\n            {\n                try\n                {\n                    if (0 == (i & 1))\n                    {\n                        aeron2.addSubscription(subChannel, streamId);\n                    }\n                    else\n                    {\n                        final long registrationId = aeron2.asyncAddSubscription(subChannel, streamId);\n                        while (null == aeron2.getSubscription(registrationId))\n                        {\n                            Tests.yield();\n                        }\n                    }\n                    fail(\"Subscription should not be created\");\n                }\n                catch (final RegistrationException ex)\n                {\n                    assertAddressInUseException(subChannel, ex);\n                }\n            }\n\n            subscription.close();\n\n            Tests.await(\n                () -> CountersReader.RECORD_RECLAIMED == aeron1.countersReader().getCounterState(channelStatusId));\n\n            final Subscription newSubscription = aeron2.addSubscription(subChannel, streamId);\n            Tests.awaitConnected(newSubscription);\n        }\n    }\n\n    @InterruptAfter(20)\n    @ParameterizedTest\n    @EnumSource(value = ThreadingMode.class, names = \"INVOKER\", mode = EnumSource.Mode.EXCLUDE)\n    void mdsSubscriptionShouldConnectToASocketOnceItIsFree(final ThreadingMode threadingMode)\n    {\n        driver2 = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .dirDeleteOnShutdown(true)\n            .threadingMode(threadingMode), testWatcher);\n\n        final int streamId = 10001;\n        final String destination1 = \"aeron:udp?endpoint=localhost:8989\";\n        final String destination2 = \"aeron:udp?endpoint=localhost:9898\";\n        final String mdsChannel = \"aeron:udp?control-mode=manual\";\n\n        try (Aeron aeron1 = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron aeron2 = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName())))\n        {\n            final ExclusivePublication publication1 =\n                aeron1.addExclusivePublication(destination1 + \"|term-length=64k\", streamId);\n            final ExclusivePublication publication2 =\n                aeron2.addExclusivePublication(destination2 + \"|term-length=64k\", streamId);\n            final Subscription subscription = aeron1.addSubscription(destination1, streamId);\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(publication1);\n\n            final int channelStatusId = subscription.channelStatusId();\n\n            final Subscription mdsSubscription = aeron2.addSubscription(mdsChannel, streamId);\n            mdsSubscription.addDestination(destination2);\n            Tests.await(() -> ChannelEndpointStatus.ACTIVE == mdsSubscription.channelStatus());\n\n            for (int i = 0; i < 3; i++)\n            {\n                try\n                {\n                    mdsSubscription.addDestination(destination1);\n                    fail(\"Destination should not be added\");\n                }\n                catch (final RegistrationException ex)\n                {\n                    assertAddressInUseException(destination1, ex);\n                    assertEquals(ChannelEndpointStatus.ACTIVE, mdsSubscription.channelStatus());\n                }\n            }\n\n            subscription.close();\n\n            Tests.await(\n                () -> CountersReader.RECORD_RECLAIMED == aeron1.countersReader().getCounterState(channelStatusId));\n\n            mdsSubscription.addDestination(destination1);\n            Tests.await(() -> mdsSubscription.imageCount() == 2);\n            Tests.awaitConnected(publication2);\n            Tests.awaitConnected(publication1);\n            assertEquals(ChannelEndpointStatus.ACTIVE, mdsSubscription.channelStatus());\n            assertThat(\n                mdsSubscription.localSocketAddresses(),\n                containsInAnyOrder(containsString(\":9898\"), containsString(\":8989\")));\n        }\n    }\n\n    @InterruptAfter(20)\n    @ParameterizedTest\n    @EnumSource(value = ThreadingMode.class, names = \"INVOKER\", mode = EnumSource.Mode.EXCLUDE)\n    void mdcPublicationShouldConnectToASocketOnceItIsFree(final ThreadingMode threadingMode)\n    {\n        driver2 = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .dirDeleteOnShutdown(true)\n            .threadingMode(threadingMode), testWatcher);\n\n\n        try (Aeron aeron1 = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron aeron2 = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName())))\n        {\n            final int streamId = 10001;\n            final String channel = \"aeron:udp?endpoint=localhost:8989\";\n            final ExclusivePublication publication =\n                aeron1.addExclusivePublication(channel + \"|term-length=64k\", streamId);\n            final Subscription subscription = aeron1.addSubscription(channel, streamId);\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(publication);\n\n            final int channelStatusId = subscription.channelStatusId();\n\n            final int mdcStreamId = 20002;\n            final String mdcChannel = \"aeron:udp?control=localhost:8989|control-mode=dynamic|term-length=64k\";\n            for (int i = 0; i < 5; i++)\n            {\n                try\n                {\n                    aeron2.addExclusivePublication(mdcChannel, mdcStreamId);\n                    fail(\"Publication should not be added\");\n                }\n                catch (final RegistrationException ex)\n                {\n                    assertAddressInUseException(mdcChannel, ex);\n                }\n            }\n\n            subscription.close();\n\n            Tests.await(\n                () -> CountersReader.RECORD_RECLAIMED == aeron1.countersReader().getCounterState(channelStatusId));\n\n            final ExclusivePublication mdcPublication = aeron2.addExclusivePublication(mdcChannel, mdcStreamId);\n            final Subscription mdcSubscription =\n                aeron1.addSubscription(\"aeron:udp?control=localhost:8989\", mdcStreamId);\n            Tests.awaitConnected(mdcSubscription);\n            Tests.awaitConnected(mdcPublication);\n        }\n    }\n\n    @InterruptAfter(20)\n    @Test\n    void publicationShouldConnectToASocketOnceItIsFree()\n    {\n        driver2 = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.DEDICATED), testWatcher);\n\n        final int streamId = 10001;\n        final String interfaceEndpoint = \"interface=localhost:9090\";\n        final String channel = \"aeron:udp?term-length=64k\";\n\n        try (Aeron aeron1 = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron aeron2 = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName())))\n        {\n            final ExclusivePublication publication = aeron1.addExclusivePublication(\n                channel + \"|endpoint=localhost:8888|\" + interfaceEndpoint, streamId);\n\n            final int channelStatusId = publication.channelStatusId();\n            Tests.await(\n                () -> CountersReader.RECORD_ALLOCATED == aeron1.countersReader().getCounterState(channelStatusId));\n\n            final String conflictingChannel = channel + \"|endpoint=localhost:7777|\" + interfaceEndpoint;\n            for (int i = 0; i < 5; i++)\n            {\n                try\n                {\n                    aeron2.addExclusivePublication(conflictingChannel, streamId);\n                    fail(\"Publication should not be added\");\n                }\n                catch (final RegistrationException ex)\n                {\n                    assertAddressInUseException(conflictingChannel, ex);\n                }\n            }\n\n            publication.close();\n\n            Tests.await(\n                () -> CountersReader.RECORD_RECLAIMED == aeron1.countersReader().getCounterState(channelStatusId));\n\n            final ExclusivePublication newPublication = aeron2.addExclusivePublication(conflictingChannel, streamId);\n            Tests.await(() ->\n                CountersReader.RECORD_ALLOCATED ==\n                aeron2.countersReader().getCounterState(newPublication.channelStatusId()));\n        }\n    }\n\n    private static List<Arguments> subscriptionTests()\n    {\n        final ArrayList<Arguments> arguments = new ArrayList<>();\n        final String[] pubChannels = {\n            \"aeron:udp?term-length=64k|endpoint=localhost:8191\",\n            \"aeron:udp?term-length=64k|control=localhost:7779|control-mode=dynamic\" };\n        final String[] subChannels = {\n            \"aeron:udp?endpoint=localhost:8191\",\n            \"aeron:udp?endpoint=0.0.0.0:8191|control=localhost:7779\" };\n        for (final ThreadingMode threadingMode : ThreadingMode.values())\n        {\n            if (ThreadingMode.INVOKER != threadingMode)\n            {\n                for (int i = 0; i < pubChannels.length; i++)\n                {\n                    arguments.add(Arguments.of(threadingMode, pubChannels[i], subChannels[i]));\n                }\n            }\n        }\n        return arguments;\n    }\n\n    private static void assertAddressInUseException(final String subChannel, final RegistrationException ex)\n    {\n        assertEquals(ErrorCode.GENERIC_ERROR, ex.errorCode());\n        assertThat(ex.getMessage(),\n            allOf(containsString(subChannel),\n            anyOf(containsString(\"Address already in use\"),\n            containsString(\"Address in use\"),\n            containsString(\"failed to bind to address\"))));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ChannelEndpointStatusTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.exceptions.InvalidChannelException;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.File;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.is;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith(InterruptingTestCallback.class)\n@InterruptAfter(20)\nclass ChannelEndpointStatusTest\n{\n    private static final String URI = \"aeron:udp?endpoint=localhost:23456\";\n    private static final String URI_NO_CONFLICT = \"aeron:udp?endpoint=localhost:23457\";\n    private static final String URI_WITH_INTERFACE_PORT =\n        \"aeron:udp?endpoint=localhost:23456|interface=localhost:24567\";\n\n    private static final int STREAM_ID = 1001;\n    private static final ThreadingMode THREADING_MODE = ThreadingMode.DEDICATED;\n\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int NUM_MESSAGES_PER_TERM = 64;\n    private static final int MESSAGE_LENGTH =\n        (TERM_BUFFER_LENGTH / NUM_MESSAGES_PER_TERM) - DataHeaderFlyweight.HEADER_LENGTH;\n\n    private Aeron clientA;\n    private Aeron clientB;\n    private Aeron clientC;\n    private TestMediaDriver driverA;\n    private TestMediaDriver driverB;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n\n    private final ErrorHandler errorHandlerClientA = mock(ErrorHandler.class);\n    private final ErrorHandler errorHandlerClientB = mock(ErrorHandler.class);\n    private final ErrorHandler errorHandlerClientC = mock(ErrorHandler.class);\n\n    private final AtomicReference<Throwable> testException = new AtomicReference<>();\n    private final ErrorHandler driverErrorHandler =\n        (ex) ->\n        {\n            if (ex instanceof AeronException && ex.getMessage().contains(\"Address already in use\"))\n            {\n                return;\n            }\n            else if (ex instanceof InvalidChannelException)\n            {\n                return;\n            }\n\n            testException.set(ex);\n        };\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before(@TempDir final File tempDir)\n    {\n        final String baseDirA = new File(tempDir, \"A\").getAbsolutePath();\n        final String baseDirB = new File(tempDir, \"B\").getAbsolutePath();\n\n        buffer.putInt(0, 1);\n\n        final MediaDriver.Context driverAContext = new MediaDriver.Context()\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirA)\n            .errorHandler(driverErrorHandler)\n            .threadingMode(THREADING_MODE);\n\n        final MediaDriver.Context driverBContext = new MediaDriver.Context()\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirB)\n            .errorHandler(driverErrorHandler)\n            .threadingMode(THREADING_MODE);\n\n        driverA = TestMediaDriver.launch(driverAContext, testWatcher);\n        testWatcher.dataCollector().add(driverA.context().aeronDirectory());\n        driverB = TestMediaDriver.launch(driverBContext, testWatcher);\n        testWatcher.dataCollector().add(driverB.context().aeronDirectory());\n\n        testWatcher.ignoreErrorsMatching((s) -> true);\n\n        clientA = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(driverAContext.aeronDirectoryName())\n                .errorHandler(errorHandlerClientA));\n\n        clientB = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(driverBContext.aeronDirectoryName())\n                .errorHandler(errorHandlerClientB));\n\n        clientC = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(driverBContext.aeronDirectoryName())\n                .errorHandler(errorHandlerClientC));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(clientC, clientB, clientA, driverB, driverA);\n    }\n\n    @Test\n    void shouldErrorBadUri()\n    {\n        assertThrows(RegistrationException.class, () -> clientA.addSubscription(\"bad uri\", STREAM_ID));\n    }\n\n    @Test\n    void shouldBeAbleToQueryChannelStatusForSubscription()\n    {\n        final Subscription subscription = clientA.addSubscription(URI, STREAM_ID);\n\n        while (subscription.channelStatus() == ChannelEndpointStatus.INITIALIZING)\n        {\n            Tests.yield();\n        }\n\n        assertThat(subscription.channelStatus(), is(ChannelEndpointStatus.ACTIVE));\n        assertNull(testException.get());\n    }\n\n    @Test\n    void shouldBeAbleToQueryChannelStatusForPublication()\n    {\n        final Publication publication = clientA.addPublication(URI, STREAM_ID);\n\n        while (publication.channelStatus() == ChannelEndpointStatus.INITIALIZING)\n        {\n            Tests.yield();\n        }\n\n        assertThat(publication.channelStatus(), is(ChannelEndpointStatus.ACTIVE));\n        assertNull(testException.get());\n    }\n\n    @Test\n    void shouldCatchErrorOnAddressAlreadyInUseForPublications()\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"C Driver raises error on conductor\");\n        final Publication publicationA = clientA.addPublication(URI_WITH_INTERFACE_PORT, STREAM_ID);\n\n        while (publicationA.channelStatus() == ChannelEndpointStatus.INITIALIZING)\n        {\n            Tests.yield();\n        }\n\n        assertThat(publicationA.channelStatus(), is(ChannelEndpointStatus.ACTIVE));\n\n        try\n        {\n            clientB.addPublication(URI_WITH_INTERFACE_PORT, STREAM_ID);\n            fail(\"Should have thrown an exception\");\n        }\n        catch (final RegistrationException ex)\n        {\n            assertThat(ex.getMessage(), containsString(URI_WITH_INTERFACE_PORT));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ChannelInterfaceTest.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.net.SocketException;\nimport java.net.UnknownHostException;\nimport java.util.Enumeration;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.endsWith;\nimport static org.hamcrest.Matchers.oneOf;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ChannelInterfaceTest\n{\n    private static final NetworkInterface LOOPBACK_INTERFACE = findLoopbackInterface();\n    private static final int STREAM_ID = 1001;\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private Aeron publishingClient;\n    private Aeron subscribingClient;\n    private TestMediaDriver driver;\n\n    @BeforeEach\n    void setUp()\n    {\n        final MediaDriver.Context driverContext = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH);\n\n        driver = TestMediaDriver.launch(driverContext, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n\n        subscribingClient = Aeron.connect();\n        publishingClient = Aeron.connect();\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(publishingClient, subscribingClient, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAcceptInterfaceNameInUnicastChannels()\n    {\n        assumeNotNativeDriverOnWindows();\n\n        final String subChannel = \"aeron:udp?endpoint=127.0.0.1:24325\";\n        final String pubChannel = subChannel + \"|interface={\" + LOOPBACK_INTERFACE.getName() + \"}:24324\";\n\n        final Subscription subscription = subscribingClient.addSubscription(subChannel, STREAM_ID);\n        final Publication publication = publishingClient.addExclusivePublication(pubChannel, STREAM_ID);\n\n        passMessage(subscription, publication);\n\n        final String pubSocketAddress = publication.localSocketAddresses().get(0);\n        assertThat(pubSocketAddress, endsWith(\":24324\"));\n        final String address = pubSocketAddress.substring(0, pubSocketAddress.indexOf(':'));\n        final String[] targetAddresses =\n            LOOPBACK_INTERFACE.inetAddresses().map(InetAddress::getHostAddress).toArray(String[]::new);\n        assertThat(address, oneOf(targetAddresses));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAcceptInterfaceNameInMulticastChannels()\n    {\n        assumeNotNativeDriverOnWindows();\n\n        final String channel =\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface={\" + LOOPBACK_INTERFACE.getName() + \"}|alias=foo\";\n\n        final Subscription subscription = subscribingClient.addSubscription(channel, STREAM_ID);\n        final Publication publication = publishingClient.addExclusivePublication(channel, STREAM_ID);\n\n        passMessage(subscription, publication);\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(10)\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=localhost:24325|interface={does_not_exist}:24326\",\n        \"aeron:udp?endpoint=224.20.30.39:24325|interface={does_not_exist}\",\n    })\n    void shouldThrowIfSpecifiedInterfaceDoesNotExist(final String channel)\n    {\n        final String expectedError = \"unknown interface does_not_exist\";\n        watcher.ignoreErrorsMatching((s) -> s.contains(expectedError));\n\n        final RegistrationException ex = assertThrows(RegistrationException.class,\n            () -> publishingClient.addPublication(channel, STREAM_ID));\n\n        assertThat(ex.getMessage(), containsString(expectedError));\n    }\n\n    private void passMessage(final Subscription subscription, final Publication publication)\n    {\n        final long payload = ThreadLocalRandom.current().nextLong();\n\n        final MutableDirectBuffer buffer = new UnsafeBuffer(new byte[SIZE_OF_LONG]);\n        buffer.putLong(0, payload);\n\n        while (0 > publication.offer(buffer))\n        {\n            Tests.yield();\n        }\n\n        final FragmentHandler fragmentHandler = (buf, off, len, hdr) ->\n            assertEquals(payload, buf.getLong(off));\n        while (0 == subscription.poll(fragmentHandler, 1))\n        {\n            Tests.yield();\n        }\n    }\n\n    private static NetworkInterface findLoopbackInterface()\n    {\n        try\n        {\n            final InetAddress home = InetAddress.getByName(\"127.0.0.1\");\n            final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();\n            while (networkInterfaces.hasMoreElements())\n            {\n                final NetworkInterface networkInterface = networkInterfaces.nextElement();\n                if (networkInterface.isLoopback() && networkInterface.inetAddresses().anyMatch(ia -> ia.equals(home)))\n                {\n                    return networkInterface;\n                }\n            }\n        }\n        catch (final UnknownHostException | SocketException e)\n        {\n            throw new RuntimeException(\"failed to find loopback interface\", e);\n        }\n\n        throw new RuntimeException(\"failed to find loopback interface\");\n    }\n\n    private static void assumeNotNativeDriverOnWindows()\n    {\n        assumeFalse(SystemUtil.isWindows() && TestMediaDriver.shouldRunCMediaDriver(),\n            \"we don't have access to the interface names used by the native MD on Windows\");\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ChannelValidationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.Configuration;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.hamcrest.Matcher;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.IOException;\nimport java.net.StandardProtocolFamily;\nimport java.net.StandardSocketOptions;\nimport java.nio.channels.DatagramChannel;\nimport java.util.ArrayList;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.hamcrest.CoreMatchers.allOf;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ChannelValidationTest\n{\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .errorHandler((ignore) -> {})\n        .dirDeleteOnStart(true)\n        .threadingMode(ThreadingMode.SHARED)\n        .publicationConnectionTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500))\n        .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100));\n\n    private final ArrayList<AutoCloseable> closeables = new ArrayList<>();\n\n    private Aeron aeron;\n    private TestMediaDriver driver;\n\n    private void launch()\n    {\n        driver = TestMediaDriver.launch(context, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n        watcher.ignoreErrorsMatching((s) -> true);\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.quietCloseAll(closeables);\n        CloseHelper.quietClose(aeron);\n        CloseHelper.quietClose(driver);\n    }\n\n    @Test\n    void publicationCantUseDifferentSoSndbufIfAlreadySetViaUri()\n    {\n        launch();\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=131072\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=65536\", 1001));\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=131072\", 1002);\n    }\n\n    @Test\n    void publicationCantUseDifferentSoSndbufIfAlreadySetViaContext()\n    {\n        context.socketSndbufLength(131072);\n        launch();\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=65536\", 1001));\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=131072\", 1002);\n    }\n\n    @Test\n    void publicationCantUseDifferentSoSndbufIfAlreadySetViaDefault()\n    {\n        context.socketRcvbufLength(131072);\n        launch();\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=65536\", 1001));\n    }\n\n\n    @Test\n    void publicationCantUseDifferentSoRcvbufIfAlreadySetViaUri()\n    {\n        launch();\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=131072\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=65536\", 1001));\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=131072\", 1002);\n    }\n\n    @Test\n    void publicationCantUseDifferentSoRcvbufIfAlreadySetViaContext()\n    {\n        context.socketRcvbufLength(131072);\n        launch();\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=65536\", 1001));\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=131072\", 1002);\n    }\n\n    @Test\n    void publicationCantUseDifferentSoRcvbufIfAlreadySetViaDefault()\n    {\n        context.socketRcvbufLength(131072);\n        launch();\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=65536\", 1001));\n    }\n\n    @Test\n    void subscriptionCantUseDifferentSoSndbufIfAlreadySetViaUri()\n    {\n        launch();\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=131072\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addSubscription(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=65536\", 1001));\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=131072\", 1002);\n    }\n\n    @Test\n    void subscriptionCantUseDifferentSoSndbufIfAlreadySetViaContext()\n    {\n        context.socketSndbufLength(131072);\n        launch();\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addSubscription(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=65536\", 1001));\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=131072\", 1002);\n    }\n\n    @Test\n    void subscriptionCantUseDifferentSoSndbufIfAlreadySetViaDefault()\n    {\n        context.socketRcvbufLength(131072);\n        launch();\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addSubscription(\"aeron:udp?endpoint=localhost:9999|so-sndbuf=65536\", 1001));\n    }\n\n\n    @Test\n    void subscriptionCantUseDifferentSoRcvbufIfAlreadySetViaUri()\n    {\n        launch();\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=131072\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addSubscription(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=65536\", 1001));\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=131072\", 1002);\n    }\n\n    @Test\n    void subscriptionCantUseDifferentSoRcvbufIfAlreadySetViaContext()\n    {\n        context.socketRcvbufLength(131072);\n        launch();\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addSubscription(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=65536\", 1001));\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=131072\", 1002);\n    }\n\n    @Test\n    void subscriptionCantUseDifferentSoRcvbufIfAlreadySetViaDefault()\n    {\n        context.socketRcvbufLength(131072);\n        launch();\n\n        addSubscription(\"aeron:udp?endpoint=localhost:9999\", 1000);\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addSubscription(\"aeron:udp?endpoint=localhost:9999|so-rcvbuf=65536\", 1001));\n    }\n\n    @Test\n    void shouldValidateMtuAgainstSoSndbufSetViaUri()\n    {\n        launch();\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?endpoint=localhost:9999|mtu=1056|so-sndbuf=1024\", 1000));\n    }\n\n    @Test\n    void shouldValidateMtuAgainstSoSndbufSetViaContext()\n    {\n        context.socketSndbufLength(4096);\n        launch();\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?endpoint=localhost:9999|mtu=4128\", 1000));\n    }\n\n    @Test\n    void shouldValidateMtuAgainstSoSndbufSetViaOsDefault() throws IOException\n    {\n        final int defaultOsSocketSndbufLength;\n        try (DatagramChannel channel = DatagramChannel.open(StandardProtocolFamily.INET))\n        {\n            defaultOsSocketSndbufLength = channel.getOption(StandardSocketOptions.SO_SNDBUF);\n        }\n\n        final int desiredMaxMessageLength = 2 * defaultOsSocketSndbufLength;\n        assumeTrue(\n            desiredMaxMessageLength < FrameDescriptor.MAX_MESSAGE_LENGTH,\n            \"OS buffer sizes to big (use sudo sysctl net.core.wmem_default=8192 to verify)\");\n\n        assumeTrue(\n            defaultOsSocketSndbufLength < Configuration.MAX_UDP_PAYLOAD_LENGTH,\n            \"OS buffer sizes to big (use sudo sysctl net.core.wmem_default=8192 to verify)\");\n\n        final int termLength = BitUtil.findNextPositivePowerOfTwo(desiredMaxMessageLength * 8);\n        context.publicationTermBufferLength(termLength);\n\n        launch();\n\n        assertThrows(\n            RegistrationException.class,\n            () ->\n            {\n                final String uri = \"aeron:udp?endpoint=localhost:9999|mtu=\" + ((2 * defaultOsSocketSndbufLength) + 32);\n                addPublication(uri, 1000);\n            });\n    }\n\n    @Test\n    void shouldValidateReceiverWindowAgainstSoRcvbufSetViaUri()\n    {\n        launch();\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addSubscription(\"aeron:udp?endpoint=localhost:9999|rcv-wnd=1056|so-rcvbuf=1024\", 1000));\n    }\n\n    @Test\n    void shouldValidateReceiverWindowAgainstSoRcvbufSetViaContext()\n    {\n        context.socketRcvbufLength(4096);\n        context.initialWindowLength(4096);\n        launch();\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addSubscription(\"aeron:udp?endpoint=localhost:9999|rcv-wnd=4128\", 1000));\n    }\n\n    @Test\n    void shouldValidateReceiverAgainstSoRcvbufSetViaOsDefault() throws IOException\n    {\n        final int defaultOsSocketRcvbufLength;\n        try (DatagramChannel channel = DatagramChannel.open(StandardProtocolFamily.INET))\n        {\n            defaultOsSocketRcvbufLength = channel.getOption(StandardSocketOptions.SO_RCVBUF);\n        }\n\n        final int desiredMaxMessageLength = 2 * defaultOsSocketRcvbufLength;\n        assumeTrue(\n            desiredMaxMessageLength < FrameDescriptor.MAX_MESSAGE_LENGTH,\n            \"OS buffer sizes to big (use sudo sysctl net.core.rmem_default=8192 to verify)\");\n\n        assumeTrue(\n            defaultOsSocketRcvbufLength < Configuration.MAX_UDP_PAYLOAD_LENGTH,\n            \"OS buffer sizes to big (use sudo sysctl net.core.rmem_default=8192 to verify)\");\n\n        final int termLength = BitUtil.findNextPositivePowerOfTwo(desiredMaxMessageLength * 8);\n        context.publicationTermBufferLength(termLength);\n        context.socketRcvbufLength(0);\n        context.initialWindowLength(defaultOsSocketRcvbufLength);\n\n        launch();\n\n        assertThrows(\n            RegistrationException.class,\n            () ->\n            {\n                final int receiverWindow = (2 * defaultOsSocketRcvbufLength) + 32;\n                addSubscription(\"aeron:udp?endpoint=localhost:9999|rcv-wnd=\" + receiverWindow, 1000);\n            });\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldValidateSenderMtuAgainstUriReceiverWindow() throws IOException\n    {\n        context.errorHandler(null);\n        launch();\n\n        final long initialErrorCount = aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERRORS.id());\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999|mtu=1408\", 1000);\n        addSubscription(\"aeron:udp?endpoint=localhost:9999|rcv-wnd=1376\", 1000);\n\n        Tests.awaitCounterDelta(aeron.countersReader(), SystemCounterDescriptor.ERRORS.id(), initialErrorCount, 1);\n\n        final Matcher<String> exceptionMessageMatcher = allOf(\n            containsString(\"mtuLength=\"),\n            containsString(\"> initialWindowLength=\"));\n\n        SystemTests.waitForErrorToOccur(driver.aeronDirectoryName(), exceptionMessageMatcher, Tests.SLEEP_1_MS);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"mtu\", \"rcv-wnd\", \"so-rcvbuf\", \"so-sndbuf\" })\n    void shouldNotAllowUriParametersForManualMdc(final String parameter)\n    {\n        launch();\n\n        final Publication publication = addPublication(\"aeron:udp?control-mode=manual\", 1000);\n\n        final RegistrationException registrationException = assertThrows(\n            RegistrationException.class,\n            () -> publication.addDestination(\"aeron:udp?endpoint=localhost:9999|\" + parameter + \"=4096\"));\n\n        assertThat(registrationException.getMessage(), containsString(parameter));\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"mtu\", \"rcv-wnd\", \"so-rcvbuf\", \"so-sndbuf\" })\n    void shouldNotAllowUriParametersForManualMds(final String parameter)\n    {\n        launch();\n\n        final Subscription subscription = addSubscription(\"aeron:udp?control-mode=manual\", 1000);\n\n        final RegistrationException registrationException = assertThrows(\n            RegistrationException.class,\n            () -> subscription.addDestination(\"aeron:udp?endpoint=localhost:9999|\" + parameter + \"=4096\"));\n\n        assertThat(registrationException.getMessage(), containsString(parameter));\n    }\n\n    @Test\n    void shouldErrorOnPublicationWithWildcardEndpoint()\n    {\n        launch();\n\n        assertThrows(RegistrationException.class, () -> addPublication(\"aeron:udp?endpoint=localhost:0\", 10001));\n    }\n\n    @Test\n    void shouldErrorOnPublicationAddDestinationWithWildcardEndpoint()\n    {\n        launch();\n\n        final Publication publication = addPublication(\"aeron:udp?control-mode=manual\", 10001);\n        assertThrows(RegistrationException.class, () -> publication.addDestination(\"aeron:udp?endpoint=localhost:0\"));\n    }\n\n    @Test\n    void shouldErrorOnSubscriptionWithWildcardControl()\n    {\n        launch();\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addSubscription(\"aeron:udp?control=localhost:0|endpoint=localhost:20000\", 10001));\n    }\n\n    @Test\n    void shouldDisallowControlEndpointWithoutControlModeOrEndpoint()\n    {\n        launch();\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?control=localhost:9999\", 10001));\n    }\n\n    @Test\n    void shouldDisallowControlModeDynamicWithoutControl()\n    {\n        launch();\n\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?control-mode=dynamic\", 10001));\n    }\n\n    @Test\n    void shouldAllowDynamicControlModeWithTags()\n    {\n        launch();\n\n        addPublication(\"aeron:udp?control-mode=dynamic|control=localhost:23454|tags=200\", 10001);\n        addPublication(\"aeron:udp?control-mode=dynamic|control=localhost:23454|tags=200\", 10001);\n        addPublication(\"aeron:udp?tags=200\", 10001);\n    }\n\n    @Test\n    void shouldNotAllowNormalToControlModeDynamicChangeWithTags()\n    {\n        launch();\n\n        addPublication(\"aeron:udp?control=localhost:23454|endpoint=localhost:23455|tags=200\", 10001);\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?control-mode=dynamic|control=localhost:23454|tags=200\", 10001));\n    }\n\n    @Test\n    void shouldNotAllowDynamicToControlModelNormalChangeWithTags()\n    {\n        launch();\n\n        addPublication(\"aeron:udp?control-mode=dynamic|control=localhost:23454|tags=200\", 10001);\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?control=localhost:23454|endpoint=localhost:23455|tags=200\", 10001));\n    }\n\n    @Test\n    void shouldNotAllowDynamicToControlModelManualChangeWithTags()\n    {\n        launch();\n\n        addPublication(\"aeron:udp?control-mode=dynamic|control=localhost:23454|tags=200\", 10001);\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?control-mode=manual|control=localhost:23454|tags=200\", 10001));\n    }\n\n    @Test\n    void shouldNotAllowManualToControlModelDynamicChangeWithTags()\n    {\n        launch();\n\n        addPublication(\"aeron:udp?control-mode=manual|control=localhost:23454|tags=200\", 10001);\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?control-mode=dynamic|control=localhost:23454|tags=200\", 10001));\n    }\n\n    @Test\n    void shouldNotAllowNormalToControlModelDynamicChangeWithTags()\n    {\n        launch();\n\n        addPublication(\"aeron:udp?endpoint=localhost:23455|control=localhost:23454|tags=200\", 10001);\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?control-mode=dynamic|control=localhost:23454|tags=200\", 10001));\n    }\n\n    @Test\n    void shouldNotAllowNormalToControlModelManualChangeWithTags()\n    {\n        launch();\n\n        addPublication(\"aeron:udp?endpoint=localhost:23455|control=localhost:23454|tags=200\", 10001);\n        assertThrows(\n            RegistrationException.class,\n            () -> addPublication(\"aeron:udp?control-mode=manual|control=localhost:23454|tags=200\", 10001));\n    }\n\n    private Publication addPublication(final String channel, final int streamId)\n    {\n        final Publication pub = aeron.addPublication(channel, streamId);\n        closeables.add(pub);\n        return pub;\n    }\n\n    private Subscription addSubscription(final String channel, final int streamId)\n    {\n        final Subscription sub = aeron.addSubscription(channel, streamId);\n        closeables.add(sub);\n        return sub;\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ClientContextTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.ClientHeartbeatTimestamp;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ConcurrentConcludeException;\nimport io.aeron.status.HeartbeatTimestamp;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ClientContextTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n    private TestMediaDriver mediaDriver;\n\n    @BeforeEach\n    void before()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        mediaDriver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.close(mediaDriver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SuppressWarnings(\"try\")\n    void shouldPreventCreatingMultipleClientsWithTheSameContext()\n    {\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(mediaDriver.aeronDirectoryName());\n\n        try (Aeron ignore = Aeron.connect(ctx))\n        {\n            assertThrows(ConcurrentConcludeException.class, () -> Aeron.connect(ctx));\n        }\n    }\n\n    @Test\n    @SuppressWarnings(\"try\")\n    void shouldRequireInvokerModeIfClientLockIsSet()\n    {\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n            .clientLock(NoOpLock.INSTANCE);\n\n        assertThrows(AeronException.class, () -> Aeron.connect(ctx));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SuppressWarnings(\"try\")\n    void shouldAllowCustomLockInAgentRunnerModeIfNotInstanceOfNoOpLock()\n    {\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n            .clientLock(new ReentrantLock());\n\n        try (Aeron aeron = Aeron.connect(ctx))\n        {\n            aeron.clientId();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldHaveUniqueCorrelationIdsAcrossMultipleClientsToTheSameDriver()\n    {\n        final Aeron.Context ctx = new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName());\n\n        try (Aeron aeron0 = Aeron.connect(ctx.clone());\n            Aeron aeron1 = Aeron.connect(ctx.clone()))\n        {\n            assertNotEquals(aeron0.nextCorrelationId(), aeron1.nextCorrelationId());\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"\", \"my-test-client\" })\n    @InterruptAfter(10)\n    void shouldAddClientInfoToTheHeartbeatTimestampCounter(final String clientName)\n    {\n        try (Aeron aeron = Aeron.connect(new Aeron.Context()\n            .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n            .clientName(clientName)\n            .keepAliveIntervalNs(TimeUnit.MILLISECONDS.toNanos(10))))\n        {\n            // trigger creation of the timestamp counter on the driver side\n            assertNotNull(aeron.addCounter(1000, \"test\"));\n\n            final long clientId = aeron.clientId();\n            int counterId = Aeron.NULL_VALUE;\n            final CountersReader countersReader = aeron.countersReader();\n            final String baseLabel = ClientHeartbeatTimestamp.NAME + \": id=\" + clientId;\n            final String expandedLabel = baseLabel + \" name=\" + clientName + \" \" +\n                AeronCounters.formatVersionInfo(AeronVersion.VERSION, AeronVersion.GIT_SHA);\n            while (true)\n            {\n                if (Aeron.NULL_VALUE == counterId)\n                {\n                    counterId = HeartbeatTimestamp.findCounterIdByRegistrationId(\n                        countersReader, HeartbeatTimestamp.HEARTBEAT_TYPE_ID, clientId);\n                }\n                else\n                {\n                    final int labelLength =\n                        countersReader.metaDataBuffer().getInt(\n                        CountersReader.metaDataOffset(counterId) + CountersReader.LABEL_OFFSET);\n                    if (labelLength > baseLabel.length())\n                    {\n                        assertEquals(expandedLabel, countersReader.getCounterLabel(counterId));\n                        break;\n                    }\n                }\n                Tests.yield();\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAddClientInfoToTheHeartbeatTimestampCounterUpToMaxLabelLength()\n    {\n        final String clientName = Tests.generateStringWithSuffix(\"\", \"X\", 100);\n        try (Aeron aeron = Aeron.connect(new Aeron.Context()\n            .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n            .clientName(clientName)\n            .keepAliveIntervalNs(TimeUnit.MILLISECONDS.toNanos(10))))\n        {\n            // trigger creation of the timestamp counter on the driver side\n            assertNotNull(aeron.addCounter(1000, \"test\"));\n\n            final long clientId = aeron.clientId();\n            int counterId = Aeron.NULL_VALUE;\n            final CountersReader countersReader = aeron.countersReader();\n            final String baseLabel = ClientHeartbeatTimestamp.NAME + \": id=\" + clientId;\n            final String expandedLabel =\n                baseLabel + \" name=\" + clientName.substring(0, 100) +\n                \" version=\" + AeronVersion.VERSION + \" commit=\" + AeronVersion.GIT_SHA;\n            while (true)\n            {\n                if (Aeron.NULL_VALUE == counterId)\n                {\n                    counterId = HeartbeatTimestamp.findCounterIdByRegistrationId(\n                        countersReader, HeartbeatTimestamp.HEARTBEAT_TYPE_ID, clientId);\n                }\n                else\n                {\n                    final int labelLength =\n                        countersReader.metaDataBuffer().getInt(\n                        CountersReader.metaDataOffset(counterId) + CountersReader.LABEL_OFFSET);\n                    if (labelLength > baseLabel.length())\n                    {\n                        assertEquals(expandedLabel, countersReader.getCounterLabel(counterId));\n                        break;\n                    }\n                }\n                Tests.yield();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ClientErrorHandlerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static io.aeron.test.Tests.awaitConnected;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ClientErrorHandlerTest\n{\n    private static final int STREAM_ID = 1001;\n    private static final String CHANNEL = \"aeron:ipc\";\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(10)\n    @SuppressWarnings(\"try\")\n    void shouldHaveCorrectTermBufferLength()\n    {\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .dirDeleteOnStart(true);\n\n        final ErrorHandler mockErrorHandlerOne = mock(ErrorHandler.class);\n        final Aeron.Context clientCtxOne = new Aeron.Context().errorHandler(mockErrorHandlerOne);\n\n        final ErrorHandler mockErrorHandlerTwo = mock(ErrorHandler.class);\n        final Aeron.Context clientCtxTwo = new Aeron.Context()\n            .errorHandler(mockErrorHandlerTwo)\n            .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE);\n\n        try (TestMediaDriver ignore = TestMediaDriver.launch(ctx, testWatcher))\n        {\n            testWatcher.dataCollector().add(ctx.aeronDirectory());\n\n            try (\n                Aeron aeronOne = Aeron.connect(clientCtxOne);\n                Aeron aeronTwo = Aeron.connect(clientCtxTwo);\n                Publication publication = aeronOne.addPublication(CHANNEL, STREAM_ID);\n                Subscription subscriptionOne = aeronOne.addSubscription(CHANNEL, STREAM_ID);\n                Subscription subscriptionTwo = aeronTwo.addSubscription(CHANNEL, STREAM_ID))\n            {\n                testWatcher.dataCollector().add(ctx.aeronDirectory());\n\n                awaitConnected(subscriptionOne);\n                awaitConnected(subscriptionTwo);\n\n                assertEquals(clientCtxOne.errorHandler(), clientCtxOne.subscriberErrorHandler());\n                assertNotEquals(clientCtxTwo.errorHandler(), clientCtxTwo.subscriberErrorHandler());\n\n                final UnsafeBuffer buffer = new UnsafeBuffer(new byte[100]);\n                while (publication.offer(buffer) < 0)\n                {\n                    Tests.yield();\n                }\n\n                final RuntimeException expectedException = new RuntimeException(\"Expected\");\n                final FragmentHandler handler =\n                    (buffer1, offset, length, header) ->\n                    {\n                        throw expectedException;\n                    };\n\n                while (0 == subscriptionOne.poll(handler, 1))\n                {\n                    Tests.yield();\n                }\n\n                verify(mockErrorHandlerOne).onError(expectedException);\n\n                try\n                {\n                    while (0 == subscriptionTwo.poll(handler, 1))\n                    {\n                        Tests.yield();\n                    }\n\n                    fail(\"Expected exception\");\n                }\n                catch (final Exception ex)\n                {\n                    assertEquals(expectedException, ex);\n                }\n\n                verify(mockErrorHandlerTwo, never()).onError(any());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ConcurrentPublicationTermRotationRaceTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.collections.LongHashSet;\nimport org.agrona.collections.MutableInteger;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Supplier;\n\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@SlowTest\n@ExtendWith(InterruptingTestCallback.class)\nclass ConcurrentPublicationTermRotationRaceTest\n{\n    private static final int NUM_PUBLISHERS = 3;\n    private static final int NUM_MESSAGES = NUM_PUBLISHERS * 50_000;\n    private static final int ITERATIONS = 100;\n    private TestMediaDriver mediaDriver;\n    private Aeron aeron;\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void setup()\n    {\n        mediaDriver = TestMediaDriver.launch(new MediaDriver.Context()\n                .dirDeleteOnStart(true)\n                .aeronDirectoryName(CommonContext.generateRandomDirName()),\n            systemTestWatcher);\n        systemTestWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(aeron, mediaDriver);\n    }\n\n    @Test\n    @InterruptAfter(600)\n    void handleTermIdMovingAheadBetweenPositionChecksAndTheTermOffsetIncrement() throws InterruptedException\n    {\n        for (int i = 0; i < ITERATIONS; i++)\n        {\n            runTest();\n        }\n    }\n\n    private void runTest() throws InterruptedException\n    {\n        final int sessionId = BitUtil.generateRandomisedId();\n        final String channel =\n            \"aeron:ipc?alias=concurrency|term-length=64K|init-term-id=11|term-id=16|term-offset=48896|mtu=8192|\" +\n            \"session-id=\" + sessionId;\n        final int streamId = 555555;\n\n        try (Subscription subscription = aeron.addSubscription(channel, streamId))\n        {\n            final CountDownLatch startLatch = new CountDownLatch(NUM_PUBLISHERS + 1);\n            final AtomicReference<Throwable> errors = new AtomicReference<>();\n            final LongHashSet publisherIds = new LongHashSet();\n            final ArrayList<MessagePublisher> publishers = new ArrayList<>(NUM_PUBLISHERS);\n            final ArrayList<Publication> publications = new ArrayList<>(NUM_PUBLISHERS);\n            for (int i = 0; i < NUM_PUBLISHERS; i++)\n            {\n                final ConcurrentPublication publication = aeron.addPublication(channel, streamId);\n                final MessagePublisher publisher = (i & 1) == 0 ?\n                    new OfferMessagePublisher(publication, 8160, \"offer-\" + i, startLatch, errors) :\n                    new TryClaimMessagePublisher(publication, 7777, \"try-claim\", startLatch, errors);\n\n                publishers.add(publisher);\n                publisherIds.add(publisher.publisherId);\n                publisher.start();\n                publications.add(publication);\n            }\n\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(publications.get(0));\n\n            startLatch.countDown();\n            startLatch.await();\n\n            final MutableInteger msgCount = new MutableInteger();\n            final ImageFragmentAssembler fragmentHandler = new ImageFragmentAssembler(\n                (buffer, offset, length, header) ->\n                {\n                    final long publisherId = buffer.getLong(offset, LITTLE_ENDIAN);\n                    assertTrue(publisherIds.contains(publisherId));\n                    msgCount.increment();\n                });\n            final Supplier<String> errorMessageSupplier = () -> \"missing messages: expected=\" + NUM_MESSAGES +\n                \", sent=\" + publishers.stream().mapToLong(p -> p.sendCount).sum() +\n                \", received=\" + msgCount;\n\n            assertEquals(1, subscription.imageCount());\n            final Image image = subscription.imageBySessionId(sessionId);\n\n            while (msgCount.get() < NUM_MESSAGES)\n            {\n                if (0 == image.poll(fragmentHandler, 10))\n                {\n                    final Throwable err = errors.get();\n                    if (null != err)\n                    {\n                        LangUtil.rethrowUnchecked(err);\n                    }\n                    Tests.yieldingIdle(errorMessageSupplier);\n                }\n            }\n\n            for (final MessagePublisher publisher : publishers)\n            {\n                publisher.join();\n            }\n\n            CloseHelper.closeAll(publications);\n\n            final Throwable err = errors.get();\n            if (null != err)\n            {\n                LangUtil.rethrowUnchecked(err);\n            }\n        }\n    }\n\n    abstract static class MessagePublisher extends Thread\n    {\n        private static final AtomicLong NEXT_ID = new AtomicLong(Integer.MAX_VALUE);\n        private final CountDownLatch startLatch;\n        private final ConcurrentPublication publication;\n        private final int messageSize;\n        private final AtomicReference<Throwable> errors;\n        final long publisherId = NEXT_ID.getAndIncrement();\n        long sendCount;\n\n        MessagePublisher(\n            final ConcurrentPublication publication,\n            final int messageSize,\n            final String name,\n            final CountDownLatch startLatch,\n            final AtomicReference<Throwable> errors)\n        {\n            this.publication = publication;\n            this.messageSize = messageSize;\n            this.startLatch = startLatch;\n            this.errors = errors;\n            setName(name);\n            setDaemon(true);\n        }\n\n        public void run()\n        {\n            startLatch.countDown();\n            try\n            {\n                startLatch.await();\n\n                final int numMessages = NUM_MESSAGES / NUM_PUBLISHERS;\n\n                for (int i = 0; i < numMessages; i++)\n                {\n                    long position;\n                    while ((position = publish(publication, publisherId, messageSize)) < 0)\n                    {\n                        if (Publication.CLOSED == position ||\n                            Publication.MAX_POSITION_EXCEEDED == position ||\n                            Publication.NOT_CONNECTED == position)\n                        {\n                            fail(\"failed to publish: \" + Publication.errorString(position));\n                        }\n                        Tests.yield();\n                    }\n\n                    sendCount++;\n                }\n            }\n            catch (final Throwable t)\n            {\n                if (!errors.compareAndSet(null, t))\n                {\n                    errors.get().addSuppressed(t);\n                }\n            }\n        }\n\n        abstract long publish(ConcurrentPublication publication, long payload, int size);\n    }\n\n    static final class OfferMessagePublisher extends MessagePublisher\n    {\n        private final ExpandableArrayBuffer msgBuffer = new ExpandableArrayBuffer(1024);\n\n        OfferMessagePublisher(\n            final ConcurrentPublication publication,\n            final int messageSize,\n            final String name,\n            final CountDownLatch startLatch,\n            final AtomicReference<Throwable> errors)\n        {\n            super(publication, messageSize, name, startLatch, errors);\n        }\n\n        long publish(final ConcurrentPublication publication, final long payload, final int size)\n        {\n            msgBuffer.checkLimit(size);\n            msgBuffer.putLong(0, payload, LITTLE_ENDIAN);\n            return publication.offer(msgBuffer, 0, size);\n        }\n    }\n\n    static final class TryClaimMessagePublisher extends MessagePublisher\n    {\n        private final BufferClaim bufferClaim = new BufferClaim();\n\n        TryClaimMessagePublisher(\n            final ConcurrentPublication publication,\n            final int messageSize,\n            final String name,\n            final CountDownLatch startLatch,\n            final AtomicReference<Throwable> errors)\n        {\n            super(publication, messageSize, name, startLatch, errors);\n        }\n\n        long publish(final ConcurrentPublication publication, final long payload, final int size)\n        {\n            final long position = publication.tryClaim(size, bufferClaim);\n            if (position > 0)\n            {\n                bufferClaim.buffer().putLong(bufferClaim.offset(), payload, LITTLE_ENDIAN);\n                bufferClaim.commit();\n            }\n\n            return position;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ControlledAssemblyTest.java",
    "content": "/*\n * Copyright 2026 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.ControlledFragmentHandler.Action;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.BiConsumer;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.logbuffer.ControlledFragmentHandler.Action.ABORT;\nimport static io.aeron.logbuffer.ControlledFragmentHandler.Action.CONTINUE;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ControlledAssemblyTest\n{\n    private static final int STREAM_ID = 1001;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context driverContext = new MediaDriver.Context()\n        .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n        .errorHandler(Tests::onError)\n        .threadingMode(ThreadingMode.SHARED);\n\n    private TestMediaDriver driver;\n\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(driverContext, testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void testHeaderInRepeatedCallbacksAfterAborting()\n    {\n        try (Subscription subscription1 = aeron.addSubscription(IPC_CHANNEL, STREAM_ID);\n            Subscription subscription2 = aeron.addSubscription(IPC_CHANNEL, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(IPC_CHANNEL, STREAM_ID))\n        {\n            final UnsafeBuffer buffer = new UnsafeBuffer(new byte[publication.maxPayloadLength() + 1]);\n\n            Tests.awaitConnected(subscription1);\n            Tests.awaitConnected(subscription2);\n\n            final int length1 = 17;\n            final long position1 = offer(publication, buffer, length1);\n\n            final int length2 = buffer.capacity();\n            final long position2 = offer(publication, buffer, length2);\n\n            final int length3 = 64;\n            final long position3 = offer(publication, buffer, length3);\n\n            final TestHandler subscriptionHandler = new TestHandler();\n            final ControlledFragmentAssembler subscriptionAssembler =\n                new ControlledFragmentAssembler(subscriptionHandler);\n\n            final Image image = subscription2.imageAtIndex(0);\n            final TestHandler imageHandler = new TestHandler();\n            final ImageControlledFragmentAssembler imageAssembler = new ImageControlledFragmentAssembler(imageHandler);\n\n            final BiConsumer<Action, Integer> pollUntilActionReturnedNTimes = (action, n) ->\n            {\n                subscriptionHandler.action = action;\n                final int subscriptionExpected = subscriptionHandler.fragments.size() + n;\n                while (subscriptionHandler.fragments.size() < subscriptionExpected)\n                {\n                    if (subscription1.controlledPoll(subscriptionAssembler, 1) == 0)\n                    {\n                        Tests.yield();\n                    }\n                }\n\n                imageHandler.action = action;\n                final int imageExpected = imageHandler.fragments.size() + n;\n                while (imageHandler.fragments.size() < imageExpected)\n                {\n                    if (image.controlledPoll(imageAssembler, 1) == 0)\n                    {\n                        Tests.yield();\n                    }\n                }\n            };\n\n            pollUntilActionReturnedNTimes.accept(ABORT, 1);\n            pollUntilActionReturnedNTimes.accept(CONTINUE, 1);\n            pollUntilActionReturnedNTimes.accept(ABORT, 2);\n            pollUntilActionReturnedNTimes.accept(CONTINUE, 2);\n\n            assertEquals(List.of(\n                new Fragment(length1, length1 + HEADER_LENGTH, NULL_VALUE, position1),\n                new Fragment(length1, length1 + HEADER_LENGTH, NULL_VALUE, position1),\n                new Fragment(length2, length2 + HEADER_LENGTH, (int)(position2 - position1), position2),\n                new Fragment(length2, length2 + HEADER_LENGTH, (int)(position2 - position1), position2),\n                new Fragment(length2, length2 + HEADER_LENGTH, (int)(position2 - position1), position2),\n                new Fragment(length3, length3 + HEADER_LENGTH, NULL_VALUE, position3)\n            ), subscriptionHandler.fragments);\n\n            assertEquals(subscriptionHandler.fragments, imageHandler.fragments);\n        }\n    }\n\n    private static long offer(final Publication publication, final DirectBuffer buffer, final int length)\n    {\n        while (true)\n        {\n            final long position = publication.offer(buffer, 0, length);\n            if (position > 0)\n            {\n                return position;\n            }\n            Tests.yield();\n        }\n    }\n\n    private static final class TestHandler implements ControlledFragmentHandler\n    {\n        private final List<Fragment> fragments = new ArrayList<>();\n        private Action action;\n\n        public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n        {\n            fragments.add(new Fragment(\n                length,\n                header.frameLength(),\n                header.fragmentedFrameLength(),\n                header.position()));\n\n            return action;\n        }\n    }\n\n    private record Fragment(int length, int frameLength, int fragmentedFrameLength, long position)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ControlledMessageTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ControlledMessageTest\n{\n    private static final String CHANNEL = CommonContext.IPC_CHANNEL;\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n    private static final int PAYLOAD_LENGTH = 10;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n                .errorHandler(Tests::onError)\n                .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n                .threadingMode(ThreadingMode.SHARED),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReceivePublishedMessage()\n    {\n        try (Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID);\n            Publication publication = aeron.addPublication(CHANNEL, STREAM_ID))\n        {\n            final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[PAYLOAD_LENGTH * 4]);\n\n            for (int i = 0; i < 4; i++)\n            {\n                srcBuffer.setMemory(i * PAYLOAD_LENGTH, PAYLOAD_LENGTH, (byte)(65 + i));\n            }\n\n            for (int i = 0; i < 4; i++)\n            {\n                while (publication.offer(srcBuffer, i * PAYLOAD_LENGTH, PAYLOAD_LENGTH) < 0L)\n                {\n                    Tests.yield();\n                }\n            }\n\n            final FragmentCollector fragmentCollector = new FragmentCollector();\n            int numFragments = 0;\n            do\n            {\n                final int fragments = subscription.controlledPoll(fragmentCollector, FRAGMENT_COUNT_LIMIT);\n                if (0 == fragments)\n                {\n                    Tests.yield();\n                }\n                numFragments += fragments;\n            }\n            while (numFragments < 4);\n\n            final UnsafeBuffer collectedBuffer = fragmentCollector.collectedBuffer();\n\n            for (int i = 0; i < srcBuffer.capacity(); i++)\n            {\n                assertEquals(srcBuffer.getByte(i), collectedBuffer.getByte(i), \"same at i=\" + i);\n            }\n        }\n    }\n\n    static class FragmentCollector implements ControlledFragmentHandler\n    {\n        private final UnsafeBuffer collectedBuffer = new UnsafeBuffer(new byte[PAYLOAD_LENGTH * 4]);\n        private int limit = 0;\n        private int fragmentCount = 0;\n\n        UnsafeBuffer collectedBuffer()\n        {\n            return collectedBuffer;\n        }\n\n        public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n        {\n            ++fragmentCount;\n\n            Action action = Action.CONTINUE;\n\n            if (fragmentCount == 3)\n            {\n                action = Action.ABORT;\n            }\n            else if (fragmentCount == 5)\n            {\n                action = Action.BREAK;\n            }\n\n            if (Action.ABORT != action)\n            {\n                collectedBuffer.putBytes(limit, buffer, offset, length);\n                limit += length;\n            }\n\n            return action;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/CounterReferencesTest.java",
    "content": "/*\n * Copyright 2023 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.CommonContext.SPY_PREFIX;\nimport static io.aeron.driver.status.PublisherPos.PUBLISHER_POS_TYPE_ID;\nimport static io.aeron.driver.status.ReceiverHwm.RECEIVER_HWM_TYPE_ID;\nimport static io.aeron.driver.status.ReceiverPos.RECEIVER_POS_TYPE_ID;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass CounterReferencesTest\n{\n    private static final String UDP_CHANNEL = \"aeron:udp?endpoint=localhost:24325\";\n    private static final int STREAM_ID = 7000;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n    private CountersReader countersReader;\n\n    @BeforeEach\n    void setUp()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .publicationTermBufferLength(TERM_MIN_LENGTH)\n            .ipcTermBufferLength(TERM_MIN_LENGTH)\n            .threadingMode(ThreadingMode.SHARED)\n            .errorHandler(Tests::onError);\n\n        driver = TestMediaDriver.launch(driverCtx, testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect(new Aeron.Context());\n        countersReader = aeron.countersReader();\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SuppressWarnings(\"try\")\n    void shouldLinkUdpSubPosToTheUnderlyingImage()\n    {\n        try (\n            Subscription subscription = aeron.addSubscription(UDP_CHANNEL, STREAM_ID);\n            Publication publication = aeron.addPublication(UDP_CHANNEL, STREAM_ID))\n        {\n            Tests.awaitConnected(subscription);\n\n            final int subPosId = subscription.imageAtIndex(0).subscriberPositionId();\n            final long referenceId = countersReader.getCounterReferenceId(subPosId);\n\n            final int rcvHwmId = countersReader.findByTypeIdAndRegistrationId(RECEIVER_HWM_TYPE_ID, referenceId);\n            assertNotEquals(NULL_COUNTER_ID, rcvHwmId);\n\n            final int rcvPosId = countersReader.findByTypeIdAndRegistrationId(RECEIVER_POS_TYPE_ID, referenceId);\n            assertNotEquals(NULL_COUNTER_ID, rcvPosId);\n\n            try (Subscription anotherSubscription = aeron.addSubscription(UDP_CHANNEL, STREAM_ID))\n            {\n                Tests.awaitConnected(anotherSubscription);\n\n                final int anotherSubPosId = anotherSubscription.imageAtIndex(0).subscriberPositionId();\n                assertEquals(referenceId, countersReader.getCounterReferenceId(anotherSubPosId));\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldLinkIpcSubPosToTheUnderlyingPublication()\n    {\n        try (\n            Subscription subscription = aeron.addSubscription(IPC_CHANNEL, STREAM_ID);\n            Publication publication = aeron.addPublication(IPC_CHANNEL, STREAM_ID))\n        {\n            Tests.awaitConnected(subscription);\n\n            final int subPosId = subscription.imageAtIndex(0).subscriberPositionId();\n            final long referenceId = countersReader.getCounterReferenceId(subPosId);\n\n            assertEquals(publication.registrationId(), referenceId);\n\n            final int pubPosId = countersReader.findByTypeIdAndRegistrationId(PUBLISHER_POS_TYPE_ID, referenceId);\n            assertNotEquals(NULL_COUNTER_ID, pubPosId);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldLinkSpySubPosToTheUnderlyingPublication()\n    {\n        try (\n            Subscription subscription = aeron.addSubscription(SPY_PREFIX + UDP_CHANNEL, STREAM_ID);\n            Publication publication = aeron.addPublication(UDP_CHANNEL, STREAM_ID))\n        {\n            Tests.awaitConnected(subscription);\n\n            final int subPosId = subscription.imageAtIndex(0).subscriberPositionId();\n            final long referenceId = countersReader.getCounterReferenceId(subPosId);\n\n            assertEquals(publication.registrationId(), referenceId);\n\n            final int pubPosId = countersReader.findByTypeIdAndRegistrationId(PUBLISHER_POS_TYPE_ID, referenceId);\n            assertNotEquals(NULL_COUNTER_ID, pubPosId);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/CounterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.ClientTimeoutException;\nimport io.aeron.exceptions.ConductorServiceTimeoutException;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.status.ReadableCounter;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.hamcrest.CoreMatchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.Mockito;\n\nimport java.util.Arrays;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static org.hamcrest.CoreMatchers.allOf;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.MatcherAssert.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass CounterTest\n{\n    private static final int COUNTER_TYPE_ID = 1101;\n    private static final String COUNTER_LABEL = \"counter label\";\n\n    private final UnsafeBuffer keyBuffer = new UnsafeBuffer(new byte[64]);\n    private final UnsafeBuffer labelBuffer = new UnsafeBuffer(new byte[COUNTER_LABEL.length()]);\n\n    private Aeron clientA;\n    private Aeron clientB;\n    private TestMediaDriver driver;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private volatile ReadableCounter readableCounter;\n\n    @BeforeEach\n    void before()\n    {\n        labelBuffer.putStringWithoutLengthAscii(0, COUNTER_LABEL);\n\n        driver = TestMediaDriver.launch(\n            new MediaDriver.Context()\n                .aeronDirectoryName(CommonContext.generateRandomDirName())\n                .errorHandler(Tests::onError)\n                .threadingMode(ThreadingMode.SHARED)\n                .clientLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(1))\n                .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(500)),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        clientA = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n        clientB = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(clientA, clientB, driver);\n        if (null != driver)\n        {\n            driver.context().deleteDirectory();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldBeAbleToAddCounter()\n    {\n        final AvailableCounterHandler availableCounterHandlerClientA = mock(AvailableCounterHandler.class);\n        clientA.addAvailableCounterHandler(availableCounterHandlerClientA);\n\n        final AvailableCounterHandler availableCounterHandlerClientB = mock(AvailableCounterHandler.class);\n        clientB.addAvailableCounterHandler(availableCounterHandlerClientB);\n\n        final Counter counter = clientA.addCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length());\n\n        assertFalse(counter.isClosed());\n        assertEquals(counter.registrationId(), clientA.countersReader().getCounterRegistrationId(counter.id()));\n        assertEquals(clientA.clientId(), clientA.countersReader().getCounterOwnerId(counter.id()));\n\n        verify(availableCounterHandlerClientA, timeout(5000L))\n            .onAvailableCounter(any(CountersReader.class), eq(counter.registrationId()), eq(counter.id()));\n        verify(availableCounterHandlerClientB, timeout(5000L))\n            .onAvailableCounter(any(CountersReader.class), eq(counter.registrationId()), eq(counter.id()));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldBeAbleToAddReadableCounterWithinHandler()\n    {\n        clientB.addAvailableCounterHandler(this::createReadableCounter);\n\n        final Counter counter = clientA.addCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length());\n\n        while (null == readableCounter)\n        {\n            Tests.sleep(1);\n        }\n\n        assertEquals(CountersReader.RECORD_ALLOCATED, readableCounter.state());\n        assertEquals(counter.id(), readableCounter.counterId());\n        assertEquals(counter.registrationId(), readableCounter.registrationId());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldCloseReadableCounterOnUnavailableCounter()\n    {\n        clientB.addAvailableCounterHandler(this::createReadableCounter);\n        clientB.addUnavailableCounterHandler(this::unavailableCounterHandler);\n\n        final Counter counter = clientA.addCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length());\n\n        while (null == readableCounter)\n        {\n            Tests.sleep(1);\n        }\n\n        assertFalse(readableCounter.isClosed());\n        assertEquals(CountersReader.RECORD_ALLOCATED, readableCounter.state());\n\n        counter.close();\n\n        while (!readableCounter.isClosed())\n        {\n            Tests.sleep(1);\n        }\n\n        while (clientA.hasActiveCommands())\n        {\n            Tests.sleep(1);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldGetUnavailableCounterWhenOwningClientIsClosed()\n    {\n        clientB.addAvailableCounterHandler(this::createReadableCounter);\n        clientB.addUnavailableCounterHandler(this::unavailableCounterHandler);\n\n        clientA.addCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length());\n\n        while (null == readableCounter)\n        {\n            Tests.sleep(1);\n        }\n\n        assertFalse(readableCounter.isClosed());\n        assertEquals(CountersReader.RECORD_ALLOCATED, readableCounter.state());\n\n        clientA.close();\n\n        while (!readableCounter.isClosed())\n        {\n            Tests.sleep(1, \"Counter not closed\");\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldBeAbleToAddStaticCounter()\n    {\n        final AvailableCounterHandler availableCounterHandlerClientA = mock(AvailableCounterHandler.class);\n        final UnavailableCounterHandler unavailableCounterHandlerClientA = mock(UnavailableCounterHandler.class);\n        clientA.addAvailableCounterHandler(availableCounterHandlerClientA);\n        clientA.addUnavailableCounterHandler(unavailableCounterHandlerClientA);\n\n        final AvailableCounterHandler availableCounterHandlerClientB = mock(AvailableCounterHandler.class);\n        final UnavailableCounterHandler unavailableCounterHandlerClientB = mock(UnavailableCounterHandler.class);\n        clientB.addAvailableCounterHandler(availableCounterHandlerClientB);\n        clientB.addUnavailableCounterHandler(unavailableCounterHandlerClientB);\n\n        final Counter counter1 = clientA.addStaticCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length(),\n            100);\n\n        assertFalse(counter1.isClosed());\n        assertEquals(100, counter1.registrationId());\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientA.countersReader().getCounterState(counter1.id()));\n        assertEquals(counter1.registrationId(), clientA.countersReader().getCounterRegistrationId(counter1.id()));\n        assertEquals(NULL_VALUE, clientA.countersReader().getCounterOwnerId(counter1.id()));\n        assertEquals(COUNTER_TYPE_ID, clientA.countersReader().getCounterTypeId(counter1.id()));\n\n        final Counter counter2 = clientB.addStaticCounter(COUNTER_TYPE_ID, \"test static counter\", 200);\n\n        assertFalse(counter2.isClosed());\n        assertEquals(200, counter2.registrationId());\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientB.countersReader().getCounterState(counter2.id()));\n        assertEquals(counter2.registrationId(), clientB.countersReader().getCounterRegistrationId(counter2.id()));\n        assertEquals(NULL_VALUE, clientB.countersReader().getCounterOwnerId(counter2.id()));\n        assertEquals(\"test static counter\", clientB.countersReader().getCounterLabel(counter2.id()));\n        assertEquals(COUNTER_TYPE_ID, clientB.countersReader().getCounterTypeId(counter2.id()));\n\n        verify(availableCounterHandlerClientA, Mockito.after(1000L).never())\n            .onAvailableCounter(any(CountersReader.class), eq(counter1.registrationId()), eq(counter1.id()));\n        verify(availableCounterHandlerClientA, never())\n            .onAvailableCounter(any(CountersReader.class), eq(counter2.registrationId()), eq(counter2.id()));\n        verify(availableCounterHandlerClientB, never())\n            .onAvailableCounter(any(CountersReader.class), eq(counter1.registrationId()), eq(counter1.id()));\n        verify(availableCounterHandlerClientB, never())\n            .onAvailableCounter(any(CountersReader.class), eq(counter2.registrationId()), eq(counter2.id()));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReturnExistingStaticCounterAndNotUpdateAnything()\n    {\n        final AvailableCounterHandler availableCounterHandlerClientA = mock(AvailableCounterHandler.class);\n        final UnavailableCounterHandler unavailableCounterHandlerClientA = mock(UnavailableCounterHandler.class);\n        clientA.addAvailableCounterHandler(availableCounterHandlerClientA);\n        clientA.addUnavailableCounterHandler(unavailableCounterHandlerClientA);\n\n        final AvailableCounterHandler availableCounterHandlerClientB = mock(AvailableCounterHandler.class);\n        final UnavailableCounterHandler unavailableCounterHandlerClientB = mock(UnavailableCounterHandler.class);\n        clientB.addAvailableCounterHandler(availableCounterHandlerClientB);\n        clientB.addUnavailableCounterHandler(unavailableCounterHandlerClientB);\n\n        final long registrationId = 888;\n        ThreadLocalRandom.current().nextBytes(keyBuffer.byteArray());\n        final byte[] expectedKeyBytes = Arrays.copyOf(keyBuffer.byteArray(), keyBuffer.capacity());\n        final Counter counter1 = clientA.addStaticCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length(),\n            registrationId);\n\n        assertFalse(counter1.isClosed());\n        assertEquals(registrationId, counter1.registrationId());\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientA.countersReader().getCounterState(counter1.id()));\n        assertEquals(counter1.registrationId(), clientA.countersReader().getCounterRegistrationId(counter1.id()));\n        assertEquals(NULL_VALUE, clientA.countersReader().getCounterOwnerId(counter1.id()));\n        assertEquals(COUNTER_TYPE_ID, clientA.countersReader().getCounterTypeId(counter1.id()));\n        assertEquals(COUNTER_LABEL, clientA.countersReader().getCounterLabel(counter1.id()));\n\n        final Counter counter2 = clientB.addStaticCounter(COUNTER_TYPE_ID, \"test static counter\", registrationId);\n\n        assertEquals(counter1.id(), counter2.id());\n        assertEquals(registrationId, counter2.registrationId());\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientB.countersReader().getCounterState(counter2.id()));\n        assertEquals(registrationId, clientB.countersReader().getCounterRegistrationId(counter2.id()));\n        assertEquals(NULL_VALUE, clientB.countersReader().getCounterOwnerId(counter2.id()));\n        assertEquals(COUNTER_TYPE_ID, clientB.countersReader().getCounterTypeId(counter2.id()));\n        assertEquals(COUNTER_LABEL, clientB.countersReader().getCounterLabel(counter2.id()));\n\n        final MutableBoolean keyChecked = new MutableBoolean(false);\n        clientB.countersReader().forEach((counterId, typeId, keyBuffer, label) ->\n        {\n            if (counterId == counter1.id())\n            {\n                final byte[] actualKeyBytes = new byte[expectedKeyBytes.length];\n                keyBuffer.getBytes(0, actualKeyBytes, 0, expectedKeyBytes.length);\n                assertArrayEquals(expectedKeyBytes, actualKeyBytes);\n                keyChecked.set(true);\n            }\n        });\n        assertTrue(keyChecked.get());\n\n        verify(availableCounterHandlerClientA, Mockito.after(1000L).never())\n            .onAvailableCounter(any(CountersReader.class), eq(registrationId), eq(counter1.id()));\n        verify(availableCounterHandlerClientB, never())\n            .onAvailableCounter(any(CountersReader.class), eq(registrationId), eq(counter2.id()));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotDeleteStaticCounterIfClosed()\n    {\n        final AvailableCounterHandler availableCounterHandlerClientA = mock(AvailableCounterHandler.class);\n        final UnavailableCounterHandler unavailableCounterHandlerClientA = mock(UnavailableCounterHandler.class);\n        clientA.addAvailableCounterHandler(availableCounterHandlerClientA);\n        clientA.addUnavailableCounterHandler(unavailableCounterHandlerClientA);\n\n        final AvailableCounterHandler availableCounterHandlerClientB = mock(AvailableCounterHandler.class);\n        final UnavailableCounterHandler unavailableCounterHandlerClientB = mock(UnavailableCounterHandler.class);\n        clientB.addAvailableCounterHandler(availableCounterHandlerClientB);\n        clientB.addUnavailableCounterHandler(unavailableCounterHandlerClientB);\n\n        final Counter counter1 = clientA.addStaticCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length(),\n            100);\n\n        assertFalse(counter1.isClosed());\n        assertEquals(100, counter1.registrationId());\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientA.countersReader().getCounterState(counter1.id()));\n        assertEquals(counter1.registrationId(), clientA.countersReader().getCounterRegistrationId(counter1.id()));\n        assertEquals(NULL_VALUE, clientA.countersReader().getCounterOwnerId(counter1.id()));\n        assertEquals(COUNTER_TYPE_ID, clientB.countersReader().getCounterTypeId(counter1.id()));\n\n        counter1.close();\n        assertTrue(counter1.isClosed());\n\n        verify(unavailableCounterHandlerClientA, Mockito.after(1000L).never())\n            .onUnavailableCounter(any(CountersReader.class), eq(counter1.registrationId()), eq(counter1.id()));\n\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientA.countersReader().getCounterState(counter1.id()));\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientB.countersReader().getCounterState(counter1.id()));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SuppressWarnings(\"indentation\")\n    void shouldReturnErrorIfANonStaticCounterExistsForTypeIdRegistrationId()\n    {\n        testWatcher.ignoreErrorsMatching((error) -> error.contains(\"(-11) generic error, see message\"));\n\n        final Counter counter = clientA.addCounter(COUNTER_TYPE_ID, \"test session-specific counter\");\n        assertNotEquals(NULL_VALUE, counter.registrationId());\n        assertEquals(clientA.clientId(), clientA.countersReader().getCounterOwnerId(counter.id()));\n\n        final RegistrationException registrationException = assertThrowsExactly(\n            RegistrationException.class,\n            () -> clientA.addStaticCounter(\n                COUNTER_TYPE_ID,\n                keyBuffer,\n                0,\n                keyBuffer.capacity(),\n                labelBuffer,\n                0,\n                COUNTER_LABEL.length(),\n                counter.registrationId()));\n\n        assertThat(\n            registrationException.getMessage(),\n            allOf(\n                containsString(\"cannot add static counter, because a non-static counter exists\"),\n                containsString(\"counterId=\" + counter.id()),\n                containsString(\"typeId=\" + COUNTER_TYPE_ID),\n                containsString(\"registrationId=\" + counter.registrationId()),\n                containsString(\"errorCodeValue=11\")));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotCloseStaticCounterWhenClientInstanceIsClosed()\n    {\n        final AtomicBoolean clientClosed = new AtomicBoolean();\n        clientA.addCloseHandler(() -> clientClosed.set(true));\n\n        final Counter counter1 = clientA.addStaticCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length(),\n            1);\n\n        final Counter counter2 = clientB.addStaticCounter(COUNTER_TYPE_ID + 2, \"test static counter\", 22);\n\n        final Counter counter3 = clientA.addCounter(COUNTER_TYPE_ID, \"delete me\");\n\n        clientA.close();\n\n        Tests.await(clientClosed::get);\n        assertFalse(counter1.isClosed());\n        assertTrue(counter3.isClosed());\n\n        Tests.await(() -> CountersReader.RECORD_RECLAIMED == clientB.countersReader().getCounterState(counter3.id()));\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientB.countersReader().getCounterState(counter1.id()));\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientB.countersReader().getCounterState(counter2.id()));\n        assertFalse(counter2.isClosed());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotCloseStaticCounterIfClientTimesOut()\n    {\n        final AvailableCounterHandler availableCounterHandler = mock(AvailableCounterHandler.class);\n        final UnavailableCounterHandler unavailableCounterHandler = mock(UnavailableCounterHandler.class);\n        final AtomicReference<Throwable> error = new AtomicReference<>();\n        try (Aeron aeron = Aeron.connect(new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .useConductorAgentInvoker(true)\n            .availableCounterHandler(availableCounterHandler)\n            .unavailableCounterHandler(unavailableCounterHandler)\n            .errorHandler(error::set)))\n        {\n            final AgentInvoker conductorAgentInvoker = aeron.conductorAgentInvoker();\n            assertNotNull(conductorAgentInvoker);\n            final CountersReader countersReader = clientA.countersReader();\n            assertEquals(0, countersReader.getCounterValue(SystemCounterDescriptor.CLIENT_TIMEOUTS.id()));\n\n            final Counter counter = aeron.addStaticCounter(COUNTER_TYPE_ID, \"test\", 42);\n            assertNotNull(counter);\n            assertFalse(counter.isClosed());\n            assertEquals(CountersReader.RECORD_ALLOCATED, aeron.countersReader().getCounterState(counter.id()));\n\n            final Counter counter2 = aeron.addCounter(COUNTER_TYPE_ID * 2, \"delete me\");\n\n            conductorAgentInvoker.invoke();\n\n            Tests.await(() -> 1 == countersReader.getCounterValue(SystemCounterDescriptor.CLIENT_TIMEOUTS.id()));\n\n            while (null == error.get())\n            {\n                conductorAgentInvoker.invoke();\n                Thread.yield();\n            }\n            final Throwable timeoutException = error.get();\n            if (timeoutException instanceof ClientTimeoutException)\n            {\n                assertEquals(\"FATAL - client timeout from driver\", timeoutException.getMessage());\n            }\n            else if (timeoutException instanceof ConductorServiceTimeoutException)\n            {\n                assertThat(timeoutException.getMessage(), CoreMatchers.startsWith(\"FATAL - service interval exceeded\"));\n            }\n            else if (timeoutException instanceof AeronException)\n            {\n                assertThat(\n                    timeoutException.getMessage(),\n                    CoreMatchers.startsWith(\"ERROR - unexpected close of heartbeat timestamp counter:\"));\n            }\n            else\n            {\n                // on unknown error print stack trace\n                timeoutException.printStackTrace();\n            }\n\n            assertTrue(counter2.isClosed());\n            assertEquals(CountersReader.RECORD_RECLAIMED, aeron.countersReader().getCounterState(counter2.id()));\n            verify(availableCounterHandler).onAvailableCounter(\n                any(CountersReader.class), eq(counter2.registrationId()), eq(counter2.id()));\n            verify(unavailableCounterHandler).onUnavailableCounter(\n                any(CountersReader.class), eq(counter2.registrationId()), eq(counter2.id()));\n\n            assertFalse(counter.isClosed());\n            assertEquals(CountersReader.RECORD_ALLOCATED, aeron.countersReader().getCounterState(counter.id()));\n            verify(availableCounterHandler, never()).onAvailableCounter(\n                any(CountersReader.class), eq(counter.registrationId()), eq(counter.id()));\n            verify(unavailableCounterHandler, never()).onUnavailableCounter(\n                any(CountersReader.class), eq(counter.registrationId()), eq(counter.id()));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAddCounterAsynchronously()\n    {\n        final AvailableCounterHandler availableCounterHandlerClientA = mock(AvailableCounterHandler.class);\n        clientA.addAvailableCounterHandler(availableCounterHandlerClientA);\n\n        final AvailableCounterHandler availableCounterHandlerClientB = mock(AvailableCounterHandler.class);\n        clientB.addAvailableCounterHandler(availableCounterHandlerClientB);\n\n        final long registrationId = clientA.asyncAddCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length());\n\n        Counter counter;\n        while (null == (counter = clientA.getCounter(registrationId)))\n        {\n            Tests.yield();\n        }\n\n        assertNotNull(counter);\n        assertFalse(counter.isClosed());\n        assertEquals(registrationId, clientA.countersReader().getCounterRegistrationId(counter.id()));\n        assertEquals(clientA.clientId(), clientA.countersReader().getCounterOwnerId(counter.id()));\n\n        assertNull(clientB.getCounter(registrationId));\n\n        verify(availableCounterHandlerClientA, timeout(5000L))\n            .onAvailableCounter(any(CountersReader.class), eq(registrationId), eq(counter.id()));\n        verify(availableCounterHandlerClientB, timeout(5000L))\n            .onAvailableCounter(any(CountersReader.class), eq(registrationId), eq(counter.id()));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAddCounterAsynchronouslyUsingLabelAndTypeId()\n    {\n        final AvailableCounterHandler availableCounterHandlerClientA = mock(AvailableCounterHandler.class);\n        clientA.addAvailableCounterHandler(availableCounterHandlerClientA);\n\n        final AvailableCounterHandler availableCounterHandlerClientB = mock(AvailableCounterHandler.class);\n        clientB.addAvailableCounterHandler(availableCounterHandlerClientB);\n\n        final long registrationId = clientA.asyncAddCounter(COUNTER_TYPE_ID, \"test\");\n\n        Counter counter;\n        while (null == (counter = clientA.getCounter(registrationId)))\n        {\n            Tests.yield();\n        }\n\n        assertNotNull(counter);\n        assertFalse(counter.isClosed());\n        assertEquals(registrationId, clientA.countersReader().getCounterRegistrationId(counter.id()));\n        assertEquals(clientA.clientId(), clientA.countersReader().getCounterOwnerId(counter.id()));\n\n        assertNull(clientB.getCounter(registrationId));\n\n        verify(availableCounterHandlerClientA, timeout(5000L))\n            .onAvailableCounter(any(CountersReader.class), eq(registrationId), eq(counter.id()));\n        verify(availableCounterHandlerClientB, timeout(5000L))\n            .onAvailableCounter(any(CountersReader.class), eq(registrationId), eq(counter.id()));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAddStaticCounterAsynchronously()\n    {\n        final AvailableCounterHandler availableCounterHandlerClientA = mock(AvailableCounterHandler.class);\n        final UnavailableCounterHandler unavailableCounterHandlerClientA = mock(UnavailableCounterHandler.class);\n        clientA.addAvailableCounterHandler(availableCounterHandlerClientA);\n        clientA.addUnavailableCounterHandler(unavailableCounterHandlerClientA);\n\n        final AvailableCounterHandler availableCounterHandlerClientB = mock(AvailableCounterHandler.class);\n        final UnavailableCounterHandler unavailableCounterHandlerClientB = mock(UnavailableCounterHandler.class);\n        clientB.addAvailableCounterHandler(availableCounterHandlerClientB);\n        clientB.addUnavailableCounterHandler(unavailableCounterHandlerClientB);\n\n        final long registrationId1 = 100;\n        final long correlationId1 = clientA.asyncAddStaticCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length(),\n            registrationId1);\n        assertNotEquals(registrationId1, correlationId1);\n\n        Counter counter1;\n        while (null == (counter1 = clientA.getCounter(correlationId1)))\n        {\n            Thread.yield();\n        }\n\n        assertNotNull(counter1);\n        assertNull(clientB.getCounter(correlationId1));\n        assertFalse(counter1.isClosed());\n        assertEquals(registrationId1, counter1.registrationId());\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientA.countersReader().getCounterState(counter1.id()));\n        assertEquals(registrationId1, clientA.countersReader().getCounterRegistrationId(counter1.id()));\n        assertEquals(NULL_VALUE, clientA.countersReader().getCounterOwnerId(counter1.id()));\n        assertEquals(COUNTER_TYPE_ID, clientA.countersReader().getCounterTypeId(counter1.id()));\n\n        final long registrationId2 = 200;\n        final long correlationId2 =\n            clientB.asyncAddStaticCounter(COUNTER_TYPE_ID, \"test static counter\", registrationId2);\n        assertNotEquals(registrationId2, correlationId2);\n\n        Counter counter2;\n        while (null == (counter2 = clientB.getCounter(correlationId2)))\n        {\n            Thread.yield();\n        }\n\n        assertNotNull(counter2);\n        assertNull(clientA.getCounter(correlationId2));\n        assertFalse(counter2.isClosed());\n        assertEquals(registrationId2, counter2.registrationId());\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientB.countersReader().getCounterState(counter2.id()));\n        assertEquals(registrationId2, clientB.countersReader().getCounterRegistrationId(counter2.id()));\n        assertEquals(NULL_VALUE, clientB.countersReader().getCounterOwnerId(counter2.id()));\n        assertEquals(\"test static counter\", clientB.countersReader().getCounterLabel(counter2.id()));\n        assertEquals(COUNTER_TYPE_ID, clientB.countersReader().getCounterTypeId(counter2.id()));\n\n        assertSame(counter1, clientA.getCounter(correlationId1));\n        assertSame(counter2, clientB.getCounter(correlationId2));\n\n        verify(availableCounterHandlerClientA, Mockito.after(1000L).never())\n            .onAvailableCounter(any(CountersReader.class), eq(counter1.registrationId()), eq(counter1.id()));\n        verify(availableCounterHandlerClientA, never())\n            .onAvailableCounter(any(CountersReader.class), eq(counter2.registrationId()), eq(counter2.id()));\n        verify(availableCounterHandlerClientB, never())\n            .onAvailableCounter(any(CountersReader.class), eq(counter1.registrationId()), eq(counter1.id()));\n        verify(availableCounterHandlerClientB, never())\n            .onAvailableCounter(any(CountersReader.class), eq(counter2.registrationId()), eq(counter2.id()));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotCloseAsyncStaticCounterWhenClientInstanceIsClosed()\n    {\n        final AtomicBoolean clientClosed = new AtomicBoolean();\n        clientA.addCloseHandler(() -> clientClosed.set(true));\n\n        final long correlationId1 = clientA.asyncAddStaticCounter(\n            COUNTER_TYPE_ID,\n            keyBuffer,\n            0,\n            keyBuffer.capacity(),\n            labelBuffer,\n            0,\n            COUNTER_LABEL.length(),\n            1);\n\n        final long correlationId2 = clientB.asyncAddStaticCounter(COUNTER_TYPE_ID + 2, \"test static counter\", 22);\n\n        final Counter counter3 = clientA.addCounter(COUNTER_TYPE_ID, \"delete me\");\n\n        Counter counter1, counter2;\n        while (null == (counter1 = clientA.getCounter(correlationId1)))\n        {\n            Tests.yield();\n        }\n        while (null == (counter2 = clientB.getCounter(correlationId2)))\n        {\n            Tests.yield();\n        }\n\n        clientA.close();\n\n        Tests.await(clientClosed::get);\n\n        assertTrue(counter3.isClosed());\n        Tests.await(() -> CountersReader.RECORD_RECLAIMED == clientB.countersReader().getCounterState(counter3.id()));\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientB.countersReader().getCounterState(counter1.id()));\n        assertEquals(CountersReader.RECORD_ALLOCATED, clientB.countersReader().getCounterState(counter2.id()));\n        assertFalse(counter1.isClosed());\n        assertFalse(counter2.isClosed());\n    }\n\n    private void createReadableCounter(final CountersReader counters, final long registrationId, final int counterId)\n    {\n        if (COUNTER_TYPE_ID == counters.getCounterTypeId(counterId))\n        {\n            readableCounter = new ReadableCounter(counters, registrationId, counterId);\n        }\n    }\n\n    private void unavailableCounterHandler(\n        final CountersReader counters, final long registrationId, final int counterId)\n    {\n        if (null != readableCounter && readableCounter.registrationId() == registrationId)\n        {\n            readableCounter.close();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/DataLossAndRecoverySystemTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.ReceiverNaksSent;\nimport io.aeron.driver.status.SenderNaksReceived;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\nimport java.util.Random;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.hamcrest.Matchers.oneOf;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\npublic class DataLossAndRecoverySystemTest\n{\n    private static final int LOSS_LENGTH = 100_000;\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n        .threadingMode(ThreadingMode.SHARED);\n    private TestMediaDriver driver;\n\n    @BeforeEach\n    void setUp()\n    {\n        TestMediaDriver.enableFixedLoss(context, 5, 102, LOSS_LENGTH);\n    }\n\n    private void launch(final MediaDriver.Context context)\n    {\n        driver = TestMediaDriver.launch(context, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietClose(driver);\n    }\n\n    @Test\n    void shouldSendStreamOfDataAndHandleLargeGapWithingSingleNakAndRetransmit()\n    {\n        launch(context);\n\n        final StreamNakCounters streamCounters = sendAndReceive(\n            \"aeron:udp?endpoint=localhost:10000|term-length=1m|init-term-id=0|term-id=0|term-offset=0\",\n            10 * 1024 * 1024);\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final CountersReader countersReader = aeron.countersReader();\n            final long retransmitCount = countersReader\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITS_SENT.id());\n            final long nakSent = countersReader\n                .getCounterValue(SystemCounterDescriptor.NAK_MESSAGES_SENT.id());\n            final long nakReceived = countersReader\n                .getCounterValue(SystemCounterDescriptor.NAK_MESSAGES_RECEIVED.id());\n            final long retransmittedBytes = countersReader\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITTED_BYTES.id());\n            assertThat(nakSent, equalTo(streamCounters.naksSent));\n            assertThat(nakReceived, equalTo(streamCounters.naksReceived));\n            assertThat(retransmitCount, lessThanOrEqualTo(nakSent));\n            assertThat(retransmittedBytes, greaterThanOrEqualTo((long)LOSS_LENGTH));\n        }\n    }\n\n    @Test\n    void shouldConfigureNakDelayPerStream()\n    {\n        dontCoalesceNaksOnReceiverByDefault();\n        launch(context);\n\n        final StreamNakCounters streamCounters = sendAndReceive(\n            \"aeron:udp?endpoint=localhost:10000|term-length=1m|init-term-id=0|term-id=0|term-offset=0|nak-delay=100us\",\n            10 * 1024 * 1024);\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final CountersReader countersReader = aeron.countersReader();\n            final long retransmitCount = countersReader\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITS_SENT.id());\n            final long nakSent = countersReader\n                .getCounterValue(SystemCounterDescriptor.NAK_MESSAGES_SENT.id());\n            final long nakReceived = countersReader\n                .getCounterValue(SystemCounterDescriptor.NAK_MESSAGES_RECEIVED.id());\n            final long retransmittedBytes = countersReader\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITTED_BYTES.id());\n            assertThat(nakSent, equalTo(streamCounters.naksSent));\n            assertThat(nakReceived, equalTo(streamCounters.naksReceived));\n            assertThat(retransmitCount, lessThanOrEqualTo(nakSent));\n            assertThat(retransmittedBytes, greaterThanOrEqualTo((long)LOSS_LENGTH));\n        }\n    }\n\n    @Test\n    void shouldSendStreamOfDataAndHandleLargeGapWithSingleRetransmitEvenIfNakingFrequently()\n    {\n        dontCoalesceNaksOnReceiverByDefault();\n        launch(context);\n\n        final StreamNakCounters streamCounters = sendAndReceive(\n            \"aeron:udp?endpoint=localhost:10000|term-length=1m|init-term-id=0|term-id=0|term-offset=0\",\n            10 * 1024 * 1024);\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final CountersReader countersReader = aeron.countersReader();\n            final long retransmitCount = countersReader\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITS_SENT.id());\n            final long nakSent = countersReader\n                .getCounterValue(SystemCounterDescriptor.NAK_MESSAGES_SENT.id());\n            final long nakReceived = countersReader\n                .getCounterValue(SystemCounterDescriptor.NAK_MESSAGES_RECEIVED.id());\n            final long retransmittedBytes = countersReader\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITTED_BYTES.id());\n            assertThat(nakSent, equalTo(streamCounters.naksSent));\n            assertThat(nakReceived, equalTo(streamCounters.naksReceived));\n            assertThat(retransmitCount, lessThanOrEqualTo(nakSent));\n            assertThat(retransmittedBytes, greaterThanOrEqualTo((long)LOSS_LENGTH));\n        }\n    }\n\n    @Test\n    @Disabled\n    void shouldRetransmitForAllMdcSubscribers()\n    {\n        dontCoalesceNaksOnReceiverByDefault();\n        final String baseDir = CommonContext.getAeronDirectoryName() + File.separator;\n\n        final MediaDriver.Context contextA = context.aeronDirectoryName(baseDir + \"driver-a\");\n\n        final MediaDriver.Context contextB = new MediaDriver.Context()\n            .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .threadingMode(ThreadingMode.SHARED)\n            .aeronDirectoryName(baseDir + \"driver-b\");\n\n        final int streamId = 10000;\n        final byte[] input = new byte[10 * 1024 * 1024];\n        final byte[] outputA = new byte[10 * 1024 * 1024];\n        final byte[] outputB = new byte[10 * 1024 * 1024];\n        final Random r = new Random(1);\n        r.nextBytes(input);\n        final UnsafeBuffer sendBuffer = new UnsafeBuffer();\n\n        int inputPosition = 0;\n        final MutableInteger outputPositionA = new MutableInteger(0);\n        final MutableInteger outputPositionB = new MutableInteger(0);\n\n        final String termUriParameters = \"term-length=1m|init-term-id=0|term-id=0|term-offset=0\";\n\n        try (\n            TestMediaDriver driverA = TestMediaDriver.launch(contextA, watcher);\n            TestMediaDriver driverB = TestMediaDriver.launch(contextB, watcher);\n            Aeron aeronA = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverA.aeronDirectoryName()));\n            Aeron aeronB = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverB.aeronDirectoryName()));\n            ExclusivePublication pub = aeronA.addExclusivePublication(\n                \"aeron:udp?control-mode=dynamic|control=localhost:10000|\" + termUriParameters, streamId);\n            Subscription subA = aeronA.addSubscription(\n                \"aeron:udp?endpoint=localhost:10001|control-mode=dynamic|control=localhost:10000\", streamId);\n            Subscription subB = aeronB.addSubscription(\n                \"aeron:udp?endpoint=localhost:10002|control-mode=dynamic|control=localhost:10000\", streamId))\n        {\n            watcher.dataCollector().add(driverA.context().aeronDirectory());\n            watcher.dataCollector().add(driverB.context().aeronDirectory());\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(subA);\n            Tests.awaitConnected(subB);\n\n            final FragmentAssembler handlerA = new FragmentAssembler(\n                (buffer, offset, length, header) ->\n                {\n                    buffer.getBytes(offset, outputA, outputPositionA.get(), length);\n                    outputPositionA.addAndGet(length);\n                });\n\n            final FragmentAssembler handlerB = new FragmentAssembler(\n                (buffer, offset, length, header) ->\n                {\n                    buffer.getBytes(offset, outputB, outputPositionB.get(), length);\n                    outputPositionB.addAndGet(length);\n                });\n\n            while (inputPosition < input.length ||\n                outputPositionA.get() < outputA.length ||\n                outputPositionB.get() < outputB.length)\n            {\n                if (inputPosition < input.length)\n                {\n                    final int length = Math.min(input.length - inputPosition, pub.maxMessageLength());\n                    sendBuffer.wrap(input, inputPosition, length);\n                    if (0 < pub.offer(sendBuffer))\n                    {\n                        inputPosition += length;\n                    }\n                }\n\n                if (outputPositionA.get() < outputA.length)\n                {\n                    subA.poll(handlerA, 10);\n                }\n\n                if (outputPositionB.get() < outputB.length)\n                {\n                    subB.poll(handlerB, 10);\n                }\n            }\n\n            assertArrayEquals(input, outputA);\n            assertArrayEquals(input, outputB);\n\n            final long retransmitCount = aeronA.countersReader()\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITS_SENT.id());\n            final long nakCount = aeronA.countersReader()\n                .getCounterValue(SystemCounterDescriptor.NAK_MESSAGES_SENT.id());\n            final long retransmittedBytes = aeronA.countersReader()\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITTED_BYTES.id());\n            assertThat(nakCount, greaterThanOrEqualTo(1L));\n            // in CI, we occasionally see an extra retransmission\n            assertThat(retransmitCount, oneOf(1L, 2L));\n            // MDC retransmits to each subscriber\n            assertThat(retransmittedBytes, greaterThanOrEqualTo(LOSS_LENGTH * 2L));\n        }\n    }\n\n    @Test\n    @Disabled\n    void shouldIncludeRetransmittedBytesInTotalBytesSent()\n    {\n        final int lossLength = 1024 * 1024 - 512;\n\n        TestMediaDriver.enableFixedLoss(context, 0, 512, lossLength);\n\n        launch(context);\n\n        sendAndReceive(\n            \"aeron:udp?endpoint=localhost:10000|term-length=1m|init-term-id=0|term-id=0|term-offset=0\",\n            1024 * 1024);\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final long retransmittedBytes = aeron.countersReader()\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITTED_BYTES.id());\n            final long totalBytes = aeron.countersReader()\n                .getCounterValue(SystemCounterDescriptor.BYTES_SENT.id());\n            assertThat(totalBytes, greaterThanOrEqualTo(512 + 2 * retransmittedBytes));\n            assertThat(retransmittedBytes, greaterThanOrEqualTo((long)lossLength));\n        }\n    }\n\n    private void dontCoalesceNaksOnReceiverByDefault()\n    {\n        TestMediaDriver.dontCoalesceNaksOnReceiverByDefault(context);\n    }\n\n    private StreamNakCounters sendAndReceive(final String channel, final int publicationLength)\n    {\n        final int streamId = 10000;\n        final byte[] input = new byte[publicationLength];\n        final byte[] output = new byte[publicationLength];\n        final Random r = new Random(1);\n        r.nextBytes(input);\n        final UnsafeBuffer sendBuffer = new UnsafeBuffer();\n\n        int inputPosition = 0;\n        final MutableInteger outputPosition = new MutableInteger(0);\n\n        final StreamNakCounters counters;\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            ExclusivePublication pub = aeron.addExclusivePublication(channel, streamId);\n            Subscription sub = aeron.addSubscription(channel, streamId))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            final FragmentAssembler handler = new FragmentAssembler(\n                (buffer, offset, length, header) ->\n                {\n                    buffer.getBytes(offset, output, outputPosition.get(), length);\n                    outputPosition.addAndGet(length);\n                });\n\n            while (inputPosition < input.length || outputPosition.get() < output.length)\n            {\n                if (inputPosition < input.length)\n                {\n                    final int length = Math.min(input.length - inputPosition, pub.maxMessageLength());\n                    sendBuffer.wrap(input, inputPosition, length);\n                    if (0 < pub.offer(sendBuffer))\n                    {\n                        inputPosition += length;\n                    }\n                }\n\n                if (outputPosition.get() < output.length)\n                {\n                    sub.poll(handler, 10);\n                }\n            }\n\n            final CountersReader countersReader = aeron.countersReader();\n            final int receiverNaksSentCounterId = countersReader.findByTypeIdAndRegistrationId(\n                ReceiverNaksSent.TYPE_ID, sub.imageAtIndex(0).correlationId());\n            assertNotEquals(CountersReader.NULL_COUNTER_ID, receiverNaksSentCounterId);\n            final int senderNaksReceivedCounterId =\n                countersReader.findByTypeIdAndRegistrationId(SenderNaksReceived.TYPE_ID, pub.registrationId());\n            assertNotEquals(CountersReader.NULL_COUNTER_ID, senderNaksReceivedCounterId);\n\n            counters = new StreamNakCounters(\n                countersReader.getCounterValue(receiverNaksSentCounterId),\n                countersReader.getCounterValue(senderNaksReceivedCounterId));\n            assertThat(counters.naksSent, greaterThanOrEqualTo(1L));\n            assertThat(counters.naksReceived, greaterThanOrEqualTo(1L));\n        }\n\n        assertArrayEquals(input, output);\n\n        return counters;\n    }\n\n    private record StreamNakCounters(long naksSent, long naksReceived)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ErrorHandlerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.hamcrest.Matcher;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.hamcrest.CoreMatchers.allOf;\nimport static org.hamcrest.CoreMatchers.containsString;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ErrorHandlerTest\n{\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .errorHandler((ignore) -> {})\n        .dirDeleteOnStart(true)\n        .publicationConnectionTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500))\n        .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100));\n\n    private final ArrayList<AutoCloseable> closeables = new ArrayList<>();\n\n    private Aeron aeron;\n    private TestMediaDriver driver;\n\n    private void launch()\n    {\n        driver = TestMediaDriver.launch(context, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n        watcher.ignoreErrorsMatching((s) -> true);\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(closeables);\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldReportToErrorHandlerAndDistinctErrorLog() throws IOException\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"C driver doesn't support ErrorHandler callbacks\");\n\n        final AtomicReference<Throwable> throwableRef = new AtomicReference<>(null);\n        context.errorHandler(throwableRef::set);\n\n        launch();\n\n        final long initialErrorCount = aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERRORS.id());\n\n        addPublication(\"aeron:udp?endpoint=localhost:9999|mtu=1408\", 1000);\n        addSubscription(\"aeron:udp?endpoint=localhost:9999|rcv-wnd=1376\", 1000);\n\n        Tests.awaitCounterDelta(aeron.countersReader(), SystemCounterDescriptor.ERRORS.id(), initialErrorCount, 1);\n\n        final Matcher<String> exceptionMessageMatcher = allOf(\n            containsString(\"mtuLength=\"),\n            containsString(\"> initialWindowLength=\"));\n\n        Tests.await(() -> null != throwableRef.get());\n        SystemTests.waitForErrorToOccur(driver.aeronDirectoryName(), exceptionMessageMatcher, Tests.SLEEP_1_MS);\n    }\n\n    private void addPublication(final String channel, final int streamId)\n    {\n        final Publication pub = aeron.addPublication(channel, streamId);\n        closeables.add(pub);\n    }\n\n    private void addSubscription(final String channel, final int streamId)\n    {\n        final Subscription sub = aeron.addSubscription(channel, streamId);\n        closeables.add(sub);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ExclusivePublicationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.RawBlockHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport static io.aeron.Publication.BACK_PRESSURED;\nimport static io.aeron.Publication.CLOSED;\nimport static io.aeron.logbuffer.FrameDescriptor.*;\nimport static io.aeron.protocol.DataHeaderFlyweight.*;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.util.Arrays.asList;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith(InterruptingTestCallback.class)\n@SuppressWarnings(\"try\")\nclass ExclusivePublicationTest\n{\n    private static List<String> channels()\n    {\n        return asList(\n            \"aeron:udp?endpoint=224.20.30.39:24323|interface=localhost|term-length=64k\",\n            \"aeron:udp?endpoint=localhost:24325|term-length=64k\",\n            \"aeron:ipc?term-length=64k\");\n    }\n\n    private static final int STREAM_ID = 1007;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n    private static final int MESSAGE_LENGTH = 200;\n\n    private final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[65 * 1024]);\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context driverContext = new MediaDriver.Context()\n        .errorHandler(Tests::onError)\n        .sharedIdleStrategy(YieldingIdleStrategy.INSTANCE)\n        .threadingMode(ThreadingMode.SHARED);\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp(final @TempDir Path tempDir)\n    {\n        driver = TestMediaDriver.launch(\n            driverContext.aeronDirectoryName(tempDir.resolve(\"driver\").toString()), testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldPublishFromIndependentExclusivePublications(final String channel)\n    {\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publicationOne = aeron.addExclusivePublication(channel, STREAM_ID);\n            ExclusivePublication publicationTwo = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            final int expectedNumberOfFragments = 778;\n            int totalFragmentsRead = 0;\n            final MutableInteger messageCount = new MutableInteger();\n            final FragmentHandler fragmentHandler =\n                (buffer, offset, length, header) ->\n                {\n                    assertEquals(MESSAGE_LENGTH, length);\n                    messageCount.value++;\n                };\n\n            Tests.awaitConnections(subscription, 2);\n\n            for (int i = 0; i < expectedNumberOfFragments; i += 2)\n            {\n                while (publicationOne.offer(srcBuffer, 0, MESSAGE_LENGTH) < 0L)\n                {\n                    Tests.yield();\n                    totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n                }\n\n                while (publicationTwo.offer(srcBuffer, 0, MESSAGE_LENGTH) < 0L)\n                {\n                    Tests.yield();\n                    totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n                }\n\n                totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n            }\n\n            do\n            {\n                totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n            }\n            while (totalFragmentsRead < expectedNumberOfFragments);\n\n            assertEquals(expectedNumberOfFragments, messageCount.value);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldPublishFromConcurrentExclusivePublications(final String channel)\n    {\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publicationOne = aeron.addExclusivePublication(channel, STREAM_ID);\n            ExclusivePublication publicationTwo = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            final int expectedNumberOfFragments = 20_000;\n            final int fragmentsPerThread = expectedNumberOfFragments / 2;\n            final MutableInteger messageCount = new MutableInteger();\n            final FragmentHandler fragmentHandler =\n                (buffer, offset, length, header) ->\n                {\n                    assertEquals(MESSAGE_LENGTH, length);\n                    messageCount.value++;\n                };\n\n            Tests.awaitConnections(subscription, 2);\n\n            final ExecutorService threadPool = Executors.newFixedThreadPool(2);\n            try\n            {\n                final CountDownLatch latch = new CountDownLatch(2);\n                threadPool.submit(\n                    () ->\n                    {\n                        latch.countDown();\n                        latch.await();\n                        for (int count = 0; count < fragmentsPerThread; count++)\n                        {\n                            while (publicationOne.offer(srcBuffer, 0, MESSAGE_LENGTH) < 0L)\n                            {\n                                Tests.yield();\n                            }\n                        }\n                        return null;\n                    });\n                threadPool.submit(\n                    () ->\n                    {\n                        latch.countDown();\n                        latch.await();\n                        for (int count = 0; count < fragmentsPerThread; count++)\n                        {\n                            while (publicationTwo.offer(srcBuffer, 0, MESSAGE_LENGTH) < 0L)\n                            {\n                                Tests.yield();\n                            }\n                        }\n                        return null;\n                    });\n\n                int totalFragmentsRead = 0;\n                do\n                {\n                    totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n                }\n                while (totalFragmentsRead < expectedNumberOfFragments);\n            }\n            finally\n            {\n                threadPool.shutdownNow();\n            }\n\n            assertEquals(expectedNumberOfFragments, messageCount.value);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldOfferTwoBuffersFromIndependentExclusivePublications(final String channel)\n    {\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publicationOne = aeron.addExclusivePublication(channel, STREAM_ID);\n            ExclusivePublication publicationTwo = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            final int expectedNumberOfFragments = 778;\n            int totalFragmentsRead = 0;\n            final MutableInteger messageCount = new MutableInteger();\n            final FragmentHandler fragmentHandler =\n                (buffer, offset, length, header) ->\n                {\n                    assertEquals(MESSAGE_LENGTH + SIZE_OF_INT, length);\n                    final int publisherId = buffer.getInt(offset);\n                    if (1 == publisherId)\n                    {\n                        assertEquals(Byte.MIN_VALUE, buffer.getByte(offset + SIZE_OF_INT));\n                    }\n                    else if (2 == publisherId)\n                    {\n                        assertEquals(Byte.MAX_VALUE, buffer.getByte(offset + SIZE_OF_INT));\n                    }\n                    else\n                    {\n                        fail(\"unknown publisherId=\" + publisherId);\n                    }\n                    messageCount.value++;\n                };\n\n            Tests.awaitConnections(subscription, 2);\n\n            final UnsafeBuffer pubOneHeader = new UnsafeBuffer(new byte[SIZE_OF_INT]);\n            pubOneHeader.putInt(0, 1);\n            final UnsafeBuffer pubOnePayload = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n            pubOnePayload.setMemory(0, MESSAGE_LENGTH, Byte.MIN_VALUE);\n            final UnsafeBuffer pubTwoHeader = new UnsafeBuffer(new byte[SIZE_OF_INT]);\n            pubTwoHeader.putInt(0, 2);\n            final UnsafeBuffer pubTwoPayload = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n            pubTwoPayload.setMemory(0, MESSAGE_LENGTH, Byte.MAX_VALUE);\n\n            for (int i = 0; i < expectedNumberOfFragments; i += 2)\n            {\n                while (publicationOne.offer(pubOneHeader, 0, SIZE_OF_INT, pubOnePayload, 0, MESSAGE_LENGTH) < 0L)\n                {\n                    Tests.yield();\n                    totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n                }\n\n                while (publicationTwo.offer(pubTwoHeader, 0, SIZE_OF_INT, pubTwoPayload, 0, MESSAGE_LENGTH) < 0L)\n                {\n                    Tests.yield();\n                    totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n                }\n\n                totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n            }\n\n            do\n            {\n                totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n            }\n            while (totalFragmentsRead < expectedNumberOfFragments);\n\n            assertEquals(expectedNumberOfFragments, messageCount.value);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldOfferTwoBuffersFromConcurrentExclusivePublications(final String channel)\n    {\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publicationOne = aeron.addExclusivePublication(channel, STREAM_ID);\n            ExclusivePublication publicationTwo = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            final int expectedNumberOfFragments = 20_000;\n            final int fragmentsPerThread = expectedNumberOfFragments / 2;\n            final MutableInteger messageCount = new MutableInteger();\n            final FragmentHandler fragmentHandler =\n                (buffer, offset, length, header) ->\n                {\n                    assertEquals(MESSAGE_LENGTH + SIZE_OF_INT, length);\n                    final int publisherId = buffer.getInt(offset);\n                    if (1 == publisherId)\n                    {\n                        assertEquals(Byte.MIN_VALUE, buffer.getByte(offset + SIZE_OF_INT));\n                    }\n                    else if (2 == publisherId)\n                    {\n                        assertEquals(Byte.MAX_VALUE, buffer.getByte(offset + SIZE_OF_INT));\n                    }\n                    else\n                    {\n                        fail(\"unknown publisherId=\" + publisherId);\n                    }\n                    messageCount.value++;\n                };\n\n            Tests.awaitConnections(subscription, 2);\n\n            final UnsafeBuffer pubOneHeader = new UnsafeBuffer(new byte[SIZE_OF_INT]);\n            pubOneHeader.putInt(0, 1);\n            final UnsafeBuffer pubOnePayload = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n            pubOnePayload.setMemory(0, MESSAGE_LENGTH, Byte.MIN_VALUE);\n            final UnsafeBuffer pubTwoHeader = new UnsafeBuffer(new byte[SIZE_OF_INT]);\n            pubTwoHeader.putInt(0, 2);\n            final UnsafeBuffer pubTwoPayload = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n            pubTwoPayload.setMemory(0, MESSAGE_LENGTH, Byte.MAX_VALUE);\n\n            final ExecutorService threadPool = Executors.newFixedThreadPool(2);\n            try\n            {\n                final CountDownLatch latch = new CountDownLatch(2);\n                threadPool.submit(\n                    () ->\n                    {\n                        latch.countDown();\n                        latch.await();\n                        for (int count = 0; count < fragmentsPerThread; count++)\n                        {\n                            while (publicationOne\n                                .offer(pubOneHeader, 0, SIZE_OF_INT, pubOnePayload, 0, MESSAGE_LENGTH) < 0L)\n                            {\n                                Tests.yield();\n                            }\n                        }\n                        return null;\n                    });\n                threadPool.submit(\n                    () ->\n                    {\n                        latch.countDown();\n                        latch.await();\n                        for (int count = 0; count < fragmentsPerThread; count++)\n                        {\n                            while (publicationTwo\n                                .offer(pubTwoHeader, 0, SIZE_OF_INT, pubTwoPayload, 0, MESSAGE_LENGTH) < 0L)\n                            {\n                                Tests.yield();\n                            }\n                        }\n                        return null;\n                    });\n                threadPool.shutdown();\n\n                int totalFragmentsRead = 0;\n                do\n                {\n                    totalFragmentsRead += pollFragments(subscription, fragmentHandler);\n                }\n                while (totalFragmentsRead < expectedNumberOfFragments);\n            }\n            finally\n            {\n                threadPool.shutdownNow();\n            }\n\n            assertEquals(expectedNumberOfFragments, messageCount.value);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void offerBlockThrowsIllegalArgumentExceptionIfLengthExceedsAvailableSpaceWithinTheTerm()\n    {\n        final String channel = \"aeron:ipc?term-length=64k\";\n        try (Subscription ignore = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            while (publication.offer(srcBuffer, 0, MESSAGE_LENGTH) < 0L)\n            {\n                Tests.yield();\n            }\n\n            final int termBufferLength = publication.termBufferLength();\n            final int termOffset = publication.termOffset();\n            final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,\n                () -> publication.offerBlock(srcBuffer, 0, termBufferLength));\n\n            assertEquals(\"invalid block length \" + termBufferLength +\n                \", remaining space in term is \" + (termBufferLength - termOffset), exception.getMessage());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void offerBlockReturnsClosedWhenPublicationIsClosed()\n    {\n        final String channel = \"aeron:ipc?term-length=64k\";\n        try (Subscription ignore = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            publication.close();\n\n            final long result = publication.offerBlock(srcBuffer, 0, MESSAGE_LENGTH);\n            assertEquals(CLOSED, result);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void offerBlockThrowsIllegalArgumentExceptionUponInvalidFirstFrame()\n    {\n        final String channel = \"aeron:ipc?term-length=64k\";\n        try (Subscription ignore = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            final int sessionId = publication.sessionId();\n            final int streamId = publication.streamId();\n            final int termId = publication.termId();\n            final int offset = 128;\n\n            frameType(srcBuffer, offset, HDR_TYPE_NAK);\n            frameSessionId(srcBuffer, offset, -19);\n            srcBuffer.putInt(offset + STREAM_ID_FIELD_OFFSET, 42, LITTLE_ENDIAN);\n\n            while (publication.availableWindow() <= 0)\n            {\n                Tests.yield();\n            }\n\n            final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,\n                () -> publication.offerBlock(srcBuffer, offset, 1000));\n\n            assertEquals(\"improperly formatted block:\" +\n                \" termOffset=0\" + \" (expected=0),\" +\n                \" sessionId=-19\" + \" (expected=\" + sessionId + \"),\" +\n                \" streamId=42 (expected=\" + streamId + \"),\" +\n                \" termId=0 (expected=\" + termId + \"),\" +\n                \" frameType=\" + HDR_TYPE_NAK + \" (expected=\" + HDR_TYPE_DATA + \")\",\n                ex.getMessage());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void offerBlockAcceptsUpToAnEntireTermOfData()\n    {\n        final String channel = \"aeron:ipc?term-length=64k\";\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            final int sessionId = publication.sessionId();\n            final int streamId = publication.streamId();\n            final int currentTermId = publication.termId();\n            final int termBufferLength = publication.termBufferLength();\n            final int offset = 1024;\n\n            final RawBlockHandler rawBlockHandler =\n                (fileChannel, fileOffset, termBuffer, termOffset, length, pollSessionId, termId) ->\n                {\n                    assertEquals(HDR_TYPE_DATA, frameType(termBuffer, termOffset));\n                    assertEquals(termBufferLength, frameLength(termBuffer, termOffset));\n                    assertEquals(sessionId, frameSessionId(termBuffer, termOffset));\n                    assertEquals(streamId, termBuffer.getInt(termOffset + STREAM_ID_FIELD_OFFSET, LITTLE_ENDIAN));\n                };\n\n            frameType(srcBuffer, offset, HDR_TYPE_DATA);\n            frameLengthOrdered(srcBuffer, offset, termBufferLength);\n            frameSessionId(srcBuffer, offset, sessionId);\n            srcBuffer.putInt(offset + STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n            srcBuffer.putInt(offset + TERM_ID_FIELD_OFFSET, currentTermId, LITTLE_ENDIAN);\n            srcBuffer.setMemory(offset + DATA_OFFSET, termBufferLength - HEADER_LENGTH, (byte)13);\n\n            Tests.awaitConnections(subscription, 1);\n            while (publication.availableWindow() <= 0)\n            {\n                Tests.yield();\n            }\n\n            final long position = publication.position();\n            final long result = publication.offerBlock(srcBuffer, offset, termBufferLength);\n            assertEquals(position + termBufferLength, result);\n\n            final long pollBytes = subscription.rawPoll(rawBlockHandler, termBufferLength);\n\n            assertEquals(termBufferLength, pollBytes);\n            assertEquals(publication.termBufferLength(), publication.termOffset());\n            assertEquals(currentTermId, publication.termId());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void offerBlockReturnsBackPressuredStatus()\n    {\n        final String channel = \"aeron:ipc?term-length=64k\";\n        try (Subscription ignore = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            final int length = publication.termBufferLength() / 2;\n            final int sessionId = publication.sessionId();\n            final int streamId = publication.streamId();\n            final int termId = publication.termId();\n            final int dataLength = length - HEADER_LENGTH;\n            final int offset = 2048;\n\n            frameType(srcBuffer, offset, HDR_TYPE_DATA);\n            frameLengthOrdered(srcBuffer, offset, dataLength);\n            frameSessionId(srcBuffer, offset, sessionId);\n            srcBuffer.putInt(offset + STREAM_ID_FIELD_OFFSET, streamId, LITTLE_ENDIAN);\n            srcBuffer.putInt(offset + TERM_ID_FIELD_OFFSET, termId, LITTLE_ENDIAN);\n            srcBuffer.setMemory(offset + DATA_OFFSET, dataLength, (byte)6);\n\n            while (publication.offerBlock(srcBuffer, offset, length) < 0)\n            {\n                Tests.yield();\n            }\n\n            final long result = publication.offerBlock(srcBuffer, 0, offset);\n            assertEquals(BACK_PRESSURED, result);\n        }\n    }\n\n    private int pollFragments(final Subscription subscription, final FragmentHandler fragmentHandler)\n    {\n        final int fragmentsRead = subscription.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n        if (0 == fragmentsRead)\n        {\n            Tests.yield();\n        }\n\n        return fragmentsRead;\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/FlowControlTests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.test.Tests;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static io.aeron.driver.status.SystemCounterDescriptor.STATUS_MESSAGES_RECEIVED;\nimport static org.agrona.concurrent.status.CountersReader.*;\n\nclass FlowControlTests\n{\n    static void awaitConnectionAndStatusMessages(\n        final CountersReader countersReader, final Subscription subscription, final Subscription... subscriptions)\n    {\n        while (!subscription.isConnected())\n        {\n            Tests.sleep(1);\n        }\n\n        for (final Subscription sub : subscriptions)\n        {\n            while (!sub.isConnected())\n            {\n                Tests.sleep(1);\n            }\n        }\n\n        final long delta = 1 + subscriptions.length;\n        Tests.awaitCounterDelta(countersReader, STATUS_MESSAGES_RECEIVED.id(), delta);\n    }\n\n    static int findCounterIdByRegistrationId(\n        final CountersReader countersReader, final int counterTypeId, final long registrationId)\n    {\n        final DirectBuffer buffer = countersReader.metaDataBuffer();\n\n        for (int counterId = 0, maxId = countersReader.maxCounterId(); counterId <= maxId; counterId++)\n        {\n            if (countersReader.getCounterState(counterId) == RECORD_ALLOCATED &&\n                countersReader.getCounterTypeId(counterId) == counterTypeId)\n            {\n                final int recordOffset = CountersReader.metaDataOffset(counterId);\n                if (buffer.getLong(recordOffset + KEY_OFFSET) == registrationId)\n                {\n                    return counterId;\n                }\n            }\n        }\n\n        return NULL_COUNTER_ID;\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/FragmentedMessageTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.ArgumentCaptor;\n\nimport java.util.List;\n\nimport static io.aeron.logbuffer.FrameDescriptor.UNFRAGMENTED;\nimport static java.util.Arrays.asList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass FragmentedMessageTest\n{\n    private static List<String> channels()\n    {\n        return asList(\n            CommonContext.IPC_CHANNEL,\n            \"aeron:udp?endpoint=localhost:24325\",\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\");\n    }\n\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private final FragmentHandler mockFragmentHandler = mock(FragmentHandler.class);\n\n    private final MediaDriver.Context driverContext = new MediaDriver.Context()\n        .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n        .errorHandler(Tests::onError)\n        .threadingMode(ThreadingMode.SHARED);\n\n    private TestMediaDriver driver;\n\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(driverContext, testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldReceivePublishedMessage(final String channel)\n    {\n        final FragmentAssembler assembler = new FragmentAssembler(mockFragmentHandler);\n\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            Publication publication = aeron.addPublication(channel, STREAM_ID))\n        {\n            final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[driver.context().mtuLength() * 4]);\n            final int offset = 0;\n            final int length = srcBuffer.capacity() / 4;\n\n            for (int i = 0; i < 4; i++)\n            {\n                srcBuffer.setMemory(i * length, length, (byte)(65 + i));\n            }\n\n            while (publication.offer(srcBuffer, offset, srcBuffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            final int expectedFragmentsBecauseOfHeader = 5;\n            int fragmentCount = 0;\n            do\n            {\n                final int fragments = subscription.poll(assembler, FRAGMENT_COUNT_LIMIT);\n                if (0 == fragments)\n                {\n                    Tests.yield();\n                }\n                fragmentCount += fragments;\n            }\n            while (fragmentCount < expectedFragmentsBecauseOfHeader);\n\n            final ArgumentCaptor<DirectBuffer> bufferArg = ArgumentCaptor.forClass(DirectBuffer.class);\n            final ArgumentCaptor<Header> headerArg = ArgumentCaptor.forClass(Header.class);\n\n            verify(mockFragmentHandler, times(1)).onFragment(\n                bufferArg.capture(), eq(offset), eq(srcBuffer.capacity()), headerArg.capture());\n\n            final DirectBuffer capturedBuffer = bufferArg.getValue();\n            for (int i = 0; i < srcBuffer.capacity(); i++)\n            {\n                assertEquals(srcBuffer.getByte(i), capturedBuffer.getByte(i), \"same at i=\" + i);\n            }\n\n            assertEquals(UNFRAGMENTED, headerArg.getValue().flags());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/GapFillLossTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.ext.DebugChannelEndpointConfiguration;\nimport io.aeron.driver.ext.DebugSendChannelEndpoint;\nimport io.aeron.driver.ext.LossGenerator;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.nio.ByteBuffer;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static io.aeron.SystemTests.verifyLossOccurredForStream;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThan;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass GapFillLossTest\n{\n    private static final String CHANNEL = \"aeron:udp?endpoint=localhost:24325\";\n    private static final String UNRELIABLE_CHANNEL =\n        CHANNEL + \"|\" + CommonContext.RELIABLE_STREAM_PARAM_NAME + \"=false\";\n\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n    private static final int MSG_LENGTH = 1024;\n    private static final int NUM_MESSAGES = 10_000;\n\n    private static final AtomicLong FINAL_POSITION = new AtomicLong(Long.MAX_VALUE);\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(10)\n    void shouldGapFillWhenLossOccurs() throws Exception\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(MSG_LENGTH));\n        srcBuffer.setMemory(0, MSG_LENGTH, (byte)7);\n\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .threadingMode(ThreadingMode.SHARED)\n            .dirDeleteOnStart(true)\n            .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH);\n\n        final LossGenerator noLossGenerator =\n            DebugChannelEndpointConfiguration.lossGeneratorSupplier(0, 0);\n\n        ctx.sendChannelEndpointSupplier((udpChannel, statusIndicator, context) -> new DebugSendChannelEndpoint(\n            udpChannel, statusIndicator, context, noLossGenerator, noLossGenerator));\n\n        TestMediaDriver.enableRandomLoss(ctx, 0.20, 0xcafebabeL, true, false);\n\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(ctx, watcher))\n        {\n            watcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n\n            try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n                Subscription subscription = aeron.addSubscription(UNRELIABLE_CHANNEL, STREAM_ID);\n                Publication publication = aeron.addPublication(CHANNEL, STREAM_ID))\n            {\n                watcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n\n                final Subscriber subscriber = new Subscriber(subscription);\n                final Thread subscriberThread = new Thread(subscriber);\n                subscriberThread.setDaemon(true);\n                subscriberThread.start();\n\n                long position = 0;\n                for (int i = 0; i < NUM_MESSAGES; i++)\n                {\n                    srcBuffer.putLong(0, i);\n\n                    while ((position = publication.offer(srcBuffer)) < 0L)\n                    {\n                        Tests.yield();\n                    }\n                }\n\n                FINAL_POSITION.set(position);\n                subscriberThread.join();\n\n                verifyLossOccurredForStream(ctx.aeronDirectoryName(), STREAM_ID);\n                assertThat(subscriber.messageCount, lessThan(NUM_MESSAGES));\n            }\n        }\n    }\n\n    static class Subscriber implements Runnable, FragmentHandler\n    {\n        private final Subscription subscription;\n        int messageCount = 0;\n\n        Subscriber(final Subscription subscription)\n        {\n            this.subscription = subscription;\n        }\n\n        public void run()\n        {\n            Tests.awaitConnected(subscription);\n\n            final Image image = subscription.imageAtIndex(0);\n\n            while (image.position() < FINAL_POSITION.get())\n            {\n                final int fragments = subscription.poll(this, FRAGMENT_COUNT_LIMIT);\n                if (0 == fragments)\n                {\n                    if (subscription.isClosed())\n                    {\n                        return;\n                    }\n                    Tests.yield();\n                }\n            }\n        }\n\n        public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n        {\n            messageCount++;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ImageAvailabilityTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static java.util.Arrays.asList;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ImageAvailabilityTest\n{\n    private static List<String> channels()\n    {\n        return asList(\n            \"aeron:ipc?term-length=64k\",\n            \"aeron:udp?endpoint=localhost:24325|term-length=64k\",\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\");\n    }\n\n    private static final int STREAM_ID = 1001;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .dirDeleteOnStart(true)\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(20))\n            .threadingMode(ThreadingMode.SHARED), testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect(new Aeron.Context()\n            .useConductorAgentInvoker(true)\n            .errorHandler(Tests::onError));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    @SuppressWarnings(\"try\")\n    void shouldCallImageHandlers(final String channel)\n    {\n        final AtomicInteger unavailableImageCount = new AtomicInteger();\n        final AtomicInteger availableImageCount = new AtomicInteger();\n        final UnavailableImageHandler unavailableHandler = (image) -> unavailableImageCount.incrementAndGet();\n        final AvailableImageHandler availableHandler = (image) -> availableImageCount.incrementAndGet();\n\n        final String spyChannel = channel.contains(\"ipc\") ? channel : CommonContext.SPY_PREFIX + channel;\n\n        try (Subscription subOne = aeron.addSubscription(channel, STREAM_ID, availableHandler, unavailableHandler);\n            Subscription subTwo = aeron.addSubscription(\n                spyChannel, STREAM_ID, availableHandler, unavailableHandler);\n            Publication publication = aeron.addPublication(channel, STREAM_ID))\n        {\n            while (!subOne.isConnected() || !subTwo.isConnected() || !publication.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            final Image image = subOne.imageAtIndex(0);\n            final Image spyImage = subTwo.imageAtIndex(0);\n\n            assertFalse(image.isClosed());\n            assertFalse(image.isEndOfStream());\n            assertFalse(spyImage.isClosed());\n            assertFalse(spyImage.isEndOfStream());\n\n            assertEquals(2, availableImageCount.get());\n            assertEquals(0, unavailableImageCount.get());\n\n            publication.close();\n\n            while (subOne.isConnected() || subTwo.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            assertTrue(image.isClosed());\n            assertTrue(image.isEndOfStream());\n            assertTrue(spyImage.isClosed());\n            assertTrue(spyImage.isEndOfStream());\n\n            assertEquals(2, availableImageCount.get());\n            assertEquals(2, unavailableImageCount.get());\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    @SuppressWarnings(\"try\")\n    void shouldCallImageHandlersWithPublisherOnDifferentClient(final String channel)\n    {\n        final AtomicInteger unavailableImageCount = new AtomicInteger();\n        final AtomicInteger availableImageCount = new AtomicInteger();\n        final UnavailableImageHandler unavailableHandler = (image) -> unavailableImageCount.incrementAndGet();\n        final AvailableImageHandler availableHandler = (image) -> availableImageCount.incrementAndGet();\n\n        final String spyChannel = channel.contains(\"ipc\") ? channel : CommonContext.SPY_PREFIX + channel;\n        final Aeron.Context ctx = new Aeron.Context()\n            .useConductorAgentInvoker(true);\n\n        try (Aeron aeronTwo = Aeron.connect(ctx);\n            Subscription subOne = aeron.addSubscription(channel, STREAM_ID, availableHandler, unavailableHandler);\n            Subscription subTwo = aeron.addSubscription(\n                spyChannel, STREAM_ID, availableHandler, unavailableHandler);\n            Publication publication = aeronTwo.addPublication(channel, STREAM_ID))\n        {\n            while (!subOne.isConnected() || !subTwo.isConnected() || !publication.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            final Image image = subOne.imageAtIndex(0);\n            final Image spyImage = subTwo.imageAtIndex(0);\n\n            assertFalse(image.isClosed());\n            assertFalse(image.isEndOfStream());\n            assertFalse(spyImage.isClosed());\n            assertFalse(spyImage.isEndOfStream());\n\n            assertEquals(2, availableImageCount.get());\n            assertEquals(0, unavailableImageCount.get());\n\n            aeronTwo.close();\n\n            while (subOne.isConnected() || subTwo.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            assertTrue(image.isClosed());\n            assertTrue(image.isEndOfStream());\n            assertTrue(spyImage.isClosed());\n            assertTrue(spyImage.isEndOfStream());\n\n            assertEquals(2, availableImageCount.get());\n            assertEquals(2, unavailableImageCount.get());\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldGetEndOfStreamPosition(final String channel)\n    {\n        final DirectBuffer message = new UnsafeBuffer(\"hello word\".getBytes(US_ASCII));\n\n        try (Subscription sub = aeron.addSubscription(channel, STREAM_ID);\n            Publication pub = aeron.addPublication(channel, STREAM_ID))\n        {\n            while (!sub.isConnected() || !pub.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            assertEquals(Long.MAX_VALUE, image.endOfStreamPosition());\n\n            final int numMessages = 10;\n            for (int i = 0; i < numMessages; i++)\n            {\n                while (pub.offer(message) < 0)\n                {\n                    Tests.yield();\n                }\n            }\n\n            assertEquals(Long.MAX_VALUE, image.endOfStreamPosition());\n\n            int messagesRemaining = numMessages;\n            final FragmentHandler noopFragmentHandler = (buffer, offset, length, header) -> {};\n            while (0 < messagesRemaining)\n            {\n                messagesRemaining -= image.poll(noopFragmentHandler, 10);\n            }\n\n            assertEquals(Long.MAX_VALUE, image.endOfStreamPosition());\n\n            for (int i = 0; i < numMessages; i++)\n            {\n                while (pub.offer(message) < 0)\n                {\n                    Tests.yield();\n                }\n            }\n\n            messagesRemaining = numMessages;\n            while (5 < messagesRemaining)\n            {\n                messagesRemaining -= image.poll(noopFragmentHandler, 1);\n            }\n\n            CloseHelper.quietClose(pub);\n            long eosPosition;\n            while (Long.MAX_VALUE == (eosPosition = image.endOfStreamPosition()))\n            {\n                Tests.yield();\n            }\n\n            assertFalse(image.isEndOfStream());\n\n            while (0 < messagesRemaining)\n            {\n                messagesRemaining -= image.poll(noopFragmentHandler, 1);\n            }\n\n            while (!image.isEndOfStream())\n            {\n                Tests.yield();\n            }\n\n            assertEquals(eosPosition, image.position());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ImageRangeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.buffer.FileStoreLogFactory;\nimport io.aeron.driver.buffer.RawLog;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.AtomicLongPosition;\nimport org.agrona.concurrent.status.Position;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.initialTermId;\nimport static io.aeron.logbuffer.LogBufferDescriptor.isConnected;\nimport static io.aeron.logbuffer.LogBufferDescriptor.mtuLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.pageSize;\nimport static io.aeron.logbuffer.LogBufferDescriptor.termLength;\nimport static org.mockito.Mockito.mock;\n\nclass ImageRangeTest\n{\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldHandleAllPossibleOffsets(final boolean useSparseFiles, final @TempDir File baseDir)\n    {\n        final int termBufferLength = 65536;\n        final int filePageSize = 4096;\n        final long subscriberPositionThatWillTriggerException = 3147497471L;\n        final Position subscriberPosition = new AtomicLongPosition();\n        final AtomicCounter bytesMappedCounter = mock(AtomicCounter.class);\n\n        try (\n            FileStoreLogFactory fileStoreLogFactory = new FileStoreLogFactory(\n                baseDir.getAbsolutePath(),\n                filePageSize,\n                false,\n                0,\n                new RethrowingErrorHandler(),\n                bytesMappedCounter);\n            RawLog rawLog = fileStoreLogFactory.newImage(0, termBufferLength, useSparseFiles))\n        {\n            initialTermId(rawLog.metaData(), 0);\n            mtuLength(rawLog.metaData(), 1408);\n            termLength(rawLog.metaData(), termBufferLength);\n            pageSize(rawLog.metaData(), filePageSize);\n            isConnected(rawLog.metaData(), true);\n\n            try (LogBuffers logBuffers = new LogBuffers(rawLog.fileName()))\n            {\n                final Image image = new Image(\n                    null, 1, subscriberPosition, logBuffers, new RethrowingErrorHandler(), \"127.0.0.1:123\", 0);\n\n                subscriberPosition.set(subscriberPositionThatWillTriggerException);\n\n                image.boundedControlledPoll(\n                    (buffer, offset, length, header) -> ControlledFragmentHandler.Action.COMMIT, 1024, 1);\n                image.boundedPoll((buffer, offset, length, header) -> {}, 1024, 1);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/LifecycleTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass LifecycleTest\n{\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(10)\n    void shouldStartAndStopInstantly()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .errorHandler(Tests::onError);\n\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(driverCtx, testWatcher))\n        {\n            testWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n\n            final Aeron.Context clientCtx = new Aeron.Context()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName());\n\n            final Aeron aeron = Aeron.connect(clientCtx);\n            aeron.close();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotifyOfClientTimestampCounter()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .errorHandler(Tests::onError);\n\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(driverCtx, testWatcher))\n        {\n            testWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n\n            final Aeron.Context clientCtxOne = new Aeron.Context()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName());\n\n            final Aeron.Context clientCtxTwo = new Aeron.Context()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName());\n\n            try (Aeron aeron = Aeron.connect(clientCtxOne))\n            {\n                final AvailableCounterHandler availableHandler = mock(AvailableCounterHandler.class);\n                aeron.addAvailableCounterHandler(availableHandler);\n                final UnavailableCounterHandler unavailableHandler = mock(UnavailableCounterHandler.class);\n                aeron.addUnavailableCounterHandler(unavailableHandler);\n\n                try (Aeron aeronTwo = Aeron.connect(clientCtxTwo))\n                {\n                    aeronTwo.addSubscription(\"aeron:ipc\", 1001);\n                    verify(availableHandler, timeout(5000))\n                        .onAvailableCounter(any(), eq(clientCtxTwo.clientId()), anyInt());\n                }\n\n                verify(unavailableHandler, timeout(5000))\n                    .onUnavailableCounter(any(), eq(clientCtxTwo.clientId()), anyInt());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MaxFlowControlStrategySystemTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MaxMulticastFlowControlSupplier;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MaxFlowControlStrategySystemTest\n{\n    private static final String MULTICAST_URI = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\";\n    private static final int STREAM_ID = 1001;\n\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int NUM_MESSAGES_PER_TERM = 64;\n    private static final int MESSAGE_LENGTH =\n        (TERM_BUFFER_LENGTH / NUM_MESSAGES_PER_TERM) - DataHeaderFlyweight.HEADER_LENGTH;\n    private static final String ROOT_DIR = SystemUtil.tmpDirName() + \"aeron-system-tests\" + File.separator;\n\n    private final MediaDriver.Context driverAContext = new MediaDriver.Context();\n    private final MediaDriver.Context driverBContext = new MediaDriver.Context();\n\n    private Aeron clientA;\n    private Aeron clientB;\n    private TestMediaDriver driverA;\n    private TestMediaDriver driverB;\n    private Publication publication;\n    private Subscription subscriptionA;\n    private Subscription subscriptionB;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n    private final FragmentHandler fragmentHandlerA = mock(FragmentHandler.class);\n    private final FragmentHandler fragmentHandlerB = mock(FragmentHandler.class);\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.quietCloseAll(clientB, clientA, driverB, driverA);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSpinUpAndShutdown()\n    {\n        launch();\n\n        subscriptionA = clientA.addSubscription(MULTICAST_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(MULTICAST_URI, STREAM_ID);\n        publication = clientA.addPublication(MULTICAST_URI, STREAM_ID);\n\n        while (!subscriptionA.isConnected() || !subscriptionB.isConnected() || !publication.isConnected())\n        {\n            Tests.yield();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldTimeoutImageWhenBehindForTooLongWithMaxMulticastFlowControlStrategy()\n    {\n        final int numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n        driverAContext.multicastFlowControlSupplier(new MaxMulticastFlowControlSupplier());\n\n        launch();\n\n        subscriptionA = clientA.addSubscription(MULTICAST_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(MULTICAST_URI, STREAM_ID);\n        publication = clientA.addPublication(MULTICAST_URI, STREAM_ID);\n\n        while (!subscriptionA.isConnected() || !subscriptionB.isConnected() || !publication.isConnected())\n        {\n            Tests.yield();\n        }\n\n        final MutableInteger fragmentsRead = new MutableInteger();\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            fragmentsRead.set(0);\n\n            // A keeps up\n            Tests.executeUntil(\n                () -> fragmentsRead.get() > 0,\n                (j) ->\n                {\n                    fragmentsRead.value += subscriptionA.poll(fragmentHandlerA, 10);\n                    Thread.yield();\n                },\n                Integer.MAX_VALUE,\n                TimeUnit.MILLISECONDS.toNanos(500));\n\n            fragmentsRead.set(0);\n\n            // B receives slowly and eventually can't keep up\n            if (i % 10 == 0)\n            {\n                Tests.executeUntil(\n                    () -> fragmentsRead.get() > 0,\n                    (j) ->\n                    {\n                        fragmentsRead.value += subscriptionB.poll(fragmentHandlerB, 1);\n                        Thread.yield();\n                    },\n                    Integer.MAX_VALUE,\n                    TimeUnit.MILLISECONDS.toNanos(500));\n            }\n        }\n\n        verify(fragmentHandlerA, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(MESSAGE_LENGTH),\n            any(Header.class));\n\n        verify(fragmentHandlerB, atMost(numMessagesToSend - 1)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(MESSAGE_LENGTH),\n            any(Header.class));\n    }\n\n    private void launch()\n    {\n        final String baseDirA = ROOT_DIR + \"A\";\n        final String baseDirB = ROOT_DIR + \"B\";\n\n        buffer.putInt(0, 1);\n\n        driverAContext.publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirA)\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n            .errorHandler(Tests::onError)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverBContext.publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirB)\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n            .errorHandler(Tests::onError)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverA = TestMediaDriver.launch(driverAContext, testWatcher);\n        driverB = TestMediaDriver.launch(driverBContext, testWatcher);\n        clientA = Aeron.connect(\n            new Aeron.Context()\n                .errorHandler(Tests::onError)\n                .aeronDirectoryName(driverAContext.aeronDirectoryName()));\n\n        clientB = Aeron.connect(\n            new Aeron.Context()\n                .errorHandler(Tests::onError)\n                .aeronDirectoryName(driverBContext.aeronDirectoryName()));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MaxPositionPublicationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.nio.ByteBuffer;\n\nimport static io.aeron.Publication.MAX_POSITION_EXCEEDED;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MaxPositionPublicationTest\n{\n    private static final int STREAM_ID = 1007;\n    private static final int MESSAGE_LENGTH = 32;\n\n    private final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocate(MESSAGE_LENGTH));\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n                .errorHandler(Tests::onError)\n                .dirDeleteOnStart(true)\n                .threadingMode(ThreadingMode.SHARED),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldPublishFromExclusivePublication()\n    {\n        final int initialTermId = -777;\n        final int termLength = 64 * 1024;\n        final long maxPosition = termLength * (Integer.MAX_VALUE + 1L);\n        final long lastMessagePosition = maxPosition - (MESSAGE_LENGTH + DataHeaderFlyweight.HEADER_LENGTH);\n\n        final String channelUri = new ChannelUriStringBuilder()\n            .initialPosition(lastMessagePosition, initialTermId, termLength)\n            .media(\"ipc\")\n            .validate()\n            .build();\n\n        try (Subscription subscription = aeron.addSubscription(channelUri, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(channelUri, STREAM_ID))\n        {\n            Tests.awaitConnected(subscription);\n\n            assertEquals(lastMessagePosition, publication.position());\n            assertEquals(lastMessagePosition, subscription.imageAtIndex(0).joinPosition());\n\n            long resultingPosition = publication.offer(srcBuffer, 0, MESSAGE_LENGTH);\n            while (resultingPosition < 0)\n            {\n                Tests.yield();\n                resultingPosition = publication.offer(srcBuffer, 0, MESSAGE_LENGTH);\n            }\n\n            assertEquals(maxPosition, publication.maxPossiblePosition());\n            assertEquals(publication.maxPossiblePosition(), resultingPosition);\n            assertEquals(MAX_POSITION_EXCEEDED, publication.offer(srcBuffer, 0, MESSAGE_LENGTH));\n            assertEquals(MAX_POSITION_EXCEEDED, publication.offer(srcBuffer, 0, MESSAGE_LENGTH));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MdsAndMdcInteractionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.function.Supplier;\n\nimport static io.aeron.logbuffer.LogBufferDescriptor.computePosition;\nimport static io.aeron.logbuffer.LogBufferDescriptor.positionBitsToShift;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\npublic class MdsAndMdcInteractionTest\n{\n    private static final int STREAM_ID = 10000;\n    private static final int SESSION_ID = 20000;\n    private static final String CATCHUP_ENDPOINT = \"aeron:udp?endpoint=localhost:20001|alias=catchup\";\n    private static final String LIVE_ENDPOINT_EARLY = \"aeron:udp?endpoint=localhost:20002|alias=live\";\n    private static final String LIVE_ENDPOINT_LATE = \"aeron:udp?endpoint=localhost:20003|alias=live\";\n    private static final String LIVE_URI = \"aeron:udp?control-mode=manual|session-id=\" + SESSION_ID;\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    @BeforeEach\n    void setUp()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driver = TestMediaDriver.launch(context, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietClose(driver);\n    }\n\n\n    @Test\n    @InterruptAfter(15)\n    void shouldSwitchFromCatchupToLive()\n    {\n        final UnsafeBuffer msg = new UnsafeBuffer(\"Hello World\".getBytes(StandardCharsets.US_ASCII));\n        final int initialTermId = 100;\n        final int activeTermId = 100;\n        final int termOffset = 0;\n        final int termLength = 64 * 1024;\n        final int positionBitsToShift = positionBitsToShift(termLength);\n        final long position = computePosition(activeTermId, termOffset, positionBitsToShift, initialTermId);\n        final int messageCount = 10;\n\n        final String catchupUri = new ChannelUriStringBuilder(CATCHUP_ENDPOINT)\n            .sessionId(SESSION_ID)\n            .initialPosition(position, initialTermId, termLength)\n            .build();\n\n        final String liveUri = new ChannelUriStringBuilder(LIVE_URI)\n            .flowControl(\"min,t:1s\")\n            .sessionId(SESSION_ID)\n            .initialPosition(position, initialTermId, termLength)\n            .build();\n\n        try (Aeron follower = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            Aeron leader = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            Subscription lateJoinSub = follower.addSubscription(LIVE_URI, STREAM_ID);\n            Publication livePub = leader.addPublication(liveUri, STREAM_ID);\n            Subscription earlySub = follower.addSubscription(LIVE_ENDPOINT_EARLY, STREAM_ID))\n        {\n            final Publication catchupPub = leader.addPublication(catchupUri, STREAM_ID);\n            lateJoinSub.addDestination(CATCHUP_ENDPOINT);\n            livePub.addDestination(LIVE_ENDPOINT_EARLY);\n            livePub.addDestination(LIVE_ENDPOINT_LATE);\n\n            Tests.awaitConnected(lateJoinSub);\n            Tests.awaitConnected(earlySub);\n            Tests.awaitConnected(catchupPub);\n            Tests.awaitConnected(livePub);\n\n            for (int i = 0; i < messageCount; i++)\n            {\n                while (catchupPub.offer(msg) < 0)\n                {\n                    Tests.yield();\n                }\n\n                while (livePub.offer(msg) < 0)\n                {\n                    Tests.yield();\n                }\n            }\n\n            awaitMessages(lateJoinSub, messageCount);\n            awaitMessages(earlySub, messageCount);\n\n            lateJoinSub.addDestination(LIVE_ENDPOINT_LATE);\n\n            while (2 != lateJoinSub.imageBySessionId(SESSION_ID).activeTransportCount())\n            {\n                Tests.yield();\n            }\n\n            lateJoinSub.removeDestination(CATCHUP_ENDPOINT);\n\n            CloseHelper.quietClose(catchupPub);\n\n            for (int i = 0; i < messageCount; i++)\n            {\n                while (livePub.offer(msg) < 0)\n                {\n                    Tests.yield();\n                }\n            }\n\n            awaitMessages(lateJoinSub, messageCount);\n            assertEquals(livePub.position(), lateJoinSub.imageBySessionId(SESSION_ID).position());\n        }\n    }\n\n    @SuppressWarnings(\"methodlength\")\n    @Test\n    @InterruptAfter(15)\n    @SlowTest\n    void shouldSwitchFromCatchupToLiveWhenRestartingClient()\n    {\n        final UnsafeBuffer msg = new UnsafeBuffer(\"Hello World\".getBytes(StandardCharsets.US_ASCII));\n        final int initialTermId = 100;\n        final int activeTermId = 100;\n        final int termOffset = 0;\n        final int termLength = 64 * 1024;\n        final int positionBitsToShift = positionBitsToShift(termLength);\n        final long position = computePosition(activeTermId, termOffset, positionBitsToShift, initialTermId);\n        final int messageCount = 10;\n\n        final String liveUri = new ChannelUriStringBuilder(LIVE_URI)\n            .flowControl(\"min,t:1s\")\n            .sessionId(SESSION_ID)\n            .initialPosition(position, initialTermId, termLength)\n            .build();\n\n        try (Aeron leader = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            Publication livePub = leader.addPublication(liveUri, STREAM_ID);\n            Subscription earlySub = leader.addSubscription(LIVE_ENDPOINT_EARLY, STREAM_ID))\n        {\n            livePub.addDestination(LIVE_ENDPOINT_EARLY);\n            livePub.addDestination(LIVE_ENDPOINT_LATE);\n            Tests.awaitConnected(earlySub);\n\n            final int lateJoinChannelStatusId;\n\n            try (Aeron follower = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n                Subscription lateJoinSub = follower.addSubscription(LIVE_URI, STREAM_ID))\n            {\n                lateJoinSub.addDestination(LIVE_ENDPOINT_LATE);\n                Tests.awaitConnected(lateJoinSub);\n                Tests.awaitConnected(livePub);\n\n                lateJoinChannelStatusId = lateJoinSub.channelStatusId();\n\n                for (int i = 0; i < messageCount; i++)\n                {\n                    while (livePub.offer(msg) < 0)\n                    {\n                        Tests.yield();\n                    }\n                }\n\n                awaitMessages(earlySub, messageCount);\n                awaitMessages(lateJoinSub, messageCount);\n            }\n\n            while (!LocalSocketAddressStatus.findAddresses(\n                leader.countersReader(), ChannelEndpointStatus.ACTIVE, lateJoinChannelStatusId).isEmpty())\n            {\n                Tests.yield();\n            }\n\n            final long restartPosition = livePub.position();\n\n            for (int i = 0; i < messageCount; i++)\n            {\n                while (livePub.offer(msg) < 0)\n                {\n                    Tests.yield();\n                }\n            }\n\n            awaitMessages(earlySub, messageCount);\n\n            try (Aeron follower = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n                Subscription lateJoinSub = follower.addSubscription(LIVE_URI, STREAM_ID))\n            {\n                lateJoinSub.addDestination(CATCHUP_ENDPOINT);\n\n                final String catchupUri = new ChannelUriStringBuilder(CATCHUP_ENDPOINT)\n                    .sessionId(SESSION_ID)\n                    .initialPosition(restartPosition, initialTermId, termLength)\n                    .build();\n\n                final Publication catchupPub = leader.addPublication(catchupUri, STREAM_ID);\n                Tests.awaitConnected(lateJoinSub);\n\n                for (int i = 0; i < messageCount; i++)\n                {\n                    while (catchupPub.offer(msg) < 0)\n                    {\n                        Tests.yield();\n                    }\n                }\n\n                awaitMessages(lateJoinSub, messageCount);\n\n                assertEquals(livePub.position(), lateJoinSub.imageBySessionId(SESSION_ID).position());\n\n                lateJoinSub.addDestination(LIVE_ENDPOINT_LATE);\n\n                while (2 != lateJoinSub.imageBySessionId(SESSION_ID).activeTransportCount())\n                {\n                    Tests.yield();\n                }\n\n                lateJoinSub.removeDestination(CATCHUP_ENDPOINT);\n\n                for (int i = 0; i < messageCount; i++)\n                {\n                    while (livePub.offer(msg) < 0)\n                    {\n                        Tests.yield();\n                    }\n                }\n\n                awaitMessages(earlySub, messageCount);\n                awaitMessages(lateJoinSub, messageCount);\n            }\n        }\n    }\n\n    private void awaitMessages(final Subscription sub, final int count)\n    {\n        final MutableInteger messageCount = new MutableInteger(0);\n        final Supplier<String> msg = () -> \"expected=\" + count + \", actual=\" + messageCount.get();\n\n        while (count != messageCount.get())\n        {\n            if (0 == sub.poll((buffer, offset, length, header) -> messageCount.increment(), 10))\n            {\n                Tests.yieldingIdle(msg);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MdsEosPositionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.test.*;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.nio.ByteBuffer;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MdsEosPositionTest\n{\n    private static final long INITIAL_STREAM_POSITION = 0;\n    public static final int SESSION_ID = 555;\n    private static final int INITIAL_TERM_ID = 777;\n    private static final int TERM_LENGTH = 64 * 1024;\n    private static final int MESSAGE_COUNT = 10;\n    private static final int STREAM_ID = 1001;\n    private static final int MESSAGE_LENGTH = 200;\n    private static final String ENDPOINT_ONE = \"localhost:3333\";\n    private static final String ENDPOINT_TWO = \"localhost:6666\";\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @Test\n    @SlowTest\n    @InterruptAfter(30)\n    void shouldHaveEosPositionAtFinalPosition()\n    {\n        final ChannelUriStringBuilder uriBuilder = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .sessionId(SESSION_ID)\n            .initialPosition(INITIAL_STREAM_POSITION, INITIAL_TERM_ID, TERM_LENGTH);\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .errorHandler(Tests::onError);\n\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(driverCtx, testWatcher);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName())))\n        {\n            testWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n\n            final Subscription subscription = aeron.addSubscription(\"aeron:udp?control-mode=manual\", STREAM_ID);\n            subscription.addDestination(\"aeron:udp?endpoint=\" + ENDPOINT_ONE);\n            subscription.addDestination(\"aeron:udp?endpoint=\" + ENDPOINT_TWO);\n\n            final Publication publicationOne = aeron.addPublication(\n                uriBuilder.endpoint(ENDPOINT_ONE).build(), STREAM_ID);\n            final Publication publicationTwo = aeron.addPublication(\n                uriBuilder.endpoint(ENDPOINT_TWO).build(), STREAM_ID);\n\n            Tests.awaitConnected(subscription);\n\n            publishStream(publicationTwo);\n\n            final long expectedPosition = publicationTwo.position();\n            final Image image = subscription.imageAtIndex(0);\n\n            consumeStream(image);\n\n            awaitActiveTransportCount(image, 2);\n\n            publicationTwo.close();\n\n            awaitActiveTransportCount(image, 1);\n\n            publicationOne.close();\n\n            awaitClosed(image);\n\n            assertTrue(image.isEndOfStream());\n            assertEquals(expectedPosition, image.position());\n            assertEquals(expectedPosition, image.endOfStreamPosition());\n        }\n    }\n\n    private static void publishStream(final Publication publicationTwo)\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocate(MESSAGE_LENGTH));\n        for (int i = 0; i < MESSAGE_COUNT; i++)\n        {\n            srcBuffer.putStringAscii(0, Integer.toString(i));\n            while (publicationTwo.offer(srcBuffer, 0, MESSAGE_LENGTH) < 0L)\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    private static void consumeStream(final Image image)\n    {\n        int i = 0;\n        while (i++ < MESSAGE_COUNT)\n        {\n            while (0 == image.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    private static void awaitActiveTransportCount(final Image image, final int activeTransportCount)\n    {\n        while (image.activeTransportCount() != activeTransportCount)\n        {\n            Tests.sleep(1);\n        }\n    }\n\n    private static void awaitClosed(final Image image)\n    {\n        while (!image.isClosed())\n        {\n            Tests.sleep(1);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MemoryOrderingTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.nio.ByteBuffer;\nimport java.util.List;\n\nimport static java.util.Arrays.asList;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MemoryOrderingTest\n{\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n    private static final int MESSAGE_LENGTH = 2000;\n    private static final int TERM_BUFFER_LENGTH = 1024 * 64;\n    private static final int NUM_MESSAGES = 15_000;\n    private static final int BURST_LENGTH = 7;\n    private static final int INTER_BURST_DURATION_NS = 100_000;\n\n    private static volatile String failedMessage = null;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n                .errorHandler(Tests::onError)\n                .dirDeleteOnStart(true)\n                .threadingMode(ThreadingMode.SHARED)\n                .publicationTermBufferLength(TERM_BUFFER_LENGTH),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldReceiveMessagesInOrderWithFirstLongWordIntact(final String channel) throws Exception\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocate(MESSAGE_LENGTH));\n        srcBuffer.setMemory(0, MESSAGE_LENGTH, (byte)7);\n\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            Publication publication = aeron.addPublication(channel, STREAM_ID))\n        {\n            final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n            final Thread subscriberThread = new Thread(new Subscriber(subscription));\n            subscriberThread.setDaemon(true);\n            subscriberThread.start();\n\n            for (int i = 0; i < NUM_MESSAGES; i++)\n            {\n                failOnError();\n\n                srcBuffer.putLong(0, i);\n\n                while (publication.offer(srcBuffer) < 0L)\n                {\n                    failOnError();\n\n                    idleStrategy.idle();\n                    Tests.checkInterruptStatus();\n                }\n\n                if (i % BURST_LENGTH == 0)\n                {\n                    final long timeoutNs = System.nanoTime() + INTER_BURST_DURATION_NS;\n                    long nowNs;\n                    do\n                    {\n                        nowNs = System.nanoTime();\n                    }\n                    while ((timeoutNs - nowNs) > 0);\n                }\n            }\n\n            subscriberThread.join();\n\n            failOnError();\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(20)\n    void shouldReceiveMessagesInOrderWithFirstLongWordIntactFromExclusivePublication(final String channel)\n        throws InterruptedException\n    {\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocate(MESSAGE_LENGTH));\n        srcBuffer.setMemory(0, MESSAGE_LENGTH, (byte)7);\n\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(channel, STREAM_ID))\n        {\n            final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n            final Thread subscriberThread = new Thread(new Subscriber(subscription));\n            subscriberThread.setDaemon(true);\n            subscriberThread.start();\n\n            for (int i = 0; i < NUM_MESSAGES; i++)\n            {\n                failOnError();\n\n                srcBuffer.putLong(0, i);\n\n                while (publication.offer(srcBuffer) < 0L)\n                {\n                    failOnError();\n\n                    idleStrategy.idle();\n                    Tests.checkInterruptStatus();\n                }\n\n                if (i % BURST_LENGTH == 0)\n                {\n                    final long timeoutNs = System.nanoTime() + INTER_BURST_DURATION_NS;\n                    long nowNs;\n                    do\n                    {\n                        nowNs = System.nanoTime();\n                    }\n                    while ((timeoutNs - nowNs) > 0);\n                }\n            }\n\n            subscriberThread.join();\n\n            failOnError();\n        }\n    }\n\n    private static void failOnError()\n    {\n        if (null != failedMessage)\n        {\n            fail(failedMessage);\n        }\n    }\n\n    private static List<String> channels()\n    {\n        return asList(\n            \"aeron:ipc?term-length=4m\",\n            \"aeron:udp?endpoint=localhost:24325|term-length=4m\"\n        );\n    }\n\n    static class Subscriber implements Runnable, FragmentHandler\n    {\n        private final FragmentAssembler fragmentAssembler = new FragmentAssembler(this);\n        private final Subscription subscription;\n\n        long previousValue = -1;\n        int messageNum = 0;\n\n        Subscriber(final Subscription subscription)\n        {\n            this.subscription = subscription;\n        }\n\n        public void run()\n        {\n            final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n\n            while (messageNum < NUM_MESSAGES && null == failedMessage)\n            {\n                idleStrategy.idle(subscription.poll(fragmentAssembler, FRAGMENT_COUNT_LIMIT));\n            }\n        }\n\n        public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n        {\n            final long messageValue = buffer.getLong(offset);\n\n            final long expectedValue = previousValue + 1;\n            if (messageValue != expectedValue)\n            {\n                final long messageValueSecondRead = buffer.getLong(offset);\n\n                final String msg = \"Issue at message number transition: \" + previousValue + \" -> \" + messageValue;\n\n                System.out.println(msg + \"\\n\" +\n                    \"offset: \" + offset + \"\\n\" +\n                    \"length: \" + length + \"\\n\" +\n                    \"expected bytes: \" + byteString(expectedValue) + \"\\n\" +\n                    \"received bytes: \" + byteString(messageValue) + \"\\n\" +\n                    \"expected bits: \" + Long.toBinaryString(expectedValue) + \"\\n\" +\n                    \"received bits: \" + Long.toBinaryString(messageValue) + \"\\n\" +\n                    \"messageValue on second read: \" + messageValueSecondRead + \"\\n\" +\n                    \"messageValue on third read: \" + buffer.getLong(offset));\n\n                failedMessage = msg;\n            }\n\n            previousValue = messageValue;\n            messageNum++;\n        }\n\n        private String byteString(final long value)\n        {\n            return String.format(\"%x %x %x %x %x %x %x %x\",\n                (byte)(value >>> 56),\n                (byte)(value >>> 48),\n                (byte)(value >>> 40),\n                (byte)(value >>> 32),\n                (byte)(value >>> 24),\n                (byte)(value >>> 18),\n                (byte)(value >>> 8),\n                (byte)value);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MinFlowControlSystemTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.FlowControlSupplier;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.MinMulticastFlowControlSupplier;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.SenderLimit;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.nio.file.Path;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Stream;\n\nimport static io.aeron.AeronCounters.FLOW_CONTROL_RECEIVERS_COUNTER_TYPE_ID;\nimport static io.aeron.FlowControlTests.awaitConnectionAndStatusMessages;\nimport static io.aeron.test.Tests.awaitConnected;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MinFlowControlSystemTest\n{\n    private static final String MULTICAST_URI = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\";\n    private static final int STREAM_ID = 1001;\n\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int NUM_MESSAGES_PER_TERM = 64;\n    private static final int MESSAGE_LENGTH =\n        (TERM_BUFFER_LENGTH / NUM_MESSAGES_PER_TERM) - DataHeaderFlyweight.HEADER_LENGTH;\n    private final MediaDriver.Context driverAContext = new MediaDriver.Context();\n    private final MediaDriver.Context driverBContext = new MediaDriver.Context();\n\n    @TempDir\n    private Path tempDir;\n\n    private Aeron clientA;\n    private Aeron clientB;\n    private TestMediaDriver driverA;\n    private TestMediaDriver driverB;\n    private Publication publication;\n    private Subscription subscriptionA;\n    private Subscription subscriptionB;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n    private final FragmentHandler fragmentHandlerA = mock(FragmentHandler.class);\n    private final FragmentHandler fragmentHandlerB = mock(FragmentHandler.class);\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private void launch()\n    {\n        buffer.putInt(0, 1);\n\n        final String baseDirA = tempDir.resolve(\"A\").toString();\n        final String baseDirB = tempDir.resolve(\"B\").toString();\n\n        driverAContext.publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirA)\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n            .flowControlReceiverTimeoutNs(TimeUnit.MILLISECONDS.toNanos(1000))\n            .errorHandler(Tests::onError)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverBContext.publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirB)\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n            .flowControlReceiverTimeoutNs(TimeUnit.MILLISECONDS.toNanos(1000))\n            .errorHandler(Tests::onError)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverA = TestMediaDriver.launch(driverAContext, testWatcher);\n        testWatcher.dataCollector().add(driverAContext.aeronDirectory());\n\n        driverB = TestMediaDriver.launch(driverBContext, testWatcher);\n        testWatcher.dataCollector().add(driverBContext.aeronDirectory());\n\n        clientA = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(driverAContext.aeronDirectoryName()));\n\n        clientB = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(driverBContext.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.quietCloseAll(clientB, clientA, driverB, driverA);\n    }\n\n    private static Stream<Arguments> strategyConfigurations()\n    {\n        return Stream.of(\n            Arguments.of(new MinMulticastFlowControlSupplier(), \"\"),\n            Arguments.of(null, \"|fc=min\"),\n            Arguments.of(null, \"|fc=min,g:/1\"));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"strategyConfigurations\")\n    @InterruptAfter(10)\n    void shouldSlowToMinMulticastFlowControlStrategy(\n        final FlowControlSupplier flowControlSupplier, final String publisherUriParams)\n    {\n        final int numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n        int numMessagesLeftToSend = numMessagesToSend;\n        int numFragmentsFromB = 0;\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n        if (null != flowControlSupplier)\n        {\n            driverAContext.multicastFlowControlSupplier(flowControlSupplier);\n        }\n        driverAContext.flowControlGroupMinSize(1);\n\n        launch();\n\n        subscriptionA = clientA.addSubscription(MULTICAST_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(MULTICAST_URI, STREAM_ID);\n        publication = clientA.addPublication(MULTICAST_URI + publisherUriParams, STREAM_ID);\n\n        final int flowControlCounterId = clientA.countersReader().findByTypeIdAndRegistrationId(\n            FLOW_CONTROL_RECEIVERS_COUNTER_TYPE_ID, publication.registrationId());\n        assertNotEquals(NULL_COUNTER_ID, flowControlCounterId);\n\n        while (!subscriptionA.isConnected() ||\n            !subscriptionB.isConnected() ||\n            !publication.isConnected() ||\n            clientA.countersReader().getCounterValue(flowControlCounterId) < 2)\n        {\n            Tests.yield();\n        }\n\n        for (long i = 0; numFragmentsFromB < numMessagesToSend; i++)\n        {\n            if (numMessagesLeftToSend > 0)\n            {\n                final long result = publication.offer(buffer, 0, buffer.capacity());\n                if (result >= 0L)\n                {\n                    numMessagesLeftToSend--;\n                }\n                else if (Publication.NOT_CONNECTED == result)\n                {\n                    fail(\"Publication not connected, numMessagesLeftToSend=\" + numMessagesLeftToSend);\n                }\n            }\n\n            Tests.yield();\n\n            // A keeps up\n            subscriptionA.poll(fragmentHandlerA, 10);\n\n            // B receives slowly\n            if ((i % 2) == 0)\n            {\n                final int bFragments = subscriptionB.poll(fragmentHandlerB, 1);\n                if (0 == bFragments && !subscriptionB.isConnected())\n                {\n                    if (subscriptionB.isClosed())\n                    {\n                        fail(\"Subscription B is closed, numFragmentsFromB=\" + numFragmentsFromB);\n                    }\n\n                    fail(\"Subscription B not connected, numFragmentsFromB=\" + numFragmentsFromB);\n                }\n\n                numFragmentsFromB += bFragments;\n            }\n        }\n\n        verify(fragmentHandlerB, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class), anyInt(), eq(MESSAGE_LENGTH), any(Header.class));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRemoveDeadReceiverWithMinMulticastFlowControlStrategy()\n    {\n        final int numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n        final MutableInteger numMessagesLeftToSend = new MutableInteger(numMessagesToSend);\n        final MutableInteger numFragmentsReadFromA = new MutableInteger(0);\n        final MutableInteger numFragmentsReadFromB = new MutableInteger(0);\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n        driverAContext.multicastFlowControlSupplier(new MinMulticastFlowControlSupplier());\n\n        launch();\n\n        publication = clientA.addPublication(MULTICAST_URI, STREAM_ID);\n\n        subscriptionA = clientA.addSubscription(MULTICAST_URI, STREAM_ID);\n        awaitConnected(subscriptionA);\n\n        subscriptionB = clientB.addSubscription(MULTICAST_URI, STREAM_ID);\n        awaitConnected(subscriptionB);\n\n        awaitConnected(publication);\n\n        boolean isBClosed = false;\n        while (numFragmentsReadFromA.get() < numMessagesToSend)\n        {\n            int workDone = 0;\n            if (numMessagesLeftToSend.get() > 0)\n            {\n                final long position = publication.offer(buffer, 0, buffer.capacity());\n                if (position >= 0L)\n                {\n                    numMessagesLeftToSend.decrement();\n                    workDone++;\n                }\n            }\n\n            // A keeps up\n            final int readA = subscriptionA.poll(fragmentHandlerA, 10);\n            numFragmentsReadFromA.addAndGet(readA);\n            workDone += readA;\n\n            // B receives up to 1/8 of the messages, then stops\n            if (numFragmentsReadFromB.get() < (numMessagesToSend / 8))\n            {\n                final int readB = subscriptionB.poll(fragmentHandlerB, 10);\n                numFragmentsReadFromB.addAndGet(readB);\n                workDone += readB;\n            }\n            else if (!isBClosed)\n            {\n                subscriptionB.close();\n                isBClosed = true;\n            }\n\n            if (0 == workDone)\n            {\n                Tests.yieldingIdle(\n                    () -> \"numMessagesToSend=\" + numMessagesToSend + \" numMessagesLeftToSend=\" + numMessagesLeftToSend +\n                    \" numFragmentsReadFromA=\" + numFragmentsReadFromA + \" numFragmentsReadFromB=\" +\n                    numFragmentsReadFromB);\n            }\n        }\n\n        verify(fragmentHandlerA, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class), anyInt(), eq(MESSAGE_LENGTH), any(Header.class));\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void shouldPreventConnectionUntilGroupMinSizeIsMet()\n    {\n        final Integer groupSize = 3;\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"224.20.30.39:24326\")\n            .networkInterface(\"localhost\");\n\n        final String uriPlain = builder\n            .flowControl((String)null)\n            .build();\n\n        final String uriWithMinFlowControl = builder\n            .groupTag((Long)null)\n            .minFlowControl(groupSize, null)\n            .build();\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n\n        launch();\n\n        final CountersReader countersReader = clientA.countersReader();\n        TestMediaDriver driverC = null;\n        Aeron clientC = null;\n        Publication publication = null;\n        Subscription subscription0 = null;\n        Subscription subscription1 = null;\n        Subscription subscription2 = null;\n\n        try\n        {\n            driverC = TestMediaDriver.launch(\n                new MediaDriver.Context().publicationTermBufferLength(TERM_BUFFER_LENGTH)\n                    .aeronDirectoryName(tempDir.resolve(\"C\").toString())\n                    .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n                    .errorHandler(Tests::onError)\n                    .threadingMode(ThreadingMode.SHARED),\n                testWatcher);\n\n            clientC = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverC.aeronDirectoryName()));\n\n            subscription0 = clientA.addSubscription(uriPlain, STREAM_ID);\n            subscription1 = clientB.addSubscription(uriPlain, STREAM_ID);\n            publication = clientA.addPublication(uriWithMinFlowControl, STREAM_ID);\n\n            awaitConnectionAndStatusMessages(countersReader, subscription0, subscription1);\n\n            assertFalse(publication.isConnected());\n\n            subscription2 = clientC.addSubscription(uriPlain, STREAM_ID);\n\n            // Should now have 3 receivers and publication should eventually be connected.\n            while (!publication.isConnected())\n            {\n                Tests.sleep(1);\n            }\n\n            subscription2.close();\n            subscription2 = null;\n\n            // Lost a receiver and publication should eventually be disconnected.\n            while (publication.isConnected())\n            {\n                Tests.sleep(1);\n            }\n\n            subscription2 = clientC.addSubscription(uriPlain, STREAM_ID);\n\n            while (!publication.isConnected())\n            {\n                Tests.sleep(1);\n            }\n        }\n        finally\n        {\n            CloseHelper.closeAll(\n                publication,\n                subscription0, subscription1, subscription2,\n                clientC,\n                driverC);\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldPreventConnectionUntilAtLeastOneSubscriberConnectedWithRequiredGroupSizeZero()\n    {\n        final Integer groupSize = 0;\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"224.20.30.41:24326\")\n            .networkInterface(\"localhost\");\n\n        final String plainUri = builder\n            .flowControl((String)null)\n            .build();\n\n        final String uriWithMinFlowControl = builder\n            .groupTag((Long)null)\n            .minFlowControl(groupSize, null)\n            .build();\n\n        launch();\n\n        clientA.addSubscription(plainUri, STREAM_ID + 1);\n        publication = clientA.addPublication(uriWithMinFlowControl, STREAM_ID);\n        final Publication otherPublication = clientA.addPublication(plainUri, STREAM_ID + 1);\n\n        awaitConnected(otherPublication);\n\n        // We know another publication on the same channel is connected\n\n        subscriptionA = clientA.addSubscription(plainUri, STREAM_ID);\n\n        while (!publication.isConnected())\n        {\n            Tests.sleep(1);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldHandleSenderLimitCorrectlyWithMinGroupSize()\n    {\n        final String publisherUri = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123/1\";\n        final String groupSubscriberUri = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|gtag=123\";\n        final String subscriberUri = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\";\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n\n        launch();\n\n        subscriptionA = clientA.addSubscription(subscriberUri, STREAM_ID);\n        publication = clientA.addPublication(publisherUri, STREAM_ID);\n\n        final CountersReader countersReader = clientA.countersReader();\n\n        final int senderLimitCounterId = FlowControlTests.findCounterIdByRegistrationId(\n            countersReader, SenderLimit.SENDER_LIMIT_TYPE_ID, publication.registrationId);\n        final long currentSenderLimit = countersReader.getCounterValue(senderLimitCounterId);\n\n        awaitConnectionAndStatusMessages(countersReader, subscriptionA);\n        assertEquals(currentSenderLimit, countersReader.getCounterValue(senderLimitCounterId));\n\n        subscriptionB = clientB.addSubscription(groupSubscriberUri, STREAM_ID);\n\n        while (currentSenderLimit == countersReader.getCounterValue(senderLimitCounterId))\n        {\n            Tests.sleep(\n                1,\n                \"currentSenderLimit(%d) == countersReader.getCounterValue(senderLimitCounterId)(%d)\",\n                currentSenderLimit,\n                countersReader.getCounterValue(senderLimitCounterId));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MinPositionSubscriptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static io.aeron.CommonContext.SPY_PREFIX;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MinPositionSubscriptionTest\n{\n    private static final int STREAM_ID = 1001;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n                .errorHandler(Tests::onError)\n                .dirDeleteOnStart(true)\n                .spiesSimulateConnection(true)\n                .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n                .threadingMode(ThreadingMode.SHARED),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @InterruptAfter(10)\n    @Test\n    void shouldJoinAtSamePositionIpc()\n    {\n        final String channel = \"aeron:ipc\";\n        shouldJoinAtSamePosition(channel, channel);\n    }\n\n    @InterruptAfter(10)\n    @Test\n    void shouldJoinAtSamePositionUdp()\n    {\n        final String channel = \"aeron:udp?endpoint=localhost:24325\";\n        shouldJoinAtSamePosition(channel, channel);\n    }\n\n    @InterruptAfter(10)\n    @Test\n    void shouldJoinAtSamePositionUdpSpy()\n    {\n        final String channel = \"aeron:udp?endpoint=localhost:24325\";\n        shouldJoinAtSamePosition(channel, SPY_PREFIX + channel);\n    }\n\n    @SuppressWarnings(\"try\")\n    private void shouldJoinAtSamePosition(final String publicationChannel, final String subscriptionChannel)\n    {\n        try (Subscription subscriptionOne = aeron.addSubscription(subscriptionChannel, STREAM_ID);\n            Publication publication = aeron.addPublication(publicationChannel, STREAM_ID))\n        {\n            final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[128]);\n\n            while (publication.offer(srcBuffer) < 0L)\n            {\n                Tests.yield();\n            }\n\n            final int sessionId = publication.sessionId();\n            publication.close();\n\n            Image imageOne;\n            while (null == (imageOne = subscriptionOne.imageBySessionId(sessionId)))\n            {\n                Tests.yield();\n            }\n\n            assertEquals(0L, imageOne.joinPosition());\n\n            try (Subscription subscriptionTwo = aeron.addSubscription(subscriptionChannel, STREAM_ID))\n            {\n                Image imageTwo;\n                while (null == (imageTwo = subscriptionTwo.imageBySessionId(sessionId)))\n                {\n                    Tests.yield();\n                }\n\n                assertEquals(imageOne.joinPosition(), imageTwo.joinPosition());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MultiDestinationCastTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.*;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static io.aeron.ChannelUri.SPY_QUALIFIER;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThan;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MultiDestinationCastTest\n{\n    private static final String PUB_MDC_DYNAMIC_URI = \"aeron:udp?control=localhost:24325|control-mode=dynamic|fc=min\";\n    private static final String SUB1_MDC_DYNAMIC_URI = \"aeron:udp?control=localhost:24325|group=true\";\n    private static final String SUB2_MDC_DYNAMIC_URI = \"aeron:udp?control=localhost:24325|group=true\";\n    private static final String SUB3_MDC_DYNAMIC_URI = CommonContext.SPY_PREFIX + PUB_MDC_DYNAMIC_URI;\n\n    private static final String PUB_MDC_MANUAL_URI = \"aeron:udp?control-mode=manual\";\n    private static final String SUB1_MDC_MANUAL_URI = \"aeron:udp?endpoint=localhost:24326|group=true\";\n    private static final String SUB2_MDC_MANUAL_URI = \"aeron:udp?endpoint=localhost:24327|group=true\";\n\n    private static final int STREAM_ID = 1001;\n\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int MESSAGES_PER_TERM = 64;\n    private static final int MESSAGE_LENGTH =\n        (TERM_BUFFER_LENGTH / MESSAGES_PER_TERM) - DataHeaderFlyweight.HEADER_LENGTH;\n    private static final String ROOT_DIR = CommonContext.generateRandomDirName() + File.separator;\n    private static final int FRAGMENT_LIMIT = 10;\n\n    private final MediaDriver.Context driverBContext = new MediaDriver.Context();\n    private final MediaDriver.Context driverAContext = new MediaDriver.Context();\n\n    private Aeron clientA;\n    private Aeron clientB;\n    private TestMediaDriver driverA;\n    private TestMediaDriver driverB;\n    private Publication publication;\n    private Subscription subscriptionA;\n    private Subscription subscriptionB;\n    private Subscription subscriptionC;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n    private final FragmentHandler fragmentHandlerA = mock(FragmentHandler.class, \"fragmentHandlerA\");\n    private final FragmentHandler fragmentHandlerB = mock(FragmentHandler.class, \"fragmentHandlerB\");\n    private final FragmentHandler fragmentHandlerC = mock(FragmentHandler.class, \"fragmentHandlerC\");\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private void launch(final ErrorHandler errorHandler)\n    {\n        final String baseDirA = ROOT_DIR + \"A\";\n        final String baseDirB = ROOT_DIR + \"B\";\n\n        buffer.putInt(0, 1);\n\n        driverAContext.errorHandler(errorHandler)\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirA)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverBContext.publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .errorHandler(errorHandler)\n            .aeronDirectoryName(baseDirB)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverA = TestMediaDriver.launch(driverAContext, testWatcher);\n        testWatcher.dataCollector().add(driverA.context().aeronDirectory());\n        driverB = TestMediaDriver.launch(driverBContext, testWatcher);\n        testWatcher.dataCollector().add(driverB.context().aeronDirectory());\n        clientA = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverAContext.aeronDirectoryName()));\n        clientB = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverBContext.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void closeEverything()\n    {\n        CloseHelper.closeAll(clientB, clientA, driverB, driverA);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSpinUpAndShutdownWithDynamic()\n    {\n        launch(Tests::onError);\n\n        publication = clientA.addPublication(PUB_MDC_DYNAMIC_URI, STREAM_ID);\n        subscriptionA = clientA.addSubscription(SUB1_MDC_DYNAMIC_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(SUB2_MDC_DYNAMIC_URI, STREAM_ID);\n        subscriptionC = clientA.addSubscription(SUB3_MDC_DYNAMIC_URI, STREAM_ID);\n\n        while (subscriptionA.hasNoImages() || subscriptionB.hasNoImages() || subscriptionC.hasNoImages())\n        {\n            Tests.yield();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldSpinUpAndShutdownWithManual()\n    {\n        driverAContext.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(1));\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(1));\n\n        launch(Tests::onError);\n\n        final String taggedMdcUri = new ChannelUriStringBuilder(PUB_MDC_MANUAL_URI).tags(\n            clientA.nextCorrelationId(),\n            clientA.nextCorrelationId()).build();\n        final String spySubscriptionUri = new ChannelUriStringBuilder(taggedMdcUri).prefix(\"aeron-spy\").build();\n\n        subscriptionA = clientA.addSubscription(SUB1_MDC_MANUAL_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(SUB2_MDC_MANUAL_URI, STREAM_ID);\n        subscriptionC = clientA.addSubscription(spySubscriptionUri, STREAM_ID);\n\n        publication = clientA.addPublication(taggedMdcUri, STREAM_ID);\n        publication.addDestination(SUB1_MDC_MANUAL_URI);\n        final long correlationId = publication.asyncAddDestination(SUB2_MDC_MANUAL_URI);\n\n        while (subscriptionA.hasNoImages() || subscriptionB.hasNoImages() || subscriptionC.hasNoImages())\n        {\n            Tests.yield();\n        }\n\n        assertFalse(clientA.isCommandActive(correlationId));\n\n        final long removeCorrelationId = publication.asyncRemoveDestination(SUB2_MDC_MANUAL_URI);\n        Tests.await(\"Remove Active\", () -> !clientA.isCommandActive(removeCorrelationId));\n        Tests.await(\"Subscription disconnect\", () -> subscriptionB.hasNoImages());\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldSendToTwoPortsWithDynamic()\n    {\n        final int numMessagesToSend = MESSAGES_PER_TERM * 3;\n\n        launch(Tests::onError);\n\n        subscriptionA = clientA.addSubscription(SUB1_MDC_DYNAMIC_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(SUB2_MDC_DYNAMIC_URI, STREAM_ID);\n        subscriptionC = clientA.addSubscription(SUB3_MDC_DYNAMIC_URI, STREAM_ID);\n        publication = clientA.addPublication(PUB_MDC_DYNAMIC_URI, STREAM_ID);\n\n        while (subscriptionA.hasNoImages() || subscriptionB.hasNoImages() || subscriptionC.hasNoImages())\n        {\n            Tests.yield();\n        }\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscriptionA, fragmentHandlerA);\n            pollForFragment(subscriptionB, fragmentHandlerB);\n            pollForFragment(subscriptionC, fragmentHandlerC);\n        }\n\n        verifyFragments(fragmentHandlerA, numMessagesToSend);\n        verifyFragments(fragmentHandlerB, numMessagesToSend);\n        verifyFragments(fragmentHandlerC, numMessagesToSend);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldSubscribeWithSessionId()\n    {\n        final int numMessagesToSend = MESSAGES_PER_TERM * 3;\n\n        launch(Tests::onError);\n\n        final String sessionId = \"|session-id=\" + BitUtil.generateRandomisedId();\n        subscriptionA = clientA.addSubscription(SUB1_MDC_DYNAMIC_URI + sessionId, STREAM_ID);\n        subscriptionB = clientB.addSubscription(SUB2_MDC_DYNAMIC_URI + sessionId, STREAM_ID);\n        publication = clientA.addPublication(PUB_MDC_DYNAMIC_URI + sessionId, STREAM_ID);\n\n        while (subscriptionA.hasNoImages() || subscriptionB.hasNoImages())\n        {\n            Tests.yield();\n        }\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscriptionA, fragmentHandlerA);\n            pollForFragment(subscriptionB, fragmentHandlerB);\n        }\n\n        verifyFragments(fragmentHandlerA, numMessagesToSend);\n        verifyFragments(fragmentHandlerB, numMessagesToSend);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldSendToTwoPortsWithDynamicSingleDriver()\n    {\n        final int numMessagesToSend = MESSAGES_PER_TERM * 3;\n\n        launch(Tests::onError);\n\n        subscriptionA = clientA.addSubscription(SUB1_MDC_DYNAMIC_URI, STREAM_ID);\n        subscriptionB = clientA.addSubscription(SUB2_MDC_DYNAMIC_URI, STREAM_ID);\n        subscriptionC = clientA.addSubscription(SUB3_MDC_DYNAMIC_URI, STREAM_ID);\n        publication = clientA.addPublication(PUB_MDC_DYNAMIC_URI, STREAM_ID);\n\n        while (!subscriptionA.isConnected() || !subscriptionB.isConnected() || !subscriptionC.isConnected())\n        {\n            Tests.yield();\n        }\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscriptionA, fragmentHandlerA);\n            pollForFragment(subscriptionB, fragmentHandlerB);\n            pollForFragment(subscriptionC, fragmentHandlerC);\n        }\n\n        verifyFragments(fragmentHandlerA, numMessagesToSend);\n        verifyFragments(fragmentHandlerB, numMessagesToSend);\n        verifyFragments(fragmentHandlerC, numMessagesToSend);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSendToTwoPortsWithManualSingleDriver()\n    {\n        final int numMessagesToSend = MESSAGES_PER_TERM * 3;\n\n        launch(Tests::onError);\n\n        subscriptionA = clientA.addSubscription(SUB1_MDC_MANUAL_URI, STREAM_ID);\n        subscriptionB = clientA.addSubscription(SUB2_MDC_MANUAL_URI, STREAM_ID);\n\n        publication = clientA.addPublication(PUB_MDC_MANUAL_URI, STREAM_ID);\n        publication.addDestination(SUB1_MDC_MANUAL_URI);\n        publication.addDestination(SUB2_MDC_MANUAL_URI);\n\n        while (!subscriptionA.isConnected() || !subscriptionB.isConnected())\n        {\n            Tests.yield();\n        }\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, MESSAGE_LENGTH) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscriptionA, fragmentHandlerA);\n            pollForFragment(subscriptionB, fragmentHandlerB);\n        }\n\n        verifyFragments(fragmentHandlerA, numMessagesToSend);\n        verifyFragments(fragmentHandlerB, numMessagesToSend);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addDestinationWithSpySubscriptionsShouldFailWithRegistrationException()\n    {\n        testWatcher.ignoreErrorsMatching(s -> s.contains(\"spies are invalid\"));\n        final ErrorHandler mockErrorHandler = mock(ErrorHandler.class);\n        launch(mockErrorHandler);\n\n        publication = clientA.addPublication(PUB_MDC_MANUAL_URI, STREAM_ID);\n        final RegistrationException registrationException = assertThrows(\n            RegistrationException.class,\n            () -> publication.addDestination(CommonContext.SPY_PREFIX + PUB_MDC_DYNAMIC_URI));\n\n        assertThat(registrationException.getMessage(), containsString(\"spies are invalid\"));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldManuallyRemovePortDuringActiveStream() throws InterruptedException\n    {\n        final int numMessagesToSend = MESSAGES_PER_TERM * 3;\n        final int numMessageForSub2 = 10;\n        final CountDownLatch unavailableImage = new CountDownLatch(1);\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n\n        launch(Tests::onError);\n\n        subscriptionA = clientA.addSubscription(SUB1_MDC_MANUAL_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(\n            SUB2_MDC_MANUAL_URI, STREAM_ID, null, (image) -> unavailableImage.countDown());\n\n        publication = clientA.addPublication(PUB_MDC_MANUAL_URI, STREAM_ID);\n        publication.addDestination(SUB1_MDC_MANUAL_URI);\n        publication.addDestination(SUB2_MDC_MANUAL_URI);\n\n        while (!subscriptionA.isConnected() || !subscriptionB.isConnected())\n        {\n            Tests.yield();\n        }\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, MESSAGE_LENGTH) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscriptionA, fragmentHandlerA);\n\n            if (i < numMessageForSub2)\n            {\n                pollForFragment(subscriptionB, fragmentHandlerB);\n            }\n            else\n            {\n                if (0 == subscriptionB.poll(fragmentHandlerB, FRAGMENT_LIMIT))\n                {\n                    Tests.yield();\n                }\n            }\n\n            if (i == numMessageForSub2 - 1)\n            {\n                publication.removeDestination(SUB2_MDC_MANUAL_URI);\n            }\n        }\n\n        unavailableImage.await();\n\n        verifyFragments(fragmentHandlerA, numMessagesToSend);\n        verifyFragments(fragmentHandlerB, numMessageForSub2);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldManuallyAddPortDuringActiveStream() throws InterruptedException\n    {\n        final int numMessagesToSend = MESSAGES_PER_TERM * 3;\n        final int numMessageForSub2 = 10;\n        final CountingFragmentHandler fragmentHandlerA = new CountingFragmentHandler(\"fragmentHandlerA\");\n        final CountingFragmentHandler fragmentHandlerB = new CountingFragmentHandler(\"fragmentHandlerB\");\n        final Supplier<String> messageSupplierA = fragmentHandlerA::toString;\n        final Supplier<String> messageSupplierB = fragmentHandlerB::toString;\n        final CountDownLatch availableImage = new CountDownLatch(1);\n        final MutableLong position = new MutableLong(0);\n        final MutableInteger messagesSent = new MutableInteger(0);\n        final Supplier<String> positionSupplier =\n            () -> \"Failed to publish, position: \" + position + \", sent: \" + messagesSent;\n\n        launch(Tests::onError);\n\n        subscriptionA = clientA.addSubscription(SUB1_MDC_MANUAL_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(\n            SUB2_MDC_MANUAL_URI, STREAM_ID, (image) -> availableImage.countDown(), null);\n\n        publication = clientA.addPublication(PUB_MDC_MANUAL_URI, STREAM_ID);\n        publication.addDestination(SUB1_MDC_MANUAL_URI);\n\n        Tests.awaitConnected(subscriptionA);\n\n        while (messagesSent.value < numMessagesToSend)\n        {\n            position.value = publication.offer(buffer, 0, MESSAGE_LENGTH);\n\n            if (0 <= position.value)\n            {\n                messagesSent.increment();\n            }\n            else\n            {\n                Tests.yieldingIdle(positionSupplier);\n            }\n\n            subscriptionA.poll(fragmentHandlerA, FRAGMENT_LIMIT);\n\n            if (messagesSent.value > (numMessagesToSend - numMessageForSub2))\n            {\n                subscriptionB.poll(fragmentHandlerB, FRAGMENT_LIMIT);\n            }\n\n            if (messagesSent.value == (numMessagesToSend - numMessageForSub2))\n            {\n                final int published = messagesSent.value;\n                // If we add B before A has reached `published` number of messages\n                // then B will receive more than the expected `numMessageForSub2`.\n                while (fragmentHandlerA.notDone(published))\n                {\n                    if (subscriptionA.poll(fragmentHandlerA, FRAGMENT_LIMIT) <= 0)\n                    {\n                        Tests.yieldingIdle(messageSupplierA);\n                    }\n                }\n\n                publication.addDestination(SUB2_MDC_MANUAL_URI);\n                availableImage.await();\n            }\n        }\n\n        while (fragmentHandlerA.notDone(numMessagesToSend) || fragmentHandlerB.notDone(numMessageForSub2))\n        {\n            if (fragmentHandlerA.notDone(numMessagesToSend) &&\n                subscriptionA.poll(fragmentHandlerA, FRAGMENT_LIMIT) <= 0)\n            {\n                Tests.yieldingIdle(messageSupplierA);\n            }\n\n            if (fragmentHandlerB.notDone(numMessageForSub2) &&\n                subscriptionB.poll(fragmentHandlerB, FRAGMENT_LIMIT) <= 0)\n            {\n                Tests.yieldingIdle(messageSupplierB);\n            }\n        }\n    }\n\n    @Test\n    void shouldSpyOnDynamicMdcConnectionWithWildcardPortUsingTags()\n    {\n        launch(Tests::onError);\n\n        final long pubTag = clientA.nextCorrelationId();\n\n        final String uri = new ChannelUriStringBuilder(\"aeron:udp?control=localhost:0|control-mode=dynamic|ssc=true\")\n            .tags(pubTag, null)\n            .build();\n\n        final String spyUri = new ChannelUriStringBuilder().prefix(SPY_QUALIFIER)\n            .media(\"udp\")\n            .tags(pubTag, null)\n            .build();\n\n        publication = clientA.addPublication(uri, STREAM_ID);\n        subscriptionA = clientA.addSubscription(spyUri, STREAM_ID);\n\n        Tests.awaitConnected(publication);\n\n        while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n        {\n            Tests.yield();\n        }\n\n        pollForFragment(subscriptionA, fragmentHandlerA);\n    }\n\n    @Test\n    void shouldHandleSubscriptionsIfUsingTagsAndEndpointsMatchButSessionIdInUse()\n    {\n        launch(Tests::onError);\n\n        try (\n            Publication pub1 = clientA.addExclusivePublication(PUB_MDC_DYNAMIC_URI, STREAM_ID);\n            Publication pub2 = clientA.addExclusivePublication(PUB_MDC_DYNAMIC_URI, STREAM_ID))\n        {\n\n            final String sub1Uri = new ChannelUriStringBuilder(SUB1_MDC_DYNAMIC_URI)\n                .endpoint(\"127.0.0.1:0\")\n                .tags(\"1001\")\n                .sessionId(pub1.sessionId())\n                .build();\n\n            final String sub2Uri = new ChannelUriStringBuilder(SUB1_MDC_DYNAMIC_URI)\n                .endpoint(\"127.0.0.1:0\")\n                .tags(\"1001\")\n                .sessionId(pub2.sessionId())\n                .build();\n\n            try (\n                Subscription sub1 = clientB.addSubscription(sub1Uri, STREAM_ID);\n                Subscription sub2 = clientB.addSubscription(sub2Uri, STREAM_ID))\n            {\n                Tests.awaitConnected(sub1);\n                Tests.awaitConnected(sub2);\n                Tests.awaitConnected(pub1);\n                Tests.awaitConnected(pub2);\n\n                while (pub1.offer(buffer, 0, buffer.capacity()) < 0L)\n                {\n                    Tests.yield();\n                }\n\n                while (pub2.offer(buffer, 0, buffer.capacity()) < 0L)\n                {\n                    Tests.yield();\n                }\n\n                final long nowMs = System.currentTimeMillis();\n                long sub1Count = 0;\n                long sub2Count = 0;\n                while (System.currentTimeMillis() - nowMs < 500L)\n                {\n                    sub1Count += sub1.poll((a, b, c, d) -> {}, 10);\n                    sub2Count += sub2.poll((a, b, c, d) -> {}, 10);\n\n                    assertThat(sub1Count, lessThan(2L));\n                    assertThat(sub2Count, lessThan(2L));\n                }\n            }\n        }\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(5)\n    void shouldNotAllowMdcSubscriptionsWhenChannelHasControlButNotSpecifiedAsMdc()\n    {\n        launch(Tests::onError);\n\n        try (\n            Publication pub = clientA.addPublication(\n                \"aeron:udp?control=localhost:24325|endpoint=localhost:10000\", STREAM_ID);\n            Subscription subOk = clientA.addSubscription(\n                \"aeron:udp?control=localhost:24325|endpoint=localhost:10000\", STREAM_ID);\n            Subscription subWrong = clientA.addSubscription(\n                \"aeron:udp?control=localhost:24325|endpoint=localhost:10001\", STREAM_ID))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(subOk);\n\n            final long deadlineMs = System.currentTimeMillis() + 2_000;\n            while (System.currentTimeMillis() < deadlineMs)\n            {\n                assertFalse(subWrong.isConnected());\n                Tests.sleep(1);\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    @SlowTest\n    void shouldAllowMdcDestinationEndpointsToBeShared()\n    {\n        launch(Tests::onError);\n\n        try (\n            Publication mdcA = clientA.addPublication(PUB_MDC_MANUAL_URI, STREAM_ID);\n            Publication mdcB = clientA.addPublication(PUB_MDC_MANUAL_URI, STREAM_ID + 1);\n            Subscription subA = clientB.addSubscription(SUB1_MDC_MANUAL_URI, STREAM_ID);\n            Subscription subB = clientB.addSubscription(SUB1_MDC_MANUAL_URI, STREAM_ID + 1))\n        {\n            mdcA.addDestination(SUB1_MDC_MANUAL_URI);\n            mdcB.addDestination(SUB1_MDC_MANUAL_URI);\n            Tests.awaitConnected(mdcA);\n            Tests.awaitConnected(mdcB);\n            Tests.awaitConnected(subA);\n            Tests.awaitConnected(subB);\n\n            while (mdcA.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subA, fragmentHandlerA);\n            assertNoFragmentsReceived(subB, 1_000L);\n\n            while (mdcB.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subB, fragmentHandlerA);\n            assertNoFragmentsReceived(subA, 1_000L);\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    @SlowTest\n    void shouldRemoveDestinationUsingRegistrationId()\n    {\n        driverAContext.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(1));\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(1));\n\n        launch(Tests::onError);\n\n        try (\n            Publication mdc = clientA.addPublication(PUB_MDC_MANUAL_URI, STREAM_ID);\n            Subscription sub1 = clientB.addSubscription(SUB1_MDC_MANUAL_URI, STREAM_ID);\n            Subscription sub2 = clientB.addSubscription(SUB2_MDC_MANUAL_URI, STREAM_ID))\n        {\n            final long registrationId1 = mdc.asyncAddDestination(SUB1_MDC_MANUAL_URI);\n            final long registrationId2 = mdc.asyncAddDestination(SUB2_MDC_MANUAL_URI);\n            while (clientA.isCommandActive(registrationId2))\n            {\n                Tests.yield();\n            }\n\n            Tests.await(\"Connections\", mdc::isConnected, sub1::isConnected, sub2::isConnected);\n\n            while (mdc.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(sub1, fragmentHandlerA);\n            pollForFragment(sub2, fragmentHandlerB);\n\n            mdc.removeDestination(registrationId2);\n\n            Tests.await(\"Disconnected\", () -> !sub2.isConnected());\n\n            while (mdc.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(sub1, fragmentHandlerA);\n            assertNoFragmentsReceived(sub2, 1_000L);\n\n            final long correlationId = mdc.asyncRemoveDestination(registrationId1);\n            Tests.await(\"Command Active\", () -> !clientA.isCommandActive(correlationId));\n            Tests.await(\"Disconnected\", () -> !sub1.isConnected(), () -> !mdc.isConnected());\n        }\n    }\n\n    private static void assertNoFragmentsReceived(final Subscription subB, final long noMessageTimeout)\n    {\n        final long t0 = System.currentTimeMillis();\n        while (System.currentTimeMillis() - t0 < noMessageTimeout)\n        {\n            assertEquals(0, subB.poll((a, b, c, d) -> {}, 10));\n            Tests.yield();\n        }\n    }\n\n    private static void pollForFragment(final Subscription subscription, final FragmentHandler handler)\n    {\n        final long startNs = System.nanoTime();\n        long nowNs = startNs;\n        int totalFragments = 0;\n\n        do\n        {\n            final int numFragments = subscription.poll(handler, FRAGMENT_LIMIT);\n            if (numFragments <= 0)\n            {\n                Thread.yield();\n                Tests.checkInterruptStatus();\n                nowNs = System.nanoTime();\n            }\n            else\n            {\n                totalFragments += numFragments;\n            }\n        }\n        while (totalFragments < 1 && ((nowNs - startNs) < TimeUnit.SECONDS.toNanos(10)));\n    }\n\n    private void verifyFragments(final FragmentHandler fragmentHandler, final int numMessagesToSend)\n    {\n        verify(fragmentHandler, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(MESSAGE_LENGTH),\n            any(Header.class));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MultiDestinationSubscriptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.ReceiveLocalSocketAddress;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.hamcrest.Matcher;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.file.Path;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.function.Supplier;\n\nimport static io.aeron.AeronCounters.DRIVER_RECEIVE_CHANNEL_STATUS_TYPE_ID;\nimport static io.aeron.ChannelUri.SPY_QUALIFIER;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\nimport static org.hamcrest.CoreMatchers.anyOf;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MultiDestinationSubscriptionTest\n{\n    private static final String UNICAST_ENDPOINT_A = \"localhost:24325\";\n    private static final String UNICAST_ENDPOINT_B = \"localhost:24326\";\n\n    private static final String PUB_UNICAST_URI = \"aeron:udp?endpoint=localhost:24325\";\n    private static final String PUB_MULTICAST_URI = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\";\n    private static final String PUB_MDC_URI = \"aeron:udp?control=localhost:24325|control-mode=dynamic\";\n    private static final String PUB_IPC_URI = \"aeron:ipc\";\n\n    private static final String SUB_URI = \"aeron:udp?control-mode=manual\";\n    private static final String SUB_MDC_DESTINATION_URI = \"aeron:udp?endpoint=localhost:24326|control=localhost:24325\";\n\n    private static final int STREAM_ID = 1001;\n\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int NUM_MESSAGES_PER_TERM = 64;\n    private static final int MESSAGE_LENGTH =\n        (TERM_BUFFER_LENGTH / NUM_MESSAGES_PER_TERM) - DataHeaderFlyweight.HEADER_LENGTH;\n\n    @TempDir\n    private Path tempDir;\n    private final MediaDriver.Context driverContextA = new MediaDriver.Context();\n    private final MediaDriver.Context driverContextB = new MediaDriver.Context();\n\n    private Aeron clientA;\n    private Aeron clientB;\n    private TestMediaDriver driverA;\n    private TestMediaDriver driverB;\n    private Publication publicationA;\n    private Publication publicationB;\n    private Subscription subscription;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n    private final FragmentHandler fragmentHandler = mock(FragmentHandler.class);\n    private final FragmentHandler copyFragmentHandler = mock(FragmentHandler.class);\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private void launch(final ErrorHandler errorHandler)\n    {\n        final String baseDirA = tempDir.resolve(\"A\").toString();\n\n        buffer.putInt(0, 1);\n\n        driverContextA\n            .errorHandler(errorHandler)\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirA)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverA = TestMediaDriver.launch(driverContextA, testWatcher);\n        testWatcher.dataCollector().add(driverA.context().aeronDirectory());\n        clientA = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverContextA.aeronDirectoryName()));\n    }\n\n    private void launchSecond()\n    {\n        final String baseDirB = tempDir.resolve(\"B\").toString();\n\n        driverContextB\n            .errorHandler(Tests::onError)\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirB)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverB = TestMediaDriver.launch(driverContextB, testWatcher);\n        testWatcher.dataCollector().add(driverB.context().aeronDirectory());\n        clientB = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverContextB.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void closeEverything()\n    {\n        CloseHelper.closeAll(clientA, clientB, driverA, driverB);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void subscriptionCloseShouldAlsoCloseMediaDriverPorts()\n    {\n        launch(Tests::onError);\n\n        final String publicationChannelA = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .endpoint(UNICAST_ENDPOINT_A)\n            .build();\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(publicationChannelA);\n\n        CloseHelper.closeAll(subscription, clientA);\n\n        clientA = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverContextA.aeronDirectoryName()));\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(publicationChannelA);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @EnabledOnOs(OS.LINUX)\n    void destinationShouldInheritSocketBufferLengthsFromSubscription()\n    {\n        launch(Tests::onError);\n\n        final String publicationChannelA = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .endpoint(\"127.0.0.1:24325\")\n            .build();\n\n        subscription = clientA.addSubscription(SUB_URI + \"|so-sndbuf=32768|so-rcvbuf=32768|rcv-wnd=32768\", STREAM_ID);\n        subscription.addDestination(publicationChannelA);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addDestinationWithSpySubscriptionBeforeAddPublication()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(SPY_QUALIFIER + \":\" + PUB_UNICAST_URI);\n\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addDestinationWithSpySubscriptionAfterAddPublication()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n\n        subscription.addDestination(SPY_QUALIFIER + \":\" + PUB_UNICAST_URI);\n\n        Tests.awaitConnected(subscription);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addDestinationWithSpySubscriptionThenDisconnectOnPublicationClose()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(SPY_QUALIFIER + \":\" + PUB_UNICAST_URI);\n\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n        CloseHelper.close(publicationA);\n        Tests.awaitConnections(subscription, 0);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addDestinationWithSpySubscriptionThenRemoveDestination()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(SPY_QUALIFIER + \":\" + PUB_UNICAST_URI);\n\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n        subscription.removeDestination(SPY_QUALIFIER + \":\" + PUB_UNICAST_URI);\n        Tests.awaitConnections(subscription, 0);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addAndRemoveNetworkDestination()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(PUB_UNICAST_URI);\n\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n\n        subscription.removeDestination(PUB_UNICAST_URI);\n        Tests.awaitConnections(subscription, 0);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addDestinationWithIpcSubscriptionBeforeAddPublication()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(PUB_IPC_URI);\n\n        publicationA = clientA.addPublication(PUB_IPC_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addDestinationWithIpcSubscriptionAfterAddPublication()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        publicationA = clientA.addPublication(PUB_IPC_URI, STREAM_ID);\n\n        subscription.addDestination(PUB_IPC_URI);\n\n        Tests.awaitConnected(subscription);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addDestinationWithIpcSubscriptionThenDisconnectOnPublicationClose()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(PUB_IPC_URI);\n\n        publicationA = clientA.addPublication(PUB_IPC_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n        CloseHelper.close(publicationA);\n        Tests.awaitConnections(subscription, 0);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void addDestinationWithIpcSubscriptionThenRemoveDestination()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(PUB_IPC_URI);\n\n        publicationA = clientA.addPublication(PUB_IPC_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n        subscription.removeDestination((PUB_IPC_URI));\n        Tests.awaitConnections(subscription, 0);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSpinUpAndShutdownWithUnicast()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(PUB_UNICAST_URI);\n\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSpinUpAndShutdownWithMulticast()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        final long correlationId = subscription.asyncAddDestination(PUB_MULTICAST_URI);\n\n        publicationA = clientA.addPublication(PUB_MULTICAST_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n\n        assertFalse(clientA.isCommandActive(correlationId));\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldSpinUpAndShutdownWithDynamicMdc()\n    {\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(SUB_MDC_DESTINATION_URI);\n\n        publicationA = clientA.addPublication(PUB_MDC_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSendToSingleDestinationSubscriptionWithUnicast()\n    {\n        final int numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(PUB_UNICAST_URI);\n\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publicationA.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscription, fragmentHandler);\n        }\n\n        verifyFragments(fragmentHandler, numMessagesToSend);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldAllowMultipleMdsSubscriptions()\n    {\n        final String unicastUri2 = \"aeron:udp?endpoint=localhost:24326\";\n\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(PUB_UNICAST_URI);\n\n        final Subscription subscriptionB = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscriptionB.addDestination(unicastUri2);\n\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n        publicationB = clientA.addPublication(unicastUri2, STREAM_ID);\n\n        while (publicationA.offer(buffer, 0, buffer.capacity()) < 0)\n        {\n            Tests.yield();\n        }\n\n        while (subscription.poll(fragmentHandler, 1) <= 0)\n        {\n            Tests.yield();\n        }\n\n        // Wait a bit to ensure a message doesn't arrive.\n        Tests.sleep(1000);\n\n        assertEquals(0, subscriptionB.poll(fragmentHandler, 1));\n\n        while (publicationB.offer(buffer, 0, buffer.capacity()) < 0)\n        {\n            Tests.yield();\n        }\n\n        while (subscriptionB.poll(fragmentHandler, 1) <= 0)\n        {\n            Tests.yield();\n        }\n\n        // Wait a bit to ensure a message doesn't arrive.\n        Tests.sleep(1000);\n\n        assertEquals(0, subscription.poll(fragmentHandler, 1));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldFindMdsSubscriptionWithTags()\n    {\n        launch(Tests::onError);\n\n        final long tagA = clientA.nextCorrelationId();\n        final long tagIgnored = clientA.nextCorrelationId();\n\n        final String taggedSubUri = new ChannelUriStringBuilder(SUB_URI).tags(tagA, null).build();\n        final String taggedSubUriIgnored = new ChannelUriStringBuilder(SUB_URI).tags(tagIgnored, null).build();\n        final String referringSubUri = new ChannelUriStringBuilder().media(\"udp\").tags(tagA, null).build();\n\n        subscription = clientA.addSubscription(taggedSubUri, STREAM_ID);\n        subscription.addDestination(PUB_UNICAST_URI);\n\n        clientA.addSubscription(taggedSubUriIgnored, STREAM_ID);\n        final Subscription subscriptionA1 = clientA.addSubscription(referringSubUri, STREAM_ID);\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n\n        while (publicationA.offer(buffer, 0, buffer.capacity()) < 0)\n        {\n            Tests.yield();\n        }\n\n        while (subscriptionA1.poll(fragmentHandler, 1) <= 0)\n        {\n            Tests.yield();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldAllowMultipleMdsSubscriptionsWithTags()\n    {\n        final String unicastUri2 = \"aeron:udp?endpoint=localhost:24326\";\n\n        launch(Tests::onError);\n\n        final long tagA = clientA.nextCorrelationId();\n        final long tagB = clientA.nextCorrelationId();\n\n        final String uriA = new ChannelUriStringBuilder(SUB_URI).tags(tagA, null).build();\n        final String referringUriA = new ChannelUriStringBuilder().media(\"udp\").tags(tagA, null).build();\n        final String uriB = new ChannelUriStringBuilder(SUB_URI).tags(tagB, null).build();\n        final String referringUriB = new ChannelUriStringBuilder().media(\"udp\").tags(tagB, null).build();\n\n        subscription = clientA.addSubscription(uriA, STREAM_ID);\n        subscription.addDestination(PUB_UNICAST_URI);\n\n        try (Subscription subscriptionB = clientA.addSubscription(uriB, STREAM_ID);\n            Subscription subscriptionA1 = clientA.addSubscription(referringUriA, STREAM_ID);\n            Subscription subscriptionB1 = clientA.addSubscription(referringUriB, STREAM_ID))\n        {\n            subscriptionB.addDestination(unicastUri2);\n\n            publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n            publicationB = clientA.addPublication(unicastUri2, STREAM_ID);\n\n            while (publicationA.offer(buffer, 0, buffer.capacity()) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (subscriptionA1.poll(fragmentHandler, 1) <= 0)\n            {\n                Tests.yield();\n            }\n\n            // Wait a bit to ensure a message doesn't arrive.\n            Tests.sleep(1000);\n\n            assertEquals(0, subscriptionB1.poll(fragmentHandler, 1));\n\n            while (publicationB.offer(buffer, 0, buffer.capacity()) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (subscriptionB1.poll(fragmentHandler, 1) <= 0)\n            {\n                Tests.yield();\n            }\n\n            // Wait a bit to ensure a message doesn't arrive.\n            Tests.sleep(1000);\n\n            assertEquals(0, subscriptionA1.poll(fragmentHandler, 1));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSendToSingleDestinationMultipleSubscriptionsWithUnicast()\n    {\n        final int numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n\n        launch(Tests::onError);\n\n        final long channelTag = clientA.nextCorrelationId();\n        final long subTag = clientA.nextCorrelationId();\n\n        final String subscriptionChannel = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .tags(channelTag, subTag)\n            .controlMode(CommonContext.MDC_CONTROL_MODE_MANUAL)\n            .build();\n\n        final String copyChannel = new ChannelUriStringBuilder().media(\"udp\").tags(channelTag, subTag).build();\n\n        subscription = clientA.addSubscription(subscriptionChannel, STREAM_ID);\n        final Subscription copySubscription = clientA.addSubscription(copyChannel, STREAM_ID);\n        subscription.addDestination(PUB_UNICAST_URI);\n\n        publicationA = clientA.addPublication(PUB_UNICAST_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publicationA.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscription, fragmentHandler);\n            pollForFragment(copySubscription, copyFragmentHandler);\n        }\n\n        verifyFragments(fragmentHandler, numMessagesToSend);\n        verifyFragments(copyFragmentHandler, numMessagesToSend);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSendToSingleDestinationSubscriptionWithMulticast()\n    {\n        final int numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(PUB_MULTICAST_URI);\n\n        publicationA = clientA.addPublication(PUB_MULTICAST_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publicationA.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscription, fragmentHandler);\n        }\n\n        verifyFragments(fragmentHandler, numMessagesToSend);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldSendToSingleDestinationSubscriptionWithDynamicMdc()\n    {\n        final int numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n\n        launch(Tests::onError);\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(SUB_MDC_DESTINATION_URI);\n\n        publicationA = clientA.addPublication(PUB_MDC_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publicationA.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscription, fragmentHandler);\n        }\n\n        verifyFragments(fragmentHandler, numMessagesToSend);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSendToMultipleDestinationSubscriptionWithSameStream()\n    {\n        final int numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n        final int numMessagesToSendForA = numMessagesToSend / 2;\n        final int numMessagesToSendForB = numMessagesToSend / 2;\n\n        launch(Tests::onError);\n\n        final long channelTag = clientA.nextCorrelationId();\n        final long pubTag = clientA.nextCorrelationId();\n\n        final String publicationChannelA = new ChannelUriStringBuilder()\n            .tags(channelTag, pubTag)\n            .media(CommonContext.UDP_MEDIA)\n            .endpoint(UNICAST_ENDPOINT_A)\n            .build();\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(publicationChannelA);\n\n        publicationA = clientA.addPublication(publicationChannelA, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n\n        for (int i = 0; i < numMessagesToSendForA; i++)\n        {\n            while (publicationA.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscription, fragmentHandler);\n        }\n\n        final long position = publicationA.position();\n        final int initialTermId = publicationA.initialTermId();\n        final int positionBitsToShift = Long.numberOfTrailingZeros(publicationA.termBufferLength());\n        final int termId = LogBufferDescriptor.computeTermIdFromPosition(position, positionBitsToShift, initialTermId);\n        final int termOffset = (int)(position & (publicationA.termBufferLength() - 1));\n\n        final String publicationChannelB = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .taggedSessionId(pubTag)\n            .initialTermId(initialTermId)\n            .termId(termId)\n            .termOffset(termOffset)\n            .endpoint(UNICAST_ENDPOINT_B)\n            .build();\n\n        publicationB = clientA.addExclusivePublication(publicationChannelB, STREAM_ID);\n\n        final String destinationChannel = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .endpoint(UNICAST_ENDPOINT_B)\n            .build();\n\n        subscription.addDestination(destinationChannel);\n\n        for (int i = 0; i < numMessagesToSendForB; i++)\n        {\n            while (publicationB.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscription, fragmentHandler);\n        }\n\n        assertEquals(1, subscription.imageCount());\n        assertEquals(2, subscription.imageAtIndex(0).activeTransportCount());\n        verifyFragments(fragmentHandler, numMessagesToSend);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldMergeStreamsFromMultiplePublicationsWithSameParams()\n    {\n        final int numMessagesToSend = 30;\n        final int numMessagesToSendForA = numMessagesToSend / 2;\n        final int numMessagesToSendForB = numMessagesToSend / 2;\n\n        launch(Tests::onError);\n        launchSecond();\n\n        final String publicationChannelA = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .endpoint(UNICAST_ENDPOINT_A)\n            .build();\n\n        final String destinationB = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .endpoint(UNICAST_ENDPOINT_B)\n            .build();\n\n        subscription = clientA.addSubscription(SUB_URI, STREAM_ID);\n        subscription.addDestination(publicationChannelA);\n        subscription.addDestination(destinationB);\n\n        publicationA = clientA.addExclusivePublication(publicationChannelA, STREAM_ID);\n\n        final String publicationChannelB = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .initialPosition(0L, publicationA.initialTermId(), publicationA.termBufferLength())\n            .sessionId(publicationA.sessionId())\n            .endpoint(UNICAST_ENDPOINT_B)\n            .build();\n\n        publicationB = clientB.addExclusivePublication(publicationChannelB, STREAM_ID);\n        final MutableLong position = new MutableLong(Long.MIN_VALUE);\n        final Supplier<String> offerFailure = () -> \"Failed to offer: \" + position;\n\n        for (int i = 0; i < numMessagesToSendForA; i++)\n        {\n            while (publicationA.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscription, fragmentHandler);\n\n            while ((position.value = publicationB.offer(buffer, 0, buffer.capacity())) < 0L)\n            {\n                Tests.yieldingIdle(offerFailure);\n            }\n\n            assertEquals(0, subscription.poll(fragmentHandler, 10));\n        }\n\n        for (int i = 0; i < numMessagesToSendForB; i++)\n        {\n            while (publicationB.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment(subscription, fragmentHandler);\n\n            while (publicationA.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            assertEquals(0, subscription.poll(fragmentHandler, 10));\n        }\n\n        assertEquals(1, subscription.imageCount());\n        assertEquals(2, subscription.imageAtIndex(0).activeTransportCount());\n        verifyFragments(fragmentHandler, numMessagesToSend);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=localhost:8889\" })\n    void shouldNotReuseEndpointAcrossMultipleSubscriptionsIfAtLeastOneIsMds(final String channel)\n    {\n        final Matcher<String> errorMatcher = SystemUtil.isWindows() && TestMediaDriver.shouldRunCMediaDriver() ?\n            containsString(\"failed to bind to address\") :\n            anyOf(containsString(\"Address already in use\"), containsString(\"Address in use\"));\n        testWatcher.ignoreErrorsMatching((err) -> errorMatcher.matches(err));\n        launch(mock(ErrorHandler.class));\n\n        try (Subscription sub1 = clientA.addSubscription(channel, STREAM_ID);\n            Subscription mdsSubscription = clientA.addSubscription(\"aeron:udp?control-mode=manual\", STREAM_ID))\n        {\n            final RegistrationException exception =\n                assertThrowsExactly(RegistrationException.class, () -> mdsSubscription.addDestination(sub1.channel()));\n            assertThat(exception.getMessage(), errorMatcher);\n\n            Tests.await(() -> 1 == clientA.countersReader().getCounterValue(SystemCounterDescriptor.ERRORS.id()));\n        }\n\n        try (Subscription mdsSubscription = clientA.addSubscription(\"aeron:udp?control-mode=manual\", STREAM_ID))\n        {\n            mdsSubscription.addDestination(channel);\n\n            final RegistrationException exception =\n                assertThrowsExactly(RegistrationException.class, () -> clientA.addSubscription(channel, STREAM_ID));\n            assertThat(exception.getMessage(), errorMatcher);\n\n            Tests.await(() -> 2 == clientA.countersReader().getCounterValue(SystemCounterDescriptor.ERRORS.id()));\n        }\n\n        try (Subscription mdsSubscription1 = clientA.addSubscription(\"aeron:udp?control-mode=manual\", STREAM_ID);\n            Subscription mdsSubscription2 = clientA.addSubscription(\"aeron:udp?control-mode=manual\", STREAM_ID))\n        {\n            mdsSubscription1.addDestination(channel);\n            final RegistrationException exception =\n                assertThrowsExactly(RegistrationException.class, () -> mdsSubscription2.addDestination(channel));\n            assertThat(exception.getMessage(), errorMatcher);\n\n            Tests.await(() -> 3 == clientA.countersReader().getCounterValue(SystemCounterDescriptor.ERRORS.id()));\n        }\n    }\n\n    @Test\n    void shouldCleanupMdcDestinationWhenSubscriptionIsClosed()\n    {\n        launch(mock(ErrorHandler.class));\n\n        final CountersReader countersReader = clientA.countersReader();\n        final MutableInteger receiveSocketCount = new MutableInteger();\n        final CountersReader.MetaData socketAddressCapture = (counterId, typeId, keyBuffer, label) ->\n        {\n            if (AeronCounters.DRIVER_LOCAL_SOCKET_ADDRESS_STATUS_TYPE_ID == typeId &&\n                label.startsWith(ReceiveLocalSocketAddress.NAME))\n            {\n                receiveSocketCount.increment();\n            }\n        };\n\n        try (Publication pub1 = clientA.addExclusivePublication(\"aeron:udp?endpoint=localhost:8889\", STREAM_ID);\n            Publication pub2 = clientA.addExclusivePublication(\n                \"aeron:udp?control=localhost:5555|control-mode=dynamic\", STREAM_ID))\n        {\n            final long registrationId;\n            try (Subscription mdsSubscription = clientA.addSubscription(\"aeron:udp?control-mode=manual\", STREAM_ID))\n            {\n                registrationId = mdsSubscription.registrationId();\n                mdsSubscription.addDestination(\"aeron:udp?endpoint=localhost:8889\");\n                mdsSubscription.addDestination(\"aeron:udp?control=localhost:5555|endpoint=localhost:0\");\n\n                Tests.awaitConnected(pub1);\n                Tests.awaitConnected(pub2);\n                Tests.awaitConnected(mdsSubscription);\n\n                final int length = ThreadLocalRandom.current().nextInt(1, buffer.capacity());\n                while (pub1.offer(buffer, 0, length) < 0)\n                {\n                    Tests.yield();\n                }\n\n                final int length2 = ThreadLocalRandom.current().nextInt(1, buffer.capacity());\n                while (pub2.offer(buffer, 0, length2) < 0)\n                {\n                    Tests.yield();\n                }\n\n                countersReader.forEach(socketAddressCapture);\n                assertEquals(2, receiveSocketCount.intValue());\n                assertNotEquals(NULL_COUNTER_ID, countersReader.findByTypeIdAndRegistrationId(\n                    DRIVER_RECEIVE_CHANNEL_STATUS_TYPE_ID, registrationId));\n            }\n\n            Tests.await(() ->\n            {\n                Tests.sleep(10);\n                receiveSocketCount.set(0);\n                countersReader.forEach(socketAddressCapture);\n                return 0 == receiveSocketCount.intValue();\n            });\n\n            Tests.await(() -> NULL_COUNTER_ID == countersReader.findByTypeIdAndRegistrationId(\n                DRIVER_RECEIVE_CHANNEL_STATUS_TYPE_ID, registrationId));\n        }\n    }\n\n    private void pollForFragment(final Subscription subscription, final FragmentHandler handler)\n    {\n        while (0 == subscription.poll(handler, 1))\n        {\n            Tests.yield();\n        }\n    }\n\n    private void verifyFragments(final FragmentHandler fragmentHandler, final int numMessagesToSend)\n    {\n        verify(fragmentHandler, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class), anyInt(), eq(MESSAGE_LENGTH), any(Header.class));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MultiDriverTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MultiDriverTest\n{\n    private static final String MULTICAST_URI = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\";\n\n    private static final int STREAM_ID = 1001;\n    private static final ThreadingMode THREADING_MODE = ThreadingMode.SHARED;\n\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int NUM_MESSAGES_PER_TERM = 64;\n    private static final int MESSAGE_LENGTH =\n        (TERM_BUFFER_LENGTH / NUM_MESSAGES_PER_TERM) - DataHeaderFlyweight.HEADER_LENGTH;\n    private static final String ROOT_DIR = SystemUtil.tmpDirName() + \"aeron-system-tests\" + File.separator;\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n    private Aeron clientA;\n    private Aeron clientB;\n    private TestMediaDriver driverA;\n    private TestMediaDriver driverB;\n    private Publication publication;\n    private Subscription subscriptionA;\n    private Subscription subscriptionB;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n\n    private final MutableInteger fragmentCountA = new MutableInteger();\n    private final FragmentHandler fragmentHandlerA = (buffer1, offset, length, header) -> fragmentCountA.value++;\n    private final MutableInteger fragmentCountB = new MutableInteger();\n    private final FragmentHandler fragmentHandlerB = (buffer1, offset, length, header) -> fragmentCountB.value++;\n\n    private void launch()\n    {\n        final String baseDirA = ROOT_DIR + \"A\";\n        final String baseDirB = ROOT_DIR + \"B\";\n\n        buffer.putInt(0, 1);\n\n        final MediaDriver.Context driverAContext = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirA)\n            .threadingMode(THREADING_MODE);\n\n        final MediaDriver.Context driverBContext = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirB)\n            .threadingMode(THREADING_MODE);\n\n        driverA = TestMediaDriver.launch(driverAContext, testWatcher);\n        testWatcher.dataCollector().add(driverA.context().aeronDirectory());\n        driverB = TestMediaDriver.launch(driverBContext, testWatcher);\n        testWatcher.dataCollector().add(driverB.context().aeronDirectory());\n        clientA = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverAContext.aeronDirectoryName()));\n        clientB = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverBContext.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(clientA, clientB, driverA, driverB);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSpinUpAndShutdown()\n    {\n        launch();\n\n        subscriptionA = clientA.addSubscription(MULTICAST_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(MULTICAST_URI, STREAM_ID);\n        publication = clientA.addPublication(MULTICAST_URI, STREAM_ID);\n\n        while (!subscriptionA.isConnected() && !subscriptionB.isConnected())\n        {\n            Tests.yield();\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldJoinExistingStreamWithLockStepSendingReceiving() throws InterruptedException\n    {\n        final int numMessagesToSendPreJoin = NUM_MESSAGES_PER_TERM / 2;\n        final int numMessagesToSendPostJoin = NUM_MESSAGES_PER_TERM;\n\n        launch();\n\n        subscriptionA = clientA.addSubscription(MULTICAST_URI, STREAM_ID);\n        publication = clientA.addPublication(MULTICAST_URI, STREAM_ID);\n\n        for (int i = 0; i < numMessagesToSendPreJoin; i++)\n        {\n            while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            final MutableInteger fragmentsRead = new MutableInteger();\n            Tests.executeUntil(\n                () -> fragmentsRead.get() > 0,\n                (j) ->\n                {\n                    fragmentsRead.value += subscriptionA.poll(fragmentHandlerA, 10);\n                    Thread.yield();\n                },\n                Integer.MAX_VALUE,\n                TimeUnit.MILLISECONDS.toNanos(500));\n        }\n\n        final CountDownLatch newImageLatch = new CountDownLatch(1);\n        subscriptionB = clientB.addSubscription(MULTICAST_URI, STREAM_ID, (image) -> newImageLatch\n            .countDown(), null);\n\n        newImageLatch.await();\n\n        for (int i = 0; i < numMessagesToSendPostJoin; i++)\n        {\n            while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            final MutableInteger fragmentsRead = new MutableInteger();\n            Tests.executeUntil(\n                () -> fragmentsRead.get() > 0,\n                (j) ->\n                {\n                    fragmentsRead.value += subscriptionA.poll(fragmentHandlerA, 10);\n                    Thread.yield();\n                },\n                Integer.MAX_VALUE,\n                TimeUnit.MILLISECONDS.toNanos(500));\n\n            fragmentsRead.set(0);\n            Tests.executeUntil(\n                () -> fragmentsRead.get() > 0,\n                (j) ->\n                {\n                    fragmentsRead.value += subscriptionB.poll(fragmentHandlerB, 10);\n                    Thread.yield();\n                },\n                Integer.MAX_VALUE,\n                TimeUnit.MILLISECONDS.toNanos(500));\n        }\n\n        assertEquals(numMessagesToSendPreJoin + numMessagesToSendPostJoin, fragmentCountA.value);\n        assertEquals(numMessagesToSendPostJoin, fragmentCountB.value);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldJoinExistingIdleStreamWithLockStepSendingReceiving() throws InterruptedException\n    {\n        final int numMessagesToSendPreJoin = 0;\n        final int numMessagesToSendPostJoin = NUM_MESSAGES_PER_TERM;\n\n        launch();\n\n        subscriptionA = clientA.addSubscription(MULTICAST_URI, STREAM_ID);\n        publication = clientA.addPublication(MULTICAST_URI, STREAM_ID);\n\n        while (!publication.isConnected() && !subscriptionA.isConnected())\n        {\n            Tests.yield();\n        }\n\n        final CountDownLatch newImageLatch = new CountDownLatch(1);\n        subscriptionB = clientB.addSubscription(MULTICAST_URI, STREAM_ID, (image) -> newImageLatch\n            .countDown(), null);\n\n        newImageLatch.await();\n\n        for (int i = 0; i < numMessagesToSendPostJoin; i++)\n        {\n            while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            final MutableInteger fragmentsRead = new MutableInteger();\n            Tests.executeUntil(\n                () -> fragmentsRead.get() > 0,\n                (j) ->\n                {\n                    fragmentsRead.value += subscriptionA.poll(fragmentHandlerA, 10);\n                    Thread.yield();\n                },\n                Integer.MAX_VALUE,\n                TimeUnit.MILLISECONDS.toNanos(500));\n\n            fragmentsRead.set(0);\n            Tests.executeUntil(\n                () -> fragmentsRead.get() > 0,\n                (j) ->\n                {\n                    fragmentsRead.value += subscriptionB.poll(fragmentHandlerB, 10);\n                    Thread.yield();\n                },\n                Integer.MAX_VALUE,\n                TimeUnit.MILLISECONDS.toNanos(500));\n        }\n\n        assertEquals(numMessagesToSendPreJoin + numMessagesToSendPostJoin, fragmentCountA.value);\n        assertEquals(numMessagesToSendPostJoin, fragmentCountB.value);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MultiGapLossAndRecoverySystemTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.Random;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\n\npublic class MultiGapLossAndRecoverySystemTest\n{\n    private static final int TOTAL_GAPS = 100;\n    private static final int GAP_LENGTH = 128;\n    private static final int GAP_RADIX = 4096;\n    private static final int TERM_ID = 0;\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .aeronDirectoryName(CommonContext.generateRandomDirName())\n        .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n        .threadingMode(ThreadingMode.SHARED);\n    private TestMediaDriver driver;\n\n    @BeforeEach\n    void setUp()\n    {\n        TestMediaDriver.enableMultiGapLoss(context, TERM_ID, GAP_RADIX, GAP_LENGTH, TOTAL_GAPS);\n    }\n\n    private void launch(final MediaDriver.Context context)\n    {\n        driver = TestMediaDriver.launch(context, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietClose(driver);\n    }\n\n    @Test\n    void shouldSendStreamOfDataAndHandleMultipleGaps()\n    {\n        launch(context);\n\n        sendAndReceive(\n            \"aeron:udp?endpoint=localhost:10000|term-length=1m|init-term-id=0|term-id=0|term-offset=0|nak-delay=50us\",\n            10 * 1024 * 1024\n        );\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final long retransmitCount = aeron.countersReader()\n                .getCounterValue(SystemCounterDescriptor.RETRANSMITS_SENT.id());\n            final long nakCount = aeron.countersReader()\n                .getCounterValue(SystemCounterDescriptor.NAK_MESSAGES_SENT.id());\n            // Prior to the advent of the UnicastRetransmitHandler, we'd end up dropping NAKs when we blew past\n            // the max retransmit action limit.  In that case, we'd have to send more than the 100 NAKs required.\n            // Now, however, the UnicastRetransmitHandler treats new NAKs as a tacit admission that the previous\n            // NAK did its job and the prior gap was filled, so we can immediately handle the new NAK.\n\n            final long gapCount = TOTAL_GAPS;\n            final long expectedCountWithBuffer = gapCount * 2;\n            assertThat(\n                retransmitCount,\n                allOf(greaterThanOrEqualTo(gapCount), lessThanOrEqualTo(expectedCountWithBuffer)));\n            assertThat(\n                nakCount,\n                allOf(greaterThanOrEqualTo(gapCount), lessThanOrEqualTo(expectedCountWithBuffer)));\n            assertThat(nakCount, lessThanOrEqualTo(expectedCountWithBuffer));\n        }\n    }\n\n    private void sendAndReceive(final String channel, final int publicationLength)\n    {\n        final int streamId = 10000;\n        final byte[] input = new byte[publicationLength];\n        final byte[] output = new byte[publicationLength];\n        final Random r = new Random(1);\n        r.nextBytes(input);\n        final UnsafeBuffer sendBuffer = new UnsafeBuffer();\n\n        int inputPosition = TERM_ID;\n        final MutableInteger outputPosition = new MutableInteger(TERM_ID);\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            ExclusivePublication pub = aeron.addExclusivePublication(channel, streamId);\n            Subscription sub = aeron.addSubscription(channel, streamId))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            final FragmentAssembler handler = new FragmentAssembler(\n                (buffer, offset, length, header) ->\n                {\n                    buffer.getBytes(offset, output, outputPosition.get(), length);\n                    outputPosition.addAndGet(length);\n                });\n\n            while (inputPosition < input.length || outputPosition.get() < output.length)\n            {\n                if (inputPosition < input.length)\n                {\n                    final int length = Math.min(input.length - inputPosition, pub.maxMessageLength());\n                    sendBuffer.wrap(input, inputPosition, length);\n                    if (TERM_ID < pub.offer(sendBuffer))\n                    {\n                        inputPosition += length;\n                    }\n                }\n\n                if (outputPosition.get() < output.length)\n                {\n                    sub.poll(handler, 10);\n                }\n            }\n        }\n\n        assertArrayEquals(input, output);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MultiSubscriberTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.mockito.ArgumentCaptor;\n\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static io.aeron.AeronCounters.DRIVER_RECEIVER_HWM_TYPE_ID;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MultiSubscriberTest\n{\n    private static final String CHANNEL_1 = \"aeron:udp?endpoint=localhost:24325|fruit=banana\";\n    private static final String CHANNEL_2 = \"aeron:udp?endpoint=localhost:24325|fruit=apple\";\n    private static final String CHANNEL_3 = \"aeron:udp?endpoint=localhost:24326|fruit=apple\";\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .errorHandler(Tests::onError)\n        .threadingMode(ThreadingMode.SHARED);\n    private final TestMediaDriver driver = TestMediaDriver.launch(context, watcher);\n\n    private final Aeron aeron = Aeron.connect();\n\n    @BeforeEach\n    void setUp()\n    {\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReceiveMessageOnSeparateSubscriptions()\n    {\n        final FragmentHandler mockFragmentHandlerOne = mock(FragmentHandler.class);\n        final FragmentHandler mockFragmentHandlerTwo = mock(FragmentHandler.class);\n\n        final FragmentAssembler adapterOne = new FragmentAssembler(mockFragmentHandlerOne);\n        final FragmentAssembler adapterTwo = new FragmentAssembler(mockFragmentHandlerTwo);\n\n        try (Subscription subscriptionOne = aeron.addSubscription(CHANNEL_1, STREAM_ID);\n            Subscription subscriptionTwo = aeron.addSubscription(CHANNEL_2, STREAM_ID);\n            Publication publication = aeron.addPublication(CHANNEL_1, STREAM_ID))\n        {\n            final byte[] expectedBytes = \"Hello, World! here is a small message\".getBytes();\n            final UnsafeBuffer srcBuffer = new UnsafeBuffer(expectedBytes);\n\n            assertEquals(0, subscriptionOne.poll(adapterOne, FRAGMENT_COUNT_LIMIT));\n            assertEquals(0, subscriptionTwo.poll(adapterTwo, FRAGMENT_COUNT_LIMIT));\n\n            while (!subscriptionOne.isConnected() || !subscriptionTwo.isConnected())\n            {\n                Tests.yield();\n            }\n\n            while (publication.offer(srcBuffer) < 0L)\n            {\n                Tests.yield();\n            }\n\n            while (subscriptionOne.poll(adapterOne, FRAGMENT_COUNT_LIMIT) == 0)\n            {\n                Tests.yield();\n            }\n\n            while (subscriptionTwo.poll(adapterTwo, FRAGMENT_COUNT_LIMIT) == 0)\n            {\n                Tests.yield();\n            }\n\n            verifyData(srcBuffer, mockFragmentHandlerOne);\n            verifyData(srcBuffer, mockFragmentHandlerTwo);\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldReportDifferentUriForEachSubscription()\n    {\n        final AtomicLong subAImageCorrelationId = new AtomicLong(-1);\n        final AtomicLong subBImageCorrelationId = new AtomicLong(-1);\n        final String channel = \"aeron:udp?endpoint=localhost:24325\";\n        final String channelA = channel + \"|reliable=false\";\n        final String channelB = channel + \"|reliable=true\";\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            Publication publication1 = aeron.addPublication(channel, STREAM_ID);\n            Publication publication2 = aeron.addPublication(channel, STREAM_ID + 1);\n            Subscription subA = aeron.addSubscription(\n                channelA,\n                STREAM_ID,\n                image -> subAImageCorrelationId.set(image.correlationId()),\n                image -> {});\n            Subscription subB = aeron.addSubscription(\n                channelB,\n                STREAM_ID + 1,\n                image -> subBImageCorrelationId.set(image.correlationId()),\n                image -> {}))\n        {\n            Tests.awaitConnected(publication1);\n            Tests.awaitConnected(publication2);\n            Tests.awaitConnected(subA);\n            Tests.awaitConnected(subB);\n\n            while (-1 == subAImageCorrelationId.get() && -1 == subBImageCorrelationId.get())\n            {\n                Tests.yield();\n            }\n\n            int counterIdA;\n            while (-1 == (counterIdA = aeron.countersReader()\n                .findByTypeIdAndRegistrationId(DRIVER_RECEIVER_HWM_TYPE_ID, subAImageCorrelationId.get())))\n            {\n                Tests.yield();\n            }\n            assertThat(aeron.countersReader().getCounterLabel(counterIdA), containsString(channelA));\n\n            int counterIdB;\n            while (-1 == (counterIdB = aeron.countersReader()\n                .findByTypeIdAndRegistrationId(DRIVER_RECEIVER_HWM_TYPE_ID, subBImageCorrelationId.get())))\n            {\n                Tests.yield();\n            }\n            assertThat(aeron.countersReader().getCounterLabel(counterIdB), containsString(channelB));\n        }\n    }\n\n    private void verifyData(final UnsafeBuffer srcBuffer, final FragmentHandler mockFragmentHandler)\n    {\n        final ArgumentCaptor<DirectBuffer> bufferArg = ArgumentCaptor.forClass(DirectBuffer.class);\n        final ArgumentCaptor<Integer> offsetArg = ArgumentCaptor.forClass(Integer.class);\n\n        verify(mockFragmentHandler, times(1)).onFragment(\n            bufferArg.capture(), offsetArg.capture(), eq(srcBuffer.capacity()), any(Header.class));\n\n        final DirectBuffer capturedBuffer = bufferArg.getValue();\n        final int offset = offsetArg.getValue();\n        for (int i = 0; i < srcBuffer.capacity(); i++)\n        {\n            final int index = offset + i;\n            assertEquals(srcBuffer.getByte(i), capturedBuffer.getByte(index), \"same at \" + index);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MultipathTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\n@ExtendWith(InterruptingTestCallback.class)\npublic class MultipathTest\n{\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(5)\n    void shouldEndMultipath()\n    {\n        final String mdcUri = \"aeron:udp?control-mode=manual\";\n        final String endpoint1 = \"aeron:udp?endpoint=localhost:10001|group=true\";\n        final String endpoint2 = \"aeron:udp?endpoint=localhost:10002|group=true\";\n        final int streamId = 10000;\n\n        try (TestMediaDriver driver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(CommonContext.generateRandomDirName()), testWatcher);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            Publication multipathPub = aeron.addPublication(mdcUri, streamId);\n            Subscription multipathSub = aeron.addSubscription(mdcUri, streamId))\n        {\n            multipathPub.addDestination(endpoint1);\n            multipathPub.addDestination(endpoint2);\n            multipathSub.addDestination(endpoint1);\n            multipathSub.addDestination(endpoint2);\n\n            Tests.awaitConnected(multipathPub);\n            Tests.awaitConnected(multipathSub);\n\n            final MutableDirectBuffer msg = new UnsafeBuffer(new byte[1024]);\n            final int numMessages = 10;\n            for (int i = 0; i < numMessages; i++)\n            {\n                msg.putInt(0, i);\n\n                while (multipathPub.offer(msg) < 0)\n                {\n                    Tests.yield();\n                }\n            }\n\n            final MutableInteger counter = new MutableInteger();\n\n            while (counter.get() < numMessages)\n            {\n                multipathSub.poll(\n                    (buffer, offset, length, header) ->\n                    {\n                        if (buffer.getInt(offset) == counter.get())\n                        {\n                            counter.increment();\n                        }\n                    }, 10);\n\n                Tests.yield();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/MultipleMulticastsSubscriptionsTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass MultipleMulticastsSubscriptionsTest\n{\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n        .threadingMode(ThreadingMode.SHARED);\n    private TestMediaDriver driver;\n\n    @BeforeEach\n    void setUp()\n    {\n        assumeFalse(TestMediaDriver.shouldRunCMediaDriver() && OS.current() == OS.WINDOWS);\n    }\n\n    private void launch(final MediaDriver.Context context)\n    {\n        driver = TestMediaDriver.launch(context, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietClose(driver);\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldBindToMultipleMulticastAddressOnTheSamePort()\n    {\n        launch(context);\n\n        final String uriA = \"aeron:udp?endpoint=239.192.11.87:20123|interface=127.0.0.1\";\n        final String uriB = \"aeron:udp?endpoint=239.192.11.91:20123|interface=127.0.0.1\";\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            Publication pubA = aeron.addPublication(uriA, 10000);\n            Publication pubB = aeron.addPublication(uriB, 10000);\n            Subscription subA = aeron.addSubscription(uriA, 10000);\n            Subscription subB = aeron.addSubscription(uriB, 10000))\n        {\n            Tests.awaitConnected(pubA);\n            Tests.awaitConnected(pubB);\n            Tests.awaitConnected(subA);\n            Tests.awaitConnected(subB);\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldBindToMultipleMulticastAddressOnTheSamePortAsMdsDestinations()\n    {\n        launch(context);\n\n        final String uriA = \"aeron:udp?endpoint=239.192.11.87:20123|interface=127.0.0.1\";\n        final String uriB = \"aeron:udp?endpoint=239.192.11.91:20123|interface=127.0.0.1\";\n        final String mdsSubscription = \"aeron:udp?control-mode=manual\";\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            Publication pubA = aeron.addPublication(uriA, 10000);\n            Publication pubB = aeron.addPublication(uriB, 10000);\n            Subscription subA = aeron.addSubscription(mdsSubscription, 10000);\n            Subscription subB = aeron.addSubscription(mdsSubscription, 10000))\n        {\n            subA.addDestination(uriA);\n            subB.addDestination(uriB);\n\n            Tests.awaitConnected(pubA);\n            Tests.awaitConnected(pubB);\n            Tests.awaitConnected(subA);\n            Tests.awaitConnected(subB);\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldBindToMultipleMulticastAddressOnTheSamePortMixingMdsAndSubscriptions()\n    {\n        launch(context);\n\n        final String uriA = \"aeron:udp?endpoint=239.192.11.87:20123|interface=127.0.0.1\";\n        final String uriB = \"aeron:udp?endpoint=239.192.11.91:20123|interface=127.0.0.1\";\n        final String mdsSubscription = \"aeron:udp?control-mode=manual\";\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            Publication pubA = aeron.addPublication(uriA, 10000);\n            Publication pubB = aeron.addPublication(uriB, 10000);\n            Subscription subA = aeron.addSubscription(uriA, 10000);\n            Subscription subB = aeron.addSubscription(mdsSubscription, 10000))\n        {\n            subB.addDestination(uriB);\n\n            Tests.awaitConnected(pubA);\n            Tests.awaitConnected(pubB);\n            Tests.awaitConnected(subA);\n            Tests.awaitConnected(subB);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/NameReResolutionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.*;\nimport io.aeron.test.driver.RedirectingNameResolver;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.SleepingMillisIdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.Matcher;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.IOException;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.status.SystemCounterDescriptor.*;\nimport static io.aeron.test.driver.RedirectingNameResolver.*;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.number.OrderingComparison.greaterThan;\nimport static org.hamcrest.number.OrderingComparison.lessThan;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\nclass NameReResolutionTest\n{\n    private static final String ENDPOINT_NAME = \"ReResTestEndpoint\";\n    private static final int ENDPOINT_PORT = 24326;\n    private static final int CONTROL_PORT = 24327;\n    private static final String ENDPOINT_WITH_ERROR_NAME = \"ReResWithErrEndpoint\";\n    private static final String PUBLICATION_MANUAL_MDC_URI =\n        \"aeron:udp?control=localhost:\" + CONTROL_PORT + \"|control-mode=manual\";\n    private static final String PUBLICATION_URI = \"aeron:udp?endpoint=\" + ENDPOINT_NAME + \":\" + ENDPOINT_PORT;\n    private static final String PUBLICATION_WITH_ERROR_URI =\n        \"aeron:udp?endpoint=\" + ENDPOINT_WITH_ERROR_NAME + \":\" + ENDPOINT_PORT;\n    private static final String FIRST_SUBSCRIPTION_URI = \"aeron:udp?endpoint=127.0.0.1:\" + ENDPOINT_PORT;\n    private static final String SECOND_SUBSCRIPTION_URI = \"aeron:udp?endpoint=127.0.0.2:\" + ENDPOINT_PORT;\n    private static final String BAD_ADDRESS = \"bad.invalid\";\n\n    private static final String CONTROL_NAME = \"ReResTestControl\";\n    private static final String FIRST_PUBLICATION_DYNAMIC_MDC_URI =\n        \"aeron:udp?control=127.0.0.1:\" + CONTROL_PORT + \"|control-mode=dynamic|linger=0\";\n    private static final String SECOND_PUBLICATION_DYNAMIC_MDC_URI =\n        \"aeron:udp?control=127.0.0.2:\" + CONTROL_PORT + \"|control-mode=dynamic\";\n    private static final String SUBSCRIPTION_DYNAMIC_MDC_URI =\n        \"aeron:udp?control=\" + CONTROL_NAME + \":\" + CONTROL_PORT + \"|control-mode=dynamic\";\n    private static final String SUBSCRIPTION_MDS_URI = \"aeron:udp?control-mode=manual\";\n\n    private static final String ENDPOINT_WITH_DELAYED_CONNECT_NAME = \"test.delayed.connect\";\n\n    private static final String STUB_LOOKUP_CONFIGURATION =\n        ENDPOINT_NAME + \",127.0.0.1,127.0.0.2|\" +\n        CONTROL_NAME + \",127.0.0.1,127.0.0.2|\" +\n        ENDPOINT_WITH_ERROR_NAME + \",localhost,\" + BAD_ADDRESS + \"|\" +\n        ENDPOINT_WITH_DELAYED_CONNECT_NAME + \",192.168.0.0,127.0.0.1|\";\n\n    private static final int STREAM_ID = 1001;\n\n    private Aeron client;\n    private TestMediaDriver driver;\n    private Subscription subscription;\n    private Publication publication;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @RegisterExtension\n    final InterruptingTestCallback testCallback = new InterruptingTestCallback();\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[4096]);\n    private final FragmentHandler handler = mock(FragmentHandler.class);\n    private CountersReader countersReader;\n\n    @BeforeEach\n    void before()\n    {\n        assumeBindAddressAvailable(\"127.0.0.1\");\n        assumeBindAddressAvailable(\"127.0.0.2\");\n\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .nameResolver(new RedirectingNameResolver(STUB_LOOKUP_CONFIGURATION))\n            .nameResolverThresholdNs(1);\n\n        driver = TestMediaDriver.launch(context, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(context.aeronDirectory());\n\n        client = Aeron.connect(new Aeron.Context().aeronDirectoryName(context.aeronDirectoryName()));\n        countersReader = client.countersReader();\n    }\n\n    @AfterEach\n    void after()\n    {\n        if (null != client)\n        {\n            CloseHelper.closeAll(client, driver);\n        }\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void shouldReResolveEndpointOnNotConnected()\n    {\n        final long initialResolutionChanges = countersReader.getCounterValue(RESOLUTION_CHANGES.id());\n\n        buffer.putInt(0, 1);\n\n        subscription = client.addSubscription(FIRST_SUBSCRIPTION_URI, STREAM_ID);\n        publication = client.addPublication(PUBLICATION_URI, STREAM_ID);\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connect to first subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to first subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on first subscription\");\n        }\n\n        subscription.close();\n\n        // wait for disconnect to ensure we stay in lock step\n        while (publication.isConnected())\n        {\n            Tests.sleep(10);\n        }\n\n        subscription = client.addSubscription(SECOND_SUBSCRIPTION_URI, STREAM_ID);\n        assertTrue(updateNameResolutionStatus(countersReader, ENDPOINT_NAME, USE_RE_RESOLUTION_HOST));\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connection to second subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to second subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on second subscription\");\n        }\n\n        Tests.awaitCounterDelta(countersReader, RESOLUTION_CHANGES.id(), initialResolutionChanges, 1);\n\n        verify(handler, times(2)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(BitUtil.SIZE_OF_INT),\n            any(Header.class));\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(30)\n    void shouldReResolveEndpointOnNotConnectedWhenNamePointsBackAtTheOriginalAddress()\n    {\n        final long initialResolutionChanges = countersReader.getCounterValue(RESOLUTION_CHANGES.id());\n\n        buffer.putInt(0, 1);\n\n        subscription = client.addSubscription(FIRST_SUBSCRIPTION_URI, STREAM_ID);\n        publication = client.addPublication(PUBLICATION_URI, STREAM_ID);\n\n        Tests.awaitConnected(publication);\n        Tests.awaitConnected(subscription);\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to first subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on first subscription\");\n        }\n\n        subscription.close();\n\n        // wait for disconnect to ensure we stay in lock step\n        while (publication.isConnected())\n        {\n            Tests.sleep(10);\n        }\n\n        subscription = client.addSubscription(SECOND_SUBSCRIPTION_URI, STREAM_ID);\n\n        assertTrue(updateNameResolutionStatus(countersReader, ENDPOINT_NAME, USE_RE_RESOLUTION_HOST));\n        Tests.awaitConnected(subscription);\n        Tests.awaitCounterDelta(countersReader, RESOLUTION_CHANGES.id(), initialResolutionChanges, 1);\n\n        subscription.close();\n\n        // wait for disconnect to ensure we stay in lock step\n        while (publication.isConnected())\n        {\n            Tests.sleep(10);\n        }\n\n        subscription = client.addSubscription(FIRST_SUBSCRIPTION_URI, STREAM_ID);\n\n        assertTrue(updateNameResolutionStatus(countersReader, ENDPOINT_NAME, USE_INITIAL_RESOLUTION_HOST));\n        Tests.awaitConnected(subscription);\n        Tests.awaitCounterDelta(countersReader, RESOLUTION_CHANGES.id(), initialResolutionChanges, 2);\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void shouldReResolveMdcManualEndpointOnNotConnected()\n    {\n        final long initialResolutionChanges = countersReader.getCounterValue(RESOLUTION_CHANGES.id());\n\n        buffer.putInt(0, 1);\n\n        subscription = client.addSubscription(FIRST_SUBSCRIPTION_URI, STREAM_ID);\n        publication = client.addPublication(PUBLICATION_MANUAL_MDC_URI, STREAM_ID);\n        publication.addDestination(PUBLICATION_URI);\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connect to first subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to first subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on first subscription\");\n        }\n\n        subscription.close();\n\n        // wait for disconnect to ensure we stay in lock step\n        while (publication.isConnected())\n        {\n            Tests.sleep(10);\n        }\n\n        subscription = client.addSubscription(SECOND_SUBSCRIPTION_URI, STREAM_ID);\n        assertTrue(updateNameResolutionStatus(countersReader, ENDPOINT_NAME, USE_RE_RESOLUTION_HOST));\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connection to second subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to second subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on second subscription\");\n        }\n\n        Tests.awaitCounterDelta(countersReader, RESOLUTION_CHANGES.id(), initialResolutionChanges, 1);\n\n        verify(handler, times(2)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(BitUtil.SIZE_OF_INT),\n            any(Header.class));\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void shouldHandleMdcManualEndpointInitiallyUnresolved()\n    {\n        final long initialResolutionChanges = countersReader.getCounterValue(RESOLUTION_CHANGES.id());\n\n        buffer.putInt(0, 1);\n\n        while (!updateNameResolutionStatus(countersReader, ENDPOINT_NAME, DISABLE_RESOLUTION))\n        {\n            Tests.yieldingIdle(\"Waiting for naming counter\");\n        }\n\n        subscription = client.addSubscription(FIRST_SUBSCRIPTION_URI, STREAM_ID);\n        publication = client.addPublication(PUBLICATION_MANUAL_MDC_URI, STREAM_ID);\n        publication.addDestination(PUBLICATION_URI);\n\n        assertEquals(Publication.NOT_CONNECTED, publication.offer(buffer, 0, BitUtil.SIZE_OF_INT));\n\n        updateNameResolutionStatus(countersReader, ENDPOINT_NAME, USE_INITIAL_RESOLUTION_HOST);\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connection to second subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to second subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on second subscription\");\n        }\n\n        Tests.awaitCounterDelta(countersReader, RESOLUTION_CHANGES.id(), initialResolutionChanges, 1);\n\n        verify(handler, times(1)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(BitUtil.SIZE_OF_INT),\n            any(Header.class));\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void shouldReResolveMdcDynamicControlOnNotConnected()\n    {\n        final long initialResolutionChanges = countersReader.getCounterValue(RESOLUTION_CHANGES.id());\n        buffer.putInt(0, 1);\n\n        subscription = client.addSubscription(SUBSCRIPTION_DYNAMIC_MDC_URI, STREAM_ID);\n        publication = client.addPublication(FIRST_PUBLICATION_DYNAMIC_MDC_URI, STREAM_ID);\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connect to first subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to first subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on first subscription\");\n        }\n\n        publication.close();\n\n        // wait for disconnect to ensure we stay in lock step\n        while (subscription.isConnected())\n        {\n            Tests.sleep(10);\n        }\n\n        publication = client.addPublication(SECOND_PUBLICATION_DYNAMIC_MDC_URI, STREAM_ID);\n        assertTrue(updateNameResolutionStatus(countersReader, CONTROL_NAME, USE_RE_RESOLUTION_HOST));\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connection to second subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to second subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on second subscription\");\n        }\n\n        Tests.awaitCounterDelta(countersReader, RESOLUTION_CHANGES.id(), initialResolutionChanges, 1);\n\n        verify(handler, times(2)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(BitUtil.SIZE_OF_INT),\n            any(Header.class));\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void shouldReResolveMdcDynamicControlOnManualDestinationSubscriptionOnNotConnected()\n    {\n        final long initialResolutionChanges = countersReader.getCounterValue(RESOLUTION_CHANGES.id());\n\n        buffer.putInt(0, 1);\n\n        subscription = client.addSubscription(SUBSCRIPTION_MDS_URI, STREAM_ID);\n        subscription.addDestination(SUBSCRIPTION_DYNAMIC_MDC_URI);\n        publication = client.addPublication(FIRST_PUBLICATION_DYNAMIC_MDC_URI, STREAM_ID);\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connect to first subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to first subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on first subscription\");\n        }\n\n        publication.close();\n\n        // wait for disconnect to ensure we stay in lock step\n        while (subscription.isConnected())\n        {\n            Tests.sleep(10);\n        }\n\n        publication = client.addPublication(SECOND_PUBLICATION_DYNAMIC_MDC_URI, STREAM_ID);\n        assertTrue(updateNameResolutionStatus(countersReader, CONTROL_NAME, USE_RE_RESOLUTION_HOST));\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connection to second subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to second subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on second subscription\");\n        }\n\n        Tests.awaitCounterDelta(countersReader, RESOLUTION_CHANGES.id(), initialResolutionChanges, 1);\n\n        verify(handler, times(2)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(BitUtil.SIZE_OF_INT),\n            any(Header.class));\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void shouldReportErrorOnReResolveFailure() throws IOException\n    {\n        systemTestWatcher.ignoreErrorsMatching((s) -> s.contains(\"Unable to resolve host\"));\n        buffer.putInt(0, 1);\n\n        subscription = client.addSubscription(FIRST_SUBSCRIPTION_URI, STREAM_ID);\n        publication = client.addPublication(PUBLICATION_WITH_ERROR_URI, STREAM_ID);\n        final long initialErrorCount = client.countersReader().getCounterValue(SystemCounterDescriptor.ERRORS.id());\n\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(\"No connect to first subscription\");\n        }\n\n        while (publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yieldingIdle(\"No message offer to first subscription\");\n        }\n\n        while (subscription.poll(handler, 1) <= 0)\n        {\n            Tests.yieldingIdle(\"No message received on first subscription\");\n        }\n\n        subscription.close();\n        assertTrue(updateNameResolutionStatus(countersReader, ENDPOINT_WITH_ERROR_NAME, USE_RE_RESOLUTION_HOST));\n\n        // wait for disconnect to ensure we stay in lock step\n        while (publication.isConnected())\n        {\n            Tests.sleep(10);\n        }\n\n        Tests.awaitCounterDelta(\n            client.countersReader(), SystemCounterDescriptor.ERRORS.id(), initialErrorCount, 1);\n\n        final Matcher<String> exceptionMessageMatcher =\n            CoreMatchers.containsString(\"endpoint=\" + ENDPOINT_WITH_ERROR_NAME);\n\n        SystemTests.waitForErrorToOccur(\n            client.context().aeronDirectoryName(),\n            exceptionMessageMatcher,\n            new SleepingMillisIdleStrategy(100));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldTrackNameResolutionTime()\n    {\n        final long thresholdCounter = countersReader.getCounterValue(NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED.id());\n\n        publication = client.addPublication(PUBLICATION_URI, STREAM_ID);\n        publication.close();\n\n        assertThat(countersReader.getCounterValue(NAME_RESOLVER_MAX_TIME.id()), CoreMatchers.is(greaterThan(0L)));\n        assertThat(\n            countersReader.getCounterValue(NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED.id()),\n            CoreMatchers.is(greaterThan(thresholdCounter)));\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void shouldReResolveUnicastAddressWhenSendChannelEndpointIsReused()\n    {\n        subscription = client.addSubscription(\"aeron:udp?endpoint=127.0.0.1:5555\", STREAM_ID);\n\n        final long startTimeNs = System.nanoTime();\n        while (true)\n        {\n            try (Publication pub = client.addPublication(\n                \"aeron:udp?endpoint=\" + ENDPOINT_WITH_DELAYED_CONNECT_NAME + \":5555\", STREAM_ID))\n            {\n                if (null != publication)\n                {\n                    publication.close();\n                    assertTrue(updateNameResolutionStatus(\n                        countersReader, ENDPOINT_WITH_DELAYED_CONNECT_NAME, USE_RE_RESOLUTION_HOST));\n                }\n                publication = pub;\n\n                final long deadlineNs = System.nanoTime() + TimeUnit.SECONDS.toNanos(1);\n                do\n                {\n                    if (pub.isConnected() && subscription.isConnected())\n                    {\n                        final long timeToReResolutionNs = System.nanoTime() - startTimeNs;\n                        final long destinationTimeoutNs = TimeUnit.SECONDS.toNanos(5);\n                        assertThat(\n                            timeToReResolutionNs,\n                            CoreMatchers.allOf(greaterThan(destinationTimeoutNs), lessThan(destinationTimeoutNs * 2)));\n                        return;\n                    }\n                    Tests.sleep(100, () -> \"Re-resolution not performed\");\n                }\n                while (System.nanoTime() < deadlineNs);\n            }\n        }\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(10)\n    void shouldHandleTaggedSubscriptionsAddressWithReResolutionToMdcPublications()\n    {\n        final String taggedUri = SUBSCRIPTION_DYNAMIC_MDC_URI + \"|tags=22701\";\n\n        subscription = client.addSubscription(taggedUri, STREAM_ID);\n        assertFalse(subscription.isConnected());\n\n        assertTrue(updateNameResolutionStatus(countersReader, CONTROL_NAME, USE_RE_RESOLUTION_HOST));\n\n        publication = client.addPublication(SECOND_PUBLICATION_DYNAMIC_MDC_URI, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n\n        try (Subscription taggedSub1 = client.addSubscription(\"aeron:udp?tags=22701\", STREAM_ID);\n            Subscription taggedSub2 = client.addSubscription(taggedUri, STREAM_ID))\n        {\n            Tests.awaitConnected(taggedSub1);\n            Tests.awaitConnected(taggedSub2);\n        }\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(10)\n    void shouldHandleTaggedPublication()\n    {\n        final String taggedUri = PUBLICATION_URI + \"|tags=22701\";\n\n        publication = client.addPublication(taggedUri, STREAM_ID);\n        assertFalse(publication.isConnected());\n\n        assertTrue(updateNameResolutionStatus(countersReader, ENDPOINT_NAME, USE_RE_RESOLUTION_HOST));\n\n        subscription = client.addSubscription(SECOND_SUBSCRIPTION_URI, STREAM_ID);\n\n        Tests.awaitConnected(publication);\n\n        try (Publication taggedPub1 = client.addPublication(\"aeron:udp?tags=22701\", STREAM_ID);\n            Publication taggedPub2 = client.addPublication(taggedUri, STREAM_ID))\n        {\n            Tests.awaitConnected(taggedPub1);\n            Tests.awaitConnected(taggedPub2);\n        }\n    }\n\n    private static void assumeBindAddressAvailable(final String address)\n    {\n        final String message = NetworkTestingUtil.isBindAddressAvailable(address);\n        Assumptions.assumeTrue(null == message, message);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/PathologicallySlowConsumerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.LangUtil;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.file.Path;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass PathologicallySlowConsumerTest\n{\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void before(@TempDir final Path tempDir)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(tempDir.resolve(\"aeron\").toString())\n            .threadingMode(ThreadingMode.SHARED_NETWORK)\n            .counterFreeToReuseTimeoutNs(0)\n            .flowControlReceiverTimeoutNs(TimeUnit.MILLISECONDS.toNanos(50))\n            .imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(375))\n            .publicationLingerTimeoutNs(TimeUnit.MILLISECONDS.toNanos(222))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(200))\n            .untetheredWindowLimitTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500))\n            .untetheredRestingTimeoutNs(TimeUnit.SECONDS.toNanos(5));\n        driver = TestMediaDriver.launch(context, testWatcher);\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void afterEach()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:ipc?term-length=64k\",\n        \"aeron:udp?term-length=64k|control-mode=dynamic|control=localhost:8338|fc=max\" })\n    @InterruptAfter(10)\n    void test(final String channel) throws InterruptedException\n    {\n        final int streamId = 9999;\n        final BufferClaim bufferClaim = new BufferClaim();\n        final FragmentHandler emptyHandler = (buffer, offset, length, header) -> {};\n        final ThreadLocalRandom random = ThreadLocalRandom.current();\n        final ExclusivePublication publication = aeron.addExclusivePublication(channel + \"|alias=publisher\", streamId);\n        final Subscription subscription = aeron.addSubscription(channel + \"|alias=subscriber\", streamId);\n\n        final String slowChannel = channel + \"|alias=slow-subscriber|tether=false\";\n        final AtomicInteger imageAvailable = new AtomicInteger(0);\n        final AtomicInteger imageUnavailable = new AtomicInteger(0);\n        try (Aeron slowAeron = Aeron.connect(new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .resourceLingerDurationNs(TimeUnit.MILLISECONDS.toNanos(30)));\n            Subscription slowSubscription = slowAeron.addSubscription(\n                slowChannel,\n                streamId,\n                image -> imageAvailable.incrementAndGet(),\n                image -> imageUnavailable.incrementAndGet()))\n        {\n            Tests.awaitConnected(publication);\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(slowSubscription);\n\n            for (int i = 0; i < 3; i++)\n            {\n                final int length = random.nextInt(4, 1000);\n                while (publication.tryClaim(length, bufferClaim) < 0)\n                {\n                    Tests.yield();\n                }\n\n                bufferClaim.buffer().putInt(bufferClaim.offset(), length);\n                bufferClaim.commit();\n\n                while (0 == subscription.poll(emptyHandler, 1))\n                {\n                    Tests.yield();\n                }\n            }\n\n            final Image slowImage = slowSubscription.imageAtIndex(0);\n\n            final AtomicReference<Throwable> error = new AtomicReference<>();\n            final Thread thread = new Thread(() ->\n            {\n                try\n                {\n                    assertEquals(1, slowSubscription.poll(\n                        (buffer, offset, length, header) -> Tests.sleep(2500),\n                        3));\n                }\n                catch (final Throwable ex)\n                {\n                    error.set(ex);\n                }\n            });\n            thread.start();\n\n            while (!slowImage.isClosed())\n            {\n                while (publication.tryClaim(publication.maxPayloadLength(), bufferClaim) < 0)\n                {\n                    Tests.yield();\n                }\n                bufferClaim.buffer().putLong(bufferClaim.offset(), random.nextLong());\n                bufferClaim.commit();\n\n                while (0 == subscription.poll(emptyHandler, Integer.MAX_VALUE))\n                {\n                    Tests.yield();\n                }\n\n                Tests.sleep(1);\n            }\n\n            thread.join();\n            assertEquals(1, imageUnavailable.get());\n            assertEquals(1, imageAvailable.get());\n            if (null != error.get())\n            {\n                LangUtil.rethrowUnchecked(error.get());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/PongTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.mockito.Mockito.*;\n\nclass PongTest\n{\n    private static final String PING_URI = \"aeron:udp?endpoint=localhost:24325\";\n    private static final String PONG_URI = \"aeron:udp?endpoint=localhost:24326\";\n\n    private static final int PING_STREAM_ID = 1001;\n    private static final int PONG_STREAM_ID = 1002;\n\n    private Aeron pingClient;\n    private Aeron pongClient;\n    private TestMediaDriver driver;\n    private Subscription pingSubscription;\n    private Subscription pongSubscription;\n    private Publication pingPublication;\n    private Publication pongPublication;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[4096]);\n    private final FragmentHandler pongHandler = mock(FragmentHandler.class);\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before()\n    {\n        driver = TestMediaDriver.launch(\n            new MediaDriver.Context()\n                .errorHandler(Tests::onError)\n                .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n                .threadingMode(ThreadingMode.SHARED),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        pingClient = Aeron.connect();\n        pongClient = Aeron.connect();\n\n        pingSubscription = pongClient.addSubscription(PING_URI, PING_STREAM_ID);\n        pingPublication = pingClient.addPublication(PING_URI, PING_STREAM_ID);\n\n        pongSubscription = pingClient.addSubscription(PONG_URI, PONG_STREAM_ID);\n        pongPublication = pongClient.addPublication(PONG_URI, PONG_STREAM_ID);\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(pongClient, pingClient, driver);\n    }\n\n    @Test\n    void playPingPong()\n    {\n        buffer.putInt(0, 1);\n\n        while (pingPublication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yield();\n        }\n\n        final MutableInteger fragmentsRead = new MutableInteger();\n\n        Tests.executeUntil(\n            () -> fragmentsRead.get() > 0,\n            (i) ->\n            {\n                fragmentsRead.value += pingSubscription.poll(this::echoPingHandler, 10);\n                Thread.yield();\n            },\n            Integer.MAX_VALUE,\n            TimeUnit.MILLISECONDS.toNanos(5900));\n\n        fragmentsRead.set(0);\n\n        Tests.executeUntil(\n            () -> fragmentsRead.get() > 0,\n            (i) ->\n            {\n                fragmentsRead.value += pongSubscription.poll(pongHandler, 10);\n                Thread.yield();\n            },\n            Integer.MAX_VALUE,\n            TimeUnit.MILLISECONDS.toNanos(5900));\n\n        verify(pongHandler).onFragment(\n            any(DirectBuffer.class),\n            eq(DataHeaderFlyweight.HEADER_LENGTH),\n            eq(BitUtil.SIZE_OF_INT),\n            any(Header.class));\n    }\n\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void playPingPongWithRestart()\n    {\n        buffer.putInt(0, 1);\n\n        while (pingPublication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yield();\n        }\n\n        final MutableInteger fragmentsRead = new MutableInteger();\n\n        Tests.executeUntil(\n            () -> fragmentsRead.get() > 0,\n            (i) ->\n            {\n                fragmentsRead.value += pingSubscription.poll(this::echoPingHandler, 1);\n                Thread.yield();\n            },\n            Integer.MAX_VALUE,\n            TimeUnit.MILLISECONDS.toNanos(5900));\n\n        fragmentsRead.set(0);\n\n        Tests.executeUntil(\n            () -> fragmentsRead.get() > 0,\n            (i) ->\n            {\n                fragmentsRead.value += pongSubscription.poll(pongHandler, 1);\n                Thread.yield();\n            },\n            Integer.MAX_VALUE,\n            TimeUnit.MILLISECONDS.toNanos(5900));\n\n        // close Pong side\n        pongPublication.close();\n        pingSubscription.close();\n\n        // wait for disconnect to ensure we stay in lock step\n        while (pingPublication.isConnected())\n        {\n            Tests.sleep(10);\n        }\n\n        // restart Pong side\n        pingSubscription = pingClient.addSubscription(PING_URI, PING_STREAM_ID);\n        pongPublication = pongClient.addPublication(PONG_URI, PONG_STREAM_ID);\n\n        fragmentsRead.set(0);\n\n        while (pingPublication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yield();\n        }\n\n        Tests.executeUntil(\n            () -> fragmentsRead.get() > 0,\n            (i) ->\n            {\n                fragmentsRead.value += pingSubscription.poll(this::echoPingHandler, 10);\n                Thread.yield();\n            },\n            Integer.MAX_VALUE,\n            TimeUnit.MILLISECONDS.toNanos(5900));\n\n        fragmentsRead.set(0);\n\n        Tests.executeUntil(\n            () -> fragmentsRead.get() > 0,\n            (i) ->\n            {\n                fragmentsRead.value += pongSubscription.poll(pongHandler, 10);\n                Thread.yield();\n            },\n            Integer.MAX_VALUE,\n            TimeUnit.MILLISECONDS.toNanos(5900));\n\n        verify(pongHandler, times(2)).onFragment(\n            any(DirectBuffer.class),\n            eq(DataHeaderFlyweight.HEADER_LENGTH),\n            eq(BitUtil.SIZE_OF_INT),\n            any(Header.class));\n    }\n\n    void echoPingHandler(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        while (pongPublication.offer(buffer, offset, length) < 0L)\n        {\n            Tests.yield();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/PrintEnvInfoTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nclass PrintEnvInfoTest\n{\n    @Disabled\n    @Test\n    void test()\n    {\n        System.out.println(\"=========================\");\n        System.out.println(\"[PrintEnvInfo] System properties:\");\n        System.out.println(\"=========================\");\n        System.getProperties().entrySet().stream()\n            .filter((e) -> ((String)e.getKey()).contains(\"java.\"))\n            .forEach((e) -> System.out.println(\"- \" + e.getKey() + \": \" + e.getValue()));\n\n        System.out.println(\"\\n=========================\");\n        System.out.println(\"[PrintEnvInfo] ENV variables:\");\n        System.out.println(\"=========================\");\n        for (final String env : new String[]{ \"JAVA_HOME\", \"BUILD_JAVA_HOME\", \"BUILD_JAVA_VERSION\", \"PATH\" })\n        {\n            System.out.println(\"- \" + env + \": \" + System.getenv(env));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/PubAndSubTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.ext.DebugChannelEndpointConfiguration;\nimport io.aeron.driver.ext.DebugSendChannelEndpoint;\nimport io.aeron.driver.ext.LossGenerator;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.RawBlockHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.InOrder;\n\nimport java.io.IOException;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Supplier;\n\nimport static io.aeron.AeronCounters.DRIVER_PUBLISHER_LIMIT_TYPE_ID;\nimport static io.aeron.AeronCounters.DRIVER_PUBLISHER_POS_TYPE_ID;\nimport static io.aeron.AeronCounters.DRIVER_RECEIVER_NAKS_SENT_TYPE_ID;\nimport static io.aeron.AeronCounters.DRIVER_RECEIVER_POS_TYPE_ID;\nimport static io.aeron.AeronCounters.DRIVER_SENDER_BPE_TYPE_ID;\nimport static io.aeron.AeronCounters.DRIVER_SENDER_LIMIT_TYPE_ID;\nimport static io.aeron.AeronCounters.DRIVER_SENDER_NAKS_RECEIVED_TYPE_ID;\nimport static io.aeron.SystemTests.verifyLossOccurredForStream;\nimport static io.aeron.logbuffer.FrameDescriptor.BEGIN_FRAG_FLAG;\nimport static io.aeron.logbuffer.FrameDescriptor.END_FRAG_FLAG;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_BUFFER_TYPE_CONCURRENT_PUBLICATION;\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_BUFFER_TYPE_EXCLUSIVE_PUBLICATION;\nimport static io.aeron.logbuffer.LogBufferDescriptor.LOG_BUFFER_TYPE_PUBLICATION_IMAGE;\nimport static io.aeron.logbuffer.LogBufferDescriptor.computeFragmentedFrameLength;\nimport static io.aeron.logbuffer.LogBufferDescriptor.type;\nimport static io.aeron.protocol.DataHeaderFlyweight.BEGIN_AND_END_FLAGS;\nimport static io.aeron.protocol.DataHeaderFlyweight.BEGIN_END_AND_EOS_FLAGS;\nimport static io.aeron.protocol.DataHeaderFlyweight.CURRENT_VERSION;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_DATA;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_RSP_SETUP;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_SM;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.protocol.DataHeaderFlyweight.VERSION_FIELD_OFFSET;\nimport static java.util.Arrays.asList;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThan;\nimport static org.hamcrest.Matchers.startsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass PubAndSubTest\n{\n    private static final String IPC_URI = \"aeron:ipc\";\n\n    private static List<String> channels()\n    {\n        return asList(\n            \"aeron:udp?endpoint=localhost:24325\",\n            \"aeron:udp?endpoint=localhost:24325|session=id=55555\",\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\",\n            IPC_URI);\n    }\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private static final int STREAM_ID = 1001;\n\n    private final MediaDriver.Context driverContext = new MediaDriver.Context()\n        .publicationConnectionTimeoutNs(MILLISECONDS.toNanos(300))\n        .imageLivenessTimeoutNs(MILLISECONDS.toNanos(500))\n        .timerIntervalNs(MILLISECONDS.toNanos(100));\n\n    private final Aeron.Context clientContext = new Aeron.Context()\n        .resourceLingerDurationNs(MILLISECONDS.toNanos(200));\n\n    private Aeron publishingClient;\n    private Aeron subscribingClient;\n    private TestMediaDriver driver;\n    private Subscription subscription;\n    private Publication publication;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[8192]);\n    private final FragmentHandler fragmentHandler = mock(FragmentHandler.class);\n    private final RawBlockHandler rawBlockHandler = mock(RawBlockHandler.class);\n    private final AvailableImageHandler availableImageHandler = mock(AvailableImageHandler.class);\n    private final UnavailableImageHandler unavailableImageHandler = mock(UnavailableImageHandler.class);\n\n    private void launch(final String channel)\n    {\n        driverContext.dirDeleteOnStart(true).threadingMode(ThreadingMode.SHARED);\n\n        driver = TestMediaDriver.launch(driverContext, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n\n        subscribingClient = Aeron.connect(clientContext.clone());\n        publishingClient = Aeron.connect(clientContext.clone());\n        subscription = subscribingClient.addSubscription(\n            channel, STREAM_ID, availableImageHandler, unavailableImageHandler);\n        publication = publishingClient.addPublication(channel, STREAM_ID);\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(publishingClient, subscribingClient, driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldReceivePublishedMessageViaPollFile(final String channel)\n    {\n        launch(channel);\n\n        publishMessage();\n\n        while (true)\n        {\n            final long bytes = subscription.rawPoll(rawBlockHandler, Integer.MAX_VALUE);\n            if (bytes > 0)\n            {\n                break;\n            }\n\n            Tests.yield();\n        }\n\n        final long expectedOffset = 0L;\n        final int expectedLength = BitUtil.align(HEADER_LENGTH + SIZE_OF_INT, FRAME_ALIGNMENT);\n\n        final ArgumentCaptor<FileChannel> channelArgumentCaptor = ArgumentCaptor.forClass(FileChannel.class);\n        verify(rawBlockHandler).onBlock(\n            channelArgumentCaptor.capture(),\n            eq(expectedOffset),\n            any(UnsafeBuffer.class),\n            eq((int)expectedOffset),\n            eq(expectedLength),\n            anyInt(),\n            anyInt());\n\n        assertTrue(channelArgumentCaptor.getValue().isOpen(), \"File Channel is closed\");\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 1408, 128 })\n    void shouldSendAndReceiveMessage(final int mtu)\n    {\n        launch(\"aeron:udp?endpoint=localhost:24325|mtu=\" + mtu);\n        final MutableInteger frameLength = new MutableInteger(-1);\n        final MutableLong position = new MutableLong(-1);\n\n        final int payloadLength = 500;\n        final FragmentHandler handler = (buffer, offset, length, header) ->\n        {\n            frameLength.set(header.frameLength());\n            position.set(header.position());\n        };\n\n        final FragmentHandler assembler =\n            HEADER_LENGTH + payloadLength < mtu ? handler : new FragmentAssembler(handler);\n\n        while (publication.offer(buffer, 0, payloadLength) < 0)\n        {\n            Tests.yield();\n        }\n\n        while (-1 == frameLength.get() || -1 == position.get())\n        {\n            subscription.poll(assembler, 10);\n            Tests.yield();\n        }\n\n        assertEquals(HEADER_LENGTH + payloadLength, frameLength.get());\n        assertEquals(subscription.imageAtIndex(0).position(), position.get());\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldContinueAfterBufferRollover(final String channel)\n    {\n        final int termBufferLength = 64 * 1024;\n        final int numMessagesInTermBuffer = 64;\n        final int messageLength = (termBufferLength / numMessagesInTermBuffer) - HEADER_LENGTH;\n        final int numMessagesToSend = numMessagesInTermBuffer + 1;\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        launch(channel);\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, messageLength) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment();\n        }\n\n        verify(fragmentHandler, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(messageLength),\n            any(Header.class));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldContinueAfterRolloverWithMinimalPaddingHeader(final String channel)\n    {\n        final int termBufferLength = 64 * 1024;\n        final int termBufferLengthMinusPaddingHeader = termBufferLength - HEADER_LENGTH;\n        final int num1kMessagesInTermBuffer = 63;\n        final int lastMessageLength =\n            termBufferLengthMinusPaddingHeader - (num1kMessagesInTermBuffer * 1024) - HEADER_LENGTH;\n        final int messageLength = 1024 - HEADER_LENGTH;\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        launch(channel);\n\n        // lock step reception until we get to within 8 messages of the end\n        for (int i = 0; i < num1kMessagesInTermBuffer - 7; i++)\n        {\n            while (publication.offer(buffer, 0, messageLength) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment();\n        }\n\n        for (int i = 7; i > 0; i--)\n        {\n            while (publication.offer(buffer, 0, messageLength) < 0L)\n            {\n                Tests.yield();\n            }\n        }\n\n        // small enough to leave room for padding that is just a header\n        while (publication.offer(buffer, 0, lastMessageLength) < 0L)\n        {\n            Tests.yield();\n        }\n\n        // no roll over\n        while (publication.offer(buffer, 0, messageLength) < 0L)\n        {\n            Tests.yield();\n        }\n\n        final MutableInteger fragmentsRead = new MutableInteger();\n\n        Tests.executeUntil(\n            () -> fragmentsRead.value == 9,\n            (j) ->\n            {\n                final int fragments = subscription.poll(fragmentHandler, 10);\n                if (0 == fragments)\n                {\n                    Thread.yield();\n                }\n                fragmentsRead.value += fragments;\n            },\n            Integer.MAX_VALUE,\n            MILLISECONDS.toNanos(500));\n\n        final InOrder inOrder = inOrder(fragmentHandler);\n\n        inOrder.verify(fragmentHandler, times(num1kMessagesInTermBuffer)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(messageLength),\n            any(Header.class));\n        inOrder.verify(fragmentHandler, times(1)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(lastMessageLength),\n            any(Header.class));\n        inOrder.verify(fragmentHandler, times(1)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(messageLength),\n            any(Header.class));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(20)\n    @SlowTest\n    void shouldReceivePublishedMessageOneForOneWithDataLoss(final String channel) throws IOException\n    {\n        assumeFalse(IPC_URI.equals(channel));\n\n        final int termBufferLength = 64 * 1024;\n        final int numMessagesInTermBuffer = 64;\n        final int messageLength = (termBufferLength / numMessagesInTermBuffer) - HEADER_LENGTH;\n        final int numMessagesToSend = 2 * numMessagesInTermBuffer;\n\n        final LossGenerator noLossGenerator =\n            DebugChannelEndpointConfiguration.lossGeneratorSupplier(0, 0);\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        driverContext.sendChannelEndpointSupplier(\n            (udpChannel, statusIndicator, context) ->\n                new DebugSendChannelEndpoint(udpChannel, statusIndicator, context, noLossGenerator, noLossGenerator));\n\n        TestMediaDriver.enableRandomLoss(driverContext, 0.1, 0xcafebabeL, true, false);\n\n        launch(channel);\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, messageLength) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment();\n        }\n\n        verify(fragmentHandler, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(messageLength),\n            any(Header.class));\n\n        verifyLossOccurredForStream(driverContext.aeronDirectoryName(), STREAM_ID);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldReceivePublishedMessageBatchedWithDataLoss(final String channel) throws IOException\n    {\n        assumeFalse(IPC_URI.equals(channel));\n\n        final int termBufferLength = 64 * 1024;\n        final int numMessagesInTermBuffer = 64;\n        final int messageLength = (termBufferLength / numMessagesInTermBuffer) - HEADER_LENGTH;\n        final int numMessagesToSend = 2 * numMessagesInTermBuffer;\n        final int numBatches = 4;\n        final int numMessagesPerBatch = numMessagesToSend / numBatches;\n\n        final LossGenerator noLossGenerator =\n            DebugChannelEndpointConfiguration.lossGeneratorSupplier(0, 0);\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        driverContext.sendChannelEndpointSupplier(\n            (udpChannel, statusIndicator, context) ->\n                new DebugSendChannelEndpoint(udpChannel, statusIndicator, context, noLossGenerator, noLossGenerator));\n\n        TestMediaDriver.enableRandomLoss(driverContext, 0.1, 0xcafebabeL, true, false);\n\n        launch(channel);\n\n        for (int i = 0; i < numBatches; i++)\n        {\n            for (int j = 0; j < numMessagesPerBatch; j++)\n            {\n                while (publication.offer(buffer, 0, messageLength) < 0L)\n                {\n                    Tests.yield();\n                }\n            }\n\n            pollForBatch(numMessagesPerBatch);\n        }\n\n        verify(fragmentHandler, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(messageLength),\n            any(Header.class));\n\n        verifyLossOccurredForStream(driverContext.aeronDirectoryName(), STREAM_ID);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldContinueAfterBufferRolloverBatched(final String channel)\n    {\n        final int termBufferLength = 64 * 1024;\n        final int numBatchesPerTerm = 4;\n        final int numMessagesPerBatch = 16;\n        final int numMessagesInTermBuffer = numMessagesPerBatch * numBatchesPerTerm;\n        final int messageLength = (termBufferLength / numMessagesInTermBuffer) - HEADER_LENGTH;\n        final int numMessagesToSend = numMessagesInTermBuffer + 1;\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        launch(channel);\n\n        for (int i = 0; i < numBatchesPerTerm; i++)\n        {\n            for (int j = 0; j < numMessagesPerBatch; j++)\n            {\n                while (publication.offer(buffer, 0, messageLength) < 0L)\n                {\n                    Tests.yield();\n                }\n            }\n\n            pollForBatch(numMessagesPerBatch);\n        }\n\n        while (publication.offer(buffer, 0, messageLength) < 0L)\n        {\n            Tests.yield();\n        }\n\n        final MutableInteger fragmentsRead = new MutableInteger();\n\n        Tests.executeUntil(\n            () -> fragmentsRead.value > 0,\n            (j) ->\n            {\n                final int fragments = subscription.poll(fragmentHandler, 10);\n                if (0 == fragments)\n                {\n                    Thread.yield();\n                }\n                fragmentsRead.value += fragments;\n            },\n            Integer.MAX_VALUE,\n            MILLISECONDS.toNanos(900));\n\n        verify(fragmentHandler, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(messageLength),\n            any(Header.class));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldContinueAfterBufferRolloverWithPadding(final String channel)\n    {\n        /*\n         * 65536 bytes in the buffer\n         * 63 * 1032 = 65016\n         * 65536 - 65016 = 520 bytes padding at the end\n         * so, sending 64 messages causes last to overflow\n         */\n        final int termBufferLength = 64 * 1024;\n        final int messageLength = 1032 - HEADER_LENGTH;\n        final int numMessagesToSend = 64;\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        launch(channel);\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, messageLength) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment();\n        }\n\n        verify(fragmentHandler, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(messageLength),\n            any(Header.class));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldContinueAfterBufferRolloverWithPaddingBatched(final String channel)\n    {\n        /*\n         * 65536 bytes in the buffer\n         * 63 * 1032 = 65016\n         * 65536 - 65016 = 520 bytes padding at the end\n         * so, sending 64 messages causes last to overflow\n         */\n        final int termBufferLength = 64 * 1024;\n        final int messageLength = 1032 - HEADER_LENGTH;\n        final int numMessagesToSend = 64;\n        final int numBatchesPerTerm = 4;\n        final int numMessagesPerBatch = numMessagesToSend / numBatchesPerTerm;\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        launch(channel);\n\n        for (int i = 0; i < numBatchesPerTerm; i++)\n        {\n            for (int j = 0; j < numMessagesPerBatch; j++)\n            {\n                while (publication.offer(buffer, 0, messageLength) < 0L)\n                {\n                    Tests.yield();\n                }\n            }\n\n            pollForBatch(numMessagesPerBatch);\n        }\n\n        verify(fragmentHandler, times(numMessagesToSend)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(messageLength),\n            any(Header.class));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldReceiveOnlyAfterSendingUpToFlowControlLimit(final String channel)\n    {\n        /*\n         * The subscriber will flow control before an entire term buffer. So, send until can't send anymore.\n         * Then start up subscriber to drain.\n         */\n        final int termBufferLength = 64 * 1024;\n        final int numMessagesPerTerm = 64;\n        final int messageLength = (termBufferLength / numMessagesPerTerm) - HEADER_LENGTH;\n        final int maxFails = 10000;\n        int messagesSent = 0;\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        launch(channel);\n\n        for (int i = 0; i < numMessagesPerTerm; i++)\n        {\n            int offerFails = 0;\n\n            while (publication.offer(buffer, 0, messageLength) < 0L)\n            {\n                if (++offerFails > maxFails)\n                {\n                    break;\n                }\n\n                Tests.yield();\n            }\n\n            if (offerFails > maxFails)\n            {\n                break;\n            }\n\n            messagesSent++;\n        }\n\n        final MutableInteger fragmentsRead = new MutableInteger();\n        final int messagesToReceive = messagesSent;\n\n        Tests.executeUntil(\n            () -> fragmentsRead.value >= messagesToReceive,\n            (j) ->\n            {\n                final int fragments = subscription.poll(fragmentHandler, 10);\n                if (0 == fragments)\n                {\n                    Thread.yield();\n                }\n                fragmentsRead.value += fragments;\n            },\n            Integer.MAX_VALUE,\n            MILLISECONDS.toNanos(500));\n\n        verify(fragmentHandler, times(messagesToReceive)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(messageLength),\n            any(Header.class));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldReceivePublishedMessageOneForOneWithReSubscription(final String channel)\n    {\n        final int termBufferLength = 64 * 1024;\n        final int numMessagesInTermBuffer = 64;\n        final int messageLength = (termBufferLength / numMessagesInTermBuffer) - HEADER_LENGTH;\n        final int numMessagesToSendStageOne = numMessagesInTermBuffer / 2;\n        final int numMessagesToSendStageTwo = numMessagesInTermBuffer;\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        launch(channel);\n\n        Tests.awaitConnected(subscription);\n\n        for (int i = 0; i < numMessagesToSendStageOne; i++)\n        {\n            while (publication.offer(buffer, 0, messageLength) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment();\n        }\n\n        assertEquals(publication.position(), subscription.imageAtIndex(0).position());\n\n        subscription.close();\n        subscription = Tests.reAddSubscription(subscribingClient, channel, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n\n        for (int i = 0; i < numMessagesToSendStageTwo; i++)\n        {\n            while (publication.offer(buffer, 0, messageLength) < 0L)\n            {\n                Tests.yield();\n            }\n\n            pollForFragment();\n        }\n\n        assertEquals(publication.position(), subscription.imageAtIndex(0).position());\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldFragmentExactMessageLengthsCorrectly(final String channel)\n    {\n        final int termBufferLength = 64 * 1024;\n        final int numFragmentsPerMessage = 2;\n        final int mtuLength = driverContext.mtuLength();\n        final int frameLength = mtuLength - HEADER_LENGTH;\n        final int messageLength = frameLength * numFragmentsPerMessage;\n        final int numMessagesToSend = 2;\n        final int numFramesToExpect = numMessagesToSend * numFragmentsPerMessage;\n\n        driverContext.publicationTermBufferLength(termBufferLength);\n\n        launch(channel);\n\n        for (int i = 0; i < numMessagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, messageLength) < 0L)\n            {\n                Tests.yield();\n            }\n        }\n\n        final MutableInteger fragmentsRead = new MutableInteger();\n\n        Tests.executeUntil(\n            () -> fragmentsRead.value > numFramesToExpect,\n            (j) ->\n            {\n                final int fragments = subscription.poll(fragmentHandler, 10);\n                if (0 == fragments)\n                {\n                    Thread.yield();\n                }\n                fragmentsRead.value += fragments;\n            },\n            Integer.MAX_VALUE,\n            MILLISECONDS.toNanos(500));\n\n        verify(fragmentHandler, times(numFramesToExpect)).onFragment(\n            any(DirectBuffer.class),\n            anyInt(),\n            eq(frameLength),\n            any(Header.class));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldNoticeDroppedSubscriber(final String channel)\n    {\n        launch(channel);\n\n        Tests.awaitConnected(publication);\n\n        subscription.close();\n\n        while (publication.isConnected())\n        {\n            Tests.yield();\n        }\n    }\n\n    @Test\n    void shouldAllowSubscriptionsIfUsingTagsAndParametersAndAllMatch()\n    {\n        launch(\"aeron:ipc\");\n\n        final String pubChannel = \"aeron:udp?control-mode=dynamic|control=127.0.0.1:9999\";\n        final String channel = \"aeron:udp?endpoint=127.0.0.1:0|control=127.0.0.1:9999|tags=1001\";\n        try (\n            Publication pub = subscribingClient.addPublication(pubChannel, 1000);\n            Subscription sub1 = subscribingClient.addSubscription(channel, 1000);\n            Subscription sub2 = subscribingClient.addSubscription(channel, 1000))\n        {\n            Tests.awaitConnected(sub1);\n            Tests.awaitConnected(sub2);\n            Tests.awaitConnected(pub);\n        }\n    }\n\n    @Test\n    void shouldRejectSubscriptionsIfUsingTagsAndParametersAndEndpointDoesNotMatchEndpointWithExplicitControl()\n    {\n        watcher.ignoreErrorsMatching((s) -> s.contains(\"has mismatched endpoint or control\"));\n        launch(\"aeron:ipc\");\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder(\n            \"aeron:udp?endpoint=127.0.0.1:0|control=127.0.0.1:9999|tags=1001\");\n\n        try (Subscription ignore1 = subscribingClient.addSubscription(builder.build(), 1000))\n        {\n            Objects.requireNonNull(ignore1);\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addSubscription(builder.endpoint(\"127.0.0.1:9999\").build(), 1000));\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addSubscription(builder.endpoint(\"127.0.0.2:0\").build(), 1000));\n        }\n    }\n\n    @Test\n    void shouldRejectSubscriptionsIfUsingTagsAndParametersAndEndpointDoesNotMatchEndpointWithoutControl()\n    {\n        watcher.ignoreErrorsMatching((s) -> s.contains(\"has mismatched endpoint or control\"));\n        launch(\"aeron:ipc\");\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder(\n            \"aeron:udp?endpoint=127.0.0.1:9999|tags=1001\");\n\n        try (Subscription ignore1 = subscribingClient.addSubscription(builder.build(), 1000))\n        {\n            Objects.requireNonNull(ignore1);\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addSubscription(builder.endpoint(\"127.0.0.1:0\").build(), 1000));\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addSubscription(builder.endpoint(\"127.0.0.2:9999\").build(), 1000));\n        }\n    }\n\n    @Test\n    void shouldRejectSubscriptionsIfUsingTagsAndParametersAndEndpointDoesNotMatchControl()\n    {\n        watcher.ignoreErrorsMatching((s) -> s.contains(\"has mismatched endpoint or control\"));\n        launch(\"aeron:ipc\");\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder(\n            \"aeron:udp?endpoint=127.0.0.1:0|control=127.0.0.1:9999|tags=1001\");\n\n        try (Subscription ignore1 = subscribingClient.addSubscription(builder.build(), 1000))\n        {\n            Objects.requireNonNull(ignore1);\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addSubscription(builder.controlEndpoint(\"127.0.0.1:10000\").build(), 1000));\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addSubscription(builder.controlEndpoint(\"127.0.0.2:9999\").build(), 1000));\n        }\n    }\n\n    @Test\n    void shouldRejectSubscriptionsIfUsingTagsAndParametersAndEndpointDoesNotMatchSocketReceiveBufferLength()\n    {\n        watcher.ignoreErrorsMatching((s) -> true);\n        launch(\"aeron:ipc\");\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder(\n            \"aeron:udp?endpoint=127.0.0.1:0|control=127.0.0.1:9999|so-rcvbuf=128K|tags=1001\");\n\n        try (Subscription ignore1 = subscribingClient.addSubscription(builder.build(), 1000))\n        {\n            Objects.requireNonNull(ignore1);\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addSubscription(builder.socketRcvbufLength(64 * 1024).build(), 1000));\n        }\n    }\n\n    @Test\n    void shouldAllowPublicationsIfUsingTagsAndParametersAndAllMatch()\n    {\n        launch(\"aeron:ipc\");\n\n        final String channel = \"aeron:udp?endpoint=127.0.0.1:9999|control=127.0.0.1:0|tags=1001\";\n        try (\n            Publication ignore1 = subscribingClient.addPublication(channel, 1000);\n            Publication ignore2 = subscribingClient.addPublication(channel, 1000))\n        {\n            Objects.requireNonNull(ignore1);\n            Objects.requireNonNull(ignore2);\n        }\n    }\n\n    @Test\n    void shouldRejectPublicationsIfUsingTagsAndParametersAndEndpointDoesNotMatchEndpointWithExplicitControl()\n    {\n        watcher.ignoreErrorsMatching((s) -> s.contains(\"has mismatched endpoint or control\"));\n        launch(\"aeron:ipc\");\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder(\n            \"aeron:udp?endpoint=127.0.0.1:10000|control=127.0.0.1:0|tags=1001\");\n\n        try (Publication ignore1 = subscribingClient.addPublication(builder.build(), 1000))\n        {\n            Objects.requireNonNull(ignore1);\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addPublication(builder.endpoint(\"127.0.0.1:9999\").build(), 1000));\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addPublication(builder.endpoint(\"127.0.0.2:10000\").build(), 1000));\n        }\n    }\n\n    @Test\n    void shouldRejectPublicationsIfUsingTagsAndParametersAndEndpointDoesNotMatchEndpointWithoutControl()\n    {\n        watcher.ignoreErrorsMatching((s) -> s.contains(\"has mismatched endpoint or control\"));\n        launch(\"aeron:ipc\");\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder(\n            \"aeron:udp?endpoint=127.0.0.1:10000|tags=1001\");\n\n        try (Subscription ignore1 = subscribingClient.addSubscription(builder.build(), 1000))\n        {\n            Objects.requireNonNull(ignore1);\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addSubscription(builder.endpoint(\"127.0.0.1:9999\").build(), 1000));\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addSubscription(builder.endpoint(\"127.0.0.2:10000\").build(), 1000));\n        }\n    }\n\n    @Test\n    void shouldRejectPublicationsIfUsingTagsAndParametersAndEndpointDoesNotMatchControl()\n    {\n        watcher.ignoreErrorsMatching((s) -> s.contains(\"has mismatched endpoint or control\"));\n        launch(\"aeron:ipc\");\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder(\n            \"aeron:udp?endpoint=127.0.0.1:10000|control=127.0.0.1:0|tags=1001\");\n\n        try (Publication ignore1 = subscribingClient.addPublication(builder.build(), 1000))\n        {\n            Objects.requireNonNull(ignore1);\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addPublication(builder.controlEndpoint(\"127.0.0.1:10000\").build(), 1000));\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addPublication(builder.controlEndpoint(\"127.0.0.2:0\").build(), 1000));\n        }\n    }\n\n    @Test\n    void shouldRejectPublicationsIfUsingTagsAndParametersAndMtuDoesNotMatch()\n    {\n        watcher.ignoreErrorsMatching((s) -> true);\n        launch(\"aeron:ipc\");\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder(\n            \"aeron:udp?endpoint=127.0.0.1:10000|control=127.0.0.1:0|mtu=1408|tags=1001\");\n\n        try (Publication ignore1 = subscribingClient.addPublication(builder.build(), 1000))\n        {\n            Objects.requireNonNull(ignore1);\n            assertThrows(\n                RegistrationException.class,\n                () -> subscribingClient.addPublication(builder.mtu(8192).build(), 1000));\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"fragmentAssemblers\")\n    @SuppressWarnings(\"MethodLength\")\n    void shouldReturnCompleteHeaderForAssembledMessages(final Class<?> assemblerClass)\n        throws ReflectiveOperationException\n    {\n        final ArrayList<ExpectedFragment> messages = new ArrayList<>();\n        final MutableInteger messageIndex = new MutableInteger();\n        final FragmentHandler rawFragmentHandler =\n            (buffer, offset, length, header) -> verifyFragment(buffer, offset, length, header, messageIndex, messages);\n        final FragmentHandler fragmentAssembler;\n        final ControlledFragmentHandler controlledFragmentAssembler;\n        if (FragmentHandler.class.isAssignableFrom(assemblerClass))\n        {\n            fragmentAssembler = (FragmentHandler)assemblerClass.getConstructor(FragmentHandler.class)\n                .newInstance(rawFragmentHandler);\n            controlledFragmentAssembler = null;\n        }\n        else\n        {\n            final ControlledFragmentHandler delegate = (buffer, offset, length, header) ->\n            {\n                rawFragmentHandler.onFragment(buffer, offset, length, header);\n                return ControlledFragmentHandler.Action.COMMIT;\n            };\n            controlledFragmentAssembler =\n                (ControlledFragmentHandler)assemblerClass.getConstructor(ControlledFragmentHandler.class)\n                    .newInstance(delegate);\n            fragmentAssembler = null;\n        }\n\n        final int mtu = 2048;\n        final int maxPayloadLength = mtu - HEADER_LENGTH;\n        final int termLength = 64 * 1024;\n        final int paddingLength = 1376;\n        final int termOffset = termLength - paddingLength;\n        final int initialTermId = 5;\n        final int termId = 13;\n        final long initialPosition = termLength * (termId - initialTermId) + termOffset;\n        final UnsafeBuffer data = new UnsafeBuffer(new byte[maxPayloadLength * 3 + 317]);\n        ThreadLocalRandom.current().nextBytes(data.byteArray());\n        launch(\"aeron:ipc?mtu=\" + mtu + \"|term-length=64K|init-term-id=\" + initialTermId +\n            \"|term-id=\" + termId + \"|term-offset=\" + termOffset);\n\n        try (Subscription unfragmentedSubscription =\n            subscribingClient.addSubscription(subscription.channel(), STREAM_ID))\n        {\n            Tests.awaitConnected(publication);\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(unfragmentedSubscription);\n\n            final BufferClaim bufferClaim = new BufferClaim();\n            final int firstMessageLength = 100;\n            while (publication.tryClaim(firstMessageLength, bufferClaim) < 0)\n            {\n                Tests.yield();\n            }\n            final int headerType = HDR_TYPE_RSP_SETUP;\n            final byte expectedFlags = (byte)(BEGIN_END_AND_EOS_FLAGS | 0xA);\n            final long reservedValue = 13131313139871L;\n            bufferClaim.headerType(headerType);\n            bufferClaim.flags(expectedFlags);\n            bufferClaim.reservedValue(reservedValue);\n            bufferClaim.buffer().putBytes(bufferClaim.offset(), data, 0, firstMessageLength);\n            bufferClaim.commit();\n\n            final long secondReservedValue = -4239462982749823794L;\n            final MutableLong fragmentedReservedValue = new MutableLong(secondReservedValue);\n            final ReservedValueSupplier reservedValueSupplier =\n                (tb, to, fl) -> fragmentedReservedValue.getAndAdd(reservedValue);\n            while (publication.offer(data, 0, data.capacity(), reservedValueSupplier) < 0)\n            {\n                Tests.yield();\n            }\n            final int fragmentedMessageLength = computeFragmentedFrameLength(data.capacity(), maxPayloadLength);\n            final int frameLength = HEADER_LENGTH + data.capacity();\n            final int position1 = ((termId + 1 - initialTermId) * termLength) + fragmentedMessageLength;\n\n            long position2;\n            while ((position2 = publication.tryClaim(maxPayloadLength, bufferClaim)) < 0)\n            {\n                Tests.yield();\n            }\n            bufferClaim.headerType(HDR_TYPE_SM);\n            bufferClaim.flags((byte)0xFF);\n            bufferClaim.reservedValue(42);\n            bufferClaim.buffer().putBytes(bufferClaim.offset(), data, 0, maxPayloadLength);\n            bufferClaim.commit();\n\n            messageIndex.set(0);\n            messages.clear();\n            final Image fragmentedImage = subscription.imageAtIndex(0);\n            messages.add(new ExpectedFragment(\n                fragmentedImage,\n                firstMessageLength + HEADER_LENGTH,\n                CURRENT_VERSION,\n                expectedFlags,\n                (short)headerType,\n                termOffset,\n                publication.sessionId(),\n                STREAM_ID,\n                termId,\n                reservedValue,\n                initialTermId,\n                publication.positionBitsToShift(),\n                BitUtil.align(initialPosition + firstMessageLength + HEADER_LENGTH, FRAME_ALIGNMENT),\n                data,\n                0,\n                firstMessageLength));\n            messages.add(new ExpectedFragment(\n                fragmentedImage,\n                frameLength,\n                CURRENT_VERSION,\n                (byte)BEGIN_AND_END_FLAGS,\n                (short)HDR_TYPE_DATA,\n                0,\n                publication.sessionId(),\n                STREAM_ID,\n                termId + 1,\n                secondReservedValue,\n                initialTermId,\n                publication.positionBitsToShift(),\n                position1,\n                data,\n                0,\n                data.capacity()));\n            messages.add(new ExpectedFragment(\n                fragmentedImage,\n                mtu,\n                CURRENT_VERSION,\n                (byte)0xFF,\n                (short)HDR_TYPE_SM,\n                fragmentedMessageLength,\n                publication.sessionId(),\n                STREAM_ID,\n                termId + 1,\n                42,\n                initialTermId,\n                publication.positionBitsToShift(),\n                position2,\n                data,\n                0,\n                maxPayloadLength));\n\n            if (null != fragmentAssembler)\n            {\n                while (messages.size() != messageIndex.get())\n                {\n                    if (0 == subscription.poll(fragmentAssembler, 10))\n                    {\n                        Tests.yield();\n                    }\n                }\n            }\n            else\n            {\n                while (messages.size() != messageIndex.get())\n                {\n                    if (0 == subscription.controlledPoll(controlledFragmentAssembler, 10))\n                    {\n                        Tests.yield();\n                    }\n                }\n            }\n\n            messageIndex.set(0);\n            messages.clear();\n            final Image unfragmentedImage = unfragmentedSubscription.imageAtIndex(0);\n            messages.add(new ExpectedFragment(\n                unfragmentedImage,\n                firstMessageLength + HEADER_LENGTH,\n                CURRENT_VERSION,\n                expectedFlags,\n                (short)headerType,\n                termOffset,\n                publication.sessionId(),\n                STREAM_ID,\n                termId,\n                reservedValue,\n                initialTermId,\n                publication.positionBitsToShift(),\n                BitUtil.align(initialPosition + firstMessageLength + HEADER_LENGTH, FRAME_ALIGNMENT),\n                data,\n                0,\n                firstMessageLength));\n            messages.add(new ExpectedFragment(\n                unfragmentedImage,\n                mtu,\n                CURRENT_VERSION,\n                BEGIN_FRAG_FLAG,\n                (short)HDR_TYPE_DATA,\n                0,\n                publication.sessionId(),\n                STREAM_ID,\n                termId + 1,\n                secondReservedValue,\n                initialTermId,\n                publication.positionBitsToShift(),\n                (termId + 1 - initialTermId) * termLength + mtu,\n                data,\n                0,\n                maxPayloadLength));\n            messages.add(new ExpectedFragment(\n                unfragmentedImage,\n                mtu,\n                CURRENT_VERSION,\n                (byte)0,\n                (short)HDR_TYPE_DATA,\n                mtu,\n                publication.sessionId(),\n                STREAM_ID,\n                termId + 1,\n                secondReservedValue + reservedValue,\n                initialTermId,\n                publication.positionBitsToShift(),\n                ((termId + 1 - initialTermId) * termLength) + 2 * mtu,\n                data,\n                maxPayloadLength,\n                maxPayloadLength));\n            messages.add(new ExpectedFragment(\n                unfragmentedImage,\n                mtu,\n                CURRENT_VERSION,\n                (byte)0,\n                (short)HDR_TYPE_DATA,\n                2 * mtu,\n                publication.sessionId(),\n                STREAM_ID,\n                termId + 1,\n                secondReservedValue + 2 * reservedValue,\n                initialTermId,\n                publication.positionBitsToShift(),\n                ((termId + 1 - initialTermId) * termLength) + 3 * mtu,\n                data,\n                2 * maxPayloadLength,\n                maxPayloadLength));\n            messages.add(new ExpectedFragment(\n                unfragmentedImage,\n                317 + HEADER_LENGTH,\n                CURRENT_VERSION,\n                END_FRAG_FLAG,\n                (short)HDR_TYPE_DATA,\n                3 * mtu,\n                publication.sessionId(),\n                STREAM_ID,\n                termId + 1,\n                secondReservedValue + 3 * reservedValue,\n                initialTermId,\n                publication.positionBitsToShift(),\n                position1,\n                data,\n                3 * maxPayloadLength,\n                317));\n            messages.add(new ExpectedFragment(\n                unfragmentedImage,\n                mtu,\n                CURRENT_VERSION,\n                (byte)0xFF,\n                (short)HDR_TYPE_SM,\n                fragmentedMessageLength,\n                publication.sessionId(),\n                STREAM_ID,\n                termId + 1,\n                42,\n                initialTermId,\n                publication.positionBitsToShift(),\n                position2,\n                data,\n                0,\n                maxPayloadLength));\n\n            while (messages.size() != messageIndex.get())\n            {\n                if (0 == unfragmentedSubscription.poll(rawFragmentHandler, 1))\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    void shouldMarkPublicationNotConnectedWhenItLoosesAllSubscribers(final String channel)\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"publication image state management\");\n\n        driverContext.timerIntervalNs(MILLISECONDS.toNanos(1500))\n            .publicationConnectionTimeoutNs(SECONDS.toNanos(2));\n        launch(channel);\n        Tests.awaitConnected(publication);\n        Tests.awaitConnected(subscription);\n\n        final BufferClaim bufferClaim = new BufferClaim();\n        final ThreadLocalRandom random = ThreadLocalRandom.current();\n        while (publication.tryClaim(random.nextInt(1, 1000), bufferClaim) < 0)\n        {\n            Tests.yield();\n        }\n        final int msgLength = bufferClaim.length();\n        bufferClaim.buffer().setMemory(bufferClaim.offset(), msgLength, (byte)random.nextInt());\n        bufferClaim.commit();\n\n        final MutableBoolean received = new MutableBoolean();\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) ->\n        {\n            received.set(true);\n            assertEquals(msgLength, length);\n        };\n        while (0 == subscription.poll(fragmentHandler, 1))\n        {\n            Tests.yield();\n        }\n        assertTrue(received.get());\n\n        subscription.close();\n\n        final long startNs = System.nanoTime();\n        Tests.await(() -> !publication.isConnected());\n        final long durationNs = System.nanoTime() - startNs;\n        assertThat(durationNs, lessThan(driver.context().timerIntervalNs() / 2));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldCleanupImagesWhenPublicationIsClosed(final String channel)\n    {\n        final AtomicInteger unavailableCount = new AtomicInteger();\n        doAnswer(invocation ->\n        {\n            unavailableCount.getAndIncrement();\n            return null;\n        }).when(unavailableImageHandler).onUnavailableImage(any(Image.class));\n\n        launch(channel);\n        final Subscription sub2 =\n            subscribingClient.addSubscription(channel, STREAM_ID, availableImageHandler, unavailableImageHandler);\n        Tests.awaitConnected(publication);\n        Tests.awaitConnected(subscription);\n        Tests.awaitConnected(sub2);\n\n        final BufferClaim bufferClaim = new BufferClaim();\n        final int msgLength = 555;\n        while (publication.tryClaim(msgLength, bufferClaim) < 0)\n        {\n            Tests.yield();\n        }\n        bufferClaim.buffer().setMemory(bufferClaim.offset(), msgLength, (byte)0xFF);\n        bufferClaim.commit();\n\n        final Image image = subscription.imageAtIndex(0);\n        final Image image2 = sub2.imageAtIndex(0);\n        assertNotSame(image, image2);\n        assertEquals(image.correlationId(), image2.correlationId());\n\n        while (0 == image.poll(fragmentHandler, 1) || 0 == image2.poll(fragmentHandler, 1))\n        {\n            Tests.yield();\n        }\n        verify(fragmentHandler, times(2)).onFragment(\n            any(DirectBuffer.class), eq(HEADER_LENGTH), eq(msgLength), any(Header.class));\n\n        final Path aeronDir = driver.context().aeronDirectory().toPath();\n        final Path pubLogBuffer = aeronDir.resolve(\"publications/\" + publication.registrationId() + \".logbuffer\");\n        final Path imageLogBuffer;\n        if (ChannelUri.parse(channel).isIpc())\n        {\n            imageLogBuffer = pubLogBuffer;\n        }\n        else\n        {\n            imageLogBuffer = aeronDir.resolve(\"images/\" + image.correlationId() + \".logbuffer\");\n        }\n        assertTrue(Files.exists(pubLogBuffer));\n        assertTrue(Files.exists(imageLogBuffer));\n\n        publication.close();\n\n        Tests.await(() -> 2 == unavailableCount.get());\n        assertEquals(0, subscription.imageCount());\n        assertEquals(0, sub2.imageCount());\n\n        while (Files.exists(pubLogBuffer) || Files.exists(imageLogBuffer))\n        {\n            Tests.yield();\n        }\n        assertFalse(Files.exists(pubLogBuffer));\n        assertFalse(Files.exists(imageLogBuffer));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldAssignOwnerIdForEachStreamCounter(final String channel)\n    {\n        launch(channel);\n        Tests.awaitConnected(publication);\n        Tests.awaitConnected(subscription);\n\n        final CountersReader countersReader = subscribingClient.countersReader();\n        verifyStreamCounters(countersReader, subscription.registrationId(), subscribingClient.clientId());\n\n        verifyStreamCounters(countersReader, publication.registrationId(), publishingClient.clientId());\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldReflectPublicationTypeInTheCountersAndLogMetadata(final String channel)\n    {\n        launch(channel);\n\n        final ExclusivePublication exclusivePublication =\n            publishingClient.addExclusivePublication(channel, STREAM_ID + 1);\n        Tests.awaitConnected(publication);\n        Tests.awaitConnected(subscription);\n\n        final CountersReader countersReader = subscribingClient.countersReader();\n        final int concurrentPubPosCounterId = countersReader.findByTypeIdAndRegistrationId(\n            DRIVER_PUBLISHER_POS_TYPE_ID, publication.registrationId());\n        assertThat(\n            countersReader.getCounterLabel(concurrentPubPosCounterId),\n            startsWith(\"pub-pos (concurrent): \"));\n        final int exclusivePubPosCounterId = countersReader.findByTypeIdAndRegistrationId(\n            DRIVER_PUBLISHER_POS_TYPE_ID, exclusivePublication.registrationId());\n        assertThat(\n            countersReader.getCounterLabel(exclusivePubPosCounterId),\n            startsWith(\"pub-pos (exclusive): \"));\n\n        verifyLogBufferType(publication.registrationId(), \"publications\", LOG_BUFFER_TYPE_CONCURRENT_PUBLICATION);\n        verifyLogBufferType(\n            exclusivePublication.registrationId(), \"publications\", LOG_BUFFER_TYPE_EXCLUSIVE_PUBLICATION);\n\n        if (ChannelUri.parse(channel).isUdp())\n        {\n            final Image image = subscription.imageBySessionId(publication.sessionId());\n            verifyLogBufferType(image.correlationId(), \"images\", LOG_BUFFER_TYPE_PUBLICATION_IMAGE);\n        }\n    }\n\n    private void verifyLogBufferType(final long registrationId, final String dir, final byte expectedType)\n    {\n        try (LogBuffers logBuffers =\n            new LogBuffers(driver.aeronDirectoryName() + \"/\" + dir + \"/\" + registrationId + \".logbuffer\"))\n        {\n            final UnsafeBuffer metaDataBuffer = logBuffers.metaDataBuffer();\n            assertEquals(expectedType, type(metaDataBuffer));\n        }\n    }\n\n    private static void verifyStreamCounters(\n        final CountersReader countersReader, final long registrationId, final long clientId)\n    {\n        countersReader.forEach(\n            (int counterId, int typeId, DirectBuffer keyBuffer, String label) ->\n            {\n                if ((typeId >= DRIVER_PUBLISHER_LIMIT_TYPE_ID &&\n                    typeId <= DRIVER_RECEIVER_POS_TYPE_ID ||\n                    (DRIVER_SENDER_LIMIT_TYPE_ID == typeId ||\n                        DRIVER_PUBLISHER_POS_TYPE_ID == typeId ||\n                        DRIVER_SENDER_BPE_TYPE_ID == typeId ||\n                        DRIVER_SENDER_NAKS_RECEIVED_TYPE_ID == typeId ||\n                        DRIVER_RECEIVER_NAKS_SENT_TYPE_ID == typeId)) &&\n                    countersReader.getCounterRegistrationId(counterId) == registrationId)\n                {\n                    assertEquals(clientId, countersReader.getCounterOwnerId(counterId), label);\n                }\n            });\n    }\n\n    private static void verifyFragment(\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final Header header,\n        final MutableInteger messageIndex,\n        final ArrayList<ExpectedFragment> messages)\n    {\n        final int index = messageIndex.getAndIncrement();\n        final Supplier<String> errorMsg = () -> \"index=\" + index;\n        final ExpectedFragment expectedFragment = messages.get(index);\n        assertSame(expectedFragment.image, header.context(), errorMsg);\n        assertEquals(expectedFragment.frameLength, header.frameLength(), errorMsg);\n        assertEquals(\n            expectedFragment.version,\n            header.buffer().getByte(header.offset() + VERSION_FIELD_OFFSET),\n            errorMsg);\n        assertEquals(expectedFragment.flags, header.flags(), errorMsg);\n        assertEquals(expectedFragment.type, (short)header.type(), errorMsg);\n        assertEquals(expectedFragment.termOffset, header.termOffset(), errorMsg);\n        assertEquals(expectedFragment.sessionId, header.sessionId(), errorMsg);\n        assertEquals(expectedFragment.streamId, header.streamId(), errorMsg);\n        assertEquals(expectedFragment.termId, header.termId(), errorMsg);\n        assertEquals(expectedFragment.reservedValue, header.reservedValue(), errorMsg);\n        assertEquals(expectedFragment.initialTermId, header.initialTermId(), errorMsg);\n        assertEquals(expectedFragment.positionBitsToShift, header.positionBitsToShift(), errorMsg);\n        assertEquals(expectedFragment.position, header.position(), errorMsg);\n        assertEquals(expectedFragment.payload.length, length, errorMsg);\n        for (int i = 0; i < length; i++)\n        {\n            assertEquals(expectedFragment.payload[i], buffer.getByte(offset + i), errorMsg);\n        }\n    }\n\n    private void publishMessage()\n    {\n        buffer.putInt(0, 1);\n\n        while (publication.offer(buffer, 0, SIZE_OF_INT) < 0L)\n        {\n            Tests.yield();\n        }\n    }\n\n    private void pollForFragment()\n    {\n        while (true)\n        {\n            final int fragments = subscription.poll(fragmentHandler, 10);\n            if (fragments > 0)\n            {\n                break;\n            }\n\n            Tests.yield();\n        }\n    }\n\n    private void pollForBatch(final int batchSize)\n    {\n        long fragmentsRead = 0;\n\n        while (true)\n        {\n            final int fragments = subscription.poll(fragmentHandler, 10);\n            fragmentsRead += fragments;\n\n            if (fragmentsRead >= batchSize)\n            {\n                break;\n            }\n\n            if (0 == fragments)\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    private static List<Class<?>> fragmentAssemblers()\n    {\n        return Arrays.asList(\n            FragmentAssembler.class,\n            ImageFragmentAssembler.class,\n            ControlledFragmentAssembler.class,\n            ImageControlledFragmentAssembler.class);\n    }\n\n    private static final class ExpectedFragment\n    {\n        private final Image image;\n        final int frameLength;\n        final byte version;\n        final byte flags;\n        final short type;\n        final int termOffset;\n        final int sessionId;\n        final int streamId;\n        final int termId;\n        final long reservedValue;\n        final int initialTermId;\n        final int positionBitsToShift;\n        final long position;\n        final byte[] payload;\n\n        private ExpectedFragment(\n            final Image image,\n            final int frameLength,\n            final byte version,\n            final byte flags,\n            final short type,\n            final int termOffset,\n            final int sessionId,\n            final int streamId,\n            final int termId,\n            final long reservedValue,\n            final int initialTermId,\n            final int positionBitsToShift,\n            final long position,\n            final UnsafeBuffer payload,\n            final int payloadOffset,\n            final int payloadLength)\n        {\n            this.image = image;\n            this.frameLength = frameLength;\n            this.version = version;\n            this.flags = flags;\n            this.type = type;\n            this.termOffset = termOffset;\n            this.sessionId = sessionId;\n            this.streamId = streamId;\n            this.termId = termId;\n            this.reservedValue = reservedValue;\n            this.initialTermId = initialTermId;\n            this.positionBitsToShift = positionBitsToShift;\n            this.position = position;\n            this.payload = new byte[payloadLength];\n            payload.getBytes(payloadOffset, this.payload);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/PublicationRevokeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.*;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Stream;\n\nimport static io.aeron.Publication.CLOSED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.PUBLICATIONS_REVOKED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.PUBLICATION_IMAGES_REVOKED;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass PublicationRevokeTest\n{\n    private static final int TERM_BUFFER_LENGTH = 1024 * 1024;\n    private static final int STREAM_ID = 1001;\n    private static final String UDP_CHANNEL = \"aeron:udp?endpoint=localhost:24325\";\n    private static final String IPC_CHANNEL = \"aeron:ipc\";\n    private static final String MCAST_CHANNEL = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\";\n    private static final String PUB_MDC_MANUAL_URI = \"aeron:udp?control-mode=manual\";\n    private static final String SUB1_MDC_MANUAL_URI = \"aeron:udp?endpoint=localhost:24326|group=true\";\n    private static final String SUB2_MDC_MANUAL_URI = \"aeron:udp?endpoint=localhost:24327|group=true\";\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context driverContext = new MediaDriver.Context()\n        .aeronDirectoryName(CommonContext.generateRandomDirName())\n        .ipcTermBufferLength(TERM_BUFFER_LENGTH)\n        .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n        .publicationConnectionTimeoutNs(MILLISECONDS.toNanos(300))\n        .imageLivenessTimeoutNs(MILLISECONDS.toNanos(500))\n        .publicationLingerTimeoutNs(SECONDS.toNanos(1))\n        .timerIntervalNs(MILLISECONDS.toNanos(100))\n        .dirDeleteOnStart(true)\n        .threadingMode(ThreadingMode.SHARED);\n\n    private final Aeron.Context clientContext = new Aeron.Context()\n        .resourceLingerDurationNs(MILLISECONDS.toNanos(200));\n\n    private Aeron client;\n    private TestMediaDriver driver;\n    private Aeron clientB;\n    private TestMediaDriver driverB;\n    private CountersReader countersReader;\n    private CountersReader countersReaderB;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[8192]);\n    private final FragmentHandler fragmentHandler = mock(FragmentHandler.class);\n    private final AvailableImageHandler availableImageHandler = mock(AvailableImageHandler.class);\n    private final UnavailableImageHandler unavailableImageHandler = mock(UnavailableImageHandler.class);\n\n    private void launch()\n    {\n        driver = TestMediaDriver.launch(driverContext.clone(), watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n\n        client = Aeron.connect(clientContext.clone().aeronDirectoryName(driver.aeronDirectoryName()));\n\n        countersReader = client.countersReader();\n\n        buffer.putInt(0, 1);\n    }\n\n    private void launch2()\n    {\n        driver = TestMediaDriver.launch(driverContext.clone(), watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n        driverB = TestMediaDriver.launch(\n            driverContext.clone().aeronDirectoryName(CommonContext.generateRandomDirName()), watcher);\n        watcher.dataCollector().add(driverB.context().aeronDirectory());\n\n        client = Aeron.connect(clientContext.clone().aeronDirectoryName(driver.aeronDirectoryName()));\n        clientB = Aeron.connect(clientContext.clone().aeronDirectoryName(driverB.aeronDirectoryName()));\n\n        countersReader = client.countersReader();\n        countersReaderB = clientB.countersReader();\n\n        buffer.putInt(0, 1);\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(client, driver, clientB, driverB);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void revokeTestSimple(\n        final String subscriptionChannel,\n        final String publicationChannel,\n        final long expectedPublicationImagesRevoked)\n    {\n        final AtomicInteger unavailableImages = new AtomicInteger(0);\n        doAnswer(invocation ->\n        {\n            final Image image = invocation.getArgument(0, Image.class);\n            assertTrue(image.isPublicationRevoked());\n\n            unavailableImages.incrementAndGet();\n            return null;\n        }).when(unavailableImageHandler).onUnavailableImage(any(Image.class));\n\n        launch();\n\n        final Subscription subscription = client.addSubscription(\n            subscriptionChannel, STREAM_ID, availableImageHandler, unavailableImageHandler);\n        try (ExclusivePublication exclusivePublication = client.addExclusivePublication(publicationChannel, STREAM_ID))\n        {\n\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(exclusivePublication);\n\n            publishMessage(exclusivePublication);\n\n            pollUntilFragments(subscription, 1);\n\n            publishMessage(exclusivePublication);\n\n            exclusivePublication.revokeOnClose();\n        }\n\n        while (unavailableImages.get() == 0)\n        {\n            Tests.yield();\n        }\n\n        assertTrue(subscription.hasNoImages());\n\n        assertEquals(1, countersReader.getCounterValue(PUBLICATIONS_REVOKED.id()));\n        assertEquals(expectedPublicationImagesRevoked, countersReader.getCounterValue(PUBLICATION_IMAGES_REVOKED.id()));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void revokeTestExclusive(\n        final String subscriptionChannel,\n        final String publicationChannel,\n        final long expectedPublicationImagesRevoked)\n    {\n        final AtomicBoolean publicationShouldBeRevoked = new AtomicBoolean(true);\n        final AtomicInteger unavailableImages = new AtomicInteger(0);\n        doAnswer(invocation ->\n        {\n            final Image image = invocation.getArgument(0, Image.class);\n            assertEquals(publicationShouldBeRevoked.get(), image.isPublicationRevoked());\n\n            unavailableImages.incrementAndGet();\n            return null;\n        }).when(unavailableImageHandler).onUnavailableImage(any(Image.class));\n\n        launch();\n\n        final Subscription subscription = client.addSubscription(\n            subscriptionChannel, STREAM_ID, availableImageHandler, unavailableImageHandler);\n        final ExclusivePublication exclusivePublication = client.addExclusivePublication(publicationChannel, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n        Tests.awaitConnected(exclusivePublication);\n\n        final ExclusivePublication publicationTwo = client.addExclusivePublication(publicationChannel, STREAM_ID);\n\n        Tests.awaitConnected(publicationTwo);\n\n        publishMessage(exclusivePublication);\n        publishMessage(publicationTwo);\n\n        pollUntilFragments(subscription, 2);\n\n        publishMessage(exclusivePublication);\n\n        assertEquals(2, subscription.imageCount());\n\n        exclusivePublication.revoke();\n\n        assertEquals(CLOSED, exclusivePublication.offer(buffer, 0, SIZE_OF_INT));\n\n        while (unavailableImages.get() == 0)\n        {\n            Tests.yield();\n        }\n\n        assertEquals(1, subscription.imageCount());\n\n        publishMessage(publicationTwo);\n        pollUntilFragments(subscription, 1);\n\n        publicationShouldBeRevoked.set(false);\n        subscription.close();\n\n        publicationTwo.close();\n\n        assertEquals(1, countersReader.getCounterValue(PUBLICATIONS_REVOKED.id()));\n        assertEquals(expectedPublicationImagesRevoked, countersReader.getCounterValue(PUBLICATION_IMAGES_REVOKED.id()));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldRevokeBeforeAllMessagesConsumed(\n        final String subscriptionChannel,\n        final String publicationChannel,\n        final long expectedPublicationImagesRevoked)\n    {\n        final AtomicInteger unavailableImages = new AtomicInteger(0);\n        doAnswer(invocation ->\n        {\n            unavailableImages.incrementAndGet();\n            return null;\n        }).when(unavailableImageHandler).onUnavailableImage(any(Image.class));\n\n        launch();\n\n        final Subscription subscription = client.addSubscription(\n            subscriptionChannel, STREAM_ID, availableImageHandler, unavailableImageHandler);\n        final ExclusivePublication exclusivePublication = client.addExclusivePublication(publicationChannel, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n        Tests.awaitConnected(exclusivePublication);\n        Tests.await(() -> exclusivePublication.availableWindow() > 0);\n\n        final long availableWindow = exclusivePublication.availableWindow();\n        while (exclusivePublication.position() < availableWindow)\n        {\n            if (exclusivePublication.offer(buffer, 0, SIZE_OF_INT) < 0)\n            {\n                Tests.yield();\n            }\n        }\n        assertEquals(availableWindow, exclusivePublication.position());\n\n        int messagesReceived = 0;\n        while (unavailableImages.get() == 0)\n        {\n            messagesReceived += subscription.poll(fragmentHandler, 1);\n\n            if (100 == messagesReceived)\n            {\n                exclusivePublication.revoke();\n                break;\n            }\n\n            Tests.yield();\n        }\n\n        Tests.await(() -> 1 == unavailableImages.get());\n\n        assertEquals(1, countersReader.getCounterValue(PUBLICATIONS_REVOKED.id()));\n        assertEquals(expectedPublicationImagesRevoked, countersReader.getCounterValue(PUBLICATION_IMAGES_REVOKED.id()));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRevokeMultipleSubscribersMulticast()\n    {\n        final AtomicInteger unavailableImages = new AtomicInteger(0);\n        doAnswer(invocation ->\n        {\n            final Image image = invocation.getArgument(0, Image.class);\n            assertTrue(image.isPublicationRevoked());\n\n            unavailableImages.incrementAndGet();\n            return null;\n        }).when(unavailableImageHandler).onUnavailableImage(any(Image.class));\n\n        launch2();\n\n        final Subscription subscription = client.addSubscription(\n            MCAST_CHANNEL, STREAM_ID, availableImageHandler, unavailableImageHandler);\n        final Subscription subscriptionB = clientB.addSubscription(\n            MCAST_CHANNEL, STREAM_ID, availableImageHandler, unavailableImageHandler);\n        final ExclusivePublication exclusivePublication = client.addExclusivePublication(MCAST_CHANNEL, STREAM_ID);\n\n        Tests.awaitConnected(subscription);\n        Tests.awaitConnected(subscriptionB);\n        Tests.awaitConnected(exclusivePublication);\n        Tests.await(() -> exclusivePublication.availableWindow() > 0);\n\n        final long availableWindow = exclusivePublication.availableWindow();\n        while (exclusivePublication.position() < availableWindow)\n        {\n            if (exclusivePublication.offer(buffer, 0, SIZE_OF_INT) < 0)\n            {\n                Tests.yield();\n            }\n        }\n        assertEquals(availableWindow, exclusivePublication.position());\n\n        int messagesReceived = 0;\n        int messagesReceivedB = 0;\n        while (true)\n        {\n            messagesReceived += subscription.poll(fragmentHandler, 1);\n            messagesReceivedB += subscriptionB.poll(fragmentHandler, 1);\n\n            if (messagesReceived >= 100 && messagesReceivedB >= 100)\n            {\n                exclusivePublication.revoke();\n                Tests.sleep(100);\n                break;\n            }\n\n            Tests.yield();\n        }\n\n        Tests.await(() -> 2 == unavailableImages.get());\n\n        assertEquals(1, countersReader.getCounterValue(PUBLICATIONS_REVOKED.id()));\n        assertEquals(1, countersReader.getCounterValue(PUBLICATION_IMAGES_REVOKED.id()));\n        assertEquals(0, countersReaderB.getCounterValue(PUBLICATIONS_REVOKED.id()));\n        assertEquals(1, countersReaderB.getCounterValue(PUBLICATION_IMAGES_REVOKED.id()));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRevokeMultipleSubscribersMdc()\n    {\n        final AtomicInteger unavailableImages = new AtomicInteger(0);\n        doAnswer(invocation ->\n        {\n            final Image image = invocation.getArgument(0, Image.class);\n            assertTrue(image.isPublicationRevoked());\n\n            unavailableImages.incrementAndGet();\n            return null;\n        }).when(unavailableImageHandler).onUnavailableImage(any(Image.class));\n\n        launch2();\n\n        final Subscription subscription = client.addSubscription(\n            SUB1_MDC_MANUAL_URI, STREAM_ID, availableImageHandler, unavailableImageHandler);\n        final Subscription subscriptionB = clientB.addSubscription(\n            SUB2_MDC_MANUAL_URI, STREAM_ID, availableImageHandler, unavailableImageHandler);\n        final ExclusivePublication exclusivePublication = client.addExclusivePublication(PUB_MDC_MANUAL_URI, STREAM_ID);\n        exclusivePublication.addDestination(SUB1_MDC_MANUAL_URI);\n        exclusivePublication.addDestination(SUB2_MDC_MANUAL_URI);\n\n        Tests.awaitConnected(subscription);\n        Tests.awaitConnected(subscriptionB);\n        Tests.awaitConnected(exclusivePublication);\n        Tests.await(() -> exclusivePublication.availableWindow() > 0);\n\n        final long availableWindow = exclusivePublication.availableWindow();\n        while (exclusivePublication.position() < availableWindow)\n        {\n            if (exclusivePublication.offer(buffer, 0, SIZE_OF_INT) < 0)\n            {\n                Tests.yield();\n            }\n        }\n        assertEquals(availableWindow, exclusivePublication.position());\n\n        int messagesReceived = 0;\n        int messagesReceivedB = 0;\n        while (true)\n        {\n            messagesReceived += subscription.poll(fragmentHandler, 1);\n            messagesReceivedB += subscriptionB.poll(fragmentHandler, 1);\n\n            if (messagesReceived >= 100 && messagesReceivedB >= 100)\n            {\n                exclusivePublication.revoke();\n                Tests.sleep(100);\n                break;\n            }\n\n            Tests.yield();\n        }\n\n        Tests.await(() -> 2 == unavailableImages.get());\n\n        assertEquals(1, countersReader.getCounterValue(PUBLICATIONS_REVOKED.id()));\n        assertEquals(1, countersReader.getCounterValue(PUBLICATION_IMAGES_REVOKED.id()));\n        assertEquals(0, countersReaderB.getCounterValue(PUBLICATIONS_REVOKED.id()));\n        assertEquals(1, countersReaderB.getCounterValue(PUBLICATION_IMAGES_REVOKED.id()));\n    }\n\n    private void publishMessage(final Publication publication)\n    {\n        while (publication.offer(buffer, 0, SIZE_OF_INT) < 0L)\n        {\n            Tests.yield();\n        }\n    }\n\n    private void pollUntilFragments(final Subscription subscription, final int expectedFragments)\n    {\n        int totalFragments = pollForFragment(subscription);\n        while (totalFragments < expectedFragments)\n        {\n            Tests.yield();\n            totalFragments += pollForFragment(subscription);\n        }\n    }\n\n    private int pollForFragment(final Subscription subscription)\n    {\n        while (true)\n        {\n            final int fragments = subscription.poll(fragmentHandler, 10);\n            if (fragments > 0)\n            {\n                return fragments;\n            }\n\n            Tests.yield();\n        }\n    }\n\n    private static Stream<Arguments> channels()\n    {\n        return Stream.of(\n            Arguments.of(UDP_CHANNEL, UDP_CHANNEL, 1),\n            Arguments.of(IPC_CHANNEL, IPC_CHANNEL, 0),\n            Arguments.of(CommonContext.SPY_PREFIX + UDP_CHANNEL, UDP_CHANNEL + \"|ssc=true\", 0)\n        );\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/PublicationUnblockTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static java.util.Arrays.asList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass PublicationUnblockTest\n{\n    private static List<String> channels()\n    {\n        return asList(\n            \"aeron:udp?endpoint=localhost:24325\",\n            \"aeron:ipc\");\n    }\n\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n                .threadingMode(ThreadingMode.SHARED)\n                .errorHandler(Tests::onError)\n                .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n                .clientLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(400))\n                .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(10))\n                .publicationUnblockTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500)),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect(new Aeron.Context()\n            .keepAliveIntervalNs(TimeUnit.MILLISECONDS.toNanos(100)));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldUnblockNonCommittedMessage(final String channel)\n    {\n        final MutableInteger fragmentCount = new MutableInteger();\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> fragmentCount.value++;\n\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            Publication publicationOne = aeron.addPublication(channel, STREAM_ID);\n            Publication publicationTwo = aeron.addPublication(channel, STREAM_ID))\n        {\n            final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[driver.context().mtuLength()]);\n            final int length = 128;\n            srcBuffer.setMemory(0, length, (byte)66);\n            final BufferClaim bufferClaim = new BufferClaim();\n\n            while (publicationOne.tryClaim(length, bufferClaim) < 0L)\n            {\n                Tests.yield();\n            }\n\n            bufferClaim.buffer().setMemory(bufferClaim.offset(), length, (byte)65);\n            bufferClaim.commit();\n\n            while (publicationTwo.offer(srcBuffer, 0, length) < 0L)\n            {\n                Tests.yield();\n            }\n\n            while (publicationOne.tryClaim(length, bufferClaim) < 0L)\n            {\n                Tests.yield();\n            }\n\n            while (publicationTwo.offer(srcBuffer, 0, length) < 0L)\n            {\n                Tests.yield();\n            }\n\n            final int expectedFragments = 3;\n            int numFragments = 0;\n            do\n            {\n                final int fragments = subscription.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n                if (fragments == 0)\n                {\n                    Tests.yield();\n                }\n\n                numFragments += fragments;\n            }\n            while (numFragments < expectedFragments);\n\n            assertEquals(expectedFragments, numFragments);\n            assertEquals(expectedFragments, fragmentCount.value);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/PublishFromArbitraryPositionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.extension.TestWatcher;\n\nimport java.nio.ByteBuffer;\nimport java.util.Random;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass PublishFromArbitraryPositionTest\n{\n    private static final int STREAM_ID = 1007;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n    private static final int MAX_MESSAGE_LENGTH = 1024 - DataHeaderFlyweight.HEADER_LENGTH;\n\n    private final FragmentHandler mockFragmentHandler = mock(FragmentHandler.class);\n    private final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(MAX_MESSAGE_LENGTH));\n    private final long seed = System.nanoTime();\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    private Aeron aeron;\n\n    @RegisterExtension\n    final TestWatcher randomSeedWatcher = new TestWatcher()\n    {\n        public void testFailed(final ExtensionContext context, final Throwable cause)\n        {\n            System.err.println(context.getDisplayName() + \" failed with random seed: \" + seed);\n        }\n    };\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n                .errorHandler(Tests::onError)\n                .threadingMode(ThreadingMode.SHARED),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldPublishFromArbitraryJoinPosition() throws InterruptedException\n    {\n        final Random rnd = new Random();\n        rnd.setSeed(seed);\n\n        final int termLength = 1 << (16 + rnd.nextInt(10)); // 64k to 64M\n        final int mtu = 1 << (10 + rnd.nextInt(3)); // 1024 to 8096\n        final int initialTermId = rnd.nextInt(1234);\n        final int termOffset = BitUtil.align(rnd.nextInt(termLength), FrameDescriptor.FRAME_ALIGNMENT);\n        final int termId = initialTermId + rnd.nextInt(1000);\n        final String channelUri = new ChannelUriStringBuilder()\n            .endpoint(\"localhost:24325\")\n            .termLength(termLength)\n            .initialTermId(initialTermId)\n            .termId(termId)\n            .termOffset(termOffset)\n            .mtu(mtu)\n            .media(\"udp\")\n            .build();\n\n        final int expectedNumberOfFragments = 10 + rnd.nextInt(10000);\n\n        try (Subscription subscription = aeron.addSubscription(channelUri, STREAM_ID);\n            ExclusivePublication publication = aeron.addExclusivePublication(channelUri, STREAM_ID))\n        {\n            Tests.awaitConnected(publication);\n\n            final Thread t = new Thread(\n                () ->\n                {\n                    int totalFragmentsRead = 0;\n                    do\n                    {\n                        int fragmentsRead = subscription.poll(mockFragmentHandler, FRAGMENT_COUNT_LIMIT);\n                        while (0 == fragmentsRead)\n                        {\n                            Thread.yield();\n                            fragmentsRead = subscription.poll(mockFragmentHandler, FRAGMENT_COUNT_LIMIT);\n                        }\n\n                        totalFragmentsRead += fragmentsRead;\n                    }\n                    while (totalFragmentsRead < expectedNumberOfFragments);\n\n                    assertEquals(expectedNumberOfFragments, totalFragmentsRead);\n                });\n\n            t.setDaemon(true);\n            t.setName(\"image-consumer\");\n            t.start();\n\n            for (int i = 0; i < expectedNumberOfFragments; i++)\n            {\n                publishMessage(srcBuffer, publication, rnd);\n            }\n\n            t.join();\n        }\n    }\n\n    private static void publishMessage(\n        final UnsafeBuffer buffer, final ExclusivePublication publication, final Random rnd)\n    {\n        while (publication.offer(buffer, 0, 1 + rnd.nextInt(MAX_MESSAGE_LENGTH - 1)) < 0L)\n        {\n            Tests.yield();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ReentrantClientTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.ErrorHandler;\nimport org.agrona.collections.MutableReference;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.mockito.Mockito.*;\n\nclass ReentrantClientTest\n{\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver mediaDriver;\n\n    @BeforeEach\n    void setUp()\n    {\n        mediaDriver = TestMediaDriver.launch(new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .dirDeleteOnStart(true),\n        testWatcher);\n        testWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.close(mediaDriver);\n    }\n\n    @Test\n    void shouldThrowWhenReentering()\n    {\n        final MutableReference<Throwable> expectedException = new MutableReference<>();\n        final ErrorHandler errorHandler = expectedException::set;\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().errorHandler(errorHandler)))\n        {\n            final String channel = CommonContext.IPC_CHANNEL;\n            final AvailableImageHandler mockHandler = mock(AvailableImageHandler.class);\n            doAnswer((invocation) -> aeron.addSubscription(channel, 3))\n                .when(mockHandler).onAvailableImage(any(Image.class));\n\n            final Subscription sub = aeron.addSubscription(channel, 1001, mockHandler, null);\n            final Publication pub = aeron.addPublication(channel, 1001);\n\n            verify(mockHandler, timeout(5000L)).onAvailableImage(any(Image.class));\n\n            pub.close();\n            sub.close();\n\n            assertThat(expectedException.get(), instanceOf(AeronException.class));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/RegistrationAndOwnerTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static io.aeron.test.Tests.awaitConnected;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass RegistrationAndOwnerTest\n{\n    private static final int STREAM_ID = 1001;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"aeron:udp?endpoint=localhost:24325\", \"aeron:ipc\" })\n    void shouldHaveCorrectOwnershipOnEntities(final String channel)\n    {\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .dirDeleteOnStart(true);\n\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(ctx, testWatcher))\n        {\n            testWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n\n            try (\n                Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n                Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n                Publication publication = aeron.addPublication(channel, STREAM_ID);\n                Counter userCounter = aeron.addCounter(1002, \"Test Counter\"))\n            {\n                testWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n\n                awaitConnected(subscription);\n                awaitConnected(publication);\n\n                final CountersReader countersReader = aeron.countersReader();\n                final int subscriberPositionId = subscription.imageAtIndex(0).subscriberPositionId();\n\n                assertEquals(aeron.clientId(), countersReader.getCounterOwnerId(subscriberPositionId));\n                assertEquals(aeron.clientId(), countersReader.getCounterOwnerId(publication.positionLimitId()));\n                assertEquals(aeron.clientId(), countersReader.getCounterOwnerId(userCounter.id()));\n\n                assertEquals(\n                    subscription.registrationId(), countersReader.getCounterRegistrationId(subscriberPositionId));\n\n                assertEquals(\n                    publication.registrationId(),\n                    countersReader.getCounterRegistrationId(publication.positionLimitId()));\n\n                assertEquals(userCounter.registrationId(),\n                    countersReader.getCounterRegistrationId(userCounter.id()));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/RejectImageTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.status.PublicationErrorFrame;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.OneToOneConcurrentArrayQueue;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.UnknownHostException;\nimport java.util.Arrays;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\nimport static io.aeron.driver.status.SystemCounterDescriptor.*;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class RejectImageTest\n{\n    private static final String UDP_CHANNEL = \"aeron:udp?endpoint=localhost:10000\";\n    private static final String IPC_CHANNEL = CommonContext.IPC_CHANNEL;\n\n    private static Stream<Arguments> channels()\n    {\n        return Stream.of(\n            Arguments.of(UDP_CHANNEL),\n            Arguments.of(IPC_CHANNEL)\n        );\n    }\n\n    public static final long A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES = 1000L;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private final String channel = \"aeron:udp?endpoint=localhost:10000\";\n    private final int streamId = 10000;\n    private final DirectBuffer message = new UnsafeBuffer(\"this is a test message\".getBytes(US_ASCII));\n\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .dirDeleteOnStart(true)\n        .threadingMode(ThreadingMode.SHARED);\n    private TestMediaDriver driver;\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietClose(driver);\n    }\n\n    private TestMediaDriver launch()\n    {\n        driver = TestMediaDriver.launch(context, systemTestWatcher);\n        return driver;\n    }\n\n    private static final class QueuedErrorFrameHandler implements PublicationErrorFrameHandler\n    {\n        private final AtomicInteger counter = new AtomicInteger(0);\n        private final OneToOneConcurrentArrayQueue<PublicationErrorFrame> errorFrameQueue =\n            new OneToOneConcurrentArrayQueue<>(512);\n\n        public void onPublicationError(final PublicationErrorFrame errorFrame)\n        {\n            if (!errorFrameQueue.offer(errorFrame.clone()))\n            {\n                counter.incrementAndGet();\n            }\n        }\n\n        PublicationErrorFrame poll()\n        {\n            if (counter.get() > 0)\n            {\n                throw new RuntimeException(\"Failed to offer to the errorFrameQueue in the test\");\n            }\n\n            return errorFrameQueue.poll();\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    @SlowTest\n    @SuppressWarnings(\"MethodLength\")\n    void shouldRejectSubscriptionsImage(final String channel) throws IOException\n    {\n        context.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(1234));\n\n        final TestMediaDriver driver = launch();\n        final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .publicationErrorFrameHandler(errorFrameHandler);\n\n        final AtomicInteger imageAvailable = new AtomicInteger();\n        final AtomicInteger imageUnavailable = new AtomicInteger();\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(channel, streamId);\n            Subscription sub = aeron.addSubscription(\n                channel,\n                streamId,\n                image -> imageAvailable.getAndIncrement(),\n                image -> imageUnavailable.getAndIncrement()))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            final CountersReader countersReader = aeron.countersReader();\n            final long initialErrorFramesReceived = countersReader.getCounterValue(ERROR_FRAMES_RECEIVED.id());\n            final long initialErrorFramesSent = countersReader.getCounterValue(ERROR_FRAMES_SENT.id());\n            final long initialErrors = countersReader.getCounterValue(ERRORS.id());\n            final long initialImagesRejected = countersReader.getCounterValue(IMAGES_REJECTED.id());\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            assertEquals(pub.position(), image.position());\n\n            final String reason = \"Needs to be closed\";\n            image.reject(reason);\n\n            final long t0 = System.nanoTime();\n            while (pub.isConnected())\n            {\n                Tests.yield();\n            }\n            final long t1 = System.nanoTime();\n            final long value = driver.context().publicationConnectionTimeoutNs();\n            assertThat(t1 - t0, lessThan(value));\n\n            while (initialImagesRejected == countersReader.getCounterValue(IMAGES_REJECTED.id()))\n            {\n                Tests.yield();\n            }\n\n            if (channel.contains(CommonContext.UDP_CHANNEL))\n            {\n                while (initialErrorFramesReceived == countersReader.getCounterValue(ERROR_FRAMES_RECEIVED.id()) ||\n                    initialErrorFramesSent == countersReader.getCounterValue(ERROR_FRAMES_SENT.id()))\n                {\n                    Tests.yield();\n                }\n            }\n\n            while (initialErrors == countersReader\n                .getCounterValue(ERRORS.id()))\n            {\n                Tests.yield();\n            }\n\n            PublicationErrorFrame errorFrame;\n            while (null == (errorFrame = errorFrameHandler.poll()))\n            {\n                Tests.yield();\n            }\n\n            assertEquals(reason, errorFrame.errorMessage());\n            assertEquals(pub.registrationId(), errorFrame.registrationId());\n\n            while (0 == imageUnavailable.get())\n            {\n                Tests.yield();\n            }\n\n            assertThat(\n                countersReader.getCounterValue(ERROR_FRAMES_RECEIVED.id()) - initialErrorFramesReceived,\n                lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES));\n\n            assertEquals(1, countersReader.getCounterValue(IMAGES_REJECTED.id()));\n\n            assertEquals(1, countersReader.getCounterValue(ERRORS.id()) - initialErrors);\n\n            SystemTests.waitForErrorToOccur(driver.aeronDirectoryName(), containsString(reason), Tests.SLEEP_1_MS);\n\n            // Should reconnect after an image liveness timeout\n            while (1 == imageAvailable.get())\n            {\n                Tests.yield();\n            }\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n            assertEquals(1, sub.imageCount());\n            assertNotSame(image, sub.imageAtIndex(0));\n            assertEquals(IPC_CHANNEL.equals(channel), image.correlationId() == sub.imageAtIndex(0).correlationId());\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldSecondConcurrentPublicationNotBeConnected(final String channel) throws IOException\n    {\n        context.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(1234));\n\n        final TestMediaDriver driver = launch();\n        final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .publicationErrorFrameHandler(errorFrameHandler);\n\n        final AtomicInteger imageAvailable = new AtomicInteger();\n        final AtomicInteger imageUnavailable = new AtomicInteger();\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(channel, streamId);\n            Subscription sub = aeron.addSubscription(\n                channel,\n                streamId,\n                image -> imageAvailable.getAndIncrement(),\n                image -> imageUnavailable.getAndIncrement()))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            assertEquals(pub.position(), image.position());\n\n            final String reason = \"Needs to be closed\";\n            image.reject(reason);\n\n            final long t0 = System.nanoTime();\n            while (pub.isConnected())\n            {\n                Tests.yield();\n            }\n            final long t1 = System.nanoTime();\n            final long value = driver.context().publicationConnectionTimeoutNs();\n            assertThat(t1 - t0, lessThan(value));\n\n            while (null == errorFrameHandler.poll())\n            {\n                Tests.yield();\n            }\n\n            try (Publication pub2 = aeron.addPublication(channel, streamId))\n            {\n                while (!pub.isConnected() && !pub2.isConnected())\n                {\n                    Tests.yield();\n                }\n                assertTrue(pub.isConnected());\n                assertTrue(pub2.isConnected());\n\n                SystemTests.waitForErrorToOccur(driver.aeronDirectoryName(), containsString(reason), Tests.SLEEP_1_MS);\n\n                // Should reconnect after an image liveness timeout\n                while (1 == imageAvailable.get())\n                {\n                    Tests.yield();\n                }\n                Tests.awaitConnected(pub);\n                Tests.awaitConnected(pub2);\n                Tests.awaitConnected(sub);\n                assertEquals(1, sub.imageCount());\n                assertNotSame(image, sub.imageAtIndex(0));\n                assertEquals(IPC_CHANNEL.equals(channel), image.correlationId() == sub.imageAtIndex(0).correlationId());\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldErrorIfSpyRejectsImage()\n    {\n        context.spiesSimulateConnection(true);\n\n        final TestMediaDriver driver = launch();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(channel, streamId);\n            Subscription sub = aeron.addSubscription(CommonContext.SPY_PREFIX + channel, streamId))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            final AeronException exception =\n                assertThrows(AeronException.class, () -> sub.imageAtIndex(0).reject(\"doesn't matter\"));\n            assertTrue(exception.getMessage().contains(\"spies\"));\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldOnlyReceivePublicationErrorFrameOnRelevantClient(final String channel)\n    {\n        context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));\n\n        final TestMediaDriver driver = launch();\n        final QueuedErrorFrameHandler errorFrameHandler1 = new QueuedErrorFrameHandler();\n        final QueuedErrorFrameHandler errorFrameHandler2 = new QueuedErrorFrameHandler();\n\n        final Aeron.Context ctx1 = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .publicationErrorFrameHandler(errorFrameHandler1);\n\n        final Aeron.Context ctx2 = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .publicationErrorFrameHandler(errorFrameHandler2);\n\n        try (Aeron aeron1 = Aeron.connect(ctx1);\n            Aeron aeron2 = Aeron.connect(ctx2);\n            Publication pub = aeron1.addPublication(channel, streamId);\n            Subscription sub = aeron1.addSubscription(channel, streamId))\n        {\n            assertNotNull(aeron2);\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            final String reason = \"Needs to be closed\";\n            image.reject(reason);\n\n            PublicationErrorFrame errorFrame;\n            while (null == (errorFrame = errorFrameHandler1.poll()))\n            {\n                Tests.yield();\n            }\n\n            assertEquals(pub.registrationId(), errorFrame.registrationId());\n            assertEquals(ErrorCode.IMAGE_REJECTED, ErrorCode.get(errorFrame.errorCode()));\n            assertEquals(reason, errorFrame.errorMessage());\n\n            final long deadlineMs = System.currentTimeMillis() + 1_000;\n            while (System.currentTimeMillis() < deadlineMs)\n            {\n                assertNull(errorFrameHandler2.poll(), \"Aeron client without publication should not report error\");\n                Tests.yield();\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldReceivePublicationErrorFramesAllRelevantClients(final String channel)\n    {\n        context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));\n\n        final TestMediaDriver driver = launch();\n        final QueuedErrorFrameHandler errorFrameHandler1 = new QueuedErrorFrameHandler();\n        final QueuedErrorFrameHandler errorFrameHandler2 = new QueuedErrorFrameHandler();\n\n        final Aeron.Context ctx1 = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .publicationErrorFrameHandler(errorFrameHandler1);\n\n        final Aeron.Context ctx2 = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .publicationErrorFrameHandler(errorFrameHandler2);\n\n        try (Aeron aeron1 = Aeron.connect(ctx1);\n            Aeron aeron2 = Aeron.connect(ctx2);\n            Publication pub = aeron1.addPublication(channel, streamId);\n            Publication pubOther = aeron2.addPublication(channel, streamId);\n            Subscription sub = aeron1.addSubscription(channel, streamId))\n        {\n            assertNotNull(aeron2);\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(pubOther);\n            Tests.awaitConnected(sub);\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            final String reason = \"Needs to be closed\";\n            image.reject(reason);\n\n            PublicationErrorFrame errorFrame;\n            while (null == (errorFrame = errorFrameHandler1.poll()))\n            {\n                Tests.yield();\n            }\n\n            assertEquals(pub.registrationId(), errorFrame.registrationId());\n            assertEquals(ErrorCode.IMAGE_REJECTED, ErrorCode.get(errorFrame.errorCode()));\n            assertEquals(reason, errorFrame.errorMessage());\n\n            while (null == (errorFrame = errorFrameHandler2.poll()))\n            {\n                Tests.yield();\n            }\n\n            assertEquals(pub.registrationId(), errorFrame.registrationId());\n            assertEquals(ErrorCode.IMAGE_REJECTED, ErrorCode.get(errorFrame.errorCode()));\n            assertEquals(reason, errorFrame.errorMessage());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldRejectSubscriptionsImageManualMdc()\n    {\n        context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));\n\n        final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler();\n        final TestMediaDriver driver = launch();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .publicationErrorFrameHandler(errorFrameHandler);\n\n        final AtomicInteger imageAvailable = new AtomicInteger(0);\n        final AtomicInteger imageUnavailable = new AtomicInteger(0);\n        final String mdc = \"aeron:udp?control-mode=manual\";\n        final String channel = \"aeron:udp?endpoint=localhost:10000\";\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(mdc, streamId);\n            Subscription sub = aeron.addSubscription(\n                channel,\n                streamId,\n                (image) -> imageAvailable.incrementAndGet(),\n                (image) -> imageUnavailable.incrementAndGet()))\n        {\n            final long destinationRegistrationId = pub.asyncAddDestination(channel);\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            final long initialErrorMessagesReceived = aeron.countersReader()\n                .getCounterValue(ERROR_FRAMES_RECEIVED.id());\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            assertEquals(pub.position(), image.position());\n\n            final int initialAvailable = imageAvailable.get();\n            final String reason = \"Needs to be closed\";\n            image.reject(reason);\n\n            final long t0 = System.nanoTime();\n            while (pub.isConnected())\n            {\n                Tests.yield();\n            }\n            final long t1 = System.nanoTime();\n            final long value = driver.context().publicationConnectionTimeoutNs();\n            assertThat(t1 - t0, lessThan(value));\n\n            while (initialErrorMessagesReceived == aeron.countersReader()\n                .getCounterValue(ERROR_FRAMES_RECEIVED.id()))\n            {\n                Tests.yield();\n            }\n\n            while (0 == imageUnavailable.get())\n            {\n                Tests.yield();\n            }\n\n            assertThat(\n                aeron.countersReader().getCounterValue(ERROR_FRAMES_RECEIVED.id()),\n                lessThan(A_VALUE_THAT_SHOWS_WE_ARENT_SPAMMING_ERROR_MESSAGES));\n\n            while (initialAvailable == imageAvailable.get())\n            {\n                Tests.yield();\n            }\n\n            PublicationErrorFrame errorFrame;\n            while (null == (errorFrame = errorFrameHandler.poll()))\n            {\n                Tests.yield();\n            }\n\n            assertEquals(reason, errorFrame.errorMessage());\n            assertEquals(pub.registrationId(), errorFrame.registrationId());\n            assertEquals(destinationRegistrationId, errorFrame.destinationRegistrationId());\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldErrorIfRejectionReasonIsTooLong(final String channel)\n    {\n        context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));\n        final byte[] bytes = new byte[1024];\n        Arrays.fill(bytes, (byte)'x');\n        final String tooLongReason = new String(bytes, US_ASCII);\n\n        final TestMediaDriver driver = launch();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(channel, streamId);\n            Subscription sub = aeron.addSubscription(channel, streamId))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            assertThrows(AeronException.class, () -> sub.imageAtIndex(0).reject(tooLongReason));\n        }\n    }\n\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldErrorIfRejectionReasonIsTooLongForLocalBuffer(final String channel)\n    {\n        context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));\n        final byte[] bytes = new byte[1024 * 1024];\n        Arrays.fill(bytes, (byte)'x');\n        final String tooLongReason = new String(bytes, US_ASCII);\n\n        final TestMediaDriver driver = launch();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(channel, streamId);\n            Subscription sub = aeron.addSubscription(channel, streamId))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            assertThrows(IllegalArgumentException.class, () -> sub.imageAtIndex(0).reject(tooLongReason));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldBeInCoolDownWhenSecondSubscriberJoins()\n    {\n        context\n            .imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(2))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100));\n\n        final String rejectionReason = \"Reject this\";\n\n        final TestMediaDriver driver = launch();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        final AtomicInteger imageAvailable = new AtomicInteger();\n        final AtomicInteger imageUnavailable = new AtomicInteger();\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(CommonContext.IPC_CHANNEL, streamId);\n            Subscription sub = aeron.addSubscription(\n                CommonContext.IPC_CHANNEL,\n                streamId,\n                image -> imageAvailable.getAndIncrement(),\n                image -> imageUnavailable.getAndIncrement()))\n        {\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            final long t0 = System.nanoTime();\n            sub.imageAtIndex(0).reject(rejectionReason);\n\n            try (Subscription sub2 = aeron.addSubscription(\n                CommonContext.IPC_CHANNEL,\n                streamId,\n                image -> imageAvailable.getAndIncrement(),\n                image -> imageUnavailable.getAndIncrement()))\n            {\n\n                while (!sub2.isConnected())\n                {\n                    Tests.yield();\n                }\n                final long t1 = System.nanoTime();\n\n                assertThat(t1 - t0, greaterThanOrEqualTo(context.imageLivenessTimeoutNs()));\n            }\n\n            assertEquals(3, imageAvailable.get());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldDeleteSubscriberWhileInCoolDown()\n    {\n        context\n            .imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(2))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100));\n\n        final String rejectionReason = \"Reject this\";\n\n        final TestMediaDriver driver = launch();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        final AtomicInteger imageAvailable = new AtomicInteger();\n        final AtomicInteger imageUnavailable = new AtomicInteger();\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(CommonContext.IPC_CHANNEL, streamId))\n        {\n\n            try (Subscription sub = aeron.addSubscription(\n                CommonContext.IPC_CHANNEL,\n                streamId,\n                image -> imageAvailable.getAndIncrement(),\n                image -> imageUnavailable.getAndIncrement()))\n            {\n\n                Tests.awaitConnected(pub);\n                Tests.awaitConnected(sub);\n\n                sub.imageAtIndex(0).reject(rejectionReason);\n\n                while (0 == imageUnavailable.get())\n                {\n                    Tests.yield();\n                }\n            }\n\n            try (Subscription sub2 = aeron.addSubscription(\n                CommonContext.IPC_CHANNEL,\n                streamId,\n                image -> imageAvailable.getAndIncrement(),\n                image -> imageUnavailable.getAndIncrement()))\n            {\n                while (!sub2.isConnected())\n                {\n                    Tests.yield();\n                }\n            }\n\n            assertEquals(2, imageAvailable.get());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldSecondSubscriberJoinsImmediatelyAfterCooldownEnds()\n    {\n        context\n            .imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(2))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100));\n\n        final String rejectionReason = \"Reject this\";\n\n        final TestMediaDriver driver = launch();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        final AtomicInteger imageAvailable = new AtomicInteger();\n        final AtomicInteger imageUnavailable = new AtomicInteger();\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(CommonContext.IPC_CHANNEL, streamId);\n            Subscription sub = aeron.addSubscription(\n                CommonContext.IPC_CHANNEL,\n                streamId,\n                image -> imageAvailable.getAndIncrement(),\n                image -> imageUnavailable.getAndIncrement()))\n        {\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            sub.imageAtIndex(0).reject(rejectionReason);\n\n            while (0 == imageUnavailable.get())\n            {\n                Tests.yield();\n            }\n\n            while (!sub.isConnected())\n            {\n                Tests.yield();\n            }\n\n            try (Subscription sub2 = aeron.addSubscription(\n                CommonContext.IPC_CHANNEL,\n                streamId,\n                image -> imageAvailable.getAndIncrement(),\n                image -> imageUnavailable.getAndIncrement()))\n            {\n                while (!sub2.isConnected())\n                {\n                    Tests.yield();\n                }\n            }\n\n            assertEquals(3, imageAvailable.get());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SlowTest\n    void shouldSecondPublisherConnectsAfterCooldown()\n    {\n        context\n            .imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(2))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100));\n\n        final String rejectionReason = \"Reject this\";\n\n        final TestMediaDriver driver = launch();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        final AtomicInteger imageAvailable = new AtomicInteger();\n        final AtomicInteger imageUnavailable = new AtomicInteger();\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(CommonContext.IPC_CHANNEL, streamId);\n            Subscription sub = aeron.addSubscription(\n                CommonContext.IPC_CHANNEL,\n                streamId,\n                image -> imageAvailable.getAndIncrement(),\n                image -> imageUnavailable.getAndIncrement()))\n        {\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            final long t0 = System.nanoTime();\n            sub.imageAtIndex(0).reject(rejectionReason);\n\n            while (0 == imageUnavailable.get())\n            {\n                Tests.yield();\n            }\n\n            try (Publication pub2 = aeron.addPublication(\n                CommonContext.IPC_CHANNEL,\n                streamId))\n            {\n                while (!pub2.isConnected())\n                {\n                    Tests.yield();\n                }\n                final long t1 = System.nanoTime();\n\n                assertThat(t1 - t0, greaterThanOrEqualTo(context.imageLivenessTimeoutNs()));\n            }\n\n            while (2 != imageAvailable.get())\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"127.0.0.1\", \"[::1]\" })\n    @InterruptAfter(5)\n    void shouldReturnAllParametersToApi(final String addressStr) throws UnknownHostException\n    {\n        final InetAddress address = InetAddress.getByName(addressStr);\n        assumeTrue(address instanceof Inet4Address || System.getProperty(\"java.net.preferIPv4Stack\") == null);\n\n        context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));\n\n        final TestMediaDriver driver = launch();\n        final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .publicationErrorFrameHandler(errorFrameHandler);\n\n        final long groupTag = 1001;\n        final int port = 10001;\n\n        final String mdc = \"aeron:udp?control-mode=dynamic|control=\" + addressStr + \":10000|fc=tagged,g:\" + groupTag;\n        final String channel =\n            \"aeron:udp?control=\" + addressStr + \":10000|endpoint=\" + addressStr + \":\" + port + \"|gtag=\" + groupTag;\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(mdc, streamId);\n            Subscription sub = aeron.addSubscription(channel, streamId))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            assertEquals(pub.position(), image.position());\n\n            final String reason = \"Needs to be closed\";\n            image.reject(reason);\n\n            PublicationErrorFrame errorFrame;\n            while (null == (errorFrame = errorFrameHandler.poll()))\n            {\n                Tests.yield();\n            }\n\n            assertEquals(reason, errorFrame.errorMessage());\n            assertEquals(pub.registrationId(), errorFrame.registrationId());\n            assertEquals(Aeron.NULL_VALUE, errorFrame.destinationRegistrationId());\n            assertEquals(pub.streamId(), errorFrame.streamId());\n            assertEquals(pub.sessionId(), errorFrame.sessionId());\n            assertEquals(groupTag, errorFrame.groupTag());\n            assertNotNull(errorFrame.sourceAddress());\n            assertEquals(new InetSocketAddress(addressStr, port), errorFrame.sourceAddress());\n        }\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(5)\n    @ValueSource(booleans = { true, false })\n    void shouldOnlyReceivePublicationErrorFrames(final boolean isExclusive)\n    {\n        context.imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(3));\n\n        final TestMediaDriver driver = launch();\n        final QueuedErrorFrameHandler errorFrameHandler = new QueuedErrorFrameHandler();\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .publicationErrorFrameHandler(errorFrameHandler);\n\n        final Function<Aeron, ? extends Publication> addPub = (aeron) -> isExclusive ?\n            aeron.addExclusivePublication(channel, streamId) : aeron.addPublication(channel, streamId);\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = addPub.apply(aeron);\n            Subscription sub = aeron.addSubscription(channel, streamId))\n        {\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            final String reason = \"Needs to be closed\";\n            image.reject(reason);\n\n            while (null == errorFrameHandler.poll())\n            {\n                Tests.yield();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/RemoteEchoTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.media.NetworkUtil;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.samples.echo.ProvisioningServerMain;\nimport io.aeron.samples.echo.api.EchoMonitorMBean;\nimport io.aeron.samples.echo.api.ProvisioningConstants;\nimport io.aeron.samples.echo.api.ProvisioningMBean;\nimport io.aeron.test.*;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport javax.management.*;\nimport javax.management.remote.JMXConnector;\nimport javax.management.remote.JMXConnectorFactory;\nimport javax.management.remote.JMXServiceURL;\nimport java.io.IOException;\nimport java.lang.management.ManagementFactory;\nimport java.lang.reflect.UndeclaredThrowableException;\nimport java.net.InetAddress;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport static io.aeron.samples.echo.api.ProvisioningConstants.IO_AERON_TYPE_PROVISIONING_NAME_TESTING;\nimport static java.lang.Math.min;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@BindingsTest\n@ExtendWith(InterruptingTestCallback.class)\nclass RemoteEchoTest\n{\n    private static final int SOURCE_DATA_LENGTH = 1024 * 1024;\n    private static MediaDriver mediaDriver = null;\n    private static ProvisioningServerMain provisioningServer = null;\n    private static JMXConnector connector = null;\n    private static IoSupplier<MBeanServerConnection> mbeanConnectionSupplier = null;\n    private static String remoteHost = null;\n    private static String localHost = null;\n    private static String aeronDir = null;\n\n    private DirectBuffer sourceData;\n    private MBeanServerConnection mBeanServerConnection;\n    private ProvisioningMBean provisioningMBean;\n\n    private static boolean isEmpty(final String s)\n    {\n        return null == s || s.trim().isEmpty();\n    }\n\n    @RegisterExtension\n    final RandomWatcher randomWatcher = new RandomWatcher(18604930465192L);\n\n    @BeforeAll\n    static void beforeAll() throws IOException\n    {\n        remoteHost = System.getProperty(\"aeron.test.system.binding.remote.host\");\n        localHost = System.getProperty(\"aeron.test.system.binding.local.host\");\n        aeronDir = System.getProperty(\"aeron.test.system.aeron.dir\");\n\n        // If aeron.dir is set assume an external driver, otherwise start embedded.\n        if (isEmpty(aeronDir))\n        {\n            mediaDriver = MediaDriver.launchEmbedded(new MediaDriver.Context().dirDeleteOnShutdown(true));\n            aeronDir = mediaDriver.aeronDirectoryName();\n        }\n\n        // If remote host is not set run the provision service locally.\n        if (isEmpty(remoteHost))\n        {\n            // TODO: echo service in basic test, check for remote access property.\n            provisioningServer = ProvisioningServerMain.launch(new Aeron.Context());\n            remoteHost = \"localhost\";\n            localHost = \"localhost\";\n            mbeanConnectionSupplier = ManagementFactory::getPlatformMBeanServer;\n        }\n        else\n        {\n            if (isEmpty(localHost))\n            {\n                final InetAddress address = InetAddress.getByName(remoteHost);\n                localHost = Objects.requireNonNull(NetworkUtil.findFirstMatchingLocalAddress(address)).getHostAddress();\n            }\n\n            final String serviceURL = \"service:jmx:rmi:///jndi/rmi://\" + remoteHost + \":10000/jmxrmi\";\n            final JMXServiceURL url = new JMXServiceURL(serviceURL);\n            connector = JMXConnectorFactory.connect(url);\n            mbeanConnectionSupplier = connector::getMBeanServerConnection;\n\n            System.out.println(\"Using local=\" + localHost + \" remote=\" + remoteHost + \" for communication\");\n        }\n    }\n\n    @AfterAll\n    static void afterAll()\n    {\n        try\n        {\n            final MBeanServerConnection mBeanServerConnection = mbeanConnectionSupplier.get();\n            final ProvisioningMBean provisioningMBean = JMX.newMBeanProxy(\n                mBeanServerConnection,\n                new ObjectName(IO_AERON_TYPE_PROVISIONING_NAME_TESTING),\n                ProvisioningMBean.class);\n            provisioningMBean.removeAll();\n        }\n        catch (final Exception ex)\n        {\n            ex.printStackTrace();\n        }\n\n        CloseHelper.quietCloseAll(connector, provisioningServer, mediaDriver);\n    }\n\n    @BeforeEach\n    void setUp() throws IOException, MalformedObjectNameException, TimeoutException\n    {\n        final byte[] bytes = new byte[SOURCE_DATA_LENGTH];\n        randomWatcher.random().nextBytes(bytes);\n        sourceData = new UnsafeBuffer(bytes);\n\n        connectJmxMBean();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldHandleSingleUnicastEchoPair() throws MalformedObjectNameException\n    {\n        final String requestUri = new ChannelUriStringBuilder()\n            .media(\"udp\").endpoint(remoteHost + \":24324\").rejoin(false).linger(0L).termLength(1 << 16).build();\n        final String responseUri = new ChannelUriStringBuilder()\n            .media(\"udp\").endpoint(localHost + \":24325\").rejoin(false).linger(0L).termLength(1 << 16).build();\n        final int requestStreamId = 1001;\n        final int responseStreamId = 1002;\n\n        provisioningMBean.createEchoPair(1, requestUri, requestStreamId, responseUri, responseStreamId);\n\n        try (\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDir));\n            Publication pub = aeron.addPublication(requestUri, requestStreamId);\n            Subscription sub = aeron.addSubscription(responseUri, responseStreamId))\n        {\n            final EchoMonitorMBean echoMonitorMBean = JMX.newMBeanProxy(\n                mBeanServerConnection,\n                new ObjectName(ProvisioningConstants.echoPairObjectName(1)),\n                EchoMonitorMBean.class);\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            sendAndReceiveRandomData(pub, sub);\n\n            assertTrue(0 < echoMonitorMBean.getFragmentCount());\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldHandleTenUnicastEchoPairs()\n    {\n        final List<Publication> pubs = new ArrayList<>();\n        final List<Subscription> subs = new ArrayList<>();\n\n        final ChannelUriStringBuilder requestUriBuilder = new ChannelUriStringBuilder()\n            .media(\"udp\").rejoin(false).linger(0L).termLength(1 << 16);\n        final ChannelUriStringBuilder responseUriBuilder = new ChannelUriStringBuilder()\n            .media(\"udp\").rejoin(false).linger(0L).termLength(1 << 16);\n        final int requestStreamId = 1001;\n        final int responseStreamId = 1002;\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDir)))\n        {\n            for (int i = 0; i < 10; i++)\n            {\n                final String requestUri = requestUriBuilder.endpoint(remoteHost + \":\" + (24300 + i)).build();\n                final String responseUri = responseUriBuilder.endpoint(localHost + \":\" + (24400 + i)).build();\n\n                provisioningMBean.createEchoPair(i + 1, requestUri, requestStreamId, responseUri, responseStreamId);\n\n                final ConcurrentPublication pub = aeron.addPublication(requestUri, requestStreamId);\n                final Subscription sub = aeron.addSubscription(responseUri, responseStreamId);\n\n                pubs.add(pub);\n                subs.add(sub);\n\n                Tests.awaitConnected(pub);\n                Tests.awaitConnected(sub);\n            }\n\n            sendAndReceiveRandomData(pubs, subs);\n        }\n        finally\n        {\n            CloseHelper.quietCloseAll(pubs);\n            CloseHelper.quietCloseAll(subs);\n        }\n    }\n\n    private void sendAndReceiveRandomData(\n        final Publication pub,\n        final Subscription sub)\n    {\n        final ExpandableArrayBuffer receivedData = new ExpandableArrayBuffer(SOURCE_DATA_LENGTH);\n        final MutableInteger sentBytes = new MutableInteger(0);\n        final MutableInteger recvBytes = new MutableInteger(0);\n\n        final FragmentHandler handler = (buffer, offset, length, header) ->\n        {\n            receivedData.putBytes(recvBytes.get(), buffer, offset, length);\n            recvBytes.addAndGet(length);\n        };\n\n        while (recvBytes.get() < SOURCE_DATA_LENGTH)\n        {\n            if (sentBytes.get() < SOURCE_DATA_LENGTH)\n            {\n                final int randomLength = randomWatcher.random().nextInt(pub.maxMessageLength());\n                final int toSend = min(SOURCE_DATA_LENGTH - sentBytes.get(), randomLength);\n                if (pub.offer(sourceData, sentBytes.get(), toSend) > 0)\n                {\n                    sentBytes.addAndGet(toSend);\n                }\n                Tests.yield();\n            }\n\n            if (sub.poll(handler, 10) <= 0)\n            {\n                Tests.yield();\n            }\n        }\n\n        assertEquals(0, sourceData.compareTo(receivedData));\n    }\n\n    private void sendAndReceiveRandomData(\n        final List<Publication> pubs,\n        final List<Subscription> subs)\n    {\n        assertEquals(pubs.size(), subs.size());\n\n        final List<ExpandableArrayBuffer> receivedDataBuffers = new ArrayList<>();\n        final List<MutableInteger> sentDataCounts = new ArrayList<>();\n        final List<MutableInteger> receivedDataCounts = new ArrayList<>();\n        final List<FragmentHandler> handlers = new ArrayList<>();\n\n        for (int i = 0; i < pubs.size(); i++)\n        {\n            final ExpandableArrayBuffer receivedData = new ExpandableArrayBuffer(SOURCE_DATA_LENGTH);\n            final MutableInteger sentBytes = new MutableInteger(0);\n            final MutableInteger recvBytes = new MutableInteger(0);\n            final FragmentHandler handler = (buffer, offset, length, header) ->\n            {\n                receivedData.putBytes(recvBytes.get(), buffer, offset, length);\n                recvBytes.addAndGet(length);\n            };\n\n            receivedDataBuffers.add(receivedData);\n            sentDataCounts.add(sentBytes);\n            receivedDataCounts.add(recvBytes);\n            handlers.add(handler);\n        }\n\n        while (dataIsPending(receivedDataCounts, SOURCE_DATA_LENGTH))\n        {\n            for (int i = 0; i < pubs.size(); i++)\n            {\n                final Publication pub = pubs.get(i);\n                final Subscription sub = subs.get(i);\n                final MutableInteger sentBytes = sentDataCounts.get(i);\n                final FragmentHandler handler = handlers.get(i);\n\n                if (sentBytes.get() < SOURCE_DATA_LENGTH)\n                {\n                    final int randomLength = randomWatcher.random().nextInt(pub.maxMessageLength());\n                    final int toSend = min(SOURCE_DATA_LENGTH - sentBytes.get(), randomLength);\n                    if (pub.offer(sourceData, sentBytes.get(), toSend) > 0)\n                    {\n                        sentBytes.addAndGet(toSend);\n                    }\n                    Tests.yield();\n                }\n\n                if (sub.poll(handler, 10) <= 0)\n                {\n                    Tests.yield();\n                }\n            }\n        }\n\n        for (final ExpandableArrayBuffer receivedData : receivedDataBuffers)\n        {\n            assertEquals(0, sourceData.compareTo(receivedData));\n        }\n\n    }\n\n    private boolean dataIsPending(final List<MutableInteger> receivedDataCounts, final int sourceDataLength)\n    {\n        for (final MutableInteger receivedDataCount : receivedDataCounts)\n        {\n            if (receivedDataCount.get() < sourceDataLength)\n            {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    private interface IoSupplier<T>\n    {\n        T get() throws IOException;\n    }\n\n    private void connectJmxMBean() throws IOException, MalformedObjectNameException, TimeoutException\n    {\n        mBeanServerConnection = mbeanConnectionSupplier.get();\n        final long deadlineMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1);\n        do\n        {\n            final ProvisioningMBean provisioningMBean = JMX.newMBeanProxy(\n                mBeanServerConnection,\n                new ObjectName(IO_AERON_TYPE_PROVISIONING_NAME_TESTING),\n                ProvisioningMBean.class);\n\n            try\n            {\n                provisioningMBean.removeAll();\n                this.provisioningMBean = provisioningMBean;\n                break;\n            }\n            catch (final UndeclaredThrowableException e)\n            {\n                if (!(e.getCause() instanceof InstanceNotFoundException))\n                {\n                    throw e;\n                }\n            }\n\n            if (deadlineMs <= System.currentTimeMillis())\n            {\n                throw new TimeoutException(\"Failed to connect to provisioning mbean\");\n            }\n        }\n        while (true);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ResolvedEndpointSystemTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.List;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.startsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass ResolvedEndpointSystemTest\n{\n    private static final int STREAM_ID = 2002;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[16]);\n    private final FragmentHandler fragmentHandler = mock(FragmentHandler.class);\n\n    private TestMediaDriver driver;\n    private Aeron client;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before()\n    {\n        buffer.putInt(0, 1);\n\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driver = TestMediaDriver.launch(context, testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(client, driver);\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldSubscribeWithSystemAssignedPort()\n    {\n        final String uri = \"aeron:udp?endpoint=localhost:0\";\n\n        try (Subscription sub = client.addSubscription(uri, STREAM_ID))\n        {\n            String resolvedUri;\n            while (null == (resolvedUri = sub.tryResolveChannelEndpointPort()))\n            {\n                Tests.yieldingIdle(\"No bind address/port for sub\");\n            }\n\n            assertThat(resolvedUri, startsWith(\"aeron:udp?endpoint=localhost:\"));\n\n            try (Publication pub = client.addPublication(resolvedUri, STREAM_ID))\n            {\n                while (pub.offer(buffer, 0, buffer.capacity()) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to publish to pub\");\n                }\n\n                while (sub.poll(fragmentHandler, 1) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to receive from sub\");\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldSubscribeToSystemAssignedPorts()\n    {\n        final long tag1 = client.nextCorrelationId();\n        final long tag2 = client.nextCorrelationId();\n\n        final String systemAssignedPortUri1 = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"127.0.0.1:0\")\n            .tags(tag1, null)\n            .build();\n        final String systemAssignedPortUri2 = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"127.0.0.1:0\")\n            .tags(tag2, null)\n            .build();\n        final String tagged1 = new ChannelUriStringBuilder().media(\"udp\").tags(tag1, null).build();\n\n        try (Subscription sub1 = client.addSubscription(systemAssignedPortUri1, STREAM_ID);\n            Subscription sub2 = client.addSubscription(systemAssignedPortUri2, STREAM_ID);\n            Subscription sub3 = client.addSubscription(tagged1, STREAM_ID + 1))\n        {\n            List<String> bindAddressAndPort1;\n            while ((bindAddressAndPort1 = sub1.localSocketAddresses()).isEmpty())\n            {\n                Tests.yieldingIdle(\"No bind address/port for sub1\");\n            }\n\n            List<String> bindAddressAndPort2;\n            while ((bindAddressAndPort2 = sub2.localSocketAddresses()).isEmpty())\n            {\n                Tests.yieldingIdle(\"No bind address/port for sub2\");\n            }\n\n            assertNotEquals(bindAddressAndPort1, bindAddressAndPort2);\n\n            List<String> bindAddressAndPort3;\n            while ((bindAddressAndPort3 = sub3.localSocketAddresses()).isEmpty())\n            {\n                Tests.yieldingIdle(\"No bind address/port for sub3\");\n            }\n\n            assertEquals(bindAddressAndPort3, bindAddressAndPort1);\n\n            final String pubUri = \"aeron:udp?endpoint=\" + bindAddressAndPort1.get(0);\n\n            try (Publication pub = client.addPublication(pubUri, STREAM_ID))\n            {\n                while (pub.offer(buffer, 0, buffer.capacity()) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to publish to pub\");\n                }\n\n                while (sub1.poll(fragmentHandler, 1) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to receive from sub1\");\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldSubscribeToSystemAssignedPortsUsingIPv6()\n    {\n        assumeFalse(\"true\".equals(System.getProperty(\"java.net.preferIPv4Stack\")));\n        final long channelTag = client.nextCorrelationId();\n\n        final String systemAssignedPortUri = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"[::1]:0\")\n            .tags(channelTag, null)\n            .build();\n        final String taggedUri = new ChannelUriStringBuilder().media(\"udp\").tags(channelTag, null).build();\n\n\n        try (Subscription sub1 = client.addSubscription(systemAssignedPortUri, STREAM_ID);\n            Subscription sub2 = client.addSubscription(taggedUri, STREAM_ID + 1))\n        {\n            List<String> bindAddressAndPort1;\n            while ((bindAddressAndPort1 = sub1.localSocketAddresses()).isEmpty())\n            {\n                Tests.yieldingIdle(\"No bind address/port for sub1\");\n            }\n\n            List<String> bindAddressAndPort2;\n            while ((bindAddressAndPort2 = sub2.localSocketAddresses()).isEmpty())\n            {\n                Tests.yieldingIdle(\"No bind address/port for sub2\");\n            }\n\n            assertEquals(bindAddressAndPort2, bindAddressAndPort1);\n\n            final String pubUri = \"aeron:udp?endpoint=\" + bindAddressAndPort1.get(0);\n\n            try (Publication pub = client.addPublication(pubUri, STREAM_ID))\n            {\n                while (pub.offer(buffer, 0, buffer.capacity()) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to publish to pub\");\n                }\n\n                while (sub1.poll(fragmentHandler, 1) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to receive from sub1\");\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldBindMultipleSystemAssignedEndpointsForMultiDestinationSubscription()\n    {\n        final String systemAssignedPortUri1 = \"aeron:udp?endpoint=127.0.0.1:0\";\n        final String systemAssignedPortUri2 = \"aeron:udp?endpoint=127.0.0.1:0\";\n\n        try (Subscription mdsSub = client.addSubscription(\"aeron:udp?control-mode=manual\", STREAM_ID))\n        {\n            mdsSub.addDestination(systemAssignedPortUri1);\n            mdsSub.addDestination(systemAssignedPortUri2);\n\n            List<String> bindAddressAndPorts;\n            while (2 > (bindAddressAndPorts = mdsSub.localSocketAddresses()).size())\n            {\n                Tests.yieldingIdle(\"Unable to get bind address/ports for mds subscription\");\n            }\n\n            final String pub1Uri = \"aeron:udp?endpoint=\" + bindAddressAndPorts.get(0);\n            final String pub2Uri = \"aeron:udp?endpoint=\" + bindAddressAndPorts.get(1);\n\n            try (Publication pub1 = client.addPublication(pub1Uri, STREAM_ID);\n                Publication pub2 = client.addPublication(pub2Uri, STREAM_ID))\n            {\n                while (pub1.offer(buffer, 0, buffer.capacity()) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to publish to pub1\");\n                }\n\n                while (pub2.offer(buffer, 0, buffer.capacity()) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to publish to pub2\");\n                }\n\n                long totalReceived = 0;\n                while (totalReceived < 2)\n                {\n                    Tests.yieldingIdle(\"Failed to receive from both publications\");\n                    totalReceived += mdsSub.poll(fragmentHandler, 10);\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldAllowSystemAssignedPortOnDynamicMultiDestinationPublication()\n    {\n        final String mdcUri = \"aeron:udp?control=localhost:0|control-mode=dynamic\";\n\n        try (Publication pub = client.addPublication(mdcUri, STREAM_ID))\n        {\n            List<String> bindAddressAndPort1;\n            while ((bindAddressAndPort1 = pub.localSocketAddresses()).isEmpty())\n            {\n                Tests.yieldingIdle(\"No bind address/port for pub\");\n            }\n\n            final String mdcSubUri = new ChannelUriStringBuilder()\n                .media(\"udp\")\n                .controlEndpoint(bindAddressAndPort1.get(0))\n                .group(Boolean.TRUE)\n                .build();\n\n            try (Subscription sub = client.addSubscription(mdcSubUri, STREAM_ID))\n            {\n                while (pub.offer(buffer, 0, buffer.capacity()) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to publish to pub\");\n                }\n\n                while (sub.poll(fragmentHandler, 1) < 0)\n                {\n                    Tests.yieldingIdle(\"Failed to receive from sub\");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/ResponseChannelsTest.java",
    "content": "/*\n * Copyright 2014-2023 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.response.ResponseClient;\nimport io.aeron.response.ResponseServer;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.collections.MutableReference;\nimport org.agrona.concurrent.Agent;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static io.aeron.AeronCounters.DRIVER_PUBLISHER_POS_TYPE_ID;\nimport static io.aeron.CommonContext.*;\nimport static io.aeron.driver.status.SendChannelStatus.SEND_CHANNEL_STATUS_TYPE_ID;\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith(InterruptingTestCallback.class)\npublic class ResponseChannelsTest\n{\n    private static final int DEFAULT_TERM_LENGTH = 512 * 1024;\n    private static final String REQUEST_ENDPOINT = \"localhost:10000\";\n    private static final int REQUEST_STREAM_ID = 10000;\n    private static final String RESPONSE_CONTROL = \"localhost:10001\";\n    private static final int RESPONSE_STREAM_ID = 10001;\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver1;\n    private TestMediaDriver driver2;\n\n    @BeforeEach\n    void setUp()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(generateRandomDirName())\n            .dirDeleteOnShutdown(true)\n            .publicationTermBufferLength(DEFAULT_TERM_LENGTH)\n            .ipcTermBufferLength(DEFAULT_TERM_LENGTH)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driver1 = TestMediaDriver.launch(\n            context.clone()\n                .aeronDirectoryName(context.aeronDirectoryName() + \"-1\")\n                /*\n                For some reason, revoke() works much quicker against the java media driver.\n                There's a check in the LINGER state for having received a unicast EOS.  In java, we usually/always\n                see it more or less immediately, but in C, we don't.  So in C, we have to wait for the linger timeout.\n                Ultimately, it would be good to get this addressed in the C media driver.\n                 */\n                .publicationLingerTimeoutNs(200_000_000L),\n            watcher);\n        driver2 = TestMediaDriver.launch(\n            context.clone().aeronDirectoryName(context.aeronDirectoryName() + \"-2\"), watcher);\n        watcher.dataCollector().add(driver1.context().aeronDirectory());\n        watcher.dataCollector().add(driver2.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietCloseAll(driver1, driver2);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReceiveResponsesOnAPerClientBasis() throws Exception\n    {\n        final String textA = \"hello from client A\";\n        final String textB = \"hello from client B\";\n        final MutableDirectBuffer messageA = new UnsafeBuffer(textA.getBytes(UTF_8));\n        final MutableDirectBuffer messageB = new UnsafeBuffer(textB.getBytes(UTF_8));\n        final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n        final List<String> responsesA = new ArrayList<>();\n        final List<String> responsesB = new ArrayList<>();\n        final FragmentHandler fragmentHandlerA =\n            (buffer, offset, length, header) -> responsesA.add(buffer.getStringWithoutLengthUtf8(offset, length));\n        final FragmentHandler fragmentHandlerB =\n            (buffer, offset, length, header) -> responsesB.add(buffer.getStringWithoutLengthUtf8(offset, length));\n\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron clientA = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron clientB = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName()));\n            ResponseServer responseServer = new ResponseServer(\n                server, (image) -> new EchoHandler(), REQUEST_ENDPOINT, REQUEST_STREAM_ID,\n                RESPONSE_CONTROL, RESPONSE_STREAM_ID, null, null);\n            ResponseClient responseClientA = new ResponseClient(\n                clientA, fragmentHandlerA, REQUEST_ENDPOINT, REQUEST_STREAM_ID, RESPONSE_CONTROL, RESPONSE_STREAM_ID);\n            ResponseClient responseClientB = new ResponseClient(\n                clientB, fragmentHandlerB, REQUEST_ENDPOINT, REQUEST_STREAM_ID, RESPONSE_CONTROL, RESPONSE_STREAM_ID))\n        {\n            final Supplier<String> msg = () -> \"responseServer.sessionCount=\" + responseServer.sessionCount() + \" \" +\n                \"clientA=\" + responseClientA + \" clientB=\" + responseClientB;\n\n            while (responseServer.sessionCount() < 2 ||\n                !responseClientA.isConnected() ||\n                !responseClientB.isConnected())\n            {\n                idleStrategy.idle(run(responseServer, responseClientA, responseClientB));\n                Tests.checkInterruptStatus(msg);\n            }\n\n            while (0 > responseClientA.offer(messageA))\n            {\n                idleStrategy.idle(run(responseServer, responseClientA, responseClientB));\n                Tests.checkInterruptStatus(\"unable to offer message to client A\");\n            }\n\n            while (0 > responseClientB.offer(messageB))\n            {\n                idleStrategy.idle(run(responseServer, responseClientA, responseClientB));\n                Tests.checkInterruptStatus(\"unable to offer message to client B\");\n            }\n\n            while (!responsesA.contains(textA) || !responsesB.contains(textB))\n            {\n                idleStrategy.idle(run(responseServer, responseClientA, responseClientB));\n                Tests.checkInterruptStatus(\"failed to receive responses\");\n            }\n\n            assertEquals(1, responsesA.size(), \"A=\" + responsesA + \", B=\" + responsesB);\n            assertEquals(1, responsesB.size(), \"A=\" + responsesA + \", B=\" + responsesB);\n        }\n    }\n\n    @Test\n    @InterruptAfter(15)\n    void shouldConnectResponsePublicationUsingImage()\n    {\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\n                \"aeron:udp?endpoint=localhost:10001\", REQUEST_STREAM_ID);\n            Subscription subRsp = client.addSubscription(\n                \"aeron:udp?control-mode=response|control=localhost:10002\", RESPONSE_STREAM_ID);\n            Publication pubReq = client.addPublication(\n                \"aeron:udp?endpoint=localhost:10001|response-correlation-id=\" + subRsp.registrationId(),\n                REQUEST_STREAM_ID))\n        {\n            Tests.awaitConnected(subReq);\n            Tests.awaitConnected(pubReq);\n            Objects.requireNonNull(subRsp);\n\n            final Image image = subReq.imageAtIndex(0);\n            final String url = \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=\" +\n                image.correlationId();\n\n            try (Publication pubRsp = server.addPublication(url, RESPONSE_STREAM_ID))\n            {\n                Tests.awaitConnected(subRsp);\n                Tests.awaitConnected(pubRsp);\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(10)\n    @ValueSource(booleans = { true, false })\n    void shouldConnectResponsePublicationUsingImageAndIpc(final boolean useExclusive)\n    {\n        CloseHelper.quietClose(driver2);\n\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\"aeron:ipc\", REQUEST_STREAM_ID))\n        {\n            try (Subscription subRsp1 = client.addSubscription(\n                \"aeron:ipc?control-mode=response|alias=client1\", RESPONSE_STREAM_ID);\n                Publication pubReq1 = newPublication(\n                    useExclusive,\n                    client,\n                    \"aeron:ipc?response-correlation-id=\" + subRsp1.registrationId(),\n                    REQUEST_STREAM_ID);\n                Subscription subRsp2 = client.addSubscription(\n                    \"aeron:ipc?control-mode=response|alias=client2\", RESPONSE_STREAM_ID);\n                Publication pubReq2 = newPublication(\n                    useExclusive,\n                    client,\n                    \"aeron:ipc?response-correlation-id=\" + subRsp2.registrationId(),\n                    REQUEST_STREAM_ID))\n            {\n                Tests.awaitConnected(pubReq1);\n                Tests.awaitConnected(pubReq2);\n                Tests.await(() -> 2 == subReq.imageCount());\n\n                final String url1 = \"aeron:ipc?control-mode=response|response-correlation-id=\" +\n                    subReq.imageAtIndex(0).correlationId();\n                final String url2 = \"aeron:ipc?control-mode=response|response-correlation-id=\" +\n                    subReq.imageAtIndex(1).correlationId();\n\n                try (Publication pubRsp1 = newPublication(useExclusive, server, url1, RESPONSE_STREAM_ID);\n                    Publication pubRsp2 = newPublication(useExclusive, server, url2, RESPONSE_STREAM_ID))\n                {\n                    Tests.awaitConnected(subRsp1);\n                    Tests.awaitConnected(subRsp2);\n                    Tests.awaitConnected(pubRsp1);\n                    Tests.awaitConnected(pubRsp2);\n\n                    final DirectBuffer msg1 = new UnsafeBuffer(\"msg1\".getBytes(UTF_8));\n                    final DirectBuffer msg2 = new UnsafeBuffer(\"msg2\".getBytes(UTF_8));\n\n                    while (pubRsp1.offer(msg1) < 0)\n                    {\n                        Tests.yield();\n                    }\n\n                    while (pubRsp2.offer(msg2) < 0)\n                    {\n                        Tests.yield();\n                    }\n\n                    final long deadlineMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1);\n                    int sub1FragmentCount = 0;\n                    int sub2FragmentCount = 0;\n                    while (System.currentTimeMillis() < deadlineMs)\n                    {\n                        sub1FragmentCount += subRsp1.poll((buffer, offset, length, header) -> {}, 10);\n                        sub2FragmentCount += subRsp2.poll((buffer, offset, length, header) -> {}, 10);\n                        Tests.yield();\n\n                        assertTrue(sub1FragmentCount < 2);\n                        assertTrue(sub2FragmentCount < 2);\n                    }\n                }\n            }\n        }\n    }\n\n    private static Publication newPublication(\n        final boolean useExclusive,\n        final Aeron client,\n        final String channel,\n        final int streamId)\n    {\n        return useExclusive ? client.addExclusivePublication(channel, streamId) :\n            client.addPublication(channel, streamId);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldCorrectlyHandleSubscriptionClosesOnPartiallyCreatedResponseSubscriptions()\n    {\n        final int responseStreamIdB = RESPONSE_STREAM_ID + 1;\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\"aeron:udp?endpoint=localhost:10001\", REQUEST_STREAM_ID);\n            Subscription subRspB = client.addSubscription(\n                \"aeron:udp?control-mode=response|control=localhost:10002|endpoint=localhost:10003\",\n                responseStreamIdB))\n        {\n            Objects.requireNonNull(subRspB);\n\n            try (\n                Subscription subRspA = client.addSubscription(\n                    \"aeron:udp?control-mode=response|control=localhost:10002|endpoint=localhost:10003\",\n                    RESPONSE_STREAM_ID);\n                Publication pubReqA = client.addPublication(\n                    \"aeron:udp?endpoint=localhost:10001|response-correlation-id=\" + subRspA.registrationId(),\n                    REQUEST_STREAM_ID))\n            {\n                Tests.awaitConnected(subReq);\n                Tests.awaitConnected(pubReqA);\n\n                final Image image = subReq.imageAtIndex(0);\n                final String url = \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=\" +\n                    image.correlationId();\n\n                try (Publication pubRsp = client.addPublication(url, RESPONSE_STREAM_ID))\n                {\n                    Tests.awaitConnected(subRspA);\n                    Tests.awaitConnected(pubRsp);\n                }\n\n                while (subRspA.isConnected())\n                {\n                    Tests.yield();\n                }\n            }\n\n            while (subReq.isConnected())\n            {\n                Tests.yield();\n            }\n\n            try (\n                Publication pubReqB = client.addPublication(\n                    \"aeron:udp?endpoint=localhost:10001|response-correlation-id=\" + subRspB.registrationId(),\n                    REQUEST_STREAM_ID))\n            {\n                Tests.awaitConnected(subReq);\n                Tests.awaitConnected(pubReqB);\n\n                final Image image = subReq.imageAtIndex(0);\n                final String url = \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=\" +\n                    image.correlationId();\n\n                try (Publication pubRsp = client.addPublication(url, responseStreamIdB))\n                {\n                    Tests.awaitConnected(subRspB);\n                    Tests.awaitConnected(pubRsp);\n                }\n\n                while (subRspB.isConnected())\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(15)\n    void shouldNotConnectSecondResponseSubscriptionUntilMatchingPublicationIsCreated()\n    {\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\n                \"aeron:udp?endpoint=localhost:10001\", REQUEST_STREAM_ID);\n            Subscription subRsp = client.addSubscription(\n                \"aeron:udp?control-mode=response|control=localhost:10002\", RESPONSE_STREAM_ID);\n            Subscription subRspAux = client.addSubscription(\n                \"aeron:udp?control-mode=response|control=localhost:10002\", RESPONSE_STREAM_ID);\n            Publication pubReq = client.addPublication(\n                \"aeron:udp?endpoint=localhost:10001|response-correlation-id=\" + subRsp.registrationId(),\n                REQUEST_STREAM_ID))\n        {\n            Tests.awaitConnected(subReq);\n            Tests.awaitConnected(pubReq);\n            Objects.requireNonNull(subRsp);\n\n            final Image image = subReq.imageAtIndex(0);\n            final String url = \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=\" +\n                image.correlationId();\n\n            try (Publication pubRsp = client.addPublication(url, RESPONSE_STREAM_ID))\n            {\n                Tests.awaitConnected(subRsp);\n                Tests.awaitConnected(pubRsp);\n\n                final long deadlineMs = System.currentTimeMillis() + 1_000;\n                while (System.currentTimeMillis() <= deadlineMs)\n                {\n                    assertFalse(subRspAux.isConnected());\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldUseResponseCorrelationIdAsAPublicationMatchingCriteria()\n    {\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription sub = aeron.addSubscription(\"aeron:udp?endpoint=localhost:10000\", 10001);\n            Publication pubA = aeron.addPublication(\"aeron:udp?endpoint=localhost:10000\", 10001);\n            Publication pubB = aeron.addPublication(\"aeron:udp?endpoint=localhost:10000\", 10001))\n        {\n            Tests.awaitConnected(sub);\n            Tests.awaitConnected(pubA);\n            Tests.awaitConnected(pubB);\n\n            assertEquals(pubA.originalRegistrationId(), pubB.originalRegistrationId());\n        }\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription sub = aeron.addSubscription(\"aeron:udp?endpoint=localhost:10000\", 10001);\n            Subscription rspSub = aeron.addSubscription(\"aeron:udp?control-mode=response\", 10001);\n            Publication pubA = aeron.addPublication(\n                \"aeron:udp?endpoint=localhost:10000|response-correlation-id=\" + rspSub.registrationId(), 10001);\n            Publication pubB = aeron.addPublication(\n                \"aeron:udp?endpoint=localhost:10000|response-correlation-id=\" + rspSub.registrationId(), 10001))\n        {\n            Tests.awaitConnected(sub);\n            Tests.awaitConnected(pubA);\n            Tests.awaitConnected(pubB);\n\n            assertEquals(pubA.originalRegistrationId(), pubB.originalRegistrationId());\n        }\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription sub = aeron.addSubscription(\"aeron:udp?endpoint=localhost:10000\", 10001);\n            Subscription rspSubA = aeron.addSubscription(\"aeron:udp?control-mode=response\", 10001);\n            Subscription rspSubB = aeron.addSubscription(\"aeron:udp?control-mode=response\", 10001);\n            Publication pubA = aeron.addPublication(\n                \"aeron:udp?endpoint=localhost:10000|response-correlation-id=\" + rspSubA.registrationId(), 10001);\n            Publication pubB = aeron.addPublication(\n                \"aeron:udp?endpoint=localhost:10000|response-correlation-id=\" + rspSubB.registrationId(), 10001))\n        {\n            Tests.awaitConnected(sub);\n            Tests.awaitConnected(pubA);\n            Tests.awaitConnected(pubB);\n\n            assertNotEquals(pubA.originalRegistrationId(), pubB.originalRegistrationId());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldErrorCreatingResponsePublicationWithImageThatDidNotRequestAResponseChannel()\n    {\n        watcher.ignoreErrorsMatching(s -> s.contains(\"did not request a response channel\"));\n\n        final int reqStreamId = 10001;\n        final int rspStreamId = 10002;\n\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\n                \"aeron:udp?endpoint=localhost:10001\", reqStreamId);\n            Publication pubReq = client.addPublication(\n                \"aeron:udp?endpoint=localhost:10001\", reqStreamId))\n        {\n            Tests.awaitConnected(subReq);\n            Tests.awaitConnected(pubReq);\n\n            final Image image = subReq.imageAtIndex(0);\n            final String url = \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=\" +\n                image.correlationId();\n\n            assertThrows(Exception.class, () -> client.addPublication(url, rspStreamId));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldErrorCreatingResponsePublicationWithMissingPublicationImage()\n    {\n        watcher.ignoreErrorsMatching(s -> s.contains(\"image.correlationId=\") && s.contains(\" not found\"));\n\n        final int reqStreamId = 10001;\n        final int rspStreamId = 10002;\n\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\n                \"aeron:udp?endpoint=localhost:10001\", reqStreamId);\n            Publication pubReq = client.addPublication(\n                \"aeron:udp?endpoint=localhost:10001\", reqStreamId))\n        {\n            Tests.awaitConnected(subReq);\n            Tests.awaitConnected(pubReq);\n\n            final Image image = subReq.imageAtIndex(0);\n            final String url = \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=\" +\n                image.correlationId();\n\n            assertThrows(Exception.class, () -> client.addPublication(url, rspStreamId));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldErrorCreatingResponsePublicationWithUnknownImage()\n    {\n        watcher.ignoreErrorsMatching(s -> s.contains(\"not found\"));\n\n        final int reqStreamId = 10001;\n        final int rspStreamId = 10002;\n\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\n                \"aeron:udp?endpoint=localhost:10001\", reqStreamId);\n            Publication pubReq = client.addPublication(\n                \"aeron:udp?endpoint=localhost:10001\", reqStreamId))\n        {\n            Tests.awaitConnected(subReq);\n            Tests.awaitConnected(pubReq);\n\n            final Image image = subReq.imageAtIndex(0);\n            final long wrongCorrelationId = image.correlationId() + 10;\n            final String url =\n                \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=\" + wrongCorrelationId;\n\n            assertThrows(Exception.class, () -> client.addPublication(url, rspStreamId));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldErrorIfResponseCorrelationIdIsMissingFromAControlModeResponsePublication()\n    {\n        watcher.ignoreErrorsMatching(\n            s -> s.contains(\"control-mode=response was specified, but no response-correlation-id set\"));\n\n        final int reqStreamId = 10001;\n        final int rspStreamId = 10002;\n\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\n                \"aeron:udp?endpoint=localhost:10001\", reqStreamId);\n            Publication pubReq = client.addPublication(\n                \"aeron:udp?endpoint=localhost:10001\", reqStreamId))\n        {\n            Tests.awaitConnected(subReq);\n            Tests.awaitConnected(pubReq);\n\n            final String url = \"aeron:udp?control-mode=response|control=localhost:10002\";\n\n            assertThrows(Exception.class, () -> client.addPublication(url, rspStreamId));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldBeAbleToProcessMultipleTermsWithMultipleResponseChannels() throws Exception\n    {\n        final UnsafeBuffer message = new UnsafeBuffer(new byte[4096]);\n        message.setMemory(0, 4096, (byte)'x');\n        final long stopPosition = 4 * 64 * 1024 + 1;\n\n        final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n        final MutableLong pubACount = new MutableLong(0);\n        final MutableLong pubBCount = new MutableLong(0);\n        final MutableLong subACount = new MutableLong(0);\n        final MutableLong subBCount = new MutableLong(0);\n\n        final FragmentHandler recvA = (buffer, offset, length, header) -> subACount.set(header.reservedValue());\n        final FragmentHandler recvB = (buffer, offset, length, header) -> subBCount.set(header.reservedValue());\n\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron clientA = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron clientB = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName()));\n            ResponseServer responseServer = new ResponseServer(\n                server, (image) -> new EchoHandler(), REQUEST_ENDPOINT, REQUEST_STREAM_ID,\n                RESPONSE_CONTROL, RESPONSE_STREAM_ID, null, \"aeron:udp?term-length=64k\");\n            ResponseClient responseClientA = new ResponseClient(\n                clientA, recvA, REQUEST_ENDPOINT, REQUEST_STREAM_ID, RESPONSE_CONTROL, RESPONSE_STREAM_ID,\n                \"aeron:udp?term-length=64k\", null);\n            ResponseClient responseClientB = new ResponseClient(\n                clientB, recvB, REQUEST_ENDPOINT, REQUEST_STREAM_ID, RESPONSE_CONTROL, RESPONSE_STREAM_ID,\n                \"aeron:udp?term-length=64k\", null))\n        {\n            final Supplier<String> msg = () -> \"responseServer.sessionCount=\" + responseServer.sessionCount() + \" \" +\n                \"clientA=\" + responseClientA + \" clientB=\" + responseClientB;\n\n            while (responseServer.sessionCount() < 2 ||\n                !responseClientA.isConnected() ||\n                !responseClientB.isConnected())\n            {\n                idleStrategy.idle(run(responseServer, responseClientA, responseClientB));\n                Tests.checkInterruptStatus(msg);\n            }\n\n            long subAStopPosition = 0;\n            long subBStopPosition = 0;\n\n            final Publication pubA = responseClientA.publication();\n            final Publication pubB = responseClientB.publication();\n            final MutableLong timeOfLastSubPositionChange = new MutableLong(System.currentTimeMillis());\n\n            final Supplier<String> errorMessage =\n                () ->\n                \"pubA.position=\" + pubA.position() +\n                \", subA.position=\" + responseClientA.subscription().imageAtIndex(0).position() +\n                \", pubA.count=\" + (pubACount.get() - 1) +\n                \", pubB.position=\" + pubB.position() +\n                \", subB.position=\" + responseClientB.subscription().imageAtIndex(0).position() +\n                \", pubB.count=\" + (pubBCount.get() - 1) +\n                \", idleTime=\" + (System.currentTimeMillis() - timeOfLastSubPositionChange.get()) + \"ms\";\n\n            long lastSubAPosition = 0;\n\n            while (pubA.position() < stopPosition ||\n                responseClientA.subscription().imageAtIndex(0).position() < subAStopPosition ||\n                pubB.position() < stopPosition ||\n                responseClientB.subscription().imageAtIndex(0).position() < subBStopPosition)\n            {\n                idleStrategy.idle(run(responseServer, responseClientA, responseClientB));\n                Tests.checkInterruptStatus(errorMessage);\n\n                if (pubA.position() < stopPosition)\n                {\n                    if (0 > pubA.offer(\n                        message, 0, pubA.maxPayloadLength(),\n                        (termBuffer, termOffset, frameLength) -> pubACount.getAndIncrement()))\n                    {\n                        Tests.yieldingIdle(errorMessage);\n                    }\n                }\n                subAStopPosition = pubA.position();\n\n                if (pubB.position() < stopPosition)\n                {\n                    if (0 > pubB.offer(\n                        message, 0, pubB.maxPayloadLength(),\n                        (termBuffer, termOffset, frameLength) -> pubBCount.getAndIncrement()))\n                    {\n                        Tests.yieldingIdle(errorMessage);\n                    }\n                }\n\n                subBStopPosition = pubB.position();\n\n                final long subAPosition = responseClientA.subscription().imageAtIndex(0).position();\n                if (lastSubAPosition != subAPosition)\n                {\n                    lastSubAPosition = subAPosition;\n                    timeOfLastSubPositionChange.set(System.currentTimeMillis());\n                }\n            }\n\n            assertEquals(pubACount.get() - 1, subACount.get());\n            assertEquals(pubBCount.get() - 1, subBCount.get());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldErrorIfNoResponseSubscriptionFound()\n    {\n        watcher.ignoreErrorsMatching(s -> s.contains(\"unable to find response subscription\"));\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription rspSub = aeron.addSubscription(\"aeron:udp?control-mode=response\", 10001))\n        {\n            final long wrongCorrelationId = rspSub.registrationId() + 10;\n            assertThrows(Exception.class, () -> aeron.addPublication(\n                \"aeron:udp?endpoint=localhost:10000|response-correlation-id=\" + wrongCorrelationId, 10001));\n        }\n    }\n\n    @Test\n    @InterruptAfter(15)\n    void shouldHandleMultipleConnectionsToTheResponseChannel()\n    {\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\n                \"aeron:udp?endpoint=localhost:10001\", REQUEST_STREAM_ID);\n            Subscription subRsp = client.addSubscription(\n                \"aeron:udp?control-mode=response|control=localhost:10002\", RESPONSE_STREAM_ID);\n            Publication pubReq = client.addPublication(\n                \"aeron:udp?endpoint=localhost:10001|response-correlation-id=\" + subRsp.registrationId(),\n                REQUEST_STREAM_ID))\n        {\n            Tests.awaitConnected(subReq);\n            Tests.awaitConnected(pubReq);\n            Objects.requireNonNull(subRsp);\n\n            final Image image = subReq.imageAtIndex(0);\n            final String url = \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=\" +\n                image.correlationId();\n\n            try (Publication pubRspA = client.addPublication(url, RESPONSE_STREAM_ID))\n            {\n                Tests.awaitConnected(subRsp);\n                Tests.awaitConnected(pubRspA);\n\n                try (Publication pubRspB = client.addPublication(url, RESPONSE_STREAM_ID))\n                {\n                    Tests.awaitConnected(pubRspB);\n\n                    assertEquals(pubRspA.originalRegistrationId(), pubRspB.originalRegistrationId());\n                }\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"aeron:udp?endpoint=localhost:8282, aeron:udp?endpoint=localhost:8282|control-mode=response\",\n        \"aeron:udp?control=localhost:8282, aeron:udp?control=localhost:8282|control-mode=response\",\n        \"aeron:udp?control=localhost:8282, aeron:udp?control-mode=response|control=localhost:8282\",\n        \"aeron:udp?endpoint=localhost:5555|control-mode=response, aeron:udp?endpoint=localhost:5555\",\n        \"aeron:udp?control=localhost:5555|control-mode=response, aeron:udp?control=localhost:5555\",\n        \"aeron:udp?control-mode=response|control=localhost:5555, aeron:udp?control=localhost:5555\",\n    })\n    void shouldRejectSubscriptionIfResponseConfigurationDoesNotMatch(final String channel1, final String channel2)\n    {\n        watcher.ignoreErrorsMatching(s -> s.contains(\"option conflicts with existing subscription\"));\n\n        final int streamId = 42;\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName())))\n        {\n            assertNotNull(aeron.addSubscription(channel1, streamId));\n\n            final RegistrationException exception =\n                assertThrowsExactly(RegistrationException.class, () -> aeron.addSubscription(channel2, streamId));\n            MatcherAssert.assertThat(\n                exception.getMessage(),\n                CoreMatchers.containsString(\n                \"option conflicts with existing subscription: isResponse=\" +\n                CONTROL_MODE_RESPONSE.equals(ChannelUri.parse(channel2).get(MDC_CONTROL_MODE_PARAM_NAME)) +\n                \" existingChannel=\" + channel1 + \" channel=\" + channel2));\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"what\", \"-2\", \"-3\" })\n    void shouldRejectPublicationsWithBadResponseCorrelationIds(final String rci)\n    {\n        watcher.ignoreErrorsMatching(s -> s.contains(\n            \"invalid response-correlation-id, must be a number greater than or equal to -1, or 'prototype'\"));\n\n        final int streamId = 42;\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName())))\n        {\n            final String channel =\n                \"aeron:udp?endpoint=localhost:8282|control-mode=response|response-correlation-id=\" + rci;\n            assertThrows(RegistrationException.class, () -> aeron.addExclusivePublication(channel, streamId));\n        }\n    }\n\n    @ParameterizedTest\n    @CsvSource({ \"true\", \"false\" })\n    @InterruptAfter(10)\n    void shouldCreateNewSendChannelWithoutPrototype(final boolean usePrototype) throws Exception\n    {\n        final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n        final List<String> responsesA = new ArrayList<>();\n        final FragmentHandler fragmentHandlerA =\n            (buffer, offset, length, header) -> responsesA.add(buffer.getStringWithoutLengthUtf8(offset, length));\n        final MutableReference<String> firstSendChannelLabel = new MutableReference<>();\n        final MutableReference<String> secondSendChannelLabel = new MutableReference<>();\n\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron clientA = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron clientB = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver2.aeronDirectoryName()));\n            ResponseServer responseServer = new ResponseServer(\n                server, (image) -> new EchoHandler(), REQUEST_ENDPOINT, REQUEST_STREAM_ID,\n                RESPONSE_CONTROL, RESPONSE_STREAM_ID, null, null))\n        {\n            responseServer.usePrototype(usePrototype);\n\n            {\n                final ResponseClient responseClientA = new ResponseClient(\n                    clientA, fragmentHandlerA, REQUEST_ENDPOINT,\n                    REQUEST_STREAM_ID, RESPONSE_CONTROL, RESPONSE_STREAM_ID);\n\n                final Supplier<String> msg = () -> \"responseServer.sessionCount=\" + responseServer.sessionCount() +\n                    \" \" + \"clientA=\" + responseClientA;\n\n                while (responseServer.sessionCount() < 1 ||\n                    !responseClientA.isConnected())\n                {\n                    idleStrategy.idle(run(responseServer, responseClientA));\n                    Tests.checkInterruptStatus(msg);\n                }\n\n                while (0 > responseClientA.offer(new UnsafeBuffer(\"hello from clientA\".getBytes(UTF_8))))\n                {\n                    idleStrategy.idle(run(responseServer, responseClientA));\n                    Tests.checkInterruptStatus(\"unable to offer message to client A\");\n                }\n\n                while (responsesA.isEmpty())\n                {\n                    idleStrategy.idle(run(responseServer, responseClientA));\n                    Tests.checkInterruptStatus(\"failed to receive responses\");\n                }\n\n                idleStrategy.idle(run(responseClientA));\n\n                driver1.counters().forEach((counterId, typeId, keyBuffer, label) ->\n                {\n                    if (SEND_CHANNEL_STATUS_TYPE_ID == typeId && label.contains(\"control-mode=response\"))\n                    {\n                        firstSendChannelLabel.set(label);\n                    }\n                });\n\n                responseClientA.close();\n            }\n\n            final MutableInteger publishers = new MutableInteger(0);\n            do\n            {\n                idleStrategy.idle(run(responseServer));\n                Tests.checkInterruptStatus(\"failed to detect publishers being closed\");\n\n                publishers.set(0);\n                driver1.counters().forEach((counterId, typeId, keyBuffer, label) ->\n                {\n                    if (typeId == DRIVER_PUBLISHER_POS_TYPE_ID && !label.contains(\"prototype\"))\n                    {\n                        publishers.increment();\n                    }\n                });\n            }\n            while (publishers.get() != 0);\n\n            try (ResponseClient responseClientB = new ResponseClient(clientB,\n                (b, o, l, h) -> {}, REQUEST_ENDPOINT, REQUEST_STREAM_ID, RESPONSE_CONTROL, RESPONSE_STREAM_ID))\n            {\n                final Supplier<String> msg = () -> \"responseServer.sessionCount=\" + responseServer.sessionCount() +\n                    \" \" + \"clientB=\" + responseClientB;\n\n                while (responseServer.sessionCount() < 1 ||\n                    !responseClientB.isConnected())\n                {\n                    idleStrategy.idle(run(responseServer, responseClientB));\n                    Tests.checkInterruptStatus(msg);\n                }\n\n                driver1.counters().forEach((counterId, typeId, keyBuffer, label) ->\n                {\n                    if (SEND_CHANNEL_STATUS_TYPE_ID == typeId && label.contains(\"control-mode=response\"))\n                    {\n                        secondSendChannelLabel.set(label);\n                    }\n                });\n            }\n\n            assertEquals(usePrototype, firstSendChannelLabel.get().equals(secondSendChannelLabel.get()));\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 256 * 1024, 1024 * 1024, 16 * 1024 * 1024, DEFAULT_TERM_LENGTH })\n    @InterruptAfter(15)\n    void shouldUseSpecifiedTermLengthWhenUsingPrototype(final int termLength)\n    {\n        try (Aeron server = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Aeron client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver1.aeronDirectoryName()));\n            Subscription subReq = server.addSubscription(\n                \"aeron:udp?endpoint=localhost:10001\", REQUEST_STREAM_ID);\n            ExclusivePublication protoRspPub = server.addExclusivePublication(\n                \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=prototype\",\n                RESPONSE_STREAM_ID);\n            Subscription subRsp = client.addSubscription(\n                \"aeron:udp?control-mode=response|control=localhost:10002\", RESPONSE_STREAM_ID);\n            Publication pubReq = client.addPublication(\n                \"aeron:udp?endpoint=localhost:10001|term-length=128k|response-correlation-id=\" +\n                    subRsp.registrationId(),\n                REQUEST_STREAM_ID))\n        {\n            protoRspPub.revokeOnClose();\n            assertEquals(LogBufferDescriptor.TERM_MIN_LENGTH, protoRspPub.termBufferLength());\n\n            Tests.awaitConnected(subReq);\n            Tests.awaitConnected(pubReq);\n            Objects.requireNonNull(subRsp);\n\n            final Image image = subReq.imageAtIndex(0);\n            assertEquals(pubReq.termBufferLength(), image.termBufferLength());\n            String url = \"aeron:udp?control-mode=response|control=localhost:10002|response-correlation-id=\" +\n                image.correlationId();\n            if (DEFAULT_TERM_LENGTH != termLength)\n            {\n                url += \"|term-length=\" + termLength;\n            }\n\n            try (Publication pubRsp = server.addPublication(url, RESPONSE_STREAM_ID))\n            {\n                Tests.awaitConnected(subRsp);\n                Tests.awaitConnected(pubRsp);\n                assertEquals(termLength, pubRsp.termBufferLength());\n                assertEquals(termLength, subRsp.imageAtIndex(0).termBufferLength());\n            }\n        }\n    }\n\n    private static final class EchoHandler implements ResponseServer.ResponseHandler\n    {\n        public boolean onMessage(\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header,\n            final Publication responsePublication)\n        {\n            return 0 < responsePublication.offer(\n                buffer, offset, length, (termBuffer, termOffset, frameLength) -> header.reservedValue());\n        }\n    }\n\n    private static int run(final Agent... agents) throws Exception\n    {\n        int work = 0;\n\n        for (final Agent agent : agents)\n        {\n            work += agent.doWork();\n        }\n\n        return work;\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/SessionSpecificPublicationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.*;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ErrorHandler;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.stream.Stream;\n\nimport static io.aeron.CommonContext.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass SessionSpecificPublicationTest\n{\n    private static final String ENDPOINT = \"localhost:24325\";\n    private static final int SESSION_ID_1 = 1077;\n    private static final int SESSION_ID_2 = 1078;\n    private static final int STREAM_ID = 1007;\n    private static final int MTU_1 = 4096;\n    private static final int MTU_2 = 8192;\n    private static final int TERM_LENGTH_1 = 64 * 1024;\n    private static final int TERM_LENGTH_2 = 128 * 1024;\n\n    static Stream<ChannelUriStringBuilder> data()\n    {\n        return Stream.of(\n            new ChannelUriStringBuilder().media(UDP_MEDIA).endpoint(ENDPOINT),\n            new ChannelUriStringBuilder().media(IPC_MEDIA));\n    }\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private final ErrorHandler mockErrorHandler = mock(ErrorHandler.class);\n    private final MediaDriver.Context mediaDriverContext = new MediaDriver.Context()\n        .errorHandler(mockErrorHandler)\n        .dirDeleteOnStart(true)\n        .spiesSimulateConnection(true)\n        .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n        .threadingMode(ThreadingMode.SHARED);\n\n    private TestMediaDriver mediaDriver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        mediaDriver = TestMediaDriver.launch(mediaDriverContext, testWatcher);\n        testWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n        testWatcher.ignoreErrorsMatching(s -> true);\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, mediaDriver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void shouldNotCreateExclusivePublicationWhenSessionIdCollidesWithExistingPublication(\n        final ChannelUriStringBuilder channelBuilder)\n    {\n        final String channel = channelBuilder.build();\n        aeron.addSubscription(channel, STREAM_ID);\n\n        final Publication publication = aeron.addExclusivePublication(channel, STREAM_ID);\n        Tests.awaitConnected(publication);\n\n        final int existingSessionId = publication.sessionId();\n        final String invalidChannel = channelBuilder.sessionId(existingSessionId).build();\n\n        assertThrows(RegistrationException.class, () ->\n        {\n            aeron.addExclusivePublication(invalidChannel, STREAM_ID);\n\n            fail(\"Exception should have been thrown due to duplicate session id\");\n        });\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void shouldNotCreatePublicationsSharingSessionIdWithDifferentMtu(\n        final ChannelUriStringBuilder channelBuilder)\n    {\n        channelBuilder.sessionId(SESSION_ID_1);\n\n        assertThrows(RegistrationException.class, () ->\n        {\n            aeron.addPublication(channelBuilder.mtu(MTU_1).build(), STREAM_ID);\n            aeron.addPublication(channelBuilder.mtu(MTU_2).build(), STREAM_ID);\n\n            fail(\"Exception should have been thrown due to non-matching mtu\");\n        });\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void shouldNotCreatePublicationsSharingSessionIdWithDifferentTermLength(\n        final ChannelUriStringBuilder channelBuilder)\n    {\n        channelBuilder.sessionId(SESSION_ID_1);\n\n        final String channelOne = channelBuilder.termLength(TERM_LENGTH_1).build();\n        final String channelTwo = channelBuilder.termLength(TERM_LENGTH_2).build();\n\n        assertThrows(RegistrationException.class, () ->\n        {\n            aeron.addPublication(channelOne, STREAM_ID);\n            aeron.addPublication(channelTwo, STREAM_ID);\n\n            fail(\"Exception should have been thrown due to non-matching term length\");\n        });\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void shouldNotCreateNonExclusivePublicationsWithDifferentSessionIdsForTheSameEndpoint(\n        final ChannelUriStringBuilder channelBuilder)\n    {\n        channelBuilder.endpoint(ENDPOINT);\n\n        final String channelOne = channelBuilder.sessionId(SESSION_ID_1).build();\n        final String channelTwo = channelBuilder.sessionId(SESSION_ID_2).build();\n\n        assertThrows(RegistrationException.class, () ->\n        {\n            aeron.addPublication(channelOne, STREAM_ID);\n            aeron.addPublication(channelTwo, STREAM_ID);\n\n            fail(\"Exception should have been thrown due using different session ids\");\n        });\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    @InterruptAfter(20)\n    @SlowTest\n    void shouldNotAddPublicationWithSameSessionUntilLingerCompletes(final ChannelUriStringBuilder builder)\n    {\n        final DirectBuffer msg = new UnsafeBuffer(new byte[8]);\n        final String channel = builder.sessionId(SESSION_ID_1).build();\n\n        final String subscriptionChannel = \"ipc\".equals(builder.media()) ? channel : SPY_PREFIX + channel;\n\n        final Publication publication1 = aeron.addPublication(channel, STREAM_ID);\n        final Subscription subscription = aeron.addSubscription(subscriptionChannel, STREAM_ID);\n        final int positionLimitId = publication1.positionLimitId();\n        assertEquals(CountersReader.RECORD_ALLOCATED, aeron.countersReader().getCounterState(positionLimitId));\n\n        while (publication1.offer(msg) < 0)\n        {\n            Tests.yieldingIdle(\"Failed to offer message\");\n        }\n\n        publication1.close();\n\n        assertThrows(RegistrationException.class, () ->\n        {\n            aeron.addPublication(channel, STREAM_ID);\n\n            fail(\"Exception should have been thrown due lingering publication keeping session id active\");\n        });\n\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> {};\n        while (subscription.poll(fragmentHandler, 10) <= 0)\n        {\n            Tests.yieldingIdle(\"Failed to drain message\");\n        }\n        subscription.close();\n\n        while (CountersReader.RECORD_ALLOCATED == aeron.countersReader().getCounterState(positionLimitId))\n        {\n            Tests.yieldingIdle(\"Publication never cleaned up\");\n        }\n\n        aeron.addPublication(channel, STREAM_ID);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void shouldAllowTheSameSessionIdOnDifferentStreamIds(final ChannelUriStringBuilder channelBuilder)\n    {\n        final String channel = channelBuilder.sessionId(SESSION_ID_1).build();\n\n        aeron.addPublication(channel, STREAM_ID);\n        aeron.addPublication(channel, STREAM_ID + 1);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/SessionSpecificSubscriptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Stream;\n\nimport static io.aeron.CommonContext.IPC_MEDIA;\nimport static io.aeron.CommonContext.UDP_MEDIA;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith(InterruptingTestCallback.class)\n@InterruptAfter(10)\nclass SessionSpecificSubscriptionTest\n{\n    private static final String ENDPOINT = \"localhost:24325\";\n    private static final int SESSION_ID_1 = 1077;\n    private static final int SESSION_ID_2 = 1078;\n    private static final int STREAM_ID = 1007;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n    private static final int MESSAGE_LENGTH = 1024 - DataHeaderFlyweight.HEADER_LENGTH;\n    private static final int EXPECTED_NUMBER_OF_MESSAGES = 10;\n\n    private final FragmentHandler mockFragmentHandler = mock(FragmentHandler.class);\n    private final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocate(MESSAGE_LENGTH));\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private static Stream<ChannelUriStringBuilder> data()\n    {\n        return Stream.of(\n            new ChannelUriStringBuilder().media(UDP_MEDIA).endpoint(ENDPOINT),\n            new ChannelUriStringBuilder().media(IPC_MEDIA));\n    }\n\n    private final FragmentHandler handlerSessionIdOne =\n        (buffer, offset, length, header) -> assertEquals(SESSION_ID_1, header.sessionId());\n    private final FragmentHandler handlerSessionIdTwo =\n        (buffer, offset, length, header) -> assertEquals(SESSION_ID_2, header.sessionId());\n\n    private final TestMediaDriver driver = TestMediaDriver.launch(new MediaDriver.Context()\n        .errorHandler(Tests::onError)\n        .dirDeleteOnStart(true)\n        .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n        .threadingMode(ThreadingMode.SHARED),\n        systemTestWatcher);\n\n    private final Aeron aeron = Aeron.connect();\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n        driver.context().deleteDirectory();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void shouldSubscribeToSpecificSessionIdsAndWildcard(final ChannelUriStringBuilder channelBuilder)\n    {\n        final String channelUriWithoutSessionId = channelBuilder.build();\n        final String channelUriWithSessionIdOne = channelBuilder.sessionId(SESSION_ID_1).build();\n        final String channelUriWithSessionIdTwo = channelBuilder.sessionId(SESSION_ID_2).build();\n\n        try (Subscription subscriptionOne = aeron.addSubscription(channelUriWithSessionIdOne, STREAM_ID);\n            Subscription subscriptionTwo = aeron.addSubscription(channelUriWithSessionIdTwo, STREAM_ID);\n            Subscription subscriptionWildcard = aeron.addSubscription(channelUriWithoutSessionId, STREAM_ID);\n            Publication publicationOne = aeron.addExclusivePublication(channelUriWithSessionIdOne, STREAM_ID);\n            Publication publicationTwo = aeron.addExclusivePublication(channelUriWithSessionIdTwo, STREAM_ID))\n        {\n            while (subscriptionOne.imageCount() != 1 ||\n                subscriptionTwo.imageCount() != 1 ||\n                subscriptionWildcard.imageCount() != 2)\n            {\n                Tests.yield();\n            }\n\n            for (int i = 0; i < EXPECTED_NUMBER_OF_MESSAGES; i++)\n            {\n                publishMessage(srcBuffer, publicationOne);\n                publishMessage(srcBuffer, publicationTwo);\n            }\n\n            int numFragments = 0;\n            do\n            {\n                Tests.checkInterruptStatus();\n                numFragments += subscriptionOne.poll(handlerSessionIdOne, FRAGMENT_COUNT_LIMIT);\n            }\n            while (numFragments < EXPECTED_NUMBER_OF_MESSAGES);\n\n            numFragments = 0;\n            do\n            {\n                Tests.checkInterruptStatus();\n                numFragments += subscriptionTwo.poll(handlerSessionIdTwo, FRAGMENT_COUNT_LIMIT);\n            }\n            while (numFragments < EXPECTED_NUMBER_OF_MESSAGES);\n\n            numFragments = 0;\n            do\n            {\n                Tests.checkInterruptStatus();\n                numFragments += subscriptionWildcard.poll(mockFragmentHandler, FRAGMENT_COUNT_LIMIT);\n            }\n            while (numFragments < (EXPECTED_NUMBER_OF_MESSAGES * 2));\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void shouldNotSubscribeWithoutSpecificSession(final ChannelUriStringBuilder channelBuilder)\n    {\n        final String channelUriWithoutSessionId = channelBuilder.build();\n        final String channelUriWithSessionIdOne = channelBuilder.sessionId(SESSION_ID_1).build();\n        final String channelUriWithSessionIdTwo = channelBuilder.sessionId(SESSION_ID_2).build();\n\n        try (Subscription subscription = aeron.addSubscription(channelUriWithSessionIdOne, STREAM_ID);\n            Publication publication = aeron.addExclusivePublication(channelUriWithSessionIdOne, STREAM_ID);\n            Publication publicationWildcard = aeron.addExclusivePublication(channelUriWithoutSessionId, STREAM_ID);\n            Publication publicationWrongSession = aeron.addExclusivePublication(channelUriWithSessionIdTwo, STREAM_ID))\n        {\n            Tests.awaitConnected(publication);\n\n            assertEquals(1, subscription.imageCount());\n\n            for (int i = 0; i < EXPECTED_NUMBER_OF_MESSAGES; i++)\n            {\n                publishMessage(srcBuffer, publication);\n            }\n\n            int numFragments = 0;\n            do\n            {\n                Tests.checkInterruptStatus();\n                numFragments += subscription.poll(handlerSessionIdOne, FRAGMENT_COUNT_LIMIT);\n            }\n            while (numFragments < EXPECTED_NUMBER_OF_MESSAGES);\n\n            assertFalse(publicationWildcard.isConnected());\n            assertFalse(publicationWrongSession.isConnected());\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void shouldOnlySeeDataOnSpecificSessionWhenUsingTags(final ChannelUriStringBuilder channelBuilder)\n    {\n        final DirectBuffer liveBuffer = new UnsafeBuffer(\"live\".getBytes(StandardCharsets.US_ASCII));\n        final DirectBuffer ignoredBuffer = new UnsafeBuffer(\"ignored\".getBytes(StandardCharsets.US_ASCII));\n\n        final String liveChannel = channelBuilder.sessionId(SESSION_ID_1).build();\n        final String generalChannel = channelBuilder.sessionId((String)null).build();\n\n        final long channelTag = aeron.nextCorrelationId();\n        final long subscriptionTag = aeron.nextCorrelationId();\n\n        final String endpointChannel = channelBuilder.tags(channelTag, subscriptionTag).build();\n        final String tagOnlyChannel = channelBuilder\n            .endpoint((String)null)\n            .tags(channelTag, subscriptionTag)\n            .sessionId(SESSION_ID_1)\n            .build();\n\n        try (\n            Publication livePub = aeron.addExclusivePublication(liveChannel, STREAM_ID);\n            Publication ignoredPub = aeron.addExclusivePublication(generalChannel, STREAM_ID);\n            Subscription endpointSub = aeron.addSubscription(endpointChannel, STREAM_ID);\n            Subscription tagOnlySub = aeron.addSubscription(tagOnlyChannel, STREAM_ID))\n        {\n            Tests.awaitConnected(livePub);\n            Tests.awaitConnected(endpointSub);\n            Tests.awaitConnected(tagOnlySub);\n            Tests.awaitConnected(ignoredPub);\n\n            while (livePub.offer(liveBuffer) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (ignoredPub.offer(ignoredBuffer) < 0)\n            {\n                Tests.yield();\n            }\n\n            final MutableBoolean isValid = new MutableBoolean(true);\n            final long deadlineMs = System.currentTimeMillis() + 2_000;\n            final IdleStrategy idleStrategy = new YieldingIdleStrategy();\n\n            final FragmentHandler fragmentHandler = (buffer, offset, length, header) ->\n            {\n                final String s = buffer.getStringWithoutLengthAscii(offset, length);\n                if (\"ignored\".equals(s))\n                {\n                    isValid.set(false);\n                }\n            };\n\n            int totalFragments = 0;\n            while (System.currentTimeMillis() < deadlineMs)\n            {\n                final int fragments = tagOnlySub.poll(fragmentHandler, 10);\n                totalFragments += fragments;\n                idleStrategy.idle(fragments);\n            }\n\n            assertTrue(isValid.get());\n            assertEquals(1, totalFragments);\n        }\n    }\n\n    @Test\n    void shouldNotSeeNotificationsForSessionsThatAreNotRelevant()\n    {\n        final String uri1 = \"aeron:udp?endpoint=localhost:20000|session-id=12345|rejoin=false\";\n        final String uri2 = \"aeron:udp?endpoint=localhost:20000|session-id=12346|rejoin=false\";\n\n        final AtomicReference<String> error1 = new AtomicReference<>(null);\n        final AtomicReference<String> error2 = new AtomicReference<>(null);\n        final AvailableImageHandler handler1 = (image) ->\n        {\n            if (image.sessionId() != 12345)\n            {\n                error1.compareAndSet(null, image.toString());\n            }\n        };\n        final AvailableImageHandler handler2 = (image) ->\n        {\n            if (image.sessionId() != 12346)\n            {\n                error2.compareAndSet(null, image.toString());\n            }\n        };\n\n        try (\n            Subscription sub1 = aeron.addSubscription(uri1, 10000, handler1, image -> {}))\n        {\n            try (\n                ExclusivePublication pub1 = aeron.addExclusivePublication(uri1, 10000))\n            {\n                Tests.awaitConnected(sub1);\n                Tests.awaitConnected(pub1);\n\n                try (Subscription sub2 = aeron.addSubscription(uri2, 10000, handler2, image -> {}))\n                {\n                    Objects.requireNonNull(sub2);\n                    Tests.sleep(500);\n                }\n            }\n        }\n\n        assertNull(error1.get());\n        assertNull(error2.get());\n    }\n\n    @Test\n    void shouldNotSeeNotificationsForSessionsThatAreNotRelevantViaIpc()\n    {\n        final String uri1 = \"aeron:ipc?session-id=12345|rejoin=false\";\n        final String uri2 = \"aeron:ipc?session-id=12346|rejoin=false\";\n\n        final AtomicReference<String> error1 = new AtomicReference<>(null);\n        final AtomicReference<String> error2 = new AtomicReference<>(null);\n        final AvailableImageHandler handler1 = (image) ->\n        {\n            if (image.sessionId() != 12345)\n            {\n                error1.compareAndSet(null, image.toString());\n            }\n        };\n        final AvailableImageHandler handler2 = (image) ->\n        {\n            if (image.sessionId() != 12346)\n            {\n                error2.compareAndSet(null, image.toString());\n            }\n        };\n\n        try (\n            Subscription sub1 = aeron.addSubscription(uri1, 10000, handler1, image -> {}))\n        {\n            try (\n                ExclusivePublication pub1 = aeron.addExclusivePublication(uri1, 10000))\n            {\n                Tests.awaitConnected(sub1);\n                Tests.awaitConnected(pub1);\n\n                try (Subscription sub2 = aeron.addSubscription(uri2, 10000, handler2, image -> {}))\n                {\n                    Objects.requireNonNull(sub2);\n                    Tests.sleep(500);\n                }\n            }\n        }\n\n        assertNull(error1.get());\n        assertNull(error2.get());\n    }\n\n    private static void publishMessage(final UnsafeBuffer buffer, final Publication publication)\n    {\n        while (publication.offer(buffer, 0, MESSAGE_LENGTH) < 0L)\n        {\n            Tests.yield();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/SpecifiedPositionPublicationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.RandomWatcher;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.util.function.Function;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nclass SpecifiedPositionPublicationTest\n{\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n    @RegisterExtension\n    final RandomWatcher randomWatcher = new RandomWatcher();\n\n    @InterruptAfter(5)\n    @ParameterizedTest\n    @CsvSource({\n        CommonContext.IPC_CHANNEL + \",true\",\n        \"aeron:udp?endpoint=localhost:24325,true\",\n        CommonContext.IPC_CHANNEL + \",false\",\n        \"aeron:udp?endpoint=localhost:24325,false\"\n    })\n    void shouldStartAtSpecifiedPositionForPublications(final String initialUri, final boolean exclusive)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .ipcPublicationTermWindowLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .threadingMode(ThreadingMode.SHARED);\n        final DirectBuffer msg = new UnsafeBuffer(new byte[64]);\n\n        final int termLength = 1 << 16;\n        final int initialTermId = randomWatcher.random().nextInt();\n        final int activeTermId = initialTermId + randomWatcher.random().nextInt(Integer.MAX_VALUE);\n        final int positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n        final int termOffset = randomWatcher.random().nextInt(termLength) & -FrameDescriptor.FRAME_ALIGNMENT;\n        final long startPosition = LogBufferDescriptor.computePosition(\n            activeTermId,\n            termOffset,\n            positionBitsToShift,\n            initialTermId);\n        final PositionCalculator positionCalculator = new PositionCalculator(startPosition, termLength, termOffset);\n        final long nextPosition = positionCalculator.addMessage(DataHeaderFlyweight.HEADER_LENGTH + msg.capacity());\n\n        final String channel = new ChannelUriStringBuilder(initialUri)\n            .initialPosition(startPosition, initialTermId, termLength)\n            .build();\n\n        final int streamId = 1001;\n        final Function<Aeron, Publication> publicationSupplier = exclusive ?\n            (a) -> a.addExclusivePublication(channel, streamId) : (a) -> a.addPublication(channel, streamId);\n\n        try (\n            TestMediaDriver mediaDriver = TestMediaDriver.launch(context, testWatcher);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(initialUri, streamId);\n            Publication publication = publicationSupplier.apply(aeron))\n        {\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(publication);\n\n            assertEquals(startPosition, publication.position());\n\n            Tests.await(() -> publication.offer(msg) > 0);\n            assertEquals(nextPosition, publication.position());\n\n            final FragmentHandler fragmentHandler =\n                (buffer, offset, length, header) -> assertEquals(nextPosition, header.position());\n\n            Tests.await(() -> subscription.poll(fragmentHandler, 1) == 1);\n        }\n        finally\n        {\n            context.deleteDirectory();\n        }\n    }\n\n    @InterruptAfter(5)\n    @ParameterizedTest\n    @CsvSource({\n        CommonContext.IPC_CHANNEL,\n        \"aeron:udp?endpoint=localhost:24325\"\n    })\n    void shouldValidateSpecifiedPositionForConcurrentPublications(final String initialUri)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .ipcPublicationTermWindowLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .threadingMode(ThreadingMode.SHARED);\n        final DirectBuffer msg = new UnsafeBuffer(new byte[64]);\n\n        final int termLength = 1 << 16;\n        final int initialTermId = randomWatcher.random().nextInt();\n        final int activeTermId = initialTermId + randomWatcher.random().nextInt(Integer.MAX_VALUE);\n        final int positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n        final int termOffset = randomWatcher.random().nextInt(termLength) & -FrameDescriptor.FRAME_ALIGNMENT;\n        final long startPosition = LogBufferDescriptor.computePosition(\n            activeTermId,\n            termOffset,\n            positionBitsToShift,\n            initialTermId);\n        final int totalMessageLength = DataHeaderFlyweight.HEADER_LENGTH + msg.capacity();\n        final PositionCalculator positionCalculator = new PositionCalculator(startPosition, termLength, termOffset);\n        final long positionMsg1 = positionCalculator.addMessage(totalMessageLength);\n        final long positionMsg2 = positionCalculator.addMessage(totalMessageLength);\n        final long positionMsg3 = positionCalculator.addMessage(totalMessageLength);\n\n        final String channel = new ChannelUriStringBuilder(initialUri)\n            .initialPosition(startPosition, initialTermId, termLength)\n            .build();\n\n        final String invalidPositionUri = new ChannelUriStringBuilder(initialUri)\n            .initialPosition(startPosition + FrameDescriptor.FRAME_ALIGNMENT, initialTermId, termLength)\n            .build();\n        final String invalidInitialTermIdUri = new ChannelUriStringBuilder(initialUri)\n            .initialPosition(startPosition, initialTermId + 1, termLength)\n            .build();\n        final String invalidTermLengthUri = new ChannelUriStringBuilder(initialUri)\n            .initialPosition(startPosition, initialTermId, termLength << 1)\n            .build();\n\n        final int streamId = 1001;\n\n        try (\n            TestMediaDriver mediaDriver = TestMediaDriver.launch(context, testWatcher);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(initialUri, streamId);\n            Publication publication = aeron.addPublication(channel, streamId))\n        {\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(publication);\n\n            assertEquals(startPosition, publication.position());\n\n            Tests.await(() -> publication.offer(msg) > 0);\n            assertEquals(positionMsg1, publication.position());\n\n            Tests.await(() -> subscription.poll((buffer, offset, length, header) -> {}, 1) == 1);\n\n            try (Publication publication2 = aeron.addPublication(channel, streamId))\n            {\n                assertEquals(positionMsg1, publication2.position());\n                Tests.await(() -> publication.offer(msg) > 0);\n                assertEquals(positionMsg2, publication2.position());\n                final FragmentHandler fragmentHandler =\n                    (buffer, offset, length, header) -> assertEquals(positionMsg2, header.position());\n                Tests.await(() -> subscription.poll(fragmentHandler, 1) == 1);\n            }\n\n            try (Publication publication3 = aeron.addPublication(initialUri, streamId))\n            {\n                assertEquals(positionMsg2, publication3.position());\n                Tests.await(() -> publication.offer(msg) > 0);\n                assertEquals(positionMsg3, publication3.position());\n                final FragmentHandler fragmentHandler =\n                    (buffer, offset, length, header) -> assertEquals(positionMsg3, header.position());\n                Tests.await(() -> subscription.poll(fragmentHandler, 1) == 1);\n            }\n\n            assertThrows(RegistrationException.class, () -> aeron.addPublication(invalidPositionUri, streamId));\n            assertThrows(RegistrationException.class, () -> aeron.addPublication(invalidInitialTermIdUri, streamId));\n            assertThrows(RegistrationException.class, () -> aeron.addPublication(invalidTermLengthUri, streamId));\n        }\n        finally\n        {\n            context.deleteDirectory();\n        }\n    }\n\n    @InterruptAfter(5)\n    @ParameterizedTest\n    @CsvSource({\n        CommonContext.IPC_CHANNEL,\n        \"aeron:udp?endpoint=localhost:24325\"\n    })\n    void shouldValidateSpecifiedPositionForConcurrentPublicationsInitiallyUnspecified(final String initialUri)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .ipcPublicationTermWindowLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .threadingMode(ThreadingMode.SHARED);\n\n        final int streamId = 1001;\n\n        try (\n            TestMediaDriver mediaDriver = TestMediaDriver.launch(context, testWatcher);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n            Subscription subscription = aeron.addSubscription(initialUri, streamId);\n            Publication publication = aeron.addPublication(initialUri, streamId))\n        {\n            Tests.awaitConnected(subscription);\n            Tests.awaitConnected(publication);\n\n            final String channel = new ChannelUriStringBuilder(initialUri)\n                .initialPosition(publication.position(), publication.initialTermId(), publication.termBufferLength())\n                .build();\n\n            try (Publication publication2 = aeron.addPublication(channel, streamId))\n            {\n                assertEquals(publication.position(), publication2.position());\n                assertEquals(publication.initialTermId(), publication2.initialTermId());\n            }\n        }\n        finally\n        {\n            context.deleteDirectory();\n        }\n    }\n\n    static final class PositionCalculator\n    {\n        final int termLength;\n        long position;\n        int termRemaining;\n\n        PositionCalculator(final long startingPosition, final int termLength, final int termOffset)\n        {\n            this.position = startingPosition;\n            this.termLength = termLength;\n            this.termRemaining = termLength - termOffset;\n        }\n\n        long addMessage(final int totalMessageLength)\n        {\n            if (termRemaining < totalMessageLength)\n            {\n                position += termRemaining;\n                termRemaining = termLength;\n            }\n\n            position += totalMessageLength;\n            termRemaining -= totalMessageLength;\n\n            return position;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/SpySimulatedConnectionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static java.util.Arrays.asList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass SpySimulatedConnectionTest\n{\n    private static List<String> channels()\n    {\n        return asList(\n            \"aeron:udp?endpoint=localhost:24325\",\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\");\n    }\n\n    private static final int STREAM_ID = 1001;\n\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int NUM_MESSAGES_PER_TERM = 64;\n    private static final int MESSAGE_LENGTH =\n        (TERM_BUFFER_LENGTH / NUM_MESSAGES_PER_TERM) - DataHeaderFlyweight.HEADER_LENGTH;\n\n    private final MediaDriver.Context driverContext = new MediaDriver.Context();\n\n    private Aeron client;\n    private TestMediaDriver driver;\n    private Publication publication;\n    private Subscription subscription;\n    private Subscription spy;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n\n    private final MutableInteger fragmentCountSpy = new MutableInteger();\n    private final FragmentHandler fragmentHandlerSpy = (buffer1, offset, length, header) -> fragmentCountSpy.value++;\n\n    private final MutableInteger fragmentCountSub = new MutableInteger();\n    private final FragmentHandler fragmentHandlerSub = (buffer1, offset, length, header) -> fragmentCountSub.value++;\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private void launch()\n    {\n        driverContext\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .errorHandler(Tests::onError)\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driver = TestMediaDriver.launch(driverContext, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n        client = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverContext.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(client, driver);\n        driverContext.deleteDirectory();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldNotSimulateConnectionWhenNotConfigured(final String channel)\n    {\n        launch();\n\n        spy = client.addSubscription(spyForChannel(channel), STREAM_ID);\n        publication = client.addPublication(channel, STREAM_ID);\n\n        Tests.awaitConnected(spy);\n\n        assertFalse(publication.isConnected());\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldSimulateConnectionWhenOnChannel(final String channel)\n    {\n        launch();\n\n        spy = client.addSubscription(spyForChannel(channel), STREAM_ID);\n        publication = client.addPublication(channel + \"|ssc=true\", STREAM_ID);\n\n        Tests.awaitConnected(spy);\n        Tests.awaitConnected(publication);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldSimulateConnectionWithNoNetworkSubscriptions(final String channel)\n    {\n        final int messagesToSend = NUM_MESSAGES_PER_TERM * 3;\n\n        driverContext\n            .publicationConnectionTimeoutNs(TimeUnit.MILLISECONDS.toNanos(250))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n            .spiesSimulateConnection(true);\n\n        launch();\n\n        spy = client.addSubscription(spyForChannel(channel), STREAM_ID);\n        publication = client.addPublication(channel, STREAM_ID);\n\n        while (!spy.isConnected() || !publication.isConnected())\n        {\n            Tests.yield();\n        }\n\n        for (int i = 0; i < messagesToSend; i++)\n        {\n            while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n\n            final MutableInteger fragmentsRead = new MutableInteger();\n            Tests.executeUntil(\n                () -> fragmentsRead.get() > 0,\n                (j) ->\n                {\n                    final int fragments = spy.poll(fragmentHandlerSpy, 10);\n                    if (0 == fragments)\n                    {\n                        Thread.yield();\n                    }\n                    fragmentsRead.value += fragments;\n                },\n                Integer.MAX_VALUE,\n                TimeUnit.MILLISECONDS.toNanos(500));\n        }\n\n        assertEquals(messagesToSend, fragmentCountSpy.value);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldSimulateConnectionWithSlowNetworkSubscription(final String channel)\n    {\n        final int messagesToSend = NUM_MESSAGES_PER_TERM * 3;\n        int messagesLeftToSend = messagesToSend;\n        int fragmentsFromSpy = 0;\n        int fragmentsFromSubscription = 0;\n\n        driverContext\n            .publicationConnectionTimeoutNs(TimeUnit.MILLISECONDS.toNanos(250))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n            .spiesSimulateConnection(true);\n\n        launch();\n\n        spy = client.addSubscription(spyForChannel(channel), STREAM_ID);\n        subscription = client.addSubscription(channel, STREAM_ID);\n        publication = client.addPublication(channel, STREAM_ID);\n\n        waitUntilFullConnectivity();\n\n        for (int i = 0; fragmentsFromSpy < messagesToSend || fragmentsFromSubscription < messagesToSend; i++)\n        {\n            if (messagesLeftToSend > 0)\n            {\n                if (publication.offer(buffer, 0, buffer.capacity()) >= 0L)\n                {\n                    messagesLeftToSend--;\n                }\n            }\n\n            Tests.yield();\n\n            fragmentsFromSpy += spy.poll(fragmentHandlerSpy, 10);\n\n            // subscription receives slowly\n            if ((i % 2) == 0)\n            {\n                fragmentsFromSubscription += subscription.poll(fragmentHandlerSub, 1);\n            }\n        }\n\n        assertEquals(messagesToSend, fragmentCountSpy.value);\n        assertEquals(messagesToSend, fragmentCountSub.value);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldSimulateConnectionWithLeavingNetworkSubscription(final String channel)\n    {\n        final int messagesToSend = NUM_MESSAGES_PER_TERM * 3;\n        int messagesLeftToSend = messagesToSend;\n        int fragmentsReadFromSpy = 0;\n        int fragmentsReadFromSubscription = 0;\n        boolean isSubscriptionClosed = false;\n\n        driverContext\n            .publicationConnectionTimeoutNs(TimeUnit.MILLISECONDS.toNanos(250))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n            .spiesSimulateConnection(true);\n\n        launch();\n\n        spy = client.addSubscription(spyForChannel(channel), STREAM_ID);\n        subscription = client.addSubscription(channel, STREAM_ID);\n        publication = client.addPublication(channel, STREAM_ID);\n\n        waitUntilFullConnectivity();\n\n        while (fragmentsReadFromSpy < messagesToSend)\n        {\n            if (messagesLeftToSend > 0)\n            {\n                if (publication.offer(buffer, 0, buffer.capacity()) >= 0L)\n                {\n                    messagesLeftToSend--;\n                }\n                else\n                {\n                    Tests.yield();\n                }\n            }\n\n            fragmentsReadFromSpy += spy.poll(fragmentHandlerSpy, 10);\n\n            // subscription receives up to 1/8 of the messages, then stops\n            if (fragmentsReadFromSubscription < (messagesToSend / 8))\n            {\n                fragmentsReadFromSubscription += subscription.poll(fragmentHandlerSub, 10);\n            }\n            else if (!isSubscriptionClosed)\n            {\n                subscription.close();\n                isSubscriptionClosed = true;\n            }\n        }\n\n        assertEquals(messagesToSend, fragmentCountSpy.value);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotHaveShortSendsWithMDCPublication()\n    {\n        launch();\n\n        final String channel = \"aeron:udp?control=localhost:40456|control-mode=dynamic\";\n\n        spy = client.addSubscription(spyForChannel(channel), STREAM_ID);\n        publication = client.addExclusivePublication(channel + \"|ssc=true\", STREAM_ID);\n\n        while (!spy.isConnected() || !publication.isConnected())\n        {\n            Tests.yield();\n        }\n\n        while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n        {\n            Tests.yield();\n        }\n\n        final FragmentHandler mockFragmentHandler = mock(FragmentHandler.class);\n\n        while (spy.poll(mockFragmentHandler, 1) == 0)\n        {\n            Tests.yield();\n        }\n\n        assertEquals(0, driver.counters().getCounterValue(SystemCounterDescriptor.SHORT_SENDS.id()));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldNotChangeConnectionStatusOnPublicationIfNormalSubscriberGoesAway(final String channel)\n        throws InterruptedException\n    {\n        launch();\n\n        spy = client.addSubscription(spyForChannel(channel), STREAM_ID);\n        subscription = client.addSubscription(channel, STREAM_ID);\n        publication = client.addPublication(channel + \"|ssc=true\", STREAM_ID);\n\n        Tests.awaitConnected(publication);\n        Tests.awaitConnected(spy);\n        Tests.awaitConnected(subscription);\n\n        for (int i = 0; i < 5; i++)\n        {\n            while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n            {\n                Tests.yield();\n            }\n        }\n\n        final AtomicBoolean running = new AtomicBoolean(true);\n        final AtomicBoolean detectedDisconnect = new AtomicBoolean();\n        final CountDownLatch latch = new CountDownLatch(1);\n        final Thread connectionChecker = new Thread(() ->\n        {\n            try (LogBuffers logBuffers = new LogBuffers(\n                driver.aeronDirectoryName() + \"/publications/\" + publication.registrationId() + \".logbuffer\"))\n            {\n                latch.countDown();\n                while (running.get())\n                {\n                    if (!LogBufferDescriptor.isConnected(logBuffers.metaDataBuffer()))\n                    {\n                        detectedDisconnect.set(true);\n                        break;\n                    }\n                }\n            }\n        });\n        connectionChecker.start();\n        latch.await();\n\n        final Image image = subscription.imageBySessionId(publication.sessionId());\n        final int subPosCounterId = image.subscriberPositionId();\n        subscription.close();\n        Tests.await(() -> client.countersReader().getCounterState(subPosCounterId) == CountersReader.RECORD_RECLAIMED);\n\n        running.set(false);\n        connectionChecker.join();\n\n        assertFalse(detectedDisconnect.get(), \"connection status changed unexpectedly\");\n        assertTrue(publication.isConnected(), \"connection status changed unexpectedly\");\n        assertFalse(publication.isClosed());\n        assertTrue(spy.isConnected());\n    }\n\n    private void waitUntilFullConnectivity()\n    {\n        while (!spy.isConnected() || !subscription.isConnected() || !publication.isConnected())\n        {\n            Tests.yield();\n        }\n\n        // send initial message to ensure connectivity\n        while (publication.offer(buffer, 0, buffer.capacity()) < 0L)\n        {\n            Tests.yield();\n        }\n\n        final FragmentHandler mockFragmentHandler = mock(FragmentHandler.class);\n\n        while (spy.poll(mockFragmentHandler, 1) == 0)\n        {\n            Tests.yield();\n        }\n\n        while (subscription.poll(mockFragmentHandler, 1) == 0)\n        {\n            Tests.yield();\n        }\n    }\n\n    private static String spyForChannel(final String channel)\n    {\n        return CommonContext.SPY_PREFIX + channel;\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/SpySubscriptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.List;\n\nimport static io.aeron.CommonContext.SPY_PREFIX;\nimport static java.util.Arrays.asList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass SpySubscriptionTest\n{\n    private static List<String> channels()\n    {\n        return asList(\n            \"aeron:udp?endpoint=localhost:24325\",\n            \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\");\n    }\n\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n    private static final int PAYLOAD_LENGTH = 10;\n\n    private final MutableInteger fragmentCountSpy = new MutableInteger();\n    private final FragmentHandler fragmentHandlerSpy = (buffer1, offset, length, header) -> fragmentCountSpy.value++;\n\n    private final MutableInteger fragmentCountSub = new MutableInteger();\n    private final FragmentHandler fragmentHandlerSub = (buffer1, offset, length, header) -> fragmentCountSub.value++;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n                .errorHandler(Tests::onError)\n                .dirDeleteOnStart(true)\n                .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n                .threadingMode(ThreadingMode.SHARED),\n            testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldReceivePublishedMessage(final String channel)\n    {\n        try (Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            Subscription spy = aeron.addSubscription(SPY_PREFIX + channel, STREAM_ID);\n            Publication publication = aeron.addPublication(channel, STREAM_ID))\n        {\n            final int expectedMessageCount = 4;\n            final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[PAYLOAD_LENGTH * expectedMessageCount]);\n\n            for (int i = 0; i < expectedMessageCount; i++)\n            {\n                srcBuffer.setMemory(i * PAYLOAD_LENGTH, PAYLOAD_LENGTH, (byte)(65 + i));\n            }\n\n            for (int i = 0; i < expectedMessageCount; i++)\n            {\n                while (publication.offer(srcBuffer, i * PAYLOAD_LENGTH, PAYLOAD_LENGTH) < 0L)\n                {\n                    Tests.yield();\n                }\n            }\n\n            int numFragments = 0;\n            int numSpyFragments = 0;\n            do\n            {\n                Tests.yield();\n\n                numFragments += subscription.poll(fragmentHandlerSub, FRAGMENT_COUNT_LIMIT);\n                numSpyFragments += spy.poll(fragmentHandlerSpy, FRAGMENT_COUNT_LIMIT);\n            }\n            while (numSpyFragments < expectedMessageCount || numFragments < expectedMessageCount);\n\n            assertEquals(expectedMessageCount, fragmentCountSpy.value);\n            assertEquals(expectedMessageCount, fragmentCountSub.value);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldConnectToRecreatedChannelByTag()\n    {\n        final long tag1 = aeron.nextCorrelationId();\n        final String channelOne = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .tags(tag1, null)\n            .endpoint(\"localhost:24325\")\n            .build();\n        final ChannelUriStringBuilder spyChannelOneBuilder = new ChannelUriStringBuilder()\n            .prefix(\"aeron-spy\")\n            .media(\"udp\")\n            .tags(tag1, null);\n\n        try (Publication publication = aeron.addExclusivePublication(channelOne, STREAM_ID);\n            Subscription spy = aeron.addSubscription(\n                spyChannelOneBuilder.sessionId(publication.sessionId()).build(), STREAM_ID))\n        {\n            Tests.await(spy::isConnected);\n            assertNotNull(spy.imageBySessionId(publication.sessionId()));\n        }\n\n        final long tag2 = aeron.nextCorrelationId();\n        final String channelTwo = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .tags(tag2, null)\n            .endpoint(\"localhost:24325\")\n            .build();\n        final ChannelUriStringBuilder spyChannelTwoBuilder = new ChannelUriStringBuilder()\n            .prefix(\"aeron-spy\")\n            .media(\"udp\")\n            .tags(tag2, null);\n\n        try (Publication publication = aeron.addExclusivePublication(channelTwo, STREAM_ID);\n            Subscription spy = aeron.addSubscription(\n                spyChannelTwoBuilder.sessionId(publication.sessionId()).build(), STREAM_ID))\n        {\n            Tests.await(spy::isConnected);\n            assertNotNull(spy.imageBySessionId(publication.sessionId()));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/StopStartSecondSubscriberTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.BooleanSupplier;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Test that a second subscriber can be stopped and started again while data is being published.\n */\n@ExtendWith(InterruptingTestCallback.class)\nclass StopStartSecondSubscriberTest\n{\n    private static final String CHANNEL1 = \"aeron:udp?endpoint=localhost:24325\";\n    private static final String CHANNEL2 = \"aeron:udp?endpoint=localhost:24326\";\n    private static final int STREAM_ID1 = 1001;\n    private static final int STREAM_ID2 = 1002;\n\n    private MediaDriver driverOne;\n    private MediaDriver driverTwo;\n    private Aeron publisherOne;\n    private Aeron subscriberOne;\n    private Aeron publisherTwo;\n    private Aeron subscriberTwo;\n    private Subscription subscriptionOne;\n    private Publication publicationOne;\n    private Subscription subscriptionTwo;\n    private Publication publicationTwo;\n\n    private final MutableDirectBuffer buffer = new ExpandableArrayBuffer();\n    private final MutableInteger subOneCount = new MutableInteger();\n    private final FragmentHandler fragmentHandlerOne = (buffer, offset, length, header) -> subOneCount.value++;\n    private final MutableInteger subTwoCount = new MutableInteger();\n    private final FragmentHandler fragmentHandlerTwo = (buffer, offset, length, header) -> subTwoCount.value++;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private void launch(final String channelOne, final int streamOne, final String channelTwo, final int streamTwo)\n    {\n        driverOne = MediaDriver.launchEmbedded(\n            new MediaDriver.Context()\n                .dirDeleteOnStart(true)\n                .dirDeleteOnShutdown(true)\n                .errorHandler(Tests::onError)\n                .termBufferSparseFile(true));\n        testWatcher.dataCollector().add(driverOne.context().aeronDirectory());\n\n        driverTwo = MediaDriver.launchEmbedded(\n            new MediaDriver.Context()\n                .dirDeleteOnStart(true)\n                .dirDeleteOnShutdown(true)\n                .errorHandler(Tests::onError)\n                .termBufferSparseFile(true));\n        testWatcher.dataCollector().add(driverTwo.context().aeronDirectory());\n\n        publisherOne = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverOne.aeronDirectoryName()));\n        subscriberOne = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverTwo.aeronDirectoryName()));\n        publisherTwo = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverOne.aeronDirectoryName()));\n        subscriberTwo = Aeron.connect(new Aeron.Context().aeronDirectoryName(driverTwo.aeronDirectoryName()));\n\n        subscriptionOne = subscriberOne.addSubscription(channelOne, streamOne);\n        subscriptionTwo = subscriberTwo.addSubscription(channelTwo, streamTwo);\n        publicationOne = publisherOne.addPublication(channelOne, streamOne);\n        publicationTwo = publisherTwo.addPublication(channelTwo, streamTwo);\n\n        while (!subscriptionOne.isConnected() || !subscriptionTwo.isConnected() || !publicationOne.isConnected() ||\n            !publicationTwo.isConnected())\n        {\n            Tests.yield();\n        }\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(\n            subscriberOne,\n            publisherOne,\n            subscriberTwo,\n            publisherTwo,\n            driverOne,\n            driverTwo);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSpinUpAndShutdown()\n    {\n        launch(CHANNEL1, STREAM_ID1, CHANNEL2, STREAM_ID2);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReceivePublishedMessage()\n    {\n        launch(CHANNEL1, STREAM_ID1, CHANNEL2, STREAM_ID2);\n\n        buffer.putInt(0, 1);\n\n        final int messagesPerPublication = 1;\n\n        while (publicationOne.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yield();\n        }\n\n        while (publicationTwo.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n        {\n            Tests.yield();\n        }\n\n        final MutableInteger fragmentsRead1 = new MutableInteger();\n        final MutableInteger fragmentsRead2 = new MutableInteger();\n        Tests.executeUntil(\n            () -> fragmentsRead1.get() >= messagesPerPublication && fragmentsRead2.get() >= messagesPerPublication,\n            (i) ->\n            {\n                fragmentsRead1.value += subscriptionOne.poll(fragmentHandlerOne, 10);\n                fragmentsRead2.value += subscriptionTwo.poll(fragmentHandlerTwo, 10);\n                Thread.yield();\n            },\n            Integer.MAX_VALUE,\n            TimeUnit.MILLISECONDS.toNanos(9900));\n\n        assertEquals(messagesPerPublication, subOneCount.get());\n        assertEquals(messagesPerPublication, subTwoCount.get());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReceiveMessagesAfterStopStartOnSameChannelSameStream()\n    {\n        shouldReceiveMessagesAfterStopStart(CHANNEL1, STREAM_ID1, CHANNEL1, STREAM_ID1);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReceiveMessagesAfterStopStartOnSameChannelDifferentStreams()\n    {\n        shouldReceiveMessagesAfterStopStart(CHANNEL1, STREAM_ID1, CHANNEL1, STREAM_ID2);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReceiveMessagesAfterStopStartOnDifferentChannelsSameStream()\n    {\n        shouldReceiveMessagesAfterStopStart(CHANNEL1, STREAM_ID1, CHANNEL2, STREAM_ID1);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReceiveMessagesAfterStopStartOnDifferentChannelsDifferentStreams()\n    {\n        shouldReceiveMessagesAfterStopStart(CHANNEL1, STREAM_ID1, CHANNEL2, STREAM_ID2);\n    }\n\n    private void doPublisherWork(final Publication publication, final AtomicBoolean running, final CountDownLatch latch)\n    {\n        while (running.get())\n        {\n            while (running.get() && publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L)\n            {\n                Tests.yield();\n            }\n        }\n\n        latch.countDown();\n    }\n\n    private void shouldReceiveMessagesAfterStopStart(\n        final String channelOne, final int streamOne, final String channelTwo, final int streamTwo)\n    {\n        final int numMessages = 1;\n        final MutableInteger subscriber2AfterRestartCount = new MutableInteger();\n        final AtomicBoolean running = new AtomicBoolean(true);\n        final CountDownLatch latch = new CountDownLatch(2);\n\n        final FragmentHandler fragmentHandler =\n            (buffer, offset, length, header) -> subscriber2AfterRestartCount.value++;\n\n        launch(channelOne, streamOne, channelTwo, streamTwo);\n\n        buffer.putInt(0, 1);\n\n        final ExecutorService executor = Executors.newFixedThreadPool(2);\n        try\n        {\n            executor.execute(() -> doPublisherWork(publicationOne, running, latch));\n            executor.execute(() -> doPublisherWork(publicationTwo, running, latch));\n\n            final MutableInteger fragmentsReadOne = new MutableInteger();\n            final MutableInteger fragmentsReadTwo = new MutableInteger();\n            final BooleanSupplier fragmentsReadCondition =\n                () -> fragmentsReadOne.get() >= numMessages && fragmentsReadTwo.get() >= numMessages;\n\n            Tests.executeUntil(\n                fragmentsReadCondition,\n                (i) ->\n                {\n                    fragmentsReadOne.value += subscriptionOne.poll(fragmentHandlerOne, 1);\n                    fragmentsReadTwo.value += subscriptionTwo.poll(fragmentHandlerTwo, 1);\n                    Thread.yield();\n                },\n                Integer.MAX_VALUE,\n                TimeUnit.MILLISECONDS.toNanos(4900));\n\n            assertTrue(subOneCount.get() >= numMessages);\n            assertTrue(subTwoCount.get() >= numMessages);\n\n            subscriptionTwo.close();\n\n            fragmentsReadOne.set(0);\n            fragmentsReadTwo.set(0);\n\n            subscriptionTwo = subscriberTwo.addSubscription(channelTwo, streamTwo);\n\n            Tests.executeUntil(\n                fragmentsReadCondition,\n                (i) ->\n                {\n                    fragmentsReadOne.value += subscriptionOne.poll(fragmentHandlerOne, 1);\n                    fragmentsReadTwo.value += subscriptionTwo.poll(fragmentHandler, 1);\n                    Thread.yield();\n                },\n                Integer.MAX_VALUE,\n                TimeUnit.MILLISECONDS.toNanos(4900));\n\n            running.set(false);\n            latch.await();\n\n            assertTrue(subOneCount.get() >= numMessages * 2,\n                \"Expecting subscriberOne to receive messages the entire time\");\n            assertTrue(subTwoCount.get() >= numMessages,\n                \"Expecting subscriberTwo to receive messages before being stopped and started\");\n            assertTrue(subscriber2AfterRestartCount.get() >= numMessages,\n                \"Expecting subscriberTwo to receive messages after being stopped and started\");\n        }\n        catch (final InterruptedException ex)\n        {\n            fail(\"Interrupted\", ex);\n        }\n        finally\n        {\n            running.set(false);\n            executor.shutdownNow();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/StreamSessionLimitsTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\n@ExtendWith(InterruptingTestCallback.class)\npublic class StreamSessionLimitsTest\n{\n    private final MediaDriver.Context context = new MediaDriver.Context();\n    private TestMediaDriver driver;\n\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private void launch()\n    {\n        context\n            .dirDeleteOnStart(true)\n            .publicationConnectionTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500))\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100));\n\n        driver = TestMediaDriver.launch(context, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n        watcher.ignoreErrorsMatching(s -> s.contains(\"session limit\"));\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietClose(driver);\n    }\n\n    @Test\n    @SlowTest\n    void shouldNotConnectPublicationWhenImageCountWouldExceedLimit()\n    {\n        context.streamSessionLimit(2);\n        launch();\n\n        final String channel = \"aeron:udp?endpoint=localhost:10000\";\n        final int streamId = 10000;\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(context.aeronDirectoryName()));\n            Subscription sub = aeron.addSubscription(channel, streamId);\n            Publication pub1 = aeron.addExclusivePublication(channel, streamId);\n            Publication pub2 = aeron.addExclusivePublication(channel, streamId))\n        {\n            Tests.awaitConnected(sub);\n            Tests.awaitConnected(pub1);\n            Tests.awaitConnected(pub2);\n\n            final Publication shouldNotConnect = aeron.addExclusivePublication(channel, streamId);\n\n            final long initialErrorCount = errorCount(aeron);\n            final long deadlineNs = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);\n            while (0 < deadlineNs - System.nanoTime())\n            {\n                assertFalse(shouldNotConnect.isConnected(), \"should not have connected\");\n            }\n            final long updatedErrorCount = errorCount(aeron);\n            assertThat(updatedErrorCount, greaterThan(initialErrorCount));\n        }\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(5)\n    void shouldAllowConnectPublicationAfterImageCountExceedsLimitButPreviousPublicationHasClosed()\n    {\n        context.streamSessionLimit(2);\n        launch();\n\n        final String channel = \"aeron:udp?endpoint=localhost:10000\";\n        final int streamId = 10000;\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(context.aeronDirectoryName()));\n            Subscription sub = aeron.addSubscription(channel, streamId);\n            Publication pub1 = aeron.addExclusivePublication(channel, streamId))\n        {\n            Tests.awaitConnected(sub);\n            Tests.awaitConnected(pub1);\n\n            try (Publication pub2 = aeron.addExclusivePublication(channel, streamId))\n            {\n                Tests.awaitConnected(pub2);\n                final long initialErrorCount = errorCount(aeron);\n\n                try (Publication shouldNotConnect = aeron.addExclusivePublication(channel, streamId))\n                {\n                    while (initialErrorCount == errorCount(aeron))\n                    {\n                        assertFalse(shouldNotConnect.isConnected());\n                        Tests.yield();\n                    }\n                }\n            }\n\n            while (2 <= sub.imageCount())\n            {\n                Tests.yield();\n            }\n\n            final Publication shouldNowConnect = aeron.addExclusivePublication(channel, streamId);\n\n            while (!shouldNowConnect.isConnected())\n            {\n                Tests.yieldingIdle(\"Never connected\");\n            }\n        }\n    }\n\n    @Test\n    @SlowTest\n    void shouldSessionLimitsShouldBeScopedToChannelAndStream()\n    {\n        context.streamSessionLimit(1);\n        launch();\n\n        final String channel1 = \"aeron:udp?endpoint=localhost:10000\";\n        final int streamId1 = 10000;\n        final String channel2 = \"aeron:udp?endpoint=localhost:10001\";\n        final int streamId2 = 10001;\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(context.aeronDirectoryName()));\n            Subscription sub1 = aeron.addSubscription(channel1, streamId1);\n            Subscription sub2 = aeron.addSubscription(channel1, streamId2);\n            Subscription sub3 = aeron.addSubscription(channel2, streamId1);\n            Subscription sub4 = aeron.addSubscription(channel2, streamId2);\n            Publication pub1 = aeron.addExclusivePublication(channel1, streamId1);\n            Publication pub2 = aeron.addExclusivePublication(channel2, streamId1);\n            Publication pub3 = aeron.addExclusivePublication(channel1, streamId2);\n            Publication pub4 = aeron.addExclusivePublication(channel2, streamId2))\n        {\n            Tests.awaitConnected(sub1);\n            Tests.awaitConnected(sub2);\n            Tests.awaitConnected(sub3);\n            Tests.awaitConnected(sub4);\n            Tests.awaitConnected(pub1);\n            Tests.awaitConnected(pub2);\n            Tests.awaitConnected(pub3);\n            Tests.awaitConnected(pub4);\n        }\n    }\n\n    private static long errorCount(final Aeron aeron)\n    {\n        return aeron.countersReader().getCounterValue(SystemCounterDescriptor.ERRORS.id());\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/SubscriberEndOfStreamTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Supplier;\n\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThan;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class SubscriberEndOfStreamTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private final DirectBuffer message = new UnsafeBuffer(\"this is a test message\".getBytes(US_ASCII));\n\n    private final MediaDriver.Context context = new MediaDriver.Context()\n        .dirDeleteOnStart(true)\n        .threadingMode(ThreadingMode.SHARED);\n    private TestMediaDriver driver;\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietClose(driver);\n    }\n\n    private TestMediaDriver launch()\n    {\n        driver = TestMediaDriver.launch(context, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n        return driver;\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?endpoint=localhost:10000|linger=0s\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=min,g:/1\",\n        \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=max\"\n    })\n    @InterruptAfter(20)\n    @SlowTest\n    void shouldDisconnectPublicationWithEosIfSubscriptionIsClosed(final String channel)\n    {\n        final TestMediaDriver driver = launch();\n        final int streamId = 10000;\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        final AtomicBoolean imageUnavailable = new AtomicBoolean(false);\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(channel, streamId))\n        {\n            final Subscription sub = aeron.addSubscription(\n                channel, streamId, image -> {}, image -> imageUnavailable.set(true));\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            assertEquals(pub.position(), image.position());\n\n            CloseHelper.close(sub);\n\n            final long t0 = System.nanoTime();\n            while (pub.isConnected())\n            {\n                Tests.yield();\n            }\n            final long t1 = System.nanoTime();\n            assertThat(t1 - t0, lessThan(driver.context().publicationConnectionTimeoutNs()));\n\n            while (!imageUnavailable.get())\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?control-mode=dynamic|control=localhost:10000|fc=min\",\n        \"aeron:udp?control-mode=dynamic|control=localhost:10000|fc=max\",\n    })\n    @InterruptAfter(20)\n    @SlowTest\n    void shouldDisconnectPublicationWithEosIfSubscriptionIsClosedMdc(final String publicationChannel)\n    {\n        final TestMediaDriver driver = launch();\n        final int streamId = 10000;\n        final String subscriptionChannel = \"aeron:udp?control=localhost:10000\";\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        final AtomicBoolean imageUnavailable = new AtomicBoolean(false);\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(publicationChannel, streamId))\n        {\n            final Subscription sub = aeron.addSubscription(\n                subscriptionChannel, streamId, image -> {}, image -> imageUnavailable.set(true));\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            assertEquals(pub.position(), image.position());\n\n            CloseHelper.close(sub);\n\n            final long t0 = System.nanoTime();\n            while (pub.isConnected())\n            {\n                Tests.yield();\n            }\n            final long t1 = System.nanoTime();\n            assertThat(t1 - t0, lessThan(driver.context().publicationConnectionTimeoutNs()));\n\n            while (!imageUnavailable.get())\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldDropReceiverOutOfFlowControlOnEndOfStream()\n    {\n        final TestMediaDriver driver = launch();\n        final int streamId = 10000;\n        final String subscriptionChannel1 = \"aeron:udp?control=localhost:10000|endpoint=localhost:10001\";\n        final String subscriptionChannel2 = \"aeron:udp?control=localhost:10000|endpoint=localhost:10002\";\n        final String publicationChannel =\n            \"aeron:udp?control-mode=dynamic|control=localhost:10000|fc=min|term-length=64k\";\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(publicationChannel, streamId);\n            Subscription sub1 = aeron.addSubscription(subscriptionChannel1, streamId))\n        {\n            final Subscription sub2 = aeron.addSubscription(subscriptionChannel2, streamId);\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub1);\n            Tests.awaitConnected(sub2);\n            final DirectBuffer message = new UnsafeBuffer(new byte[pub.maxPayloadLength()]);\n            final int fcReceiversCounterId = aeron.countersReader().findByTypeIdAndRegistrationId(\n                AeronCounters.FLOW_CONTROL_RECEIVERS_COUNTER_TYPE_ID, pub.registrationId());\n\n            while (aeron.countersReader().getCounterValue(fcReceiversCounterId) < 2)\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub1.imageAtIndex(0);\n            final Supplier<String> errorMsg =\n                () -> \"Image.position=\" + image.position() + \" Publication.position=\" + pub.position();\n\n            int messageCount = 0;\n            do\n            {\n                final long position = pub.offer(message);\n                if (position == Publication.BACK_PRESSURED && messageCount != 0)\n                {\n                    break;\n                }\n                else\n                {\n                    Tests.yield();\n                }\n\n                final int fragments = sub1.poll((buffer, offset, length, header) -> {}, 10);\n                if (0 == fragments)\n                {\n                    Tests.yieldingIdle(errorMsg);\n                }\n                else\n                {\n                    messageCount += fragments;\n                }\n            }\n            while (true);\n\n            CloseHelper.close(sub2);\n            final long t0 = System.nanoTime();\n            while (2 == aeron.countersReader().getCounterValue(fcReceiversCounterId))\n            {\n                Tests.yield();\n            }\n            final long t1 = System.nanoTime();\n            assertThat(t1 - t0, lessThan(driver.context().publicationConnectionTimeoutNs()));\n\n            final long t2 = System.nanoTime();\n            while (0 < pub.offer(message))\n            {\n                Tests.yield();\n            }\n            final long t3 = System.nanoTime();\n            assertThat(t3 - t2, lessThan(driver.context().publicationConnectionTimeoutNs()));\n        }\n    }\n\n    @Test\n    void shouldNotLingerUnicastPublicationWhenReceivingEndOfStream()\n    {\n        final String addressAlreadyInUseMessage = \"Address already in use\";\n        systemTestWatcher.ignoreErrorsMatching((s) -> s.contains(addressAlreadyInUseMessage));\n        final long lingerTimeoutMs = 5000;\n\n        final TestMediaDriver driver = launch();\n        final int streamId = 10000;\n        final String subscriptionChannel1 = \"aeron:udp?endpoint=localhost:10000\";\n        final String pubChannel1 =\n            \"aeron:udp?endpoint=localhost:10000|control=localhost:10001|linger=\" + lingerTimeoutMs + \"ms|eos=true\";\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        try (Aeron aeron = Aeron.connect(ctx))\n        {\n            final Publication pub1 = aeron.addPublication(pubChannel1, streamId);\n            final Subscription sub1 = aeron.addSubscription(subscriptionChannel1, streamId);\n\n            Tests.awaitConnected(sub1);\n            Tests.awaitConnected(pub1);\n\n            while (pub1.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            final int channelStatusId = pub1.channelStatusId();\n            CloseHelper.close(pub1);\n\n            final long t0 = System.currentTimeMillis();\n            while (null != LocalSocketAddressStatus.findAddress(\n                aeron.countersReader(), ChannelEndpointStatus.ACTIVE, channelStatusId))\n            {\n                Tests.yield();\n            }\n            final long t1 = System.currentTimeMillis();\n\n            assertThat((t1 - t0), lessThan(lingerTimeoutMs));\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?control-mode=manual|fc=min\",\n        \"aeron:udp?control-mode=manual|fc=max\",\n    })\n    @InterruptAfter(20)\n    @SlowTest\n    void shouldDisconnectPublicationWithEosIfSubscriptionIsClosedMdcManual(final String publicationChannel)\n    {\n        final TestMediaDriver driver = launch();\n        final int streamId = 10000;\n        final String subscriptionChannel = \"aeron:udp?endpoint=localhost:10000\";\n\n        final Aeron.Context ctx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName());\n\n        final AtomicBoolean imageUnavailable = new AtomicBoolean(false);\n\n        try (Aeron aeron = Aeron.connect(ctx);\n            Publication pub = aeron.addPublication(publicationChannel, streamId))\n        {\n            pub.addDestination(subscriptionChannel);\n            final Subscription sub = aeron.addSubscription(\n                subscriptionChannel, streamId, image -> {}, image -> imageUnavailable.set(true));\n\n            Tests.awaitConnected(pub);\n            Tests.awaitConnected(sub);\n\n            while (pub.offer(message) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == sub.poll((buffer, offset, length, header) -> {}, 1))\n            {\n                Tests.yield();\n            }\n\n            final Image image = sub.imageAtIndex(0);\n            assertEquals(pub.position(), image.position());\n\n            CloseHelper.close(sub);\n\n            final long t0 = System.nanoTime();\n            while (pub.isConnected())\n            {\n                Tests.yield();\n            }\n            final long t1 = System.nanoTime();\n            assertThat(t1 - t0, lessThan(driver.context().publicationConnectionTimeoutNs()));\n\n            while (!imageUnavailable.get())\n            {\n                Tests.yield();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/SubscriptionReconnectTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.status.LocalSocketAddressStatus;\nimport io.aeron.test.*;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableInteger;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\n@SlowTest\nclass SubscriptionReconnectTest\n{\n    private static final int STREAM_ID = 1001;\n\n    private static List<String> channels()\n    {\n        return Arrays.asList(\n            \"aeron:udp?endpoint=localhost:3333|linger=0\",\n            \"aeron:udp?endpoint=localhost:3333|linger=0|session-id=1\");\n    }\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    @BeforeEach\n    void setUp()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driver = TestMediaDriver.launch(driverCtx, testWatcher);\n        testWatcher.dataCollector().add(driverCtx.aeronDirectory());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.close(driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldAddExclusivePublication(final String channel)\n    {\n        try (Aeron aeron = Aeron.connect();\n            Subscription subscription = aeron.addSubscription(channel, STREAM_ID))\n        {\n            final BufferClaim bufferClaim = new BufferClaim();\n            final MutableInteger value = new MutableInteger();\n            final FragmentHandler handler = (buffer, offset, length, header) -> value.set(buffer.getInt(offset));\n\n            for (int i = 0; i < 3; i++)\n            {\n                final Publication publication = aeron.addExclusivePublication(channel, STREAM_ID);\n\n                while (!LocalSocketAddressStatus.isActive(aeron.countersReader(), publication.channelStatusId()))\n                {\n                    Tests.yield();\n                }\n\n                Tests.awaitConnected(subscription);\n\n                while (true)\n                {\n                    if (publication.tryClaim(SIZE_OF_INT, bufferClaim) > 0)\n                    {\n                        bufferClaim.buffer().putInt(bufferClaim.offset(), i);\n                        bufferClaim.commit();\n                        break;\n                    }\n\n                    Tests.yield();\n                }\n\n                value.set(Aeron.NULL_VALUE);\n\n                while (subscription.poll(handler, 1) <= 0)\n                {\n                    Tests.yield();\n                }\n\n                assertEquals(i, value.get());\n\n                publication.close();\n\n                while (LocalSocketAddressStatus.isActive(aeron.countersReader(), publication.channelStatusId()))\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/SystemTests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.reports.LossReportReader;\nimport io.aeron.driver.reports.LossReportUtil;\nimport io.aeron.test.Tests;\nimport org.agrona.BufferUtil;\nimport org.agrona.concurrent.*;\nimport org.agrona.concurrent.errors.ErrorConsumer;\nimport org.agrona.concurrent.errors.ErrorLogReader;\nimport org.hamcrest.Matcher;\nimport org.hamcrest.StringDescription;\n\nimport java.io.*;\nimport java.nio.MappedByteBuffer;\nimport java.nio.channels.FileChannel;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static java.nio.channels.FileChannel.MapMode.READ_ONLY;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nclass SystemTests\n{\n    static void verifyLossOccurredForStream(final String aeronDirectoryName, final int streamId)\n        throws IOException\n    {\n        final File lossReportFile = LossReportUtil.file(aeronDirectoryName);\n        assertTrue(lossReportFile.exists());\n\n        MappedByteBuffer mappedByteBuffer = null;\n\n        try (RandomAccessFile file = new RandomAccessFile(lossReportFile, \"r\");\n            FileChannel channel = file.getChannel())\n        {\n            mappedByteBuffer = channel.map(READ_ONLY, 0, channel.size());\n            final AtomicBuffer buffer = new UnsafeBuffer(mappedByteBuffer);\n\n            final LossReportReader.EntryConsumer lossEntryConsumer = mock(LossReportReader.EntryConsumer.class);\n            LossReportReader.read(buffer, lossEntryConsumer);\n\n            verify(lossEntryConsumer).accept(\n                longThat((l) -> l > 0),\n                longThat((l) -> l > 0),\n                anyLong(),\n                anyLong(),\n                anyInt(),\n                eq(streamId),\n                any(),\n                any());\n        }\n        finally\n        {\n            BufferUtil.free(mappedByteBuffer);\n        }\n    }\n\n    static void waitForErrorToOccur(\n        final String aeronDirectoryName,\n        final Matcher<String> matcher,\n        final IdleStrategy retryIdle) throws IOException\n    {\n        final File cncFile = CommonContext.newCncFile(aeronDirectoryName);\n        assertTrue(cncFile.exists());\n\n        MappedByteBuffer cncByteBuffer = null;\n\n        try (\n            RandomAccessFile file = new RandomAccessFile(cncFile, \"r\");\n            FileChannel channel = file.getChannel())\n        {\n            cncByteBuffer = channel.map(READ_ONLY, 0, channel.size());\n            final AtomicBuffer errorLogBuffer = CommonContext.errorLogBuffer(cncByteBuffer);\n\n            final MatcherErrorConsumer errorConsumer = new MatcherErrorConsumer(matcher);\n\n            ErrorLogReader.read(errorLogBuffer, errorConsumer);\n            if (errorConsumer.hasMatched())\n            {\n                return;\n            }\n\n            if (null == retryIdle)\n            {\n                fail(errorConsumer.toString());\n            }\n\n            while (!errorConsumer.hasMatched())\n            {\n                errorConsumer.reset();\n                ErrorLogReader.read(errorLogBuffer, errorConsumer);\n\n                Tests.idle(retryIdle, errorConsumer::toString);\n            }\n        }\n        finally\n        {\n            BufferUtil.free(cncByteBuffer);\n        }\n    }\n\n    static final class MatcherErrorConsumer implements ErrorConsumer\n    {\n        private final Matcher<String> matcher;\n        private final List<String> encodedExceptions = new ArrayList<>();\n        private boolean hasMatched = false;\n\n        MatcherErrorConsumer(final Matcher<String> matcher)\n        {\n            this.matcher = matcher;\n        }\n\n        public void accept(\n            final int observationCount,\n            final long firstObservationTimestamp,\n            final long lastObservationTimestamp,\n            final String encodedException)\n        {\n            hasMatched = matcher.matches(encodedException);\n            encodedExceptions.add(encodedException);\n        }\n\n        void reset()\n        {\n            encodedExceptions.clear();\n        }\n\n        boolean hasMatched()\n        {\n            return hasMatched;\n        }\n\n        public String toString()\n        {\n            final StringDescription description = new StringDescription();\n            final String lineSeparator = System.lineSeparator();\n\n            description.appendText(\"Unable to match: \");\n            matcher.describeTo(description);\n            description.appendText(\", against the following errors:\");\n            encodedExceptions.forEach(\n                (encodedException) ->\n                {\n                    description.appendText(lineSeparator).appendText(\"  \");\n                    description.appendText(encodedException);\n                });\n            description.appendText(lineSeparator);\n\n            return description.toString();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/TaggedFlowControlSystemTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.FlowControlSupplier;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.TaggedMulticastFlowControlSupplier;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.SenderLimit;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\nimport java.util.stream.Stream;\n\nimport static io.aeron.FlowControlTests.awaitConnectionAndStatusMessages;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass TaggedFlowControlSystemTest\n{\n    private static final String MULTICAST_URI = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\";\n    private static final int STREAM_ID = 1001;\n\n    private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int NUM_MESSAGES_PER_TERM = 64;\n    private static final int MESSAGE_LENGTH =\n        (TERM_BUFFER_LENGTH / NUM_MESSAGES_PER_TERM) - DataHeaderFlyweight.HEADER_LENGTH;\n    private static final String ROOT_DIR = SystemUtil.tmpDirName() + \"aeron-system-tests\" + File.separator;\n\n    private final MediaDriver.Context driverAContext = new MediaDriver.Context();\n    private final MediaDriver.Context driverBContext = new MediaDriver.Context();\n\n    private Aeron clientA;\n    private Aeron clientB;\n    private TestMediaDriver driverA;\n    private TestMediaDriver driverB;\n    private Publication publication;\n    private Subscription subscriptionA;\n    private Subscription subscriptionB;\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n    private final FragmentHandler fragmentHandlerA = mock(FragmentHandler.class);\n    private final FragmentHandler fragmentHandlerB = mock(FragmentHandler.class);\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private void launch()\n    {\n        buffer.putInt(0, 1);\n\n        final String baseDirA = ROOT_DIR + \"A\";\n        final String baseDirB = ROOT_DIR + \"B\";\n\n        driverAContext.publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirA)\n            .flowControlReceiverTimeoutNs(TimeUnit.MILLISECONDS.toNanos(1000))\n            .errorHandler(Tests::onError)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverBContext.publicationTermBufferLength(TERM_BUFFER_LENGTH)\n            .aeronDirectoryName(baseDirB)\n            .flowControlReceiverTimeoutNs(TimeUnit.MILLISECONDS.toNanos(1000))\n            .errorHandler(Tests::onError)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driverA = TestMediaDriver.launch(driverAContext, testWatcher);\n        testWatcher.dataCollector().add(driverA.context().aeronDirectory());\n\n        driverB = TestMediaDriver.launch(driverBContext, testWatcher);\n        testWatcher.dataCollector().add(driverB.context().aeronDirectory());\n\n        clientA = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(driverAContext.aeronDirectoryName()));\n\n        clientB = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(driverBContext.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(subscriptionB, subscriptionA, publication, clientB, clientA, driverB, driverA);\n    }\n\n    private static Stream<Arguments> strategyConfigurations()\n    {\n        return Stream.of(\n            Arguments.of(new TaggedMulticastFlowControlSupplier(), null, null, \"\", \"|gtag=-1\"),\n            Arguments.of(new TaggedMulticastFlowControlSupplier(), null, 2004L, \"\", \"|gtag=2004\"),\n            Arguments.of(null, 2020L, 2020L, \"|fc=tagged\", \"\"),\n            Arguments.of(null, null, null, \"|fc=tagged,g:123\", \"|gtag=123\"));\n    }\n\n    private static final class State\n    {\n        private int numMessagesToSend;\n        private int numMessagesLeftToSend;\n        private int numFragmentsReadFromA;\n        private int numFragmentsReadFromB;\n        private boolean isBClosed = false;\n\n        public String toString()\n        {\n            return \"State{\" +\n                \"numMessagesToSend=\" + numMessagesToSend +\n                \", numMessagesLeftToSend=\" + numMessagesLeftToSend +\n                \", numFragmentsReadFromA=\" + numFragmentsReadFromA +\n                \", numFragmentsReadFromB=\" + numFragmentsReadFromB +\n                \", isBClosed=\" + isBClosed +\n                '}';\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"strategyConfigurations\")\n    @InterruptAfter(10)\n    void shouldSlowToTaggedWithMulticastFlowControlStrategy(\n        final FlowControlSupplier flowControlSupplier,\n        final Long groupTag,\n        final Long flowControlGroupTag,\n        final String publisherUriParams,\n        final String subscriptionBUriParams)\n    {\n        final State state = new State();\n        state.numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n        state.numMessagesLeftToSend = state.numMessagesToSend;\n        state.numFragmentsReadFromB = 0;\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n        if (null != flowControlSupplier)\n        {\n            driverAContext.multicastFlowControlSupplier(flowControlSupplier);\n        }\n        if (null != flowControlGroupTag)\n        {\n            driverAContext.flowControlGroupTag(flowControlGroupTag);\n        }\n        if (null != groupTag)\n        {\n            driverBContext.receiverGroupTag(groupTag);\n        }\n        driverAContext.flowControlGroupMinSize(1);\n\n        launch();\n\n        subscriptionA = clientA.addSubscription(MULTICAST_URI, STREAM_ID);\n        subscriptionB = clientB.addSubscription(MULTICAST_URI + subscriptionBUriParams, STREAM_ID);\n        publication = clientA.addPublication(MULTICAST_URI + publisherUriParams, STREAM_ID);\n\n        Tests.awaitConnected(subscriptionA);\n        Tests.awaitConnected(subscriptionB);\n        Tests.awaitConnected(publication);\n\n        for (long i = 0; state.numFragmentsReadFromB < state.numMessagesToSend; i++)\n        {\n            if (state.numMessagesLeftToSend > 0)\n            {\n                final long result = publication.offer(buffer, 0, buffer.capacity());\n                if (result >= 0L)\n                {\n                    state.numMessagesLeftToSend--;\n                }\n                else if (Publication.NOT_CONNECTED == result)\n                {\n                    fail(\"Publication not connected, numMessagesLeftToSend=\" + state.numMessagesLeftToSend);\n                }\n                else\n                {\n                    Tests.yieldingIdle(state::toString);\n                }\n            }\n\n            Tests.yieldingIdle(state::toString);\n\n            // A keeps up\n            pollWithTimeout(subscriptionA, fragmentHandlerA, 10, state::toString);\n\n            // B receives slowly\n            if ((i % 2) == 0)\n            {\n                final int bFragments = pollWithTimeout(subscriptionB, fragmentHandlerB, 1, state::toString);\n                if (0 == bFragments && !subscriptionB.isConnected())\n                {\n                    if (subscriptionB.isClosed())\n                    {\n                        fail(\"Subscription B is closed, numFragmentsFromB=\" + state.numFragmentsReadFromB);\n                    }\n\n                    fail(\"Subscription B not connected, numFragmentsFromB=\" + state.numFragmentsReadFromB);\n                }\n\n                state.numFragmentsReadFromB += bFragments;\n            }\n        }\n\n        verify(fragmentHandlerB, times(state.numMessagesToSend)).onFragment(\n            any(DirectBuffer.class), anyInt(), eq(MESSAGE_LENGTH), any(Header.class));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRemoveDeadReceiver()\n    {\n        final State state = new State();\n        state.numMessagesToSend = NUM_MESSAGES_PER_TERM * 3;\n        state.numMessagesLeftToSend = state.numMessagesToSend;\n        state.numFragmentsReadFromA = 0;\n        state.numFragmentsReadFromB = 0;\n        state.isBClosed = false;\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n\n        launch();\n\n        publication = clientA.addPublication(MULTICAST_URI + \"|fc=tagged,g:123,t:1s\", STREAM_ID);\n\n        subscriptionA = clientA.addSubscription(MULTICAST_URI + \"|gtag=123\", STREAM_ID);\n        Tests.awaitConnected(subscriptionA);\n        subscriptionB = clientB.addSubscription(MULTICAST_URI + \"|gtag=123\", STREAM_ID);\n        Tests.awaitConnected(subscriptionB);\n\n        Tests.awaitConnected(publication);\n\n        while (state.numFragmentsReadFromA < state.numMessagesToSend)\n        {\n            if (state.numMessagesLeftToSend > 0)\n            {\n                final long position = publication.offer(buffer, 0, buffer.capacity());\n                if (position >= 0L)\n                {\n                    state.numMessagesLeftToSend--;\n                }\n                else\n                {\n                    Tests.yieldingIdle(\"position: %d, state: %s\", position, state);\n                }\n            }\n\n            // A keeps up\n            state.numFragmentsReadFromA += pollWithTimeout(subscriptionA, fragmentHandlerA, 10, state::toString);\n\n            // B receives up to 1/8 of the messages, then stops\n            if (state.numFragmentsReadFromB < (state.numMessagesToSend / 8))\n            {\n                state.numFragmentsReadFromB += pollWithTimeout(\n                    subscriptionB, fragmentHandlerB, 10, state::toString);\n            }\n            else if (!state.isBClosed)\n            {\n                subscriptionB.close();\n                state.isBClosed = true;\n            }\n        }\n\n        verify(fragmentHandlerA, times(state.numMessagesToSend)).onFragment(\n            any(DirectBuffer.class), anyInt(), eq(MESSAGE_LENGTH), any(Header.class));\n    }\n\n    @SuppressWarnings(\"methodlength\")\n    @SlowTest\n    @Test\n    @InterruptAfter(20)\n    void shouldPreventConnectionUntilRequiredGroupSizeMatchTagIsMet()\n    {\n        final Long groupTag = 2701L;\n        final Integer groupSize = 3;\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"224.20.30.39:24326\")\n            .networkInterface(\"localhost\");\n\n        final String uriWithGroupTag = builder\n            .groupTag(groupTag)\n            .flowControl((String)null)\n            .build();\n\n        final String uriPlain = builder\n            .groupTag((Long)null)\n            .flowControl((String)null)\n            .build();\n\n        final String uriWithTaggedFlowControl = builder\n            .groupTag((Long)null)\n            .taggedFlowControl(groupTag, groupSize, null)\n            .build();\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n\n        launch();\n\n        TestMediaDriver driverC = null;\n        Aeron clientC = null;\n\n        TestMediaDriver driverD = null;\n        Aeron clientD = null;\n\n        Publication publication = null;\n        Subscription subscription0 = null;\n        Subscription subscription1 = null;\n        Subscription subscription2 = null;\n        Subscription subscription3 = null;\n        Subscription subscription4 = null;\n        Subscription subscription5 = null;\n\n        try\n        {\n            driverC = TestMediaDriver.launch(\n                new MediaDriver.Context().publicationTermBufferLength(TERM_BUFFER_LENGTH)\n                    .aeronDirectoryName(ROOT_DIR + \"C\")\n                    .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n                    .errorHandler(Tests::onError)\n                    .threadingMode(ThreadingMode.SHARED),\n                testWatcher);\n\n            clientC = Aeron.connect(\n                new Aeron.Context()\n                    .aeronDirectoryName(driverC.aeronDirectoryName()));\n\n            driverD = TestMediaDriver.launch(\n                new MediaDriver.Context().publicationTermBufferLength(TERM_BUFFER_LENGTH)\n                    .aeronDirectoryName(ROOT_DIR + \"D\")\n                    .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n                    .errorHandler(Tests::onError)\n                    .threadingMode(ThreadingMode.SHARED),\n                testWatcher);\n\n            clientD = Aeron.connect(\n                new Aeron.Context()\n                    .aeronDirectoryName(driverD.aeronDirectoryName()));\n\n            publication = clientA.addPublication(uriWithTaggedFlowControl, STREAM_ID);\n\n            subscription0 = clientA.addSubscription(uriPlain, STREAM_ID);\n            subscription1 = clientA.addSubscription(uriPlain, STREAM_ID);\n            subscription2 = clientA.addSubscription(uriPlain, STREAM_ID);\n            subscription3 = clientB.addSubscription(uriWithGroupTag, STREAM_ID);\n            subscription4 = clientC.addSubscription(uriWithGroupTag, STREAM_ID);\n\n            awaitConnectionAndStatusMessages(\n                clientA.countersReader(),\n                subscription0, subscription1, subscription2, subscription3, subscription4);\n\n            assertFalse(publication.isConnected());\n\n            subscription5 = clientD.addSubscription(uriWithGroupTag, STREAM_ID);\n\n            // Should now have 3 receivers and publication should eventually be connected.\n            while (!publication.isConnected())\n            {\n                Tests.sleep(1);\n            }\n\n            subscription5.close();\n            subscription5 = null;\n\n            // Lost a receiver and publication should eventually be disconnected.\n            while (publication.isConnected())\n            {\n                Tests.sleep(1);\n            }\n\n            subscription5 = clientD.addSubscription(uriWithGroupTag, STREAM_ID);\n\n            // Aaaaaand reconnect.\n            while (!publication.isConnected())\n            {\n                Tests.sleep(1);\n            }\n        }\n        finally\n        {\n            CloseHelper.closeAll(\n                publication,\n                subscription0, subscription1, subscription2, subscription3, subscription4, subscription5,\n                clientC, clientD,\n                driverC, driverD);\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldPreventConnectionUntilAtLeastOneSubscriberConnectedWithRequiredGroupSizeZero()\n    {\n        final Long groupTag = 2701L;\n        final Integer groupSize = 0;\n\n        final ChannelUriStringBuilder builder = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"224.20.30.41:24326\")\n            .networkInterface(\"localhost\");\n\n        final String plainUri = builder.build();\n\n        final String uriWithGroupTag = builder\n            .groupTag(groupTag)\n            .flowControl((String)null)\n            .build();\n\n        final String uriWithTaggedFlowControl = builder\n            .groupTag((Long)null)\n            .taggedFlowControl(groupTag, groupSize, null)\n            .build();\n\n        launch();\n\n        publication = clientA.addPublication(uriWithTaggedFlowControl, STREAM_ID);\n        final Publication otherPublication = clientA.addPublication(plainUri, STREAM_ID + 1);\n\n        clientA.addSubscription(plainUri, STREAM_ID + 1);\n\n        while (!otherPublication.isConnected())\n        {\n            Tests.sleep(1);\n        }\n        // We know another publication on the same channel is connected\n\n        assertFalse(publication.isConnected());\n\n        subscriptionA = clientA.addSubscription(uriWithGroupTag, STREAM_ID);\n\n        Tests.awaitConnected(publication);\n        Tests.awaitConnected(subscriptionA);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldHandleSenderLimitCorrectlyWithMinGroupSize()\n    {\n        final String publisherUri = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|fc=tagged,g:123/1\";\n        final String groupSubscriberUri = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost|gtag=123\";\n        final String subscriberUri = \"aeron:udp?endpoint=224.20.30.39:24326|interface=localhost\";\n\n        driverBContext.imageLivenessTimeoutNs(TimeUnit.MILLISECONDS.toNanos(500));\n\n        launch();\n\n        publication = clientA.addPublication(publisherUri, STREAM_ID);\n\n        final CountersReader countersReader = clientA.countersReader();\n\n        final int senderLimitCounterId = FlowControlTests.findCounterIdByRegistrationId(\n            countersReader, SenderLimit.SENDER_LIMIT_TYPE_ID, publication.registrationId);\n        final long currentSenderLimit = countersReader.getCounterValue(senderLimitCounterId);\n\n        subscriptionA = clientA.addSubscription(subscriberUri, STREAM_ID);\n\n        awaitConnectionAndStatusMessages(countersReader, subscriptionA);\n        assertEquals(currentSenderLimit, countersReader.getCounterValue(senderLimitCounterId));\n\n        subscriptionB = clientB.addSubscription(groupSubscriberUri, STREAM_ID);\n\n        while (currentSenderLimit == countersReader.getCounterValue(senderLimitCounterId))\n        {\n            Tests.sleep(1);\n        }\n    }\n\n    private int pollWithTimeout(\n        final Subscription subscription,\n        final FragmentHandler fragmentHandler,\n        final int fragmentLimit,\n        final Supplier<String> message)\n    {\n        final int numFragments = subscription.poll(fragmentHandler, fragmentLimit);\n        if (0 == numFragments)\n        {\n            Tests.yieldingIdle(message);\n        }\n\n        return numFragments;\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/TermBufferLengthTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass TermBufferLengthTest\n{\n    private static final int STREAM_ID = 1001;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @ParameterizedTest\n    @CsvSource({\n        \"aeron:udp?endpoint=localhost:24325|term-length=64K, 65536\",\n        \"aeron:ipc?term-length=64K, 65536\",\n        \"aeron:udp?endpoint=localhost:24325|term-length=1G, 1073741824\",\n        \"aeron:ipc?term-length=1G, 1073741824\",\n        \"aeron:udp?endpoint=localhost:24325|term-length=4M, 4194304\",\n        \"aeron:ipc?term-length=512K, 524288\",\n    })\n    void shouldHaveCorrectTermBufferLength(final String channel, final int expectedTermBufferLength)\n    {\n        final MediaDriver.Context ctx = new MediaDriver.Context()\n            .errorHandler(Tests::onError)\n            .dirDeleteOnStart(true)\n            .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH * 2)\n            .ipcTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH * 2);\n\n        try (\n            TestMediaDriver mediaDriver = TestMediaDriver.launch(ctx, testWatcher))\n        {\n            testWatcher.dataCollector().add(mediaDriver.context().aeronDirectory());\n\n            try (\n                Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(mediaDriver.aeronDirectoryName()));\n                Publication publication = aeron.addPublication(channel, STREAM_ID))\n            {\n                assertEquals(expectedTermBufferLength, publication.termBufferLength());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/TimestampingSystemTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.function.Predicate;\n\nimport static java.util.Objects.requireNonNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass TimestampingSystemTest\n{\n    public static final Predicate<String> IGNORE_MEDIA_TIMESTAMPS_PREDICATE =\n        s -> s.contains(\"Media timestamps 'media-rcv-ts-offset' are not supported in the Java driver\");\n\n    private static final long SENTINEL_VALUE = -1L;\n    private static final String CHANNEL_WITH_MEDIA_TIMESTAMP =\n        \"aeron:udp?endpoint=localhost:0|media-rcv-ts-offset=reserved\";\n    private static final int RECEIVE_TIMESTAMP_OFFSET = 0;\n    private static final int SEND_TIMESTAMP_OFFSET = 8;\n\n    private static final String CHANNEL_WITH_CHANNEL_TIMESTAMPS = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:0\")\n        .channelReceiveTimestampOffset(RECEIVE_TIMESTAMP_OFFSET + \"\")\n        .channelSendTimestampOffset(SEND_TIMESTAMP_OFFSET + \"\")\n        .build();\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver()\n    {\n        // TODO: temporary removal of SHARED to test\n        final MediaDriver.Context context = new MediaDriver.Context().dirDeleteOnStart(true);\n        final TestMediaDriver driver = TestMediaDriver.launch(context, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n        return driver;\n    }\n\n    @BeforeEach\n    void setUp()\n    {\n        systemTestWatcher.ignoreErrorsMatching(s -> s.contains(\"no known ATS session\"));\n    }\n\n    @Test\n    void shouldErrorOnMediaReceiveTimestampsInJavaDriver()\n    {\n        assumeTrue(TestMediaDriver.shouldRunJavaMediaDriver());\n        systemTestWatcher.ignoreErrorsMatching((s) -> s.contains(\"ERROR - Media timestamps\"));\n\n        try (TestMediaDriver driver = driver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            assertThrows(\n                RegistrationException.class,\n                () -> aeron.addSubscription(CHANNEL_WITH_MEDIA_TIMESTAMP, 1000));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @EnabledOnOs(OS.LINUX)\n    void shouldSupportMediaReceiveTimestampsInCDriver()\n    {\n        assumeTrue(TestMediaDriver.shouldRunCMediaDriver());\n\n        final DirectBuffer buffer = new UnsafeBuffer(new byte[64]);\n\n        try (TestMediaDriver driver = driver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final Subscription sub = aeron.addSubscription(CHANNEL_WITH_MEDIA_TIMESTAMP, 1000);\n\n            while (null == sub.resolvedEndpoint())\n            {\n                Tests.yieldingIdle(\"Failed to resolve endpoint\");\n            }\n\n            final String uri = \"aeron:udp?endpoint=\" + sub.resolvedEndpoint();\n            final Publication pub = aeron.addPublication(uri, 1000);\n\n            Tests.awaitConnected(pub);\n\n            while (0 > pub.offer(buffer, 0, buffer.capacity(), (termBuffer, termOffset, frameLength) -> SENTINEL_VALUE))\n            {\n                Tests.yieldingIdle(\"Failed to offer message\");\n            }\n\n            final FragmentHandler fragmentHandler =\n                (buffer1, offset, length, header) -> assertNotEquals(SENTINEL_VALUE, header .reservedValue());\n            while (1 > sub.poll(fragmentHandler, 1))\n            {\n                Tests.yieldingIdle(\"Failed to receive message\");\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSupportSendReceiveTimestamps()\n    {\n        final MutableDirectBuffer buffer = new UnsafeBuffer(new byte[64]);\n\n        try (TestMediaDriver driver = driver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final Subscription sub = aeron.addSubscription(CHANNEL_WITH_CHANNEL_TIMESTAMPS, 1000);\n\n            while (null == sub.resolvedEndpoint())\n            {\n                Tests.yieldingIdle(\"Failed to resolve endpoint\");\n            }\n\n            final String uri = new ChannelUriStringBuilder(CHANNEL_WITH_CHANNEL_TIMESTAMPS)\n                .endpoint(requireNonNull(sub.resolvedEndpoint()))\n                .build();\n\n            final Publication pub = aeron.addPublication(uri, 1000);\n\n            Tests.awaitConnected(pub);\n\n            buffer.putLong(RECEIVE_TIMESTAMP_OFFSET, SENTINEL_VALUE);\n            buffer.putLong(SEND_TIMESTAMP_OFFSET, SENTINEL_VALUE);\n\n            while (0 > pub.offer(buffer, 0, buffer.capacity()))\n            {\n                Tests.yieldingIdle(\"Failed to offer message\");\n            }\n\n            final MutableLong receiveTimestamp = new MutableLong(SENTINEL_VALUE);\n            final MutableLong sendTimestamp = new MutableLong(SENTINEL_VALUE);\n            final FragmentHandler fragmentHandler = (buffer1, offset, length, header) ->\n            {\n                receiveTimestamp.set(buffer1.getLong(offset + RECEIVE_TIMESTAMP_OFFSET));\n                sendTimestamp.set(buffer1.getLong(offset + SEND_TIMESTAMP_OFFSET));\n            };\n\n            while (1 > sub.poll(fragmentHandler, 1))\n            {\n                Tests.yieldingIdle(\"Failed to receive message\");\n            }\n\n            assertNotEquals(SENTINEL_VALUE, receiveTimestamp.longValue());\n            assertNotEquals(SENTINEL_VALUE, sendTimestamp.longValue());\n        }\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(10)\n    @ValueSource(booleans = { true, false })\n    @EnabledOnOs(OS.LINUX)\n    void shouldNotCorruptFragmentedMessagesWhenTimestampsAreEnabled(final boolean testMediaTimestamps)\n    {\n        final String channel = testMediaTimestamps ? CHANNEL_WITH_MEDIA_TIMESTAMP : CHANNEL_WITH_CHANNEL_TIMESTAMPS;\n\n        systemTestWatcher.ignoreErrorsMatching(IGNORE_MEDIA_TIMESTAMPS_PREDICATE);\n\n        try (TestMediaDriver driver = driver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final MutableDirectBuffer buffer = new UnsafeBuffer(new byte[16 + driver.context().mtuLength() * 2]);\n            setAll(buffer, (byte)0xFF);\n\n            final Subscription sub;\n            try\n            {\n                sub = aeron.addSubscription(channel, 1000);\n            }\n            catch (final Exception e)\n            {\n                if (thisIsTheJavaDriverThenIgnoreMediaTimestampParameter(e))\n                {\n                    return;\n                }\n\n                throw e;\n            }\n\n            while (null == sub.resolvedEndpoint())\n            {\n                Tests.yieldingIdle(\"Failed to resolve endpoint\");\n            }\n\n            final String uri = new ChannelUriStringBuilder(channel)\n                .endpoint(requireNonNull(sub.resolvedEndpoint()))\n                .build();\n\n            final Publication pub = aeron.addPublication(uri, 1000);\n\n            Tests.awaitConnected(pub);\n\n            final MutableInteger received = new MutableInteger(0);\n            final FragmentHandler fragmentHandler = new FragmentAssembler(\n                (buffer1, offset, length, header) ->\n                {\n                    received.increment();\n\n                    for (int i = 16; i < length; i++)\n                    {\n                        assertEquals((byte)0xFF, buffer1.getByte(offset + i));\n                    }\n                });\n\n            final int toSend = 100;\n            int sent = 0;\n\n            while (received.get() < toSend)\n            {\n                if (sent < toSend && 0 < pub.offer(buffer, 0, buffer.capacity()))\n                {\n                    sent++;\n                }\n\n                if (sub.poll(fragmentHandler, 10) < 1)\n                {\n                    Tests.yieldingIdle(\"Failed to receive message - sent=\" + sent + \", received=\" + received);\n                }\n            }\n        }\n    }\n\n    @Test\n    void shouldErrorIfSubscriptionConfigurationForTimestampsDoesNotMatch()\n    {\n        systemTestWatcher.ignoreErrorsMatching((s) -> s.contains(\"option conflicts\"));\n\n        try (TestMediaDriver driver = driver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            aeron.addSubscription(\"aeron:udp?endpoint=localhost:23436|channel-rcv-ts-offset=reserved\", 1000);\n\n            assertThrows(\n                RegistrationException.class, () -> aeron.addSubscription(\"aeron:udp?endpoint=localhost:23436\", 1000));\n            assertThrows(\n                RegistrationException.class,\n                () -> aeron.addSubscription(\"aeron:udp?endpoint=localhost:23436|channel-rcv-ts-offset=8\", 1000));\n        }\n    }\n\n    @Test\n    void shouldErrorIfPublicationConfigurationForTimestampsDoesNotMatch()\n    {\n        systemTestWatcher.ignoreErrorsMatching((s) -> s.contains(\"option conflicts\"));\n\n        try (TestMediaDriver driver = driver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            aeron.addPublication(\"aeron:udp?endpoint=localhost:23436|channel-snd-ts-offset=reserved\", 1000);\n\n            assertThrows(\n                RegistrationException.class, () -> aeron.addPublication(\"aeron:udp?endpoint=localhost:23436\", 1000));\n            assertThrows(\n                RegistrationException.class,\n                () -> aeron.addPublication(\"aeron:udp?endpoint=localhost:23436|channel-snd-ts-offset=8\", 1000));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSupportChannelSendTimestampsOnMdc()\n    {\n        final MutableDirectBuffer buffer = new UnsafeBuffer(new byte[64]);\n\n        try (TestMediaDriver driver = driver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final Publication mdcPub = aeron.addPublication(\n                \"aeron:udp?control-mode=manual|channel-snd-ts-offset=0\", 1000);\n\n            final Subscription sub1 = aeron.addSubscription(\"aeron:udp?endpoint=localhost:23424\", 1000);\n            final Subscription sub2 = aeron.addSubscription(\"aeron:udp?endpoint=localhost:23425\", 1000);\n            mdcPub.addDestination(\"aeron:udp?endpoint=localhost:23424\");\n            mdcPub.addDestination(\"aeron:udp?endpoint=localhost:23425\");\n\n            while (!sub1.isConnected() || !sub2.isConnected())\n            {\n                Tests.yieldingIdle(\"Failed to connect\");\n            }\n\n            buffer.putLong(0, SENTINEL_VALUE);\n\n            while (0 > mdcPub.offer(buffer, 0, buffer.capacity()))\n            {\n                Tests.yieldingIdle(\"Failed to offer message\");\n            }\n\n            final MutableLong sendTimestamp = new MutableLong(SENTINEL_VALUE);\n            final FragmentHandler fragmentHandler =\n                (buffer1, offset, length, header) -> sendTimestamp.set(buffer1 .getLong(offset));\n            while (1 > sub1.poll(fragmentHandler, 1))\n            {\n                Tests.yieldingIdle(\"Failed to receive message\");\n            }\n\n            assertNotEquals(SENTINEL_VALUE, sendTimestamp.longValue());\n\n            while (1 > sub2.poll(fragmentHandler, 1))\n            {\n                Tests.yieldingIdle(\"Failed to receive message\");\n            }\n\n            assertNotEquals(SENTINEL_VALUE, sendTimestamp.longValue());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSupportReceiveTimestampsOnMds()\n    {\n        final MutableDirectBuffer buffer = new UnsafeBuffer(new byte[64]);\n\n        try (TestMediaDriver driver = driver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final Subscription mdsSub = aeron.addSubscription(\n                \"aeron:udp?control-mode=manual|channel-rcv-ts-offset=0\", 1000);\n\n            final Publication pub1 = aeron.addPublication(\"aeron:udp?endpoint=localhost:23424\", 1000);\n            final Publication pub2 = aeron.addPublication(\"aeron:udp?endpoint=localhost:23425\", 1000);\n            mdsSub.addDestination(\"aeron:udp?endpoint=localhost:23424\");\n            mdsSub.addDestination(\"aeron:udp?endpoint=localhost:23425\");\n\n            while (!pub1.isConnected() || !pub2.isConnected())\n            {\n                Tests.yieldingIdle(\"Failed to connect\");\n            }\n\n            buffer.putLong(0, SENTINEL_VALUE);\n\n            while (0 > pub1.offer(buffer, 0, buffer.capacity()))\n            {\n                Tests.yieldingIdle(\"Failed to offer message\");\n            }\n\n            while (0 > pub2.offer(buffer, 0, buffer.capacity()))\n            {\n                Tests.yieldingIdle(\"Failed to offer message\");\n            }\n\n            final MutableLong sendTimestamp = new MutableLong(SENTINEL_VALUE);\n            final FragmentHandler fragmentHandler =\n                (buffer1, offset, length, header) -> sendTimestamp.set(buffer1.getLong(offset));\n            while (1 > mdsSub.poll(fragmentHandler, 1))\n            {\n                Tests.yieldingIdle(\"Failed to receive message\");\n            }\n\n            assertNotEquals(SENTINEL_VALUE, sendTimestamp.longValue());\n\n            while (1 > mdsSub.poll(fragmentHandler, 1))\n            {\n                Tests.yieldingIdle(\"Failed to receive message\");\n            }\n\n            assertNotEquals(SENTINEL_VALUE, sendTimestamp.longValue());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSupportReceiveTimestampsOnMergedMds()\n    {\n        final MutableDirectBuffer buffer = new UnsafeBuffer(new byte[64]);\n\n        try (TestMediaDriver driver = driver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final Subscription mdsSub = aeron.addSubscription(\n                \"aeron:udp?control-mode=manual|channel-rcv-ts-offset=0\", 1000);\n\n            final Publication pub1 = aeron.addExclusivePublication(\"aeron:udp?endpoint=localhost:23424\", 1000);\n            final String pub2Uri = new ChannelUriStringBuilder(\"aeron:udp?endpoint=localhost:23425\")\n                .initialPosition(0L, pub1.initialTermId(), pub1.termBufferLength())\n                .sessionId(pub1.sessionId())\n                .build();\n\n            final Publication pub2 = aeron.addExclusivePublication(pub2Uri, 1000);\n            mdsSub.addDestination(\"aeron:udp?endpoint=localhost:23424\");\n            mdsSub.addDestination(\"aeron:udp?endpoint=localhost:23425\");\n\n            while (!pub1.isConnected() || !pub2.isConnected())\n            {\n                Tests.yieldingIdle(\"Failed to connect\");\n            }\n\n            final MutableLong sendTimestamp = new MutableLong(SENTINEL_VALUE);\n\n            buffer.putLong(0, SENTINEL_VALUE);\n            while (0 > pub1.offer(buffer, 0, buffer.capacity()))\n            {\n                Tests.yieldingIdle(\"Failed to offer message\");\n            }\n\n            buffer.putLong(0, SENTINEL_VALUE);\n            while (0 > pub2.offer(buffer, 0, buffer.capacity()))\n            {\n                Tests.yieldingIdle(\"Failed to offer message\");\n            }\n\n            final FragmentHandler fragmentHandler =\n                (buffer1, offset, length, header) -> sendTimestamp.set(buffer1.getLong(offset));\n            while (1 > mdsSub.poll(fragmentHandler, 1))\n            {\n                Tests.yieldingIdle(\"Failed to receive message\");\n            }\n\n            assertNotEquals(SENTINEL_VALUE, sendTimestamp.longValue());\n\n            buffer.putLong(0, SENTINEL_VALUE);\n            while (0 > pub2.offer(buffer, 0, buffer.capacity()))\n            {\n                Tests.yieldingIdle(\"Failed to offer message\");\n            }\n\n            buffer.putLong(0, SENTINEL_VALUE);\n            while (0 > pub1.offer(buffer, 0, buffer.capacity()))\n            {\n                Tests.yieldingIdle(\"Failed to offer message\");\n            }\n\n            sendTimestamp.set(SENTINEL_VALUE);\n            while (1 > mdsSub.poll(fragmentHandler, 1))\n            {\n                Tests.yieldingIdle(\"Failed to receive message\");\n            }\n\n            assertNotEquals(SENTINEL_VALUE, sendTimestamp.longValue());\n        }\n    }\n\n    private static void setAll(final MutableDirectBuffer buffer, final byte value)\n    {\n        for (int i = 0, n = buffer.capacity(); i < n; i++)\n        {\n            buffer.putByte(i, value);\n        }\n    }\n\n    private static boolean thisIsTheJavaDriverThenIgnoreMediaTimestampParameter(final Exception e)\n    {\n        return IGNORE_MEDIA_TIMESTAMPS_PREDICATE.test(e.getMessage());\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/TwoBufferOfferMessageTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableReference;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(InterruptingTestCallback.class)\nclass TwoBufferOfferMessageTest\n{\n    private static final String CHANNEL = \"aeron:ipc?term-length=64k\";\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private final MediaDriver.Context driverContext = new MediaDriver.Context()\n        .errorHandler(Tests::onError)\n        .dirDeleteOnStart(true)\n        .threadingMode(ThreadingMode.SHARED);\n\n    private TestMediaDriver driver;\n\n    private Aeron aeron;\n\n    @BeforeEach\n    void setUp()\n    {\n        driver = TestMediaDriver.launch(driverContext, testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldTransferUnfragmentedTwoPartMessage()\n    {\n        final UnsafeBuffer expectedBuffer = new UnsafeBuffer(new byte[256]);\n        final UnsafeBuffer bufferOne = new UnsafeBuffer(expectedBuffer, 0, 32);\n        final UnsafeBuffer bufferTwo = new UnsafeBuffer(expectedBuffer, 32, expectedBuffer.capacity() - 32);\n\n        bufferOne.setMemory(0, bufferOne.capacity(), (byte)'a');\n        bufferTwo.setMemory(0, bufferTwo.capacity(), (byte)'b');\n        final String expectedMessage = expectedBuffer.getStringWithoutLengthAscii(0, expectedBuffer.capacity());\n\n        final MutableReference<String> receivedMessage = new MutableReference<>();\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) ->\n            receivedMessage.set(buffer.getStringWithoutLengthAscii(offset, length));\n\n        try (Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID))\n        {\n            try (Publication publication = aeron.addPublication(CHANNEL, STREAM_ID))\n            {\n                publishMessage(bufferOne, bufferTwo, publication);\n                pollForMessage(subscription, receivedMessage, fragmentHandler);\n\n                assertEquals(expectedMessage, receivedMessage.get());\n            }\n\n            try (Publication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n            {\n                publishMessage(bufferOne, bufferTwo, publication);\n                pollForMessage(subscription, receivedMessage, fragmentHandler);\n\n                assertEquals(expectedMessage, receivedMessage.get());\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldTransferFragmentedTwoPartMessage()\n    {\n        final UnsafeBuffer expectedBuffer = new UnsafeBuffer(new byte[32 + driver.context().mtuLength()]);\n        final UnsafeBuffer bufferOne = new UnsafeBuffer(expectedBuffer, 0, 32);\n        final UnsafeBuffer bufferTwo = new UnsafeBuffer(expectedBuffer, 32, expectedBuffer.capacity() - 32);\n\n        bufferOne.setMemory(0, bufferOne.capacity(), (byte)'a');\n        bufferTwo.setMemory(0, bufferTwo.capacity(), (byte)'b');\n        final String expectedMessage = expectedBuffer.getStringWithoutLengthAscii(0, expectedBuffer.capacity());\n\n        final MutableReference<String> receivedMessage = new MutableReference<>();\n        final FragmentHandler fragmentHandler = new FragmentAssembler((buffer, offset, length, header) ->\n            receivedMessage.set(buffer.getStringWithoutLengthAscii(offset, length)));\n\n        try (Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID))\n        {\n            try (Publication publication = aeron.addPublication(CHANNEL, STREAM_ID))\n            {\n                publishMessage(bufferOne, bufferTwo, publication);\n                pollForMessage(subscription, receivedMessage, fragmentHandler);\n\n                assertEquals(expectedMessage, receivedMessage.get());\n            }\n\n            try (Publication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID))\n            {\n                publishMessage(bufferOne, bufferTwo, publication);\n                pollForMessage(subscription, receivedMessage, fragmentHandler);\n\n                assertEquals(expectedMessage, receivedMessage.get());\n            }\n        }\n    }\n\n    private static void publishMessage(\n        final UnsafeBuffer bufferOne, final UnsafeBuffer bufferTwo, final Publication publication)\n    {\n        while (publication.offer(bufferOne, 0, bufferOne.capacity(), bufferTwo, 0, bufferTwo.capacity()) < 0L)\n        {\n            Tests.yield();\n        }\n    }\n\n    private void pollForMessage(\n        final Subscription subscription, final MutableReference<String> receivedMessage, final FragmentHandler handler)\n    {\n        receivedMessage.set(null);\n\n        while (receivedMessage.get() == null)\n        {\n            final int fragments = subscription.poll(handler, FRAGMENT_COUNT_LIMIT);\n            if (fragments == 0)\n            {\n                Tests.yield();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/UntetheredSubscriptionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.ByteBuffer;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static io.aeron.CommonContext.UDP_MEDIA;\nimport static io.aeron.CommonContext.UNTETHERED_LINGER_TIMEOUT_PARAM_NAME;\nimport static io.aeron.CommonContext.UNTETHERED_RESTING_TIMEOUT_PARAM_NAME;\nimport static java.util.Arrays.asList;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_ALLOCATED;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass UntetheredSubscriptionTest\n{\n    private static List<String> channels()\n    {\n        return asList(\n            \"aeron:ipc?term-length=64k\",\n            \"aeron:udp?endpoint=localhost:24325|term-length=64k\",\n            \"aeron-spy:aeron:udp?endpoint=localhost:24325|term-length=64k\",\n            \"\"\"\n                aeron:ipc?term-length=64k|untethered-window-limit-timeout=50ms|\\\n                untethered-resting-timeout=50ms|untethered-linger-timeout=25ms\"\"\",\n            \"\"\"\n                aeron:udp?endpoint=localhost:24325|term-length=64k|\\\n                untethered-window-limit-timeout=50ms|untethered-resting-timeout=50ms|\\\n                untethered-linger-timeout=25ms\"\"\",\n            \"\"\"\n                aeron-spy:aeron:udp?endpoint=localhost:24325|term-length=64k|\\\n                untethered-window-limit-timeout=50ms|untethered-resting-timeout=50ms|\\\n                untethered-linger-timeout=25ms\"\"\"\n        );\n    }\n\n    private static final int STREAM_ID = 1001;\n    private static final int FRAGMENT_COUNT_LIMIT = 10;\n    private static final int MESSAGE_LENGTH = 512 - DataHeaderFlyweight.HEADER_LENGTH;\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    private Aeron aeron;\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    private void launch(final String channel)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .errorHandler(Tests::onError)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true)\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(3))\n            .statusMessageTimeoutNs(TimeUnit.MILLISECONDS.toNanos(50))\n            .threadingMode(ThreadingMode.SHARED);\n\n        final ChannelUri channelUri = ChannelUri.parse(channel);\n        if (!channelUri.containsKey(CommonContext.UNTETHERED_WINDOW_LIMIT_TIMEOUT_PARAM_NAME))\n        {\n            context.untetheredWindowLimitTimeoutNs(TimeUnit.MILLISECONDS.toNanos(59));\n        }\n\n        if (!channelUri.containsKey(CommonContext.UNTETHERED_LINGER_TIMEOUT_PARAM_NAME))\n        {\n            context.untetheredLingerTimeoutNs(TimeUnit.MILLISECONDS.toNanos(25));\n        }\n\n        if (!channelUri.containsKey(UNTETHERED_RESTING_TIMEOUT_PARAM_NAME))\n        {\n            context.untetheredRestingTimeoutNs(TimeUnit.MILLISECONDS.toNanos(111));\n        }\n\n        driver = TestMediaDriver.launch(context, testWatcher);\n        testWatcher.dataCollector().add(driver.context().aeronDirectory());\n        aeron = Aeron.connect(new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .useConductorAgentInvoker(true));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldBecomeUnavailableWhenNotKeepingUp(final String channel)\n    {\n        launch(channel);\n\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> {};\n        final AtomicBoolean unavailableCalled = new AtomicBoolean();\n        final UnavailableImageHandler handler = (image) -> unavailableCalled.set(true);\n\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocate(MESSAGE_LENGTH));\n        final String untetheredChannel = channel + \"|tether=false\";\n        final String publicationChannel = channel.startsWith(\"aeron-spy\") ? channel.substring(10) : channel;\n        boolean pollingUntethered = true;\n\n        try (Subscription tetheredSub = aeron.addSubscription(channel, STREAM_ID);\n            Subscription untetheredSub = aeron.addSubscription(untetheredChannel, STREAM_ID, null, handler);\n            Publication publication = aeron.addPublication(publicationChannel, STREAM_ID))\n        {\n            while (!tetheredSub.isConnected() || !untetheredSub.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            while (true)\n            {\n                if (publication.offer(srcBuffer) < 0)\n                {\n                    Tests.yield();\n                    aeron.conductorAgentInvoker().invoke();\n                }\n\n                if (pollingUntethered && untetheredSub.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT) > 0)\n                {\n                    pollingUntethered = false;\n                }\n\n                tetheredSub.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n\n                if (unavailableCalled.get())\n                {\n                    assertTrue(tetheredSub.isConnected());\n                    assertFalse(untetheredSub.isConnected());\n\n                    while (publication.offer(srcBuffer) < 0)\n                    {\n                        Tests.yield();\n                        aeron.conductorAgentInvoker().invoke();\n                    }\n\n                    return;\n                }\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldRejoinAfterResting(final String channel)\n    {\n        launch(channel);\n\n        final AtomicInteger unavailableImageCount = new AtomicInteger();\n        final AtomicInteger availableImageCount = new AtomicInteger();\n        final UnavailableImageHandler unavailableHandler = (image) -> unavailableImageCount.incrementAndGet();\n        final AvailableImageHandler availableHandler = (image) -> availableImageCount.incrementAndGet();\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> {};\n\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocate(MESSAGE_LENGTH));\n        srcBuffer.setMemory(0, MESSAGE_LENGTH, (byte)-1);\n        final String untetheredChannel = channel + \"|tether=false\";\n        final String publicationChannel = channel.startsWith(\"aeron-spy\") ? channel.substring(10) : channel;\n        boolean pollingUntethered = true;\n\n        try (Subscription tetheredSub = aeron.addSubscription(channel, STREAM_ID);\n            Subscription untetheredSub = aeron.addSubscription(\n                untetheredChannel, STREAM_ID, availableHandler, unavailableHandler);\n            Publication publication = aeron.addPublication(publicationChannel, STREAM_ID))\n        {\n            while (!tetheredSub.isConnected() || !untetheredSub.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            while (0 == unavailableImageCount.get())\n            {\n                if (publication.offer(srcBuffer, 0, ThreadLocalRandom.current().nextInt(1, MESSAGE_LENGTH)) < 0)\n                {\n                    Tests.yield();\n                    aeron.conductorAgentInvoker().invoke();\n                }\n\n                if (pollingUntethered && untetheredSub.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT) > 0)\n                {\n                    pollingUntethered = false;\n                }\n\n                tetheredSub.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n            }\n\n            while (availableImageCount.get() < 2)\n            {\n                publication.offer(srcBuffer, 0, ThreadLocalRandom.current().nextInt(1, MESSAGE_LENGTH));\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            final Image tetheredImage = tetheredSub.imageAtIndex(0);\n            final Image untetheredImage = untetheredSub.imageAtIndex(0);\n            while (untetheredImage.position() < publication.position() ||\n                tetheredImage.position() < publication.position())\n            {\n                int fragments = 0;\n                fragments += tetheredSub.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n                fragments += untetheredSub.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n                if (0 == fragments)\n                {\n                    Tests.yield();\n                    aeron.conductorAgentInvoker().invoke();\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldStoreUntetheredTimeoutsInLogBufferMetadata()\n    {\n        final String channel = \"aeron:udp?term-length=64k|endpoint=localhost:5555\";\n        launch(\"aeron:ipc\");\n\n        final int streamId = 1142;\n        final ChannelUriStringBuilder publicationBuilder = new ChannelUriStringBuilder(channel)\n            .untetheredWindowLimitTimeoutNs(TimeUnit.MILLISECONDS.toNanos(150))\n            .untetheredLingerTimeoutNs(TimeUnit.MILLISECONDS.toNanos(68))\n            .untetheredRestingTimeoutNs(TimeUnit.MILLISECONDS.toNanos(140));\n        final ExclusivePublication publication = aeron.addExclusivePublication(publicationBuilder.build(), streamId);\n\n        final ChannelUriStringBuilder subscriptionBuilder = new ChannelUriStringBuilder(channel)\n            .untetheredWindowLimitTimeoutNs(TimeUnit.SECONDS.toNanos(200))\n            .untetheredLingerTimeoutNs(TimeUnit.SECONDS.toNanos(300))\n            .untetheredRestingTimeoutNs(TimeUnit.SECONDS.toNanos(444));\n        final Subscription subscription = aeron.addSubscription(subscriptionBuilder.build(), streamId);\n        while (!subscription.isConnected())\n        {\n            aeron.conductorAgentInvoker().invoke();\n        }\n\n        assertUntetheredParametersInLogBufferMetadata(\n            \"publications\",\n            publication.registrationId(),\n            publicationBuilder.untetheredWindowLimitTimeoutNs(),\n            publicationBuilder.untetheredLingerTimeoutNs(),\n            publicationBuilder.untetheredRestingTimeoutNs());\n\n        assertUntetheredParametersInLogBufferMetadata(\n            \"images\",\n            subscription.imageAtIndex(0).correlationId(),\n            subscriptionBuilder.untetheredWindowLimitTimeoutNs(),\n            subscriptionBuilder.untetheredLingerTimeoutNs(),\n            subscriptionBuilder.untetheredRestingTimeoutNs());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @SuppressWarnings(\"try\")\n    @InterruptAfter(10)\n    void shouldSetConnectedStatusCorrectlyWhenUntetheredSpyReconnectsAfterResting(final boolean spiesSimulateConnection)\n    {\n        final String channel =\n            \"aeron:udp?endpoint=localhost:5596|term-length=64k|ssc=\" + spiesSimulateConnection;\n        launch(channel);\n\n        final AtomicLong spyUnavailableImageCount = new AtomicLong();\n        final AtomicLong spyAvailableImageCount = new AtomicLong();\n        try (Publication publication = aeron.addExclusivePublication(channel, STREAM_ID);\n            Subscription subscription = aeron.addSubscription(channel + \"|tether=true\", STREAM_ID);\n            Subscription spy = aeron.addSubscription(\n                CommonContext.SPY_PREFIX + channel + \"|tether=false\",\n                STREAM_ID,\n                (image) -> spyAvailableImageCount.incrementAndGet(),\n                (image) -> spyUnavailableImageCount.incrementAndGet()))\n        {\n            while (!publication.isConnected() || !subscription.isConnected() || !spy.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n            assertEquals(1, spyAvailableImageCount.get());\n\n            final UnsafeBuffer data = new UnsafeBuffer(new byte[1024]);\n            ThreadLocalRandom.current().nextBytes(data.byteArray());\n            final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> {};\n            while (0 == spyUnavailableImageCount.get())\n            {\n                if (publication.offer(data) > 0)\n                {\n                    while (0 == subscription.poll(fragmentHandler, 1))\n                    {\n                        Tests.yield();\n                    }\n                }\n\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            final int subPosCounterId = subscription.imageBySessionId(publication.sessionId()).subscriberPositionId();\n            subscription.close();\n            subscription.close();\n            awaitCounterClosed(subPosCounterId); // await subscription close\n\n            while (2 != spyAvailableImageCount.get()) // wait for spy to re-connect\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            assertEquals(1, spyUnavailableImageCount.get());\n            assertEquals(publication.position(), spy.imageAtIndex(0).position());\n            assertTrue(spy.isConnected());\n\n            if (spiesSimulateConnection)\n            {\n                while (!publication.isConnected())\n                {\n                    Tests.yield();\n                    aeron.conductorAgentInvoker().invoke();\n                }\n            }\n            else\n            {\n                final long startNs = System.nanoTime();\n                final long endNs = startNs + 5 * resolveTimeoutNs(\n                    publication, UNTETHERED_RESTING_TIMEOUT_PARAM_NAME, driver.context().untetheredRestingTimeoutNs());\n                do\n                {\n                    assertFalse(publication.isConnected());\n                    assertFalse(publication.isClosed());\n                    assertThat(publication.availableWindow(), lessThanOrEqualTo(0L));\n                }\n                while (System.nanoTime() < endNs);\n            }\n        }\n    }\n\n    @Test\n    @SuppressWarnings(\"try\")\n    @InterruptAfter(10)\n    void shouldSetConnectedStatusCorrectlyWhenUntetheredIpcSubscriptionReconnectsAfterResting()\n    {\n        final String channel =\n            \"aeron:ipc?endpoint=localhost:5596|term-length=64k|untethered-resting-timeout=300ms\";\n        launch(channel);\n\n        final AtomicLong unavailableImageCount = new AtomicLong();\n        final AtomicLong availableImageCount = new AtomicLong();\n        try (Publication publication = aeron.addExclusivePublication(channel, STREAM_ID);\n            Subscription subscription = aeron.addSubscription(channel, STREAM_ID);\n            Subscription untetheredSubscription = aeron.addSubscription(\n                channel + \"|tether=false\",\n                STREAM_ID,\n                (image) -> availableImageCount.incrementAndGet(),\n                (image) -> unavailableImageCount.incrementAndGet()))\n        {\n            while (!publication.isConnected() || !subscription.isConnected() || !untetheredSubscription.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n            assertEquals(1, availableImageCount.get());\n\n            final UnsafeBuffer data = new UnsafeBuffer(new byte[1024]);\n            ThreadLocalRandom.current().nextBytes(data.byteArray());\n            final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> {};\n            while (0 == unavailableImageCount.get())\n            {\n                if (publication.offer(data) > 0)\n                {\n                    while (0 == subscription.poll(fragmentHandler, 1))\n                    {\n                        Tests.yield();\n                    }\n                }\n\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n\n            final int subPosCounterId = subscription.imageBySessionId(publication.sessionId()).subscriberPositionId();\n            subscription.close();\n            awaitCounterClosed(subPosCounterId); // await subscription close\n\n            boolean publicationWasDisconnected = !publication.isConnected();\n            while (2 != availableImageCount.get()) // wait for re-connect after resting\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n                if (!publication.isConnected())\n                {\n                    publicationWasDisconnected = true;\n                }\n            }\n\n            assertTrue(publicationWasDisconnected);\n            Tests.awaitConnected(publication);\n\n            assertEquals(publication.position(), untetheredSubscription.imageAtIndex(0).position());\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"channels\")\n    @InterruptAfter(10)\n    void shouldNotRejoinAfterRestingIfRejoinIsFalse(final String channel)\n    {\n        launch(channel);\n\n        final AtomicInteger unavailableImageCount = new AtomicInteger();\n        final AtomicInteger availableImageCount = new AtomicInteger();\n        final UnavailableImageHandler unavailableHandler = (image) -> unavailableImageCount.incrementAndGet();\n        final AvailableImageHandler availableHandler = (image) -> availableImageCount.incrementAndGet();\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> {};\n\n        final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n        ThreadLocalRandom.current().nextBytes(srcBuffer.byteArray());\n        final String untetheredChannel = channel + \"|tether=false|rejoin=false\";\n        final String publicationChannel = channel.startsWith(\"aeron-spy\") ? channel.substring(10) : channel;\n        final boolean isUdp = UDP_MEDIA.equals(ChannelUri.parse(channel).media());\n        boolean pollingUntethered = true;\n\n        try (Subscription tetheredSub = aeron.addSubscription(isUdp ? channel + \"|rejoin=false\" : channel, STREAM_ID);\n            Subscription untetheredSub = aeron.addSubscription(\n                untetheredChannel, STREAM_ID, availableHandler, unavailableHandler);\n            Publication publication = aeron.addPublication(publicationChannel, STREAM_ID))\n        {\n            while (!tetheredSub.isConnected() || !untetheredSub.isConnected())\n            {\n                Tests.yield();\n                aeron.conductorAgentInvoker().invoke();\n            }\n            final Image tetheredImage = tetheredSub.imageBySessionId(publication.sessionId());\n            final Image untetheredImage = untetheredSub.imageBySessionId(publication.sessionId());\n            final int untetheredSubPosCounterId = untetheredImage.subscriberPositionId();\n\n            while (0 == unavailableImageCount.get())\n            {\n                if (publication.offer(srcBuffer, 0, ThreadLocalRandom.current().nextInt(1, MESSAGE_LENGTH)) < 0)\n                {\n                    Tests.yield();\n                    aeron.conductorAgentInvoker().invoke();\n                }\n\n                if (pollingUntethered && untetheredImage.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT) > 0)\n                {\n                    pollingUntethered = false;\n                }\n\n                tetheredImage.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT);\n            }\n\n            final long startNs = System.nanoTime();\n            final long endNs = startNs + 5 * resolveTimeoutNs(\n                publication, UNTETHERED_LINGER_TIMEOUT_PARAM_NAME, driver.context().untetheredLingerTimeoutNs());\n            do\n            {\n                assertEquals(1, unavailableImageCount.get());\n                assertEquals(1, availableImageCount.get());\n                assertFalse(untetheredSub.isConnected());\n                assertTrue(tetheredSub.isConnected());\n\n                if (publication.offer(srcBuffer, 0, ThreadLocalRandom.current().nextInt(1, MESSAGE_LENGTH)) < 0)\n                {\n                    Tests.yield();\n                    aeron.conductorAgentInvoker().invoke();\n                }\n            }\n            while (System.nanoTime() < endNs);\n\n            while (tetheredImage.position() < publication.position())\n            {\n                if (0 == tetheredImage.poll(fragmentHandler, FRAGMENT_COUNT_LIMIT))\n                {\n                    Tests.yield();\n                    aeron.conductorAgentInvoker().invoke();\n                }\n            }\n\n            awaitCounterClosed(untetheredSubPosCounterId);\n        }\n    }\n\n    private void awaitCounterClosed(final int counterId)\n    {\n        while (RECORD_ALLOCATED == aeron.countersReader().getCounterState(counterId))\n        {\n            Tests.yield();\n            aeron.conductorAgentInvoker().invoke();\n        }\n    }\n\n    private static long resolveTimeoutNs(\n        final Publication publication, final String paramName, final long defaultValue)\n    {\n        final ChannelUri channelUri = ChannelUri.parse(publication.channel());\n        final String timeout = channelUri.get(paramName);\n        if (null != timeout)\n        {\n            return SystemUtil.parseDuration(paramName, timeout);\n        }\n        return defaultValue;\n    }\n\n    private void assertUntetheredParametersInLogBufferMetadata(\n        final String directory,\n        final long registrationId,\n        final long expectedWindowLimitTimeoutNs,\n        final long expectedLingerTimeoutNs,\n        final long expectedRestingTmeoutNs)\n    {\n        try (LogBuffers logBuffers = new LogBuffers(\n            driver.aeronDirectoryName() + \"/\" + directory + \"/\" + registrationId + \".logbuffer\"))\n        {\n            assertEquals(\n                expectedWindowLimitTimeoutNs,\n                LogBufferDescriptor.untetheredWindowLimitTimeoutNs(logBuffers.metaDataBuffer()));\n            assertEquals(\n                expectedLingerTimeoutNs,\n                LogBufferDescriptor.untetheredLingerTimeoutNs(logBuffers.metaDataBuffer()));\n            assertEquals(\n                expectedRestingTmeoutNs,\n                LogBufferDescriptor.untetheredRestingTimeoutNs(logBuffers.metaDataBuffer()));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/UriValidationTest.java",
    "content": "/*\n * Copyright 2023 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.function.Executable;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\n\n@ExtendWith(InterruptingTestCallback.class)\npublic class UriValidationTest\n{\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void setup()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .threadingMode(ThreadingMode.SHARED), testWatcher);\n\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"aeron:udp?endpoint=localhost:8080|ssc=false\", \"aeron:ipc?mtu=2K\"})\n    @InterruptAfter(10)\n    void shouldRejectChannelUrisWhenTooLong(final String baseUri)\n    {\n        final int streamId = 15;\n        final String uri = Tests.generateStringWithSuffix(\n            baseUri + \"|alias=too-looong-\", \"x\", ChannelUri.MAX_URI_LENGTH);\n\n        assertUriRejected(uri, () -> aeron.addPublication(uri, streamId));\n        assertUriRejected(uri, () -> aeron.addSubscription(uri, streamId));\n    }\n\n    private void assertUriRejected(final String uri, final Executable executable)\n    {\n        final RegistrationException exception =\n            assertThrowsExactly(RegistrationException.class, executable);\n        assertEquals(ErrorCode.INVALID_CHANNEL, exception.errorCode());\n        assertThat(exception.getMessage(), containsString(uri.substring(0, ChannelUri.MAX_URI_LENGTH)));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/WildcardPortManagerSystemTest.java",
    "content": "/*\n * Copyright 2023 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.status.ChannelEndpointStatus;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n@ExtendWith(InterruptingTestCallback.class)\npublic class WildcardPortManagerSystemTest\n{\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldAllocatePortsInTheGivenRanges()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .publicationTermBufferLength(64 * 1024)\n            .timerIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n            .receiverWildcardPortRange(\"20700 20701\")\n            .senderWildcardPortRange(\"20702 20702\");\n\n        final Aeron.Context aeronCtx = new Aeron.Context()\n            .aeronDirectoryName(driverCtx.aeronDirectoryName());\n\n        driver = TestMediaDriver.launch(driverCtx, testWatcher);\n        aeron = Aeron.connect(aeronCtx);\n\n        final String subscriptionChannel = \"aeron:udp?endpoint=127.0.0.1:0\";\n\n        final Subscription subscription1 = aeron.addSubscription(subscriptionChannel, 1);\n        final String endpoint1 = awaitResolvedEndpoint(subscription1);\n        assertEquals(\"127.0.0.1:20700\", endpoint1);\n\n        final Subscription subscription2 = aeron.addSubscription(subscriptionChannel, 2);\n        final String endpoint2 = awaitResolvedEndpoint(subscription2);\n        assertEquals(\"127.0.0.1:20701\", endpoint2);\n\n        final RegistrationException registrationException1 = assertThrows(\n            RegistrationException.class,\n            () -> aeron.addSubscription(subscriptionChannel, 3));\n        assertThat(registrationException1.getMessage(), containsString(\"no available ports in range 20700 20701\"));\n\n        subscription2.close();\n\n        final Subscription subscription3 = await(0, () -> aeron.addSubscription(subscriptionChannel, 4));\n        final String endpoint3 = awaitResolvedEndpoint(subscription3);\n        assertEquals(\"127.0.0.1:20701\", endpoint3);\n\n        final String publicationChannel = \"aeron:udp?control=127.0.0.1:0|control-mode=dynamic|linger=0\";\n\n        final Publication publication1 = aeron.addPublication(publicationChannel, 5);\n        final String controlEndpoint1 = awaitResolvedEndpoint(publication1);\n        assertEquals(\"127.0.0.1:20702\", controlEndpoint1);\n\n        final RegistrationException registrationException2 = assertThrows(\n            RegistrationException.class,\n            () -> aeron.addPublication(publicationChannel, 6));\n        assertThat(registrationException2.getMessage(), containsString(\"no available ports in range 20702 20702\"));\n\n        publication1.close();\n\n        final Publication publication2 = await(200, () -> aeron.addPublication(publicationChannel, 7));\n        final String controlEndpoint2 = awaitResolvedEndpoint(publication2);\n        assertEquals(\"127.0.0.1:20702\", controlEndpoint2);\n    }\n\n    private <T> T await(final long initialDelayMs, final Supplier<T> supplier)\n    {\n        if (initialDelayMs > 0)\n        {\n            Tests.sleep(initialDelayMs);\n        }\n\n        final List<Exception> exceptions = new ArrayList<>();\n\n        while (true)\n        {\n            try\n            {\n                return supplier.get();\n            }\n            catch (final Exception e)\n            {\n                exceptions.add(e);\n            }\n\n            try\n            {\n                Thread.sleep(100);\n            }\n            catch (final InterruptedException e)\n            {\n                Thread.currentThread().interrupt();\n\n                final TimeoutException timeoutException = new TimeoutException(\"Timed out awaiting result\");\n                exceptions.forEach(timeoutException::addSuppressed);\n                throw timeoutException;\n            }\n        }\n    }\n\n    private String awaitResolvedEndpoint(final Subscription subscription)\n    {\n        String endpoint;\n\n        while ((endpoint = subscription.resolvedEndpoint()) == null)\n        {\n            if (subscription.channelStatus() == ChannelEndpointStatus.ERRORED)\n            {\n                throw new AssertionError(\"Channel endpoint error\");\n            }\n\n            Tests.yieldingIdle(\"Could not resolve endpoint\");\n        }\n\n        return endpoint;\n    }\n\n    private String awaitResolvedEndpoint(final Publication publication)\n    {\n        List<String> localSocketAddresses;\n\n        while ((localSocketAddresses = publication.localSocketAddresses()).isEmpty())\n        {\n            Tests.yieldingIdle(\"Could not resolve endpoint\");\n        }\n\n        return localSocketAddresses.get(0);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveAbandonedClientTest.java",
    "content": "/*\n * Copyright 2014-2023 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\nimport java.nio.file.Path;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThan;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class ArchiveAbandonedClientTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n    @TempDir\n    Path tempDir;\n\n    private TestMediaDriver driver;\n    private Archive archive;\n    private Aeron aeron;\n    private AeronArchive client1;\n    private AeronArchive client2;\n\n    @BeforeEach\n    void setUp()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .aeronDirectoryName(tempDir.resolve(\"media-driver\").toString())\n            .termBufferSparseFile(true)\n            .sharedIdleStrategy(YieldingIdleStrategy.INSTANCE)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true)\n            .threadingMode(ThreadingMode.SHARED);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(client1, client2, aeron, archive, driver);\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n        \"aeron:ipc, aeron:ipc\",\n        \"aeron:udp?endpoint=localhost:10001, aeron:udp?endpoint=localhost:10002\",\n        \"aeron:udp?endpoint=localhost:10001, aeron:udp?control=localhost:10002|control-mode=response\",\n    })\n    @InterruptAfter(15)\n    void test(final String requestChannel, final String responseChannel)\n    {\n        launch(requestChannel, responseChannel);\n\n        final String channel = \"aeron:ipc?ssc=true|term-length=128k\";\n        final int streamId = 444;\n        final ExclusivePublication recordedPublication =\n            client1.addRecordedExclusivePublication(channel, streamId);\n        final UnsafeBuffer buffer = new UnsafeBuffer(new byte[1024]);\n        ThreadLocalRandom.current().nextBytes(buffer.byteArray());\n\n        final CountersReader counters = aeron.countersReader();\n        final int recordingCounterId = Tests.awaitRecordingCounterId(\n            counters, recordedPublication.sessionId(), client1.archiveId());\n        final long recordingId = RecordingPos.getRecordingId(counters, recordingCounterId);\n\n        final int responseSubscriptionCounterId1 =\n            client1.controlResponsePoller().subscription().imageAtIndex(0).subscriberPositionId();\n        final int responseSubscriptionCounterId2 =\n            client2.controlResponsePoller().subscription().imageAtIndex(0).subscriberPositionId();\n\n        // ensure at least one full term of archive responses is generated in order to trigger blocking\n        final int controlTermBufferLength = archive.context().controlTermBufferLength();\n        while (counters.getCounterValue(responseSubscriptionCounterId1) < controlTermBufferLength)\n        {\n            final int length = ThreadLocalRandom.current().nextInt(buffer.capacity());\n            while (recordedPublication.offer(buffer, 0, length) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (client1.getMaxRecordedPosition(recordingId) < recordedPublication.position())\n            {\n                Tests.yield();\n            }\n        }\n\n        if (counters.getCounterState(responseSubscriptionCounterId2) == CountersReader.RECORD_ALLOCATED)\n        {\n            assertThat(\n                counters.getCounterValue(responseSubscriptionCounterId2),\n                lessThan(counters.getCounterValue(responseSubscriptionCounterId1)));\n        }\n    }\n\n    private void launch(final String requestChannel, final String responseChannel)\n    {\n        final int requestStreamId = 111;\n        final int responseStreamId = 222;\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .controlChannel(\"aeron:udp?endpoint=localhost:10001\")\n            .controlStreamId(requestStreamId)\n            .localControlChannel(\"aeron:ipc\")\n            .localControlStreamId(requestStreamId)\n            .catalogCapacity(ArchiveSystemTests.CATALOG_CAPACITY)\n            .fileSyncLevel(0)\n            .deleteArchiveOnStart(true)\n            .archiveDir(tempDir.resolve(\"archive-test\").toFile())\n            .segmentFileLength(1024 * 1024)\n            .idleStrategySupplier(YieldingIdleStrategy::new)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n        final Aeron.Context aeronCtx = new Aeron.Context()\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .useConductorAgentInvoker(true);\n\n        final AeronArchive.Context aeronArchiveContext = new AeronArchive.Context()\n            .controlRequestChannel(requestChannel)\n            .controlRequestStreamId(requestStreamId)\n            .controlResponseChannel(responseChannel)\n            .controlResponseStreamId(responseStreamId)\n            .controlTermBufferLength(64 * 1024)\n            .controlTermBufferSparse(true);\n\n        archive = Archive.launch(archiveContext);\n        systemTestWatcher.dataCollector().add(archive.context().archiveDir());\n\n        aeron = Aeron.connect(aeronCtx);\n        client1 = AeronArchive.connect(aeronArchiveContext.clone().aeron(aeron).ownsAeronClient(false));\n        client2 = AeronArchive.connect(aeronArchiveContext.clone().aeron(aeron).ownsAeronClient(false));\n\n        assertEquals(archive.context().archiveId(), client1.archiveId());\n        assertEquals(archive.context().archiveId(), client2.archiveId());\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveAuthenticationTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.AuthenticatorSupplier;\nimport io.aeron.security.CredentialsSupplier;\nimport io.aeron.security.SessionProxy;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\n\nimport static io.aeron.archive.ArchiveSystemTests.CATALOG_CAPACITY;\nimport static io.aeron.archive.ArchiveSystemTests.consume;\nimport static io.aeron.archive.ArchiveSystemTests.offer;\nimport static io.aeron.archive.codecs.SourceLocation.LOCAL;\nimport static io.aeron.security.NullCredentialsSupplier.NULL_CREDENTIAL;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.spy;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ArchiveAuthenticationTest\n{\n    private static final int RECORDED_STREAM_ID = 1033;\n    private static final String RECORDED_CHANNEL = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3333\")\n        .termLength(ArchiveSystemTests.TERM_LENGTH)\n        .build();\n\n    private static final String CREDENTIALS_STRING = \"username=\\\"admin\\\"|password=\\\"secret\\\"\";\n    private static final String CHALLENGE_STRING = \"I challenge you!\";\n    private static final String PRINCIPAL_STRING = \"I am THE Principal!\";\n\n    private final byte[] encodedCredentials = CREDENTIALS_STRING.getBytes();\n    private final byte[] encodedChallenge = CHALLENGE_STRING.getBytes();\n\n    private TestMediaDriver driver;\n    private Archive archive;\n    private Aeron aeron;\n    private AeronArchive aeronArchive;\n\n    private final String aeronDirectoryName = CommonContext.generateRandomDirName();\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeronArchive, aeron, archive, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldBeAbleToRecordWithDefaultCredentialsAndAuthenticator()\n    {\n        launchArchivingMediaDriver(null);\n        connectClient(null);\n\n        createRecording();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldBeAbleToRecordWithAuthenticateOnConnectRequestWithCredentials()\n    {\n        final MutableLong authenticatorSessionId = new MutableLong(-1L);\n\n        final CredentialsSupplier credentialsSupplier = spy(new CredentialsSupplier()\n        {\n            public byte[] encodedCredentials()\n            {\n                return encodedCredentials;\n            }\n\n            public byte[] onChallenge(final byte[] encodedChallenge)\n            {\n                fail();\n                return null;\n            }\n        });\n\n        final Authenticator authenticator = spy(new Authenticator()\n        {\n            public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                authenticatorSessionId.value = sessionId;\n                assertEquals(CREDENTIALS_STRING, new String(encodedCredentials));\n            }\n\n            public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                fail();\n            }\n\n            public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                sessionProxy.authenticate(PRINCIPAL_STRING.getBytes());\n            }\n\n            public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                fail();\n            }\n        });\n\n        launchArchivingMediaDriver(() -> authenticator);\n        connectClient(credentialsSupplier);\n\n        assertEquals(aeronArchive.controlSessionId(), authenticatorSessionId.value);\n\n        createRecording();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldBeAbleToRecordWithAuthenticateOnChallengeResponse()\n    {\n        final MutableLong authenticatorSessionId = new MutableLong(-1L);\n\n        final CredentialsSupplier credentialsSupplier = spy(new CredentialsSupplier()\n        {\n            public byte[] encodedCredentials()\n            {\n                return NULL_CREDENTIAL;\n            }\n\n            public byte[] onChallenge(final byte[] encodedChallenge)\n            {\n                assertEquals(CHALLENGE_STRING, new String(encodedChallenge));\n                return encodedCredentials;\n            }\n        });\n\n        final Authenticator authenticator = spy(new Authenticator()\n        {\n            boolean challengeSuccessful = false;\n\n            public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                authenticatorSessionId.value = sessionId;\n                assertEquals(0, encodedCredentials.length);\n            }\n\n            public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                assertEquals(sessionId, authenticatorSessionId.value);\n                assertEquals(CREDENTIALS_STRING, new String(encodedCredentials));\n                challengeSuccessful = true;\n            }\n\n            public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                sessionProxy.challenge(encodedChallenge);\n            }\n\n            public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                if (challengeSuccessful)\n                {\n                    assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                    sessionProxy.authenticate(PRINCIPAL_STRING.getBytes());\n                }\n            }\n        });\n\n        launchArchivingMediaDriver(() -> authenticator);\n        connectClient(credentialsSupplier);\n\n        assertEquals(aeronArchive.controlSessionId(), authenticatorSessionId.value);\n\n        createRecording();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotBeAbleToConnectWithRejectOnConnectRequest()\n    {\n        final MutableLong authenticatorSessionId = new MutableLong(-1L);\n\n        final CredentialsSupplier credentialsSupplier = spy(new CredentialsSupplier()\n        {\n            public byte[] encodedCredentials()\n            {\n                return NULL_CREDENTIAL;\n            }\n\n            public byte[] onChallenge(final byte[] encodedChallenge)\n            {\n                assertEquals(CHALLENGE_STRING, new String(encodedChallenge));\n                return encodedCredentials;\n            }\n        });\n\n        final Authenticator authenticator = spy(new Authenticator()\n        {\n            public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                authenticatorSessionId.value = sessionId;\n                assertEquals(0, encodedCredentials.length);\n            }\n\n            public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                fail();\n            }\n\n            public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                sessionProxy.reject();\n            }\n\n            public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                fail();\n            }\n        });\n\n        launchArchivingMediaDriver(() -> authenticator);\n\n        try\n        {\n            connectClient(credentialsSupplier);\n        }\n        catch (final ArchiveException ex)\n        {\n            assertEquals(ArchiveException.AUTHENTICATION_REJECTED, ex.errorCode());\n            return;\n        }\n\n        fail(\"should have seen exception\");\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotBeAbleToConnectWithRejectOnChallengeResponse()\n    {\n        final MutableLong authenticatorSessionId = new MutableLong(-1L);\n\n        final CredentialsSupplier credentialsSupplier = spy(new CredentialsSupplier()\n        {\n            public byte[] encodedCredentials()\n            {\n                return NULL_CREDENTIAL;\n            }\n\n            public byte[] onChallenge(final byte[] encodedChallenge)\n            {\n                assertEquals(CHALLENGE_STRING, new String(encodedChallenge));\n                return encodedCredentials;\n            }\n        });\n\n        final Authenticator authenticator = spy(new Authenticator()\n        {\n            boolean challengeRespondedTo = false;\n\n            public void onConnectRequest(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                authenticatorSessionId.value = sessionId;\n                assertEquals(0, encodedCredentials.length);\n            }\n\n            public void onChallengeResponse(final long sessionId, final byte[] encodedCredentials, final long nowMs)\n            {\n                assertEquals(sessionId, authenticatorSessionId.value);\n                assertEquals(CREDENTIALS_STRING, new String(encodedCredentials));\n                challengeRespondedTo = true;\n            }\n\n            public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                sessionProxy.challenge(encodedChallenge);\n            }\n\n            public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n            {\n                if (challengeRespondedTo)\n                {\n                    assertEquals(sessionProxy.sessionId(), authenticatorSessionId.value);\n                    sessionProxy.reject();\n                }\n            }\n        });\n\n        launchArchivingMediaDriver(() -> authenticator);\n\n        try\n        {\n            connectClient(credentialsSupplier);\n        }\n        catch (final ArchiveException ex)\n        {\n            assertEquals(ArchiveException.AUTHENTICATION_REJECTED, ex.errorCode());\n            return;\n        }\n\n        fail(\"should have seen exception\");\n    }\n\n    private void connectClient(final CredentialsSupplier credentialsSupplier)\n    {\n        aeron = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(aeronDirectoryName));\n\n        aeronArchive = AeronArchive.connect(\n            TestContexts.localhostAeronArchive()\n                .credentialsSupplier(credentialsSupplier)\n                .aeron(aeron));\n    }\n\n    private void launchArchivingMediaDriver(final AuthenticatorSupplier authenticatorSupplier)\n    {\n        final MediaDriver.Context mediaDriverCtx = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDirectoryName)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(false)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true);\n\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .catalogCapacity(CATALOG_CAPACITY)\n            .aeronDirectoryName(aeronDirectoryName)\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"archive\"))\n            .fileSyncLevel(0)\n            .authenticatorSupplier(authenticatorSupplier)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n        driver = TestMediaDriver.launch(mediaDriverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(mediaDriverCtx.aeronDirectory());\n        archive = Archive.launch(archiveCtx);\n        systemTestWatcher.dataCollector().add(archiveCtx.archiveDir());\n    }\n\n    private void createRecording()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(\n                counters, publication.sessionId(), aeronArchive.archiveId());\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            final long currentPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, currentPosition);\n        }\n\n        aeronArchive.stopRecording(subscriptionId);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveDeleteAndRestartTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.samples.archive.RecordingDescriptorCollector;\nimport io.aeron.test.*;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.extension.TestWatcher;\n\nimport java.io.File;\nimport java.util.Random;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ArchiveDeleteAndRestartTest\n{\n    private static final int SYNC_LEVEL = 0;\n    private static final int STREAM_ID = 1;\n\n    private final long seed = System.nanoTime();\n\n    @RegisterExtension\n    final TestWatcher randomSeedWatcher = Tests.seedWatcher(seed);\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Archive archive;\n    private Aeron client;\n\n    private Archive.Context archiveContext;\n\n    @BeforeEach\n    void before()\n    {\n        final Random rnd = new Random();\n        rnd.setSeed(seed);\n\n        final int termLength = 1 << (16 + rnd.nextInt(10)); // 1M to 8M\n        final int segmentFileLength = termLength << rnd.nextInt(4);\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .sharedIdleStrategy(YieldingIdleStrategy.INSTANCE)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true);\n\n        archiveContext = TestContexts.localhostArchive()\n            .catalogCapacity(ArchiveSystemTests.CATALOG_CAPACITY)\n            .fileSyncLevel(SYNC_LEVEL)\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"archive-test\"))\n            .segmentFileLength(segmentFileLength)\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .idleStrategySupplier(YieldingIdleStrategy::new);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n        archive = Archive.launch(archiveContext.clone());\n        systemTestWatcher.dataCollector().add(archiveContext.archiveDir());\n\n        client = Aeron.connect();\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(client, archive, driver);\n    }\n\n    @InterruptAfter(10)\n    @Test\n    void recordAndReplayExclusivePublication()\n    {\n        final UnsafeBuffer buffer = new UnsafeBuffer(new byte[1024]);\n        buffer.setMemory(0, buffer.capacity(), (byte)'z');\n\n        AeronArchive aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive().aeron(client));\n\n        final String uri = \"aeron:ipc?term-length=16m|init-term-id=502090867|term-offset=0|term-id=502090867\";\n        final ExclusivePublication recordedPublication1 = client.addExclusivePublication(uri, STREAM_ID);\n\n        final long subscriptionId = aeronArchive.startRecording(uri, STREAM_ID, SourceLocation.LOCAL);\n\n        for (int i = 0; i < 10; i++)\n        {\n            while (recordedPublication1.offer(buffer, 0, 1024) < 0)\n            {\n                Tests.yieldingIdle(\"Failed to offer data\");\n            }\n        }\n\n        final long position1 = recordedPublication1.position();\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n\n        while (aeronArchive.listRecordings(0, Integer.MAX_VALUE, collector.reset()) < 1)\n        {\n            Tests.yieldingIdle(\"Didn't find recording\");\n        }\n\n        while (position1 != aeronArchive.getRecordingPosition(collector.descriptors().get(0).recordingId()))\n        {\n            Tests.yieldingIdle(\"Failed to record data\");\n        }\n\n        recordedPublication1.close();\n        aeronArchive.stopRecording(subscriptionId);\n\n        while (position1 != aeronArchive.getStopPosition(collector.descriptors().get(0).recordingId()))\n        {\n            Tests.yieldingIdle(\"Failed to stop recording\");\n        }\n\n        aeronArchive.close();\n        archive.close();\n        archive.context().deleteDirectory();\n\n        archive = Archive.launch(archiveContext.clone());\n        aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive().aeron(client));\n\n        final ExclusivePublication recordedPublication2 = client.addExclusivePublication(uri, STREAM_ID);\n        aeronArchive.startRecording(uri, STREAM_ID, SourceLocation.LOCAL);\n\n        for (int i = 0; i < 10; i++)\n        {\n            while (recordedPublication2.offer(buffer, 0, 1024) < 0)\n            {\n                Tests.yieldingIdle(\"Failed to offer data\");\n            }\n        }\n\n        while (aeronArchive.listRecordings(0, Integer.MAX_VALUE, collector.reset()) < 1)\n        {\n            Tests.yieldingIdle(\"Didn't find recording\");\n        }\n\n        assertEquals(\n            1, aeronArchive.listRecordings(0, Integer.MAX_VALUE, collector.reset()), collector.descriptors()::toString);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveListRecordingsTest.java",
    "content": "/*\n * Copyright 2014-2024 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.samples.archive.RecordingDescriptor;\nimport io.aeron.samples.archive.RecordingDescriptorCollector;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\n\nimport static io.aeron.archive.ArchiveSystemTests.recordData;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class ArchiveListRecordingsTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher()\n        .ignoreErrorsMatching(s -> s.contains(\"response publication is closed\"));\n\n    private TestMediaDriver driver;\n    private Archive archive;\n\n    @BeforeEach\n    void setUp()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .termBufferSparseFile(true)\n            .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .sharedIdleStrategy(YieldingIdleStrategy.INSTANCE)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true);\n\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .aeronDirectoryName(driverCtx.aeronDirectoryName())\n            .controlChannel(\"aeron:udp?endpoint=localhost:10001\")\n            .catalogCapacity(ArchiveSystemTests.CATALOG_CAPACITY)\n            .fileSyncLevel(0)\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"archive-test\"))\n            .segmentFileLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .idleStrategySupplier(YieldingIdleStrategy::new);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n\n        archive = Archive.launch(archiveContext);\n        systemTestWatcher.dataCollector().add(archiveContext.archiveDir());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietCloseAll(archive);\n        CloseHelper.quietCloseAll(driver);\n    }\n\n    @Test\n    @InterruptAfter(3)\n    void shouldFilterByChannelUri()\n    {\n        try (AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()))\n        {\n            final ArchiveSystemTests.RecordingResult result1 = recordData(\n                aeronArchive, 1, \"snapshot-id:10;complete=true;\");\n\n            final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n\n            assertEquals(1, aeronArchive.listRecordingsForUri(\n                0, Integer.MAX_VALUE, \"alias=snapshot-id:10;\", result1.streamId(), collector.reset()));\n\n            assertEquals(0, aeronArchive.listRecordingsForUri(\n                0, Integer.MAX_VALUE, \"alias=snapshot-id:1;\", result1.streamId(), collector.reset()));\n\n            assertEquals(1, aeronArchive.listRecordingsForUri(\n                0, Integer.MAX_VALUE, \"alias=snapshot-id:1\", result1.streamId(), collector.reset()));\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldUpdateChannelForARecording()\n    {\n        try (AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()))\n        {\n            final ArchiveSystemTests.RecordingResult result1 = recordData(\n                aeronArchive, 1, \"snapshot-id-in-progress:10;complete=true;\");\n\n            final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n\n            assertEquals(1, aeronArchive.listRecordingsForUri(\n                0, Integer.MAX_VALUE, \"snapshot-id-in-progress:\", result1.streamId(), collector.reset()));\n\n            assertEquals(1, collector.descriptors().size());\n            RecordingDescriptor recordingDescriptor = collector.descriptors().get(0);\n\n            final ChannelUri channel = ChannelUri.parse(recordingDescriptor.originalChannel());\n            channel.put(CommonContext.ALIAS_PARAM_NAME, \"snapshot-id:10;\");\n\n            aeronArchive.updateChannel(recordingDescriptor.recordingId(), channel.toString());\n\n            assertEquals(1, aeronArchive.listRecordingsForUri(\n                0, Integer.MAX_VALUE, \"snapshot-id:\", result1.streamId(), collector.reset()));\n\n            assertEquals(1, collector.descriptors().size());\n            recordingDescriptor = collector.descriptors().get(0);\n\n            assertThat(recordingDescriptor.originalChannel(), containsString(\"snapshot-id:10\"));\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldFailToUpdateChannelForARecordingThatDoesntExist()\n    {\n        try (AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()))\n        {\n            final ArchiveSystemTests.RecordingResult result1 = recordData(\n                aeronArchive, 1, \"snapshot-id-in-progress:10;complete=true;\");\n\n            final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n\n            assertEquals(1, aeronArchive.listRecordingsForUri(\n                0, Integer.MAX_VALUE, \"snapshot-id-in-progress:\", result1.streamId(), collector.reset()));\n\n            assertEquals(1, collector.descriptors().size());\n            final RecordingDescriptor recordingDescriptor = collector.descriptors().get(0);\n\n            final ChannelUri channel = ChannelUri.parse(recordingDescriptor.originalChannel());\n            channel.put(CommonContext.ALIAS_PARAM_NAME, \"snapshot-id:10;\");\n\n            final long invalidRecordingId = 98273498273498723L;\n\n            final ArchiveException archiveException = assertThrows(\n                ArchiveException.class,\n                () -> aeronArchive.updateChannel(invalidRecordingId, channel.toString()));\n            assertThat(archiveException.getMessage(), containsString(\"RECORDING_UNKNOWN\"));\n        }\n    }\n\n    @Test\n    @InterruptAfter(15)\n    @SlowTest\n    void shouldFailToUpdateChannelWhileARecordingListingIsRunning()\n    {\n        try (AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()))\n        {\n            final ArchiveSystemTests.RecordingResult result1 = recordData(\n                aeronArchive, 1, \"alias=original\");\n\n            final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n\n            assertEquals(1, aeronArchive.listRecordingsForUri(\n                0, Integer.MAX_VALUE, \"alias=original\", result1.streamId(), collector.reset()));\n\n            assertEquals(1, collector.descriptors().size());\n            final RecordingDescriptor recordingDescriptor = collector.descriptors().get(0);\n\n            for (int i = 0; i < 150; i++)\n            {\n                recordData(aeronArchive, 1, \"snapshot-id:\" + (i + 1));\n            }\n\n            assertTrue(aeronArchive.archiveProxy().listRecordings(\n                0,\n                Integer.MAX_VALUE,\n                aeronArchive.context().aeron().nextCorrelationId(),\n                aeronArchive.controlSessionId()));\n\n            final ChannelUri channel = ChannelUri.parse(recordingDescriptor.originalChannel());\n            channel.put(CommonContext.ALIAS_PARAM_NAME, \"alias=update\");\n\n            final ArchiveException archiveException = assertThrows(\n                ArchiveException.class,\n                () -> aeronArchive.updateChannel(recordingDescriptor.recordingId(), channel.toString()));\n            assertThat(archiveException.getMessage(), containsString(\"active listing already in progress\"));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveReplayTest.java",
    "content": "/*\n * Copyright 2014-2023 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.client.ReplayParams;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.AeronArchive.REPLAY_ALL_AND_FOLLOW;\nimport static io.aeron.archive.client.AeronArchive.REPLAY_ALL_AND_STOP;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@ExtendWith({ InterruptingTestCallback.class, EventLogExtension.class })\npublic class ArchiveReplayTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher()\n        .ignoreErrorsMatching(s -> s.contains(\"response publication is closed\"));\n\n    private TestMediaDriver driver;\n    private Archive archive;\n\n    @BeforeEach\n    void setUp()\n    {\n        final int termLength = 64 * 1024;\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .termBufferSparseFile(true)\n            .publicationTermBufferLength(termLength)\n            .sharedIdleStrategy(YieldingIdleStrategy.INSTANCE)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true);\n\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .aeronDirectoryName(driverCtx.aeronDirectoryName())\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .controlChannel(\"aeron:udp?endpoint=localhost:10001\")\n            .catalogCapacity(ArchiveSystemTests.CATALOG_CAPACITY)\n            .fileSyncLevel(0)\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"archive-test\"))\n            .segmentFileLength(1024 * 1024)\n            .idleStrategySupplier(YieldingIdleStrategy::new);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n\n        archive = Archive.launch(archiveContext);\n        systemTestWatcher.dataCollector().add(archiveContext.archiveDir());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(archive, driver);\n    }\n\n    @Test\n    void shouldNotErrorOnReplayThatHasAlreadyStopped()\n    {\n        try (AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()))\n        {\n            final ArchiveSystemTests.RecordingResult recordingResult = ArchiveSystemTests.recordData(aeronArchive);\n\n            final Aeron aeron = aeronArchive.context().aeron();\n            final int replayStreamId = 10001;\n\n            final long replaySessionId = aeronArchive.startReplay(\n                recordingResult.recordingId(), IPC_CHANNEL, replayStreamId, new ReplayParams());\n\n            final String replayChannel = ChannelUri.addSessionId(IPC_CHANNEL, (int)replaySessionId);\n            final Subscription replay = aeron.addSubscription(replayChannel, replayStreamId);\n\n            final MutableLong replayPosition = new MutableLong();\n            while (replayPosition.get() < recordingResult.position() / 2)\n            {\n                if (0 == replay.poll((buffer, offset, length, header) -> replayPosition.set(header.position()), 10))\n                {\n                    aeronArchive.checkForErrorResponse();\n                    Tests.yield();\n                }\n            }\n\n            CloseHelper.quietClose(replay);\n            aeronArchive.stopReplay(replaySessionId);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldExitOnEmptyRecording()\n    {\n        try (AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()))\n        {\n            final Aeron aeron = aeronArchive.context().aeron();\n            final CountersReader countersReader = aeron.countersReader();\n            final long recordingId;\n            final int recordingCounterId;\n            try (Publication publication = aeronArchive.addRecordedPublication(\"aeron:ipc\", 10000))\n            {\n                recordingCounterId = Tests.awaitRecordingCounterId(\n                    countersReader, publication.sessionId(), aeronArchive.archiveId());\n                recordingId = RecordingPos.getRecordingId(countersReader, recordingCounterId);\n            }\n\n            while (CountersReader.RECORD_ALLOCATED == countersReader.getCounterState(recordingCounterId))\n            {\n                Tests.yield();\n            }\n\n            assertEquals(0, aeronArchive.getStopPosition(recordingId));\n\n            final int replayStreamId = 10001;\n\n            try\n            {\n                aeronArchive.startReplay(\n                    recordingId,\n                    CommonContext.IPC_CHANNEL,\n                    replayStreamId,\n                    new ReplayParams().position(AeronArchive.NULL_POSITION).length(AeronArchive.REPLAY_ALL_AND_STOP));\n\n                fail(\"Should have thrown exception\");\n            }\n            catch (final ArchiveException ex)\n            {\n                assertEquals(ArchiveException.EMPTY_RECORDING, ex.errorCode());\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldExitOnNonEmptyLiveRecording()\n    {\n        try (AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()))\n        {\n            final Aeron aeron = aeronArchive.context().aeron();\n            final long recordingId;\n            try (Publication publication = aeronArchive.addRecordedPublication(\"aeron:ipc\", 10000))\n            {\n                final int recordingCounterId = Tests.awaitRecordingCounterId(\n                    aeron.countersReader(), publication.sessionId(), aeronArchive.archiveId());\n                recordingId = RecordingPos.getRecordingId(aeron.countersReader(), recordingCounterId);\n\n                writeMessages(publication, \"this is a test message\", 1);\n                awaitRecordingPosition(aeronArchive, recordingId, publication.position());\n\n                final int replayStreamId = 10001;\n\n                final long replaySessionId = aeronArchive.startReplay(\n                    recordingId,\n                    IPC_CHANNEL,\n                    replayStreamId,\n                    new ReplayParams().position(NULL_POSITION).length(REPLAY_ALL_AND_STOP));\n\n                final String replayChannel = ChannelUri.addSessionId(IPC_CHANNEL, (int)replaySessionId);\n                final AtomicReference<Image> availableImage = new AtomicReference<>();\n                final AtomicReference<Image> unavailableImage = new AtomicReference<>();\n                final Subscription replay = aeron.addSubscription(\n                    replayChannel, replayStreamId, availableImage::set, unavailableImage::set);\n\n                Image image;\n                while (null == (image = availableImage.get()))\n                {\n                    aeronArchive.checkForErrorResponse();\n                    Tests.yield();\n                }\n\n                final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> {};\n                while (!image.isEndOfStream())\n                {\n                    if (0 == image.poll(fragmentHandler, 100))\n                    {\n                        aeronArchive.checkForErrorResponse();\n                        Tests.yield();\n                    }\n                }\n\n                while (image != unavailableImage.get())\n                {\n                    Tests.yield();\n                }\n\n                CloseHelper.quietClose(replay);\n                aeronArchive.stopReplay(replaySessionId);\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldExitOnEmptyLiveRecording()\n    {\n        try (AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()))\n        {\n            final Aeron aeron = aeronArchive.context().aeron();\n            try (Publication publication = aeronArchive.addRecordedPublication(\"aeron:ipc\", 10000))\n            {\n                final int recordingCounterId = Tests.awaitRecordingCounterId(\n                    aeron.countersReader(), publication.sessionId(), aeronArchive.archiveId());\n                final long recordingId = RecordingPos.getRecordingId(aeron.countersReader(), recordingCounterId);\n\n                final int replayStreamId = 10001;\n\n                try\n                {\n                    aeronArchive.startReplay(\n                        recordingId,\n                        IPC_CHANNEL,\n                        replayStreamId,\n                        new ReplayParams().position(NULL_POSITION).length(REPLAY_ALL_AND_STOP));\n\n                    fail(\"Should have thrown exception\");\n                }\n                catch (final ArchiveException ex)\n                {\n                    assertEquals(ArchiveException.EMPTY_RECORDING, ex.errorCode());\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldNotExitWhenFollowingAnEmptyLiveRecording()\n    {\n        try (AeronArchive aeronArchive = AeronArchive.connect(TestContexts.ipcAeronArchive()))\n        {\n            final Aeron aeron = aeronArchive.context().aeron();\n            final long recordingId;\n            try (Publication publication = aeronArchive.addRecordedPublication(\"aeron:ipc\", 10000))\n            {\n                final int recordingCounterId = Tests.awaitRecordingCounterId(\n                    aeron.countersReader(), publication.sessionId(), aeronArchive.archiveId());\n                recordingId = RecordingPos.getRecordingId(aeron.countersReader(), recordingCounterId);\n\n                final int replayStreamId = 10001;\n\n                final long replaySessionId = aeronArchive.startReplay(\n                    recordingId,\n                    IPC_CHANNEL,\n                    replayStreamId,\n                    new ReplayParams().position(NULL_POSITION).length(REPLAY_ALL_AND_FOLLOW));\n\n                final String replayChannel = ChannelUri.addSessionId(IPC_CHANNEL, (int)replaySessionId);\n                final AtomicReference<Image> availableImage = new AtomicReference<>();\n                final AtomicReference<Image> unavailableImage = new AtomicReference<>();\n                final Subscription replay = aeron.addSubscription(\n                    replayChannel, replayStreamId, availableImage::set, unavailableImage::set);\n\n                Image image;\n                while (null == (image = availableImage.get()))\n                {\n                    aeronArchive.checkForErrorResponse();\n                    Tests.yield();\n                }\n\n                final long deadlineNs = System.nanoTime() + TimeUnit.SECONDS.toNanos(1);\n                while (System.nanoTime() < deadlineNs)\n                {\n                    assertFalse(image.isEndOfStream());\n                    aeronArchive.checkForErrorResponse();\n                    Tests.yield();\n                }\n\n                assertNull(unavailableImage.get());\n\n                CloseHelper.quietClose(replay);\n                aeronArchive.stopReplay(replaySessionId);\n            }\n        }\n    }\n\n    private static void awaitRecordingPosition(\n        final AeronArchive aeronArchive,\n        final long recordingId,\n        final long position)\n    {\n        while (aeronArchive.getMaxRecordedPosition(recordingId) < position)\n        {\n            Tests.yield();\n            aeronArchive.checkForErrorResponse();\n        }\n    }\n\n    private static void writeMessages(final Publication publication, final String thisIsATestMessage, final int count)\n    {\n        final UnsafeBuffer unsafeBuffer = new UnsafeBuffer(thisIsATestMessage.getBytes());\n        for (int i = 0; i < count; i++)\n        {\n            while (publication.offer(unsafeBuffer, 0, unsafeBuffer.capacity()) < 0)\n            {\n                Tests.yield();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveResponseClientFailuresTest.java",
    "content": "/*\n * Copyright 2014-2023 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ReplayParams;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.test.AdjustableClock;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.AeronCounters.ARCHIVE_CONTROL_SESSIONS_TYPE_ID;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith(InterruptingTestCallback.class)\npublic class ArchiveResponseClientFailuresTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private final AdjustableClock adjustableClock = new AdjustableClock();\n    private TestMediaDriver driver;\n    private Archive archive;\n\n    @BeforeEach\n    void setUp()\n    {\n        final int termLength = 64 * 1024;\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .termBufferSparseFile(true)\n            .publicationTermBufferLength(termLength)\n            .sharedIdleStrategy(YieldingIdleStrategy.INSTANCE)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true);\n\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .epochClock(adjustableClock)\n            .nanoClock(adjustableClock)\n            .aeronDirectoryName(driverCtx.aeronDirectoryName())\n            .controlChannel(\"aeron:udp?endpoint=localhost:10001\")\n            .catalogCapacity(ArchiveSystemTests.CATALOG_CAPACITY)\n            .connectTimeoutNs(TimeUnit.SECONDS.toNanos(1))\n            .fileSyncLevel(0)\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"archive-test\"))\n            .segmentFileLength(1024 * 1024)\n            .idleStrategySupplier(YieldingIdleStrategy::new);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n\n        archive = Archive.launch(archiveContext);\n        systemTestWatcher.dataCollector().add(archiveContext.archiveDir());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietCloseAll(archive);\n        CloseHelper.quietCloseAll(driver);\n    }\n\n    @Test\n    @InterruptAfter(15)\n    void shouldTimeoutReplyTokenChannel()\n    {\n        systemTestWatcher.ignoreErrorsMatching(s -> s.contains(\"Unknown session or token timeout\"));\n\n        final AeronArchive.Context aeronArchiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(archive.context().controlChannel())\n            .controlResponseChannel(\"aeron:udp?control-mode=response|control=localhost:10002\");\n\n        AeronArchive aeronArchive = null;\n        try (AeronArchive.AsyncConnect asyncConnect = AeronArchive.asyncConnect(aeronArchiveCtx))\n        {\n            do\n            {\n                aeronArchive = asyncConnect.poll();\n            }\n            while (null == aeronArchive);\n\n            final ArchiveSystemTests.RecordingResult recordingResult = ArchiveSystemTests.recordData(aeronArchive);\n\n            final long tokenCorrelationId = aeronArchive.context().aeron().nextCorrelationId();\n            aeronArchive.archiveProxy().requestReplayToken(\n                tokenCorrelationId, aeronArchive.controlSessionId(), recordingResult.recordingId());\n            while (0 == aeronArchive.controlResponsePoller().poll())\n            {\n                Tests.yield();\n            }\n\n            assertTrue(aeronArchive.controlResponsePoller().isPollComplete());\n            final long replayToken = aeronArchive.controlResponsePoller().relevantId();\n\n            final long timeoutNs = archive.context().connectTimeoutNs();\n            final long incrementNs = TimeUnit.MILLISECONDS.toNanos(500);\n            adjustableClock.slewTimeDelta(timeoutNs, incrementNs);\n\n            final long replayCorrelationId = aeronArchive.context().aeron().nextCorrelationId();\n            final ReplayParams replayParams = new ReplayParams();\n            replayParams.replayToken(replayToken);\n\n            assertTrue(aeronArchive.archiveProxy().replay(\n                recordingResult.recordingId(),\n                \"aeron:udp?control-mode=response|control=localhost:10002\",\n                10001,\n                replayParams,\n                replayCorrelationId,\n                aeronArchive.controlSessionId()));\n\n            final long deadlineMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1);\n            while (System.currentTimeMillis() < deadlineMs && 0 == archive.context().errorCounter().get())\n            {\n                aeronArchive.controlResponsePoller().poll();\n            }\n\n            assertThat(archive.context().errorCounter().get(), greaterThan(0L));\n        }\n        finally\n        {\n            CloseHelper.quietClose(aeronArchive);\n        }\n    }\n\n    @Test\n    @InterruptAfter(15)\n    void shouldRemoveTokenOnAeronArchiveClose()\n    {\n        systemTestWatcher.ignoreErrorsMatching(s -> s.contains(\"Unknown session or token timeout\"));\n\n        final AeronArchive.Context aeronArchiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(archive.context().controlChannel())\n            .controlResponseChannel(\"aeron:udp?control-mode=response|control=localhost:10002\");\n\n        AeronArchive aeronArchive = null;\n        try (AeronArchive.AsyncConnect asyncConnect = AeronArchive.asyncConnect(aeronArchiveCtx))\n        {\n            do\n            {\n                aeronArchive = asyncConnect.poll();\n            }\n            while (null == aeronArchive);\n\n            final ArchiveSystemTests.RecordingResult recordingResult = ArchiveSystemTests.recordData(aeronArchive);\n\n            final Aeron aeron = aeronArchive.context().aeron();\n            final long tokenCorrelationId = aeron.nextCorrelationId();\n            aeronArchive.archiveProxy().requestReplayToken(\n                tokenCorrelationId, aeronArchive.controlSessionId(), recordingResult.recordingId());\n            while (0 == aeronArchive.controlResponsePoller().poll())\n            {\n                Tests.yield();\n            }\n\n            assertTrue(aeronArchive.controlResponsePoller().isPollComplete());\n            final long replayToken = aeronArchive.controlResponsePoller().relevantId();\n            final long replayCorrelationId = aeron.nextCorrelationId();\n\n            final int sessionCounterId = ArchiveCounters.find(\n                aeron.countersReader(),\n                ARCHIVE_CONTROL_SESSIONS_TYPE_ID,\n                archive.context().archiveId());\n\n            final long numSessions = aeron.countersReader().getCounterValue(sessionCounterId);\n\n            aeronArchive.archiveProxy().closeSession(aeronArchive.controlSessionId());\n\n            while (numSessions <= aeron.countersReader().getCounterValue(sessionCounterId))\n            {\n                Tests.yield();\n            }\n\n            final ReplayParams replayParams = new ReplayParams();\n            replayParams.replayToken(replayToken);\n\n            assertTrue(aeronArchive.archiveProxy().replay(\n                recordingResult.recordingId(),\n                \"aeron:udp?control-mode=response|control=localhost:10002\",\n                10001,\n                replayParams,\n                replayCorrelationId,\n                aeronArchive.controlSessionId()));\n\n            final long deadlineMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1);\n            while (System.currentTimeMillis() < deadlineMs && 0 == archive.context().errorCounter().get())\n            {\n                final int poll = aeronArchive.controlResponsePoller().poll();\n                assertEquals(0, poll);\n            }\n\n            assertThat(archive.context().errorCounter().get(), greaterThan(0L));\n        }\n        finally\n        {\n            CloseHelper.close(aeronArchive);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveResponseClientTest.java",
    "content": "/*\n * Copyright 2014-2023 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.Counter;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ReplayParams;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\n\nimport static io.aeron.CommonContext.RESPONSE_CORRELATION_ID_PARAM_NAME;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class ArchiveResponseClientTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Archive archive;\n\n    @BeforeEach\n    void setUp()\n    {\n        final int termLength = 64 * 1024;\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .termBufferSparseFile(true)\n            .publicationTermBufferLength(termLength)\n            .sharedIdleStrategy(YieldingIdleStrategy.INSTANCE)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true);\n\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .aeronDirectoryName(driverCtx.aeronDirectoryName())\n            .controlChannel(\"aeron:udp?endpoint=localhost:10001\")\n            .catalogCapacity(ArchiveSystemTests.CATALOG_CAPACITY)\n            .fileSyncLevel(0)\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"archive-test\"))\n            .segmentFileLength(1024 * 1024)\n            .idleStrategySupplier(YieldingIdleStrategy::new);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n\n        archive = Archive.launch(archiveContext);\n        systemTestWatcher.dataCollector().add(archiveContext.archiveDir());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.closeAll(archive, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReplayUsingResponseChannel()\n    {\n        final AeronArchive.Context aeronArchiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(archive.context().controlChannel())\n            .controlResponseChannel(\"aeron:udp?control-mode=response|control=localhost:10002\");\n        try (AeronArchive aeronArchive = AeronArchive.connect(aeronArchiveCtx))\n        {\n            final ArchiveSystemTests.RecordingResult recordingResult = ArchiveSystemTests.recordData(aeronArchive);\n\n            final Subscription replay = aeronArchive.replay(\n                recordingResult.recordingId(), \"aeron:udp?control-mode=response|control=localhost:10002\", 10001,\n                new ReplayParams());\n\n            final MutableLong replayPosition = new MutableLong();\n            while (replayPosition.get() < recordingResult.position())\n            {\n                if (0 == replay.poll((buffer, offset, length, header) -> replayPosition.set(header.position()), 10))\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldBoundedReplayUsingResponseChannel()\n    {\n        final AeronArchive.Context aeronArchiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(archive.context().controlChannel())\n            .controlResponseChannel(\"aeron:udp?control-mode=response|control=localhost:10002\");\n        try (AeronArchive aeronArchive = AeronArchive.connect(aeronArchiveCtx))\n        {\n            final ArchiveSystemTests.RecordingResult recordingResult = ArchiveSystemTests.recordData(aeronArchive);\n            final Counter testBoundedCounter = aeronArchive.context().aeron().addCounter(10001, \"test bounded counter\");\n            testBoundedCounter.set(recordingResult.halfwayPosition());\n\n            final ReplayParams replayParams = new ReplayParams();\n            replayParams.boundingLimitCounterId(testBoundedCounter.id());\n\n            final Subscription replay = aeronArchive.replay(\n                recordingResult.recordingId(),\n                \"aeron:udp?control-mode=response|control=localhost:10002\",\n                10001,\n                replayParams);\n\n            final MutableLong replayPosition = new MutableLong();\n            while (replayPosition.get() < recordingResult.halfwayPosition())\n            {\n                if (0 == replay.poll((buffer, offset, length, header) -> replayPosition.set(header.position()), 10))\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldStartReplayUsingResponseChannel()\n    {\n        final String responseChannel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n        final int replayStreamId = 10001;\n\n        final AeronArchive.Context aeronArchiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(archive.context().controlChannel())\n            .controlResponseChannel(responseChannel);\n        try (AeronArchive aeronArchive = AeronArchive.connect(aeronArchiveCtx);\n            Subscription replay = aeronArchive.context().aeron().addSubscription(responseChannel, replayStreamId))\n        {\n            final ArchiveSystemTests.RecordingResult recordingResult = ArchiveSystemTests.recordData(aeronArchive);\n            final ReplayParams replayParams = new ReplayParams();\n            replayParams.subscriptionRegistrationId(replay.registrationId());\n\n            aeronArchive.startReplay(recordingResult.recordingId(), responseChannel, replayStreamId, replayParams);\n\n            final MutableLong replayPosition = new MutableLong();\n            while (replayPosition.get() < recordingResult.position())\n            {\n                if (0 == replay.poll((buffer, offset, length, header) -> replayPosition.set(header.position()), 10))\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldStartBoundedReplayUsingResponseChannel()\n    {\n        final String responseChannel = \"aeron:udp?control-mode=response|control=localhost:10002\";\n        final int replayStreamId = 10001;\n\n        final AeronArchive.Context aeronArchiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(archive.context().controlChannel())\n            .controlResponseChannel(responseChannel);\n\n        try (AeronArchive aeronArchive = AeronArchive.connect(aeronArchiveCtx);\n            Subscription replay = aeronArchive.context().aeron().addSubscription(responseChannel, replayStreamId))\n        {\n            final ArchiveSystemTests.RecordingResult recordingResult = ArchiveSystemTests.recordData(aeronArchive);\n            final Counter testBoundedCounter = aeronArchive.context().aeron().addCounter(10001, \"test bounded counter\");\n            testBoundedCounter.set(recordingResult.halfwayPosition());\n\n            final ReplayParams replayParams = new ReplayParams();\n            replayParams\n                .boundingLimitCounterId(testBoundedCounter.id())\n                .subscriptionRegistrationId(replay.registrationId());\n\n            aeronArchive.startReplay(recordingResult.recordingId(), responseChannel, replayStreamId, replayParams);\n\n            final MutableLong replayPosition = new MutableLong();\n            while (replayPosition.get() < recordingResult.halfwayPosition())\n            {\n                if (0 == replay.poll((buffer, offset, length, header) -> replayPosition.set(header.position()), 10))\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"aeron:udp?control-mode=response|control=localhost:10002\",\n        \"aeron:udp?endpoint=localhost:10002\"\n    })\n    @InterruptAfter(10)\n    void shouldAsyncConnectUsingResponseChannel(final String responseChannel)\n    {\n        final AeronArchive.Context aeronArchiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(archive.context().controlChannel())\n            .controlResponseChannel(responseChannel);\n        try (AeronArchive.AsyncConnect asyncConnect = AeronArchive.asyncConnect(aeronArchiveCtx))\n        {\n            AeronArchive aeronArchive;\n            while (null == (aeronArchive = asyncConnect.poll()))\n            {\n                Tests.yield();\n            }\n\n            try\n            {\n                assertNotEquals(Aeron.NULL_VALUE, aeronArchive.controlSessionId());\n                assertEquals(archive.context().archiveId(), aeronArchive.archiveId());\n            }\n            finally\n            {\n                CloseHelper.close(aeronArchive);\n            }\n        }\n    }\n\n    @Test\n    void shouldConnectUsingLocalIpcResponseChannels()\n    {\n        final AeronArchive.Context context = new AeronArchive.Context()\n            .controlRequestChannel(archive.context().localControlChannel())\n            .controlRequestStreamId(archive.context().localControlStreamId())\n            .controlResponseChannel(\"aeron:ipc?control-mode=response\");\n        try (AeronArchive aeronArchive = AeronArchive.connect(context))\n        {\n            final ChannelUri requestChannel = ChannelUri.parse(context.controlRequestChannel());\n            assertEquals(\n                Long.toString(aeronArchive.controlResponsePoller().subscription().registrationId()),\n                requestChannel.get(RESPONSE_CORRELATION_ID_PARAM_NAME));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveSystemTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveProxy;\nimport io.aeron.archive.client.ControlResponseAdapter;\nimport io.aeron.archive.client.RecordingEventsAdapter;\nimport io.aeron.archive.client.ReplayParams;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.IoUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.extension.TestWatcher;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.File;\nimport java.util.Random;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.stream.Stream;\n\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ArchiveSystemTest\n{\n    private static Stream<Arguments> threadingModes()\n    {\n        return Stream.of(\n            arguments(ThreadingMode.SHARED, ArchiveThreadingMode.SHARED),\n            arguments(ThreadingMode.SHARED_NETWORK, ArchiveThreadingMode.SHARED),\n            arguments(ThreadingMode.DEDICATED, ArchiveThreadingMode.DEDICATED));\n    }\n\n    private static final String CONTROL_RESPONSE_URI = CommonContext.IPC_CHANNEL;\n    private static final int CONTROL_RESPONSE_STREAM_ID = AeronArchive.Configuration.controlResponseStreamId();\n    private static final String REPLAY_URI = CommonContext.IPC_CHANNEL;\n    private static final int MESSAGE_COUNT = 5000;\n    private static final int SYNC_LEVEL = 0;\n    private static final int PUBLISH_STREAM_ID = 1033;\n    private static final int MAX_FRAGMENT_SIZE = 1024;\n    private static final int REPLAY_STREAM_ID = 101;\n    private final Random rnd = new Random();\n    private final long seed = System.nanoTime();\n\n    @RegisterExtension\n    final TestWatcher randomSeedWatcher = Tests.seedWatcher(seed);\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private long controlSessionId = -1;\n    private String publishUri;\n    private Aeron client;\n    private Archive archive;\n    private TestMediaDriver driver;\n    private long recordingId;\n    private long remaining;\n    private int messageCount;\n    private int[] messageLengths;\n    private long totalDataLength;\n    private long requestedStartPosition;\n\n    private Subscription controlResponse;\n    private int requestedInitialTermId;\n\n    private Thread replayConsumer = null;\n    private Thread progressTracker = null;\n\n    private volatile long recorded = 0;\n    private volatile long totalRecordingLength;\n    private volatile long startPosition;\n    private volatile long stopPosition = NULL_POSITION;\n    private volatile Throwable trackerError;\n\n    private void before(final ThreadingMode threadingMode, final ArchiveThreadingMode archiveThreadingMode)\n    {\n        if (threadingMode == ThreadingMode.INVOKER)\n        {\n            TestMediaDriver.notSupportedOnCMediaDriver(\"C driver does not integrate with Java Invoker\");\n        }\n\n        IoUtil.delete(new File(CommonContext.getAeronDirectoryName()), false);\n        rnd.setSeed(seed);\n        requestedInitialTermId = rnd.nextInt(1234);\n\n        final int termLength = 1 << (16 + rnd.nextInt(10)); // 1M to 8M\n        final int mtu = 1 << (10 + rnd.nextInt(3)); // 1024 to 8096\n        final int requestedStartTermOffset = BitUtil.align(rnd.nextInt(termLength), FrameDescriptor.FRAME_ALIGNMENT);\n        final int requestedStartTermId = requestedInitialTermId + rnd.nextInt(1000);\n        final int segmentFileLength = termLength << rnd.nextInt(4);\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .termBufferSparseFile(true)\n            .threadingMode(threadingMode)\n            .sharedIdleStrategy(YieldingIdleStrategy.INSTANCE)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true);\n\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .catalogCapacity(ArchiveSystemTests.CATALOG_CAPACITY)\n            .fileSyncLevel(SYNC_LEVEL)\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"archive-test\"))\n            .segmentFileLength(segmentFileLength)\n            .threadingMode(archiveThreadingMode)\n            .recordingEventsChannel(\"aeron:udp?control-mode=dynamic|control=localhost:8030\")\n            .recordingEventsEnabled(true)\n            .idleStrategySupplier(YieldingIdleStrategy::new);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n\n        if (threadingMode == ThreadingMode.INVOKER)\n        {\n            archiveContext.mediaDriverAgentInvoker(driver.sharedAgentInvoker());\n        }\n\n        archive = Archive.launch(archiveContext);\n        systemTestWatcher.dataCollector().add(archiveContext.archiveDir());\n\n        client = Aeron.connect();\n\n        requestedStartPosition =\n            ((requestedStartTermId - requestedInitialTermId) * (long)termLength) + requestedStartTermOffset;\n\n        publishUri = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"localhost:24325\")\n            .termLength(termLength)\n            .mtu(mtu)\n            .initialTermId(requestedInitialTermId)\n            .termId(requestedStartTermId)\n            .termOffset(requestedStartTermOffset)\n            .build();\n    }\n\n    @AfterEach\n    void after() throws Exception\n    {\n        try\n        {\n            if (null != replayConsumer)\n            {\n                replayConsumer.interrupt();\n                replayConsumer.join();\n            }\n\n            if (null != progressTracker)\n            {\n                progressTracker.interrupt();\n                progressTracker.join();\n            }\n        }\n        finally\n        {\n            CloseHelper.closeAll(client, archive, driver);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"threadingModes\")\n    @InterruptAfter(10)\n    void recordAndReplayExclusivePublication(\n        final ThreadingMode threadingMode, final ArchiveThreadingMode archiveThreadingMode)\n    {\n        before(threadingMode, archiveThreadingMode);\n\n        final String controlChannel = archive.context().localControlChannel();\n        final int controlStreamId = archive.context().localControlStreamId();\n\n        final String recordingChannel = archive.context().recordingEventsChannel();\n        final int recordingStreamId = archive.context().recordingEventsStreamId();\n\n        final ExclusivePublication controlPublication = client.addExclusivePublication(controlChannel, controlStreamId);\n        final Subscription recordingEvents = client.addSubscription(recordingChannel, recordingStreamId);\n        final ArchiveProxy archiveProxy = new ArchiveProxy(controlPublication);\n\n        prePublicationActionsAndVerifications(archiveProxy, controlPublication, recordingEvents);\n\n        final ExclusivePublication recordedPublication = client.addExclusivePublication(publishUri, PUBLISH_STREAM_ID);\n\n        final int sessionId = recordedPublication.sessionId();\n        final int termBufferLength = recordedPublication.termBufferLength();\n        final int initialTermId = recordedPublication.initialTermId();\n        final int maxPayloadLength = recordedPublication.maxPayloadLength();\n        final long startPosition = recordedPublication.position();\n\n        assertEquals(requestedStartPosition, startPosition);\n        assertEquals(requestedInitialTermId, recordedPublication.initialTermId());\n        preSendChecks(archiveProxy, recordingEvents, sessionId, termBufferLength, startPosition);\n\n        prepAndSendMessages(recordingEvents, recordedPublication);\n\n        postPublicationValidations(\n            archiveProxy, recordingEvents, termBufferLength, initialTermId, maxPayloadLength);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"threadingModes\")\n    @InterruptAfter(10)\n    void replayExclusivePublicationWhileRecording(\n        final ThreadingMode threadingMode, final ArchiveThreadingMode archiveThreadingMode)\n    {\n        before(threadingMode, archiveThreadingMode);\n\n        final String controlChannel = archive.context().localControlChannel();\n        final int controlStreamId = archive.context().localControlStreamId();\n\n        final String recordingChannel = archive.context().recordingEventsChannel();\n        final int recordingStreamId = archive.context().recordingEventsStreamId();\n\n        final ExclusivePublication controlPublication = client.addExclusivePublication(controlChannel, controlStreamId);\n        final Subscription recordingEvents = client.addSubscription(recordingChannel, recordingStreamId);\n        final ArchiveProxy archiveProxy = new ArchiveProxy(controlPublication);\n\n        prePublicationActionsAndVerifications(archiveProxy, controlPublication, recordingEvents);\n\n        final ExclusivePublication recordedPublication = client.addExclusivePublication(publishUri, PUBLISH_STREAM_ID);\n\n        final int sessionId = recordedPublication.sessionId();\n        final int termBufferLength = recordedPublication.termBufferLength();\n        final int initialTermId = recordedPublication.initialTermId();\n        final int maxPayloadLength = recordedPublication.maxPayloadLength();\n        final long startPosition = recordedPublication.position();\n\n        assertEquals(requestedStartPosition, startPosition);\n        assertEquals(requestedInitialTermId, recordedPublication.initialTermId());\n        preSendChecks(archiveProxy, recordingEvents, sessionId, termBufferLength, startPosition);\n\n        final CountDownLatch latch = new CountDownLatch(2);\n\n        prepMessagesAndListener(recordingEvents, latch);\n        replayConsumer = validateActiveRecordingReplay(\n            archiveProxy, termBufferLength, initialTermId, maxPayloadLength, latch);\n\n        publishDataToBeRecorded(recordedPublication);\n        await(latch);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"threadingModes\")\n    @InterruptAfter(10)\n    void recordAndReplayConcurrentPublication(\n        final ThreadingMode threadingMode, final ArchiveThreadingMode archiveThreadingMode)\n    {\n        before(threadingMode, archiveThreadingMode);\n\n        final ExclusivePublication controlPublication = client.addExclusivePublication(\n            archive.context().localControlChannel(), archive.context().localControlStreamId());\n        final ArchiveProxy archiveProxy = new ArchiveProxy(controlPublication);\n\n        final Subscription recordingEvents = client.addSubscription(\n            archive.context().recordingEventsChannel(), archive.context().recordingEventsStreamId());\n        prePublicationActionsAndVerifications(archiveProxy, controlPublication, recordingEvents);\n\n        final Publication recordedPublication = client.addExclusivePublication(publishUri, PUBLISH_STREAM_ID);\n        final int termBufferLength = recordedPublication.termBufferLength();\n        final long startPosition = recordedPublication.position();\n\n        preSendChecks(archiveProxy, recordingEvents, recordedPublication.sessionId(), termBufferLength, startPosition);\n        prepAndSendMessages(recordingEvents, recordedPublication);\n\n        postPublicationValidations(\n            archiveProxy,\n            recordingEvents,\n            termBufferLength,\n            recordedPublication.initialTermId(),\n            recordedPublication.maxPayloadLength());\n    }\n\n    private void preSendChecks(\n        final ArchiveProxy archiveProxy,\n        final Subscription recordingEvents,\n        final int sessionId,\n        final int termBufferLength,\n        final long startPosition)\n    {\n        final MutableBoolean isRecordingStarted = new MutableBoolean();\n        final RecordingEventsAdapter recordingEventsAdapter = new RecordingEventsAdapter(\n            new FailRecordingEventsListener()\n            {\n                public void onStart(\n                    final long recordingId,\n                    final long startPosition0,\n                    final int sessionId0,\n                    final int streamId,\n                    final String channel,\n                    final String sourceIdentity)\n                {\n                    ArchiveSystemTest.this.recordingId = recordingId;\n                    assertEquals(PUBLISH_STREAM_ID, streamId);\n                    assertEquals(sessionId, sessionId0);\n                    assertEquals(startPosition, startPosition0);\n                    isRecordingStarted.set(true);\n                }\n            },\n            recordingEvents,\n            1);\n\n        while (!isRecordingStarted.get())\n        {\n            if (recordingEventsAdapter.poll() == 0)\n            {\n                if (!recordingEvents.isConnected())\n                {\n                    throw new IllegalStateException(\"recording events not connected\");\n                }\n\n                Tests.yield();\n            }\n        }\n\n        verifyDescriptorListOngoingArchive(archiveProxy, termBufferLength);\n    }\n\n    private void postPublicationValidations(\n        final ArchiveProxy archiveProxy,\n        final Subscription recordingEvents,\n        final int termBufferLength,\n        final int initialTermId,\n        final int maxPayloadLength)\n    {\n        verifyDescriptorListOngoingArchive(archiveProxy, termBufferLength);\n        assertNull(trackerError);\n\n        final long requestCorrelationId = client.nextCorrelationId();\n        if (!archiveProxy.stopRecording(publishUri, PUBLISH_STREAM_ID, requestCorrelationId, controlSessionId))\n        {\n            throw new IllegalStateException(\"failed to send stop recording\");\n        }\n\n        ArchiveTests.awaitOk(controlResponse, requestCorrelationId);\n\n        final MutableBoolean isRecordingStopped = new MutableBoolean();\n        final RecordingEventsAdapter recordingEventsAdapter = new RecordingEventsAdapter(\n            new FailRecordingEventsListener()\n            {\n                public void onStop(final long id, final long startPosition, final long stopPosition)\n                {\n                    assertEquals(recordingId, id);\n                    isRecordingStopped.set(true);\n                }\n            },\n            recordingEvents,\n            1);\n\n        while (!isRecordingStopped.get())\n        {\n            if (recordingEventsAdapter.poll() == 0)\n            {\n                Tests.yield();\n            }\n        }\n        final Archive.Context context = archive.context();\n        Tests.await(() -> context.totalWriteBytesCounter().get() >= totalDataLength);\n        final long totalWriteTimeNs = context.totalWriteTimeCounter().get();\n        assertNotEquals(0, totalWriteTimeNs);\n        final long maxWriteTimeNs = context.maxWriteTimeCounter().get();\n        assertNotEquals(0, maxWriteTimeNs);\n        assertTrue(totalWriteTimeNs > maxWriteTimeNs);\n\n        verifyDescriptorListOngoingArchive(archiveProxy, termBufferLength);\n        validateArchiveFile(recordingId);\n        validateReplay(archiveProxy, initialTermId, maxPayloadLength, termBufferLength);\n    }\n\n    private void prePublicationActionsAndVerifications(\n        final ArchiveProxy archiveProxy, final Publication controlPublication, final Subscription recordingEvents)\n    {\n        Tests.awaitConnected(controlPublication);\n        Tests.awaitConnected(recordingEvents);\n        Tests.awaitCounterDelta(client.countersReader(), SystemCounterDescriptor.HEARTBEATS_RECEIVED.id(), 2);\n\n        controlResponse = client.addSubscription(CONTROL_RESPONSE_URI, CONTROL_RESPONSE_STREAM_ID);\n        final long connectCorrelationId = client.nextCorrelationId();\n        assertTrue(archiveProxy.connect(CONTROL_RESPONSE_URI, CONTROL_RESPONSE_STREAM_ID, connectCorrelationId));\n\n        ArchiveTests.awaitConnectResponse(\n            controlResponse, connectCorrelationId, (sessionId) -> controlSessionId = sessionId);\n        verifyEmptyDescriptorList(archiveProxy);\n\n        final long startRecordingCorrelationId = client.nextCorrelationId();\n        if (!archiveProxy.startRecording(\n            publishUri,\n            PUBLISH_STREAM_ID,\n            SourceLocation.LOCAL,\n            startRecordingCorrelationId,\n            controlSessionId))\n        {\n            throw new IllegalStateException(\"failed to start recording\");\n        }\n\n        ArchiveTests.awaitOk(controlResponse, startRecordingCorrelationId);\n    }\n\n    private void verifyEmptyDescriptorList(final ArchiveProxy archiveProxy)\n    {\n        final long requestCorrelationId = client.nextCorrelationId();\n        archiveProxy.listRecordings(0, 100, requestCorrelationId, controlSessionId);\n        ArchiveTests.awaitResponse(controlResponse, requestCorrelationId);\n    }\n\n    private void verifyDescriptorListOngoingArchive(\n        final ArchiveProxy archiveProxy, final int publicationTermBufferLength)\n    {\n        final long requestCorrelationId = client.nextCorrelationId();\n        archiveProxy.listRecording(recordingId, requestCorrelationId, controlSessionId);\n        final MutableBoolean isDone = new MutableBoolean();\n\n        final ControlResponseAdapter controlResponseAdapter = new ControlResponseAdapter(\n            new FailControlResponseListener()\n            {\n                public void onRecordingDescriptor(\n                    final long controlSessionId,\n                    final long correlationId,\n                    final long recordingId,\n                    final long startTimestamp,\n                    final long stopTimestamp,\n                    final long startPosition,\n                    final long stopPosition,\n                    final int initialTermId,\n                    final int segmentFileLength,\n                    final int termBufferLength,\n                    final int mtuLength,\n                    final int sessionId,\n                    final int streamId,\n                    final String strippedChannel,\n                    final String originalChannel,\n                    final String sourceIdentity)\n                {\n                    assertEquals(requestCorrelationId, correlationId);\n                    assertEquals(ArchiveSystemTest.this.recordingId, recordingId);\n                    assertEquals(publicationTermBufferLength, termBufferLength);\n                    assertEquals(PUBLISH_STREAM_ID, streamId);\n                    assertEquals(publishUri, originalChannel);\n\n                    isDone.set(true);\n                }\n            },\n            controlResponse,\n            1);\n\n        while (!isDone.get())\n        {\n            if (controlResponseAdapter.poll() == 0)\n            {\n                if (!controlResponse.isConnected())\n                {\n                    throw new IllegalStateException(\"control response not connected\");\n                }\n\n                Tests.yield();\n            }\n        }\n    }\n\n    private void prepAndSendMessages(final Subscription recordingEvents, final Publication publication)\n    {\n        final CountDownLatch complete = new CountDownLatch(1);\n        prepMessagesAndListener(recordingEvents, complete);\n        publishDataToBeRecorded(publication);\n        await(complete);\n    }\n\n    private void await(final CountDownLatch latch)\n    {\n        try\n        {\n            latch.await();\n        }\n        catch (final InterruptedException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    private void prepMessagesAndListener(final Subscription recordingEvents, final CountDownLatch latch)\n    {\n        messageLengths = new int[MESSAGE_COUNT];\n        for (int i = 0; i < MESSAGE_COUNT; i++)\n        {\n            final int messageLength = 64 + rnd.nextInt(MAX_FRAGMENT_SIZE - 64) - HEADER_LENGTH;\n            messageLengths[i] = messageLength + HEADER_LENGTH;\n            totalDataLength += BitUtil.align(messageLengths[i], FrameDescriptor.FRAME_ALIGNMENT);\n        }\n\n        progressTracker = trackRecordingProgress(recordingEvents, latch);\n    }\n\n    private void publishDataToBeRecorded(final Publication publication)\n    {\n        Tests.awaitConnected(publication);\n        startPosition = publication.position();\n\n        final UnsafeBuffer buffer = new UnsafeBuffer(new byte[4096]);\n        buffer.setMemory(0, 1024, (byte)'z');\n        buffer.putStringAscii(32, \"TEST\");\n\n        for (int i = 0; i < MESSAGE_COUNT; i++)\n        {\n            final int dataLength = messageLengths[i] - HEADER_LENGTH;\n            buffer.putInt(0, i);\n\n            while (true)\n            {\n                final long result = publication.offer(buffer, 0, dataLength);\n                if (result > 0)\n                {\n                    break;\n                }\n\n                if (result == Publication.CLOSED || result == Publication.NOT_CONNECTED)\n                {\n                    throw new IllegalStateException(\"Publication not connected: result=\" + result);\n                }\n\n                Tests.yield();\n            }\n        }\n\n        final long position = publication.position();\n        totalRecordingLength = position - startPosition;\n        stopPosition = position;\n    }\n\n    private void validateReplay(\n        final ArchiveProxy archiveProxy,\n        final int initialTermId,\n        final int maxPayloadLength,\n        final int termBufferLength)\n    {\n        try (Subscription replay = client.addSubscription(REPLAY_URI, REPLAY_STREAM_ID))\n        {\n            final long replayCorrelationId = client.nextCorrelationId();\n\n            assertTrue(archiveProxy.replay(\n                recordingId,\n                startPosition,\n                totalRecordingLength,\n                REPLAY_URI,\n                REPLAY_STREAM_ID,\n                replayCorrelationId,\n                controlSessionId),\n                \"failed to replay\");\n\n            ArchiveTests.awaitOk(controlResponse, replayCorrelationId);\n            Tests.awaitConnected(replay);\n\n            final Image image = replay.images().get(0);\n            assertEquals(initialTermId, image.initialTermId());\n            assertEquals(maxPayloadLength + HEADER_LENGTH, image.mtuLength());\n            assertEquals(termBufferLength, image.termBufferLength());\n            assertEquals(startPosition, image.position());\n\n            messageCount = 0;\n            remaining = totalDataLength;\n\n            while (remaining > 0)\n            {\n                final int fragments = replay.poll(this::validateFragment, 10);\n                if (0 == fragments)\n                {\n                    Tests.yield();\n                }\n            }\n\n            assertEquals(MESSAGE_COUNT, messageCount);\n            assertEquals(0L, remaining);\n        }\n\n        final Archive.Context context = archive.context();\n        Tests.await(() -> context.totalReadBytesCounter().get() >= totalDataLength);\n        final long totalReadTimeNs = context.totalReadTimeCounter().get();\n        assertNotEquals(0, totalReadTimeNs);\n        final long maxReadTimeNs = context.maxReadTimeCounter().get();\n        assertNotEquals(0, maxReadTimeNs);\n        assertTrue(totalReadTimeNs > maxReadTimeNs);\n    }\n\n    private void validateArchiveFile(final long recordingId)\n    {\n        final File archiveDir = archive.context().archiveDir();\n        final Catalog catalog = archive.context().catalog();\n        remaining = totalDataLength;\n        messageCount = 0;\n\n        while (catalog.stopPosition(recordingId) != stopPosition)\n        {\n            Tests.yield();\n        }\n\n        try (RecordingReader recordingReader = new RecordingReader(\n            catalog.recordingSummary(recordingId, new RecordingSummary()),\n            archiveDir,\n            NULL_POSITION,\n            AeronArchive.NULL_LENGTH))\n        {\n            while (!recordingReader.isDone())\n            {\n                if (0 == recordingReader.poll(this::validateRecordingFragment, MESSAGE_COUNT))\n                {\n                    Tests.yield();\n                }\n            }\n        }\n\n        assertEquals(0L, remaining);\n        assertEquals(MESSAGE_COUNT, messageCount);\n    }\n\n    private void validateRecordingFragment(\n        final UnsafeBuffer buffer,\n        final int offset,\n        final int length,\n        final int frameType,\n        final byte flags,\n        final long reservedValue)\n    {\n        if (!FrameDescriptor.isPaddingFrame(buffer, offset - HEADER_LENGTH))\n        {\n            final int messageLength = messageLengths[messageCount];\n            final int expectedLength = messageLength - HEADER_LENGTH;\n            if (length != expectedLength)\n            {\n                fail(\"messageLength=\" + length + \" expected=\" + expectedLength + \" messageCount=\" + messageCount);\n            }\n\n            assertEquals(messageCount, buffer.getInt(offset));\n            assertEquals((byte)'z', buffer.getByte(offset + 4));\n\n            remaining -= BitUtil.align(messageLength, FrameDescriptor.FRAME_ALIGNMENT);\n            messageCount++;\n        }\n    }\n\n    private void validateFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        final int messageLength = messageLengths[messageCount];\n        assertEquals(messageLength - HEADER_LENGTH, length);\n        assertEquals(messageCount, buffer.getInt(offset));\n        assertEquals((byte)'z', buffer.getByte(offset + 4));\n\n        remaining -= BitUtil.align(messageLength, FrameDescriptor.FRAME_ALIGNMENT);\n        messageCount++;\n    }\n\n    private Thread trackRecordingProgress(final Subscription recordingEvents, final CountDownLatch latch)\n    {\n        recorded = 0;\n\n        final RecordingEventsAdapter recordingEventsAdapter = new RecordingEventsAdapter(\n            new FailRecordingEventsListener()\n            {\n                public void onProgress(final long recordingId0, final long startPosition, final long position)\n                {\n                    assertEquals(recordingId, recordingId0);\n                    recorded = position - startPosition;\n                }\n            },\n            recordingEvents,\n            1);\n\n        final Thread thread = new Thread(\n            () ->\n            {\n                try\n                {\n                    while (NULL_POSITION == stopPosition || recorded < totalRecordingLength)\n                    {\n                        if (recordingEventsAdapter.poll() == 0)\n                        {\n                            if (!recordingEvents.isConnected())\n                            {\n                                break;\n                            }\n\n                            Tests.sleep(1);\n                        }\n                    }\n                }\n                catch (final Exception ex)\n                {\n                    ex.printStackTrace();\n                    trackerError = ex;\n                }\n\n                latch.countDown();\n            });\n\n        thread.setDaemon(true);\n        thread.setName(\"recording-progress-tracker\");\n        thread.start();\n\n        return thread;\n    }\n\n    private Thread validateActiveRecordingReplay(\n        final ArchiveProxy archiveProxy,\n        final int termBufferLength,\n        final int initialTermId,\n        final int maxPayloadLength,\n        final CountDownLatch completeLatch)\n    {\n        final Thread thread = new Thread(\n            () ->\n            {\n                while (0 == recorded)\n                {\n                    Tests.sleep(1);\n                }\n\n                try (Subscription replay = client.addSubscription(REPLAY_URI, REPLAY_STREAM_ID))\n                {\n                    final long replayCorrelationId = client.nextCorrelationId();\n\n                    final ReplayParams replayParams = new ReplayParams()\n                        .position(startPosition)\n                        .length(Long.MAX_VALUE)\n                        .fileIoMaxLength(4096);\n\n                    if (!archiveProxy.replay(\n                        recordingId, REPLAY_URI, REPLAY_STREAM_ID, replayParams, replayCorrelationId, controlSessionId))\n                    {\n                        throw new IllegalStateException(\"failed to start replay\");\n                    }\n\n                    ArchiveTests.awaitOk(controlResponse, replayCorrelationId);\n                    Tests.awaitConnected(replay);\n\n                    final Image image = replay.images().get(0);\n                    assertEquals(initialTermId, image.initialTermId());\n                    assertEquals(maxPayloadLength + HEADER_LENGTH, image.mtuLength());\n                    assertEquals(termBufferLength, image.termBufferLength());\n                    assertEquals(startPosition, image.position());\n\n                    messageCount = 0;\n                    remaining = totalDataLength;\n\n                    final FragmentHandler fragmentHandler = this::validateFragment;\n                    while (messageCount < MESSAGE_COUNT)\n                    {\n                        final int fragments = replay.poll(fragmentHandler, 10);\n                        if (0 == fragments)\n                        {\n                            if (!replay.isConnected())\n                            {\n                                break;\n                            }\n\n                            Tests.yield();\n                        }\n                    }\n                }\n\n                completeLatch.countDown();\n            });\n\n        thread.setName(\"replay-consumer\");\n        thread.setDaemon(true);\n        thread.start();\n\n        return thread;\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveSystemTests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.Tests;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.util.function.Supplier;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\nclass ArchiveSystemTests\n{\n    static final long CATALOG_CAPACITY = 128 * 1024;\n    static final int TERM_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    static final int FRAGMENT_LIMIT = 10;\n\n    static void offer(final Publication publication, final int count, final String prefix)\n    {\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n\n        for (int i = 0; i < count; i++)\n        {\n            final int length = buffer.putStringWithoutLengthAscii(0, prefix + i);\n\n            while (publication.offer(buffer, 0, length) <= 0)\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    static void offerToPosition(final Publication publication, final String prefix, final long minimumPosition)\n    {\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n\n        for (int i = 0; publication.position() < minimumPosition; i++)\n        {\n            final int length = buffer.putStringWithoutLengthAscii(0, prefix + i);\n\n            while (publication.offer(buffer, 0, length) <= 0)\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    static void consume(final Subscription subscription, final int count, final String prefix)\n    {\n        final MutableInteger received = new MutableInteger(0);\n\n        final FragmentHandler fragmentHandler = new FragmentAssembler(\n            (buffer, offset, length, header) ->\n            {\n                final String expected = prefix + received.value;\n                final String actual = buffer.getStringWithoutLengthAscii(offset, length);\n\n                assertEquals(expected, actual);\n\n                received.value++;\n            });\n\n        while (received.value < count)\n        {\n            if (0 == subscription.poll(fragmentHandler, FRAGMENT_LIMIT))\n            {\n                Tests.yield();\n            }\n        }\n\n        assertEquals(count, received.get());\n    }\n\n    static TestRecordingSignalConsumer injectRecordingSignalConsumer(final AeronArchive aeronArchive)\n    {\n        final long controlSessionId = aeronArchive.controlSessionId();\n        final TestRecordingSignalConsumer recordingSignalConsumer = new TestRecordingSignalConsumer(controlSessionId);\n        aeronArchive.context().recordingSignalConsumer(recordingSignalConsumer);\n        return recordingSignalConsumer;\n    }\n\n    static void awaitSignal(\n        final AeronArchive aeronArchive,\n        final TestRecordingSignalConsumer signalConsumer,\n        final RecordingSignal expectedSignal)\n    {\n        while (expectedSignal != signalConsumer.signal)\n        {\n            if (0 == aeronArchive.pollForRecordingSignals())\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    static void resetAndAwaitSignal(\n        final AeronArchive aeronArchive,\n        final TestRecordingSignalConsumer signalConsumer,\n        final RecordingSignal expectedSignal)\n    {\n        signalConsumer.reset();\n        awaitSignal(aeronArchive, signalConsumer, expectedSignal);\n    }\n\n    static void awaitSignal(\n        final AeronArchive aeronArchive,\n        final TestRecordingSignalConsumer signalConsumer,\n        final long expectedRecordingId,\n        final RecordingSignal expectedSignal)\n    {\n        final Supplier<String> errorMessage =\n            () -> \"Expected signal: expectedSignal=\" + expectedSignal + \" vs actualSignal=\" + signalConsumer.signal +\n            \", expectedRecordingId=\" + expectedRecordingId + \" vs actualRecordingId=\" + signalConsumer.recordingId;\n        while (expectedRecordingId != signalConsumer.recordingId || expectedSignal != signalConsumer.signal)\n        {\n            if (0 == aeronArchive.pollForRecordingSignals())\n            {\n                Tests.yieldingIdle(errorMessage);\n            }\n        }\n    }\n\n    static void resetAndAwaitSignal(\n        final AeronArchive aeronArchive,\n        final TestRecordingSignalConsumer signalConsumer,\n        final long expectedRecordingId,\n        final RecordingSignal expectedSignal)\n    {\n        signalConsumer.reset();\n        awaitSignal(aeronArchive, signalConsumer, expectedRecordingId, expectedSignal);\n    }\n\n    static RecordingResult recordData(final AeronArchive aeronArchive)\n    {\n        return recordData(aeronArchive, 1000, null);\n    }\n\n    static RecordingResult recordData(\n        final AeronArchive aeronArchive, final int totalMessageCount, final String alias)\n    {\n        final TestRecordingSignalConsumer testRecordingSignalConsumer = new TestRecordingSignalConsumer(\n            aeronArchive.controlSessionId());\n        aeronArchive.context().recordingSignalConsumer(testRecordingSignalConsumer);\n\n        final UnsafeBuffer message = new UnsafeBuffer(new byte[1024]);\n        message.setMemory(0, message.capacity(), (byte)'x');\n\n        final long recordingId;\n        final long position;\n        long halfWayPosition = Aeron.NULL_VALUE;\n\n        final ChannelUriStringBuilder channelUriStringBuilder = new ChannelUriStringBuilder()\n            .media(\"ipc\");\n\n        if (null != alias)\n        {\n            channelUriStringBuilder.alias(alias);\n        }\n\n        final String channel = channelUriStringBuilder.toString();\n        final int streamId = 10000;\n\n        try (Publication publication = aeronArchive.addRecordedPublication(channel, streamId))\n        {\n            final CountersReader countersReader = aeronArchive.context().aeron().countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(\n                    countersReader, publication.sessionId(), aeronArchive.archiveId());\n            recordingId = RecordingPos.getRecordingId(countersReader, counterId);\n\n            int messageCount = totalMessageCount;\n            while (messageCount > 0)\n            {\n                if (0 < publication.offer(message))\n                {\n                    --messageCount;\n                    if (Aeron.NULL_VALUE == halfWayPosition && messageCount == messageCount / 2)\n                    {\n                        halfWayPosition = publication.position();\n                    }\n                }\n                else\n                {\n                    aeronArchive.checkForErrorResponse();\n                    Tests.yield();\n                }\n            }\n\n            position = publication.position();\n            assertNotEquals(0, position);\n\n            while (countersReader.getCounterValue(counterId) < position)\n            {\n                Tests.yield();\n            }\n\n            testRecordingSignalConsumer.reset();\n        }\n\n        awaitSignal(aeronArchive, testRecordingSignalConsumer, recordingId, RecordingSignal.STOP);\n\n        return new RecordingResult(position, halfWayPosition, recordingId, streamId);\n    }\n\n    record RecordingResult(long position, long halfwayPosition, long recordingId, int streamId)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ArchiveTruncateRecordingTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.checksum.Checksum;\nimport io.aeron.archive.checksum.Checksums;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.EnumSet;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.Archive.Configuration.CATALOG_FILE_NAME;\nimport static io.aeron.archive.Archive.Configuration.RECORDING_SEGMENT_SUFFIX;\nimport static io.aeron.archive.Archive.segmentFileName;\nimport static io.aeron.archive.ArchiveSystemTests.*;\nimport static io.aeron.archive.client.AeronArchive.segmentFileBasePosition;\nimport static java.util.Arrays.asList;\nimport static java.util.Arrays.copyOfRange;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ArchiveTruncateRecordingTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n    private final FragmentAssembler fragmentHandler = new FragmentAssembler((buffer, offset, length, header) -> {});\n    private TestMediaDriver driver;\n    private Archive archive;\n    private Aeron aeron;\n    private AeronArchive aeronArchive;\n    private TestRecordingSignalConsumer recordingSignalConsumer;\n\n    @BeforeEach\n    void before(final @TempDir Path tempDir)\n    {\n        final Checksum checksum = Checksums.crc32c();\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .sharedIdleStrategy(YieldingIdleStrategy.INSTANCE)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true)\n            .aeronDirectoryName(tempDir.resolve(\"aeron-driver\").toString());\n\n        final Archive.Context archiveContext = TestContexts.localhostArchive()\n            .catalogCapacity(CATALOG_CAPACITY)\n            .deleteArchiveOnStart(true)\n            .aeronDirectoryName(driverCtx.aeronDirectoryName())\n            .archiveDir(tempDir.resolve(\"archive-test\").toFile())\n            .segmentFileLength(LogBufferDescriptor.TERM_MIN_LENGTH * 2)\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .recordChecksum(checksum)\n            .replayChecksum(checksum)\n            .idleStrategySupplier(YieldingIdleStrategy::new);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n\n        archive = Archive.launch(archiveContext);\n        systemTestWatcher.dataCollector().add(archiveContext.archiveDir());\n\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n\n        aeronArchive = AeronArchive.connect(TestContexts.localhostAeronArchive().aeron(aeron));\n        recordingSignalConsumer = injectRecordingSignalConsumer(aeronArchive);\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeronArchive, aeron, archive, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void truncateRecordingShouldDeleteAllFilesWhenTruncatingToTheStartOfTheRecording() throws IOException\n    {\n        final String channel = \"aeron:ipc?term-length=64k\";\n        final int streamId = 3333;\n        try (ExclusivePublication publication = aeron.addExclusivePublication(channel, streamId);\n            Subscription subscription = aeron.addSubscription(channel, streamId))\n        {\n            final int messageLength = publication.maxMessageLength();\n            final UnsafeBuffer data = new UnsafeBuffer(new byte[messageLength]);\n            ThreadLocalRandom.current().nextBytes(data.byteArray());\n\n            sendMessages(publication, subscription, messageLength, data, 99);\n            final long startPosition = publication.position();\n\n            final long subscriptionId = aeronArchive.startRecording(\n                ChannelUri.addSessionId(channel, publication.sessionId()), streamId, SourceLocation.LOCAL);\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n            assertEquals(startPosition, aeronArchive.getStartPosition(recordingId));\n            assertEquals(NULL_VALUE, aeronArchive.getStopPosition(recordingId));\n\n            ThreadLocalRandom.current().nextBytes(data.byteArray());\n            sendMessages(publication, subscription, messageLength, data, 42);\n            final long stopPosition = publication.position();\n\n            while (stopPosition != aeronArchive.getRecordingPosition(recordingId))\n            {\n                Tests.yield();\n            }\n\n            recordingSignalConsumer.reset();\n            aeronArchive.stopRecording(subscriptionId);\n            awaitSignal(aeronArchive, recordingSignalConsumer, recordingId, RecordingSignal.STOP);\n            assertEquals(startPosition, aeronArchive.getStartPosition(recordingId));\n            assertEquals(stopPosition, aeronArchive.getStopPosition(recordingId));\n\n            final Path archiveDir = archive.context().archiveDir().toPath();\n            final ArrayList<String> segmentFiles = Catalog.listSegmentFiles(archiveDir.toFile(), recordingId);\n            segmentFiles.sort(Comparator.naturalOrder());\n            assertEquals(asList(\n                recordingId + \"-1048576.rec\",\n                recordingId + \"-1179648.rec\",\n                recordingId + \"-1310720.rec\",\n                recordingId + \"-917504.rec\"),\n                segmentFiles);\n\n            final ArrayList<Path> deleteList = new ArrayList<>();\n            for (final String segmentFileName : segmentFiles)\n            {\n                deleteList.add(archiveDir.resolve(segmentFileName));\n            }\n            deleteList.add(Files.createFile(archiveDir.resolve(recordingId + \"-0\" + RECORDING_SEGMENT_SUFFIX)));\n            deleteList.add(Files.createFile(archiveDir.resolve(recordingId + \"-1\" + RECORDING_SEGMENT_SUFFIX)));\n            deleteList.add(Files.createFile(archiveDir.resolve(recordingId + \"-1048575\" + RECORDING_SEGMENT_SUFFIX)));\n            deleteList.add(Files.createFile(archiveDir.resolve(recordingId + \"-1048577\" + RECORDING_SEGMENT_SUFFIX)));\n            deleteList.add(Files.createFile(archiveDir.resolve(recordingId + \"-222222222\" + RECORDING_SEGMENT_SUFFIX)));\n            deleteList.add(Files.createFile(archiveDir.resolve(\n                recordingId + \"-\" + archive.context().segmentFileLength() + RECORDING_SEGMENT_SUFFIX)));\n\n            final Path otherFile = Files.createFile(\n                archiveDir.resolve((recordingId + 1) + \"-1179648\" + RECORDING_SEGMENT_SUFFIX));\n            final Path otherFile2 = Files.createFile(archiveDir.resolve(\"something-else.txt\"));\n\n            recordingSignalConsumer.reset();\n            aeronArchive.truncateRecording(recordingId, startPosition);\n            awaitSignal(aeronArchive, recordingSignalConsumer, recordingId, RecordingSignal.DELETE);\n\n            assertEquals(startPosition, aeronArchive.getStartPosition(recordingId));\n            assertEquals(startPosition, aeronArchive.getStopPosition(recordingId));\n            assertEquals(Collections.emptyList(), Catalog.listSegmentFiles(archiveDir.toFile(), recordingId));\n            for (final Path file : deleteList)\n            {\n                assertFalse(Files.exists(file));\n            }\n            assertTrue(Files.exists(otherFile));\n            assertTrue(Files.exists(otherFile2));\n            assertTrue(Files.exists(archiveDir.resolve(CATALOG_FILE_NAME)));\n\n            verifyRecording(recordingId);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void truncateRecordingShouldDeleteAllFilesWhenTruncatingToZero() throws IOException\n    {\n        final String channel = \"aeron:ipc?term-length=\" + (archive.context().segmentFileLength() * 2);\n        final int streamId = 3333;\n        try (ExclusivePublication publication = aeronArchive.addRecordedExclusivePublication(channel, streamId);\n            Subscription subscription = aeron.addSubscription(channel, streamId))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n            assertEquals(0, aeronArchive.getStartPosition(recordingId));\n\n            final int messageLength = 1600;\n            final UnsafeBuffer data = new UnsafeBuffer(new byte[messageLength]);\n            ThreadLocalRandom.current().nextBytes(data.byteArray());\n\n            sendMessages(publication, subscription, messageLength, data, 333);\n            final long stopPosition = publication.position();\n\n            while (stopPosition != aeronArchive.getRecordingPosition(recordingId))\n            {\n                Tests.yield();\n            }\n\n            recordingSignalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, recordingSignalConsumer, recordingId, RecordingSignal.STOP);\n            assertEquals(0, aeronArchive.getStartPosition(recordingId));\n            assertEquals(stopPosition, aeronArchive.getStopPosition(recordingId));\n\n            final Path archiveDir = archive.context().archiveDir().toPath();\n            final ArrayList<String> segmentFiles = Catalog.listSegmentFiles(archiveDir.toFile(), recordingId);\n            segmentFiles.sort(Comparator.naturalOrder());\n            assertEquals(\n                asList(recordingId + \"-0.rec\", recordingId + \"-262144.rec\", recordingId + \"-524288.rec\"),\n                segmentFiles);\n\n            final ArrayList<Path> deleteList = new ArrayList<>();\n            for (final String segmentFileName : segmentFiles)\n            {\n                final Path file = archiveDir.resolve(segmentFileName);\n                final Path renamed = Files.move(file, archiveDir.resolve(segmentFileName + \".del\"));\n                assertTrue(Files.exists(renamed));\n                deleteList.add(renamed);\n            }\n            deleteList.add(Files.createFile(archiveDir.resolve(recordingId + \"-1\" + RECORDING_SEGMENT_SUFFIX)));\n            deleteList.add(Files.createFile(archiveDir.resolve(\n                recordingId + \"-2\" + RECORDING_SEGMENT_SUFFIX + \".del\")));\n            deleteList.add(Files.createFile(archiveDir.resolve(recordingId + \"-262143\" + RECORDING_SEGMENT_SUFFIX)));\n            deleteList.add(Files.createFile(archiveDir.resolve(recordingId + \"-262145\" + RECORDING_SEGMENT_SUFFIX)));\n            deleteList.add(Files.createFile(archiveDir.resolve(\n                recordingId + \"-\" + archive.context().segmentFileLength() + RECORDING_SEGMENT_SUFFIX)));\n\n            final Path otherFile = Files.createFile(archiveDir.resolve(\n                (recordingId + 1) + \"-\" + publication.termBufferLength() + RECORDING_SEGMENT_SUFFIX));\n            final Path otherFile2 = Files.createFile(archiveDir.resolve(\"something-else.txt\"));\n            final Path otherFile3 = Files.createFile(archiveDir.resolve(\n                (recordingId + 2) + \"-\" + archive.context().segmentFileLength() + RECORDING_SEGMENT_SUFFIX + \".del\"));\n\n            recordingSignalConsumer.reset();\n            aeronArchive.truncateRecording(recordingId, 0);\n            awaitSignal(aeronArchive, recordingSignalConsumer, recordingId, RecordingSignal.DELETE);\n\n            assertEquals(0, aeronArchive.getStartPosition(recordingId));\n            assertEquals(0, aeronArchive.getStopPosition(recordingId));\n            assertEquals(Collections.emptyList(), Catalog.listSegmentFiles(archiveDir.toFile(), recordingId));\n            for (final Path file : deleteList)\n            {\n                assertFalse(Files.exists(file), () -> \"not deleted: \" + file);\n            }\n            assertTrue(Files.exists(otherFile));\n            assertTrue(Files.exists(otherFile2));\n            assertTrue(Files.exists(otherFile3));\n            assertTrue(Files.exists(archiveDir.resolve(CATALOG_FILE_NAME)));\n\n            verifyRecording(recordingId);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SuppressWarnings(\"MethodLength\")\n    void truncateRecordingShouldDeleteSegmentFilesPastPositionAndEraseAlreadyWrittenData() throws IOException\n    {\n        final String channel = \"aeron:ipc?term-length=\" + (archive.context().segmentFileLength() * 4);\n        final int streamId = 555;\n        try (\n            ExclusivePublication rec1 = aeronArchive.addRecordedExclusivePublication(\"aeron:ipc\", 2000);\n            ExclusivePublication rec2 = aeronArchive.addRecordedExclusivePublication(\n                \"aeron:udp?endpoint=localhost:5555\", 1000);\n            ExclusivePublication publication = aeron.addExclusivePublication(channel, streamId);\n            Subscription subscription = aeron.addSubscription(channel, streamId))\n        {\n            final int messageLength = publication.maxMessageLength();\n            final UnsafeBuffer data = new UnsafeBuffer(new byte[messageLength]);\n\n            Tests.awaitConnected(rec1);\n            Tests.awaitConnected(rec2);\n            assertTrue(rec1.offer(data, 0, 10) > 0);\n            assertTrue(rec2.offer(data, 0, 99) > 0);\n            aeronArchive.stopRecording(rec1);\n            aeronArchive.stopRecording(rec2);\n\n            ThreadLocalRandom.current().nextBytes(data.byteArray());\n            sendMessages(publication, subscription, messageLength, data, 10);\n            final long startPosition = publication.position();\n\n            aeronArchive.startRecording(\n                ChannelUri.addSessionId(channel, publication.sessionId()), streamId, SourceLocation.LOCAL);\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n            assertEquals(startPosition, aeronArchive.getStartPosition(recordingId));\n\n            ThreadLocalRandom.current().nextBytes(data.byteArray());\n            sendMessages(publication, subscription, messageLength, data, 10);\n            final long truncatePosition = publication.position();\n\n            ThreadLocalRandom.current().nextBytes(data.byteArray());\n            sendMessages(publication, subscription, messageLength, data, 5);\n            final long stopPosition = publication.position();\n\n            while (stopPosition != aeronArchive.getRecordingPosition(recordingId))\n            {\n                Tests.yield();\n            }\n\n            recordingSignalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, recordingSignalConsumer, recordingId, RecordingSignal.STOP);\n            assertEquals(startPosition, aeronArchive.getStartPosition(recordingId));\n            assertEquals(stopPosition, aeronArchive.getStopPosition(recordingId));\n\n            final Path archiveDir = archive.context().archiveDir().toPath();\n            final int termLength = publication.termBufferLength();\n            final int segmentFileLength = Math.max(archive.context().segmentFileLength(), termLength);\n            final Path startFile = archiveDir.resolve(segmentFileName(recordingId,\n                segmentFileBasePosition(startPosition, startPosition, termLength, segmentFileLength)));\n            final Path stopFile = archiveDir.resolve(segmentFileName(recordingId,\n                segmentFileBasePosition(startPosition, stopPosition, termLength, segmentFileLength)));\n            final long truncateFilePosition = segmentFileBasePosition(\n                startPosition, truncatePosition, termLength, segmentFileLength);\n            final Path truncatedFile = archiveDir.resolve(segmentFileName(recordingId, truncateFilePosition));\n            final byte[] startFileData = Files.readAllBytes(startFile);\n            final byte[] truncatedFileData = Files.readAllBytes(truncatedFile);\n\n            final ArrayList<String> segmentFiles = Catalog.listSegmentFiles(archiveDir.toFile(), recordingId);\n            segmentFiles.sort(Comparator.naturalOrder());\n            assertEquals(Arrays.asList(\n                truncatedFile.getFileName().toString(),\n                stopFile.getFileName().toString(),\n                startFile.getFileName().toString()),\n                segmentFiles);\n\n            recordingSignalConsumer.reset();\n            aeronArchive.truncateRecording(recordingId, truncatePosition);\n            awaitSignal(aeronArchive, recordingSignalConsumer, recordingId, RecordingSignal.DELETE);\n\n            assertEquals(startPosition, aeronArchive.getStartPosition(recordingId));\n            assertEquals(truncatePosition, aeronArchive.getStopPosition(recordingId));\n\n            // prior to truncate position all files should be unchanged\n            assertArrayEquals(startFileData, Files.readAllBytes(startFile));\n\n            // the file with the truncate position must be truncated\n            final byte[] afterTruncate = Files.readAllBytes(truncatedFile);\n            assertFalse(Arrays.equals(truncatedFileData, afterTruncate));\n            final int lengthBeforeTruncationPoint = (int)(truncatePosition - truncateFilePosition);\n            assertArrayEquals(\n                copyOfRange(truncatedFileData, 0, lengthBeforeTruncationPoint),\n                copyOfRange(afterTruncate, 0, lengthBeforeTruncationPoint));\n            for (int i = lengthBeforeTruncationPoint; i < segmentFileLength; i++)\n            {\n                assertEquals(0, afterTruncate[i]);\n            }\n\n            // files after truncate position are deleted\n            assertFalse(Files.exists(stopFile));\n\n            verifyRecording(recordingId);\n\n            assertTrue(Files.exists(archiveDir.resolve(CATALOG_FILE_NAME)));\n        }\n    }\n\n    private void sendMessages(\n        final ExclusivePublication publication,\n        final Subscription subscription,\n        final int messageLength,\n        final UnsafeBuffer data,\n        final int numberOfMessages)\n    {\n        for (int i = 0; i < numberOfMessages; i++)\n        {\n            while (publication.offer(data, 0, messageLength) < 0)\n            {\n                Tests.yield();\n            }\n\n            while (0 == subscription.poll(fragmentHandler, Integer.MAX_VALUE))\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    private void verifyRecording(final long recordingId)\n    {\n        final PrintStream out = mock(PrintStream.class);\n        assertTrue(ArchiveTool.verifyRecording(\n            out,\n            archive.context().archiveDir(),\n            recordingId,\n            EnumSet.allOf(ArchiveTool.VerifyOption.class),\n            archive.context().recordChecksum(),\n            SystemEpochClock.INSTANCE,\n            (file) -> true));\n\n        verify(out).println(\"(recordingId=\" + recordingId + \") OK\");\n        verifyNoMoreInteractions(out);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/BasicArchiveTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.client.RecordingDescriptorConsumer;\nimport io.aeron.archive.client.ReplayParams;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.samples.archive.RecordingDescriptor;\nimport io.aeron.samples.archive.RecordingDescriptorCollector;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.Hashing;\nimport org.agrona.collections.Long2LongHashMap;\nimport org.agrona.collections.LongArrayList;\nimport org.agrona.collections.LongHashSet;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Random;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.LongConsumer;\nimport java.util.function.Supplier;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.ArchiveSystemTests.CATALOG_CAPACITY;\nimport static io.aeron.archive.ArchiveSystemTests.RecordingResult;\nimport static io.aeron.archive.ArchiveSystemTests.TERM_LENGTH;\nimport static io.aeron.archive.ArchiveSystemTests.awaitSignal;\nimport static io.aeron.archive.ArchiveSystemTests.consume;\nimport static io.aeron.archive.ArchiveSystemTests.injectRecordingSignalConsumer;\nimport static io.aeron.archive.ArchiveSystemTests.offer;\nimport static io.aeron.archive.ArchiveSystemTests.recordData;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.codecs.SourceLocation.LOCAL;\nimport static org.hamcrest.CoreMatchers.endsWith;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.hamcrest.Matchers.lessThan;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass BasicArchiveTest\n{\n    private static final String RECORDED_CHANNEL_ALIAS = \"named-log\";\n    private static final int RECORDED_STREAM_ID = 33;\n    private static final String RECORDED_CHANNEL = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3333\")\n        .termLength(ArchiveSystemTests.TERM_LENGTH)\n        .alias(RECORDED_CHANNEL_ALIAS)\n        .build();\n\n    private static final int REPLAY_STREAM_ID = 66;\n    private static final String REPLAY_CHANNEL = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:6666\")\n        .build();\n\n    private TestMediaDriver driver;\n    private Archive archive;\n    private Aeron aeron;\n    private AeronArchive aeronArchive;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private File archiveDir;\n    private TestRecordingSignalConsumer recordingSignalConsumer;\n\n    @BeforeEach\n    public void before()\n    {\n        final String aeronDirectoryName = CommonContext.generateRandomDirName();\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDirectoryName)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(false)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true);\n\n        archiveDir = new File(SystemUtil.tmpDirName(), \"archive\");\n\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .catalogCapacity(CATALOG_CAPACITY)\n            .segmentFileLength(TERM_LENGTH)\n            .aeronDirectoryName(aeronDirectoryName)\n            .deleteArchiveOnStart(true)\n            .archiveDir(archiveDir)\n            .fileSyncLevel(0)\n            .maxConcurrentRecordings(50)\n            .threadingMode(ArchiveThreadingMode.DEDICATED); // testing concurrent operations\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n        archive = Archive.launch(archiveCtx);\n        systemTestWatcher.dataCollector().add(archiveCtx.archiveDir());\n\n        aeron = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(aeronDirectoryName));\n\n        aeronArchive = AeronArchive.connect(\n            TestContexts.localhostAeronArchive()\n                .aeron(aeron));\n\n        recordingSignalConsumer = injectRecordingSignalConsumer(aeronArchive);\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeronArchive, aeron, archive, driver);\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRecordThenReplayThenTruncate()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long stopPosition;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n        final long recordingIdFromCounter;\n        final int sessionId;\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            sessionId = publication.sessionId();\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(counters, sessionId, aeronArchive.archiveId());\n            recordingIdFromCounter = RecordingPos.getRecordingId(counters, counterId);\n\n            assertEquals(CommonContext.IPC_CHANNEL, RecordingPos.getSourceIdentity(counters, counterId));\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            stopPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, stopPosition);\n\n            final long joinPosition = subscription.imageBySessionId(sessionId).joinPosition();\n            assertEquals(joinPosition, aeronArchive.getStartPosition(recordingIdFromCounter));\n            assertEquals(stopPosition, aeronArchive.getRecordingPosition(recordingIdFromCounter));\n            assertEquals(NULL_VALUE, aeronArchive.getStopPosition(recordingIdFromCounter));\n            assertEquals(stopPosition, aeronArchive.getMaxRecordedPosition(recordingIdFromCounter));\n        }\n\n        aeronArchive.stopRecording(subscriptionId);\n        Tests.await(() -> stopPosition == aeronArchive.getStopPosition(recordingIdFromCounter));\n\n        final long recordingId = aeronArchive.findLastMatchingRecording(\n            0, \"alias=\" + RECORDED_CHANNEL_ALIAS, RECORDED_STREAM_ID, sessionId);\n\n        assertFalse(aeronArchive.tryStopRecordingByIdentity(recordingId));\n\n        final long position = 0L;\n        final long length = stopPosition - position;\n\n        try (Subscription subscription = aeronArchive.replay(\n            recordingId, position, length, REPLAY_CHANNEL, REPLAY_STREAM_ID))\n        {\n            consume(subscription, messageCount, messagePrefix);\n            assertEquals(stopPosition, subscription.imageAtIndex(0).position());\n        }\n\n        aeronArchive.truncateRecording(recordingId, position);\n\n        final int count = aeronArchive.listRecording(\n            recordingId,\n            (controlSessionId,\n            correlationId,\n            recordingId1,\n            startTimestamp,\n            stopTimestamp,\n            startPosition,\n            newStopPosition,\n            initialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId1,\n            streamId,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity) -> assertEquals(startPosition, newStopPosition));\n\n        assertEquals(1, count);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void jumboRecordingDescriptorEndToEndTest()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long stopPosition;\n\n        final String recordedChannel = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"localhost:3333\")\n            .termLength(TERM_LENGTH)\n            .alias(Tests.generateStringWithSuffix(\"alias\", \"X\", 2000))\n            .build();\n\n        final long subscriptionId = aeronArchive.startRecording(recordedChannel, RECORDED_STREAM_ID, LOCAL);\n        final long recordingId;\n        final int sessionId;\n\n        try (Subscription subscription = aeron.addSubscription(recordedChannel, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(recordedChannel, RECORDED_STREAM_ID))\n        {\n            sessionId = publication.sessionId();\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(counters, sessionId, aeronArchive.archiveId());\n            recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            assertEquals(CommonContext.IPC_CHANNEL, RecordingPos.getSourceIdentity(counters, counterId));\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            stopPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, stopPosition);\n\n            final long joinPosition = subscription.imageBySessionId(sessionId).joinPosition();\n            assertEquals(joinPosition, aeronArchive.getStartPosition(recordingId));\n            assertEquals(stopPosition, aeronArchive.getRecordingPosition(recordingId));\n            assertEquals(NULL_VALUE, aeronArchive.getStopPosition(recordingId));\n            assertEquals(stopPosition, aeronArchive.getMaxRecordedPosition(recordingId));\n        }\n\n        aeronArchive.stopRecording(subscriptionId);\n\n        final int count = aeronArchive.listRecording(\n            recordingId,\n            (controlSessionId,\n            correlationId,\n            recordingId1,\n            startTimestamp,\n            stopTimestamp,\n            startPosition,\n            newStopPosition,\n            initialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId1,\n            streamId,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity) -> assertEquals(recordedChannel, originalChannel));\n\n        assertEquals(1, count);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void purgeRecording()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long stopPosition;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n        final long recordingId;\n        final int sessionId;\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            sessionId = publication.sessionId();\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(counters, sessionId, aeronArchive.archiveId());\n            recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            assertEquals(CommonContext.IPC_CHANNEL, RecordingPos.getSourceIdentity(counters, counterId));\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            stopPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, stopPosition);\n\n            final long joinPosition = subscription.imageBySessionId(sessionId).joinPosition();\n            assertEquals(joinPosition, aeronArchive.getStartPosition(recordingId));\n            assertEquals(stopPosition, aeronArchive.getRecordingPosition(recordingId));\n            assertEquals(NULL_VALUE, aeronArchive.getStopPosition(recordingId));\n            assertEquals(stopPosition, aeronArchive.getMaxRecordedPosition(recordingId));\n        }\n\n        aeronArchive.stopRecording(subscriptionId);\n        Tests.await(() -> stopPosition == aeronArchive.getStopPosition(recordingId));\n\n        assertEquals(recordingId, aeronArchive.findLastMatchingRecording(\n            0, \"alias=\" + RECORDED_CHANNEL_ALIAS, RECORDED_STREAM_ID, sessionId));\n\n        assertFalse(aeronArchive.tryStopRecordingByIdentity(recordingId));\n\n        final ArrayList<String> segmentFiles = Catalog.listSegmentFiles(archiveDir, recordingId);\n\n        assertNotEquals(0, segmentFiles.size());\n\n        aeronArchive.purgeRecording(recordingId);\n\n        final int count = aeronArchive.listRecording(recordingId, new FailingRecordingDescriptorConsumer());\n\n        assertEquals(0, count);\n        Tests.await(() -> Catalog.listSegmentFiles(archiveDir, recordingId).isEmpty());\n\n        for (final String segmentFile : segmentFiles)\n        {\n            assertFalse(new File(archiveDir, segmentFile).exists());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void purgeRecordingFailsIfRecordingIsActive()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long stopPosition;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n        try\n        {\n            final long recordingIdFromCounter;\n            final int sessionId;\n\n            try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n                Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n            {\n                sessionId = publication.sessionId();\n\n                final CountersReader counters = aeron.countersReader();\n                final int counterId = Tests.awaitRecordingCounterId(counters, sessionId, aeronArchive.archiveId());\n                recordingIdFromCounter = RecordingPos.getRecordingId(counters, counterId);\n\n                assertEquals(CommonContext.IPC_CHANNEL, RecordingPos.getSourceIdentity(counters, counterId));\n\n                offer(publication, messageCount, messagePrefix);\n                consume(subscription, messageCount, messagePrefix);\n\n                stopPosition = publication.position();\n                Tests.awaitPosition(counters, counterId, stopPosition);\n\n                final long joinPosition = subscription.imageBySessionId(sessionId).joinPosition();\n                assertEquals(joinPosition, aeronArchive.getStartPosition(recordingIdFromCounter));\n                assertEquals(stopPosition, aeronArchive.getRecordingPosition(recordingIdFromCounter));\n                assertEquals(NULL_VALUE, aeronArchive.getStopPosition(recordingIdFromCounter));\n                assertEquals(stopPosition, aeronArchive.getMaxRecordedPosition(recordingIdFromCounter));\n\n                final long recordingId = aeronArchive.findLastMatchingRecording(\n                    0, \"alias=\" + RECORDED_CHANNEL_ALIAS, RECORDED_STREAM_ID, sessionId);\n                assertEquals(recordingIdFromCounter, recordingId);\n\n                final ArchiveException exception = assertThrows(\n                    ArchiveException.class, () -> aeronArchive.purgeRecording(recordingId));\n                assertThat(exception.getMessage(), endsWith(\"error: cannot purge active recording \" + recordingId));\n\n                final ArrayList<String> segmentFiles = Catalog.listSegmentFiles(archiveDir, recordingId);\n                assertNotEquals(0, segmentFiles.size());\n\n                for (final String segmentFile : segmentFiles)\n                {\n                    assertTrue(new File(archiveDir, segmentFile).exists());\n                }\n            }\n        }\n        finally\n        {\n            aeronArchive.stopRecording(subscriptionId);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SuppressWarnings(\"try\")\n    void purgeRecordingFailsIfThereAreActiveReplays()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long stopPosition;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n        final long recordingId;\n        final int sessionId;\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            sessionId = publication.sessionId();\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(counters, sessionId, aeronArchive.archiveId());\n            recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            assertEquals(CommonContext.IPC_CHANNEL, RecordingPos.getSourceIdentity(counters, counterId));\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            stopPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, stopPosition);\n\n            final long joinPosition = subscription.imageBySessionId(sessionId).joinPosition();\n            assertEquals(joinPosition, aeronArchive.getStartPosition(recordingId));\n            assertEquals(stopPosition, aeronArchive.getRecordingPosition(recordingId));\n            assertEquals(NULL_VALUE, aeronArchive.getStopPosition(recordingId));\n            assertEquals(stopPosition, aeronArchive.getMaxRecordedPosition(recordingId));\n        }\n\n        aeronArchive.stopRecording(subscriptionId);\n        Tests.await(() -> stopPosition == aeronArchive.getStopPosition(recordingId));\n\n        assertEquals(recordingId, aeronArchive.findLastMatchingRecording(\n            0, \"alias=\" + RECORDED_CHANNEL_ALIAS, RECORDED_STREAM_ID, sessionId));\n\n        final long position = 0L;\n        final long length = stopPosition - position;\n\n        try (Subscription ignore = aeronArchive.replay(\n            recordingId, position, length, REPLAY_CHANNEL, REPLAY_STREAM_ID))\n        {\n            final ArchiveException exception = assertThrows(\n                ArchiveException.class, () -> aeronArchive.purgeRecording(recordingId));\n            assertThat(exception.getMessage(),\n                endsWith(\"error: cannot purge recording with active replay \" + recordingId));\n\n            final ArrayList<String> segmentFiles = Catalog.listSegmentFiles(archiveDir, recordingId);\n            assertNotEquals(0, segmentFiles.size());\n\n            for (final String segmentFile : segmentFiles)\n            {\n                assertTrue(new File(archiveDir, segmentFile).exists());\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRecordReplayAndCancelReplayEarly()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final long stopPosition;\n        final int messageCount = 10;\n        final long recordingId;\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeronArchive.addRecordedPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            stopPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, stopPosition);\n\n            assertEquals(stopPosition, aeronArchive.getRecordingPosition(recordingId));\n\n            assertTrue(aeronArchive.tryStopRecordingByIdentity(recordingId));\n\n            while (NULL_POSITION != aeronArchive.getRecordingPosition(recordingId))\n            {\n                Tests.yield();\n            }\n        }\n\n        final long position = 0L;\n        final long length = stopPosition - position;\n\n        final long replaySessionId = aeronArchive.startReplay(\n            recordingId, position, length, REPLAY_CHANNEL, REPLAY_STREAM_ID);\n\n        aeronArchive.stopReplay(replaySessionId);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReplayRecordingFromLateJoinPosition()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            final long currentPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, currentPosition);\n\n            try (Subscription replaySubscription = aeronArchive.replay(\n                recordingId, currentPosition, AeronArchive.NULL_LENGTH, REPLAY_CHANNEL, REPLAY_STREAM_ID))\n            {\n                offer(publication, messageCount, messagePrefix);\n                consume(subscription, messageCount, messagePrefix);\n                consume(replaySubscription, messageCount, messagePrefix);\n\n                final long endPosition = publication.position();\n                assertEquals(endPosition, replaySubscription.imageAtIndex(0).position());\n            }\n        }\n\n        aeronArchive.stopRecording(subscriptionId);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldFindLastMatchingRecordingIdWithFullUri()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            final long currentPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, currentPosition);\n\n            final long lastMatchingRecording = aeronArchive.findLastMatchingRecording(\n                0, RECORDED_CHANNEL, RECORDED_STREAM_ID, publication.sessionId());\n\n            assertEquals(lastMatchingRecording, recordingId);\n        }\n\n        aeronArchive.stopRecording(subscriptionId);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReturnNullValueWithFindLastMatchingRecordingIdDoesNotFindTheRecording()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n            assertNotEquals(RecordingPos.NULL_RECORDING_ID, recordingId);\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            final long currentPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, currentPosition);\n\n            final long lastMatchingRecording = aeronArchive.findLastMatchingRecording(\n                0, RECORDED_CHANNEL, RECORDED_STREAM_ID, publication.sessionId() + 1);\n\n            assertEquals(Aeron.NULL_VALUE, lastMatchingRecording);\n        }\n\n        aeronArchive.stopRecording(subscriptionId);\n    }\n\n    @SuppressWarnings(\"checkstyle:MethodLength\")\n    @Test\n    @SlowTest\n    @InterruptAfter(20)\n    public void shouldRecordThenBoundReplayWithCounter()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 100;\n        final long stopPosition;\n        final int timeout = 3_000;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n        final long recordingIdFromCounter;\n        final int sessionId;\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            sessionId = publication.sessionId();\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(counters, sessionId, aeronArchive.archiveId());\n            recordingIdFromCounter = RecordingPos.getRecordingId(counters, counterId);\n\n            assertEquals(CommonContext.IPC_CHANNEL, RecordingPos.getSourceIdentity(counters, counterId));\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            stopPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, stopPosition);\n\n            final long joinPosition = subscription.imageBySessionId(sessionId).joinPosition();\n            assertEquals(joinPosition, aeronArchive.getStartPosition(recordingIdFromCounter));\n            assertEquals(stopPosition, aeronArchive.getRecordingPosition(recordingIdFromCounter));\n            assertEquals(NULL_VALUE, aeronArchive.getStopPosition(recordingIdFromCounter));\n            assertEquals(stopPosition, aeronArchive.getMaxRecordedPosition(recordingIdFromCounter));\n        }\n\n        recordingSignalConsumer.reset();\n        aeronArchive.stopRecording(subscriptionId);\n        awaitSignal(aeronArchive, recordingSignalConsumer, recordingIdFromCounter, RecordingSignal.STOP);\n\n        final long recordingId = aeronArchive.findLastMatchingRecording(\n            0, \"alias=\" + RECORDED_CHANNEL_ALIAS, RECORDED_STREAM_ID, sessionId);\n        assertEquals(recordingIdFromCounter, recordingId);\n\n        final Counter boundingCounter = aeron.addCounter(10000, \"bounding counter\");\n\n        final RecordingDescriptorCollector recordingDescriptorCollector = new RecordingDescriptorCollector(1);\n        assertEquals(1, aeronArchive.listRecording(recordingId, recordingDescriptorCollector.reset()));\n        final RecordingDescriptor recordingDescriptor = recordingDescriptorCollector.descriptors().get(0);\n\n        assertNotEquals(NULL_VALUE, recordingDescriptor.stopPosition());\n        final long halfLength = (recordingDescriptor.stopPosition() - recordingDescriptor.startPosition()) / 2;\n        final long halfPosition = recordingDescriptor.startPosition() + halfLength;\n        final long tqPosition = recordingDescriptor.startPosition() + halfLength + (halfLength / 2);\n\n        boundingCounter.setRelease(0);\n\n        final ReplayParams replayParams = new ReplayParams()\n            .position(recordingDescriptor.startPosition())\n            .length(Long.MAX_VALUE)\n            .boundingLimitCounterId(boundingCounter.id());\n\n        final long replaySessionId = aeronArchive.startReplay(\n            recordingId, REPLAY_CHANNEL, REPLAY_STREAM_ID, replayParams);\n\n        final String channel = new ChannelUriStringBuilder(REPLAY_CHANNEL).sessionId((int)replaySessionId).build();\n\n        final AtomicReference<Image> replayImage = new AtomicReference<>();\n        try (Subscription replaySubscription = aeron.addSubscription(\n            channel, REPLAY_STREAM_ID, replayImage::set, image -> {}))\n        {\n            boundingCounter.setRelease(halfPosition);\n\n            while (null == replayImage.get())\n            {\n                Tests.yieldingIdle(\"replay image did not become available\");\n            }\n\n            final Supplier<String> halfErrorMessage =\n                () -> \"replayImage.position(\" + replayImage.get().position() + \") < halfPosition(\" + halfPosition + \")\";\n\n            while (replayImage.get().position() < halfPosition)\n            {\n                if (0 == replaySubscription.poll((buffer, offset, length, header) -> {}, 20))\n                {\n                    Tests.yieldingIdle(halfErrorMessage);\n                }\n            }\n\n            final long halfPollDeadline = System.currentTimeMillis() + timeout;\n            while (System.currentTimeMillis() < halfPollDeadline)\n            {\n                replaySubscription.poll((buffer, offset, length, header) -> {}, 20);\n                assertThat(replayImage.get().position(), lessThanOrEqualTo(halfPosition));\n            }\n\n            boundingCounter.setRelease(tqPosition);\n\n            final Supplier<String> tqErrorMessage =\n                () -> \"replayImage.position(\" + replayImage.get().position() + \") < tqPosition(\" + tqPosition + \")\";\n\n            while (replayImage.get().position() < tqPosition)\n            {\n                if (0 == replaySubscription.poll((buffer, offset, length, header) -> {}, 20))\n                {\n                    Tests.yieldingIdle(tqErrorMessage);\n                }\n            }\n\n            final long tqPollDeadline = System.currentTimeMillis() + timeout;\n            while (System.currentTimeMillis() < tqPollDeadline)\n            {\n                replaySubscription.poll((buffer, offset, length, header) -> {}, 20);\n                assertThat(replayImage.get().position(), lessThanOrEqualTo(tqPosition));\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldErrorReplayFileIoMaxLengthLessThanMtu()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 100;\n        final long stopPosition;\n\n        final long subscriptionId = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n        final long recordingIdFromCounter;\n        final int sessionId;\n\n        try (Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = aeron.addPublication(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n            sessionId = publication.sessionId();\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(counters, sessionId, aeronArchive.archiveId());\n            recordingIdFromCounter = RecordingPos.getRecordingId(counters, counterId);\n\n            assertEquals(CommonContext.IPC_CHANNEL, RecordingPos.getSourceIdentity(counters, counterId));\n\n            offer(publication, messageCount, messagePrefix);\n            consume(subscription, messageCount, messagePrefix);\n\n            stopPosition = publication.position();\n            Tests.awaitPosition(counters, counterId, stopPosition);\n        }\n\n        aeronArchive.stopRecording(subscriptionId);\n\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(1);\n        assertEquals(1, aeronArchive.listRecording(recordingIdFromCounter, collector.reset()));\n\n        final int invalidFileIoMaxLength = collector.descriptors().get(0).mtuLength() - 1;\n\n        final long correlationId = aeron.nextCorrelationId();\n        assertTrue(aeronArchive.archiveProxy().replay(\n            recordingIdFromCounter,\n            REPLAY_CHANNEL,\n            REPLAY_STREAM_ID,\n            new ReplayParams().fileIoMaxLength(invalidFileIoMaxLength),\n            correlationId,\n            aeronArchive.controlSessionId()));\n\n        String error;\n        while (null == (error = aeronArchive.pollForErrorResponse()))\n        {\n            Tests.yieldingIdle(\"Error not reported\");\n        }\n\n        assertThat(error, containsString(\"mtuLength\"));\n        assertThat(error, containsString(\"fileIoMaxLength\"));\n    }\n\n    @Test\n    void shouldNotListRecordingThatWasPurged()\n    {\n        final RecordingResult recording1 = recordData(aeronArchive);\n        final RecordingResult recording2 = recordData(aeronArchive);\n        final RecordingResult recording3 = recordData(aeronArchive);\n\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(3);\n        assertEquals(3, aeronArchive.listRecordings(NULL_VALUE, 100, collector.reset()));\n        assertEquals(recording1.recordingId(), collector.descriptors().get(0).recordingId());\n        assertEquals(recording2.recordingId(), collector.descriptors().get(1).recordingId());\n        assertEquals(recording3.recordingId(), collector.descriptors().get(2).recordingId());\n\n        final RecordingDescriptor descriptor = collector.descriptors().get(0);\n        final ChannelUri channelUri = ChannelUri.parse(descriptor.originalChannel());\n        channelUri.remove(CommonContext.SESSION_ID_PARAM_NAME);\n        final int streamId = descriptor.streamId();\n        assertEquals(3, aeronArchive.listRecordingsForUri(\n            NULL_VALUE, 100, channelUri.toString(), streamId, collector.reset()));\n        assertEquals(recording1.recordingId(), collector.descriptors().get(0).recordingId());\n        assertEquals(recording2.recordingId(), collector.descriptors().get(1).recordingId());\n        assertEquals(recording3.recordingId(), collector.descriptors().get(2).recordingId());\n\n        assertEquals(1, aeronArchive.listRecording(recording1.recordingId(), collector.reset()));\n        assertEquals(recording1.recordingId(), collector.descriptors().get(0).recordingId());\n\n        assertEquals(1, aeronArchive.listRecording(recording2.recordingId(), collector.reset()));\n        assertEquals(recording2.recordingId(), collector.descriptors().get(0).recordingId());\n\n        assertEquals(1, aeronArchive.listRecording(recording3.recordingId(), collector.reset()));\n        assertEquals(recording3.recordingId(), collector.descriptors().get(0).recordingId());\n\n        assertNotEquals(0, aeronArchive.purgeRecording(recording2.recordingId()));\n\n        assertEquals(1, aeronArchive.listRecording(recording1.recordingId(), collector.reset()));\n        assertEquals(recording1.recordingId(), collector.descriptors().get(0).recordingId());\n\n        assertEquals(0, aeronArchive.listRecording(recording2.recordingId(), collector.reset()));\n        assertThat(collector.descriptors(), hasSize(0));\n\n        assertEquals(1, aeronArchive.listRecording(recording3.recordingId(), collector.reset()));\n        assertEquals(recording3.recordingId(), collector.descriptors().get(0).recordingId());\n\n        assertEquals(2, aeronArchive.listRecordings(NULL_VALUE, 100, collector.reset()));\n        assertEquals(recording1.recordingId(), collector.descriptors().get(0).recordingId());\n        assertEquals(recording3.recordingId(), collector.descriptors().get(1).recordingId());\n\n        assertEquals(2, aeronArchive.listRecordingsForUri(\n            NULL_VALUE, 100, channelUri.toString(), streamId, collector.reset()));\n        assertEquals(recording1.recordingId(), collector.descriptors().get(0).recordingId());\n        assertEquals(recording3.recordingId(), collector.descriptors().get(1).recordingId());\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shakeListingAndPurgingRecordings()\n    {\n        final int recordingCount = 1000;\n\n        final String channel = \"aeron:ipc?term-length=64k\";\n        final Catalog catalog = archive.context().catalog();\n\n        for (int i = 0; i < recordingCount; i++)\n        {\n            final long startTimestamp = System.currentTimeMillis();\n            final long startPosition = (long)Math.pow(2, ThreadLocalRandom.current().nextInt(10, 30));\n            catalog.addNewRecording(\n                startPosition,\n                startPosition + startPosition * 10,\n                startTimestamp,\n                startTimestamp + i * 1_000,\n                ThreadLocalRandom.current().nextInt(),\n                1024 * 1024,\n                LogBufferDescriptor.TERM_MIN_LENGTH,\n                1408,\n                ThreadLocalRandom.current().nextInt(),\n                10000 + i,\n                channel,\n                channel,\n                channel);\n        }\n\n        final LongArrayList existingRecordingIds = new LongArrayList(recordingCount, NULL_VALUE);\n        final Long2LongHashMap recordingIdToStreamId =\n            new Long2LongHashMap(recordingCount, Hashing.DEFAULT_LOAD_FACTOR, NULL_VALUE);\n        int count = aeronArchive.listRecordings(\n            0,\n            Integer.MAX_VALUE,\n            (controlSessionId,\n            correlationId,\n            recordingId,\n            startTimestamp,\n            stopTimestamp,\n            startPosition,\n            newStopPosition,\n            initialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId,\n            streamId,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity) ->\n            {\n                existingRecordingIds.addLong(recordingId);\n                recordingIdToStreamId.put(recordingId, streamId);\n            });\n\n        final Supplier<String> details = () -> existingRecordingIds + \" \" + recordingIdToStreamId;\n        assertEquals(recordingCount, count, details);\n        assertEquals(recordingCount, existingRecordingIds.size(), details);\n        assertEquals(recordingCount, recordingIdToStreamId.size(), details);\n\n        final FailingRecordingDescriptorConsumer failingRecordingDescriptorConsumer =\n            new FailingRecordingDescriptorConsumer();\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(recordingCount);\n        final long seed = ThreadLocalRandom.current().nextLong();\n        final Random random = new Random(seed);\n        try\n        {\n            for (int i = 0; i < 20; i++)\n            {\n                final int victimIndex = random.nextInt(existingRecordingIds.size());\n                final long recordingId = existingRecordingIds.removeAt(victimIndex);\n\n                aeronArchive.purgeRecording(recordingId);\n\n                count = aeronArchive.listRecording(recordingId, failingRecordingDescriptorConsumer);\n                assertEquals(0, count);\n\n                final int fromIndex = random.nextInt(existingRecordingIds.size());\n                final long fromRecordingId = existingRecordingIds.get(fromIndex);\n                final int recordCount = random.nextInt(existingRecordingIds.size() + 1) + 1;\n                final int expectedCount = Math.min(recordCount, existingRecordingIds.size() - fromIndex);\n\n                count = aeronArchive.listRecordings(fromRecordingId, recordCount, collector.reset());\n                assertEquals(expectedCount, count);\n\n                final LongHashSet foundRecordingIds = new LongHashSet();\n                for (final RecordingDescriptor descriptor : collector.descriptors())\n                {\n                    final long recId = descriptor.recordingId();\n                    assertEquals(recordingIdToStreamId.get(recId), descriptor.streamId());\n                    assertTrue(existingRecordingIds.contains(recId));\n                    foundRecordingIds.add(recId);\n                }\n                assertEquals(expectedCount, foundRecordingIds.size());\n            }\n        }\n        catch (final Exception e)\n        {\n            fail(\"seed=\" + seed, e);\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldStopReplayWithoutConsumingAnEntireRecording()\n    {\n        testStopReplay(aeronArchive::stopReplay);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldStopAllReplaysWithoutConsumingAnEntireRecording()\n    {\n        testStopReplay((replaySessionId) -> aeronArchive.stopAllReplays(RecordingPos.NULL_RECORDING_ID));\n    }\n\n    private void testStopReplay(final LongConsumer stopCommand)\n    {\n        final long recordingId;\n        final long stopPosition;\n        try (ExclusivePublication publication =\n            aeronArchive.addRecordedExclusivePublication(\"aeron:ipc?term-length=64K\", 555))\n        {\n            final int counterId = Tests.awaitRecordingCounterId(\n                aeron.countersReader(), publication.sessionId(), aeronArchive.archiveId());\n\n            offer(publication, 10000, \"test-\");\n\n            stopPosition = publication.position();\n            Tests.awaitPosition(aeron.countersReader(), counterId, stopPosition);\n            recordingId = RecordingPos.getRecordingId(aeron.countersReader(), counterId);\n        }\n\n        final String replayChannel = \"aeron:udp?endpoint=localhost:17171\";\n        final int replayStreamId = 777;\n        final long replaySessionId =\n            aeronArchive.startReplay(recordingId, 0, Long.MAX_VALUE, replayChannel, replayStreamId);\n        final AtomicBoolean imageUnavailable = new AtomicBoolean(false);\n        final Subscription subscription = aeron.addSubscription(\n            ChannelUri.addSessionId(replayChannel, (int)replaySessionId),\n            replayStreamId,\n            image -> {},\n            image -> imageUnavailable.set(true));\n        Tests.awaitConnected(subscription);\n        final Image image = subscription.imageAtIndex(0);\n        final MutableInteger counter = new MutableInteger();\n        final FragmentHandler fragmentHandler = (buffer, offset, length, header) -> counter.increment();\n        while (counter.get() < 5)\n        {\n            if (0 == image.poll(fragmentHandler, 1))\n            {\n                Tests.yield();\n            }\n        }\n\n        stopCommand.accept(replaySessionId);\n\n        final long startTimeNs = System.nanoTime();\n        Tests.await(imageUnavailable::get);\n        final long endTimeNs = System.nanoTime();\n        assertThat(endTimeNs - startTimeNs, lessThan(driver.context().imageLivenessTimeoutNs()));\n\n        subscription.close();\n    }\n\n    private static final class FailingRecordingDescriptorConsumer implements RecordingDescriptorConsumer\n    {\n        public void onRecordingDescriptor(\n            final long controlSessionId,\n            final long correlationId,\n            final long recordingId,\n            final long startTimestamp,\n            final long stopTimestamp,\n            final long startPosition,\n            final long stopPosition,\n            final int initialTermId,\n            final int segmentFileLength,\n            final int termBufferLength,\n            final int mtuLength,\n            final int sessionId,\n            final int streamId,\n            final String strippedChannel,\n            final String originalChannel,\n            final String sourceIdentity)\n        {\n            fail(\"unexpected recording \" + recordingId);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/CatalogWithJumboRecordingsAndGapsTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.File;\nimport java.util.List;\nimport java.util.stream.IntStream;\n\nimport static io.aeron.archive.AbstractListRecordingsSession.MAX_SCANS_PER_WORK_CYCLE;\nimport static io.aeron.archive.Catalog.PAGE_SIZE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.client.AeronArchive.NULL_TIMESTAMP;\nimport static io.aeron.archive.codecs.RecordingState.INVALID;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static io.aeron.test.Tests.generateStringWithSuffix;\nimport static java.util.Arrays.asList;\nimport static org.agrona.BitUtil.next;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass CatalogWithJumboRecordingsAndGapsTest\n{\n    private static final int MTU_LENGTH = PAGE_SIZE * 4;\n    private static final int TERM_LENGTH = MTU_LENGTH * 8;\n    private static final int SEGMENT_LENGTH = TERM_LENGTH * 4;\n    private static final int NUM_RECORDINGS = MAX_SCANS_PER_WORK_CYCLE * 3;\n    private static final String[] STRIPPED_CHANNELS =\n        new String[]{ generateStringWithSuffix(\"ch\", \"1\", 2000), \"ch2\", \"ch3\" };\n    private static final String[] ORIGINAL_CHANNELS =\n        new String[]{\n            \"ch1?tag=OK\",\n            generateStringWithSuffix(\"ch2?tag=O\", \"K\", 2500),\n            \"ch3?tag=OK|endpoint=localhost:8089\" };\n    private static final String[] SOURCE_IDENTITIES =\n        new String[]{ \"src1\", \"src2\", generateStringWithSuffix(\"src\", \"3\", 1999) };\n\n    private final File archiveDir = ArchiveTests.makeTestDirectory();\n    private final CachedEpochClock epochClock = new CachedEpochClock();\n    private long[] recordingIds;\n\n    private TestMediaDriver mediaDriver;\n    private Archive archive;\n    private Aeron aeron;\n    private AeronArchive aeronArchive;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before()\n    {\n        epochClock.update(1);\n        recordingIds = new long[NUM_RECORDINGS];\n\n        try (Catalog catalog = new Catalog(archiveDir, null, 0, 1024, epochClock, null, null))\n        {\n            for (int i = 0, current = 0; i < recordingIds.length; i++)\n            {\n                recordingIds[i] = catalog.addNewRecording(\n                    i,\n                    NULL_POSITION,\n                    i * 100L,\n                    NULL_TIMESTAMP,\n                    0,\n                    SEGMENT_LENGTH,\n                    TERM_LENGTH,\n                    MTU_LENGTH,\n                    current,\n                    current,\n                    STRIPPED_CHANNELS[current],\n                    ORIGINAL_CHANNELS[current],\n                    SOURCE_IDENTITIES[current]);\n\n                current = next(current, 3);\n            }\n\n            changeRecordingsState(catalog, 0, 3);\n            changeRecordingsState(catalog, 20, 30);\n            changeRecordingsState(catalog, 100, 111);\n            changeRecordingsState(catalog, recordingIds.length - 5, recordingIds.length);\n        }\n\n        final String aeronDirectoryName = CommonContext.generateRandomDirName();\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDirectoryName)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(false)\n            .publicationTermBufferLength(TERM_MIN_LENGTH)\n            .ipcTermBufferLength(TERM_MIN_LENGTH)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true);\n\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .catalogCapacity(ArchiveSystemTests.CATALOG_CAPACITY)\n            .aeronDirectoryName(aeronDirectoryName)\n            .archiveDir(archiveDir)\n            .fileSyncLevel(0)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n        mediaDriver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n        archive = Archive.launch(archiveCtx);\n        systemTestWatcher.dataCollector().add(archiveCtx.archiveDir());\n\n        aeron = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(aeronDirectoryName));\n\n        aeronArchive = AeronArchive.connect(\n            TestContexts.localhostAeronArchive()\n                .aeron(aeron));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeronArchive, aeron, archive, mediaDriver);\n        IoUtil.delete(archiveDir, false);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void listRecording()\n    {\n        final int count = aeronArchive.listRecording(recordingIds[3],\n            (controlSessionId,\n            correlationId,\n            recordingId,\n            startTimestamp,\n            stopTimestamp,\n            startPosition,\n            stopPosition,\n            initialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId,\n            streamId,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity) ->\n            {\n                assertEquals(300, startTimestamp);\n                assertEquals(\"ch1?tag=OK\", originalChannel);\n                assertEquals(\"src1\", sourceIdentity);\n            });\n\n        assertEquals(1, count);\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(10)\n    @MethodSource(\"listRecordingsArguments\")\n    void listRecordings(final long fromRecordingId, final int recordCount, final int expectedRecordCount)\n    {\n        final MutableInteger callCount = new MutableInteger();\n\n        final int count = aeronArchive.listRecordings(\n            fromRecordingId,\n            recordCount,\n            (controlSessionId,\n            correlationId,\n            recordingId,\n            startTimestamp,\n            stopTimestamp,\n            startPosition,\n            stopPosition,\n            initialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId,\n            streamId,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity) -> callCount.increment());\n\n        assertEquals(expectedRecordCount, count);\n        assertEquals(expectedRecordCount, callCount.get());\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(10)\n    @MethodSource(\"listRecordingsForUriArguments\")\n    void listRecordingsForUri(\n        final long fromRecordingId,\n        final int recordCount,\n        final String channelFragment,\n        final int streamId,\n        final int expectedRecordCount)\n    {\n        final MutableInteger callCount = new MutableInteger();\n\n        final int count = aeronArchive.listRecordingsForUri(\n            fromRecordingId,\n            recordCount,\n            channelFragment,\n            streamId,\n            (controlSessionId,\n            correlationId,\n            recordingId,\n            startTimestamp,\n            stopTimestamp,\n            startPosition,\n            stopPosition,\n            initialTermId,\n            segmentFileLength,\n            termBufferLength,\n            mtuLength,\n            sessionId,\n            streamId1,\n            strippedChannel,\n            originalChannel,\n            sourceIdentity) -> callCount.increment());\n\n        assertEquals(expectedRecordCount, count);\n        assertEquals(expectedRecordCount, callCount.get());\n    }\n\n    private void changeRecordingsState(final Catalog catalog, final int from, final int to)\n    {\n        IntStream.range(from, to).forEach(i -> catalog.changeState(recordingIds[i], INVALID));\n    }\n\n    private static List<Arguments> listRecordingsArguments()\n    {\n        return asList(\n            arguments(Long.MAX_VALUE, 5, 0),\n            arguments(-1, 10, 10),\n            arguments(2, 10, 10),\n            arguments(5, MAX_SCANS_PER_WORK_CYCLE, MAX_SCANS_PER_WORK_CYCLE),\n            arguments(25, 100, 100),\n            arguments(10, MAX_SCANS_PER_WORK_CYCLE * 2, MAX_SCANS_PER_WORK_CYCLE * 2),\n            arguments(NUM_RECORDINGS - 10, MAX_SCANS_PER_WORK_CYCLE, 5));\n    }\n\n    private static List<Arguments> listRecordingsForUriArguments()\n    {\n        return asList(\n            arguments(Long.MAX_VALUE, 5, ORIGINAL_CHANNELS[2], 2, 0),\n            arguments(-1, 10, ORIGINAL_CHANNELS[2], 2, 10),\n            arguments(2, 10, ORIGINAL_CHANNELS[2], 2, 10),\n            arguments(-1, 10, ORIGINAL_CHANNELS[2], 0, 0),\n            arguments(-1, 10, ORIGINAL_CHANNELS[1], 2, 0),\n            arguments(5, MAX_SCANS_PER_WORK_CYCLE, ORIGINAL_CHANNELS[2], 2, MAX_SCANS_PER_WORK_CYCLE - 11),\n            arguments(10, MAX_SCANS_PER_WORK_CYCLE * 2, ORIGINAL_CHANNELS[2], 2, MAX_SCANS_PER_WORK_CYCLE - 13),\n            arguments(NUM_RECORDINGS - 10, MAX_SCANS_PER_WORK_CYCLE, ORIGINAL_CHANNELS[2], 2, 2));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/DualReplayMergeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ReplayMerge;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.samples.archive.RecordingDescriptor;\nimport io.aeron.samples.archive.RecordingDescriptorCollector;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static io.aeron.CommonContext.MDC_CONTROL_MODE_MANUAL;\nimport static io.aeron.CommonContext.UDP_MEDIA;\nimport static io.aeron.archive.ArchiveSystemTests.CATALOG_CAPACITY;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class DualReplayMergeTest\n{\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private static final int STREAM_ID = 10000;\n    private static final String LIVE_ENDPOINT = \"239.192.12.87:20123\";\n    private static final String LIVE_INTERFACE = \"127.0.0.1\";\n    private static final String REPLAY_ENDPOINT_A = \"localhost:0\";\n    private static final String REPLAY_ENDPOINT_B = \"localhost:0\";\n\n    private final MediaDriver.Context driverCtx = new MediaDriver.Context();\n\n    private TestMediaDriver driver;\n    private Archive archive;\n\n    private void launch()\n    {\n        driverCtx.dirDeleteOnStart(true);\n\n        driver = TestMediaDriver.launch(driverCtx, watcher);\n\n        final File archiveDir = new File(SystemUtil.tmpDirName(), \"archive\");\n        archive = Archive.launch(\n            TestContexts.localhostArchive()\n                .catalogCapacity(CATALOG_CAPACITY)\n                .aeronDirectoryName(driver.context().aeronDirectoryName())\n                .archiveDir(archiveDir)\n                .recordingEventsEnabled(false)\n                .threadingMode(ArchiveThreadingMode.SHARED)\n                .deleteArchiveOnStart(true));\n\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n        watcher.dataCollector().add(archive.context().archiveDir());\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietCloseAll(archive, driver);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(20)\n    @SuppressWarnings(\"methodlength\")\n    void shouldMergeTwoIndependentStreams(final boolean concurrent)\n    {\n        assumeTrue(OS.WINDOWS != OS.current() || TestMediaDriver.shouldRunJavaMediaDriver());\n\n        launch();\n\n        final int sessionIdA = 1111;\n        final int sessionIdB = 2222;\n\n        final String publicationChannelA = new ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .endpoint(LIVE_ENDPOINT)\n            .networkInterface(LIVE_INTERFACE)\n            .sessionId(sessionIdA)\n            .spiesSimulateConnection(true)\n            .build();\n        final String publicationChannelB = new ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .endpoint(LIVE_ENDPOINT)\n            .networkInterface(LIVE_INTERFACE)\n            .sessionId(sessionIdB)\n            .spiesSimulateConnection(true)\n            .build();\n        final String subscriptionChannelA = new ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .controlMode(MDC_CONTROL_MODE_MANUAL)\n            .sessionId(sessionIdA)\n            .alias(\"B\")\n            .build();\n        final String subscriptionChannelB = new ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .controlMode(MDC_CONTROL_MODE_MANUAL)\n            .sessionId(sessionIdB)\n            .alias(\"B\")\n            .build();\n        final String recordingChannel = new ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .endpoint(LIVE_ENDPOINT)\n            .networkInterface(LIVE_INTERFACE)\n            .build();\n        final String replayChannelA = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .sessionId(sessionIdA)\n            .build();\n        final String replayChannelB = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .sessionId(sessionIdB)\n            .build();\n        final String liveDestination = new ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .endpoint(LIVE_ENDPOINT)\n            .networkInterface(LIVE_INTERFACE)\n            .build();\n        final String replayDestinationA = new ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .endpoint(REPLAY_ENDPOINT_A)\n            .build();\n        final String replayDestinationB = new ChannelUriStringBuilder()\n            .media(UDP_MEDIA)\n            .endpoint(REPLAY_ENDPOINT_B)\n            .build();\n\n        final AeronArchive.Context aeronArchiveCtxA = new AeronArchive.Context()\n            .aeronDirectoryName(driver.context().aeronDirectoryName())\n            .controlRequestChannel(archive.context().localControlChannel())\n            .controlResponseChannel(archive.context().localControlChannel());\n        final AeronArchive.Context aeronArchiveCtxB = new AeronArchive.Context()\n            .aeronDirectoryName(driver.context().aeronDirectoryName())\n            .controlRequestChannel(archive.context().localControlChannel())\n            .controlResponseChannel(archive.context().localControlChannel())\n            .controlResponseStreamId(aeronArchiveCtxA.controlRequestStreamId() + 1);\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n            AeronArchive aeronArchiveA = AeronArchive.connect(aeronArchiveCtxA);\n            AeronArchive aeronArchiveB = AeronArchive.connect(aeronArchiveCtxB);\n            Publication pubA = aeron.addExclusivePublication(publicationChannelA, STREAM_ID);\n            Publication pubB = aeron.addExclusivePublication(publicationChannelB, STREAM_ID))\n        {\n            final long subscriptionId = aeronArchiveA.startRecording(recordingChannel, STREAM_ID, SourceLocation.LOCAL);\n\n            final int numMessages = 10;\n            sendMessages(pubA, numMessages);\n            sendMessages(pubB, numMessages);\n\n            final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(2);\n\n            long recordingIdA = -1;\n            long recordingIdB = -1;\n\n            while (2 > aeronArchiveA.listRecordings(0, Integer.MAX_VALUE, collector.reset()))\n            {\n                Tests.yield();\n            }\n\n            for (final RecordingDescriptor descriptor : collector.descriptors())\n            {\n                if (descriptor.sessionId() == sessionIdA)\n                {\n                    recordingIdA = descriptor.recordingId();\n                }\n                else if (descriptor.sessionId() == sessionIdB)\n                {\n                    recordingIdB = descriptor.recordingId();\n                }\n            }\n\n            assertNotEquals(-1, recordingIdA);\n            assertNotEquals(-1, recordingIdB);\n\n            try (Subscription subA = aeron.addSubscription(subscriptionChannelA, STREAM_ID);\n                Subscription subB = aeron.addSubscription(subscriptionChannelB, STREAM_ID))\n            {\n                final ReplayMerge replayMergeA = new ReplayMerge(\n                    subA, aeronArchiveA, replayChannelA, replayDestinationA, liveDestination, recordingIdA, 0);\n                final ReplayMerge replayMergeB = new ReplayMerge(\n                    subB, aeronArchiveB, replayChannelB, replayDestinationB, liveDestination, recordingIdB, 0);\n                final List<InProcessMerge> merges = Arrays.asList(\n                    new InProcessMerge(\"A\", replayMergeA), new InProcessMerge(\"B\", replayMergeB));\n\n                if (concurrent)\n                {\n                    processConcurrently(merges, numMessages);\n                }\n                else\n                {\n                    processSequentially(merges, numMessages);\n                }\n            }\n\n            aeronArchiveA.stopRecording(subscriptionId);\n        }\n    }\n\n    private static void processConcurrently(final List<InProcessMerge> merges, final int numMessages)\n    {\n        boolean allComplete;\n        do\n        {\n            allComplete = true;\n\n            for (final InProcessMerge merge : merges)\n            {\n                try\n                {\n                    final int workCount = merge.doWork();\n                    if (0 == workCount)\n                    {\n                        Tests.yield();\n                    }\n                }\n                catch (final Exception ex)\n                {\n                    throw new RuntimeException(\n                        \"replayMerge=\" + merge.replayMerge.subscription().channel() + \" merges=\" + merges, ex);\n                }\n            }\n\n            for (final InProcessMerge merge : merges)\n            {\n                allComplete &= merge.isComplete(numMessages);\n            }\n        }\n        while (!allComplete);\n    }\n\n    private static void processSequentially(final List<InProcessMerge> merges, final int numMessages)\n    {\n        for (final InProcessMerge merge : merges)\n        {\n            try\n            {\n                while (!merge.isComplete(numMessages))\n                {\n                    final int workCount = merge.doWork();\n                    if (0 == workCount)\n                    {\n                        Tests.yield();\n                    }\n                }\n            }\n            catch (final Exception ex)\n            {\n                throw new RuntimeException(\n                    \"replayMerge=\" + merge.replayMerge.subscription().channel() + \" merges=\" + merges, ex);\n            }\n        }\n    }\n\n    private void sendMessages(final Publication pubA, final int numMessages)\n    {\n        final DirectBuffer buffer = new UnsafeBuffer(\"this is a test message\".getBytes(US_ASCII));\n        for (int i = 0; i < numMessages; i++)\n        {\n            while (0 > pubA.offer(buffer))\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    private static final class InProcessMerge\n    {\n        private final String name;\n        private final ReplayMerge replayMerge;\n        private int messageCount = 0;\n\n        private InProcessMerge(final String name, final ReplayMerge replayMerge)\n        {\n            this.name = name;\n            this.replayMerge = replayMerge;\n        }\n\n        boolean isComplete(final int totalMessages)\n        {\n            return replayMerge.isMerged() && totalMessages <= messageCount;\n        }\n\n        int doWork()\n        {\n            final int fragments = replayMerge.poll((buffer, offset, length, header) -> {}, 1);\n            messageCount += fragments;\n            return fragments;\n        }\n\n        public String toString()\n        {\n            return \"InProcessMerge{\" +\n                \"name=\" + name +\n                \", messageCount=\" + messageCount +\n                '}';\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ExtendRecordingTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.RecordingSignalConsumer;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.Configuration;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.samples.archive.RecordingDescriptor;\nimport io.aeron.samples.archive.RecordingDescriptorCollector;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.hamcrest.Matcher;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.InOrder;\nimport org.mockito.Mockito;\n\nimport java.nio.file.Path;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.codecs.RecordingSignal.*;\nimport static io.aeron.archive.codecs.SourceLocation.LOCAL;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ExtendRecordingTest\n{\n    private static final String MY_ALIAS = \"my-log\";\n    private static final String MESSAGE_PREFIX = \"Message-Prefix-\";\n    private static final int MTU_LENGTH = Configuration.mtuLength();\n\n    private static final int RECORDED_STREAM_ID = 33;\n    private static final String RECORDED_CHANNEL = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3333\")\n        .mtu(MTU_LENGTH)\n        .termLength(ArchiveSystemTests.TERM_LENGTH)\n        .alias(MY_ALIAS)\n        .build();\n\n    private static final int REPLAY_STREAM_ID = 66;\n    private static final String REPLAY_CHANNEL = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:6666\")\n        .build();\n\n    private static final String EXTEND_CHANNEL = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3333\")\n        .build();\n\n    private TestMediaDriver driver;\n    private Archive archive;\n    private Aeron aeron;\n    private AeronArchive aeronArchive;\n\n    private final RecordingSignalConsumer mockRecordingSignalConsumer = mock(RecordingSignalConsumer.class);\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before(@TempDir final Path tempDir)\n    {\n        final String aeronDirectoryName = CommonContext.generateRandomDirName();\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDirectoryName)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(false)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true);\n\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .catalogCapacity(ArchiveSystemTests.CATALOG_CAPACITY)\n            .aeronDirectoryName(aeronDirectoryName)\n            .archiveDir(tempDir.resolve(\"archive\").toFile())\n            .fileSyncLevel(0)\n            .segmentFileLength(TERM_MIN_LENGTH)\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .deleteArchiveOnStart(true);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n        archive = Archive.launch(archiveCtx);\n        systemTestWatcher.dataCollector().add(archiveCtx.archiveDir());\n\n        aeron = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(aeronDirectoryName));\n\n        aeronArchive = AeronArchive.connect(\n            TestContexts.localhostAeronArchive()\n                .recordingSignalConsumer(mockRecordingSignalConsumer)\n                .aeron(aeron));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeronArchive, aeron, archive, driver);\n    }\n\n    private interface PublicationFactory\n    {\n        Publication create(Aeron aeron, String uri, int streamId);\n    }\n\n    @InterruptAfter(10)\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldExtendRecordingAndReplay(final boolean exclusive)\n    {\n        final long controlSessionId = aeronArchive.controlSessionId();\n        final int messageCount = 10;\n        final long subscriptionIdOne;\n        final long subscriptionIdTwo;\n        final long stopOne;\n        final long stopTwo;\n        final long recordingId;\n\n        final PublicationFactory publicationFactory =\n            exclusive ? Aeron::addExclusivePublication : Aeron::addPublication;\n\n        try (Publication publication = publicationFactory.create(aeron, RECORDED_CHANNEL, RECORDED_STREAM_ID);\n            Subscription subscription = aeron.addSubscription(RECORDED_CHANNEL, RECORDED_STREAM_ID))\n        {\n\n            subscriptionIdOne = aeronArchive.startRecording(RECORDED_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n            pollForRecordingSignal(aeronArchive);\n\n            try\n            {\n                offer(publication, 0, messageCount);\n\n                final CountersReader counters = aeron.countersReader();\n                final int counterId =\n                    RecordingPos.findCounterIdBySession(counters, publication.sessionId(), aeronArchive.archiveId());\n                recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n                consume(subscription, 0, messageCount);\n\n                stopOne = publication.position();\n                Tests.awaitPosition(counters, counterId, stopOne);\n            }\n            finally\n            {\n                aeronArchive.stopRecording(subscriptionIdOne);\n                pollForRecordingSignal(aeronArchive);\n            }\n        }\n\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n        assertEquals(\n            1L, aeronArchive.listRecordingsForUri(0, 10, \"alias=\" + MY_ALIAS, RECORDED_STREAM_ID, collector.reset()));\n        final RecordingDescriptor recording = collector.descriptors().get(0);\n        assertEquals(recordingId, recording.recordingId());\n\n        final String publicationExtendChannel = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .endpoint(\"localhost:3333\")\n            .initialPosition(recording.stopPosition(), recording.initialTermId(), recording.termBufferLength())\n            .mtu(recording.mtuLength())\n            .alias(MY_ALIAS)\n            .build();\n\n        try (Subscription subscription = Tests.reAddSubscription(aeron, EXTEND_CHANNEL, RECORDED_STREAM_ID);\n            Publication publication = publicationFactory.create(aeron, publicationExtendChannel, RECORDED_STREAM_ID))\n        {\n            subscriptionIdTwo = aeronArchive.extendRecording(recordingId, EXTEND_CHANNEL, RECORDED_STREAM_ID, LOCAL);\n            pollForRecordingSignal(aeronArchive);\n\n            try\n            {\n                offer(publication, messageCount, messageCount);\n\n                final CountersReader counters = aeron.countersReader();\n                final int counterId =\n                    RecordingPos.findCounterIdBySession(counters, publication.sessionId(), aeronArchive.archiveId());\n\n                consume(subscription, messageCount, messageCount);\n\n                stopTwo = publication.position();\n                Tests.awaitPosition(counters, counterId, stopTwo);\n            }\n            finally\n            {\n                aeronArchive.stopRecording(subscriptionIdTwo);\n                pollForRecordingSignal(aeronArchive);\n            }\n        }\n\n        replay(messageCount, stopTwo, recordingId);\n\n        final InOrder inOrder = Mockito.inOrder(mockRecordingSignalConsumer);\n        inOrder.verify(mockRecordingSignalConsumer).onSignal(\n            eq(controlSessionId), anyLong(), eq(recordingId), eq(subscriptionIdOne), eq(0L), eq(START));\n        inOrder.verify(mockRecordingSignalConsumer).onSignal(\n            eq(controlSessionId), anyLong(), eq(recordingId), eq(subscriptionIdOne), eq(stopOne), eq(STOP));\n        inOrder.verify(mockRecordingSignalConsumer).onSignal(\n            eq(controlSessionId), anyLong(), eq(recordingId), eq(subscriptionIdTwo), eq(stopOne), eq(EXTEND));\n        inOrder.verify(mockRecordingSignalConsumer).onSignal(\n            eq(controlSessionId), anyLong(), eq(recordingId), eq(subscriptionIdTwo), eq(stopTwo), eq(STOP));\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    void shouldTruncateAndExtendFromTheMiddleOfTheTerm()\n    {\n        final int[] data = ThreadLocalRandom.current().ints(5000).toArray();\n        final BufferClaim bufferClaim = new BufferClaim();\n\n        final int termLength = TERM_MIN_LENGTH;\n        final String channel = \"aeron:ipc?ssc=true|term-length=\" + termLength;\n        final int streamId = 42;\n        final int initialTermId;\n        final long recordingId;\n        final long publicationPosition;\n        try (ExclusivePublication publication = aeronArchive.addRecordedExclusivePublication(channel, streamId))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            Tests.awaitConnected(publication);\n            initialTermId = publication.initialTermId();\n\n            for (final int value : data)\n            {\n                while (publication.tryClaim(SIZE_OF_INT, bufferClaim) < 0)\n                {\n                    Tests.yield();\n                }\n\n                bufferClaim.buffer().putInt(bufferClaim.offset(), value);\n                bufferClaim.commit();\n            }\n\n            Tests.awaitPosition(counters, counterId, publication.position());\n            publicationPosition = publication.position();\n        }\n\n        Tests.await(() -> publicationPosition == aeronArchive.getStopPosition(recordingId));\n\n        final int truncateIndex = 1139;\n        final int truncatePosition = truncateIndex * 64;\n        assertEquals(3, aeronArchive.truncateRecording(recordingId, truncatePosition));\n\n        final int extendMessageCount = 100;\n        final long extendPosition;\n        try (ExclusivePublication publication = aeron.addExclusivePublication(\n            new ChannelUriStringBuilder(channel).initialPosition(truncatePosition, initialTermId, termLength).build(),\n            streamId))\n        {\n            assertNotEquals(NULL_VALUE, aeronArchive.extendRecording(recordingId, channel, streamId, LOCAL, true));\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n\n            Tests.awaitConnected(publication);\n\n            for (int i = 0; i < extendMessageCount; i++)\n            {\n                while (publication.tryClaim(SIZE_OF_INT, bufferClaim) < 0)\n                {\n                    Tests.yield();\n                }\n\n                bufferClaim.buffer().putInt(bufferClaim.offset(), data[i]);\n                bufferClaim.commit();\n            }\n\n            Tests.awaitPosition(counters, counterId, publication.position());\n            extendPosition = publication.position();\n        }\n\n        Tests.await(() -> extendPosition == aeronArchive.getStopPosition(recordingId));\n\n        final String replayChannel = \"aeron:ipc\";\n        final int replayStreamId = -96;\n        try (Subscription subscription =\n            aeronArchive.replay(recordingId, termLength, Long.MAX_VALUE, replayChannel, replayStreamId))\n        {\n            Tests.awaitConnected(subscription);\n\n            assertEquals(1, subscription.imageCount());\n            final Image image = subscription.imageAtIndex(0);\n            final MutableInteger msgCount = new MutableInteger();\n            final int[] replayData = new int[300];\n            final FragmentHandler fragmentHandler = (buffer, offset, length, header) ->\n            {\n                replayData[msgCount.get()] = buffer.getInt(offset);\n                msgCount.increment();\n            };\n\n            while (image.position() < extendPosition && 0 == subscription.poll(fragmentHandler, Integer.MAX_VALUE))\n            {\n                Tests.yield();\n            }\n\n            final int replayIndex = 1024;\n            assertEquals(truncateIndex - replayIndex + extendMessageCount, msgCount.get());\n\n            int j = 0;\n            for (int i = replayIndex; i < truncateIndex; i++, j++)\n            {\n                assertEquals(data[i], replayData[j]);\n            }\n            for (int i = 0; i < extendMessageCount; i++, j++)\n            {\n                assertEquals(data[i], replayData[j]);\n            }\n            assertEquals(msgCount.get(), j);\n        }\n    }\n\n    private void assertThat(final long stopPosition, final Matcher<Integer> integerMatcher)\n    {\n    }\n\n    private void replay(final int messageCount, final long secondStopPosition, final long recordingId)\n    {\n        final long fromPosition = 0L;\n        final long length = secondStopPosition - fromPosition;\n\n        try (Subscription subscription = aeronArchive.replay(\n            recordingId, fromPosition, length, REPLAY_CHANNEL, REPLAY_STREAM_ID))\n        {\n            consume(subscription, 0, messageCount * 2);\n            assertEquals(secondStopPosition, subscription.imageAtIndex(0).position());\n        }\n    }\n\n    private static void offer(final Publication publication, final int startIndex, final int count)\n    {\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n\n        for (int i = startIndex; i < (startIndex + count); i++)\n        {\n            final int length = buffer.putStringWithoutLengthAscii(0, MESSAGE_PREFIX + i);\n\n            while (publication.offer(buffer, 0, length) <= 0)\n            {\n                Tests.yield();\n            }\n        }\n    }\n\n    private static void consume(final Subscription subscription, final int startIndex, final int count)\n    {\n        final MutableInteger received = new MutableInteger(startIndex);\n\n        final FragmentHandler fragmentHandler = new FragmentAssembler(\n            (buffer, offset, length, header) ->\n            {\n                final String expected = MESSAGE_PREFIX + received.value;\n                final String actual = buffer.getStringWithoutLengthAscii(offset, length);\n\n                assertEquals(expected, actual);\n\n                received.value++;\n            });\n\n        while (received.value < (startIndex + count))\n        {\n            if (0 == subscription.poll(fragmentHandler, ArchiveSystemTests.FRAGMENT_LIMIT))\n            {\n                Tests.yield();\n            }\n        }\n\n        assertEquals(startIndex + count, received.get());\n    }\n\n    private void pollForRecordingSignal(final AeronArchive aeronArchive)\n    {\n        while (0 == aeronArchive.pollForRecordingSignals())\n        {\n            Tests.yield();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ManageRecordingHistoryTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.Publication;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Path;\n\nimport static io.aeron.archive.ArchiveSystemTests.CATALOG_CAPACITY;\nimport static io.aeron.archive.ArchiveSystemTests.awaitSignal;\nimport static io.aeron.archive.ArchiveSystemTests.injectRecordingSignalConsumer;\nimport static io.aeron.archive.ArchiveSystemTests.offerToPosition;\nimport static io.aeron.archive.client.AeronArchive.*;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ManageRecordingHistoryTest\n{\n    private static final int TERM_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int SEGMENT_LENGTH = TERM_LENGTH * 2;\n    private static final int STREAM_ID = 1033;\n    private static final int MTU_LENGTH = 1024;\n\n    private final ChannelUriStringBuilder uriBuilder = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .endpoint(\"localhost:3333\")\n        .mtu(MTU_LENGTH)\n        .termLength(TERM_LENGTH);\n    private TestMediaDriver driver;\n    private Archive archive;\n    private Aeron aeron;\n    private AeronArchive aeronArchive;\n    private TestRecordingSignalConsumer signalConsumer;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before(@TempDir final Path tempDir)\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .publicationTermBufferLength(TERM_LENGTH)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true);\n\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .catalogCapacity(CATALOG_CAPACITY)\n            .segmentFileLength(SEGMENT_LENGTH)\n            .deleteArchiveOnStart(true)\n            .archiveDir(tempDir.resolve(\"archive\").toFile())\n            .fileSyncLevel(0)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n        archive = Archive.launch(archiveCtx);\n        systemTestWatcher.dataCollector().add(archiveCtx.archiveDir());\n\n        aeron = Aeron.connect();\n\n        aeronArchive = connect(\n            TestContexts.localhostAeronArchive()\n                .aeron(aeron));\n\n        signalConsumer = injectRecordingSignalConsumer(aeronArchive);\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeronArchive, aeron, archive, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldPurgeForStreamJoinedAtTheBeginning()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final long targetPosition = (SEGMENT_LENGTH * 3L) + 1;\n\n        try (Publication publication = aeronArchive.addRecordedPublication(uriBuilder.build(), STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offerToPosition(publication, messagePrefix, targetPosition);\n            Tests.awaitPosition(counters, counterId, publication.position());\n\n            final long startPosition = 0L;\n            final long segmentFileBasePosition = segmentFileBasePosition(\n                startPosition, SEGMENT_LENGTH * 2L, TERM_LENGTH, SEGMENT_LENGTH);\n\n            signalConsumer.reset();\n            final long count = aeronArchive.purgeSegments(recordingId, segmentFileBasePosition);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.DELETE);\n            assertEquals(recordingId, signalConsumer.recordingId);\n            assertEquals(2L, count);\n            assertEquals(segmentFileBasePosition, aeronArchive.getStartPosition(recordingId));\n\n            signalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.STOP);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldPurgeForLateJoinedStream() throws IOException\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int initialTermId = 7;\n        final long targetPosition = (SEGMENT_LENGTH * 15L) + 100;\n        final long startPosition = (SEGMENT_LENGTH * 10L) + 7 * FRAME_ALIGNMENT;\n        uriBuilder.initialPosition(startPosition, initialTermId, TERM_LENGTH);\n\n        try (Publication publication = aeronArchive.addRecordedExclusivePublication(uriBuilder.build(), STREAM_ID))\n        {\n            assertEquals(startPosition, publication.position());\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offerToPosition(publication, messagePrefix, targetPosition);\n            Tests.awaitPosition(counters, counterId, publication.position());\n\n            final File archiveDir = archive.context().archiveDir();\n            final String fileNamePrefix = recordingId + \"-\";\n            final String[] recordingFiles = archiveDir.list((dir, name) -> name.startsWith(fileNamePrefix));\n            assertThat(recordingFiles, arrayWithSize(6));\n\n            final long segmentFileBasePosition = segmentFileBasePosition(\n                startPosition,\n                startPosition + 2 * SEGMENT_LENGTH + TERM_LENGTH + FRAME_ALIGNMENT * 5,\n                TERM_LENGTH,\n                SEGMENT_LENGTH);\n\n            signalConsumer.reset();\n            final long purgeSegments = aeronArchive.purgeSegments(recordingId, segmentFileBasePosition);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.DELETE);\n            assertEquals(recordingId, signalConsumer.recordingId);\n            assertEquals(2, purgeSegments);\n            assertEquals(segmentFileBasePosition, aeronArchive.getStartPosition(recordingId));\n\n            signalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.STOP);\n\n            final String[] files = archiveDir.list((dir, name) -> name.startsWith(fileNamePrefix));\n            assertThat(files, arrayContainingInAnyOrder(\n                Archive.segmentFileName(recordingId, SEGMENT_LENGTH * 12L),\n                Archive.segmentFileName(recordingId, SEGMENT_LENGTH * 13L),\n                Archive.segmentFileName(recordingId, SEGMENT_LENGTH * 14L),\n                Archive.segmentFileName(recordingId, SEGMENT_LENGTH * 15L)));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldDetachThenAttachFullSegments()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final long targetPosition = (SEGMENT_LENGTH * 3L) + 1;\n\n        try (Publication publication = aeronArchive.addRecordedPublication(uriBuilder.build(), STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offerToPosition(publication, messagePrefix, targetPosition);\n            Tests.awaitPosition(counters, counterId, publication.position());\n\n            signalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.STOP);\n            assertEquals(recordingId, signalConsumer.recordingId);\n\n            final long startPosition = 0L;\n            final long segmentFileBasePosition = segmentFileBasePosition(\n                startPosition, SEGMENT_LENGTH * 2L, TERM_LENGTH, SEGMENT_LENGTH);\n\n            aeronArchive.detachSegments(recordingId, segmentFileBasePosition);\n            assertEquals(segmentFileBasePosition, aeronArchive.getStartPosition(recordingId));\n\n            final long attachSegments = aeronArchive.attachSegments(recordingId);\n            assertEquals(2L, attachSegments);\n            assertEquals(startPosition, aeronArchive.getStartPosition(recordingId));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldDetachThenAttachWhenStartNotSegmentAligned()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int initialTermId = 7;\n        final long targetPosition = (SEGMENT_LENGTH * 3L) + 1;\n        final long startPosition = (TERM_LENGTH * 2L) + (FRAME_ALIGNMENT * 2L);\n        uriBuilder.initialPosition(startPosition, initialTermId, TERM_LENGTH);\n\n        try (Publication publication = aeronArchive.addRecordedExclusivePublication(uriBuilder.build(), STREAM_ID))\n        {\n            assertEquals(startPosition, publication.position());\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offerToPosition(publication, messagePrefix, targetPosition);\n            Tests.awaitPosition(counters, counterId, publication.position());\n\n            signalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.STOP);\n            assertEquals(recordingId, signalConsumer.recordingId);\n\n            final long segmentFileBasePosition = segmentFileBasePosition(\n                startPosition, startPosition + (SEGMENT_LENGTH * 2L), TERM_LENGTH, SEGMENT_LENGTH);\n\n            aeronArchive.detachSegments(recordingId, segmentFileBasePosition);\n            assertEquals(segmentFileBasePosition, aeronArchive.getStartPosition(recordingId));\n\n            final long attachSegments = aeronArchive.attachSegments(recordingId);\n            assertEquals(2L, attachSegments);\n            assertEquals(startPosition, aeronArchive.getStartPosition(recordingId));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldDeleteDetachedFullSegments()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final long targetPosition = (SEGMENT_LENGTH * 3L) + 1;\n\n        try (Publication publication = aeronArchive.addRecordedPublication(uriBuilder.build(), STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offerToPosition(publication, messagePrefix, targetPosition);\n            Tests.awaitPosition(counters, counterId, publication.position());\n\n            signalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.STOP);\n            assertEquals(recordingId, signalConsumer.recordingId);\n\n            final String prefix = recordingId + \"-\";\n            final File archiveDir = archive.context().archiveDir();\n            final String[] files = archiveDir.list((dir, name) -> name.startsWith(prefix));\n            assertThat(files, arrayWithSize(4));\n\n            final long startPosition = 0L;\n            final long segmentFileBasePosition = segmentFileBasePosition(\n                startPosition, SEGMENT_LENGTH * 2L, TERM_LENGTH, SEGMENT_LENGTH);\n\n            aeronArchive.detachSegments(recordingId, segmentFileBasePosition);\n            assertEquals(segmentFileBasePosition, aeronArchive.getStartPosition(recordingId));\n\n            signalConsumer.reset();\n            final long deletedSegments = aeronArchive.deleteDetachedSegments(recordingId);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.DELETE);\n            assertEquals(2L, deletedSegments);\n            assertEquals(segmentFileBasePosition, aeronArchive.getStartPosition(recordingId));\n\n            final String[] updatedFiles = archiveDir.list((dir, name) -> name.startsWith(prefix));\n            assertThat(updatedFiles, arrayContainingInAnyOrder(\n                Archive.segmentFileName(recordingId, segmentFileBasePosition),\n                Archive.segmentFileName(recordingId, segmentFileBasePosition + SEGMENT_LENGTH)\n            ));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldDeleteDetachedSegmentsWhenStartNotSegmentAligned()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int initialTermId = 7;\n        final long targetPosition = (SEGMENT_LENGTH * 3L) + 1;\n        final long startPosition = (TERM_LENGTH * 2L) + (FRAME_ALIGNMENT * 2L);\n        uriBuilder.initialPosition(startPosition, initialTermId, TERM_LENGTH);\n\n        try (Publication publication = aeronArchive.addRecordedExclusivePublication(uriBuilder.build(), STREAM_ID))\n        {\n            assertEquals(startPosition, publication.position());\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offerToPosition(publication, messagePrefix, targetPosition);\n            Tests.awaitPosition(counters, counterId, publication.position());\n\n            signalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, signalConsumer, recordingId, RecordingSignal.STOP);\n\n            final String prefix = recordingId + \"-\";\n            final String[] files = archive.context().archiveDir().list((dir, name) -> name.startsWith(prefix));\n            assertThat(files, arrayWithSize(3));\n\n            final long segmentFileBasePosition = segmentFileBasePosition(\n                startPosition, startPosition + (SEGMENT_LENGTH * 2L), TERM_LENGTH, SEGMENT_LENGTH);\n\n            aeronArchive.detachSegments(recordingId, segmentFileBasePosition);\n            assertEquals(segmentFileBasePosition, aeronArchive.getStartPosition(recordingId));\n\n            signalConsumer.reset();\n            final long deletedSegments = aeronArchive.deleteDetachedSegments(recordingId);\n            awaitSignal(aeronArchive, signalConsumer, recordingId, RecordingSignal.DELETE);\n            assertEquals(2, deletedSegments);\n            assertEquals(segmentFileBasePosition, aeronArchive.getStartPosition(recordingId));\n\n            final String[] updatedFiles = archive.context().archiveDir()\n                .list((dir, name) -> name.startsWith(prefix));\n            assertThat(\n                updatedFiles,\n                arrayContaining(Archive.segmentFileName(recordingId, segmentFileBasePosition)));\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(longs = { 0, (TERM_LENGTH * 2L) + (FRAME_ALIGNMENT * 2L)})\n    @InterruptAfter(10)\n    void deleteDetachedSegmentsIsANoOpIfNoFilesWereDetached(final long startPosition)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int initialTermId = 19;\n        final long firstSegmentFilePosition =\n            segmentFileBasePosition(startPosition, startPosition, TERM_LENGTH, SEGMENT_LENGTH);\n        final long targetPosition = firstSegmentFilePosition + (SEGMENT_LENGTH * 3L) + 139;\n        uriBuilder.initialPosition(startPosition, initialTermId, TERM_LENGTH);\n\n        try (Publication publication = aeronArchive.addRecordedExclusivePublication(uriBuilder.build(), STREAM_ID))\n        {\n            assertEquals(startPosition, publication.position());\n\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offerToPosition(publication, messagePrefix, targetPosition);\n            Tests.awaitPosition(counters, counterId, publication.position());\n\n            signalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, signalConsumer, recordingId, RecordingSignal.STOP);\n\n            final String prefix = recordingId + \"-\";\n            final String[] files = archive.context().archiveDir().list((dir, name) -> name.startsWith(prefix));\n            assertThat(files, arrayWithSize(4));\n\n            signalConsumer.reset();\n            final long deletedSegments = aeronArchive.deleteDetachedSegments(recordingId);\n            awaitSignal(aeronArchive, signalConsumer, recordingId, RecordingSignal.DELETE);\n            assertEquals(0, deletedSegments);\n            assertEquals(startPosition, aeronArchive.getStartPosition(recordingId));\n\n            final String[] updatedFiles =\n                archive.context().archiveDir().list((dir, name) -> name.startsWith(prefix));\n            assertThat(updatedFiles, is(files));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldPurgeRecording() throws IOException\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final long targetPosition = (SEGMENT_LENGTH * 3L) + 1;\n\n        try (Publication publication = aeronArchive.addRecordedPublication(uriBuilder.build(), STREAM_ID))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long newRecordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offerToPosition(publication, messagePrefix, targetPosition);\n            Tests.awaitPosition(counters, counterId, publication.position());\n\n            signalConsumer.reset();\n            aeronArchive.stopRecording(publication);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.STOP);\n            assertEquals(newRecordingId, signalConsumer.recordingId);\n\n            final String prefix = newRecordingId + \"-\";\n            final File archiveDir = archive.context().archiveDir();\n            assertTrue(new File(archiveDir, prefix + (SEGMENT_LENGTH * 4L) + \".rec\").createNewFile());\n            assertTrue(new File(archiveDir, prefix + (SEGMENT_LENGTH * 5L) + \".rec.del\").createNewFile());\n\n            signalConsumer.reset();\n            aeronArchive.purgeRecording(newRecordingId);\n            awaitSignal(aeronArchive, signalConsumer, RecordingSignal.DELETE);\n\n            assertThat(archiveDir.list(((dir, name) -> name.startsWith(prefix))), arrayWithSize(0));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/MigrateSegmentsTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.codecs.SourceLocation;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.zip.CRC32;\n\nimport static io.aeron.archive.ArchiveSystemTests.CATALOG_CAPACITY;\nimport static io.aeron.archive.ArchiveSystemTests.awaitSignal;\nimport static io.aeron.archive.ArchiveSystemTests.injectRecordingSignalConsumer;\nimport static io.aeron.archive.ArchiveSystemTests.offerToPosition;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.arrayContainingInAnyOrder;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass MigrateSegmentsTest\n{\n    private static final String REPLAY_CHANNEL = \"aeron:ipc\";\n    private static final int REPLAY_STREAM_ID = 1034;\n    private static final int TERM_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH;\n    private static final int SEGMENT_LENGTH = TERM_LENGTH * 2;\n    private static final int STREAM_ID = 1033;\n    private static final int MTU_LENGTH = 1024;\n    private static final int FRAGMENT_LIMIT = 10;\n\n    private final Set<AutoCloseable> openPublications = new HashSet<>();\n    private TestMediaDriver driver;\n    private Archive archive;\n    private Aeron aeron;\n    private AeronArchive aeronArchive;\n    private TestRecordingSignalConsumer signalConsumer;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before(@TempDir final Path tempDir)\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .publicationTermBufferLength(TERM_LENGTH)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(true)\n            .dirDeleteOnStart(true);\n\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .catalogCapacity(CATALOG_CAPACITY)\n            .segmentFileLength(SEGMENT_LENGTH)\n            .deleteArchiveOnStart(true)\n            .archiveDir(tempDir.resolve(\"archive\").toFile())\n            .fileSyncLevel(0)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n        archive = Archive.launch(archiveCtx);\n        systemTestWatcher.dataCollector().add(archiveCtx.archiveDir());\n\n        aeron = Aeron.connect();\n\n        aeronArchive = AeronArchive.connect(\n            TestContexts.localhostAeronArchive()\n                .aeron(aeron));\n\n        signalConsumer = injectRecordingSignalConsumer(aeronArchive);\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(openPublications);\n        CloseHelper.closeAll(aeronArchive, aeron, archive, driver);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"validCases\")\n    void shouldMigrateSegments(final TestCaseParams testCase)\n    {\n        final RecordingParams src = testCase.source();\n        final RecordingParams dst = testCase.destination();\n\n        final long srcRecordingId = arrangeRecording(src);\n        final long dstRecordingId = arrangeRecording(dst);\n\n        final boolean isPrepend = src.recordedPosition() <= dst.startPosition();\n        final CRC32 originalChecksum = new CRC32();\n        if (isPrepend)\n        {\n            calculateChecksum(originalChecksum, srcRecordingId, src.startPosition(), src.recordedPosition());\n            calculateChecksum(originalChecksum, dstRecordingId, dst.startPosition(), dst.recordedPosition());\n        }\n        else\n        {\n            calculateChecksum(originalChecksum, dstRecordingId, dst.startPosition(), dst.recordedPosition());\n            calculateChecksum(originalChecksum, srcRecordingId, src.startPosition(), src.recordedPosition());\n        }\n\n        signalConsumer.reset();\n        final long migratedSegmentCount = aeronArchive.migrateSegments(srcRecordingId, dstRecordingId);\n        awaitSignal(aeronArchive, signalConsumer, RecordingSignal.DELETE);\n        assertEquals(srcRecordingId, signalConsumer.recordingId);\n\n        final CRC32 migratedChecksum = new CRC32();\n        calculateChecksum(migratedChecksum, dstRecordingId,\n            Math.min(src.startPosition(), dst.startPosition()),\n            Math.max(src.recordedPosition(), dst.recordedPosition()));\n\n        assertEquals(testCase.expectedMigratedSegmentCount, migratedSegmentCount);\n\n        assertEquals(src.expectedStartPosition(), aeronArchive.getStartPosition(srcRecordingId));\n        assertEquals(src.expectedStopPosition(), aeronArchive.getStopPosition(srcRecordingId));\n        assertEquals(dst.expectedStartPosition(), aeronArchive.getStartPosition(dstRecordingId));\n        assertEquals(dst.expectedStopPosition(), aeronArchive.getStopPosition(dstRecordingId));\n\n        final File archiveDir = archive.context().archiveDir();\n\n        final String[] segmentFiles = archiveDir.list(\n            (dir, name) -> name.endsWith(\".rec\") || name.endsWith(\".rec.del\"));\n        assertThat(segmentFiles, arrayContainingInAnyOrder(testCase.expectedSegments(dstRecordingId)));\n\n        assertEquals(originalChecksum.getValue(), migratedChecksum.getValue());\n\n        assertCanExtend(srcRecordingId, src);\n        if (dst.state() != RecordingState.LIVE)\n        {\n            assertCanExtend(dstRecordingId, dst);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"invalidCases\")\n    void migrateSegmentsShouldThrow(final FailureCaseParams testCase)\n    {\n        final RecordingParams src = testCase.source();\n        final RecordingParams dst = testCase.destination();\n\n        final long srcRecordingId = arrangeRecording(src);\n        final long dstRecordingId = arrangeRecording(dst);\n\n        final File archiveDir = archive.context().archiveDir();\n        testCase.archiveDirPerturbation().perturb(archiveDir, srcRecordingId, dstRecordingId);\n\n        final ArchiveException ex = assertThrows(ArchiveException.class,\n            () -> aeronArchive.migrateSegments(srcRecordingId, dstRecordingId));\n        final String subErrorMessage = ex.getMessage()\n            .replaceFirst(\".* error: \", \"\")\n            .replace(archiveDir.toString(), \"${ARCHIVE_DIR}\")\n            .replace(\"\\\\\", \"/\");\n        assertEquals(testCase.expectedErrorMessage(), subErrorMessage);\n    }\n\n    static TestCaseParams[] validCases()\n    {\n        return new TestCaseParams[]\n            {\n                prependSegmentAlignedStream(FollowingSegmentAction.TRUNCATE),\n                prependSegmentAlignedStream(FollowingSegmentAction.ASSERT_EXISTS),\n                prependSegmentAlignedStream(FollowingSegmentAction.RENAME_FOR_DELETION),\n                prependStreamThatDoesNotStartAtSegmentBoundary(),\n                prependToLiveStream(),\n                appendSegmentAlignedStream(FollowingSegmentAction.TRUNCATE),\n                appendSegmentAlignedStream(FollowingSegmentAction.ASSERT_EXISTS),\n                appendSegmentAlignedStream(FollowingSegmentAction.RENAME_FOR_DELETION),\n            };\n    }\n\n    static FailureCaseParams[] invalidCases()\n    {\n        return new FailureCaseParams[]\n            {\n                differentStreamIds(),\n                differentInitialTermIds(),\n                differentMtuLengths(),\n                differentTermBufferLengths(),\n                prependLiveStream(),\n                prependWithGap(),\n                prependWithMissingSegmentFile(),\n                prependOverPreexistingSegmentFile(),\n                seamAtNonSegmentButTermBoundary(),\n                seamAtNonSegmentAndNonTermBoundary(),\n                appendWithGap(),\n                appendToNonTruncatedStream(),\n                appendToLiveStream(),\n                appendWithMissingSegmentFile(),\n                appendOverPreexistingSegmentFile()\n            };\n    }\n\n    private void assertCanExtend(final long recordingId, final RecordingParams recordingParams)\n    {\n        final long extendPosition = recordingParams.expectedStopPosition();\n\n        final StreamParams streamParams = recordingParams.stream();\n        final String channelUri = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .mtu(streamParams.mtuLength())\n            .endpoint(streamParams.endpoint())\n            .initialPosition(extendPosition, streamParams.initialTermId(), streamParams.termLength())\n            .build();\n\n        final long subscriptionId = aeronArchive.extendRecording(\n            recordingId, channelUri, streamParams.streamId(), SourceLocation.LOCAL);\n\n        try (Publication publication = aeron.addExclusivePublication(channelUri, streamParams.streamId()))\n        {\n            final CountersReader counters = aeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n\n            offerToPosition(publication, \"ext-message-\", extendPosition + SEGMENT_LENGTH + 1L);\n            Tests.awaitPosition(counters, counterId, publication.position());\n        }\n\n        signalConsumer.reset();\n        aeronArchive.stopRecording(subscriptionId);\n        awaitSignal(aeronArchive, signalConsumer, recordingId, RecordingSignal.STOP);\n    }\n\n    private void calculateChecksum(\n        final CRC32 checksum,\n        final long recordingId,\n        final long startPosition,\n        final long endPosition)\n    {\n        final long length = endPosition - startPosition;\n        try (Subscription replay = aeronArchive.replay(\n            recordingId, startPosition, length, REPLAY_CHANNEL, REPLAY_STREAM_ID))\n        {\n            while (replay.hasNoImages())\n            {\n                Tests.yield();\n            }\n\n            final Image image = replay.imageAtIndex(0);\n\n            final FragmentAssembler fragmentAssembler = new FragmentAssembler(\n                (buffer, offset, msgLength, header) ->\n                {\n                    final byte[] data = new byte[msgLength];\n                    buffer.getBytes(offset, data);\n                    checksum.update(data);\n                });\n\n            while (!image.isEndOfStream() && image.position() < endPosition)\n            {\n                image.poll(fragmentAssembler, FRAGMENT_LIMIT);\n\n                if (image.isClosed())\n                {\n                    fail(\"Replay image closed unexpectedly.\");\n                }\n            }\n        }\n    }\n\n    private long arrangeRecording(final RecordingParams recordingParams)\n    {\n        final StreamParams streamParams = recordingParams.stream();\n\n        final String channelUri = new ChannelUriStringBuilder()\n            .media(\"udp\")\n            .mtu(streamParams.mtuLength())\n            .endpoint(streamParams.endpoint())\n            .initialPosition(recordingParams.startPosition(), streamParams.initialTermId(), streamParams.termLength())\n            .build();\n\n        final Publication publication = aeron.addExclusivePublication(channelUri, streamParams.streamId());\n        openPublications.add(publication);\n        final long subscriptionId = aeronArchive.startRecording(\n            channelUri, streamParams.streamId(), SourceLocation.LOCAL, false);\n        final CountersReader counters = aeron.countersReader();\n        final int counterId =\n            Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n        final long recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n        offerToPosition(publication, \"src-message-\", recordingParams.recordedPosition());\n        Tests.awaitPosition(counters, counterId, publication.position());\n\n        if (recordingParams.state() == RecordingState.LIVE)\n        {\n            if (recordingParams.followingSegment() != FollowingSegmentAction.LEAVE_ALONE)\n            {\n                fail(\"cannot alter following segment of live recordings\");\n            }\n        }\n        else\n        {\n            signalConsumer.reset();\n            aeronArchive.stopRecording(subscriptionId);\n            awaitSignal(aeronArchive, signalConsumer, recordingId, RecordingSignal.STOP);\n            openPublications.remove(publication);\n            publication.close();\n        }\n\n        final File archiveDir = archive.context().archiveDir();\n\n        switch (recordingParams.followingSegment())\n        {\n            case TRUNCATE:\n                signalConsumer.reset();\n                aeronArchive.truncateRecording(recordingId, recordingParams.recordedPosition());\n                awaitSignal(aeronArchive, signalConsumer, recordingId, RecordingSignal.DELETE);\n                break;\n\n            case ASSERT_EXISTS:\n                assertEmptyFollowingSegmentExists(recordingParams, recordingId, archiveDir);\n                break;\n\n            case RENAME_FOR_DELETION:\n                final File segmentFile = assertEmptyFollowingSegmentExists(recordingParams, recordingId, archiveDir);\n                final String normalFileName = Archive.segmentFileName(recordingId, recordingParams.recordedPosition());\n                final String deletingFileName = normalFileName + \".del\";\n                final File segmentFileForDeletion = new File(archiveDir, deletingFileName);\n\n                if (!segmentFile.renameTo(segmentFileForDeletion))\n                {\n                    fail(\"failed to rename segment file.\");\n                }\n                break;\n\n            case LEAVE_ALONE:\n                break;\n\n            default:\n                fail(\"unsupported kind of following segment.\");\n                break;\n        }\n\n        return recordingId;\n    }\n\n    private File assertEmptyFollowingSegmentExists(\n        final RecordingParams recordingParams,\n        final long recordingId,\n        final File archiveDir)\n    {\n        if (recordingParams.recordedPosition() % SEGMENT_LENGTH != 0)\n        {\n            fail(\"following empty segment only possible when recording stops on a segment boundary\");\n        }\n\n        final String fileName = Archive.segmentFileName(recordingId, recordingParams.recordedPosition());\n        final File segmentFile = new File(archiveDir, fileName);\n        if (!segmentFile.exists())\n        {\n            fail(\"expected empty following segment file to exist\");\n        }\n\n        return segmentFile;\n    }\n\n    private static TestCaseParams prependSegmentAlignedStream(final FollowingSegmentAction followingSrcSegment)\n    {\n        final TestCaseParams test = new TestCaseParams();\n\n        test.source()\n            .startPosition(0)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .expectedStartPosition(0)\n            .expectedStopPosition(0)\n            .followingSegment(followingSrcSegment)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(4 * SEGMENT_LENGTH)\n            .expectedStartPosition(0)\n            .expectedStopPosition(4 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.ASSERT_EXISTS)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedMigratedSegmentCount(2);\n\n        final SegmentFileExpectation segmentFileExpectation = (dstRecordingId) -> new String[]\n        {\n            Archive.segmentFileName(dstRecordingId, 0),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 2),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 3),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 4)\n        };\n        test.expectedSegments(segmentFileExpectation);\n\n        return test;\n    }\n\n    private static TestCaseParams appendSegmentAlignedStream(final FollowingSegmentAction followingSrcSegment)\n    {\n        final TestCaseParams test = new TestCaseParams();\n\n        test.source()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(4 * SEGMENT_LENGTH)\n            .expectedStartPosition(2 * SEGMENT_LENGTH)\n            .expectedStopPosition(2 * SEGMENT_LENGTH)\n            .followingSegment(followingSrcSegment)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(0)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .expectedStartPosition(0)\n            .expectedStopPosition(4 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedMigratedSegmentCount(2);\n\n        final SegmentFileExpectation segmentFileExpectation = (dstRecordingId) -> new String[]\n        {\n            Archive.segmentFileName(dstRecordingId, 0),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 2),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 3)\n        };\n        test.expectedSegments(segmentFileExpectation);\n\n        return test;\n    }\n\n    private static TestCaseParams prependToLiveStream()\n    {\n        final TestCaseParams test = new TestCaseParams();\n\n        test.source()\n            .startPosition(0)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .expectedStartPosition(0)\n            .expectedStopPosition(0)\n            .followingSegment(FollowingSegmentAction.ASSERT_EXISTS)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(4 * SEGMENT_LENGTH)\n            .expectedStartPosition(0)\n            .expectedStopPosition(-1L)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .state(RecordingState.LIVE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedMigratedSegmentCount(2);\n\n        final SegmentFileExpectation segmentFileExpectation = (dstRecordingId) -> new String[]\n        {\n            Archive.segmentFileName(dstRecordingId, 0),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 2),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 3),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 4)\n        };\n        test.expectedSegments(segmentFileExpectation);\n\n        return test;\n    }\n\n    private static TestCaseParams prependStreamThatDoesNotStartAtSegmentBoundary()\n    {\n        final TestCaseParams test = new TestCaseParams();\n\n        final int srcStartPosition = SEGMENT_LENGTH + 256;\n\n        test.source()\n            .startPosition(srcStartPosition)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .expectedStartPosition(srcStartPosition)\n            .expectedStopPosition(srcStartPosition)\n            .followingSegment(FollowingSegmentAction.ASSERT_EXISTS)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(4 * SEGMENT_LENGTH)\n            .expectedStartPosition(srcStartPosition)\n            .expectedStopPosition(4 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.ASSERT_EXISTS)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedMigratedSegmentCount(1);\n\n        final SegmentFileExpectation segmentFileExpectation = (dstRecordingId) -> new String[]\n        {\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 2),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 3),\n            Archive.segmentFileName(dstRecordingId, SEGMENT_LENGTH * 4)\n        };\n        test.expectedSegments(segmentFileExpectation);\n\n        return test;\n    }\n\n    private static FailureCaseParams differentStreamIds()\n    {\n        final FailureCaseParams test = createValidPrependParams();\n        test.source().stream().streamId(42);\n        test.destination().stream().streamId(1337);\n        test.expectedErrorMessage(\"invalid migrate: srcStreamId=42 dstStreamId=1337\");\n        return test;\n    }\n\n    private static FailureCaseParams differentMtuLengths()\n    {\n        final FailureCaseParams test = createValidPrependParams();\n        test.source().stream().mtuLength(MTU_LENGTH);\n        test.destination().stream().mtuLength(MTU_LENGTH * 2);\n        test.expectedErrorMessage(\"invalid migrate: srcMtuLength=1024 dstMtuLength=2048\");\n        return test;\n    }\n\n    private static FailureCaseParams differentInitialTermIds()\n    {\n        final FailureCaseParams test = createValidPrependParams();\n        test.source().stream().initialTermId(42);\n        test.destination().stream().initialTermId(1337);\n        test.expectedErrorMessage(\"invalid migrate: srcInitialTermId=42 dstInitialTermId=1337\");\n        return test;\n    }\n\n    private static FailureCaseParams differentTermBufferLengths()\n    {\n        final FailureCaseParams test = createValidPrependParams();\n        test.source().stream().termLength(TERM_LENGTH);\n        test.destination().stream().termLength(TERM_LENGTH * 2);\n        test.expectedErrorMessage(\n            \"invalid migrate:\" +\n            \" srcTermBufferLength=\" + TERM_LENGTH +\n            \" dstTermBufferLength=\" + TERM_LENGTH * 2);\n        return test;\n    }\n\n    private static FailureCaseParams prependLiveStream()\n    {\n        final FailureCaseParams test = createValidPrependParams();\n        test.source().state(RecordingState.LIVE);\n        test.expectedErrorMessage(\"recording 0 is still active\");\n        return test;\n    }\n\n    private static FailureCaseParams createValidPrependParams()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(0)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(4 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .stream().endpoint(\"localhost:3334\");\n\n        return test;\n    }\n\n    private static FailureCaseParams prependWithGap()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(0)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(3 * SEGMENT_LENGTH)\n            .recordedPosition(4 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedErrorMessage(\"invalid migrate: src and dst are not contiguous\" +\n            \" srcStartPosition=0 srcStopPosition=262144\" +\n            \" dstStartPosition=393216 dstStopPosition=524288\");\n\n        return test;\n    }\n\n    private static FailureCaseParams seamAtNonSegmentButTermBoundary()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(0)\n            .recordedPosition(TERM_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(TERM_LENGTH)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedErrorMessage(\"invalid migrate: join position is not on segment boundary\" +\n            \" of src recording seamPosition=65536\" +\n            \" startPosition=0 stopPosition=65536\" +\n            \" termBufferLength=65536 segmentFileLength=131072\");\n\n        return test;\n    }\n\n    private static FailureCaseParams seamAtNonSegmentAndNonTermBoundary()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(0)\n            .recordedPosition(1024)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(1024)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedErrorMessage(\"invalid migrate: join position is not on segment boundary\" +\n            \" of src recording seamPosition=1024\" +\n            \" startPosition=0 stopPosition=1024\" +\n            \" termBufferLength=65536 segmentFileLength=131072\");\n\n        return test;\n    }\n\n    private static FailureCaseParams appendWithGap()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(3 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(0)\n            .recordedPosition(SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedErrorMessage(\"invalid migrate: src and dst are not contiguous\" +\n            \" srcStartPosition=262144 srcStopPosition=393216\" +\n            \" dstStartPosition=0 dstStopPosition=131072\");\n\n        return test;\n    }\n\n    private static FailureCaseParams appendToNonTruncatedStream()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(3 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(0)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedErrorMessage(\"preexisting dst segment file ${ARCHIVE_DIR}/1-262144.rec\");\n\n        return test;\n    }\n\n    private static FailureCaseParams appendToLiveStream()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(3 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(0)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .state(RecordingState.LIVE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.expectedErrorMessage(\"invalid migrate: src and dst are not contiguous\" +\n            \" srcStartPosition=262144 srcStopPosition=393216\" +\n            \" dstStartPosition=0 dstStopPosition=-1\");\n\n        return test;\n    }\n\n    private static FailureCaseParams prependWithMissingSegmentFile()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(0)\n            .recordedPosition(3 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(3 * SEGMENT_LENGTH)\n            .recordedPosition(5 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.archiveDirPerturbation(new DeleteSrcSegmentPerturbation(SEGMENT_LENGTH));\n\n        test.expectedErrorMessage(\"missing src segment file ${ARCHIVE_DIR}/0-131072.rec\");\n\n        return test;\n    }\n\n    private static FailureCaseParams prependOverPreexistingSegmentFile()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(0)\n            .recordedPosition(3 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(3 * SEGMENT_LENGTH)\n            .recordedPosition(5 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.LEAVE_ALONE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.archiveDirPerturbation(new AddDstSegmentPerturbation(SEGMENT_LENGTH));\n\n        test.expectedErrorMessage(\"preexisting dst segment file ${ARCHIVE_DIR}/1-131072.rec\");\n\n        return test;\n    }\n\n    private static FailureCaseParams appendWithMissingSegmentFile()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(5 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(0)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.archiveDirPerturbation(new DeleteSrcSegmentPerturbation(3 * SEGMENT_LENGTH));\n\n        test.expectedErrorMessage(\"missing src segment file ${ARCHIVE_DIR}/0-393216.rec\");\n\n        return test;\n    }\n\n\n    private static FailureCaseParams appendOverPreexistingSegmentFile()\n    {\n        final FailureCaseParams test = new FailureCaseParams();\n\n        test.source()\n            .startPosition(2 * SEGMENT_LENGTH)\n            .recordedPosition(5 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3333\");\n\n        test.destination()\n            .startPosition(0)\n            .recordedPosition(2 * SEGMENT_LENGTH)\n            .followingSegment(FollowingSegmentAction.TRUNCATE)\n            .stream().endpoint(\"localhost:3334\");\n\n        test.archiveDirPerturbation(new AddDstSegmentPerturbation(3 * SEGMENT_LENGTH));\n\n        test.expectedErrorMessage(\"preexisting dst segment file ${ARCHIVE_DIR}/1-393216.rec\");\n\n        return test;\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    private static final class StreamParams\n    {\n        private String endpoint;\n        private int initialTermId = 1337;\n        private int mtuLength = MTU_LENGTH;\n        private int termLength = TERM_LENGTH;\n        private int streamId = STREAM_ID;\n\n        public String endpoint()\n        {\n            return endpoint;\n        }\n\n        public StreamParams endpoint(final String endpoint)\n        {\n            this.endpoint = endpoint;\n            return this;\n        }\n\n        public int initialTermId()\n        {\n            return initialTermId;\n        }\n\n        public StreamParams initialTermId(final int initialTermId)\n        {\n            this.initialTermId = initialTermId;\n            return this;\n        }\n\n        public int mtuLength()\n        {\n            return mtuLength;\n        }\n\n        public StreamParams mtuLength(final int mtuLength)\n        {\n            this.mtuLength = mtuLength;\n            return this;\n        }\n\n        public int termLength()\n        {\n            return termLength;\n        }\n\n        public StreamParams termLength(final int termLength)\n        {\n            this.termLength = termLength;\n            return this;\n        }\n\n        public int streamId()\n        {\n            return streamId;\n        }\n\n        public StreamParams streamId(final int streamId)\n        {\n            this.streamId = streamId;\n            return this;\n        }\n\n        public String toString()\n        {\n            return \"{\" +\n                \"endpoint='\" + endpoint + '\\'' +\n                \", initialTermId=\" + initialTermId +\n                \", mtuLength=\" + mtuLength +\n                \", termLength=\" + termLength +\n                \", streamId=\" + streamId +\n                '}';\n        }\n    }\n\n    private enum FollowingSegmentAction\n    {\n        TRUNCATE,\n        ASSERT_EXISTS,\n        RENAME_FOR_DELETION,\n        LEAVE_ALONE\n    }\n\n    private enum RecordingState\n    {\n        LIVE,\n        STOPPED\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    private static final class RecordingParams\n    {\n        private final StreamParams stream = new StreamParams();\n        private long startPosition;\n        private long recordedPosition;\n        private FollowingSegmentAction followingSegment = FollowingSegmentAction.TRUNCATE;\n        private long expectedStartPosition;\n        private long expectedStopPosition;\n        private RecordingState state = RecordingState.STOPPED;\n\n        public StreamParams stream()\n        {\n            return stream;\n        }\n\n        public long startPosition()\n        {\n            return startPosition;\n        }\n\n        public RecordingParams startPosition(final long startPosition)\n        {\n            this.startPosition = startPosition;\n            return this;\n        }\n\n        public long recordedPosition()\n        {\n            return recordedPosition;\n        }\n\n        public RecordingParams recordedPosition(final long recordedPosition)\n        {\n            this.recordedPosition = recordedPosition;\n            return this;\n        }\n\n        public FollowingSegmentAction followingSegment()\n        {\n            return followingSegment;\n        }\n\n        public RecordingParams followingSegment(final FollowingSegmentAction followingSegment)\n        {\n            this.followingSegment = followingSegment;\n            return this;\n        }\n\n        public RecordingState state()\n        {\n            return state;\n        }\n\n        public RecordingParams state(final RecordingState state)\n        {\n            this.state = state;\n            return this;\n        }\n\n        public long expectedStartPosition()\n        {\n            return expectedStartPosition;\n        }\n\n        public RecordingParams expectedStartPosition(final long expectedStartPosition)\n        {\n            this.expectedStartPosition = expectedStartPosition;\n            return this;\n        }\n\n        public long expectedStopPosition()\n        {\n            return expectedStopPosition;\n        }\n\n        public RecordingParams expectedStopPosition(final long expectedStopPosition)\n        {\n            this.expectedStopPosition = expectedStopPosition;\n            return this;\n        }\n\n        public String toString()\n        {\n            return \"{\" +\n                \"startPosition=\" + startPosition +\n                \", recordedPosition=\" + recordedPosition +\n                \", followingSegment=\" + followingSegment +\n                \", state=\" + state +\n                '}';\n        }\n    }\n\n    private interface SegmentFileExpectation\n    {\n        String[] getExpectedFiles(long dstRecordingId);\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    private static final class TestCaseParams\n    {\n        private final RecordingParams source = new RecordingParams();\n        private final RecordingParams destination = new RecordingParams();\n        private long expectedMigratedSegmentCount;\n        private SegmentFileExpectation expectedSegments;\n\n        public RecordingParams source()\n        {\n            return source;\n        }\n\n        public RecordingParams destination()\n        {\n            return destination;\n        }\n\n        public TestCaseParams expectedMigratedSegmentCount(final long expectedMigratedSegmentCount)\n        {\n            this.expectedMigratedSegmentCount = expectedMigratedSegmentCount;\n            return this;\n        }\n\n        public TestCaseParams expectedSegments(final SegmentFileExpectation expectedSegments)\n        {\n            this.expectedSegments = expectedSegments;\n            return this;\n        }\n\n        public String[] expectedSegments(final long dstRecordingId)\n        {\n            return expectedSegments.getExpectedFiles(dstRecordingId);\n        }\n\n        public String toString()\n        {\n            return \"{\" +\n                \"source=\" + source +\n                \", destination=\" + destination +\n                '}';\n        }\n    }\n\n    private interface ArchiveDirPerturbation\n    {\n        void perturb(\n            File archiveDir,\n            long srcRecordingId,\n            long dstRecordingId);\n    }\n\n    private static final class NullPerturbation implements ArchiveDirPerturbation\n    {\n        private static final NullPerturbation INSTANCE = new NullPerturbation();\n\n        private NullPerturbation()\n        {\n        }\n\n        public void perturb(final File archiveDir, final long srcRecordingId, final long dstRecordingId)\n        {\n        }\n\n        public String toString()\n        {\n            return \"None\";\n        }\n    }\n\n    private static final class DeleteSrcSegmentPerturbation implements ArchiveDirPerturbation\n    {\n        private final long segmentBasePosition;\n\n        private DeleteSrcSegmentPerturbation(final long segmentBasePosition)\n        {\n            this.segmentBasePosition = segmentBasePosition;\n        }\n\n        public void perturb(final File archiveDir, final long srcRecordingId, final long dstRecordingId)\n        {\n            final String fileName = Archive.segmentFileName(srcRecordingId, segmentBasePosition);\n            final File segmentFile = new File(archiveDir, fileName);\n            assertTrue(segmentFile.exists());\n            assertTrue(segmentFile.delete());\n        }\n\n        public String toString()\n        {\n            return \"DeleteSrcSegment(\" + segmentBasePosition + \")\";\n        }\n    }\n\n    private static final class AddDstSegmentPerturbation implements ArchiveDirPerturbation\n    {\n        private final long segmentBasePosition;\n\n        private AddDstSegmentPerturbation(final long segmentBasePosition)\n        {\n            this.segmentBasePosition = segmentBasePosition;\n        }\n\n        public void perturb(final File archiveDir, final long srcRecordingId, final long dstRecordingId)\n        {\n            final String fileName = Archive.segmentFileName(dstRecordingId, segmentBasePosition);\n            final File segmentFile = new File(archiveDir, fileName);\n            assertFalse(segmentFile.exists());\n            try\n            {\n                Files.write(segmentFile.toPath(), new byte[] {0x1, 0x2, 0x3}, StandardOpenOption.CREATE_NEW);\n            }\n            catch (final IOException ex)\n            {\n                LangUtil.rethrowUnchecked(ex);\n            }\n        }\n\n        public String toString()\n        {\n            return \"AddDstSegment(\" + segmentBasePosition + \")\";\n        }\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    private static final class FailureCaseParams\n    {\n        private final RecordingParams source = new RecordingParams();\n        private final RecordingParams destination = new RecordingParams();\n        private ArchiveDirPerturbation archiveDirPerturbation = NullPerturbation.INSTANCE;\n        private String expectedErrorMessage;\n\n        public RecordingParams source()\n        {\n            return source;\n        }\n\n        public RecordingParams destination()\n        {\n            return destination;\n        }\n\n        public String expectedErrorMessage()\n        {\n            return expectedErrorMessage;\n        }\n\n        public FailureCaseParams expectedErrorMessage(final String expectedErrorMessage)\n        {\n            this.expectedErrorMessage = expectedErrorMessage;\n            return this;\n        }\n\n        public ArchiveDirPerturbation archiveDirPerturbation()\n        {\n            return archiveDirPerturbation;\n        }\n\n        public FailureCaseParams archiveDirPerturbation(final ArchiveDirPerturbation archiveDirPerturbation)\n        {\n            this.archiveDirPerturbation = archiveDirPerturbation;\n            return this;\n        }\n\n        public String toString()\n        {\n            return \"{\" +\n                \"source=\" + source +\n                \", destination=\" + destination +\n                \", archiveDirPerturbation=\" + archiveDirPerturbation +\n                '}';\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ReplayMergeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ReplayMerge;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static io.aeron.archive.ArchiveSystemTests.CATALOG_CAPACITY;\nimport static io.aeron.archive.ArchiveSystemTests.FRAGMENT_LIMIT;\nimport static io.aeron.archive.ArchiveSystemTests.TERM_LENGTH;\nimport static io.aeron.archive.codecs.SourceLocation.REMOTE;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ReplayMergeTest\n{\n    private static final String MESSAGE_PREFIX = \"Message-Prefix-\";\n    private static final int MIN_MESSAGES_PER_TERM =\n        TERM_LENGTH / (MESSAGE_PREFIX.length() + DataHeaderFlyweight.HEADER_LENGTH);\n\n    private static final int STREAM_ID = 1033;\n\n    private static final String CONTROL_ENDPOINT = \"localhost:23265\";\n    private static final String RECORDING_ENDPOINT = \"localhost:23266\";\n    private static final String LIVE_ENDPOINT = \"localhost:23267\";\n    private static final String REPLAY_ENDPOINT = \"localhost:0\";\n    private static final long GROUP_TAG = 99901L;\n\n    private static final int INITIAL_MESSAGE_COUNT = MIN_MESSAGES_PER_TERM * 3;\n    private static final int SUBSEQUENT_MESSAGE_COUNT = MIN_MESSAGES_PER_TERM * 3;\n    private static final int TOTAL_MESSAGE_COUNT = INITIAL_MESSAGE_COUNT + SUBSEQUENT_MESSAGE_COUNT;\n\n    private final String publicationChannel = new ChannelUriStringBuilder()\n        .media(CommonContext.UDP_MEDIA)\n        .controlEndpoint(CONTROL_ENDPOINT)\n        .controlMode(CommonContext.MDC_CONTROL_MODE_DYNAMIC)\n        .termLength(TERM_LENGTH)\n        .taggedFlowControl(GROUP_TAG, 1, \"5s\")\n        .build();\n\n    private final String liveDestination = new ChannelUriStringBuilder()\n        .media(CommonContext.UDP_MEDIA)\n        .endpoint(LIVE_ENDPOINT)\n        .controlEndpoint(CONTROL_ENDPOINT)\n        .build();\n\n    private final String replayDestination = new ChannelUriStringBuilder()\n        .media(CommonContext.UDP_MEDIA)\n        .endpoint(REPLAY_ENDPOINT)\n        .build();\n\n    private final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n    private final MutableLong receivedMessageCount = new MutableLong();\n    private final MutableLong receivedPosition = new MutableLong();\n    private final MediaDriver.Context mediaDriverContext = new MediaDriver.Context();\n\n    private TestMediaDriver driver;\n    private Archive archive;\n    private Aeron aeron;\n    private AeronArchive aeronArchive;\n    private int messagesPublished = 0;\n\n    private final FragmentHandler fragmentHandler = new FragmentAssembler(\n        (buffer, offset, length, header) ->\n        {\n            final String expected = MESSAGE_PREFIX + receivedMessageCount.get();\n            final String actual = buffer.getStringWithoutLengthAscii(offset, length);\n\n            assertEquals(expected, actual);\n            receivedMessageCount.incrementAndGet();\n            receivedPosition.set(header.position());\n        });\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before()\n    {\n        final File archiveDir = new File(SystemUtil.tmpDirName(), \"archive\");\n\n        driver = TestMediaDriver.launch(\n            mediaDriverContext\n                .termBufferSparseFile(true)\n                .publicationTermBufferLength(TERM_LENGTH)\n                .threadingMode(ThreadingMode.SHARED)\n                .spiesSimulateConnection(false)\n                .imageLivenessTimeoutNs(TimeUnit.SECONDS.toNanos(10))\n                .dirDeleteOnStart(true),\n            systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        archive = Archive.launch(\n            TestContexts.localhostArchive()\n                .catalogCapacity(CATALOG_CAPACITY)\n                .aeronDirectoryName(driver.context().aeronDirectoryName())\n                .archiveDir(archiveDir)\n                .recordingEventsEnabled(false)\n                .threadingMode(ArchiveThreadingMode.SHARED)\n                .deleteArchiveOnStart(true));\n        systemTestWatcher.dataCollector().add(archive.context().archiveDir());\n\n        aeron = Aeron.connect(\n            new Aeron.Context()\n                .aeronDirectoryName(mediaDriverContext.aeronDirectoryName()));\n\n        aeronArchive = AeronArchive.connect(\n            new AeronArchive.Context()\n                .errorHandler(Tests::onError)\n                .controlRequestChannel(archive.context().localControlChannel())\n                .controlRequestStreamId(archive.context().localControlStreamId())\n                .controlResponseChannel(archive.context().localControlChannel())\n                .aeron(aeron));\n    }\n\n    @AfterEach\n    void after()\n    {\n        if (receivedMessageCount.get() != MIN_MESSAGES_PER_TERM * 6L)\n        {\n            System.out.println(\n                \"received \" + receivedMessageCount.get() + \", sent \" + messagesPublished +\n                \", total \" + (MIN_MESSAGES_PER_TERM * 6L));\n        }\n\n        CloseHelper.closeAll(aeronArchive, aeron, archive, driver);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldMergeFromReplayToLive()\n    {\n        try (Publication publication = aeron.addPublication(publicationChannel, STREAM_ID))\n        {\n            final String recordingChannel = new ChannelUriStringBuilder()\n                .media(CommonContext.UDP_MEDIA)\n                .endpoint(RECORDING_ENDPOINT)\n                .controlEndpoint(CONTROL_ENDPOINT)\n                .sessionId(publication.sessionId())\n                .groupTag(GROUP_TAG)\n                .build();\n\n            final String subscriptionChannel = new ChannelUriStringBuilder()\n                .media(CommonContext.UDP_MEDIA)\n                .controlMode(CommonContext.MDC_CONTROL_MODE_MANUAL)\n                .sessionId(publication.sessionId())\n                .build();\n\n            aeronArchive.startRecording(recordingChannel, STREAM_ID, REMOTE, true);\n            final CountersReader counters = aeron.countersReader();\n            final int recordingCounterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n            final long recordingId = RecordingPos.getRecordingId(counters, recordingCounterId);\n\n            Tests.awaitConnected(publication);\n            publishMessages(publication);\n            Tests.awaitPosition(counters, recordingCounterId, publication.position());\n            int attempt = 1;\n\n            while (!attemptReplayMerge(\n                attempt, recordingId, recordingCounterId, counters, publication, subscriptionChannel))\n            {\n                Tests.yield();\n                attempt++;\n            }\n\n            assertEquals(TOTAL_MESSAGE_COUNT, receivedMessageCount.get());\n            assertEquals(publication.position(), receivedPosition.get());\n        }\n    }\n\n    private boolean attemptReplayMerge(\n        final int attempt,\n        final long recordingId,\n        final int recordingCounterId,\n        final CountersReader counters,\n        final Publication publication,\n        final String subscriptionChannel)\n    {\n        final String replayChannel = new ChannelUriStringBuilder()\n            .media(CommonContext.UDP_MEDIA)\n            .sessionId(publication.sessionId())\n            .build();\n\n        try (Subscription subscription = aeron.addSubscription(subscriptionChannel, STREAM_ID);\n            ReplayMerge replayMerge = new ReplayMerge(\n                subscription,\n                aeronArchive,\n                replayChannel,\n                replayDestination,\n                liveDestination,\n                recordingId,\n                receivedPosition.get()))\n        {\n            final Supplier<String> msgOne = () -> String.format(\n                \"replay did not merge: attempt=%d %s\", attempt, replayMerge);\n            final Supplier<String> msgTwo = () -> String.format(\n                \"receivedMessageCount=%d < totalMessageCount=%d: attempt=%d %s\",\n                receivedMessageCount.get(), TOTAL_MESSAGE_COUNT, attempt, replayMerge);\n\n            for (int i = messagesPublished; i < TOTAL_MESSAGE_COUNT; i++)\n            {\n                while (true)\n                {\n                    final long offerResult = offerMessage(publication, i);\n                    if (offerResult > 0)\n                    {\n                        messagesPublished++;\n                        break;\n                    }\n                    else if (Publication.BACK_PRESSURED == offerResult)\n                    {\n                        awaitRecordingPositionChange(\n                            attempt, replayMerge, counters, recordingCounterId, recordingId, publication);\n\n                        if (0 == replayMerge.poll(fragmentHandler, FRAGMENT_LIMIT) && replayMerge.hasFailed())\n                        {\n                            return false;\n                        }\n                    }\n                    else if (Publication.NOT_CONNECTED == offerResult)\n                    {\n                        throw new IllegalStateException(\"publication is not connected\");\n                    }\n                    else if (Publication.CLOSED == offerResult)\n                    {\n                        throw new IllegalStateException(\"publication is closed\");\n                    }\n                }\n\n                if (0 == replayMerge.poll(fragmentHandler, FRAGMENT_LIMIT) && replayMerge.hasFailed())\n                {\n                    return false;\n                }\n            }\n\n            while (!replayMerge.isMerged())\n            {\n                if (0 == replayMerge.poll(fragmentHandler, FRAGMENT_LIMIT))\n                {\n                    if (replayMerge.hasFailed())\n                    {\n                        return false;\n                    }\n                    Tests.yieldingIdle(msgOne);\n                }\n            }\n\n            final Image image = replayMerge.image();\n            while (receivedMessageCount.get() < TOTAL_MESSAGE_COUNT)\n            {\n                if (0 == image.poll(fragmentHandler, FRAGMENT_LIMIT))\n                {\n                    if (image.isClosed())\n                    {\n                        return false;\n                    }\n                    Tests.yieldingIdle(msgTwo);\n                }\n            }\n\n            assertTrue(replayMerge.isMerged());\n            assertTrue(replayMerge.isLiveAdded());\n            assertFalse(replayMerge.hasFailed());\n        }\n\n        return true;\n    }\n\n    static void awaitRecordingPositionChange(\n        final int attempt,\n        final ReplayMerge replayMerge,\n        final CountersReader counters,\n        final int counterId,\n        final long recordingId,\n        final Publication publication)\n    {\n        final long position = publication.position();\n        final long initialTimestampNs = System.nanoTime();\n        final long currentPosition = counters.getCounterValue(counterId);\n        final Supplier<String> msg = () -> String.format(\n            \"publicationPosition=%d recordingPosition=%d timeSinceLastChangeMs=%d attempt=%d %s\",\n            position,\n            currentPosition,\n            (System.nanoTime() - initialTimestampNs) / 1_000_000,\n            attempt,\n            replayMerge);\n\n        do\n        {\n            Tests.yieldingIdle(msg);\n\n            if (!RecordingPos.isActive(counters, counterId, recordingId))\n            {\n                throw new IllegalStateException(\"recording not active: \" + counterId);\n            }\n        }\n        while (currentPosition == counters.getCounterValue(counterId) && currentPosition < position);\n    }\n\n    private long offerMessage(final Publication publication, final int index)\n    {\n        int length = buffer.putStringWithoutLengthAscii(0, MESSAGE_PREFIX);\n        length += buffer.putIntAscii(length, index);\n\n        return publication.offer(buffer, 0, length);\n    }\n\n    private void publishMessages(final Publication publication)\n    {\n        for (int i = 0; i < INITIAL_MESSAGE_COUNT; i++)\n        {\n            int length = buffer.putStringWithoutLengthAscii(0, MESSAGE_PREFIX);\n            length += buffer.putIntAscii(length, i);\n\n            while (publication.offer(buffer, 0, length) <= 0)\n            {\n                Tests.yield();\n            }\n        }\n\n        messagesPublished = INITIAL_MESSAGE_COUNT;\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ReplicateRecordingTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.client.ReplicationParams;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.samples.archive.RecordingDescriptor;\nimport io.aeron.samples.archive.RecordingDescriptorCollector;\nimport io.aeron.samples.archive.SampleAuthenticatorSupplier;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.function.Executable;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.CommonContext.IPC_CHANNEL;\nimport static io.aeron.CommonContext.generateRandomDirName;\nimport static io.aeron.archive.ArchiveSystemTests.CATALOG_CAPACITY;\nimport static io.aeron.archive.ArchiveSystemTests.TERM_LENGTH;\nimport static io.aeron.archive.ArchiveSystemTests.awaitSignal;\nimport static io.aeron.archive.ArchiveSystemTests.consume;\nimport static io.aeron.archive.ArchiveSystemTests.injectRecordingSignalConsumer;\nimport static io.aeron.archive.ArchiveSystemTests.offer;\nimport static io.aeron.archive.ArchiveSystemTests.resetAndAwaitSignal;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.codecs.RecordingSignal.DELETE;\nimport static io.aeron.archive.codecs.RecordingSignal.EXTEND;\nimport static io.aeron.archive.codecs.RecordingSignal.MERGE;\nimport static io.aeron.archive.codecs.RecordingSignal.REPLICATE;\nimport static io.aeron.archive.codecs.RecordingSignal.REPLICATE_END;\nimport static io.aeron.archive.codecs.RecordingSignal.START;\nimport static io.aeron.archive.codecs.RecordingSignal.STOP;\nimport static io.aeron.archive.codecs.RecordingSignal.SYNC;\nimport static io.aeron.archive.codecs.SourceLocation.LOCAL;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.endsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ReplicateRecordingTest\n{\n    public static final String SRC_RESPONSE_CHANNEL = \"aeron:udp?control-mode=response|control=localhost:10000\";\n    private static final int SRC_CONTROL_STREAM_ID = AeronArchive.Configuration.CONTROL_STREAM_ID_DEFAULT;\n    private static final String SRC_CONTROL_REQUEST_CHANNEL = \"aeron:udp?endpoint=localhost:8090\";\n    private static final String INVALID_SRC_CONTROL_REQUEST_CHANNEL = \"aeron:udp?endpoint=localhost:18090\";\n    private static final String SRC_CONTROL_RESPONSE_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n    private static final String DST_CONTROL_REQUEST_CHANNEL = \"aeron:udp?endpoint=localhost:8095\";\n    private static final String DST_CONTROL_RESPONSE_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n    private static final String SRC_REPLICATION_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n    private static final String DST_REPLICATION_CHANNEL = \"aeron:udp?endpoint=localhost:20000\";\n    private static final String REPLAY_CHANNEL = \"aeron:udp?endpoint=localhost:6666\";\n    private static final int REPLAY_STREAM_ID = 101;\n    private static final long TIMER_INTERVAL_NS = TimeUnit.MILLISECONDS.toNanos(15);\n\n    private static final int LIVE_STREAM_ID = 1033;\n    private static final String LIVE_CHANNEL = new ChannelUriStringBuilder()\n        .media(\"udp\")\n        .controlMode(\"dynamic\")\n        .controlEndpoint(\"localhost:8100\")\n        .termLength(TERM_LENGTH)\n        .build();\n    private TestMediaDriver srcDriver;\n    private Archive.Context srcArchiveCtx;\n    private Archive srcArchive;\n    private TestMediaDriver dstDriver;\n    private Archive dstArchive;\n    private AeronArchive srcAeronArchive;\n    private AeronArchive dstAeronArchive;\n    private TestRecordingSignalConsumer srcRecordingSignalConsumer;\n    private TestRecordingSignalConsumer dstRecordingSignalConsumer;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n    private AeronArchive.Context srcAeronArchiveCtx;\n\n    @BeforeEach\n    void before()\n    {\n        final String srcAeronDirectoryName = generateRandomDirName();\n        final String dstAeronDirectoryName = generateRandomDirName();\n\n        final MediaDriver.Context srcContext = new MediaDriver.Context()\n            .aeronDirectoryName(srcAeronDirectoryName)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(true)\n            .timerIntervalNs(TIMER_INTERVAL_NS)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true);\n\n        srcArchiveCtx = new Archive.Context()\n            .catalogCapacity(CATALOG_CAPACITY)\n            .aeronDirectoryName(srcAeronDirectoryName)\n            .controlChannel(SRC_CONTROL_REQUEST_CHANNEL)\n            .archiveClientContext(new AeronArchive.Context().controlResponseChannel(SRC_CONTROL_RESPONSE_CHANNEL))\n            .recordingEventsEnabled(false)\n            .replicationChannel(SRC_REPLICATION_CHANNEL)\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"src-archive\"))\n            .fileSyncLevel(0)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n        final MediaDriver.Context dstContext = new MediaDriver.Context()\n            .aeronDirectoryName(dstAeronDirectoryName)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(true)\n            .timerIntervalNs(TIMER_INTERVAL_NS)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true);\n\n        final Archive.Context dstArchiveCtx = new Archive.Context()\n            .catalogCapacity(CATALOG_CAPACITY)\n            .aeronDirectoryName(dstAeronDirectoryName)\n            .controlChannel(DST_CONTROL_REQUEST_CHANNEL)\n            .archiveClientContext(new AeronArchive.Context().controlResponseChannel(DST_CONTROL_RESPONSE_CHANNEL))\n            .recordingEventsEnabled(false)\n            .replicationChannel(DST_REPLICATION_CHANNEL)\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"dst-archive\"))\n            .fileSyncLevel(0)\n            .threadingMode(ArchiveThreadingMode.SHARED);\n\n        srcDriver = TestMediaDriver.launch(srcContext, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(srcContext.aeronDirectory());\n        srcArchive = Archive.launch(srcArchiveCtx.clone());\n        systemTestWatcher.dataCollector().add(srcArchiveCtx.archiveDir());\n        dstDriver = TestMediaDriver.launch(dstContext, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(dstContext.aeronDirectory());\n        dstArchive = Archive.launch(dstArchiveCtx);\n        systemTestWatcher.dataCollector().add(dstArchiveCtx.archiveDir());\n\n        srcAeronArchiveCtx = new AeronArchive.Context()\n            .idleStrategy(YieldingIdleStrategy.INSTANCE)\n            .controlRequestChannel(SRC_CONTROL_REQUEST_CHANNEL)\n            .controlResponseChannel(SRC_CONTROL_RESPONSE_CHANNEL)\n            .aeronDirectoryName(srcAeronDirectoryName);\n\n        srcAeronArchive = AeronArchive.connect(srcAeronArchiveCtx.clone());\n\n        dstAeronArchive = AeronArchive.connect(\n            new AeronArchive.Context()\n                .idleStrategy(YieldingIdleStrategy.INSTANCE)\n                .controlRequestChannel(DST_CONTROL_REQUEST_CHANNEL)\n                .controlResponseChannel(DST_CONTROL_RESPONSE_CHANNEL)\n                .aeronDirectoryName(dstAeronDirectoryName));\n\n        srcRecordingSignalConsumer = injectRecordingSignalConsumer(srcAeronArchive);\n        dstRecordingSignalConsumer = injectRecordingSignalConsumer(dstAeronArchive);\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(\n            srcAeronArchive,\n            dstAeronArchive,\n            srcArchive,\n            dstArchive,\n            dstDriver,\n            srcDriver);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldThrowExceptionWhenDstRecordingIdUnknown(final boolean useParams)\n    {\n        final long unknownId = 7L;\n        try\n        {\n            if (useParams)\n            {\n                dstAeronArchive.replicate(\n                    NULL_VALUE,\n                    SRC_CONTROL_STREAM_ID,\n                    SRC_CONTROL_REQUEST_CHANNEL,\n                    new ReplicationParams().dstRecordingId(unknownId));\n            }\n            else\n            {\n                dstAeronArchive.replicate(\n                    NULL_VALUE, unknownId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, null);\n            }\n        }\n        catch (final ArchiveException ex)\n        {\n            assertEquals(ArchiveException.UNKNOWN_RECORDING, ex.errorCode());\n            assertThat(ex.getMessage(), endsWith(Long.toString(unknownId)));\n            return;\n        }\n\n        fail(\"expected archive exception\");\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldThrowExceptionWhenSrcRecordingIdUnknown(final boolean useParams)\n    {\n        final long unknownId = 7L;\n        if (useParams)\n        {\n            dstAeronArchive.replicate(\n                unknownId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, new ReplicationParams());\n        }\n        else\n        {\n            dstAeronArchive.replicate(\n                unknownId, NULL_VALUE, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, null);\n        }\n\n        String errorResponse;\n        while (null == (errorResponse = dstAeronArchive.pollForErrorResponse()))\n        {\n            Thread.yield();\n        }\n        assertEquals(\"unknown src recording id \" + unknownId, errorResponse);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldReplicateStoppedRecording(final boolean useParams)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader counters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(counters, counterId, publication.position());\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n\n        dstRecordingSignalConsumer.reset();\n        if (useParams)\n        {\n            dstAeronArchive.replicate(\n                srcRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, new ReplicationParams());\n        }\n        else\n        {\n            dstAeronArchive.replicate(\n                srcRecordingId, NULL_VALUE, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, null);\n        }\n\n        awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n        final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, SYNC);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldThrowExceptionLiveDestinationUsedWithResponseChannels()\n    {\n        final long unknownId = 7L;\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .replicationChannel(SRC_RESPONSE_CHANNEL)\n            .liveDestination(LIVE_CHANNEL);\n\n        final Executable replication = () -> dstAeronArchive.replicate(\n            unknownId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, replicationParams);\n        final ArchiveException archiveException = assertThrows(ArchiveException.class, replication);\n        assertThat(\n            archiveException.getMessage(), containsString(\"response channels can't be used with live destinations\"));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldThrowExceptionTagsUsedWithResponseChannels()\n    {\n        final long unknownId = 7L;\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .replicationChannel(SRC_RESPONSE_CHANNEL)\n            .channelTagId(1).subscriptionTagId(2);\n\n        final Executable replication = () -> dstAeronArchive.replicate(\n            unknownId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, replicationParams);\n        final ArchiveException archiveException = assertThrows(ArchiveException.class, replication);\n        assertThat(\n            archiveException.getMessage(), containsString(\"response channels can't be used with tagged replication\"));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReplicateStoppedRecordingWithResponseChannel()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader counters = srcAeron.countersReader();\n            final int counterId = Tests.awaitRecordingCounterId(\n                counters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(counters, counterId, publication.position());\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n\n        dstRecordingSignalConsumer.reset();\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .replicationChannel(SRC_RESPONSE_CHANNEL)\n            .srcResponseChannel(SRC_RESPONSE_CHANNEL);\n\n        dstAeronArchive.replicate(\n            srcRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, replicationParams);\n\n        awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n        final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, SYNC);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReplicateRecordingWithCustomSessionId()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final int specifiedSessionId = 100_024;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader counters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(counters, counterId, publication.position());\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n\n        dstRecordingSignalConsumer.reset();\n        dstAeronArchive.replicate(\n            srcRecordingId,\n            SRC_CONTROL_STREAM_ID,\n            SRC_CONTROL_REQUEST_CHANNEL,\n            new ReplicationParams().replicationSessionId(specifiedSessionId));\n\n        awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n        final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, SYNC);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n\n\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(1);\n        assertEquals(1, dstAeronArchive.listRecording(dstRecordingId, collector.reset()));\n        final RecordingDescriptor dstRecording = collector.descriptors().get(0).retain();\n\n        assertEquals(specifiedSessionId, dstRecording.sessionId());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldReplicateWithOlderVersion(final boolean useParams)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader counters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(counters, counterId, publication.position());\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n\n        dstRecordingSignalConsumer.reset();\n        if (useParams)\n        {\n            dstAeronArchive.replicate(\n                srcRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, new ReplicationParams());\n        }\n        else\n        {\n            dstAeronArchive.replicate(\n                srcRecordingId, NULL_VALUE, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, null);\n        }\n\n        awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n        final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, SYNC);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReplicateStoppedRecordingsConcurrently()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long[] srcRecordingIds = new long[2];\n        long position = 0;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n\n        for (int i = 0; i < 2; i++)\n        {\n            try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n            {\n                final CountersReader counters = srcAeron.countersReader();\n                final int counterId =\n                    Tests.awaitRecordingCounterId(counters, publication.sessionId(), srcAeronArchive.archiveId());\n                srcRecordingIds[i] = RecordingPos.getRecordingId(counters, counterId);\n\n                offer(publication, messageCount, messagePrefix);\n                position = publication.position();\n                Tests.awaitPosition(counters, counterId, position);\n            }\n            awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingIds[i], STOP);\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n\n        for (int i = 0; i < 2; i++)\n        {\n            dstRecordingSignalConsumer.reset();\n            dstAeronArchive.archiveProxy().replicate(\n                srcRecordingIds[i],\n                SRC_CONTROL_STREAM_ID,\n                SRC_CONTROL_REQUEST_CHANNEL,\n                new ReplicationParams(),\n                dstAeronArchive.context().aeron().nextCorrelationId(),\n                dstAeronArchive.controlSessionId());\n        }\n\n        int stopCount = 0;\n        while (stopCount < 2)\n        {\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, STOP);\n            stopCount++;\n        }\n\n        assertEquals(dstAeronArchive.getStopPosition(0), position);\n        assertEquals(dstAeronArchive.getStopPosition(1), position);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldReplicateLiveWithoutMergingRecording(final boolean useParams)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader srcCounters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(srcCounters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(srcCounters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(srcCounters, counterId, publication.position());\n\n            dstRecordingSignalConsumer.reset();\n            final long replicationId;\n            if (useParams)\n            {\n                replicationId = dstAeronArchive.replicate(\n                    srcRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, new ReplicationParams());\n            }\n            else\n            {\n                replicationId = dstAeronArchive.replicate(\n                    srcRecordingId, NULL_VALUE, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, null);\n            }\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n            final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n            resetAndAwaitSignal(\n                dstAeronArchive,\n                dstRecordingSignalConsumer,\n                dstRecordingId,\n                EXTEND);\n\n            final CountersReader dstCounters = dstAeronArchive.context().aeron().countersReader();\n            final int dstCounterId =\n                RecordingPos.findCounterIdByRecording(dstCounters, dstRecordingId, dstAeronArchive.archiveId());\n            Tests.awaitPosition(dstCounters, dstCounterId, publication.position());\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(dstCounters, dstCounterId, publication.position());\n\n            dstRecordingSignalConsumer.reset();\n            dstAeronArchive.stopReplication(replicationId);\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldReplicateLiveRecordingAndStopAtSpecifiedPosition(final boolean useParams)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader srcCounters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(srcCounters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(srcCounters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            final long firstPosition = publication.position();\n            Tests.awaitPosition(srcCounters, counterId, firstPosition);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(srcCounters, counterId, publication.position());\n\n            dstRecordingSignalConsumer.reset();\n            if (useParams)\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId,\n                    SRC_CONTROL_STREAM_ID,\n                    SRC_CONTROL_REQUEST_CHANNEL,\n                    new ReplicationParams().stopPosition(firstPosition));\n            }\n            else\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId,\n                    NULL_VALUE,\n                    firstPosition,\n                    SRC_CONTROL_STREAM_ID,\n                    SRC_CONTROL_REQUEST_CHANNEL,\n                    null,\n                    null);\n            }\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n            final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n\n            offer(publication, messageCount, messagePrefix);\n            final int srcCounterId =\n                RecordingPos.findCounterIdByRecording(srcCounters, srcRecordingId, srcAeronArchive.archiveId());\n            Tests.awaitPosition(srcCounters, srcCounterId, publication.position());\n\n            assertTrue(firstPosition < publication.position());\n            long dstStopPosition;\n            while (NULL_POSITION == (dstStopPosition = dstAeronArchive.getStopPosition(dstRecordingId)))\n            {\n                Tests.yield();\n            }\n            assertEquals(firstPosition, dstStopPosition);\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReplicateMoreThanOnce()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n        final ReplicationParams replicationParams = new ReplicationParams();\n\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader srcCounters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(srcCounters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(srcCounters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(srcCounters, counterId, publication.position());\n\n            dstRecordingSignalConsumer.reset();\n            replicationParams\n                .reset()\n                .replicationSessionId((int)dstAeronArchive.context().aeron().nextCorrelationId());\n\n            long replicationId = dstAeronArchive.replicate(\n                srcRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, replicationParams);\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n            final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n\n            final CountersReader dstCounters = dstArchive.context().aeron().countersReader();\n            int dstCounterId =\n                RecordingPos.findCounterIdByRecording(dstCounters, dstRecordingId, dstAeronArchive.archiveId());\n            Tests.awaitPosition(dstCounters, dstCounterId, publication.position());\n\n            dstRecordingSignalConsumer.reset();\n            dstAeronArchive.stopReplication(replicationId);\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n\n            dstRecordingSignalConsumer.reset();\n\n            replicationParams\n                .reset()\n                .replicationSessionId((int)dstAeronArchive.context().aeron().nextCorrelationId())\n                .dstRecordingId(dstRecordingId);\n            replicationId = dstAeronArchive.replicate(\n                srcRecordingId,\n                SRC_CONTROL_STREAM_ID,\n                SRC_CONTROL_REQUEST_CHANNEL,\n                replicationParams);\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n\n            dstCounterId =\n                RecordingPos.findCounterIdByRecording(dstCounters, dstRecordingId, dstAeronArchive.archiveId());\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(dstCounters, dstCounterId, publication.position());\n\n            dstRecordingSignalConsumer.reset();\n            dstAeronArchive.stopReplication(replicationId);\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldHandleMultipleConcurrentReplicationsToSeveralArchives(\n        final boolean useResponseChannels, final @TempDir Path tempDir)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n        final ReplicationParams replicationParams = new ReplicationParams()\n            .replicationSessionId(42);\n        if (useResponseChannels)\n        {\n            replicationParams\n                .replicationChannel(\"aeron:udp?control-mode=response|control=localhost:3000\")\n                .srcResponseChannel(SRC_RESPONSE_CHANNEL);\n        }\n        else\n        {\n            replicationParams.replicationChannel(\"aeron:udp?endpoint=localhost:0\");\n        }\n\n        try (TestMediaDriver driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .threadingMode(ThreadingMode.SHARED), systemTestWatcher);\n            Archive archive = Archive.launch(srcArchiveCtx.clone()\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .archiveDir(tempDir.resolve(\"archive\").toFile())\n                .archiveClientContext(new AeronArchive.Context()\n                    .controlResponseChannel(\"aeron:udp?endpoint=localhost:0\").controlResponseStreamId(222))\n                .controlChannel(\"aeron:udp?endpoint=127.0.0.1:8099\")\n                .controlStreamId(110)\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .archiveId(Long.MAX_VALUE));\n            AeronArchive aeronArchive = AeronArchive.connect(new AeronArchive.Context()\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .controlRequestChannel(archive.context().controlChannel())\n                .controlRequestStreamId(archive.context().controlStreamId())\n                .controlResponseStreamId(120)\n                .controlResponseChannel(\"aeron:udp?control-mode=response|control=127.0.0.1:30000\")))\n        {\n            final TestRecordingSignalConsumer recordingSignalConsumer =\n                injectRecordingSignalConsumer(aeronArchive);\n\n            try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n            {\n                final CountersReader srcCounters = srcAeron.countersReader();\n                final int counterId =\n                    Tests.awaitRecordingCounterId(srcCounters, publication.sessionId(), srcAeronArchive.archiveId());\n                srcRecordingId = RecordingPos.getRecordingId(srcCounters, counterId);\n\n                offer(publication, messageCount, messagePrefix);\n                Tests.awaitPosition(srcCounters, counterId, publication.position());\n\n                dstRecordingSignalConsumer.reset();\n                final long replicationId1 = dstAeronArchive.replicate(\n                    srcRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, replicationParams);\n\n                recordingSignalConsumer.reset();\n                final long replicationId2 = aeronArchive.replicate(\n                    srcRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, replicationParams);\n\n                awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n                final long dstRecordingId1 = dstRecordingSignalConsumer.recordingId;\n                resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId1, EXTEND);\n\n                awaitSignal(aeronArchive, recordingSignalConsumer, REPLICATE);\n                final long dstRecordingId2 = recordingSignalConsumer.recordingId;\n                resetAndAwaitSignal(aeronArchive, recordingSignalConsumer, dstRecordingId2, EXTEND);\n\n                final CountersReader counters1 = dstArchive.context().aeron().countersReader();\n                final int counterId1 =\n                    RecordingPos.findCounterIdByRecording(counters1, dstRecordingId1, dstAeronArchive.archiveId());\n                Tests.awaitPosition(counters1, counterId1, publication.position());\n\n                final CountersReader counters2 = aeronArchive.context().aeron().countersReader();\n                final int counterId2 =\n                    RecordingPos.findCounterIdByRecording(counters2, dstRecordingId2, aeronArchive.archiveId());\n                Tests.awaitPosition(counters2, counterId2, publication.position());\n\n                offer(publication, messageCount, messagePrefix);\n                Tests.awaitPosition(counters1, counterId1, publication.position());\n                Tests.awaitPosition(counters2, counterId2, publication.position());\n\n                dstRecordingSignalConsumer.reset();\n                dstAeronArchive.stopReplication(replicationId1);\n\n                recordingSignalConsumer.reset();\n                aeronArchive.stopReplication(replicationId2);\n\n                awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId1, STOP);\n                awaitSignal(aeronArchive, recordingSignalConsumer, dstRecordingId1, STOP);\n            }\n        }\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldReplicateSyncedRecording(final boolean useParams)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader srcCounters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(srcCounters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(srcCounters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(srcCounters, counterId, publication.position());\n\n            srcRecordingSignalConsumer.reset();\n            srcAeronArchive.stopRecording(subscriptionId);\n            awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n\n            dstRecordingSignalConsumer.reset();\n            if (useParams)\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, new ReplicationParams());\n            }\n            else\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId, NULL_VALUE, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, null);\n            }\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n            final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n\n\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, SYNC);\n            resetAndAwaitSignal(\n                dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n\n            dstRecordingSignalConsumer.reset();\n            if (useParams)\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId,\n                    SRC_CONTROL_STREAM_ID,\n                    SRC_CONTROL_REQUEST_CHANNEL,\n                    new ReplicationParams().dstRecordingId(dstRecordingId));\n            }\n            else\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId, dstRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, null);\n            }\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, SYNC);\n            resetAndAwaitSignal(\n                dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldReplicateLiveRecordingAndMerge(final boolean useParams)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n\n        srcRecordingSignalConsumer.reset();\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, START);\n            final long signaledRecordingId = srcRecordingSignalConsumer.recordingId;\n            final CountersReader srcCounters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(srcCounters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(srcCounters, counterId);\n            assertEquals(srcRecordingId, signaledRecordingId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(srcCounters, counterId, publication.position());\n\n            dstRecordingSignalConsumer.reset();\n            if (useParams)\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId,\n                    SRC_CONTROL_STREAM_ID,\n                    SRC_CONTROL_REQUEST_CHANNEL,\n                    new ReplicationParams().liveDestination(LIVE_CHANNEL));\n            }\n            else\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId, NULL_VALUE, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, LIVE_CHANNEL);\n            }\n\n            offer(publication, messageCount, messagePrefix);\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n            final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, MERGE);\n\n            final CountersReader dstCounters = dstArchive.context().aeron().countersReader();\n            final int dstCounterId =\n                RecordingPos.findCounterIdByRecording(dstCounters, dstRecordingId, dstAeronArchive.archiveId());\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(dstCounters, dstCounterId, publication.position());\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldReplicateLiveRecordingAndMergeBeforeDataFlows(final boolean useParams)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n        final long dstRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader srcCounters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(srcCounters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(srcCounters, counterId);\n\n            dstRecordingSignalConsumer.reset();\n            if (useParams)\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId,\n                    SRC_CONTROL_STREAM_ID,\n                    SRC_CONTROL_REQUEST_CHANNEL,\n                    new ReplicationParams().liveDestination(LIVE_CHANNEL));\n            }\n            else\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId, NULL_VALUE, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, LIVE_CHANNEL);\n            }\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n            dstRecordingId = dstRecordingSignalConsumer.recordingId;\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, MERGE);\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n\n            final CountersReader dstCounters = dstArchive.context().aeron().countersReader();\n            final int dstCounterId =\n                RecordingPos.findCounterIdByRecording(dstCounters, dstRecordingId, dstAeronArchive.archiveId());\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(dstCounters, dstCounterId, publication.position());\n        }\n\n        dstRecordingSignalConsumer.reset();\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n        assertEquals(0, dstAeronArchive.pollForRecordingSignals());\n        assertEquals(NULL_VALUE, dstRecordingSignalConsumer.recordingId);\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldReplicateLiveRecordingAndMergeWhileFollowingWithTaggedSubscription(final boolean useParams)\n    {\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n        final Aeron dstAeron = dstAeronArchive.context().aeron();\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n        final long dstRecordingId;\n        final long channelTagId = dstAeron.nextCorrelationId();\n        final long subscriptionTagId = dstAeron.nextCorrelationId();\n        final String taggedChannel =\n            \"aeron:udp?control-mode=manual|rejoin=false|tags=\" + channelTagId + \",\" + subscriptionTagId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID);\n            Subscription taggedSubscription = dstAeron.addSubscription(taggedChannel, LIVE_STREAM_ID))\n        {\n            final CountersReader srcCounters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(srcCounters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(srcCounters, counterId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(srcCounters, counterId, publication.position());\n\n            dstRecordingSignalConsumer.reset();\n            if (useParams)\n            {\n                final ReplicationParams replicationParams = new ReplicationParams()\n                    .liveDestination(LIVE_CHANNEL)\n                    .channelTagId(channelTagId)\n                    .subscriptionTagId(subscriptionTagId);\n\n                dstAeronArchive.replicate(\n                    srcRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, replicationParams);\n            }\n            else\n            {\n                dstAeronArchive.taggedReplicate(\n                    srcRecordingId,\n                    NULL_VALUE,\n                    channelTagId,\n                    subscriptionTagId,\n                    SRC_CONTROL_STREAM_ID,\n                    SRC_CONTROL_REQUEST_CHANNEL,\n                    LIVE_CHANNEL);\n            }\n\n            consume(taggedSubscription, messageCount, messagePrefix);\n\n            offer(publication, messageCount, messagePrefix);\n            consume(taggedSubscription, messageCount, messagePrefix);\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n            dstRecordingId = dstRecordingSignalConsumer.recordingId;\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, MERGE);\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n\n            final CountersReader dstCounters = dstAeron.countersReader();\n            final int dstCounterId =\n                RecordingPos.findCounterIdByRecording(dstCounters, dstRecordingId, dstAeronArchive.archiveId());\n\n            offer(publication, messageCount, messagePrefix);\n            consume(taggedSubscription, messageCount, messagePrefix);\n            Tests.awaitPosition(dstCounters, dstCounterId, publication.position());\n\n            final Image image = taggedSubscription.imageBySessionId(publication.sessionId());\n            assertEquals(publication.position(), image.position());\n        }\n\n        dstRecordingSignalConsumer.reset();\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n        assertEquals(0, dstAeronArchive.pollForRecordingSignals());\n        assertEquals(NULL_VALUE, dstRecordingSignalConsumer.recordingId);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    public void shouldReplicateStoppedRecordingWithFileIoMaxLength()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int fileIoMaxLength = 1500;\n        final int longMessagePadding = (2 * fileIoMaxLength) + 1000;\n\n        final StringBuilder longMessagePrefix = new StringBuilder();\n        longMessagePrefix.append(messagePrefix);\n        while (longMessagePrefix.length() < longMessagePadding)\n        {\n            longMessagePrefix.append('X');\n        }\n        longMessagePrefix.append('-');\n\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader counters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offer(publication, messageCount, longMessagePrefix.toString());\n            Tests.awaitPosition(counters, counterId, publication.position());\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n\n        dstRecordingSignalConsumer.reset();\n        dstAeronArchive.replicate(\n            srcRecordingId,\n            SRC_CONTROL_STREAM_ID,\n            SRC_CONTROL_REQUEST_CHANNEL,\n            new ReplicationParams().fileIoMaxLength(fileIoMaxLength));\n\n        awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n        final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, SYNC);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n\n        assertNotEquals(NULL_VALUE, dstRecordingId);\n\n        validateRecordingAreEqual(srcRecordingId, dstRecordingId);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    public void shouldErrorReplicateIfFileIoMaxLengthIsLessThanMtu()\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int fileIoMaxLength = 1500;\n        final int longMessagePadding = (2 * fileIoMaxLength) + 1000;\n\n        final StringBuilder longMessagePrefix = new StringBuilder();\n        longMessagePrefix.append(messagePrefix);\n        while (longMessagePrefix.length() < longMessagePadding)\n        {\n            longMessagePrefix.append('X');\n        }\n        longMessagePrefix.append('-');\n\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            final CountersReader counters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(counters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(counters, counterId);\n\n            offer(publication, messageCount, longMessagePrefix.toString());\n            Tests.awaitPosition(counters, counterId, publication.position());\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n        final int i = srcAeronArchive.listRecording(srcRecordingId, collector.reset());\n        assertEquals(1, i);\n\n        final RecordingDescriptor descriptor = collector.descriptors().get(0);\n        final int mtu = descriptor.mtuLength();\n\n        dstAeronArchive.replicate(\n            srcRecordingId,\n            SRC_CONTROL_STREAM_ID,\n            SRC_CONTROL_REQUEST_CHANNEL,\n            new ReplicationParams().fileIoMaxLength(mtu - 1));\n\n        String error;\n        while (null == (error = dstAeronArchive.pollForErrorResponse()))\n        {\n            Tests.yield();\n        }\n\n        assertThat(error, Matchers.containsString(\"mtuLength\"));\n        assertThat(error, Matchers.containsString(\"fileIoMaxLength\"));\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(10)\n    @CsvSource({\n        \"aeron:ipc?alias=src-recording|mtu=1344|init-term-id=777|term-id=1111112|term-offset=4096|\" +\n            \"term-length=512K, aeron:udp?alias=OTHER|endpoint=localhost:8108|term-length=1G, 5, 1\",\n        \"aeron:udp?alias=OTHER|endpoint=localhost:8108|term-length=1G, aeron:ipc?alias=dst-recording|mtu=1344|\" +\n            \"init-term-id=1111111|term-id=1111112|term-offset=4096|term-length=512K, 3, 10\",\n        \"aeron:udp?endpoint=localhost:8108|mtu=1344|init-term-id=11|term-id=15|term-offset=1024|term-length=512K, \" +\n            \"aeron:udp?endpoint=localhost:8109|mtu=1376|init-term-id=222|term-id=333|term-offset=96|term-length=256M\" +\n            \", 7, 4\",\n        \"aeron:ipc?alias=src, aeron:udp?alias=dst|endpoint=localhost:8080, 21, 21\",\n        \"aeron:udp?alias=src|endpoint=localhost:8080|init-term-id=3|term-id=5|term-length=64K|term-offset=64, \" +\n            \"aeron:ipc?alias=dst|init-term-id=11|term-id=13|term-length=64K|term-offset=2752, 42, 19\"\n    })\n    public void shouldReplicateStoppedRecordingOverAnExistingTruncatedRecordingReplacingAllParameters(\n        final String srcChannel, final String dstChannel, final int srcMessageCount, final int dstMessageCount)\n    {\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(1);\n        final int srcStreamId = 3333;\n        final long srcRecordingId = createStoppedRecording(\n            srcAeronArchive,\n            srcRecordingSignalConsumer,\n            srcChannel,\n            srcStreamId,\n            \"src recording data\", srcMessageCount);\n\n        int dstStreamId = 555;\n        long dstRecordingId;\n        while (srcRecordingId >= (dstRecordingId = createStoppedRecording(\n            dstAeronArchive, dstRecordingSignalConsumer, \"aeron:ipc?term-length=64K\", dstStreamId, \"temp\", 1)))\n        {\n            dstRecordingSignalConsumer.reset();\n            dstAeronArchive.truncateRecording(dstRecordingId, 0);\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, DELETE);\n            dstStreamId++;\n        }\n\n        dstRecordingId = createStoppedRecording(\n            dstAeronArchive, dstRecordingSignalConsumer, dstChannel, dstStreamId, \"destination 42\", dstMessageCount);\n        assertNotEquals(srcRecordingId, dstRecordingId);\n        assertNotEquals(srcStreamId, dstStreamId);\n\n        assertEquals(1, srcAeronArchive.listRecording(srcRecordingId, collector.reset()));\n        final RecordingDescriptor srcRecording = collector.descriptors().get(0).retain();\n        assertEquals(1, dstAeronArchive.listRecording(dstRecordingId, collector.reset()));\n        final RecordingDescriptor dstRecording = collector.descriptors().get(0).retain();\n        assertNotEquals(srcRecording.startTimestamp(), dstRecording.startTimestamp());\n        assertNotEquals(srcRecording.stopTimestamp(), dstRecording.stopTimestamp());\n        assertNotEquals(srcRecording.controlSessionId(), dstRecording.controlSessionId());\n        assertNotEquals(srcRecording.sessionId(), dstRecording.sessionId());\n        assertNotEquals(srcRecording.streamId(), dstRecording.streamId());\n        assertNotEquals(srcRecording.strippedChannel(), dstRecording.strippedChannel());\n        assertNotEquals(srcRecording.originalChannel(), dstRecording.originalChannel());\n        assertEquals(srcRecording.sourceIdentity(), dstRecording.sourceIdentity());\n\n        dstRecordingSignalConsumer.reset();\n        dstAeronArchive.truncateRecording(dstRecordingId, dstRecording.startPosition());\n        awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, DELETE);\n\n        dstRecordingSignalConsumer.reset();\n        dstAeronArchive.replicate(\n            srcRecordingId, dstRecordingId, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, null);\n\n        awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, SYNC);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n        resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n\n        assertEquals(1, dstAeronArchive.listRecording(dstRecordingId, collector.reset()));\n        final RecordingDescriptor replicatedRecording = collector.descriptors().get(0).retain();\n        assertEquals(srcRecording.startTimestamp(), replicatedRecording.startTimestamp());\n        assertEquals(srcRecording.startPosition(), replicatedRecording.startPosition());\n        assertEquals(srcRecording.stopPosition(), replicatedRecording.stopPosition());\n        assertEquals(srcRecording.initialTermId(), replicatedRecording.initialTermId());\n        assertEquals(srcRecording.segmentFileLength(), replicatedRecording.segmentFileLength());\n        assertEquals(srcRecording.termBufferLength(), replicatedRecording.termBufferLength());\n        assertEquals(srcRecording.mtuLength(), replicatedRecording.mtuLength());\n        assertEquals(srcRecording.sessionId(), replicatedRecording.sessionId());\n        assertEquals(srcRecording.streamId(), replicatedRecording.streamId());\n        assertEquals(srcRecording.strippedChannel(), replicatedRecording.strippedChannel());\n        assertEquals(srcRecording.originalChannel(), replicatedRecording.originalChannel());\n        assertEquals(srcRecording.sourceIdentity(), replicatedRecording.sourceIdentity());\n        assertEquals(dstRecording.controlSessionId(), replicatedRecording.controlSessionId());\n        // extend recording will overwrite the stopTimestamp\n        assertNotEquals(srcRecording.stopTimestamp(), replicatedRecording.stopTimestamp());\n        assertNotEquals(dstRecording.stopTimestamp(), replicatedRecording.stopTimestamp());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    @InterruptAfter(10)\n    void shouldReplicateLiveRecordingAndMergeIntoADestinationIpcOnlyArchive(final boolean useParams)\n    {\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        CloseHelper.closeAll(dstAeronArchive, dstArchive, dstDriver);\n\n        final long subscriptionId = srcAeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n\n        dstDriver = TestMediaDriver.launch(new MediaDriver.Context()\n                .aeronDirectoryName(generateRandomDirName())\n                .termBufferSparseFile(true)\n                .threadingMode(ThreadingMode.SHARED)\n                .spiesSimulateConnection(true)\n                .timerIntervalNs(TIMER_INTERVAL_NS)\n                .dirDeleteOnStart(true),\n            systemTestWatcher);\n        systemTestWatcher.dataCollector().add(dstDriver.context().aeronDirectory());\n\n        dstArchive = Archive.launch(new Archive.Context()\n            .catalogCapacity(CATALOG_CAPACITY)\n            .aeronDirectoryName(dstDriver.aeronDirectoryName())\n            .controlChannel(null)\n            .controlChannelEnabled(false)\n            .localControlChannel(IPC_CHANNEL)\n            .localControlStreamId(4444)\n            .archiveClientContext(\n                new AeronArchive.Context().controlResponseChannel(\"aeron:udp?endpoint=localhost:0\"))\n            .recordingEventsEnabled(false)\n            .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n            .deleteArchiveOnStart(true)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"ipc-archive\"))\n            .fileSyncLevel(0)\n            .threadingMode(ArchiveThreadingMode.SHARED));\n        systemTestWatcher.dataCollector().add(dstArchive.context().archiveDir());\n\n        dstAeronArchive = AeronArchive.connect(new AeronArchive.Context()\n            .controlRequestChannel(dstArchive.context().localControlChannel())\n            .controlRequestStreamId(dstArchive.context().localControlStreamId())\n            .controlResponseChannel(IPC_CHANNEL)\n            .controlResponseStreamId(5555)\n            .idleStrategy(YieldingIdleStrategy.INSTANCE)\n            .aeronDirectoryName(dstDriver.aeronDirectoryName()));\n\n        dstRecordingSignalConsumer = injectRecordingSignalConsumer(dstAeronArchive);\n        final Aeron srcAeron = srcAeronArchive.context().aeron();\n\n        srcRecordingSignalConsumer.reset();\n        try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n        {\n            awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, START);\n            final long signaledRecordingId = srcRecordingSignalConsumer.recordingId;\n            final CountersReader srcCounters = srcAeron.countersReader();\n            final int counterId =\n                Tests.awaitRecordingCounterId(srcCounters, publication.sessionId(), srcAeronArchive.archiveId());\n            srcRecordingId = RecordingPos.getRecordingId(srcCounters, counterId);\n            assertEquals(srcRecordingId, signaledRecordingId);\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(srcCounters, counterId, publication.position());\n\n            dstRecordingSignalConsumer.reset();\n            if (useParams)\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId,\n                    SRC_CONTROL_STREAM_ID,\n                    SRC_CONTROL_REQUEST_CHANNEL,\n                    new ReplicationParams().liveDestination(LIVE_CHANNEL));\n            }\n            else\n            {\n                dstAeronArchive.replicate(\n                    srcRecordingId, NULL_VALUE, SRC_CONTROL_STREAM_ID, SRC_CONTROL_REQUEST_CHANNEL, LIVE_CHANNEL);\n            }\n\n            offer(publication, messageCount, messagePrefix);\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n            final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, MERGE);\n\n            final CountersReader dstCounters = dstArchive.context().aeron().countersReader();\n            final int dstCounterId =\n                RecordingPos.findCounterIdByRecording(dstCounters, dstRecordingId, dstArchive.context().archiveId());\n\n            offer(publication, messageCount, messagePrefix);\n            Tests.awaitPosition(dstCounters, dstCounterId, publication.position());\n        }\n\n        srcRecordingSignalConsumer.reset();\n        srcAeronArchive.stopRecording(subscriptionId);\n        awaitSignal(srcAeronArchive, srcRecordingSignalConsumer, srcRecordingId, STOP);\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(30)\n    void shouldHandleInvalidSrcEndpoint()\n    {\n        try\n        {\n            dstAeronArchive.replicate(\n                NULL_VALUE,\n                SRC_CONTROL_STREAM_ID,\n                INVALID_SRC_CONTROL_REQUEST_CHANNEL,\n                new ReplicationParams());\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n        }\n        catch (final ArchiveException ex)\n        {\n            assertEquals(ArchiveException.REPLICATION_CONNECTION_FAILURE, ex.errorCode());\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE_END);\n\n            return;\n        }\n\n        fail(\"expected archive exception\");\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldReplicateWithAuthentication()\n    {\n        final Archive.Context authArchiveCtx = srcArchiveCtx\n            .clone()\n            .controlChannel(\"aeron:udp?endpoint=localhost:8098\")\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"auth-archive\"))\n            .authenticatorSupplier(new SampleAuthenticatorSupplier());\n        final AeronArchive.Context authAeronArchiveCtx = srcAeronArchiveCtx\n            .clone()\n            .credentialsSupplier(TestCluster.SIMPLE_CREDENTIALS_SUPPLIER)\n            .controlRequestChannel(authArchiveCtx.controlChannel());\n\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        try (Archive archive = Archive.launch(authArchiveCtx);\n            AeronArchive aeronArchive = AeronArchive.connect(authAeronArchiveCtx))\n        {\n            Objects.requireNonNull(archive);\n            final TestRecordingSignalConsumer authSignalConsumer = injectRecordingSignalConsumer(aeronArchive);\n\n            final long subscriptionId = aeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n\n            final Aeron srcAeron = aeronArchive.context().aeron();\n            try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n            {\n                final CountersReader counters = srcAeron.countersReader();\n                final int counterId =\n                    Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n                srcRecordingId = RecordingPos.getRecordingId(counters, counterId);\n\n                offer(publication, messageCount, messagePrefix);\n                Tests.awaitPosition(counters, counterId, publication.position());\n            }\n\n            authSignalConsumer.reset();\n            aeronArchive.stopRecording(subscriptionId);\n            awaitSignal(aeronArchive, authSignalConsumer, srcRecordingId, STOP);\n\n            dstRecordingSignalConsumer.reset();\n            final ReplicationParams replicationParams = new ReplicationParams()\n                .encodedCredentials(TestCluster.SIMPLE_CREDENTIALS_SUPPLIER.encodedCredentials());\n            dstAeronArchive.replicate(\n                srcRecordingId,\n                authAeronArchiveCtx.controlRequestStreamId(),\n                authAeronArchiveCtx.controlRequestChannel(),\n                replicationParams);\n\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE);\n            final long dstRecordingId = dstRecordingSignalConsumer.recordingId;\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, EXTEND);\n            resetAndAwaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, REPLICATE_END);\n            awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, dstRecordingId, STOP);\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldFailReplicationWithUsefulErrorWithChallengeResponseAuthentication()\n    {\n        final Archive.Context authArchiveCtx = srcArchiveCtx\n            .clone()\n            .controlChannel(\"aeron:udp?endpoint=localhost:8098\")\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"auth-archive\"))\n            .authenticatorSupplier(new SampleAuthenticatorSupplier());\n        final AeronArchive.Context authAeronArchiveCtx = srcAeronArchiveCtx\n            .clone()\n            .credentialsSupplier(TestCluster.CHALLENGE_RESPONSE_CREDENTIALS_SUPPLIER)\n            .controlRequestChannel(authArchiveCtx.controlChannel());\n\n        final String messagePrefix = \"Message-Prefix-\";\n        final int messageCount = 10;\n        final long srcRecordingId;\n\n        try (Archive archive = Archive.launch(authArchiveCtx);\n            AeronArchive aeronArchive = AeronArchive.connect(authAeronArchiveCtx))\n        {\n            Objects.requireNonNull(archive);\n            final TestRecordingSignalConsumer authSignalConsumer = injectRecordingSignalConsumer(aeronArchive);\n\n            final long subscriptionId = aeronArchive.startRecording(LIVE_CHANNEL, LIVE_STREAM_ID, LOCAL);\n\n            final Aeron srcAeron = aeronArchive.context().aeron();\n            try (Publication publication = srcAeron.addPublication(LIVE_CHANNEL, LIVE_STREAM_ID))\n            {\n                final CountersReader counters = srcAeron.countersReader();\n                final int counterId =\n                    Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n                srcRecordingId = RecordingPos.getRecordingId(counters, counterId);\n\n                offer(publication, messageCount, messagePrefix);\n                Tests.awaitPosition(counters, counterId, publication.position());\n            }\n\n            authSignalConsumer.reset();\n            aeronArchive.stopRecording(subscriptionId);\n            awaitSignal(aeronArchive, authSignalConsumer, srcRecordingId, STOP);\n\n            dstRecordingSignalConsumer.reset();\n            final ReplicationParams replicationParams = new ReplicationParams()\n                .encodedCredentials(TestCluster.CHALLENGE_RESPONSE_CREDENTIALS_SUPPLIER.encodedCredentials());\n            dstAeronArchive.replicate(\n                srcRecordingId,\n                authAeronArchiveCtx.controlRequestStreamId(),\n                authAeronArchiveCtx.controlRequestChannel(),\n                replicationParams);\n\n            final ArchiveException archiveException = assertThrows(\n                ArchiveException.class,\n                () -> awaitSignal(dstAeronArchive, dstRecordingSignalConsumer, REPLICATE));\n            assertThat(\n                archiveException.getMessage(),\n                containsString(\"Replication does not support challenge/response authentication\"));\n        }\n    }\n\n    private void readRecordingIntoBuffer(final long srcRecordingId, final ExpandableArrayBuffer srcRecordingData)\n    {\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n        final int i = srcAeronArchive.listRecording(srcRecordingId, collector.reset());\n        assertEquals(1, i);\n        final RecordingDescriptor descriptor = collector.descriptors().get(0);\n        final long length = descriptor.stopPosition() - descriptor.startPosition();\n\n        try (Subscription replay = srcAeronArchive.replay(\n            srcRecordingId, descriptor.startPosition(), length, REPLAY_CHANNEL, REPLAY_STREAM_ID))\n        {\n            final MutableInteger position = new MutableInteger(0);\n            Tests.awaitConnected(replay);\n\n            // Assumes session specific subscription used for replay.\n            final Image image = replay.imageAtIndex(0);\n            final FragmentHandler fragmentHandler =\n                (buffer, offset, len, header) ->\n                {\n                    srcRecordingData.putBytes(position.get(), buffer, offset, len);\n                    position.addAndGet(len);\n                };\n\n            while (!image.isEndOfStream())\n            {\n                image.poll(fragmentHandler, 10);\n            }\n        }\n    }\n\n    private long createStoppedRecording(\n        final AeronArchive aeronArchive,\n        final TestRecordingSignalConsumer recordingSignalConsumer,\n        final String channel,\n        final int streamId,\n        final String payload,\n        final int messageCount)\n    {\n        try (ExclusivePublication publication = aeronArchive.addRecordedExclusivePublication(channel, streamId))\n        {\n            long recordingId = Long.MIN_VALUE;\n            try\n            {\n                final CountersReader counters = aeronArchive.context().aeron().countersReader();\n                final int counterId =\n                    Tests.awaitRecordingCounterId(counters, publication.sessionId(), aeronArchive.archiveId());\n                recordingId = RecordingPos.getRecordingId(counters, counterId);\n\n                offer(publication, messageCount, payload);\n                Tests.awaitPosition(counters, counterId, publication.position());\n                return recordingId;\n            }\n            finally\n            {\n                recordingSignalConsumer.reset();\n                aeronArchive.stopRecording(publication);\n                awaitSignal(aeronArchive, recordingSignalConsumer, recordingId, STOP);\n            }\n        }\n    }\n\n    private void validateRecordingAreEqual(final long srcRecordingId, final long dstRecordingId)\n    {\n        final ExpandableArrayBuffer srcRecordingData = new ExpandableArrayBuffer();\n        readRecordingIntoBuffer(srcRecordingId, srcRecordingData);\n\n        final ExpandableArrayBuffer dstRecordingData = new ExpandableArrayBuffer();\n        readRecordingIntoBuffer(dstRecordingId, dstRecordingData);\n\n        assertEquals(srcRecordingData, dstRecordingData);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/TestRecordingSignalConsumer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.archive.client.RecordingSignalConsumer;\nimport io.aeron.archive.codecs.RecordingSignal;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\n\nclass TestRecordingSignalConsumer implements RecordingSignalConsumer\n{\n    final long controlSessionId;\n    long correlationId;\n    long recordingId;\n    long subscriptionId;\n    long position;\n    RecordingSignal signal;\n\n    TestRecordingSignalConsumer(final long controlSessionId)\n    {\n        this.controlSessionId = controlSessionId;\n    }\n\n    public void onSignal(\n        final long controlSessionId,\n        final long correlationId,\n        final long recordingId,\n        final long subscriptionId,\n        final long position,\n        final RecordingSignal signal)\n    {\n        if (this.controlSessionId != controlSessionId)\n        {\n            throw new IllegalStateException(\"unexpected controlSessionId=\" + controlSessionId);\n        }\n        this.correlationId = correlationId;\n        this.recordingId = recordingId;\n        this.subscriptionId = subscriptionId;\n        this.position = position;\n        this.signal = signal;\n    }\n\n    public void reset()\n    {\n        correlationId = NULL_VALUE;\n        recordingId = NULL_VALUE;\n        subscriptionId = NULL_VALUE;\n        position = NULL_POSITION;\n        signal = null;\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/archive/ValidationTests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.archive;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.CachedEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.nio.file.Path;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.Aeron.Context;\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.Aeron.connect;\nimport static io.aeron.archive.ArchiveSystemTests.awaitSignal;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.codecs.SourceLocation.LOCAL;\nimport static io.aeron.logbuffer.LogBufferDescriptor.TERM_MIN_LENGTH;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.endsWith;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\n\nclass ValidationTests\n{\n    private static final int MESSAGE_LENGTH = 4200;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]);\n    private final CachedEpochClock epochClock = new CachedEpochClock();\n    private TestMediaDriver driver;\n    private Archive archive;\n    private AeronArchive aeronArchive;\n    private Aeron aeron;\n    private TestRecordingSignalConsumer recordingSignalConsumer;\n\n    @BeforeEach\n    void before(final @TempDir Path tempDir)\n    {\n        driver = TestMediaDriver.launch(\n            new MediaDriver.Context()\n                .aeronDirectoryName(CommonContext.generateRandomDirName())\n                .threadingMode(ThreadingMode.SHARED)\n                .ipcTermBufferLength(TERM_MIN_LENGTH)\n                .publicationTermBufferLength(TERM_MIN_LENGTH),\n            systemTestWatcher);\n\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = connect(new Context().aeronDirectoryName(driver.aeronDirectoryName()));\n\n        epochClock.update(System.currentTimeMillis());\n        archive = Archive.launch(\n            new Archive.Context()\n                .aeronDirectoryName(driver.aeronDirectoryName())\n                .archiveDir(tempDir.resolve(\"archive\").toFile())\n                .threadingMode(ArchiveThreadingMode.INVOKER)\n                .controlChannel(\"aeron:udp?endpoint=localhost:5050\")\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .segmentFileLength(TERM_MIN_LENGTH)\n                .epochClock(epochClock));\n\n        systemTestWatcher.dataCollector().add(archive.context().archiveDir());\n\n        aeronArchive = AeronArchive.connect(new AeronArchive.Context()\n            .controlRequestChannel(archive.context().localControlChannel())\n            .controlRequestStreamId(archive.context().localControlStreamId())\n            .controlResponseChannel(\"aeron:ipc\")\n            .controlResponseStreamId(4004)\n            .aeronDirectoryName(driver.aeronDirectoryName())\n            .agentInvoker(archive.invoker()));\n\n        recordingSignalConsumer = new TestRecordingSignalConsumer(aeronArchive.controlSessionId());\n        aeronArchive.context().recordingSignalConsumer(recordingSignalConsumer);\n\n        ThreadLocalRandom.current().nextBytes(buffer.byteArray());\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, aeronArchive, archive, driver);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRejectExtendRecordingRequestIfTruncationIsInProgress()\n    {\n        assertNotNull(archive.invoker());\n\n        final String channel = \"aeron:ipc?ssc=true\";\n        final int streamId = 888;\n        final CountersReader countersReader = aeron.countersReader();\n\n        final ExclusivePublication publication = aeron.addExclusivePublication(channel, streamId);\n        final String recordChannel = ChannelUri.addSessionId(channel, publication.sessionId());\n        final long recordingSessionId = aeronArchive.startRecording(\n            recordChannel, streamId, LOCAL, true);\n\n        int recordingCounterId;\n        while (NULL_COUNTER_ID == (recordingCounterId = RecordingPos.findCounterIdBySession(\n            countersReader, publication.sessionId(), aeronArchive.archiveId())))\n        {\n            archive.invoker().invoke();\n        }\n\n        final long recordingId = RecordingPos.getRecordingId(countersReader, recordingCounterId);\n\n        while (publication.offer(buffer, 0, MESSAGE_LENGTH) < 0)\n        {\n            archive.invoker().invoke();\n        }\n        final long truncatePosition = publication.position();\n        final long endPosition = 2L * publication.termBufferLength() + truncatePosition;\n\n        while (publication.position() < endPosition)\n        {\n            if (publication.offer(buffer, 0, MESSAGE_LENGTH) < 0)\n            {\n                archive.invoker().invoke();\n            }\n        }\n        final long stopPosition = publication.position();\n        assertThat(stopPosition, greaterThanOrEqualTo(endPosition));\n\n        while (countersReader.getCounterValue(recordingCounterId) != stopPosition)\n        {\n            archive.invoker().invoke();\n        }\n\n        aeronArchive.stopRecording(recordingSessionId);\n        while (aeronArchive.getStopPosition(recordingId) == NULL_POSITION)\n        {\n            Tests.sleep(1);\n        }\n\n        recordingSignalConsumer.reset();\n        assertEquals(2, aeronArchive.truncateRecording(recordingId, truncatePosition));\n\n        final ArchiveException exception = assertThrowsExactly(\n            ArchiveException.class,\n            () -> aeronArchive.extendRecording(recordingId, recordChannel, streamId, LOCAL, true));\n        assertEquals(ArchiveException.GENERIC, exception.errorCode());\n        assertThat(\n            exception.getMessage(),\n            endsWith(\"cannot extend recording \" + recordingId + \" due to an outstanding delete operation\"));\n\n        awaitSignal(aeronArchive, recordingSignalConsumer, recordingId, RecordingSignal.DELETE);\n\n        // once delete is complete we can extend the recording\n        assertNotEquals(NULL_VALUE, aeronArchive.extendRecording(recordingId, recordChannel, streamId, LOCAL, true));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRejectExtendRecordingSessionIfTruncateIsInProgress()\n    {\n        assertNotNull(archive.invoker());\n\n        final String channel = \"aeron:ipc?ssc=true\";\n        final int streamId = 456;\n        final CountersReader countersReader = aeron.countersReader();\n\n        final ExclusivePublication publication = aeron.addExclusivePublication(channel, streamId);\n        final String recordChannel = ChannelUri.addSessionId(channel, publication.sessionId());\n        final long recordingSessionId = aeronArchive.startRecording(\n            recordChannel, streamId, LOCAL, true);\n\n        int recordingCounterId;\n        while (NULL_COUNTER_ID == (recordingCounterId = RecordingPos.findCounterIdBySession(\n            countersReader, publication.sessionId(), aeronArchive.archiveId())))\n        {\n            epochClock.update(System.currentTimeMillis());\n            archive.invoker().invoke();\n        }\n\n        final long recordingId = RecordingPos.getRecordingId(countersReader, recordingCounterId);\n\n        while (publication.offer(buffer, 0, MESSAGE_LENGTH) < 0)\n        {\n            archive.invoker().invoke();\n        }\n        final long truncatePosition = publication.position();\n        final long endPosition = 10L * publication.termBufferLength() + truncatePosition;\n\n        while (publication.position() < endPosition)\n        {\n            if (publication.offer(buffer, 0, MESSAGE_LENGTH) < 0)\n            {\n                epochClock.update(System.currentTimeMillis());\n                archive.invoker().invoke();\n            }\n        }\n        final long stopPosition = publication.position();\n        assertThat(stopPosition, greaterThanOrEqualTo(endPosition));\n\n        while (countersReader.getCounterValue(recordingCounterId) != stopPosition)\n        {\n            epochClock.update(System.currentTimeMillis());\n            archive.invoker().invoke();\n        }\n\n        aeronArchive.stopRecording(recordingSessionId);\n        while (aeronArchive.getStopPosition(recordingId) == NULL_POSITION)\n        {\n            Tests.sleep(1);\n        }\n\n        assertNotEquals(\n            NULL_VALUE, aeronArchive.extendRecording(recordingId, recordChannel, streamId, LOCAL, true));\n\n        assertEquals(10, aeronArchive.truncateRecording(recordingId, truncatePosition));\n\n        epochClock.advance(1);\n        archive.invoker().invoke();\n\n        final String error = aeronArchive.pollForErrorResponse();\n        assertThat(\n            error,\n            containsString(\"cannot extend recording \" + recordingId + \" due to an outstanding delete operation\"));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRejectDeleteAttemptIfAnotherOneInProgress()\n    {\n        assertNotNull(archive.invoker());\n\n        final String channel = \"aeron:ipc?ssc=true\";\n        final int streamId = 123;\n        final CountersReader countersReader = aeron.countersReader();\n\n        final ExclusivePublication publication = aeron.addExclusivePublication(channel, streamId);\n        final String recordChannel = ChannelUri.addSessionId(channel, publication.sessionId());\n        final long recordingSessionId = aeronArchive.startRecording(\n            recordChannel, streamId, LOCAL, true);\n\n        int recordingCounterId;\n        while (NULL_COUNTER_ID == (recordingCounterId = RecordingPos.findCounterIdBySession(\n            countersReader, publication.sessionId(), aeronArchive.archiveId())))\n        {\n            epochClock.update(System.currentTimeMillis());\n            archive.invoker().invoke();\n        }\n\n        final long recordingId = RecordingPos.getRecordingId(countersReader, recordingCounterId);\n\n        final long truncatePosition = 0;\n        final long endPosition = 3L * publication.termBufferLength() + 1000;\n\n        while (publication.position() < endPosition)\n        {\n            if (publication.offer(buffer, 0, MESSAGE_LENGTH) < 0)\n            {\n                epochClock.update(System.currentTimeMillis());\n                archive.invoker().invoke();\n            }\n        }\n        final long stopPosition = publication.position();\n        assertThat(stopPosition, greaterThanOrEqualTo(endPosition));\n\n        while (countersReader.getCounterValue(recordingCounterId) != stopPosition)\n        {\n            epochClock.update(System.currentTimeMillis());\n            archive.invoker().invoke();\n        }\n\n        aeronArchive.stopRecording(recordingSessionId);\n        while (aeronArchive.getStopPosition(recordingId) == NULL_POSITION)\n        {\n            Tests.sleep(1);\n        }\n\n        assertEquals(4, aeronArchive.truncateRecording(recordingId, truncatePosition));\n\n        final ArchiveException exception =\n            assertThrowsExactly(ArchiveException.class, () -> aeronArchive.purgeRecording(recordingId));\n        assertThat(\n            exception.getMessage(),\n            endsWith(\"another delete operation in progress for recording id: \" + recordingId));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/AppointedLeaderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass AppointedLeaderTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private static final int LEADER_ID = 1;\n\n    @Test\n    @InterruptAfter(20)\n    void shouldConnectAndSendKeepAlive()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).withAppointedLeader(LEADER_ID).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        assertEquals(LEADER_ID, leader.index());\n        assertEquals(Cluster.Role.LEADER, leader.role());\n\n        cluster.connectClient();\n        assertTrue(cluster.client().sendKeepAlive());\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldEchoMessagesViaService()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).withAppointedLeader(LEADER_ID).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        assertEquals(LEADER_ID, leader.index());\n        assertEquals(Cluster.Role.LEADER, leader.role());\n\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(10);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/ClusterBackupTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.samples.archive.SampleAuthenticator;\nimport io.aeron.samples.archive.SampleAuthorisationService;\nimport io.aeron.security.AuthenticatorSupplier;\nimport io.aeron.security.AuthorisationServiceSupplier;\nimport io.aeron.security.NullCredentialsSupplier;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestBackupNode;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.errors.ErrorLogReader;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.List;\n\nimport static io.aeron.cluster.ClusterBackup.Configuration.ReplayStart.LATEST_SNAPSHOT;\nimport static io.aeron.test.SystemTestWatcher.UNKNOWN_HOST_FILTER;\nimport static io.aeron.test.cluster.TestCluster.*;\nimport static java.util.Collections.singletonList;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@SlowTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ClusterBackupTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBackupClusterNoSnapshotsAndEmptyLog()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        cluster.startClusterBackupNode(true);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(cluster.findLeader().service().cluster().logPosition());\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n\n        assertEquals(0, node.service().messageCount());\n        assertFalse(node.service().wasSnapshotLoaded());\n    }\n\n    @InterruptAfter(30)\n    @ParameterizedTest()\n    @EnumSource(value = ClusterBackup.SourceType.class)\n    void shouldBackupClusterNoSnapshotsAndNonEmptyLog(final ClusterBackup.SourceType sourceType)\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        final long logPosition = leader.service().cluster().logPosition();\n\n        cluster.startClusterBackupNode(true, sourceType);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n\n        assertEquals(messageCount, node.service().messageCount());\n        assertFalse(node.service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBackupClusterNoSnapshotsAndThenSendMessages()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.startClusterBackupNode(true);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        final long logPosition = leader.service().cluster().logPosition();\n\n        cluster.awaitBackupLiveLogPosition(logPosition);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n\n        assertEquals(messageCount, node.service().messageCount());\n        assertFalse(node.service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldBackupClusterWithSnapshot()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).useResponseChannels(true).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        final long logPosition = leader.service().cluster().logPosition();\n\n        cluster.startClusterBackupNode(true);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n        cluster.awaitBackupSnapshotRetrievedCount(1);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n\n        assertEquals(messageCount, node.service().messageCount());\n        assertTrue(node.service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBackupClusterAfterCleanShutdown()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.node(0).isTerminationExpected(true);\n        cluster.node(1).isTerminationExpected(true);\n        cluster.node(2).isTerminationExpected(true);\n\n        cluster.shutdownCluster(leader);\n        cluster.awaitNodeTerminations();\n\n        assertTrue(cluster.node(0).service().wasSnapshotTaken());\n        assertTrue(cluster.node(1).service().wasSnapshotTaken());\n        assertTrue(cluster.node(2).service().wasSnapshotTaken());\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n        final TestNode newLeader = cluster.awaitLeader();\n        final long logPosition = newLeader.service().cluster().logPosition();\n\n        cluster.startClusterBackupNode(true);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBackupClusterWithSnapshotAndNonEmptyLog()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int preSnapshotMessageCount = 10;\n        final int postSnapshotMessageCount = 7;\n        final int totalMessageCount = preSnapshotMessageCount + postSnapshotMessageCount;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(preSnapshotMessageCount);\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.sendMessages(postSnapshotMessageCount);\n        cluster.awaitResponseMessageCount(totalMessageCount);\n        cluster.awaitServiceMessageCount(leader, totalMessageCount);\n\n        final long logPosition = leader.service().cluster().logPosition();\n\n        cluster.startClusterBackupNode(true);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n        cluster.awaitServiceMessageCount(node, totalMessageCount);\n\n        assertEquals(totalMessageCount, node.service().messageCount());\n        assertTrue(node.service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBackupClusterWithSnapshotAndNonEmptyLogWithSimpleAuthentication()\n    {\n        final AuthenticatorSupplier authenticatorSupplier = SampleAuthenticator::new;\n        final AuthorisationServiceSupplier authorisationServiceSupplier =\n            () -> new SampleAuthorisationService(singletonList(SampleAuthenticator.PRINCIPAL));\n\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withAuthenticationSupplier(authenticatorSupplier)\n            .withAuthorisationServiceSupplier(authorisationServiceSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int preSnapshotMessageCount = 10;\n        final int postSnapshotMessageCount = 7;\n        final int totalMessageCount = preSnapshotMessageCount + postSnapshotMessageCount;\n\n        cluster.connectClient(TestCluster.SIMPLE_CREDENTIALS_SUPPLIER);\n        cluster.sendAndAwaitMessages(preSnapshotMessageCount);\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.sendMessages(postSnapshotMessageCount);\n        cluster.awaitResponseMessageCount(totalMessageCount);\n        cluster.awaitServiceMessageCount(leader, totalMessageCount);\n\n        final long logPosition = leader.service().cluster().logPosition();\n\n        cluster.startClusterBackupNode(true, TestCluster.SIMPLE_CREDENTIALS_SUPPLIER);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n        cluster.awaitServiceMessageCount(node, totalMessageCount);\n\n        assertEquals(totalMessageCount, node.service().messageCount());\n        assertTrue(node.service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBackupClusterWithSnapshotAndNonEmptyLogWithChallengeResponseAuthentication()\n    {\n        final AuthenticatorSupplier authenticatorSupplier = SampleAuthenticator::new;\n\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withAuthenticationSupplier(authenticatorSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int preSnapshotMessageCount = 10;\n        final int postSnapshotMessageCount = 7;\n        final int totalMessageCount = preSnapshotMessageCount + postSnapshotMessageCount;\n\n        cluster.connectClient(TestCluster.CHALLENGE_RESPONSE_CREDENTIALS_SUPPLIER);\n        cluster.sendAndAwaitMessages(preSnapshotMessageCount);\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.sendMessages(postSnapshotMessageCount);\n        cluster.awaitResponseMessageCount(totalMessageCount);\n        cluster.awaitServiceMessageCount(leader, totalMessageCount);\n\n        final long logPosition = leader.service().cluster().logPosition();\n\n        cluster.startClusterBackupNode(true, TestCluster.CHALLENGE_RESPONSE_CREDENTIALS_SUPPLIER);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n        cluster.awaitServiceMessageCount(node, totalMessageCount);\n\n        assertEquals(totalMessageCount, node.service().messageCount());\n        assertTrue(node.service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldLogErrorWithInvalidCredentials()\n    {\n        final AuthenticatorSupplier authenticatorSupplier = SampleAuthenticator::new;\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withAuthenticationSupplier(authenticatorSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching(s -> s.contains(\"AUTHENTICATION_REJECTED\"));\n\n        cluster.awaitLeader();\n        final TestBackupNode testBackupNode = cluster.startClusterBackupNode(true, INVALID_SIMPLE_CREDENTIALS_SUPPLIER);\n\n        cluster.awaitBackupState(ClusterBackup.State.RESET_BACKUP);\n\n        awaitErrorLogged(testBackupNode, \"AUTHENTICATION_REJECTED\");\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldLogErrorWithInvalidChallengeResponse()\n    {\n        final AuthenticatorSupplier authenticatorSupplier = SampleAuthenticator::new;\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withAuthenticationSupplier(authenticatorSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching(s -> s.contains(\"AUTHENTICATION_REJECTED\"));\n\n        cluster.awaitLeader();\n        final TestBackupNode testBackupNode = cluster.startClusterBackupNode(\n            true, INVALID_CHALLENGE_RESPONSE_CREDENTIALS_SUPPLIER);\n\n        cluster.awaitBackupState(ClusterBackup.State.RESET_BACKUP);\n\n        awaitErrorLogged(testBackupNode, \"AUTHENTICATION_REJECTED\");\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBackupClusterWithSnapshotThenSend()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int preSnapshotMessageCount = 10;\n        final int postSnapshotMessageCount = 7;\n        final int totalMessageCount = preSnapshotMessageCount + postSnapshotMessageCount;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(preSnapshotMessageCount);\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.startClusterBackupNode(true);\n\n        cluster.sendMessages(postSnapshotMessageCount);\n        cluster.awaitResponseMessageCount(totalMessageCount);\n        cluster.awaitServiceMessageCount(leader, totalMessageCount);\n\n        final long logPosition = leader.service().cluster().logPosition();\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n        cluster.awaitServiceMessageCount(node, totalMessageCount);\n\n        assertEquals(totalMessageCount, node.service().messageCount());\n        assertTrue(node.service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBackupClusterWithFromLatestSnapshotLogPosition()\n    {\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .replayStart(LATEST_SNAPSHOT)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int preSnapshotMessageCount = 10;\n        final int postSnapshotMessageCount = 7;\n        final int totalMessageCount = preSnapshotMessageCount + postSnapshotMessageCount;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(preSnapshotMessageCount);\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n        final long snapshotPosition = leader.service().cluster().logPosition();\n\n        cluster.sendMessages(postSnapshotMessageCount);\n        cluster.awaitResponseMessageCount(totalMessageCount);\n        cluster.awaitServiceMessageCount(leader, totalMessageCount);\n        final long logPosition = leader.service().cluster().logPosition();\n\n        final TestBackupNode testBackupNode = cluster.startClusterBackupNode(true);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n\n        assertEquals(snapshotPosition, testBackupNode.recordingLogStartPosition());\n\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n        cluster.awaitServiceMessageCount(node, totalMessageCount);\n\n        assertEquals(totalMessageCount, node.service().messageCount());\n        assertTrue(node.service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBeAbleToGetTimeOfNextBackupQuery()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        final TestBackupNode backupNode = cluster.startClusterBackupNode(true);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n\n        final long nowMs = backupNode.epochClock().time();\n        assertThat(backupNode.nextBackupQueryDeadlineMs(), greaterThan(nowMs));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBackupClusterNoSnapshotsAndNonEmptyLogWithReQuery()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        final long logPosition = leader.service().cluster().logPosition();\n        final TestBackupNode backupNode = cluster.startClusterBackupNode(true);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n\n        assertTrue(backupNode.nextBackupQueryDeadlineMs(0));\n\n        cluster.sendMessages(5);\n        cluster.awaitResponseMessageCount(messageCount + 5);\n        cluster.awaitServiceMessageCount(leader, messageCount + 5);\n\n        final long nextLogPosition = leader.service().cluster().logPosition();\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(nextLogPosition);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n\n        assertEquals(messageCount + 5, node.service().messageCount());\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldBackupClusterNoSnapshotsAndNonEmptyLogAfterFailure()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leaderOne = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.stopNode(leaderOne);\n\n        final TestNode leaderTwo = cluster.awaitLeader();\n        final long logPosition = leaderTwo.service().cluster().logPosition();\n\n        cluster.startClusterBackupNode(true);\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n\n        assertEquals(messageCount, node.service().messageCount());\n        assertFalse(node.service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(60)\n    void shouldBackupClusterNoSnapshotsAndNonEmptyLogWithFailure()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leaderOne = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        final AeronCluster aeronCluster = cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        final long logPosition = leaderOne.service().cluster().logPosition();\n\n        cluster.startClusterBackupNode(true);\n\n        aeronCluster.sendKeepAlive();\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        aeronCluster.sendKeepAlive();\n        cluster.awaitBackupLiveLogPosition(logPosition);\n        cluster.stopNode(leaderOne);\n\n        final TestNode leaderTwo = cluster.awaitLeader();\n        cluster.awaitNewLeadershipEvent(1);\n\n        cluster.sendMessages(5);\n        cluster.awaitResponseMessageCount(messageCount + 5);\n\n        final long nextLogPosition = leaderTwo.service().cluster().logPosition();\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(nextLogPosition);\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n\n        assertEquals(messageCount + 5, node.service().messageCount());\n        assertFalse(node.service().wasSnapshotLoaded());\n    }\n\n    @InterruptAfter(30)\n    @ParameterizedTest()\n    @EnumSource(value = ClusterBackup.SourceType.class)\n    void shouldBackupClusterWithInvalidNameResolution(final ClusterBackup.SourceType sourceType)\n    {\n        final int backupNodeId = 3;\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withMemberSpecificInvalidNameResolution(backupNodeId)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching(UNKNOWN_HOST_FILTER);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        final long logPosition = leader.service().cluster().logPosition();\n\n        cluster.startClusterBackupNode(true, sourceType);\n\n        cluster.awaitBackupNodeErrors();\n\n        cluster.restoreByMemberNameResolution(backupNodeId);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(logPosition);\n    }\n\n    @Test\n    @InterruptAfter(60)\n    void shouldBackupClusterAndJoinLive()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n        final TestNode leader = cluster.awaitLeader();\n\n        cluster.connectClient();\n        cluster.sendMessages(100);\n\n        cluster.startClusterBackupNode(true);\n\n        cluster.sendMessages(100);\n        cluster.awaitServiceMessageCount(leader, 200);\n\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(leader.service().cluster().logPosition());\n        cluster.stopAllNodes();\n\n        final TestNode node = cluster.startStaticNodeFromBackup();\n        cluster.awaitLeader();\n        cluster.awaitServiceMessageCount(node, 200);\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 0, 8833 })\n    @InterruptAfter(60)\n    void shouldResumeBackupIfStopped(final int catchupPort)\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n        cluster.awaitLeader();\n\n        cluster.connectClient();\n        final int initialMessageCount = 100_000; // minimum number of messages to trigger the bug\n        cluster.sendAndAwaitMessages(initialMessageCount);\n\n        cluster.client().close();\n\n        final TestBackupNode backupNode = cluster.startClusterBackupNode(\n            true, new NullCredentialsSupplier(), ClusterBackup.SourceType.FOLLOWER, catchupPort);\n        cluster.awaitBackupLiveLogPosition(1);\n        backupNode.close();\n\n        final int delta = 1000;\n        cluster.reconnectClient();\n        cluster.sendAndAwaitMessages(delta, initialMessageCount + delta);\n\n        cluster.startClusterBackupNode(\n            false, new NullCredentialsSupplier(), ClusterBackup.SourceType.FOLLOWER, catchupPort);\n        cluster.awaitBackupState(ClusterBackup.State.BACKING_UP);\n        cluster.awaitBackupLiveLogPosition(cluster.findLeader().service().cluster().logPosition());\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldQueryForSnapshotsWithLogPosition()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n        final TestNode leader = cluster.awaitLeader();\n\n        cluster.connectClient();\n        final int messageCount = 20; // minimum number of messages to trigger the bug\n\n        cluster.sendAndAwaitMessages(messageCount);\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.sendAndAwaitMessages(messageCount, 2 * messageCount);\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(2);\n\n        cluster.sendAndAwaitMessages(messageCount, 3 * messageCount);\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(3);\n\n        cluster.sendAndAwaitMessages(messageCount, 4 * messageCount);\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(4);\n\n        final List<SnapshotRecord> snapshots = cluster.snapshots(leader);\n\n        cluster.backupQueryContainsSnapshot(leader, 0, snapshots.get(0));\n\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(0).logPosition() - 1, snapshots.get(0));\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(0).logPosition(), snapshots.get(0));\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(0).logPosition() + 1, snapshots.get(0));\n\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(1).logPosition() - 1, snapshots.get(0));\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(1).logPosition(), snapshots.get(1));\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(1).logPosition() + 1, snapshots.get(1));\n\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(2).logPosition() - 1, snapshots.get(1));\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(2).logPosition(), snapshots.get(2));\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(2).logPosition() + 1, snapshots.get(2));\n\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(3).logPosition() - 1, snapshots.get(2));\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(3).logPosition(), snapshots.get(3));\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(3).logPosition() + 1, snapshots.get(3));\n\n        cluster.backupQueryContainsSnapshot(leader, snapshots.get(3).logPosition() + 1_000_000, snapshots.get(3));\n        cluster.backupQueryContainsSnapshot(leader, Long.MAX_VALUE, snapshots.get(3));\n    }\n\n    private static void awaitErrorLogged(final TestBackupNode testBackupNode, final String expectedErrorMessage)\n    {\n        final AtomicBuffer atomicBuffer = testBackupNode.clusterBackupErrorLog();\n        final MutableBoolean foundError = new MutableBoolean();\n\n        Tests.await(() ->\n        {\n            ErrorLogReader.read(\n                atomicBuffer,\n                (observationCount, firstObservationTimestamp, lastObservationTimestamp, encodedException) ->\n                {\n                    if (encodedException.contains(expectedErrorMessage))\n                    {\n                        foundError.set(true);\n                    }\n                });\n            return foundError.get();\n        });\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/ClusterInstrumentor.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport net.bytebuddy.ByteBuddy;\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.agent.builder.ResettableClassFileTransformer;\nimport net.bytebuddy.asm.Advice;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.dynamic.scaffold.TypeValidation;\nimport net.bytebuddy.matcher.ElementMatchers;\nimport net.bytebuddy.utility.JavaModule;\n\nimport java.lang.instrument.Instrumentation;\n\nclass ClusterInstrumentor\n{\n    private final ResettableClassFileTransformer resettableClassFileTransformer;\n    private final Instrumentation instrumentation;\n\n    ClusterInstrumentor(\n        final Class<?> adviceClass,\n        final String simpleClassName,\n        final String methodName)\n    {\n        instrumentation = ByteBuddyAgent.install();\n\n        final AgentBuilder agentBuilder = new AgentBuilder.Default(new ByteBuddy()\n            .with(TypeValidation.DISABLED))\n            .disableClassFormatChanges()\n            .with(new AgentBuilderListener())\n            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);\n\n        resettableClassFileTransformer = agentBuilder\n            .type(ElementMatchers.nameEndsWith(simpleClassName))\n            .transform(\n                (builder, typeDescription, classLoader, module, protectionDomain) ->\n                    builder.visit(Advice.to(adviceClass).on(ElementMatchers.named(methodName))))\n            .installOn(instrumentation);\n    }\n\n    void reset()\n    {\n        resettableClassFileTransformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);\n    }\n\n    static class AgentBuilderListener implements AgentBuilder.Listener\n    {\n        public void onDiscovery(\n            final String typeName,\n            final ClassLoader classLoader,\n            final JavaModule javaModule,\n            final boolean loaded)\n        {\n        }\n\n        public void onTransformation(\n            final TypeDescription typeDescription,\n            final ClassLoader classLoader,\n            final JavaModule javaModule,\n            final boolean loaded,\n            final DynamicType dynamicType)\n        {\n        }\n\n        public void onIgnored(\n            final TypeDescription typeDescription,\n            final ClassLoader classLoader,\n            final JavaModule javaModule,\n            final boolean loaded)\n        {\n        }\n\n        public void onError(\n            final String typeName,\n            final ClassLoader classLoader,\n            final JavaModule javaModule,\n            final boolean loaded,\n            final Throwable throwable)\n        {\n            System.err.println(\"typeName=\" + typeName +\n                \", classLoader=\" + classLoader +\n                \", javaModule=\" + javaModule +\n                \", loaded=\" + loaded);\n            throwable.printStackTrace(System.err);\n        }\n\n        public void onComplete(\n            final String typeName,\n            final ClassLoader classLoader,\n            final JavaModule javaModule,\n            final boolean loaded)\n        {\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/ClusterNetworkPartitionTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.IpTables;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.TopologyTest;\nimport io.aeron.test.cluster.ClusterTests;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\n\nimport static io.aeron.cluster.client.AeronCluster.SESSION_HEADER_LENGTH;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static io.aeron.test.cluster.TestCluster.awaitElectionClosed;\nimport static io.aeron.test.cluster.TestCluster.awaitElectionState;\nimport static io.aeron.test.cluster.TestCluster.awaitLeaderLogRecording;\nimport static org.agrona.BitUtil.align;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\n\n@TopologyTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\n@EnabledOnOs(OS.LINUX)\nclass ClusterNetworkPartitionTest\n{\n    private static final List<String> HOSTNAMES =\n        List.of(\"127.1.0.0\", \"127.1.1.0\", \"127.1.2.0\", \"127.1.3.0\", \"127.1.4.0\");\n    private static final int CLUSTER_SIZE = HOSTNAMES.size();\n    private static final String CHAIN_NAME = \"CLUSTER-TEST\";\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n    private TestCluster cluster;\n\n    @BeforeEach\n    void setUp()\n    {\n        IpTables.setupChain(CHAIN_NAME);\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        IpTables.tearDownChain(CHAIN_NAME);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldStartClusterThenElectNewLeaderAfterPartition()\n    {\n        cluster = aCluster()\n            .withStaticNodes(CLUSTER_SIZE)\n            .withCustomAddresses(HOSTNAMES)\n            .withClusterId(7)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode firstLeader = cluster.awaitLeader();\n\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(100);\n\n        final long initialLeaderLogPosition = firstLeader.appendPosition();\n\n        IpTables.makeSymmetricNetworkPartition(\n            CHAIN_NAME,\n            List.of(HOSTNAMES.get(firstLeader.memberId())),\n            IntStream.range(0, CLUSTER_SIZE)\n                .filter((i) -> i != firstLeader.memberId())\n                .mapToObj(HOSTNAMES::get)\n                .toList());\n\n        cluster.sendMessages(50); // will be sent to the old leader\n        Tests.await(() -> firstLeader.appendPosition() > initialLeaderLogPosition);\n\n        final TestNode interimLeader = cluster.awaitLeaderWithoutElectionTerminationCheck(firstLeader.memberId());\n        assertNotEquals(firstLeader.memberId(), interimLeader.memberId());\n\n        cluster.awaitNodeState(firstLeader, (n) -> n.electionState() == ElectionState.CANVASS);\n\n        IpTables.flushChain(CHAIN_NAME);\n\n        final TestNode finalLeader = cluster.awaitLeader(); // ensure no more elections\n        assertNotEquals(firstLeader.memberId(), finalLeader.memberId());\n\n        cluster.reconnectClient();\n        cluster.sendAndAwaitMessages(100, 200);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldRestartClusterWithMajorityOfNodesBeingBehind()\n    {\n        cluster = aCluster()\n            .withStaticNodes(CLUSTER_SIZE)\n            .withCustomAddresses(HOSTNAMES)\n            .withClusterId(7)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode firstLeader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final ClusterMember[] clusterMembers =\n            ClusterMember.parse(firstLeader.consensusModule().context().clusterMembers());\n\n        cluster.connectClient();\n        final int initialMessageCount = 100;\n        cluster.sendAndAwaitMessages(initialMessageCount);\n\n        cluster.takeSnapshot(firstLeader);\n        cluster.awaitSnapshotCount(1);\n\n        final int messagesAfterSnapshot = 50;\n        final int committedMessageCount = initialMessageCount + messagesAfterSnapshot;\n        cluster.sendAndAwaitMessages(messagesAfterSnapshot, committedMessageCount);\n\n        final long commitPositionBeforePartition = firstLeader.commitPosition();\n\n        blockTrafficToSpecificEndpoint(\n            List.of(clusterMembers[firstLeader.memberId()]),\n            followers.stream()\n                .map((node) -> clusterMembers[node.memberId()])\n                .toList(),\n            ClusterMember::logEndpoint);\n\n        final int messagesReceivedByMinority = 300;\n        cluster.sendMessages(messagesReceivedByMinority);\n\n        awaitLeaderLogRecording(cluster, firstLeader, committedMessageCount + messagesReceivedByMinority);\n\n        verifyClusterState(commitPositionBeforePartition, committedMessageCount);\n\n        cluster.terminationsExpected(true);\n        cluster.stopAllNodes();\n\n        IpTables.flushChain(CHAIN_NAME); // remove network partition\n\n        cluster.restartAllNodes(false);\n        final TestNode newLeader = cluster.awaitLeader();\n        assertEquals(firstLeader.memberId(), newLeader.memberId());\n        cluster.reconnectClient();\n\n        final int newMessages = 200;\n        cluster.sendMessages(newMessages);\n        cluster.awaitResponseMessageCount(newMessages);\n        cluster.awaitServicesMessageCount(committedMessageCount + messagesReceivedByMinority + newMessages);\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 128 * 1024, 256 * 1024 })\n    @InterruptAfter(30)\n    void shouldRecoverClusterWithMajorityOfNodesBeingBehind(final int amountOfLogMajorityShouldBeBehind)\n    {\n        final long leaderHeartbeatTimeoutNs = TimeUnit.SECONDS.toNanos(10);\n        cluster = aCluster()\n            .withStaticNodes(CLUSTER_SIZE)\n            .withCustomAddresses(HOSTNAMES)\n            .withClusterId(7)\n            .withLogChannel(\"aeron:udp?term-length=512k|alias=raft\")\n            .withLeaderHeartbeatTimeoutNs(leaderHeartbeatTimeoutNs)\n            .withStartupCanvassTimeoutNs(leaderHeartbeatTimeoutNs * 2)\n            .withElectionTimeoutNs(leaderHeartbeatTimeoutNs / 2)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode firstLeader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final ClusterMember[] clusterMembers =\n            ClusterMember.parse(firstLeader.consensusModule().context().clusterMembers());\n\n        cluster.connectClient();\n        final int initialMessageCount = 100;\n        cluster.sendAndAwaitMessages(initialMessageCount);\n\n        cluster.takeSnapshot(firstLeader);\n        cluster.awaitSnapshotCount(1);\n\n        final int messagesAfterSnapshot = 50;\n        final int committedMessageCount = initialMessageCount + messagesAfterSnapshot;\n        cluster.sendAndAwaitMessages(messagesAfterSnapshot, committedMessageCount);\n\n        final long commitPositionBeforePartition = firstLeader.commitPosition();\n\n        blockTrafficToSpecificEndpoint(\n            List.of(clusterMembers[firstLeader.memberId()]),\n            followers.stream()\n                .map((node) -> clusterMembers[node.memberId()])\n                .toList(),\n            ClusterMember::logEndpoint);\n\n        final int messagesReceivedByMinority = 1 + amountOfLogMajorityShouldBeBehind / align(\n            HEADER_LENGTH + SESSION_HEADER_LENGTH + ClusterTests.LARGE_MSG.length(), FRAME_ALIGNMENT);\n        cluster.sendLargeMessages(messagesReceivedByMinority);\n\n        awaitLeaderLogRecording(cluster, firstLeader, committedMessageCount + messagesReceivedByMinority);\n\n        verifyClusterState(commitPositionBeforePartition, committedMessageCount);\n\n        IpTables.flushChain(CHAIN_NAME); // remove network partition\n\n        final TestNode newLeader = cluster.awaitLeader();\n        assertEquals(firstLeader.memberId(), newLeader.memberId());\n\n        final int newMessages = 200;\n        cluster.sendMessages(newMessages);\n        cluster.awaitResponseMessageCount(newMessages);\n        cluster.awaitServicesMessageCount(committedMessageCount + messagesReceivedByMinority + newMessages);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldNotAllowLogReplayBeyondCommitPosition()\n    {\n        final long leaderHeartbeatTimeoutNs = TimeUnit.SECONDS.toNanos(10);\n        cluster = aCluster()\n            .withStaticNodes(CLUSTER_SIZE)\n            .withCustomAddresses(HOSTNAMES)\n            .withClusterId(3)\n            .withLogChannel(\"aeron:udp?term-length=512k|alias=raft\")\n            .withLeaderHeartbeatTimeoutNs(leaderHeartbeatTimeoutNs)\n            .withStartupCanvassTimeoutNs(leaderHeartbeatTimeoutNs * 2)\n            .withElectionTimeoutNs(leaderHeartbeatTimeoutNs / 2)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode fastFollower = followers.get(0);\n        final TestNode[] slowFollowers = followers.subList(1, followers.size()).toArray(new TestNode[0]);\n        final ClusterMember[] clusterMembers =\n            ClusterMember.parse(leader.consensusModule().context().clusterMembers());\n\n        cluster.connectClient();\n        final int initialMessageCount = 100;\n        cluster.sendAndAwaitMessages(initialMessageCount);\n\n        final long commitPositionBeforePartition = leader.commitPosition();\n\n        // block log traffic from leader to the slow nodes\n        blockTrafficToSpecificEndpoint(\n            List.of(clusterMembers[leader.memberId()]),\n            Stream.of(slowFollowers)\n                .map((node) -> clusterMembers[node.memberId()])\n                .toList(),\n            ClusterMember::logEndpoint);\n\n        final int messagesReceivedByMinority = 300;\n        cluster.sendMessages(messagesReceivedByMinority); // these messages will be only received by 2 out of 5 nodes\n\n        final int expectedFinalMessageCount = initialMessageCount + messagesReceivedByMinority;\n        final long leaderAppendPosition = awaitLeaderLogRecording(cluster, leader, expectedFinalMessageCount);\n\n        Tests.await(() -> leaderAppendPosition == fastFollower.appendPosition());\n\n        verifyClusterState(commitPositionBeforePartition, initialMessageCount);\n\n        // restart follower to force an election, i.e. to replay its log\n        fastFollower.isTerminationExpected(true);\n        fastFollower.close();\n        final TestNode fastFollowerRestarted = cluster.startStaticNode(fastFollower.memberId(), false);\n        awaitElectionState(fastFollowerRestarted, ElectionState.FOLLOWER_REPLAY);\n        verifyNodeState(fastFollowerRestarted, commitPositionBeforePartition, initialMessageCount);\n\n        IpTables.flushChain(CHAIN_NAME); // remove network partition\n        assertSame(leader, cluster.awaitLeader());\n        awaitElectionClosed(fastFollowerRestarted);\n\n        // Once the network partition is removed the majority of nodes will receive the missing data and the commit\n        // position will advance. This in turn will unblock `FOLLOWER_REPLAY` progress that is bounded by it.\n        verifyClusterState(leaderAppendPosition, expectedFinalMessageCount);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldNotAllowLogReplayBeyondCommitPositionAfterLeadershipTermChange()\n    {\n        cluster = aCluster()\n            .withStaticNodes(CLUSTER_SIZE)\n            .withCustomAddresses(HOSTNAMES)\n            .withClusterId(7)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode fastFollower = followers.get(0);\n        final TestNode[] slowFollowers = followers.subList(1, followers.size()).toArray(new TestNode[0]);\n        final ClusterMember[] clusterMembers =\n            ClusterMember.parse(originalLeader.consensusModule().context().clusterMembers());\n\n        cluster.connectClient();\n        final int initialMessageCount = 100;\n        cluster.sendAndAwaitMessages(initialMessageCount);\n\n        // block log traffic from leader to the slow nodes\n        final List<ClusterMember> leaderMember = List.of(clusterMembers[originalLeader.memberId()]);\n        final List<ClusterMember> majorityMembers = Stream.of(slowFollowers)\n            .map((node) -> clusterMembers[node.memberId()])\n            .toList();\n        blockTrafficToSpecificEndpoint(leaderMember, majorityMembers, ClusterMember::logEndpoint);\n\n        final int messagesReceivedByMinority = 300;\n        cluster.sendMessages(messagesReceivedByMinority); // these messages will be only received by 2 out of 5 nodes\n\n        final long leaderAppendPosition =\n            awaitLeaderLogRecording(cluster, originalLeader, initialMessageCount + messagesReceivedByMinority);\n\n        Tests.await(() -> leaderAppendPosition == fastFollower.appendPosition());\n\n        // stop fast follower\n        fastFollower.isTerminationExpected(true);\n        fastFollower.close();\n\n        // force the majority of nodes to elect a new leader\n        blockTrafficToSpecificEndpoint(leaderMember, majorityMembers, ClusterMember::consensusEndpoint);\n        final TestNode majorityLeader = cluster.awaitLeaderWithoutElectionTerminationCheck(originalLeader.memberId());\n        assertNotEquals(originalLeader.memberId(), majorityLeader.memberId());\n\n        IpTables.flushChain(CHAIN_NAME); // remove network partition\n\n        // wait for old leader to become a follower\n        assertSame(majorityLeader, cluster.awaitLeader());\n        assertEquals(Cluster.Role.FOLLOWER, originalLeader.role());\n\n        // restart sleeping node in new term\n        final TestNode fastFollowerRestarted = cluster.startStaticNode(fastFollower.memberId(), false);\n        awaitElectionClosed(fastFollowerRestarted);\n        assertEquals(Cluster.Role.FOLLOWER, fastFollowerRestarted.role());\n\n        final long commitPositionInNewTerm = majorityLeader.commitPosition();\n        verifyClusterState(commitPositionInNewTerm, initialMessageCount);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldNotStuckInFollowerCatchup()\n    {\n        final long leaderHeartbeatTimeoutNs = TimeUnit.SECONDS.toNanos(10);\n        final List<String> hosts = HOSTNAMES.subList(0, 3);\n        cluster = aCluster()\n            .withStaticNodes(hosts.size())\n            .withCustomAddresses(hosts)\n            .withClusterId(0)\n            .withLeaderHeartbeatTimeoutNs(leaderHeartbeatTimeoutNs)\n            .withStartupCanvassTimeoutNs(leaderHeartbeatTimeoutNs * 2)\n            .withElectionTimeoutNs(leaderHeartbeatTimeoutNs / 2)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode follower1 = followers.get(0);\n        final TestNode follower2 = followers.get(1);\n        final ClusterMember[] clusterMembers =\n            ClusterMember.parse(leader.consensusModule().context().clusterMembers());\n\n        cluster.connectClient();\n        final int initialMessageCount = 100;\n        cluster.sendAndAwaitMessages(initialMessageCount);\n\n        blockTrafficToSpecificEndpoint(\n            List.of(clusterMembers[leader.memberId()]),\n            List.of(clusterMembers[follower2.memberId()]),\n            ClusterMember::logEndpoint);\n\n        // stop a follower for it to fall behind\n        follower1.isTerminationExpected(true);\n        follower1.close();\n\n        final int numMessagesAfterPartition = 111;\n        cluster.sendMessages(numMessagesAfterPartition); // only leader will receive these messages\n\n        final long leaderAppendPosition =\n            awaitLeaderLogRecording(cluster, leader, initialMessageCount + numMessagesAfterPartition);\n\n        final TestNode follower1Restarted = cluster.startStaticNode(follower1.memberId(), false);\n        awaitElectionState(follower1Restarted, ElectionState.FOLLOWER_CATCHUP_AWAIT);\n\n        follower2.isTerminationExpected(true);\n        follower2.close();\n\n        IpTables.flushChain(CHAIN_NAME); // remove network partition\n\n        final TestNode follower2Restarted = cluster.startStaticNode(follower2.memberId(), false);\n        awaitElectionState(follower2Restarted, ElectionState.FOLLOWER_CATCHUP_AWAIT);\n\n        // wait for election to be complete\n        assertSame(leader, cluster.awaitLeader());\n        assertEquals(Cluster.Role.LEADER, leader.role());\n        awaitElectionClosed(follower1Restarted);\n        awaitElectionClosed(follower2Restarted);\n\n        verifyClusterState(leaderAppendPosition, initialMessageCount + numMessagesAfterPartition);\n    }\n\n    private static void blockTrafficToSpecificEndpoint(\n        final List<ClusterMember> from,\n        final List<ClusterMember> to,\n        final Function<ClusterMember, String> endpointFunction)\n    {\n        for (final ClusterMember dest : to)\n        {\n            final String blockedEndpoint = endpointFunction.apply(dest);\n            final String blockedPort = blockedEndpoint.substring(blockedEndpoint.indexOf(':') + 1);\n            for (final ClusterMember src : from)\n            {\n                IpTables.dropUdpTrafficBetweenHosts(\n                    CHAIN_NAME, HOSTNAMES.get(src.id()), \"\", HOSTNAMES.get(dest.id()), blockedPort);\n            }\n        }\n    }\n\n    private void verifyClusterState(\n        final long expectedCommitPosition, final int expectedCommittedMessageCount)\n    {\n        final TestNode leader = cluster.findLeader();\n        verifyNodeState(leader, expectedCommitPosition, expectedCommittedMessageCount);\n        for (final TestNode follower : cluster.followers())\n        {\n            verifyNodeState(follower, expectedCommitPosition, expectedCommittedMessageCount);\n        }\n    }\n\n    private static void verifyNodeState(\n        final TestNode node, final long expectedCommitPosition, final int expectedCommittedMessageCount)\n    {\n        Tests.await(() -> node.service().messageCount() >= expectedCommittedMessageCount);\n\n        final Supplier<String> errMsg = () ->\n            \"memberId=\" + node.memberId() +\n                \" role=\" + node.role() +\n                \" electionState=\" + node.electionState() +\n                \" electionCount=\" + node.electionCount();\n        assertEquals(expectedCommitPosition, node.commitPosition(), errMsg);\n        assertEquals(expectedCommittedMessageCount, node.service().messageCount(), errMsg);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/ClusterNetworkTopologyTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport com.sun.tools.attach.VirtualMachine;\nimport com.sun.tools.attach.VirtualMachineDescriptor;\nimport io.aeron.CommonContext;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.EgressListener;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.samples.cluster.ClusterConfig;\nimport io.aeron.samples.cluster.EchoServiceNode;\nimport io.aeron.samples.cluster.tutorial.BasicAuctionClusterClient;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.TopologyTest;\nimport io.aeron.test.driver.TestMediaDriver;\nimport io.aeron.test.launcher.FileResolveUtil;\nimport io.aeron.test.launcher.RemoteLaunchClient;\nimport org.agrona.IoUtil;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.MutableReference;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.channels.ReadableByteChannel;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.nio.channels.SocketChannel;\nimport java.nio.charset.CharsetDecoder;\nimport java.nio.charset.CoderResult;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@TopologyTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\n@EnabledOnOs(OS.LINUX)\nclass ClusterNetworkTopologyTest\n{\n    private static final int REMOTE_LAUNCH_PORT = 11112;\n    private static final long STARTUP_CANVASS_TIMEOUT_S =\n        NANOSECONDS.toSeconds(2 * ConsensusModule.Configuration.leaderHeartbeatTimeoutNs());\n    private static final List<String> HOSTNAMES = Arrays.asList(\"10.42.0.10\", \"10.42.0.11\", \"10.42.0.12\");\n    private static final List<String> INTERNAL_HOSTNAMES = Arrays.asList(\"10.42.1.10\", \"10.42.1.11\", \"10.42.1.12\");\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private File baseDir;\n\n    @BeforeEach\n    void setUp()\n    {\n        Tests.await(\n            () ->\n            {\n                final List<VirtualMachineDescriptor> list = VirtualMachine.list();\n                final List<VirtualMachineDescriptor> echoServices = list.stream()\n                    .filter((vm) -> EchoServiceNode.class.getName().equals(vm.displayName()))\n                    .collect(Collectors.toList());\n\n                if (!echoServices.isEmpty())\n                {\n                    System.out.println(echoServices);\n                    Tests.sleep(200, () -> \"Failed to shutdown EchoServiceNode\");\n                }\n\n                return echoServices.isEmpty();\n            },\n            SECONDS.toNanos(10));\n\n        baseDir = FileResolveUtil.resolveClusterScriptDir();\n        IoUtil.delete(new File(baseDir, \"node0\"), true);\n        IoUtil.delete(new File(baseDir, \"node1\"), true);\n        IoUtil.delete(new File(baseDir, \"node2\"), true);\n    }\n\n    @Test\n    void shouldGetNetworkInformationFromAgentNodes()\n    {\n        assertTimeoutPreemptively(\n            Duration.ofMillis(10_000),\n            () ->\n            {\n                RemoteLaunchClient.connect(HOSTNAMES.get(0), REMOTE_LAUNCH_PORT)\n                    .executeBlocking(System.out, \"/usr/sbin/ip\", \"a\");\n                RemoteLaunchClient.connect(HOSTNAMES.get(1), REMOTE_LAUNCH_PORT)\n                    .executeBlocking(System.out, \"/usr/sbin/ip\", \"a\");\n                RemoteLaunchClient.connect(HOSTNAMES.get(2), REMOTE_LAUNCH_PORT)\n                    .executeBlocking(System.out, \"/usr/sbin/ip\", \"a\");\n            });\n    }\n\n    private static Stream<Arguments> provideTopologyConfigurations()\n    {\n        return Stream.of(\n            Arguments.of(HOSTNAMES, null, \"aeron:udp\", null),\n            Arguments.of(HOSTNAMES, null, \"aeron:udp?endpoint=239.20.90.11:9152|interface=10.42.0.0/24\", null),\n            Arguments.of(HOSTNAMES, null, \"aeron:udp\", \"aeron:udp?endpoint=239.20.90.13:9152|interface=10.42.0.0/24\"),\n            Arguments.of(HOSTNAMES, null, \"aeron:udp\", \"aeron:udp?endpoint=239.20.90.13:9152|interface=10.42.1.0/24\"),\n            Arguments.of(HOSTNAMES, INTERNAL_HOSTNAMES, \"aeron:udp\", null));\n    }\n\n    private static Stream<Arguments> singleTopologyConfigurations()\n    {\n        return Stream.of(Arguments.of(\n            HOSTNAMES, null, \"aeron:udp?endpoint=239.20.90.11:9152|interface=10.42.0.0/24\", null));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"provideTopologyConfigurations\")\n    @InterruptAfter(60)\n    void shouldGetEchoFromCluster(\n        final List<String> hostnames,\n        final List<String> internalHostnames,\n        final String ingressChannel,\n        final String logChannel) throws Exception\n    {\n        assertNotNull(hostnames);\n        assertEquals(3, hostnames.size());\n        setupDataCollection(3);\n        final String ingressEndpoints = ingressChannel.contains(\"endpoint\") ?\n            null : BasicAuctionClusterClient.ingressEndpoints(hostnames);\n\n        try (\n            RemoteLaunchClient remote0 = RemoteLaunchClient.connect(hostnames.get(0), REMOTE_LAUNCH_PORT);\n            RemoteLaunchClient remote1 = RemoteLaunchClient.connect(hostnames.get(1), REMOTE_LAUNCH_PORT);\n            RemoteLaunchClient remote2 = RemoteLaunchClient.connect(hostnames.get(2), REMOTE_LAUNCH_PORT))\n        {\n            final Selector selector = Selector.open();\n            launchNode(hostnames, internalHostnames, ingressChannel, logChannel, remote0, selector, 0);\n            launchNode(hostnames, internalHostnames, ingressChannel, logChannel, remote1, selector, 1);\n            launchNode(hostnames, internalHostnames, ingressChannel, logChannel, remote2, selector, 2);\n\n            connectAndSendMessages(ingressChannel, ingressEndpoints, selector, 1);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"singleTopologyConfigurations\")\n    @InterruptAfter(60)\n    void shouldLogReplicate(\n        final List<String> hostnames,\n        final List<String> internalHostnames,\n        final String ingressChannel,\n        final String logChannel) throws Exception\n    {\n        assertNotNull(hostnames);\n        assertEquals(3, hostnames.size());\n        setupDataCollection(3);\n\n        final String ingressEndpoints = ingressChannel.contains(\"endpoint\") ?\n            null : BasicAuctionClusterClient.ingressEndpoints(hostnames);\n\n        try (\n            RemoteLaunchClient remote0 = RemoteLaunchClient.connect(hostnames.get(0), REMOTE_LAUNCH_PORT);\n            RemoteLaunchClient remote1 = RemoteLaunchClient.connect(hostnames.get(1), REMOTE_LAUNCH_PORT))\n        {\n            final Selector selector = Selector.open();\n            launchNode(hostnames, internalHostnames, ingressChannel, logChannel, remote0, selector, 0);\n            launchNode(hostnames, internalHostnames, ingressChannel, logChannel, remote1, selector, 1);\n\n            connectAndSendMessages(ingressChannel, ingressEndpoints, selector, 10);\n        }\n\n        Thread.sleep(5_000);\n\n        try (\n            RemoteLaunchClient remote0 = RemoteLaunchClient.connect(hostnames.get(0), REMOTE_LAUNCH_PORT);\n            RemoteLaunchClient remote2 = RemoteLaunchClient.connect(hostnames.get(2), REMOTE_LAUNCH_PORT))\n        {\n            final Selector selector = Selector.open();\n            launchNode(hostnames, internalHostnames, ingressChannel, logChannel, remote0, selector, 0);\n            launchNode(hostnames, internalHostnames, ingressChannel, logChannel, remote2, selector, 2);\n\n            connectAndSendMessages(ingressChannel, ingressEndpoints, selector, 10);\n        }\n    }\n\n    private void setupDataCollection(final int nodeCount)\n    {\n        for (int nodeId = 0; nodeId < nodeCount; nodeId++)\n        {\n            systemTestWatcher.dataCollector().add(\n                new File(CommonContext.getAeronDirectoryName() + \"-\" + nodeId + \"-driver\"));\n            systemTestWatcher.dataCollector().add(new File(clusterNodeDir(nodeId), ClusterConfig.ARCHIVE_SUB_DIR));\n            systemTestWatcher.dataCollector().add(new File(clusterNodeDir(nodeId), ClusterConfig.CLUSTER_SUB_DIR));\n            systemTestWatcher.dataCollector().add(new File(clusterNodeDir(nodeId), \"event.log\"));\n            systemTestWatcher.dataCollector().add(new File(clusterNodeDir(nodeId), \"command.out\"));\n        }\n    }\n\n    private File clusterNodeDir(final int nodeId)\n    {\n        return new File(baseDir, \"aeron-cluster-\" + nodeId);\n    }\n\n    private void launchNode(\n        final List<String> hostnames,\n        final List<String> internalHostnames,\n        final String ingressChannel,\n        final String logChannel,\n        final RemoteLaunchClient remote0,\n        final Selector selector,\n        final int nodeId) throws IOException\n    {\n        final String[] command0 = deriveCommand(nodeId, hostnames, internalHostnames, ingressChannel, logChannel);\n        final SocketChannel execute0 = remote0.execute(false, command0);\n        final Node node = new Node();\n\n        execute0.register(selector, SelectionKey.OP_READ, node);\n        while (node.checkOutput(\"Started Cluster Node\"))\n        {\n            pollSelector(selector);\n        }\n    }\n\n    private void connectAndSendMessages(\n        final String ingressChannel,\n        final String ingressEndpoints,\n        final Selector selector,\n        final int messageCount)\n    {\n        final String message = \"Hello World!\";\n        final MutableDirectBuffer messageBuffer = new UnsafeBuffer(ByteBuffer.allocate(128));\n        final int length = messageBuffer.putStringAscii(0, message);\n        final MutableReference<String> egressResponse = new MutableReference<>();\n\n        final EgressListener egressListener =\n            (clusterSessionId, timestamp, buffer, offset, length1, header) ->\n            {\n                final String stringAscii = buffer.getStringAscii(offset);\n                egressResponse.set(stringAscii);\n            };\n\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(new MediaDriver.Context()\n                .threadingMode(ThreadingMode.SHARED)\n                .dirDeleteOnStart(true)\n                .dirDeleteOnShutdown(true), systemTestWatcher);\n            AeronCluster.AsyncConnect asyncConnect = AeronCluster.asyncConnect(new AeronCluster.Context()\n                .messageTimeoutNs(SECONDS.toNanos(STARTUP_CANVASS_TIMEOUT_S * 2))\n                .egressListener(egressListener)\n                .egressChannel(\"aeron:udp?endpoint=10.42.0.1:0\")\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .ingressChannel(ingressChannel)\n                .ingressEndpoints(ingressEndpoints));\n            AeronCluster aeronCluster = pollUntilConnected(asyncConnect, selector))\n        {\n            for (int i = 0; i < messageCount; i++)\n            {\n                Tests.await(\n                    () ->\n                    {\n                        final long position = aeronCluster.offer(messageBuffer, 0, length);\n                        pollSelector(selector);\n                        return 0 < position;\n                    },\n                    SECONDS.toNanos(5));\n\n                Tests.await(\n                    () ->\n                    {\n                        aeronCluster.pollEgress();\n                        pollSelector(selector);\n                        return message.equals(egressResponse.get());\n                    },\n                    SECONDS.toNanos(5));\n            }\n        }\n    }\n\n    private AeronCluster pollUntilConnected(final AeronCluster.AsyncConnect asyncConnect, final Selector selector)\n    {\n        AeronCluster aeronCluster;\n        while (null == (aeronCluster = asyncConnect.poll()))\n        {\n            pollSelector(selector);\n            Tests.sleep(1);\n        }\n\n        return aeronCluster;\n    }\n\n    @Test\n    void shouldMatchPatternSplitAcrossReads()\n    {\n        final Node n = new Node();\n        final String s = \"Some text first: \\u1F600\";\n        final byte[] bytes = s.getBytes(StandardCharsets.UTF_8);\n\n        final ByteBuffer allocate = ByteBuffer.allocate(1024);\n        allocate.put(bytes, 0, 19);\n        n.applyResponseData(allocate);\n        allocate.put(bytes, 19, bytes.length - 19);\n        n.applyResponseData(allocate);\n\n        assertTrue(n.checkOutput(s));\n    }\n\n    @Test\n    void shouldMatchPatternSplitAcrossBufferBoundary()\n    {\n        final Node n = new Node();\n        final String s = \"Some text first: \\u1F600\";\n        final byte[] toMatch = s.getBytes(StandardCharsets.UTF_8);\n        final byte[] bytes = new byte[4096];\n        Arrays.fill(bytes, (byte)'a');\n\n        final ByteBuffer allocate = ByteBuffer.allocate(4096);\n\n        allocate.put(bytes);\n        n.applyResponseData(allocate);\n\n        System.arraycopy(toMatch, 0, bytes, bytes.length - (toMatch.length / 2), toMatch.length / 2);\n        allocate.put(bytes);\n        n.applyResponseData(allocate);\n\n        Arrays.fill(bytes, (byte)'a');\n        System.arraycopy(toMatch, (toMatch.length / 2), bytes, 0, toMatch.length - (toMatch.length / 2));\n        allocate.put(bytes);\n        n.applyResponseData(allocate);\n\n        assertTrue(n.checkOutput(s));\n    }\n\n    static final class Node\n    {\n        private final CharBuffer textOutput = CharBuffer.allocate(8192);\n        private final ByteBuffer binaryOutput = ByteBuffer.allocateDirect(textOutput.capacity() / 2);\n        private final CharBuffer textOutputDup = textOutput.duplicate();\n        private final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();\n\n        void readChannel(final ReadableByteChannel channel) throws IOException\n        {\n            final int read = channel.read(binaryOutput);\n            if (read > 0)\n            {\n                int initialTextPosition = textOutput.position();\n                applyResponseData(binaryOutput);\n                final int resultTextPosition = textOutput.position();\n\n                if (resultTextPosition < initialTextPosition)\n                {\n                    initialTextPosition -= textOutput.capacity() / 2;\n                }\n\n                if (initialTextPosition < resultTextPosition)\n                {\n                    textOutputDup.clear().position(initialTextPosition).limit(resultTextPosition);\n                }\n            }\n        }\n\n        boolean checkOutput(final String regexToMatch)\n        {\n            final Pattern pattern = Pattern.compile(regexToMatch);\n            final CharBuffer duplicate = textOutput.duplicate();\n            duplicate.flip();\n\n            return pattern.matcher(duplicate).find();\n        }\n\n        private void applyResponseData(final ByteBuffer data)\n        {\n            data.flip();\n\n            final CoderResult result = decoder.decode(data, textOutput, false);\n            if (CoderResult.OVERFLOW == result)\n            {\n                textOutput.limit(textOutput.capacity());\n                textOutput.position(textOutput.capacity() / 2);\n                textOutput.compact();\n                decoder.decode(data, textOutput, false);\n            }\n\n            data.compact();\n        }\n    }\n\n    private void pollSelector(final Selector selector)\n    {\n        try\n        {\n            final int select = selector.selectNow();\n            if (select < 1)\n            {\n                return;\n            }\n\n            final Set<SelectionKey> selectionKeys = selector.selectedKeys();\n            for (final SelectionKey selectionKey : selectionKeys)\n            {\n                final Node node = (Node)selectionKey.attachment();\n                final ReadableByteChannel toReadFrom = (ReadableByteChannel)selectionKey.channel();\n\n                node.readChannel(toReadFrom);\n            }\n            selectionKeys.clear();\n        }\n        catch (final IOException ex)\n        {\n            throw new UncheckedIOException(ex);\n        }\n    }\n\n    private String[] deriveCommand(\n        final int nodeId,\n        final List<String> hostnames,\n        final List<String> internalHostnames,\n        final String ingressChannel,\n        final String logChannel)\n    {\n        final ArrayList<String> command = new ArrayList<>();\n        command.add(FileResolveUtil.resolveJavaBinary().getAbsolutePath());\n\n        // Clean up and create the cluster node's base directory\n        final File clusterDir = clusterNodeDir(nodeId);\n        IoUtil.delete(clusterDir, false);\n        IoUtil.ensureDirectoryExists(clusterDir, \"cluster base directory\");\n\n        command.add(\"--add-opens\");\n        command.add(\"java.base/jdk.internal.misc=ALL-UNNAMED\");\n        command.add(\"--add-opens\");\n        command.add(\"java.base/java.util.zip=ALL-UNNAMED\");\n        command.add(\"-Xmx32m\");\n        command.add(\"-cp\");\n        command.add(FileResolveUtil.resolveAeronAllJar().getAbsolutePath());\n        command.add(\"-javaagent:\" + FileResolveUtil.resolveAeronAgentJar().getAbsolutePath());\n        command.add(\"-Djava.net.preferIPv4Stack=true\");\n        command.add(\"-Daeron.dir.delete.on.start=true\");\n        command.add(\"-Daeron.event.cluster.log=all\");\n        command.add(\"-Daeron.event.cluster.log.disable=CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION\");\n        command.add(\"-Daeron.event.log.filename=\" + new File(clusterDir, \"event.log\").getAbsolutePath());\n        command.add(\"-Daeron.driver.resolver.name=node\" + nodeId);\n        command.add(\"-Daeron.cluster.startup.canvass.timeout=\" + STARTUP_CANVASS_TIMEOUT_S + \"s\");\n\n        if (null != ingressChannel)\n        {\n            command.add(\"-Daeron.cluster.ingress.channel=\" + ingressChannel);\n        }\n\n        if (null != logChannel)\n        {\n            command.add(\"-Daeron.cluster.log.channel=\" + logChannel);\n        }\n\n        command.add(\"-Daeron.cluster.tutorial.hostnames=\" + String.join(\",\", hostnames));\n        if (null != internalHostnames && internalHostnames.size() == hostnames.size())\n        {\n            command.add(\"-Daeron.cluster.tutorial.hostnames.internal=\" + String.join(\",\", hostnames));\n        }\n\n        command.add(\"-Daeron.cluster.tutorial.nodeId=\" + nodeId);\n        command.add(\"-Daeron.cluster.tutorial.baseDir=\" + clusterDir);\n        command.add(EchoServiceNode.class.getName());\n\n        return command.toArray(new String[0]);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/ClusterSessionReliabilityTest.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.ChannelUri;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.EgressListener;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.driver.ReceiveChannelEndpointSupplier;\nimport io.aeron.driver.SendChannelEndpointSupplier;\nimport io.aeron.driver.ext.DebugReceiveChannelEndpoint;\nimport io.aeron.driver.ext.DebugSendChannelEndpoint;\nimport io.aeron.driver.ext.LossGenerator;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport io.aeron.test.driver.PortLossGenerator;\nimport io.aeron.test.driver.StreamIdLossGenerator;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.DirectBuffer;\nimport org.agrona.MutableDirectBuffer;\nimport org.agrona.collections.Long2LongHashMap;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.function.IntFunction;\n\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ClusterSessionReliabilityTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(10)\n    void sessionShouldGetClosedWhenIngressImageGoesUnavailableToPreventSilentMessageLoss()\n    {\n        sessionShouldGetClosedWhenIngressImageGoesUnavailableToPreventSilentMessageLoss(false, false);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void sessionShouldGetClosedWhenMulticastIngressImageGoesUnavailableToPreventSilentMessageLoss()\n    {\n        sessionShouldGetClosedWhenIngressImageGoesUnavailableToPreventSilentMessageLoss(true, false);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void sessionShouldGetClosedWhenIngressImageGoesUnavailableAfterFailoverToPreventSilentMessageLoss()\n    {\n        sessionShouldGetClosedWhenIngressImageGoesUnavailableToPreventSilentMessageLoss(false, true);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void sessionShouldGetClosedWhenMulticastIngressImageGoesUnavailableAfterFailoverToPreventSilentMessageLoss()\n    {\n        sessionShouldGetClosedWhenIngressImageGoesUnavailableToPreventSilentMessageLoss(true, true);\n    }\n\n    private void sessionShouldGetClosedWhenIngressImageGoesUnavailableToPreventSilentMessageLoss(\n        final boolean multicastIngress,\n        final boolean withFailover)\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"uses custom channel endpoint suppliers for simulating loss\");\n\n        final AtomicLong outOfSequenceCount = new AtomicLong();\n        final IntFunction<TestNode.TestService[]> serviceSupplier =\n            (index) -> new TestNode.TestService[]\n            {\n                new SequenceCheckingService(index, outOfSequenceCount)\n            };\n        String ingressChannel = \"aeron:udp?term-length=128k|alias=ingress\";\n        if (multicastIngress)\n        {\n            ingressChannel += \"|endpoint=239.192.11.87:20123|interface=127.0.0.1\";\n        }\n        final long imageLivenessTimeoutNs = TimeUnit.MILLISECONDS.toNanos(1000);\n        final long sessionTimeoutNs = TimeUnit.MILLISECONDS.toNanos(4000);\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withServiceSupplier(serviceSupplier)\n            .withIngressChannel(ingressChannel)\n            .withImageLivenessTimeoutNs(imageLivenessTimeoutNs)\n            .withSessionTimeoutNs(sessionTimeoutNs)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        TestNode leader = cluster.awaitLeader();\n\n        final PortLossGenerator clientSendLossGenerator = new PortLossGenerator();\n        cluster.clientSendChannelEndpointSupplier(sendChannelEndpointSupplier(clientSendLossGenerator));\n        final AeronCluster client = cluster.connectClient();\n\n        if (withFailover)\n        {\n            leader.gracefulClose();\n\n            cluster.awaitNewLeadershipEvent(1);\n\n            final int previousLeaderIndex = leader.index();\n            leader = cluster.awaitLeader();\n            assertNotEquals(previousLeaderIndex, leader.index());\n\n            while (!client.sendKeepAlive())\n            {\n                Tests.yieldingIdle(\"failed to send keep-alive\");\n            }\n        }\n\n        final SequenceCheckingService leaderService = (SequenceCheckingService)leader.service();\n\n        cluster.shouldErrorOnClientClose(false);\n\n        final MutableDirectBuffer buffer = new UnsafeBuffer(new byte[SIZE_OF_LONG]);\n        long sequence = 0;\n        boolean lossRequested = false;\n        long now = System.nanoTime();\n        long nextMessageAt = now;\n        final long deadline = now + 2 * sessionTimeoutNs;\n        while (true)\n        {\n            now = System.nanoTime();\n\n            if (now - deadline >= 0)\n            {\n                break;\n            }\n\n            if (now - nextMessageAt >= 0)\n            {\n                buffer.putLong(0, sequence);\n                if (client.offer(buffer, 0, buffer.capacity()) > 0)\n                {\n                    sequence++;\n                    nextMessageAt += TimeUnit.MILLISECONDS.toNanos(10);\n                }\n            }\n\n            client.pollEgress();\n\n            if (client.isClosed())\n            {\n                break;\n            }\n\n            if (!lossRequested && leaderService.messageCount() >= 5)\n            {\n                clientSendLossGenerator.startDropping(\n                    endpointPort(client.ingressPublication().channel()),\n                    imageLivenessTimeoutNs + TimeUnit.MILLISECONDS.toNanos(200));\n                lossRequested = true;\n            }\n\n            Tests.sleep(1);\n        }\n\n        assertTrue(lossRequested);\n        assertEquals(0, outOfSequenceCount.get());\n        assertTrue(client.isClosed());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void clientShouldNotRejoinEgressImageFromTheSameNodeToPreventSilentMessageLoss()\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"uses custom channel endpoint suppliers for simulating loss\");\n\n        final IntFunction<TestNode.TestService[]> serviceSupplier =\n            (index) -> new TestNode.TestService[]\n            {\n                new SequencedEgressService(index)\n            };\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(1)\n            .withServiceSupplier(serviceSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final SequenceCheckingEgressListener egressListener = new SequenceCheckingEgressListener();\n        cluster.egressListener(egressListener);\n        final StreamIdLossGenerator clientReceiveLossGenerator = new StreamIdLossGenerator();\n        cluster.clientReceiveChannelEndpointSupplier(receiveChannelEndpointSupplier(clientReceiveLossGenerator));\n        final long imageLivenessTimeoutNs = TimeUnit.MILLISECONDS.toNanos(1000);\n        cluster.clientImageLivenessTimeoutNs(imageLivenessTimeoutNs);\n        final AeronCluster client = cluster.connectClient();\n\n        boolean lossRequested = false;\n        long now = System.nanoTime();\n        final long deadline = now + imageLivenessTimeoutNs + TimeUnit.SECONDS.toNanos(2);\n        while (true)\n        {\n            now = System.nanoTime();\n\n            if (now - deadline >= 0)\n            {\n                break;\n            }\n\n            client.pollEgress();\n\n            if (!lossRequested && egressListener.nextSequence >= 5)\n            {\n                lossRequested = true;\n                clientReceiveLossGenerator.enable(client.context().egressStreamId());\n            }\n\n            Tests.sleep(1);\n        }\n\n        clientReceiveLossGenerator.disable();\n        assertTrue(lossRequested);\n        assertEquals(0, egressListener.outOfSequenceCount);\n    }\n\n    private int endpointPort(final String channel)\n    {\n        final ChannelUri uri = ChannelUri.parse(channel);\n        final String endpoint = uri.get(ENDPOINT_PARAM_NAME);\n        return Integer.parseInt(endpoint.substring(endpoint.indexOf(':') + 1));\n    }\n\n    private static SendChannelEndpointSupplier sendChannelEndpointSupplier(final LossGenerator lossGenerator)\n    {\n        return (udpChannel, statusIndicator, context) ->\n            new DebugSendChannelEndpoint(udpChannel, statusIndicator, context, lossGenerator, lossGenerator);\n    }\n\n    private static ReceiveChannelEndpointSupplier receiveChannelEndpointSupplier(final LossGenerator lossGenerator)\n    {\n        return (udpChannel, dispatcher, statusIndicator, context) ->\n            new DebugReceiveChannelEndpoint(\n                udpChannel, dispatcher, statusIndicator, context, lossGenerator, lossGenerator);\n    }\n\n    private static class SequenceCheckingService extends TestNode.TestService\n    {\n        private final Long2LongHashMap nextSequenceBySessionId = new Long2LongHashMap(-1);\n        private final AtomicLong outOfSequenceCount;\n\n        SequenceCheckingService(final int index, final AtomicLong outOfSequenceCount)\n        {\n            this.outOfSequenceCount = outOfSequenceCount;\n            index(index);\n        }\n\n        public void onSessionMessage(\n            final ClientSession session,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            final long sessionId = session.id();\n            final long sequence = buffer.getLong(offset);\n            final long expectedSequence = nextSequenceBySessionId.get(sessionId);\n            if (sequence != expectedSequence)\n            {\n                System.out.println(\"expected sequence \" + expectedSequence + \", but got \" + sequence);\n                outOfSequenceCount.incrementAndGet();\n            }\n            nextSequenceBySessionId.put(sessionId, sequence + 1);\n            messageCount.incrementAndGet();\n        }\n\n        public void onSessionOpen(final ClientSession session, final long timestamp)\n        {\n            super.onSessionOpen(session, timestamp);\n            nextSequenceBySessionId.put(session.id(), 0);\n        }\n\n        public void onSessionClose(final ClientSession session, final long timestamp, final CloseReason closeReason)\n        {\n            super.onSessionClose(session, timestamp, closeReason);\n            nextSequenceBySessionId.remove(session.id());\n        }\n    }\n\n    private static class SequencedEgressService extends TestNode.TestService\n    {\n        private static final long TIMER_CORRELATION_ID = 1;\n\n        private final MutableDirectBuffer buffer = new UnsafeBuffer(new byte[SIZE_OF_LONG]);\n        private final Long2LongHashMap nextSequenceBySessionId = new Long2LongHashMap(-1);\n\n        SequencedEgressService(final int index)\n        {\n            index(index);\n        }\n\n        public void onSessionOpen(final ClientSession session, final long timestamp)\n        {\n            super.onSessionOpen(session, timestamp);\n            nextSequenceBySessionId.put(session.id(), 0);\n            if (nextSequenceBySessionId.size() == 1)\n            {\n                scheduleTimer();\n            }\n        }\n\n        public void onSessionClose(final ClientSession session, final long timestamp, final CloseReason closeReason)\n        {\n            super.onSessionClose(session, timestamp, closeReason);\n            nextSequenceBySessionId.remove(session.id());\n            if (nextSequenceBySessionId.isEmpty())\n            {\n                cancelTimer();\n            }\n        }\n\n        private void scheduleTimer()\n        {\n            final long deadline = cluster.time() + cluster.timeUnit().convert(10, TimeUnit.MILLISECONDS);\n\n            cluster.idleStrategy().reset();\n            while (!cluster.scheduleTimer(TIMER_CORRELATION_ID, deadline))\n            {\n                cluster.idleStrategy().idle();\n            }\n        }\n\n        private void cancelTimer()\n        {\n            cluster.idleStrategy().reset();\n            while (!cluster.cancelTimer(TIMER_CORRELATION_ID))\n            {\n                cluster.idleStrategy().idle();\n            }\n        }\n\n        public void onTimerEvent(final long correlationId, final long timestamp)\n        {\n            super.onTimerEvent(correlationId, timestamp);\n            scheduleTimer();\n            cluster.forEachClientSession(this::offerToIngress);\n        }\n\n        private void offerToIngress(final ClientSession clientSession)\n        {\n            final long sessionId = clientSession.id();\n            final long sequence = nextSequenceBySessionId.get(sessionId);\n            buffer.putLong(0, sequence);\n            if (clientSession.offer(buffer, 0, buffer.capacity()) > 0)\n            {\n                nextSequenceBySessionId.put(sessionId, sequence + 1);\n            }\n        }\n    }\n\n    private static final class SequenceCheckingEgressListener implements EgressListener\n    {\n        private long outOfSequenceCount;\n        private long nextSequence;\n\n        public void onMessage(\n            final long clusterSessionId,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            final long sequence = buffer.getLong(offset);\n            if (sequence != nextSequence)\n            {\n                outOfSequenceCount++;\n                System.out.println(\"expected sequence \" + nextSequence + \", but got \" + sequence);\n            }\n            nextSequence = sequence + 1;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/ClusterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.ChannelUri;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.ArchiveException;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.AeronClusterVersion;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.client.ControlledEgressListener;\nimport io.aeron.cluster.client.EgressListener;\nimport io.aeron.cluster.codecs.AdminRequestEncoder;\nimport io.aeron.cluster.codecs.AdminRequestType;\nimport io.aeron.cluster.codecs.AdminResponseCode;\nimport io.aeron.cluster.codecs.AdminResponseEncoder;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderDecoder;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusterCounters;\nimport io.aeron.cluster.service.ClusterTerminationException;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.cluster.service.SnapshotDurationTracker;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.driver.ext.DebugReceiveChannelEndpoint;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.security.AuthenticationException;\nimport io.aeron.security.Authenticator;\nimport io.aeron.security.AuthorisationService;\nimport io.aeron.security.SessionProxy;\nimport io.aeron.status.HeartbeatTimestamp;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.ClusterTests;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport io.aeron.test.driver.StreamIdLossGenerator;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.AsciiEncoding;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.Hashing;\nimport org.agrona.collections.IntArrayList;\nimport org.agrona.collections.IntHashSet;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.concurrent.AgentRunner;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.errors.ErrorConsumer;\nimport org.agrona.concurrent.errors.ErrorLogReader;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.hamcrest.CoreMatchers;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.IntFunction;\nimport java.util.function.Predicate;\nimport java.util.stream.IntStream;\nimport java.util.zip.CRC32;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.AeronCounters.CLUSTER_CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID;\nimport static io.aeron.AeronCounters.CLUSTER_SNAPSHOT_COUNTER_TYPE_ID;\nimport static io.aeron.CommonContext.ENDPOINT_PARAM_NAME;\nimport static io.aeron.CommonContext.REJOIN_PARAM_NAME;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.client.AeronCluster.SESSION_HEADER_LENGTH;\nimport static io.aeron.cluster.service.Cluster.Role.FOLLOWER;\nimport static io.aeron.cluster.service.Cluster.Role.LEADER;\nimport static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT;\nimport static io.aeron.logbuffer.FrameDescriptor.UNFRAGMENTED;\nimport static io.aeron.logbuffer.FrameDescriptor.computeMaxMessageLength;\nimport static io.aeron.protocol.DataHeaderFlyweight.BEGIN_AND_END_FLAGS;\nimport static io.aeron.protocol.DataHeaderFlyweight.CURRENT_VERSION;\nimport static io.aeron.protocol.DataHeaderFlyweight.DEFAULT_RESERVE_VALUE;\nimport static io.aeron.protocol.DataHeaderFlyweight.HDR_TYPE_DATA;\nimport static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;\nimport static io.aeron.status.HeartbeatTimestamp.HEARTBEAT_TYPE_ID;\nimport static io.aeron.test.SystemTestWatcher.UNKNOWN_HOST_FILTER;\nimport static io.aeron.test.Tests.awaitAvailableWindow;\nimport static io.aeron.test.cluster.ClusterTests.LARGE_MSG;\nimport static io.aeron.test.cluster.ClusterTests.NO_OP_MSG;\nimport static io.aeron.test.cluster.ClusterTests.REGISTER_TIMER_MSG;\nimport static io.aeron.test.cluster.ClusterTests.startPublisherThread;\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static io.aeron.test.cluster.TestCluster.awaitElectionClosed;\nimport static io.aeron.test.cluster.TestCluster.awaitElectionState;\nimport static io.aeron.test.cluster.TestCluster.awaitLeaderLogRecording;\nimport static io.aeron.test.cluster.TestCluster.ingressEndpoint;\nimport static io.aeron.test.cluster.TestNode.atMost;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrowsExactly;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mock;\n\n@SlowTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ClusterTest\n{\n    private static final String EMPTY_MSG = \"\";\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestCluster cluster = null;\n\n    @Test\n    @InterruptAfter(30)\n    void shouldStopFollowerAndRestartFollower()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        assertEquals(1, leader.consensusModule().context().electionCounter().get());\n        TestNode follower = cluster.followers().get(0);\n        assertEquals(1, follower.consensusModule().context().electionCounter().get());\n\n        awaitElectionClosed(follower);\n        cluster.stopNode(follower);\n\n        follower = cluster.startStaticNode(follower.index(), false);\n\n        awaitElectionClosed(follower);\n        assertEquals(FOLLOWER, follower.role());\n        assertEquals(1 /* new counter */, follower.consensusModule().context().electionCounter().get());\n        assertEquals(1, leader.consensusModule().context().electionCounter().get());\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldNotifyClientOfNewLeader()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        assertEquals(1, leader.consensusModule().context().electionCounter().get());\n        final long leadershipTermId = leader.consensusModule().context().leadershipTermIdCounter().get();\n        assertNotEquals(-1, leadershipTermId);\n        final List<TestNode> followers = cluster.followers();\n        for (final TestNode follower : followers)\n        {\n            assertEquals(1, follower.consensusModule().context().electionCounter().get());\n            assertEquals(leadershipTermId, follower.consensusModule().context().leadershipTermIdCounter().get());\n        }\n\n        cluster.connectClient();\n        cluster.awaitActiveSessionCount(1);\n\n        cluster.stopNode(leader);\n        cluster.awaitNewLeadershipEvent(1);\n        final TestNode leader2 = cluster.awaitLeader();\n        final long leadershipTermId2 = leader2.consensusModule().context().leadershipTermIdCounter().get();\n        for (final TestNode follower : followers)\n        {\n            assertEquals(2, follower.consensusModule().context().electionCounter().get());\n            assertEquals(leadershipTermId2, follower.consensusModule().context().leadershipTermIdCounter().get());\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldStopLeaderAndFollowersThenRestartAllWithSnapshot()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n        assertEquals(2, cluster.followers().size());\n\n        cluster.awaitSnapshotsLoaded();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldNotSnapshotOnPrimaryClusterWhenStandbySnapshotIsRequested()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        cluster.takeStandbySnapshot(leader);\n        cluster.awaitNeutralControlToggle(leader);\n\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(1);\n        assertEquals(0, cluster.getSnapshotCount(leader));\n    }\n\n    @Test\n    @InterruptAfter(5)\n    void shouldStartClusterWithExtension()\n    {\n        cluster = aCluster().withStaticNodes(3)\n            .withExtensionSuppler(TestNode.TestConsensusModuleExtension::new)\n            .withServiceSupplier(value -> new TestNode.TestService[0])\n            .start();\n\n        systemTestWatcher.cluster(cluster);\n        cluster.awaitLeader();\n\n        cluster.node(0).validateOnElectionState(0);\n        cluster.node(1).validateOnElectionState(0);\n        cluster.node(2).validateOnElectionState(0);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldStopClusteredServicesOnAppropriateMessage()\n    {\n        systemTestWatcher.ignoreErrorsMatching((error) -> error.contains(\"publication is not connected\"));\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n\n        cluster.terminationsExpected(true);\n        cluster.connectClient();\n        cluster.sendTerminateMessage();\n        cluster.awaitNodeTerminations();\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldShutdownClusterAndRestartWithSnapshots()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        cluster.node(0).isTerminationExpected(true);\n        cluster.node(1).isTerminationExpected(true);\n        cluster.node(2).isTerminationExpected(true);\n\n        cluster.shutdownCluster(leader);\n        cluster.awaitNodeTerminations();\n\n        assertTrue(cluster.node(0).service().wasSnapshotTaken());\n        assertTrue(cluster.node(1).service().wasSnapshotTaken());\n        assertTrue(cluster.node(2).service().wasSnapshotTaken());\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n        final TestNode leader2 = cluster.awaitLeader();\n        final long leadershipTermId = leader2.consensusModule().context().leadershipTermIdCounter().get();\n        assertEquals(2, cluster.followers().size());\n        for (final TestNode follower : cluster.followers())\n        {\n            assertEquals(leadershipTermId, follower.consensusModule().context().leadershipTermIdCounter().get());\n        }\n\n        cluster.awaitSnapshotsLoaded();\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldAbortClusterAndRestart()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        awaitElectionClosed(cluster.node(0));\n        awaitElectionClosed(cluster.node(1));\n        awaitElectionClosed(cluster.node(2));\n\n        cluster.node(0).isTerminationExpected(true);\n        cluster.node(1).isTerminationExpected(true);\n        cluster.node(2).isTerminationExpected(true);\n\n        cluster.abortCluster(leader);\n        cluster.awaitNodeTerminations();\n\n        assertFalse(cluster.node(0).service().wasSnapshotTaken());\n        assertFalse(cluster.node(1).service().wasSnapshotTaken());\n        assertFalse(cluster.node(2).service().wasSnapshotTaken());\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n        assertEquals(2, cluster.followers().size());\n\n        assertFalse(cluster.node(0).service().wasSnapshotLoaded());\n        assertFalse(cluster.node(1).service().wasSnapshotLoaded());\n        assertFalse(cluster.node(2).service().wasSnapshotLoaded());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldAbortClusterOnTerminationTimeout()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n\n        assertEquals(2, followers.size());\n        final TestNode followerA = followers.get(0);\n        final TestNode followerB = followers.get(1);\n\n        leader.isTerminationExpected(true);\n        followerA.isTerminationExpected(true);\n\n        cluster.stopNode(followerB);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        cluster.abortCluster(leader);\n        cluster.awaitNodeTermination(leader);\n        cluster.awaitNodeTermination(followerA);\n\n        cluster.stopNode(leader);\n        cluster.stopNode(followerA);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldEchoMessages()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        assertNotNull(cluster.asyncConnectClient());\n\n        cluster.sendAndAwaitMessages(10);\n        cluster.awaitServiceMessagePredicate(cluster.awaitLeader(), atMost(10));\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldHandleLeaderFailOverWhenNameIsNotResolvable()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster).ignoreErrorsMatching(UNKNOWN_HOST_FILTER);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.disableNameResolution(originalLeader.hostname());\n        cluster.stopNode(originalLeader);\n\n        cluster.awaitNewLeadershipEvent(1);\n        cluster.sendAndAwaitMessages(messageCount, 2 * messageCount);\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldHandleClusterStartWhenANameIsNotResolvable()\n    {\n        final int initiallyUnresolvableNodeId = 1;\n\n        cluster = aCluster().withStaticNodes(3).withInvalidNameResolution(initiallyUnresolvableNodeId).start();\n        systemTestWatcher.cluster(cluster).ignoreErrorsMatching(UNKNOWN_HOST_FILTER);\n\n        cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.restoreNameResolution(initiallyUnresolvableNodeId);\n        assertNotNull(cluster.startStaticNode(initiallyUnresolvableNodeId, true));\n\n        cluster.awaitServiceMessageCount(cluster.node(initiallyUnresolvableNodeId), messageCount);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldElectSameLeaderAfterLoosingQuorum()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        TestNode followerOne = cluster.followers().get(0);\n        final TestNode followerTwo = cluster.followers().get(1);\n\n        awaitElectionClosed(followerOne);\n        awaitElectionClosed(followerTwo);\n        cluster.stopNode(followerOne);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.stopNode(followerTwo);\n        cluster.awaitLossOfLeadership(leader.service());\n\n        followerOne = cluster.startStaticNode(followerOne.index(), false);\n        cluster.client().sendKeepAlive();\n        awaitElectionClosed(followerOne);\n\n        final TestNode newLeader = cluster.awaitLeader();\n        cluster.awaitNewLeadershipEvent(1);\n\n        assertEquals(FOLLOWER, followerOne.role());\n        assertEquals(leader.index(), newLeader.index());\n\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 2);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldElectNewLeaderAfterGracefulLeaderClose()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n\n        final int messageCount = 10;\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        leader.gracefulClose();\n\n        final TestNode newLeader = cluster.awaitLeader();\n        cluster.awaitNewLeadershipEvent(1);\n        assertNotEquals(newLeader.index(), leader.index());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldHandleClusterStartWhereMostNamesBecomeResolvableDuringElection()\n    {\n        cluster = aCluster().withStaticNodes(3).withInvalidNameResolution(0).withInvalidNameResolution(2).start();\n        systemTestWatcher.cluster(cluster).ignoreErrorsMatching(UNKNOWN_HOST_FILTER);\n\n        awaitElectionState(cluster.node(1), ElectionState.CANVASS);\n\n        cluster.restoreNameResolution(0);\n        cluster.restoreNameResolution(2);\n        assertNotNull(cluster.startStaticNode(0, true));\n        assertNotNull(cluster.startStaticNode(2, true));\n\n        cluster.awaitLeader();\n        cluster.connectClient();\n\n        cluster.sendAndAwaitMessages(10);\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldEchoMessagesThenContinueOnNewLeader()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n\n        final int preFailureMessageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(preFailureMessageCount);\n\n        assertEquals(originalLeader.index(), cluster.client().leaderMemberId());\n\n        cluster.stopNode(originalLeader);\n\n        final TestNode newLeader = cluster.awaitLeader(originalLeader.index());\n        cluster.awaitNewLeadershipEvent(1);\n        assertEquals(newLeader.index(), cluster.client().leaderMemberId());\n\n        final int postFailureMessageCount = 7;\n        cluster.sendMessages(postFailureMessageCount);\n        cluster.awaitResponseMessageCount(preFailureMessageCount + postFailureMessageCount);\n\n        final TestNode follower = cluster.followers().get(0);\n\n        cluster.awaitServiceMessageCount(newLeader, preFailureMessageCount + postFailureMessageCount);\n        cluster.awaitServiceMessageCount(follower, preFailureMessageCount + postFailureMessageCount);\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldStopLeaderAndRestartAsFollower()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n\n        cluster.stopNode(originalLeader);\n        cluster.awaitLeader(originalLeader.index());\n\n        final TestNode follower = cluster.startStaticNode(originalLeader.index(), false);\n\n        awaitElectionClosed(follower);\n        assertEquals(FOLLOWER, follower.role());\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldStopLeaderAndRestartAsFollowerWithSendingAfter()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n\n        cluster.stopNode(originalLeader);\n        cluster.awaitLeader(originalLeader.index());\n\n        final TestNode follower = cluster.startStaticNode(originalLeader.index(), false);\n\n        awaitElectionClosed(follower);\n        assertEquals(FOLLOWER, follower.role());\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n    }\n\n    @Test\n    @InterruptAfter(60)\n    void shouldStopLeaderAndRestartAsFollowerWithSendingAfterThenStopLeader()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n\n        cluster.stopNode(originalLeader);\n        cluster.awaitLeader(originalLeader.index());\n\n        final TestNode follower = cluster.startStaticNode(originalLeader.index(), false);\n        awaitElectionClosed(follower);\n\n        assertEquals(FOLLOWER, follower.role());\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.stopNode(leader);\n\n        cluster.awaitLeader(leader.index());\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldAcceptMessagesAfterSingleNodeCleanRestart()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        TestNode follower = cluster.followers().get(0);\n\n        awaitElectionClosed(follower);\n        cluster.stopNode(follower);\n\n        follower = cluster.startStaticNode(follower.index(), true);\n\n        awaitElectionClosed(cluster.node(follower.index()));\n        assertEquals(FOLLOWER, follower.role());\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n        cluster.awaitServiceMessageCount(follower, messageCount);\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldReplaySnapshotTakenWhileDown()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode followerA = followers.get(0);\n        TestNode followerB = followers.get(1);\n\n        awaitElectionClosed(followerB);\n        cluster.stopNode(followerB);\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(leader, 1);\n        cluster.awaitSnapshotCount(followerA, 1);\n\n        final int messageCount = 10;\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        followerB = cluster.startStaticNode(followerB.index(), false);\n\n        cluster.awaitSnapshotCount(followerB, 1);\n        assertEquals(FOLLOWER, followerB.role());\n\n        cluster.awaitServiceMessageCount(followerB, messageCount);\n    }\n\n    @Test\n    @InterruptAfter(50)\n    void shouldTolerateMultipleLeaderFailures()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode firstLeader = cluster.awaitLeader();\n        cluster.stopNode(firstLeader);\n\n        final TestNode secondLeader = cluster.awaitLeader();\n\n        final long commitPosition = secondLeader.commitPosition();\n        final TestNode newFollower = cluster.startStaticNode(firstLeader.index(), false);\n\n        cluster.awaitCommitPosition(newFollower, commitPosition);\n        awaitElectionClosed(newFollower);\n\n        cluster.stopNode(secondLeader);\n        cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n    }\n\n    @Test\n    @InterruptAfter(90)\n    void shouldRecoverAfterTwoLeaderNodesFailAndComeBackUpAtSameTime()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode firstLeader = cluster.awaitLeader();\n\n        final int sufficientMessageCountForReplay = 10_000;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(sufficientMessageCountForReplay);\n        cluster.closeClient();\n\n        cluster.awaitActiveSessionCount(0);\n        cluster.stopNode(firstLeader);\n\n        final TestNode secondLeader = cluster.awaitLeader();\n        cluster.stopNode(secondLeader);\n\n        cluster.startStaticNode(firstLeader.index(), false);\n        cluster.startStaticNode(secondLeader.index(), false);\n        cluster.awaitLeader();\n\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(10, sufficientMessageCountForReplay + 10);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldAcceptMessagesAfterTwoNodeCleanRestart()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        TestNode followerA = followers.get(0), followerB = followers.get(1);\n\n        awaitElectionClosed(followerA);\n        awaitElectionClosed(followerB);\n\n        cluster.stopNode(followerA);\n        cluster.stopNode(followerB);\n\n        followerA = cluster.startStaticNode(followerA.index(), true);\n        followerB = cluster.startStaticNode(followerB.index(), true);\n\n        awaitElectionClosed(followerA);\n        awaitElectionClosed(followerB);\n\n        assertEquals(FOLLOWER, followerA.role());\n        assertEquals(FOLLOWER, followerB.role());\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n        cluster.awaitServiceMessageCount(followerA, messageCount);\n        cluster.awaitServiceMessageCount(followerB, messageCount);\n    }\n\n    @Test\n    @InterruptAfter(60)\n    void shouldRecoverWithUncommittedMessagesAfterRestartWhenNewCommitPosExceedsPreviousAppendedPos()\n    {\n        cluster = aCluster().withStaticNodes(5).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        TestNode followerA = followers.get(0);\n        TestNode followerB = followers.get(1);\n        TestNode followerC = followers.get(2);\n        TestNode followerD = followers.get(3);\n\n        cluster.connectClient();\n\n        cluster.stopNode(followerA);\n        cluster.stopNode(followerB);\n        cluster.stopNode(followerC);\n\n        cluster.sendUnexpectedMessages(10);\n\n        final long commitPosition = leader.commitPosition();\n        while (leader.appendPosition() <= commitPosition)\n        {\n            Tests.yieldingIdle(\n                \"leader.appendPosition=\" + leader.appendPosition() + \" leader.commitPosition=\" + commitPosition);\n        }\n\n        final long targetPosition = leader.appendPosition();\n\n        cluster.stopNode(followerD);\n        cluster.stopNode(leader);\n        cluster.closeClient();\n\n        followerA = cluster.startStaticNode(followerA.index(), false);\n        followerB = cluster.startStaticNode(followerB.index(), false);\n        followerC = cluster.startStaticNode(followerC.index(), false);\n\n        cluster.awaitLeader();\n\n        awaitElectionClosed(followerA);\n        awaitElectionClosed(followerB);\n        awaitElectionClosed(followerC);\n\n        cluster.connectClient();\n\n        final int messageLength = 128;\n        int messageCount = 0;\n        while (followerA.commitPosition() < targetPosition)\n        {\n            cluster.pollUntilMessageSent(messageLength);\n            messageCount++;\n        }\n\n        cluster.awaitResponseMessageCount(messageCount);\n        cluster.awaitServiceMessageCount(followerA, messageCount);\n        cluster.awaitServiceMessageCount(followerB, messageCount);\n        cluster.awaitServiceMessageCount(followerC, messageCount);\n\n        final TestNode oldLeader = cluster.startStaticNode(leader.index(), false);\n        followerD = cluster.startStaticNode(followerD.index(), false);\n        cluster.awaitServiceMessageCount(oldLeader, messageCount);\n        cluster.awaitServiceMessageCount(followerD, messageCount);\n    }\n\n    @Test\n    @InterruptAfter(50)\n    void shouldRecoverWithUncommittedMessagesAfterRestartWhenNewCommitPosIsLessThanPreviousAppendedPos()\n    {\n        cluster = aCluster().withStaticNodes(5).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode followerA = followers.get(0);\n        final TestNode followerB = followers.get(1);\n        final TestNode followerC = followers.get(2);\n        final TestNode followerD = followers.get(3);\n\n        cluster.connectClient();\n\n        cluster.stopNode(followerA);\n        cluster.stopNode(followerB);\n        cluster.stopNode(followerC);\n\n        final int messageCount = 10;\n        cluster.sendUnexpectedMessages(messageCount);\n\n        final long commitPosition = leader.commitPosition();\n        while (leader.appendPosition() <= commitPosition)\n        {\n            Tests.yield();\n        }\n\n        cluster.stopNode(leader);\n        cluster.stopNode(followerD);\n        cluster.closeClient();\n\n        cluster.startStaticNode(followerA.index(), false);\n        cluster.startStaticNode(followerB.index(), false);\n        cluster.startStaticNode(followerC.index(), false);\n        cluster.awaitLeader();\n\n        final TestNode oldLeader = cluster.startStaticNode(leader.index(), false);\n        cluster.startStaticNode(followerD.index(), false);\n        awaitElectionClosed(oldLeader);\n\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldCallOnRoleChangeOnBecomingLeader()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leaderOne = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode followerA = followers.get(0);\n        final TestNode followerB = followers.get(1);\n\n        awaitElectionClosed(followerA);\n        awaitElectionClosed(followerB);\n\n        assertEquals(LEADER, leaderOne.service().roleChangedTo());\n        assertNull(followerA.service().roleChangedTo());\n        assertNull(followerB.service().roleChangedTo());\n\n        cluster.stopNode(leaderOne);\n\n        final TestNode leaderTwo = cluster.awaitLeader(leaderOne.index());\n        final TestNode follower = cluster.followers().get(0);\n\n        assertEquals(LEADER, leaderTwo.service().roleChangedTo());\n        assertNull(follower.service().roleChangedTo());\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldCallOnRoleChangeOnBecomingLeaderSingleNodeCluster()\n    {\n        cluster = aCluster().withStaticNodes(1).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        assertEquals(LEADER, leader.service().roleChangedTo());\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldLoseLeadershipWhenNoActiveQuorumOfFollowers()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode followerA = followers.get(0);\n        final TestNode followerB = followers.get(1);\n\n        assertEquals(LEADER, leader.role());\n        assertEquals(LEADER, leader.service().roleChangedTo());\n\n        awaitElectionClosed(followerA);\n        awaitElectionClosed(followerB);\n\n        cluster.stopNode(followerA);\n        cluster.stopNode(followerB);\n\n        cluster.awaitLossOfLeadership(leader.service());\n        assertEquals(FOLLOWER, leader.role());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldTerminateLeaderWhenServiceStops()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n\n        leader.isTerminationExpected(true);\n        leader.container().close();\n\n        while (!leader.hasMemberTerminated())\n        {\n            Tests.sleep(1);\n        }\n\n        cluster.awaitNewLeadershipEvent(1);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldEnterElectionWhenRecordingStopsUnexpectedlyOnLeader()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(1);\n\n        final AeronArchive.Context archiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(leader.archive().context().localControlChannel())\n            .controlResponseChannel(leader.archive().context().localControlChannel())\n            .controlRequestStreamId(leader.archive().context().localControlStreamId())\n            .aeronDirectoryName(leader.mediaDriver().aeronDirectoryName());\n\n        try (AeronArchive archive = AeronArchive.connect(archiveCtx))\n        {\n            final int firstRecordingIdIsTheClusterLog = 0;\n            assertTrue(archive.tryStopRecordingByIdentity(firstRecordingIdIsTheClusterLog));\n        }\n\n        cluster.awaitNewLeadershipEvent(1);\n        cluster.awaitLeader();\n        cluster.followers(2);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldEnterElectionWhenRecordingStopsUnexpectedlyOnLeaderOfSingleNodeCluster()\n    {\n        cluster = aCluster().withStaticNodes(1).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(1);\n\n        final AeronArchive.Context archiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(leader.archive().context().localControlChannel())\n            .controlResponseChannel(leader.archive().context().localControlChannel())\n            .controlRequestStreamId(leader.archive().context().localControlStreamId())\n            .aeronDirectoryName(leader.mediaDriver().aeronDirectoryName());\n\n        try (AeronArchive archive = AeronArchive.connect(archiveCtx))\n        {\n            final int firstRecordingIdIsTheClusterLog = 0;\n            assertTrue(archive.tryStopRecordingByIdentity(firstRecordingIdIsTheClusterLog));\n        }\n\n        cluster.awaitNewLeadershipEvent(1);\n        cluster.awaitLeader();\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldEnterElectionWhenLosesQuorumUnexpectedlyOnLeaderOfSingleNodeCluster()\n    {\n        final OffsetMillisecondClusterClock clusterClock = new OffsetMillisecondClusterClock(SystemEpochClock.INSTANCE);\n        cluster = aCluster().withStaticNodes(1).withClusterClock(clusterClock).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final AeronCluster client = cluster.connectClient();\n        cluster.sendAndAwaitMessages(1);\n\n        final long timeoutMs = NANOSECONDS.toMillis(Math.max(\n            leader.consensusModule().context().sessionTimeoutNs(),\n            leader.consensusModule().context().leaderHeartbeatTimeoutNs()));\n        clusterClock.addOffset(timeoutMs + 1);\n\n        cluster.shouldErrorOnClientClose(false);\n        while (!client.isClosed())\n        {\n            Tests.sleep(1);\n            client.pollEgress();\n        }\n\n        cluster.awaitLeader();\n        cluster.reconnectClient();\n        cluster.sendAndAwaitMessages(1, 2);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldCloseClientOnTimeout()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final AeronCluster client = cluster.connectClient();\n        final ConsensusModule.Context context = leader.consensusModule().context();\n        final Counter timedOutClientCounter = context.timedOutClientCounter();\n\n        assertEquals(0, timedOutClientCounter.get());\n        assertFalse(client.isClosed());\n\n        Tests.sleep(NANOSECONDS.toMillis(context.sessionTimeoutNs()));\n\n        cluster.shouldErrorOnClientClose(false);\n        while (!client.isClosed())\n        {\n            Tests.sleep(1);\n            client.pollEgress();\n        }\n\n        assertEquals(1, timedOutClientCounter.get());\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldCloseClientAfterClusterBecomesUnavailable()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n\n        final AeronCluster client = cluster.connectClient(cluster.clientCtx().newLeaderTimeoutNs(SECONDS.toNanos(1)));\n        assertFalse(client.isClosed());\n\n        cluster.shouldErrorOnClientClose(false);\n        cluster.terminationsExpected(true);\n        cluster.stopAllNodes();\n\n        while (!client.isClosed())\n        {\n            Tests.sleep(10);\n            client.sendKeepAlive();\n            client.pollEgress();\n        }\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldRecoverWhileMessagesContinue() throws InterruptedException\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final MutableInteger messageCounter = new MutableInteger();\n        cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode follower = followers.get(1);\n\n        cluster.connectClient();\n\n        final Thread messageThread = startPublisherThread(cluster, messageCounter);\n        final TestNode restartedFollowerB;\n        try\n        {\n            Tests.await(() -> follower.commitPosition() > 0);\n\n            cluster.stopNode(follower);\n            final int delaySoClusterAdvancesMs = 2_000;\n            Tests.sleep(delaySoClusterAdvancesMs);\n\n            restartedFollowerB = cluster.startStaticNode(follower.index(), false);\n            awaitElectionClosed(follower);\n            final int delaySoIngressAdvancesAfterCatchupMs = 2_000;\n            Tests.sleep(delaySoIngressAdvancesAfterCatchupMs);\n        }\n        finally\n        {\n            messageThread.interrupt();\n            messageThread.join();\n        }\n\n        cluster.awaitResponseMessageCount(messageCounter.get());\n        cluster.awaitServiceMessageCount(restartedFollowerB, messageCounter.get());\n\n        cluster.client().close();\n        cluster.awaitActiveSessionCount(0);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldCatchupFromEmptyLog()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        TestNode follower = followers.get(1);\n\n        awaitElectionClosed(follower);\n        cluster.stopNode(follower);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        follower = cluster.startStaticNode(follower.index(), true);\n        cluster.awaitServiceMessageCount(follower, messageCount);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldCatchupFromEmptyLogThenSnapshotAfterShutdownAndFollowerCleanStart()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers(2);\n        final TestNode followerA = followers.get(0);\n        final TestNode followerB = followers.get(1);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        leader.isTerminationExpected(true);\n        followerA.isTerminationExpected(true);\n        followerB.isTerminationExpected(true);\n\n        cluster.shutdownCluster(leader);\n        cluster.awaitNodeTerminations();\n\n        assertTrue(cluster.node(0).service().wasSnapshotTaken());\n        assertTrue(cluster.node(1).service().wasSnapshotTaken());\n        assertTrue(cluster.node(2).service().wasSnapshotTaken());\n\n        cluster.stopAllNodes();\n\n        cluster.startStaticNode(0, false);\n        cluster.startStaticNode(1, false);\n        cluster.startStaticNode(2, true);\n\n        final TestNode newLeader = cluster.awaitLeader();\n        assertNotEquals(2, newLeader.index());\n\n        assertTrue(cluster.node(0).service().wasSnapshotLoaded());\n        assertTrue(cluster.node(1).service().wasSnapshotLoaded());\n        assertFalse(cluster.node(2).service().wasSnapshotLoaded());\n\n        cluster.awaitServiceMessageCount(cluster.node(2), messageCount);\n        cluster.awaitSnapshotCount(cluster.node(2), 1);\n        assertTrue(cluster.node(2).service().wasSnapshotTaken());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldCatchUpTwoFreshNodesAfterRestart()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n\n        final int messageCount = 5_000;\n        cluster.connectClient();\n        final int messageLength = cluster.msgBuffer().putStringWithoutLengthAscii(0, NO_OP_MSG);\n        for (int i = 0; i < messageCount; i++)\n        {\n            cluster.pollUntilMessageSent(messageLength);\n        }\n        cluster.awaitResponseMessageCount(messageCount);\n        cluster.awaitServicesMessageCount(messageCount);\n\n        cluster.terminationsExpected(true);\n        cluster.abortCluster(leader);\n        cluster.awaitNodeTerminations();\n        cluster.stopAllNodes();\n\n        final TestNode oldLeader = cluster.startStaticNode(leader.index(), false);\n        cluster.startStaticNode(followers.get(0).index(), true);\n        cluster.startStaticNode(followers.get(1).index(), true);\n\n        final TestNode newLeader = cluster.awaitLeader();\n        assertEquals(newLeader.index(), oldLeader.index());\n\n        cluster.followers(2);\n        cluster.awaitServicesMessageCount(messageCount);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldReplayMultipleSnapshotsWithEmptyFollowerLog()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        int messageCount = 2;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.takeSnapshot(leader);\n        final int memberCount = 3;\n        for (int memberId = 0; memberId < memberCount; memberId++)\n        {\n            final TestNode node = cluster.node(memberId);\n            cluster.awaitSnapshotCount(node, 1);\n            assertTrue(node.service().wasSnapshotTaken());\n            node.service().resetSnapshotTaken();\n        }\n\n        cluster.sendMessages(1);\n        messageCount++;\n        cluster.awaitResponseMessageCount(messageCount);\n        cluster.awaitServicesMessageCount(messageCount);\n\n        cluster.terminationsExpected(true);\n\n        cluster.awaitNeutralControlToggle(leader);\n        cluster.shutdownCluster(leader);\n        cluster.awaitNodeTerminations();\n\n        assertTrue(cluster.node(0).service().wasSnapshotTaken());\n        assertTrue(cluster.node(1).service().wasSnapshotTaken());\n        assertTrue(cluster.node(2).service().wasSnapshotTaken());\n\n        cluster.stopAllNodes();\n\n        cluster.startStaticNode(0, false);\n        cluster.startStaticNode(1, false);\n        cluster.startStaticNode(2, true);\n\n        final TestNode newLeader = cluster.awaitLeader();\n        assertNotEquals(2, newLeader.index());\n\n        assertTrue(cluster.node(0).service().wasSnapshotLoaded());\n        assertTrue(cluster.node(1).service().wasSnapshotLoaded());\n        assertFalse(cluster.node(2).service().wasSnapshotLoaded());\n\n        assertEquals(messageCount, cluster.node(0).service().messageCount());\n        assertEquals(messageCount, cluster.node(1).service().messageCount());\n\n        Tests.await(() -> cluster.node(2).service().messageCount() >= 3);\n        assertEquals(messageCount, cluster.node(2).service().messageCount());\n\n        final int messageCountAfterStart = 4;\n        messageCount += messageCountAfterStart;\n        cluster.reconnectClient();\n        cluster.sendAndAwaitMessages(messageCountAfterStart, messageCount);\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldRecoverQuicklyAfterKillingFollowersThenRestartingOne()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode followerOne = followers.get(0);\n        final TestNode followerTwo = followers.get(1);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        cluster.stopNode(followerOne);\n        cluster.stopNode(followerTwo);\n\n        while (leader.role() == LEADER)\n        {\n            cluster.sendMessages(1);\n            Tests.sleep(500);\n        }\n\n        cluster.startStaticNode(followerTwo.index(), true);\n        cluster.awaitLeader();\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldRecoverWhenLeaderHasAppendedMoreThanFollower()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode followerOne = followers.get(0);\n        final TestNode followerTwo = followers.get(1);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        cluster.stopNode(followerOne);\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount * 2);\n\n        cluster.stopNode(followerTwo);\n        cluster.stopNode(leader);\n\n        cluster.startStaticNode(leader.index(), false);\n        cluster.startStaticNode(followerOne.index(), false);\n        cluster.awaitLeader();\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(90)\n    @ValueSource(booleans = { true, false })\n    void shouldRecoverWhenFollowerIsMultipleTermsBehind(final boolean useResponseChannels)\n    {\n        cluster = aCluster().withStaticNodes(3).useResponseChannels(useResponseChannels).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        cluster.stopNode(originalLeader);\n        final TestNode newLeader = cluster.awaitLeader();\n        assertNotNull(cluster.reconnectClient());\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount * 2);\n\n        cluster.stopNode(newLeader);\n        cluster.startStaticNode(newLeader.index(), false);\n        cluster.awaitLeader();\n        cluster.reconnectClient();\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount * 3);\n\n        cluster.startStaticNode(originalLeader.index(), false);\n        final TestNode lateJoiningNode = cluster.node(originalLeader.index());\n\n        while (lateJoiningNode.service().messageCount() < messageCount * 3)\n        {\n            Tests.yieldingIdle(\"Waiting for late joining follower to catch up\");\n        }\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(60)\n    @ValueSource(booleans = { true, false })\n    void shouldRecoverWhenFollowerIsMultipleTermsBehindFromEmptyLog(final boolean useResponseChannels)\n    {\n        cluster = aCluster().withStaticNodes(4).useResponseChannels(useResponseChannels).start();\n\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n\n        final int messageCount = 100;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        cluster.stopNode(originalLeader);\n        final TestNode newLeader = cluster.awaitLeader();\n        cluster.reconnectClient();\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount * 2);\n\n        cluster.stopNode(newLeader);\n        cluster.startStaticNode(newLeader.index(), true);\n        cluster.awaitLeader();\n        cluster.reconnectClient();\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount * 3);\n\n        final TestNode lateJoiningNode = cluster.startStaticNode(originalLeader.index(), true);\n        awaitElectionClosed(lateJoiningNode);\n\n        cluster.awaitServiceMessageCount(lateJoiningNode, messageCount * 3);\n    }\n\n    @Test\n    @InterruptAfter(90)\n    void shouldRecoverWhenFollowerWithInitialSnapshotAndArchivePurgeThenIsMultipleTermsBehind()\n    {\n        cluster = aCluster()\n            .withLogChannel(\"aeron:udp?term-length=256k|alias=raft\")\n            .withStaticNodes(3)\n            .start();\n\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n\n        final int initialMessageCount = 300;\n        final int additionalMessageCount = 100;\n\n        cluster.connectClient();\n        cluster.sendLargeMessages(initialMessageCount);\n        cluster.awaitResponseMessageCount(initialMessageCount);\n        cluster.awaitServicesMessageCount(initialMessageCount);\n\n        cluster.takeSnapshot(originalLeader);\n        cluster.awaitSnapshotCount(1);\n        cluster.purgeLogToLastSnapshot();\n\n        cluster.stopNode(originalLeader);\n        cluster.awaitLeader();\n\n        cluster.reconnectClient();\n        cluster.sendLargeMessages(additionalMessageCount);\n        cluster.awaitResponseMessageCount(initialMessageCount + additionalMessageCount);\n\n        cluster.startStaticNode(originalLeader.index(), false);\n        final TestNode lateJoiningNode = cluster.node(originalLeader.index());\n\n        cluster.awaitServiceMessageCount(lateJoiningNode, initialMessageCount + additionalMessageCount);\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldRecoverWhenFollowerArrivesPartWayThroughTerm()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        final TestNode followerOne = cluster.followers().get(0);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        cluster.stopNode(followerOne);\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount * 2);\n\n        cluster.startStaticNode(followerOne.index(), false);\n\n        Tests.await(() -> cluster.node(followerOne.index()).service().messageCount() >= messageCount * 2);\n        assertEquals(messageCount * 2, cluster.node(followerOne.index()).service().messageCount());\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldRecoverWhenFollowerArrivePartWayThroughTermAfterMissingElection()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        final TestNode followerOne = followers.get(0);\n        final TestNode followerTwo = followers.get(1);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        cluster.stopNode(followerOne);\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount * 2);\n\n        cluster.stopNode(followerTwo);\n        cluster.stopNode(leader);\n\n        cluster.startStaticNode(leader.index(), false);\n        cluster.startStaticNode(followerTwo.index(), false);\n        cluster.awaitLeader();\n        cluster.reconnectClient();\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount * 3);\n\n        cluster.startStaticNode(followerOne.index(), false);\n\n        Tests.await(() -> cluster.node(followerOne.index()).service().messageCount() >= messageCount * 3);\n        assertEquals(messageCount * 3, cluster.node(followerOne.index()).service().messageCount());\n    }\n\n    @Test\n    @InterruptAfter(40)\n    void shouldRecoverWhenLastSnapshotIsMarkedInvalid()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader0 = cluster.awaitLeader();\n\n        final int messageCount = 3;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitServicesMessageCount(messageCount);\n\n        cluster.takeSnapshot(leader0);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitServicesMessageCount(messageCount * 2);\n\n        cluster.takeSnapshot(leader0);\n        cluster.awaitSnapshotCount(2);\n\n        cluster.stopNode(leader0);\n        cluster.awaitLeader(leader0.index());\n        cluster.awaitNewLeadershipEvent(1);\n        awaitAvailableWindow(cluster.client().ingressPublication());\n        assertTrue(cluster.client().sendKeepAlive());\n        cluster.startStaticNode(leader0.index(), false);\n\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 3);\n\n        cluster.terminationsExpected(true);\n        cluster.stopAllNodes();\n\n        cluster.invalidateLatestSnapshot();\n\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n        cluster.awaitServicesMessageCount(messageCount * 3);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldRecoverWhenLastSnapshotForShutdownIsMarkedInvalid()\n    {\n        cluster = aCluster().withStaticNodes(1).start();\n        systemTestWatcher.cluster(cluster);\n\n        TestNode leader = cluster.awaitLeader();\n\n        final int messageCount = 3;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitServicesMessageCount(messageCount);\n\n        cluster.stopNode(leader);\n        cluster.startStaticNode(leader.index(), false);\n        leader = cluster.awaitLeader();\n\n        cluster.terminationsExpected(true);\n        cluster.shutdownCluster(leader);\n        cluster.awaitNodeTerminations();\n        assertTrue(leader.service().wasSnapshotTaken());\n        cluster.stopNode(leader);\n\n        cluster.invalidateLatestSnapshot();\n\n        cluster.restartAllNodes(false);\n        leader = cluster.awaitLeader();\n        cluster.awaitServicesMessageCount(messageCount);\n        assertTrue(leader.service().wasSnapshotTaken());\n    }\n\n    @Test\n    @InterruptAfter(60)\n    void shouldHandleMultipleElections()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader0 = cluster.awaitLeader();\n\n        final int messageCount = 3;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.stopNode(leader0);\n        final TestNode leader1 = cluster.awaitLeader(leader0.index());\n        cluster.awaitNewLeadershipEvent(1);\n        awaitAvailableWindow(cluster.client().ingressPublication());\n        assertTrue(cluster.client().sendKeepAlive());\n        cluster.startStaticNode(leader0.index(), false);\n        awaitElectionClosed(cluster.node(leader0.index()));\n\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 2);\n\n        cluster.stopNode(leader1);\n        cluster.awaitLeader(leader1.index());\n        cluster.awaitNewLeadershipEvent(2);\n        awaitAvailableWindow(cluster.client().ingressPublication());\n        assertTrue(cluster.client().sendKeepAlive());\n        cluster.startStaticNode(leader1.index(), false);\n        awaitElectionClosed(cluster.node(leader1.index()));\n\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 3);\n    }\n\n    @Test\n    @InterruptAfter(50)\n    void shouldRecoverWhenLastSnapshotIsInvalidBetweenTwoElections()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader0 = cluster.awaitLeader();\n\n        final int messageCount = 3;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.stopNode(leader0);\n        final TestNode leader1 = cluster.awaitLeader(leader0.index());\n        cluster.awaitNewLeadershipEvent(1);\n        awaitAvailableWindow(cluster.client().ingressPublication());\n        assertTrue(cluster.client().sendKeepAlive());\n        cluster.startStaticNode(leader0.index(), false);\n\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 2);\n\n        cluster.takeSnapshot(leader1);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.stopNode(leader1);\n        cluster.awaitLeader(leader1.index());\n        cluster.awaitNewLeadershipEvent(2);\n        awaitAvailableWindow(cluster.client().ingressPublication());\n        assertTrue(cluster.client().sendKeepAlive());\n        cluster.startStaticNode(leader1.index(), false);\n\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 3);\n\n        // No snapshot for Term 2\n\n        cluster.terminationsExpected(true);\n        cluster.stopAllNodes();\n\n        cluster.invalidateLatestSnapshot();\n\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n        cluster.awaitServicesMessageCount(messageCount * 3);\n    }\n\n    @Test\n    @InterruptAfter(50)\n    void shouldRecoverWhenLastTwosSnapshotsAreInvalidAfterElection()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader0 = cluster.awaitLeader();\n\n        final int messageCount = 3;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.takeSnapshot(leader0);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.stopNode(leader0);\n        final TestNode leader1 = cluster.awaitLeader(leader0.index());\n        cluster.awaitNewLeadershipEvent(1);\n        awaitAvailableWindow(cluster.client().ingressPublication());\n        assertTrue(cluster.client().sendKeepAlive());\n        cluster.startStaticNode(leader0.index(), false);\n\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 2);\n\n        cluster.takeSnapshot(leader1);\n        for (int i = 0; i < 3; i++)\n        {\n            cluster.awaitSnapshotCount(cluster.node(i), leader0.index() == i ? 1 : 2);\n        }\n\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 3);\n\n        cluster.takeSnapshot(leader1);\n        for (int i = 0; i < 3; i++)\n        {\n            cluster.awaitSnapshotCount(cluster.node(i), leader0.index() == i ? 2 : 3);\n        }\n\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 4);\n\n        cluster.terminationsExpected(true);\n        cluster.stopAllNodes();\n\n        cluster.invalidateLatestSnapshot();\n        cluster.invalidateLatestSnapshot();\n\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n\n        cluster.awaitSnapshotCount(2);\n\n        cluster.awaitServicesMessageCount(messageCount * 4);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldCatchUpAfterFollowerMissesOneMessage()\n    {\n        shouldCatchUpAfterFollowerMissesMessage(NO_OP_MSG);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldCatchUpAfterFollowerMissesTimerRegistration()\n    {\n        shouldCatchUpAfterFollowerMissesMessage(REGISTER_TIMER_MSG);\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    @Test\n    @InterruptAfter(30)\n    void shouldAllowChangingTermBufferLengthAndMtuAfterRecordingLogIsTruncatedToTheLatestSnapshot()\n    {\n        final int originalTermLength = 256 * 1024;\n        final int originalMtu = 1408;\n        final int newTermLength = 2 * 1024 * 1024;\n        final int newMtu = 8992;\n        final int staticNodeCount = 3;\n        final CRC32 crc32 = new CRC32();\n\n        cluster = aCluster().withStaticNodes(staticNodeCount)\n            .withLogChannel(\"aeron:udp?term-length=\" + originalTermLength + \"|mtu=\" + originalMtu)\n            .withIngressChannel(\"aeron:udp?term-length=\" + originalTermLength + \"|mtu=\" + originalMtu)\n            .withEgressChannel(\n                \"aeron:udp?endpoint=localhost:0|term-length=\" + originalTermLength + \"|mtu=\" + originalMtu)\n            .withServiceSupplier(\n                (i) -> new TestNode.TestService[]{ new TestNode.TestService(), new TestNode.ChecksumService() })\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        for (int i = 0; i < staticNodeCount; i++)\n        {\n            assertEquals(2, cluster.node(i).services().length);\n        }\n\n        cluster.connectClient();\n        final int firstBatch = 9;\n        int messageLength = computeMaxMessageLength(originalTermLength) - SESSION_HEADER_LENGTH;\n        int payloadLength = messageLength - SIZE_OF_INT;\n        cluster.msgBuffer().setMemory(0, payloadLength, (byte)'x');\n        crc32.reset();\n        crc32.update(cluster.msgBuffer().byteArray(), 0, payloadLength);\n        int msgChecksum = (int)crc32.getValue();\n        cluster.msgBuffer().putInt(payloadLength, msgChecksum, LITTLE_ENDIAN);\n        long checksum = 0;\n        for (int i = 0; i < firstBatch; i++)\n        {\n            cluster.pollUntilMessageSent(messageLength);\n            checksum = Hashing.hash(checksum ^ msgChecksum);\n        }\n        cluster.awaitResponseMessageCount(firstBatch);\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.msgBuffer().setMemory(0, payloadLength, (byte)'y');\n        crc32.reset();\n        crc32.update(cluster.msgBuffer().byteArray(), 0, payloadLength);\n        msgChecksum = (int)crc32.getValue();\n        cluster.msgBuffer().putInt(payloadLength, msgChecksum, LITTLE_ENDIAN);\n        final int secondBatch = 11;\n        cluster.reconnectClient();\n        for (int i = 0; i < secondBatch; i++)\n        {\n            try\n            {\n                cluster.pollUntilMessageSent(messageLength);\n            }\n            catch (final ClusterException ex)\n            {\n                throw new RuntimeException(\"i=\" + i, ex);\n            }\n        }\n        cluster.awaitResponseMessageCount(firstBatch + secondBatch);\n\n        cluster.stopAllNodes();\n\n        cluster.seedRecordingsFromLatestSnapshot();\n\n        cluster.logChannel(\"aeron:udp?term-length=\" + newTermLength + \"|mtu=\" + newMtu);\n        cluster.ingressChannel(\"aeron:udp?term-length=\" + newTermLength + \"|mtu=\" + newMtu);\n        cluster.egressChannel(\"aeron:udp?endpoint=localhost:0|term-length=\" + newTermLength + \"|mtu=\" + newMtu);\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n        assertEquals(2, cluster.followers().size());\n        for (int i = 0; i < staticNodeCount; i++)\n        {\n            assertEquals(2, cluster.node(i).services().length);\n        }\n\n        cluster.awaitSnapshotsLoaded();\n\n        cluster.reconnectClient();\n        messageLength = computeMaxMessageLength(newTermLength) - SESSION_HEADER_LENGTH;\n        payloadLength = messageLength - SIZE_OF_INT;\n        cluster.msgBuffer().setMemory(0, payloadLength, (byte)'z');\n        crc32.reset();\n        crc32.update(cluster.msgBuffer().byteArray(), 0, payloadLength);\n        msgChecksum = (int)crc32.getValue();\n        cluster.msgBuffer().putInt(payloadLength, msgChecksum, LITTLE_ENDIAN);\n        final int thirdBatch = 5;\n        for (int i = 0; i < thirdBatch; i++)\n        {\n            cluster.pollUntilMessageSent(messageLength);\n            checksum = Hashing.hash(checksum ^ msgChecksum);\n        }\n        cluster.awaitResponseMessageCount(firstBatch + secondBatch + thirdBatch);\n\n        final int finalMessageCount = firstBatch + thirdBatch;\n        final long finalChecksum = checksum;\n        final Predicate<TestNode> finalServiceState =\n            (node) ->\n            {\n                final TestNode.TestService[] services = node.services();\n                return finalMessageCount == services[0].messageCount() &&\n                    finalChecksum == ((TestNode.ChecksumService)services[1]).checksum();\n            };\n\n        for (int i = 0; i < staticNodeCount; i++)\n        {\n            final TestNode node = cluster.node(i);\n            cluster.awaitNodeState(node, finalServiceState);\n        }\n    }\n\n    @Test\n    @InterruptAfter(60)\n    void shouldRecoverWhenFollowersIsMultipleTermsBehindFromEmptyLogAndPartialLogWithoutCommittedLogEntry()\n    {\n        cluster = aCluster().withStaticNodes(5).start(4);\n\n        systemTestWatcher.cluster(cluster);\n\n        final int messageCount = 10;\n        final int termCount = 3;\n        int totalMessages = 0;\n\n        int partialNode = NULL_VALUE;\n\n        for (int i = 0; i < termCount; i++)\n        {\n            final TestNode oldLeader = cluster.awaitLeader();\n\n            cluster.connectClient();\n            cluster.sendMessages(messageCount);\n            totalMessages += messageCount;\n            cluster.awaitResponseMessageCount(totalMessages);\n\n            if (NULL_VALUE == partialNode)\n            {\n                partialNode = (oldLeader.index() + 1) % 4;\n                cluster.stopNode(cluster.node(partialNode));\n            }\n\n            cluster.stopNode(oldLeader);\n            cluster.startStaticNode(oldLeader.index(), false);\n            cluster.awaitLeader();\n        }\n\n        final TestNode lateJoiningNode = cluster.startStaticNode(4, true);\n        awaitElectionClosed(lateJoiningNode);\n        cluster.awaitServiceMessageCount(lateJoiningNode, totalMessages);\n\n        final TestNode node = cluster.startStaticNode(partialNode, false);\n        awaitElectionClosed(node);\n        cluster.awaitServiceMessageCount(node, totalMessages);\n\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        totalMessages += messageCount;\n\n        cluster.awaitResponseMessageCount(totalMessages);\n        cluster.awaitServiceMessageCount(node, totalMessages);\n\n        cluster.assertRecordingLogsEqual();\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRejectTakeSnapshotRequestWithAnAuthorisationError()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n\n        final long requestCorrelationId = System.nanoTime();\n        final MutableBoolean hasResponse = injectAdminResponseEgressListener(\n            requestCorrelationId,\n            AdminRequestType.SNAPSHOT,\n            AdminResponseCode.UNAUTHORISED_ACCESS,\n            \"Execution of the \" + AdminRequestType.SNAPSHOT + \" request was not authorised\");\n\n        final AeronCluster client = cluster.connectClient();\n        while (!client.sendAdminRequestToTakeASnapshot(requestCorrelationId))\n        {\n            Tests.yield();\n        }\n\n        while (!hasResponse.get())\n        {\n            client.pollEgress();\n            Tests.yield();\n        }\n\n        long time = System.nanoTime();\n        final long deadline = time + leader.consensusModule().context().leaderHeartbeatTimeoutNs();\n        do\n        {\n            assertEquals(0, cluster.getSnapshotCount(leader));\n            for (final TestNode follower : followers)\n            {\n                assertEquals(0, cluster.getSnapshotCount(follower));\n            }\n            Tests.sleep(10);\n            time = System.nanoTime();\n        }\n        while (time < deadline);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldRejectAnInvalidAdminRequest()\n    {\n        final AdminRequestType invalidRequestType = AdminRequestType.NULL_VAL;\n        final AtomicBoolean isAuthorisedInvoked = new AtomicBoolean();\n        cluster = aCluster()\n            .withStaticNodes(3)\n            .withAuthorisationServiceSupplier(() ->\n                (protocolId, actionId, type, encodedPrincipal) ->\n                {\n                    isAuthorisedInvoked.set(true);\n                    assertEquals(MessageHeaderDecoder.SCHEMA_ID, protocolId);\n                    assertEquals(AdminRequestEncoder.TEMPLATE_ID, actionId);\n                    assertEquals(invalidRequestType, type);\n                    return true;\n                })\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n\n        final long requestCorrelationId = System.nanoTime();\n        final MutableBoolean hasResponse = injectAdminResponseEgressListener(\n            requestCorrelationId,\n            invalidRequestType,\n            AdminResponseCode.ERROR,\n            \"Unknown request type: \" + invalidRequestType);\n\n        final AeronCluster client = cluster.connectClient();\n        final AdminRequestEncoder adminRequestEncoder = new AdminRequestEncoder()\n            .wrapAndApplyHeader(cluster.msgBuffer(), 0, new MessageHeaderEncoder())\n            .leadershipTermId(client.leadershipTermId())\n            .clusterSessionId(client.clusterSessionId())\n            .correlationId(requestCorrelationId)\n            .requestType(invalidRequestType);\n\n        final Publication ingressPublication = client.ingressPublication();\n        while (ingressPublication.offer(\n            adminRequestEncoder.buffer(),\n            0,\n            MessageHeaderEncoder.ENCODED_LENGTH + adminRequestEncoder.encodedLength()) < 0)\n        {\n            Tests.yield();\n        }\n\n        Tests.await(isAuthorisedInvoked::get);\n\n        while (!hasResponse.get())\n        {\n            client.pollEgress();\n            Tests.yield();\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldTakeASnapshotAfterReceivingAdminRequestOfTypeSnapshot()\n    {\n        cluster = aCluster()\n            .withStaticNodes(3)\n            .withAuthorisationServiceSupplier(() -> AuthorisationService.ALLOW_ALL)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final long requestCorrelationId = System.nanoTime();\n        final MutableBoolean hasResponse = injectAdminResponseEgressListener(\n            requestCorrelationId, AdminRequestType.SNAPSHOT, AdminResponseCode.OK, EMPTY_MSG);\n\n        final AeronCluster client = cluster.connectClient();\n        while (!client.sendAdminRequestToTakeASnapshot(requestCorrelationId))\n        {\n            Tests.yield();\n        }\n\n        while (!hasResponse.get())\n        {\n            client.pollEgress();\n            Tests.yield();\n        }\n\n        cluster.awaitSnapshotCount(1);\n        cluster.awaitNeutralControlToggle(leader);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    @SuppressWarnings(\"MethodLength\")\n    void shouldTrackSnapshotDuration()\n    {\n        final long service1SnapshotDelayMs = 111;\n        final long service2SnapshotDelayMs = 222;\n\n        cluster = aCluster()\n            .withServiceSupplier(\n                (i) -> new TestNode.TestService[]\n                    {\n                        new TestNode.SleepOnSnapshotTestService()\n                            .snapshotDelayMs(service1SnapshotDelayMs).index(i),\n                        new TestNode.SleepOnSnapshotTestService()\n                            .snapshotDelayMs(service2SnapshotDelayMs).index(i)\n                    })\n            .withStaticNodes(3)\n            .withAuthorisationServiceSupplier(() -> AuthorisationService.ALLOW_ALL)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final SnapshotDurationTracker totalSnapshotDurationTracker = leader.consensusModule().context()\n            .totalSnapshotDurationTracker();\n\n        final SnapshotDurationTracker service1SnapshotDurationTracker = leader.container(0).context()\n            .snapshotDurationTracker();\n\n        final SnapshotDurationTracker service2SnapshotDurationTracker = leader.container(1).context()\n            .snapshotDurationTracker();\n\n        final long requestCorrelationId = System.nanoTime();\n        final MutableBoolean hasResponse = injectAdminResponseEgressListener(\n            requestCorrelationId, AdminRequestType.SNAPSHOT, AdminResponseCode.OK, EMPTY_MSG);\n\n        final AeronCluster client = cluster.connectClient();\n\n        assertEquals(0, totalSnapshotDurationTracker.snapshotDurationThresholdExceededCount().get());\n        assertEquals(0, totalSnapshotDurationTracker.maxSnapshotDuration().get());\n\n        assertEquals(0, service1SnapshotDurationTracker.snapshotDurationThresholdExceededCount().get());\n        assertEquals(0, service1SnapshotDurationTracker.maxSnapshotDuration().get());\n\n        assertEquals(0, service2SnapshotDurationTracker.snapshotDurationThresholdExceededCount().get());\n        assertEquals(0, service2SnapshotDurationTracker.maxSnapshotDuration().get());\n\n        while (!client.sendAdminRequestToTakeASnapshot(requestCorrelationId))\n        {\n            Tests.yield();\n        }\n\n        while (!hasResponse.get())\n        {\n            client.pollEgress();\n            Tests.yield();\n        }\n\n        cluster.awaitSnapshotCount(1);\n        cluster.awaitNeutralControlToggle(leader);\n\n        assertEquals(1, totalSnapshotDurationTracker.snapshotDurationThresholdExceededCount().get());\n        assertThat(\n            totalSnapshotDurationTracker.maxSnapshotDuration().get(),\n            greaterThanOrEqualTo(\n                percent90(MILLISECONDS.toNanos(Math.max(service1SnapshotDelayMs, service2SnapshotDelayMs)))));\n\n        assertEquals(1, service1SnapshotDurationTracker.snapshotDurationThresholdExceededCount().get());\n        assertThat(\n            service1SnapshotDurationTracker.maxSnapshotDuration().get(),\n            greaterThanOrEqualTo(percent90(MILLISECONDS.toNanos(service1SnapshotDelayMs))));\n\n        assertEquals(1, service2SnapshotDurationTracker.snapshotDurationThresholdExceededCount().get());\n        assertThat(\n            service2SnapshotDurationTracker.maxSnapshotDuration().get(),\n            greaterThanOrEqualTo(percent90(MILLISECONDS.toNanos(service2SnapshotDelayMs))));\n\n        for (final TestNode follower : cluster.followers())\n        {\n            final SnapshotDurationTracker snapshotDurationTracker = follower.consensusModule().context()\n                .totalSnapshotDurationTracker();\n            assertEquals(1, snapshotDurationTracker.snapshotDurationThresholdExceededCount().get());\n            assertThat(\n                snapshotDurationTracker.maxSnapshotDuration().get(),\n                greaterThanOrEqualTo(\n                    percent90(MILLISECONDS.toNanos(Math.max(service1SnapshotDelayMs, service2SnapshotDelayMs)))));\n\n            final SnapshotDurationTracker service1SnapshotTracker = follower.container(0).context()\n                .snapshotDurationTracker();\n\n            assertEquals(1, service1SnapshotTracker.snapshotDurationThresholdExceededCount().get());\n            assertThat(\n                service1SnapshotTracker.maxSnapshotDuration().get(),\n                greaterThanOrEqualTo(percent90(MILLISECONDS.toNanos(service1SnapshotDelayMs))));\n\n            final SnapshotDurationTracker service2SnapshotTracker = follower.container(1).context()\n                .snapshotDurationTracker();\n\n            assertEquals(1, service2SnapshotTracker.snapshotDurationThresholdExceededCount().get());\n            assertThat(\n                service2SnapshotTracker.maxSnapshotDuration().get(),\n                greaterThanOrEqualTo(percent90(MILLISECONDS.toNanos(service1SnapshotDelayMs))));\n        }\n    }\n\n    private static long percent90(final long value)\n    {\n        return 90 * (value / 100);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldTakeASnapshotAfterReceivingAdminRequestOfTypeSnapshotAndNotifyViaControlledPoll()\n    {\n        cluster = aCluster()\n            .withStaticNodes(3)\n            .withAuthorisationServiceSupplier(() ->\n                (protocolId, actionId, type, encodedPrincipal) ->\n                {\n                    assertEquals(MessageHeaderDecoder.SCHEMA_ID, protocolId);\n                    assertEquals(AdminRequestEncoder.TEMPLATE_ID, actionId);\n                    assertEquals(AdminRequestType.SNAPSHOT, type);\n                    return true;\n                })\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final long requestCorrelationId = System.nanoTime();\n        final MutableBoolean hasResponse = injectAdminRequestControlledEgressListener(requestCorrelationId);\n\n        final AeronCluster client = cluster.connectClient();\n        while (!client.sendAdminRequestToTakeASnapshot(requestCorrelationId))\n        {\n            Tests.yield();\n        }\n\n        while (!hasResponse.get())\n        {\n            client.controlledPollEgress();\n            Tests.yield();\n        }\n\n        cluster.awaitSnapshotCount(1);\n        cluster.awaitNeutralControlToggle(leader);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldHandleTrimmingClusterFromTheFront()\n    {\n        cluster = aCluster().withSegmentFileLength(512 * 1024).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leaderNode = cluster.awaitLeader();\n        cluster.connectClient();\n        cluster.sendLargeMessages(1024);\n        cluster.awaitResponseMessageCount(1024);\n        cluster.awaitServicesMessageCount(1024);\n\n        cluster.takeSnapshot(leaderNode);\n        cluster.awaitSnapshotCount(1);\n        cluster.purgeLogToLastSnapshot();\n\n        cluster.terminationsExpected(true);\n        cluster.abortCluster(leaderNode);\n        cluster.awaitNodeTermination(leaderNode);\n        cluster.awaitNodeTermination(cluster.followers().get(0));\n        cluster.awaitNodeTermination(cluster.followers().get(1));\n        cluster.close();\n\n        cluster.restartAllNodes(false);\n        cluster.awaitServicesMessageCount(1024);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldHandleReusingCorrelationIdsAcrossASnapshot()\n    {\n        cluster = aCluster().withSegmentFileLength(512 * 1024).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        cluster.connectClient();\n        final int messageLength1 = cluster.msgBuffer().putStringWithoutLengthAscii(0, REGISTER_TIMER_MSG);\n        cluster.pollUntilMessageSent(messageLength1);\n        cluster.awaitResponseMessageCount(1);\n\n        cluster.awaitTimerEventCount(1);\n\n        final int messageLength2 = cluster.msgBuffer().putStringWithoutLengthAscii(0, REGISTER_TIMER_MSG);\n        cluster.pollUntilMessageSent(messageLength2);\n        cluster.awaitResponseMessageCount(2);\n\n        cluster.awaitTimerEventCount(1);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldHandleReplayAfterShutdown()\n    {\n        cluster = aCluster().withStaticNodes(1).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(10);\n\n        leader.container().close(); // Will cause shutdown in consensus module\n        Tests.sleep(1_000);\n\n        CloseHelper.quietCloseAll(leader.consensusModule(), leader.archive(), leader.mediaDriver());\n\n        cluster.startStaticNode(0, false);\n        cluster.awaitLeader();\n        cluster.reconnectClient();\n        cluster.sendAndAwaitMessages(10);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldRemainStableWhenThereIsASlowFollower()\n    {\n        cluster = aCluster().withStaticNodes(3).withLogChannel(\"aeron:udp?term-length=64k\").start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        final TestNode followerToRestart = cluster.followers().get(0);\n        final TestNode liveFollower = cluster.followers().get(1);\n\n        awaitElectionClosed(followerToRestart);\n        cluster.stopNode(followerToRestart);\n\n        cluster.connectClient();\n\n        final long slowDownDelayMs = 1500;\n        cluster.sendMessageToSlowDownService(liveFollower.index(), MILLISECONDS.toNanos(slowDownDelayMs));\n        cluster.sendMessages(100);\n\n        final TestNode restartedFollower = cluster.startStaticNode(followerToRestart.index(), false);\n        awaitElectionClosed(restartedFollower);\n\n        cluster.sendMessages(100);\n        cluster.awaitServicesMessageCount(200);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldCatchupFollowerWithSlowService()\n    {\n        final int sleepTimeMs = 100;\n        final IntFunction<TestNode.TestService[]> serviceSupplier =\n            (i) -> new TestNode.TestService[]\n                {\n                    new TestNode.TestService().index(i),\n                    new TestNode.TestService()\n                    {\n                        public void onSessionMessage(\n                            final ClientSession session,\n                            final long timestamp,\n                            final DirectBuffer buffer,\n                            final int offset,\n                            final int length,\n                            final Header header)\n                        {\n                            Tests.sleep(sleepTimeMs);\n                            messageCount.incrementAndGet();\n                        }\n                    }.index(i)\n                };\n\n        cluster = aCluster()\n            .withLogChannel(\"aeron:udp?term-length=1m|alias=log\")\n            .withStaticNodes(3)\n            .withServiceSupplier(serviceSupplier)\n            .start();\n\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        cluster.connectClient();\n\n        final int firstBatchCount = 500 / sleepTimeMs;\n        cluster.sendMessages(firstBatchCount);\n        cluster.awaitResponseMessageCount(firstBatchCount);\n        cluster.awaitServicesMessageCount(firstBatchCount);\n\n        final TestNode followerA = cluster.followers().get(0);\n        final TestNode followerB = cluster.followers().get(1);\n        cluster.stopNode(followerA);\n\n        final int secondBatchCount = 700 / sleepTimeMs;\n        cluster.sendMessages(secondBatchCount);\n        cluster.awaitResponseMessageCount(firstBatchCount + secondBatchCount);\n        cluster.awaitServiceMessageCount(followerB, firstBatchCount + secondBatchCount);\n\n        cluster.startStaticNode(followerA.index(), false);\n\n        final int thirdBatchCount = 300 / sleepTimeMs;\n        cluster.sendMessages(thirdBatchCount);\n        cluster.awaitResponseMessageCount(firstBatchCount + secondBatchCount + thirdBatchCount);\n        cluster.awaitServicesMessageCount(firstBatchCount + secondBatchCount + thirdBatchCount);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SuppressWarnings(\"MethodLength\")\n    void shouldAssembleFragmentedSessionMessages()\n    {\n        final UnsafeBuffer[] messagesByIndex =\n        {\n            new UnsafeBuffer(new byte[8192]),\n            new UnsafeBuffer(new byte[8192]),\n            new UnsafeBuffer(new byte[8192])\n        };\n        cluster = aCluster().withServiceSupplier(\n            (i) -> new TestNode.TestService[]{ new TestNode.TestService()\n            {\n                private int messageOffset;\n\n                public void onSessionMessage(\n                    final ClientSession session,\n                    final long timestamp,\n                    final DirectBuffer buffer,\n                    final int offset,\n                    final int length,\n                    final Header header)\n                {\n                    final UnsafeBuffer messages = messagesByIndex[i];\n                    messages.putBytes(messageOffset, header.buffer(), header.offset(), HEADER_LENGTH);\n                    messages.putBytes(\n                        messageOffset + HEADER_LENGTH,\n                        buffer,\n                        offset - SESSION_HEADER_LENGTH,\n                        length + SESSION_HEADER_LENGTH);\n                    messageOffset += BitUtil.align(length + SESSION_HEADER_LENGTH + HEADER_LENGTH, FRAME_ALIGNMENT);\n                    echoMessage(session, buffer, offset, length);\n                }\n            }.index(i) }\n        ).withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final int logStreamId = leader.consensusModule().context().logStreamId();\n        final AeronCluster client = cluster.connectClient();\n        final int logOffset = 288; // NewLeadershipTermEvent + SessionOpenEvent\n\n        final ExpandableArrayBuffer msgBuffer = cluster.msgBuffer();\n        final int unfragmentedMessageLength = 63;\n        final long unfragmentedReservedValue = 2348723482321L;\n        final BufferClaim bufferClaim = new BufferClaim();\n        while (client.tryClaim(unfragmentedMessageLength, bufferClaim) < 0)\n        {\n            Tests.sleep(1);\n            ClusterTests.failOnClusterError();\n        }\n        bufferClaim.buffer().setMemory(\n            bufferClaim.offset() + SESSION_HEADER_LENGTH, unfragmentedMessageLength, (byte)0xEA);\n        bufferClaim.flags((byte)BEGIN_AND_END_FLAGS);\n        bufferClaim.reservedValue(unfragmentedReservedValue);\n        bufferClaim.commit();\n\n        final int messageLength = 5979;\n        msgBuffer.setMemory(0, messageLength, (byte)0xBC);\n        while (client.offer(msgBuffer, 0, messageLength) < 0)\n        {\n            Tests.sleep(1);\n            ClusterTests.failOnClusterError();\n        }\n\n        cluster.awaitResponseMessageCount(2);\n\n        final DataHeaderFlyweight headerFlyweight = new DataHeaderFlyweight();\n        final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n        final SessionMessageHeaderDecoder sessionMessageHeaderDecoder = new SessionMessageHeaderDecoder();\n        final Publication ingressPublication = client.ingressPublication();\n        final UnsafeBuffer messages = messagesByIndex[leader.index()];\n\n        headerFlyweight.wrap(messages, 0, HEADER_LENGTH);\n        assertEquals(unfragmentedMessageLength + SESSION_HEADER_LENGTH + HEADER_LENGTH, headerFlyweight.frameLength());\n        assertEquals(CURRENT_VERSION, headerFlyweight.version());\n        assertEquals(UNFRAGMENTED, (byte)headerFlyweight.flags());\n        assertEquals(HDR_TYPE_DATA, headerFlyweight.headerType());\n        assertEquals(logOffset, headerFlyweight.termOffset());\n        assertNotEquals(ingressPublication.sessionId(), headerFlyweight.sessionId());\n        assertEquals(logStreamId, headerFlyweight.streamId());\n        assertEquals(0, headerFlyweight.termId());\n        assertEquals(DEFAULT_RESERVE_VALUE, headerFlyweight.reservedValue()); // assign value is not propagated\n        sessionMessageHeaderDecoder.wrapAndApplyHeader(messages, HEADER_LENGTH, messageHeaderDecoder);\n        assertEquals(client.leadershipTermId(), sessionMessageHeaderDecoder.leadershipTermId());\n        assertEquals(client.clusterSessionId(), sessionMessageHeaderDecoder.clusterSessionId());\n        assertNotEquals(0, sessionMessageHeaderDecoder.timestamp());\n        for (int i = 0; i < unfragmentedMessageLength; i++)\n        {\n            assertEquals((byte)0xEA, messages.getByte(SESSION_HEADER_LENGTH + HEADER_LENGTH + i));\n        }\n\n        final int offset =\n            BitUtil.align(unfragmentedMessageLength + SESSION_HEADER_LENGTH + HEADER_LENGTH, FRAME_ALIGNMENT);\n        headerFlyweight.wrap(messages, offset, HEADER_LENGTH);\n        assertEquals(HEADER_LENGTH + SESSION_HEADER_LENGTH + messageLength, headerFlyweight.frameLength());\n        assertEquals(CURRENT_VERSION, headerFlyweight.version());\n        assertEquals(UNFRAGMENTED, (byte)headerFlyweight.flags());\n        assertEquals(HDR_TYPE_DATA, headerFlyweight.headerType());\n        assertEquals(logOffset + offset, headerFlyweight.termOffset());\n        assertNotEquals(ingressPublication.sessionId(), headerFlyweight.sessionId());\n        assertEquals(logStreamId, headerFlyweight.streamId());\n        assertEquals(0, headerFlyweight.termId());\n        assertEquals(DEFAULT_RESERVE_VALUE, headerFlyweight.reservedValue());\n        sessionMessageHeaderDecoder.wrapAndApplyHeader(messages, HEADER_LENGTH, messageHeaderDecoder);\n        assertEquals(client.leadershipTermId(), sessionMessageHeaderDecoder.leadershipTermId());\n        assertEquals(client.clusterSessionId(), sessionMessageHeaderDecoder.clusterSessionId());\n        assertNotEquals(0, sessionMessageHeaderDecoder.timestamp());\n        for (int i = 0; i < messageLength; i++)\n        {\n            assertEquals((byte)0xBC, messages.getByte(HEADER_LENGTH + SESSION_HEADER_LENGTH + offset + i));\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldCatchupAndJoinAsFollowerWhileSendingBigMessages()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        TestNode leader = cluster.awaitLeader();\n        final long leaderHeartbeatTimeoutNs = leader.consensusModule().context().leaderHeartbeatTimeoutNs();\n        final int leaderId = leader.consensusModule().context().clusterMemberId();\n        assertEquals(1, leader.consensusModule().context().electionCounter().get());\n        final List<TestNode> followers = cluster.followers();\n        for (final TestNode follower : followers)\n        {\n            assertEquals(1, follower.consensusModule().context().electionCounter().get());\n        }\n\n        cluster.connectClient();\n        int messageCount = 1000;\n        cluster.sendLargeMessages(messageCount);\n\n        // Choose the slowest node to stop to ensure that there is quorum in the Cluster after follower is stopped\n        TestNode follower1 = followers.get(0);\n        TestNode follower2 = followers.get(1);\n        if (follower1.appendPosition() < follower2.appendPosition())\n        {\n            final TestNode tmp = follower2;\n            follower2 = follower1;\n            follower1 = tmp;\n        }\n        final int follower1Id = follower1.memberId();\n        final int follower2Id = follower2.memberId();\n        assertNotEquals(leaderId, follower1Id);\n        assertNotEquals(leaderId, follower2Id);\n        assertNotEquals(follower1Id, follower2Id);\n\n        cluster.stopNode(follower2);\n        long startNs = System.nanoTime();\n        long endNs = startNs + leaderHeartbeatTimeoutNs;\n        final int messageLength = cluster.msgBuffer().putStringWithoutLengthAscii(0, LARGE_MSG);\n        while (System.nanoTime() < endNs)\n        {\n            cluster.pollUntilMessageSent(messageLength);\n            messageCount++;\n            Tests.sleep(50);\n        }\n\n        follower2 = cluster.startStaticNode(follower2.index(), false);\n        leader = cluster.awaitLeader();\n        follower1 = cluster.followers().stream()\n            .filter(f -> follower1Id == f.memberId())\n            .findFirst()\n            .orElse(null);\n        assertNotNull(follower1);\n        assertEquals(leaderId, leader.memberId(), \"leader changed\");\n        assertEquals(follower2Id, follower2.memberId(), \"wrong follower restarted\");\n\n        startNs = System.nanoTime();\n        endNs = startNs + 3 * leaderHeartbeatTimeoutNs;\n        while (System.nanoTime() < endNs)\n        {\n            cluster.pollUntilMessageSent(messageLength);\n            messageCount++;\n            Tests.sleep(50);\n        }\n\n        cluster.awaitResponseMessageCount(messageCount);\n        cluster.awaitServicesMessageCount(messageCount);\n        assertThat(\n            \"unexpected election on leader\",\n            leader.consensusModule().context().electionCounter().get(),\n            equalTo(1L /* startup */));\n        assertThat(\n            \"unexpected election on follower 1\",\n            follower1.consensusModule().context().electionCounter().get(),\n            equalTo(1L /* startup */));\n        assertThat(\n            \"election loop detected\",\n            follower2.consensusModule().context().electionCounter().get(),\n            equalTo(1L /* node restarted */));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSetClientName()\n    {\n        cluster = aCluster().withStaticNodes(1).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final ConsensusModule.Context leaderContext = leader.consensusModule().context();\n        try (Aeron aeron = Aeron.connect(new Aeron.Context()\n            .aeronDirectoryName(leaderContext.aeronDirectoryName())))\n        {\n            verifyClientName(aeron, leaderContext.aeron().clientId(), leaderContext.agentRoleName());\n\n            final ClusteredServiceContainer.Context containerContext = leader.container().context();\n            verifyClientName(aeron, containerContext.aeron().clientId(), containerContext.serviceName());\n        }\n    }\n\n    @Test\n    @SuppressWarnings(\"MethodLength\")\n    @InterruptAfter(30)\n    void twoClustersCanShareArchiveAndMediaDriver(@TempDir final Path tmpDir)\n    {\n        final ConsensusModule.Context cmContext1 = new ConsensusModule.Context();\n        final ClusteredServiceContainer.Context cscContext1 = new ClusteredServiceContainer.Context();\n        final ConsensusModule.Context cmContext2 = new ConsensusModule.Context();\n        final ClusteredServiceContainer.Context cscContext2 = new ClusteredServiceContainer.Context();\n        final MutableInteger leadershipCounter1 = new MutableInteger();\n        final MutableInteger leadershipCounter2 = new MutableInteger();\n        try (TestMediaDriver mediaDriver = TestMediaDriver.launch(new MediaDriver.Context()\n                .threadingMode(ThreadingMode.SHARED)\n                .aeronDirectoryName(tmpDir.resolve(\"aeron\").toString()),\n            systemTestWatcher);\n            Archive archive = Archive.launch(new Archive.Context()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .archiveDir(tmpDir.resolve(\"archive\").toFile())\n                .threadingMode(ArchiveThreadingMode.SHARED)\n                .recordingEventsEnabled(false)\n                .controlChannel(\"aeron:udp?endpoint=localhost:8888|term-length=64k\")\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\"));\n            ConsensusModule consensusModule1 = ConsensusModule.launch(cmContext1\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .clusterId(5)\n                .serviceCount(1)\n                .clusterDir(tmpDir.resolve(\"cluster-\" + cmContext1.clusterId()).toFile())\n                .ingressChannel(\"aeron:udp?term-length=128k|alias=ingress-cluster-\" + cmContext1.clusterId())\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .clusterMembers(\"0,localhost:8811,localhost:8822,localhost:8833,localhost:0,localhost:8888\")\n                .consensusModuleStreamId(cmContext1.consensusModuleStreamId() + 100)\n                .serviceStreamId(cmContext1.serviceStreamId() + 100)\n                .snapshotStreamId(cmContext1.snapshotStreamId() + 100)\n                .replayStreamId(cmContext1.replayStreamId() + 100));\n            ClusteredServiceContainer clusteredServiceContainer1 = ClusteredServiceContainer.launch(cscContext1\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .clusterDir(consensusModule1.context().clusterDir())\n                .clusterId(consensusModule1.context().clusterId())\n                .serviceStreamId(cmContext1.serviceStreamId())\n                .consensusModuleStreamId(cmContext1.consensusModuleStreamId())\n                .snapshotStreamId(cscContext1.snapshotStreamId() + 100)\n                .replayStreamId(cmContext1.replayStreamId())\n                .serviceId(0)\n                .serviceName(\"test1\")\n                .clusteredService(new TestNode.TestService()\n                {\n                    public void onNewLeadershipTermEvent(\n                        final long leadershipTermId,\n                        final long logPosition,\n                        final long timestamp,\n                        final long termBaseLogPosition,\n                        final int leaderMemberId,\n                        final int logSessionId,\n                        final TimeUnit timeUnit,\n                        final int appVersion)\n                    {\n                        leadershipCounter1.increment();\n                    }\n                }));\n            ConsensusModule consensusModule2 = ConsensusModule.launch(cmContext2\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .clusterId(7)\n                .serviceCount(1)\n                .clusterDir(tmpDir.resolve(\"cluster-\" + cmContext2.clusterId()).toFile())\n                .ingressChannel(\"aeron:udp?term-length=128k|alias=ingress-cluster-\" + cmContext2.clusterId())\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\")\n                .clusterMembers(\"0,localhost:9911,localhost:9922,localhost:9933,localhost:0,localhost:8888\")\n                .consensusModuleStreamId(cmContext2.consensusModuleStreamId() + 200)\n                .serviceStreamId(cmContext2.serviceStreamId() + 200)\n                .snapshotStreamId(cmContext2.snapshotStreamId() + 200)\n                .replayStreamId(cmContext2.replayStreamId() + 200));\n            ClusteredServiceContainer clusteredServiceContainer2 = ClusteredServiceContainer.launch(cscContext2\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .clusterDir(consensusModule2.context().clusterDir())\n                .clusterId(consensusModule2.context().clusterId())\n                .serviceStreamId(cmContext2.serviceStreamId())\n                .consensusModuleStreamId(cmContext2.consensusModuleStreamId())\n                .snapshotStreamId(cscContext2.snapshotStreamId() + 100)\n                .replayStreamId(cmContext2.replayStreamId())\n                .serviceId(0)\n                .serviceName(\"test2\")\n                .clusteredService(new TestNode.TestService()\n                {\n                    public void onNewLeadershipTermEvent(\n                        final long leadershipTermId,\n                        final long logPosition,\n                        final long timestamp,\n                        final long termBaseLogPosition,\n                        final int leaderMemberId,\n                        final int logSessionId,\n                        final TimeUnit timeUnit,\n                        final int appVersion)\n                    {\n                        leadershipCounter2.increment();\n                    }\n                })))\n        {\n            Tests.await(() ->\n                ElectionState.CLOSED == ElectionState.get(consensusModule1.context().electionStateCounter()));\n            Tests.await(() ->\n                ElectionState.CLOSED == ElectionState.get(consensusModule2.context().electionStateCounter()));\n\n            assertEquals(1L, consensusModule1.context().electionCounter().get());\n            assertEquals(1L, consensusModule2.context().electionCounter().get());\n            Tests.await(() -> 1 == leadershipCounter1.get() && 1 == leadershipCounter2.get());\n\n            try (AeronArchive aeronArchive = AeronArchive.connect(new AeronArchive.Context()\n                .aeronDirectoryName(archive.context().aeronDirectoryName())\n                .controlRequestChannel(archive.context().controlChannel())\n                .controlResponseChannel(\"aeron:udp?endpoint=localhost:0\")))\n            {\n                final IntHashSet logSessions = new IntHashSet();\n                assertEquals(2, aeronArchive.listRecordings(\n                        0,\n                        Integer.MAX_VALUE,\n                        (controlSessionId,\n                            correlationId,\n                            recordingId,\n                            startTimestamp,\n                            stopTimestamp,\n                            startPosition,\n                            stopPosition,\n                            initialTermId,\n                            segmentFileLength,\n                            termBufferLength,\n                            mtuLength,\n                            sessionId,\n                            streamId,\n                            strippedChannel,\n                            originalChannel,\n                            sourceIdentity) ->\n                        {\n                            assertThat(originalChannel, CoreMatchers.containsString(\"alias=log\"));\n                            logSessions.add(sessionId);\n                        }),\n                    \"wrong number of recordings\");\n                assertEquals(2, logSessions.size());\n            }\n\n            try (AeronCluster client = AeronCluster.connect(new AeronCluster.Context()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .ingressChannel(\"aeron:udp?term-length=128k\")\n                .ingressEndpoints(\"0=localhost:8811\")\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\")))\n            {\n                assertEquals(1, client.clusterSessionId());\n            }\n\n            final MutableLong client1SessionId = new MutableLong();\n            final MutableLong client2SessionId = new MutableLong();\n            final MutableInteger clientResponsesCount1 = new MutableInteger();\n            final MutableInteger clientResponsesCount2 = new MutableInteger();\n            try (AeronCluster client1 = AeronCluster.connect(new AeronCluster.Context()\n                .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                .ingressChannel(\"aeron:udp?term-length=128k\")\n                .ingressEndpoints(\"0=localhost:8811\")\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\")\n                .egressListener((clusterSessionId, timestamp, buffer, offset, length, header) ->\n                {\n                    assertEquals(client1SessionId.get(), clusterSessionId);\n                    clientResponsesCount1.getAndIncrement();\n                }));\n\n                AeronCluster client2 = AeronCluster.connect(new AeronCluster.Context()\n                    .aeronDirectoryName(mediaDriver.aeronDirectoryName())\n                    .ingressChannel(\"aeron:udp?term-length=128k\")\n                    .ingressEndpoints(\"0=localhost:9911\")\n                    .egressChannel(\"aeron:udp?endpoint=localhost:0\")\n                    .egressListener((clusterSessionId, timestamp, buffer, offset, length, header) ->\n                    {\n                        assertEquals(client2SessionId.get(), clusterSessionId);\n                        clientResponsesCount2.getAndIncrement();\n                    })))\n            {\n                assertNotEquals(client1.clusterSessionId(), client2.clusterSessionId());\n                client1SessionId.set(client1.clusterSessionId());\n                client2SessionId.set(client2.clusterSessionId());\n\n                final UnsafeBuffer msgBuf = new UnsafeBuffer(new byte[32]);\n                ThreadLocalRandom.current().nextBytes(msgBuf.byteArray());\n\n                for (int i = 0; i < 3; i++)\n                {\n                    while (client1.offer(msgBuf, 0, 16) < 0)\n                    {\n                        client1.pollEgress();\n                    }\n                }\n\n                ThreadLocalRandom.current().nextBytes(msgBuf.byteArray());\n                for (int i = 0; i < 5; i++)\n                {\n                    while (client2.offer(msgBuf, 0, msgBuf.capacity()) < 0)\n                    {\n                        client2.pollEgress();\n                    }\n                }\n\n                Tests.await(() ->\n                {\n                    client2.pollEgress();\n                    final TestNode.TestService service =\n                        (TestNode.TestService)clusteredServiceContainer2.context().clusteredService();\n                    return 5 == service.messageCount();\n                });\n\n                Tests.await(() ->\n                {\n                    client1.pollEgress();\n                    final TestNode.TestService service =\n                        (TestNode.TestService)clusteredServiceContainer1.context().clusteredService();\n                    return 3 == service.messageCount();\n                });\n\n                assertEquals(3,\n                    ((TestNode.TestService)clusteredServiceContainer1.context().clusteredService()).messageCount());\n                assertEquals(5,\n                    ((TestNode.TestService)clusteredServiceContainer2.context().clusteredService()).messageCount());\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(15)\n    void shouldAddCommittedNextSessionIdToTheConsensusModuleSnapshot()\n    {\n        final MutableInteger sessionCounter = new MutableInteger(0);\n\n        cluster = aCluster()\n            .withStaticNodes(3)\n            .withAuthenticationSupplier(() -> new Authenticator()\n            {\n                @Override\n                public void onConnectRequest(\n                    final long sessionId, final byte[] encodedCredentials, final long nowMs)\n                {\n                    sessionCounter.increment();\n                }\n\n                @Override\n                public void onChallengeResponse(\n                    final long sessionId, final byte[] encodedCredentials, final long nowMs)\n                {\n                }\n\n                @Override\n                public void onConnectedSession(final SessionProxy sessionProxy, final long nowMs)\n                {\n                    if (sessionCounter.get() > 2)\n                    {\n                        sessionProxy.reject();\n                    }\n                    else\n                    {\n                        sessionProxy.authenticate(\"admin\".getBytes(StandardCharsets.US_ASCII));\n                    }\n\n                }\n\n                @Override\n                public void onChallengedSession(final SessionProxy sessionProxy, final long nowMs)\n                {\n                }\n            })\n            .start();\n\n        systemTestWatcher.cluster(cluster);\n\n        // wait for cluster to hold election\n        final TestNode leader = cluster.awaitLeader();\n\n        // Original client - should always be allowed\n        final AeronCluster client = cluster.connectClient();\n\n        final AeronCluster.Context clientContext = new AeronCluster.Context()\n            .aeronDirectoryName(client.context().aeronDirectoryName())\n            .aeron(client.context().aeron())\n            .ingressChannel(client.context().ingressChannel())\n            .egressChannel(client.context().egressChannel())\n            .ingressEndpoints(client.context().ingressEndpoints());\n\n        // Another session -> also OK\n        try (AeronCluster client2 = AeronCluster.connect(clientContext.clone()))\n        {\n            assertNotNull(client2);\n        }\n\n        // Any further connections are rejected\n        assertThrowsExactly(AuthenticationException.class, () -> AeronCluster.connect(clientContext.clone()));\n        assertThrowsExactly(AuthenticationException.class, () -> AeronCluster.connect(clientContext.clone()));\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        final long[] sessionIdsByNode = new long[3];\n        for (int nodeIdx = 0; nodeIdx < 3; nodeIdx++)\n        {\n            sessionIdsByNode[nodeIdx] = readSnapshot(cluster.node(nodeIdx));\n        }\n        assertEquals(sessionIdsByNode[0], sessionIdsByNode[1]);\n        assertEquals(sessionIdsByNode[0], sessionIdsByNode[2]);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void clientShouldHandleRedirectResponseDuringConnectPhaseWithASubsetOfNodesConfigured()\n    {\n        cluster = aCluster().withStaticNodes(3).withClusterId(4).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final String leaderIngressEndpoint =\n            ingressEndpoint(cluster.clusterId(), leader.memberId(), cluster.memberCount());\n\n        final StringBuilder followerIngressEndpoints = new StringBuilder();\n        for (final TestNode node : cluster.followers())\n        {\n            followerIngressEndpoints.append(node.memberId()).append(\"=\")\n                .append(ingressEndpoint(cluster.clusterId(), node.memberId(), cluster.memberCount())).append(\",\");\n        }\n        followerIngressEndpoints.deleteCharAt(followerIngressEndpoints.length() - 1);\n\n        final TestMediaDriver clientDriver = cluster.startClientMediaDriver();\n\n        try (AeronCluster aeronCluster = AeronCluster.connect(new AeronCluster.Context()\n            .aeronDirectoryName(clientDriver.aeronDirectoryName())\n            .ingressChannel(\"aeron:udp?alias=ingress\")\n            .ingressEndpoints(followerIngressEndpoints.toString())\n            .egressChannel(\"aeron:udp?endpoint=localhost:0|alias=redirect-test\")))\n        {\n            final Publication ingressPublication = aeronCluster.ingressPublication();\n            assertNotNull(ingressPublication);\n            assertEquals(\n                leaderIngressEndpoint,\n                ChannelUri.parse(ingressPublication.channel()).get(ENDPOINT_PARAM_NAME));\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void clientShouldHandleRedirectResponseWhenInInvokerModeUsingConnect()\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"does not work with ATS enabled\");\n\n        cluster = aCluster().withStaticNodes(3).withClusterId(4).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final String leaderIngressEndpoint =\n            ingressEndpoint(cluster.clusterId(), leader.memberId(), cluster.memberCount());\n\n        final StringBuilder followerIngressEndpoints = new StringBuilder();\n        for (final TestNode node : cluster.followers())\n        {\n            followerIngressEndpoints.append(node.memberId()).append(\"=\")\n                .append(ingressEndpoint(cluster.clusterId(), node.memberId(), cluster.memberCount())).append(\",\");\n        }\n        followerIngressEndpoints.deleteCharAt(followerIngressEndpoints.length() - 1);\n\n        try (MediaDriver clientDriver = MediaDriver.launch(cluster\n            .newClientMediaDriverContext()\n            .threadingMode(ThreadingMode.INVOKER));\n            Aeron client = Aeron.connect(new Aeron.Context()\n                .aeronDirectoryName(clientDriver.aeronDirectoryName())\n                .driverAgentInvoker(null) // this is on purpose\n                .useConductorAgentInvoker(true)\n                .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE));\n            AeronCluster aeronCluster = AeronCluster.connect(new AeronCluster.Context()\n                .agentInvoker(clientDriver.sharedAgentInvoker())\n                .aeron(client)\n                .ingressChannel(\"aeron:udp?alias=ingress\")\n                .ingressEndpoints(followerIngressEndpoints.toString())\n                .egressChannel(\"aeron:udp?endpoint=localhost:0|alias=redirect-test\")))\n        {\n            assertNull(client.context().driverAgentInvoker());\n\n            final Publication ingressPublication = aeronCluster.ingressPublication();\n            assertNotNull(ingressPublication);\n            assertEquals(\n                leaderIngressEndpoint,\n                ChannelUri.parse(ingressPublication.channel()).get(ENDPOINT_PARAM_NAME));\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void clientShouldHandleRedirectResponseWhenInInvokerModeUsingAsyncConnect()\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"does not work with ATS enabled\");\n\n        cluster = aCluster().withStaticNodes(3).withClusterId(4).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final String leaderIngressEndpoint =\n            ingressEndpoint(cluster.clusterId(), leader.memberId(), cluster.memberCount());\n\n        final StringBuilder followerIngressEndpoints = new StringBuilder();\n        for (final TestNode node : cluster.followers())\n        {\n            followerIngressEndpoints.append(node.memberId()).append(\"=\")\n                .append(ingressEndpoint(cluster.clusterId(), node.memberId(), cluster.memberCount())).append(\",\");\n        }\n        followerIngressEndpoints.deleteCharAt(followerIngressEndpoints.length() - 1);\n\n        try (MediaDriver clientDriver = MediaDriver.launch(cluster\n            .newClientMediaDriverContext()\n            .threadingMode(ThreadingMode.INVOKER));\n            Aeron client = Aeron.connect(new Aeron.Context()\n                .aeronDirectoryName(clientDriver.aeronDirectoryName())\n                .driverAgentInvoker(null) // this is on purpose\n                .useConductorAgentInvoker(true)\n                .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE));\n            AeronCluster.AsyncConnect asyncConnect = AeronCluster.asyncConnect(new AeronCluster.Context()\n                .agentInvoker(clientDriver.sharedAgentInvoker())\n                .aeron(client)\n                .ingressChannel(\"aeron:udp?alias=ingress\")\n                .ingressEndpoints(followerIngressEndpoints.toString())\n                .egressChannel(\"aeron:udp?endpoint=localhost:0|alias=redirect-test\")))\n        {\n            assertNull(client.context().driverAgentInvoker());\n\n            AeronCluster aeronCluster;\n            while (null == (aeronCluster = asyncConnect.poll()))\n            {\n                Tests.yield();\n            }\n\n            final Publication ingressPublication = aeronCluster.ingressPublication();\n            assertNotNull(ingressPublication);\n            assertEquals(\n                leaderIngressEndpoint,\n                ChannelUri.parse(ingressPublication.channel()).get(ENDPOINT_PARAM_NAME));\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void clientShouldHandleLeadershipChangeWhenInInvokerMode()\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"does not work with ATS enabled\");\n\n        cluster = aCluster().withStaticNodes(3).withClusterId(4).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode originalLeader = cluster.awaitLeader();\n        final String leaderIngressEndpoint =\n            ingressEndpoint(cluster.clusterId(), originalLeader.memberId(), cluster.memberCount());\n\n        final MutableInteger onNewLeader = new MutableInteger(NULL_VALUE);\n        final EgressListener egressListener = new EgressListener()\n        {\n            public void onMessage(\n                final long clusterSessionId,\n                final long timestamp,\n                final DirectBuffer buffer,\n                final int offset,\n                final int length,\n                final Header header)\n            {\n            }\n\n            public void onNewLeader(\n                final long clusterSessionId,\n                final long leadershipTermId,\n                final int leaderMemberId,\n                final String ingressEndpoints)\n            {\n                onNewLeader.set(leaderMemberId);\n            }\n        };\n\n        try (MediaDriver clientDriver = MediaDriver.launch(cluster\n            .newClientMediaDriverContext()\n            .threadingMode(ThreadingMode.INVOKER));\n            Aeron client = Aeron.connect(new Aeron.Context()\n                .aeronDirectoryName(clientDriver.aeronDirectoryName())\n                .driverAgentInvoker(null) // this is on purpose\n                .useConductorAgentInvoker(true)\n                .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE));\n            AeronCluster aeronCluster = AeronCluster.connect(cluster.clientCtx()\n                .agentInvoker(clientDriver.sharedAgentInvoker())\n                .aeron(client)\n                .ingressChannel(\"aeron:udp?alias=ingress\")\n                .egressChannel(\"aeron:udp?endpoint=localhost:0\")\n                .egressListener(egressListener)))\n        {\n            assertNull(client.context().driverAgentInvoker());\n\n            final Publication originalIngressPublication = aeronCluster.ingressPublication();\n            assertNotNull(originalIngressPublication);\n            assertEquals(\n                leaderIngressEndpoint,\n                ChannelUri.parse(originalIngressPublication.channel()).get(ENDPOINT_PARAM_NAME));\n\n            cluster.stopNode(originalLeader);\n\n            final TestNode newLeader = cluster.awaitLeader();\n\n            while (NULL_VALUE == onNewLeader.get())\n            {\n                aeronCluster.pollEgress();\n                client.conductorAgentInvoker().invoke();\n                clientDriver.sharedAgentInvoker().invoke();\n            }\n            assertEquals(newLeader.memberId(), onNewLeader.get());\n\n            final Publication newIngressPublication = aeronCluster.ingressPublication();\n            assertNotNull(newIngressPublication);\n            assertNotSame(originalIngressPublication, newIngressPublication);\n            assertNotEquals(originalIngressPublication.registrationId(), newIngressPublication.registrationId());\n            assertEquals(\n                ingressEndpoint(cluster.clusterId(), newLeader.memberId(), cluster.memberCount()),\n                ChannelUri.parse(newIngressPublication.channel()).get(ENDPOINT_PARAM_NAME));\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"valid\", \"invalid\", \"wrong port\" })\n    @InterruptAfter(30)\n    void clientShouldReuseLeaderPublicationIfValidDuringRedirectHandling(final String mode)\n    {\n        cluster = aCluster().withStaticNodes(5).withClusterId(2).withAppointedLeader(4).start();\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching((error) -> error.contains(\"endpoint=invalid\"));\n\n        final TestNode leader = cluster.awaitLeader();\n        final String leaderIngressEndpoint =\n            ingressEndpoint(cluster.clusterId(), leader.memberId(), cluster.memberCount());\n\n        final StringBuilder ingressEndpoints = new StringBuilder();\n        for (final TestNode node : cluster.followers())\n        {\n            ingressEndpoints.append(node.memberId()).append(\"=\")\n                .append(ingressEndpoint(cluster.clusterId(), node.memberId(), cluster.memberCount())).append(\",\");\n        }\n\n        ingressEndpoints.append(leader.memberId()).append(\"=\");\n        final int separatorIndex = leaderIngressEndpoint.indexOf(':');\n        switch (mode)\n        {\n            case \"valid\":\n                ingressEndpoints.append(leaderIngressEndpoint);\n                break;\n            case \"invalid\":\n                ingressEndpoints\n                    .append(\"invalid\")\n                    .append(leaderIngressEndpoint, separatorIndex, leaderIngressEndpoint.length());\n                break;\n            case \"wrong port\":\n                ingressEndpoints\n                    .append(leaderIngressEndpoint, 0, separatorIndex + 1)\n                    .append(AsciiEncoding.parseIntAscii(\n                        leaderIngressEndpoint,\n                        separatorIndex + 1,\n                        leaderIngressEndpoint.length() - separatorIndex - 1) + 1111);\n                break;\n            default:\n                fail(\"unknown mode: \" + mode);\n        }\n\n        final TestMediaDriver clientDriver = cluster.startClientMediaDriver();\n\n        final ConsensusModule.Context leaderConsensusModule = leader.consensusModule().context();\n        final AtomicInteger availableImageCount = new AtomicInteger();\n        final AtomicInteger unAvailableImageCount = new AtomicInteger();\n        final ChannelUri ingressChannel = ChannelUri.parse(leaderConsensusModule.ingressChannel());\n        ingressChannel.put(ENDPOINT_PARAM_NAME, leaderIngressEndpoint);\n        ingressChannel.put(REJOIN_PARAM_NAME, \"false\");\n        try (Aeron leaderClient = Aeron.connect(\n            new Aeron.Context().aeronDirectoryName(leader.mediaDriver().aeronDirectoryName()));\n            Subscription ingressSubscription = leaderClient.addSubscription(\n                ingressChannel.toString(),\n                leaderConsensusModule.ingressStreamId(),\n                (image) -> availableImageCount.getAndIncrement(),\n                (image) -> unAvailableImageCount.getAndIncrement());\n            AeronCluster aeronCluster = AeronCluster.connect(new AeronCluster.Context()\n                .aeronDirectoryName(clientDriver.aeronDirectoryName())\n                .ingressChannel(\"aeron:udp?alias=ingress\")\n                .ingressEndpoints(ingressEndpoints.toString())\n                .egressChannel(\"aeron:udp?endpoint=localhost:0|alias=redirect-test\")))\n        {\n            final Publication ingressPublication = aeronCluster.ingressPublication();\n            assertNotNull(ingressPublication);\n            Tests.awaitConnected(ingressPublication);\n            Tests.awaitConnected(ingressSubscription);\n\n            assertEquals(\n                leaderIngressEndpoint,\n                ChannelUri.parse(ingressPublication.channel()).get(ENDPOINT_PARAM_NAME));\n            Tests.await(() -> availableImageCount.get() > 0);\n            assertEquals(1, availableImageCount.get());\n            assertEquals(0, unAvailableImageCount.get());\n        }\n    }\n\n    @Test\n    @InterruptAfter(15)\n    void clusterShouldCreateSessionCounterForEachConnectedClient()\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final ConsensusModule.Context leaderContext = leader.consensusModule().context();\n        final CountersReader leaderCounters = leaderContext.aeron().countersReader();\n\n        final TestMediaDriver clientDriver = cluster.startClientMediaDriver();\n\n        final AeronCluster.Context context =\n            cluster.clientCtx().aeronDirectoryName(clientDriver.aeronDirectoryName());\n        final IntArrayList sessionCounters = new IntArrayList();\n        try (AeronCluster client1 = AeronCluster.connect(context.clone().clientName(\"test client\"));\n            AeronCluster client2 = AeronCluster.connect(context.clone().clientName(null)))\n        {\n            leaderCounters.forEach((counterId, typeId, keyBuffer, label) ->\n            {\n                if (AeronCounters.CLUSTER_SESSION_TYPE_ID == typeId)\n                {\n                    sessionCounters.add(counterId);\n                    assertEquals(leaderContext.clusterId(), keyBuffer.getInt(0));\n                    final long clusterSessionId = keyBuffer.getLong(SIZE_OF_INT);\n                    if (client1.clusterSessionId() == clusterSessionId)\n                    {\n                        assertEquals(clusterSessionCounterLabel(client1, leaderContext.clusterId()), label);\n                    }\n                    else\n                    {\n                        assertEquals(client2.clusterSessionId(), clusterSessionId);\n                        assertEquals(clusterSessionCounterLabel(client2, leaderContext.clusterId()), label);\n                    }\n                }\n            });\n            assertEquals(2, sessionCounters.size(), \"cluster-session counters not found\");\n        }\n\n        Tests.await(() -> sessionCounters.intStream()\n            .allMatch(counterId -> CountersReader.RECORD_RECLAIMED == leaderCounters.getCounterState(counterId)));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSwitchBackToActiveStateIfSnapshotFailsWithException()\n    {\n        class ThrowingExtension extends TestNode.TestConsensusModuleExtension\n        {\n            public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n            {\n                throw new RuntimeException(\"some snapshot error\");\n            }\n        }\n\n        systemTestWatcher.ignoreErrorsMatching((error) -> error.contains(\"failed to take snapshot\"));\n        cluster = aCluster().withStaticNodes(3)\n            .withExtensionSuppler(ThrowingExtension::new)\n            .withServiceSupplier(value -> new TestNode.TestService[0])\n            .start();\n\n        systemTestWatcher.cluster(cluster);\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        cluster.sendMessages(5);\n\n        cluster.takeSnapshot(leader);\n\n        Tests.awaitValue(leader.consensusModule().context().errorCounter(), 1);\n        Tests.await(() -> ConsensusModule.State.ACTIVE == leader.moduleState());\n        Tests.awaitValue(cluster.getClusterControlToggle(leader), ClusterControl.ToggleState.NEUTRAL.code());\n        assertEquals(0, leader.consensusModule().context().snapshotCounter().get());\n\n        for (final TestNode follower : cluster.followers())\n        {\n            Tests.awaitValue(follower.consensusModule().context().errorCounter(), 1);\n            Tests.await(() -> ConsensusModule.State.ACTIVE == follower.moduleState());\n            assertEquals(0, follower.consensusModule().context().snapshotCounter().get());\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldContinueTerminationSequenceIfSnapshotFailsWithException()\n    {\n        class ThrowingExtension extends TestNode.TestConsensusModuleExtension\n        {\n            public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n            {\n                throw new RuntimeException(\"some other error\");\n            }\n        }\n\n        systemTestWatcher.ignoreErrorsMatching((error) -> error.contains(\"failed to take snapshot\"));\n        cluster = aCluster().withStaticNodes(3)\n            .withExtensionSuppler(ThrowingExtension::new)\n            .withServiceSupplier(value -> new TestNode.TestService[0])\n            .withErrorCounterSupplier((aeron) ->\n                addStaticCounter(aeron, CLUSTER_CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID, \"error\"))\n            .withSnapshotCounterSupplier((aeron) ->\n                addStaticCounter(aeron, CLUSTER_SNAPSHOT_COUNTER_TYPE_ID, \"snapshot\"))\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        cluster.sendMessages(5);\n\n        cluster.terminationsExpected(true);\n        cluster.shutdownCluster(leader);\n        cluster.awaitNodeTerminations();\n\n        for (int i = 0; i < cluster.memberCount(); i++)\n        {\n            final TestNode node = cluster.node(i);\n            Tests.awaitValue(node.consensusModule().context().errorCounter(), 1);\n            assertEquals(0, node.consensusModule().context().snapshotCounter().get());\n        }\n\n        cluster.stopAllNodes();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"terminalExceptions\")\n    @InterruptAfter(10)\n    void shouldShutdownClusterIfSnapshotFailsWithTerminalException(final RuntimeException terminalException)\n    {\n        class ThrowingExtension extends TestNode.TestConsensusModuleExtension\n        {\n            public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n            {\n                throw terminalException;\n            }\n        }\n\n        systemTestWatcher.ignoreErrorsMatching((error) -> error.contains(\"failed to take snapshot\"));\n        cluster = aCluster().withStaticNodes(3)\n            .withExtensionSuppler(ThrowingExtension::new)\n            .withServiceSupplier(value -> new TestNode.TestService[0])\n            .withErrorCounterSupplier((aeron) ->\n                addStaticCounter(aeron, CLUSTER_CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID, \"error\"))\n            .withSnapshotCounterSupplier((aeron) ->\n                addStaticCounter(aeron, CLUSTER_SNAPSHOT_COUNTER_TYPE_ID, \"snapshot\"))\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        cluster.sendMessages(5);\n\n        cluster.terminationsExpected(true);\n        cluster.takeSnapshot(leader);\n        cluster.awaitNodeTerminations();\n\n        for (int i = 0; i < cluster.memberCount(); i++)\n        {\n            final TestNode node = cluster.node(i);\n            Tests.awaitValue(node.consensusModule().context().errorCounter(), 1);\n            assertEquals(0, node.consensusModule().context().snapshotCounter().get());\n        }\n\n        cluster.stopAllNodes();\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldHandleQuorumPositionGoingBackwards()\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"manual loss generator\");\n\n        final int clusterSize = 3;\n        final List<StreamIdLossGenerator> lossGenerators = IntStream.range(0, clusterSize)\n            .mapToObj(i -> new StreamIdLossGenerator()).toList();\n        cluster = aCluster()\n            .withStaticNodes(clusterSize)\n            .withReceiveChannelEndpointSupplier((memberId) ->\n                (udpChannel, dispatcher, statusIndicator, context) ->\n                {\n                    final StreamIdLossGenerator lossGenerator = lossGenerators.get(memberId);\n                    return new DebugReceiveChannelEndpoint(\n                        udpChannel, dispatcher, statusIndicator, context, lossGenerator, lossGenerator);\n                })\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        TestNode fastFollower = followers.get(0);\n        final TestNode slowFollower = followers.get(1);\n        final ClusterMember[] clusterMembers =\n            ClusterMember.parse(leader.consensusModule().context().clusterMembers());\n\n        cluster.connectClient();\n\n        int sentMessages = cluster.sendAndAwaitMessages(100);\n        final long slowFollowerAppendPosition = slowFollower.appendPosition();\n\n        final StreamIdLossGenerator slowFollowerLossGenerator = lossGenerators.get(slowFollower.memberId());\n        slowFollowerLossGenerator.enable(slowFollower.consensusModule().context().logStreamId());\n\n        sentMessages += cluster.sendMessages(200);\n        cluster.awaitResponseMessageCount(sentMessages);\n        cluster.awaitServiceMessageCount(leader, sentMessages);\n        cluster.awaitServiceMessageCount(fastFollower, sentMessages);\n\n        final long quorumPosition = leader.commitPosition();\n\n        // stop the fast follower to cause quorum position regression\n        fastFollower.isTerminationExpected(true);\n        fastFollower.close();\n\n        sentMessages += cluster.sendMessages(150);\n        final long leaderAppendPosition = awaitLeaderLogRecording(cluster, leader, sentMessages);\n        assertEquals(quorumPosition, leader.commitPosition(), \"commit-pos cannot go backwards\");\n\n        final AtomicBuffer errorBuffer = leader.consensusModule().context().errorLog().buffer();\n        final String expectedError = \"quorum position went backwards: leaderCommitPosition=\" + quorumPosition +\n            \" quorumPosition=\" + slowFollowerAppendPosition;\n        final MutableBoolean found = new MutableBoolean(false);\n        final ErrorConsumer errorConsumer =\n            (observationCount, firstObservationTimestamp, lastObservationTimestamp, encodedException) ->\n            {\n                if (encodedException.contains(\"quorum position went backwards:\"))\n                {\n                    found.set(true);\n                    assertEquals(1, observationCount);\n                    assertThat(encodedException, containsString(expectedError));\n                }\n            };\n        while (!found.get())\n        {\n            if (0 == ErrorLogReader.read(errorBuffer, errorConsumer))\n            {\n                Tests.sleep(1);\n            }\n        }\n\n        slowFollowerLossGenerator.disable();\n\n        fastFollower = cluster.startStaticNode(fastFollower.memberId(), false);\n\n        while (slowFollower.appendPosition() < leaderAppendPosition)\n        {\n            assertThat(\n                \"leader commit-pos went backwards\",\n                leader.commitPosition(),\n                allOf(greaterThanOrEqualTo(quorumPosition), lessThanOrEqualTo(leaderAppendPosition)));\n            assertThat(\n                \"follower commit-pos went backwards\",\n                slowFollower.commitPosition(),\n                allOf(greaterThanOrEqualTo(slowFollowerAppendPosition), lessThanOrEqualTo(leaderAppendPosition)));\n        }\n\n        awaitElectionClosed(fastFollower);\n        cluster.awaitResponseMessageCount(sentMessages);\n        cluster.awaitServicesMessageCount(sentMessages);\n\n        assertEquals(1, ErrorLogReader.read(errorBuffer, errorConsumer));\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldFormClusterAfterFullPartition()\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"loss generator\");\n\n        final int clusterSize = 3;\n        final List<StreamIdLossGenerator> lossGenerators = IntStream.range(0, clusterSize)\n            .mapToObj(i -> new StreamIdLossGenerator()).toList();\n        cluster = aCluster()\n            .withStaticNodes(clusterSize)\n            .withReceiveChannelEndpointSupplier((memberId) ->\n                (udpChannel, dispatcher, statusIndicator, context) ->\n                {\n                    final StreamIdLossGenerator lossGenerator = lossGenerators.get(memberId);\n                    return new DebugReceiveChannelEndpoint(\n                        udpChannel, dispatcher, statusIndicator, context, lossGenerator, lossGenerator);\n                })\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode oldLeader = cluster.awaitLeader();\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(1);\n\n        // stop consensus traffic between the nodes\n        for (int i = 0; i < clusterSize; i++)\n        {\n            final StreamIdLossGenerator lossGenerator = lossGenerators.get(i);\n            lossGenerator.enable(cluster.node(i).consensusModule().context().consensusStreamId());\n        }\n\n        // wait for the next round of elections to start on all nodes\n        Tests.await(() ->\n        {\n            for (int i = 0; i < clusterSize; i++)\n            {\n                if (ElectionState.CLOSED == cluster.node(i).electionState())\n                {\n                    return false;\n                }\n            }\n            return true;\n        });\n\n        // kill old leader to create a dead node in the members list\n        cluster.stopNode(oldLeader);\n\n        // restore consensus traffic so that election can complete\n        for (final StreamIdLossGenerator lossGenerator : lossGenerators)\n        {\n            lossGenerator.disable();\n        }\n\n        final TestNode leader = cluster.awaitLeader(oldLeader.memberId());\n        assertEquals(2, leader.electionCount());\n\n        cluster.reconnectClient();\n        cluster.sendAndAwaitMessages(1);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldTerminateNodeWithInvalidSnapshotAndRecoveryAfterInvalidation()\n    {\n        cluster = aCluster()\n            .withServiceSupplier((i) -> new TestNode.TestService[] { new ExceptionOnLoadService().index(i) })\n            .start();\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching(\n            (s) -> s.contains(\"failed to start service=0\") || s.contains(\"failed to start clustered service(s)\"));\n\n        final TestNode leader = cluster.awaitLeader();\n        TestNode follower = cluster.followers().get(0);\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(10);\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.stopNode(follower);\n        cluster.startStaticNode(follower.index(), false);\n        follower = cluster.node(follower.index());\n\n        final AgentRunner clusteredServiceRunner = Tests.getField(follower.container(), \"serviceAgentRunner\");\n        while (!clusteredServiceRunner.isClosed())\n        {\n            Tests.yield();\n        }\n\n        final AgentRunner conductorRunner = Tests.getField(follower.consensusModule(), \"conductorRunner\");\n        while (!conductorRunner.isClosed())\n        {\n            Tests.yield();\n        }\n\n        cluster.waitForError(follower, (s) -> s.contains(\"failed to start service=0\"));\n\n        cluster.stopNode(follower);\n        final File followerClusterDir = follower.consensusModule().context().clusterDir();\n        assertTrue(ClusterTool.invalidateLatestSnapshot(mock(PrintStream.class), followerClusterDir));\n\n        cluster.startStaticNode(follower.index(), false);\n        follower = cluster.node(follower.index());\n        assertEquals(2, cluster.followers(2).size());\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldTerminateNodeWithMultipleServicesWithSingleServiceInvalidSnapshot()\n    {\n        cluster = aCluster()\n            .withServiceSupplier((i) -> new TestNode.TestService[]\n                { new TestNode.TestService().index(i), new ExceptionOnLoadService().index(i) })\n            .start();\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching(\n            (s) -> s.contains(\"failed to start service=1\") || s.contains(\"failed to start clustered service(s)\"));\n\n        final TestNode leader = cluster.awaitLeader();\n        TestNode follower = cluster.followers().get(0);\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(10);\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.stopNode(follower);\n        cluster.startStaticNode(follower.index(), false);\n        follower = cluster.node(follower.index());\n\n        final AgentRunner failingClusteredServiceRunner = Tests.getField(follower.container(1), \"serviceAgentRunner\");\n        while (!failingClusteredServiceRunner.isClosed())\n        {\n            Tests.yield();\n        }\n\n        final AgentRunner conductorRunner = Tests.getField(follower.consensusModule(), \"conductorRunner\");\n        while (!conductorRunner.isClosed())\n        {\n            Tests.yield();\n        }\n\n        final AgentRunner healthyClusteredServiceRunner = Tests.getField(follower.container(0), \"serviceAgentRunner\");\n        while (!healthyClusteredServiceRunner.isClosed())\n        {\n            Tests.yield();\n        }\n\n        cluster.waitForError(follower, (s) -> s.contains(\"failed to start service=1\"));\n    }\n\n    private static final class ExceptionOnLoadService extends TestNode.TestService\n    {\n        public void onStart(final Cluster cluster, final Image snapshotImage)\n        {\n            if (null != snapshotImage)\n            {\n                throw new IllegalStateException(\"this snapshot failed to load\");\n            }\n            super.onStart(cluster, snapshotImage);\n        }\n    }\n\n    private static List<RuntimeException> terminalExceptions()\n    {\n        return List.of(\n            new AgentTerminationException(\"test\"),\n            new ClusterTerminationException(true),\n            new ArchiveException(\"disc is gone\", ArchiveException.STORAGE_SPACE));\n    }\n\n    private String clusterSessionCounterLabel(final AeronCluster client, final int clusterId)\n    {\n        final Publication ingressPublication = client.ingressPublication();\n        return \"cluster-session: name=\" + client.context().clientName() + \" \" +\n            AeronCounters.formatVersionInfo(AeronClusterVersion.VERSION, AeronClusterVersion.GIT_SHA) +\n            \" sourceIdentity=\" + ingressPublication.localSocketAddresses().get(0) +\n            \" sessionId=\" + ingressPublication.sessionId() +\n            ClusterCounters.CLUSTER_ID_LABEL_SUFFIX + clusterId;\n    }\n\n    private long readSnapshot(final TestNode node)\n    {\n        final long recordingId;\n        try (RecordingLog recordingLog = new RecordingLog(node.consensusModule().context().clusterDir(),\n            false))\n        {\n            final RecordingLog.Entry snapshot = recordingLog.getLatestSnapshot(\n                ConsensusModule.Configuration.SERVICE_ID);\n            assertNotNull(snapshot);\n            recordingId = snapshot.recordingId;\n        }\n\n        final AeronArchive.Context archiveCtx = new AeronArchive.Context()\n            .controlRequestChannel(node.archive().context().localControlChannel())\n            .controlResponseChannel(node.archive().context().localControlChannel())\n            .controlRequestStreamId(node.archive().context().localControlStreamId())\n            .aeronDirectoryName(node.mediaDriver().aeronDirectoryName());\n\n        try (AeronArchive archive = AeronArchive.connect(archiveCtx);\n            Subscription subscription = archive.replay(\n                recordingId, NULL_POSITION, Long.MAX_VALUE, \"aeron:ipc\", 12345))\n        {\n            Tests.awaitConnected(subscription);\n            final Image image = subscription.imageAtIndex(0);\n\n            final MyConsensusModuleSnapshotListener listener = new MyConsensusModuleSnapshotListener();\n            final ConsensusModuleSnapshotAdapter adapter = new ConsensusModuleSnapshotAdapter(image, listener);\n\n            while (true)\n            {\n                final int fragments = adapter.poll();\n                if (adapter.isDone())\n                {\n                    break;\n                }\n                if (0 == fragments)\n                {\n                    if (image.isClosed())\n                    {\n                        throw new ClusterException(\"snapshot ended unexpectedly: \" + image);\n                    }\n                    archive.checkForErrorResponse();\n                    Thread.yield();\n                }\n            }\n\n            return listener.nextSessionId;\n        }\n    }\n\n    private static final class MyConsensusModuleSnapshotListener implements ConsensusModuleSnapshotListener\n    {\n        long nextSessionId = NULL_VALUE;\n\n        @Override\n        public void onLoadBeginSnapshot(final int appVersion, final TimeUnit timeUnit,\n            final DirectBuffer buffer, final int offset, final int length)\n        {\n        }\n\n        @Override\n        public void onLoadConsensusModuleState(final long nextSessionId,\n            final long nextServiceSessionId, final long logServiceSessionId,\n            final int pendingMessageCapacity, final DirectBuffer buffer,\n            final int offset, final int length)\n        {\n            this.nextSessionId = nextSessionId;\n        }\n\n        @Override\n        public void onLoadPendingMessage(final long clusterSessionId, final DirectBuffer buffer,\n            final int offset, final int length)\n        {\n        }\n\n        @Override\n        public void onLoadClusterSession(final long clusterSessionId, final long correlationId,\n            final long openedLogPosition,\n            final long timeOfLastActivity,\n            final CloseReason closeReason,\n            final int responseStreamId, final String responseChannel,\n            final DirectBuffer buffer, final int offset, final int length)\n        {\n        }\n\n        @Override\n        public void onLoadTimer(final long correlationId, final long deadline, final DirectBuffer buffer,\n            final int offset, final int length)\n        {\n        }\n\n        @Override\n        public void onLoadPendingMessageTracker(final long nextServiceSessionId,\n            final long logServiceSessionId,\n            final int pendingMessageCapacity,\n            final int serviceId,\n            final DirectBuffer buffer,\n            final int offset, final int length)\n        {\n        }\n\n        @Override\n        public void onLoadEndSnapshot(final DirectBuffer buffer, final int offset, final int length)\n        {\n        }\n    }\n\n    private static void verifyClientName(final Aeron aeron, final long targetClientId, final String expectedClientName)\n    {\n        assertNotEquals(aeron.clientId(), targetClientId);\n        final CountersReader countersReader = aeron.countersReader();\n        int counterId = NULL_COUNTER_ID;\n        String counterLabel = null;\n        while (true)\n        {\n            if (NULL_COUNTER_ID == counterId)\n            {\n                counterId = HeartbeatTimestamp.findCounterIdByRegistrationId(\n                    countersReader, HEARTBEAT_TYPE_ID, targetClientId);\n            }\n            else if (null == counterLabel || !counterLabel.contains(\"name=\"))\n            {\n                counterLabel = countersReader.getCounterLabel(counterId);\n            }\n            else\n            {\n                assertThat(counterLabel, containsString(expectedClientName));\n                break;\n            }\n            Tests.checkInterruptStatus();\n        }\n    }\n\n    private void shouldCatchUpAfterFollowerMissesMessage(final String message)\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        TestNode follower = cluster.followers().get(0);\n\n        cluster.stopNode(follower);\n\n        cluster.connectClient();\n        final int messageLength = cluster.msgBuffer().putStringWithoutLengthAscii(0, message);\n        cluster.pollUntilMessageSent(messageLength);\n        cluster.awaitResponseMessageCount(1);\n\n        follower = cluster.startStaticNode(follower.index(), false);\n\n        awaitElectionClosed(follower);\n        assertEquals(FOLLOWER, follower.role());\n    }\n\n    private MutableBoolean injectAdminResponseEgressListener(\n        final long expectedCorrelationId,\n        final AdminRequestType expectedRequestType,\n        final AdminResponseCode expectedResponseCode,\n        final String expectedMessage)\n    {\n        final MutableBoolean hasResponse = new MutableBoolean();\n\n        cluster.egressListener(\n            new EgressListener()\n            {\n                public void onMessage(\n                    final long clusterSessionId,\n                    final long timestamp,\n                    final DirectBuffer buffer,\n                    final int offset,\n                    final int length,\n                    final Header header)\n                {\n                }\n\n                public void onAdminResponse(\n                    final long clusterSessionId,\n                    final long correlationId,\n                    final AdminRequestType requestType,\n                    final AdminResponseCode responseCode,\n                    final String message,\n                    final DirectBuffer payload,\n                    final int payloadOffset,\n                    final int payloadLength)\n                {\n                    hasResponse.set(true);\n                    assertEquals(expectedCorrelationId, correlationId);\n                    assertEquals(expectedRequestType, requestType);\n                    assertEquals(expectedResponseCode, responseCode);\n                    assertEquals(expectedMessage, message);\n                    assertNotNull(payload);\n                    final int minPayloadOffset =\n                        MessageHeaderEncoder.ENCODED_LENGTH +\n                        AdminResponseEncoder.BLOCK_LENGTH +\n                        AdminResponseEncoder.messageHeaderLength() +\n                        message.length() +\n                        AdminResponseEncoder.payloadHeaderLength();\n                    assertTrue(payloadOffset > minPayloadOffset);\n                    assertEquals(0, payloadLength);\n                }\n            });\n\n        return hasResponse;\n    }\n\n    private MutableBoolean injectAdminRequestControlledEgressListener(final long expectedCorrelationId)\n    {\n        final MutableBoolean hasResponse = new MutableBoolean();\n\n        cluster.controlledEgressListener(\n            new ControlledEgressListener()\n            {\n                public ControlledFragmentHandler.Action onMessage(\n                    final long clusterSessionId,\n                    final long timestamp,\n                    final DirectBuffer buffer,\n                    final int offset,\n                    final int length,\n                    final Header header)\n                {\n                    return ControlledFragmentHandler.Action.ABORT;\n                }\n\n                public void onAdminResponse(\n                    final long clusterSessionId,\n                    final long correlationId,\n                    final AdminRequestType requestType,\n                    final AdminResponseCode responseCode,\n                    final String message,\n                    final DirectBuffer payload,\n                    final int payloadOffset,\n                    final int payloadLength)\n                {\n                    hasResponse.set(true);\n                    assertEquals(expectedCorrelationId, correlationId);\n                    assertEquals(AdminRequestType.SNAPSHOT, requestType);\n                    assertEquals(AdminResponseCode.OK, responseCode);\n                    assertEquals(EMPTY_MSG, message);\n                    assertNotNull(payload);\n                    final int minPayloadOffset =\n                        MessageHeaderEncoder.ENCODED_LENGTH +\n                        AdminResponseEncoder.BLOCK_LENGTH +\n                        AdminResponseEncoder.messageHeaderLength() +\n                        message.length() +\n                        AdminResponseEncoder.payloadHeaderLength();\n                    assertTrue(payloadOffset > minPayloadOffset);\n                    assertEquals(0, payloadLength);\n                }\n            });\n\n        return hasResponse;\n    }\n\n    private static Counter addStaticCounter(final Aeron aeron, final int typeId, final String label)\n    {\n        final long registrationId = aeron.nextCorrelationId();\n        return aeron.addStaticCounter(\n            typeId, label + \"-\" + registrationId, registrationId);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/ClusterToolTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.CommonContext;\nimport io.aeron.cluster.codecs.mark.ClusterComponentType;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.CapturingPrintStream;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.FileTime;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport java.util.regex.Pattern;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static java.nio.file.StandardOpenOption.CREATE_NEW;\nimport static java.nio.file.StandardOpenOption.WRITE;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.hamcrest.Matchers.matchesRegex;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\n\n@SlowTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ClusterToolTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(30)\n    void shouldHandleSnapshotOnLeaderOnly()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        final long initialSnapshotCount = leader.consensusModule().context().snapshotCounter().get();\n        final CapturingPrintStream capturingPrintStream = new CapturingPrintStream();\n\n        assertTrue(ClusterTool.snapshot(\n            leader.consensusModule().context().clusterDir(),\n            capturingPrintStream.resetAndGetPrintStream()));\n\n        assertThat(\n            capturingPrintStream.flushAndGetContent(),\n            containsString(\"SNAPSHOT applied successfully\"));\n\n        final long expectedSnapshotCount = initialSnapshotCount + 1;\n        cluster.awaitSnapshotCount(expectedSnapshotCount);\n\n        for (final TestNode follower : cluster.followers())\n        {\n            assertFalse(ClusterTool.snapshot(\n                follower.consensusModule().context().clusterDir(),\n                capturingPrintStream.resetAndGetPrintStream()));\n\n            assertThat(\n                capturingPrintStream.flushAndGetContent(),\n                containsString(\"Current node is not the leader\"));\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldDescribeLatestConsensusModuleSnapshot()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final CapturingPrintStream capturingPrintStream = new CapturingPrintStream();\n\n        assertTrue(ClusterTool.snapshot(\n            leader.consensusModule().context().clusterDir(),\n            capturingPrintStream.resetAndGetPrintStream()));\n\n        assertTrue(ClusterTool.describeLatestConsensusModuleSnapshot(\n            capturingPrintStream.resetAndGetPrintStream(),\n            leader.consensusModule().context().clusterDir()));\n\n        assertThat(\n            capturingPrintStream.flushAndGetContent(),\n            containsString(\"Snapshot: appVersion=1 timeUnit=MILLISECONDS\"));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldNotSnapshotWhenSuspendedOnly()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final long initialSnapshotCount = leader.consensusModule().context().snapshotCounter().get();\n        final CapturingPrintStream capturingPrintStream = new CapturingPrintStream();\n\n        assertTrue(ClusterTool.suspend(\n            leader.consensusModule().context().clusterDir(),\n            capturingPrintStream.resetAndGetPrintStream()));\n\n        assertThat(\n            capturingPrintStream.flushAndGetContent(),\n            containsString(\"SUSPEND applied successfully\"));\n\n        assertFalse(ClusterTool.snapshot(\n            leader.consensusModule().context().clusterDir(),\n            capturingPrintStream.resetAndGetPrintStream()));\n\n        final String expectedMessage =\n            \"Unable to SNAPSHOT as the state of the consensus module is SUSPENDED, but needs to be ACTIVE\";\n        assertThat(capturingPrintStream.flushAndGetContent(), containsString(expectedMessage));\n\n        assertEquals(initialSnapshotCount, leader.consensusModule().context().snapshotCounter().get());\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldSuspendAndResume()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        final CapturingPrintStream capturingPrintStream = new CapturingPrintStream();\n\n        assertTrue(ClusterTool.suspend(\n            leader.consensusModule().context().clusterDir(),\n            capturingPrintStream.resetAndGetPrintStream()));\n\n        assertThat(\n            capturingPrintStream.flushAndGetContent(),\n            containsString(\"SUSPEND applied successfully\"));\n\n        assertTrue(ClusterTool.resume(\n            leader.consensusModule().context().clusterDir(),\n            capturingPrintStream.resetAndGetPrintStream()));\n\n        assertThat(\n            capturingPrintStream.flushAndGetContent(),\n            containsString(\"RESUME applied successfully\"));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldFailIfMarkFileUnavailable(final @TempDir Path emptyClusterDir)\n    {\n        final CapturingPrintStream capturingPrintStream = new CapturingPrintStream();\n\n        assertFalse(ClusterTool.snapshot(emptyClusterDir.toFile(), capturingPrintStream.resetAndGetPrintStream()));\n        assertThat(\n            capturingPrintStream.flushAndGetContent(),\n            containsString(\"cluster-mark.dat does not exist\"));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBeAbleToAccessClusterMarkFilesInANonDefaultLocation(final @TempDir File markFileDir)\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).markFileBaseDir(markFileDir).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        cluster.sendErrorGeneratingMessages(1);\n        cluster.awaitResponseMessageCount(1);\n\n        final CapturingPrintStream stream = new CapturingPrintStream();\n        ClusterTool.errors(\n            stream.resetAndGetPrintStream(),\n            cluster.node(leader.index()).consensusModule().context().clusterDir());\n\n        final String errorContent = stream.flushAndGetContent();\n        assertThat(errorContent, containsString(\"This message will cause an error\"));\n        assertThat(errorContent, containsString(\"mark file exists\"));\n        final String path = markFileDir.getName();\n        final Pattern serviceMarkFileName = Pattern.compile(\n            \".*mark file exists:.*\" + path + \".*cluster-mark-service-0.dat.*\", Pattern.DOTALL);\n        assertThat(\"Tool output: \" + errorContent, errorContent, matchesRegex(serviceMarkFileName));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void sortRecordingLogIsANoOpIfRecordLogIsEmpty(final @TempDir Path emptyClusterDir) throws IOException\n    {\n        final File clusterDir = emptyClusterDir.toFile();\n        final RecordingLog recordingLog = new RecordingLog(clusterDir, true);\n        recordingLog.close();\n\n        final Path logFile = emptyClusterDir.resolve(RecordingLog.RECORDING_LOG_FILE_NAME);\n\n        final boolean result = ClusterTool.sortRecordingLog(clusterDir);\n\n        assertFalse(result);\n        assertArrayEquals(new byte[0], Files.readAllBytes(logFile));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void sortRecordingLogIsANoOpIfRecordDoesNotExist(final @TempDir Path emptyClusterDir)\n    {\n        final File clusterDir = emptyClusterDir.toFile();\n        final Path logFile = emptyClusterDir.resolve(RecordingLog.RECORDING_LOG_FILE_NAME);\n\n        final boolean result = ClusterTool.sortRecordingLog(clusterDir);\n\n        assertFalse(result);\n        assertFalse(Files.exists(logFile));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void sortRecordingLogIsANoOpIfRecordLogIsAlreadySorted(final @TempDir Path emptyClusterDir) throws IOException\n    {\n        final File clusterDir = emptyClusterDir.toFile();\n        final Path logFile = emptyClusterDir.resolve(RecordingLog.RECORDING_LOG_FILE_NAME);\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            recordingLog.appendTerm(21, 0, 100, 100);\n            recordingLog.appendSnapshot(0, 0, 100, 0, 200, 0);\n            recordingLog.appendTerm(21, 1, 1024, 200);\n        }\n\n        final byte[] originalBytes = Files.readAllBytes(logFile);\n        assertNotEquals(0, originalBytes.length);\n\n        final boolean result = ClusterTool.sortRecordingLog(clusterDir);\n\n        assertFalse(result);\n        assertArrayEquals(originalBytes, Files.readAllBytes(logFile));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void sortRecordingLogShouldRearrangeDataOnDisc(final @TempDir Path emptyClusterDir) throws IOException\n    {\n        final File clusterDir = emptyClusterDir.toFile();\n        final Path logFile = emptyClusterDir.resolve(RecordingLog.RECORDING_LOG_FILE_NAME);\n        final List<RecordingLog.Entry> sortedEntries = new ArrayList<>();\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            recordingLog.appendTerm(21, 2, 100, 100);\n            recordingLog.appendSnapshot(1, 2, 50, 60, 42, 89);\n            recordingLog.appendTerm(21, 1, 1024, 200);\n            recordingLog.appendSnapshot(0, 0, 0, 0, 200, 0);\n\n            final List<RecordingLog.Entry> entries = recordingLog.entries();\n            for (int i = 0, size = entries.size(); i < size; i++)\n            {\n                final RecordingLog.Entry entry = entries.get(i);\n                assertNotEquals(i, entry.entryIndex);\n                sortedEntries.add(new RecordingLog.Entry(\n                    entry.recordingId,\n                    entry.leadershipTermId,\n                    entry.termBaseLogPosition,\n                    entry.logPosition,\n                    entry.timestamp,\n                    entry.serviceId,\n                    entry.type,\n                    null,\n                    entry.isValid,\n                    i));\n            }\n        }\n\n        final byte[] originalBytes = Files.readAllBytes(logFile);\n        assertNotEquals(0, originalBytes.length);\n\n        final boolean result = ClusterTool.sortRecordingLog(clusterDir);\n\n        assertTrue(result);\n        assertFalse(Arrays.equals(originalBytes, Files.readAllBytes(logFile)));\n        assertArrayEquals(new String[]{ RecordingLog.RECORDING_LOG_FILE_NAME }, clusterDir.list());\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            final List<RecordingLog.Entry> entries = recordingLog.entries();\n            assertEquals(sortedEntries, entries);\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void seedRecordingLogFromSnapshotShouldDeleteOriginalRecordingLogFileIfThereAreNoValidSnapshots(\n        final @TempDir Path emptyClusterDir) throws IOException\n    {\n        final File clusterDir = emptyClusterDir.toFile();\n        final Path logFile = emptyClusterDir.resolve(RecordingLog.RECORDING_LOG_FILE_NAME);\n        final Path backupLogFile = emptyClusterDir.resolve(RecordingLog.RECORDING_LOG_FILE_NAME + \".bak\");\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            recordingLog.appendTerm(1, 1, 0, 100);\n            recordingLog.appendSnapshot(1, 1, 1000, 256, 300, 0);\n            recordingLog.appendSnapshot(1, 1, 1000, 256, 300, ConsensusModule.Configuration.SERVICE_ID);\n            recordingLog.appendTerm(1, 2, 2000, 400);\n            recordingLog.appendSnapshot(2, 5, 56, 111, 500, 5);\n\n            assertTrue(recordingLog.invalidateLatestSnapshot());\n        }\n\n        assertTrue(Files.exists(logFile));\n        assertFalse(Files.exists(backupLogFile));\n        final byte[] logContents = Files.readAllBytes(logFile);\n\n        ClusterTool.seedRecordingLogFromSnapshot(clusterDir);\n\n        assertFalse(Files.exists(logFile));\n        assertTrue(Files.exists(backupLogFile));\n        assertArrayEquals(logContents, Files.readAllBytes(backupLogFile));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void seedRecordingLogFromSnapshotShouldCreateANewRecordingLogFromALatestValidSnapshot(\n        final @TempDir Path emptyClusterDir) throws IOException\n    {\n        testSeedRecordingLogFromSnapshot(emptyClusterDir, ClusterTool::seedRecordingLogFromSnapshot);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void seedRecordingLogFromSnapshotShouldCreateANewRecordingLogFromALatestValidSnapshotCommandLine(\n        final @TempDir Path emptyClusterDir) throws IOException\n    {\n        testSeedRecordingLogFromSnapshot(\n            emptyClusterDir,\n            clusterDir -> ClusterTool.main(new String[]{ clusterDir.toString(), \"seed-recording-log-from-snapshot\" }));\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldCheckForLeaderInAnyStateAfterElectionWasClosed()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n        final PrintStream out = mock(PrintStream.class);\n\n        final TestNode leader = cluster.awaitLeader();\n        assertEquals(0, ClusterTool.isLeader(out, leader.consensusModule().context().clusterDir()));\n        for (final TestNode follower : cluster.followers())\n        {\n            assertEquals(1, ClusterTool.isLeader(out, follower.consensusModule().context().clusterDir()));\n        }\n\n        assertTrue(ClusterTool.suspend(leader.consensusModule().context().clusterDir(), out));\n        assertEquals(0, ClusterTool.isLeader(out, leader.consensusModule().context().clusterDir()));\n        for (final TestNode follower : cluster.followers())\n        {\n            assertEquals(1, ClusterTool.isLeader(out, follower.consensusModule().context().clusterDir()));\n        }\n    }\n\n    @Test\n    void listMembersShouldReturnFalseIfNoMarkFileExists(@TempDir final File emptyClusterDir)\n    {\n        final ClusterMembership clusterMembership = new ClusterMembership();\n        assertFalse(ClusterTool.listMembers(clusterMembership, emptyClusterDir, 0));\n    }\n\n    @Test\n    void listMembersShouldReturnFalseIfQueryTimesOut(@TempDir final Path clusterDir)\n    {\n        final SystemEpochClock clock = SystemEpochClock.INSTANCE;\n        try (TestMediaDriver driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName()), systemTestWatcher);\n            ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n                clusterDir.resolve(ClusterMarkFile.FILENAME).toFile(),\n                ClusterComponentType.BACKUP,\n                1024 * 1024,\n                clock,\n                1000,\n                LogBufferDescriptor.PAGE_MIN_SIZE))\n        {\n            clusterMarkFile.encoder()\n                .activityTimestamp(clock.time())\n                .consensusModuleStreamId(111)\n                .serviceStreamId(222)\n                .aeronDirectory(driver.aeronDirectoryName())\n                .controlChannel(\"aeron:udp?endpoint=localhost:7799\");\n            clusterMarkFile.signalReady(clock.time());\n\n            final ClusterMembership clusterMembership = new ClusterMembership();\n            assertFalse(ClusterTool.listMembers(clusterMembership, clusterMarkFile.parentDirectory(), 1));\n        }\n    }\n\n    private void testSeedRecordingLogFromSnapshot(final Path emptyClusterDir, final Consumer<File> truncateAction)\n        throws IOException\n    {\n        final Path logFile = emptyClusterDir.resolve(RecordingLog.RECORDING_LOG_FILE_NAME);\n        final Path backupLogFile = emptyClusterDir.resolve(RecordingLog.RECORDING_LOG_FILE_NAME + \".bak\");\n        Files.write(backupLogFile, new byte[]{ 0x1, -128, 0, 1, -1, 127 }, CREATE_NEW, WRITE);\n        Files.setLastModifiedTime(backupLogFile, FileTime.fromMillis(0));\n\n        final File clusterDir = emptyClusterDir.toFile();\n        final List<RecordingLog.Entry> truncatedEntries = new ArrayList<>();\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            recordingLog.appendTerm(1, 0, 0, 0);\n            recordingLog.appendSnapshot(1, 3, 4000, 4000, 600, 2);\n            recordingLog.appendTerm(1, 3, 3000, 500);\n            recordingLog.appendSnapshot(1, 2, 2900, 2200, 400, 2);\n            recordingLog.appendSnapshot(1, 2, 2900, 2200, 400, 1);\n            recordingLog.appendSnapshot(1, 2, 2900, 2200, 400, 0);\n            recordingLog.appendSnapshot(1, 2, 2900, 2200, 400, ConsensusModule.Configuration.SERVICE_ID);\n            recordingLog.appendTerm(1, 2, 2000, 300);\n            recordingLog.appendSnapshot(1, 1, 1800, 1000, 200, ConsensusModule.Configuration.SERVICE_ID);\n            recordingLog.appendSnapshot(1, 1, 1800, 1000, 200, 0);\n            recordingLog.appendSnapshot(1, 1, 1800, 1000, 200, 1);\n            recordingLog.appendTerm(1, 1, 1000, 100);\n\n            assertTrue(recordingLog.invalidateLatestSnapshot());\n\n            final List<RecordingLog.Entry> entries = recordingLog.entries();\n            for (int i = 2; i < 5; i++)\n            {\n                final RecordingLog.Entry entry = entries.get(i);\n                truncatedEntries.add(new RecordingLog.Entry(\n                    entry.recordingId,\n                    entry.leadershipTermId,\n                    0,\n                    0,\n                    entry.timestamp,\n                    entry.serviceId,\n                    entry.type,\n                    null,\n                    entry.isValid,\n                    i - 2));\n            }\n        }\n\n        final byte[] logContents = Files.readAllBytes(logFile);\n        final FileTime logLastModifiedTime = Files.getLastModifiedTime(logFile);\n\n        truncateAction.accept(clusterDir);\n\n        try (RecordingLog recordingLog = new RecordingLog(clusterDir, true))\n        {\n            assertEquals(truncatedEntries, recordingLog.entries());\n        }\n\n        assertArrayEquals(logContents, Files.readAllBytes(backupLogFile));\n        // compare up to millis, because upon copy file timestamp seems to be truncated\n        // e.g. expected: <2021-09-27T09:49:22.756944756Z> but was: <2021-09-27T09:49:22.756944Z>\n        assertEquals(logLastModifiedTime.toMillis(), Files.getLastModifiedTime(backupLogFile).toMillis());\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/ClusterUncommittedStateTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.driver.DataPacketDispatcher;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ReceiveChannelEndpointSupplier;\nimport io.aeron.driver.SendChannelEndpointSupplier;\nimport io.aeron.driver.ext.DebugReceiveChannelEndpoint;\nimport io.aeron.driver.ext.DebugSendChannelEndpoint;\nimport io.aeron.driver.ext.LossGenerator;\nimport io.aeron.driver.media.ReceiveChannelEndpoint;\nimport io.aeron.driver.media.SendChannelEndpoint;\nimport io.aeron.driver.media.UdpChannel;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.FrameDescriptor;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.protocol.DataHeaderFlyweight;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.agrona.BitUtil;\nimport org.agrona.DirectBuffer;\nimport org.agrona.collections.LongArrayQueue;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.net.InetSocketAddress;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static io.aeron.test.driver.TestMediaDriver.shouldRunJavaMediaDriver;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class ClusterUncommittedStateTest\n{\n    private static final int NODE_COUNT = 3;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n    private TestCluster cluster;\n    private final ToggledLossControl[] toggledLossControls = new ToggledLossControl[NODE_COUNT];\n\n    @BeforeEach\n    void setUp()\n    {\n        for (int i = 0; i < NODE_COUNT; ++i)\n        {\n            toggledLossControls[i] = new ToggledLossControl();\n        }\n    }\n\n    @ParameterizedTest(name = \"ShouldRollbackUncommittedSuspendControlToggle hasService={0}\")\n    @ValueSource(booleans = {true, false})\n    @SlowTest\n    @InterruptAfter(20)\n    void shouldRollbackUncommittedSuspendControlToggle(final boolean hasService)\n    {\n        assumeTrue(shouldRunJavaMediaDriver());\n\n        final TestCluster.Builder clusterBuilder = aCluster()\n            .withStaticNodes(NODE_COUNT)\n            .withReceiveChannelEndpointSupplier((memberId) -> toggledLossControls[memberId])\n            .withSendChannelEndpointSupplier((memberId) -> toggledLossControls[memberId]);\n        if (!hasService)\n        {\n            clusterBuilder\n                .withExtensionSuppler(TestNode.TestConsensusModuleExtension::new)\n                .withServiceSupplier(value -> new TestNode.TestService[0]);\n        }\n        cluster = clusterBuilder.start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode firstLeader = cluster.awaitLeader();\n        final ToggledLossControl leaderLossControl = toggledLossControls[firstLeader.memberId()];\n\n        leaderLossControl.toggleLoss(true);\n        Tests.await(() -> 0 < leaderLossControl.droppedOutboundFrames.get() &&\n            0 < leaderLossControl.droppedInboundFrames.get());\n\n        cluster.suspendCluster(firstLeader);\n        Tests.await(() -> ConsensusModule.State.SUSPENDED == firstLeader.moduleState());\n\n        Tests.await(() -> null != cluster.findLeader(firstLeader.memberId()));\n\n        leaderLossControl.toggleLoss(false);\n        Tests.await(() -> ConsensusModule.State.ACTIVE == firstLeader.moduleState());\n    }\n\n    @ParameterizedTest(name = \"ShouldRollbackUncommittedResumeControlToggle hasService={0}\")\n    @ValueSource(booleans = {true, false})\n    @SlowTest\n    @InterruptAfter(20)\n    void shouldRollbackUncommittedResumeControlToggle(final boolean hasService)\n    {\n        assumeTrue(shouldRunJavaMediaDriver());\n\n        final TestCluster.Builder clusterBuilder = aCluster()\n            .withStaticNodes(NODE_COUNT)\n            .withReceiveChannelEndpointSupplier((memberId) -> toggledLossControls[memberId])\n            .withSendChannelEndpointSupplier((memberId) -> toggledLossControls[memberId]);\n        if (!hasService)\n        {\n            clusterBuilder\n                .withExtensionSuppler(TestNode.TestConsensusModuleExtension::new)\n                .withServiceSupplier(value -> new TestNode.TestService[0]);\n        }\n        cluster = clusterBuilder.start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode firstLeader = cluster.awaitLeader();\n        final ToggledLossControl leaderLossControl = toggledLossControls[firstLeader.memberId()];\n\n        cluster.suspendCluster(firstLeader);\n        Tests.await(() ->\n        {\n            for (int i = 0; i < cluster.memberCount(); ++i)\n            {\n                if (ConsensusModule.State.SUSPENDED != cluster.node(i).moduleState())\n                {\n                    return false;\n                }\n            }\n            return true;\n        });\n\n        leaderLossControl.toggleLoss(true);\n        Tests.await(() -> 0 < leaderLossControl.droppedOutboundFrames.get() &&\n            0 < leaderLossControl.droppedInboundFrames.get());\n\n        cluster.resumeCluster(firstLeader);\n        Tests.await(() -> ConsensusModule.State.ACTIVE == firstLeader.moduleState());\n\n        Tests.await(() -> null != cluster.findLeader(firstLeader.memberId()));\n\n        leaderLossControl.toggleLoss(false);\n        Tests.await(() -> ConsensusModule.State.SUSPENDED == firstLeader.moduleState());\n    }\n\n    @ParameterizedTest(name = \"ShouldRollbackUncommittedSnapshotToggle hasService={0}\")\n    @ValueSource(booleans = {true, false})\n    @SlowTest\n    @InterruptAfter(20)\n    void shouldRollbackUncommittedSnapshotToggle(final boolean hasService)\n    {\n        assumeTrue(shouldRunJavaMediaDriver());\n\n        final TestCluster.Builder clusterBuilder = aCluster()\n            .withStaticNodes(NODE_COUNT)\n            .withReceiveChannelEndpointSupplier((memberId) -> toggledLossControls[memberId])\n            .withSendChannelEndpointSupplier((memberId) -> toggledLossControls[memberId]);\n        if (!hasService)\n        {\n            clusterBuilder\n                .withExtensionSuppler(TestNode.TestConsensusModuleExtension::new)\n                .withServiceSupplier(value -> new TestNode.TestService[0]);\n        }\n        cluster = clusterBuilder.start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode firstLeader = cluster.awaitLeader();\n        final ToggledLossControl leaderLossControl = toggledLossControls[firstLeader.memberId()];\n\n        cluster.suspendCluster(firstLeader);\n        Tests.await(() ->\n        {\n            for (int i = 0; i < cluster.memberCount(); ++i)\n            {\n                if (ConsensusModule.State.SUSPENDED != cluster.node(i).moduleState())\n                {\n                    return false;\n                }\n            }\n            return true;\n        });\n\n        leaderLossControl.toggleLoss(true);\n        Tests.await(() -> 0 < leaderLossControl.droppedOutboundFrames.get() &&\n            0 < leaderLossControl.droppedInboundFrames.get());\n\n        cluster.resumeCluster(firstLeader);\n        Tests.await(() -> ConsensusModule.State.ACTIVE == firstLeader.moduleState());\n        Tests.await(() -> ClusterControl.ToggleState.NEUTRAL ==\n            ClusterControl.ToggleState.get(firstLeader.consensusModule().context().controlToggleCounter()));\n\n        cluster.suspendCluster(firstLeader);\n        Tests.await(() -> ConsensusModule.State.SUSPENDED == firstLeader.moduleState());\n\n        cluster.resumeCluster(firstLeader);\n        Tests.await(() -> ConsensusModule.State.ACTIVE == firstLeader.moduleState());\n        Tests.await(() -> ClusterControl.ToggleState.NEUTRAL ==\n            ClusterControl.ToggleState.get(firstLeader.consensusModule().context().controlToggleCounter()));\n\n        cluster.takeSnapshot(firstLeader);\n        Tests.await(() -> ConsensusModule.State.SNAPSHOT == firstLeader.moduleState());\n\n        Tests.await(() -> null != cluster.findLeader(firstLeader.memberId()));\n\n        leaderLossControl.toggleLoss(false);\n        Tests.await(() -> ConsensusModule.State.SUSPENDED == firstLeader.moduleState());\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(20)\n    void shouldSnapshotWithNoServicesWithUncommittedData()\n    {\n        assumeTrue(shouldRunJavaMediaDriver());\n\n        cluster = aCluster()\n            .withStaticNodes(NODE_COUNT)\n            .withReceiveChannelEndpointSupplier((index) -> toggledLossControls[index])\n            .withSendChannelEndpointSupplier((index) -> toggledLossControls[index])\n            .withExtensionSuppler(TestCounterExtension::new)\n            .withServiceSupplier(value -> new TestNode.TestService[0])\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode firstLeader = cluster.awaitLeader();\n        final ToggledLossControl leaderLossControl = toggledLossControls[firstLeader.memberId()];\n\n        leaderLossControl.toggleLoss(true);\n        Tests.await(() -> 0 < leaderLossControl.droppedOutboundFrames.get() &&\n            0 < leaderLossControl.droppedInboundFrames.get());\n\n        cluster.connectIpcClient(new AeronCluster.Context(), firstLeader.mediaDriver().aeronDirectoryName());\n        cluster.sendExtensionMessages(32);\n        final long messageLength = BitUtil.align(\n            DataHeaderFlyweight.HEADER_LENGTH + MessageHeaderEncoder.ENCODED_LENGTH + BitUtil.SIZE_OF_INT,\n            FrameDescriptor.FRAME_ALIGNMENT);\n        Tests.await(() -> firstLeader.appendPosition() > 32L * messageLength);\n\n        cluster.takeSnapshot(firstLeader);\n        Tests.await(() -> ConsensusModule.State.SNAPSHOT == firstLeader.moduleState());\n\n        leaderLossControl.toggleLoss(false);\n        cluster.awaitSnapshotCount(1);\n\n        final TestCounterExtension node0Extension =\n            (TestCounterExtension)cluster.node(0).consensusModule().context().consensusModuleExtension();\n        final TestCounterExtension node1Extension =\n            (TestCounterExtension)cluster.node(1).consensusModule().context().consensusModuleExtension();\n        final TestCounterExtension node2Extension =\n            (TestCounterExtension)cluster.node(2).consensusModule().context().consensusModuleExtension();\n        final List<Integer> node0Snapshots = node0Extension.counterSnapshots();\n        final List<Integer> node1Snapshots = node1Extension.counterSnapshots();\n        final List<Integer> node2Snapshots = node2Extension.counterSnapshots();\n\n        assertEquals(1, node0Snapshots.size());\n        assertEquals(1, node1Snapshots.size());\n        assertEquals(1, node2Snapshots.size());\n        assertEquals(31, node0Snapshots.get(0));\n        assertEquals(31, node1Snapshots.get(0));\n        assertEquals(31, node2Snapshots.get(0));\n    }\n\n    private static final class TestCounterExtension extends TestNode.TestConsensusModuleExtension\n    {\n        private final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n\n        private Counter commitPosition = null;\n        private ExclusivePublication logPublication = null;\n        private final LongArrayQueue uncommittedCounters = new LongArrayQueue(Long.MAX_VALUE);\n        private int committedCounter = 0;\n        private final List<Integer> counterSnapshots = new ArrayList<>();\n\n        public void onStart(final ConsensusModuleControl consensusModuleControl, final Image snapshotImage)\n        {\n            assertNull(snapshotImage);\n            commitPosition = consensusModuleControl.context().commitPositionCounter();\n        }\n\n        public void onPrepareForNewLeadership()\n        {\n            logPublication = null;\n            drainUncommitted();\n            uncommittedCounters.clear();\n        }\n\n        public void onElectionComplete(final ConsensusControlState consensusControlState)\n        {\n            logPublication = consensusControlState.logPublication();\n        }\n\n        public ControlledFragmentHandler.Action onIngressExtensionMessage(\n            final int actingBlockLength,\n            final int templateId,\n            final int schemaId,\n            final int actingVersion,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            messageHeaderDecoder.wrap(buffer, offset);\n            assertEquals(TestCluster.EXTENSION_SCHEMA_ID, messageHeaderDecoder.schemaId());\n\n            if (logPublication.offer(buffer, offset, length) < 0)\n            {\n                return ControlledFragmentHandler.Action.ABORT;\n            }\n\n            assertTrue(uncommittedCounters.offerLong(logPublication.position()));\n            assertTrue(uncommittedCounters.offerLong(buffer.getLong(offset + MessageHeaderDecoder.ENCODED_LENGTH)));\n\n            return ControlledFragmentHandler.Action.CONTINUE;\n        }\n\n        public ControlledFragmentHandler.Action onLogExtensionMessage(\n            final int actingBlockLength,\n            final int templateId,\n            final int schemaId,\n            final int actingVersion,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            assertEquals(TestCluster.EXTENSION_SCHEMA_ID, schemaId);\n            committedCounter = buffer.getInt(offset);\n\n            return ControlledFragmentHandler.Action.CONTINUE;\n        }\n\n        public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n        {\n            drainUncommitted();\n            counterSnapshots.add(committedCounter);\n        }\n\n        private List<Integer> counterSnapshots()\n        {\n            return counterSnapshots;\n        }\n\n        private void drainUncommitted()\n        {\n            while (uncommittedCounters.peekLong() <= commitPosition.get())\n            {\n                uncommittedCounters.pollLong();\n                committedCounter = (int)uncommittedCounters.pollLong();\n            }\n        }\n    }\n\n    private static final class ToggledLossControl implements ReceiveChannelEndpointSupplier, SendChannelEndpointSupplier\n    {\n        final AtomicBoolean shouldDropOutboundFrames = new AtomicBoolean(false);\n        final AtomicInteger droppedOutboundFrames = new AtomicInteger(0);\n        final ToggledLossGenerator outboundLossGenerator = new ToggledLossGenerator(\n            shouldDropOutboundFrames, droppedOutboundFrames);\n\n        final AtomicBoolean shouldDropInboundFrames = new AtomicBoolean(false);\n        final AtomicInteger droppedInboundFrames = new AtomicInteger(0);\n        final ToggledLossGenerator inboundLossGenerator = new ToggledLossGenerator(\n            shouldDropInboundFrames, droppedInboundFrames);\n\n        public ReceiveChannelEndpoint newInstance(\n            final UdpChannel udpChannel,\n            final DataPacketDispatcher dispatcher,\n            final AtomicCounter statusIndicator,\n            final MediaDriver.Context context)\n        {\n            return new DebugReceiveChannelEndpoint(\n                udpChannel, dispatcher, statusIndicator, context, inboundLossGenerator, inboundLossGenerator);\n        }\n\n        public SendChannelEndpoint newInstance(\n            final UdpChannel udpChannel,\n            final AtomicCounter statusIndicator,\n            final MediaDriver.Context context)\n        {\n            return new DebugSendChannelEndpoint(\n                udpChannel, statusIndicator, context, outboundLossGenerator, outboundLossGenerator);\n        }\n\n        void toggleLoss(final boolean loss)\n        {\n            shouldDropOutboundFrames.set(loss);\n            shouldDropInboundFrames.set(loss);\n        }\n\n        private static final class ToggledLossGenerator implements LossGenerator\n        {\n            private final AtomicBoolean shouldDropFrame;\n            private final AtomicInteger droppedFrames;\n\n            private ToggledLossGenerator(final AtomicBoolean shouldDropFrame, final AtomicInteger droppedFrames)\n            {\n                this.shouldDropFrame = shouldDropFrame;\n                this.droppedFrames = droppedFrames;\n            }\n\n            public boolean shouldDropFrame(final InetSocketAddress address, final UnsafeBuffer buffer, final int length)\n            {\n                droppedFrames.incrementAndGet();\n                return shouldDropFrame.get();\n            }\n\n            public boolean shouldDropFrame(\n                final InetSocketAddress address,\n                final UnsafeBuffer buffer,\n                final int streamId,\n                final int sessionId,\n                final int termId,\n                final int termOffset,\n                final int length)\n            {\n                droppedFrames.incrementAndGet();\n                return shouldDropFrame.get();\n            }\n        }\n    }\n}"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/FailedFirstElectionClusterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport net.bytebuddy.asm.Advice;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\n\n@SlowTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass FailedFirstElectionClusterTest\n{\n    private static ClusterInstrumentor clusterInstrumentor;\n\n    @BeforeAll\n    static void beforeAll()\n    {\n        clusterInstrumentor = new ClusterInstrumentor(FailFirstElectionIntercept.class, \"Election\", \"onRequestVote\");\n    }\n\n    @AfterAll\n    static void afterAll()\n    {\n        clusterInstrumentor.reset();\n    }\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    public static class FailFirstElectionIntercept\n    {\n        @Advice.OnMethodEnter\n        static void onRequestVote(\n            final long logLeadershipTermId,\n            final long logPosition,\n            final long candidateTermId,\n            final int candidateId,\n            @Advice.This final Object thisObject)\n        {\n            final Election election = (Election)thisObject;\n            if (candidateId != election.thisMemberId() && candidateTermId == 0)\n            {\n                throw new ClusterException(\n                    \"Forced failure: memberId=\" + election.thisMemberId() + \", candidateTermId=\" + candidateTermId);\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(120)\n    void shouldRecoverWhenFollowerIsMultipleTermsBehindFromEmptyLog()\n    {\n        final int numNodes = 3;\n        final int messageCount = 10;\n        final int numTerms = 3;\n\n        final TestCluster cluster = aCluster().withStaticNodes(numNodes).start(2);\n\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching((s) -> s.contains(\"Forced failure\"));\n\n        int totalMessages = 0;\n\n        for (int i = 0; i < numTerms; i++)\n        {\n            final TestNode oldLeader = cluster.awaitLeader();\n\n            cluster.connectClient();\n            cluster.sendMessages(messageCount);\n            totalMessages += messageCount;\n            cluster.awaitResponseMessageCount(totalMessages);\n\n            cluster.stopNode(oldLeader);\n            cluster.startStaticNode(oldLeader.index(), false);\n            cluster.awaitLeader();\n        }\n\n        cluster.startStaticNode(2, true);\n        cluster.awaitLeader();\n\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        totalMessages += messageCount;\n\n        cluster.awaitResponseMessageCount(totalMessages);\n        cluster.awaitServicesMessageCount(totalMessages);\n\n        cluster.assertRecordingLogsEqual();\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/InitiateShutdownThenImmediatelyCloseLeaderTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@SlowTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass InitiateShutdownThenImmediatelyCloseLeaderTest\n{\n    private MethodCallBlocker methodCallBlocker;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void setUp()\n    {\n        methodCallBlocker = new MethodCallBlocker();\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        methodCallBlocker.removeInstrumentation();\n    }\n\n    @Test\n    @InterruptAfter(20)\n    @Disabled(\"Intermittent\")\n    void shouldNotSendTerminationAckToNewLeaderWhenOldLeaderInitiatedShutdownAndImmediatelyClosed()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching(error -> error.contains(\"termination ack not sent\"));\n\n        final TestNode firstLeader = cluster.awaitLeader();\n        assertEquals(Cluster.Role.LEADER, firstLeader.role());\n\n        final int aIndex = (firstLeader.index() + 1) % 3;\n        final int bIndex = (firstLeader.index() + 2) % 3;\n        final TestNode aNode = cluster.node(aIndex);\n        final TestNode bNode = cluster.node(bIndex);\n\n        aNode.isTerminationExpected(true);\n        bNode.isTerminationExpected(true);\n\n        final MethodCallBlocker.Controller aOnTerminationPosition = methodCallBlocker.getControllerFor(\n            \"io.aeron.cluster.ConsensusModuleAgent\",\n            \"onTerminationPosition\",\n            \"consensus-module-0-\" + aIndex\n        );\n\n        final MethodCallBlocker.Controller aAckBlocker = methodCallBlocker.getControllerFor(\n            \"io.aeron.cluster.service.ConsensusModuleProxy\",\n            \"ack\",\n            \"clustered-service-0-\" + aIndex + \"-0\"\n        );\n\n        final MethodCallBlocker.Controller bPollArchiveEventsBlocker = methodCallBlocker.getControllerFor(\n            \"io.aeron.cluster.ConsensusModuleAgent\",\n            \"consensusWork\",\n            \"consensus-module-0-\" + bIndex\n        );\n\n        aOnTerminationPosition.blockNextEntry();\n        bPollArchiveEventsBlocker.blockNextEntry();\n        bPollArchiveEventsBlocker.awaitBlocked();\n\n        cluster.shutdownCluster(firstLeader);\n\n        aOnTerminationPosition.awaitBlocked();\n\n        firstLeader.gracefulClose();\n\n        final long logRecordingId = 0;\n        final long archiveId = bNode.archive().context().archiveId();\n        final CountersReader counters = bNode.mediaDriver().counters();\n\n        Tests.await(() ->\n            RecordingPos.findCounterIdByRecording(counters, logRecordingId, archiveId) == RecordingPos.NULL_RECORDING_ID\n        );\n\n        aAckBlocker.blockNextEntry();\n        bPollArchiveEventsBlocker.release();\n        aOnTerminationPosition.release();\n\n        Tests.await(() -> aNode.role() == Cluster.Role.LEADER || bNode.role() == Cluster.Role.LEADER);\n        final TestNode secondLeader = aNode.role() == Cluster.Role.LEADER ? aNode : bNode;\n\n        aAckBlocker.release();\n\n        if (secondLeader.index() == aIndex)\n        {\n            cluster.awaitNodeTermination(bNode);\n        }\n        else if (secondLeader.index() == bIndex)\n        {\n            cluster.awaitNodeTermination(aNode);\n        }\n        secondLeader.close();\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/MethodCallBlocker.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.test.Tests;\nimport net.bytebuddy.ByteBuddy;\nimport net.bytebuddy.agent.ByteBuddyAgent;\nimport net.bytebuddy.agent.builder.AgentBuilder;\nimport net.bytebuddy.agent.builder.ResettableClassFileTransformer;\nimport net.bytebuddy.asm.Advice;\nimport net.bytebuddy.description.annotation.AnnotationDescription;\nimport net.bytebuddy.description.field.FieldDescription;\nimport net.bytebuddy.description.method.MethodDescription;\nimport net.bytebuddy.description.type.TypeDescription;\nimport net.bytebuddy.dynamic.ClassFileLocator;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.dynamic.loading.ClassLoadingStrategy;\nimport net.bytebuddy.dynamic.scaffold.TypeValidation;\nimport net.bytebuddy.implementation.Implementation;\nimport net.bytebuddy.implementation.bytecode.StackManipulation;\nimport net.bytebuddy.implementation.bytecode.member.FieldAccess;\nimport net.bytebuddy.implementation.bytecode.member.MethodInvocation;\nimport net.bytebuddy.implementation.bytecode.member.MethodReturn;\nimport net.bytebuddy.matcher.ElementMatchers;\nimport net.bytebuddy.utility.JavaModule;\n\nimport java.lang.instrument.Instrumentation;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic final class MethodCallBlocker\n{\n    private static final Method RUNNABLE_RUN_METHOD = runnableRunMethod();\n    private static final String ON_ENTER_FIELD_NAME = \"onEnter\";\n    private final HashMap<String, MethodCallHandler> methodCallHandlers = new HashMap<>();\n    private final Instrumentation instrumentation = ByteBuddyAgent.install();\n\n    public Controller getControllerFor(\n        final String className,\n        final String methodName,\n        final String threadName\n    )\n    {\n        return obtainInstrumentedMethodCallHandler(className, methodName).getControllerForThread(threadName);\n    }\n\n    public void removeInstrumentation()\n    {\n        for (final MethodCallHandler methodCallHandler : methodCallHandlers.values())\n        {\n            methodCallHandler.removeInstrumentation();\n        }\n        methodCallHandlers.clear();\n    }\n\n    private static Method runnableRunMethod()\n    {\n        try\n        {\n            return Runnable.class.getMethod(\"run\");\n        }\n        catch (final NoSuchMethodException exception)\n        {\n            throw new RuntimeException(exception);\n        }\n    }\n\n    private MethodCallHandler obtainInstrumentedMethodCallHandler(final String className, final String methodName)\n    {\n        final String key = className + \"#\" + methodName;\n        return methodCallHandlers.computeIfAbsent(key, ignored -> instrumentMethodCall(className, methodName));\n    }\n\n    private MethodCallHandler instrumentMethodCall(\n        final String className,\n        final String methodName)\n    {\n        final AgentBuilder agentBuilder = new AgentBuilder.Default(new ByteBuddy()\n            .with(TypeValidation.DISABLED))\n            .disableClassFormatChanges()\n            .with(new AgentBuilderListener())\n            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);\n\n        final String adviceClassName = \"io.aeron.cluster.DynamicThreadBlockAdvice\" +\n            UUID.randomUUID().toString().replace(\"-\", \"\");\n\n        try (DynamicType.Unloaded<Object> adviceType = createAdviceType(adviceClassName))\n        {\n            final Class<?> dynamicAdviceClass = adviceType\n                .load(MethodCallBlocker.class.getClassLoader(), ClassLoadingStrategy.UsingLookup.of(\n                    MethodHandles.privateLookupIn(MethodCallBlocker.class, MethodHandles.lookup())))\n                .getLoaded();\n\n            final MethodCallHandler methodCallHandler = new MethodCallHandler();\n\n            setStaticOnEnterField(dynamicAdviceClass, methodCallHandler::onEnter);\n\n            final TypeDescription adviceTypeDesc = adviceType.getTypeDescription();\n\n            @SuppressWarnings(\"resource\") final ClassFileLocator.Simple classFileLocator = new ClassFileLocator.Simple(\n                Map.of(adviceTypeDesc.getName(), adviceType.getBytes())\n            );\n\n            final AgentBuilder.Transformer change = (builder, typeDescription, classLoader, module, protectionDomain) ->\n                builder.visit(Advice.to(adviceTypeDesc, classFileLocator).on(ElementMatchers.named(methodName)));\n\n            final ResettableClassFileTransformer transformer = agentBuilder\n                .type(ElementMatchers.named(className))\n                .transform(change)\n                .installOn(instrumentation);\n\n            methodCallHandler.transformer(transformer);\n\n            return methodCallHandler;\n        }\n        catch (final IllegalAccessException exception)\n        {\n            throw new RuntimeException(exception);\n        }\n    }\n\n    private static void setStaticOnEnterField(final Class<?> dynamicAdviceClass, final Runnable onEnterRunnable)\n    {\n        try\n        {\n            final Field onEnterField = dynamicAdviceClass.getField(ON_ENTER_FIELD_NAME);\n            onEnterField.set(null, onEnterRunnable);\n        }\n        catch (final NoSuchFieldException | IllegalAccessException exception)\n        {\n            throw new RuntimeException(exception);\n        }\n    }\n\n    private static FieldDescription getOnEnterFieldDescription(final String adviceClassName)\n    {\n        final FieldDescription onEnterDescription;\n        try (DynamicType.Unloaded<Object> firstPass = new ByteBuddy()\n            .subclass(Object.class)\n            .name(adviceClassName)\n            .defineField(ON_ENTER_FIELD_NAME, Runnable.class, Modifier.PUBLIC | Modifier.STATIC)\n            .make())\n        {\n\n            onEnterDescription = firstPass.getTypeDescription()\n                .getDeclaredFields()\n                .filter(ElementMatchers.named(ON_ENTER_FIELD_NAME))\n                .getOnly();\n        }\n        return onEnterDescription;\n    }\n\n    private static DynamicType.Unloaded<Object> createAdviceType(final String adviceClassName)\n    {\n        final FieldDescription onEnterDescription = getOnEnterFieldDescription(adviceClassName);\n\n        return new ByteBuddy()\n            .subclass(Object.class)\n            .name(adviceClassName)\n            .defineField(ON_ENTER_FIELD_NAME, Runnable.class, Modifier.PUBLIC | Modifier.STATIC)\n            .defineMethod(\"enter\", void.class, Modifier.PUBLIC | Modifier.STATIC)\n            .intercept(new Implementation.Simple(new StackManipulation.Compound(\n                FieldAccess.forField(onEnterDescription).read(),\n                MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(RUNNABLE_RUN_METHOD)),\n                MethodReturn.VOID\n            )))\n            .annotateMethod(AnnotationDescription.Builder.ofType(Advice.OnMethodEnter.class).build())\n            .make();\n    }\n\n    private final class MethodCallHandler\n    {\n        private final ConcurrentHashMap<Thread, Controller> controllers = new ConcurrentHashMap<>();\n        private ResettableClassFileTransformer transformer;\n\n        public void transformer(final ResettableClassFileTransformer transformer)\n        {\n            this.transformer = transformer;\n        }\n\n        public Controller getControllerForThread(final String threadName)\n        {\n            final Set<Thread> threads = Thread.getAllStackTraces().keySet();\n            Thread matchingThread = null;\n\n            for (final Thread thread : threads)\n            {\n                if (thread.getName().equals(threadName))\n                {\n                    if (matchingThread != null)\n                    {\n                        throw new IllegalStateException(\"Multiple threads match: '\" + threadName + \"'\");\n                    }\n\n                    matchingThread = thread;\n                }\n            }\n\n            if (matchingThread == null)\n            {\n                throw new IllegalStateException(\"No thread found matching: '\" + threadName + \"'. Available threads: \" +\n                    threads.stream().map(Thread::getName).toList());\n            }\n\n            return getControllerForThread(matchingThread);\n        }\n\n        private Controller getControllerForThread(final Thread thread)\n        {\n            return controllers.computeIfAbsent(thread, ignored -> new Controller());\n        }\n\n        private void removeInstrumentation()\n        {\n            transformer.reset(instrumentation, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);\n        }\n\n        private void onEnter()\n        {\n            final Controller controller = getControllerForThread(Thread.currentThread());\n            controller.onEnter();\n        }\n    }\n\n    private static final class AgentBuilderListener extends AgentBuilder.Listener.Adapter\n    {\n        @SuppressWarnings(\"NullableProblems\")\n        public void onError(\n            final String typeName,\n            final ClassLoader classLoader,\n            final JavaModule javaModule,\n            final boolean loaded,\n            final Throwable throwable)\n        {\n            System.err.println(\"typeName=\" + typeName +\n                \", classLoader=\" + classLoader +\n                \", javaModule=\" + javaModule +\n                \", loaded=\" + loaded);\n            throwable.printStackTrace(System.err);\n        }\n    }\n\n    public static final class Controller\n    {\n        private static final int OPEN = 0;\n        private static final int BLOCKING = 1;\n        private static final int BLOCKED = 2;\n        private final AtomicInteger state = new AtomicInteger(OPEN);\n\n        private void onEnter()\n        {\n            if (state.compareAndSet(BLOCKING, BLOCKED))\n            {\n                while (state.get() == BLOCKED)\n                {\n                    Tests.sleep(1);\n                }\n            }\n        }\n\n        public void blockNextEntry()\n        {\n            if (!state.compareAndSet(OPEN, BLOCKING))\n            {\n                throw new IllegalStateException(\"expected OPEN state\");\n            }\n        }\n\n        public void awaitBlocked()\n        {\n            if (state.get() == OPEN)\n            {\n                throw new IllegalStateException(\"expected BLOCKING or BLOCKED state\");\n            }\n\n            while (state.get() != BLOCKED)\n            {\n                Tests.sleep(1);\n            }\n        }\n\n        public void release()\n        {\n            state.set(OPEN);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/MultiClusteredServicesTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.agrona.DirectBuffer;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass MultiClusteredServicesTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void setUp()\n    {\n    }\n\n    static final class Service extends TestNode.TestService\n    {\n        final AtomicLong count = new AtomicLong(0);\n\n        public void onSessionMessage(\n            final ClientSession session,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            count.incrementAndGet();\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldSupportMultipleServicesPerNode()\n    {\n        final Service serviceA = new Service();\n        final Service serviceB = new Service();\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withServiceSupplier((i) -> new TestNode.TestService[]{ serviceA, serviceB })\n            .start(3);\n        systemTestWatcher.cluster(cluster);\n\n        cluster.connectClient();\n        cluster.sendMessages(3);\n\n        Tests.awaitValue(serviceA.count, 3);\n        Tests.awaitValue(serviceB.count, 3);\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 1, 2 })\n    @InterruptAfter(40)\n    void shouldContinueFromLogIfSnapshotThrowsException(final int failedServiceCount)\n    {\n        final TestNode.TestService[][] clusterTestServices =\n            {\n                { new TestNode.TestService(), new TestNode.TestService() },\n                { new TestNode.TestService(), new TestNode.TestService() },\n                { new TestNode.TestService(), new TestNode.TestService() }\n            };\n\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withServiceSupplier((i) -> clusterTestServices[i])\n            .start(3);\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader0 = cluster.awaitLeader();\n\n        final int messageCount = 3;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitServicesMessageCount(messageCount);\n\n        for (final TestNode.TestService[] testServices : clusterTestServices)\n        {\n            for (int i = 0; i < failedServiceCount; i++)\n            {\n                testServices[i].failNextSnapshot(true);\n            }\n        }\n\n        cluster.takeSnapshot(leader0);\n        cluster.sendMessages(messageCount);\n        cluster.awaitServicesMessageCount(messageCount * 2);\n\n        cluster.awaitSnapshotCount(0);\n        cluster.awaitServiceErrors(failedServiceCount);\n\n        Tests.sleep(1_000);\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n        cluster.reconnectClient();\n\n        cluster.sendMessages(messageCount);\n        cluster.awaitServicesMessageCount(messageCount * 3);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/MultiModuleSharedDriverTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.CommonContext;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.archive.ArchivingMediaDriver;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.EgressListener;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.NameResolver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.DataCollector;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.Tests;\nimport io.aeron.test.cluster.StubClusteredService;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.driver.RedirectingNameResolver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableReference;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass MultiModuleSharedDriverTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(20)\n    @SuppressWarnings({ \"try\", \"methodlength\" })\n    void shouldSupportTwoSingleNodeClusters()\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .threadingMode(ThreadingMode.SHARED)\n            .nameResolver(new RedirectingNameResolver(TestCluster.DEFAULT_NODE_MAPPINGS))\n            .dirDeleteOnShutdown(false)\n            .dirDeleteOnStart(true);\n\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .archiveDir(new File(SystemUtil.tmpDirName(), \"archive\"))\n            .recordingEventsEnabled(false)\n            .deleteArchiveOnStart(true);\n\n        try (ArchivingMediaDriver ignore = ArchivingMediaDriver.launch(driverCtx, archiveCtx))\n        {\n            final ConsensusModule.Context moduleCtx0 = TestContexts.localhostConsensusModule()\n                .clusterId(0)\n                .deleteDirOnStart(true)\n                .clusterDir(new File(SystemUtil.tmpDirName(), \"cluster-0-0\"))\n                .logChannel(\"aeron:ipc?term-length=64k\")\n                .logStreamId(100)\n                .serviceStreamId(104)\n                .consensusModuleStreamId(105)\n                .ingressChannel(\"aeron:udp?endpoint=localhost:9020\")\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\");\n\n            final ClusteredServiceContainer.Context containerCtx0 = new ClusteredServiceContainer.Context()\n                .clusterId(moduleCtx0.clusterId())\n                .clusteredService(new EchoService())\n                .clusterDir(moduleCtx0.clusterDir())\n                .serviceStreamId(moduleCtx0.serviceStreamId())\n                .consensusModuleStreamId(moduleCtx0.consensusModuleStreamId());\n\n            final ConsensusModule.Context moduleCtx1 = TestContexts.localhostConsensusModule()\n                .clusterId(1)\n                .deleteDirOnStart(true)\n                .clusterDir(new File(SystemUtil.tmpDirName(), \"cluster-0-1\"))\n                .logChannel(\"aeron:ipc?term-length=64k\")\n                .logStreamId(200)\n                .serviceStreamId(204)\n                .consensusModuleStreamId(205)\n                .ingressChannel(\"aeron:udp?endpoint=localhost:9021\")\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\");\n\n            final ClusteredServiceContainer.Context containerCtx1 = new ClusteredServiceContainer.Context()\n                .clusteredService(new EchoService())\n                .clusterDir(moduleCtx1.clusterDir())\n                .serviceStreamId(moduleCtx1.serviceStreamId())\n                .consensusModuleStreamId(moduleCtx1.consensusModuleStreamId())\n                .clusterId(moduleCtx1.clusterId());\n\n            ConsensusModule consensusModule0 = null;\n            ClusteredServiceContainer container0 = null;\n            ConsensusModule consensusModule1 = null;\n            ClusteredServiceContainer container1 = null;\n            AeronCluster client0 = null;\n            AeronCluster client1 = null;\n\n            try\n            {\n                consensusModule0 = ConsensusModule.launch(moduleCtx0);\n                consensusModule1 = ConsensusModule.launch(moduleCtx1);\n\n                container0 = ClusteredServiceContainer.launch(containerCtx0);\n                container1 = ClusteredServiceContainer.launch(containerCtx1);\n\n                final MutableReference<String> egress = new MutableReference<>();\n                final EgressListener egressListener = (clusterSessionId, timestamp, buffer, offset, length, header) ->\n                    egress.set(buffer.getStringWithoutLengthAscii(offset, length));\n\n                client0 = AeronCluster.connect(new AeronCluster.Context()\n                    .egressListener(egressListener)\n                    .ingressChannel(moduleCtx0.ingressChannel())\n                    .egressChannel(\"aeron:udp?endpoint=localhost:0\"));\n\n                client1 = AeronCluster.connect(new AeronCluster.Context()\n                    .egressListener(egressListener)\n                    .ingressChannel(moduleCtx1.ingressChannel())\n                    .egressChannel(\"aeron:udp?endpoint=localhost:0\"));\n\n                echoMessage(client0, \"Message 0\", egress);\n                echoMessage(client1, \"Message 1\", egress);\n            }\n            finally\n            {\n                systemTestWatcher.dataCollector().add(moduleCtx0.clusterDir());\n                systemTestWatcher.dataCollector().add(moduleCtx1.clusterDir());\n                CloseHelper.closeAll(client0, client1, consensusModule0, consensusModule1, container0, container1);\n            }\n        }\n        finally\n        {\n            systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n            systemTestWatcher.dataCollector().add(archiveCtx.archiveDir());\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldSupportTwoMultiNodeClusters()\n    {\n        try (MultiClusterNode node0 = new MultiClusterNode(0, systemTestWatcher.dataCollector());\n            MultiClusterNode node1 = new MultiClusterNode(1, systemTestWatcher.dataCollector()))\n        {\n            final MutableReference<String> egress = new MutableReference<>();\n            final EgressListener egressListener = (clusterSessionId, timestamp, buffer, offset, length, header) ->\n                egress.set(buffer.getStringWithoutLengthAscii(offset, length));\n\n            try (\n                AeronCluster client0 = AeronCluster.connect(new AeronCluster.Context()\n                    .aeronDirectoryName(node0.archivingMediaDriver.mediaDriver().aeronDirectoryName())\n                    .egressListener(egressListener)\n                    .ingressChannel(\"aeron:udp?term-length=64k\")\n                    .ingressEndpoints(TestCluster.ingressEndpoints(\n                        node0.consensusModule0.context().clusterId(), 0, 2, 2))\n                    .egressChannel(\"aeron:udp?endpoint=localhost:0\"));\n                AeronCluster client1 = AeronCluster.connect(new AeronCluster.Context()\n                    .aeronDirectoryName(node1.archivingMediaDriver.mediaDriver().aeronDirectoryName())\n                    .egressListener(egressListener)\n                    .ingressChannel(\"aeron:udp?term-length=64k\")\n                    .ingressEndpoints(TestCluster.ingressEndpoints(\n                        node1.consensusModule1.context().clusterId(), 0, 2, 2))\n                    .egressChannel(\"aeron:udp?endpoint=localhost:0\")))\n            {\n                echoMessage(client0, \"Message 0\", egress);\n                echoMessage(client1, \"Message 1\", egress);\n            }\n        }\n    }\n\n    private void echoMessage(final AeronCluster client, final String message, final MutableReference<String> egress)\n    {\n        final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer();\n        final int messageLength = buffer.putStringWithoutLengthAscii(0, message);\n\n        while (client.offer(buffer, 0, messageLength) < 0)\n        {\n            Tests.yield();\n        }\n\n        egress.set(null);\n        while (null == egress.get())\n        {\n            Tests.yield();\n            client.pollEgress();\n        }\n\n        assertEquals(message, egress.get());\n    }\n\n    static class EchoService extends StubClusteredService\n    {\n        public void onSessionMessage(\n            final ClientSession session,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            echoMessage(session, buffer, offset, length);\n        }\n    }\n\n    static class MultiClusterNode implements AutoCloseable\n    {\n        final int nodeId;\n        final DataCollector dataCollector;\n        final ArchivingMediaDriver archivingMediaDriver;\n        final NameResolver nameResolver;\n\n        final ConsensusModule consensusModule0;\n        final ClusteredServiceContainer container0;\n        AeronCluster client0;\n\n        final ConsensusModule consensusModule1;\n        final ClusteredServiceContainer container1;\n        AeronCluster client1;\n\n        MultiClusterNode(final int nodeId, final DataCollector dataCollector)\n        {\n            this.nodeId = nodeId;\n            this.dataCollector = dataCollector;\n            nameResolver = new RedirectingNameResolver(TestCluster.DEFAULT_NODE_MAPPINGS);\n\n            final MediaDriver.Context driverCtx = new MediaDriver.Context()\n                .aeronDirectoryName(CommonContext.generateRandomDirName())\n                .threadingMode(ThreadingMode.SHARED)\n                .nameResolver(nameResolver)\n                .dirDeleteOnStart(true);\n\n            final Archive.Context archiveCtx = TestContexts.localhostArchive()\n                .threadingMode(ArchiveThreadingMode.SHARED)\n                .archiveDir(new File(SystemUtil.tmpDirName(), \"archive-\" + nodeId))\n                .controlChannel(\"aeron:udp?endpoint=localhost:801\" + nodeId)\n                .recordingEventsEnabled(false)\n                .deleteArchiveOnStart(true);\n\n            archivingMediaDriver = ArchivingMediaDriver.launch(driverCtx, archiveCtx);\n            dataCollector.add(archivingMediaDriver.archive().context().archiveDir());\n            dataCollector.add(archivingMediaDriver.mediaDriver().context().aeronDirectory());\n\n            consensusModule0 = consensusModule(0, driverCtx.aeronDirectoryName());\n            dataCollector.add(consensusModule0.context().clusterDir());\n            container0 = container(consensusModule0.context());\n\n            consensusModule1 = consensusModule(1, driverCtx.aeronDirectoryName());\n            dataCollector.add(consensusModule1.context().clusterDir());\n            container1 = container(consensusModule1.context());\n        }\n\n        public void close()\n        {\n            CloseHelper.closeAll(\n                client0,\n                consensusModule0,\n                container0,\n                client1,\n                consensusModule1,\n                container1,\n                archivingMediaDriver);\n        }\n\n        ConsensusModule consensusModule(final int clusterId, final String aeronDirectoryName)\n        {\n            final int nodeOffset = (clusterId * 100) + (nodeId * 10);\n            final String ingressChannelWithInvalidEndpointFormatToBeRemovedByNameResolver =\n                \"aeron:udp?term-length=64k|endpoint=node\" + nodeId + \":2\" + clusterId + \"11\" + nodeId;\n            final ConsensusModule.Context ctx = new ConsensusModule.Context()\n                .clusterMemberId(nodeId)\n                .clusterId(clusterId)\n                .deleteDirOnStart(true)\n                .aeronDirectoryName(aeronDirectoryName)\n                .clusterDir(new File(SystemUtil.tmpDirName(), \"cluster-\" + nodeId + \"-\" + clusterId))\n                .clusterMembers(TestCluster.clusterMembers(clusterId, 0, 2, 2, false))\n                .logChannel(\"aeron:udp?term-length=64k\")\n                .serviceStreamId(104 + nodeOffset)\n                .consensusModuleStreamId(105 + nodeOffset)\n                .ingressChannel(ingressChannelWithInvalidEndpointFormatToBeRemovedByNameResolver)\n                .replicationChannel(\"aeron:udp?endpoint=localhost:0\");\n\n            return ConsensusModule.launch(ctx);\n        }\n\n        ClusteredServiceContainer container(final ConsensusModule.Context moduleCtx)\n        {\n            final ClusteredServiceContainer.Context ctx = new ClusteredServiceContainer.Context()\n                .clusterId(moduleCtx.clusterId())\n                .clusteredService(new EchoService())\n                .aeronDirectoryName(moduleCtx.aeronDirectoryName())\n                .clusterDir(moduleCtx.clusterDir())\n                .serviceStreamId(moduleCtx.serviceStreamId())\n                .consensusModuleStreamId(moduleCtx.consensusModuleStreamId());\n\n            return ClusteredServiceContainer.launch(ctx);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/MultiNodeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass MultiNodeTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(20)\n    void shouldElectAppointedLeaderWithThreeNodesWithNoReplayNoSnapshot()\n    {\n        final int appointedLeaderIndex = 1;\n\n        final TestCluster cluster = aCluster().withStaticNodes(3).withAppointedLeader(appointedLeaderIndex).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        assertEquals(appointedLeaderIndex, leader.index());\n        assertEquals(Cluster.Role.LEADER, leader.role());\n        assertEquals(Cluster.Role.FOLLOWER, cluster.node(0).role());\n        assertEquals(Cluster.Role.FOLLOWER, cluster.node(2).role());\n    }\n\n    @Test\n    @InterruptAfter(20)\n    @SlowTest\n    void shouldReplayWithAppointedLeaderWithThreeNodesWithNoSnapshot()\n    {\n        final int appointedLeaderIndex = 1;\n\n        final TestCluster cluster = aCluster().withStaticNodes(3).withAppointedLeader(appointedLeaderIndex).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        assertEquals(appointedLeaderIndex, leader.index());\n        assertEquals(Cluster.Role.LEADER, leader.role());\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount);\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n\n        cluster.awaitLeader();\n        cluster.awaitServicesMessageCount(messageCount);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    @SlowTest\n    void shouldCatchUpWithAppointedLeaderWithThreeNodesWithNoSnapshot()\n    {\n        final int appointedLeaderIndex = 1;\n\n        final TestCluster cluster = aCluster().withStaticNodes(3).withAppointedLeader(appointedLeaderIndex).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        assertEquals(appointedLeaderIndex, leader.index());\n        assertEquals(Cluster.Role.LEADER, leader.role());\n\n        final int preCatchupMessageCount = 5;\n        final int postCatchupMessageCount = 10;\n        final int totalMessageCount = preCatchupMessageCount + postCatchupMessageCount;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(preCatchupMessageCount);\n\n        cluster.stopNode(cluster.node(0));\n\n        cluster.sendAndAwaitMessages(postCatchupMessageCount, totalMessageCount);\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n\n        cluster.awaitLeader();\n        cluster.awaitServicesMessageCount(totalMessageCount);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldConnectClientOverIpc()\n    {\n        final AeronCluster.Context clientCtx = new AeronCluster.Context();\n\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int numMessages = 10;\n        assertNotNull(cluster.connectIpcClient(clientCtx, leader.mediaDriver().aeronDirectoryName()));\n        cluster.sendMessages(numMessages);\n        cluster.awaitResponseMessageCount(numMessages);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"9020\", \"0\" })\n    @InterruptAfter(20)\n    void shouldConnectClientUsingResolvedResponsePort(final String responsePort)\n    {\n        final AeronCluster.Context clientCtx = new AeronCluster.Context()\n            .ingressChannel(\"aeron:udp?term-length=64k\")\n            .egressChannel(\"aeron:udp?term-length=64k|endpoint=localhost:\" + responsePort);\n\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final int numMessages = 10;\n        cluster.connectClient(clientCtx);\n        cluster.sendMessages(numMessages);\n        cluster.awaitResponseMessageCount(numMessages);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/OffsetMillisecondClusterClock.java",
    "content": "/*\n * Copyright 2026 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.service.ClusterClock;\nimport org.agrona.concurrent.EpochClock;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\n\nfinal class OffsetMillisecondClusterClock implements ClusterClock\n{\n    private static final VarHandle OFFSET_VH;\n\n    static\n    {\n        try\n        {\n            OFFSET_VH = MethodHandles.lookup()\n                .findVarHandle(OffsetMillisecondClusterClock.class, \"offset\", long.class);\n        }\n        catch (final ReflectiveOperationException ex)\n        {\n            throw new ExceptionInInitializerError(ex);\n        }\n    }\n\n    private final EpochClock epochClock;\n    @SuppressWarnings(\"unused\")\n    private volatile long offset;\n\n    OffsetMillisecondClusterClock(final EpochClock epochClock)\n    {\n        this.epochClock = epochClock;\n    }\n\n    public long time()\n    {\n        return epochClock.time() + (long)OFFSET_VH.getAcquire(this);\n    }\n\n    void addOffset(final long deltaMs)\n    {\n        OFFSET_VH.getAndAddRelease(this, deltaMs);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/OffsetMillisecondClusterClockTest.java",
    "content": "/*\n * Copyright 2026 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport org.agrona.collections.MutableLong;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass OffsetMillisecondClusterClockTest\n{\n    @Test\n    void shouldOffsetDelegateTime()\n    {\n        final MutableLong time = new MutableLong(1000);\n        final OffsetMillisecondClusterClock clock = new OffsetMillisecondClusterClock(time::get);\n        assertEquals(TimeUnit.MILLISECONDS, clock.timeUnit());\n        assertEquals(1000, clock.time());\n        clock.addOffset(7);\n        assertEquals(1007, clock.time());\n        time.increment();\n        assertEquals(1008, clock.time());\n        clock.addOffset(3);\n        time.increment();\n        assertEquals(1012, clock.time());\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/RacingCatchupClusterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.test.*;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport net.bytebuddy.asm.Advice;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.List;\nimport java.util.concurrent.Exchanger;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\n\n@SlowTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass RacingCatchupClusterTest\n{\n    static final Exchanger<String> EXCHANGER = new Exchanger<>();\n    private static ClusterInstrumentor clusterInstrumentor;\n\n    @BeforeAll\n    static void beforeAll()\n    {\n        clusterInstrumentor = new ClusterInstrumentor(\n            StallFollowerCatchupIntercept.class, \"Election\", \"followerCatchupInit\");\n    }\n\n    @AfterAll\n    static void afterAll()\n    {\n        clusterInstrumentor.reset();\n    }\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    static void tagAndWaitForTag()\n    {\n        try\n        {\n            EXCHANGER.exchange(\"a\");\n            EXCHANGER.exchange(\"a\");\n        }\n        catch (final InterruptedException ignore)\n        {\n        }\n    }\n\n    static void tag()\n    {\n        try\n        {\n            EXCHANGER.exchange(\"a\");\n        }\n        catch (final InterruptedException ignore)\n        {\n        }\n    }\n\n    public static class StallFollowerCatchupIntercept\n    {\n        static boolean shouldStall = true;\n\n        @Advice.OnMethodEnter\n        static void followerCatchupInit(final long nowNs)\n        {\n            tagAndWaitForTag();\n        }\n    }\n\n    @Test\n    @InterruptAfter(40)\n    @Disabled\n    void shouldCatchupIfLogPositionMovesForwardBeforeFollowersCommitPositionWhenCatchingUpNodeIsOnlyFollower()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n\n        systemTestWatcher.cluster(cluster);\n\n        final int messageCount = 10;\n        int totalMessages = 0;\n        final TestNode oldLeader = cluster.awaitLeader();\n\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        totalMessages += messageCount;\n        cluster.awaitResponseMessageCount(totalMessages);\n        cluster.awaitServicesMessageCount(totalMessages);\n\n        cluster.stopNode(oldLeader);\n\n        cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        cluster.connectClient();\n\n        for (final TestNode follower : followers)\n        {\n            cluster.stopNode(follower);\n        }\n        Tests.sleep(1);\n\n        cluster.sendMessages(messageCount);\n        totalMessages += messageCount;\n\n        cluster.startStaticNode(followers.get(0).index(), false);\n\n        tag();\n\n        cluster.sendMessages(messageCount);\n        totalMessages += messageCount;\n\n        tag();\n\n        cluster.awaitResponseMessageCount(totalMessages);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/RecoverAfterFailedCatchupClusterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport net.bytebuddy.asm.Advice;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.List;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static io.aeron.test.cluster.TestCluster.awaitElectionClosed;\n\n@SlowTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass RecoverAfterFailedCatchupClusterTest\n{\n    static ClusterInstrumentor clusterInstrumentor;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeAll\n    static void beforeAll()\n    {\n        clusterInstrumentor = new ClusterInstrumentor(\n            FailFirstFollowerCatchup.class, \"Election\", \"state\");\n    }\n\n    public static class FailFirstFollowerCatchup\n    {\n        static boolean shouldStall = true;\n\n        @Advice.OnMethodEnter\n        static void state(final ElectionState state, final long nowNs)\n        {\n            if (ElectionState.FOLLOWER_CATCHUP == state && shouldStall)\n            {\n                shouldStall = false;\n                throw new ClusterException(\"For catchup failure\");\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldCatchupFromEmptyLog()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching((s) -> s.contains(\"For catchup failure\"));\n        systemTestWatcher.ignoreErrorsMatching((s) -> s.contains(\"failed to join catchup log\"));\n\n        cluster.awaitLeader();\n        final List<TestNode> followers = cluster.followers();\n        TestNode follower = followers.get(1);\n\n        awaitElectionClosed(follower);\n        cluster.stopNode(follower);\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n\n        follower = cluster.startStaticNode(follower.index(), true);\n        cluster.awaitServiceMessageCount(follower, messageCount);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/ServiceIpcIngressMessageTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.test.*;\nimport io.aeron.test.cluster.ClusterTests;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.Int2IntCounterMap;\nimport org.agrona.collections.IntArrayList;\nimport org.agrona.collections.IntHashSet;\nimport org.agrona.collections.LongArrayList;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.Collections;\nimport java.util.function.IntFunction;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static io.aeron.test.cluster.TestCluster.awaitElectionClosed;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ServiceIpcIngressMessageTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @AfterEach\n    void tearDown()\n    {\n        TestNode.MessageTrackingService.delaySessionMessageProcessing(false);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldEchoServiceIpcMessages()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        cluster.connectClient();\n        final int messageLength = cluster.msgBuffer().putStringWithoutLengthAscii(\n            0, ClusterTests.ECHO_SERVICE_IPC_INGRESS_MSG);\n\n        final int messageCount = 10;\n        for (int i = 0; i < messageCount; i++)\n        {\n            cluster.pollUntilMessageSent(messageLength);\n        }\n\n        cluster.awaitResponseMessageCount(messageCount);\n        cluster.awaitServicesMessageCount(messageCount);\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(60)\n    void shouldProcessServiceMessagesWithoutDuplicates()\n    {\n        final IntFunction<TestNode.TestService[]> serviceSupplier =\n            (i) -> new TestNode.TestService[]\n            {\n                new TestNode.MessageTrackingService(1, i),\n                new TestNode.MessageTrackingService(2, i),\n                new TestNode.MessageTrackingService(3, i)\n            };\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withTimerServiceSupplier(new PriorityHeapTimerServiceSupplier())\n            .withServiceSupplier(serviceSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        final int serviceCount = cluster.node(0).services().length;\n\n        TestNode oldLeader = cluster.awaitLeader();\n        cluster.connectClient();\n\n        final ExpandableArrayBuffer msgBuffer = cluster.msgBuffer();\n        int messageCount = 0;\n        for (int i = 0; i < 50; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitMessageCounts(cluster, messageCount);\n\n        cluster.stopNode(oldLeader);\n\n        final TestNode newLeader = cluster.awaitLeader(oldLeader.index());\n        final TestNode follower = cluster.node(3 - oldLeader.index() - newLeader.index());\n        assertEquals(Cluster.Role.FOLLOWER, follower.role());\n        cluster.reconnectClient();\n        for (int i = 0; i < 30; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitMessageCounts(cluster, messageCount);\n\n        oldLeader = cluster.startStaticNode(oldLeader.index(), false);\n        awaitElectionClosed(oldLeader);\n        assertEquals(Cluster.Role.FOLLOWER, oldLeader.role());\n        awaitMessageCounts(cluster, oldLeader, messageCount);\n\n        assertTrackedMessages(cluster, -1, messageCount);\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(60)\n    void shouldProcessServiceMessagesWithoutDuplicatesDuringFailoverWithUncommittedPendingServiceMessages()\n    {\n        final IntFunction<TestNode.TestService[]> serviceSupplier =\n            (i) -> new TestNode.TestService[]\n            {\n                new TestNode.MessageTrackingService(1, i),\n                new TestNode.MessageTrackingService(2, i),\n                new TestNode.MessageTrackingService(3, i)\n            };\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withTimerServiceSupplier(new PriorityHeapTimerServiceSupplier())\n            .withServiceSupplier(serviceSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        systemTestWatcher.ignoreErrorsMatching((error) -> error.contains(\"publication is not connected\"));\n\n        final TestNode oldLeader = cluster.awaitLeader();\n        cluster.connectClient();\n\n        final int ingressMessageCount = 50;\n        final int expectedServiceMessageCount = computeExpectedMessageCount(\n            oldLeader.services().length, ingressMessageCount);\n\n        final ExpandableArrayBuffer msgBuffer = cluster.msgBuffer();\n        for (int i = 0; i < ingressMessageCount; i++)\n        {\n            msgBuffer.putInt(0, i + i, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n\n        stopLeaderWithMessagesInFlight(cluster, oldLeader);\n\n        cluster.awaitLeader();\n        cluster.awaitServicesMessageCount(expectedServiceMessageCount);\n\n        for (int i = 0; i < 3; i++)\n        {\n            final TestNode node = cluster.node(i);\n            final TestNode.TestService[] services = node.services();\n            for (final TestNode.TestService service : services)\n            {\n                final TestNode.MessageTrackingService trackingService1 = (TestNode.MessageTrackingService)service;\n\n                final IntArrayList actualServiceMessages = trackingService1.serviceMessages();\n\n                assertNoDuplicates(node, actualServiceMessages);\n            }\n        }\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(40)\n    void shouldProcessServiceMessagesAndTimersWithoutDuplicatesWhenLeaderServicesAreStopped()\n    {\n        final IntFunction<TestNode.TestService[]> serviceSupplier =\n            (i) -> new TestNode.TestService[]\n            {\n                new TestNode.MessageTrackingService(1, i),\n                new TestNode.MessageTrackingService(2, i)\n            };\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withTimerServiceSupplier(new PriorityHeapTimerServiceSupplier())\n            .withServiceSupplier(serviceSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        final int serviceCount = cluster.node(0).services().length;\n\n        TestNode oldLeader = cluster.awaitLeader();\n        cluster.connectClient();\n\n        final ExpandableArrayBuffer msgBuffer = cluster.msgBuffer();\n        int messageCount = 0;\n        for (int i = 0; i < 50; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitMessageCounts(cluster, messageCount);\n\n        oldLeader.stopServiceContainers(); // stop services to cause a new election\n\n        final TestNode newLeader = cluster.awaitLeader(oldLeader.index());\n        final TestNode follower = cluster.node(3 - oldLeader.index() - newLeader.index());\n        assertEquals(Cluster.Role.FOLLOWER, follower.role());\n        cluster.awaitNodeState(oldLeader, node -> Cluster.Role.FOLLOWER == node.role());\n        cluster.reconnectClient();\n        for (int i = 0; i < 30; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitMessageCounts(cluster, newLeader, messageCount);\n        awaitMessageCounts(cluster, follower, messageCount);\n        assertTrackedMessages(cluster, oldLeader.index(), messageCount);\n\n        cluster.stopNode(oldLeader);\n        oldLeader = cluster.startStaticNode(oldLeader.index(), false);\n        awaitMessageCounts(cluster, oldLeader, messageCount);\n\n        assertTrackedMessages(cluster, -1, messageCount);\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(30)\n    void shouldProcessServiceMessagesWithoutDuplicatesAfterAFullClusterRestart()\n    {\n        final IntFunction<TestNode.TestService[]> serviceSupplier =\n            (i) -> new TestNode.TestService[]\n            {\n                new TestNode.MessageTrackingService(1, i)\n            };\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withTimerServiceSupplier(new PriorityHeapTimerServiceSupplier())\n            .withServiceSupplier(serviceSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        final int serviceCount = cluster.node(0).services().length;\n\n        cluster.awaitLeader();\n        cluster.connectClient();\n\n        final ExpandableArrayBuffer msgBuffer = cluster.msgBuffer();\n        int messageCount = 0;\n        for (int i = 0; i < 10; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitMessageCounts(cluster, messageCount);\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n\n        cluster.reconnectClient();\n        for (int i = 0; i < 20; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitMessageCounts(cluster, messageCount);\n\n        assertTrackedMessages(cluster, -1, messageCount);\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(60)\n    void shouldProcessServiceMessagesWithoutDuplicatesWhenClusterIsRestartedAfterTakingASnapshot()\n    {\n        final IntFunction<TestNode.TestService[]> serviceSupplier =\n            (i) -> new TestNode.TestService[]\n            {\n                new TestNode.MessageTrackingService(1, i),\n                new TestNode.MessageTrackingService(2, i)\n            };\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withTimerServiceSupplier(new PriorityHeapTimerServiceSupplier())\n            .withServiceSupplier(serviceSupplier)\n            .start();\n        systemTestWatcher.cluster(cluster);\n        final int serviceCount = cluster.node(0).services().length;\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n\n        final ExpandableArrayBuffer msgBuffer = cluster.msgBuffer();\n        int messageCount = 0;\n        TestNode.MessageTrackingService.delaySessionMessageProcessing(true);\n        for (int i = 0; i < 1999; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n        TestNode.MessageTrackingService.delaySessionMessageProcessing(false);\n\n        for (int i = 0; i < 567; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitMessageCounts(cluster, messageCount);\n        assertTrackedMessages(cluster, -1, messageCount);\n\n        final TestNode.MessageTrackingService leaderTrackingService =\n            (TestNode.MessageTrackingService)leader.services()[0];\n        final IntArrayList clientMessagesBeforeRestart = leaderTrackingService.clientMessages();\n        final IntArrayList serviceMessagesBeforeRestart = leaderTrackingService.serviceMessages();\n        final LongArrayList timersBeforeRestart = leaderTrackingService.timers();\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n        final TestNode newLeader = cluster.awaitLeader();\n        final TestNode.MessageTrackingService newLeaderTrackingService =\n            (TestNode.MessageTrackingService)newLeader.services()[0];\n        assertEquals(clientMessagesBeforeRestart, newLeaderTrackingService.clientMessages());\n        assertEquals(serviceMessagesBeforeRestart, newLeaderTrackingService.serviceMessages());\n        assertEquals(timersBeforeRestart, newLeaderTrackingService.timers());\n        assertTrackedMessages(cluster, -1, messageCount);\n\n        cluster.reconnectClient();\n        for (int i = 0; i < 20; i++)\n        {\n            msgBuffer.putInt(0, ++messageCount, LITTLE_ENDIAN);\n            cluster.pollUntilMessageSent(SIZE_OF_INT);\n        }\n        cluster.awaitResponseMessageCount(messageCount * serviceCount);\n        awaitMessageCounts(cluster, messageCount);\n        assertTrackedMessages(cluster, -1, messageCount);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldHandleServiceMessagesMissedOnTheFollowerWhenSnapshot()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        int messageLength = cluster.msgBuffer().putStringWithoutLengthAscii(\n            0, ClusterTests.ECHO_SERVICE_IPC_INGRESS_MSG);\n\n        final int messageCount = 5;\n        for (int i = 0; i < messageCount; i++)\n        {\n            cluster.pollUntilMessageSent(messageLength);\n        }\n\n        messageLength = cluster.msgBuffer().putStringWithoutLengthAscii(\n            0, ClusterTests.ECHO_SERVICE_IPC_INGRESS_MSG_SKIP_FOLLOWER);\n\n        cluster.pollUntilMessageSent(messageLength);\n\n        messageLength = cluster.msgBuffer().putStringWithoutLengthAscii(\n            0, ClusterTests.ECHO_SERVICE_IPC_INGRESS_MSG);\n\n        for (int i = 0; i < messageCount; i++)\n        {\n            cluster.pollUntilMessageSent(messageLength);\n        }\n\n        cluster.awaitResponseMessageCount(2 * messageCount + 1);\n        cluster.awaitServicesMessageCount(2 * messageCount + 1);\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.stopAllNodes();\n        cluster.restartAllNodes(false);\n        cluster.awaitLeader();\n    }\n\n    private static void awaitMessageCounts(final TestCluster cluster, final int messageCount)\n    {\n        for (int i = 0; i < 3; i++)\n        {\n            final TestNode node = cluster.node(i);\n            if (null != node && !node.isClosed())\n            {\n                awaitMessageCounts(cluster, node, messageCount);\n            }\n        }\n    }\n\n    private static void awaitMessageCounts(final TestCluster cluster, final TestNode node, final int messageCount)\n    {\n        final TestNode.TestService[] services = node.services();\n        for (final TestNode.TestService service : services)\n        {\n            // 1 client message + 3 service messages x number of services\n            cluster.awaitServiceMessageCount(node, service, messageCount + (messageCount * 3 * services.length));\n            // 2 timers x number of services\n            cluster.awaitTimerEventCount(node, service, messageCount * 2 * services.length);\n        }\n    }\n\n    private static void assertTrackedMessages(\n        final TestCluster cluster, final int excludeIndex, final int messageCount)\n    {\n        final TestNode leader = cluster.findLeader();\n        assertNotEquals(leader.index(), excludeIndex);\n\n        final TestNode.TestService[] leaderServices = leader.services();\n        final int numberOfTrackingServices = leaderServices.length;\n        final TestNode.MessageTrackingService trackingService = (TestNode.MessageTrackingService)leaderServices[0];\n        final IntArrayList clientMessages = trackingService.clientMessages();\n        final IntArrayList serviceMessages = trackingService.serviceMessages();\n        final LongArrayList timers = trackingService.timers();\n\n        assertEquals(\n            messageCount,\n            clientMessages.size(),\n            () -> \"Invalid client message count on leader: \" + leader);\n        assertEquals(\n            messageCount * 3 * numberOfTrackingServices,\n            serviceMessages.size(),\n            () -> \"Invalid service message count on leader: \" + leader);\n        assertEquals(messageCount * 2 * numberOfTrackingServices,\n            timers.size(),\n            () -> \"Invalid timer event count on leader: \" + leader);\n\n        for (int i = 0; i < 3; i++)\n        {\n            if (excludeIndex != i)\n            {\n                final TestNode node = cluster.node(i);\n                assertTrackedServiceState(node, clientMessages, serviceMessages, timers);\n            }\n        }\n    }\n\n    private static void assertTrackedServiceState(\n        final TestNode node,\n        final IntArrayList expectedClientMessages,\n        final IntArrayList expectedServiceMessages,\n        final LongArrayList expectedTimers)\n    {\n        final TestNode.TestService[] services = node.services();\n        for (final TestNode.TestService service : services)\n        {\n            final TestNode.MessageTrackingService trackingService = (TestNode.MessageTrackingService)service;\n            final IntArrayList actualClientMessages = trackingService.clientMessages();\n            if (!expectedClientMessages.equals(actualClientMessages))\n            {\n                fail(\"memberId=\" + node.index() + \", role=\" + node.role() + \": Client messages diverged: expected=\" +\n                    expectedClientMessages.size() + \", actual=\" + actualClientMessages.size());\n            }\n\n            final IntArrayList actualServiceMessages = trackingService.serviceMessages();\n            if (!expectedServiceMessages.equals(actualServiceMessages))\n            {\n                fail(\"memberId=\" + node.index() + \", role=\" + node.role() + \": Service messages diverged: expected=\" +\n                    expectedServiceMessages.size() + \", actual=\" + actualServiceMessages.size());\n            }\n\n            final LongArrayList actualTimers = trackingService.timers();\n            if (!expectedTimers.equals(actualTimers))\n            {\n                fail(\"memberId=\" + node.index() + \", role=\" + node.role() + \": Timers diverged: expected=\" +\n                    expectedTimers.size() + \", actual=\" + actualTimers.size());\n            }\n\n            assertNoDuplicates(node, actualServiceMessages);\n        }\n    }\n\n    private static void assertNoDuplicates(final TestNode node, final IntArrayList actualServiceMessages)\n    {\n        final IntHashSet set = new IntHashSet(actualServiceMessages.size());\n        set.addAll(actualServiceMessages);\n\n        if (set.size() != actualServiceMessages.size())\n        {\n            final Int2IntCounterMap messageCounts = new Int2IntCounterMap(0);\n            for (final int messageId : actualServiceMessages)\n            {\n                messageCounts.incrementAndGet(messageId);\n            }\n\n            final IntArrayList duplicateMessageIds = new IntArrayList();\n            messageCounts.forEach((messageId, count) ->\n            {\n                if (count > 1)\n                {\n                    duplicateMessageIds.add(messageId);\n                }\n            });\n\n            Collections.sort(duplicateMessageIds);\n\n            fail(\"memberId=\" + node.index() + \", role=\" + node.role() + \": Duplicate messages found: \" +\n                duplicateMessageIds);\n        }\n    }\n\n    private static int computeExpectedMessageCount(final int serviceCount, final int ingressMessageCount)\n    {\n        final int echoMessageCount = 1;\n        final int totalMessagesPerIngress =\n            serviceCount * TestNode.MessageTrackingService.SERVICE_MESSAGES_PER_INGRESS + echoMessageCount;\n        return ingressMessageCount * totalMessagesPerIngress;\n    }\n\n    private static void stopLeaderWithMessagesInFlight(final TestCluster cluster, final TestNode leader)\n    {\n        cluster.awaitResponseMessageCount(1);\n        cluster.stopNode(leader);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/SingleNodeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchivingMediaDriver;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.driver.DriverConductorProxy;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.samples.archive.RecordingDescriptor;\nimport io.aeron.samples.archive.RecordingDescriptorCollector;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.TestContexts;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.Aeron.Configuration.PRE_TOUCH_MAPPED_MEMORY_PROP_NAME;\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass SingleNodeTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(20)\n    void shouldStartCluster()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(1).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        assertEquals(0, leader.index());\n        assertEquals(Cluster.Role.LEADER, leader.role());\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldNotConsiderItselfInactiveAndEnterAnElection()\n    {\n        final OffsetMillisecondClusterClock clusterClock = new OffsetMillisecondClusterClock(SystemEpochClock.INSTANCE);\n        final TestCluster cluster = aCluster()\n            .withClusterClock(clusterClock)\n            .withStaticNodes(1)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n        assertEquals(0, leader.index());\n        assertEquals(Cluster.Role.LEADER, leader.role());\n\n        final long heartbeatTimeOutNs = leader.consensusModule().context().leaderHeartbeatTimeoutNs();\n        clusterClock.addOffset(TimeUnit.NANOSECONDS.toMillis(heartbeatTimeOutNs) * 2);\n\n        final ClusterMembership clusterMembership = leader.clusterMembership();\n        assertEquals(1, clusterMembership.activeMembers.size());\n        assertEquals(0, clusterMembership.activeMembers.get(0).leadershipTermId());\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { false, true })\n    @InterruptAfter(20)\n    void shouldSendMessagesToCluster(final boolean preTouch)\n    {\n        System.setProperty(PRE_TOUCH_MAPPED_MEMORY_PROP_NAME, Boolean.toString(preTouch));\n        try\n        {\n            final TestCluster cluster = aCluster().withStaticNodes(1).start();\n            systemTestWatcher.cluster(cluster);\n\n            final TestNode leader = cluster.awaitLeader();\n\n            assertEquals(0, leader.index());\n            assertEquals(Cluster.Role.LEADER, leader.role());\n\n            cluster.connectClient();\n            cluster.sendMessages(10);\n            cluster.awaitResponseMessageCount(10);\n            cluster.awaitServiceMessageCount(leader, 10);\n        }\n        finally\n        {\n            System.clearProperty(PRE_TOUCH_MAPPED_MEMORY_PROP_NAME);\n        }\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldReplayLog()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(1).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int messageCount = 10;\n        cluster.connectClient();\n        cluster.sendMessages(messageCount);\n        cluster.awaitResponseMessageCount(messageCount);\n        cluster.awaitServiceMessageCount(leader, messageCount);\n\n        cluster.stopNode(leader);\n\n        cluster.startStaticNode(0, false);\n        final TestNode newLeader = cluster.awaitLeader();\n        cluster.awaitServiceMessageCount(newLeader, messageCount);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldReplayLogWithPaddingAtEndOfRecording()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(1).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader = cluster.awaitLeader();\n\n        final int largeMessageCount = 481;\n        cluster.connectClient();\n        cluster.sendLargeMessages(largeMessageCount);\n        cluster.awaitResponseMessageCount(largeMessageCount);\n        cluster.awaitServiceMessageCount(leader, largeMessageCount);\n        final int smallMessageCount = 8;\n        cluster.sendMessages(smallMessageCount);\n        cluster.awaitResponseMessageCount(largeMessageCount + smallMessageCount);\n        cluster.awaitServiceMessageCount(leader, largeMessageCount + smallMessageCount);\n\n        final String aeronDirectoryName = leader.mediaDriver().context().aeronDirectoryName();\n        final File archiveDir = leader.archive().context().archiveDir();\n\n        cluster.stopClient();\n        cluster.stopAllNodes();\n\n        truncateRecordingToTermLength(aeronDirectoryName, archiveDir);\n\n        cluster.startStaticNode(0, false);\n        final TestNode newLeader = cluster.awaitLeader();\n        cluster.awaitServiceMessageCount(newLeader, largeMessageCount);\n    }\n\n    @Test\n    @SlowTest\n    @InterruptAfter(20)\n    void shouldReattemptEgressSubscriptionCreationOnTransientError()\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"uses instrumentation to simulate race condition\");\n\n        final MethodCallBlocker methodCallBlocker = new MethodCallBlocker();\n\n        try\n        {\n            final TestCluster cluster = aCluster().withStaticNodes(1).start();\n            systemTestWatcher.cluster(cluster);\n\n            final TestNode leader = cluster.awaitLeader();\n\n            assertEquals(0, leader.index());\n            assertEquals(Cluster.Role.LEADER, leader.role());\n\n            cluster.clientThreadingMode(ThreadingMode.DEDICATED);\n            cluster.egressChannel(\"aeron:udp?term-length=128k|endpoint=localhost:30000|alias=egress\");\n            final AeronCluster client = cluster.connectClient();\n\n            final MethodCallBlocker.Controller receiveChannelEndpointClosedController =\n                methodCallBlocker.getControllerFor(\n                    DriverConductorProxy.class.getCanonicalName(),\n                    \"receiveChannelEndpointClosed\",\n                    \"receiver\"\n                );\n\n            receiveChannelEndpointClosedController.blockNextEntry();\n            client.close();\n            receiveChannelEndpointClosedController.awaitBlocked();\n\n            final TestMediaDriver clientDriver = cluster.startClientMediaDriver();\n            final AeronCluster.Context context = cluster.clientCtx()\n                .aeronDirectoryName(clientDriver.aeronDirectoryName());\n            try (AeronCluster.AsyncConnect asyncConnect = AeronCluster.asyncConnect(context))\n            {\n                AeronCluster client2;\n                int iterations = 0;\n                while (null == (client2 = asyncConnect.poll()))\n                {\n                    if (++iterations == 10)\n                    {\n                        receiveChannelEndpointClosedController.release();\n                    }\n                    Thread.yield();\n                }\n                CloseHelper.close(client2);\n            }\n        }\n        finally\n        {\n            methodCallBlocker.removeInstrumentation();\n        }\n    }\n\n    private void truncateRecordingToTermLength(final String aeronDirectoryName, final File archiveDir)\n    {\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDirectoryName);\n        final Archive.Context archiveCtx = TestContexts.localhostArchive()\n            .archiveDir(archiveDir);\n        final AeronArchive.Context aeronArchiveCtx = TestContexts.localhostAeronArchive()\n            .aeronDirectoryName(aeronDirectoryName);\n\n        try (ArchivingMediaDriver driver = ArchivingMediaDriver.launch(driverCtx, archiveCtx);\n            AeronArchive archive = AeronArchive.connect(aeronArchiveCtx))\n        {\n            assertNotNull(driver);\n            final RecordingDescriptorCollector recordingDescriptorCollector = new RecordingDescriptorCollector(1);\n            archive.listRecordings(0, Integer.MAX_VALUE, recordingDescriptorCollector.reset());\n            final List<RecordingDescriptor> descriptors = recordingDescriptorCollector.descriptors();\n            assertEquals(1, descriptors.size());\n            final RecordingDescriptor recordingDescriptor = descriptors.get(0);\n            assertEquals(512 * 1024, recordingDescriptor.termBufferLength());\n            assertThat(recordingDescriptor.stopPosition(), greaterThan((long)recordingDescriptor.termBufferLength()));\n            archive.truncateRecording(recordingDescriptor.recordingId(), recordingDescriptor.termBufferLength());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/StalledLeaderLogReplicationClusterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport net.bytebuddy.asm.Advice;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.LockSupport;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static io.aeron.test.cluster.TestCluster.awaitElectionClosed;\n\n@SlowTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass StalledLeaderLogReplicationClusterTest\n{\n    private static ClusterInstrumentor clusterInstrumentor;\n\n    @BeforeAll\n    static void beforeAll()\n    {\n        clusterInstrumentor = new ClusterInstrumentor(\n            StallLeaderLogReplicationIntercept.class, \"Election\", \"leaderLogReplication\");\n    }\n\n    @AfterAll\n    static void afterAll()\n    {\n        clusterInstrumentor.reset();\n    }\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    public static class StallLeaderLogReplicationIntercept\n    {\n        static boolean shouldStall = true;\n\n        @Advice.OnMethodEnter\n        static void leaderLogReplication(final long nowNs, @Advice.This final Object election)\n        {\n            if (shouldStall && 0 == ((Election)election).leadershipTermId())\n            {\n                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(2_500));\n                shouldStall = false;\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(120)\n    void shouldHandleMultipleElections()\n    {\n        final TestCluster cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        final TestNode leader0 = cluster.awaitLeader();\n\n        final int messageCount = 3;\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount, messageCount);\n\n        cluster.stopNode(leader0);\n        final TestNode leader1 = cluster.awaitLeader(leader0.index());\n        cluster.connectClient();\n        cluster.startStaticNode(leader0.index(), false);\n        awaitElectionClosed(cluster.node(leader0.index()));\n\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 2);\n\n        cluster.stopNode(leader1);\n        cluster.awaitLeader(leader1.index());\n        cluster.startStaticNode(leader1.index(), false);\n        awaitElectionClosed(cluster.node(leader1.index()));\n\n        cluster.connectClient();\n        cluster.sendAndAwaitMessages(messageCount, messageCount * 3);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/StartFromTruncatedRecordingLogTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.archive.ArchiveMarkFile;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.cluster.TestNode;\nimport org.agrona.IoUtil;\nimport org.agrona.collections.LongHashSet;\nimport org.agrona.collections.MutableInteger;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\n\nimport static io.aeron.cluster.RecordingLog.RECORDING_LOG_FILE_NAME;\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@SlowTest\n@ExtendWith({ InterruptingTestCallback.class, EventLogExtension.class })\nclass StartFromTruncatedRecordingLogTest\n{\n    private static final int MESSAGE_COUNT = 10;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private final MutableInteger responseCount = new MutableInteger();\n    private TestCluster cluster;\n\n    @Test\n    @InterruptAfter(30)\n    void shouldBeAbleToStartClusterFromTruncatedRecordingLog() throws IOException\n    {\n        cluster = aCluster().withStaticNodes(3).start();\n        systemTestWatcher.cluster(cluster);\n\n        restartClusterWithTruncatedRecordingLog();\n        assertClusterIsOperational();\n\n        restartClusterWithTruncatedRecordingLog();\n        assertClusterIsOperational();\n\n        restartClusterWithTruncatedRecordingLog();\n        assertClusterIsOperational();\n    }\n\n    private void restartClusterWithTruncatedRecordingLog() throws IOException\n    {\n        final TestNode leader = cluster.awaitLeader();\n        cluster.connectClient();\n        final int leaderMemberId = leader.index();\n        final int followerMemberIdA = cluster.followers().get(0).index();\n        final int followerMemberIdB = cluster.followers().get(1).index();\n\n        cluster.takeSnapshot(leader);\n        cluster.awaitSnapshotCount(1);\n        cluster.awaitNeutralControlToggle(leader);\n\n        cluster.stopNode(leader);\n        cluster.awaitSnapshotCount(1);\n\n        cluster.stopAllNodes();\n\n        truncateRecordingLogAndDeleteMarkFiles(leaderMemberId);\n        truncateRecordingLogAndDeleteMarkFiles(followerMemberIdA);\n        truncateRecordingLogAndDeleteMarkFiles(followerMemberIdB);\n\n        cluster.restartAllNodes(false);\n    }\n\n    private void assertClusterIsOperational()\n    {\n        cluster.awaitLeader();\n        cluster.connectClient();\n\n        final int initialCount = responseCount.get();\n        cluster.sendMessages(MESSAGE_COUNT);\n        cluster.awaitResponseMessageCount(MESSAGE_COUNT + initialCount);\n        responseCount.addAndGet(MESSAGE_COUNT);\n    }\n\n    private void truncateRecordingLogAndDeleteMarkFiles(final int index) throws IOException\n    {\n        final File consensusModuleDataDir = cluster.node(index).consensusModule().context().clusterDir();\n        final File archiveDataDir = cluster.node(index).archive().context().archiveDir();\n        final String baseDirName = consensusModuleDataDir.getParentFile().getAbsolutePath();\n\n        final File tmpRecordingFile = new File(baseDirName, RECORDING_LOG_FILE_NAME);\n        deleteFile(tmpRecordingFile);\n        deleteFile(new File(archiveDataDir, ArchiveMarkFile.FILENAME));\n        deleteFile(new File(consensusModuleDataDir, ClusterMarkFile.FILENAME));\n\n        try (RecordingLog recordingLog = new RecordingLog(consensusModuleDataDir, false))\n        {\n            final RecordingLog.Entry lastTermEntry = recordingLog.findLastTerm();\n            if (null == lastTermEntry)\n            {\n                throw new IllegalStateException(\"no term found in recording log\");\n            }\n\n            try (RecordingLog newRecordingLog = new RecordingLog(new File(baseDirName), true))\n            {\n                newRecordingLog.appendTerm(\n                    lastTermEntry.recordingId,\n                    lastTermEntry.leadershipTermId,\n                    lastTermEntry.termBaseLogPosition,\n                    lastTermEntry.timestamp);\n                newRecordingLog.commitLogPosition(lastTermEntry.leadershipTermId, lastTermEntry.logPosition);\n\n                appendServiceSnapshot(recordingLog, newRecordingLog, 0);\n                appendServiceSnapshot(recordingLog, newRecordingLog, ConsensusModule.Configuration.SERVICE_ID);\n            }\n        }\n\n        Files.copy(\n            new File(baseDirName).toPath().resolve(RECORDING_LOG_FILE_NAME),\n            consensusModuleDataDir.toPath().resolve(RECORDING_LOG_FILE_NAME),\n            StandardCopyOption.REPLACE_EXISTING);\n\n        try (RecordingLog copiedRecordingLog = new RecordingLog(consensusModuleDataDir, false))\n        {\n            final LongHashSet recordingIds = new LongHashSet();\n            copiedRecordingLog.entries().stream().mapToLong((e) -> e.recordingId).forEach(recordingIds::add);\n            final Predicate<Path> filterPredicate = (p) -> p.getFileName().toString().endsWith(\".rec\");\n\n            try (Stream<Path> segments = Files.list(archiveDataDir.toPath()).filter(filterPredicate))\n            {\n                segments\n                    .filter(\n                        (p) ->\n                        {\n                            final String fileName = p.getFileName().toString();\n                            final long recordingId = Long.parseLong(fileName.split(\"-\")[0]);\n\n                            return !recordingIds.contains(recordingId);\n                        })\n                    .map(Path::toFile).forEach(StartFromTruncatedRecordingLogTest::deleteFile);\n            }\n\n            assertTrue(copiedRecordingLog.entries().size() <= 3);\n        }\n    }\n\n    private static void appendServiceSnapshot(\n        final RecordingLog oldRecordingLog, final RecordingLog newRecordingLog, final int serviceId)\n    {\n        final RecordingLog.Entry snapshot = oldRecordingLog.getLatestSnapshot(serviceId);\n        assertNotNull(snapshot);\n\n        newRecordingLog.appendSnapshot(\n            snapshot.recordingId,\n            snapshot.leadershipTermId,\n            snapshot.termBaseLogPosition,\n            snapshot.logPosition,\n            snapshot.timestamp,\n            snapshot.serviceId);\n    }\n\n    private static void deleteFile(final File file)\n    {\n        IoUtil.delete(file, false);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/cluster/TestClusterTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.cluster;\n\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.cluster.TestCluster;\nimport org.hamcrest.Matchers;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.nio.file.Path;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport static io.aeron.test.cluster.TestCluster.aCluster;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@SlowTest\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class TestClusterTest\n{\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(20)\n    void testCustomAeronDirectory(@TempDir final Path tempDir)\n    {\n        final String aeronDirectory = tempDir.toString();\n        final AeronCluster.Context clientCtx = new AeronCluster.Context()\n            .aeronDirectoryName(aeronDirectory);\n\n        final TestCluster cluster = aCluster()\n            .withStaticNodes(3)\n            .withAeronBaseDir(aeronDirectory)\n            .start();\n        systemTestWatcher.cluster(cluster);\n\n        cluster.awaitLeader();\n        assertNotNull(cluster.connectClient());\n\n        final Set<String> seen = new HashSet<>();\n        for (int i = 0; i < cluster.memberCount(); i++)\n        {\n            final String dir = cluster.node(i).mediaDriver().context().aeronDirectoryName();\n            assertTrue(seen.add(dir), \"Cluster has a duplicate Aeron dir: \" + dir);\n            assertThat(dir, Matchers.startsWith(aeronDirectory));\n        }\n\n        final String dir = clientCtx.aeronDirectoryName();\n        assertTrue(seen.add(dir), \"Client has duplicate Aeron dir: \" + dir);\n        assertThat(dir, Matchers.startsWith(aeronDirectory));\n    }\n\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/BytesSentAndReceivedTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class BytesSentAndReceivedTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver mediaDriver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void before(@TempDir final Path tempDir)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(tempDir.toString())\n            .threadingMode(ThreadingMode.SHARED);\n        mediaDriver = TestMediaDriver.launch(context, systemTestWatcher);\n\n        systemTestWatcher.dataCollector().add(context.aeronDirectory());\n\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(context.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, mediaDriver);\n    }\n\n    @ParameterizedTest\n    @InterruptAfter(20)\n    @ValueSource(ints = { 1, 5, 10 })\n    void unicast(final int numberOfTransports)\n    {\n        final UnsafeBuffer buffer = new UnsafeBuffer(new byte[1024]);\n        final Random random = new Random(-4732947238473892L);\n        random.nextBytes(buffer.byteArray());\n        final MutableInteger fragmentLength = new MutableInteger();\n        final FragmentHandler fragmentHandler = (buf, offset, length, header) -> fragmentLength.set(length);\n\n        final List<Publication> publications = new ArrayList<>();\n        final List<Subscription> subscriptions = new ArrayList<>();\n\n        for (int i = 0, port = 5500; i < numberOfTransports; i++)\n        {\n            final String channel = \"aeron:udp?term-length=64k|endpoint=localhost:\" + (++port);\n            publications.add(aeron.addPublication(channel, port));\n            subscriptions.add(aeron.addSubscription(channel, port));\n        }\n\n        long expectedTotalBytes = 0;\n        for (int i = 0; i < numberOfTransports; i++)\n        {\n            final Publication publication = publications.get(i);\n            final Subscription subscription = subscriptions.get(i);\n            Tests.awaitConnected(publication);\n            Tests.awaitConnected(subscription);\n\n            final int length = random.nextInt(100, 1000);\n            while (publication.offer(buffer, 0, length) < 0)\n            {\n                Tests.yield();\n            }\n\n            fragmentLength.set(0);\n            while (fragmentLength.get() != length)\n            {\n                if (0 == subscription.poll(fragmentHandler, 1))\n                {\n                    Tests.yield();\n                }\n            }\n\n            assertThat(subscription.imageAtIndex(0).position(), equalTo(publication.position()));\n\n            expectedTotalBytes += publication.position();\n        }\n\n        final CountersReader countersReader = aeron.countersReader();\n        while (countersReader.getCounterValue(SystemCounterDescriptor.BYTES_SENT.id()) < expectedTotalBytes ||\n            countersReader.getCounterValue(SystemCounterDescriptor.BYTES_RECEIVED.id()) < expectedTotalBytes)\n        {\n            Tests.yield();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/DriverNameResolverTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.CommonContext;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SlowTest;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.collections.MutableReference;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.SleepingMillisIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.DisabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport java.net.BindException;\nimport java.util.Map;\nimport java.util.TreeMap;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.driver.DriverNameResolver.NEIGHBOR_RESOLUTION_INTERVAL_MS;\nimport static org.agrona.concurrent.status.CountersReader.METADATA_LENGTH;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_ALLOCATED;\nimport static org.agrona.concurrent.status.CountersReader.RECORD_UNUSED;\nimport static org.agrona.concurrent.status.CountersReader.TYPE_ID_OFFSET;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.endsWith;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@SlowTest\n@ExtendWith(InterruptingTestCallback.class)\nclass DriverNameResolverTest\n{\n    private static final SleepingMillisIdleStrategy SLEEP_50_MS = new SleepingMillisIdleStrategy(50);\n    private final String baseDir = CommonContext.generateRandomDirName();\n    private final Map<String, TestMediaDriver> drivers = new TreeMap<>();\n    private final Map<String, Aeron> clients = new TreeMap<>();\n\n    @RegisterExtension\n    final SystemTestWatcher testWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before()\n    {\n        testWatcher.ignoreErrorsMatching(s -> s.contains(\"Failed to send resolution frames to neighbor\"));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(clients.values());\n        CloseHelper.closeAll(drivers.values());\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldInitializeWithDefaultsAndHaveResolverCounters()\n    {\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-A\")\n            .resolverName(\"A\")\n            .resolverInterface(\"0.0.0.0:0\"), testWatcher));\n        startClients();\n\n        final int neighborsCounterId = awaitNeighborsCounterId(\"A\");\n        assertNotEquals(neighborsCounterId, NULL_VALUE);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldSeeNeighbor()\n    {\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-A\")\n            .resolverName(\"A\")\n            .resolverInterface(\"0.0.0.0:8050\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-B\")\n            .resolverName(\"B\")\n            .resolverInterface(\"0.0.0.0:8051\")\n            .resolverBootstrapNeighbor(\"localhost:8050\"), testWatcher));\n        startClients();\n\n        final int aNeighborsCounterId = awaitNeighborsCounterId(\"A\");\n        final int bNeighborsCounterId = awaitNeighborsCounterId(\"B\");\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 1);\n        awaitCounterValue(\"B\", bNeighborsCounterId, 1);\n    }\n\n    @Test\n    @InterruptAfter(20)\n    void shouldSeeNeighborsViaGossip()\n    {\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-B\")\n            .resolverName(\"B\")\n            .resolverInterface(\"0.0.0.0:8051\")\n            .resolverBootstrapNeighbor(\"localhost:8050\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-C\")\n            .resolverName(\"C\")\n            .resolverInterface(\"0.0.0.0:8052\")\n            .resolverBootstrapNeighbor(\"localhost:8051\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-A\")\n            .resolverName(\"A\")\n            .resolverInterface(\"0.0.0.0:8050\"), testWatcher));\n\n        startClients();\n\n        final int aNeighborsCounterId = awaitNeighborsCounterId(\"A\");\n        final int bNeighborsCounterId = awaitNeighborsCounterId(\"B\");\n        final int cNeighborsCounterId = awaitNeighborsCounterId(\"C\");\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 2);\n        awaitCounterValue(\"B\", bNeighborsCounterId, 2);\n        awaitCounterValue(\"C\", cNeighborsCounterId, 2);\n    }\n\n    @Test\n    @InterruptAfter(15)\n    void shouldSeeNeighborsViaGossipAsLateJoiningDriver()\n    {\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-A\")\n            .resolverName(\"A\")\n            .resolverInterface(\"0.0.0.0:8050\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-B\")\n            .resolverName(\"B\")\n            .resolverInterface(\"0.0.0.0:8051\")\n            .resolverBootstrapNeighbor(\"localhost:8050\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-C\")\n            .resolverName(\"C\")\n            .resolverInterface(\"0.0.0.0:8052\")\n            .resolverBootstrapNeighbor(\"localhost:8050\"), testWatcher));\n        startClients();\n\n        final int aNeighborsCounterId = awaitNeighborsCounterId(\"A\");\n        final int bNeighborsCounterId = awaitNeighborsCounterId(\"B\");\n        final int cNeighborsCounterId = awaitNeighborsCounterId(\"C\");\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 2);\n        awaitCounterValue(\"B\", bNeighborsCounterId, 2);\n        awaitCounterValue(\"C\", cNeighborsCounterId, 2);\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-D\")\n            .resolverName(\"D\")\n            .resolverInterface(\"0.0.0.0:8053\")\n            .resolverBootstrapNeighbor(\"localhost:8050\"), testWatcher));\n        startClients();\n\n        final int dNeighborsCounterId = awaitNeighborsCounterId(\"D\");\n\n        awaitCounterValue(\"D\", dNeighborsCounterId, 3);\n        awaitCounterValue(\"A\", aNeighborsCounterId, 3);\n        awaitCounterValue(\"B\", bNeighborsCounterId, 3);\n        awaitCounterValue(\"C\", cNeighborsCounterId, 3);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldResolveDriverNameAndAllowConnection()\n    {\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-A\")\n            .resolverName(\"A\")\n            .resolverInterface(\"0.0.0.0:8050\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-B\")\n            .resolverName(\"B\")\n            .resolverInterface(\"0.0.0.0:8051\")\n            .resolverBootstrapNeighbor(\"localhost:8050\"), testWatcher));\n        startClients();\n\n        final int aNeighborsCounterId = awaitNeighborsCounterId(\"A\");\n        final int bNeighborsCounterId = awaitNeighborsCounterId(\"B\");\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 1);\n        awaitCounterValue(\"B\", bNeighborsCounterId, 1);\n\n        final int aCacheEntriesCounterId = awaitCacheEntriesCounterId(\"A\");\n\n        awaitCounterValue(\"A\", aCacheEntriesCounterId, 1);\n\n        try (Subscription subscription = clients.get(\"B\").addSubscription(\"aeron:udp?endpoint=localhost:24325\", 1);\n            Publication publication = clients.get(\"A\").addPublication(\"aeron:udp?endpoint=B:24325\", 1))\n        {\n            while (!publication.isConnected() || !subscription.isConnected())\n            {\n                Tests.sleep(50);\n            }\n        }\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldTimeoutAllNeighborsAndCacheEntries()\n    {\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-A\")\n            .resolverName(\"A\")\n            .resolverInterface(\"0.0.0.0:8050\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-B\")\n            .resolverName(\"B\")\n            .resolverInterface(\"0.0.0.0:8051\")\n            .resolverBootstrapNeighbor(\"localhost:8050\"), testWatcher));\n        startClients();\n\n        final int aNeighborsCounterId = awaitNeighborsCounterId(\"A\");\n        final int bNeighborsCounterId = awaitNeighborsCounterId(\"B\");\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 1);\n        awaitCounterValue(\"B\", bNeighborsCounterId, 1);\n\n        final int aCacheEntriesCounterId = awaitCacheEntriesCounterId(\"A\");\n\n        awaitCounterValue(\"A\", aCacheEntriesCounterId, 1);\n\n        closeDriver(\"B\");\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 0);\n        awaitCounterValue(\"A\", aCacheEntriesCounterId, 0);\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldTimeoutNeighborsAndCacheEntriesThatAreSeenViaGossip()\n    {\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-A\")\n            .resolverName(\"A\")\n            .resolverInterface(\"0.0.0.0:8050\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-B\")\n            .resolverName(\"B\")\n            .resolverInterface(\"0.0.0.0:8051\")\n            .resolverBootstrapNeighbor(\"localhost:8050\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-C\")\n            .resolverName(\"C\")\n            .resolverInterface(\"0.0.0.0:8052\")\n            .resolverBootstrapNeighbor(\"localhost:8050\"), testWatcher));\n        startClients();\n\n        final int aNeighborsCounterId = awaitNeighborsCounterId(\"A\");\n        final int bNeighborsCounterId = awaitNeighborsCounterId(\"B\");\n        final int cNeighborsCounterId = awaitNeighborsCounterId(\"C\");\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 2);\n        awaitCounterValue(\"B\", bNeighborsCounterId, 2);\n        awaitCounterValue(\"C\", cNeighborsCounterId, 2);\n\n        final int aCacheEntriesCounterId = awaitCacheEntriesCounterId(\"A\");\n        final int bCacheEntriesCounterId = awaitCacheEntriesCounterId(\"B\");\n        awaitCounterValue(\"A\", aCacheEntriesCounterId, 2);\n        awaitCounterValue(\"B\", bCacheEntriesCounterId, 2);\n\n        closeDriver(\"B\");\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 1);\n        awaitCounterValue(\"A\", aCacheEntriesCounterId, 1);\n        awaitCounterValue(\"C\", bNeighborsCounterId, 1);\n        awaitCounterValue(\"C\", bCacheEntriesCounterId, 1);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void shouldUseFirstAvailableBootstrapNeighbor()\n    {\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-A\")\n            .resolverName(\"A\")\n            .resolverInterface(\"0.0.0.0:8050\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(\n            setDefaults(new MediaDriver.Context())\n                .aeronDirectoryName(baseDir + \"-B\")\n                .resolverName(\"B\")\n                .resolverInterface(\"0.0.0.0:8051\")\n                .resolverBootstrapNeighbor(\"just:wrong,non_existing_host:8050,localhost:8050,localhost:8051\"),\n            testWatcher));\n        startClients();\n\n        final int aNeighborsCounterId = awaitNeighborsCounterId(\"A\");\n        final int bNeighborsCounterId = awaitNeighborsCounterId(\"B\");\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 1);\n        awaitCounterValue(\"B\", bNeighborsCounterId, 1);\n        awaitCounterLabel(\"A\", aNeighborsCounterId, \"Resolver neighbors: bound 0.0.0.0:8050\");\n        awaitCounterLabel(\n            \"B\", bNeighborsCounterId, \"Resolver neighbors: bound 0.0.0.0:8051 bootstrap 127.0.0.1:8050\");\n    }\n\n    @Test\n    @InterruptAfter(30)\n    void shouldFallbackToAnotherBootstrapNeighborIfOneBecomesUnavailable()\n    {\n        assumeTrue(TestMediaDriver.shouldRunJavaMediaDriver());\n\n        final NameResolver bootstrapResolver = DriverNameResolver.bootstrapNameResolver;\n        try\n        {\n            final MutableBoolean resolveHostA = new MutableBoolean(true);\n            DriverNameResolver.bootstrapNameResolver = (name, uriParamName, isReResolution) ->\n                (resolveHostA.get() || !name.endsWith(\"A\")) ?\n                    DefaultNameResolver.INSTANCE.resolve(\n                        name.substring(0, name.length() - 1), uriParamName, isReResolution) : null;\n\n            addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n                .aeronDirectoryName(baseDir + \"-A\")\n                .resolverName(\"A\")\n                .resolverInterface(\"0.0.0.0:8050\"), testWatcher));\n\n            addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n                .aeronDirectoryName(baseDir + \"-B\")\n                .resolverName(\"B\")\n                .resolverInterface(\"0.0.0.0:8051\")\n                .resolverBootstrapNeighbor(\"localhostA:8050,localhostB:8051\"), testWatcher));\n\n            addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n                .aeronDirectoryName(baseDir + \"-C\")\n                .resolverName(\"C\")\n                .resolverInterface(\"0.0.0.0:8052\")\n                .resolverBootstrapNeighbor(\"localhostA:8050,localhostB:8051\"), testWatcher));\n            startClients();\n\n            final int aNeighborsCounterId = awaitNeighborsCounterId(\"A\");\n            final int bNeighborsCounterId = awaitNeighborsCounterId(\"B\");\n            final int cNeighborsCounterId = awaitNeighborsCounterId(\"C\");\n\n            awaitCounterValue(\"A\", aNeighborsCounterId, 2);\n            awaitCounterValue(\"B\", bNeighborsCounterId, 2);\n            awaitCounterValue(\"C\", cNeighborsCounterId, 2);\n            awaitCounterLabel(\"A\", aNeighborsCounterId, \"Resolver neighbors: bound 0.0.0.0:8050\");\n            awaitCounterLabel(\n                \"B\", bNeighborsCounterId, \"Resolver neighbors: bound 0.0.0.0:8051 bootstrap 127.0.0.1:8050\");\n            awaitCounterLabel(\n                \"C\", cNeighborsCounterId, \"Resolver neighbors: bound 0.0.0.0:8052 bootstrap 127.0.0.1:8050\");\n\n            closeDriver(\"A\");\n            resolveHostA.set(false);\n\n            awaitCounterValue(\"B\", bNeighborsCounterId, 1);\n            awaitCounterValue(\"C\", cNeighborsCounterId, 1);\n            awaitCounterLabel(\"B\", bNeighborsCounterId,\n                \"Resolver neighbors: bound 0.0.0.0:8051 bootstrap 127.0.0.1:8051\");\n            awaitCounterLabel(\"C\", cNeighborsCounterId,\n                \"Resolver neighbors: bound 0.0.0.0:8052 bootstrap 127.0.0.1:8051\");\n\n            addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n                .aeronDirectoryName(baseDir + \"-D\")\n                .resolverName(\"D\")\n                .resolverInterface(\"0.0.0.0:8053\")\n                .resolverBootstrapNeighbor(\"localhostA:8050,localhostB:8051\"), testWatcher));\n            startClients();\n\n            final int dNeighborsCounterId = awaitNeighborsCounterId(\"D\");\n\n            awaitCounterValue(\"B\", bNeighborsCounterId, 2);\n            awaitCounterValue(\"C\", cNeighborsCounterId, 2);\n            awaitCounterValue(\"D\", dNeighborsCounterId, 2);\n            awaitCounterLabel(\"D\", dNeighborsCounterId,\n                \"Resolver neighbors: bound 0.0.0.0:8053 bootstrap 127.0.0.1:8051\");\n        }\n        finally\n        {\n            DriverNameResolver.bootstrapNameResolver = bootstrapResolver;\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @DisabledOnOs(OS.MAC)\n    void shouldMatchFullNameWhenPortsAreTheSameAndNamesCanBePrefixMatched()\n    {\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-A\")\n            .resolverName(\"A\")\n            .resolverInterface(\"127.0.0.1:4809\")\n            .resolverBootstrapNeighbor(\"127.0.0.2:4809\"), testWatcher));\n\n        addDriver(TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .aeronDirectoryName(baseDir + \"-B\")\n            .resolverName(\"AA\")\n            .resolverInterface(\"127.0.0.2:4809\"), testWatcher));\n        startClients();\n\n        final int aNeighborsCounterId = awaitNeighborsCounterId(\"A\");\n        final int bNeighborsCounterId = awaitNeighborsCounterId(\"AA\");\n\n        final long deadlineNs = System.nanoTime() + 2 * TimeUnit.MILLISECONDS.toNanos(NEIGHBOR_RESOLUTION_INTERVAL_MS);\n\n        awaitCounterValue(\"A\", aNeighborsCounterId, 1);\n        awaitCounterValue(\"AA\", bNeighborsCounterId, 1);\n\n        // Ensure that self entry is not added to the cache if received without SELF flag\n        final CountersReader aCounters = clients.get(\"A\").countersReader();\n        final CountersReader bCounters = clients.get(\"AA\").countersReader();\n        do\n        {\n            assertEquals(1, aCounters.getCounterValue(aNeighborsCounterId));\n            assertEquals(1, bCounters.getCounterValue(bNeighborsCounterId));\n        }\n        while (System.nanoTime() < deadlineNs);\n    }\n\n    @Test\n    @InterruptAfter(10)\n    @SuppressWarnings(\"try\")\n    void shouldUseActuallySpecifiedHostNamePortPairForCreatingChannelUri()\n    {\n        assumeTrue(TestMediaDriver.shouldRunJavaMediaDriver());\n\n        final String aeronDir = baseDir + \"-error\";\n        final MutableReference<Throwable> error = new MutableReference<>();\n        try (TestMediaDriver driver = TestMediaDriver.launch(setDefaults(new MediaDriver.Context())\n            .threadingMode(ThreadingMode.INVOKER)\n            .errorHandler(error::set)\n            .aeronDirectoryName(aeronDir)\n            .resolverName(\"test\")\n            .resolverInterface(\"1.0.0.0:4809\"), testWatcher))\n        {\n            final Throwable exception = error.get();\n            assertNull(exception.getCause());\n            final Throwable[] suppressed = exception.getSuppressed();\n            assertNotNull(suppressed);\n            assertEquals(1, suppressed.length);\n            final Throwable channelError = suppressed[0];\n            assertInstanceOf(AeronException.class, channelError);\n            assertThat(channelError.getMessage(), endsWith(\"aeron:udp?endpoint=1.0.0.0:4809\"));\n            assertInstanceOf(BindException.class, channelError.getCause());\n        }\n    }\n\n    private void closeDriver(final String name)\n    {\n        clients.remove(name).close();\n        final TestMediaDriver driver = drivers.remove(name);\n        driver.close();\n        driver.context().deleteDirectory();\n    }\n\n    private static MediaDriver.Context setDefaults(final MediaDriver.Context context)\n    {\n        context\n            .errorHandler(Tests::onError)\n            .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .threadingMode(ThreadingMode.SHARED)\n            .dirDeleteOnStart(true);\n\n        return context;\n    }\n\n    private int awaitNeighborsCounterId(final String name)\n    {\n        final Aeron aeron = clients.get(name);\n        final AtomicBuffer metaDataBuffer = aeron.countersReader().metaDataBuffer();\n\n        while (true)\n        {\n            for (int offset = 0, counterId = 0, capacity = metaDataBuffer.capacity();\n                 offset < capacity;\n                 offset += METADATA_LENGTH, counterId++)\n            {\n                final int recordStatus = metaDataBuffer.getIntVolatile(offset);\n                if (RECORD_ALLOCATED == recordStatus)\n                {\n                    final int typeId = metaDataBuffer.getInt(offset + TYPE_ID_OFFSET);\n                    if (AeronCounters.NAME_RESOLVER_NEIGHBORS_COUNTER_TYPE_ID == typeId)\n                    {\n                        return counterId;\n                    }\n                }\n                else if (RECORD_UNUSED == recordStatus)\n                {\n                    break;\n                }\n            }\n\n            Tests.sleep(1);\n            if (aeron.isClosed())\n            {\n                fail(\"unexpected Aeron client close\");\n            }\n        }\n    }\n\n    private int awaitCacheEntriesCounterId(final String name)\n    {\n        final Aeron aeron = clients.get(name);\n        final AtomicBuffer metaDataBuffer = aeron.countersReader().metaDataBuffer();\n\n        while (true)\n        {\n            for (int offset = 0, counterId = 0, capacity = metaDataBuffer.capacity();\n                 offset < capacity;\n                 offset += METADATA_LENGTH, counterId++)\n            {\n                final int recordStatus = metaDataBuffer.getIntVolatile(offset);\n                if (RECORD_ALLOCATED == recordStatus)\n                {\n                    final int typeId = metaDataBuffer.getInt(offset + TYPE_ID_OFFSET);\n                    if (AeronCounters.NAME_RESOLVER_CACHE_ENTRIES_COUNTER_TYPE_ID == typeId)\n                    {\n                        return counterId;\n                    }\n                }\n                else if (RECORD_UNUSED == recordStatus)\n                {\n                    break;\n                }\n            }\n\n            Tests.sleep(1);\n            if (aeron.isClosed())\n            {\n                fail(\"unexpected Aeron client close\");\n            }\n        }\n    }\n\n    private void awaitCounterValue(final String name, final int counterId, final long expectedValue)\n    {\n        final Aeron aeron = clients.get(name);\n        final CountersReader countersReader = aeron.countersReader();\n        final Supplier<String> messageSupplier =\n            () -> \"Counter value: \" + countersReader.getCounterValue(counterId) + \", expected: \" + expectedValue;\n\n        while (countersReader.getCounterValue(counterId) != expectedValue)\n        {\n            Tests.idle(SLEEP_50_MS, messageSupplier);\n            if (aeron.isClosed())\n            {\n                fail(messageSupplier.get());\n            }\n        }\n    }\n\n    private void awaitCounterLabel(final String name, final int counterId, final String expectedLabel)\n    {\n        final Aeron aeron = clients.get(name);\n        final CountersReader countersReader = aeron.countersReader();\n        final Supplier<String> messageSupplier =\n            () -> \"Counter label: \" + countersReader.getCounterLabel(counterId) + \", expected: \" + expectedLabel;\n\n        while (!expectedLabel.equals(countersReader.getCounterLabel(counterId)))\n        {\n            Tests.idle(SLEEP_50_MS, messageSupplier);\n            if (aeron.isClosed())\n            {\n                fail(messageSupplier.get());\n            }\n        }\n    }\n\n    private void startClients()\n    {\n        for (final Map.Entry<String, TestMediaDriver> entry : drivers.entrySet())\n        {\n            final String name = entry.getKey();\n            final TestMediaDriver driver = entry.getValue();\n            if (!clients.containsKey(name))\n            {\n                clients.put(name, Aeron.connect(new Aeron.Context()\n                    .aeronDirectoryName(driver.aeronDirectoryName())\n                    .driverTimeoutMs(driver.context().driverTimeoutMs())\n                    .errorHandler(Tests::onError)));\n            }\n        }\n    }\n\n    private void addDriver(final TestMediaDriver testMediaDriver)\n    {\n        final String name = testMediaDriver.context().resolverName();\n        drivers.put(name, testMediaDriver);\n        testWatcher.dataCollector().add(testMediaDriver.context().aeronDirectory());\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/DriverShouldStartIfAeronDirectoryExistsTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CncFileDescriptor;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\npublic class DriverShouldStartIfAeronDirectoryExistsTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(10)\n    void test(final @TempDir Path tempDir) throws IOException\n    {\n        final Path aeronDir = tempDir.resolve(\"aeron-existing-dir\");\n        Files.createDirectories(aeronDir);\n        assertTrue(Files.exists(aeronDir));\n        final Path cncFile = aeronDir.resolve(CncFileDescriptor.CNC_FILE);\n        assertTrue(Files.notExists(cncFile));\n\n        try (TestMediaDriver driver = TestMediaDriver.launch(\n            new MediaDriver.Context().aeronDirectoryName(aeronDir.toString()), systemTestWatcher);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            assertTrue(aeron.context().cncFile().exists());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/DriverSpaceTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.ConcurrentPublication;\nimport io.aeron.ErrorCode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.FileStore;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.api.Assumptions.abort;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\npublic class DriverSpaceTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n    private Path aeronDir;\n    private Path publicationsDir;\n\n    @BeforeEach\n    void verifyFileSystemSetup()\n    {\n        final Path tempfsDir;\n        switch (OS.current())\n        {\n            case WINDOWS:\n                tempfsDir = new File(\"T:/tmp_aeron_dir\").toPath();\n                break;\n            case MAC:\n                tempfsDir = new File(\"/Volumes/tmp_aeron_dir\").toPath();\n                break;\n            default:\n                tempfsDir = new File(\"/mnt/tmp_aeron_dir\").toPath();\n                break;\n        }\n        assumeTrue(Files.exists(tempfsDir), () -> tempfsDir + \" does not exist\");\n        assumeTrue(Files.isDirectory(tempfsDir), () -> tempfsDir + \" is not a directory\");\n        assumeTrue(Files.isWritable(tempfsDir), () -> tempfsDir + \" is not writable\");\n\n        try\n        {\n            final FileStore fileStore = Files.getFileStore(tempfsDir);\n            assumeTrue(fileStore.getUsableSpace() < (64 * 1024 * 1024), \"Skipping as file system is too large\");\n        }\n        catch (final IOException e)\n        {\n            abort(\"File store not accessible\");\n        }\n\n        aeronDir = tempfsDir.resolve(\"aeron-no-space\");\n        publicationsDir = aeronDir.resolve(\"publications\");\n    }\n\n    @Test\n    void shouldCreatePublicationUsingSparseFiles()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.toString())\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .performStorageChecks(false);\n\n        try (TestMediaDriver driver = TestMediaDriver.launch(context, systemTestWatcher);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final ConcurrentPublication publication =\n                aeron.addPublication(\"aeron:ipc?term-length=1g|sparse=true\", 20002);\n            assertNotNull(publication);\n\n            final File[] files = publicationsDir.toFile().listFiles();\n            assertNotNull(files);\n            assertEquals(1, files.length);\n            final File file = files[0];\n            assertEquals(3221229568L, file.length());\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldThrowExceptionIfOutOfDiscSpace(final boolean performStorageChecks)\n    {\n        assumeTrue(performStorageChecks || OS.WINDOWS == OS.current() || TestMediaDriver.shouldRunCMediaDriver());\n\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDir.toString())\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .performStorageChecks(performStorageChecks);\n\n        try (TestMediaDriver driver = TestMediaDriver.launch(context, systemTestWatcher);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            try\n            {\n                aeron.addPublication(\"aeron:ipc?term-length=16m|sparse=false\", 10001);\n                fail(\"RegistrationException was not thrown\");\n            }\n            catch (final RegistrationException ex)\n            {\n                assertEquals(ErrorCode.STORAGE_SPACE, ex.errorCode());\n                assertTrue(Files.exists(publicationsDir));\n                assertArrayEquals(new String[0], publicationsDir.toFile().list(), \"Log file was not deleted\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/DutyCycleLabelFormatTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.driver.status.SystemCounterDescriptor.CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.CONDUCTOR_MAX_CYCLE_TIME;\nimport static io.aeron.driver.status.SystemCounterDescriptor.NAME_RESOLVER_MAX_TIME;\nimport static io.aeron.driver.status.SystemCounterDescriptor.NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.RECEIVER_MAX_CYCLE_TIME;\nimport static io.aeron.driver.status.SystemCounterDescriptor.SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED;\nimport static io.aeron.driver.status.SystemCounterDescriptor.SENDER_MAX_CYCLE_TIME;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.endsWith;\nimport static org.hamcrest.Matchers.is;\n\npublic class DutyCycleLabelFormatTest\n{\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.close(driver);\n    }\n\n    @ParameterizedTest\n    @EnumSource(ThreadingMode.class)\n    void test(final ThreadingMode threadingMode)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(threadingMode)\n            .conductorCycleThresholdNs(TimeUnit.HOURS.toNanos(2))\n            .senderCycleThresholdNs(TimeUnit.MICROSECONDS.toNanos(321))\n            .receiverCycleThresholdNs(TimeUnit.MILLISECONDS.toNanos(250))\n            .nameResolverThresholdNs(TimeUnit.SECONDS.toNanos(15));\n\n        driver = TestMediaDriver.launch(context, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(context.aeronDirectoryName())))\n        {\n            final CountersReader countersReader = aeron.countersReader();\n            assertThat(countersReader.getCounterLabel(CONDUCTOR_MAX_CYCLE_TIME.id()), endsWith(\": \" + threadingMode));\n            assertThat(\n                countersReader.getCounterLabel(CONDUCTOR_CYCLE_TIME_THRESHOLD_EXCEEDED.id()),\n                endsWith(\": threshold=7200s \" + threadingMode));\n\n            assertThat(countersReader.getCounterLabel(SENDER_MAX_CYCLE_TIME.id()), endsWith(\": \" + threadingMode));\n            assertThat(\n                countersReader.getCounterLabel(SENDER_CYCLE_TIME_THRESHOLD_EXCEEDED.id()),\n                endsWith(\": threshold=321us \" + threadingMode));\n\n            assertThat(countersReader.getCounterLabel(RECEIVER_MAX_CYCLE_TIME.id()), endsWith(\": \" + threadingMode));\n            assertThat(\n                countersReader.getCounterLabel(RECEIVER_CYCLE_TIME_THRESHOLD_EXCEEDED.id()),\n                endsWith(\": threshold=250ms \" + threadingMode));\n\n            assertThat(countersReader.getCounterLabel(NAME_RESOLVER_MAX_TIME.id()), is(NAME_RESOLVER_MAX_TIME.label()));\n            assertThat(\n                countersReader.getCounterLabel(NAME_RESOLVER_TIME_THRESHOLD_EXCEEDED.id()),\n                endsWith(\": threshold=15s\"));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/ExperimentalDriverFeaturesTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.logbuffer.LogBufferDescriptor;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\npublic class ExperimentalDriverFeaturesTest\n{\n    @RegisterExtension\n    final SystemTestWatcher watcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    @BeforeEach\n    void setUp()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .publicationTermBufferLength(LogBufferDescriptor.TERM_MIN_LENGTH)\n            .threadingMode(ThreadingMode.SHARED)\n            .dirDeleteOnShutdown(true);\n\n        driver = TestMediaDriver.launch(context, watcher);\n        watcher.dataCollector().add(driver.context().aeronDirectory());\n        watcher.ignoreErrorsMatching(s -> true);\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        CloseHelper.quietClose(driver);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/FilePageSizeTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.aeron.CommonContext.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class FilePageSizeTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(driver);\n    }\n\n    @ParameterizedTest\n    @ValueSource(ints = { 16 * 1024, 2 * 1024 * 1024 })\n    void shouldStoreFilePageSizeInTheCnCFileMetadata(final int filePageSize)\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .aeronDirectoryName(generateRandomDirName())\n            .dirDeleteOnShutdown(true)\n            .filePageSize(filePageSize), systemTestWatcher);\n\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        assertEquals(filePageSize, driverFilePageSize(\n            new File(driver.aeronDirectoryName()), SystemEpochClock.INSTANCE, TimeUnit.SECONDS.toMillis(10)));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/Issue1719Test.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Subscription;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.FieldSource;\n\nimport java.nio.file.Path;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass Issue1719Test\n{\n    private static List<String> channels = List.of(\n        \"aeron:udp?endpoint=localhost:5555\",\n        \"aeron:udp?control=localhost:7777|control-mode=dynamic\"\n    );\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @BeforeEach\n    void before(@TempDir final Path tempDir)\n    {\n        final String aeronDirectoryName = CommonContext.generateRandomDirName();\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDirectoryName)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .spiesSimulateConnection(false)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true);\n\n        driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driverCtx.aeronDirectory());\n\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(aeronDirectoryName));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest\n    @FieldSource(\"channels\")\n    void shouldAddWildcardSpyBeforePublication(final String channel)\n    {\n        final String subUri = CommonContext.SPY_PREFIX + channel;\n        final String pubUri = channel + \"|ssc=true\";\n        final int streamId1 = 333;\n        final int streamId2 = 777;\n\n        final Subscription spyStream1 = aeron.addSubscription(subUri, streamId1);\n        assertEquals(streamId1, spyStream1.streamId());\n        final Subscription spyStream2 = aeron.addSubscription(subUri, streamId2);\n        assertEquals(streamId2, spyStream2.streamId());\n\n        final ExclusivePublication pubStream1 = aeron.addExclusivePublication(pubUri, streamId1);\n        final ExclusivePublication pubStream2 = aeron.addExclusivePublication(pubUri, streamId2);\n        assertNotEquals(pubStream1.sessionId(), pubStream2.sessionId());\n\n        Tests.awaitConnected(pubStream1);\n        Tests.awaitConnected(pubStream2);\n        Tests.awaitConnected(spyStream1);\n        Tests.awaitConnected(spyStream2);\n\n        assertEquals(1, spyStream1.imageCount());\n        assertEquals(pubStream1.sessionId(), spyStream1.imageAtIndex(0).sessionId());\n\n        assertEquals(1, spyStream2.imageCount());\n        assertEquals(pubStream2.sessionId(), spyStream2.imageAtIndex(0).sessionId());\n    }\n\n    @ParameterizedTest\n    @FieldSource(\"channels\")\n    void shouldAddWildcardSpyAfterPublication(final String channel)\n    {\n        final String subUri = CommonContext.SPY_PREFIX + channel;\n        final String pubUri = channel + \"|ssc=true\";\n        final int streamId1 = 333;\n        final int streamId2 = 777;\n\n        final ExclusivePublication pubStream1 = aeron.addExclusivePublication(pubUri, streamId1);\n        final ExclusivePublication pubStream2 = aeron.addExclusivePublication(pubUri, streamId2);\n        assertNotEquals(pubStream1.sessionId(), pubStream2.sessionId());\n\n        final Subscription spyStream1 = aeron.addSubscription(subUri, streamId1);\n        assertEquals(streamId1, spyStream1.streamId());\n        final Subscription spyStream2 = aeron.addSubscription(subUri, streamId2);\n        assertEquals(streamId2, spyStream2.streamId());\n\n        Tests.awaitConnected(pubStream1);\n        Tests.awaitConnected(pubStream2);\n        Tests.awaitConnected(spyStream1);\n        Tests.awaitConnected(spyStream2);\n\n        assertEquals(1, spyStream1.imageCount());\n        assertEquals(pubStream1.sessionId(), spyStream1.imageAtIndex(0).sessionId());\n\n        assertEquals(1, spyStream2.imageCount());\n        assertEquals(pubStream2.sessionId(), spyStream2.imageAtIndex(0).sessionId());\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/NextCorrelationIdTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static io.aeron.CommonContext.generateRandomDirName;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class NextCorrelationIdTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(driver);\n    }\n\n    @Test\n    void shouldStoreFilePageSizeInTheCnCFileMetadata()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .aeronDirectoryName(generateRandomDirName()), systemTestWatcher);\n\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        try (Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final long prevId = aeron.nextCorrelationId();\n            assertEquals(prevId + 1, CommonContext.nextCorrelationId(\n                aeron.context().aeronDirectory(),\n                aeron.context().epochClock(),\n                aeron.context().driverTimeoutMs()));\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/NextSessionIdTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static io.aeron.CommonContext.generateRandomDirName;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\npublic class NextSessionIdTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void before()\n    {\n        driver = TestMediaDriver.launch(new MediaDriver.Context()\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED)\n            .aeronDirectoryName(generateRandomDirName()), systemTestWatcher);\n\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName()));\n\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    void shouldFetchNextAvailableSessionId()\n    {\n        final ExclusivePublication publication =\n            aeron.addExclusivePublication(\"aeron:ipc?term-length=64k\", 555);\n\n        final int nextSessionId = aeron.nextSessionId(777);\n        assertEquals(publication.sessionId() + 1, nextSessionId);\n\n        final int nextSessionId2 = aeron.nextSessionId(42);\n        assertEquals(nextSessionId + 1, nextSessionId2);\n    }\n\n    @Test\n    void shouldSkipActiveSessionId()\n    {\n        final int nextSessionId = aeron.nextSessionId(777);\n\n        final ExclusivePublication pub1 =\n            aeron.addExclusivePublication(\"aeron:ipc?term-length=64k|session-id=\" + (nextSessionId + 1), 555);\n        final ExclusivePublication pub2 =\n            aeron.addExclusivePublication(\"aeron:ipc?term-length=64k|session-id=\" + (nextSessionId + 2), 333);\n\n        final int result = aeron.nextSessionId(pub1.streamId());\n        assertNotEquals(pub1.sessionId(), result);\n        assertEquals(pub2.sessionId(), result);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/ResolveEphemeralSubscriptionPortTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Subscription;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.nio.file.Path;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass ResolveEphemeralSubscriptionPortTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void before(@TempDir final Path tempDir)\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(tempDir.toAbsolutePath().toString())\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.DEDICATED);\n        driver = TestMediaDriver.launch(context, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(context.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @ParameterizedTest(name = \"{0}\")\n    @ValueSource(strings = { \"|alias=test\", \"|session-id=99\" })\n    @InterruptAfter(10)\n    void test(final String additionalUriParameters)\n    {\n        final int streamId = -1142;\n        final String tags = \"tags=\" + aeron.nextCorrelationId() + \",\" + aeron.nextCorrelationId();\n\n        final Subscription wildcardSubscription = aeron.addSubscription(\n            \"aeron:udp?endpoint=localhost:0|\" + tags, streamId);\n        Tests.await(() -> null != wildcardSubscription.resolvedEndpoint());\n        final String resolvedEndpoint = wildcardSubscription.resolvedEndpoint();\n\n        final long publicationId = aeron.asyncAddExclusivePublication(\n            \"aeron:udp?term-length=64k|endpoint=\" + resolvedEndpoint + additionalUriParameters, streamId);\n\n        final long subscriptionId = aeron.asyncAddSubscription(\"aeron:udp?\" + tags + additionalUriParameters, streamId);\n        wildcardSubscription.close();\n\n        ExclusivePublication publication;\n        while (null == (publication = aeron.getExclusivePublication(publicationId)))\n        {\n            Tests.yield();\n        }\n\n        Subscription subscription;\n        while (null == (subscription = aeron.getSubscription(subscriptionId)))\n        {\n            Tests.yield();\n        }\n\n        Tests.awaitConnected(publication);\n        Tests.awaitConnected(subscription);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/SocketLifecycleTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.CommonContext;\nimport io.aeron.ErrorCode;\nimport io.aeron.Subscription;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.test.EventLogExtension;\nimport io.aeron.test.InterruptAfter;\nimport io.aeron.test.InterruptingTestCallback;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.concurrent.NoOpIdleStrategy;\nimport org.agrona.concurrent.SleepingMillisIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\n@ExtendWith({ EventLogExtension.class, InterruptingTestCallback.class })\nclass SocketLifecycleTest\n{\n    private static final int TEST_ITERATION_COUNT = 10;\n\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    @Test\n    @InterruptAfter(10)\n    void supportsClosingOpeningSubscriptionWithSameChannelUri0()\n    {\n        try (TestMediaDriver driver = launchDriver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            final CountersReader countersReader = aeron.countersReader();\n\n            for (int i = 0; i < TEST_ITERATION_COUNT; i++)\n            {\n                final Subscription subscription = aeron.addSubscription(\n                    \"aeron:udp?endpoint=localhost:10000\", 1000);\n                final int counterId = subscription.channelStatusId();\n                final long registrationId = countersReader.getCounterRegistrationId(counterId);\n                assertEquals(subscription.registrationId(), registrationId);\n                subscription.close();\n                while (registrationId == countersReader.getCounterRegistrationId(counterId) &&\n                    CountersReader.RECORD_ALLOCATED == countersReader.getCounterState(counterId))\n                {\n                    Tests.yield();\n                }\n            }\n\n            assertEquals(0, errorCount(aeron));\n        }\n    }\n\n    @Test\n    @InterruptAfter(10)\n    void supportsClosingOpeningSubscriptionWithSameChannelUri1()\n    {\n        int unavailableCount = 0;\n        try (TestMediaDriver driver = launchDriver();\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(driver.aeronDirectoryName())))\n        {\n            for (int i = 0; i < TEST_ITERATION_COUNT; i++)\n            {\n                Subscription subscription = null;\n                while (null == subscription)\n                {\n                    try\n                    {\n                        subscription = aeron.addSubscription(\"aeron:udp?endpoint=localhost:10000\", 1000);\n                    }\n                    catch (final RegistrationException exception)\n                    {\n                        if (ErrorCode.RESOURCE_TEMPORARILY_UNAVAILABLE == exception.errorCode())\n                        {\n                            ++unavailableCount;\n                            Tests.yield();\n                        }\n                        else\n                        {\n                            throw exception;\n                        }\n                    }\n                }\n                subscription.close();\n            }\n\n            assumeTrue(unavailableCount > 0, \"Expected at least one RESOURCE_TEMPORARILY_UNAVAILABLE exception\");\n            assertEquals(0, errorCount(aeron));\n        }\n    }\n\n    private TestMediaDriver launchDriver()\n    {\n        TestMediaDriver.notSupportedOnCMediaDriver(\"C Media Driver requires more work\");\n\n        final String aeronDirectoryName = CommonContext.generateRandomDirName();\n\n        final MediaDriver.Context driverCtx = new MediaDriver.Context()\n            .aeronDirectoryName(aeronDirectoryName)\n            .termBufferSparseFile(true)\n            .threadingMode(ThreadingMode.DEDICATED)\n            .dirDeleteOnStart(true)\n            .conductorIdleStrategy(new NoOpIdleStrategy())\n            .receiverIdleStrategy(new SleepingMillisIdleStrategy(2))\n            .senderIdleStrategy(new SleepingMillisIdleStrategy(2));\n\n        final TestMediaDriver driver = TestMediaDriver.launch(driverCtx, systemTestWatcher);\n\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        return driver;\n    }\n\n    private static long errorCount(final Aeron aeron)\n    {\n        final CountersReader countersReader = aeron.countersReader();\n        final int counterId = countersReader.findByTypeIdAndRegistrationId(\n            AeronCounters.DRIVER_SYSTEM_COUNTER_TYPE_ID,\n            AeronCounters.SYSTEM_COUNTER_ID_ERRORS);\n        return countersReader.getCounterValue(counterId);\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/driver/SystemCountersTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.command.ControlProtocolEvents;\nimport io.aeron.driver.status.SystemCounterDescriptor;\nimport io.aeron.test.SystemTestWatcher;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.collections.Int2ObjectHashMap;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\nimport static org.hamcrest.CoreMatchers.startsWith;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class SystemCountersTest\n{\n    @RegisterExtension\n    final SystemTestWatcher systemTestWatcher = new SystemTestWatcher();\n\n    private TestMediaDriver driver;\n    private Aeron aeron;\n\n    @BeforeEach\n    void before()\n    {\n        final MediaDriver.Context context = new MediaDriver.Context()\n            .aeronDirectoryName(CommonContext.generateRandomDirName())\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .threadingMode(ThreadingMode.SHARED);\n        driver = TestMediaDriver.launch(context, systemTestWatcher);\n        systemTestWatcher.dataCollector().add(driver.context().aeronDirectory());\n\n        aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(context.aeronDirectoryName()));\n    }\n\n    @AfterEach\n    void after()\n    {\n        CloseHelper.closeAll(aeron, driver);\n    }\n\n    @Test\n    void verifySystemCounters()\n    {\n        final CountersReader countersReader = aeron.countersReader();\n        final Int2ObjectHashMap<String> idToLabel = new Int2ObjectHashMap<>();\n        countersReader.forEach((counterId, typeId, keyBuffer, label) ->\n        {\n            if (SystemCounterDescriptor.SYSTEM_COUNTER_TYPE_ID == typeId)\n            {\n                assertEquals(counterId, keyBuffer.getInt(0));\n                idToLabel.put(counterId, label);\n            }\n        });\n\n        for (final SystemCounterDescriptor counter : SystemCounterDescriptor.values())\n        {\n            final String counterLabel = idToLabel.get(counter.id());\n            assertThat(counterLabel, startsWith(counter.label()));\n            assertEquals(counter.id(), countersReader.getCounterRegistrationId(counter.id()));\n            assertEquals(Aeron.NULL_VALUE, countersReader.getCounterOwnerId(counter.id()));\n        }\n    }\n\n    @Test\n    void controlProtocolVersion()\n    {\n        assertEquals(\n            ControlProtocolEvents.CONTROL_PROTOCOL_SEMANTIC_VERSION,\n            aeron.countersReader().getCounterValue(SystemCounterDescriptor.CONTROL_PROTOCOL_VERSION.id()));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/security/SimpleAuthenticatorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nclass SimpleAuthenticatorTest\n{\n    @Test\n    void shouldAuthenticate()\n    {\n        final SessionProxy mockSessionProxy = mock(SessionProxy.class);\n        final long nowMs = 1_000_000L;\n        final long sessionId = 982374;\n\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final byte[] encodedCredentials = \"user:pass\".getBytes(US_ASCII);\n\n        when(mockSessionProxy.sessionId()).thenReturn(sessionId);\n\n        final SimpleAuthenticator simpleAuthenticator = new SimpleAuthenticator.Builder()\n            .principal(encodedPrincipal, encodedCredentials)\n            .newInstance();\n\n        simpleAuthenticator.onConnectRequest(sessionId, encodedCredentials, nowMs);\n        simpleAuthenticator.onConnectedSession(mockSessionProxy, nowMs);\n\n        verify(mockSessionProxy).authenticate(encodedPrincipal);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"user:wrong\", \"wrong:pass\"})\n    void shouldReject(final String incorrectCredentials)\n    {\n        final SessionProxy mockSessionProxy = mock(SessionProxy.class);\n        final long nowMs = 1_000_000L;\n        final long sessionId = 982374;\n\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final byte[] encodedCredentials = \"user:pass\".getBytes(US_ASCII);\n\n        when(mockSessionProxy.sessionId()).thenReturn(sessionId);\n\n        final SimpleAuthenticator simpleAuthenticator = new SimpleAuthenticator.Builder()\n            .principal(encodedPrincipal, encodedCredentials)\n            .newInstance();\n\n        simpleAuthenticator.onConnectRequest(sessionId, incorrectCredentials.getBytes(US_ASCII), nowMs);\n        simpleAuthenticator.onConnectedSession(mockSessionProxy, nowMs);\n        verify(mockSessionProxy).reject();\n    }\n\n    @Test\n    void shouldHandleMultipleConcurrentAuthenticationRequests()\n    {\n        final long nowMs = 9283479L;\n        final String[][] users = {\n            { \"user1\", \"user1:pass1\" },\n            { \"user2\", \"user2:pass2\" },\n            { \"user3\", \"user3:pass3\" },\n            { \"user4\", \"user4:pass4\" },\n            { \"user5\", \"user5:pass5\" },\n        };\n        final SessionProxy mockSessionProxy = mock(SessionProxy.class);\n\n        final SimpleAuthenticator.Builder builder = new SimpleAuthenticator.Builder();\n\n        for (final String[] user : users)\n        {\n            builder.principal(user[0].getBytes(US_ASCII), user[1].getBytes(US_ASCII));\n        }\n\n        final SimpleAuthenticator simpleAuthenticator = builder.newInstance();\n\n        for (int i = 0; i < users.length; i++)\n        {\n            simpleAuthenticator.onConnectRequest(i + 1000, users[i][1].getBytes(US_ASCII), nowMs);\n        }\n\n        for (int i = 0; i < users.length; i++)\n        {\n            when(mockSessionProxy.sessionId()).thenReturn(i + 1000L);\n            simpleAuthenticator.onConnectedSession(mockSessionProxy, nowMs);\n            verify(mockSessionProxy).authenticate(users[i][0].getBytes(US_ASCII));\n        }\n    }\n}"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/security/SimpleAuthorisationServiceTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.security;\n\nimport io.aeron.archive.codecs.MessageHeaderDecoder;\nimport io.aeron.archive.codecs.ReplicateRequest2Decoder;\nimport io.aeron.archive.codecs.StartRecordingRequestDecoder;\nimport io.aeron.archive.codecs.TruncateRecordingRequestDecoder;\nimport io.aeron.cluster.codecs.BackupQueryDecoder;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static io.aeron.security.AuthorisationService.ALLOW_ALL;\nimport static io.aeron.security.AuthorisationService.DENY_ALL;\nimport static java.nio.charset.StandardCharsets.US_ASCII;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n@SuppressWarnings(\"checkstyle:Indentation\")\npublic class SimpleAuthorisationServiceTest\n{\n    public static final int ARCHIVE_PROTOCOL_ID = MessageHeaderDecoder.SCHEMA_ID;\n    public static final int CLUSTER_PROTOCOL_ID = io.aeron.cluster.codecs.MessageHeaderDecoder.SCHEMA_ID;\n    public static final int OTHER_PROTOCOL_ID = 873648576;\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableGeneralAuthorisationForProtocol(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, true)\n            .addGeneralRule(CLUSTER_PROTOCOL_ID, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            OTHER_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, null, encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableGeneralAuthorisationForProtocolAndMessage(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, true)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, null, encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableGeneralAuthorisationForProtocolAndMessageAndType(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n        final String typeAllowed = \"allowed\";\n        final String typeDenied = \"denied\";\n        final String typeUnspecified = \"unspecified\";\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, true)\n            .addGeneralRule(ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeDenied, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeDenied, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeUnspecified, encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableUserSpecificAuthorisationForProtocol(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addPrincipalRule(ARCHIVE_PROTOCOL_ID, encodedPrincipal, true)\n            .addPrincipalRule(CLUSTER_PROTOCOL_ID, encodedPrincipal, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, BackupQueryDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, BackupQueryDecoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            OTHER_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            OTHER_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableUserSpecificAuthorisationForProtocolAndMessage(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addPrincipalRule(ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, encodedPrincipal, true)\n            .addPrincipalRule(ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, encodedPrincipal, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, ReplicateRequest2Decoder.TEMPLATE_ID, \"some resource\", encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldEnableUserSpecificAuthorisationForProtocolAndMessageAndType(final boolean shouldAcceptByDefault)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final AuthorisationService defaultAuthorisation = shouldAcceptByDefault ? ALLOW_ALL : DENY_ALL;\n\n        final String typeAllowed = \"allowed\";\n        final String typeDenied = \"denied\";\n        final String typeUnspecified = \"unspecified\";\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(defaultAuthorisation)\n            .addPrincipalRule(\n                ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal, true)\n            .addPrincipalRule(\n                ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeDenied, encodedPrincipal, false)\n            .newInstance();\n\n        assertTrue(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal));\n\n        assertFalse(simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeDenied, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeUnspecified, encodedPrincipal));\n\n        assertEquals(shouldAcceptByDefault, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = { true, false })\n    void shouldPrioritisePrincipalOverGeneralRules(final boolean accept)\n    {\n        final byte[] encodedPrincipal = \"user\".getBytes(US_ASCII);\n        final byte[] unknownPrincipal = \"unknownUser\".getBytes(US_ASCII);\n        final String typeAllowed = \"allowed\";\n\n        final SimpleAuthorisationService simpleAuthorisationService = new SimpleAuthorisationService.Builder()\n            .defaultAuthorisation(!accept ? ALLOW_ALL : DENY_ALL)\n            .addPrincipalRule(\n                ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal, accept)\n            .addGeneralRule(\n                ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, !accept)\n            .addPrincipalRule(\n                ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, encodedPrincipal, accept)\n            .addGeneralRule(\n                ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, !accept)\n            .addPrincipalRule(\n                CLUSTER_PROTOCOL_ID, encodedPrincipal, accept)\n            .addGeneralRule(\n                CLUSTER_PROTOCOL_ID, !accept)\n            .newInstance();\n\n        assertEquals(accept, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, encodedPrincipal));\n        assertEquals(accept, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, null, encodedPrincipal));\n        assertEquals(accept, simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, BackupQueryDecoder.TEMPLATE_ID, null, encodedPrincipal));\n\n        assertEquals(!accept, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, StartRecordingRequestDecoder.TEMPLATE_ID, typeAllowed, unknownPrincipal));\n        assertEquals(!accept, simpleAuthorisationService.isAuthorised(\n            ARCHIVE_PROTOCOL_ID, TruncateRecordingRequestDecoder.TEMPLATE_ID, null, unknownPrincipal));\n        assertEquals(!accept, simpleAuthorisationService.isAuthorised(\n            CLUSTER_PROTOCOL_ID, BackupQueryDecoder.TEMPLATE_ID, null, unknownPrincipal));\n    }\n}\n"
  },
  {
    "path": "aeron-system-tests/src/test/java/io/aeron/test/driver/TestMediaDriverTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.CommonContext;\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.IoUtil;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\nclass TestMediaDriverTest\n{\n    @Test\n    void countersReaderReturnsTheSameInstanceForTheEntireLifetimeOfTheDriver()\n    {\n        final File aeronDirectory;\n        final MediaDriver.Context context = new MediaDriver.Context().dirDeleteOnStart(true).dirDeleteOnShutdown(false);\n        try (TestMediaDriver driver = TestMediaDriver.launch(context, null))\n        {\n            aeronDirectory = driver.context().aeronDirectory();\n            assertNotNull(aeronDirectory);\n\n            final CountersReader countersReader = driver.counters();\n            assertNotNull(countersReader);\n            assertSame(countersReader, driver.counters());\n        }\n\n        assertTrue(aeronDirectory.exists());\n        IoUtil.delete(aeronDirectory, false);\n        assertFalse(aeronDirectory.exists());\n    }\n\n    @Test\n    void connectToCMediaDriverWithoutSpecifyingAeronDir()\n    {\n        assumeTrue(TestMediaDriver.shouldRunCMediaDriver());\n\n        final MediaDriver.Context context = new MediaDriver.Context().dirDeleteOnStart(true).dirDeleteOnShutdown(false);\n        assertEquals(CommonContext.getAeronDirectoryName(), context.aeronDirectoryName());\n        try (TestMediaDriver mediaDriver = CTestMediaDriver.launch(context, false, null);\n            Aeron aeron = Aeron.connect(new Aeron.Context().aeronDirectoryName(context.aeronDirectoryName())))\n        {\n            final File aeronDirectory = aeron.context().aeronDirectory();\n            assertNotNull(aeronDirectory);\n            assertTrue(aeronDirectory.exists());\n            assertNotNull(mediaDriver);\n        }\n        finally\n        {\n            context.deleteDirectory();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/README.md",
    "content": "Aeron Test Support\n===\n\nUtility classes and functions common to unit and system tests."
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/AdjustableClock.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.NanoClock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.SystemNanoClock;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\n\npublic class AdjustableClock implements NanoClock, EpochClock\n{\n    private final AtomicLong nanoCallCount = new AtomicLong(0);\n    private final AtomicLong epochCallCount = new AtomicLong(0);\n    private final EpochClock baseEpochClock = SystemEpochClock.INSTANCE;\n    private final NanoClock baseNanoClock = SystemNanoClock.INSTANCE;\n    private final AtomicLong offsetNs = new AtomicLong(0);\n\n    public long time()\n    {\n        return baseEpochClock.time() + TimeUnit.NANOSECONDS.toMillis(offsetNs.get());\n    }\n\n    public long nanoTime()\n    {\n        nanoCallCount.incrementAndGet();\n        return baseNanoClock.nanoTime() + offsetNs.get();\n    }\n\n    public void incrementOffsetNs(final long incrementNs)\n    {\n        epochCallCount.incrementAndGet();\n        offsetNs.addAndGet(incrementNs);\n    }\n\n    public long epochCallCount()\n    {\n        return epochCallCount.get();\n    }\n\n    public long nanoCallCount()\n    {\n        return nanoCallCount.get();\n    }\n\n    public void slewTimeDelta(final long timeoutNs, final long incrementNs)\n    {\n        final long nowMs = time();\n        while (time() < (nowMs + TimeUnit.NANOSECONDS.toMillis(timeoutNs)))\n        {\n            incrementOffsetNs(incrementNs);\n            final long count = nanoCallCount();\n            while (nanoCallCount() < count + 2)\n            {\n                Tests.yieldingIdle(\"count=\" + count + \", nanoCallCount=\" + nanoCallCount());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/BindingsTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.Tag;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target({ ElementType.TYPE, ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@Tag(\"bindings\")\npublic @interface BindingsTest\n{\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/CapturingPrintStream.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.PrintStream;\n\nimport static java.nio.charset.StandardCharsets.US_ASCII;\n\npublic class CapturingPrintStream\n{\n    private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n    private final PrintStream printStream = new PrintStream(byteArrayOutputStream);\n\n    public PrintStream resetAndGetPrintStream()\n    {\n        byteArrayOutputStream.reset();\n        return printStream;\n    }\n\n    public String flushAndGetContent()\n    {\n        printStream.flush();\n        return byteArrayOutputStream.toString(US_ASCII);\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/CountersAnswer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport io.aeron.Counter;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\n/**\n * Provide a Mockito Answer that will allow a mapping of Aeron.addCounter onto a CountersManager.\n */\npublic final class CountersAnswer implements Answer<Counter>\n{\n    private final CountersManager countersManager;\n\n    private CountersAnswer(final CountersManager countersManager)\n    {\n        this.countersManager = countersManager;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public Counter answer(final InvocationOnMock invocation)\n    {\n        final int counterId;\n        if (2 == invocation.getArguments().length)\n        {\n            counterId = countersManager.allocate(\n                invocation.getArgument(0, String.class),\n                invocation.getArgument(1, Integer.class));\n        }\n        else if (7 == invocation.getArguments().length)\n        {\n            counterId = countersManager.allocate(\n                invocation.getArgument(0, Integer.class),\n                invocation.getArgument(1, DirectBuffer.class),\n                invocation.getArgument(2, Integer.class),\n                invocation.getArgument(3, Integer.class),\n                invocation.getArgument(4, DirectBuffer.class),\n                invocation.getArgument(5, Integer.class),\n                invocation.getArgument(6, Integer.class));\n        }\n        else\n        {\n            throw new RuntimeException(\n                \"Unexpected number of arguments, should be used for Aeron::addCounter(String, int) or \" +\n                \"Aeron::addCounter(int, DirectBuffer, int, int, DirectBuffer, int, int)\");\n        }\n\n        return new Counter(countersManager, 0, counterId);\n    }\n\n    /**\n     * Set up an answer that will map addCounter requests from addCounter methods (generally from a mocked\n     * Aeron instance).\n     *\n     * @param countersManager delegate to pass addCounter requests to.\n     * @return an Answer that will use the supplied {@link CountersManager}.\n     */\n    public static CountersAnswer mapTo(final CountersManager countersManager)\n    {\n        return new CountersAnswer(countersManager);\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/CountingFragmentHandler.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\n\npublic final class CountingFragmentHandler implements FragmentHandler\n{\n    private final String name;\n    private int lastCheckedTargetValue = 0;\n    private int received = 0;\n\n    public CountingFragmentHandler(final String name)\n    {\n        this.name = name;\n    }\n\n    public boolean notDone(final int targetValue)\n    {\n        lastCheckedTargetValue = targetValue;\n        return targetValue != received;\n    }\n\n    public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)\n    {\n        received++;\n    }\n\n    public String toString()\n    {\n        return \"CountingFragmentHandler{\" +\n            \"name='\" + name + '\\'' +\n            \", received=\" + received +\n            \", lastCheckedTargetValue=\" + lastCheckedTargetValue +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/DataCollector.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport io.aeron.CncFileDescriptor;\nimport org.agrona.LangUtil;\nimport org.agrona.SystemUtil;\n\nimport java.io.File;\nimport java.io.FileFilter;\nimport java.io.IOException;\nimport java.nio.file.*;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Predicate;\n\nimport static java.nio.file.LinkOption.NOFOLLOW_LINKS;\nimport static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;\nimport static java.nio.file.StandardCopyOption.REPLACE_EXISTING;\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.toList;\nimport static org.agrona.Strings.isEmpty;\n\n/**\n * {@code DataCollector} is a helper class to preserve data upon test failure.\n */\npublic final class DataCollector\n{\n    static final String THREAD_DUMP_FILE_NAME = \"thread_dump.txt\";\n    static final AtomicInteger UNIQUE_ID = new AtomicInteger(0);\n    static final Predicate<Path> BLANK_TEMPLATE_FILTER =\n        (path) -> \"blank.template\".equals(path.getFileName().toString());\n    static final Predicate<Path> RECORDING_FILE_FILTER =\n        (path) -> path.getFileName().toString().endsWith(\".rec\");\n    private static final Predicate<Path> DATA_COLLECTED_DEFAULT_FILE_FILTER =\n        BLANK_TEMPLATE_FILTER.negate();\n    private final Path rootDir;\n    private final Set<Path> locations = new LinkedHashSet<>();\n    private final Set<Path> cleanupLocations = new LinkedHashSet<>();\n    private Predicate<Path> fileFilter = DATA_COLLECTED_DEFAULT_FILE_FILTER;\n\n    public DataCollector()\n    {\n        this(Paths.get(\"build/test-output\"));\n    }\n\n    public DataCollector(final Path rootDir)\n    {\n        requireNonNull(rootDir);\n        if (Files.exists(rootDir) && !Files.isDirectory(rootDir))\n        {\n            throw new IllegalArgumentException(rootDir + \" is not a directory\");\n        }\n        this.rootDir = rootDir;\n    }\n\n    /**\n     * Add a file/directory to be preserved.\n     *\n     * @param location file or directory to preserve.\n     * @see #dumpData(String, byte[])\n     */\n    public void add(final Path location)\n    {\n        locations.add(requireNonNull(location));\n    }\n\n    /**\n     * Add a file/directory to be preserved. Converting from a File to a Path if not null.\n     *\n     * @param location file or directory to preserve.\n     * @see #dumpData(String, byte[])\n     */\n    public void add(final File location)\n    {\n        if (null != location)\n        {\n            add(location.toPath());\n        }\n    }\n\n    /**\n     * Add a location to be cleaned up.\n     *\n     * @param location to be added to the list of cleanup locations.\n     */\n    public void addForCleanup(final File location)\n    {\n        if (null != location)\n        {\n            addForCleanup(location.toPath());\n        }\n    }\n\n    /**\n     * Add a location to be cleaned up.\n     *\n     * @param location to be added to the list of cleanup locations.\n     */\n    public void addForCleanup(final Path location)\n    {\n        cleanupLocations.add(Objects.requireNonNull(location));\n    }\n\n    /**\n     * Copy data from all the added locations to the directory {@code $rootDir/$destinationDir}, where:\n     * <ul>\n     *     <li>{@code $rootDir} is the root directory specified when {@link #DataCollector} was created.</li>\n     *     <li>{@code $destinationDir} is the destination directory name.</li>\n     * </ul>\n     * <p>\n     *     <em>\n     *     Note: If the destination directory already exists then a unique ID suffix will be added to the name.\n     *     For example given that root directory is {@code build/test-output} and the destination directory is\n     *     {@code my-dir} the actual directory could be {@code build/test-output/my-dir_5}, where {@code _5} is the\n     *     suffix added.\n     *     </em>\n     * </p>\n     *\n     * @param destinationDir destination directory where the data should be copied into.\n     * @param threadDump     bytes representing stacktraces of all running threads in the system, i.e.\n     *                       {@link SystemUtil#threadDump()}.\n     * @return {@code null} if no data was copied or an actual destination directory used.\n     */\n    Path dumpData(final String destinationDir, final byte[] threadDump)\n    {\n        if (isEmpty(destinationDir))\n        {\n            throw new IllegalArgumentException(\"destination dir is required\");\n        }\n        Objects.requireNonNull(threadDump);\n\n        return copyData(destinationDir, threadDump);\n    }\n\n    /**\n     * Find all the driver cnc files.\n     *\n     * @return list of paths to collected driver cnc files.\n     */\n    public List<Path> cncFiles()\n    {\n        return findMatchingFiles(file -> CncFileDescriptor.CNC_FILE.equals(file.getName()));\n    }\n\n    /**\n     * Find all mark files for specific dissector.\n     *\n     * @param dissector to use as a filter.\n     * @return list of paths to the associated mark files.\n     */\n    public List<Path> markFiles(final SystemTestWatcher.MarkFileDissector dissector)\n    {\n        return findMatchingFiles(dissector::isRelevantFile);\n    }\n\n    /**\n     * Gets a collection of all registered locations.\n     *\n     * @return list of all locations.\n     */\n    public Collection<Path> allLocations()\n    {\n        return locations;\n    }\n\n    /**\n     * Returns all the locations that need to be deleted. This method will use any locations added for collection\n     * as well as those added for clean up.\n     *\n     * @return collection of locations that need to be removed.\n     */\n    public Collection<Path> cleanupLocations()\n    {\n        final ArrayList<Path> cleanupLocations = new ArrayList<>();\n        cleanupLocations.addAll(this.cleanupLocations);\n        cleanupLocations.addAll(this.locations);\n        return Collections.unmodifiableList(cleanupLocations);\n    }\n\n    private List<Path> findMatchingFiles(final FileFilter filter)\n    {\n        final List<Path> found = new ArrayList<>();\n        for (final Path location : locations)\n        {\n            find(found, location.toFile(), filter);\n        }\n        return found;\n    }\n\n    private static void find(final List<Path> found, final File file, final FileFilter filter)\n    {\n        if (existsAndIsNotSymbolicLink(file))\n        {\n            if (file.isFile())\n            {\n                if (filter.accept(file))\n                {\n                    found.add(file.toPath());\n                }\n            }\n            else if (file.isDirectory())\n            {\n                final File[] files = file.listFiles();\n                if (null != files)\n                {\n                    for (final File f : files)\n                    {\n                        find(found, f, filter);\n                    }\n                }\n            }\n        }\n    }\n\n    private static boolean existsAndIsNotSymbolicLink(final File file)\n    {\n        try\n        {\n            final BasicFileAttributes basicFileAttributes = Files.readAttributes(\n                file.toPath(), BasicFileAttributes.class, NOFOLLOW_LINKS);\n            return !basicFileAttributes.isSymbolicLink() && file.exists();\n        }\n        catch (final IOException ex)\n        {\n            return false;\n        }\n    }\n\n    public String toString()\n    {\n        return \"DataCollector{\" +\n            \"rootDir=\" + rootDir +\n            \", locations=\" + locations +\n            '}';\n    }\n\n    /**\n     * Reset the capture filter so all files are captured.\n     */\n    public void captureAllFiles()\n    {\n        fileFilter = (file) -> true;\n    }\n\n    /**\n     * Add a specific exclusion for a file to be captured.\n     *\n     * @param fileExclusion predicate that returns true of a specific file should not be captured.\n     */\n    public void addFileExclusion(final Predicate<Path> fileExclusion)\n    {\n        fileFilter = fileFilter.and(fileExclusion.negate());\n    }\n\n    private Path copyData(final String destinationDir, final byte[] threadDump)\n    {\n        final boolean isInterrupted = Thread.interrupted();\n        final List<Path> locations = this.locations.stream().filter(Files::exists).collect(toList());\n        if (locations.isEmpty())\n        {\n            return null;\n        }\n\n        try\n        {\n            final Path destination = createUniqueDirectory(destinationDir);\n            final Map<Path, Set<Path>> groups = groupByParent(locations);\n            for (final Map.Entry<Path, Set<Path>> group : groups.entrySet())\n            {\n                final Set<Path> files = group.getValue();\n                final Path parent = adjustParentToEnsureUniqueContext(destination, files, group.getKey());\n                for (final Path srcFile : files)\n                {\n                    final Path dstFile = destination.resolve(parent.relativize(srcFile));\n                    copyFiles(srcFile, dstFile);\n                }\n            }\n\n            Files.write(destination.resolve(THREAD_DUMP_FILE_NAME), threadDump);\n            Tests.dumpCollectedLogs(destination.resolve(\"events.log\").toString());\n\n            return destination;\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n        finally\n        {\n            if (isInterrupted)\n            {\n                Thread.currentThread().interrupt();\n            }\n        }\n        return null;\n    }\n\n    private Path createUniqueDirectory(final String name) throws IOException\n    {\n        Path path;\n        try\n        {\n            path = rootDir.resolve(name);\n        }\n        catch (final InvalidPathException ex)\n        {\n            throw new IOException(\"Unable to resolve path for name=\" + name);\n        }\n\n        while (Files.exists(path))\n        {\n            path = rootDir.resolve(name + \"-\" + UNIQUE_ID.incrementAndGet());\n        }\n\n        return Files.createDirectories(path);\n    }\n\n    private Map<Path, Set<Path>> groupByParent(final List<Path> locations)\n    {\n        final LinkedHashMap<Path, Set<Path>> map = new LinkedHashMap<>();\n        for (final Path p : locations)\n        {\n            map.put(p, Set.of(p));\n        }\n\n        removeNestedPaths(locations, map);\n\n        return groupByParent(map);\n    }\n\n    private void removeNestedPaths(final List<Path> locations, final LinkedHashMap<Path, Set<Path>> map)\n    {\n        for (final Path p : locations)\n        {\n            Path parent = p.getParent();\n            while (null != parent)\n            {\n                if (map.containsKey(parent))\n                {\n                    map.remove(p);\n                    break;\n                }\n                parent = parent.getParent();\n            }\n        }\n    }\n\n    private LinkedHashMap<Path, Set<Path>> groupByParent(final LinkedHashMap<Path, Set<Path>> locations)\n    {\n        if (1 == locations.size())\n        {\n            return locations;\n        }\n\n        final LinkedHashMap<Path, Set<Path>> result = new LinkedHashMap<>();\n        final Set<Path> processed = new HashSet<>();\n        boolean recurse = false;\n        for (final Map.Entry<Path, Set<Path>> e1 : locations.entrySet())\n        {\n            final Path path1 = e1.getKey();\n            if (processed.add(path1))\n            {\n                boolean found = false;\n                final Path parent = path1.getParent();\n                if (null != parent && !parent.equals(path1.getRoot()))\n                {\n                    for (final Map.Entry<Path, Set<Path>> e2 : locations.entrySet())\n                    {\n                        final Path path2 = e2.getKey();\n                        if (!processed.contains(path2) && path2.startsWith(parent))\n                        {\n                            found = true;\n                            processed.add(path2);\n                            final Set<Path> children = result.computeIfAbsent(parent, (key) -> new HashSet<>());\n                            children.addAll(e1.getValue());\n                            children.addAll(e2.getValue());\n                        }\n                    }\n                }\n\n                if (!found)\n                {\n                    result.put(path1, e1.getValue());\n                }\n                recurse = recurse || found;\n            }\n        }\n\n        if (recurse)\n        {\n            return groupByParent(result);\n        }\n\n        return locations;\n    }\n\n    private Path adjustParentToEnsureUniqueContext(final Path destination, final Set<Path> files, final Path root)\n    {\n        Path parent = root;\n        for (final Path srcFile : files)\n        {\n            while (true)\n            {\n                final Path dst = destination.resolve(parent.relativize(srcFile));\n                if (!Files.exists(dst))\n                {\n                    break;\n                }\n                parent = parent.getParent();\n            }\n        }\n\n        return parent;\n    }\n\n    private void copyFiles(final Path src, final Path dst) throws IOException\n    {\n        if (Files.isRegularFile(src))\n        {\n            ensurePathExists(dst);\n\n            if (fileFilter.test(src))\n            {\n                Files.copy(src, dst, COPY_ATTRIBUTES, REPLACE_EXISTING);\n            }\n        }\n        else\n        {\n            Files.walkFileTree(\n                src,\n                new SimpleFileVisitor<>()\n                {\n                    public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs)\n                    {\n                        try\n                        {\n                            final Path dstDir = dst.resolve(src.relativize(dir));\n                            ensurePathExists(dstDir);\n                            Files.copy(dir, dstDir, COPY_ATTRIBUTES);\n                        }\n                        catch (final IOException e)\n                        {\n                            System.err.println(\"Failed to copy directory \" + dir + \", skipping.\");\n                        }\n\n                        return FileVisitResult.CONTINUE;\n                    }\n\n                    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)\n                    {\n                        try\n                        {\n                            if (fileFilter.test(file))\n                            {\n                                Files.copy(file, dst.resolve(src.relativize(file)), COPY_ATTRIBUTES);\n                            }\n                        }\n                        catch (final IOException e)\n                        {\n                            System.err.println(\"Failed to copy file \" + file + \", skipping.\");\n                        }\n\n                        return FileVisitResult.CONTINUE;\n                    }\n\n                    public FileVisitResult visitFileFailed(final Path file, final IOException exc)\n                    {\n                        // Ignore failure visiting file.\n                        return FileVisitResult.CONTINUE;\n                    }\n                });\n        }\n    }\n\n    private void ensurePathExists(final Path dst) throws IOException\n    {\n        if (!Files.exists(dst.getParent()))\n        {\n            Files.createDirectories(dst.getParent());\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/DisableJavaUtilLogging.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport java.util.logging.LogManager;\n\n/**\n * This is an implementation of the {@link LogManager} that disables java util logging completely.\n * In order to work it must be configured via the system property {@code java.util.logging.config.class}.\n */\npublic class DisableJavaUtilLogging extends LogManager\n{\n    @SuppressWarnings(\"this-escape\")\n    public DisableJavaUtilLogging()\n    {\n        reset(); // Close all logging handlers\n    }\n}\n\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/EventLogExtension.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport java.lang.reflect.Method;\n\n/**\n * JUnit framework extension used to start and reset the CollectingEventLogReaderAgent.\n */\npublic class EventLogExtension implements BeforeEachCallback, AfterEachCallback\n{\n    /**\n     * {@inheritDoc}\n     */\n    public void beforeEach(final ExtensionContext context)\n    {\n        final String className = context.getTestClass().map(Class::getSimpleName).orElse(\"<UNKNOWN>\");\n        final String methodName = context.getTestMethod().map(Method::getName).orElse(context.getDisplayName());\n\n        Tests.startLogCollecting(\"TEST: \" + className + \".\" + methodName);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public void afterEach(final ExtensionContext context)\n    {\n        Tests.resetLogCollecting();\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/HideStdErrExtension.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport java.io.PrintStream;\n\npublic class HideStdErrExtension implements BeforeEachCallback, AfterEachCallback\n{\n    private final PrintStream nullPrintStream = new PrintStream(new NullOutputStream());\n    private PrintStream originalStream = null;\n\n    public void beforeEach(final ExtensionContext context)\n    {\n        if (context.getTestMethod().isPresent() &&\n            null != context.getTestMethod().get().getAnnotation(IgnoreStdErr.class))\n        {\n            originalStream = System.err;\n            System.setErr(nullPrintStream);\n        }\n    }\n\n    public void afterEach(final ExtensionContext context)\n    {\n        if (null != originalStream)\n        {\n            System.setErr(originalStream);\n            originalStream = null;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/IgnoreStdErr.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target({ ElementType.TYPE, ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface IgnoreStdErr\n{\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/InterruptAfter.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.concurrent.TimeUnit;\n\n@Target({ ElementType.TYPE, ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface InterruptAfter\n{\n    long value();\n\n    TimeUnit unit() default TimeUnit.SECONDS;\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/InterruptingTestCallback.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.platform.commons.util.RuntimeUtils;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\n\npublic class InterruptingTestCallback implements BeforeEachCallback, AfterEachCallback\n{\n    private static final boolean TIMEOUT_DISABLED_ON_DEBUG =\n        !\"enabled\".equals(System.getProperty(Timeout.TIMEOUT_MODE_PROPERTY_NAME));\n\n    private static final ScheduledExecutorService SCHEDULER = Executors.newSingleThreadScheduledExecutor(\n        (runnable) ->\n        {\n            final Thread thread = new Thread(runnable);\n            thread.setDaemon(true);\n            thread.setName(\"interrupting-test-callback\");\n\n            return thread;\n        });\n\n    private ScheduledFuture<?> timer = null;\n\n    public void afterEach(final ExtensionContext context)\n    {\n        if (null != timer)\n        {\n            timer.cancel(false);\n            if (!timer.isDone())\n            {\n                try\n                {\n                    timer.get();\n                }\n                catch (final InterruptedException | ExecutionException ignore)\n                {\n                }\n            }\n\n            timer = null;\n        }\n    }\n\n    public void beforeEach(final ExtensionContext context)\n    {\n        timer = null;\n        final InterruptAfter annotation = context.getRequiredTestMethod().getAnnotation(InterruptAfter.class);\n\n        if (null != annotation && !(RuntimeUtils.isDebugMode() && TIMEOUT_DISABLED_ON_DEBUG))\n        {\n            final Thread testThread = Thread.currentThread();\n\n            long delayMs = annotation.unit().toMillis(annotation.value());\n            if (SystemTestConfig.DRIVER_AWAIT_COUNTER_CLOSE)\n            {\n                delayMs = Math.max(delayMs, SystemTestConfig.MIN_COUNTER_CLOSE_INTERRUPT_TIMEOUT_MS);\n            }\n\n            timer = SCHEDULER.schedule(testThread::interrupt, delayMs, TimeUnit.MILLISECONDS);\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/IpTables.java",
    "content": "/*\n * Copyright 2014-2024 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.agrona.Strings;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class IpTables\n{\n    public static boolean runIpTablesCmd(final boolean ignoreError, final List<String> command)\n    {\n        try\n        {\n            final Process start = new ProcessBuilder(command)\n                .redirectErrorStream(true)\n                .start();\n\n            final ByteArrayOutputStream commandOutput = new ByteArrayOutputStream();\n            try (InputStream inputStream = start.getInputStream())\n            {\n                final byte[] block = new byte[4096];\n                while (start.isAlive())\n                {\n                    final int read = inputStream.read(block);\n                    if (0 < read)\n                    {\n                        commandOutput.write(block, 0, read);\n                    }\n                    Tests.yield();\n                }\n            }\n\n            final boolean isSuccess = 0 == start.exitValue();\n            if (!isSuccess && !ignoreError)\n            {\n                final String commandMsg = commandOutput.toString(StandardCharsets.UTF_8);\n                throw new RuntimeException(\"Command: '\" + String.join(\" \", command) + \"' failed - \" + commandMsg);\n            }\n\n            return isSuccess;\n        }\n        catch (final IOException ex)\n        {\n            throw new RuntimeException(ex);\n        }\n    }\n\n    public static void deleteChain(final String chainName)\n    {\n        runIpTablesCmd(true, List.of(\"sudo\", \"iptables\", \"-X\", chainName));\n    }\n\n    public static void removeFromInput(final String chainName)\n    {\n        final List<String> command = List.of(\"sudo\", \"-n\", \"iptables\", \"--delete\", \"INPUT\", \"--jump\", chainName);\n        boolean isSuccess;\n        do\n        {\n            isSuccess = runIpTablesCmd(true, command);\n        }\n        while (isSuccess);\n    }\n\n    public static void addToInput(final String chainName)\n    {\n        runIpTablesCmd(true, List.of(\"sudo\", \"-n\", \"iptables\", \"--insert\", \"INPUT\", \"--jump\", chainName));\n    }\n\n    public static void makeSymmetricNetworkPartition(\n        final String chainName, final List<String> groupA, final List<String> groupB)\n    {\n        for (final String hostA : groupA)\n        {\n            for (final String hostB : groupB)\n            {\n                dropUdpTrafficBetweenHosts(chainName, hostB, \"\", hostA, \"\");\n            }\n            for (final String hostB : groupB)\n            {\n                dropUdpTrafficBetweenHosts(chainName, hostA, \"\", hostB, \"\");\n            }\n        }\n    }\n\n    public static void dropUdpTrafficBetweenHosts(\n        final String chainName,\n        final String srcHostname,\n        final String srcPort,\n        final String destHostname,\n        final String destPort)\n    {\n        final List<String> command = new ArrayList<>();\n        command.add(\"sudo\");\n        command.add(\"-n\");\n        command.add(\"iptables\");\n        command.add(\"--insert\");\n        command.add(chainName);\n        command.add(\"--ipv4\");\n        command.add(\"--protocol\");\n        command.add(\"udp\");\n\n        command.add(\"--source\");\n        command.add(srcHostname);\n        if (!Strings.isEmpty(srcPort))\n        {\n            command.add(\"--source-port\");\n            command.add(srcPort);\n        }\n\n        command.add(\"--destination\");\n        command.add(destHostname);\n        if (!Strings.isEmpty(destPort))\n        {\n            command.add(\"--destination-port\");\n            command.add(destPort);\n        }\n\n        command.add(\"--jump\");\n        command.add(\"DROP\");\n\n        runIpTablesCmd(false, command);\n    }\n\n    public static void createChain(final String chainName)\n    {\n        runIpTablesCmd(true, List.of(\"sudo\", \"-n\", \"iptables\", \"--new-chain\", chainName));\n        runIpTablesCmd(false, List.of(\"sudo\", \"-n\", \"iptables\", \"--append\", chainName, \"--jump\", \"RETURN\"));\n    }\n\n    public static void flushChain(final String chainName)\n    {\n        runIpTablesCmd(true, List.of(\"sudo\", \"-n\", \"iptables\", \"--flush\", chainName));\n    }\n\n    public static void setupChain(final String chainName)\n    {\n        createChain(chainName);\n        flushChain(chainName);\n        addToInput(chainName);\n    }\n\n    public static void tearDownChain(final String chainName)\n    {\n        flushChain(chainName);\n        removeFromInput(chainName);\n        deleteChain(chainName);\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/MediaDriverTestUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.IoUtil;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.nio.file.Files;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nclass MediaDriverTestUtil\n{\n    private final Map<String, ProcessDetails> outputFilesByAeronDirectoryName = new LinkedHashMap<>();\n\n    public void testFailed()\n    {\n        dumpMediaDriverDiagnostics();\n    }\n\n    private void dumpMediaDriverDiagnostics()\n    {\n        if (TestMediaDriver.shouldRunCMediaDriver())\n        {\n            outputFilesByAeronDirectoryName.forEach(\n                (aeronDirectoryName, files) ->\n                {\n                    try\n                    {\n                        System.out.println();\n                        System.out.println(\n                            \"Media Driver: \" + aeronDirectoryName +\n                            \", exit code: \" + files.exitValue +\n                            \" (\" + files.exitMessage + \")\");\n                        System.out.println();\n                        Tests.printDirectoryContents(aeronDirectoryName, System.out);\n                        System.out.println();\n                        printEnvironment(files.environment, System.out);\n                        System.out.println();\n                        System.out.println(\"*** STDOUT ***\");\n                        Files.copy(files.stdout.toPath(), System.out);\n                        System.out.println();\n                        System.out.println(\"*** STDERR ***\");\n                        Files.copy(files.stderr.toPath(), System.out);\n                        System.out.println(\"====\");\n                    }\n                    catch (final IOException ex)\n                    {\n                        throw new RuntimeException(\"Failed to output logs to stdout\", ex);\n                    }\n                });\n\n            deleteFiles();\n        }\n    }\n\n    private void printEnvironment(final Map<String, String> environment, final PrintStream out)\n    {\n        environment.forEach(\n            (name, value) ->\n            {\n                out.print(name);\n                out.print('=');\n                out.print(value);\n                out.println();\n            });\n    }\n\n    public void testSuccessful()\n    {\n        if (TestMediaDriver.shouldRunCMediaDriver())\n        {\n            deleteFiles();\n        }\n    }\n\n    public void afterTestExecution(final ExtensionContext context)\n    {\n        if (null == context.getRequiredTestMethod().getAnnotation(IgnoreStdErr.class))\n        {\n            verifyNoStdError();\n        }\n    }\n\n    private void deleteFiles()\n    {\n        outputFilesByAeronDirectoryName.forEach(\n            (aeronDirectoryName, files) ->\n            {\n                IoUtil.delete(files.stdout, true);\n                IoUtil.delete(files.stderr, true);\n            });\n    }\n\n    public void outputFiles(final String aeronDirectoryName, final File stdoutFile, final File stderrFile)\n    {\n        outputFilesByAeronDirectoryName.put(aeronDirectoryName, new ProcessDetails(stdoutFile, stderrFile));\n    }\n\n    public void exitCode(final String aeronDirectoryName, final int exitValue, final String exitMessage)\n    {\n        outputFilesByAeronDirectoryName.get(aeronDirectoryName).exitValue(exitValue, exitMessage);\n    }\n\n    public void environmentVariables(final String aeronDirectoryName, final Map<String, String> environment)\n    {\n        outputFilesByAeronDirectoryName.get(aeronDirectoryName).environment(environment);\n    }\n\n    public void verifyNoStdError()\n    {\n        outputFilesByAeronDirectoryName.values().forEach(\n            processDetails -> Assertions.assertEquals(0, processDetails.stderr.length(), \"stderr contains data\"));\n    }\n\n    static final class ProcessDetails\n    {\n        private final File stderr;\n        private final File stdout;\n        private int exitValue;\n        private String exitMessage;\n        private Map<String, String> environment;\n\n        ProcessDetails(final File stdout, final File stderr)\n        {\n            this.stderr = stderr;\n            this.stdout = stdout;\n        }\n\n        public void exitValue(final int exitValue, final String exitMessage)\n        {\n            this.exitValue = exitValue;\n            this.exitMessage = exitMessage;\n        }\n\n        public void environment(final Map<String, String> environment)\n        {\n            this.environment = environment;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/NetworkTestingUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport java.net.InetAddress;\nimport java.net.ServerSocket;\n\npublic class NetworkTestingUtil\n{\n    /**\n     * Return an error message if this address can't be bound, null if successful.\n     *\n     * @param address The address to attempt to bind to.\n     * @return null if successful, error message otherwise.\n     */\n    @SuppressWarnings(\"try\")\n    public static String isBindAddressAvailable(final String address)\n    {\n        try (ServerSocket ignored = new ServerSocket(0, 1024, InetAddress.getByName(address)))\n        {\n            return null;\n        }\n        catch (final Exception ex)\n        {\n            return \"Binding to \" + address + \" failed, error: \" + ex.getMessage();\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/NullOutputStream.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport java.io.OutputStream;\n\npublic class NullOutputStream extends OutputStream\n{\n    public void write(final int ignore)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/RandomWatcher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.extension.TestWatcher;\n\nimport java.util.Random;\n\npublic class RandomWatcher implements TestWatcher\n{\n    private final Random random;\n    private final long seed;\n\n    public RandomWatcher(final long seed)\n    {\n        this.seed = seed;\n        random = new Random(seed);\n    }\n\n    public RandomWatcher()\n    {\n        this(System.nanoTime());\n    }\n\n    public void testFailed(final ExtensionContext context, final Throwable cause)\n    {\n        System.err.println(context.getDisplayName() + \" failed with random seed: \" + seed);\n    }\n\n    public Random random()\n    {\n        return random;\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/SlowTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.Tag;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target({ ElementType.TYPE, ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@Tag(\"slow\")\npublic @interface SlowTest\n{\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/SystemTestConfig.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\npublic class SystemTestConfig\n{\n    public static final String DRIVER_AWAIT_COUNTER_CLOSE_PROP_NAME = \"aeron.test.system.driver.await.counters\";\n    public static final boolean DRIVER_AWAIT_COUNTER_CLOSE = Boolean.getBoolean(DRIVER_AWAIT_COUNTER_CLOSE_PROP_NAME);\n    public static final long MIN_COUNTER_CLOSE_INTERRUPT_TIMEOUT_MS = 20_000L;\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/SystemTestWatcher.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport io.aeron.CommonContext;\nimport io.aeron.archive.ArchiveMarkFile;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.cluster.service.ClusterTerminationException;\nimport io.aeron.samples.SamplesUtil;\nimport io.aeron.test.cluster.TestCluster;\nimport io.aeron.test.driver.DriverOutputConsumer;\nimport org.agrona.CloseHelper;\nimport org.agrona.IoUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.SemanticVersion;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.MutableReference;\nimport org.agrona.collections.Object2ObjectHashMap;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.errors.ErrorConsumer;\nimport org.agrona.concurrent.errors.ErrorLogReader;\nimport org.agrona.concurrent.ringbuffer.RingBufferDescriptor;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.AfterTestExecutionCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.opentest4j.AssertionFailedError;\nimport org.opentest4j.TestAbortedException;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.net.UnknownHostException;\nimport java.nio.MappedByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Predicate;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static io.aeron.CncFileDescriptor.*;\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class SystemTestWatcher implements\n    DriverOutputConsumer,\n    AfterTestExecutionCallback,\n    BeforeEachCallback,\n    AfterEachCallback\n{\n    public static final Pattern PARAMETERISED_TEST_INDEX_PATTERN = Pattern.compile(\"\\\\[([0-9]+)].*\");\n    private static final String CLUSTER_TERMINATION_EXCEPTION = ClusterTerminationException.class.getName();\n    private static final String UNKNOWN_HOST_EXCEPTION = UnknownHostException.class.getName();\n    private static final String ATS_GCM_DECRYPT_ERROR =\n        \"ats_gcm_decrypt final_ex: error:00000000:lib(0):func(0):reason(0)\";\n    private static final String ATS_GCM_DECRYPT_ERROR_OTHER =\n        \"ats_gcm_decrypt final_ex: error:00000000:lib(0)::reason(0)\";\n    public static final Predicate<String> UNKNOWN_HOST_FILTER =\n        (s) -> s.contains(UNKNOWN_HOST_EXCEPTION) || s.contains(\"unknown host\");\n    public static final Predicate<String> WARNING_FILTER = (s) -> s.contains(\"WARN\");\n    public static final Predicate<String> CLUSTER_TERMINATION_FILTER =\n        (s) -> s.contains(CLUSTER_TERMINATION_EXCEPTION);\n    public static final Predicate<String> ATS_GCM_DECRYPT_ERROR_FILTER =\n        (s) -> s.contains(ATS_GCM_DECRYPT_ERROR) || s.contains(ATS_GCM_DECRYPT_ERROR_OTHER);\n    public static final Predicate<String> TEST_CLUSTER_DEFAULT_LOG_FILTER =\n        WARNING_FILTER.negate()\n        .and(CLUSTER_TERMINATION_FILTER.negate())\n        .and(ATS_GCM_DECRYPT_ERROR_FILTER.negate());\n\n    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSSZ\");\n\n    private final MediaDriverTestUtil mediaDriverTestUtil = new MediaDriverTestUtil();\n    private final Map<String, MarkFileDissector> errorDissectors = new Object2ObjectHashMap<>();\n\n    private Predicate<String> logFilter = TEST_CLUSTER_DEFAULT_LOG_FILTER;\n    private DataCollector dataCollector = new DataCollector();\n    private final ArrayList<AutoCloseable> closeables = new ArrayList<>();\n    private long startTimeNs;\n\n    public SystemTestWatcher()\n    {\n        addDissectorInternal(new ArchiveMarkFileDissector());\n        addDissectorInternal(new ConsensusModuleMarkFileDissector());\n        addDissectorInternal(new ClusteredServiceMarkFileDissector());\n    }\n\n    public SystemTestWatcher cluster(final TestCluster testCluster)\n    {\n        this.dataCollector = testCluster.dataCollector();\n        return addClosable(testCluster);\n    }\n\n    public SystemTestWatcher addClosable(final AutoCloseable closeable)\n    {\n        closeables.add(Objects.requireNonNull(closeable));\n        return this;\n    }\n\n    private void addDissectorInternal(final MarkFileDissector markFileDissector)\n    {\n        errorDissectors.put(markFileDissector.filename(), markFileDissector);\n    }\n\n    @SuppressWarnings(\"unused\")\n    public SystemTestWatcher addDissector(final MarkFileDissector markFileDissector)\n    {\n        addDissectorInternal(markFileDissector);\n        return this;\n    }\n\n    public DataCollector dataCollector()\n    {\n        return dataCollector;\n    }\n\n    public void outputFiles(final String aeronDirectoryName, final File stdoutFile, final File stderrFile)\n    {\n        mediaDriverTestUtil.outputFiles(aeronDirectoryName, stdoutFile, stderrFile);\n        dataCollector.add(stdoutFile);\n        dataCollector.add(stderrFile);\n    }\n\n    public void exitCode(final String aeronDirectoryName, final int exitValue, final String exitMessage)\n    {\n        mediaDriverTestUtil.exitCode(aeronDirectoryName, exitValue, exitMessage);\n    }\n\n    public void environmentVariables(final String aeronDirectoryName, final Map<String, String> environment)\n    {\n        mediaDriverTestUtil.environmentVariables(aeronDirectoryName, environment);\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public SystemTestWatcher ignoreErrorsMatching(final Predicate<String> logFilter)\n    {\n        this.logFilter = this.logFilter.and(logFilter.negate());\n        return this;\n    }\n\n    /**\n     * Useful when debugging tests to get them to fail on warnings as well as errors.\n     */\n    @SuppressWarnings(\"unused\")\n    public void showAllErrors()\n    {\n        this.logFilter = (s) -> true;\n    }\n\n    public void beforeEach(final ExtensionContext context)\n    {\n        Thread.interrupted(); // clean the interrupted flag so that it does not affect the next test\n        startTimeNs = System.nanoTime();\n    }\n\n    @SuppressWarnings(\"MethodLength\")\n    public void afterTestExecution(final ExtensionContext context)\n    {\n        final long endTimeNs = System.nanoTime();\n        Thread.interrupted(); // clean the interrupted flag so that it does not prevent cleanup in the tests\n\n        Throwable error = context.getExecutionException()\n            .filter((t) -> !(t instanceof TestAbortedException))\n            .orElse(null);\n        try\n        {\n            try\n            {\n                mediaDriverTestUtil.afterTestExecution(context);\n            }\n            catch (final Throwable t)\n            {\n                error = Tests.setOrUpdateError(error, t);\n            }\n\n            try\n            {\n                final MutableInteger count = new MutableInteger();\n                final StringBuilder errors = new StringBuilder();\n                filterErrors(count, errors);\n\n                if (null == error)\n                {\n                    assertEquals(\n                        0, count.get(), () -> \"Errors observed in \" + context.getDisplayName() + \":\\n\" + errors);\n                }\n                else if (0 != count.get())\n                {\n                    error = Tests.setOrUpdateError(error, new AssertionFailedError(\n                        \"Errors observed in \" + context.getDisplayName() + \":\\n\" + errors));\n                }\n            }\n            catch (final Throwable t)\n            {\n                error = Tests.setOrUpdateError(error, t);\n            }\n\n            if (null != error)\n            {\n                final String testMethod = context.getTestClass().map(Class::getName).orElse(\"unknown\") + \"-\" +\n                    context.getTestMethod().map(Method::getName).orElse(\"unknown\");\n                final String testName;\n                final String directoryName;\n                if (context.getTestMethod().map((m) -> m.getAnnotation(ParameterizedTest.class)).isPresent())\n                {\n                    testName = testMethod + \"(\" + context.getDisplayName() + \")\";\n                    final Matcher matcher = PARAMETERISED_TEST_INDEX_PATTERN.matcher(context.getDisplayName());\n                    if (matcher.matches())\n                    {\n                        directoryName = testMethod + \"_\" + matcher.group(1);\n                    }\n                    else\n                    {\n                        directoryName = testMethod + \"_\" + System.nanoTime();\n                    }\n                }\n                else\n                {\n                    testName = testMethod + \"()\";\n                    directoryName = testMethod;\n                }\n\n                System.out.println(\n                    \"*** \" + testName + \" failed in endTimeNs(\" + endTimeNs + \") - startTimeNs(\" + startTimeNs + \") \" +\n                    \" = \" + NANOSECONDS.toMillis(endTimeNs - startTimeNs) + \" ms, cause: \" + error);\n                final Throwable terminateError = reportAndTerminate(directoryName);\n                error = Tests.setOrUpdateError(error, terminateError);\n                try\n                {\n                    mediaDriverTestUtil.testFailed();\n                }\n                catch (final Throwable t)\n                {\n                    error = Tests.setOrUpdateError(error, t);\n                }\n            }\n            else\n            {\n                setTerminationExpected();\n                try\n                {\n                    CloseHelper.closeAll(closeables);\n                }\n                catch (final Throwable t)\n                {\n                    error = Tests.setOrUpdateError(error, t);\n                }\n\n                try\n                {\n                    mediaDriverTestUtil.testSuccessful();\n                }\n                catch (final Throwable t)\n                {\n                    error = Tests.setOrUpdateError(error, t);\n                }\n            }\n        }\n        finally\n        {\n            if (null != error)\n            {\n                System.out.println(\"*** Complete stack trace (PID=\" + SystemUtil.getPid() + \"): \");\n                error.printStackTrace(System.out);\n                LangUtil.rethrowUnchecked(error);\n            }\n        }\n    }\n\n    public void afterEach(final ExtensionContext context)\n    {\n        deleteAllLocations();\n    }\n\n    private void setTerminationExpected()\n    {\n        for (final AutoCloseable closeable : closeables)\n        {\n            if (closeable instanceof TestCluster)\n            {\n                ((TestCluster)closeable).terminationsExpected(true);\n            }\n        }\n    }\n\n    private void filterErrors(final MutableInteger count, final StringBuilder errors) throws IOException\n    {\n        filterCncFileErrors(dataCollector.cncFiles(), count, errors);\n\n        for (final MarkFileDissector dissector : errorDissectors.values())\n        {\n            filterErrors(dissector, count, errors);\n        }\n    }\n\n    private void filterErrors(\n        final MarkFileDissector markFileDissector,\n        final MutableInteger count,\n        final StringBuilder errors) throws IOException\n    {\n        final List<Path> paths = dataCollector.markFiles(markFileDissector);\n        markFileDissector.filterErrors(paths, count, errors, logFilter);\n    }\n\n    private void filterCncFileErrors(final List<Path> paths, final MutableInteger count, final StringBuilder errors)\n    {\n        for (final Path path : paths)\n        {\n            final File file = path.toFile();\n            if (file.exists() && file.length() > 0)\n            {\n                final MappedByteBuffer mmap = SamplesUtil.mapExistingFileReadOnly(file);\n                try\n                {\n                    final UnsafeBuffer metaDataBuffer = createMetaDataBuffer(mmap);\n                    final int errorLogBufferLength = metaDataBuffer.getInt(errorLogBufferLengthOffset(0));\n                    if (errorLogBufferLength > 0)\n                    {\n                        readErrors(path, CommonContext.errorLogBuffer(mmap), count, errors, logFilter);\n                    }\n                }\n                finally\n                {\n                    IoUtil.unmap(mmap);\n                }\n            }\n        }\n    }\n\n    public static void readErrors(\n        final Path path,\n        final AtomicBuffer buffer,\n        final MutableInteger count,\n        final StringBuilder errors,\n        final Predicate<String> logFilter)\n    {\n        ErrorLogReader.read(\n            buffer,\n            (observationCount, firstObservationTimestamp, lastObservationTimestamp, encodedException) ->\n            {\n                if (logFilter.test(encodedException))\n                {\n                    count.set(count.get() + observationCount);\n                    appendError(errors, path, encodedException);\n                }\n            });\n    }\n\n    private static void appendError(final StringBuilder errors, final Path path, final String encodedException)\n    {\n        final String errorMessage;\n        final int lineFeedIndex = encodedException.indexOf('\\n');\n        if (lineFeedIndex > 0)\n        {\n            final int endOfMessageIndex =\n                '\\r' != encodedException.charAt(lineFeedIndex - 1) ? lineFeedIndex : lineFeedIndex - 1;\n            errorMessage = encodedException.substring(0, endOfMessageIndex);\n        }\n        else\n        {\n            errorMessage = encodedException;\n        }\n        errors.append(path).append(\": \").append(errorMessage).append('\\n');\n    }\n\n    private static ClusterMarkFile openClusterMarkFile(final Path path)\n    {\n        try\n        {\n            return new ClusterMarkFile(\n                path.getParent().toFile(), path.getFileName().toString(), SystemEpochClock.INSTANCE, 0, (s) -> {});\n        }\n        catch (final RuntimeException ex)\n        {\n            throw new RuntimeException(\"Failed to open mark file=\" + path, ex);\n        }\n    }\n\n    private static ArchiveMarkFile openArchiveMarkFile(final Path path)\n    {\n        return new ArchiveMarkFile(\n            path.getParent().toFile(), path.getFileName().toString(), SystemEpochClock.INSTANCE, 0, (s) -> {});\n    }\n\n    private void printObservationCallback(\n        final int observationCount,\n        final long firstObservationTimestamp,\n        final long lastObservationTimestamp,\n        final String encodedException)\n    {\n        final String ignored = !logFilter.test(encodedException) ? \"(ignored) \" : \"\";\n        System.out.format(\n            \"***%n%s%d observations from %s to %s for:%n %s%n\",\n            ignored,\n            observationCount,\n            DATE_FORMAT.format(new Date(firstObservationTimestamp)),\n            DATE_FORMAT.format(new Date(lastObservationTimestamp)),\n            encodedException);\n    }\n\n    private Throwable reportAndTerminate(final String directoryName)\n    {\n        final MutableReference<Throwable> error = new MutableReference<>();\n        setOrUpdateError(error, printCncInfo(dataCollector.cncFiles()));\n\n        errorDissectors.forEach((filename, dissector) -> setOrUpdateError(\n            error,\n            dissector.printErrors(dataCollector.markFiles(dissector), this::printObservationCallback)));\n\n        //grab thread dump while components are still running\n        final byte[] threadDump = SystemUtil.threadDump().getBytes(UTF_8);\n\n        try\n        {\n            System.out.println(\"Reported and termination: \" + closeables);\n            CloseHelper.closeAll(closeables);\n        }\n        catch (final Throwable t)\n        {\n            setOrUpdateError(error, t);\n        }\n\n        try\n        {\n            dataCollector.dumpData(directoryName, threadDump);\n        }\n        catch (final Throwable t)\n        {\n            setOrUpdateError(error, t);\n        }\n\n        return error.get();\n    }\n\n    private static void setOrUpdateError(final MutableReference<Throwable> existingError, final Throwable newError)\n    {\n        if (null == existingError.get())\n        {\n            existingError.set(newError);\n        }\n        else if (null != newError)\n        {\n            existingError.get().addSuppressed(newError);\n        }\n    }\n\n    private Throwable printCncInfo(final List<Path> paths)\n    {\n        Throwable error = null;\n        for (final Path path : paths)\n        {\n            final File cncFile = path.toFile();\n            if (!cncFile.exists() || 0 == cncFile.length())\n            {\n                System.out.printf(\"%n%nCommand `n Control file %s was not created!%n\", cncFile);\n                continue;\n            }\n            System.out.printf(\"%n%nCommand `n Control file %s, length=%d%n\", cncFile, cncFile.length());\n            System.out.println(\"---------------------------------------------------------------------------------\");\n            final MappedByteBuffer mappedByteBuffer = SamplesUtil.mapExistingFileReadOnly(cncFile);\n            try\n            {\n                final UnsafeBuffer metaDataBuffer = createMetaDataBuffer(mappedByteBuffer);\n                final int cncVersion = metaDataBuffer.getInt(cncVersionOffset(0));\n                System.out.printf(\n                    \"%27s: %s%n\", \"version\", 0 == cncVersion ? \"N/A\" : SemanticVersion.toString(cncVersion));\n                System.out.printf(\n                    \"%27s: %d%n\", \"toDriverBufferLength\", metaDataBuffer.getInt(toDriverBufferLengthOffset(0)));\n                System.out.printf(\n                    \"%27s: %d%n\", \"toClientsBufferLength\", metaDataBuffer.getInt(toClientsBufferLengthOffset(0)));\n                final int counterMetaDataBufferLength = metaDataBuffer.getInt(countersMetaDataBufferLengthOffset(0));\n                System.out.printf(\"%27s: %d%n\", \"counterMetaDataBufferLength\", counterMetaDataBufferLength);\n                final int counterValuesBufferLength = metaDataBuffer.getInt(countersValuesBufferLengthOffset(0));\n                System.out.printf(\"%27s: %d%n\", \"counterValuesBufferLength\", counterValuesBufferLength);\n                final int errorLogBufferLength = metaDataBuffer.getInt(errorLogBufferLengthOffset(0));\n                System.out.printf(\"%27s: %d%n\", \"errorLogBufferLength\", errorLogBufferLength);\n                System.out.printf(\n                    \"%27s: %d%n\", \"clientLivenessTimeoutNs\", metaDataBuffer.getLong(clientLivenessTimeoutOffset(0)));\n                System.out.printf(\n                    \"%27s: %d%n\", \"startTimestampMs\", metaDataBuffer.getLong(startTimestampOffset(0)));\n                System.out.printf(\"%27s: %d%n\", \"pid\", metaDataBuffer.getLong(pidOffset(0)));\n                final UnsafeBuffer toDriverBuffer = createToDriverBuffer(mappedByteBuffer, metaDataBuffer);\n                final int driveHeartbeatOffset = toDriverBuffer.capacity() - RingBufferDescriptor.TRAILER_LENGTH +\n                    RingBufferDescriptor.CONSUMER_HEARTBEAT_OFFSET;\n                System.out.printf(\"%27s: %s%n\", \"driverHeartbeatMs\",\n                    driveHeartbeatOffset < 0 ? \"N/A\" : toDriverBuffer.getLong(driveHeartbeatOffset));\n                System.out.println(\"---------------------------------------------------------------------------------\");\n\n                if (counterMetaDataBufferLength > 0 && counterValuesBufferLength > 0)\n                {\n                    final CountersReader countersReader = new CountersReader(\n                        createCountersMetaDataBuffer(mappedByteBuffer, metaDataBuffer),\n                        createCountersValuesBuffer(mappedByteBuffer, metaDataBuffer),\n                        StandardCharsets.US_ASCII);\n                    countersReader.forEach(\n                        (counterId, label) ->\n                        {\n                            final long value = countersReader.getCounterValue(counterId);\n                            System.out.format(\"%3d: %,20d - %s%n\", counterId, value, label);\n                        });\n                    System.out.println(\n                        \"---------------------------------------------------------------------------------\");\n                }\n\n                if (errorLogBufferLength > 0)\n                {\n                    final AtomicBuffer buffer =\n                        createErrorLogBuffer(mappedByteBuffer, metaDataBuffer);\n                    System.out.printf(\"%nCommand `n Control Errors%n\");\n                    final int distinctErrorCount = ErrorLogReader.read(buffer, this::printObservationCallback);\n                    System.out.format(\"%d distinct errors observed.%n\", distinctErrorCount);\n                }\n            }\n            catch (final Throwable t)\n            {\n                error = Tests.setOrUpdateError(error, t);\n            }\n            finally\n            {\n                IoUtil.unmap(mappedByteBuffer);\n            }\n        }\n\n        return error;\n    }\n\n    private void deleteAllLocations()\n    {\n        for (final Path path : dataCollector.cleanupLocations())\n        {\n            try\n            {\n                IoUtil.delete(path.toFile(), true);\n            }\n            catch (final Exception e)\n            {\n                System.err.println(\"Failed to delete: '\" + path + \"', skipping: \" + e.getMessage());\n            }\n        }\n    }\n\n    public interface MarkFileDissector\n    {\n        String filename();\n\n        Throwable printErrors(List<Path> paths, ErrorConsumer errorConsumer);\n\n        boolean isRelevantFile(File file);\n\n        void filterErrors(List<Path> paths, MutableInteger count, StringBuilder errors, Predicate<String> logFilter)\n            throws IOException;\n    }\n\n    private static final class ArchiveMarkFileDissector implements MarkFileDissector\n    {\n        public String filename()\n        {\n            return ArchiveMarkFile.FILENAME;\n        }\n\n        public boolean isRelevantFile(final File file)\n        {\n            return ArchiveMarkFile.FILENAME.equals(file.getName());\n        }\n\n        public Throwable printErrors(final List<Path> paths, final ErrorConsumer errorConsumer)\n        {\n            Throwable error = null;\n            for (final Path path : paths)\n            {\n                if (Files.exists(path) && path.toFile().length() > 0)\n                {\n                    try (ArchiveMarkFile archiveFile = openArchiveMarkFile(path))\n                    {\n                        final AtomicBuffer buffer = archiveFile.errorBuffer();\n\n                        System.out.printf(\"%n%n%s file %s%n\", \"Archive Errors\", path);\n                        final int distinctErrorCount = ErrorLogReader.read(buffer, errorConsumer);\n                        System.out.format(\"%d distinct errors observed.%n\", distinctErrorCount);\n                    }\n                    catch (final Throwable t)\n                    {\n                        error = Tests.setOrUpdateError(error, t);\n                    }\n                }\n            }\n\n            return error;\n        }\n\n        public void filterErrors(\n            final List<Path> paths,\n            final MutableInteger count,\n            final StringBuilder errors,\n            final Predicate<String> logFilter) throws IOException\n        {\n            for (final Path path : paths)\n            {\n                if (Files.exists(path) && Files.size(path) > 0)\n                {\n                    try (ArchiveMarkFile archive = openArchiveMarkFile(path))\n                    {\n                        final AtomicBuffer buffer = archive.errorBuffer();\n                        readErrors(path, buffer, count, errors, logFilter);\n                    }\n                }\n            }\n        }\n    }\n\n    private abstract static class ClusterMarkFileDissector implements MarkFileDissector\n    {\n        public Throwable printErrors(final List<Path> paths, final ErrorConsumer errorConsumer)\n        {\n            Throwable error = null;\n            for (final Path path : paths)\n            {\n                if (Files.exists(path) && path.toFile().length() > 0)\n                {\n                    try (ClusterMarkFile clusterMarkFile = openClusterMarkFile(path))\n                    {\n                        final AtomicBuffer buffer = clusterMarkFile.errorBuffer();\n\n                        System.out.printf(\"%n%n%s file %s%n\", fileDescription(), path);\n                        final int distinctErrorCount = ErrorLogReader.read(buffer, errorConsumer);\n                        System.out.format(\"%d distinct errors observed.%n\", distinctErrorCount);\n                    }\n                    catch (final Throwable t)\n                    {\n                        error = Tests.setOrUpdateError(error, t);\n                    }\n                }\n            }\n            return error;\n        }\n\n        public void filterErrors(\n            final List<Path> paths,\n            final MutableInteger count,\n            final StringBuilder errors,\n            final Predicate<String> logFilter) throws IOException\n        {\n            for (final Path path : paths)\n            {\n                if (Files.exists(path) && Files.size(path) > 0)\n                {\n                    try (ClusterMarkFile clusterMarkFile = openClusterMarkFile(path))\n                    {\n                        final AtomicBuffer buffer = clusterMarkFile.errorBuffer();\n                        readErrors(path, buffer, count, errors, logFilter);\n                    }\n                }\n            }\n        }\n\n        protected abstract String fileDescription();\n    }\n\n    private static final class ConsensusModuleMarkFileDissector extends ClusterMarkFileDissector\n    {\n        public String filename()\n        {\n            return ClusterMarkFile.FILENAME;\n        }\n\n        public boolean isRelevantFile(final File file)\n        {\n            return ClusterMarkFile.FILENAME.equals(file.getName());\n        }\n\n        protected String fileDescription()\n        {\n            return \"Consensus Module\";\n        }\n    }\n\n    private static final class ClusteredServiceMarkFileDissector extends ClusterMarkFileDissector\n    {\n        public String filename()\n        {\n            return ClusterMarkFile.SERVICE_FILENAME_PREFIX + \"X\" + ClusterMarkFile.FILE_EXTENSION;\n        }\n\n        public boolean isRelevantFile(final File file)\n        {\n            final String name = file.getName();\n            return name.startsWith(ClusterMarkFile.SERVICE_FILENAME_PREFIX) &&\n                name.endsWith(ClusterMarkFile.FILE_EXTENSION);\n        }\n\n        protected String fileDescription()\n        {\n            return \"Clustered Service\";\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/TestContexts.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.ConsensusModule;\n\nimport static io.aeron.CommonContext.IPC_CHANNEL;\n\npublic class TestContexts\n{\n    public static final String LOCALHOST_REPLICATION_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n    public static final String LOCALHOST_CONTROL_REQUEST_CHANNEL = \"aeron:udp?endpoint=localhost:8010\";\n    public static final String LOCALHOST_CONTROL_RESPONSE_CHANNEL = \"aeron:udp?endpoint=localhost:0\";\n    public static final String LOCALHOST_SINGLE_HOST_CLUSTER_MEMBERS =\n        \"0,localhost:20000,localhost:20001,localhost:20002,localhost:0,localhost:8010\";\n\n    public static Archive.Context localhostArchive()\n    {\n        return new Archive.Context()\n            .controlChannel(LOCALHOST_CONTROL_REQUEST_CHANNEL)\n            .replicationChannel(LOCALHOST_REPLICATION_CHANNEL);\n    }\n\n    public static AeronArchive.Context localhostAeronArchive()\n    {\n        return new AeronArchive.Context()\n            .controlRequestChannel(LOCALHOST_CONTROL_REQUEST_CHANNEL)\n            .controlResponseChannel(LOCALHOST_CONTROL_RESPONSE_CHANNEL);\n    }\n\n    public static AeronArchive.Context ipcAeronArchive()\n    {\n        return new AeronArchive.Context()\n            .controlRequestChannel(IPC_CHANNEL)\n            .controlResponseChannel(IPC_CHANNEL);\n    }\n\n    public static ConsensusModule.Context localhostConsensusModule()\n    {\n        return new ConsensusModule.Context()\n            .clusterMembers(LOCALHOST_SINGLE_HOST_CLUSTER_MEMBERS);\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/TestIdleStrategy.java",
    "content": "/*\n * Copyright 2024-2025 Adaptive Financial Consulting Limited.\n */\npackage io.aeron.test;\n\nimport org.agrona.concurrent.IdleStrategy;\n\npublic class TestIdleStrategy implements IdleStrategy\n{\n    /**\n     * Static instance to reduce allocation.\n     */\n    public static final TestIdleStrategy INSTANCE = new TestIdleStrategy();\n\n    public void idle(final int i)\n    {\n        if (i == 0)\n        {\n            idle();\n        }\n    }\n\n    public void idle()\n    {\n        Tests.yield();\n    }\n\n    public void reset()\n    {\n\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/Tests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.aeron.test;\n\nimport io.aeron.Aeron;\nimport io.aeron.Counter;\nimport io.aeron.Publication;\nimport io.aeron.Subscription;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.driver.Configuration;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.exceptions.TimeoutException;\nimport org.agrona.DirectBuffer;\nimport org.agrona.LangUtil;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.SleepingMillisIdleStrategy;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersManager;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.extension.TestWatcher;\nimport org.mockito.stubbing.Answer;\n\nimport javax.management.InstanceNotFoundException;\nimport javax.management.MBeanServer;\nimport javax.management.ObjectName;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.lang.management.ManagementFactory;\nimport java.lang.reflect.Field;\nimport java.nio.ByteBuffer;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.FileVisitor;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.locks.LockSupport;\nimport java.util.function.BooleanSupplier;\nimport java.util.function.Function;\nimport java.util.function.IntConsumer;\nimport java.util.function.LongSupplier;\nimport java.util.function.Predicate;\nimport java.util.function.Supplier;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static org.mockito.Mockito.doAnswer;\n\n/**\n * Utilities to help with writing tests.\n */\npublic class Tests\n{\n    public static final IdleStrategy SLEEP_1_MS = new SleepingMillisIdleStrategy(1);\n    private static final String LOGGING_MBEAN_NAME = \"io.aeron:type=logging\";\n    private static final ClassValue<Map<String, Field>> CLASS_FIELDS = new ClassValue<>()\n    {\n        protected Map<String, Field> computeValue(final Class<?> type)\n        {\n            return new HashMap<>();\n        }\n    };\n\n    /**\n     * Set a private field in a class for testing.\n     *\n     * @param instance  of the object to set the field value.\n     * @param fieldName to be set.\n     * @param value     to be set on the field.\n     */\n    public static void setField(final Object instance, final String fieldName, final Object value)\n    {\n        try\n        {\n            resolveField(instance.getClass(), fieldName).set(instance, value);\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * Get a private field in a class for testing.\n     *\n     * @param <T>       return type.\n     * @param instance  of the object to get the field value.\n     * @param fieldName to read.\n     * @return field value.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T getField(final Object instance, final String fieldName)\n    {\n        try\n        {\n            return (T)resolveField(instance.getClass(), fieldName).get(instance);\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n            return null;\n        }\n    }\n\n    /**\n     * Error handler that can be used as an implementation of {@link org.agrona.ErrorHandler} which will print out\n     * a stacktrace unless the exception is to type {@link io.aeron.exceptions.AeronException.Category#WARN}.\n     *\n     * @param ex to be handled.\n     */\n    public static void onError(final Throwable ex)\n    {\n        if (ex instanceof AeronException && ((AeronException)ex).category() == AeronException.Category.WARN)\n        {\n            //System.out.println(\"Warning: \" + ex.getMessage());\n            return;\n        }\n\n        ex.printStackTrace();\n    }\n\n    /**\n     * Check if the interrupt flag has been set on the current thread and fail the test if it has.\n     * <p>\n     * This is useful for terminating tests stuck in a loop on timeout otherwise JUnit will proceed to the next test\n     * and leave the thread spinning and consuming CPU resource.\n     */\n    public static void checkInterruptStatus()\n    {\n        if (Thread.currentThread().isInterrupted())\n        {\n            throw new TimeoutException(\"unexpected interrupt\");\n        }\n    }\n\n    /**\n     * Check if the interrupt flag has been set on the current thread and fail the test if it has.\n     * <p>\n     * This is useful for terminating tests stuck in a loop on timeout otherwise JUnit will proceed to the next test\n     * and leave the thread spinning and consuming CPU resource.\n     *\n     * @param messageSupplier additional context information to include in the failure message\n     */\n    public static void checkInterruptStatus(final Supplier<String> messageSupplier)\n    {\n        if (Thread.currentThread().isInterrupted())\n        {\n            throw new TimeoutException(messageSupplier.get());\n        }\n    }\n\n    /**\n     * Check if the interrupt flag has been set on the current thread and fail the test if it has.\n     * <p>\n     * This is useful for terminating tests stuck in a loop on timeout otherwise JUnit will proceed to the next test\n     * and leave the thread spinning and consuming CPU resource.\n     *\n     * @param format A format string, {@link java.util.Formatter} to use as additional context information in the\n     *               failure message\n     * @param args   arguments to the format string\n     */\n    public static void checkInterruptStatus(final String format, final Object... args)\n    {\n        if (Thread.currentThread().isInterrupted())\n        {\n            throw new TimeoutException(String.format(format, args));\n        }\n    }\n\n    /**\n     * Check if the interrupt flag has been set on the current thread and fail the test if it has.\n     * <p>\n     * This is useful for terminating tests stuck in a loop on timeout otherwise JUnit will proceed to the next test\n     * and leave the thread spinning and consuming CPU resource.\n     *\n     * @param message to provide additional context on unexpected interrupt.\n     */\n    public static void checkInterruptStatus(final String message)\n    {\n        if (Thread.currentThread().isInterrupted())\n        {\n            throw new TimeoutException(message);\n        }\n    }\n\n    /**\n     * Print out the message and stack trace on thread interrupt.\n     *\n     * @param message to provide additional context on unexpected interrupt.\n     */\n    public static void unexpectedInterruptStackTrace(final String message)\n    {\n        final StringBuilder sb = new StringBuilder();\n        sb.append(\"*** unexpected interrupt\");\n\n        if (null != message)\n        {\n            sb.append(\" - \").append(message);\n        }\n\n        appendStackTrace(sb).append('\\n');\n    }\n\n    /**\n     * Append the current thread stack trace to a {@link StringBuilder}.\n     *\n     * @param sb to append the stack trace to.\n     * @return the builder for a fluent API.\n     */\n    public static StringBuilder appendStackTrace(final StringBuilder sb)\n    {\n        return appendStackTrace(sb, Thread.currentThread().getStackTrace());\n    }\n\n    /**\n     * Append a thread stack trace to a {@link StringBuilder}.\n     *\n     * @param sb                 to append the stack trace to.\n     * @param stackTraceElements to be appended.\n     * @return the builder for a fluent API.\n     */\n    public static StringBuilder appendStackTrace(final StringBuilder sb, final StackTraceElement[] stackTraceElements)\n    {\n        sb.append(System.lineSeparator());\n\n        for (int i = 1, length = stackTraceElements.length; i < length; i++)\n        {\n            sb.append(stackTraceElements[i]).append(System.lineSeparator());\n        }\n\n        return sb;\n    }\n\n    /**\n     * Same as {@link Thread#sleep(long)} but without the checked exception.\n     *\n     * @param durationMs to sleep.\n     */\n    public static void sleep(final long durationMs)\n    {\n        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(durationMs));\n        checkInterruptStatus();\n    }\n\n    /**\n     * Same as {@link Thread#sleep(long)} but without the checked exception.\n     *\n     * @param durationMs      to sleep.\n     * @param messageSupplier of message to be reported on interrupt.\n     */\n    public static void sleep(final long durationMs, final Supplier<String> messageSupplier)\n    {\n        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(durationMs));\n        checkInterruptStatus(messageSupplier);\n    }\n\n    /**\n     * Same as {@link Thread#sleep(long)} but without the checked exception.\n     *\n     * @param durationMs to sleep.\n     * @param format     of the message.\n     * @param params     to be formatted.\n     */\n    public static void sleep(final long durationMs, final String format, final Object... params)\n    {\n        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(durationMs));\n        checkInterruptStatus(format, params);\n    }\n\n    /**\n     * Yield the thread then check for interrupt in a test.\n     *\n     * @see #checkInterruptStatus()\n     */\n    public static void yield()\n    {\n        Thread.yield();\n        checkInterruptStatus();\n    }\n\n    /**\n     * Helper method to mock {@link AutoCloseable#close()} method to throw exception.\n     *\n     * @param mock      to have its method mocked\n     * @param exception exception to be thrown\n     * @throws Exception to make compiler happy\n     */\n    public static void throwOnClose(final AutoCloseable mock, final Throwable exception) throws Exception\n    {\n        doAnswer(\n            (invocation) ->\n            {\n                LangUtil.rethrowUnchecked(exception);\n                return null;\n            }).when(mock).close();\n    }\n\n    /**\n     * {@link IdleStrategy#idle()} the provide strategy and check for thread interrupt after.\n     *\n     * @param idleStrategy    to be used for the idle operation.\n     * @param messageSupplier to be used in the event of interrupt.\n     */\n    public static void idle(final IdleStrategy idleStrategy, final Supplier<String> messageSupplier)\n    {\n        idleStrategy.idle();\n        checkInterruptStatus(messageSupplier);\n    }\n\n    /**\n     * {@link IdleStrategy#idle()} the provide strategy and check for thread interrupt after.\n     *\n     * @param idleStrategy to be used for the idle operation.\n     * @param format       of the message to be used in the event of interrupt.\n     * @param params       to be substituted into the message format.\n     */\n    public static void idle(final IdleStrategy idleStrategy, final String format, final Object... params)\n    {\n        idleStrategy.idle();\n        checkInterruptStatus(format, params);\n    }\n\n    /**\n     * {@link IdleStrategy#idle()} the provide strategy and check for thread interrupt after.\n     *\n     * @param idleStrategy to be used for the idle operation.\n     * @param message      to be used in the event of interrupt.\n     */\n    public static void idle(final IdleStrategy idleStrategy, final String message)\n    {\n        idleStrategy.idle();\n        checkInterruptStatus(message);\n    }\n\n    /**\n     * Call {@link YieldingIdleStrategy#idle()} and check for thread interrupt after.\n     *\n     * @param messageSupplier to be used in the event of interrupt.\n     */\n    public static void yieldingIdle(final Supplier<String> messageSupplier)\n    {\n        idle(YieldingIdleStrategy.INSTANCE, messageSupplier);\n    }\n\n    /**\n     * Call {@link YieldingIdleStrategy#idle()} and check for thread interrupt after.\n     *\n     * @param format of the message to be used in the event of interrupt.\n     * @param params to be substituted into the message format.\n     */\n    public static void yieldingIdle(final String format, final Object... params)\n    {\n        idle(YieldingIdleStrategy.INSTANCE, format, params);\n    }\n\n    /**\n     * Call {@link YieldingIdleStrategy#idle()} and check for thread interrupt after.\n     *\n     * @param message to be used in the event of interrupt.\n     */\n    public static void yieldingIdle(final String message)\n    {\n        idle(YieldingIdleStrategy.INSTANCE, message);\n    }\n\n    /**\n     * Execute a task until a condition is satisfied, or a maximum number of iterations, or a timeout is reached.\n     *\n     * @param condition         keep executing while true.\n     * @param iterationConsumer to be invoked with the iteration count.\n     * @param maxIterations     to be executed.\n     * @param timeoutNs         to stay within.\n     */\n    public static void executeUntil(\n        final BooleanSupplier condition,\n        final IntConsumer iterationConsumer,\n        final int maxIterations,\n        final long timeoutNs)\n    {\n        final long startNs = System.nanoTime();\n        long nowNs;\n        int i = 0;\n\n        do\n        {\n            checkInterruptStatus();\n            iterationConsumer.accept(i);\n            nowNs = System.nanoTime();\n        }\n        while (!condition.getAsBoolean() && ((nowNs - startNs) < timeoutNs) && i++ < maxIterations);\n    }\n\n    /**\n     * Await a condition with a timeout and also check for thread interrupt.\n     *\n     * @param conditionSupplier for the condition to be awaited.\n     * @param timeoutNs         to await.\n     */\n    public static void await(final BooleanSupplier conditionSupplier, final long timeoutNs)\n    {\n        await(conditionSupplier, timeoutNs, () -> \"\");\n    }\n\n    /**\n     * Await a condition with a timeout and also check for thread interrupt.\n     *\n     * @param conditionSupplier    for the condition to be awaited.\n     * @param timeoutNs            to await.\n     * @param errorMessageSupplier supplier of error message if timeout reached\n     */\n    public static void await(\n        final BooleanSupplier conditionSupplier,\n        final long timeoutNs,\n        final Supplier<String> errorMessageSupplier)\n    {\n        final long deadlineNs = System.nanoTime() + timeoutNs;\n        while (!conditionSupplier.getAsBoolean())\n        {\n            if ((deadlineNs - System.nanoTime()) <= 0)\n            {\n                throw new TimeoutException(errorMessageSupplier.get());\n            }\n\n            Tests.yield();\n        }\n    }\n\n    /**\n     * Await a condition with and check for thread interrupt.\n     *\n     * @param conditionSupplier    for the condition to be awaited.\n     * @param errorMessageSupplier supplier of error message if interrupt signalled\n     */\n    public static void await(final BooleanSupplier conditionSupplier, final Supplier<String> errorMessageSupplier)\n    {\n        while (!conditionSupplier.getAsBoolean())\n        {\n            Tests.yieldingIdle(errorMessageSupplier);\n        }\n    }\n\n    /**\n     * Await a condition with a timeout and also check for thread interrupt.\n     *\n     * @param argSupplier         argument for the condition + message.\n     * @param conditionsPredicate for the condition to be awaited.\n     * @param timeoutNs           to await.\n     * @param errorMessageCreator supplier of error message if timeout reached\n     * @param <T>                 type of argument to condition + message\n     */\n    public static <T> void await(\n        final Supplier<T> argSupplier,\n        final Predicate<T> conditionsPredicate,\n        final long timeoutNs,\n        final Function<T, String> errorMessageCreator)\n    {\n        final long deadlineNs = System.nanoTime() + timeoutNs;\n        T arg = argSupplier.get();\n        while (!conditionsPredicate.test(arg))\n        {\n            if ((deadlineNs - System.nanoTime()) <= 0)\n            {\n                throw new TimeoutException(errorMessageCreator.apply(arg));\n            }\n\n            Tests.yield();\n            arg = argSupplier.get();\n        }\n    }\n\n    /**\n     * Await a condition with and check for thread interrupt.\n     *\n     * @param argSupplier         argument for the condition + message.\n     * @param conditionsPredicate for the condition to be awaited.\n     * @param errorMessageCreator supplier of error message if timeout reached\n     * @param <T>                 type of argument to condition + message\n     */\n    public static <T> void await(\n        final Supplier<T> argSupplier,\n        final Predicate<T> conditionsPredicate,\n        final Function<T, String> errorMessageCreator)\n    {\n        final Supplier<String> msg = () -> errorMessageCreator.apply(argSupplier.get());\n        while (!conditionsPredicate.test(argSupplier.get()))\n        {\n            Tests.yieldingIdle(msg);\n        }\n    }\n\n    /**\n     * Await a condition with a check for thread interrupt.\n     *\n     * @param conditionSupplier for the condition to be awaited.\n     */\n    public static void await(final BooleanSupplier conditionSupplier)\n    {\n        while (!conditionSupplier.getAsBoolean())\n        {\n            Tests.yield();\n            if (Thread.currentThread().isInterrupted())\n            {\n                throw new TimeoutException(\"while awaiting\");\n            }\n        }\n    }\n\n    /**\n     * Await a counter reaching or passing a value while checking for thread interrupt.\n     *\n     * @param counter to be evaluated.\n     * @param value   as threshold to awaited.\n     */\n    public static void awaitValue(final AtomicLong counter, final long value)\n    {\n        long counterValue;\n        while ((counterValue = counter.get()) < value)\n        {\n            Thread.yield();\n            if (Thread.currentThread().isInterrupted())\n            {\n                throw new TimeoutException(\"awaiting=\" + value + \" counter=\" + counterValue);\n            }\n        }\n    }\n\n    /**\n     * Await a counter reaching or passing a value while checking for thread interrupt.\n     *\n     * @param counter to be evaluated.\n     * @param value   as threshold to awaited.\n     */\n    public static void awaitValue(final AtomicCounter counter, final long value)\n    {\n        long counterValue;\n        while ((counterValue = counter.get()) < value)\n        {\n            Thread.yield();\n            if (Thread.currentThread().isInterrupted())\n            {\n                throw new TimeoutException(\"awaiting=\" + value + \" counter=\" + counterValue);\n            }\n\n            if (counter.isClosed())\n            {\n                unexpectedInterruptStackTrace(\"awaiting=\" + value + \" counter=\" + counterValue);\n            }\n        }\n    }\n\n    /**\n     * Await a counter increasing by a delta that will sleep and check for thread interrupt.\n     *\n     * @param counters  reader over all counters.\n     * @param counterId of the specific counter to be read.\n     * @param delta     increase to await from initial value.\n     */\n    public static void awaitCounterDelta(final CountersReader counters, final int counterId, final long delta)\n    {\n        awaitCounterDelta(counters, counterId, counters.getCounterValue(counterId), delta);\n    }\n\n    /**\n     * Await a counter increasing by a delta that will sleep and check for thread interrupt.\n     *\n     * @param counters     reader over all counters.\n     * @param counterId    of the specific counter to be read.\n     * @param initialValue from which the delta will be tracked.\n     * @param delta        increase to await from initial value.\n     */\n    public static void awaitCounterDelta(\n        final CountersReader counters, final int counterId, final long initialValue, final long delta)\n    {\n        final long expectedValue = initialValue + delta;\n        final Supplier<String> counterMessage = () ->\n            \"timed out waiting for '\" + counters.getCounterLabel(counterId) + \"' to reach \" + expectedValue;\n\n        while (counters.getCounterValue(counterId) < expectedValue)\n        {\n            idle(SLEEP_1_MS, counterMessage);\n        }\n    }\n\n    /**\n     * Repeat the attempt to re-add a subscription until successful if it fails with a warning\n     * {@link RegistrationException} which could be due to a port clash.\n     *\n     * @param aeron    to add the subscription on.\n     * @param channel  for the subscription.\n     * @param streamId for the subscription.\n     * @return the added subscription.\n     */\n    public static Subscription reAddSubscription(final Aeron aeron, final String channel, final int streamId)\n    {\n        while (true)\n        {\n            try\n            {\n                return aeron.addSubscription(channel, streamId);\n            }\n            catch (final RegistrationException ex)\n            {\n                if (ex.category() != AeronException.Category.WARN)\n                {\n                    throw ex;\n                }\n\n                yieldingIdle(ex.getMessage());\n            }\n        }\n    }\n\n    /**\n     * Await a Publication being connected by yielding and checking for thread interrupt.\n     *\n     * @param publication to await being connected.\n     */\n    public static void awaitConnected(final Publication publication)\n    {\n        while (!publication.isConnected())\n        {\n            Tests.yield();\n        }\n    }\n\n    public static void await(final String message, final BooleanSupplier... elements)\n    {\n        for (final BooleanSupplier element : elements)\n        {\n            while (!element.getAsBoolean())\n            {\n                Tests.yieldingIdle(message);\n            }\n        }\n    }\n\n    /**\n     * Await a Publication having an available windows for sending by yielding and checking for thread interrupt.\n     *\n     * @param publication to await having an available window.\n     */\n    public static void awaitAvailableWindow(final Publication publication)\n    {\n        while (publication.availableWindow() <= 0)\n        {\n            Tests.yield();\n        }\n    }\n\n    /**\n     * Await a Subscription being connected by yielding and checking for thread interrupt.\n     *\n     * @param subscription to await being connected.\n     */\n    public static void awaitConnected(final Subscription subscription)\n    {\n        while (!subscription.isConnected())\n        {\n            Tests.yieldingIdle(subscription.channel());\n        }\n    }\n\n    /**\n     * Await a Subscription have a minimum number of connections by yielding and checking for thread interrupt.\n     *\n     * @param subscription    to await being connected.\n     * @param connectionCount to await.\n     */\n    public static void awaitConnections(final Subscription subscription, final int connectionCount)\n    {\n        while (subscription.imageCount() < connectionCount)\n        {\n            Tests.yieldingIdle(subscription.channel());\n        }\n    }\n\n    /**\n     * Generates a string value that is a prefix with a suffix appended a number of times.\n     *\n     * @param prefix            for the beginning of the string.\n     * @param suffix            for the end of the string.\n     * @param suffixRepeatCount for number of times the suffix is appended.\n     * @return the generated string.\n     */\n    public static String generateStringWithSuffix(final String prefix, final String suffix, final int suffixRepeatCount)\n    {\n        final StringBuilder builder = new StringBuilder(prefix.length() + (suffix.length() * suffixRepeatCount));\n\n        builder.append(prefix);\n\n        for (int i = 0; i < suffixRepeatCount; i++)\n        {\n            builder.append(suffix);\n        }\n\n        return builder.toString();\n    }\n\n    /**\n     * Start the collecting log of debug events for a test run.\n     *\n     * @param displayName for the test the log is being collected for.\n     */\n    public static void startLogCollecting(final String displayName)\n    {\n        try\n        {\n            final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();\n            final ObjectName loggingName = new ObjectName(LOGGING_MBEAN_NAME);\n\n            try\n            {\n                mBeanServer.invoke(\n                    loggingName, \"startCollecting\", new Object[]{ displayName }, new String[]{ \"java.lang.String\" });\n            }\n            catch (final InstanceNotFoundException ignore)\n            {\n                // It must not have been set up for the test. Expected in many cases.\n            }\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * Reset the collecting of logs for a new test run.\n     */\n    public static void resetLogCollecting()\n    {\n        try\n        {\n            final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();\n            final ObjectName loggingName = new ObjectName(LOGGING_MBEAN_NAME);\n\n            try\n            {\n                mBeanServer.invoke(loggingName, \"reset\", new Object[0], new String[0]);\n            }\n            catch (final InstanceNotFoundException ignore)\n            {\n                // It must not have been set up for the test. Expected in many cases.\n            }\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    /**\n     * Dump the collected log of events to a file.\n     *\n     * @param filename to dump the log of events to.\n     */\n    public static void dumpCollectedLogs(final String filename)\n    {\n        try\n        {\n            final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();\n            final ObjectName loggingName = new ObjectName(LOGGING_MBEAN_NAME);\n\n            try\n            {\n                mBeanServer.invoke(\n                    loggingName, \"writeToFile\", new Object[]{ filename }, new String[]{ \"java.lang.String\" });\n            }\n            catch (final InstanceNotFoundException ignore)\n            {\n                // It must not have been set up for the test. Expected in many cases.\n            }\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n    }\n\n    public static TestWatcher seedWatcher(final long seed)\n    {\n        return new TestWatcher()\n        {\n            public void testFailed(final ExtensionContext context, final Throwable cause)\n            {\n                System.err.println(context.getDisplayName() + \" failed with random seed: \" + seed);\n            }\n        };\n    }\n\n    public static int awaitRecordingCounterId(final CountersReader counters, final int sessionId, final long archiveId)\n    {\n        int counterId;\n        while (NULL_VALUE == (counterId = RecordingPos.findCounterIdBySession(counters, sessionId, archiveId)))\n        {\n            Tests.yield();\n        }\n\n        return counterId;\n    }\n\n    public static void awaitPosition(final CountersReader counters, final int counterId, final long position)\n    {\n        while (counters.getCounterValue(counterId) < position)\n        {\n            if (counters.getCounterState(counterId) != CountersReader.RECORD_ALLOCATED)\n            {\n                throw new IllegalStateException(\"count not active: \" + counterId);\n            }\n\n            Tests.yield();\n        }\n    }\n\n    public static void printDirectoryContents(final String directoryName, final PrintStream out) throws IOException\n    {\n        printDirectoryContents(Paths.get(directoryName), out);\n    }\n\n    public static void printDirectoryContents(\n        final Path path,\n        final PrintStream out) throws IOException\n    {\n        Files.walkFileTree(path, new PrintingFileVisitor(out));\n    }\n\n    public static CountersManager newCountersManager(final int dataLength)\n    {\n        return new CountersManager(\n            new UnsafeBuffer(ByteBuffer.allocateDirect(Configuration.countersMetadataBufferLength(dataLength))),\n            new UnsafeBuffer(ByteBuffer.allocateDirect(dataLength)));\n    }\n\n    public static Answer<Counter> addCounterAnswer(\n        final CountersManager countersManager,\n        final LongSupplier registrationId)\n    {\n        return invocation ->\n        {\n            final int counterType = invocation.getArgument(0, Integer.class);\n            final DirectBuffer keyBuffer = invocation.getArgument(1, DirectBuffer.class);\n            final int keyOffset = invocation.getArgument(2, Integer.class);\n            final int keyLength = invocation.getArgument(3, Integer.class);\n            final DirectBuffer labelBuffer = invocation.getArgument(4, DirectBuffer.class);\n            final int labelOffset = invocation.getArgument(5, Integer.class);\n            final int labelLength = invocation.getArgument(6, Integer.class);\n\n            final int allocate = countersManager.allocate(\n                counterType, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength);\n\n            return new Counter(countersManager, registrationId.getAsLong(), allocate);\n        };\n    }\n\n    public static Throwable setOrUpdateError(final Throwable existingError, final Throwable newError)\n    {\n        if (null == existingError)\n        {\n            return newError;\n        }\n\n        if (null != newError)\n        {\n            existingError.addSuppressed(newError);\n        }\n\n        return existingError;\n    }\n\n    private static void pad(final int indent, final PrintStream out)\n    {\n        if (0 != indent)\n        {\n            out.printf(\"%\" + indent + \"s\", \"\");\n        }\n    }\n\n    private static Field resolveField(final Class<?> type, final String fieldName)\n    {\n        final Map<String, Field> fields = CLASS_FIELDS.get(type);\n        Field field = fields.get(fieldName);\n        try\n        {\n            if (null == field)\n            {\n                field = type.getDeclaredField(fieldName);\n                field.setAccessible(true);\n                fields.put(fieldName, field);\n            }\n        }\n        catch (final Exception ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n        }\n        return field;\n    }\n\n    private static class PrintingFileVisitor implements FileVisitor<Path>\n    {\n        private final PrintStream out;\n        private int indent = 0;\n\n        PrintingFileVisitor(final PrintStream out)\n        {\n            this.out = out;\n        }\n\n        public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs)\n        {\n            pad(indent, out);\n            out.println(\"[\" + dir.toAbsolutePath() + \"]\");\n\n            if (Files.isSymbolicLink(dir))\n            {\n                return FileVisitResult.SKIP_SUBTREE;\n            }\n            else\n            {\n                indent += 2;\n                return FileVisitResult.CONTINUE;\n            }\n        }\n\n        public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException\n        {\n            if (Files.isSymbolicLink(file))\n            {\n                final Path dest = Files.readSymbolicLink(file);\n                pad(indent, out);\n                out.println(file.getName(file.getNameCount() - 1) + \" -> \" + dest.toAbsolutePath());\n                return FileVisitResult.SKIP_SUBTREE;\n            }\n            else\n            {\n                pad(indent, out);\n                final long size = Files.size(file);\n                out.printf(\"%-40s %10d%n\", file.getName(file.getNameCount() - 1), size);\n                return FileVisitResult.CONTINUE;\n            }\n        }\n\n        public FileVisitResult visitFileFailed(final Path file, final IOException exc)\n        {\n            return FileVisitResult.CONTINUE;\n        }\n\n        public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)\n        {\n            indent -= 2;\n            return FileVisitResult.CONTINUE;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/ThreadNamingTestCallback.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.AfterTestExecutionCallback;\nimport org.junit.jupiter.api.extension.BeforeTestExecutionCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport java.lang.reflect.Method;\n\npublic class ThreadNamingTestCallback\n    implements BeforeTestExecutionCallback, AfterTestExecutionCallback, AfterEachCallback\n{\n    private final ThreadLocal<String> oldThreadName = new ThreadLocal<>();\n    {\n        oldThreadName.set(\"UNKNOWN\");\n    }\n\n    public void beforeTestExecution(final ExtensionContext context) throws Exception\n    {\n        final String className = context.getTestClass().map(Class::getSimpleName).orElse(\"UNKNOWN\");\n        final String methodName = context.getTestMethod().map(Method::getName).orElse(\"UNKNOWN\");\n        final Thread thread = Thread.currentThread();\n        final String existingThreadName = thread.getName();\n        final String testThreadName = \"TEST::\" + className + \".\" + methodName;\n        oldThreadName.set(existingThreadName);\n        thread.setName(testThreadName);\n    }\n\n    public void afterEach(final ExtensionContext context) throws Exception\n    {\n        Thread.currentThread().setName(oldThreadName.get());\n        oldThreadName.remove();\n    }\n\n    public void afterTestExecution(final ExtensionContext context) throws Exception\n    {\n        final Thread thread = Thread.currentThread();\n        thread.setName(thread.getName() + \":tearDown\");\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/TopologyTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.Tag;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target({ ElementType.TYPE, ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@Tag(\"topology\")\npublic @interface TopologyTest\n{\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/archive/RecordingSignalCollector.java",
    "content": "/*\n * Copyright 2014-2024 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.archive;\n\nimport io.aeron.archive.client.RecordingSignalConsumer;\nimport io.aeron.archive.codecs.RecordingSignal;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic final class RecordingSignalCollector implements RecordingSignalConsumer\n{\n    public record CollectedSignal(\n        long controlSessionId,\n        long correlationId,\n        long recordingId,\n        long subscriptionId,\n        long position,\n        RecordingSignal signal)\n    {\n    }\n\n    private final List<CollectedSignal> collectedSignals = new ArrayList<>();\n\n    public void onSignal(\n        final long controlSessionId,\n        final long correlationId,\n        final long recordingId,\n        final long subscriptionId,\n        final long position,\n        final RecordingSignal signal)\n    {\n        collectedSignals.add(new CollectedSignal(\n            controlSessionId, correlationId, recordingId, subscriptionId, position, signal));\n    }\n\n    public List<CollectedSignal> collectedSignals()\n    {\n        return collectedSignals;\n    }\n\n    public String toString()\n    {\n        return \"RecordingSignalCollector{\" +\n            \"collectedSignals=\" + collectedSignals +\n            '}';\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/cluster/ClusterTests.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.cluster;\n\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.service.ClusterTerminationException;\nimport io.aeron.exceptions.AeronException;\nimport org.agrona.ErrorHandler;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.IdleStrategy;\nimport org.agrona.concurrent.YieldingIdleStrategy;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.net.UnknownHostException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic class ClusterTests\n{\n    public static final String HELLO_WORLD_MSG = \"Hello World!\";\n    public static final String NO_OP_MSG = \"No op!           \";\n    public static final String REGISTER_TIMER_MSG = \"Register a timer!\";\n    public static final String ECHO_SERVICE_IPC_INGRESS_MSG = \"Echo as Service IPC ingress\";\n    public static final String ECHO_SERVICE_IPC_INGRESS_MSG_SKIP_FOLLOWER =\n        \"Echo as Service IPC ingress (skip follower)\";\n    public static final String UNEXPECTED_MSG =\n        \"Should never get this message because it is not going to be committed!\";\n    public static final String ERROR_MSG = \"This message will cause an error\";\n    public static final String LARGE_MSG;\n    public static final String TERMINATE_MSG = \"Please terminate the clustered service\";\n    public static final String PAUSE = \"Please pause when processing message\";\n\n    static\n    {\n        final byte[] bs = new byte[1024];\n        Arrays.fill(bs, (byte)'a');\n        LARGE_MSG = new String(bs, StandardCharsets.US_ASCII);\n    }\n\n    private static final AtomicReference<Throwable> ERROR = new AtomicReference<>();\n    private static final AtomicReference<Throwable> WARNING = new AtomicReference<>();\n\n    public static final Runnable NOOP_TERMINATION_HOOK = () -> {};\n\n    public static Runnable terminationHook(final AtomicBoolean isTerminationExpected, final AtomicBoolean hasTerminated)\n    {\n        return\n            () ->\n            {\n                if (null != isTerminationExpected && isTerminationExpected.get())\n                {\n                    if (null != hasTerminated)\n                    {\n                        hasTerminated.set(true);\n                    }\n                }\n            };\n    }\n\n    public static ErrorHandler errorHandler(final int memberId)\n    {\n        return\n            (ex) ->\n            {\n                if (ex instanceof AeronException &&\n                    ((AeronException)ex).category() == AeronException.Category.WARN || shouldDownScaleToWarning(ex))\n                {\n                    addWarning(ex);\n                    return;\n                }\n\n                if (ex instanceof ClusterTerminationException)\n                {\n                    return;\n                }\n\n                addError(ex);\n                printMessageAndStackTrace(\"\\n*** Error in member \" + memberId + \" ***\\n\\n\", ex);\n                printWarning();\n            };\n    }\n\n    private static void printMessageAndStackTrace(final String message, final Throwable ex)\n    {\n        final StringWriter out = new StringWriter();\n        final PrintWriter writer = new PrintWriter(out);\n        writer.println(message);\n        ex.printStackTrace(writer);\n        System.err.println(out);\n    }\n\n    public static void addError(final Throwable ex)\n    {\n        final Throwable error = ERROR.get();\n        if (null == error)\n        {\n            ERROR.set(ex);\n        }\n        else if (error != ex)\n        {\n            error.addSuppressed(ex);\n        }\n    }\n\n    public static void addWarning(final Throwable ex)\n    {\n        final Throwable warning = WARNING.get();\n        if (null == warning)\n        {\n            WARNING.set(ex);\n        }\n        else if (warning != ex)\n        {\n            warning.addSuppressed(ex);\n        }\n    }\n\n    public static void printWarning()\n    {\n        final Throwable warning = WARNING.get();\n        if (null != warning)\n        {\n            printMessageAndStackTrace(\"\\n*** Warning captured ***\", warning);\n            warning.printStackTrace();\n        }\n    }\n\n    public static void failOnClusterError()\n    {\n        Throwable error = ERROR.getAndSet(null);\n        Throwable warning = WARNING.getAndSet(null);\n\n        if (null != error && shouldDownScaleToWarning(error))\n        {\n            warning = error;\n            error = null;\n        }\n\n        if (null != error)\n        {\n            if (null != warning)\n            {\n                System.err.println(\"\\n*** Warning captured with error ***\");\n                warning.printStackTrace(System.err);\n            }\n\n            throw new RuntimeException(\"Cluster node received error\", error);\n        }\n\n        if (Thread.currentThread().isInterrupted() && null != warning)\n        {\n            System.err.println(\"\\n*** Warning captured with interrupt ***\");\n            warning.printStackTrace(System.err);\n        }\n    }\n\n    public static void resetClusterError()\n    {\n        ERROR.set(null);\n        WARNING.set(null);\n    }\n\n    private static boolean shouldDownScaleToWarning(final Throwable error)\n    {\n        int depthLimit = 10;\n        Throwable maybeWarning = error;\n        while (null != maybeWarning && 0 < --depthLimit)\n        {\n            if (maybeWarning instanceof UnknownHostException)\n            {\n                return true;\n            }\n\n            maybeWarning = maybeWarning.getCause();\n        }\n\n        return false;\n    }\n\n    public static Thread startPublisherThread(final TestCluster testCluster, final MutableInteger messageCounter)\n    {\n        final Thread thread = new Thread(\n            () ->\n            {\n                final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;\n                final AeronCluster client = testCluster.client();\n                final ExpandableArrayBuffer msgBuffer = testCluster.msgBuffer();\n                final int messageLength = msgBuffer.putStringWithoutLengthAscii(0, HELLO_WORLD_MSG);\n\n                while (!Thread.interrupted())\n                {\n                    final long result = client.offer(msgBuffer, 0, messageLength);\n                    if (result > 0)\n                    {\n                        messageCounter.increment();\n                    }\n                    else\n                    {\n                        if (client.isClosed())\n                        {\n                            break;\n                        }\n                    }\n\n                    try\n                    {\n                        Thread.sleep(1);\n                    }\n                    catch (final InterruptedException ignore)\n                    {\n                        break;\n                    }\n\n                    idleStrategy.idle(client.pollEgress());\n                }\n            });\n\n        thread.setDaemon(true);\n        thread.setName(\"message-thread\");\n        thread.start();\n\n        return thread;\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/cluster/StubClusteredService.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.cluster;\n\nimport io.aeron.ExclusivePublication;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusteredService;\nimport io.aeron.logbuffer.Header;\nimport org.agrona.DirectBuffer;\nimport org.agrona.concurrent.IdleStrategy;\n\npublic class StubClusteredService implements ClusteredService\n{\n    protected Cluster cluster;\n    protected IdleStrategy idleStrategy;\n\n    public void onStart(final Cluster cluster, final Image snapshotImage)\n    {\n        this.cluster = cluster;\n        this.idleStrategy = cluster.idleStrategy();\n    }\n\n    public void onSessionOpen(final ClientSession session, final long timestamp)\n    {\n    }\n\n    public void onSessionClose(final ClientSession session, final long timestamp, final CloseReason closeReason)\n    {\n    }\n\n    public void onSessionMessage(\n        final ClientSession session,\n        final long timestamp,\n        final DirectBuffer buffer,\n        final int offset,\n        final int length,\n        final Header header)\n    {\n    }\n\n    public void onTimerEvent(final long correlationId, final long timestamp)\n    {\n    }\n\n    public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n    {\n    }\n\n    public void onRoleChange(final Cluster.Role newRole)\n    {\n    }\n\n    public void onTerminate(final Cluster cluster)\n    {\n    }\n\n    protected long serviceCorrelationId(final int correlationId)\n    {\n        return ((long)cluster.context().serviceId()) << 56 | correlationId;\n    }\n\n    protected final void echoMessage(\n        final ClientSession session, final DirectBuffer buffer, final int offset, final int length)\n    {\n        idleStrategy.reset();\n        while (true)\n        {\n            final long result = session.offer(buffer, offset, length);\n            if (result > 0)\n            {\n                return;\n            }\n\n            if (Publication.BACK_PRESSURED == result)\n            {\n                idleStrategy.idle();\n            }\n            else if (Publication.ADMIN_ACTION != result)\n            {\n                throw new ClusterException(\"egress publication error: result=\" +\n                    Publication.errorString(result) + \", clusterSessionId=\" + session.id());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/cluster/TestBackupNode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.cluster;\n\nimport io.aeron.Counter;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.cluster.ClusterBackup;\nimport io.aeron.cluster.ClusterTool;\nimport io.aeron.cluster.RecordingLog;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.test.DataCollector;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.EpochClock;\n\nimport java.util.Objects;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\n\npublic class TestBackupNode implements AutoCloseable\n{\n    private final TestMediaDriver mediaDriver;\n    private final Archive archive;\n    private final ClusterBackup clusterBackup;\n    private final int index;\n    private final Context context;\n    private boolean isClosed = false;\n\n    TestBackupNode(final int index, final Context context, final DataCollector dataCollector)\n    {\n        this.index = index;\n        this.context = context;\n        try\n        {\n            mediaDriver = TestMediaDriver.launch(\n                context.mediaDriverContext, TestCluster.clientDriverOutputConsumer(dataCollector));\n\n            final String aeronDirectoryName = mediaDriver.context().aeronDirectoryName();\n            archive = Archive.launch(context.archiveContext.aeronDirectoryName(aeronDirectoryName));\n\n            clusterBackup = ClusterBackup.launch(context.clusterBackupContext.aeronDirectoryName(aeronDirectoryName));\n\n            dataCollector.add(clusterBackup.context().clusterDir().toPath());\n            dataCollector.add(archive.context().archiveDir().toPath());\n            dataCollector.add(mediaDriver.context().aeronDirectory().toPath());\n        }\n        catch (final RuntimeException ex)\n        {\n            try\n            {\n                close();\n            }\n            catch (final Exception ex2)\n            {\n                ex.addSuppressed(ex2);\n            }\n            throw ex;\n        }\n    }\n\n    public void close()\n    {\n        if (!isClosed)\n        {\n            isClosed = true;\n            CloseHelper.closeAll(clusterBackup, archive, mediaDriver);\n        }\n    }\n\n    boolean isClosed()\n    {\n        return isClosed;\n    }\n\n    ClusterBackup.State backupState()\n    {\n        return ClusterBackup.State.get(context.clusterBackupContext.stateCounter());\n    }\n\n    long liveLogPosition()\n    {\n        final Counter counter = context.clusterBackupContext.liveLogPositionCounter();\n        if (counter.isClosed())\n        {\n            return NULL_POSITION;\n        }\n\n        return counter.get();\n    }\n\n    public long snapshotRetrieveCount()\n    {\n        return context.clusterBackupContext.snapshotRetrieveCounter().get();\n    }\n\n    public EpochClock epochClock()\n    {\n        return context.clusterBackupContext.epochClock();\n    }\n\n    public long nextBackupQueryDeadlineMs()\n    {\n        return ClusterTool.nextBackupQueryDeadlineMs(context.clusterBackupContext.clusterDir());\n    }\n\n    public boolean nextBackupQueryDeadlineMs(final long delayMs)\n    {\n        final long nowMs = epochClock().time();\n\n        return ClusterTool.nextBackupQueryDeadlineMs(context.clusterBackupContext.clusterDir(), nowMs + delayMs);\n    }\n\n    public AtomicBuffer clusterBackupErrorLog()\n    {\n        return clusterBackup.context().clusterMarkFile().errorBuffer();\n    }\n\n    long clusterBackupErrorCount()\n    {\n        return clusterBackup.context().errorCounter().get();\n    }\n\n    int index()\n    {\n        return index;\n    }\n\n    TestMediaDriver mediaDriver()\n    {\n        return mediaDriver;\n    }\n\n    public long recordingLogStartPosition()\n    {\n        try (RecordingLog recordingLog = new RecordingLog(context.clusterBackupContext.clusterDir(), false))\n        {\n            final long recordingId = Objects.requireNonNull(recordingLog.findLastTerm()).recordingId;\n\n            final AeronArchive.Context backupArchiveContext = context.clusterBackupContext.archiveContext();\n            try (AeronArchive aeronArchive = AeronArchive.connect(new AeronArchive.Context()\n                .aeronDirectoryName(backupArchiveContext.aeronDirectoryName())\n                .controlRequestChannel(backupArchiveContext.controlRequestChannel())\n                .controlRequestStreamId(backupArchiveContext.controlRequestStreamId())\n                .controlResponseChannel(backupArchiveContext.controlResponseChannel())\n                .controlResponseStreamId(ThreadLocalRandom.current().nextInt())))\n            {\n                return aeronArchive.getStartPosition(recordingId);\n            }\n        }\n    }\n\n    static class Context\n    {\n        final MediaDriver.Context mediaDriverContext = new MediaDriver.Context();\n        final Archive.Context archiveContext = new Archive.Context();\n        final ClusterBackup.Context clusterBackupContext = new ClusterBackup.Context();\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/cluster/TestCluster.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.ChannelUri;\nimport io.aeron.ChannelUriStringBuilder;\nimport io.aeron.CommonContext;\nimport io.aeron.Counter;\nimport io.aeron.Image;\nimport io.aeron.Publication;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.Subscription;\nimport io.aeron.archive.ArchiveThreadingMode;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.client.RecordingSignalConsumer;\nimport io.aeron.archive.codecs.RecordingSignal;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.cluster.ClusterBackup;\nimport io.aeron.cluster.ClusterBackupEventsListener;\nimport io.aeron.cluster.ClusterControl;\nimport io.aeron.cluster.ClusterMember;\nimport io.aeron.cluster.ClusterMembership;\nimport io.aeron.cluster.ClusterTool;\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.ConsensusModuleExtension;\nimport io.aeron.cluster.ElectionState;\nimport io.aeron.cluster.NodeControl;\nimport io.aeron.cluster.RecordingLog;\nimport io.aeron.cluster.TimerServiceSupplier;\nimport io.aeron.cluster.client.AeronCluster;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.client.ControlledEgressListener;\nimport io.aeron.cluster.client.EgressListener;\nimport io.aeron.cluster.codecs.BackupQueryEncoder;\nimport io.aeron.cluster.codecs.BackupResponseDecoder;\nimport io.aeron.cluster.codecs.EventCode;\nimport io.aeron.cluster.codecs.MessageHeaderDecoder;\nimport io.aeron.cluster.codecs.MessageHeaderEncoder;\nimport io.aeron.cluster.codecs.NewLeadershipTermEventDecoder;\nimport io.aeron.cluster.codecs.SessionMessageHeaderDecoder;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusterClock;\nimport io.aeron.cluster.service.ClusterMarkFile;\nimport io.aeron.driver.Configuration;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ReceiveChannelEndpointSupplier;\nimport io.aeron.driver.SendChannelEndpointSupplier;\nimport io.aeron.driver.ThreadingMode;\nimport io.aeron.exceptions.RegistrationException;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.samples.archive.RecordingDescriptor;\nimport io.aeron.samples.archive.RecordingDescriptorCollector;\nimport io.aeron.security.AuthenticatorSupplier;\nimport io.aeron.security.AuthorisationServiceSupplier;\nimport io.aeron.security.CredentialsSupplier;\nimport io.aeron.security.DefaultAuthenticatorSupplier;\nimport io.aeron.security.NullCredentialsSupplier;\nimport io.aeron.test.DataCollector;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.DriverOutputConsumer;\nimport io.aeron.test.driver.JavaTestMediaDriver;\nimport io.aeron.test.driver.RedirectingNameResolver;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.BitUtil;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.ArrayUtil;\nimport org.agrona.collections.IntHashSet;\nimport org.agrona.collections.MutableBoolean;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.MutableLong;\nimport org.agrona.collections.Object2ObjectHashMap;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.AtomicBuffer;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.SystemEpochClock;\nimport org.agrona.concurrent.errors.ErrorLogReader;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\nimport org.mockito.internal.matchers.apachecommons.ReflectionEquals;\n\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Comparator;\nimport java.util.EnumMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport java.util.function.IntFunction;\nimport java.util.function.IntPredicate;\nimport java.util.function.Predicate;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.cluster.ConsensusModule.Configuration.SERVICE_ID;\nimport static io.aeron.cluster.service.Cluster.Role.FOLLOWER;\nimport static io.aeron.test.cluster.ClusterTests.LARGE_MSG;\nimport static io.aeron.test.cluster.ClusterTests.PAUSE;\nimport static io.aeron.test.cluster.ClusterTests.errorHandler;\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.toList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\npublic final class TestCluster implements AutoCloseable\n{\n    static final int TERM_LENGTH = 64 * 1024;\n    static final int SEGMENT_FILE_LENGTH = 128 * 1024;\n    static final long CATALOG_CAPACITY = 128 * 1024;\n\n    static final String LOG_CHANNEL = \"aeron:udp?term-length=512k|alias=raft\";\n    static final String ARCHIVE_LOCAL_CONTROL_CHANNEL = \"aeron:ipc\";\n    static final String EGRESS_CHANNEL = \"aeron:udp?term-length=128k|endpoint=localhost:0|alias=egress\";\n    static final String INGRESS_CHANNEL = \"aeron:udp?term-length=128k|alias=ingress\";\n    static final String CONSENSUS_CHANNEL = \"aeron:udp?alias=consensus\";\n    static final long LEADER_HEARTBEAT_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(1);\n    static final long LEADER_HEARTBEAT_INTERVAL_NS = TimeUnit.MILLISECONDS.toNanos(100);\n    static final long ELECTION_TIMEOUT_NS = TimeUnit.MILLISECONDS.toNanos(500);\n    static final long ELECTION_STATUS_INTERVAL_NS = TimeUnit.MILLISECONDS.toNanos(100);\n    static final long STARTUP_CANVASS_TIMEOUT_NS = LEADER_HEARTBEAT_TIMEOUT_NS * 2;\n    static final long TERMINATION_TIMEOUT_NS = LEADER_HEARTBEAT_TIMEOUT_NS;\n    public static final String CLUSTER_BASE_DIR_PROP_NAME = \"aeron.test.system.cluster.base.dir\";\n\n    public static final String DEFAULT_NODE_MAPPINGS =\n        \"node0,localhost,localhost|\" +\n        \"node1,localhost,localhost|\" +\n        \"node2,localhost,localhost|\";\n\n    public static final short EXTENSION_TEMPLATE_ID = 10001;\n    public static final short EXTENSION_SCHEMA_ID = 10002;\n    public static final short EXTENSION_VERSION = (short)1;\n\n    private final DataCollector dataCollector = new DataCollector();\n    private final ExpandableArrayBuffer msgBuffer = new ExpandableArrayBuffer();\n    private final MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();\n    private final DefaultEgressListener defaultEgressListener = new DefaultEgressListener();\n    private EgressListener egressListener = defaultEgressListener;\n    private ControlledEgressListener controlledEgressListener;\n\n    private final TestNode[] nodes;\n    private final String clusterMembers;\n    private final String clusterMemberEndpoints;\n    private final String[] senderWildcardPortRanges;\n    private final String[] receiverWildcardPortRanges;\n    private final String clusterConsensusEndpoints;\n    private final int memberCount;\n    private final int appointedLeaderId;\n    private final int backupNodeIndex;\n    private final int clusterId;\n    private final IntFunction<TestNode.TestService[]> serviceSupplier;\n    private final boolean useResponseChannels;\n    private final Map<TestNode, BackupQueryRunner> backQueryRunners = new Object2ObjectHashMap<>();\n\n    private long leaderHeartbeatTimeoutNs;\n    private long leaderHeartbeatIntervalNs;\n    private long electionTimeoutNs;\n    private long electionStatusIntervalNs;\n    private long startupCanvassTimeoutNs;\n    private long terminationTimeoutNs;\n\n    private String logChannel;\n    private String ingressChannel;\n    private String egressChannel;\n    private AuthorisationServiceSupplier authorisationServiceSupplier;\n    private AuthenticatorSupplier authenticationSupplier;\n    private TimerServiceSupplier timerServiceSupplier;\n    private SendChannelEndpointSupplier clientSendChannelEndpointSupplier;\n    private ReceiveChannelEndpointSupplier clientReceiveChannelEndpointSupplier;\n    private long clientImageLivenessTimeoutNs = Configuration.imageLivenessTimeoutNs();\n    private ThreadingMode clientThreadingMode = ThreadingMode.SHARED;\n    private TestMediaDriver clientMediaDriver;\n    private AeronCluster client;\n    private TestBackupNode backupNode;\n    private long imageLivenessTimeoutNs;\n    private long sessionTimeoutNs;\n    private int archiveSegmentFileLength;\n    private IntHashSet byHostInvalidInitialResolutions;\n    private IntHashSet byMemberInvalidInitialResolutions;\n    private boolean acceptStandbySnapshots;\n    private File markFileBaseDir;\n    private String clusterBaseDir;\n    private String aeronBaseDir;\n    private ClusterBackup.Configuration.ReplayStart replayStart;\n    private List<String> hostnames;\n    private Function<Aeron, Counter> errorCounterSupplier;\n    private Function<Aeron, Counter> snapshotCounterSupplier;\n    private Supplier<ConsensusModuleExtension> extensionSupplier;\n    private IntFunction<SendChannelEndpointSupplier> sendChannelEndpointSupplier;\n    private IntFunction<ReceiveChannelEndpointSupplier> receiveChannelEndpointSupplier;\n    private ClusterClock clusterClock;\n\n    private TestCluster(\n        final int clusterId,\n        final int memberCount,\n        final int appointedLeaderId,\n        final IntHashSet byHostInvalidInitialResolutions,\n        final IntFunction<TestNode.TestService[]> serviceSupplier,\n        final boolean useResponseChannels)\n    {\n        this.clusterId = clusterId;\n        if ((memberCount + 1) >= 10)\n        {\n            throw new IllegalArgumentException(\"max members exceeded: max=9 count=\" + memberCount);\n        }\n        this.memberCount = memberCount;\n        this.serviceSupplier = requireNonNull(serviceSupplier);\n\n        this.nodes = new TestNode[memberCount + 1];\n        this.backupNodeIndex = memberCount;\n        this.useResponseChannels = useResponseChannels;\n        this.clusterMembers = clusterMembers(clusterId, 0, memberCount, memberCount, this.useResponseChannels);\n        this.clusterMemberEndpoints = ingressEndpoints(clusterId, 0, memberCount, memberCount);\n        this.senderWildcardPortRanges = senderWildcardPortRanges();\n        this.receiverWildcardPortRanges = receiverWildcardPortRanges();\n        this.clusterConsensusEndpoints = clusterConsensusEndpoints(0, memberCount);\n        this.appointedLeaderId = appointedLeaderId;\n        this.byHostInvalidInitialResolutions = byHostInvalidInitialResolutions;\n    }\n\n    public static long awaitLeaderLogRecording(\n        final TestCluster cluster, final TestNode leader, final int expectedMessageCount)\n    {\n        final long firstLeaderLogRecordingId =\n            RecordingPos.getRecordingId(leader.mediaDriver().counters(), leader.logRecordingCounterId());\n        final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n        final SessionMessageHeaderDecoder sessionHeaderDecoder = new SessionMessageHeaderDecoder();\n\n        // await leader to record all ingress messages\n        try (AeronArchive aeronArchive = AeronArchive.connect(new AeronArchive.Context()\n            .clientName(\"test\")\n            .aeronDirectoryName(cluster.startClientMediaDriver().aeronDirectoryName())\n            .controlRequestChannel(leader.archive().context().controlChannel())\n            .controlRequestStreamId(leader.archive().context().controlStreamId())\n            .controlResponseChannel(\"aeron:udp?endpoint=localhost:0\")))\n        {\n            final Aeron aeron = aeronArchive.context().aeron();\n            final String replayChannel = \"aeron:udp?endpoint=localhost:18181\";\n            final int replayStreamId = 1111;\n            final long replaySubscriptionId = aeronArchive.startReplay(\n                firstLeaderLogRecordingId, 0, AeronArchive.REPLAY_ALL_AND_FOLLOW, replayChannel, replayStreamId);\n            final int sessionId = (int)replaySubscriptionId;\n            final Subscription subscription =\n                aeron.addSubscription(ChannelUri.addSessionId(replayChannel, sessionId), replayStreamId);\n            Tests.awaitConnected(subscription);\n\n            final Image image = subscription.imageBySessionId(sessionId);\n            assertNotNull(image);\n            final MutableInteger messageCount = new MutableInteger();\n            final FragmentHandler fragmentHandler = (buffer, offset, length, header) ->\n            {\n                messageHeaderDecoder.wrap(buffer, offset);\n                if (MessageHeaderDecoder.SCHEMA_ID == messageHeaderDecoder.schemaId() &&\n                    SessionMessageHeaderDecoder.TEMPLATE_ID == messageHeaderDecoder.templateId())\n                {\n                    sessionHeaderDecoder.wrap(\n                        buffer,\n                        offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                        messageHeaderDecoder.blockLength(),\n                        messageHeaderDecoder.version());\n                    messageCount.increment();\n                }\n            };\n\n            final Supplier<String> messageSupplier = () -> \"awaiting expectedMessageCount=\" + expectedMessageCount +\n                \", currentMessageCount=\" + messageCount.get();\n            while (messageCount.get() < expectedMessageCount)\n            {\n                if (0 == image.poll(fragmentHandler, 100))\n                {\n                    Tests.yieldingIdle(messageSupplier);\n                }\n            }\n\n            final long position = image.position();\n\n            subscription.close();\n            aeronArchive.stopReplay(replaySubscriptionId);\n\n            return position;\n        }\n    }\n\n    public int clusterId()\n    {\n        return clusterId;\n    }\n\n    public int memberCount()\n    {\n        return memberCount;\n    }\n\n    public static void awaitElectionClosed(final TestNode follower)\n    {\n        awaitElectionState(follower, ElectionState.CLOSED);\n    }\n\n    public static void awaitElectionState(final TestNode node, final ElectionState electionState)\n    {\n        final Supplier<String> msg = () -> \"index=\" + node.index() + \" role=\" + node.role() + \" electionState=\" +\n            node.electionState() + \" expected=\" + electionState;\n        while (node.electionState() != electionState)\n        {\n            await(10, msg);\n        }\n    }\n\n    private static void await(final int delayMs)\n    {\n        Tests.sleep(delayMs);\n        ClusterTests.failOnClusterError();\n    }\n\n    private static void await(final int delayMs, final Supplier<String> message)\n    {\n        Tests.sleep(delayMs, message);\n        ClusterTests.failOnClusterError();\n    }\n\n    public static ClusterMembership awaitMembershipSize(final TestNode node, final int size)\n    {\n        while (true)\n        {\n            final ClusterMembership clusterMembership = node.clusterMembership();\n            if (clusterMembership.activeMembers.size() == size)\n            {\n                return clusterMembership;\n            }\n            await(10);\n        }\n    }\n\n    public static void awaitActiveMember(final TestNode node)\n    {\n        while (true)\n        {\n            final ClusterMembership clusterMembership = node.clusterMembership();\n            if (clusterMembership.activeMembers.stream().anyMatch((cm) -> cm.id() == node.index()))\n            {\n                return;\n            }\n            await(10);\n        }\n    }\n\n    public void close()\n    {\n        final boolean isInterrupted = Thread.interrupted();\n        try\n        {\n            backQueryRunners.values().forEach(CloseHelper::close);\n\n            CloseHelper.closeAll(\n                client,\n                clientMediaDriver,\n                null != backupNode ? () -> backupNode.close() : null,\n                () -> CloseHelper.closeAll(Stream.of(nodes).filter(Objects::nonNull).collect(toList())));\n        }\n        finally\n        {\n            if (isInterrupted)\n            {\n                Thread.currentThread().interrupt();\n            }\n        }\n\n        ClusterTests.failOnClusterError();\n    }\n\n    public TestNode startStaticNode(final int index, final boolean cleanStart)\n    {\n        return startStaticNode(index, cleanStart, serviceSupplier);\n    }\n\n    public TestNode startStaticNode(\n        final int index, final boolean cleanStart, final IntFunction<TestNode.TestService[]> serviceSupplier)\n    {\n        final String baseDirName = clusterBaseDir + \"-\" + index;\n        final String aeronDirName = aeronBaseDir + \"-\" + index;\n        final File markFileDir = null != markFileBaseDir ? new File(markFileBaseDir, \"mark-\" + index) : null;\n        final TestNode.Context context = new TestNode.Context(\n            serviceSupplier.apply(index), hostname(index, memberCount), nodeNameMappings());\n        context.extensionSupplier = extensionSupplier;\n        context.errorCounterSupplier = errorCounterSupplier;\n        context.snapshotCounterSupplier = snapshotCounterSupplier;\n\n        context.aeronArchiveContext\n            .lock(NoOpLock.INSTANCE)\n            .controlRequestChannel(archiveControlRequestChannel(index))\n            .controlResponseChannel(archiveControlResponseChannel(index))\n            .controlResponseStreamId(3000 + index)\n            .aeronDirectoryName(aeronDirName);\n\n        context.mediaDriverContext\n            .aeronDirectoryName(aeronDirName)\n            .threadingMode(ThreadingMode.SHARED)\n            .termBufferSparseFile(true)\n            .senderWildcardPortRange(senderWildcardPortRanges[index])\n            .receiverWildcardPortRange(receiverWildcardPortRanges[index])\n            .dirDeleteOnShutdown(true)\n            .dirDeleteOnStart(true)\n            .imageLivenessTimeoutNs(imageLivenessTimeoutNs)\n            .ipcPublicationTermWindowLength(TERM_LENGTH)\n            .publicationTermBufferLength(TERM_LENGTH)\n            .sendChannelEndpointSupplier(sendChannelEndpointSupplier.apply(index))\n            .receiveChannelEndpointSupplier(receiveChannelEndpointSupplier.apply(index));\n\n        context.archiveContext\n            .archiveId(index)\n            .catalogCapacity(CATALOG_CAPACITY)\n            .archiveDir(new File(baseDirName, \"archive\"))\n            .controlChannel(context.aeronArchiveContext.controlRequestChannel())\n            .controlStreamId(context.aeronArchiveContext.controlRequestStreamId())\n            .localControlChannel(ARCHIVE_LOCAL_CONTROL_CHANNEL + \"?alias=archiveId-\" + index)\n            .localControlStreamId(context.aeronArchiveContext.controlRequestStreamId())\n            .recordingEventsEnabled(false)\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .deleteArchiveOnStart(cleanStart)\n            .segmentFileLength(archiveSegmentFileLength)\n            .replicationChannel(archiveReplicationChannel(index));\n\n        context.consensusModuleContext\n            .clusterId(clusterId)\n            .clusterMemberId(index)\n            .clusterMembers(clusterMembers)\n            .startupCanvassTimeoutNs(startupCanvassTimeoutNs)\n            .terminationTimeoutNs(terminationTimeoutNs)\n            .leaderHeartbeatTimeoutNs(leaderHeartbeatTimeoutNs)\n            .leaderHeartbeatIntervalNs(leaderHeartbeatIntervalNs)\n            .electionTimeoutNs(electionTimeoutNs)\n            .electionStatusIntervalNs(electionStatusIntervalNs)\n            .appointedLeaderId(appointedLeaderId)\n            .clusterDir(new File(baseDirName, \"consensus-module\"))\n            .ingressChannel(ingressChannel)\n            .logChannel(logChannel)\n            .consensusChannel(CONSENSUS_CHANNEL)\n            .replicationChannel(clusterReplicationChannel(index))\n            .archiveContext(context.aeronArchiveContext.clone()\n                .controlRequestChannel(context.archiveContext.localControlChannel())\n                .controlRequestStreamId(context.archiveContext.localControlStreamId())\n                .controlResponseChannel(ARCHIVE_LOCAL_CONTROL_CHANNEL))\n            .sessionTimeoutNs(sessionTimeoutNs)\n            .totalSnapshotDurationThresholdNs(TimeUnit.MILLISECONDS.toNanos(100))\n            .clusterClock(clusterClock)\n            .authenticatorSupplier(authenticationSupplier)\n            .authorisationServiceSupplier(authorisationServiceSupplier)\n            .timerServiceSupplier(timerServiceSupplier)\n            .acceptStandbySnapshots(acceptStandbySnapshots)\n            .terminationTimeoutNs(leaderHeartbeatTimeoutNs)\n            .markFileDir(markFileDir)\n            .deleteDirOnStart(cleanStart);\n\n        nodes[index] = new TestNode(context, dataCollector);\n\n        return nodes[index];\n    }\n\n    public TestBackupNode startClusterBackupNode(final boolean cleanStart)\n    {\n        return startClusterBackupNode(cleanStart, new NullCredentialsSupplier());\n    }\n\n    public TestBackupNode startClusterBackupNode(final boolean cleanStart, final ClusterBackup.SourceType sourceType)\n    {\n        return startClusterBackupNode(cleanStart, new NullCredentialsSupplier(), sourceType);\n    }\n\n    public TestBackupNode startClusterBackupNode(\n        final boolean cleanStart,\n        final CredentialsSupplier credentialsSupplier)\n    {\n        return startClusterBackupNode(cleanStart, credentialsSupplier, ClusterBackup.SourceType.FOLLOWER);\n    }\n\n    public TestBackupNode startClusterBackupNode(\n        final boolean cleanStart,\n        final CredentialsSupplier credentialsSupplier,\n        final ClusterBackup.SourceType sourceType)\n    {\n        return startClusterBackupNode(cleanStart, credentialsSupplier, sourceType, 0);\n    }\n\n    public TestBackupNode startClusterBackupNode(\n        final boolean cleanStart,\n        final CredentialsSupplier credentialsSupplier,\n        final ClusterBackup.SourceType sourceType,\n        final int catchupEndpointPort)\n    {\n        final int index = memberCount;\n        final String baseDirName = clusterBaseDir + \"-\" + index;\n        final String aeronDirName = aeronBaseDir + \"-\" + index;\n        final File markFileDir = null != markFileBaseDir ? new File(markFileBaseDir, \"mark-\" + index) : null;\n        final TestBackupNode.Context context = new TestBackupNode.Context();\n\n        context.mediaDriverContext\n            .aeronDirectoryName(aeronDirName)\n            .threadingMode(ThreadingMode.SHARED)\n            .termBufferSparseFile(true)\n            .errorHandler(errorHandler(index))\n            .senderWildcardPortRange(senderWildcardPortRanges[index])\n            .receiverWildcardPortRange(receiverWildcardPortRanges[index])\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .nameResolver(new RedirectingNameResolver(nodeNameMappings(index, index + 1)))\n            .imageLivenessTimeoutNs(imageLivenessTimeoutNs)\n            .ipcPublicationTermWindowLength(TERM_LENGTH)\n            .publicationTermBufferLength(TERM_LENGTH);\n\n        context.archiveContext\n            .archiveId(index)\n            .catalogCapacity(CATALOG_CAPACITY)\n            .aeronDirectoryName(aeronDirName)\n            .archiveDir(new File(baseDirName, \"archive\"))\n            .controlChannel(archiveControlRequestChannel(index) + \"|alias=backup-control\")\n            .controlStreamId(-2734238)\n            .localControlChannel(\"aeron:ipc?alias=backup-local-control\")\n            .localControlStreamId(8080808 + index)\n            .recordingEventsEnabled(false)\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .deleteArchiveOnStart(cleanStart)\n            .segmentFileLength(archiveSegmentFileLength)\n            .replicationChannel(archiveReplicationChannel(index));\n\n        final ChannelUri consensusChannelUri = ChannelUri.parse(context.clusterBackupContext.consensusChannel());\n        final String backupStatusEndpoint = clusterBackupStatusEndpoint(index);\n        consensusChannelUri.put(CommonContext.ENDPOINT_PARAM_NAME, backupStatusEndpoint);\n\n        context.clusterBackupContext\n            .clusterId(clusterId)\n            .clusterConsensusEndpoints(clusterConsensusEndpoints)\n            .consensusChannel(consensusChannelUri.toString())\n            .clusterBackupCoolDownIntervalNs(TimeUnit.SECONDS.toNanos(1))\n            .catchupEndpoint(hostname(index, memberCount) + \":\" + catchupEndpointPort)\n            .archiveContext(new AeronArchive.Context()\n                .aeronDirectoryName(aeronDirName)\n                .controlRequestChannel(context.archiveContext.localControlChannel())\n                .controlRequestStreamId(context.archiveContext.localControlStreamId())\n                .controlResponseChannel(\"aeron:ipc?alias=backup-archive-local-resp\")\n                .controlResponseStreamId(9090909 + index))\n            .clusterArchiveContext(new AeronArchive.Context()\n                .aeronDirectoryName(aeronDirName)\n                .controlRequestChannel(context.archiveContext.controlChannel())\n                .controlResponseChannel(archiveControlResponseChannel(index)))\n            .clusterDir(new File(baseDirName, \"cluster-backup\"))\n            .credentialsSupplier(credentialsSupplier)\n            .sourceType(sourceType)\n            .deleteDirOnStart(cleanStart)\n            .markFileDir(markFileDir)\n            .initialReplayStart(replayStart)\n            .eventsListener(new BackupListener());\n\n        backupNode = new TestBackupNode(index, context, dataCollector);\n\n        return backupNode;\n    }\n\n    public TestNode startStaticNodeFromBackup()\n    {\n        return startStaticNodeFromBackup(serviceSupplier);\n    }\n\n    public TestNode startStaticNodeFromBackup(final IntFunction<TestNode.TestService[]> serviceSupplier)\n    {\n        final String baseDirName = clusterBaseDir + \"-\" + backupNodeIndex;\n        final String aeronDirName = aeronBaseDir + \"-\" + backupNodeIndex;\n        final File markFileDir = null != markFileBaseDir ? new File(markFileBaseDir, \"mark-\" + backupNodeIndex) : null;\n        final TestNode.Context context = new TestNode.Context(\n            serviceSupplier.apply(backupNodeIndex), hostname(backupNodeIndex, memberCount), nodeNameMappings());\n\n        if (null == backupNode || !backupNode.isClosed())\n        {\n            throw new IllegalStateException(\"backup node must be closed before starting from backup\");\n        }\n\n        context.aeronArchiveContext\n            .controlRequestChannel(archiveControlRequestChannel(backupNodeIndex))\n            .controlResponseChannel(archiveControlResponseChannel(backupNodeIndex))\n            .aeronDirectoryName(aeronDirName);\n\n        context.mediaDriverContext\n            .aeronDirectoryName(aeronDirName)\n            .threadingMode(ThreadingMode.SHARED)\n            .termBufferSparseFile(true)\n            .senderWildcardPortRange(senderWildcardPortRanges[backupNodeIndex])\n            .receiverWildcardPortRange(receiverWildcardPortRanges[backupNodeIndex])\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true);\n\n        context.archiveContext\n            .catalogCapacity(CATALOG_CAPACITY)\n            .archiveDir(new File(baseDirName, \"archive\"))\n            .controlChannel(context.aeronArchiveContext.controlRequestChannel())\n            .localControlChannel(ARCHIVE_LOCAL_CONTROL_CHANNEL)\n            .recordingEventsEnabled(false)\n            .threadingMode(ArchiveThreadingMode.SHARED)\n            .deleteArchiveOnStart(false)\n            .replicationChannel(archiveReplicationChannel(backupNodeIndex));\n\n        context.consensusModuleContext\n            .clusterMemberId(backupNodeIndex)\n            .clusterMembers(singleNodeClusterMember(backupNodeIndex))\n            .appointedLeaderId(backupNodeIndex)\n            .clusterDir(new File(baseDirName, \"cluster-backup\"))\n            .ingressChannel(ingressChannel)\n            .logChannel(logChannel)\n            .replicationChannel(clusterReplicationChannel(backupNodeIndex))\n            .archiveContext(context.aeronArchiveContext.clone()\n                .controlRequestChannel(ARCHIVE_LOCAL_CONTROL_CHANNEL)\n                .controlResponseChannel(ARCHIVE_LOCAL_CONTROL_CHANNEL))\n            .sessionTimeoutNs(sessionTimeoutNs)\n            .authorisationServiceSupplier(authorisationServiceSupplier)\n            .timerServiceSupplier(timerServiceSupplier)\n            .acceptStandbySnapshots(acceptStandbySnapshots)\n            .markFileDir(markFileDir)\n            .deleteDirOnStart(false);\n\n        backupNode = null;\n        nodes[backupNodeIndex] = new TestNode(context, dataCollector);\n\n        return nodes[backupNodeIndex];\n    }\n\n    public void leaderHeartbeatTimeoutNs(final long leaderHeartbeatTimeoutNs)\n    {\n        this.leaderHeartbeatTimeoutNs = leaderHeartbeatTimeoutNs;\n    }\n\n    public void leaderHeartbeatIntervalNs(final long leaderHeartbeatIntervalNs)\n    {\n        this.leaderHeartbeatIntervalNs = leaderHeartbeatIntervalNs;\n    }\n\n    public void electionTimeoutNs(final long electionTimeoutNs)\n    {\n        this.electionTimeoutNs = electionTimeoutNs;\n    }\n\n    public void electionStatusIntervalNs(final long electionStatusIntervalNs)\n    {\n        this.electionStatusIntervalNs = electionStatusIntervalNs;\n    }\n\n    public void startupCanvassTimeoutNs(final long startupCanvassTimeoutNs)\n    {\n        this.startupCanvassTimeoutNs = startupCanvassTimeoutNs;\n    }\n\n    public void terminationTimeoutNs(final long terminationTimeoutNs)\n    {\n        this.terminationTimeoutNs = terminationTimeoutNs;\n    }\n\n    public void stopNode(final TestNode testNode)\n    {\n        testNode.close();\n    }\n\n    public void stopAllNodes()\n    {\n        CloseHelper.close(backupNode);\n        CloseHelper.closeAll(nodes);\n    }\n\n    public void stopClient()\n    {\n        CloseHelper.closeAll(client, clientMediaDriver);\n    }\n\n    public void restartAllNodes(final boolean cleanStart)\n    {\n        for (int i = 0; i < memberCount; i++)\n        {\n            startStaticNode(i, cleanStart);\n        }\n    }\n\n    public void shouldErrorOnClientClose(final boolean shouldErrorOnClose)\n    {\n        defaultEgressListener.shouldErrorOnClientClose = shouldErrorOnClose;\n    }\n\n    public void logChannel(final String logChannel)\n    {\n        this.logChannel = logChannel;\n    }\n\n    public void ingressChannel(final String ingressChannel)\n    {\n        this.ingressChannel = ingressChannel;\n    }\n\n    public void egressChannel(final String egressChannel)\n    {\n        this.egressChannel = egressChannel;\n    }\n\n    public void egressListener(final EgressListener egressListener)\n    {\n        this.egressListener = egressListener;\n    }\n\n    public void controlledEgressListener(final ControlledEgressListener controlledEgressListener)\n    {\n        this.controlledEgressListener = controlledEgressListener;\n    }\n\n    public void authorisationServiceSupplier(final AuthorisationServiceSupplier authorisationServiceSupplier)\n    {\n        this.authorisationServiceSupplier = authorisationServiceSupplier;\n    }\n\n    public void timerServiceSupplier(final TimerServiceSupplier timerServiceSupplier)\n    {\n        this.timerServiceSupplier = timerServiceSupplier;\n    }\n\n    public void authenticationSupplier(final AuthenticatorSupplier authenticationSupplier)\n    {\n        this.authenticationSupplier = authenticationSupplier;\n    }\n\n    public void imageLivenessTimeoutNs(final long imageLivenessTimeoutNs)\n    {\n        this.imageLivenessTimeoutNs = imageLivenessTimeoutNs;\n    }\n\n    public void sessionTimeoutNs(final long sessionTimeoutNs)\n    {\n        this.sessionTimeoutNs = sessionTimeoutNs;\n    }\n\n    private void segmentFileLength(final int archiveSegmentFileLength)\n    {\n        this.archiveSegmentFileLength = archiveSegmentFileLength;\n    }\n\n    public void clientSendChannelEndpointSupplier(final SendChannelEndpointSupplier clientSendChannelEndpointSupplier)\n    {\n        this.clientSendChannelEndpointSupplier = clientSendChannelEndpointSupplier;\n    }\n\n    public void clientReceiveChannelEndpointSupplier(\n        final ReceiveChannelEndpointSupplier clientReceiveChannelEndpointSupplier)\n    {\n        this.clientReceiveChannelEndpointSupplier = clientReceiveChannelEndpointSupplier;\n    }\n\n    public void clientImageLivenessTimeoutNs(final long clientImageLivenessTimeoutNs)\n    {\n        this.clientImageLivenessTimeoutNs = clientImageLivenessTimeoutNs;\n    }\n\n    public void clientThreadingMode(final ThreadingMode threadingMode)\n    {\n        this.clientThreadingMode = threadingMode;\n    }\n\n    public AeronCluster client()\n    {\n        return client;\n    }\n\n    public ExpandableArrayBuffer msgBuffer()\n    {\n        return msgBuffer;\n    }\n\n    public AeronCluster reconnectClient()\n    {\n        if (null == client)\n        {\n            throw new IllegalStateException(\"Aeron client not previously connected\");\n        }\n\n        return connectClient();\n    }\n\n    public AeronCluster.Context clientCtx()\n    {\n        final AeronCluster.Context context = new AeronCluster.Context().ingressChannel(ingressChannel)\n            .egressChannel(egressChannel);\n        setIngressEndpoints(context);\n        return context;\n    }\n\n    public AeronCluster connectClient()\n    {\n        return connectClient(clientCtx());\n    }\n\n    public AeronCluster connectClient(final CredentialsSupplier credentialsSupplier)\n    {\n        return connectClient(clientCtx().credentialsSupplier(credentialsSupplier));\n    }\n\n    public AeronCluster connectClient(final AeronCluster.Context clientCtx)\n    {\n        startClientMediaDriver();\n\n        clientCtx\n            .aeronDirectoryName(clientMediaDriver.aeronDirectoryName())\n            .isIngressExclusive(true)\n            .egressListener(egressListener)\n            .controlledEgressListener(controlledEgressListener);\n\n        setIngressEndpoints(clientCtx);\n\n        try\n        {\n            CloseHelper.close(client);\n            client = AeronCluster.connect(clientCtx.clone());\n        }\n        catch (final TimeoutException ex)\n        {\n            System.out.println(\"Warning: \" + ex);\n\n            CloseHelper.close(client);\n            client = AeronCluster.connect(clientCtx);\n        }\n\n        return client;\n    }\n\n    public AeronCluster asyncConnectClient()\n    {\n        final AeronCluster.Context clientCtx = clientCtx();\n\n        startClientMediaDriver();\n\n        final Aeron aeron = Aeron.connect(new Aeron.Context()\n            .useConductorAgentInvoker(true)\n            .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE)\n            .aeronDirectoryName(clientMediaDriver.aeronDirectoryName()));\n\n        clientCtx\n            .aeron(aeron)\n            .ownsAeronClient(true)\n            .isIngressExclusive(true)\n            .egressListener(egressListener)\n            .controlledEgressListener(controlledEgressListener);\n\n        setIngressEndpoints(clientCtx);\n\n        final AgentInvoker conductorAgentInvoker = aeron.conductorAgentInvoker();\n        try\n        {\n            CloseHelper.close(client);\n            final AeronCluster.AsyncConnect asyncConnect = AeronCluster.asyncConnect(clientCtx.clone());\n            while (null == (client = asyncConnect.poll()))\n            {\n                invokeSharedAgentInvoker();\n                if (null != conductorAgentInvoker)\n                {\n                    conductorAgentInvoker.invoke();\n                }\n\n                Tests.yield();\n            }\n        }\n        catch (final TimeoutException ex)\n        {\n            System.out.println(\"Warning: \" + ex);\n\n            CloseHelper.close(client);\n            final AeronCluster.AsyncConnect asyncConnect = AeronCluster.asyncConnect(clientCtx.clone());\n            while (null == (client = asyncConnect.poll()))\n            {\n                invokeSharedAgentInvoker();\n                if (null != conductorAgentInvoker)\n                {\n                    conductorAgentInvoker.invoke();\n                }\n\n                Tests.yield();\n            }\n        }\n\n        return client;\n    }\n\n    public TestMediaDriver startClientMediaDriver()\n    {\n        if (null == clientMediaDriver)\n        {\n            final MediaDriver.Context ctx = newClientMediaDriverContext();\n\n            clientMediaDriver = TestMediaDriver.launch(ctx, clientDriverOutputConsumer(dataCollector));\n        }\n        return clientMediaDriver;\n    }\n\n    public MediaDriver.Context newClientMediaDriverContext()\n    {\n        final String aeronDirName = aeronBaseDir + \"-client\";\n        dataCollector.add(Paths.get(aeronDirName));\n\n        return new MediaDriver.Context()\n            .threadingMode(clientThreadingMode)\n            .dirDeleteOnStart(true)\n            .dirDeleteOnShutdown(true)\n            .aeronDirectoryName(aeronDirName)\n            .nameResolver(new RedirectingNameResolver(nodeNameMappings()))\n            .senderWildcardPortRange(\"20700 20709\")\n            .receiverWildcardPortRange(\"20710 20719\")\n            .sendChannelEndpointSupplier(clientSendChannelEndpointSupplier)\n            .receiveChannelEndpointSupplier(clientReceiveChannelEndpointSupplier)\n            .imageLivenessTimeoutNs(clientImageLivenessTimeoutNs)\n            .ipcPublicationTermWindowLength(TERM_LENGTH)\n            .publicationTermBufferLength(TERM_LENGTH);\n    }\n\n    private void setIngressEndpoints(final AeronCluster.Context clientCtx)\n    {\n        final ChannelUri ingressChannelUri = ChannelUri.parse(ingressChannel);\n        if (ingressChannelUri.isUdp() && !ingressChannelUri.containsKey(CommonContext.ENDPOINT_PARAM_NAME))\n        {\n            clientCtx.ingressEndpoints(clusterMemberEndpoints);\n        }\n    }\n\n    public AeronCluster connectIpcClient(final AeronCluster.Context clientCtx, final String aeronDirName)\n    {\n        clientCtx\n            .aeronDirectoryName(aeronDirName)\n            .isIngressExclusive(true)\n            .ingressChannel(\"aeron:ipc\")\n            .egressChannel(\"aeron:ipc\")\n            .egressListener(egressListener)\n            .controlledEgressListener(controlledEgressListener)\n            .ingressEndpoints(null);\n\n        try\n        {\n            CloseHelper.close(client);\n            client = AeronCluster.connect(clientCtx.clone());\n        }\n        catch (final TimeoutException ex)\n        {\n            CloseHelper.close(client);\n            client = AeronCluster.connect(clientCtx);\n        }\n\n        return client;\n    }\n\n    public void closeClient()\n    {\n        CloseHelper.close(client);\n    }\n\n    public int sendMessages(final int messageCount)\n    {\n        for (int i = 0; i < messageCount; i++)\n        {\n            msgBuffer.putInt(0, i);\n            try\n            {\n                pollUntilMessageSent(BitUtil.SIZE_OF_INT);\n            }\n            catch (final Exception ex)\n            {\n                final String msg = \"failed to send message \" + i + \" of \" + messageCount + \" cause=\" + ex.getMessage();\n                throw new ClusterException(msg, ex);\n            }\n        }\n        return messageCount;\n    }\n\n    public void sendExtensionMessages(final int messageCount)\n    {\n        messageHeaderEncoder.wrap(msgBuffer, 0)\n            .blockLength(BitUtil.SIZE_OF_INT)\n            .templateId(EXTENSION_TEMPLATE_ID)\n            .schemaId(EXTENSION_SCHEMA_ID)\n            .version(EXTENSION_VERSION);\n\n        for (int i = 0; i < messageCount; i++)\n        {\n            msgBuffer.putInt(MessageHeaderEncoder.ENCODED_LENGTH, i);\n            try\n            {\n                pollUntilMessageSent(\n                    client.ingressPublication(),\n                    msgBuffer,\n                    0,\n                    MessageHeaderEncoder.ENCODED_LENGTH + BitUtil.SIZE_OF_INT);\n            }\n            catch (final Exception ex)\n            {\n                final String msg = \"failed to send message \" + i + \" of \" + messageCount + \" cause=\" + ex.getMessage();\n                throw new ClusterException(msg, ex);\n            }\n        }\n    }\n\n    public void sendLargeMessages(final int messageCount)\n    {\n        final int messageLength = msgBuffer.putStringWithoutLengthAscii(0, LARGE_MSG);\n\n        for (int i = 0; i < messageCount; i++)\n        {\n            try\n            {\n                pollUntilMessageSent(messageLength);\n            }\n            catch (final Exception ex)\n            {\n                final String msg = \"failed to send message \" + i + \" of \" + messageCount + \" cause=\" + ex.getMessage();\n                throw new ClusterException(msg, ex);\n            }\n        }\n    }\n\n    public void sendMessageToSlowDownService(final int index, final long durationNs)\n    {\n        final String pause = PAUSE + \"|\" + index + \"|\" + durationNs;\n        final int messageLength = msgBuffer.putStringWithoutLengthAscii(0, pause);\n\n        try\n        {\n            pollUntilMessageSent(messageLength);\n        }\n        catch (final Exception ex)\n        {\n            final String msg = \"failed to send message cause=\" + ex.getMessage();\n            throw new ClusterException(msg, ex);\n        }\n    }\n\n    public void sendUnexpectedMessages(final int messageCount)\n    {\n        final int messageLength = msgBuffer.putStringWithoutLengthAscii(0, ClusterTests.UNEXPECTED_MSG);\n\n        for (int i = 0; i < messageCount; i++)\n        {\n            try\n            {\n                pollUntilMessageSent(messageLength);\n            }\n            catch (final Exception ex)\n            {\n                final String msg = \"failed to send message \" + i + \" of \" + messageCount + \" cause=\" + ex.getMessage();\n                throw new ClusterException(msg, ex);\n            }\n        }\n    }\n\n    public void sendErrorGeneratingMessages(final int messageCount)\n    {\n        final int messageLength = msgBuffer.putStringWithoutLengthAscii(0, ClusterTests.ERROR_MSG);\n\n        for (int i = 0; i < messageCount; i++)\n        {\n            try\n            {\n                pollUntilMessageSent(messageLength);\n            }\n            catch (final Exception ex)\n            {\n                final String msg = \"failed to send message \" + i + \" of \" + messageCount + \" cause=\" + ex.getMessage();\n                throw new ClusterException(msg, ex);\n            }\n        }\n    }\n\n    public void sendTerminateMessage()\n    {\n        final int messageLength = msgBuffer.putStringWithoutLengthAscii(0, ClusterTests.TERMINATE_MSG);\n\n        try\n        {\n            pollUntilMessageSent(messageLength);\n        }\n        catch (final Exception ex)\n        {\n            throw new ClusterException(\"failed to send message cause=\" + ex.getMessage(), ex);\n        }\n    }\n\n    public int sendAndAwaitMessages(final int messageCount)\n    {\n        return sendAndAwaitMessages(messageCount, messageCount);\n    }\n\n    public int sendAndAwaitMessages(final int messageCount, final int awaitCount)\n    {\n        sendMessages(messageCount);\n        awaitResponseMessageCount(awaitCount);\n        awaitServicesMessageCount(awaitCount);\n        return messageCount;\n    }\n\n    public void pollUntilMessageSent(final int messageLength)\n    {\n        while (true)\n        {\n            requireNonNull(client, \"Client is not connected\").pollEgress();\n\n            final long position = client.offer(msgBuffer, 0, messageLength);\n            if (position > 0)\n            {\n                return;\n            }\n\n            if (Publication.ADMIN_ACTION == position)\n            {\n                continue;\n            }\n\n            if (Publication.MAX_POSITION_EXCEEDED == position)\n            {\n                throw new ClusterException(\"max position exceeded\");\n            }\n\n            await(1);\n        }\n    }\n\n    public void pollUntilMessageSent(\n        final Publication pub,\n        final DirectBuffer buffer,\n        final int offset,\n        final int messageLength)\n    {\n        while (true)\n        {\n            final long position = pub.offer(buffer, offset, messageLength);\n            if (position > 0)\n            {\n                return;\n            }\n\n            if (Publication.ADMIN_ACTION == position)\n            {\n                continue;\n            }\n\n            if (Publication.MAX_POSITION_EXCEEDED == position)\n            {\n                throw new ClusterException(\"max position exceeded\");\n            }\n\n            await(1);\n        }\n    }\n\n    public void awaitResponseMessageCount(final int messageCount)\n    {\n        clientKeepAlive.init();\n        final Supplier<String> msg =\n            () -> \"expected=\" + messageCount + \" responseCount=\" + defaultEgressListener.responseCount();\n\n        while (defaultEgressListener.responseCount() < messageCount)\n        {\n            if (0 == pollClient())\n            {\n                Tests.yieldingIdle(msg);\n            }\n\n            try\n            {\n                clientKeepAlive.run();\n            }\n            catch (final ClusterException ex)\n            {\n                final String message = \"count=\" + defaultEgressListener.responseCount() + \" awaiting=\" + messageCount +\n                    \" cause=\" + ex.getMessage();\n                throw new RuntimeException(message, ex);\n            }\n        }\n    }\n\n    public void awaitNewLeadershipEvent(final int count)\n    {\n        while (defaultEgressListener.newLeaderEvent() < count || !client.ingressPublication().isConnected())\n        {\n            await(1);\n            pollClient();\n        }\n    }\n\n    public void awaitLossOfLeadership(final TestNode.TestService leaderService)\n    {\n        if (null != client)\n        {\n            clientKeepAlive.init();\n        }\n\n        while (leaderService.roleChangedTo() != FOLLOWER)\n        {\n            Tests.sleep(100);\n            if (null != client)\n            {\n                clientKeepAlive.run();\n            }\n        }\n    }\n\n    private int pollClient()\n    {\n        invokeSharedAgentInvoker();\n\n        final AgentInvoker conductorAgentInvoker = client.context().aeron().conductorAgentInvoker();\n        if (null != conductorAgentInvoker)\n        {\n            conductorAgentInvoker.invoke();\n        }\n\n        return client.pollEgress();\n    }\n\n    private void invokeSharedAgentInvoker()\n    {\n        if (clientMediaDriver instanceof JavaTestMediaDriver &&\n            ThreadingMode.INVOKER == clientMediaDriver.context().threadingMode())\n        {\n            clientMediaDriver.sharedAgentInvoker().invoke();\n        }\n    }\n\n    public void awaitCommitPosition(final TestNode node, final long logPosition)\n    {\n        while (node.commitPosition() < logPosition)\n        {\n            Tests.yield();\n        }\n    }\n\n    public void awaitActiveSessionCount(final TestNode node, final int count)\n    {\n        final Supplier<String> message =\n            () -> \"node \" + node + \" fail to reach active session count, expected=\" + count + \", current=\" +\n            node.service().activeSessionCount();\n\n        while (node.service().activeSessionCount() != count)\n        {\n            await(1, message);\n        }\n    }\n\n    public void awaitActiveSessionCount(final int count)\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node && !node.isClosed())\n            {\n                awaitActiveSessionCount(node, count);\n            }\n        }\n    }\n\n    public TestNode findLeader(final int skipIndex)\n    {\n        for (int i = 0; i < nodes.length; i++)\n        {\n            final TestNode node = nodes[i];\n            if (i == skipIndex || null == node || node.isClosed())\n            {\n                continue;\n            }\n\n            if (node.isLeader() && ElectionState.CLOSED == node.electionState())\n            {\n                return node;\n            }\n        }\n\n        return null;\n    }\n\n    public TestNode findLeader()\n    {\n        return findLeader(NULL_VALUE);\n    }\n\n    public TestNode awaitLeaderWithoutElectionTerminationCheck(final int skipIndex)\n    {\n        final Supplier<String> message = () -> Arrays.stream(nodes)\n            .map((node) -> null != node ? node.index() + \" \" + node.role() + \" \" + node.electionState() : \"null\")\n            .collect(Collectors.joining(\", \"));\n\n        TestNode leaderNode;\n        while (null == (leaderNode = findLeader(skipIndex)))\n        {\n            await(10, message);\n        }\n\n        return leaderNode;\n    }\n\n    public TestNode awaitLeader(final int skipIndex)\n    {\n        final TestNode leaderNode = awaitLeaderWithoutElectionTerminationCheck(skipIndex);\n\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                awaitElectionClosed(node);\n            }\n        }\n\n        return leaderNode;\n    }\n\n    public TestNode awaitLeader()\n    {\n        return awaitLeader(NULL_VALUE);\n    }\n\n    public List<TestNode> followers()\n    {\n        return followers(0);\n    }\n\n    public ArrayList<TestNode> followers(final int expectedMinimumFollowerCount)\n    {\n        final ArrayList<TestNode> followers = new ArrayList<>();\n        final EnumMap<Cluster.Role, ArrayList<TestNode>> nonFollowers = new EnumMap<>(Cluster.Role.class);\n\n        for (final TestNode node : nodes)\n        {\n            if (null != node && !node.isClosed())\n            {\n                while (ElectionState.CLOSED != node.electionState())\n                {\n                    Tests.yield();\n                }\n\n                final Cluster.Role role = node.role();\n                if (role == Cluster.Role.FOLLOWER)\n                {\n                    followers.add(node);\n                }\n                else\n                {\n                    nonFollowers.computeIfAbsent(role, (r) -> new ArrayList<>()).add(node);\n                }\n            }\n        }\n\n        if (followers.size() < expectedMinimumFollowerCount)\n        {\n            throw new RuntimeException(\n                \"expectedMinimumFollowerCount=\" + expectedMinimumFollowerCount +\n                \" < followers.size=\" + followers.size() +\n                \" nonFollowers=\" + nonFollowers);\n        }\n\n        return followers;\n    }\n\n    public void awaitBackupState(final ClusterBackup.State targetState)\n    {\n        if (null == backupNode)\n        {\n            throw new IllegalStateException(\"no backup node present\");\n        }\n\n        final Supplier<String> message =\n            () -> \"expectedState=\" + targetState + \" actualState=\" + backupNode.backupState();\n        while (true)\n        {\n            final ClusterBackup.State state = backupNode.backupState();\n            if (targetState == state)\n            {\n                break;\n            }\n\n            if (ClusterBackup.State.CLOSED == state)\n            {\n                throw new IllegalStateException(\"backup is closed\");\n            }\n\n            Tests.sleep(10, message);\n        }\n    }\n\n    public void awaitBackupLiveLogPosition(final long position)\n    {\n        if (null == backupNode)\n        {\n            throw new IllegalStateException(\"no backup node present\");\n        }\n\n        while (true)\n        {\n            final long livePosition = backupNode.liveLogPosition();\n            if (livePosition >= position)\n            {\n                break;\n            }\n\n            if (NULL_POSITION == livePosition)\n            {\n                throw new ClusterException(\"backup live log position is closed\");\n            }\n\n            Tests.sleep(10, \"awaiting position=%d livePosition=%d\", position, livePosition);\n        }\n    }\n\n    public void awaitBackupSnapshotRetrievedCount(final long snapshotCount)\n    {\n        if (null == backupNode)\n        {\n            throw new IllegalStateException(\"no backup node present\");\n        }\n\n        @SuppressWarnings(\"indentation\")\n        final Supplier<String> msg =\n            () -> \"Snapshot retrieve count not found expected=\" + snapshotCount +\n                \" actual=\" + backupNode.snapshotRetrieveCount();\n\n        while (backupNode.snapshotRetrieveCount() < snapshotCount)\n        {\n            Tests.yieldingIdle(msg);\n        }\n    }\n\n    public TestNode node(final int index)\n    {\n        return nodes[index];\n    }\n\n    public void takeSnapshot(final TestNode leaderNode)\n    {\n        final AtomicCounter controlToggle = getClusterControlToggle(leaderNode);\n        assertTrue(ClusterControl.ToggleState.SNAPSHOT.toggle(controlToggle));\n    }\n\n    public void takeStandbySnapshot(final TestNode leaderNode)\n    {\n        final AtomicCounter controlToggle = getClusterControlToggle(leaderNode);\n        assertTrue(ClusterControl.ToggleState.STANDBY_SNAPSHOT.toggle(controlToggle));\n    }\n\n    public void shutdownCluster(final TestNode leaderNode)\n    {\n        final AtomicCounter controlToggle = getClusterControlToggle(leaderNode);\n        assertTrue(ClusterControl.ToggleState.SHUTDOWN.toggle(controlToggle));\n    }\n\n    public void abortCluster(final TestNode leaderNode)\n    {\n        final AtomicCounter controlToggle = getClusterControlToggle(leaderNode);\n        assertTrue(ClusterControl.ToggleState.ABORT.toggle(controlToggle));\n    }\n\n    public void suspendCluster(final TestNode leaderNode)\n    {\n        final AtomicCounter controlToggle = getClusterControlToggle(leaderNode);\n        assertTrue(ClusterControl.ToggleState.SUSPEND.toggle(controlToggle));\n    }\n\n    public void resumeCluster(final TestNode leaderNode)\n    {\n        final AtomicCounter controlToggle = getClusterControlToggle(leaderNode);\n        assertTrue(ClusterControl.ToggleState.RESUME.toggle(controlToggle));\n    }\n\n    public void awaitSnapshotCount(final long value)\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node && !node.isClosed())\n            {\n                awaitSnapshotCount(node, value);\n            }\n        }\n    }\n\n    public void awaitSnapshotCount(final TestNode node, final long value)\n    {\n        clientKeepAlive.init();\n        awaitCounter(node, value, node.consensusModule().context().snapshotCounter(), clientKeepAlive);\n    }\n\n    public long getSnapshotCount(final TestNode node)\n    {\n        final Counter snapshotCounter = node.consensusModule().context().snapshotCounter();\n\n        if (snapshotCounter.isClosed())\n        {\n            throw new IllegalStateException(\"counter is unexpectedly closed\");\n        }\n\n        return snapshotCounter.get();\n    }\n\n    public void awaitStandbySnapshotCount(final long value)\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node && !node.isClosed())\n            {\n                awaitStandbySnapshotCount(node, value);\n            }\n        }\n    }\n\n    public void awaitStandbySnapshotCount(final TestNode node, final long value)\n    {\n        clientKeepAlive.init();\n        awaitCounter(node,\n            value,\n            requireNonNull(\n            node.consensusModule().context().standbySnapshotCounter(), \"node not configured for standby snapshots\"),\n            clientKeepAlive);\n    }\n\n    public long getStandbySnapshotCount(final TestNode node)\n    {\n        final Counter snapshotCounter = requireNonNull(\n            node.consensusModule().context().standbySnapshotCounter(), \"node not configured for standby snapshots\");\n\n        if (snapshotCounter.isClosed())\n        {\n            throw new IllegalStateException(\"counter is unexpectedly closed\");\n        }\n\n        return snapshotCounter.get();\n    }\n\n    private static void awaitCounter(\n        final TestNode node, final long value, final Counter snapshotCounter, final Runnable keepAlive)\n    {\n        final Supplier<String> msg = () -> \"node=\" + node.index() +\n            \" role=\" + node.role() +\n            \" expected=\" + value +\n            \" snapshotCount=\" + snapshotCounter.get();\n\n        while (true)\n        {\n            if (snapshotCounter.isClosed())\n            {\n                throw new IllegalStateException(\"counter is unexpectedly closed\");\n            }\n\n            if (snapshotCounter.get() >= value)\n            {\n                break;\n            }\n\n            Tests.yieldingIdle(msg);\n\n            keepAlive.run();\n        }\n    }\n\n    public long logPosition()\n    {\n        return findLeader().commitPosition();\n    }\n\n    public void awaitNodeTermination(final TestNode node)\n    {\n        final Supplier<String> msg =\n            () -> \"Failed to see node=\" + node.index() + \" terminate, \" +\n            \"hasMemberTerminated=\" + node.hasMemberTerminated() +\n            \", hasServiceTerminated=\" + node.hasServiceTerminated();\n\n        while (!node.hasMemberTerminated() || !node.hasServiceTerminated())\n        {\n            Tests.yieldingIdle(msg);\n        }\n    }\n\n    public void awaitNodeTerminations()\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                awaitNodeTermination(node);\n            }\n        }\n    }\n\n    public void awaitServicesMessageCount(final int messageCount)\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node && !node.isClosed())\n            {\n                awaitServiceMessageCount(node, messageCount);\n            }\n        }\n    }\n\n    public void awaitTimerEventCount(final int expectedTimerEventsCount)\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node && !node.isClosed())\n            {\n                awaitTimerEventCount(node, expectedTimerEventsCount);\n            }\n        }\n    }\n\n    public void terminationsExpected(final boolean isExpected)\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                node.isTerminationExpected(isExpected);\n            }\n        }\n    }\n\n    public void awaitServiceMessageCount(final TestNode node, final int messageCount)\n    {\n        final TestNode.TestService[] services = node.services();\n        awaitServiceMessageCount(node, services, messageCount);\n    }\n\n    public void awaitServiceMessagePredicate(final TestNode node, final IntPredicate countPredicate)\n    {\n        final TestNode.TestService service = node.service();\n        awaitServiceMessagePredicate(node, service, countPredicate);\n    }\n\n    private final KeepAlive clientKeepAlive = new KeepAlive();\n\n    public void awaitServiceMessageCount(\n        final TestNode node, final TestNode.TestService service, final int messageCount)\n    {\n        clientKeepAlive.init();\n        service.awaitServiceMessageCount(messageCount, clientKeepAlive, node);\n    }\n\n    public void awaitServiceMessageCount(\n        final TestNode node, final TestNode.TestService[] services, final int messageCount)\n    {\n        clientKeepAlive.init();\n        for (final TestNode.TestService service : services)\n        {\n            service.awaitServiceMessageCount(messageCount, clientKeepAlive, node);\n        }\n    }\n\n    public void awaitServiceMessagePredicate(\n        final TestNode node, final TestNode.TestService service, final IntPredicate countPredicate)\n    {\n        service.awaitServiceMessagePredicate(countPredicate, clientKeepAlive, node);\n    }\n\n    public void awaitLiveAndSnapshotMessageCount(\n        final TestNode node,\n        final TestNode.TestService service,\n        final IntPredicate liveCountPredicate,\n        final IntPredicate snapshotCountPredicate)\n    {\n        clientKeepAlive.init();\n        service.awaitLiveAndSnapshotMessageCount(liveCountPredicate, snapshotCountPredicate, clientKeepAlive, node);\n    }\n\n    public void awaitLiveAndSnapshotMessageCount(\n        final TestNode node, final IntPredicate liveCountPredicate, final IntPredicate snapshotCountPredicate)\n    {\n        final TestNode.TestService service = node.service();\n        awaitLiveAndSnapshotMessageCount(node, service, liveCountPredicate, snapshotCountPredicate);\n    }\n\n    public void awaitTimerEventCount(final TestNode node, final int expectedTimerEventsCount)\n    {\n        final TestNode.TestService service = node.service();\n        awaitTimerEventCount(node, service, expectedTimerEventsCount);\n    }\n\n    public void awaitTimerEventCount(\n        final TestNode node, final TestNode.TestService service, final int expectedTimerEventsCount)\n    {\n        clientKeepAlive.init();\n        long count;\n\n        while ((count = service.timerCount()) < expectedTimerEventsCount)\n        {\n            Thread.yield();\n            if (Thread.interrupted())\n            {\n                throw new TimeoutException(\"await timer events: count=\" + count + \" awaiting=\" +\n                    expectedTimerEventsCount + \" node=\" + node);\n            }\n\n            if (service.hasReceivedUnexpectedMessage())\n            {\n                fail(\"service received unexpected message\");\n            }\n\n            clientKeepAlive.run();\n        }\n    }\n\n    public void awaitNodeState(final TestNode node, final Predicate<TestNode> predicate)\n    {\n        clientKeepAlive.init();\n\n        while (!predicate.test(node))\n        {\n            Thread.yield();\n            if (Thread.interrupted())\n            {\n                throw new TimeoutException(\"timeout while awaiting node state\");\n            }\n\n            clientKeepAlive.run();\n        }\n    }\n\n    public void awaitSnapshotsLoaded()\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                awaitSnapshotLoadedForService(node);\n            }\n        }\n    }\n\n    public void awaitSnapshotLoadedForService(final TestNode node)\n    {\n        while (!node.allSnapshotsLoaded())\n        {\n            Tests.yield();\n        }\n    }\n\n    public void awaitNeutralControlToggle(final TestNode leaderNode)\n    {\n        final AtomicCounter controlToggle = getClusterControlToggle(leaderNode);\n        while (controlToggle.get() != ClusterControl.ToggleState.NEUTRAL.code())\n        {\n            Tests.yield();\n        }\n    }\n\n    public AtomicCounter getClusterControlToggle(final TestNode leaderNode)\n    {\n        final CountersReader counters = leaderNode.countersReader();\n        final int clusterId = leaderNode.consensusModule().context().clusterId();\n        final AtomicCounter controlToggle = ClusterControl.findControlToggle(counters, clusterId);\n        assertNotNull(controlToggle);\n\n        return controlToggle;\n    }\n\n    public void awaitNeutralNodeControlToggle()\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                awaitNeutralNodeControlToggle(node);\n            }\n        }\n    }\n\n    public void awaitNeutralNodeControlToggle(final TestNode node)\n    {\n        final AtomicCounter controlToggle = getNodeControlToggle(node);\n        while (controlToggle.get() != NodeControl.ToggleState.NEUTRAL.code())\n        {\n            Tests.yield();\n        }\n    }\n\n    public AtomicCounter getNodeControlToggle(final TestNode node)\n    {\n        final CountersReader counters = node.countersReader();\n        final int clusterId = node.consensusModule().context().clusterId();\n        final AtomicCounter controlToggle = NodeControl.findControlToggle(counters, clusterId);\n        assertNotNull(controlToggle);\n\n        return controlToggle;\n    }\n\n    public static String clusterMembers(\n        final int clusterId,\n        final int beginIndex,\n        final int endIndex,\n        final int memberCount,\n        final boolean useResponseChannels)\n    {\n        final StringBuilder builder = new StringBuilder();\n\n        for (int i = beginIndex; i < endIndex; i++)\n        {\n            appendClusterMember(builder, clusterId, i, memberCount, useResponseChannels);\n            builder.append('|');\n        }\n\n        builder.setLength(builder.length() - 1);\n\n        return builder.toString();\n    }\n\n    private static void appendClusterMember(\n        final StringBuilder builder,\n        final int clusterId,\n        final int memberId,\n        final int memberCount,\n        final boolean useResponseChannels)\n    {\n        final String hostname = hostname(memberId, memberCount);\n        builder\n            .append(memberId).append(',')\n            .append(hostname).append(\":2\").append(clusterId).append(\"11\").append(memberId).append(',')\n            .append(hostname).append(\":2\").append(clusterId).append(\"22\").append(memberId).append(',')\n            .append(hostname).append(\":2\").append(clusterId).append(\"33\").append(memberId).append(',')\n            .append(hostname).append(\":0,\")\n            .append(hostname).append(\":801\").append(memberId);\n\n        if (useResponseChannels)\n        {\n            builder.append(',').append(hostname).append(\":2\").append(clusterId).append(\"44\").append(memberId);\n        }\n    }\n\n    public String singleNodeClusterMember(final int memberId)\n    {\n        final StringBuilder builder = new StringBuilder();\n        appendClusterMember(builder, clusterId, memberId, memberCount, false);\n        return builder.toString();\n    }\n\n    public static String ingressEndpoints(\n        final int clusterId, final int beginIndex, final int endIndex, final int memberCount)\n    {\n        final StringBuilder builder = new StringBuilder();\n\n        for (int i = beginIndex; i < endIndex; i++)\n        {\n            builder.append(i)\n                .append('=')\n                .append(ingressEndpoint(clusterId, i, memberCount))\n                .append(',');\n        }\n\n        builder.setLength(builder.length() - 1);\n\n        return builder.toString();\n    }\n\n    public static String ingressEndpoint(final int clusterId, final int memberId, final int memberCount)\n    {\n        return hostname(memberId, memberCount) + \":2\" + clusterId + \"11\" + memberId;\n    }\n\n    public void purgeLogToLastSnapshot()\n    {\n        for (final TestNode testNode : nodes)\n        {\n            purgeLogToLastSnapshot(testNode);\n        }\n    }\n\n    public void purgeLogToLastSnapshot(final TestNode node)\n    {\n        if (null == node || node.isClosed())\n        {\n            return;\n        }\n\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n        final RecordingLog recordingLog = new RecordingLog(node.consensusModule().context().clusterDir(), false);\n        final RecordingLog.Entry latestSnapshot = requireNonNull(recordingLog.getLatestSnapshot(SERVICE_ID));\n        final long recordingId = recordingLog.findLastTermRecordingId();\n        if (RecordingPos.NULL_RECORDING_ID == recordingId)\n        {\n            throw new RuntimeException(\"Unable to find log recording\");\n        }\n\n        try (Aeron aeron = Aeron.connect(\n            new Aeron.Context().aeronDirectoryName(node.mediaDriver().aeronDirectoryName())))\n        {\n            final MutableBoolean segmentsDeleted = new MutableBoolean(false);\n            final RecordingSignalConsumer deleteSignalConsumer =\n                (controlSessionId, correlationId, recordingId1, subscriptionId, position, signal) ->\n                {\n                    if (RecordingSignal.DELETE == signal)\n                    {\n                        segmentsDeleted.set(true);\n                    }\n                };\n\n            final AeronArchive.Context aeronArchiveCtx = node\n                .consensusModule()\n                .context()\n                .archiveContext()\n                .clone();\n\n            aeronArchiveCtx\n                .recordingSignalConsumer(deleteSignalConsumer)\n                .aeron(aeron)\n                .ownsAeronClient(false)\n                .controlResponseStreamId(10000 + node.index())\n                .controlResponseChannel(new ChannelUriStringBuilder(aeronArchiveCtx.controlResponseChannel())\n                    .alias(\"purge-log-node-\" + node.index())\n                    .build());\n\n            try (AeronArchive aeronArchive = AeronArchive.connect(aeronArchiveCtx))\n            {\n                aeronArchive.listRecording(recordingId, collector.reset());\n                final RecordingDescriptor recordingDescriptor = collector.descriptors().get(0);\n\n                final long newStartPosition = AeronArchive.segmentFileBasePosition(\n                    recordingDescriptor.startPosition(),\n                    latestSnapshot.logPosition,\n                    recordingDescriptor.termBufferLength(),\n                    recordingDescriptor.segmentFileLength());\n                final long segmentsDeleteCount = aeronArchive.purgeSegments(recordingId, newStartPosition);\n\n                while (0 < segmentsDeleteCount && !segmentsDeleted.get())\n                {\n                    aeronArchive.pollForRecordingSignals();\n                }\n            }\n        }\n    }\n\n    /**\n     * Purge segments from the cluster log. Used by Cluster Standby tests.\n     *\n     * @param numSegments to purge from the log.\n     */\n    @SuppressWarnings(\"unused\")\n    public void purgeSegmentsFromLog(final long numSegments)\n    {\n        for (final TestNode testNode : nodes)\n        {\n            purgeSegmentsFromLog(numSegments, testNode);\n        }\n    }\n\n    public void purgeSegmentsFromLog(final long numSegments, final TestNode node)\n    {\n        if (null == node || node.isClosed())\n        {\n            return;\n        }\n\n        final RecordingDescriptorCollector collector = new RecordingDescriptorCollector(10);\n        final RecordingLog recordingLog = new RecordingLog(node.consensusModule().context().clusterDir(), false);\n        final long recordingId = recordingLog.findLastTermRecordingId();\n        if (RecordingPos.NULL_RECORDING_ID == recordingId)\n        {\n            throw new RuntimeException(\"Unable to find log recording\");\n        }\n\n        try (Aeron aeron = Aeron.connect(\n            new Aeron.Context().aeronDirectoryName(node.mediaDriver().aeronDirectoryName())))\n        {\n            final MutableBoolean segmentsDeleted = new MutableBoolean(false);\n            final RecordingSignalConsumer deleteSignalConsumer =\n                (controlSessionId, correlationId, recordingId1, subscriptionId, position, signal) ->\n                {\n                    if (RecordingSignal.DELETE == signal)\n                    {\n                        segmentsDeleted.set(true);\n                    }\n                };\n\n            final AeronArchive.Context aeronArchiveCtx = node\n                .consensusModule()\n                .context()\n                .archiveContext()\n                .clone();\n\n            aeronArchiveCtx\n                .recordingSignalConsumer(deleteSignalConsumer)\n                .aeron(aeron)\n                .ownsAeronClient(false)\n                .controlResponseStreamId(10000 + node.index())\n                .controlResponseChannel(new ChannelUriStringBuilder(aeronArchiveCtx.controlResponseChannel())\n                    .alias(\"purge-log-node-\" + node.index())\n                    .build());\n\n            try (AeronArchive aeronArchive = AeronArchive.connect(aeronArchiveCtx))\n            {\n                aeronArchive.listRecording(recordingId, collector.reset());\n                final RecordingDescriptor recordingDescriptor = collector.descriptors().get(0);\n\n                final long newStartPosition = AeronArchive.segmentFileBasePosition(\n                    recordingDescriptor.startPosition(),\n                    recordingDescriptor.startPosition() + (numSegments * recordingDescriptor.segmentFileLength()),\n                    recordingDescriptor.termBufferLength(),\n                    recordingDescriptor.segmentFileLength());\n                final long segmentsDeleteCount = aeronArchive.purgeSegments(recordingId, newStartPosition);\n\n                while (0 < segmentsDeleteCount && !segmentsDeleted.get())\n                {\n                    aeronArchive.pollForRecordingSignals();\n                }\n            }\n        }\n    }\n\n    String clusterConsensusEndpoints(final int beginIndex, final int endIndex)\n    {\n        final StringBuilder builder = new StringBuilder();\n\n        for (int i = beginIndex; i < endIndex; i++)\n        {\n            builder.append(hostname(i, memberCount))\n                .append(\":2\").append(clusterId).append(\"22\").append(i).append(',');\n        }\n\n        builder.setLength(builder.length() - 1);\n\n        return builder.toString();\n    }\n\n    private String[] senderWildcardPortRanges()\n    {\n        final String[] ranges = new String[memberCount + 1];\n\n        for (int i = 0; i <= memberCount; i++)\n        {\n            ranges[i] = \"2\" + clusterId + \"5\" + i + \"0 \" + \"2\" + clusterId + \"5\" + i + \"9\";\n        }\n\n        return ranges;\n    }\n\n    private String[] receiverWildcardPortRanges()\n    {\n        final String[] ranges = new String[memberCount + 1];\n\n        for (int i = 0; i <= memberCount; i++)\n        {\n            ranges[i] = \"2\" + clusterId + \"6\" + i + \"0 \" + \"2\" + clusterId + \"6\" + i + \"9\";\n        }\n\n        return ranges;\n    }\n\n    static String hostname(final int memberId, final int memberCount)\n    {\n        return memberId < memberCount ? \"node\" + memberId : \"localhost\";\n    }\n\n    String clusterBackupStatusEndpoint(final int memberId)\n    {\n        return hostname(memberId, memberCount) + \":2\" + clusterId + \"22\" + memberId;\n    }\n\n    String archiveControlRequestChannel(final int memberId)\n    {\n        return \"aeron:udp?endpoint=\" + archiveControlRequestEndpoint(memberId);\n    }\n\n    String archiveControlRequestEndpoint(final int memberId)\n    {\n        return hostname(memberId, memberCount) + \":801\" + memberId;\n    }\n\n    String archiveControlResponseChannel(final int memberId)\n    {\n        return \"aeron:udp?endpoint=\" + archiveControlResponseEndpoint(memberId);\n    }\n\n    String archiveControlResponseEndpoint(final int memberId)\n    {\n        return hostname(memberId, memberCount) + \":0\";\n    }\n\n    String archiveReplicationChannel(final int memberId)\n    {\n        return \"aeron:udp?endpoint=\" + archiveReplicationEndpoint(memberId);\n    }\n\n    String archiveReplicationEndpoint(final int memberId)\n    {\n        return hostname(memberId, memberCount) + \":802\" + memberId;\n    }\n\n    String clusterReplicationChannel(final int memberId)\n    {\n        return \"aeron:udp?endpoint=\" + clusterReplicationEndpoint(memberId) + \"|linger=5000000000\";\n    }\n\n    String clusterReplicationEndpoint(final int memberId)\n    {\n        return hostname(memberId, memberCount) + \":2\" + clusterId + \"55\" + memberId;\n    }\n\n    public void invalidateLatestSnapshot()\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                try (RecordingLog recordingLog = new RecordingLog(node.consensusModule().context().clusterDir(), false))\n                {\n                    assertTrue(recordingLog.invalidateLatestSnapshot());\n                }\n            }\n        }\n    }\n\n    public void disableNameResolution(final String hostname)\n    {\n        toggleNameResolution(hostname, RedirectingNameResolver.DISABLE_RESOLUTION);\n    }\n\n    public void enableNameResolution(final String hostname)\n    {\n        toggleNameResolution(hostname, RedirectingNameResolver.USE_INITIAL_RESOLUTION_HOST);\n    }\n\n    public void restoreNameResolution(final int nodeId)\n    {\n        byHostInvalidInitialResolutions.remove(nodeId);\n        toggleNameResolution(hostname(nodeId, memberCount), RedirectingNameResolver.USE_RE_RESOLUTION_HOST);\n    }\n\n    public void restoreByMemberNameResolution(final int memberId)\n    {\n        byMemberInvalidInitialResolutions.remove(memberId);\n        final TestMediaDriver mediaDriver = null != backupNode && backupNode.index() == memberId ?\n            backupNode.mediaDriver() : node(memberId).mediaDriver();\n        final CountersReader counters = mediaDriver.counters();\n\n        for (int i = 0; i < memberCount; i++)\n        {\n            RedirectingNameResolver.updateNameResolutionStatus(\n                counters, hostname(i, memberCount), RedirectingNameResolver.USE_RE_RESOLUTION_HOST);\n        }\n    }\n\n    private void toggleNameResolution(final String hostname, final int disableValue)\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                final CountersReader counters = node.mediaDriver().counters();\n                RedirectingNameResolver.updateNameResolutionStatus(counters, hostname, disableValue);\n            }\n        }\n    }\n\n    private String nodeNameMappings()\n    {\n        final List<String> initialAddresses =\n            initialAddresses(memberCount, byHostInvalidInitialResolutions, this::defaultHostname);\n        return nodeNameMappings(initialAddresses, this::defaultHostname);\n    }\n\n    private String nodeNameMappings(final int memberId, final int memberCount)\n    {\n        final List<String> initialAddresses = initialAddresses(\n            memberCount,\n            byHostInvalidInitialResolutions,\n            byMemberInvalidInitialResolutions,\n            memberId,\n            this::defaultHostname);\n        return nodeNameMappings(initialAddresses, this::defaultHostname);\n    }\n\n    private List<String> initialAddresses(\n        final int memberCount,\n        final IntHashSet invalidInitialResolutions,\n        final IntFunction<String> defaultAddresses)\n    {\n        return IntStream.range(0, memberCount)\n            .mapToObj(i -> invalidInitialResolutions.contains(i) ? \"bad.invalid\" : defaultAddresses.apply(i))\n            .toList();\n    }\n\n    private static List<String> initialAddresses(\n        final int memberCount,\n        final IntHashSet byHostInvalidInitialResolutions,\n        final IntHashSet byMemberInvalidInitialResolutions,\n        final int memberId,\n        final IntFunction<String> defaultAddresses)\n    {\n        final boolean memberInvalid = byMemberInvalidInitialResolutions.contains(memberId);\n        return IntStream.range(0, memberCount)\n            .mapToObj(i -> memberInvalid || byHostInvalidInitialResolutions.contains(i) ?\n                \"bad.invalid\" : defaultAddresses.apply(i))\n            .toList();\n    }\n\n    private String nodeNameMappings(\n        final List<String> initialHostnames,\n        final IntFunction<String> reresolveHostnames)\n    {\n        if (initialHostnames.size() < memberCount)\n        {\n            throw new IllegalStateException(\"Number of host names (\" + initialHostnames.size() +\n                \") does not match cluster size: \" + memberCount);\n        }\n\n        return IntStream.range(0, initialHostnames.size())\n            .mapToObj(i -> \"node\" + i + \",\" + initialHostnames.get(i) + \",\" + reresolveHostnames.apply(i))\n            .collect(Collectors.joining(\"|\"));\n    }\n\n    public DataCollector dataCollector()\n    {\n        return dataCollector;\n    }\n\n    public void assertRecordingLogsEqual()\n    {\n        List<RecordingLog.Entry> firstEntries = null;\n        for (final TestNode node : nodes)\n        {\n            if (null == node)\n            {\n                continue;\n            }\n\n            try (RecordingLog recordingLog = new RecordingLog(node.consensusModule().context().clusterDir(), false))\n            {\n                if (null == firstEntries)\n                {\n                    firstEntries = recordingLog.entries();\n                }\n                else\n                {\n                    final List<RecordingLog.Entry> entries = recordingLog.entries();\n                    assertEquals(\n                        firstEntries.size(),\n                        entries.size(),\n                        \"length mismatch: \\n[0]\" + firstEntries + \" != \" + \"\\n[\" + node.index() + \"] \" + entries);\n\n                    for (int i = 0; i < firstEntries.size(); i++)\n                    {\n                        final RecordingLog.Entry a = firstEntries.get(i);\n                        final RecordingLog.Entry b = entries.get(i);\n\n                        final ReflectionEquals matcher = new ReflectionEquals(a, \"timestamp\");\n                        assertTrue(matcher.matches(b), \"Mismatch (\" + i + \"): \" + a + \" != \" + b);\n                    }\n                }\n            }\n        }\n    }\n\n    public void validateRecordingLogWithReplay(final int nodeId)\n    {\n        final TestNode node = node(nodeId);\n        final ConsensusModule.Context consensusModuleCtx = node.consensusModule().context();\n        final AeronArchive.Context clone = consensusModuleCtx.archiveContext().clone();\n        try (\n            AeronArchive aeronArchive = AeronArchive.connect(clone);\n            RecordingLog recordingLog = new RecordingLog(consensusModuleCtx.clusterDir(), false))\n        {\n            final RecordingLog.Entry lastTerm = recordingLog.findLastTerm();\n            assertNotNull(lastTerm);\n            final long recordingId = lastTerm.recordingId;\n\n            final long recordingPosition = aeronArchive.getRecordingPosition(recordingId);\n            final Subscription replay = aeronArchive.replay(\n                recordingId,\n                0,\n                recordingPosition,\n                \"aeron:udp?endpoint=localhost:6666\",\n                100001);\n\n            final MutableLong position = new MutableLong();\n            final MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();\n            final NewLeadershipTermEventDecoder newLeadershipTermEventDecoder = new NewLeadershipTermEventDecoder();\n            final FragmentHandler fragmentHandler =\n                (buffer, offset, length, header) ->\n                {\n                    messageHeaderDecoder.wrap(buffer, offset);\n\n                    if (NewLeadershipTermEventDecoder.TEMPLATE_ID == messageHeaderDecoder.templateId())\n                    {\n                        newLeadershipTermEventDecoder.wrapAndApplyHeader(buffer, offset, messageHeaderDecoder);\n\n                        final RecordingLog.Entry termEntry = recordingLog.findTermEntry(\n                            newLeadershipTermEventDecoder.leadershipTermId());\n\n                        assertNotNull(termEntry);\n                        assertEquals(\n                            newLeadershipTermEventDecoder.termBaseLogPosition(), termEntry.termBaseLogPosition);\n\n                        if (0 < newLeadershipTermEventDecoder.leadershipTermId())\n                        {\n                            final RecordingLog.Entry previousTermEntry = recordingLog.findTermEntry(\n                                newLeadershipTermEventDecoder.leadershipTermId() - 1);\n                            assertNotNull(previousTermEntry);\n                            assertEquals(\n                                newLeadershipTermEventDecoder.termBaseLogPosition(),\n                                previousTermEntry.logPosition,\n                                previousTermEntry.toString());\n                        }\n                    }\n\n                    position.set(header.position());\n                };\n\n            while (position.get() < recordingPosition)\n            {\n                if (0 == replay.poll(fragmentHandler, 10))\n                {\n                    Tests.yield();\n                }\n            }\n        }\n    }\n\n    public void seedRecordingsFromLatestSnapshot()\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                ClusterTool.seedRecordingLogFromSnapshot(node.consensusModule().context().clusterDir());\n            }\n        }\n    }\n\n    public static Builder aCluster()\n    {\n        return new Builder();\n    }\n\n    public void awaitBackupNodeErrors()\n    {\n        final TestBackupNode testBackupNode = requireNonNull(backupNode);\n        while (0 == testBackupNode.clusterBackupErrorCount())\n        {\n            Tests.sleep(1, \"No errors observed on backup node\");\n        }\n    }\n\n    private long countAllServiceErrors(final TestNode node)\n    {\n        final TestNode.TestService[] services = node.services();\n\n        long errorCount = 0;\n        for (final TestNode.TestService service : services)\n        {\n            errorCount += service.cluster().context().errorCounter().get();\n        }\n\n        return errorCount;\n    }\n\n    public void awaitServiceErrors(final TestNode node, final long count)\n    {\n        while (count < countAllServiceErrors(node))\n        {\n            Tests.sleep(1, \"Errors count=\" + count + \" not seen on node=\" + node.index());\n        }\n    }\n\n    public void awaitServiceErrors(final long count)\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                awaitServiceErrors(node, count);\n            }\n        }\n    }\n\n    public void failNextSnapshot(final boolean failNextSnapshot)\n    {\n        for (final TestNode node : nodes)\n        {\n            if (null != node)\n            {\n                node.service().failNextSnapshot(failNextSnapshot);\n            }\n        }\n    }\n\n    public void backupQueryContainsSnapshot(\n        final TestNode leader,\n        final long logPosition,\n        final SnapshotRecord snapshotRecord)\n    {\n        final BackupQueryRunner backupQueryRunner = backQueryRunners.computeIfAbsent(leader, BackupQueryRunner::new);\n        backupQueryRunner.run(logPosition, snapshotRecord);\n    }\n\n    public void waitForError(final TestNode follower, final Predicate<String> predicate)\n    {\n        final File consensusModuleDir = follower.consensusModule().context().clusterDir();\n        final ClusterMarkFile clusterMarkFile = new ClusterMarkFile(\n            consensusModuleDir, ClusterMarkFile.FILENAME, new SystemEpochClock(), 10_000, System.out::println);\n\n        final ArrayList<AtomicBuffer> errorBuffers = new ArrayList<>();\n        errorBuffers.add(clusterMarkFile.errorBuffer());\n\n        for (int i = 0, n = follower.services().length; i < n; i++)\n        {\n            final File serviceClusterDir = follower.container(i).context().clusterDir();\n            final ClusterMarkFile serviceMarkFile = new ClusterMarkFile(\n                serviceClusterDir, ClusterMarkFile.markFilenameForService(i), new SystemEpochClock(), 10_000,\n                System.out::println);\n\n            errorBuffers.add(serviceMarkFile.errorBuffer());\n        }\n\n        final ArrayList<String> errors = new ArrayList<>();\n\n        while (true)\n        {\n            errors.clear();\n\n            for (final AtomicBuffer errorBuffer : errorBuffers)\n            {\n                ErrorLogReader.read(errorBuffer, (_0, _1, _2, encodedException) -> errors.add(encodedException));\n            }\n\n            if (errors.stream().anyMatch(predicate))\n            {\n                break;\n            }\n\n            Tests.sleep(1);\n        }\n    }\n\n    private class BackupQueryRunner implements AutoCloseable\n    {\n        private final ExpandableArrayBuffer expandableArrayBuffer = new ExpandableArrayBuffer(128);\n        private final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder();\n        private final BackupQueryEncoder backupQueryEncoder = new BackupQueryEncoder();\n        private final MessageHeaderDecoder headerDecoder = new MessageHeaderDecoder();\n        private final BackupResponseDecoder backupResponseDecoder = new BackupResponseDecoder();\n\n        private final Subscription subscription;\n        private final Publication publication;\n        private final String responseChannel;\n        private final Aeron aeron;\n\n        BackupQueryRunner(final TestNode node)\n        {\n            final String channel = new ChannelUriStringBuilder()\n                .media(\"udp\")\n                .endpoint(clusterConsensusEndpoints(node.memberId(), node.memberId() + 1))\n                .termLength(64 * 1024)\n                .build();\n\n            final int streamId = ConsensusModule.Configuration.consensusStreamId();\n            final int responseStreamId = 29876;\n\n            aeron = client.context().aeron();\n            subscription = aeron.addSubscription(\"aeron:udp?endpoint=localhost:0\", responseStreamId);\n            publication = aeron.addPublication(channel, streamId);\n\n            List<String> socketAddresses;\n            do\n            {\n                socketAddresses = subscription.localSocketAddresses();\n                if (!socketAddresses.isEmpty())\n                {\n                    break;\n                }\n                Tests.yield();\n            }\n            while (true);\n\n            responseChannel = \"aeron:udp?endpoint=\" + socketAddresses.get(0);\n        }\n\n        void run(final long logPosition, final SnapshotRecord snapshotRecord)\n        {\n            final long correlationId = aeron.nextCorrelationId();\n\n            backupQueryEncoder.wrapAndApplyHeader(expandableArrayBuffer, 0, headerEncoder)\n                .correlationId(correlationId)\n                .responseStreamId(subscription.streamId())\n                .version(AeronCluster.Configuration.PROTOCOL_SEMANTIC_VERSION)\n                .logPosition(logPosition)\n                .responseChannel(responseChannel);\n\n            final int requestLength = headerEncoder.encodedLength() + backupQueryEncoder.encodedLength();\n            while (publication.offer(expandableArrayBuffer, 0, requestLength) < 0)\n            {\n                Tests.yield();\n            }\n\n            final MutableBoolean found = new MutableBoolean(false);\n            final MutableBoolean matches = new MutableBoolean(false);\n\n            final FragmentHandler handler = (buffer, offset, length, header) ->\n            {\n                if (length < MessageHeaderDecoder.ENCODED_LENGTH)\n                {\n                    return;\n                }\n\n                headerDecoder.wrap(buffer, offset);\n\n                if (headerDecoder.templateId() != backupResponseDecoder.sbeTemplateId())\n                {\n                    return;\n                }\n\n                backupResponseDecoder.wrapAndApplyHeader(buffer, offset, headerDecoder);\n\n                if (correlationId != backupResponseDecoder.correlationId())\n                {\n                    return;\n                }\n\n                found.set(true);\n\n                final BackupResponseDecoder.SnapshotsDecoder snapshots = backupResponseDecoder.snapshots();\n                boolean hasMatch = (0 < snapshots.count());\n                while (snapshots.hasNext())\n                {\n                    final BackupResponseDecoder.SnapshotsDecoder next = snapshots.next();\n                    hasMatch &= next.logPosition() == snapshotRecord.logPosition();\n                }\n\n                matches.set(hasMatch);\n            };\n\n            while (!found.get())\n            {\n                final int fragments = subscription.poll(handler, 10);\n                if (0 == fragments)\n                {\n                    Tests.yield();\n                }\n            }\n\n            assertTrue(matches.get());\n        }\n\n        public void close()\n        {\n            CloseHelper.quietCloseAll(subscription, publication);\n        }\n    }\n\n    public record SnapshotRecord(long recordingId, long logPosition)\n    {\n    }\n\n    public List<SnapshotRecord> snapshots(final TestNode testNode)\n    {\n        final File file = testNode.consensusModule().context().clusterDir();\n        try (RecordingLog recordingLog = new RecordingLog(file, false))\n        {\n            return recordingLog.entries().stream()\n                .filter((entry) -> RecordingLog.ENTRY_TYPE_SNAPSHOT == entry.type && SERVICE_ID == entry.serviceId)\n                .map(entry -> new SnapshotRecord(entry.recordingId, entry.logPosition))\n                .sorted(Comparator.comparingLong(SnapshotRecord::logPosition))\n                .toList();\n        }\n    }\n\n    public static final class Builder\n    {\n        private int clusterId;\n        private int nodeCount = 3;\n        private int appointedLeaderId = NULL_VALUE;\n        private String logChannel = LOG_CHANNEL;\n        private String ingressChannel = INGRESS_CHANNEL;\n        private String egressChannel = EGRESS_CHANNEL;\n        private AuthorisationServiceSupplier authorisationServiceSupplier;\n        private AuthenticatorSupplier authenticationSupplier = new DefaultAuthenticatorSupplier();\n        private TimerServiceSupplier timerServiceSupplier;\n        private IntFunction<TestNode.TestService[]> serviceSupplier =\n            (i) -> new TestNode.TestService[]{ new TestNode.TestService().index(i) };\n        private final IntHashSet byHostInvalidInitialResolutions = new IntHashSet();\n        private final IntHashSet byMemberInvalidInitialResolutions = new IntHashSet();\n        private long imageLivenessTimeoutNs = Configuration.imageLivenessTimeoutNs();\n        private long sessionTimeoutNs = TimeUnit.SECONDS.toNanos(10);\n        private int archiveSegmentFileLength = TestCluster.SEGMENT_FILE_LENGTH;\n        private boolean acceptStandbySnapshots = false;\n        private ClusterBackup.Configuration.ReplayStart replayStart = ClusterBackup.Configuration.ReplayStart.BEGINNING;\n        private File markFileBaseDir = null;\n        private String clusterBaseDir = System.getProperty(\n            CLUSTER_BASE_DIR_PROP_NAME, CommonContext.generateRandomDirName());\n        private String aeronBaseDir = CommonContext.generateRandomDirName();\n        private boolean useResponseChannels = false;\n        private Supplier<ConsensusModuleExtension> extensionSupplier;\n        private List<String> hostnames;\n        private Function<Aeron, Counter> errorCounterSupplier;\n        private Function<Aeron, Counter> snapshotCounterSupplier;\n        private ClusterClock clusterClock;\n        private long leaderHeartbeatTimeoutNs = LEADER_HEARTBEAT_TIMEOUT_NS;\n        private long leaderHeartbeatIntervalNs = LEADER_HEARTBEAT_INTERVAL_NS;\n        private long electionTimeoutNs = ELECTION_TIMEOUT_NS;\n        private long electionStatusIntervalNs = ELECTION_STATUS_INTERVAL_NS;\n        private long startupCanvassTimeoutNs = STARTUP_CANVASS_TIMEOUT_NS;\n        private long terminationTimeoutNs = TERMINATION_TIMEOUT_NS;\n        private IntFunction<SendChannelEndpointSupplier> sendChannelEndpointSupplier = (memberId) -> null;\n        private IntFunction<ReceiveChannelEndpointSupplier> receiveChannelEndpointSupplier = (memberId) -> null;\n\n        public Builder withLeaderHeartbeatTimeoutNs(final long leaderHeartbeatTimeoutNs)\n        {\n            this.leaderHeartbeatTimeoutNs = leaderHeartbeatTimeoutNs;\n            return this;\n        }\n\n        public Builder withLeaderHeartbeatIntervalNs(final long leaderHeartbeatIntervalNs)\n        {\n            this.leaderHeartbeatIntervalNs = leaderHeartbeatIntervalNs;\n            return this;\n        }\n\n        public Builder withElectionTimeoutNs(final long electionTimeoutNs)\n        {\n            this.electionTimeoutNs = electionTimeoutNs;\n            return this;\n        }\n\n        public Builder withElectionStatusIntervalNs(final long electionStatusIntervalNs)\n        {\n            this.electionStatusIntervalNs = electionStatusIntervalNs;\n            return this;\n        }\n\n        public Builder withStartupCanvassTimeoutNs(final long startupCanvassTimeoutNs)\n        {\n            this.startupCanvassTimeoutNs = startupCanvassTimeoutNs;\n            return this;\n        }\n\n        public Builder withTerminationTimeoutNs(final long terminationTimeoutNs)\n        {\n            this.terminationTimeoutNs = terminationTimeoutNs;\n            return this;\n        }\n\n        public Builder withStaticNodes(final int nodeCount)\n        {\n            this.nodeCount = nodeCount;\n            return this;\n        }\n\n        public Builder withClusterId(final int clusterId)\n        {\n            Objects.checkIndex(clusterId, 10);\n            this.clusterId = clusterId;\n            return this;\n        }\n\n        public Builder withAppointedLeader(final int appointedLeaderId)\n        {\n            this.appointedLeaderId = appointedLeaderId;\n            return this;\n        }\n\n        public Builder withInvalidNameResolution(final int nodeId)\n        {\n            if (2 < nodeId)\n            {\n                throw new IllegalArgumentException(\"Only nodes 0 to 2 have name mappings that can be invalidated\");\n            }\n\n            byHostInvalidInitialResolutions.add(nodeId);\n            return this;\n        }\n\n        public Builder withMemberSpecificInvalidNameResolution(final int memberId)\n        {\n            byMemberInvalidInitialResolutions.add(memberId);\n            return this;\n        }\n\n        public Builder withLogChannel(final String logChannel)\n        {\n            this.logChannel = logChannel;\n            return this;\n        }\n\n        public Builder withIngressChannel(final String ingressChannel)\n        {\n            this.ingressChannel = ingressChannel;\n            return this;\n        }\n\n        public Builder withEgressChannel(final String egressChannel)\n        {\n            this.egressChannel = egressChannel;\n            return this;\n        }\n\n        public Builder withServiceSupplier(final IntFunction<TestNode.TestService[]> serviceSupplier)\n        {\n            this.serviceSupplier = serviceSupplier;\n            return this;\n        }\n\n        public Builder withSendChannelEndpointSupplier(\n            final IntFunction<SendChannelEndpointSupplier> sendChannelEndpointSupplier)\n        {\n            this.sendChannelEndpointSupplier = sendChannelEndpointSupplier;\n            return this;\n        }\n\n        public Builder withReceiveChannelEndpointSupplier(\n            final IntFunction<ReceiveChannelEndpointSupplier> receiveChannelEndpointSupplier)\n        {\n            this.receiveChannelEndpointSupplier = receiveChannelEndpointSupplier;\n            return this;\n        }\n\n        public Builder withAuthorisationServiceSupplier(final AuthorisationServiceSupplier authorisationServiceSupplier)\n        {\n            this.authorisationServiceSupplier = authorisationServiceSupplier;\n            return this;\n        }\n\n        public Builder withAuthenticationSupplier(final AuthenticatorSupplier authenticatorSupplier)\n        {\n            this.authenticationSupplier = authenticatorSupplier;\n            return this;\n        }\n\n        public Builder withTimerServiceSupplier(final TimerServiceSupplier timerServiceSupplier)\n        {\n            this.timerServiceSupplier = timerServiceSupplier;\n            return this;\n        }\n\n        public Builder withStandbySnapshots(final boolean acceptStandbySnapshots)\n        {\n            this.acceptStandbySnapshots = acceptStandbySnapshots;\n            return this;\n        }\n\n        public Builder withImageLivenessTimeoutNs(final long imageLivenessTimeoutNs)\n        {\n            this.imageLivenessTimeoutNs = imageLivenessTimeoutNs;\n            return this;\n        }\n\n        public Builder withSessionTimeoutNs(final long sessionTimeoutNs)\n        {\n            this.sessionTimeoutNs = sessionTimeoutNs;\n            return this;\n        }\n\n        public Builder withSegmentFileLength(final int archiveSegmentFileLength)\n        {\n            this.archiveSegmentFileLength = archiveSegmentFileLength;\n            return this;\n        }\n\n        public Builder withAeronBaseDir(final String baseDir)\n        {\n            this.aeronBaseDir = baseDir;\n            return this;\n        }\n\n        public Builder withClusterBaseDir(final String clusterBaseDir)\n        {\n            this.clusterBaseDir = clusterBaseDir;\n            return this;\n        }\n\n        public Builder markFileBaseDir(final File markFileBaseDir)\n        {\n            this.markFileBaseDir = markFileBaseDir;\n            return this;\n        }\n\n        public Builder replayStart(final ClusterBackup.Configuration.ReplayStart replayStart)\n        {\n            this.replayStart = replayStart;\n            return this;\n        }\n\n        public Builder useResponseChannels(final boolean enabled)\n        {\n            this.useResponseChannels = enabled;\n            return this;\n        }\n\n        public Builder withCustomAddresses(final List<String> addresses)\n        {\n            this.hostnames = addresses;\n            return this;\n        }\n\n        public Builder withErrorCounterSupplier(final Function<Aeron, Counter> errorCounterSupplier)\n        {\n            this.errorCounterSupplier = errorCounterSupplier;\n            return this;\n        }\n\n        public Builder withSnapshotCounterSupplier(final Function<Aeron, Counter> snapshotCounterSupplier)\n        {\n            this.snapshotCounterSupplier = snapshotCounterSupplier;\n            return this;\n        }\n\n        public Builder withClusterClock(final ClusterClock clusterClock)\n        {\n            this.clusterClock = clusterClock;\n            return this;\n        }\n\n        public TestCluster start()\n        {\n            return start(nodeCount);\n        }\n\n        public TestCluster start(final int toStart)\n        {\n            if (toStart > nodeCount)\n            {\n                throw new IllegalStateException(\n                    \"Unable to start \" + toStart + \" nodes, only \" + nodeCount + \" available\");\n            }\n\n            final TestCluster testCluster = new TestCluster(\n                clusterId,\n                nodeCount,\n                appointedLeaderId,\n                byHostInvalidInitialResolutions,\n                serviceSupplier,\n                useResponseChannels);\n            testCluster.logChannel(logChannel);\n            testCluster.ingressChannel(ingressChannel);\n            testCluster.egressChannel(egressChannel);\n            testCluster.authenticationSupplier(authenticationSupplier);\n            testCluster.authorisationServiceSupplier(authorisationServiceSupplier);\n            testCluster.timerServiceSupplier(timerServiceSupplier);\n            testCluster.imageLivenessTimeoutNs(imageLivenessTimeoutNs);\n            testCluster.leaderHeartbeatTimeoutNs(leaderHeartbeatTimeoutNs);\n            testCluster.leaderHeartbeatIntervalNs(leaderHeartbeatIntervalNs);\n            testCluster.electionTimeoutNs(electionTimeoutNs);\n            testCluster.electionStatusIntervalNs(electionStatusIntervalNs);\n            testCluster.startupCanvassTimeoutNs(startupCanvassTimeoutNs);\n            testCluster.terminationTimeoutNs(terminationTimeoutNs);\n            testCluster.sessionTimeoutNs(sessionTimeoutNs);\n            testCluster.segmentFileLength(archiveSegmentFileLength);\n            testCluster.invalidInitialResolutions(byHostInvalidInitialResolutions, byMemberInvalidInitialResolutions);\n            testCluster.acceptStandbySnapshots(acceptStandbySnapshots);\n            testCluster.markFileBaseDir(markFileBaseDir);\n            testCluster.aeronBaseDir(aeronBaseDir);\n            testCluster.clusterBaseDir(clusterBaseDir);\n            testCluster.replyStart(replayStart);\n            testCluster.extensionSupplier(extensionSupplier);\n            testCluster.hostnames(hostnames);\n            testCluster.errorCounterSupplier = errorCounterSupplier;\n            testCluster.snapshotCounterSupplier = snapshotCounterSupplier;\n            testCluster.sendChannelEndpointSupplier = sendChannelEndpointSupplier;\n            testCluster.receiveChannelEndpointSupplier = receiveChannelEndpointSupplier;\n            testCluster.clusterClock = clusterClock;\n\n            try\n            {\n                for (int i = 0; i < toStart; i++)\n                {\n                    try\n                    {\n                        testCluster.startStaticNode(i, true);\n                    }\n                    catch (final RegistrationException e)\n                    {\n                        if (!byHostInvalidInitialResolutions.contains(i))\n                        {\n                            throw e;\n                        }\n                    }\n                }\n            }\n            catch (final Exception e)\n            {\n                CloseHelper.close(testCluster);\n                throw e;\n            }\n\n            return testCluster;\n        }\n\n        public Builder withExtensionSuppler(final Supplier<ConsensusModuleExtension> extensionSuppler)\n        {\n            this.extensionSupplier = extensionSuppler;\n            return this;\n        }\n    }\n\n    private void replyStart(final ClusterBackup.Configuration.ReplayStart replayStart)\n    {\n        this.replayStart = replayStart;\n    }\n\n    private void extensionSupplier(final Supplier<ConsensusModuleExtension> extensionSupplier)\n    {\n        this.extensionSupplier = extensionSupplier;\n    }\n\n    private void hostnames(final List<String> hostnames)\n    {\n        this.hostnames = hostnames;\n    }\n\n    private String defaultHostname(final int nodeId)\n    {\n        if (null != hostnames && nodeId < hostnames.size())\n        {\n            return hostnames.get(nodeId);\n        }\n\n        return \"localhost\";\n    }\n\n    private void aeronBaseDir(final String aeronBaseDir)\n    {\n        this.aeronBaseDir = aeronBaseDir;\n    }\n\n    private void clusterBaseDir(final String clusterBaseDir)\n    {\n        this.clusterBaseDir = clusterBaseDir;\n    }\n\n    private void markFileBaseDir(final File markFileBaseDir)\n    {\n        this.markFileBaseDir = markFileBaseDir;\n    }\n\n    private void invalidInitialResolutions(\n        final IntHashSet byHostInvalidInitialResolutions,\n        final IntHashSet byMemberInvalidInitialResolutions)\n    {\n        this.byHostInvalidInitialResolutions = byHostInvalidInitialResolutions;\n        this.byMemberInvalidInitialResolutions = byMemberInvalidInitialResolutions;\n    }\n\n    private void acceptStandbySnapshots(final boolean acceptStandbySnapshots)\n    {\n        this.acceptStandbySnapshots = acceptStandbySnapshots;\n    }\n\n    public static DriverOutputConsumer clientDriverOutputConsumer(final DataCollector dataCollector)\n    {\n        if (TestMediaDriver.shouldRunJavaMediaDriver())\n        {\n            return null;\n        }\n\n        return new DriverOutputConsumer()\n        {\n            public void outputFiles(final String aeronDirectoryName, final File stdoutFile, final File stderrFile)\n            {\n                dataCollector.add(stdoutFile.toPath());\n                dataCollector.add(stderrFile.toPath());\n            }\n        };\n    }\n\n    public static final CredentialsSupplier SIMPLE_CREDENTIALS_SUPPLIER = new CredentialsSupplier()\n    {\n        public byte[] encodedCredentials()\n        {\n            return \"admin:admin\".getBytes(StandardCharsets.US_ASCII);\n        }\n\n        public byte[] onChallenge(final byte[] encodedChallenge)\n        {\n            return ArrayUtil.EMPTY_BYTE_ARRAY;\n        }\n    };\n\n    public static final CredentialsSupplier CHALLENGE_RESPONSE_CREDENTIALS_SUPPLIER = new CredentialsSupplier()\n    {\n        public byte[] encodedCredentials()\n        {\n            return \"admin:adminC\".getBytes(StandardCharsets.US_ASCII);\n        }\n\n        public byte[] onChallenge(final byte[] encodedChallenge)\n        {\n            return \"admin:CSadmin\".getBytes(StandardCharsets.US_ASCII);\n        }\n    };\n\n    public static final CredentialsSupplier INVALID_SIMPLE_CREDENTIALS_SUPPLIER = new CredentialsSupplier()\n    {\n        public byte[] encodedCredentials()\n        {\n            return \"admin:invalid\".getBytes(StandardCharsets.US_ASCII);\n        }\n\n        public byte[] onChallenge(final byte[] encodedChallenge)\n        {\n            return ArrayUtil.EMPTY_BYTE_ARRAY;\n        }\n    };\n\n    public static final CredentialsSupplier INVALID_CHALLENGE_RESPONSE_CREDENTIALS_SUPPLIER = new CredentialsSupplier()\n    {\n        public byte[] encodedCredentials()\n        {\n            return \"admin:adminC\".getBytes(StandardCharsets.US_ASCII);\n        }\n\n        public byte[] onChallenge(final byte[] encodedChallenge)\n        {\n            return \"admin:invalid\".getBytes(StandardCharsets.US_ASCII);\n        }\n    };\n\n    private final class KeepAlive implements Runnable\n    {\n        private long keepAliveDeadlineMs;\n        private EpochClock epochClock;\n\n        private void init()\n        {\n            this.epochClock = requireNonNull(client, \"client is not connected\")\n                .context().aeron().context().epochClock();\n            final long nowMs = epochClock.time();\n            keepAliveDeadlineMs = nowMs + TimeUnit.SECONDS.toMillis(1);\n        }\n\n        public void run()\n        {\n            final long nowMs = requireNonNull(epochClock, \"did you call init() first?\").time();\n            if (nowMs > keepAliveDeadlineMs)\n            {\n                client.sendKeepAlive();\n                keepAliveDeadlineMs = nowMs + TimeUnit.SECONDS.toMillis(1);\n            }\n            pollClient();\n        }\n    }\n\n    static class DefaultEgressListener implements EgressListener\n    {\n        private final MutableLong responseCount = new MutableLong();\n        private final MutableInteger newLeaderEvent = new MutableInteger();\n        private boolean shouldErrorOnClientClose = true;\n\n        public void onMessage(\n            final long clusterSessionId,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            responseCount.increment();\n        }\n\n        public void onSessionEvent(\n            final long correlationId,\n            final long clusterSessionId,\n            final long leadershipTermId,\n            final int leaderMemberId,\n            final EventCode code,\n            final String detail)\n        {\n            if (EventCode.ERROR == code)\n            {\n                throw new ClusterException(detail);\n            }\n            else if (EventCode.CLOSED == code && shouldErrorOnClientClose)\n            {\n                final String msg = \"[\" + System.nanoTime() / 1_000_000_000.0 + \"] session closed due to \" + detail;\n                throw new ClusterException(msg);\n            }\n        }\n\n        public void onNewLeader(\n            final long clusterSessionId,\n            final long leadershipTermId,\n            final int leaderMemberId,\n            final String ingressEndpoints)\n        {\n            newLeaderEvent.increment();\n        }\n\n        long responseCount()\n        {\n            return responseCount.get();\n        }\n\n        int newLeaderEvent()\n        {\n            return newLeaderEvent.get();\n        }\n    }\n\n    private static final class BackupListener implements ClusterBackupEventsListener\n    {\n        public void onBackupQuery()\n        {\n        }\n\n        public void onPossibleFailure(final Exception ex)\n        {\n        }\n\n        public void onBackupResponse(\n            final ClusterMember[] clusterMembers,\n            final ClusterMember logSourceMember,\n            final List<RecordingLog.Snapshot> snapshotsToRetrieve)\n        {\n            for (final ClusterMember clusterMember : clusterMembers)\n            {\n                if (clusterMember.isLeader())\n                {\n                    return;\n                }\n            }\n\n            throw new RuntimeException(\"No member has isLeader flag set\");\n        }\n\n        public void onUpdatedRecordingLog(\n            final RecordingLog recordingLog, final List<RecordingLog.Snapshot> snapshotsRetrieved)\n        {\n        }\n\n        public void onLiveLogProgress(final long recordingId, final long recordingPosCounterId, final long logPosition)\n        {\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/cluster/TestClusterClock.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.cluster;\n\nimport io.aeron.cluster.service.ClusterClock;\nimport org.agrona.concurrent.EpochClock;\nimport org.agrona.concurrent.NanoClock;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\n\npublic class TestClusterClock implements ClusterClock, NanoClock\n{\n    private final AtomicLong tick = new AtomicLong();\n    private final TimeUnit timeUnit;\n\n    public TestClusterClock()\n    {\n        this(TimeUnit.MILLISECONDS);\n    }\n\n    public TestClusterClock(final TimeUnit timeUnit)\n    {\n        this.timeUnit = timeUnit;\n    }\n\n    public TimeUnit timeUnit()\n    {\n        return timeUnit;\n    }\n\n    public long time()\n    {\n        return tick.get();\n    }\n\n    public long nanoTime()\n    {\n        return timeUnit.toNanos(tick.get());\n    }\n\n    public long timeMillis()\n    {\n        return timeUnit.toMillis(tick.get());\n    }\n\n    public long timeMicros()\n    {\n        return timeUnit.toMicros(tick.get());\n    }\n\n    public long timeNanos()\n    {\n        return timeUnit.toNanos(tick.get());\n    }\n\n    public long convertToNanos(final long time)\n    {\n        return timeUnit.toNanos(time);\n    }\n\n    public void update(final long tick, final TimeUnit tickTimeUnit)\n    {\n        this.tick.set(timeUnit.convert(tick, tickTimeUnit));\n    }\n\n    public long increment(final long tick, final TimeUnit tickTimeUnit)\n    {\n        return this.tick.addAndGet(timeUnit.convert(tick, tickTimeUnit));\n    }\n\n    public long increment(final long tick)\n    {\n        return increment(tick, timeUnit());\n    }\n\n    public EpochClock asEpochClock()\n    {\n        return this::timeMillis;\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/cluster/TestNode.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.cluster;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.Counter;\nimport io.aeron.ExclusivePublication;\nimport io.aeron.FragmentAssembler;\nimport io.aeron.Image;\nimport io.aeron.RethrowingErrorHandler;\nimport io.aeron.archive.Archive;\nimport io.aeron.archive.ArchiveTool;\nimport io.aeron.archive.client.AeronArchive;\nimport io.aeron.archive.status.RecordingPos;\nimport io.aeron.cluster.ClusterMembership;\nimport io.aeron.cluster.ClusterTool;\nimport io.aeron.cluster.ConsensusControlState;\nimport io.aeron.cluster.ConsensusModule;\nimport io.aeron.cluster.ConsensusModuleControl;\nimport io.aeron.cluster.ConsensusModuleExtension;\nimport io.aeron.cluster.ConsensusModuleVersion;\nimport io.aeron.cluster.ElectionState;\nimport io.aeron.cluster.client.ClusterException;\nimport io.aeron.cluster.codecs.CloseReason;\nimport io.aeron.cluster.service.ClientSession;\nimport io.aeron.cluster.service.Cluster;\nimport io.aeron.cluster.service.ClusterCounters;\nimport io.aeron.cluster.service.ClusterTerminationException;\nimport io.aeron.cluster.service.ClusteredServiceContainer;\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.exceptions.AeronException;\nimport io.aeron.exceptions.TimeoutException;\nimport io.aeron.logbuffer.BufferClaim;\nimport io.aeron.logbuffer.ControlledFragmentHandler;\nimport io.aeron.logbuffer.FragmentHandler;\nimport io.aeron.logbuffer.Header;\nimport io.aeron.test.DataCollector;\nimport io.aeron.test.Tests;\nimport io.aeron.test.driver.RedirectingNameResolver;\nimport io.aeron.test.driver.TestMediaDriver;\nimport org.agrona.CloseHelper;\nimport org.agrona.DirectBuffer;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.Hashing;\nimport org.agrona.collections.IntArrayList;\nimport org.agrona.collections.LongArrayList;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.concurrent.AgentTerminationException;\nimport org.agrona.concurrent.NoOpLock;\nimport org.agrona.concurrent.UnsafeBuffer;\nimport org.agrona.concurrent.YieldingIdleStrategy;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.util.Arrays;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.locks.LockSupport;\nimport java.util.function.Function;\nimport java.util.function.IntPredicate;\nimport java.util.function.Supplier;\nimport java.util.zip.CRC32;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static io.aeron.archive.client.AeronArchive.NULL_POSITION;\nimport static io.aeron.archive.status.RecordingPos.NULL_RECORDING_ID;\nimport static java.nio.ByteOrder.LITTLE_ENDIAN;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\nimport static org.agrona.BitUtil.SIZE_OF_LONG;\nimport static org.agrona.concurrent.status.CountersReader.NULL_COUNTER_ID;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\npublic final class TestNode implements AutoCloseable\n{\n    private final Archive archive;\n    private final ConsensusModule consensusModule;\n    private final ClusteredServiceContainer[] containers;\n    private final TestService[] services;\n    private final Context context;\n    private final TestMediaDriver mediaDriver;\n    private final ConsensusModuleExtension extension;\n    private boolean isClosed = false;\n    private long logRecordingId = NULL_RECORDING_ID;\n    private int logRecordingCounterId = NULL_COUNTER_ID;\n\n    TestNode(final Context context, final DataCollector dataCollector)\n    {\n        if (0 != context.services.length && null != context.extensionSupplier)\n        {\n            throw new IllegalStateException(\"Cannot use extension context\");\n        }\n\n        this.context = context;\n\n        try\n        {\n            mediaDriver = TestMediaDriver.launch(\n                context.mediaDriverContext, TestCluster.clientDriverOutputConsumer(dataCollector));\n\n            final String aeronDirectoryName = mediaDriver.context().aeronDirectoryName();\n            archive = Archive.launch(context.archiveContext.aeronDirectoryName(aeronDirectoryName));\n\n            services = context.services;\n\n            Aeron consensusModuleAeronClient = null;\n            if (null != context.errorCounterSupplier || null != context.snapshotCounterSupplier)\n            {\n                consensusModuleAeronClient = Aeron.connect(new Aeron.Context()\n                    .aeronDirectoryName(aeronDirectoryName)\n                    .subscriberErrorHandler(RethrowingErrorHandler.INSTANCE)\n                    .useConductorAgentInvoker(true)\n                    .awaitingIdleStrategy(YieldingIdleStrategy.INSTANCE)\n                    .clientLock(NoOpLock.INSTANCE)\n                    .clientName(\"test-cm-client-\" + context.consensusModuleContext.clusterId() + \"-\" +\n                        context.consensusModuleContext.clusterMemberId()));\n            }\n\n            context.consensusModuleContext\n                .serviceCount(services.length)\n                .aeron(consensusModuleAeronClient)\n                .aeronDirectoryName(aeronDirectoryName)\n                .isIpcIngressAllowed(true)\n                .terminationHook(ClusterTests.terminationHook(\n                    context.isTerminationExpected, context.hasMemberTerminated));\n\n            if (null != context.errorCounterSupplier)\n            {\n                context.consensusModuleContext.errorCounter(\n                    context.errorCounterSupplier.apply(consensusModuleAeronClient));\n            }\n            else if (null != consensusModuleAeronClient)\n            {\n                final ExpandableArrayBuffer tempBuffer = new ExpandableArrayBuffer();\n                context.consensusModuleContext.errorCounter(ClusterCounters.allocateVersioned(\n                    consensusModuleAeronClient,\n                    tempBuffer,\n                    \"Cluster Errors\",\n                    AeronCounters.CLUSTER_CONSENSUS_MODULE_ERROR_COUNT_TYPE_ID,\n                    context.consensusModuleContext.clusterId(),\n                    ConsensusModuleVersion.VERSION,\n                    ConsensusModuleVersion.GIT_SHA));\n            }\n\n            if (null != context.snapshotCounterSupplier)\n            {\n                context.consensusModuleContext.snapshotCounter(\n                    context.snapshotCounterSupplier.apply(consensusModuleAeronClient));\n            }\n\n            if (null != context.extensionSupplier)\n            {\n                extension = context.extensionSupplier.get();\n                context.consensusModuleContext.consensusModuleExtension(extension);\n            }\n            else\n            {\n                extension = null;\n            }\n\n            final AeronArchive.Context archiveContext = context.consensusModuleContext.archiveContext().clone();\n\n            consensusModule = ConsensusModule.launch(context.consensusModuleContext);\n            final File baseDir = context.consensusModuleContext.clusterDir().getParentFile();\n            dataCollector.addForCleanup(baseDir);\n\n            containers = new ClusteredServiceContainer[services.length];\n            for (int i = 0; i < services.length; i++)\n            {\n                final ClusteredServiceContainer.Context ctx = context.serviceContainerContext.clone();\n\n                final int clusterId = context.consensusModuleContext.clusterId();\n                final int memberId = context.consensusModuleContext.clusterMemberId();\n\n                ctx.aeronDirectoryName(aeronDirectoryName)\n                    .clusterId(clusterId)\n                    .archiveContext(archiveContext.clone())\n                    .terminationHook(ClusterTests.terminationHook(\n                        context.isTerminationExpected, context.hasServiceTerminated[i]))\n                    .clusterDir(context.consensusModuleContext.clusterDir())\n                    .markFileDir(context.consensusModuleContext.markFileDir())\n                    .clusteredService(services[i])\n                    .snapshotDurationThresholdNs(TimeUnit.MILLISECONDS.toNanos(100))\n                    .serviceName(\"clustered-service-\" + clusterId + \"-\" + memberId + \"-\" + i)\n                    .serviceId(i);\n                containers[i] = ClusteredServiceContainer.launch(ctx);\n            }\n\n            dataCollector.add(consensusModule.context().clusterDir().toPath());\n            dataCollector.add(archive.context().archiveDir().toPath());\n            dataCollector.add(mediaDriver.context().aeronDirectory().toPath());\n        }\n        catch (final RuntimeException ex)\n        {\n            try\n            {\n                close();\n            }\n            catch (final Exception e)\n            {\n                ex.addSuppressed(e);\n            }\n\n            throw ex;\n        }\n    }\n\n    public void stopServiceContainers()\n    {\n        CloseHelper.closeAll(containers);\n        for (final AtomicBoolean terminationFlag : context.hasServiceTerminated)\n        {\n            terminationFlag.set(true);\n        }\n    }\n\n    public TestMediaDriver mediaDriver()\n    {\n        return mediaDriver;\n    }\n\n    public Archive archive()\n    {\n        return archive;\n    }\n\n    public ConsensusModule consensusModule()\n    {\n        return consensusModule;\n    }\n\n    public ClusteredServiceContainer container()\n    {\n        if (1 != containers.length)\n        {\n            throw new IllegalStateException(\"container count expected=1 actual=\" + containers.length);\n        }\n\n        return container(0);\n    }\n\n    public ClusteredServiceContainer container(final int index)\n    {\n        return containers[index];\n    }\n\n    public TestService service()\n    {\n        if (1 != services.length)\n        {\n            throw new IllegalStateException(\"service count expected=1 actual=\" + services.length);\n        }\n\n        return services[0];\n    }\n\n    public TestService[] services()\n    {\n        return services;\n    }\n\n    public void close()\n    {\n        if (!isClosed)\n        {\n            isClosed = true;\n            CloseHelper.closeAll(consensusModule, () -> CloseHelper.closeAll(containers), archive, mediaDriver);\n        }\n    }\n\n    public boolean isClosed()\n    {\n        return isClosed;\n    }\n\n    public void gracefulClose()\n    {\n        CloseHelper.closeAll(consensusModule, () -> CloseHelper.closeAll(containers));\n    }\n\n    public Cluster.Role role()\n    {\n        final Counter roleCounter = consensusModule.context().clusterNodeRoleCounter();\n        if (!roleCounter.isClosed())\n        {\n            return Cluster.Role.get(roleCounter);\n        }\n        return Cluster.Role.FOLLOWER;\n    }\n\n    public void awaitElectionState(final ElectionState electionState)\n    {\n        while (electionState() != electionState)\n        {\n            Tests.sleep(1);\n        }\n    }\n\n    public ElectionState electionState()\n    {\n        return ElectionState.get(consensusModule.context().electionStateCounter());\n    }\n\n    public long electionCount()\n    {\n        return consensusModule.context().electionCounter().get();\n    }\n\n    public ConsensusModule.State moduleState()\n    {\n        return ConsensusModule.State.get(consensusModule.context().moduleStateCounter());\n    }\n\n    public long commitPosition()\n    {\n        final Counter counter = consensusModule.context().commitPositionCounter();\n        if (counter.isClosed())\n        {\n            return NULL_POSITION;\n        }\n\n        return counter.get();\n    }\n\n    public long appendPosition()\n    {\n        final int counterId = logRecordingCounterId();\n        if (NULL_VALUE == counterId)\n        {\n            ArchiveTool.describeRecording(System.out, archive().context().archiveDir(), logRecordingId);\n            fail(\"recording not active \" + logRecordingId);\n        }\n        return countersReader().getCounterValue(counterId);\n    }\n\n    public int logRecordingCounterId()\n    {\n        if (NULL_RECORDING_ID == logRecordingId)\n        {\n            final long recordingId = consensusModule().context().recordingLog().findLastTermRecordingId();\n            if (NULL_RECORDING_ID == recordingId)\n            {\n                fail(\"no recording for last term\");\n            }\n            logRecordingId = recordingId;\n        }\n\n        if (NULL_COUNTER_ID == logRecordingCounterId)\n        {\n            logRecordingCounterId =\n                RecordingPos.findCounterIdByRecording(countersReader(), logRecordingId, archive.context().archiveId());\n        }\n        return logRecordingCounterId;\n    }\n\n    boolean isLeader()\n    {\n        return role() == Cluster.Role.LEADER && moduleState() != ConsensusModule.State.CLOSED;\n    }\n\n    boolean isFollower()\n    {\n        return role() == Cluster.Role.FOLLOWER;\n    }\n\n    public void isTerminationExpected(final boolean isTerminationExpected)\n    {\n        context.isTerminationExpected.set(isTerminationExpected);\n    }\n\n    boolean hasServiceTerminated()\n    {\n        if (services.length > 1)\n        {\n            throw new IllegalStateException(\"service count expected=1 actual=\" + services.length);\n        }\n\n        return 0 == services.length || context.hasServiceTerminated[0].get();\n    }\n\n    public boolean hasMemberTerminated()\n    {\n        return context.hasMemberTerminated.get();\n    }\n\n    public int index()\n    {\n        return 0 == services.length ? -1 : services[0].index();\n    }\n\n    public int memberId()\n    {\n        return consensusModule.context().clusterMemberId();\n    }\n\n    CountersReader countersReader()\n    {\n        return mediaDriver.counters();\n    }\n\n    public ClusterMembership clusterMembership()\n    {\n        final ClusterMembership clusterMembership = new ClusterMembership();\n        final File clusterDir = consensusModule.context().clusterDir();\n\n        if (!ClusterTool.listMembers(clusterMembership, clusterDir, TimeUnit.SECONDS.toMillis(3)))\n        {\n            throw new IllegalStateException(\"timeout waiting for cluster members info\");\n        }\n\n        return clusterMembership;\n    }\n\n    public String hostname()\n    {\n        return context.hostName;\n    }\n\n    public boolean allSnapshotsLoaded()\n    {\n        for (final TestService service : services)\n        {\n            if (!service.wasSnapshotLoaded())\n            {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    public void validateOnElectionState(final long minJoinPosition)\n    {\n        assertInstanceOf(TestConsensusModuleExtension.class, extension);\n        final ConsensusControlState onElectionConsensusControlState =\n            ((TestConsensusModuleExtension)extension).onElectionConsensusControlState;\n        assertNotNull(onElectionConsensusControlState);\n        if (onElectionConsensusControlState.isLeader())\n        {\n            assertNotNull(onElectionConsensusControlState.leaderLogSubscription());\n            assertNotEquals(0, onElectionConsensusControlState.leaderLogSubscription().imageCount());\n            final Image image = onElectionConsensusControlState.leaderLogSubscription().imageAtIndex(0);\n            assertTrue(minJoinPosition <= image.joinPosition());\n        }\n        else\n        {\n            assertNull(onElectionConsensusControlState.leaderLogSubscription());\n        }\n    }\n\n    public static class TestService extends StubClusteredService\n    {\n        static final int SNAPSHOT_FRAGMENT_COUNT = 500;\n        static final int SNAPSHOT_MSG_LENGTH = 1000;\n\n        volatile boolean wasSnapshotTaken = false;\n        volatile boolean wasSnapshotLoaded = false;\n        volatile boolean failNextSnapshot = false;\n        private int index;\n        private volatile boolean hasReceivedUnexpectedMessage = false;\n        private volatile Cluster.Role roleChangedTo = null;\n        private final AtomicInteger activeSessionCount = new AtomicInteger();\n        protected final AtomicInteger messageCount = new AtomicInteger();\n\n        final AtomicInteger liveMessageCount = new AtomicInteger();\n        final AtomicInteger snapshotMessageCount = new AtomicInteger();\n        final AtomicInteger timerCount = new AtomicInteger();\n\n        public TestService index(final int index)\n        {\n            this.index = index;\n            return this;\n        }\n\n        int index()\n        {\n            return index;\n        }\n\n        int activeSessionCount()\n        {\n            return activeSessionCount.get();\n        }\n\n        public int messageCount()\n        {\n            return messageCount.get();\n        }\n\n        public int timerCount()\n        {\n            return timerCount.get();\n        }\n\n        public boolean wasSnapshotTaken()\n        {\n            return wasSnapshotTaken;\n        }\n\n        public void resetSnapshotTaken()\n        {\n            wasSnapshotTaken = false;\n        }\n\n        public boolean wasSnapshotLoaded()\n        {\n            return wasSnapshotLoaded;\n        }\n\n        public Cluster.Role roleChangedTo()\n        {\n            return roleChangedTo;\n        }\n\n        public Cluster cluster()\n        {\n            return cluster;\n        }\n\n        boolean hasReceivedUnexpectedMessage()\n        {\n            return hasReceivedUnexpectedMessage;\n        }\n\n        public void onStart(final Cluster cluster, final Image snapshotImage)\n        {\n            super.onStart(cluster, snapshotImage);\n\n            if (null != snapshotImage)\n            {\n                activeSessionCount.set(cluster.clientSessions().size());\n\n                final FragmentHandler handler =\n                    (buffer, offset, length, header) ->\n                    {\n                        final int value = buffer.getInt(offset);\n                        messageCount.set(value);\n                        snapshotMessageCount.set(value);\n                    };\n\n                int fragmentCount = 0;\n                while (true)\n                {\n                    final int fragments = snapshotImage.poll(handler, 10);\n                    fragmentCount += fragments;\n\n                    if (snapshotImage.isClosed() || snapshotImage.isEndOfStream())\n                    {\n                        break;\n                    }\n\n                    idleStrategy.idle(fragments);\n                }\n\n                if (fragmentCount != SNAPSHOT_FRAGMENT_COUNT)\n                {\n                    throw new AgentTerminationException(\n                        \"unexpected snapshot length: expected=\" + SNAPSHOT_FRAGMENT_COUNT + \" actual=\" + fragmentCount);\n                }\n\n                wasSnapshotLoaded = true;\n            }\n        }\n\n        public void onSessionMessage(\n            final ClientSession session,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            final String message = buffer.getStringWithoutLengthAscii(offset, length);\n            if (ClusterTests.REGISTER_TIMER_MSG.equals(message))\n            {\n                idleStrategy.reset();\n                while (!cluster.scheduleTimer(1, cluster.time() + 1_000))\n                {\n                    idleStrategy.idle();\n                }\n            }\n\n            if (message.startsWith(ClusterTests.PAUSE))\n            {\n                try\n                {\n                    final String[] messageComponents = message.split(\"\\\\|\");\n                    final int nodeIndex = Integer.parseInt(messageComponents[1]);\n\n                    if (nodeIndex == index)\n                    {\n                        final long durationNs = Long.parseLong(messageComponents[2]);\n                        LockSupport.parkNanos(durationNs);\n                    }\n                }\n                catch (final NumberFormatException ex)\n                {\n                    throw new AeronException(\"Invalid message components with message: \" + message, ex);\n                }\n            }\n\n            switch (message)\n            {\n                case ClusterTests.UNEXPECTED_MSG:\n                    hasReceivedUnexpectedMessage = true;\n                    throw new IllegalStateException(\"unexpected message received\");\n\n                case ClusterTests.TERMINATE_MSG:\n                    throw new ClusterTerminationException(false);\n\n                case ClusterTests.ECHO_SERVICE_IPC_INGRESS_MSG:\n                    sendServiceIpcMessage(session, buffer, offset, length);\n                    break;\n\n                case ClusterTests.ECHO_SERVICE_IPC_INGRESS_MSG_SKIP_FOLLOWER:\n                    simulateBuggyApplicationCodeThatSkipsServiceMessageOnFollower(session, buffer, offset, length);\n                    break;\n\n                default:\n                    if (ClusterTests.ERROR_MSG.equals(message))\n                    {\n                        cluster.context().errorHandler().onError(new Exception(message));\n                    }\n\n                    if (null != session)\n                    {\n                        echoMessage(session, buffer, offset, length);\n                    }\n                    break;\n            }\n\n            messageCount.incrementAndGet();\n            liveMessageCount.incrementAndGet();\n        }\n\n        private void simulateBuggyApplicationCodeThatSkipsServiceMessageOnFollower(\n            final ClientSession session,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            if (Cluster.Role.LEADER == cluster.role())\n            {\n                sendServiceIpcMessage(session, buffer, offset, length);\n            }\n        }\n\n        private void sendServiceIpcMessage(\n            final ClientSession session,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length)\n        {\n            if (null != session)\n            {\n                idleStrategy.reset();\n                while (cluster.offer(buffer, offset, length) < 0)\n                {\n                    idleStrategy.idle();\n                }\n            }\n            else\n            {\n                for (final ClientSession clientSession : cluster.clientSessions())\n                {\n                    echoMessage(clientSession, buffer, offset, length);\n                }\n            }\n        }\n\n        public void onTimerEvent(final long correlationId, final long timestamp)\n        {\n            timerCount.incrementAndGet();\n        }\n\n        public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n        {\n            if (failNextSnapshot)\n            {\n                failNextSnapshot = false;\n                throw new RuntimeException(\"This is a simulated failure for this snapshot\");\n            }\n\n            final UnsafeBuffer buffer = new UnsafeBuffer(new byte[SNAPSHOT_MSG_LENGTH]);\n            buffer.putInt(0, messageCount.get());\n            buffer.putInt(SNAPSHOT_MSG_LENGTH - SIZE_OF_INT, messageCount.get());\n\n            for (int i = 0; i < SNAPSHOT_FRAGMENT_COUNT; i++)\n            {\n                idleStrategy.reset();\n                while (snapshotPublication.offer(buffer, 0, SNAPSHOT_MSG_LENGTH) < 0)\n                {\n                    idleStrategy.idle();\n                }\n            }\n\n            wasSnapshotTaken = true;\n        }\n\n        public void onSessionOpen(final ClientSession session, final long timestamp)\n        {\n            super.onSessionOpen(session, timestamp);\n            activeSessionCount.incrementAndGet();\n        }\n\n        public void onSessionClose(final ClientSession session, final long timestamp, final CloseReason closeReason)\n        {\n            super.onSessionClose(session, timestamp, closeReason);\n            activeSessionCount.decrementAndGet();\n        }\n\n        public void onRoleChange(final Cluster.Role newRole)\n        {\n            roleChangedTo = newRole;\n        }\n\n        public void awaitServiceMessageCount(final int messageCount, final Runnable keepAlive, final Object node)\n        {\n            awaitServiceMessagePredicate(atLeast(messageCount), keepAlive, node);\n        }\n\n        public void awaitServiceMessagePredicate(\n            final IntPredicate predicate,\n            final Runnable keepAlive,\n            final Object node)\n        {\n            while (true)\n            {\n                final int count = messageCount();\n                if (predicate.test(count))\n                {\n                    return;\n                }\n\n                Thread.yield();\n                if (Thread.interrupted())\n                {\n                    throw new TimeoutException(\"count=\" + count + \" awaiting=\" + predicate + \" node=\" + node);\n                }\n\n                if (hasReceivedUnexpectedMessage())\n                {\n                    fail(\"service received unexpected message\");\n                }\n\n                keepAlive.run();\n            }\n        }\n\n        public void awaitLiveAndSnapshotMessageCount(\n            final IntPredicate livePredicate,\n            final IntPredicate snapshotPredicate,\n            final Runnable keepAlive,\n            final Object node)\n        {\n            while (true)\n            {\n                final int liveCount = liveMessageCount.get();\n                final int snapshotCount = snapshotMessageCount.get();\n                if (livePredicate.test(liveCount) && snapshotPredicate.test(snapshotCount))\n                {\n                    return;\n                }\n\n                Thread.yield();\n                if (Thread.interrupted())\n                {\n                    throw new TimeoutException(\n                        \"liveCount=\" + liveCount + \" snapshotCount=\" + snapshotCount +\n                        \" awaitingLive=\" + livePredicate + \" awaitingSnapshot=\" + snapshotPredicate + \" node=\" + node);\n                }\n\n                if (hasReceivedUnexpectedMessage())\n                {\n                    fail(\"service received unexpected message\");\n                }\n\n                keepAlive.run();\n            }\n        }\n\n        public void failNextSnapshot(final boolean failNextSnapshot)\n        {\n            this.failNextSnapshot = failNextSnapshot;\n        }\n\n        public String toString()\n        {\n            return \"TestService{\" +\n                \"wasSnapshotTaken=\" + wasSnapshotTaken +\n                \", wasSnapshotLoaded=\" + wasSnapshotLoaded +\n                \", failNextSnapshot=\" + failNextSnapshot +\n                \", index=\" + index +\n                \", hasReceivedUnexpectedMessage=\" + hasReceivedUnexpectedMessage +\n                \", roleChangedTo=\" + roleChangedTo +\n                \", activeSessionCount=\" + activeSessionCount +\n                \", messageCount=\" + messageCount +\n                \", liveMessageCount=\" + liveMessageCount +\n                \", snapshotMessageCount=\" + snapshotMessageCount +\n                \", timerCount=\" + timerCount +\n                '}';\n        }\n    }\n\n    public static class SleepOnSnapshotTestService extends TestNode.TestService\n    {\n        long snapshotDelayMs = 0;\n\n        public TestService snapshotDelayMs(final long snapshotDelayMs)\n        {\n            this.snapshotDelayMs = snapshotDelayMs;\n            return this;\n        }\n\n        public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n        {\n            super.onTakeSnapshot(snapshotPublication);\n\n            if (snapshotDelayMs > 0)\n            {\n                Tests.sleep(snapshotDelayMs);\n            }\n        }\n    }\n\n    public static class ChecksumService extends TestNode.TestService\n    {\n        private final BufferClaim bufferClaim = new BufferClaim();\n        private final CRC32 crc32 = new CRC32();\n        private long checksum;\n\n        public long checksum()\n        {\n            return checksum;\n        }\n\n        public void onStart(final Cluster cluster, final Image snapshotImage)\n        {\n            checksum = 0;\n            wasSnapshotLoaded = false;\n            this.cluster = cluster;\n            this.idleStrategy = cluster.idleStrategy();\n\n            if (null != snapshotImage)\n            {\n                final FragmentHandler handler =\n                    (buffer, offset, length, header) -> checksum = buffer.getLong(offset, LITTLE_ENDIAN);\n                while (true)\n                {\n                    final int fragments = snapshotImage.poll(handler, 1);\n\n                    if (snapshotImage.isClosed() || snapshotImage.isEndOfStream())\n                    {\n                        break;\n                    }\n\n                    idleStrategy.idle(fragments);\n                }\n                wasSnapshotLoaded = true;\n            }\n        }\n\n        public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n        {\n            idleStrategy.reset();\n            while (true)\n            {\n                if (snapshotPublication.tryClaim(SIZE_OF_LONG, bufferClaim) > 0)\n                {\n                    bufferClaim.buffer().putLong(bufferClaim.offset(), checksum, LITTLE_ENDIAN);\n                    bufferClaim.commit();\n                    break;\n                }\n                idleStrategy.idle();\n            }\n            wasSnapshotTaken = true;\n        }\n\n        public void onSessionMessage(\n            final ClientSession session,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            final int payloadLength = length - SIZE_OF_INT;\n            final int msgChecksum = buffer.getInt(offset + payloadLength, LITTLE_ENDIAN);\n            crc32.reset();\n            crc32.update(buffer.byteArray(), offset, payloadLength);\n            final int computedChecksum = (int)crc32.getValue();\n            if (computedChecksum != msgChecksum)\n            {\n                throw new ClusterException(\"checksum mismatch\");\n            }\n\n            checksum = Hashing.hash(checksum ^ msgChecksum);\n        }\n    }\n\n    public static class TestConsensusModuleExtension implements ConsensusModuleExtension\n    {\n        private ConsensusControlState onElectionConsensusControlState;\n        private ConsensusControlState onNewLeadershipTermConsensusControlState;\n\n        public int supportedSchemaId()\n        {\n            return 0;\n        }\n\n        public void onStart(final ConsensusModuleControl consensusModuleControl, final Image snapshotImage)\n        {\n        }\n\n        public int doWork(final long nowNs)\n        {\n            return 0;\n        }\n\n        public int slowTickWork(final long nowNs)\n        {\n            return 0;\n        }\n\n        public int consensusWork(final long nowNs)\n        {\n            return 0;\n        }\n\n        public void onElectionComplete(final ConsensusControlState consensusControlState)\n        {\n            onElectionConsensusControlState = consensusControlState;\n        }\n\n        public void onNewLeadershipTerm(final ConsensusControlState consensusControlState)\n        {\n            onNewLeadershipTermConsensusControlState = consensusControlState;\n        }\n\n        public ControlledFragmentHandler.Action onIngressExtensionMessage(\n            final int actingBlockLength,\n            final int templateId,\n            final int schemaId,\n            final int actingVersion,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            return ControlledFragmentHandler.Action.CONTINUE;\n        }\n\n        public ControlledFragmentHandler.Action onLogExtensionMessage(\n            final int actingBlockLength,\n            final int templateId,\n            final int schemaId,\n            final int actingVersion,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            return ControlledFragmentHandler.Action.CONTINUE;\n        }\n\n        public void close()\n        {\n\n        }\n\n        public void onSessionOpened(final long clusterSessionId)\n        {\n\n        }\n\n        public void onSessionClosed(final long clusterSessionId, final CloseReason closeReason)\n        {\n\n        }\n\n        public void onPrepareForNewLeadership()\n        {\n\n        }\n\n        public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n        {\n\n        }\n\n        public long leaderSubscriptionJoinPosition()\n        {\n            if (null == onElectionConsensusControlState)\n            {\n                return NULL_VALUE;\n            }\n\n            if (null == onElectionConsensusControlState.leaderLogSubscription())\n            {\n                return NULL_VALUE;\n            }\n\n            if (0 == onElectionConsensusControlState.leaderLogSubscription().imageCount())\n            {\n                return NULL_VALUE;\n            }\n\n            return onNewLeadershipTermConsensusControlState.leaderLogSubscription().imageAtIndex(0).joinPosition();\n        }\n    }\n\n    public static class MessageTrackingService extends TestNode.TestService\n    {\n        public static final int TIMER_MESSAGES_PER_INGRESS = 2;\n        public static final int SERVICE_MESSAGES_PER_INGRESS = 3;\n\n        private static volatile boolean delaySessionMessageProcessing;\n        private static final byte SNAPSHOT_COUNTERS = (byte)1;\n        private static final byte SNAPSHOT_CLIENT_MESSAGES = (byte)2;\n        private static final byte SNAPSHOT_SERVICE_MESSAGES = (byte)3;\n        private static final byte SNAPSHOT_TIMERS = (byte)4;\n        private final int serviceId;\n        private final ExpandableArrayBuffer messageBuffer = new ExpandableArrayBuffer();\n        private final IntArrayList clientMessages = new IntArrayList();\n        private final IntArrayList serviceMessages = new IntArrayList();\n        private final LongArrayList timers = new LongArrayList();\n        private int nextServiceMessageNumber;\n        private long nextTimerCorrelationId;\n\n        public static void delaySessionMessageProcessing(final boolean shouldDelay)\n        {\n            delaySessionMessageProcessing = shouldDelay;\n        }\n\n        @SuppressWarnings(\"this-escape\")\n        public MessageTrackingService(final int serviceId, final int index)\n        {\n            this.serviceId = serviceId;\n            index(index);\n        }\n\n        public IntArrayList clientMessages()\n        {\n            return copy(clientMessages);\n        }\n\n        public IntArrayList serviceMessages()\n        {\n            return copy(serviceMessages);\n        }\n\n        public LongArrayList timers()\n        {\n            return copy(timers);\n        }\n\n        public void onStart(final Cluster cluster, final Image snapshotImage)\n        {\n            nextServiceMessageNumber = 1_000_000 * serviceId;\n            nextTimerCorrelationId = -1L * 1_000_000 * serviceId;\n            clientMessages.clear();\n            serviceMessages.clear();\n            timers.clear();\n            wasSnapshotLoaded = false;\n            this.cluster = cluster;\n            this.idleStrategy = cluster.idleStrategy();\n\n            if (null != snapshotImage)\n            {\n                final FragmentHandler handler =\n                    new FragmentAssembler((buffer, offset, length, header) ->\n                    {\n                        int index = offset;\n                        final byte snapshotType = buffer.getByte(index);\n                        index++;\n                        if (SNAPSHOT_COUNTERS == snapshotType)\n                        {\n                            final int storedServiceId = buffer.getInt(index, LITTLE_ENDIAN);\n                            if (serviceId != storedServiceId)\n                            {\n                                throw new IllegalStateException(\"Invalid snapshot!\");\n                            }\n                            index += SIZE_OF_INT;\n                            messageCount.set(buffer.getInt(index, LITTLE_ENDIAN));\n                            index += SIZE_OF_INT;\n                            timerCount.set(buffer.getInt(index, LITTLE_ENDIAN));\n                            index += SIZE_OF_INT;\n                            nextServiceMessageNumber = buffer.getInt(index, LITTLE_ENDIAN);\n                            index += SIZE_OF_INT;\n                            nextTimerCorrelationId = buffer.getLong(index, LITTLE_ENDIAN);\n                        }\n                        else if (SNAPSHOT_CLIENT_MESSAGES == snapshotType) // client messages\n                        {\n                            restoreMessages(buffer, index, clientMessages);\n                        }\n                        else if (SNAPSHOT_SERVICE_MESSAGES == snapshotType) // service messages\n                        {\n                            restoreMessages(buffer, index, serviceMessages);\n                        }\n                        else if (SNAPSHOT_TIMERS == snapshotType) // timers\n                        {\n                            restoreTimers(buffer, index);\n                        }\n                        else\n                        {\n                            throw new IllegalStateException(\"Unknown snapshot type: \" + snapshotType);\n                        }\n                    });\n\n                while (true)\n                {\n                    final int fragments = snapshotImage.poll(handler, 1);\n\n                    if (snapshotImage.isClosed() || snapshotImage.isEndOfStream())\n                    {\n                        break;\n                    }\n\n                    idleStrategy.idle(fragments);\n                }\n                wasSnapshotLoaded = true;\n            }\n        }\n\n        public void onTakeSnapshot(final ExclusivePublication snapshotPublication)\n        {\n            wasSnapshotTaken = false;\n\n            int offset = 0;\n\n            messageBuffer.putByte(offset, SNAPSHOT_COUNTERS);\n            offset++;\n            messageBuffer.putInt(offset, serviceId, LITTLE_ENDIAN);\n            offset += SIZE_OF_INT;\n            messageBuffer.putInt(offset, messageCount(), LITTLE_ENDIAN);\n            offset += SIZE_OF_INT;\n            messageBuffer.putInt(offset, timerCount(), LITTLE_ENDIAN);\n            offset += SIZE_OF_INT;\n            messageBuffer.putInt(offset, nextServiceMessageNumber, LITTLE_ENDIAN);\n            offset += SIZE_OF_INT;\n            messageBuffer.putLong(offset, nextTimerCorrelationId, LITTLE_ENDIAN);\n            offset += SIZE_OF_LONG;\n            idleStrategy.reset();\n            while (snapshotPublication.offer(messageBuffer, 0, offset) < 0)\n            {\n                idleStrategy.idle();\n            }\n\n            snapshotMessages(snapshotPublication, SNAPSHOT_CLIENT_MESSAGES, clientMessages);\n            snapshotMessages(snapshotPublication, SNAPSHOT_SERVICE_MESSAGES, serviceMessages);\n            snapshotTimers(snapshotPublication);\n\n            wasSnapshotTaken = true;\n        }\n\n        public void onSessionMessage(\n            final ClientSession session,\n            final long timestamp,\n            final DirectBuffer buffer,\n            final int offset,\n            final int length,\n            final Header header)\n        {\n            if (delaySessionMessageProcessing)\n            {\n                Tests.sleep(1);\n            }\n\n            if (null != session)\n            {\n                final int messageId = buffer.getInt(offset, LITTLE_ENDIAN);\n                clientMessages.addInt(messageId);\n\n                // Send 3 service messages\n                for (int i = 0; i < SERVICE_MESSAGES_PER_INGRESS; i++)\n                {\n                    messageBuffer.putInt(0, ++nextServiceMessageNumber, LITTLE_ENDIAN);\n\n                    idleStrategy.reset();\n                    while (cluster.offer(messageBuffer, 0, SIZE_OF_INT) < 0)\n                    {\n                        idleStrategy.idle();\n                    }\n                }\n\n                // Schedule two timers\n                for (int i = 0; i < TIMER_MESSAGES_PER_INGRESS; i++)\n                {\n                    final long timerId = --nextTimerCorrelationId;\n                    idleStrategy.reset();\n                    while (!cluster.scheduleTimer(timerId, cluster.time() - 1))\n                    {\n                        idleStrategy.idle();\n                    }\n                }\n\n                // Echo input message back to the client\n                while (session.offer(buffer, offset, length) < 0)\n                {\n                    idleStrategy.idle();\n                }\n            }\n            else\n            {\n                final int serviceMessageId = buffer.getInt(offset, LITTLE_ENDIAN);\n                serviceMessages.addInt(serviceMessageId);\n            }\n\n            messageCount.incrementAndGet(); // count all messages\n        }\n\n        public void onTimerEvent(final long correlationId, final long timestamp)\n        {\n            timers.add(correlationId);\n            super.onTimerEvent(correlationId, timestamp);\n        }\n\n        public String toString()\n        {\n            return \"MessageTrackingService{\" +\n                \"serviceId=\" + serviceId +\n                \", messageCount=\" + messageCount() +\n                \", timerCount=\" + timerCount() +\n                \", nextServiceMessageNumber=\" + nextServiceMessageNumber +\n                \", nextTimerCorrelationId=\" + nextTimerCorrelationId +\n                '}';\n        }\n\n        private void snapshotMessages(\n            final ExclusivePublication snapshotPublication, final byte snapshotType, final IntArrayList messages)\n        {\n            final MutableInteger offset = new MutableInteger();\n            messageBuffer.putByte(offset.get(), snapshotType);\n            offset.increment();\n            messageBuffer.putInt(offset.get(), messages.size(), LITTLE_ENDIAN);\n            offset.addAndGet(SIZE_OF_INT);\n\n            messages.forEachInt(\n                (messageId) ->\n                {\n                    messageBuffer.putInt(offset.get(), messageId);\n                    offset.addAndGet(SIZE_OF_INT);\n                });\n\n            idleStrategy.reset();\n            while (snapshotPublication.offer(messageBuffer, 0, offset.get()) < 0)\n            {\n                idleStrategy.idle();\n            }\n        }\n\n        private void snapshotTimers(final ExclusivePublication snapshotPublication)\n        {\n            final MutableInteger offset = new MutableInteger();\n            messageBuffer.putByte(offset.get(), SNAPSHOT_TIMERS);\n            offset.increment();\n            messageBuffer.putInt(offset.get(), timers.size(), LITTLE_ENDIAN);\n            offset.addAndGet(SIZE_OF_INT);\n\n            timers.forEachLong(\n                (correlationId) ->\n                {\n                    messageBuffer.putLong(offset.get(), correlationId);\n                    offset.addAndGet(SIZE_OF_LONG);\n                });\n\n            idleStrategy.reset();\n            while (snapshotPublication.offer(messageBuffer, 0, offset.get()) < 0)\n            {\n                idleStrategy.idle();\n            }\n        }\n\n        private void restoreMessages(final DirectBuffer buffer, final int offset, final IntArrayList messages)\n        {\n            int absoluteOffset = offset;\n            final int count = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n            absoluteOffset += SIZE_OF_INT;\n            for (int i = 0; i < count; i++)\n            {\n                final int messageId = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n                absoluteOffset += SIZE_OF_INT;\n                messages.addInt(messageId);\n            }\n        }\n\n        private void restoreTimers(final DirectBuffer buffer, final int offset)\n        {\n            int absoluteOffset = offset;\n            final int count = buffer.getInt(absoluteOffset, LITTLE_ENDIAN);\n            absoluteOffset += SIZE_OF_INT;\n            for (int i = 0; i < count; i++)\n            {\n                final long correlationId = buffer.getLong(absoluteOffset, LITTLE_ENDIAN);\n                absoluteOffset += SIZE_OF_LONG;\n                timers.add(correlationId);\n            }\n        }\n\n        private static IntArrayList copy(final IntArrayList values)\n        {\n            return new IntArrayList(values.toIntArray(), values.size(), values.nullValue());\n        }\n\n        private static LongArrayList copy(final LongArrayList values)\n        {\n            return new LongArrayList(values.toLongArray(), values.size(), values.nullValue());\n        }\n    }\n\n    static class Context\n    {\n        final MediaDriver.Context mediaDriverContext = new MediaDriver.Context();\n        final Archive.Context archiveContext = new Archive.Context();\n        final AeronArchive.Context aeronArchiveContext = new AeronArchive.Context();\n        final ConsensusModule.Context consensusModuleContext = new ConsensusModule.Context();\n        final ClusteredServiceContainer.Context serviceContainerContext = new ClusteredServiceContainer.Context();\n        final AtomicBoolean isTerminationExpected = new AtomicBoolean();\n        final AtomicBoolean hasMemberTerminated = new AtomicBoolean();\n        final AtomicBoolean[] hasServiceTerminated;\n        final String hostName;\n        final TestService[] services;\n        Supplier<ConsensusModuleExtension> extensionSupplier;\n        Function<Aeron, Counter> errorCounterSupplier;\n        Function<Aeron, Counter> snapshotCounterSupplier;\n\n        Context(final TestService[] services, final String hostName, final String nodeMappings)\n        {\n            this.hostName = hostName;\n            mediaDriverContext.nameResolver(new RedirectingNameResolver(nodeMappings));\n\n            this.services = services;\n            hasServiceTerminated = new AtomicBoolean[services.length];\n            for (int i = 0; i < services.length; i++)\n            {\n                hasServiceTerminated[i] = new AtomicBoolean();\n            }\n        }\n    }\n\n    public String toString()\n    {\n        return \"TestNode{\" +\n            \"memberId=\" + index() +\n            \", role=\" + role() +\n            \", services=\" + Arrays.toString(services) +\n            '}';\n    }\n\n    public static IntPredicate atLeast(final int count)\n    {\n        return new IntPredicate()\n        {\n            public boolean test(final int value)\n            {\n                return count <= value;\n            }\n\n            public String toString()\n            {\n                return \"atLeast(\" + count + \")\";\n            }\n        };\n    }\n\n    public static IntPredicate atMost(final int count)\n    {\n        return new IntPredicate()\n        {\n            public boolean test(final int value)\n            {\n                return value <= count;\n            }\n\n            public String toString()\n            {\n                return \"atMost(\" + count + \")\";\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/driver/CTestMediaDriver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.driver;\n\nimport io.aeron.Aeron;\nimport io.aeron.AeronCounters;\nimport io.aeron.CommonContext;\nimport io.aeron.driver.*;\nimport io.aeron.protocol.HeaderFlyweight;\nimport io.aeron.test.SystemTestConfig;\nimport io.aeron.test.Tests;\nimport org.agrona.IoUtil;\nimport org.agrona.LangUtil;\nimport org.agrona.SystemUtil;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.Object2ObjectHashMap;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\n\npublic final class CTestMediaDriver implements TestMediaDriver\n{\n    private static final String UDP_CHANNEL_OUTGOING_INTERCEPTORS_ENV_VAR = \"AERON_UDP_CHANNEL_OUTGOING_INTERCEPTORS\";\n    private static final String UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR = \"AERON_UDP_CHANNEL_INCOMING_INTERCEPTORS\";\n\n    private static final File NULL_FILE = SystemUtil.isWindows() ? new File(\"NUL\") : new File(\"/dev/null\");\n    private static final Map<Class<? extends FlowControlSupplier>, String> C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE =\n        new IdentityHashMap<>();\n    private static final ThreadLocal<Map<MediaDriver.Context, Map<String, String>>> C_DRIVER_ADDITIONAL_ENV_VARS =\n        ThreadLocal.withInitial(IdentityHashMap::new);\n    private static final Collection<String> JOINABLE_ENV_VARS = Arrays.asList(\n        UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, UDP_CHANNEL_OUTGOING_INTERCEPTORS_ENV_VAR);\n\n    static\n    {\n        C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(\n            DefaultMulticastFlowControlSupplier.class, \"aeron_max_multicast_flow_control_strategy_supplier\");\n        C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(\n            MaxMulticastFlowControlSupplier.class, \"aeron_max_multicast_flow_control_strategy_supplier\");\n        C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(\n            MinMulticastFlowControlSupplier.class, \"aeron_min_flow_control_strategy_supplier\");\n        C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(\n            DefaultUnicastFlowControlSupplier.class, \"aeron_unicast_flow_control_strategy_supplier\");\n        C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.put(\n            TaggedMulticastFlowControlSupplier.class, \"aeron_tagged_flow_control_strategy_supplier\");\n    }\n\n    private final Process aeronMediaDriverProcess;\n    private final MediaDriver.Context context;\n    private final DriverOutputConsumer driverOutputConsumer;\n    private final File stdoutFile;\n    private final File stderrFile;\n    private Aeron.Context aeronContext;\n    private CountersReader countersReader;\n    private boolean isClosed = false;\n\n    private CTestMediaDriver(\n        final Process aeronMediaDriverProcess,\n        final MediaDriver.Context context,\n        final DriverOutputConsumer driverOutputConsumer,\n        final File stdoutFile,\n        final File stderrFile)\n    {\n        this.aeronMediaDriverProcess = aeronMediaDriverProcess;\n        this.context = context;\n        this.driverOutputConsumer = driverOutputConsumer;\n        this.stdoutFile = stdoutFile;\n        this.stderrFile = stderrFile;\n    }\n\n    public void close()\n    {\n        if (isClosed)\n        {\n            return;\n        }\n\n        awaitSendersAndReceiversClosed();\n\n        isClosed = true;\n\n        Exception error = null;\n        try\n        {\n            if (null != aeronContext)\n            {\n                aeronContext.close();\n            }\n        }\n        catch (final Exception ex)\n        {\n            error = ex;\n        }\n\n        try\n        {\n            final ExitStatus exitStatus = terminateDriver();\n            if (null != driverOutputConsumer)\n            {\n                driverOutputConsumer.exitCode(\n                    context.aeronDirectoryName(), exitStatus.exitCode, exitStatus.exitMessage);\n            }\n        }\n        catch (final Exception ex)\n        {\n            if (null == error)\n            {\n                error = ex;\n            }\n            else\n            {\n                error.addSuppressed(ex);\n            }\n        }\n\n        if (null != error)\n        {\n            LangUtil.rethrowUnchecked(error);\n        }\n    }\n\n    private void awaitSendersAndReceiversClosed()\n    {\n        if (!SystemTestConfig.DRIVER_AWAIT_COUNTER_CLOSE)\n        {\n            return;\n        }\n\n        final MutableInteger counterCount = new MutableInteger();\n        final CountersReader.MetaData metaData = (counterId, typeId, keyBuffer, label) ->\n        {\n            if (AeronCounters.DRIVER_RECEIVE_CHANNEL_STATUS_TYPE_ID == typeId ||\n                AeronCounters.DRIVER_SEND_CHANNEL_STATUS_TYPE_ID == typeId)\n            {\n                counterCount.increment();\n            }\n        };\n\n        final long deadlineMs = System.currentTimeMillis() + 15_000;\n        do\n        {\n            counterCount.set(0);\n            counters().forEach(metaData);\n\n            Tests.checkInterruptStatus();\n            Tests.yield();\n        }\n        while (0 != counterCount.get() && System.currentTimeMillis() < deadlineMs);\n    }\n\n    public void cleanup()\n    {\n        if (NULL_FILE != stdoutFile)\n        {\n            IoUtil.delete(stdoutFile, true);\n        }\n\n        if (NULL_FILE != stderrFile)\n        {\n            IoUtil.delete(stderrFile, true);\n        }\n    }\n\n    public CountersReader counters()\n    {\n        if (null == countersReader)\n        {\n            aeronContext = new Aeron.Context()\n                .aeronDirectoryName(context.aeronDirectoryName())\n                .keepAliveIntervalNs(TimeUnit.MILLISECONDS.toNanos(100))\n                .conclude();\n            countersReader = new CountersReader(\n                aeronContext.countersMetaDataBuffer(), aeronContext.countersValuesBuffer(), StandardCharsets.US_ASCII);\n        }\n\n        return countersReader;\n    }\n\n    @SuppressWarnings(\"methodlength\")\n    public static CTestMediaDriver launch(\n        final MediaDriver.Context context,\n        final boolean withAeronDir,\n        final DriverOutputConsumer driverOutputConsumer)\n    {\n        final String aeronMediaDriverPath = System.getProperty(TestMediaDriver.AERONMD_PATH_PROP_NAME);\n        final File aeronBinary = new File(aeronMediaDriverPath);\n\n        if (!aeronBinary.exists())\n        {\n            throw new RuntimeException(\"Unable to find native media driver binary: \" + aeronBinary.getAbsolutePath());\n        }\n\n        context.concludeAeronDirectory();\n        IoUtil.ensureDirectoryExists(context.aeronDirectory().getParentFile(), \"Aeron C Media Driver directory\");\n\n        final HashMap<String, String> environment = new HashMap<>();\n\n        if (withAeronDir)\n        {\n            environment.put(\"AERON_DIR\", context.aeronDirectoryName());\n        }\n        environment.put(\"AERON_CLIENT_LIVENESS_TIMEOUT\", String.valueOf(context.clientLivenessTimeoutNs()));\n        environment.put(\"AERON_IMAGE_LIVENESS_TIMEOUT\", String.valueOf(context.imageLivenessTimeoutNs()));\n        environment.put(\"AERON_DRIVER_TERMINATION_VALIDATOR\", \"allow\");\n        environment.put(\"AERON_DIR_DELETE_ON_START\", Boolean.toString(context.dirDeleteOnStart()));\n        environment.put(\"AERON_DIR_DELETE_ON_SHUTDOWN\", Boolean.toString(context.dirDeleteOnShutdown()));\n        environment.put(\"AERON_TERM_BUFFER_SPARSE_FILE\", Boolean.toString(context.termBufferSparseFile()));\n        environment.put(\"AERON_TERM_BUFFER_LENGTH\", String.valueOf(context.publicationTermBufferLength()));\n        environment.put(\"AERON_IPC_TERM_BUFFER_LENGTH\", String.valueOf(context.ipcTermBufferLength()));\n        environment.put(\"AERON_FILE_PAGE_SIZE\", String.valueOf(context.filePageSize()));\n        environment.put(\n            \"AERON_PUBLICATION_UNBLOCK_TIMEOUT\", String.valueOf(context.publicationUnblockTimeoutNs()));\n        environment.put(\n            \"AERON_PUBLICATION_CONNECTION_TIMEOUT\", String.valueOf(context.publicationConnectionTimeoutNs()));\n        environment.put(\"AERON_PUBLICATION_LINGER_TIMEOUT\", String.valueOf(context.publicationLingerTimeoutNs()));\n        environment.put(\"AERON_SPIES_SIMULATE_CONNECTION\", Boolean.toString(context.spiesSimulateConnection()));\n        environment.put(\"AERON_RCV_STATUS_MESSAGE_TIMEOUT\", String.valueOf(context.statusMessageTimeoutNs()));\n        environment.put(\"AERON_PERFORM_STORAGE_CHECKS\", Boolean.toString(context.performStorageChecks()));\n        if (null != context.threadingMode())\n        {\n            environment.put(\"AERON_THREADING_MODE\", context.threadingMode().name());\n        }\n        environment.put(\"AERON_TIMER_INTERVAL\", String.valueOf(context.timerIntervalNs()));\n        environment.put(\n            \"AERON_UNTETHERED_WINDOW_LIMIT_TIMEOUT\", String.valueOf(context.untetheredWindowLimitTimeoutNs()));\n        final long untetheredLingerTimeoutNs = context.untetheredLingerTimeoutNs();\n        if (Aeron.NULL_VALUE != untetheredLingerTimeoutNs)\n        {\n            environment.put(\"AERON_UNTETHERED_LINGER_TIMEOUT\", String.valueOf(untetheredLingerTimeoutNs));\n        }\n        environment.put(\"AERON_UNTETHERED_RESTING_TIMEOUT\", String.valueOf(context.untetheredRestingTimeoutNs()));\n\n        if (null != context.receiverGroupTag())\n        {\n            environment.put(\"AERON_RECEIVER_GROUP_TAG\", context.receiverGroupTag().toString());\n        }\n        environment.put(\"AERON_FLOW_CONTROL_GROUP_TAG\", String.valueOf(context.flowControlGroupTag()));\n        environment.put(\n            \"AERON_FLOW_CONTROL_GROUP_MIN_SIZE\", String.valueOf(context.flowControlGroupMinSize()));\n        environment.put(\"AERON_PRINT_CONFIGURATION\", \"true\");\n\n        if (null != context.resolverName())\n        {\n            environment.put(\"AERON_DRIVER_RESOLVER_NAME\", context.resolverName());\n        }\n        if (null != context.resolverInterface())\n        {\n            environment.put(\"AERON_DRIVER_RESOLVER_INTERFACE\", context.resolverInterface());\n            environment.put(\"AERON_NAME_RESOLVER_SUPPLIER\", \"driver\");\n        }\n        if (null != context.resolverBootstrapNeighbor())\n        {\n            environment.put(\"AERON_DRIVER_RESOLVER_BOOTSTRAP_NEIGHBOR\", context.resolverBootstrapNeighbor());\n        }\n        environment.put(\"AERON_SOCKET_SO_RCVBUF\", String.valueOf(context.socketRcvbufLength()));\n        environment.put(\"AERON_SOCKET_SO_SNDBUF\", String.valueOf(context.socketSndbufLength()));\n        environment.put(\"AERON_RCV_INITIAL_WINDOW_LENGTH\", String.valueOf(context.initialWindowLength()));\n        environment.put(\"AERON_PUBLICATION_UNBLOCK_TIMEOUT\", String.valueOf(context.publicationUnblockTimeoutNs()));\n        final NameResolver nameResolver = context.nameResolver();\n        if (nameResolver instanceof RedirectingNameResolver)\n        {\n            final String csvConfiguration = ((RedirectingNameResolver)nameResolver).csvConfiguration();\n            environment.put(\"AERON_NAME_RESOLVER_SUPPLIER\", \"csv_table\");\n            environment.put(\"AERON_NAME_RESOLVER_INIT_ARGS\", csvConfiguration);\n        }\n        environment.put(\"AERON_DRIVER_CONDUCTOR_CYCLE_THRESHOLD\", String.valueOf(context.conductorCycleThresholdNs()));\n        environment.put(\"AERON_DRIVER_SENDER_CYCLE_THRESHOLD\", String.valueOf(context.senderCycleThresholdNs()));\n        environment.put(\"AERON_DRIVER_RECEIVER_CYCLE_THRESHOLD\", String.valueOf(context.receiverCycleThresholdNs()));\n        environment.put(\"AERON_DRIVER_NAME_RESOLVER_THRESHOLD\", String.valueOf(context.nameResolverThresholdNs()));\n        environment.put(\"AERON_DRIVER_ASYNC_EXECUTOR_THREADS\", String.valueOf(context.asyncTaskExecutorThreads()));\n        final String senderWildcardPortRange = context.senderWildcardPortRange();\n        if (null != senderWildcardPortRange)\n        {\n            environment.put(\"AERON_SENDER_WILDCARD_PORT_RANGE\", senderWildcardPortRange);\n        }\n        final String receiverWildcardPortRange = context.receiverWildcardPortRange();\n        if (null != receiverWildcardPortRange)\n        {\n            environment.put(\"AERON_RECEIVER_WILDCARD_PORT_RANGE\", receiverWildcardPortRange);\n        }\n\n        environment.put(\"AERON_ENABLE_EXPERIMENTAL_FEATURES\", String.valueOf(context.enableExperimentalFeatures()));\n\n        environment.put(\"AERON_DRIVER_STREAM_SESSION_LIMIT\", String.valueOf(context.streamSessionLimit()));\n\n        setFlowControlStrategy(environment, context);\n        setLogging(environment);\n        setTransportSecurity(environment);\n        setAdditionalEnvVars(environment, getAdditionalEnvVarsMap(context));\n\n        try\n        {\n            File stdoutFile = NULL_FILE;\n            File stderrFile = NULL_FILE;\n\n            final ProcessBuilder pb = new ProcessBuilder(aeronBinary.getAbsolutePath());\n            if (null != driverOutputConsumer)\n            {\n                stdoutFile = Files.createTempFile(context.aeronDirectory().getName() + \"-driver-\", \".out\").toFile();\n                final String tmpName = stdoutFile.getName().substring(0, stdoutFile.getName().length() - 4) + \".err\";\n                stderrFile = new File(stdoutFile.getParent(), tmpName);\n                driverOutputConsumer.outputFiles(context.aeronDirectoryName(), stdoutFile, stderrFile);\n                driverOutputConsumer.environmentVariables(context.aeronDirectoryName(), environment);\n            }\n\n            pb.environment().putAll(environment);\n            pb.redirectOutput(stdoutFile).redirectError(stderrFile);\n            final Process process = pb.start();\n            Thread.yield();\n\n            return new CTestMediaDriver(process, context, driverOutputConsumer, stdoutFile, stderrFile);\n        }\n        catch (final IOException ex)\n        {\n            LangUtil.rethrowUnchecked(ex);\n            return null;\n        }\n    }\n\n    public MediaDriver.Context context()\n    {\n        return context;\n    }\n\n    public String aeronDirectoryName()\n    {\n        return context.aeronDirectoryName();\n    }\n\n    public AgentInvoker sharedAgentInvoker()\n    {\n        throw new UnsupportedOperationException(\"Not supported in C media driver\");\n    }\n\n    public static void enableRandomLossOnReceive(\n        final MediaDriver.Context context,\n        final double rate,\n        final long seed,\n        final boolean loseDataMessages,\n        final boolean loseControlMessages)\n    {\n        int receiveMessageTypeMask = 0;\n        receiveMessageTypeMask |= loseDataMessages ? 1 << HeaderFlyweight.HDR_TYPE_DATA : 0;\n        receiveMessageTypeMask |= loseControlMessages ? 1 << HeaderFlyweight.HDR_TYPE_SM : 0;\n        receiveMessageTypeMask |= loseControlMessages ? 1 << HeaderFlyweight.HDR_TYPE_NAK : 0;\n        receiveMessageTypeMask |= loseControlMessages ? 1 << HeaderFlyweight.HDR_TYPE_RTTM : 0;\n\n        final Object2ObjectHashMap<String, String> lossTransportEnv = new Object2ObjectHashMap<>();\n\n        final String interceptor = \"loss\";\n        final String lossArgs = \"rate=\" + rate +\n            \"|seed=\" + seed +\n            \"|recv-msg-mask=0x\" + Integer.toHexString(receiveMessageTypeMask);\n\n        lossTransportEnv.put(UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, interceptor);\n        lossTransportEnv.put(\"AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_LOSS_ARGS\", lossArgs);\n\n        getAdditionalEnvVarsMap(context).putAll(lossTransportEnv);\n    }\n\n    public static void enableFixedLossOnReceive(\n        final MediaDriver.Context context,\n        final int termId,\n        final int termOffset,\n        final int length)\n    {\n        final Object2ObjectHashMap<String, String> fixedLossTransportEnv = new Object2ObjectHashMap<>();\n\n        final String interceptor = \"fixed-loss\";\n        final String fixedLossArgs = \"term-id=\" + termId +\n            \"|term-offset=\" + termOffset +\n            \"|length=\" + length;\n\n        fixedLossTransportEnv.put(UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, interceptor);\n        fixedLossTransportEnv.put(\"AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_FIXED_LOSS_ARGS\", fixedLossArgs);\n\n        getAdditionalEnvVarsMap(context).putAll(fixedLossTransportEnv);\n    }\n\n    public static void enableMultiGapLossOnReceive(\n        final MediaDriver.Context context,\n        final int termId,\n        final int gapRadix,\n        final int gapLength,\n        final int totalGaps)\n    {\n        final Object2ObjectHashMap<String, String> multiGapLossTransportEnv = new Object2ObjectHashMap<>();\n\n        final String interceptor = \"multi-gap-loss\";\n        final String multiGapLossArgs = \"term-id=\" + termId +\n            \"|gap-radix=\" + gapRadix +\n            \"|gap-length=\" + gapLength +\n            \"|total-gaps=\" + totalGaps;\n\n        multiGapLossTransportEnv.put(UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, interceptor);\n        multiGapLossTransportEnv.put(\"AERON_UDP_CHANNEL_TRANSPORT_BINDINGS_MULTI_GAP_LOSS_ARGS\", multiGapLossArgs);\n\n        getAdditionalEnvVarsMap(context).putAll(multiGapLossTransportEnv);\n    }\n\n    public static void dontCoalesceNaksOnReceiverByDefault(final MediaDriver.Context context)\n    {\n        getAdditionalEnvVarsMap(context).put(\"AERON_NAK_UNICAST_DELAY\", \"0\");\n    }\n\n    public static Map<String, String> getAdditionalEnvVarsMap(final MediaDriver.Context context)\n    {\n        return C_DRIVER_ADDITIONAL_ENV_VARS.get().computeIfAbsent(context, c -> new IdentityHashMap<>());\n    }\n\n    private static void setLogging(final Map<String, String> environment)\n    {\n        environment.put(\"AERON_EVENT_LOG\", System.getProperty(\n            \"aeron.event.log\",\n            \"admin\"));\n        environment.put(\"AERON_EVENT_LOG_DISABLE\", System.getProperty(\"aeron.event.log.disable\", \"\"));\n    }\n\n    private static void setTransportSecurity(final HashMap<String, String> environment)\n    {\n        final String atsLibPath = (String)System.getProperties().get(ATS_LIBRARY_PATH_PROP_NAME);\n        if (null != atsLibPath && !atsLibPath.isEmpty())\n        {\n            IoUtil.checkFileExists(new File(atsLibPath), ATS_LIBRARY_PATH_PROP_NAME);\n\n            environment.put(\"AERON_DRIVER_DYNAMIC_LIBRARIES\", atsLibPath);\n            environment.put(\n                UDP_CHANNEL_OUTGOING_INTERCEPTORS_ENV_VAR, \"aeron_transport_security_channel_interceptor_load\");\n            environment.put(\n                UDP_CHANNEL_INCOMING_INTERCEPTORS_ENV_VAR, \"aeron_transport_security_channel_interceptor_load\");\n\n            final String atsConfDir = System.getProperty(ATS_LIBRARY_CONF_PATH_PROP_NAME);\n            if (null != atsConfDir)\n            {\n                environment.put(\"AERON_TRANSPORT_SECURITY_CONF_DIR\", atsConfDir);\n            }\n            final String atsConfFile = System.getProperty(ATS_LIBRARY_CONF_FILE_PROP_NAME);\n            if (null != atsConfFile)\n            {\n                environment.put(\"AERON_TRANSPORT_SECURITY_CONF_FILE\", atsConfFile);\n            }\n        }\n    }\n\n    private static void setAdditionalEnvVars(\n        final HashMap<String, String> environment,\n        final Map<String, String> additionalEnvVars)\n    {\n        additionalEnvVars.forEach(\n            (k, v) ->\n            {\n                final String existingValue = environment.putIfAbsent(k, v);\n                if (null != existingValue)\n                {\n                    if (JOINABLE_ENV_VARS.contains(k))\n                    {\n                        environment.put(k, existingValue + \",\" + v);\n                    }\n                    else\n                    {\n                        throw new RuntimeException(\n                            \"Variable: \" + k + \" is already specified as: \" + existingValue + \" cannot set to: \" + v);\n                    }\n                }\n            });\n    }\n\n    private static void setFlowControlStrategy(final Map<String, String> environment, final MediaDriver.Context context)\n    {\n        final FlowControlSupplier multicastFlowControlSupplier = context.multicastFlowControlSupplier();\n        final String multicastFlowControlStrategyName = getFlowControlStrategyName(multicastFlowControlSupplier);\n        if (null != multicastFlowControlStrategyName)\n        {\n            environment.put(\"AERON_MULTICAST_FLOWCONTROL_SUPPLIER\", multicastFlowControlStrategyName);\n        }\n        else if (null != multicastFlowControlSupplier)\n        {\n            throw new RuntimeException(\"No equivalent C multicast flow control strategy for: \" +\n                multicastFlowControlSupplier.getClass());\n        }\n\n        final FlowControlSupplier unicastFlowControlSupplier = context.unicastFlowControlSupplier();\n        final String unicastFlowControlStrategyName = getFlowControlStrategyName(unicastFlowControlSupplier);\n        if (null != unicastFlowControlStrategyName)\n        {\n            environment.put(\"AERON_UNICAST_FLOWCONTROL_SUPPLIER\", unicastFlowControlStrategyName);\n        }\n        else if (null != unicastFlowControlSupplier)\n        {\n            throw new RuntimeException(\"No equivalent C unicast flow control strategy for: \" +\n                unicastFlowControlSupplier.getClass());\n        }\n    }\n\n    private static String getFlowControlStrategyName(final FlowControlSupplier flowControlSupplier)\n    {\n        return null == flowControlSupplier ?\n            null : C_DRIVER_FLOW_CONTROL_STRATEGY_NAME_BY_TYPE.get(flowControlSupplier.getClass());\n    }\n\n    private static final class ExitStatus\n    {\n        private final int exitCode;\n        private final String exitMessage;\n\n        private ExitStatus(final int exitCode, final String exitMessage)\n        {\n            this.exitCode = exitCode;\n            this.exitMessage = exitMessage;\n        }\n    }\n\n    private ExitStatus terminateDriver()\n    {\n        boolean isInterrupted = false;\n        boolean requestTermination = true;\n        try\n        {\n            try\n            {\n                final int exitCode = aeronMediaDriverProcess.exitValue();\n                String exitMessage = \"Process exited early\";\n                if (!SystemUtil.isWindows())\n                {\n                    final int exitSignal = (0x7F & exitCode); // Essentially the same on Linux and macOS.\n                    if (0 != exitSignal)\n                    {\n                        exitMessage += \" - signal \" + exitSignal;\n                    }\n                }\n                return new ExitStatus(exitCode, exitMessage);\n            }\n            catch (final IllegalThreadStateException ignore)\n            {\n            }\n\n            while (true)\n            {\n                isInterrupted |= Thread.interrupted();\n                try\n                {\n                    if (requestTermination)\n                    {\n                        requestTermination = false;\n                        if (requestDriverTermination() && aeronMediaDriverProcess.waitFor(10, TimeUnit.SECONDS))\n                        {\n                            return new ExitStatus(aeronMediaDriverProcess.exitValue(), \"Process shutdown cleanly\");\n                        }\n                    }\n\n                    aeronMediaDriverProcess.destroyForcibly().waitFor(5, TimeUnit.SECONDS);\n                    final int exitCode = aeronMediaDriverProcess.exitValue();\n                    return new ExitStatus(exitCode, \"Process destroyed forcibly\");\n                }\n                catch (final InterruptedException ex)\n                {\n                    isInterrupted = true;\n                }\n            }\n        }\n        finally\n        {\n            if (isInterrupted)\n            {\n                Thread.currentThread().interrupt();\n            }\n        }\n    }\n\n    private boolean requestDriverTermination()\n    {\n        try\n        {\n            return CommonContext.requestDriverTermination(new File(context.aeronDirectoryName()), null, 0, 0);\n        }\n        catch (final Exception ex)\n        {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/driver/DriverOutputConsumer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.driver;\n\nimport java.io.File;\nimport java.util.Map;\n\npublic interface DriverOutputConsumer\n{\n    default void outputFiles(final String aeronDirectoryName, final File stdoutFile, final File stderrFile)\n    {\n    }\n\n    default void exitCode(final String aeronDirectoryName, final int exitValue, final String exitMessage)\n    {\n    }\n\n    default void environmentVariables(final String aeronDirectoryName, final Map<String, String> environment)\n    {\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/driver/JavaTestMediaDriver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.driver;\n\nimport io.aeron.driver.MediaDriver;\nimport io.aeron.driver.ReceiveChannelEndpointSupplier;\nimport io.aeron.driver.StaticDelayGenerator;\nimport io.aeron.driver.ext.*;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.status.CountersManager;\n\npublic final class JavaTestMediaDriver implements TestMediaDriver\n{\n    private final MediaDriver mediaDriver;\n\n    private JavaTestMediaDriver(final MediaDriver mediaDriver)\n    {\n        this.mediaDriver = mediaDriver;\n    }\n\n    public void close()\n    {\n        mediaDriver.close();\n    }\n\n    public void cleanup()\n    {\n    }\n\n    public static JavaTestMediaDriver launch(final MediaDriver.Context context)\n    {\n        final MediaDriver mediaDriver = MediaDriver.launch(context);\n        return new JavaTestMediaDriver(mediaDriver);\n    }\n\n    public MediaDriver.Context context()\n    {\n        return mediaDriver.context();\n    }\n\n    public String aeronDirectoryName()\n    {\n        return mediaDriver.aeronDirectoryName();\n    }\n\n    public AgentInvoker sharedAgentInvoker()\n    {\n        return mediaDriver.sharedAgentInvoker();\n    }\n\n    public CountersManager counters()\n    {\n        return mediaDriver.context().countersManager();\n    }\n\n    private static void enableLossOnReceive(\n        final MediaDriver.Context context,\n        final LossGenerator dataLossGenerator,\n        final LossGenerator controlLossGenerator)\n    {\n        final ReceiveChannelEndpointSupplier endpointSupplier =\n            (udpChannel, dispatcher, statusIndicator, ctx) ->\n            {\n                return new DebugReceiveChannelEndpoint(\n                    udpChannel, dispatcher, statusIndicator, ctx,\n                    dataLossGenerator == null ? (address, buffer, length) -> false : dataLossGenerator,\n                    controlLossGenerator == null ? (address, buffer, length) -> false : controlLossGenerator);\n            };\n\n        context.receiveChannelEndpointSupplier(endpointSupplier);\n    }\n\n    public static void enableRandomLossOnReceive(\n        final MediaDriver.Context context,\n        final double rate,\n        final long seed,\n        final boolean loseDataMessages,\n        final boolean loseControlMessages)\n    {\n        enableLossOnReceive(\n            context,\n            loseDataMessages ? DebugChannelEndpointConfiguration.lossGeneratorSupplier(rate, seed) : null,\n            loseControlMessages ? DebugChannelEndpointConfiguration.lossGeneratorSupplier(rate, seed) : null);\n    }\n\n    public static void enableFixedLossOnReceive(\n        final MediaDriver.Context context,\n        final int termId,\n        final int termOffset,\n        final int length)\n    {\n        enableLossOnReceive(context, new FixedLossGenerator(termId, termOffset, length), null);\n    }\n\n    public static void enableMultiGapLossOnReceive(\n        final MediaDriver.Context context,\n        final int termId,\n        final int gapRadix,\n        final int gapLength,\n        final int totalGaps)\n    {\n        enableLossOnReceive(context, new MultiGapLossGenerator(termId, gapRadix, gapLength, totalGaps), null);\n    }\n\n    public static void dontCoalesceNaksOnReceiverByDefault(final MediaDriver.Context context)\n    {\n        context.unicastFeedbackDelayGenerator(new StaticDelayGenerator(0, 0));\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/driver/PortLossGenerator.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.driver;\n\nimport io.aeron.driver.ext.LossGenerator;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.net.InetSocketAddress;\n\npublic class PortLossGenerator implements LossGenerator\n{\n    private int port;\n    private long dropUntil;\n    private volatile boolean drop;\n\n    public void startDropping(final int port, final long durationNs)\n    {\n        this.port = port;\n        dropUntil = System.nanoTime() + durationNs;\n        drop = true;\n    }\n\n    public boolean shouldDropFrame(final InetSocketAddress address, final UnsafeBuffer buffer, final int length)\n    {\n        if (drop && port == address.getPort())\n        {\n            if (System.nanoTime() - dropUntil < 0)\n            {\n                return true;\n            }\n            else\n            {\n                drop = false;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/driver/RedirectingNameResolver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.driver;\n\nimport io.aeron.CounterProvider;\nimport io.aeron.driver.DefaultNameResolver;\nimport io.aeron.driver.NameResolver;\nimport org.agrona.ExpandableArrayBuffer;\nimport org.agrona.collections.MutableInteger;\nimport org.agrona.collections.Object2ObjectHashMap;\nimport org.agrona.concurrent.status.AtomicCounter;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport java.net.InetAddress;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static io.aeron.Aeron.NULL_VALUE;\nimport static org.agrona.BitUtil.SIZE_OF_INT;\n\npublic class RedirectingNameResolver implements NameResolver\n{\n    public static final int DISABLE_RESOLUTION = -1;\n    public static final int USE_INITIAL_RESOLUTION_HOST = 0;\n    public static final int USE_RE_RESOLUTION_HOST = 1;\n    public static final int NAME_ENTRY_COUNTER_TYPE_ID = 2001;\n    public static final int EXPECTED_COLUMN_COUNT = 3;\n    private static final String INVALID_HOSTNAME_SENTINEL = \"forced-resolve-failure.invalid\";\n\n    private final Map<String, NameEntry> nameToEntryMap = new Object2ObjectHashMap<>();\n    private final String csvConfiguration;\n\n    public RedirectingNameResolver(final String csvConfiguration)\n    {\n        this.csvConfiguration = csvConfiguration;\n        final String[] lines = csvConfiguration.split(\"\\\\|\");\n        for (final String line : lines)\n        {\n            final String[] params = line.split(\",\");\n            if (EXPECTED_COLUMN_COUNT != params.length)\n            {\n                throw new IllegalArgumentException(\"Expected 3 elements per row\");\n            }\n\n            final NameEntry nameEntry = new NameEntry(params[0], params[1], params[2]);\n            nameToEntryMap.put(nameEntry.name, nameEntry);\n        }\n    }\n\n    public void init(final CountersReader countersReader, final CounterProvider counterProvider)\n    {\n        countersReader.forEach((counterId, typeId, keyBuffer, label) ->\n        {\n            if (typeId == NAME_ENTRY_COUNTER_TYPE_ID && keyBuffer.capacity() > SIZE_OF_INT)\n            {\n                final String entryName = keyBuffer.getStringAscii(0);\n                final NameEntry entry = nameToEntryMap.get(entryName);\n                if (null != entry)\n                {\n                    entry.counter(new AtomicCounter(countersReader.valuesBuffer(), counterId));\n                }\n            }\n        });\n\n        final ExpandableArrayBuffer tmpBuffer = new ExpandableArrayBuffer();\n        for (final NameEntry nameEntry : nameToEntryMap.values())\n        {\n            if (null == nameEntry.counter)\n            {\n                final int keyLength = tmpBuffer.putStringAscii(0, nameEntry.name);\n                final int labelLength = tmpBuffer.putStringWithoutLengthAscii(keyLength, nameEntry.toString());\n\n                final AtomicCounter atomicCounter = counterProvider.newCounter(\n                    NAME_ENTRY_COUNTER_TYPE_ID,\n                    tmpBuffer,\n                    0,\n                    keyLength,\n                    tmpBuffer,\n                    keyLength,\n                    labelLength);\n                nameEntry.counter(atomicCounter);\n            }\n        }\n    }\n\n    public String lookup(final String name, final String uriParamName, final boolean isReLookup)\n    {\n        return name.endsWith(\":X\") ? name.substring(0, name.length() - 2) : name;\n    }\n\n    public InetAddress resolve(final String name, final String uriParamName, final boolean isReResolution)\n    {\n        final NameEntry nameEntry = nameToEntryMap.get(name);\n        final String hostname = null != nameEntry ? nameEntry.redirectHost(name) : name;\n\n        InetAddress resolvedAddress = null;\n        if (!Objects.equals(INVALID_HOSTNAME_SENTINEL, hostname))\n        {\n            resolvedAddress = DefaultNameResolver.INSTANCE.resolve(hostname, uriParamName, isReResolution);\n        }\n\n        return resolvedAddress;\n    }\n\n    public String csvConfiguration()\n    {\n        return csvConfiguration;\n    }\n\n    public static boolean updateNameResolutionStatus(\n        final CountersReader counters,\n        final String hostname,\n        final int operationValue)\n    {\n        final MutableInteger nameCounterId = new MutableInteger(NULL_VALUE);\n        counters.forEach((counterId, typeId, keyBuffer, label) ->\n        {\n            if (typeId == NAME_ENTRY_COUNTER_TYPE_ID && hostname.equals(keyBuffer.getStringAscii(0)))\n            {\n                nameCounterId.set(counterId);\n            }\n        });\n\n        final boolean counterFound = NULL_VALUE != nameCounterId.get();\n        if (counterFound)\n        {\n            final AtomicCounter nameCounter = new AtomicCounter(counters.valuesBuffer(), nameCounterId.get());\n\n            nameCounter.set(operationValue);\n        }\n\n        return counterFound;\n    }\n\n    static final class NameEntry\n    {\n        private final String name;\n        private final String initialResolutionHost;\n        private final String reResolutionHost;\n        private AtomicCounter counter;\n\n        NameEntry(final String name, final String initialResolutionHost, final String reResolutionHost)\n        {\n            this.name = name;\n            this.initialResolutionHost = initialResolutionHost;\n            this.reResolutionHost = reResolutionHost;\n        }\n\n        void counter(final AtomicCounter counter)\n        {\n            this.counter = counter;\n        }\n\n        String redirectHost(final String name)\n        {\n            final long operation = null != counter ? counter.get() : USE_INITIAL_RESOLUTION_HOST;\n            if (DISABLE_RESOLUTION == operation)\n            {\n                return INVALID_HOSTNAME_SENTINEL;\n            }\n            else if (USE_INITIAL_RESOLUTION_HOST == operation)\n            {\n                return initialResolutionHost;\n            }\n            else if (USE_RE_RESOLUTION_HOST == operation)\n            {\n                return reResolutionHost;\n            }\n            else\n            {\n                return name;\n            }\n        }\n\n        public String toString()\n        {\n            return \"NameEntry{\" +\n                \"name='\" + name + '\\'' +\n                \", initialResolutionHost='\" + initialResolutionHost + '\\'' +\n                \", reResolutionHost='\" + reResolutionHost + \"'}\";\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/driver/StreamIdLossGenerator.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.driver;\n\nimport io.aeron.driver.ext.LossGenerator;\nimport org.agrona.concurrent.UnsafeBuffer;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.VarHandle;\nimport java.net.InetSocketAddress;\n\npublic final class StreamIdLossGenerator implements LossGenerator\n{\n    private static final VarHandle ENABLED_VH;\n\n    static\n    {\n        try\n        {\n            ENABLED_VH = MethodHandles.lookup().findVarHandle(StreamIdLossGenerator.class, \"enabled\", boolean.class);\n        }\n        catch (final NoSuchFieldException | IllegalAccessException e)\n        {\n            throw new Error(e);\n        }\n    }\n\n    private int streamId;\n    private volatile boolean enabled;\n\n    public void enable(final int streamId)\n    {\n        this.streamId = streamId;\n        ENABLED_VH.setRelease(this, true);\n    }\n\n    public void disable()\n    {\n        ENABLED_VH.setRelease(this, false);\n    }\n\n    public boolean shouldDropFrame(\n        final InetSocketAddress address,\n        final UnsafeBuffer buffer,\n        final int streamId,\n        final int sessionId,\n        final int termId,\n        final int termOffset,\n        final int length)\n    {\n        return (boolean)ENABLED_VH.getAcquire(this) && streamId == this.streamId;\n    }\n\n    public boolean shouldDropFrame(final InetSocketAddress address, final UnsafeBuffer buffer, final int length)\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/driver/TestMediaDriver.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.driver;\n\nimport io.aeron.driver.MediaDriver;\nimport org.agrona.concurrent.AgentInvoker;\nimport org.agrona.concurrent.status.CountersReader;\n\nimport static org.agrona.Strings.isEmpty;\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\n\npublic interface TestMediaDriver extends AutoCloseable\n{\n    String AERONMD_PATH_PROP_NAME = \"aeron.test.system.aeronmd.path\";\n    String ATS_LIBRARY_PATH_PROP_NAME = \"aeron.test.system.ats.path\";\n    String ATS_LIBRARY_CONF_PATH_PROP_NAME = \"aeron.test.system.ats.conf.dir\";\n    String ATS_LIBRARY_CONF_FILE_PROP_NAME = \"aeron.test.system.ats.conf.file\";\n    String DRIVER_AGENT_PATH_PROP_NAME = \"aeron.test.system.driver.agent.path\";\n\n    static boolean shouldRunCMediaDriver()\n    {\n        return !isEmpty(System.getProperty(AERONMD_PATH_PROP_NAME));\n    }\n\n    static boolean shouldRunJavaMediaDriver()\n    {\n        return !shouldRunCMediaDriver();\n    }\n\n    static void notSupportedOnCMediaDriver(final String reason)\n    {\n        assumeFalse(shouldRunCMediaDriver(), () -> \"not support by C Media Driver: \" + reason);\n    }\n\n    static TestMediaDriver launch(final MediaDriver.Context context, final DriverOutputConsumer driverOutputConsumer)\n    {\n        return shouldRunCMediaDriver() ?\n            CTestMediaDriver.launch(context, true, driverOutputConsumer) : JavaTestMediaDriver.launch(context);\n    }\n\n    static void enableRandomLoss(\n        final MediaDriver.Context context,\n        final double rate,\n        final long seed,\n        final boolean loseDataMessages,\n        final boolean loseControlMessages)\n    {\n        if (shouldRunCMediaDriver())\n        {\n            CTestMediaDriver.enableRandomLossOnReceive(context, rate, seed, loseDataMessages, loseControlMessages);\n        }\n        else\n        {\n            JavaTestMediaDriver.enableRandomLossOnReceive(\n                context, rate, seed, loseDataMessages, loseControlMessages);\n        }\n    }\n\n    static void enableFixedLoss(\n        final MediaDriver.Context context,\n        final int termId,\n        final int termOffset,\n        final int length)\n    {\n        if (shouldRunCMediaDriver())\n        {\n            CTestMediaDriver.enableFixedLossOnReceive(context, termId, termOffset, length);\n        }\n        else\n        {\n            JavaTestMediaDriver.enableFixedLossOnReceive(context, termId, termOffset, length);\n        }\n    }\n\n    static void enableMultiGapLoss(\n        final MediaDriver.Context context,\n        final int termId,\n        final int gapRadix,\n        final int gapLength,\n        final int totalGaps)\n    {\n        if (shouldRunCMediaDriver())\n        {\n            CTestMediaDriver.enableMultiGapLossOnReceive(context, termId, gapRadix, gapLength, totalGaps);\n        }\n        else\n        {\n            JavaTestMediaDriver.enableMultiGapLossOnReceive(context, termId, gapRadix, gapLength, totalGaps);\n        }\n    }\n\n    static void dontCoalesceNaksOnReceiverByDefault(final MediaDriver.Context context)\n    {\n        if (shouldRunCMediaDriver())\n        {\n            CTestMediaDriver.dontCoalesceNaksOnReceiverByDefault(context);\n        }\n        else\n        {\n            JavaTestMediaDriver.dontCoalesceNaksOnReceiverByDefault(context);\n        }\n    }\n\n    MediaDriver.Context context();\n\n    String aeronDirectoryName();\n\n    void close();\n\n    void cleanup();\n\n    AgentInvoker sharedAgentInvoker();\n\n    CountersReader counters();\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/launcher/FileResolveUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.launcher;\n\nimport java.io.File;\nimport java.util.Arrays;\n\npublic class FileResolveUtil\n{\n    public static File resolveProjectRoot()\n    {\n        final File workingDir = new File(System.getProperty(\"user.dir\"));\n        File parent = workingDir;\n\n        do\n        {\n            final String[] versionTxtFile = parent.list((dir, name) -> \"version.txt\".equals(name));\n            if (null != versionTxtFile && 1 == versionTxtFile.length)\n            {\n                return parent;\n            }\n\n            parent = workingDir.getParentFile();\n        }\n        while (null != parent);\n\n        throw new RuntimeException(\"unable to find project root directory from: \" + workingDir);\n    }\n\n    public static File resolveAeronAllJar()\n    {\n        return resolveAeronJar(\"aeron-all\", false);\n    }\n\n    public static File resolveAeronAgentJar()\n    {\n        return resolveAeronJar(\"aeron-agent\", true);\n    }\n\n    private static File resolveAeronJar(final String moduleName, final boolean allowMultipleFiles)\n    {\n        final File projectRoot = resolveProjectRoot();\n\n        final File allBuildLibs = new File(projectRoot, moduleName + \"/build/libs\");\n        if (!allBuildLibs.exists())\n        {\n            throw new RuntimeException(\"directory: \" + allBuildLibs + \" does not exist\");\n        }\n\n        final File[] aeronAllJarFiles = allBuildLibs.listFiles(\n            (dir, name) ->\n            name.startsWith(moduleName + \"-\") &&\n            name.endsWith(\".jar\") &&\n            !name.endsWith(\"-sources.jar\") &&\n            !name.endsWith(\"-javadoc.jar\"));\n\n        if (null == aeronAllJarFiles || 0 == aeronAllJarFiles.length)\n        {\n            throw new RuntimeException(\"unable to find aeron jar files in directory: \" + allBuildLibs);\n        }\n\n        if (!allowMultipleFiles && 1 != aeronAllJarFiles.length)\n        {\n            throw new RuntimeException(\n                \"multiple libs found, run './gradlew clean': \" + Arrays.toString(aeronAllJarFiles));\n        }\n\n        return aeronAllJarFiles[0];\n    }\n\n    public static File resolveJavaBinary()\n    {\n        final File javaHome = new File(System.getProperty(\"java.home\"));\n        if (!javaHome.exists())\n        {\n            throw new RuntimeException(\"java.home: \" + javaHome + \" does not exist??\");\n        }\n\n        final File javaBinary = new File(javaHome, \"bin/java\");\n        if (!javaBinary.exists())\n        {\n            throw new RuntimeException(\"java binary: \" + javaBinary + \" does not exist??\");\n        }\n\n        return javaBinary;\n    }\n\n    public static File resolveClusterScriptDir()\n    {\n        return new File(resolveProjectRoot(), \"aeron-samples/scripts/cluster\");\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/launcher/RemoteLaunchClient.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.launcher;\n\nimport io.aeron.driver.Configuration;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectOutputStream;\nimport java.io.OutputStream;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.ReadableByteChannel;\nimport java.nio.channels.SocketChannel;\n\npublic final class RemoteLaunchClient implements AutoCloseable\n{\n    private final String host;\n    private final int port;\n    private SocketChannel clientChannel;\n\n    private RemoteLaunchClient(final String host, final int port)\n    {\n        this.host = host;\n        this.port = port;\n    }\n\n    public static RemoteLaunchClient connect(final String host, final int port) throws IOException\n    {\n        final RemoteLaunchClient remoteLaunchClient = new RemoteLaunchClient(host, port);\n        remoteLaunchClient.init();\n\n        return remoteLaunchClient;\n    }\n\n    private void init() throws IOException\n    {\n        clientChannel = SocketChannel.open();\n        clientChannel.socket().connect(new InetSocketAddress(host, port), 5_000);\n    }\n\n    public ReadableByteChannel execute(final String... command) throws IOException\n    {\n        return execute(true, command);\n    }\n\n    public SocketChannel execute(final boolean usingBlockingIo, final String... command) throws IOException\n    {\n        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n        try (ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream))\n        {\n            out.writeObject(command);\n        }\n\n        final ByteBuffer buffer = ByteBuffer.allocateDirect(byteArrayOutputStream.size());\n        buffer.put(byteArrayOutputStream.toByteArray());\n        buffer.flip();\n        clientChannel.write(buffer);\n\n        clientChannel.configureBlocking(usingBlockingIo);\n\n        return clientChannel;\n    }\n\n    public void executeBlocking(final OutputStream out, final String... command) throws IOException\n    {\n        try (ReadableByteChannel commandResponse = execute(command))\n        {\n            final ByteBuffer buffer = ByteBuffer.allocate(Configuration.filePageSize());\n\n            while (commandResponse.isOpen())\n            {\n                final int read = commandResponse.read(buffer);\n                if (read < 0)\n                {\n                    break;\n                }\n\n                buffer.flip();\n                out.write(buffer.array(), buffer.position(), buffer.limit());\n                buffer.clear();\n            }\n        }\n    }\n\n    public void close() throws IOException\n    {\n        clientChannel.close();\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/main/java/io/aeron/test/launcher/RemoteLaunchServer.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.launcher;\n\nimport io.aeron.test.NullOutputStream;\nimport org.agrona.CloseHelper;\n\nimport java.io.*;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.AsynchronousCloseException;\nimport java.nio.channels.Channels;\nimport java.nio.channels.ServerSocketChannel;\nimport java.nio.channels.SocketChannel;\nimport java.util.Collection;\nimport java.util.concurrent.ConcurrentLinkedDeque;\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic class RemoteLaunchServer\n{\n    private final ServerSocketChannel serverChannel;\n    private final Collection<Connection> connections = new ConcurrentLinkedDeque<>();\n\n    public static void main(final String[] args) throws IOException\n    {\n        final String host = System.getProperty(\"aeron.test.launch.host\", \"0.0.0.0\");\n        final int port = Integer.getInteger(\"aeron.test.launch.port\", 11112);\n\n        final RemoteLaunchServer server = new RemoteLaunchServer(host, port);\n        Runtime.getRuntime().addShutdownHook(new Thread(server::close));\n\n        server.run();\n    }\n\n    public RemoteLaunchServer(final String host, final int port) throws IOException\n    {\n        serverChannel = ServerSocketChannel.open();\n        final InetSocketAddress local = new InetSocketAddress(host, port);\n        serverChannel.bind(local);\n    }\n\n    public void run()\n    {\n        Thread.setDefaultUncaughtExceptionHandler(\n            (t, e) ->\n            {\n                System.err.println(\"Uncaught exception on: \" + t);\n                e.printStackTrace(System.out);\n            });\n\n        try\n        {\n            while (!Thread.currentThread().isInterrupted())\n            {\n                final SocketChannel connectionChannel = serverChannel.accept();\n                final Connection connection = new Connection(connectionChannel);\n                connection.start();\n                connections.add(connection);\n            }\n        }\n        catch (final AsynchronousCloseException ignore)\n        {\n            // Normal close\n        }\n        catch (final IOException ex)\n        {\n            throw new RuntimeException(ex);\n        }\n    }\n\n    public void close()\n    {\n        try\n        {\n            serverChannel.close();\n        }\n        catch (final IOException e)\n        {\n            e.printStackTrace(System.out);\n        }\n\n        connections.forEach(Connection::stop);\n    }\n\n    static class Connection\n    {\n        private final SocketChannel connectionChannel;\n        private final AtomicReference<State> currentState = new AtomicReference<>(State.CREATED);\n        private volatile ProcessResponseReader responseReader;\n        private Process process = null;\n\n        private enum State\n        {\n            CREATED,\n            STARTING,\n            PENDING,\n            RUNNING,\n            CLOSING\n        }\n\n        Connection(final SocketChannel connectionChannel)\n        {\n            this.connectionChannel = connectionChannel;\n        }\n\n        public void start()\n        {\n            final Thread requestThread = new Thread(this::runRequests);\n            if (currentState.compareAndSet(State.CREATED, State.STARTING))\n            {\n                requestThread.start();\n            }\n        }\n\n        public void stop()\n        {\n            final State state = this.currentState.get();\n            do\n            {\n                switch (state)\n                {\n                    case CREATED:\n                        if (currentState.compareAndSet(State.CREATED, State.CLOSING))\n                        {\n                            return;\n                        }\n                        break;\n\n                    case STARTING:\n                        break;\n\n                    case PENDING:\n                        if (currentState.compareAndSet(State.PENDING, State.CLOSING))\n                        {\n                            try\n                            {\n                                connectionChannel.close();\n                            }\n                            catch (final IOException e)\n                            {\n                                e.printStackTrace(System.out);\n                            }\n                            return;\n                        }\n                        break;\n\n                    case RUNNING:\n                        if (currentState.compareAndSet(State.RUNNING, State.CLOSING))\n                        {\n                            responseReader.markClosed();\n                            try\n                            {\n                                connectionChannel.close();\n                            }\n                            catch (final IOException e)\n                            {\n                                e.printStackTrace(System.out);\n                            }\n                            process.destroy();\n                            return;\n                        }\n                        break;\n\n                    case CLOSING:\n                        return;\n                }\n            }\n            while (true);\n        }\n\n        private void runRequests()\n        {\n            try\n            {\n                if (!currentState.compareAndSet(State.STARTING, State.PENDING))\n                {\n                    throw new IllegalStateException(\"Should not happen\");\n                }\n\n                try (ObjectInputStream in = new ObjectInputStream(Channels.newInputStream(connectionChannel)))\n                {\n                    while (!Thread.currentThread().isInterrupted())\n                    {\n                        final Object o = in.readObject();\n                        if (o instanceof String[])\n                        {\n                            final State state = currentState.get();\n                            switch (state)\n                            {\n                                case CREATED:\n                                    break;\n\n                                case PENDING:\n                                    if (!currentState.compareAndSet(State.PENDING, State.STARTING))\n                                    {\n                                        return;\n                                    }\n                                    currentState.set(startProcess((String[])o));\n                                    break;\n\n                                case STARTING:\n                                    throw new IllegalStateException(\"Should not happen\");\n\n                                case RUNNING:\n                                    // Allow submission of more commands???\n                                    break;\n\n                                case CLOSING:\n                                    return;\n                            }\n                        }\n                        else\n                        {\n                            if (currentState.compareAndSet(State.RUNNING, State.CLOSING) && null != process)\n                            {\n                                responseReader.markClosed();\n                                CloseHelper.close(connectionChannel);\n                                process.destroy();\n                            }\n                        }\n                    }\n                }\n            }\n            catch (final EOFException ex)\n            {\n                if (currentState.compareAndSet(State.RUNNING, State.CLOSING) && null != process)\n                {\n                    responseReader.markClosed();\n                    CloseHelper.close(connectionChannel);\n                    process.destroy();\n                }\n            }\n            catch (final AsynchronousCloseException ex)\n            {\n                if (currentState.compareAndSet(State.RUNNING, State.CLOSING) && null != process)\n                {\n                    responseReader.markClosed();\n                    process.destroy();\n                }\n            }\n            catch (final IOException | ClassNotFoundException ex)\n            {\n                System.out.println(\"Error occurred\");\n                ex.printStackTrace(System.out);\n\n                if (currentState.compareAndSet(State.RUNNING, State.CLOSING) && null != process)\n                {\n                    responseReader.markClosed();\n                    CloseHelper.close(connectionChannel);\n                    process.destroy();\n                }\n            }\n        }\n\n        private long pid()\n        {\n            return ProcessHandle.current().pid();\n        }\n\n        private State startProcess(final String[] command) throws IOException\n        {\n            try\n            {\n                final Process p = new ProcessBuilder(command)\n                    .redirectErrorStream(true)\n                    .start();\n                process = p;\n\n                final long startTimeMs = System.currentTimeMillis();\n                System.out.println(\"[\" + pid() + \"] Started: \" + String.join(\" \", command));\n                final PrintStream stdOutputStream = parseBaseDirectory(command);\n\n                responseReader = new ProcessResponseReader(connectionChannel, pid(), stdOutputStream);\n                final Thread responseThread = new Thread(() -> responseReader.runResponses(p.getInputStream()));\n                responseThread.setDaemon(true);\n                responseThread.start();\n\n                final Thread processMonitorThread = new Thread(\n                    () ->\n                    {\n                        try\n                        {\n                            final int exitCode = process.waitFor();\n                            final long endTimeMs = System.currentTimeMillis();\n                            System.out.println(\n                                \"[\" + pid() + \"] Exited with code: \" + exitCode +\n                                \" after: \" + (endTimeMs - startTimeMs) + \"ms\");\n                        }\n                        catch (final InterruptedException ex)\n                        {\n                            System.out.println(\"[\" + pid() + \"] Unexpected exception waiting on exit code\");\n                            ex.printStackTrace(System.out);\n                        }\n                    });\n                processMonitorThread.start();\n\n                return State.RUNNING;\n            }\n            catch (final IOException ex)\n            {\n                final ByteArrayOutputStream baos = new ByteArrayOutputStream();\n                ex.printStackTrace(new PrintStream(baos));\n                connectionChannel.write(ByteBuffer.wrap(baos.toByteArray()));\n                connectionChannel.close();\n\n                if (!currentState.compareAndSet(State.STARTING, State.CLOSING))\n                {\n                    throw new IllegalStateException(\"Should not happen\");\n                }\n\n                return State.CLOSING;\n            }\n        }\n\n        private PrintStream parseBaseDirectory(final String[] command)\n        {\n            final String baseDirPrefix = \"-Daeron.cluster.tutorial.baseDir=\";\n            for (final String arg : command)\n            {\n                final String trimmedArg = arg.trim();\n                if (trimmedArg.startsWith(baseDirPrefix))\n                {\n                    final File file = new File(trimmedArg.substring(baseDirPrefix.length()), \"command.out\");\n                    try\n                    {\n                        return new PrintStream(file.getAbsolutePath(), \"UTF-8\");\n                    }\n                    catch (final Exception ex)\n                    {\n                        System.err.println(\"Failed to create stream for command std: \" + ex.getMessage());\n                    }\n                }\n            }\n\n            return new PrintStream(new NullOutputStream());\n        }\n    }\n\n    private static final class ProcessResponseReader\n    {\n        private final SocketChannel connectionChannel;\n        private final long pid;\n        private final PrintStream stdOutputStream;\n        private volatile boolean isClosed = false;\n\n        private ProcessResponseReader(\n            final SocketChannel connectionChannel,\n            final long pid,\n            final PrintStream stdOutputStream)\n        {\n            this.connectionChannel = connectionChannel;\n            this.pid = pid;\n            this.stdOutputStream = stdOutputStream;\n        }\n\n        private void runResponses(final InputStream processOutput)\n        {\n            final ByteBuffer data = ByteBuffer.allocate(1024);\n            try\n            {\n                while (!isClosed)\n                {\n                    final int read = processOutput.read(data.array());\n                    if (-1 != read)\n                    {\n                        data.position(0).limit(read);\n                        connectionChannel.write(data);\n                        stdOutputStream.write(data.array(), 0, read);\n                    }\n                    else\n                    {\n                        connectionChannel.close();\n                        break;\n                    }\n                }\n            }\n            catch (final IOException e)\n            {\n                if (!isClosed)\n                {\n                    throw new RuntimeException(e);\n                }\n                else\n                {\n                    System.out.println(\"[\" + pid + \"] Process closed\");\n                }\n            }\n            finally\n            {\n                CloseHelper.quietClose(stdOutputStream);\n            }\n        }\n\n        public void markClosed()\n        {\n            isClosed = true;\n        }\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/test/java/io/aeron/test/DataCollectorTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static io.aeron.test.DataCollector.THREAD_DUMP_FILE_NAME;\nimport static io.aeron.test.DataCollector.UNIQUE_ID;\nimport static java.nio.file.Files.*;\nimport static org.agrona.collections.ArrayUtil.EMPTY_BYTE_ARRAY;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass DataCollectorTest\n{\n    @Test\n    void throwsNullPointerExceptionIfTargetDirectoryIsNull()\n    {\n        assertThrows(NullPointerException.class, () -> new DataCollector(null));\n    }\n\n    @Test\n    void throwsIllegalArgumentExceptionIfTargetDirectoryIsAFile(final @TempDir Path tempDir) throws IOException\n    {\n        final Path file = createFile(tempDir.resolve(\"my.txt\"));\n        final IllegalArgumentException exception = assertThrows(\n            IllegalArgumentException.class, () -> new DataCollector(file));\n\n        assertEquals(file + \" is not a directory\", exception.getMessage());\n    }\n\n    @Test\n    void addFileThrowsNullPointerExceptionIfFileIsNull()\n    {\n        final DataCollector dataCollector = new DataCollector();\n        assertThrows(NullPointerException.class, () -> dataCollector.add((Path)null));\n    }\n\n    @Test\n    void dumpDataUsingDirectoryNameThrowsIllegalArgumentExceptionIfNull()\n    {\n        final DataCollector dataCollector = new DataCollector();\n        assertThrows(IllegalArgumentException.class, () -> dataCollector.dumpData(null, EMPTY_BYTE_ARRAY));\n    }\n\n    @Test\n    void dumpDataUsingDirectoryNameThrowsIllegalArgumentExceptionIfEmpty()\n    {\n        final DataCollector testWatcher = new DataCollector();\n        assertThrows(IllegalArgumentException.class, () -> testWatcher.dumpData(\"\", EMPTY_BYTE_ARRAY));\n    }\n\n    @Test\n    void dumpDataUsingDirectoryPathThrowsNullPointerExceptionIfThreadDumpIsNull(final @TempDir Path tempDir)\n    {\n        final DataCollector dataCollector = new DataCollector(tempDir);\n        assertThrowsExactly(NullPointerException.class, () -> dataCollector.dumpData(\"test\", null));\n    }\n\n    @Test\n    void dumpDataUsingDirectoryName(final @TempDir Path tempDir) throws Exception\n    {\n        testDumpDataUsingDirectoryName(tempDir);\n    }\n\n    @Test\n    void dumpDataUsingDirectoryNameShouldHandleThreadInterrupt(final @TempDir Path tempDir) throws Exception\n    {\n        Thread.currentThread().interrupt();\n\n        testDumpDataUsingDirectoryName(tempDir);\n\n        assertTrue(Thread.interrupted());\n    }\n\n    @Test\n    void dumpDataUsingDirectoryNameIsANoOpIfNoFilesRegistered(final @TempDir Path tempDir)\n    {\n        final Path rootDirectory = tempDir.resolve(\"no-copy\");\n        final DataCollector dataCollector = new DataCollector(rootDirectory);\n\n        assertNull(dataCollector.dumpData(\"some-dir\", EMPTY_BYTE_ARRAY));\n\n        assertFalse(exists(rootDirectory));\n    }\n\n    @Test\n    void dumpDataUsingDirectoryNameShouldProduceAThreadDump(final @TempDir Path tempDir) throws IOException\n    {\n        final Path rootDirectory = tempDir.resolve(\"thread-dump\");\n        final DataCollector dataCollector = new DataCollector(rootDirectory);\n        dataCollector.add(createFile(tempDir.resolve(\"my.txt\")));\n        final byte[] threads = new byte[32];\n        ThreadLocalRandom.current().nextBytes(threads);\n\n        final Path destination = dataCollector.dumpData(\"my-out-dir\", threads);\n\n        assertNotNull(destination);\n        assertTrue(exists(destination));\n        assertTrue(exists(destination.resolve(\"my.txt\")));\n        final Path threadDump = destination.resolve(THREAD_DUMP_FILE_NAME);\n        assertArrayEquals(threads, Files.readAllBytes(threadDump));\n    }\n\n    private void testDumpDataUsingDirectoryName(final Path tempDir) throws IOException\n    {\n        final Path rootDir = tempDir.resolve(\"copy-root\");\n        createDirectories(rootDir.resolve(\"destination\"));\n\n        final Path file0 = createFile(tempDir.resolve(\"my.txt\"));\n        final Path nonExistingDir = createDirectories(tempDir.resolve(\"non-existing/folder\"));\n        final Path file1 = createFile(nonExistingDir.resolve(\"some.png\"));\n        final Path dir1 = createDirectories(tempDir.resolve(\"my-dir/nested/again\"));\n        final Path dir2 = createDirectories(tempDir.resolve(\"again\"));\n        final Path dir3 = createDirectories(tempDir.resolve(\"nested/again\"));\n        final Path dir4 = createDirectories(tempDir.resolve(\"level1/level2/level3/again\"));\n        createFile(dir1.resolve(\"file1.txt\"));\n        createFile(dir2.resolve(\"file2.txt\"));\n        createFile(dir3.resolve(\"file3.txt\"));\n        createFile(dir4.resolve(\"file4.txt\"));\n\n        final DataCollector dataCollector = new DataCollector(rootDir);\n        dataCollector.add(file0);\n        dataCollector.add(tempDir.resolve(\"my-dir\"));\n        dataCollector.add(tempDir.resolve(\"again\"));\n        dataCollector.add(tempDir.resolve(\"nested\"));\n        dataCollector.add(dir4);\n        dataCollector.add(file1);\n\n        final Path destination = dataCollector.dumpData(\"destination\", EMPTY_BYTE_ARRAY);\n\n        assertNotNull(destination);\n        assertTrue(exists(destination));\n        assertEquals(rootDir.resolve(\"destination-\" + UNIQUE_ID.get()), destination);\n        assertTrue(exists(destination.resolve(\"my.txt\")));\n        assertTrue(exists(destination.resolve(\"my-dir/nested/again/file1.txt\")));\n        assertTrue(exists(destination.resolve(\"again/file2.txt\")));\n        assertTrue(exists(destination.resolve(\"nested/again/file3.txt\")));\n        assertTrue(exists(destination.resolve(\"level1/level2/level3/again/file4.txt\")));\n        assertTrue(exists(destination.resolve(\"non-existing/folder/some.png\")));\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/test/java/io/aeron/test/ThreadNamingCallbackTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport static org.hamcrest.CoreMatchers.not;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsString;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n/**\n * This has some disabled tests as they are evaluating behaviour of the callback when tests fail, so enabling them\n * will cause the full build to fail. The tests are there, so that the code can be easily tested manually.\n */\n@ExtendWith(ThreadNamingTestCallback.class)\npublic class ThreadNamingCallbackTest\n{\n    private String threadName = \"unset\";\n\n    @BeforeEach\n    void setUp()\n    {\n        threadName = Thread.currentThread().getName();\n        assertThat(threadName, not(containsString(\"TEST\")));\n    }\n\n    @Test\n    void testThatSucceeds()\n    {\n        assertThat(Thread.currentThread().getName(), containsString(\"testThatSucceeds\"));\n    }\n\n    @Test\n    @Disabled\n    void testThatFails()\n    {\n        assertThat(Thread.currentThread().getName(), containsString(this.getClass().getSimpleName()));\n        assertThat(Thread.currentThread().getName(), containsString(\"testThatFails\"));\n        fail(\"forced failure\");\n    }\n\n    @Test\n    @Disabled\n    void testThatThrowsCheckedException() throws Exception\n    {\n        assertThat(Thread.currentThread().getName(), containsString(this.getClass().getSimpleName()));\n        assertThat(Thread.currentThread().getName(), containsString(\"testThatThrowsCheckedException\"));\n        throw new Exception(\"forced failure\");\n    }\n\n    @Test\n    @Disabled\n    void testThatThrowsUncheckedException()\n    {\n        assertThat(Thread.currentThread().getName(), containsString(this.getClass().getSimpleName()));\n        assertThat(Thread.currentThread().getName(), containsString(\"testThatThrowsUncheckedException\"));\n        throw new RuntimeException(\"forced failure\");\n    }\n\n    @AfterEach\n    void tearDown()\n    {\n        assertNotEquals(threadName, Thread.currentThread().getName());\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/test/java/io/aeron/test/cluster/TestClusterClockTest.java",
    "content": "/*\n * Copyright 2025 Adaptive Financial Consulting Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.cluster;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nclass TestClusterClockTest\n{\n    @Test\n    void testMillisecondClock()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.MILLISECONDS);\n        assertEquals(TimeUnit.MILLISECONDS, clock.timeUnit());\n\n        clock.update(1765290339569L, TimeUnit.MILLISECONDS);\n        assertEquals(1765290339569L, clock.time());\n        assertEquals(1765290339569L, clock.timeMillis());\n        assertEquals(1765290339569L, clock.asEpochClock().time());\n        assertEquals(1765290339569000L, clock.timeMicros());\n        assertEquals(1765290339569000000L, clock.timeNanos());\n        assertEquals(1765290339569000000L, clock.nanoTime());\n\n        clock.update(1765383800L, TimeUnit.SECONDS);\n        assertEquals(1765383800000L, clock.time());\n\n        clock.increment(3, TimeUnit.SECONDS);\n        assertEquals(1765383803000L, clock.time());\n\n        clock.increment(1);\n        assertEquals(1765383803001L, clock.time());\n    }\n\n    @Test\n    void testNanosecondClock()\n    {\n        final TestClusterClock clock = new TestClusterClock(TimeUnit.NANOSECONDS);\n        assertEquals(TimeUnit.NANOSECONDS, clock.timeUnit());\n\n        clock.update(1765383558894627344L, TimeUnit.NANOSECONDS);\n        assertEquals(1765383558894627344L, clock.time());\n        assertEquals(1765383558894627344L, clock.timeNanos());\n        assertEquals(1765383558894627344L, clock.nanoTime());\n        assertEquals(1765383558894627L, clock.timeMicros());\n        assertEquals(1765383558894L, clock.timeMillis());\n        assertEquals(1765383558894L, clock.asEpochClock().time());\n\n        clock.update(1765384128532L, TimeUnit.MILLISECONDS);\n        assertEquals(1765384128532000000L, clock.time());\n\n        clock.increment(123, TimeUnit.MICROSECONDS);\n        assertEquals(1765384128532123000L, clock.time());\n\n        clock.increment(1);\n        assertEquals(1765384128532123001L, clock.time());\n    }\n}\n"
  },
  {
    "path": "aeron-test-support/src/test/java/io/aeron/test/launcher/RemoteLauncherTest.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.test.launcher;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.Arrays;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n@EnabledOnOs(OS.LINUX)\npublic class RemoteLauncherTest\n{\n    @Test\n    @Disabled\n    void shouldLaunchShortLivedCommand() throws IOException, InterruptedException\n    {\n        final RemoteLaunchServer server = new RemoteLaunchServer(\"localhost\", 11112);\n        final Thread t = new Thread(server::run);\n        t.start();\n        final RemoteLaunchClient client = RemoteLaunchClient.connect(\"localhost\", 11112);\n        try\n        {\n            final String message = \"helloworld\";\n            assertTimeoutPreemptively(\n                Duration.ofMillis(5_000),\n                () ->\n                {\n                    final ByteArrayOutputStream out = new ByteArrayOutputStream();\n                    client.executeBlocking(out, \"echo\", message);\n\n                    final byte[] expectedBytes = message.getBytes(StandardCharsets.UTF_8);\n                    final byte[] actualBytes = Arrays.copyOf(out.toByteArray(), expectedBytes.length);\n                    assertArrayEquals(expectedBytes, actualBytes, out + \" vs \" + new String(actualBytes));\n                }\n            );\n        }\n        finally\n        {\n            server.close();\n            t.join();\n        }\n    }\n\n    @Test\n    void shouldResolveAeronAllJar()\n    {\n        assertNotNull(FileResolveUtil.resolveAeronAllJar());\n    }\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nplugins {\n    id 'java-library'\n    alias(libs.plugins.shadow).apply(false)\n    alias(libs.plugins.versions)\n}\n\ndefaultTasks 'clean', 'build'\n\nstatic def rawBuildJavaVersion()\n{\n    return System.getenv('BUILD_JAVA_VERSION') ?: JavaVersion.current().getMajorVersion()\n}\nboolean isEarlyAccessJavaVersion = rawBuildJavaVersion().endsWith(\"-ea\")\n\nstatic def getBuildJavaVersion() {\n    def buildJavaVersion = rawBuildJavaVersion()\n\n    if (buildJavaVersion.indexOf('.') > 0) {\n        buildJavaVersion = buildJavaVersion.substring(0, buildJavaVersion.indexOf('.'))\n    }\n\n    if (buildJavaVersion.indexOf('-') > 0) {\n        buildJavaVersion = buildJavaVersion.substring(0, buildJavaVersion.indexOf('-'))\n    }\n\n    Integer.parseInt(buildJavaVersion)\n}\nint buildJavaVersion = getBuildJavaVersion()\n\ndef toolchainLauncher = javaToolchains.launcherFor {\n    languageVersion = JavaLanguageVersion.of(buildJavaVersion)\n}\n\ndef aeronGroup = 'io.aeron'\ndef aeronVersion = file('version.txt').text.trim()\ndef gitCommitHash = io.aeron.build.GithubUtil.currentGitHash(\"${projectDir}\")\n\ndef getConfigProperty(final String projectPropertyName, final String envVarName) {\n    String value = project.findProperty(projectPropertyName)\n    if (!value) {\n        value = System.getenv(envVarName)\n        if (!value) {\n            return null\n        }\n    }\n\n    value = value.trim()\n\n    return value ? value : null\n}\n\next {\n    isReleaseVersion = !aeronVersion.endsWith('-SNAPSHOT')\n\n    sonatypeCentralPortalReleasesRepoUrl = 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/'\n    sonatypeCentralPortalSnapshotsRepoUrl = 'https://central.sonatype.com/repository/maven-snapshots/'\n    sonatypeCentralPortalUsername = getConfigProperty('sonatypeCentralPortalUsername', 'SONATYPE_CENTRAL_PORTAL_USERNAME')\n    sonatypeCentralPortalPassword = getConfigProperty('sonatypeCentralPortalPassword', 'SONATYPE_CENTRAL_PORTAL_PASSWORD')\n\n    signingKey = getConfigProperty('signingKey', 'SIGNING_GPG_SECRET_KEY')         // NOTE: ASCII armored secret key\n    signingPassword = getConfigProperty('signingPassword', 'SIGNING_GPG_PASSWORD') // NOTE: Plain text\n}\n\ndef projectPom = {\n    name = 'aeron'\n    // optionally artifactId can be defined here\n    description = 'Efficient reliable UDP unicast, UDP multicast, and IPC transport protocol.'\n    url = 'https://github.com/aeron-io/aeron'\n\n    scm {\n        connection = 'scm:git:https://github.com/aeron-io/aeron.git'\n        developerConnection = 'scm:git:https://github.com/aeron-io/aeron.git'\n        url = 'https://github.com/aeron-io/aeron.git'\n    }\n\n    licenses {\n        license {\n            name = 'The Apache License, Version 2.0'\n            url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'\n        }\n    }\n\n    developers {\n        developer {\n            id = 'tmontgomery'\n            name = 'Todd L. Montgomery'\n            email = 'tmont@nard.net'\n            url = 'https://github.com/tmontgomery'\n        }\n        developer {\n            id = 'mjpt777'\n            name = 'Martin Thompson'\n            email = 'mjpt777@gmail.com'\n            url = 'https://github.com/mjpt777'\n        }\n        developer {\n            id = 'RichardWarburton'\n            name = 'Richard Warburton'\n            email = 'richard.warburton@gmail.com'\n            url = 'https://github.com/RichardWarburton'\n        }\n        developer {\n            id = 'nitsanw'\n            name = 'Nitsan Wakart'\n            email = 'nitsanw@yahoo.com'\n            url = 'https://github.com/nitsanw'\n        }\n        developer {\n            id = 'mikeb01'\n            name = 'Mike Barker'\n            email = 'mikeb01@gmail.com'\n            url = 'https://github.com/mikeb01'\n        }\n        developer {\n            id = 'vyazelenko'\n            name = 'Dmytro Vyazelenko'\n            email = 'vyazelenko@protonmail.com'\n            url = 'https://github.com/vyazelenko'\n        }\n    }\n}\n\njar.enabled = false\n\nallprojects {\n    repositories {\n        mavenLocal()\n        mavenCentral()\n    }\n\n    configurations.configureEach {\n        resolutionStrategy {\n            failOnVersionConflict()\n\n            force libs.agrona,\n                  libs.byteBuddy,\n                  libs.byteBuddy.agent,\n                  // patching conflicting Checkstyle dependencies\n                  libs.commons.codec,\n                  libs.commons.lang3,\n                  libs.httpcore,\n                  libs.plexus.utils\n        }\n    }\n\n    tasks.withType(JavaExec).configureEach {\n        javaLauncher.set(toolchainLauncher)\n    }\n}\n\nsubprojects {\n    apply plugin: 'base'\n    apply plugin: 'java-library'\n    apply plugin: 'checkstyle'\n\n    group = aeronGroup\n    version = aeronVersion\n\n    checkstyle.toolVersion = libs.versions.checkstyle.get()\n\n    dependencies {\n        testImplementation libs.hamcrest\n        testImplementation libs.mockito\n        testImplementation platform(libs.junit.bom)\n        testImplementation \"org.junit.jupiter:junit-jupiter-params\"\n        testRuntimeOnly \"org.junit.jupiter:junit-jupiter-engine\"\n        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'\n    }\n\n    java {\n        toolchain {\n            languageVersion = JavaLanguageVersion.of(buildJavaVersion)\n        }\n        sourceCompatibility = JavaVersion.VERSION_17\n    }\n\n    tasks.withType(Sign).configureEach {\n        onlyIf {\n            isReleaseVersion && gradle.taskGraph.hasTask(tasks.publish)\n        }\n    }\n\n    tasks.withType(Jar).configureEach {\n        enabled = true\n        includeEmptyDirs = false\n    }\n\n    tasks.withType(JavaCompile).configureEach {\n        options.encoding = 'UTF-8'\n        options.deprecation = true\n        options.compilerArgs.add('--release')\n        options.compilerArgs.add(java.sourceCompatibility.majorVersion)\n        options.compilerArgs.addAll(['-Xlint:all,-processing', '-Werror']) // Enable all warnings and treat them as errors\n        options.compilerArgs.add(\"-Aio.aeron.version=${aeronVersion}\")\n        options.compilerArgs.add(\"-Aio.aeron.gitsha=${gitCommitHash}\")\n    }\n\n    def mockitoAgent = configurations.create('mockitoAgent') { transitive = false }\n    mockitoAgent.withDependencies {it.add(libs.mockito.get()) }\n\n    tasks.withType(Test).configureEach { test ->\n        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')\n        jvmArgs('--add-opens', 'java.base/java.util.zip=ALL-UNNAMED')\n        jvmArgs(\"-javaagent:${mockitoAgent.asPath}\")\n        if (buildJavaVersion >= 21) {\n            jvmArgs('-XX:+EnableDynamicAgentLoading')\n        }\n\n        testClassesDirs = testing.suites.test.sources.output.classesDirs\n        classpath = testing.suites.test.sources.runtimeClasspath\n\n        def baseLogFileName = \"${layout.buildDirectory.get()}/${layout.projectDirectory.asFile.getName()}-${test.name}-%p\"\n        jvmArgs(\"-XX:MaxDirectMemorySize=2g\",\n                \"-Xms2048m\",\n                \"-Xmx2048m\",\n                \"-Xmn1536m\",\n                \"-XX:+CreateCoredumpOnCrash\",\n                \"-XX:+HeapDumpOnOutOfMemoryError\",\n                \"-XX:+CrashOnOutOfMemoryError\",\n                \"-XX:HeapDumpPath=${baseLogFileName}-heap.hprof\",\n                \"-XX:+UseParallelGC\",\n                \"-Xlog:gc*,safepoint=info,arguments=info:file=${baseLogFileName}-gc.log:time\",\n                \"-XX:ErrorFile=${baseLogFileName}-crash.log\")\n\n        useJUnitPlatform {\n            if (test.name != 'slowTest' && test.name != 'bindingsTest' && test.name != 'topologyTest')\n            {\n                excludeTags 'any()'\n            }\n        }\n\n        testLogging {\n            for (def level : LogLevel.values())\n            {\n                def testLogging = get(level)\n                testLogging.exceptionFormat = 'full'\n                testLogging.events = [\"FAILED\", \"STANDARD_OUT\", \"STANDARD_ERROR\"]\n            }\n        }\n\n        systemProperties(\n            'java.util.logging.config.class': 'io.aeron.test.DisableJavaUtilLogging',\n            'aeron.shared.idle.strategy': 'yield',\n            'aeron.conductor.idle.strategy': 'yield',\n            'aeron.sender.idle.strategy': 'yield',\n            'aeron.receiver.idle.strategy': 'yield',\n            'aeron.use.windows.high.res.timer': 'true',\n            'aeron.timer.interval': '100ms',\n            'aeron.dir.delete.on.start': 'true',\n            'aeron.term.buffer.sparse.file': 'true',\n            'aeron.perform.storage.checks': 'false',\n            'agrona.disable.bounds.checks': 'false',\n            'agrona.strict.alignment.checks': 'true',\n            'net.bytebuddy.experimental': 'true',\n            'io.aeron.shadow.net.bytebuddy.experimental': 'true',\n            'net.bytebuddy.safe': 'true',\n            'io.aeron.shadow.net.bytebuddy.safe': 'true',\n            'java.net.preferIPv4Stack': 'true',\n            'aeron.fallback.logger': 'no_op',\n            'aeron.test.system.aeronmd.path': System.getProperty('aeron.test.system.aeronmd.path'),\n            'aeron.test.system.binding.remote.host': System.getProperty('aeron.test.system.binding.remote.host'),\n            'aeron.test.system.binding.local.host': System.getProperty('aeron.test.system.binding.local.host'),\n            'aeron.test.system.aeron.dir': System.getProperty('aeron.test.system.aeron.dir'),\n            'aeron.test.system.ats.path': System.getProperty('aeron.test.system.ats.path'),\n            'aeron.test.system.ats.conf.dir': System.getProperty('aeron.test.system.ats.conf.dir'),\n            'aeron.test.system.ats.conf.file': System.getProperty('aeron.test.system.ats.conf.file'),\n            'aeron.test.system.driver.await.counters': System.getProperty('aeron.test.system.driver.await.counters'))\n\n        javaLauncher.set(toolchainLauncher)\n    }\n\n    def aeronLoggingAgent = project.file(\"../aeron-agent/build/libs/aeron-agent-${aeronVersion}.jar\").absolutePath\n\n    tasks.register('slowTest', Test) {\n        group = 'verification'\n        dependsOn ':aeron-agent:shadowJar'\n\n        maxParallelForks = 1\n        useJUnitPlatform {\n            includeTags 'slow'\n        }\n\n        jvmArgs(\"-javaagent:${aeronLoggingAgent}\")\n        systemProperty 'aeron.event.cluster.log', 'all'\n        systemProperty 'aeron.event.cluster.log.disable', 'CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION'\n        systemProperty 'aeron.event.archive.log', 'all'\n        systemProperty 'aeron.event.log', 'admin'\n        systemProperty 'aeron.debug.timeout', '3600s'\n        systemProperty 'aeron.event.log.reader.classname', 'io.aeron.agent.CollectingEventLogReaderAgent'\n    }\n\n    tasks.register('bindingsTest', Test) {\n        group = 'verification'\n        dependsOn ':aeron-agent:shadowJar'\n\n        maxParallelForks = 1\n        useJUnitPlatform {\n            includeTags 'bindings'\n        }\n\n        jvmArgs(\"-javaagent:${aeronLoggingAgent}\")\n        systemProperty 'aeron.event.cluster.log', 'all'\n        systemProperty 'aeron.event.cluster.log.disable', 'CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION'\n        systemProperty 'aeron.event.archive.log', 'all'\n        systemProperty 'aeron.event.log', 'admin'\n        systemProperty 'aeron.debug.timeout', '3600s'\n        systemProperty 'aeron.event.log.reader.classname', 'io.aeron.agent.CollectingEventLogReaderAgent'\n    }\n\n    tasks.register('topologyTest', Test) {\n        group = 'verification'\n        maxParallelForks = 1\n        useJUnitPlatform {\n            includeTags 'topology'\n        }\n\n        jvmArgs(\"-javaagent:${aeronLoggingAgent}\")\n        systemProperty 'aeron.event.cluster.log', 'all'\n        systemProperty 'aeron.event.cluster.log.disable', 'CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION'\n        systemProperty 'aeron.event.archive.log', 'all'\n        systemProperty 'aeron.event.log', 'admin'\n        systemProperty 'aeron.debug.timeout', '3600s'\n        systemProperty 'aeron.event.log.reader.classname', 'io.aeron.agent.CollectingEventLogReaderAgent'\n    }\n\n    javadoc {\n        failOnError = false\n        title = '<h1>Aeron Message Transport</h1>'\n        options.bottom = '<i>Copyright &#169; 2014-2025 Real Logic Limited. All Rights Reserved.</i>'\n        options.encoding = 'UTF-8'\n        options.docEncoding = 'UTF-8'\n        options.charSet = 'UTF-8'\n        options.links(\"https://www.javadoc.io/doc/org.agrona/agrona/${libs.versions.agrona.get()}/\")\n\n        if (isEarlyAccessJavaVersion) {\n            options.links(\"https://download.java.net/java/early_access/jdk${buildJavaVersion}/docs/api/\")\n        }\n        else {\n            options.links(\"https://docs.oracle.com/en/java/javase/${buildJavaVersion}/docs/api/\")\n        }\n\n        exclude 'io/aeron/archive/codecs/**'\n        exclude 'io/aeron/cluster/codecs/**'\n        exclude 'io/aeron/samples/**'\n        exclude 'io/aeron/test/**'\n\n        options.addStringOption('-release', java.sourceCompatibility.majorVersion)\n        options.addBooleanOption('Werror', true)\n        options.addBooleanOption('Xdoclint:all', true)\n    }\n\n    tasks.register('testJar', Jar) {\n        dependsOn 'testClasses'\n        archiveClassifier.set(\"test-${base.archivesName}\")\n        from sourceSets.test.output\n    }\n\n    configurations {\n        tests\n    }\n\n    artifacts {\n        tests testJar\n    }\n}\n\nproject(':aeron-annotations') {\n    apply plugin: 'maven-publish'\n    apply plugin: 'signing'\n\n    jar {\n        manifest.attributes(\n            'Automatic-Module-Name': 'io.aeron.annotations',\n            'Implementation-Title': 'Aeron',\n            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',\n            'Implementation-Version': aeronVersion\n        )\n    }\n\n    java {\n        withSourcesJar()\n        withJavadocJar()\n    }\n\n    publishing {\n        publications {\n            aeronAnnotations(MavenPublication) {\n                from components.java\n                pom(projectPom)\n            }\n        }\n\n        repositories {\n            maven {\n                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl\n                credentials {\n                    username = sonatypeCentralPortalUsername\n                    password = sonatypeCentralPortalPassword\n                }\n            }\n        }\n    }\n\n    signing {\n        if (signingKey != null) {\n            useInMemoryPgpKeys(signingKey, signingPassword)\n        }\n        sign publishing.publications.aeronAnnotations\n    }\n}\n\nproject(':aeron-client') {\n    apply plugin: 'maven-publish'\n    apply plugin: 'signing'\n\n    dependencies {\n        api libs.agrona\n        implementation project(':aeron-annotations')\n        annotationProcessor project(':aeron-annotations')\n        testImplementation project(':aeron-test-support')\n    }\n\n    jar {\n        manifest.attributes(\n            'Automatic-Module-Name': 'io.aeron.client',\n            'Implementation-Title': 'Aeron',\n            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',\n            'Implementation-Version': aeronVersion\n        )\n    }\n\n    java {\n        withSourcesJar()\n        withJavadocJar()\n    }\n\n    javadoc {\n        source += project(':aeron-annotations').sourceSets.main.allJava\n    }\n\n    publishing {\n        publications {\n            aeronClient(MavenPublication) {\n                from components.java\n                pom(projectPom)\n            }\n        }\n\n        repositories {\n            maven {\n                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl\n                credentials {\n                    username = sonatypeCentralPortalUsername\n                    password = sonatypeCentralPortalPassword\n                }\n            }\n        }\n    }\n\n    signing {\n        if (signingKey != null) {\n            useInMemoryPgpKeys(signingKey, signingPassword)\n        }\n        sign publishing.publications.aeronClient\n    }\n\n    def configInfoFile = \"${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat\"\n\n    tasks.register('validateConfigExpectations', JavaExec) {\n        def sourceDir = 'src/main/c'\n\n        inputs.files(configInfoFile)\n\n        mainClass.set('io.aeron.config.validation.ValidateConfigExpectationsTask')\n        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath\n        args = [configInfoFile, sourceDir]\n    }\n\n    tasks.register('generateConfigDoc', JavaExec) {\n        def generatedDocFile = \"${layout.buildDirectory.get()}/generated-doc/out.md\"\n\n        inputs.files(configInfoFile)\n        outputs.files(generatedDocFile)\n\n        mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask')\n        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath\n        args = [configInfoFile, generatedDocFile]\n    }\n\n    tasks.register('validateCounterExpectations', JavaExec) {\n        def counterInfoFile = \"${layout.buildDirectory.get()}/generated/sources/headers/java/main/counter-info.dat\"\n        def sourceDir = 'src/main/c'\n\n        inputs.files(counterInfoFile)\n\n        mainClass.set('io.aeron.counter.validation.ValidateCounterExpectationsTask')\n        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath\n        args = [counterInfoFile, sourceDir]\n    }\n}\n\nproject(':aeron-driver') {\n    apply plugin: 'maven-publish'\n    apply plugin: 'signing'\n    apply plugin: 'application'\n\n    application {\n        mainClass.set('io.aeron.driver.MediaDriver')\n    }\n\n    dependencies {\n        api project(':aeron-client')\n        implementation project(':aeron-annotations')\n        annotationProcessor project(':aeron-annotations')\n        testImplementation project(':aeron-test-support')\n    }\n\n    jar {\n        manifest.attributes(\n            'Automatic-Module-Name': 'io.aeron.driver',\n            'Implementation-Title': 'Aeron',\n            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',\n            'Implementation-Version': aeronVersion\n        )\n    }\n\n    jar.finalizedBy assembleDist\n\n    java {\n        withSourcesJar()\n        withJavadocJar()\n    }\n\n    javadoc {\n        source += project(':aeron-annotations').sourceSets.main.allJava\n        source += project(':aeron-client').sourceSets.main.allJava\n    }\n\n    publishing {\n        publications {\n            aeronDriver(MavenPublication) {\n                from components.java\n                artifact distZip\n                artifact distTar\n                pom(projectPom)\n            }\n        }\n\n        repositories {\n            maven {\n                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl\n                credentials {\n                    username = sonatypeCentralPortalUsername\n                    password = sonatypeCentralPortalPassword\n                }\n            }\n        }\n    }\n\n    signing {\n        if (signingKey != null) {\n            useInMemoryPgpKeys(signingKey, signingPassword)\n        }\n        sign publishing.publications.aeronDriver\n    }\n\n    tasks.register('validateConfigExpectations', JavaExec) {\n        def configInfoFile = \"${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat\"\n        def sourceDir = 'src/main/c'\n\n        inputs.files(configInfoFile)\n\n        mainClass.set('io.aeron.config.validation.ValidateConfigExpectationsTask')\n        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath\n        args = [configInfoFile, sourceDir]\n    }\n\n    tasks.register('generateConfigDoc', JavaExec) {\n        def configInfoFile = \"${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat\"\n        def generatedDocFile = \"${layout.buildDirectory.get()}/generated-doc/out.md\"\n\n        inputs.files(configInfoFile)\n        outputs.files(generatedDocFile)\n\n        mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask')\n        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath\n        args = [configInfoFile, generatedDocFile]\n    }\n}\n\nproject(':aeron-archive') {\n    apply plugin: 'maven-publish'\n    apply plugin: 'signing'\n\n    configurations{\n        codecGeneration\n    }\n\n    dependencies {\n        api project(':aeron-driver')\n        api files('build/classes/java/generated')\n        codecGeneration libs.sbe\n        implementation project(':aeron-annotations')\n        annotationProcessor project(':aeron-annotations')\n        testImplementation project(':aeron-test-support')\n        testImplementation files(\"${layout.buildDirectory.get()}/classes/java/generatedTest\")\n    }\n\n    def generatedSrcDir = file(\"${layout.buildDirectory.get()}/generated-src\")\n    def generatedTestDir = file(\"${layout.buildDirectory.get()}/generated-test\")\n\n    sourceSets {\n        generated {\n            java.srcDir generatedSrcDir\n            compileClasspath += configurations.codecGeneration\n        }\n        generatedTest {\n            java.srcDir generatedTestDir\n            compileClasspath += configurations.codecGeneration\n        }\n    }\n\n    def codecsFile = 'src/main/resources/archive/aeron-archive-codecs.xml'\n    def markCodecsFile = 'src/main/resources/archive/aeron-archive-mark-codecs.xml'\n    def sbeFile = 'src/main/resources/archive/fpl/sbe.xsd'\n\n    tasks.register('generateCodecs', JavaExec) {\n        inputs.files(codecsFile, markCodecsFile, sbeFile)\n        outputs.dir generatedSrcDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')\n        classpath = configurations.codecGeneration\n        systemProperties(\n            'sbe.output.dir': generatedSrcDir,\n            'sbe.target.language': 'Java',\n            'sbe.validation.xsd': sbeFile,\n            'sbe.validation.stop.on.error': 'true')\n        args = [codecsFile, markCodecsFile]\n    }\n\n    def generatedCppDir = file(System.getProperty('codec.target.dir') ?: \"${rootDir}/cppbuild/Release/generated\")\n    tasks.register('generateCppCodecs', JavaExec) {\n        inputs.files(codecsFile, sbeFile)\n        outputs.dir generatedCppDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')\n        classpath = configurations.codecGeneration\n        systemProperties(\n            'sbe.output.dir': generatedCppDir,\n            'sbe.target.language': 'Cpp',\n            'sbe.target.namespace': 'aeron.archive.client',\n            'sbe.validation.xsd': sbeFile,\n            'sbe.validation.stop.on.error': 'true')\n        args = [codecsFile]\n    }\n\n    def generatedCDir = file(System.getProperty('codec.target.dir') ?: \"${rootDir}/cppbuild/Release/generated/c\")\n    tasks.register('generateCCodecs', JavaExec) {\n        inputs.files(codecsFile, sbeFile)\n        outputs.dir generatedCDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')\n        classpath = configurations.codecGeneration\n        systemProperties(\n            'sbe.output.dir': generatedCDir,\n            'sbe.target.language': 'C',\n            'sbe.target.namespace': 'aeron.archive.client',\n            'sbe.validation.xsd': sbeFile,\n            'sbe.validation.stop.on.error': 'true')\n        args = [codecsFile]\n    }\n\n    tasks.register('generateTestCodecsV6', JavaExec) {\n        inputs.files(codecsFile, sbeFile)\n        outputs.dir generatedTestDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')\n        classpath = configurations.codecGeneration\n        systemProperties(\n            'sbe.output.dir': generatedTestDir,\n            'sbe.target.language': 'Java',\n            'sbe.validation.xsd': sbeFile,\n            'sbe.validation.stop.on.error': 'true',\n            'sbe.target.namespace': 'io.aeron.archive.codecs.v6',\n            'sbe.schema.transform.version': '*:6')\n        args = [codecsFile]\n    }\n\n    tasks.register('generateTestCodecsMarkFileV0', JavaExec) {\n        inputs.files(markCodecsFile, sbeFile)\n        outputs.dir generatedTestDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')\n        classpath = configurations.codecGeneration\n        systemProperties(\n            'sbe.output.dir': generatedTestDir,\n            'sbe.target.language': 'Java',\n            'sbe.validation.xsd': sbeFile,\n            'sbe.validation.stop.on.error': 'true',\n            'sbe.target.namespace': 'io.aeron.archive.codecs.mark.v0',\n            'sbe.schema.transform.version': '*:0')\n        args = [markCodecsFile]\n    }\n\n    tasks.register('generateTestCodecsMarkFileV1', JavaExec) {\n        inputs.files(markCodecsFile, sbeFile)\n        outputs.dir generatedTestDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')\n        classpath = configurations.codecGeneration\n        systemProperties(\n            'sbe.output.dir': generatedTestDir,\n            'sbe.target.language': 'Java',\n            'sbe.validation.xsd': sbeFile,\n            'sbe.validation.stop.on.error': 'true',\n            'sbe.target.namespace': 'io.aeron.archive.codecs.mark.v1',\n            'sbe.schema.transform.version': '*:1')\n        args = [markCodecsFile]\n    }\n\n    compileJava.dependsOn 'compileGeneratedJava'\n    compileGeneratedJava.dependsOn 'generateCodecs'\n    compileTestJava.dependsOn 'compileGeneratedTestJava'\n    compileGeneratedTestJava.dependsOn 'generateTestCodecsV6', 'generateTestCodecsMarkFileV0', 'generateTestCodecsMarkFileV1'\n\n    jar {\n        from sourceSets.generated.output\n\n        manifest.attributes(\n            'Automatic-Module-Name': 'io.aeron.archive',\n            'Implementation-Title': 'Aeron',\n            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',\n            'Implementation-Version': aeronVersion\n        )\n    }\n\n    tasks.register('sourcesJar', Jar) {\n        dependsOn 'generateCodecs'\n        archiveClassifier.set('sources')\n        from sourceSets.main.allSource\n        from sourceSets.generated.allSource\n    }\n\n    javadoc {\n        source += sourceSets.generated.allJava\n        source += project(':aeron-annotations').sourceSets.main.allJava\n        source += project(':aeron-client').sourceSets.main.allJava\n        source += project(':aeron-driver').sourceSets.main.allJava\n    }\n\n    javadoc.dependsOn generateCodecs\n\n    java {\n        withSourcesJar()\n        withJavadocJar()\n    }\n\n    publishing {\n        publications {\n            aeronArchive(MavenPublication) {\n                from components.java\n                pom(projectPom)\n            }\n        }\n\n        repositories {\n            maven {\n                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl\n                credentials {\n                    username = sonatypeCentralPortalUsername\n                    password = sonatypeCentralPortalPassword\n                }\n            }\n        }\n    }\n\n    signing {\n        if (signingKey != null) {\n            useInMemoryPgpKeys(signingKey, signingPassword)\n        }\n        sign publishing.publications.aeronArchive\n    }\n\n    tasks.register('generateConfigDoc', JavaExec) {\n        def configInfoFile = \"${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat\"\n        def generatedDocFile = \"${layout.buildDirectory.get()}/generated-doc/out.md\"\n\n        inputs.files(configInfoFile)\n        outputs.files(generatedDocFile)\n\n        mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask')\n        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath\n        args = [configInfoFile, generatedDocFile]\n    }\n}\n\nproject(':aeron-cluster') {\n    apply plugin: 'maven-publish'\n    apply plugin: 'signing'\n\n    configurations{\n        codecGeneration\n    }\n\n    dependencies {\n        api project(':aeron-archive')\n        api files('build/classes/java/generated')\n        codecGeneration libs.sbe\n        implementation project(':aeron-annotations')\n        annotationProcessor project(':aeron-annotations')\n        testImplementation project(':aeron-test-support')\n        testImplementation files(\"${layout.buildDirectory.get()}/classes/java/generatedTest\")\n    }\n\n    def generatedSrcDir = file(\"${layout.buildDirectory.get()}/generated-src\")\n    def generatedTestDir = file(\"${layout.buildDirectory.get()}/generated-test\")\n\n    sourceSets {\n        generated {\n            java.srcDir generatedSrcDir\n            compileClasspath += configurations.codecGeneration\n        }\n        generatedTest {\n            java.srcDir generatedTestDir\n            compileClasspath += configurations.codecGeneration\n        }\n    }\n\n    def codecsFile = 'src/main/resources/cluster/aeron-cluster-codecs.xml'\n    def markCodecsFile = 'src/main/resources/cluster/aeron-cluster-mark-codecs.xml'\n    def nodeStateCodecsFile = 'src/main/resources/cluster/aeron-cluster-node-state-codecs.xml'\n    def sbeFile = 'src/main/resources/cluster/fpl/sbe.xsd'\n\n    tasks.register('generateCodecs', JavaExec) {\n        inputs.files(codecsFile, markCodecsFile, sbeFile)\n        outputs.dir generatedSrcDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        classpath = configurations.codecGeneration\n        systemProperties(\n            'sbe.output.dir': generatedSrcDir,\n            'sbe.target.language': 'Java',\n            'sbe.validation.xsd': sbeFile,\n            'sbe.validation.stop.on.error': 'true')\n        args = [codecsFile, markCodecsFile, nodeStateCodecsFile]\n    }\n\n    def markCodecsFileV0 = 'src/test/resources/aeron-cluster-mark-codecs-v0.xml'\n    tasks.register('generateTestCodecsMarkFileV0', JavaExec) {\n        inputs.files(markCodecsFileV0, sbeFile)\n        outputs.dir generatedTestDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')\n        classpath = configurations.codecGeneration\n        systemProperties(\n            'sbe.output.dir': generatedTestDir,\n            'sbe.target.language': 'Java',\n            'sbe.validation.xsd': sbeFile,\n            'sbe.validation.stop.on.error': 'true',\n            'sbe.target.namespace': 'io.aeron.cluster.codecs.mark.v0',\n            'sbe.schema.transform.version': '*:0')\n        args = [markCodecsFileV0]\n    }\n\n    tasks.register('generateTestCodecsV5', JavaExec) {\n        inputs.files(codecsFile, sbeFile)\n        outputs.dir generatedTestDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        classpath = configurations.codecGeneration\n        systemProperties(\n                'sbe.output.dir': generatedTestDir,\n                'sbe.target.language': 'Java',\n                'sbe.validation.xsd': sbeFile,\n                'sbe.validation.stop.on.error': 'true',\n                'sbe.target.namespace': 'io.aeron.cluster.codecs.v5',\n                'sbe.schema.transform.version': '*:5')\n        args = [codecsFile]\n    }\n\n    tasks.register('generateTestCodecsV12', JavaExec) {\n        inputs.files(codecsFile, sbeFile)\n        outputs.dir generatedTestDir\n\n        mainClass.set('uk.co.real_logic.sbe.SbeTool')\n        classpath = configurations.codecGeneration\n        systemProperties(\n                'sbe.output.dir': generatedTestDir,\n                'sbe.target.language': 'Java',\n                'sbe.validation.xsd': sbeFile,\n                'sbe.validation.stop.on.error': 'true',\n                'sbe.target.namespace': 'io.aeron.cluster.codecs.v12',\n                'sbe.schema.transform.version': '*:12')\n        args = [codecsFile]\n    }\n\n    compileJava.dependsOn 'compileGeneratedJava'\n    compileGeneratedJava.dependsOn 'generateCodecs'\n    compileTestJava.dependsOn 'compileGeneratedTestJava'\n    compileGeneratedTestJava.dependsOn 'generateTestCodecsMarkFileV0', 'generateTestCodecsV5', 'generateTestCodecsV12'\n\n    jar {\n        from sourceSets.generated.output\n\n        manifest.attributes(\n            'Automatic-Module-Name': 'io.aeron.cluster',\n            'Implementation-Title': 'Aeron',\n            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',\n            'Implementation-Version': aeronVersion\n        )\n    }\n\n    tasks.register('sourcesJar', Jar) {\n        dependsOn 'generateCodecs'\n        archiveClassifier.set('sources')\n        from sourceSets.main.allSource\n        from sourceSets.generated.allSource\n    }\n\n    javadoc {\n        source += sourceSets.generated.allJava\n        source += project(':aeron-annotations').sourceSets.main.allJava\n        source += project(':aeron-client').sourceSets.main.allJava\n        source += project(':aeron-driver').sourceSets.main.allJava\n        source += project(':aeron-archive').sourceSets.main.allJava\n        source += project(':aeron-archive').sourceSets.generated.allJava\n    }\n\n    javadoc.dependsOn generateCodecs\n\n    java {\n        withSourcesJar()\n        withJavadocJar()\n    }\n\n    publishing {\n        publications {\n            aeronCluster(MavenPublication) {\n                from components.java\n                pom(projectPom)\n            }\n        }\n\n        repositories {\n            maven {\n                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl\n                credentials {\n                    username = sonatypeCentralPortalUsername\n                    password = sonatypeCentralPortalPassword\n                }\n            }\n        }\n    }\n\n    signing {\n        if (signingKey != null) {\n            useInMemoryPgpKeys(signingKey, signingPassword)\n        }\n        sign publishing.publications.aeronCluster\n    }\n\n    tasks.register('generateConfigDoc', JavaExec) {\n        def configInfoFile = \"${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat\"\n        def generatedDocFile = \"${layout.buildDirectory.get()}/generated-doc/out.md\"\n\n        inputs.files(configInfoFile)\n        outputs.files(generatedDocFile)\n\n        mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask')\n        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath\n        args = [configInfoFile, generatedDocFile]\n    }\n}\n\nproject(':aeron-agent') {\n    apply plugin: 'maven-publish'\n    apply plugin: 'signing'\n    apply plugin: 'com.gradleup.shadow'\n\n    def aeronClientProject = project(':aeron-client')\n    def aeronDriverProject = project(':aeron-driver')\n    def aeronArchiveProject = project(':aeron-archive')\n    def aeronClusterProject = project(':aeron-cluster')\n\n    dependencies {\n        implementation aeronClusterProject\n        implementation libs.byteBuddy\n        implementation libs.byteBuddy.agent\n        implementation project(':aeron-annotations')\n        annotationProcessor project(':aeron-annotations')\n        compileOnly libs.findbugs.annotations\n        testCompileOnly libs.findbugs.annotations\n        testImplementation project(':aeron-test-support')\n    }\n\n    shadowJar {\n        archiveClassifier.set('')\n\n        duplicatesStrategy = DuplicatesStrategy.EXCLUDE // exclude duplicate files by default\n\n        mergeServiceFiles('META-INF/services') // merge service files into a single file\n        filesMatching('META-INF/services/**') {\n            duplicatesStrategy = DuplicatesStrategy.INCLUDE // copy all services across\n        }\n\n        manifest.attributes(\n            'Automatic-Module-Name': 'io.aeron.agent',\n            'Implementation-Title': 'Aeron',\n            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',\n            'Implementation-Version': aeronVersion,\n            \"Premain-Class\": \"io.aeron.agent.EventLogAgent\",\n            \"Agent-Class\": \"io.aeron.agent.EventLogAgent\",\n            \"Can-Redefine-Classes\": \"true\",\n            \"Can-Retransform-Classes\": \"true\"\n        )\n\n        relocate 'net.bytebuddy', 'io.aeron.shadow.net.bytebuddy'\n    }\n\n    jar.enabled = false\n\n    tasks.register('sourcesJar', Jar) {\n        dependsOn ':aeron-archive:generateCodecs', ':aeron-cluster:generateCodecs'\n        archiveClassifier.set('sources')\n        from files(\n            aeronClientProject.sourceSets.main.allSource,\n            aeronDriverProject.sourceSets.main.allSource,\n            aeronArchiveProject.sourceSets.main.allSource,\n            aeronArchiveProject.sourceSets.generated.allSource,\n            aeronClusterProject.sourceSets.main.allSource,\n            aeronClusterProject.sourceSets.generated.allSource)\n    }\n\n    javadoc {\n        source += project(':aeron-annotations').sourceSets.main.allJava\n        source += aeronClientProject.sourceSets.main.allJava\n        source += aeronDriverProject.sourceSets.main.allJava\n        source += aeronArchiveProject.sourceSets.main.allJava\n        source += aeronArchiveProject.sourceSets.generated.allJava\n        source += aeronClusterProject.sourceSets.main.allJava\n        source += aeronClusterProject.sourceSets.generated.allJava\n    }\n\n    javadoc.dependsOn ':aeron-archive:generateCodecs', ':aeron-cluster:generateCodecs'\n\n    tasks.register('javadocJar', Jar) {\n        dependsOn 'javadoc'\n        archiveClassifier.set('javadoc')\n        from javadoc.destinationDir\n    }\n\n    publishing {\n        publications {\n            aeronAgent(MavenPublication) {\n                artifact shadowJar\n                artifact sourcesJar\n                artifact javadocJar\n                pom(projectPom)\n            }\n        }\n        repositories {\n            maven {\n                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl\n                credentials {\n                    username = sonatypeCentralPortalUsername\n                    password = sonatypeCentralPortalPassword\n                }\n            }\n        }\n    }\n\n    signing {\n        if (signingKey != null) {\n            useInMemoryPgpKeys(signingKey, signingPassword)\n        }\n        sign publishing.publications.aeronAgent\n    }\n}\n\nproject(':aeron-samples') {\n    apply plugin: 'maven-publish'\n    apply plugin: 'signing'\n\n    dependencies {\n        api project(':aeron-cluster')\n        implementation libs.hdrHistogram\n        testImplementation project(':aeron-test-support')\n    }\n\n    java {\n        withSourcesJar()\n        withJavadocJar()\n    }\n\n    publishing {\n        publications {\n            aeronSamples(MavenPublication) {\n                from components.java\n                pom(projectPom)\n            }\n        }\n\n        repositories {\n            maven {\n                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl\n                credentials {\n                    username = sonatypeCentralPortalUsername\n                    password = sonatypeCentralPortalPassword\n                }\n            }\n        }\n    }\n\n    javadoc {\n        source += project(':aeron-annotations').sourceSets.main.allJava\n        source += project(':aeron-client').sourceSets.main.allJava\n        source += project(':aeron-driver').sourceSets.main.allJava\n        source += project(':aeron-archive').sourceSets.main.allJava\n        source += project(':aeron-archive').sourceSets.generated.allJava\n    }\n\n    signing {\n        if (signingKey != null) {\n            useInMemoryPgpKeys(signingKey, signingPassword)\n        }\n        sign publishing.publications.aeronSamples\n    }\n\n    tasks.named('test').configure {\n        dependsOn ':aeron-agent:shadowJar'\n\n        ext.agentFilename = {\n            project.file(\"../aeron-agent/build/libs/aeron-agent-${aeronVersion}.jar\").absolutePath\n        }\n\n        jvmArgs '-javaagent:' + agentFilename()\n        systemProperty 'aeron.event.cluster.log', 'all'\n        systemProperty 'aeron.event.cluster.log.disable', 'CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION'\n        systemProperty 'aeron.event.archive.log', 'all'\n        systemProperty 'aeron.event.log', 'admin'\n        systemProperty 'aeron.debug.timeout', '3600s'\n        systemProperty 'aeron.event.log.reader.classname', 'io.aeron.agent.CollectingEventLogReaderAgent'\n    }\n\n    tasks.register('asciidoctorGithub', io.aeron.build.AsciidoctorPreprocessTask) {\n        versionText = aeronVersion\n    }\n\n    tasks.register('removeWikiDirectory', Delete) {\n        delete \"${layout.buildDirectory.get()}/tmp/tutorialPublish\"\n    }\n\n    tasks.register('tutorialPublish', io.aeron.build.TutorialPublishTask) {\n        dependsOn 'removeWikiDirectory', 'asciidoctorGithub'\n        apiKey = project.hasProperty('aeron.ci.apiKey') ? project.property('aeron.ci.apiKey') : ''\n        source = asciidoctorGithub.target\n        remoteName = project.hasProperty('aeron.ci.remoteName') ? project.property('aeron.ci.remoteName') : 'origin'\n    }\n}\n\nproject(':aeron-system-tests') {\n    apply plugin: 'com.gradleup.shadow'\n\n    dependencies {\n        testImplementation project(':aeron-archive')\n        testImplementation project(':aeron-test-support')\n        testImplementation project(':aeron-samples')\n        testImplementation project(path: ':aeron-client', configuration: 'tests')\n        testImplementation project(path: ':aeron-archive', configuration: 'tests')\n        testImplementation project(path: ':aeron-cluster')\n        testImplementation libs.byteBuddy\n        testImplementation libs.byteBuddy.agent\n        testCompileOnly libs.findbugs.annotations\n    }\n\n    tasks.named('test').configure {\n        dependsOn ':aeron-agent:shadowJar'\n\n        ext.agentFilename = {\n            project.file(\"../aeron-agent/build/libs/aeron-agent-${aeronVersion}.jar\").absolutePath\n        }\n\n        jvmArgs '-javaagent:' + agentFilename()\n        systemProperty 'aeron.event.cluster.log', 'all'\n        systemProperty 'aeron.event.cluster.log.disable', 'CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION'\n        systemProperty 'aeron.event.archive.log', 'all'\n        systemProperty 'aeron.event.log', 'admin'\n        systemProperty 'aeron.debug.timeout', '3600s'\n        systemProperty 'aeron.event.log.reader.classname', 'io.aeron.agent.CollectingEventLogReaderAgent'\n    }\n\n    shadowJar {\n        archiveClassifier.set('tests')\n        from sourceSets.test.output\n        configurations = [project.configurations.testRuntimeClasspath]\n    }\n\n    javadoc.enabled = false\n}\n\nproject(':aeron-test-support') {\n    apply plugin: 'maven-publish'\n    apply plugin: 'signing'\n\n    dependencies {\n        implementation project(':aeron-client')\n        implementation project(':aeron-cluster')\n        implementation project(':aeron-samples')\n        implementation platform(libs.junit.bom)\n        implementation \"org.junit.jupiter:junit-jupiter-api\"\n        implementation \"org.junit.jupiter:junit-jupiter-params\"\n        implementation libs.mockito\n    }\n\n    jar {\n        manifest.attributes(\n            'Automatic-Module-Name': 'io.aeron.test-support',\n            'Implementation-Title': 'Aeron',\n            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',\n            'Implementation-Version': aeronVersion\n        )\n    }\n\n    java {\n        withSourcesJar()\n        withJavadocJar()\n    }\n\n    publishing {\n        publications {\n            aeronTestSupport(MavenPublication) {\n                from components.java\n                pom(projectPom)\n            }\n        }\n\n        repositories {\n            maven {\n                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl\n                credentials {\n                    username = sonatypeCentralPortalUsername\n                    password = sonatypeCentralPortalPassword\n                }\n            }\n        }\n    }\n\n    signing {\n        if (signingKey != null) {\n            useInMemoryPgpKeys(signingKey, signingPassword)\n        }\n        sign publishing.publications.aeronTestSupport\n    }\n}\n\nproject(':aeron-all') {\n    apply plugin: 'maven-publish'\n    apply plugin: 'signing'\n    apply plugin: 'com.gradleup.shadow'\n\n    def aeronClientProject = project(':aeron-client')\n    def aeronDriverProject = project(':aeron-driver')\n    def aeronArchiveProject = project(':aeron-archive')\n    def aeronClusterProject = project(':aeron-cluster')\n    def aeronSamplesProject = project(':aeron-samples')\n\n    dependencies {\n        implementation aeronClusterProject\n        implementation aeronSamplesProject\n    }\n\n    shadowJar {\n        archiveClassifier.set('')\n\n        duplicatesStrategy = DuplicatesStrategy.EXCLUDE // exclude duplicate files by default\n\n        mergeServiceFiles('META-INF/services') // merge service files into a single file\n        filesMatching('META-INF/services/**') {\n            duplicatesStrategy = DuplicatesStrategy.INCLUDE // copy all services across\n        }\n\n        manifest.attributes(\n            'Automatic-Module-Name': 'io.aeron.all',\n            'Implementation-Title': 'Aeron',\n            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',\n            'Implementation-Version': aeronVersion\n        )\n\n        relocate 'net.bytebuddy', 'io.aeron.shadow.net.bytebuddy'\n        relocate 'org.HdrHistogram', 'io.aeron.shadow.org.HdrHistogram'\n    }\n\n    jar.finalizedBy shadowJar\n\n    tasks.register('sourcesJar', Jar) {\n        dependsOn ':aeron-archive:generateCodecs', ':aeron-cluster:generateCodecs'\n        archiveClassifier.set('sources')\n        from files(\n            aeronClientProject.sourceSets.main.allSource,\n            aeronDriverProject.sourceSets.main.allSource,\n            aeronArchiveProject.sourceSets.main.allSource,\n            aeronArchiveProject.sourceSets.generated.allSource,\n            aeronClusterProject.sourceSets.main.allSource,\n            aeronClusterProject.sourceSets.generated.allSource,\n            aeronSamplesProject.sourceSets.main.allSource)\n    }\n\n    javadoc {\n        source += project(':aeron-annotations').sourceSets.main.allJava\n        source += aeronClientProject.sourceSets.main.allJava\n        source += aeronDriverProject.sourceSets.main.allJava\n        source += aeronArchiveProject.sourceSets.main.allJava\n        source += aeronArchiveProject.sourceSets.generated.allJava\n        source += aeronClusterProject.sourceSets.main.allJava\n        source += aeronClusterProject.sourceSets.generated.allJava\n        source += aeronSamplesProject.sourceSets.main.allJava\n    }\n\n    javadoc.dependsOn ':aeron-archive:generateCodecs', ':aeron-cluster:generateCodecs'\n\n    tasks.register('javadocJar', Jar) {\n        dependsOn 'javadoc'\n        archiveClassifier.set('javadoc')\n        from javadoc.destinationDir\n    }\n\n    publishing {\n        publications {\n            aeronAll(MavenPublication) {\n                artifact shadowJar\n                artifact sourcesJar\n                artifact javadocJar\n                pom(projectPom)\n            }\n        }\n        repositories {\n            maven {\n                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl\n                credentials {\n                    username = sonatypeCentralPortalUsername\n                    password = sonatypeCentralPortalPassword\n                }\n            }\n        }\n    }\n\n    signing {\n        if (signingKey != null) {\n            useInMemoryPgpKeys(signingKey, signingPassword)\n        }\n        sign publishing.publications.aeronAll\n    }\n}\n\ntasks.register('testReport', TestReport) {\n    destinationDirectory = file(\"${layout.buildDirectory.get()}/reports/allTests\")\n    // Include the results from the `test` task in all sub-projects\n    testResults.setFrom(subprojects*.test)\n}\n\ntasks.register('copyTestLogs', Copy) {\n    from '.'\n    include '**/build/test-output/**'\n    include '**/*.hprof'\n    include '**/*.mdmp'\n    include '/var/coredump/core*'\n    include '**/*.log'\n    include '**/*.tlog'\n    include '**/*.PML'\n    include '**/build/reports/tests/**'\n    include '**/build/test-results/**/*'\n    include 'LICENSE'\n    exclude 'build'\n    into 'build/test_logs'\n\n    includeEmptyDirs = false\n}\n\ntasks.register('tarTestLogs', Tar) {\n    dependsOn tasks.named('copyTestLogs')\n    archiveBaseName.set('test_logs')\n    from 'build/test_logs'\n    compression = Compression.BZIP2\n}\n\ndef isNonStable = { String version ->\n    def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) }\n    def regex = /^[0-9,.v-]+(-r)?$/\n    return !stableKeyword && !(version ==~ regex)\n}\n\ntasks.named('dependencyUpdates').configure {\n    // Reject all non stable versions\n    rejectVersionIf {\n        isNonStable(it.candidate.version)\n    }\n}\n\ntasks.register('uploadArtifactsToCentralPortal', io.aeron.build.SonatypeCentralPortalUploadRepositoryTask) {\n    portalUsername.set(sonatypeCentralPortalUsername)\n    portalPassword.set(sonatypeCentralPortalPassword)\n    groupId.set(aeronGroup)\n    snapshotRelease.set(!isReleaseVersion)\n}\n\nwrapper {\n    distributionType = 'ALL'\n}\n"
  },
  {
    "path": "buildSrc/build.gradle",
    "content": "repositories {\n    mavenCentral()\n}\n\nconfigurations.configureEach {\n    resolutionStrategy {\n        failOnVersionConflict()\n\n        force libs.asm,\n              libs.asm.analysis,\n              libs.asm.commons,\n              libs.asm.tree,\n              libs.asm.util\n    }\n}\n\ndependencies {\n    implementation libs.asciidoctorj\n    implementation libs.jgit\n    implementation libs.json\n}\n\ntasks.withType(JavaCompile).configureEach {\n    configure(options) {\n        options.compilerArgs << '-Xlint:deprecation' << '-Xlint:unchecked' // examples\n    }\n}\n"
  },
  {
    "path": "buildSrc/settings.gradle",
    "content": "dependencyResolutionManagement {\n    versionCatalogs {\n        create(\"libs\") {\n            from(files(\"../gradle/libs.versions.toml\"))\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/io/aeron/build/AsciidocUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.build;\n\nimport java.io.File;\nimport java.util.List;\n\nimport static java.util.Arrays.asList;\n\nfinal class AsciidocUtil\n{\n    private static final List<String> EXTENSIONS = asList(\".adoc\", \".asciidoc\");\n\n    private AsciidocUtil()\n    {\n    }\n\n    public static File[] filterAsciidocFiles(final File directory)\n    {\n        final File[] asciidocFiles = directory.listFiles(\n            (dir, name) ->\n            {\n                for (final String extension : EXTENSIONS)\n                {\n                    if (name.endsWith(extension))\n                    {\n                        return true;\n                    }\n                }\n                return false;\n            });\n\n        return null != asciidocFiles ? asciidocFiles : new File[0];\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/io/aeron/build/AsciidoctorPreprocessTask.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.build;\n\nimport org.asciidoctor.Asciidoctor;\nimport org.asciidoctor.Attributes;\nimport org.asciidoctor.Options;\nimport org.asciidoctor.SafeMode;\nimport org.asciidoctor.ast.Document;\nimport org.asciidoctor.extension.PreprocessorReader;\nimport org.asciidoctor.log.Severity;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.InputDirectory;\nimport org.gradle.api.tasks.OutputDirectory;\nimport org.gradle.api.tasks.TaskAction;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintStream;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.joining;\n\n/**\n * Gradle task to prepare AsciiDoc files.\n */\npublic class AsciidoctorPreprocessTask extends DefaultTask\n{\n    private final String sampleBaseDir = getProject().getProjectDir().getAbsolutePath();\n\n    private final String sampleSourceDir = sampleBaseDir + \"/src/main/java\";\n\n    private final File source = new File(sampleBaseDir, \"/src/docs/asciidoc\");\n\n    private final File target = new File(\n        getProject().getLayout().getBuildDirectory().getAsFile().get(), \"/asciidoc/asciidoc\");\n\n    // Has a slightly silly name to avoid name clashes in the build script.\n    private String versionText;\n\n    /**\n     * Base directory containing the samples code.\n     *\n     * @return base directory for samples.\n     */\n    @Input\n    public String getSampleBaseDir()\n    {\n        return sampleBaseDir;\n    }\n\n    /**\n     * Directory containing the samples source code.\n     *\n     * @return sources within the directory for samples.\n     */\n    @Input\n    public String getSampleSourceDir()\n    {\n        return sampleSourceDir;\n    }\n\n    /**\n     * Directory containing the source files.\n     *\n     * @return source directory.\n     */\n    @InputDirectory\n    public File getSource()\n    {\n        return source;\n    }\n\n    /**\n     * Directory containing the target files.\n     *\n     * @return target directory.\n     */\n    @OutputDirectory\n    public File getTarget()\n    {\n        return target;\n    }\n\n    /**\n     * Returns the version string.\n     *\n     * @return version string.\n     */\n    @Input\n    public String getVersionText()\n    {\n        return versionText;\n    }\n\n    /**\n     * Sets the version string.\n     *\n     * @param versionText version string.\n     */\n    public void setVersionText(final String versionText)\n    {\n        this.versionText = versionText;\n    }\n\n    /**\n     * Implementation of the task action.\n     *\n     * @throws Exception in case of errors.\n     */\n    @TaskAction\n    public void preprocess() throws Exception\n    {\n        if (!target.exists() && !target.mkdirs())\n        {\n            throw new IOException(\"unable to create build directory\");\n        }\n\n        final File[] asciidocFiles = AsciidocUtil.filterAsciidocFiles(source);\n\n        System.out.println(\"Transforming from: \" + source);\n        System.out.println(\"Found files: \" + Arrays.stream(asciidocFiles).map(File::getName).collect(joining(\", \")));\n\n        final Map<File, Integer> errors = new HashMap<>();\n\n        for (final File asciidocFile : asciidocFiles)\n        {\n            final File outputFile = new File(target, asciidocFile.getName());\n\n            final Asciidoctor asciidoctor = Asciidoctor.Factory.create();\n\n            final int[] errorCount = { 0 };\n\n            asciidoctor.registerLogHandler(\n                (logRecord) ->\n                {\n                    if (logRecord.getSeverity() == Severity.ERROR || logRecord.getSeverity() == Severity.FATAL)\n                    {\n                        errorCount[0]++;\n                    }\n                });\n\n            final Attributes attributes = Attributes.builder()\n                .attribute(\"sampleBaseDir\", requireNonNull(sampleBaseDir, \"Must specify sampleBaseDir\"))\n                .attribute(\"sampleSourceDir\", requireNonNull(sampleSourceDir, \"Must specify sampleSourceDir\"))\n                .build();\n\n            final Options options = Options.builder()\n                .attributes(attributes)\n                .safe(SafeMode.UNSAFE)\n                .build();\n\n            try (PrintStream output = new PrintStream(outputFile))\n            {\n                asciidoctor.javaExtensionRegistry().preprocessor(\n                    new org.asciidoctor.extension.Preprocessor()\n                    {\n                        public void process(final Document document, final PreprocessorReader reader)\n                        {\n                            String line;\n                            while (null != (line = reader.readLine()))\n                            {\n                                if (line.startsWith(\":aeronVersion:\"))\n                                {\n                                    output.println(\":aeronVersion: \" + versionText);\n                                }\n                                else\n                                {\n                                    output.println(line);\n                                }\n                            }\n                        }\n                    });\n\n                asciidoctor.loadFile(asciidocFile, options);\n\n                if (0 < errorCount[0])\n                {\n                    errors.put(asciidocFile, errorCount[0]);\n                }\n            }\n        }\n\n        errors.forEach((key, value) -> System.out.println(\"file: \" + key + \", error count: \" + value));\n\n        if (!errors.isEmpty())\n        {\n            throw new Exception(\"failed due to errors in parsing\");\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/io/aeron/build/GithubUtil.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.build;\n\nimport org.eclipse.jgit.api.Git;\nimport org.eclipse.jgit.api.Status;\nimport org.eclipse.jgit.api.errors.GitAPIException;\nimport org.eclipse.jgit.lib.ObjectId;\nimport org.eclipse.jgit.lib.ObjectReader;\nimport org.eclipse.jgit.lib.Repository;\nimport org.eclipse.jgit.revwalk.RevCommit;\nimport org.eclipse.jgit.storage.file.FileRepositoryBuilder;\nimport org.eclipse.jgit.transport.URIish;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\n\nfinal class GithubUtil\n{\n    private GithubUtil()\n    {\n    }\n\n    static String currentGitHash(final String projectDir) throws IOException, GitAPIException\n    {\n        final FileRepositoryBuilder repositoryBuilder = new FileRepositoryBuilder().findGitDir(new File(projectDir));\n        if (repositoryBuilder.getGitDir() == null)\n        {\n            // No .git directory. That will be the case when people download the repo as a zip file.\n            return \"unknown\";\n        }\n\n        try (Repository repository = repositoryBuilder.build();\n            ObjectReader reader = repository.newObjectReader();\n            Git git = new Git(repository))\n        {\n            final RevCommit commit = git.log().setMaxCount(1).call().iterator().next();\n            final ObjectId commitId = commit.toObjectId();\n            final String commitSha = reader.abbreviate(commitId, 10).name();\n            final Status status = git.status().call();\n\n            return status.hasUncommittedChanges() ? commitSha + \"+guilty\" : commitSha;\n        }\n    }\n\n    static String getWikiUriFromOriginUri(final String remoteUri) throws URISyntaxException\n    {\n        final URIish urIish = new URIish(remoteUri);\n        final String uriPath = urIish.getPath();\n\n        if (uriPath.endsWith(\"/\"))\n        {\n            throw new IllegalArgumentException(\"unable to handle URI path ending in '/': \" + remoteUri);\n        }\n\n        final int lastSlashIndex = urIish.getPath().lastIndexOf('/');\n\n        final String path = lastSlashIndex == -1 ? \"\" : uriPath.substring(0, lastSlashIndex + 1);\n        final String prefixedPath = path.startsWith(\"/\") ? path : \"/\" + path;\n        final String repoName = lastSlashIndex == -1 ? uriPath : uriPath.substring(lastSlashIndex + 1);\n        final String name = stripSuffix(repoName, \".git\");\n        final String host = stripSuffix(urIish.getHost(), \"/\");\n\n        final String wikiUri = \"https://\" + host + prefixedPath + name + \".wiki.git\";\n\n        System.out.println(\"Origin: \" + remoteUri);\n        System.out.println(\"Wiki  : \" + wikiUri);\n\n        return wikiUri;\n    }\n\n    private static String stripSuffix(final String s, final String suffix)\n    {\n        if (s.endsWith(suffix))\n        {\n            return s.substring(0, s.length() - suffix.length());\n        }\n\n        return s;\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/io/aeron/build/SonatypeCentralPortalUploadRepositoryTask.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.build;\n\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.TaskAction;\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.net.http.HttpResponse.BodySubscribers;\nimport java.net.http.HttpResponse.ResponseInfo;\nimport java.time.Duration;\nimport java.util.Base64;\n\nimport static java.nio.charset.StandardCharsets.US_ASCII;\n\n/**\n * This task performs manual steps to publish artifacts to Central Portal via OSSRH Staging API.\n */\npublic class SonatypeCentralPortalUploadRepositoryTask extends DefaultTask\n{\n    private static final String CENTRAL_PORTAL_OSSRH_API_URI = \"https://ossrh-staging-api.central.sonatype.com\";\n    private static final Duration CONNECTION_TIMEOUT = Duration.ofSeconds(30);\n\n    private final Property<String> portalUsername;\n    private final Property<String> portalPassword;\n    private final Property<String> groupId;\n    private final Property<Boolean> snapshotRelease;\n\n    /**\n     * Create new task instance.\n     */\n    public SonatypeCentralPortalUploadRepositoryTask()\n    {\n        portalUsername = getProject().getObjects().property(String.class);\n        portalPassword = getProject().getObjects().property(String.class);\n        groupId = getProject().getObjects().property(String.class);\n        snapshotRelease = getProject().getObjects().property(Boolean.class);\n    }\n\n    /**\n     * Return property to set Central Portal username.\n     *\n     * @return Central Portal username.\n     */\n    @Input\n    public Property<String> getPortalUsername()\n    {\n        return portalUsername;\n    }\n\n    /**\n     * Return property to set Central Portal password.\n     *\n     * @return Central Portal password.\n     */\n    @Input\n    public Property<String> getPortalPassword()\n    {\n        return portalPassword;\n    }\n\n    /**\n     * Return property to set {@code groupId} of the project.\n     *\n     * @return {@code groupId} of the project.\n     */\n    @Input\n    public Property<String> getGroupId()\n    {\n        return groupId;\n    }\n\n    /**\n     * Return property to set snapshot release.\n     *\n     * @return {@code true} if snapshot release.\n     */\n    @Input\n    public Property<Boolean> getSnapshotRelease()\n    {\n        return snapshotRelease;\n    }\n\n    /**\n     * Publish staging repository to the Central Portal.\n     */\n    @TaskAction\n    public void run() throws IOException, InterruptedException\n    {\n        if (!portalUsername.isPresent())\n        {\n            return; // release is not configured\n        }\n\n        if (snapshotRelease.get())\n        {\n            return; // snapshots are published directly\n        }\n\n        final HttpClient httpClient = HttpClient.newBuilder()\n            .connectTimeout(CONNECTION_TIMEOUT)\n            .build();\n\n        final String userNameAndPassword = portalUsername.get() + \":\" + portalPassword.get();\n        final String bearer = new String(\n            Base64.getEncoder().encode(userNameAndPassword.getBytes(US_ASCII)), US_ASCII);\n\n        final HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()\n            .header(\"Authorization\", \"Bearer \" + bearer);\n\n        final URI apiUri = URI.create(CENTRAL_PORTAL_OSSRH_API_URI);\n\n        final String repositoryKey = findOpenRepository(apiUri, httpClient, requestBuilder);\n        uploadRepositoryToPortal(apiUri, httpClient, requestBuilder, repositoryKey);\n        dropRepository(apiUri, httpClient, requestBuilder, repositoryKey);\n    }\n\n    private String findOpenRepository(\n        final URI apiUri,\n        final HttpClient httpClient,\n        final HttpRequest.Builder requestBuilder) throws IOException, InterruptedException\n    {\n        final HttpRequest request = requestBuilder\n            .copy()\n            .GET()\n            .uri(apiUri.resolve(\"/manual/search/repositories?ip=client\"))\n            .build();\n        final HttpResponse<String> response = httpClient.send(\n            request, (ResponseInfo responseInfo) -> BodySubscribers.ofString(US_ASCII));\n\n        if (200 != response.statusCode())\n        {\n            throw new IllegalStateException(\"Failed to query repositories: \" +\n                \"status=\" + response.statusCode() + \", response=\" + response.body());\n        }\n\n        final JSONArray repositories = new JSONObject(response.body()).getJSONArray(\"repositories\");\n        if (repositories.isEmpty())\n        {\n            throw new IllegalStateException(\"No open repositories found!\");\n        }\n\n        String repositoryKey = null;\n        final String group = groupId.get();\n        for (int i = 0; i < repositories.length(); i++)\n        {\n            final JSONObject repo = (JSONObject)repositories.get(i);\n            if (\"open\".equals(repo.getString(\"state\")))\n            {\n                final String key = repo.getString(\"key\");\n                if (key.contains(group))\n                {\n                    repositoryKey = key;\n                    break;\n                }\n            }\n        }\n\n        if (null == repositoryKey)\n        {\n            throw new IllegalStateException(\"No open repositories found!\");\n        }\n        return repositoryKey;\n    }\n\n    private static void uploadRepositoryToPortal(\n        final URI apiUri,\n        final HttpClient httpClient,\n        final HttpRequest.Builder requestBuilder,\n        final String repositoryKey) throws IOException, InterruptedException\n    {\n        final HttpRequest request = requestBuilder\n            .copy()\n            .POST(HttpRequest.BodyPublishers.noBody())\n            .uri(apiUri.resolve(\"/manual/upload/repository/\" + repositoryKey + \"?publishing_type=automatic\"))\n            .build();\n        final HttpResponse<String> response = httpClient.send(\n            request, (ResponseInfo responseInfo) -> BodySubscribers.ofString(US_ASCII));\n\n        if (200 != response.statusCode())\n        {\n            throw new IllegalStateException(\"Failed to upload repository: repository_key=\" + repositoryKey +\n                \", status=\" + response.statusCode() + \", response=\" + response.body());\n        }\n    }\n\n    private static void dropRepository(\n        final URI apiUri,\n        final HttpClient httpClient,\n        final HttpRequest.Builder requestBuilder,\n        final String repositoryKey) throws IOException, InterruptedException\n    {\n        final HttpRequest request = requestBuilder\n            .copy()\n            .DELETE()\n            .uri(apiUri.resolve(\"/manual/drop/repository/\" + repositoryKey))\n            .build();\n        final HttpResponse<String> response = httpClient.send(\n            request, (ResponseInfo responseInfo) -> BodySubscribers.ofString(US_ASCII));\n\n        if (204 != response.statusCode())\n        {\n            throw new IllegalStateException(\"Failed to drop repository: repository_key=\" + repositoryKey +\n                \", status=\" + response.statusCode() + \", response=\" + response.body());\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/java/io/aeron/build/TutorialPublishTask.java",
    "content": "/*\n * Copyright 2014-2025 Real Logic Limited.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.aeron.build;\n\nimport org.eclipse.jgit.api.Git;\nimport org.eclipse.jgit.lib.Repository;\nimport org.eclipse.jgit.storage.file.FileRepositoryBuilder;\nimport org.eclipse.jgit.transport.CredentialsProvider;\nimport org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.InputDirectory;\nimport org.gradle.api.tasks.TaskAction;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.StandardCopyOption;\nimport java.util.Arrays;\n\nimport static java.util.Objects.requireNonNull;\nimport static java.util.stream.Collectors.joining;\nimport static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_URL;\nimport static org.eclipse.jgit.lib.ConfigConstants.CONFIG_REMOTE_SECTION;\n\n/**\n * Gradle task to publish the tutorial documentation.\n */\npublic class TutorialPublishTask extends DefaultTask\n{\n    private String apiKey;\n\n    private File source;\n\n    private String remoteName;\n\n    /**\n     * Returns the GitHub API key to use for publishing.\n     *\n     * @return GitHub API key.\n     */\n    @Input\n    public String getApiKey()\n    {\n        return apiKey;\n    }\n\n    /**\n     * Returns the source directory.\n     *\n     * @return source directory.\n     */\n    @InputDirectory\n    public File getSource()\n    {\n        return source;\n    }\n\n    /**\n     * Gets the name of the remote repo.\n     *\n     * @return name of the remote repo.\n     */\n    @Input\n    public String getRemoteName()\n    {\n        return remoteName;\n    }\n\n    /**\n     * Sets GitHub API key.\n     *\n     * @param apiKey used for publishing.\n     */\n    public void setApiKey(final String apiKey)\n    {\n        this.apiKey = apiKey;\n    }\n\n    /**\n     * Sets the source directory.\n     *\n     * @param source directory.\n     */\n    public void setSource(final File source)\n    {\n        this.source = source;\n    }\n\n    /**\n     * Sets the name of the remote repo.\n     *\n     * @param remoteName of Git repository.\n     */\n    public void setRemoteName(final String remoteName)\n    {\n        this.remoteName = remoteName;\n    }\n\n    /**\n     * Task action implementation.\n     *\n     * @throws Exception in case of errors.\n     */\n    @TaskAction\n    public void publish() throws Exception\n    {\n        final String wikiUri = getWikiUri();\n        final File directory =\n            new File(getProject().getLayout().getBuildDirectory().getAsFile().get(), \"tmp/tutorialPublish\");\n        // Use Personal Access Token or GITHUB_TOKEN for workflows\n        final CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(apiKey, \"\");\n\n        final Git git = Git.cloneRepository()\n            .setURI(wikiUri)\n            .setCredentialsProvider(credentialsProvider)\n            .setDirectory(directory)\n            .call();\n\n        final File[] asciidocFiles = AsciidocUtil.filterAsciidocFiles(source);\n        System.out.println(\"Publishing from: \" + source);\n        System.out.println(\"Found files: \" + Arrays.stream(asciidocFiles).map(File::getName).collect(joining(\", \")));\n\n        for (final File asciidocFile : asciidocFiles)\n        {\n            Files.copy(\n                asciidocFile.toPath(),\n                new File(directory, asciidocFile.getName()).toPath(),\n                StandardCopyOption.REPLACE_EXISTING);\n        }\n\n        git.add().addFilepattern(\".\").setUpdate(false).call();\n        git.commit()\n            .setSign(false)\n            .setMessage(\"Update Docs\")\n            .call();\n\n        System.out.println(\"Publishing to: \" + wikiUri);\n\n        git.push().setCredentialsProvider(credentialsProvider).call();\n    }\n\n    private String getWikiUri() throws IOException, URISyntaxException\n    {\n        final File baseGitDir = new File(getProject().getRootDir(), \".git\");\n        if (!baseGitDir.exists() || !baseGitDir.isDirectory())\n        {\n            throw new IllegalStateException(\"unable to find valid git repository at: \" + baseGitDir);\n        }\n\n        final Repository baseGitRepo = new FileRepositoryBuilder()\n            .setGitDir(new File(getProject().getRootDir(), \".git\"))\n            .build();\n\n        final String origin = baseGitRepo.getConfig().getString(\n            CONFIG_REMOTE_SECTION,\n            requireNonNull(remoteName, \"'remoteName' must be set, use origin as a default\"),\n            CONFIG_KEY_URL);\n\n        if (null == origin)\n        {\n            throw new IllegalStateException(\"unable to find origin URI\");\n        }\n\n        return GithubUtil.getWikiUriFromOriginUri(origin);\n    }\n}\n"
  },
  {
    "path": "config/checkstyle/checkstyle.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC \"-//Puppy Crawl//DTD Check Configuration 1.3//EN\"\n        \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n<module name=\"Checker\">\n    <module name=\"SuppressionFilter\">\n        <property name=\"file\" value=\"${config_loc}/suppressions.xml\"/>\n    </module>\n\n    <module name=\"SuppressWithPlainTextCommentFilter\">\n        <property name=\"offCommentFormat\" value=\"CHECKSTYLE\\:OFF\\:(\\w+)\"/>\n        <property name=\"onCommentFormat\" value=\"CHECKSTYLE\\:ON\\:(\\w+)\"/>\n        <property name=\"checkFormat\" value=\"$1\"/>\n    </module>\n\n    <module name=\"SuppressWarningsFilter\"/>\n\n    <module name=\"SeverityMatchFilter\">\n        <property name=\"severity\" value=\"info\"/>\n        <property name=\"acceptOnMatch\" value=\"false\"/>\n    </module>\n\n    <module name=\"FileTabCharacter\">\n        <property name=\"eachLine\" value=\"true\"/>\n    </module>\n\n    <module name=\"LineLength\">\n        <property name=\"max\" value=\"120\"/>\n        <property name=\"ignorePattern\" value=\"^[ \\t]*\\*.*@.*$\"/>\n    </module>\n\n    <module name=\"RegexpHeader\">\n        <property name=\"header\" value=\"/*\\n * Copyright \\d{4}(-\\d{4})? (Real Logic|Adaptive Financial Consulting) Limited.\"/>\n        <property name=\"fileExtensions\" value=\"java\"/>\n    </module>\n\n    <module name=\"TreeWalker\">\n        <property name=\"tabWidth\" value=\"4\"/>\n        <property name=\"severity\" value=\"error\"/>\n\n        <module name=\"SuppressWarningsHolder\"/>\n\n        <module name=\"JavadocStyle\"/>\n        <module name=\"JavadocType\"/>\n        <module name=\"JavadocMethod\"/>\n        <module name=\"JavadocVariable\">\n            <property name=\"accessModifiers\" value=\"public\"/>\n        </module>\n\n        <module name=\"MissingJavadocType\"/>\n        <module name=\"MissingJavadocMethod\"/>\n        <module name=\"MissingJavadocPackage\"/>\n\n        <module name=\"Indentation\"/>\n\n        <module name=\"ConstantName\"/>\n\n        <module name=\"FinalParameters\">\n            <property name=\"tokens\" value=\"METHOD_DEF, CTOR_DEF, LITERAL_CATCH, FOR_EACH_CLAUSE\"/>\n        </module>\n\n        <module name=\"FinalLocalVariable\">\n            <property name=\"validateEnhancedForLoopVariable\" value=\"true\"/>\n        </module>\n\n        <module name=\"LocalFinalVariableName\"/>\n\n        <module name=\"LocalVariableName\"/>\n\n        <module name=\"MemberName\">\n            <property name=\"format\" value=\"^[a-z][a-zA-Z0-9_]*$\"/>\n        </module>\n\n        <module name=\"MethodName\"/>\n\n        <module name=\"PackageName\"/>\n\n        <module name=\"ParameterName\"/>\n\n        <module name=\"StaticVariableName\"/>\n\n        <module name=\"TypeName\">\n            <property name=\"format\" value=\"^[A-Z][a-zA-Z0-9_]*$\"/>\n        </module>\n\n        <module name=\"RedundantImport\"/>\n\n        <module name=\"UnusedImports\"/>\n\n        <module name=\"MethodLength\">\n            <property name=\"tokens\" value=\"METHOD_DEF\"/>\n            <property name=\"max\" value=\"100\"/>\n        </module>\n\n        <module name=\"EmptyForInitializerPad\"/>\n\n        <module name=\"MethodParamPad\"/>\n\n        <module name=\"NoWhitespaceBefore\"/>\n\n        <module name=\"WhitespaceAfter\">\n            <property name=\"tokens\" value=\"COMMA, SEMI, LITERAL_IF, LITERAL_ELSE, LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE\"/>\n        </module>\n\n        <module name=\"NoWhitespaceAfter\">\n            <property name=\"tokens\" value=\"INC, DEC, UNARY_MINUS, UNARY_PLUS, BNOT, LNOT, DOT, TYPECAST, ARRAY_DECLARATOR, INDEX_OP, METHOD_REF\"/>\n            <property name=\"allowLineBreaks\" value=\"false\"/>\n        </module>\n\n        <module name=\"WhitespaceAround\">\n            <property name=\"allowEmptyLambdas\" value=\"true\"/>\n        </module>\n\n        <module name=\"SingleSpaceSeparator\"/>\n\n        <module name=\"OperatorWrap\">\n            <property name=\"option\" value=\"eol\"/>\n        </module>\n\n        <module name=\"NeedBraces\"/>\n\n        <module name=\"ParenPad\"/>\n\n        <module name=\"TypecastParenPad\"/>\n\n        <module name=\"ModifierOrder\"/>\n\n        <module name=\"RedundantModifier\"/>\n\n        <module name=\"NestedTryDepth\">\n            <property name=\"max\" value=\"2\"/>\n        </module>\n\n        <module name=\"CovariantEquals\"/>\n\n        <module name=\"LeftCurly\">\n            <property name=\"option\" value=\"nl\"/>\n        </module>\n\n        <module name=\"RightCurly\">\n            <property name=\"option\" value=\"alone\"/>\n            <property name=\"tokens\" value=\"LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT\"/>\n        </module>\n\n        <module name=\"EmptyStatement\"/>\n\n        <module name=\"EqualsHashCode\"/>\n\n        <module name=\"DefaultComesLast\"/>\n\n        <module name=\"SimplifyBooleanExpression\"/>\n\n        <module name=\"SimplifyBooleanReturn\"/>\n\n        <module name=\"StringLiteralEquality\"/>\n\n        <module name=\"PackageDeclaration\"/>\n\n        <module name=\"FallThrough\"/>\n\n        <module name=\"FinalClass\"/>\n\n        <module name=\"MutableException\"/>\n\n        <module name=\"EmptyLineSeparator\">\n            <property name=\"allowNoEmptyLineBetweenFields\" value=\"true\"/>\n            <property name=\"tokens\" value=\"IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF, STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF\"/>\n        </module>\n\n        <module name=\"TodoComment\">\n            <property name=\"severity\" value=\"info\"/>\n            <property name=\"format\" value=\"TODO\"/>\n        </module>\n\n        <module name=\"UpperEll\"/>\n\n        <module name=\"IllegalType\">\n            <property name=\"legalAbstractClassNames\"\n                      value=\"AbstractBeanDefinition, AbstractEntry\"/>\n            <property name=\"illegalClassNames\"\n                      value=\"java.util.GregorianCalendar, java.util.Vector\"/>\n        </module>\n\n        <module name=\"DescendantToken\">\n            <property name=\"tokens\" value=\"LITERAL_ASSERT\"/>\n            <property name=\"limitedTokens\"\n                      value=\"ASSIGN,DEC,INC,POST_DEC,POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN,BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,METHOD_CALL\"/>\n            <property name=\"maximumNumber\" value=\"2\"/>\n        </module>\n\n        <module name=\"Regexp\">\n            <property name=\"format\" value=\"[ \\t]+$\"/>\n            <property name=\"illegalPattern\" value=\"true\"/>\n            <property name=\"message\" value=\"Trailing whitespace\"/>\n        </module>\n\n        <module name=\"JavadocMethod\"/>\n    </module>\n</module>\n"
  },
  {
    "path": "config/checkstyle/suppressions.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE suppressions PUBLIC\n        \"-//Puppy Crawl//DTD Suppressions 1.0//EN\"\n        \"https://checkstyle.org/dtds/suppressions_1_0.dtd\">\n<suppressions>\n    <suppress files=\".*generated-src.*\" checks=\".\"/>\n    <suppress files=\".*generated-test.*\" checks=\".\"/>\n    <suppress files=\"[\\\\/]test[\\\\/]\" checks=\"MissingJavadoc.*\"/>\n    <suppress files=\"[\\\\/]test[\\\\/]\" checks=\"JavadocVariable\"/>\n    <suppress files=\".*package-info.java\" checks=\"LineLength\"/>\n</suppressions>"
  },
  {
    "path": "config/ide/clion/aeron_cpp.xml",
    "content": "<code_scheme name=\"aeron-cpp\" version=\"173\">\n  <option name=\"LINE_SEPARATOR\" value=\"&#xA;\" />\n  <Objective-C>\n    <option name=\"INDENT_NAMESPACE_MEMBERS\" value=\"0\" />\n    <option name=\"KEEP_NESTED_NAMESPACES_IN_ONE_LINE\" value=\"true\" />\n    <option name=\"NAMESPACE_BRACE_PLACEMENT\" value=\"2\" />\n    <option name=\"FUNCTION_BRACE_PLACEMENT\" value=\"2\" />\n    <option name=\"BLOCK_BRACE_PLACEMENT\" value=\"2\" />\n    <option name=\"FUNCTION_NON_TOP_AFTER_RETURN_TYPE_WRAP\" value=\"0\" />\n    <option name=\"FUNCTION_TOP_AFTER_RETURN_TYPE_WRAP\" value=\"0\" />\n    <option name=\"FUNCTION_PARAMETERS_WRAP\" value=\"5\" />\n    <option name=\"FUNCTION_PARAMETERS_ALIGN_MULTILINE\" value=\"false\" />\n    <option name=\"FUNCTION_PARAMETERS_NEW_LINE_AFTER_LPAR\" value=\"true\" />\n    <option name=\"FUNCTION_CALL_ARGUMENTS_WRAP\" value=\"5\" />\n    <option name=\"FUNCTION_CALL_ARGUMENTS_ALIGN_MULTILINE\" value=\"false\" />\n    <option name=\"FUNCTION_CALL_ARGUMENTS_NEW_LINE_AFTER_LPAR\" value=\"true\" />\n    <option name=\"SHIFT_OPERATION_WRAP\" value=\"0\" />\n    <option name=\"CLASS_CONSTRUCTOR_INIT_LIST_WRAP\" value=\"5\" />\n    <option name=\"CLASS_CONSTRUCTOR_INIT_LIST_ALIGN_MULTILINE\" value=\"false\" />\n    <option name=\"CLASS_CONSTRUCTOR_INIT_LIST_NEW_LINE_BEFORE_COLON\" value=\"0\" />\n    <option name=\"CLASS_CONSTRUCTOR_INIT_LIST_NEW_LINE_AFTER_COLON\" value=\"2\" />\n    <option name=\"SUPERCLASS_LIST_ALIGN_MULTILINE\" value=\"false\" />\n    <option name=\"SUPERCLASS_LIST_BEFORE_COLON\" value=\"0\" />\n    <option name=\"SUPERCLASS_LIST_AFTER_COLON\" value=\"2\" />\n  </Objective-C>\n  <codeStyleSettings language=\"CMake\">\n    <indentOptions>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"HTML\">\n    <indentOptions>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"ObjectiveC\">\n    <option name=\"KEEP_CONTROL_STATEMENT_IN_ONE_LINE\" value=\"false\" />\n    <option name=\"BLANK_LINES_BEFORE_IMPORTS\" value=\"0\" />\n    <option name=\"BLANK_LINES_AFTER_IMPORTS\" value=\"0\" />\n    <option name=\"BRACE_STYLE\" value=\"2\" />\n    <option name=\"CLASS_BRACE_STYLE\" value=\"2\" />\n    <option name=\"ELSE_ON_NEW_LINE\" value=\"true\" />\n    <option name=\"WHILE_ON_NEW_LINE\" value=\"true\" />\n    <option name=\"CATCH_ON_NEW_LINE\" value=\"true\" />\n    <option name=\"ALIGN_MULTILINE_FOR\" value=\"false\" />\n    <option name=\"ALIGN_MULTILINE_BINARY_OPERATION\" value=\"false\" />\n    <option name=\"ALIGN_MULTILINE_ASSIGNMENT\" value=\"false\" />\n    <option name=\"ALIGN_MULTILINE_TERNARY_OPERATION\" value=\"false\" />\n    <option name=\"ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION\" value=\"false\" />\n    <option name=\"SPACE_WITHIN_ARRAY_INITIALIZER_BRACES\" value=\"true\" />\n    <option name=\"SPACE_AFTER_TYPE_CAST\" value=\"false\" />\n    <option name=\"BINARY_OPERATION_WRAP\" value=\"0\" />\n    <option name=\"TERNARY_OPERATION_WRAP\" value=\"0\" />\n    <option name=\"KEEP_SIMPLE_METHODS_IN_ONE_LINE\" value=\"false\" />\n    <option name=\"ARRAY_INITIALIZER_WRAP\" value=\"5\" />\n    <option name=\"ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE\" value=\"true\" />\n    <option name=\"ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE\" value=\"true\" />\n    <option name=\"IF_BRACE_FORCE\" value=\"3\" />\n    <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\" />\n    <option name=\"WHILE_BRACE_FORCE\" value=\"3\" />\n    <option name=\"FOR_BRACE_FORCE\" value=\"3\" />\n    <indentOptions>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      <option name=\"LABEL_INDENT_ABSOLUTE\" value=\"true\" />\n    </indentOptions>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"XML\">\n    <indentOptions>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n    </indentOptions>\n  </codeStyleSettings>\n</code_scheme>"
  },
  {
    "path": "config/ide/idea/aeron.xml",
    "content": "<code_scheme name=\"aeron\" version=\"173\">\n  <option name=\"OTHER_INDENT_OPTIONS\">\n    <value>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n    </value>\n  </option>\n  <option name=\"LINE_SEPARATOR\" value=\"&#10;\" />\n  <JavaCodeStyleSettings>\n    <option name=\"GENERATE_FINAL_LOCALS\" value=\"true\" />\n    <option name=\"GENERATE_FINAL_PARAMETERS\" value=\"true\" />\n    <option name=\"INSERT_OVERRIDE_ANNOTATION\" value=\"false\" />\n    <option name=\"CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"999\" />\n    <option name=\"NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"999\" />\n  </JavaCodeStyleSettings>\n  <codeStyleSettings language=\"JAVA\">\n    <option name=\"BRACE_STYLE\" value=\"2\" />\n    <option name=\"CLASS_BRACE_STYLE\" value=\"2\" />\n    <option name=\"METHOD_BRACE_STYLE\" value=\"2\" />\n    <option name=\"LAMBDA_BRACE_STYLE\" value=\"5\" />\n    <option name=\"ELSE_ON_NEW_LINE\" value=\"true\" />\n    <option name=\"WHILE_ON_NEW_LINE\" value=\"true\" />\n    <option name=\"CATCH_ON_NEW_LINE\" value=\"true\" />\n    <option name=\"FINALLY_ON_NEW_LINE\" value=\"true\" />\n    <option name=\"ALIGN_MULTILINE_PARAMETERS\" value=\"false\" />\n    <option name=\"ALIGN_MULTILINE_RESOURCES\" value=\"false\" />\n    <option name=\"SPACE_WITHIN_ARRAY_INITIALIZER_BRACES\" value=\"true\" />\n    <option name=\"SPACE_AFTER_TYPE_CAST\" value=\"false\" />\n    <option name=\"PREFER_PARAMETERS_WRAP\" value=\"true\" />\n    <option name=\"METHOD_CALL_CHAIN_WRAP\" value=\"1\" />\n    <option name=\"KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE\" value=\"true\" />\n    <option name=\"KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE\" value=\"true\" />\n    <option name=\"IF_BRACE_FORCE\" value=\"3\" />\n    <option name=\"DOWHILE_BRACE_FORCE\" value=\"3\" />\n    <option name=\"WHILE_BRACE_FORCE\" value=\"3\" />\n    <option name=\"FOR_BRACE_FORCE\" value=\"3\" />\n    <indentOptions>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n    </indentOptions>\n  </codeStyleSettings>\n</code_scheme>"
  },
  {
    "path": "cppbuild/Doxyfile.in",
    "content": "# Doxyfile 1.8.9.1\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the config file\n# that follow. The default is UTF-8 which is also the encoding used for all text\n# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv\n# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv\n# for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = \"Aeron\"\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = doc\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       =\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 4\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines.\n\nALIASES                =\n\n# This tag can be used to specify a number of word-keyword mappings (TCL only).\n# A mapping has the form \"name=value\". For example adding \"class=itcl::class\"\n# will allow you to use the command class in the itcl::class meaning.\n\nTCL_SUBST              =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = NO\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, Javascript,\n# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:\n# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:\n# Fortran. In the later case the parser tries to guess whether the code is fixed\n# or free formatted code, this is the default for Fortran type files), VHDL. For\n# instance to make doxygen treat .inc files as Fortran files (default is PHP),\n# and .f files as C (default is Fortran), use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See http://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = NO\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = NO\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# (class|struct|union) declarations. If set to NO, these declarations will be\n# included in the documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES, upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# and Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = NO\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces.\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = @CMAKE_CURRENT_SOURCE_DIR@/aeron-client/src/main/cpp @CMAKE_CURRENT_SOURCE_DIR@/aeron-client/src/main/c @CMAKE_CURRENT_SOURCE_DIR@/README.md @CMAKE_CURRENT_SOURCE_DIR@/aeron-archive/src/main/cpp\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: http://www.gnu.org/software/libiconv) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank the\n# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,\n# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,\n# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,\n# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,\n# *.qsf, *.as and *.js.\n\nFILE_PATTERNS          =\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                =\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           = @CMAKE_CURRENT_SOURCE_DIR@/aeron-samples/src/main/cpp\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       =\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE = README.md\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = NO\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# function all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see http://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the config file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in\n# which the alphabetical index list will be split.\n# Minimum value: 1, maximum value: 20, default value: 5.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nCOLS_IN_ALPHA_INDEX    = 5\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# http://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: http://developer.apple.com/tools/xcode/), introduced with\n# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html\n# for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the master .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-\n# folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# http://www.mathjax.org) which uses client side Javascript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from http://www.mathjax.org before deployment.\n# The default value is: http://cdn.mathjax.org/mathjax/latest.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using Javascript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when enabling USE_PDFLATEX this option is only used for generating\n# bitmaps for formulas in the HTML output, but not in the Makefile that is\n# written to the output directory.\n# The default file is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. To get the times font for\n# instance you can specify\n# EXTRA_PACKAGES=times\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES, to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# http://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's config\n# file, i.e. a series of assignments. You only have to provide replacements,\n# missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's config file. A template extensions file can be generated\n# using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sf.net) file that captures the\n# structure of the code including all documentation. Note that this feature is\n# still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n# The PERL_PATH should be the absolute path and name of the perl script\n# interpreter (i.e. the result of 'which perl').\n# The default file (with absolute path) is: /usr/bin/perl.\n\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. Doxygen will then run the mscgen tool (see:\n# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the\n# documentation. The MSCGEN_PATH tag allows you to specify the directory where\n# the mscgen tool resides. If left empty the tool is assumed to be found in the\n# default search path.\n\nMSCGEN_PATH            =\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot.\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif and svg.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "cppbuild/cppbuild",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nSOURCE_DIR=\"$(pwd)\"\nexport BUILD_DIR=\"${SOURCE_DIR}/cppbuild/Release\"\nexport BUILD_CONFIG=Release\nEXTRA_CMAKE_ARGS=\"\"\nBUILD_PACKAGE=false\nCOVERAGE_BUILD=0\n\nBUILD_DELETE_CMAKE=false\nBUILD_CMAKE_VERSION=4.2.1\nBUILD_CMAKE_DIR=\"$(dirname \"${BUILD_DIR}\")/cmake\"\n\nncpus=1\ncase \"$(uname)\" in\n  Darwin* )\n    ncpus=$(sysctl -n hw.ncpu)\n    BUILD_CMAKE_OS=\"macos-universal\"\n    BUILD_CMAKE_PATH=\"${BUILD_CMAKE_DIR}/CMake.app/Contents/bin\"\n    ;;\n  Linux*)\n    ncpus=$(lscpu -p | grep -c -E -v '^#')\n    BUILD_CMAKE_OS=\"linux-$(arch)\"\n    BUILD_CMAKE_PATH=\"${BUILD_CMAKE_DIR}/bin\"\n    ;;\nesac\n\nfunction install_cmake()\n{\n  if [[ true = \"${BUILD_DELETE_CMAKE}\" ]]\n  then\n    echo \"Removing old CMake installation\"\n    rm -rf \"${BUILD_CMAKE_DIR}\"\n  fi\n\n  local version_file=\"${BUILD_CMAKE_DIR}/version.txt\"\n  local version_info=\"${BUILD_CMAKE_VERSION}-${BUILD_CMAKE_OS}\"\n  if [[ \"${version_info}\" != \"$(cat \"${version_file}\")\" ]]\n  then\n    echo \"Installing CMake ${version_info}\"\n    rm -rf \"${BUILD_CMAKE_DIR}\"\n    mkdir -p \"${BUILD_CMAKE_DIR}\"\n    (curl -LJ \"https://github.com/Kitware/CMake/releases/download/v${BUILD_CMAKE_VERSION}/cmake-${BUILD_CMAKE_VERSION}-${BUILD_CMAKE_OS}.tar.gz\" | tar xzf - -C \"${BUILD_CMAKE_DIR}\" --strip-components 1\n      echo \"${version_info}\" > \"${version_file}\")\n  fi\n}\n\nwhile [[ $# -gt 0 ]]\ndo\n  option=\"${1}\"\n  case ${option} in\n    --c-warnings-as-errors)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DC_WARNINGS_AS_ERRORS=ON\"\n      echo \"Enabling warnings as errors for c\"\n      shift\n      ;;\n    --cxx-warnings-as-errors)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DCXX_WARNINGS_AS_ERRORS=ON\"\n      echo \"Enabling warnings as errors for c++\"\n      shift\n      ;;\n    --cxx-hide-deprecation-message)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DAERON_HIDE_DEPRECATION_MESSAGE=ON\"\n      echo \"Hiding API deprecation message for c++\"\n      shift\n      ;;\n    -a|--build-archive-api)\n      echo \"Enabling building of Aeron Archive API is the default\"\n      shift\n      ;;\n    --skip-archive-api)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DBUILD_AERON_ARCHIVE_API=OFF\"\n      echo \"Disabline building of Aeron Archive API\"\n      shift\n      ;;\n    -d|--debug-build)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=Debug\"\n      export BUILD_DIR=\"${SOURCE_DIR}/cppbuild/Debug\"\n      export BUILD_CONFIG=Debug\n      echo \"Enabling debug build\"\n      shift\n      ;;\n    --relwithdebinfo-build)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=RelWithDebInfo\"\n      export BUILD_DIR=\"${SOURCE_DIR}/cppbuild/RelWithDebInfo\"\n      export BUILD_CONFIG=RelWithDebInfo\n      echo \"Enabling release with debug info build\"\n      shift\n      ;;\n    --compiler-optimization-level)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DAERON_COMPILER_OPTIMIZATION_LEVEL=${2}\"\n      echo \"Setting compiler optimization level to: -O${2}\"\n      shift\n      shift\n      ;;\n    -b|--build-aeron-driver)\n      echo \"Enabling building of Aeron driver is the default\"\n      shift\n      ;;\n    --no-parallel)\n      ncpus=1\n      echo \"Disabling parallel build\"\n      shift\n      ;;\n    --parallel-cpus)\n      ncpus=${2}\n      shift\n      shift\n      ;;\n    --no-tests)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DAERON_TESTS=OFF\"\n      echo \"Disabling all tests\"\n      shift\n      ;;\n    --no-unit-tests)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DAERON_UNIT_TESTS=OFF\"\n      echo \"Disabling unit tests\"\n      shift\n      ;;\n    --no-system-tests)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DAERON_SYSTEM_TESTS=OFF\"\n      echo \"Disabling system tests\"\n      shift\n      ;;\n    --slow-system-tests)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DAERON_SLOW_SYSTEM_TESTS=ON\"\n      echo \"Enabling slow system tests\"\n      shift\n      ;;\n    --sanitise-build)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DSANITISE_BUILD=ON\"\n      echo \"Enabling sanitise build\"\n      shift\n      ;;\n    --coverage-build)\n      if (hash lcov 2>/dev/null && hash genhtml 2>/dev/null); then\n        EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DCOVERAGE_BUILD=ON\"\n        echo \"Enabling coverage build\"\n        COVERAGE_BUILD=1\n      else\n        echo \"lcov/genhtml not found - you need these installed to run the coverage build\"\n        exit\n      fi\n      shift\n      ;;\n    --gradle-wrapper)\n      EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DGRADLE_WRAPPER=${2}\"\n      echo \"Setting -DGRADLE_WRAPPER=${2}\"\n      shift\n      shift\n      ;;\n    --package)\n      BUILD_PACKAGE=true\n      shift\n      ;;\n    --rebuild-cmake)\n      BUILD_DELETE_CMAKE=true\n      shift\n      ;;\n    --cmake-version)\n      BUILD_CMAKE_VERSION=${2}\n      echo \"Setting BUILD_CMAKE_VERSION=${2}\"\n      shift\n      shift\n      ;;\n    -h|--help)\n      echo \"${0} [--c-warnings-as-errors] [--cxx-warnings-as-errors] [--debug-build] [--relwithdebinfo-build] [--build-aeron-driver] [--build-archive-api] [--sanitise-build] [--coverage-build] [--no-parallel] [--no-system-tests] [--slow-system-tests] [--gradle-wrapper path_to_gradle] [--package] [--rebuild-cmake] [--cmake-version \\$cmake_version] [--help]\"\n      exit\n      ;;\n    *)\n      echo \"Unknown option ${option}\"\n      echo \"Use --help for help\"\n      exit 1\n      ;;\n  esac\ndone\n\necho \"Will make with \\\"-j ${ncpus}\\\".\"\n\nif [[ -d \"${BUILD_DIR}\" ]] ; then\n  echo \"Build directory (${BUILD_DIR}) exists, removing.\"\n  rm -rf \"${BUILD_DIR}\"\nfi\n\nmkdir -p \"${BUILD_DIR}\"\n\ninstall_cmake\n\nif [[ ${COVERAGE_BUILD} -eq 1 ]] ; then\n  cd \"${BUILD_DIR}\" || exit\n  # shellcheck disable=SC2086\n  \"${BUILD_CMAKE_PATH}/cmake\" -G \"Unix Makefiles\" ${EXTRA_CMAKE_ARGS} \"${SOURCE_DIR}\" && make clean && make -j \"${ncpus}\" all && \"${BUILD_CMAKE_PATH}/ctest\" -C ${BUILD_CONFIG} --timeout 2000 --output-on-failure\n  rm -rf coverage\n  mkdir -p coverage\n  lcov --directory . --base-directory . --capture -o coverage/cov.info\n  lcov -o coverage/cov.stripped.info --remove coverage/cov.info \"/usr/include/*\" \"*/googletest/*\" \"*/test/cpp/*\" \"*/googlemock/*\"\n  genhtml coverage/cov.stripped.info --demangle-cpp -o coverage\nelse\n  EXTRA_CMAKE_ARGS=\"${EXTRA_CMAKE_ARGS} -DDART_TESTING_TIMEOUT=2000\"\n\n  cd \"${BUILD_DIR}\" || exit\n  # shellcheck disable=SC2086\n  \"${BUILD_CMAKE_PATH}/cmake\" -G \"CodeBlocks - Unix Makefiles\" ${EXTRA_CMAKE_ARGS} \"${SOURCE_DIR}\"\n  make clean\n  make -j \"${ncpus}\" all\n  \"${BUILD_CMAKE_PATH}/ctest\" -C ${BUILD_CONFIG} --timeout 2000 --output-on-failure\nfi\n\nif [ true = \"${BUILD_PACKAGE}\" ]\nthen\n  (\n    cd \"${BUILD_DIR}\"\n    make package\n  )\nfi\n"
  },
  {
    "path": "cppbuild/cppbuild.ps1",
    "content": "#\n# Copyright 2014-2025 Real Logic Limited.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nfunction Add-Arg\n{\n\n    [CmdletBinding()]\n    param (\n        [string]$aggregate,\n        [string]$toAppend\n    )\n\n    if ($aggregate) { \"$aggregate $toAppend\" } else { $toAppend }\n}\n\n$CmakeExtraArgs = \"\"\n$CmakeBuildParallelLevel = [Environment]::ProcessorCount\n$DeleteBuildDir = $true\n$BuildConfig = \"Release\"\n\nfor ($i = 0; $i -lt $Args.count; $i++)\n{\n    $arg = $Args[$i]\n    if ($arg -eq \"--help\")\n    {\n        Write-Host \"[--c-warnings-as-errors] [--cxx-warnings-as-errors] [--build-aeron-driver] [--link-samples-client-shared] [--build-archive-api] [--skip-rmdir] [--slow-system-tests] [--no-system-tests] [--debug-build] [--sanitise-build]  [--gradle-wrapper path_to_gradle] [--help]\"\n    }\n    elseif ($args -eq \"--cmake-extra-args\")\n    {\n        if ($i + 1 -eq $Args.count)\n        {\n            throw \"--cmake-extra-args requires a parameter\"\n        }\n\n        $nextArg = $Args[$i + 1]\n        $CmakeExtraArgs = if ($CmakeExtraArgs) { \"$CmakeExtraArgs $nextArg\" } else { $nextArg }\n        $i++\n    }\n    elseif ($arg -eq \"--c-warnings-as-errors\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DC_WARNINGS_AS_ERRORS=ON\"\n    }\n    elseif ($arg -eq \"--cxx-warnings-as-errors\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DCXX_WARNINGS_AS_ERRORS=ON\"\n    }\n    elseif ($arg -eq \"--cxx-hide-deprecation-message\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DAERON_HIDE_DEPRECATION_MESSAGE=ON\"\n    }\n    elseif ($arg -eq \"--link-samples-client-shared\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DLINK_SAMPLES_CLIENT_SHARED=ON\"\n    }\n    elseif ($arg -eq \"--skip-rmdir\")\n    {\n        $DeleteBuildDir = $false\n    }\n    elseif ($arg -eq \"--no-tests\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DAERON_TESTS=OFF\"\n    }\n    elseif ($arg -eq \"--no-system-tests\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DAERON_SYSTEM_TESTS=OFF\"\n    }\n    elseif ($arg -eq \"--no-unit-tests\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DAERON_UNIT_TESTS=OFF\"\n    }\n    elseif ($arg -eq \"--slow-system-tests\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DAERON_SLOW_SYSTEM_TESTS=ON\"\n    }\n    elseif ($arg -eq \"--debug-build\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DCMAKE_BUILD_TYPE=Debug\"\n        $BuildConfig = \"Debug\"\n        Write-Host \"Enabling debug build\"\n    }\n    elseif ($arg -eq \"--relwithdebinfo-build\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DCMAKE_BUILD_TYPE=RelWithDebInfo\"\n        $BuildConfig = \"RelWithDebInfo\"\n        Write-Host \"Enabling release with debug info build\"\n    }\n    elseif ($arg -eq \"--compiler-optimization-level\" -or $arg -eq \"--compiler-optimisation-level\")\n    {\n        if ($i + 1 -eq $Args.count)\n        {\n            throw \"--compiler-optimization-level requires a parameter\"\n        }\n        $nextArg = $Args[$i + 1]\n\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DAERON_COMPILER_OPTIMIZATION_LEVEL=$nextArg\"\n        Write-Host \"Setting compiler optimisation level to: /O$nextArg\"\n        $i++\n    }\n    elseif ($arg -eq \"--sanitise-build\")\n    {\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DSANITISE_BUILD=ON\"\n    }\n    elseif ($arg -eq \"--gradle-wrapper\")\n    {\n        if ($i + 1 -eq $Args.count)\n        {\n            throw \"--gradle-wrapper requires a parameter\"\n        }\n        $nextArg = $Args[$i + 1]\n\n        $CmakeExtraArgs = Add-Arg $CmakeExtraArgs \"-DGRADLE_WRAPPER=$nextArg\"\n        Write-Host \"Setting -DGRADLE_WRAPPER=$nextArg\"\n        $i++\n    }\n    elseif ($arg -eq \"--no-parallel\")\n    {\n        $CmakeBuildParallelLevel = 1\n        Write-Host \"Disabling parallel build\"\n    }\n    elseif ($arg -eq \"--parallel-cpus\")\n    {\n        if ($i + 1 -eq $Args.count)\n        {\n            throw \"--gradle-wrapper requires a parameter\"\n        }\n        $nextArg = $Args[$i + 1]\n\n        $CmakeBuildParallelLevel = $nextArg\n        Write-Host \"Using $CmakeBuildParallelLevel cpus\"\n        $i++\n    }\n    else\n    {\n        Write-Error \"Unknown option $arg\"\n        throw \"Use --help for help\"\n    }\n}\n\n$BuildDir = \"$PSScriptRoot\\$BuildConfig\"\n$SourceDir = \"$PSScriptRoot\\..\"\n$CMakeVersion = \"4.2.1\"\n$CMakeDirName = \"cmake-$CMakeVersion-windows-x86_64\"\n$CMakeArchive = \"$CMakeDirName.zip\"\n$CMakePath = \"$PSScriptRoot\\$CMakeDirName\"\n$OldPath = $env:Path\n\ntry\n{\n    if (-not (Test-Path $CMakePath))\n    {\n        Write-Host \"Installing $CMakeArchive ...\"\n\n        $client = New-Object System.Net.WebClient\n        $client.DownloadFile(\"https://github.com/Kitware/CMake/releases/download/v$CMakeVersion/$CMakeArchive\", \"$PSScriptRoot\\$CMakeArchive\")\n\n        Push-Location $PSScriptRoot\n        Expand-Archive -LiteralPath \"$CMakeArchive\" -DestinationPath \"$PSScriptRoot\"\n        Remove-Item \"$CMakeArchive\"\n        Pop-Location\n\n        Write-Host \"Success: $CMakePath\"\n    }\n\n    if ((Test-Path $BuildDir) -and ($DeleteBuildDir))\n    {\n        Remove-Item -Path $BuildDir -Force -Recurse\n    }\n\n    if (-not (Test-Path $BuildDir))\n    {\n        [void](New-Item -Path $BuildDir -Type Directory -Force)\n    }\n\n    Push-Location -Path $BuildDir\n\n    $vsPath = &(Join-Path ${env:ProgramFiles(x86)} \"\\Microsoft Visual Studio\\Installer\\vswhere.exe\") -property installationpath\n    Write-Host $vsPath\n    Import-Module (Join-Path $vsPath \"Common7\\Tools\\Microsoft.VisualStudio.DevShell.dll\")\n    Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation\n\n    $env:Path = \"$CMakePath\\bin;$env:Path\"\n\n    # need to split single string into an array of multiple arguments before passing to cmake\n    $CmakeExtraArgs = $CmakeExtraArgs.split(' ')\n\n    cmake $CmakeExtraArgs $SourceDir\n    cmake --build . --config $BuildConfig --parallel $CmakeBuildParallelLevel\n    if (-not $?)\n    {\n        Write-Host \"Compile Failed\"\n        Exit 1\n    }\n\n    ctest -C $BuildConfig --output-on-failure --timeout 2000\n}\nfinally\n{\n    Pop-Location\n    $env:Path = $OldPath\n}\n\n\n"
  },
  {
    "path": "cppbuild/rocky/Dockerfile",
    "content": "ARG VERSION=\"9\"\nFROM rockylinux:${VERSION} AS builder\n\nRUN yum update -y && yum install -y https://cdn.azul.com/zulu/bin/zulu-repo-1.0.0-1.noarch.rpm && \\\n    yum update -y && yum install -y \\\n    tar \\\n    zlib-devel \\\n    libuuid-devel \\\n    git \\\n    findutils \\\n    openssl-devel \\\n    procps-ng \\\n    zulu17-jdk\n\nRUN yum groupinstall 'Development Tools' -y\n\nENV JAVA_HOME=/usr/lib/jvm/java-17-zulu-openjdk \\\n    BUILD_JAVA_HOME=/usr/lib/jvm/java-17-zulu-openjdk \\\n    BUILD_JAVA_VERSION=17 \\\n    GRADLE_OPTS=\"-Dorg.gradle.daemon=false -Dorg.gradle.java.installations.auto-detect=false -Dorg.gradle.warning.mode=fail\"\n\nARG USER_ID=\"1000\"\nARG GROUP_ID=\"1000\"\nRUN groupadd --gid $GROUP_ID --non-unique --system athena\nRUN adduser --uid $USER_ID --system --create-home --gid $GROUP_ID athena\n\nUSER athena\nWORKDIR /opt/aeron\n\nFROM builder AS essentials-test\nENTRYPOINT [\"cppbuild/cppbuild\", \"--c-warnings-as-errors\", \"--cxx-warnings-as-errors\", \"--package\"]\n"
  },
  {
    "path": "cppbuild/rocky-docker-build",
    "content": "#!/usr/bin/env bash\n\nSOURCE_DIR=\"$(pwd)\"\n\ndocker build --tag rocky-aeron \\\n  --build-arg USER_ID=\"$(id -u)\" \\\n  --build-arg GROUP_ID=\"$(id -g)\" \\\n  --target essentials-test \\\n  \"${SOURCE_DIR}/cppbuild/rocky\"\n\ndocker run --rm --shm-size=1G --network host \\\n  --volume=\"${SOURCE_DIR}\":/opt/aeron \\\n  --volume=\"$(realpath ~/.gradle)\":/home/athena/.gradle \\\n  rocky-aeron\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nagrona = \"2.4.0\"\nasciidoctorj = \"2.5.13\"\nasm = \"9.9.1\"\nbyteBuddy = \"1.18.7\"\ncheckstyle = \"12.3.1\"\ncommons-codec = \"1.15\"\ncommons-lang3 = \"3.8.1\"\nfindbugs = \"3.0.1\"\nhamcrest = \"3.0\"\nhdrHistogram = \"2.2.2\"\nhttpcore = \"4.4.14\"\njgit = \"7.3.0.202506031305-r\"\njson = \"20250517\"\njunit = \"6.0.3\"\nmockito = \"5.23.0\"\nplexus = \"3.3.0\"\nsbe = \"1.37.1\"\nshadow = \"9.4.0\"\nversions = \"0.53.0\"\n\n[libraries]\nagrona = { group = \"org.agrona\", name = \"agrona\", version.ref = \"agrona\" }\nasciidoctorj = { group = \"org.asciidoctor\", name = \"asciidoctorj\", version.ref = \"asciidoctorj\" }\nasm = { group = \"org.ow2.asm\", name = \"asm\", version.ref = \"asm\" }\nasm-analysis = { group = \"org.ow2.asm\", name = \"asm-analysis\", version.ref = \"asm\" }\nasm-commons = { group = \"org.ow2.asm\", name = \"asm-commons\", version.ref = \"asm\" }\nasm-tree = { group = \"org.ow2.asm\", name = \"asm-tree\", version.ref = \"asm\" }\nasm-util = { group = \"org.ow2.asm\", name = \"asm-util\", version.ref = \"asm\" }\nbyteBuddy = { group = \"net.bytebuddy\", name = \"byte-buddy\", version.ref = \"byteBuddy\" }\nbyteBuddy-agent = { group = \"net.bytebuddy\", name = \"byte-buddy-agent\", version.ref = \"byteBuddy\" }\ncommons-codec = { group = \"commons-codec\", name = \"commons-codec\", version.ref = \"commons-codec\" }\ncommons-lang3 = { group = \"org.apache.commons\", name = \"commons-lang3\", version.ref = \"commons-lang3\" }\nfindbugs-annotations = { group = \"com.google.code.findbugs\", name = \"findbugs-annotations\", version.ref= \"findbugs\" }\nhamcrest = { group = \"org.hamcrest\", name = \"hamcrest\", version.ref = \"hamcrest\" }\nhdrHistogram = { group = \"org.hdrhistogram\", name = \"HdrHistogram\", version.ref = \"hdrHistogram\" }\nhttpcore = { group = \"org.apache.httpcomponents\", name = \"httpcore\", version.ref = \"httpcore\" }\njgit = { group = \"org.eclipse.jgit\", name = \"org.eclipse.jgit\", version.ref = \"jgit\" }\njson = { group = \"org.json\", name = \"json\", version.ref = \"json\" }\njunit-bom = { group = \"org.junit\", name = \"junit-bom\", version.ref = \"junit\" }\nmockito = { group = \"org.mockito\", name = \"mockito-core\", version.ref = \"mockito\" }\nplexus-utils = { group = \"org.codehaus.plexus\", name = \"plexus-utils\", version.ref = \"plexus\" }\nsbe = { group = \"uk.co.real-logic\", name = \"sbe-tool\", version.ref = \"sbe\" }\n\n[plugins]\nshadow = { id = \"com.gradleup.shadow\", version.ref = \"shadow\" }\nversions = { id = \"com.github.ben-manes.versions\", version.ref = \"versions\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.4.1-all.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.java.installations.auto-detect=false\norg.gradle.java.installations.auto-download=false\norg.gradle.java.installations.fromEnv=BUILD_JAVA_HOME\n\norg.gradle.logging.level=lifecycle\norg.gradle.warning.mode=all\n\n# HTTP timeouts for Gradle\nsystemProp.org.gradle.internal.http.connectionTimeout=300000\nsystemProp.org.gradle.internal.http.socketTimeout=300000\nsystemProp.org.gradle.internal.repository.max.retries=1\nsystemProp.org.gradle.internal.publish.checksums.insecure=true"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "run-ci-tests.sh",
    "content": "#!/usr/bin/env bash\n\nif [ -z \"$AERON_GITHUB_PAT\" ]\nthen\n  echo \"Please set AERON_GITHUB_PAT environment variable to contain your token\"\n  exit 1\nfi\n\nevent_type=run-commit-tests\n\nfor option in \"$@\"\ndo\n  case ${option} in\n    -s|--slow)\n      event_type=run-slow-tests\n      shift\n      ;;\n    -c|--commit)\n      shift\n      ;;\n    *)\n      echo \"$0 [-s|--slow-tests] (run slow tests) [-c|--commit] (run commit tests) default: commit tests\"\n      exit\n      ;;\n  esac\ndone\n\necho \"Sending repository_dispatch, event_type: ${event_type}\"\n\ncurl -v -H \"Accept: application/vnd.github.everest-preview+json\" \\\n    -H \"Authorization: token ${AERON_GITHUB_PAT}\" \\\n    --request POST \\\n    --data \"{\\\"event_type\\\": \\\"${event_type}\\\"}\" \\\n    https://api.github.com/repos/aeron-io/aeron/dispatches"
  },
  {
    "path": "settings.gradle",
    "content": "include (\n    'aeron-annotations',\n    'aeron-client',\n    'aeron-driver',\n    'aeron-archive',\n    'aeron-cluster',\n    'aeron-agent',\n    'aeron-samples',\n    'aeron-system-tests',\n    'aeron-test-support',\n    'aeron-all')\n\n\n"
  },
  {
    "path": "version.txt",
    "content": "1.51.0-SNAPSHOT\n"
  }
]